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