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