perfetto-ui: Fix js stack overflow

Fix a couple of instances where we used:

someArray.push(...someOtherArray);

to append all elements from someOtherArray to someArray as
this (I now realaise) is an anti-pattern. If someOtherArray is very
large this code will cause a stackoverflow attempting to pass all the
elements as arguments.

Also fix an N+1 query pattern in trace_controller that was slow when
there are many tracks.

Bug: 144595839
Change-Id: I5fa8773af66adb617578795c68da8ccfe1035504
diff --git a/ui/src/controller/globals.ts b/ui/src/controller/globals.ts
index 1dde45d..f8b51b3 100644
--- a/ui/src/controller/globals.ts
+++ b/ui/src/controller/globals.ts
@@ -72,8 +72,14 @@
       if (iter > 100) throw new Error('Controllers are stuck in a livelock');
       const actions = this._queuedActions;
       this._queuedActions = new Array<DeferredAction>();
+
       for (const action of actions) {
-        patches.push(...this.applyAction(action));
+        const originalLength = patches.length;
+        const morePatches = this.applyAction(action);
+        patches.length += morePatches.length;
+        for (let i = 0; i < morePatches.length; ++i) {
+          patches[i + originalLength] = morePatches[i];
+        }
       }
       this._runningControllers = true;
       try {
@@ -109,7 +115,11 @@
           (StateActions as any)[action.type](draft, action.args);
         },
         (morePatches, _) => {
-          patches.push(...morePatches);
+          const originalLength = patches.length;
+          patches.length += morePatches.length;
+          for (let i = 0; i < morePatches.length; ++i) {
+            patches[i + originalLength] = morePatches[i];
+          }
         });
     return patches;
   }
diff --git a/ui/src/controller/trace_controller.ts b/ui/src/controller/trace_controller.ts
index ffa27e1..1afdea3 100644
--- a/ui/src/controller/trace_controller.ts
+++ b/ui/src/controller/trace_controller.ts
@@ -534,9 +534,13 @@
           pid,
           thread.name as threadName,
           process.name as processName,
-          total_dur as totalDur
+          total_dur as totalDur,
+          ifnull(has_sched, false) as hasSched
         from
           thread
+          left join (select utid, count(1), true as has_sched
+              from sched group by utid
+          ) using(utid)
           left join process using(upid)
           left join (select upid, sum(dur) as total_dur
               from sched join thread using(utid)
@@ -558,6 +562,7 @@
            threadName: STR_NULL,
            processName: STR_NULL,
            totalDur: NUM_NULL,
+           hasSched: NUM,
          })) {
       const utid = row.utid;
       const tid = row.tid;
@@ -566,9 +571,7 @@
       const threadName = row.threadName;
       const processName = row.processName;
       const hasSchedEvents = !!row.totalDur;
-      const threadSched =
-          await engine.query(`select count(1) from sched where utid = ${utid}`);
-      const threadHasSched = threadSched.columns[0].longValues![0] > 0;
+      const threadHasSched = !!row.hasSched;
 
       const threadTrack =
           utid === null ? undefined : utidToThreadTrack.get(utid);