| # -*- coding: utf-8 -*- |
| |
| """ |
| Machinery for generating tracing-related intermediate files. |
| """ |
| |
| __author__ = "Lluís Vilanova <vilanova@ac.upc.edu>" |
| __copyright__ = "Copyright 2012-2017, Lluís Vilanova <vilanova@ac.upc.edu>" |
| __license__ = "GPL version 2 or (at your option) any later version" |
| |
| __maintainer__ = "Stefan Hajnoczi" |
| __email__ = "stefanha@redhat.com" |
| |
| |
| import re |
| import sys |
| import weakref |
| |
| import tracetool.format |
| import tracetool.backend |
| import tracetool.transform |
| |
| |
| def error_write(*lines): |
| """Write a set of error lines.""" |
| sys.stderr.writelines("\n".join(lines) + "\n") |
| |
| def error(*lines): |
| """Write a set of error lines and exit.""" |
| error_write(*lines) |
| sys.exit(1) |
| |
| |
| out_lineno = 1 |
| out_filename = '<none>' |
| out_fobj = sys.stdout |
| |
| def out_open(filename): |
| global out_filename, out_fobj |
| out_filename = filename |
| out_fobj = open(filename, 'wt') |
| |
| def out(*lines, **kwargs): |
| """Write a set of output lines. |
| |
| You can use kwargs as a shorthand for mapping variables when formatting all |
| the strings in lines. |
| |
| The 'out_lineno' kwarg is automatically added to reflect the current output |
| file line number. The 'out_next_lineno' kwarg is also automatically added |
| with the next output line number. The 'out_filename' kwarg is automatically |
| added with the output filename. |
| """ |
| global out_lineno |
| output = [] |
| for l in lines: |
| kwargs['out_lineno'] = out_lineno |
| kwargs['out_next_lineno'] = out_lineno + 1 |
| kwargs['out_filename'] = out_filename |
| output.append(l % kwargs) |
| out_lineno += 1 |
| |
| out_fobj.writelines("\n".join(output) + "\n") |
| |
| # We only want to allow standard C types or fixed sized |
| # integer types. We don't want QEMU specific types |
| # as we can't assume trace backends can resolve all the |
| # typedefs |
| ALLOWED_TYPES = [ |
| "int", |
| "long", |
| "short", |
| "char", |
| "bool", |
| "unsigned", |
| "signed", |
| "int8_t", |
| "uint8_t", |
| "int16_t", |
| "uint16_t", |
| "int32_t", |
| "uint32_t", |
| "int64_t", |
| "uint64_t", |
| "void", |
| "size_t", |
| "ssize_t", |
| "uintptr_t", |
| "ptrdiff_t", |
| ] |
| |
| def validate_type(name): |
| bits = name.split(" ") |
| for bit in bits: |
| bit = re.sub("\*", "", bit) |
| if bit == "": |
| continue |
| if bit == "const": |
| continue |
| if bit not in ALLOWED_TYPES: |
| raise ValueError("Argument type '%s' is not allowed. " |
| "Only standard C types and fixed size integer " |
| "types should be used. struct, union, and " |
| "other complex pointer types should be " |
| "declared as 'void *'" % name) |
| |
| class Arguments: |
| """Event arguments description.""" |
| |
| def __init__(self, args): |
| """ |
| Parameters |
| ---------- |
| args : |
| List of (type, name) tuples or Arguments objects. |
| """ |
| self._args = [] |
| for arg in args: |
| if isinstance(arg, Arguments): |
| self._args.extend(arg._args) |
| else: |
| self._args.append(arg) |
| |
| def copy(self): |
| """Create a new copy.""" |
| return Arguments(list(self._args)) |
| |
| @staticmethod |
| def build(arg_str): |
| """Build and Arguments instance from an argument string. |
| |
| Parameters |
| ---------- |
| arg_str : str |
| String describing the event arguments. |
| """ |
| res = [] |
| for arg in arg_str.split(","): |
| arg = arg.strip() |
| if not arg: |
| raise ValueError("Empty argument (did you forget to use 'void'?)") |
| if arg == 'void': |
| continue |
| |
| if '*' in arg: |
| arg_type, identifier = arg.rsplit('*', 1) |
| arg_type += '*' |
| identifier = identifier.strip() |
| else: |
| arg_type, identifier = arg.rsplit(None, 1) |
| |
| validate_type(arg_type) |
| res.append((arg_type, identifier)) |
| return Arguments(res) |
| |
| def __getitem__(self, index): |
| if isinstance(index, slice): |
| return Arguments(self._args[index]) |
| else: |
| return self._args[index] |
| |
| def __iter__(self): |
| """Iterate over the (type, name) pairs.""" |
| return iter(self._args) |
| |
| def __len__(self): |
| """Number of arguments.""" |
| return len(self._args) |
| |
| def __str__(self): |
| """String suitable for declaring function arguments.""" |
| if len(self._args) == 0: |
| return "void" |
| else: |
| return ", ".join([ " ".join([t, n]) for t,n in self._args ]) |
| |
| def __repr__(self): |
| """Evaluable string representation for this object.""" |
| return "Arguments(\"%s\")" % str(self) |
| |
| def names(self): |
| """List of argument names.""" |
| return [ name for _, name in self._args ] |
| |
| def types(self): |
| """List of argument types.""" |
| return [ type_ for type_, _ in self._args ] |
| |
| def casted(self): |
| """List of argument names casted to their type.""" |
| return ["(%s)%s" % (type_, name) for type_, name in self._args] |
| |
| def transform(self, *trans): |
| """Return a new Arguments instance with transformed types. |
| |
| The types in the resulting Arguments instance are transformed according |
| to tracetool.transform.transform_type. |
| """ |
| res = [] |
| for type_, name in self._args: |
| res.append((tracetool.transform.transform_type(type_, *trans), |
| name)) |
| return Arguments(res) |
| |
| |
| class Event(object): |
| """Event description. |
| |
| Attributes |
| ---------- |
| name : str |
| The event name. |
| fmt : str |
| The event format string. |
| properties : set(str) |
| Properties of the event. |
| args : Arguments |
| The event arguments. |
| lineno : int |
| The line number in the input file. |
| filename : str |
| The path to the input file. |
| |
| """ |
| |
| _CRE = re.compile("((?P<props>[\w\s]+)\s+)?" |
| "(?P<name>\w+)" |
| "\((?P<args>[^)]*)\)" |
| "\s*" |
| "(?:(?:(?P<fmt_trans>\".+),)?\s*(?P<fmt>\".+))?" |
| "\s*") |
| |
| _VALID_PROPS = set(["disable", "vcpu"]) |
| |
| def __init__(self, name, props, fmt, args, lineno, filename, orig=None, |
| event_trans=None, event_exec=None): |
| """ |
| Parameters |
| ---------- |
| name : string |
| Event name. |
| props : list of str |
| Property names. |
| fmt : str, list of str |
| Event printing format string(s). |
| args : Arguments |
| Event arguments. |
| lineno : int |
| The line number in the input file. |
| filename : str |
| The path to the input file. |
| orig : Event or None |
| Original Event before transformation/generation. |
| event_trans : Event or None |
| Generated translation-time event ("tcg" property). |
| event_exec : Event or None |
| Generated execution-time event ("tcg" property). |
| |
| """ |
| self.name = name |
| self.properties = props |
| self.fmt = fmt |
| self.args = args |
| self.lineno = int(lineno) |
| self.filename = str(filename) |
| self.event_trans = event_trans |
| self.event_exec = event_exec |
| |
| if len(args) > 10: |
| raise ValueError("Event '%s' has more than maximum permitted " |
| "argument count" % name) |
| |
| if orig is None: |
| self.original = weakref.ref(self) |
| else: |
| self.original = orig |
| |
| unknown_props = set(self.properties) - self._VALID_PROPS |
| if len(unknown_props) > 0: |
| raise ValueError("Unknown properties: %s" |
| % ", ".join(unknown_props)) |
| assert isinstance(self.fmt, str) or len(self.fmt) == 2 |
| |
| def copy(self): |
| """Create a new copy.""" |
| return Event(self.name, list(self.properties), self.fmt, |
| self.args.copy(), self.lineno, self.filename, |
| self, self.event_trans, self.event_exec) |
| |
| @staticmethod |
| def build(line_str, lineno, filename): |
| """Build an Event instance from a string. |
| |
| Parameters |
| ---------- |
| line_str : str |
| Line describing the event. |
| lineno : int |
| Line number in input file. |
| filename : str |
| Path to input file. |
| """ |
| m = Event._CRE.match(line_str) |
| assert m is not None |
| groups = m.groupdict('') |
| |
| name = groups["name"] |
| props = groups["props"].split() |
| fmt = groups["fmt"] |
| fmt_trans = groups["fmt_trans"] |
| if fmt.find("%m") != -1 or fmt_trans.find("%m") != -1: |
| raise ValueError("Event format '%m' is forbidden, pass the error " |
| "as an explicit trace argument") |
| if fmt.endswith(r'\n"'): |
| raise ValueError("Event format must not end with a newline " |
| "character") |
| |
| if len(fmt_trans) > 0: |
| fmt = [fmt_trans, fmt] |
| args = Arguments.build(groups["args"]) |
| |
| event = Event(name, props, fmt, args, lineno, filename) |
| |
| # add implicit arguments when using the 'vcpu' property |
| import tracetool.vcpu |
| event = tracetool.vcpu.transform_event(event) |
| |
| return event |
| |
| def __repr__(self): |
| """Evaluable string representation for this object.""" |
| if isinstance(self.fmt, str): |
| fmt = self.fmt |
| else: |
| fmt = "%s, %s" % (self.fmt[0], self.fmt[1]) |
| return "Event('%s %s(%s) %s')" % (" ".join(self.properties), |
| self.name, |
| self.args, |
| fmt) |
| # Star matching on PRI is dangerous as one might have multiple |
| # arguments with that format, hence the non-greedy version of it. |
| _FMT = re.compile("(%[\d\.]*\w+|%.*?PRI\S+)") |
| |
| def formats(self): |
| """List conversion specifiers in the argument print format string.""" |
| assert not isinstance(self.fmt, list) |
| return self._FMT.findall(self.fmt) |
| |
| QEMU_TRACE = "trace_%(name)s" |
| QEMU_TRACE_NOCHECK = "_nocheck__" + QEMU_TRACE |
| QEMU_TRACE_TCG = QEMU_TRACE + "_tcg" |
| QEMU_DSTATE = "_TRACE_%(NAME)s_DSTATE" |
| QEMU_BACKEND_DSTATE = "TRACE_%(NAME)s_BACKEND_DSTATE" |
| QEMU_EVENT = "_TRACE_%(NAME)s_EVENT" |
| |
| def api(self, fmt=None): |
| if fmt is None: |
| fmt = Event.QEMU_TRACE |
| return fmt % {"name": self.name, "NAME": self.name.upper()} |
| |
| def transform(self, *trans): |
| """Return a new Event with transformed Arguments.""" |
| return Event(self.name, |
| list(self.properties), |
| self.fmt, |
| self.args.transform(*trans), |
| self.lineno, |
| self.filename, |
| self) |
| |
| |
| def read_events(fobj, fname): |
| """Generate the output for the given (format, backends) pair. |
| |
| Parameters |
| ---------- |
| fobj : file |
| Event description file. |
| fname : str |
| Name of event file |
| |
| Returns a list of Event objects |
| """ |
| |
| events = [] |
| for lineno, line in enumerate(fobj, 1): |
| if line[-1] != '\n': |
| raise ValueError("%s does not end with a new line" % fname) |
| if not line.strip(): |
| continue |
| if line.lstrip().startswith('#'): |
| continue |
| |
| try: |
| event = Event.build(line, lineno, fname) |
| except ValueError as e: |
| arg0 = 'Error at %s:%d: %s' % (fname, lineno, e.args[0]) |
| e.args = (arg0,) + e.args[1:] |
| raise |
| |
| events.append(event) |
| |
| return events |
| |
| |
| class TracetoolError (Exception): |
| """Exception for calls to generate.""" |
| pass |
| |
| |
| def try_import(mod_name, attr_name=None, attr_default=None): |
| """Try to import a module and get an attribute from it. |
| |
| Parameters |
| ---------- |
| mod_name : str |
| Module name. |
| attr_name : str, optional |
| Name of an attribute in the module. |
| attr_default : optional |
| Default value if the attribute does not exist in the module. |
| |
| Returns |
| ------- |
| A pair indicating whether the module could be imported and the module or |
| object or attribute value. |
| """ |
| try: |
| module = __import__(mod_name, globals(), locals(), ["__package__"]) |
| if attr_name is None: |
| return True, module |
| return True, getattr(module, str(attr_name), attr_default) |
| except ImportError: |
| return False, None |
| |
| |
| def generate(events, group, format, backends, |
| binary=None, probe_prefix=None): |
| """Generate the output for the given (format, backends) pair. |
| |
| Parameters |
| ---------- |
| events : list |
| list of Event objects to generate for |
| group: str |
| Name of the tracing group |
| format : str |
| Output format name. |
| backends : list |
| Output backend names. |
| binary : str or None |
| See tracetool.backend.dtrace.BINARY. |
| probe_prefix : str or None |
| See tracetool.backend.dtrace.PROBEPREFIX. |
| """ |
| # fix strange python error (UnboundLocalError tracetool) |
| import tracetool |
| |
| format = str(format) |
| if len(format) == 0: |
| raise TracetoolError("format not set") |
| if not tracetool.format.exists(format): |
| raise TracetoolError("unknown format: %s" % format) |
| |
| if len(backends) == 0: |
| raise TracetoolError("no backends specified") |
| for backend in backends: |
| if not tracetool.backend.exists(backend): |
| raise TracetoolError("unknown backend: %s" % backend) |
| backend = tracetool.backend.Wrapper(backends, format) |
| |
| import tracetool.backend.dtrace |
| tracetool.backend.dtrace.BINARY = binary |
| tracetool.backend.dtrace.PROBEPREFIX = probe_prefix |
| |
| tracetool.format.generate(events, format, backend, group) |