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