1#!/usr/bin/env python3 2# SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) 3"""Convert directories of JSON events to C code.""" 4import argparse 5import csv 6from functools import lru_cache 7import json 8import metric 9import os 10import sys 11from typing import (Callable, Dict, Optional, Sequence, Set, Tuple) 12import collections 13 14# Global command line arguments. 15_args = None 16# List of regular event tables. 17_event_tables = [] 18# List of event tables generated from "/sys" directories. 19_sys_event_tables = [] 20# List of regular metric tables. 21_metric_tables = [] 22# List of metric tables generated from "/sys" directories. 23_sys_metric_tables = [] 24# Mapping between sys event table names and sys metric table names. 25_sys_event_table_to_metric_table_mapping = {} 26# Map from an event name to an architecture standard 27# JsonEvent. Architecture standard events are in json files in the top 28# f'{_args.starting_dir}/{_args.arch}' directory. 29_arch_std_events = {} 30# Events to write out when the table is closed 31_pending_events = [] 32# Name of events table to be written out 33_pending_events_tblname = None 34# Metrics to write out when the table is closed 35_pending_metrics = [] 36# Name of metrics table to be written out 37_pending_metrics_tblname = None 38# Global BigCString shared by all structures. 39_bcs = None 40# Map from the name of a metric group to a description of the group. 41_metricgroups = {} 42# Order specific JsonEvent attributes will be visited. 43_json_event_attributes = [ 44 # cmp_sevent related attributes. 45 'name', 'topic', 'desc', 46 # Seems useful, put it early. 47 'event', 48 # Short things in alphabetical order. 49 'compat', 'deprecated', 'perpkg', 'unit', 50 # Longer things (the last won't be iterated over during decompress). 51 'long_desc' 52] 53 54# Attributes that are in pmu_metric rather than pmu_event. 55_json_metric_attributes = [ 56 'metric_name', 'metric_group', 'metric_expr', 'metric_threshold', 57 'desc', 'long_desc', 'unit', 'compat', 'metricgroup_no_group', 58 'default_metricgroup_name', 'aggr_mode', 'event_grouping' 59] 60# Attributes that are bools or enum int values, encoded as '0', '1',... 61_json_enum_attributes = ['aggr_mode', 'deprecated', 'event_grouping', 'perpkg'] 62 63def removesuffix(s: str, suffix: str) -> str: 64 """Remove the suffix from a string 65 66 The removesuffix function is added to str in Python 3.9. We aim for 3.6 67 compatibility and so provide our own function here. 68 """ 69 return s[0:-len(suffix)] if s.endswith(suffix) else s 70 71 72def file_name_to_table_name(prefix: str, parents: Sequence[str], 73 dirname: str) -> str: 74 """Generate a C table name from directory names.""" 75 tblname = prefix 76 for p in parents: 77 tblname += '_' + p 78 tblname += '_' + dirname 79 return tblname.replace('-', '_') 80 81 82def c_len(s: str) -> int: 83 """Return the length of s a C string 84 85 This doesn't handle all escape characters properly. It first assumes 86 all \\ are for escaping, it then adjusts as it will have over counted 87 \\. The code uses \000 rather than \0 as a terminator as an adjacent 88 number would be folded into a string of \0 (ie. "\0" + "5" doesn't 89 equal a terminator followed by the number 5 but the escape of 90 \05). The code adjusts for \000 but not properly for all octal, hex 91 or unicode values. 92 """ 93 try: 94 utf = s.encode(encoding='utf-8',errors='strict') 95 except: 96 print(f'broken string {s}') 97 raise 98 return len(utf) - utf.count(b'\\') + utf.count(b'\\\\') - (utf.count(b'\\000') * 2) 99 100class BigCString: 101 """A class to hold many strings concatenated together. 102 103 Generating a large number of stand-alone C strings creates a large 104 number of relocations in position independent code. The BigCString 105 is a helper for this case. It builds a single string which within it 106 are all the other C strings (to avoid memory issues the string 107 itself is held as a list of strings). The offsets within the big 108 string are recorded and when stored to disk these don't need 109 relocation. To reduce the size of the string further, identical 110 strings are merged. If a longer string ends-with the same value as a 111 shorter string, these entries are also merged. 112 """ 113 strings: Set[str] 114 big_string: Sequence[str] 115 offsets: Dict[str, int] 116 insert_number: int 117 insert_point: Dict[str, int] 118 metrics: Set[str] 119 120 def __init__(self): 121 self.strings = set() 122 self.insert_number = 0; 123 self.insert_point = {} 124 self.metrics = set() 125 126 def add(self, s: str, metric: bool) -> None: 127 """Called to add to the big string.""" 128 if s not in self.strings: 129 self.strings.add(s) 130 self.insert_point[s] = self.insert_number 131 self.insert_number += 1 132 if metric: 133 self.metrics.add(s) 134 135 def compute(self) -> None: 136 """Called once all strings are added to compute the string and offsets.""" 137 138 folded_strings = {} 139 # Determine if two strings can be folded, ie. let 1 string use the 140 # end of another. First reverse all strings and sort them. 141 sorted_reversed_strings = sorted([x[::-1] for x in self.strings]) 142 143 # Strings 'xyz' and 'yz' will now be [ 'zy', 'zyx' ]. Scan forward 144 # for each string to see if there is a better candidate to fold it 145 # into, in the example rather than using 'yz' we can use'xyz' at 146 # an offset of 1. We record which string can be folded into which 147 # in folded_strings, we don't need to record the offset as it is 148 # trivially computed from the string lengths. 149 for pos,s in enumerate(sorted_reversed_strings): 150 best_pos = pos 151 for check_pos in range(pos + 1, len(sorted_reversed_strings)): 152 if sorted_reversed_strings[check_pos].startswith(s): 153 best_pos = check_pos 154 else: 155 break 156 if pos != best_pos: 157 folded_strings[s[::-1]] = sorted_reversed_strings[best_pos][::-1] 158 159 # Compute reverse mappings for debugging. 160 fold_into_strings = collections.defaultdict(set) 161 for key, val in folded_strings.items(): 162 if key != val: 163 fold_into_strings[val].add(key) 164 165 # big_string_offset is the current location within the C string 166 # being appended to - comments, etc. don't count. big_string is 167 # the string contents represented as a list. Strings are immutable 168 # in Python and so appending to one causes memory issues, while 169 # lists are mutable. 170 big_string_offset = 0 171 self.big_string = [] 172 self.offsets = {} 173 174 def string_cmp_key(s: str) -> Tuple[bool, int, str]: 175 return (s in self.metrics, self.insert_point[s], s) 176 177 # Emit all strings that aren't folded in a sorted manner. 178 for s in sorted(self.strings, key=string_cmp_key): 179 if s not in folded_strings: 180 self.offsets[s] = big_string_offset 181 self.big_string.append(f'/* offset={big_string_offset} */ "') 182 self.big_string.append(s) 183 self.big_string.append('"') 184 if s in fold_into_strings: 185 self.big_string.append(' /* also: ' + ', '.join(fold_into_strings[s]) + ' */') 186 self.big_string.append('\n') 187 big_string_offset += c_len(s) 188 continue 189 190 # Compute the offsets of the folded strings. 191 for s in folded_strings.keys(): 192 assert s not in self.offsets 193 folded_s = folded_strings[s] 194 self.offsets[s] = self.offsets[folded_s] + c_len(folded_s) - c_len(s) 195 196_bcs = BigCString() 197 198class JsonEvent: 199 """Representation of an event loaded from a json file dictionary.""" 200 201 def __init__(self, jd: dict): 202 """Constructor passed the dictionary of parsed json values.""" 203 204 def llx(x: int) -> str: 205 """Convert an int to a string similar to a printf modifier of %#llx.""" 206 return str(x) if x >= 0 and x < 10 else hex(x) 207 208 def fixdesc(s: str) -> str: 209 """Fix formatting issue for the desc string.""" 210 if s is None: 211 return None 212 return removesuffix(removesuffix(removesuffix(s, '. '), 213 '. '), '.').replace('\n', '\\n').replace( 214 '\"', '\\"').replace('\r', '\\r') 215 216 def convert_aggr_mode(aggr_mode: str) -> Optional[str]: 217 """Returns the aggr_mode_class enum value associated with the JSON string.""" 218 if not aggr_mode: 219 return None 220 aggr_mode_to_enum = { 221 'PerChip': '1', 222 'PerCore': '2', 223 } 224 return aggr_mode_to_enum[aggr_mode] 225 226 def convert_metric_constraint(metric_constraint: str) -> Optional[str]: 227 """Returns the metric_event_groups enum value associated with the JSON string.""" 228 if not metric_constraint: 229 return None 230 metric_constraint_to_enum = { 231 'NO_GROUP_EVENTS': '1', 232 'NO_GROUP_EVENTS_NMI': '2', 233 'NO_NMI_WATCHDOG': '2', 234 'NO_GROUP_EVENTS_SMT': '3', 235 } 236 return metric_constraint_to_enum[metric_constraint] 237 238 def lookup_msr(num: str) -> Optional[str]: 239 """Converts the msr number, or first in a list to the appropriate event field.""" 240 if not num: 241 return None 242 msrmap = { 243 0x3F6: 'ldlat=', 244 0x1A6: 'offcore_rsp=', 245 0x1A7: 'offcore_rsp=', 246 0x3F7: 'frontend=', 247 } 248 return msrmap[int(num.split(',', 1)[0], 0)] 249 250 def real_event(name: str, event: str) -> Optional[str]: 251 """Convert well known event names to an event string otherwise use the event argument.""" 252 fixed = { 253 'inst_retired.any': 'event=0xc0,period=2000003', 254 'inst_retired.any_p': 'event=0xc0,period=2000003', 255 'cpu_clk_unhalted.ref': 'event=0x0,umask=0x03,period=2000003', 256 'cpu_clk_unhalted.thread': 'event=0x3c,period=2000003', 257 'cpu_clk_unhalted.core': 'event=0x3c,period=2000003', 258 'cpu_clk_unhalted.thread_any': 'event=0x3c,any=1,period=2000003', 259 } 260 if not name: 261 return None 262 if name.lower() in fixed: 263 return fixed[name.lower()] 264 return event 265 266 def unit_to_pmu(unit: str) -> Optional[str]: 267 """Convert a JSON Unit to Linux PMU name.""" 268 if not unit: 269 return 'default_core' 270 # Comment brought over from jevents.c: 271 # it's not realistic to keep adding these, we need something more scalable ... 272 table = { 273 'CBO': 'uncore_cbox', 274 'QPI LL': 'uncore_qpi', 275 'SBO': 'uncore_sbox', 276 'iMPH-U': 'uncore_arb', 277 'CPU-M-CF': 'cpum_cf', 278 'CPU-M-SF': 'cpum_sf', 279 'PAI-CRYPTO' : 'pai_crypto', 280 'PAI-EXT' : 'pai_ext', 281 'UPI LL': 'uncore_upi', 282 'hisi_sicl,cpa': 'hisi_sicl,cpa', 283 'hisi_sccl,ddrc': 'hisi_sccl,ddrc', 284 'hisi_sccl,hha': 'hisi_sccl,hha', 285 'hisi_sccl,l3c': 'hisi_sccl,l3c', 286 'imx8_ddr': 'imx8_ddr', 287 'L3PMC': 'amd_l3', 288 'DFPMC': 'amd_df', 289 'UMCPMC': 'amd_umc', 290 'cpu_core': 'cpu_core', 291 'cpu_atom': 'cpu_atom', 292 'ali_drw': 'ali_drw', 293 'arm_cmn': 'arm_cmn', 294 } 295 return table[unit] if unit in table else f'uncore_{unit.lower()}' 296 297 def is_zero(val: str) -> bool: 298 try: 299 if val.startswith('0x'): 300 return int(val, 16) == 0 301 else: 302 return int(val) == 0 303 except e: 304 return False 305 306 def canonicalize_value(val: str) -> str: 307 try: 308 if val.startswith('0x'): 309 return llx(int(val, 16)) 310 return str(int(val)) 311 except e: 312 return val 313 314 eventcode = 0 315 if 'EventCode' in jd: 316 eventcode = int(jd['EventCode'].split(',', 1)[0], 0) 317 if 'ExtSel' in jd: 318 eventcode |= int(jd['ExtSel']) << 8 319 configcode = int(jd['ConfigCode'], 0) if 'ConfigCode' in jd else None 320 eventidcode = int(jd['EventidCode'], 0) if 'EventidCode' in jd else None 321 self.name = jd['EventName'].lower() if 'EventName' in jd else None 322 self.topic = '' 323 self.compat = jd.get('Compat') 324 self.desc = fixdesc(jd.get('BriefDescription')) 325 self.long_desc = fixdesc(jd.get('PublicDescription')) 326 precise = jd.get('PEBS') 327 msr = lookup_msr(jd.get('MSRIndex')) 328 msrval = jd.get('MSRValue') 329 extra_desc = '' 330 if 'Data_LA' in jd: 331 extra_desc += ' Supports address when precise' 332 if 'Errata' in jd: 333 extra_desc += '.' 334 if 'Errata' in jd: 335 extra_desc += ' Spec update: ' + jd['Errata'] 336 self.pmu = unit_to_pmu(jd.get('Unit')) 337 filter = jd.get('Filter') 338 self.unit = jd.get('ScaleUnit') 339 self.perpkg = jd.get('PerPkg') 340 self.aggr_mode = convert_aggr_mode(jd.get('AggregationMode')) 341 self.deprecated = jd.get('Deprecated') 342 self.metric_name = jd.get('MetricName') 343 self.metric_group = jd.get('MetricGroup') 344 self.metricgroup_no_group = jd.get('MetricgroupNoGroup') 345 self.default_metricgroup_name = jd.get('DefaultMetricgroupName') 346 self.event_grouping = convert_metric_constraint(jd.get('MetricConstraint')) 347 self.metric_expr = None 348 if 'MetricExpr' in jd: 349 self.metric_expr = metric.ParsePerfJson(jd['MetricExpr']).Simplify() 350 # Note, the metric formula for the threshold isn't parsed as the & 351 # and > have incorrect precedence. 352 self.metric_threshold = jd.get('MetricThreshold') 353 354 arch_std = jd.get('ArchStdEvent') 355 if precise and self.desc and '(Precise Event)' not in self.desc: 356 extra_desc += ' (Must be precise)' if precise == '2' else (' (Precise ' 357 'event)') 358 event = None 359 if configcode is not None: 360 event = f'config={llx(configcode)}' 361 elif eventidcode is not None: 362 event = f'eventid={llx(eventidcode)}' 363 else: 364 event = f'event={llx(eventcode)}' 365 event_fields = [ 366 ('AnyThread', 'any='), 367 ('PortMask', 'ch_mask='), 368 ('CounterMask', 'cmask='), 369 ('EdgeDetect', 'edge='), 370 ('FCMask', 'fc_mask='), 371 ('Invert', 'inv='), 372 ('SampleAfterValue', 'period='), 373 ('UMask', 'umask='), 374 ('NodeType', 'type='), 375 ('RdWrMask', 'rdwrmask='), 376 ] 377 for key, value in event_fields: 378 if key in jd and not is_zero(jd[key]): 379 event += f',{value}{canonicalize_value(jd[key])}' 380 if filter: 381 event += f',{filter}' 382 if msr: 383 event += f',{msr}{msrval}' 384 if self.desc and extra_desc: 385 self.desc += extra_desc 386 if self.long_desc and extra_desc: 387 self.long_desc += extra_desc 388 if arch_std: 389 if arch_std.lower() in _arch_std_events: 390 event = _arch_std_events[arch_std.lower()].event 391 # Copy from the architecture standard event to self for undefined fields. 392 for attr, value in _arch_std_events[arch_std.lower()].__dict__.items(): 393 if hasattr(self, attr) and not getattr(self, attr): 394 setattr(self, attr, value) 395 else: 396 raise argparse.ArgumentTypeError('Cannot find arch std event:', arch_std) 397 398 self.event = real_event(self.name, event) 399 400 def __repr__(self) -> str: 401 """String representation primarily for debugging.""" 402 s = '{\n' 403 for attr, value in self.__dict__.items(): 404 if value: 405 s += f'\t{attr} = {value},\n' 406 return s + '}' 407 408 def build_c_string(self, metric: bool) -> str: 409 s = '' 410 for attr in _json_metric_attributes if metric else _json_event_attributes: 411 x = getattr(self, attr) 412 if metric and x and attr == 'metric_expr': 413 # Convert parsed metric expressions into a string. Slashes 414 # must be doubled in the file. 415 x = x.ToPerfJson().replace('\\', '\\\\') 416 if metric and x and attr == 'metric_threshold': 417 x = x.replace('\\', '\\\\') 418 if attr in _json_enum_attributes: 419 s += x if x else '0' 420 else: 421 s += f'{x}\\000' if x else '\\000' 422 return s 423 424 def to_c_string(self, metric: bool) -> str: 425 """Representation of the event as a C struct initializer.""" 426 427 s = self.build_c_string(metric) 428 return f'{{ { _bcs.offsets[s] } }}, /* {s} */\n' 429 430 431@lru_cache(maxsize=None) 432def read_json_events(path: str, topic: str) -> Sequence[JsonEvent]: 433 """Read json events from the specified file.""" 434 try: 435 events = json.load(open(path), object_hook=JsonEvent) 436 except BaseException as err: 437 print(f"Exception processing {path}") 438 raise 439 metrics: list[Tuple[str, str, metric.Expression]] = [] 440 for event in events: 441 event.topic = topic 442 if event.metric_name and '-' not in event.metric_name: 443 metrics.append((event.pmu, event.metric_name, event.metric_expr)) 444 updates = metric.RewriteMetricsInTermsOfOthers(metrics) 445 if updates: 446 for event in events: 447 if event.metric_name in updates: 448 # print(f'Updated {event.metric_name} from\n"{event.metric_expr}"\n' 449 # f'to\n"{updates[event.metric_name]}"') 450 event.metric_expr = updates[event.metric_name] 451 452 return events 453 454def preprocess_arch_std_files(archpath: str) -> None: 455 """Read in all architecture standard events.""" 456 global _arch_std_events 457 for item in os.scandir(archpath): 458 if item.is_file() and item.name.endswith('.json'): 459 for event in read_json_events(item.path, topic=''): 460 if event.name: 461 _arch_std_events[event.name.lower()] = event 462 if event.metric_name: 463 _arch_std_events[event.metric_name.lower()] = event 464 465 466def add_events_table_entries(item: os.DirEntry, topic: str) -> None: 467 """Add contents of file to _pending_events table.""" 468 for e in read_json_events(item.path, topic): 469 if e.name: 470 _pending_events.append(e) 471 if e.metric_name: 472 _pending_metrics.append(e) 473 474 475def print_pending_events() -> None: 476 """Optionally close events table.""" 477 478 def event_cmp_key(j: JsonEvent) -> Tuple[str, str, bool, str, str]: 479 def fix_none(s: Optional[str]) -> str: 480 if s is None: 481 return '' 482 return s 483 484 return (fix_none(j.pmu).replace(',','_'), fix_none(j.name), j.desc is not None, fix_none(j.topic), 485 fix_none(j.metric_name)) 486 487 global _pending_events 488 if not _pending_events: 489 return 490 491 global _pending_events_tblname 492 if _pending_events_tblname.endswith('_sys'): 493 global _sys_event_tables 494 _sys_event_tables.append(_pending_events_tblname) 495 else: 496 global event_tables 497 _event_tables.append(_pending_events_tblname) 498 499 first = True 500 last_pmu = None 501 pmus = set() 502 for event in sorted(_pending_events, key=event_cmp_key): 503 if event.pmu != last_pmu: 504 if not first: 505 _args.output_file.write('};\n') 506 pmu_name = event.pmu.replace(',', '_') 507 _args.output_file.write( 508 f'static const struct compact_pmu_event {_pending_events_tblname}_{pmu_name}[] = {{\n') 509 first = False 510 last_pmu = event.pmu 511 pmus.add((event.pmu, pmu_name)) 512 513 _args.output_file.write(event.to_c_string(metric=False)) 514 _pending_events = [] 515 516 _args.output_file.write(f""" 517}}; 518 519const struct pmu_table_entry {_pending_events_tblname}[] = {{ 520""") 521 for (pmu, tbl_pmu) in sorted(pmus): 522 pmu_name = f"{pmu}\\000" 523 _args.output_file.write(f"""{{ 524 .entries = {_pending_events_tblname}_{tbl_pmu}, 525 .num_entries = ARRAY_SIZE({_pending_events_tblname}_{tbl_pmu}), 526 .pmu_name = {{ {_bcs.offsets[pmu_name]} /* {pmu_name} */ }}, 527}}, 528""") 529 _args.output_file.write('};\n\n') 530 531def print_pending_metrics() -> None: 532 """Optionally close metrics table.""" 533 534 def metric_cmp_key(j: JsonEvent) -> Tuple[bool, str, str]: 535 def fix_none(s: Optional[str]) -> str: 536 if s is None: 537 return '' 538 return s 539 540 return (j.desc is not None, fix_none(j.pmu), fix_none(j.metric_name)) 541 542 global _pending_metrics 543 if not _pending_metrics: 544 return 545 546 global _pending_metrics_tblname 547 if _pending_metrics_tblname.endswith('_sys'): 548 global _sys_metric_tables 549 _sys_metric_tables.append(_pending_metrics_tblname) 550 else: 551 global metric_tables 552 _metric_tables.append(_pending_metrics_tblname) 553 554 first = True 555 last_pmu = None 556 pmus = set() 557 for metric in sorted(_pending_metrics, key=metric_cmp_key): 558 if metric.pmu != last_pmu: 559 if not first: 560 _args.output_file.write('};\n') 561 pmu_name = metric.pmu.replace(',', '_') 562 _args.output_file.write( 563 f'static const struct compact_pmu_event {_pending_metrics_tblname}_{pmu_name}[] = {{\n') 564 first = False 565 last_pmu = metric.pmu 566 pmus.add((metric.pmu, pmu_name)) 567 568 _args.output_file.write(metric.to_c_string(metric=True)) 569 _pending_metrics = [] 570 571 _args.output_file.write(f""" 572}}; 573 574const struct pmu_table_entry {_pending_metrics_tblname}[] = {{ 575""") 576 for (pmu, tbl_pmu) in sorted(pmus): 577 pmu_name = f"{pmu}\\000" 578 _args.output_file.write(f"""{{ 579 .entries = {_pending_metrics_tblname}_{tbl_pmu}, 580 .num_entries = ARRAY_SIZE({_pending_metrics_tblname}_{tbl_pmu}), 581 .pmu_name = {{ {_bcs.offsets[pmu_name]} /* {pmu_name} */ }}, 582}}, 583""") 584 _args.output_file.write('};\n\n') 585 586def get_topic(topic: str) -> str: 587 if topic.endswith('metrics.json'): 588 return 'metrics' 589 return removesuffix(topic, '.json').replace('-', ' ') 590 591def preprocess_one_file(parents: Sequence[str], item: os.DirEntry) -> None: 592 593 if item.is_dir(): 594 return 595 596 # base dir or too deep 597 level = len(parents) 598 if level == 0 or level > 4: 599 return 600 601 # Ignore other directories. If the file name does not have a .json 602 # extension, ignore it. It could be a readme.txt for instance. 603 if not item.is_file() or not item.name.endswith('.json'): 604 return 605 606 if item.name == 'metricgroups.json': 607 metricgroup_descriptions = json.load(open(item.path)) 608 for mgroup in metricgroup_descriptions: 609 assert len(mgroup) > 1, parents 610 description = f"{metricgroup_descriptions[mgroup]}\\000" 611 mgroup = f"{mgroup}\\000" 612 _bcs.add(mgroup, metric=True) 613 _bcs.add(description, metric=True) 614 _metricgroups[mgroup] = description 615 return 616 617 topic = get_topic(item.name) 618 for event in read_json_events(item.path, topic): 619 pmu_name = f"{event.pmu}\\000" 620 if event.name: 621 _bcs.add(pmu_name, metric=False) 622 _bcs.add(event.build_c_string(metric=False), metric=False) 623 if event.metric_name: 624 _bcs.add(pmu_name, metric=True) 625 _bcs.add(event.build_c_string(metric=True), metric=True) 626 627def process_one_file(parents: Sequence[str], item: os.DirEntry) -> None: 628 """Process a JSON file during the main walk.""" 629 def is_leaf_dir(path: str) -> bool: 630 for item in os.scandir(path): 631 if item.is_dir(): 632 return False 633 return True 634 635 # model directory, reset topic 636 if item.is_dir() and is_leaf_dir(item.path): 637 print_pending_events() 638 print_pending_metrics() 639 640 global _pending_events_tblname 641 _pending_events_tblname = file_name_to_table_name('pmu_events_', parents, item.name) 642 global _pending_metrics_tblname 643 _pending_metrics_tblname = file_name_to_table_name('pmu_metrics_', parents, item.name) 644 645 if item.name == 'sys': 646 _sys_event_table_to_metric_table_mapping[_pending_events_tblname] = _pending_metrics_tblname 647 return 648 649 # base dir or too deep 650 level = len(parents) 651 if level == 0 or level > 4: 652 return 653 654 # Ignore other directories. If the file name does not have a .json 655 # extension, ignore it. It could be a readme.txt for instance. 656 if not item.is_file() or not item.name.endswith('.json') or item.name == 'metricgroups.json': 657 return 658 659 add_events_table_entries(item, get_topic(item.name)) 660 661 662def print_mapping_table(archs: Sequence[str]) -> None: 663 """Read the mapfile and generate the struct from cpuid string to event table.""" 664 _args.output_file.write(""" 665/* Struct used to make the PMU event table implementation opaque to callers. */ 666struct pmu_events_table { 667 const struct pmu_table_entry *pmus; 668 uint32_t num_pmus; 669}; 670 671/* Struct used to make the PMU metric table implementation opaque to callers. */ 672struct pmu_metrics_table { 673 const struct pmu_table_entry *pmus; 674 uint32_t num_pmus; 675}; 676 677/* 678 * Map a CPU to its table of PMU events. The CPU is identified by the 679 * cpuid field, which is an arch-specific identifier for the CPU. 680 * The identifier specified in tools/perf/pmu-events/arch/xxx/mapfile 681 * must match the get_cpuid_str() in tools/perf/arch/xxx/util/header.c) 682 * 683 * The cpuid can contain any character other than the comma. 684 */ 685struct pmu_events_map { 686 const char *arch; 687 const char *cpuid; 688 struct pmu_events_table event_table; 689 struct pmu_metrics_table metric_table; 690}; 691 692/* 693 * Global table mapping each known CPU for the architecture to its 694 * table of PMU events. 695 */ 696const struct pmu_events_map pmu_events_map[] = { 697""") 698 for arch in archs: 699 if arch == 'test': 700 _args.output_file.write("""{ 701\t.arch = "testarch", 702\t.cpuid = "testcpu", 703\t.event_table = { 704\t\t.pmus = pmu_events__test_soc_cpu, 705\t\t.num_pmus = ARRAY_SIZE(pmu_events__test_soc_cpu), 706\t}, 707\t.metric_table = { 708\t\t.pmus = pmu_metrics__test_soc_cpu, 709\t\t.num_pmus = ARRAY_SIZE(pmu_metrics__test_soc_cpu), 710\t} 711}, 712""") 713 else: 714 with open(f'{_args.starting_dir}/{arch}/mapfile.csv') as csvfile: 715 table = csv.reader(csvfile) 716 first = True 717 for row in table: 718 # Skip the first row or any row beginning with #. 719 if not first and len(row) > 0 and not row[0].startswith('#'): 720 event_tblname = file_name_to_table_name('pmu_events_', [], row[2].replace('/', '_')) 721 if event_tblname in _event_tables: 722 event_size = f'ARRAY_SIZE({event_tblname})' 723 else: 724 event_tblname = 'NULL' 725 event_size = '0' 726 metric_tblname = file_name_to_table_name('pmu_metrics_', [], row[2].replace('/', '_')) 727 if metric_tblname in _metric_tables: 728 metric_size = f'ARRAY_SIZE({metric_tblname})' 729 else: 730 metric_tblname = 'NULL' 731 metric_size = '0' 732 if event_size == '0' and metric_size == '0': 733 continue 734 cpuid = row[0].replace('\\', '\\\\') 735 _args.output_file.write(f"""{{ 736\t.arch = "{arch}", 737\t.cpuid = "{cpuid}", 738\t.event_table = {{ 739\t\t.pmus = {event_tblname}, 740\t\t.num_pmus = {event_size} 741\t}}, 742\t.metric_table = {{ 743\t\t.pmus = {metric_tblname}, 744\t\t.num_pmus = {metric_size} 745\t}} 746}}, 747""") 748 first = False 749 750 _args.output_file.write("""{ 751\t.arch = 0, 752\t.cpuid = 0, 753\t.event_table = { 0, 0 }, 754\t.metric_table = { 0, 0 }, 755} 756}; 757""") 758 759 760def print_system_mapping_table() -> None: 761 """C struct mapping table array for tables from /sys directories.""" 762 _args.output_file.write(""" 763struct pmu_sys_events { 764\tconst char *name; 765\tstruct pmu_events_table event_table; 766\tstruct pmu_metrics_table metric_table; 767}; 768 769static const struct pmu_sys_events pmu_sys_event_tables[] = { 770""") 771 printed_metric_tables = [] 772 for tblname in _sys_event_tables: 773 _args.output_file.write(f"""\t{{ 774\t\t.event_table = {{ 775\t\t\t.pmus = {tblname}, 776\t\t\t.num_pmus = ARRAY_SIZE({tblname}) 777\t\t}},""") 778 metric_tblname = _sys_event_table_to_metric_table_mapping[tblname] 779 if metric_tblname in _sys_metric_tables: 780 _args.output_file.write(f""" 781\t\t.metric_table = {{ 782\t\t\t.pmus = {metric_tblname}, 783\t\t\t.num_pmus = ARRAY_SIZE({metric_tblname}) 784\t\t}},""") 785 printed_metric_tables.append(metric_tblname) 786 _args.output_file.write(f""" 787\t\t.name = \"{tblname}\", 788\t}}, 789""") 790 for tblname in _sys_metric_tables: 791 if tblname in printed_metric_tables: 792 continue 793 _args.output_file.write(f"""\t{{ 794\t\t.metric_table = {{ 795\t\t\t.pmus = {tblname}, 796\t\t\t.num_pmus = ARRAY_SIZE({tblname}) 797\t\t}}, 798\t\t.name = \"{tblname}\", 799\t}}, 800""") 801 _args.output_file.write("""\t{ 802\t\t.event_table = { 0, 0 }, 803\t\t.metric_table = { 0, 0 }, 804\t}, 805}; 806 807static void decompress_event(int offset, struct pmu_event *pe) 808{ 809\tconst char *p = &big_c_string[offset]; 810""") 811 for attr in _json_event_attributes: 812 _args.output_file.write(f'\n\tpe->{attr} = ') 813 if attr in _json_enum_attributes: 814 _args.output_file.write("*p - '0';\n") 815 else: 816 _args.output_file.write("(*p == '\\0' ? NULL : p);\n") 817 if attr == _json_event_attributes[-1]: 818 continue 819 if attr in _json_enum_attributes: 820 _args.output_file.write('\tp++;') 821 else: 822 _args.output_file.write('\twhile (*p++);') 823 _args.output_file.write("""} 824 825static void decompress_metric(int offset, struct pmu_metric *pm) 826{ 827\tconst char *p = &big_c_string[offset]; 828""") 829 for attr in _json_metric_attributes: 830 _args.output_file.write(f'\n\tpm->{attr} = ') 831 if attr in _json_enum_attributes: 832 _args.output_file.write("*p - '0';\n") 833 else: 834 _args.output_file.write("(*p == '\\0' ? NULL : p);\n") 835 if attr == _json_metric_attributes[-1]: 836 continue 837 if attr in _json_enum_attributes: 838 _args.output_file.write('\tp++;') 839 else: 840 _args.output_file.write('\twhile (*p++);') 841 _args.output_file.write("""} 842 843static int pmu_events_table__for_each_event_pmu(const struct pmu_events_table *table, 844 const struct pmu_table_entry *pmu, 845 pmu_event_iter_fn fn, 846 void *data) 847{ 848 int ret; 849 struct pmu_event pe = { 850 .pmu = &big_c_string[pmu->pmu_name.offset], 851 }; 852 853 for (uint32_t i = 0; i < pmu->num_entries; i++) { 854 decompress_event(pmu->entries[i].offset, &pe); 855 if (!pe.name) 856 continue; 857 ret = fn(&pe, table, data); 858 if (ret) 859 return ret; 860 } 861 return 0; 862 } 863 864static int pmu_events_table__find_event_pmu(const struct pmu_events_table *table, 865 const struct pmu_table_entry *pmu, 866 const char *name, 867 pmu_event_iter_fn fn, 868 void *data) 869{ 870 struct pmu_event pe = { 871 .pmu = &big_c_string[pmu->pmu_name.offset], 872 }; 873 int low = 0, high = pmu->num_entries - 1; 874 875 while (low <= high) { 876 int cmp, mid = (low + high) / 2; 877 878 decompress_event(pmu->entries[mid].offset, &pe); 879 880 if (!pe.name && !name) 881 goto do_call; 882 883 if (!pe.name && name) { 884 low = mid + 1; 885 continue; 886 } 887 if (pe.name && !name) { 888 high = mid - 1; 889 continue; 890 } 891 892 cmp = strcasecmp(pe.name, name); 893 if (cmp < 0) { 894 low = mid + 1; 895 continue; 896 } 897 if (cmp > 0) { 898 high = mid - 1; 899 continue; 900 } 901 do_call: 902 return fn ? fn(&pe, table, data) : 0; 903 } 904 return -1000; 905} 906 907int pmu_events_table__for_each_event(const struct pmu_events_table *table, 908 struct perf_pmu *pmu, 909 pmu_event_iter_fn fn, 910 void *data) 911{ 912 for (size_t i = 0; i < table->num_pmus; i++) { 913 const struct pmu_table_entry *table_pmu = &table->pmus[i]; 914 const char *pmu_name = &big_c_string[table_pmu->pmu_name.offset]; 915 int ret; 916 917 if (pmu && !pmu__name_match(pmu, pmu_name)) 918 continue; 919 920 ret = pmu_events_table__for_each_event_pmu(table, table_pmu, fn, data); 921 if (pmu || ret) 922 return ret; 923 } 924 return 0; 925} 926 927int pmu_events_table__find_event(const struct pmu_events_table *table, 928 struct perf_pmu *pmu, 929 const char *name, 930 pmu_event_iter_fn fn, 931 void *data) 932{ 933 for (size_t i = 0; i < table->num_pmus; i++) { 934 const struct pmu_table_entry *table_pmu = &table->pmus[i]; 935 const char *pmu_name = &big_c_string[table_pmu->pmu_name.offset]; 936 int ret; 937 938 if (!pmu__name_match(pmu, pmu_name)) 939 continue; 940 941 ret = pmu_events_table__find_event_pmu(table, table_pmu, name, fn, data); 942 if (ret != -1000) 943 return ret; 944 } 945 return -1000; 946} 947 948size_t pmu_events_table__num_events(const struct pmu_events_table *table, 949 struct perf_pmu *pmu) 950{ 951 size_t count = 0; 952 953 for (size_t i = 0; i < table->num_pmus; i++) { 954 const struct pmu_table_entry *table_pmu = &table->pmus[i]; 955 const char *pmu_name = &big_c_string[table_pmu->pmu_name.offset]; 956 957 if (pmu__name_match(pmu, pmu_name)) 958 count += table_pmu->num_entries; 959 } 960 return count; 961} 962 963static int pmu_metrics_table__for_each_metric_pmu(const struct pmu_metrics_table *table, 964 const struct pmu_table_entry *pmu, 965 pmu_metric_iter_fn fn, 966 void *data) 967{ 968 int ret; 969 struct pmu_metric pm = { 970 .pmu = &big_c_string[pmu->pmu_name.offset], 971 }; 972 973 for (uint32_t i = 0; i < pmu->num_entries; i++) { 974 decompress_metric(pmu->entries[i].offset, &pm); 975 if (!pm.metric_expr) 976 continue; 977 ret = fn(&pm, table, data); 978 if (ret) 979 return ret; 980 } 981 return 0; 982} 983 984int pmu_metrics_table__for_each_metric(const struct pmu_metrics_table *table, 985 pmu_metric_iter_fn fn, 986 void *data) 987{ 988 for (size_t i = 0; i < table->num_pmus; i++) { 989 int ret = pmu_metrics_table__for_each_metric_pmu(table, &table->pmus[i], 990 fn, data); 991 992 if (ret) 993 return ret; 994 } 995 return 0; 996} 997 998static const struct pmu_events_map *map_for_pmu(struct perf_pmu *pmu) 999{ 1000 static struct { 1001 const struct pmu_events_map *map; 1002 struct perf_pmu *pmu; 1003 } last_result; 1004 static struct { 1005 const struct pmu_events_map *map; 1006 char *cpuid; 1007 } last_map_search; 1008 static bool has_last_result, has_last_map_search; 1009 const struct pmu_events_map *map = NULL; 1010 char *cpuid = NULL; 1011 size_t i; 1012 1013 if (has_last_result && last_result.pmu == pmu) 1014 return last_result.map; 1015 1016 cpuid = perf_pmu__getcpuid(pmu); 1017 1018 /* 1019 * On some platforms which uses cpus map, cpuid can be NULL for 1020 * PMUs other than CORE PMUs. 1021 */ 1022 if (!cpuid) 1023 goto out_update_last_result; 1024 1025 if (has_last_map_search && !strcmp(last_map_search.cpuid, cpuid)) { 1026 map = last_map_search.map; 1027 free(cpuid); 1028 } else { 1029 i = 0; 1030 for (;;) { 1031 map = &pmu_events_map[i++]; 1032 1033 if (!map->arch) { 1034 map = NULL; 1035 break; 1036 } 1037 1038 if (!strcmp_cpuid_str(map->cpuid, cpuid)) 1039 break; 1040 } 1041 free(last_map_search.cpuid); 1042 last_map_search.cpuid = cpuid; 1043 last_map_search.map = map; 1044 has_last_map_search = true; 1045 } 1046out_update_last_result: 1047 last_result.pmu = pmu; 1048 last_result.map = map; 1049 has_last_result = true; 1050 return map; 1051} 1052 1053const struct pmu_events_table *perf_pmu__find_events_table(struct perf_pmu *pmu) 1054{ 1055 const struct pmu_events_map *map = map_for_pmu(pmu); 1056 1057 if (!map) 1058 return NULL; 1059 1060 if (!pmu) 1061 return &map->event_table; 1062 1063 for (size_t i = 0; i < map->event_table.num_pmus; i++) { 1064 const struct pmu_table_entry *table_pmu = &map->event_table.pmus[i]; 1065 const char *pmu_name = &big_c_string[table_pmu->pmu_name.offset]; 1066 1067 if (pmu__name_match(pmu, pmu_name)) 1068 return &map->event_table; 1069 } 1070 return NULL; 1071} 1072 1073const struct pmu_metrics_table *perf_pmu__find_metrics_table(struct perf_pmu *pmu) 1074{ 1075 const struct pmu_events_map *map = map_for_pmu(pmu); 1076 1077 if (!map) 1078 return NULL; 1079 1080 if (!pmu) 1081 return &map->metric_table; 1082 1083 for (size_t i = 0; i < map->metric_table.num_pmus; i++) { 1084 const struct pmu_table_entry *table_pmu = &map->metric_table.pmus[i]; 1085 const char *pmu_name = &big_c_string[table_pmu->pmu_name.offset]; 1086 1087 if (pmu__name_match(pmu, pmu_name)) 1088 return &map->metric_table; 1089 } 1090 return NULL; 1091} 1092 1093const struct pmu_events_table *find_core_events_table(const char *arch, const char *cpuid) 1094{ 1095 for (const struct pmu_events_map *tables = &pmu_events_map[0]; 1096 tables->arch; 1097 tables++) { 1098 if (!strcmp(tables->arch, arch) && !strcmp_cpuid_str(tables->cpuid, cpuid)) 1099 return &tables->event_table; 1100 } 1101 return NULL; 1102} 1103 1104const struct pmu_metrics_table *find_core_metrics_table(const char *arch, const char *cpuid) 1105{ 1106 for (const struct pmu_events_map *tables = &pmu_events_map[0]; 1107 tables->arch; 1108 tables++) { 1109 if (!strcmp(tables->arch, arch) && !strcmp_cpuid_str(tables->cpuid, cpuid)) 1110 return &tables->metric_table; 1111 } 1112 return NULL; 1113} 1114 1115int pmu_for_each_core_event(pmu_event_iter_fn fn, void *data) 1116{ 1117 for (const struct pmu_events_map *tables = &pmu_events_map[0]; 1118 tables->arch; 1119 tables++) { 1120 int ret = pmu_events_table__for_each_event(&tables->event_table, 1121 /*pmu=*/ NULL, fn, data); 1122 1123 if (ret) 1124 return ret; 1125 } 1126 return 0; 1127} 1128 1129int pmu_for_each_core_metric(pmu_metric_iter_fn fn, void *data) 1130{ 1131 for (const struct pmu_events_map *tables = &pmu_events_map[0]; 1132 tables->arch; 1133 tables++) { 1134 int ret = pmu_metrics_table__for_each_metric(&tables->metric_table, fn, data); 1135 1136 if (ret) 1137 return ret; 1138 } 1139 return 0; 1140} 1141 1142const struct pmu_events_table *find_sys_events_table(const char *name) 1143{ 1144 for (const struct pmu_sys_events *tables = &pmu_sys_event_tables[0]; 1145 tables->name; 1146 tables++) { 1147 if (!strcmp(tables->name, name)) 1148 return &tables->event_table; 1149 } 1150 return NULL; 1151} 1152 1153int pmu_for_each_sys_event(pmu_event_iter_fn fn, void *data) 1154{ 1155 for (const struct pmu_sys_events *tables = &pmu_sys_event_tables[0]; 1156 tables->name; 1157 tables++) { 1158 int ret = pmu_events_table__for_each_event(&tables->event_table, 1159 /*pmu=*/ NULL, fn, data); 1160 1161 if (ret) 1162 return ret; 1163 } 1164 return 0; 1165} 1166 1167int pmu_for_each_sys_metric(pmu_metric_iter_fn fn, void *data) 1168{ 1169 for (const struct pmu_sys_events *tables = &pmu_sys_event_tables[0]; 1170 tables->name; 1171 tables++) { 1172 int ret = pmu_metrics_table__for_each_metric(&tables->metric_table, fn, data); 1173 1174 if (ret) 1175 return ret; 1176 } 1177 return 0; 1178} 1179""") 1180 1181def print_metricgroups() -> None: 1182 _args.output_file.write(""" 1183static const int metricgroups[][2] = { 1184""") 1185 for mgroup in sorted(_metricgroups): 1186 description = _metricgroups[mgroup] 1187 _args.output_file.write( 1188 f'\t{{ {_bcs.offsets[mgroup]}, {_bcs.offsets[description]} }}, /* {mgroup} => {description} */\n' 1189 ) 1190 _args.output_file.write(""" 1191}; 1192 1193const char *describe_metricgroup(const char *group) 1194{ 1195 int low = 0, high = (int)ARRAY_SIZE(metricgroups) - 1; 1196 1197 while (low <= high) { 1198 int mid = (low + high) / 2; 1199 const char *mgroup = &big_c_string[metricgroups[mid][0]]; 1200 int cmp = strcmp(mgroup, group); 1201 1202 if (cmp == 0) { 1203 return &big_c_string[metricgroups[mid][1]]; 1204 } else if (cmp < 0) { 1205 low = mid + 1; 1206 } else { 1207 high = mid - 1; 1208 } 1209 } 1210 return NULL; 1211} 1212""") 1213 1214def main() -> None: 1215 global _args 1216 1217 def dir_path(path: str) -> str: 1218 """Validate path is a directory for argparse.""" 1219 if os.path.isdir(path): 1220 return path 1221 raise argparse.ArgumentTypeError(f'\'{path}\' is not a valid directory') 1222 1223 def ftw(path: str, parents: Sequence[str], 1224 action: Callable[[Sequence[str], os.DirEntry], None]) -> None: 1225 """Replicate the directory/file walking behavior of C's file tree walk.""" 1226 for item in sorted(os.scandir(path), key=lambda e: e.name): 1227 if _args.model != 'all' and item.is_dir(): 1228 # Check if the model matches one in _args.model. 1229 if len(parents) == _args.model.split(',')[0].count('/'): 1230 # We're testing the correct directory. 1231 item_path = '/'.join(parents) + ('/' if len(parents) > 0 else '') + item.name 1232 if 'test' not in item_path and item_path not in _args.model.split(','): 1233 continue 1234 action(parents, item) 1235 if item.is_dir(): 1236 ftw(item.path, parents + [item.name], action) 1237 1238 ap = argparse.ArgumentParser() 1239 ap.add_argument('arch', help='Architecture name like x86') 1240 ap.add_argument('model', help='''Select a model such as skylake to 1241reduce the code size. Normally set to "all". For architectures like 1242ARM64 with an implementor/model, the model must include the implementor 1243such as "arm/cortex-a34".''', 1244 default='all') 1245 ap.add_argument( 1246 'starting_dir', 1247 type=dir_path, 1248 help='Root of tree containing architecture directories containing json files' 1249 ) 1250 ap.add_argument( 1251 'output_file', type=argparse.FileType('w', encoding='utf-8'), nargs='?', default=sys.stdout) 1252 _args = ap.parse_args() 1253 1254 _args.output_file.write(""" 1255#include <pmu-events/pmu-events.h> 1256#include "util/header.h" 1257#include "util/pmu.h" 1258#include <string.h> 1259#include <stddef.h> 1260 1261struct compact_pmu_event { 1262 int offset; 1263}; 1264 1265struct pmu_table_entry { 1266 const struct compact_pmu_event *entries; 1267 uint32_t num_entries; 1268 struct compact_pmu_event pmu_name; 1269}; 1270 1271""") 1272 archs = [] 1273 for item in os.scandir(_args.starting_dir): 1274 if not item.is_dir(): 1275 continue 1276 if item.name == _args.arch or _args.arch == 'all' or item.name == 'test': 1277 archs.append(item.name) 1278 1279 if len(archs) < 2: 1280 raise IOError(f'Missing architecture directory \'{_args.arch}\'') 1281 1282 archs.sort() 1283 for arch in archs: 1284 arch_path = f'{_args.starting_dir}/{arch}' 1285 preprocess_arch_std_files(arch_path) 1286 ftw(arch_path, [], preprocess_one_file) 1287 1288 _bcs.compute() 1289 _args.output_file.write('static const char *const big_c_string =\n') 1290 for s in _bcs.big_string: 1291 _args.output_file.write(s) 1292 _args.output_file.write(';\n\n') 1293 for arch in archs: 1294 arch_path = f'{_args.starting_dir}/{arch}' 1295 ftw(arch_path, [], process_one_file) 1296 print_pending_events() 1297 print_pending_metrics() 1298 1299 print_mapping_table(archs) 1300 print_system_mapping_table() 1301 print_metricgroups() 1302 1303if __name__ == '__main__': 1304 main() 1305