162306a36Sopenharmony_ci# SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause)
262306a36Sopenharmony_ci"""Parse or generate representations of perf metrics."""
362306a36Sopenharmony_ciimport ast
462306a36Sopenharmony_ciimport decimal
562306a36Sopenharmony_ciimport json
662306a36Sopenharmony_ciimport re
762306a36Sopenharmony_cifrom typing import Dict, List, Optional, Set, Tuple, Union
862306a36Sopenharmony_ci
962306a36Sopenharmony_ci
1062306a36Sopenharmony_ciclass Expression:
1162306a36Sopenharmony_ci  """Abstract base class of elements in a metric expression."""
1262306a36Sopenharmony_ci
1362306a36Sopenharmony_ci  def ToPerfJson(self) -> str:
1462306a36Sopenharmony_ci    """Returns a perf json file encoded representation."""
1562306a36Sopenharmony_ci    raise NotImplementedError()
1662306a36Sopenharmony_ci
1762306a36Sopenharmony_ci  def ToPython(self) -> str:
1862306a36Sopenharmony_ci    """Returns a python expr parseable representation."""
1962306a36Sopenharmony_ci    raise NotImplementedError()
2062306a36Sopenharmony_ci
2162306a36Sopenharmony_ci  def Simplify(self):
2262306a36Sopenharmony_ci    """Returns a simplified version of self."""
2362306a36Sopenharmony_ci    raise NotImplementedError()
2462306a36Sopenharmony_ci
2562306a36Sopenharmony_ci  def Equals(self, other) -> bool:
2662306a36Sopenharmony_ci    """Returns true when two expressions are the same."""
2762306a36Sopenharmony_ci    raise NotImplementedError()
2862306a36Sopenharmony_ci
2962306a36Sopenharmony_ci  def Substitute(self, name: str, expression: 'Expression') -> 'Expression':
3062306a36Sopenharmony_ci    raise NotImplementedError()
3162306a36Sopenharmony_ci
3262306a36Sopenharmony_ci  def __str__(self) -> str:
3362306a36Sopenharmony_ci    return self.ToPerfJson()
3462306a36Sopenharmony_ci
3562306a36Sopenharmony_ci  def __or__(self, other: Union[int, float, 'Expression']) -> 'Operator':
3662306a36Sopenharmony_ci    return Operator('|', self, other)
3762306a36Sopenharmony_ci
3862306a36Sopenharmony_ci  def __ror__(self, other: Union[int, float, 'Expression']) -> 'Operator':
3962306a36Sopenharmony_ci    return Operator('|', other, self)
4062306a36Sopenharmony_ci
4162306a36Sopenharmony_ci  def __xor__(self, other: Union[int, float, 'Expression']) -> 'Operator':
4262306a36Sopenharmony_ci    return Operator('^', self, other)
4362306a36Sopenharmony_ci
4462306a36Sopenharmony_ci  def __and__(self, other: Union[int, float, 'Expression']) -> 'Operator':
4562306a36Sopenharmony_ci    return Operator('&', self, other)
4662306a36Sopenharmony_ci
4762306a36Sopenharmony_ci  def __rand__(self, other: Union[int, float, 'Expression']) -> 'Operator':
4862306a36Sopenharmony_ci    return Operator('&', other, self)
4962306a36Sopenharmony_ci
5062306a36Sopenharmony_ci  def __lt__(self, other: Union[int, float, 'Expression']) -> 'Operator':
5162306a36Sopenharmony_ci    return Operator('<', self, other)
5262306a36Sopenharmony_ci
5362306a36Sopenharmony_ci  def __gt__(self, other: Union[int, float, 'Expression']) -> 'Operator':
5462306a36Sopenharmony_ci    return Operator('>', self, other)
5562306a36Sopenharmony_ci
5662306a36Sopenharmony_ci  def __add__(self, other: Union[int, float, 'Expression']) -> 'Operator':
5762306a36Sopenharmony_ci    return Operator('+', self, other)
5862306a36Sopenharmony_ci
5962306a36Sopenharmony_ci  def __radd__(self, other: Union[int, float, 'Expression']) -> 'Operator':
6062306a36Sopenharmony_ci    return Operator('+', other, self)
6162306a36Sopenharmony_ci
6262306a36Sopenharmony_ci  def __sub__(self, other: Union[int, float, 'Expression']) -> 'Operator':
6362306a36Sopenharmony_ci    return Operator('-', self, other)
6462306a36Sopenharmony_ci
6562306a36Sopenharmony_ci  def __rsub__(self, other: Union[int, float, 'Expression']) -> 'Operator':
6662306a36Sopenharmony_ci    return Operator('-', other, self)
6762306a36Sopenharmony_ci
6862306a36Sopenharmony_ci  def __mul__(self, other: Union[int, float, 'Expression']) -> 'Operator':
6962306a36Sopenharmony_ci    return Operator('*', self, other)
7062306a36Sopenharmony_ci
7162306a36Sopenharmony_ci  def __rmul__(self, other: Union[int, float, 'Expression']) -> 'Operator':
7262306a36Sopenharmony_ci    return Operator('*', other, self)
7362306a36Sopenharmony_ci
7462306a36Sopenharmony_ci  def __truediv__(self, other: Union[int, float, 'Expression']) -> 'Operator':
7562306a36Sopenharmony_ci    return Operator('/', self, other)
7662306a36Sopenharmony_ci
7762306a36Sopenharmony_ci  def __rtruediv__(self, other: Union[int, float, 'Expression']) -> 'Operator':
7862306a36Sopenharmony_ci    return Operator('/', other, self)
7962306a36Sopenharmony_ci
8062306a36Sopenharmony_ci  def __mod__(self, other: Union[int, float, 'Expression']) -> 'Operator':
8162306a36Sopenharmony_ci    return Operator('%', self, other)
8262306a36Sopenharmony_ci
8362306a36Sopenharmony_ci
8462306a36Sopenharmony_cidef _Constify(val: Union[bool, int, float, Expression]) -> Expression:
8562306a36Sopenharmony_ci  """Used to ensure that the nodes in the expression tree are all Expression."""
8662306a36Sopenharmony_ci  if isinstance(val, bool):
8762306a36Sopenharmony_ci    return Constant(1 if val else 0)
8862306a36Sopenharmony_ci  if isinstance(val, (int, float)):
8962306a36Sopenharmony_ci    return Constant(val)
9062306a36Sopenharmony_ci  return val
9162306a36Sopenharmony_ci
9262306a36Sopenharmony_ci
9362306a36Sopenharmony_ci# Simple lookup for operator precedence, used to avoid unnecessary
9462306a36Sopenharmony_ci# brackets. Precedence matches that of the simple expression parser
9562306a36Sopenharmony_ci# but differs from python where comparisons are lower precedence than
9662306a36Sopenharmony_ci# the bitwise &, ^, | but not the logical versions that the expression
9762306a36Sopenharmony_ci# parser doesn't have.
9862306a36Sopenharmony_ci_PRECEDENCE = {
9962306a36Sopenharmony_ci    '|': 0,
10062306a36Sopenharmony_ci    '^': 1,
10162306a36Sopenharmony_ci    '&': 2,
10262306a36Sopenharmony_ci    '<': 3,
10362306a36Sopenharmony_ci    '>': 3,
10462306a36Sopenharmony_ci    '+': 4,
10562306a36Sopenharmony_ci    '-': 4,
10662306a36Sopenharmony_ci    '*': 5,
10762306a36Sopenharmony_ci    '/': 5,
10862306a36Sopenharmony_ci    '%': 5,
10962306a36Sopenharmony_ci}
11062306a36Sopenharmony_ci
11162306a36Sopenharmony_ci
11262306a36Sopenharmony_ciclass Operator(Expression):
11362306a36Sopenharmony_ci  """Represents a binary operator in the parse tree."""
11462306a36Sopenharmony_ci
11562306a36Sopenharmony_ci  def __init__(self, operator: str, lhs: Union[int, float, Expression],
11662306a36Sopenharmony_ci               rhs: Union[int, float, Expression]):
11762306a36Sopenharmony_ci    self.operator = operator
11862306a36Sopenharmony_ci    self.lhs = _Constify(lhs)
11962306a36Sopenharmony_ci    self.rhs = _Constify(rhs)
12062306a36Sopenharmony_ci
12162306a36Sopenharmony_ci  def Bracket(self,
12262306a36Sopenharmony_ci              other: Expression,
12362306a36Sopenharmony_ci              other_str: str,
12462306a36Sopenharmony_ci              rhs: bool = False) -> str:
12562306a36Sopenharmony_ci    """If necessary brackets the given other value.
12662306a36Sopenharmony_ci
12762306a36Sopenharmony_ci    If ``other`` is an operator then a bracket is necessary when
12862306a36Sopenharmony_ci    this/self operator has higher precedence. Consider: '(a + b) * c',
12962306a36Sopenharmony_ci    ``other_str`` will be 'a + b'. A bracket is necessary as without
13062306a36Sopenharmony_ci    the bracket 'a + b * c' will evaluate 'b * c' first. However, '(a
13162306a36Sopenharmony_ci    * b) + c' doesn't need a bracket as 'a * b' will always be
13262306a36Sopenharmony_ci    evaluated first. For 'a / (b * c)' (ie the same precedence level
13362306a36Sopenharmony_ci    operations) then we add the bracket to best match the original
13462306a36Sopenharmony_ci    input, but not for '(a / b) * c' where the bracket is unnecessary.
13562306a36Sopenharmony_ci
13662306a36Sopenharmony_ci    Args:
13762306a36Sopenharmony_ci      other (Expression): is a lhs or rhs operator
13862306a36Sopenharmony_ci      other_str (str): ``other`` in the appropriate string form
13962306a36Sopenharmony_ci      rhs (bool):  is ``other`` on the RHS
14062306a36Sopenharmony_ci
14162306a36Sopenharmony_ci    Returns:
14262306a36Sopenharmony_ci      str: possibly bracketed other_str
14362306a36Sopenharmony_ci    """
14462306a36Sopenharmony_ci    if isinstance(other, Operator):
14562306a36Sopenharmony_ci      if _PRECEDENCE.get(self.operator, -1) > _PRECEDENCE.get(
14662306a36Sopenharmony_ci          other.operator, -1):
14762306a36Sopenharmony_ci        return f'({other_str})'
14862306a36Sopenharmony_ci      if rhs and _PRECEDENCE.get(self.operator, -1) == _PRECEDENCE.get(
14962306a36Sopenharmony_ci          other.operator, -1):
15062306a36Sopenharmony_ci        return f'({other_str})'
15162306a36Sopenharmony_ci    return other_str
15262306a36Sopenharmony_ci
15362306a36Sopenharmony_ci  def ToPerfJson(self):
15462306a36Sopenharmony_ci    return (f'{self.Bracket(self.lhs, self.lhs.ToPerfJson())} {self.operator} '
15562306a36Sopenharmony_ci            f'{self.Bracket(self.rhs, self.rhs.ToPerfJson(), True)}')
15662306a36Sopenharmony_ci
15762306a36Sopenharmony_ci  def ToPython(self):
15862306a36Sopenharmony_ci    return (f'{self.Bracket(self.lhs, self.lhs.ToPython())} {self.operator} '
15962306a36Sopenharmony_ci            f'{self.Bracket(self.rhs, self.rhs.ToPython(), True)}')
16062306a36Sopenharmony_ci
16162306a36Sopenharmony_ci  def Simplify(self) -> Expression:
16262306a36Sopenharmony_ci    lhs = self.lhs.Simplify()
16362306a36Sopenharmony_ci    rhs = self.rhs.Simplify()
16462306a36Sopenharmony_ci    if isinstance(lhs, Constant) and isinstance(rhs, Constant):
16562306a36Sopenharmony_ci      return Constant(ast.literal_eval(lhs + self.operator + rhs))
16662306a36Sopenharmony_ci
16762306a36Sopenharmony_ci    if isinstance(self.lhs, Constant):
16862306a36Sopenharmony_ci      if self.operator in ('+', '|') and lhs.value == '0':
16962306a36Sopenharmony_ci        return rhs
17062306a36Sopenharmony_ci
17162306a36Sopenharmony_ci      # Simplify multiplication by 0 except for the slot event which
17262306a36Sopenharmony_ci      # is deliberately introduced using this pattern.
17362306a36Sopenharmony_ci      if self.operator == '*' and lhs.value == '0' and (
17462306a36Sopenharmony_ci          not isinstance(rhs, Event) or 'slots' not in rhs.name.lower()):
17562306a36Sopenharmony_ci        return Constant(0)
17662306a36Sopenharmony_ci
17762306a36Sopenharmony_ci      if self.operator == '*' and lhs.value == '1':
17862306a36Sopenharmony_ci        return rhs
17962306a36Sopenharmony_ci
18062306a36Sopenharmony_ci    if isinstance(rhs, Constant):
18162306a36Sopenharmony_ci      if self.operator in ('+', '|') and rhs.value == '0':
18262306a36Sopenharmony_ci        return lhs
18362306a36Sopenharmony_ci
18462306a36Sopenharmony_ci      if self.operator == '*' and rhs.value == '0':
18562306a36Sopenharmony_ci        return Constant(0)
18662306a36Sopenharmony_ci
18762306a36Sopenharmony_ci      if self.operator == '*' and self.rhs.value == '1':
18862306a36Sopenharmony_ci        return lhs
18962306a36Sopenharmony_ci
19062306a36Sopenharmony_ci    return Operator(self.operator, lhs, rhs)
19162306a36Sopenharmony_ci
19262306a36Sopenharmony_ci  def Equals(self, other: Expression) -> bool:
19362306a36Sopenharmony_ci    if isinstance(other, Operator):
19462306a36Sopenharmony_ci      return self.operator == other.operator and self.lhs.Equals(
19562306a36Sopenharmony_ci          other.lhs) and self.rhs.Equals(other.rhs)
19662306a36Sopenharmony_ci    return False
19762306a36Sopenharmony_ci
19862306a36Sopenharmony_ci  def Substitute(self, name: str, expression: Expression) -> Expression:
19962306a36Sopenharmony_ci    if self.Equals(expression):
20062306a36Sopenharmony_ci      return Event(name)
20162306a36Sopenharmony_ci    lhs = self.lhs.Substitute(name, expression)
20262306a36Sopenharmony_ci    rhs = None
20362306a36Sopenharmony_ci    if self.rhs:
20462306a36Sopenharmony_ci      rhs = self.rhs.Substitute(name, expression)
20562306a36Sopenharmony_ci    return Operator(self.operator, lhs, rhs)
20662306a36Sopenharmony_ci
20762306a36Sopenharmony_ci
20862306a36Sopenharmony_ciclass Select(Expression):
20962306a36Sopenharmony_ci  """Represents a select ternary in the parse tree."""
21062306a36Sopenharmony_ci
21162306a36Sopenharmony_ci  def __init__(self, true_val: Union[int, float, Expression],
21262306a36Sopenharmony_ci               cond: Union[int, float, Expression],
21362306a36Sopenharmony_ci               false_val: Union[int, float, Expression]):
21462306a36Sopenharmony_ci    self.true_val = _Constify(true_val)
21562306a36Sopenharmony_ci    self.cond = _Constify(cond)
21662306a36Sopenharmony_ci    self.false_val = _Constify(false_val)
21762306a36Sopenharmony_ci
21862306a36Sopenharmony_ci  def ToPerfJson(self):
21962306a36Sopenharmony_ci    true_str = self.true_val.ToPerfJson()
22062306a36Sopenharmony_ci    cond_str = self.cond.ToPerfJson()
22162306a36Sopenharmony_ci    false_str = self.false_val.ToPerfJson()
22262306a36Sopenharmony_ci    return f'({true_str} if {cond_str} else {false_str})'
22362306a36Sopenharmony_ci
22462306a36Sopenharmony_ci  def ToPython(self):
22562306a36Sopenharmony_ci    return (f'Select({self.true_val.ToPython()}, {self.cond.ToPython()}, '
22662306a36Sopenharmony_ci            f'{self.false_val.ToPython()})')
22762306a36Sopenharmony_ci
22862306a36Sopenharmony_ci  def Simplify(self) -> Expression:
22962306a36Sopenharmony_ci    cond = self.cond.Simplify()
23062306a36Sopenharmony_ci    true_val = self.true_val.Simplify()
23162306a36Sopenharmony_ci    false_val = self.false_val.Simplify()
23262306a36Sopenharmony_ci    if isinstance(cond, Constant):
23362306a36Sopenharmony_ci      return false_val if cond.value == '0' else true_val
23462306a36Sopenharmony_ci
23562306a36Sopenharmony_ci    if true_val.Equals(false_val):
23662306a36Sopenharmony_ci      return true_val
23762306a36Sopenharmony_ci
23862306a36Sopenharmony_ci    return Select(true_val, cond, false_val)
23962306a36Sopenharmony_ci
24062306a36Sopenharmony_ci  def Equals(self, other: Expression) -> bool:
24162306a36Sopenharmony_ci    if isinstance(other, Select):
24262306a36Sopenharmony_ci      return self.cond.Equals(other.cond) and self.false_val.Equals(
24362306a36Sopenharmony_ci          other.false_val) and self.true_val.Equals(other.true_val)
24462306a36Sopenharmony_ci    return False
24562306a36Sopenharmony_ci
24662306a36Sopenharmony_ci  def Substitute(self, name: str, expression: Expression) -> Expression:
24762306a36Sopenharmony_ci    if self.Equals(expression):
24862306a36Sopenharmony_ci      return Event(name)
24962306a36Sopenharmony_ci    true_val = self.true_val.Substitute(name, expression)
25062306a36Sopenharmony_ci    cond = self.cond.Substitute(name, expression)
25162306a36Sopenharmony_ci    false_val = self.false_val.Substitute(name, expression)
25262306a36Sopenharmony_ci    return Select(true_val, cond, false_val)
25362306a36Sopenharmony_ci
25462306a36Sopenharmony_ci
25562306a36Sopenharmony_ciclass Function(Expression):
25662306a36Sopenharmony_ci  """A function in an expression like min, max, d_ratio."""
25762306a36Sopenharmony_ci
25862306a36Sopenharmony_ci  def __init__(self,
25962306a36Sopenharmony_ci               fn: str,
26062306a36Sopenharmony_ci               lhs: Union[int, float, Expression],
26162306a36Sopenharmony_ci               rhs: Optional[Union[int, float, Expression]] = None):
26262306a36Sopenharmony_ci    self.fn = fn
26362306a36Sopenharmony_ci    self.lhs = _Constify(lhs)
26462306a36Sopenharmony_ci    self.rhs = _Constify(rhs)
26562306a36Sopenharmony_ci
26662306a36Sopenharmony_ci  def ToPerfJson(self):
26762306a36Sopenharmony_ci    if self.rhs:
26862306a36Sopenharmony_ci      return f'{self.fn}({self.lhs.ToPerfJson()}, {self.rhs.ToPerfJson()})'
26962306a36Sopenharmony_ci    return f'{self.fn}({self.lhs.ToPerfJson()})'
27062306a36Sopenharmony_ci
27162306a36Sopenharmony_ci  def ToPython(self):
27262306a36Sopenharmony_ci    if self.rhs:
27362306a36Sopenharmony_ci      return f'{self.fn}({self.lhs.ToPython()}, {self.rhs.ToPython()})'
27462306a36Sopenharmony_ci    return f'{self.fn}({self.lhs.ToPython()})'
27562306a36Sopenharmony_ci
27662306a36Sopenharmony_ci  def Simplify(self) -> Expression:
27762306a36Sopenharmony_ci    lhs = self.lhs.Simplify()
27862306a36Sopenharmony_ci    rhs = self.rhs.Simplify() if self.rhs else None
27962306a36Sopenharmony_ci    if isinstance(lhs, Constant) and isinstance(rhs, Constant):
28062306a36Sopenharmony_ci      if self.fn == 'd_ratio':
28162306a36Sopenharmony_ci        if rhs.value == '0':
28262306a36Sopenharmony_ci          return Constant(0)
28362306a36Sopenharmony_ci        Constant(ast.literal_eval(f'{lhs} / {rhs}'))
28462306a36Sopenharmony_ci      return Constant(ast.literal_eval(f'{self.fn}({lhs}, {rhs})'))
28562306a36Sopenharmony_ci
28662306a36Sopenharmony_ci    return Function(self.fn, lhs, rhs)
28762306a36Sopenharmony_ci
28862306a36Sopenharmony_ci  def Equals(self, other: Expression) -> bool:
28962306a36Sopenharmony_ci    if isinstance(other, Function):
29062306a36Sopenharmony_ci      result = self.fn == other.fn and self.lhs.Equals(other.lhs)
29162306a36Sopenharmony_ci      if self.rhs:
29262306a36Sopenharmony_ci        result = result and self.rhs.Equals(other.rhs)
29362306a36Sopenharmony_ci      return result
29462306a36Sopenharmony_ci    return False
29562306a36Sopenharmony_ci
29662306a36Sopenharmony_ci  def Substitute(self, name: str, expression: Expression) -> Expression:
29762306a36Sopenharmony_ci    if self.Equals(expression):
29862306a36Sopenharmony_ci      return Event(name)
29962306a36Sopenharmony_ci    lhs = self.lhs.Substitute(name, expression)
30062306a36Sopenharmony_ci    rhs = None
30162306a36Sopenharmony_ci    if self.rhs:
30262306a36Sopenharmony_ci      rhs = self.rhs.Substitute(name, expression)
30362306a36Sopenharmony_ci    return Function(self.fn, lhs, rhs)
30462306a36Sopenharmony_ci
30562306a36Sopenharmony_ci
30662306a36Sopenharmony_cidef _FixEscapes(s: str) -> str:
30762306a36Sopenharmony_ci  s = re.sub(r'([^\\]),', r'\1\\,', s)
30862306a36Sopenharmony_ci  return re.sub(r'([^\\])=', r'\1\\=', s)
30962306a36Sopenharmony_ci
31062306a36Sopenharmony_ci
31162306a36Sopenharmony_ciclass Event(Expression):
31262306a36Sopenharmony_ci  """An event in an expression."""
31362306a36Sopenharmony_ci
31462306a36Sopenharmony_ci  def __init__(self, name: str, legacy_name: str = ''):
31562306a36Sopenharmony_ci    self.name = _FixEscapes(name)
31662306a36Sopenharmony_ci    self.legacy_name = _FixEscapes(legacy_name)
31762306a36Sopenharmony_ci
31862306a36Sopenharmony_ci  def ToPerfJson(self):
31962306a36Sopenharmony_ci    result = re.sub('/', '@', self.name)
32062306a36Sopenharmony_ci    return result
32162306a36Sopenharmony_ci
32262306a36Sopenharmony_ci  def ToPython(self):
32362306a36Sopenharmony_ci    return f'Event(r"{self.name}")'
32462306a36Sopenharmony_ci
32562306a36Sopenharmony_ci  def Simplify(self) -> Expression:
32662306a36Sopenharmony_ci    return self
32762306a36Sopenharmony_ci
32862306a36Sopenharmony_ci  def Equals(self, other: Expression) -> bool:
32962306a36Sopenharmony_ci    return isinstance(other, Event) and self.name == other.name
33062306a36Sopenharmony_ci
33162306a36Sopenharmony_ci  def Substitute(self, name: str, expression: Expression) -> Expression:
33262306a36Sopenharmony_ci    return self
33362306a36Sopenharmony_ci
33462306a36Sopenharmony_ci
33562306a36Sopenharmony_ciclass Constant(Expression):
33662306a36Sopenharmony_ci  """A constant within the expression tree."""
33762306a36Sopenharmony_ci
33862306a36Sopenharmony_ci  def __init__(self, value: Union[float, str]):
33962306a36Sopenharmony_ci    ctx = decimal.Context()
34062306a36Sopenharmony_ci    ctx.prec = 20
34162306a36Sopenharmony_ci    dec = ctx.create_decimal(repr(value) if isinstance(value, float) else value)
34262306a36Sopenharmony_ci    self.value = dec.normalize().to_eng_string()
34362306a36Sopenharmony_ci    self.value = self.value.replace('+', '')
34462306a36Sopenharmony_ci    self.value = self.value.replace('E', 'e')
34562306a36Sopenharmony_ci
34662306a36Sopenharmony_ci  def ToPerfJson(self):
34762306a36Sopenharmony_ci    return self.value
34862306a36Sopenharmony_ci
34962306a36Sopenharmony_ci  def ToPython(self):
35062306a36Sopenharmony_ci    return f'Constant({self.value})'
35162306a36Sopenharmony_ci
35262306a36Sopenharmony_ci  def Simplify(self) -> Expression:
35362306a36Sopenharmony_ci    return self
35462306a36Sopenharmony_ci
35562306a36Sopenharmony_ci  def Equals(self, other: Expression) -> bool:
35662306a36Sopenharmony_ci    return isinstance(other, Constant) and self.value == other.value
35762306a36Sopenharmony_ci
35862306a36Sopenharmony_ci  def Substitute(self, name: str, expression: Expression) -> Expression:
35962306a36Sopenharmony_ci    return self
36062306a36Sopenharmony_ci
36162306a36Sopenharmony_ci
36262306a36Sopenharmony_ciclass Literal(Expression):
36362306a36Sopenharmony_ci  """A runtime literal within the expression tree."""
36462306a36Sopenharmony_ci
36562306a36Sopenharmony_ci  def __init__(self, value: str):
36662306a36Sopenharmony_ci    self.value = value
36762306a36Sopenharmony_ci
36862306a36Sopenharmony_ci  def ToPerfJson(self):
36962306a36Sopenharmony_ci    return self.value
37062306a36Sopenharmony_ci
37162306a36Sopenharmony_ci  def ToPython(self):
37262306a36Sopenharmony_ci    return f'Literal({self.value})'
37362306a36Sopenharmony_ci
37462306a36Sopenharmony_ci  def Simplify(self) -> Expression:
37562306a36Sopenharmony_ci    return self
37662306a36Sopenharmony_ci
37762306a36Sopenharmony_ci  def Equals(self, other: Expression) -> bool:
37862306a36Sopenharmony_ci    return isinstance(other, Literal) and self.value == other.value
37962306a36Sopenharmony_ci
38062306a36Sopenharmony_ci  def Substitute(self, name: str, expression: Expression) -> Expression:
38162306a36Sopenharmony_ci    return self
38262306a36Sopenharmony_ci
38362306a36Sopenharmony_ci
38462306a36Sopenharmony_cidef min(lhs: Union[int, float, Expression], rhs: Union[int, float,
38562306a36Sopenharmony_ci                                                       Expression]) -> Function:
38662306a36Sopenharmony_ci  # pylint: disable=redefined-builtin
38762306a36Sopenharmony_ci  # pylint: disable=invalid-name
38862306a36Sopenharmony_ci  return Function('min', lhs, rhs)
38962306a36Sopenharmony_ci
39062306a36Sopenharmony_ci
39162306a36Sopenharmony_cidef max(lhs: Union[int, float, Expression], rhs: Union[int, float,
39262306a36Sopenharmony_ci                                                       Expression]) -> Function:
39362306a36Sopenharmony_ci  # pylint: disable=redefined-builtin
39462306a36Sopenharmony_ci  # pylint: disable=invalid-name
39562306a36Sopenharmony_ci  return Function('max', lhs, rhs)
39662306a36Sopenharmony_ci
39762306a36Sopenharmony_ci
39862306a36Sopenharmony_cidef d_ratio(lhs: Union[int, float, Expression],
39962306a36Sopenharmony_ci            rhs: Union[int, float, Expression]) -> Function:
40062306a36Sopenharmony_ci  # pylint: disable=redefined-builtin
40162306a36Sopenharmony_ci  # pylint: disable=invalid-name
40262306a36Sopenharmony_ci  return Function('d_ratio', lhs, rhs)
40362306a36Sopenharmony_ci
40462306a36Sopenharmony_ci
40562306a36Sopenharmony_cidef source_count(event: Event) -> Function:
40662306a36Sopenharmony_ci  # pylint: disable=redefined-builtin
40762306a36Sopenharmony_ci  # pylint: disable=invalid-name
40862306a36Sopenharmony_ci  return Function('source_count', event)
40962306a36Sopenharmony_ci
41062306a36Sopenharmony_ci
41162306a36Sopenharmony_cidef has_event(event: Event) -> Function:
41262306a36Sopenharmony_ci  # pylint: disable=redefined-builtin
41362306a36Sopenharmony_ci  # pylint: disable=invalid-name
41462306a36Sopenharmony_ci  return Function('has_event', event)
41562306a36Sopenharmony_ci
41662306a36Sopenharmony_cidef strcmp_cpuid_str(cpuid: Event) -> Function:
41762306a36Sopenharmony_ci  # pylint: disable=redefined-builtin
41862306a36Sopenharmony_ci  # pylint: disable=invalid-name
41962306a36Sopenharmony_ci  return Function('strcmp_cpuid_str', cpuid)
42062306a36Sopenharmony_ci
42162306a36Sopenharmony_ciclass Metric:
42262306a36Sopenharmony_ci  """An individual metric that will specifiable on the perf command line."""
42362306a36Sopenharmony_ci  groups: Set[str]
42462306a36Sopenharmony_ci  expr: Expression
42562306a36Sopenharmony_ci  scale_unit: str
42662306a36Sopenharmony_ci  constraint: bool
42762306a36Sopenharmony_ci
42862306a36Sopenharmony_ci  def __init__(self,
42962306a36Sopenharmony_ci               name: str,
43062306a36Sopenharmony_ci               description: str,
43162306a36Sopenharmony_ci               expr: Expression,
43262306a36Sopenharmony_ci               scale_unit: str,
43362306a36Sopenharmony_ci               constraint: bool = False):
43462306a36Sopenharmony_ci    self.name = name
43562306a36Sopenharmony_ci    self.description = description
43662306a36Sopenharmony_ci    self.expr = expr.Simplify()
43762306a36Sopenharmony_ci    # Workraound valid_only_metric hiding certain metrics based on unit.
43862306a36Sopenharmony_ci    scale_unit = scale_unit.replace('/sec', ' per sec')
43962306a36Sopenharmony_ci    if scale_unit[0].isdigit():
44062306a36Sopenharmony_ci      self.scale_unit = scale_unit
44162306a36Sopenharmony_ci    else:
44262306a36Sopenharmony_ci      self.scale_unit = f'1{scale_unit}'
44362306a36Sopenharmony_ci    self.constraint = constraint
44462306a36Sopenharmony_ci    self.groups = set()
44562306a36Sopenharmony_ci
44662306a36Sopenharmony_ci  def __lt__(self, other):
44762306a36Sopenharmony_ci    """Sort order."""
44862306a36Sopenharmony_ci    return self.name < other.name
44962306a36Sopenharmony_ci
45062306a36Sopenharmony_ci  def AddToMetricGroup(self, group):
45162306a36Sopenharmony_ci    """Callback used when being added to a MetricGroup."""
45262306a36Sopenharmony_ci    self.groups.add(group.name)
45362306a36Sopenharmony_ci
45462306a36Sopenharmony_ci  def Flatten(self) -> Set['Metric']:
45562306a36Sopenharmony_ci    """Return a leaf metric."""
45662306a36Sopenharmony_ci    return set([self])
45762306a36Sopenharmony_ci
45862306a36Sopenharmony_ci  def ToPerfJson(self) -> Dict[str, str]:
45962306a36Sopenharmony_ci    """Return as dictionary for Json generation."""
46062306a36Sopenharmony_ci    result = {
46162306a36Sopenharmony_ci        'MetricName': self.name,
46262306a36Sopenharmony_ci        'MetricGroup': ';'.join(sorted(self.groups)),
46362306a36Sopenharmony_ci        'BriefDescription': self.description,
46462306a36Sopenharmony_ci        'MetricExpr': self.expr.ToPerfJson(),
46562306a36Sopenharmony_ci        'ScaleUnit': self.scale_unit
46662306a36Sopenharmony_ci    }
46762306a36Sopenharmony_ci    if self.constraint:
46862306a36Sopenharmony_ci      result['MetricConstraint'] = 'NO_NMI_WATCHDOG'
46962306a36Sopenharmony_ci
47062306a36Sopenharmony_ci    return result
47162306a36Sopenharmony_ci
47262306a36Sopenharmony_ci
47362306a36Sopenharmony_ciclass _MetricJsonEncoder(json.JSONEncoder):
47462306a36Sopenharmony_ci  """Special handling for Metric objects."""
47562306a36Sopenharmony_ci
47662306a36Sopenharmony_ci  def default(self, o):
47762306a36Sopenharmony_ci    if isinstance(o, Metric):
47862306a36Sopenharmony_ci      return o.ToPerfJson()
47962306a36Sopenharmony_ci    return json.JSONEncoder.default(self, o)
48062306a36Sopenharmony_ci
48162306a36Sopenharmony_ci
48262306a36Sopenharmony_ciclass MetricGroup:
48362306a36Sopenharmony_ci  """A group of metrics.
48462306a36Sopenharmony_ci
48562306a36Sopenharmony_ci  Metric groups may be specificd on the perf command line, but within
48662306a36Sopenharmony_ci  the json they aren't encoded. Metrics may be in multiple groups
48762306a36Sopenharmony_ci  which can facilitate arrangements similar to trees.
48862306a36Sopenharmony_ci  """
48962306a36Sopenharmony_ci
49062306a36Sopenharmony_ci  def __init__(self, name: str, metric_list: List[Union[Metric,
49162306a36Sopenharmony_ci                                                        'MetricGroup']]):
49262306a36Sopenharmony_ci    self.name = name
49362306a36Sopenharmony_ci    self.metric_list = metric_list
49462306a36Sopenharmony_ci    for metric in metric_list:
49562306a36Sopenharmony_ci      metric.AddToMetricGroup(self)
49662306a36Sopenharmony_ci
49762306a36Sopenharmony_ci  def AddToMetricGroup(self, group):
49862306a36Sopenharmony_ci    """Callback used when a MetricGroup is added into another."""
49962306a36Sopenharmony_ci    for metric in self.metric_list:
50062306a36Sopenharmony_ci      metric.AddToMetricGroup(group)
50162306a36Sopenharmony_ci
50262306a36Sopenharmony_ci  def Flatten(self) -> Set[Metric]:
50362306a36Sopenharmony_ci    """Returns a set of all leaf metrics."""
50462306a36Sopenharmony_ci    result = set()
50562306a36Sopenharmony_ci    for x in self.metric_list:
50662306a36Sopenharmony_ci      result = result.union(x.Flatten())
50762306a36Sopenharmony_ci
50862306a36Sopenharmony_ci    return result
50962306a36Sopenharmony_ci
51062306a36Sopenharmony_ci  def ToPerfJson(self) -> str:
51162306a36Sopenharmony_ci    return json.dumps(sorted(self.Flatten()), indent=2, cls=_MetricJsonEncoder)
51262306a36Sopenharmony_ci
51362306a36Sopenharmony_ci  def __str__(self) -> str:
51462306a36Sopenharmony_ci    return self.ToPerfJson()
51562306a36Sopenharmony_ci
51662306a36Sopenharmony_ci
51762306a36Sopenharmony_ciclass _RewriteIfExpToSelect(ast.NodeTransformer):
51862306a36Sopenharmony_ci  """Transformer to convert if-else nodes to Select expressions."""
51962306a36Sopenharmony_ci
52062306a36Sopenharmony_ci  def visit_IfExp(self, node):
52162306a36Sopenharmony_ci    # pylint: disable=invalid-name
52262306a36Sopenharmony_ci    self.generic_visit(node)
52362306a36Sopenharmony_ci    call = ast.Call(
52462306a36Sopenharmony_ci        func=ast.Name(id='Select', ctx=ast.Load()),
52562306a36Sopenharmony_ci        args=[node.body, node.test, node.orelse],
52662306a36Sopenharmony_ci        keywords=[])
52762306a36Sopenharmony_ci    ast.copy_location(call, node.test)
52862306a36Sopenharmony_ci    return call
52962306a36Sopenharmony_ci
53062306a36Sopenharmony_ci
53162306a36Sopenharmony_cidef ParsePerfJson(orig: str) -> Expression:
53262306a36Sopenharmony_ci  """A simple json metric expression decoder.
53362306a36Sopenharmony_ci
53462306a36Sopenharmony_ci  Converts a json encoded metric expression by way of python's ast and
53562306a36Sopenharmony_ci  eval routine. First tokens are mapped to Event calls, then
53662306a36Sopenharmony_ci  accidentally converted keywords or literals are mapped to their
53762306a36Sopenharmony_ci  appropriate calls. Python's ast is used to match if-else that can't
53862306a36Sopenharmony_ci  be handled via operator overloading. Finally the ast is evaluated.
53962306a36Sopenharmony_ci
54062306a36Sopenharmony_ci  Args:
54162306a36Sopenharmony_ci    orig (str): String to parse.
54262306a36Sopenharmony_ci
54362306a36Sopenharmony_ci  Returns:
54462306a36Sopenharmony_ci    Expression: The parsed string.
54562306a36Sopenharmony_ci  """
54662306a36Sopenharmony_ci  # pylint: disable=eval-used
54762306a36Sopenharmony_ci  py = orig.strip()
54862306a36Sopenharmony_ci  # First try to convert everything that looks like a string (event name) into Event(r"EVENT_NAME").
54962306a36Sopenharmony_ci  # This isn't very selective so is followed up by converting some unwanted conversions back again
55062306a36Sopenharmony_ci  py = re.sub(r'([a-zA-Z][^-+/\* \\\(\),]*(?:\\.[^-+/\* \\\(\),]*)*)',
55162306a36Sopenharmony_ci              r'Event(r"\1")', py)
55262306a36Sopenharmony_ci  # If it started with a # it should have been a literal, rather than an event name
55362306a36Sopenharmony_ci  py = re.sub(r'#Event\(r"([^"]*)"\)', r'Literal("#\1")', py)
55462306a36Sopenharmony_ci  # Convert accidentally converted hex constants ("0Event(r"xDEADBEEF)"") back to a constant,
55562306a36Sopenharmony_ci  # but keep it wrapped in Event(), otherwise Python drops the 0x prefix and it gets interpreted as
55662306a36Sopenharmony_ci  # a double by the Bison parser
55762306a36Sopenharmony_ci  py = re.sub(r'0Event\(r"[xX]([0-9a-fA-F]*)"\)', r'Event("0x\1")', py)
55862306a36Sopenharmony_ci  # Convert accidentally converted scientific notation constants back
55962306a36Sopenharmony_ci  py = re.sub(r'([0-9]+)Event\(r"(e[0-9]+)"\)', r'\1\2', py)
56062306a36Sopenharmony_ci  # Convert all the known keywords back from events to just the keyword
56162306a36Sopenharmony_ci  keywords = ['if', 'else', 'min', 'max', 'd_ratio', 'source_count', 'has_event', 'strcmp_cpuid_str',
56262306a36Sopenharmony_ci              'cpuid_not_more_than']
56362306a36Sopenharmony_ci  for kw in keywords:
56462306a36Sopenharmony_ci    py = re.sub(rf'Event\(r"{kw}"\)', kw, py)
56562306a36Sopenharmony_ci  try:
56662306a36Sopenharmony_ci    parsed = ast.parse(py, mode='eval')
56762306a36Sopenharmony_ci  except SyntaxError as e:
56862306a36Sopenharmony_ci    raise SyntaxError(f'Parsing expression:\n{orig}') from e
56962306a36Sopenharmony_ci  _RewriteIfExpToSelect().visit(parsed)
57062306a36Sopenharmony_ci  parsed = ast.fix_missing_locations(parsed)
57162306a36Sopenharmony_ci  return _Constify(eval(compile(parsed, orig, 'eval')))
57262306a36Sopenharmony_ci
57362306a36Sopenharmony_ci
57462306a36Sopenharmony_cidef RewriteMetricsInTermsOfOthers(metrics: List[Tuple[str, str, Expression]]
57562306a36Sopenharmony_ci                                  )-> Dict[Tuple[str, str], Expression]:
57662306a36Sopenharmony_ci  """Shorten metrics by rewriting in terms of others.
57762306a36Sopenharmony_ci
57862306a36Sopenharmony_ci  Args:
57962306a36Sopenharmony_ci    metrics (list): pmus, metric names and their expressions.
58062306a36Sopenharmony_ci  Returns:
58162306a36Sopenharmony_ci    Dict: mapping from a pmu, metric name pair to a shortened expression.
58262306a36Sopenharmony_ci  """
58362306a36Sopenharmony_ci  updates: Dict[Tuple[str, str], Expression] = dict()
58462306a36Sopenharmony_ci  for outer_pmu, outer_name, outer_expression in metrics:
58562306a36Sopenharmony_ci    if outer_pmu is None:
58662306a36Sopenharmony_ci      outer_pmu = 'cpu'
58762306a36Sopenharmony_ci    updated = outer_expression
58862306a36Sopenharmony_ci    while True:
58962306a36Sopenharmony_ci      for inner_pmu, inner_name, inner_expression in metrics:
59062306a36Sopenharmony_ci        if inner_pmu is None:
59162306a36Sopenharmony_ci          inner_pmu = 'cpu'
59262306a36Sopenharmony_ci        if inner_pmu.lower() != outer_pmu.lower():
59362306a36Sopenharmony_ci          continue
59462306a36Sopenharmony_ci        if inner_name.lower() == outer_name.lower():
59562306a36Sopenharmony_ci          continue
59662306a36Sopenharmony_ci        if (inner_pmu, inner_name) in updates:
59762306a36Sopenharmony_ci          inner_expression = updates[(inner_pmu, inner_name)]
59862306a36Sopenharmony_ci        updated = updated.Substitute(inner_name, inner_expression)
59962306a36Sopenharmony_ci      if updated.Equals(outer_expression):
60062306a36Sopenharmony_ci        break
60162306a36Sopenharmony_ci      if (outer_pmu, outer_name) in updates and updated.Equals(updates[(outer_pmu, outer_name)]):
60262306a36Sopenharmony_ci        break
60362306a36Sopenharmony_ci      updates[(outer_pmu, outer_name)] = updated
60462306a36Sopenharmony_ci  return updates
605