8f929dd3e06530ced92107763f6138e68cb55495
[linux-2.6-microblaze.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 # Events to write out when the table is closed
22 _pending_events = []
23
24
25 def removesuffix(s: str, suffix: str) -> str:
26   """Remove the suffix from a string
27
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.
30   """
31   return s[0:-len(suffix)] if s.endswith(suffix) else s
32
33
34 def file_name_to_table_name(parents: Sequence[str], dirname: str) -> str:
35   """Generate a C table name from directory names."""
36   tblname = 'pme'
37   for p in parents:
38     tblname += '_' + p
39   tblname += '_' + dirname
40   return tblname.replace('-', '_')
41
42
43 class JsonEvent:
44   """Representation of an event loaded from a json file dictionary."""
45
46   def __init__(self, jd: dict):
47     """Constructor passed the dictionary of parsed json values."""
48
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)
52
53     def fixdesc(s: str) -> str:
54       """Fix formatting issue for the desc string."""
55       if s is None:
56         return None
57       return removesuffix(removesuffix(removesuffix(s, '.  '),
58                                        '. '), '.').replace('\n', '\\n').replace(
59                                            '\"', '\\"').replace('\r', '\\r')
60
61     def convert_aggr_mode(aggr_mode: str) -> Optional[str]:
62       """Returns the aggr_mode_class enum value associated with the JSON string."""
63       if not aggr_mode:
64         return None
65       aggr_mode_to_enum = {
66           'PerChip': '1',
67           'PerCore': '2',
68       }
69       return aggr_mode_to_enum[aggr_mode]
70
71     def lookup_msr(num: str) -> Optional[str]:
72       """Converts the msr number, or first in a list to the appropriate event field."""
73       if not num:
74         return None
75       msrmap = {
76           0x3F6: 'ldlat=',
77           0x1A6: 'offcore_rsp=',
78           0x1A7: 'offcore_rsp=',
79           0x3F7: 'frontend=',
80       }
81       return msrmap[int(num.split(',', 1)[0], 0)]
82
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."""
85       fixed = {
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',
92       }
93       if not name:
94         return None
95       if name.lower() in fixed:
96         return fixed[name.lower()]
97       return event
98
99     def unit_to_pmu(unit: str) -> Optional[str]:
100       """Convert a JSON Unit to Linux PMU name."""
101       if not unit:
102         return None
103       # Comment brought over from jevents.c:
104       # it's not realistic to keep adding these, we need something more scalable ...
105       table = {
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',
119           'L3PMC': 'amd_l3',
120           'DFPMC': 'amd_df',
121           'cpu_core': 'cpu_core',
122           'cpu_atom': 'cpu_atom',
123       }
124       return table[unit] if unit in table else f'uncore_{unit.lower()}'
125
126     eventcode = 0
127     if 'EventCode' in jd:
128       eventcode = int(jd['EventCode'].split(',', 1)[0], 0)
129     if 'ExtSel' in jd:
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
133     self.topic = ''
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')
140     extra_desc = ''
141     if 'Data_LA' in jd:
142       extra_desc += '  Supports address when precise'
143       if 'Errata' in jd:
144         extra_desc += '.'
145     if 'Errata' in jd:
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')
157     if self.metric_expr:
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 '
162                                                                  'event)')
163     event = f'config={llx(configcode)}' if configcode is not None else f'event={llx(eventcode)}'
164     event_fields = [
165         ('AnyThread', 'any='),
166         ('PortMask', 'ch_mask='),
167         ('CounterMask', 'cmask='),
168         ('EdgeDetect', 'edge='),
169         ('FCMask', 'fc_mask='),
170         ('Invert', 'inv='),
171         ('SampleAfterValue', 'period='),
172         ('UMask', 'umask='),
173     ]
174     for key, value in event_fields:
175       if key in jd and jd[key] != '0':
176         event += ',' + value + jd[key]
177     if filter:
178       event += f',{filter}'
179     if msr:
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
185     if self.pmu:
186       if self.desc and not self.desc.endswith('. '):
187         self.desc += '. '
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)
195
196     self.event = real_event(self.name, event)
197
198   def __repr__(self) -> str:
199     """String representation primarily for debugging."""
200     s = '{\n'
201     for attr, value in self.__dict__.items():
202       if value:
203         s += f'\t{attr} = {value},\n'
204     return s + '}'
205
206   def to_c_string(self) -> str:
207     """Representation of the event as a C struct initializer."""
208
209     def attr_string(attr: str, value: str) -> str:
210       return f'\t.{attr} = \"{value}\",\n'
211
212     def str_if_present(self, attr: str) -> str:
213       if not getattr(self, attr):
214         return ''
215       return attr_string(attr, getattr(self, attr))
216
217     s = '{\n'
218     for attr in [
219         'aggr_mode', 'compat', 'deprecated', 'desc', 'event', 'long_desc',
220         'metric_constraint', 'metric_expr', 'metric_group', 'metric_name',
221         'name', 'perpkg', 'pmu', 'topic', 'unit'
222     ]:
223       s += str_if_present(self, attr)
224     s += '},\n'
225     return s
226
227
228 def read_json_events(path: str, topic: str) -> Sequence[JsonEvent]:
229   """Read json events from the specified file."""
230
231   try:
232     result = json.load(open(path), object_hook=JsonEvent)
233   except BaseException as err:
234     print(f"Exception processing {path}")
235     raise
236   for event in result:
237     event.topic = topic
238   return result
239
240
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=''):
247         if event.name:
248           _arch_std_events[event.name.lower()] = event
249
250
251 def print_events_table_prefix(tblname: str) -> None:
252   """Called when a new events table is started."""
253   global _close_table
254   if _close_table:
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')
257   _close_table = True
258
259
260 def add_events_table_entries(item: os.DirEntry, topic: str) -> None:
261   """Add contents of file to _pending_events table."""
262   if not _close_table:
263     raise IOError('Table entries missing prefix')
264   for e in read_json_events(item.path, topic):
265     _pending_events.append(e)
266
267
268 def print_events_table_suffix() -> None:
269   """Optionally close events table."""
270
271   def event_cmp_key(j: JsonEvent):
272     def fix_none(s: str):
273       if s is None:
274         return ''
275       return s
276
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))
279
280   global _close_table
281   if not _close_table:
282     return
283
284   global _pending_events
285   for event in sorted(_pending_events, key=event_cmp_key):
286     _args.output_file.write(event.to_c_string())
287     _pending_events = []
288
289   _args.output_file.write("""{
290 \t.name = 0,
291 \t.event = 0,
292 \t.desc = 0,
293 },
294 };
295 """)
296   _close_table = False
297
298
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
302
303   def get_topic(topic: str) -> str:
304     return removesuffix(topic, '.json').replace('-', ' ')
305
306   def is_leaf_dir(path: str) -> bool:
307     for item in os.scandir(path):
308       if item.is_dir():
309         return False
310     return True
311
312   # model directory, reset topic
313   if item.is_dir() and is_leaf_dir(item.path):
314     print_events_table_suffix()
315
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)
320     return
321
322   # base dir or too deep
323   level = len(parents)
324   if level == 0 or level > 4:
325     return
326
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'):
330     return
331
332   add_events_table_entries(item, get_topic(item.name))
333
334
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')
338   for arch in archs:
339     if arch == 'test':
340       _args.output_file.write("""{
341 \t.arch = "testarch",
342 \t.cpuid = "testcpu",
343 \t.table = pme_test_soc_cpu,
344 },
345 """)
346     else:
347       with open(f'{_args.starting_dir}/{arch}/mapfile.csv') as csvfile:
348         table = csv.reader(csvfile)
349         first = True
350         for row in table:
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"""{{
356 \t.arch = "{arch}",
357 \t.cpuid = "{cpuid}",
358 \t.table = {tblname}
359 }},
360 """)
361           first = False
362
363   _args.output_file.write("""{
364 \t.arch = 0,
365 \t.cpuid = 0,
366 \t.table = 0,
367 }
368 };
369 """)
370
371
372 def print_system_mapping_table() -> None:
373   """C struct mapping table array for tables from /sys directories."""
374   _args.output_file.write("""
375 struct pmu_sys_events {
376 \tconst char *name;
377 \tconst struct pmu_event *table;
378 };
379
380 static const struct pmu_sys_events pmu_sys_event_tables[] = {
381 """)
382   for tblname in _sys_event_tables:
383     _args.output_file.write(f"""\t{{
384 \t\t.table = {tblname},
385 \t\t.name = \"{tblname}\",
386 \t}},
387 """)
388   _args.output_file.write("""\t{
389 \t\t.table = 0
390 \t},
391 };
392
393 const struct pmu_event *find_sys_events_table(const char *name)
394 {
395         for (const struct pmu_sys_events *tables = &pmu_sys_event_tables[0];
396              tables->name;
397              tables++) {
398                 if (!strcmp(tables->name, name))
399                         return tables->table;
400         }
401         return NULL;
402 }
403
404 int pmu_for_each_sys_event(pmu_event_iter_fn fn, void *data)
405 {
406         for (const struct pmu_sys_events *tables = &pmu_sys_event_tables[0];
407              tables->name;
408              tables++) {
409                 for (const struct pmu_event *pe = &tables->table[0];
410                      pe->name || pe->metric_group || pe->metric_name;
411                      pe++) {
412                         int ret = fn(pe, data);
413
414                         if (ret)
415                                 return ret;
416                 }
417         }
418         return 0;
419 }
420 """)
421
422
423 def main() -> None:
424   global _args
425
426   def dir_path(path: str) -> str:
427     """Validate path is a directory for argparse."""
428     if os.path.isdir(path):
429       return path
430     raise argparse.ArgumentTypeError(f'\'{path}\' is not a valid directory')
431
432   def ftw(path: str, parents: Sequence[str],
433           action: Callable[[Sequence[str], os.DirEntry], None]) -> None:
434     """Replicate the directory/file walking behavior of C's file tree walk."""
435     for item in os.scandir(path):
436       action(parents, item)
437       if item.is_dir():
438         ftw(item.path, parents + [item.name], action)
439
440   ap = argparse.ArgumentParser()
441   ap.add_argument('arch', help='Architecture name like x86')
442   ap.add_argument(
443       'starting_dir',
444       type=dir_path,
445       help='Root of tree containing architecture directories containing json files'
446   )
447   ap.add_argument(
448       'output_file', type=argparse.FileType('w'), nargs='?', default=sys.stdout)
449   _args = ap.parse_args()
450
451   _args.output_file.write("""
452 #include "pmu-events/pmu-events.h"
453 #include <string.h>
454 #include <stddef.h>
455
456 """)
457   archs = []
458   for item in os.scandir(_args.starting_dir):
459     if not item.is_dir():
460       continue
461     if item.name == _args.arch or _args.arch == 'all' or item.name == 'test':
462       archs.append(item.name)
463
464   if len(archs) < 2:
465     raise IOError(f'Missing architecture directory \'{_args.arch}\'')
466
467   archs.sort()
468   for arch in archs:
469     arch_path = f'{_args.starting_dir}/{arch}'
470     preprocess_arch_std_files(arch_path)
471     ftw(arch_path, [], process_one_file)
472     print_events_table_suffix()
473
474   print_mapping_table(archs)
475   print_system_mapping_table()
476
477
478 if __name__ == '__main__':
479   main()