Merge "tools: fix amalgamated SDK breakage in sanitizer builds" into main
diff --git a/Android.bp b/Android.bp
index 91fce88..585df73 100644
--- a/Android.bp
+++ b/Android.bp
@@ -2376,6 +2376,7 @@
":perfetto_src_trace_processor_perfetto_sql_intrinsics_table_functions_interface",
":perfetto_src_trace_processor_perfetto_sql_intrinsics_table_functions_table_functions",
":perfetto_src_trace_processor_sorter_sorter",
+ ":perfetto_src_trace_processor_sqlite_bindings_bindings",
":perfetto_src_trace_processor_sqlite_query_constraints",
":perfetto_src_trace_processor_sqlite_sqlite",
":perfetto_src_trace_processor_storage_minimal",
@@ -11942,6 +11943,7 @@
srcs: [
"src/trace_processor/metrics/sql/android/ad_services_metric.sql",
"src/trace_processor/metrics/sql/android/android_anr.sql",
+ "src/trace_processor/metrics/sql/android/android_auto_multiuser.sql",
"src/trace_processor/metrics/sql/android/android_batt.sql",
"src/trace_processor/metrics/sql/android/android_binder.sql",
"src/trace_processor/metrics/sql/android/android_blocking_calls_cuj_metric.sql",
@@ -12221,6 +12223,7 @@
"src/trace_processor/perfetto_sql/intrinsics/table_functions/connected_flow.cc",
"src/trace_processor/perfetto_sql/intrinsics/table_functions/descendant.cc",
"src/trace_processor/perfetto_sql/intrinsics/table_functions/dfs.cc",
+ "src/trace_processor/perfetto_sql/intrinsics/table_functions/dfs_weight_bounded.cc",
"src/trace_processor/perfetto_sql/intrinsics/table_functions/dominator_tree.cc",
"src/trace_processor/perfetto_sql/intrinsics/table_functions/experimental_annotated_stack.cc",
"src/trace_processor/perfetto_sql/intrinsics/table_functions/experimental_counter_dur.cc",
@@ -12314,6 +12317,7 @@
"src/trace_processor/perfetto_sql/stdlib/android/battery_stats.sql",
"src/trace_processor/perfetto_sql/stdlib/android/binder.sql",
"src/trace_processor/perfetto_sql/stdlib/android/broadcasts.sql",
+ "src/trace_processor/perfetto_sql/stdlib/android/critical_blocking_calls.sql",
"src/trace_processor/perfetto_sql/stdlib/android/dvfs.sql",
"src/trace_processor/perfetto_sql/stdlib/android/freezer.sql",
"src/trace_processor/perfetto_sql/stdlib/android/garbage_collection.sql",
@@ -12442,6 +12446,11 @@
],
}
+// GN: //src/trace_processor/sqlite/bindings:bindings
+filegroup {
+ name: "perfetto_src_trace_processor_sqlite_bindings_bindings",
+}
+
// GN: //src/trace_processor/sqlite:query_constraints
filegroup {
name: "perfetto_src_trace_processor_sqlite_query_constraints",
@@ -14281,6 +14290,7 @@
":perfetto_src_trace_processor_rpc_unittests",
":perfetto_src_trace_processor_sorter_sorter",
":perfetto_src_trace_processor_sorter_unittests",
+ ":perfetto_src_trace_processor_sqlite_bindings_bindings",
":perfetto_src_trace_processor_sqlite_query_constraints",
":perfetto_src_trace_processor_sqlite_sqlite",
":perfetto_src_trace_processor_sqlite_unittests",
@@ -14995,6 +15005,7 @@
":perfetto_src_trace_processor_rpc_rpc",
":perfetto_src_trace_processor_rpc_stdiod",
":perfetto_src_trace_processor_sorter_sorter",
+ ":perfetto_src_trace_processor_sqlite_bindings_bindings",
":perfetto_src_trace_processor_sqlite_query_constraints",
":perfetto_src_trace_processor_sqlite_sqlite",
":perfetto_src_trace_processor_storage_minimal",
@@ -15427,6 +15438,7 @@
":perfetto_src_trace_processor_perfetto_sql_intrinsics_table_functions_interface",
":perfetto_src_trace_processor_perfetto_sql_intrinsics_table_functions_table_functions",
":perfetto_src_trace_processor_sorter_sorter",
+ ":perfetto_src_trace_processor_sqlite_bindings_bindings",
":perfetto_src_trace_processor_sqlite_query_constraints",
":perfetto_src_trace_processor_sqlite_sqlite",
":perfetto_src_trace_processor_storage_minimal",
diff --git a/BUILD b/BUILD
index 8eedd8d..d678720 100644
--- a/BUILD
+++ b/BUILD
@@ -259,6 +259,7 @@
":src_trace_processor_perfetto_sql_intrinsics_table_functions_tables",
":src_trace_processor_rpc_rpc",
":src_trace_processor_sorter_sorter",
+ ":src_trace_processor_sqlite_bindings_bindings",
":src_trace_processor_sqlite_query_constraints",
":src_trace_processor_sqlite_sqlite",
":src_trace_processor_storage_minimal",
@@ -1945,6 +1946,7 @@
srcs = [
"src/trace_processor/metrics/sql/android/ad_services_metric.sql",
"src/trace_processor/metrics/sql/android/android_anr.sql",
+ "src/trace_processor/metrics/sql/android/android_auto_multiuser.sql",
"src/trace_processor/metrics/sql/android/android_batt.sql",
"src/trace_processor/metrics/sql/android/android_binder.sql",
"src/trace_processor/metrics/sql/android/android_blocking_calls_cuj_metric.sql",
@@ -2304,6 +2306,8 @@
"src/trace_processor/perfetto_sql/intrinsics/table_functions/descendant.h",
"src/trace_processor/perfetto_sql/intrinsics/table_functions/dfs.cc",
"src/trace_processor/perfetto_sql/intrinsics/table_functions/dfs.h",
+ "src/trace_processor/perfetto_sql/intrinsics/table_functions/dfs_weight_bounded.cc",
+ "src/trace_processor/perfetto_sql/intrinsics/table_functions/dfs_weight_bounded.h",
"src/trace_processor/perfetto_sql/intrinsics/table_functions/dominator_tree.cc",
"src/trace_processor/perfetto_sql/intrinsics/table_functions/dominator_tree.h",
"src/trace_processor/perfetto_sql/intrinsics/table_functions/experimental_annotated_stack.cc",
@@ -2384,6 +2388,7 @@
"src/trace_processor/perfetto_sql/stdlib/android/battery_stats.sql",
"src/trace_processor/perfetto_sql/stdlib/android/binder.sql",
"src/trace_processor/perfetto_sql/stdlib/android/broadcasts.sql",
+ "src/trace_processor/perfetto_sql/stdlib/android/critical_blocking_calls.sql",
"src/trace_processor/perfetto_sql/stdlib/android/dvfs.sql",
"src/trace_processor/perfetto_sql/stdlib/android/freezer.sql",
"src/trace_processor/perfetto_sql/stdlib/android/garbage_collection.sql",
@@ -2635,6 +2640,17 @@
],
)
+# GN target: //src/trace_processor/sqlite/bindings:bindings
+perfetto_filegroup(
+ name = "src_trace_processor_sqlite_bindings_bindings",
+ srcs = [
+ "src/trace_processor/sqlite/bindings/sqlite_aggregate_function.h",
+ "src/trace_processor/sqlite/bindings/sqlite_module.h",
+ "src/trace_processor/sqlite/bindings/sqlite_result.h",
+ "src/trace_processor/sqlite/bindings/sqlite_window_function.h",
+ ],
+)
+
# GN target: //src/trace_processor/sqlite:query_constraints
perfetto_filegroup(
name = "src_trace_processor_sqlite_query_constraints",
@@ -2658,14 +2674,12 @@
"src/trace_processor/sqlite/sql_stats_table.h",
"src/trace_processor/sqlite/sqlite_engine.cc",
"src/trace_processor/sqlite/sqlite_engine.h",
- "src/trace_processor/sqlite/sqlite_result.h",
"src/trace_processor/sqlite/sqlite_table.cc",
"src/trace_processor/sqlite/sqlite_table.h",
"src/trace_processor/sqlite/sqlite_tokenizer.cc",
"src/trace_processor/sqlite/sqlite_tokenizer.h",
"src/trace_processor/sqlite/sqlite_utils.cc",
"src/trace_processor/sqlite/sqlite_utils.h",
- "src/trace_processor/sqlite/sqlite_window_function.h",
"src/trace_processor/sqlite/stats_table.cc",
"src/trace_processor/sqlite/stats_table.h",
],
@@ -5684,6 +5698,7 @@
":src_trace_processor_perfetto_sql_intrinsics_table_functions_table_functions",
":src_trace_processor_perfetto_sql_intrinsics_table_functions_tables",
":src_trace_processor_sorter_sorter",
+ ":src_trace_processor_sqlite_bindings_bindings",
":src_trace_processor_sqlite_query_constraints",
":src_trace_processor_sqlite_sqlite",
":src_trace_processor_storage_minimal",
@@ -5855,6 +5870,7 @@
":src_trace_processor_rpc_rpc",
":src_trace_processor_rpc_stdiod",
":src_trace_processor_sorter_sorter",
+ ":src_trace_processor_sqlite_bindings_bindings",
":src_trace_processor_sqlite_query_constraints",
":src_trace_processor_sqlite_sqlite",
":src_trace_processor_storage_minimal",
@@ -6078,6 +6094,7 @@
":src_trace_processor_perfetto_sql_intrinsics_table_functions_table_functions",
":src_trace_processor_perfetto_sql_intrinsics_table_functions_tables",
":src_trace_processor_sorter_sorter",
+ ":src_trace_processor_sqlite_bindings_bindings",
":src_trace_processor_sqlite_query_constraints",
":src_trace_processor_sqlite_sqlite",
":src_trace_processor_storage_minimal",
diff --git a/protos/perfetto/metrics/metrics.proto b/protos/perfetto/metrics/metrics.proto
index 5130b29..f06f085 100644
--- a/protos/perfetto/metrics/metrics.proto
+++ b/protos/perfetto/metrics/metrics.proto
@@ -116,7 +116,7 @@
// Root message for all Perfetto-based metrics.
//
-// Next id: 63
+// Next id: 64
message TraceMetrics {
reserved 4, 10, 13, 14, 16, 19;
@@ -290,6 +290,10 @@
// Android garbage collection metrics
optional AndroidGarbageCollectionUnaggMetric android_garbage_collection_unagg = 63;
+ // Multiuser - metrics for switching users.
+ // Specific for Android Auto
+ optional AndroidMultiuserMetric android_auto_multiuser = 64;
+
// Demo extensions.
extensions 450 to 499;
diff --git a/protos/perfetto/metrics/perfetto_merged_metrics.proto b/protos/perfetto/metrics/perfetto_merged_metrics.proto
index eb9ab74..b2c3392 100644
--- a/protos/perfetto/metrics/perfetto_merged_metrics.proto
+++ b/protos/perfetto/metrics/perfetto_merged_metrics.proto
@@ -2520,7 +2520,7 @@
// Root message for all Perfetto-based metrics.
//
-// Next id: 63
+// Next id: 64
message TraceMetrics {
reserved 4, 10, 13, 14, 16, 19;
@@ -2694,6 +2694,10 @@
// Android garbage collection metrics
optional AndroidGarbageCollectionUnaggMetric android_garbage_collection_unagg = 63;
+ // Multiuser - metrics for switching users.
+ // Specific for Android Auto
+ optional AndroidMultiuserMetric android_auto_multiuser = 64;
+
// Demo extensions.
extensions 450 to 499;
diff --git a/python/perfetto/trace_processor/metrics.descriptor b/python/perfetto/trace_processor/metrics.descriptor
index 5c04c0a..6b4b352 100644
--- a/python/perfetto/trace_processor/metrics.descriptor
+++ b/python/perfetto/trace_processor/metrics.descriptor
Binary files differ
diff --git a/src/trace_processor/importers/proto/system_probes_parser.cc b/src/trace_processor/importers/proto/system_probes_parser.cc
index 2654fdb..8812674 100644
--- a/src/trace_processor/importers/proto/system_probes_parser.cc
+++ b/src/trace_processor/importers/proto/system_probes_parser.cc
@@ -107,6 +107,10 @@
SystemProbesParser::SystemProbesParser(TraceProcessorContext* context)
: context_(context),
utid_name_id_(context->storage->InternString("utid")),
+ ns_unit_id_(context->storage->InternString("ns")),
+ bytes_unit_id_(context->storage->InternString("bytes")),
+ available_chunks_unit_id_(
+ context->storage->InternString("available chunks")),
num_forks_name_id_(context->storage->InternString("num_forks")),
num_irq_total_name_id_(context->storage->InternString("num_irq_total")),
num_softirq_total_name_id_(
@@ -208,6 +212,8 @@
context_->event_tracker->PushCounter(ts, value, track);
};
+ // TODO(rsavitski): with the UI now supporting rate mode for counter tracks,
+ // this is likely redundant.
auto calculate_throughput = [](double amount, int64_t diff) {
return diff == 0 ? 0 : amount * MS_PER_SEC / static_cast<double>(diff);
};
@@ -277,9 +283,10 @@
}
// /proc/meminfo counters are in kB, convert to bytes
TrackId track = context_->track_tracker->InternGlobalCounterTrack(
- TrackTracker::Group::kMemory, meminfo_strs_id_[key]);
+ TrackTracker::Group::kMemory, meminfo_strs_id_[key], {},
+ bytes_unit_id_);
context_->event_tracker->PushCounter(
- ts, static_cast<double>(mi.value()) * 1024., track);
+ ts, static_cast<double>(mi.value()) * 1024, track);
}
for (auto it = sys_stats.devfreq(); it; ++it) {
@@ -402,20 +409,23 @@
ts, static_cast<double>(sys_stats.num_softirq_total()), track);
}
+ // Fragmentation of the kernel binary buddy memory allocator.
+ // See /proc/buddyinfo in `man 5 proc`.
for (auto it = sys_stats.buddy_info(); it; ++it) {
protos::pbzero::SysStats::BuddyInfo::Decoder bi(*it);
int order = 0;
for (auto order_it = bi.order_pages(); order_it; ++order_it) {
std::string node = bi.node().ToStdString();
std::string zone = bi.zone().ToStdString();
- uint32_t size_kb =
+ uint32_t chunk_size_kb =
static_cast<uint32_t>(((1 << order) * page_size_) / 1024);
base::StackString<255> counter_name("mem.buddyinfo[%s][%s][%u kB]",
- node.c_str(), zone.c_str(), size_kb);
+ node.c_str(), zone.c_str(),
+ chunk_size_kb);
StringId name =
context_->storage->InternString(counter_name.string_view());
TrackId track = context_->track_tracker->InternGlobalCounterTrack(
- TrackTracker::Group::kMemory, name);
+ TrackTracker::Group::kMemory, name, {}, available_chunks_unit_id_);
context_->event_tracker->PushCounter(ts, static_cast<double>(*order_it),
track);
order++;
@@ -426,6 +436,8 @@
ParseDiskStats(ts, *it);
}
+ // Pressure Stall Information. See
+ // https://docs.kernel.org/accounting/psi.html.
for (auto it = sys_stats.psi(); it; ++it) {
protos::pbzero::SysStats::PsiSample::Decoder psi(*it);
@@ -436,11 +448,12 @@
continue;
}
+ // Unit = total blocked time on this resource in nanoseconds.
// TODO(b/315152880): Consider moving psi entries for cpu/io/memory into
// groups specific to that resource (e.g., `Group::kMemory`).
TrackId track = context_->track_tracker->InternGlobalCounterTrack(
TrackTracker::Group::kDeviceState,
- sys_stats_psi_resource_names_[resource]);
+ sys_stats_psi_resource_names_[resource], {}, ns_unit_id_);
context_->event_tracker->PushCounter(
ts, static_cast<double>(psi.total_ns()), track);
}
diff --git a/src/trace_processor/importers/proto/system_probes_parser.h b/src/trace_processor/importers/proto/system_probes_parser.h
index 776b2c6..a54beb7 100644
--- a/src/trace_processor/importers/proto/system_probes_parser.h
+++ b/src/trace_processor/importers/proto/system_probes_parser.h
@@ -50,6 +50,10 @@
TraceProcessorContext* const context_;
const StringId utid_name_id_;
+ const StringId ns_unit_id_;
+ const StringId bytes_unit_id_;
+ const StringId available_chunks_unit_id_;
+
const StringId num_forks_name_id_;
const StringId num_irq_total_name_id_;
const StringId num_softirq_total_name_id_;
diff --git a/src/trace_processor/metrics/metrics.cc b/src/trace_processor/metrics/metrics.cc
index d351e0a..ff492f4 100644
--- a/src/trace_processor/metrics/metrics.cc
+++ b/src/trace_processor/metrics/metrics.cc
@@ -518,7 +518,7 @@
return base::OkStatus();
}
-void RepeatedFieldStep(sqlite3_context* ctx, int argc, sqlite3_value** argv) {
+void RepeatedField::Step(sqlite3_context* ctx, int argc, sqlite3_value** argv) {
if (argc != 1) {
sqlite::result::Error(ctx, "RepeatedField: only expected one arg");
return;
@@ -546,7 +546,7 @@
}
}
-void RepeatedFieldFinal(sqlite3_context* ctx) {
+void RepeatedField::Final(sqlite3_context* ctx) {
// Note: we choose the size intentionally to be zero because we don't want to
// allocate if the Step has never been called.
auto** builder_ptr_ptr =
diff --git a/src/trace_processor/metrics/metrics.h b/src/trace_processor/metrics/metrics.h
index 7eead1f..3918489 100644
--- a/src/trace_processor/metrics/metrics.h
+++ b/src/trace_processor/metrics/metrics.h
@@ -36,6 +36,7 @@
#include "perfetto/trace_processor/trace_processor.h"
#include "src/trace_processor/perfetto_sql/engine/perfetto_sql_engine.h"
#include "src/trace_processor/perfetto_sql/intrinsics/functions/sql_function.h"
+#include "src/trace_processor/sqlite/bindings/sqlite_aggregate_function.h"
#include "src/trace_processor/util/descriptors.h"
#include "protos/perfetto/trace_processor/metrics_impl.pbzero.h"
@@ -194,8 +195,10 @@
};
// These functions implement the RepeatedField SQL aggregate functions.
-void RepeatedFieldStep(sqlite3_context* ctx, int argc, sqlite3_value** argv);
-void RepeatedFieldFinal(sqlite3_context* ctx);
+struct RepeatedField : public SqliteAggregateFunction {
+ static void Step(sqlite3_context* ctx, int argc, sqlite3_value** argv);
+ static void Final(sqlite3_context* ctx);
+};
base::Status ComputeMetrics(PerfettoSqlEngine*,
const std::vector<std::string>& metrics_to_compute,
diff --git a/src/trace_processor/metrics/sql/android/BUILD.gn b/src/trace_processor/metrics/sql/android/BUILD.gn
index c3d3da0..f87b560 100644
--- a/src/trace_processor/metrics/sql/android/BUILD.gn
+++ b/src/trace_processor/metrics/sql/android/BUILD.gn
@@ -21,6 +21,7 @@
sources = [
"ad_services_metric.sql",
"android_anr.sql",
+ "android_auto_multiuser.sql",
"android_batt.sql",
"android_binder.sql",
"android_blocking_calls_cuj_metric.sql",
diff --git a/src/trace_processor/metrics/sql/android/android_auto_multiuser.sql b/src/trace_processor/metrics/sql/android/android_auto_multiuser.sql
new file mode 100644
index 0000000..3b12305
--- /dev/null
+++ b/src/trace_processor/metrics/sql/android/android_auto_multiuser.sql
@@ -0,0 +1,64 @@
+--
+-- Copyright 2024 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
+--
+-- https://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 PERFETTO MODULE android.startup.startups;
+
+-- Collect the last ts for user switch event.
+-- The metric should represent time elapsed between
+-- the latest user start and the latest carlauncher startup.
+DROP VIEW IF EXISTS auto_multiuser_events;
+CREATE PERFETTO VIEW auto_multiuser_events AS
+SELECT
+ user_start_time_ns AS event_start_time_ns,
+ launcher_end_time_ns AS event_end_time_ns
+FROM
+ (
+ SELECT MAX(slice.ts) as user_start_time_ns
+ FROM slice
+ WHERE (
+ slice.name GLOB "UserController.startUser*"
+ AND slice.name NOT GLOB "UserController.startUser-10*"
+ )
+ ),
+ (
+ SELECT ts_end AS launcher_end_time_ns
+ FROM android_startups
+ WHERE (package = 'com.android.car.carlauncher')
+ );
+
+-- Precompute user switch duration time.
+-- Take only positive duration values(user start ts < carlauncher start ts)
+-- If there are potential duplicates in carlauncher startup,
+-- take the smallest value. It represents the closest carlaucnher startup
+DROP TABLE IF EXISTS android_auto_multiuser_timing;
+CREATE PERFETTO TABLE android_auto_multiuser_timing AS
+SELECT
+ cast_int!((event_end_time_ns - event_start_time_ns) / 1e6 + 0.5) as duration_ms
+FROM
+ auto_multiuser_events
+WHERE duration_ms > 0
+ORDER BY duration_ms ASC
+LIMIT 1;
+
+DROP VIEW IF EXISTS android_auto_multiuser_output;
+CREATE PERFETTO VIEW android_auto_multiuser_output AS
+SELECT AndroidMultiuserMetric (
+ 'user_switch', AndroidMultiuserMetric_EventData(
+ 'duration_ms', (
+ SELECT duration_ms FROM android_auto_multiuser_timing
+ )
+ )
+);
\ No newline at end of file
diff --git a/src/trace_processor/perfetto_sql/engine/perfetto_sql_engine.cc b/src/trace_processor/perfetto_sql/engine/perfetto_sql_engine.cc
index 6d7f965..8959989 100644
--- a/src/trace_processor/perfetto_sql/engine/perfetto_sql_engine.cc
+++ b/src/trace_processor/perfetto_sql/engine/perfetto_sql_engine.cc
@@ -182,8 +182,8 @@
}
engine_->RegisterVirtualTableModule<RuntimeTableFunction>(
- "runtime_table_function", this, SqliteTable::TableType::kExplicitCreate,
- false);
+ "runtime_table_function", this,
+ SqliteTableLegacy::TableType::kExplicitCreate, false);
auto context = std::make_unique<DbSqliteTable::Context>(
query_cache_.get(),
[this](const std::string& name) {
@@ -197,7 +197,7 @@
});
engine_->RegisterVirtualTableModule<DbSqliteTable>(
"runtime_table", std::move(context),
- SqliteTable::TableType::kExplicitCreate, false);
+ SqliteTableLegacy::TableType::kExplicitCreate, false);
}
PerfettoSqlEngine::~PerfettoSqlEngine() {
@@ -215,7 +215,7 @@
query_cache_.get(), &table, std::move(schema));
static_tables_.Insert(table_name, &table);
engine_->RegisterVirtualTableModule<DbSqliteTable>(
- table_name, std::move(context), SqliteTable::kEponymousOnly, false);
+ table_name, std::move(context), SqliteTableLegacy::kEponymousOnly, false);
// Register virtual tables into an internal 'perfetto_tables' table.
// This is used for iterating through all the tables during a database
@@ -237,7 +237,7 @@
auto context = std::make_unique<DbSqliteTable::Context>(query_cache_.get(),
std::move(fn));
engine_->RegisterVirtualTableModule<DbSqliteTable>(
- table_name, std::move(context), SqliteTable::kEponymousOnly, false);
+ table_name, std::move(context), SqliteTableLegacy::kEponymousOnly, false);
}
base::StatusOr<PerfettoSqlEngine::ExecutionStats> PerfettoSqlEngine::Execute(
diff --git a/src/trace_processor/perfetto_sql/engine/perfetto_sql_engine.h b/src/trace_processor/perfetto_sql/engine/perfetto_sql_engine.h
index f11d04e..8fe5425 100644
--- a/src/trace_processor/perfetto_sql/engine/perfetto_sql_engine.h
+++ b/src/trace_processor/perfetto_sql/engine/perfetto_sql_engine.h
@@ -38,12 +38,13 @@
#include "src/trace_processor/perfetto_sql/engine/runtime_table_function.h"
#include "src/trace_processor/perfetto_sql/intrinsics/functions/sql_function.h"
#include "src/trace_processor/perfetto_sql/intrinsics/table_functions/static_table_function.h"
+#include "src/trace_processor/sqlite/bindings/sqlite_aggregate_function.h"
+#include "src/trace_processor/sqlite/bindings/sqlite_result.h"
+#include "src/trace_processor/sqlite/bindings/sqlite_window_function.h"
#include "src/trace_processor/sqlite/query_cache.h"
#include "src/trace_processor/sqlite/sql_source.h"
#include "src/trace_processor/sqlite/sqlite_engine.h"
-#include "src/trace_processor/sqlite/sqlite_result.h"
#include "src/trace_processor/sqlite/sqlite_utils.h"
-#include "src/trace_processor/sqlite/sqlite_window_function.h"
#include "src/trace_processor/util/sql_argument.h"
#include "src/trace_processor/util/sql_modules.h"
@@ -88,14 +89,15 @@
//
// The format of the function is given by the |SqlFunction|.
//
- // |name|: name of the function in SQL.
- // |argc|: number of arguments for this function. This can be -1 if
- // the number of arguments is variable.
- // |ctx|: context object for the function (see SqlFunction::Run);
- // this object *must* outlive the function so should likely be
- // either static or scoped to the lifetime of TraceProcessor.
- // |determistic|: whether this function has deterministic output given the
- // same set of arguments.
+ // |name|: name of the function in SQL.
+ // |argc|: number of arguments for this function. This can be -1 if
+ // the number of arguments is variable.
+ // |ctx|: context object for the function (see SqlFunction::Run);
+ // this object *must* outlive the function so should likely
+ // be either static or scoped to the lifetime of
+ // TraceProcessor.
+ // |deterministic|: whether this function has deterministic output given the
+ // same set of arguments.
template <typename Function = SqlFunction>
base::Status RegisterStaticFunction(const char* name,
int argc,
@@ -115,18 +117,36 @@
std::unique_ptr<typename Function::Context> ctx,
bool deterministic = true);
+ // Registers a trace processor C++ aggregate function to be runnable from SQL.
+ //
+ // The format of the function is given by the |SqliteAggregateFunction|.
+ //
+ // |name|: name of the function in SQL
+ // |argc|: number of arguments for this function. This can be -1 if
+ // the number of arguments is variable.
+ // |ctx|: context object for the function; this object *must*
+ // outlive the function so should likely be either static or
+ // scoped to the lifetime of TraceProcessor.
+ // |deterministic|: whether this function has deterministic output given the
+ // same set of arguments.
+ template <typename Function = SqliteAggregateFunction>
+ base::Status RegisterSqliteAggregateFunction(const char* name,
+ int argc,
+ typename Function::Context* ctx,
+ bool deterministic = true);
+
// Registers a trace processor C++ window function to be runnable from SQL.
//
// The format of the function is given by the |SqliteWindowFunction|.
//
- // |name|: name of the function in SQL.
- // |argc|: number of arguments for this function. This can be -1 if
- // the number of arguments is variable.
- // |ctx|: context object for the function (see SqlFunction::Run);
- // this object *must* outlive the function so should likely be
- // either static or scoped to the lifetime of TraceProcessor.
- // |determistic|: whether this function has deterministic output given the
- // same set of arguments.
+ // |name|: name of the function in SQL.
+ // |argc|: number of arguments for this function. This can be -1 if
+ // the number of arguments is variable.
+ // |ctx|: context object for the function; this object *must*
+ // outlive the function so should likely be either static or
+ // scoped to the lifetime of TraceProcessor.
+ // |deterministic|: whether this function has deterministic output given the
+ // same set of arguments.
template <typename Function = SqliteWindowFunction>
base::Status RegisterSqliteWindowFunction(const char* name,
int argc,
@@ -191,8 +211,9 @@
// The missing objects from the above query are static functions, runtime
// functions and macros. Add those in now.
- return query_count + static_function_count_ + runtime_function_count_ +
- static_window_function_count_ + macros_.size();
+ return query_count + static_function_count_ +
+ static_window_function_count_ + static_aggregate_function_count_ +
+ runtime_function_count_ + macros_.size();
}
// Find RuntimeTable registered with engine with provided name.
@@ -252,8 +273,9 @@
StringPool* pool_ = nullptr;
uint64_t static_function_count_ = 0;
- uint64_t runtime_function_count_ = 0;
+ uint64_t static_aggregate_function_count_ = 0;
uint64_t static_window_function_count_ = 0;
+ uint64_t runtime_function_count_ = 0;
base::FlatHashMap<std::string, std::unique_ptr<RuntimeTableFunction::State>>
runtime_table_fn_states_;
@@ -336,6 +358,17 @@
}
template <typename Function>
+base::Status PerfettoSqlEngine::RegisterSqliteAggregateFunction(
+ const char* name,
+ int argc,
+ typename Function::Context* ctx,
+ bool deterministic) {
+ static_aggregate_function_count_++;
+ return engine_->RegisterAggregateFunction(
+ name, argc, Function::Step, Function::Final, ctx, nullptr, deterministic);
+}
+
+template <typename Function>
base::Status PerfettoSqlEngine::RegisterSqliteWindowFunction(
const char* name,
int argc,
diff --git a/src/trace_processor/perfetto_sql/engine/runtime_table_function.cc b/src/trace_processor/perfetto_sql/engine/runtime_table_function.cc
index 1eaab01..3d0af93 100644
--- a/src/trace_processor/perfetto_sql/engine/runtime_table_function.cc
+++ b/src/trace_processor/perfetto_sql/engine/runtime_table_function.cc
@@ -20,7 +20,7 @@
#include <utility>
#include "src/trace_processor/perfetto_sql/engine/perfetto_sql_engine.h"
-#include "src/trace_processor/sqlite/sqlite_result.h"
+#include "src/trace_processor/sqlite/bindings/sqlite_result.h"
#include "src/trace_processor/util/status_macros.h"
namespace perfetto {
@@ -52,7 +52,7 @@
return base::OkStatus();
}
-SqliteTable::Schema RuntimeTableFunction::CreateSchema() {
+SqliteTableLegacy::Schema RuntimeTableFunction::CreateSchema() {
std::vector<Column> columns;
for (size_t i = 0; i < state_->return_values.size(); ++i) {
const auto& ret = state_->return_values[i];
@@ -80,10 +80,11 @@
Column(columns.size(), "_primary_key", SqlValue::kLong, true));
primary_keys.emplace_back(columns.size() - 1);
- return SqliteTable::Schema(std::move(columns), std::move(primary_keys));
+ return SqliteTableLegacy::Schema(std::move(columns), std::move(primary_keys));
}
-std::unique_ptr<SqliteTable::BaseCursor> RuntimeTableFunction::CreateCursor() {
+std::unique_ptr<SqliteTableLegacy::BaseCursor>
+RuntimeTableFunction::CreateCursor() {
return std::unique_ptr<Cursor>(new Cursor(this, state_));
}
@@ -109,7 +110,7 @@
}
RuntimeTableFunction::Cursor::Cursor(RuntimeTableFunction* table, State* state)
- : SqliteTable::BaseCursor(table), table_(table), state_(state) {
+ : SqliteTableLegacy::BaseCursor(table), table_(table), state_(state) {
if (state->reusable_stmt) {
stmt_ = std::move(state->reusable_stmt);
state->reusable_stmt = std::nullopt;
diff --git a/src/trace_processor/perfetto_sql/engine/runtime_table_function.h b/src/trace_processor/perfetto_sql/engine/runtime_table_function.h
index 7c92fcc..80edf72 100644
--- a/src/trace_processor/perfetto_sql/engine/runtime_table_function.h
+++ b/src/trace_processor/perfetto_sql/engine/runtime_table_function.h
@@ -27,8 +27,8 @@
class PerfettoSqlEngine;
-// The implementation of the SqliteTable interface for table functions defined
-// at runtime using SQL.
+// The implementation of the SqliteTableLegacy interface for table functions
+// defined at runtime using SQL.
class RuntimeTableFunction final
: public TypedSqliteTable<RuntimeTableFunction, PerfettoSqlEngine*> {
public:
@@ -65,7 +65,7 @@
kPrimaryKeyColumns;
}
};
- class Cursor final : public SqliteTable::BaseCursor {
+ class Cursor final : public SqliteTableLegacy::BaseCursor {
public:
explicit Cursor(RuntimeTableFunction* table, State* state);
~Cursor() final;
@@ -92,7 +92,7 @@
~RuntimeTableFunction() final;
base::Status Init(int argc, const char* const* argv, Schema*) final;
- std::unique_ptr<SqliteTable::BaseCursor> CreateCursor() final;
+ std::unique_ptr<SqliteTableLegacy::BaseCursor> CreateCursor() final;
int BestIndex(const QueryConstraints& qc, BestIndexInfo* info) final;
private:
diff --git a/src/trace_processor/perfetto_sql/intrinsics/functions/layout_functions.cc b/src/trace_processor/perfetto_sql/intrinsics/functions/layout_functions.cc
index 70b3bce..3384376 100644
--- a/src/trace_processor/perfetto_sql/intrinsics/functions/layout_functions.cc
+++ b/src/trace_processor/perfetto_sql/intrinsics/functions/layout_functions.cc
@@ -23,9 +23,9 @@
#include "perfetto/ext/base/status_or.h"
#include "perfetto/trace_processor/basic_types.h"
#include "src/trace_processor/perfetto_sql/engine/perfetto_sql_engine.h"
-#include "src/trace_processor/sqlite/sqlite_result.h"
+#include "src/trace_processor/sqlite/bindings/sqlite_result.h"
+#include "src/trace_processor/sqlite/bindings/sqlite_window_function.h"
#include "src/trace_processor/sqlite/sqlite_utils.h"
-#include "src/trace_processor/sqlite/sqlite_window_function.h"
#include "src/trace_processor/util/status_macros.h"
namespace perfetto::trace_processor {
diff --git a/src/trace_processor/perfetto_sql/intrinsics/functions/pprof_functions.cc b/src/trace_processor/perfetto_sql/intrinsics/functions/pprof_functions.cc
index d02eaeb..8fda79e 100644
--- a/src/trace_processor/perfetto_sql/intrinsics/functions/pprof_functions.cc
+++ b/src/trace_processor/perfetto_sql/intrinsics/functions/pprof_functions.cc
@@ -34,6 +34,7 @@
#include "perfetto/trace_processor/basic_types.h"
#include "perfetto/trace_processor/status.h"
#include "protos/perfetto/trace_processor/stack.pbzero.h"
+#include "src/trace_processor/perfetto_sql/engine/perfetto_sql_engine.h"
#include "src/trace_processor/sqlite/sqlite_utils.h"
#include "src/trace_processor/types/trace_processor_context.h"
#include "src/trace_processor/util/profile_builder.h"
@@ -156,7 +157,9 @@
std::vector<int64_t> sample_values_;
};
-base::Status Step(sqlite3_context* ctx, size_t argc, sqlite3_value** argv) {
+base::Status StepStatus(sqlite3_context* ctx,
+ size_t argc,
+ sqlite3_value** argv) {
auto** agg_context_ptr = static_cast<AggregateContext**>(
sqlite3_aggregate_context(ctx, sizeof(AggregateContext*)));
if (!agg_context_ptr) {
@@ -178,42 +181,39 @@
return (*agg_context_ptr)->Step(argc, argv);
}
-void StepWrapper(sqlite3_context* ctx, int argc, sqlite3_value** argv) {
- PERFETTO_CHECK(argc >= 0);
+struct ProfileBuilder {
+ using Context = TraceProcessorContext;
- base::Status status = Step(ctx, static_cast<size_t>(argc), argv);
+ static void Step(sqlite3_context* ctx, int argc, sqlite3_value** argv) {
+ PERFETTO_CHECK(argc >= 0);
- if (!status.ok()) {
- sqlite::utils::SetError(ctx, kFunctionName, status);
- }
-}
+ base::Status status = StepStatus(ctx, static_cast<size_t>(argc), argv);
-void FinalWrapper(sqlite3_context* ctx) {
- auto** agg_context_ptr =
- static_cast<AggregateContext**>(sqlite3_aggregate_context(ctx, 0));
-
- if (!agg_context_ptr) {
- return;
+ if (!status.ok()) {
+ sqlite::utils::SetError(ctx, kFunctionName, status);
+ }
}
- (*agg_context_ptr)->Final(ctx);
+ static void Final(sqlite3_context* ctx) {
+ auto** agg_context_ptr =
+ static_cast<AggregateContext**>(sqlite3_aggregate_context(ctx, 0));
- delete (*agg_context_ptr);
-}
+ if (!agg_context_ptr) {
+ return;
+ }
+
+ (*agg_context_ptr)->Final(ctx);
+
+ delete (*agg_context_ptr);
+ }
+};
} // namespace
-base::Status PprofFunctions::Register(sqlite3* db,
+base::Status PprofFunctions::Register(PerfettoSqlEngine& engine,
TraceProcessorContext* context) {
- int flags = SQLITE_UTF8 | SQLITE_DETERMINISTIC;
- int ret =
- sqlite3_create_function_v2(db, kFunctionName, -1, flags, context, nullptr,
- StepWrapper, FinalWrapper, nullptr);
- if (ret != SQLITE_OK) {
- return base::ErrStatus("Unable to register function with name %s",
- kFunctionName);
- }
- return base::OkStatus();
+ return engine.RegisterSqliteAggregateFunction<ProfileBuilder>(kFunctionName,
+ -1, context);
}
} // namespace perfetto::trace_processor
diff --git a/src/trace_processor/perfetto_sql/intrinsics/functions/pprof_functions.h b/src/trace_processor/perfetto_sql/intrinsics/functions/pprof_functions.h
index 24e6d72..ca5b459 100644
--- a/src/trace_processor/perfetto_sql/intrinsics/functions/pprof_functions.h
+++ b/src/trace_processor/perfetto_sql/intrinsics/functions/pprof_functions.h
@@ -20,17 +20,16 @@
#include <sqlite3.h>
#include "perfetto/base/status.h"
+#include "src/trace_processor/perfetto_sql/engine/perfetto_sql_engine.h"
-namespace perfetto {
-namespace trace_processor {
+namespace perfetto::trace_processor {
class TraceProcessorContext;
struct PprofFunctions {
- static base::Status Register(sqlite3* db, TraceProcessorContext* context);
+ static base::Status Register(PerfettoSqlEngine&, TraceProcessorContext*);
};
-} // namespace trace_processor
-} // namespace perfetto
+} // namespace perfetto::trace_processor
#endif // SRC_TRACE_PROCESSOR_PERFETTO_SQL_INTRINSICS_FUNCTIONS_PPROF_FUNCTIONS_H_
diff --git a/src/trace_processor/perfetto_sql/intrinsics/functions/window_functions.h b/src/trace_processor/perfetto_sql/intrinsics/functions/window_functions.h
index 00de395..c9f606d 100644
--- a/src/trace_processor/perfetto_sql/intrinsics/functions/window_functions.h
+++ b/src/trace_processor/perfetto_sql/intrinsics/functions/window_functions.h
@@ -23,8 +23,8 @@
#include "perfetto/base/logging.h"
#include "src/trace_processor/perfetto_sql/engine/perfetto_sql_engine.h"
-#include "src/trace_processor/sqlite/sqlite_result.h"
-#include "src/trace_processor/sqlite/sqlite_window_function.h"
+#include "src/trace_processor/sqlite/bindings/sqlite_result.h"
+#include "src/trace_processor/sqlite/bindings/sqlite_window_function.h"
namespace perfetto::trace_processor {
diff --git a/src/trace_processor/perfetto_sql/intrinsics/operators/span_join_operator.cc b/src/trace_processor/perfetto_sql/intrinsics/operators/span_join_operator.cc
index 6fe221d..66ffa4b 100644
--- a/src/trace_processor/perfetto_sql/intrinsics/operators/span_join_operator.cc
+++ b/src/trace_processor/perfetto_sql/intrinsics/operators/span_join_operator.cc
@@ -47,7 +47,7 @@
}
std::optional<std::string> HasDuplicateColumns(
- const std::vector<SqliteTable::Column>& cols) {
+ const std::vector<SqliteTableLegacy::Column>& cols) {
std::set<std::string> names;
for (const auto& col : cols) {
if (names.count(col.name()) > 0)
@@ -180,7 +180,7 @@
if (!status.ok())
return status;
- std::vector<SqliteTable::Column> cols;
+ std::vector<SqliteTableLegacy::Column> cols;
// Ensure the shared columns are consistently ordered and are not
// present twice in the final schema
cols.emplace_back(Column::kTimestamp, kTsColumnName, SqlValue::Type::kLong);
@@ -217,7 +217,7 @@
void SpanJoinOperatorTable::CreateSchemaColsForDefn(
const TableDefinition& defn,
- std::vector<SqliteTable::Column>* cols) {
+ std::vector<SqliteTableLegacy::Column>* cols) {
for (size_t i = 0; i < defn.columns().size(); i++) {
const auto& n = defn.columns()[i].name();
if (IsRequiredColumn(n) || n == defn.partition_col())
@@ -231,7 +231,8 @@
}
}
-std::unique_ptr<SqliteTable::BaseCursor> SpanJoinOperatorTable::CreateCursor() {
+std::unique_ptr<SqliteTableLegacy::BaseCursor>
+SpanJoinOperatorTable::CreateCursor() {
return std::unique_ptr<SpanJoinOperatorTable::Cursor>(
new Cursor(this, engine_));
}
@@ -331,7 +332,7 @@
desc.name.c_str());
}
- std::vector<SqliteTable::Column> cols;
+ std::vector<SqliteTableLegacy::Column> cols;
RETURN_IF_ERROR(sqlite::utils::GetColumnsForTable(
engine_->sqlite_engine()->db(), desc.name, cols));
@@ -396,7 +397,7 @@
SpanJoinOperatorTable::Cursor::Cursor(SpanJoinOperatorTable* table,
PerfettoSqlEngine* engine)
- : SqliteTable::BaseCursor(table),
+ : SqliteTableLegacy::BaseCursor(table),
t1_(table, &table->t1_defn_, engine),
t2_(table, &table->t2_defn_, engine),
table_(table) {}
@@ -783,7 +784,7 @@
std::string SpanJoinOperatorTable::Query::CreateSqlQuery(
const std::vector<std::string>& cs) const {
std::vector<std::string> col_names;
- for (const SqliteTable::Column& c : defn_->columns()) {
+ for (const SqliteTableLegacy::Column& c : defn_->columns()) {
col_names.push_back("`" + c.name() + "`");
}
@@ -833,7 +834,7 @@
SpanJoinOperatorTable::TableDefinition::TableDefinition(
std::string name,
std::string partition_col,
- std::vector<SqliteTable::Column> cols,
+ std::vector<SqliteTableLegacy::Column> cols,
EmitShadowType emit_shadow_type,
uint32_t ts_idx,
uint32_t dur_idx,
diff --git a/src/trace_processor/perfetto_sql/intrinsics/operators/span_join_operator.h b/src/trace_processor/perfetto_sql/intrinsics/operators/span_join_operator.h
index 2c663c8..c734b7f 100644
--- a/src/trace_processor/perfetto_sql/intrinsics/operators/span_join_operator.h
+++ b/src/trace_processor/perfetto_sql/intrinsics/operators/span_join_operator.h
@@ -98,7 +98,7 @@
TableDefinition(std::string name,
std::string partition_col,
- std::vector<SqliteTable::Column> cols,
+ std::vector<SqliteTableLegacy::Column> cols,
EmitShadowType emit_shadow_type,
uint32_t ts_idx,
uint32_t dur_idx,
@@ -120,7 +120,9 @@
const std::string& name() const { return name_; }
const std::string& partition_col() const { return partition_col_; }
- const std::vector<SqliteTable::Column>& columns() const { return cols_; }
+ const std::vector<SqliteTableLegacy::Column>& columns() const {
+ return cols_;
+ }
uint32_t ts_idx() const { return ts_idx_; }
uint32_t dur_idx() const { return dur_idx_; }
@@ -131,7 +133,7 @@
std::string name_;
std::string partition_col_;
- std::vector<SqliteTable::Column> cols_;
+ std::vector<SqliteTableLegacy::Column> cols_;
uint32_t ts_idx_ = std::numeric_limits<uint32_t>::max();
uint32_t dur_idx_ = std::numeric_limits<uint32_t>::max();
@@ -323,7 +325,7 @@
};
// Base class for a cursor on the span table.
- class Cursor final : public SqliteTable::BaseCursor {
+ class Cursor final : public SqliteTableLegacy::BaseCursor {
public:
Cursor(SpanJoinOperatorTable*, PerfettoSqlEngine*);
~Cursor() final;
@@ -361,8 +363,8 @@
~SpanJoinOperatorTable() final;
// Table implementation.
- util::Status Init(int, const char* const*, SqliteTable::Schema*) final;
- std::unique_ptr<SqliteTable::BaseCursor> CreateCursor() final;
+ util::Status Init(int, const char* const*, SqliteTableLegacy::Schema*) final;
+ std::unique_ptr<SqliteTableLegacy::BaseCursor> CreateCursor() final;
int BestIndex(const QueryConstraints& qc, BestIndexInfo* info) final;
int FindFunction(const char* name, FindFunctionFn* fn, void** args) final;
@@ -430,7 +432,7 @@
int global_column);
void CreateSchemaColsForDefn(const TableDefinition& defn,
- std::vector<SqliteTable::Column>* cols);
+ std::vector<SqliteTableLegacy::Column>* cols);
TableDefinition t1_defn_;
TableDefinition t2_defn_;
diff --git a/src/trace_processor/perfetto_sql/intrinsics/operators/span_join_operator_unittest.cc b/src/trace_processor/perfetto_sql/intrinsics/operators/span_join_operator_unittest.cc
index bcd3ea9..2695e43 100644
--- a/src/trace_processor/perfetto_sql/intrinsics/operators/span_join_operator_unittest.cc
+++ b/src/trace_processor/perfetto_sql/intrinsics/operators/span_join_operator_unittest.cc
@@ -28,10 +28,11 @@
public:
SpanJoinOperatorTableTest() {
engine_.sqlite_engine()->RegisterVirtualTableModule<SpanJoinOperatorTable>(
- "span_join", &engine_, SqliteTable::TableType::kExplicitCreate, false);
- engine_.sqlite_engine()->RegisterVirtualTableModule<SpanJoinOperatorTable>(
- "span_left_join", &engine_, SqliteTable::TableType::kExplicitCreate,
+ "span_join", &engine_, SqliteTableLegacy::TableType::kExplicitCreate,
false);
+ engine_.sqlite_engine()->RegisterVirtualTableModule<SpanJoinOperatorTable>(
+ "span_left_join", &engine_,
+ SqliteTableLegacy::TableType::kExplicitCreate, false);
}
void PrepareValidStatement(const std::string& sql) {
diff --git a/src/trace_processor/perfetto_sql/intrinsics/operators/window_operator.cc b/src/trace_processor/perfetto_sql/intrinsics/operators/window_operator.cc
index 41c2841..eb31bb3 100644
--- a/src/trace_processor/perfetto_sql/intrinsics/operators/window_operator.cc
+++ b/src/trace_processor/perfetto_sql/intrinsics/operators/window_operator.cc
@@ -16,142 +16,204 @@
#include "src/trace_processor/perfetto_sql/intrinsics/operators/window_operator.h"
-#include "perfetto/base/status.h"
-#include "src/trace_processor/sqlite/sqlite_result.h"
+#include <sqlite3.h>
+#include <cstdint>
+#include <memory>
+
+#include "perfetto/base/logging.h"
+#include "src/trace_processor/sqlite/bindings/sqlite_result.h"
#include "src/trace_processor/sqlite/sqlite_utils.h"
-namespace perfetto {
-namespace trace_processor {
+namespace perfetto::trace_processor {
-WindowOperatorTable::WindowOperatorTable(sqlite3*, const TraceStorage*) {}
-WindowOperatorTable::~WindowOperatorTable() = default;
-
-base::Status WindowOperatorTable::Init(int,
- const char* const*,
- Schema* schema) {
- const bool kHidden = true;
- *schema = Schema(
- {
- // These are the operator columns:
- SqliteTable::Column(Column::kRowId, "rowid", SqlValue::Type::kLong,
- kHidden),
- SqliteTable::Column(Column::kQuantum, "quantum",
- SqlValue::Type::kLong, kHidden),
- SqliteTable::Column(Column::kWindowStart, "window_start",
- SqlValue::Type::kLong, kHidden),
- SqliteTable::Column(Column::kWindowDur, "window_dur",
- SqlValue::Type::kLong, kHidden),
- // These are the ouput columns:
- SqliteTable::Column(Column::kTs, "ts", SqlValue::Type::kLong),
- SqliteTable::Column(Column::kDuration, "dur", SqlValue::Type::kLong),
- SqliteTable::Column(Column::kQuantumTs, "quantum_ts",
- SqlValue::Type::kLong),
- },
- {Column::kRowId});
- return base::OkStatus();
+namespace {
+constexpr char kSchema[] = R"(
+ CREATE TABLE x(
+ rowid BIGINT HIDDEN,
+ quantum BIGINT HIDDEN,
+ window_start BIGINT HIDDEN,
+ window_dur BIGINT HIDDEN,
+ ts BIGINT,
+ dur BIGINT,
+ quantum_ts BIGINT,
+ PRIMARY KEY(rowid)
+ ) WITHOUT ROWID
+ )";
}
-std::unique_ptr<SqliteTable::BaseCursor> WindowOperatorTable::CreateCursor() {
- return std::unique_ptr<SqliteTable::BaseCursor>(new Cursor(this));
-}
+int WindowOperatorModule::Create(sqlite3* db,
+ void* raw_ctx,
+ int argc,
+ const char* const* argv,
+ sqlite3_vtab** vtab,
+ char**) {
+ PERFETTO_CHECK(argc == 3);
+ if (int ret = sqlite3_declare_vtab(db, kSchema); ret != SQLITE_OK) {
+ return ret;
+ }
+ auto* ctx = GetContext(raw_ctx);
+ auto it_and_inserted = ctx->state_by_name.Insert(argv[2], nullptr);
+ PERFETTO_CHECK(
+ it_and_inserted.second ||
+ (it_and_inserted.first && it_and_inserted.first->get()->disconnected));
+ *it_and_inserted.first = std::make_unique<State>();
-int WindowOperatorTable::BestIndex(const QueryConstraints&, BestIndexInfo*) {
+ std::unique_ptr<Vtab> res = std::make_unique<Vtab>();
+ res->context = ctx;
+ res->name = argv[2];
+ res->state = it_and_inserted.first->get();
+ *vtab = res.release();
return SQLITE_OK;
}
-base::Status WindowOperatorTable::ModifyConstraints(QueryConstraints* qc) {
- // Remove ordering on timestamp if it is the only ordering as we are already
- // sorted on TS. This makes span joining significantly faster.
- const auto& ob = qc->order_by();
- if (ob.size() == 1 && ob[0].iColumn == Column::kTs && !ob[0].desc) {
- qc->mutable_order_by()->clear();
- }
- return base::OkStatus();
+int WindowOperatorModule::Destroy(sqlite3_vtab* vtab) {
+ auto* tab = GetVtab(vtab);
+ PERFETTO_CHECK(tab->context->state_by_name.Erase(tab->name));
+ delete tab;
+ return SQLITE_OK;
}
-base::Status WindowOperatorTable::Update(int argc,
- sqlite3_value** argv,
- sqlite3_int64*) {
- // We only support updates to ts and dur. Disallow deletes (argc == 1) and
- // inserts (argv[0] == null).
- if (argc < 2 || sqlite3_value_type(argv[0]) == SQLITE_NULL) {
- return base::ErrStatus(
- "Invalid number/value of arguments when updating window table");
+int WindowOperatorModule::Connect(sqlite3* db,
+ void* raw_ctx,
+ int argc,
+ const char* const* argv,
+ sqlite3_vtab** vtab,
+ char**) {
+ PERFETTO_CHECK(argc == 3);
+ if (int ret = sqlite3_declare_vtab(db, kSchema); ret != SQLITE_OK) {
+ return ret;
}
+ auto* ctx = GetContext(raw_ctx);
+ auto* ptr = ctx->state_by_name.Find(argv[2]);
+ PERFETTO_CHECK(ptr);
+ ptr->get()->disconnected = false;
- int64_t new_quantum = sqlite3_value_int64(argv[3]);
- int64_t new_start = sqlite3_value_int64(argv[4]);
- int64_t new_dur = sqlite3_value_int64(argv[5]);
- if (new_dur == 0) {
- return base::ErrStatus("Cannot set duration of window table to zero.");
- }
-
- quantum_ = new_quantum;
- window_start_ = new_start;
- window_dur_ = new_dur;
-
- return base::OkStatus();
+ std::unique_ptr<Vtab> res = std::make_unique<Vtab>();
+ res->context = ctx;
+ res->name = argv[2];
+ res->state = ptr->get();
+ *vtab = res.release();
+ return SQLITE_OK;
}
-WindowOperatorTable::Cursor::Cursor(WindowOperatorTable* table)
- : SqliteTable::BaseCursor(table), table_(table) {}
-WindowOperatorTable::Cursor::~Cursor() = default;
+int WindowOperatorModule::Disconnect(sqlite3_vtab* vtab) {
+ auto* tab = GetVtab(vtab);
+ auto* ptr = tab->context->state_by_name.Find(tab->name);
+ PERFETTO_CHECK(ptr);
+ ptr->get()->disconnected = true;
+ delete tab;
+ return SQLITE_OK;
+}
-base::Status WindowOperatorTable::Cursor::Filter(const QueryConstraints& qc,
- sqlite3_value** argv,
- FilterHistory) {
- *this = Cursor(table_);
- window_start_ = table_->window_start_;
- window_end_ = table_->window_start_ + table_->window_dur_;
- step_size_ = table_->quantum_ == 0 ? table_->window_dur_ : table_->quantum_;
-
- current_ts_ = window_start_;
+int WindowOperatorModule::BestIndex(sqlite3_vtab*, sqlite3_index_info* info) {
+ info->orderByConsumed = info->nOrderBy == 1 &&
+ info->aOrderBy[0].iColumn == Column::kTs &&
+ !info->aOrderBy[0].desc;
// Set return first if there is a equals constraint on the row id asking to
// return the first row.
- bool return_first = qc.constraints().size() == 1 &&
- qc.constraints()[0].column == Column::kRowId &&
- sqlite::utils::IsOpEq(qc.constraints()[0].op) &&
- sqlite3_value_int(argv[0]) == 0;
- if (return_first) {
- filter_type_ = FilterType::kReturnFirst;
+ bool is_row_id_constraint = info->nConstraint == 1 &&
+ info->aConstraint[0].iColumn == Column::kRowId &&
+ info->aConstraint[0].usable &&
+ sqlite::utils::IsOpEq(info->aConstraint[0].op);
+ if (is_row_id_constraint) {
+ info->idxNum = 1;
+ info->aConstraintUsage[0].argvIndex = 1;
} else {
- filter_type_ = FilterType::kReturnAll;
+ info->idxNum = 0;
}
- return base::OkStatus();
+ return SQLITE_OK;
}
-base::Status WindowOperatorTable::Cursor::Column(sqlite3_context* context,
- int N) {
+int WindowOperatorModule::Open(sqlite3_vtab*, sqlite3_vtab_cursor** cursor) {
+ std::unique_ptr<Cursor> c = std::make_unique<Cursor>();
+ *cursor = c.release();
+ return SQLITE_OK;
+}
+
+int WindowOperatorModule::Close(sqlite3_vtab_cursor* cursor) {
+ delete GetCursor(cursor);
+ return SQLITE_OK;
+}
+
+int WindowOperatorModule::Filter(sqlite3_vtab_cursor* cursor,
+ int is_row_id_constraint,
+ const char*,
+ int argc,
+ sqlite3_value** argv) {
+ auto* t = GetVtab(cursor->pVtab);
+ auto* c = GetCursor(cursor);
+
+ c->window_start = t->state->window_start;
+ c->window_end = t->state->window_start + t->state->window_dur;
+ c->step_size =
+ t->state->quantum == 0 ? t->state->window_dur : t->state->quantum;
+
+ c->current_ts = c->window_start;
+
+ if (is_row_id_constraint) {
+ PERFETTO_CHECK(argc == 1);
+ c->filter_type = sqlite3_value_int(argv[0]) == 0 ? FilterType::kReturnFirst
+ : FilterType::kReturnAll;
+ } else {
+ c->filter_type = FilterType::kReturnAll;
+ }
+ return SQLITE_OK;
+}
+
+int WindowOperatorModule::Next(sqlite3_vtab_cursor* cursor) {
+ auto* c = GetCursor(cursor);
+ switch (c->filter_type) {
+ case FilterType::kReturnFirst:
+ c->current_ts = c->window_end;
+ break;
+ case FilterType::kReturnAll:
+ c->current_ts += c->step_size;
+ c->quantum_ts++;
+ break;
+ }
+ c->row_id++;
+ return SQLITE_OK;
+}
+
+int WindowOperatorModule::Eof(sqlite3_vtab_cursor* cursor) {
+ auto* c = GetCursor(cursor);
+ return c->current_ts >= c->window_end;
+}
+
+int WindowOperatorModule::Column(sqlite3_vtab_cursor* cursor,
+ sqlite3_context* ctx,
+ int N) {
+ auto* t = GetVtab(cursor->pVtab);
+ auto* c = GetCursor(cursor);
switch (N) {
case Column::kQuantum: {
- sqlite::result::Long(context,
- static_cast<sqlite_int64>(table_->quantum_));
+ sqlite::result::Long(ctx, static_cast<sqlite_int64>(t->state->quantum));
break;
}
case Column::kWindowStart: {
- sqlite::result::Long(context,
- static_cast<sqlite_int64>(table_->window_start_));
+ sqlite::result::Long(ctx,
+ static_cast<sqlite_int64>(t->state->window_start));
break;
}
case Column::kWindowDur: {
- sqlite::result::Long(context, static_cast<int>(table_->window_dur_));
+ sqlite::result::Long(ctx, static_cast<int>(t->state->window_dur));
break;
}
case Column::kTs: {
- sqlite::result::Long(context, static_cast<sqlite_int64>(current_ts_));
+ sqlite::result::Long(ctx, static_cast<sqlite_int64>(c->current_ts));
break;
}
case Column::kDuration: {
- sqlite::result::Long(context, static_cast<sqlite_int64>(step_size_));
+ sqlite::result::Long(ctx, static_cast<sqlite_int64>(c->step_size));
break;
}
case Column::kQuantumTs: {
- sqlite::result::Long(context, static_cast<sqlite_int64>(quantum_ts_));
+ sqlite::result::Long(ctx, static_cast<sqlite_int64>(c->quantum_ts));
break;
}
case Column::kRowId: {
- sqlite::result::Long(context, static_cast<sqlite_int64>(row_id_));
+ sqlite::result::Long(ctx, static_cast<sqlite_int64>(c->row_id));
break;
}
default: {
@@ -159,26 +221,39 @@
break;
}
}
- return base::OkStatus();
+ return SQLITE_OK;
}
-base::Status WindowOperatorTable::Cursor::Next() {
- switch (filter_type_) {
- case FilterType::kReturnFirst:
- current_ts_ = window_end_;
- break;
- case FilterType::kReturnAll:
- current_ts_ += step_size_;
- quantum_ts_++;
- break;
+int WindowOperatorModule::Rowid(sqlite3_vtab_cursor*, sqlite_int64*) {
+ return SQLITE_ERROR;
+}
+
+int WindowOperatorModule::Update(sqlite3_vtab* tab,
+ int argc,
+ sqlite3_value** argv,
+ sqlite_int64*) {
+ auto* t = GetVtab(tab);
+
+ // We only support updates to ts and dur. Disallow deletes (argc == 1) and
+ // inserts (argv[0] == null).
+ if (argc < 2 || sqlite3_value_type(argv[0]) == SQLITE_NULL) {
+ return sqlite::utils::SetError(
+ tab, "Invalid number/value of arguments when updating window table");
}
- row_id_++;
- return base::OkStatus();
+
+ int64_t new_quantum = sqlite3_value_int64(argv[3]);
+ int64_t new_start = sqlite3_value_int64(argv[4]);
+ int64_t new_dur = sqlite3_value_int64(argv[5]);
+ if (new_dur == 0) {
+ return sqlite::utils::SetError(
+ tab, "Cannot set duration of window table to zero.");
+ }
+
+ t->state->quantum = new_quantum;
+ t->state->window_start = new_start;
+ t->state->window_dur = new_dur;
+
+ return SQLITE_OK;
}
-bool WindowOperatorTable::Cursor::Eof() {
- return current_ts_ >= window_end_;
-}
-
-} // namespace trace_processor
-} // namespace perfetto
+} // namespace perfetto::trace_processor
diff --git a/src/trace_processor/perfetto_sql/intrinsics/operators/window_operator.h b/src/trace_processor/perfetto_sql/intrinsics/operators/window_operator.h
index 8e26961..d023042 100644
--- a/src/trace_processor/perfetto_sql/intrinsics/operators/window_operator.h
+++ b/src/trace_processor/perfetto_sql/intrinsics/operators/window_operator.h
@@ -17,20 +17,55 @@
#ifndef SRC_TRACE_PROCESSOR_PERFETTO_SQL_INTRINSICS_OPERATORS_WINDOW_OPERATOR_H_
#define SRC_TRACE_PROCESSOR_PERFETTO_SQL_INTRINSICS_OPERATORS_WINDOW_OPERATOR_H_
+#include <cstdint>
#include <limits>
#include <memory>
+#include <string>
-#include "perfetto/base/status.h"
-#include "src/trace_processor/sqlite/sqlite_table.h"
+#include "perfetto/ext/base/flat_hash_map.h"
+#include "src/trace_processor/sqlite/bindings/sqlite_module.h"
-namespace perfetto {
-namespace trace_processor {
+namespace perfetto::trace_processor {
class TraceStorage;
-class WindowOperatorTable final
- : public TypedSqliteTable<WindowOperatorTable, const TraceStorage*> {
- public:
+// Operator table which can emit spans of a configurable duration.
+struct WindowOperatorModule : sqlite::Module<WindowOperatorModule> {
+ // Defines the data to be generated by the table.
+ enum FilterType {
+ // Returns all the spans.
+ kReturnAll = 0,
+ // Only returns the first span of the table. Useful for UPDATE operations.
+ kReturnFirst = 1,
+ };
+ struct State {
+ bool disconnected = false;
+ int64_t quantum = 0;
+ int64_t window_start = 0;
+
+ // max of int64_t because SQLite technically only supports int64s and not
+ // uint64s.
+ int64_t window_dur = std::numeric_limits<int64_t>::max();
+ };
+ struct Context {
+ base::FlatHashMap<std::string, std::unique_ptr<State>> state_by_name;
+ };
+ struct Vtab : sqlite::Module<WindowOperatorModule>::Vtab {
+ Context* context;
+ std::string name;
+ State* state = nullptr;
+ };
+ struct Cursor : sqlite::Module<WindowOperatorModule>::Cursor {
+ int64_t window_start = 0;
+ int64_t window_end = 0;
+ int64_t step_size = 0;
+
+ int64_t current_ts = 0;
+ int64_t quantum_ts = 0;
+ int64_t row_id = 0;
+
+ FilterType filter_type = FilterType::kReturnAll;
+ };
enum Column {
kRowId = 0,
kQuantum = 1,
@@ -40,64 +75,44 @@
kDuration = 5,
kQuantumTs = 6
};
- class Cursor final : public SqliteTable::BaseCursor {
- public:
- explicit Cursor(WindowOperatorTable*);
- ~Cursor() final;
- Cursor(Cursor&&) = default;
- Cursor& operator=(Cursor&&) = default;
+ static constexpr auto kType = kCreateOnly;
+ static constexpr bool kDoesOverloadFunctions = false;
- // Implementation of SqliteTable::Cursor.
- base::Status Filter(const QueryConstraints& qc,
- sqlite3_value**,
- FilterHistory);
- base::Status Next();
- bool Eof();
- base::Status Column(sqlite3_context*, int N);
+ static int Create(sqlite3*,
+ void*,
+ int,
+ const char* const*,
+ sqlite3_vtab**,
+ char**);
+ static int Destroy(sqlite3_vtab*);
- private:
- // Defines the data to be generated by the table.
- enum FilterType {
- // Returns all the spans.
- kReturnAll = 0,
- // Only returns the first span of the table. Useful for UPDATE operations.
- kReturnFirst = 1,
- };
+ static int Connect(sqlite3*,
+ void*,
+ int,
+ const char* const*,
+ sqlite3_vtab**,
+ char**);
+ static int Disconnect(sqlite3_vtab*);
- int64_t window_start_ = 0;
- int64_t window_end_ = 0;
- int64_t step_size_ = 0;
+ static int BestIndex(sqlite3_vtab*, sqlite3_index_info*);
- int64_t current_ts_ = 0;
- int64_t quantum_ts_ = 0;
- int64_t row_id_ = 0;
+ static int Open(sqlite3_vtab*, sqlite3_vtab_cursor**);
+ static int Close(sqlite3_vtab_cursor*);
- FilterType filter_type_ = FilterType::kReturnAll;
+ static int Filter(sqlite3_vtab_cursor*,
+ int,
+ const char*,
+ int,
+ sqlite3_value**);
+ static int Next(sqlite3_vtab_cursor*);
+ static int Eof(sqlite3_vtab_cursor*);
+ static int Column(sqlite3_vtab_cursor*, sqlite3_context*, int);
+ static int Rowid(sqlite3_vtab_cursor*, sqlite_int64*);
- WindowOperatorTable* table_ = nullptr;
- };
-
- WindowOperatorTable(sqlite3*, const TraceStorage*);
- ~WindowOperatorTable() final;
-
- // Table implementation.
- base::Status Init(int, const char* const*, Schema* schema) final;
- std::unique_ptr<SqliteTable::BaseCursor> CreateCursor() final;
- int BestIndex(const QueryConstraints&, BestIndexInfo*) final;
- base::Status ModifyConstraints(QueryConstraints* qc) final;
- base::Status Update(int, sqlite3_value**, sqlite3_int64*) final;
-
- private:
- int64_t quantum_ = 0;
- int64_t window_start_ = 0;
-
- // max of int64_t because SQLite technically only supports int64s and not
- // uint64s.
- int64_t window_dur_ = std::numeric_limits<int64_t>::max();
+ static int Update(sqlite3_vtab*, int, sqlite3_value**, sqlite_int64*);
};
-} // namespace trace_processor
-} // namespace perfetto
+} // namespace perfetto::trace_processor
#endif // SRC_TRACE_PROCESSOR_PERFETTO_SQL_INTRINSICS_OPERATORS_WINDOW_OPERATOR_H_
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 6edd50b..814b6ba 100644
--- a/src/trace_processor/perfetto_sql/intrinsics/table_functions/BUILD.gn
+++ b/src/trace_processor/perfetto_sql/intrinsics/table_functions/BUILD.gn
@@ -27,6 +27,8 @@
"descendant.h",
"dfs.cc",
"dfs.h",
+ "dfs_weight_bounded.cc",
+ "dfs_weight_bounded.h",
"dominator_tree.cc",
"dominator_tree.h",
"experimental_annotated_stack.cc",
diff --git a/src/trace_processor/perfetto_sql/intrinsics/table_functions/dfs.h b/src/trace_processor/perfetto_sql/intrinsics/table_functions/dfs.h
index 8dad16d..43e3239 100644
--- a/src/trace_processor/perfetto_sql/intrinsics/table_functions/dfs.h
+++ b/src/trace_processor/perfetto_sql/intrinsics/table_functions/dfs.h
@@ -35,9 +35,9 @@
//
// Arguments:
// 1) |source_node_ids|: RepeatedBuilderResult proto containing a column of
-// int64 values corresponding to the source of edges.
+// uint32 values corresponding to the source of edges.
// 2) |dest_node_ids|: RepeatedBuilderResult proto containing a column of
-// int64 values corresponding to the destination of edges. This number of
+// uint32 values corresponding to the destination of edges. This number of
// values should be the same as |source_node_ids| with each index in
// |source_node_ids| acting as the source for the corresponding index in
// |dest_node_ids|.
@@ -47,7 +47,7 @@
// Returns:
// A table with the nodes reachable from the start node and their "parent" in
// the tree generated by the DFS. The schema of the table
-// is (node_id int64_t, parent_node_id optional<int64_t>).
+// is (node_id uint32_t, parent_node_id optional<uint32_t>).
//
// Note: this function is not intended to be used directly from SQL: instead
// macros exist in the standard library, wrapping it and making it
diff --git a/src/trace_processor/perfetto_sql/intrinsics/table_functions/dfs_weight_bounded.cc b/src/trace_processor/perfetto_sql/intrinsics/table_functions/dfs_weight_bounded.cc
new file mode 100644
index 0000000..4ff33cc
--- /dev/null
+++ b/src/trace_processor/perfetto_sql/intrinsics/table_functions/dfs_weight_bounded.cc
@@ -0,0 +1,288 @@
+/*
+ * Copyright (C) 2024 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/dfs_weight_bounded.h"
+
+#include <algorithm>
+#include <cstddef>
+#include <cstdint>
+#include <memory>
+#include <optional>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "perfetto/base/logging.h"
+#include "perfetto/base/status.h"
+#include "perfetto/ext/base/status_or.h"
+#include "perfetto/protozero/proto_decoder.h"
+#include "perfetto/trace_processor/basic_types.h"
+#include "protos/perfetto/trace_processor/metrics_impl.pbzero.h"
+#include "src/trace_processor/containers/string_pool.h"
+#include "src/trace_processor/db/column.h"
+#include "src/trace_processor/db/table.h"
+#include "src/trace_processor/perfetto_sql/intrinsics/table_functions/tables_py.h"
+#include "src/trace_processor/util/status_macros.h"
+
+namespace perfetto::trace_processor {
+namespace tables {
+DfsWeightBoundedTable::~DfsWeightBoundedTable() = default;
+} // namespace tables
+
+namespace {
+struct Edge {
+ uint32_t id;
+ uint32_t weight;
+};
+using Destinations = std::vector<Edge>;
+
+base::StatusOr<std::vector<Destinations>> ParseSourceToDestionationsMap(
+ protos::pbzero::RepeatedBuilderResult::Decoder& source,
+ protos::pbzero::RepeatedBuilderResult::Decoder& dest,
+ protos::pbzero::RepeatedBuilderResult::Decoder& weight) {
+ std::vector<Destinations> source_to_destinations_map;
+ bool parse_error = false;
+ auto source_node_ids = source.int_values(&parse_error);
+ auto dest_node_ids = dest.int_values(&parse_error);
+ auto edge_weights = weight.int_values(&parse_error);
+
+ for (; source_node_ids && dest_node_ids && edge_weights;
+ ++source_node_ids, ++dest_node_ids, ++edge_weights) {
+ source_to_destinations_map.resize(
+ std::max(source_to_destinations_map.size(),
+ std::max(static_cast<size_t>(*source_node_ids + 1),
+ static_cast<size_t>(*dest_node_ids + 1))));
+ source_to_destinations_map[static_cast<uint32_t>(*source_node_ids)]
+ .push_back(Edge{static_cast<uint32_t>(*dest_node_ids),
+ static_cast<uint32_t>(*edge_weights)});
+ }
+ if (parse_error) {
+ return base::ErrStatus("Failed while parsing source or dest ids");
+ }
+ if (static_cast<bool>(source_node_ids) != static_cast<bool>(dest_node_ids)) {
+ return base::ErrStatus(
+ "dfs_weight_bounded: length of source and destination columns is not "
+ "the same");
+ }
+ return source_to_destinations_map;
+}
+
+base::StatusOr<std::vector<Edge>> ParseRootToMaxWeightMap(
+ protos::pbzero::RepeatedBuilderResult::Decoder& start,
+ protos::pbzero::RepeatedBuilderResult::Decoder& end) {
+ std::vector<Edge> roots;
+ bool parse_error = false;
+ auto root_node_ids = start.int_values(&parse_error);
+ auto max_weights = end.int_values(&parse_error);
+
+ for (; root_node_ids && max_weights; ++root_node_ids, ++max_weights) {
+ roots.push_back(Edge{static_cast<uint32_t>(*root_node_ids),
+ static_cast<uint32_t>(*max_weights)});
+ }
+
+ if (parse_error) {
+ return base::ErrStatus(
+ "Failed while parsing root_node_ids or root_max_weights");
+ }
+ if (static_cast<bool>(root_node_ids) != static_cast<bool>(max_weights)) {
+ return base::ErrStatus(
+ "dfs_weight_bounded: length of root_node_ids and root_max_weights "
+ "columns is not the same");
+ }
+ return roots;
+}
+
+void DfsWeightBoundedImpl(
+ tables::DfsWeightBoundedTable* table,
+ const std::vector<Destinations>& source_to_destinations_map,
+ const std::vector<Edge>& roots) {
+ struct StackState {
+ uint32_t id;
+ uint32_t weight;
+ std::optional<uint32_t> parent_id;
+ };
+
+ std::vector<uint8_t> seen_node_ids(source_to_destinations_map.size());
+ std::vector<StackState> stack;
+
+ for (const auto& root : roots) {
+ stack.clear();
+ stack.push_back({root.id, 0, std::nullopt});
+ std::fill(seen_node_ids.begin(), seen_node_ids.end(), 0);
+
+ for (uint32_t total_weight = 0; !stack.empty();) {
+ StackState stack_state = stack.back();
+ stack.pop_back();
+
+ if (seen_node_ids[stack_state.id]) {
+ continue;
+ }
+ seen_node_ids[stack_state.id] = true;
+
+ // We want to greedily return all possible edges that are reachable within
+ // the target weight. If an edge already fails the requirement, skip it
+ // and don't include it's weight but continue the search, some other edges
+ // might fit.
+ if (total_weight + stack_state.weight > root.weight) {
+ continue;
+ }
+ total_weight += stack_state.weight;
+
+ tables::DfsWeightBoundedTable::Row row;
+ row.root_node_id = root.id;
+ row.node_id = stack_state.id;
+ row.parent_node_id = stack_state.parent_id;
+ table->Insert(row);
+
+ PERFETTO_DCHECK(stack_state.id < source_to_destinations_map.size());
+
+ const auto& children = source_to_destinations_map[stack_state.id];
+ for (auto it = children.rbegin(); it != children.rend(); ++it) {
+ stack.emplace_back(StackState{(*it).id, (*it).weight, stack_state.id});
+ }
+ }
+ }
+}
+} // namespace
+
+DfsWeightBounded::DfsWeightBounded(StringPool* pool) : pool_(pool) {}
+DfsWeightBounded::~DfsWeightBounded() = default;
+
+Table::Schema DfsWeightBounded::CreateSchema() {
+ return tables::DfsWeightBoundedTable::ComputeStaticSchema();
+}
+
+std::string DfsWeightBounded::TableName() {
+ return tables::DfsWeightBoundedTable::Name();
+}
+
+uint32_t DfsWeightBounded::EstimateRowCount() {
+ // TODO(lalitm): improve this estimate.
+ return 1024;
+}
+
+base::StatusOr<std::unique_ptr<Table>> DfsWeightBounded::ComputeTable(
+ const std::vector<SqlValue>& arguments) {
+ PERFETTO_CHECK(arguments.size() == 5);
+
+ const SqlValue& raw_source_ids = arguments[0];
+ const SqlValue& raw_dest_ids = arguments[1];
+ const SqlValue& raw_edge_weights = arguments[2];
+ const SqlValue& raw_root_ids = arguments[3];
+ const SqlValue& raw_root_max_weights = arguments[4];
+
+ if (raw_source_ids.is_null() && raw_dest_ids.is_null() &&
+ raw_edge_weights.is_null() && raw_root_ids.is_null() &&
+ raw_root_max_weights.is_null()) {
+ return std::unique_ptr<Table>(
+ std::make_unique<tables::DfsWeightBoundedTable>(pool_));
+ }
+
+ if (raw_source_ids.is_null() || raw_dest_ids.is_null() ||
+ raw_edge_weights.is_null() || raw_root_ids.is_null() ||
+ raw_root_max_weights.is_null()) {
+ return base::ErrStatus(
+ "dfs_weight_bounded: either all arguments should be null or none "
+ "should be");
+ }
+ if (raw_source_ids.type != SqlValue::kBytes) {
+ return base::ErrStatus(
+ "dfs_weight_bounded: source_node_ids should be a repeated field");
+ }
+ if (raw_dest_ids.type != SqlValue::kBytes) {
+ return base::ErrStatus(
+ "dfs_weight_bounded: dest_node_ids should be a repeated field");
+ }
+ if (raw_edge_weights.type != SqlValue::kBytes) {
+ return base::ErrStatus(
+ "dfs_weight_bounded: edge_weights should be a repeated field");
+ }
+ if (raw_root_ids.type != SqlValue::kBytes) {
+ return base::ErrStatus(
+ "dfs_weight_bounded: root_ids should be a repeated field");
+ }
+ if (raw_root_max_weights.type != SqlValue::kBytes) {
+ return base::ErrStatus(
+ "dfs_weight_bounded: root_max_weights should be a repeated field");
+ }
+
+ protos::pbzero::ProtoBuilderResult::Decoder proto_source_ids(
+ static_cast<const uint8_t*>(raw_source_ids.AsBytes()),
+ raw_source_ids.bytes_count);
+ if (!proto_source_ids.is_repeated()) {
+ return base::ErrStatus(
+ "dfs_weight_bounded: source_node_ids is not generated by RepeatedField "
+ "function");
+ }
+ protos::pbzero::RepeatedBuilderResult::Decoder source_ids(
+ proto_source_ids.repeated());
+
+ protos::pbzero::ProtoBuilderResult::Decoder proto_dest_ids(
+ static_cast<const uint8_t*>(raw_dest_ids.AsBytes()),
+ raw_dest_ids.bytes_count);
+ if (!proto_dest_ids.is_repeated()) {
+ return base::ErrStatus(
+ "dfs_weight_bounded: dest_node_ids is not generated by RepeatedField "
+ "function");
+ }
+ protos::pbzero::RepeatedBuilderResult::Decoder dest_ids(
+ proto_dest_ids.repeated());
+
+ protos::pbzero::ProtoBuilderResult::Decoder proto_edge_weights(
+ static_cast<const uint8_t*>(raw_edge_weights.AsBytes()),
+ raw_edge_weights.bytes_count);
+ if (!proto_edge_weights.is_repeated()) {
+ return base::ErrStatus(
+ "dfs_weight_bounded: edge_weights is not generated by RepeatedField "
+ "function");
+ }
+ protos::pbzero::RepeatedBuilderResult::Decoder edge_weights(
+ proto_edge_weights.repeated());
+
+ protos::pbzero::ProtoBuilderResult::Decoder proto_root_ids(
+ static_cast<const uint8_t*>(raw_root_ids.AsBytes()),
+ raw_root_ids.bytes_count);
+ if (!proto_root_ids.is_repeated()) {
+ return base::ErrStatus(
+ "dfs_weight_bounded: root_ids is not generated by RepeatedField "
+ "function");
+ }
+ protos::pbzero::RepeatedBuilderResult::Decoder root_ids(
+ proto_root_ids.repeated());
+
+ protos::pbzero::ProtoBuilderResult::Decoder proto_root_max_weights(
+ static_cast<const uint8_t*>(raw_root_max_weights.AsBytes()),
+ raw_root_max_weights.bytes_count);
+ if (!proto_root_max_weights.is_repeated()) {
+ return base::ErrStatus(
+ "dfs_weight_bounded: root_max_weights is not generated by "
+ "RepeatedField function");
+ }
+ protos::pbzero::RepeatedBuilderResult::Decoder root_max_weights(
+ proto_root_max_weights.repeated());
+
+ ASSIGN_OR_RETURN(auto map, ParseSourceToDestionationsMap(source_ids, dest_ids,
+ edge_weights));
+
+ ASSIGN_OR_RETURN(auto roots,
+ ParseRootToMaxWeightMap(root_ids, root_max_weights));
+
+ auto table = std::make_unique<tables::DfsWeightBoundedTable>(pool_);
+ DfsWeightBoundedImpl(table.get(), map, roots);
+ return std::unique_ptr<Table>(std::move(table));
+}
+
+} // namespace perfetto::trace_processor
diff --git a/src/trace_processor/perfetto_sql/intrinsics/table_functions/dfs_weight_bounded.h b/src/trace_processor/perfetto_sql/intrinsics/table_functions/dfs_weight_bounded.h
new file mode 100644
index 0000000..f1af772
--- /dev/null
+++ b/src/trace_processor/perfetto_sql/intrinsics/table_functions/dfs_weight_bounded.h
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2024 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_DFS_WEIGHT_BOUNDED_H_
+#define SRC_TRACE_PROCESSOR_PERFETTO_SQL_INTRINSICS_TABLE_FUNCTIONS_DFS_WEIGHT_BOUNDED_H_
+
+#include <cstdint>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "perfetto/ext/base/status_or.h"
+#include "perfetto/trace_processor/basic_types.h"
+#include "src/trace_processor/containers/string_pool.h"
+#include "src/trace_processor/db/table.h"
+#include "src/trace_processor/perfetto_sql/intrinsics/table_functions/static_table_function.h"
+
+namespace perfetto::trace_processor {
+
+// An SQL table-function which performs a weight bounded DFS from a set of start
+// nodes in a graph and returns all the nodes which are reachable from each
+// start node independently.
+//
+// Arguments:
+// 1) |source_node_ids|: RepeatedBuilderResult proto containing a column of
+// uint32 values corresponding to the source of edges.
+// 2) |dest_node_ids|: RepeatedBuilderResult proto containing a column of
+// uint32 values corresponding to the destination of edges. This number of
+// values should be the same as |source_node_ids| with each index in
+// |source_node_ids| acting as the source for the corresponding index in
+// |dest_node_ids|.
+// 3) |edge_weights|: RepeatedBuilderResult proto containing a column of
+// uint32 values corresponding to the weight of edges. This number of
+// values should be the same as |source_node_ids| with each index in
+// |source_node_ids| acting as the source for the corresponding index in
+// |edge_weights|.
+// 4) |root_node_ids|: RepeatedBuilderResult proto containing a column of
+// uint32 values corresponding to the ID of the start nodes in the graph
+// from which reachability should be computed.
+// 5) |root_max_weights|: RepeatedBuilderResult proto containing a column of
+// uint32 values corresponding to the max sum of edge weights inclusive,
+// at which point the DFS from the |root_node_ids| stops. This number of
+// values should be the same as |root_node_ids|.
+//
+// Returns:
+// A table with the nodes reachable from the start node, their "parent" in
+// the tree generated by the DFS and the starting node itself "root". The
+// schema of the table is (root_node_id uint32_t, node_id uint32_t,
+// parent_node_id optional<uint32_t>).
+//
+// Note: this function is not intended to be used directly from SQL: instead
+// macros exist in the standard library, wrapping it and making it
+// user-friendly.
+class DfsWeightBounded : public StaticTableFunction {
+ public:
+ explicit DfsWeightBounded(StringPool*);
+ virtual ~DfsWeightBounded() override;
+
+ // StaticTableFunction implementation.
+ Table::Schema CreateSchema() override;
+ std::string TableName() override;
+ uint32_t EstimateRowCount() override;
+ base::StatusOr<std::unique_ptr<Table>> ComputeTable(
+ const std::vector<SqlValue>& arguments) override;
+
+ private:
+ StringPool* pool_ = nullptr;
+};
+
+} // namespace perfetto::trace_processor
+
+#endif // SRC_TRACE_PROCESSOR_PERFETTO_SQL_INTRINSICS_TABLE_FUNCTIONS_DFS_WEIGHT_BOUNDED_H_
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 3c86e50..0570082 100644
--- a/src/trace_processor/perfetto_sql/intrinsics/table_functions/tables.py
+++ b/src/trace_processor/perfetto_sql/intrinsics/table_functions/tables.py
@@ -187,6 +187,27 @@
C("in_right_durs", CppOptional(CppString()), flags=ColumnFlag.HIDDEN),
])
+DFS_WEIGHT_BOUNDED_TABLE = Table(
+ python_module=__file__,
+ class_name="DfsWeightBoundedTable",
+ sql_name="__intrinsic_dfs_weight_bounded",
+ columns=[
+ C("root_node_id", CppUint32()),
+ C("node_id", CppUint32()),
+ C("parent_node_id", CppOptional(CppUint32())),
+ C("in_source_node_ids",
+ CppOptional(CppUint32()),
+ flags=ColumnFlag.HIDDEN),
+ C("in_dest_node_ids", CppOptional(CppUint32()),
+ flags=ColumnFlag.HIDDEN),
+ C("in_edge_weights", CppOptional(CppUint32()), flags=ColumnFlag.HIDDEN),
+ C("in_root_node_ids", CppOptional(CppUint32()),
+ flags=ColumnFlag.HIDDEN),
+ C("in_root_max_weights",
+ CppOptional(CppUint32()),
+ flags=ColumnFlag.HIDDEN),
+ ])
+
# Keep this list sorted.
ALL_TABLES = [
ANCESTOR_SLICE_BY_STACK_TABLE,
@@ -196,6 +217,7 @@
DESCENDANT_SLICE_BY_STACK_TABLE,
DESCENDANT_SLICE_TABLE,
DFS_TABLE,
+ DFS_WEIGHT_BOUNDED_TABLE,
DOMINATOR_TREE_TABLE,
EXPERIMENTAL_ANNOTATED_CALLSTACK_TABLE,
EXPERIMENTAL_COUNTER_DUR_TABLE,
diff --git a/src/trace_processor/perfetto_sql/stdlib/android/BUILD.gn b/src/trace_processor/perfetto_sql/stdlib/android/BUILD.gn
index 29c4b89..87f8dec 100644
--- a/src/trace_processor/perfetto_sql/stdlib/android/BUILD.gn
+++ b/src/trace_processor/perfetto_sql/stdlib/android/BUILD.gn
@@ -23,6 +23,7 @@
"battery_stats.sql",
"binder.sql",
"broadcasts.sql",
+ "critical_blocking_calls.sql",
"dvfs.sql",
"freezer.sql",
"garbage_collection.sql",
diff --git a/src/trace_processor/perfetto_sql/stdlib/android/critical_blocking_calls.sql b/src/trace_processor/perfetto_sql/stdlib/android/critical_blocking_calls.sql
new file mode 100644
index 0000000..ba5a3f6
--- /dev/null
+++ b/src/trace_processor/perfetto_sql/stdlib/android/critical_blocking_calls.sql
@@ -0,0 +1,84 @@
+--
+-- Copyright 2024 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
+--
+-- https://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 PERFETTO MODULE android.slices;
+INCLUDE PERFETTO MODULE android.binder;
+INCLUDE PERFETTO MODULE slices.with_context;
+
+CREATE PERFETTO FUNCTION _is_relevant_blocking_call(name STRING, depth INT)
+RETURNS BOOL AS SELECT
+ $name = 'measure'
+ OR $name = 'layout'
+ OR $name = 'configChanged'
+ OR $name = 'animation'
+ OR $name = 'input'
+ OR $name = 'traversal'
+ OR $name = 'Contending for pthread mutex'
+ OR $name = 'postAndWait'
+ OR $name GLOB 'monitor contention with*'
+ OR $name GLOB 'SuspendThreadByThreadId*'
+ OR $name GLOB 'LoadApkAssetsFd*'
+ OR $name GLOB '*binder transaction*'
+ OR $name GLOB 'inflate*'
+ OR $name GLOB 'Lock contention on*'
+ OR $name GLOB 'android.os.Handler: kotlinx.coroutines*'
+ OR $name GLOB 'relayoutWindow*'
+ OR $name GLOB 'ImageDecoder#decode*'
+ OR $name GLOB 'NotificationStackScrollLayout#onMeasure'
+ OR $name GLOB 'ExpNotRow#*'
+ OR $name GLOB 'GC: Wait For*'
+ OR (
+ -- Some top level handler slices
+ $depth = 0
+ AND $name NOT GLOB '*Choreographer*'
+ AND $name NOT GLOB '*Input*'
+ AND $name NOT GLOB '*input*'
+ AND $name NOT GLOB 'android.os.Handler: #*'
+ AND (
+ -- Handler pattern heuristics
+ $name GLOB '*Handler: *$*'
+ OR $name GLOB '*.*.*: *$*'
+ OR $name GLOB '*.*$*: #*'
+ )
+ );
+
+
+--Extract all slice data on main thread for all processes.
+CREATE PERFETTO TABLE _android_critical_blocking_calls AS
+SELECT
+ android_standardize_slice_name(s.name) AS name,
+ s.ts,
+ s.dur,
+ s.id,
+ s.process_name,
+ thread.utid,
+ s.upid
+FROM thread_slice s JOIN
+thread USING (utid)
+WHERE
+ thread.is_main_thread AND _is_relevant_blocking_call(s.name, s.depth)
+UNION ALL
+-- As binder names are not included in slice table, extract these directly from the
+-- android_binder_txns table.
+SELECT
+ tx.aidl_name AS name,
+ tx.client_ts AS ts,
+ tx.client_dur AS dur,
+ tx.binder_txn_id AS id,
+ tx.client_process as process_name,
+ tx.client_utid as utid,
+ tx.client_upid as upid
+FROM android_binder_txns AS tx
+WHERE is_main_thread AND aidl_name IS NOT NULL AND is_sync = 1;
diff --git a/src/trace_processor/perfetto_sql/stdlib/graphs/search.sql b/src/trace_processor/perfetto_sql/stdlib/graphs/search.sql
index 4bba25a..6214716 100644
--- a/src/trace_processor/perfetto_sql/stdlib/graphs/search.sql
+++ b/src/trace_processor/perfetto_sql/stdlib/graphs/search.sql
@@ -99,3 +99,82 @@
SELECT node_id, lead(node_id) OVER (PARTITION BY node_parent_id ORDER BY sort_key) AS next_node_id
FROM $graph_table
);
+
+-- Computes the "reachable" set of nodes in a directed graph from a set of
+-- starting (root) nodes by performing a depth-first search from each root node on the graph.
+-- The search is bounded by the sum of edge weights on the path and the root node specifies the
+-- max weight (inclusive) allowed before stopping the search.
+-- The returned nodes are structured as a tree with parent-child relationships corresponding
+-- to the order in which nodes were encountered by the DFS. Each row also has the root node from
+-- which where the edge was encountered.
+--
+-- While this macro can be used directly by end users (hence being public),
+-- it is primarily intended as a lower-level building block upon which higher
+-- level functions/macros in the standard library can be built.
+--
+-- Example usage on traces with sched info:
+-- ```
+-- -- Compute the reachable nodes from a sched wakeup chain
+-- INCLUDE PERFETTO MODULE sched.thread_executing_spans;
+--
+-- SELECT *
+-- FROM
+-- graph_reachable_dfs_bounded
+-- !(
+-- (
+-- SELECT
+-- id AS source_node_id,
+-- COALESCE(parent_id, id) AS dest_node_id,
+-- id - COALESCE(parent_id, id) AS edge_weight
+-- FROM _wakeup_chain
+-- ),
+-- (
+-- SELECT
+-- id AS root_node_id,
+-- id - COALESCE(prev_id, id) AS root_max_weight
+-- FROM _wakeup_chain
+-- ));
+-- ```
+CREATE PERFETTO MACRO graph_reachable_weight_bounded_dfs(
+ -- A table/view/subquery corresponding to a directed graph on which the
+ -- reachability search should be performed. This table must have the columns
+ -- "source_node_id" and "dest_node_id" corresponding to the two nodes on
+ -- either end of the edges in the graph and an "edge_weight" corresponding to the
+ -- weight of the edge between the node.
+ --
+ -- Note: the columns must contain uint32 similar to ids in trace processor
+ -- tables (i.e. the values should be relatively dense and close to zero). The
+ -- implementation makes assumptions on this for performance reasons and, if
+ -- this criteria is not, can lead to enormous amounts of memory being
+ -- allocated.
+ graph_table TableOrSubquery,
+ -- A table/view/subquery corresponding to start nodes to |graph_table| which will be the
+ -- roots of the reachability trees. This table must have the columns
+ -- "root_node_id" and "root_max_weight" corresponding to the starting node id and the max
+ -- weight allowed on the tree.
+ --
+ -- Note: the columns must contain uint32 similar to ids in trace processor
+ -- tables (i.e. the values should be relatively dense and close to zero). The
+ -- implementation makes assumptions on this for performance reasons and, if
+ -- this criteria is not, can lead to enormous amounts of memory being
+ -- allocated.
+ root_table TableOrSubquery
+)
+-- The returned table has the schema (root_node_id, node_id UINT32, parent_node_id UINT32).
+-- |root_node_id| is the id of the starting node under which this edge was encountered.
+-- |node_id| is the id of the node from the input graph and |parent_node_id|
+-- is the id of the node which was the first encountered predecessor in a DFS
+-- search of the graph.
+RETURNS TableOrSubquery AS
+(
+ WITH __temp_graph_table AS (SELECT * FROM $graph_table),
+ __temp_root_table AS (SELECT * FROM $root_table)
+ SELECT dt.root_node_id, dt.node_id, dt.parent_node_id
+ FROM __intrinsic_dfs_weight_bounded(
+ (SELECT RepeatedField(source_node_id) FROM __temp_graph_table),
+ (SELECT RepeatedField(dest_node_id) FROM __temp_graph_table),
+ (SELECT RepeatedField(edge_weight) FROM __temp_graph_table),
+ (SELECT RepeatedField(root_node_id) FROM __temp_root_table),
+ (SELECT RepeatedField(root_max_weight) FROM __temp_root_table)
+ ) dt
+);
diff --git a/src/trace_processor/sqlite/BUILD.gn b/src/trace_processor/sqlite/BUILD.gn
index e82712d..0a3beef 100644
--- a/src/trace_processor/sqlite/BUILD.gn
+++ b/src/trace_processor/sqlite/BUILD.gn
@@ -28,15 +28,12 @@
"sql_stats_table.h",
"sqlite_engine.cc",
"sqlite_engine.h",
- "sqlite_result.h",
"sqlite_table.cc",
"sqlite_table.h",
"sqlite_tokenizer.cc",
"sqlite_tokenizer.h",
"sqlite_utils.cc",
"sqlite_utils.h",
- "sqlite_utils.h",
- "sqlite_window_function.h",
"stats_table.cc",
"stats_table.h",
]
@@ -61,6 +58,7 @@
"../util:profile_builder",
"../util:regex",
]
+ public_deps = [ "bindings" ]
}
source_set("query_constraints") {
diff --git a/src/trace_processor/sqlite/bindings/BUILD.gn b/src/trace_processor/sqlite/bindings/BUILD.gn
new file mode 100644
index 0000000..b61cfde
--- /dev/null
+++ b/src/trace_processor/sqlite/bindings/BUILD.gn
@@ -0,0 +1,31 @@
+# Copyright (C) 2024 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.
+
+import("../../../../gn/test.gni")
+
+assert(enable_perfetto_trace_processor_sqlite)
+
+source_set("bindings") {
+ sources = [
+ "sqlite_aggregate_function.h",
+ "sqlite_module.h",
+ "sqlite_result.h",
+ "sqlite_window_function.h",
+ ]
+ deps = [
+ "../../../../gn:default_deps",
+ "../../../../gn:sqlite",
+ ]
+ visibility = [ "..:sqlite" ]
+}
diff --git a/src/trace_processor/sqlite/bindings/README.md b/src/trace_processor/sqlite/bindings/README.md
new file mode 100644
index 0000000..fff42e8
--- /dev/null
+++ b/src/trace_processor/sqlite/bindings/README.md
@@ -0,0 +1,3 @@
+This folder contains very lightweight bindings around SQLite to adapt
+it C++. Any non-trivial code should not live in this folder but in some
+higher layer.
\ No newline at end of file
diff --git a/src/trace_processor/sqlite/bindings/sqlite_aggregate_function.h b/src/trace_processor/sqlite/bindings/sqlite_aggregate_function.h
new file mode 100644
index 0000000..103dc32
--- /dev/null
+++ b/src/trace_processor/sqlite/bindings/sqlite_aggregate_function.h
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2024 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_SQLITE_BINDINGS_SQLITE_AGGREGATE_FUNCTION_H_
+#define SRC_TRACE_PROCESSOR_SQLITE_BINDINGS_SQLITE_AGGREGATE_FUNCTION_H_
+
+struct sqlite3_context;
+struct sqlite3_value;
+
+namespace perfetto::trace_processor {
+
+// Prototype for a aggregate function which can be registered with SQLite.
+//
+// See https://www.sqlite.org/c3ref/create_function.html for details on how to
+// implement the methods of this class.
+struct SqliteAggregateFunction {
+ // The type of the context object which will be passed to the function.
+ // Can be redefined in any sub-classes to override the context.
+ using Context = void;
+
+ // The xStep function which will be executed by SQLite to add a row of values
+ // to the aggregate.
+ //
+ // Implementations MUST define this function themselves; this function is
+ // declared but *not* defined so linker errors will be thrown if not defined.
+ static void Step(sqlite3_context*, int argc, sqlite3_value** argv);
+
+ // The xFinal function which will be executed by SQLite to obtain the current
+ // value of the aggregate *and* free all resources allocated by previous calls
+ // to Step.
+ //
+ // Implementations MUST define this function themselves; this function is
+ // declared but *not* defined so linker errors will be thrown if not defined.
+ static void Final(sqlite3_context* ctx);
+};
+
+} // namespace perfetto::trace_processor
+
+#endif // SRC_TRACE_PROCESSOR_SQLITE_BINDINGS_SQLITE_AGGREGATE_FUNCTION_H_
diff --git a/src/trace_processor/sqlite/bindings/sqlite_module.h b/src/trace_processor/sqlite/bindings/sqlite_module.h
new file mode 100644
index 0000000..0da20cc
--- /dev/null
+++ b/src/trace_processor/sqlite/bindings/sqlite_module.h
@@ -0,0 +1,234 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache, 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, software
+ * distributed under the License is distributed on an "AS IS",
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef SRC_TRACE_PROCESSOR_SQLITE_BINDINGS_SQLITE_MODULE_H_
+#define SRC_TRACE_PROCESSOR_SQLITE_BINDINGS_SQLITE_MODULE_H_
+
+#include <sqlite3.h>
+
+namespace perfetto::trace_processor::sqlite {
+
+// Prototype for a virtual table (vtab) module which can be registered with
+// SQLite.
+//
+// See https://www.sqlite.org/vtab.html for how to implement this class.
+template <typename Impl>
+struct Module {
+ // Specifies the type of module: implementations can override this field by
+ // declaring and defining it.
+ //
+ // Specifying this to kCreateOnly requires that the |Create| and |Destroy|
+ // functions are defined.
+ //
+ // See the SQLite documentation on what these types mean.
+ static constexpr enum { kEponymousOnly, kCreateOnly } kType = kCreateOnly;
+
+ // Specifies whether this table is supports making changes to it:
+ // implementations can override this field by declaring and defining it.
+ //
+ // Setting this to true requires the |Update| function to be defined.
+ static constexpr bool kSupportsWrites = true;
+
+ // Specifies whether this table supports overloading functions:
+ // implementations can override this field by declaring and defining it.
+ //
+ // Setting this to true requires that the |FindFunction| function is defined.
+ static constexpr bool kDoesOverloadFunctions = true;
+
+ // sqlite3_module object corresponding to the module. Used to pass information
+ // about this module to SQLite.
+ //
+ // Note: this has to be defined here to allow referencing the functions
+ // defined above.
+ static constexpr sqlite3_module kModule = []() {
+ sqlite3_module module{};
+ module.xBestIndex = &Impl::BestIndex;
+ module.xOpen = &Impl::Open;
+ module.xClose = &Impl::Close;
+ module.xFilter = &Impl::Filter;
+ module.xNext = &Impl::Next;
+ module.xEof = &Impl::Eof;
+ module.xColumn = &Impl::Column;
+ module.xRowid = &Impl::Rowid;
+ if constexpr (Impl::kType == kCreateOnly) {
+ module.xCreate = &Impl::Create;
+ module.xDestroy = &Impl::Destroy;
+ module.xConnect = &Impl::Connect;
+ module.xDisconnect = &Impl::Disconnect;
+ } else {
+ module.xCreate = nullptr;
+ module.xDestroy = [](sqlite3_vtab*) -> int {
+ __builtin_trap();
+ __builtin_unreachable();
+ };
+ module.xConnect = &Impl::Connect;
+ module.xDisconnect = &Impl::Disconnect;
+ }
+ if constexpr (Impl::kSupportsWrites) {
+ module.xUpdate = &Impl::Update;
+ }
+ if constexpr (Impl::kDoesOverloadFunctions) {
+ module.xFindFunction = &Impl::FindFunction;
+ }
+ return module;
+ }();
+
+ // Specifies the type of context for the module. Implementations should define
+ // this type to match the context type which is expected to be passed into
+ // |sqlite3_create_module|.
+ using Context = void;
+
+ // Specifies the type for the vtab created by this module.
+ //
+ // Implementations should define this type to match the vtab type they use in
+ // |Create| and |Connect|.
+ using Vtab = sqlite3_vtab;
+
+ // Specifies the type for the cursor created by this module.
+ //
+ // Implementations should define this type to match the cursor type they use
+ // in |Open| and |Close|.
+ using Cursor = sqlite3_vtab_cursor;
+
+ // Creates a new instance of a virtual table and its backing storage.
+ //
+ // Implementations MUST define this function themselves if
+ // |kType| == |kCreateOnly|; this function is declared but *not* defined so
+ // linker errors will be thrown if not defined.
+ static int Create(sqlite3*,
+ void*,
+ int,
+ const char* const*,
+ sqlite3_vtab**,
+ char**);
+
+ // Destroys the virtual table and its backing storage.
+ //
+ // Implementations MUST define this function themselves if
+ // |kType| == |kCreateOnly|; this function is declared but *not* defined so
+ // linker errors will be thrown if not defined.
+ static int Destroy(sqlite3_vtab*);
+
+ // Creates a new instance of the virtual table, connecting to existing
+ // backing storage.
+ //
+ // Implementations MUST define this function themselves; this function is
+ // declared but *not* defined so linker errors will be thrown if not defined.
+ static int Connect(sqlite3*,
+ void*,
+ int,
+ const char* const*,
+ sqlite3_vtab**,
+ char**);
+
+ // Destroys the virtual table but *not* its backing storage.
+ //
+ // Implementations MUST define this function themselves; this function is
+ // declared but *not* defined so linker errors will be thrown if not defined.
+ static int Disconnect(sqlite3_vtab*);
+
+ // Specifies filtering and cost information for the query planner.
+ //
+ // Implementations MUST define this function themselves; this function is
+ // declared but *not* defined so linker errors will be thrown if not defined.
+ static int BestIndex(sqlite3_vtab*, sqlite3_index_info*);
+
+ // Opens a cursor into the given vtab.
+ //
+ // Implementations MUST define this function themselves; this function is
+ // declared but *not* defined so linker errors will be thrown if not defined.
+ static int Open(sqlite3_vtab*, sqlite3_vtab_cursor**);
+
+ // Closes the cursor.
+ //
+ // Implementations MUST define this function themselves; this function is
+ // declared but *not* defined so linker errors will be thrown if not defined.
+ static int Close(sqlite3_vtab_cursor*);
+
+ // Resets this cursor to filter rows matching the provided set of filter
+ // constraints and order by clauses.
+ //
+ // Implementations MUST define this function themselves; this function is
+ // declared but *not* defined so linker errors will be thrown if not defined.
+ static int Filter(sqlite3_vtab_cursor*,
+ int,
+ const char*,
+ int,
+ sqlite3_value**);
+
+ // Forwards the cursor to point to the next row.
+ //
+ // Implementations MUST define this function themselves; this function is
+ // declared but *not* defined so linker errors will be thrown if not defined.
+ static int Next(sqlite3_vtab_cursor*);
+
+ // Returns 1 if the cursor has reached its end or 0 otherwise.
+ //
+ // Implementations MUST define this function themselves; this function is
+ // declared but *not* defined so linker errors will be thrown if not defined.
+ static int Eof(sqlite3_vtab_cursor*);
+
+ // Returns the value column at the given index for the current row the cursor
+ // points to.
+ //
+ // Implementations MUST define this function themselves; this function is
+ // declared but *not* defined so linker errors will be thrown if not defined.
+ static int Column(sqlite3_vtab_cursor*, sqlite3_context*, int);
+
+ // Returns the rowid for the current row.
+ //
+ // Implementations MUST define this function themselves; this function is
+ // declared but *not* defined so linker errors will be thrown if not defined.
+ static int Rowid(sqlite3_vtab_cursor*, sqlite_int64*);
+
+ // Inserts/deletes/updates one row.
+ //
+ // Implementations MUST define this function themselves if
+ // |kSupportsWrites| == |true|; this function is declared but *not* defined so
+ // linker errors will be thrown if not defined.
+ static int Update(sqlite3_vtab*, int, sqlite3_value**, sqlite_int64*);
+
+ // Overloads a function with the given name when executed with a vtab column
+ // as the first argument.
+ //
+ // Implementations MUST define this function themselves if
+ // |kDoesOverloadFunctions| == |true|; this function is declared but *not*
+ // defined so linker errors will be thrown if not defined.
+ static int FindFunction(sqlite3_vtab*,
+ int,
+ const char*,
+ void (**)(sqlite3_context*, int, sqlite3_value**),
+ void**);
+
+ // Helper function to cast the module context pointer to the correct type.
+ static auto GetContext(void* ctx) {
+ return static_cast<typename Impl::Context*>(ctx);
+ }
+
+ // Helper function to cast the vtab pointer to the correct type.
+ static auto GetVtab(sqlite3_vtab* vtab) {
+ return static_cast<typename Impl::Vtab*>(vtab);
+ }
+
+ // Helper function to cast the cursor pointer to the correct type.
+ static auto GetCursor(sqlite3_vtab_cursor* cursor) {
+ return static_cast<typename Impl::Cursor*>(cursor);
+ }
+};
+
+} // namespace perfetto::trace_processor::sqlite
+
+#endif // SRC_TRACE_PROCESSOR_SQLITE_BINDINGS_SQLITE_MODULE_H_
diff --git a/src/trace_processor/sqlite/sqlite_result.h b/src/trace_processor/sqlite/bindings/sqlite_result.h
similarity index 93%
rename from src/trace_processor/sqlite/sqlite_result.h
rename to src/trace_processor/sqlite/bindings/sqlite_result.h
index 4030043..5c1fb21 100644
--- a/src/trace_processor/sqlite/sqlite_result.h
+++ b/src/trace_processor/sqlite/bindings/sqlite_result.h
@@ -14,8 +14,8 @@
* limitations under the License.
*/
-#ifndef SRC_TRACE_PROCESSOR_SQLITE_SQLITE_RESULT_H_
-#define SRC_TRACE_PROCESSOR_SQLITE_SQLITE_RESULT_H_
+#ifndef SRC_TRACE_PROCESSOR_SQLITE_BINDINGS_SQLITE_RESULT_H_
+#define SRC_TRACE_PROCESSOR_SQLITE_BINDINGS_SQLITE_RESULT_H_
#include <sqlite3.h>
#include <cstdint>
@@ -92,4 +92,4 @@
} // namespace perfetto::trace_processor::sqlite::result
-#endif // SRC_TRACE_PROCESSOR_SQLITE_SQLITE_RESULT_H_
+#endif // SRC_TRACE_PROCESSOR_SQLITE_BINDINGS_SQLITE_RESULT_H_
diff --git a/src/trace_processor/sqlite/sqlite_window_function.h b/src/trace_processor/sqlite/bindings/sqlite_window_function.h
similarity index 92%
rename from src/trace_processor/sqlite/sqlite_window_function.h
rename to src/trace_processor/sqlite/bindings/sqlite_window_function.h
index ac61c4f..cba290b 100644
--- a/src/trace_processor/sqlite/sqlite_window_function.h
+++ b/src/trace_processor/sqlite/bindings/sqlite_window_function.h
@@ -14,8 +14,8 @@
* limitations under the License.
*/
-#ifndef SRC_TRACE_PROCESSOR_SQLITE_SQLITE_WINDOW_FUNCTION_H_
-#define SRC_TRACE_PROCESSOR_SQLITE_SQLITE_WINDOW_FUNCTION_H_
+#ifndef SRC_TRACE_PROCESSOR_SQLITE_BINDINGS_SQLITE_WINDOW_FUNCTION_H_
+#define SRC_TRACE_PROCESSOR_SQLITE_BINDINGS_SQLITE_WINDOW_FUNCTION_H_
struct sqlite3_context;
struct sqlite3_value;
@@ -64,4 +64,4 @@
} // namespace perfetto::trace_processor
-#endif // SRC_TRACE_PROCESSOR_SQLITE_SQLITE_WINDOW_FUNCTION_H_
+#endif // SRC_TRACE_PROCESSOR_SQLITE_BINDINGS_SQLITE_WINDOW_FUNCTION_H_
diff --git a/src/trace_processor/sqlite/db_sqlite_table.cc b/src/trace_processor/sqlite/db_sqlite_table.cc
index b0b6678..8c1d9a0 100644
--- a/src/trace_processor/sqlite/db_sqlite_table.cc
+++ b/src/trace_processor/sqlite/db_sqlite_table.cc
@@ -210,9 +210,10 @@
return base::OkStatus();
}
-SqliteTable::Schema DbSqliteTable::ComputeSchema(const Table::Schema& schema,
- const char* table_name) {
- std::vector<SqliteTable::Column> schema_cols;
+SqliteTableLegacy::Schema DbSqliteTable::ComputeSchema(
+ const Table::Schema& schema,
+ const char* table_name) {
+ std::vector<SqliteTableLegacy::Column> schema_cols;
for (uint32_t i = 0; i < schema.columns.size(); ++i) {
const auto& col = schema.columns[i];
schema_cols.emplace_back(i, col.name, col.type, col.is_hidden);
@@ -437,12 +438,12 @@
return QueryCost{final_cost, current_row_count};
}
-std::unique_ptr<SqliteTable::BaseCursor> DbSqliteTable::CreateCursor() {
+std::unique_ptr<SqliteTableLegacy::BaseCursor> DbSqliteTable::CreateCursor() {
return std::make_unique<Cursor>(this, context_->cache);
}
DbSqliteTable::Cursor::Cursor(DbSqliteTable* sqlite_table, QueryCache* cache)
- : SqliteTable::BaseCursor(sqlite_table),
+ : SqliteTableLegacy::BaseCursor(sqlite_table),
db_sqlite_table_(sqlite_table),
cache_(cache) {
switch (db_sqlite_table_->context_->computation) {
diff --git a/src/trace_processor/sqlite/db_sqlite_table.h b/src/trace_processor/sqlite/db_sqlite_table.h
index 82993ca..ded0f84 100644
--- a/src/trace_processor/sqlite/db_sqlite_table.h
+++ b/src/trace_processor/sqlite/db_sqlite_table.h
@@ -89,7 +89,7 @@
using Context = DbSqliteTableContext;
using TableComputation = Context::Computation;
- class Cursor final : public SqliteTable::BaseCursor {
+ class Cursor final : public SqliteTableLegacy::BaseCursor {
public:
Cursor(DbSqliteTable*, QueryCache*);
~Cursor() final;
@@ -100,7 +100,7 @@
Cursor(Cursor&&) noexcept = delete;
Cursor& operator=(Cursor&&) = delete;
- // Implementation of SqliteTable::Cursor.
+ // Implementation of SqliteTableLegacy::Cursor.
base::Status Filter(const QueryConstraints& qc,
sqlite3_value** argv,
FilterHistory);
@@ -197,15 +197,15 @@
virtual ~DbSqliteTable() final;
// Table implementation.
- base::Status Init(int, const char* const*, SqliteTable::Schema*) final;
- std::unique_ptr<SqliteTable::BaseCursor> CreateCursor() final;
+ base::Status Init(int, const char* const*, SqliteTableLegacy::Schema*) final;
+ std::unique_ptr<SqliteTableLegacy::BaseCursor> CreateCursor() final;
base::Status ModifyConstraints(QueryConstraints*) final;
int BestIndex(const QueryConstraints&, BestIndexInfo*) final;
// These static functions are useful to allow other callers to make use
// of them.
- static SqliteTable::Schema ComputeSchema(const Table::Schema&,
- const char* table_name);
+ static SqliteTableLegacy::Schema ComputeSchema(const Table::Schema&,
+ const char* table_name);
static void ModifyConstraints(const Table::Schema&, QueryConstraints*);
static void BestIndex(const Table::Schema&,
uint32_t row_count,
diff --git a/src/trace_processor/sqlite/query_constraints.h b/src/trace_processor/sqlite/query_constraints.h
index 6ff7e5b..8fc08e7 100644
--- a/src/trace_processor/sqlite/query_constraints.h
+++ b/src/trace_processor/sqlite/query_constraints.h
@@ -46,8 +46,8 @@
int op;
// The original index of this constraint in the aConstraint array.
- // Used internally by SqliteTable for xBestIndex - this should not be
- // read or modified by subclasses of SqliteTable.
+ // Used internally by SqliteTableLegacy for xBestIndex - this should not be
+ // read or modified by subclasses of SqliteTableLegacy.
int a_constraint_idx;
};
struct OrderBy {
diff --git a/src/trace_processor/sqlite/sql_source.cc b/src/trace_processor/sqlite/sql_source.cc
index ee86e8c..9de3a03 100644
--- a/src/trace_processor/sqlite/sql_source.cc
+++ b/src/trace_processor/sqlite/sql_source.cc
@@ -18,6 +18,7 @@
#include <sqlite3.h>
#include <algorithm>
+#include <cstddef>
#include <cstdint>
#include <iterator>
#include <limits>
@@ -28,12 +29,16 @@
#include <vector>
#include "perfetto/base/logging.h"
-#include "perfetto/ext/base/flat_hash_map.h"
#include "perfetto/ext/base/string_utils.h"
-#include "perfetto/ext/base/sys_types.h"
-namespace perfetto {
-namespace trace_processor {
+#if SQLITE_VERSION_NUMBER < 3041002
+// There is a bug in pre-3.41.2 versions of SQLite where sqlite3_error_offset
+// can return an offset out of bounds. Make it a hard compiler error to prevent
+// us from hitting this bug.
+#error "SQLite version is too old."
+#endif
+
+namespace perfetto::trace_processor {
namespace {
@@ -47,7 +52,7 @@
const char* new_start = sql.c_str() + offset;
size_t prev_nl = sql.rfind('\n', offset - 1);
- ssize_t nl_count = std::count(sql.c_str(), new_start, '\n');
+ int64_t nl_count = std::count(sql.c_str(), new_start, '\n');
PERFETTO_DCHECK((nl_count == 0) == (prev_nl == std::string_view::npos));
if (prev_nl == std::string::npos) {
@@ -55,7 +60,7 @@
column + static_cast<uint32_t>(offset));
}
- ssize_t new_column = std::distance(sql.c_str() + prev_nl, new_start);
+ int64_t new_column = std::distance(sql.c_str() + prev_nl, new_start);
return std::make_pair(line + static_cast<uint32_t>(nl_count),
static_cast<uint32_t>(new_column));
}
@@ -100,24 +105,24 @@
}
SqlSource SqlSource::FromExecuteQuery(std::string sql) {
- return SqlSource(std::move(sql), "File \"stdin\"", true);
+ return {std::move(sql), "File \"stdin\"", true};
}
SqlSource SqlSource::FromMetric(std::string sql, const std::string& name) {
- return SqlSource(std::move(sql), "Metric \"" + name + "\"", true);
+ return {std::move(sql), "Metric \"" + name + "\"", true};
}
SqlSource SqlSource::FromMetricFile(std::string sql, const std::string& name) {
- return SqlSource(std::move(sql), "Metric file \"" + name + "\"", false);
+ return {std::move(sql), "Metric file \"" + name + "\"", false};
}
SqlSource SqlSource::FromModuleInclude(std::string sql,
const std::string& module) {
- return SqlSource(std::move(sql), "Module include \"" + module + "\"", false);
+ return {std::move(sql), "Module include \"" + module + "\"", false};
}
SqlSource SqlSource::FromTraceProcessorImplementation(std::string sql) {
- return SqlSource(std::move(sql), "Trace Processor Internal", false);
+ return {std::move(sql), "Trace Processor Internal", false};
}
std::string SqlSource::AsTraceback(uint32_t offset) const {
@@ -127,16 +132,7 @@
std::string SqlSource::AsTracebackForSqliteOffset(
std::optional<uint32_t> opt_offset) const {
uint32_t offset = opt_offset.value_or(0);
- // Unfortunately, there is a bug in pre-3.41.2 versions of SQLite where
- // sqlite3_error_offset can return an offset out of bounds. In these
- // situations, zero the offset.
-#if SQLITE_VERSION_NUMBER < 3041002
- if (offset >= sql().size()) {
- offset = 0;
- }
-#else
PERFETTO_CHECK(offset <= sql().size());
-#endif
return AsTraceback(offset);
}
@@ -389,5 +385,4 @@
return SqlSource(std::move(orig_));
}
-} // namespace trace_processor
-} // namespace perfetto
+} // namespace perfetto::trace_processor
diff --git a/src/trace_processor/sqlite/sql_stats_table.cc b/src/trace_processor/sqlite/sql_stats_table.cc
index fb1d59c..3a7b1ab 100644
--- a/src/trace_processor/sqlite/sql_stats_table.cc
+++ b/src/trace_processor/sqlite/sql_stats_table.cc
@@ -17,86 +17,107 @@
#include "src/trace_processor/sqlite/sql_stats_table.h"
#include <sqlite3.h>
+#include <memory>
-#include <algorithm>
-#include <bitset>
-#include <numeric>
-
-#include "perfetto/base/status.h"
-#include "src/trace_processor/sqlite/sqlite_utils.h"
+#include "perfetto/base/logging.h"
+#include "src/trace_processor/sqlite/bindings/sqlite_result.h"
#include "src/trace_processor/storage/trace_storage.h"
-namespace perfetto {
-namespace trace_processor {
+namespace perfetto::trace_processor {
-SqlStatsTable::SqlStatsTable(sqlite3*, const TraceStorage* storage)
- : storage_(storage) {}
-SqlStatsTable::~SqlStatsTable() = default;
-
-base::Status SqlStatsTable::Init(int, const char* const*, Schema* schema) {
- *schema = Schema(
- {
- SqliteTable::Column(Column::kQuery, "query", SqlValue::Type::kString),
- SqliteTable::Column(Column::kTimeStarted, "started",
- SqlValue::Type::kLong),
- SqliteTable::Column(Column::kTimeFirstNext, "first_next",
- SqlValue::Type::kLong),
- SqliteTable::Column(Column::kTimeEnded, "ended",
- SqlValue::Type::kLong),
- },
- {Column::kTimeStarted});
- return util::OkStatus();
-}
-
-std::unique_ptr<SqliteTable::BaseCursor> SqlStatsTable::CreateCursor() {
- return std::unique_ptr<SqliteTable::BaseCursor>(new Cursor(this));
-}
-
-int SqlStatsTable::BestIndex(const QueryConstraints&, BestIndexInfo*) {
+int SqlStatsModule::Connect(sqlite3* db,
+ void* aux,
+ int,
+ const char* const*,
+ sqlite3_vtab** vtab,
+ char**) {
+ static constexpr char kSchema[] = R"(
+ CREATE TABLE x(
+ query TEXT,
+ started BIGINT,
+ first_next BIGINT,
+ ended BIGINT,
+ PRIMARY KEY(started)
+ ) WITHOUT ROWID
+ )";
+ if (int ret = sqlite3_declare_vtab(db, kSchema); ret != SQLITE_OK) {
+ return ret;
+ }
+ std::unique_ptr<Vtab> res = std::make_unique<Vtab>();
+ res->storage = GetContext(aux);
+ *vtab = res.release();
return SQLITE_OK;
}
-SqlStatsTable::Cursor::Cursor(SqlStatsTable* table)
- : SqliteTable::BaseCursor(table),
- storage_(table->storage_),
- table_(table) {}
-SqlStatsTable::Cursor::~Cursor() = default;
-
-base::Status SqlStatsTable::Cursor::Filter(const QueryConstraints&,
- sqlite3_value**,
- FilterHistory) {
- *this = Cursor(table_);
- num_rows_ = storage_->sql_stats().size();
- return base::OkStatus();
+int SqlStatsModule::Disconnect(sqlite3_vtab* vtab) {
+ delete GetVtab(vtab);
+ return SQLITE_OK;
}
-base::Status SqlStatsTable::Cursor::Next() {
- row_++;
- return base::OkStatus();
+int SqlStatsModule::BestIndex(sqlite3_vtab*, sqlite3_index_info*) {
+ return SQLITE_OK;
}
-bool SqlStatsTable::Cursor::Eof() {
- return row_ >= num_rows_;
+int SqlStatsModule::Open(sqlite3_vtab* raw_vtab, sqlite3_vtab_cursor** cursor) {
+ std::unique_ptr<Cursor> c = std::make_unique<Cursor>();
+ c->storage = GetVtab(raw_vtab)->storage;
+ *cursor = c.release();
+ return SQLITE_OK;
}
-base::Status SqlStatsTable::Cursor::Column(sqlite3_context* context, int col) {
- const TraceStorage::SqlStats& stats = storage_->sql_stats();
- switch (col) {
+int SqlStatsModule::Close(sqlite3_vtab_cursor* cursor) {
+ delete GetCursor(cursor);
+ return SQLITE_OK;
+}
+
+int SqlStatsModule::Filter(sqlite3_vtab_cursor* cursor,
+ int,
+ const char*,
+ int,
+ sqlite3_value**) {
+ auto* c = GetCursor(cursor);
+ c->row = 0;
+ c->num_rows = c->storage->sql_stats().size();
+ return SQLITE_OK;
+}
+
+int SqlStatsModule::Next(sqlite3_vtab_cursor* cursor) {
+ GetCursor(cursor)->row++;
+ return SQLITE_OK;
+}
+
+int SqlStatsModule::Eof(sqlite3_vtab_cursor* cursor) {
+ auto* c = GetCursor(cursor);
+ return c->row >= c->num_rows;
+}
+
+int SqlStatsModule::Column(sqlite3_vtab_cursor* cursor,
+ sqlite3_context* ctx,
+ int N) {
+ auto* c = GetCursor(cursor);
+ const TraceStorage::SqlStats& stats = c->storage->sql_stats();
+ switch (N) {
case Column::kQuery:
- sqlite::result::StaticString(context, stats.queries()[row_].c_str());
+ sqlite::result::StaticString(ctx, stats.queries()[c->row].c_str());
break;
case Column::kTimeStarted:
- sqlite::result::Long(context, stats.times_started()[row_]);
+ sqlite::result::Long(ctx, stats.times_started()[c->row]);
break;
case Column::kTimeFirstNext:
- sqlite::result::Long(context, stats.times_first_next()[row_]);
+ sqlite::result::Long(ctx, stats.times_first_next()[c->row]);
break;
case Column::kTimeEnded:
- sqlite::result::Long(context, stats.times_ended()[row_]);
+ sqlite::result::Long(ctx, stats.times_ended()[c->row]);
+ break;
+ default:
+ PERFETTO_FATAL("Unknown column %d", N);
break;
}
- return base::OkStatus();
+ return SQLITE_OK;
}
-} // namespace trace_processor
-} // namespace perfetto
+int SqlStatsModule::Rowid(sqlite3_vtab_cursor*, sqlite_int64*) {
+ return SQLITE_ERROR;
+}
+
+} // namespace perfetto::trace_processor
diff --git a/src/trace_processor/sqlite/sql_stats_table.h b/src/trace_processor/sqlite/sql_stats_table.h
index d78224c..45d5b14 100644
--- a/src/trace_processor/sqlite/sql_stats_table.h
+++ b/src/trace_processor/sqlite/sql_stats_table.h
@@ -17,23 +17,27 @@
#ifndef SRC_TRACE_PROCESSOR_SQLITE_SQL_STATS_TABLE_H_
#define SRC_TRACE_PROCESSOR_SQLITE_SQL_STATS_TABLE_H_
-#include <limits>
-#include <memory>
+#include <cstddef>
-#include "perfetto/base/status.h"
-#include "src/trace_processor/sqlite/sqlite_table.h"
+#include "src/trace_processor/sqlite/bindings/sqlite_module.h"
-namespace perfetto {
-namespace trace_processor {
+namespace perfetto::trace_processor {
class QueryConstraints;
class TraceStorage;
// A virtual table that allows to introspect performances of the SQL engine
// for the kMaxLogEntries queries.
-class SqlStatsTable final
- : public TypedSqliteTable<SqlStatsTable, const TraceStorage*> {
- public:
+struct SqlStatsModule : sqlite::Module<SqlStatsModule> {
+ using Context = TraceStorage;
+ struct Vtab : sqlite::Module<SqlStatsModule>::Vtab {
+ TraceStorage* storage = nullptr;
+ };
+ struct Cursor : sqlite::Module<SqlStatsModule>::Cursor {
+ const TraceStorage* storage = nullptr;
+ size_t row = 0;
+ size_t num_rows = 0;
+ };
enum Column {
kQuery = 0,
kTimeStarted = 1,
@@ -41,46 +45,34 @@
kTimeEnded = 3,
};
- // Implementation of the SQLite cursor interface.
- class Cursor final : public SqliteTable::BaseCursor {
- public:
- explicit Cursor(SqlStatsTable* storage);
- ~Cursor() final;
+ static constexpr auto kType = kEponymousOnly;
+ static constexpr bool kSupportsWrites = false;
+ static constexpr bool kDoesOverloadFunctions = false;
- // Implementation of SqliteTable::Cursor.
- base::Status Filter(const QueryConstraints&,
- sqlite3_value**,
- FilterHistory);
- base::Status Next();
- bool Eof();
- base::Status Column(sqlite3_context*, int N);
+ static int Connect(sqlite3*,
+ void*,
+ int,
+ const char* const*,
+ sqlite3_vtab**,
+ char**);
+ static int Disconnect(sqlite3_vtab*);
- private:
- Cursor(Cursor&) = delete;
- Cursor& operator=(const Cursor&) = delete;
+ static int BestIndex(sqlite3_vtab*, sqlite3_index_info*);
- Cursor(Cursor&&) noexcept = default;
- Cursor& operator=(Cursor&&) = default;
+ static int Open(sqlite3_vtab*, sqlite3_vtab_cursor**);
+ static int Close(sqlite3_vtab_cursor*);
- size_t row_ = 0;
- size_t num_rows_ = 0;
- const TraceStorage* storage_ = nullptr;
- SqlStatsTable* table_ = nullptr;
- };
-
- SqlStatsTable(sqlite3*, const TraceStorage* storage);
- ~SqlStatsTable() final;
-
- // Table implementation.
- base::Status Init(int, const char* const*, Schema*) final;
- std::unique_ptr<SqliteTable::BaseCursor> CreateCursor() final;
- int BestIndex(const QueryConstraints&, BestIndexInfo*) final;
-
- private:
- const TraceStorage* const storage_;
+ static int Filter(sqlite3_vtab_cursor*,
+ int,
+ const char*,
+ int,
+ sqlite3_value**);
+ static int Next(sqlite3_vtab_cursor*);
+ static int Eof(sqlite3_vtab_cursor*);
+ static int Column(sqlite3_vtab_cursor*, sqlite3_context*, int);
+ static int Rowid(sqlite3_vtab_cursor*, sqlite_int64*);
};
-} // namespace trace_processor
-} // namespace perfetto
+} // namespace perfetto::trace_processor
#endif // SRC_TRACE_PROCESSOR_SQLITE_SQL_STATS_TABLE_H_
diff --git a/src/trace_processor/sqlite/sqlite_engine.cc b/src/trace_processor/sqlite/sqlite_engine.cc
index ec9c01d..00defc2 100644
--- a/src/trace_processor/sqlite/sqlite_engine.cc
+++ b/src/trace_processor/sqlite/sqlite_engine.cc
@@ -123,7 +123,7 @@
for (auto it = all_created_sqlite_tables_.rbegin();
it != all_created_sqlite_tables_.rend(); it++) {
if (auto* type = sqlite_tables_.Find(*it);
- !type || *type != SqliteTable::TableType::kExplicitCreate) {
+ !type || *type != SqliteTableLegacy::TableType::kExplicitCreate) {
continue;
}
if (auto it_and_ins = dropped_tables.insert(*it); !it_and_ins.second) {
@@ -199,6 +199,24 @@
return base::OkStatus();
}
+base::Status SqliteEngine::RegisterAggregateFunction(
+ const char* name,
+ int argc,
+ AggregateFnStep* step,
+ AggregateFnFinal* final,
+ void* ctx,
+ FnCtxDestructor* destructor,
+ bool deterministic) {
+ int flags = SQLITE_UTF8 | (deterministic ? SQLITE_DETERMINISTIC : 0);
+ int ret =
+ sqlite3_create_function_v2(db_.get(), name, static_cast<int>(argc), flags,
+ ctx, nullptr, step, final, destructor);
+ if (ret != SQLITE_OK) {
+ return base::ErrStatus("Unable to register function with name %s", name);
+ }
+ return base::OkStatus();
+}
+
base::Status SqliteEngine::RegisterWindowFunction(const char* name,
int argc,
WindowFnStep* step,
@@ -238,8 +256,9 @@
return base::OkStatus();
}
-base::Status SqliteEngine::SaveSqliteTable(const std::string& table_name,
- std::unique_ptr<SqliteTable> table) {
+base::Status SqliteEngine::SaveSqliteTable(
+ const std::string& table_name,
+ std::unique_ptr<SqliteTableLegacy> table) {
auto res = saved_tables_.Insert(table_name, {});
if (!res.second) {
return base::ErrStatus("Table with name %s already is saved",
@@ -249,14 +268,14 @@
return base::OkStatus();
}
-base::StatusOr<std::unique_ptr<SqliteTable>> SqliteEngine::RestoreSqliteTable(
- const std::string& table_name) {
+base::StatusOr<std::unique_ptr<SqliteTableLegacy>>
+SqliteEngine::RestoreSqliteTable(const std::string& table_name) {
auto* res = saved_tables_.Find(table_name);
if (!res) {
return base::ErrStatus("Table with name %s does not exist in saved state",
table_name.c_str());
}
- std::unique_ptr<SqliteTable> table = std::move(*res);
+ std::unique_ptr<SqliteTableLegacy> table = std::move(*res);
PERFETTO_CHECK(saved_tables_.Erase(table_name));
return std::move(table);
}
@@ -271,7 +290,7 @@
}
void SqliteEngine::OnSqliteTableCreated(const std::string& name,
- SqliteTable::TableType type) {
+ SqliteTableLegacy::TableType type) {
auto it_and_inserted = sqlite_tables_.Insert(name, type);
PERFETTO_CHECK(it_and_inserted.second);
all_created_sqlite_tables_.push_back(name);
diff --git a/src/trace_processor/sqlite/sqlite_engine.h b/src/trace_processor/sqlite/sqlite_engine.h
index e46b9ee..55e991f 100644
--- a/src/trace_processor/sqlite/sqlite_engine.h
+++ b/src/trace_processor/sqlite/sqlite_engine.h
@@ -26,10 +26,12 @@
#include <type_traits>
#include <vector>
+#include "perfetto/base/logging.h"
#include "perfetto/base/status.h"
#include "perfetto/ext/base/flat_hash_map.h"
#include "perfetto/ext/base/hash.h"
#include "src/trace_processor/db/table.h"
+#include "src/trace_processor/sqlite/bindings/sqlite_module.h"
#include "src/trace_processor/sqlite/query_cache.h"
#include "src/trace_processor/sqlite/scoped_db.h"
#include "src/trace_processor/sqlite/sql_source.h"
@@ -51,6 +53,10 @@
class SqliteEngine {
public:
using Fn = void(sqlite3_context* ctx, int argc, sqlite3_value** argv);
+ using AggregateFnStep = void(sqlite3_context* ctx,
+ int argc,
+ sqlite3_value** argv);
+ using AggregateFnFinal = void(sqlite3_context* ctx);
using WindowFnStep = void(sqlite3_context* ctx,
int argc,
sqlite3_value** argv);
@@ -98,6 +104,15 @@
FnCtxDestructor* ctx_destructor,
bool deterministic);
+ // Registers a C++ aggregate function to be runnable from SQL.
+ base::Status RegisterAggregateFunction(const char* name,
+ int argc,
+ AggregateFnStep* step,
+ AggregateFnFinal* final,
+ void* ctx,
+ FnCtxDestructor* ctx_destructor,
+ bool deterministic);
+
// Registers a C++ window function to be runnable from SQL.
base::Status RegisterWindowFunction(const char* name,
int argc,
@@ -113,10 +128,20 @@
base::Status UnregisterFunction(const char* name, int argc);
// Registers a SQLite virtual table module with the given name.
+ template <typename Module>
+ void RegisterVirtualTableModule(const std::string& module_name,
+ typename Module::Context* ctx);
+
+ // Registers a SQLite virtual table module with the given name.
+ template <typename Module>
+ void RegisterVirtualTableModule(const std::string& module_name,
+ std::unique_ptr<typename Module::Context>);
+
+ // Registers a SQLite virtual table module with the given name.
template <typename Vtab, typename Context>
void RegisterVirtualTableModule(const std::string& module_name,
Context ctx,
- SqliteTable::TableType table_type,
+ SqliteTableLegacy::TableType table_type,
bool updatable);
// Declares a virtual table with SQLite.
@@ -124,19 +149,20 @@
// Saves a SQLite table across a pair of xDisconnect/xConnect callbacks.
base::Status SaveSqliteTable(const std::string& table_name,
- std::unique_ptr<SqliteTable>);
+ std::unique_ptr<SqliteTableLegacy>);
// Restores a SQLite table across a pair of xDisconnect/xConnect callbacks.
- base::StatusOr<std::unique_ptr<SqliteTable>> RestoreSqliteTable(
+ base::StatusOr<std::unique_ptr<SqliteTableLegacy>> RestoreSqliteTable(
const std::string& table_name);
// Gets the context for a registered SQL function.
void* GetFunctionContext(const std::string& name, int argc);
- // Should be called when a SqliteTable instance is created.
- void OnSqliteTableCreated(const std::string& name, SqliteTable::TableType);
+ // Should be called when a SqliteTableLegacy instance is created.
+ void OnSqliteTableCreated(const std::string& name,
+ SqliteTableLegacy::TableType);
- // Should be called when a SqliteTable instance is destroyed.
+ // Should be called when a SqliteTableLegacy instance is destroyed.
void OnSqliteTableDestroyed(const std::string& name);
sqlite3* db() const { return db_.get(); }
@@ -156,9 +182,10 @@
SqliteEngine(SqliteEngine&&) noexcept = delete;
SqliteEngine& operator=(SqliteEngine&&) = delete;
- base::FlatHashMap<std::string, SqliteTable::TableType> sqlite_tables_;
+ base::FlatHashMap<std::string, SqliteTableLegacy::TableType> sqlite_tables_;
std::vector<std::string> all_created_sqlite_tables_;
- base::FlatHashMap<std::string, std::unique_ptr<SqliteTable>> saved_tables_;
+ base::FlatHashMap<std::string, std::unique_ptr<SqliteTableLegacy>>
+ saved_tables_;
base::FlatHashMap<std::pair<std::string, int>, void*, FnHasher> fn_ctx_;
ScopedDb db_;
@@ -171,15 +198,37 @@
// in the header file because it is templated code. We separate it out
// like this to keep the API people actually care about easy to read.
-namespace perfetto {
-namespace trace_processor {
+namespace perfetto::trace_processor {
+
+template <typename Module>
+void SqliteEngine::RegisterVirtualTableModule(const std::string& module_name,
+ typename Module::Context* ctx) {
+ static_assert(std::is_base_of_v<sqlite::Module<Module>, Module>,
+ "Must subclass sqlite::Module");
+ int res = sqlite3_create_module_v2(db_.get(), module_name.c_str(),
+ &Module::kModule, ctx, nullptr);
+ PERFETTO_CHECK(res == SQLITE_OK);
+}
+
+template <typename Module>
+void SqliteEngine::RegisterVirtualTableModule(
+ const std::string& module_name,
+ std::unique_ptr<typename Module::Context> ctx) {
+ static_assert(std::is_base_of_v<sqlite::Module<Module>, Module>,
+ "Must subclass sqlite::Module");
+ int res = sqlite3_create_module_v2(
+ db_.get(), module_name.c_str(), &Module::kModule, ctx.release(),
+ [](void* arg) { delete static_cast<typename Module::Context*>(arg); });
+ PERFETTO_CHECK(res == SQLITE_OK);
+}
template <typename Vtab, typename Context>
-void SqliteEngine::RegisterVirtualTableModule(const std::string& module_name,
- Context ctx,
- SqliteTable::TableType table_type,
- bool updatable) {
- static_assert(std::is_base_of_v<SqliteTable, Vtab>,
+void SqliteEngine::RegisterVirtualTableModule(
+ const std::string& module_name,
+ Context ctx,
+ SqliteTableLegacy::TableType table_type,
+ bool updatable) {
+ static_assert(std::is_base_of_v<SqliteTableLegacy, Vtab>,
"Must subclass TypedSqliteTable");
auto module_arg =
@@ -191,7 +240,6 @@
PERFETTO_CHECK(res == SQLITE_OK);
}
-} // namespace trace_processor
-} // namespace perfetto
+} // namespace perfetto::trace_processor
#endif // SRC_TRACE_PROCESSOR_SQLITE_SQLITE_ENGINE_H_
diff --git a/src/trace_processor/sqlite/sqlite_table.cc b/src/trace_processor/sqlite/sqlite_table.cc
index c599b2f..7411657 100644
--- a/src/trace_processor/sqlite/sqlite_table.cc
+++ b/src/trace_processor/sqlite/sqlite_table.cc
@@ -82,7 +82,7 @@
return "limit";
case SQLITE_INDEX_CONSTRAINT_OFFSET:
return "offset";
- case SqliteTable::CustomFilterOpcode::kSourceGeqOpCode:
+ case SqliteTableLegacy::CustomFilterOpcode::kSourceGeqOpCode:
return "source_geq";
default:
PERFETTO_FATAL("Operator to string conversion not impemented for %d", op);
@@ -90,7 +90,7 @@
}
void ConstraintsToString(const QueryConstraints& qc,
- const SqliteTable::Schema& schema,
+ const SqliteTableLegacy::Schema& schema,
std::string& out) {
bool is_first = true;
for (const auto& cs : qc.constraints()) {
@@ -105,7 +105,7 @@
}
void OrderByToString(const QueryConstraints& qc,
- const SqliteTable::Schema& schema,
+ const SqliteTableLegacy::Schema& schema,
std::string& out) {
bool is_first = true;
for (const auto& ob : qc.order_by()) {
@@ -120,7 +120,7 @@
}
std::string QcDebugStr(const QueryConstraints& qc,
- const SqliteTable::Schema& schema) {
+ const SqliteTableLegacy::Schema& schema) {
std::string str_result;
str_result.reserve(512);
@@ -144,7 +144,7 @@
void WriteQueryConstraintsToMetatrace(metatrace::Record* r,
const QueryConstraints& qc,
- const SqliteTable::Schema& schema) {
+ const SqliteTableLegacy::Schema& schema) {
r->AddArg("constraint_count", std::to_string(qc.constraints().size()));
std::string constraints;
ConstraintsToString(qc, schema, constraints);
@@ -159,24 +159,26 @@
} // namespace
// static
-bool SqliteTable::debug = false;
+bool SqliteTableLegacy::debug = false;
-SqliteTable::SqliteTable() = default;
-SqliteTable::~SqliteTable() = default;
+SqliteTableLegacy::SqliteTableLegacy() = default;
+SqliteTableLegacy::~SqliteTableLegacy() = default;
-base::Status SqliteTable::ModifyConstraints(QueryConstraints*) {
+base::Status SqliteTableLegacy::ModifyConstraints(QueryConstraints*) {
return base::OkStatus();
}
-int SqliteTable::FindFunction(const char*, FindFunctionFn*, void**) {
+int SqliteTableLegacy::FindFunction(const char*, FindFunctionFn*, void**) {
return 0;
}
-base::Status SqliteTable::Update(int, sqlite3_value**, sqlite3_int64*) {
+base::Status SqliteTableLegacy::Update(int, sqlite3_value**, sqlite3_int64*) {
return base::ErrStatus("Updating not supported");
}
-bool SqliteTable::ReadConstraints(int idxNum, const char* idxStr, int argc) {
+bool SqliteTableLegacy::ReadConstraints(int idxNum,
+ const char* idxStr,
+ int argc) {
bool cache_hit = true;
if (idxNum != qc_hash_) {
qc_cache_ = QueryConstraints::FromString(idxStr);
@@ -196,7 +198,7 @@
// Logging this every ReadConstraints just leads to log spam on joins making
// it unusable. Instead, only print this out when we miss the cache (which
// happens precisely when the constraint set from SQLite changes.)
- if (SqliteTable::debug && !cache_hit) {
+ if (SqliteTableLegacy::debug && !cache_hit) {
PERFETTO_LOG("[%s::ParseConstraints] constraints=%s argc=%d", name_.c_str(),
QcDebugStr(qc_cache_, schema_).c_str(), argc);
}
@@ -204,34 +206,35 @@
}
////////////////////////////////////////////////////////////////////////////////
-// SqliteTable::BaseCursor implementation
+// SqliteTableLegacy::BaseCursor implementation
////////////////////////////////////////////////////////////////////////////////
-SqliteTable::BaseCursor::BaseCursor(SqliteTable* table) : table_(table) {
+SqliteTableLegacy::BaseCursor::BaseCursor(SqliteTableLegacy* table)
+ : table_(table) {
// This is required to prevent us from leaving this field uninitialised if
// we ever move construct the Cursor.
pVtab = table;
}
-SqliteTable::BaseCursor::~BaseCursor() = default;
+SqliteTableLegacy::BaseCursor::~BaseCursor() = default;
////////////////////////////////////////////////////////////////////////////////
-// SqliteTable::Column implementation
+// SqliteTableLegacy::Column implementation
////////////////////////////////////////////////////////////////////////////////
-SqliteTable::Column::Column(size_t index,
- std::string name,
- SqlValue::Type type,
- bool hidden)
+SqliteTableLegacy::Column::Column(size_t index,
+ std::string name,
+ SqlValue::Type type,
+ bool hidden)
: index_(index), name_(name), type_(type), hidden_(hidden) {}
////////////////////////////////////////////////////////////////////////////////
-// SqliteTable::Schema implementation
+// SqliteTableLegacy::Schema implementation
////////////////////////////////////////////////////////////////////////////////
-SqliteTable::Schema::Schema() = default;
+SqliteTableLegacy::Schema::Schema() = default;
-SqliteTable::Schema::Schema(std::vector<Column> columns,
- std::vector<size_t> primary_keys)
+SqliteTableLegacy::Schema::Schema(std::vector<Column> columns,
+ std::vector<size_t> primary_keys)
: columns_(std::move(columns)), primary_keys_(std::move(primary_keys)) {
for (size_t i = 0; i < columns_.size(); i++) {
PERFETTO_CHECK(columns_[i].index() == i);
@@ -241,10 +244,11 @@
}
}
-SqliteTable::Schema::Schema(const Schema&) = default;
-SqliteTable::Schema& SqliteTable::Schema::operator=(const Schema&) = default;
+SqliteTableLegacy::Schema::Schema(const Schema&) = default;
+SqliteTableLegacy::Schema& SqliteTableLegacy::Schema::operator=(const Schema&) =
+ default;
-std::string SqliteTable::Schema::ToCreateTableStmt() const {
+std::string SqliteTableLegacy::Schema::ToCreateTableStmt() const {
std::string stmt = "CREATE TABLE x(";
for (size_t i = 0; i < columns_.size(); ++i) {
const Column& col = columns_[i];
@@ -279,7 +283,7 @@
TypedSqliteTableBase::~TypedSqliteTableBase() = default;
base::Status TypedSqliteTableBase::DeclareAndAssignVtab(
- std::unique_ptr<SqliteTable> table,
+ std::unique_ptr<SqliteTableLegacy> table,
sqlite3_vtab** tab) {
auto create_stmt = table->schema().ToCreateTableStmt();
PERFETTO_DLOG("Create table statement: %s", create_stmt.c_str());
@@ -289,7 +293,7 @@
}
int TypedSqliteTableBase::xDestroy(sqlite3_vtab* t) {
- auto* table = static_cast<SqliteTable*>(t);
+ auto* table = static_cast<SqliteTableLegacy*>(t);
table->engine_->OnSqliteTableDestroyed(table->name_);
delete table;
return SQLITE_OK;
@@ -309,7 +313,7 @@
// SQLite guarantees that argv[2] contains the name of the table.
std::string table_name = argv[2];
- base::StatusOr<std::unique_ptr<SqliteTable>> table =
+ base::StatusOr<std::unique_ptr<SqliteTableLegacy>> table =
xArg->engine->RestoreSqliteTable(table_name);
if (!table.status().ok()) {
*pzErr = sqlite3_mprintf("%s", table.status().c_message());
@@ -326,7 +330,7 @@
int TypedSqliteTableBase::xDisconnectSaveTable(sqlite3_vtab* t) {
auto* table = static_cast<TypedSqliteTableBase*>(t);
base::Status status = table->engine_->SaveSqliteTable(
- table->name(), std::unique_ptr<SqliteTable>(table));
+ table->name(), std::unique_ptr<SqliteTableLegacy>(table));
return table->SetStatusAndReturn(status);
}
@@ -423,7 +427,7 @@
});
auto out_qc_str = qc.ToNewSqlite3String();
- if (SqliteTable::debug) {
+ if (SqliteTableLegacy::debug) {
PERFETTO_LOG(
"[%s::BestIndex] constraints=%s orderByConsumed=%d estimatedCost=%f "
"estimatedRows=%" PRId64,
diff --git a/src/trace_processor/sqlite/sqlite_table.h b/src/trace_processor/sqlite/sqlite_table.h
index b2a7e40..d3b1c32 100644
--- a/src/trace_processor/sqlite/sqlite_table.h
+++ b/src/trace_processor/sqlite/sqlite_table.h
@@ -35,9 +35,9 @@
// Abstract base class representing a SQLite virtual table. Implements the
// common bookeeping required across all tables and allows subclasses to
// implement a friendlier API than that required by SQLite.
-class SqliteTable : public sqlite3_vtab {
+class SqliteTableLegacy : public sqlite3_vtab {
public:
- // Custom opcodes used by subclasses of SqliteTable.
+ // Custom opcodes used by subclasses of SqliteTableLegacy.
// Stored here as we need a central repository of opcodes to prevent clashes
// between different sub-classes.
enum CustomFilterOpcode {
@@ -82,7 +82,7 @@
kSame = 1,
};
- explicit BaseCursor(SqliteTable* table);
+ explicit BaseCursor(SqliteTableLegacy* table);
virtual ~BaseCursor();
// Methods to be implemented by derived table classes.
@@ -105,7 +105,7 @@
// Used to extract the value from the column at index |N|.
void Column(sqlite3_context* context, int N);
- SqliteTable* table() const { return table_; }
+ SqliteTableLegacy* table() const { return table_; }
protected:
BaseCursor(BaseCursor&) = delete;
@@ -115,7 +115,7 @@
BaseCursor& operator=(BaseCursor&&) = default;
private:
- SqliteTable* table_ = nullptr;
+ SqliteTableLegacy* table_ = nullptr;
};
// The schema of the table. Created by subclasses to allow the table class to
@@ -157,7 +157,7 @@
};
// Public for unique_ptr destructor calls.
- virtual ~SqliteTable();
+ virtual ~SqliteTableLegacy();
// When set it logs all BestIndex and Filter actions on the console.
static bool debug;
@@ -188,7 +188,7 @@
int64_t estimated_rows = 0;
};
- SqliteTable();
+ SqliteTableLegacy();
// Methods to be implemented by derived table classes.
virtual base::Status Init(int argc, const char* const* argv, Schema*) = 0;
@@ -214,8 +214,8 @@
friend class TypedSqliteTable;
friend class TypedSqliteTableBase;
- SqliteTable(const SqliteTable&) = delete;
- SqliteTable& operator=(const SqliteTable&) = delete;
+ SqliteTableLegacy(const SqliteTableLegacy&) = delete;
+ SqliteTableLegacy& operator=(const SqliteTableLegacy&) = delete;
// The engine class this table is registered with. Used for restoring/saving
// the table.
@@ -238,7 +238,7 @@
int best_index_num_ = 0;
};
-class TypedSqliteTableBase : public SqliteTable {
+class TypedSqliteTableBase : public SqliteTableLegacy {
protected:
struct BaseModuleArg {
sqlite3_module module;
@@ -262,8 +262,9 @@
static int xOpen(sqlite3_vtab*, sqlite3_vtab_cursor**);
static int xBestIndex(sqlite3_vtab*, sqlite3_index_info*);
- static base::Status DeclareAndAssignVtab(std::unique_ptr<SqliteTable> table,
- sqlite3_vtab** tab);
+ static base::Status DeclareAndAssignVtab(
+ std::unique_ptr<SqliteTableLegacy> table,
+ sqlite3_vtab** tab);
base::Status InitInternal(SqliteEngine* engine,
int argc,
diff --git a/src/trace_processor/sqlite/sqlite_utils.cc b/src/trace_processor/sqlite/sqlite_utils.cc
index 04ddf9c..4e6277d 100644
--- a/src/trace_processor/sqlite/sqlite_utils.cc
+++ b/src/trace_processor/sqlite/sqlite_utils.cc
@@ -92,9 +92,10 @@
return {reinterpret_cast<const wchar_t*>(sqlite3_value_text16(value)), count};
}
-base::Status GetColumnsForTable(sqlite3* db,
- const std::string& raw_table_name,
- std::vector<SqliteTable::Column>& columns) {
+base::Status GetColumnsForTable(
+ sqlite3* db,
+ const std::string& raw_table_name,
+ std::vector<SqliteTableLegacy::Column>& columns) {
PERFETTO_DCHECK(columns.empty());
char sql[1024];
const char kRawSql[] = "SELECT name, type from pragma_table_info(\"%s\")";
diff --git a/src/trace_processor/sqlite/sqlite_utils.h b/src/trace_processor/sqlite/sqlite_utils.h
index 7cdb1d1..f3196a8 100644
--- a/src/trace_processor/sqlite/sqlite_utils.h
+++ b/src/trace_processor/sqlite/sqlite_utils.h
@@ -30,7 +30,7 @@
#include "perfetto/base/status.h"
#include "perfetto/ext/base/status_or.h"
#include "perfetto/trace_processor/basic_types.h"
-#include "src/trace_processor/sqlite/sqlite_result.h"
+#include "src/trace_processor/sqlite/bindings/sqlite_result.h"
#include "src/trace_processor/sqlite/sqlite_table.h"
namespace perfetto::trace_processor::sqlite::utils {
@@ -137,6 +137,12 @@
}
}
+inline int SetError(sqlite3_vtab* tab, const char* status) {
+ sqlite3_free(tab->zErrMsg);
+ tab->zErrMsg = sqlite3_mprintf("%s", status);
+ return SQLITE_ERROR;
+}
+
inline void SetError(sqlite3_context* ctx, const base::Status& status) {
PERFETTO_CHECK(!status.ok());
sqlite::result::Error(ctx, status.c_message());
@@ -167,9 +173,10 @@
std::optional<const char*>&);
// Returns the column names for the table named by |raw_table_name|.
-base::Status GetColumnsForTable(sqlite3* db,
- const std::string& raw_table_name,
- std::vector<SqliteTable::Column>& columns);
+base::Status GetColumnsForTable(
+ sqlite3* db,
+ const std::string& raw_table_name,
+ std::vector<SqliteTableLegacy::Column>& columns);
// Reads a `SQLITE_TEXT` value and returns it as a wstring (UTF-16) in the
// default byte order. `value` must be a `SQLITE_TEXT`.
diff --git a/src/trace_processor/sqlite/sqlite_utils_unittest.cc b/src/trace_processor/sqlite/sqlite_utils_unittest.cc
index 4347317..4e879d3 100644
--- a/src/trace_processor/sqlite/sqlite_utils_unittest.cc
+++ b/src/trace_processor/sqlite/sqlite_utils_unittest.cc
@@ -52,7 +52,7 @@
TEST_F(GetColumnsForTableTest, ValidInput) {
RunStatement("CREATE TABLE foo (name STRING, ts INT, dur INT);");
- std::vector<SqliteTable::Column> columns;
+ std::vector<SqliteTableLegacy::Column> columns;
auto status = sqlite::utils::GetColumnsForTable(*db_, "foo", columns);
ASSERT_TRUE(status.ok());
}
@@ -62,13 +62,13 @@
// doesn't recognise. This just ensures that the query fails rather than
// crashing.
RunStatement("CREATE TABLE foo (name NUM, ts INT, dur INT);");
- std::vector<SqliteTable::Column> columns;
+ std::vector<SqliteTableLegacy::Column> columns;
auto status = sqlite::utils::GetColumnsForTable(*db_, "foo", columns);
ASSERT_FALSE(status.ok());
}
TEST_F(GetColumnsForTableTest, UnknownTableName) {
- std::vector<SqliteTable::Column> columns;
+ std::vector<SqliteTableLegacy::Column> columns;
auto status =
sqlite::utils::GetColumnsForTable(*db_, "unknowntable", columns);
ASSERT_FALSE(status.ok());
diff --git a/src/trace_processor/sqlite/stats_table.cc b/src/trace_processor/sqlite/stats_table.cc
index d93057d..4c3aa6d 100644
--- a/src/trace_processor/sqlite/stats_table.cc
+++ b/src/trace_processor/sqlite/stats_table.cc
@@ -16,78 +16,117 @@
#include "src/trace_processor/sqlite/stats_table.h"
+#include <sqlite3.h>
#include <memory>
-#include "perfetto/base/status.h"
-#include "perfetto/trace_processor/basic_types.h"
-#include "src/trace_processor/sqlite/query_constraints.h"
-#include "src/trace_processor/sqlite/sqlite_result.h"
-#include "src/trace_processor/sqlite/sqlite_table.h"
-#include "src/trace_processor/sqlite/sqlite_utils.h"
+#include "perfetto/base/logging.h"
+#include "src/trace_processor/sqlite/bindings/sqlite_result.h"
#include "src/trace_processor/storage/stats.h"
#include "src/trace_processor/storage/trace_storage.h"
namespace perfetto::trace_processor {
-StatsTable::StatsTable(sqlite3*, const TraceStorage* storage)
- : storage_(storage) {}
-
-StatsTable::~StatsTable() = default;
-
-base::Status StatsTable::Init(int, const char* const*, Schema* schema) {
- *schema = Schema(
- {
- SqliteTable::Column(Column::kName, "name", SqlValue::Type::kString),
- // Calling a column "index" causes sqlite to silently fail, hence idx.
- SqliteTable::Column(Column::kIndex, "idx", SqlValue::Type::kLong),
- SqliteTable::Column(Column::kSeverity, "severity",
- SqlValue::Type::kString),
- SqliteTable::Column(Column::kSource, "source",
- SqlValue::Type::kString),
- SqliteTable::Column(Column::kValue, "value", SqlValue::Type::kLong),
- SqliteTable::Column(Column::kDescription, "description",
- SqlValue::Type::kString),
- },
- {Column::kName});
- return base::OkStatus();
-}
-
-std::unique_ptr<SqliteTable::BaseCursor> StatsTable::CreateCursor() {
- return std::unique_ptr<SqliteTable::BaseCursor>(new Cursor(this));
-}
-
-int StatsTable::BestIndex(const QueryConstraints&, BestIndexInfo*) {
+int StatsModule::Connect(sqlite3* db,
+ void* aux,
+ int,
+ const char* const*,
+ sqlite3_vtab** vtab,
+ char**) {
+ static constexpr char kSchema[] = R"(
+ CREATE TABLE x(
+ name TEXT,
+ idx BIGINT,
+ severity TEXT,
+ source TEXT,
+ value BIGINT,
+ description TEXT,
+ PRIMARY KEY(name)
+ ) WITHOUT ROWID
+ )";
+ if (int ret = sqlite3_declare_vtab(db, kSchema); ret != SQLITE_OK) {
+ return ret;
+ }
+ std::unique_ptr<Vtab> res = std::make_unique<Vtab>();
+ res->storage = GetContext(aux);
+ *vtab = res.release();
return SQLITE_OK;
}
-StatsTable::Cursor::Cursor(StatsTable* table)
- : SqliteTable::BaseCursor(table),
- table_(table),
- storage_(table->storage_) {}
-
-StatsTable::Cursor::~Cursor() = default;
-
-base::Status StatsTable::Cursor::Filter(const QueryConstraints&,
- sqlite3_value**,
- FilterHistory) {
- *this = Cursor(table_);
- return base::OkStatus();
+int StatsModule::Disconnect(sqlite3_vtab* vtab) {
+ delete GetVtab(vtab);
+ return SQLITE_OK;
}
-base::Status StatsTable::Cursor::Column(sqlite3_context* ctx, int N) {
+int StatsModule::BestIndex(sqlite3_vtab*, sqlite3_index_info*) {
+ return SQLITE_OK;
+}
+
+int StatsModule::Open(sqlite3_vtab* raw_vtab, sqlite3_vtab_cursor** cursor) {
+ std::unique_ptr<Cursor> c = std::make_unique<Cursor>();
+ c->storage = GetVtab(raw_vtab)->storage;
+ *cursor = c.release();
+ return SQLITE_OK;
+}
+
+int StatsModule::Close(sqlite3_vtab_cursor* cursor) {
+ delete GetCursor(cursor);
+ return SQLITE_OK;
+}
+
+int StatsModule::Filter(sqlite3_vtab_cursor* cursor,
+ int,
+ const char*,
+ int,
+ sqlite3_value**) {
+ auto* c = GetCursor(cursor);
+ c->key = {};
+ c->it = {};
+ return SQLITE_OK;
+}
+
+int StatsModule::Next(sqlite3_vtab_cursor* cursor) {
+ static_assert(stats::kTypes[0] == stats::kSingle,
+ "the first stats entry cannot be indexed");
+
+ auto* c = GetCursor(cursor);
+ const auto* cur_entry = &c->storage->stats()[c->key];
+ if (stats::kTypes[c->key] == stats::kIndexed) {
+ if (++c->it != cur_entry->indexed_values.end()) {
+ return SQLITE_OK;
+ }
+ }
+ while (++c->key < stats::kNumKeys) {
+ cur_entry = &c->storage->stats()[c->key];
+ c->it = cur_entry->indexed_values.begin();
+ if (stats::kTypes[c->key] == stats::kSingle ||
+ !cur_entry->indexed_values.empty()) {
+ break;
+ }
+ }
+ return SQLITE_OK;
+}
+
+int StatsModule::Eof(sqlite3_vtab_cursor* cursor) {
+ return GetCursor(cursor)->key >= stats::kNumKeys;
+}
+
+int StatsModule::Column(sqlite3_vtab_cursor* cursor,
+ sqlite3_context* ctx,
+ int N) {
+ auto* c = GetCursor(cursor);
switch (N) {
case Column::kName:
- sqlite::result::StaticString(ctx, stats::kNames[key_]);
+ sqlite::result::StaticString(ctx, stats::kNames[c->key]);
break;
case Column::kIndex:
- if (stats::kTypes[key_] == stats::kIndexed) {
- sqlite::result::Long(ctx, index_->first);
+ if (stats::kTypes[c->key] == stats::kIndexed) {
+ sqlite::result::Long(ctx, c->it->first);
} else {
sqlite::result::Null(ctx);
}
break;
case Column::kSeverity:
- switch (stats::kSeverities[key_]) {
+ switch (stats::kSeverities[c->key]) {
case stats::kInfo:
sqlite::result::StaticString(ctx, "info");
break;
@@ -100,7 +139,7 @@
}
break;
case Column::kSource:
- switch (stats::kSources[key_]) {
+ switch (stats::kSources[c->key]) {
case stats::kTrace:
sqlite::result::StaticString(ctx, "trace");
break;
@@ -110,44 +149,24 @@
}
break;
case Column::kValue:
- if (stats::kTypes[key_] == stats::kIndexed) {
- sqlite::result::Long(ctx, index_->second);
+ if (stats::kTypes[c->key] == stats::kIndexed) {
+ sqlite::result::Long(ctx, c->it->second);
} else {
- sqlite::result::Long(ctx, storage_->stats()[key_].value);
+ sqlite::result::Long(ctx, c->storage->stats()[c->key].value);
}
break;
case Column::kDescription:
- sqlite::result::StaticString(ctx, stats::kDescriptions[key_]);
+ sqlite::result::StaticString(ctx, stats::kDescriptions[c->key]);
break;
default:
PERFETTO_FATAL("Unknown column %d", N);
break;
}
- return base::OkStatus();
+ return SQLITE_OK;
}
-base::Status StatsTable::Cursor::Next() {
- static_assert(stats::kTypes[0] == stats::kSingle,
- "the first stats entry cannot be indexed");
- const auto* cur_entry = &storage_->stats()[key_];
- if (stats::kTypes[key_] == stats::kIndexed) {
- if (++index_ != cur_entry->indexed_values.end()) {
- return base::OkStatus();
- }
- }
- while (++key_ < stats::kNumKeys) {
- cur_entry = &storage_->stats()[key_];
- index_ = cur_entry->indexed_values.begin();
- if (stats::kTypes[key_] == stats::kSingle ||
- !cur_entry->indexed_values.empty()) {
- break;
- }
- }
- return base::OkStatus();
-}
-
-bool StatsTable::Cursor::Eof() {
- return key_ >= stats::kNumKeys;
+int StatsModule::Rowid(sqlite3_vtab_cursor*, sqlite_int64*) {
+ return SQLITE_ERROR;
}
} // namespace perfetto::trace_processor
diff --git a/src/trace_processor/sqlite/stats_table.h b/src/trace_processor/sqlite/stats_table.h
index 2824cb6..bff7ae8 100644
--- a/src/trace_processor/sqlite/stats_table.h
+++ b/src/trace_processor/sqlite/stats_table.h
@@ -17,61 +17,56 @@
#ifndef SRC_TRACE_PROCESSOR_SQLITE_STATS_TABLE_H_
#define SRC_TRACE_PROCESSOR_SQLITE_STATS_TABLE_H_
-#include <limits>
-#include <memory>
+#include <cstddef>
-#include "src/trace_processor/sqlite/sqlite_table.h"
-#include "src/trace_processor/storage/stats.h"
+#include "src/trace_processor/sqlite/bindings/sqlite_module.h"
#include "src/trace_processor/storage/trace_storage.h"
-namespace perfetto {
-namespace trace_processor {
+namespace perfetto::trace_processor {
// The stats table contains diagnostic info and errors that are either:
// - Collected at trace time (e.g., ftrace buffer overruns).
// - Generated at parsing time (e.g., clock events out-of-order).
-class StatsTable final
- : public TypedSqliteTable<StatsTable, const TraceStorage*> {
- public:
- enum Column { kName = 0, kIndex, kSeverity, kSource, kValue, kDescription };
- class Cursor final : public SqliteTable::BaseCursor {
- public:
- explicit Cursor(StatsTable*);
- ~Cursor() final;
-
- // Implementation of SqliteTable::Cursor.
- base::Status Filter(const QueryConstraints&,
- sqlite3_value**,
- FilterHistory);
- base::Status Next();
- bool Eof();
- base::Status Column(sqlite3_context*, int N);
-
- private:
- Cursor(Cursor&) = delete;
- Cursor& operator=(const Cursor&) = delete;
-
- Cursor(Cursor&&) noexcept = default;
- Cursor& operator=(Cursor&&) = default;
-
- StatsTable* table_ = nullptr;
- const TraceStorage* storage_ = nullptr;
- size_t key_ = 0;
- TraceStorage::Stats::IndexMap::const_iterator index_{};
+struct StatsModule : sqlite::Module<StatsModule> {
+ using Context = TraceStorage;
+ struct Vtab : sqlite::Module<StatsModule>::Vtab {
+ TraceStorage* storage = nullptr;
};
+ struct Cursor : sqlite::Module<StatsModule>::Cursor {
+ const TraceStorage* storage = nullptr;
+ size_t key = 0;
+ TraceStorage::Stats::IndexMap::const_iterator it{};
+ };
+ enum Column { kName = 0, kIndex, kSeverity, kSource, kValue, kDescription };
- StatsTable(sqlite3*, const TraceStorage*);
- ~StatsTable() final;
+ static constexpr auto kType = kEponymousOnly;
+ static constexpr bool kSupportsWrites = false;
+ static constexpr bool kDoesOverloadFunctions = false;
- // Table implementation.
- util::Status Init(int, const char* const*, SqliteTable::Schema*) final;
- std::unique_ptr<SqliteTable::BaseCursor> CreateCursor() final;
- int BestIndex(const QueryConstraints&, BestIndexInfo*) final;
+ static int Connect(sqlite3*,
+ void*,
+ int,
+ const char* const*,
+ sqlite3_vtab**,
+ char**);
+ static int Disconnect(sqlite3_vtab*);
- private:
- const TraceStorage* const storage_;
+ static int BestIndex(sqlite3_vtab*, sqlite3_index_info*);
+
+ static int Open(sqlite3_vtab*, sqlite3_vtab_cursor**);
+ static int Close(sqlite3_vtab_cursor*);
+
+ static int Filter(sqlite3_vtab_cursor*,
+ int,
+ const char*,
+ int,
+ sqlite3_value**);
+ static int Next(sqlite3_vtab_cursor*);
+ static int Eof(sqlite3_vtab_cursor*);
+ static int Column(sqlite3_vtab_cursor*, sqlite3_context*, int);
+ static int Rowid(sqlite3_vtab_cursor*, sqlite_int64*);
};
-} // namespace trace_processor
-} // namespace perfetto
+
+} // namespace perfetto::trace_processor
#endif // SRC_TRACE_PROCESSOR_SQLITE_STATS_TABLE_H_
diff --git a/src/trace_processor/trace_processor.cc b/src/trace_processor/trace_processor.cc
index cd6ebda..4d022f7 100644
--- a/src/trace_processor/trace_processor.cc
+++ b/src/trace_processor/trace_processor.cc
@@ -36,7 +36,7 @@
void EnableSQLiteVtableDebugging() {
// This level of indirection is required to avoid clients to depend on table.h
// which in turn requires sqlite headers.
- SqliteTable::debug = true;
+ SqliteTableLegacy::debug = true;
}
} // namespace trace_processor
diff --git a/src/trace_processor/trace_processor_impl.cc b/src/trace_processor/trace_processor_impl.cc
index 9ac4a5c..b8b7850 100644
--- a/src/trace_processor/trace_processor_impl.cc
+++ b/src/trace_processor/trace_processor_impl.cc
@@ -23,6 +23,7 @@
#include <cstdint>
#include <limits>
#include <memory>
+#include <optional>
#include <string>
#include <unordered_map>
#include <utility>
@@ -84,6 +85,7 @@
#include "src/trace_processor/perfetto_sql/intrinsics/table_functions/connected_flow.h"
#include "src/trace_processor/perfetto_sql/intrinsics/table_functions/descendant.h"
#include "src/trace_processor/perfetto_sql/intrinsics/table_functions/dfs.h"
+#include "src/trace_processor/perfetto_sql/intrinsics/table_functions/dfs_weight_bounded.h"
#include "src/trace_processor/perfetto_sql/intrinsics/table_functions/dominator_tree.h"
#include "src/trace_processor/perfetto_sql/intrinsics/table_functions/experimental_annotated_stack.h"
#include "src/trace_processor/perfetto_sql/intrinsics/table_functions/experimental_counter_dur.h"
@@ -95,6 +97,8 @@
#include "src/trace_processor/perfetto_sql/intrinsics/table_functions/table_info.h"
#include "src/trace_processor/perfetto_sql/prelude/tables_views.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"
#include "src/trace_processor/sqlite/scoped_db.h"
#include "src/trace_processor/sqlite/sql_source.h"
#include "src/trace_processor/sqlite/sql_stats_table.h"
@@ -171,86 +175,88 @@
}
}
-struct ValueAtMaxTsContext {
- bool initialized;
- int value_type;
+class ValueAtMaxTs : public SqliteAggregateFunction {
+ public:
+ struct Context {
+ bool initialized;
+ int value_type;
- int64_t max_ts;
- int64_t int_value_at_max_ts;
- double double_value_at_max_ts;
-};
+ int64_t max_ts;
+ int64_t int_value_at_max_ts;
+ double double_value_at_max_ts;
+ };
-void ValueAtMaxTsStep(sqlite3_context* ctx, int, sqlite3_value** argv) {
- sqlite3_value* ts = argv[0];
- sqlite3_value* value = argv[1];
+ static void Step(sqlite3_context* ctx, int, sqlite3_value** argv) {
+ sqlite3_value* ts = argv[0];
+ sqlite3_value* value = argv[1];
- // Note that sqlite3_aggregate_context zeros the memory for us so all the
- // variables of the struct should be zero.
- auto* fn_ctx = reinterpret_cast<ValueAtMaxTsContext*>(
- sqlite3_aggregate_context(ctx, sizeof(ValueAtMaxTsContext)));
+ // Note that sqlite3_aggregate_context zeros the memory for us so all the
+ // variables of the struct should be zero.
+ auto* fn_ctx = reinterpret_cast<Context*>(
+ sqlite3_aggregate_context(ctx, sizeof(Context)));
- // For performance reasons, we only do the check for the type of ts and value
- // on the first call of the function.
- if (PERFETTO_UNLIKELY(!fn_ctx->initialized)) {
+ // For performance reasons, we only do the check for the type of ts and
+ // value on the first call of the function.
+ if (PERFETTO_UNLIKELY(!fn_ctx->initialized)) {
+ if (sqlite3_value_type(ts) != SQLITE_INTEGER) {
+ return sqlite::result::Error(
+ ctx, "VALUE_AT_MAX_TS: ts passed was not an integer");
+ }
+
+ fn_ctx->value_type = sqlite3_value_type(value);
+ if (fn_ctx->value_type != SQLITE_INTEGER &&
+ fn_ctx->value_type != SQLITE_FLOAT) {
+ return sqlite::result::Error(
+ ctx, "VALUE_AT_MAX_TS: value passed was not an integer or float");
+ }
+
+ fn_ctx->max_ts = std::numeric_limits<int64_t>::min();
+ fn_ctx->initialized = true;
+ }
+
+ // On dcheck builds however, we check every passed ts and value.
+#if PERFETTO_DCHECK_IS_ON()
if (sqlite3_value_type(ts) != SQLITE_INTEGER) {
return sqlite::result::Error(
ctx, "VALUE_AT_MAX_TS: ts passed was not an integer");
}
-
- fn_ctx->value_type = sqlite3_value_type(value);
- if (fn_ctx->value_type != SQLITE_INTEGER &&
- fn_ctx->value_type != SQLITE_FLOAT) {
+ if (sqlite3_value_type(value) != fn_ctx->value_type) {
return sqlite::result::Error(
- ctx, "VALUE_AT_MAX_TS: value passed was not an integer or float");
+ ctx, "VALUE_AT_MAX_TS: value type is inconsistent");
}
-
- fn_ctx->max_ts = std::numeric_limits<int64_t>::min();
- fn_ctx->initialized = true;
- }
-
- // On dcheck builds however, we check every passed ts and value.
-#if PERFETTO_DCHECK_IS_ON()
- if (sqlite3_value_type(ts) != SQLITE_INTEGER) {
- return sqlite::result::Error(
- ctx, "VALUE_AT_MAX_TS: ts passed was not an integer");
- }
- if (sqlite3_value_type(value) != fn_ctx->value_type) {
- return sqlite::result::Error(ctx,
- "VALUE_AT_MAX_TS: value type is inconsistent");
- }
#endif
- int64_t ts_int = sqlite3_value_int64(ts);
- if (PERFETTO_LIKELY(fn_ctx->max_ts <= ts_int)) {
- fn_ctx->max_ts = ts_int;
+ int64_t ts_int = sqlite3_value_int64(ts);
+ if (PERFETTO_LIKELY(fn_ctx->max_ts <= ts_int)) {
+ fn_ctx->max_ts = ts_int;
- if (fn_ctx->value_type == SQLITE_INTEGER) {
- fn_ctx->int_value_at_max_ts = sqlite3_value_int64(value);
- } else {
- fn_ctx->double_value_at_max_ts = sqlite3_value_double(value);
+ if (fn_ctx->value_type == SQLITE_INTEGER) {
+ fn_ctx->int_value_at_max_ts = sqlite3_value_int64(value);
+ } else {
+ fn_ctx->double_value_at_max_ts = sqlite3_value_double(value);
+ }
}
}
-}
-void ValueAtMaxTsFinal(sqlite3_context* ctx) {
- ValueAtMaxTsContext* fn_ctx =
- reinterpret_cast<ValueAtMaxTsContext*>(sqlite3_aggregate_context(ctx, 0));
- if (!fn_ctx) {
- sqlite::result::Null(ctx);
- return;
+ static void Final(sqlite3_context* ctx) {
+ Context* fn_ctx =
+ reinterpret_cast<Context*>(sqlite3_aggregate_context(ctx, 0));
+ if (!fn_ctx) {
+ sqlite::result::Null(ctx);
+ return;
+ }
+ if (fn_ctx->value_type == SQLITE_INTEGER) {
+ sqlite::result::Long(ctx, fn_ctx->int_value_at_max_ts);
+ } else {
+ sqlite::result::Double(ctx, fn_ctx->double_value_at_max_ts);
+ }
}
- if (fn_ctx->value_type == SQLITE_INTEGER) {
- sqlite::result::Long(ctx, fn_ctx->int_value_at_max_ts);
- } else {
- sqlite::result::Double(ctx, fn_ctx->double_value_at_max_ts);
- }
-}
+};
-void RegisterValueAtMaxTsFunction(sqlite3* db) {
- auto ret = sqlite3_create_function_v2(
- db, "VALUE_AT_MAX_TS", 2, SQLITE_UTF8 | SQLITE_DETERMINISTIC, nullptr,
- nullptr, &ValueAtMaxTsStep, &ValueAtMaxTsFinal, nullptr);
- if (ret) {
+void RegisterValueAtMaxTsFunction(PerfettoSqlEngine& engine) {
+ base::Status status = engine.RegisterSqliteAggregateFunction<ValueAtMaxTs>(
+ "VALUE_AT_MAX_TS", 2, nullptr);
+ if (!status.ok()) {
PERFETTO_ELOG("Error initializing VALUE_AT_MAX_TS");
}
}
@@ -693,7 +699,7 @@
// Old style function registration.
// TODO(lalitm): migrate this over to using RegisterFunction once aggregate
// functions are supported.
- RegisterValueAtMaxTsFunction(db);
+ RegisterValueAtMaxTsFunction(*engine_);
{
base::Status status = RegisterLastNonNullFunction(*engine_);
if (!status.ok())
@@ -705,7 +711,7 @@
PERFETTO_ELOG("%s", status.c_message());
}
{
- base::Status status = PprofFunctions::Register(db, &context_);
+ base::Status status = PprofFunctions::Register(*engine_, &context_);
if (!status.ok())
PERFETTO_ELOG("%s", status.c_message());
}
@@ -725,20 +731,20 @@
PERFETTO_ELOG("%s", status.c_message());
}
- const TraceStorage* storage = context_.storage.get();
+ TraceStorage* storage = context_.storage.get();
// Operator tables.
engine_->sqlite_engine()->RegisterVirtualTableModule<SpanJoinOperatorTable>(
- "span_join", engine_.get(), SqliteTable::TableType::kExplicitCreate,
+ "span_join", engine_.get(), SqliteTableLegacy::TableType::kExplicitCreate,
false);
engine_->sqlite_engine()->RegisterVirtualTableModule<SpanJoinOperatorTable>(
- "span_left_join", engine_.get(), SqliteTable::TableType::kExplicitCreate,
- false);
+ "span_left_join", engine_.get(),
+ SqliteTableLegacy::TableType::kExplicitCreate, false);
engine_->sqlite_engine()->RegisterVirtualTableModule<SpanJoinOperatorTable>(
- "span_outer_join", engine_.get(), SqliteTable::TableType::kExplicitCreate,
- false);
- engine_->sqlite_engine()->RegisterVirtualTableModule<WindowOperatorTable>(
- "window", storage, SqliteTable::TableType::kExplicitCreate, true);
+ "span_outer_join", engine_.get(),
+ SqliteTableLegacy::TableType::kExplicitCreate, false);
+ engine_->sqlite_engine()->RegisterVirtualTableModule<WindowOperatorModule>(
+ "window", std::make_unique<WindowOperatorModule::Context>());
// Initalize the tables and views in the prelude.
InitializePreludeTablesViews(db);
@@ -753,14 +759,12 @@
}
// Register metrics functions.
- // TODO(lalitm): migrate this over to using RegisterFunction once aggregate
- // functions are supported.
{
- auto ret = sqlite3_create_function_v2(
- db, "RepeatedField", 1, SQLITE_UTF8, nullptr, nullptr,
- metrics::RepeatedFieldStep, metrics::RepeatedFieldFinal, nullptr);
- if (ret)
- PERFETTO_FATAL("Error initializing RepeatedField");
+ base::Status status =
+ engine_->RegisterSqliteAggregateFunction<metrics::RepeatedField>(
+ "RepeatedField", 1, nullptr);
+ if (!status.ok())
+ PERFETTO_ELOG("%s", status.c_message());
}
RegisterFunction<metrics::NullIfEmpty>(engine_.get(), "NULL_IF_EMPTY", 1);
@@ -772,10 +776,10 @@
metrics::RunMetric::Context{engine_.get(), &sql_metrics_}));
// Legacy tables.
- engine_->sqlite_engine()->RegisterVirtualTableModule<SqlStatsTable>(
- "sqlstats", storage, SqliteTable::TableType::kEponymousOnly, false);
- engine_->sqlite_engine()->RegisterVirtualTableModule<StatsTable>(
- "stats", storage, SqliteTable::TableType::kEponymousOnly, false);
+ engine_->sqlite_engine()->RegisterVirtualTableModule<SqlStatsModule>(
+ "sqlstats", storage);
+ engine_->sqlite_engine()->RegisterVirtualTableModule<StatsModule>("stats",
+ storage);
// New style db-backed tables.
// Note: if adding a table here which might potentially contain many rows
@@ -916,6 +920,8 @@
context_.storage->mutable_string_pool()));
engine_->RegisterStaticTableFunction(
std::make_unique<Dfs>(context_.storage->mutable_string_pool()));
+ engine_->RegisterStaticTableFunction(std::make_unique<DfsWeightBounded>(
+ context_.storage->mutable_string_pool()));
// Metrics.
RegisterAllProtoBuilderFunctions(&pool_, engine_.get(), this);
diff --git a/test/cts/heapprofd_test_cts.cc b/test/cts/heapprofd_test_cts.cc
index 6c87cf4..fec7aea 100644
--- a/test/cts/heapprofd_test_cts.cc
+++ b/test/cts/heapprofd_test_cts.cc
@@ -20,11 +20,14 @@
#include <sys/wait.h>
#include <random>
+#include <string>
+#include <string_view>
#include "perfetto/base/logging.h"
#include "perfetto/ext/base/string_utils.h"
#include "perfetto/tracing/core/data_source_config.h"
#include "src/base/test/test_task_runner.h"
+#include "src/base/test/tmp_dir_tree.h"
#include "test/android_test_utils.h"
#include "test/gtest_and_gmock.h"
#include "test/test_helper.h"
@@ -71,25 +74,47 @@
return result;
}
-std::optional<int64_t> ReadInt64FromFile(const std::string& path) {
- std::string contents;
- if (!base::ReadFile(path, &contents)) {
- return std::nullopt;
+// Asks FileContentProvider.java inside the app to read a file.
+class ContentProviderReader {
+ public:
+ explicit ContentProviderReader(const std::string& app,
+ const std::string& path) {
+ tmp_dir_.TrackFile("contents.txt");
+ tempfile_ = tmp_dir_.AbsolutePath("contents.txt");
+ cmd_ = std::string("content read --uri content://") + app +
+ std::string("/") + path + " >" + tempfile_;
}
- return base::StringToInt64(contents);
-}
+ std::optional<int64_t> ReadInt64() {
+ if (system(cmd_.c_str()) != 0) {
+ return std::nullopt;
+ }
+ return ReadInt64FromFile(tempfile_);
+ }
+
+ private:
+ std::optional<int64_t> ReadInt64FromFile(const std::string& path) {
+ std::string contents;
+ if (!base::ReadFile(path, &contents)) {
+ return std::nullopt;
+ }
+ return base::StringToInt64(contents);
+ }
+
+ base::TmpDirTree tmp_dir_;
+ std::string tempfile_;
+ std::string cmd_;
+};
bool WaitForAppAllocationCycle(const std::string& app_name, size_t timeout_ms) {
const size_t sleep_per_attempt_us = 100 * 1000;
const size_t max_attempts = timeout_ms * 1000 / sleep_per_attempt_us;
- std::string path = std::string("/sdcard/Android/data/") + app_name +
- std::string("/files/") + std::string(kReportCyclePath);
+ ContentProviderReader app_reader(app_name, std::string(kReportCyclePath));
for (size_t attempts = 0; attempts < max_attempts;) {
int64_t first_value;
for (; attempts < max_attempts; attempts++) {
- std::optional<int64_t> val = ReadInt64FromFile(path);
+ std::optional<int64_t> val = app_reader.ReadInt64();
if (val) {
first_value = *val;
break;
@@ -98,7 +123,7 @@
}
for (; attempts < max_attempts; attempts++) {
- std::optional<int64_t> val = ReadInt64FromFile(path);
+ std::optional<int64_t> val = app_reader.ReadInt64();
if (!val || *val < first_value) {
break;
}
diff --git a/test/cts/test_apps/AndroidManifest_debuggable.xml b/test/cts/test_apps/AndroidManifest_debuggable.xml
index 291469e..79993f8 100755
--- a/test/cts/test_apps/AndroidManifest_debuggable.xml
+++ b/test/cts/test_apps/AndroidManifest_debuggable.xml
@@ -71,5 +71,10 @@
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity-alias>
+ <provider
+ android:name="android.perfetto.cts.app.FileContentProvider"
+ android:authorities="android.perfetto.cts.app.debuggable"
+ android:exported="true"
+ android:grantUriPermissions="true" />
</application>
</manifest>
diff --git a/test/cts/test_apps/AndroidManifest_nonprofileable.xml b/test/cts/test_apps/AndroidManifest_nonprofileable.xml
index a332175..8322daf 100755
--- a/test/cts/test_apps/AndroidManifest_nonprofileable.xml
+++ b/test/cts/test_apps/AndroidManifest_nonprofileable.xml
@@ -59,6 +59,11 @@
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity-alias>
+ <provider
+ android:name="android.perfetto.cts.app.FileContentProvider"
+ android:authorities="android.perfetto.cts.app.nonprofileable"
+ android:exported="true"
+ android:grantUriPermissions="true" />
</application>
</manifest>
diff --git a/test/cts/test_apps/AndroidManifest_profileable.xml b/test/cts/test_apps/AndroidManifest_profileable.xml
index 077fd95..cd434d4 100755
--- a/test/cts/test_apps/AndroidManifest_profileable.xml
+++ b/test/cts/test_apps/AndroidManifest_profileable.xml
@@ -72,6 +72,11 @@
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity-alias>
+ <provider
+ android:name="android.perfetto.cts.app.FileContentProvider"
+ android:authorities="android.perfetto.cts.app.profileable"
+ android:exported="true"
+ android:grantUriPermissions="true" />
</application>
</manifest>
diff --git a/test/cts/test_apps/AndroidManifest_release.xml b/test/cts/test_apps/AndroidManifest_release.xml
index 417a539..1795a59 100755
--- a/test/cts/test_apps/AndroidManifest_release.xml
+++ b/test/cts/test_apps/AndroidManifest_release.xml
@@ -71,5 +71,10 @@
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity-alias>
+ <provider
+ android:name="android.perfetto.cts.app.FileContentProvider"
+ android:authorities="android.perfetto.cts.app.release"
+ android:exported="true"
+ android:grantUriPermissions="true" />
</application>
</manifest>
diff --git a/test/cts/test_apps/src/android/perfetto/cts/app/FileContentProvider.java b/test/cts/test_apps/src/android/perfetto/cts/app/FileContentProvider.java
new file mode 100644
index 0000000..5419cfe
--- /dev/null
+++ b/test/cts/test_apps/src/android/perfetto/cts/app/FileContentProvider.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2024 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.
+ */
+
+package android.perfetto.cts.app;
+
+import android.content.ContentProvider;
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.ParcelFileDescriptor;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+
+public class FileContentProvider extends ContentProvider {
+ @Override
+ public boolean onCreate() {
+ return true;
+ }
+
+ @Override
+ public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
+ String sortOrder) {
+ return null;
+ }
+
+ @Override
+ public String getType(Uri uri) {
+ return null;
+ }
+
+ @Override
+ public String[] getStreamTypes(Uri uri, String mimeTypeFilter) {
+ return null;
+ }
+
+ @Override
+ public Uri insert(Uri uri, ContentValues values) {
+ return null;
+ }
+
+ @Override
+ public int delete(Uri uri, String selection, String[] selectionArgs) {
+ return 0;
+ }
+
+ @Override
+ public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
+ return 0;
+ }
+
+ @Override
+ public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
+ String filePath = uri.getPath();
+ File file = new File(getContext().getExternalFilesDir(null), filePath);
+ return ParcelFileDescriptor.open(file, ParcelFileDescriptor.parseMode(mode));
+ }
+}
diff --git a/test/trace_processor/diff_tests/metrics/android/tests.py b/test/trace_processor/diff_tests/metrics/android/tests.py
index f5de58f..5038aec 100644
--- a/test/trace_processor/diff_tests/metrics/android/tests.py
+++ b/test/trace_processor/diff_tests/metrics/android/tests.py
@@ -235,4 +235,65 @@
return DiffTestBlueprint(
trace=DataPath('android_postboot_unlock.pftrace'),
query=Metric('android_garbage_collection_unagg'),
- out=Path('android_garbage_collection_unagg.out'))
\ No newline at end of file
+ out=Path('android_garbage_collection_unagg.out'))
+
+ def test_android_auto_multiuser_switch(self):
+ return DiffTestBlueprint(
+ trace=TextProto(r"""
+ packet {
+ ftrace_events {
+ cpu: 2
+ event {
+ timestamp: 1000000000
+ pid: 4032
+ print {
+ buf: "S|5993|UserController.startUser-10-fg-start-mode-1|0\n"
+ }
+ }
+ }
+ }
+ packet {
+ ftrace_events {
+ cpu: 2
+ event {
+ timestamp: 2000000000
+ pid: 4065
+ print {
+ buf: "S|2608|launching: com.android.car.carlauncher|0\n"
+ }
+ }
+ }
+ }
+ packet {
+ ftrace_events {
+ cpu: 2
+ event {
+ timestamp: 3000000000
+ pid: 4032
+ print {
+ buf: "S|5993|UserController.startUser-11-fg-start-mode-1|0\n"
+ }
+ }
+ }
+ }
+ packet {
+ ftrace_events {
+ cpu: 2
+ event {
+ timestamp: 6878000000
+ pid: 4065
+ print {
+ buf: "S|2609|launching: com.android.car.carlauncher|0\n"
+ }
+ }
+ }
+ }
+ """),
+ query=Metric('android_auto_multiuser'),
+ out=TextProto(r"""
+ android_auto_multiuser {
+ user_switch {
+ duration_ms: 3878
+ }
+ }
+ """))
\ No newline at end of file
diff --git a/test/trace_processor/diff_tests/stdlib/chrome/chrome_stdlib_testsuites.py b/test/trace_processor/diff_tests/stdlib/chrome/chrome_stdlib_testsuites.py
new file mode 100644
index 0000000..4ef5bba
--- /dev/null
+++ b/test/trace_processor/diff_tests/stdlib/chrome/chrome_stdlib_testsuites.py
@@ -0,0 +1,19 @@
+# Copyright 2024 The Chromium Authors
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+"""
+A list of Chrome Stdlib TestSuites to be used by the DiffTestRunner. We
+define this here so we can use the same list in both the Chromium and
+Perfetto repositories.
+"""
+
+from .tests import ChromeStdlib
+from .tests_scroll_jank import ChromeScrollJankStdlib
+from .tests_chrome_interactions import ChromeInteractions
+
+CHROME_STDLIB_TESTSUITES = [
+ ChromeScrollJankStdlib,
+ ChromeStdlib,
+ ChromeInteractions,
+]
\ No newline at end of file
diff --git a/test/trace_processor/diff_tests/stdlib/graphs/search_tests.py b/test/trace_processor/diff_tests/stdlib/graphs/search_tests.py
index 1e3b96b..48cc56f 100644
--- a/test/trace_processor/diff_tests/stdlib/graphs/search_tests.py
+++ b/test/trace_processor/diff_tests/stdlib/graphs/search_tests.py
@@ -153,3 +153,48 @@
3,2
2,"[NULL]"
"""))
+
+ def test_weight_bounded_dfs(self):
+ return DiffTestBlueprint(
+ trace=DataPath('counters.json'),
+ query="""
+ INCLUDE PERFETTO MODULE graphs.search;
+
+ CREATE PERFETTO TABLE foo AS
+ SELECT 0 AS source_node_id, 0 AS dest_node_id, 0 AS edge_weight
+ UNION ALL
+ VALUES (1, 2, 1)
+ UNION ALL
+ VALUES (1, 3, 1)
+ UNION ALL
+ VALUES (3, 4, 1)
+ UNION ALL
+ VALUES (3, 5, 0)
+ UNION ALL
+ VALUES (5, 6, 0);
+
+ CREATE PERFETTO TABLE roots AS
+ SELECT 0 AS root_node_id, 0 AS root_max_weight
+ UNION ALL
+ VALUES (1, 2)
+ UNION ALL
+ VALUES (3, 1)
+ UNION ALL
+ VALUES (2, 0);
+
+ SELECT * FROM graph_reachable_weight_bounded_dfs!(foo, roots);
+ """,
+ out=Csv("""
+ "root_node_id","node_id","parent_node_id"
+ 0,0,"[NULL]"
+ 1,1,"[NULL]"
+ 1,2,1
+ 1,3,1
+ 1,5,3
+ 1,6,5
+ 3,3,"[NULL]"
+ 3,4,3
+ 3,5,3
+ 3,6,5
+ 2,2,"[NULL]"
+ """))
diff --git a/tools/tmux b/tools/tmux
index 48c625e..06594b8 100755
--- a/tools/tmux
+++ b/tools/tmux
@@ -56,18 +56,16 @@
function reset_tracing() {
if is_android "$OUT"; then
- # Newer versions of Android don't have debugfs mounted at all
- # anymore so use /sys/kernel/tracing if /d/tracing doesn't exist
- adb shell 'test -d /sys/kernel/tracing && echo 0 > /sys/kernel/tracing/tracing_on || echo 0 > /sys/kernel/debug/tracing/tracing_on'
+ adb shell 'test -d /sys/kernel/tracing && echo 0 > /sys/kernel/tracing/tracing_on'
elif ! is_mac; then
# shellcheck disable=SC2016
local script='
- if [ ! -w /sys/kernel/debug ]; then
- echo "debugfs not accessible, try sudo chown -R $USER /sys/kernel/debug"
- sudo chown -R "$USER" /sys/kernel/debug
+ if [ ! -w /sys/kernel/tracing ]; then
+ echo "tracefs not accessible, try sudo chown -R $USER /sys/kernel/tracing"
+ sudo chown -R "$USER" /sys/kernel/tracing
fi
- echo 0 > /sys/kernel/debug/tracing/tracing_on
+ echo 0 > /sys/kernel/tracing/tracing_on
'
if is_ssh_target; then
diff --git a/ui/src/common/state.ts b/ui/src/common/state.ts
index 0969527..9aa747b 100644
--- a/ui/src/common/state.ts
+++ b/ui/src/common/state.ts
@@ -15,7 +15,6 @@
import {BigintMath} from '../base/bigint_math';
import {duration, Time, time} from '../base/time';
import {RecordConfig} from '../controller/record_config_types';
-import {GenericSliceDetailsTabConfigBase} from '../frontend/generic_slice_details_tab';
import {
Aggregation,
PivotTree,
@@ -25,6 +24,28 @@
import {Direction} from './event_set';
+import {
+ selectionToLegacySelection,
+ Selection,
+ LegacySelection,
+ ProfileType,
+} from '../core/selection_manager';
+
+export {
+ Selection,
+ SelectionKind,
+ NoteSelection,
+ SliceSelection,
+ CounterSelection,
+ HeapProfileSelection,
+ PerfSamplesSelection,
+ LegacySelection,
+ AreaSelection,
+ ProfileType,
+ ChromeSliceSelection,
+ CpuProfileSampleSelection,
+} from '../core/selection_manager';
+
/**
* A plain js object, holding objects of type |Class| keyed by string id.
* We use this instead of using |Map| object since it is simpler and faster to
@@ -60,18 +81,6 @@
resolution: duration;
}
-export interface AreaSelection {
- kind: 'AREA';
- areaId: string;
- // When an area is marked it will be assigned a unique note id and saved as
- // an AreaNote for the user to return to later. id = 0 is the special id that
- // is overwritten when a new area is marked. Any other id is a persistent
- // marking that will not be overwritten.
- // When not set, the area selection will be replaced with any
- // new area selection (i.e. not saved anywhere).
- noteId?: string;
-}
-
export type AreaById = Area & {id: string};
export interface Area {
@@ -173,15 +182,6 @@
};
};
-export enum ProfileType {
- HEAP_PROFILE = 'heap_profile',
- MIXED_HEAP_PROFILE = 'heap_profile:com.android.art,libc.malloc',
- NATIVE_HEAP_PROFILE = 'heap_profile:libc.malloc',
- JAVA_HEAP_SAMPLES = 'heap_profile:com.android.art',
- JAVA_HEAP_GRAPH = 'graph',
- PERF_SAMPLE = 'perf',
-}
-
export enum FlamegraphStateViewingOption {
SPACE_MEMORY_ALLOCATED_NOT_FREED_KEY = 'SPACE',
ALLOC_SPACE_MEMORY_ALLOCATED_KEY = 'ALLOC_SPACE',
@@ -190,6 +190,17 @@
PERF_SAMPLES_KEY = 'PERF_SAMPLES',
}
+export interface FlamegraphState {
+ kind: 'FLAMEGRAPH_STATE';
+ upids: number[];
+ start: time;
+ end: time;
+ type: ProfileType;
+ viewingOption: FlamegraphStateViewingOption;
+ focusRegex: string;
+ expandedCallsite?: CallsiteInfo;
+}
+
export interface CallsiteInfo {
id: number;
parentId: number;
@@ -310,134 +321,6 @@
text: string;
}
-export interface NoteSelection {
- kind: 'NOTE';
- id: string;
-}
-
-export interface SliceSelection {
- kind: 'SLICE';
- id: number;
-}
-
-export interface CounterSelection {
- kind: 'COUNTER';
- leftTs: time;
- rightTs: time;
- id: number;
-}
-
-export interface HeapProfileSelection {
- kind: 'HEAP_PROFILE';
- id: number;
- upid: number;
- ts: time;
- type: ProfileType;
-}
-
-export interface PerfSamplesSelection {
- kind: 'PERF_SAMPLES';
- id: number;
- upid: number;
- leftTs: time;
- rightTs: time;
- type: ProfileType;
-}
-
-export interface FlamegraphState {
- kind: 'FLAMEGRAPH_STATE';
- upids: number[];
- start: time;
- end: time;
- type: ProfileType;
- viewingOption: FlamegraphStateViewingOption;
- focusRegex: string;
- expandedCallsite?: CallsiteInfo;
-}
-
-export interface CpuProfileSampleSelection {
- kind: 'CPU_PROFILE_SAMPLE';
- id: number;
- utid: number;
- ts: time;
-}
-
-export interface ChromeSliceSelection {
- kind: 'CHROME_SLICE';
- id: number;
- table?: string;
-}
-
-export interface ThreadStateSelection {
- kind: 'THREAD_STATE';
- id: number;
-}
-
-export interface LogSelection {
- kind: 'LOG';
- id: number;
- trackKey: string;
-}
-
-export interface GenericSliceSelection {
- kind: 'GENERIC_SLICE';
- id: number;
- sqlTableName: string;
- start: time;
- duration: duration;
- // NOTE: this config can be expanded for multiple details panel types.
- detailsPanelConfig: {kind: string; config: GenericSliceDetailsTabConfigBase};
-}
-
-export type LegacySelection = (
- | NoteSelection
- | SliceSelection
- | CounterSelection
- | HeapProfileSelection
- | CpuProfileSampleSelection
- | ChromeSliceSelection
- | ThreadStateSelection
- | AreaSelection
- | PerfSamplesSelection
- | LogSelection
- | GenericSliceSelection
-) & {trackKey?: string};
-export type SelectionKind = LegacySelection['kind']; // 'THREAD_STATE' | 'SLICE' ...
-
-export interface LegacySelectionWrapper {
- kind: 'legacy';
- legacySelection: LegacySelection;
-}
-
-export interface SingleSelection {
- kind: 'single';
- trackKey: string;
- eventId: string;
-}
-
-export interface NewAreaSelection {
- kind: 'area';
- trackKey: string;
- start: time;
- end: time;
-}
-
-export interface UnionSelection {
- kind: 'union';
- selections: Selection[];
-}
-
-export interface EmptySelection {
- kind: 'empty';
-}
-
-export type Selection =
- | SingleSelection
- | NewAreaSelection
- | UnionSelection
- | EmptySelection
- | LegacySelectionWrapper;
-
export interface Pagination {
offset: number;
count: number;
@@ -1034,9 +917,5 @@
}
export function getLegacySelection(state: State): LegacySelection | null {
- const selection = state.selection;
- if (selection.kind === 'legacy') {
- return selection.legacySelection;
- }
- return null;
+ return selectionToLegacySelection(state.selection);
}
diff --git a/ui/src/controller/track_decider.ts b/ui/src/controller/track_decider.ts
index 206d1a9..7d1039b 100644
--- a/ui/src/controller/track_decider.ts
+++ b/ui/src/controller/track_decider.ts
@@ -450,7 +450,7 @@
if (!devMap.has(groupName)) {
devMap.set(groupName, uuidv4());
}
- track.name = 'Size: ' + size;
+ track.name = 'Chunk size: ' + size;
track.trackGroup = devMap.get(groupName);
}
diff --git a/ui/src/core/generic_slice_details_types.ts b/ui/src/core/generic_slice_details_types.ts
new file mode 100644
index 0000000..186f6e5
--- /dev/null
+++ b/ui/src/core/generic_slice_details_types.ts
@@ -0,0 +1,32 @@
+// Copyright (C) 2024 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.
+
+export interface ColumnConfig {
+ displayName?: string;
+}
+
+export type Columns = {
+ [columnName: string]: ColumnConfig;
+};
+
+export interface GenericSliceDetailsTabConfigBase {
+ sqlTableName: string;
+ title: string;
+ // All columns are rendered if |columns| is undefined.
+ columns?: Columns;
+}
+
+export type GenericSliceDetailsTabConfig = GenericSliceDetailsTabConfigBase & {
+ id: number;
+};
diff --git a/ui/src/core/selection_manager.ts b/ui/src/core/selection_manager.ts
index 0a5b9be..961ce95 100644
--- a/ui/src/core/selection_manager.ts
+++ b/ui/src/core/selection_manager.ts
@@ -11,3 +11,155 @@
// 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.
+
+import {duration, time} from '../base/time';
+import {GenericSliceDetailsTabConfigBase} from './generic_slice_details_types';
+
+export enum ProfileType {
+ HEAP_PROFILE = 'heap_profile',
+ MIXED_HEAP_PROFILE = 'heap_profile:com.android.art,libc.malloc',
+ NATIVE_HEAP_PROFILE = 'heap_profile:libc.malloc',
+ JAVA_HEAP_SAMPLES = 'heap_profile:com.android.art',
+ JAVA_HEAP_GRAPH = 'graph',
+ PERF_SAMPLE = 'perf',
+}
+
+// LEGACY Selection types:
+export interface AreaSelection {
+ kind: 'AREA';
+ areaId: string;
+ // When an area is marked it will be assigned a unique note id and saved as
+ // an AreaNote for the user to return to later. id = 0 is the special id that
+ // is overwritten when a new area is marked. Any other id is a persistent
+ // marking that will not be overwritten.
+ // When not set, the area selection will be replaced with any
+ // new area selection (i.e. not saved anywhere).
+ noteId?: string;
+}
+
+export interface NoteSelection {
+ kind: 'NOTE';
+ id: string;
+}
+
+export interface SliceSelection {
+ kind: 'SLICE';
+ id: number;
+}
+
+export interface CounterSelection {
+ kind: 'COUNTER';
+ leftTs: time;
+ rightTs: time;
+ id: number;
+}
+
+export interface HeapProfileSelection {
+ kind: 'HEAP_PROFILE';
+ id: number;
+ upid: number;
+ ts: time;
+ type: ProfileType;
+}
+
+export interface PerfSamplesSelection {
+ kind: 'PERF_SAMPLES';
+ id: number;
+ upid: number;
+ leftTs: time;
+ rightTs: time;
+ type: ProfileType;
+}
+
+export interface CpuProfileSampleSelection {
+ kind: 'CPU_PROFILE_SAMPLE';
+ id: number;
+ utid: number;
+ ts: time;
+}
+
+export interface ChromeSliceSelection {
+ kind: 'CHROME_SLICE';
+ id: number;
+ table?: string;
+}
+
+export interface ThreadStateSelection {
+ kind: 'THREAD_STATE';
+ id: number;
+}
+
+export interface LogSelection {
+ kind: 'LOG';
+ id: number;
+ trackKey: string;
+}
+
+export interface GenericSliceSelection {
+ kind: 'GENERIC_SLICE';
+ id: number;
+ sqlTableName: string;
+ start: time;
+ duration: duration;
+ // NOTE: this config can be expanded for multiple details panel types.
+ detailsPanelConfig: {kind: string; config: GenericSliceDetailsTabConfigBase};
+}
+
+export type LegacySelection = (
+ | NoteSelection
+ | SliceSelection
+ | CounterSelection
+ | HeapProfileSelection
+ | CpuProfileSampleSelection
+ | ChromeSliceSelection
+ | ThreadStateSelection
+ | AreaSelection
+ | PerfSamplesSelection
+ | LogSelection
+ | GenericSliceSelection
+) & {trackKey?: string};
+export type SelectionKind = LegacySelection['kind']; // 'THREAD_STATE' | 'SLICE' ...
+
+// New Selection types:
+export interface LegacySelectionWrapper {
+ kind: 'legacy';
+ legacySelection: LegacySelection;
+}
+
+export interface SingleSelection {
+ kind: 'single';
+ trackKey: string;
+ eventId: string;
+}
+
+export interface NewAreaSelection {
+ kind: 'area';
+ trackKey: string;
+ start: time;
+ end: time;
+}
+
+export interface UnionSelection {
+ kind: 'union';
+ selections: Selection[];
+}
+
+export interface EmptySelection {
+ kind: 'empty';
+}
+
+export type Selection =
+ | SingleSelection
+ | NewAreaSelection
+ | UnionSelection
+ | EmptySelection
+ | LegacySelectionWrapper;
+
+export function selectionToLegacySelection(
+ selection: Selection,
+): LegacySelection | null {
+ if (selection.kind === 'legacy') {
+ return selection.legacySelection;
+ }
+ return null;
+}
diff --git a/ui/src/frontend/app.ts b/ui/src/frontend/app.ts
index bad01f0..17b0ad9 100644
--- a/ui/src/frontend/app.ts
+++ b/ui/src/frontend/app.ts
@@ -589,25 +589,25 @@
id: 'perfetto.NextFlow',
name: 'Next flow',
callback: () => focusOtherFlow('Forward'),
- defaultHotkey: ']',
+ defaultHotkey: 'Mod+]',
},
{
id: 'perfetto.PrevFlow',
name: 'Prev flow',
callback: () => focusOtherFlow('Backward'),
- defaultHotkey: '[',
+ defaultHotkey: 'Mod+[',
},
{
id: 'perfetto.MoveNextFlow',
name: 'Move next flow',
callback: () => moveByFocusedFlow('Forward'),
- defaultHotkey: 'Mod+]',
+ defaultHotkey: ']',
},
{
id: 'perfetto.MovePrevFlow',
name: 'Move prev flow',
callback: () => moveByFocusedFlow('Backward'),
- defaultHotkey: 'Mod+[',
+ defaultHotkey: '[',
},
{
id: 'perfetto.SelectAll',
diff --git a/ui/src/frontend/generic_slice_details_tab.ts b/ui/src/frontend/generic_slice_details_tab.ts
index 43fcd88..fdc5bb8 100644
--- a/ui/src/frontend/generic_slice_details_tab.ts
+++ b/ui/src/frontend/generic_slice_details_tab.ts
@@ -25,24 +25,14 @@
import {BottomTab, NewBottomTabArgs} from './bottom_tab';
import {sqlValueToString} from './sql_utils';
-export interface ColumnConfig {
- displayName?: string;
-}
+import {GenericSliceDetailsTabConfig} from '../core/generic_slice_details_types';
-export type Columns = {
- [columnName: string]: ColumnConfig;
-};
-
-export interface GenericSliceDetailsTabConfigBase {
- sqlTableName: string;
- title: string;
- // All columns are rendered if |columns| is undefined.
- columns?: Columns;
-}
-
-export type GenericSliceDetailsTabConfig = GenericSliceDetailsTabConfigBase & {
- id: number;
-};
+export {
+ ColumnConfig,
+ Columns,
+ GenericSliceDetailsTabConfigBase,
+ GenericSliceDetailsTabConfig,
+} from '../core/generic_slice_details_types';
// A details tab, which fetches slice-like object from a given SQL table by id
// and renders it according to the provided config, specifying which columns