Add RewriteMetricsInTermsOfOthers that iterates over pairs of names and expressions trying to replace an expression, within the current expression, with its name.
Signed-off-by: Ian Rogers <irog...@google.com> --- tools/perf/pmu-events/metric.py | 73 +++++++++++++++++++++++++++- tools/perf/pmu-events/metric_test.py | 10 ++++ 2 files changed, 81 insertions(+), 2 deletions(-) diff --git a/tools/perf/pmu-events/metric.py b/tools/perf/pmu-events/metric.py index 2f2fd220e843..ed13efac7389 100644 --- a/tools/perf/pmu-events/metric.py +++ b/tools/perf/pmu-events/metric.py @@ -4,7 +4,7 @@ import ast import decimal import json import re -from typing import Dict, List, Optional, Set, Union +from typing import Dict, List, Optional, Set, Tuple, Union class Expression: @@ -26,6 +26,9 @@ class Expression: """Returns true when two expressions are the same.""" raise NotImplementedError() + def Substitute(self, name: str, expression: 'Expression') -> 'Expression': + raise NotImplementedError() + def __str__(self) -> str: return self.ToPerfJson() @@ -186,6 +189,15 @@ class Operator(Expression): other.lhs) and self.rhs.Equals(other.rhs) return False + def Substitute(self, name: str, expression: Expression) -> Expression: + if self.Equals(expression): + return Event(name) + lhs = self.lhs.Substitute(name, expression) + rhs = None + if self.rhs: + rhs = self.rhs.Substitute(name, expression) + return Operator(self.operator, lhs, rhs) + class Select(Expression): """Represents a select ternary in the parse tree.""" @@ -225,6 +237,14 @@ class Select(Expression): other.false_val) and self.true_val.Equals(other.true_val) return False + def Substitute(self, name: str, expression: Expression) -> Expression: + if self.Equals(expression): + return Event(name) + true_val = self.true_val.Substitute(name, expression) + cond = self.cond.Substitute(name, expression) + false_val = self.false_val.Substitute(name, expression) + return Select(true_val, cond, false_val) + class Function(Expression): """A function in an expression like min, max, d_ratio.""" @@ -267,6 +287,15 @@ class Function(Expression): return result return False + def Substitute(self, name: str, expression: Expression) -> Expression: + if self.Equals(expression): + return Event(name) + lhs = self.lhs.Substitute(name, expression) + rhs = None + if self.rhs: + rhs = self.rhs.Substitute(name, expression) + return Function(self.fn, lhs, rhs) + def _FixEscapes(s: str) -> str: s = re.sub(r'([^\\]),', r'\1\\,', s) @@ -293,6 +322,9 @@ class Event(Expression): def Equals(self, other: Expression) -> bool: return isinstance(other, Event) and self.name == other.name + def Substitute(self, name: str, expression: Expression) -> Expression: + return self + class Constant(Expression): """A constant within the expression tree.""" @@ -317,6 +349,9 @@ class Constant(Expression): def Equals(self, other: Expression) -> bool: return isinstance(other, Constant) and self.value == other.value + def Substitute(self, name: str, expression: Expression) -> Expression: + return self + class Literal(Expression): """A runtime literal within the expression tree.""" @@ -336,6 +371,9 @@ class Literal(Expression): def Equals(self, other: Expression) -> bool: return isinstance(other, Literal) and self.value == other.value + def Substitute(self, name: str, expression: Expression) -> Expression: + return self + def min(lhs: Union[int, float, Expression], rhs: Union[int, float, Expression]) -> Function: @@ -461,6 +499,7 @@ class MetricGroup: class _RewriteIfExpToSelect(ast.NodeTransformer): + """Transformer to convert if-else nodes to Select expressions.""" def visit_IfExp(self, node): # pylint: disable=invalid-name @@ -498,7 +537,37 @@ def ParsePerfJson(orig: str) -> Expression: for kw in keywords: py = re.sub(rf'Event\(r"{kw}"\)', kw, py) - parsed = ast.parse(py, mode='eval') + try: + parsed = ast.parse(py, mode='eval') + except SyntaxError as e: + raise SyntaxError(f'Parsing expression:\n{orig}') from e _RewriteIfExpToSelect().visit(parsed) parsed = ast.fix_missing_locations(parsed) return _Constify(eval(compile(parsed, orig, 'eval'))) + + +def RewriteMetricsInTermsOfOthers(metrics: list[Tuple[str, Expression]] + )-> Dict[str, Expression]: + """Shorten metrics by rewriting in terms of others. + + Args: + metrics (list): pairs of metric names and their expressions. + Returns: + Dict: mapping from a metric name to a shortened expression. + """ + updates: Dict[str, Expression] = dict() + for outer_name, outer_expression in metrics: + updated = outer_expression + while True: + for inner_name, inner_expression in metrics: + if inner_name.lower() == outer_name.lower(): + continue + if inner_name in updates: + inner_expression = updates[inner_name] + updated = updated.Substitute(inner_name, inner_expression) + if updated.Equals(outer_expression): + break + if outer_name in updates and updated.Equals(updates[outer_name]): + break + updates[outer_name] = updated + return updates diff --git a/tools/perf/pmu-events/metric_test.py b/tools/perf/pmu-events/metric_test.py index 15315d0f716c..ced5998bd827 100644 --- a/tools/perf/pmu-events/metric_test.py +++ b/tools/perf/pmu-events/metric_test.py @@ -2,7 +2,9 @@ import unittest from metric import Constant from metric import Event +from metric import Expression from metric import ParsePerfJson +from metric import RewriteMetricsInTermsOfOthers class TestMetricExpressions(unittest.TestCase): @@ -153,5 +155,13 @@ class TestMetricExpressions(unittest.TestCase): after = '0 * SLOTS' self.assertEqual(ParsePerfJson(before).Simplify().ToPerfJson(), after) + def test_RewriteMetricsInTermsOfOthers(self): + Expression.__eq__ = lambda e1, e2: e1.Equals(e2) + before = [('m1', ParsePerfJson('a + b + c + d')), + ('m2', ParsePerfJson('a + b + c'))] + after = {'m1': ParsePerfJson('m2 + d')} + self.assertEqual(RewriteMetricsInTermsOfOthers(before), after) + Expression.__eq__ = None + if __name__ == '__main__': unittest.main() -- 2.39.1.456.gfc5497dd1b-goog