• 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 */
16
17import {assertDefined} from 'common/assert_utils';
18import {Timestamp} from 'common/time/time';
19import {ParserTimestampConverter} from 'common/time/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    const zeroTs = this.timestampConverter.makeZeroTimestamp();
80    for (let index = 0; index < this.getLengthEntries(); index++) {
81      const entry = await this.getEntry(index);
82      const ts = this.getTimestampFromTransitionProperties(entry);
83      this.timestamps.push(ts ?? zeroTs);
84    }
85  }
86
87  override getLengthEntries(): number {
88    return assertDefined(this.decodedEntries).length;
89  }
90
91  override getEntry(index: number): Promise<PropertyTreeNode> {
92    const entry = assertDefined(this.decodedEntries)[index];
93    return Promise.resolve(entry);
94  }
95
96  override getDescriptors(): string[] {
97    return this.descriptors;
98  }
99
100  override getTraceType(): TraceType {
101    return TraceType.TRANSITION;
102  }
103
104  override getRealToMonotonicTimeOffsetNs(): bigint | undefined {
105    return undefined;
106  }
107
108  override getRealToBootTimeOffsetNs(): bigint | undefined {
109    return undefined;
110  }
111
112  private compressEntries(
113    allTransitions: PropertyTreeNode[],
114  ): PropertyTreeNode[] {
115    const idToTransition = new Map<number, PropertyTreeNode>();
116
117    for (const transition of allTransitions) {
118      const id = assertDefined(transition.getChildByName('id')).getValue();
119
120      const accumulatedTransition = idToTransition.get(id);
121      if (!accumulatedTransition) {
122        idToTransition.set(id, transition);
123      } else {
124        const mergedTransition = this.mergePartialTransitions(
125          accumulatedTransition,
126          transition,
127        );
128        idToTransition.set(id, mergedTransition);
129      }
130    }
131
132    const compressedTransitions = Array.from(idToTransition.values());
133    compressedTransitions.forEach((transition) => {
134      EntryPropertiesTreeFactory.TRANSITION_OPERATIONS.forEach((operation) =>
135        operation.apply(transition),
136      );
137    });
138    return compressedTransitions.sort((a, b) => this.compareByTimestamp(a, b));
139  }
140
141  private compareByTimestamp(a: PropertyTreeNode, b: PropertyTreeNode): number {
142    const aNs = this.getTimestampFromTransitionProperties(a) ?? 0n;
143    const bNs = this.getTimestampFromTransitionProperties(b) ?? 0n;
144    if (aNs !== bNs) {
145      return aNs < bNs ? -1 : 1;
146    }
147    // fallback to id
148    return assertDefined(a.getChildByName('id')).getValue() <
149      assertDefined(b.getChildByName('id')).getValue()
150      ? -1
151      : 1;
152  }
153
154  private getTimestampFromTransitionProperties(
155    transition: PropertyTreeNode,
156  ): Timestamp | undefined {
157    // Entry timestamps are defined as shell dispatch time - if this is
158    // null and send time is not null we fall back on send time
159    return (
160      assertDefined(transition.getChildByName('shellData'))
161        .getChildByName('dispatchTimeNs')
162        ?.getValue() ??
163      assertDefined(transition.getChildByName('wmData'))
164        .getChildByName('sendTimeNs')
165        ?.getValue()
166    );
167  }
168
169  private mergePartialTransitions(
170    transition1: PropertyTreeNode,
171    transition2: PropertyTreeNode,
172  ): PropertyTreeNode {
173    const mergedTransition = this.mergeProperties(
174      transition1,
175      transition2,
176      false,
177    );
178
179    const wmData1 = assertDefined(transition1.getChildByName('wmData'));
180    const wmData2 = assertDefined(transition2.getChildByName('wmData'));
181    const mergedWmData = this.mergeProperties(wmData1, wmData2);
182    mergedTransition.addOrReplaceChild(mergedWmData);
183
184    const shellData1 = assertDefined(transition1.getChildByName('shellData'));
185    const shellData2 = assertDefined(transition2.getChildByName('shellData'));
186    const mergedShellData = this.mergeProperties(shellData1, shellData2);
187    mergedTransition.addOrReplaceChild(mergedShellData);
188
189    return mergedTransition;
190  }
191
192  private mergeProperties(
193    node1: PropertyTreeNode,
194    node2: PropertyTreeNode,
195    visitNestedChildren = true,
196  ): PropertyTreeNode {
197    const mergedNode = new PropertyTreeNode(
198      node1.id,
199      node1.name,
200      node1.source,
201      undefined,
202    );
203
204    node1.getAllChildren().forEach((property1) => {
205      if (!visitNestedChildren && property1.getAllChildren().length > 0) {
206        return;
207      }
208
209      const property2 = node2.getChildByName(property1.name);
210      if (
211        !property2 ||
212        property2.getValue()?.toString() < property1.getValue()?.toString()
213      ) {
214        mergedNode.addOrReplaceChild(property1);
215        return;
216      }
217
218      if (visitNestedChildren && property1.getAllChildren().length > 0) {
219        const mergedProperty = this.mergeProperties(property1, property2);
220        mergedNode.addOrReplaceChild(mergedProperty);
221        return;
222      }
223
224      mergedNode.addOrReplaceChild(property2);
225    });
226
227    node2.getAllChildren().forEach((property2) => {
228      const existingProperty = mergedNode.getChildByName(property2.name);
229      if (!existingProperty) {
230        mergedNode.addOrReplaceChild(property2);
231        return;
232      }
233    });
234
235    return mergedNode;
236  }
237}
238