1046 lines
34 KiB
Python
Executable File
1046 lines
34 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
# SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause)
|
|
"""Convert directories of JSON events to C code."""
|
|
import argparse
|
|
import csv
|
|
from functools import lru_cache
|
|
import json
|
|
import metric
|
|
import os
|
|
import sys
|
|
from typing import (Callable, Dict, Optional, Sequence, Set, Tuple)
|
|
import collections
|
|
|
|
# Global command line arguments.
|
|
_args = None
|
|
# List of regular event tables.
|
|
_event_tables = []
|
|
# List of event tables generated from "/sys" directories.
|
|
_sys_event_tables = []
|
|
# List of regular metric tables.
|
|
_metric_tables = []
|
|
# List of metric tables generated from "/sys" directories.
|
|
_sys_metric_tables = []
|
|
# Mapping between sys event table names and sys metric table names.
|
|
_sys_event_table_to_metric_table_mapping = {}
|
|
# Map from an event name to an architecture standard
|
|
# JsonEvent. Architecture standard events are in json files in the top
|
|
# f'{_args.starting_dir}/{_args.arch}' directory.
|
|
_arch_std_events = {}
|
|
# Events to write out when the table is closed
|
|
_pending_events = []
|
|
# Name of events table to be written out
|
|
_pending_events_tblname = None
|
|
# Metrics to write out when the table is closed
|
|
_pending_metrics = []
|
|
# Name of metrics table to be written out
|
|
_pending_metrics_tblname = None
|
|
# Global BigCString shared by all structures.
|
|
_bcs = None
|
|
# Map from the name of a metric group to a description of the group.
|
|
_metricgroups = {}
|
|
# Order specific JsonEvent attributes will be visited.
|
|
_json_event_attributes = [
|
|
# cmp_sevent related attributes.
|
|
'name', 'pmu', 'topic', 'desc',
|
|
# Seems useful, put it early.
|
|
'event',
|
|
# Short things in alphabetical order.
|
|
'compat', 'deprecated', 'perpkg', 'unit',
|
|
# Longer things (the last won't be iterated over during decompress).
|
|
'long_desc'
|
|
]
|
|
|
|
# Attributes that are in pmu_metric rather than pmu_event.
|
|
_json_metric_attributes = [
|
|
'pmu', 'metric_name', 'metric_group', 'metric_expr', 'metric_threshold',
|
|
'desc', 'long_desc', 'unit', 'compat', 'metricgroup_no_group',
|
|
'default_metricgroup_name', 'aggr_mode', 'event_grouping'
|
|
]
|
|
# Attributes that are bools or enum int values, encoded as '0', '1',...
|
|
_json_enum_attributes = ['aggr_mode', 'deprecated', 'event_grouping', 'perpkg']
|
|
|
|
def removesuffix(s: str, suffix: str) -> str:
|
|
"""Remove the suffix from a string
|
|
|
|
The removesuffix function is added to str in Python 3.9. We aim for 3.6
|
|
compatibility and so provide our own function here.
|
|
"""
|
|
return s[0:-len(suffix)] if s.endswith(suffix) else s
|
|
|
|
|
|
def file_name_to_table_name(prefix: str, parents: Sequence[str],
|
|
dirname: str) -> str:
|
|
"""Generate a C table name from directory names."""
|
|
tblname = prefix
|
|
for p in parents:
|
|
tblname += '_' + p
|
|
tblname += '_' + dirname
|
|
return tblname.replace('-', '_')
|
|
|
|
|
|
def c_len(s: str) -> int:
|
|
"""Return the length of s a C string
|
|
|
|
This doesn't handle all escape characters properly. It first assumes
|
|
all \ are for escaping, it then adjusts as it will have over counted
|
|
\\. The code uses \000 rather than \0 as a terminator as an adjacent
|
|
number would be folded into a string of \0 (ie. "\0" + "5" doesn't
|
|
equal a terminator followed by the number 5 but the escape of
|
|
\05). The code adjusts for \000 but not properly for all octal, hex
|
|
or unicode values.
|
|
"""
|
|
try:
|
|
utf = s.encode(encoding='utf-8',errors='strict')
|
|
except:
|
|
print(f'broken string {s}')
|
|
raise
|
|
return len(utf) - utf.count(b'\\') + utf.count(b'\\\\') - (utf.count(b'\\000') * 2)
|
|
|
|
class BigCString:
|
|
"""A class to hold many strings concatenated together.
|
|
|
|
Generating a large number of stand-alone C strings creates a large
|
|
number of relocations in position independent code. The BigCString
|
|
is a helper for this case. It builds a single string which within it
|
|
are all the other C strings (to avoid memory issues the string
|
|
itself is held as a list of strings). The offsets within the big
|
|
string are recorded and when stored to disk these don't need
|
|
relocation. To reduce the size of the string further, identical
|
|
strings are merged. If a longer string ends-with the same value as a
|
|
shorter string, these entries are also merged.
|
|
"""
|
|
strings: Set[str]
|
|
big_string: Sequence[str]
|
|
offsets: Dict[str, int]
|
|
|
|
def __init__(self):
|
|
self.strings = set()
|
|
|
|
def add(self, s: str) -> None:
|
|
"""Called to add to the big string."""
|
|
self.strings.add(s)
|
|
|
|
def compute(self) -> None:
|
|
"""Called once all strings are added to compute the string and offsets."""
|
|
|
|
folded_strings = {}
|
|
# Determine if two strings can be folded, ie. let 1 string use the
|
|
# end of another. First reverse all strings and sort them.
|
|
sorted_reversed_strings = sorted([x[::-1] for x in self.strings])
|
|
|
|
# Strings 'xyz' and 'yz' will now be [ 'zy', 'zyx' ]. Scan forward
|
|
# for each string to see if there is a better candidate to fold it
|
|
# into, in the example rather than using 'yz' we can use'xyz' at
|
|
# an offset of 1. We record which string can be folded into which
|
|
# in folded_strings, we don't need to record the offset as it is
|
|
# trivially computed from the string lengths.
|
|
for pos,s in enumerate(sorted_reversed_strings):
|
|
best_pos = pos
|
|
for check_pos in range(pos + 1, len(sorted_reversed_strings)):
|
|
if sorted_reversed_strings[check_pos].startswith(s):
|
|
best_pos = check_pos
|
|
else:
|
|
break
|
|
if pos != best_pos:
|
|
folded_strings[s[::-1]] = sorted_reversed_strings[best_pos][::-1]
|
|
|
|
# Compute reverse mappings for debugging.
|
|
fold_into_strings = collections.defaultdict(set)
|
|
for key, val in folded_strings.items():
|
|
if key != val:
|
|
fold_into_strings[val].add(key)
|
|
|
|
# big_string_offset is the current location within the C string
|
|
# being appended to - comments, etc. don't count. big_string is
|
|
# the string contents represented as a list. Strings are immutable
|
|
# in Python and so appending to one causes memory issues, while
|
|
# lists are mutable.
|
|
big_string_offset = 0
|
|
self.big_string = []
|
|
self.offsets = {}
|
|
|
|
# Emit all strings that aren't folded in a sorted manner.
|
|
for s in sorted(self.strings):
|
|
if s not in folded_strings:
|
|
self.offsets[s] = big_string_offset
|
|
self.big_string.append(f'/* offset={big_string_offset} */ "')
|
|
self.big_string.append(s)
|
|
self.big_string.append('"')
|
|
if s in fold_into_strings:
|
|
self.big_string.append(' /* also: ' + ', '.join(fold_into_strings[s]) + ' */')
|
|
self.big_string.append('\n')
|
|
big_string_offset += c_len(s)
|
|
continue
|
|
|
|
# Compute the offsets of the folded strings.
|
|
for s in folded_strings.keys():
|
|
assert s not in self.offsets
|
|
folded_s = folded_strings[s]
|
|
self.offsets[s] = self.offsets[folded_s] + c_len(folded_s) - c_len(s)
|
|
|
|
_bcs = BigCString()
|
|
|
|
class JsonEvent:
|
|
"""Representation of an event loaded from a json file dictionary."""
|
|
|
|
def __init__(self, jd: dict):
|
|
"""Constructor passed the dictionary of parsed json values."""
|
|
|
|
def llx(x: int) -> str:
|
|
"""Convert an int to a string similar to a printf modifier of %#llx."""
|
|
return '0' if x == 0 else hex(x)
|
|
|
|
def fixdesc(s: str) -> str:
|
|
"""Fix formatting issue for the desc string."""
|
|
if s is None:
|
|
return None
|
|
return removesuffix(removesuffix(removesuffix(s, '. '),
|
|
'. '), '.').replace('\n', '\\n').replace(
|
|
'\"', '\\"').replace('\r', '\\r')
|
|
|
|
def convert_aggr_mode(aggr_mode: str) -> Optional[str]:
|
|
"""Returns the aggr_mode_class enum value associated with the JSON string."""
|
|
if not aggr_mode:
|
|
return None
|
|
aggr_mode_to_enum = {
|
|
'PerChip': '1',
|
|
'PerCore': '2',
|
|
}
|
|
return aggr_mode_to_enum[aggr_mode]
|
|
|
|
def convert_metric_constraint(metric_constraint: str) -> Optional[str]:
|
|
"""Returns the metric_event_groups enum value associated with the JSON string."""
|
|
if not metric_constraint:
|
|
return None
|
|
metric_constraint_to_enum = {
|
|
'NO_GROUP_EVENTS': '1',
|
|
'NO_GROUP_EVENTS_NMI': '2',
|
|
'NO_NMI_WATCHDOG': '2',
|
|
'NO_GROUP_EVENTS_SMT': '3',
|
|
}
|
|
return metric_constraint_to_enum[metric_constraint]
|
|
|
|
def lookup_msr(num: str) -> Optional[str]:
|
|
"""Converts the msr number, or first in a list to the appropriate event field."""
|
|
if not num:
|
|
return None
|
|
msrmap = {
|
|
0x3F6: 'ldlat=',
|
|
0x1A6: 'offcore_rsp=',
|
|
0x1A7: 'offcore_rsp=',
|
|
0x3F7: 'frontend=',
|
|
}
|
|
return msrmap[int(num.split(',', 1)[0], 0)]
|
|
|
|
def real_event(name: str, event: str) -> Optional[str]:
|
|
"""Convert well known event names to an event string otherwise use the event argument."""
|
|
fixed = {
|
|
'inst_retired.any': 'event=0xc0,period=2000003',
|
|
'inst_retired.any_p': 'event=0xc0,period=2000003',
|
|
'cpu_clk_unhalted.ref': 'event=0x0,umask=0x03,period=2000003',
|
|
'cpu_clk_unhalted.thread': 'event=0x3c,period=2000003',
|
|
'cpu_clk_unhalted.core': 'event=0x3c,period=2000003',
|
|
'cpu_clk_unhalted.thread_any': 'event=0x3c,any=1,period=2000003',
|
|
}
|
|
if not name:
|
|
return None
|
|
if name.lower() in fixed:
|
|
return fixed[name.lower()]
|
|
return event
|
|
|
|
def unit_to_pmu(unit: str) -> Optional[str]:
|
|
"""Convert a JSON Unit to Linux PMU name."""
|
|
if not unit:
|
|
return None
|
|
# Comment brought over from jevents.c:
|
|
# it's not realistic to keep adding these, we need something more scalable ...
|
|
table = {
|
|
'CBO': 'uncore_cbox',
|
|
'QPI LL': 'uncore_qpi',
|
|
'SBO': 'uncore_sbox',
|
|
'iMPH-U': 'uncore_arb',
|
|
'CPU-M-CF': 'cpum_cf',
|
|
'CPU-M-SF': 'cpum_sf',
|
|
'PAI-CRYPTO' : 'pai_crypto',
|
|
'PAI-EXT' : 'pai_ext',
|
|
'UPI LL': 'uncore_upi',
|
|
'hisi_sicl,cpa': 'hisi_sicl,cpa',
|
|
'hisi_sccl,ddrc': 'hisi_sccl,ddrc',
|
|
'hisi_sccl,hha': 'hisi_sccl,hha',
|
|
'hisi_sccl,l3c': 'hisi_sccl,l3c',
|
|
'imx8_ddr': 'imx8_ddr',
|
|
'L3PMC': 'amd_l3',
|
|
'DFPMC': 'amd_df',
|
|
'cpu_core': 'cpu_core',
|
|
'cpu_atom': 'cpu_atom',
|
|
}
|
|
return table[unit] if unit in table else f'uncore_{unit.lower()}'
|
|
|
|
eventcode = 0
|
|
if 'EventCode' in jd:
|
|
eventcode = int(jd['EventCode'].split(',', 1)[0], 0)
|
|
if 'ExtSel' in jd:
|
|
eventcode |= int(jd['ExtSel']) << 8
|
|
configcode = int(jd['ConfigCode'], 0) if 'ConfigCode' in jd else None
|
|
self.name = jd['EventName'].lower() if 'EventName' in jd else None
|
|
self.topic = ''
|
|
self.compat = jd.get('Compat')
|
|
self.desc = fixdesc(jd.get('BriefDescription'))
|
|
self.long_desc = fixdesc(jd.get('PublicDescription'))
|
|
precise = jd.get('PEBS')
|
|
msr = lookup_msr(jd.get('MSRIndex'))
|
|
msrval = jd.get('MSRValue')
|
|
extra_desc = ''
|
|
if 'Data_LA' in jd:
|
|
extra_desc += ' Supports address when precise'
|
|
if 'Errata' in jd:
|
|
extra_desc += '.'
|
|
if 'Errata' in jd:
|
|
extra_desc += ' Spec update: ' + jd['Errata']
|
|
self.pmu = unit_to_pmu(jd.get('Unit'))
|
|
filter = jd.get('Filter')
|
|
self.unit = jd.get('ScaleUnit')
|
|
self.perpkg = jd.get('PerPkg')
|
|
self.aggr_mode = convert_aggr_mode(jd.get('AggregationMode'))
|
|
self.deprecated = jd.get('Deprecated')
|
|
self.metric_name = jd.get('MetricName')
|
|
self.metric_group = jd.get('MetricGroup')
|
|
self.metricgroup_no_group = jd.get('MetricgroupNoGroup')
|
|
self.default_metricgroup_name = jd.get('DefaultMetricgroupName')
|
|
self.event_grouping = convert_metric_constraint(jd.get('MetricConstraint'))
|
|
self.metric_expr = None
|
|
if 'MetricExpr' in jd:
|
|
self.metric_expr = metric.ParsePerfJson(jd['MetricExpr']).Simplify()
|
|
# Note, the metric formula for the threshold isn't parsed as the &
|
|
# and > have incorrect precedence.
|
|
self.metric_threshold = jd.get('MetricThreshold')
|
|
|
|
arch_std = jd.get('ArchStdEvent')
|
|
if precise and self.desc and '(Precise Event)' not in self.desc:
|
|
extra_desc += ' (Must be precise)' if precise == '2' else (' (Precise '
|
|
'event)')
|
|
event = f'config={llx(configcode)}' if configcode is not None else f'event={llx(eventcode)}'
|
|
event_fields = [
|
|
('AnyThread', 'any='),
|
|
('PortMask', 'ch_mask='),
|
|
('CounterMask', 'cmask='),
|
|
('EdgeDetect', 'edge='),
|
|
('FCMask', 'fc_mask='),
|
|
('Invert', 'inv='),
|
|
('SampleAfterValue', 'period='),
|
|
('UMask', 'umask='),
|
|
]
|
|
for key, value in event_fields:
|
|
if key in jd and jd[key] != '0':
|
|
event += ',' + value + jd[key]
|
|
if filter:
|
|
event += f',{filter}'
|
|
if msr:
|
|
event += f',{msr}{msrval}'
|
|
if self.desc and extra_desc:
|
|
self.desc += extra_desc
|
|
if self.long_desc and extra_desc:
|
|
self.long_desc += extra_desc
|
|
if self.pmu:
|
|
if self.desc and not self.desc.endswith('. '):
|
|
self.desc += '. '
|
|
self.desc = (self.desc if self.desc else '') + ('Unit: ' + self.pmu + ' ')
|
|
if arch_std and arch_std.lower() in _arch_std_events:
|
|
event = _arch_std_events[arch_std.lower()].event
|
|
# Copy from the architecture standard event to self for undefined fields.
|
|
for attr, value in _arch_std_events[arch_std.lower()].__dict__.items():
|
|
if hasattr(self, attr) and not getattr(self, attr):
|
|
setattr(self, attr, value)
|
|
|
|
self.event = real_event(self.name, event)
|
|
|
|
def __repr__(self) -> str:
|
|
"""String representation primarily for debugging."""
|
|
s = '{\n'
|
|
for attr, value in self.__dict__.items():
|
|
if value:
|
|
s += f'\t{attr} = {value},\n'
|
|
return s + '}'
|
|
|
|
def build_c_string(self, metric: bool) -> str:
|
|
s = ''
|
|
for attr in _json_metric_attributes if metric else _json_event_attributes:
|
|
x = getattr(self, attr)
|
|
if metric and x and attr == 'metric_expr':
|
|
# Convert parsed metric expressions into a string. Slashes
|
|
# must be doubled in the file.
|
|
x = x.ToPerfJson().replace('\\', '\\\\')
|
|
if metric and x and attr == 'metric_threshold':
|
|
x = x.replace('\\', '\\\\')
|
|
if attr in _json_enum_attributes:
|
|
s += x if x else '0'
|
|
else:
|
|
s += f'{x}\\000' if x else '\\000'
|
|
return s
|
|
|
|
def to_c_string(self, metric: bool) -> str:
|
|
"""Representation of the event as a C struct initializer."""
|
|
|
|
s = self.build_c_string(metric)
|
|
return f'{{ { _bcs.offsets[s] } }}, /* {s} */\n'
|
|
|
|
|
|
@lru_cache(maxsize=None)
|
|
def read_json_events(path: str, topic: str) -> Sequence[JsonEvent]:
|
|
"""Read json events from the specified file."""
|
|
try:
|
|
events = json.load(open(path), object_hook=JsonEvent)
|
|
except BaseException as err:
|
|
print(f"Exception processing {path}")
|
|
raise
|
|
metrics: list[Tuple[str, str, metric.Expression]] = []
|
|
for event in events:
|
|
event.topic = topic
|
|
if event.metric_name and '-' not in event.metric_name:
|
|
metrics.append((event.pmu, event.metric_name, event.metric_expr))
|
|
updates = metric.RewriteMetricsInTermsOfOthers(metrics)
|
|
if updates:
|
|
for event in events:
|
|
if event.metric_name in updates:
|
|
# print(f'Updated {event.metric_name} from\n"{event.metric_expr}"\n'
|
|
# f'to\n"{updates[event.metric_name]}"')
|
|
event.metric_expr = updates[event.metric_name]
|
|
|
|
return events
|
|
|
|
def preprocess_arch_std_files(archpath: str) -> None:
|
|
"""Read in all architecture standard events."""
|
|
global _arch_std_events
|
|
for item in os.scandir(archpath):
|
|
if item.is_file() and item.name.endswith('.json'):
|
|
for event in read_json_events(item.path, topic=''):
|
|
if event.name:
|
|
_arch_std_events[event.name.lower()] = event
|
|
if event.metric_name:
|
|
_arch_std_events[event.metric_name.lower()] = event
|
|
|
|
|
|
def add_events_table_entries(item: os.DirEntry, topic: str) -> None:
|
|
"""Add contents of file to _pending_events table."""
|
|
for e in read_json_events(item.path, topic):
|
|
if e.name:
|
|
_pending_events.append(e)
|
|
if e.metric_name:
|
|
_pending_metrics.append(e)
|
|
|
|
|
|
def print_pending_events() -> None:
|
|
"""Optionally close events table."""
|
|
|
|
def event_cmp_key(j: JsonEvent) -> Tuple[bool, str, str, str, str]:
|
|
def fix_none(s: Optional[str]) -> str:
|
|
if s is None:
|
|
return ''
|
|
return s
|
|
|
|
return (j.desc is not None, fix_none(j.topic), fix_none(j.name), fix_none(j.pmu),
|
|
fix_none(j.metric_name))
|
|
|
|
global _pending_events
|
|
if not _pending_events:
|
|
return
|
|
|
|
global _pending_events_tblname
|
|
if _pending_events_tblname.endswith('_sys'):
|
|
global _sys_event_tables
|
|
_sys_event_tables.append(_pending_events_tblname)
|
|
else:
|
|
global event_tables
|
|
_event_tables.append(_pending_events_tblname)
|
|
|
|
_args.output_file.write(
|
|
f'static const struct compact_pmu_event {_pending_events_tblname}[] = {{\n')
|
|
|
|
for event in sorted(_pending_events, key=event_cmp_key):
|
|
_args.output_file.write(event.to_c_string(metric=False))
|
|
_pending_events = []
|
|
|
|
_args.output_file.write('};\n\n')
|
|
|
|
def print_pending_metrics() -> None:
|
|
"""Optionally close metrics table."""
|
|
|
|
def metric_cmp_key(j: JsonEvent) -> Tuple[bool, str, str]:
|
|
def fix_none(s: Optional[str]) -> str:
|
|
if s is None:
|
|
return ''
|
|
return s
|
|
|
|
return (j.desc is not None, fix_none(j.pmu), fix_none(j.metric_name))
|
|
|
|
global _pending_metrics
|
|
if not _pending_metrics:
|
|
return
|
|
|
|
global _pending_metrics_tblname
|
|
if _pending_metrics_tblname.endswith('_sys'):
|
|
global _sys_metric_tables
|
|
_sys_metric_tables.append(_pending_metrics_tblname)
|
|
else:
|
|
global metric_tables
|
|
_metric_tables.append(_pending_metrics_tblname)
|
|
|
|
_args.output_file.write(
|
|
f'static const struct compact_pmu_event {_pending_metrics_tblname}[] = {{\n')
|
|
|
|
for metric in sorted(_pending_metrics, key=metric_cmp_key):
|
|
_args.output_file.write(metric.to_c_string(metric=True))
|
|
_pending_metrics = []
|
|
|
|
_args.output_file.write('};\n\n')
|
|
|
|
def get_topic(topic: str) -> str:
|
|
if topic.endswith('metrics.json'):
|
|
return 'metrics'
|
|
return removesuffix(topic, '.json').replace('-', ' ')
|
|
|
|
def preprocess_one_file(parents: Sequence[str], item: os.DirEntry) -> None:
|
|
|
|
if item.is_dir():
|
|
return
|
|
|
|
# base dir or too deep
|
|
level = len(parents)
|
|
if level == 0 or level > 4:
|
|
return
|
|
|
|
# Ignore other directories. If the file name does not have a .json
|
|
# extension, ignore it. It could be a readme.txt for instance.
|
|
if not item.is_file() or not item.name.endswith('.json'):
|
|
return
|
|
|
|
if item.name == 'metricgroups.json':
|
|
metricgroup_descriptions = json.load(open(item.path))
|
|
for mgroup in metricgroup_descriptions:
|
|
assert len(mgroup) > 1, parents
|
|
description = f"{metricgroup_descriptions[mgroup]}\\000"
|
|
mgroup = f"{mgroup}\\000"
|
|
_bcs.add(mgroup)
|
|
_bcs.add(description)
|
|
_metricgroups[mgroup] = description
|
|
return
|
|
|
|
topic = get_topic(item.name)
|
|
for event in read_json_events(item.path, topic):
|
|
if event.name:
|
|
_bcs.add(event.build_c_string(metric=False))
|
|
if event.metric_name:
|
|
_bcs.add(event.build_c_string(metric=True))
|
|
|
|
def process_one_file(parents: Sequence[str], item: os.DirEntry) -> None:
|
|
"""Process a JSON file during the main walk."""
|
|
def is_leaf_dir(path: str) -> bool:
|
|
for item in os.scandir(path):
|
|
if item.is_dir():
|
|
return False
|
|
return True
|
|
|
|
# model directory, reset topic
|
|
if item.is_dir() and is_leaf_dir(item.path):
|
|
print_pending_events()
|
|
print_pending_metrics()
|
|
|
|
global _pending_events_tblname
|
|
_pending_events_tblname = file_name_to_table_name('pmu_events_', parents, item.name)
|
|
global _pending_metrics_tblname
|
|
_pending_metrics_tblname = file_name_to_table_name('pmu_metrics_', parents, item.name)
|
|
|
|
if item.name == 'sys':
|
|
_sys_event_table_to_metric_table_mapping[_pending_events_tblname] = _pending_metrics_tblname
|
|
return
|
|
|
|
# base dir or too deep
|
|
level = len(parents)
|
|
if level == 0 or level > 4:
|
|
return
|
|
|
|
# Ignore other directories. If the file name does not have a .json
|
|
# extension, ignore it. It could be a readme.txt for instance.
|
|
if not item.is_file() or not item.name.endswith('.json') or item.name == 'metricgroups.json':
|
|
return
|
|
|
|
add_events_table_entries(item, get_topic(item.name))
|
|
|
|
|
|
def print_mapping_table(archs: Sequence[str]) -> None:
|
|
"""Read the mapfile and generate the struct from cpuid string to event table."""
|
|
_args.output_file.write("""
|
|
/* Struct used to make the PMU event table implementation opaque to callers. */
|
|
struct pmu_events_table {
|
|
const struct compact_pmu_event *entries;
|
|
size_t length;
|
|
};
|
|
|
|
/* Struct used to make the PMU metric table implementation opaque to callers. */
|
|
struct pmu_metrics_table {
|
|
const struct compact_pmu_event *entries;
|
|
size_t length;
|
|
};
|
|
|
|
/*
|
|
* Map a CPU to its table of PMU events. The CPU is identified by the
|
|
* cpuid field, which is an arch-specific identifier for the CPU.
|
|
* The identifier specified in tools/perf/pmu-events/arch/xxx/mapfile
|
|
* must match the get_cpuid_str() in tools/perf/arch/xxx/util/header.c)
|
|
*
|
|
* The cpuid can contain any character other than the comma.
|
|
*/
|
|
struct pmu_events_map {
|
|
const char *arch;
|
|
const char *cpuid;
|
|
struct pmu_events_table event_table;
|
|
struct pmu_metrics_table metric_table;
|
|
};
|
|
|
|
/*
|
|
* Global table mapping each known CPU for the architecture to its
|
|
* table of PMU events.
|
|
*/
|
|
const struct pmu_events_map pmu_events_map[] = {
|
|
""")
|
|
for arch in archs:
|
|
if arch == 'test':
|
|
_args.output_file.write("""{
|
|
\t.arch = "testarch",
|
|
\t.cpuid = "testcpu",
|
|
\t.event_table = {
|
|
\t\t.entries = pmu_events__test_soc_cpu,
|
|
\t\t.length = ARRAY_SIZE(pmu_events__test_soc_cpu),
|
|
\t},
|
|
\t.metric_table = {
|
|
\t\t.entries = pmu_metrics__test_soc_cpu,
|
|
\t\t.length = ARRAY_SIZE(pmu_metrics__test_soc_cpu),
|
|
\t}
|
|
},
|
|
""")
|
|
else:
|
|
with open(f'{_args.starting_dir}/{arch}/mapfile.csv') as csvfile:
|
|
table = csv.reader(csvfile)
|
|
first = True
|
|
for row in table:
|
|
# Skip the first row or any row beginning with #.
|
|
if not first and len(row) > 0 and not row[0].startswith('#'):
|
|
event_tblname = file_name_to_table_name('pmu_events_', [], row[2].replace('/', '_'))
|
|
if event_tblname in _event_tables:
|
|
event_size = f'ARRAY_SIZE({event_tblname})'
|
|
else:
|
|
event_tblname = 'NULL'
|
|
event_size = '0'
|
|
metric_tblname = file_name_to_table_name('pmu_metrics_', [], row[2].replace('/', '_'))
|
|
if metric_tblname in _metric_tables:
|
|
metric_size = f'ARRAY_SIZE({metric_tblname})'
|
|
else:
|
|
metric_tblname = 'NULL'
|
|
metric_size = '0'
|
|
if event_size == '0' and metric_size == '0':
|
|
continue
|
|
cpuid = row[0].replace('\\', '\\\\')
|
|
_args.output_file.write(f"""{{
|
|
\t.arch = "{arch}",
|
|
\t.cpuid = "{cpuid}",
|
|
\t.event_table = {{
|
|
\t\t.entries = {event_tblname},
|
|
\t\t.length = {event_size}
|
|
\t}},
|
|
\t.metric_table = {{
|
|
\t\t.entries = {metric_tblname},
|
|
\t\t.length = {metric_size}
|
|
\t}}
|
|
}},
|
|
""")
|
|
first = False
|
|
|
|
_args.output_file.write("""{
|
|
\t.arch = 0,
|
|
\t.cpuid = 0,
|
|
\t.event_table = { 0, 0 },
|
|
\t.metric_table = { 0, 0 },
|
|
}
|
|
};
|
|
""")
|
|
|
|
|
|
def print_system_mapping_table() -> None:
|
|
"""C struct mapping table array for tables from /sys directories."""
|
|
_args.output_file.write("""
|
|
struct pmu_sys_events {
|
|
\tconst char *name;
|
|
\tstruct pmu_events_table event_table;
|
|
\tstruct pmu_metrics_table metric_table;
|
|
};
|
|
|
|
static const struct pmu_sys_events pmu_sys_event_tables[] = {
|
|
""")
|
|
printed_metric_tables = []
|
|
for tblname in _sys_event_tables:
|
|
_args.output_file.write(f"""\t{{
|
|
\t\t.event_table = {{
|
|
\t\t\t.entries = {tblname},
|
|
\t\t\t.length = ARRAY_SIZE({tblname})
|
|
\t\t}},""")
|
|
metric_tblname = _sys_event_table_to_metric_table_mapping[tblname]
|
|
if metric_tblname in _sys_metric_tables:
|
|
_args.output_file.write(f"""
|
|
\t\t.metric_table = {{
|
|
\t\t\t.entries = {metric_tblname},
|
|
\t\t\t.length = ARRAY_SIZE({metric_tblname})
|
|
\t\t}},""")
|
|
printed_metric_tables.append(metric_tblname)
|
|
_args.output_file.write(f"""
|
|
\t\t.name = \"{tblname}\",
|
|
\t}},
|
|
""")
|
|
for tblname in _sys_metric_tables:
|
|
if tblname in printed_metric_tables:
|
|
continue
|
|
_args.output_file.write(f"""\t{{
|
|
\t\t.metric_table = {{
|
|
\t\t\t.entries = {tblname},
|
|
\t\t\t.length = ARRAY_SIZE({tblname})
|
|
\t\t}},
|
|
\t\t.name = \"{tblname}\",
|
|
\t}},
|
|
""")
|
|
_args.output_file.write("""\t{
|
|
\t\t.event_table = { 0, 0 },
|
|
\t\t.metric_table = { 0, 0 },
|
|
\t},
|
|
};
|
|
|
|
static void decompress_event(int offset, struct pmu_event *pe)
|
|
{
|
|
\tconst char *p = &big_c_string[offset];
|
|
""")
|
|
for attr in _json_event_attributes:
|
|
_args.output_file.write(f'\n\tpe->{attr} = ')
|
|
if attr in _json_enum_attributes:
|
|
_args.output_file.write("*p - '0';\n")
|
|
else:
|
|
_args.output_file.write("(*p == '\\0' ? NULL : p);\n")
|
|
if attr == _json_event_attributes[-1]:
|
|
continue
|
|
if attr in _json_enum_attributes:
|
|
_args.output_file.write('\tp++;')
|
|
else:
|
|
_args.output_file.write('\twhile (*p++);')
|
|
_args.output_file.write("""}
|
|
|
|
static void decompress_metric(int offset, struct pmu_metric *pm)
|
|
{
|
|
\tconst char *p = &big_c_string[offset];
|
|
""")
|
|
for attr in _json_metric_attributes:
|
|
_args.output_file.write(f'\n\tpm->{attr} = ')
|
|
if attr in _json_enum_attributes:
|
|
_args.output_file.write("*p - '0';\n")
|
|
else:
|
|
_args.output_file.write("(*p == '\\0' ? NULL : p);\n")
|
|
if attr == _json_metric_attributes[-1]:
|
|
continue
|
|
if attr in _json_enum_attributes:
|
|
_args.output_file.write('\tp++;')
|
|
else:
|
|
_args.output_file.write('\twhile (*p++);')
|
|
_args.output_file.write("""}
|
|
|
|
int pmu_events_table_for_each_event(const struct pmu_events_table *table,
|
|
pmu_event_iter_fn fn,
|
|
void *data)
|
|
{
|
|
for (size_t i = 0; i < table->length; i++) {
|
|
struct pmu_event pe;
|
|
int ret;
|
|
|
|
decompress_event(table->entries[i].offset, &pe);
|
|
if (!pe.name)
|
|
continue;
|
|
ret = fn(&pe, table, data);
|
|
if (ret)
|
|
return ret;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int pmu_metrics_table_for_each_metric(const struct pmu_metrics_table *table,
|
|
pmu_metric_iter_fn fn,
|
|
void *data)
|
|
{
|
|
for (size_t i = 0; i < table->length; i++) {
|
|
struct pmu_metric pm;
|
|
int ret;
|
|
|
|
decompress_metric(table->entries[i].offset, &pm);
|
|
if (!pm.metric_expr)
|
|
continue;
|
|
ret = fn(&pm, table, data);
|
|
if (ret)
|
|
return ret;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
const struct pmu_events_table *perf_pmu__find_events_table(struct perf_pmu *pmu)
|
|
{
|
|
const struct pmu_events_table *table = NULL;
|
|
char *cpuid = perf_pmu__getcpuid(pmu);
|
|
int i;
|
|
|
|
/* on some platforms which uses cpus map, cpuid can be NULL for
|
|
* PMUs other than CORE PMUs.
|
|
*/
|
|
if (!cpuid)
|
|
return NULL;
|
|
|
|
i = 0;
|
|
for (;;) {
|
|
const struct pmu_events_map *map = &pmu_events_map[i++];
|
|
if (!map->arch)
|
|
break;
|
|
|
|
if (!strcmp_cpuid_str(map->cpuid, cpuid)) {
|
|
table = &map->event_table;
|
|
break;
|
|
}
|
|
}
|
|
free(cpuid);
|
|
return table;
|
|
}
|
|
|
|
const struct pmu_metrics_table *perf_pmu__find_metrics_table(struct perf_pmu *pmu)
|
|
{
|
|
const struct pmu_metrics_table *table = NULL;
|
|
char *cpuid = perf_pmu__getcpuid(pmu);
|
|
int i;
|
|
|
|
/* on some platforms which uses cpus map, cpuid can be NULL for
|
|
* PMUs other than CORE PMUs.
|
|
*/
|
|
if (!cpuid)
|
|
return NULL;
|
|
|
|
i = 0;
|
|
for (;;) {
|
|
const struct pmu_events_map *map = &pmu_events_map[i++];
|
|
if (!map->arch)
|
|
break;
|
|
|
|
if (!strcmp_cpuid_str(map->cpuid, cpuid)) {
|
|
table = &map->metric_table;
|
|
break;
|
|
}
|
|
}
|
|
free(cpuid);
|
|
return table;
|
|
}
|
|
|
|
const struct pmu_events_table *find_core_events_table(const char *arch, const char *cpuid)
|
|
{
|
|
for (const struct pmu_events_map *tables = &pmu_events_map[0];
|
|
tables->arch;
|
|
tables++) {
|
|
if (!strcmp(tables->arch, arch) && !strcmp_cpuid_str(tables->cpuid, cpuid))
|
|
return &tables->event_table;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
const struct pmu_metrics_table *find_core_metrics_table(const char *arch, const char *cpuid)
|
|
{
|
|
for (const struct pmu_events_map *tables = &pmu_events_map[0];
|
|
tables->arch;
|
|
tables++) {
|
|
if (!strcmp(tables->arch, arch) && !strcmp_cpuid_str(tables->cpuid, cpuid))
|
|
return &tables->metric_table;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
int pmu_for_each_core_event(pmu_event_iter_fn fn, void *data)
|
|
{
|
|
for (const struct pmu_events_map *tables = &pmu_events_map[0];
|
|
tables->arch;
|
|
tables++) {
|
|
int ret = pmu_events_table_for_each_event(&tables->event_table, fn, data);
|
|
|
|
if (ret)
|
|
return ret;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int pmu_for_each_core_metric(pmu_metric_iter_fn fn, void *data)
|
|
{
|
|
for (const struct pmu_events_map *tables = &pmu_events_map[0];
|
|
tables->arch;
|
|
tables++) {
|
|
int ret = pmu_metrics_table_for_each_metric(&tables->metric_table, fn, data);
|
|
|
|
if (ret)
|
|
return ret;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
const struct pmu_events_table *find_sys_events_table(const char *name)
|
|
{
|
|
for (const struct pmu_sys_events *tables = &pmu_sys_event_tables[0];
|
|
tables->name;
|
|
tables++) {
|
|
if (!strcmp(tables->name, name))
|
|
return &tables->event_table;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
int pmu_for_each_sys_event(pmu_event_iter_fn fn, void *data)
|
|
{
|
|
for (const struct pmu_sys_events *tables = &pmu_sys_event_tables[0];
|
|
tables->name;
|
|
tables++) {
|
|
int ret = pmu_events_table_for_each_event(&tables->event_table, fn, data);
|
|
|
|
if (ret)
|
|
return ret;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int pmu_for_each_sys_metric(pmu_metric_iter_fn fn, void *data)
|
|
{
|
|
for (const struct pmu_sys_events *tables = &pmu_sys_event_tables[0];
|
|
tables->name;
|
|
tables++) {
|
|
int ret = pmu_metrics_table_for_each_metric(&tables->metric_table, fn, data);
|
|
|
|
if (ret)
|
|
return ret;
|
|
}
|
|
return 0;
|
|
}
|
|
""")
|
|
|
|
def print_metricgroups() -> None:
|
|
_args.output_file.write("""
|
|
static const int metricgroups[][2] = {
|
|
""")
|
|
for mgroup in sorted(_metricgroups):
|
|
description = _metricgroups[mgroup]
|
|
_args.output_file.write(
|
|
f'\t{{ {_bcs.offsets[mgroup]}, {_bcs.offsets[description]} }}, /* {mgroup} => {description} */\n'
|
|
)
|
|
_args.output_file.write("""
|
|
};
|
|
|
|
const char *describe_metricgroup(const char *group)
|
|
{
|
|
int low = 0, high = (int)ARRAY_SIZE(metricgroups) - 1;
|
|
|
|
while (low <= high) {
|
|
int mid = (low + high) / 2;
|
|
const char *mgroup = &big_c_string[metricgroups[mid][0]];
|
|
int cmp = strcmp(mgroup, group);
|
|
|
|
if (cmp == 0) {
|
|
return &big_c_string[metricgroups[mid][1]];
|
|
} else if (cmp < 0) {
|
|
low = mid + 1;
|
|
} else {
|
|
high = mid - 1;
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
""")
|
|
|
|
def main() -> None:
|
|
global _args
|
|
|
|
def dir_path(path: str) -> str:
|
|
"""Validate path is a directory for argparse."""
|
|
if os.path.isdir(path):
|
|
return path
|
|
raise argparse.ArgumentTypeError(f'\'{path}\' is not a valid directory')
|
|
|
|
def ftw(path: str, parents: Sequence[str],
|
|
action: Callable[[Sequence[str], os.DirEntry], None]) -> None:
|
|
"""Replicate the directory/file walking behavior of C's file tree walk."""
|
|
for item in sorted(os.scandir(path), key=lambda e: e.name):
|
|
if _args.model != 'all' and item.is_dir():
|
|
# Check if the model matches one in _args.model.
|
|
if len(parents) == _args.model.split(',')[0].count('/'):
|
|
# We're testing the correct directory.
|
|
item_path = '/'.join(parents) + ('/' if len(parents) > 0 else '') + item.name
|
|
if 'test' not in item_path and item_path not in _args.model.split(','):
|
|
continue
|
|
action(parents, item)
|
|
if item.is_dir():
|
|
ftw(item.path, parents + [item.name], action)
|
|
|
|
ap = argparse.ArgumentParser()
|
|
ap.add_argument('arch', help='Architecture name like x86')
|
|
ap.add_argument('model', help='''Select a model such as skylake to
|
|
reduce the code size. Normally set to "all". For architectures like
|
|
ARM64 with an implementor/model, the model must include the implementor
|
|
such as "arm/cortex-a34".''',
|
|
default='all')
|
|
ap.add_argument(
|
|
'starting_dir',
|
|
type=dir_path,
|
|
help='Root of tree containing architecture directories containing json files'
|
|
)
|
|
ap.add_argument(
|
|
'output_file', type=argparse.FileType('w', encoding='utf-8'), nargs='?', default=sys.stdout)
|
|
_args = ap.parse_args()
|
|
|
|
_args.output_file.write("""
|
|
#include <pmu-events/pmu-events.h>
|
|
#include "util/header.h"
|
|
#include "util/pmu.h"
|
|
#include <string.h>
|
|
#include <stddef.h>
|
|
|
|
struct compact_pmu_event {
|
|
int offset;
|
|
};
|
|
|
|
""")
|
|
archs = []
|
|
for item in os.scandir(_args.starting_dir):
|
|
if not item.is_dir():
|
|
continue
|
|
if item.name == _args.arch or _args.arch == 'all' or item.name == 'test':
|
|
archs.append(item.name)
|
|
|
|
if len(archs) < 2:
|
|
raise IOError(f'Missing architecture directory \'{_args.arch}\'')
|
|
|
|
archs.sort()
|
|
for arch in archs:
|
|
arch_path = f'{_args.starting_dir}/{arch}'
|
|
preprocess_arch_std_files(arch_path)
|
|
ftw(arch_path, [], preprocess_one_file)
|
|
|
|
_bcs.compute()
|
|
_args.output_file.write('static const char *const big_c_string =\n')
|
|
for s in _bcs.big_string:
|
|
_args.output_file.write(s)
|
|
_args.output_file.write(';\n\n')
|
|
for arch in archs:
|
|
arch_path = f'{_args.starting_dir}/{arch}'
|
|
ftw(arch_path, [], process_one_file)
|
|
print_pending_events()
|
|
print_pending_metrics()
|
|
|
|
print_mapping_table(archs)
|
|
print_system_mapping_table()
|
|
print_metricgroups()
|
|
|
|
if __name__ == '__main__':
|
|
main()
|