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 */ 16import {assertDefined} from 'common/assert_utils'; 17import {AbstractParser} from 'parsers/perfetto/abstract_parser'; 18import {FakeProtoBuilder} from 'parsers/perfetto/fake_proto_builder'; 19import {EntryPropertiesTreeFactory} from 'parsers/transitions/entry_properties_tree_factory'; 20import {perfetto} from 'protos/transitions/latest/static'; 21import {TraceType} from 'trace/trace_type'; 22import {PropertyTreeNode} from 'trace/tree_node/property_tree_node'; 23 24export class ParserTransitions extends AbstractParser<PropertyTreeNode> { 25 private handlerIdToName: {[id: number]: string} | undefined = undefined; 26 private readonly internalTableName = 'window_manager_shell_transitions'; 27 28 protected override async preProcessTrace() { 29 // Entry timestamps are defined as shell dispatch time, which corresponds 30 // to the ts column of the internal Perfetto table - if this is 0 and 31 // send time is not null we fall back on send time 32 const sql = ` 33 CREATE PERFETTO TABLE ${this.getTableName()} AS 34 SELECT 35 STATE.id as id, 36 CASE 37 WHEN (STATE.ts = 0 AND TRANS.int_value IS NOT NULL) THEN TRANS.int_value 38 ELSE STATE.ts END 39 AS ts, 40 STATE.transition_id, 41 STATE.arg_set_id 42 FROM ${this.internalTableName} STATE 43 LEFT JOIN args TRANS 44 ON TRANS.arg_set_id = STATE.arg_set_id AND TRANS.key = 'send_time_ns' 45 ORDER BY id; 46 `; 47 await this.traceProcessor.queryAllRows(sql); 48 } 49 50 override getTraceType(): TraceType { 51 return TraceType.TRANSITION; 52 } 53 54 override async getEntry(index: number): Promise<PropertyTreeNode> { 55 const transitionProto = await this.queryEntry(index); 56 if (this.handlerIdToName === undefined) { 57 const handlers = await this.queryHandlers(); 58 this.handlerIdToName = {}; 59 handlers.forEach( 60 (it) => (assertDefined(this.handlerIdToName)[it.id] = it.name), 61 ); 62 } 63 return this.makePropertiesTree(transitionProto); 64 } 65 66 protected override getTableName(): string { 67 return 'transitions_with_updated_ts'; 68 } 69 70 private async queryEntry( 71 index: number, 72 ): Promise<perfetto.protos.ShellTransition> { 73 const protoBuilder = new FakeProtoBuilder(); 74 75 const sql = ` 76 SELECT 77 transitions.transition_id, 78 args.key, 79 args.value_type, 80 args.int_value, 81 args.string_value, 82 args.real_value 83 FROM 84 ${this.getTableName()} as transitions 85 INNER JOIN args ON transitions.arg_set_id = args.arg_set_id 86 WHERE transitions.id = ${this.entryIndexToRowIdMap[index]}; 87 `; 88 const result = await this.traceProcessor.queryAllRows(sql); 89 90 for (const it = result.iter({}); it.valid(); it.next()) { 91 protoBuilder.addArg( 92 it.get('key') as string, 93 it.get('value_type') as string, 94 it.get('int_value') as bigint | undefined, 95 it.get('real_value') as number | undefined, 96 it.get('string_value') as string | undefined, 97 ); 98 } 99 100 return protoBuilder.build(); 101 } 102 103 private makePropertiesTree( 104 transitionProto: perfetto.protos.ShellTransition, 105 ): PropertyTreeNode { 106 this.validatePerfettoTransition(transitionProto); 107 108 const perfettoTransitionInfo = { 109 entry: transitionProto, 110 realToBootTimeOffsetNs: undefined, 111 handlerMapping: this.handlerIdToName, 112 timestampConverter: this.timestampConverter, 113 }; 114 115 const shellEntryTree = EntryPropertiesTreeFactory.makeShellPropertiesTree( 116 perfettoTransitionInfo, 117 [ 118 'createTimeNs', 119 'sendTimeNs', 120 'wmAbortTimeNs', 121 'finishTimeNs', 122 'startTransactionId', 123 'finishTransactionId', 124 'type', 125 'targets', 126 'flags', 127 'startingWindowRemoveTimeNs', 128 ], 129 ); 130 const wmEntryTree = EntryPropertiesTreeFactory.makeWmPropertiesTree( 131 perfettoTransitionInfo, 132 [ 133 'dispatchTimeNs', 134 'mergeTimeNs', 135 'mergeRequestTimeNs', 136 'shellAbortTimeNs', 137 'handler', 138 'mergeTarget', 139 ], 140 ); 141 142 return EntryPropertiesTreeFactory.makeTransitionPropertiesTree( 143 shellEntryTree, 144 wmEntryTree, 145 ); 146 } 147 148 private async queryHandlers(): Promise<TransitionHandler[]> { 149 const sql = 150 'SELECT handler_id, handler_name FROM window_manager_shell_transition_handlers;'; 151 const result = await this.traceProcessor.queryAllRows(sql); 152 153 const handlers: TransitionHandler[] = []; 154 for (const it = result.iter({}); it.valid(); it.next()) { 155 handlers.push({ 156 id: it.get('handler_id') as number, 157 name: it.get('handler_name') as string, 158 }); 159 } 160 161 return handlers; 162 } 163 164 private validatePerfettoTransition( 165 transition: perfetto.protos.IShellTransition, 166 ) { 167 if (transition.id === 0) { 168 throw new Error('Transitions entry need a non null id'); 169 } 170 if ( 171 !transition.createTimeNs && 172 !transition.sendTimeNs && 173 !transition.wmAbortTimeNs && 174 !transition.finishTimeNs && 175 !transition.dispatchTimeNs && 176 !transition.mergeRequestTimeNs && 177 !transition.mergeTimeNs && 178 !transition.shellAbortTimeNs 179 ) { 180 throw new Error( 181 'Transitions entry requires at least one non-null timestamp', 182 ); 183 } 184 } 185} 186 187interface TransitionHandler { 188 id: number; 189 name: string; 190} 191