tp: Dump the hierarchy path for SF layers. (#2169) So we can avoid running slow recursive queries for trace search. Ensure we handle recursive hierarchies. Bug: 411363817 Bug: 431939202 Test: tools/diff_test_trace_processor.py out/linux_clang_release/trace_processor_shell --name-filter="SurfaceFlingerLayer|PerfettoTable:winscope"
diff --git a/Android.bp b/Android.bp index b6d62fe..2154eff 100644 --- a/Android.bp +++ b/Android.bp
@@ -14879,6 +14879,7 @@ "src/trace_processor/perfetto_sql/intrinsics/table_functions/flamegraph_construction_algorithms.cc", "src/trace_processor/perfetto_sql/intrinsics/table_functions/table_info.cc", "src/trace_processor/perfetto_sql/intrinsics/table_functions/winscope_proto_to_args_with_defaults.cc", + "src/trace_processor/perfetto_sql/intrinsics/table_functions/winscope_surfaceflinger_hierarchy_paths.cc", ], }
diff --git a/BUILD b/BUILD index e662182..ece3be6 100644 --- a/BUILD +++ b/BUILD
@@ -2881,6 +2881,8 @@ "src/trace_processor/perfetto_sql/intrinsics/table_functions/table_info.h", "src/trace_processor/perfetto_sql/intrinsics/table_functions/winscope_proto_to_args_with_defaults.cc", "src/trace_processor/perfetto_sql/intrinsics/table_functions/winscope_proto_to_args_with_defaults.h", + "src/trace_processor/perfetto_sql/intrinsics/table_functions/winscope_surfaceflinger_hierarchy_paths.cc", + "src/trace_processor/perfetto_sql/intrinsics/table_functions/winscope_surfaceflinger_hierarchy_paths.h", ], )
diff --git a/src/trace_processor/importers/proto/winscope/surfaceflinger_layers_parser.cc b/src/trace_processor/importers/proto/winscope/surfaceflinger_layers_parser.cc index b9dfdfe..3f27b0b 100644 --- a/src/trace_processor/importers/proto/winscope/surfaceflinger_layers_parser.cc +++ b/src/trace_processor/importers/proto/winscope/surfaceflinger_layers_parser.cc
@@ -178,21 +178,23 @@ visibility, const std::unordered_map<int32_t, LayerDecoder>& layers_by_id, const surfaceflinger_layers::SurfaceFlingerRects& rects) { + auto* string_pool = + context_->trace_processor_context_->storage->mutable_string_pool(); + tables::SurfaceFlingerLayerTable::Row layer; layer.snapshot_id = snapshot_id; - layer.base64_proto_id = - context_->trace_processor_context_->storage->mutable_string_pool() - ->InternString( - base::StringView(base::Base64Encode(blob.data, blob.size))) - .raw_id(); + layer.base64_proto_id = string_pool + ->InternString(base::StringView( + base::Base64Encode(blob.data, blob.size))) + .raw_id(); LayerDecoder layer_decoder(blob); if (layer_decoder.has_id()) { layer.layer_id = layer_decoder.id(); } + if (layer_decoder.has_name()) { layer.layer_name = - context_->trace_processor_context_->storage->mutable_string_pool() - ->InternString(base::StringView(layer_decoder.name())); + string_pool->InternString(base::StringView(layer_decoder.name())); } if (layer_decoder.has_parent()) { layer.parent = layer_decoder.parent();
diff --git a/src/trace_processor/perfetto_sql/intrinsics/table_functions/BUILD.gn b/src/trace_processor/perfetto_sql/intrinsics/table_functions/BUILD.gn index 24d131a..201096e 100644 --- a/src/trace_processor/perfetto_sql/intrinsics/table_functions/BUILD.gn +++ b/src/trace_processor/perfetto_sql/intrinsics/table_functions/BUILD.gn
@@ -46,6 +46,8 @@ sources += [ "winscope_proto_to_args_with_defaults.cc", "winscope_proto_to_args_with_defaults.h", + "winscope_surfaceflinger_hierarchy_paths.cc", + "winscope_surfaceflinger_hierarchy_paths.h", ] } deps = [ @@ -70,7 +72,11 @@ "../../engine", ] if (enable_perfetto_winscope) { - deps += [ "../../../util:winscope_proto_mapping" ] + deps += [ + "../../../../../protos/perfetto/trace/android:winscope_regular_zero", + "../../../importers/proto/winscope:full", + "../../../util:winscope_proto_mapping", + ] } public_deps = [ ":interface" ] }
diff --git a/src/trace_processor/perfetto_sql/intrinsics/table_functions/tables.py b/src/trace_processor/perfetto_sql/intrinsics/table_functions/tables.py index 4e6941c..95bf4a1 100644 --- a/src/trace_processor/perfetto_sql/intrinsics/table_functions/tables.py +++ b/src/trace_processor/perfetto_sql/intrinsics/table_functions/tables.py
@@ -28,6 +28,7 @@ from src.trace_processor.tables.profiler_tables import STACK_PROFILE_FRAME_TABLE from src.trace_processor.tables.slice_tables import SLICE_TABLE from src.trace_processor.tables.track_tables import TRACK_TABLE +from src.trace_processor.tables.winscope_tables import SURFACE_FLINGER_LAYERS_SNAPSHOT_TABLE TABLE_INFO_TABLE = Table( python_module=__file__, @@ -182,6 +183,17 @@ ], ) +SURFACE_FLINGER_HIERARCHY_PATH_TABLE = Table( + python_module=__file__, + class_name="WinscopeSurfaceFlingerHierarchyPathTable", + sql_name="__intrinsic_winscope_surfaceflinger_hierarchy_path", + columns=[ + C('snapshot_id', CppUint32()), + C('layer_id', CppUint32()), + C('ancestor_id', CppUint32()), + ], +) + # Keep this list sorted. ALL_TABLES = [ ANCESTOR_STACK_PROFILE_CALLSITE_TABLE, @@ -192,5 +204,6 @@ EXPERIMENTAL_ANNOTATED_CALLSTACK_TABLE, EXPERIMENTAL_SLICE_LAYOUT_TABLE, SLICE_SUBSET_TABLE, + SURFACE_FLINGER_HIERARCHY_PATH_TABLE, TABLE_INFO_TABLE, ]
diff --git a/src/trace_processor/perfetto_sql/intrinsics/table_functions/winscope_surfaceflinger_hierarchy_paths.cc b/src/trace_processor/perfetto_sql/intrinsics/table_functions/winscope_surfaceflinger_hierarchy_paths.cc new file mode 100644 index 0000000..b46a38e --- /dev/null +++ b/src/trace_processor/perfetto_sql/intrinsics/table_functions/winscope_surfaceflinger_hierarchy_paths.cc
@@ -0,0 +1,166 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "src/trace_processor/perfetto_sql/intrinsics/table_functions/winscope_surfaceflinger_hierarchy_paths.h" +#include <sys/types.h> + +#include <cstddef> +#include <cstdint> +#include <memory> +#include <optional> +#include <string> +#include <unordered_map> +#include <vector> + +#include "perfetto/base/logging.h" +#include "perfetto/base/status.h" +#include "perfetto/ext/base/base64.h" +#include "perfetto/ext/base/string_view.h" +#include "perfetto/protozero/field.h" +#include "perfetto/trace_processor/basic_types.h" +#include "protos/perfetto/trace/android/surfaceflinger_layers.pbzero.h" +#include "src/trace_processor/containers/string_pool.h" +#include "src/trace_processor/dataframe/dataframe.h" +#include "src/trace_processor/dataframe/specs.h" +#include "src/trace_processor/dataframe/typed_cursor.h" +#include "src/trace_processor/importers/proto/winscope/surfaceflinger_layers_extractor.h" +#include "src/trace_processor/perfetto_sql/engine/perfetto_sql_engine.h" +#include "src/trace_processor/perfetto_sql/intrinsics/table_functions/static_table_function.h" +#include "src/trace_processor/perfetto_sql/intrinsics/table_functions/tables_py.h" +#include "src/trace_processor/tables/winscope_tables_py.h" + +namespace perfetto::trace_processor { + +namespace { +using LayerDecoder = protos::pbzero::LayerProto::Decoder; + +std::vector<int32_t> GetHierarchyPath( + const LayerDecoder& layer_decoder, + const std::unordered_map<int32_t, LayerDecoder>& layers_by_id) { + std::vector<int32_t> hierarchy_path = {layer_decoder.id()}; + auto pos = layers_by_id.find(layer_decoder.parent()); + while (pos != layers_by_id.end()) { + const auto& layer_dec = pos->second; + auto id = layer_dec.id(); + auto parent = layer_dec.parent(); + if (id == parent) { + // handles recursive hierarchies + break; + } + hierarchy_path.push_back(id); + pos = layers_by_id.find(parent); + } + return hierarchy_path; +} + +base::Status InsertRows( + const dataframe::Dataframe& snapshot_table, + tables::WinscopeSurfaceFlingerHierarchyPathTable* paths_table, + StringPool* string_pool) { + constexpr auto kSpec = tables::SurfaceFlingerLayersSnapshotTable::kSpec; + + for (uint32_t i = 0; i < snapshot_table.row_count(); ++i) { + auto base64_proto_id = + snapshot_table + .GetCellUnchecked<tables::SurfaceFlingerLayersSnapshotTable:: + ColumnIndex::base64_proto_id>(kSpec, i); + PERFETTO_CHECK(base64_proto_id.has_value()); + + const auto raw_proto = + string_pool->Get(StringPool::Id::Raw(*base64_proto_id)); + const auto blob = *base::Base64Decode(raw_proto); + const auto cb = protozero::ConstBytes{ + reinterpret_cast<const uint8_t*>(blob.data()), blob.size()}; + protos::pbzero::LayersSnapshotProto::Decoder snapshot(cb); + protos::pbzero::LayersProto::Decoder layers(snapshot.layers()); + + const auto& layers_by_id = + winscope::surfaceflinger_layers::ExtractLayersById(layers); + + for (auto it = layers.layers(); it; ++it) { + LayerDecoder layer(*it); + if (!layer.has_id()) { + continue; + } + auto layer_id = static_cast<uint32_t>(layer.id()); + + auto path = GetHierarchyPath(layer, layers_by_id); + for (auto path_it = path.rbegin(); path_it != path.rend(); ++path_it) { + tables::WinscopeSurfaceFlingerHierarchyPathTable::Row row; + row.snapshot_id = i; + row.layer_id = layer_id; + row.ancestor_id = static_cast<uint32_t>(*path_it); + paths_table->Insert(row); + } + } + } + return base::OkStatus(); +} +} // namespace + +WinscopeSurfaceFlingerHierarchyPaths::Cursor::Cursor( + StringPool* string_pool, + const PerfettoSqlEngine* engine) + : string_pool_(string_pool), engine_(engine), table_(string_pool) {} + +bool WinscopeSurfaceFlingerHierarchyPaths::Cursor::Run( + const std::vector<SqlValue>& arguments) { + PERFETTO_DCHECK(arguments.size() == 0); + auto table_name = tables::SurfaceFlingerLayersSnapshotTable::Name(); + const dataframe::Dataframe* static_table_from_engine = + engine_->GetDataframeOrNull(table_name); + if (!static_table_from_engine) { + return OnFailure(base::ErrStatus("Failed to find %s table.", + std::string(table_name).c_str())); + } + + table_.Clear(); + + base::Status status = + InsertRows(*static_table_from_engine, &table_, string_pool_); + if (!status.ok()) { + return OnFailure(status); + } + return OnSuccess(&table_.dataframe()); +} + +WinscopeSurfaceFlingerHierarchyPaths::WinscopeSurfaceFlingerHierarchyPaths( + StringPool* string_pool, + const PerfettoSqlEngine* engine) + : string_pool_(string_pool), engine_(engine) {} + +std::unique_ptr<StaticTableFunction::Cursor> +WinscopeSurfaceFlingerHierarchyPaths::MakeCursor() { + return std::make_unique<Cursor>(string_pool_, engine_); +} + +dataframe::DataframeSpec WinscopeSurfaceFlingerHierarchyPaths::CreateSpec() { + return tables::WinscopeSurfaceFlingerHierarchyPathTable::kSpec + .ToUntypedDataframeSpec(); +} + +std::string WinscopeSurfaceFlingerHierarchyPaths::TableName() { + return tables::WinscopeSurfaceFlingerHierarchyPathTable::Name(); +} + +uint32_t WinscopeSurfaceFlingerHierarchyPaths::GetArgumentCount() const { + return 0; +} +uint32_t WinscopeSurfaceFlingerHierarchyPaths::EstimateRowCount() { + // 1 path per 100 elements per 100 entries + return 10000; +} +} // namespace perfetto::trace_processor
diff --git a/src/trace_processor/perfetto_sql/intrinsics/table_functions/winscope_surfaceflinger_hierarchy_paths.h b/src/trace_processor/perfetto_sql/intrinsics/table_functions/winscope_surfaceflinger_hierarchy_paths.h new file mode 100644 index 0000000..b13de9b --- /dev/null +++ b/src/trace_processor/perfetto_sql/intrinsics/table_functions/winscope_surfaceflinger_hierarchy_paths.h
@@ -0,0 +1,66 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef SRC_TRACE_PROCESSOR_PERFETTO_SQL_INTRINSICS_TABLE_FUNCTIONS_WINSCOPE_SURFACEFLINGER_HIERARCHY_PATHS_H_ +#define SRC_TRACE_PROCESSOR_PERFETTO_SQL_INTRINSICS_TABLE_FUNCTIONS_WINSCOPE_SURFACEFLINGER_HIERARCHY_PATHS_H_ + +#include <cstddef> +#include <cstdint> +#include <memory> +#include <string> +#include <vector> + +#include "perfetto/trace_processor/basic_types.h" +#include "src/trace_processor/containers/string_pool.h" +#include "src/trace_processor/dataframe/specs.h" +#include "src/trace_processor/perfetto_sql/engine/perfetto_sql_engine.h" +#include "src/trace_processor/perfetto_sql/intrinsics/table_functions/static_table_function.h" +#include "src/trace_processor/perfetto_sql/intrinsics/table_functions/tables_py.h" + +namespace perfetto::trace_processor { + +class TraceProcessorContext; + +class WinscopeSurfaceFlingerHierarchyPaths : public StaticTableFunction { + public: + class Cursor : public StaticTableFunction::Cursor { + public: + explicit Cursor(StringPool* string_pool, const PerfettoSqlEngine* engine); + bool Run(const std::vector<SqlValue>& arguments) override; + + private: + StringPool* string_pool_ = nullptr; + const PerfettoSqlEngine* engine_ = nullptr; + tables::WinscopeSurfaceFlingerHierarchyPathTable table_; + }; + + explicit WinscopeSurfaceFlingerHierarchyPaths(StringPool*, + const PerfettoSqlEngine*); + + std::unique_ptr<StaticTableFunction::Cursor> MakeCursor() override; + dataframe::DataframeSpec CreateSpec() override; + std::string TableName() override; + uint32_t GetArgumentCount() const override; + uint32_t EstimateRowCount() override; + + private: + StringPool* string_pool_ = nullptr; + const PerfettoSqlEngine* engine_ = nullptr; +}; + +} // namespace perfetto::trace_processor + +#endif // SRC_TRACE_PROCESSOR_PERFETTO_SQL_INTRINSICS_TABLE_FUNCTIONS_WINSCOPE_SURFACEFLINGER_HIERARCHY_PATHS_H_
diff --git a/src/trace_processor/tables/winscope_tables.py b/src/trace_processor/tables/winscope_tables.py index 74e222b..7448286 100644 --- a/src/trace_processor/tables/winscope_tables.py +++ b/src/trace_processor/tables/winscope_tables.py
@@ -275,7 +275,7 @@ C('z_order_relative_of', CppOptional(CppInt64())), C('is_missing_z_parent', CppInt64()), C('layer_rect_id', CppOptional(CppTableId(WINSCOPE_TRACE_RECT_TABLE))), - C('input_rect_id', CppOptional(CppTableId(WINSCOPE_TRACE_RECT_TABLE))) + C('input_rect_id', CppOptional(CppTableId(WINSCOPE_TRACE_RECT_TABLE))), ], tabledoc=TableDoc( doc='SurfaceFlinger layer',
diff --git a/src/trace_processor/trace_processor_impl.cc b/src/trace_processor/trace_processor_impl.cc index ccacb02..80bc67a 100644 --- a/src/trace_processor/trace_processor_impl.cc +++ b/src/trace_processor/trace_processor_impl.cc
@@ -123,7 +123,6 @@ #include "src/trace_processor/perfetto_sql/intrinsics/table_functions/experimental_slice_layout.h" #include "src/trace_processor/perfetto_sql/intrinsics/table_functions/static_table_function.h" #include "src/trace_processor/perfetto_sql/intrinsics/table_functions/table_info.h" -#include "src/trace_processor/perfetto_sql/intrinsics/table_functions/winscope_proto_to_args_with_defaults.h" #include "src/trace_processor/perfetto_sql/stdlib/stdlib.h" #include "src/trace_processor/sqlite/bindings/sqlite_aggregate_function.h" #include "src/trace_processor/sqlite/bindings/sqlite_result.h" @@ -165,6 +164,7 @@ #if PERFETTO_BUILDFLAG(PERFETTO_ENABLE_WINSCOPE) #include "src/trace_processor/perfetto_sql/intrinsics/table_functions/winscope_proto_to_args_with_defaults.h" +#include "src/trace_processor/perfetto_sql/intrinsics/table_functions/winscope_surfaceflinger_hierarchy_paths.h" #endif namespace perfetto::trace_processor { @@ -1156,6 +1156,8 @@ #if PERFETTO_BUILDFLAG(PERFETTO_ENABLE_WINSCOPE) fns.emplace_back(std::make_unique<WinscopeProtoToArgsWithDefaults>( storage->mutable_string_pool(), engine, context)); + fns.emplace_back(std::make_unique<WinscopeSurfaceFlingerHierarchyPaths>( + storage->mutable_string_pool(), engine)); #endif if (config.enable_dev_features) {
diff --git a/test/trace_processor/diff_tests/parser/android/surfaceflinger_layers.textproto b/test/trace_processor/diff_tests/parser/android/surfaceflinger_layers.textproto index b8ba94d..18c1072 100644 --- a/test/trace_processor/diff_tests/parser/android/surfaceflinger_layers.textproto +++ b/test/trace_processor/diff_tests/parser/android/surfaceflinger_layers.textproto
@@ -289,6 +289,7 @@ excludes_composition_state: true layers { layers { parent: -1 } + layers { id: -2 parent: -2 } layers { id: 1 name: "layer1"
diff --git a/test/trace_processor/diff_tests/parser/android/tests_surfaceflinger_layers.py b/test/trace_processor/diff_tests/parser/android/tests_surfaceflinger_layers.py index bc3b17c..0048001 100644 --- a/test/trace_processor/diff_tests/parser/android/tests_surfaceflinger_layers.py +++ b/test/trace_processor/diff_tests/parser/android/tests_surfaceflinger_layers.py
@@ -121,7 +121,7 @@ input_rect_id FROM surfaceflinger_layer - LIMIT 7; + LIMIT 8; """, out=Csv(""" "id","snapshot_id","layer_id","layer_name","parent","corner_radius_tl","corner_radius_tr","corner_radius_bl","corner_radius_br","hwc_composition_type","z_order_relative_of","is_missing_z_parent","is_visible","layer_rect_id","input_rect_id" @@ -130,8 +130,9 @@ 2,1,3,"Display 0 name="Built-in Screen"#3","[NULL]",0.000000,0.000000,0.000000,0.000000,"[NULL]","[NULL]",0,0,"[NULL]","[NULL]" 3,1,4,"WindowedMagnification:0:31#4",3,0.000000,0.000000,0.000000,0.000000,"[NULL]","[NULL]",0,0,3,"[NULL]" 4,2,"[NULL]","[NULL]",-1,"[NULL]","[NULL]","[NULL]","[NULL]","[NULL]","[NULL]",0,0,"[NULL]","[NULL]" - 5,2,1,"layer1","[NULL]",1.000000,1.000000,1.000000,1.000000,2,"[NULL]",0,0,9,10 - 6,2,2,"layer2","[NULL]","[NULL]","[NULL]","[NULL]","[NULL]","[NULL]","[NULL]",0,1,11,12 + 5,2,-2,"[NULL]",-2,"[NULL]","[NULL]","[NULL]","[NULL]","[NULL]","[NULL]",0,0,"[NULL]","[NULL]" + 6,2,1,"layer1","[NULL]",1.000000,1.000000,1.000000,1.000000,2,"[NULL]",0,0,9,10 + 7,2,2,"layer2","[NULL]","[NULL]","[NULL]","[NULL]","[NULL]","[NULL]","[NULL]",0,1,11,12 """)) def test_tables_have_raw_protos(self): @@ -147,7 +148,7 @@ out=Csv(""" "COUNT(*)" 3 - 19 + 20 """)) def test_layer_args(self): @@ -158,7 +159,7 @@ args.key, args.display_value FROM surfaceflinger_layer AS sfl JOIN args ON sfl.arg_set_id = args.arg_set_id - WHERE sfl.id = 2 and (key like "screen_bounds%" or key like "visibility_reason%") + WHERE sfl.id = 2 AND (key GLOB "screen_bounds*" OR key GLOB "visibility_reason*") ORDER BY args.key """, out=Csv("""
diff --git a/test/trace_processor/diff_tests/syntax/table_tests.py b/test/trace_processor/diff_tests/syntax/table_tests.py index 82d9b50..9606c54 100644 --- a/test/trace_processor/diff_tests/syntax/table_tests.py +++ b/test/trace_processor/diff_tests/syntax/table_tests.py
@@ -708,3 +708,25 @@ "class_name_iid","class_name_iid",3,"[NULL]" "view_id","view_id","[NULL]","NO_ID" """)) + + def test_winscope_surfaceflinger_hierarchy_paths(self): + return DiffTestBlueprint( + trace=Path('../parser/android/surfaceflinger_layers.textproto'), + query=""" + SELECT * FROM __intrinsic_winscope_surfaceflinger_hierarchy_path() as tbl + ORDER BY tbl.id + LIMIT 10 + """, + out=Csv(""" + "id","snapshot_id","layer_id","ancestor_id" + 0,0,3,3 + 1,0,4,3 + 2,0,4,4 + 3,1,3,3 + 4,1,4,3 + 5,1,4,4 + 6,2,4294967294,4294967294 + 7,2,1,1 + 8,2,2,2 + 9,2,3,3 + """))