| #!/usr/bin/env python3 |
| # SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) |
| """Convert directories of JSON events to C code.""" |
| import argparse |
| import csv |
| from functools import lru_cache |
| import json |
| import metric |
| import os |
| import sys |
| from typing import (Callable, Dict, Optional, Sequence, Set, Tuple) |
| import collections |
| |
| # Global command line arguments. |
| _args = None |
| # List of regular event tables. |
| _event_tables = [] |
| # List of event tables generated from "/sys" directories. |
| _sys_event_tables = [] |
| # List of regular metric tables. |
| _metric_tables = [] |
| # List of metric tables generated from "/sys" directories. |
| _sys_metric_tables = [] |
| # Mapping between sys event table names and sys metric table names. |
| _sys_event_table_to_metric_table_mapping = {} |
| # Map from an event name to an architecture standard |
| # JsonEvent. Architecture standard events are in json files in the top |
| # f'{_args.starting_dir}/{_args.arch}' directory. |
| _arch_std_events = {} |
| # Events to write out when the table is closed |
| _pending_events = [] |
| # Name of events table to be written out |
| _pending_events_tblname = None |
| # Metrics to write out when the table is closed |
| _pending_metrics = [] |
| # Name of metrics table to be written out |
| _pending_metrics_tblname = None |
| # Global BigCString shared by all structures. |
| _bcs = None |
| # Map from the name of a metric group to a description of the group. |
| _metricgroups = {} |
| # Order specific JsonEvent attributes will be visited. |
| _json_event_attributes = [ |
| # cmp_sevent related attributes. |
| 'name', 'topic', 'desc', |
| # Seems useful, put it early. |
| 'event', |
| # Short things in alphabetical order. |
| 'compat', 'deprecated', 'perpkg', 'unit', |
| # Longer things (the last won't be iterated over during decompress). |
| 'long_desc' |
| ] |
| |
| # Attributes that are in pmu_metric rather than pmu_event. |
| _json_metric_attributes = [ |
| 'metric_name', 'metric_group', 'metric_expr', 'metric_threshold', |
| 'desc', 'long_desc', 'unit', 'compat', 'metricgroup_no_group', |
| 'default_metricgroup_name', 'aggr_mode', 'event_grouping' |
| ] |
| # Attributes that are bools or enum int values, encoded as '0', '1',... |
| _json_enum_attributes = ['aggr_mode', 'deprecated', 'event_grouping', 'perpkg'] |
| |
| def removesuffix(s: str, suffix: str) -> str: |
| """Remove the suffix from a string |
| |
| The removesuffix function is added to str in Python 3.9. We aim for 3.6 |
| compatibility and so provide our own function here. |
| """ |
| return s[0:-len(suffix)] if s.endswith(suffix) else s |
| |
| |
| def file_name_to_table_name(prefix: str, parents: Sequence[str], |
| dirname: str) -> str: |
| """Generate a C table name from directory names.""" |
| tblname = prefix |
| for p in parents: |
| tblname += '_' + p |
| tblname += '_' + dirname |
| return tblname.replace('-', '_') |
| |
| |
| def c_len(s: str) -> int: |
| """Return the length of s a C string |
| |
| This doesn't handle all escape characters properly. It first assumes |
| all \\ are for escaping, it then adjusts as it will have over counted |
| \\. The code uses \000 rather than \0 as a terminator as an adjacent |
| number would be folded into a string of \0 (ie. "\0" + "5" doesn't |
| equal a terminator followed by the number 5 but the escape of |
| \05). The code adjusts for \000 but not properly for all octal, hex |
| or unicode values. |
| """ |
| try: |
| utf = s.encode(encoding='utf-8',errors='strict') |
| except: |
| print(f'broken string {s}') |
| raise |
| return len(utf) - utf.count(b'\\') + utf.count(b'\\\\') - (utf.count(b'\\000') * 2) |
| |
| class BigCString: |
| """A class to hold many strings concatenated together. |
| |
| Generating a large number of stand-alone C strings creates a large |
| number of relocations in position independent code. The BigCString |
| is a helper for this case. It builds a single string which within it |
| are all the other C strings (to avoid memory issues the string |
| itself is held as a list of strings). The offsets within the big |
| string are recorded and when stored to disk these don't need |
| relocation. To reduce the size of the string further, identical |
| strings are merged. If a longer string ends-with the same value as a |
| shorter string, these entries are also merged. |
| """ |
| strings: Set[str] |
| big_string: Sequence[str] |
| offsets: Dict[str, int] |
| insert_number: int |
| insert_point: Dict[str, int] |
| metrics: Set[str] |
| |
| def __init__(self): |
| self.strings = set() |
| self.insert_number = 0; |
| self.insert_point = {} |
| self.metrics = set() |
| |
| def add(self, s: str, metric: bool) -> None: |
| """Called to add to the big string.""" |
| if s not in self.strings: |
| self.strings.add(s) |
| self.insert_point[s] = self.insert_number |
| self.insert_number += 1 |
| if metric: |
| self.metrics.add(s) |
| |
| def compute(self) -> None: |
| """Called once all strings are added to compute the string and offsets.""" |
| |
| folded_strings = {} |
| # Determine if two strings can be folded, ie. let 1 string use the |
| # end of another. First reverse all strings and sort them. |
| sorted_reversed_strings = sorted([x[::-1] for x in self.strings]) |
| |
| # Strings 'xyz' and 'yz' will now be [ 'zy', 'zyx' ]. Scan forward |
| # for each string to see if there is a better candidate to fold it |
| # into, in the example rather than using 'yz' we can use'xyz' at |
| # an offset of 1. We record which string can be folded into which |
| # in folded_strings, we don't need to record the offset as it is |
| # trivially computed from the string lengths. |
| for pos,s in enumerate(sorted_reversed_strings): |
| best_pos = pos |
| for check_pos in range(pos + 1, len(sorted_reversed_strings)): |
| if sorted_reversed_strings[check_pos].startswith(s): |
| best_pos = check_pos |
| else: |
| break |
| if pos != best_pos: |
| folded_strings[s[::-1]] = sorted_reversed_strings[best_pos][::-1] |
| |
| # Compute reverse mappings for debugging. |
| fold_into_strings = collections.defaultdict(set) |
| for key, val in folded_strings.items(): |
| if key != val: |
| fold_into_strings[val].add(key) |
| |
| # big_string_offset is the current location within the C string |
| # being appended to - comments, etc. don't count. big_string is |
| # the string contents represented as a list. Strings are immutable |
| # in Python and so appending to one causes memory issues, while |
| # lists are mutable. |
| big_string_offset = 0 |
| self.big_string = [] |
| self.offsets = {} |
| |
| def string_cmp_key(s: str) -> Tuple[bool, int, str]: |
| return (s in self.metrics, self.insert_point[s], s) |
| |
| # Emit all strings that aren't folded in a sorted manner. |
| for s in sorted(self.strings, key=string_cmp_key): |
| if s not in folded_strings: |
| self.offsets[s] = big_string_offset |
| self.big_string.append(f'/* offset={big_string_offset} */ "') |
| self.big_string.append(s) |
| self.big_string.append('"') |
| if s in fold_into_strings: |
| self.big_string.append(' /* also: ' + ', '.join(fold_into_strings[s]) + ' */') |
| self.big_string.append('\n') |
| big_string_offset += c_len(s) |
| continue |
| |
| # Compute the offsets of the folded strings. |
| for s in folded_strings.keys(): |
| assert s not in self.offsets |
| folded_s = folded_strings[s] |
| self.offsets[s] = self.offsets[folded_s] + c_len(folded_s) - c_len(s) |
| |
| _bcs = BigCString() |
| |
| class JsonEvent: |
| """Representation of an event loaded from a json file dictionary.""" |
| |
| def __init__(self, jd: dict): |
| """Constructor passed the dictionary of parsed json values.""" |
| |
| def llx(x: int) -> str: |
| """Convert an int to a string similar to a printf modifier of %#llx.""" |
| return str(x) if x >= 0 and x < 10 else hex(x) |
| |
| def fixdesc(s: str) -> str: |
| """Fix formatting issue for the desc string.""" |
| if s is None: |
| return None |
| return removesuffix(removesuffix(removesuffix(s, '. '), |
| '. '), '.').replace('\n', '\\n').replace( |
| '\"', '\\"').replace('\r', '\\r') |
| |
| def convert_aggr_mode(aggr_mode: str) -> Optional[str]: |
| """Returns the aggr_mode_class enum value associated with the JSON string.""" |
| if not aggr_mode: |
| return None |
| aggr_mode_to_enum = { |
| 'PerChip': '1', |
| 'PerCore': '2', |
| } |
| return aggr_mode_to_enum[aggr_mode] |
| |
| def convert_metric_constraint(metric_constraint: str) -> Optional[str]: |
| """Returns the metric_event_groups enum value associated with the JSON string.""" |
| if not metric_constraint: |
| return None |
| metric_constraint_to_enum = { |
| 'NO_GROUP_EVENTS': '1', |
| 'NO_GROUP_EVENTS_NMI': '2', |
| 'NO_NMI_WATCHDOG': '2', |
| 'NO_GROUP_EVENTS_SMT': '3', |
| } |
| return metric_constraint_to_enum[metric_constraint] |
| |
| def lookup_msr(num: str) -> Optional[str]: |
| """Converts the msr number, or first in a list to the appropriate event field.""" |
| if not num: |
| return None |
| msrmap = { |
| 0x3F6: 'ldlat=', |
| 0x1A6: 'offcore_rsp=', |
| 0x1A7: 'offcore_rsp=', |
| 0x3F7: 'frontend=', |
| } |
| return msrmap[int(num.split(',', 1)[0], 0)] |
| |
| def real_event(name: str, event: str) -> Optional[str]: |
| """Convert well known event names to an event string otherwise use the event argument.""" |
| fixed = { |
| 'inst_retired.any': 'event=0xc0,period=2000003', |
| 'inst_retired.any_p': 'event=0xc0,period=2000003', |
| 'cpu_clk_unhalted.ref': 'event=0x0,umask=0x03,period=2000003', |
| 'cpu_clk_unhalted.thread': 'event=0x3c,period=2000003', |
| 'cpu_clk_unhalted.core': 'event=0x3c,period=2000003', |
| 'cpu_clk_unhalted.thread_any': 'event=0x3c,any=1,period=2000003', |
| } |
| if not name: |
| return None |
| if name.lower() in fixed: |
| return fixed[name.lower()] |
| return event |
| |
| def unit_to_pmu(unit: str) -> Optional[str]: |
| """Convert a JSON Unit to Linux PMU name.""" |
| if not unit: |
| return 'default_core' |
| # Comment brought over from jevents.c: |
| # it's not realistic to keep adding these, we need something more scalable ... |
| table = { |
| 'CBO': 'uncore_cbox', |
| 'QPI LL': 'uncore_qpi', |
| 'SBO': 'uncore_sbox', |
| 'iMPH-U': 'uncore_arb', |
| 'CPU-M-CF': 'cpum_cf', |
| 'CPU-M-SF': 'cpum_sf', |
| 'PAI-CRYPTO' : 'pai_crypto', |
| 'PAI-EXT' : 'pai_ext', |
| 'UPI LL': 'uncore_upi', |
| 'hisi_sicl,cpa': 'hisi_sicl,cpa', |
| 'hisi_sccl,ddrc': 'hisi_sccl,ddrc', |
| 'hisi_sccl,hha': 'hisi_sccl,hha', |
| 'hisi_sccl,l3c': 'hisi_sccl,l3c', |
| 'imx8_ddr': 'imx8_ddr', |
| 'imx9_ddr': 'imx9_ddr', |
| 'L3PMC': 'amd_l3', |
| 'DFPMC': 'amd_df', |
| 'UMCPMC': 'amd_umc', |
| 'cpu_core': 'cpu_core', |
| 'cpu_atom': 'cpu_atom', |
| 'ali_drw': 'ali_drw', |
| 'arm_cmn': 'arm_cmn', |
| } |
| return table[unit] if unit in table else f'uncore_{unit.lower()}' |
| |
| def is_zero(val: str) -> bool: |
| try: |
| if val.startswith('0x'): |
| return int(val, 16) == 0 |
| else: |
| return int(val) == 0 |
| except e: |
| return False |
| |
| def canonicalize_value(val: str) -> str: |
| try: |
| if val.startswith('0x'): |
| return llx(int(val, 16)) |
| return str(int(val)) |
| except e: |
| return val |
| |
| eventcode = 0 |
| if 'EventCode' in jd: |
| eventcode = int(jd['EventCode'].split(',', 1)[0], 0) |
| if 'ExtSel' in jd: |
| eventcode |= int(jd['ExtSel']) << 8 |
| configcode = int(jd['ConfigCode'], 0) if 'ConfigCode' in jd else None |
| eventidcode = int(jd['EventidCode'], 0) if 'EventidCode' in jd else None |
| self.name = jd['EventName'].lower() if 'EventName' in jd else None |
| self.topic = '' |
| self.compat = jd.get('Compat') |
| self.desc = fixdesc(jd.get('BriefDescription')) |
| self.long_desc = fixdesc(jd.get('PublicDescription')) |
| precise = jd.get('PEBS') |
| msr = lookup_msr(jd.get('MSRIndex')) |
| msrval = jd.get('MSRValue') |
| extra_desc = '' |
| if 'Data_LA' in jd: |
| extra_desc += ' Supports address when precise' |
| if 'Errata' in jd: |
| extra_desc += '.' |
| if 'Errata' in jd: |
| extra_desc += ' Spec update: ' + jd['Errata'] |
| self.pmu = unit_to_pmu(jd.get('Unit')) |
| filter = jd.get('Filter') |
| self.unit = jd.get('ScaleUnit') |
| self.perpkg = jd.get('PerPkg') |
| self.aggr_mode = convert_aggr_mode(jd.get('AggregationMode')) |
| self.deprecated = jd.get('Deprecated') |
| self.metric_name = jd.get('MetricName') |
| self.metric_group = jd.get('MetricGroup') |
| self.metricgroup_no_group = jd.get('MetricgroupNoGroup') |
| self.default_metricgroup_name = jd.get('DefaultMetricgroupName') |
| self.event_grouping = convert_metric_constraint(jd.get('MetricConstraint')) |
| self.metric_expr = None |
| if 'MetricExpr' in jd: |
| self.metric_expr = metric.ParsePerfJson(jd['MetricExpr']).Simplify() |
| # Note, the metric formula for the threshold isn't parsed as the & |
| # and > have incorrect precedence. |
| self.metric_threshold = jd.get('MetricThreshold') |
| |
| arch_std = jd.get('ArchStdEvent') |
| if precise and self.desc and '(Precise Event)' not in self.desc: |
| extra_desc += ' (Must be precise)' if precise == '2' else (' (Precise ' |
| 'event)') |
| event = None |
| if configcode is not None: |
| event = f'config={llx(configcode)}' |
| elif eventidcode is not None: |
| event = f'eventid={llx(eventidcode)}' |
| else: |
| event = f'event={llx(eventcode)}' |
| event_fields = [ |
| ('AnyThread', 'any='), |
| ('PortMask', 'ch_mask='), |
| ('CounterMask', 'cmask='), |
| ('EdgeDetect', 'edge='), |
| ('FCMask', 'fc_mask='), |
| ('Invert', 'inv='), |
| ('SampleAfterValue', 'period='), |
| ('UMask', 'umask='), |
| ('NodeType', 'type='), |
| ('RdWrMask', 'rdwrmask='), |
| ('EnAllCores', 'enallcores='), |
| ('EnAllSlices', 'enallslices='), |
| ('SliceId', 'sliceid='), |
| ('ThreadMask', 'threadmask='), |
| ] |
| for key, value in event_fields: |
| if key in jd and not is_zero(jd[key]): |
| event += f',{value}{canonicalize_value(jd[key])}' |
| if filter: |
| event += f',{filter}' |
| if msr: |
| event += f',{msr}{msrval}' |
| if self.desc and extra_desc: |
| self.desc += extra_desc |
| if self.long_desc and extra_desc: |
| self.long_desc += extra_desc |
| if arch_std: |
| if arch_std.lower() in _arch_std_events: |
| event = _arch_std_events[arch_std.lower()].event |
| # Copy from the architecture standard event to self for undefined fields. |
| for attr, value in _arch_std_events[arch_std.lower()].__dict__.items(): |
| if hasattr(self, attr) and not getattr(self, attr): |
| setattr(self, attr, value) |
| else: |
| raise argparse.ArgumentTypeError('Cannot find arch std event:', arch_std) |
| |
| self.event = real_event(self.name, event) |
| |
| def __repr__(self) -> str: |
| """String representation primarily for debugging.""" |
| s = '{\n' |
| for attr, value in self.__dict__.items(): |
| if value: |
| s += f'\t{attr} = {value},\n' |
| return s + '}' |
| |
| def build_c_string(self, metric: bool) -> str: |
| s = '' |
| for attr in _json_metric_attributes if metric else _json_event_attributes: |
| x = getattr(self, attr) |
| if metric and x and attr == 'metric_expr': |
| # Convert parsed metric expressions into a string. Slashes |
| # must be doubled in the file. |
| x = x.ToPerfJson().replace('\\', '\\\\') |
| if metric and x and attr == 'metric_threshold': |
| x = x.replace('\\', '\\\\') |
| if attr in _json_enum_attributes: |
| s += x if x else '0' |
| else: |
| s += f'{x}\\000' if x else '\\000' |
| return s |
| |
| def to_c_string(self, metric: bool) -> str: |
| """Representation of the event as a C struct initializer.""" |
| |
| s = self.build_c_string(metric) |
| return f'{{ { _bcs.offsets[s] } }}, /* {s} */\n' |
| |
| |
| @lru_cache(maxsize=None) |
| def read_json_events(path: str, topic: str) -> Sequence[JsonEvent]: |
| """Read json events from the specified file.""" |
| try: |
| events = json.load(open(path), object_hook=JsonEvent) |
| except BaseException as err: |
| print(f"Exception processing {path}") |
| raise |
| metrics: list[Tuple[str, str, metric.Expression]] = [] |
| for event in events: |
| event.topic = topic |
| if event.metric_name and '-' not in event.metric_name: |
| metrics.append((event.pmu, event.metric_name, event.metric_expr)) |
| updates = metric.RewriteMetricsInTermsOfOthers(metrics) |
| if updates: |
| for event in events: |
| if event.metric_name in updates: |
| # print(f'Updated {event.metric_name} from\n"{event.metric_expr}"\n' |
| # f'to\n"{updates[event.metric_name]}"') |
| event.metric_expr = updates[event.metric_name] |
| |
| return events |
| |
| def preprocess_arch_std_files(archpath: str) -> None: |
| """Read in all architecture standard events.""" |
| global _arch_std_events |
| for item in os.scandir(archpath): |
| if item.is_file() and item.name.endswith('.json'): |
| for event in read_json_events(item.path, topic=''): |
| if event.name: |
| _arch_std_events[event.name.lower()] = event |
| if event.metric_name: |
| _arch_std_events[event.metric_name.lower()] = event |
| |
| |
| def add_events_table_entries(item: os.DirEntry, topic: str) -> None: |
| """Add contents of file to _pending_events table.""" |
| for e in read_json_events(item.path, topic): |
| if e.name: |
| _pending_events.append(e) |
| if e.metric_name: |
| _pending_metrics.append(e) |
| |
| |
| def print_pending_events() -> None: |
| """Optionally close events table.""" |
| |
| def event_cmp_key(j: JsonEvent) -> Tuple[str, str, bool, str, str]: |
| def fix_none(s: Optional[str]) -> str: |
| if s is None: |
| return '' |
| return s |
| |
| return (fix_none(j.pmu).replace(',','_'), fix_none(j.name), j.desc is not None, fix_none(j.topic), |
| fix_none(j.metric_name)) |
| |
| global _pending_events |
| if not _pending_events: |
| return |
| |
| global _pending_events_tblname |
| if _pending_events_tblname.endswith('_sys'): |
| global _sys_event_tables |
| _sys_event_tables.append(_pending_events_tblname) |
| else: |
| global event_tables |
| _event_tables.append(_pending_events_tblname) |
| |
| first = True |
| last_pmu = None |
| last_name = None |
| pmus = set() |
| for event in sorted(_pending_events, key=event_cmp_key): |
| if last_pmu and last_pmu == event.pmu: |
| assert event.name != last_name, f"Duplicate event: {last_pmu}/{last_name}/ in {_pending_events_tblname}" |
| if event.pmu != last_pmu: |
| if not first: |
| _args.output_file.write('};\n') |
| pmu_name = event.pmu.replace(',', '_') |
| _args.output_file.write( |
| f'static const struct compact_pmu_event {_pending_events_tblname}_{pmu_name}[] = {{\n') |
| first = False |
| last_pmu = event.pmu |
| pmus.add((event.pmu, pmu_name)) |
| |
| _args.output_file.write(event.to_c_string(metric=False)) |
| last_name = event.name |
| _pending_events = [] |
| |
| _args.output_file.write(f""" |
| }}; |
| |
| const struct pmu_table_entry {_pending_events_tblname}[] = {{ |
| """) |
| for (pmu, tbl_pmu) in sorted(pmus): |
| pmu_name = f"{pmu}\\000" |
| _args.output_file.write(f"""{{ |
| .entries = {_pending_events_tblname}_{tbl_pmu}, |
| .num_entries = ARRAY_SIZE({_pending_events_tblname}_{tbl_pmu}), |
| .pmu_name = {{ {_bcs.offsets[pmu_name]} /* {pmu_name} */ }}, |
| }}, |
| """) |
| _args.output_file.write('};\n\n') |
| |
| def print_pending_metrics() -> None: |
| """Optionally close metrics table.""" |
| |
| def metric_cmp_key(j: JsonEvent) -> Tuple[bool, str, str]: |
| def fix_none(s: Optional[str]) -> str: |
| if s is None: |
| return '' |
| return s |
| |
| return (j.desc is not None, fix_none(j.pmu), fix_none(j.metric_name)) |
| |
| global _pending_metrics |
| if not _pending_metrics: |
| return |
| |
| global _pending_metrics_tblname |
| if _pending_metrics_tblname.endswith('_sys'): |
| global _sys_metric_tables |
| _sys_metric_tables.append(_pending_metrics_tblname) |
| else: |
| global metric_tables |
| _metric_tables.append(_pending_metrics_tblname) |
| |
| first = True |
| last_pmu = None |
| pmus = set() |
| for metric in sorted(_pending_metrics, key=metric_cmp_key): |
| if metric.pmu != last_pmu: |
| if not first: |
| _args.output_file.write('};\n') |
| pmu_name = metric.pmu.replace(',', '_') |
| _args.output_file.write( |
| f'static const struct compact_pmu_event {_pending_metrics_tblname}_{pmu_name}[] = {{\n') |
| first = False |
| last_pmu = metric.pmu |
| pmus.add((metric.pmu, pmu_name)) |
| |
| _args.output_file.write(metric.to_c_string(metric=True)) |
| _pending_metrics = [] |
| |
| _args.output_file.write(f""" |
| }}; |
| |
| const struct pmu_table_entry {_pending_metrics_tblname}[] = {{ |
| """) |
| for (pmu, tbl_pmu) in sorted(pmus): |
| pmu_name = f"{pmu}\\000" |
| _args.output_file.write(f"""{{ |
| .entries = {_pending_metrics_tblname}_{tbl_pmu}, |
| .num_entries = ARRAY_SIZE({_pending_metrics_tblname}_{tbl_pmu}), |
| .pmu_name = {{ {_bcs.offsets[pmu_name]} /* {pmu_name} */ }}, |
| }}, |
| """) |
| _args.output_file.write('};\n\n') |
| |
| def get_topic(topic: str) -> str: |
| if topic.endswith('metrics.json'): |
| return 'metrics' |
| return removesuffix(topic, '.json').replace('-', ' ') |
| |
| def preprocess_one_file(parents: Sequence[str], item: os.DirEntry) -> None: |
| |
| if item.is_dir(): |
| return |
| |
| # base dir or too deep |
| level = len(parents) |
| if level == 0 or level > 4: |
| return |
| |
| # Ignore other directories. If the file name does not have a .json |
| # extension, ignore it. It could be a readme.txt for instance. |
| if not item.is_file() or not item.name.endswith('.json'): |
| return |
| |
| if item.name == 'metricgroups.json': |
| metricgroup_descriptions = json.load(open(item.path)) |
| for mgroup in metricgroup_descriptions: |
| assert len(mgroup) > 1, parents |
| description = f"{metricgroup_descriptions[mgroup]}\\000" |
| mgroup = f"{mgroup}\\000" |
| _bcs.add(mgroup, metric=True) |
| _bcs.add(description, metric=True) |
| _metricgroups[mgroup] = description |
| return |
| |
| topic = get_topic(item.name) |
| for event in read_json_events(item.path, topic): |
| pmu_name = f"{event.pmu}\\000" |
| if event.name: |
| _bcs.add(pmu_name, metric=False) |
| _bcs.add(event.build_c_string(metric=False), metric=False) |
| if event.metric_name: |
| _bcs.add(pmu_name, metric=True) |
| _bcs.add(event.build_c_string(metric=True), metric=True) |
| |
| def process_one_file(parents: Sequence[str], item: os.DirEntry) -> None: |
| """Process a JSON file during the main walk.""" |
| def is_leaf_dir_ignoring_sys(path: str) -> bool: |
| for item in os.scandir(path): |
| if item.is_dir() and item.name != 'sys': |
| return False |
| return True |
| |
| # Model directories are leaves (ignoring possible sys |
| # directories). The FTW will walk into the directory next. Flush |
| # pending events and metrics and update the table names for the new |
| # model directory. |
| if item.is_dir() and is_leaf_dir_ignoring_sys(item.path): |
| print_pending_events() |
| print_pending_metrics() |
| |
| global _pending_events_tblname |
| _pending_events_tblname = file_name_to_table_name('pmu_events_', parents, item.name) |
| global _pending_metrics_tblname |
| _pending_metrics_tblname = file_name_to_table_name('pmu_metrics_', parents, item.name) |
| |
| if item.name == 'sys': |
| _sys_event_table_to_metric_table_mapping[_pending_events_tblname] = _pending_metrics_tblname |
| return |
| |
| # base dir or too deep |
| level = len(parents) |
| if level == 0 or level > 4: |
| return |
| |
| # Ignore other directories. If the file name does not have a .json |
| # extension, ignore it. It could be a readme.txt for instance. |
| if not item.is_file() or not item.name.endswith('.json') or item.name == 'metricgroups.json': |
| return |
| |
| add_events_table_entries(item, get_topic(item.name)) |
| |
| |
| def print_mapping_table(archs: Sequence[str]) -> None: |
| """Read the mapfile and generate the struct from cpuid string to event table.""" |
| _args.output_file.write(""" |
| /* Struct used to make the PMU event table implementation opaque to callers. */ |
| struct pmu_events_table { |
| const struct pmu_table_entry *pmus; |
| uint32_t num_pmus; |
| }; |
| |
| /* Struct used to make the PMU metric table implementation opaque to callers. */ |
| struct pmu_metrics_table { |
| const struct pmu_table_entry *pmus; |
| uint32_t num_pmus; |
| }; |
| |
| /* |
| * Map a CPU to its table of PMU events. The CPU is identified by the |
| * cpuid field, which is an arch-specific identifier for the CPU. |
| * The identifier specified in tools/perf/pmu-events/arch/xxx/mapfile |
| * must match the get_cpuid_str() in tools/perf/arch/xxx/util/header.c) |
| * |
| * The cpuid can contain any character other than the comma. |
| */ |
| struct pmu_events_map { |
| const char *arch; |
| const char *cpuid; |
| struct pmu_events_table event_table; |
| struct pmu_metrics_table metric_table; |
| }; |
| |
| /* |
| * Global table mapping each known CPU for the architecture to its |
| * table of PMU events. |
| */ |
| const struct pmu_events_map pmu_events_map[] = { |
| """) |
| for arch in archs: |
| if arch == 'test': |
| _args.output_file.write("""{ |
| \t.arch = "testarch", |
| \t.cpuid = "testcpu", |
| \t.event_table = { |
| \t\t.pmus = pmu_events__test_soc_cpu, |
| \t\t.num_pmus = ARRAY_SIZE(pmu_events__test_soc_cpu), |
| \t}, |
| \t.metric_table = { |
| \t\t.pmus = pmu_metrics__test_soc_cpu, |
| \t\t.num_pmus = ARRAY_SIZE(pmu_metrics__test_soc_cpu), |
| \t} |
| }, |
| """) |
| else: |
| with open(f'{_args.starting_dir}/{arch}/mapfile.csv') as csvfile: |
| table = csv.reader(csvfile) |
| first = True |
| for row in table: |
| # Skip the first row or any row beginning with #. |
| if not first and len(row) > 0 and not row[0].startswith('#'): |
| event_tblname = file_name_to_table_name('pmu_events_', [], row[2].replace('/', '_')) |
| if event_tblname in _event_tables: |
| event_size = f'ARRAY_SIZE({event_tblname})' |
| else: |
| event_tblname = 'NULL' |
| event_size = '0' |
| metric_tblname = file_name_to_table_name('pmu_metrics_', [], row[2].replace('/', '_')) |
| if metric_tblname in _metric_tables: |
| metric_size = f'ARRAY_SIZE({metric_tblname})' |
| else: |
| metric_tblname = 'NULL' |
| metric_size = '0' |
| if event_size == '0' and metric_size == '0': |
| continue |
| cpuid = row[0].replace('\\', '\\\\') |
| _args.output_file.write(f"""{{ |
| \t.arch = "{arch}", |
| \t.cpuid = "{cpuid}", |
| \t.event_table = {{ |
| \t\t.pmus = {event_tblname}, |
| \t\t.num_pmus = {event_size} |
| \t}}, |
| \t.metric_table = {{ |
| \t\t.pmus = {metric_tblname}, |
| \t\t.num_pmus = {metric_size} |
| \t}} |
| }}, |
| """) |
| first = False |
| |
| _args.output_file.write("""{ |
| \t.arch = 0, |
| \t.cpuid = 0, |
| \t.event_table = { 0, 0 }, |
| \t.metric_table = { 0, 0 }, |
| } |
| }; |
| """) |
| |
| |
| def print_system_mapping_table() -> None: |
| """C struct mapping table array for tables from /sys directories.""" |
| _args.output_file.write(""" |
| struct pmu_sys_events { |
| \tconst char *name; |
| \tstruct pmu_events_table event_table; |
| \tstruct pmu_metrics_table metric_table; |
| }; |
| |
| static const struct pmu_sys_events pmu_sys_event_tables[] = { |
| """) |
| printed_metric_tables = [] |
| for tblname in _sys_event_tables: |
| _args.output_file.write(f"""\t{{ |
| \t\t.event_table = {{ |
| \t\t\t.pmus = {tblname}, |
| \t\t\t.num_pmus = ARRAY_SIZE({tblname}) |
| \t\t}},""") |
| metric_tblname = _sys_event_table_to_metric_table_mapping[tblname] |
| if metric_tblname in _sys_metric_tables: |
| _args.output_file.write(f""" |
| \t\t.metric_table = {{ |
| \t\t\t.pmus = {metric_tblname}, |
| \t\t\t.num_pmus = ARRAY_SIZE({metric_tblname}) |
| \t\t}},""") |
| printed_metric_tables.append(metric_tblname) |
| _args.output_file.write(f""" |
| \t\t.name = \"{tblname}\", |
| \t}}, |
| """) |
| for tblname in _sys_metric_tables: |
| if tblname in printed_metric_tables: |
| continue |
| _args.output_file.write(f"""\t{{ |
| \t\t.metric_table = {{ |
| \t\t\t.pmus = {tblname}, |
| \t\t\t.num_pmus = ARRAY_SIZE({tblname}) |
| \t\t}}, |
| \t\t.name = \"{tblname}\", |
| \t}}, |
| """) |
| _args.output_file.write("""\t{ |
| \t\t.event_table = { 0, 0 }, |
| \t\t.metric_table = { 0, 0 }, |
| \t}, |
| }; |
| |
| static void decompress_event(int offset, struct pmu_event *pe) |
| { |
| \tconst char *p = &big_c_string[offset]; |
| """) |
| for attr in _json_event_attributes: |
| _args.output_file.write(f'\n\tpe->{attr} = ') |
| if attr in _json_enum_attributes: |
| _args.output_file.write("*p - '0';\n") |
| else: |
| _args.output_file.write("(*p == '\\0' ? NULL : p);\n") |
| if attr == _json_event_attributes[-1]: |
| continue |
| if attr in _json_enum_attributes: |
| _args.output_file.write('\tp++;') |
| else: |
| _args.output_file.write('\twhile (*p++);') |
| _args.output_file.write("""} |
| |
| static void decompress_metric(int offset, struct pmu_metric *pm) |
| { |
| \tconst char *p = &big_c_string[offset]; |
| """) |
| for attr in _json_metric_attributes: |
| _args.output_file.write(f'\n\tpm->{attr} = ') |
| if attr in _json_enum_attributes: |
| _args.output_file.write("*p - '0';\n") |
| else: |
| _args.output_file.write("(*p == '\\0' ? NULL : p);\n") |
| if attr == _json_metric_attributes[-1]: |
| continue |
| if attr in _json_enum_attributes: |
| _args.output_file.write('\tp++;') |
| else: |
| _args.output_file.write('\twhile (*p++);') |
| _args.output_file.write("""} |
| |
| static int pmu_events_table__for_each_event_pmu(const struct pmu_events_table *table, |
| const struct pmu_table_entry *pmu, |
| pmu_event_iter_fn fn, |
| void *data) |
| { |
| int ret; |
| struct pmu_event pe = { |
| .pmu = &big_c_string[pmu->pmu_name.offset], |
| }; |
| |
| for (uint32_t i = 0; i < pmu->num_entries; i++) { |
| decompress_event(pmu->entries[i].offset, &pe); |
| if (!pe.name) |
| continue; |
| ret = fn(&pe, table, data); |
| if (ret) |
| return ret; |
| } |
| return 0; |
| } |
| |
| static int pmu_events_table__find_event_pmu(const struct pmu_events_table *table, |
| const struct pmu_table_entry *pmu, |
| const char *name, |
| pmu_event_iter_fn fn, |
| void *data) |
| { |
| struct pmu_event pe = { |
| .pmu = &big_c_string[pmu->pmu_name.offset], |
| }; |
| int low = 0, high = pmu->num_entries - 1; |
| |
| while (low <= high) { |
| int cmp, mid = (low + high) / 2; |
| |
| decompress_event(pmu->entries[mid].offset, &pe); |
| |
| if (!pe.name && !name) |
| goto do_call; |
| |
| if (!pe.name && name) { |
| low = mid + 1; |
| continue; |
| } |
| if (pe.name && !name) { |
| high = mid - 1; |
| continue; |
| } |
| |
| cmp = strcasecmp(pe.name, name); |
| if (cmp < 0) { |
| low = mid + 1; |
| continue; |
| } |
| if (cmp > 0) { |
| high = mid - 1; |
| continue; |
| } |
| do_call: |
| return fn ? fn(&pe, table, data) : 0; |
| } |
| return PMU_EVENTS__NOT_FOUND; |
| } |
| |
| int pmu_events_table__for_each_event(const struct pmu_events_table *table, |
| struct perf_pmu *pmu, |
| pmu_event_iter_fn fn, |
| void *data) |
| { |
| for (size_t i = 0; i < table->num_pmus; i++) { |
| const struct pmu_table_entry *table_pmu = &table->pmus[i]; |
| const char *pmu_name = &big_c_string[table_pmu->pmu_name.offset]; |
| int ret; |
| |
| if (pmu && !pmu__name_match(pmu, pmu_name)) |
| continue; |
| |
| ret = pmu_events_table__for_each_event_pmu(table, table_pmu, fn, data); |
| if (pmu || ret) |
| return ret; |
| } |
| return 0; |
| } |
| |
| int pmu_events_table__find_event(const struct pmu_events_table *table, |
| struct perf_pmu *pmu, |
| const char *name, |
| pmu_event_iter_fn fn, |
| void *data) |
| { |
| for (size_t i = 0; i < table->num_pmus; i++) { |
| const struct pmu_table_entry *table_pmu = &table->pmus[i]; |
| const char *pmu_name = &big_c_string[table_pmu->pmu_name.offset]; |
| int ret; |
| |
| if (!pmu__name_match(pmu, pmu_name)) |
| continue; |
| |
| ret = pmu_events_table__find_event_pmu(table, table_pmu, name, fn, data); |
| if (ret != PMU_EVENTS__NOT_FOUND) |
| return ret; |
| } |
| return PMU_EVENTS__NOT_FOUND; |
| } |
| |
| size_t pmu_events_table__num_events(const struct pmu_events_table *table, |
| struct perf_pmu *pmu) |
| { |
| size_t count = 0; |
| |
| for (size_t i = 0; i < table->num_pmus; i++) { |
| const struct pmu_table_entry *table_pmu = &table->pmus[i]; |
| const char *pmu_name = &big_c_string[table_pmu->pmu_name.offset]; |
| |
| if (pmu__name_match(pmu, pmu_name)) |
| count += table_pmu->num_entries; |
| } |
| return count; |
| } |
| |
| static int pmu_metrics_table__for_each_metric_pmu(const struct pmu_metrics_table *table, |
| const struct pmu_table_entry *pmu, |
| pmu_metric_iter_fn fn, |
| void *data) |
| { |
| int ret; |
| struct pmu_metric pm = { |
| .pmu = &big_c_string[pmu->pmu_name.offset], |
| }; |
| |
| for (uint32_t i = 0; i < pmu->num_entries; i++) { |
| decompress_metric(pmu->entries[i].offset, &pm); |
| if (!pm.metric_expr) |
| continue; |
| ret = fn(&pm, table, data); |
| if (ret) |
| return ret; |
| } |
| return 0; |
| } |
| |
| int pmu_metrics_table__for_each_metric(const struct pmu_metrics_table *table, |
| pmu_metric_iter_fn fn, |
| void *data) |
| { |
| for (size_t i = 0; i < table->num_pmus; i++) { |
| int ret = pmu_metrics_table__for_each_metric_pmu(table, &table->pmus[i], |
| fn, data); |
| |
| if (ret) |
| return ret; |
| } |
| return 0; |
| } |
| |
| static const struct pmu_events_map *map_for_pmu(struct perf_pmu *pmu) |
| { |
| static struct { |
| const struct pmu_events_map *map; |
| struct perf_pmu *pmu; |
| } last_result; |
| static struct { |
| const struct pmu_events_map *map; |
| char *cpuid; |
| } last_map_search; |
| static bool has_last_result, has_last_map_search; |
| const struct pmu_events_map *map = NULL; |
| char *cpuid = NULL; |
| size_t i; |
| |
| if (has_last_result && last_result.pmu == pmu) |
| return last_result.map; |
| |
| cpuid = perf_pmu__getcpuid(pmu); |
| |
| /* |
| * On some platforms which uses cpus map, cpuid can be NULL for |
| * PMUs other than CORE PMUs. |
| */ |
| if (!cpuid) |
| goto out_update_last_result; |
| |
| if (has_last_map_search && !strcmp(last_map_search.cpuid, cpuid)) { |
| map = last_map_search.map; |
| free(cpuid); |
| } else { |
| i = 0; |
| for (;;) { |
| map = &pmu_events_map[i++]; |
| |
| if (!map->arch) { |
| map = NULL; |
| break; |
| } |
| |
| if (!strcmp_cpuid_str(map->cpuid, cpuid)) |
| break; |
| } |
| free(last_map_search.cpuid); |
| last_map_search.cpuid = cpuid; |
| last_map_search.map = map; |
| has_last_map_search = true; |
| } |
| out_update_last_result: |
| last_result.pmu = pmu; |
| last_result.map = map; |
| has_last_result = true; |
| return map; |
| } |
| |
| const struct pmu_events_table *perf_pmu__find_events_table(struct perf_pmu *pmu) |
| { |
| const struct pmu_events_map *map = map_for_pmu(pmu); |
| |
| if (!map) |
| return NULL; |
| |
| if (!pmu) |
| return &map->event_table; |
| |
| for (size_t i = 0; i < map->event_table.num_pmus; i++) { |
| const struct pmu_table_entry *table_pmu = &map->event_table.pmus[i]; |
| const char *pmu_name = &big_c_string[table_pmu->pmu_name.offset]; |
| |
| if (pmu__name_match(pmu, pmu_name)) |
| return &map->event_table; |
| } |
| return NULL; |
| } |
| |
| const struct pmu_metrics_table *perf_pmu__find_metrics_table(struct perf_pmu *pmu) |
| { |
| const struct pmu_events_map *map = map_for_pmu(pmu); |
| |
| if (!map) |
| return NULL; |
| |
| if (!pmu) |
| return &map->metric_table; |
| |
| for (size_t i = 0; i < map->metric_table.num_pmus; i++) { |
| const struct pmu_table_entry *table_pmu = &map->metric_table.pmus[i]; |
| const char *pmu_name = &big_c_string[table_pmu->pmu_name.offset]; |
| |
| if (pmu__name_match(pmu, pmu_name)) |
| return &map->metric_table; |
| } |
| return NULL; |
| } |
| |
| const struct pmu_events_table *find_core_events_table(const char *arch, const char *cpuid) |
| { |
| for (const struct pmu_events_map *tables = &pmu_events_map[0]; |
| tables->arch; |
| tables++) { |
| if (!strcmp(tables->arch, arch) && !strcmp_cpuid_str(tables->cpuid, cpuid)) |
| return &tables->event_table; |
| } |
| return NULL; |
| } |
| |
| const struct pmu_metrics_table *find_core_metrics_table(const char *arch, const char *cpuid) |
| { |
| for (const struct pmu_events_map *tables = &pmu_events_map[0]; |
| tables->arch; |
| tables++) { |
| if (!strcmp(tables->arch, arch) && !strcmp_cpuid_str(tables->cpuid, cpuid)) |
| return &tables->metric_table; |
| } |
| return NULL; |
| } |
| |
| int pmu_for_each_core_event(pmu_event_iter_fn fn, void *data) |
| { |
| for (const struct pmu_events_map *tables = &pmu_events_map[0]; |
| tables->arch; |
| tables++) { |
| int ret = pmu_events_table__for_each_event(&tables->event_table, |
| /*pmu=*/ NULL, fn, data); |
| |
| if (ret) |
| return ret; |
| } |
| return 0; |
| } |
| |
| int pmu_for_each_core_metric(pmu_metric_iter_fn fn, void *data) |
| { |
| for (const struct pmu_events_map *tables = &pmu_events_map[0]; |
| tables->arch; |
| tables++) { |
| int ret = pmu_metrics_table__for_each_metric(&tables->metric_table, fn, data); |
| |
| if (ret) |
| return ret; |
| } |
| return 0; |
| } |
| |
| const struct pmu_events_table *find_sys_events_table(const char *name) |
| { |
| for (const struct pmu_sys_events *tables = &pmu_sys_event_tables[0]; |
| tables->name; |
| tables++) { |
| if (!strcmp(tables->name, name)) |
| return &tables->event_table; |
| } |
| return NULL; |
| } |
| |
| int pmu_for_each_sys_event(pmu_event_iter_fn fn, void *data) |
| { |
| for (const struct pmu_sys_events *tables = &pmu_sys_event_tables[0]; |
| tables->name; |
| tables++) { |
| int ret = pmu_events_table__for_each_event(&tables->event_table, |
| /*pmu=*/ NULL, fn, data); |
| |
| if (ret) |
| return ret; |
| } |
| return 0; |
| } |
| |
| int pmu_for_each_sys_metric(pmu_metric_iter_fn fn, void *data) |
| { |
| for (const struct pmu_sys_events *tables = &pmu_sys_event_tables[0]; |
| tables->name; |
| tables++) { |
| int ret = pmu_metrics_table__for_each_metric(&tables->metric_table, fn, data); |
| |
| if (ret) |
| return ret; |
| } |
| return 0; |
| } |
| """) |
| |
| def print_metricgroups() -> None: |
| _args.output_file.write(""" |
| static const int metricgroups[][2] = { |
| """) |
| for mgroup in sorted(_metricgroups): |
| description = _metricgroups[mgroup] |
| _args.output_file.write( |
| f'\t{{ {_bcs.offsets[mgroup]}, {_bcs.offsets[description]} }}, /* {mgroup} => {description} */\n' |
| ) |
| _args.output_file.write(""" |
| }; |
| |
| const char *describe_metricgroup(const char *group) |
| { |
| int low = 0, high = (int)ARRAY_SIZE(metricgroups) - 1; |
| |
| while (low <= high) { |
| int mid = (low + high) / 2; |
| const char *mgroup = &big_c_string[metricgroups[mid][0]]; |
| int cmp = strcmp(mgroup, group); |
| |
| if (cmp == 0) { |
| return &big_c_string[metricgroups[mid][1]]; |
| } else if (cmp < 0) { |
| low = mid + 1; |
| } else { |
| high = mid - 1; |
| } |
| } |
| return NULL; |
| } |
| """) |
| |
| def main() -> None: |
| global _args |
| |
| def dir_path(path: str) -> str: |
| """Validate path is a directory for argparse.""" |
| if os.path.isdir(path): |
| return path |
| raise argparse.ArgumentTypeError(f'\'{path}\' is not a valid directory') |
| |
| def ftw(path: str, parents: Sequence[str], |
| action: Callable[[Sequence[str], os.DirEntry], None]) -> None: |
| """Replicate the directory/file walking behavior of C's file tree walk.""" |
| for item in sorted(os.scandir(path), key=lambda e: e.name): |
| if _args.model != 'all' and item.is_dir(): |
| # Check if the model matches one in _args.model. |
| if len(parents) == _args.model.split(',')[0].count('/'): |
| # We're testing the correct directory. |
| item_path = '/'.join(parents) + ('/' if len(parents) > 0 else '') + item.name |
| if 'test' not in item_path and item_path not in _args.model.split(','): |
| continue |
| action(parents, item) |
| if item.is_dir(): |
| ftw(item.path, parents + [item.name], action) |
| |
| ap = argparse.ArgumentParser() |
| ap.add_argument('arch', help='Architecture name like x86') |
| ap.add_argument('model', help='''Select a model such as skylake to |
| reduce the code size. Normally set to "all". For architectures like |
| ARM64 with an implementor/model, the model must include the implementor |
| such as "arm/cortex-a34".''', |
| default='all') |
| ap.add_argument( |
| 'starting_dir', |
| type=dir_path, |
| help='Root of tree containing architecture directories containing json files' |
| ) |
| ap.add_argument( |
| 'output_file', type=argparse.FileType('w', encoding='utf-8'), nargs='?', default=sys.stdout) |
| _args = ap.parse_args() |
| |
| _args.output_file.write(f""" |
| /* SPDX-License-Identifier: GPL-2.0 */ |
| /* THIS FILE WAS AUTOGENERATED BY jevents.py arch={_args.arch} model={_args.model} ! */ |
| """) |
| _args.output_file.write(""" |
| #include <pmu-events/pmu-events.h> |
| #include "util/header.h" |
| #include "util/pmu.h" |
| #include <string.h> |
| #include <stddef.h> |
| |
| struct compact_pmu_event { |
| int offset; |
| }; |
| |
| struct pmu_table_entry { |
| const struct compact_pmu_event *entries; |
| uint32_t num_entries; |
| struct compact_pmu_event pmu_name; |
| }; |
| |
| """) |
| archs = [] |
| for item in os.scandir(_args.starting_dir): |
| if not item.is_dir(): |
| continue |
| if item.name == _args.arch or _args.arch == 'all' or item.name == 'test': |
| archs.append(item.name) |
| |
| if len(archs) < 2 and _args.arch != 'none': |
| raise IOError(f'Missing architecture directory \'{_args.arch}\'') |
| |
| archs.sort() |
| for arch in archs: |
| arch_path = f'{_args.starting_dir}/{arch}' |
| preprocess_arch_std_files(arch_path) |
| ftw(arch_path, [], preprocess_one_file) |
| |
| _bcs.compute() |
| _args.output_file.write('static const char *const big_c_string =\n') |
| for s in _bcs.big_string: |
| _args.output_file.write(s) |
| _args.output_file.write(';\n\n') |
| for arch in archs: |
| arch_path = f'{_args.starting_dir}/{arch}' |
| ftw(arch_path, [], process_one_file) |
| print_pending_events() |
| print_pending_metrics() |
| |
| print_mapping_table(archs) |
| print_system_mapping_table() |
| print_metricgroups() |
| |
| if __name__ == '__main__': |
| main() |