__init__.py 13.7 KB
Newer Older
1 2 3 4 5 6 7 8
#!/usr/bin/env python
# -*- coding: utf-8 -*-

"""
Machinery for generating tracing-related intermediate files.
"""

__author__     = "Lluís Vilanova <vilanova@ac.upc.edu>"
9
__copyright__  = "Copyright 2012-2017, Lluís Vilanova <vilanova@ac.upc.edu>"
10 11 12 13 14 15 16 17
__license__    = "GPL version 2 or (at your option) any later version"

__maintainer__ = "Stefan Hajnoczi"
__email__      = "stefanha@linux.vnet.ibm.com"


import re
import sys
18
import weakref
19 20 21

import tracetool.format
import tracetool.backend
22
import tracetool.transform
23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43


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)


def out(*lines, **kwargs):
    """Write a set of output lines.

    You can use kwargs as a shorthand for mapping variables when formating all
    the strings in lines.
    """
    lines = [ l % kwargs for l in lines ]
    sys.stdout.writelines("\n".join(lines) + "\n")

44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86
# 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",
    # Magic substitution is done by tracetool
    "TCGv",
]

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 in whitelist. "
                             "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)
87 88 89 90 91 92 93 94 95

class Arguments:
    """Event arguments description."""

    def __init__(self, args):
        """
        Parameters
        ----------
        args :
96
            List of (type, name) tuples or Arguments objects.
97
        """
98 99 100 101 102 103
        self._args = []
        for arg in args:
            if isinstance(arg, Arguments):
                self._args.extend(arg._args)
            else:
                self._args.append(arg)
104

105 106 107 108
    def copy(self):
        """Create a new copy."""
        return Arguments(list(self._args))

109 110 111 112 113 114 115 116 117 118 119 120
    @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()
121 122
            if not arg:
                raise ValueError("Empty argument (did you forget to use 'void'?)")
123
            if arg == 'void':
124
                continue
125 126 127 128 129 130 131 132

            if '*' in arg:
                arg_type, identifier = arg.rsplit('*', 1)
                arg_type += '*'
                identifier = identifier.strip()
            else:
                arg_type, identifier = arg.rsplit(None, 1)

133
            validate_type(arg_type)
134
            res.append((arg_type, identifier))
135 136
        return Arguments(res)

137 138 139 140 141 142
    def __getitem__(self, index):
        if isinstance(index, slice):
            return Arguments(self._args[index])
        else:
            return self._args[index]

143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169
    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 ]

170 171 172 173
    def casted(self):
        """List of argument names casted to their type."""
        return ["(%s)%s" % (type_, name) for type_, name in self._args]

174 175 176 177 178 179 180 181 182 183 184 185
    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)

186 187 188 189 190 191 192 193 194 195 196 197 198 199

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.
200

201 202
    """

203 204
    _CRE = re.compile("((?P<props>[\w\s]+)\s+)?"
                      "(?P<name>\w+)"
205 206 207 208
                      "\((?P<args>[^)]*)\)"
                      "\s*"
                      "(?:(?:(?P<fmt_trans>\".+),)?\s*(?P<fmt>\".+))?"
                      "\s*")
209

210
    _VALID_PROPS = set(["disable", "tcg", "tcg-trans", "tcg-exec", "vcpu"])
211

212 213
    def __init__(self, name, props, fmt, args, orig=None,
                 event_trans=None, event_exec=None):
214 215 216 217 218 219 220
        """
        Parameters
        ----------
        name : string
            Event name.
        props : list of str
            Property names.
221
        fmt : str, list of str
222
            Event printing format string(s).
223 224
        args : Arguments
            Event arguments.
225
        orig : Event or None
226 227 228 229 230
            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).
231

