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