aboutsummaryrefslogtreecommitdiff
path: root/tools/perf/pmu-events/metric.py
diff options
context:
space:
mode:
authorLibravatar Linus Torvalds <torvalds@linux-foundation.org>2023-02-21 18:24:12 -0800
committerLibravatar Linus Torvalds <torvalds@linux-foundation.org>2023-02-21 18:24:12 -0800
commit5b7c4cabbb65f5c469464da6c5f614cbd7f730f2 (patch)
treecc5c2d0a898769fd59549594fedb3ee6f84e59a0 /tools/perf/pmu-events/metric.py
downloadlinux-5b7c4cabbb65f5c469464da6c5f614cbd7f730f2.tar.gz
linux-5b7c4cabbb65f5c469464da6c5f614cbd7f730f2.zip
Merge tag 'net-next-6.3' of git://git.kernel.org/pub/scm/linux/kernel/git/netdev/net-nextgrafted
Pull networking updates from Jakub Kicinski: "Core: - Add dedicated kmem_cache for typical/small skb->head, avoid having to access struct page at kfree time, and improve memory use. - Introduce sysctl to set default RPS configuration for new netdevs. - Define Netlink protocol specification format which can be used to describe messages used by each family and auto-generate parsers. Add tools for generating kernel data structures and uAPI headers. - Expose all net/core sysctls inside netns. - Remove 4s sleep in netpoll if carrier is instantly detected on boot. - Add configurable limit of MDB entries per port, and port-vlan. - Continue populating drop reasons throughout the stack. - Retire a handful of legacy Qdiscs and classifiers. Protocols: - Support IPv4 big TCP (TSO frames larger than 64kB). - Add IP_LOCAL_PORT_RANGE socket option, to control local port range on socket by socket basis. - Track and report in procfs number of MPTCP sockets used. - Support mixing IPv4 and IPv6 flows in the in-kernel MPTCP path manager. - IPv6: don't check net.ipv6.route.max_size and rely on garbage collection to free memory (similarly to IPv4). - Support Penultimate Segment Pop (PSP) flavor in SRv6 (RFC8986). - ICMP: add per-rate limit counters. - Add support for user scanning requests in ieee802154. - Remove static WEP support. - Support minimal Wi-Fi 7 Extremely High Throughput (EHT) rate reporting. - WiFi 7 EHT channel puncturing support (client & AP). BPF: - Add a rbtree data structure following the "next-gen data structure" precedent set by recently added linked list, that is, by using kfunc + kptr instead of adding a new BPF map type. - Expose XDP hints via kfuncs with initial support for RX hash and timestamp metadata. - Add BPF_F_NO_TUNNEL_KEY extension to bpf_skb_set_tunnel_key to better support decap on GRE tunnel devices not operating in collect metadata. - Improve x86 JIT's codegen for PROBE_MEM runtime error checks. - Remove the need for trace_printk_lock for bpf_trace_printk and bpf_trace_vprintk helpers. - Extend libbpf's bpf_tracing.h support for tracing arguments of kprobes/uprobes and syscall as a special case. - Significantly reduce the search time for module symbols by livepatch and BPF. - Enable cpumasks to be used as kptrs, which is useful for tracing programs tracking which tasks end up running on which CPUs in different time intervals. - Add support for BPF trampoline on s390x and riscv64. - Add capability to export the XDP features supported by the NIC. - Add __bpf_kfunc tag for marking kernel functions as kfuncs. - Add cgroup.memory=nobpf kernel parameter option to disable BPF memory accounting for container environments. Netfilter: - Remove the CLUSTERIP target. It has been marked as obsolete for years, and we still have WARN splats wrt races of the out-of-band /proc interface installed by this target. - Add 'destroy' commands to nf_tables. They are identical to the existing 'delete' commands, but do not return an error if the referenced object (set, chain, rule...) did not exist. Driver API: - Improve cpumask_local_spread() locality to help NICs set the right IRQ affinity on AMD platforms. - Separate C22 and C45 MDIO bus transactions more clearly. - Introduce new DCB table to control DSCP rewrite on egress. - Support configuration of Physical Layer Collision Avoidance (PLCA) Reconciliation Sublayer (RS) (802.3cg-2019). Modern version of shared medium Ethernet. - Support for MAC Merge layer (IEEE 802.3-2018 clause 99). Allowing preemption of low priority frames by high priority frames. - Add support for controlling MACSec offload using netlink SET. - Rework devlink instance refcounts to allow registration and de-registration under the instance lock. Split the code into multiple files, drop some of the unnecessarily granular locks and factor out common parts of netlink operation handling. - Add TX frame aggregation parameters (for USB drivers). - Add a new attr TCA_EXT_WARN_MSG to report TC (offload) warning messages with notifications for debug. - Allow offloading of UDP NEW connections via act_ct. - Add support for per action HW stats in TC. - Support hardware miss to TC action (continue processing in SW from a specific point in the action chain). - Warn if old Wireless Extension user space interface is used with modern cfg80211/mac80211 drivers. Do not support Wireless Extensions for Wi-Fi 7 devices at all. Everyone should switch to using nl80211 interface instead. - Improve the CAN bit timing configuration. Use extack to return error messages directly to user space, update the SJW handling, including the definition of a new default value that will benefit CAN-FD controllers, by increasing their oscillator tolerance. New hardware / drivers: - Ethernet: - nVidia BlueField-3 support (control traffic driver) - Ethernet support for imx93 SoCs - Motorcomm yt8531 gigabit Ethernet PHY - onsemi NCN26000 10BASE-T1S PHY (with support for PLCA) - Microchip LAN8841 PHY (incl. cable diagnostics and PTP) - Amlogic gxl MDIO mux - WiFi: - RealTek RTL8188EU (rtl8xxxu) - Qualcomm Wi-Fi 7 devices (ath12k) - CAN: - Renesas R-Car V4H Drivers: - Bluetooth: - Set Per Platform Antenna Gain (PPAG) for Intel controllers. - Ethernet NICs: - Intel (1G, igc): - support TSN / Qbv / packet scheduling features of i226 model - Intel (100G, ice): - use GNSS subsystem instead of TTY - multi-buffer XDP support - extend support for GPIO pins to E823 devices - nVidia/Mellanox: - update the shared buffer configuration on PFC commands - implement PTP adjphase function for HW offset control - TC support for Geneve and GRE with VF tunnel offload - more efficient crypto key management method - multi-port eswitch support - Netronome/Corigine: - add DCB IEEE support - support IPsec offloading for NFP3800 - Freescale/NXP (enetc): - support XDP_REDIRECT for XDP non-linear buffers - improve reconfig, avoid link flap and waiting for idle - support MAC Merge layer - Other NICs: - sfc/ef100: add basic devlink support for ef100 - ionic: rx_push mode operation (writing descriptors via MMIO) - bnxt: use the auxiliary bus abstraction for RDMA - r8169: disable ASPM and reset bus in case of tx timeout - cpsw: support QSGMII mode for J721e CPSW9G - cpts: support pulse-per-second output - ngbe: add an mdio bus driver - usbnet: optimize usbnet_bh() by avoiding unnecessary queuing - r8152: handle devices with FW with NCM support - amd-xgbe: support 10Mbps, 2.5GbE speeds and rx-adaptation - virtio-net: support multi buffer XDP - virtio/vsock: replace virtio_vsock_pkt with sk_buff - tsnep: XDP support - Ethernet high-speed switches: - nVidia/Mellanox (mlxsw): - add support for latency TLV (in FW control messages) - Microchip (sparx5): - separate explicit and implicit traffic forwarding rules, make the implicit rules always active - add support for egress DSCP rewrite - IS0 VCAP support (Ingress Classification) - IS2 VCAP filters (protos, L3 addrs, L4 ports, flags, ToS etc.) - ES2 VCAP support (Egress Access Control) - support for Per-Stream Filtering and Policing (802.1Q, 8.6.5.1) - Ethernet embedded switches: - Marvell (mv88e6xxx): - add MAB (port auth) offload support - enable PTP receive for mv88e6390 - NXP (ocelot): - support MAC Merge layer - support for the the vsc7512 internal copper phys - Microchip: - lan9303: convert to PHYLINK - lan966x: support TC flower filter statistics - lan937x: PTP support for KSZ9563/KSZ8563 and LAN937x - lan937x: support Credit Based Shaper configuration - ksz9477: support Energy Efficient Ethernet - other: - qca8k: convert to regmap read/write API, use bulk operations - rswitch: Improve TX timestamp accuracy - Intel WiFi (iwlwifi): - EHT (Wi-Fi 7) rate reporting - STEP equalizer support: transfer some STEP (connection to radio on platforms with integrated wifi) related parameters from the BIOS to the firmware. - Qualcomm 802.11ax WiFi (ath11k): - IPQ5018 support - Fine Timing Measurement (FTM) responder role support - channel 177 support - MediaTek WiFi (mt76): - per-PHY LED support - mt7996: EHT (Wi-Fi 7) support - Wireless Ethernet Dispatch (WED) reset support - switch to using page pool allocator - RealTek WiFi (rtw89): - support new version of Bluetooth co-existance - Mobile: - rmnet: support TX aggregation" * tag 'net-next-6.3' of git://git.kernel.org/pub/scm/linux/kernel/git/netdev/net-next: (1872 commits) page_pool: add a comment explaining the fragment counter usage net: ethtool: fix __ethtool_dev_mm_supported() implementation ethtool: pse-pd: Fix double word in comments xsk: add linux/vmalloc.h to xsk.c sefltests: netdevsim: wait for devlink instance after netns removal selftest: fib_tests: Always cleanup before exit net/mlx5e: Align IPsec ASO result memory to be as required by hardware net/mlx5e: TC, Set CT miss to the specific ct action instance net/mlx5e: Rename CHAIN_TO_REG to MAPPED_OBJ_TO_REG net/mlx5: Refactor tc miss handling to a single function net/mlx5: Kconfig: Make tc offload depend on tc skb extension net/sched: flower: Support hardware miss to tc action net/sched: flower: Move filter handle initialization earlier net/sched: cls_api: Support hardware miss to tc action net/sched: Rename user cookie and act cookie sfc: fix builds without CONFIG_RTC_LIB sfc: clean up some inconsistent indentings net/mlx4_en: Introduce flexible array to silence overflow warning net: lan966x: Fix possible deadlock inside PTP net/ulp: Remove redundant ->clone() test in inet_clone_ulp(). ...
Diffstat (limited to 'tools/perf/pmu-events/metric.py')
-rw-r--r--tools/perf/pmu-events/metric.py502
1 files changed, 502 insertions, 0 deletions
diff --git a/tools/perf/pmu-events/metric.py b/tools/perf/pmu-events/metric.py
new file mode 100644
index 000000000..4797ed4fd
--- /dev/null
+++ b/tools/perf/pmu-events/metric.py
@@ -0,0 +1,502 @@
+# SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause)
+"""Parse or generate representations of perf metrics."""
+import ast
+import decimal
+import json
+import re
+from typing import Dict, List, Optional, Set, Union
+
+
+class Expression:
+ """Abstract base class of elements in a metric expression."""
+
+ def ToPerfJson(self) -> str:
+ """Returns a perf json file encoded representation."""
+ raise NotImplementedError()
+
+ def ToPython(self) -> str:
+ """Returns a python expr parseable representation."""
+ raise NotImplementedError()
+
+ def Simplify(self):
+ """Returns a simplified version of self."""
+ raise NotImplementedError()
+
+ def Equals(self, other) -> bool:
+ """Returns true when two expressions are the same."""
+ raise NotImplementedError()
+
+ def __str__(self) -> str:
+ return self.ToPerfJson()
+
+ def __or__(self, other: Union[int, float, 'Expression']) -> 'Operator':
+ return Operator('|', self, other)
+
+ def __ror__(self, other: Union[int, float, 'Expression']) -> 'Operator':
+ return Operator('|', other, self)
+
+ def __xor__(self, other: Union[int, float, 'Expression']) -> 'Operator':
+ return Operator('^', self, other)
+
+ def __and__(self, other: Union[int, float, 'Expression']) -> 'Operator':
+ return Operator('&', self, other)
+
+ def __lt__(self, other: Union[int, float, 'Expression']) -> 'Operator':
+ return Operator('<', self, other)
+
+ def __gt__(self, other: Union[int, float, 'Expression']) -> 'Operator':
+ return Operator('>', self, other)
+
+ def __add__(self, other: Union[int, float, 'Expression']) -> 'Operator':
+ return Operator('+', self, other)
+
+ def __radd__(self, other: Union[int, float, 'Expression']) -> 'Operator':
+ return Operator('+', other, self)
+
+ def __sub__(self, other: Union[int, float, 'Expression']) -> 'Operator':
+ return Operator('-', self, other)
+
+ def __rsub__(self, other: Union[int, float, 'Expression']) -> 'Operator':
+ return Operator('-', other, self)
+
+ def __mul__(self, other: Union[int, float, 'Expression']) -> 'Operator':
+ return Operator('*', self, other)
+
+ def __rmul__(self, other: Union[int, float, 'Expression']) -> 'Operator':
+ return Operator('*', other, self)
+
+ def __truediv__(self, other: Union[int, float, 'Expression']) -> 'Operator':
+ return Operator('/', self, other)
+
+ def __rtruediv__(self, other: Union[int, float, 'Expression']) -> 'Operator':
+ return Operator('/', other, self)
+
+ def __mod__(self, other: Union[int, float, 'Expression']) -> 'Operator':
+ return Operator('%', self, other)
+
+
+def _Constify(val: Union[bool, int, float, Expression]) -> Expression:
+ """Used to ensure that the nodes in the expression tree are all Expression."""
+ if isinstance(val, bool):
+ return Constant(1 if val else 0)
+ if isinstance(val, (int, float)):
+ return Constant(val)
+ return val
+
+
+# Simple lookup for operator precedence, used to avoid unnecessary
+# brackets. Precedence matches that of python and the simple expression parser.
+_PRECEDENCE = {
+ '|': 0,
+ '^': 1,
+ '&': 2,
+ '<': 3,
+ '>': 3,
+ '+': 4,
+ '-': 4,
+ '*': 5,
+ '/': 5,
+ '%': 5,
+}
+
+
+class Operator(Expression):
+ """Represents a binary operator in the parse tree."""
+
+ def __init__(self, operator: str, lhs: Union[int, float, Expression],
+ rhs: Union[int, float, Expression]):
+ self.operator = operator
+ self.lhs = _Constify(lhs)
+ self.rhs = _Constify(rhs)
+
+ def Bracket(self,
+ other: Expression,
+ other_str: str,
+ rhs: bool = False) -> str:
+ """If necessary brackets the given other value.
+
+ If ``other`` is an operator then a bracket is necessary when
+ this/self operator has higher precedence. Consider: '(a + b) * c',
+ ``other_str`` will be 'a + b'. A bracket is necessary as without
+ the bracket 'a + b * c' will evaluate 'b * c' first. However, '(a
+ * b) + c' doesn't need a bracket as 'a * b' will always be
+ evaluated first. For 'a / (b * c)' (ie the same precedence level
+ operations) then we add the bracket to best match the original
+ input, but not for '(a / b) * c' where the bracket is unnecessary.
+
+ Args:
+ other (Expression): is a lhs or rhs operator
+ other_str (str): ``other`` in the appropriate string form
+ rhs (bool): is ``other`` on the RHS
+
+ Returns:
+ str: possibly bracketed other_str
+ """
+ if isinstance(other, Operator):
+ if _PRECEDENCE.get(self.operator, -1) > _PRECEDENCE.get(
+ other.operator, -1):
+ return f'({other_str})'
+ if rhs and _PRECEDENCE.get(self.operator, -1) == _PRECEDENCE.get(
+ other.operator, -1):
+ return f'({other_str})'
+ return other_str
+
+ def ToPerfJson(self):
+ return (f'{self.Bracket(self.lhs, self.lhs.ToPerfJson())} {self.operator} '
+ f'{self.Bracket(self.rhs, self.rhs.ToPerfJson(), True)}')
+
+ def ToPython(self):
+ return (f'{self.Bracket(self.lhs, self.lhs.ToPython())} {self.operator} '
+ f'{self.Bracket(self.rhs, self.rhs.ToPython(), True)}')
+
+ def Simplify(self) -> Expression:
+ lhs = self.lhs.Simplify()
+ rhs = self.rhs.Simplify()
+ if isinstance(lhs, Constant) and isinstance(rhs, Constant):
+ return Constant(ast.literal_eval(lhs + self.operator + rhs))
+
+ if isinstance(self.lhs, Constant):
+ if self.operator in ('+', '|') and lhs.value == '0':
+ return rhs
+
+ # Simplify multiplication by 0 except for the slot event which
+ # is deliberately introduced using this pattern.
+ if self.operator == '*' and lhs.value == '0' and (
+ not isinstance(rhs, Event) or 'slots' not in rhs.name.lower()):
+ return Constant(0)
+
+ if self.operator == '*' and lhs.value == '1':
+ return rhs
+
+ if isinstance(rhs, Constant):
+ if self.operator in ('+', '|') and rhs.value == '0':
+ return lhs
+
+ if self.operator == '*' and rhs.value == '0':
+ return Constant(0)
+
+ if self.operator == '*' and self.rhs.value == '1':
+ return lhs
+
+ return Operator(self.operator, lhs, rhs)
+
+ def Equals(self, other: Expression) -> bool:
+ if isinstance(other, Operator):
+ return self.operator == other.operator and self.lhs.Equals(
+ other.lhs) and self.rhs.Equals(other.rhs)
+ return False
+
+
+class Select(Expression):
+ """Represents a select ternary in the parse tree."""
+
+ def __init__(self, true_val: Union[int, float, Expression],
+ cond: Union[int, float, Expression],
+ false_val: Union[int, float, Expression]):
+ self.true_val = _Constify(true_val)
+ self.cond = _Constify(cond)
+ self.false_val = _Constify(false_val)
+
+ def ToPerfJson(self):
+ true_str = self.true_val.ToPerfJson()
+ cond_str = self.cond.ToPerfJson()
+ false_str = self.false_val.ToPerfJson()
+ return f'({true_str} if {cond_str} else {false_str})'
+
+ def ToPython(self):
+ return (f'Select({self.true_val.ToPython()}, {self.cond.ToPython()}, '
+ f'{self.false_val.ToPython()})')
+
+ def Simplify(self) -> Expression:
+ cond = self.cond.Simplify()
+ true_val = self.true_val.Simplify()
+ false_val = self.false_val.Simplify()
+ if isinstance(cond, Constant):
+ return false_val if cond.value == '0' else true_val
+
+ if true_val.Equals(false_val):
+ return true_val
+
+ return Select(true_val, cond, false_val)
+
+ def Equals(self, other: Expression) -> bool:
+ if isinstance(other, Select):
+ return self.cond.Equals(other.cond) and self.false_val.Equals(
+ other.false_val) and self.true_val.Equals(other.true_val)
+ return False
+
+
+class Function(Expression):
+ """A function in an expression like min, max, d_ratio."""
+
+ def __init__(self,
+ fn: str,
+ lhs: Union[int, float, Expression],
+ rhs: Optional[Union[int, float, Expression]] = None):
+ self.fn = fn
+ self.lhs = _Constify(lhs)
+ self.rhs = _Constify(rhs)
+
+ def ToPerfJson(self):
+ if self.rhs:
+ return f'{self.fn}({self.lhs.ToPerfJson()}, {self.rhs.ToPerfJson()})'
+ return f'{self.fn}({self.lhs.ToPerfJson()})'
+
+ def ToPython(self):
+ if self.rhs:
+ return f'{self.fn}({self.lhs.ToPython()}, {self.rhs.ToPython()})'
+ return f'{self.fn}({self.lhs.ToPython()})'
+
+ def Simplify(self) -> Expression:
+ lhs = self.lhs.Simplify()
+ rhs = self.rhs.Simplify() if self.rhs else None
+ if isinstance(lhs, Constant) and isinstance(rhs, Constant):
+ if self.fn == 'd_ratio':
+ if rhs.value == '0':
+ return Constant(0)
+ Constant(ast.literal_eval(f'{lhs} / {rhs}'))
+ return Constant(ast.literal_eval(f'{self.fn}({lhs}, {rhs})'))
+
+ return Function(self.fn, lhs, rhs)
+
+ def Equals(self, other: Expression) -> bool:
+ if isinstance(other, Function):
+ return self.fn == other.fn and self.lhs.Equals(
+ other.lhs) and self.rhs.Equals(other.rhs)
+ return False
+
+
+def _FixEscapes(s: str) -> str:
+ s = re.sub(r'([^\\]),', r'\1\\,', s)
+ return re.sub(r'([^\\])=', r'\1\\=', s)
+
+
+class Event(Expression):
+ """An event in an expression."""
+
+ def __init__(self, name: str, legacy_name: str = ''):
+ self.name = _FixEscapes(name)
+ self.legacy_name = _FixEscapes(legacy_name)
+
+ def ToPerfJson(self):
+ result = re.sub('/', '@', self.name)
+ return result
+
+ def ToPython(self):
+ return f'Event(r"{self.name}")'
+
+ def Simplify(self) -> Expression:
+ return self
+
+ def Equals(self, other: Expression) -> bool:
+ return isinstance(other, Event) and self.name == other.name
+
+
+class Constant(Expression):
+ """A constant within the expression tree."""
+
+ def __init__(self, value: Union[float, str]):
+ ctx = decimal.Context()
+ ctx.prec = 20
+ dec = ctx.create_decimal(repr(value) if isinstance(value, float) else value)
+ self.value = dec.normalize().to_eng_string()
+ self.value = self.value.replace('+', '')
+ self.value = self.value.replace('E', 'e')
+
+ def ToPerfJson(self):
+ return self.value
+
+ def ToPython(self):
+ return f'Constant({self.value})'
+
+ def Simplify(self) -> Expression:
+ return self
+
+ def Equals(self, other: Expression) -> bool:
+ return isinstance(other, Constant) and self.value == other.value
+
+
+class Literal(Expression):
+ """A runtime literal within the expression tree."""
+
+ def __init__(self, value: str):
+ self.value = value
+
+ def ToPerfJson(self):
+ return self.value
+
+ def ToPython(self):
+ return f'Literal({self.value})'
+
+ def Simplify(self) -> Expression:
+ return self
+
+ def Equals(self, other: Expression) -> bool:
+ return isinstance(other, Literal) and self.value == other.value
+
+
+def min(lhs: Union[int, float, Expression], rhs: Union[int, float,
+ Expression]) -> Function:
+ # pylint: disable=redefined-builtin
+ # pylint: disable=invalid-name
+ return Function('min', lhs, rhs)
+
+
+def max(lhs: Union[int, float, Expression], rhs: Union[int, float,
+ Expression]) -> Function:
+ # pylint: disable=redefined-builtin
+ # pylint: disable=invalid-name
+ return Function('max', lhs, rhs)
+
+
+def d_ratio(lhs: Union[int, float, Expression],
+ rhs: Union[int, float, Expression]) -> Function:
+ # pylint: disable=redefined-builtin
+ # pylint: disable=invalid-name
+ return Function('d_ratio', lhs, rhs)
+
+
+def source_count(event: Event) -> Function:
+ # pylint: disable=redefined-builtin
+ # pylint: disable=invalid-name
+ return Function('source_count', event)
+
+
+class Metric:
+ """An individual metric that will specifiable on the perf command line."""
+ groups: Set[str]
+ expr: Expression
+ scale_unit: str
+ constraint: bool
+
+ def __init__(self,
+ name: str,
+ description: str,
+ expr: Expression,
+ scale_unit: str,
+ constraint: bool = False):
+ self.name = name
+ self.description = description
+ self.expr = expr.Simplify()
+ # Workraound valid_only_metric hiding certain metrics based on unit.
+ scale_unit = scale_unit.replace('/sec', ' per sec')
+ if scale_unit[0].isdigit():
+ self.scale_unit = scale_unit
+ else:
+ self.scale_unit = f'1{scale_unit}'
+ self.constraint = constraint
+ self.groups = set()
+
+ def __lt__(self, other):
+ """Sort order."""
+ return self.name < other.name
+
+ def AddToMetricGroup(self, group):
+ """Callback used when being added to a MetricGroup."""
+ self.groups.add(group.name)
+
+ def Flatten(self) -> Set['Metric']:
+ """Return a leaf metric."""
+ return set([self])
+
+ def ToPerfJson(self) -> Dict[str, str]:
+ """Return as dictionary for Json generation."""
+ result = {
+ 'MetricName': self.name,
+ 'MetricGroup': ';'.join(sorted(self.groups)),
+ 'BriefDescription': self.description,
+ 'MetricExpr': self.expr.ToPerfJson(),
+ 'ScaleUnit': self.scale_unit
+ }
+ if self.constraint:
+ result['MetricConstraint'] = 'NO_NMI_WATCHDOG'
+
+ return result
+
+
+class _MetricJsonEncoder(json.JSONEncoder):
+ """Special handling for Metric objects."""
+
+ def default(self, o):
+ if isinstance(o, Metric):
+ return o.ToPerfJson()
+ return json.JSONEncoder.default(self, o)
+
+
+class MetricGroup:
+ """A group of metrics.
+
+ Metric groups may be specificd on the perf command line, but within
+ the json they aren't encoded. Metrics may be in multiple groups
+ which can facilitate arrangements similar to trees.
+ """
+
+ def __init__(self, name: str, metric_list: List[Union[Metric,
+ 'MetricGroup']]):
+ self.name = name
+ self.metric_list = metric_list
+ for metric in metric_list:
+ metric.AddToMetricGroup(self)
+
+ def AddToMetricGroup(self, group):
+ """Callback used when a MetricGroup is added into another."""
+ for metric in self.metric_list:
+ metric.AddToMetricGroup(group)
+
+ def Flatten(self) -> Set[Metric]:
+ """Returns a set of all leaf metrics."""
+ result = set()
+ for x in self.metric_list:
+ result = result.union(x.Flatten())
+
+ return result
+
+ def ToPerfJson(self) -> str:
+ return json.dumps(sorted(self.Flatten()), indent=2, cls=_MetricJsonEncoder)
+
+ def __str__(self) -> str:
+ return self.ToPerfJson()
+
+
+class _RewriteIfExpToSelect(ast.NodeTransformer):
+
+ def visit_IfExp(self, node):
+ # pylint: disable=invalid-name
+ self.generic_visit(node)
+ call = ast.Call(
+ func=ast.Name(id='Select', ctx=ast.Load()),
+ args=[node.body, node.test, node.orelse],
+ keywords=[])
+ ast.copy_location(call, node.test)
+ return call
+
+
+def ParsePerfJson(orig: str) -> Expression:
+ """A simple json metric expression decoder.
+
+ Converts a json encoded metric expression by way of python's ast and
+ eval routine. First tokens are mapped to Event calls, then
+ accidentally converted keywords or literals are mapped to their
+ appropriate calls. Python's ast is used to match if-else that can't
+ be handled via operator overloading. Finally the ast is evaluated.
+
+ Args:
+ orig (str): String to parse.
+
+ Returns:
+ Expression: The parsed string.
+ """
+ # pylint: disable=eval-used
+ py = orig.strip()
+ py = re.sub(r'([a-zA-Z][^-+/\* \\\(\),]*(?:\\.[^-+/\* \\\(\),]*)*)',
+ r'Event(r"\1")', py)
+ py = re.sub(r'#Event\(r"([^"]*)"\)', r'Literal("#\1")', py)
+ py = re.sub(r'([0-9]+)Event\(r"(e[0-9]+)"\)', r'\1\2', py)
+ keywords = ['if', 'else', 'min', 'max', 'd_ratio', 'source_count']
+ for kw in keywords:
+ py = re.sub(rf'Event\(r"{kw}"\)', kw, py)
+
+ parsed = ast.parse(py, mode='eval')
+ _RewriteIfExpToSelect().visit(parsed)
+ parsed = ast.fix_missing_locations(parsed)
+ return _Constify(eval(compile(parsed, orig, 'eval')))