xref: /aosp_15_r20/development/tools/winscope/src/parsers/input/perfetto/traces_parser_input.ts (revision 90c8c64db3049935a07c6143d7fd006e26f8ecca)
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