1 /*
2 * Copyright (C) 2023 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 #include "src/trace_processor/perfetto_sql/intrinsics/functions/stack_functions.h"
18
19 #include <stdlib.h>
20 #include <cstdint>
21 #include <cstring>
22 #include <deque>
23 #include <iterator>
24 #include <optional>
25 #include <type_traits>
26 #include <vector>
27
28 #include "perfetto/base/logging.h"
29 #include "perfetto/base/status.h"
30 #include "perfetto/protozero/scattered_heap_buffer.h"
31 #include "perfetto/trace_processor/basic_types.h"
32 #include "perfetto/trace_processor/status.h"
33 #include "protos/perfetto/trace_processor/stack.pbzero.h"
34 #include "src/trace_processor/perfetto_sql/engine/perfetto_sql_engine.h"
35 #include "src/trace_processor/perfetto_sql/intrinsics/functions/sql_function.h"
36 #include "src/trace_processor/sqlite/sqlite_utils.h"
37 #include "src/trace_processor/storage/trace_storage.h"
38 #include "src/trace_processor/types/trace_processor_context.h"
39 #include "src/trace_processor/util/status_macros.h"
40
41 namespace perfetto {
42 namespace trace_processor {
43 namespace {
44
45 using protos::pbzero::Stack;
46
SetBytesOutputValue(const std::vector<uint8_t> & src,SqlValue & out,SqlFunction::Destructors & destructors)47 util::Status SetBytesOutputValue(const std::vector<uint8_t>& src,
48 SqlValue& out,
49 SqlFunction::Destructors& destructors) {
50 void* dest = malloc(src.size());
51 if (dest == nullptr) {
52 return base::ErrStatus("Out of memory");
53 }
54 memcpy(dest, src.data(), src.size());
55 out = SqlValue::Bytes(dest, src.size());
56 destructors.bytes_destructor = free;
57 return util::OkStatus();
58 }
59
60 // CAT_STACKS(root BLOB/STRING, level_1 BLOB/STRING, …, leaf BLOB/STRING)
61 // Creates a Stack by concatenating other Stacks. Also accepts strings for which
62 // it generates a fake Frame
63 struct CatStacksFunction : public SqlFunction {
64 static constexpr char kFunctionName[] = "CAT_STACKS";
65 using Context = void;
66
Runperfetto::trace_processor::__anon7a4157590111::CatStacksFunction67 static base::Status Run(void* cxt,
68 size_t argc,
69 sqlite3_value** argv,
70 SqlValue& out,
71 Destructors& destructors) {
72 base::Status status = RunImpl(cxt, argc, argv, out, destructors);
73 if (!status.ok()) {
74 return base::ErrStatus("%s: %s", kFunctionName, status.message().c_str());
75 }
76 return status;
77 }
78
RunImplperfetto::trace_processor::__anon7a4157590111::CatStacksFunction79 static base::Status RunImpl(void*,
80 size_t argc,
81 sqlite3_value** argv,
82 SqlValue& out,
83 Destructors& destructors) {
84 protozero::HeapBuffered<Stack> stack;
85
86 // Note, this SQL function expects the root frame to be the first argument.
87 // Stack expects the opposite, thus iterates the args in reverse order.
88 for (size_t i = argc; i > 0; --i) {
89 size_t arg_index = i - 1;
90 SqlValue value = sqlite::utils::SqliteValueToSqlValue(argv[arg_index]);
91 switch (value.type) {
92 case SqlValue::kBytes: {
93 stack->AppendRawProtoBytes(value.bytes_value, value.bytes_count);
94 break;
95 }
96 case SqlValue::kString: {
97 stack->add_entries()->set_name(value.AsString());
98 break;
99 }
100 case SqlValue::kNull:
101 break;
102 case SqlValue::kLong:
103 case SqlValue::kDouble:
104 return sqlite::utils::InvalidArgumentTypeError(
105 "entry", arg_index, value.type, SqlValue::kBytes,
106 SqlValue::kString, SqlValue::kNull);
107 }
108 }
109
110 return SetBytesOutputValue(stack.SerializeAsArray(), out, destructors);
111 }
112 };
113
114 // STACK_FROM_STACK_PROFILE_CALLSITE(callsite_id LONG, [annotate BOOLEAN])
115 // Creates a stack by taking a callsite_id (reference to the
116 // stack_profile_callsite table) and generating a list of frames (by walking the
117 // stack_profile_callsite table)
118 // Optionally annotates frames (annotate param has a default value of false)
119 //
120 // Important: Annotations might interfere with certain aggregations, as we
121 // will could have a frame that is annotated with different annotations. That
122 // will lead to multiple functions being generated (same name, line etc, but
123 // different annotation).
124 struct StackFromStackProfileCallsiteFunction : public SqlFunction {
125 static constexpr char kFunctionName[] = "STACK_FROM_STACK_PROFILE_CALLSITE";
126 using Context = TraceStorage;
127
Runperfetto::trace_processor::__anon7a4157590111::StackFromStackProfileCallsiteFunction128 static base::Status Run(TraceStorage* storage,
129 size_t argc,
130 sqlite3_value** argv,
131 SqlValue& out,
132 Destructors& destructors) {
133 base::Status status = RunImpl(storage, argc, argv, out, destructors);
134 if (!status.ok()) {
135 return base::ErrStatus("%s: %s", kFunctionName, status.message().c_str());
136 }
137 return status;
138 }
139
RunImplperfetto::trace_processor::__anon7a4157590111::StackFromStackProfileCallsiteFunction140 static base::Status RunImpl(TraceStorage* storage,
141 size_t argc,
142 sqlite3_value** argv,
143 SqlValue& out,
144 Destructors& destructors) {
145 if (argc != 1 && argc != 2) {
146 return base::ErrStatus(
147 "%s; Invalid number of arguments: expected 1 or 2, actual %zu",
148 kFunctionName, argc);
149 }
150
151 base::StatusOr<SqlValue> value = sqlite::utils::ExtractArgument(
152 argc, argv, "callsite_id", 0, SqlValue::kNull, SqlValue::kLong);
153 if (!value.ok()) {
154 return value.status();
155 }
156 if (value->is_null()) {
157 return base::OkStatus();
158 }
159
160 if (value->AsLong() > std::numeric_limits<uint32_t>::max() ||
161 !storage->stack_profile_callsite_table()
162 .FindById(tables::StackProfileCallsiteTable::Id(
163 static_cast<uint32_t>(value->AsLong())))
164 .has_value()) {
165 return sqlite::utils::ToInvalidArgumentError(
166 "callsite_id", 0,
167 base::ErrStatus("callsite_id does not exist: %" PRId64,
168 value->AsLong()));
169 }
170
171 uint32_t callsite_id = static_cast<uint32_t>(value->AsLong());
172
173 bool annotate = false;
174 if (argc == 2) {
175 value = sqlite::utils::ExtractArgument(argc, argv, "annotate", 1,
176 SqlValue::Type::kLong);
177 if (!value.ok()) {
178 return value.status();
179 }
180 // true = 1 and false = 0 in SQL
181 annotate = (value->AsLong() != 0);
182 }
183
184 protozero::HeapBuffered<Stack> stack;
185 if (annotate) {
186 stack->add_entries()->set_annotated_callsite_id(callsite_id);
187 } else {
188 stack->add_entries()->set_callsite_id(callsite_id);
189 }
190 return SetBytesOutputValue(stack.SerializeAsArray(), out, destructors);
191 }
192 };
193
194 // STACK_FROM_STACK_PROFILE_FRAME(frame_id LONG)
195 // Creates a stack with just the frame referenced by frame_id (reference to the
196 // stack_profile_frame table)
197 struct StackFromStackProfileFrameFunction : public SqlFunction {
198 static constexpr char kFunctionName[] = "STACK_FROM_STACK_PROFILE_FRAME";
199 using Context = TraceStorage;
200
Runperfetto::trace_processor::__anon7a4157590111::StackFromStackProfileFrameFunction201 static base::Status Run(TraceStorage* storage,
202 size_t argc,
203 sqlite3_value** argv,
204 SqlValue& out,
205 Destructors& destructors) {
206 base::Status status = RunImpl(storage, argc, argv, out, destructors);
207 if (!status.ok()) {
208 return base::ErrStatus("%s: %s", kFunctionName, status.message().c_str());
209 }
210 return status;
211 }
212
RunImplperfetto::trace_processor::__anon7a4157590111::StackFromStackProfileFrameFunction213 static base::Status RunImpl(TraceStorage* storage,
214 size_t argc,
215 sqlite3_value** argv,
216 SqlValue& out,
217 Destructors& destructors) {
218 base::StatusOr<SqlValue> value = sqlite::utils::ExtractArgument(
219 argc, argv, "frame_id", 0, SqlValue::kNull, SqlValue::kLong);
220
221 if (!value.ok()) {
222 return value.status();
223 }
224
225 if (value->is_null()) {
226 return util::OkStatus();
227 }
228
229 if (value->AsLong() > std::numeric_limits<uint32_t>::max() ||
230 !storage->stack_profile_frame_table()
231 .FindById(tables::StackProfileFrameTable::Id(
232 static_cast<uint32_t>(value->AsLong())))
233 .has_value()) {
234 return base::ErrStatus("%s; frame_id does not exist: %" PRId64,
235 kFunctionName, value->AsLong());
236 }
237
238 uint32_t frame_id = static_cast<uint32_t>(value->AsLong());
239 protozero::HeapBuffered<Stack> stack;
240 stack->add_entries()->set_frame_id(frame_id);
241 return SetBytesOutputValue(stack.SerializeAsArray(), out, destructors);
242 }
243 };
244
245 } // namespace
246
RegisterStackFunctions(PerfettoSqlEngine * engine,TraceProcessorContext * context)247 base::Status RegisterStackFunctions(PerfettoSqlEngine* engine,
248 TraceProcessorContext* context) {
249 RETURN_IF_ERROR(engine->RegisterStaticFunction<CatStacksFunction>(
250 CatStacksFunction::kFunctionName, -1, context->storage.get()));
251 RETURN_IF_ERROR(
252 engine->RegisterStaticFunction<StackFromStackProfileFrameFunction>(
253 StackFromStackProfileFrameFunction::kFunctionName, 1,
254 context->storage.get()));
255 return engine->RegisterStaticFunction<StackFromStackProfileCallsiteFunction>(
256 StackFromStackProfileCallsiteFunction::kFunctionName, -1,
257 context->storage.get());
258 }
259
260 } // namespace trace_processor
261 } // namespace perfetto
262