1 # SPDX-License-Identifier: (LGPL-2.1 OR BSD-2- 1 # SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) 2 """Parse or generate representations of perf m 2 """Parse or generate representations of perf metrics.""" 3 import ast 3 import ast 4 import decimal 4 import decimal 5 import json 5 import json 6 import re 6 import re 7 from typing import Dict, List, Optional, Set, 7 from typing import Dict, List, Optional, Set, Tuple, Union 8 8 9 9 10 class Expression: 10 class Expression: 11 """Abstract base class of elements in a metr 11 """Abstract base class of elements in a metric expression.""" 12 12 13 def ToPerfJson(self) -> str: 13 def ToPerfJson(self) -> str: 14 """Returns a perf json file encoded repres 14 """Returns a perf json file encoded representation.""" 15 raise NotImplementedError() 15 raise NotImplementedError() 16 16 17 def ToPython(self) -> str: 17 def ToPython(self) -> str: 18 """Returns a python expr parseable represe 18 """Returns a python expr parseable representation.""" 19 raise NotImplementedError() 19 raise NotImplementedError() 20 20 21 def Simplify(self): 21 def Simplify(self): 22 """Returns a simplified version of self."" 22 """Returns a simplified version of self.""" 23 raise NotImplementedError() 23 raise NotImplementedError() 24 24 25 def Equals(self, other) -> bool: 25 def Equals(self, other) -> bool: 26 """Returns true when two expressions are t 26 """Returns true when two expressions are the same.""" 27 raise NotImplementedError() 27 raise NotImplementedError() 28 28 29 def Substitute(self, name: str, expression: 29 def Substitute(self, name: str, expression: 'Expression') -> 'Expression': 30 raise NotImplementedError() 30 raise NotImplementedError() 31 31 32 def __str__(self) -> str: 32 def __str__(self) -> str: 33 return self.ToPerfJson() 33 return self.ToPerfJson() 34 34 35 def __or__(self, other: Union[int, float, 'E 35 def __or__(self, other: Union[int, float, 'Expression']) -> 'Operator': 36 return Operator('|', self, other) 36 return Operator('|', self, other) 37 37 38 def __ror__(self, other: Union[int, float, ' 38 def __ror__(self, other: Union[int, float, 'Expression']) -> 'Operator': 39 return Operator('|', other, self) 39 return Operator('|', other, self) 40 40 41 def __xor__(self, other: Union[int, float, ' 41 def __xor__(self, other: Union[int, float, 'Expression']) -> 'Operator': 42 return Operator('^', self, other) 42 return Operator('^', self, other) 43 43 44 def __and__(self, other: Union[int, float, ' 44 def __and__(self, other: Union[int, float, 'Expression']) -> 'Operator': 45 return Operator('&', self, other) 45 return Operator('&', self, other) 46 46 47 def __rand__(self, other: Union[int, float, 47 def __rand__(self, other: Union[int, float, 'Expression']) -> 'Operator': 48 return Operator('&', other, self) 48 return Operator('&', other, self) 49 49 50 def __lt__(self, other: Union[int, float, 'E 50 def __lt__(self, other: Union[int, float, 'Expression']) -> 'Operator': 51 return Operator('<', self, other) 51 return Operator('<', self, other) 52 52 53 def __gt__(self, other: Union[int, float, 'E 53 def __gt__(self, other: Union[int, float, 'Expression']) -> 'Operator': 54 return Operator('>', self, other) 54 return Operator('>', self, other) 55 55 56 def __add__(self, other: Union[int, float, ' 56 def __add__(self, other: Union[int, float, 'Expression']) -> 'Operator': 57 return Operator('+', self, other) 57 return Operator('+', self, other) 58 58 59 def __radd__(self, other: Union[int, float, 59 def __radd__(self, other: Union[int, float, 'Expression']) -> 'Operator': 60 return Operator('+', other, self) 60 return Operator('+', other, self) 61 61 62 def __sub__(self, other: Union[int, float, ' 62 def __sub__(self, other: Union[int, float, 'Expression']) -> 'Operator': 63 return Operator('-', self, other) 63 return Operator('-', self, other) 64 64 65 def __rsub__(self, other: Union[int, float, 65 def __rsub__(self, other: Union[int, float, 'Expression']) -> 'Operator': 66 return Operator('-', other, self) 66 return Operator('-', other, self) 67 67 68 def __mul__(self, other: Union[int, float, ' 68 def __mul__(self, other: Union[int, float, 'Expression']) -> 'Operator': 69 return Operator('*', self, other) 69 return Operator('*', self, other) 70 70 71 def __rmul__(self, other: Union[int, float, 71 def __rmul__(self, other: Union[int, float, 'Expression']) -> 'Operator': 72 return Operator('*', other, self) 72 return Operator('*', other, self) 73 73 74 def __truediv__(self, other: Union[int, floa 74 def __truediv__(self, other: Union[int, float, 'Expression']) -> 'Operator': 75 return Operator('/', self, other) 75 return Operator('/', self, other) 76 76 77 def __rtruediv__(self, other: Union[int, flo 77 def __rtruediv__(self, other: Union[int, float, 'Expression']) -> 'Operator': 78 return Operator('/', other, self) 78 return Operator('/', other, self) 79 79 80 def __mod__(self, other: Union[int, float, ' 80 def __mod__(self, other: Union[int, float, 'Expression']) -> 'Operator': 81 return Operator('%', self, other) 81 return Operator('%', self, other) 82 82 83 83 84 def _Constify(val: Union[bool, int, float, Exp 84 def _Constify(val: Union[bool, int, float, Expression]) -> Expression: 85 """Used to ensure that the nodes in the expr 85 """Used to ensure that the nodes in the expression tree are all Expression.""" 86 if isinstance(val, bool): 86 if isinstance(val, bool): 87 return Constant(1 if val else 0) 87 return Constant(1 if val else 0) 88 if isinstance(val, (int, float)): 88 if isinstance(val, (int, float)): 89 return Constant(val) 89 return Constant(val) 90 return val 90 return val 91 91 92 92 93 # Simple lookup for operator precedence, used 93 # Simple lookup for operator precedence, used to avoid unnecessary 94 # brackets. Precedence matches that of the sim 94 # brackets. Precedence matches that of the simple expression parser 95 # but differs from python where comparisons ar 95 # but differs from python where comparisons are lower precedence than 96 # the bitwise &, ^, | but not the logical vers 96 # the bitwise &, ^, | but not the logical versions that the expression 97 # parser doesn't have. 97 # parser doesn't have. 98 _PRECEDENCE = { 98 _PRECEDENCE = { 99 '|': 0, 99 '|': 0, 100 '^': 1, 100 '^': 1, 101 '&': 2, 101 '&': 2, 102 '<': 3, 102 '<': 3, 103 '>': 3, 103 '>': 3, 104 '+': 4, 104 '+': 4, 105 '-': 4, 105 '-': 4, 106 '*': 5, 106 '*': 5, 107 '/': 5, 107 '/': 5, 108 '%': 5, 108 '%': 5, 109 } 109 } 110 110 111 111 112 class Operator(Expression): 112 class Operator(Expression): 113 """Represents a binary operator in the parse 113 """Represents a binary operator in the parse tree.""" 114 114 115 def __init__(self, operator: str, lhs: Union 115 def __init__(self, operator: str, lhs: Union[int, float, Expression], 116 rhs: Union[int, float, Expressi 116 rhs: Union[int, float, Expression]): 117 self.operator = operator 117 self.operator = operator 118 self.lhs = _Constify(lhs) 118 self.lhs = _Constify(lhs) 119 self.rhs = _Constify(rhs) 119 self.rhs = _Constify(rhs) 120 120 121 def Bracket(self, 121 def Bracket(self, 122 other: Expression, 122 other: Expression, 123 other_str: str, 123 other_str: str, 124 rhs: bool = False) -> str: 124 rhs: bool = False) -> str: 125 """If necessary brackets the given other v 125 """If necessary brackets the given other value. 126 126 127 If ``other`` is an operator then a bracket 127 If ``other`` is an operator then a bracket is necessary when 128 this/self operator has higher precedence. 128 this/self operator has higher precedence. Consider: '(a + b) * c', 129 ``other_str`` will be 'a + b'. A bracket i 129 ``other_str`` will be 'a + b'. A bracket is necessary as without 130 the bracket 'a + b * c' will evaluate 'b * 130 the bracket 'a + b * c' will evaluate 'b * c' first. However, '(a 131 * b) + c' doesn't need a bracket as 'a * b 131 * b) + c' doesn't need a bracket as 'a * b' will always be 132 evaluated first. For 'a / (b * c)' (ie the 132 evaluated first. For 'a / (b * c)' (ie the same precedence level 133 operations) then we add the bracket to bes 133 operations) then we add the bracket to best match the original 134 input, but not for '(a / b) * c' where the 134 input, but not for '(a / b) * c' where the bracket is unnecessary. 135 135 136 Args: 136 Args: 137 other (Expression): is a lhs or rhs oper 137 other (Expression): is a lhs or rhs operator 138 other_str (str): ``other`` in the approp 138 other_str (str): ``other`` in the appropriate string form 139 rhs (bool): is ``other`` on the RHS 139 rhs (bool): is ``other`` on the RHS 140 140 141 Returns: 141 Returns: 142 str: possibly bracketed other_str 142 str: possibly bracketed other_str 143 """ 143 """ 144 if isinstance(other, Operator): 144 if isinstance(other, Operator): 145 if _PRECEDENCE.get(self.operator, -1) > 145 if _PRECEDENCE.get(self.operator, -1) > _PRECEDENCE.get( 146 other.operator, -1): 146 other.operator, -1): 147 return f'({other_str})' 147 return f'({other_str})' 148 if rhs and _PRECEDENCE.get(self.operator 148 if rhs and _PRECEDENCE.get(self.operator, -1) == _PRECEDENCE.get( 149 other.operator, -1): 149 other.operator, -1): 150 return f'({other_str})' 150 return f'({other_str})' 151 return other_str 151 return other_str 152 152 153 def ToPerfJson(self): 153 def ToPerfJson(self): 154 return (f'{self.Bracket(self.lhs, self.lhs 154 return (f'{self.Bracket(self.lhs, self.lhs.ToPerfJson())} {self.operator} ' 155 f'{self.Bracket(self.rhs, self.rhs 155 f'{self.Bracket(self.rhs, self.rhs.ToPerfJson(), True)}') 156 156 157 def ToPython(self): 157 def ToPython(self): 158 return (f'{self.Bracket(self.lhs, self.lhs 158 return (f'{self.Bracket(self.lhs, self.lhs.ToPython())} {self.operator} ' 159 f'{self.Bracket(self.rhs, self.rhs 159 f'{self.Bracket(self.rhs, self.rhs.ToPython(), True)}') 160 160 161 def Simplify(self) -> Expression: 161 def Simplify(self) -> Expression: 162 lhs = self.lhs.Simplify() 162 lhs = self.lhs.Simplify() 163 rhs = self.rhs.Simplify() 163 rhs = self.rhs.Simplify() 164 if isinstance(lhs, Constant) and isinstanc 164 if isinstance(lhs, Constant) and isinstance(rhs, Constant): 165 return Constant(ast.literal_eval(lhs + s 165 return Constant(ast.literal_eval(lhs + self.operator + rhs)) 166 166 167 if isinstance(self.lhs, Constant): 167 if isinstance(self.lhs, Constant): 168 if self.operator in ('+', '|') and lhs.v 168 if self.operator in ('+', '|') and lhs.value == '0': 169 return rhs 169 return rhs 170 170 171 # Simplify multiplication by 0 except fo 171 # Simplify multiplication by 0 except for the slot event which 172 # is deliberately introduced using this 172 # is deliberately introduced using this pattern. 173 if self.operator == '*' and lhs.value == 173 if self.operator == '*' and lhs.value == '0' and ( 174 not isinstance(rhs, Event) or 'slots 174 not isinstance(rhs, Event) or 'slots' not in rhs.name.lower()): 175 return Constant(0) 175 return Constant(0) 176 176 177 if self.operator == '*' and lhs.value == 177 if self.operator == '*' and lhs.value == '1': 178 return rhs 178 return rhs 179 179 180 if isinstance(rhs, Constant): 180 if isinstance(rhs, Constant): 181 if self.operator in ('+', '|') and rhs.v 181 if self.operator in ('+', '|') and rhs.value == '0': 182 return lhs 182 return lhs 183 183 184 if self.operator == '*' and rhs.value == 184 if self.operator == '*' and rhs.value == '0': 185 return Constant(0) 185 return Constant(0) 186 186 187 if self.operator == '*' and self.rhs.val 187 if self.operator == '*' and self.rhs.value == '1': 188 return lhs 188 return lhs 189 189 190 return Operator(self.operator, lhs, rhs) 190 return Operator(self.operator, lhs, rhs) 191 191 192 def Equals(self, other: Expression) -> bool: 192 def Equals(self, other: Expression) -> bool: 193 if isinstance(other, Operator): 193 if isinstance(other, Operator): 194 return self.operator == other.operator a 194 return self.operator == other.operator and self.lhs.Equals( 195 other.lhs) and self.rhs.Equals(other 195 other.lhs) and self.rhs.Equals(other.rhs) 196 return False 196 return False 197 197 198 def Substitute(self, name: str, expression: 198 def Substitute(self, name: str, expression: Expression) -> Expression: 199 if self.Equals(expression): 199 if self.Equals(expression): 200 return Event(name) 200 return Event(name) 201 lhs = self.lhs.Substitute(name, expression 201 lhs = self.lhs.Substitute(name, expression) 202 rhs = None 202 rhs = None 203 if self.rhs: 203 if self.rhs: 204 rhs = self.rhs.Substitute(name, expressi 204 rhs = self.rhs.Substitute(name, expression) 205 return Operator(self.operator, lhs, rhs) 205 return Operator(self.operator, lhs, rhs) 206 206 207 207 208 class Select(Expression): 208 class Select(Expression): 209 """Represents a select ternary in the parse 209 """Represents a select ternary in the parse tree.""" 210 210 211 def __init__(self, true_val: Union[int, floa 211 def __init__(self, true_val: Union[int, float, Expression], 212 cond: Union[int, float, Express 212 cond: Union[int, float, Expression], 213 false_val: Union[int, float, Ex 213 false_val: Union[int, float, Expression]): 214 self.true_val = _Constify(true_val) 214 self.true_val = _Constify(true_val) 215 self.cond = _Constify(cond) 215 self.cond = _Constify(cond) 216 self.false_val = _Constify(false_val) 216 self.false_val = _Constify(false_val) 217 217 218 def ToPerfJson(self): 218 def ToPerfJson(self): 219 true_str = self.true_val.ToPerfJson() 219 true_str = self.true_val.ToPerfJson() 220 cond_str = self.cond.ToPerfJson() 220 cond_str = self.cond.ToPerfJson() 221 false_str = self.false_val.ToPerfJson() 221 false_str = self.false_val.ToPerfJson() 222 return f'({true_str} if {cond_str} else {f 222 return f'({true_str} if {cond_str} else {false_str})' 223 223 224 def ToPython(self): 224 def ToPython(self): 225 return (f'Select({self.true_val.ToPython() 225 return (f'Select({self.true_val.ToPython()}, {self.cond.ToPython()}, ' 226 f'{self.false_val.ToPython()})') 226 f'{self.false_val.ToPython()})') 227 227 228 def Simplify(self) -> Expression: 228 def Simplify(self) -> Expression: 229 cond = self.cond.Simplify() 229 cond = self.cond.Simplify() 230 true_val = self.true_val.Simplify() 230 true_val = self.true_val.Simplify() 231 false_val = self.false_val.Simplify() 231 false_val = self.false_val.Simplify() 232 if isinstance(cond, Constant): 232 if isinstance(cond, Constant): 233 return false_val if cond.value == '0' el 233 return false_val if cond.value == '0' else true_val 234 234 235 if true_val.Equals(false_val): 235 if true_val.Equals(false_val): 236 return true_val 236 return true_val 237 237 238 return Select(true_val, cond, false_val) 238 return Select(true_val, cond, false_val) 239 239 240 def Equals(self, other: Expression) -> bool: 240 def Equals(self, other: Expression) -> bool: 241 if isinstance(other, Select): 241 if isinstance(other, Select): 242 return self.cond.Equals(other.cond) and 242 return self.cond.Equals(other.cond) and self.false_val.Equals( 243 other.false_val) and self.true_val.E 243 other.false_val) and self.true_val.Equals(other.true_val) 244 return False 244 return False 245 245 246 def Substitute(self, name: str, expression: 246 def Substitute(self, name: str, expression: Expression) -> Expression: 247 if self.Equals(expression): 247 if self.Equals(expression): 248 return Event(name) 248 return Event(name) 249 true_val = self.true_val.Substitute(name, 249 true_val = self.true_val.Substitute(name, expression) 250 cond = self.cond.Substitute(name, expressi 250 cond = self.cond.Substitute(name, expression) 251 false_val = self.false_val.Substitute(name 251 false_val = self.false_val.Substitute(name, expression) 252 return Select(true_val, cond, false_val) 252 return Select(true_val, cond, false_val) 253 253 254 254 255 class Function(Expression): 255 class Function(Expression): 256 """A function in an expression like min, max 256 """A function in an expression like min, max, d_ratio.""" 257 257 258 def __init__(self, 258 def __init__(self, 259 fn: str, 259 fn: str, 260 lhs: Union[int, float, Expressi 260 lhs: Union[int, float, Expression], 261 rhs: Optional[Union[int, float, 261 rhs: Optional[Union[int, float, Expression]] = None): 262 self.fn = fn 262 self.fn = fn 263 self.lhs = _Constify(lhs) 263 self.lhs = _Constify(lhs) 264 self.rhs = _Constify(rhs) 264 self.rhs = _Constify(rhs) 265 265 266 def ToPerfJson(self): 266 def ToPerfJson(self): 267 if self.rhs: 267 if self.rhs: 268 return f'{self.fn}({self.lhs.ToPerfJson( 268 return f'{self.fn}({self.lhs.ToPerfJson()}, {self.rhs.ToPerfJson()})' 269 return f'{self.fn}({self.lhs.ToPerfJson()} 269 return f'{self.fn}({self.lhs.ToPerfJson()})' 270 270 271 def ToPython(self): 271 def ToPython(self): 272 if self.rhs: 272 if self.rhs: 273 return f'{self.fn}({self.lhs.ToPython()} 273 return f'{self.fn}({self.lhs.ToPython()}, {self.rhs.ToPython()})' 274 return f'{self.fn}({self.lhs.ToPython()})' 274 return f'{self.fn}({self.lhs.ToPython()})' 275 275 276 def Simplify(self) -> Expression: 276 def Simplify(self) -> Expression: 277 lhs = self.lhs.Simplify() 277 lhs = self.lhs.Simplify() 278 rhs = self.rhs.Simplify() if self.rhs else 278 rhs = self.rhs.Simplify() if self.rhs else None 279 if isinstance(lhs, Constant) and isinstanc 279 if isinstance(lhs, Constant) and isinstance(rhs, Constant): 280 if self.fn == 'd_ratio': 280 if self.fn == 'd_ratio': 281 if rhs.value == '0': 281 if rhs.value == '0': 282 return Constant(0) 282 return Constant(0) 283 Constant(ast.literal_eval(f'{lhs} / {r 283 Constant(ast.literal_eval(f'{lhs} / {rhs}')) 284 return Constant(ast.literal_eval(f'{self 284 return Constant(ast.literal_eval(f'{self.fn}({lhs}, {rhs})')) 285 285 286 return Function(self.fn, lhs, rhs) 286 return Function(self.fn, lhs, rhs) 287 287 288 def Equals(self, other: Expression) -> bool: 288 def Equals(self, other: Expression) -> bool: 289 if isinstance(other, Function): 289 if isinstance(other, Function): 290 result = self.fn == other.fn and self.lh 290 result = self.fn == other.fn and self.lhs.Equals(other.lhs) 291 if self.rhs: 291 if self.rhs: 292 result = result and self.rhs.Equals(ot 292 result = result and self.rhs.Equals(other.rhs) 293 return result 293 return result 294 return False 294 return False 295 295 296 def Substitute(self, name: str, expression: 296 def Substitute(self, name: str, expression: Expression) -> Expression: 297 if self.Equals(expression): 297 if self.Equals(expression): 298 return Event(name) 298 return Event(name) 299 lhs = self.lhs.Substitute(name, expression 299 lhs = self.lhs.Substitute(name, expression) 300 rhs = None 300 rhs = None 301 if self.rhs: 301 if self.rhs: 302 rhs = self.rhs.Substitute(name, expressi 302 rhs = self.rhs.Substitute(name, expression) 303 return Function(self.fn, lhs, rhs) 303 return Function(self.fn, lhs, rhs) 304 304 305 305 306 def _FixEscapes(s: str) -> str: 306 def _FixEscapes(s: str) -> str: 307 s = re.sub(r'([^\\]),', r'\1\\,', s) 307 s = re.sub(r'([^\\]),', r'\1\\,', s) 308 return re.sub(r'([^\\])=', r'\1\\=', s) 308 return re.sub(r'([^\\])=', r'\1\\=', s) 309 309 310 310 311 class Event(Expression): 311 class Event(Expression): 312 """An event in an expression.""" 312 """An event in an expression.""" 313 313 314 def __init__(self, name: str, legacy_name: s 314 def __init__(self, name: str, legacy_name: str = ''): 315 self.name = _FixEscapes(name) 315 self.name = _FixEscapes(name) 316 self.legacy_name = _FixEscapes(legacy_name 316 self.legacy_name = _FixEscapes(legacy_name) 317 317 318 def ToPerfJson(self): 318 def ToPerfJson(self): 319 result = re.sub('/', '@', self.name) 319 result = re.sub('/', '@', self.name) 320 return result 320 return result 321 321 322 def ToPython(self): 322 def ToPython(self): 323 return f'Event(r"{self.name}")' 323 return f'Event(r"{self.name}")' 324 324 325 def Simplify(self) -> Expression: 325 def Simplify(self) -> Expression: 326 return self 326 return self 327 327 328 def Equals(self, other: Expression) -> bool: 328 def Equals(self, other: Expression) -> bool: 329 return isinstance(other, Event) and self.n 329 return isinstance(other, Event) and self.name == other.name 330 330 331 def Substitute(self, name: str, expression: 331 def Substitute(self, name: str, expression: Expression) -> Expression: 332 return self 332 return self 333 333 334 334 335 class Constant(Expression): 335 class Constant(Expression): 336 """A constant within the expression tree.""" 336 """A constant within the expression tree.""" 337 337 338 def __init__(self, value: Union[float, str]) 338 def __init__(self, value: Union[float, str]): 339 ctx = decimal.Context() 339 ctx = decimal.Context() 340 ctx.prec = 20 340 ctx.prec = 20 341 dec = ctx.create_decimal(repr(value) if is 341 dec = ctx.create_decimal(repr(value) if isinstance(value, float) else value) 342 self.value = dec.normalize().to_eng_string 342 self.value = dec.normalize().to_eng_string() 343 self.value = self.value.replace('+', '') 343 self.value = self.value.replace('+', '') 344 self.value = self.value.replace('E', 'e') 344 self.value = self.value.replace('E', 'e') 345 345 346 def ToPerfJson(self): 346 def ToPerfJson(self): 347 return self.value 347 return self.value 348 348 349 def ToPython(self): 349 def ToPython(self): 350 return f'Constant({self.value})' 350 return f'Constant({self.value})' 351 351 352 def Simplify(self) -> Expression: 352 def Simplify(self) -> Expression: 353 return self 353 return self 354 354 355 def Equals(self, other: Expression) -> bool: 355 def Equals(self, other: Expression) -> bool: 356 return isinstance(other, Constant) and sel 356 return isinstance(other, Constant) and self.value == other.value 357 357 358 def Substitute(self, name: str, expression: 358 def Substitute(self, name: str, expression: Expression) -> Expression: 359 return self 359 return self 360 360 361 361 362 class Literal(Expression): 362 class Literal(Expression): 363 """A runtime literal within the expression t 363 """A runtime literal within the expression tree.""" 364 364 365 def __init__(self, value: str): 365 def __init__(self, value: str): 366 self.value = value 366 self.value = value 367 367 368 def ToPerfJson(self): 368 def ToPerfJson(self): 369 return self.value 369 return self.value 370 370 371 def ToPython(self): 371 def ToPython(self): 372 return f'Literal({self.value})' 372 return f'Literal({self.value})' 373 373 374 def Simplify(self) -> Expression: 374 def Simplify(self) -> Expression: 375 return self 375 return self 376 376 377 def Equals(self, other: Expression) -> bool: 377 def Equals(self, other: Expression) -> bool: 378 return isinstance(other, Literal) and self 378 return isinstance(other, Literal) and self.value == other.value 379 379 380 def Substitute(self, name: str, expression: 380 def Substitute(self, name: str, expression: Expression) -> Expression: 381 return self 381 return self 382 382 383 383 384 def min(lhs: Union[int, float, Expression], rh 384 def min(lhs: Union[int, float, Expression], rhs: Union[int, float, 385 385 Expression]) -> Function: 386 # pylint: disable=redefined-builtin 386 # pylint: disable=redefined-builtin 387 # pylint: disable=invalid-name 387 # pylint: disable=invalid-name 388 return Function('min', lhs, rhs) 388 return Function('min', lhs, rhs) 389 389 390 390 391 def max(lhs: Union[int, float, Expression], rh 391 def max(lhs: Union[int, float, Expression], rhs: Union[int, float, 392 392 Expression]) -> Function: 393 # pylint: disable=redefined-builtin 393 # pylint: disable=redefined-builtin 394 # pylint: disable=invalid-name 394 # pylint: disable=invalid-name 395 return Function('max', lhs, rhs) 395 return Function('max', lhs, rhs) 396 396 397 397 398 def d_ratio(lhs: Union[int, float, Expression] 398 def d_ratio(lhs: Union[int, float, Expression], 399 rhs: Union[int, float, Expression] 399 rhs: Union[int, float, Expression]) -> Function: 400 # pylint: disable=redefined-builtin 400 # pylint: disable=redefined-builtin 401 # pylint: disable=invalid-name 401 # pylint: disable=invalid-name 402 return Function('d_ratio', lhs, rhs) 402 return Function('d_ratio', lhs, rhs) 403 403 404 404 405 def source_count(event: Event) -> Function: 405 def source_count(event: Event) -> Function: 406 # pylint: disable=redefined-builtin 406 # pylint: disable=redefined-builtin 407 # pylint: disable=invalid-name 407 # pylint: disable=invalid-name 408 return Function('source_count', event) 408 return Function('source_count', event) 409 409 410 410 411 def has_event(event: Event) -> Function: 411 def has_event(event: Event) -> Function: 412 # pylint: disable=redefined-builtin 412 # pylint: disable=redefined-builtin 413 # pylint: disable=invalid-name 413 # pylint: disable=invalid-name 414 return Function('has_event', event) 414 return Function('has_event', event) 415 415 416 def strcmp_cpuid_str(cpuid: Event) -> Function 416 def strcmp_cpuid_str(cpuid: Event) -> Function: 417 # pylint: disable=redefined-builtin 417 # pylint: disable=redefined-builtin 418 # pylint: disable=invalid-name 418 # pylint: disable=invalid-name 419 return Function('strcmp_cpuid_str', cpuid) 419 return Function('strcmp_cpuid_str', cpuid) 420 420 421 class Metric: 421 class Metric: 422 """An individual metric that will specifiabl 422 """An individual metric that will specifiable on the perf command line.""" 423 groups: Set[str] 423 groups: Set[str] 424 expr: Expression 424 expr: Expression 425 scale_unit: str 425 scale_unit: str 426 constraint: bool 426 constraint: bool 427 427 428 def __init__(self, 428 def __init__(self, 429 name: str, 429 name: str, 430 description: str, 430 description: str, 431 expr: Expression, 431 expr: Expression, 432 scale_unit: str, 432 scale_unit: str, 433 constraint: bool = False): 433 constraint: bool = False): 434 self.name = name 434 self.name = name 435 self.description = description 435 self.description = description 436 self.expr = expr.Simplify() 436 self.expr = expr.Simplify() 437 # Workraound valid_only_metric hiding cert 437 # Workraound valid_only_metric hiding certain metrics based on unit. 438 scale_unit = scale_unit.replace('/sec', ' 438 scale_unit = scale_unit.replace('/sec', ' per sec') 439 if scale_unit[0].isdigit(): 439 if scale_unit[0].isdigit(): 440 self.scale_unit = scale_unit 440 self.scale_unit = scale_unit 441 else: 441 else: 442 self.scale_unit = f'1{scale_unit}' 442 self.scale_unit = f'1{scale_unit}' 443 self.constraint = constraint 443 self.constraint = constraint 444 self.groups = set() 444 self.groups = set() 445 445 446 def __lt__(self, other): 446 def __lt__(self, other): 447 """Sort order.""" 447 """Sort order.""" 448 return self.name < other.name 448 return self.name < other.name 449 449 450 def AddToMetricGroup(self, group): 450 def AddToMetricGroup(self, group): 451 """Callback used when being added to a Met 451 """Callback used when being added to a MetricGroup.""" 452 self.groups.add(group.name) 452 self.groups.add(group.name) 453 453 454 def Flatten(self) -> Set['Metric']: 454 def Flatten(self) -> Set['Metric']: 455 """Return a leaf metric.""" 455 """Return a leaf metric.""" 456 return set([self]) 456 return set([self]) 457 457 458 def ToPerfJson(self) -> Dict[str, str]: 458 def ToPerfJson(self) -> Dict[str, str]: 459 """Return as dictionary for Json generatio 459 """Return as dictionary for Json generation.""" 460 result = { 460 result = { 461 'MetricName': self.name, 461 'MetricName': self.name, 462 'MetricGroup': ';'.join(sorted(self.gr 462 'MetricGroup': ';'.join(sorted(self.groups)), 463 'BriefDescription': self.description, 463 'BriefDescription': self.description, 464 'MetricExpr': self.expr.ToPerfJson(), 464 'MetricExpr': self.expr.ToPerfJson(), 465 'ScaleUnit': self.scale_unit 465 'ScaleUnit': self.scale_unit 466 } 466 } 467 if self.constraint: 467 if self.constraint: 468 result['MetricConstraint'] = 'NO_NMI_WAT 468 result['MetricConstraint'] = 'NO_NMI_WATCHDOG' 469 469 470 return result 470 return result 471 471 472 472 473 class _MetricJsonEncoder(json.JSONEncoder): 473 class _MetricJsonEncoder(json.JSONEncoder): 474 """Special handling for Metric objects.""" 474 """Special handling for Metric objects.""" 475 475 476 def default(self, o): 476 def default(self, o): 477 if isinstance(o, Metric): 477 if isinstance(o, Metric): 478 return o.ToPerfJson() 478 return o.ToPerfJson() 479 return json.JSONEncoder.default(self, o) 479 return json.JSONEncoder.default(self, o) 480 480 481 481 482 class MetricGroup: 482 class MetricGroup: 483 """A group of metrics. 483 """A group of metrics. 484 484 485 Metric groups may be specificd on the perf c 485 Metric groups may be specificd on the perf command line, but within 486 the json they aren't encoded. Metrics may be 486 the json they aren't encoded. Metrics may be in multiple groups 487 which can facilitate arrangements similar to 487 which can facilitate arrangements similar to trees. 488 """ 488 """ 489 489 490 def __init__(self, name: str, metric_list: L 490 def __init__(self, name: str, metric_list: List[Union[Metric, 491 491 'MetricGroup']]): 492 self.name = name 492 self.name = name 493 self.metric_list = metric_list 493 self.metric_list = metric_list 494 for metric in metric_list: 494 for metric in metric_list: 495 metric.AddToMetricGroup(self) 495 metric.AddToMetricGroup(self) 496 496 497 def AddToMetricGroup(self, group): 497 def AddToMetricGroup(self, group): 498 """Callback used when a MetricGroup is add 498 """Callback used when a MetricGroup is added into another.""" 499 for metric in self.metric_list: 499 for metric in self.metric_list: 500 metric.AddToMetricGroup(group) 500 metric.AddToMetricGroup(group) 501 501 502 def Flatten(self) -> Set[Metric]: 502 def Flatten(self) -> Set[Metric]: 503 """Returns a set of all leaf metrics.""" 503 """Returns a set of all leaf metrics.""" 504 result = set() 504 result = set() 505 for x in self.metric_list: 505 for x in self.metric_list: 506 result = result.union(x.Flatten()) 506 result = result.union(x.Flatten()) 507 507 508 return result 508 return result 509 509 510 def ToPerfJson(self) -> str: 510 def ToPerfJson(self) -> str: 511 return json.dumps(sorted(self.Flatten()), 511 return json.dumps(sorted(self.Flatten()), indent=2, cls=_MetricJsonEncoder) 512 512 513 def __str__(self) -> str: 513 def __str__(self) -> str: 514 return self.ToPerfJson() 514 return self.ToPerfJson() 515 515 516 516 517 class _RewriteIfExpToSelect(ast.NodeTransforme 517 class _RewriteIfExpToSelect(ast.NodeTransformer): 518 """Transformer to convert if-else nodes to S 518 """Transformer to convert if-else nodes to Select expressions.""" 519 519 520 def visit_IfExp(self, node): 520 def visit_IfExp(self, node): 521 # pylint: disable=invalid-name 521 # pylint: disable=invalid-name 522 self.generic_visit(node) 522 self.generic_visit(node) 523 call = ast.Call( 523 call = ast.Call( 524 func=ast.Name(id='Select', ctx=ast.Loa 524 func=ast.Name(id='Select', ctx=ast.Load()), 525 args=[node.body, node.test, node.orels 525 args=[node.body, node.test, node.orelse], 526 keywords=[]) 526 keywords=[]) 527 ast.copy_location(call, node.test) 527 ast.copy_location(call, node.test) 528 return call 528 return call 529 529 530 530 531 def ParsePerfJson(orig: str) -> Expression: 531 def ParsePerfJson(orig: str) -> Expression: 532 """A simple json metric expression decoder. 532 """A simple json metric expression decoder. 533 533 534 Converts a json encoded metric expression by 534 Converts a json encoded metric expression by way of python's ast and 535 eval routine. First tokens are mapped to Eve 535 eval routine. First tokens are mapped to Event calls, then 536 accidentally converted keywords or literals 536 accidentally converted keywords or literals are mapped to their 537 appropriate calls. Python's ast is used to m 537 appropriate calls. Python's ast is used to match if-else that can't 538 be handled via operator overloading. Finally 538 be handled via operator overloading. Finally the ast is evaluated. 539 539 540 Args: 540 Args: 541 orig (str): String to parse. 541 orig (str): String to parse. 542 542 543 Returns: 543 Returns: 544 Expression: The parsed string. 544 Expression: The parsed string. 545 """ 545 """ 546 # pylint: disable=eval-used 546 # pylint: disable=eval-used 547 py = orig.strip() 547 py = orig.strip() 548 # First try to convert everything that looks 548 # First try to convert everything that looks like a string (event name) into Event(r"EVENT_NAME"). 549 # This isn't very selective so is followed u 549 # This isn't very selective so is followed up by converting some unwanted conversions back again 550 py = re.sub(r'([a-zA-Z][^-+/\* \\\(\),]*(?:\ 550 py = re.sub(r'([a-zA-Z][^-+/\* \\\(\),]*(?:\\.[^-+/\* \\\(\),]*)*)', 551 r'Event(r"\1")', py) 551 r'Event(r"\1")', py) 552 # If it started with a # it should have been 552 # If it started with a # it should have been a literal, rather than an event name 553 py = re.sub(r'#Event\(r"([^"]*)"\)', r'Liter 553 py = re.sub(r'#Event\(r"([^"]*)"\)', r'Literal("#\1")', py) 554 # Convert accidentally converted hex constan 554 # Convert accidentally converted hex constants ("0Event(r"xDEADBEEF)"") back to a constant, 555 # but keep it wrapped in Event(), otherwise 555 # but keep it wrapped in Event(), otherwise Python drops the 0x prefix and it gets interpreted as 556 # a double by the Bison parser 556 # a double by the Bison parser 557 py = re.sub(r'0Event\(r"[xX]([0-9a-fA-F]*)"\ 557 py = re.sub(r'0Event\(r"[xX]([0-9a-fA-F]*)"\)', r'Event("0x\1")', py) 558 # Convert accidentally converted scientific 558 # Convert accidentally converted scientific notation constants back 559 py = re.sub(r'([0-9]+)Event\(r"(e[0-9]+)"\)' 559 py = re.sub(r'([0-9]+)Event\(r"(e[0-9]+)"\)', r'\1\2', py) 560 # Convert all the known keywords back from e 560 # Convert all the known keywords back from events to just the keyword 561 keywords = ['if', 'else', 'min', 'max', 'd_r 561 keywords = ['if', 'else', 'min', 'max', 'd_ratio', 'source_count', 'has_event', 'strcmp_cpuid_str'] 562 for kw in keywords: 562 for kw in keywords: 563 py = re.sub(rf'Event\(r"{kw}"\)', kw, py) 563 py = re.sub(rf'Event\(r"{kw}"\)', kw, py) 564 try: 564 try: 565 parsed = ast.parse(py, mode='eval') 565 parsed = ast.parse(py, mode='eval') 566 except SyntaxError as e: 566 except SyntaxError as e: 567 raise SyntaxError(f'Parsing expression:\n{ 567 raise SyntaxError(f'Parsing expression:\n{orig}') from e 568 _RewriteIfExpToSelect().visit(parsed) 568 _RewriteIfExpToSelect().visit(parsed) 569 parsed = ast.fix_missing_locations(parsed) 569 parsed = ast.fix_missing_locations(parsed) 570 return _Constify(eval(compile(parsed, orig, 570 return _Constify(eval(compile(parsed, orig, 'eval'))) 571 571 572 572 573 def RewriteMetricsInTermsOfOthers(metrics: Lis 573 def RewriteMetricsInTermsOfOthers(metrics: List[Tuple[str, str, Expression]] 574 )-> Dict[Tup 574 )-> Dict[Tuple[str, str], Expression]: 575 """Shorten metrics by rewriting in terms of 575 """Shorten metrics by rewriting in terms of others. 576 576 577 Args: 577 Args: 578 metrics (list): pmus, metric names and the 578 metrics (list): pmus, metric names and their expressions. 579 Returns: 579 Returns: 580 Dict: mapping from a pmu, metric name pair 580 Dict: mapping from a pmu, metric name pair to a shortened expression. 581 """ 581 """ 582 updates: Dict[Tuple[str, str], Expression] = 582 updates: Dict[Tuple[str, str], Expression] = dict() 583 for outer_pmu, outer_name, outer_expression 583 for outer_pmu, outer_name, outer_expression in metrics: 584 if outer_pmu is None: 584 if outer_pmu is None: 585 outer_pmu = 'cpu' 585 outer_pmu = 'cpu' 586 updated = outer_expression 586 updated = outer_expression 587 while True: 587 while True: 588 for inner_pmu, inner_name, inner_express 588 for inner_pmu, inner_name, inner_expression in metrics: 589 if inner_pmu is None: 589 if inner_pmu is None: 590 inner_pmu = 'cpu' 590 inner_pmu = 'cpu' 591 if inner_pmu.lower() != outer_pmu.lowe 591 if inner_pmu.lower() != outer_pmu.lower(): 592 continue 592 continue 593 if inner_name.lower() == outer_name.lo 593 if inner_name.lower() == outer_name.lower(): 594 continue 594 continue 595 if (inner_pmu, inner_name) in updates: 595 if (inner_pmu, inner_name) in updates: 596 inner_expression = updates[(inner_pm 596 inner_expression = updates[(inner_pmu, inner_name)] 597 updated = updated.Substitute(inner_nam 597 updated = updated.Substitute(inner_name, inner_expression) 598 if updated.Equals(outer_expression): 598 if updated.Equals(outer_expression): 599 break 599 break 600 if (outer_pmu, outer_name) in updates an 600 if (outer_pmu, outer_name) in updates and updated.Equals(updates[(outer_pmu, outer_name)]): 601 break 601 break 602 updates[(outer_pmu, outer_name)] = updat 602 updates[(outer_pmu, outer_name)] = updated 603 return updates 603 return updates
Linux® is a registered trademark of Linus Torvalds in the United States and other countries.
TOMOYO® is a registered trademark of NTT DATA CORPORATION.