Merge "Create a reusable thread_state table"
diff --git a/ui/src/controller/trace_controller.ts b/ui/src/controller/trace_controller.ts
index e2f692e..ffa27e1 100644
--- a/ui/src/controller/trace_controller.ts
+++ b/ui/src/controller/trace_controller.ts
@@ -276,6 +276,7 @@
 
     await this.listThreads();
     await this.loadTimelineOverview(traceTime);
+    await this.initaliseHelperViews();
     return engineMode;
   }
 
@@ -796,6 +797,68 @@
     globals.publish('OverviewData', slicesData);
   }
 
+  async initaliseHelperViews() {
+    const engine = assertExists<Engine>(this.engine);
+    let event = 'sched_waking';
+    const waking = await engine.query(
+        `select * from instants where name = 'sched_waking' limit 1`);
+    if (waking.numRecords === 0) {
+      // Only use sched_wakeup if sched_waking is not in the trace.
+      event = 'sched_wakeup';
+    }
+    await engine.query(`create view runnable AS
+      select
+        ts,
+        lead(ts, 1, (select end_ts from trace_bounds))
+          OVER(partition by ref order by ts) - ts as dur,
+        ref as utid
+      from instants
+      where name = '${event}'`);
+
+    // Get the first ts for each utid - whether a sched wakeup/waking
+    // or sched event.
+    await engine.query(`create view first_thread as
+      select min(ts) as ts, utid from
+      (select min(ts) as ts, utid from runnable group by utid
+       UNION
+      select min(ts) as ts,utid from sched group by utid)
+      group by utid`);
+
+    // Create an entry from first ts to either the first sched_wakeup/waking
+    // or to the end if there are no sched wakeup/ings. This means we will
+    // show all information we have even with no sched_wakeup/waking events.
+    await engine.query(`create view fill as
+      select first_thread.ts as ts,
+      coalesce(min(runnable.ts), (select end_ts from trace_bounds)) -
+      first_thread.ts as dur,
+      runnable.utid as utid
+      from runnable
+      JOIN first_thread using(utid) group by utid`);
+
+    await engine.query(`create view full_runnable as
+        select * from runnable UNION
+        select * from fill`);
+
+    await engine.query(`create virtual table thread_span
+        using span_left_join(
+          full_runnable partitioned utid,
+          sched partitioned utid)`);
+
+    // For performance reasons we need to create a table here.
+    // Once b/145350531 is fixed this should be able to revert to a
+    // view and we can recover the extra memory use.
+    await engine.query(`create table thread_state as
+      select ts, dur, utid, cpu,
+      case when end_state is not null then 'Running'
+      when lag(end_state) over ordered is not null
+      then lag(end_state) over ordered else 'Runnable'
+      end as state
+      from thread_span window ordered as
+      (partition by utid order by ts)`);
+
+    await engine.query(`create index utid_index on thread_state(utid)`);
+  }
+
   private updateStatus(msg: string): void {
     globals.dispatch(Actions.updateStatus({
       msg,
diff --git a/ui/src/tracks/thread_state/controller.ts b/ui/src/tracks/thread_state/controller.ts
index 7c29edb..2c90c0d 100644
--- a/ui/src/tracks/thread_state/controller.ts
+++ b/ui/src/tracks/thread_state/controller.ts
@@ -42,81 +42,21 @@
     }
 
     if (this.setup === false) {
-      let event = 'sched_waking';
-      const waking = await this.query(
-          `select * from instants where name = 'sched_waking' limit 1`);
-      if (waking.numRecords === 0) {
-        // Only use sched_wakeup if sched_waking is not in the trace.
-        event = 'sched_wakeup';
-      }
-      await this.query(`create view ${this.tableName('runnable')} AS
-      select
-        ts,
-        lead(ts, 1, (select end_ts from trace_bounds))
-          OVER(order by ts) - ts as dur,
-        ref as utid
-      from instants
-      where name = '${event}'
-      and utid = ${this.config.utid}`);
-
       await this.query(
           `create virtual table ${this.tableName('window')} using window;`);
 
-      // Get the first ts for this utid - whether a sched wakeup/waking
-      // or sched event.
-      await this.query(`create view ${this.tableName('start')} as
-      select min(ts) as ts from
-        (select ts from ${this.tableName('runnable')} UNION
-        select ts from sched where utid = ${this.config.utid})`);
-
-      // Create an entry from first ts to either the first sched_wakeup/waking
-      // or to the end if there are no sched wakeup/ings. This means we will
-      // show all information we have even with no sched_wakeup/waking events.
-      await this.query(`create view ${this.tableName('fill')} AS
-        select
-        (select ts from ${this.tableName('start')}),
-        (select coalesce(
-          (select min(ts) from ${this.tableName('runnable')}),
-          (select end_ts from trace_bounds)
-        )) - (select ts from ${this.tableName('start')}) as dur,
-        ${this.config.utid} as utid
-        `);
-
-      await this.query(`create view ${this.tableName('full_runnable')} as
-        select * from ${this.tableName('runnable')} UNION
-        select * from ${this.tableName('fill')}`);
-
-      await this.query(`create virtual table ${this.tableName('span')}
-        using span_left_join(
-          ${this.tableName('full_runnable')} partitioned utid,
-          sched partitioned utid)`);
-
-      // Need to compute the lag(end_state) before joining with the window
-      // table to avoid the first visible slice always having a null prev
-      // end state.
-      await this.query(`create view ${this.tableName('span_view')} as
-        select ts, dur, utid, cpu,
-        case
-        when end_state is not null
-        then 'Running'
-        when lag(end_state) over ${this.tableName('ordered')} is not null
-        then lag(end_state) over ${this.tableName('ordered')}
-        else 'Runnable'
-        end as state
-        from ${this.tableName('span')}
-        where utid = ${this.config.utid}
-        window ${this.tableName('ordered')} as (order by ts)`);
-
       await this.query(`create view ${this.tableName('long_states')} as
-      select * from ${this.tableName('span_view')} where dur >= ${minNs}`);
+      select * from thread_state where dur >= ${minNs} and utid = ${
+          this.config.utid}`);
 
       // Create a slice from the first ts to the end of the trace. To
       // be span joined with the long states - This effectively combines all
       // of the short states into a single 'Busy' state.
       await this.query(`create view ${this.tableName('fill_gaps')} as select
-      (select min(ts) from ${this.tableName('span_view')}) as ts,
+      (select min(ts) from thread_state where utid = ${this.config.utid}) as ts,
       (select end_ts from trace_bounds) -
-      (select min(ts) from ${this.tableName('span_view')}) as dur,
+      (select min(ts) from thread_state where utid = ${
+          this.config.utid}) as dur,
       ${this.config.utid} as utid`);
 
       await this.query(`create virtual table ${this.tableName('summarized')}
@@ -128,7 +68,6 @@
         ${this.tableName('window')},
         ${this.tableName('summarized')} partitioned utid)`);
 
-
       this.setup = true;
     }
 
@@ -143,13 +82,15 @@
     this.query(`drop view if exists ${this.tableName('fill_gaps')}`);
 
     await this.query(`create view ${this.tableName('long_states')} as
-     select * from ${this.tableName('span_view')} where dur > ${minNs}`);
+      select * from thread_state where dur >= ${minNs} and utid = ${
+        this.config.utid}`);
 
     await this.query(`create view ${this.tableName('fill_gaps')} as select
-     (select min(ts) from ${this.tableName('span_view')}) as ts,
-     (select end_ts from trace_bounds) - (select min(ts) from ${
-        this.tableName('span_view')}) as dur,
-     ${this.config.utid} as utid, -1 as cpu`);
+      (select min(ts) from thread_state where utid = ${this.config.utid}) as ts,
+      (select end_ts from trace_bounds) -
+      (select min(ts) from thread_state where utid = ${
+        this.config.utid}) as dur,
+      ${this.config.utid} as utid`);
 
     const query = `select ts, cast(dur as double), utid,
     case when state is not null then state else 'Busy' end as state,
@@ -197,13 +138,8 @@
   onDestroy(): void {
     if (this.setup) {
       this.query(`drop table ${this.tableName('window')}`);
-      this.query(`drop table ${this.tableName('span')}`);
       this.query(`drop table ${this.tableName('current')}`);
       this.query(`drop table ${this.tableName('summarized')}`);
-      this.query(`drop view ${this.tableName('runnable')}`);
-      this.query(`drop view ${this.tableName('fill')}`);
-      this.query(`drop view ${this.tableName('full_runnable')}`);
-      this.query(`drop view ${this.tableName('span_view')}`);
       this.query(`drop view ${this.tableName('long_states')}`);
       this.query(`drop view ${this.tableName('fill_gaps')}`);
       this.setup = false;