Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,10 @@
import m from 'mithril';
import {TargetPlatformId} from '../interfaces/target_platform';
import {TraceConfigBuilder} from './trace_config_builder';
import {RecordPluginSchema, RecordSessionSchema} from '../serialization_schema';
import {
ProbesSessionSchema,
RecordPluginSchema,
} from '../serialization_schema';

/**
* A sub-page of the Record page.
Expand Down Expand Up @@ -59,8 +62,8 @@ export type RecordSubpage = {

// Save-restore the page state into the JSON object that is saved in
// localstorage and shared when sharing a config.
serialize(state: RecordSessionSchema): void;
deserialize(state: RecordSessionSchema): void;
serialize(state: ProbesSessionSchema): void;
deserialize(state: ProbesSessionSchema): void;
}
| {
kind: 'GLOBAL_PAGE';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import {Slider} from './widgets/slider';
import {RecordMode, TraceConfigBuilder} from '../config/trace_config_builder';
import {ConfigManager} from '../config/config_manager';
import {RecordSubpage} from '../config/config_interfaces';
import {RecordSessionSchema} from '../serialization_schema';
import {ProbesSessionSchema} from '../serialization_schema';
import {Toggle} from './widgets/toggle';

type RecMgrAttrs = {recMgr: RecordingManager};
Expand All @@ -34,7 +34,7 @@ export function bufferConfigPage(recMgr: RecordingManager): RecordSubpage {
render() {
return m(BufferConfigPage, {recMgr});
},
serialize(state: RecordSessionSchema) {
serialize(state: ProbesSessionSchema) {
const tc: TraceConfigBuilder = recMgr.recordConfig.traceConfig;
state.mode = tc.mode;
state.bufSizeKb = tc.defaultBuffer.sizeKb;
Expand All @@ -43,7 +43,7 @@ export function bufferConfigPage(recMgr: RecordingManager): RecordSubpage {
state.fileWritePeriodMs = tc.fileWritePeriodMs;
state.compression = tc.compression;
},
async deserialize(state: RecordSessionSchema) {
async deserialize(state: ProbesSessionSchema) {
const tc: TraceConfigBuilder = recMgr.recordConfig.traceConfig;
tc.mode = state.mode;
tc.defaultBuffer.sizeKb = state.bufSizeKb;
Expand Down
30 changes: 26 additions & 4 deletions ui/src/plugins/dev.perfetto.RecordTraceV2/pages/record_page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -128,9 +128,20 @@ export class RecordPageV2 implements m.ClassComponent<RecordPageAttrs> {
private renderSubpage(page: RecordSubpage): m.Children {
switch (page.kind) {
case 'PROBES_PAGE':
return page.probes
.filter((p) => supportsPlatform(p, this.recMgr.currentPlatform))
.map((probe) => m(Probe, {cfgMgr: this.recMgr.recordConfig, probe}));
return [
this.recMgr.hasCustomTraceConfig &&
m(
Callout,
{intent: Intent.Primary, icon: 'upload_file'},
'Using imported custom config. Changes to probe settings ' +
'below will not take effect.',
),
...page.probes
.filter((p) => supportsPlatform(p, this.recMgr.currentPlatform))
.map((probe) =>
m(Probe, {cfgMgr: this.recMgr.recordConfig, probe}),
),
];
case 'GLOBAL_PAGE':
case 'SESSION_PAGE':
return page.render();
Expand Down Expand Up @@ -201,8 +212,19 @@ export class RecordPageV2 implements m.ClassComponent<RecordPageAttrs> {
},
}),
),
this.recMgr.hasCustomTraceConfig &&
m(
'.pf-custom-config-notice',
m(Icon, {icon: 'upload_file'}),
m('span', 'Imported config overrides probe settings'),
),
m(
'ul',
{
className: this.recMgr.hasCustomTraceConfig
? 'pf-probes-disabled'
: '',
},
this.getSortedProbes(Array.from(pages.values())).map((rc) =>
this.renderMenuEntry(rc),
),
Expand Down Expand Up @@ -252,7 +274,7 @@ export class RecordPageV2 implements m.ClassComponent<RecordPageAttrs> {
showModal({title: 'Restore error', content: res.error});
return;
}
this.recMgr.app.navigate('#!/record/cmdline');
this.recMgr.app.navigate('#!/record');
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,16 @@ import {DisposableStack} from '../../../base/disposable_stack';
import {CurrentTracingSession, RecordingManager} from '../recording_manager';
import {download} from '../../../base/download_utils';
import {RecordSubpage} from '../config/config_interfaces';
import {RecordPluginSchema} from '../serialization_schema';
import {RecordPluginSchema, SavedSessionSchema} from '../serialization_schema';
import {Checkbox} from '../../../widgets/checkbox';
import {linkify} from '../../../widgets/anchor';
import {getPresetsForPlatform} from '../presets';
import {Icons} from '../../../base/semantic_icons';
import {shareRecordConfig} from '../config/config_sharing';
import {Card} from '../../../widgets/card';
import {showModal} from '../../../widgets/modal';
import {traceConfigToPb} from '../../../base/proto_utils_wasm';
import protos from '../../../protos';

type RecMgrAttrs = {recMgr: RecordingManager};

Expand Down Expand Up @@ -68,7 +71,7 @@ export function targetSelectionPage(recMgr: RecordingManager): RecordSubpage {
// Restore config
const hasSavedProbes =
state.lastSession !== undefined &&
state.lastSession.probes !== undefined &&
state.lastSession.kind === 'probes' &&
Object.keys(state.lastSession.probes).length > 0;

if (state.selectedConfigId || hasSavedProbes) {
Expand Down Expand Up @@ -185,6 +188,7 @@ class RecordConfigSelector implements m.ClassComponent<RecMgrAttrs> {
const isEmptySelected =
recMgr.selectedConfigId === undefined &&
recMgr.isConfigModified === false &&
!recMgr.hasCustomTraceConfig &&
!recMgr.recordConfig.hasActiveProbes();

return [
Expand Down Expand Up @@ -225,62 +229,63 @@ class RecordConfigSelector implements m.ClassComponent<RecMgrAttrs> {

private renderSavedConfigsSection(recMgr: RecordingManager) {
const hasActiveProbes = recMgr.recordConfig.hasActiveProbes();
const hasUnsavedCustomConfig =
recMgr.hasCustomTraceConfig && recMgr.selectedConfigId === undefined;
const shouldHighlightSave =
hasUnsavedCustomConfig ||
(hasActiveProbes && recMgr.selectedConfigId === undefined) ||
recMgr.isConfigModified === true;
const hasSavedConfigs = recMgr.savedConfigs.length > 0;
const showSection = hasSavedConfigs || shouldHighlightSave;
if (!showSection) {
return null;
}
return [
m('h3', 'User configs'),
m('.pf-config-selector__grid', [
// Saved configs
...recMgr.savedConfigs.map((config) => {
...recMgr.savedConfigs.map((saved) => {
const isSelected =
recMgr.selectedConfigId === `saved:${config.name}` &&
recMgr.selectedConfigId === `saved:${saved.name}` &&
recMgr.isConfigModified === false;
const config = saved.config;
const isCustom = config.kind === 'custom';
return m(
Card,
{
className:
'pf-preset-card' +
(isSelected ? ' pf-preset-card--selected' : ''),
onclick: () =>
recMgr.loadConfig({
config: config.config,
configId: `saved:${config.name}`,
configName: config.name,
}),
onclick: () => this.loadSavedConfig(recMgr, saved),
tabindex: 0,
},
m(Icon, {icon: 'bookmark'}),
m('.pf-preset-card__title', config.name),
m(Icon, {icon: isCustom ? 'description' : 'bookmark'}),
m('.pf-preset-card__title', saved.name),
isCustom &&
m(
'.pf-preset-card__subtitle',
`Imported from ${config.customConfigFileName ?? 'textproto'}`,
),
m('.pf-preset-card__actions', [
m(Button, {
icon: 'save',
compact: true,
title: 'Overwrite with current settings',
onclick: (e: Event) => {
e.stopPropagation();
if (
confirm(
`Overwrite config "${config.name}" with current settings?`,
)
) {
recMgr.saveConfig(config.name, recMgr.serializeSession());
recMgr.app.raf.scheduleFullRedraw();
}
},
}),
!isCustom &&
m(Button, {
icon: 'save',
compact: true,
title: 'Overwrite with current settings',
onclick: (e: Event) => {
e.stopPropagation();
if (
confirm(
`Overwrite config "${saved.name}" with current settings?`,
)
) {
recMgr.saveConfig(saved.name);
recMgr.app.raf.scheduleFullRedraw();
}
},
}),
m(Button, {
icon: 'share',
compact: true,
title: 'Share configuration',
onclick: (e: Event) => {
e.stopPropagation();
shareRecordConfig(config.config);
shareRecordConfig(saved.config);
},
}),
m(Button, {
Expand All @@ -289,8 +294,8 @@ class RecordConfigSelector implements m.ClassComponent<RecMgrAttrs> {
title: 'Delete configuration',
onclick: (e: Event) => {
e.stopPropagation();
if (confirm(`Delete "${config.name}"?`)) {
recMgr.deleteConfig(config.name);
if (confirm(`Delete "${saved.name}"?`)) {
recMgr.deleteConfig(saved.name);
recMgr.app.raf.scheduleFullRedraw();
}
},
Expand All @@ -315,26 +320,70 @@ class RecordConfigSelector implements m.ClassComponent<RecMgrAttrs> {
);
return;
}
const savedConfig = recMgr.serializeSession();
recMgr.saveConfig(trimmedName, savedConfig);
recMgr.loadConfig({
config: savedConfig,
configId: `saved:${trimmedName}`,
configName: trimmedName,
});
const saved = recMgr.saveConfig(trimmedName);
this.loadSavedConfig(recMgr, saved);
recMgr.app.raf.scheduleFullRedraw();
}
},
tabindex: 0,
},
m(Icon, {icon: 'tune'}),
m('.pf-preset-card__title', 'Custom'),
m(Icon, {icon: hasUnsavedCustomConfig ? 'description' : 'tune'}),
m(
'.pf-preset-card__title',
hasUnsavedCustomConfig
? recMgr.customConfigFileName ?? 'Imported config'
: 'Custom',
),
m('.pf-preset-card__subtitle', 'Click to save'),
),
this.renderImportCard(recMgr),
]),
];
}

private loadSavedConfig(recMgr: RecordingManager, saved: SavedSessionSchema) {
recMgr.loadConfig({
config: saved.config,
configId: `saved:${saved.name}`,
configName: saved.name,
});
}

private renderImportCard(recMgr: RecordingManager) {
return m(
Card,
{
className: 'pf-preset-card pf-preset-card--dashed',
onclick: () => this.openImportDialog(recMgr),
tabindex: 0,
},
m(Icon, {icon: 'upload_file'}),
m('.pf-preset-card__title', 'Import'),
m('.pf-preset-card__subtitle', 'Load textproto'),
);
}

private openImportDialog(recMgr: RecordingManager) {
const input = document.createElement('input');
input.type = 'file';
input.onchange = async () => {
const file = input.files?.[0];
if (!file) return;
const text = await file.text();
const res = await traceConfigToPb(text);
if (!res.ok) {
showModal({
title: 'Import error',
content: `Failed to parse config: ${res.error}`,
});
return;
}
const config = protos.TraceConfig.decode(res.value);
recMgr.setCustomTraceConfig(config, file.name);
};
input.click();
}

private renderCard(
icon: string,
title: string,
Expand Down
8 changes: 8 additions & 0 deletions ui/src/plugins/dev.perfetto.RecordTraceV2/presets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ const CHROME_DEFAULT_PRESET: Preset = {
subtitle: 'Common Chrome trace events',
icon: 'public',
session: {
kind: 'probes',
mode: 'STOP_WHEN_FULL',
bufSizeKb: 256 * 1024,
durationMs: 30_000,
Expand All @@ -133,6 +134,7 @@ const CHROME_V8_PRESET: Preset = {
subtitle: 'JavaScript, wasm & GC',
icon: 'mode_fan',
session: {
kind: 'probes',
mode: 'STOP_WHEN_FULL',
bufSizeKb: 256 * 1024,
durationMs: 30_000,
Expand Down Expand Up @@ -174,6 +176,7 @@ export const ANDROID_PRESETS: Preset[] = [
subtitle: 'The default config for general purpose tracing',
icon: 'auto_awesome',
session: {
kind: 'probes',
mode: 'STOP_WHEN_FULL',
bufSizeKb: 64 * 1024,
durationMs: 10_000,
Expand Down Expand Up @@ -206,6 +209,7 @@ export const ANDROID_PRESETS: Preset[] = [
subtitle: 'Battery usage and power consumption',
icon: 'battery_profile',
session: {
kind: 'probes',
mode: 'STOP_WHEN_FULL',
bufSizeKb: 64 * 1024,
durationMs: 30_000,
Expand All @@ -231,6 +235,7 @@ export const ANDROID_PRESETS: Preset[] = [
subtitle: 'Thermal throttling and mitigation',
icon: 'thermostat',
session: {
kind: 'probes',
mode: 'STOP_WHEN_FULL',
bufSizeKb: 64 * 1024,
durationMs: 30_000,
Expand Down Expand Up @@ -259,6 +264,7 @@ export const ANDROID_PRESETS: Preset[] = [
subtitle: 'Graphics pipeline and system compositor',
icon: 'layers',
session: {
kind: 'probes',
mode: 'STOP_WHEN_FULL',
bufSizeKb: 64 * 1024,
durationMs: 30000,
Expand Down Expand Up @@ -297,6 +303,7 @@ export const LINUX_PRESETS: Preset[] = [
subtitle: 'General purpose CPU and system tracing',
icon: 'auto_awesome',
session: {
kind: 'probes',
mode: 'STOP_WHEN_FULL',
bufSizeKb: 64 * 1024,
durationMs: 10_000,
Expand All @@ -318,6 +325,7 @@ export const LINUX_PRESETS: Preset[] = [
subtitle: 'CPU scheduling and process activity',
icon: 'schedule',
session: {
kind: 'probes',
mode: 'STOP_WHEN_FULL',
bufSizeKb: 64 * 1024,
durationMs: 10_000,
Expand Down
Loading