aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSteve Golton <stevegolton@google.com>2023-12-14 11:36:50 +0000
committerAutomerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>2023-12-14 11:36:50 +0000
commit4d288c677e29a67b24d726b15fee85b5edc4e5d4 (patch)
treeab7472d9ee4544db690f328e0d4c50f8daeb423a
parent54cd946c6a377ff79a6a5ad12df903f9e2a2f1b3 (diff)
parent30871616815dd3cbaf6c5ad8db41c6f5ae01c833 (diff)
downloadperfetto-4d288c677e29a67b24d726b15fee85b5edc4e5d4.tar.gz
Merge "[ui] Created TrackCache object to manage track lifecycles." into main am: b907e13f7d am: afd0d26a3c am: 3087161681
Original change: https://android-review.googlesource.com/c/platform/external/perfetto/+/2870378 Change-Id: I35087d13c8c34489b78e01aaa98e7f0c825d89ac Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
-rw-r--r--ui/src/common/plugins.ts9
-rw-r--r--ui/src/common/track_cache.ts110
-rw-r--r--ui/src/frontend/globals.ts5
-rw-r--r--ui/src/frontend/track_group_panel.ts43
-rw-r--r--ui/src/frontend/track_panel.ts41
-rw-r--r--ui/src/frontend/viewer_page.ts6
6 files changed, 134 insertions, 80 deletions
diff --git a/ui/src/common/plugins.ts b/ui/src/common/plugins.ts
index dd4adf0cf..89187112d 100644
--- a/ui/src/common/plugins.ts
+++ b/ui/src/common/plugins.ts
@@ -30,8 +30,6 @@ import {
PluginDescriptor,
PrimaryTrackSortKey,
Store,
- Track,
- TrackContext,
TrackDescriptor,
TrackPredicate,
TrackRef,
@@ -427,13 +425,6 @@ export class PluginManager {
return this.trackRegistry.get(uri);
}
- // Create a new plugin track object from its URI.
- // Returns undefined if no such track is registered.
- createTrack(uri: string, trackCtx: TrackContext): Track|undefined {
- const trackInfo = pluginManager.trackRegistry.get(uri);
- return trackInfo && trackInfo.track(trackCtx);
- }
-
private doPluginTraceLoad(
pluginDetails: PluginDetails, engine: Engine, pluginId: string): void {
const {plugin, context} = pluginDetails;
diff --git a/ui/src/common/track_cache.ts b/ui/src/common/track_cache.ts
new file mode 100644
index 000000000..e6397c1cd
--- /dev/null
+++ b/ui/src/common/track_cache.ts
@@ -0,0 +1,110 @@
+// Copyright (C) 2023 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 {globals} from '../frontend/globals';
+import {Migrate, Track, TrackContext, TrackDescriptor} from '../public';
+
+import {pluginManager} from './plugins';
+
+export interface TrackCacheEntry {
+ track: Track;
+ desc: TrackDescriptor;
+}
+
+// This class is responsible for managing the lifecycle of tracks over render
+// cycles.
+
+// Example usage:
+// function render() {
+// const trackCache = new TrackCache();
+// const foo = trackCache.resolveTrack('foo', 'exampleURI', {});
+// const bar = trackCache.resolveTrack('bar', 'exampleURI', {});
+// trackCache.flushOldTracks(); // <-- Destroys any unused cached tracks
+// }
+
+// Example of how flushing works:
+// First cycle
+// resolveTrack('foo', ...) <-- new track 'foo' created
+// resolveTrack('bar', ...) <-- new track 'bar' created
+// flushTracks()
+// Second cycle
+// resolveTrack('foo', ...) <-- returns cached 'foo' track
+// flushTracks() <-- 'bar' is destroyed, as it was not resolved this cycle
+// Third cycle
+// flushTracks() <-- 'foo' is destroyed.
+export class TrackCache {
+ private safeCache = new Map<string, TrackCacheEntry>();
+ private recycleBin = new Map<string, TrackCacheEntry>();
+
+ // Creates a new track using |uri| and |params| or retrieves a cached track if
+ // |key| exists in the cache.
+ resolveTrack(key: string, uri: string, params: unknown): TrackCacheEntry
+ |undefined {
+ const trackDesc = pluginManager.resolveTrackInfo(uri);
+ if (!trackDesc) {
+ return undefined;
+ }
+
+ // Search for a cached version of this track in either of the caches.
+ const cached = this.recycleBin.get(key) ?? this.safeCache.get(key);
+
+ // Ensure the cached track has the same factory type as the resolved track.
+ // If this has changed, the track should be re-created.
+ if (cached && trackDesc.track === cached.desc.track) {
+ // Keep our cached track descriptor up to date, if anything's changed.
+ cached.desc = trackDesc;
+
+ // Move this track from the recycle bin to the safe cache, which means
+ // it's safe from disposal for this cycle.
+ this.safeCache.set(key, cached);
+ this.recycleBin.delete(key);
+
+ return cached;
+ } else {
+ // Cached track doesn't exist or is out of date, create a new one.
+ const trackContext: TrackContext = {
+ trackKey: key,
+ mountStore: <T>(migrate: Migrate<T>) => {
+ const {store, state} = globals;
+ const migratedState = migrate(state.tracks[key].state);
+ globals.store.edit((draft) => {
+ draft.tracks[key].state = migratedState;
+ });
+ return store.createProxy<T>(['tracks', key, 'state']);
+ },
+ params,
+ };
+ const entry: TrackCacheEntry = {
+ desc: trackDesc,
+ track: trackDesc.track(trackContext),
+ };
+ entry.track.onCreate(trackContext);
+
+ // Push track into the safe cache.
+ this.safeCache.set(key, entry);
+ return entry;
+ }
+ }
+
+ // Destroys all tracks in the recycle bin and moves all safe tracks into
+ // the recycle bin.
+ flushOldTracks() {
+ for (const entry of this.recycleBin.values()) {
+ entry.track.onDestroy();
+ }
+
+ this.recycleBin = this.safeCache;
+ this.safeCache = new Map<string, TrackCacheEntry>();
+ }
+}
diff --git a/ui/src/frontend/globals.ts b/ui/src/frontend/globals.ts
index 845c0d50f..4b606af23 100644
--- a/ui/src/frontend/globals.ts
+++ b/ui/src/frontend/globals.ts
@@ -44,6 +44,7 @@ import {
State,
} from '../common/state';
import {TimestampFormat, timestampFormat} from '../common/timestamp_format';
+import {TrackCache} from '../common/track_cache';
import {setPerfHooks} from '../core/perf';
import {raf} from '../core/raf_scheduler';
import {Engine} from '../trace_processor/engine';
@@ -308,6 +309,10 @@ class Globals {
engines = new Map<string, Engine>();
+ // This is only in globals since tracks are resolved from different scopes.
+ // TODO(stevegolton): Move into ViewerPage.
+ readonly trackCache = new TrackCache();
+
initialize(
dispatch: Dispatch, router: Router, initialState: State,
cmdManager: CommandManager) {
diff --git a/ui/src/frontend/track_group_panel.ts b/ui/src/frontend/track_group_panel.ts
index 04ae41eec..0ca7f29c6 100644
--- a/ui/src/frontend/track_group_panel.ts
+++ b/ui/src/frontend/track_group_panel.ts
@@ -18,13 +18,12 @@ import m from 'mithril';
import {assertExists} from '../base/logging';
import {Icons} from '../base/semantic_icons';
import {Actions} from '../common/actions';
-import {pluginManager} from '../common/plugins';
import {
getContainingTrackId,
TrackGroupState,
TrackState,
} from '../common/state';
-import {Migrate, Track, TrackContext, TrackTags} from '../public';
+import {Track, TrackTags} from '../public';
import {globals} from './globals';
import {drawGridLines} from './gridline_helper';
@@ -51,29 +50,6 @@ export class TrackGroupPanel extends Panel<Attrs> {
this.trackGroupId = attrs.trackGroupId;
}
- private tryLoadTrack() {
- const groupId = this.trackGroupId;
- const trackState = this.summaryTrackState;
-
- const {key, uri, params} = trackState;
-
- const ctx: TrackContext = {
- trackKey: key,
- mountStore: <T>(migrate: Migrate<T>) => {
- const {store, state} = globals;
- const migratedState = migrate(state.trackGroups[groupId].state);
- store.edit((draft) => {
- draft.trackGroups[groupId].state = migratedState;
- });
- return store.createProxy<T>(['trackGroups', groupId, 'state']);
- },
- params,
- };
-
- this.summaryTrack = pluginManager.createTrack(uri, ctx);
- this.summaryTrackTags = pluginManager.resolveTrackInfo(uri)?.tags;
- }
-
get trackGroupState(): TrackGroupState {
return assertExists(globals.state.trackGroups[this.trackGroupId]);
}
@@ -83,9 +59,13 @@ export class TrackGroupPanel extends Panel<Attrs> {
}
view({attrs}: m.CVnode<Attrs>) {
- if (!this.summaryTrack) {
- this.tryLoadTrack();
- }
+ const trackState = this.summaryTrackState;
+
+ const {key, uri, params} = trackState;
+
+ const track = globals.trackCache.resolveTrack(key, uri, params);
+ this.summaryTrack = track?.track;
+ this.summaryTrackTags = track?.desc.tags;
const collapsed = this.trackGroupState.collapsed;
let name = this.trackGroupState.name;
@@ -196,13 +176,6 @@ export class TrackGroupPanel extends Panel<Attrs> {
}
}
- onremove() {
- if (this.summaryTrack !== undefined) {
- this.summaryTrack.onDestroy();
- this.summaryTrack = undefined;
- }
- }
-
highlightIfTrackSelected(ctx: CanvasRenderingContext2D, size: PanelSize) {
const {visibleTimeScale} = globals.timeline;
const selection = globals.state.currentSelection;
diff --git a/ui/src/frontend/track_panel.ts b/ui/src/frontend/track_panel.ts
index b4fcda4b0..e462161e7 100644
--- a/ui/src/frontend/track_panel.ts
+++ b/ui/src/frontend/track_panel.ts
@@ -19,10 +19,9 @@ import {currentTargetOffset} from '../base/dom_utils';
import {Icons} from '../base/semantic_icons';
import {time} from '../base/time';
import {Actions} from '../common/actions';
-import {pluginManager} from '../common/plugins';
import {TrackState} from '../common/state';
import {raf} from '../core/raf_scheduler';
-import {Migrate, SliceRect, Track, TrackContext, TrackTags} from '../public';
+import {SliceRect, Track, TrackTags} from '../public';
import {checkerboard} from './checkerboard';
import {SELECTION_FILL_COLOR, TRACK_SHELL_WIDTH} from './css_constants';
@@ -347,38 +346,15 @@ export class TrackPanel extends Panel<TrackPanelAttrs> {
private trackState: TrackState|undefined;
private tags: TrackTags|undefined;
- private tryLoadTrack(vnode: m.CVnode<TrackPanelAttrs>) {
+ view(vnode: m.CVnode<TrackPanelAttrs>) {
const trackKey = vnode.attrs.trackKey;
const trackState = globals.state.tracks[trackKey];
-
- if (!trackState) return;
-
const {uri, params} = trackState;
- const trackCtx: TrackContext = {
- trackKey,
- mountStore: <T>(migrate: Migrate<T>) => {
- const {store, state} = globals;
- const migratedState = migrate(state.tracks[trackKey].state);
- globals.store.edit((draft) => {
- draft.tracks[trackKey].state = migratedState;
- });
- return store.createProxy<T>(['tracks', trackKey, 'state']);
- },
- params,
- };
-
- this.track = pluginManager.createTrack(uri, trackCtx);
- this.tags = pluginManager.resolveTrackInfo(uri)?.tags;
-
- this.track?.onCreate(trackCtx);
+ const track = globals.trackCache.resolveTrack(trackKey, uri, params);
+ this.track = track?.track;
+ this.tags = track?.desc.tags;
this.trackState = trackState;
- }
-
- view(vnode: m.CVnode<TrackPanelAttrs>) {
- if (!this.track) {
- this.tryLoadTrack(vnode);
- }
if (this.track === undefined || this.trackState === undefined) {
return m(TrackComponent, {
@@ -408,13 +384,6 @@ export class TrackPanel extends Panel<TrackPanelAttrs> {
}
}
- onremove() {
- if (this.track !== undefined) {
- this.track.onDestroy();
- this.track = undefined;
- }
- }
-
highlightIfTrackSelected(ctx: CanvasRenderingContext2D, size: PanelSize) {
const {visibleTimeScale} = globals.timeline;
const selection = globals.state.currentSelection;
diff --git a/ui/src/frontend/viewer_page.ts b/ui/src/frontend/viewer_page.ts
index 071c9683d..d59983737 100644
--- a/ui/src/frontend/viewer_page.ts
+++ b/ui/src/frontend/viewer_page.ts
@@ -222,6 +222,12 @@ class TraceViewer implements m.ClassComponent {
});
}
+ onupdate() {
+ // We know all tracks will have been resolved at this point.
+ // I.e. all the view() functions of all our children will have been called.
+ globals.trackCache.flushOldTracks();
+ }
+
onremove() {
window.removeEventListener('resize', this.onResize);
if (this.zoomContent) this.zoomContent.dispose();