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