Skip to content

Commit

Permalink
simpletrace: added simplified Analyzer2 class
Browse files Browse the repository at this point in the history
By moving the dynamic argument construction to keyword-arguments,
we can remove all of the specialized handling, and streamline it.
If a tracing method wants to access these, they can define the
kwargs, or ignore it be placing `**kwargs` at the end of the
function's arguments list.

Added deprecation warning to Analyzer class to make users aware
of the Analyzer2 class. No removal date is planned.

Signed-off-by: Mads Ynddal <m.ynddal@samsung.com>
Message-id: 20230926103436.25700-13-mads@ynddal.dk
Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
  • Loading branch information
Baekalfen authored and stefanhaRH committed Sep 26, 2023
1 parent d1f89c2 commit 3470fef
Showing 1 changed file with 75 additions and 23 deletions.
98 changes: 75 additions & 23 deletions scripts/simpletrace.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,11 @@
import sys
import struct
import inspect
import warnings
from tracetool import read_events, Event
from tracetool.backend.simple import is_string

__all__ = ['Analyzer', 'process', 'run']
__all__ = ['Analyzer', 'Analyzer2', 'process', 'run']

# This is the binary format that the QEMU "simple" trace backend
# emits. There is no specification documentation because the format is
Expand Down Expand Up @@ -130,7 +131,9 @@ def read_trace_records(events, fobj, read_header):
yield (event_mapping[event_name], event_name, timestamp_ns, pid) + tuple(args)

class Analyzer:
"""A trace file analyzer which processes trace records.
"""[Deprecated. Refer to Analyzer2 instead.]
A trace file analyzer which processes trace records.
An analyzer can be passed to run() or process(). The begin() method is
invoked, then each trace record is processed, and finally the end() method
Expand Down Expand Up @@ -188,6 +191,11 @@ def _build_fn(self, event):
return lambda _, rec: fn(*rec[3:3 + event_argcount])

def _process_event(self, rec_args, *, event, event_id, timestamp_ns, pid, **kwargs):
warnings.warn(
"Use of deprecated Analyzer class. Refer to Analyzer2 instead.",
DeprecationWarning,
)

if not hasattr(self, '_fn_cache'):
# NOTE: Cannot depend on downstream subclasses to have
# super().__init__() because of legacy.
Expand All @@ -211,6 +219,56 @@ def __exit__(self, exc_type, exc_val, exc_tb):
self.end()
return False

class Analyzer2(Analyzer):
"""A trace file analyzer which processes trace records.
An analyzer can be passed to run() or process(). The begin() method is
invoked, then each trace record is processed, and finally the end() method
is invoked. When Analyzer is used as a context-manager (using the `with`
statement), begin() and end() are called automatically.
If a method matching a trace event name exists, it is invoked to process
that trace record. Otherwise the catchall() method is invoked.
The methods are called with a set of keyword-arguments. These can be ignored
using `**kwargs` or defined like any keyword-argument.
The following keyword-arguments are available, but make sure to have an
**kwargs to allow for unmatched arguments in the future:
event: Event object of current trace
event_id: The id of the event in the current trace file
timestamp_ns: The timestamp in nanoseconds of the trace
pid: The process id recorded for the given trace
Example:
The following method handles the runstate_set(int new_state) trace event::
def runstate_set(self, new_state, **kwargs):
...
The method can also explicitly take a timestamp keyword-argument with the
trace event arguments::
def runstate_set(self, new_state, *, timestamp_ns, **kwargs):
...
Timestamps have the uint64_t type and are in nanoseconds.
The pid can be included in addition to the timestamp and is useful when
dealing with traces from multiple processes:
def runstate_set(self, new_state, *, timestamp_ns, pid, **kwargs):
...
"""

def catchall(self, *rec_args, event, timestamp_ns, pid, event_id, **kwargs):
"""Called if no specific method for processing a trace event has been found."""
pass

def _process_event(self, rec_args, *, event, **kwargs):
fn = getattr(self, event.name, self.catchall)
fn(*rec_args, event=event, **kwargs)

def process(events, log, analyzer, read_header=True):
"""Invoke an analyzer on each event in a log.
Args:
Expand Down Expand Up @@ -278,30 +336,24 @@ def run(analyzer):
process(events_fobj, log_fobj, analyzer, read_header=not no_header)

if __name__ == '__main__':
class Formatter(Analyzer):
class Formatter2(Analyzer2):
def __init__(self):
self.last_timestamp = None

def catchall(self, event, rec):
timestamp = rec[1]
if self.last_timestamp is None:
self.last_timestamp = timestamp
delta_ns = timestamp - self.last_timestamp
self.last_timestamp = timestamp

fields = [event.name, '%0.3f' % (delta_ns / 1000.0),
'pid=%d' % rec[2]]
i = 3
for type, name in event.args:
if is_string(type):
fields.append('%s=%s' % (name, rec[i]))
else:
fields.append('%s=0x%x' % (name, rec[i]))
i += 1
print(' '.join(fields))
self.last_timestamp_ns = None

def catchall(self, *rec_args, event, timestamp_ns, pid, event_id):
if self.last_timestamp_ns is None:
self.last_timestamp_ns = timestamp_ns
delta_ns = timestamp_ns - self.last_timestamp_ns
self.last_timestamp_ns = timestamp_ns

fields = [
f'{name}={r}' if is_string(type) else f'{name}=0x{r:x}'
for r, (type, name) in zip(rec_args, event.args)
]
print(f'{event.name} {delta_ns / 1000:0.3f} {pid=} ' + ' '.join(fields))

try:
run(Formatter())
run(Formatter2())
except SimpleException as e:
sys.stderr.write(str(e) + "\n")
sys.exit(1)

0 comments on commit 3470fef

Please sign in to comment.