xref: /linux/tools/perf/pmu-events/jevents.py (revision 159a8bb06f7bb298da1aacc99975e9817e2cf02c)
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',
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
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          'ali_drw': 'ali_drw',
278      }
279      return table[unit] if unit in table else f'uncore_{unit.lower()}'
280
281    eventcode = 0
282    if 'EventCode' in jd:
283      eventcode = int(jd['EventCode'].split(',', 1)[0], 0)
284    if 'ExtSel' in jd:
285      eventcode |= int(jd['ExtSel']) << 8
286    configcode = int(jd['ConfigCode'], 0) if 'ConfigCode' in jd else None
287    self.name = jd['EventName'].lower() if 'EventName' in jd else None
288    self.topic = ''
289    self.compat = jd.get('Compat')
290    self.desc = fixdesc(jd.get('BriefDescription'))
291    self.long_desc = fixdesc(jd.get('PublicDescription'))
292    precise = jd.get('PEBS')
293    msr = lookup_msr(jd.get('MSRIndex'))
294    msrval = jd.get('MSRValue')
295    extra_desc = ''
296    if 'Data_LA' in jd:
297      extra_desc += '  Supports address when precise'
298      if 'Errata' in jd:
299        extra_desc += '.'
300    if 'Errata' in jd:
301      extra_desc += '  Spec update: ' + jd['Errata']
302    self.pmu = unit_to_pmu(jd.get('Unit'))
303    filter = jd.get('Filter')
304    self.unit = jd.get('ScaleUnit')
305    self.perpkg = jd.get('PerPkg')
306    self.aggr_mode = convert_aggr_mode(jd.get('AggregationMode'))
307    self.deprecated = jd.get('Deprecated')
308    self.metric_name = jd.get('MetricName')
309    self.metric_group = jd.get('MetricGroup')
310    self.metricgroup_no_group = jd.get('MetricgroupNoGroup')
311    self.default_metricgroup_name = jd.get('DefaultMetricgroupName')
312    self.event_grouping = convert_metric_constraint(jd.get('MetricConstraint'))
313    self.metric_expr = None
314    if 'MetricExpr' in jd:
315      self.metric_expr = metric.ParsePerfJson(jd['MetricExpr']).Simplify()
316    # Note, the metric formula for the threshold isn't parsed as the &
317    # and > have incorrect precedence.
318    self.metric_threshold = jd.get('MetricThreshold')
319
320    arch_std = jd.get('ArchStdEvent')
321    if precise and self.desc and '(Precise Event)' not in self.desc:
322      extra_desc += ' (Must be precise)' if precise == '2' else (' (Precise '
323                                                                 'event)')
324    event = f'config={llx(configcode)}' if configcode is not None else f'event={llx(eventcode)}'
325    event_fields = [
326        ('AnyThread', 'any='),
327        ('PortMask', 'ch_mask='),
328        ('CounterMask', 'cmask='),
329        ('EdgeDetect', 'edge='),
330        ('FCMask', 'fc_mask='),
331        ('Invert', 'inv='),
332        ('SampleAfterValue', 'period='),
333        ('UMask', 'umask='),
334    ]
335    for key, value in event_fields:
336      if key in jd and jd[key] != '0':
337        event += ',' + value + jd[key]
338    if filter:
339      event += f',{filter}'
340    if msr:
341      event += f',{msr}{msrval}'
342    if self.desc and extra_desc:
343      self.desc += extra_desc
344    if self.long_desc and extra_desc:
345      self.long_desc += extra_desc
346    if self.pmu:
347      if self.desc and not self.desc.endswith('. '):
348        self.desc += '. '
349      self.desc = (self.desc if self.desc else '') + ('Unit: ' + self.pmu + ' ')
350    if arch_std and arch_std.lower() in _arch_std_events:
351      event = _arch_std_events[arch_std.lower()].event
352      # Copy from the architecture standard event to self for undefined fields.
353      for attr, value in _arch_std_events[arch_std.lower()].__dict__.items():
354        if hasattr(self, attr) and not getattr(self, attr):
355          setattr(self, attr, value)
356
357    self.event = real_event(self.name, event)
358
359  def __repr__(self) -> str:
360    """String representation primarily for debugging."""
361    s = '{\n'
362    for attr, value in self.__dict__.items():
363      if value:
364        s += f'\t{attr} = {value},\n'
365    return s + '}'
366
367  def build_c_string(self, metric: bool) -> str:
368    s = ''
369    for attr in _json_metric_attributes if metric else _json_event_attributes:
370      x = getattr(self, attr)
371      if metric and x and attr == 'metric_expr':
372        # Convert parsed metric expressions into a string. Slashes
373        # must be doubled in the file.
374        x = x.ToPerfJson().replace('\\', '\\\\')
375      if metric and x and attr == 'metric_threshold':
376        x = x.replace('\\', '\\\\')
377      if attr in _json_enum_attributes:
378        s += x if x else '0'
379      else:
380        s += f'{x}\\000' if x else '\\000'
381    return s
382
383  def to_c_string(self, metric: bool) -> str:
384    """Representation of the event as a C struct initializer."""
385
386    s = self.build_c_string(metric)
387    return f'{{ { _bcs.offsets[s] } }}, /* {s} */\n'
388
389
390@lru_cache(maxsize=None)
391def read_json_events(path: str, topic: str) -> Sequence[JsonEvent]:
392  """Read json events from the specified file."""
393  try:
394    events = json.load(open(path), object_hook=JsonEvent)
395  except BaseException as err:
396    print(f"Exception processing {path}")
397    raise
398  metrics: list[Tuple[str, str, metric.Expression]] = []
399  for event in events:
400    event.topic = topic
401    if event.metric_name and '-' not in event.metric_name:
402      metrics.append((event.pmu, event.metric_name, event.metric_expr))
403  updates = metric.RewriteMetricsInTermsOfOthers(metrics)
404  if updates:
405    for event in events:
406      if event.metric_name in updates:
407        # print(f'Updated {event.metric_name} from\n"{event.metric_expr}"\n'
408        #       f'to\n"{updates[event.metric_name]}"')
409        event.metric_expr = updates[event.metric_name]
410
411  return events
412
413def preprocess_arch_std_files(archpath: str) -> None:
414  """Read in all architecture standard events."""
415  global _arch_std_events
416  for item in os.scandir(archpath):
417    if item.is_file() and item.name.endswith('.json'):
418      for event in read_json_events(item.path, topic=''):
419        if event.name:
420          _arch_std_events[event.name.lower()] = event
421        if event.metric_name:
422          _arch_std_events[event.metric_name.lower()] = event
423
424
425def add_events_table_entries(item: os.DirEntry, topic: str) -> None:
426  """Add contents of file to _pending_events table."""
427  for e in read_json_events(item.path, topic):
428    if e.name:
429      _pending_events.append(e)
430    if e.metric_name:
431      _pending_metrics.append(e)
432
433
434def print_pending_events() -> None:
435  """Optionally close events table."""
436
437  def event_cmp_key(j: JsonEvent) -> Tuple[bool, str, str, str, str]:
438    def fix_none(s: Optional[str]) -> str:
439      if s is None:
440        return ''
441      return s
442
443    return (j.desc is not None, fix_none(j.topic), fix_none(j.name), fix_none(j.pmu),
444            fix_none(j.metric_name))
445
446  global _pending_events
447  if not _pending_events:
448    return
449
450  global _pending_events_tblname
451  if _pending_events_tblname.endswith('_sys'):
452    global _sys_event_tables
453    _sys_event_tables.append(_pending_events_tblname)
454  else:
455    global event_tables
456    _event_tables.append(_pending_events_tblname)
457
458  _args.output_file.write(
459      f'static const struct compact_pmu_event {_pending_events_tblname}[] = {{\n')
460
461  for event in sorted(_pending_events, key=event_cmp_key):
462    _args.output_file.write(event.to_c_string(metric=False))
463  _pending_events = []
464
465  _args.output_file.write('};\n\n')
466
467def print_pending_metrics() -> None:
468  """Optionally close metrics table."""
469
470  def metric_cmp_key(j: JsonEvent) -> Tuple[bool, str, str]:
471    def fix_none(s: Optional[str]) -> str:
472      if s is None:
473        return ''
474      return s
475
476    return (j.desc is not None, fix_none(j.pmu), fix_none(j.metric_name))
477
478  global _pending_metrics
479  if not _pending_metrics:
480    return
481
482  global _pending_metrics_tblname
483  if _pending_metrics_tblname.endswith('_sys'):
484    global _sys_metric_tables
485    _sys_metric_tables.append(_pending_metrics_tblname)
486  else:
487    global metric_tables
488    _metric_tables.append(_pending_metrics_tblname)
489
490  _args.output_file.write(
491      f'static const struct compact_pmu_event {_pending_metrics_tblname}[] = {{\n')
492
493  for metric in sorted(_pending_metrics, key=metric_cmp_key):
494    _args.output_file.write(metric.to_c_string(metric=True))
495  _pending_metrics = []
496
497  _args.output_file.write('};\n\n')
498
499def get_topic(topic: str) -> str:
500  if topic.endswith('metrics.json'):
501    return 'metrics'
502  return removesuffix(topic, '.json').replace('-', ' ')
503
504def preprocess_one_file(parents: Sequence[str], item: os.DirEntry) -> None:
505
506  if item.is_dir():
507    return
508
509  # base dir or too deep
510  level = len(parents)
511  if level == 0 or level > 4:
512    return
513
514  # Ignore other directories. If the file name does not have a .json
515  # extension, ignore it. It could be a readme.txt for instance.
516  if not item.is_file() or not item.name.endswith('.json'):
517    return
518
519  if item.name == 'metricgroups.json':
520    metricgroup_descriptions = json.load(open(item.path))
521    for mgroup in metricgroup_descriptions:
522      assert len(mgroup) > 1, parents
523      description = f"{metricgroup_descriptions[mgroup]}\\000"
524      mgroup = f"{mgroup}\\000"
525      _bcs.add(mgroup)
526      _bcs.add(description)
527      _metricgroups[mgroup] = description
528    return
529
530  topic = get_topic(item.name)
531  for event in read_json_events(item.path, topic):
532    if event.name:
533      _bcs.add(event.build_c_string(metric=False))
534    if event.metric_name:
535      _bcs.add(event.build_c_string(metric=True))
536
537def process_one_file(parents: Sequence[str], item: os.DirEntry) -> None:
538  """Process a JSON file during the main walk."""
539  def is_leaf_dir(path: str) -> bool:
540    for item in os.scandir(path):
541      if item.is_dir():
542        return False
543    return True
544
545  # model directory, reset topic
546  if item.is_dir() and is_leaf_dir(item.path):
547    print_pending_events()
548    print_pending_metrics()
549
550    global _pending_events_tblname
551    _pending_events_tblname = file_name_to_table_name('pmu_events_', parents, item.name)
552    global _pending_metrics_tblname
553    _pending_metrics_tblname = file_name_to_table_name('pmu_metrics_', parents, item.name)
554
555    if item.name == 'sys':
556      _sys_event_table_to_metric_table_mapping[_pending_events_tblname] = _pending_metrics_tblname
557    return
558
559  # base dir or too deep
560  level = len(parents)
561  if level == 0 or level > 4:
562    return
563
564  # Ignore other directories. If the file name does not have a .json
565  # extension, ignore it. It could be a readme.txt for instance.
566  if not item.is_file() or not item.name.endswith('.json') or item.name == 'metricgroups.json':
567    return
568
569  add_events_table_entries(item, get_topic(item.name))
570
571
572def print_mapping_table(archs: Sequence[str]) -> None:
573  """Read the mapfile and generate the struct from cpuid string to event table."""
574  _args.output_file.write("""
575/* Struct used to make the PMU event table implementation opaque to callers. */
576struct pmu_events_table {
577        const struct compact_pmu_event *entries;
578        size_t length;
579};
580
581/* Struct used to make the PMU metric table implementation opaque to callers. */
582struct pmu_metrics_table {
583        const struct compact_pmu_event *entries;
584        size_t length;
585};
586
587/*
588 * Map a CPU to its table of PMU events. The CPU is identified by the
589 * cpuid field, which is an arch-specific identifier for the CPU.
590 * The identifier specified in tools/perf/pmu-events/arch/xxx/mapfile
591 * must match the get_cpuid_str() in tools/perf/arch/xxx/util/header.c)
592 *
593 * The  cpuid can contain any character other than the comma.
594 */
595struct pmu_events_map {
596        const char *arch;
597        const char *cpuid;
598        struct pmu_events_table event_table;
599        struct pmu_metrics_table metric_table;
600};
601
602/*
603 * Global table mapping each known CPU for the architecture to its
604 * table of PMU events.
605 */
606const struct pmu_events_map pmu_events_map[] = {
607""")
608  for arch in archs:
609    if arch == 'test':
610      _args.output_file.write("""{
611\t.arch = "testarch",
612\t.cpuid = "testcpu",
613\t.event_table = {
614\t\t.entries = pmu_events__test_soc_cpu,
615\t\t.length = ARRAY_SIZE(pmu_events__test_soc_cpu),
616\t},
617\t.metric_table = {
618\t\t.entries = pmu_metrics__test_soc_cpu,
619\t\t.length = ARRAY_SIZE(pmu_metrics__test_soc_cpu),
620\t}
621},
622""")
623    else:
624      with open(f'{_args.starting_dir}/{arch}/mapfile.csv') as csvfile:
625        table = csv.reader(csvfile)
626        first = True
627        for row in table:
628          # Skip the first row or any row beginning with #.
629          if not first and len(row) > 0 and not row[0].startswith('#'):
630            event_tblname = file_name_to_table_name('pmu_events_', [], row[2].replace('/', '_'))
631            if event_tblname in _event_tables:
632              event_size = f'ARRAY_SIZE({event_tblname})'
633            else:
634              event_tblname = 'NULL'
635              event_size = '0'
636            metric_tblname = file_name_to_table_name('pmu_metrics_', [], row[2].replace('/', '_'))
637            if metric_tblname in _metric_tables:
638              metric_size = f'ARRAY_SIZE({metric_tblname})'
639            else:
640              metric_tblname = 'NULL'
641              metric_size = '0'
642            if event_size == '0' and metric_size == '0':
643              continue
644            cpuid = row[0].replace('\\', '\\\\')
645            _args.output_file.write(f"""{{
646\t.arch = "{arch}",
647\t.cpuid = "{cpuid}",
648\t.event_table = {{
649\t\t.entries = {event_tblname},
650\t\t.length = {event_size}
651\t}},
652\t.metric_table = {{
653\t\t.entries = {metric_tblname},
654\t\t.length = {metric_size}
655\t}}
656}},
657""")
658          first = False
659
660  _args.output_file.write("""{
661\t.arch = 0,
662\t.cpuid = 0,
663\t.event_table = { 0, 0 },
664\t.metric_table = { 0, 0 },
665}
666};
667""")
668
669
670def print_system_mapping_table() -> None:
671  """C struct mapping table array for tables from /sys directories."""
672  _args.output_file.write("""
673struct pmu_sys_events {
674\tconst char *name;
675\tstruct pmu_events_table event_table;
676\tstruct pmu_metrics_table metric_table;
677};
678
679static const struct pmu_sys_events pmu_sys_event_tables[] = {
680""")
681  printed_metric_tables = []
682  for tblname in _sys_event_tables:
683    _args.output_file.write(f"""\t{{
684\t\t.event_table = {{
685\t\t\t.entries = {tblname},
686\t\t\t.length = ARRAY_SIZE({tblname})
687\t\t}},""")
688    metric_tblname = _sys_event_table_to_metric_table_mapping[tblname]
689    if metric_tblname in _sys_metric_tables:
690      _args.output_file.write(f"""
691\t\t.metric_table = {{
692\t\t\t.entries = {metric_tblname},
693\t\t\t.length = ARRAY_SIZE({metric_tblname})
694\t\t}},""")
695      printed_metric_tables.append(metric_tblname)
696    _args.output_file.write(f"""
697\t\t.name = \"{tblname}\",
698\t}},
699""")
700  for tblname in _sys_metric_tables:
701    if tblname in printed_metric_tables:
702      continue
703    _args.output_file.write(f"""\t{{
704\t\t.metric_table = {{
705\t\t\t.entries = {tblname},
706\t\t\t.length = ARRAY_SIZE({tblname})
707\t\t}},
708\t\t.name = \"{tblname}\",
709\t}},
710""")
711  _args.output_file.write("""\t{
712\t\t.event_table = { 0, 0 },
713\t\t.metric_table = { 0, 0 },
714\t},
715};
716
717static void decompress_event(int offset, struct pmu_event *pe)
718{
719\tconst char *p = &big_c_string[offset];
720""")
721  for attr in _json_event_attributes:
722    _args.output_file.write(f'\n\tpe->{attr} = ')
723    if attr in _json_enum_attributes:
724      _args.output_file.write("*p - '0';\n")
725    else:
726      _args.output_file.write("(*p == '\\0' ? NULL : p);\n")
727    if attr == _json_event_attributes[-1]:
728      continue
729    if attr in _json_enum_attributes:
730      _args.output_file.write('\tp++;')
731    else:
732      _args.output_file.write('\twhile (*p++);')
733  _args.output_file.write("""}
734
735static void decompress_metric(int offset, struct pmu_metric *pm)
736{
737\tconst char *p = &big_c_string[offset];
738""")
739  for attr in _json_metric_attributes:
740    _args.output_file.write(f'\n\tpm->{attr} = ')
741    if attr in _json_enum_attributes:
742      _args.output_file.write("*p - '0';\n")
743    else:
744      _args.output_file.write("(*p == '\\0' ? NULL : p);\n")
745    if attr == _json_metric_attributes[-1]:
746      continue
747    if attr in _json_enum_attributes:
748      _args.output_file.write('\tp++;')
749    else:
750      _args.output_file.write('\twhile (*p++);')
751  _args.output_file.write("""}
752
753int pmu_events_table_for_each_event(const struct pmu_events_table *table,
754                                    pmu_event_iter_fn fn,
755                                    void *data)
756{
757        for (size_t i = 0; i < table->length; i++) {
758                struct pmu_event pe;
759                int ret;
760
761                decompress_event(table->entries[i].offset, &pe);
762                if (!pe.name)
763                        continue;
764                ret = fn(&pe, table, data);
765                if (ret)
766                        return ret;
767        }
768        return 0;
769}
770
771int pmu_metrics_table_for_each_metric(const struct pmu_metrics_table *table,
772                                     pmu_metric_iter_fn fn,
773                                     void *data)
774{
775        for (size_t i = 0; i < table->length; i++) {
776                struct pmu_metric pm;
777                int ret;
778
779                decompress_metric(table->entries[i].offset, &pm);
780                if (!pm.metric_expr)
781                        continue;
782                ret = fn(&pm, table, data);
783                if (ret)
784                        return ret;
785        }
786        return 0;
787}
788
789const struct pmu_events_table *perf_pmu__find_events_table(struct perf_pmu *pmu)
790{
791        const struct pmu_events_table *table = NULL;
792        char *cpuid = perf_pmu__getcpuid(pmu);
793        int i;
794
795        /* on some platforms which uses cpus map, cpuid can be NULL for
796         * PMUs other than CORE PMUs.
797         */
798        if (!cpuid)
799                return NULL;
800
801        i = 0;
802        for (;;) {
803                const struct pmu_events_map *map = &pmu_events_map[i++];
804                if (!map->arch)
805                        break;
806
807                if (!strcmp_cpuid_str(map->cpuid, cpuid)) {
808                        table = &map->event_table;
809                        break;
810                }
811        }
812        free(cpuid);
813        return table;
814}
815
816const struct pmu_metrics_table *perf_pmu__find_metrics_table(struct perf_pmu *pmu)
817{
818        const struct pmu_metrics_table *table = NULL;
819        char *cpuid = perf_pmu__getcpuid(pmu);
820        int i;
821
822        /* on some platforms which uses cpus map, cpuid can be NULL for
823         * PMUs other than CORE PMUs.
824         */
825        if (!cpuid)
826                return NULL;
827
828        i = 0;
829        for (;;) {
830                const struct pmu_events_map *map = &pmu_events_map[i++];
831                if (!map->arch)
832                        break;
833
834                if (!strcmp_cpuid_str(map->cpuid, cpuid)) {
835                        table = &map->metric_table;
836                        break;
837                }
838        }
839        free(cpuid);
840        return table;
841}
842
843const struct pmu_events_table *find_core_events_table(const char *arch, const char *cpuid)
844{
845        for (const struct pmu_events_map *tables = &pmu_events_map[0];
846             tables->arch;
847             tables++) {
848                if (!strcmp(tables->arch, arch) && !strcmp_cpuid_str(tables->cpuid, cpuid))
849                        return &tables->event_table;
850        }
851        return NULL;
852}
853
854const struct pmu_metrics_table *find_core_metrics_table(const char *arch, const char *cpuid)
855{
856        for (const struct pmu_events_map *tables = &pmu_events_map[0];
857             tables->arch;
858             tables++) {
859                if (!strcmp(tables->arch, arch) && !strcmp_cpuid_str(tables->cpuid, cpuid))
860                        return &tables->metric_table;
861        }
862        return NULL;
863}
864
865int pmu_for_each_core_event(pmu_event_iter_fn fn, void *data)
866{
867        for (const struct pmu_events_map *tables = &pmu_events_map[0];
868             tables->arch;
869             tables++) {
870                int ret = pmu_events_table_for_each_event(&tables->event_table, fn, data);
871
872                if (ret)
873                        return ret;
874        }
875        return 0;
876}
877
878int pmu_for_each_core_metric(pmu_metric_iter_fn fn, void *data)
879{
880        for (const struct pmu_events_map *tables = &pmu_events_map[0];
881             tables->arch;
882             tables++) {
883                int ret = pmu_metrics_table_for_each_metric(&tables->metric_table, fn, data);
884
885                if (ret)
886                        return ret;
887        }
888        return 0;
889}
890
891const struct pmu_events_table *find_sys_events_table(const char *name)
892{
893        for (const struct pmu_sys_events *tables = &pmu_sys_event_tables[0];
894             tables->name;
895             tables++) {
896                if (!strcmp(tables->name, name))
897                        return &tables->event_table;
898        }
899        return NULL;
900}
901
902int pmu_for_each_sys_event(pmu_event_iter_fn fn, void *data)
903{
904        for (const struct pmu_sys_events *tables = &pmu_sys_event_tables[0];
905             tables->name;
906             tables++) {
907                int ret = pmu_events_table_for_each_event(&tables->event_table, fn, data);
908
909                if (ret)
910                        return ret;
911        }
912        return 0;
913}
914
915int pmu_for_each_sys_metric(pmu_metric_iter_fn fn, void *data)
916{
917        for (const struct pmu_sys_events *tables = &pmu_sys_event_tables[0];
918             tables->name;
919             tables++) {
920                int ret = pmu_metrics_table_for_each_metric(&tables->metric_table, fn, data);
921
922                if (ret)
923                        return ret;
924        }
925        return 0;
926}
927""")
928
929def print_metricgroups() -> None:
930  _args.output_file.write("""
931static const int metricgroups[][2] = {
932""")
933  for mgroup in sorted(_metricgroups):
934    description = _metricgroups[mgroup]
935    _args.output_file.write(
936        f'\t{{ {_bcs.offsets[mgroup]}, {_bcs.offsets[description]} }}, /* {mgroup} => {description} */\n'
937    )
938  _args.output_file.write("""
939};
940
941const char *describe_metricgroup(const char *group)
942{
943        int low = 0, high = (int)ARRAY_SIZE(metricgroups) - 1;
944
945        while (low <= high) {
946                int mid = (low + high) / 2;
947                const char *mgroup = &big_c_string[metricgroups[mid][0]];
948                int cmp = strcmp(mgroup, group);
949
950                if (cmp == 0) {
951                        return &big_c_string[metricgroups[mid][1]];
952                } else if (cmp < 0) {
953                        low = mid + 1;
954                } else {
955                        high = mid - 1;
956                }
957        }
958        return NULL;
959}
960""")
961
962def main() -> None:
963  global _args
964
965  def dir_path(path: str) -> str:
966    """Validate path is a directory for argparse."""
967    if os.path.isdir(path):
968      return path
969    raise argparse.ArgumentTypeError(f'\'{path}\' is not a valid directory')
970
971  def ftw(path: str, parents: Sequence[str],
972          action: Callable[[Sequence[str], os.DirEntry], None]) -> None:
973    """Replicate the directory/file walking behavior of C's file tree walk."""
974    for item in sorted(os.scandir(path), key=lambda e: e.name):
975      if _args.model != 'all' and item.is_dir():
976        # Check if the model matches one in _args.model.
977        if len(parents) == _args.model.split(',')[0].count('/'):
978          # We're testing the correct directory.
979          item_path = '/'.join(parents) + ('/' if len(parents) > 0 else '') + item.name
980          if 'test' not in item_path and item_path not in _args.model.split(','):
981            continue
982      action(parents, item)
983      if item.is_dir():
984        ftw(item.path, parents + [item.name], action)
985
986  ap = argparse.ArgumentParser()
987  ap.add_argument('arch', help='Architecture name like x86')
988  ap.add_argument('model', help='''Select a model such as skylake to
989reduce the code size.  Normally set to "all". For architectures like
990ARM64 with an implementor/model, the model must include the implementor
991such as "arm/cortex-a34".''',
992                  default='all')
993  ap.add_argument(
994      'starting_dir',
995      type=dir_path,
996      help='Root of tree containing architecture directories containing json files'
997  )
998  ap.add_argument(
999      'output_file', type=argparse.FileType('w', encoding='utf-8'), nargs='?', default=sys.stdout)
1000  _args = ap.parse_args()
1001
1002  _args.output_file.write("""
1003#include <pmu-events/pmu-events.h>
1004#include "util/header.h"
1005#include "util/pmu.h"
1006#include <string.h>
1007#include <stddef.h>
1008
1009struct compact_pmu_event {
1010  int offset;
1011};
1012
1013""")
1014  archs = []
1015  for item in os.scandir(_args.starting_dir):
1016    if not item.is_dir():
1017      continue
1018    if item.name == _args.arch or _args.arch == 'all' or item.name == 'test':
1019      archs.append(item.name)
1020
1021  if len(archs) < 2:
1022    raise IOError(f'Missing architecture directory \'{_args.arch}\'')
1023
1024  archs.sort()
1025  for arch in archs:
1026    arch_path = f'{_args.starting_dir}/{arch}'
1027    preprocess_arch_std_files(arch_path)
1028    ftw(arch_path, [], preprocess_one_file)
1029
1030  _bcs.compute()
1031  _args.output_file.write('static const char *const big_c_string =\n')
1032  for s in _bcs.big_string:
1033    _args.output_file.write(s)
1034  _args.output_file.write(';\n\n')
1035  for arch in archs:
1036    arch_path = f'{_args.starting_dir}/{arch}'
1037    ftw(arch_path, [], process_one_file)
1038    print_pending_events()
1039    print_pending_metrics()
1040
1041  print_mapping_table(archs)
1042  print_system_mapping_table()
1043  print_metricgroups()
1044
1045if __name__ == '__main__':
1046  main()
1047