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 {ArrayUtils} from 'common/array_utils'; 18import {Parser} from 'trace/parser'; 19import {Timestamp, TimestampType} from 'trace/timestamp'; 20import {TraceFile} from 'trace/trace_file'; 21import {TraceType} from 'trace/trace_type'; 22 23abstract class AbstractParser<T extends object = object> implements Parser<T> { 24 protected traceFile: TraceFile; 25 protected decodedEntries: any[] = []; 26 private timestamps: Map<TimestampType, Timestamp[]> = new Map<TimestampType, Timestamp[]>(); 27 28 protected constructor(trace: TraceFile) { 29 this.traceFile = trace; 30 } 31 32 async parse() { 33 const traceBuffer = new Uint8Array(await this.traceFile.file.arrayBuffer()); 34 35 const magicNumber = this.getMagicNumber(); 36 if (magicNumber !== undefined) { 37 const bufferContainsMagicNumber = ArrayUtils.equal( 38 magicNumber, 39 traceBuffer.slice(0, magicNumber.length) 40 ); 41 if (!bufferContainsMagicNumber) { 42 throw TypeError("buffer doesn't contain expected magic number"); 43 } 44 } 45 46 this.decodedEntries = this.decodeTrace(traceBuffer).map((it) => this.addDefaultProtoFields(it)); 47 48 for (const type of [TimestampType.ELAPSED, TimestampType.REAL]) { 49 const timestamps: Timestamp[] = []; 50 let areTimestampsValid = true; 51 52 for (const entry of this.decodedEntries) { 53 const timestamp = this.getTimestamp(type, entry); 54 if (timestamp === undefined) { 55 areTimestampsValid = false; 56 break; 57 } 58 timestamps.push(timestamp); 59 } 60 61 if (areTimestampsValid) { 62 this.timestamps.set(type, timestamps); 63 } 64 } 65 } 66 67 abstract getTraceType(): TraceType; 68 69 getDescriptors(): string[] { 70 return [this.traceFile.getDescriptor()]; 71 } 72 73 getLengthEntries(): number { 74 return this.decodedEntries.length; 75 } 76 77 getTimestamps(type: TimestampType): undefined | Timestamp[] { 78 return this.timestamps.get(type); 79 } 80 81 getEntry(index: number, timestampType: TimestampType): Promise<T> { 82 const entry = this.processDecodedEntry(index, timestampType, this.decodedEntries[index]); 83 return Promise.resolve(entry); 84 } 85 86 // Add default values to the proto objects. 87 private addDefaultProtoFields(protoObj: any): any { 88 if (!protoObj || protoObj !== Object(protoObj) || !protoObj.$type) { 89 return protoObj; 90 } 91 92 for (const fieldName in protoObj.$type.fields) { 93 if (Object.prototype.hasOwnProperty.call(protoObj.$type.fields, fieldName)) { 94 const fieldProperties = protoObj.$type.fields[fieldName]; 95 const field = protoObj[fieldName]; 96 97 if (Array.isArray(field)) { 98 field.forEach((item, _) => { 99 this.addDefaultProtoFields(item); 100 }); 101 continue; 102 } 103 104 if (!field) { 105 protoObj[fieldName] = fieldProperties.defaultValue; 106 } 107 108 if (fieldProperties.resolvedType && fieldProperties.resolvedType.valuesById) { 109 protoObj[fieldName] = 110 fieldProperties.resolvedType.valuesById[protoObj[fieldProperties.name]]; 111 continue; 112 } 113 this.addDefaultProtoFields(protoObj[fieldName]); 114 } 115 } 116 117 return protoObj; 118 } 119 120 protected abstract getMagicNumber(): undefined | number[]; 121 protected abstract decodeTrace(trace: Uint8Array): any[]; 122 protected abstract getTimestamp(type: TimestampType, decodedEntry: any): undefined | Timestamp; 123 protected abstract processDecodedEntry( 124 index: number, 125 timestampType: TimestampType, 126 decodedEntry: any 127 ): any; 128} 129 130export {AbstractParser}; 131