Add performance metrics.

measureUserAgentSpecificMemory can only be used if Winscope is cross-origin isolated. This requirement is incompatible with Winscope cross-tool integration (e.g. opening from Buganizer/ABT).
To collect at least some performance metrics from the field, we use the
the deprecated memory property.

Bug: 392658517
Test: npm run test:unit:ci
Change-Id: Icd1d6323373321ca2ad1ded29c7fb84cbf138c6d
diff --git a/tools/winscope/src/app/components/trace_view_component.ts b/tools/winscope/src/app/components/trace_view_component.ts
index 17ff79b..d532892 100644
--- a/tools/winscope/src/app/components/trace_view_component.ts
+++ b/tools/winscope/src/app/components/trace_view_component.ts
@@ -467,8 +467,8 @@
       this.currentActiveTab.view.htmlElement.style.display = 'none';
     }
 
-    const firstSwitch = tab.addedToDom;
-    if (!tab.addedToDom) {
+    const firstSwitch = !tab.addedToDom;
+    if (firstSwitch) {
       // Workaround for b/255966194:
       // make sure that the first time a tab content is rendered
       // (added to the DOM) it has style.display == "". This fixes the
@@ -493,6 +493,9 @@
         firstSwitch,
       );
     }
+    if (firstSwitch) {
+      Analytics.Memory.logUsage('tab_initialized', {firstSwitch});
+    }
   }
 
   private validateFilterPresetName(
diff --git a/tools/winscope/src/app/mediator.ts b/tools/winscope/src/app/mediator.ts
index 0112d6c..b70e2f9 100644
--- a/tools/winscope/src/app/mediator.ts
+++ b/tools/winscope/src/app/mediator.ts
@@ -385,26 +385,27 @@
 
     for (const viewer of viewers) {
       const type = viewer.getTraces().at(0)?.type;
-      const traceName = type !== undefined ? TRACE_INFO[type].name : 'Unknown';
+      const traceType = type !== undefined ? TRACE_INFO[type].name : 'Unknown';
       try {
         const startTimeMs = Date.now();
         await viewer.onWinscopeEvent(event);
         if (source !== undefined) {
           Analytics.Loading.logViewerInitializationTime(
-            traceName,
+            traceType,
             source,
             Date.now() - startTimeMs,
           );
+          Analytics.Memory.logUsage('viewer_initialized', {traceType});
         }
         Analytics.Navigation.logTimePropagated(
-          traceName,
+          traceType,
           Date.now() - startTimeMs,
         );
       } catch (e) {
         console.error(e);
         warnings.push(
           new CannotVisualizeTraceEntry(
-            `Cannot parse entry for ${traceName} trace: Trace may be corrupted.`,
+            `Cannot parse entry for ${traceType} trace: Trace may be corrupted.`,
           ),
         );
       }
@@ -431,6 +432,7 @@
     if (warnings.length > 0) {
       warnings.forEach((w) => UserNotifier.add(w));
     }
+    Analytics.Memory.logUsage('time_propagated');
   }
 
   private isViewerVisible(viewer: Viewer): boolean {
@@ -503,6 +505,7 @@
       const startTimeMs = Date.now();
       await this.tracePipeline.buildTraces();
       Analytics.Loading.logFrameMapBuildTime(Date.now() - startTimeMs);
+      Analytics.Memory.logUsage('frame_map_built');
       this.currentProgressListener?.onOperationFinished(true);
     } catch (e) {
       UserNotifier.add(new IncompleteFrameMapping((e as Error).message));
@@ -549,6 +552,7 @@
     // at this stage, while the "initializing UI" progress message is still being displayed.
     // The viewers initialization is triggered by sending them a "trace position update".
     await this.propagateTracePosition(initialPosition, true, source);
+    Analytics.Memory.logUsage('viewers_initialized');
 
     this.focusedTabView = this.viewers
       .find((v) => v.getViews()[0].type === ViewType.TRACE_TAB)
diff --git a/tools/winscope/src/app/trace_pipeline.ts b/tools/winscope/src/app/trace_pipeline.ts
index 15fc298..a51fe94 100644
--- a/tools/winscope/src/app/trace_pipeline.ts
+++ b/tools/winscope/src/app/trace_pipeline.ts
@@ -242,6 +242,7 @@
       source,
       Date.now() - startTimeMs,
     );
+    Analytics.Memory.logUsage('legacy_files_parsed');
 
     let perfettoParsers: FileAndParsers | undefined;
 
@@ -257,6 +258,7 @@
         source,
         Date.now() - startTimeMs,
       );
