xref: /aosp_15_r20/development/tools/winscope/src/viewers/viewer_transactions/presenter.ts (revision 90c8c64db3049935a07c6143d7fd006e26f8ecca)
1/*
2 * Copyright (C) 2022 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 {PersistentStoreProxy} from 'common/persistent_store_proxy';
19import {Store} from 'common/store';
20import {Trace} from 'trace/trace';
21import {PropertyTreeNode} from 'trace/tree_node/property_tree_node';
22import {
23  AbstractLogViewerPresenter,
24  NotifyLogViewCallbackType,
25} from 'viewers/common/abstract_log_viewer_presenter';
26import {LogSelectFilter} from 'viewers/common/log_filters';
27import {LogPresenter} from 'viewers/common/log_presenter';
28import {PropertiesPresenter} from 'viewers/common/properties_presenter';
29import {TextFilter} from 'viewers/common/text_filter';
30import {LogField, LogHeader} from 'viewers/common/ui_data_log';
31import {UserOptions} from 'viewers/common/user_options';
32import {SetRootDisplayNames} from './operations/set_root_display_name';
33import {TransactionsEntry, TransactionsEntryType, UiData} from './ui_data';
34
35export class Presenter extends AbstractLogViewerPresenter<
36  UiData,
37  PropertyTreeNode
38> {
39  private static readonly COLUMNS = {
40    id: {name: 'TX ID', cssClass: 'transaction-id right-align'},
41    vsyncId: {name: 'VSYNC ID', cssClass: 'vsyncid right-align'},
42    pid: {name: 'PID', cssClass: 'pid right-align'},
43    uid: {name: 'UID', cssClass: 'uid right-align'},
44    type: {name: 'TYPE', cssClass: 'transaction-type'},
45    layerOrDisplayId: {
46      name: 'LAYER/DISP ID',
47      cssClass: 'layer-or-display-id right-align',
48    },
49    flags: {name: 'Flags', cssClass: 'flags'},
50  };
51
52  protected override keepCalculated = true;
53  protected override logPresenter = new LogPresenter<TransactionsEntry>();
54  protected override propertiesPresenter = new PropertiesPresenter(
55    PersistentStoreProxy.new<UserOptions>(
56      'TransactionsPropertyOptions',
57      {
58        showDefaults: {
59          name: 'Show defaults',
60          enabled: false,
61          tooltip: `
62                If checked, shows the value of all properties.
63                Otherwise, hides all properties whose value is
64                the default for its data type.
65              `,
66        },
67      },
68      this.storage,
69    ),
70    new TextFilter(),
71    [],
72    [new SetRootDisplayNames()],
73  );
74
75  constructor(
76    trace: Trace<PropertyTreeNode>,
77    readonly storage: Store,
78    notifyViewCallback: NotifyLogViewCallbackType<UiData>,
79  ) {
80    super(trace, notifyViewCallback, UiData.createEmpty());
81  }
82
83  protected override makeHeaders(): LogHeader[] {
84    return [
85      new LogHeader(
86        Presenter.COLUMNS.id,
87        new LogSelectFilter([], false, '125'),
88      ),
89      new LogHeader(
90        Presenter.COLUMNS.vsyncId,
91        new LogSelectFilter([], false, '90'),
92      ),
93      new LogHeader(Presenter.COLUMNS.pid, new LogSelectFilter([])),
94      new LogHeader(Presenter.COLUMNS.uid, new LogSelectFilter([])),
95      new LogHeader(
96        Presenter.COLUMNS.type,
97        new LogSelectFilter([], false, '175'),
98      ),
99      new LogHeader(
100        Presenter.COLUMNS.layerOrDisplayId,
101        new LogSelectFilter([]),
102      ),
103      new LogHeader(
104        Presenter.COLUMNS.flags,
105        new LogSelectFilter([], true, '250', '100%'),
106      ),
107    ];
108  }
109
110  protected override updateDefaultAllowlist(
111    tree: PropertyTreeNode | undefined,
112  ): void {
113    if (!tree) {
114      return;
115    }
116    const allowlist: string[] = [];
117    tree
118      .getChildByName('what')
119      ?.formattedValue()
120      .split(' | ')
121      .forEach((flag) => {
122        const properties = layerChangeFlagToPropertiesMap.get(flag);
123        if (properties !== undefined) {
124          allowlist.push(...properties);
125        } else if (flag.startsWith('e')) {
126          const candidateProperty = flag.split('Changed')[0].slice(1);
127          allowlist.push(
128            candidateProperty[0].toLowerCase() + candidateProperty.slice(1),
129          );
130        }
131      });
132    this.propertiesPresenter.updateDefaultAllowList(allowlist);
133  }
134
135  protected override async makeUiDataEntries(): Promise<TransactionsEntry[]> {
136    const entries: TransactionsEntry[] = [];
137
138    const entryProtos = await Promise.all(
139      this.trace.mapEntry(async (entry) => {
140        return await entry.getValue();
141      }),
142    );
143
144    for (
145      let traceIndex = 0;
146      traceIndex < this.trace.lengthEntries;
147      ++traceIndex
148    ) {
149      const entry = this.trace.getEntry(traceIndex);
150      const entryNode = entryProtos[traceIndex];
151      const vsyncId = Number(
152        assertDefined(entryNode.getChildByName('vsyncId')).getValue(),
153      );
154
155      for (const transactionState of assertDefined(
156        entryNode.getChildByName('transactions'),
157      ).getAllChildren()) {
158        const transactionId = assertDefined(
159          transactionState.getChildByName('transactionId'),
160        ).formattedValue();
161        const pid = assertDefined(
162          transactionState.getChildByName('pid'),
163        ).formattedValue();
164        const uid = assertDefined(
165          transactionState.getChildByName('uid'),
166        ).formattedValue();
167        const layerChanges = assertDefined(
168          transactionState.getChildByName('layerChanges'),
169        ).getAllChildren();
170
171        for (const layerState of layerChanges) {
172          const fields: LogField[] = [
173            {spec: Presenter.COLUMNS.id, value: transactionId},
174            {spec: Presenter.COLUMNS.vsyncId, value: vsyncId},
175            {spec: Presenter.COLUMNS.pid, value: pid},
176            {spec: Presenter.COLUMNS.uid, value: uid},
177            {
178              spec: Presenter.COLUMNS.type,
179              value: TransactionsEntryType.LAYER_CHANGED,
180            },
181            {
182              spec: Presenter.COLUMNS.layerOrDisplayId,
183              value: assertDefined(
184                layerState.getChildByName('layerId'),
185              ).formattedValue(),
186            },
187            {
188              spec: Presenter.COLUMNS.flags,
189              value: assertDefined(
190                layerState.getChildByName('what'),
191              ).formattedValue(),
192            },
193          ];
194          entries.push(new TransactionsEntry(entry, fields, layerState));
195        }
196
197        const displayChanges = assertDefined(
198          transactionState.getChildByName('displayChanges'),
199        ).getAllChildren();
200        for (const displayState of displayChanges) {
201          const fields: LogField[] = [
202            {spec: Presenter.COLUMNS.id, value: transactionId},
203            {spec: Presenter.COLUMNS.vsyncId, value: vsyncId},
204            {spec: Presenter.COLUMNS.pid, value: pid},
205            {spec: Presenter.COLUMNS.uid, value: uid},
206            {
207              spec: Presenter.COLUMNS.type,
208              value: TransactionsEntryType.DISPLAY_CHANGED,
209            },
210            {
211              spec: Presenter.COLUMNS.layerOrDisplayId,
212              value: assertDefined(
213                displayState.getChildByName('id'),
214              ).formattedValue(),
215            },
216            {
217              spec: Presenter.COLUMNS.flags,
218              value: assertDefined(
219                displayState.getChildByName('what'),
220              ).formattedValue(),
221            },
222          ];
223          entries.push(new TransactionsEntry(entry, fields, displayState));
224        }
225
226        if (layerChanges.length === 0 && displayChanges.length === 0) {
227          const fields: LogField[] = [
228            {spec: Presenter.COLUMNS.id, value: transactionId},
229            {spec: Presenter.COLUMNS.vsyncId, value: vsyncId},
230            {spec: Presenter.COLUMNS.pid, value: pid},
231            {spec: Presenter.COLUMNS.uid, value: uid},
232            {
233              spec: Presenter.COLUMNS.type,
234              value: TransactionsEntryType.NO_OP,
235            },
236            {spec: Presenter.COLUMNS.layerOrDisplayId, value: ''},
237            {spec: Presenter.COLUMNS.flags, value: ''},
238          ];
239          entries.push(new TransactionsEntry(entry, fields, undefined));
240        }
241      }
242
243      for (const layerCreationArgs of assertDefined(
244        entryNode.getChildByName('addedLayers'),
245      ).getAllChildren()) {
246        const fields: LogField[] = [
247          {spec: Presenter.COLUMNS.id, value: ''},
248          {spec: Presenter.COLUMNS.vsyncId, value: vsyncId},
249          {spec: Presenter.COLUMNS.pid, value: Presenter.VALUE_NA},
250          {spec: Presenter.COLUMNS.uid, value: Presenter.VALUE_NA},
251          {
252            spec: Presenter.COLUMNS.type,
253            value: TransactionsEntryType.LAYER_ADDED,
254          },
255          {
256            spec: Presenter.COLUMNS.layerOrDisplayId,
257            value: assertDefined(
258              layerCreationArgs.getChildByName('layerId'),
259            ).formattedValue(),
260          },
261          {spec: Presenter.COLUMNS.flags, value: ''},
262        ];
263        entries.push(new TransactionsEntry(entry, fields, layerCreationArgs));
264      }
265
266      for (const destroyedLayerId of assertDefined(
267        entryNode.getChildByName('destroyedLayers'),
268      ).getAllChildren()) {
269        const fields: LogField[] = [
270          {spec: Presenter.COLUMNS.id, value: ''},
271          {spec: Presenter.COLUMNS.vsyncId, value: vsyncId},
272          {spec: Presenter.COLUMNS.pid, value: Presenter.VALUE_NA},
273          {spec: Presenter.COLUMNS.uid, value: Presenter.VALUE_NA},
274          {
275            spec: Presenter.COLUMNS.type,
276            value: TransactionsEntryType.LAYER_DESTROYED,
277          },
278          {
279            spec: Presenter.COLUMNS.layerOrDisplayId,
280            value: destroyedLayerId.formattedValue(),
281          },
282          {spec: Presenter.COLUMNS.flags, value: ''},
283        ];
284        entries.push(new TransactionsEntry(entry, fields, destroyedLayerId));
285      }
286
287      for (const displayState of assertDefined(
288        entryNode.getChildByName('addedDisplays'),
289      ).getAllChildren()) {
290        const fields: LogField[] = [
291          {spec: Presenter.COLUMNS.id, value: ''},
292          {spec: Presenter.COLUMNS.vsyncId, value: vsyncId},
293          {spec: Presenter.COLUMNS.pid, value: Presenter.VALUE_NA},
294          {spec: Presenter.COLUMNS.uid, value: Presenter.VALUE_NA},
295          {
296            spec: Presenter.COLUMNS.type,
297            value: TransactionsEntryType.DISPLAY_ADDED,
298          },
299          {
300            spec: Presenter.COLUMNS.layerOrDisplayId,
301            value: assertDefined(
302              displayState.getChildByName('id'),
303            ).formattedValue(),
304          },
305          {
306            spec: Presenter.COLUMNS.flags,
307            value: assertDefined(
308              displayState.getChildByName('what'),
309            ).formattedValue(),
310          },
311        ];
312        entries.push(new TransactionsEntry(entry, fields, displayState));
313      }
314
315      for (const removedDisplayId of assertDefined(
316        entryNode.getChildByName('removedDisplays'),
317      ).getAllChildren()) {
318        const fields: LogField[] = [
319          {spec: Presenter.COLUMNS.id, value: ''},
320          {spec: Presenter.COLUMNS.vsyncId, value: vsyncId},
321          {spec: Presenter.COLUMNS.pid, value: Presenter.VALUE_NA},
322          {spec: Presenter.COLUMNS.uid, value: Presenter.VALUE_NA},
323          {
324            spec: Presenter.COLUMNS.type,
325            value: TransactionsEntryType.DISPLAY_REMOVED,
326          },
327          {
328            spec: Presenter.COLUMNS.layerOrDisplayId,
329            value: removedDisplayId.formattedValue(),
330          },
331          {spec: Presenter.COLUMNS.flags, value: ''},
332        ];
333        entries.push(new TransactionsEntry(entry, fields, removedDisplayId));
334      }
335
336      for (const destroyedLayerHandleId of assertDefined(
337        entryNode.getChildByName('destroyedLayerHandles'),
338      ).getAllChildren()) {
339        const fields: LogField[] = [
340          {spec: Presenter.COLUMNS.id, value: ''},
341          {spec: Presenter.COLUMNS.vsyncId, value: vsyncId},
342          {spec: Presenter.COLUMNS.pid, value: Presenter.VALUE_NA},
343          {spec: Presenter.COLUMNS.uid, value: Presenter.VALUE_NA},
344          {
345            spec: Presenter.COLUMNS.type,
346            value: TransactionsEntryType.LAYER_HANDLE_DESTROYED,
347          },
348          {
349            spec: Presenter.COLUMNS.layerOrDisplayId,
350            value: destroyedLayerHandleId.formattedValue(),
351          },
352          {spec: Presenter.COLUMNS.flags, value: ''},
353        ];
354        entries.push(
355          new TransactionsEntry(entry, fields, destroyedLayerHandleId),
356        );
357      }
358    }
359
360    return entries;
361  }
362
363  protected override updateFiltersInHeaders(
364    headers: LogHeader[],
365    allEntries: TransactionsEntry[],
366  ) {
367    for (const header of headers) {
368      if (header.spec === Presenter.COLUMNS.flags) {
369        (assertDefined(header.filter) as LogSelectFilter).options =
370          this.getUniqueUiDataEntryValues(
371            allEntries,
372            (entry: TransactionsEntry) =>
373              assertDefined(
374                entry.fields.find((f) => f.spec === header.spec)
375                  ?.value as string,
376              )
377                .split('|')
378                .map((flag) => flag.trim()),
379          );
380      } else {
381        (assertDefined(header.filter) as LogSelectFilter).options =
382          this.getUniqueUiDataEntryValues(
383            allEntries,
384            (entry: TransactionsEntry) =>
385              assertDefined(
386                entry.fields.find((f) => f.spec === header.spec),
387              ).value.toString(),
388          );
389      }
390    }
391  }
392
393  private getUniqueUiDataEntryValues<T>(
394    entries: TransactionsEntry[],
395    getValue: (entry: TransactionsEntry) => T | T[],
396  ): T[] {
397    const uniqueValues = new Set<T>();
398    entries.forEach((entry: TransactionsEntry) => {
399      const value = getValue(entry);
400      if (Array.isArray(value)) {
401        value.forEach((val) => uniqueValues.add(val));
402      } else {
403        uniqueValues.add(value);
404      }
405    });
406
407    const result = [...uniqueValues];
408
409    result.sort((a, b) => {
410      const aIsNumber = !isNaN(Number(a));
411      const bIsNumber = !isNaN(Number(b));
412
413      if (aIsNumber && bIsNumber) {
414        return Number(a) - Number(b);
415      } else if (aIsNumber) {
416        return 1; // place number after strings in the result
417      } else if (bIsNumber) {
418        return -1; // place number after strings in the result
419      }
420
421      // a and b are both strings
422      if (a < b) {
423        return -1;
424      } else if (a > b) {
425        return 1;
426      } else {
427        return 0;
428      }
429    });
430
431    return result;
432  }
433}
434
435const layerChangeFlagToPropertiesMap = new Map([
436  ['eReparent', ['parentId']],
437  ['eRelativeLayerChanged', ['relativeParentId']],
438  ['eLayerChanged', ['layerId']],
439  ['ePositionChanged', ['x', 'y', 'z']],
440]);
441