[ui] Port cpu_freq tracks.
CpuFreqTrackController does some async stuff in it's onSetup function.
Previously this function was being called fire-n-forget, which meant
that onBoundsChange could be called before onSetup was complete. Thus,
this CL adds a more strict lifecycle to tracks where onCreate() is
guaranteed to complete before any of the other methods are called.
Change-Id: I5998fea769741c3218b28817f60cbfb279c3381c
diff --git a/ui/src/common/basic_async_track.ts b/ui/src/common/basic_async_track.ts
index 24e3f28..4e65e49 100644
--- a/ui/src/common/basic_async_track.ts
+++ b/ui/src/common/basic_async_track.ts
@@ -46,9 +46,9 @@
private currentState?: TrackData;
protected data?: Data;
- onCreate(): void {}
+ async onCreate(): Promise<void> {}
- onDestroy(): void {
+ async onDestroy(): Promise<void> {
this.queuedRequest = false;
this.currentState = undefined;
this.data = undefined;
diff --git a/ui/src/common/track_adapter.ts b/ui/src/common/track_adapter.ts
index 0538e23..87d9a56 100644
--- a/ui/src/common/track_adapter.ts
+++ b/ui/src/common/track_adapter.ts
@@ -57,15 +57,15 @@
this.controller = new Controller(config, engine);
}
- onCreate(): void {
- this.controller.onSetup();
- super.onCreate();
+ async onCreate(): Promise<void> {
+ await this.controller.onSetup();
+ await super.onCreate();
}
- onDestroy(): void {
- this.track.onDestroy();
- this.controller.onDestroy();
- super.onDestroy();
+ async onDestroy(): Promise<void> {
+ await this.track.onDestroy();
+ await this.controller.onDestroy();
+ await super.onDestroy();
}
getSliceRect(
@@ -205,8 +205,8 @@
abstract onBoundsChange(start: time, end: time, resolution: duration):
Promise<Data>;
- onSetup(): void {}
- onDestroy(): void {}
+ async onSetup(): Promise<void> {}
+ async onDestroy(): Promise<void> {}
// Returns a valid SQL table name with the given prefix that should be unique
// for each track.
diff --git a/ui/src/controller/track_decider.ts b/ui/src/controller/track_decider.ts
index c29e7c5..7d4732e 100644
--- a/ui/src/controller/track_decider.ts
+++ b/ui/src/controller/track_decider.ts
@@ -50,7 +50,6 @@
} from '../tracks/chrome_scroll_jank/chrome_tasks_scroll_jank_track';
import {SLICE_TRACK_KIND} from '../tracks/chrome_slices';
import {COUNTER_TRACK_KIND} from '../tracks/counter';
-import {CPU_FREQ_TRACK_KIND} from '../tracks/cpu_freq';
import {CPU_PROFILE_TRACK_KIND} from '../tracks/cpu_profile';
import {
EXPECTED_FRAMES_SLICE_TRACK_KIND,
@@ -202,54 +201,35 @@
async addCpuFreqTracks(engine: EngineProxy): Promise<void> {
const cpus = await this.engine.getCpus();
- const maxCpuFreqResult = await engine.query(`
- select ifnull(max(value), 0) as freq
- from counter c
- inner join cpu_counter_track t on c.track_id = t.id
- where name = 'cpufreq';
- `);
- const maxCpuFreq = maxCpuFreqResult.firstRow({freq: NUM}).freq;
-
for (const cpu of cpus) {
// Only add a cpu freq track if we have
// cpu freq data.
// TODO(hjd): Find a way to display cpu idle
// events even if there are no cpu freq events.
const cpuFreqIdleResult = await engine.query(`
- select
- id as cpuFreqId,
- (
- select id
- from cpu_counter_track
- where name = 'cpuidle'
- and cpu = ${cpu}
- limit 1
- ) as cpuIdleId
- from cpu_counter_track
- where name = 'cpufreq' and cpu = ${cpu}
- limit 1;
- `);
+ select
+ id as cpuFreqId,
+ (
+ select id
+ from cpu_counter_track
+ where name = 'cpuidle'
+ and cpu = ${cpu}
+ limit 1
+ ) as cpuIdleId
+ from cpu_counter_track
+ where name = 'cpufreq' and cpu = ${cpu}
+ limit 1;
+ `);
if (cpuFreqIdleResult.numRows() > 0) {
- const row = cpuFreqIdleResult.firstRow({
- cpuFreqId: NUM,
- cpuIdleId: NUM_NULL,
- });
- const freqTrackId = row.cpuFreqId;
- const idleTrackId = row.cpuIdleId === null ? undefined : row.cpuIdleId;
-
this.tracksToAdd.push({
engineId: this.engineId,
- kind: CPU_FREQ_TRACK_KIND,
+ kind: PLUGIN_TRACK_KIND,
trackSortKey: PrimaryTrackSortKey.ORDINARY_TRACK,
name: `Cpu ${cpu} Frequency`,
trackGroup: SCROLLING_TRACK_GROUP,
- config: {
- cpu,
- maximumValue: maxCpuFreq,
- freqTrackId,
- idleTrackId,
- },
+ config: {},
+ uri: `perfetto.CpuFreq#${cpu}`,
});
}
}
diff --git a/ui/src/frontend/base_slice_track.ts b/ui/src/frontend/base_slice_track.ts
index f16057b..130987b 100644
--- a/ui/src/frontend/base_slice_track.ts
+++ b/ui/src/frontend/base_slice_track.ts
@@ -505,10 +505,10 @@
} // if (hoveredSlice)
}
- onDestroy() {
- super.onDestroy();
+ async onDestroy() {
+ await super.onDestroy();
this.isDestroyed = true;
- this.engine.query(`DROP VIEW IF EXISTS ${this.tableName}`);
+ await this.engine.query(`DROP VIEW IF EXISTS ${this.tableName}`);
}
// This method figures out if the visible window is outside the bounds of
diff --git a/ui/src/frontend/track.ts b/ui/src/frontend/track.ts
index 31e9c79..46d16c6 100644
--- a/ui/src/frontend/track.ts
+++ b/ui/src/frontend/track.ts
@@ -75,11 +75,11 @@
this.lastTrackState = assertExists(globals.state.tracks[this.trackId]);
}
- onCreate() {}
+ async onCreate(): Promise<void> {}
// Last call the track will receive. Called just before the last reference to
// this object is removed.
- onDestroy() {}
+ async onDestroy(): Promise<void> {}
protected abstract renderCanvas(ctx: CanvasRenderingContext2D): void;
diff --git a/ui/src/frontend/track_group_panel.ts b/ui/src/frontend/track_group_panel.ts
index 4459c98..a799112 100644
--- a/ui/src/frontend/track_group_panel.ts
+++ b/ui/src/frontend/track_group_panel.ts
@@ -30,7 +30,11 @@
import {globals} from './globals';
import {drawGridLines} from './gridline_helper';
import {Panel, PanelSize} from './panel';
-import {renderChips, TrackContent} from './track_panel';
+import {
+ renderChips,
+ TrackContent,
+ TrackLifecycleContainer,
+} from './track_panel';
import {trackRegistry} from './track_registry';
import {
drawVerticalLineAtTime,
@@ -45,7 +49,7 @@
private readonly trackGroupId: string;
private shellWidth = 0;
private backgroundColor = '#ffffff'; // Updated from CSS later.
- private summaryTrack: TrackLike|undefined;
+ private summaryTrack?: TrackLifecycleContainer;
constructor(vnode: m.CVnode<Attrs>) {
super();
@@ -70,10 +74,12 @@
},
};
- this.summaryTrack =
+ const track =
uri ? pluginManager.createTrack(uri, ctx) : loadTrack(trackState, id);
- this.summaryTrack?.onCreate();
+ if (track) {
+ this.summaryTrack = new TrackLifecycleContainer(track);
+ }
}
get trackGroupState(): TrackGroupState {
@@ -200,7 +206,7 @@
onremove() {
if (this.summaryTrack !== undefined) {
- this.summaryTrack.onDestroy();
+ this.summaryTrack.dispose();
this.summaryTrack = undefined;
}
}
diff --git a/ui/src/frontend/track_panel.ts b/ui/src/frontend/track_panel.ts
index 2c2d5e7..64cfd63 100644
--- a/ui/src/frontend/track_panel.ts
+++ b/ui/src/frontend/track_panel.ts
@@ -15,6 +15,7 @@
import {hex} from 'color-convert';
import m from 'mithril';
+import {Disposable} from '../base/disposable';
import {currentTargetOffset} from '../base/dom_utils';
import {Icons} from '../base/semantic_icons';
import {duration, Span, time} from '../base/time';
@@ -101,7 +102,7 @@
}
interface TrackShellAttrs {
- track: TrackLike;
+ track: TrackLifecycleContainer;
trackState: TrackState;
}
@@ -233,7 +234,7 @@
}
export interface TrackContentAttrs {
- track: TrackLike;
+ track: TrackLifecycleContainer;
}
export class TrackContent implements m.ClassComponent<TrackContentAttrs> {
private mouseDownX?: number;
@@ -292,7 +293,7 @@
interface TrackComponentAttrs {
trackState: TrackState;
- track: TrackLike;
+ track: TrackLifecycleContainer;
}
class TrackComponent implements m.ClassComponent<TrackComponentAttrs> {
view({attrs}: m.CVnode<TrackComponentAttrs>) {
@@ -352,12 +353,121 @@
selectable: boolean;
}
+enum TrackLifecycleState {
+ Initializing,
+ Initialized,
+ DestroyPending,
+ Destroying,
+ Destroyed,
+}
+
+export class TrackLifecycleContainer implements Disposable {
+ private state = TrackLifecycleState.Initializing;
+
+ constructor(private track: TrackLike) {
+ track.onCreate().then(() => {
+ if (this.state === TrackLifecycleState.DestroyPending) {
+ track.onDestroy();
+ this.state = TrackLifecycleState.Destroying;
+ } else {
+ this.state = TrackLifecycleState.Initialized;
+ }
+ });
+ }
+
+ onFullRedraw(): void {
+ if (this.state === TrackLifecycleState.Initialized) {
+ this.track.onFullRedraw();
+ }
+ }
+
+ getSliceRect(
+ visibleTimeScale: TimeScale, visibleWindow: Span<time, bigint>,
+ windowSpan: PxSpan, tStart: time, tEnd: time, depth: number): SliceRect
+ |undefined {
+ if (this.state === TrackLifecycleState.Initialized) {
+ return this.track.getSliceRect(
+ visibleTimeScale, visibleWindow, windowSpan, tStart, tEnd, depth);
+ } else {
+ return undefined;
+ }
+ }
+
+ getHeight(): number {
+ if (this.state === TrackLifecycleState.Initialized) {
+ return this.track.getHeight();
+ } else {
+ return 18;
+ }
+ }
+
+ getTrackShellButtons(): m.Vnode<TrackButtonAttrs, {}>[] {
+ if (this.state === TrackLifecycleState.Initialized) {
+ return this.track.getTrackShellButtons();
+ } else {
+ return [];
+ }
+ }
+
+ getContextMenu(): m.Vnode<any, {}>|null {
+ if (this.state === TrackLifecycleState.Initialized) {
+ return this.track.getContextMenu();
+ } else {
+ return null;
+ }
+ }
+
+ onMouseMove(position: {x: number; y: number;}): void {
+ if (this.state === TrackLifecycleState.Initialized) {
+ this.track.onMouseMove(position);
+ }
+ }
+
+ onMouseClick(position: {x: number; y: number;}): boolean {
+ if (this.state === TrackLifecycleState.Initialized) {
+ return this.track.onMouseClick(position);
+ } else {
+ return false;
+ }
+ }
+
+ onMouseOut(): void {
+ if (this.state === TrackLifecycleState.Initialized) {
+ this.track.onMouseOut();
+ }
+ }
+
+ render(ctx: CanvasRenderingContext2D) {
+ if (this.state === TrackLifecycleState.Initialized) {
+ this.track.render(ctx);
+ }
+ }
+
+ dispose() {
+ switch (this.state) {
+ case TrackLifecycleState.Initializing:
+ this.state = TrackLifecycleState.DestroyPending;
+ break;
+ case TrackLifecycleState.Initialized:
+ this.state = TrackLifecycleState.Destroying;
+ this.track.onDestroy().then(() => {
+ this.state = TrackLifecycleState.Destroyed;
+ });
+ break;
+ case TrackLifecycleState.DestroyPending:
+ case TrackLifecycleState.Destroying:
+ case TrackLifecycleState.Destroyed:
+ break;
+ default:
+ const x: never = this.state;
+ throw new Error(`Invalid state "${x}"`);
+ }
+ }
+}
+
export class TrackPanel extends Panel<TrackPanelAttrs> {
- // TODO(hjd): It would be nicer if these could not be undefined here.
- // We should implement a NullTrack which can be used if the trackState
- // has disappeared.
- private track: TrackLike|undefined;
- private trackState: TrackState|undefined;
+ private track?: TrackLifecycleContainer;
+ private trackState?: TrackState;
private tryLoadTrack(vnode: m.CVnode<TrackPanelAttrs>) {
const trackId = vnode.attrs.id;
@@ -379,10 +489,13 @@
},
};
- this.track = uri ? pluginManager.createTrack(uri, trackCtx) :
- loadTrack(trackState, id);
- this.track?.onCreate();
- this.trackState = trackState;
+ const track = uri ? pluginManager.createTrack(uri, trackCtx) :
+ loadTrack(trackState, id);
+
+ if (track) {
+ this.track = new TrackLifecycleContainer(track);
+ this.trackState = trackState;
+ }
}
view(vnode: m.CVnode<TrackPanelAttrs>) {
@@ -410,7 +523,7 @@
onremove() {
if (this.track !== undefined) {
- this.track.onDestroy();
+ this.track.dispose();
this.track = undefined;
}
}
diff --git a/ui/src/public/index.ts b/ui/src/public/index.ts
index 8c90493..1ae68b0 100644
--- a/ui/src/public/index.ts
+++ b/ui/src/public/index.ts
@@ -190,7 +190,7 @@
// TODO(stevegolton): Rename `Track` to `BaseTrack` (or similar) and rename this
// interface to `Track`.
export interface TrackLike {
- onCreate(): void;
+ onCreate(): Promise<void>;
render(ctx: CanvasRenderingContext2D): void;
onFullRedraw(): void;
getSliceRect(
@@ -203,7 +203,7 @@
onMouseMove(position: {x: number, y: number}): void;
onMouseClick(position: {x: number, y: number}): boolean;
onMouseOut(): void;
- onDestroy(): void;
+ onDestroy(): Promise<void>;
}
export interface PluginTrackInfo {
diff --git a/ui/src/tracks/chrome_scroll_jank/event_latency_track.ts b/ui/src/tracks/chrome_scroll_jank/event_latency_track.ts
index 8632bf4..d4b24aa 100644
--- a/ui/src/tracks/chrome_scroll_jank/event_latency_track.ts
+++ b/ui/src/tracks/chrome_scroll_jank/event_latency_track.ts
@@ -65,8 +65,8 @@
});
}
- onDestroy() {
- super.onDestroy();
+ async onDestroy() {
+ await super.onDestroy();
ScrollJankPluginState.getInstance().unregisterTrack(EventLatencyTrack.kind);
}
diff --git a/ui/src/tracks/chrome_scroll_jank/scroll_jank_v3_track.ts b/ui/src/tracks/chrome_scroll_jank/scroll_jank_v3_track.ts
index b4d47ff..87a6a3e 100644
--- a/ui/src/tracks/chrome_scroll_jank/scroll_jank_v3_track.ts
+++ b/ui/src/tracks/chrome_scroll_jank/scroll_jank_v3_track.ts
@@ -89,8 +89,8 @@
};
}
- onDestroy() {
- super.onDestroy();
+ async onDestroy() {
+ await super.onDestroy();
ScrollJankPluginState.getInstance().unregisterTrack(ScrollJankV3Track.kind);
}
diff --git a/ui/src/tracks/chrome_scroll_jank/scroll_track.ts b/ui/src/tracks/chrome_scroll_jank/scroll_track.ts
index 56a672b..e2d050f 100644
--- a/ui/src/tracks/chrome_scroll_jank/scroll_track.ts
+++ b/ui/src/tracks/chrome_scroll_jank/scroll_track.ts
@@ -69,8 +69,8 @@
});
}
- onDestroy() {
- super.onDestroy();
+ async onDestroy() {
+ await super.onDestroy();
ScrollJankPluginState.getInstance().unregisterTrack(
TopLevelScrollTrack.kind);
}
diff --git a/ui/src/tracks/counter/index.ts b/ui/src/tracks/counter/index.ts
index 478266d..00a89ca 100644
--- a/ui/src/tracks/counter/index.ts
+++ b/ui/src/tracks/counter/index.ts
@@ -126,7 +126,6 @@
}
export class CounterTrack extends BasicAsyncTrack<Data> {
- private setup = false;
private maximumValueSeen = 0;
private minimumValueSeen = 0;
private maximumDeltaSeen = 0;
@@ -166,61 +165,59 @@
}
}
- async onBoundsChange(start: time, end: time, resolution: duration):
- Promise<Data> {
- if (!this.setup) {
- if (this.config.namespace === undefined) {
- await this.engine.query(`
- create view ${this.tableName('counter_view')} as
- select
- id,
- ts,
- dur,
- value,
- delta
- from experimental_counter_dur
- where track_id = ${this.config.trackId};
- `);
- } else {
- await this.engine.query(`
- create view ${this.tableName('counter_view')} as
- select
- id,
- ts,
- lead(ts, 1, ts) over (order by ts) - ts as dur,
- lead(value, 1, value) over (order by ts) - value as delta,
- value
- from ${this.namespaceTable('counter')}
- where track_id = ${this.config.trackId};
- `);
- }
-
- const maxDurResult = await this.engine.query(`
- select
- max(
- iif(dur != -1, dur, (select end_ts from trace_bounds) - ts)
- ) as maxDur
- from ${this.tableName('counter_view')}
- `);
- this.maxDurNs = maxDurResult.firstRow({maxDur: LONG_NULL}).maxDur || 0n;
-
- const queryRes = await this.engine.query(`
+ async onCreate() {
+ if (this.config.namespace === undefined) {
+ await this.engine.query(`
+ create view ${this.tableName('counter_view')} as
select
- ifnull(max(value), 0) as maxValue,
- ifnull(min(value), 0) as minValue,
- ifnull(max(delta), 0) as maxDelta,
- ifnull(min(delta), 0) as minDelta
- from ${this.tableName('counter_view')}`);
- const row = queryRes.firstRow(
- {maxValue: NUM, minValue: NUM, maxDelta: NUM, minDelta: NUM});
- this.maximumValueSeen = row.maxValue;
- this.minimumValueSeen = row.minValue;
- this.maximumDeltaSeen = row.maxDelta;
- this.minimumDeltaSeen = row.minDelta;
-
- this.setup = true;
+ id,
+ ts,
+ dur,
+ value,
+ delta
+ from experimental_counter_dur
+ where track_id = ${this.config.trackId};
+ `);
+ } else {
+ await this.engine.query(`
+ create view ${this.tableName('counter_view')} as
+ select
+ id,
+ ts,
+ lead(ts, 1, ts) over (order by ts) - ts as dur,
+ lead(value, 1, value) over (order by ts) - value as delta,
+ value
+ from ${this.namespaceTable('counter')}
+ where track_id = ${this.config.trackId};
+ `);
}
+ const maxDurResult = await this.engine.query(`
+ select
+ max(
+ iif(dur != -1, dur, (select end_ts from trace_bounds) - ts)
+ ) as maxDur
+ from ${this.tableName('counter_view')}
+ `);
+ this.maxDurNs = maxDurResult.firstRow({maxDur: LONG_NULL}).maxDur || 0n;
+
+ const queryRes = await this.engine.query(`
+ select
+ ifnull(max(value), 0) as maxValue,
+ ifnull(min(value), 0) as minValue,
+ ifnull(max(delta), 0) as maxDelta,
+ ifnull(min(delta), 0) as minDelta
+ from ${this.tableName('counter_view')}`);
+ const row = queryRes.firstRow(
+ {maxValue: NUM, minValue: NUM, maxDelta: NUM, minDelta: NUM});
+ this.maximumValueSeen = row.maxValue;
+ this.minimumValueSeen = row.minValue;
+ this.maximumDeltaSeen = row.maxDelta;
+ this.minimumDeltaSeen = row.minDelta;
+ }
+
+ async onBoundsChange(start: time, end: time, resolution: duration):
+ Promise<Data> {
const queryRes = await this.engine.query(`
select
(ts + ${resolution / 2n}) / ${resolution} * ${resolution} as tsq,
@@ -601,8 +598,9 @@
}
}
- onDestroy(): void {
- this.engine.query(`DROP VIEW IF EXISTS ${this.tableName('counter_view')}`);
+ async onDestroy(): Promise<void> {
+ await this.engine.query(
+ `DROP VIEW IF EXISTS ${this.tableName('counter_view')}`);
}
}
diff --git a/ui/src/tracks/cpu_freq/index.ts b/ui/src/tracks/cpu_freq/index.ts
index baa24f9..5b182be 100644
--- a/ui/src/tracks/cpu_freq/index.ts
+++ b/ui/src/tracks/cpu_freq/index.ts
@@ -26,12 +26,21 @@
NUM_NULL,
QueryResult,
} from '../../common/query_result';
+import {
+ TrackAdapter,
+ TrackControllerAdapter,
+ TrackWithControllerAdapter,
+} from '../../common/track_adapter';
import {TrackData} from '../../common/track_data';
-import {TrackController} from '../../controller/track_controller';
import {checkerboardExcept} from '../../frontend/checkerboard';
import {globals} from '../../frontend/globals';
-import {NewTrackArgs, Track} from '../../frontend/track';
-import {Plugin, PluginContext, PluginInfo} from '../../public';
+import {NewTrackArgs} from '../../frontend/track';
+import {
+ Plugin,
+ PluginContext,
+ PluginInfo,
+ TracePluginContext,
+} from '../../public';
export const CPU_FREQ_TRACK_KIND = 'CpuFreqTrack';
@@ -55,9 +64,7 @@
minimumValue?: number;
}
-class CpuFreqTrackController extends TrackController<Config, Data> {
- static readonly kind = CPU_FREQ_TRACK_KIND;
-
+class CpuFreqTrackController extends TrackControllerAdapter<Config, Data> {
private maxDur: duration = 0n;
private maxTsEnd: time = Time.ZERO;
private maximumValueSeen = 0;
@@ -266,8 +273,7 @@
const MARGIN_TOP = 4.5;
const RECT_HEIGHT = 20;
-class CpuFreqTrack extends Track<Config, Data> {
- static readonly kind = CPU_FREQ_TRACK_KIND;
+class CpuFreqTrack extends TrackAdapter<Config, Data> {
static create(args: NewTrackArgs): CpuFreqTrack {
return new CpuFreqTrack(args);
}
@@ -484,9 +490,70 @@
}
class CpuFreq implements Plugin {
- onActivate(ctx: PluginContext): void {
- ctx.registerTrackController(CpuFreqTrackController);
- ctx.registerTrack(CpuFreqTrack);
+ onActivate(_ctx: PluginContext): void {}
+
+ async onTraceLoad(ctx: TracePluginContext): Promise<void> {
+ const {engine} = ctx;
+
+ const cpus = await engine.getCpus();
+
+ const maxCpuFreqResult = await engine.query(`
+ select ifnull(max(value), 0) as freq
+ from counter c
+ inner join cpu_counter_track t on c.track_id = t.id
+ where name = 'cpufreq';
+ `);
+ const maxCpuFreq = maxCpuFreqResult.firstRow({freq: NUM}).freq;
+
+ for (const cpu of cpus) {
+ // Only add a cpu freq track if we have
+ // cpu freq data.
+ // TODO(hjd): Find a way to display cpu idle
+ // events even if there are no cpu freq events.
+ const cpuFreqIdleResult = await engine.query(`
+ select
+ id as cpuFreqId,
+ (
+ select id
+ from cpu_counter_track
+ where name = 'cpuidle'
+ and cpu = ${cpu}
+ limit 1
+ ) as cpuIdleId
+ from cpu_counter_track
+ where name = 'cpufreq' and cpu = ${cpu}
+ limit 1;
+ `);
+
+ if (cpuFreqIdleResult.numRows() > 0) {
+ const row = cpuFreqIdleResult.firstRow({
+ cpuFreqId: NUM,
+ cpuIdleId: NUM_NULL,
+ });
+ const freqTrackId = row.cpuFreqId;
+ const idleTrackId = row.cpuIdleId === null ? undefined : row.cpuIdleId;
+
+ ctx.addTrack({
+ uri: `perfetto.CpuFreq#${cpu}`,
+ displayName: `Cpu ${cpu} Frequency`,
+ kind: CPU_FREQ_TRACK_KIND,
+ cpu,
+ trackFactory: ({trackInstanceId}) => {
+ return new TrackWithControllerAdapter<Config, Data>(
+ engine,
+ trackInstanceId,
+ {
+ cpu,
+ maximumValue: maxCpuFreq,
+ freqTrackId,
+ idleTrackId,
+ },
+ CpuFreqTrack,
+ CpuFreqTrackController);
+ },
+ });
+ }
+ }
}
}
diff --git a/ui/src/tracks/process_summary/process_summary_track.ts b/ui/src/tracks/process_summary/process_summary_track.ts
index eb62667..7085cb8 100644
--- a/ui/src/tracks/process_summary/process_summary_track.ts
+++ b/ui/src/tracks/process_summary/process_summary_track.ts
@@ -41,47 +41,44 @@
// and no cpu scheduling.
export class ProcessSummaryTrackController extends
TrackControllerAdapter<Config, Data> {
- private setup = false;
+ async onSetup(): Promise<void> {
+ await this.query(
+ `create virtual table ${this.tableName('window')} using window;`);
+
+ let utids = [this.config.utid];
+ if (this.config.upid) {
+ const threadQuery = await this.query(
+ `select utid from thread where upid=${this.config.upid}`);
+ utids = [];
+ for (const it = threadQuery.iter({utid: NUM}); it.valid(); it.next()) {
+ utids.push(it.utid);
+ }
+ }
+
+ const trackQuery = await this.query(
+ `select id from thread_track where utid in (${utids.join(',')})`);
+ const tracks = [];
+ for (const it = trackQuery.iter({id: NUM}); it.valid(); it.next()) {
+ tracks.push(it.id);
+ }
+
+ const processSliceView = this.tableName('process_slice_view');
+ await this.query(
+ `create view ${processSliceView} as ` +
+ // 0 as cpu is a dummy column to perform span join on.
+ `select ts, dur/${utids.length} as dur ` +
+ `from slice s ` +
+ `where depth = 0 and track_id in ` +
+ `(${tracks.join(',')})`);
+ await this.query(`create virtual table ${this.tableName('span')}
+ using span_join(${processSliceView},
+ ${this.tableName('window')});`);
+ }
async onBoundsChange(start: time, end: time, resolution: duration):
Promise<Data> {
assertFalse(resolution === 0n, 'Resolution cannot be 0');
- if (this.setup === false) {
- await this.query(
- `create virtual table ${this.tableName('window')} using window;`);
-
- let utids = [this.config.utid];
- if (this.config.upid) {
- const threadQuery = await this.query(
- `select utid from thread where upid=${this.config.upid}`);
- utids = [];
- for (const it = threadQuery.iter({utid: NUM}); it.valid(); it.next()) {
- utids.push(it.utid);
- }
- }
-
- const trackQuery = await this.query(
- `select id from thread_track where utid in (${utids.join(',')})`);
- const tracks = [];
- for (const it = trackQuery.iter({id: NUM}); it.valid(); it.next()) {
- tracks.push(it.id);
- }
-
- const processSliceView = this.tableName('process_slice_view');
- await this.query(
- `create view ${processSliceView} as ` +
- // 0 as cpu is a dummy column to perform span join on.
- `select ts, dur/${utids.length} as dur ` +
- `from slice s ` +
- `where depth = 0 and track_id in ` +
- `(${tracks.join(',')})`);
- await this.query(`create virtual table ${this.tableName('span')}
- using span_join(${processSliceView},
- ${this.tableName('window')});`);
- this.setup = true;
- }
-
// |resolution| is in ns/px we want # ns for 10px window:
// Max value with 1 so we don't end up with resolution 0.
const bucketSize = resolution * 10n;
@@ -132,12 +129,9 @@
return summary;
}
- onDestroy(): void {
- if (this.setup) {
- this.query(`drop table ${this.tableName('window')}`);
- this.query(`drop table ${this.tableName('span')}`);
- this.setup = false;
- }
+ async onDestroy(): Promise<void> {
+ await this.query(`drop table ${this.tableName('window')}; drop table ${
+ this.tableName('span')}`);
}
}