| // Copyright (C) 2025 The Android Open Source Project | |
| // | |
| // Licensed under the Apache License, Version 2.0 (the "License"); | |
| // you may not use this file except in compliance with the License. | |
| // You may obtain a copy of the License at | |
| // | |
| // http://www.apache.org/licenses/LICENSE-2.0 | |
| // | |
| // Unless required by applicable law or agreed to in writing, software | |
| // distributed under the License is distributed on an "AS IS" BASIS, | |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| // See the License for the specific language governing permissions and | |
| // limitations under the License. | |
| #include "src/trace_processor/plugins/args/args.h" | |
| #include <cstdint> | |
| #include <limits> | |
| #include <optional> | |
| #include <string_view> | |
| #include <utility> | |
| #include <memory> | |
| #include <vector> | |
| #include "perfetto/base/compiler.h" | |
| #include "perfetto/ext/base/string_utils.h" | |
| #include "src/trace_processor/containers/null_term_string_view.h" | |
| #include "src/trace_processor/core/plugin/plugin.h" | |
| #include "src/trace_processor/perfetto_sql/engine/perfetto_sql_connection.h" | |
| #include "src/trace_processor/sqlite/bindings/sqlite_result.h" | |
| #include "src/trace_processor/sqlite/bindings/sqlite_type.h" | |
| #include "src/trace_processor/sqlite/bindings/sqlite_value.h" | |
| #include "src/trace_processor/storage/trace_storage.h" | |
| #include "src/trace_processor/tables/metadata_tables_py.h" | |
| #include "src/trace_processor/types/trace_processor_context.h" | |
| #include "src/trace_processor/types/variadic.h" | |
| #include "src/trace_processor/util/args_utils.h" | |
| #include "src/trace_processor/util/simple_json_serializer.h" | |
| namespace perfetto::trace_processor { | |
| namespace { | |
| void WriteVariadic(const Variadic& v, | |
| const TraceStorage* storage, | |
| json::JsonValueSerializer&& writer) { | |
| switch (v.type) { | |
| case Variadic::Type::kNull: | |
| std::move(writer).WriteNull(); | |
| break; | |
| case Variadic::Type::kBool: | |
| std::move(writer).WriteBool(v.bool_value); | |
| break; | |
| case Variadic::Type::kInt: | |
| std::move(writer).WriteInt(v.int_value); | |
| break; | |
| case Variadic::Type::kUint: | |
| std::move(writer).WriteUint(v.uint_value); | |
| break; | |
| case Variadic::Type::kReal: | |
| std::move(writer).WriteDouble(v.real_value); | |
| break; | |
| case Variadic::Type::kString: { | |
| if (v.string_value.is_null()) { | |
| std::move(writer).WriteNull(); | |
| break; | |
| } | |
| NullTermStringView str = storage->GetString(v.string_value); | |
| std::move(writer).WriteString(str.c_str()); | |
| break; | |
| } | |
| case Variadic::Type::kPointer: { | |
| std::move(writer).WriteString(base::Uint64ToHexString(v.pointer_value)); | |
| break; | |
| } | |
| case Variadic::Type::kJson: { | |
| // For JSON values, we need to parse and reconstruct them properly | |
| // For now, just treat as string | |
| if (v.json_value.is_null()) { | |
| std::move(writer).WriteNull(); | |
| break; | |
| } | |
| NullTermStringView str = storage->GetString(v.json_value); | |
| std::move(writer).WriteString(str.c_str()); | |
| break; | |
| } | |
| } | |
| } | |
| void WriteArgNode(const ArgNode& node, | |
| const TraceStorage* storage, | |
| json::JsonValueSerializer&& writer); | |
| void WriteArgNode(const ArgNode& node, | |
| const TraceStorage* storage, | |
| json::JsonArraySerializer& writer); | |
| void WriteArgNode(const ArgNode& node, | |
| const TraceStorage* storage, | |
| json::JsonDictSerializer& writer, | |
| std::string_view key); | |
| void WriteArgNode(const ArgNode& node, | |
| const TraceStorage* storage, | |
| json::JsonValueSerializer&& writer) { | |
| switch (node.GetType()) { | |
| case ArgNode::Type::kPrimitive: | |
| WriteVariadic(node.GetPrimitiveValue(), storage, std::move(writer)); | |
| break; | |
| case ArgNode::Type::kArray: | |
| std::move(writer).WriteArray( | |
| [&node, storage](json::JsonArraySerializer& arr) { | |
| for (const auto& child : node.GetArray()) { | |
| WriteArgNode(child, storage, arr); | |
| } | |
| }); | |
| break; | |
| case ArgNode::Type::kDict: | |
| std::move(writer).WriteDict( | |
| [&node, storage](json::JsonDictSerializer& dict) { | |
| for (const auto& [k, v] : node.GetDict()) { | |
| WriteArgNode(v, storage, dict, k); | |
| } | |
| }); | |
| break; | |
| } | |
| } | |
| void WriteArgNode(const ArgNode& node, | |
| const TraceStorage* storage, | |
| json::JsonArraySerializer& writer) { | |
| writer.Append([&node, storage](json::JsonValueSerializer&& value_writer) { | |
| WriteArgNode(node, storage, std::move(value_writer)); | |
| }); | |
| } | |
| void WriteArgNode(const ArgNode& node, | |
| const TraceStorage* storage, | |
| json::JsonDictSerializer& writer, | |
| std::string_view key) { | |
| writer.Add(key, [&node, storage](json::JsonValueSerializer&& value_writer) { | |
| WriteArgNode(node, storage, std::move(value_writer)); | |
| }); | |
| } | |
| } // namespace | |
| // static | |
| void ExtractArgFunction::Step(sqlite3_context* ctx, int, sqlite3_value** argv) { | |
| sqlite::Type arg_set_value = sqlite::value::Type(argv[0]); | |
| sqlite::Type key_value = sqlite::value::Type(argv[1]); | |
| // If the arg set id is null, just return null as the result. | |
| if (arg_set_value == sqlite::Type::kNull) { | |
| return; | |
| } | |
| if (arg_set_value != sqlite::Type::kInteger) { | |
| return sqlite::result::Error( | |
| ctx, "EXTRACT_ARG: 1st argument should be arg set id"); | |
| } | |
| if (key_value != sqlite::Type::kText) { | |
| return sqlite::result::Error(ctx, | |
| "EXTRACT_ARG: 2nd argument should be key"); | |
| } | |
| auto arg_set_id = static_cast<uint32_t>(sqlite::value::Int64(argv[0])); | |
| const char* key = sqlite::value::Text(argv[1]); | |
| auto* user_data = GetUserData(ctx); | |
| auto* storage = user_data->storage; | |
| uint32_t row = user_data->args_cursor.Get(arg_set_id, key); | |
| if (row == std::numeric_limits<uint32_t>::max()) { | |
| return; | |
| } | |
| auto rr = storage->arg_table()[row]; | |
| switch (*storage->GetVariadicTypeForId(rr.value_type())) { | |
| case Variadic::Type::kBool: | |
| case Variadic::Type::kInt: | |
| case Variadic::Type::kUint: | |
| case Variadic::Type::kPointer: | |
| return sqlite::result::Long(ctx, *rr.int_value()); | |
| case Variadic::Type::kJson: | |
| case Variadic::Type::kString: { | |
| auto opt_string_id = rr.string_value(); | |
| if (!opt_string_id.has_value() || opt_string_id->is_null()) { | |
| sqlite::result::Null(ctx); | |
| return; | |
| } | |
| NullTermStringView value = storage->GetString(opt_string_id); | |
| return sqlite::result::StaticString(ctx, value.c_str()); | |
| } | |
| case Variadic::Type::kReal: | |
| return sqlite::result::Double(ctx, *rr.real_value()); | |
| case Variadic::Type::kNull: | |
| return; | |
| } | |
| } | |
| // static | |
| void ArgSetToJson::Step(sqlite3_context* ctx, int, sqlite3_value** argv) { | |
| sqlite::Type arg_set_value = sqlite::value::Type(argv[0]); | |
| if (arg_set_value == sqlite::Type::kNull) { | |
| return; | |
| } | |
| if (arg_set_value != sqlite::Type::kInteger) { | |
| return sqlite::result::Error( | |
| ctx, "PRINT_ARGS: 1st argument should be arg set id"); | |
| } | |
| auto arg_set_id = static_cast<uint32_t>(sqlite::value::Int64(argv[0])); | |
| auto* user_data = GetUserData(ctx); | |
| auto* storage = user_data->storage; | |
| auto& args_cursor = user_data->arg_cursor; | |
| auto& arg_set = user_data->arg_set; | |
| auto& json_serializer = user_data->json_serializer; | |
| // Set filter value and execute cursor | |
| args_cursor.SetFilterValueUnchecked(0, arg_set_id); | |
| args_cursor.Execute(); | |
| // Reuse arg_set - clear but retain capacity | |
| arg_set.Clear(); | |
| for (; !args_cursor.Eof(); args_cursor.Next()) { | |
| const auto result = arg_set.AppendArg(storage->GetString(args_cursor.key()), | |
| GetArgValue(*storage, args_cursor)); | |
| if (!result.ok()) { | |
| return sqlite::result::Error(ctx, result.c_message()); | |
| } | |
| } | |
| // Reuse json_serializer - clear but retain capacity | |
| json_serializer.Clear(); | |
| json::JsonValueSerializer(json_serializer) | |
| .WriteDict([&](json::JsonDictSerializer& writer) { | |
| for (const auto& [key, value] : arg_set.root().GetDict()) { | |
| WriteArgNode(value, storage, writer, key); | |
| } | |
| }); | |
| auto json_sv = json_serializer.GetStringView(); | |
| return sqlite::result::TransientString(ctx, json_sv.data(), | |
| static_cast<int>(json_sv.size())); | |
| } | |
| namespace args { | |
| namespace { | |
| class ArgsPlugin : public Plugin<ArgsPlugin> { | |
| public: | |
| ~ArgsPlugin() override; | |
| void RegisterFunctions(PerfettoSqlConnection*, | |
| std::vector<FunctionRegistration>& out) override { | |
| auto* s = trace_context_->storage.get(); | |
| out.push_back(MakeFunctionRegistration<ExtractArgFunction>( | |
| std::make_unique<ExtractArgFunction::Context>(s))); | |
| out.push_back(MakeFunctionRegistration<ArgSetToJson>( | |
| std::make_unique<ArgSetToJson::Context>(s))); | |
| } | |
| }; | |
| ArgsPlugin::~ArgsPlugin() = default; | |
| } // namespace | |
| void RegisterPlugin() { | |
| static PluginRegistration reg( | |
| []() -> std::unique_ptr<PluginBase> { | |
| return std::make_unique<ArgsPlugin>(); | |
| }, | |
| ArgsPlugin::kPluginId, ArgsPlugin::kDepIds.data(), | |
| ArgsPlugin::kDepIds.size()); | |
| base::ignore_result(reg); | |
| } | |
| } // namespace args | |
| } // namespace perfetto::trace_processor |