Merge changes I1d0c46ce,I126a9ef5 into main
* changes:
ui: add support for saving/restoring sets of tracks by name
ui: switch pinned tracks save/restore feature to use Zod
diff --git a/src/trace_processor/perfetto_sql/stdlib/wattson/curves/w_dsu_dependence.sql b/src/trace_processor/perfetto_sql/stdlib/wattson/curves/w_dsu_dependence.sql
index dbaf4ac..9435c09 100644
--- a/src/trace_processor/perfetto_sql/stdlib/wattson/curves/w_dsu_dependence.sql
+++ b/src/trace_processor/perfetto_sql/stdlib/wattson/curves/w_dsu_dependence.sql
@@ -55,6 +55,22 @@
base.freq_7 = lut7.freq_khz AND
base.idle_7 = lut7.idle;
+-- Get nominal devfreq_dsu counter, OR use a dummy one for Pixel 9 VM traces
+-- The VM doesn't have a DSU, so the placeholder value of FMin is put in. The
+-- DSU frequency is a prerequisite for power estimation on Pixel 9.
+CREATE PERFETTO TABLE _dsu_frequency AS
+SELECT * from linux_devfreq_dsu_counter
+UNION ALL
+SELECT
+ 0 as id,
+ trace_start() as ts,
+ trace_end() - trace_start() as dur,
+ 610000 as dsu_freq
+-- Only add this for traces from a VM on Pixel 9 where DSU values aren't present
+WHERE (SELECT str_value FROM metadata WHERE name = 'android_guest_soc_model')
+ IN (SELECT device FROM _use_devfreq)
+ AND (SELECT COUNT(*) FROM linux_devfreq_dsu_counter) = 0;
+
CREATE PERFETTO TABLE _w_dsu_dependence AS
SELECT
c.ts, c.dur,
@@ -80,10 +96,10 @@
FROM _interval_intersect!(
(
_ii_subquery!(_cpu_curves),
- _ii_subquery!(linux_devfreq_dsu_counter)
+ _ii_subquery!(_dsu_frequency)
),
()
) ii
JOIN _cpu_curves AS c ON c._auto_id = id_0
-JOIN linux_devfreq_dsu_counter AS d on d._auto_id = id_1;
+JOIN _dsu_frequency AS d on d._auto_id = id_1;
diff --git a/test/data/wattson_tk4_vm.pb.sha256 b/test/data/wattson_tk4_vm.pb.sha256
new file mode 100644
index 0000000..52b09dd
--- /dev/null
+++ b/test/data/wattson_tk4_vm.pb.sha256
@@ -0,0 +1 @@
+12beb14bc06e28ecbdfc618d423aeea2badd449aeb9ccc65a457aa6c05ebf2be
\ No newline at end of file
diff --git a/test/trace_processor/diff_tests/stdlib/wattson/tests.py b/test/trace_processor/diff_tests/stdlib/wattson/tests.py
index b5c9cae..5b20333 100644
--- a/test/trace_processor/diff_tests/stdlib/wattson/tests.py
+++ b/test/trace_processor/diff_tests/stdlib/wattson/tests.py
@@ -452,3 +452,30 @@
452415394221,69579176303,13654,13361,11651,9609,1
564873995228,135118729231,45223,37594,22798,20132,1
"""))
+
+ # Tests traces from VM that have incomplete CPU tracks
+ def test_wattson_missing_cpus_on_guest(self):
+ return DiffTestBlueprint(
+ trace=DataPath('wattson_tk4_vm.pb'),
+ query=("""
+ INCLUDE PERFETTO MODULE wattson.curves.estimates;
+ SELECT
+ ts, dur, cpu0_mw, cpu1_mw, cpu2_mw, cpu3_mw, cpu4_mw, cpu5_mw,
+ cpu6_mw
+ FROM _system_state_mw
+ WHERE ts > 25150000000
+ LIMIT 10
+ """),
+ out=Csv("""
+ "ts","dur","cpu0_mw","cpu1_mw","cpu2_mw","cpu3_mw","cpu4_mw","cpu5_mw","cpu6_mw"
+ 25150029000,1080,0.000000,0.000000,0.000000,0.000000,70.050000,83.260000,0.000000
+ 25150030640,42920,0.000000,0.000000,0.000000,0.000000,70.050000,70.050000,0.000000
+ 25150073560,99800,0.000000,0.000000,0.000000,0.000000,70.050000,0.000000,0.000000
+ 25150173360,28240,176.280000,0.000000,0.000000,0.000000,70.050000,0.000000,0.000000
+ 25150201600,6480,176.280000,0.000000,0.000000,176.280000,70.050000,0.000000,0.000000
+ 25150208080,29840,176.280000,0.000000,0.000000,176.280000,70.050000,70.050000,0.000000
+ 25150237920,129800,0.000000,0.000000,0.000000,176.280000,70.050000,70.050000,0.000000
+ 25150367720,37480,0.000000,0.000000,0.000000,176.280000,70.050000,0.000000,0.000000
+ 25150405200,15120,0.000000,176.280000,0.000000,176.280000,70.050000,0.000000,0.000000
+ 25150420320,15920,0.000000,176.280000,0.000000,0.000000,70.050000,0.000000,0.000000
+ """))
diff --git a/ui/src/assets/explore_page.scss b/ui/src/assets/explore_page.scss
new file mode 100644
index 0000000..c31b288
--- /dev/null
+++ b/ui/src/assets/explore_page.scss
@@ -0,0 +1,16 @@
+// Copyright (C) 2024 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.
+.explore-page {
+ overflow: auto;
+}
diff --git a/ui/src/assets/perfetto.scss b/ui/src/assets/perfetto.scss
index 3e04c42..cb4387d 100644
--- a/ui/src/assets/perfetto.scss
+++ b/ui/src/assets/perfetto.scss
@@ -30,6 +30,7 @@
@import "viz_page";
@import "widgets_page";
@import "plugins_page";
+@import "explore_page";
// Widgets - keep these sorted (they should NOT have any inter-dependencies)
@import "widgets/anchor";
diff --git a/ui/src/frontend/flow_events_renderer.ts b/ui/src/frontend/flow_events_renderer.ts
index 28ee34b..e221954 100644
--- a/ui/src/frontend/flow_events_renderer.ts
+++ b/ui/src/frontend/flow_events_renderer.ts
@@ -18,7 +18,7 @@
import {Flow} from '../core/flow_types';
import {RenderedPanelInfo} from './panel_container';
import {TimeScale} from '../base/time_scale';
-import {TrackNode} from '../public/workspace';
+import {TrackNode, TrackNodeContainer} from '../public/workspace';
import {TraceImpl} from '../core/trace_impl';
const TRACK_GROUP_CONNECTION_OFFSET = 5;
@@ -57,6 +57,7 @@
ctx: CanvasRenderingContext2D,
size: Size2D,
panels: ReadonlyArray<RenderedPanelInfo>,
+ tracks: TrackNodeContainer,
): void {
const timescale = new TimeScale(trace.timeline.visibleWindow, {
left: 0,
@@ -74,7 +75,7 @@
// the tree to find containing groups.
const sqlTrackIdToTrack = new Map<number, TrackNode>();
- trace.workspace.flatTracks.forEach((track) =>
+ tracks.flatTracks.forEach((track) =>
track.uri
? trace.tracks
.getTrack(track.uri)
diff --git a/ui/src/frontend/viewer_page.ts b/ui/src/frontend/viewer_page.ts
index 9509c1d..7bb2af7 100644
--- a/ui/src/frontend/viewer_page.ts
+++ b/ui/src/frontend/viewer_page.ts
@@ -24,7 +24,7 @@
import {TimeScale} from '../base/time_scale';
import {featureFlags} from '../core/feature_flags';
import {raf} from '../core/raf_scheduler';
-import {TrackNode} from '../public/workspace';
+import {TrackNode, TrackNodeContainer} from '../public/workspace';
import {TRACK_BORDER_COLOR, TRACK_SHELL_WIDTH} from './css_constants';
import {renderFlows} from './flow_events_renderer';
import {generateTicks, getMaxMajorTicks, TickType} from './gridline_helper';
@@ -323,44 +323,58 @@
m(PanelContainer, {
trace: attrs.trace,
className: 'pinned-panel-container',
- panels: attrs.trace.workspace.pinnedTracks.map((trackNode) => {
- if (trackNode.uri) {
- const tr = attrs.trace.tracks.getTrackRenderer(trackNode.uri);
- return new TrackPanel({
- trace: attrs.trace,
- reorderable: true,
- node: trackNode,
- trackRenderer: tr,
- revealOnCreate: true,
- indentationLevel: 0,
- topOffsetPx: 0,
- });
- } else {
- return new TrackPanel({
- trace: attrs.trace,
- node: trackNode,
- revealOnCreate: true,
- indentationLevel: 0,
- topOffsetPx: 0,
- });
- }
- }),
+ panels: AppImpl.instance.isLoadingTrace
+ ? []
+ : attrs.trace.workspace.pinnedTracks.map((trackNode) => {
+ if (trackNode.uri) {
+ const tr = attrs.trace.tracks.getTrackRenderer(trackNode.uri);
+ return new TrackPanel({
+ trace: attrs.trace,
+ reorderable: true,
+ node: trackNode,
+ trackRenderer: tr,
+ revealOnCreate: true,
+ indentationLevel: 0,
+ topOffsetPx: 0,
+ });
+ } else {
+ return new TrackPanel({
+ trace: attrs.trace,
+ node: trackNode,
+ revealOnCreate: true,
+ indentationLevel: 0,
+ topOffsetPx: 0,
+ });
+ }
+ }),
renderUnderlay: (ctx, size) => renderUnderlay(attrs.trace, ctx, size),
renderOverlay: (ctx, size, panels) =>
- renderOverlay(attrs.trace, ctx, size, panels),
+ renderOverlay(
+ attrs.trace,
+ ctx,
+ size,
+ panels,
+ attrs.trace.workspace.pinnedRoot,
+ ),
selectedYRange: this.getYRange('pinned-panel-container'),
}),
m(PanelContainer, {
trace: attrs.trace,
className: 'scrolling-panel-container',
- panels: scrollingPanels,
+ panels: AppImpl.instance.isLoadingTrace ? [] : scrollingPanels,
onPanelStackResize: (width) => {
const timelineWidth = width - TRACK_SHELL_WIDTH;
this.timelineWidthPx = timelineWidth;
},
renderUnderlay: (ctx, size) => renderUnderlay(attrs.trace, ctx, size),
renderOverlay: (ctx, size, panels) =>
- renderOverlay(attrs.trace, ctx, size, panels),
+ renderOverlay(
+ attrs.trace,
+ ctx,
+ size,
+ panels,
+ attrs.trace.workspace,
+ ),
selectedYRange: this.getYRange('scrolling-panel-container'),
}),
),
@@ -411,6 +425,7 @@
ctx: CanvasRenderingContext2D,
canvasSize: Size2D,
panels: ReadonlyArray<RenderedPanelInfo>,
+ trackContainer: TrackNodeContainer,
): void {
const size = {
width: canvasSize.width - TRACK_SHELL_WIDTH,
@@ -422,7 +437,7 @@
canvasClip(ctx, 0, 0, size.width, size.height);
// TODO(primiano): plumb the TraceImpl obj throughout the viwer page.
- renderFlows(trace, ctx, size, panels);
+ renderFlows(trace, ctx, size, panels, trackContainer);
const timewindow = trace.timeline.visibleWindow;
const timescale = new TimeScale(timewindow, {left: 0, right: size.width});
diff --git a/ui/src/plugins/dev.perfetto.AndroidDmabuf/index.ts b/ui/src/plugins/dev.perfetto.AndroidDmabuf/index.ts
index a946cc2..b9d0919 100644
--- a/ui/src/plugins/dev.perfetto.AndroidDmabuf/index.ts
+++ b/ui/src/plugins/dev.perfetto.AndroidDmabuf/index.ts
@@ -17,13 +17,10 @@
SqlDataSource,
} from '../../public/lib/tracks/query_counter_track';
import {PerfettoPlugin} from '../../public/plugin';
-import {
- getOrCreateGroupForProcess,
- getOrCreateGroupForThread,
-} from '../../public/standard_groups';
import {Trace} from '../../public/trace';
import {TrackNode} from '../../public/workspace';
import {NUM_NULL} from '../../trace_processor/query_result';
+import ProcessThreadGroupsPlugin from '../dev.perfetto.ProcessThreadGroups';
async function registerAllocsTrack(
ctx: Trace,
@@ -44,6 +41,8 @@
export default class implements PerfettoPlugin {
static readonly id = 'dev.perfetto.AndroidDmabuf';
+ static readonly dependencies = [ProcessThreadGroupsPlugin];
+
async onTraceLoad(ctx: Trace): Promise<void> {
const e = ctx.engine;
await e.query(`INCLUDE PERFETTO MODULE android.memory.dmabuf`);
@@ -66,9 +65,10 @@
WHERE upid = ${it.upid}`,
};
await registerAllocsTrack(ctx, uri, config);
- getOrCreateGroupForProcess(ctx.workspace, it.upid).addChildInOrder(
- new TrackNode({uri, title: 'dmabuf allocs'}),
- );
+ ctx.plugins
+ .getPlugin(ProcessThreadGroupsPlugin)
+ .getGroupForProcess(it.upid)
+ ?.addChildInOrder(new TrackNode({uri, title: 'dmabuf allocs'}));
} else if (it.utid != null) {
const uri = `/android_process_dmabuf_utid_${it.utid}`;
const config: SqlDataSource = {
@@ -76,9 +76,10 @@
WHERE utid = ${it.utid}`,
};
await registerAllocsTrack(ctx, uri, config);
- getOrCreateGroupForThread(ctx.workspace, it.utid).addChildInOrder(
- new TrackNode({uri, title: 'dmabuf allocs'}),
- );
+ ctx.plugins
+ .getPlugin(ProcessThreadGroupsPlugin)
+ .getGroupForThread(it.utid)
+ ?.addChildInOrder(new TrackNode({uri, title: 'dmabuf allocs'}));
}
}
}
diff --git a/ui/src/plugins/dev.perfetto.AsyncSlices/index.ts b/ui/src/plugins/dev.perfetto.AsyncSlices/index.ts
index ecd8dab..e5940cd 100644
--- a/ui/src/plugins/dev.perfetto.AsyncSlices/index.ts
+++ b/ui/src/plugins/dev.perfetto.AsyncSlices/index.ts
@@ -20,19 +20,18 @@
import {getThreadUriPrefix, getTrackName} from '../../public/utils';
import {NUM, NUM_NULL, STR, STR_NULL} from '../../trace_processor/query_result';
import {AsyncSliceTrack} from './async_slice_track';
-import {
- getOrCreateGroupForProcess,
- getOrCreateGroupForThread,
-} from '../../public/standard_groups';
import {exists} from '../../base/utils';
import {assertExists, assertTrue} from '../../base/logging';
import {SliceSelectionAggregator} from './slice_selection_aggregator';
import {sqlTableRegistry} from '../../frontend/widgets/sql/table/sql_table_registry';
import {getSliceTable} from './table';
import {extensions} from '../../public/lib/extensions';
+import ProcessThreadGroupsPlugin from '../dev.perfetto.ProcessThreadGroups';
export default class implements PerfettoPlugin {
static readonly id = 'dev.perfetto.AsyncSlices';
+ static readonly dependencies = [ProcessThreadGroupsPlugin];
+
async onTraceLoad(ctx: Trace): Promise<void> {
const trackIdsToUris = new Map<number, string>();
@@ -298,8 +297,10 @@
if (parent !== false && parent !== undefined) {
parent.trackNode.addChildInOrder(t.trackNode);
} else {
- const processGroup = getOrCreateGroupForProcess(ctx.workspace, t.upid);
- processGroup.addChildInOrder(t.trackNode);
+ const processGroup = ctx.plugins
+ .getPlugin(ProcessThreadGroupsPlugin)
+ .getGroupForProcess(t.upid);
+ processGroup?.addChildInOrder(t.trackNode);
}
});
}
@@ -399,8 +400,10 @@
if (parent !== false && parent !== undefined) {
parent.trackNode.addChildInOrder(t.trackNode);
} else {
- const group = getOrCreateGroupForThread(ctx.workspace, t.utid);
- group.addChildInOrder(t.trackNode);
+ const group = ctx.plugins
+ .getPlugin(ProcessThreadGroupsPlugin)
+ .getGroupForThread(t.utid);
+ group?.addChildInOrder(t.trackNode);
}
});
}
diff --git a/ui/src/plugins/dev.perfetto.Counter/index.ts b/ui/src/plugins/dev.perfetto.Counter/index.ts
index ef063ca..47a8f61 100644
--- a/ui/src/plugins/dev.perfetto.Counter/index.ts
+++ b/ui/src/plugins/dev.perfetto.Counter/index.ts
@@ -27,11 +27,8 @@
import {TraceProcessorCounterTrack} from './trace_processor_counter_track';
import {exists} from '../../base/utils';
import {TrackNode} from '../../public/workspace';
-import {
- getOrCreateGroupForProcess,
- getOrCreateGroupForThread,
-} from '../../public/standard_groups';
import {CounterSelectionAggregator} from './counter_selection_aggregator';
+import ProcessThreadGroupsPlugin from '../dev.perfetto.ProcessThreadGroups';
const NETWORK_TRACK_REGEX = new RegExp('^.* (Received|Transmitted)( KB)?$');
const ENTITY_RESIDENCY_REGEX = new RegExp('^Entity residency:');
@@ -108,6 +105,8 @@
export default class implements PerfettoPlugin {
static readonly id = 'dev.perfetto.Counter';
+ static readonly dependencies = [ProcessThreadGroupsPlugin];
+
async onTraceLoad(ctx: Trace): Promise<void> {
await this.addCounterTracks(ctx);
await this.addGpuFrequencyTracks(ctx);
@@ -313,9 +312,11 @@
name,
),
});
- const group = getOrCreateGroupForThread(ctx.workspace, utid);
+ const group = ctx.plugins
+ .getPlugin(ProcessThreadGroupsPlugin)
+ .getGroupForThread(utid);
const track = new TrackNode({uri, title: name, sortOrder: 30});
- group.addChildInOrder(track);
+ group?.addChildInOrder(track);
}
}
@@ -371,9 +372,11 @@
name,
),
});
- const group = getOrCreateGroupForProcess(ctx.workspace, upid);
+ const group = ctx.plugins
+ .getPlugin(ProcessThreadGroupsPlugin)
+ .getGroupForProcess(upid);
const track = new TrackNode({uri, title: name, sortOrder: 20});
- group.addChildInOrder(track);
+ group?.addChildInOrder(track);
}
}
diff --git a/ui/src/plugins/dev.perfetto.CpuProfile/index.ts b/ui/src/plugins/dev.perfetto.CpuProfile/index.ts
index e33e341..05718df 100644
--- a/ui/src/plugins/dev.perfetto.CpuProfile/index.ts
+++ b/ui/src/plugins/dev.perfetto.CpuProfile/index.ts
@@ -19,11 +19,13 @@
import {CpuProfileTrack} from './cpu_profile_track';
import {getThreadUriPrefix} from '../../public/utils';
import {exists} from '../../base/utils';
-import {getOrCreateGroupForThread} from '../../public/standard_groups';
import {TrackNode} from '../../public/workspace';
+import ProcessThreadGroupsPlugin from '../dev.perfetto.ProcessThreadGroups';
export default class implements PerfettoPlugin {
static readonly id = 'dev.perfetto.CpuProfile';
+ static readonly dependencies = [ProcessThreadGroupsPlugin];
+
async onTraceLoad(ctx: Trace): Promise<void> {
const result = await ctx.engine.query(`
with thread_cpu_sample as (
@@ -62,9 +64,11 @@
},
track: new CpuProfileTrack(ctx, uri, utid),
});
- const group = getOrCreateGroupForThread(ctx.workspace, utid);
+ const group = ctx.plugins
+ .getPlugin(ProcessThreadGroupsPlugin)
+ .getGroupForThread(utid);
const track = new TrackNode({uri, title, sortOrder: -40});
- group.addChildInOrder(track);
+ group?.addChildInOrder(track);
}
}
}
diff --git a/ui/src/plugins/dev.perfetto.Frames/index.ts b/ui/src/plugins/dev.perfetto.Frames/index.ts
index 8d3abec..d9df86c 100644
--- a/ui/src/plugins/dev.perfetto.Frames/index.ts
+++ b/ui/src/plugins/dev.perfetto.Frames/index.ts
@@ -18,16 +18,18 @@
} from '../../public/track_kinds';
import {Trace} from '../../public/trace';
import {PerfettoPlugin} from '../../public/plugin';
-import {getOrCreateGroupForProcess} from '../../public/standard_groups';
import {getTrackName} from '../../public/utils';
import {TrackNode} from '../../public/workspace';
import {NUM, NUM_NULL, STR, STR_NULL} from '../../trace_processor/query_result';
import {ActualFramesTrack} from './actual_frames_track';
import {ExpectedFramesTrack} from './expected_frames_track';
import {FrameSelectionAggregator} from './frame_selection_aggregator';
+import ProcessThreadGroupsPlugin from '../dev.perfetto.ProcessThreadGroups';
export default class implements PerfettoPlugin {
static readonly id = 'dev.perfetto.Frames';
+ static readonly dependencies = [ProcessThreadGroupsPlugin];
+
async onTraceLoad(ctx: Trace): Promise<void> {
this.addExpectedFrames(ctx);
this.addActualFrames(ctx);
@@ -88,9 +90,11 @@
kind: EXPECTED_FRAMES_SLICE_TRACK_KIND,
},
});
- const group = getOrCreateGroupForProcess(ctx.workspace, upid);
+ const group = ctx.plugins
+ .getPlugin(ProcessThreadGroupsPlugin)
+ .getGroupForProcess(upid);
const track = new TrackNode({uri, title, sortOrder: -50});
- group.addChildInOrder(track);
+ group?.addChildInOrder(track);
}
}
@@ -151,9 +155,11 @@
kind: ACTUAL_FRAMES_SLICE_TRACK_KIND,
},
});
- const group = getOrCreateGroupForProcess(ctx.workspace, upid);
+ const group = ctx.plugins
+ .getPlugin(ProcessThreadGroupsPlugin)
+ .getGroupForProcess(upid);
const track = new TrackNode({uri, title, sortOrder: -50});
- group.addChildInOrder(track);
+ group?.addChildInOrder(track);
}
}
}
diff --git a/ui/src/plugins/dev.perfetto.HeapProfile/index.ts b/ui/src/plugins/dev.perfetto.HeapProfile/index.ts
index 2e0591f..fcd79a3 100644
--- a/ui/src/plugins/dev.perfetto.HeapProfile/index.ts
+++ b/ui/src/plugins/dev.perfetto.HeapProfile/index.ts
@@ -17,9 +17,9 @@
import {PerfettoPlugin} from '../../public/plugin';
import {LONG, NUM, STR} from '../../trace_processor/query_result';
import {HeapProfileTrack} from './heap_profile_track';
-import {getOrCreateGroupForProcess} from '../../public/standard_groups';
import {TrackNode} from '../../public/workspace';
import {createPerfettoTable} from '../../trace_processor/sql_utils';
+import ProcessThreadGroupsPlugin from '../dev.perfetto.ProcessThreadGroups';
function getUriForTrack(upid: number): string {
return `/process_${upid}/heap_profile`;
@@ -27,6 +27,8 @@
export default class implements PerfettoPlugin {
static readonly id = 'dev.perfetto.HeapProfile';
+ static readonly dependencies = [ProcessThreadGroupsPlugin];
+
async onTraceLoad(ctx: Trace): Promise<void> {
const it = await ctx.engine.query(`
select value from stats
@@ -94,9 +96,11 @@
},
track: new HeapProfileTrack(ctx, uri, tableName, upid, incomplete),
});
- const group = getOrCreateGroupForProcess(ctx.workspace, upid);
+ const group = ctx.plugins
+ .getPlugin(ProcessThreadGroupsPlugin)
+ .getGroupForProcess(upid);
const track = new TrackNode({uri, title, sortOrder: -30});
- group.addChildInOrder(track);
+ group?.addChildInOrder(track);
}
ctx.addEventListener('traceready', async () => {
diff --git a/ui/src/plugins/dev.perfetto.PerfSamplesProfile/index.ts b/ui/src/plugins/dev.perfetto.PerfSamplesProfile/index.ts
index 04e8b13..1488376 100644
--- a/ui/src/plugins/dev.perfetto.PerfSamplesProfile/index.ts
+++ b/ui/src/plugins/dev.perfetto.PerfSamplesProfile/index.ts
@@ -23,11 +23,8 @@
ThreadPerfSamplesProfileTrack,
} from './perf_samples_profile_track';
import {getThreadUriPrefix} from '../../public/utils';
-import {
- getOrCreateGroupForProcess,
- getOrCreateGroupForThread,
-} from '../../public/standard_groups';
import {TrackNode} from '../../public/workspace';
+import ProcessThreadGroupsPlugin from '../dev.perfetto.ProcessThreadGroups';
export interface Data extends TrackData {
tsStarts: BigInt64Array;
@@ -39,6 +36,8 @@
export default class implements PerfettoPlugin {
static readonly id = 'dev.perfetto.PerfSamplesProfile';
+ static readonly dependencies = [ProcessThreadGroupsPlugin];
+
async onTraceLoad(ctx: Trace): Promise<void> {
const pResult = await ctx.engine.query(`
select distinct upid
@@ -59,9 +58,11 @@
},
track: new ProcessPerfSamplesProfileTrack(ctx, uri, upid),
});
- const group = getOrCreateGroupForProcess(ctx.workspace, upid);
+ const group = ctx.plugins
+ .getPlugin(ProcessThreadGroupsPlugin)
+ .getGroupForProcess(upid);
const track = new TrackNode({uri, title, sortOrder: -40});
- group.addChildInOrder(track);
+ group?.addChildInOrder(track);
}
const tResult = await ctx.engine.query(`
select distinct
@@ -99,9 +100,11 @@
},
track: new ThreadPerfSamplesProfileTrack(ctx, uri, utid),
});
- const group = getOrCreateGroupForThread(ctx.workspace, utid);
+ const group = ctx.plugins
+ .getPlugin(ProcessThreadGroupsPlugin)
+ .getGroupForThread(utid);
const track = new TrackNode({uri, title, sortOrder: -50});
- group.addChildInOrder(track);
+ group?.addChildInOrder(track);
}
ctx.addEventListener('traceready', async () => {
diff --git a/ui/src/plugins/dev.perfetto.ProcessThreadGroups/index.ts b/ui/src/plugins/dev.perfetto.ProcessThreadGroups/index.ts
index 60aa11c..eb720c9 100644
--- a/ui/src/plugins/dev.perfetto.ProcessThreadGroups/index.ts
+++ b/ui/src/plugins/dev.perfetto.ProcessThreadGroups/index.ts
@@ -14,10 +14,6 @@
import {Trace} from '../../public/trace';
import {PerfettoPlugin} from '../../public/plugin';
-import {
- getOrCreateGroupForProcess,
- getOrCreateGroupForThread,
-} from '../../public/standard_groups';
import {TrackNode} from '../../public/workspace';
import {NUM, STR, STR_NULL} from '../../trace_processor/query_result';
@@ -41,24 +37,35 @@
// including the kernel groups, sorting, and adding summary tracks.
export default class implements PerfettoPlugin {
static readonly id = 'dev.perfetto.ProcessThreadGroups';
- async onTraceLoad(ctx: Trace): Promise<void> {
- const processGroups = new Map<number, TrackNode>();
- const threadGroups = new Map<number, TrackNode>();
+ private readonly processGroups = new Map<number, TrackNode>();
+ private readonly threadGroups = new Map<number, TrackNode>();
+
+ constructor(private readonly ctx: Trace) {}
+
+ getGroupForProcess(upid: number): TrackNode | undefined {
+ return this.processGroups.get(upid);
+ }
+
+ getGroupForThread(utid: number): TrackNode | undefined {
+ return this.threadGroups.get(utid);
+ }
+
+ async onTraceLoad(ctx: Trace): Promise<void> {
// Pre-group all kernel "threads" (actually processes) if this is a linux
// system trace. Below, addProcessTrackGroups will skip them due to an
// existing group uuid, and addThreadStateTracks will fill in the
// per-thread tracks. Quirk: since all threads will appear to be
// TrackKindPriority.MAIN_THREAD, any process-level tracks will end up
// pushed to the bottom of the group in the UI.
- await this.addKernelThreadGrouping(ctx, threadGroups);
+ await this.addKernelThreadGrouping();
// Create the per-process track groups. Note that this won't necessarily
// create a track per process. If a process has been completely idle and has
// no sched events, no track group will be emitted.
// Will populate this.addTrackGroupActions
- await this.addProcessGroups(ctx, processGroups, threadGroups);
- await this.addThreadGroups(ctx, processGroups, threadGroups);
+ await this.addProcessGroups();
+ await this.addThreadGroups();
ctx.addEventListener('traceready', () => {
// If, by the time the trace has finished loading, some of the process or
@@ -68,15 +75,12 @@
g.remove();
}
};
- processGroups.forEach(removeIfEmpty);
- threadGroups.forEach(removeIfEmpty);
+ this.processGroups.forEach(removeIfEmpty);
+ this.threadGroups.forEach(removeIfEmpty);
});
}
- private async addKernelThreadGrouping(
- ctx: Trace,
- threadGroups: Map<number, TrackNode>,
- ): Promise<void> {
+ private async addKernelThreadGrouping(): Promise<void> {
// Identify kernel threads if this is a linux system trace, and sufficient
// process information is available. Kernel threads are identified by being
// children of kthreadd (always pid 2).
@@ -86,7 +90,7 @@
// which has pid 0 but appears as a distinct process (with its own comm) on
// each cpu. It'd make sense to exclude its thread state track, but still
// put process-scoped tracks in this group.
- const result = await ctx.engine.query(`
+ const result = await this.ctx.engine.query(`
select
t.utid, p.upid, (case p.pid when 2 then 1 else 0 end) isKthreadd
from
@@ -123,28 +127,27 @@
sortOrder: 50,
isSummary: true,
});
- ctx.workspace.addChildInOrder(kernelThreadsGroup);
+ this.ctx.workspace.addChildInOrder(kernelThreadsGroup);
// Set the group for all kernel threads (including kthreadd itself).
for (; it.valid(); it.next()) {
const {utid} = it;
- const threadGroup = getOrCreateGroupForThread(ctx.workspace, utid);
- threadGroup.headless = true;
+ const threadGroup = new TrackNode({
+ uri: `thread${utid}`,
+ title: `Thread ${utid}`,
+ isSummary: true,
+ headless: true,
+ });
kernelThreadsGroup.addChildInOrder(threadGroup);
-
- threadGroups.set(utid, threadGroup);
+ this.threadGroups.set(utid, threadGroup);
}
}
// Adds top level groups for processes and thread that don't belong to a
// process.
- private async addProcessGroups(
- ctx: Trace,
- processGroups: Map<number, TrackNode>,
- threadGroups: Map<number, TrackNode>,
- ): Promise<void> {
- const result = await ctx.engine.query(`
+ private async addProcessGroups(): Promise<void> {
+ const result = await this.ctx.engine.query(`
with processGroups as (
select
upid,
@@ -231,7 +234,7 @@
if (kind === 'process') {
// Ignore kernel process groups
- if (processGroups.has(uid)) {
+ if (this.processGroups.has(uid)) {
continue;
}
@@ -247,41 +250,41 @@
}
const displayName = getProcessDisplayName(name ?? undefined, id);
- const group = getOrCreateGroupForProcess(ctx.workspace, uid);
- group.title = displayName;
- group.uri = `/process_${uid}`; // Summary track URI
- group.sortOrder = 50;
+ const group = new TrackNode({
+ uri: `/process_${uid}`,
+ title: displayName,
+ isSummary: true,
+ sortOrder: 50,
+ });
// Re-insert the child node to sort it
- ctx.workspace.addChildInOrder(group);
- processGroups.set(uid, group);
+ this.ctx.workspace.addChildInOrder(group);
+ this.processGroups.set(uid, group);
} else {
// Ignore kernel process groups
- if (threadGroups.has(uid)) {
+ if (this.threadGroups.has(uid)) {
continue;
}
const displayName = getThreadDisplayName(name ?? undefined, id);
- const group = getOrCreateGroupForThread(ctx.workspace, uid);
- group.title = displayName;
- group.uri = `/thread_${uid}`; // Summary track URI
- group.sortOrder = 50;
+ const group = new TrackNode({
+ uri: `/thread_${uid}`,
+ title: displayName,
+ isSummary: true,
+ sortOrder: 50,
+ });
// Re-insert the child node to sort it
- ctx.workspace.addChildInOrder(group);
- threadGroups.set(uid, group);
+ this.ctx.workspace.addChildInOrder(group);
+ this.threadGroups.set(uid, group);
}
}
}
// Create all the nested & headless thread groups that live inside existing
// process groups.
- private async addThreadGroups(
- ctx: Trace,
- processGroups: Map<number, TrackNode>,
- threadGroups: Map<number, TrackNode>,
- ): Promise<void> {
- const result = await ctx.engine.query(`
+ private async addThreadGroups(): Promise<void> {
+ const result = await this.ctx.engine.query(`
with threadGroups as (
select
utid,
@@ -329,15 +332,18 @@
const {utid, tid, upid, threadName} = it;
// Ignore kernel thread groups
- if (threadGroups.has(utid)) {
+ if (this.threadGroups.has(utid)) {
continue;
}
- const group = getOrCreateGroupForThread(ctx.workspace, utid);
- group.title = getThreadDisplayName(threadName ?? undefined, tid);
- threadGroups.set(utid, group);
- group.headless = true;
- processGroups.get(upid)?.addChildInOrder(group);
+ const group = new TrackNode({
+ uri: `/thread_${utid}`,
+ title: getThreadDisplayName(threadName ?? undefined, tid),
+ isSummary: true,
+ headless: true,
+ });
+ this.threadGroups.set(utid, group);
+ this.processGroups.get(upid)?.addChildInOrder(group);
}
}
}
diff --git a/ui/src/plugins/dev.perfetto.ThreadState/index.ts b/ui/src/plugins/dev.perfetto.ThreadState/index.ts
index 155b35f..d7c2363 100644
--- a/ui/src/plugins/dev.perfetto.ThreadState/index.ts
+++ b/ui/src/plugins/dev.perfetto.ThreadState/index.ts
@@ -22,9 +22,9 @@
import {getThreadStateTable} from './table';
import {sqlTableRegistry} from '../../frontend/widgets/sql/table/sql_table_registry';
import {TrackNode} from '../../public/workspace';
-import {getOrCreateGroupForThread} from '../../public/standard_groups';
import {ThreadStateSelectionAggregator} from './thread_state_selection_aggregator';
import {extensions} from '../../public/lib/extensions';
+import ProcessThreadGroupsPlugin from '../dev.perfetto.ProcessThreadGroups';
function uriForThreadStateTrack(upid: number | null, utid: number): string {
return `${getThreadUriPrefix(upid, utid)}_state`;
@@ -32,6 +32,8 @@
export default class implements PerfettoPlugin {
static readonly id = 'dev.perfetto.ThreadState';
+ static readonly dependencies = [ProcessThreadGroupsPlugin];
+
async onTraceLoad(ctx: Trace): Promise<void> {
const {engine} = ctx;
@@ -87,9 +89,11 @@
track: new ThreadStateTrack(ctx, uri, utid),
});
- const group = getOrCreateGroupForThread(ctx.workspace, utid);
+ const group = ctx.plugins
+ .getPlugin(ProcessThreadGroupsPlugin)
+ .getGroupForThread(utid);
const track = new TrackNode({uri, title, sortOrder: 10});
- group.addChildInOrder(track);
+ group?.addChildInOrder(track);
}
sqlTableRegistry['thread_state'] = getThreadStateTable();
diff --git a/ui/src/public/standard_groups.ts b/ui/src/public/standard_groups.ts
index 61b844a..2bc7570 100644
--- a/ui/src/public/standard_groups.ts
+++ b/ui/src/public/standard_groups.ts
@@ -15,40 +15,6 @@
import {TrackNode, TrackNodeArgs, Workspace} from './workspace';
/**
- * Gets or creates a group for a given process given the normal grouping
- * conventions.
- *
- * @param workspace - The workspace to search for the group on.
- * @param upid - The upid of teh process to find.
- */
-export function getOrCreateGroupForProcess(
- workspace: Workspace,
- upid: number,
-): TrackNode {
- return getOrCreateGroup(workspace, `process${upid}`, {
- title: `Process ${upid}`,
- isSummary: true,
- });
-}
-
-/**
- * Gets or creates a group for a given thread given the normal grouping
- * conventions.
- *
- * @param workspace - The workspace to search for the group on.
- * @param utid - The utid of the thread to find.
- */
-export function getOrCreateGroupForThread(
- workspace: Workspace,
- utid: number,
-): TrackNode {
- return getOrCreateGroup(workspace, `thread${utid}`, {
- title: `Thread ${utid}`,
- isSummary: true,
- });
-}
-
-/**
* Gets or creates a group for user interaction
*
* @param workspace - The workspace on which to create the group.
diff --git a/ui/src/public/workspace.ts b/ui/src/public/workspace.ts
index a2bab1f..c6766b9 100644
--- a/ui/src/public/workspace.ts
+++ b/ui/src/public/workspace.ts
@@ -472,7 +472,7 @@
onchange: (w: Workspace) => void = () => {};
// Dummy node to contain the pinned tracks
- private pinnedRoot = new TrackNode();
+ public readonly pinnedRoot = new TrackNode();
get pinnedTracks(): ReadonlyArray<TrackNode> {
return this.pinnedRoot.children;