perfetto-ui: Update CPU track to use span join

Change-Id: I8aec6ee181680ed1352de0ee3532880008ab4a8e
diff --git a/ui/src/tracks/cpu_slices/controller.ts b/ui/src/tracks/cpu_slices/controller.ts
index 17a1b0f..77fbdf0 100644
--- a/ui/src/tracks/cpu_slices/controller.ts
+++ b/ui/src/tracks/cpu_slices/controller.ts
@@ -23,49 +23,82 @@
 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) {
+  onBoundsChange(start: number, end: number, resolution: number): void {
+    this.update(start, end, resolution);
+  }
+
+  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 LIMIT = 10000;
-    const query = 'select ts,dur,utid from sched ' +
-        `where cpu = ${this.config.cpu} ` +
-        `and ts_lower_bound = ${Math.round(start * 1e9)} ` +
-        `and ts <= ${Math.round(end * 1e9)} ` +
-        `and dur >= ${Math.round(resolution * 1e9)} ` +
-        `and utid != 0 ` +
-        `order by ts ` +
-        `limit ${LIMIT};`;
+
+    const startNs = Math.round(start * 1e9);
+    const endNs = Math.round(end * 1e9);
+    const resolutionNs = Math.round(resolution * 1e9);
 
     this.busy = true;
-    this.engine.query(query).then(rawResult => {
-      this.busy = false;
-      if (rawResult.error) {
-        throw new Error(`Query error "${query}": ${rawResult.error}`);
-      }
-      const numRows = +rawResult.numRecords;
+    if (this.setup === false) {
+      await this.query(
+          `create virtual table window_${this.trackState.id} using window;`);
+      await this.query(`create virtual table span_${this.trackState.id}
+                     using span(sched, window_${this.trackState.id}, cpu);`);
+      this.setup = true;
+    }
 
-      const slices: Data = {
-        start,
-        end,
-        resolution,
-        starts: new Float64Array(numRows),
-        ends: new Float64Array(numRows),
-        utids: new Uint32Array(numRows),
-      };
+    this.query(`update window_${this.trackState.id} set
+      window_start=${startNs},
+      window_dur=${endNs - startNs}
+      where rowid = 0;`);
 
-      for (let row = 0; row < numRows; row++) {
-        const cols = rawResult.columns;
-        const startSec = fromNs(+cols[0].longValues![row]);
-        slices.starts[row] = startSec;
-        slices.ends[row] = startSec + fromNs(+cols[1].longValues![row]);
-        slices.utids[row] = +cols[2].longValues![row];
-      }
-      if (numRows === LIMIT) {
-        slices.end = slices.ends[slices.ends.length - 1];
-      }
-      this.publish(slices);
-    });
+    const LIMIT = 10000;
+    const query = `select ts,dur,utid from span_${this.trackState.id} 
+        where cpu = ${this.config.cpu}
+        and utid != 0
+        and dur >= ${resolutionNs}
+        limit ${LIMIT};`;
+    const rawResult = await this.query(query);
+
+    const numRows = +rawResult.numRecords;
+
+    const slices: Data = {
+      start,
+      end,
+      resolution,
+      starts: new Float64Array(numRows),
+      ends: new Float64Array(numRows),
+      utids: new Uint32Array(numRows),
+    };
+
+    for (let row = 0; row < numRows; row++) {
+      const cols = rawResult.columns;
+      const startSec = fromNs(+cols[0].longValues![row]);
+      slices.starts[row] = startSec;
+      slices.ends[row] = startSec + fromNs(+cols[1].longValues![row]);
+      slices.utids[row] = +cols[2].longValues![row];
+    }
+    if (numRows === LIMIT) {
+      slices.end = slices.ends[slices.ends.length - 1];
+    }
+    this.publish(slices);
+    this.busy = false;
+  }
+
+  private async query(query: string) {
+    const result = await this.engine.query(query);
+    if (result.error) {
+      throw new Error(`Query error "${query}": ${result.error}`);
+    }
+    return result;
+  }
+
+  onDestroy(): void {
+    if (this.setup) {
+      this.query(`drop table window_${this.trackState.id}`);
+      this.query(`drop table span_${this.trackState.id}`);
+      this.setup = false;
+    }
   }
 }