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