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