ansible-trace eingebaut (#283)
Co-authored-by: Michael Grote <michael.grote@posteo.de> Reviewed-on: mg/ansible#283 Co-authored-by: mg <michael.grote@posteo.de> Co-committed-by: mg <michael.grote@posteo.de>
This commit is contained in:
parent
14fef2df59
commit
5c67dc8e30
3 changed files with 170 additions and 0 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -4,3 +4,4 @@ id_rsa_ansible_user
|
||||||
id_rsa_ansible_user_pub
|
id_rsa_ansible_user_pub
|
||||||
plugins/lookup/__pycache__/**
|
plugins/lookup/__pycache__/**
|
||||||
plugins/callback/__pycache__/
|
plugins/callback/__pycache__/
|
||||||
|
trace/**json
|
||||||
|
|
|
@ -12,6 +12,7 @@ gathering = smart
|
||||||
#display_skipped_hosts = yes # dito
|
#display_skipped_hosts = yes # dito
|
||||||
callback_plugins = ./plugins/callback
|
callback_plugins = ./plugins/callback
|
||||||
# python3 -m ara.setup.callback_plugins
|
# python3 -m ara.setup.callback_plugins
|
||||||
|
callbacks_enabled = mhansen.ansible_trace.trace # https://github.com/mhansen/ansible-trace
|
||||||
[inventory]
|
[inventory]
|
||||||
|
|
||||||
[privilege_escalation]
|
[privilege_escalation]
|
||||||
|
|
168
plugins/callback/trace.py
Normal file
168
plugins/callback/trace.py
Normal file
|
@ -0,0 +1,168 @@
|
||||||
|
# Copyright 2021 Google LLC
|
||||||
|
#
|
||||||
|
# This program is free software; you can redistribute it and/or
|
||||||
|
# modify it under the terms of the GNU General Public License
|
||||||
|
# version 3 as published by the Free Software Foundation.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
|
||||||
|
|
||||||
|
DOCUMENTATION = '''
|
||||||
|
name: trace
|
||||||
|
type: aggregate
|
||||||
|
short_description: write playbook output to Chrome's Trace Event Format file
|
||||||
|
description:
|
||||||
|
- This callback writes playbook output to Chrome Trace Event Format file.
|
||||||
|
author: Mark Hansen (@mhansen)
|
||||||
|
options:
|
||||||
|
trace_output_dir:
|
||||||
|
name: output dir
|
||||||
|
default: ./trace
|
||||||
|
description: Directory to write files to.
|
||||||
|
env:
|
||||||
|
- name: TRACE_OUTPUT_DIR
|
||||||
|
hide_task_arguments:
|
||||||
|
name: Hide the arguments for a task
|
||||||
|
default: False
|
||||||
|
description: Hide the arguments for a task
|
||||||
|
env:
|
||||||
|
- name: HIDE_TASK_ARGUMENTS
|
||||||
|
requirements:
|
||||||
|
- enable in configuration
|
||||||
|
'''
|
||||||
|
|
||||||
|
import atexit
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import time
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from datetime import datetime
|
||||||
|
from typing import Dict, TextIO
|
||||||
|
|
||||||
|
from ansible.plugins.callback import CallbackBase
|
||||||
|
|
||||||
|
|
||||||
|
class CallbackModule(CallbackBase):
|
||||||
|
"""
|
||||||
|
This callback traces execution time of tasks to Trace Event Format.
|
||||||
|
|
||||||
|
This plugin makes use of the following environment variables:
|
||||||
|
TRACE_OUTPUT_DIR (optional): Directory to write JSON files to.
|
||||||
|
Default: ./trace
|
||||||
|
TRACE_HIDE_TASK_ARGUMENTS (optional): Hide the arguments for a task
|
||||||
|
Default: False
|
||||||
|
"""
|
||||||
|
|
||||||
|
CALLBACK_VERSION = 2.0
|
||||||
|
CALLBACK_TYPE = 'aggregate'
|
||||||
|
CALLBACK_NAME = 'trace'
|
||||||
|
CALLBACK_NEEDS_ENABLED = True
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
super(CallbackModule, self).__init__()
|
||||||
|
|
||||||
|
self._output_dir: str = os.getenv('TRACE_OUTPUT_DIR', os.path.join(os.path.expanduser('.'), 'trace'))
|
||||||
|
self._hide_task_arguments: str = os.getenv('TRACE_HIDE_TASK_ARGUMENTS', 'False').lower()
|
||||||
|
self._hosts: Dict[Host] = {}
|
||||||
|
self._next_pid: int = 1
|
||||||
|
self._first: bool = True
|
||||||
|
self._start_date: str = datetime.now().isoformat()
|
||||||
|
self._output_file: str = 'trace-%s.json' % self._start_date
|
||||||
|
|
||||||
|
if not os.path.exists(self._output_dir):
|
||||||
|
os.makedirs(self._output_dir)
|
||||||
|
output_file = os.path.join(self._output_dir, self._output_file)
|
||||||
|
self._f: TextIO = open(output_file, 'w')
|
||||||
|
self._f.write("[\n")
|
||||||
|
|
||||||
|
atexit.register(self._end)
|
||||||
|
|
||||||
|
def _write_event(self, e: Dict):
|
||||||
|
if not self._first:
|
||||||
|
self._f.write(",\n")
|
||||||
|
self._first = False
|
||||||
|
json.dump(e, self._f, sort_keys=True, indent=2) # sort for reproducibility
|
||||||
|
self._f.flush()
|
||||||
|
|
||||||
|
def v2_runner_on_start(self, host, task):
|
||||||
|
uuid = task._uuid
|
||||||
|
name = task.get_name().strip()
|
||||||
|
|
||||||
|
args = None
|
||||||
|
if not task.no_log and self._hide_task_arguments == 'false':
|
||||||
|
args = task.args
|
||||||
|
|
||||||
|
host_uuid = host._uuid
|
||||||
|
if host_uuid not in self._hosts:
|
||||||
|
pid = self._next_pid
|
||||||
|
self._hosts[host_uuid] = Host(pid=pid, name=host.name)
|
||||||
|
self._next_pid += 1
|
||||||
|
# https://docs.google.com/document/d/1CvAClvFfyA5R-PhYUmn5OOQtYMH4h6I0nSsKchNAySU/preview#bookmark=id.iycbnb4z7i9g
|
||||||
|
self._write_event({
|
||||||
|
"name": "process_name",
|
||||||
|
"pid": pid,
|
||||||
|
"cat": "process",
|
||||||
|
"ph": "M",
|
||||||
|
"args": {
|
||||||
|
"name": host.name,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
# See "Duration Events" in:
|
||||||
|
# https://docs.google.com/document/d/1CvAClvFfyA5R-PhYUmn5OOQtYMH4h6I0nSsKchNAySU/preview#heading=h.nso4gcezn7n1
|
||||||
|
self._write_event({
|
||||||
|
"name": name,
|
||||||
|
"cat": "runner",
|
||||||
|
"ph": "B", # Begin
|
||||||
|
"ts": time.time_ns() / 1000,
|
||||||
|
"pid": self._hosts[host_uuid].pid,
|
||||||
|
"id": abs(hash(uuid)),
|
||||||
|
"args": {
|
||||||
|
"args": args,
|
||||||
|
"task": name,
|
||||||
|
"path": task.get_path(),
|
||||||
|
"host": host.name,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
def _end_span(self, result, status: str):
|
||||||
|
task = result._task
|
||||||
|
uuid = task._uuid
|
||||||
|
# See "Duration Events" in:
|
||||||
|
# https://docs.google.com/document/d/1CvAClvFfyA5R-PhYUmn5OOQtYMH4h6I0nSsKchNAySU/preview#heading=h.nso4gcezn7n1
|
||||||
|
self._write_event({
|
||||||
|
"name": task.get_name().strip(),
|
||||||
|
"cat": "runner",
|
||||||
|
"id": abs(hash(uuid)),
|
||||||
|
"ph": "E", # End
|
||||||
|
"ts": time.time_ns() / 1000,
|
||||||
|
"pid": self._hosts[result._host._uuid].pid,
|
||||||
|
"args": {
|
||||||
|
"status": status,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
def v2_runner_on_ok(self, result):
|
||||||
|
self._end_span(result, status="ok")
|
||||||
|
|
||||||
|
def v2_runner_on_unreachable(self, result):
|
||||||
|
self._end_span(result, 'unreachable')
|
||||||
|
|
||||||
|
def v2_runner_on_failed(self, result, ignore_errors=False):
|
||||||
|
self._end_span(result, status='failed')
|
||||||
|
|
||||||
|
def v2_runner_on_skipped(self, result):
|
||||||
|
self._end_span(result, status='skipped')
|
||||||
|
|
||||||
|
def _end(self):
|
||||||
|
self._f.write("\n]")
|
||||||
|
self._f.close()
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Host:
|
||||||
|
name: str
|
||||||
|
pid: int
|
Loading…
Reference in a new issue