From a735b202f7f52fd69f297c621db44269593f08e7 Mon Sep 17 00:00:00 2001 From: Pablo Gamito Date: Mon, 22 May 2023 10:24:37 +0000 Subject: [PATCH] Add transition traces parser Bug: 277181336 Test: npm run test:all Change-Id: I7eb981367191c47d3b50b31ee66f147ff1d6923c --- tools/winscope/src/app/trace_info.ts | 6 + tools/winscope/src/app/trace_pipeline.ts | 16 +++ tools/winscope/src/parsers/abstract_parser.ts | 2 +- .../src/parsers/abstract_traces_parser.ts | 75 ++++++++++++ .../src/parsers/traces_parser_transitions.ts | 111 ++++++++++++++++++ .../parsers/traces_parser_transitions_test.ts | 68 +++++++++++ tools/winscope/src/trace/trace_type.ts | 5 +- tools/winscope/src/trace/traces.ts | 4 + 8 files changed, 284 insertions(+), 3 deletions(-) create mode 100644 tools/winscope/src/parsers/abstract_traces_parser.ts create mode 100644 tools/winscope/src/parsers/traces_parser_transitions.ts create mode 100644 tools/winscope/src/parsers/traces_parser_transitions_test.ts diff --git a/tools/winscope/src/app/trace_info.ts b/tools/winscope/src/app/trace_info.ts index 1fc02427f..5cad268bd 100644 --- a/tools/winscope/src/app/trace_info.ts +++ b/tools/winscope/src/app/trace_info.ts @@ -155,4 +155,10 @@ export const TRACE_INFO: TraceInfoMap = { color: '#EC407A', downloadArchiveDir: 'transition', }, + [TraceType.TRANSITION]: { + name: 'Transitions', + icon: TRANSITION_ICON, + color: '#EC407A', + downloadArchiveDir: 'transition', + }, }; diff --git a/tools/winscope/src/app/trace_pipeline.ts b/tools/winscope/src/app/trace_pipeline.ts index eacb15ca4..010541573 100644 --- a/tools/winscope/src/app/trace_pipeline.ts +++ b/tools/winscope/src/app/trace_pipeline.ts @@ -16,6 +16,7 @@ import {FunctionUtils, OnProgressUpdateType} from 'common/function_utils'; import {ParserError, ParserFactory} from 'parsers/parser_factory'; +import {TracesParserTransitions} from 'parsers/traces_parser_transitions'; import {FrameMapper} from 'trace/frame_mapper'; import {LoadedTrace} from 'trace/loaded_trace'; import {Parser} from 'trace/parser'; @@ -42,10 +43,23 @@ class TracePipeline { ); this.parsers = parsers.map((it) => it.parser); + const tracesParser = new TracesParserTransitions(this.parsers); + if (tracesParser.canProvideEntries()) { + this.parsers.push(tracesParser); + } + for (const parser of parsers) { this.files.set(parser.parser.getTraceType(), parser.file); } + if (this.parsers.some((it) => it.getTraceType() === TraceType.TRANSITION)) { + this.parsers = this.parsers.filter( + (it) => + it.getTraceType() !== TraceType.WM_TRANSITION && + it.getTraceType() !== TraceType.SHELL_TRANSITION + ); + } + return parserErrors; } @@ -69,8 +83,10 @@ class TracePipeline { this.traces = new Traces(); this.parsers.forEach((parser) => { const trace = Trace.newUninitializedTrace(parser); + trace.init(commonTimestampType); this.traces?.setTrace(parser.getTraceType(), trace); }); + new FrameMapper(this.traces).computeMapping(); } diff --git a/tools/winscope/src/parsers/abstract_parser.ts b/tools/winscope/src/parsers/abstract_parser.ts index f35950a1a..91e2dea72 100644 --- a/tools/winscope/src/parsers/abstract_parser.ts +++ b/tools/winscope/src/parsers/abstract_parser.ts @@ -42,7 +42,7 @@ abstract class AbstractParser implements Parser { throw TypeError("buffer doesn't contain expected magic number"); } } - + this.decodedEntries = this.decodeTrace(traceBuffer).map((it) => this.addDefaultProtoFields(it)); for (const type of [TimestampType.ELAPSED, TimestampType.REAL]) { diff --git a/tools/winscope/src/parsers/abstract_traces_parser.ts b/tools/winscope/src/parsers/abstract_traces_parser.ts new file mode 100644 index 000000000..66a8b1f4a --- /dev/null +++ b/tools/winscope/src/parsers/abstract_traces_parser.ts @@ -0,0 +1,75 @@ +/* + * 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 {Parser} from 'trace/parser'; +import {Timestamp, TimestampType} from 'trace/timestamp'; +import {TraceFile} from 'trace/trace_file'; +import {TraceType} from 'trace/trace_type'; + +export abstract class AbstractTracesParser implements Parser { + constructor(readonly parsers: Array>) {} + + getTraceFile(): TraceFile { + throw new Error('Method not implemented.'); + } + + abstract canProvideEntries(): boolean; + + abstract getDescriptors(): string[]; + + abstract getTraceType(): TraceType; + + abstract getEntry(index: number, timestampType: TimestampType): T; + + abstract getLengthEntries(): number; + + getTimestamps(type: TimestampType): Timestamp[] | undefined { + this.setTimestamps(); + return this.timestamps.get(type); + } + + private setTimestamps() { + if (this.timestampsSet) { + return; + } + + for (const type of [TimestampType.ELAPSED, TimestampType.REAL]) { + const timestamps: Timestamp[] = []; + let areTimestampsValid = true; + + for (let index = 0; index < this.getLengthEntries(); index++) { + const entry = this.getEntry(index, type); + const timestamp = this.getTimestamp(type, entry); + if (timestamp === undefined) { + areTimestampsValid = false; + break; + } + timestamps.push(timestamp); + } + + if (areTimestampsValid) { + this.timestamps.set(type, timestamps); + } + } + + this.timestampsSet = true; + } + + abstract getTimestamp(type: TimestampType, decodedEntry: any): undefined | Timestamp; + + private timestampsSet: boolean = false; + private timestamps: Map = new Map(); +} diff --git a/tools/winscope/src/parsers/traces_parser_transitions.ts b/tools/winscope/src/parsers/traces_parser_transitions.ts new file mode 100644 index 000000000..b751f4eff --- /dev/null +++ b/tools/winscope/src/parsers/traces_parser_transitions.ts @@ -0,0 +1,111 @@ +/* + * Copyright 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 {Transition, TransitionsTrace} from 'trace/flickerlib/common'; +import {Parser} from 'trace/parser'; +import {Timestamp, TimestampType} from 'trace/timestamp'; +import {TraceType} from 'trace/trace_type'; +import {AbstractTracesParser} from './abstract_traces_parser'; + +export class TracesParserTransitions extends AbstractTracesParser { + private readonly wmTransitionTrace: Parser | undefined; + private readonly shellTransitionTrace: Parser | undefined; + private readonly descriptors: string[]; + + constructor(parsers: Array>) { + super(parsers); + + const wmTransitionTraces = this.parsers.filter( + (it) => it.getTraceType() === TraceType.WM_TRANSITION + ); + if (wmTransitionTraces.length > 0) { + this.wmTransitionTrace = wmTransitionTraces[0]; + } + const shellTransitionTraces = this.parsers.filter( + (it) => it.getTraceType() === TraceType.SHELL_TRANSITION + ); + if (shellTransitionTraces.length > 0) { + this.shellTransitionTrace = shellTransitionTraces[0]; + } + if (this.wmTransitionTrace !== undefined && this.shellTransitionTrace !== undefined) { + this.descriptors = this.wmTransitionTrace + .getDescriptors() + .concat(this.shellTransitionTrace.getDescriptors()); + } else { + this.descriptors = []; + } + } + + override canProvideEntries(): boolean { + return this.wmTransitionTrace !== undefined && this.shellTransitionTrace !== undefined; + } + + getLengthEntries(): number { + return this.getDecodedEntries().length; + } + + getEntry(index: number, timestampType: TimestampType): Transition { + return this.getDecodedEntries()[index]; + } + + private decodedEntries: Transition[] | undefined; + getDecodedEntries(): Transition[] { + if (this.decodedEntries === undefined) { + if (this.wmTransitionTrace === undefined) { + throw new Error('Missing WM Transition trace'); + } + + if (this.shellTransitionTrace === undefined) { + throw new Error('Missing Shell Transition trace'); + } + + const wmTransitionEntries: Transition[] = []; + for (let index = 0; index < this.wmTransitionTrace.getLengthEntries(); index++) { + wmTransitionEntries.push(this.wmTransitionTrace.getEntry(index, TimestampType.REAL)); + } + + const shellTransitionEntries: Transition[] = []; + for (let index = 0; index < this.shellTransitionTrace.getLengthEntries(); index++) { + shellTransitionEntries.push(this.shellTransitionTrace.getEntry(index, TimestampType.REAL)); + } + + const transitionsTrace = new TransitionsTrace( + wmTransitionEntries.concat(shellTransitionEntries) + ); + + this.decodedEntries = transitionsTrace.asCompressed().entries as Transition[]; + } + + return this.decodedEntries; + } + + override getDescriptors(): string[] { + return this.descriptors; + } + + getTraceType(): TraceType { + return TraceType.TRANSITION; + } + + override getTimestamp(type: TimestampType, transition: Transition): undefined | Timestamp { + if (type === TimestampType.ELAPSED) { + return new Timestamp(type, BigInt(transition.timestamp.elapsedNanos.toString())); + } else if (type === TimestampType.REAL) { + return new Timestamp(type, BigInt(transition.timestamp.unixNanos.toString())); + } + return undefined; + } +} diff --git a/tools/winscope/src/parsers/traces_parser_transitions_test.ts b/tools/winscope/src/parsers/traces_parser_transitions_test.ts new file mode 100644 index 000000000..4e811c51f --- /dev/null +++ b/tools/winscope/src/parsers/traces_parser_transitions_test.ts @@ -0,0 +1,68 @@ +/* + * 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 {UnitTestUtils} from 'test/unit/utils'; +import {Transition} from 'trace/flickerlib/common'; +import {Parser} from 'trace/parser'; +import {Timestamp, TimestampType} from 'trace/timestamp'; +import {TraceType} from 'trace/trace_type'; +import {TracesParserTransitions} from './traces_parser_transitions'; + +describe('ParserTransitions', () => { + let parser: Parser; + + beforeAll(async () => { + const wmSideParser = await UnitTestUtils.getParser( + 'traces/elapsed_and_real_timestamp/wm_transition_trace.pb' + ); + const shellSideParser = await UnitTestUtils.getParser( + 'traces/elapsed_and_real_timestamp/shell_transition_trace.pb' + ); + + parser = new TracesParserTransitions([wmSideParser, shellSideParser]); + }); + + it('has expected trace type', () => { + expect(parser.getTraceType()).toEqual(TraceType.TRANSITION); + }); + + it('provides elapsed timestamps', () => { + const timestamps = parser.getTimestamps(TimestampType.ELAPSED)!; + + expect(timestamps.length).toEqual(4); + + const expected = [ + new Timestamp(TimestampType.ELAPSED, 57649586217344n), + new Timestamp(TimestampType.ELAPSED, 57649691956439n), + new Timestamp(TimestampType.ELAPSED, 57651263812071n), + ]; + expect(timestamps.slice(0, 3)).toEqual(expected); + }); + + it('provides real timestamps', () => { + const expected = [ + new Timestamp(TimestampType.REAL, 1683188477542869667n), + new Timestamp(TimestampType.REAL, 1683188477648608762n), + new Timestamp(TimestampType.REAL, 1683188479220464394n), + ]; + + const timestamps = parser.getTimestamps(TimestampType.REAL)!; + + expect(timestamps.length).toEqual(4); + + expect(timestamps.slice(0, 3)).toEqual(expected); + }); +}); diff --git a/tools/winscope/src/trace/trace_type.ts b/tools/winscope/src/trace/trace_type.ts index 6175c1205..9d1277f51 100644 --- a/tools/winscope/src/trace/trace_type.ts +++ b/tools/winscope/src/trace/trace_type.ts @@ -13,8 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import {Event} from 'trace/flickerlib/common'; -import {Transition} from './flickerlib/common'; +import {Event, Transition} from 'trace/flickerlib/common'; import {LayerTraceEntry} from './flickerlib/layers/LayerTraceEntry'; import {WindowManagerState} from './flickerlib/windows/WindowManagerState'; import {LogMessage} from './protolog'; @@ -38,6 +37,7 @@ export enum TraceType { EVENT_LOG, WM_TRANSITION, SHELL_TRANSITION, + TRANSITION, TAG, ERROR, TEST_TRACE_STRING, @@ -62,6 +62,7 @@ export interface TraceEntryTypeMap { [TraceType.EVENT_LOG]: Event; [TraceType.WM_TRANSITION]: object; [TraceType.SHELL_TRANSITION]: object; + [TraceType.TRANSITION]: Transition; [TraceType.TAG]: object; [TraceType.ERROR]: object; [TraceType.TEST_TRACE_STRING]: string; diff --git a/tools/winscope/src/trace/traces.ts b/tools/winscope/src/trace/traces.ts index cfae651a6..eabdad4a8 100644 --- a/tools/winscope/src/trace/traces.ts +++ b/tools/winscope/src/trace/traces.ts @@ -30,6 +30,10 @@ export class Traces { return this.traces.get(type) as Trace | undefined; } + deleteTrace(type: T) { + this.traces.delete(type); + } + sliceTime(start?: Timestamp, end?: Timestamp): Traces { const slice = new Traces(); this.traces.forEach((trace, type) => {