2 # SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause)
3 """Convert directories of JSON events to C code."""
9 from typing import (Callable, Optional, Sequence)
11 # Global command line arguments.
13 # List of event tables generated from "/sys" directories.
14 _sys_event_tables = []
15 # Map from an event name to an architecture standard
16 # JsonEvent. Architecture standard events are in json files in the top
17 # f'{_args.starting_dir}/{_args.arch}' directory.
19 # Track whether an events table is currently being defined and needs closing.
21 # Events to write out when the table is closed
25 def removesuffix(s: str, suffix: str) -> str:
26 """Remove the suffix from a string
28 The removesuffix function is added to str in Python 3.9. We aim for 3.6
29 compatibility and so provide our own function here.
31 return s[0:-len(suffix)] if s.endswith(suffix) else s
34 def file_name_to_table_name(parents: Sequence[str], dirname: str) -> str:
35 """Generate a C table name from directory names."""
39 tblname += '_' + dirname
40 return tblname.replace('-', '_')
44 """Representation of an event loaded from a json file dictionary."""
46 def __init__(self, jd: dict):
47 """Constructor passed the dictionary of parsed json values."""
49 def llx(x: int) -> str:
50 """Convert an int to a string similar to a printf modifier of %#llx."""
51 return '0' if x == 0 else hex(x)
53 def fixdesc(s: str) -> str:
54 """Fix formatting issue for the desc string."""
57 return removesuffix(removesuffix(removesuffix(s, '. '),
58 '. '), '.').replace('\n', '\\n').replace(
59 '\"', '\\"').replace('\r', '\\r')
61 def convert_aggr_mode(aggr_mode: str) -> Optional[str]:
62 """Returns the aggr_mode_class enum value associated with the JSON string."""
69 return aggr_mode_to_enum[aggr_mode]
71 def lookup_msr(num: str) -> Optional[str]:
72 """Converts the msr number, or first in a list to the appropriate event field."""
77 0x1A6: 'offcore_rsp=',
78 0x1A7: 'offcore_rsp=',
81 return msrmap[int(num.split(',', 1)[0], 0)]
83 def real_event(name: str, event: str) -> Optional[str]:
84 """Convert well known event names to an event string otherwise use the event argument."""
86 'inst_retired.any': 'event=0xc0,period=2000003',
87 'inst_retired.any_p': 'event=0xc0,period=2000003',
88 'cpu_clk_unhalted.ref': 'event=0x0,umask=0x03,period=2000003',
89 'cpu_clk_unhalted.thread': 'event=0x3c,period=2000003',
90 'cpu_clk_unhalted.core': 'event=0x3c,period=2000003',
91 'cpu_clk_unhalted.thread_any': 'event=0x3c,any=1,period=2000003',
95 if name.lower() in fixed:
96 return fixed[name.lower()]
99 def unit_to_pmu(unit: str) -> Optional[str]:
100 """Convert a JSON Unit to Linux PMU name."""
103 # Comment brought over from jevents.c:
104 # it's not realistic to keep adding these, we need something more scalable ...
106 'CBO': 'uncore_cbox',
107 'QPI LL': 'uncore_qpi',
108 'SBO': 'uncore_sbox',
109 'iMPH-U': 'uncore_arb',
110 'CPU-M-CF': 'cpum_cf',
111 'CPU-M-SF': 'cpum_sf',
112 'PAI-CRYPTO' : 'pai_crypto',
113 'UPI LL': 'uncore_upi',
114 'hisi_sicl,cpa': 'hisi_sicl,cpa',
115 'hisi_sccl,ddrc': 'hisi_sccl,ddrc',
116 'hisi_sccl,hha': 'hisi_sccl,hha',
117 'hisi_sccl,l3c': 'hisi_sccl,l3c',
118 'imx8_ddr': 'imx8_ddr',
121 'cpu_core': 'cpu_core',
122 'cpu_atom': 'cpu_atom',
124 return table[unit] if unit in table else f'uncore_{unit.lower()}'
127 if 'EventCode' in jd:
128 eventcode = int(jd['EventCode'].split(',', 1)[0], 0)
130 eventcode |= int(jd['ExtSel']) << 8
131 configcode = int(jd['ConfigCode'], 0) if 'ConfigCode' in jd else None
132 self.name = jd['EventName'].lower() if 'EventName' in jd else None
134 self.compat = jd.get('Compat')
135 self.desc = fixdesc(jd.get('BriefDescription'))
136 self.long_desc = fixdesc(jd.get('PublicDescription'))
137 precise = jd.get('PEBS')
138 msr = lookup_msr(jd.get('MSRIndex'))
139 msrval = jd.get('MSRValue')
142 extra_desc += ' Supports address when precise'
146 extra_desc += ' Spec update: ' + jd['Errata']
147 self.pmu = unit_to_pmu(jd.get('Unit'))
148 filter = jd.get('Filter')
149 self.unit = jd.get('ScaleUnit')
150 self.perpkg = jd.get('PerPkg')
151 self.aggr_mode = convert_aggr_mode(jd.get('AggregationMode'))
152 self.deprecated = jd.get('Deprecated')
153 self.metric_name = jd.get('MetricName')
154 self.metric_group = jd.get('MetricGroup')
155 self.metric_constraint = jd.get('MetricConstraint')
156 self.metric_expr = jd.get('MetricExpr')
158 self.metric_expr = self.metric_expr.replace('\\', '\\\\')
159 arch_std = jd.get('ArchStdEvent')
160 if precise and self.desc and '(Precise Event)' not in self.desc:
161 extra_desc += ' (Must be precise)' if precise == '2' else (' (Precise '
163 event = f'config={llx(configcode)}' if configcode is not None else f'event={llx(eventcode)}'
165 ('AnyThread', 'any='),
166 ('PortMask', 'ch_mask='),
167 ('CounterMask', 'cmask='),
168 ('EdgeDetect', 'edge='),
169 ('FCMask', 'fc_mask='),
171 ('SampleAfterValue', 'period='),
174 for key, value in event_fields:
175 if key in jd and jd[key] != '0':
176 event += ',' + value + jd[key]
178 event += f',{filter}'
180 event += f',{msr}{msrval}'
181 if self.desc and extra_desc:
182 self.desc += extra_desc
183 if self.long_desc and extra_desc:
184 self.long_desc += extra_desc
186 if self.desc and not self.desc.endswith('. '):
188 self.desc = (self.desc if self.desc else '') + ('Unit: ' + self.pmu + ' ')
189 if arch_std and arch_std.lower() in _arch_std_events:
190 event = _arch_std_events[arch_std.lower()].event
191 # Copy from the architecture standard event to self for undefined fields.
192 for attr, value in _arch_std_events[arch_std.lower()].__dict__.items():
193 if hasattr(self, attr) and not getattr(self, attr):
194 setattr(self, attr, value)
196 self.event = real_event(self.name, event)
198 def __repr__(self) -> str:
199 """String representation primarily for debugging."""
201 for attr, value in self.__dict__.items():
203 s += f'\t{attr} = {value},\n'
206 def to_c_string(self) -> str:
207 """Representation of the event as a C struct initializer."""
209 def attr_string(attr: str, value: str) -> str:
210 return f'\t.{attr} = \"{value}\",\n'
212 def str_if_present(self, attr: str) -> str:
213 if not getattr(self, attr):
215 return attr_string(attr, getattr(self, attr))
219 'aggr_mode', 'compat', 'deprecated', 'desc', 'event', 'long_desc',
220 'metric_constraint', 'metric_expr', 'metric_group', 'metric_name',
221 'name', 'perpkg', 'pmu', 'topic', 'unit'
223 s += str_if_present(self, attr)
228 def read_json_events(path: str, topic: str) -> Sequence[JsonEvent]:
229 """Read json events from the specified file."""
232 result = json.load(open(path), object_hook=JsonEvent)
233 except BaseException as err:
234 print(f"Exception processing {path}")
241 def preprocess_arch_std_files(archpath: str) -> None:
242 """Read in all architecture standard events."""
243 global _arch_std_events
244 for item in os.scandir(archpath):
245 if item.is_file() and item.name.endswith('.json'):
246 for event in read_json_events(item.path, topic=''):
248 _arch_std_events[event.name.lower()] = event
251 def print_events_table_prefix(tblname: str) -> None:
252 """Called when a new events table is started."""
255 raise IOError('Printing table prefix but last table has no suffix')
256 _args.output_file.write(f'static const struct pmu_event {tblname}[] = {{\n')
260 def add_events_table_entries(item: os.DirEntry, topic: str) -> None:
261 """Add contents of file to _pending_events table."""
263 raise IOError('Table entries missing prefix')
264 for e in read_json_events(item.path, topic):
265 _pending_events.append(e)
268 def print_events_table_suffix() -> None:
269 """Optionally close events table."""
271 def event_cmp_key(j: JsonEvent):
272 def fix_none(s: str):
277 return (not j.desc is None, fix_none(j.topic), fix_none(j.name), fix_none(j.pmu),
278 fix_none(j.metric_name))
284 global _pending_events
285 for event in sorted(_pending_events, key=event_cmp_key):
286 _args.output_file.write(event.to_c_string())
289 _args.output_file.write("""{
299 def process_one_file(parents: Sequence[str], item: os.DirEntry) -> None:
300 """Process a JSON file during the main walk."""
301 global _sys_event_tables
303 def get_topic(topic: str) -> str:
304 return removesuffix(topic, '.json').replace('-', ' ')
306 def is_leaf_dir(path: str) -> bool:
307 for item in os.scandir(path):
312 # model directory, reset topic
313 if item.is_dir() and is_leaf_dir(item.path):
314 print_events_table_suffix()
316 tblname = file_name_to_table_name(parents, item.name)
317 if item.name == 'sys':
318 _sys_event_tables.append(tblname)
319 print_events_table_prefix(tblname)
322 # base dir or too deep
324 if level == 0 or level > 4:
327 # Ignore other directories. If the file name does not have a .json
328 # extension, ignore it. It could be a readme.txt for instance.
329 if not item.is_file() or not item.name.endswith('.json'):
332 add_events_table_entries(item, get_topic(item.name))
335 def print_mapping_table(archs: Sequence[str]) -> None:
336 """Read the mapfile and generate the struct from cpuid string to event table."""
337 _args.output_file.write('const struct pmu_events_map pmu_events_map[] = {\n')
340 _args.output_file.write("""{
341 \t.arch = "testarch",
342 \t.cpuid = "testcpu",
343 \t.table = pme_test_soc_cpu,
347 with open(f'{_args.starting_dir}/{arch}/mapfile.csv') as csvfile:
348 table = csv.reader(csvfile)
351 # Skip the first row or any row beginning with #.
352 if not first and len(row) > 0 and not row[0].startswith('#'):
353 tblname = file_name_to_table_name([], row[2].replace('/', '_'))
354 cpuid = row[0].replace('\\', '\\\\')
355 _args.output_file.write(f"""{{
357 \t.cpuid = "{cpuid}",
363 _args.output_file.write("""{
372 def print_system_mapping_table() -> None:
373 """C struct mapping table array for tables from /sys directories."""
374 _args.output_file.write(
375 '\nconst struct pmu_sys_events pmu_sys_event_tables[] = {\n')
376 for tblname in _sys_event_tables:
377 _args.output_file.write(f"""\t{{
378 \t\t.table = {tblname},
379 \t\t.name = \"{tblname}\",
382 _args.output_file.write("""\t{
392 def dir_path(path: str) -> str:
393 """Validate path is a directory for argparse."""
394 if os.path.isdir(path):
396 raise argparse.ArgumentTypeError(f'\'{path}\' is not a valid directory')
398 def ftw(path: str, parents: Sequence[str],
399 action: Callable[[Sequence[str], os.DirEntry], None]) -> None:
400 """Replicate the directory/file walking behavior of C's file tree walk."""
401 for item in os.scandir(path):
402 action(parents, item)
404 ftw(item.path, parents + [item.name], action)
406 ap = argparse.ArgumentParser()
407 ap.add_argument('arch', help='Architecture name like x86')
411 help='Root of tree containing architecture directories containing json files'
414 'output_file', type=argparse.FileType('w'), nargs='?', default=sys.stdout)
415 _args = ap.parse_args()
417 _args.output_file.write("#include \"pmu-events/pmu-events.h\"\n")
419 for item in os.scandir(_args.starting_dir):
420 if not item.is_dir():
422 if item.name == _args.arch or _args.arch == 'all' or item.name == 'test':
423 archs.append(item.name)
426 raise IOError(f'Missing architecture directory \'{_args.arch}\'')
430 arch_path = f'{_args.starting_dir}/{arch}'
431 preprocess_arch_std_files(arch_path)
432 ftw(arch_path, [], process_one_file)
433 print_events_table_suffix()
435 print_mapping_table(archs)
436 print_system_mapping_table()
439 if __name__ == '__main__':