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 + self.operator + rhs)) 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 self.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 = None 320 if self.rhs: 321 rhs = self.rhs.Substitute(name, expression) 322 return Operator(self.operator, lhs, rhs) 323 324 325class Select(Expression): 326 """Represents a select ternary in the parse tree.""" 327 328 def __init__(self, true_val: Union[int, float, Expression], 329 cond: Union[int, float, Expression], 330 false_val: Union[int, float, Expression]): 331 self.true_val = _Constify(true_val) 332 self.cond = _Constify(cond) 333 self.false_val = _Constify(false_val) 334 335 def ToPerfJson(self): 336 true_str = self.true_val.ToPerfJson() 337 cond_str = self.cond.ToPerfJson() 338 false_str = self.false_val.ToPerfJson() 339 return f'({true_str} if {cond_str} else {false_str})' 340 341 def ToPython(self): 342 return (f'Select({self.true_val.ToPython()}, {self.cond.ToPython()}, ' 343 f'{self.false_val.ToPython()})') 344 345 def Simplify(self) -> Expression: 346 cond = self.cond.Simplify() 347 true_val = self.true_val.Simplify() 348 false_val = self.false_val.Simplify() 349 if isinstance(cond, Constant): 350 return false_val if cond.value == '0' else true_val 351 352 if true_val.Equals(false_val): 353 return true_val 354 355 return Select(true_val, cond, false_val) 356 357 def HasExperimentalEvents(self) -> bool: 358 return (self.cond.HasExperimentalEvents() or self.true_val.HasExperimentalEvents() or 359 self.false_val.HasExperimentalEvents()) 360 361 def Equals(self, other: Expression) -> bool: 362 if isinstance(other, Select): 363 return self.cond.Equals(other.cond) and self.false_val.Equals( 364 other.false_val) and self.true_val.Equals(other.true_val) 365 return False 366 367 def Substitute(self, name: str, expression: Expression) -> Expression: 368 if self.Equals(expression): 369 return Event(name) 370 true_val = self.true_val.Substitute(name, expression) 371 cond = self.cond.Substitute(name, expression) 372 false_val = self.false_val.Substitute(name, expression) 373 return Select(true_val, cond, false_val) 374 375 376class Function(Expression): 377 """A function in an expression like min, max, d_ratio.""" 378 379 def __init__(self, 380 fn: str, 381 lhs: Union[int, float, Expression], 382 rhs: Optional[Union[int, float, Expression]] = None): 383 self.fn = fn 384 self.lhs = _Constify(lhs) 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 (self.rhs and self.rhs.HasExperimentalEvents()) 411 412 def Equals(self, other: Expression) -> bool: 413 if isinstance(other, Function): 414 result = self.fn == other.fn and self.lhs.Equals(other.lhs) 415 if self.rhs: 416 result = result and self.rhs.Equals(other.rhs) 417 return result 418 return False 419 420 def Substitute(self, name: str, expression: Expression) -> Expression: 421 if self.Equals(expression): 422 return Event(name) 423 lhs = self.lhs.Substitute(name, expression) 424 rhs = None 425 if self.rhs: 426 rhs = self.rhs.Substitute(name, expression) 427 return Function(self.fn, lhs, rhs) 428 429 430def _FixEscapes(s: str) -> str: 431 s = re.sub(r'([^\\]),', r'\1\\,', s) 432 return re.sub(r'([^\\])=', r'\1\\=', s) 433 434 435class Event(Expression): 436 """An event in an expression.""" 437 438 def __init__(self, *args: str): 439 error = "" 440 CheckEveryEvent(*args) 441 for name in args: 442 if CheckEvent(name): 443 self.name = _FixEscapes(name) 444 return 445 if error: 446 error += " or " + name 447 else: 448 error = name 449 global all_events 450 raise Exception(f"No event {error} in:\n{all_events}") 451 452 def HasExperimentalEvents(self) -> bool: 453 return IsExperimentalEvent(self.name) 454 455 def ToPerfJson(self): 456 result = re.sub('/', '@', self.name) 457 return result 458 459 def ToPython(self): 460 return f'Event(r"{self.name}")' 461 462 def Simplify(self) -> Expression: 463 return self 464 465 def Equals(self, other: Expression) -> bool: 466 return isinstance(other, Event) and self.name == other.name 467 468 def Substitute(self, name: str, expression: Expression) -> Expression: 469 return self 470 471 472class MetricRef(Expression): 473 """A metric reference in an expression.""" 474 475 def __init__(self, name: str): 476 self.name = _FixEscapes(name) 477 478 def ToPerfJson(self): 479 return self.name 480 481 def ToPython(self): 482 return f'MetricRef(r"{self.name}")' 483 484 def Simplify(self) -> Expression: 485 return self 486 487 def HasExperimentalEvents(self) -> bool: 488 return False 489 490 def Equals(self, other: Expression) -> bool: 491 return isinstance(other, MetricRef) and self.name == other.name 492 493 def Substitute(self, name: str, expression: Expression) -> Expression: 494 return self 495 496 497class Constant(Expression): 498 """A constant within the expression tree.""" 499 500 def __init__(self, value: Union[float, str]): 501 ctx = decimal.Context() 502 ctx.prec = 20 503 dec = ctx.create_decimal(repr(value) if isinstance(value, float) else value) 504 self.value = dec.normalize().to_eng_string() 505 self.value = self.value.replace('+', '') 506 self.value = self.value.replace('E', 'e') 507 508 def ToPerfJson(self): 509 return self.value 510 511 def ToPython(self): 512 return f'Constant({self.value})' 513 514 def Simplify(self) -> Expression: 515 return self 516 517 def HasExperimentalEvents(self) -> bool: 518 return False 519 520 def Equals(self, other: Expression) -> bool: 521 return isinstance(other, Constant) and self.value == other.value 522 523 def Substitute(self, name: str, expression: Expression) -> Expression: 524 return self 525 526 527class Literal(Expression): 528 """A runtime literal within the expression tree.""" 529 530 def __init__(self, value: str): 531 self.value = value 532 533 def ToPerfJson(self): 534 return self.value 535 536 def ToPython(self): 537 return f'Literal({self.value})' 538 539 def Simplify(self) -> Expression: 540 return self 541 542 def HasExperimentalEvents(self) -> bool: 543 return False 544 545 def Equals(self, other: Expression) -> bool: 546 return isinstance(other, Literal) and self.value == other.value 547 548 def Substitute(self, name: str, expression: Expression) -> Expression: 549 return self 550 551 552def min(lhs: Union[int, float, Expression], rhs: Union[int, float, 553 Expression]) -> Function: 554 # pylint: disable=redefined-builtin 555 # pylint: disable=invalid-name 556 return Function('min', lhs, rhs) 557 558 559def max(lhs: Union[int, float, Expression], rhs: Union[int, float, 560 Expression]) -> Function: 561 # pylint: disable=redefined-builtin 562 # pylint: disable=invalid-name 563 return Function('max', lhs, rhs) 564 565 566def d_ratio(lhs: Union[int, float, Expression], 567 rhs: Union[int, float, Expression]) -> Function: 568 # pylint: disable=redefined-builtin 569 # pylint: disable=invalid-name 570 return Function('d_ratio', lhs, rhs) 571 572 573def source_count(event: Event) -> Function: 574 # pylint: disable=redefined-builtin 575 # pylint: disable=invalid-name 576 return Function('source_count', event) 577 578 579def aggr_nr(event: Event) -> Function: 580 # pylint: disable=invalid-name 581 return Function('aggr_nr', event) 582 583 584def has_event(event: Event) -> Function: 585 # pylint: disable=redefined-builtin 586 # pylint: disable=invalid-name 587 return Function('has_event', event) 588 589def strcmp_cpuid_str(cpuid: Event) -> Function: 590 # pylint: disable=redefined-builtin 591 # pylint: disable=invalid-name 592 return Function('strcmp_cpuid_str', cpuid) 593 594class Metric: 595 """An individual metric that will specifiable on the perf command line.""" 596 groups: Set[str] 597 expr: Expression 598 scale_unit: str 599 constraint: MetricConstraint 600 threshold: Optional[Expression] 601 602 def __init__(self, 603 name: str, 604 description: str, 605 expr: Expression, 606 scale_unit: str, 607 constraint: MetricConstraint = MetricConstraint.GROUPED_EVENTS, 608 threshold: Optional[Expression] = None): 609 self.name = name 610 self.description = description 611 self.expr = expr.Simplify() 612 if self.expr.HasExperimentalEvents(): 613 self.description += " (metric should be considered experimental as it contains experimental events)." 614 # Workraound valid_only_metric hiding certain metrics based on unit. 615 scale_unit = scale_unit.replace('/sec', ' per sec') 616 if scale_unit[0].isdigit(): 617 self.scale_unit = scale_unit 618 else: 619 self.scale_unit = f'1{scale_unit}' 620 self.constraint = constraint 621 self.threshold = threshold 622 self.groups = set() 623 624 def __lt__(self, other): 625 """Sort order.""" 626 return self.name < other.name 627 628 def AddToMetricGroup(self, group): 629 """Callback used when being added to a MetricGroup.""" 630 if group.name: 631 self.groups.add(group.name) 632 633 def Flatten(self) -> Set['Metric']: 634 """Return a leaf metric.""" 635 return set([self]) 636 637 def ToPerfJson(self) -> Dict[str, str]: 638 """Return as dictionary for Json generation.""" 639 result = { 640 'MetricName': self.name, 641 'MetricGroup': ';'.join(sorted(self.groups)), 642 'BriefDescription': self.description, 643 'MetricExpr': self.expr.ToPerfJson(), 644 'ScaleUnit': self.scale_unit 645 } 646 if self.constraint != MetricConstraint.GROUPED_EVENTS: 647 result['MetricConstraint'] = self.constraint.name 648 if self.threshold: 649 result['MetricThreshold'] = self.threshold.ToPerfJson() 650 651 return result 652 653 def ToMetricGroupDescriptions(self, root: bool = True) -> Dict[str, str]: 654 return {} 655 656class MetricGroup: 657 """A group of metrics. 658 659 Metric groups may be specificd on the perf command line, but within 660 the json they aren't encoded. Metrics may be in multiple groups 661 which can facilitate arrangements similar to trees. 662 """ 663 664 def __init__(self, name: str, 665 metric_list: List[Union[Optional[Metric], Optional['MetricGroup']]], 666 description: Optional[str] = None): 667 self.name = name 668 self.metric_list = [] 669 self.description = description 670 for metric in metric_list: 671 if metric: 672 self.metric_list.append(metric) 673 metric.AddToMetricGroup(self) 674 675 def AddToMetricGroup(self, group): 676 """Callback used when a MetricGroup is added into another.""" 677 for metric in self.metric_list: 678 metric.AddToMetricGroup(group) 679 680 def Flatten(self) -> Set[Metric]: 681 """Returns a set of all leaf metrics.""" 682 result = set() 683 for x in self.metric_list: 684 result = result.union(x.Flatten()) 685 686 return result 687 688 def ToPerfJson(self) -> List[Dict[str, str]]: 689 result = [] 690 for x in sorted(self.Flatten()): 691 result.append(x.ToPerfJson()) 692 return result 693 694 def ToMetricGroupDescriptions(self, root: bool = True) -> Dict[str, str]: 695 result = {self.name: self.description} if self.description else {} 696 for x in self.metric_list: 697 result.update(x.ToMetricGroupDescriptions(False)) 698 return result 699 700 def __str__(self) -> str: 701 return str(self.ToPerfJson()) 702 703 704def JsonEncodeMetric(x: MetricGroup): 705 class MetricJsonEncoder(json.JSONEncoder): 706 """Special handling for Metric objects.""" 707 708 def default(self, o): 709 if isinstance(o, Metric) or isinstance(o, MetricGroup): 710 return o.ToPerfJson() 711 return json.JSONEncoder.default(self, o) 712 713 return json.dumps(x, indent=2, cls=MetricJsonEncoder) 714 715 716def JsonEncodeMetricGroupDescriptions(x: MetricGroup): 717 return json.dumps(x.ToMetricGroupDescriptions(), indent=2) 718 719 720class _RewriteIfExpToSelect(ast.NodeTransformer): 721 """Transformer to convert if-else nodes to Select expressions.""" 722 723 def visit_IfExp(self, node): 724 # pylint: disable=invalid-name 725 self.generic_visit(node) 726 call = ast.Call( 727 func=ast.Name(id='Select', ctx=ast.Load()), 728 args=[node.body, node.test, node.orelse], 729 keywords=[]) 730 ast.copy_location(call, node.test) 731 return call 732 733 734def ParsePerfJson(orig: str) -> Expression: 735 """A simple json metric expression decoder. 736 737 Converts a json encoded metric expression by way of python's ast and 738 eval routine. First tokens are mapped to Event calls, then 739 accidentally converted keywords or literals are mapped to their 740 appropriate calls. Python's ast is used to match if-else that can't 741 be handled via operator overloading. Finally the ast is evaluated. 742 743 Args: 744 orig (str): String to parse. 745 746 Returns: 747 Expression: The parsed string. 748 """ 749 # pylint: disable=eval-used 750 py = orig.strip() 751 # First try to convert everything that looks like a string (event name) into Event(r"EVENT_NAME"). 752 # This isn't very selective so is followed up by converting some unwanted conversions back again 753 py = re.sub(r'([a-zA-Z][^-+/\* \\\(\),]*(?:\\.[^-+/\* \\\(\),]*)*)', 754 r'Event(r"\1")', py) 755 # If it started with a # it should have been a literal, rather than an event name 756 py = re.sub(r'#Event\(r"([^"]*)"\)', r'Literal("#\1")', py) 757 # Fix events wrongly broken at a ',' 758 while True: 759 prev_py = py 760 py = re.sub(r'Event\(r"([^"]*)"\),Event\(r"([^"]*)"\)', r'Event(r"\1,\2")', py) 761 if py == prev_py: 762 break 763 # Convert accidentally converted hex constants ("0Event(r"xDEADBEEF)"") back to a constant, 764 # but keep it wrapped in Event(), otherwise Python drops the 0x prefix and it gets interpreted as 765 # a double by the Bison parser 766 py = re.sub(r'0Event\(r"[xX]([0-9a-fA-F]*)"\)', r'Event("0x\1")', py) 767 # Convert accidentally converted scientific notation constants back 768 py = re.sub(r'([0-9]+)Event\(r"(e[0-9]*)"\)', r'\1\2', py) 769 # Convert all the known keywords back from events to just the keyword 770 keywords = ['if', 'else', 'min', 'max', 'd_ratio', 'source_count', 'aggr_nr', 'has_event', 'strcmp_cpuid_str'] 771 for kw in keywords: 772 py = re.sub(rf'Event\(r"{kw}"\)', kw, py) 773 try: 774 parsed = ast.parse(py, mode='eval') 775 except SyntaxError as e: 776 raise SyntaxError(f'Parsing expression:\n{orig}') from e 777 _RewriteIfExpToSelect().visit(parsed) 778 parsed = ast.fix_missing_locations(parsed) 779 return _Constify(eval(compile(parsed, orig, 'eval'))) 780 781def RewriteMetricsInTermsOfOthers(metrics: List[Tuple[str, str, Expression]] 782 )-> Dict[Tuple[str, str], Expression]: 783 """Shorten metrics by rewriting in terms of others. 784 785 Args: 786 metrics (list): pmus, metric names and their expressions. 787 Returns: 788 Dict: mapping from a pmu, metric name pair to a shortened expression. 789 """ 790 updates: Dict[Tuple[str, str], Expression] = dict() 791 for outer_pmu, outer_name, outer_expression in metrics: 792 if outer_pmu is None: 793 outer_pmu = 'cpu' 794 updated = outer_expression 795 while True: 796 for inner_pmu, inner_name, inner_expression in metrics: 797 if inner_pmu is None: 798 inner_pmu = 'cpu' 799 if inner_pmu.lower() != outer_pmu.lower(): 800 continue 801 if inner_name.lower() == outer_name.lower(): 802 continue 803 if (inner_pmu, inner_name) in updates: 804 inner_expression = updates[(inner_pmu, inner_name)] 805 updated = updated.Substitute(inner_name, inner_expression) 806 if updated.Equals(outer_expression): 807 break 808 if (outer_pmu, outer_name) in updates and updated.Equals(updates[(outer_pmu, outer_name)]): 809 break 810 updates[(outer_pmu, outer_name)] = updated 811 return updates 812