xref: /openbmc/linux/tools/perf/pmu-events/jevents.py (revision 270205be)
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# Order specific JsonEvent attributes will be visited.
41_json_event_attributes = [
42    # cmp_sevent related attributes.
43    'name', 'pmu', 'topic', 'desc',
44    # Seems useful, put it early.
45    'event',
46    # Short things in alphabetical order.
47    'compat', 'deprecated', 'perpkg', 'unit',
48    # Longer things (the last won't be iterated over during decompress).
49    'long_desc'
50]
51
52# Attributes that are in pmu_metric rather than pmu_event.
53_json_metric_attributes = [
54    'metric_name', 'metric_group', 'metric_expr', 'metric_threshold', 'desc',
55    'long_desc', 'unit', 'compat', 'aggr_mode', 'event_grouping'
56]
57# Attributes that are bools or enum int values, encoded as '0', '1',...
58_json_enum_attributes = ['aggr_mode', 'deprecated', 'event_grouping', 'perpkg']
59
60def removesuffix(s: str, suffix: str) -> str:
61  """Remove the suffix from a string
62
63  The removesuffix function is added to str in Python 3.9. We aim for 3.6
64  compatibility and so provide our own function here.
65  """
66  return s[0:-len(suffix)] if s.endswith(suffix) else s
67
68
69def file_name_to_table_name(prefix: str, parents: Sequence[str],
70                            dirname: str) -> str:
71  """Generate a C table name from directory names."""
72  tblname = prefix
73  for p in parents:
74    tblname += '_' + p
75  tblname += '_' + dirname
76  return tblname.replace('-', '_')
77
78
79def c_len(s: str) -> int:
80  """Return the length of s a C string
81
82  This doesn't handle all escape characters properly. It first assumes
83  all \ are for escaping, it then adjusts as it will have over counted
84  \\. The code uses \000 rather than \0 as a terminator as an adjacent
85  number would be folded into a string of \0 (ie. "\0" + "5" doesn't
86  equal a terminator followed by the number 5 but the escape of
87  \05). The code adjusts for \000 but not properly for all octal, hex
88  or unicode values.
89  """
90  try:
91    utf = s.encode(encoding='utf-8',errors='strict')
92  except:
93    print(f'broken string {s}')
94    raise
95  return len(utf) - utf.count(b'\\') + utf.count(b'\\\\') - (utf.count(b'\\000') * 2)
96
97class BigCString:
98  """A class to hold many strings concatenated together.
99
100  Generating a large number of stand-alone C strings creates a large
101  number of relocations in position independent code. The BigCString
102  is a helper for this case. It builds a single string which within it
103  are all the other C strings (to avoid memory issues the string
104  itself is held as a list of strings). The offsets within the big
105  string are recorded and when stored to disk these don't need
106  relocation. To reduce the size of the string further, identical
107  strings are merged. If a longer string ends-with the same value as a
108  shorter string, these entries are also merged.
109  """
110  strings: Set[str]
111  big_string: Sequence[str]
112  offsets: Dict[str, int]
113
114  def __init__(self):
115    self.strings = set()
116
117  def add(self, s: str) -> None:
118    """Called to add to the big string."""
119    self.strings.add(s)
120
121  def compute(self) -> None:
122    """Called once all strings are added to compute the string and offsets."""
123
124    folded_strings = {}
125    # Determine if two strings can be folded, ie. let 1 string use the
126    # end of another. First reverse all strings and sort them.
127    sorted_reversed_strings = sorted([x[::-1] for x in self.strings])
128
129    # Strings 'xyz' and 'yz' will now be [ 'zy', 'zyx' ]. Scan forward
130    # for each string to see if there is a better candidate to fold it
131    # into, in the example rather than using 'yz' we can use'xyz' at
132    # an offset of 1. We record which string can be folded into which
133    # in folded_strings, we don't need to record the offset as it is
134    # trivially computed from the string lengths.
135    for pos,s in enumerate(sorted_reversed_strings):
136      best_pos = pos
137      for check_pos in range(pos + 1, len(sorted_reversed_strings)):
138        if sorted_reversed_strings[check_pos].startswith(s):
139          best_pos = check_pos
140        else:
141          break
142      if pos != best_pos:
143        folded_strings[s[::-1]] = sorted_reversed_strings[best_pos][::-1]
144
145    # Compute reverse mappings for debugging.
146    fold_into_strings = collections.defaultdict(set)
147    for key, val in folded_strings.items():
148      if key != val:
149        fold_into_strings[val].add(key)
150
151    # big_string_offset is the current location within the C string
152    # being appended to - comments, etc. don't count. big_string is
153    # the string contents represented as a list. Strings are immutable
154    # in Python and so appending to one causes memory issues, while
155    # lists are mutable.
156    big_string_offset = 0
157    self.big_string = []
158    self.offsets = {}
159
160    # Emit all strings that aren't folded in a sorted manner.
161    for s in sorted(self.strings):
162      if s not in folded_strings:
163        self.offsets[s] = big_string_offset
164        self.big_string.append(f'/* offset={big_string_offset} */ "')
165        self.big_string.append(s)
166        self.big_string.append('"')
167        if s in fold_into_strings:
168          self.big_string.append(' /* also: ' + ', '.join(fold_into_strings[s]) + ' */')
169        self.big_string.append('\n')
170        big_string_offset += c_len(s)
171        continue
172
173    # Compute the offsets of the folded strings.
174    for s in folded_strings.keys():
175      assert s not in self.offsets
176      folded_s = folded_strings[s]
177      self.offsets[s] = self.offsets[folded_s] + c_len(folded_s) - c_len(s)
178
179_bcs = BigCString()
180
181class JsonEvent:
182  """Representation of an event loaded from a json file dictionary."""
183
184  def __init__(self, jd: dict):
185    """Constructor passed the dictionary of parsed json values."""
186
187    def llx(x: int) -> str:
188      """Convert an int to a string similar to a printf modifier of %#llx."""
189      return '0' if x == 0 else hex(x)
190
191    def fixdesc(s: str) -> str:
192      """Fix formatting issue for the desc string."""
193      if s is None:
194        return None
195      return removesuffix(removesuffix(removesuffix(s, '.  '),
196                                       '. '), '.').replace('\n', '\\n').replace(
197                                           '\"', '\\"').replace('\r', '\\r')
198
199    def convert_aggr_mode(aggr_mode: str) -> Optional[str]:
200      """Returns the aggr_mode_class enum value associated with the JSON string."""
201      if not aggr_mode:
202        return None
203      aggr_mode_to_enum = {
204          'PerChip': '1',
205          'PerCore': '2',
206      }
207      return aggr_mode_to_enum[aggr_mode]
208
209    def convert_metric_constraint(metric_constraint: str) -> Optional[str]:
210      """Returns the metric_event_groups enum value associated with the JSON string."""
211      if not metric_constraint:
212        return None
213      metric_constraint_to_enum = {
214          'NO_GROUP_EVENTS': '1',
215          'NO_GROUP_EVENTS_NMI': '2',
216          'NO_NMI_WATCHDOG': '2',
217          'NO_GROUP_EVENTS_SMT': '3',
218      }
219      return metric_constraint_to_enum[metric_constraint]
220
221    def lookup_msr(num: str) -> Optional[str]:
222      """Converts the msr number, or first in a list to the appropriate event field."""
223      if not num:
224        return None
225      msrmap = {
226          0x3F6: 'ldlat=',
227          0x1A6: 'offcore_rsp=',
228          0x1A7: 'offcore_rsp=',
229          0x3F7: 'frontend=',
230      }
231      return msrmap[int(num.split(',', 1)[0], 0)]
232
233    def real_event(name: str, event: str) -> Optional[str]:
234      """Convert well known event names to an event string otherwise use the event argument."""
235      fixed = {
236          'inst_retired.any': 'event=0xc0,period=2000003',
237          'inst_retired.any_p': 'event=0xc0,period=2000003',
238          'cpu_clk_unhalted.ref': 'event=0x0,umask=0x03,period=2000003',
239          'cpu_clk_unhalted.thread': 'event=0x3c,period=2000003',
240          'cpu_clk_unhalted.core': 'event=0x3c,period=2000003',
241          'cpu_clk_unhalted.thread_any': 'event=0x3c,any=1,period=2000003',
242      }
243      if not name:
244        return None
245      if name.lower() in fixed:
246        return fixed[name.lower()]
247      return event
248
249    def unit_to_pmu(unit: str) -> Optional[str]:
250      """Convert a JSON Unit to Linux PMU name."""
251      if not unit:
252        return None
253      # Comment brought over from jevents.c:
254      # it's not realistic to keep adding these, we need something more scalable ...
255      table = {
256          'CBO': 'uncore_cbox',
257          'QPI LL': 'uncore_qpi',
258          'SBO': 'uncore_sbox',
259          'iMPH-U': 'uncore_arb',
260          'CPU-M-CF': 'cpum_cf',
261          'CPU-M-SF': 'cpum_sf',
262          'PAI-CRYPTO' : 'pai_crypto',
263          'PAI-EXT' : 'pai_ext',
264          'UPI LL': 'uncore_upi',
265          'hisi_sicl,cpa': 'hisi_sicl,cpa',
266          'hisi_sccl,ddrc': 'hisi_sccl,ddrc',
267          'hisi_sccl,hha': 'hisi_sccl,hha',
268          'hisi_sccl,l3c': 'hisi_sccl,l3c',
269          'imx8_ddr': 'imx8_ddr',
270          'L3PMC': 'amd_l3',
271          'DFPMC': 'amd_df',
272          'cpu_core': 'cpu_core',
273          'cpu_atom': 'cpu_atom',
274      }
275      return table[unit] if unit in table else f'uncore_{unit.lower()}'
276
277    eventcode = 0
278    if 'EventCode' in jd:
279      eventcode = int(jd['EventCode'].split(',', 1)[0], 0)
280    if 'ExtSel' in jd:
281      eventcode |= int(jd['ExtSel']) << 8
282    configcode = int(jd['ConfigCode'], 0) if 'ConfigCode' in jd else None
283    self.name = jd['EventName'].lower() if 'EventName' in jd else None
284    self.topic = ''
285    self.compat = jd.get('Compat')
286    self.desc = fixdesc(jd.get('BriefDescription'))
287    self.long_desc = fixdesc(jd.get('PublicDescription'))
288    precise = jd.get('PEBS')
289    msr = lookup_msr(jd.get('MSRIndex'))
290    msrval = jd.get('MSRValue')
291    extra_desc = ''
292    if 'Data_LA' in jd:
293      extra_desc += '  Supports address when precise'
294      if 'Errata' in jd:
295        extra_desc += '.'
296    if 'Errata' in jd:
297      extra_desc += '  Spec update: ' + jd['Errata']
298    self.pmu = unit_to_pmu(jd.get('Unit'))
299    filter = jd.get('Filter')
300    self.unit = jd.get('ScaleUnit')
301    self.perpkg = jd.get('PerPkg')
302    self.aggr_mode = convert_aggr_mode(jd.get('AggregationMode'))
303    self.deprecated = jd.get('Deprecated')
304    self.metric_name = jd.get('MetricName')
305    self.metric_group = jd.get('MetricGroup')
306    self.event_grouping = convert_metric_constraint(jd.get('MetricConstraint'))
307    self.metric_expr = None
308    if 'MetricExpr' in jd:
309      self.metric_expr = metric.ParsePerfJson(jd['MetricExpr']).Simplify()
310    # Note, the metric formula for the threshold isn't parsed as the &
311    # and > have incorrect precedence.
312    self.metric_threshold = jd.get('MetricThreshold')
313
314    arch_std = jd.get('ArchStdEvent')
315    if precise and self.desc and '(Precise Event)' not in self.desc:
316      extra_desc += ' (Must be precise)' if precise == '2' else (' (Precise '
317                                                                 'event)')
318    event = f'config={llx(configcode)}' if configcode is not None else f'event={llx(eventcode)}'
319    event_fields = [
320        ('AnyThread', 'any='),
321        ('PortMask', 'ch_mask='),
322        ('CounterMask', 'cmask='),
323        ('EdgeDetect', 'edge='),
324        ('FCMask', 'fc_mask='),
325        ('Invert', 'inv='),
326        ('SampleAfterValue', 'period='),
327        ('UMask', 'umask='),
328    ]
329    for key, value in event_fields:
330      if key in jd and jd[key] != '0':
331        event += ',' + value + jd[key]
332    if filter:
333      event += f',{filter}'
334    if msr:
335      event += f',{msr}{msrval}'
336    if self.desc and extra_desc:
337      self.desc += extra_desc
338    if self.long_desc and extra_desc:
339      self.long_desc += extra_desc
340    if self.pmu:
341      if self.desc and not self.desc.endswith('. '):
342        self.desc += '. '
343      self.desc = (self.desc if self.desc else '') + ('Unit: ' + self.pmu + ' ')
344    if arch_std and arch_std.lower() in _arch_std_events:
345      event = _arch_std_events[arch_std.lower()].event
346      # Copy from the architecture standard event to self for undefined fields.
347      for attr, value in _arch_std_events[arch_std.lower()].__dict__.items():
348        if hasattr(self, attr) and not getattr(self, attr):
349          setattr(self, attr, value)
350
351    self.event = real_event(self.name, event)
352
353  def __repr__(self) -> str:
354    """String representation primarily for debugging."""
355    s = '{\n'
356    for attr, value in self.__dict__.items():
357      if value:
358        s += f'\t{attr} = {value},\n'
359    return s + '}'
360
361  def build_c_string(self, metric: bool) -> str:
362    s = ''
363    for attr in _json_metric_attributes if metric else _json_event_attributes:
364      x = getattr(self, attr)
365      if metric and x and attr == 'metric_expr':
366        # Convert parsed metric expressions into a string. Slashes
367        # must be doubled in the file.
368        x = x.ToPerfJson().replace('\\', '\\\\')
369      if metric and x and attr == 'metric_threshold':
370        x = x.replace('\\', '\\\\')
371      if attr in _json_enum_attributes:
372        s += x if x else '0'
373      else:
374        s += f'{x}\\000' if x else '\\000'
375    return s
376
377  def to_c_string(self, metric: bool) -> str:
378    """Representation of the event as a C struct initializer."""
379
380    s = self.build_c_string(metric)
381    return f'{{ { _bcs.offsets[s] } }}, /* {s} */\n'
382
383
384@lru_cache(maxsize=None)
385def read_json_events(path: str, topic: str) -> Sequence[JsonEvent]:
386  """Read json events from the specified file."""
387  try:
388    events = json.load(open(path), object_hook=JsonEvent)
389  except BaseException as err:
390    print(f"Exception processing {path}")
391    raise
392  metrics: list[Tuple[str, metric.Expression]] = []
393  for event in events:
394    event.topic = topic
395    if event.metric_name and '-' not in event.metric_name:
396      metrics.append((event.metric_name, event.metric_expr))
397  updates = metric.RewriteMetricsInTermsOfOthers(metrics)
398  if updates:
399    for event in events:
400      if event.metric_name in updates:
401        # print(f'Updated {event.metric_name} from\n"{event.metric_expr}"\n'
402        #       f'to\n"{updates[event.metric_name]}"')
403        event.metric_expr = updates[event.metric_name]
404
405  return events
406
407def preprocess_arch_std_files(archpath: str) -> None:
408  """Read in all architecture standard events."""
409  global _arch_std_events
410  for item in os.scandir(archpath):
411    if item.is_file() and item.name.endswith('.json'):
412      for event in read_json_events(item.path, topic=''):
413        if event.name:
414          _arch_std_events[event.name.lower()] = event
415        if event.metric_name:
416          _arch_std_events[event.metric_name.lower()] = event
417
418
419def add_events_table_entries(item: os.DirEntry, topic: str) -> None:
420  """Add contents of file to _pending_events table."""
421  for e in read_json_events(item.path, topic):
422    if e.name:
423      _pending_events.append(e)
424    if e.metric_name:
425      _pending_metrics.append(e)
426
427
428def print_pending_events() -> None:
429  """Optionally close events table."""
430
431  def event_cmp_key(j: JsonEvent) -> Tuple[bool, str, str, str, str]:
432    def fix_none(s: Optional[str]) -> str:
433      if s is None:
434        return ''
435      return s
436
437    return (j.desc is not None, fix_none(j.topic), fix_none(j.name), fix_none(j.pmu),
438            fix_none(j.metric_name))
439
440  global _pending_events
441  if not _pending_events:
442    return
443
444  global _pending_events_tblname
445  if _pending_events_tblname.endswith('_sys'):
446    global _sys_event_tables
447    _sys_event_tables.append(_pending_events_tblname)
448  else:
449    global event_tables
450    _event_tables.append(_pending_events_tblname)
451
452  _args.output_file.write(
453      f'static const struct compact_pmu_event {_pending_events_tblname}[] = {{\n')
454
455  for event in sorted(_pending_events, key=event_cmp_key):
456    _args.output_file.write(event.to_c_string(metric=False))
457  _pending_events = []
458
459  _args.output_file.write('};\n\n')
460
461def print_pending_metrics() -> None:
462  """Optionally close metrics table."""
463
464  def metric_cmp_key(j: JsonEvent) -> Tuple[bool, str, str]:
465    def fix_none(s: Optional[str]) -> str:
466      if s is None:
467        return ''
468      return s
469
470    return (j.desc is not None, fix_none(j.pmu), fix_none(j.metric_name))
471
472  global _pending_metrics
473  if not _pending_metrics:
474    return
475
476  global _pending_metrics_tblname
477  if _pending_metrics_tblname.endswith('_sys'):
478    global _sys_metric_tables
479    _sys_metric_tables.append(_pending_metrics_tblname)
480  else:
481    global metric_tables
482    _metric_tables.append(_pending_metrics_tblname)
483
484  _args.output_file.write(
485      f'static const struct compact_pmu_event {_pending_metrics_tblname}[] = {{\n')
486
487  for metric in sorted(_pending_metrics, key=metric_cmp_key):
488    _args.output_file.write(metric.to_c_string(metric=True))
489  _pending_metrics = []
490
491  _args.output_file.write('};\n\n')
492
493def get_topic(topic: str) -> str:
494  if topic.endswith('metrics.json'):
495    return 'metrics'
496  return removesuffix(topic, '.json').replace('-', ' ')
497
498def preprocess_one_file(parents: Sequence[str], item: os.DirEntry) -> None:
499
500  if item.is_dir():
501    return
502
503  # base dir or too deep
504  level = len(parents)
505  if level == 0 or level > 4:
506    return
507
508  # Ignore other directories. If the file name does not have a .json
509  # extension, ignore it. It could be a readme.txt for instance.
510  if not item.is_file() or not item.name.endswith('.json'):
511    return
512
513  topic = get_topic(item.name)
514  for event in read_json_events(item.path, topic):
515    if event.name:
516      _bcs.add(event.build_c_string(metric=False))
517    if event.metric_name:
518      _bcs.add(event.build_c_string(metric=True))
519
520def process_one_file(parents: Sequence[str], item: os.DirEntry) -> None:
521  """Process a JSON file during the main walk."""
522  def is_leaf_dir(path: str) -> bool:
523    for item in os.scandir(path):
524      if item.is_dir():
525        return False
526    return True
527
528  # model directory, reset topic
529  if item.is_dir() and is_leaf_dir(item.path):
530    print_pending_events()
531    print_pending_metrics()
532
533    global _pending_events_tblname
534    _pending_events_tblname = file_name_to_table_name('pmu_events_', parents, item.name)
535    global _pending_metrics_tblname
536    _pending_metrics_tblname = file_name_to_table_name('pmu_metrics_', parents, item.name)
537
538    if item.name == 'sys':
539      _sys_event_table_to_metric_table_mapping[_pending_events_tblname] = _pending_metrics_tblname
540    return
541
542  # base dir or too deep
543  level = len(parents)
544  if level == 0 or level > 4:
545    return
546
547  # Ignore other directories. If the file name does not have a .json
548  # extension, ignore it. It could be a readme.txt for instance.
549  if not item.is_file() or not item.name.endswith('.json'):
550    return
551
552  add_events_table_entries(item, get_topic(item.name))
553
554
555def print_mapping_table(archs: Sequence[str]) -> None:
556  """Read the mapfile and generate the struct from cpuid string to event table."""
557  _args.output_file.write("""
558/* Struct used to make the PMU event table implementation opaque to callers. */
559struct pmu_events_table {
560        const struct compact_pmu_event *entries;
561        size_t length;
562};
563
564/* Struct used to make the PMU metric table implementation opaque to callers. */
565struct pmu_metrics_table {
566        const struct compact_pmu_event *entries;
567        size_t length;
568};
569
570/*
571 * Map a CPU to its table of PMU events. The CPU is identified by the
572 * cpuid field, which is an arch-specific identifier for the CPU.
573 * The identifier specified in tools/perf/pmu-events/arch/xxx/mapfile
574 * must match the get_cpuid_str() in tools/perf/arch/xxx/util/header.c)
575 *
576 * The  cpuid can contain any character other than the comma.
577 */
578struct pmu_events_map {
579        const char *arch;
580        const char *cpuid;
581        struct pmu_events_table event_table;
582        struct pmu_metrics_table metric_table;
583};
584
585/*
586 * Global table mapping each known CPU for the architecture to its
587 * table of PMU events.
588 */
589const struct pmu_events_map pmu_events_map[] = {
590""")
591  for arch in archs:
592    if arch == 'test':
593      _args.output_file.write("""{
594\t.arch = "testarch",
595\t.cpuid = "testcpu",
596\t.event_table = {
597\t\t.entries = pmu_events__test_soc_cpu,
598\t\t.length = ARRAY_SIZE(pmu_events__test_soc_cpu),
599\t},
600\t.metric_table = {
601\t\t.entries = pmu_metrics__test_soc_cpu,
602\t\t.length = ARRAY_SIZE(pmu_metrics__test_soc_cpu),
603\t}
604},
605""")
606    else:
607      with open(f'{_args.starting_dir}/{arch}/mapfile.csv') as csvfile:
608        table = csv.reader(csvfile)
609        first = True
610        for row in table:
611          # Skip the first row or any row beginning with #.
612          if not first and len(row) > 0 and not row[0].startswith('#'):
613            event_tblname = file_name_to_table_name('pmu_events_', [], row[2].replace('/', '_'))
614            if event_tblname in _event_tables:
615              event_size = f'ARRAY_SIZE({event_tblname})'
616            else:
617              event_tblname = 'NULL'
618              event_size = '0'
619            metric_tblname = file_name_to_table_name('pmu_metrics_', [], row[2].replace('/', '_'))
620            if metric_tblname in _metric_tables:
621              metric_size = f'ARRAY_SIZE({metric_tblname})'
622            else:
623              metric_tblname = 'NULL'
624              metric_size = '0'
625            if event_size == '0' and metric_size == '0':
626              continue
627            cpuid = row[0].replace('\\', '\\\\')
628            _args.output_file.write(f"""{{
629\t.arch = "{arch}",
630\t.cpuid = "{cpuid}",
631\t.event_table = {{
632\t\t.entries = {event_tblname},
633\t\t.length = {event_size}
634\t}},
635\t.metric_table = {{
636\t\t.entries = {metric_tblname},
637\t\t.length = {metric_size}
638\t}}
639}},
640""")
641          first = False
642
643  _args.output_file.write("""{
644\t.arch = 0,
645\t.cpuid = 0,
646\t.event_table = { 0, 0 },
647\t.metric_table = { 0, 0 },
648}
649};
650""")
651
652
653def print_system_mapping_table() -> None:
654  """C struct mapping table array for tables from /sys directories."""
655  _args.output_file.write("""
656struct pmu_sys_events {
657\tconst char *name;
658\tstruct pmu_events_table event_table;
659\tstruct pmu_metrics_table metric_table;
660};
661
662static const struct pmu_sys_events pmu_sys_event_tables[] = {
663""")
664  printed_metric_tables = []
665  for tblname in _sys_event_tables:
666    _args.output_file.write(f"""\t{{
667\t\t.event_table = {{
668\t\t\t.entries = {tblname},
669\t\t\t.length = ARRAY_SIZE({tblname})
670\t\t}},""")
671    metric_tblname = _sys_event_table_to_metric_table_mapping[tblname]
672    if metric_tblname in _sys_metric_tables:
673      _args.output_file.write(f"""
674\t\t.metric_table = {{
675\t\t\t.entries = {metric_tblname},
676\t\t\t.length = ARRAY_SIZE({metric_tblname})
677\t\t}},""")
678      printed_metric_tables.append(metric_tblname)
679    _args.output_file.write(f"""
680\t\t.name = \"{tblname}\",
681\t}},
682""")
683  for tblname in _sys_metric_tables:
684    if tblname in printed_metric_tables:
685      continue
686    _args.output_file.write(f"""\t{{
687\t\t.metric_table = {{
688\t\t\t.entries = {tblname},
689\t\t\t.length = ARRAY_SIZE({tblname})
690\t\t}},
691\t\t.name = \"{tblname}\",
692\t}},
693""")
694  _args.output_file.write("""\t{
695\t\t.event_table = { 0, 0 },
696\t\t.metric_table = { 0, 0 },
697\t},
698};
699
700static void decompress_event(int offset, struct pmu_event *pe)
701{
702\tconst char *p = &big_c_string[offset];
703""")
704  for attr in _json_event_attributes:
705    _args.output_file.write(f'\n\tpe->{attr} = ')
706    if attr in _json_enum_attributes:
707      _args.output_file.write("*p - '0';\n")
708    else:
709      _args.output_file.write("(*p == '\\0' ? NULL : p);\n")
710    if attr == _json_event_attributes[-1]:
711      continue
712    if attr in _json_enum_attributes:
713      _args.output_file.write('\tp++;')
714    else:
715      _args.output_file.write('\twhile (*p++);')
716  _args.output_file.write("""}
717
718static void decompress_metric(int offset, struct pmu_metric *pm)
719{
720\tconst char *p = &big_c_string[offset];
721""")
722  for attr in _json_metric_attributes:
723    _args.output_file.write(f'\n\tpm->{attr} = ')
724    if attr in _json_enum_attributes:
725      _args.output_file.write("*p - '0';\n")
726    else:
727      _args.output_file.write("(*p == '\\0' ? NULL : p);\n")
728    if attr == _json_metric_attributes[-1]:
729      continue
730    if attr in _json_enum_attributes:
731      _args.output_file.write('\tp++;')
732    else:
733      _args.output_file.write('\twhile (*p++);')
734  _args.output_file.write("""}
735
736int pmu_events_table_for_each_event(const struct pmu_events_table *table,
737                                    pmu_event_iter_fn fn,
738                                    void *data)
739{
740        for (size_t i = 0; i < table->length; i++) {
741                struct pmu_event pe;
742                int ret;
743
744                decompress_event(table->entries[i].offset, &pe);
745                if (!pe.name)
746                        continue;
747                ret = fn(&pe, table, data);
748                if (ret)
749                        return ret;
750        }
751        return 0;
752}
753
754int pmu_metrics_table_for_each_metric(const struct pmu_metrics_table *table,
755                                     pmu_metric_iter_fn fn,
756                                     void *data)
757{
758        for (size_t i = 0; i < table->length; i++) {
759                struct pmu_metric pm;
760                int ret;
761
762                decompress_metric(table->entries[i].offset, &pm);
763                if (!pm.metric_expr)
764                        continue;
765                ret = fn(&pm, table, data);
766                if (ret)
767                        return ret;
768        }
769        return 0;
770}
771
772const struct pmu_events_table *perf_pmu__find_events_table(struct perf_pmu *pmu)
773{
774        const struct pmu_events_table *table = NULL;
775        char *cpuid = perf_pmu__getcpuid(pmu);
776        int i;
777
778        /* on some platforms which uses cpus map, cpuid can be NULL for
779         * PMUs other than CORE PMUs.
780         */
781        if (!cpuid)
782                return NULL;
783
784        i = 0;
785        for (;;) {
786                const struct pmu_events_map *map = &pmu_events_map[i++];
787                if (!map->arch)
788                        break;
789
790                if (!strcmp_cpuid_str(map->cpuid, cpuid)) {
791                        table = &map->event_table;
792                        break;
793                }
794        }
795        free(cpuid);
796        return table;
797}
798
799const struct pmu_metrics_table *perf_pmu__find_metrics_table(struct perf_pmu *pmu)
800{
801        const struct pmu_metrics_table *table = NULL;
802        char *cpuid = perf_pmu__getcpuid(pmu);
803        int i;
804
805        /* on some platforms which uses cpus map, cpuid can be NULL for
806         * PMUs other than CORE PMUs.
807         */
808        if (!cpuid)
809                return NULL;
810
811        i = 0;
812        for (;;) {
813                const struct pmu_events_map *map = &pmu_events_map[i++];
814                if (!map->arch)
815                        break;
816
817                if (!strcmp_cpuid_str(map->cpuid, cpuid)) {
818                        table = &map->metric_table;
819                        break;
820                }
821        }
822        free(cpuid);
823        return table;
824}
825
826const struct pmu_events_table *find_core_events_table(const char *arch, const char *cpuid)
827{
828        for (const struct pmu_events_map *tables = &pmu_events_map[0];
829             tables->arch;
830             tables++) {
831                if (!strcmp(tables->arch, arch) && !strcmp_cpuid_str(tables->cpuid, cpuid))
832                        return &tables->event_table;
833        }
834        return NULL;
835}
836
837const struct pmu_metrics_table *find_core_metrics_table(const char *arch, const char *cpuid)
838{
839        for (const struct pmu_events_map *tables = &pmu_events_map[0];
840             tables->arch;
841             tables++) {
842                if (!strcmp(tables->arch, arch) && !strcmp_cpuid_str(tables->cpuid, cpuid))
843                        return &tables->metric_table;
844        }
845        return NULL;
846}
847
848int pmu_for_each_core_event(pmu_event_iter_fn fn, void *data)
849{
850        for (const struct pmu_events_map *tables = &pmu_events_map[0];
851             tables->arch;
852             tables++) {
853                int ret = pmu_events_table_for_each_event(&tables->event_table, fn, data);
854
855                if (ret)
856                        return ret;
857        }
858        return 0;
859}
860
861int pmu_for_each_core_metric(pmu_metric_iter_fn fn, void *data)
862{
863        for (const struct pmu_events_map *tables = &pmu_events_map[0];
864             tables->arch;
865             tables++) {
866                int ret = pmu_metrics_table_for_each_metric(&tables->metric_table, fn, data);
867
868                if (ret)
869                        return ret;
870        }
871        return 0;
872}
873
874const struct pmu_events_table *find_sys_events_table(const char *name)
875{
876        for (const struct pmu_sys_events *tables = &pmu_sys_event_tables[0];
877             tables->name;
878             tables++) {
879                if (!strcmp(tables->name, name))
880                        return &tables->event_table;
881        }
882        return NULL;
883}
884
885int pmu_for_each_sys_event(pmu_event_iter_fn fn, void *data)
886{
887        for (const struct pmu_sys_events *tables = &pmu_sys_event_tables[0];
888             tables->name;
889             tables++) {
890                int ret = pmu_events_table_for_each_event(&tables->event_table, fn, data);
891
892                if (ret)
893                        return ret;
894        }
895        return 0;
896}
897
898int pmu_for_each_sys_metric(pmu_metric_iter_fn fn, void *data)
899{
900        for (const struct pmu_sys_events *tables = &pmu_sys_event_tables[0];
901             tables->name;
902             tables++) {
903                int ret = pmu_metrics_table_for_each_metric(&tables->metric_table, fn, data);
904
905                if (ret)
906                        return ret;
907        }
908        return 0;
909}
910""")
911
912
913def main() -> None:
914  global _args
915
916  def dir_path(path: str) -> str:
917    """Validate path is a directory for argparse."""
918    if os.path.isdir(path):
919      return path
920    raise argparse.ArgumentTypeError(f'\'{path}\' is not a valid directory')
921
922  def ftw(path: str, parents: Sequence[str],
923          action: Callable[[Sequence[str], os.DirEntry], None]) -> None:
924    """Replicate the directory/file walking behavior of C's file tree walk."""
925    for item in sorted(os.scandir(path), key=lambda e: e.name):
926      if _args.model != 'all' and item.is_dir():
927        # Check if the model matches one in _args.model.
928        if len(parents) == _args.model.split(',')[0].count('/'):
929          # We're testing the correct directory.
930          item_path = '/'.join(parents) + ('/' if len(parents) > 0 else '') + item.name
931          if 'test' not in item_path and item_path not in _args.model.split(','):
932            continue
933      action(parents, item)
934      if item.is_dir():
935        ftw(item.path, parents + [item.name], action)
936
937  ap = argparse.ArgumentParser()
938  ap.add_argument('arch', help='Architecture name like x86')
939  ap.add_argument('model', help='''Select a model such as skylake to
940reduce the code size.  Normally set to "all". For architectures like
941ARM64 with an implementor/model, the model must include the implementor
942such as "arm/cortex-a34".''',
943                  default='all')
944  ap.add_argument(
945      'starting_dir',
946      type=dir_path,
947      help='Root of tree containing architecture directories containing json files'
948  )
949  ap.add_argument(
950      'output_file', type=argparse.FileType('w', encoding='utf-8'), nargs='?', default=sys.stdout)
951  _args = ap.parse_args()
952
953  _args.output_file.write("""
954#include "pmu-events/pmu-events.h"
955#include "util/header.h"
956#include "util/pmu.h"
957#include <string.h>
958#include <stddef.h>
959
960struct compact_pmu_event {
961  int offset;
962};
963
964""")
965  archs = []
966  for item in os.scandir(_args.starting_dir):
967    if not item.is_dir():
968      continue
969    if item.name == _args.arch or _args.arch == 'all' or item.name == 'test':
970      archs.append(item.name)
971
972  if len(archs) < 2:
973    raise IOError(f'Missing architecture directory \'{_args.arch}\'')
974
975  archs.sort()
976  for arch in archs:
977    arch_path = f'{_args.starting_dir}/{arch}'
978    preprocess_arch_std_files(arch_path)
979    ftw(arch_path, [], preprocess_one_file)
980
981  _bcs.compute()
982  _args.output_file.write('static const char *const big_c_string =\n')
983  for s in _bcs.big_string:
984    _args.output_file.write(s)
985  _args.output_file.write(';\n\n')
986  for arch in archs:
987    arch_path = f'{_args.starting_dir}/{arch}'
988    ftw(arch_path, [], process_one_file)
989    print_pending_events()
990    print_pending_metrics()
991
992  print_mapping_table(archs)
993  print_system_mapping_table()
994
995
996if __name__ == '__main__':
997  main()
998