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 {RectsComputation} from 'parsers/surface_flinger/computations/rects_computation';
27import {VisibilityPropertiesComputation} from 'parsers/surface_flinger/computations/visibility_properties_computation';
28import {ZOrderPathsComputation} from 'parsers/surface_flinger/computations/z_order_paths_computation';
29import {HierarchyTreeBuilderSf} from 'parsers/surface_flinger/hierarchy_tree_builder_sf';
30import {ParserSfUtils} from 'parsers/surface_flinger/parser_surface_flinger_utils';
31import {TamperedMessageType} from 'parsers/tampered_message_type';
32import root from 'protos/surfaceflinger/latest/json';
33import {perfetto} from 'protos/surfaceflinger/latest/static';
34import {
35  CustomQueryParserResultTypeMap,
36  CustomQueryType,
37  VisitableParserCustomQuery,
38} from 'trace/custom_query';
39import {EntriesRange} from 'trace/trace';
40import {TraceFile} from 'trace/trace_file';
41import {TraceType} from 'trace/trace_type';
42import {EnumFormatter, LAYER_ID_FORMATTER} from 'trace/tree_node/formatters';
43import {HierarchyTreeNode} from 'trace/tree_node/hierarchy_tree_node';
44import {PropertiesProvider} from 'trace/tree_node/properties_provider';
45import {PropertiesProviderBuilder} from 'trace/tree_node/properties_provider_builder';
46import {WasmEngineProxy} from 'trace_processor/wasm_engine_proxy';
47
48export class ParserSurfaceFlinger extends AbstractParser<HierarchyTreeNode> {
49  private static readonly CUSTOM_FORMATTERS = new Map([
50    ['cropLayerId', LAYER_ID_FORMATTER],
51    ['zOrderRelativeOf', LAYER_ID_FORMATTER],
52    [
53      'hwcCompositionType',
54      new EnumFormatter(perfetto.protos.HwcCompositionType),
55    ],
56  ]);
57
58  private static readonly LayersTraceFileProto = TamperedMessageType.tamper(
59    root.lookupType('perfetto.protos.LayersTraceFileProto'),
60  );
61  private static readonly entryField =
62    ParserSurfaceFlinger.LayersTraceFileProto.fields['entry'];
63  private static readonly layerField = assertDefined(
64    ParserSurfaceFlinger.entryField.tamperedMessageType?.fields['layers']
65      .tamperedMessageType,
66  ).fields['layers'];
67
68  private static readonly Operations = {
69    SetFormattersLayer: new SetFormatters(
70      ParserSurfaceFlinger.layerField,
71      ParserSurfaceFlinger.CUSTOM_FORMATTERS,
72    ),
73    TranslateIntDefLayer: new TranslateIntDef(ParserSurfaceFlinger.layerField),
74    AddDefaultsLayerEager: new AddDefaults(
75      ParserSurfaceFlinger.layerField,
76      ParserSfUtils.EAGER_PROPERTIES,
77    ),
78    AddDefaultsLayerLazy: new AddDefaults(
79      ParserSurfaceFlinger.layerField,
80      undefined,
81      ParserSfUtils.EAGER_PROPERTIES.concat(ParserSfUtils.DENYLIST_PROPERTIES),
82    ),
83    SetFormattersEntry: new SetFormatters(
84      ParserSurfaceFlinger.entryField,
85      ParserSurfaceFlinger.CUSTOM_FORMATTERS,
86    ),
87    TranslateIntDefEntry: new TranslateIntDef(ParserSurfaceFlinger.entryField),
88    AddDefaultsEntryEager: new AddDefaults(ParserSurfaceFlinger.entryField, [
89      'displays',
90    ]),
91    AddDefaultsEntryLazy: new AddDefaults(
92      ParserSurfaceFlinger.entryField,
93      undefined,
94      ParserSfUtils.DENYLIST_PROPERTIES,
95    ),
96  };
97
98  private layersSnapshotProtoTransformer: FakeProtoTransformer;
99  private layerProtoTransformer: FakeProtoTransformer;
100
101  constructor(
102    traceFile: TraceFile,
103    traceProcessor: WasmEngineProxy,
104    timestampConverter: ParserTimestampConverter,
105  ) {
106    super(traceFile, traceProcessor, timestampConverter);
107    this.layersSnapshotProtoTransformer = new FakeProtoTransformer(
108      assertDefined(ParserSurfaceFlinger.entryField.tamperedMessageType),
109    );
110    this.layerProtoTransformer = new FakeProtoTransformer(
111      assertDefined(ParserSurfaceFlinger.layerField.tamperedMessageType),
112    );
113  }
114
115  override getTraceType(): TraceType {
116    return TraceType.SURFACE_FLINGER;
117  }
118
119  override async getEntry(index: number): Promise<HierarchyTreeNode> {
120    let snapshotProto = await Utils.queryEntry(
121      this.traceProcessor,
122      this.getTableName(),
123      this.entryIndexToRowIdMap,
124      index,
125    );
126    snapshotProto =
127      this.layersSnapshotProtoTransformer.transform(snapshotProto);
128
129    const layerProtos = (await this.querySnapshotLayers(index)).map(
130      (layerProto) => this.layerProtoTransformer.transform(layerProto),
131    );
132
133    return this.makeHierarchyTree(snapshotProto, layerProtos);
134  }
135
136  override async customQuery<Q extends CustomQueryType>(
137    type: Q,
138    entriesRange: EntriesRange,
139  ): Promise<CustomQueryParserResultTypeMap[Q]> {
140    return new VisitableParserCustomQuery(type)
141      .visit(CustomQueryType.VSYNCID, async () => {
142        return Utils.queryVsyncId(
143          this.traceProcessor,
144          this.getTableName(),
145          this.entryIndexToRowIdMap,
146          entriesRange,
147        );
148      })
149      .visit(CustomQueryType.SF_LAYERS_ID_AND_NAME, async () => {
150        const sql = `
151        SELECT DISTINCT group_concat(value) AS id_and_name FROM (
152          SELECT sfl.id AS id, args.key AS key, args.display_value AS value
153          FROM surfaceflinger_layer AS sfl
154          INNER JOIN args ON sfl.arg_set_id = args.arg_set_id
155          WHERE (args.key = 'id' OR args.key = 'name')
156          ORDER BY key
157        )
158        GROUP BY id;
159      `;
160        const queryResult = await this.traceProcessor.query(sql).waitAllRows();
161        const result: CustomQueryParserResultTypeMap[CustomQueryType.SF_LAYERS_ID_AND_NAME] =
162          [];
163        for (const it = queryResult.iter({}); it.valid(); it.next()) {
164          const idAndName = it.get('id_and_name') as string;
165          const indexDelimiter = idAndName.indexOf(',');
166          assertTrue(
167            indexDelimiter > 0,
168            () => `Unexpected value in query result: ${idAndName}`,
169          );
170          const id = Number(idAndName.slice(0, indexDelimiter));
171          const name = idAndName.slice(indexDelimiter + 1);
172          result.push({id, name});
173        }
174        return result;
175      })
176      .getResult();
177  }
178
179  protected override getTableName(): string {
180    return 'surfaceflinger_layers_snapshot';
181  }
182
183  private makeHierarchyTree(
184    snapshotProto: perfetto.protos.ILayersSnapshotProto,
185    layerProtos: perfetto.protos.ILayerProto[],
186  ): HierarchyTreeNode {
187    const excludesCompositionState =
188      snapshotProto?.excludesCompositionState ?? false;
189    const addExcludesCompositionState = excludesCompositionState
190      ? ParserSfUtils.OPERATIONS.AddExcludesCompositionStateTrue
191      : ParserSfUtils.OPERATIONS.AddExcludesCompositionStateFalse;
192
193    const processed = new Map<number, number>();
194
195    const layers: PropertiesProvider[] = layerProtos.map(
196      (layer: perfetto.protos.ILayerProto) => {
197        const duplicateCount = processed.get(assertDefined(layer.id)) ?? 0;
198        processed.set(assertDefined(layer.id), duplicateCount + 1);
199        const eagerProperties = ParserSfUtils.makeEagerPropertiesTree(
200          layer,
201          duplicateCount,
202        );
203        const lazyPropertiesStrategy =
204          ParserSfUtils.makeLayerLazyPropertiesStrategy(layer, duplicateCount); //TODO: fetch these lazily instead
205
206        const layerProps = new PropertiesProviderBuilder()
207          .setEagerProperties(eagerProperties)
208          .setLazyPropertiesStrategy(lazyPropertiesStrategy)
209          .setCommonOperations([
210            ParserSurfaceFlinger.Operations.SetFormattersLayer,
211            ParserSurfaceFlinger.Operations.TranslateIntDefLayer,
212          ])
213          .setEagerOperations([
214            ParserSurfaceFlinger.Operations.AddDefaultsLayerEager,
215            ParserSfUtils.OPERATIONS.AddCompositionType,
216            ParserSfUtils.OPERATIONS.UpdateTransforms,
217            ParserSfUtils.OPERATIONS.AddVerboseFlags,
218            addExcludesCompositionState,
219          ])
220          .setLazyOperations([
221            ParserSurfaceFlinger.Operations.AddDefaultsLayerLazy,
222          ])
223          .build();
224        return layerProps;
225      },
226    );
227
228    const entry = new PropertiesProviderBuilder()
229      .setEagerProperties(
230        ParserSfUtils.makeEntryEagerPropertiesTree(snapshotProto),
231      )
232      .setLazyPropertiesStrategy(
233        ParserSfUtils.makeEntryLazyPropertiesStrategy(snapshotProto),
234      )
235      .setCommonOperations([
236        ParserSfUtils.OPERATIONS.AddDisplayProperties,
237        ParserSurfaceFlinger.Operations.SetFormattersEntry,
238        ParserSurfaceFlinger.Operations.TranslateIntDefEntry,
239      ])
240      .setEagerOperations([
241        ParserSurfaceFlinger.Operations.AddDefaultsEntryEager,
242      ])
243      .setLazyOperations([
244        ParserSurfaceFlinger.Operations.AddDefaultsEntryLazy,
245        ParserSfUtils.OPERATIONS.AddDisplayProperties,
246      ])
247      .build();
248
249    return new HierarchyTreeBuilderSf()
250      .setRoot(entry)
251      .setChildren(layers)
252      .setComputations([
253        new ZOrderPathsComputation(),
254        new VisibilityPropertiesComputation(),
255        new RectsComputation(),
256      ])
257      .build();
258  }
259
260  private async querySnapshotLayers(
261    index: number,
262  ): Promise<perfetto.protos.ILayerProto[]> {
263    const layerIdToBuilder = new Map<number, FakeProtoBuilder>();
264    const getBuilder = (layerId: number) => {
265      if (!layerIdToBuilder.has(layerId)) {
266        layerIdToBuilder.set(layerId, new FakeProtoBuilder());
267      }
268      return assertDefined(layerIdToBuilder.get(layerId));
269    };
270
271    const sql = `
272      SELECT
273          sfl.snapshot_id,
274          sfl.id as layer_id,
275          args.key,
276          args.value_type,
277          args.int_value,
278          args.string_value,
279          args.real_value
280      FROM
281          surfaceflinger_layer as sfl
282          INNER JOIN args ON sfl.arg_set_id = args.arg_set_id
283      WHERE snapshot_id = ${this.entryIndexToRowIdMap[index]};
284    `;
285    const result = await this.traceProcessor.query(sql).waitAllRows();
286
287    for (const it = result.iter({}); it.valid(); it.next()) {
288      const builder = getBuilder(it.get('layer_id') as number);
289      builder.addArg(
290        it.get('key') as string,
291        it.get('value_type') as string,
292        it.get('int_value') as bigint | undefined,
293        it.get('real_value') as number | undefined,
294        it.get('string_value') as string | undefined,
295      );
296    }
297
298    const layerProtos: perfetto.protos.ILayerProto[] = [];
299    layerIdToBuilder.forEach((builder) => {
300      layerProtos.push(builder.build());
301    });
302
303    return layerProtos;
304  }
305}
306