blob: 1f1b81b5161e9d259b033c61e887463d309aac67 [file]
/*
* 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