Support multiple files for a single trace

Bug: 277181336
Test: npm run build:all && npm run test:all
Change-Id: I7eb981367191c47d3b50b31ee66f147ff1d69431
This commit is contained in:
Pablo Gamito
2023-05-22 12:15:39 +00:00
parent 28c77be00f
commit 99afc1013b
15 changed files with 176 additions and 74 deletions

View File

@@ -331,24 +331,23 @@ export class AppComponent implements TraceDataListener {
private makeActiveTraceFileInfo(view: View): string {
const traceFile = this.tracePipeline
.getLoadedTraceFiles()
.find((file) => file.type === view.dependencies[0])?.traceFile;
.find((file) => file.type === view.dependencies[0]);
if (!traceFile) {
return '';
}
if (!traceFile.parentArchive) {
return traceFile.file.name;
}
return `${traceFile.parentArchive.name} - ${traceFile.file.name}`;
return `${traceFile.type} (${traceFile.descriptors.join(', ')})`;
}
private async makeTraceFilesForDownload(): Promise<File[]> {
return this.tracePipeline.getLoadedTraceFiles().map((trace) => {
const traceType = TRACE_INFO[trace.type].name;
const newName = traceType + '/' + FileUtils.removeDirFromFileName(trace.traceFile.file.name);
return new File([trace.traceFile.file], newName);
const loadedFiles = this.tracePipeline.getLoadedFiles();
return [...loadedFiles.keys()].map((traceType) => {
const file = loadedFiles.get(traceType)!;
const path = TRACE_INFO[traceType].downloadArchiveDir;
const newName = path + '/' + FileUtils.removeDirFromFileName(file.file.name);
return new File([file.file], newName);
});
}
}

View File

@@ -67,7 +67,7 @@ export class SnackBarOpener implements UserNotificationListener {
}
private convertErrorToMessage(error: ParserError): string {
const fileName = error.trace !== undefined ? error.trace.name : '<no file name>';
const fileName = error.trace !== undefined ? error.trace : '<no file name>';
const traceTypeName =
error.traceType !== undefined ? TRACE_INFO[error.traceType].name : '<unknown>';

View File

@@ -25,7 +25,7 @@ import {
import {TRACE_INFO} from 'app/trace_info';
import {TracePipeline} from 'app/trace_pipeline';
import {ProgressListener} from 'interfaces/progress_listener';
import {LoadedTraceFile} from 'trace/trace_file';
import {LoadedTrace} from 'trace/loaded_trace';
import {LoadProgressComponent} from './load_progress_component';
@Component({
@@ -63,7 +63,8 @@ import {LoadProgressComponent} from './load_progress_component';
{{ TRACE_INFO[trace.type].icon }}
</mat-icon>
<p matLine>{{ trace.traceFile.file.name }} ({{ TRACE_INFO[trace.type].name }})</p>
<p matLine>{{ TRACE_INFO[trace.type].name }}</p>
<p matLine *ngFor="let descriptor of trace.descriptors">{{ descriptor }}</p>
<button color="primary" mat-icon-button (click)="onRemoveTrace($event, trace)">
<mat-icon>close</mat-icon>
@@ -235,7 +236,7 @@ export class UploadTracesComponent implements ProgressListener {
this.filesUploaded.emit(Array.from(droppedFiles));
}
onRemoveTrace(event: MouseEvent, trace: LoadedTraceFile) {
onRemoveTrace(event: MouseEvent, trace: LoadedTrace) {
event.preventDefault();
event.stopPropagation();
this.tracePipeline.removeTraceFile(trace.type);

View File

@@ -36,6 +36,7 @@ interface TraceInfoMap {
name: string;
icon: string;
color: string;
downloadArchiveDir: string;
};
}
@@ -44,95 +45,114 @@ export const TRACE_INFO: TraceInfoMap = {
name: 'Accessibility',
icon: ACCESSIBILITY_ICON,
color: '#FF63B8',
downloadArchiveDir: 'accessibility',
},
[TraceType.WINDOW_MANAGER]: {
name: 'Window Manager',
icon: WINDOW_MANAGER_ICON,
color: '#AF5CF7',
downloadArchiveDir: 'wm',
},
[TraceType.SURFACE_FLINGER]: {
name: 'Surface Flinger',
icon: SURFACE_FLINGER_ICON,
color: '#4ECDE6',
downloadArchiveDir: 'sf',
},
[TraceType.SCREEN_RECORDING]: {
name: 'Screen Recording',
icon: SCREEN_RECORDING_ICON,
color: '#8A9CF9',
downloadArchiveDir: '',
},
[TraceType.TRANSACTIONS]: {
name: 'Transactions',
icon: TRANSACTION_ICON,
color: '#5BB974',
downloadArchiveDir: 'sf',
},
[TraceType.TRANSACTIONS_LEGACY]: {
name: 'Transactions Legacy',
icon: TRANSACTION_ICON,
color: '#5BB974',
downloadArchiveDir: 'sf',
},
[TraceType.WAYLAND]: {
name: 'Wayland',
icon: WAYLAND_ICON,
color: '#FDC274',
downloadArchiveDir: 'wayland',
},
[TraceType.WAYLAND_DUMP]: {
name: 'Wayland Dump',
icon: WAYLAND_ICON,
color: '#D01884',
downloadArchiveDir: 'wayland',
},
[TraceType.PROTO_LOG]: {
name: 'ProtoLog',
icon: PROTO_LOG_ICON,
color: '#40A58A',
downloadArchiveDir: 'protolog',
},
[TraceType.SYSTEM_UI]: {
name: 'System UI',
icon: SYSTEM_UI_ICON,
color: '#7A86FF',
downloadArchiveDir: 'sysui',
},
[TraceType.LAUNCHER]: {
name: 'Launcher',
icon: LAUNCHER_ICON,
color: '#137333',
downloadArchiveDir: 'launcher',
},
[TraceType.INPUT_METHOD_CLIENTS]: {
name: 'IME Clients',
icon: IME_ICON,
color: '#FA903E',
downloadArchiveDir: 'ime',
},
[TraceType.INPUT_METHOD_SERVICE]: {
name: 'IME Service',
icon: IME_ICON,
color: '#F29900',
downloadArchiveDir: 'ime',
},
[TraceType.INPUT_METHOD_MANAGER_SERVICE]: {
name: 'IME Manager Service',
icon: IME_ICON,
color: '#D93025',
downloadArchiveDir: 'ime',
},
[TraceType.TAG]: {
name: 'Tag',
icon: TAG_ICON,
color: '#4575B4',
downloadArchiveDir: '',
},
[TraceType.ERROR]: {
name: 'Error',
icon: TRACE_ERROR_ICON,
color: '#D73027',
downloadArchiveDir: '',
},
[TraceType.EVENT_LOG]: {
name: 'Event Log',
icon: EVENT_LOG_ICON,
color: '#fdd663',
downloadArchiveDir: 'eventlog',
},
[TraceType.WM_TRANSITION]: {
name: 'WM Transitions',
icon: TRANSITION_ICON,
color: '#EC407A',
downloadArchiveDir: 'transition',
},
[TraceType.SHELL_TRANSITION]: {
name: 'Shell Transitions',
icon: TRANSITION_ICON,
color: '#EC407A',
downloadArchiveDir: 'transition',
},
};

View File

@@ -17,16 +17,18 @@
import {FunctionUtils, OnProgressUpdateType} from 'common/function_utils';
import {ParserError, ParserFactory} from 'parsers/parser_factory';
import {FrameMapper} from 'trace/frame_mapper';
import {LoadedTrace} from 'trace/loaded_trace';
import {Parser} from 'trace/parser';
import {TimestampType} from 'trace/timestamp';
import {Trace} from 'trace/trace';
import {Traces} from 'trace/traces';
import {LoadedTraceFile, TraceFile} from 'trace/trace_file';
import {TraceFile} from 'trace/trace_file';
import {TraceType} from 'trace/trace_type';
class TracePipeline {
private parserFactory = new ParserFactory();
private parsers: Array<Parser<object>> = [];
private files = new Map<TraceType, TraceFile>();
private traces?: Traces;
private commonTimestampType?: TimestampType;
@@ -38,7 +40,12 @@ class TracePipeline {
traceFiles,
onLoadProgressUpdate
);
this.parsers = parsers;
this.parsers = parsers.map((it) => it.parser);
for (const parser of parsers) {
this.files.set(parser.parser.getTraceType(), parser.file);
}
return parserErrors;
}
@@ -46,9 +53,13 @@ class TracePipeline {
this.parsers = this.parsers.filter((parser) => parser.getTraceType() !== type);
}
getLoadedTraceFiles(): LoadedTraceFile[] {
getLoadedFiles(): Map<TraceType, TraceFile> {
return this.files;
}
getLoadedTraceFiles(): LoadedTrace[] {
return this.parsers.map(
(parser: Parser<object>) => new LoadedTraceFile(parser.getTraceFile(), parser.getTraceType())
(parser: Parser<object>) => new LoadedTrace(parser.getDescriptors(), parser.getTraceType())
);
}
@@ -57,14 +68,7 @@ class TracePipeline {
this.traces = new Traces();
this.parsers.forEach((parser) => {
const trace = new Trace(
parser.getTraceType(),
parser.getTraceFile(),
undefined,
parser,
commonTimestampType,
{start: 0, end: parser.getLengthEntries()}
);
const trace = Trace.newUninitializedTrace(parser);
this.traces?.setTrace(parser.getTraceType(), trace);
});
new FrameMapper(this.traces).computeMapping();
@@ -88,6 +92,7 @@ class TracePipeline {
this.parsers = [];
this.traces = undefined;
this.commonTimestampType = undefined;
this.files.clear();
}
private getCommonTimestampType(): TimestampType {

View File

@@ -93,7 +93,8 @@ describe('TracePipeline', () => {
const files = tracePipeline.getLoadedTraceFiles();
expect(files.length).toEqual(2);
expect(files[0].traceFile).toBeTruthy();
expect(files[0].descriptors).toBeTruthy();
expect(files[0].descriptors.length).toBeGreaterThan(0);
const actualTraceTypes = new Set(files.map((file) => file.type));
const expectedTraceTypes = new Set([TraceType.SURFACE_FLINGER, TraceType.WINDOW_MANAGER]);

View File

@@ -42,7 +42,7 @@ abstract class AbstractParser implements Parser<object> {
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]) {
@@ -66,8 +66,8 @@ abstract class AbstractParser implements Parser<object> {
abstract getTraceType(): TraceType;
getTraceFile(): TraceFile {
return this.traceFile;
getDescriptors(): string[] {
return [this.traceFile.getDescriptor()];
}
getLengthEntries(): number {

View File

@@ -56,9 +56,11 @@ export class ParserFactory {
async createParsers(
traceFiles: TraceFile[],
onProgressUpdate: OnProgressUpdateType = FunctionUtils.DO_NOTHING
): Promise<[Array<Parser<object>>, ParserError[]]> {
): Promise<[Array<{file: TraceFile; parser: Parser<object>}>, ParserError[]]> {
const errors: ParserError[] = [];
const parsers = new Array<{file: TraceFile; parser: Parser<object>}>();
if (traceFiles.length === 0) {
errors.push(new ParserError(ParserErrorType.NO_INPUT_FILES));
}
@@ -73,6 +75,7 @@ export class ParserFactory {
hasFoundParser = true;
if (this.shouldUseParser(parser, errors)) {
this.parsers.set(parser.getTraceType(), parser);
parsers.push({file: traceFile, parser});
}
break;
} catch (error) {
@@ -82,37 +85,37 @@ export class ParserFactory {
if (!hasFoundParser) {
console.log(`Failed to load trace ${traceFile.file.name}`);
errors.push(new ParserError(ParserErrorType.UNSUPPORTED_FORMAT, traceFile.file));
errors.push(new ParserError(ParserErrorType.UNSUPPORTED_FORMAT, traceFile.getDescriptor()));
}
onProgressUpdate((100 * (index + 1)) / traceFiles.length);
}
return [Array.from(this.parsers.values()), errors];
return [parsers, errors];
}
private shouldUseParser(newParser: Parser<object>, errors: ParserError[]): boolean {
const oldParser = this.parsers.get(newParser.getTraceType());
if (!oldParser) {
console.log(
`Loaded trace ${
newParser.getTraceFile().file.name
} (trace type: ${newParser.getTraceType()})`
`Loaded trace ${newParser
.getDescriptors()
.join()} (trace type: ${newParser.getTraceType()})`
);
return true;
}
if (newParser.getLengthEntries() > oldParser.getLengthEntries()) {
console.log(
`Loaded trace ${
newParser.getTraceFile().file.name
} (trace type: ${newParser.getTraceType()}).` +
` Replace trace ${oldParser.getTraceFile().file.name}`
`Loaded trace ${newParser
.getDescriptors()
.join()} (trace type: ${newParser.getTraceType()}).` +
` Replace trace ${oldParser.getDescriptors().join()}`
);
errors.push(
new ParserError(
ParserErrorType.OVERRIDE,
oldParser.getTraceFile().file,
oldParser.getDescriptors().join(),
oldParser.getTraceType()
)
);
@@ -120,15 +123,15 @@ export class ParserFactory {
}
console.log(
`Skipping trace ${
newParser.getTraceFile().file.name
} (trace type: ${newParser.getTraceType()}).` +
` Keep trace ${oldParser.getTraceFile().file.name}`
`Skipping trace ${newParser
.getDescriptors()
.join()} (trace type: ${newParser.getTraceType()}).` +
` Keep trace ${oldParser.getDescriptors().join()}`
);
errors.push(
new ParserError(
ParserErrorType.OVERRIDE,
newParser.getTraceFile().file,
newParser.getDescriptors().join(),
newParser.getTraceType()
)
);
@@ -145,7 +148,7 @@ export enum ParserErrorType {
export class ParserError {
constructor(
public type: ParserErrorType,
public trace: File | undefined = undefined,
public trace: string | undefined = undefined,
public traceType: TraceType | undefined = undefined
) {}
}

View File

@@ -31,6 +31,7 @@ export class TraceBuilder<T> {
private timestampType = TimestampType.REAL;
private frameMap?: FrameMap;
private frameMapBuilder?: FrameMapBuilder;
private descriptors: string[] = [];
setType(type: TraceType): TraceBuilder<T> {
this.type = type;
@@ -73,6 +74,11 @@ export class TraceBuilder<T> {
return this;
}
setDescriptors(descriptors: string[]): TraceBuilder<T> {
this.descriptors = descriptors;
return this;
}
build(): Trace<T> {
if (!this.parser) {
this.parser = this.createParser();
@@ -82,11 +88,10 @@ export class TraceBuilder<T> {
start: 0,
end: this.parser.getLengthEntries(),
};
const trace = new Trace<T>(
const trace = Trace.newInitializedTrace<T>(
this.type,
undefined,
undefined,
this.parser,
this.descriptors,
this.timestampType,
entriesRange
);

View File

@@ -19,15 +19,28 @@ import {CommonTestUtils} from 'test/common/utils';
import {LayerTraceEntry, WindowManagerState} from 'trace/flickerlib/common';
import {Parser} from 'trace/parser';
import {TimestampType} from 'trace/timestamp';
import {Trace} from 'trace/trace';
import {TraceFile} from 'trace/trace_file';
import {TraceType} from 'trace/trace_type';
class UnitTestUtils extends CommonTestUtils {
static async getTraceFromFile(filename: string): Promise<Trace<object>> {
const parser = await UnitTestUtils.getParser(filename);
const trace = Trace.newUninitializedTrace(parser);
trace.init(
parser.getTimestamps(TimestampType.REAL) !== undefined
? TimestampType.REAL
: TimestampType.ELAPSED
);
return trace;
}
static async getParser(filename: string): Promise<Parser<object>> {
const file = new TraceFile(await CommonTestUtils.getFixtureFile(filename), undefined);
const [parsers, errors] = await new ParserFactory().createParsers([file]);
expect(parsers.length).toEqual(1);
return parsers[0];
return parsers[0].parser;
}
static async getWindowManagerState(): Promise<WindowManagerState> {

View File

@@ -0,0 +1,21 @@
/*
* 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 {TraceType} from './trace_type';
export class LoadedTrace {
constructor(public descriptors: string[], public type: TraceType) {}
}

View File

@@ -15,13 +15,13 @@
*/
import {Timestamp, TimestampType} from './timestamp';
import {TraceFile} from './trace_file';
import {TraceType} from './trace_type';
export interface Parser<T> {
getTraceType(): TraceType;
getTraceFile(): TraceFile;
getLengthEntries(): number;
getTimestamps(type: TimestampType): Timestamp[] | undefined;
getEntry(index: number, timestampType: TimestampType): T;
getDescriptors(): string[];
}

View File

@@ -48,4 +48,8 @@ export class ParserMock<T> implements Parser<T> {
getEntry(index: number): T {
return this.entries[index];
}
getDescriptors(): string[] {
return ['MockTrace'];
}
}

View File

@@ -72,32 +72,56 @@ export class TraceEntry<T> {
}
export class Trace<T> {
readonly type: TraceType;
readonly file?: TraceFile;
readonly lengthEntries: number;
readonly fullTrace: Trace<T>;
private readonly parser: Parser<T>;
private readonly timestampType: TimestampType;
private timestampType: TimestampType | undefined;
private readonly entriesRange: EntriesRange;
private frameMap?: FrameMap;
private framesRange?: FramesRange;
constructor(
static newUninitializedTrace<T>(parser: Parser<T>): Trace<T> {
return new Trace(
parser.getTraceType(),
parser,
parser.getDescriptors(),
undefined,
undefined,
undefined
);
}
static newInitializedTrace<T>(
type: TraceType,
file: TraceFile | undefined,
fullTrace: Trace<T> | undefined,
parser: Parser<T>,
entryProvider: Parser<T>,
descriptors: string[],
timestampType: TimestampType,
entriesRange: EntriesRange
) {
this.type = type;
this.file = file;
this.lengthEntries = entriesRange.end - entriesRange.start;
this.fullTrace = fullTrace ? fullTrace : this;
this.parser = parser;
): Trace<T> {
return new Trace(type, entryProvider, descriptors, undefined, timestampType, entriesRange);
}
init(timestampType: TimestampType) {
this.timestampType = timestampType;
this.entriesRange = entriesRange;
}
private constructor(
readonly type: TraceType,
readonly parser: Parser<T>,
readonly descriptors: string[],
fullTrace: Trace<T> | undefined,
timestampType: TimestampType | undefined,
entriesRange: EntriesRange | undefined
) {
this.fullTrace = fullTrace ?? this;
this.entriesRange = entriesRange ?? {start: 0, end: parser.getLengthEntries()};
this.lengthEntries = this.entriesRange.end - this.entriesRange.start;
this.timestampType = timestampType;
}
getDescriptors(): string[] {
return this.parser.getDescriptors();
}
setFrameInfo(frameMap: FrameMap, framesRange: FramesRange | undefined) {
@@ -315,6 +339,10 @@ export class Trace<T> {
}
private getFullTraceTimestamps(): Timestamp[] {
if (this.timestampType === undefined) {
throw new Error('Forgot to initialize trace?');
}
const timestamps = this.parser.getTimestamps(this.timestampType);
if (!timestamps) {
throw new Error(`Timestamp type ${this.timestampType} is expected to be available`);
@@ -350,9 +378,9 @@ export class Trace<T> {
const slice = new Trace<T>(
this.type,
this.file,
this.fullTrace,
this.parser,
this.descriptors,
this.fullTrace,
this.timestampType,
entries
);

View File

@@ -14,12 +14,14 @@
* limitations under the License.
*/
import {TraceType} from './trace_type';
export class TraceFile {
constructor(public file: File, public parentArchive?: File) {}
}
export class LoadedTraceFile {
constructor(public traceFile: TraceFile, public type: TraceType) {}
getDescriptor(): string {
let descriptor = this.file.name;
if (this.parentArchive?.name !== undefined) {
descriptor += ` (${this.parentArchive?.name})`;
}
return descriptor;
}
}