OSDN Git Service

Merge tag 'perf-urgent-2023-09-10' of git://git.kernel.org/pub/scm/linux/kernel/git...
[tomoyo/tomoyo-test1.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 from functools import lru_cache
7 import json
8 import metric
9 import os
10 import sys
11 from typing import (Callable, Dict, Optional, Sequence, Set, Tuple)
12 import 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
63 def 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
72 def 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
82 def 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
100 class 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
198 class 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 '0' if x == 0 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           'L3PMC': 'amd_l3',
288           'DFPMC': 'amd_df',
289           'cpu_core': 'cpu_core',
290           'cpu_atom': 'cpu_atom',
291           'ali_drw': 'ali_drw',
292       }
293       return table[unit] if unit in table else f'uncore_{unit.lower()}'
294
295     eventcode = 0
296     if 'EventCode' in jd:
297       eventcode = int(jd['EventCode'].split(',', 1)[0], 0)
298     if 'ExtSel' in jd:
299       eventcode |= int(jd['ExtSel']) << 8
300     configcode = int(jd['ConfigCode'], 0) if 'ConfigCode' in jd else None
301     self.name = jd['EventName'].lower() if 'EventName' in jd else None
302     self.topic = ''
303     self.compat = jd.get('Compat')
304     self.desc = fixdesc(jd.get('BriefDescription'))
305     self.long_desc = fixdesc(jd.get('PublicDescription'))
306     precise = jd.get('PEBS')
307     msr = lookup_msr(jd.get('MSRIndex'))
308     msrval = jd.get('MSRValue')
309     extra_desc = ''
310     if 'Data_LA' in jd:
311       extra_desc += '  Supports address when precise'
312       if 'Errata' in jd:
313         extra_desc += '.'
314     if 'Errata' in jd:
315       extra_desc += '  Spec update: ' + jd['Errata']
316     self.pmu = unit_to_pmu(jd.get('Unit'))
317     filter = jd.get('Filter')
318     self.unit = jd.get('ScaleUnit')
319     self.perpkg = jd.get('PerPkg')
320     self.aggr_mode = convert_aggr_mode(jd.get('AggregationMode'))
321     self.deprecated = jd.get('Deprecated')
322     self.metric_name = jd.get('MetricName')
323     self.metric_group = jd.get('MetricGroup')
324     self.metricgroup_no_group = jd.get('MetricgroupNoGroup')
325     self.default_metricgroup_name = jd.get('DefaultMetricgroupName')
326     self.event_grouping = convert_metric_constraint(jd.get('MetricConstraint'))
327     self.metric_expr = None
328     if 'MetricExpr' in jd:
329       self.metric_expr = metric.ParsePerfJson(jd['MetricExpr']).Simplify()
330     # Note, the metric formula for the threshold isn't parsed as the &
331     # and > have incorrect precedence.
332     self.metric_threshold = jd.get('MetricThreshold')
333
334     arch_std = jd.get('ArchStdEvent')
335     if precise and self.desc and '(Precise Event)' not in self.desc:
336       extra_desc += ' (Must be precise)' if precise == '2' else (' (Precise '
337                                                                  'event)')
338     event = f'config={llx(configcode)}' if configcode is not None else f'event={llx(eventcode)}'
339     event_fields = [
340         ('AnyThread', 'any='),
341         ('PortMask', 'ch_mask='),
342         ('CounterMask', 'cmask='),
343         ('EdgeDetect', 'edge='),
344         ('FCMask', 'fc_mask='),
345         ('Invert', 'inv='),
346         ('SampleAfterValue', 'period='),
347         ('UMask', 'umask='),
348     ]
349     for key, value in event_fields:
350       if key in jd and jd[key] != '0':
351         event += ',' + value + jd[key]
352     if filter:
353       event += f',{filter}'
354     if msr:
355       event += f',{msr}{msrval}'
356     if self.desc and extra_desc:
357       self.desc += extra_desc
358     if self.long_desc and extra_desc:
359       self.long_desc += extra_desc
360     if arch_std:
361       if arch_std.lower() in _arch_std_events:
362         event = _arch_std_events[arch_std.lower()].event
363         # Copy from the architecture standard event to self for undefined fields.
364         for attr, value in _arch_std_events[arch_std.lower()].__dict__.items():
365           if hasattr(self, attr) and not getattr(self, attr):
366             setattr(self, attr, value)
367       else:
368         raise argparse.ArgumentTypeError('Cannot find arch std event:', arch_std)
369
370     self.event = real_event(self.name, event)
371
372   def __repr__(self) -> str:
373     """String representation primarily for debugging."""
374     s = '{\n'
375     for attr, value in self.__dict__.items():
376       if value:
377         s += f'\t{attr} = {value},\n'
378     return s + '}'
379
380   def build_c_string(self, metric: bool) -> str:
381     s = ''
382     for attr in _json_metric_attributes if metric else _json_event_attributes:
383       x = getattr(self, attr)
384       if metric and x and attr == 'metric_expr':
385         # Convert parsed metric expressions into a string. Slashes
386         # must be doubled in the file.
387         x = x.ToPerfJson().replace('\\', '\\\\')
388       if metric and x and attr == 'metric_threshold':
389         x = x.replace('\\', '\\\\')
390       if attr in _json_enum_attributes:
391         s += x if x else '0'
392       else:
393         s += f'{x}\\000' if x else '\\000'
394     return s
395
396   def to_c_string(self, metric: bool) -> str:
397     """Representation of the event as a C struct initializer."""
398
399     s = self.build_c_string(metric)
400     return f'{{ { _bcs.offsets[s] } }}, /* {s} */\n'
401
402
403 @lru_cache(maxsize=None)
404 def read_json_events(path: str, topic: str) -> Sequence[JsonEvent]:
405   """Read json events from the specified file."""
406   try:
407     events = json.load(open(path), object_hook=JsonEvent)
408   except BaseException as err:
409     print(f"Exception processing {path}")
410     raise
411   metrics: list[Tuple[str, str, metric.Expression]] = []
412   for event in events:
413     event.topic = topic
414     if event.metric_name and '-' not in event.metric_name:
415       metrics.append((event.pmu, event.metric_name, event.metric_expr))
416   updates = metric.RewriteMetricsInTermsOfOthers(metrics)
417   if updates:
418     for event in events:
419       if event.metric_name in updates:
420         # print(f'Updated {event.metric_name} from\n"{event.metric_expr}"\n'
421         #       f'to\n"{updates[event.metric_name]}"')
422         event.metric_expr = updates[event.metric_name]
423
424   return events
425
426 def preprocess_arch_std_files(archpath: str) -> None:
427   """Read in all architecture standard events."""
428   global _arch_std_events
429   for item in os.scandir(archpath):
430     if item.is_file() and item.name.endswith('.json'):
431       for event in read_json_events(item.path, topic=''):
432         if event.name:
433           _arch_std_events[event.name.lower()] = event
434         if event.metric_name:
435           _arch_std_events[event.metric_name.lower()] = event
436
437
438 def add_events_table_entries(item: os.DirEntry, topic: str) -> None:
439   """Add contents of file to _pending_events table."""
440   for e in read_json_events(item.path, topic):
441     if e.name:
442       _pending_events.append(e)
443     if e.metric_name:
444       _pending_metrics.append(e)
445
446
447 def print_pending_events() -> None:
448   """Optionally close events table."""
449
450   def event_cmp_key(j: JsonEvent) -> Tuple[str, str, bool, str, str]:
451     def fix_none(s: Optional[str]) -> str:
452       if s is None:
453         return ''
454       return s
455
456     return (fix_none(j.pmu).replace(',','_'), fix_none(j.name), j.desc is not None, fix_none(j.topic),
457             fix_none(j.metric_name))
458
459   global _pending_events
460   if not _pending_events:
461     return
462
463   global _pending_events_tblname
464   if _pending_events_tblname.endswith('_sys'):
465     global _sys_event_tables
466     _sys_event_tables.append(_pending_events_tblname)
467   else:
468     global event_tables
469     _event_tables.append(_pending_events_tblname)
470
471   first = True
472   last_pmu = None
473   pmus = set()
474   for event in sorted(_pending_events, key=event_cmp_key):
475     if event.pmu != last_pmu:
476       if not first:
477         _args.output_file.write('};\n')
478       pmu_name = event.pmu.replace(',', '_')
479       _args.output_file.write(
480           f'static const struct compact_pmu_event {_pending_events_tblname}_{pmu_name}[] = {{\n')
481       first = False
482       last_pmu = event.pmu
483       pmus.add((event.pmu, pmu_name))
484
485     _args.output_file.write(event.to_c_string(metric=False))
486   _pending_events = []
487
488   _args.output_file.write(f"""
489 }};
490
491 const struct pmu_table_entry {_pending_events_tblname}[] = {{
492 """)
493   for (pmu, tbl_pmu) in sorted(pmus):
494     pmu_name = f"{pmu}\\000"
495     _args.output_file.write(f"""{{
496      .entries = {_pending_events_tblname}_{tbl_pmu},
497      .num_entries = ARRAY_SIZE({_pending_events_tblname}_{tbl_pmu}),
498      .pmu_name = {{ {_bcs.offsets[pmu_name]} /* {pmu_name} */ }},
499 }},
500 """)
501   _args.output_file.write('};\n\n')
502
503 def print_pending_metrics() -> None:
504   """Optionally close metrics table."""
505
506   def metric_cmp_key(j: JsonEvent) -> Tuple[bool, str, str]:
507     def fix_none(s: Optional[str]) -> str:
508       if s is None:
509         return ''
510       return s
511
512     return (j.desc is not None, fix_none(j.pmu), fix_none(j.metric_name))
513
514   global _pending_metrics
515   if not _pending_metrics:
516     return
517
518   global _pending_metrics_tblname
519   if _pending_metrics_tblname.endswith('_sys'):
520     global _sys_metric_tables
521     _sys_metric_tables.append(_pending_metrics_tblname)
522   else:
523     global metric_tables
524     _metric_tables.append(_pending_metrics_tblname)
525
526   first = True
527   last_pmu = None
528   pmus = set()
529   for metric in sorted(_pending_metrics, key=metric_cmp_key):
530     if metric.pmu != last_pmu:
531       if not first:
532         _args.output_file.write('};\n')
533       pmu_name = metric.pmu.replace(',', '_')
534       _args.output_file.write(
535           f'static const struct compact_pmu_event {_pending_metrics_tblname}_{pmu_name}[] = {{\n')
536       first = False
537       last_pmu = metric.pmu
538       pmus.add((metric.pmu, pmu_name))
539
540     _args.output_file.write(metric.to_c_string(metric=True))
541   _pending_metrics = []
542
543   _args.output_file.write(f"""
544 }};
545
546 const struct pmu_table_entry {_pending_metrics_tblname}[] = {{
547 """)
548   for (pmu, tbl_pmu) in sorted(pmus):
549     pmu_name = f"{pmu}\\000"
550     _args.output_file.write(f"""{{
551      .entries = {_pending_metrics_tblname}_{tbl_pmu},
552      .num_entries = ARRAY_SIZE({_pending_metrics_tblname}_{tbl_pmu}),
553      .pmu_name = {{ {_bcs.offsets[pmu_name]} /* {pmu_name} */ }},
554 }},
555 """)
556   _args.output_file.write('};\n\n')
557
558 def get_topic(topic: str) -> str:
559   if topic.endswith('metrics.json'):
560     return 'metrics'
561   return removesuffix(topic, '.json').replace('-', ' ')
562
563 def preprocess_one_file(parents: Sequence[str], item: os.DirEntry) -> None:
564
565   if item.is_dir():
566     return
567
568   # base dir or too deep
569   level = len(parents)
570   if level == 0 or level > 4:
571     return
572
573   # Ignore other directories. If the file name does not have a .json
574   # extension, ignore it. It could be a readme.txt for instance.
575   if not item.is_file() or not item.name.endswith('.json'):
576     return
577
578   if item.name == 'metricgroups.json':
579     metricgroup_descriptions = json.load(open(item.path))
580     for mgroup in metricgroup_descriptions:
581       assert len(mgroup) > 1, parents
582       description = f"{metricgroup_descriptions[mgroup]}\\000"
583       mgroup = f"{mgroup}\\000"
584       _bcs.add(mgroup, metric=True)
585       _bcs.add(description, metric=True)
586       _metricgroups[mgroup] = description
587     return
588
589   topic = get_topic(item.name)
590   for event in read_json_events(item.path, topic):
591     pmu_name = f"{event.pmu}\\000"
592     if event.name:
593       _bcs.add(pmu_name, metric=False)
594       _bcs.add(event.build_c_string(metric=False), metric=False)
595     if event.metric_name:
596       _bcs.add(pmu_name, metric=True)
597       _bcs.add(event.build_c_string(metric=True), metric=True)
598
599 def process_one_file(parents: Sequence[str], item: os.DirEntry) -> None:
600   """Process a JSON file during the main walk."""
601   def is_leaf_dir(path: str) -> bool:
602     for item in os.scandir(path):
603       if item.is_dir():
604         return False
605     return True
606
607   # model directory, reset topic
608   if item.is_dir() and is_leaf_dir(item.path):
609     print_pending_events()
610     print_pending_metrics()
611
612     global _pending_events_tblname
613     _pending_events_tblname = file_name_to_table_name('pmu_events_', parents, item.name)
614     global _pending_metrics_tblname
615     _pending_metrics_tblname = file_name_to_table_name('pmu_metrics_', parents, item.name)
616
617     if item.name == 'sys':
618       _sys_event_table_to_metric_table_mapping[_pending_events_tblname] = _pending_metrics_tblname
619     return
620
621   # base dir or too deep
622   level = len(parents)
623   if level == 0 or level > 4:
624     return
625
626   # Ignore other directories. If the file name does not have a .json
627   # extension, ignore it. It could be a readme.txt for instance.
628   if not item.is_file() or not item.name.endswith('.json') or item.name == 'metricgroups.json':
629     return
630
631   add_events_table_entries(item, get_topic(item.name))
632
633
634 def print_mapping_table(archs: Sequence[str]) -> None:
635   """Read the mapfile and generate the struct from cpuid string to event table."""
636   _args.output_file.write("""
637 /* Struct used to make the PMU event table implementation opaque to callers. */
638 struct pmu_events_table {
639         const struct pmu_table_entry *pmus;
640         uint32_t num_pmus;
641 };
642
643 /* Struct used to make the PMU metric table implementation opaque to callers. */
644 struct pmu_metrics_table {
645         const struct pmu_table_entry *pmus;
646         uint32_t num_pmus;
647 };
648
649 /*
650  * Map a CPU to its table of PMU events. The CPU is identified by the
651  * cpuid field, which is an arch-specific identifier for the CPU.
652  * The identifier specified in tools/perf/pmu-events/arch/xxx/mapfile
653  * must match the get_cpuid_str() in tools/perf/arch/xxx/util/header.c)
654  *
655  * The  cpuid can contain any character other than the comma.
656  */
657 struct pmu_events_map {
658         const char *arch;
659         const char *cpuid;
660         struct pmu_events_table event_table;
661         struct pmu_metrics_table metric_table;
662 };
663
664 /*
665  * Global table mapping each known CPU for the architecture to its
666  * table of PMU events.
667  */
668 const struct pmu_events_map pmu_events_map[] = {
669 """)
670   for arch in archs:
671     if arch == 'test':
672       _args.output_file.write("""{
673 \t.arch = "testarch",
674 \t.cpuid = "testcpu",
675 \t.event_table = {
676 \t\t.pmus = pmu_events__test_soc_cpu,
677 \t\t.num_pmus = ARRAY_SIZE(pmu_events__test_soc_cpu),
678 \t},
679 \t.metric_table = {
680 \t\t.pmus = pmu_metrics__test_soc_cpu,
681 \t\t.num_pmus = ARRAY_SIZE(pmu_metrics__test_soc_cpu),
682 \t}
683 },
684 """)
685     else:
686       with open(f'{_args.starting_dir}/{arch}/mapfile.csv') as csvfile:
687         table = csv.reader(csvfile)
688         first = True
689         for row in table:
690           # Skip the first row or any row beginning with #.
691           if not first and len(row) > 0 and not row[0].startswith('#'):
692             event_tblname = file_name_to_table_name('pmu_events_', [], row[2].replace('/', '_'))
693             if event_tblname in _event_tables:
694               event_size = f'ARRAY_SIZE({event_tblname})'
695             else:
696               event_tblname = 'NULL'
697               event_size = '0'
698             metric_tblname = file_name_to_table_name('pmu_metrics_', [], row[2].replace('/', '_'))
699             if metric_tblname in _metric_tables:
700               metric_size = f'ARRAY_SIZE({metric_tblname})'
701             else:
702               metric_tblname = 'NULL'
703               metric_size = '0'
704             if event_size == '0' and metric_size == '0':
705               continue
706             cpuid = row[0].replace('\\', '\\\\')
707             _args.output_file.write(f"""{{
708 \t.arch = "{arch}",
709 \t.cpuid = "{cpuid}",
710 \t.event_table = {{
711 \t\t.pmus = {event_tblname},
712 \t\t.num_pmus = {event_size}
713 \t}},
714 \t.metric_table = {{
715 \t\t.pmus = {metric_tblname},
716 \t\t.num_pmus = {metric_size}
717 \t}}
718 }},
719 """)
720           first = False
721
722   _args.output_file.write("""{
723 \t.arch = 0,
724 \t.cpuid = 0,
725 \t.event_table = { 0, 0 },
726 \t.metric_table = { 0, 0 },
727 }
728 };
729 """)
730
731
732 def print_system_mapping_table() -> None:
733   """C struct mapping table array for tables from /sys directories."""
734   _args.output_file.write("""
735 struct pmu_sys_events {
736 \tconst char *name;
737 \tstruct pmu_events_table event_table;
738 \tstruct pmu_metrics_table metric_table;
739 };
740
741 static const struct pmu_sys_events pmu_sys_event_tables[] = {
742 """)
743   printed_metric_tables = []
744   for tblname in _sys_event_tables:
745     _args.output_file.write(f"""\t{{
746 \t\t.event_table = {{
747 \t\t\t.pmus = {tblname},
748 \t\t\t.num_pmus = ARRAY_SIZE({tblname})
749 \t\t}},""")
750     metric_tblname = _sys_event_table_to_metric_table_mapping[tblname]
751     if metric_tblname in _sys_metric_tables:
752       _args.output_file.write(f"""
753 \t\t.metric_table = {{
754 \t\t\t.pmus = {metric_tblname},
755 \t\t\t.num_pmus = ARRAY_SIZE({metric_tblname})
756 \t\t}},""")
757       printed_metric_tables.append(metric_tblname)
758     _args.output_file.write(f"""
759 \t\t.name = \"{tblname}\",
760 \t}},
761 """)
762   for tblname in _sys_metric_tables:
763     if tblname in printed_metric_tables:
764       continue
765     _args.output_file.write(f"""\t{{
766 \t\t.metric_table = {{
767 \t\t\t.entries = {tblname},
768 \t\t\t.length = ARRAY_SIZE({tblname})
769 \t\t}},
770 \t\t.name = \"{tblname}\",
771 \t}},
772 """)
773   _args.output_file.write("""\t{
774 \t\t.event_table = { 0, 0 },
775 \t\t.metric_table = { 0, 0 },
776 \t},
777 };
778
779 static void decompress_event(int offset, struct pmu_event *pe)
780 {
781 \tconst char *p = &big_c_string[offset];
782 """)
783   for attr in _json_event_attributes:
784     _args.output_file.write(f'\n\tpe->{attr} = ')
785     if attr in _json_enum_attributes:
786       _args.output_file.write("*p - '0';\n")
787     else:
788       _args.output_file.write("(*p == '\\0' ? NULL : p);\n")
789     if attr == _json_event_attributes[-1]:
790       continue
791     if attr in _json_enum_attributes:
792       _args.output_file.write('\tp++;')
793     else:
794       _args.output_file.write('\twhile (*p++);')
795   _args.output_file.write("""}
796
797 static void decompress_metric(int offset, struct pmu_metric *pm)
798 {
799 \tconst char *p = &big_c_string[offset];
800 """)
801   for attr in _json_metric_attributes:
802     _args.output_file.write(f'\n\tpm->{attr} = ')
803     if attr in _json_enum_attributes:
804       _args.output_file.write("*p - '0';\n")
805     else:
806       _args.output_file.write("(*p == '\\0' ? NULL : p);\n")
807     if attr == _json_metric_attributes[-1]:
808       continue
809     if attr in _json_enum_attributes:
810       _args.output_file.write('\tp++;')
811     else:
812       _args.output_file.write('\twhile (*p++);')
813   _args.output_file.write("""}
814
815 static int pmu_events_table__for_each_event_pmu(const struct pmu_events_table *table,
816                                                 const struct pmu_table_entry *pmu,
817                                                 pmu_event_iter_fn fn,
818                                                 void *data)
819 {
820         int ret;
821         struct pmu_event pe = {
822                 .pmu = &big_c_string[pmu->pmu_name.offset],
823         };
824
825         for (uint32_t i = 0; i < pmu->num_entries; i++) {
826                 decompress_event(pmu->entries[i].offset, &pe);
827                 if (!pe.name)
828                         continue;
829                 ret = fn(&pe, table, data);
830                 if (ret)
831                         return ret;
832         }
833         return 0;
834  }
835
836 static int pmu_events_table__find_event_pmu(const struct pmu_events_table *table,
837                                             const struct pmu_table_entry *pmu,
838                                             const char *name,
839                                             pmu_event_iter_fn fn,
840                                             void *data)
841 {
842         struct pmu_event pe = {
843                 .pmu = &big_c_string[pmu->pmu_name.offset],
844         };
845         int low = 0, high = pmu->num_entries - 1;
846
847         while (low <= high) {
848                 int cmp, mid = (low + high) / 2;
849
850                 decompress_event(pmu->entries[mid].offset, &pe);
851
852                 if (!pe.name && !name)
853                         goto do_call;
854
855                 if (!pe.name && name) {
856                         low = mid + 1;
857                         continue;
858                 }
859                 if (pe.name && !name) {
860                         high = mid - 1;
861                         continue;
862                 }
863
864                 cmp = strcasecmp(pe.name, name);
865                 if (cmp < 0) {
866                         low = mid + 1;
867                         continue;
868                 }
869                 if (cmp > 0) {
870                         high = mid - 1;
871                         continue;
872                 }
873   do_call:
874                 return fn ? fn(&pe, table, data) : 0;
875         }
876         return -1000;
877 }
878
879 int pmu_events_table__for_each_event(const struct pmu_events_table *table,
880                                     struct perf_pmu *pmu,
881                                     pmu_event_iter_fn fn,
882                                     void *data)
883 {
884         for (size_t i = 0; i < table->num_pmus; i++) {
885                 const struct pmu_table_entry *table_pmu = &table->pmus[i];
886                 const char *pmu_name = &big_c_string[table_pmu->pmu_name.offset];
887                 int ret;
888
889                 if (pmu && !pmu__name_match(pmu, pmu_name))
890                         continue;
891
892                 ret = pmu_events_table__for_each_event_pmu(table, table_pmu, fn, data);
893                 if (pmu || ret)
894                         return ret;
895         }
896         return 0;
897 }
898
899 int pmu_events_table__find_event(const struct pmu_events_table *table,
900                                  struct perf_pmu *pmu,
901                                  const char *name,
902                                  pmu_event_iter_fn fn,
903                                  void *data)
904 {
905         for (size_t i = 0; i < table->num_pmus; i++) {
906                 const struct pmu_table_entry *table_pmu = &table->pmus[i];
907                 const char *pmu_name = &big_c_string[table_pmu->pmu_name.offset];
908                 int ret;
909
910                 if (!pmu__name_match(pmu, pmu_name))
911                         continue;
912
913                 ret = pmu_events_table__find_event_pmu(table, table_pmu, name, fn, data);
914                 if (ret != -1000)
915                         return ret;
916         }
917         return -1000;
918 }
919
920 size_t pmu_events_table__num_events(const struct pmu_events_table *table,
921                                     struct perf_pmu *pmu)
922 {
923         size_t count = 0;
924
925         for (size_t i = 0; i < table->num_pmus; i++) {
926                 const struct pmu_table_entry *table_pmu = &table->pmus[i];
927                 const char *pmu_name = &big_c_string[table_pmu->pmu_name.offset];
928
929                 if (pmu__name_match(pmu, pmu_name))
930                         count += table_pmu->num_entries;
931         }
932         return count;
933 }
934
935 static int pmu_metrics_table__for_each_metric_pmu(const struct pmu_metrics_table *table,
936                                                 const struct pmu_table_entry *pmu,
937                                                 pmu_metric_iter_fn fn,
938                                                 void *data)
939 {
940         int ret;
941         struct pmu_metric pm = {
942                 .pmu = &big_c_string[pmu->pmu_name.offset],
943         };
944
945         for (uint32_t i = 0; i < pmu->num_entries; i++) {
946                 decompress_metric(pmu->entries[i].offset, &pm);
947                 if (!pm.metric_expr)
948                         continue;
949                 ret = fn(&pm, table, data);
950                 if (ret)
951                         return ret;
952         }
953         return 0;
954 }
955
956 int pmu_metrics_table__for_each_metric(const struct pmu_metrics_table *table,
957                                      pmu_metric_iter_fn fn,
958                                      void *data)
959 {
960         for (size_t i = 0; i < table->num_pmus; i++) {
961                 int ret = pmu_metrics_table__for_each_metric_pmu(table, &table->pmus[i],
962                                                                  fn, data);
963
964                 if (ret)
965                         return ret;
966         }
967         return 0;
968 }
969
970 const struct pmu_events_table *perf_pmu__find_events_table(struct perf_pmu *pmu)
971 {
972         const struct pmu_events_table *table = NULL;
973         char *cpuid = perf_pmu__getcpuid(pmu);
974         size_t i;
975
976         /* on some platforms which uses cpus map, cpuid can be NULL for
977          * PMUs other than CORE PMUs.
978          */
979         if (!cpuid)
980                 return NULL;
981
982         i = 0;
983         for (;;) {
984                 const struct pmu_events_map *map = &pmu_events_map[i++];
985                 if (!map->arch)
986                         break;
987
988                 if (!strcmp_cpuid_str(map->cpuid, cpuid)) {
989                         table = &map->event_table;
990                         break;
991                 }
992         }
993         free(cpuid);
994         if (!pmu)
995                 return table;
996
997         for (i = 0; i < table->num_pmus; i++) {
998                 const struct pmu_table_entry *table_pmu = &table->pmus[i];
999                 const char *pmu_name = &big_c_string[table_pmu->pmu_name.offset];
1000
1001                 if (pmu__name_match(pmu, pmu_name))
1002                         return table;
1003         }
1004         return NULL;
1005 }
1006
1007 const struct pmu_metrics_table *perf_pmu__find_metrics_table(struct perf_pmu *pmu)
1008 {
1009         const struct pmu_metrics_table *table = NULL;
1010         char *cpuid = perf_pmu__getcpuid(pmu);
1011         int i;
1012
1013         /* on some platforms which uses cpus map, cpuid can be NULL for
1014          * PMUs other than CORE PMUs.
1015          */
1016         if (!cpuid)
1017                 return NULL;
1018
1019         i = 0;
1020         for (;;) {
1021                 const struct pmu_events_map *map = &pmu_events_map[i++];
1022                 if (!map->arch)
1023                         break;
1024
1025                 if (!strcmp_cpuid_str(map->cpuid, cpuid)) {
1026                         table = &map->metric_table;
1027                         break;
1028                 }
1029         }
1030         free(cpuid);
1031         return table;
1032 }
1033
1034 const struct pmu_events_table *find_core_events_table(const char *arch, const char *cpuid)
1035 {
1036         for (const struct pmu_events_map *tables = &pmu_events_map[0];
1037              tables->arch;
1038              tables++) {
1039                 if (!strcmp(tables->arch, arch) && !strcmp_cpuid_str(tables->cpuid, cpuid))
1040                         return &tables->event_table;
1041         }
1042         return NULL;
1043 }
1044
1045 const struct pmu_metrics_table *find_core_metrics_table(const char *arch, const char *cpuid)
1046 {
1047         for (const struct pmu_events_map *tables = &pmu_events_map[0];
1048              tables->arch;
1049              tables++) {
1050                 if (!strcmp(tables->arch, arch) && !strcmp_cpuid_str(tables->cpuid, cpuid))
1051                         return &tables->metric_table;
1052         }
1053         return NULL;
1054 }
1055
1056 int pmu_for_each_core_event(pmu_event_iter_fn fn, void *data)
1057 {
1058         for (const struct pmu_events_map *tables = &pmu_events_map[0];
1059              tables->arch;
1060              tables++) {
1061                 int ret = pmu_events_table__for_each_event(&tables->event_table,
1062                                                            /*pmu=*/ NULL, fn, data);
1063
1064                 if (ret)
1065                         return ret;
1066         }
1067         return 0;
1068 }
1069
1070 int pmu_for_each_core_metric(pmu_metric_iter_fn fn, void *data)
1071 {
1072         for (const struct pmu_events_map *tables = &pmu_events_map[0];
1073              tables->arch;
1074              tables++) {
1075                 int ret = pmu_metrics_table__for_each_metric(&tables->metric_table, fn, data);
1076
1077                 if (ret)
1078                         return ret;
1079         }
1080         return 0;
1081 }
1082
1083 const struct pmu_events_table *find_sys_events_table(const char *name)
1084 {
1085         for (const struct pmu_sys_events *tables = &pmu_sys_event_tables[0];
1086              tables->name;
1087              tables++) {
1088                 if (!strcmp(tables->name, name))
1089                         return &tables->event_table;
1090         }
1091         return NULL;
1092 }
1093
1094 int pmu_for_each_sys_event(pmu_event_iter_fn fn, void *data)
1095 {
1096         for (const struct pmu_sys_events *tables = &pmu_sys_event_tables[0];
1097              tables->name;
1098              tables++) {
1099                 int ret = pmu_events_table__for_each_event(&tables->event_table,
1100                                                            /*pmu=*/ NULL, fn, data);
1101
1102                 if (ret)
1103                         return ret;
1104         }
1105         return 0;
1106 }
1107
1108 int pmu_for_each_sys_metric(pmu_metric_iter_fn fn, void *data)
1109 {
1110         for (const struct pmu_sys_events *tables = &pmu_sys_event_tables[0];
1111              tables->name;
1112              tables++) {
1113                 int ret = pmu_metrics_table__for_each_metric(&tables->metric_table, fn, data);
1114
1115                 if (ret)
1116                         return ret;
1117         }
1118         return 0;
1119 }
1120 """)
1121
1122 def print_metricgroups() -> None:
1123   _args.output_file.write("""
1124 static const int metricgroups[][2] = {
1125 """)
1126   for mgroup in sorted(_metricgroups):
1127     description = _metricgroups[mgroup]
1128     _args.output_file.write(
1129         f'\t{{ {_bcs.offsets[mgroup]}, {_bcs.offsets[description]} }}, /* {mgroup} => {description} */\n'
1130     )
1131   _args.output_file.write("""
1132 };
1133
1134 const char *describe_metricgroup(const char *group)
1135 {
1136         int low = 0, high = (int)ARRAY_SIZE(metricgroups) - 1;
1137
1138         while (low <= high) {
1139                 int mid = (low + high) / 2;
1140                 const char *mgroup = &big_c_string[metricgroups[mid][0]];
1141                 int cmp = strcmp(mgroup, group);
1142
1143                 if (cmp == 0) {
1144                         return &big_c_string[metricgroups[mid][1]];
1145                 } else if (cmp < 0) {
1146                         low = mid + 1;
1147                 } else {
1148                         high = mid - 1;
1149                 }
1150         }
1151         return NULL;
1152 }
1153 """)
1154
1155 def main() -> None:
1156   global _args
1157
1158   def dir_path(path: str) -> str:
1159     """Validate path is a directory for argparse."""
1160     if os.path.isdir(path):
1161       return path
1162     raise argparse.ArgumentTypeError(f'\'{path}\' is not a valid directory')
1163
1164   def ftw(path: str, parents: Sequence[str],
1165           action: Callable[[Sequence[str], os.DirEntry], None]) -> None:
1166     """Replicate the directory/file walking behavior of C's file tree walk."""
1167     for item in sorted(os.scandir(path), key=lambda e: e.name):
1168       if _args.model != 'all' and item.is_dir():
1169         # Check if the model matches one in _args.model.
1170         if len(parents) == _args.model.split(',')[0].count('/'):
1171           # We're testing the correct directory.
1172           item_path = '/'.join(parents) + ('/' if len(parents) > 0 else '') + item.name
1173           if 'test' not in item_path and item_path not in _args.model.split(','):
1174             continue
1175       action(parents, item)
1176       if item.is_dir():
1177         ftw(item.path, parents + [item.name], action)
1178
1179   ap = argparse.ArgumentParser()
1180   ap.add_argument('arch', help='Architecture name like x86')
1181   ap.add_argument('model', help='''Select a model such as skylake to
1182 reduce the code size.  Normally set to "all". For architectures like
1183 ARM64 with an implementor/model, the model must include the implementor
1184 such as "arm/cortex-a34".''',
1185                   default='all')
1186   ap.add_argument(
1187       'starting_dir',
1188       type=dir_path,
1189       help='Root of tree containing architecture directories containing json files'
1190   )
1191   ap.add_argument(
1192       'output_file', type=argparse.FileType('w', encoding='utf-8'), nargs='?', default=sys.stdout)
1193   _args = ap.parse_args()
1194
1195   _args.output_file.write("""
1196 #include <pmu-events/pmu-events.h>
1197 #include "util/header.h"
1198 #include "util/pmu.h"
1199 #include <string.h>
1200 #include <stddef.h>
1201
1202 struct compact_pmu_event {
1203         int offset;
1204 };
1205
1206 struct pmu_table_entry {
1207         const struct compact_pmu_event *entries;
1208         uint32_t num_entries;
1209         struct compact_pmu_event pmu_name;
1210 };
1211
1212 """)
1213   archs = []
1214   for item in os.scandir(_args.starting_dir):
1215     if not item.is_dir():
1216       continue
1217     if item.name == _args.arch or _args.arch == 'all' or item.name == 'test':
1218       archs.append(item.name)
1219
1220   if len(archs) < 2:
1221     raise IOError(f'Missing architecture directory \'{_args.arch}\'')
1222
1223   archs.sort()
1224   for arch in archs:
1225     arch_path = f'{_args.starting_dir}/{arch}'
1226     preprocess_arch_std_files(arch_path)
1227     ftw(arch_path, [], preprocess_one_file)
1228
1229   _bcs.compute()
1230   _args.output_file.write('static const char *const big_c_string =\n')
1231   for s in _bcs.big_string:
1232     _args.output_file.write(s)
1233   _args.output_file.write(';\n\n')
1234   for arch in archs:
1235     arch_path = f'{_args.starting_dir}/{arch}'
1236     ftw(arch_path, [], process_one_file)
1237     print_pending_events()
1238     print_pending_metrics()
1239
1240   print_mapping_table(archs)
1241   print_system_mapping_table()
1242   print_metricgroups()
1243
1244 if __name__ == '__main__':
1245   main()