xref: /linux/tools/perf/pmu-events/jevents.py (revision 67f8bc848ee31831336bd478e57d2f993551902e)
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."""
4import argparse
5import csv
6from functools import lru_cache
7import json
8import metric
9import os
10import sys
11from typing import (Callable, Dict, Optional, Sequence, Set, Tuple)
12import collections
13
14# Global command line arguments.
15_args = None
16# List of regular event tables.
17_event_tables: list[str] = []
18# List of event tables generated from "/sys" directories.
19_sys_event_tables: list[str] = []
20# List of regular metric tables.
21_metric_tables: list[str] = []
22# List of metric tables generated from "/sys" directories.
23_sys_metric_tables: list[str] = []
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: Dict[str, "JsonEvent"] = {}
30# Events to write out when the table is closed
31_pending_events: list["JsonEvent"] = []
32# Name of events table to be written out
33_pending_events_tblname: Optional[str] = None
34# Metrics to write out when the table is closed
35_pending_metrics: list["JsonEvent"] = []
36# Name of metrics table to be written out
37_pending_metrics_tblname: Optional[str] = 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    # Retirement latency specific to Intel granite rapids currently.
51    'retirement_latency_mean', 'retirement_latency_min',
52    'retirement_latency_max',
53    # Longer things (the last won't be iterated over during decompress).
54    'long_desc'
55]
56
57# Attributes that are in pmu_metric rather than pmu_event.
58_json_metric_attributes = [
59    'metric_name', 'metric_group', 'metric_expr', 'metric_threshold',
60    'desc', 'long_desc', 'unit', 'compat', 'metricgroup_no_group',
61    'default_metricgroup_name', 'aggr_mode', 'event_grouping',
62    'default_show_events'
63]
64# Attributes that are bools or enum int values, encoded as '0', '1',...
65_json_enum_attributes = ['aggr_mode', 'deprecated', 'event_grouping', 'perpkg',
66                         'default_show_events']
67
68def removesuffix(s: str, suffix: str) -> str:
69  """Remove the suffix from a string
70
71  The removesuffix function is added to str in Python 3.9. We aim for 3.6
72  compatibility and so provide our own function here.
73  """
74  return s[0:-len(suffix)] if s.endswith(suffix) else s
75
76
77def file_name_to_table_name(prefix: str, parents: Sequence[str],
78                            dirname: str) -> str:
79  """Generate a C table name from directory names."""
80  tblname = prefix
81  for p in parents:
82    tblname += '_' + p
83  tblname += '_' + dirname
84  return tblname.replace('-', '_')
85
86
87def c_len(s: str) -> int:
88  """Return the length of s a C string
89
90  This doesn't handle all escape characters properly. It first assumes
91  all \\ are for escaping, it then adjusts as it will have over counted
92  \\. The code uses \000 rather than \0 as a terminator as an adjacent
93  number would be folded into a string of \0 (ie. "\0" + "5" doesn't
94  equal a terminator followed by the number 5 but the escape of
95  \05). The code adjusts for \000 but not properly for all octal, hex
96  or unicode values.
97  """
98  try:
99    utf = s.encode(encoding='utf-8',errors='strict')
100  except:
101    print(f'broken string {s}')
102    raise
103  return len(utf) - utf.count(b'\\') + utf.count(b'\\\\') - (utf.count(b'\\000') * 2)
104
105class BigCString:
106  """A class to hold many strings concatenated together.
107
108  Generating a large number of stand-alone C strings creates a large
109  number of relocations in position independent code. The BigCString
110  is a helper for this case. It builds a single string which within it
111  are all the other C strings (to avoid memory issues the string
112  itself is held as a list of strings). The offsets within the big
113  string are recorded and when stored to disk these don't need
114  relocation. To reduce the size of the string further, identical
115  strings are merged. If a longer string ends-with the same value as a
116  shorter string, these entries are also merged.
117  """
118  strings: Set[str]
119  big_string: Sequence[str]
120  offsets: Dict[str, int]
121  insert_number: int
122  insert_point: Dict[str, int]
123  metrics: Set[str]
124
125  def __init__(self):
126    self.strings = set()
127    self.insert_number = 0;
128    self.insert_point = {}
129    self.metrics = set()
130
131  def add(self, s: str, metric: bool) -> None:
132    """Called to add to the big string."""
133    if s not in self.strings:
134      self.strings.add(s)
135      self.insert_point[s] = self.insert_number
136      self.insert_number += 1
137      if metric:
138        self.metrics.add(s)
139
140  def compute(self) -> None:
141    """Called once all strings are added to compute the string and offsets."""
142
143    folded_strings = {}
144    # Determine if two strings can be folded, ie. let 1 string use the
145    # end of another. First reverse all strings and sort them.
146    sorted_reversed_strings = sorted([x[::-1] for x in self.strings])
147
148    # Strings 'xyz' and 'yz' will now be [ 'zy', 'zyx' ]. Scan forward
149    # for each string to see if there is a better candidate to fold it
150    # into, in the example rather than using 'yz' we can use'xyz' at
151    # an offset of 1. We record which string can be folded into which
152    # in folded_strings, we don't need to record the offset as it is
153    # trivially computed from the string lengths.
154    for pos,s in enumerate(sorted_reversed_strings):
155      best_pos = pos
156      for check_pos in range(pos + 1, len(sorted_reversed_strings)):
157        if sorted_reversed_strings[check_pos].startswith(s):
158          best_pos = check_pos
159        else:
160          break
161      if pos != best_pos:
162        folded_strings[s[::-1]] = sorted_reversed_strings[best_pos][::-1]
163
164    # Compute reverse mappings for debugging.
165    fold_into_strings = collections.defaultdict(set)
166    for key, val in folded_strings.items():
167      if key != val:
168        fold_into_strings[val].add(key)
169
170    # big_string_offset is the current location within the C string
171    # being appended to - comments, etc. don't count. big_string is
172    # the string contents represented as a list. Strings are immutable
173    # in Python and so appending to one causes memory issues, while
174    # lists are mutable.
175    big_string_offset = 0
176    self.big_string = []
177    self.offsets = {}
178
179    def string_cmp_key(s: str) -> Tuple[bool, int, str]:
180      return (s in self.metrics, self.insert_point[s], s)
181
182    # Emit all strings that aren't folded in a sorted manner.
183    for s in sorted(self.strings, key=string_cmp_key):
184      if s not in folded_strings:
185        self.offsets[s] = big_string_offset
186        self.big_string.append(f'/* offset={big_string_offset} */\n"')
187        self.big_string.append(s)
188        self.big_string.append('"')
189        if s in fold_into_strings:
190          self.big_string.append(' /* also: ' + ', '.join(fold_into_strings[s]) + ' */')
191        self.big_string.append('\n')
192        big_string_offset += c_len(s)
193        continue
194
195    # Compute the offsets of the folded strings.
196    for s in folded_strings.keys():
197      assert s not in self.offsets
198      folded_s = folded_strings[s]
199      self.offsets[s] = self.offsets[folded_s] + c_len(folded_s) - c_len(s)
200
201_bcs = BigCString()
202
203class JsonEvent:
204  """Representation of an event loaded from a json file dictionary."""
205
206  def __init__(self, jd: dict):
207    """Constructor passed the dictionary of parsed json values."""
208
209    def llx(x: int) -> str:
210      """Convert an int to a string similar to a printf modifier of %#llx."""
211      return str(x) if x >= 0 and x < 10 else hex(x)
212
213    def fixdesc(s: Optional[str]) -> Optional[str]:
214      """Fix formatting issue for the desc string."""
215      if s is None:
216        return None
217      return removesuffix(removesuffix(removesuffix(s, '.  '),
218                                       '. '), '.').replace('\n', '\\n').replace(
219                                           '\"', '\\"').replace('\r', '\\r')
220
221    def convert_aggr_mode(aggr_mode: Optional[str]) -> Optional[str]:
222      """Returns the aggr_mode_class enum value associated with the JSON string."""
223      if not aggr_mode:
224        return None
225      aggr_mode_to_enum = {
226          'PerChip': '1',
227          'PerCore': '2',
228      }
229      return aggr_mode_to_enum[aggr_mode]
230
231    def convert_metric_constraint(metric_constraint: Optional[str]) -> Optional[str]:
232      """Returns the metric_event_groups enum value associated with the JSON string."""
233      if not metric_constraint:
234        return None
235      metric_constraint_to_enum = {
236          'NO_GROUP_EVENTS': '1',
237          'NO_GROUP_EVENTS_NMI': '2',
238          'NO_NMI_WATCHDOG': '2',
239          'NO_GROUP_EVENTS_SMT': '3',
240          'NO_THRESHOLD_AND_NMI': '4',
241      }
242      return metric_constraint_to_enum[metric_constraint]
243
244    def lookup_msr(num: Optional[str]) -> Optional[str]:
245      """Converts the msr number, or first in a list to the appropriate event field."""
246      if not num:
247        return None
248      msrmap = {
249          0x3F6: 'ldlat=',
250          0x1A6: 'offcore_rsp=',
251          0x1A7: 'offcore_rsp=',
252          0x3E0: 'offcore_rsp=',
253          0x3E1: 'offcore_rsp=',
254          0x3E2: 'offcore_rsp=',
255          0x3E3: 'offcore_rsp=',
256          0x3F7: 'frontend=',
257      }
258      return msrmap[int(num.split(',', 1)[0], 0)]
259
260    def real_event(name: Optional[str], event: Optional[str]) -> Optional[str]:
261      """Convert well known event names to an event string otherwise use the event argument."""
262      fixed = {
263          'inst_retired.any': 'event=0xc0,period=2000003',
264          'inst_retired.any_p': 'event=0xc0,period=2000003',
265          'cpu_clk_unhalted.ref': 'event=0x0,umask=0x03,period=2000003',
266          'cpu_clk_unhalted.thread': 'event=0x3c,period=2000003',
267          'cpu_clk_unhalted.core': 'event=0x3c,period=2000003',
268          'cpu_clk_unhalted.thread_any': 'event=0x3c,any=1,period=2000003',
269      }
270      if not name:
271        return None
272      if name.lower() in fixed:
273        return fixed[name.lower()]
274      return event
275
276    def unit_to_pmu(unit: Optional[str]) -> Optional[str]:
277      """Convert a JSON Unit to Linux PMU name."""
278      if not unit:
279        return 'default_core'
280      # Comment brought over from jevents.c:
281      # it's not realistic to keep adding these, we need something more scalable ...
282      table = {
283          'CBO': 'uncore_cbox',
284          'QPI LL': 'uncore_qpi',
285          'SBO': 'uncore_sbox',
286          'iMPH-U': 'uncore_arb',
287          'CPU-M-CF': 'cpum_cf',
288          'CPU-M-SF': 'cpum_sf',
289          'PAI-CRYPTO' : 'pai_crypto',
290          'PAI-EXT' : 'pai_ext',
291          'UPI LL': 'uncore_upi',
292          'hisi_sicl,cpa': 'hisi_sicl,cpa',
293          'hisi_sccl,ddrc': 'hisi_sccl,ddrc',
294          'hisi_sccl,hha': 'hisi_sccl,hha',
295          'hisi_sccl,l3c': 'hisi_sccl,l3c',
296          'imx8_ddr': 'imx8_ddr',
297          'imx9_ddr': 'imx9_ddr',
298          'L3PMC': 'amd_l3',
299          'DFPMC': 'amd_df',
300          'UMCPMC': 'amd_umc',
301          'cpu_core': 'cpu_core',
302          'cpu_atom': 'cpu_atom',
303          'ali_drw': 'ali_drw',
304          'arm_cmn': 'arm_cmn',
305          'software': 'software',
306          'tool': 'tool',
307      }
308      return table[unit] if unit in table else f'uncore_{unit.lower()}'
309
310    def is_zero(val: str) -> bool:
311      try:
312        if val.startswith('0x'):
313          return int(val, 16) == 0
314        else:
315          return int(val) == 0
316      except:
317        return False
318
319    def canonicalize_value(val: str) -> str:
320      try:
321        if val.startswith('0x'):
322          return llx(int(val, 16))
323        return str(int(val))
324      except:
325        return val
326
327    eventcode = 0
328    if 'EventCode' in jd:
329      eventcode = int(jd['EventCode'].split(',', 1)[0], 0)
330    if 'ExtSel' in jd:
331      eventcode |= int(jd['ExtSel']) << 8
332    configcode = int(jd['ConfigCode'], 0) if 'ConfigCode' in jd else None
333    eventidcode = int(jd['EventidCode'], 0) if 'EventidCode' in jd else None
334    legacy_hw_config = int(jd['LegacyConfigCode'], 0) if 'LegacyConfigCode' in jd else None
335    legacy_cache_config = int(jd['LegacyCacheCode'], 0) if 'LegacyCacheCode' in jd else None
336    self.name = jd['EventName'].lower() if 'EventName' in jd else None
337    self.topic = ''
338    self.compat = jd.get('Compat')
339    self.desc = fixdesc(jd.get('BriefDescription'))
340    self.long_desc = fixdesc(jd.get('PublicDescription'))
341    precise = jd.get('PEBS')
342    msr = lookup_msr(jd.get('MSRIndex'))
343    msrval = jd.get('MSRValue')
344    extra_desc = ''
345    if 'Data_LA' in jd:
346      extra_desc += '  Supports address when precise'
347      if 'Errata' in jd:
348        extra_desc += '.'
349    if 'Errata' in jd:
350      extra_desc += '  Spec update: ' + jd['Errata']
351    self.pmu = unit_to_pmu(jd.get('Unit'))
352    filter = jd.get('Filter')
353    self.unit = jd.get('ScaleUnit')
354    self.perpkg = jd.get('PerPkg')
355    self.aggr_mode = convert_aggr_mode(jd.get('AggregationMode'))
356    self.deprecated = jd.get('Deprecated')
357    self.retirement_latency_mean = jd.get('RetirementLatencyMean')
358    self.retirement_latency_min = jd.get('RetirementLatencyMin')
359    self.retirement_latency_max = jd.get('RetirementLatencyMax')
360    self.metric_name = jd.get('MetricName')
361    self.metric_group = jd.get('MetricGroup')
362    self.metricgroup_no_group = jd.get('MetricgroupNoGroup')
363    self.default_metricgroup_name = jd.get('DefaultMetricgroupName')
364    self.event_grouping = convert_metric_constraint(jd.get('MetricConstraint'))
365    self.default_show_events = jd.get('DefaultShowEvents')
366    self.metric_expr = None
367    if 'MetricExpr' in jd:
368      self.metric_expr = metric.ParsePerfJson(jd['MetricExpr']).Simplify()
369    # Note, the metric formula for the threshold isn't parsed as the &
370    # and > have incorrect precedence.
371    self.metric_threshold = jd.get('MetricThreshold')
372
373    arch_std = jd.get('ArchStdEvent')
374    if precise and self.desc and '(Precise Event)' not in self.desc:
375      extra_desc += ' (Must be precise)' if precise == '2' else (' (Precise '
376                                                                 'event)')
377    self.event: Optional[str] = None
378    event: Optional[str] = None
379    if configcode is not None:
380      event = f'config={llx(configcode)}'
381    elif eventidcode is not None:
382      event = f'eventid={llx(eventidcode)}'
383    elif legacy_hw_config is not None:
384      event = f'legacy-hardware-config={llx(legacy_hw_config)}'
385    elif legacy_cache_config is not None:
386      event = f'legacy-cache-config={llx(legacy_cache_config)}'
387    else:
388      event = f'event={llx(eventcode)}'
389    event_fields = [
390        ('AnyThread', 'any='),
391        ('PortMask', 'ch_mask='),
392        ('CounterMask', 'cmask='),
393        ('EdgeDetect', 'edge='),
394        ('FCMask', 'fc_mask='),
395        ('Invert', 'inv='),
396        ('SampleAfterValue', 'period='),
397        ('UMask', 'umask='),
398        ('NodeType', 'type='),
399        ('RdWrMask', 'rdwrmask='),
400        ('EnAllCores', 'enallcores='),
401        ('EnAllSlices', 'enallslices='),
402        ('SliceId', 'sliceid='),
403        ('ThreadMask', 'threadmask='),
404    ]
405    for key, value in event_fields:
406      if key in jd and not is_zero(jd[key]):
407        event += f',{value}{canonicalize_value(jd[key])}'
408    if filter:
409      event += f',{filter}'
410    if msr:
411      event += f',{msr}{msrval}'
412    if self.desc and extra_desc:
413      self.desc += extra_desc
414    if self.long_desc and extra_desc:
415      self.long_desc += extra_desc
416    if self.desc and self.long_desc and self.desc == self.long_desc:
417        # Avoid duplicated descriptions.
418        self.long_desc = None
419    if arch_std:
420      if arch_std.lower() in _arch_std_events:
421        event = _arch_std_events[arch_std.lower()].event
422        # Copy from the architecture standard event to self for undefined fields.
423        for attr, value in _arch_std_events[arch_std.lower()].__dict__.items():
424          if hasattr(self, attr) and not getattr(self, attr):
425            setattr(self, attr, value)
426      else:
427        raise argparse.ArgumentTypeError('Cannot find arch std event:', arch_std)
428
429    self.event = real_event(self.name, event)
430
431  def __repr__(self) -> str:
432    """String representation primarily for debugging."""
433    s = '{\n'
434    for attr, value in self.__dict__.items():
435      if value:
436        s += f'\t{attr} = {value},\n'
437    return s + '}'
438
439  def build_c_string(self, metric: bool) -> str:
440    s = ''
441    for attr in _json_metric_attributes if metric else _json_event_attributes:
442      x = getattr(self, attr)
443      if metric and x and attr == 'metric_expr':
444        # Convert parsed metric expressions into a string. Slashes
445        # must be doubled in the file.
446        x = x.ToPerfJson().replace('\\', '\\\\')
447      if metric and x and attr == 'metric_threshold':
448        x = x.replace('\\', '\\\\')
449      if attr in _json_enum_attributes:
450        s += x if x else '0'
451      else:
452        s += f'{x}\\000' if x else '\\000'
453    return s
454
455  def to_c_string(self, metric: bool) -> str:
456    """Representation of the event as a C struct initializer."""
457
458    def make_comment(s: str) -> str:
459        s = s.replace('*/', r'\*\/')
460        return f'\t/* {s} */\n' if len(s) < 80 else f'\t/* {s[0:80]}... */\n'
461
462    s = self.build_c_string(metric)
463    assert _bcs is not None
464    return f'{make_comment(s)}\t{{ { _bcs.offsets[s] } }},\n'
465
466
467_json_cache = {}
468def _read_json_events_impl(path: str, topic: str) -> Sequence[JsonEvent]:
469  """Read json events from the specified file."""
470  try:
471    events = json.load(open(path), object_hook=JsonEvent)
472  except BaseException as err:
473    print(f"Exception processing {path}")
474    raise
475  metrics: list[Tuple[str, str, metric.Expression]] = []
476  for event in events:
477    event.topic = topic
478    if event.metric_name and '-' not in event.metric_name:
479      metrics.append((event.pmu, event.metric_name, event.metric_expr))
480  updates = metric.RewriteMetricsInTermsOfOthers(metrics)
481  if updates:
482    for event in events:
483      if event.metric_name in updates:
484        event.metric_expr = updates[event.metric_name]
485
486  return events
487
488def read_json_events(path: str, topic: str) -> Sequence[JsonEvent]:
489  key = (path, topic)
490  if key not in _json_cache:
491    _json_cache[key] = _read_json_events_impl(path, topic)
492  return _json_cache[key]
493
494def preprocess_arch_std_files(archpath: str) -> None:
495  """Read in all architecture standard events."""
496  global _arch_std_events
497  for item in os.scandir(archpath):
498    if not item.is_file() or not item.name.endswith('.json'):
499      continue
500    try:
501      for event in read_json_events(item.path, topic=''):
502        if event.name:
503          _arch_std_events[event.name.lower()] = event
504        if event.metric_name:
505          _arch_std_events[event.metric_name.lower()] = event
506    except Exception as e:
507        raise RuntimeError(f'Failure processing \'{item.name}\' in \'{archpath}\'') from e
508
509
510def add_events_table_entries(item: os.DirEntry, topic: str) -> None:
511  """Add contents of file to _pending_events table."""
512  for e in read_json_events(item.path, topic):
513    if e.name:
514      _pending_events.append(e)
515    if e.metric_name and not any(e.metric_name == x.metric_name and
516                                 e.pmu == x.pmu for x in _pending_metrics):
517      _pending_metrics.append(e)
518
519
520def print_pending_events() -> None:
521  """Optionally close events table."""
522
523  def event_cmp_key(j: JsonEvent) -> Tuple[str, str, bool, str, str]:
524    def fix_none(s: Optional[str]) -> str:
525      if s is None:
526        return ''
527      return s
528
529    return (fix_none(j.pmu).replace(',','_'), fix_none(j.name), j.desc is not None, fix_none(j.topic),
530            fix_none(j.metric_name))
531
532  global _pending_events
533  if not _pending_events:
534    return
535
536  global _pending_events_tblname
537  assert _pending_events_tblname is not None
538  if _pending_events_tblname.endswith('_sys'):
539    global _sys_event_tables
540    _sys_event_tables.append(_pending_events_tblname)
541  else:
542    global event_tables
543    _event_tables.append(_pending_events_tblname)
544
545  first = True
546  last_pmu = None
547  last_name = None
548  pmus: Set[Tuple[str, str]] = set()
549  assert _args is not None
550  for event in sorted(_pending_events, key=event_cmp_key):
551    if last_pmu and last_pmu == event.pmu:
552      assert event.name != last_name, f"Duplicate event: {last_pmu}/{last_name}/ in {_pending_events_tblname}"
553    if event.pmu != last_pmu:
554      if not first:
555        _args.output_file.write('};\n')
556      pmu_name = event.pmu.replace(',', '_')
557      _args.output_file.write(
558          f'static const struct compact_pmu_event {_pending_events_tblname}_{pmu_name}[] = {{\n')
559      first = False
560      last_pmu = event.pmu
561      pmus.add((event.pmu, pmu_name))
562
563    _args.output_file.write(event.to_c_string(metric=False))
564    last_name = event.name
565  _pending_events = []
566
567  _args.output_file.write(f"""
568}};
569
570static const struct pmu_table_entry {_pending_events_tblname}[] = {{
571""")
572  for (pmu, tbl_pmu) in sorted(pmus):
573    pmu_name = f"{pmu}\\000"
574    _args.output_file.write(f"""\t{{
575\t\t.entries = {_pending_events_tblname}_{tbl_pmu},
576\t\t.num_entries = ARRAY_SIZE({_pending_events_tblname}_{tbl_pmu}),
577\t\t.pmu_name = {{ {_bcs.offsets[pmu_name]} /* {pmu_name} */ }},
578\t}},
579""")
580  _args.output_file.write('};\n\n')
581
582def print_pending_metrics() -> None:
583  """Optionally close metrics table."""
584
585  def metric_cmp_key(j: JsonEvent) -> Tuple[str, str, str, str]:
586    def fix_none(s: Optional[str]) -> str:
587      if s is None:
588        return ''
589      return s
590
591    assert j.metric_expr is not None
592    return (fix_none(j.pmu), fix_none(j.metric_name), j.metric_expr.ToPerfJson(),
593            fix_none(j.desc))
594
595  global _pending_metrics
596  if not _pending_metrics:
597    return
598
599  global _pending_metrics_tblname
600  assert _pending_metrics_tblname is not None
601  if _pending_metrics_tblname.endswith('_sys'):
602    global _sys_metric_tables
603    _sys_metric_tables.append(_pending_metrics_tblname)
604  else:
605    global metric_tables
606    _metric_tables.append(_pending_metrics_tblname)
607
608  first = True
609  last_pmu = None
610  pmus: Set[Tuple[str, str]] = set()
611  assert _args is not None
612  for metric in sorted(_pending_metrics, key=metric_cmp_key):
613    if metric.pmu != last_pmu:
614      if not first:
615        _args.output_file.write('};\n')
616      pmu_name = metric.pmu.replace(',', '_')
617      _args.output_file.write(
618          f'static const struct compact_pmu_event {_pending_metrics_tblname}_{pmu_name}[] = {{\n')
619      first = False
620      last_pmu = metric.pmu
621      pmus.add((metric.pmu, pmu_name))
622
623    _args.output_file.write(metric.to_c_string(metric=True))
624  _pending_metrics = []
625
626  _args.output_file.write(f"""
627}};
628
629static const struct pmu_table_entry {_pending_metrics_tblname}[] = {{
630""")
631  for (pmu, tbl_pmu) in sorted(pmus):
632    pmu_name = f"{pmu}\\000"
633    _args.output_file.write(f"""\t{{
634\t\t.entries = {_pending_metrics_tblname}_{tbl_pmu},
635\t\t.num_entries = ARRAY_SIZE({_pending_metrics_tblname}_{tbl_pmu}),
636\t\t.pmu_name = {{ {_bcs.offsets[pmu_name]} /* {pmu_name} */ }},
637\t}},
638""")
639  _args.output_file.write('};\n\n')
640
641def get_topic(topic: str) -> str:
642  if topic.endswith('metrics.json'):
643    return 'metrics'
644  return removesuffix(topic, '.json').replace('-', ' ')
645
646def preprocess_one_file(parents: Sequence[str], item: os.DirEntry) -> None:
647  if item.is_dir():
648    return
649
650  # base dir or too deep
651  level = len(parents)
652  if level == 0 or level > 4:
653    return
654
655  # Ignore other directories. If the file name does not have a .json
656  # extension, ignore it. It could be a readme.txt for instance.
657  if not item.is_file() or not item.name.endswith('.json'):
658    return
659
660  assert _bcs is not None
661  if item.name.endswith('metricgroups.json'):
662    metricgroup_descriptions = json.load(open(item.path))
663    for mgroup in metricgroup_descriptions:
664      assert len(mgroup) > 1, parents
665      description = f"{metricgroup_descriptions[mgroup]}\\000"
666      mgroup = f"{mgroup}\\000"
667      _bcs.add(mgroup, metric=True)
668      _bcs.add(description, metric=True)
669      _metricgroups[mgroup] = description
670    return
671
672  topic = get_topic(item.name)
673  for event in read_json_events(item.path, topic):
674    pmu_name = f"{event.pmu}\\000"
675    if event.name:
676      _bcs.add(pmu_name, metric=False)
677      _bcs.add(event.build_c_string(metric=False), metric=False)
678    if event.metric_name:
679      _bcs.add(pmu_name, metric=True)
680      _bcs.add(event.build_c_string(metric=True), metric=True)
681
682def process_one_file(parents: Sequence[str], item: os.DirEntry) -> None:
683  """Process a JSON file during the main walk."""
684  def is_leaf_dir_ignoring_sys(path: str) -> bool:
685    for item in os.scandir(path):
686      if item.is_dir() and item.name != 'sys':
687        return False
688    return True
689
690  # Model directories are leaves (ignoring possible sys
691  # directories). The FTW will walk into the directory next. Flush
692  # pending events and metrics and update the table names for the new
693  # model directory.
694  if item.is_dir() and is_leaf_dir_ignoring_sys(item.path):
695    print_pending_events()
696    print_pending_metrics()
697
698    global _pending_events_tblname
699    _pending_events_tblname = file_name_to_table_name('pmu_events_', parents, item.name)
700    global _pending_metrics_tblname
701    _pending_metrics_tblname = file_name_to_table_name('pmu_metrics_', parents, item.name)
702
703    if item.name == 'sys':
704      _sys_event_table_to_metric_table_mapping[_pending_events_tblname] = _pending_metrics_tblname
705    return
706
707  # base dir or too deep
708  level = len(parents)
709  if level == 0 or level > 4:
710    return
711
712  # Ignore other directories. If the file name does not have a .json
713  # extension, ignore it. It could be a readme.txt for instance.
714  if not item.is_file() or not item.name.endswith('.json') or item.name.endswith('metricgroups.json'):
715    return
716
717  add_events_table_entries(item, get_topic(item.name))
718
719
720def print_mapping_table(archs: Sequence[str]) -> None:
721  """Read the mapfile and generate the struct from cpuid string to event table."""
722  assert _args is not None
723  _args.output_file.write("""
724/* Struct used to make the PMU event table implementation opaque to callers. */
725struct pmu_events_table {
726\tconst struct pmu_table_entry *pmus;
727\tuint32_t num_pmus;
728};
729
730/* Struct used to make the PMU metric table implementation opaque to callers. */
731struct pmu_metrics_table {
732\tconst char *name;
733\tconst struct pmu_table_entry *pmus;
734\tuint32_t num_pmus;
735};
736
737/*
738 * Map a CPU to its table of PMU events. The CPU is identified by the
739 * cpuid field, which is an arch-specific identifier for the CPU.
740 * The identifier specified in tools/perf/pmu-events/arch/xxx/mapfile
741 * must match the get_cpuid_str() in tools/perf/arch/xxx/util/header.c)
742 *
743 * The  cpuid can contain any character other than the comma.
744 */
745struct pmu_events_map {
746\tconst char *arch;
747\tconst char *cpuid;
748\tstruct pmu_events_table event_table;
749\tstruct pmu_metrics_table metric_table;
750};
751
752/*
753 * Global table mapping each known CPU for the architecture to its
754 * table of PMU events.
755 */
756static const struct pmu_events_map pmu_events_map[] = {
757""")
758  for arch in archs:
759    if arch == 'test':
760      _args.output_file.write("""{
761\t.arch = "testarch",
762\t.cpuid = "testcpu",
763\t.event_table = {
764\t\t.pmus = pmu_events__test_soc_cpu,
765\t\t.num_pmus = ARRAY_SIZE(pmu_events__test_soc_cpu),
766\t},
767\t.metric_table = {
768\t\t.name = "test_soc_cpu",
769\t\t.pmus = pmu_metrics__test_soc_cpu,
770\t\t.num_pmus = ARRAY_SIZE(pmu_metrics__test_soc_cpu),
771\t}
772},
773""")
774    elif arch == 'common':
775      _args.output_file.write("""{
776\t.arch = "common",
777\t.cpuid = "common",
778\t.event_table = {
779\t\t.pmus = pmu_events__common,
780\t\t.num_pmus = ARRAY_SIZE(pmu_events__common),
781\t},
782\t.metric_table = {
783\t\t.name = "common",
784\t\t.pmus = pmu_metrics__common,
785\t\t.num_pmus = ARRAY_SIZE(pmu_metrics__common),
786\t},
787},
788""")
789    else:
790      with open(f'{_args.starting_dir}/{arch}/mapfile.csv') as csvfile:
791        table = csv.reader(csvfile)
792        first = True
793        for row in table:
794          # Skip the first row or any row beginning with #.
795          if not first and len(row) > 0 and not row[0].startswith('#'):
796            event_tblname = file_name_to_table_name('pmu_events_', [], row[2].replace('/', '_'))
797            if event_tblname in _event_tables:
798              event_size = f'ARRAY_SIZE({event_tblname})'
799            else:
800              event_tblname = 'NULL'
801              event_size = '0'
802            metric_tblname = file_name_to_table_name('pmu_metrics_', [], row[2].replace('/', '_'))
803            if metric_tblname in _metric_tables:
804              metric_name = f'"{metric_tblname.replace("pmu_metrics__", "")}"'
805              metric_size = f'ARRAY_SIZE({metric_tblname})'
806            else:
807              metric_name = 'NULL'
808              metric_tblname = 'NULL'
809              metric_size = '0'
810            if event_size == '0' and metric_size == '0':
811              continue
812            cpuid = row[0].replace('\\', '\\\\')
813            _args.output_file.write(f"""{{
814\t.arch = "{arch}",
815\t.cpuid = "{cpuid}",
816\t.event_table = {{
817\t\t.pmus = {event_tblname},
818\t\t.num_pmus = {event_size}
819\t}},
820\t.metric_table = {{
821\t\t.name = {metric_name},
822\t\t.pmus = {metric_tblname},
823\t\t.num_pmus = {metric_size}
824\t}}
825}},
826""")
827          first = False
828
829  _args.output_file.write("""{
830\t.arch = 0,
831\t.cpuid = 0,
832\t.event_table = { 0, 0 },
833\t.metric_table = { 0 },
834}
835};
836""")
837
838
839def print_metric_table_functions() -> None:
840  assert _args is not None
841  _args.output_file.write("""
842const char *pmu_metrics_table__name(const struct pmu_metrics_table *table)
843{
844\treturn table ? table->name : NULL;
845}
846
847int pmu_metrics_table__iterate_tables(pmu_metrics_table_iter_t fn, void *data)
848{
849\tsize_t i;
850\tint ret;
851
852\tfor (i = 0; pmu_events_map[i].cpuid; i++) {
853\t\tsize_t j;
854\t\tbool found = false;
855
856\t\tif (!pmu_events_map[i].metric_table.pmus)
857\t\t\tcontinue;
858\t\tfor (j = 0; j < i; j++) {
859\t\t\tif (pmu_events_map[j].metric_table.pmus ==
860\t\t\t    pmu_events_map[i].metric_table.pmus) {
861\t\t\t\tfound = true;
862\t\t\t\tbreak;
863\t\t\t}
864\t\t}
865\t\tif (found)
866\t\t\tcontinue;
867\t\tret = fn(&pmu_events_map[i].metric_table, data);
868\t\tif (ret)
869\t\t\treturn ret;
870\t}
871\tfor (i = 0; pmu_sys_event_tables[i].name; i++) {
872\t\tif (!pmu_sys_event_tables[i].metric_table.pmus)
873\t\t\tcontinue;
874\t\tret = fn(&pmu_sys_event_tables[i].metric_table, data);
875\t\tif (ret)
876\t\t\treturn ret;
877\t}
878\treturn 0;
879}
880""")
881
882
883def print_system_mapping_table() -> None:
884  assert _args is not None
885  """C struct mapping table array for tables from /sys directories."""
886  _args.output_file.write("""
887struct pmu_sys_events {
888\tconst char *name;
889\tstruct pmu_events_table event_table;
890\tstruct pmu_metrics_table metric_table;
891};
892
893static const struct pmu_sys_events pmu_sys_event_tables[] = {
894""")
895  printed_metric_tables = []
896  for tblname in _sys_event_tables:
897    _args.output_file.write(f"""\t{{
898\t\t.event_table = {{
899\t\t\t.pmus = {tblname},
900\t\t\t.num_pmus = ARRAY_SIZE({tblname})
901\t\t}},""")
902    metric_tblname = _sys_event_table_to_metric_table_mapping[tblname]
903    if metric_tblname in _sys_metric_tables:
904      _args.output_file.write(f"""
905\t\t.metric_table = {{
906\t\t\t.name = "{metric_tblname.replace('pmu_metrics__', '')}",
907\t\t\t.pmus = {metric_tblname},
908\t\t\t.num_pmus = ARRAY_SIZE({metric_tblname})
909\t\t}},""")
910      printed_metric_tables.append(metric_tblname)
911    _args.output_file.write(f"""
912\t\t.name = \"{tblname}\",
913\t}},
914""")
915  for tblname in _sys_metric_tables:
916    if tblname in printed_metric_tables:
917      continue
918    _args.output_file.write(f"""\t{{
919\t\t.metric_table = {{
920\t\t\t.name = "{tblname.replace('pmu_metrics__', '')}",
921\t\t\t.pmus = {tblname},
922\t\t\t.num_pmus = ARRAY_SIZE({tblname})
923\t\t}},
924\t\t.name = \"{tblname}\",
925\t}},
926""")
927  _args.output_file.write("""\t{
928\t\t.event_table = { 0, 0 },
929\t\t.metric_table = { 0 },
930\t},
931};
932
933static void decompress_event(int offset, struct pmu_event *pe)
934{
935\tconst char *p = &big_c_string[offset];
936""")
937  for attr in _json_event_attributes:
938    _args.output_file.write(f'\n\tpe->{attr} = ')
939    if attr in _json_enum_attributes:
940      _args.output_file.write("*p - '0';\n")
941    else:
942      _args.output_file.write("(*p == '\\0' ? NULL : p);\n")
943    if attr == _json_event_attributes[-1]:
944      continue
945    if attr in _json_enum_attributes:
946      _args.output_file.write('\tp++;')
947    else:
948      _args.output_file.write('\twhile (*p++);')
949  _args.output_file.write("""}
950
951static void decompress_metric(int offset, struct pmu_metric *pm)
952{
953\tconst char *p = &big_c_string[offset];
954""")
955  for attr in _json_metric_attributes:
956    _args.output_file.write(f'\n\tpm->{attr} = ')
957    if attr in _json_enum_attributes:
958      _args.output_file.write("*p - '0';\n")
959    else:
960      _args.output_file.write("(*p == '\\0' ? NULL : p);\n")
961    if attr == _json_metric_attributes[-1]:
962      continue
963    if attr in _json_enum_attributes:
964      _args.output_file.write('\tp++;')
965    else:
966      _args.output_file.write('\twhile (*p++);')
967  _args.output_file.write("""}
968
969static int pmu_events_table__for_each_event_pmu(const struct pmu_events_table *table,
970\t\t\t\t\t\tconst struct pmu_table_entry *pmu,
971\t\t\t\t\t\tpmu_event_iter_fn fn,
972\t\t\t\t\t\tvoid *data)
973{
974\tint ret;
975\tstruct pmu_event pe = {
976\t\t.pmu = &big_c_string[pmu->pmu_name.offset],
977\t};
978
979\tfor (uint32_t i = 0; i < pmu->num_entries; i++) {
980\t\tdecompress_event(pmu->entries[i].offset, &pe);
981\t\tif (!pe.name)
982\t\t\tcontinue;
983\t\tret = fn(&pe, table, data);
984\t\tif (ret)
985\t\t\treturn ret;
986\t}
987\treturn 0;
988 }
989
990static int pmu_events_table__find_event_pmu(const struct pmu_events_table *table,
991\t\t\t\t\t    const struct pmu_table_entry *pmu,
992\t\t\t\t\t    const char *name,
993\t\t\t\t\t    pmu_event_iter_fn fn,
994\t\t\t\t\t    void *data)
995{
996\tstruct pmu_event pe = {
997\t\t.pmu = &big_c_string[pmu->pmu_name.offset],
998\t};
999\tint low = 0, high = pmu->num_entries - 1;
1000
1001\twhile (low <= high) {
1002\t\tint cmp, mid = (low + high) / 2;
1003
1004\t\tdecompress_event(pmu->entries[mid].offset, &pe);
1005
1006\t\tif (!pe.name && !name)
1007\t\t\tgoto do_call;
1008
1009\t\tif (!pe.name && name) {
1010\t\t\tlow = mid + 1;
1011\t\t\tcontinue;
1012\t\t}
1013\t\tif (pe.name && !name) {
1014\t\t\thigh = mid - 1;
1015\t\t\tcontinue;
1016\t\t}
1017
1018\t\tcmp = strcasecmp(pe.name, name);
1019\t\tif (cmp < 0) {
1020\t\t\tlow = mid + 1;
1021\t\t\tcontinue;
1022\t\t}
1023\t\tif (cmp > 0) {
1024\t\t\thigh = mid - 1;
1025\t\t\tcontinue;
1026\t\t}
1027  do_call:
1028\t\treturn fn ? fn(&pe, table, data) : 0;
1029\t}
1030\treturn PMU_EVENTS__NOT_FOUND;
1031}
1032
1033int pmu_events_table__for_each_event(const struct pmu_events_table *table,
1034\t\t\t\t    struct perf_pmu *pmu,
1035\t\t\t\t    pmu_event_iter_fn fn,
1036\t\t\t\t    void *data)
1037{
1038\tif (!table)
1039\t\treturn 0;
1040\tfor (size_t i = 0; i < table->num_pmus; i++) {
1041\t\tconst struct pmu_table_entry *table_pmu = &table->pmus[i];
1042\t\tconst char *pmu_name = &big_c_string[table_pmu->pmu_name.offset];
1043\t\tint ret;
1044
1045\t\tif (pmu && !perf_pmu__name_wildcard_match(pmu, pmu_name))
1046\t\t\tcontinue;
1047
1048\t\tret = pmu_events_table__for_each_event_pmu(table, table_pmu, fn, data);
1049\t\tif (ret)
1050\t\t\treturn ret;
1051\t}
1052\treturn 0;
1053}
1054
1055int pmu_events_table__find_event(const struct pmu_events_table *table,
1056\t\t\t\t struct perf_pmu *pmu,
1057\t\t\t\t const char *name,
1058\t\t\t\t pmu_event_iter_fn fn,
1059\t\t\t\t void *data)
1060{
1061\tif (!table)
1062\t\treturn PMU_EVENTS__NOT_FOUND;
1063\tfor (size_t i = 0; i < table->num_pmus; i++) {
1064\t\tconst struct pmu_table_entry *table_pmu = &table->pmus[i];
1065\t\tconst char *pmu_name = &big_c_string[table_pmu->pmu_name.offset];
1066\t\tint ret;
1067
1068\t\tif (pmu && !perf_pmu__name_wildcard_match(pmu, pmu_name))
1069\t\t\tcontinue;
1070
1071\t\tret = pmu_events_table__find_event_pmu(table, table_pmu, name, fn, data);
1072\t\tif (ret != PMU_EVENTS__NOT_FOUND)
1073\t\t\treturn ret;
1074\t}
1075\treturn PMU_EVENTS__NOT_FOUND;
1076}
1077
1078size_t pmu_events_table__num_events(const struct pmu_events_table *table, struct perf_pmu *pmu)
1079{
1080\tsize_t count = 0;
1081
1082\tif (!table)
1083\t\treturn 0;
1084\tfor (size_t i = 0; i < table->num_pmus; i++) {
1085\t\tconst struct pmu_table_entry *table_pmu = &table->pmus[i];
1086\t\tconst char *pmu_name = &big_c_string[table_pmu->pmu_name.offset];
1087
1088\t\tif (perf_pmu__name_wildcard_match(pmu, pmu_name))
1089\t\t\tcount += table_pmu->num_entries;
1090\t}
1091\treturn count;
1092}
1093
1094static int pmu_metrics_table__for_each_metric_pmu(const struct pmu_metrics_table *table,
1095\t\t\t\t\t\tconst struct pmu_table_entry *pmu,
1096\t\t\t\t\t\tpmu_metric_iter_fn fn,
1097\t\t\t\t\t\tvoid *data)
1098{
1099\tint ret;
1100\tstruct pmu_metric pm = {
1101\t\t.pmu = &big_c_string[pmu->pmu_name.offset],
1102\t};
1103
1104\tfor (uint32_t i = 0; i < pmu->num_entries; i++) {
1105\t\tdecompress_metric(pmu->entries[i].offset, &pm);
1106\t\tif (!pm.metric_expr)
1107\t\t\tcontinue;
1108\t\tret = fn(&pm, table, data);
1109\t\tif (ret)
1110\t\t\treturn ret;
1111\t}
1112\treturn 0;
1113}
1114
1115static int pmu_metrics_table__find_metric_pmu(const struct pmu_metrics_table *table,
1116\t\t\t\t\t    const struct pmu_table_entry *pmu,
1117\t\t\t\t\t    const char *metric,
1118\t\t\t\t\t    pmu_metric_iter_fn fn,
1119\t\t\t\t\t    void *data)
1120{
1121\tstruct pmu_metric pm = {
1122\t\t.pmu = &big_c_string[pmu->pmu_name.offset],
1123\t};
1124\tint low = 0, high = pmu->num_entries - 1;
1125
1126\twhile (low <= high) {
1127\t\tint cmp, mid = (low + high) / 2;
1128
1129\t\tdecompress_metric(pmu->entries[mid].offset, &pm);
1130
1131\t\tif (!pm.metric_name && !metric)
1132\t\t\tgoto do_call;
1133
1134\t\tif (!pm.metric_name && metric) {
1135\t\t\tlow = mid + 1;
1136\t\t\tcontinue;
1137\t\t}
1138\t\tif (pm.metric_name && !metric) {
1139\t\t\thigh = mid - 1;
1140\t\t\tcontinue;
1141\t\t}
1142
1143\t\tcmp = strcmp(pm.metric_name, metric);
1144\t\tif (cmp < 0) {
1145\t\t\tlow = mid + 1;
1146\t\t\tcontinue;
1147\t\t}
1148\t\tif (cmp > 0) {
1149\t\t\thigh = mid - 1;
1150\t\t\tcontinue;
1151\t\t}
1152  do_call:
1153\t\treturn fn ? fn(&pm, table, data) : 0;
1154\t}
1155\treturn PMU_METRICS__NOT_FOUND;
1156}
1157
1158int pmu_metrics_table__for_each_metric(const struct pmu_metrics_table *table,
1159\t\t\t\t     pmu_metric_iter_fn fn,
1160\t\t\t\t     void *data)
1161{
1162\tif (!table)
1163\t\treturn 0;
1164\tfor (size_t i = 0; i < table->num_pmus; i++) {
1165\t\tint ret = pmu_metrics_table__for_each_metric_pmu(table, &table->pmus[i], fn, data);
1166
1167\t\tif (ret)
1168\t\t\treturn ret;
1169\t}
1170\treturn 0;
1171}
1172
1173int pmu_metrics_table__find_metric(const struct pmu_metrics_table *table,
1174\t\t\t\t struct perf_pmu *pmu,
1175\t\t\t\t const char *metric,
1176\t\t\t\t pmu_metric_iter_fn fn,
1177\t\t\t\t void *data)
1178{
1179\tif (!table)
1180\t\treturn 0;
1181\tfor (size_t i = 0; i < table->num_pmus; i++) {
1182\t\tconst struct pmu_table_entry *table_pmu = &table->pmus[i];
1183\t\tconst char *pmu_name = &big_c_string[table_pmu->pmu_name.offset];
1184\t\tint ret;
1185
1186\t\tif (pmu && !perf_pmu__name_wildcard_match(pmu, pmu_name))
1187\t\t\tcontinue;
1188
1189\t\tret = pmu_metrics_table__find_metric_pmu(table, table_pmu, metric, fn, data);
1190\t\tif (ret != PMU_METRICS__NOT_FOUND)
1191\t\t\treturn ret;
1192\t}
1193\treturn PMU_METRICS__NOT_FOUND;
1194}
1195
1196static const struct pmu_events_map *map_for_cpu(struct perf_cpu cpu)
1197{
1198\tstatic struct {
1199\t\tconst struct pmu_events_map *map;
1200\t\tstruct perf_cpu cpu;
1201\t} last_result;
1202\tstatic struct {
1203\t\tconst struct pmu_events_map *map;
1204\t\tchar *cpuid;
1205\t} last_map_search;
1206\tstatic bool has_last_result, has_last_map_search;
1207\tconst struct pmu_events_map *map = NULL;
1208\tchar *cpuid = NULL;
1209\tsize_t i;
1210
1211\tif (has_last_result && last_result.cpu.cpu == cpu.cpu)
1212\t\treturn last_result.map;
1213
1214\tcpuid = get_cpuid_allow_env_override(cpu);
1215
1216\t/*
1217\t * On some platforms which uses cpus map, cpuid can be NULL for
1218\t * PMUs other than CORE PMUs.
1219\t */
1220\tif (!cpuid)
1221\t\tgoto out_update_last_result;
1222
1223\tif (has_last_map_search && !strcmp(last_map_search.cpuid, cpuid)) {
1224\t\tmap = last_map_search.map;
1225\t\tfree(cpuid);
1226\t} else {
1227\t\ti = 0;
1228\t\tfor (;;) {
1229\t\t\tmap = &pmu_events_map[i++];
1230
1231\t\t\tif (!map->arch) {
1232\t\t\t\tmap = NULL;
1233\t\t\t\tbreak;
1234\t\t\t}
1235
1236\t\t\tif (!strcmp_cpuid_str(map->cpuid, cpuid))
1237\t\t\t\tbreak;
1238\t\t}
1239\t\tfree(last_map_search.cpuid);
1240\t\tlast_map_search.cpuid = cpuid;
1241\t\tlast_map_search.map = map;
1242\t\thas_last_map_search = true;
1243\t}
1244out_update_last_result:
1245\tlast_result.cpu = cpu;
1246\tlast_result.map = map;
1247\thas_last_result = true;
1248\treturn map;
1249}
1250
1251static const struct pmu_events_map *map_for_pmu(struct perf_pmu *pmu)
1252{
1253\tstruct perf_cpu cpu = { -1 };
1254
1255\tif (pmu) {
1256\t\tfor (size_t i = 0; i < ARRAY_SIZE(pmu_events__common); i++) {
1257\t\t\tconst char *pmu_name = &big_c_string[pmu_events__common[i].pmu_name.offset];
1258
1259\t\t\tif (!strcmp(pmu_name, pmu->name)) {
1260\t\t\t\tconst struct pmu_events_map *map = &pmu_events_map[0];
1261
1262\t\t\t\twhile (strcmp("common", map->arch))
1263\t\t\t\t\tmap++;
1264\t\t\t\treturn map;
1265\t\t\t}
1266\t\t}
1267\t\tcpu = perf_cpu_map__min(pmu->cpus);
1268\t}
1269\treturn map_for_cpu(cpu);
1270}
1271
1272const struct pmu_events_table *perf_pmu__find_events_table(struct perf_pmu *pmu)
1273{
1274\tconst struct pmu_events_map *map = map_for_pmu(pmu);
1275
1276\tif (!map)
1277\t\treturn NULL;
1278
1279\tif (!pmu)
1280\t\treturn &map->event_table;
1281
1282\tfor (size_t i = 0; i < map->event_table.num_pmus; i++) {
1283\t\tconst struct pmu_table_entry *table_pmu = &map->event_table.pmus[i];
1284\t\tconst char *pmu_name = &big_c_string[table_pmu->pmu_name.offset];
1285
1286\t\tif (perf_pmu__name_wildcard_match(pmu, pmu_name))
1287\t\t\treturn &map->event_table;
1288\t}
1289\treturn NULL;
1290}
1291
1292const struct pmu_events_table *perf_pmu__default_core_events_table(void)
1293{
1294\tint i = 0;
1295
1296\tfor (;;) {
1297\t\tconst struct pmu_events_map *map = &pmu_events_map[i++];
1298
1299\t\tif (!map->arch)
1300\t\t\tbreak;
1301
1302\t\tif (!strcmp(map->cpuid, "common"))
1303\t\t\treturn &map->event_table;
1304\t}
1305\treturn NULL;
1306}
1307
1308const struct pmu_metrics_table *pmu_metrics_table__find(void)
1309{
1310\tstruct perf_cpu cpu = { -1 };
1311\tconst struct pmu_events_map *map = map_for_cpu(cpu);
1312
1313\treturn map ? &map->metric_table : NULL;
1314}
1315
1316const struct pmu_metrics_table *pmu_metrics_table__default(void)
1317{
1318\tint i = 0;
1319
1320\tfor (;;) {
1321\t\tconst struct pmu_events_map *map = &pmu_events_map[i++];
1322
1323\t\tif (!map->arch)
1324\t\t\tbreak;
1325
1326\t\tif (!strcmp(map->cpuid, "common"))
1327\t\t\treturn &map->metric_table;
1328\t}
1329\treturn NULL;
1330}
1331
1332const struct pmu_events_table *find_core_events_table(const char *arch, const char *cpuid)
1333{
1334\tfor (const struct pmu_events_map *tables = &pmu_events_map[0];
1335\t     tables->arch;
1336\t     tables++) {
1337\t\tif (!strcmp(tables->arch, arch) && !strcmp_cpuid_str(tables->cpuid, cpuid))
1338\t\t\treturn &tables->event_table;
1339\t}
1340\treturn NULL;
1341}
1342
1343const struct pmu_metrics_table *find_core_metrics_table(const char *arch, const char *cpuid)
1344{
1345\tfor (const struct pmu_events_map *tables = &pmu_events_map[0];
1346\t     tables->arch;
1347\t     tables++) {
1348\t\tif (!strcmp(tables->arch, arch) && !strcmp_cpuid_str(tables->cpuid, cpuid))
1349\t\t\treturn &tables->metric_table;
1350\t}
1351\treturn NULL;
1352}
1353
1354int pmu_for_each_core_event(pmu_event_iter_fn fn, void *data)
1355{
1356\tfor (const struct pmu_events_map *tables = &pmu_events_map[0];
1357\t     tables->arch;
1358\t     tables++) {
1359\t\tint ret = pmu_events_table__for_each_event(&tables->event_table,
1360\t\t\t\t\t\t\t   /*pmu=*/NULL, fn, data);
1361
1362\t\tif (ret)
1363\t\t\treturn ret;
1364\t}
1365\treturn 0;
1366}
1367
1368int pmu_for_each_core_metric(pmu_metric_iter_fn fn, void *data)
1369{
1370\tfor (const struct pmu_events_map *tables = &pmu_events_map[0];
1371\t     tables->arch;
1372\t     tables++) {
1373\t\tint ret = pmu_metrics_table__for_each_metric(&tables->metric_table, fn, data);
1374
1375\t\tif (ret)
1376\t\t\treturn ret;
1377\t}
1378\treturn 0;
1379}
1380
1381const struct pmu_events_table *find_sys_events_table(const char *name)
1382{
1383\tfor (const struct pmu_sys_events *tables = &pmu_sys_event_tables[0];
1384\t     tables->name;
1385\t     tables++) {
1386\t\tif (!strcmp(tables->name, name))
1387\t\t\treturn &tables->event_table;
1388\t}
1389\treturn NULL;
1390}
1391
1392int pmu_for_each_sys_event(pmu_event_iter_fn fn, void *data)
1393{
1394\tfor (const struct pmu_sys_events *tables = &pmu_sys_event_tables[0];
1395\t     tables->name;
1396\t     tables++) {
1397\t\tint ret = pmu_events_table__for_each_event(&tables->event_table,
1398\t\t\t\t\t\t\t   /*pmu=*/NULL, fn, data);
1399
1400\t\tif (ret)
1401\t\t\treturn ret;
1402\t}
1403\treturn 0;
1404}
1405
1406int pmu_for_each_sys_metric(pmu_metric_iter_fn fn, void *data)
1407{
1408\tfor (const struct pmu_sys_events *tables = &pmu_sys_event_tables[0];
1409\t     tables->name;
1410\t     tables++) {
1411\t\tint ret = pmu_metrics_table__for_each_metric(&tables->metric_table, fn, data);
1412
1413\t\tif (ret)
1414\t\t\treturn ret;
1415\t}
1416\treturn 0;
1417}
1418""")
1419
1420def print_metricgroups() -> None:
1421  assert _args is not None
1422  _args.output_file.write("""
1423static const int metricgroups[][2] = {
1424""")
1425  for mgroup in sorted(_metricgroups):
1426    description = _metricgroups[mgroup]
1427    _args.output_file.write(
1428        f'\t{{ {_bcs.offsets[mgroup]}, {_bcs.offsets[description]} }}, /* {mgroup} => {description} */\n'
1429    )
1430  _args.output_file.write("""
1431};
1432
1433const char *describe_metricgroup(const char *group)
1434{
1435\tint low = 0, high = (int)ARRAY_SIZE(metricgroups) - 1;
1436
1437\twhile (low <= high) {
1438\t\tint mid = (low + high) / 2;
1439\t\tconst char *mgroup = &big_c_string[metricgroups[mid][0]];
1440\t\tint cmp = strcmp(mgroup, group);
1441
1442\t\tif (cmp == 0)
1443\t\t\treturn &big_c_string[metricgroups[mid][1]];
1444\t\telse if (cmp < 0)
1445\t\t\tlow = mid + 1;
1446\t\telse
1447\t\t\thigh = mid - 1;
1448\t}
1449\treturn NULL;
1450}
1451""")
1452
1453def _parallel_read_json_events(task: Tuple[str, str]) -> Tuple[str, str, Sequence[JsonEvent]]:
1454  path, topic = task
1455  return path, topic, _read_json_events_impl(path, topic)
1456
1457def _init_worker(std_events: dict) -> None:
1458  global _arch_std_events
1459  _arch_std_events = std_events
1460
1461def main() -> None:
1462  global _args
1463
1464  def dir_path(path: str) -> str:
1465    """Validate path is a directory for argparse."""
1466    if os.path.isdir(path):
1467      return path
1468    raise argparse.ArgumentTypeError(f'\'{path}\' is not a valid directory')
1469
1470  def ftw(path: str, parents: Sequence[str],
1471          action: Callable[[Sequence[str], os.DirEntry], None]) -> None:
1472    """Replicate the directory/file walking behavior of C's file tree walk."""
1473    assert _args is not None
1474    for item in sorted(os.scandir(path), key=lambda e: e.name):
1475      if _args.model != 'all' and item.is_dir():
1476        # Check if the model matches one in _args.model.
1477        if len(parents) == _args.model.split(',')[0].count('/'):
1478          # We're testing the correct directory.
1479          item_path = '/'.join(parents) + ('/' if len(parents) > 0 else '') + item.name
1480          if 'test' not in item_path and 'common' not in item_path and item_path not in _args.model.split(','):
1481            continue
1482      try:
1483        action(parents, item)
1484      except Exception as e:
1485        raise RuntimeError(f'Action failure for \'{item.name}\' in {parents}') from e
1486      if item.is_dir():
1487        ftw(item.path, parents + [item.name], action)
1488
1489  ap = argparse.ArgumentParser()
1490  ap.add_argument('arch', help='Architecture name like x86')
1491  ap.add_argument('model', help='''Select a model such as skylake to
1492reduce the code size.  Normally set to "all". For architectures like
1493ARM64 with an implementor/model, the model must include the implementor
1494such as "arm/cortex-a34".''',
1495                  default='all')
1496  ap.add_argument(
1497      'starting_dir',
1498      type=dir_path,
1499      help='Root of tree containing architecture directories containing json files'
1500  )
1501  ap.add_argument(
1502      'output_file', type=argparse.FileType('w', encoding='utf-8'), nargs='?', default=sys.stdout)
1503  ap.add_argument(
1504      'output_string_file', type=argparse.FileType('w', encoding='utf-8'), nargs='?', default=None)
1505  _args = ap.parse_args()
1506
1507  _args.output_file.write(f"""/* SPDX-License-Identifier: GPL-2.0 */
1508/* THIS FILE WAS AUTOGENERATED BY `jevents.py arch={_args.arch} model={_args.model}` ! */
1509""")
1510  _args.output_file.write("""
1511#include <pmu-events/pmu-events.h>
1512#include "util/header.h"
1513#include "util/pmu.h"
1514#include <string.h>
1515#include <stddef.h>
1516
1517struct compact_pmu_event {
1518\tint offset;
1519};
1520
1521struct pmu_table_entry {
1522\tconst struct compact_pmu_event *entries;
1523\tuint32_t num_entries;
1524\tstruct compact_pmu_event pmu_name;
1525};
1526
1527""")
1528  archs = []
1529  for item in os.scandir(_args.starting_dir):
1530    if not item.is_dir():
1531      continue
1532    if item.name == _args.arch or _args.arch == 'all' or item.name == 'test' or item.name == 'common':
1533      archs.append(item.name)
1534
1535  if len(archs) < 2 and _args.arch != 'none':
1536    raise IOError(f'Missing architecture directory \'{_args.arch}\'')
1537
1538  archs.sort()
1539  import concurrent.futures
1540  tasks = []
1541  def collect_json(parents: Sequence[str], item: os.DirEntry) -> None:
1542    if len(parents) == 0:
1543      return
1544    if item.is_file() and item.name.endswith('.json') and not item.name.endswith('metricgroups.json'):
1545      tasks.append((item.path, get_topic(item.name)))
1546
1547  for arch in archs:
1548    arch_path = f'{_args.starting_dir}/{arch}'
1549    preprocess_arch_std_files(arch_path)
1550    ftw(arch_path, [], collect_json)
1551
1552  with concurrent.futures.ProcessPoolExecutor(initializer=_init_worker, initargs=(_arch_std_events,)) as executor:
1553    for path, topic, events in executor.map(_parallel_read_json_events, tasks):
1554      _json_cache[(path, topic)] = events
1555
1556  for arch in archs:
1557    arch_path = f'{_args.starting_dir}/{arch}'
1558    ftw(arch_path, [], preprocess_one_file)
1559
1560  assert _bcs is not None
1561  _bcs.compute()
1562  _args.output_file.write('/* clang-format off */\n')
1563  if not _args.output_string_file:
1564    _args.output_file.write('static const char *const big_c_string =\n')
1565    for s in _bcs.big_string:
1566      _args.output_file.write(s)
1567    _args.output_file.write(';\n\n')
1568  else:
1569    _args.output_string_file.write('/* SPDX-License-Identifier: GPL-2.0 */\n')
1570    _args.output_string_file.write('/* Autogenerated by jevents.py */\n')
1571    _args.output_string_file.write('extern const char big_c_string[];\n')
1572    _args.output_string_file.write('const char big_c_string[] =\n')
1573    for s in _bcs.big_string:
1574      _args.output_string_file.write(s)
1575    _args.output_string_file.write(';\n')
1576    _args.output_file.write('extern const char big_c_string[];\n\n')
1577  for arch in archs:
1578    arch_path = f'{_args.starting_dir}/{arch}'
1579    ftw(arch_path, [], process_one_file)
1580    print_pending_events()
1581    print_pending_metrics()
1582
1583  print_mapping_table(archs)
1584  print_system_mapping_table()
1585  _args.output_file.write('/* clang-format on */\n')
1586  print_metric_table_functions()
1587  print_metricgroups()
1588  _args.output_file.close()
1589  if _args.output_string_file:
1590    _args.output_string_file.close()
1591
1592if __name__ == '__main__':
1593  main()
1594