xref: /openbmc/qemu/scripts/tracetool/__init__.py (revision 8f0a3716)
1#!/usr/bin/env python
2# -*- coding: utf-8 -*-
3
4"""
5Machinery for generating tracing-related intermediate files.
6"""
7
8__author__     = "Lluís Vilanova <vilanova@ac.upc.edu>"
9__copyright__  = "Copyright 2012-2017, Lluís Vilanova <vilanova@ac.upc.edu>"
10__license__    = "GPL version 2 or (at your option) any later version"
11
12__maintainer__ = "Stefan Hajnoczi"
13__email__      = "stefanha@linux.vnet.ibm.com"
14
15
16import re
17import sys
18import weakref
19
20import tracetool.format
21import tracetool.backend
22import tracetool.transform
23
24
25def error_write(*lines):
26    """Write a set of error lines."""
27    sys.stderr.writelines("\n".join(lines) + "\n")
28
29def error(*lines):
30    """Write a set of error lines and exit."""
31    error_write(*lines)
32    sys.exit(1)
33
34
35def out(*lines, **kwargs):
36    """Write a set of output lines.
37
38    You can use kwargs as a shorthand for mapping variables when formating all
39    the strings in lines.
40    """
41    lines = [ l % kwargs for l in lines ]
42    sys.stdout.writelines("\n".join(lines) + "\n")
43
44
45class Arguments:
46    """Event arguments description."""
47
48    def __init__(self, args):
49        """
50        Parameters
51        ----------
52        args :
53            List of (type, name) tuples or Arguments objects.
54        """
55        self._args = []
56        for arg in args:
57            if isinstance(arg, Arguments):
58                self._args.extend(arg._args)
59            else:
60                self._args.append(arg)
61
62    def copy(self):
63        """Create a new copy."""
64        return Arguments(list(self._args))
65
66    @staticmethod
67    def build(arg_str):
68        """Build and Arguments instance from an argument string.
69
70        Parameters
71        ----------
72        arg_str : str
73            String describing the event arguments.
74        """
75        res = []
76        for arg in arg_str.split(","):
77            arg = arg.strip()
78            if not arg:
79                raise ValueError("Empty argument (did you forget to use 'void'?)")
80            if arg == 'void':
81                continue
82
83            if '*' in arg:
84                arg_type, identifier = arg.rsplit('*', 1)
85                arg_type += '*'
86                identifier = identifier.strip()
87            else:
88                arg_type, identifier = arg.rsplit(None, 1)
89
90            res.append((arg_type, identifier))
91        return Arguments(res)
92
93    def __getitem__(self, index):
94        if isinstance(index, slice):
95            return Arguments(self._args[index])
96        else:
97            return self._args[index]
98
99    def __iter__(self):
100        """Iterate over the (type, name) pairs."""
101        return iter(self._args)
102
103    def __len__(self):
104        """Number of arguments."""
105        return len(self._args)
106
107    def __str__(self):
108        """String suitable for declaring function arguments."""
109        if len(self._args) == 0:
110            return "void"
111        else:
112            return ", ".join([ " ".join([t, n]) for t,n in self._args ])
113
114    def __repr__(self):
115        """Evaluable string representation for this object."""
116        return "Arguments(\"%s\")" % str(self)
117
118    def names(self):
119        """List of argument names."""
120        return [ name for _, name in self._args ]
121
122    def types(self):
123        """List of argument types."""
124        return [ type_ for type_, _ in self._args ]
125
126    def casted(self):
127        """List of argument names casted to their type."""
128        return ["(%s)%s" % (type_, name) for type_, name in self._args]
129
130    def transform(self, *trans):
131        """Return a new Arguments instance with transformed types.
132
133        The types in the resulting Arguments instance are transformed according
134        to tracetool.transform.transform_type.
135        """
136        res = []
137        for type_, name in self._args:
138            res.append((tracetool.transform.transform_type(type_, *trans),
139                        name))
140        return Arguments(res)
141
142
143class Event(object):
144    """Event description.
145
146    Attributes
147    ----------
148    name : str
149        The event name.
150    fmt : str
151        The event format string.
152    properties : set(str)
153        Properties of the event.
154    args : Arguments
155        The event arguments.
156
157    """
158
159    _CRE = re.compile("((?P<props>[\w\s]+)\s+)?"
160                      "(?P<name>\w+)"
161                      "\((?P<args>[^)]*)\)"
162                      "\s*"
163                      "(?:(?:(?P<fmt_trans>\".+),)?\s*(?P<fmt>\".+))?"
164                      "\s*")
165
166    _VALID_PROPS = set(["disable", "tcg", "tcg-trans", "tcg-exec", "vcpu"])
167
168    def __init__(self, name, props, fmt, args, orig=None,
169                 event_trans=None, event_exec=None):
170        """
171        Parameters
172        ----------
173        name : string
174            Event name.
175        props : list of str
176            Property names.
177        fmt : str, list of str
178            Event printing format string(s).
179        args : Arguments
180            Event arguments.
181        orig : Event or None
182            Original Event before transformation/generation.
183        event_trans : Event or None
184            Generated translation-time event ("tcg" property).
185        event_exec : Event or None
186            Generated execution-time event ("tcg" property).
187
188        """
189        self.name = name
190        self.properties = props
191        self.fmt = fmt
192        self.args = args
193        self.event_trans = event_trans
194        self.event_exec = event_exec
195
196        if len(args) > 10:
197            raise ValueError("Event '%s' has more than maximum permitted "
198                             "argument count" % name)
199
200        if orig is None:
201            self.original = weakref.ref(self)
202        else:
203            self.original = orig
204
205        unknown_props = set(self.properties) - self._VALID_PROPS
206        if len(unknown_props) > 0:
207            raise ValueError("Unknown properties: %s"
208                             % ", ".join(unknown_props))
209        assert isinstance(self.fmt, str) or len(self.fmt) == 2
210
211    def copy(self):
212        """Create a new copy."""
213        return Event(self.name, list(self.properties), self.fmt,
214                     self.args.copy(), self, self.event_trans, self.event_exec)
215
216    @staticmethod
217    def build(line_str):
218        """Build an Event instance from a string.
219
220        Parameters
221        ----------
222        line_str : str
223            Line describing the event.
224        """
225        m = Event._CRE.match(line_str)
226        assert m is not None
227        groups = m.groupdict('')
228
229        name = groups["name"]
230        props = groups["props"].split()
231        fmt = groups["fmt"]
232        fmt_trans = groups["fmt_trans"]
233        if len(fmt_trans) > 0:
234            fmt = [fmt_trans, fmt]
235        args = Arguments.build(groups["args"])
236
237        if "tcg-trans" in props:
238            raise ValueError("Invalid property 'tcg-trans'")
239        if "tcg-exec" in props:
240            raise ValueError("Invalid property 'tcg-exec'")
241        if "tcg" not in props and not isinstance(fmt, str):
242            raise ValueError("Only events with 'tcg' property can have two format strings")
243        if "tcg" in props and isinstance(fmt, str):
244            raise ValueError("Events with 'tcg' property must have two format strings")
245
246        event = Event(name, props, fmt, args)
247
248        # add implicit arguments when using the 'vcpu' property
249        import tracetool.vcpu
250        event = tracetool.vcpu.transform_event(event)
251
252        return event
253
254    def __repr__(self):
255        """Evaluable string representation for this object."""
256        if isinstance(self.fmt, str):
257            fmt = self.fmt
258        else:
259            fmt = "%s, %s" % (self.fmt[0], self.fmt[1])
260        return "Event('%s %s(%s) %s')" % (" ".join(self.properties),
261                                          self.name,
262                                          self.args,
263                                          fmt)
264
265    _FMT = re.compile("(%[\d\.]*\w+|%.*PRI\S+)")
266
267    def formats(self):
268        """List conversion specifiers in the argument print format string."""
269        assert not isinstance(self.fmt, list)
270        return self._FMT.findall(self.fmt)
271
272    QEMU_TRACE               = "trace_%(name)s"
273    QEMU_TRACE_NOCHECK       = "_nocheck__" + QEMU_TRACE
274    QEMU_TRACE_TCG           = QEMU_TRACE + "_tcg"
275    QEMU_DSTATE              = "_TRACE_%(NAME)s_DSTATE"
276    QEMU_BACKEND_DSTATE      = "TRACE_%(NAME)s_BACKEND_DSTATE"
277    QEMU_EVENT               = "_TRACE_%(NAME)s_EVENT"
278
279    def api(self, fmt=None):
280        if fmt is None:
281            fmt = Event.QEMU_TRACE
282        return fmt % {"name": self.name, "NAME": self.name.upper()}
283
284    def transform(self, *trans):
285        """Return a new Event with transformed Arguments."""
286        return Event(self.name,
287                     list(self.properties),
288                     self.fmt,
289                     self.args.transform(*trans),
290                     self)
291
292
293def read_events(fobj):
294    """Generate the output for the given (format, backends) pair.
295
296    Parameters
297    ----------
298    fobj : file
299        Event description file.
300
301    Returns a list of Event objects
302    """
303
304    events = []
305    for lineno, line in enumerate(fobj, 1):
306        if not line.strip():
307            continue
308        if line.lstrip().startswith('#'):
309            continue
310
311        try:
312            event = Event.build(line)
313        except ValueError as e:
314            arg0 = 'Error on line %d: %s' % (lineno, e.args[0])
315            e.args = (arg0,) + e.args[1:]
316            raise
317
318        # transform TCG-enabled events
319        if "tcg" not in event.properties:
320            events.append(event)
321        else:
322            event_trans = event.copy()
323            event_trans.name += "_trans"
324            event_trans.properties += ["tcg-trans"]
325            event_trans.fmt = event.fmt[0]
326            # ignore TCG arguments
327            args_trans = []
328            for atrans, aorig in zip(
329                    event_trans.transform(tracetool.transform.TCG_2_HOST).args,
330                    event.args):
331                if atrans == aorig:
332                    args_trans.append(atrans)
333            event_trans.args = Arguments(args_trans)
334
335            event_exec = event.copy()
336            event_exec.name += "_exec"
337            event_exec.properties += ["tcg-exec"]
338            event_exec.fmt = event.fmt[1]
339            event_exec.args = event_exec.args.transform(tracetool.transform.TCG_2_HOST)
340
341            new_event = [event_trans, event_exec]
342            event.event_trans, event.event_exec = new_event
343
344            events.extend(new_event)
345
346    return events
347
348
349class TracetoolError (Exception):
350    """Exception for calls to generate."""
351    pass
352
353
354def try_import(mod_name, attr_name=None, attr_default=None):
355    """Try to import a module and get an attribute from it.
356
357    Parameters
358    ----------
359    mod_name : str
360        Module name.
361    attr_name : str, optional
362        Name of an attribute in the module.
363    attr_default : optional
364        Default value if the attribute does not exist in the module.
365
366    Returns
367    -------
368    A pair indicating whether the module could be imported and the module or
369    object or attribute value.
370    """
371    try:
372        module = __import__(mod_name, globals(), locals(), ["__package__"])
373        if attr_name is None:
374            return True, module
375        return True, getattr(module, str(attr_name), attr_default)
376    except ImportError:
377        return False, None
378
379
380def generate(events, group, format, backends,
381             binary=None, probe_prefix=None):
382    """Generate the output for the given (format, backends) pair.
383
384    Parameters
385    ----------
386    events : list
387        list of Event objects to generate for
388    group: str
389        Name of the tracing group
390    format : str
391        Output format name.
392    backends : list
393        Output backend names.
394    binary : str or None
395        See tracetool.backend.dtrace.BINARY.
396    probe_prefix : str or None
397        See tracetool.backend.dtrace.PROBEPREFIX.
398    """
399    # fix strange python error (UnboundLocalError tracetool)
400    import tracetool
401
402    format = str(format)
403    if len(format) is 0:
404        raise TracetoolError("format not set")
405    if not tracetool.format.exists(format):
406        raise TracetoolError("unknown format: %s" % format)
407
408    if len(backends) is 0:
409        raise TracetoolError("no backends specified")
410    for backend in backends:
411        if not tracetool.backend.exists(backend):
412            raise TracetoolError("unknown backend: %s" % backend)
413    backend = tracetool.backend.Wrapper(backends, format)
414
415    import tracetool.backend.dtrace
416    tracetool.backend.dtrace.BINARY = binary
417    tracetool.backend.dtrace.PROBEPREFIX = probe_prefix
418
419    tracetool.format.generate(events, format, backend, group)
420