Add process grouping for chrome traces

Currently the summary view is extremely slow (especially on the example
chrome trace), so it is disabled if there are more than 10 processes.
To try it out, you can use a smaller trace (you can find one at
https://goo.gl/KuFdtb).

Preview: https://deepanjan.me/previews/aosp/790720/#!/

Change-Id: I5e1c7c2874aaa52126985cfa6cdab777b1c70563
diff --git a/ui/src/controller/trace_controller.ts b/ui/src/controller/trace_controller.ts
index cbefcf5..fd12b15 100644
--- a/ui/src/controller/trace_controller.ts
+++ b/ui/src/controller/trace_controller.ts
@@ -14,15 +14,19 @@
 
 import '../tracks/all_controller';
 
+import * as uuidv4 from 'uuid/v4';
+
 import {assertExists, assertTrue} from '../base/logging';
 import {
   Actions,
   DeferredAction,
 } from '../common/actions';
+import {SCROLLING_TRACK_GROUP} from '../common/state';
 import {TimeSpan} from '../common/time';
 import {QuantizedLoad, ThreadDesc} from '../frontend/globals';
 import {SLICE_TRACK_KIND} from '../tracks/chrome_slices/common';
 import {CPU_SLICE_TRACK_KIND} from '../tracks/cpu_slices/common';
+import {PROCESS_SUMMARY_TRACK} from '../tracks/process_summary/common';
 
 import {Child, Children, Controller} from './controller';
 import {Engine} from './engine';
@@ -221,38 +225,79 @@
         engineId: this.engineId,
         kind: CPU_SLICE_TRACK_KIND,
         name: `Cpu ${cpu}`,
+        trackGroup: SCROLLING_TRACK_GROUP,
         config: {
           cpu,
         }
       }));
     }
 
-    const threadQuery = await engine.query(`
-      select upid, utid, tid, thread.name, depth
-      from thread inner join (
-        select utid, max(slices.depth) as depth
-        from slices
-        group by utid
-      ) using(utid)`);
+    // Local experiments shows getting maxDepth separately is ~2x faster than
+    // joining with threads and processes.
+    const maxDepthQuery =
+        await engine.query('select utid, max(depth) from slices group by utid');
+
+    const utidToMaxDepth = new Map<number, number>();
+    for (let i = 0; i < maxDepthQuery.numRecords; i++) {
+      const utid = maxDepthQuery.columns[0].longValues![i] as number;
+      const maxDepth = maxDepthQuery.columns[1].longValues![i] as number;
+      utidToMaxDepth.set(utid, maxDepth);
+    }
+
+    const threadQuery = await engine.query(
+        'select utid, tid, upid, pid, thread.name, process.name ' +
+        'from thread inner join process using(upid)');
+
+    const upidToUuid = new Map<number, string>();
+    const addSummaryTrackActions: DeferredAction[] = [];
+    const addTrackGroupActions: DeferredAction[] = [];
     for (let i = 0; i < threadQuery.numRecords; i++) {
-      const upid = threadQuery.columns[0].longValues![i];
-      const utid = threadQuery.columns[1].longValues![i];
-      const threadId = threadQuery.columns[2].longValues![i];
-      let threadName = threadQuery.columns[3].stringValues![i];
-      threadName += `[${threadId}]`;
-      const maxDepth = threadQuery.columns[4].longValues![i];
+      const utid = threadQuery.columns[0].longValues![i] as number;
+
+      const maxDepth = utidToMaxDepth.get(utid);
+      if (maxDepth === undefined) {
+        // This thread does not have stackable slices.
+        continue;
+      }
+
+      const tid = threadQuery.columns[1].longValues![i] as number;
+      const upid = threadQuery.columns[2].longValues![i] as number;
+      const pid = threadQuery.columns[3].longValues![i] as number;
+      const threadName = threadQuery.columns[4].stringValues![i];
+      const processName = threadQuery.columns[5].stringValues![i];
+
+      let pUuid = upidToUuid.get(upid);
+      if (pUuid === undefined) {
+        pUuid = uuidv4();
+        const summaryTrackId = uuidv4();
+        upidToUuid.set(upid, pUuid);
+        addSummaryTrackActions.push(Actions.addTrack({
+          id: summaryTrackId,
+          engineId: this.engineId,
+          kind: PROCESS_SUMMARY_TRACK,
+          name: `${pid} summary`,
+          config: {upid, pid, maxDepth, utid},
+        }));
+        addTrackGroupActions.push(Actions.addTrackGroup({
+          engineId: this.engineId,
+          summaryTrackId,
+          name: `${processName} ${pid}`,
+          id: pUuid,
+          collapsed: true,
+        }));
+      }
+
       addToTrackActions.push(Actions.addTrack({
         engineId: this.engineId,
         kind: SLICE_TRACK_KIND,
-        name: threadName,
-        config: {
-          upid: upid as number,
-          utid: utid as number,
-          maxDepth: maxDepth as number,
-        }
+        name: threadName + `[${tid}]`,
+        trackGroup: pUuid,
+        config: {upid, utid, maxDepth},
       }));
     }
-    globals.dispatchMultiple(addToTrackActions);
+    const allActions =
+        addSummaryTrackActions.concat(addTrackGroupActions, addToTrackActions);
+    globals.dispatchMultiple(allActions);
   }
 
   private async listThreads() {