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

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



Hi Yuri,

In the subject: s/parcing/parsing .

Please see my comments inline.

On 5/10/19 9:29 PM, Yuri Volchkov wrote:
> With this patch trace.py can print the data collected in the
> tracefile. And two more use cases are covered for convince:

s/for convince/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/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:

It can get confusing on why there are 2 classes for tracepoints,
tracepoint and tp_sample. I think their names should reflect that the
former are the tracepoint definitions and latter are the tracepoint values.

> +    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))
> +

I think it would have been clearer if the parsing alone would have
stayed in its own module. Or at least delimit the sections in the module
with (block) comments.

> +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
> 

_______________________________________________
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®.