1/* 2 * Copyright 2024, 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 {ParserTimestampConverter} from 'common/timestamp_converter'; 19import {AbstractTracesParser} from 'parsers/traces/abstract_traces_parser'; 20import {CoarseVersion} from 'trace/coarse_version'; 21import { 22 CustomQueryParserResultTypeMap, 23 CustomQueryType, 24 VisitableParserCustomQuery, 25} from 'trace/custom_query'; 26import {EntriesRange, Trace} from 'trace/trace'; 27import {Traces} from 'trace/traces'; 28import {TraceType} from 'trace/trace_type'; 29import {PropertyTreeNode} from 'trace/tree_node/property_tree_node'; 30 31type OriginalTraceIndex = number; 32 33export class TracesParserInput extends AbstractTracesParser<PropertyTreeNode> { 34 private readonly keyEventTrace: Trace<PropertyTreeNode> | undefined; 35 private readonly motionEventTrace: Trace<PropertyTreeNode> | undefined; 36 private readonly descriptors: string[]; 37 private mergedEntryIndexMap: 38 | Array<[OriginalTraceIndex, TraceType]> 39 | undefined; 40 41 constructor(traces: Traces, timestampConverter: ParserTimestampConverter) { 42 super(timestampConverter); 43 this.keyEventTrace = traces.getTrace(TraceType.INPUT_KEY_EVENT); 44 this.motionEventTrace = traces.getTrace(TraceType.INPUT_MOTION_EVENT); 45 this.descriptors = this.keyEventTrace?.getDescriptors() ?? []; 46 this.motionEventTrace?.getDescriptors().forEach((d) => { 47 if (!this.descriptors.includes(d)) { 48 this.descriptors.push(d); 49 } 50 }); 51 } 52 53 override async parse() { 54 if ( 55 this.keyEventTrace === undefined && 56 this.motionEventTrace === undefined 57 ) { 58 throw new Error('Missing input traces'); 59 } 60 this.mergedEntryIndexMap = TracesParserInput.createMergedEntryIndexMap( 61 this.keyEventTrace, 62 this.motionEventTrace, 63 ); 64 await this.createTimestamps(); 65 } 66 67 override getCoarseVersion(): CoarseVersion { 68 return CoarseVersion.LATEST; 69 } 70 71 override getLengthEntries(): number { 72 return assertDefined(this.mergedEntryIndexMap).length; 73 } 74 75 override getEntry(index: number): Promise<PropertyTreeNode> { 76 const [subIndex, type] = assertDefined(this.mergedEntryIndexMap)[index]; 77 const trace = assertDefined( 78 type === TraceType.INPUT_KEY_EVENT 79 ? this.keyEventTrace 80 : this.motionEventTrace, 81 ); 82 return trace.getEntry(subIndex).getValue(); 83 } 84 85 override getDescriptors(): string[] { 86 return this.descriptors; 87 } 88 89 override getTraceType(): TraceType { 90 return TraceType.INPUT_EVENT_MERGED; 91 } 92 93 override getRealToMonotonicTimeOffsetNs(): bigint | undefined { 94 return undefined; 95 } 96 97 override getRealToBootTimeOffsetNs(): bigint | undefined { 98 return undefined; 99 } 100 101 override async createTimestamps() { 102 this.timestamps = []; 103 assertDefined(this.mergedEntryIndexMap).forEach(([index, traceType]) => { 104 const trace = assertDefined( 105 traceType === TraceType.INPUT_KEY_EVENT 106 ? this.keyEventTrace 107 : this.motionEventTrace, 108 ); 109 this.timestamps?.push( 110 assertDefined(trace.getParser().getTimestamps())[index], 111 ); 112 }); 113 } 114 115 // Given two traces, merge the two traces into one based on their timestamps. 116 // Returns the mapping from the index of the merged trace to the index in the 117 // sub-trace. 118 private static createMergedEntryIndexMap( 119 trace1: Trace<PropertyTreeNode> | undefined, 120 trace2: Trace<PropertyTreeNode> | undefined, 121 ): Array<[OriginalTraceIndex, TraceType]> { 122 // We are assuming the parsers entries are sorted by timestamps. 123 const timestamps1 = trace1?.getParser().getTimestamps() ?? []; 124 const timestamps2 = trace2?.getParser().getTimestamps() ?? []; 125 const type1 = trace1?.type ?? TraceType.INPUT_EVENT_MERGED; 126 const type2 = trace2?.type ?? TraceType.INPUT_EVENT_MERGED; 127 const mergedIndices: Array<[OriginalTraceIndex, TraceType]> = []; 128 129 let curIndex1 = 0; 130 let curIndex2 = 0; 131 while (curIndex1 < timestamps1.length && curIndex2 < timestamps2.length) { 132 if ( 133 timestamps1[curIndex1].getValueNs() <= 134 timestamps2[curIndex2].getValueNs() 135 ) { 136 mergedIndices.push([curIndex1++, type1]); 137 continue; 138 } 139 mergedIndices.push([curIndex2++, type2]); 140 } 141 while (curIndex1 < timestamps1.length) { 142 mergedIndices.push([curIndex1++, type1]); 143 } 144 while (curIndex2 < timestamps2.length) { 145 mergedIndices.push([curIndex2++, type2]); 146 } 147 148 return mergedIndices; 149 } 150 151 override async customQuery<Q extends CustomQueryType>( 152 type: Q, 153 entriesRange: EntriesRange, 154 ): Promise<CustomQueryParserResultTypeMap[Q]> { 155 return new VisitableParserCustomQuery(type) 156 .visit(CustomQueryType.VSYNCID, async () => { 157 assertTrue(entriesRange.start < entriesRange.end); 158 159 const {keyRange, motionRange} = this.getSubTraceRanges(entriesRange); 160 161 let keyResult: Array<bigint> = []; 162 if (keyRange !== undefined) { 163 keyResult = 164 (await this.keyEventTrace 165 ?.getParser() 166 .customQuery(CustomQueryType.VSYNCID, keyRange)) ?? []; 167 } 168 169 let motionResult: Array<bigint> = []; 170 if (motionRange !== undefined) { 171 motionResult = 172 (await this.motionEventTrace 173 ?.getParser() 174 .customQuery(CustomQueryType.VSYNCID, motionRange)) ?? []; 175 } 176 177 const mergedResult: Array<bigint> = []; 178 let curKeyIndex = 0; 179 let curMotionIndex = 0; 180 for (let i = entriesRange.start; i < entriesRange.end; i++) { 181 if ( 182 assertDefined(this.mergedEntryIndexMap)[i][1] === 183 TraceType.INPUT_KEY_EVENT 184 ) { 185 mergedResult.push(keyResult[curKeyIndex++]); 186 } else { 187 mergedResult.push(motionResult[curMotionIndex++]); 188 } 189 } 190 return mergedResult; 191 }) 192 .getResult(); 193 } 194 195 // Given the entries range for the merged trace, get the entries ranges for 196 // the individual sub-traces that make up this merged trace. 197 private getSubTraceRanges(entriesRange: EntriesRange): { 198 keyRange?: EntriesRange; 199 motionRange?: EntriesRange; 200 } { 201 const ranges: {keyRange?: EntriesRange; motionRange?: EntriesRange} = {}; 202 203 for (let i = entriesRange.start; i < entriesRange.end; i++) { 204 const [subEventIndex, type] = assertDefined(this.mergedEntryIndexMap)[i]; 205 if (type === TraceType.INPUT_KEY_EVENT) { 206 if (ranges.keyRange === undefined) { 207 ranges.keyRange = {start: subEventIndex, end: subEventIndex + 1}; 208 } else { 209 ranges.keyRange.end = subEventIndex + 1; 210 } 211 } else { 212 if (ranges.motionRange === undefined) { 213 ranges.motionRange = {start: subEventIndex, end: subEventIndex + 1}; 214 } else { 215 ranges.motionRange.end = subEventIndex + 1; 216 } 217 } 218 } 219 return ranges; 220 } 221} 222