xref: /aosp_15_r20/development/tools/winscope/src/parsers/transitions/legacy/traces_parser_transitions.ts (revision 90c8c64db3049935a07c6143d7fd006e26f8ecca)
1/*
2 * Copyright 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 */
16
17import {assertDefined} from 'common/assert_utils';
18import {Timestamp} from 'common/time';
19import {ParserTimestampConverter} from 'common/timestamp_converter';
20import {AbstractTracesParser} from 'parsers/traces/abstract_traces_parser';
21import {EntryPropertiesTreeFactory} from 'parsers/transitions/entry_properties_tree_factory';
22import {CoarseVersion} from 'trace/coarse_version';
23import {Trace} from 'trace/trace';
24import {Traces} from 'trace/traces';
25import {TraceType} from 'trace/trace_type';
26import {PropertyTreeNode} from 'trace/tree_node/property_tree_node';
27
28export class TracesParserTransitions extends AbstractTracesParser<PropertyTreeNode> {
29  private readonly wmTransitionTrace: Trace<PropertyTreeNode> | undefined;
30  private readonly shellTransitionTrace: Trace<PropertyTreeNode> | undefined;
31  private readonly descriptors: string[];
32  private decodedEntries: PropertyTreeNode[] | undefined;
33
34  constructor(traces: Traces, timestampConverter: ParserTimestampConverter) {
35    super(timestampConverter);
36    const wmTransitionTrace = traces.getTrace(TraceType.WM_TRANSITION);
37    const shellTransitionTrace = traces.getTrace(TraceType.SHELL_TRANSITION);
38    if (wmTransitionTrace && shellTransitionTrace) {
39      this.wmTransitionTrace = wmTransitionTrace;
40      this.shellTransitionTrace = shellTransitionTrace;
41      this.descriptors = this.wmTransitionTrace
42        .getDescriptors()
43        .concat(this.shellTransitionTrace.getDescriptors());
44    } else {
45      this.descriptors = [];
46    }
47  }
48
49  override getCoarseVersion(): CoarseVersion {
50    return CoarseVersion.LEGACY;
51  }
52
53  override async parse() {
54    if (this.wmTransitionTrace === undefined) {
55      throw new Error('Missing WM Transition trace');
56    }
57
58    if (this.shellTransitionTrace === undefined) {
59      throw new Error('Missing Shell Transition trace');
60    }
61
62    const wmTransitionEntries: PropertyTreeNode[] = await Promise.all(
63      this.wmTransitionTrace.mapEntry((entry) => entry.getValue()),
64    );
65
66    const shellTransitionEntries: PropertyTreeNode[] = await Promise.all(
67      this.shellTransitionTrace.mapEntry((entry) => entry.getValue()),
68    );
69
70    const allEntries = wmTransitionEntries.concat(shellTransitionEntries);
71
72    this.decodedEntries = this.compressEntries(allEntries);
73
74    await this.createTimestamps();
75  }
76
77  override async createTimestamps() {
78    this.timestamps = [];
79    for (let index = 0; index < this.getLengthEntries(); index++) {
80      const entry = await this.getEntry(index);
81
82      const shellData = entry.getChildByName('shellData');
83      const dispatchTimestamp: Timestamp | undefined = shellData
84        ?.getChildByName('dispatchTimeNs')
85        ?.getValue();
86
87      const realToBootTimeOffsetNs: bigint =
88        shellData
89          ?.getChildByName('realToBootTimeOffsetTimestamp')
90          ?.getValue()
91          ?.getValueNs() ?? 0n;
92
93      // for consistency with all transitions, elapsed nanos are defined as shell dispatch time else 0n
94      const timestampNs: bigint = dispatchTimestamp
95        ? dispatchTimestamp.getValueNs()
96        : realToBootTimeOffsetNs;
97      const timestamp =
98        this.timestampConverter.makeTimestampFromRealNs(timestampNs);
99      this.timestamps.push(timestamp);
100    }
101  }
102
103  override getLengthEntries(): number {
104    return assertDefined(this.decodedEntries).length;
105  }
106
107  override getEntry(index: number): Promise<PropertyTreeNode> {
108    const entry = assertDefined(this.decodedEntries)[index];
109    return Promise.resolve(entry);
110  }
111
112  override getDescriptors(): string[] {
113    return this.descriptors;
114  }
115
116  override getTraceType(): TraceType {
117    return TraceType.TRANSITION;
118  }
119
120  override getRealToMonotonicTimeOffsetNs(): bigint | undefined {
121    return undefined;
122  }
123
124  override getRealToBootTimeOffsetNs(): bigint | undefined {
125    return undefined;
126  }
127
128  private compressEntries(
129    allTransitions: PropertyTreeNode[],
130  ): PropertyTreeNode[] {
131    const idToTransition = new Map<number, PropertyTreeNode>();
132
133    for (const transition of allTransitions) {
134      const id = assertDefined(transition.getChildByName('id')).getValue();
135
136      const accumulatedTransition = idToTransition.get(id);
137      if (!accumulatedTransition) {
138        idToTransition.set(id, transition);
139      } else {
140        const mergedTransition = this.mergePartialTransitions(
141          accumulatedTransition,
142          transition,
143        );
144        idToTransition.set(id, mergedTransition);
145      }
146    }
147
148    const compressedTransitions = Array.from(idToTransition.values());
149    compressedTransitions.forEach((transition) => {
150      EntryPropertiesTreeFactory.TRANSITION_OPERATIONS.forEach((operation) =>
151        operation.apply(transition),
152      );
153    });
154    return compressedTransitions.sort(this.compareByTimestamp);
155  }
156
157  private compareByTimestamp(a: PropertyTreeNode, b: PropertyTreeNode): number {
158    const aNanos =
159      assertDefined(a.getChildByName('shellData'))
160        .getChildByName('dispatchTimeNs')
161        ?.getValue()
162        ?.getValueNs() ?? 0n;
163    const bNanos =
164      assertDefined(b.getChildByName('shellData'))
165        .getChildByName('dispatchTimeNs')
166        ?.getValue()
167        ?.getValueNs() ?? 0n;
168    if (aNanos !== bNanos) {
169      return aNanos < bNanos ? -1 : 1;
170    }
171    // if dispatchTimeNs not present for both, fallback to id
172    return assertDefined(a.getChildByName('id')).getValue() <
173      assertDefined(b.getChildByName('id')).getValue()
174      ? -1
175      : 1;
176  }
177
178  private mergePartialTransitions(
179    transition1: PropertyTreeNode,
180    transition2: PropertyTreeNode,
181  ): PropertyTreeNode {
182    if (
183      assertDefined(transition1.getChildByName('id')).getValue() !==
184      assertDefined(transition2.getChildByName('id')).getValue()
185    ) {
186      throw new Error("Can't merge transitions with mismatching ids");
187    }
188
189    const mergedTransition = this.mergeProperties(
190      transition1,
191      transition2,
192      false,
193    );
194
195    const wmData1 = assertDefined(transition1.getChildByName('wmData'));
196    const wmData2 = assertDefined(transition2.getChildByName('wmData'));
197    const mergedWmData = this.mergeProperties(wmData1, wmData2);
198    mergedTransition.addOrReplaceChild(mergedWmData);
199
200    const shellData1 = assertDefined(transition1.getChildByName('shellData'));
201    const shellData2 = assertDefined(transition2.getChildByName('shellData'));
202    const mergedShellData = this.mergeProperties(shellData1, shellData2);
203    mergedTransition.addOrReplaceChild(mergedShellData);
204
205    return mergedTransition;
206  }
207
208  private mergeProperties(
209    node1: PropertyTreeNode,
210    node2: PropertyTreeNode,
211    visitNestedChildren = true,
212  ): PropertyTreeNode {
213    const mergedNode = new PropertyTreeNode(
214      node1.id,
215      node1.name,
216      node1.source,
217      undefined,
218    );
219
220    node1.getAllChildren().forEach((property1) => {
221      if (!visitNestedChildren && property1.getAllChildren().length > 0) {
222        return;
223      }
224
225      const property2 = node2.getChildByName(property1.name);
226      if (
227        !property2 ||
228        property2.getValue()?.toString() < property1.getValue()?.toString()
229      ) {
230        mergedNode.addOrReplaceChild(property1);
231        return;
232      }
233
234      if (visitNestedChildren && property1.getAllChildren().length > 0) {
235        const mergedProperty = this.mergeProperties(property1, property2);
236        mergedNode.addOrReplaceChild(mergedProperty);
237        return;
238      }
239
240      mergedNode.addOrReplaceChild(property2);
241    });
242
243    node2.getAllChildren().forEach((property2) => {
244      const existingProperty = mergedNode.getChildByName(property2.name);
245      if (!existingProperty) {
246        mergedNode.addOrReplaceChild(property2);
247        return;
248      }
249    });
250
251    return mergedNode;
252  }
253}
254