232 233 234 235 236
        """
        self.name = name
        self.properties = props
        self.fmt = fmt
        self.args = args
237 238
        self.event_trans = event_trans
        self.event_exec = event_exec
239

240 241 242 243
        if len(args) > 10:
            raise ValueError("Event '%s' has more than maximum permitted "
                             "argument count" % name)

244 245 246 247 248
        if orig is None:
            self.original = weakref.ref(self)
        else:
            self.original = orig

249 250
        unknown_props = set(self.properties) - self._VALID_PROPS
        if len(unknown_props) > 0:
251 252
            raise ValueError("Unknown properties: %s"
                             % ", ".join(unknown_props))
253
        assert isinstance(self.fmt, str) or len(self.fmt) == 2
254

255 256 257
    def copy(self):
        """Create a new copy."""
        return Event(self.name, list(self.properties), self.fmt,
258
                     self.args.copy(), self, self.event_trans, self.event_exec)
259

260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275
    @staticmethod
    def build(line_str):
        """Build an Event instance from a string.

        Parameters
        ----------
        line_str : str
            Line describing the event.
        """
        m = Event._CRE.match(line_str)
        assert m is not None
        groups = m.groupdict('')

        name = groups["name"]
        props = groups["props"].split()
        fmt = groups["fmt"]
276 277 278
        fmt_trans = groups["fmt_trans"]
        if len(fmt_trans) > 0:
            fmt = [fmt_trans, fmt]
279 280
        args = Arguments.build(groups["args"])

281 282 283 284 285
        if "tcg-trans" in props:
            raise ValueError("Invalid property 'tcg-trans'")
        if "tcg-exec" in props:
            raise ValueError("Invalid property 'tcg-exec'")
        if "tcg" not in props and not isinstance(fmt, str):
286
            raise ValueError("Only events with 'tcg' property can have two format strings")
287
        if "tcg" in props and isinstance(fmt, str):
288
            raise ValueError("Events with 'tcg' property must have two format strings")
289

290 291 292 293 294 295 296
        event = Event(name, props, fmt, args)

        # add implicit arguments when using the 'vcpu' property
        import tracetool.vcpu
        event = tracetool.vcpu.transform_event(event)

        return event
297 298 299

    def __repr__(self):
        """Evaluable string representation for this object."""
300 301 302 303
        if isinstance(self.fmt, str):
            fmt = self.fmt
        else:
            fmt = "%s, %s" % (self.fmt[0], self.fmt[1])
304 305 306
        return "Event('%s %s(%s) %s')" % (" ".join(self.properties),
                                          self.name,
                                          self.args,
307
                                          fmt)
308 309 310
    # 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+)")
311 312

    def formats(self):
313
        """List conversion specifiers in the argument print format string."""
314 315 316
        assert not isinstance(self.fmt, list)
        return self._FMT.findall(self.fmt)

317
    QEMU_TRACE               = "trace_%(name)s"
318
    QEMU_TRACE_NOCHECK       = "_nocheck__" + QEMU_TRACE
319
    QEMU_TRACE_TCG           = QEMU_TRACE + "_tcg"
320
    QEMU_DSTATE              = "_TRACE_%(NAME)s_DSTATE"
321
    QEMU_BACKEND_DSTATE      = "TRACE_%(NAME)s_BACKEND_DSTATE"
322
    QEMU_EVENT               = "_TRACE_%(NAME)s_EVENT"
323 324 325 326

    def api(self, fmt=None):
        if fmt is None:
            fmt = Event.QEMU_TRACE
327
        return fmt % {"name": self.name, "NAME": self.name.upper()}
328

329 330 331 332 333 334 335 336
    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)

337

338
def read_events(fobj, fname):
339 340 341 342 343 344
    """Generate the output for the given (format, backends) pair.

    Parameters
    ----------
    fobj : file
        Event description file.
345 346
    fname : str
        Name of event file
347 348 349 350

    Returns a list of Event objects
    """

351
    events = []
352
    for lineno, line in enumerate(fobj, 1):
353 354 355 356
        if not line.strip():
            continue
        if line.lstrip().startswith('#'):
            continue
357

358 359 360
        try:
            event = Event.build(line)
        except ValueError as e:
361
            arg0 = 'Error at %s:%d: %s' % (fname, lineno, e.args[0])
362 363
            e.args = (arg0,) + e.args[1:]
            raise
364 365 366 367 368 369 370 371 372

        # transform TCG-enabled events
        if "tcg" not in event.properties:
            events.append(event)
        else:
            event_trans = event.copy()
            event_trans.name += "_trans"
            event_trans.properties += ["tcg-trans"]
            event_trans.fmt = event.fmt[0]
373
            # ignore TCG arguments
374 375 376 377 378 379 380 381 382 383 384 385
            args_trans = []
            for atrans, aorig in zip(
                    event_trans.transform(tracetool.transform.TCG_2_HOST).args,
                    event.args):
                if atrans == aorig:
                    args_trans.append(atrans)
            event_trans.args = Arguments(args_trans)

            event_exec = event.copy()
            event_exec.name += "_exec"
            event_exec.properties += ["tcg-exec"]
            event_exec.fmt = event.fmt[1]
386
            event_exec.args = event_exec.args.transform(tracetool.transform.TCG_2_HOST)
387 388 389 390 391 392 393

            new_event = [event_trans, event_exec]
            event.event_trans, event.event_exec = new_event

            events.extend(new_event)

    return events
394 395 396 397 398 399 400


class TracetoolError (Exception):
    """Exception for calls to generate."""
    pass


401
def try_import(mod_name, attr_name=None, attr_default=None):
402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418
    """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:
419
        module = __import__(mod_name, globals(), locals(), ["__package__"])
420 421 422 423 424 425 426
        if attr_name is None:
            return True, module
        return True, getattr(module, str(attr_name), attr_default)
    except ImportError:
        return False, None


427
def generate(events, group, format, backends,
428
             binary=None, probe_prefix=None):
L
Lluís Vilanova 已提交
429
    """Generate the output for the given (format, backends) pair.
430 431 432

    Parameters
    ----------
433 434
    events : list
        list of Event objects to generate for
435 436
    group: str
        Name of the tracing group
437 438
    format : str
        Output format name.
L
Lluís Vilanova 已提交
439 440
    backends : list
        Output backend names.
441 442 443 444
    binary : str or None
        See tracetool.backend.dtrace.BINARY.
    probe_prefix : str or None
        See tracetool.backend.dtrace.PROBEPREFIX.
445 446 447 448 449 450 451
    """
    # fix strange python error (UnboundLocalError tracetool)
    import tracetool

    format = str(format)
    if len(format) is 0:
        raise TracetoolError("format not set")
452
    if not tracetool.format.exists(format):
453
        raise TracetoolError("unknown format: %s" % format)
L
Lluís Vilanova 已提交
454 455 456 457 458 459 460

    if len(backends) is 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)
461

462 463 464 465
    import tracetool.backend.dtrace
    tracetool.backend.dtrace.BINARY = binary
    tracetool.backend.dtrace.PROBEPREFIX = probe_prefix

466
    tracetool.format.generate(events, format, backend, group)