|
@@ -12,10 +12,11 @@
|
|
import sys
|
|
import sys
|
|
import struct
|
|
import struct
|
|
import inspect
|
|
import inspect
|
|
|
|
+import warnings
|
|
from tracetool import read_events, Event
|
|
from tracetool import read_events, Event
|
|
from tracetool.backend.simple import is_string
|
|
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
|
|
# This is the binary format that the QEMU "simple" trace backend
|
|
# emits. There is no specification documentation because the format is
|
|
# emits. There is no specification documentation because the format is
|
|
@@ -130,7 +131,9 @@ def read_trace_records(events, fobj, read_header):
|
|
yield (event_mapping[event_name], event_name, timestamp_ns, pid) + tuple(args)
|
|
yield (event_mapping[event_name], event_name, timestamp_ns, pid) + tuple(args)
|
|
|
|
|
|
class Analyzer:
|
|
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
|
|
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
|
|
invoked, then each trace record is processed, and finally the end() method
|
|
@@ -188,6 +191,11 @@ def _build_fn(self, event):
|
|
return lambda _, rec: fn(*rec[3:3 + event_argcount])
|
|
return lambda _, rec: fn(*rec[3:3 + event_argcount])
|
|
|
|
|
|
def _process_event(self, rec_args, *, event, event_id, timestamp_ns, pid, **kwargs):
|
|
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'):
|
|
if not hasattr(self, '_fn_cache'):
|
|
# NOTE: Cannot depend on downstream subclasses to have
|
|
# NOTE: Cannot depend on downstream subclasses to have
|
|
# super().__init__() because of legacy.
|
|
# super().__init__() because of legacy.
|
|
@@ -211,6 +219,56 @@ def __exit__(self, exc_type, exc_val, exc_tb):
|
|
self.end()
|
|
self.end()
|
|
return False
|
|
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):
|
|
def process(events, log, analyzer, read_header=True):
|
|
"""Invoke an analyzer on each event in a log.
|
|
"""Invoke an analyzer on each event in a log.
|
|
Args:
|
|
Args:
|
|
@@ -278,30 +336,24 @@ def run(analyzer):
|
|
process(events_fobj, log_fobj, analyzer, read_header=not no_header)
|
|
process(events_fobj, log_fobj, analyzer, read_header=not no_header)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
if __name__ == '__main__':
|
|
- class Formatter(Analyzer):
|
|
|
|
|
|
+ class Formatter2(Analyzer2):
|
|
def __init__(self):
|
|
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:
|
|
try:
|
|
- run(Formatter())
|
|
|
|
|
|
+ run(Formatter2())
|
|
except SimpleException as e:
|
|
except SimpleException as e:
|
|
sys.stderr.write(str(e) + "\n")
|
|
sys.stderr.write(str(e) + "\n")
|
|
sys.exit(1)
|
|
sys.exit(1)
|