• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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