blob: 24fb6f76eb4738ed227f7c830d42cbda820e0824 [file]
// Copyright (C) 2026 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import {describe, expect, test, vi} from 'vitest';
import type {App} from '../../public/app';
import type {
ProbeSetting,
RecordProbe,
} from '../dev.perfetto.RecordTraceV2/config/config_interfaces';
import {TraceConfigBuilder} from '../dev.perfetto.RecordTraceV2/config/trace_config_builder';
import type {RecordingManager} from '../dev.perfetto.RecordTraceV2/recording_manager';
import {getCpuId, type ArmTelemetryCpuSpec} from './arm_telemetry_spec';
import type {
ArmTelemetrySpecChange,
ArmTelemetrySpecChangeCallback,
ArmTelemetrySpecManager,
} from './arm_telemetry_spec_manager';
import {
deserializePerfConfigurationState,
normalizeCpuLeader,
type PerfConfigurationState,
type PerfCpuConfigurationState,
pmuRecordSection,
serializePerfConfigurationState,
} from './pmu';
type PmuOptionForTest = {
id: string;
};
type PmuCpuConfigurationComponentForTest = {
controller: {
cpus: Map<string, string>;
pmuOptions: PmuOptionForTest[];
};
};
type ComponentConstructorForTest<T> = new () => T;
type PerfConfigurationComponentForTest = {
oninit: (vnode: {
attrs: {
state: PerfConfigurationState;
app: App;
recMgr: RecordingManager;
specManager: ArmTelemetrySpecManager;
};
}) => void;
view: (vnode: {
attrs: {
state: PerfConfigurationState;
app: App;
recMgr: RecordingManager;
specManager: ArmTelemetrySpecManager;
};
}) => {
children: Array<unknown>;
};
};
type PerfCpuConfigurationComponentConstructorForTest =
ComponentConstructorForTest<
PmuCpuConfigurationComponentForTest & {
oninit: (vnode: {
attrs: {
state: PerfCpuConfigurationState;
app: App;
recMgr: RecordingManager;
specManager: ArmTelemetrySpecManager;
};
}) => void;
}
>;
const CPU_SPEC: ArmTelemetryCpuSpec = {
product_configuration: {
product_name: 'Test CPU',
part_num: '333',
major_revision: 1,
minor_revision: 0,
implementer: '0x41',
architecture: 'armv9',
pmu_architecture: 'pmuv3',
num_slots: 4,
},
events: {
CPU_CYCLES: {
code: '0x11',
title: 'Cycles',
description: 'Cycles',
},
INST_RETIRED: {
code: '0x08',
title: 'Instructions',
description: 'Instructions',
},
},
metrics: {},
groups: {
function: {},
metrics: {},
},
methodologies: {
topdown_methodology: {
decision_tree: {
root_nodes: [],
metrics: [],
},
},
},
};
const CPU_SPEC_1: ArmTelemetryCpuSpec = {
...CPU_SPEC,
product_configuration: {
...CPU_SPEC.product_configuration,
product_name: 'Updated Test CPU',
},
events: {
BR_RETIRED: {
code: '0x21',
title: 'Branches retired',
description: 'Branches retired',
},
L1D_CACHE_REFILL: {
code: '0x03',
title: 'L1D cache refill',
description: 'L1D cache refill',
},
},
};
const CPU_SPEC_2: ArmTelemetryCpuSpec = {
...CPU_SPEC,
product_configuration: {
...CPU_SPEC.product_configuration,
product_name: 'Added Test CPU',
part_num: '444',
},
events: {
STALL_BACKEND: {
code: '0x24',
title: 'Backend stalls',
description: 'Backend stalls',
},
},
};
class FakeArmTelemetrySpecManager implements ArmTelemetrySpecManager {
private callbacks = new Set<ArmTelemetrySpecChangeCallback>();
private cpuDescs = new Map<string, ArmTelemetryCpuSpec | undefined>();
constructor(
cpuDesc: ArmTelemetryCpuSpec | undefined,
private readonly cpuids: string[] = ['0x4114d'],
) {
this.cpuids.forEach((cpuid) => {
this.cpuDescs.set(cpuid, cpuid === '0x4114d' ? cpuDesc : undefined);
});
}
add(desc: ArmTelemetryCpuSpec): void {
this.cpuDescs.set(getCpuId(desc), desc);
this.notify({kind: 'ADD', desc});
}
update(desc: ArmTelemetryCpuSpec): void {
this.cpuDescs.set(getCpuId(desc), desc);
this.notify({kind: 'UPDATE', desc});
}
clear(): void {
this.cpuDescs.clear();
this.notify({kind: 'CLEAR'});
}
hasSpecs(): boolean {
return this.cpuDescs.size > 0;
}
registeredCpuids(): string[] {
return [...this.cpuDescs.keys()];
}
getCpuDesc(cpuid: string): ArmTelemetryCpuSpec {
if (!this.cpuDescs.has(cpuid)) {
throw new Error(`Unexpected CPU ID: ${cpuid}`);
}
return this.cpuDescs.get(cpuid) as ArmTelemetryCpuSpec;
}
addOnChangeCallback(callback: ArmTelemetrySpecChangeCallback): Disposable {
this.callbacks.add(callback);
return {[Symbol.dispose]: () => this.callbacks.delete(callback)};
}
notify(change: ArmTelemetrySpecChange): void {
this.callbacks.forEach((callback) => callback(change));
}
}
function createFakeAppForTest(): App {
return {
raf: {
scheduleFullRedraw: vi.fn(),
},
} as unknown as App;
}
function createSpecManagerForTest(
specManager: ArmTelemetrySpecManager = new FakeArmTelemetrySpecManager(
CPU_SPEC,
),
) {
return specManager;
}
function createPmuProbeForTest(
specManager: ArmTelemetrySpecManager = new FakeArmTelemetrySpecManager(
CPU_SPEC,
),
app: App = {} as App,
): RecordProbe {
const subpage = pmuRecordSection({} as RecordingManager, app, specManager);
if (subpage.kind !== 'PROBES_PAGE') {
throw new Error('Expected PMU record section to be a probes page');
}
return subpage.probes[0];
}
function getPmuSettingForTest(probe = createPmuProbeForTest()): ProbeSetting {
return probe.settings!.testSetting;
}
function validSerializedPmuConfig(
configurationOverrides: Record<string, unknown> = {},
) {
return {
currentTabKey: 'tab-1',
tabs: [
{
key: 'tab-1',
configuration: {
cpuSelected: '0x4114d',
pmusSelected: ['CPU_CYCLES', 'INST_RETIRED'],
cpuLeader: 'CPU_CYCLES',
cpuLeaderUserSelected: true,
cpuPerfType: '8',
sampleByFrequency: true,
frequency: 1000,
period: 10000,
captureCallstack: true,
...configurationOverrides,
},
},
],
};
}
function genConfigForTest(serializedState: unknown): TraceConfigBuilder {
const probe = createPmuProbeForTest();
getPmuSettingForTest(probe).deserialize(serializedState);
const tc = new TraceConfigBuilder();
probe.genConfig(tc);
return tc;
}
function dataSourceForTest(tc: TraceConfigBuilder, name: string) {
return tc.dataSources.get(`${name}undefined`)?.config;
}
function perfEventConfigForTest(tc: TraceConfigBuilder, index = 0) {
return dataSourceForTest(tc, `linux.perf ${index}`)?.perfEventConfig;
}
function mutableStateForTest(setting: ProbeSetting): PerfConfigurationState {
const vnode = setting.render() as unknown as {
children: Array<{attrs: {state: PerfConfigurationState}}>;
};
return vnode.children[0].attrs.state;
}
function initializePmuTabForTest(setting: ProbeSetting) {
const containerVnode = setting.render() as unknown as {
children: Array<{
tag: ComponentConstructorForTest<PerfConfigurationComponentForTest>;
attrs: {
state: PerfConfigurationState;
app: App;
recMgr: RecordingManager;
specManager: ArmTelemetrySpecManager;
};
}>;
};
const perfConfigurationVnode = containerVnode.children[0];
const perfConfiguration = new perfConfigurationVnode.tag();
perfConfiguration.oninit({attrs: perfConfigurationVnode.attrs});
const rendered = perfConfiguration.view({
attrs: perfConfigurationVnode.attrs,
});
const tabsVnode = rendered.children[2] as {
attrs: {
tabs: Array<{
content: {
tag: PerfCpuConfigurationComponentConstructorForTest;
attrs: {
state: PerfCpuConfigurationState;
app: App;
recMgr: RecordingManager;
specManager: ArmTelemetrySpecManager;
};
};
}>;
};
};
const perfCpuConfigurations: PmuCpuConfigurationComponentForTest[] =
tabsVnode.attrs.tabs.map((tab) => {
const perfCpuConfiguration = new tab.content.tag();
perfCpuConfiguration.oninit({attrs: tab.content.attrs});
return perfCpuConfiguration;
});
return {
state: perfConfigurationVnode.attrs.state,
perfConfiguration,
perfCpuConfigurations,
};
}
describe('PMU config persistence', () => {
test('serializes all PMU tabs and settings', () => {
const state: PerfConfigurationState = {
currentTabKey: 'tab-2',
tabs: [
{
key: 'tab-1',
configuration: {
cpuSelected: '0x4114d',
pmusSelected: ['CPU_CYCLES'],
onCpuSelected: () => {},
cpuLeader: 'CPU_CYCLES',
cpuLeaderUserSelected: false,
targetCpus: '0,2-3',
cpuPerfType: '8',
sampleByFrequency: false,
period: 5000,
frequency: 100,
captureCallstack: true,
},
},
{
key: 'tab-2',
configuration: {
cpuSelected: undefined,
pmusSelected: [],
onCpuSelected: undefined,
cpuLeader: undefined,
cpuLeaderUserSelected: false,
targetCpus: undefined,
cpuPerfType: undefined,
sampleByFrequency: true,
period: 10000,
frequency: 100,
captureCallstack: false,
},
},
],
};
expect(serializePerfConfigurationState(state)).toEqual({
currentTabKey: 'tab-2',
tabs: [
{
key: 'tab-1',
configuration: {
cpuSelected: '0x4114d',
pmusSelected: ['CPU_CYCLES'],
cpuLeader: 'CPU_CYCLES',
cpuLeaderUserSelected: false,
targetCpus: '0,2-3',
cpuPerfType: '8',
sampleByFrequency: false,
period: 5000,
frequency: 100,
captureCallstack: true,
},
},
{
key: 'tab-2',
configuration: {
cpuSelected: undefined,
pmusSelected: [],
cpuLeader: undefined,
cpuLeaderUserSelected: false,
targetCpus: undefined,
cpuPerfType: undefined,
sampleByFrequency: true,
period: 10000,
frequency: 100,
captureCallstack: false,
},
},
],
});
});
test('deserializes with validation and defaults', () => {
const specManager = createSpecManagerForTest();
const state: PerfConfigurationState = {
currentTabKey: undefined,
tabs: [],
};
deserializePerfConfigurationState(specManager, state, {
currentTabKey: 'tab-1',
tabs: [
{
key: 'tab-1',
configuration: {
cpuSelected: '0x4114d',
pmusSelected: ['CPU_CYCLES', 'DOES_NOT_EXIST', 'INST_RETIRED'],
cpuLeader: 'DOES_NOT_EXIST',
cpuLeaderUserSelected: true,
targetCpus: '0,2-3',
cpuPerfType: '8',
sampleByFrequency: false,
period: 5000,
frequency: 1000,
captureCallstack: true,
},
},
{
key: 'tab-2',
configuration: {
cpuSelected: 'missing-cpu',
pmusSelected: ['CPU_CYCLES'],
cpuLeader: 'CPU_CYCLES',
cpuLeaderUserSelected: false,
targetCpus: '1,,3',
cpuPerfType: '-1',
sampleByFrequency: 'yes',
period: 0,
frequency: -1,
captureCallstack: 'no',
},
},
{
key: 'tab-3',
configuration: {
cpuSelected: '0x4114d',
pmusSelected: ['CPU_CYCLES', 'INST_RETIRED'],
cpuLeader: 'INST_RETIRED',
},
},
],
});
expect(state).toEqual({
currentTabKey: 'tab-1',
tabs: [
{
key: 'tab-1',
configuration: {
cpuSelected: '0x4114d',
pmusSelected: ['CPU_CYCLES', 'INST_RETIRED'],
onCpuSelected: undefined,
cpuLeader: 'CPU_CYCLES',
cpuLeaderUserSelected: true,
targetCpus: '0,2-3',
cpuPerfType: '8',
sampleByFrequency: false,
period: 5000,
frequency: 1000,
captureCallstack: true,
},
},
{
key: 'tab-2',
configuration: {
cpuSelected: undefined,
pmusSelected: [],
onCpuSelected: undefined,
cpuLeader: undefined,
cpuLeaderUserSelected: false,
targetCpus: undefined,
cpuPerfType: undefined,
sampleByFrequency: true,
period: 10000,
frequency: 100,
captureCallstack: false,
},
},
{
key: 'tab-3',
configuration: {
cpuSelected: '0x4114d',
pmusSelected: ['CPU_CYCLES', 'INST_RETIRED'],
onCpuSelected: undefined,
cpuLeader: 'INST_RETIRED',
cpuLeaderUserSelected: false,
targetCpus: undefined,
cpuPerfType: undefined,
sampleByFrequency: true,
period: 10000,
frequency: 100,
captureCallstack: false,
},
},
],
});
});
test('drops PMU selection when a registered CPU descriptor is unavailable after refresh', () => {
const specManager = createSpecManagerForTest(
new FakeArmTelemetrySpecManager(undefined),
);
const state: PerfConfigurationState = {
currentTabKey: undefined,
tabs: [],
};
deserializePerfConfigurationState(specManager, state, {
currentTabKey: 'tab-1',
tabs: [
{
key: 'tab-1',
configuration: {
cpuSelected: '0x4114d',
pmusSelected: ['CPU_CYCLES', 'INST_RETIRED'],
cpuLeader: 'CPU_CYCLES',
cpuLeaderUserSelected: true,
targetCpus: '0,2-3',
cpuPerfType: '8',
sampleByFrequency: false,
period: 5000,
frequency: 1000,
captureCallstack: true,
},
},
],
});
expect(state).toEqual({
currentTabKey: 'tab-1',
tabs: [
{
key: 'tab-1',
configuration: {
cpuSelected: undefined,
pmusSelected: [],
onCpuSelected: undefined,
cpuLeader: undefined,
cpuLeaderUserSelected: false,
targetCpus: '0,2-3',
cpuPerfType: '8',
sampleByFrequency: false,
period: 5000,
frequency: 1000,
captureCallstack: true,
},
},
],
});
});
test('refreshes PMU tabs when the selected CPU spec is replaced', () => {
expect(getCpuId(CPU_SPEC_1)).toEqual(getCpuId(CPU_SPEC));
const specManager = new FakeArmTelemetrySpecManager(CPU_SPEC);
const app = createFakeAppForTest();
const probe = createPmuProbeForTest(specManager, app);
const setting = getPmuSettingForTest(probe);
setting.deserialize({
currentTabKey: 'tab-1',
tabs: [
validSerializedPmuConfig().tabs[0],
{
key: 'tab-2',
configuration: {
cpuSelected: '0x4114d',
pmusSelected: ['INST_RETIRED'],
cpuLeader: 'INST_RETIRED',
cpuLeaderUserSelected: true,
cpuPerfType: '8',
sampleByFrequency: true,
frequency: 1000,
period: 10000,
captureCallstack: false,
},
},
],
});
const {state, perfCpuConfigurations} = initializePmuTabForTest(setting);
expect(
perfCpuConfigurations.map((component) => component.controller.cpus),
).toEqual([
new Map([['0x4114d', 'Test CPU']]),
new Map([['0x4114d', 'Test CPU']]),
]);
expect(
perfCpuConfigurations.map((component) =>
component.controller.pmuOptions.map((option) => option.id),
),
).toEqual([
['CPU_CYCLES', 'INST_RETIRED'],
['CPU_CYCLES', 'INST_RETIRED'],
]);
specManager.update(CPU_SPEC_1);
expect(
state.tabs.map((tab) => ({
cpuSelected: tab.configuration.cpuSelected,
pmusSelected: tab.configuration.pmusSelected,
cpuLeader: tab.configuration.cpuLeader,
cpuLeaderUserSelected: tab.configuration.cpuLeaderUserSelected,
})),
).toEqual([
{
cpuSelected: '0x4114d',
pmusSelected: [],
cpuLeader: undefined,
cpuLeaderUserSelected: false,
},
{
cpuSelected: '0x4114d',
pmusSelected: [],
cpuLeader: undefined,
cpuLeaderUserSelected: false,
},
]);
expect(
perfCpuConfigurations.map((component) => component.controller.cpus),
).toEqual([
new Map([['0x4114d', 'Updated Test CPU']]),
new Map([['0x4114d', 'Updated Test CPU']]),
]);
expect(
perfCpuConfigurations.map((component) =>
component.controller.pmuOptions.map((option) => option.id),
),
).toEqual([
['BR_RETIRED', 'L1D_CACHE_REFILL'],
['BR_RETIRED', 'L1D_CACHE_REFILL'],
]);
expect(app.raf.scheduleFullRedraw).toHaveBeenCalled();
});
test('refreshes CPU lists when a new CPU spec is added', () => {
expect(getCpuId(CPU_SPEC_2)).not.toEqual(getCpuId(CPU_SPEC));
const specManager = new FakeArmTelemetrySpecManager(CPU_SPEC);
const app = createFakeAppForTest();
const probe = createPmuProbeForTest(specManager, app);
const setting = getPmuSettingForTest(probe);
setting.deserialize(validSerializedPmuConfig());
const {state, perfCpuConfigurations} = initializePmuTabForTest(setting);
expect(perfCpuConfigurations[0].controller.cpus).toEqual(
new Map([['0x4114d', 'Test CPU']]),
);
specManager.add(CPU_SPEC_2);
expect(state.tabs[0].configuration).toEqual({
cpuSelected: '0x4114d',
pmusSelected: ['CPU_CYCLES', 'INST_RETIRED'],
onCpuSelected: expect.any(Function),
cpuLeader: 'CPU_CYCLES',
cpuLeaderUserSelected: true,
targetCpus: undefined,
cpuPerfType: '8',
sampleByFrequency: true,
period: 10000,
frequency: 1000,
captureCallstack: true,
});
expect(perfCpuConfigurations[0].controller.cpus).toEqual(
new Map([
['0x4114d', 'Test CPU'],
[getCpuId(CPU_SPEC_2), 'Added Test CPU'],
]),
);
expect(
perfCpuConfigurations[0].controller.pmuOptions.map((option) => option.id),
).toEqual(['CPU_CYCLES', 'INST_RETIRED']);
expect(app.raf.scheduleFullRedraw).toHaveBeenCalled();
});
test('clears PMU tabs when all CPU specs are cleared', () => {
const specManager = new FakeArmTelemetrySpecManager(CPU_SPEC);
const app = createFakeAppForTest();
const probe = createPmuProbeForTest(specManager, app);
const setting = getPmuSettingForTest(probe);
setting.deserialize({
currentTabKey: 'tab-1',
tabs: [
validSerializedPmuConfig().tabs[0],
{
key: 'tab-2',
configuration: {
cpuSelected: '0x4114d',
pmusSelected: ['INST_RETIRED'],
cpuLeader: 'INST_RETIRED',
cpuLeaderUserSelected: true,
cpuPerfType: '8',
sampleByFrequency: true,
frequency: 1000,
period: 10000,
captureCallstack: false,
},
},
],
});
const {state, perfCpuConfigurations} = initializePmuTabForTest(setting);
expect(perfCpuConfigurations[0].controller.cpus).toEqual(
new Map([['0x4114d', 'Test CPU']]),
);
specManager.clear();
expect(
state.tabs.map((tab) => ({
cpuSelected: tab.configuration.cpuSelected,
pmusSelected: tab.configuration.pmusSelected,
cpuLeader: tab.configuration.cpuLeader,
cpuLeaderUserSelected: tab.configuration.cpuLeaderUserSelected,
})),
).toEqual([
{
cpuSelected: undefined,
pmusSelected: [],
cpuLeader: undefined,
cpuLeaderUserSelected: false,
},
{
cpuSelected: undefined,
pmusSelected: [],
cpuLeader: undefined,
cpuLeaderUserSelected: false,
},
]);
expect(
perfCpuConfigurations.map((component) => component.controller.cpus),
).toEqual([new Map(), new Map()]);
expect(app.raf.scheduleFullRedraw).toHaveBeenCalled();
});
test('auto leader prefers CPU_CYCLES unless the user picked a leader', () => {
expect(
normalizeCpuLeader('INST_RETIRED', ['CPU_CYCLES', 'INST_RETIRED'], false),
).toEqual('CPU_CYCLES');
expect(
normalizeCpuLeader('INST_RETIRED', ['CPU_CYCLES', 'INST_RETIRED'], true),
).toEqual('INST_RETIRED');
expect(normalizeCpuLeader(undefined, ['INST_RETIRED'], false)).toEqual(
'INST_RETIRED',
);
});
test('auto leader handles stale and empty selections', () => {
expect(normalizeCpuLeader('MISSING', ['INST_RETIRED'], true)).toEqual(
'INST_RETIRED',
);
expect(normalizeCpuLeader('CPU_CYCLES', [], true)).toBeUndefined();
expect(normalizeCpuLeader(' ', ['CPU_CYCLES'], true)).toEqual(
'CPU_CYCLES',
);
});
test('deserializes duplicate tab keys with unique replacements', () => {
const specManager = createSpecManagerForTest();
const state: PerfConfigurationState = {
currentTabKey: undefined,
tabs: [],
};
deserializePerfConfigurationState(specManager, state, {
currentTabKey: 'tab-1',
tabs: [
{key: 'tab-1', configuration: {}},
{key: 'tab-1', configuration: {}},
],
});
expect(state.currentTabKey).toEqual('tab-1');
expect(state.tabs[0].key).toEqual('tab-1');
expect(state.tabs[1].key).not.toEqual('tab-1');
expect(new Set(state.tabs.map((tab) => tab.key)).size).toEqual(2);
});
test('deserializes invalid current tab key with first-tab fallback', () => {
const specManager = createSpecManagerForTest();
const state: PerfConfigurationState = {
currentTabKey: undefined,
tabs: [],
};
deserializePerfConfigurationState(specManager, state, {
currentTabKey: 'missing-tab',
tabs: [
{key: 'tab-1', configuration: {}},
{key: 'tab-2', configuration: {}},
],
});
expect(state.currentTabKey).toEqual('tab-1');
});
test('deserializes non-object or missing tabs by clearing state', () => {
const specManager = createSpecManagerForTest();
const state: PerfConfigurationState = {
currentTabKey: 'tab-1',
tabs: [
{
key: 'tab-1',
configuration: {
cpuSelected: '0x4114d',
pmusSelected: ['CPU_CYCLES'],
onCpuSelected: undefined,
cpuLeader: 'CPU_CYCLES',
cpuLeaderUserSelected: false,
targetCpus: undefined,
cpuPerfType: '8',
sampleByFrequency: true,
period: 10000,
frequency: 100,
captureCallstack: false,
},
},
],
};
deserializePerfConfigurationState(specManager, state, undefined);
expect(state).toEqual({currentTabKey: undefined, tabs: []});
deserializePerfConfigurationState(specManager, state, {
currentTabKey: 'tab-1',
});
expect(state).toEqual({currentTabKey: undefined, tabs: []});
});
});
describe('PMU config generation', () => {
test('genConfig emits frequency-based PMU config', () => {
const tc = genConfigForTest(validSerializedPmuConfig());
expect(dataSourceForTest(tc, 'linux.system_info')).toBeDefined();
expect(dataSourceForTest(tc, 'linux.perf 0')).toBeDefined();
const perfConf = perfEventConfigForTest(tc)!;
expect(perfConf.timebase).toEqual({
name: 'CPU_CYCLES',
rawEvent: {
type: 8,
config: 0x11,
},
frequency: 1000,
});
expect(perfConf.followers).toEqual([
{
name: 'INST_RETIRED',
rawEvent: {
type: 8,
config: 0x08,
},
},
]);
expect(perfConf.callstackSampling).toEqual({});
});
test('genConfig emits period-based PMU config', () => {
const tc = genConfigForTest(
validSerializedPmuConfig({
sampleByFrequency: false,
period: 5000,
}),
);
const timebase = perfEventConfigForTest(tc)!.timebase!;
expect(timebase.period).toEqual(5000);
expect(timebase.frequency).toBeUndefined();
});
test('genConfig uses targetCpu when targetCpus is configured', () => {
const tc = genConfigForTest(
validSerializedPmuConfig({
targetCpus: '0,2-3',
}),
);
const perfConf = perfEventConfigForTest(tc)!;
expect(perfConf.targetCpu).toEqual([0, 2, 3]);
expect(perfConf.cpuid).toBeUndefined();
});
test('genConfig falls back to cpuid when targetCpus is omitted', () => {
const tc = genConfigForTest(validSerializedPmuConfig());
const perfConf = perfEventConfigForTest(tc)!;
expect(perfConf.cpuid).toEqual(['4114d']);
});
test('genConfig skips incomplete tabs', () => {
const probe = createPmuProbeForTest();
const setting = getPmuSettingForTest(probe);
const state = mutableStateForTest(setting);
state.tabs = [
{
key: 'missing-cpu',
configuration: {
cpuSelected: undefined,
pmusSelected: ['CPU_CYCLES'],
onCpuSelected: undefined,
cpuLeader: 'CPU_CYCLES',
cpuLeaderUserSelected: false,
targetCpus: undefined,
cpuPerfType: '8',
sampleByFrequency: true,
period: 10000,
frequency: 1000,
captureCallstack: false,
},
},
{
key: 'empty-pmus',
configuration: {
cpuSelected: '0x4114d',
pmusSelected: [],
onCpuSelected: undefined,
cpuLeader: 'CPU_CYCLES',
cpuLeaderUserSelected: false,
targetCpus: undefined,
cpuPerfType: '8',
sampleByFrequency: true,
period: 10000,
frequency: 1000,
captureCallstack: false,
},
},
{
key: 'missing-leader',
configuration: {
cpuSelected: '0x4114d',
pmusSelected: ['CPU_CYCLES'],
onCpuSelected: undefined,
cpuLeader: undefined,
cpuLeaderUserSelected: false,
targetCpus: undefined,
cpuPerfType: '8',
sampleByFrequency: true,
period: 10000,
frequency: 1000,
captureCallstack: false,
},
},
];
const tc = new TraceConfigBuilder();
probe.genConfig(tc);
expect(perfEventConfigForTest(tc)).toBeUndefined();
expect(perfEventConfigForTest(tc, 1)).toBeUndefined();
expect(perfEventConfigForTest(tc, 2)).toBeUndefined();
});
test.each([
[
'invalid perf CPU event type',
(state: PerfConfigurationState) => {
state.tabs[0].configuration.cpuPerfType = '0';
},
'PMU Configuration Error: Invalid perf CPU event type',
],
[
'invalid sampling frequency',
(state: PerfConfigurationState) => {
state.tabs[0].configuration.frequency = 0;
},
'PMU Configuration Error: Invalid sampling frequency',
],
[
'invalid sampling period',
(state: PerfConfigurationState) => {
state.tabs[0].configuration.sampleByFrequency = false;
state.tabs[0].configuration.period = 0;
},
'PMU Configuration Error: Invalid sampling period',
],
[
'invalid target CPUs',
(state: PerfConfigurationState) => {
state.tabs[0].configuration.targetCpus = '1,,3';
},
'PMU Configuration Error: Invalid target CPU value',
],
])('genConfig rejects %s', (_name, mutateState, alertMessage) => {
const probe = createPmuProbeForTest();
const setting = getPmuSettingForTest(probe);
setting.deserialize(validSerializedPmuConfig());
mutateState(mutableStateForTest(setting));
const alert = vi.fn();
vi.stubGlobal('alert', alert);
const tc = new TraceConfigBuilder();
try {
probe.genConfig(tc);
} finally {
vi.unstubAllGlobals();
}
expect(alert).toHaveBeenCalledWith(alertMessage);
expect(perfEventConfigForTest(tc)).toBeUndefined();
});
});