perfetto-ui: Track data request changes

How it now works:
- The visible time in global state is updated every 50ms
- The track controller will check and request more data for that
visible time if necessary. (Only if a pending request is not already
in progress and that track is actually visible)

Visible tracks are kept up to date in the frontendLocalState when
the canvas is rendered for a track. If the visible tracks have changed
since the last canvas redraw then the global state is updated with
the new visible tracks.

This fixes the issue that was causing a patch to be sent to the
global state and the DOM to be redrawn every time a dataReq was sent.
Now the horizontally scrolling is very fast + smooth.

Bug:136162323
Change-Id: I6d96b21dce2b798970de295cb2919841fbbf7e88
diff --git a/ui/src/common/actions.ts b/ui/src/common/actions.ts
index 5e68600..76e4bd4 100644
--- a/ui/src/common/actions.ts
+++ b/ui/src/common/actions.ts
@@ -147,20 +147,8 @@
     };
   },
 
-  reqTrackData(state: StateDraft, args: {
-    trackId: string; start: number; end: number; resolution: number;
-  }): void {
-    const id = args.trackId;
-    state.tracks[id].dataReq = {
-      start: args.start,
-      end: args.end,
-      resolution: args.resolution
-    };
-  },
-
-  clearTrackDataReq(state: StateDraft, args: {trackId: string}): void {
-    const id = args.trackId;
-    state.tracks[id].dataReq = undefined;
+  setVisibleTracks(state: StateDraft, args: {tracks: string[]}) {
+    state.visibleTracks = args.tracks;
   },
 
   executeQuery(
@@ -261,8 +249,10 @@
   },
 
   setVisibleTraceTime(
-      state: StateDraft, args: {time: TraceTime; lastUpdate: number;}): void {
+      state: StateDraft,
+      args: {time: TraceTime; res: number, lastUpdate: number;}): void {
     state.frontendLocalState.visibleTraceTime = args.time;
+    state.frontendLocalState.curResolution = args.res;
     state.frontendLocalState.lastUpdate = args.lastUpdate;
   },
 
diff --git a/ui/src/common/state.ts b/ui/src/common/state.ts
index a7be250..cdc46ea 100644
--- a/ui/src/common/state.ts
+++ b/ui/src/common/state.ts
@@ -29,7 +29,6 @@
   kind: string;
   name: string;
   trackGroup?: string;
-  dataReq?: TrackDataRequest;
   config: {};
 }
 
@@ -42,12 +41,6 @@
   summaryTrackId: string;
 }
 
