xref: /aosp_15_r20/development/tools/winscope/src/parsers/events/traces_parser_cujs.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 {SetFormatters} from 'parsers/operations/set_formatters';
21import {AbstractTracesParser} from 'parsers/traces/abstract_traces_parser';
22import {CoarseVersion} from 'trace/coarse_version';
23import {Trace} from 'trace/trace';
24import {Traces} from 'trace/traces';
25import {TraceType} from 'trace/trace_type';
26import {CUJ_TYPE_FORMATTER} from 'trace/tree_node/formatters';
27import {PropertyTreeBuilderFromProto} from 'trace/tree_node/property_tree_builder_from_proto';
28import {PropertyTreeNode} from 'trace/tree_node/property_tree_node';
29import {EventTag} from './event_tag';
30import {AddCujProperties} from './operations/add_cuj_properties';
31
32export class TracesParserCujs extends AbstractTracesParser<PropertyTreeNode> {
33  private static readonly AddCujProperties = new AddCujProperties();
34  private readonly eventLogTrace: Trace<PropertyTreeNode> | undefined;
35  private readonly descriptors: string[];
36  private decodedEntries: PropertyTreeNode[] | undefined;
37
38  constructor(traces: Traces, timestampConverter: ParserTimestampConverter) {
39    super(timestampConverter);
40
41    const eventlogTrace = traces.getTrace(TraceType.EVENT_LOG);
42    if (eventlogTrace !== undefined) {
43      this.eventLogTrace = eventlogTrace;
44      this.descriptors = this.eventLogTrace.getDescriptors();
45    } else {
46      this.descriptors = [];
47    }
48  }
49
50  override getCoarseVersion(): CoarseVersion {
51    return CoarseVersion.LEGACY;
52  }
53
54  override async parse() {
55    if (this.eventLogTrace === undefined) {
56      throw new Error('EventLog trace not defined');
57    }
58
59    const eventsPromises = this.eventLogTrace.mapEntry((entry) =>
60      entry.getValue(),
61    );
62    const events = await Promise.all(eventsPromises);
63    const cujEvents = events.filter((event) => {
64      const tag = assertDefined(event.getChildByName('tag')).getValue();
65      return (
66        tag === EventTag.JANK_CUJ_BEGIN_TAG ||
67        tag === EventTag.JANK_CUJ_END_TAG ||
68        tag === EventTag.JANK_CUJ_CANCEL_TAG
69      );
70    });
71    this.decodedEntries = this.makeCujsFromEvents(cujEvents);
72    await this.createTimestamps();
73  }
74
75  override async createTimestamps() {
76    this.timestamps = [];
77    for (let index = 0; index < this.getLengthEntries(); index++) {
78      const entry = await this.getEntry(index);
79      const timestamp = entry?.getChildByName('startTimestamp')?.getValue();
80      this.timestamps.push(timestamp);
81    }
82  }
83
84  getLengthEntries(): number {
85    return assertDefined(this.decodedEntries).length;
86  }
87
88  getEntry(index: number): Promise<PropertyTreeNode> {
89    const entry = assertDefined(this.decodedEntries)[index];
90    return Promise.resolve(entry);
91  }
92
93  override getDescriptors(): string[] {
94    return this.descriptors;
95  }
96
97  getTraceType(): TraceType {
98    return TraceType.CUJS;
99  }
100
101  override getRealToMonotonicTimeOffsetNs(): bigint | undefined {
102    return undefined;
103  }
104
105  override getRealToBootTimeOffsetNs(): bigint | undefined {
106    return undefined;
107  }
108
109  private makeCujTimestampObject(timestamp: PropertyTreeNode): Timestamp {
110    return this.timestampConverter.makeTimestampFromRealNs(
111      assertDefined(timestamp.getChildByName('unixNanos')).getValue(),
112    );
113  }
114
115  private makeCujsFromEvents(events: PropertyTreeNode[]): PropertyTreeNode[] {
116    events.forEach((event) => TracesParserCujs.AddCujProperties.apply(event));
117
118    const startEvents = this.filterEventsByTag(
119      events,
120      EventTag.JANK_CUJ_BEGIN_TAG,
121    );
122    const endEvents = this.filterEventsByTag(events, EventTag.JANK_CUJ_END_TAG);
123    const canceledEvents = this.filterEventsByTag(
124      events,
125      EventTag.JANK_CUJ_CANCEL_TAG,
126    );
127
128    const cujs: PropertyTreeNode[] = [];
129
130    for (const startEvent of startEvents) {
131      const cujType = assertDefined(
132        startEvent.getChildByName('cujType'),
133      ).getValue();
134      const startTimestamp = assertDefined(
135        startEvent.getChildByName('cujTimestamp'),
136      );
137
138      const matchingEndEvent = this.findMatchingEvent(
139        endEvents,
140        cujType,
141        startTimestamp,
142      );
143      const matchingCancelEvent = this.findMatchingEvent(
144        canceledEvents,
145        cujType,
146        startTimestamp,
147      );
148
149      if (!matchingEndEvent && !matchingCancelEvent) {
150        continue;
151      }
152
153      const closingEvent = this.getClosingEvent(
154        matchingEndEvent,
155        matchingCancelEvent,
156      );
157
158      const closingEventTimestamp = assertDefined(
159        closingEvent.getChildByName('cujTimestamp'),
160      );
161      const canceled =
162        assertDefined(closingEvent.getChildByName('tag')?.getValue()) ===
163        EventTag.JANK_CUJ_CANCEL_TAG;
164
165      const cuj: Cuj = {
166        cujType,
167        startTimestamp: this.makeCujTimestampObject(startTimestamp),
168        endTimestamp: this.makeCujTimestampObject(closingEventTimestamp),
169        canceled,
170      };
171
172      cujs.push(this.makeCujPropertyTree(cuj));
173    }
174    return cujs;
175  }
176
177  private filterEventsByTag(
178    events: PropertyTreeNode[],
179    targetTag: EventTag,
180  ): PropertyTreeNode[] {
181    return events.filter((event) => {
182      const tag = assertDefined(event.getChildByName('tag')).getValue();
183      return tag === targetTag;
184    });
185  }
186
187  private findMatchingEvent(
188    events: PropertyTreeNode[],
189    targetCujType: number,
190    startTimestamp: PropertyTreeNode,
191  ): PropertyTreeNode | undefined {
192    return events.find((event) => {
193      const cujType = assertDefined(event.getChildByName('cujType')).getValue();
194      const timestamp = assertDefined(event.getChildByName('cujTimestamp'));
195      return (
196        targetCujType === cujType &&
197        this.cujTimestampIsGreaterThan(timestamp, startTimestamp)
198      );
199    });
200  }
201
202  private cujTimestampIsGreaterThan(
203    a: PropertyTreeNode,
204    b: PropertyTreeNode,
205  ): boolean {
206    const aUnixNanos: bigint = assertDefined(
207      a.getChildByName('unixNanos'),
208    ).getValue();
209    const bUnixNanos: bigint = assertDefined(
210      b.getChildByName('unixNanos'),
211    ).getValue();
212    return aUnixNanos > bUnixNanos;
213  }
214
215  private getClosingEvent(
216    endEvent: PropertyTreeNode | undefined,
217    cancelEvent: PropertyTreeNode | undefined,
218  ): PropertyTreeNode {
219    const endTimestamp = endEvent?.getChildByName('cujTimestamp');
220    const cancelTimestamp = cancelEvent?.getChildByName('cujTimestamp');
221
222    let closingEvent: PropertyTreeNode | undefined;
223    if (!endTimestamp) {
224      closingEvent = cancelEvent;
225    } else if (!cancelTimestamp) {
226      closingEvent = endEvent;
227    } else {
228      const canceledBeforeEnd = this.cujTimestampIsGreaterThan(
229        endTimestamp,
230        cancelTimestamp,
231      );
232      closingEvent = canceledBeforeEnd ? cancelEvent : endEvent;
233    }
234
235    if (!closingEvent) {
236      throw new Error('Should have found one matching closing event for CUJ');
237    }
238
239    return closingEvent;
240  }
241
242  private makeCujPropertyTree(cuj: Cuj): PropertyTreeNode {
243    const tree = new PropertyTreeBuilderFromProto()
244      .setData(cuj)
245      .setRootId('CujTrace')
246      .setRootName('cuj')
247      .build();
248
249    new SetFormatters(
250      undefined,
251      new Map([['cujType', CUJ_TYPE_FORMATTER]]),
252    ).apply(tree);
253    return tree;
254  }
255}
256
257interface Cuj {
258  cujType: number;
259  startTimestamp: Timestamp;
260  endTimestamp: Timestamp;
261  canceled: boolean;
262}
263