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