diff options
author | Steve Golton <stevegolton@google.com> | 2023-12-14 10:32:36 +0000 |
---|---|---|
committer | Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com> | 2023-12-14 10:32:36 +0000 |
commit | afd0d26a3cb7936ad6186bde7b2bdc4aae32e723 (patch) | |
tree | ab7472d9ee4544db690f328e0d4c50f8daeb423a | |
parent | 3b4ec41c40d2fa88c05981e3e513cfcf451bf26d (diff) | |
parent | b907e13f7d72e8e49742483530ebb0683e300a27 (diff) | |
download | perfetto-afd0d26a3cb7936ad6186bde7b2bdc4aae32e723.tar.gz |
Merge "[ui] Created TrackCache object to manage track lifecycles." into main am: b907e13f7d
Original change: https://android-review.googlesource.com/c/platform/external/perfetto/+/2870378
Change-Id: Iee7d526be18e5c356c2ef5217c76ea613935c0c7
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
-rw-r--r-- | ui/src/common/plugins.ts | 9 | ||||
-rw-r--r-- | ui/src/common/track_cache.ts | 110 | ||||
-rw-r--r-- | ui/src/frontend/globals.ts | 5 | ||||
-rw-r--r-- | ui/src/frontend/track_group_panel.ts | 43 | ||||
-rw-r--r-- | ui/src/frontend/track_panel.ts | 41 | ||||
-rw-r--r-- | ui/src/frontend/viewer_page.ts | 6 |
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(); |