xref: /aosp_15_r20/development/tools/winscope/src/parsers/surface_flinger/perfetto/parser_surface_flinger.ts (revision 90c8c64db3049935a07c6143d7fd006e26f8ecca)
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, assertTrue} from 'common/assert_utils';
18import {ParserTimestampConverter} from 'common/timestamp_converter';
19import {AddDefaults} from 'parsers/operations/add_defaults';
20import {SetFormatters} from 'parsers/operations/set_formatters';
21import {TranslateIntDef} from 'parsers/operations/translate_intdef';
22import {AbstractParser} from 'parsers/perfetto/abstract_parser';
23import {FakeProtoBuilder} from 'parsers/perfetto/fake_proto_builder';
24import {FakeProtoTransformer} from 'parsers/perfetto/fake_proto_transformer';
25import {Utils} from 'parsers/perfetto/utils';
26import {DENYLIST_PROPERTIES} from 'parsers/surface_flinger/denylist_properties';
27import {EAGER_PROPERTIES} from 'parsers/surface_flinger/eager_properties';
28import {EntryHierarchyTreeFactory} from 'parsers/surface_flinger/entry_hierarchy_tree_factory';
29import {TamperedMessageType} from 'parsers/tampered_message_type';
30import root from 'protos/surfaceflinger/latest/json';
31import {perfetto} from 'protos/surfaceflinger/latest/static';
32import {
33  CustomQueryParserResultTypeMap,
34  CustomQueryType,
35  VisitableParserCustomQuery,
36} from 'trace/custom_query';
37import {EntriesRange} from 'trace/trace';
38import {TraceFile} from 'trace/trace_file';
39import {TraceType} from 'trace/trace_type';
40import {EnumFormatter, LAYER_ID_FORMATTER} from 'trace/tree_node/formatters';
41import {HierarchyTreeNode} from 'trace/tree_node/hierarchy_tree_node';
42import {WasmEngineProxy} from 'trace_processor/wasm_engine_proxy';
43
44export class ParserSurfaceFlinger extends AbstractParser<HierarchyTreeNode> {
45  private static readonly CUSTOM_FORMATTERS = new Map([
46    ['cropLayerId', LAYER_ID_FORMATTER],
47    ['zOrderRelativeOf', LAYER_ID_FORMATTER],
48    [
49      'hwcCompositionType',
50      new EnumFormatter(perfetto.protos.HwcCompositionType),
51    ],
52  ]);
53
54  private static readonly LayersTraceFileProto = TamperedMessageType.tamper(
55    root.lookupType('perfetto.protos.LayersTraceFileProto'),
56  );
57  private static readonly entryField =
58    ParserSurfaceFlinger.LayersTraceFileProto.fields['entry'];
59  private static readonly layerField = assertDefined(
60    ParserSurfaceFlinger.entryField.tamperedMessageType?.fields['layers']
61      .tamperedMessageType,
62  ).fields['layers'];
63
64  static readonly Operations = {
65    SetFormattersLayer: new SetFormatters(
66      ParserSurfaceFlinger.layerField,
67      ParserSurfaceFlinger.CUSTOM_FORMATTERS,
68    ),
69    TranslateIntDefLayer: new TranslateIntDef(ParserSurfaceFlinger.layerField),
70    AddDefaultsLayerEager: new AddDefaults(
71      ParserSurfaceFlinger.layerField,
72      EAGER_PROPERTIES,
73    ),
74    AddDefaultsLayerLazy: new AddDefaults(
75      ParserSurfaceFlinger.layerField,
76      undefined,
77      EAGER_PROPERTIES.concat(DENYLIST_PROPERTIES),
78    ),
79    SetFormattersEntry: new SetFormatters(
80      ParserSurfaceFlinger.entryField,
81      ParserSurfaceFlinger.CUSTOM_FORMATTERS,
82    ),
83    TranslateIntDefEntry: new TranslateIntDef(ParserSurfaceFlinger.entryField),
84    AddDefaultsEntryEager: new AddDefaults(ParserSurfaceFlinger.entryField, [
85      'displays',
86    ]),
87    AddDefaultsEntryLazy: new AddDefaults(
88      ParserSurfaceFlinger.entryField,
89      undefined,
90      DENYLIST_PROPERTIES,
91    ),
92  };
93
94  private readonly factory = new EntryHierarchyTreeFactory();
95  private layersSnapshotProtoTransformer: FakeProtoTransformer;
96  private layerProtoTransformer: FakeProtoTransformer;
97
98  constructor(
99    traceFile: TraceFile,
100    traceProcessor: WasmEngineProxy,
101    timestampConverter: ParserTimestampConverter,
102  ) {
103    super(traceFile, traceProcessor, timestampConverter);
104    this.layersSnapshotProtoTransformer = new FakeProtoTransformer(
105      assertDefined(ParserSurfaceFlinger.entryField.tamperedMessageType),
106    );
107    this.layerProtoTransformer = new FakeProtoTransformer(
108      assertDefined(ParserSurfaceFlinger.layerField.tamperedMessageType),
109    );
110  }
111
112  override getTraceType(): TraceType {
113    return TraceType.SURFACE_FLINGER;
114  }
115
116  override async getEntry(index: number): Promise<HierarchyTreeNode> {
117    let snapshotProto = await Utils.queryEntry(
118      this.traceProcessor,
119      this.getTableName(),
120      this.entryIndexToRowIdMap,
121      index,
122    );
123    snapshotProto =
124      this.layersSnapshotProtoTransformer.transform(snapshotProto);
125
126    const layerProtos = (await this.querySnapshotLayers(index)).map(
127      (layerProto) => this.layerProtoTransformer.transform(layerProto),
128    );
129
130    return this.factory.makeEntryHierarchyTree(
131      snapshotProto,
132      layerProtos,
133      ParserSurfaceFlinger,
134    );
135  }
136
137  override async customQuery<Q extends CustomQueryType>(
138    type: Q,
139    entriesRange: EntriesRange,
140  ): Promise<CustomQueryParserResultTypeMap[Q]> {
141    return new VisitableParserCustomQuery(type)
142      .visit(CustomQueryType.VSYNCID, async () => {
143        return Utils.queryVsyncId(
144          this.traceProcessor,
145          this.getTableName(),
146          this.entryIndexToRowIdMap,
147          entriesRange,
148        );
149      })
150      .visit(CustomQueryType.SF_LAYERS_ID_AND_NAME, async () => {
151        const sql = `
152        SELECT DISTINCT group_concat(value) AS id_and_name FROM (
153          SELECT sfl.id AS id, args.key AS key, args.display_value AS value
154          FROM surfaceflinger_layer AS sfl
155          INNER JOIN args ON sfl.arg_set_id = args.arg_set_id
156          WHERE (args.key = 'id' OR args.key = 'name')
157          ORDER BY key
158        )
159        GROUP BY id;
160      `;
161        const queryResult = await this.traceProcessor.query(sql).waitAllRows();
162        const result: CustomQueryParserResultTypeMap[CustomQueryType.SF_LAYERS_ID_AND_NAME] =
163          [];
164        for (const it = queryResult.iter({}); it.valid(); it.next()) {
165          const idAndName = it.get('id_and_name') as string;
166          const indexDelimiter = idAndName.indexOf(',');
167          assertTrue(
168            indexDelimiter > 0,
169            () => `Unexpected value in query result: ${idAndName}`,
170          );
171          const id = Number(idAndName.slice(0, indexDelimiter));
172          const name = idAndName.slice(indexDelimiter + 1);
173          result.push({id, name});
174        }
175        return result;
176      })
177      .getResult();
178  }
179
180  protected override getTableName(): string {
181    return 'surfaceflinger_layers_snapshot';
182  }
183
184  private async querySnapshotLayers(
185    index: number,
186  ): Promise<perfetto.protos.ILayerProto[]> {
187    const layerIdToBuilder = new Map<number, FakeProtoBuilder>();
188    const getBuilder = (layerId: number) => {
189      if (!layerIdToBuilder.has(layerId)) {
190        layerIdToBuilder.set(layerId, new FakeProtoBuilder());
191      }
192      return assertDefined(layerIdToBuilder.get(layerId));
193    };
194
195    const sql = `
196      SELECT
197          sfl.snapshot_id,
198          sfl.id as layer_id,
199          args.key,
200          args.value_type,
201          args.int_value,
202          args.string_value,
203          args.real_value
204      FROM
205          surfaceflinger_layer as sfl
206          INNER JOIN args ON sfl.arg_set_id = args.arg_set_id
207      WHERE snapshot_id = ${this.entryIndexToRowIdMap[index]};
208    `;
209    const result = await this.traceProcessor.query(sql).waitAllRows();
210
211    for (const it = result.iter({}); it.valid(); it.next()) {
212      const builder = getBuilder(it.get('layer_id') as number);
213      builder.addArg(
214        it.get('key') as string,
215        it.get('value_type') as string,
216        it.get('int_value') as bigint | undefined,
217        it.get('real_value') as number | undefined,
218        it.get('string_value') as string | undefined,
219      );
220    }
221
222    const layerProtos: perfetto.protos.ILayerProto[] = [];
223    layerIdToBuilder.forEach((builder) => {
224      layerProtos.push(builder.build());
225    });
226
227    return layerProtos;
228  }
229}
230