]>
Commit | Line | Data |
---|---|---|
650ab98d LV |
1 | # -*- coding: utf-8 -*- |
2 | ||
3 | """ | |
4 | Machinery for generating tracing-related intermediate files. | |
5 | """ | |
6 | ||
7 | __author__ = "Lluís Vilanova <[email protected]>" | |
864a2178 | 8 | __copyright__ = "Copyright 2012-2017, Lluís Vilanova <[email protected]>" |
650ab98d LV |
9 | __license__ = "GPL version 2 or (at your option) any later version" |
10 | ||
11 | __maintainer__ = "Stefan Hajnoczi" | |
f892b494 | 12 | __email__ = "[email protected]" |
650ab98d LV |
13 | |
14 | ||
15 | import re | |
16 | import sys | |
b55835ac | 17 | import weakref |
650ab98d LV |
18 | |
19 | import tracetool.format | |
20 | import tracetool.backend | |
b2b36c22 | 21 | import tracetool.transform |
650ab98d LV |
22 | |
23 | ||
24 | def error_write(*lines): | |
25 | """Write a set of error lines.""" | |
26 | sys.stderr.writelines("\n".join(lines) + "\n") | |
27 | ||
28 | def error(*lines): | |
29 | """Write a set of error lines and exit.""" | |
30 | error_write(*lines) | |
31 | sys.exit(1) | |
32 | ||
33 | ||
294170c1 | 34 | out_lineno = 1 |
c05012a3 SH |
35 | out_filename = '<none>' |
36 | out_fobj = sys.stdout | |
37 | ||
38 | def out_open(filename): | |
39 | global out_filename, out_fobj | |
40 | out_filename = filename | |
41 | out_fobj = open(filename, 'wt') | |
42 | ||
650ab98d LV |
43 | def out(*lines, **kwargs): |
44 | """Write a set of output lines. | |
45 | ||
65fdb3cc | 46 | You can use kwargs as a shorthand for mapping variables when formatting all |
650ab98d | 47 | the strings in lines. |
c05012a3 | 48 | |
294170c1 SH |
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. | |
650ab98d | 53 | """ |
294170c1 | 54 | global out_lineno |
c05012a3 SH |
55 | output = [] |
56 | for l in lines: | |
294170c1 SH |
57 | kwargs['out_lineno'] = out_lineno |
58 | kwargs['out_next_lineno'] = out_lineno + 1 | |
c05012a3 SH |
59 | kwargs['out_filename'] = out_filename |
60 | output.append(l % kwargs) | |
294170c1 | 61 | out_lineno += 1 |
c05012a3 SH |
62 | |
63 | out_fobj.writelines("\n".join(output) + "\n") | |
650ab98d | 64 | |
73ff0610 DB |
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 | |
69 | ALLOWED_TYPES = [ | |
70 | "int", | |
71 | "long", | |
72 | "short", | |
73 | "char", | |
74 | "bool", | |
75 | "unsigned", | |
76 | "signed", | |
73ff0610 DB |
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", | |
73ff0610 DB |
90 | ] |
91 | ||
92 | def 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: | |
54fa79b7 | 101 | raise ValueError("Argument type '%s' is not allowed. " |
73ff0610 DB |
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) | |
650ab98d LV |
106 | |
107 | class Arguments: | |
108 | """Event arguments description.""" | |
109 | ||
110 | def __init__(self, args): | |
111 | """ | |
112 | Parameters | |
113 | ---------- | |
114 | args : | |
3596f524 | 115 | List of (type, name) tuples or Arguments objects. |
650ab98d | 116 | """ |
3596f524 LV |
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) | |
650ab98d | 123 | |
ad7443e4 LV |
124 | def copy(self): |
125 | """Create a new copy.""" | |
126 | return Arguments(list(self._args)) | |
127 | ||
650ab98d LV |
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() | |
24f4d3d3 SH |
140 | if not arg: |
141 | raise ValueError("Empty argument (did you forget to use 'void'?)") | |
b3ef0ade | 142 | if arg == 'void': |
650ab98d | 143 | continue |
b3ef0ade SH |
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 | ||
73ff0610 | 152 | validate_type(arg_type) |
b3ef0ade | 153 | res.append((arg_type, identifier)) |
650ab98d LV |
154 | return Arguments(res) |
155 | ||
3596f524 LV |
156 | def __getitem__(self, index): |
157 | if isinstance(index, slice): | |
158 | return Arguments(self._args[index]) | |
159 | else: | |
160 | return self._args[index] | |
161 | ||
650ab98d LV |
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 | ||
bc9beb47 LV |
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 | ||
b55835ac LV |
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 | ||
650ab98d LV |
205 | |
206 | class 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. | |
4e66c9ef SH |
219 | lineno : int |
220 | The line number in the input file. | |
221 | filename : str | |
222 | The path to the input file. | |
23214429 | 223 | |
650ab98d LV |
224 | """ |
225 | ||
f9bbba95 SH |
226 | _CRE = re.compile("((?P<props>[\w\s]+)\s+)?" |
227 | "(?P<name>\w+)" | |
b2b36c22 LV |
228 | "\((?P<args>[^)]*)\)" |
229 | "\s*" | |
230 | "(?:(?:(?P<fmt_trans>\".+),)?\s*(?P<fmt>\".+))?" | |
231 | "\s*") | |
650ab98d | 232 | |
126d4123 | 233 | _VALID_PROPS = set(["disable", "vcpu"]) |
650ab98d | 234 | |
4e66c9ef | 235 | def __init__(self, name, props, fmt, args, lineno, filename, orig=None, |
4ade0541 | 236 | event_trans=None, event_exec=None): |
650ab98d LV |
237 | """ |
238 | Parameters | |
239 | ---------- | |
240 | name : string | |
241 | Event name. | |
242 | props : list of str | |
243 | Property names. | |
b2b36c22 | 244 | fmt : str, list of str |
6e497fa1 | 245 | Event printing format string(s). |
650ab98d LV |
246 | args : Arguments |
247 | Event arguments. | |
4e66c9ef SH |
248 | lineno : int |
249 | The line number in the input file. | |
250 | filename : str | |
251 | The path to the input file. | |
b55835ac | 252 | orig : Event or None |
4ade0541 LV |
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). | |
41ef7b00 | 258 | |
650ab98d LV |
259 | """ |
260 | self.name = name | |
261 | self.properties = props | |
262 | self.fmt = fmt | |
263 | self.args = args | |
4e66c9ef SH |
264 | self.lineno = int(lineno) |
265 | self.filename = str(filename) | |
4ade0541 LV |
266 | self.event_trans = event_trans |
267 | self.event_exec = event_exec | |
650ab98d | 268 | |
f3fddaf6 DB |
269 | if len(args) > 10: |
270 | raise ValueError("Event '%s' has more than maximum permitted " | |
271 | "argument count" % name) | |
272 | ||
b55835ac LV |
273 | if orig is None: |
274 | self.original = weakref.ref(self) | |
275 | else: | |
276 | self.original = orig | |
277 | ||
650ab98d LV |
278 | unknown_props = set(self.properties) - self._VALID_PROPS |
279 | if len(unknown_props) > 0: | |
9c24a52e LV |
280 | raise ValueError("Unknown properties: %s" |
281 | % ", ".join(unknown_props)) | |
b2b36c22 | 282 | assert isinstance(self.fmt, str) or len(self.fmt) == 2 |
650ab98d | 283 | |
ad7443e4 LV |
284 | def copy(self): |
285 | """Create a new copy.""" | |
286 | return Event(self.name, list(self.properties), self.fmt, | |
4e66c9ef SH |
287 | self.args.copy(), self.lineno, self.filename, |
288 | self, self.event_trans, self.event_exec) | |
ad7443e4 | 289 | |
650ab98d | 290 | @staticmethod |
4e66c9ef | 291 | def build(line_str, lineno, filename): |
650ab98d LV |
292 | """Build an Event instance from a string. |
293 | ||
294 | Parameters | |
295 | ---------- | |
296 | line_str : str | |
297 | Line describing the event. | |
4e66c9ef SH |
298 | lineno : int |
299 | Line number in input file. | |
300 | filename : str | |
301 | Path to input file. | |
650ab98d LV |
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"] | |
b2b36c22 | 310 | fmt_trans = groups["fmt_trans"] |
772f1b37 DB |
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") | |
9f7ad79c PMD |
314 | if fmt.endswith(r'\n"'): |
315 | raise ValueError("Event format must not end with a newline " | |
316 | "character") | |
772f1b37 | 317 | |
b2b36c22 LV |
318 | if len(fmt_trans) > 0: |
319 | fmt = [fmt_trans, fmt] | |
650ab98d LV |
320 | args = Arguments.build(groups["args"]) |
321 | ||
4e66c9ef | 322 | event = Event(name, props, fmt, args, lineno, filename) |
3d211d9f LV |
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 | |
650ab98d LV |
329 | |
330 | def __repr__(self): | |
331 | """Evaluable string representation for this object.""" | |
b2b36c22 LV |
332 | if isinstance(self.fmt, str): |
333 | fmt = self.fmt | |
334 | else: | |
335 | fmt = "%s, %s" % (self.fmt[0], self.fmt[1]) | |
650ab98d LV |
336 | return "Event('%s %s(%s) %s')" % (" ".join(self.properties), |
337 | self.name, | |
338 | self.args, | |
b2b36c22 | 339 | fmt) |
fb1a66bc JEJ |
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+)") | |
23214429 LV |
343 | |
344 | def formats(self): | |
6e497fa1 | 345 | """List conversion specifiers in the argument print format string.""" |
23214429 LV |
346 | assert not isinstance(self.fmt, list) |
347 | return self._FMT.findall(self.fmt) | |
348 | ||
7d08f0da | 349 | QEMU_TRACE = "trace_%(name)s" |
864a2178 | 350 | QEMU_TRACE_NOCHECK = "_nocheck__" + QEMU_TRACE |
707c8a98 | 351 | QEMU_TRACE_TCG = QEMU_TRACE + "_tcg" |
93977402 | 352 | QEMU_DSTATE = "_TRACE_%(NAME)s_DSTATE" |
3932ef3f | 353 | QEMU_BACKEND_DSTATE = "TRACE_%(NAME)s_BACKEND_DSTATE" |
79218be4 | 354 | QEMU_EVENT = "_TRACE_%(NAME)s_EVENT" |
7d08f0da LV |
355 | |
356 | def api(self, fmt=None): | |
357 | if fmt is None: | |
358 | fmt = Event.QEMU_TRACE | |
93977402 | 359 | return fmt % {"name": self.name, "NAME": self.name.upper()} |
7d08f0da | 360 | |
b55835ac LV |
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), | |
4e66c9ef SH |
367 | self.lineno, |
368 | self.filename, | |
b55835ac LV |
369 | self) |
370 | ||
7d08f0da | 371 | |
86b5aacf | 372 | def read_events(fobj, fname): |
d1b97bce DB |
373 | """Generate the output for the given (format, backends) pair. |
374 | ||
375 | Parameters | |
376 | ---------- | |
377 | fobj : file | |
378 | Event description file. | |
86b5aacf DB |
379 | fname : str |
380 | Name of event file | |
d1b97bce DB |
381 | |
382 | Returns a list of Event objects | |
383 | """ | |
384 | ||
776ec96f | 385 | events = [] |
5069b561 | 386 | for lineno, line in enumerate(fobj, 1): |
77606363 DB |
387 | if line[-1] != '\n': |
388 | raise ValueError("%s does not end with a new line" % fname) | |
650ab98d LV |
389 | if not line.strip(): |
390 | continue | |
391 | if line.lstrip().startswith('#'): | |
392 | continue | |
776ec96f | 393 | |
5069b561 | 394 | try: |
4e66c9ef | 395 | event = Event.build(line, lineno, fname) |
5069b561 | 396 | except ValueError as e: |
86b5aacf | 397 | arg0 = 'Error at %s:%d: %s' % (fname, lineno, e.args[0]) |
5069b561 SH |
398 | e.args = (arg0,) + e.args[1:] |
399 | raise | |
776ec96f | 400 | |
126d4123 | 401 | events.append(event) |
776ec96f CS |
402 | |
403 | return events | |
650ab98d LV |
404 | |
405 | ||
406 | class TracetoolError (Exception): | |
407 | """Exception for calls to generate.""" | |
408 | pass | |
409 | ||
410 | ||
9c24a52e | 411 | def try_import(mod_name, attr_name=None, attr_default=None): |
650ab98d LV |
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: | |
45d6c787 | 429 | module = __import__(mod_name, globals(), locals(), ["__package__"]) |
650ab98d LV |
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 | ||
80dd5c49 | 437 | def generate(events, group, format, backends, |
9c24a52e | 438 | binary=None, probe_prefix=None): |
5b808275 | 439 | """Generate the output for the given (format, backends) pair. |
650ab98d LV |
440 | |
441 | Parameters | |
442 | ---------- | |
9096b78a DB |
443 | events : list |
444 | list of Event objects to generate for | |
80dd5c49 DB |
445 | group: str |
446 | Name of the tracing group | |
650ab98d LV |
447 | format : str |
448 | Output format name. | |
5b808275 LV |
449 | backends : list |
450 | Output backend names. | |
52ef093a LV |
451 | binary : str or None |
452 | See tracetool.backend.dtrace.BINARY. | |
453 | probe_prefix : str or None | |
454 | See tracetool.backend.dtrace.PROBEPREFIX. | |
650ab98d LV |
455 | """ |
456 | # fix strange python error (UnboundLocalError tracetool) | |
457 | import tracetool | |
458 | ||
459 | format = str(format) | |
403e11ed | 460 | if len(format) == 0: |
650ab98d | 461 | raise TracetoolError("format not set") |
53158adc | 462 | if not tracetool.format.exists(format): |
650ab98d | 463 | raise TracetoolError("unknown format: %s" % format) |
5b808275 | 464 | |
403e11ed | 465 | if len(backends) == 0: |
5b808275 LV |
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) | |
650ab98d | 471 | |
52ef093a LV |
472 | import tracetool.backend.dtrace |
473 | tracetool.backend.dtrace.BINARY = binary | |
474 | tracetool.backend.dtrace.PROBEPREFIX = probe_prefix | |
475 | ||
80dd5c49 | 476 | tracetool.format.generate(events, format, backend, group) |