1/* 2 * Copyright (C) 2022 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17import {FunctionUtils, OnProgressUpdateType} from 'common/function_utils'; 18import {ParserError, ParserFactory} from 'parsers/parser_factory'; 19import {TracesParserFactory} from 'parsers/traces_parser_factory'; 20import {FrameMapper} from 'trace/frame_mapper'; 21import {Parser} from 'trace/parser'; 22import {TimestampType} from 'trace/timestamp'; 23import {Trace} from 'trace/trace'; 24import {Traces} from 'trace/traces'; 25import {TraceFile} from 'trace/trace_file'; 26import {TraceType} from 'trace/trace_type'; 27 28class TracePipeline { 29 private parserFactory = new ParserFactory(); 30 private tracesParserFactory = new TracesParserFactory(); 31 private parsers: Array<Parser<object>> = []; 32 private files = new Map<TraceType, TraceFile>(); 33 private traces = new Traces(); 34 private commonTimestampType?: TimestampType; 35 36 async loadTraceFiles( 37 traceFiles: TraceFile[], 38 onLoadProgressUpdate: OnProgressUpdateType = FunctionUtils.DO_NOTHING 39 ): Promise<ParserError[]> { 40 traceFiles = await this.filterBugreportFilesIfNeeded(traceFiles); 41 const [fileAndParsers, parserErrors] = await this.parserFactory.createParsers( 42 traceFiles, 43 onLoadProgressUpdate 44 ); 45 for (const fileAndParser of fileAndParsers) { 46 this.files.set(fileAndParser.parser.getTraceType(), fileAndParser.file); 47 } 48 49 const newParsers = fileAndParsers.map((it) => it.parser); 50 this.parsers = this.parsers.concat(newParsers); 51 52 const tracesParsers = await this.tracesParserFactory.createParsers(this.parsers); 53 54 const allParsers = this.parsers.concat(tracesParsers); 55 56 this.traces = new Traces(); 57 allParsers.forEach((parser) => { 58 const trace = Trace.newUninitializedTrace(parser); 59 this.traces?.setTrace(parser.getTraceType(), trace); 60 }); 61 62 const hasTransitionTrace = this.traces 63 .mapTrace((trace) => trace.type) 64 .some((type) => type === TraceType.TRANSITION); 65 if (hasTransitionTrace) { 66 this.traces.deleteTrace(TraceType.WM_TRANSITION); 67 this.traces.deleteTrace(TraceType.SHELL_TRANSITION); 68 } 69 70 return parserErrors; 71 } 72 73 removeTrace(trace: Trace<object>) { 74 this.parsers = this.parsers.filter((parser) => parser.getTraceType() !== trace.type); 75 this.traces.deleteTrace(trace.type); 76 } 77 78 getLoadedFiles(): Map<TraceType, TraceFile> { 79 return this.files; 80 } 81 82 async buildTraces() { 83 const commonTimestampType = this.getCommonTimestampType(); 84 this.traces.forEachTrace((trace) => trace.init(commonTimestampType)); 85 await new FrameMapper(this.traces).computeMapping(); 86 } 87 88 getTraces(): Traces { 89 return this.traces; 90 } 91 92 async getScreenRecordingVideo(): Promise<undefined | Blob> { 93 const screenRecording = this.getTraces().getTrace(TraceType.SCREEN_RECORDING); 94 if (!screenRecording || screenRecording.lengthEntries === 0) { 95 return undefined; 96 } 97 return (await screenRecording.getEntry(0).getValue()).videoData; 98 } 99 100 clear() { 101 this.parserFactory = new ParserFactory(); 102 this.parsers = []; 103 this.traces = new Traces(); 104 this.commonTimestampType = undefined; 105 this.files = new Map<TraceType, TraceFile>(); 106 } 107 108 private async filterBugreportFilesIfNeeded(files: TraceFile[]): Promise<TraceFile[]> { 109 const bugreportMainEntry = files.find((file) => file.file.name === 'main_entry.txt'); 110 if (!bugreportMainEntry) { 111 return files; 112 } 113 114 const bugreportName = (await bugreportMainEntry.file.text()).trim(); 115 const isBugreport = files.find((file) => file.file.name === bugreportName) !== undefined; 116 if (!isBugreport) { 117 return files; 118 } 119 120 const BUGREPORT_FILES_ALLOWLIST = [ 121 'FS/data/misc/wmtrace/', 122 'FS/data/misc/perfetto-traces/', 123 'proto/window_CRITICAL.proto', 124 ]; 125 const isFileAllowlisted = (file: TraceFile) => { 126 for (const traceDir of BUGREPORT_FILES_ALLOWLIST) { 127 if (file.file.name.startsWith(traceDir)) { 128 return true; 129 } 130 } 131 return false; 132 }; 133 134 const fileBelongsToBugreport = (file: TraceFile) => 135 file.parentArchive === bugreportMainEntry.parentArchive; 136 137 return files.filter((file) => { 138 return isFileAllowlisted(file) || !fileBelongsToBugreport(file); 139 }); 140 } 141 142 private getCommonTimestampType(): TimestampType { 143 if (this.commonTimestampType !== undefined) { 144 return this.commonTimestampType; 145 } 146 147 const priorityOrder = [TimestampType.REAL, TimestampType.ELAPSED]; 148 for (const type of priorityOrder) { 149 if (this.parsers.every((it) => it.getTimestamps(type) !== undefined)) { 150 this.commonTimestampType = type; 151 return this.commonTimestampType; 152 } 153 } 154 155 throw Error('Failed to find common timestamp type across all traces'); 156 } 157} 158 159export {TracePipeline}; 160