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