Skip to content

Commit ea00018

Browse files
committed
ui: Add Arm telemetry spec plugin
Add a new UI plugin for Arm CPU telemetry specifications. Introduces the ArmTelemetryManager interface for parsing, storing, and accessing the telemetry specifications. These specifications are unique to Arm CPU cores and include: - Events: PMU events available on the core. - Metrics: Computed from events for performance analysis. - Groups: Coherent collections of events or metrics. - Methodology: Describes how to conduct performance and micro-architecture analysis using the available metrics. The ArmTelemetryManager is initialized when the plugin is activated, so other plugins can access the shared telemetry spec registry.
1 parent 9317640 commit ea00018

4 files changed

Lines changed: 289 additions & 0 deletions

File tree

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
// Copyright (C) 2026 The Android Open Source Project
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
import {z} from 'zod';
16+
17+
const ARM_TELEMETRY_PMU_EVENT_SCHEMA = z.object({
18+
code: z.string(),
19+
title: z.string(),
20+
description: z.string(),
21+
});
22+
23+
const ARM_TELEMETRY_PMU_METRIC_SCHEMA = z.object({
24+
title: z.string(),
25+
formula: z.string(),
26+
description: z.string(),
27+
units: z.string(),
28+
events: z.array(z.string()),
29+
});
30+
31+
export type ArmTelemetryPmuMetric = z.infer<
32+
typeof ARM_TELEMETRY_PMU_METRIC_SCHEMA
33+
>;
34+
35+
const ARM_TELEMETRY_PMU_EVENT_GROUP_SCHEMA = z.object({
36+
title: z.string(),
37+
description: z.string(),
38+
events: z.array(z.string()),
39+
});
40+
41+
const ARM_TELEMETRY_PMU_METRIC_GROUP_SCHEMA = z.object({
42+
title: z.string(),
43+
description: z.string(),
44+
metrics: z.array(z.string()),
45+
});
46+
47+
export const ARM_TELEMETRY_CPU_SPEC_SCHEMA = z.object({
48+
product_configuration: z.object({
49+
product_name: z.string(),
50+
part_num: z.string(),
51+
major_revision: z.coerce.number(),
52+
minor_revision: z.coerce.number(),
53+
implementer: z.string(),
54+
architecture: z.string(),
55+
pmu_architecture: z.string(),
56+
num_slots: z.number(),
57+
}),
58+
events: z.record(z.string(), ARM_TELEMETRY_PMU_EVENT_SCHEMA),
59+
metrics: z.record(z.string(), ARM_TELEMETRY_PMU_METRIC_SCHEMA),
60+
groups: z.object({
61+
function: z.record(z.string(), ARM_TELEMETRY_PMU_EVENT_GROUP_SCHEMA),
62+
metrics: z.record(z.string(), ARM_TELEMETRY_PMU_METRIC_GROUP_SCHEMA),
63+
}),
64+
methodologies: z.object({
65+
topdown_methodology: z.object({
66+
decision_tree: z.object({
67+
root_nodes: z.array(z.string()),
68+
metrics: z.array(
69+
z.object({
70+
name: z.string(),
71+
next_items: z.array(z.string()),
72+
}),
73+
),
74+
}),
75+
}),
76+
}),
77+
});
78+
79+
export type ArmTelemetryCpuSpec = z.infer<typeof ARM_TELEMETRY_CPU_SPEC_SCHEMA>;
80+
81+
export function getCpuId(desc: ArmTelemetryCpuSpec): string {
82+
return (
83+
desc.product_configuration.implementer +
84+
Number(desc.product_configuration.part_num).toString(16)
85+
);
86+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
// Copyright (C) 2026 The Android Open Source Project
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
import {ArmTelemetryCpuSpec} from './arm_telemetry_spec';
16+
17+
export type ArmTelemetrySpecChangeCallback = (
18+
change: 'ADD' | 'REMOVE' | 'UPDATE',
19+
desc: ArmTelemetryCpuSpec,
20+
) => void;
21+
22+
export interface ArmTelemetryManager {
23+
parse(data: string): ArmTelemetryCpuSpec | undefined;
24+
add(desc: ArmTelemetryCpuSpec): void;
25+
update(desc: ArmTelemetryCpuSpec): void;
26+
addOnChangeCallback(callback: ArmTelemetrySpecChangeCallback): Disposable;
27+
registeredCpuids(): string[];
28+
getCpuDesc(cpuid: string): ArmTelemetryCpuSpec;
29+
}
Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
// Copyright (C) 2026 The Android Open Source Project
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
import {Registry} from '../../base/registry';
16+
import {exists} from '../../base/utils';
17+
import {App} from '../../public/app';
18+
import {
19+
ARM_TELEMETRY_CPU_SPEC_SCHEMA,
20+
ArmTelemetryCpuSpec,
21+
getCpuId,
22+
} from './arm_telemetry_spec';
23+
import {
24+
ArmTelemetrySpecChangeCallback,
25+
ArmTelemetryManager,
26+
} from './arm_telemetry_spec_manager';
27+
28+
function validateArmTelemetrySpec(cpuDesc: unknown): ArmTelemetryCpuSpec {
29+
const result = ARM_TELEMETRY_CPU_SPEC_SCHEMA.parse(cpuDesc);
30+
31+
const eventNames = Object.keys(result.events);
32+
const metricNames = Object.keys(result.metrics);
33+
34+
for (const [name, metric] of Object.entries(result.metrics)) {
35+
if (!metric.events.every((eventName) => eventNames.includes(eventName))) {
36+
throw new Error(`Non existent event listed in metric ${name}`);
37+
}
38+
}
39+
40+
for (const [name, group] of Object.entries(result.groups.function)) {
41+
if (!group.events.every((eventName) => eventNames.includes(eventName))) {
42+
throw new Error(`Non existent event listed in function group ${name}`);
43+
}
44+
}
45+
46+
for (const [name, group] of Object.entries(result.groups.metrics)) {
47+
if (
48+
!group.metrics.every((metricName) => metricNames.includes(metricName))
49+
) {
50+
throw new Error(`Non existent metric listed in function group ${name}`);
51+
}
52+
}
53+
54+
const rootNodes =
55+
result.methodologies.topdown_methodology.decision_tree.root_nodes;
56+
if (!rootNodes.every((metricName) => metricNames.includes(metricName))) {
57+
throw new Error(`Non existent metric listed at top down methodology root`);
58+
}
59+
60+
const metricAndMetricGroupNames = [
61+
...Object.keys(result.groups.metrics),
62+
...Object.keys(result.metrics),
63+
];
64+
const topDownMetrics =
65+
result.methodologies.topdown_methodology.decision_tree.metrics;
66+
for (const metric of topDownMetrics) {
67+
if (
68+
!metric.next_items.every((groupName) =>
69+
metricAndMetricGroupNames.includes(groupName),
70+
)
71+
) {
72+
throw new Error(
73+
`Non existent metric group ${metric.name} listed in top down methodology tree`,
74+
);
75+
}
76+
}
77+
78+
return result;
79+
}
80+
81+
class CpuRegistry extends Registry<ArmTelemetryCpuSpec> {
82+
constructor() {
83+
super((cpu) => getCpuId(cpu));
84+
}
85+
}
86+
87+
export class ArmTelemetryManagerImpl implements ArmTelemetryManager {
88+
constructor(private readonly app: App) {}
89+
90+
private readonly cpuRegistry = new CpuRegistry();
91+
private readonly changeCallbacks = new Set<ArmTelemetrySpecChangeCallback>();
92+
private readonly registryEntries = new Map<string, Disposable>();
93+
94+
parse(data: string): ArmTelemetryCpuSpec | undefined {
95+
try {
96+
const json = JSON.parse(data);
97+
return validateArmTelemetrySpec(json);
98+
} catch {
99+
return undefined;
100+
}
101+
}
102+
103+
add(desc: ArmTelemetryCpuSpec): void {
104+
const entry = this.cpuRegistry.register(desc);
105+
this.registryEntries.set(getCpuId(desc), entry);
106+
this.changeCallbacks.forEach((cb) => cb('ADD', desc));
107+
this.app.raf.scheduleFullRedraw();
108+
}
109+
110+
update(desc: ArmTelemetryCpuSpec): void {
111+
let entry = this.registryEntries.get(getCpuId(desc));
112+
if (exists(entry)) {
113+
entry[Symbol.dispose]();
114+
entry = this.cpuRegistry.register(desc);
115+
this.registryEntries.set(getCpuId(desc), entry);
116+
this.changeCallbacks.forEach((cb) => cb('UPDATE', desc));
117+
this.app.raf.scheduleFullRedraw();
118+
return;
119+
}
120+
this.add(desc);
121+
}
122+
123+
addOnChangeCallback(callback: ArmTelemetrySpecChangeCallback): Disposable {
124+
this.changeCallbacks.add(callback);
125+
return {
126+
[Symbol.dispose]: () => {
127+
this.changeCallbacks.delete(callback);
128+
},
129+
};
130+
}
131+
132+
registeredCpuids(): string[] {
133+
return [...this.registryEntries.keys()];
134+
}
135+
136+
getCpuDesc(cpuid: string): ArmTelemetryCpuSpec {
137+
return this.cpuRegistry.get(cpuid);
138+
}
139+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
// Copyright (C) 2026 The Android Open Source Project
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
import {App} from '../../public/app';
16+
import {PerfettoPlugin} from '../../public/plugin';
17+
import {Trace} from '../../public/trace';
18+
import {ArmTelemetryManager} from './arm_telemetry_spec_manager';
19+
import {ArmTelemetryManagerImpl} from './arm_telemetry_spec_manager_impl';
20+
21+
export default class ArmTelemetrySpecPlugin implements PerfettoPlugin {
22+
static readonly id = 'com.arm.ArmTelemetrySpec';
23+
static readonly description = 'Arm telemetry specification plugin';
24+
private static manager: ArmTelemetryManager;
25+
26+
static onActivate(app: App): void {
27+
ArmTelemetrySpecPlugin.manager = new ArmTelemetryManagerImpl(app);
28+
}
29+
30+
async onTraceLoad(_trace: Trace): Promise<void> {}
31+
32+
static getManager(): ArmTelemetryManager {
33+
return ArmTelemetrySpecPlugin.manager;
34+
}
35+
}

0 commit comments

Comments
 (0)