| /* |
| * Copyright (C) 2023 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/prelude/functions/stack_functions.h" |
| #include <cstdint> |
| #include <cstring> |
| #include <deque> |
| #include <iterator> |
| #include <type_traits> |
| #include <vector> |
| |
| #include "perfetto/base/logging.h" |
| #include "perfetto/ext/base/optional.h" |
| #include "perfetto/protozero/scattered_heap_buffer.h" |
| #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/prelude/functions/register_function.h" |
| #include "src/trace_processor/storage/trace_storage.h" |
| #include "src/trace_processor/types/trace_processor_context.h" |
| #include "src/trace_processor/util/status_macros.h" |
| |
| namespace perfetto { |
| namespace trace_processor { |
| namespace { |
| |
| using protos::pbzero::Stack; |
| |
| util::Status SetBytesOutputValue(const std::vector<uint8_t>& src, |
| SqlValue& out, |
| SqlFunction::Destructors& destructors) { |
| void* dest = malloc(src.size()); |
| if (dest == nullptr) { |
| return base::ErrStatus("Out of memory"); |
| } |
| memcpy(dest, src.data(), src.size()); |
| out = SqlValue::Bytes(dest, src.size()); |
| destructors.bytes_destructor = free; |
| return util::OkStatus(); |
| } |
| |
| // CAT_STACKS(root BLOB/STRING, level_1 BLOB/STRING, …, leaf BLOB/STRING) |
| // Creates a Stack by concatenating other Stacks. Also accepts strings for which |
| // it generates a fake Frame |
| struct CatStacksFunction : public SqlFunction { |
| static constexpr char kFunctionName[] = "CAT_STACKS"; |
| using Context = void; |
| |
| static base::Status Run(void* cxt, |
| size_t argc, |
| sqlite3_value** argv, |
| SqlValue& out, |
| Destructors& destructors) { |
| base::Status status = RunImpl(cxt, argc, argv, out, destructors); |
| if (!status.ok()) { |
| return base::ErrStatus("%s: %s", kFunctionName, status.message().c_str()); |
| } |
| return status; |
| } |
| |
| static base::Status RunImpl(void*, |
| size_t argc, |
| sqlite3_value** argv, |
| SqlValue& out, |
| Destructors& destructors) { |
| protozero::HeapBuffered<Stack> stack; |
| |
| // Note, this SQL function expects the root frame to be the first argument. |
| // Stack expects the opposite, thus iterates the args in reverse order. |
| for (size_t i = argc; i > 0; --i) { |
| size_t arg_index = i - 1; |
| SqlValue value = sqlite_utils::SqliteValueToSqlValue(argv[arg_index]); |
| switch (value.type) { |
| case SqlValue::kBytes: { |
| stack->AppendRawProtoBytes(value.bytes_value, value.bytes_count); |
| break; |
| } |
| case SqlValue::kString: { |
| stack->add_entries()->set_name(value.AsString()); |
| break; |
| } |
| case SqlValue::kNull: |
| break; |
| case SqlValue::kLong: |
| case SqlValue::kDouble: |
| return sqlite_utils::InvalidArgumentTypeError( |
| "entry", arg_index, value.type, SqlValue::kBytes, |
| SqlValue::kString, SqlValue::kNull); |
| } |
| } |
| |
| return SetBytesOutputValue(stack.SerializeAsArray(), out, destructors); |
| } |
| }; |
| |
| // STACK_FROM_STACK_PROFILE_CALLSITE(callsite_id LONG) |
| // Creates a stack by taking a callsite_id (reference to the |
| // stack_profile_callsite table) and generating a list of frames (by walking the |
| // stack_profile_callsite table) |
| struct StackFromStackProfileCallsiteFunction : public SqlFunction { |
| static constexpr char kFunctionName[] = "STACK_FROM_STACK_PROFILE_CALLSITE"; |
| static constexpr int kArgc = 1; |
| using Context = TraceStorage; |
| |
| static base::Status Run(TraceStorage* storage, |
| size_t argc, |
| sqlite3_value** argv, |
| SqlValue& out, |
| Destructors& destructors) { |
| base::Status status = RunImpl(storage, argc, argv, out, destructors); |
| if (!status.ok()) { |
| return base::ErrStatus("%s: %s", kFunctionName, status.message().c_str()); |
| } |
| return status; |
| } |
| |
| static base::Status RunImpl(TraceStorage* storage, |
| size_t argc, |
| sqlite3_value** argv, |
| SqlValue& out, |
| Destructors& destructors) { |
| PERFETTO_CHECK(argc == kArgc); |
| |
| base::StatusOr<SqlValue> value = sqlite_utils::ExtractArgument( |
| argc, argv, "callsite_id", 0, SqlValue::kNull, SqlValue::kLong); |
| |
| if (!value.ok()) { |
| return value.status(); |
| } |
| |
| if (value->is_null()) { |
| return util::OkStatus(); |
| } |
| |
| if (value->AsLong() > std::numeric_limits<uint32_t>::max() || |
| !storage->stack_profile_callsite_table() |
| .FindById(tables::StackProfileCallsiteTable::Id( |
| static_cast<uint32_t>(value->AsLong()))) |
| .has_value()) { |
| return sqlite_utils::ToInvalidArgumentError( |
| "callsite_id", 0, |
| base::ErrStatus("callsite_id does not exist: %" PRId64, |
| value->AsLong())); |
| } |
| |
| uint32_t callsite_id = static_cast<uint32_t>(value->AsLong()); |
| protozero::HeapBuffered<Stack> stack; |
| stack->add_entries()->set_callsite_id(callsite_id); |
| return SetBytesOutputValue(stack.SerializeAsArray(), out, destructors); |
| } |
| }; |
| |
| // STACK_FROM_STACK_PROFILE_FRAME(frame_id LONG) |
| // Creates a stack with just the frame referenced by frame_id (reference to the |
| // stack_profile_frame table) |
| struct StackFromStackProfileFrameFunction : public SqlFunction { |
| static constexpr char kFunctionName[] = "STACK_FROM_STACK_PROFILE_FRAME"; |
| using Context = TraceStorage; |
| |
| static base::Status Run(TraceStorage* storage, |
| size_t argc, |
| sqlite3_value** argv, |
| SqlValue& out, |
| Destructors& destructors) { |
| base::Status status = RunImpl(storage, argc, argv, out, destructors); |
| if (!status.ok()) { |
| return base::ErrStatus("%s: %s", kFunctionName, status.message().c_str()); |
| } |
| return status; |
| } |
| |
| static base::Status RunImpl(TraceStorage* storage, |
| size_t argc, |
| sqlite3_value** argv, |
| SqlValue& out, |
| Destructors& destructors) { |
| base::StatusOr<SqlValue> value = sqlite_utils::ExtractArgument( |
| argc, argv, "frame_id", 0, SqlValue::kNull, SqlValue::kLong); |
| |
| if (!value.ok()) { |
| return value.status(); |
| } |
| |
| if (value->is_null()) { |
| return util::OkStatus(); |
| } |
| |
| if (value->AsLong() > std::numeric_limits<uint32_t>::max() || |
| !storage->stack_profile_frame_table() |
| .FindById(tables::StackProfileFrameTable::Id( |
| static_cast<uint32_t>(value->AsLong()))) |
| .has_value()) { |
| return base::ErrStatus("%s; frame_id does not exist: %" PRId64, |
| kFunctionName, value->AsLong()); |
| } |
| |
| uint32_t frame_id = static_cast<uint32_t>(value->AsLong()); |
| protozero::HeapBuffered<Stack> stack; |
| stack->add_entries()->set_frame_id(frame_id); |
| return SetBytesOutputValue(stack.SerializeAsArray(), out, destructors); |
| } |
| }; |
| |
| } // namespace |
| |
| base::Status RegisterStackFunctions(sqlite3* db, |
| TraceProcessorContext* context) { |
| RETURN_IF_ERROR(RegisterSqlFunction<CatStacksFunction>( |
| db, CatStacksFunction::kFunctionName, -1, context->storage.get())); |
| RETURN_IF_ERROR(RegisterSqlFunction<StackFromStackProfileFrameFunction>( |
| db, StackFromStackProfileFrameFunction::kFunctionName, 1, |
| context->storage.get())); |
| return RegisterSqlFunction<StackFromStackProfileCallsiteFunction>( |
| db, StackFromStackProfileCallsiteFunction::kFunctionName, 1, |
| context->storage.get()); |
| } |
| |
| } // namespace trace_processor |
| } // namespace perfetto |