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 {ParserTransitionsUtils} from 'parsers/transitions/parser_transitions_utils';
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
27  override getTraceType(): TraceType {
28    return TraceType.TRANSITION;
29  }
30
31  override async getEntry(index: number): Promise<PropertyTreeNode> {
32    const transitionProto = await this.queryEntry(index);
33    if (this.handlerIdToName === undefined) {
34      const handlers = await this.queryHandlers();
35      this.handlerIdToName = {};
36      handlers.forEach(
37        (it) => (assertDefined(this.handlerIdToName)[it.id] = it.name),
38      );
39    }
40    return this.makePropertiesTree(transitionProto);
41  }
42
43  protected override getTableName(): string {
44    return 'window_manager_shell_transitions';
45  }
46
47  private async queryEntry(
48    index: number,
49  ): Promise<perfetto.protos.ShellTransition> {
50    const protoBuilder = new FakeProtoBuilder();
51
52    const sql = `
53      SELECT
54        transitions.transition_id,
55        args.key,
56        args.value_type,
57        args.int_value,
58        args.string_value,
59        args.real_value
60      FROM
61        window_manager_shell_transitions as transitions
62        INNER JOIN args ON transitions.arg_set_id = args.arg_set_id
63      WHERE transitions.id = ${this.entryIndexToRowIdMap[index]};
64    `;
65    const result = await this.traceProcessor.query(sql).waitAllRows();
66
67    for (const it = result.iter({}); it.valid(); it.next()) {
68      protoBuilder.addArg(
69        it.get('key') as string,
70        it.get('value_type') as string,
71        it.get('int_value') as bigint | undefined,
72        it.get('real_value') as number | undefined,
73        it.get('string_value') as string | undefined,
74      );
75    }
76
77    return protoBuilder.build();
78  }
79
80  private makePropertiesTree(
81    transitionProto: perfetto.protos.ShellTransition,
82  ): PropertyTreeNode {
83    this.validatePerfettoTransition(transitionProto);
84
85    const perfettoTransitionInfo = {
86      entry: transitionProto,
87      realToBootTimeOffsetNs: undefined,
88      handlerMapping: this.handlerIdToName,
89      timestampConverter: this.timestampConverter,
90    };
91
92    const shellEntryTree = ParserTransitionsUtils.makeShellPropertiesTree(
93      perfettoTransitionInfo,
94      [
95        'createTimeNs',
96        'sendTimeNs',
97        'wmAbortTimeNs',
98        'finishTimeNs',
99        'startTransactionId',
100        'finishTransactionId',
101        'type',
102        'targets',
103        'flags',
104        'startingWindowRemoveTimeNs',
105      ],
106    );
107    const wmEntryTree = ParserTransitionsUtils.makeWmPropertiesTree(
108      perfettoTransitionInfo,
109      [
110        'dispatchTimeNs',
111        'mergeTimeNs',
112        'mergeRequestTimeNs',
113        'shellAbortTimeNs',
114        'handler',
115        'mergeTarget',
116      ],
117    );
118
119    return ParserTransitionsUtils.makeTransitionPropertiesTree(
120      shellEntryTree,
121      wmEntryTree,
122    );
123  }
124
125  private async queryHandlers(): Promise<TransitionHandler[]> {
126    const sql =
127      'SELECT handler_id, handler_name FROM window_manager_shell_transition_handlers;';
128    const result = await this.traceProcessor.query(sql).waitAllRows();
129
130    const handlers: TransitionHandler[] = [];
131    for (const it = result.iter({}); it.valid(); it.next()) {
132      handlers.push({
133        id: it.get('handler_id') as number,
134        name: it.get('handler_name') as string,
135      });
136    }
137
138    return handlers;
139  }
140
141  private validatePerfettoTransition(
142    transition: perfetto.protos.IShellTransition,
143  ) {
144    if (transition.id === 0) {
145      throw new Error('Entry need a non null id');
146    }
147    if (
148      !transition.createTimeNs &&
149      !transition.sendTimeNs &&
150      !transition.wmAbortTimeNs &&
151      !transition.finishTimeNs &&
152      !transition.dispatchTimeNs &&
153      !transition.mergeRequestTimeNs &&
154      !transition.mergeTimeNs &&
155      !transition.shellAbortTimeNs
156    ) {
157      throw new Error('Requires at least one non-null timestamp');
158    }
159  }
160}
161
162interface TransitionHandler {
163  id: number;
164  name: string;
165}
166