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', 'aggr_mode', 58 '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.event_grouping = convert_metric_constraint(jd.get('MetricConstraint')) 311 self.metric_expr = None 312 if 'MetricExpr' in jd: 313 self.metric_expr = metric.ParsePerfJson(jd['MetricExpr']).Simplify() 314 # Note, the metric formula for the threshold isn't parsed as the & 315 # and > have incorrect precedence. 316 self.metric_threshold = jd.get('MetricThreshold') 317 318 arch_std = jd.get('ArchStdEvent') 319 if precise and self.desc and '(Precise Event)' not in self.desc: 320 extra_desc += ' (Must be precise)' if precise == '2' else (' (Precise ' 321 'event)') 322 event = f'config={llx(configcode)}' if configcode is not None else f'event={llx(eventcode)}' 323 event_fields = [ 324 ('AnyThread', 'any='), 325 ('PortMask', 'ch_mask='), 326 ('CounterMask', 'cmask='), 327 ('EdgeDetect', 'edge='), 328 ('FCMask', 'fc_mask='), 329 ('Invert', 'inv='), 330 ('SampleAfterValue', 'period='), 331 ('UMask', 'umask='), 332 ] 333 for key, value in event_fields: 334 if key in jd and jd[key] != '0': 335 event += ',' + value + jd[key] 336 if filter: 337 event += f',{filter}' 338 if msr: 339 event += f',{msr}{msrval}' 340 if self.desc and extra_desc: 341 self.desc += extra_desc 342 if self.long_desc and extra_desc: 343 self.long_desc += extra_desc 344 if self.pmu: 345 if self.desc and not self.desc.endswith('. '): 346 self.desc += '. ' 347 self.desc = (self.desc if self.desc else '') + ('Unit: ' + self.pmu + ' ') 348 if arch_std and arch_std.lower() in _arch_std_events: 349 event = _arch_std_events[arch_std.lower()].event 350 # Copy from the architecture standard event to self for undefined fields. 351 for attr, value in _arch_std_events[arch_std.lower()].__dict__.items(): 352 if hasattr(self, attr) and not getattr(self, attr): 353 setattr(self, attr, value) 354 355 self.event = real_event(self.name, event) 356 357 def __repr__(self) -> str: 358 """String representation primarily for debugging.""" 359 s = '{\n' 360 for attr, value in self.__dict__.items(): 361 if value: 362 s += f'\t{attr} = {value},\n' 363 return s + '}' 364 365 def build_c_string(self, metric: bool) -> str: 366 s = '' 367 for attr in _json_metric_attributes if metric else _json_event_attributes: 368 x = getattr(self, attr) 369 if metric and x and attr == 'metric_expr': 370 # Convert parsed metric expressions into a string. Slashes 371 # must be doubled in the file. 372 x = x.ToPerfJson().replace('\\', '\\\\') 373 if metric and x and attr == 'metric_threshold': 374 x = x.replace('\\', '\\\\') 375 if attr in _json_enum_attributes: 376 s += x if x else '0' 377 else: 378 s += f'{x}\\000' if x else '\\000' 379 return s 380 381 def to_c_string(self, metric: bool) -> str: 382 """Representation of the event as a C struct initializer.""" 383 384 s = self.build_c_string(metric) 385 return f'{{ { _bcs.offsets[s] } }}, /* {s} */\n' 386 387 388@lru_cache(maxsize=None) 389def read_json_events(path: str, topic: str) -> Sequence[JsonEvent]: 390 """Read json events from the specified file.""" 391 try: 392 events = json.load(open(path), object_hook=JsonEvent) 393 except BaseException as err: 394 print(f"Exception processing {path}") 395 raise 396 metrics: list[Tuple[str, str, metric.Expression]] = [] 397 for event in events: 398 event.topic = topic 399 if event.metric_name and '-' not in event.metric_name: 400 metrics.append((event.pmu, event.metric_name, event.metric_expr)) 401 updates = metric.RewriteMetricsInTermsOfOthers(metrics) 402 if updates: 403 for event in events: 404 if event.metric_name in updates: 405 # print(f'Updated {event.metric_name} from\n"{event.metric_expr}"\n' 406 # f'to\n"{updates[event.metric_name]}"') 407 event.metric_expr = updates[event.metric_name] 408 409 return events 410 411def preprocess_arch_std_files(archpath: str) -> None: 412 """Read in all architecture standard events.""" 413 global _arch_std_events 414 for item in os.scandir(archpath): 415 if item.is_file() and item.name.endswith('.json'): 416 for event in read_json_events(item.path, topic=''): 417 if event.name: 418 _arch_std_events[event.name.lower()] = event 419 if event.metric_name: 420 _arch_std_events[event.metric_name.lower()] = event 421 422 423def add_events_table_entries(item: os.DirEntry, topic: str) -> None: 424 """Add contents of file to _pending_events table.""" 425 for e in read_json_events(item.path, topic): 426 if e.name: 427 _pending_events.append(e) 428 if e.metric_name: 429 _pending_metrics.append(e) 430 431 432def print_pending_events() -> None: 433 """Optionally close events table.""" 434 435 def event_cmp_key(j: JsonEvent) -> Tuple[bool, str, str, str, str]: 436 def fix_none(s: Optional[str]) -> str: 437 if s is None: 438 return '' 439 return s 440 441 return (j.desc is not None, fix_none(j.topic), fix_none(j.name), fix_none(j.pmu), 442 fix_none(j.metric_name)) 443 444 global _pending_events 445 if not _pending_events: 446 return 447 448 global _pending_events_tblname 449 if _pending_events_tblname.endswith('_sys'): 450 global _sys_event_tables 451 _sys_event_tables.append(_pending_events_tblname) 452 else: 453 global event_tables 454 _event_tables.append(_pending_events_tblname) 455 456 _args.output_file.write( 457 f'static const struct compact_pmu_event {_pending_events_tblname}[] = {{\n') 458 459 for event in sorted(_pending_events, key=event_cmp_key): 460 _args.output_file.write(event.to_c_string(metric=False)) 461 _pending_events = [] 462 463 _args.output_file.write('};\n\n') 464 465def print_pending_metrics() -> None: 466 """Optionally close metrics table.""" 467 468 def metric_cmp_key(j: JsonEvent) -> Tuple[bool, str, str]: 469 def fix_none(s: Optional[str]) -> str: 470 if s is None: 471 return '' 472 return s 473 474 return (j.desc is not None, fix_none(j.pmu), fix_none(j.metric_name)) 475 476 global _pending_metrics 477 if not _pending_metrics: 478 return 479 480 global _pending_metrics_tblname 481 if _pending_metrics_tblname.endswith('_sys'): 482 global _sys_metric_tables 483 _sys_metric_tables.append(_pending_metrics_tblname) 484 else: 485 global metric_tables 486 _metric_tables.append(_pending_metrics_tblname) 487 488 _args.output_file.write( 489 f'static const struct compact_pmu_event {_pending_metrics_tblname}[] = {{\n') 490 491 for metric in sorted(_pending_metrics, key=metric_cmp_key): 492 _args.output_file.write(metric.to_c_string(metric=True)) 493 _pending_metrics = [] 494 495 _args.output_file.write('};\n\n') 496 497def get_topic(topic: str) -> str: 498 if topic.endswith('metrics.json'): 499 return 'metrics' 500 return removesuffix(topic, '.json').replace('-', ' ') 501 502def preprocess_one_file(parents: Sequence[str], item: os.DirEntry) -> None: 503 504 if item.is_dir(): 505 return 506 507 # base dir or too deep 508 level = len(parents) 509 if level == 0 or level > 4: 510 return 511 512 # Ignore other directories. If the file name does not have a .json 513 # extension, ignore it. It could be a readme.txt for instance. 514 if not item.is_file() or not item.name.endswith('.json'): 515 return 516 517 if item.name == 'metricgroups.json': 518 metricgroup_descriptions = json.load(open(item.path)) 519 for mgroup in metricgroup_descriptions: 520 assert len(mgroup) > 1, parents 521 description = f"{metricgroup_descriptions[mgroup]}\\000" 522 mgroup = f"{mgroup}\\000" 523 _bcs.add(mgroup) 524 _bcs.add(description) 525 _metricgroups[mgroup] = description 526 return 527 528 topic = get_topic(item.name) 529 for event in read_json_events(item.path, topic): 530 if event.name: 531 _bcs.add(event.build_c_string(metric=False)) 532 if event.metric_name: 533 _bcs.add(event.build_c_string(metric=True)) 534 535def process_one_file(parents: Sequence[str], item: os.DirEntry) -> None: 536 """Process a JSON file during the main walk.""" 537 def is_leaf_dir(path: str) -> bool: 538 for item in os.scandir(path): 539 if item.is_dir(): 540 return False 541 return True 542 543 # model directory, reset topic 544 if item.is_dir() and is_leaf_dir(item.path): 545 print_pending_events() 546 print_pending_metrics() 547 548 global _pending_events_tblname 549 _pending_events_tblname = file_name_to_table_name('pmu_events_', parents, item.name) 550 global _pending_metrics_tblname 551 _pending_metrics_tblname = file_name_to_table_name('pmu_metrics_', parents, item.name) 552 553 if item.name == 'sys': 554 _sys_event_table_to_metric_table_mapping[_pending_events_tblname] = _pending_metrics_tblname 555 return 556 557 # base dir or too deep 558 level = len(parents) 559 if level == 0 or level > 4: 560 return 561 562 # Ignore other directories. If the file name does not have a .json 563 # extension, ignore it. It could be a readme.txt for instance. 564 if not item.is_file() or not item.name.endswith('.json') or item.name == 'metricgroups.json': 565 return 566 567 add_events_table_entries(item, get_topic(item.name)) 568 569 570def print_mapping_table(archs: Sequence[str]) -> None: 571 """Read the mapfile and generate the struct from cpuid string to event table.""" 572 _args.output_file.write(""" 573/* Struct used to make the PMU event table implementation opaque to callers. */ 574struct pmu_events_table { 575 const struct compact_pmu_event *entries; 576 size_t length; 577}; 578 579/* Struct used to make the PMU metric table implementation opaque to callers. */ 580struct pmu_metrics_table { 581 const struct compact_pmu_event *entries; 582 size_t length; 583}; 584 585/* 586 * Map a CPU to its table of PMU events. The CPU is identified by the 587 * cpuid field, which is an arch-specific identifier for the CPU. 588 * The identifier specified in tools/perf/pmu-events/arch/xxx/mapfile 589 * must match the get_cpuid_str() in tools/perf/arch/xxx/util/header.c) 590 * 591 * The cpuid can contain any character other than the comma. 592 */ 593struct pmu_events_map { 594 const char *arch; 595 const char *cpuid; 596 struct pmu_events_table event_table; 597 struct pmu_metrics_table metric_table; 598}; 599 600/* 601 * Global table mapping each known CPU for the architecture to its 602 * table of PMU events. 603 */ 604const struct pmu_events_map pmu_events_map[] = { 605""") 606 for arch in archs: 607 if arch == 'test': 608 _args.output_file.write("""{ 609\t.arch = "testarch", 610\t.cpuid = "testcpu", 611\t.event_table = { 612\t\t.entries = pmu_events__test_soc_cpu, 613\t\t.length = ARRAY_SIZE(pmu_events__test_soc_cpu), 614\t}, 615\t.metric_table = { 616\t\t.entries = pmu_metrics__test_soc_cpu, 617\t\t.length = ARRAY_SIZE(pmu_metrics__test_soc_cpu), 618\t} 619}, 620""") 621 else: 622 with open(f'{_args.starting_dir}/{arch}/mapfile.csv') as csvfile: 623 table = csv.reader(csvfile) 624 first = True 625 for row in table: 626 # Skip the first row or any row beginning with #. 627 if not first and len(row) > 0 and not row[0].startswith('#'): 628 event_tblname = file_name_to_table_name('pmu_events_', [], row[2].replace('/', '_')) 629 if event_tblname in _event_tables: 630 event_size = f'ARRAY_SIZE({event_tblname})' 631 else: 632 event_tblname = 'NULL' 633 event_size = '0' 634 metric_tblname = file_name_to_table_name('pmu_metrics_', [], row[2].replace('/', '_')) 635 if metric_tblname in _metric_tables: 636 metric_size = f'ARRAY_SIZE({metric_tblname})' 637 else: 638 metric_tblname = 'NULL' 639 metric_size = '0' 640 if event_size == '0' and metric_size == '0': 641 continue 642 cpuid = row[0].replace('\\', '\\\\') 643 _args.output_file.write(f"""{{ 644\t.arch = "{arch}", 645\t.cpuid = "{cpuid}", 646\t.event_table = {{ 647\t\t.entries = {event_tblname}, 648\t\t.length = {event_size} 649\t}}, 650\t.metric_table = {{ 651\t\t.entries = {metric_tblname}, 652\t\t.length = {metric_size} 653\t}} 654}}, 655""") 656 first = False 657 658 _args.output_file.write("""{ 659\t.arch = 0, 660\t.cpuid = 0, 661\t.event_table = { 0, 0 }, 662\t.metric_table = { 0, 0 }, 663} 664}; 665""") 666 667 668def print_system_mapping_table() -> None: 669 """C struct mapping table array for tables from /sys directories.""" 670 _args.output_file.write(""" 671struct pmu_sys_events { 672\tconst char *name; 673\tstruct pmu_events_table event_table; 674\tstruct pmu_metrics_table metric_table; 675}; 676 677static const struct pmu_sys_events pmu_sys_event_tables[] = { 678""") 679 printed_metric_tables = [] 680 for tblname in _sys_event_tables: 681 _args.output_file.write(f"""\t{{ 682\t\t.event_table = {{ 683\t\t\t.entries = {tblname}, 684\t\t\t.length = ARRAY_SIZE({tblname}) 685\t\t}},""") 686 metric_tblname = _sys_event_table_to_metric_table_mapping[tblname] 687 if metric_tblname in _sys_metric_tables: 688 _args.output_file.write(f""" 689\t\t.metric_table = {{ 690\t\t\t.entries = {metric_tblname}, 691\t\t\t.length = ARRAY_SIZE({metric_tblname}) 692\t\t}},""") 693 printed_metric_tables.append(metric_tblname) 694 _args.output_file.write(f""" 695\t\t.name = \"{tblname}\", 696\t}}, 697""") 698 for tblname in _sys_metric_tables: 699 if tblname in printed_metric_tables: 700 continue 701 _args.output_file.write(f"""\t{{ 702\t\t.metric_table = {{ 703\t\t\t.entries = {tblname}, 704\t\t\t.length = ARRAY_SIZE({tblname}) 705\t\t}}, 706\t\t.name = \"{tblname}\", 707\t}}, 708""") 709 _args.output_file.write("""\t{ 710\t\t.event_table = { 0, 0 }, 711\t\t.metric_table = { 0, 0 }, 712\t}, 713}; 714 715static void decompress_event(int offset, struct pmu_event *pe) 716{ 717\tconst char *p = &big_c_string[offset]; 718""") 719 for attr in _json_event_attributes: 720 _args.output_file.write(f'\n\tpe->{attr} = ') 721 if attr in _json_enum_attributes: 722 _args.output_file.write("*p - '0';\n") 723 else: 724 _args.output_file.write("(*p == '\\0' ? NULL : p);\n") 725 if attr == _json_event_attributes[-1]: 726 continue 727 if attr in _json_enum_attributes: 728 _args.output_file.write('\tp++;') 729 else: 730 _args.output_file.write('\twhile (*p++);') 731 _args.output_file.write("""} 732 733static void decompress_metric(int offset, struct pmu_metric *pm) 734{ 735\tconst char *p = &big_c_string[offset]; 736""") 737 for attr in _json_metric_attributes: 738 _args.output_file.write(f'\n\tpm->{attr} = ') 739 if attr in _json_enum_attributes: 740 _args.output_file.write("*p - '0';\n") 741 else: 742 _args.output_file.write("(*p == '\\0' ? NULL : p);\n") 743 if attr == _json_metric_attributes[-1]: 744 continue 745 if attr in _json_enum_attributes: 746 _args.output_file.write('\tp++;') 747 else: 748 _args.output_file.write('\twhile (*p++);') 749 _args.output_file.write("""} 750 751int pmu_events_table_for_each_event(const struct pmu_events_table *table, 752 pmu_event_iter_fn fn, 753 void *data) 754{ 755 for (size_t i = 0; i < table->length; i++) { 756 struct pmu_event pe; 757 int ret; 758 759 decompress_event(table->entries[i].offset, &pe); 760 if (!pe.name) 761 continue; 762 ret = fn(&pe, table, data); 763 if (ret) 764 return ret; 765 } 766 return 0; 767} 768 769int pmu_metrics_table_for_each_metric(const struct pmu_metrics_table *table, 770 pmu_metric_iter_fn fn, 771 void *data) 772{ 773 for (size_t i = 0; i < table->length; i++) { 774 struct pmu_metric pm; 775 int ret; 776 777 decompress_metric(table->entries[i].offset, &pm); 778 if (!pm.metric_expr) 779 continue; 780 ret = fn(&pm, table, data); 781 if (ret) 782 return ret; 783 } 784 return 0; 785} 786 787const struct pmu_events_table *perf_pmu__find_events_table(struct perf_pmu *pmu) 788{ 789 const struct pmu_events_table *table = NULL; 790 char *cpuid = perf_pmu__getcpuid(pmu); 791 int i; 792 793 /* on some platforms which uses cpus map, cpuid can be NULL for 794 * PMUs other than CORE PMUs. 795 */ 796 if (!cpuid) 797 return NULL; 798 799 i = 0; 800 for (;;) { 801 const struct pmu_events_map *map = &pmu_events_map[i++]; 802 if (!map->arch) 803 break; 804 805 if (!strcmp_cpuid_str(map->cpuid, cpuid)) { 806 table = &map->event_table; 807 break; 808 } 809 } 810 free(cpuid); 811 return table; 812} 813 814const struct pmu_metrics_table *perf_pmu__find_metrics_table(struct perf_pmu *pmu) 815{ 816 const struct pmu_metrics_table *table = NULL; 817 char *cpuid = perf_pmu__getcpuid(pmu); 818 int i; 819 820 /* on some platforms which uses cpus map, cpuid can be NULL for 821 * PMUs other than CORE PMUs. 822 */ 823 if (!cpuid) 824 return NULL; 825 826 i = 0; 827 for (;;) { 828 const struct pmu_events_map *map = &pmu_events_map[i++]; 829 if (!map->arch) 830 break; 831 832 if (!strcmp_cpuid_str(map->cpuid, cpuid)) { 833 table = &map->metric_table; 834 break; 835 } 836 } 837 free(cpuid); 838 return table; 839} 840 841const struct pmu_events_table *find_core_events_table(const char *arch, const char *cpuid) 842{ 843 for (const struct pmu_events_map *tables = &pmu_events_map[0]; 844 tables->arch; 845 tables++) { 846 if (!strcmp(tables->arch, arch) && !strcmp_cpuid_str(tables->cpuid, cpuid)) 847 return &tables->event_table; 848 } 849 return NULL; 850} 851 852const struct pmu_metrics_table *find_core_metrics_table(const char *arch, const char *cpuid) 853{ 854 for (const struct pmu_events_map *tables = &pmu_events_map[0]; 855 tables->arch; 856 tables++) { 857 if (!strcmp(tables->arch, arch) && !strcmp_cpuid_str(tables->cpuid, cpuid)) 858 return &tables->metric_table; 859 } 860 return NULL; 861} 862 863int pmu_for_each_core_event(pmu_event_iter_fn fn, void *data) 864{ 865 for (const struct pmu_events_map *tables = &pmu_events_map[0]; 866 tables->arch; 867 tables++) { 868 int ret = pmu_events_table_for_each_event(&tables->event_table, fn, data); 869 870 if (ret) 871 return ret; 872 } 873 return 0; 874} 875 876int pmu_for_each_core_metric(pmu_metric_iter_fn fn, void *data) 877{ 878 for (const struct pmu_events_map *tables = &pmu_events_map[0]; 879 tables->arch; 880 tables++) { 881 int ret = pmu_metrics_table_for_each_metric(&tables->metric_table, fn, data); 882 883 if (ret) 884 return ret; 885 } 886 return 0; 887} 888 889const struct pmu_events_table *find_sys_events_table(const char *name) 890{ 891 for (const struct pmu_sys_events *tables = &pmu_sys_event_tables[0]; 892 tables->name; 893 tables++) { 894 if (!strcmp(tables->name, name)) 895 return &tables->event_table; 896 } 897 return NULL; 898} 899 900int pmu_for_each_sys_event(pmu_event_iter_fn fn, void *data) 901{ 902 for (const struct pmu_sys_events *tables = &pmu_sys_event_tables[0]; 903 tables->name; 904 tables++) { 905 int ret = pmu_events_table_for_each_event(&tables->event_table, fn, data); 906 907 if (ret) 908 return ret; 909 } 910 return 0; 911} 912 913int pmu_for_each_sys_metric(pmu_metric_iter_fn fn, void *data) 914{ 915 for (const struct pmu_sys_events *tables = &pmu_sys_event_tables[0]; 916 tables->name; 917 tables++) { 918 int ret = pmu_metrics_table_for_each_metric(&tables->metric_table, fn, data); 919 920 if (ret) 921 return ret; 922 } 923 return 0; 924} 925""") 926 927def print_metricgroups() -> None: 928 _args.output_file.write(""" 929static const int metricgroups[][2] = { 930""") 931 for mgroup in sorted(_metricgroups): 932 description = _metricgroups[mgroup] 933 _args.output_file.write( 934 f'\t{{ {_bcs.offsets[mgroup]}, {_bcs.offsets[description]} }}, /* {mgroup} => {description} */\n' 935 ) 936 _args.output_file.write(""" 937}; 938 939const char *describe_metricgroup(const char *group) 940{ 941 int low = 0, high = (int)ARRAY_SIZE(metricgroups) - 1; 942 943 while (low <= high) { 944 int mid = (low + high) / 2; 945 const char *mgroup = &big_c_string[metricgroups[mid][0]]; 946 int cmp = strcmp(mgroup, group); 947 948 if (cmp == 0) { 949 return &big_c_string[metricgroups[mid][1]]; 950 } else if (cmp < 0) { 951 low = mid + 1; 952 } else { 953 high = mid - 1; 954 } 955 } 956 return NULL; 957} 958""") 959 960def main() -> None: 961 global _args 962 963 def dir_path(path: str) -> str: 964 """Validate path is a directory for argparse.""" 965 if os.path.isdir(path): 966 return path 967 raise argparse.ArgumentTypeError(f'\'{path}\' is not a valid directory') 968 969 def ftw(path: str, parents: Sequence[str], 970 action: Callable[[Sequence[str], os.DirEntry], None]) -> None: 971 """Replicate the directory/file walking behavior of C's file tree walk.""" 972 for item in sorted(os.scandir(path), key=lambda e: e.name): 973 if _args.model != 'all' and item.is_dir(): 974 # Check if the model matches one in _args.model. 975 if len(parents) == _args.model.split(',')[0].count('/'): 976 # We're testing the correct directory. 977 item_path = '/'.join(parents) + ('/' if len(parents) > 0 else '') + item.name 978 if 'test' not in item_path and item_path not in _args.model.split(','): 979 continue 980 action(parents, item) 981 if item.is_dir(): 982 ftw(item.path, parents + [item.name], action) 983 984 ap = argparse.ArgumentParser() 985 ap.add_argument('arch', help='Architecture name like x86') 986 ap.add_argument('model', help='''Select a model such as skylake to 987reduce the code size. Normally set to "all". For architectures like 988ARM64 with an implementor/model, the model must include the implementor 989such as "arm/cortex-a34".''', 990 default='all') 991 ap.add_argument( 992 'starting_dir', 993 type=dir_path, 994 help='Root of tree containing architecture directories containing json files' 995 ) 996 ap.add_argument( 997 'output_file', type=argparse.FileType('w', encoding='utf-8'), nargs='?', default=sys.stdout) 998 _args = ap.parse_args() 999 1000 _args.output_file.write(""" 1001#include "pmu-events/pmu-events.h" 1002#include "util/header.h" 1003#include "util/pmu.h" 1004#include <string.h> 1005#include <stddef.h> 1006 1007struct compact_pmu_event { 1008 int offset; 1009}; 1010 1011""") 1012 archs = [] 1013 for item in os.scandir(_args.starting_dir): 1014 if not item.is_dir(): 1015 continue 1016 if item.name == _args.arch or _args.arch == 'all' or item.name == 'test': 1017 archs.append(item.name) 1018 1019 if len(archs) < 2: 1020 raise IOError(f'Missing architecture directory \'{_args.arch}\'') 1021 1022 archs.sort() 1023 for arch in archs: 1024 arch_path = f'{_args.starting_dir}/{arch}' 1025 preprocess_arch_std_files(arch_path) 1026 ftw(arch_path, [], preprocess_one_file) 1027 1028 _bcs.compute() 1029 _args.output_file.write('static const char *const big_c_string =\n') 1030 for s in _bcs.big_string: 1031 _args.output_file.write(s) 1032 _args.output_file.write(';\n\n') 1033 for arch in archs: 1034 arch_path = f'{_args.starting_dir}/{arch}' 1035 ftw(arch_path, [], process_one_file) 1036 print_pending_events() 1037 print_pending_metrics() 1038 1039 print_mapping_table(archs) 1040 print_system_mapping_table() 1041 print_metricgroups() 1042 1043if __name__ == '__main__': 1044 main() 1045