31936eafa9ff6af32df4d1ce830065c7cec65ae1
[platform/kernel/linux-starfive.git] / tools / perf / pmu-events / jevents.py
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."""
4 import argparse
5 import csv
6 import json
7 import os
8 import sys
9 from typing import (Callable, Optional, Sequence)
10
11 # Global command line arguments.
12 _args = None
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.
18 _arch_std_events = {}
19 # Track whether an events table is currently being defined and needs closing.
20 _close_table = False
21
22
23 def removesuffix(s: str, suffix: str) -> str:
24   """Remove the suffix from a string
25
26   The removesuffix function is added to str in Python 3.9. We aim for 3.6
27   compatibility and so provide our own function here.
28   """
29   return s[0:-len(suffix)] if s.endswith(suffix) else s
30
31
32 def file_name_to_table_name(parents: Sequence[str], dirname: str) -> str:
33   """Generate a C table name from directory names."""
34   tblname = 'pme'
35   for p in parents:
36     tblname += '_' + p
37   tblname += '_' + dirname
38   return tblname.replace('-', '_')
39
40
41 class JsonEvent:
42   """Representation of an event loaded from a json file dictionary."""
43
44   def __init__(self, jd: dict):
45     """Constructor passed the dictionary of parsed json values."""
46
47     def llx(x: int) -> str:
48       """Convert an int to a string similar to a printf modifier of %#llx."""
49       return '0' if x == 0 else hex(x)
50
51     def fixdesc(s: str) -> str:
52       """Fix formatting issue for the desc string."""
53       if s is None:
54         return None
55       return removesuffix(removesuffix(removesuffix(s, '.  '),
56                                        '. '), '.').replace('\n', '\\n').replace(
57                                            '\"', '\\"').replace('\r', '\\r')
58
59     def convert_aggr_mode(aggr_mode: str) -> Optional[str]:
60       """Returns the aggr_mode_class enum value associated with the JSON string."""
61       if not aggr_mode:
62         return None
63       aggr_mode_to_enum = {
64           'PerChip': '1',
65           'PerCore': '2',
66       }
67       return aggr_mode_to_enum[aggr_mode]
68
69     def lookup_msr(num: str) -> Optional[str]:
70       """Converts the msr number, or first in a list to the appropriate event field."""
71       if not num:
72         return None
73       msrmap = {
74           0x3F6: 'ldlat=',
75           0x1A6: 'offcore_rsp=',
76           0x1A7: 'offcore_rsp=',
77           0x3F7: 'frontend=',
78       }
79       return msrmap[int(num.split(',', 1)[0], 0)]
80
81     def real_event(name: str, event: str) -> Optional[str]:
82       """Convert well known event names to an event string otherwise use the event argument."""
83       fixed = {
84           'inst_retired.any': 'event=0xc0,period=2000003',
85           'inst_retired.any_p': 'event=0xc0,period=2000003',
86           'cpu_clk_unhalted.ref': 'event=0x0,umask=0x03,period=2000003',
87           'cpu_clk_unhalted.thread': 'event=0x3c,period=2000003',
88           'cpu_clk_unhalted.core': 'event=0x3c,period=2000003',
89           'cpu_clk_unhalted.thread_any': 'event=0x3c,any=1,period=2000003',
90       }
91       if not name:
92         return None
93       if name.lower() in fixed:
94         return fixed[name.lower()]
95       return event
96
97     def unit_to_pmu(unit: str) -> Optional[str]:
98       """Convert a JSON Unit to Linux PMU name."""
99       if not unit:
100         return None
101       # Comment brought over from jevents.c:
102       # it's not realistic to keep adding these, we need something more scalable ...
103       table = {
104           'CBO': 'uncore_cbox',
105           'QPI LL': 'uncore_qpi',
106           'SBO': 'uncore_sbox',
107           'iMPH-U': 'uncore_arb',
108           'CPU-M-CF': 'cpum_cf',
109           'CPU-M-SF': 'cpum_sf',
110           'PAI-CRYPTO' : 'pai_crypto',
111           'UPI LL': 'uncore_upi',
112           'hisi_sicl,cpa': 'hisi_sicl,cpa',
113           'hisi_sccl,ddrc': 'hisi_sccl,ddrc',
114           'hisi_sccl,hha': 'hisi_sccl,hha',
115           'hisi_sccl,l3c': 'hisi_sccl,l3c',
116           'imx8_ddr': 'imx8_ddr',
117           'L3PMC': 'amd_l3',
118           'DFPMC': 'amd_df',
119           'cpu_core': 'cpu_core',
120           'cpu_atom': 'cpu_atom',
121       }
122       return table[unit] if unit in table else f'uncore_{unit.lower()}'
123
124     eventcode = 0
125     if 'EventCode' in jd:
126       eventcode = int(jd['EventCode'].split(',', 1)[0], 0)
127     if 'ExtSel' in jd:
128       eventcode |= int(jd['ExtSel']) << 8
129     configcode = int(jd['ConfigCode'], 0) if 'ConfigCode' in jd else None
130     self.name = jd['EventName'].lower() if 'EventName' in jd else None
131     self.compat = jd.get('Compat')
132     self.desc = fixdesc(jd.get('BriefDescription'))
133     self.long_desc = fixdesc(jd.get('PublicDescription'))
134     precise = jd.get('PEBS')
135     msr = lookup_msr(jd.get('MSRIndex'))
136     msrval = jd.get('MSRValue')
137     extra_desc = ''
138     if 'Data_LA' in jd:
139       extra_desc += '  Supports address when precise'
140       if 'Errata' in jd:
141         extra_desc += '.'
142     if 'Errata' in jd:
143       extra_desc += '  Spec update: ' + jd['Errata']
144     self.pmu = unit_to_pmu(jd.get('Unit'))
145     filter = jd.get('Filter')
146     self.unit = jd.get('ScaleUnit')
147     self.perpkg = jd.get('PerPkg')
148     self.aggr_mode = convert_aggr_mode(jd.get('AggregationMode'))
149     self.deprecated = jd.get('Deprecated')
150     self.metric_name = jd.get('MetricName')
151     self.metric_group = jd.get('MetricGroup')
152     self.metric_constraint = jd.get('MetricConstraint')
153     self.metric_expr = jd.get('MetricExpr')
154     if self.metric_expr:
155       self.metric_expr = self.metric_expr.replace('\\', '\\\\')
156     arch_std = jd.get('ArchStdEvent')
157     if precise and self.desc and '(Precise Event)' not in self.desc:
158       extra_desc += ' (Must be precise)' if precise == '2' else (' (Precise '
159                                                                  'event)')
160     event = f'config={llx(configcode)}' if configcode is not None else f'event={llx(eventcode)}'
161     event_fields = [
162         ('AnyThread', 'any='),
163         ('PortMask', 'ch_mask='),
164         ('CounterMask', 'cmask='),
165         ('EdgeDetect', 'edge='),
166         ('FCMask', 'fc_mask='),
167         ('Invert', 'inv='),
168         ('SampleAfterValue', 'period='),
169         ('UMask', 'umask='),
170     ]
171     for key, value in event_fields:
172       if key in jd and jd[key] != '0':
173         event += ',' + value + jd[key]
174     if filter:
175       event += f',{filter}'
176     if msr:
177       event += f',{msr}{msrval}'
178     if self.desc and extra_desc:
179       self.desc += extra_desc
180     if self.long_desc and extra_desc:
181       self.long_desc += extra_desc
182     if self.pmu:
183       if self.desc and not self.desc.endswith('. '):
184         self.desc += '. '
185       self.desc = (self.desc if self.desc else '') + ('Unit: ' + self.pmu + ' ')
186     if arch_std and arch_std.lower() in _arch_std_events:
187       event = _arch_std_events[arch_std.lower()].event
188       # Copy from the architecture standard event to self for undefined fields.
189       for attr, value in _arch_std_events[arch_std.lower()].__dict__.items():
190         if hasattr(self, attr) and not getattr(self, attr):
191           setattr(self, attr, value)
192
193     self.event = real_event(self.name, event)
194
195   def __repr__(self) -> str:
196     """String representation primarily for debugging."""
197     s = '{\n'
198     for attr, value in self.__dict__.items():
199       if value:
200         s += f'\t{attr} = {value},\n'
201     return s + '}'
202
203   def to_c_string(self, topic_local: str) -> str:
204     """Representation of the event as a C struct initializer."""
205
206     def attr_string(attr: str, value: str) -> str:
207       return f'\t.{attr} = \"{value}\",\n'
208
209     def str_if_present(self, attr: str) -> str:
210       if not getattr(self, attr):
211         return ''
212       return attr_string(attr, getattr(self, attr))
213
214     s = '{\n'
215     s += f'\t.topic = "{topic_local}",\n'
216     for attr in [
217         'aggr_mode', 'compat', 'deprecated', 'desc', 'event', 'long_desc',
218         'metric_constraint', 'metric_expr', 'metric_group', 'metric_name',
219         'name', 'perpkg', 'pmu', 'unit'
220     ]:
221       s += str_if_present(self, attr)
222     s += '},\n'
223     return s
224
225
226 def read_json_events(path: str) -> Sequence[JsonEvent]:
227   """Read json events from the specified file."""
228
229   try:
230     return json.load(open(path), object_hook=lambda d: JsonEvent(d))
231   except BaseException as err:
232     print(f"Exception processing {path}")
233     raise
234
235
236 def preprocess_arch_std_files(archpath: str) -> None:
237   """Read in all architecture standard events."""
238   global _arch_std_events
239   for item in os.scandir(archpath):
240     if item.is_file() and item.name.endswith('.json'):
241       for event in read_json_events(item.path):
242         if event.name:
243           _arch_std_events[event.name.lower()] = event
244
245
246 def print_events_table_prefix(tblname: str) -> None:
247   """Called when a new events table is started."""
248   global _close_table
249   if _close_table:
250     raise IOError('Printing table prefix but last table has no suffix')
251   _args.output_file.write(f'static const struct pmu_event {tblname}[] = {{\n')
252   _close_table = True
253
254
255 def print_events_table_entries(item: os.DirEntry, topic: str) -> None:
256   """Create contents of an events table."""
257   if not _close_table:
258     raise IOError('Table entries missing prefix')
259   for event in read_json_events(item.path):
260     _args.output_file.write(event.to_c_string(topic))
261
262
263 def print_events_table_suffix() -> None:
264   """Optionally close events table."""
265   global _close_table
266   if _close_table:
267     _args.output_file.write("""{
268 \t.name = 0,
269 \t.event = 0,
270 \t.desc = 0,
271 },
272 };
273 """)
274   _close_table = False
275
276
277 def process_one_file(parents: Sequence[str], item: os.DirEntry) -> None:
278   """Process a JSON file during the main walk."""
279   global _sys_event_tables
280
281   def get_topic(topic: str) -> str:
282     return removesuffix(topic, '.json').replace('-', ' ')
283
284   def is_leaf_dir(path: str) -> bool:
285     for item in os.scandir(path):
286       if item.is_dir():
287         return False
288     return True
289
290   # model directory, reset topic
291   if item.is_dir() and is_leaf_dir(item.path):
292     print_events_table_suffix()
293
294     tblname = file_name_to_table_name(parents, item.name)
295     if item.name == 'sys':
296       _sys_event_tables.append(tblname)
297     print_events_table_prefix(tblname)
298     return
299
300   # base dir or too deep
301   level = len(parents)
302   if level == 0 or level > 4:
303     return
304
305   # Ignore other directories. If the file name does not have a .json
306   # extension, ignore it. It could be a readme.txt for instance.
307   if not item.is_file() or not item.name.endswith('.json'):
308     return
309
310   print_events_table_entries(item, get_topic(item.name))
311
312
313 def print_mapping_table(archs: Sequence[str]) -> None:
314   """Read the mapfile and generate the struct from cpuid string to event table."""
315   _args.output_file.write('const struct pmu_events_map pmu_events_map[] = {\n')
316   for arch in archs:
317     if arch == 'test':
318       _args.output_file.write("""{
319 \t.arch = "testarch",
320 \t.cpuid = "testcpu",
321 \t.table = pme_test_soc_cpu,
322 },
323 """)
324     else:
325       with open(f'{_args.starting_dir}/{arch}/mapfile.csv') as csvfile:
326         table = csv.reader(csvfile)
327         first = True
328         for row in table:
329           # Skip the first row or any row beginning with #.
330           if not first and len(row) > 0 and not row[0].startswith('#'):
331             tblname = file_name_to_table_name([], row[2].replace('/', '_'))
332             cpuid = row[0].replace('\\', '\\\\')
333             _args.output_file.write(f"""{{
334 \t.arch = "{arch}",
335 \t.cpuid = "{cpuid}",
336 \t.table = {tblname}
337 }},
338 """)
339           first = False
340
341   _args.output_file.write("""{
342 \t.arch = 0,
343 \t.cpuid = 0,
344 \t.table = 0,
345 }
346 };
347 """)
348
349
350 def print_system_mapping_table() -> None:
351   """C struct mapping table array for tables from /sys directories."""
352   _args.output_file.write(
353       '\nconst struct pmu_sys_events pmu_sys_event_tables[] = {\n')
354   for tblname in _sys_event_tables:
355     _args.output_file.write(f"""\t{{
356 \t\t.table = {tblname},
357 \t\t.name = \"{tblname}\",
358 \t}},
359 """)
360   _args.output_file.write("""\t{
361 \t\t.table = 0
362 \t},
363 };
364 """)
365
366
367 def main() -> None:
368   global _args
369
370   def dir_path(path: str) -> str:
371     """Validate path is a directory for argparse."""
372     if os.path.isdir(path):
373       return path
374     raise argparse.ArgumentTypeError(f'\'{path}\' is not a valid directory')
375
376   def ftw(path: str, parents: Sequence[str],
377           action: Callable[[Sequence[str], os.DirEntry], None]) -> None:
378     """Replicate the directory/file walking behavior of C's file tree walk."""
379     for item in os.scandir(path):
380       action(parents, item)
381       if item.is_dir():
382         ftw(item.path, parents + [item.name], action)
383
384   ap = argparse.ArgumentParser()
385   ap.add_argument('arch', help='Architecture name like x86')
386   ap.add_argument(
387       'starting_dir',
388       type=dir_path,
389       help='Root of tree containing architecture directories containing json files'
390   )
391   ap.add_argument(
392       'output_file', type=argparse.FileType('w'), nargs='?', default=sys.stdout)
393   _args = ap.parse_args()
394
395   _args.output_file.write("#include \"pmu-events/pmu-events.h\"\n")
396   archs = []
397   for item in os.scandir(_args.starting_dir):
398     if not item.is_dir():
399       continue
400     if item.name == _args.arch or _args.arch == 'all' or item.name == 'test':
401       archs.append(item.name)
402
403   if len(archs) < 2:
404     raise IOError(f'Missing architecture directory \'{_args.arch}\'')
405
406   archs.sort()
407   for arch in archs:
408     arch_path = f'{_args.starting_dir}/{arch}'
409     preprocess_arch_std_files(arch_path)
410     ftw(arch_path, [], process_one_file)
411     print_events_table_suffix()
412
413   print_mapping_table(archs)
414   print_system_mapping_table()
415
416
417 if __name__ == '__main__':
418   main()