[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[Minios-devel] [UNIKRAFT PATCH 6/8] support/scripts: implement trace buffer parcing



With this patch trace.py can print the data collected in the
tracefile. And two more use cases are covered for convince:

  - 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/trace.py          |  23 ++++-
 support/scripts/uk-gdb.py         |   9 +-
 support/scripts/unikraft/trace.py | 151 ++++++++++++++++++++++++++++++
 3 files changed, 180 insertions(+), 3 deletions(-)

diff --git a/support/scripts/trace.py b/support/scripts/trace.py
index 49b469a5..5632d39f 100755
--- a/support/scripts/trace.py
+++ b/support/scripts/trace.py
@@ -36,6 +36,8 @@ import click
 import os, sys
 import pickle
 import subprocess
+from tabulate import tabulate
+
 scripts_dir = os.path.dirname(os.path.realpath(__file__))
 sys.path.append(scripts_dir)
 
@@ -64,6 +66,19 @@ def parse_tf(trace_file):
 
     return trace.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(),
@@ -73,8 +88,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):
@@ -99,6 +117,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()
diff --git a/support/scripts/uk-gdb.py b/support/scripts/uk-gdb.py
index 6e9b3930..9ae8ee66 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 = trace.sample_parser(trace.get_keyvals(elf),
+                                      trace.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/unikraft/trace.py 
b/support/scripts/unikraft/trace.py
index 407f47e3..f74bcf41 100644
--- a/support/scripts/unikraft/trace.py
+++ b/support/scripts/unikraft/trace.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 tracepoint:
+    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] = tracepoint(name, args_nr, fmt, sizes, types)
+
+    return ret
+
 def get_tp_sections(elf):
     f = tempfile.NamedTemporaryFile()
     objcopy_cmd  = 'objcopy -O binary %s ' % elf
-- 
2.19.2


_______________________________________________
Minios-devel mailing list
Minios-devel@xxxxxxxxxxxxxxxxxxxx
https://lists.xenproject.org/mailman/listinfo/minios-devel

 


Rackspace

Lists.xenproject.org is hosted with RackSpace, monitoring our
servers 24x7x365 and backed by RackSpace's Fanatical Support®.