| /* |
| * Copyright (C) 2026 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/plugins/flamegraph/flamegraph_function.h" |
| |
| #include <sqlite3.h> |
| #include <algorithm> |
| #include <cstdint> |
| #include <initializer_list> |
| #include <memory> |
| #include <optional> |
| #include <string> |
| #include <string_view> |
| #include <utility> |
| #include <vector> |
| |
| #include "perfetto/base/compiler.h" |
| #include "perfetto/base/logging.h" |
| #include "perfetto/base/status.h" |
| #include "perfetto/ext/base/regex.h" |
| #include "perfetto/ext/base/status_macros.h" |
| #include "perfetto/ext/base/status_or.h" |
| #include "perfetto/public/compiler.h" |
| #include "src/trace_processor/containers/string_pool.h" |
| #include "src/trace_processor/core/common/storage_types.h" |
| #include "src/trace_processor/core/dataframe/adhoc_dataframe_builder.h" |
| #include "src/trace_processor/core/plugin/plugin.h" |
| #include "src/trace_processor/core/tree/tree.h" |
| #include "src/trace_processor/core/tree/tree_from_dataframe.h" |
| #include "src/trace_processor/perfetto_sql/engine/perfetto_sql_connection.h" |
| #include "src/trace_processor/perfetto_sql/engine/sqlite_dataframe_builder.h" |
| #include "src/trace_processor/plugins/flamegraph/flamegraph.h" |
| #include "src/trace_processor/sqlite/bindings/sqlite_column.h" |
| #include "src/trace_processor/sqlite/bindings/sqlite_result.h" |
| #include "src/trace_processor/sqlite/bindings/sqlite_type.h" |
| #include "src/trace_processor/sqlite/module_state_manager.h" |
| #include "src/trace_processor/sqlite/sql_source.h" |
| #include "src/trace_processor/sqlite/sqlite_tagged_args.h" |
| #include "src/trace_processor/sqlite/sqlite_utils.h" |
| #include "src/trace_processor/storage/trace_storage.h" |
| #include "src/trace_processor/types/trace_processor_context.h" |
| |
| namespace perfetto::trace_processor { |
| namespace { |
| |
| constexpr char kConfigPointerType[] = "FLAMEGRAPH_CONFIG"; |
| |
| constexpr uint32_t kConfigColumn = 0; |
| constexpr uint32_t kTreeIdColumn = 1; |
| constexpr uint32_t kTreeParentIdColumn = 2; |
| constexpr uint32_t kDepthColumn = 3; |
| constexpr uint32_t kNameColumn = 4; |
| constexpr uint32_t kSelfValueColumn = 5; |
| constexpr uint32_t kCumulativeValueColumn = 6; |
| constexpr uint32_t kParentCumulativeValueColumn = 7; |
| constexpr uint32_t kXStartColumn = 8; |
| constexpr uint32_t kXEndColumn = 9; |
| constexpr uint32_t kPropertyColumnStart = 10; |
| constexpr uint32_t kArgCount = 1; |
| constexpr int kSuperRootConstraint = SQLITE_INDEX_CONSTRAINT_FUNCTION + 1; |
| constexpr int kSuperRootPlan = 1; |
| |
| bool IsArgumentColumn(size_t column) { |
| return column == kConfigColumn; |
| } |
| |
| base::StatusOr<FlamegraphRegexSpec> ParseRegexSpec(const char* context, |
| sqlite3_value* pattern, |
| sqlite3_value* flags) { |
| ASSIGN_OR_RETURN(std::string parsed_pattern, |
| sqlite::TaggedArgText(context, pattern)); |
| ASSIGN_OR_RETURN(std::string parsed_flags, |
| sqlite::TaggedArgText(context, flags)); |
| if (!parsed_flags.empty() && parsed_flags != "i") { |
| return base::ErrStatus("%s: unknown regex flags '%s'", context, |
| parsed_flags.c_str()); |
| } |
| return FlamegraphRegexSpec{std::move(parsed_pattern), parsed_flags == "i"}; |
| } |
| |
| // Parses a text token which must be one of the named values. |
| template <typename T> |
| base::StatusOr<T> ParseToken( |
| const char* context, |
| sqlite3_value* value, |
| std::initializer_list<std::pair<const char*, T>> tokens) { |
| ASSIGN_OR_RETURN(std::string token, sqlite::TaggedArgText(context, value)); |
| for (const auto& [name, parsed] : tokens) { |
| if (token == name) { |
| return parsed; |
| } |
| } |
| return base::ErrStatus("%s: unknown value '%s'", context, token.c_str()); |
| } |
| |
| base::StatusOr<base::Regex> CompileRegex(const FlamegraphRegexSpec& spec) { |
| const auto sensitivity = spec.case_insensitive |
| ? base::Regex::CaseSensitivity::kInsensitive |
| : base::Regex::CaseSensitivity::kSensitive; |
| return base::Regex::Create(spec.pattern, sensitivity); |
| } |
| |
| base::StatusOr<const core::Tree::Column*> ResolveColumn( |
| const core::Tree& tree, |
| const char* context, |
| const std::string& name) { |
| auto column = tree.Find(name); |
| if (!column) { |
| return base::ErrStatus("%s: column '%s' does not exist", context, |
| name.c_str()); |
| } |
| return *column; |
| } |
| |
| base::StatusOr<flamegraph::Config> ResolveConfig(const core::Tree& source, |
| StringPool& pool, |
| const FlamegraphQuery& query) { |
| flamegraph::Config config(pool); |
| config.view = query.view; |
| if (query.view_pattern) { |
| ASSIGN_OR_RETURN(config.view_pattern, CompileRegex(*query.view_pattern)); |
| } |
| ASSIGN_OR_RETURN(config.name, |
| ResolveColumn(source, "flamegraph: name", "name")); |
| // Null is the type of a column with no values at all: every frame is |
| // unnamed, which the builder handles the same way it handles a string |
| // column whose rows happen to all be null. |
| if (!config.name->type.Is<core::String>() && |
| !config.name->type.Is<core::Null>()) { |
| return base::ErrStatus("flamegraph: name column must be a string"); |
| } |
| for (const std::string& name : query.grouping_columns) { |
| ASSIGN_OR_RETURN( |
| auto column, |
| ResolveColumn(source, "flamegraph: grouping column", name)); |
| config.grouping_columns.push_back(column); |
| } |
| for (const std::string& name : query.value_columns) { |
| ASSIGN_OR_RETURN(auto column, |
| ResolveColumn(source, "flamegraph: value column", name)); |
| if (!flamegraph::IsNumericColumn(*column)) { |
| return base::ErrStatus("flamegraph: value columns must be numeric"); |
| } |
| config.value_columns.push_back(column); |
| } |
| if (config.value_columns.empty()) { |
| return base::ErrStatus("flamegraph: at least one value column is required"); |
| } |
| for (const FlamegraphFilterSpec& filter : query.filters) { |
| ASSIGN_OR_RETURN(auto regex, CompileRegex(filter.regex)); |
| switch (filter.kind) { |
| case FlamegraphFilterSpec::Kind::kShowStack: |
| config.show_stack_filters.push_back(std::move(regex)); |
| break; |
| case FlamegraphFilterSpec::Kind::kHideStack: |
| config.hide_stack_filters.push_back(std::move(regex)); |
| break; |
| case FlamegraphFilterSpec::Kind::kHideFrame: |
| config.hide_frame_filters.push_back(std::move(regex)); |
| break; |
| } |
| } |
| if (config.show_stack_filters.size() > |
| flamegraph::Config::kMaxShowStackFilters) { |
| return base::ErrStatus("flamegraph: too many SHOW_STACK filters"); |
| } |
| for (const FlamegraphAggregateSpec& aggregate : query.aggregate_columns) { |
| if (aggregate.output_name != aggregate.input_name) { |
| return base::ErrStatus( |
| "flamegraph: aggregate output must match its source column"); |
| } |
| ASSIGN_OR_RETURN(auto column, |
| ResolveColumn(source, "flamegraph: aggregate column", |
| aggregate.input_name)); |
| if (column->type.Is<core::Null>()) { |
| // A column with no values aggregates to no values under any mode. |
| } else if (aggregate.aggregate == flamegraph::Config::Aggregate::kSum && |
| !flamegraph::IsNumericColumn(*column)) { |
| return base::ErrStatus( |
| "flamegraph: SUM aggregate columns must be numeric"); |
| } |
| config.aggregate_columns.push_back( |
| {column, aggregate.aggregate, aggregate.output_name}); |
| } |
| return std::move(config); |
| } |
| |
| bool IsReservedOutputName(std::string_view name) { |
| return name == "in_config" || name == "_tree_id" || |
| name == "_tree_parent_id" || name == "depth" || name == "name" || |
| name == "self_value" || name == "cumulative_value" || |
| name == "parent_cumulative_value" || name == "x_start" || |
| name == "x_end"; |
| } |
| |
| // Number of fixed leading source columns: id, parent_id, name, value. |
| constexpr uint32_t kFixedSourceColumns = 4; |
| |
| base::StatusOr<std::unique_ptr<FlamegraphOperator::State>> LoadSource( |
| FlamegraphOperator::Context* context, |
| const char* source) { |
| std::string sql = "SELECT * FROM "; |
| sql.append(source); |
| ASSIGN_OR_RETURN( |
| auto execution, |
| context->connection->ExecuteUntilLastStatement( |
| SqlSource::FromTraceProcessorImplementation(std::move(sql)))); |
| sqlite3_stmt* stmt = execution.stmt.sqlite_stmt(); |
| const uint32_t column_count = sqlite::column::Count(stmt); |
| if (column_count < kFixedSourceColumns) { |
| return base::ErrStatus( |
| "flamegraph: source must contain id, parent_id, name and value"); |
| } |
| std::vector<std::string> names; |
| names.reserve(column_count); |
| for (uint32_t column = 0; column < column_count; ++column) { |
| names.emplace_back(sqlite::column::Name(stmt, column)); |
| } |
| if (names[0] != "id" || names[1] != "parent_id" || names[2] != "name" || |
| names[3] != "value") { |
| return base::ErrStatus( |
| "flamegraph: first source columns must be id, parent_id, name, value"); |
| } |
| for (uint32_t column = kFixedSourceColumns; column < column_count; ++column) { |
| if (IsReservedOutputName(names[column])) { |
| return base::ErrStatus("flamegraph: reserved property column '%s'", |
| names[column].c_str()); |
| } |
| } |
| |
| SqliteDataframeBuilderOptions options; |
| options.nullability = dataframe::NullabilityType::kDenseNull; |
| ASSIGN_OR_RETURN(auto builder, BuildRuntimeDataframeFromSqliteStatement( |
| context->pool, names, &execution.stmt, |
| "flamegraph", std::move(options))); |
| ASSIGN_OR_RETURN(core::Tree tree, core::BuildTree(std::move(builder))); |
| auto state = std::make_unique<FlamegraphOperator::State>(); |
| state->pool = context->pool; |
| state->source = std::move(tree); |
| state->property_names.assign(names.begin() + kFixedSourceColumns, |
| names.end()); |
| for (uint32_t column = kFixedSourceColumns; column < column_count; ++column) { |
| if (flamegraph::IsNumericColumn(state->source.columns[column])) { |
| state->candidate_value_names.push_back(names[column]); |
| } |
| } |
| const core::Tree::Column* value = *state->source.Find("value"); |
| if (state->source.row_count > 0 && !flamegraph::IsNumericColumn(*value)) { |
| return base::ErrStatus("flamegraph: value column must be numeric"); |
| } |
| for (uint32_t row = 0; row < state->source.row_count; ++row) { |
| if (value->null_bv.size() > 0 && !value->null_bv.is_set(row)) { |
| continue; |
| } |
| state->source_value_sum += |
| value->type.Is<core::Int64>() |
| ? static_cast<double>(value->unchecked_data<int64_t>()[row]) |
| : value->unchecked_data<double>()[row]; |
| } |
| state->candidate_column_start = |
| kPropertyColumnStart + |
| static_cast<uint32_t>(state->property_names.size()); |
| state->output_column_count = |
| state->candidate_column_start + |
| 2 * static_cast<uint32_t>(state->candidate_value_names.size()); |
| return std::move(state); |
| } |
| |
| std::string QuoteIdentifier(std::string_view identifier) { |
| std::string quoted(1, '"'); |
| for (char c : identifier) { |
| quoted.push_back(c); |
| if (c == '"') { |
| quoted.push_back('"'); |
| } |
| } |
| quoted.push_back('"'); |
| return quoted; |
| } |
| |
| std::string BuildSchema(const FlamegraphOperator::State& state) { |
| std::string schema = R"( |
| CREATE TABLE x( |
| in_config BLOB HIDDEN, |
| _tree_id BIGINT, |
| _tree_parent_id BIGINT, |
| depth BIGINT, |
| name TEXT, |
| self_value, |
| cumulative_value, |
| parent_cumulative_value, |
| x_start, |
| x_end)"; |
| for (const std::string& property : state.property_names) { |
| schema.append(",\n "); |
| schema.append(QuoteIdentifier(property)); |
| } |
| for (const std::string& value : state.candidate_value_names) { |
| schema.append(",\n "); |
| schema.append(QuoteIdentifier("self_" + value)); |
| schema.append(",\n "); |
| schema.append(QuoteIdentifier("cumulative_" + value)); |
| } |
| schema.append(R"(, |
| PRIMARY KEY(_tree_id) |
| ) WITHOUT ROWID |
| )"); |
| return schema; |
| } |
| |
| int DeclareSchema(sqlite3* db, |
| const FlamegraphOperator::State& state, |
| char** error) { |
| std::string schema = BuildSchema(state); |
| int result = sqlite3_declare_vtab(db, schema.c_str()); |
| if (result != SQLITE_OK && error) { |
| *error = sqlite3_mprintf("flamegraph: failed to declare schema"); |
| } |
| return result; |
| } |
| |
| void ReturnTreeColumn(sqlite3_context* context, |
| StringPool& pool, |
| const core::Tree::Column* column, |
| uint32_t row) { |
| if (!column || (column->null_bv.size() > 0 && !column->null_bv.is_set(row))) { |
| sqlite::result::Null(context); |
| return; |
| } |
| if (column->type.Is<core::Int64>()) { |
| sqlite::result::Long(context, column->unchecked_data<int64_t>()[row]); |
| return; |
| } |
| if (column->type.Is<core::Double>()) { |
| sqlite::result::Double(context, column->unchecked_data<double>()[row]); |
| return; |
| } |
| StringPool::Id id = column->unchecked_data<StringPool::Id>()[row]; |
| if (id.is_null()) { |
| sqlite::result::Null(context); |
| return; |
| } |
| const auto value = pool.Get(id); |
| // Static (no copy) is safe: StringPool storage is never freed while the |
| // connection is alive. |
| sqlite::result::StaticString(context, value.c_str(), |
| static_cast<int>(value.size())); |
| } |
| |
| } // namespace |
| |
| void FlamegraphFindFunction::Step(sqlite3_context* context, |
| int, |
| sqlite3_value**) { |
| sqlite::result::Error( |
| context, "__intrinsic_flamegraph_find must constrain a flamegraph"); |
| } |
| |
| void FlamegraphConfigFunction::Step(sqlite3_context* context, |
| int argc, |
| sqlite3_value** argv) { |
| FlamegraphQuery query; |
| base::Status status = sqlite::ParseTaggedArgs( |
| kName, argc, argv, |
| { |
| {"view", 1, |
| [&](sqlite3_value** values) -> base::Status { |
| using Config = flamegraph::Config; |
| ASSIGN_OR_RETURN( |
| query.view, |
| ParseToken<Config::View>( |
| "flamegraph_config: view", values[0], |
| {{"TOP_DOWN", Config::View(Config::TopDown{})}, |
| {"BOTTOM_UP", Config::View(Config::BottomUp{})}, |
| {"PIVOT", Config::View(Config::Pivot{})}, |
| {"FROM_FRAME", Config::View(Config::FromFrame{})}})); |
| return base::OkStatus(); |
| }}, |
| {"view_pattern", 2, |
| [&](sqlite3_value** values) -> base::Status { |
| ASSIGN_OR_RETURN(query.view_pattern, |
| ParseRegexSpec("flamegraph_config: view_pattern", |
| values[0], values[1])); |
| return base::OkStatus(); |
| }}, |
| {"filter", 3, |
| [&](sqlite3_value** values) -> base::Status { |
| using Kind = FlamegraphFilterSpec::Kind; |
| ASSIGN_OR_RETURN( |
| Kind kind, |
| ParseToken<Kind>("flamegraph_config: filter", values[0], |
| {{"SHOW_STACK", Kind::kShowStack}, |
| {"HIDE_STACK", Kind::kHideStack}, |
| {"HIDE_FRAME", Kind::kHideFrame}})); |
| ASSIGN_OR_RETURN(auto regex, |
| ParseRegexSpec("flamegraph_config: filter", |
| values[1], values[2])); |
| query.filters.push_back({kind, std::move(regex)}); |
| return base::OkStatus(); |
| }}, |
| {"grouping", 1, |
| [&](sqlite3_value** values) -> base::Status { |
| ASSIGN_OR_RETURN(std::string name, |
| sqlite::TaggedArgText( |
| "flamegraph_config: grouping", values[0])); |
| query.grouping_columns.push_back(std::move(name)); |
| return base::OkStatus(); |
| }}, |
| {"value", 1, |
| [&](sqlite3_value** values) -> base::Status { |
| ASSIGN_OR_RETURN( |
| std::string name, |
| sqlite::TaggedArgText("flamegraph_config: value", values[0])); |
| query.value_columns.push_back(std::move(name)); |
| return base::OkStatus(); |
| }}, |
| {"aggregate", 3, |
| [&](sqlite3_value** values) -> base::Status { |
| using Aggregate = flamegraph::Config::Aggregate; |
| ASSIGN_OR_RETURN( |
| Aggregate aggregate, |
| ParseToken<Aggregate>( |
| "flamegraph_config: aggregate", values[0], |
| {{"SUM", Aggregate::kSum}, |
| {"ONE_OR_SUMMARY", Aggregate::kOneOrSummary}, |
| {"CONCAT_WITH_COMMA", Aggregate::kConcatWithComma}})); |
| ASSIGN_OR_RETURN(std::string input, |
| sqlite::TaggedArgText( |
| "flamegraph_config: aggregate", values[1])); |
| ASSIGN_OR_RETURN(std::string output, |
| sqlite::TaggedArgText( |
| "flamegraph_config: aggregate", values[2])); |
| query.aggregate_columns.push_back( |
| {aggregate, std::move(input), std::move(output)}); |
| return base::OkStatus(); |
| }}, |
| }); |
| if (!status.ok()) { |
| sqlite::utils::SetError(context, status); |
| return; |
| } |
| const bool is_pattern_view = |
| query.view.IsAnyOf<flamegraph::Config::PatternViews>(); |
| if (is_pattern_view != query.view_pattern.has_value()) { |
| sqlite::utils::SetError( |
| context, |
| "flamegraph_config: PIVOT and FROM_FRAME require a view pattern"); |
| return; |
| } |
| sqlite::utils::MovePointerResult(context, std::move(query), |
| kConfigPointerType); |
| } |
| |
| int FlamegraphOperator::Create(sqlite3* db, |
| void* raw_context, |
| int argc, |
| const char* const* argv, |
| sqlite3_vtab** output, |
| char** error) { |
| if (argc != 4) { |
| *error = sqlite3_mprintf("flamegraph: wrong number of arguments"); |
| return SQLITE_ERROR; |
| } |
| auto* context = GetContext(raw_context); |
| auto state = LoadSource(context, argv[3]); |
| if (!state.ok()) { |
| *error = sqlite3_mprintf("%s", state.status().c_message()); |
| return SQLITE_ERROR; |
| } |
| if (int result = DeclareSchema(db, **state, error); result != SQLITE_OK) { |
| return result; |
| } |
| std::unique_ptr<Vtab> vtab = std::make_unique<Vtab>(); |
| vtab->state = context->OnCreate(argc, argv, std::move(*state)); |
| *output = vtab.release(); |
| return SQLITE_OK; |
| } |
| |
| int FlamegraphOperator::Destroy(sqlite3_vtab* raw_vtab) { |
| std::unique_ptr<Vtab> vtab(GetVtab(raw_vtab)); |
| sqlite::ModuleStateManager<FlamegraphOperator>::OnDestroy(vtab->state); |
| return SQLITE_OK; |
| } |
| |
| int FlamegraphOperator::Connect(sqlite3* db, |
| void* raw_context, |
| int argc, |
| const char* const* argv, |
| sqlite3_vtab** output, |
| char** error) { |
| auto* context = GetContext(raw_context); |
| auto* managed_state = context->OnConnect(argc, argv); |
| if (!managed_state) { |
| *error = sqlite3_mprintf("flamegraph: state not found"); |
| return SQLITE_ERROR; |
| } |
| State* state = |
| sqlite::ModuleStateManager<FlamegraphOperator>::GetState(managed_state); |
| if (int result = DeclareSchema(db, *state, error); result != SQLITE_OK) { |
| return result; |
| } |
| std::unique_ptr<Vtab> vtab = std::make_unique<Vtab>(); |
| vtab->state = managed_state; |
| *output = vtab.release(); |
| return SQLITE_OK; |
| } |
| |
| int FlamegraphOperator::Disconnect(sqlite3_vtab* raw_vtab) { |
| std::unique_ptr<Vtab> vtab(GetVtab(raw_vtab)); |
| return SQLITE_OK; |
| } |
| |
| int FlamegraphOperator::BestIndex(sqlite3_vtab*, sqlite3_index_info* info) { |
| base::Status status = sqlite::utils::ValidateFunctionArguments( |
| info, kArgCount, IsArgumentColumn); |
| if (!status.ok()) { |
| return SQLITE_CONSTRAINT; |
| } |
| for (int i = 0; i < info->nConstraint; ++i) { |
| const auto& input = info->aConstraint[i]; |
| if (input.usable && input.iColumn == kTreeIdColumn && |
| input.op == kSuperRootConstraint) { |
| info->aConstraintUsage[i].argvIndex = 2; |
| info->aConstraintUsage[i].omit = true; |
| info->idxNum = kSuperRootPlan; |
| break; |
| } |
| } |
| return SQLITE_OK; |
| } |
| |
| int FlamegraphOperator::Open(sqlite3_vtab*, sqlite3_vtab_cursor** output) { |
| std::unique_ptr<Cursor> cursor = std::make_unique<Cursor>(); |
| *output = cursor.release(); |
| return SQLITE_OK; |
| } |
| |
| int FlamegraphOperator::Close(sqlite3_vtab_cursor* raw_cursor) { |
| std::unique_ptr<Cursor> cursor(GetCursor(raw_cursor)); |
| return SQLITE_OK; |
| } |
| |
| int FlamegraphOperator::Filter(sqlite3_vtab_cursor* raw_cursor, |
| int plan, |
| const char*, |
| int argc, |
| sqlite3_value** argv) { |
| Cursor* cursor = GetCursor(raw_cursor); |
| Vtab* vtab = GetVtab(cursor->pVtab); |
| State* state = |
| sqlite::ModuleStateManager<FlamegraphOperator>::GetState(vtab->state); |
| PERFETTO_CHECK(argc == kArgCount || |
| (plan == kSuperRootPlan && argc == kArgCount + 1)); |
| cursor->result.reset(); |
| cursor->columns.clear(); |
| cursor->layout = {}; |
| cursor->row = 0; |
| cursor->super_root_only = plan == kSuperRootPlan; |
| if (cursor->super_root_only) { |
| const unsigned char* selector = sqlite3_value_text(argv[1]); |
| if (!selector || |
| strcmp(reinterpret_cast<const char*>(selector), "SUPER_ROOT") != 0) { |
| return sqlite::utils::SetError(vtab, "flamegraph: unknown find selector"); |
| } |
| } |
| |
| auto query = sqlite::utils::TakeMovePointerValue<FlamegraphQuery>( |
| argv[0], kConfigPointerType, "flamegraph"); |
| if (!query.ok()) { |
| return sqlite::utils::SetError(vtab, query.status()); |
| } |
| if (query->value_columns.empty()) { |
| return sqlite::utils::SetError( |
| vtab, "flamegraph: at least one value column is required"); |
| } |
| if (cursor->super_root_only || state->source.row_count == 0) { |
| return SQLITE_OK; |
| } |
| |
| auto config = ResolveConfig(state->source, *state->pool, *query); |
| if (!config.ok()) { |
| return sqlite::utils::SetError(vtab, config.status()); |
| } |
| // TODO: Cache metric-independent intermediate results across metric queries, |
| // not complete query results. In particular, preserve the computed tree shape |
| // when only the value columns change so switching metrics only recomputes |
| // metric-dependent values. |
| auto result = flamegraph::Build(state->source, *config); |
| if (!result.ok()) { |
| return sqlite::utils::SetError(vtab, result.status()); |
| } |
| cursor->result = std::make_unique<core::Tree>(std::move(*result)); |
| |
| // Resolve every schema column against the result tree once so that Column() |
| // is a plain array lookup per cell. |
| const core::Tree& tree = *cursor->result; |
| cursor->columns.assign(state->output_column_count, nullptr); |
| auto resolve = [&](uint32_t index, const std::string& name) { |
| auto found = tree.Find(name); |
| cursor->columns[index] = found ? *found : nullptr; |
| }; |
| const std::string& primary = query->value_columns[0]; |
| resolve(kDepthColumn, "depth"); |
| resolve(kNameColumn, "name"); |
| resolve(kSelfValueColumn, "self_" + primary); |
| resolve(kCumulativeValueColumn, "cumulative_" + primary); |
| for (uint32_t i = 0; i < state->property_names.size(); ++i) { |
| resolve(kPropertyColumnStart + i, state->property_names[i]); |
| } |
| for (uint32_t i = 0; i < state->candidate_value_names.size(); ++i) { |
| const std::string& name = state->candidate_value_names[i]; |
| resolve(state->candidate_column_start + 2 * i, "self_" + name); |
| resolve(state->candidate_column_start + 2 * i + 1, "cumulative_" + name); |
| } |
| |
| const core::Tree::Column* cumulative = |
| cursor->columns[kCumulativeValueColumn]; |
| const core::Tree::Column* depth = cursor->columns[kDepthColumn]; |
| PERFETTO_CHECK(cumulative && depth); |
| cursor->value_is_int = cumulative->type.Is<core::Int64>(); |
| cursor->layout = flamegraph::ComputeLayout(tree, *cumulative, *depth); |
| return SQLITE_OK; |
| } |
| |
| int FlamegraphOperator::Next(sqlite3_vtab_cursor* raw_cursor) { |
| GetCursor(raw_cursor)->row++; |
| return SQLITE_OK; |
| } |
| |
| int FlamegraphOperator::Eof(sqlite3_vtab_cursor* raw_cursor) { |
| Cursor* cursor = GetCursor(raw_cursor); |
| if (cursor->super_root_only) { |
| return cursor->row > 0; |
| } |
| return cursor->row >= cursor->layout.node.size(); |
| } |
| |
| int FlamegraphOperator::Column(sqlite3_vtab_cursor* raw_cursor, |
| sqlite3_context* context, |
| int raw_column) { |
| Cursor* cursor = GetCursor(raw_cursor); |
| Vtab* vtab = GetVtab(cursor->pVtab); |
| State* state = |
| sqlite::ModuleStateManager<FlamegraphOperator>::GetState(vtab->state); |
| const auto column = static_cast<uint32_t>(raw_column); |
| if (cursor->super_root_only) { |
| if (column == kTreeIdColumn) { |
| sqlite::result::Long(context, 0); |
| } else if (column == kDepthColumn) { |
| sqlite::result::Long(context, 0); |
| } else if (column == kCumulativeValueColumn) { |
| sqlite::result::Double(context, state->source_value_sum); |
| } else { |
| sqlite::result::Null(context); |
| } |
| return SQLITE_OK; |
| } |
| const uint32_t row = cursor->row; |
| switch (column) { |
| case kTreeIdColumn: |
| sqlite::result::Long(context, row); |
| return SQLITE_OK; |
| case kTreeParentIdColumn: { |
| const uint32_t parent = cursor->layout.parent_row[row]; |
| if (parent == core::Tree::kNullParent) { |
| sqlite::result::Null(context); |
| } else { |
| sqlite::result::Long(context, parent); |
| } |
| return SQLITE_OK; |
| } |
| case kParentCumulativeValueColumn: { |
| const uint32_t parent = cursor->layout.parent_row[row]; |
| if (parent == core::Tree::kNullParent) { |
| sqlite::result::Null(context); |
| } else { |
| ReturnTreeColumn(context, *state->pool, |
| cursor->columns[kCumulativeValueColumn], |
| cursor->layout.node[parent]); |
| } |
| return SQLITE_OK; |
| } |
| case kXStartColumn: |
| case kXEndColumn: { |
| double x = cursor->layout.x_start[row]; |
| if (column == kXEndColumn) { |
| // A node's width is its cumulative value clamped to zero. |
| const core::Tree::Column* cumulative = |
| cursor->columns[kCumulativeValueColumn]; |
| const uint32_t node = cursor->layout.node[row]; |
| const double value = |
| cursor->value_is_int |
| ? static_cast<double>( |
| cumulative->unchecked_data<int64_t>()[node]) |
| : cumulative->unchecked_data<double>()[node]; |
| x += std::max(value, 0.0); |
| } |
| if (cursor->value_is_int) { |
| sqlite::result::Long(context, static_cast<int64_t>(x)); |
| } else { |
| sqlite::result::Double(context, x); |
| } |
| return SQLITE_OK; |
| } |
| default: |
| break; |
| } |
| if (column >= cursor->columns.size()) { |
| return sqlite::utils::SetError(vtab, "flamegraph: bad column"); |
| } |
| ReturnTreeColumn(context, *state->pool, cursor->columns[column], |
| cursor->layout.node[row]); |
| return SQLITE_OK; |
| } |
| |
| int FlamegraphOperator::Rowid(sqlite3_vtab_cursor*, sqlite_int64*) { |
| return SQLITE_ERROR; |
| } |
| |
| int FlamegraphOperator::FindFunction(sqlite3_vtab*, |
| int, |
| const char* name, |
| FindFunctionFn** function, |
| void**) { |
| if (base::CaseInsensitiveEqual(name, FlamegraphFindFunction::kName)) { |
| *function = [](sqlite3_context* context, int, sqlite3_value**) { |
| sqlite::result::Error(context, "super-root constraint was not consumed"); |
| }; |
| return kSuperRootConstraint; |
| } |
| return SQLITE_OK; |
| } |
| |
| namespace flamegraph { |
| namespace { |
| |
| class FlamegraphPlugin : public Plugin<FlamegraphPlugin> { |
| public: |
| ~FlamegraphPlugin() override; |
| |
| void RegisterFunctions(PerfettoSqlConnection*, |
| std::vector<FunctionRegistration>& out) override { |
| StringPool* pool = trace_context_->storage->mutable_string_pool(); |
| out.push_back(MakeFunctionRegistration<FlamegraphConfigFunction>(pool)); |
| out.push_back(MakeFunctionRegistration<FlamegraphFindFunction>(nullptr)); |
| } |
| |
| void RegisterSqliteModules( |
| PerfettoSqlConnection* connection, |
| std::vector<SqliteModuleRegistration>& out) override { |
| StringPool* pool = trace_context_->storage->mutable_string_pool(); |
| out.push_back(MakeSqliteModule<FlamegraphOperator>( |
| "__intrinsic_flamegraph", |
| std::make_unique<FlamegraphOperator::Context>(connection, pool))); |
| } |
| }; |
| |
| FlamegraphPlugin::~FlamegraphPlugin() = default; |
| |
| } // namespace |
| |
| void RegisterPlugin() { |
| static PluginRegistration registration( |
| []() -> std::unique_ptr<PluginBase> { |
| return std::make_unique<FlamegraphPlugin>(); |
| }, |
| FlamegraphPlugin::kPluginId, FlamegraphPlugin::kDepIds.data(), |
| FlamegraphPlugin::kDepIds.size()); |
| base::ignore_result(registration); |
| } |
| |
| } // namespace flamegraph |
| } // namespace perfetto::trace_processor |