|
[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index] [Minios-devel] [UNIKRAFT PATCH v2 6/8] support/scripts: implement trace buffer parsing
With this patch trace.py can print the data collected in the
tracefile. And two more use cases are covered for convenience:
- If --list specified trace.py will parse freshly fetched tracefile
and print out what is collected
- Gdb command 'uk trace' parses and prints data without intermediate
trace files
Signed-off-by: Yuri Volchkov <yuri.volchkov@xxxxxxxxx>
---
support/scripts/uk-gdb.py | 9 +-
support/scripts/uk_trace/parse.py | 151 ++++++++++++++++++++++++++++++
support/scripts/uk_trace/trace.py | 22 ++++-
3 files changed, 179 insertions(+), 3 deletions(-)
diff --git a/support/scripts/uk-gdb.py b/support/scripts/uk-gdb.py
index e4b0038d..14aa9aaf 100644
--- a/support/scripts/uk-gdb.py
+++ b/support/scripts/uk-gdb.py
@@ -98,8 +98,13 @@ class uk_trace(gdb.Command):
gdb.Command.__init__(self, 'uk trace',
gdb.COMMAND_USER, gdb.COMPLETE_COMMAND, True)
def invoke(self, arg, from_tty):
- # TODO
- pass
+ elf = gdb.current_progspace().filename
+ samples = parse.sample_parser(parse.get_keyvals(elf),
+ parse.get_tp_sections(elf),
+ get_trace_buffer(), PTR_SIZE)
+ for sample in samples:
+ print(sample)
+
class uk_trace_save(gdb.Command):
def __init__(self):
diff --git a/support/scripts/uk_trace/parse.py
b/support/scripts/uk_trace/parse.py
index 407f47e3..8142bc45 100644
--- a/support/scripts/uk_trace/parse.py
+++ b/support/scripts/uk_trace/parse.py
@@ -31,10 +31,161 @@
#
# THIS HEADER MAY NOT BE EXTRACTED OR MODIFIED IN ANY WAY.
+import struct
+import sys
import subprocess
import re
import tempfile
+TP_HEADER_MAGIC = 'TRhd'
+TP_DEF_MAGIC = 'TPde'
+UK_TRACE_ARG_INT = 0
+UK_TRACE_ARG_STRING = 1
+# Not sure why gcc aligns data on 32 bytes
+__STRUCT_ALIGNMENT = 32
+
+FORMAT_VERSION = 1
+
+def align_down(v, alignment):
+ return v & ~(alignment - 1)
+
+def align_up(v, alignment):
+ return align_down(v + alignment - 1, alignment)
+
+class tp_sample:
+ def __init__(self, tp, time, args):
+ self.tp = tp
+ self.args = args
+ self.time = time
+ def __str__(self):
+ return (("%016d %s: " % (self.time, self.tp.name)) +
+ (self.tp.fmt % self.args))
+ def tabulate_fmt(self):
+ return [self.time, self.tp.name, (self.tp.fmt % self.args)]
+
+class EndOfBuffer(Exception):
+ pass
+
+# Parsing of trace buffer is designed to be used without gdb. If ever
+# Unikraft will have a bare metal port, it would be complicated to use
+# gdb for fetching and parsing runtime data.
+#
+# However, it is possible anyways to get an address of the statically
+# allocated trace buffer using gdb/nm/objdump. And we can use some
+# different mechanism to copy memory from the running instance of
+# Unikraft (which is yet to be designed).
+#
+# Similar considerations are applied to parsing trace point
+# definitions. We can do that disregarding if it is possible to attach
+# gdb to a running instance or not
+class sample_parser:
+ def __init__(self, keyvals, tp_defs_data, trace_buff, ptr_size):
+ if (int(keyvals['format_version']) > FORMAT_VERSION):
+ print("Warning: Version of trace format is more recent",
+ file=sys.stderr)
+ self.data = unpacker(trace_buff)
+ self.tps = get_tp_definitions(tp_defs_data, ptr_size)
+ def __iter__(self):
+ self.data.pos = 0
+ return self
+ def __next__(self):
+ try:
+ # TODO: generate format. Cookie can be 4 bytes long on other
+ # platforms
+ magic,size,time,cookie = self.data.unpack('4sLQQ')
+ except EndOfBuffer:
+ raise StopIteration
+
+ magic = magic.decode()
+ if (magic != TP_HEADER_MAGIC):
+ raise StopIteration
+
+ tp = self.tps[cookie]
+ args = []
+ for i in range(tp.args_nr):
+ if tp.types[i] == UK_TRACE_ARG_STRING:
+ args += [self.data.unpack_string()]
+ else:
+ args += [self.data.unpack_int(tp.sizes[i])]
+
+ return tp_sample(tp, time, tuple(args))
+
+class unpacker:
+ def __init__(self, data):
+ self.data = data
+ self.pos = 0
+ self.endian = '<'
+ def unpack(self, fmt):
+ fmt = self.endian + fmt
+ size = struct.calcsize(fmt)
+ if size > len(self.data) - self.pos:
+ raise EndOfBuffer("No data in buffer for unpacking %s bytes" %
size)
+ cur = self.data[self.pos:self.pos + size]
+ self.pos += size
+ return struct.unpack(fmt, cur)
+ def unpack_string(self):
+ strlen, = self.unpack('B')
+ fmt = '%ds' % strlen
+ ret, = self.unpack(fmt)
+ return ret.decode()
+ def unpack_int(self, size):
+ if size == 1:
+ fmt = 'B'
+ elif size == 2:
+ fmt = 'H'
+ elif size == 4:
+ fmt = 'I'
+ elif size == 8:
+ fmt = 'Q'
+ ret, = self.unpack(fmt)
+ return ret
+ def align_pos(self, alignment):
+ self.pos = align_up(self.pos, alignment)
+
+class tp_definition:
+ def __init__(self, name, args_nr, fmt, sizes, types):
+ self.name = name
+ self.args_nr = args_nr
+ self.fmt = fmt
+ self.sizes = sizes
+ self.types = types
+
+ def __str__(self):
+ return '%s %s' % (self.name, self.fmt)
+
+def get_tp_definitions(tp_data, ptr_size):
+ ptr_fmt = '0x%0' + '%dx' % (ptr_size * 2)
+ data = unpacker(tp_data)
+
+ ret = dict()
+
+ while True:
+ data.align_pos(__STRUCT_ALIGNMENT)
+ try:
+ magic, size, cookie, args_nr, name_len, fmt_len = \
+ data.unpack("4sIQBBB")
+ except EndOfBuffer:
+ break
+
+ magic = magic.decode()
+ if (magic != TP_DEF_MAGIC):
+ raise Exception("Wrong tracepoint definition magic")
+
+ sizes = data.unpack('B' * args_nr)
+ types = data.unpack('B' * args_nr)
+ name,fmt = data.unpack('%ds%ds' % (name_len, fmt_len))
+ # Strange, but terminating '\0' makes a problem if the script
+ # is running in the gdb
+ name = name[:-1].decode()
+ fmt = fmt[:-1].decode()
+
+ # Convert from c-printf format into python one
+ fmt = fmt.replace('%p', ptr_fmt)
+
+ ret[cookie] = tp_definition(name, args_nr, fmt, sizes, types)
+
+ return ret
+
def get_tp_sections(elf):
f = tempfile.NamedTemporaryFile()
objcopy_cmd = 'objcopy -O binary %s ' % elf
diff --git a/support/scripts/uk_trace/trace.py
b/support/scripts/uk_trace/trace.py
index 10c985e5..5fafca63 100755
--- a/support/scripts/uk_trace/trace.py
+++ b/support/scripts/uk_trace/trace.py
@@ -36,6 +36,7 @@ import click
import os, sys
import pickle
import subprocess
+from tabulate import tabulate
import parse
@@ -62,6 +63,19 @@ def parse_tf(trace_file):
return parse.sample_parser(keyvals, tp_defs, trace_buff, ptr_size)
+@cli.command()
+@click.argument('trace_file', type=click.Path(exists=True),
default='tracefile')
+@click.option('--no-tabulate', is_flag=True,
+ help='No pretty printing')
+def list(trace_file, no_tabulate):
+ """Parse binary trace file fetched from Unikraft"""
+ if not no_tabulate:
+ print_data = [x.tabulate_fmt() for x in parse_tf(trace_file)]
+ print(tabulate(print_data, headers=['time', 'tp_name', 'msg']))
+ else:
+ for i in parse_tf(trace_file):
+ print(i)
+
@cli.command()
@click.argument('uk_img', type=click.Path(exists=True))
@click.option('--out', '-o', type=click.Path(),
@@ -71,8 +85,11 @@ def parse_tf(trace_file):
default=':1234', show_default=True,
help='How to connect to the gdb session '+
'(parameters for "target remote" command)')
+@click.option('--list', 'do_list', is_flag=True,
+ default=False,
+ help='Parse the fetched tracefile and list events')
@click.option('--verbose', is_flag=True, default=False)
-def fetch(uk_img, out, remote, verbose):
+def fetch(uk_img, out, remote, do_list, verbose):
"""Fetch binary trace file from Unikraft (using gdb)"""
if os.path.exists(out):
@@ -98,6 +115,9 @@ def fetch(uk_img, out, remote, verbose):
if verbose:
print(_stdout)
+ if do_list:
+ for i in parse_tf(out):
+ print(i)
if __name__ == '__main__':
cli()
--
2.19.2
_______________________________________________
Minios-devel mailing list
Minios-devel@xxxxxxxxxxxxxxxxxxxx
https://lists.xenproject.org/mailman/listinfo/minios-devel
|
![]() |
Lists.xenproject.org is hosted with RackSpace, monitoring our |