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', 'pmu', '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 'pmu', '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 117 def __init__(self): 118 self.strings = set() 119 120 def add(self, s: str) -> None: 121 """Called to add to the big string.""" 122 self.strings.add(s) 123 124 def compute(self) -> None: 125 """Called once all strings are added to compute the string and offsets.""" 126 127 folded_strings = {} 128 # Determine if two strings can be folded, ie. let 1 string use the 129 # end of another. First reverse all strings and sort them. 130 sorted_reversed_strings = sorted([x[::-1] for x in self.strings]) 131 132 # Strings 'xyz' and 'yz' will now be [ 'zy', 'zyx' ]. Scan forward 133 # for each string to see if there is a better candidate to fold it 134 # into, in the example rather than using 'yz' we can use'xyz' at 135 # an offset of 1. We record which string can be folded into which 136 # in folded_strings, we don't need to record the offset as it is 137 # trivially computed from the string lengths. 138 for pos,s in enumerate(sorted_reversed_strings): 139 best_pos = pos 140 for check_pos in range(pos + 1, len(sorted_reversed_strings)): 141 if sorted_reversed_strings[check_pos].startswith(s): 142 best_pos = check_pos 143 else: 144 break 145 if pos != best_pos: 146 folded_strings[s[::-1]] = sorted_reversed_strings[best_pos][::-1] 147 148 # Compute reverse mappings for debugging. 149 fold_into_strings = collections.defaultdict(set) 150 for key, val in folded_strings.items(): 151 if key != val: 152 fold_into_strings[val].add(key) 153 154 # big_string_offset is the current location within the C string 155 # being appended to - comments, etc. don't count. big_string is 156 # the string contents represented as a list. Strings are immutable 157 # in Python and so appending to one causes memory issues, while 158 # lists are mutable. 159 big_string_offset = 0 160 self.big_string = [] 161 self.offsets = {} 162 163 # Emit all strings that aren't folded in a sorted manner. 164 for s in sorted(self.strings): 165 if s not in folded_strings: 166 self.offsets[s] = big_string_offset 167 self.big_string.append(f'/* offset={big_string_offset} */ "') 168 self.big_string.append(s) 169 self.big_string.append('"') 170 if s in fold_into_strings: 171 self.big_string.append(' /* also: ' + ', '.join(fold_into_strings[s]) + ' */') 172 self.big_string.append('\n') 173 big_string_offset += c_len(s) 174 continue 175 176 # Compute the offsets of the folded strings. 177 for s in folded_strings.keys(): 178 assert s not in self.offsets 179 folded_s = folded_strings[s] 180 self.offsets[s] = self.offsets[folded_s] + c_len(folded_s) - c_len(s) 181 182_bcs = BigCString() 183 184class JsonEvent: 185 """Representation of an event loaded from a json file dictionary.""" 186 187 def __init__(self, jd: dict): 188 """Constructor passed the dictionary of parsed json values.""" 189 190 def llx(x: int) -> str: 191 """Convert an int to a string similar to a printf modifier of %#llx.""" 192 return '0' if x == 0 else hex(x) 193 194 def fixdesc(s: str) -> str: 195 """Fix formatting issue for the desc string.""" 196 if s is None: 197 return None 198 return removesuffix(removesuffix(removesuffix(s, '. '), 199 '. '), '.').replace('\n', '\\n').replace( 200 '\"', '\\"').replace('\r', '\\r') 201 202 def convert_aggr_mode(aggr_mode: str) -> Optional[str]: 203 """Returns the aggr_mode_class enum value associated with the JSON string.""" 204 if not aggr_mode: 205 return None 206 aggr_mode_to_enum = { 207 'PerChip': '1', 208 'PerCore': '2', 209 } 210 return aggr_mode_to_enum[aggr_mode] 211 212 def convert_metric_constraint(metric_constraint: str) -> Optional[str]: 213 """Returns the metric_event_groups enum value associated with the JSON string.""" 214 if not metric_constraint: 215 return None 216 metric_constraint_to_enum = { 217 'NO_GROUP_EVENTS': '1', 218 'NO_GROUP_EVENTS_NMI': '2', 219 'NO_NMI_WATCHDOG': '2', 220 'NO_GROUP_EVENTS_SMT': '3', 221 } 222 return metric_constraint_to_enum[metric_constraint] 223 224 def lookup_msr(num: str) -> Optional[str]: 225 """Converts the msr number, or first in a list to the appropriate event field.""" 226 if not num: 227 return None 228 msrmap = { 229 0x3F6: 'ldlat=', 230 0x1A6: 'offcore_rsp=', 231 0x1A7: 'offcore_rsp=', 232 0x3F7: 'frontend=', 233 } 234 return msrmap[int(num.split(',', 1)[0], 0)] 235 236 def real_event(name: str, event: str) -> Optional[str]: 237 """Convert well known event names to an event string otherwise use the event argument.""" 238 fixed = { 239 'inst_retired.any': 'event=0xc0,period=2000003', 240 'inst_retired.any_p': 'event=0xc0,period=2000003', 241 'cpu_clk_unhalted.ref': 'event=0x0,umask=0x03,period=2000003', 242 'cpu_clk_unhalted.thread': 'event=0x3c,period=2000003', 243 'cpu_clk_unhalted.core': 'event=0x3c,period=2000003', 244 'cpu_clk_unhalted.thread_any': 'event=0x3c,any=1,period=2000003', 245 } 246 if not name: 247 return None 248 if name.lower() in fixed: 249 return fixed[name.lower()] 250 return event 251 252 def unit_to_pmu(unit: str) -> Optional[str]: 253 """Convert a JSON Unit to Linux PMU name.""" 254 if not unit: 255 return None 256 # Comment brought over from jevents.c: 257 # it's not realistic to keep adding these, we need something more scalable ... 258 table = { 259 'CBO': 'uncore_cbox', 260 'QPI LL': 'uncore_qpi', 261 'SBO': 'uncore_sbox', 262 'iMPH-U': 'uncore_arb', 263 'CPU-M-CF': 'cpum_cf', 264 'CPU-M-SF': 'cpum_sf', 265 'PAI-CRYPTO' : 'pai_crypto', 266 'PAI-EXT' : 'pai_ext', 267 'UPI LL': 'uncore_upi', 268 'hisi_sicl,cpa': 'hisi_sicl,cpa', 269 'hisi_sccl,ddrc': 'hisi_sccl,ddrc', 270 'hisi_sccl,hha': 'hisi_sccl,hha', 271 'hisi_sccl,l3c': 'hisi_sccl,l3c', 272 'imx8_ddr': 'imx8_ddr', 273 'L3PMC': 'amd_l3', 274 'DFPMC': 'amd_df', 275 'cpu_core': 'cpu_core', 276 'cpu_atom': 'cpu_atom', 277 } 278 return table[unit] if unit in table else f'uncore_{unit.lower()}' 279 280 eventcode = 0 281 if 'EventCode' in jd: 282 eventcode = int(jd['EventCode'].split(',', 1)[0], 0) 283 if 'ExtSel' in jd: 284 eventcode |= int(jd['ExtSel']) << 8 285 configcode = int(jd['ConfigCode'], 0) if 'ConfigCode' in jd else None 286 self.name = jd['EventName'].lower() if 'EventName' in jd else None 287 self.topic = '' 288 self.compat = jd.get('Compat') 289 self.desc = fixdesc(jd.get('BriefDescription')) 290 self.long_desc = fixdesc(jd.get('PublicDescription')) 291 precise = jd.get('PEBS') 292 msr = lookup_msr(jd.get('MSRIndex')) 293 msrval = jd.get('MSRValue') 294 extra_desc = '' 295 if 'Data_LA' in jd: 296 extra_desc += ' Supports address when precise' 297 if 'Errata' in jd: 298 extra_desc += '.' 299 if 'Errata' in jd: 300 extra_desc += ' Spec update: ' + jd['Errata'] 301 self.pmu = unit_to_pmu(jd.get('Unit')) 302 filter = jd.get('Filter') 303 self.unit = jd.get('ScaleUnit') 304 self.perpkg = jd.get('PerPkg') 305 self.aggr_mode = convert_aggr_mode(jd.get('AggregationMode')) 306 self.deprecated = jd.get('Deprecated') 307 self.metric_name = jd.get('MetricName') 308 self.metric_group = jd.get('MetricGroup') 309 self.metricgroup_no_group = jd.get('MetricgroupNoGroup') 310 self.default_metricgroup_name = jd.get('DefaultMetricgroupName') 311 self.event_grouping = convert_metric_constraint(jd.get('MetricConstraint')) 312 self.metric_expr = None 313 if 'MetricExpr' in jd: 314 self.metric_expr = metric.ParsePerfJson(jd['MetricExpr']).Simplify() 315 # Note, the metric formula for the threshold isn't parsed as the & 316 # and > have incorrect precedence. 317 self.metric_threshold = jd.get('MetricThreshold') 318 319 arch_std = jd.get('ArchStdEvent') 320 if precise and self.desc and '(Precise Event)' not in self.desc: 321 extra_desc += ' (Must be precise)' if precise == '2' else (' (Precise ' 322 'event)') 323 event = f'config={llx(configcode)}' if configcode is not None else f'event={llx(eventcode)}' 324 event_fields = [ 325 ('AnyThread', 'any='), 326 ('PortMask', 'ch_mask='), 327 ('CounterMask', 'cmask='), 328 ('EdgeDetect', 'edge='), 329 ('FCMask', 'fc_mask='), 330 ('Invert', 'inv='), 331 ('SampleAfterValue', 'period='), 332 ('UMask', 'umask='), 333 ] 334 for key, value in event_fields: 335 if key in jd and jd[key] != '0': 336 event += ',' + value + jd[key] 337 if filter: 338 event += f',{filter}' 339 if msr: 340 event += f',{msr}{msrval}' 341 if self.desc and extra_desc: 342 self.desc += extra_desc 343 if self.long_desc and extra_desc: 344 self.long_desc += extra_desc 345 if self.pmu: 346 if self.desc and not self.desc.endswith('. '): 347 self.desc += '. ' 348 self.desc = (self.desc if self.desc else '') + ('Unit: ' + self.pmu + ' ') 349 if arch_std and arch_std.lower() in _arch_std_events: 350 event = _arch_std_events[arch_std.lower()].event 351 # Copy from the architecture standard event to self for undefined fields. 352 for attr, value in _arch_std_events[arch_std.lower()].__dict__.items(): 353 if hasattr(self, attr) and not getattr(self, attr): 354 setattr(self, attr, value) 355 356 self.event = real_event(self.name, event) 357 358 def __repr__(self) -> str: 359 """String representation primarily for debugging.""" 360 s = '{\n' 361 for attr, value in self.__dict__.items(): 362 if value: 363 s += f'\t{attr} = {value},\n' 364 return s + '}' 365 366 def build_c_string(self, metric: bool) -> str: 367 s = '' 368 for attr in _json_metric_attributes if metric else _json_event_attributes: 369 x = getattr(self, attr) 370 if metric and x and attr == 'metric_expr': 371 # Convert parsed metric expressions into a string. Slashes 372 # must be doubled in the file. 373 x = x.ToPerfJson().replace('\\', '\\\\') 374 if metric and x and attr == 'metric_threshold': 375 x = x.replace('\\', '\\\\') 376 if attr in _json_enum_attributes: 377 s += x if x else '0' 378 else: 379 s += f'{x}\\000' if x else '\\000' 380 return s 381 382 def to_c_string(self, metric: bool) -> str: 383 """Representation of the event as a C struct initializer.""" 384 385 s = self.build_c_string(metric) 386 return f'{{ { _bcs.offsets[s] } }}, /* {s} */\n' 387 388 389@lru_cache(maxsize=None) 390def read_json_events(path: str, topic: str) -> Sequence[JsonEvent]: 391 """Read json events from the specified file.""" 392 try: 393 events = json.load(open(path), object_hook=JsonEvent) 394 except BaseException as err: 395 print(f"Exception processing {path}") 396 raise 397 metrics: list[Tuple[str, str, metric.Expression]] = [] 398 for event in events: 399 event.topic = topic 400 if event.metric_name and '-' not in event.metric_name: 401 metrics.append((event.pmu, event.metric_name, event.metric_expr)) 402 updates = metric.RewriteMetricsInTermsOfOthers(metrics) 403 if updates: 404 for event in events: 405 if event.metric_name in updates: 406 # print(f'Updated {event.metric_name} from\n"{event.metric_expr}"\n' 407 # f'to\n"{updates[event.metric_name]}"') 408 event.metric_expr = updates[event.metric_name] 409 410 return events 411 412def preprocess_arch_std_files(archpath: str) -> None: 413 """Read in all architecture standard events.""" 414 global _arch_std_events 415 for item in os.scandir(archpath): 416 if item.is_file() and item.name.endswith('.json'): 417 for event in read_json_events(item.path, topic=''): 418 if event.name: 419 _arch_std_events[event.name.lower()] = event 420 if event.metric_name: 421 _arch_std_events[event.metric_name.lower()] = event 422 423 424def add_events_table_entries(item: os.DirEntry, topic: str) -> None: 425 """Add contents of file to _pending_events table.""" 426 for e in read_json_events(item.path, topic): 427 if e.name: 428 _pending_events.append(e) 429 if e.metric_name: 430 _pending_metrics.append(e) 431 432 433def print_pending_events() -> None: 434 """Optionally close events table.""" 435 436 def event_cmp_key(j: JsonEvent) -> Tuple[bool, str, str, str, str]: 437 def fix_none(s: Optional[str]) -> str: 438 if s is None: 439 return '' 440 return s 441 442 return (j.desc is not None, fix_none(j.topic), fix_none(j.name), fix_none(j.pmu), 443 fix_none(j.metric_name)) 444 445 global _pending_events 446 if not _pending_events: 447 return 448 449 global _pending_events_tblname 450 if _pending_events_tblname.endswith('_sys'): 451 global _sys_event_tables 452 _sys_event_tables.append(_pending_events_tblname) 453 else: 454 global event_tables 455 _event_tables.append(_pending_events_tblname) 456 457 _args.output_file.write( 458 f'static const struct compact_pmu_event {_pending_events_tblname}[] = {{\n') 459 460 for event in sorted(_pending_events, key=event_cmp_key): 461 _args.output_file.write(event.to_c_string(metric=False)) 462 _pending_events = [] 463 464 _args.output_file.write('};\n\n') 465 466def print_pending_metrics() -> None: 467 """Optionally close metrics table.""" 468 469 def metric_cmp_key(j: JsonEvent) -> Tuple[bool, str, str]: 470 def fix_none(s: Optional[str]) -> str: 471 if s is None: 472 return '' 473 return s 474 475 return (j.desc is not None, fix_none(j.pmu), fix_none(j.metric_name)) 476 477 global _pending_metrics 478 if not _pending_metrics: 479 return 480 481 global _pending_metrics_tblname 482 if _pending_metrics_tblname.endswith('_sys'): 483 global _sys_metric_tables 484 _sys_metric_tables.append(_pending_metrics_tblname) 485 else: 486 global metric_tables 487 _metric_tables.append(_pending_metrics_tblname) 488 489 _args.output_file.write( 490 f'static const struct compact_pmu_event {_pending_metrics_tblname}[] = {{\n') 491 492 for metric in sorted(_pending_metrics, key=metric_cmp_key): 493 _args.output_file.write(metric.to_c_string(metric=True)) 494 _pending_metrics = [] 495 496 _args.output_file.write('};\n\n') 497 498def get_topic(topic: str) -> str: 499 if topic.endswith('metrics.json'): 500 return 'metrics' 501 return removesuffix(topic, '.json').replace('-', ' ') 502 503def preprocess_one_file(parents: Sequence[str], item: os.DirEntry) -> None: 504 505 if item.is_dir(): 506 return 507 508 # base dir or too deep 509 level = len(parents) 510 if level == 0 or level > 4: 511 return 512 513 # Ignore other directories. If the file name does not have a .json 514 # extension, ignore it. It could be a readme.txt for instance. 515 if not item.is_file() or not item.name.endswith('.json'): 516 return 517 518 if item.name == 'metricgroups.json': 519 metricgroup_descriptions = json.load(open(item.path)) 520 for mgroup in metricgroup_descriptions: 521 assert len(mgroup) > 1, parents 522 description = f"{metricgroup_descriptions[mgroup]}\\000" 523 mgroup = f"{mgroup}\\000" 524 _bcs.add(mgroup) 525 _bcs.add(description) 526 _metricgroups[mgroup] = description 527 return 528 529 topic = get_topic(item.name) 530 for event in read_json_events(item.path, topic): 531 if event.name: 532 _bcs.add(event.build_c_string(metric=False)) 533 if event.metric_name: 534 _bcs.add(event.build_c_string(metric=True)) 535 536def process_one_file(parents: Sequence[str], item: os.DirEntry) -> None: 537 """Process a JSON file during the main walk.""" 538 def is_leaf_dir(path: str) -> bool: 539 for item in os.scandir(path): 540 if item.is_dir(): 541 return False 542 return True 543 544 # model directory, reset topic 545 if item.is_dir() and is_leaf_dir(item.path): 546 print_pending_events() 547 print_pending_metrics() 548 549 global _pending_events_tblname 550 _pending_events_tblname = file_name_to_table_name('pmu_events_', parents, item.name) 551 global _pending_metrics_tblname 552 _pending_metrics_tblname = file_name_to_table_name('pmu_metrics_', parents, item.name) 553 554 if item.name == 'sys': 555 _sys_event_table_to_metric_table_mapping[_pending_events_tblname] = _pending_metrics_tblname 556 return 557 558 # base dir or too deep 559 level = len(parents) 560 if level == 0 or level > 4: 561 return 562 563 # Ignore other directories. If the file name does not have a .json 564 # extension, ignore it. It could be a readme.txt for instance. 565 if not item.is_file() or not item.name.endswith('.json') or item.name == 'metricgroups.json': 566 return 567 568 add_events_table_entries(item, get_topic(item.name)) 569 570 571def print_mapping_table(archs: Sequence[str]) -> None: 572 """Read the mapfile and generate the struct from cpuid string to event table.""" 573 _args.output_file.write(""" 574/* Struct used to make the PMU event table implementation opaque to callers. */ 575struct pmu_events_table { 576 const struct compact_pmu_event *entries; 577 size_t length; 578}; 579 580/* Struct used to make the PMU metric table implementation opaque to callers. */ 581struct pmu_metrics_table { 582 const struct compact_pmu_event *entries; 583 size_t length; 584}; 585 586/* 587 * Map a CPU to its table of PMU events. The CPU is identified by the 588 * cpuid field, which is an arch-specific identifier for the CPU. 589 * The identifier specified in tools/perf/pmu-events/arch/xxx/mapfile 590 * must match the get_cpuid_str() in tools/perf/arch/xxx/util/header.c) 591 * 592 * The cpuid can contain any character other than the comma. 593 */ 594struct pmu_events_map { 595 const char *arch; 596 const char *cpuid; 597 struct pmu_events_table event_table; 598 struct pmu_metrics_table metric_table; 599}; 600 601/* 602 * Global table mapping each known CPU for the architecture to its 603 * table of PMU events. 604 */ 605const struct pmu_events_map pmu_events_map[] = { 606""") 607 for arch in archs: 608 if arch == 'test': 609 _args.output_file.write("""{ 610\t.arch = "testarch", 611\t.cpuid = "testcpu", 612\t.event_table = { 613\t\t.entries = pmu_events__test_soc_cpu, 614\t\t.length = ARRAY_SIZE(pmu_events__test_soc_cpu), 615\t}, 616\t.metric_table = { 617\t\t.entries = pmu_metrics__test_soc_cpu, 618\t\t.length = ARRAY_SIZE(pmu_metrics__test_soc_cpu), 619\t} 620}, 621""") 622 else: 623 with open(f'{_args.starting_dir}/{arch}/mapfile.csv') as csvfile: 624 table = csv.reader(csvfile) 625 first = True 626 for row in table: 627 # Skip the first row or any row beginning with #. 628 if not first and len(row) > 0 and not row[0].startswith('#'): 629 event_tblname = file_name_to_table_name('pmu_events_', [], row[2].replace('/', '_')) 630 if event_tblname in _event_tables: 631 event_size = f'ARRAY_SIZE({event_tblname})' 632 else: 633 event_tblname = 'NULL' 634 event_size = '0' 635 metric_tblname = file_name_to_table_name('pmu_metrics_', [], row[2].replace('/', '_')) 636 if metric_tblname in _metric_tables: 637 metric_size = f'ARRAY_SIZE({metric_tblname})' 638 else: 639 metric_tblname = 'NULL' 640 metric_size = '0' 641 if event_size == '0' and metric_size == '0': 642 continue 643 cpuid = row[0].replace('\\', '\\\\') 644 _args.output_file.write(f"""{{ 645\t.arch = "{arch}", 646\t.cpuid = "{cpuid}", 647\t.event_table = {{ 648\t\t.entries = {event_tblname}, 649\t\t.length = {event_size} 650\t}}, 651\t.metric_table = {{ 652\t\t.entries = {metric_tblname}, 653\t\t.length = {metric_size} 654\t}} 655}}, 656""") 657 first = False 658 659 _args.output_file.write("""{ 660\t.arch = 0, 661\t.cpuid = 0, 662\t.event_table = { 0, 0 }, 663\t.metric_table = { 0, 0 }, 664} 665}; 666""") 667 668 669def print_system_mapping_table() -> None: 670 """C struct mapping table array for tables from /sys directories.""" 671 _args.output_file.write(""" 672struct pmu_sys_events { 673\tconst char *name; 674\tstruct pmu_events_table event_table; 675\tstruct pmu_metrics_table metric_table; 676}; 677 678static const struct pmu_sys_events pmu_sys_event_tables[] = { 679""") 680 printed_metric_tables = [] 681 for tblname in _sys_event_tables: 682 _args.output_file.write(f"""\t{{ 683\t\t.event_table = {{ 684\t\t\t.entries = {tblname}, 685\t\t\t.length = ARRAY_SIZE({tblname}) 686\t\t}},""") 687 metric_tblname = _sys_event_table_to_metric_table_mapping[tblname] 688 if metric_tblname in _sys_metric_tables: 689 _args.output_file.write(f""" 690\t\t.metric_table = {{ 691\t\t\t.entries = {metric_tblname}, 692\t\t\t.length = ARRAY_SIZE({metric_tblname}) 693\t\t}},""") 694 printed_metric_tables.append(metric_tblname) 695 _args.output_file.write(f""" 696\t\t.name = \"{tblname}\", 697\t}}, 698""") 699 for tblname in _sys_metric_tables: 700 if tblname in printed_metric_tables: 701 continue 702 _args.output_file.write(f"""\t{{ 703\t\t.metric_table = {{ 704\t\t\t.entries = {tblname}, 705\t\t\t.length = ARRAY_SIZE({tblname}) 706\t\t}}, 707\t\t.name = \"{tblname}\", 708\t}}, 709""") 710 _args.output_file.write("""\t{ 711\t\t.event_table = { 0, 0 }, 712\t\t.metric_table = { 0, 0 }, 713\t}, 714}; 715 716static void decompress_event(int offset, struct pmu_event *pe) 717{ 718\tconst char *p = &big_c_string[offset]; 719""") 720 for attr in _json_event_attributes: 721 _args.output_file.write(f'\n\tpe->{attr} = ') 722 if attr in _json_enum_attributes: 723 _args.output_file.write("*p - '0';\n") 724 else: 725 _args.output_file.write("(*p == '\\0' ? NULL : p);\n") 726 if attr == _json_event_attributes[-1]: 727 continue 728 if attr in _json_enum_attributes: 729 _args.output_file.write('\tp++;') 730 else: 731 _args.output_file.write('\twhile (*p++);') 732 _args.output_file.write("""} 733 734static void decompress_metric(int offset, struct pmu_metric *pm) 735{ 736\tconst char *p = &big_c_string[offset]; 737""") 738 for attr in _json_metric_attributes: 739 _args.output_file.write(f'\n\tpm->{attr} = ') 740 if attr in _json_enum_attributes: 741 _args.output_file.write("*p - '0';\n") 742 else: 743 _args.output_file.write("(*p == '\\0' ? NULL : p);\n") 744 if attr == _json_metric_attributes[-1]: 745 continue 746 if attr in _json_enum_attributes: 747 _args.output_file.write('\tp++;') 748 else: 749 _args.output_file.write('\twhile (*p++);') 750 _args.output_file.write("""} 751 752int pmu_events_table_for_each_event(const struct pmu_events_table *table, 753 pmu_event_iter_fn fn, 754 void *data) 755{ 756 for (size_t i = 0; i < table->length; i++) { 757 struct pmu_event pe; 758 int ret; 759 760 decompress_event(table->entries[i].offset, &pe); 761 if (!pe.name) 762 continue; 763 ret = fn(&pe, table, data); 764 if (ret) 765 return ret; 766 } 767 return 0; 768} 769 770int pmu_metrics_table_for_each_metric(const struct pmu_metrics_table *table, 771 pmu_metric_iter_fn fn, 772 void *data) 773{ 774 for (size_t i = 0; i < table->length; i++) { 775 struct pmu_metric pm; 776 int ret; 777 778 decompress_metric(table->entries[i].offset, &pm); 779 if (!pm.metric_expr) 780 continue; 781 ret = fn(&pm, table, data); 782 if (ret) 783 return ret; 784 } 785 return 0; 786} 787 788const struct pmu_events_table *perf_pmu__find_events_table(struct perf_pmu *pmu) 789{ 790 const struct pmu_events_table *table = NULL; 791 char *cpuid = perf_pmu__getcpuid(pmu); 792 int i; 793 794 /* on some platforms which uses cpus map, cpuid can be NULL for 795 * PMUs other than CORE PMUs. 796 */ 797 if (!cpuid) 798 return NULL; 799 800 i = 0; 801 for (;;) { 802 const struct pmu_events_map *map = &pmu_events_map[i++]; 803 if (!map->arch) 804 break; 805 806 if (!strcmp_cpuid_str(map->cpuid, cpuid)) { 807 table = &map->event_table; 808 break; 809 } 810 } 811 free(cpuid); 812 return table; 813} 814 815const struct pmu_metrics_table *perf_pmu__find_metrics_table(struct perf_pmu *pmu) 816{ 817 const struct pmu_metrics_table *table = NULL; 818 char *cpuid = perf_pmu__getcpuid(pmu); 819 int i; 820 821 /* on some platforms which uses cpus map, cpuid can be NULL for 822 * PMUs other than CORE PMUs. 823 */ 824 if (!cpuid) 825 return NULL; 826 827 i = 0; 828 for (;;) { 829 const struct pmu_events_map *map = &pmu_events_map[i++]; 830 if (!map->arch) 831 break; 832 833 if (!strcmp_cpuid_str(map->cpuid, cpuid)) { 834 table = &map->metric_table; 835 break; 836 } 837 } 838 free(cpuid); 839 return table; 840} 841 842const struct pmu_events_table *find_core_events_table(const char *arch, const char *cpuid) 843{ 844 for (const struct pmu_events_map *tables = &pmu_events_map[0]; 845 tables->arch; 846 tables++) { 847 if (!strcmp(tables->arch, arch) && !strcmp_cpuid_str(tables->cpuid, cpuid)) 848 return &tables->event_table; 849 } 850 return NULL; 851} 852 853const struct pmu_metrics_table *find_core_metrics_table(const char *arch, const char *cpuid) 854{ 855 for (const struct pmu_events_map *tables = &pmu_events_map[0]; 856 tables->arch; 857 tables++) { 858 if (!strcmp(tables->arch, arch) && !strcmp_cpuid_str(tables->cpuid, cpuid)) 859 return &tables->metric_table; 860 } 861 return NULL; 862} 863 864int pmu_for_each_core_event(pmu_event_iter_fn fn, void *data) 865{ 866 for (const struct pmu_events_map *tables = &pmu_events_map[0]; 867 tables->arch; 868 tables++) { 869 int ret = pmu_events_table_for_each_event(&tables->event_table, fn, data); 870 871 if (ret) 872 return ret; 873 } 874 return 0; 875} 876 877int pmu_for_each_core_metric(pmu_metric_iter_fn fn, void *data) 878{ 879 for (const struct pmu_events_map *tables = &pmu_events_map[0]; 880 tables->arch; 881 tables++) { 882 int ret = pmu_metrics_table_for_each_metric(&tables->metric_table, fn, data); 883 884 if (ret) 885 return ret; 886 } 887 return 0; 888} 889 890const struct pmu_events_table *find_sys_events_table(const char *name) 891{ 892 for (const struct pmu_sys_events *tables = &pmu_sys_event_tables[0]; 893 tables->name; 894 tables++) { 895 if (!strcmp(tables->name, name)) 896 return &tables->event_table; 897 } 898 return NULL; 899} 900 901int pmu_for_each_sys_event(pmu_event_iter_fn fn, void *data) 902{ 903 for (const struct pmu_sys_events *tables = &pmu_sys_event_tables[0]; 904 tables->name; 905 tables++) { 906 int ret = pmu_events_table_for_each_event(&tables->event_table, fn, data); 907 908 if (ret) 909 return ret; 910 } 911 return 0; 912} 913 914int pmu_for_each_sys_metric(pmu_metric_iter_fn fn, void *data) 915{ 916 for (const struct pmu_sys_events *tables = &pmu_sys_event_tables[0]; 917 tables->name; 918 tables++) { 919 int ret = pmu_metrics_table_for_each_metric(&tables->metric_table, fn, data); 920 921 if (ret) 922 return ret; 923 } 924 return 0; 925} 926""") 927 928def print_metricgroups() -> None: 929 _args.output_file.write(""" 930static const int metricgroups[][2] = { 931""") 932 for mgroup in sorted(_metricgroups): 933 description = _metricgroups[mgroup] 934 _args.output_file.write( 935 f'\t{{ {_bcs.offsets[mgroup]}, {_bcs.offsets[description]} }}, /* {mgroup} => {description} */\n' 936 ) 937 _args.output_file.write(""" 938}; 939 940const char *describe_metricgroup(const char *group) 941{ 942 int low = 0, high = (int)ARRAY_SIZE(metricgroups) - 1; 943 944 while (low <= high) { 945 int mid = (low + high) / 2; 946 const char *mgroup = &big_c_string[metricgroups[mid][0]]; 947 int cmp = strcmp(mgroup, group); 948 949 if (cmp == 0) { 950 return &big_c_string[metricgroups[mid][1]]; 951 } else if (cmp < 0) { 952 low = mid + 1; 953 } else { 954 high = mid - 1; 955 } 956 } 957 return NULL; 958} 959""") 960 961def main() -> None: 962 global _args 963 964 def dir_path(path: str) -> str: 965 """Validate path is a directory for argparse.""" 966 if os.path.isdir(path): 967 return path 968 raise argparse.ArgumentTypeError(f'\'{path}\' is not a valid directory') 969 970 def ftw(path: str, parents: Sequence[str], 971 action: Callable[[Sequence[str], os.DirEntry], None]) -> None: 972 """Replicate the directory/file walking behavior of C's file tree walk.""" 973 for item in sorted(os.scandir(path), key=lambda e: e.name): 974 if _args.model != 'all' and item.is_dir(): 975 # Check if the model matches one in _args.model. 976 if len(parents) == _args.model.split(',')[0].count('/'): 977 # We're testing the correct directory. 978 item_path = '/'.join(parents) + ('/' if len(parents) > 0 else '') + item.name 979 if 'test' not in item_path and item_path not in _args.model.split(','): 980 continue 981 action(parents, item) 982 if item.is_dir(): 983 ftw(item.path, parents + [item.name], action) 984 985 ap = argparse.ArgumentParser() 986 ap.add_argument('arch', help='Architecture name like x86') 987 ap.add_argument('model', help='''Select a model such as skylake to 988reduce the code size. Normally set to "all". For architectures like 989ARM64 with an implementor/model, the model must include the implementor 990such as "arm/cortex-a34".''', 991 default='all') 992 ap.add_argument( 993 'starting_dir', 994 type=dir_path, 995 help='Root of tree containing architecture directories containing json files' 996 ) 997 ap.add_argument( 998 'output_file', type=argparse.FileType('w', encoding='utf-8'), nargs='?', default=sys.stdout) 999 _args = ap.parse_args() 1000 1001 _args.output_file.write(""" 1002#include "pmu-events/pmu-events.h" 1003#include "util/header.h" 1004#include "util/pmu.h" 1005#include <string.h> 1006#include <stddef.h> 1007 1008struct compact_pmu_event { 1009 int offset; 1010}; 1011 1012""") 1013 archs = [] 1014 for item in os.scandir(_args.starting_dir): 1015 if not item.is_dir(): 1016 continue 1017 if item.name == _args.arch or _args.arch == 'all' or item.name == 'test': 1018 archs.append(item.name) 1019 1020 if len(archs) < 2: 1021 raise IOError(f'Missing architecture directory \'{_args.arch}\'') 1022 1023 archs.sort() 1024 for arch in archs: 1025 arch_path = f'{_args.starting_dir}/{arch}' 1026 preprocess_arch_std_files(arch_path) 1027 ftw(arch_path, [], preprocess_one_file) 1028 1029 _bcs.compute() 1030 _args.output_file.write('static const char *const big_c_string =\n') 1031 for s in _bcs.big_string: 1032 _args.output_file.write(s) 1033 _args.output_file.write(';\n\n') 1034 for arch in archs: 1035 arch_path = f'{_args.starting_dir}/{arch}' 1036 ftw(arch_path, [], process_one_file) 1037 print_pending_events() 1038 print_pending_metrics() 1039 1040 print_mapping_table(archs) 1041 print_system_mapping_table() 1042 print_metricgroups() 1043 1044if __name__ == '__main__': 1045 main() 1046