-export interface TrackDataRequest {
-  start: number;
-  end: number;
-  resolution: number;
-}
-
 export interface EngineConfig {
   id: string;
   ready: boolean;
@@ -72,6 +65,7 @@
 
 export interface FrontendLocalState {
   visibleTraceTime: TraceTime;
+  curResolution: number;
   lastUpdate: number;  // Epoch in seconds (Date.now() / 1000).
 }
 
@@ -145,6 +139,7 @@
   traceTime: TraceTime;
   trackGroups: ObjectById<TrackGroupState>;
   tracks: ObjectById<TrackState>;
+  visibleTracks: string[];
   scrollingTracks: string[];
   pinnedTracks: string[];
   queries: ObjectById<QueryConfig>;
@@ -288,6 +283,7 @@
     traceTime: {...defaultTraceTime},
     tracks: {},
     trackGroups: {},
+    visibleTracks: [],
     pinnedTracks: [],
     scrollingTracks: [],
     queries: {},
@@ -300,6 +296,7 @@
     frontendLocalState: {
       visibleTraceTime: {...defaultTraceTime},
       lastUpdate: 0,
+      curResolution: 0,
     },
 
     logsPagination: {
diff --git a/ui/src/controller/trace_controller.ts b/ui/src/controller/trace_controller.ts
index cbd0a6f..9ac9d78 100644
--- a/ui/src/controller/trace_controller.ts
+++ b/ui/src/controller/trace_controller.ts
@@ -30,8 +30,8 @@
 import {ANDROID_LOGS_TRACK_KIND} from '../tracks/android_log/common';
 import {SLICE_TRACK_KIND} from '../tracks/chrome_slices/common';
 import {CPU_FREQ_TRACK_KIND} from '../tracks/cpu_freq/common';
-import {GPU_FREQ_TRACK_KIND} from '../tracks/gpu_freq/common';
 import {CPU_SLICE_TRACK_KIND} from '../tracks/cpu_slices/common';
+import {GPU_FREQ_TRACK_KIND} from '../tracks/gpu_freq/common';
 import {
   PROCESS_SCHEDULING_TRACK_KIND
 } from '../tracks/process_scheduling/common';
@@ -209,10 +209,10 @@
     ];
 
     if (globals.state.frontendLocalState.lastUpdate === 0) {
-      actions.push(Actions.setVisibleTraceTime({
-        time: traceTimeState,
-        lastUpdate: Date.now() / 1000,
-      }));
+      // We don't know the resolution at this point. However this will be
+      // replaced in 50ms so a guess is fine.
+      actions.push(Actions.setVisibleTraceTime(
+          {time: traceTimeState, lastUpdate: Date.now() / 1000, res: 0.008}));
     }
 
     globals.dispatchMultiple(actions);
diff --git a/ui/src/controller/track_controller.ts b/ui/src/controller/track_controller.ts
index 1f0a15f..16d49bd 100644
--- a/ui/src/controller/track_controller.ts
+++ b/ui/src/controller/track_controller.ts
@@ -13,11 +13,10 @@
 // limitations under the License.
 
 import {assertExists} from '../base/logging';
-import {Actions} from '../common/actions';
 import {Engine} from '../common/engine';
 import {Registry} from '../common/registry';
-import {TrackState} from '../common/state';
-import {TrackData} from '../common/track_data';
+import {TraceTime, TrackState} from '../common/state';
+import {LIMIT, TrackData} from '../common/track_data';
 
 import {Controller} from './controller';
 import {ControllerFactory} from './controller';
@@ -31,6 +30,9 @@
     extends Controller<'main'> {
   readonly trackId: string;
   readonly engine: Engine;
+  private data?: TrackData;
+  private pending = false;
+  private queuedRequest = false;
 
   constructor(args: TrackControllerArgs) {
     super('main');
@@ -52,7 +54,13 @@
   }
 
   publish(data: Data): void {
+    this.data = data;
     globals.publish('TrackData', {id: this.trackId, data});
+    this.pending = false;
+    if (this.queuedRequest) {
+      this.queuedRequest = false;
+      this.run();
+    }
   }
 
   /**
@@ -72,11 +80,43 @@
     return resolution >= 0.0008;
   }
 
+  shouldRequestData(traceTime: TraceTime): boolean {
+    if (this.data === undefined) return true;
+
+    // If at the limit only request more data if the view has moved.
+    const atLimit = this.data.length === LIMIT;
+    if (atLimit) {
+      // We request more data than the window, so add window duration to find
+      // the previous window.
+      const prevWindowStart =
+          this.data.start + (traceTime.startSec - traceTime.endSec);
+      return traceTime.startSec !== prevWindowStart;
+    }
+
+    // Otherwise request more data only when out of range of current data or
+    // resolution has changed.
+    const inRange = traceTime.startSec >= this.data.start &&
+        traceTime.endSec <= this.data.end;
+    return !inRange ||
+        this.data.resolution !== globals.state.frontendLocalState.curResolution;
+  }
+
   run() {
-    const dataReq = this.trackState.dataReq;
-    if (dataReq === undefined) return;
-    globals.dispatch(Actions.clearTrackDataReq({trackId: this.trackId}));
-    this.onBoundsChange(dataReq.start, dataReq.end, dataReq.resolution);
+    const visibleTime = globals.state.frontendLocalState.visibleTraceTime;
+    if (visibleTime === undefined) return;
+    const dur = visibleTime.endSec - visibleTime.startSec;
+    if (globals.state.visibleTracks.includes(this.trackId) &&
+        this.shouldRequestData(visibleTime)) {
+      if (this.pending) {
+        this.queuedRequest = true;
+      } else {
+        this.pending = true;
+        this.onBoundsChange(
+            visibleTime.startSec - dur,
+            visibleTime.endSec + dur,
+            globals.state.frontendLocalState.curResolution);
+      }
+    }
   }
 }
 
diff --git a/ui/src/frontend/frontend_local_state.ts b/ui/src/frontend/frontend_local_state.ts
index e93197c..aa3ed41 100644
--- a/ui/src/frontend/frontend_local_state.ts
+++ b/ui/src/frontend/frontend_local_state.ts
@@ -36,6 +36,8 @@
   showTimeSelectPreview = false;
   showNotePreview = false;
   localOnlyMode = false;
+  visibleTracks = new Set<string>();
+  prevVisibleTracks = new Set<string>();
 
   // TODO: there is some redundancy in the fact that both |visibleWindowTime|
   // and a |timeScale| have a notion of time range. That should live in one
@@ -49,6 +51,7 @@
 
     // Post a delayed update to the controller.
     if (this.pendingGlobalTimeUpdate) return;
+    this.pendingGlobalTimeUpdate = true;
     setTimeout(() => {
       this._lastUpdate = Date.now() / 1000;
       globals.dispatch(Actions.setVisibleTraceTime({
@@ -57,9 +60,10 @@
           endSec: this.visibleWindowTime.end,
         },
         lastUpdate: this._lastUpdate,
+        res: globals.getCurResolution(),
       }));
       this.pendingGlobalTimeUpdate = false;
-    }, 100);
+    }, 50);
   }
 
   mergeState(frontendLocalState: FrontendState): void {
@@ -103,4 +107,24 @@
     this.showTimeSelectPreview = show;
     globals.rafScheduler.scheduleRedraw();
   }
+
+  addVisibleTrack(trackId: string) {
+    this.visibleTracks.add(trackId);
+  }
+
+  // Called when beginning a canvas redraw.
+  clearVisibleTracks() {
+    this.prevVisibleTracks = new Set(this.visibleTracks);
+    this.visibleTracks.clear();
+  }
+
+  // Called when the canvas redraw is complete.
+  sendVisibleTracks() {
+    if (this.prevVisibleTracks.size !== this.visibleTracks.size ||
+        ![...this.prevVisibleTracks].every(
+            value => this.visibleTracks.has(value))) {
+      globals.dispatch(
+          Actions.setVisibleTracks({tracks: Array.from(this.visibleTracks)}));
+    }
+  }
 }
diff --git a/ui/src/frontend/globals.ts b/ui/src/frontend/globals.ts
index 034c94d..a0de043 100644
--- a/ui/src/frontend/globals.ts
+++ b/ui/src/frontend/globals.ts
@@ -13,7 +13,7 @@
 // limitations under the License.
 
 import {assertExists} from '../base/logging';
-import {Actions, DeferredAction} from '../common/actions';
+import {DeferredAction} from '../common/actions';
 import {createEmptyState, State} from '../common/state';
 
 import {FrontendLocalState} from './frontend_local_state';
@@ -140,24 +140,6 @@
     return Math.pow(10, Math.floor(Math.log10(resolution)));
   }
 
-  requestTrackData(trackId: string) {
-    const pending = assertExists(this._pendingTrackRequests);
-    if (pending.has(trackId)) return;
-
-    const {visibleWindowTime} = globals.frontendLocalState;
-    const resolution = this.getCurResolution();
-    const start = visibleWindowTime.start - visibleWindowTime.duration;
-    const end = visibleWindowTime.end + visibleWindowTime.duration;
-
-    pending.add(trackId);
-    globals.dispatch(Actions.reqTrackData({
-      trackId,
-      start,
-      end,
-      resolution,
-    }));
-  }
-
   resetForTesting() {
     this._dispatch = undefined;
     this._state = undefined;
diff --git a/ui/src/frontend/index.ts b/ui/src/frontend/index.ts
index 6d85cbe..0b73834 100644
--- a/ui/src/frontend/index.ts
+++ b/ui/src/frontend/index.ts
@@ -51,7 +51,7 @@
 
     // Only redraw if something other than the frontendLocalState changed.
     for (const key in globals.state) {
-      if (key !== 'frontendLocalState' &&
+      if (key !== 'frontendLocalState' && key !== 'visibleTracks' &&
           oldState[key] !== globals.state[key]) {
         this.redraw();
         return;
diff --git a/ui/src/frontend/raf_scheduler.ts b/ui/src/frontend/raf_scheduler.ts
index ef39f32..dd708c2 100644
--- a/ui/src/frontend/raf_scheduler.ts
+++ b/ui/src/frontend/raf_scheduler.ts
@@ -16,6 +16,8 @@
 
 import {assertTrue} from '../base/logging';
 
+import {globals} from './globals';
+
 import {
   debugNow,
   measure,
@@ -116,9 +118,11 @@
   private syncCanvasRedraw(nowMs: number) {
     const redrawStart = debugNow();
     if (this.isRedrawing) return;
+    globals.frontendLocalState.clearVisibleTracks();
     this.isRedrawing = true;
     for (const redraw of this.canvasRedrawCallbacks) redraw(nowMs);
     this.isRedrawing = false;
+    globals.frontendLocalState.sendVisibleTracks();
     if (perfDebug()) {
       this.perfStats.rafCanvas.addValue(debugNow() - redrawStart);
     }
diff --git a/ui/src/frontend/track.ts b/ui/src/frontend/track.ts
index daa0500..acd475c 100644
--- a/ui/src/frontend/track.ts
+++ b/ui/src/frontend/track.ts
@@ -13,7 +13,7 @@
 // limitations under the License.
 
 import {TrackState} from '../common/state';
-import {LIMIT, TrackData} from '../common/track_data';
+import {TrackData} from '../common/track_data';
 
 import {globals} from './globals';
 
@@ -37,7 +37,7 @@
  */
 export abstract class Track<Config = {}, Data extends TrackData = TrackData> {
   constructor(protected trackState: TrackState) {}
-  abstract renderCanvas(ctx: CanvasRenderingContext2D): void;
+  protected abstract renderCanvas(ctx: CanvasRenderingContext2D): void;
 
   get config(): Config {
     return this.trackState.config as Config;
@@ -63,22 +63,8 @@
 
   onMouseOut() {}
 
-  shouldRequestData(
-      data: Data|undefined, windowStart: number, windowEnd: number): boolean {
-    if (data === undefined) return true;
-
-    // If at the limit only request more data if the view has moved.
-    const atLimit = data.length === LIMIT;
-    if (atLimit) {
-      // We request more data than the window, so add window duration to find
-      // the previous window.
-      const prevWindowStart = data.start + (windowEnd - windowStart);
-      return windowStart !== prevWindowStart;
-    }
-
-    // Otherwise request more data only when out of range of current data or
-    // resolution has changed.
-    const inRange = windowStart >= data.start && windowEnd <= data.end;
-    return !inRange || data.resolution !== globals.getCurResolution();
+  render(ctx: CanvasRenderingContext2D) {
+    globals.frontendLocalState.addVisibleTrack(this.trackState.id);
+    this.renderCanvas(ctx);
   }
 }
diff --git a/ui/src/frontend/track_group_panel.ts b/ui/src/frontend/track_group_panel.ts
index 0e4eb37..c25e724 100644
--- a/ui/src/frontend/track_group_panel.ts
+++ b/ui/src/frontend/track_group_panel.ts
@@ -113,7 +113,7 @@
 
     ctx.translate(this.shellWidth, 0);
     if (this.summaryTrack) {
-      this.summaryTrack.renderCanvas(ctx);
+      this.summaryTrack.render(ctx);
     }
     ctx.restore();
 
diff --git a/ui/src/frontend/track_panel.ts b/ui/src/frontend/track_panel.ts
index 06fc589..30177ad 100644
--- a/ui/src/frontend/track_panel.ts
+++ b/ui/src/frontend/track_panel.ts
@@ -159,10 +159,17 @@
 }
 class TrackComponent implements m.ClassComponent<TrackComponentAttrs> {
   view({attrs}: m.CVnode<TrackComponentAttrs>) {
-    return m('.track', [
-      m(TrackShell, {trackState: attrs.trackState}),
-      m(TrackContent, {track: attrs.track})
-    ]);
+    return m(
+        '.track',
+        {
+          style: {
+            height: `${attrs.track.getHeight()}px`,
+          }
+        },
+        [
+          m(TrackShell, {trackState: attrs.trackState}),
+          m(TrackContent, {track: attrs.track})
+        ]);
   }
 }
 
@@ -196,17 +203,6 @@
   }
 
   view() {
-    return m(
-        '.track',
-        {
-          style: {
-            height: `${this.track.getHeight()}px`,
-          }
-        },
-        [
-          m(TrackShell, {trackState: this.trackState}),
-          m(TrackContent, {track: this.track})
-        ]);
     return m(TrackComponent, {trackState: this.trackState, track: this.track});
   }
 
@@ -221,7 +217,7 @@
 
     ctx.translate(TRACK_SHELL_WIDTH, 0);
 
-    this.track.renderCanvas(ctx);
+    this.track.render(ctx);
 
     ctx.restore();
 
diff --git a/ui/src/tracks/android_log/controller.ts b/ui/src/tracks/android_log/controller.ts
index 516aceb..5795564 100644
--- a/ui/src/tracks/android_log/controller.ts
+++ b/ui/src/tracks/android_log/controller.ts
@@ -23,17 +23,12 @@
 
 class AndroidLogTrackController extends TrackController<Config, Data> {
   static readonly kind = ANDROID_LOGS_TRACK_KIND;
-  private busy = false;
 
   onBoundsChange(start: number, end: number, resolution: number) {
     this.update(start, end, resolution);
   }
 
   private async update(start: number, end: number, resolution: number) {
-    // TODO(hjd): we should really call TraceProcessor.Interrupt() here.
-    if (this.busy) return;
-    this.busy = true;
-
     const startNs = Math.floor(start * 1e9);
     const endNs = Math.ceil(end * 1e9);
 
@@ -49,7 +44,6 @@
       where ts >= ${startNs} and ts <= ${endNs}
       group by ts_quant, prio
       order by ts_quant, prio limit ${LIMIT};`);
-    this.busy = false;
 
     const rowCount = +rawResult.numRecords;
     const result = {
diff --git a/ui/src/tracks/android_log/frontend.ts b/ui/src/tracks/android_log/frontend.ts
index 7d15fbb..8a89544 100644
--- a/ui/src/tracks/android_log/frontend.ts
+++ b/ui/src/tracks/android_log/frontend.ts
@@ -53,11 +53,6 @@
 
     const data = this.data();
 
-    if (this.shouldRequestData(
-            data, visibleWindowTime.start, visibleWindowTime.end)) {
-      globals.requestTrackData(this.trackState.id);
-    }
-
     if (data === undefined) return;  // Can't possibly draw anything.
 
     const dataStartPx = timeScale.timeToPx(data.start);
diff --git a/ui/src/tracks/chrome_slices/controller.ts b/ui/src/tracks/chrome_slices/controller.ts
index 137d403..04cf76e 100644
--- a/ui/src/tracks/chrome_slices/controller.ts
+++ b/ui/src/tracks/chrome_slices/controller.ts
@@ -23,7 +23,6 @@
 
 class ChromeSliceTrackController extends TrackController<Config, Data> {
   static readonly kind = SLICE_TRACK_KIND;
-  private busy = false;
   private setup = false;
 
   onBoundsChange(start: number, end: number, resolution: number): void {
@@ -31,14 +30,10 @@
   }
 
   private async update(start: number, end: number, resolution: number) {
-    // TODO: we should really call TraceProcessor.Interrupt() at this point.
-    if (this.busy) return;
-
     const startNs = Math.round(start * 1e9);
     const endNs = Math.round(end * 1e9);
     // Ns in 1px width. We want all slices smaller than 1px to be grouped.
     const minNs = Math.round(resolution * 1e9);
-    this.busy = true;
 
     if (!this.setup) {
       await this.query(
@@ -103,7 +98,6 @@
         `select * from ${this.tableName('big')} order by ts limit ${LIMIT}`;
 
     const rawResult = await this.query(query);
-    this.busy = false;
 
     if (rawResult.error) {
       throw new Error(`Query error "${query}": ${rawResult.error}`);
diff --git a/ui/src/tracks/chrome_slices/frontend.ts b/ui/src/tracks/chrome_slices/frontend.ts
index eb719d8..2f46251 100644
--- a/ui/src/tracks/chrome_slices/frontend.ts
+++ b/ui/src/tracks/chrome_slices/frontend.ts
@@ -52,11 +52,6 @@
     const {timeScale, visibleWindowTime} = globals.frontendLocalState;
     const data = this.data();
 
-    if (this.shouldRequestData(
-            data, visibleWindowTime.start, visibleWindowTime.end)) {
-      globals.requestTrackData(this.trackState.id);
-    }
-
     if (data === undefined) return;  // Can't possibly draw anything.
 
     // If the cached trace slices don't fully cover the visible time range,
diff --git a/ui/src/tracks/counter/controller.ts b/ui/src/tracks/counter/controller.ts
index 6138e25..5c16188 100644
--- a/ui/src/tracks/counter/controller.ts
+++ b/ui/src/tracks/counter/controller.ts
@@ -28,7 +28,6 @@
 
 class CounterTrackController extends TrackController<Config, Data> {
   static readonly kind = COUNTER_TRACK_KIND;
-  private busy = false;
   private setup = false;
   private maximumValueSeen = 0;
   private minimumValueSeen = 0;
@@ -39,13 +38,9 @@
 
   private async update(start: number, end: number, resolution: number):
       Promise<void> {
-    // TODO: we should really call TraceProcessor.Interrupt() at this point.
-    if (this.busy) return;
-
     const startNs = Math.round(start * 1e9);
     const endNs = Math.round(end * 1e9);
 
-    this.busy = true;
     if (!this.setup) {
       const result = await this.query(`
       select max(value), min(value) from
@@ -133,7 +128,6 @@
     }
 
     this.publish(data);
-    this.busy = false;
   }
 
   private maximumValue() {
diff --git a/ui/src/tracks/counter/frontend.ts b/ui/src/tracks/counter/frontend.ts
index 5319ff8..3133003 100644
--- a/ui/src/tracks/counter/frontend.ts
+++ b/ui/src/tracks/counter/frontend.ts
@@ -50,11 +50,6 @@
     const {timeScale, visibleWindowTime} = globals.frontendLocalState;
     const data = this.data();
 
-    if (this.shouldRequestData(
-            data, visibleWindowTime.start, visibleWindowTime.end)) {
-      globals.requestTrackData(this.trackState.id);
-    }
-
     if (data === undefined) return;  // Can't possibly draw anything.
 
     assertTrue(data.timestamps.length === data.values.length);
diff --git a/ui/src/tracks/cpu_freq/controller.ts b/ui/src/tracks/cpu_freq/controller.ts
index e93ff14..1cd4a7a 100644
--- a/ui/src/tracks/cpu_freq/controller.ts
+++ b/ui/src/tracks/cpu_freq/controller.ts
@@ -28,7 +28,6 @@
 
 class CpuFreqTrackController extends TrackController<Config, Data> {
   static readonly kind = CPU_FREQ_TRACK_KIND;
-  private busy = false;
   private setup = false;
   private maximumValueSeen = 0;
 
@@ -38,13 +37,9 @@
 
   private async update(start: number, end: number, resolution: number):
       Promise<void> {
-    // TODO: we should really call TraceProcessor.Interrupt() at this point.
-    if (this.busy) return;
-
     const startNs = Math.round(start * 1e9);
     const endNs = Math.round(end * 1e9);
 
-    this.busy = true;
     if (!this.setup) {
       const result = await this.query(`
       select max(value) from
@@ -173,7 +168,6 @@
     }
 
     this.publish(data);
-    this.busy = false;
   }
 
   private maximumValue() {
diff --git a/ui/src/tracks/cpu_freq/frontend.ts b/ui/src/tracks/cpu_freq/frontend.ts
index 7433ae3..057264f 100644
--- a/ui/src/tracks/cpu_freq/frontend.ts
+++ b/ui/src/tracks/cpu_freq/frontend.ts
@@ -52,10 +52,6 @@
     const {timeScale, visibleWindowTime} = globals.frontendLocalState;
     const data = this.data();
 
-    if (this.shouldRequestData(
-            data, visibleWindowTime.start, visibleWindowTime.end)) {
-      globals.requestTrackData(this.trackState.id);
-    }
     if (data === undefined) return;  // Can't possibly draw anything.
 
     assertTrue(data.tsStarts.length === data.freqKHz.length);
diff --git a/ui/src/tracks/cpu_slices/controller.ts b/ui/src/tracks/cpu_slices/controller.ts
index 4b1ae0e..fbc6048 100644
--- a/ui/src/tracks/cpu_slices/controller.ts
+++ b/ui/src/tracks/cpu_slices/controller.ts
@@ -30,7 +30,6 @@
 
 class CpuSliceTrackController extends TrackController<Config, Data> {
   static readonly kind = CPU_SLICE_TRACK_KIND;
-  private busy = false;
   private setup = false;
 
   onBoundsChange(start: number, end: number, resolution: number): void {
@@ -39,13 +38,10 @@
 
   private async update(start: number, end: number, resolution: number):
       Promise<void> {
-    // TODO: we should really call TraceProcessor.Interrupt() at this point.
-    if (this.busy) return;
 
     const startNs = Math.round(start * 1e9);
     const endNs = Math.round(end * 1e9);
 
-    this.busy = true;
     if (this.setup === false) {
       await this.query(
           `create virtual table ${this.tableName('window')} using window;`);
@@ -77,7 +73,6 @@
       this.publish(
           await this.computeSlices(fromNs(windowStartNs), end, resolution));
     }
-    this.busy = false;
   }
 
   private async computeSummary(
diff --git a/ui/src/tracks/cpu_slices/frontend.ts b/ui/src/tracks/cpu_slices/frontend.ts
index 8d989fe..4d2d03a 100644
--- a/ui/src/tracks/cpu_slices/frontend.ts
+++ b/ui/src/tracks/cpu_slices/frontend.ts
@@ -56,10 +56,6 @@
     const {timeScale, visibleWindowTime} = globals.frontendLocalState;
     const data = this.data();
 
-    if (this.shouldRequestData(
-            data, visibleWindowTime.start, visibleWindowTime.end)) {
-      globals.requestTrackData(this.trackState.id);
-    }
     if (data === undefined) return;  // Can't possibly draw anything.
 
     // If the cached trace slices don't fully cover the visible time range,
diff --git a/ui/src/tracks/gpu_freq/controller.ts b/ui/src/tracks/gpu_freq/controller.ts
index a02f434..4fb1fc8 100644
--- a/ui/src/tracks/gpu_freq/controller.ts
+++ b/ui/src/tracks/gpu_freq/controller.ts
@@ -28,7 +28,6 @@
 
 class GpuFreqTrackController extends TrackController<Config, Data> {
   static readonly kind = GPU_FREQ_TRACK_KIND;
-  private busy = false;
   private setup = false;
   private maximumValueSeen = 0;
 
@@ -38,13 +37,10 @@
 
   private async update(start: number, end: number, resolution: number):
       Promise<void> {
-    // TODO: we should really call TraceProcessor.Interrupt() at this point.
-    if (this.busy) return;
 
     const startNs = Math.round(start * 1e9);
     const endNs = Math.round(end * 1e9);
 
-    this.busy = true;
     if (!this.setup) {
       const result = await this.query(`
       select max(value) from
@@ -143,7 +139,6 @@
     }
 
     this.publish(data);
-    this.busy = false;
   }
 
   private maximumValue() {
diff --git a/ui/src/tracks/gpu_freq/frontend.ts b/ui/src/tracks/gpu_freq/frontend.ts
index 741a659..1d15b62 100644
--- a/ui/src/tracks/gpu_freq/frontend.ts
+++ b/ui/src/tracks/gpu_freq/frontend.ts
@@ -51,10 +51,6 @@
     const {timeScale, visibleWindowTime} = globals.frontendLocalState;
     const data = this.data();
 
-    if (this.shouldRequestData(
-            data, visibleWindowTime.start, visibleWindowTime.end)) {
-      globals.requestTrackData(this.trackState.id);
-    }
     if (data === undefined) return;  // Can't possibly draw anything.
 
     assertTrue(data.tsStarts.length === data.freqKHz.length);
diff --git a/ui/src/tracks/process_scheduling/controller.ts b/ui/src/tracks/process_scheduling/controller.ts
index fa800c9..e59eda6 100644
--- a/ui/src/tracks/process_scheduling/controller.ts
+++ b/ui/src/tracks/process_scheduling/controller.ts
@@ -30,7 +30,6 @@
 
 class ProcessSchedulingTrackController extends TrackController<Config, Data> {
   static readonly kind = PROCESS_SCHEDULING_TRACK_KIND;
-  private busy = false;
   private setup = false;
   private numCpus = 0;
 
@@ -40,9 +39,6 @@
 
   private async update(start: number, end: number, resolution: number):
       Promise<void> {
-    // TODO: we should really call TraceProcessor.Interrupt() at this point.
-    if (this.busy) return;
-
     if (!this.config.upid) {
       return;
     }
@@ -50,7 +46,6 @@
     const startNs = Math.round(start * 1e9);
     const endNs = Math.round(end * 1e9);
 
-    this.busy = true;
     if (this.setup === false) {
       await this.query(
           `create virtual table ${this.tableName('window')} using window;`);
@@ -87,7 +82,6 @@
       this.publish(
           await this.computeSlices(fromNs(windowStartNs), end, resolution));
     }
-    this.busy = false;
   }
 
   private async computeSummary(
diff --git a/ui/src/tracks/process_scheduling/frontend.ts b/ui/src/tracks/process_scheduling/frontend.ts
index 2e7e3e6..e143692 100644
--- a/ui/src/tracks/process_scheduling/frontend.ts
+++ b/ui/src/tracks/process_scheduling/frontend.ts
@@ -50,10 +50,6 @@
     const {timeScale, visibleWindowTime} = globals.frontendLocalState;
     const data = this.data();
 
-    if (this.shouldRequestData(
-            data, visibleWindowTime.start, visibleWindowTime.end)) {
-      globals.requestTrackData(this.trackState.id);
-    }
     if (data === undefined) return;  // Can't possibly draw anything.
 
     // If the cached trace slices don't fully cover the visible time range,
diff --git a/ui/src/tracks/process_summary/controller.ts b/ui/src/tracks/process_summary/controller.ts
index 650ff41..7c8b9e9 100644
--- a/ui/src/tracks/process_summary/controller.ts
+++ b/ui/src/tracks/process_summary/controller.ts
@@ -28,7 +28,6 @@
 
 class ProcessSummaryTrackController extends TrackController<Config, Data> {
   static readonly kind = PROCESS_SUMMARY_TRACK;
-  private busy = false;
   private setup = false;
 
   onBoundsChange(start: number, end: number, resolution: number): void {
@@ -37,10 +36,6 @@
 
   private async update(start: number, end: number, resolution: number):
       Promise<void> {
-    // TODO: we should really call TraceProcessor.Interrupt() at this point.
-    if (this.busy) return;
-    this.busy = true;
-
     const startNs = Math.round(start * 1e9);
     const endNs = Math.round(end * 1e9);
 
@@ -82,7 +77,6 @@
 
     this.publish(await this.computeSummary(
         fromNs(windowStartNs), end, resolution, bucketSizeNs));
-    this.busy = false;
   }
 
   private async computeSummary(
diff --git a/ui/src/tracks/process_summary/frontend.ts b/ui/src/tracks/process_summary/frontend.ts
index 34da876..69182f3 100644
--- a/ui/src/tracks/process_summary/frontend.ts
+++ b/ui/src/tracks/process_summary/frontend.ts
@@ -41,11 +41,6 @@
   renderCanvas(ctx: CanvasRenderingContext2D): void {
     const {timeScale, visibleWindowTime} = globals.frontendLocalState;
     const data = this.data();
-
-    if (this.shouldRequestData(
-            data, visibleWindowTime.start, visibleWindowTime.end)) {
-      globals.requestTrackData(this.trackState.id);
-    }
     if (data === undefined) return;  // Can't possibly draw anything.
 
     checkerboardExcept(
diff --git a/ui/src/tracks/thread_state/controller.ts b/ui/src/tracks/thread_state/controller.ts
index 7aa1653..5a40809 100644
--- a/ui/src/tracks/thread_state/controller.ts
+++ b/ui/src/tracks/thread_state/controller.ts
@@ -29,7 +29,6 @@
 
 class ThreadStateTrackController extends TrackController<Config, Data> {
   static readonly kind = THREAD_STATE_TRACK_KIND;
-  private busy = false;
   private setup = false;
 
   onBoundsChange(start: number, end: number, resolution: number): void {
@@ -38,9 +37,6 @@
 
   private async update(start: number, end: number, resolution: number):
       Promise<void> {
-    if (this.busy) return;
-    this.busy = true;
-
     const startNs = Math.round(start * 1e9);
     const endNs = Math.round(end * 1e9);
     let minNs = 0;
@@ -199,7 +195,6 @@
     }
 
     this.publish(summary);
-    this.busy = false;
   }
 
   private async query(query: string) {
diff --git a/ui/src/tracks/thread_state/frontend.ts b/ui/src/tracks/thread_state/frontend.ts
index b8ccd83..979fea7 100644
--- a/ui/src/tracks/thread_state/frontend.ts
+++ b/ui/src/tracks/thread_state/frontend.ts
@@ -52,10 +52,6 @@
     const data = this.data();
     const charWidth = ctx.measureText('dbpqaouk').width / 8;
 
-    if (this.shouldRequestData(
-            data, visibleWindowTime.start, visibleWindowTime.end)) {
-      globals.requestTrackData(this.trackState.id);
-    }
     if (data === undefined) return;  // Can't possibly draw anything.
 
     for (let i = 0; i < data.starts.length; i++) {
diff --git a/ui/src/tracks/vsync/controller.ts b/ui/src/tracks/vsync/controller.ts
index e6776cb..f5dd4c7 100644
--- a/ui/src/tracks/vsync/controller.ts
+++ b/ui/src/tracks/vsync/controller.ts
@@ -24,7 +24,6 @@
 
 class VsyncTrackController extends TrackController<Config, Data> {
   static readonly kind = KIND;
-  private busy = false;
   private setup = false;
 
   onBoundsChange(start: number, end: number, resolution: number) {
@@ -32,10 +31,6 @@
   }
 
   private async update(start: number, end: number, resolution: number) {
-    // TODO(hjd): we should really call TraceProcessor.Interrupt() here.
-    if (this.busy) return;
-    this.busy = true;
-
     if (this.setup === false) {
       await this.query(
           `create virtual table window_${this.trackState.id} using window;`);
@@ -50,7 +45,6 @@
       select ts from counters
         where name like "${this.config.counterName}%"
         order by ts limit ${LIMIT};`);
-    this.busy = false;
     const rowCount = +rawResult.numRecords;
     const result = {
       start,
diff --git a/ui/src/tracks/vsync/frontend.ts b/ui/src/tracks/vsync/frontend.ts
index c2b19ea..8513884 100644
--- a/ui/src/tracks/vsync/frontend.ts
+++ b/ui/src/tracks/vsync/frontend.ts
@@ -38,10 +38,6 @@
     const {timeScale, visibleWindowTime} = globals.frontendLocalState;
 
     const data = this.data();
-    if (this.shouldRequestData(
-            data, visibleWindowTime.start, visibleWindowTime.end)) {
-      globals.requestTrackData(this.trackState.id);
-    }
     if (data === undefined) return;  // Can't possibly draw anything.
 
     const dataStartPx = timeScale.timeToPx(data.start);