1/* 2 * Copyright (C) 2023 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 {assertDefined, assertTrue} from 'common/assert_utils'; 18import {INVALID_TIME_NS, Timestamp} from 'common/time/time'; 19import {ParserTimestampConverter} from 'common/time/timestamp_converter'; 20import {CoarseVersion} from 'trace/coarse_version'; 21import { 22 CustomQueryParamTypeMap, 23 CustomQueryParserResultTypeMap, 24 CustomQueryType, 25} from 'trace/custom_query'; 26import {AbsoluteEntryIndex, EntriesRange} from 'trace/index_types'; 27import {Parser} from 'trace/parser'; 28import {TraceFile} from 'trace/trace_file'; 29import {TRACE_INFO} from 'trace/trace_info'; 30import {TraceType} from 'trace/trace_type'; 31import {TraceProcessor} from 'trace_processor/trace_processor'; 32 33export abstract class AbstractParser<T> implements Parser<T> { 34 protected traceProcessor: TraceProcessor; 35 protected realToBootTimeOffsetNs?: bigint; 36 protected timestampConverter: ParserTimestampConverter; 37 protected entryIndexToRowIdMap: number[] = []; 38 protected preProcessTrace?(): Promise<void>; 39 40 private lengthEntries = 0; 41 private traceFile: TraceFile; 42 private bootTimeTimestampsNs: Array<bigint> = []; 43 private timestamps: Timestamp[] | undefined; 44 45 constructor( 46 traceFile: TraceFile, 47 traceProcessor: TraceProcessor, 48 timestampConverter: ParserTimestampConverter, 49 ) { 50 this.traceFile = traceFile; 51 this.traceProcessor = traceProcessor; 52 this.timestampConverter = timestampConverter; 53 } 54 55 async parse() { 56 const module = this.getStdLibModuleName(); 57 if (module) { 58 await this.traceProcessor.query(`INCLUDE PERFETTO MODULE ${module};`); 59 } 60 61 if (this.preProcessTrace) { 62 await this.preProcessTrace(); 63 } 64 65 this.entryIndexToRowIdMap = await this.buildEntryIndexToRowIdMap(); 66 const rowBootTimeTimestampsNs = await this.queryRowBootTimeTimestamps(); 67 this.bootTimeTimestampsNs = this.entryIndexToRowIdMap.map( 68 (rowId) => rowBootTimeTimestampsNs[rowId], 69 ); 70 this.lengthEntries = this.bootTimeTimestampsNs.length; 71 assertTrue( 72 this.lengthEntries > 0, 73 () => 74 `Perfetto trace has no ${TRACE_INFO[this.getTraceType()].name} entries`, 75 ); 76 77 let lastNonZeroTimestamp: bigint | undefined; 78 for (let i = this.bootTimeTimestampsNs.length - 1; i >= 0; i--) { 79 if (this.bootTimeTimestampsNs[i] !== INVALID_TIME_NS) { 80 lastNonZeroTimestamp = this.bootTimeTimestampsNs[i]; 81 break; 82 } 83 } 84 this.realToBootTimeOffsetNs = await this.queryRealToBootTimeOffset( 85 assertDefined(lastNonZeroTimestamp), 86 ); 87 } 88 89 createTimestamps() { 90 this.timestamps = this.bootTimeTimestampsNs.map((ns) => { 91 if (ns === INVALID_TIME_NS) { 92 return this.timestampConverter.makeZeroTimestamp(); 93 } 94 return this.timestampConverter.makeTimestampFromBootTimeNs(ns); 95 }); 96 } 97 98 getLengthEntries(): number { 99 return this.lengthEntries; 100 } 101 102 getTimestamps(): Timestamp[] | undefined { 103 return this.timestamps; 104 } 105 106 getCoarseVersion(): CoarseVersion { 107 return CoarseVersion.LATEST; 108 } 109 110 customQuery<Q extends CustomQueryType>( 111 type: Q, 112 entriesRange: EntriesRange, 113 param?: CustomQueryParamTypeMap[Q], 114 ): Promise<CustomQueryParserResultTypeMap[Q]> { 115 throw new Error('Not implemented'); 116 } 117 118 getDescriptors(): string[] { 119 return [this.traceFile.getDescriptor()]; 120 } 121 122 getRealToMonotonicTimeOffsetNs(): bigint | undefined { 123 return undefined; 124 } 125 126 getRealToBootTimeOffsetNs(): bigint | undefined { 127 return this.realToBootTimeOffsetNs; 128 } 129 130 protected async buildEntryIndexToRowIdMap(): Promise<AbsoluteEntryIndex[]> { 131 const sqlRowIdAndTimestamp = ` 132 SELECT DISTINCT tbl.id AS id, tbl.ts 133 FROM ${this.getTableName()} AS tbl 134 ORDER BY tbl.ts; 135 `; 136 const result = await this.traceProcessor.queryAllRows(sqlRowIdAndTimestamp); 137 const entryIndexToRowId: AbsoluteEntryIndex[] = []; 138 for (const it = result.iter({}); it.valid(); it.next()) { 139 const rowId = Number(it.get('id') as bigint); 140 entryIndexToRowId.push(rowId); 141 } 142 return entryIndexToRowId; 143 } 144 145 private async queryRowBootTimeTimestamps(): Promise<Array<bigint>> { 146 const sql = `SELECT ts FROM ${this.getTableName()} ORDER BY id;`; 147 const result = await this.traceProcessor.queryAllRows(sql); 148 const timestamps: Array<bigint> = []; 149 for (const it = result.iter({}); it.valid(); it.next()) { 150 timestamps.push(it.get('ts') as bigint); 151 } 152 return timestamps; 153 } 154 155 // Query the real-to-boot time offset at the specified time 156 // (timestamp parameter). 157 // The timestamp parameter must be a non-zero timestamp queried/provided by TP, 158 // otherwise the TO_REALTIME() SQL function might return invalid values. 159 private async queryRealToBootTimeOffset(bootTimeNs: bigint): Promise<bigint> { 160 const sql = ` 161 SELECT TO_REALTIME(${bootTimeNs}) as realtime; 162 `; 163 164 const result = await this.traceProcessor.queryAllRows(sql); 165 assertTrue( 166 result.numRows() === 1, 167 () => 'Failed to query realtime timestamp', 168 ); 169 170 const real = result.iter({}).get('realtime') as bigint; 171 return real - bootTimeNs; 172 } 173 174 protected getStdLibModuleName(): string | undefined { 175 return undefined; 176 } 177 178 protected abstract getTableName(): string; 179 abstract getEntry(index: AbsoluteEntryIndex): Promise<T>; 180 abstract getTraceType(): TraceType; 181} 182