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