xref: /aosp_15_r20/development/tools/winscope/src/parsers/input/perfetto/abstract_input_event_parser.ts (revision 90c8c64db3049935a07c6143d7fd006e26f8ecca)
1/*
2 * Copyright (C) 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} from 'common/assert_utils';
18import {ParserTimestampConverter} from 'common/timestamp_converter';
19import {SetFormatters} from 'parsers/operations/set_formatters';
20import {TranslateIntDef} from 'parsers/operations/translate_intdef';
21import {AbstractParser} from 'parsers/perfetto/abstract_parser';
22import {FakeProtoBuilder} from 'parsers/perfetto/fake_proto_builder';
23import {FakeProtoTransformer} from 'parsers/perfetto/fake_proto_transformer';
24import {Utils} from 'parsers/perfetto/utils';
25import {TamperedMessageType} from 'parsers/tampered_message_type';
26import root from 'protos/input/latest/json';
27import {perfetto} from 'protos/input/latest/static';
28import {
29  CustomQueryParserResultTypeMap,
30  CustomQueryType,
31  VisitableParserCustomQuery,
32} from 'trace/custom_query';
33import {EntriesRange} from 'trace/index_types';
34import {TraceFile} from 'trace/trace_file';
35import {PropertyTreeNode} from 'trace/tree_node/property_tree_node';
36import {WasmEngineProxy} from 'trace_processor/wasm_engine_proxy';
37
38export abstract class AbstractInputEventParser extends AbstractParser<PropertyTreeNode> {
39  protected static readonly WrapperProto = TamperedMessageType.tamper(
40    root.lookupType('perfetto.protos.InputEventWrapper'),
41  );
42
43  private static readonly DispatchEventsField =
44    AbstractInputEventParser.WrapperProto.fields['windowDispatchEvents'];
45
46  private static readonly DISPATCH_EVENT_OPS = [
47    new SetFormatters(AbstractInputEventParser.DispatchEventsField),
48    new TranslateIntDef(AbstractInputEventParser.DispatchEventsField),
49  ];
50
51  private static readonly DispatchTableName = 'android_input_event_dispatch';
52
53  private dispatchEventTransformer: FakeProtoTransformer;
54
55  protected constructor(
56    traceFile: TraceFile,
57    traceProcessor: WasmEngineProxy,
58    timestampConverter: ParserTimestampConverter,
59  ) {
60    super(traceFile, traceProcessor, timestampConverter);
61
62    this.dispatchEventTransformer = new FakeProtoTransformer(
63      assertDefined(
64        AbstractInputEventParser.DispatchEventsField.tamperedMessageType,
65      ),
66    );
67  }
68
69  protected async getDispatchEvents(
70    eventId: number,
71  ): Promise<perfetto.protos.AndroidWindowInputDispatchEvent[]> {
72    const sql = `
73        SELECT d.id,
74               args.key,
75               args.value_type,
76               args.int_value,
77               args.string_value,
78               args.real_value
79        FROM ${AbstractInputEventParser.DispatchTableName} AS d
80                 INNER JOIN args ON d.arg_set_id = args.arg_set_id
81        WHERE d.event_id = ${eventId}
82        ORDER BY d.id;
83    `;
84    const result = await this.traceProcessor.query(sql).waitAllRows();
85
86    const dispatchEvents: perfetto.protos.AndroidWindowInputDispatchEvent[] =
87      [];
88    for (const it = result.iter({}); it.valid(); ) {
89      const builder = new FakeProtoBuilder();
90      const prevId = it.get('id');
91      while (it.valid() && it.get('id') === prevId) {
92        builder.addArg(
93          it.get('key') as string,
94          it.get('value_type') as string,
95          it.get('int_value') as bigint | undefined,
96          it.get('real_value') as number | undefined,
97          it.get('string_value') as string | undefined,
98        );
99        it.next();
100      }
101      dispatchEvents.push(builder.build());
102    }
103    return dispatchEvents;
104  }
105
106  protected override getStdLibModuleName(): string | undefined {
107    return 'android.input';
108  }
109
110  protected processDispatchEventsTree(tree: PropertyTreeNode) {
111    AbstractInputEventParser.DISPATCH_EVENT_OPS.forEach((operation) => {
112      operation.apply(tree);
113    });
114  }
115
116  override async customQuery<Q extends CustomQueryType>(
117    type: Q,
118    entriesRange: EntriesRange,
119  ): Promise<CustomQueryParserResultTypeMap[Q]> {
120    return new VisitableParserCustomQuery(type)
121      .visit(CustomQueryType.VSYNCID, async () => {
122        return Utils.queryVsyncId(
123          this.traceProcessor,
124          this.getTableName(),
125          this.entryIndexToRowIdMap,
126          entriesRange,
127          AbstractInputEventParser.createVsyncIdQuery,
128        );
129      })
130      .getResult();
131  }
132
133  // Use a custom sql query to get the vsync_id of the first dispatch
134  // entry associated with an input event, if any.
135  private static createVsyncIdQuery(
136    tableName: string,
137    minRowId: number,
138    maxRowId: number,
139  ): string {
140    return `
141      SELECT
142        tbl.id AS id,
143        args.key,
144        args.value_type,
145        args.int_value
146      FROM ${tableName} AS tbl
147      INNER JOIN ${AbstractInputEventParser.DispatchTableName} AS d
148          ON tbl.event_id = d.event_id
149      INNER JOIN args ON d.arg_set_id = args.arg_set_id
150      WHERE
151        tbl.id BETWEEN ${minRowId} AND ${maxRowId}
152        AND args.key = 'vsync_id'
153      GROUP BY tbl.id
154      ORDER BY tbl.id;
155    `;
156  }
157}
158