1# SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) 2"""Parse or generate representations of perf metrics.""" 3import ast 4import decimal 5import json 6import os 7import re 8from enum import Enum 9from typing import Dict, List, Optional, Set, Tuple, Union 10 11all_pmus = set() 12all_events = set() 13experimental_events = set() 14all_events_all_models = set() 15 16def LoadEvents(directory: str) -> None: 17 """Populate a global set of all known events for the purpose of validating Event names""" 18 global all_pmus 19 global all_events 20 global experimental_events 21 global all_events_all_models 22 all_events = { 23 "context\\-switches", 24 "cpu\\-cycles", 25 "cycles", 26 "duration_time", 27 "instructions", 28 } 29 for file in os.listdir(os.fsencode(directory)): 30 filename = os.fsdecode(file) 31 if filename.endswith(".json"): 32 try: 33 for x in json.load(open(f"{directory}/{filename}")): 34 if "Unit" in x: 35 all_pmus.add(x["Unit"]) 36 if "EventName" in x: 37 all_events.add(x["EventName"]) 38 if "Experimental" in x and x["Experimental"] == "1": 39 experimental_events.add(x["EventName"]) 40 elif "ArchStdEvent" in x: 41 all_events.add(x["ArchStdEvent"]) 42 except json.decoder.JSONDecodeError: 43 # The generated directory may be the same as the input, which 44 # causes partial json files. Ignore errors. 45 pass 46 all_events_all_models = all_events.copy() 47 for root, dirs, files in os.walk(directory + ".."): 48 for filename in files: 49 if filename.endswith(".json"): 50 try: 51 for x in json.load(open(f"{root}/{filename}")): 52 if "EventName" in x: 53 all_events_all_models.add(x["EventName"]) 54 elif "ArchStdEvent" in x: 55 all_events_all_models.add(x["ArchStdEvent"]) 56 except json.decoder.JSONDecodeError: 57 # The generated directory may be the same as the input, which 58 # causes partial json files. Ignore errors. 59 pass 60 61 62def CheckPmu(name: str) -> bool: 63 return name in all_pmus 64 65 66def CheckEvent(name: str) -> bool: 67 """Check the event name exists in the set of all loaded events""" 68 global all_events 69 if len(all_events) == 0: 70 # No events loaded so assume any event is good. 71 return True 72 73 if ':' in name: 74 # Remove trailing modifier. 75 name = name[:name.find(':')] 76 elif '/' in name: 77 # Name could begin with a PMU or an event, for now assume it is good. 78 return True 79 80 return name in all_events 81 82def CheckEveryEvent(*names: str) -> None: 83 """Check all the events exist in at least one json file""" 84 global all_events_all_models 85 if len(all_events_all_models) == 0: 86 assert len(names) == 1, f"Cannot determine valid events in {names}" 87 # No events loaded so assume any event is good. 88 return 89 90 for name in names: 91 # Remove trailing modifier. 92 if ':' in name: 93 name = name[:name.find(':')] 94 elif '/' in name: 95 name = name[:name.find('/')] 96 if any([name.startswith(x) for x in ['amd', 'arm', 'cpu', 'msr', 'power', 'cha', 'uncore']]): 97 continue 98 if name not in all_events_all_models: 99 raise Exception(f"Is {name} a named json event?") 100 101 102def IsExperimentalEvent(name: str) -> bool: 103 global experimental_events 104 if ':' in name: 105 # Remove trailing modifier. 106 name = name[:name.find(':')] 107 elif '/' in name: 108 # Name could begin with a PMU or an event, for now assume it is not experimental. 109 return False 110 111 return name in experimental_events 112 113 114class MetricConstraint(Enum): 115 GROUPED_EVENTS = 0 116 NO_GROUP_EVENTS = 1 117 NO_GROUP_EVENTS_NMI = 2 118 NO_GROUP_EVENTS_SMT = 3 119 120class Expression: 121 """Abstract base class of elements in a metric expression.""" 122 123 def ToPerfJson(self) -> str: 124 """Returns a perf json file encoded representation.""" 125 raise NotImplementedError() 126 127 def ToPython(self) -> str: 128 """Returns a python expr parseable representation.""" 129 raise NotImplementedError() 130 131 def Simplify(self): 132 """Returns a simplified version of self.""" 133 raise NotImplementedError() 134 135 def HasExperimentalEvents(self) -> bool: 136 """Are experimental events used in the expression?""" 137 raise NotImplementedError() 138 139 def Equals(self, other) -> bool: 140 """Returns true when two expressions are the same.""" 141 raise NotImplementedError() 142 143 def Substitute(self, name: str, expression: 'Expression') -> 'Expression': 144 raise NotImplementedError() 145 146 def __str__(self) -> str: 147 return self.ToPerfJson() 148 149 def __or__(self, other: Union[int, float, 'Expression']) -> 'Operator': 150 return Operator('|', self, other) 151 152 def __ror__(self, other: Union[int, float, 'Expression']) -> 'Operator': 153 return Operator('|', other, self) 154 155 def __xor__(self, other: Union[int, float, 'Expression']) -> 'Operator': 156 return Operator('^', self, other) 157 158 def __and__(self, other: Union[int, float, 'Expression']) -> 'Operator': 159 return Operator('&', self, other) 160 161 def __rand__(self, other: Union[int, float, 'Expression']) -> 'Operator': 162 return Operator('&', other, self) 163 164 def __lt__(self, other: Union[int, float, 'Expression']) -> 'Operator': 165 return Operator('<', self, other) 166 167 def __gt__(self, other: Union[int, float, 'Expression']) -> 'Operator': 168 return Operator('>', self, other) 169 170 def __add__(self, other: Union[int, float, 'Expression']) -> 'Operator': 171 return Operator('+', self, other) 172 173 def __radd__(self, other: Union[int, float, 'Expression']) -> 'Operator': 174 return Operator('+', other, self) 175 176 def __sub__(self, other: Union[int, float, 'Expression']) -> 'Operator': 177 return Operator('-', self, other) 178 179 def __rsub__(self, other: Union[int, float, 'Expression']) -> 'Operator': 180 return Operator('-', other, self) 181 182 def __mul__(self, other: Union[int, float, 'Expression']) -> 'Operator': 183 return Operator('*', self, other) 184 185 def __rmul__(self, other: Union[int, float, 'Expression']) -> 'Operator': 186 return Operator('*', other, self) 187 188 def __truediv__(self, other: Union[int, float, 'Expression']) -> 'Operator': 189 return Operator('/', self, other) 190 191 def __rtruediv__(self, other: Union[int, float, 'Expression']) -> 'Operator': 192 return Operator('/', other, self) 193 194 def __mod__(self, other: Union[int, float, 'Expression']) -> 'Operator': 195 return Operator('%', self, other) 196 197 198def _Constify(val: Union[bool, int, float, Expression]) -> Expression: 199 """Used to ensure that the nodes in the expression tree are all Expression.""" 200 if isinstance(val, bool): 201 return Constant(1 if val else 0) 202 if isinstance(val, (int, float)): 203 return Constant(val) 204 return val 205 206 207# Simple lookup for operator precedence, used to avoid unnecessary 208# brackets. Precedence matches that of the simple expression parser 209# but differs from python where comparisons are lower precedence than 210# the bitwise &, ^, | but not the logical versions that the expression 211# parser doesn't have. 212_PRECEDENCE = { 213 '|': 0, 214 '^': 1, 215 '&': 2, 216 '<': 3, 217 '>': 3, 218 '+': 4, 219 '-': 4, 220 '*': 5, 221 '/': 5, 222 '%': 5, 223} 224 225 226class Operator(Expression): 227 """Represents a binary operator in the parse tree.""" 228 229 def __init__(self, operator: str, lhs: Union[int, float, Expression], 230 rhs: Union[int, float, Expression]): 231 self.operator = operator 232 self.lhs = _Constify(lhs) 233 self.rhs = _Constify(rhs) 234 235 def Bracket(self, 236 other: Expression, 237 other_str: str, 238 rhs: bool = False) -> str: 239 """If necessary brackets the given other value. 240 241 If ``other`` is an operator then a bracket is necessary when 242 this/self operator has higher precedence. Consider: '(a + b) * c', 243 ``other_str`` will be 'a + b'. A bracket is necessary as without 244 the bracket 'a + b * c' will evaluate 'b * c' first. However, '(a 245 * b) + c' doesn't need a bracket as 'a * b' will always be 246 evaluated first. For 'a / (b * c)' (ie the same precedence level 247 operations) then we add the bracket to best match the original 248 input, but not for '(a / b) * c' where the bracket is unnecessary. 249 250 Args: 251 other (Expression): is a lhs or rhs operator 252 other_str (str): ``other`` in the appropriate string form 253 rhs (bool): is ``other`` on the RHS 254 255 Returns: 256 str: possibly bracketed other_str 257 """ 258 if isinstance(other, Operator): 259 if _PRECEDENCE.get(self.operator, -1) > _PRECEDENCE.get( 260 other.operator, -1): 261 return f'({other_str})' 262 if rhs and _PRECEDENCE.get(self.operator, -1) == _PRECEDENCE.get( 263 other.operator, -1): 264 return f'({other_str})' 265 return other_str 266 267 def ToPerfJson(self): 268 return (f'{self.Bracket(self.lhs, self.lhs.ToPerfJson())} {self.operator} ' 269 f'{self.Bracket(self.rhs, self.rhs.ToPerfJson(), True)}') 270 271 def ToPython(self): 272 return (f'{self.Bracket(self.lhs, self.lhs.ToPython())} {self.operator} ' 273 f'{self.Bracket(self.rhs, self.rhs.ToPython(), True)}') 274 275 def Simplify(self) -> Expression: 276 lhs = self.lhs.Simplify() 277 rhs = self.rhs.Simplify() 278 if isinstance(lhs, Constant) and isinstance(rhs, Constant): 279 return Constant(ast.literal_eval(lhs.value + self.operator + rhs.value)) 280 281 if isinstance(self.lhs, Constant): 282 if self.operator in ('+', '|') and lhs.value == '0': 283 return rhs 284 285 # Simplify multiplication by 0 except for the slot event which 286 # is deliberately introduced using this pattern. 287 if self.operator == '*' and lhs.value == '0' and ( 288 not isinstance(rhs, Event) or 'slots' not in rhs.name.lower()): 289 return Constant(0) 290 291 if self.operator == '*' and lhs.value == '1': 292 return rhs 293 294 if isinstance(rhs, Constant): 295 if self.operator in ('+', '|') and rhs.value == '0': 296 return lhs 297 298 if self.operator == '*' and rhs.value == '0': 299 return Constant(0) 300 301 if self.operator == '*' and rhs.value == '1': 302 return lhs 303 304 return Operator(self.operator, lhs, rhs) 305 306 def HasExperimentalEvents(self) -> bool: 307 return self.lhs.HasExperimentalEvents() or self.rhs.HasExperimentalEvents() 308 309 def Equals(self, other: Expression) -> bool: 310 if isinstance(other, Operator): 311 return self.operator == other.operator and self.lhs.Equals( 312 other.lhs) and self.rhs.Equals(other.rhs) 313 return False 314 315 def Substitute(self, name: str, expression: Expression) -> Expression: 316 if self.Equals(expression): 317 return Event(name) 318 lhs = self.lhs.Substitute(name, expression) 319 rhs = self.rhs.Substitute(name, expression) 320 return Operator(self.operator, lhs, rhs) 321 322 323class Select(Expression): 324 """Represents a select ternary in the parse tree.""" 325 326 def __init__(self, true_val: Union[int, float, Expression], 327 cond: Union[int, float, Expression], 328 false_val: Union[int, float, Expression]): 329 self.true_val = _Constify(true_val) 330 self.cond = _Constify(cond) 331 self.false_val = _Constify(false_val) 332 333 def ToPerfJson(self): 334 true_str = self.true_val.ToPerfJson() 335 cond_str = self.cond.ToPerfJson() 336 false_str = self.false_val.ToPerfJson() 337 return f'({true_str} if {cond_str} else {false_str})' 338 339 def ToPython(self): 340 return (f'Select({self.true_val.ToPython()}, {self.cond.ToPython()}, ' 341 f'{self.false_val.ToPython()})') 342 343 def Simplify(self) -> Expression: 344 cond = self.cond.Simplify() 345 true_val = self.true_val.Simplify() 346 false_val = self.false_val.Simplify() 347 if isinstance(cond, Constant): 348 return false_val if cond.value == '0' else true_val 349 350 if true_val.Equals(false_val): 351 return true_val 352 353 return Select(true_val, cond, false_val) 354 355 def HasExperimentalEvents(self) -> bool: 356 return (self.cond.HasExperimentalEvents() or self.true_val.HasExperimentalEvents() or 357 self.false_val.HasExperimentalEvents()) 358 359 def Equals(self, other: Expression) -> bool: 360 if isinstance(other, Select): 361 return self.cond.Equals(other.cond) and self.false_val.Equals( 362 other.false_val) and self.true_val.Equals(other.true_val) 363 return False 364 365 def Substitute(self, name: str, expression: Expression) -> Expression: 366 if self.Equals(expression): 367 return Event(name) 368 true_val = self.true_val.Substitute(name, expression) 369 cond = self.cond.Substitute(name, expression) 370 false_val = self.false_val.Substitute(name, expression) 371 return Select(true_val, cond, false_val) 372 373 374class Function(Expression): 375 """A function in an expression like min, max, d_ratio.""" 376 377 def __init__(self, 378 fn: str, 379 lhs: Union[int, float, Expression], 380 rhs: Optional[Union[int, float, Expression]] = None): 381 self.fn = fn 382 self.lhs = _Constify(lhs) 383 self.rhs = None 384 if rhs is not None: 385 self.rhs = _Constify(rhs) 386 387 def ToPerfJson(self): 388 if self.rhs: 389 return f'{self.fn}({self.lhs.ToPerfJson()}, {self.rhs.ToPerfJson()})' 390 return f'{self.fn}({self.lhs.ToPerfJson()})' 391 392 def ToPython(self): 393 if self.rhs: 394 return f'{self.fn}({self.lhs.ToPython()}, {self.rhs.ToPython()})' 395 return f'{self.fn}({self.lhs.ToPython()})' 396 397 def Simplify(self) -> Expression: 398 lhs = self.lhs.Simplify() 399 rhs = self.rhs.Simplify() if self.rhs else None 400 if isinstance(lhs, Constant) and isinstance(rhs, Constant): 401 if self.fn == 'd_ratio': 402 if rhs.value == '0': 403 return Constant(0) 404 Constant(ast.literal_eval(f'{lhs} / {rhs}')) 405 return Constant(ast.literal_eval(f'{self.fn}({lhs}, {rhs})')) 406 407 return Function(self.fn, lhs, rhs) 408 409 def HasExperimentalEvents(self) -> bool: 410 return (self.lhs.HasExperimentalEvents() or 411 (self.rhs is not None and self.rhs.HasExperimentalEvents())) 412 413 def Equals(self, other: Expression) -> bool: 414 if isinstance(other, Function): 415 result = self.fn == other.fn and self.lhs.Equals(other.lhs) 416 if self.rhs: 417 result = result and self.rhs.Equals(other.rhs) 418 return result 419 return False 420 421 def Substitute(self, name: str, expression: Expression) -> Expression: 422 if self.Equals(expression): 423 return Event(name) 424 lhs = self.lhs.Substitute(name, expression) 425 rhs = None 426 if self.rhs: 427 rhs = self.rhs.Substitute(name, expression) 428 return Function(self.fn, lhs, rhs) 429 430 431def _FixEscapes(s: str) -> str: 432 s = re.sub(r'([^\\]),', r'\1\\,', s) 433 return re.sub(r'([^\\])=', r'\1\\=', s) 434 435 436class Event(Expression): 437 """An event in an expression.""" 438 439 def __init__(self, *args: str): 440 error = "" 441 CheckEveryEvent(*args) 442 for name in args: 443 if CheckEvent(name): 444 self.name = _FixEscapes(name) 445 return 446 if error: 447 error += " or " + name 448 else: 449 error = name 450 global all_events 451 raise Exception(f"No event {error} in:\n{all_events}") 452 453 def HasExperimentalEvents(self) -> bool: 454 return IsExperimentalEvent(self.name) 455 456 def ToPerfJson(self): 457 result = re.sub('/', '@', self.name) 458 return result 459 460 def ToPython(self): 461 return f'Event(r"{self.name}")' 462 463 def Simplify(self) -> Expression: 464 return self 465 466 def Equals(self, other: Expression) -> bool: 467 return isinstance(other, Event) and self.name == other.name 468 469 def Substitute(self, name: str, expression: Expression) -> Expression: 470 return self 471 472 473class MetricRef(Expression): 474 """A metric reference in an expression.""" 475 476 def __init__(self, name: str): 477 self.name = _FixEscapes(name) 478 479 def ToPerfJson(self): 480 return self.name 481 482 def ToPython(self): 483 return f'MetricRef(r"{self.name}")' 484 485 def Simplify(self) -> Expression: 486 return self 487 488 def HasExperimentalEvents(self) -> bool: 489 return False 490 491 def Equals(self, other: Expression) -> bool: 492 return isinstance(other, MetricRef) and self.name == other.name 493 494 def Substitute(self, name: str, expression: Expression) -> Expression: 495 return self 496 497 498class Constant(Expression): 499 """A constant within the expression tree.""" 500 501 def __init__(self, value: Union[float, str]): 502 ctx = decimal.Context() 503 ctx.prec = 20 504 dec = ctx.create_decimal(repr(value) if isinstance(value, float) else value) 505 self.value = dec.normalize().to_eng_string() 506 self.value = self.value.replace('+', '') 507 self.value = self.value.replace('E', 'e') 508 509 def ToPerfJson(self): 510 return self.value 511 512 def ToPython(self): 513 return f'Constant({self.value})' 514 515 def Simplify(self) -> Expression: 516 return self 517 518 def HasExperimentalEvents(self) -> bool: 519 return False 520 521 def Equals(self, other: Expression) -> bool: 522 return isinstance(other, Constant) and self.value == other.value 523 524 def Substitute(self, name: str, expression: Expression) -> Expression: 525 return self 526 527 528class Literal(Expression): 529 """A runtime literal within the expression tree.""" 530 531 def __init__(self, value: str): 532 self.value = value 533 534 def ToPerfJson(self): 535 return self.value 536 537 def ToPython(self): 538 return f'Literal({self.value})' 539 540 def Simplify(self) -> Expression: 541 return self 542 543 def HasExperimentalEvents(self) -> bool: 544 return False 545 546 def Equals(self, other: Expression) -> bool: 547 return isinstance(other, Literal) and self.value == other.value 548 549 def Substitute(self, name: str, expression: Expression) -> Expression: 550 return self 551 552 553def min(lhs: Union[int, float, Expression], rhs: Union[int, float, 554 Expression]) -> Function: 555 # pylint: disable=redefined-builtin 556 # pylint: disable=invalid-name 557 return Function('min', lhs, rhs) 558 559 560def max(lhs: Union[int, float, Expression], rhs: Union[int, float, 561 Expression]) -> Function: 562 # pylint: disable=redefined-builtin 563 # pylint: disable=invalid-name 564 return Function('max', lhs, rhs) 565 566 567def d_ratio(lhs: Union[int, float, Expression], 568 rhs: Union[int, float, Expression]) -> Function: 569 # pylint: disable=redefined-builtin 570 # pylint: disable=invalid-name 571 return Function('d_ratio', lhs, rhs) 572 573 574def source_count(event: Event) -> Function: 575 # pylint: disable=redefined-builtin 576 # pylint: disable=invalid-name 577 return Function('source_count', event) 578 579 580def aggr_nr(event: Event) -> Function: 581 # pylint: disable=invalid-name 582 return Function('aggr_nr', event) 583 584 585def has_event(event: Event) -> Function: 586 # pylint: disable=redefined-builtin 587 # pylint: disable=invalid-name 588 return Function('has_event', event) 589 590def strcmp_cpuid_str(cpuid: Event) -> Function: 591 # pylint: disable=redefined-builtin 592 # pylint: disable=invalid-name 593 return Function('strcmp_cpuid_str', cpuid) 594 595class Metric: 596 """An individual metric that will specifiable on the perf command line.""" 597 groups: Set[str] 598 expr: Expression 599 scale_unit: str 600 constraint: MetricConstraint 601 threshold: Optional[Expression] 602 603 def __init__(self, 604 name: str, 605 description: str, 606 expr: Expression, 607 scale_unit: str, 608 constraint: MetricConstraint = MetricConstraint.GROUPED_EVENTS, 609 threshold: Optional[Expression] = None): 610 self.name = name 611 self.description = description 612 self.expr = expr.Simplify() 613 if self.expr.HasExperimentalEvents(): 614 self.description += " (metric should be considered experimental as it contains experimental events)." 615 # Workraound valid_only_metric hiding certain metrics based on unit. 616 scale_unit = scale_unit.replace('/sec', ' per sec') 617 if scale_unit[0].isdigit(): 618 self.scale_unit = scale_unit 619 else: 620 self.scale_unit = f'1{scale_unit}' 621 self.constraint = constraint 622 self.threshold = threshold 623 self.groups = set() 624 625 def __lt__(self, other): 626 """Sort order.""" 627 if self.name != other.name: 628 return self.name < other.name 629 if not self.expr.Equals(other.expr): 630 return self.expr.ToPerfJson() < other.expr.ToPerfJson() 631 return self.description < other.description 632 633 def AddToMetricGroup(self, group): 634 """Callback used when being added to a MetricGroup.""" 635 if group.name: 636 self.groups.add(group.name) 637 638 def Flatten(self) -> Set['Metric']: 639 """Return a leaf metric.""" 640 return set([self]) 641 642 def ToPerfJson(self) -> Dict[str, str]: 643 """Return as dictionary for Json generation.""" 644 result = { 645 'MetricName': self.name, 646 'MetricGroup': ';'.join(sorted(self.groups)), 647 'BriefDescription': self.description, 648 'MetricExpr': self.expr.ToPerfJson(), 649 'ScaleUnit': self.scale_unit 650 } 651 if self.constraint != MetricConstraint.GROUPED_EVENTS: 652 result['MetricConstraint'] = self.constraint.name 653 if self.threshold: 654 result['MetricThreshold'] = self.threshold.ToPerfJson() 655 656 return result 657 658 def ToMetricGroupDescriptions(self, root: bool = True) -> Dict[str, str]: 659 return {} 660 661class MetricGroup: 662 """A group of metrics. 663 664 Metric groups may be specificd on the perf command line, but within 665 the json they aren't encoded. Metrics may be in multiple groups 666 which can facilitate arrangements similar to trees. 667 """ 668 669 def __init__(self, name: str, 670 metric_list: List[Union[Optional[Metric], Optional['MetricGroup']]], 671 description: Optional[str] = None): 672 self.name = name 673 self.metric_list = [] 674 self.description = description 675 for metric in metric_list: 676 if metric: 677 self.metric_list.append(metric) 678 metric.AddToMetricGroup(self) 679 680 def AddToMetricGroup(self, group): 681 """Callback used when a MetricGroup is added into another.""" 682 for metric in self.metric_list: 683 metric.AddToMetricGroup(group) 684 685 def Flatten(self) -> Set[Metric]: 686 """Returns a set of all leaf metrics.""" 687 result: Set[Metric] = set() 688 for x in self.metric_list: 689 result = result.union(x.Flatten()) 690 691 return result 692 693 def ToPerfJson(self) -> List[Dict[str, str]]: 694 result = [] 695 for x in sorted(self.Flatten()): 696 result.append(x.ToPerfJson()) 697 return result 698 699 def ToMetricGroupDescriptions(self, root: bool = True) -> Dict[str, str]: 700 result = {self.name: self.description} if self.description else {} 701 for x in self.metric_list: 702 result.update(x.ToMetricGroupDescriptions(False)) 703 return result 704 705 def __str__(self) -> str: 706 return str(self.ToPerfJson()) 707 708 709def JsonEncodeMetric(x: MetricGroup): 710 class MetricJsonEncoder(json.JSONEncoder): 711 """Special handling for Metric objects.""" 712 713 def default(self, o): 714 if isinstance(o, Metric) or isinstance(o, MetricGroup): 715 return o.ToPerfJson() 716 return json.JSONEncoder.default(self, o) 717 718 return json.dumps(x, indent=2, cls=MetricJsonEncoder) 719 720 721def JsonEncodeMetricGroupDescriptions(x: MetricGroup): 722 return json.dumps(x.ToMetricGroupDescriptions(), indent=2) 723 724 725class _RewriteIfExpToSelect(ast.NodeTransformer): 726 """Transformer to convert if-else nodes to Select expressions.""" 727 728 def visit_IfExp(self, node): 729 # pylint: disable=invalid-name 730 self.generic_visit(node) 731 call = ast.Call( 732 func=ast.Name(id='Select', ctx=ast.Load()), 733 args=[node.body, node.test, node.orelse], 734 keywords=[]) 735 ast.copy_location(call, node.test) 736 return call 737 738 739def ParsePerfJson(orig: str) -> Expression: 740 """A simple json metric expression decoder. 741 742 Converts a json encoded metric expression by way of python's ast and 743 eval routine. First tokens are mapped to Event calls, then 744 accidentally converted keywords or literals are mapped to their 745 appropriate calls. Python's ast is used to match if-else that can't 746 be handled via operator overloading. Finally the ast is evaluated. 747 748 Args: 749 orig (str): String to parse. 750 751 Returns: 752 Expression: The parsed string. 753 """ 754 # pylint: disable=eval-used 755 py = orig.strip() 756 # First try to convert everything that looks like a string (event name) into Event(r"EVENT_NAME"). 757 # This isn't very selective so is followed up by converting some unwanted conversions back again 758 py = re.sub(r'([a-zA-Z][^-+/\* \\\(\),]*(?:\\.[^-+/\* \\\(\),]*)*)', 759 r'Event(r"\1")', py) 760 # If it started with a # it should have been a literal, rather than an event name 761 py = re.sub(r'#Event\(r"([^"]*)"\)', r'Literal("#\1")', py) 762 # Fix events wrongly broken at a ',' 763 while True: 764 prev_py = py 765 py = re.sub(r'Event\(r"([^"]*)"\),Event\(r"([^"]*)"\)', r'Event(r"\1,\2")', py) 766 if py == prev_py: 767 break 768 # Convert accidentally converted hex constants ("0Event(r"xDEADBEEF)"") back to a constant, 769 # but keep it wrapped in Event(), otherwise Python drops the 0x prefix and it gets interpreted as 770 # a double by the Bison parser 771 py = re.sub(r'0Event\(r"[xX]([0-9a-fA-F]*)"\)', r'Event("0x\1")', py) 772 # Convert accidentally converted scientific notation constants back 773 py = re.sub(r'([0-9]+)Event\(r"(e[0-9]*)"\)', r'\1\2', py) 774 # Convert all the known keywords back from events to just the keyword 775 keywords = ['if', 'else', 'min', 'max', 'd_ratio', 'source_count', 'aggr_nr', 'has_event', 'strcmp_cpuid_str'] 776 for kw in keywords: 777 py = re.sub(rf'Event\(r"{kw}"\)', kw, py) 778 try: 779 parsed = ast.parse(py, mode='eval') 780 except SyntaxError as e: 781 raise SyntaxError(f'Parsing expression:\n{orig}') from e 782 _RewriteIfExpToSelect().visit(parsed) 783 parsed = ast.fix_missing_locations(parsed) 784 return _Constify(eval(compile(parsed, orig, 'eval'))) 785 786def RewriteMetricsInTermsOfOthers(metrics: List[Tuple[str, str, Expression]] 787 )-> Dict[Tuple[str, str], Expression]: 788 """Shorten metrics by rewriting in terms of others. 789 790 Args: 791 metrics (list): pmus, metric names and their expressions. 792 Returns: 793 Dict: mapping from a pmu, metric name pair to a shortened expression. 794 """ 795 updates: Dict[Tuple[str, str], Expression] = dict() 796 for outer_pmu, outer_name, outer_expression in metrics: 797 if outer_pmu is None: 798 outer_pmu = 'cpu' 799 updated = outer_expression 800 while True: 801 for inner_pmu, inner_name, inner_expression in metrics: 802 if inner_pmu is None: 803 inner_pmu = 'cpu' 804 if inner_pmu.lower() != outer_pmu.lower(): 805 continue 806 if inner_name.lower() == outer_name.lower(): 807 continue 808 if (inner_pmu, inner_name) in updates: 809 inner_expression = updates[(inner_pmu, inner_name)] 810 updated = updated.Substitute(inner_name, inner_expression) 811 if updated.Equals(outer_expression): 812 break 813 if (outer_pmu, outer_name) in updates and updated.Equals(updates[(outer_pmu, outer_name)]): 814 break 815 updates[(outer_pmu, outer_name)] = updated 816 return updates 817