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;
}