+      Analytics.Memory.logUsage('perfetto_files_parsed');
       perfettoParsers = new FileAndParsers(filterResult.perfetto, parsers);
     }
 
diff --git a/tools/winscope/src/logging/analytics.ts b/tools/winscope/src/logging/analytics.ts
index 0095ad3..ab81534 100644
--- a/tools/winscope/src/logging/analytics.ts
+++ b/tools/winscope/src/logging/analytics.ts
@@ -35,6 +35,7 @@
   private static FRAME_MAP_ERROR = 'frame_map_error';
   private static GLOBAL_EXCEPTION = 'global_exception';
   private static HIERARCHY_SETTINGS = 'hierarchy_settings';
+  private static JS_MEMORY_USAGE = 'js_memory_usage';
   private static NAVIGATION_ZOOM_EVENT = 'navigation_zoom';
   private static PROPERTIES_SETTINGS = 'properties_settings';
   private static PROXY_ERROR = 'proxy_error';
@@ -130,6 +131,23 @@
     }
   };
 
+  static Memory = class {
+    static logUsage(stage: string, params: object = {}) {
+      const memory: Memory | undefined = (performance as any).memory;
+      if (memory) {
+        Object.assign(params, {
+          stage,
+          heapSizeLimit: memory.jsHeapSizeLimit,
+          allocatedHeapSize: memory.totalJSHeapSize,
+          fractionAllocated: memory.totalJSHeapSize / memory.jsHeapSizeLimit,
+          usedHeapSize: memory.usedJSHeapSize,
+          fractionUsed: memory.usedJSHeapSize / memory.jsHeapSizeLimit,
+        });
+        Analytics.doLogEvent(Analytics.JS_MEMORY_USAGE, params);
+      }
+    }
+  };
+
   static Navigation = class {
     static logDiffComputationTime(
       component: 'hierarchy' | 'properties',
@@ -362,3 +380,15 @@
     }
   }
 }
+
+// https://developer.mozilla.org/en-US/docs/Web/API/Performance/memory
+// This feature is deprecated so may not work in all browsers. We collect
+// metrics from the field based on the JS heap because the replacement API,
+// measureUserAgentSpecificMemory(), requires the app to be cross-origin
+// isolated, which is incompatible with Winscope cross-tool integration.
+
+interface Memory {
+  jsHeapSizeLimit: number; // Maximum heap size, in bytes, available to the context.
+  totalJSHeapSize: number; // Total allocated heap size, in bytes.
+  usedJSHeapSize: number; // Currently active segment of JS heap, in bytes.
+}
diff --git a/tools/winscope/src/parsers/perfetto/parser_factory.ts b/tools/winscope/src/parsers/perfetto/parser_factory.ts
index 9732b81..5c5794c 100644
--- a/tools/winscope/src/parsers/perfetto/parser_factory.ts
+++ b/tools/winscope/src/parsers/perfetto/parser_factory.ts
@@ -16,6 +16,7 @@
 
 import {ParserTimestampConverter} from 'common/time/timestamp_converter';
 import {UserNotifier} from 'common/user_notifier';
+import {Analytics} from 'logging/analytics';
 import {ProgressListener} from 'messaging/progress_listener';
 import {
   InvalidPerfettoTrace,
@@ -151,6 +152,7 @@
       ingestFtraceInRawTable: false,
       analyzeTraceProtoContent: false,
     });
+    Analytics.Memory.logUsage('tp_initialized');
 
     return traceProcessor;
   }