Support perfetto transition trace parsing in Winscope
Bug: 276431795
Test: npm run test:presubmit
Change-Id: If02ef76e93cf809dbd165502fd6bfb91be5d74cb
diff --git a/tools/winscope/src/flickerlib/common.js b/tools/winscope/src/flickerlib/common.js
index 1bb79c9..397dde8 100644
--- a/tools/winscope/src/flickerlib/common.js
+++ b/tools/winscope/src/flickerlib/common.js
@@ -103,6 +103,7 @@
const RectF = require('flickerlib/flicker').android.tools.common.datatypes.RectF;
const WindowingMode = require('flickerlib/flicker').android.tools.common.traces.wm.WindowingMode;
const CrossPlatform = require('flickerlib/flicker').android.tools.common.CrossPlatform;
+const Timestamp = require('flickerlib/flicker').android.tools.common.Timestamp;
const TimestampFactory = require('flickerlib/flicker').android.tools.common.TimestampFactory;
const NoCache = require('flickerlib/flicker').android.tools.common.NoCache;
@@ -341,6 +342,7 @@
Rotation,
WindowingMode,
CrossPlatform,
+ Timestamp,
TimestampFactory,
NoCache,
// Service
diff --git a/tools/winscope/src/parsers/perfetto/parser_factory.ts b/tools/winscope/src/parsers/perfetto/parser_factory.ts
index 8f64522..9159e53 100644
--- a/tools/winscope/src/parsers/perfetto/parser_factory.ts
+++ b/tools/winscope/src/parsers/perfetto/parser_factory.ts
@@ -20,11 +20,16 @@
import {Parser} from 'trace/parser';
import {TraceFile} from 'trace/trace_file';
import {initWasm, resetEngineWorker, WasmEngineProxy} from 'trace_processor/wasm_engine_proxy';
+import {ParserTransitions} from './parser_transitions';
import {ParserSurfaceFlinger} from './parser_surface_flinger';
import {ParserTransactions} from './parser_transactions';
export class ParserFactory {
- private static readonly PARSERS = [ParserSurfaceFlinger, ParserTransactions];
+ private static readonly PARSERS = [
+ ParserSurfaceFlinger,
+ ParserTransactions,
+ ParserTransitions,
+ ];
private static readonly CHUNK_SIZE_BYTES = 50 * 1024 * 1024;
private static traceProcessor?: WasmEngineProxy;
diff --git a/tools/winscope/src/parsers/perfetto/parser_transitions.ts b/tools/winscope/src/parsers/perfetto/parser_transitions.ts
new file mode 100644
index 0000000..83f6b68
--- /dev/null
+++ b/tools/winscope/src/parsers/perfetto/parser_transitions.ts
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import {assertDefined} from 'common/assert_utils';
+import {TimestampType} from 'common/time';
+import {
+ CrossPlatform,
+ ShellTransitionData,
+ Timestamp,
+ Transition,
+ TransitionChange,
+ TransitionType,
+ WmTransitionData,
+} from 'flickerlib/common';
+import {LayerTraceEntry} from 'flickerlib/layers/LayerTraceEntry';
+import {TraceFile} from 'trace/trace_file';
+import {TraceType} from 'trace/trace_type';
+import {WasmEngineProxy} from 'trace_processor/wasm_engine_proxy';
+import {AbstractParser} from './abstract_parser';
+import {FakeProto, FakeProtoBuilder} from './fake_proto_builder';
+
+export class ParserTransitions extends AbstractParser<Transition> {
+ constructor(traceFile: TraceFile, traceProcessor: WasmEngineProxy) {
+ super(traceFile, traceProcessor);
+ }
+
+ override getTraceType(): TraceType {
+ return TraceType.TRANSITION;
+ }
+
+ override async getEntry(index: number, timestampType: TimestampType): Promise<LayerTraceEntry> {
+ const transitionProto = await this.queryTransition(index);
+
+ if (this.handlerIdToName === undefined) {
+ const handlers = await this.queryHandlers();
+ this.handlerIdToName = {};
+ handlers.forEach((it) => (assertDefined(this.handlerIdToName)[it.id] = it.name));
+ }
+
+ return new Transition(
+ Number(transitionProto.id),
+ new WmTransitionData(
+ this.toTimestamp(transitionProto.createTimeNs),
+ this.toTimestamp(transitionProto.sendTimeNs),
+ this.toTimestamp(transitionProto.wmAbortTimeNs),
+ this.toTimestamp(transitionProto.finishTimeNs),
+ this.toTimestamp(transitionProto.startingWindowRemoveTimeNs),
+ transitionProto.startTransactionId.toString(),
+ transitionProto.finishTransactionId.toString(),
+ TransitionType.Companion.fromInt(Number(transitionProto.type)),
+ transitionProto.targets.map(
+ (it: any) =>
+ new TransitionChange(
+ TransitionType.Companion.fromInt(Number(it.mode)),
+ Number(it.layerId),
+ Number(it.windowId)
+ )
+ )
+ ),
+ new ShellTransitionData(
+ this.toTimestamp(transitionProto.dispatchTimeNs),
+ this.toTimestamp(transitionProto.mergeRequestTimeNs),
+ this.toTimestamp(transitionProto.mergeTimeNs),
+ this.toTimestamp(transitionProto.shellAbortTimeNs),
+ this.handlerIdToName[Number(transitionProto.handler)],
+ transitionProto.mergeTarget ? Number(transitionProto.mergeTarget) : null
+ )
+ );
+ }
+
+ private toTimestamp(n: BigInt | undefined | null): Timestamp | null {
+ if (n === undefined || n === null) {
+ return null;
+ }
+
+ const realToElapsedTimeOffsetNs = assertDefined(this.realToElapsedTimeOffsetNs);
+ const unixNs = BigInt(n.toString()) + realToElapsedTimeOffsetNs;
+
+ return CrossPlatform.timestamp.fromString(n.toString(), null, unixNs.toString());
+ }
+
+ protected override getTableName(): string {
+ return 'window_manager_shell_transitions';
+ }
+
+ private async queryTransition(index: number): Promise<FakeProto> {
+ const protoBuilder = new FakeProtoBuilder();
+
+ const sql = `
+ SELECT
+ transitions.transition_id,
+ args.key,
+ args.value_type,
+ args.int_value,
+ args.string_value,
+ args.real_value
+ FROM
+ window_manager_shell_transitions as transitions
+ INNER JOIN args ON transitions.arg_set_id = args.arg_set_id
+ WHERE transitions.id = ${index};
+ `;
+ const result = await this.traceProcessor.query(sql).waitAllRows();
+
+ for (const it = result.iter({}); it.valid(); it.next()) {
+ protoBuilder.addArg(
+ it.get('key') as string,
+ it.get('value_type') as string,
+ it.get('int_value') as bigint | undefined,
+ it.get('real_value') as number | undefined,
+ it.get('string_value') as string | undefined
+ );
+ }
+
+ return protoBuilder.build();
+ }
+
+ private async queryHandlers(): Promise<TransitionHandler[]> {
+ const sql = 'SELECT handler_id, handler_name FROM window_manager_shell_transition_handlers;';
+ const result = await this.traceProcessor.query(sql).waitAllRows();
+
+ const handlers: TransitionHandler[] = [];
+ for (const it = result.iter({}); it.valid(); it.next()) {
+ handlers.push({
+ id: it.get('handler_id') as number,
+ name: it.get('handler_name') as string,
+ });
+ }
+
+ return handlers;
+ }
+
+ private handlerIdToName: {[id: number]: string} | undefined = undefined;
+}
+
+interface TransitionHandler {
+ id: number;
+ name: string;
+}
diff --git a/tools/winscope/src/parsers/perfetto/parser_transitions_test.ts b/tools/winscope/src/parsers/perfetto/parser_transitions_test.ts
new file mode 100644
index 0000000..02113d5
--- /dev/null
+++ b/tools/winscope/src/parsers/perfetto/parser_transitions_test.ts
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import {assertDefined} from 'common/assert_utils';
+import {ElapsedTimestamp, RealTimestamp, TimestampType} from 'common/time';
+import {Transition, TransitionType} from 'flickerlib/common';
+import {TraceBuilder} from 'test/unit/trace_builder';
+import {UnitTestUtils} from 'test/unit/utils';
+import {Parser} from 'trace/parser';
+import {Trace} from 'trace/trace';
+import {TraceType} from 'trace/trace_type';
+
+describe('Perfetto ParserTransitions', () => {
+ describe('valid trace', () => {
+ let parser: Parser<Transition>;
+ let trace: Trace<Transition>;
+
+ beforeAll(async () => {
+ parser = await UnitTestUtils.getPerfettoParser(
+ TraceType.TRANSITION,
+ 'traces/perfetto/shell_transitions_trace.perfetto-trace'
+ );
+ trace = new TraceBuilder().setType(TraceType.TRANSITION).setParser(parser).build();
+ });
+
+ it('has expected trace type', () => {
+ expect(parser.getTraceType()).toEqual(TraceType.TRANSITION);
+ });
+
+ it('provides elapsed timestamps', () => {
+ const expected = [
+ new ElapsedTimestamp(479602824452n),
+ new ElapsedTimestamp(480676958445n),
+ new ElapsedTimestamp(487195167758n),
+ ];
+ const actual = assertDefined(parser.getTimestamps(TimestampType.ELAPSED)).slice(0, 3);
+ expect(actual).toEqual(expected);
+ });
+
+ it('provides real timestamps', () => {
+ const expected = [
+ new RealTimestamp(1700573903102738218n),
+ new RealTimestamp(1700573904176872211n),
+ new RealTimestamp(1700573910695081524n),
+ ];
+ const actual = assertDefined(parser.getTimestamps(TimestampType.REAL)).slice(0, 3);
+ expect(actual).toEqual(expected);
+ });
+
+ it('decodes transition properties', async () => {
+ const entry = await parser.getEntry(0, TimestampType.REAL);
+
+ expect(entry.id).toEqual(32);
+ expect(entry.createTime.elapsedNanos.toString()).toEqual('479583450794');
+ expect(entry.sendTime.elapsedNanos.toString()).toEqual('479596405791');
+ expect(entry.abortTime).toEqual(null);
+ expect(entry.finishTime.elapsedNanos.toString()).toEqual('480124777862');
+ expect(entry.startingWindowRemoveTime.elapsedNanos.toString()).toEqual('479719652658');
+ expect(entry.dispatchTime.elapsedNanos.toString()).toEqual('479602824452');
+ expect(entry.mergeRequestTime).toEqual(null);
+ expect(entry.mergeTime).toEqual(null);
+ expect(entry.shellAbortTime).toEqual(null);
+ expect(entry.startTransactionId.toString()).toEqual('5811090758076');
+ expect(entry.finishTransactionId.toString()).toEqual('5811090758077');
+ expect(entry.type).toEqual(TransitionType.OPEN);
+ expect(entry.mergeTarget).toEqual(null);
+ expect(entry.handler).toEqual('com.android.wm.shell.transition.DefaultMixedHandler');
+ expect(entry.merged).toEqual(false);
+ expect(entry.played).toEqual(true);
+ expect(entry.aborted).toEqual(false);
+ expect(entry.changes.length).toEqual(2);
+ expect(entry.changes[0].layerId).toEqual(398);
+ expect(entry.changes[1].layerId).toEqual(47);
+ expect(entry.changes[0].transitMode).toEqual(TransitionType.TO_FRONT);
+ expect(entry.changes[1].transitMode).toEqual(TransitionType.TO_BACK);
+ });
+ });
+});
diff --git a/tools/winscope/src/test/fixtures/traces/perfetto/shell_transitions_trace.perfetto-trace b/tools/winscope/src/test/fixtures/traces/perfetto/shell_transitions_trace.perfetto-trace
new file mode 100644
index 0000000..3625318
--- /dev/null
+++ b/tools/winscope/src/test/fixtures/traces/perfetto/shell_transitions_trace.perfetto-trace
Binary files differ