1 /*
2 * Copyright (C) 2022 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/util/profile_builder.h"
18
19 #include <algorithm>
20 #include <cstddef>
21 #include <cstdint>
22 #include <functional>
23 #include <iterator>
24 #include <memory>
25 #include <optional>
26 #include <string>
27 #include <utility>
28 #include <vector>
29
30 #include "perfetto/base/logging.h"
31 #include "perfetto/ext/base/string_utils.h"
32 #include "perfetto/ext/base/string_view.h"
33 #include "perfetto/ext/base/utils.h"
34 #include "perfetto/ext/trace_processor/demangle.h"
35 #include "perfetto/protozero/packed_repeated_fields.h"
36 #include "perfetto/protozero/proto_utils.h"
37 #include "perfetto/protozero/scattered_heap_buffer.h"
38 #include "src/trace_processor/containers/null_term_string_view.h"
39 #include "src/trace_processor/containers/string_pool.h"
40 #include "src/trace_processor/db/column/types.h"
41 #include "src/trace_processor/storage/trace_storage.h"
42 #include "src/trace_processor/tables/profiler_tables_py.h"
43 #include "src/trace_processor/types/trace_processor_context.h"
44 #include "src/trace_processor/util/annotated_callsites.h"
45
46 #include "protos/third_party/pprof/profile.pbzero.h"
47
48 namespace perfetto::trace_processor {
49 namespace {
50
51 using protos::pbzero::Stack;
52 using third_party::perftools::profiles::pbzero::Profile;
53 using third_party::perftools::profiles::pbzero::Sample;
54
ToString(CallsiteAnnotation annotation)55 base::StringView ToString(CallsiteAnnotation annotation) {
56 switch (annotation) {
57 case CallsiteAnnotation::kNone:
58 return "";
59 case CallsiteAnnotation::kArtAot:
60 return "aot";
61 case CallsiteAnnotation::kArtInterpreted:
62 return "interp";
63 case CallsiteAnnotation::kArtJit:
64 return "jit";
65 case CallsiteAnnotation::kCommonFrame:
66 return "common-frame";
67 case CallsiteAnnotation::kCommonFrameInterp:
68 return "common-frame-interp";
69 }
70 PERFETTO_FATAL("For GCC");
71 }
72
73 } // namespace
74
StringTable(protozero::HeapBuffered<third_party::perftools::profiles::pbzero::Profile> * result,const StringPool * string_pool)75 GProfileBuilder::StringTable::StringTable(
76 protozero::HeapBuffered<third_party::perftools::profiles::pbzero::Profile>*
77 result,
78 const StringPool* string_pool)
79 : string_pool_(*string_pool), result_(*result) {
80 // String at index 0 of the string table must be the empty string (see
81 // profile.proto)
82 int64_t empty_index = WriteString("");
83 PERFETTO_CHECK(empty_index == kEmptyStringIndex);
84 }
85
InternString(base::StringView str)86 int64_t GProfileBuilder::StringTable::InternString(base::StringView str) {
87 if (str.empty()) {
88 return kEmptyStringIndex;
89 }
90 auto hash = str.Hash();
91 auto it = seen_strings_.find(hash);
92 if (it != seen_strings_.end()) {
93 return it->second;
94 }
95
96 auto pool_id = string_pool_.GetId(str);
97 int64_t index = pool_id ? InternString(*pool_id) : WriteString(str);
98
99 seen_strings_.insert({hash, index});
100 return index;
101 }
102
InternString(StringPool::Id string_pool_id)103 int64_t GProfileBuilder::StringTable::InternString(
104 StringPool::Id string_pool_id) {
105 auto it = seen_string_pool_ids_.find(string_pool_id);
106 if (it != seen_string_pool_ids_.end()) {
107 return it->second;
108 }
109
110 NullTermStringView str = string_pool_.Get(string_pool_id);
111
112 int64_t index = str.empty() ? kEmptyStringIndex : WriteString(str);
113 seen_string_pool_ids_.insert({string_pool_id, index});
114 return index;
115 }
116
GetAnnotatedString(StringPool::Id str,CallsiteAnnotation annotation)117 int64_t GProfileBuilder::StringTable::GetAnnotatedString(
118 StringPool::Id str,
119 CallsiteAnnotation annotation) {
120 if (str.is_null() || annotation == CallsiteAnnotation::kNone) {
121 return InternString(str);
122 }
123 return GetAnnotatedString(string_pool_.Get(str), annotation);
124 }
125
GetAnnotatedString(base::StringView str,CallsiteAnnotation annotation)126 int64_t GProfileBuilder::StringTable::GetAnnotatedString(
127 base::StringView str,
128 CallsiteAnnotation annotation) {
129 if (str.empty() || annotation == CallsiteAnnotation::kNone) {
130 return InternString(str);
131 }
132 return InternString(base::StringView(
133 str.ToStdString() + " [" + ToString(annotation).ToStdString() + "]"));
134 }
135
WriteString(base::StringView str)136 int64_t GProfileBuilder::StringTable::WriteString(base::StringView str) {
137 result_->add_string_table(str.data(), str.size());
138 return next_index_++;
139 }
140
MappingKey(const tables::StackProfileMappingTable::ConstRowReference & mapping,StringTable & string_table)141 GProfileBuilder::MappingKey::MappingKey(
142 const tables::StackProfileMappingTable::ConstRowReference& mapping,
143 StringTable& string_table) {
144 size = static_cast<uint64_t>(mapping.end() - mapping.start());
145 file_offset = static_cast<uint64_t>(mapping.exact_offset());
146 build_id_or_filename = string_table.InternString(mapping.build_id());
147 if (build_id_or_filename == kEmptyStringIndex) {
148 build_id_or_filename = string_table.InternString(mapping.name());
149 }
150 }
151
Mapping(const tables::StackProfileMappingTable::ConstRowReference & mapping,const StringPool & string_pool,StringTable & string_table)152 GProfileBuilder::Mapping::Mapping(
153 const tables::StackProfileMappingTable::ConstRowReference& mapping,
154 const StringPool& string_pool,
155 StringTable& string_table)
156 : memory_start(static_cast<uint64_t>(mapping.start())),
157 memory_limit(static_cast<uint64_t>(mapping.end())),
158 file_offset(static_cast<uint64_t>(mapping.exact_offset())),
159 filename(string_table.InternString(mapping.name())),
160 build_id(string_table.InternString(mapping.build_id())),
161 filename_str(string_pool.Get(mapping.name()).ToStdString()) {}
162
163 // Do some very basic scoring.
ComputeMainBinaryScore() const164 int64_t GProfileBuilder::Mapping::ComputeMainBinaryScore() const {
165 constexpr const char* kBadSuffixes[] = {".so"};
166 constexpr const char* kBadPrefixes[] = {"/apex", "/system", "/[", "["};
167
168 int64_t score = 0;
169 if (build_id != kEmptyStringIndex) {
170 score += 10;
171 }
172
173 if (filename != kEmptyStringIndex) {
174 score += 10;
175 }
176
177 if (debug_info.has_functions) {
178 score += 10;
179 }
180 if (debug_info.has_filenames) {
181 score += 10;
182 }
183 if (debug_info.has_line_numbers) {
184 score += 10;
185 }
186 if (debug_info.has_inline_frames) {
187 score += 10;
188 }
189
190 if (memory_limit == memory_start) {
191 score -= 1000;
192 }
193
194 for (const char* suffix : kBadSuffixes) {
195 if (base::EndsWith(filename_str, suffix)) {
196 score -= 1000;
197 break;
198 }
199 }
200
201 for (const char* prefix : kBadPrefixes) {
202 if (base::StartsWith(filename_str, prefix)) {
203 score -= 1000;
204 break;
205 }
206 }
207
208 return score;
209 }
210
AddSample(const protozero::PackedVarInt & location_ids,const std::vector<int64_t> & values)211 bool GProfileBuilder::SampleAggregator::AddSample(
212 const protozero::PackedVarInt& location_ids,
213 const std::vector<int64_t>& values) {
214 SerializedLocationId key(location_ids.data(),
215 location_ids.data() + location_ids.size());
216 std::vector<int64_t>* agg_values = samples_.Find(key);
217 if (!agg_values) {
218 samples_.Insert(std::move(key), values);
219 return true;
220 }
221 // All samples must have the same number of values.
222 if (values.size() != agg_values->size()) {
223 return false;
224 }
225 std::transform(values.begin(), values.end(), agg_values->begin(),
226 agg_values->begin(), std::plus<int64_t>());
227 return true;
228 }
229
WriteTo(Profile & profile)230 void GProfileBuilder::SampleAggregator::WriteTo(Profile& profile) {
231 protozero::PackedVarInt values;
232 for (auto it = samples_.GetIterator(); it; ++it) {
233 values.Reset();
234 for (int64_t value : it.value()) {
235 values.Append(value);
236 }
237 Sample* sample = profile.add_sample();
238 sample->set_value(values);
239 // Map key is the serialized varint. Just append the bytes.
240 sample->AppendBytes(Sample::kLocationIdFieldNumber, it.key().data(),
241 it.key().size());
242 }
243 }
244
GProfileBuilder(const TraceProcessorContext * context,const std::vector<ValueType> & sample_types)245 GProfileBuilder::GProfileBuilder(const TraceProcessorContext* context,
246 const std::vector<ValueType>& sample_types)
247 : context_(*context),
248 string_table_(&result_, &context->storage->string_pool()),
249 annotations_(context) {
250 // Make sure the empty function always gets id 0 which will be ignored
251 // when writing the proto file.
252 functions_.insert(
253 {Function{kEmptyStringIndex, kEmptyStringIndex, kEmptyStringIndex},
254 kNullFunctionId});
255 WriteSampleTypes(sample_types);
256 }
257
258 GProfileBuilder::~GProfileBuilder() = default;
259
WriteSampleTypes(const std::vector<ValueType> & sample_types)260 void GProfileBuilder::WriteSampleTypes(
261 const std::vector<ValueType>& sample_types) {
262 for (const auto& value_type : sample_types) {
263 // Write strings first
264 int64_t type =
265 string_table_.InternString(base::StringView(value_type.type));
266 int64_t unit =
267 string_table_.InternString(base::StringView(value_type.unit));
268 // Add message later, remember protozero does not allow you to
269 // interleave these write calls.
270 auto* sample_type = result_->add_sample_type();
271 sample_type->set_type(type);
272 sample_type->set_unit(unit);
273 }
274 }
275
AddSample(const Stack::Decoder & stack,const std::vector<int64_t> & values)276 bool GProfileBuilder::AddSample(const Stack::Decoder& stack,
277 const std::vector<int64_t>& values) {
278 PERFETTO_CHECK(!finalized_);
279
280 auto it = stack.entries();
281 if (!it) {
282 return true;
283 }
284
285 auto next = it;
286 ++next;
287 if (!next) {
288 Stack::Entry::Decoder entry(it->as_bytes());
289 if (entry.has_callsite_id() || entry.has_annotated_callsite_id()) {
290 bool annotated = entry.has_annotated_callsite_id();
291 uint32_t callsite_id = entry.has_callsite_id()
292 ? entry.callsite_id()
293 : entry.annotated_callsite_id();
294 return samples_.AddSample(
295 GetLocationIdsForCallsite(CallsiteId(callsite_id), annotated),
296 values);
297 }
298 }
299
300 // Note pprof orders the stacks leafs first. That is also the ordering
301 // StackBlob uses for entries
302 protozero::PackedVarInt location_ids;
303 for (; it; ++it) {
304 Stack::Entry::Decoder entry(it->as_bytes());
305 if (entry.has_name()) {
306 location_ids.Append(
307 WriteFakeLocationIfNeeded(entry.name().ToStdString()));
308 } else if (entry.has_callsite_id() || entry.has_annotated_callsite_id()) {
309 bool annotated = entry.has_annotated_callsite_id();
310 uint32_t callsite_id = entry.has_callsite_id()
311 ? entry.callsite_id()
312 : entry.annotated_callsite_id();
313 const protozero::PackedVarInt& ids =
314 GetLocationIdsForCallsite(CallsiteId(callsite_id), annotated);
315 for (const uint8_t* p = ids.data(); p < ids.data() + ids.size();) {
316 uint64_t location_id;
317 p = protozero::proto_utils::ParseVarInt(p, ids.data() + ids.size(),
318 &location_id);
319 location_ids.Append(location_id);
320 }
321 } else if (entry.has_frame_id()) {
322 location_ids.Append(WriteLocationIfNeeded(FrameId(entry.frame_id()),
323 CallsiteAnnotation::kNone));
324 }
325 }
326 return samples_.AddSample(location_ids, values);
327 }
328
Finalize()329 void GProfileBuilder::Finalize() {
330 if (finalized_) {
331 return;
332 }
333 WriteMappings();
334 WriteFunctions();
335 WriteLocations();
336 samples_.WriteTo(*result_.get());
337 finalized_ = true;
338 }
339
Build()340 std::string GProfileBuilder::Build() {
341 Finalize();
342 return result_.SerializeAsString();
343 }
344
GetLocationIdsForCallsite(const CallsiteId & callsite_id,bool annotated)345 const protozero::PackedVarInt& GProfileBuilder::GetLocationIdsForCallsite(
346 const CallsiteId& callsite_id,
347 bool annotated) {
348 auto it = cached_location_ids_.find({callsite_id, annotated});
349 if (it != cached_location_ids_.end()) {
350 return it->second;
351 }
352
353 protozero::PackedVarInt& location_ids =
354 cached_location_ids_[{callsite_id, annotated}];
355
356 const auto& cs_table = context_.storage->stack_profile_callsite_table();
357
358 std::optional<tables::StackProfileCallsiteTable::ConstRowReference>
359 start_ref = cs_table.FindById(callsite_id);
360 if (!start_ref) {
361 return location_ids;
362 }
363
364 location_ids.Append(WriteLocationIfNeeded(
365 start_ref->frame_id(), annotated ? annotations_.GetAnnotation(*start_ref)
366 : CallsiteAnnotation::kNone));
367
368 std::optional<CallsiteId> parent_id = start_ref->parent_id();
369 while (parent_id) {
370 auto parent_ref = cs_table.FindById(*parent_id);
371 location_ids.Append(WriteLocationIfNeeded(
372 parent_ref->frame_id(), annotated
373 ? annotations_.GetAnnotation(*parent_ref)
374 : CallsiteAnnotation::kNone));
375 parent_id = parent_ref->parent_id();
376 }
377
378 return location_ids;
379 }
380
WriteLocationIfNeeded(FrameId frame_id,CallsiteAnnotation annotation)381 uint64_t GProfileBuilder::WriteLocationIfNeeded(FrameId frame_id,
382 CallsiteAnnotation annotation) {
383 AnnotatedFrameId key{frame_id, annotation};
384 auto it = seen_locations_.find(key);
385 if (it != seen_locations_.end()) {
386 return it->second;
387 }
388
389 const auto& frames = context_.storage->stack_profile_frame_table();
390 auto frame = *frames.FindById(key.frame_id);
391
392 const auto& mappings = context_.storage->stack_profile_mapping_table();
393 auto mapping = *mappings.FindById(frame.mapping());
394 uint64_t mapping_id = WriteMappingIfNeeded(mapping);
395
396 uint64_t& id =
397 locations_[Location{mapping_id, static_cast<uint64_t>(frame.rel_pc()),
398 GetLines(frame, key.annotation, mapping_id)}];
399
400 if (id == 0) {
401 id = locations_.size();
402 }
403
404 seen_locations_.insert({key, id});
405
406 return id;
407 }
408
WriteFakeLocationIfNeeded(const std::string & name)409 uint64_t GProfileBuilder::WriteFakeLocationIfNeeded(const std::string& name) {
410 int64_t name_id = string_table_.InternString(base::StringView(name));
411 auto it = seen_fake_locations_.find(name_id);
412 if (it != seen_fake_locations_.end()) {
413 return it->second;
414 }
415
416 uint64_t& id =
417 locations_[Location{0, 0, {{WriteFakeFunctionIfNeeded(name_id), 0}}}];
418
419 if (id == 0) {
420 id = locations_.size();
421 }
422
423 seen_fake_locations_.insert({name_id, id});
424
425 return id;
426 }
427
WriteLocations()428 void GProfileBuilder::WriteLocations() {
429 for (const auto& entry : locations_) {
430 auto* location = result_->add_location();
431 location->set_id(entry.second);
432 location->set_mapping_id(entry.first.mapping_id);
433 if (entry.first.mapping_id != 0) {
434 location->set_address(entry.first.rel_pc +
435 GetMapping(entry.first.mapping_id).memory_start);
436 }
437 for (const Line& line : entry.first.lines) {
438 auto* l = location->add_line();
439 l->set_function_id(line.function_id);
440 if (line.line != 0) {
441 l->set_line(line.line);
442 }
443 }
444 }
445 }
446
GetLines(const tables::StackProfileFrameTable::ConstRowReference & frame,CallsiteAnnotation annotation,uint64_t mapping_id)447 std::vector<GProfileBuilder::Line> GProfileBuilder::GetLines(
448 const tables::StackProfileFrameTable::ConstRowReference& frame,
449 CallsiteAnnotation annotation,
450 uint64_t mapping_id) {
451 std::vector<Line> lines;
452
453 lines = GetLinesForJitFrame(frame, annotation, mapping_id);
454 if (!lines.empty()) {
455 return lines;
456 }
457
458 lines = GetLinesForSymbolSetId(frame.symbol_set_id(), annotation, mapping_id);
459 if (!lines.empty()) {
460 return lines;
461 }
462
463 if (uint64_t function_id =
464 WriteFunctionIfNeeded(frame, annotation, mapping_id);
465 function_id != kNullFunctionId) {
466 lines.push_back({function_id, 0});
467 }
468
469 return lines;
470 }
471
472 namespace {
473 template <typename Table>
GetByConstraint(const Table & table,Constraint && c)474 std::optional<typename Table::ConstRowReference> GetByConstraint(
475 const Table& table,
476 Constraint&& c) {
477 Query q;
478 q.constraints = {c};
479 q.limit = 1;
480 auto row_map = table.QueryToRowMap(q);
481 if (row_map.empty())
482 return {};
483 return table[row_map.Get(0)];
484 }
485 } // namespace
486
GetLinesForJitFrame(const tables::StackProfileFrameTable::ConstRowReference & frame,CallsiteAnnotation annotation,uint64_t mapping_id)487 std::vector<GProfileBuilder::Line> GProfileBuilder::GetLinesForJitFrame(
488 const tables::StackProfileFrameTable::ConstRowReference& frame,
489 CallsiteAnnotation annotation,
490 uint64_t mapping_id) {
491 // Execute the equivalent of the SQL logic in callstacks/stack_profile.sql,
492 // namely
493 //
494 // ```
495 // COALESCE(
496 // 'JS: ' || IIF(jsf.name = "", "(anonymous)", jsf.name) || ':' ||
497 // jsf.line || ':' || jsf.col || ' [' || LOWER(jsc.tier) || ']', 'WASM: '
498 // || wc.function_name || ' [' || LOWER(wc.tier) || ']', 'REGEXP: ' ||
499 // rc.pattern, 'V8: ' || v8c.function_name, 'JIT: ' || jc.function_name
500 // ) AS name,
501 // FROM _callstack_spc_raw_forest c
502 // JOIN stack_profile_frame f ON c.frame_id = f.id
503 // LEFT JOIN _v8_js_code jsc USING(jit_code_id)
504 // LEFT JOIN v8_js_function jsf USING(v8_js_function_id)
505 // LEFT JOIN _v8_internal_code v8c USING(jit_code_id)
506 // LEFT JOIN _v8_wasm_code wc USING(jit_code_id)
507 // LEFT JOIN _v8_regexp_code rc USING(jit_code_id)
508 // LEFT JOIN __intrinsic_jit_code jc ON c.jit_code_id = jc.id
509 // ```
510 //
511 // TODO(leszeks): Unify these manual table lookups with the SQL
512 // implementation, e.g. by calling _callstacks_for_callsites in
513 // GProfileBuilder.
514
515 const auto& jit_frames = context_.storage->jit_frame_table();
516 auto maybe_jf =
517 GetByConstraint(jit_frames, jit_frames.frame_id().eq(frame.id().value));
518 if (!maybe_jf) {
519 return {};
520 }
521 auto jf = *maybe_jf;
522
523 const auto& v8_js_code = context_.storage->v8_js_code_table();
524 auto maybe_jsc = GetByConstraint(
525 v8_js_code, v8_js_code.jit_code_id().eq(jf.jit_code_id().value));
526 if (maybe_jsc) {
527 auto jsc = *maybe_jsc;
528
529 const auto& v8_js_funcs = context_.storage->v8_js_function_table();
530 auto maybe_jsf = v8_js_funcs.FindById(jsc.v8_js_function_id());
531 if (maybe_jsf) {
532 auto jsf = *maybe_jsf;
533 auto jsf_name = context_.storage->string_pool().Get(jsf.name());
534 auto jsf_tier = context_.storage->string_pool().Get(jsc.tier());
535 base::StackString<64> name(
536 "JS: %s:%u:%u [%s]",
537 jsf_name.empty() ? "(anonymous)" : jsf_name.c_str(),
538 jsf.line().value_or(0), jsf.col().value_or(0), jsf_tier.c_str());
539
540 const auto& v8_js_scripts = context_.storage->v8_js_script_table();
541 auto maybe_jss = v8_js_scripts.FindById(jsf.v8_js_script_id());
542
543 return {
544 Line{WriteFunctionIfNeeded(
545 name.string_view(),
546 maybe_jss.has_value() ? maybe_jss->name() : kNullStringId,
547 annotation, mapping_id),
548 jsf.line().value_or(0)}};
549 }
550 }
551
552 const auto& v8_wasm_code = context_.storage->v8_wasm_code_table();
553 auto maybe_wc = GetByConstraint(
554 v8_wasm_code, v8_wasm_code.jit_code_id().eq(jf.jit_code_id().value));
555 if (maybe_wc) {
556 auto wc = *maybe_wc;
557 std::string name =
558 "WASM: " +
559 context_.storage->GetString(wc.function_name()).ToStdString();
560 return {Line{WriteFunctionIfNeeded(base::StringView(name), kNullStringId,
561 annotation, mapping_id),
562 0}};
563 }
564
565 const auto& v8_regexp_code = context_.storage->v8_regexp_code_table();
566 auto maybe_rc = GetByConstraint(
567 v8_regexp_code, v8_regexp_code.jit_code_id().eq(jf.jit_code_id().value));
568 if (maybe_rc) {
569 auto rc = *maybe_rc;
570 std::string name =
571 "REGEXP: " + context_.storage->GetString(rc.pattern()).ToStdString();
572 return {Line{WriteFunctionIfNeeded(base::StringView(name), kNullStringId,
573 annotation, mapping_id),
574 0}};
575 }
576
577 const auto& v8_internal_code = context_.storage->v8_internal_code_table();
578 auto maybe_v8c = GetByConstraint(
579 v8_internal_code,
580 v8_internal_code.jit_code_id().eq(jf.jit_code_id().value));
581 if (maybe_v8c) {
582 auto v8c = *maybe_v8c;
583 std::string name =
584 "V8: " + context_.storage->GetString(v8c.function_name()).ToStdString();
585 return {Line{WriteFunctionIfNeeded(base::StringView(name), kNullStringId,
586 annotation, mapping_id),
587 0}};
588 }
589
590 const auto& jit_code = context_.storage->jit_code_table();
591 auto maybe_jc = jit_code.FindById(jf.jit_code_id());
592 if (maybe_jc) {
593 auto jc = *maybe_jc;
594 std::string name =
595 "JIT: " + context_.storage->GetString(jc.function_name()).ToStdString();
596 return {Line{WriteFunctionIfNeeded(base::StringView(name), kNullStringId,
597 annotation, mapping_id),
598 0}};
599 }
600
601 return {};
602 }
603
GetLinesForSymbolSetId(std::optional<uint32_t> symbol_set_id,CallsiteAnnotation annotation,uint64_t mapping_id)604 std::vector<GProfileBuilder::Line> GProfileBuilder::GetLinesForSymbolSetId(
605 std::optional<uint32_t> symbol_set_id,
606 CallsiteAnnotation annotation,
607 uint64_t mapping_id) {
608 if (!symbol_set_id) {
609 return {};
610 }
611
612 const auto& symbols = context_.storage->symbol_table();
613
614 using RowRef =
615 perfetto::trace_processor::tables::SymbolTable::ConstRowReference;
616 std::vector<RowRef> symbol_set;
617 Query q;
618 q.constraints = {symbols.symbol_set_id().eq(*symbol_set_id)};
619 for (auto it = symbols.FilterToIterator(q); it; ++it) {
620 symbol_set.push_back(it.row_reference());
621 }
622
623 std::sort(symbol_set.begin(), symbol_set.end(),
624 [](const RowRef& a, const RowRef& b) { return a.id() < b.id(); });
625
626 std::vector<GProfileBuilder::Line> lines;
627 for (const RowRef& symbol : symbol_set) {
628 if (uint64_t function_id =
629 WriteFunctionIfNeeded(symbol, annotation, mapping_id);
630 function_id != kNullFunctionId) {
631 lines.push_back({function_id, symbol.line_number().value_or(0)});
632 }
633 }
634
635 GetMapping(mapping_id).debug_info.has_inline_frames = true;
636 GetMapping(mapping_id).debug_info.has_line_numbers = true;
637
638 return lines;
639 }
640
WriteFakeFunctionIfNeeded(int64_t name_id)641 uint64_t GProfileBuilder::WriteFakeFunctionIfNeeded(int64_t name_id) {
642 auto ins = functions_.insert(
643 {Function{name_id, kEmptyStringIndex, kEmptyStringIndex},
644 functions_.size() + 1});
645 return ins.first->second;
646 }
647
WriteFunctionIfNeeded(base::StringView name,StringPool::Id filename,CallsiteAnnotation annotation,uint64_t mapping_id)648 uint64_t GProfileBuilder::WriteFunctionIfNeeded(base::StringView name,
649 StringPool::Id filename,
650 CallsiteAnnotation annotation,
651 uint64_t mapping_id) {
652 int64_t name_id = string_table_.GetAnnotatedString(name, annotation);
653 int64_t filename_id = !filename.is_null()
654 ? string_table_.InternString(filename)
655 : kEmptyStringIndex;
656
657 auto ins =
658 functions_.insert({Function{name_id, kEmptyStringIndex, filename_id},
659 functions_.size() + 1});
660 uint64_t id = ins.first->second;
661
662 if (ins.second) {
663 if (name_id != kEmptyStringIndex) {
664 GetMapping(mapping_id).debug_info.has_functions = true;
665 }
666 if (filename_id != kEmptyStringIndex) {
667 GetMapping(mapping_id).debug_info.has_filenames = true;
668 }
669 }
670
671 return id;
672 }
673
WriteFunctionIfNeeded(const tables::SymbolTable::ConstRowReference & symbol,CallsiteAnnotation annotation,uint64_t mapping_id)674 uint64_t GProfileBuilder::WriteFunctionIfNeeded(
675 const tables::SymbolTable::ConstRowReference& symbol,
676 CallsiteAnnotation annotation,
677 uint64_t mapping_id) {
678 int64_t name = string_table_.GetAnnotatedString(symbol.name(), annotation);
679 int64_t filename = symbol.source_file().has_value()
680 ? string_table_.InternString(*symbol.source_file())
681 : kEmptyStringIndex;
682
683 auto ins = functions_.insert(
684 {Function{name, kEmptyStringIndex, filename}, functions_.size() + 1});
685 uint64_t id = ins.first->second;
686
687 if (ins.second) {
688 if (name != kEmptyStringIndex) {
689 GetMapping(mapping_id).debug_info.has_functions = true;
690 }
691 if (filename != kEmptyStringIndex) {
692 GetMapping(mapping_id).debug_info.has_filenames = true;
693 }
694 }
695
696 return id;
697 }
698
GetNameForFrame(const tables::StackProfileFrameTable::ConstRowReference & frame,CallsiteAnnotation annotation)699 int64_t GProfileBuilder::GetNameForFrame(
700 const tables::StackProfileFrameTable::ConstRowReference& frame,
701 CallsiteAnnotation annotation) {
702 NullTermStringView system_name = context_.storage->GetString(frame.name());
703 int64_t name = kEmptyStringIndex;
704 if (frame.deobfuscated_name()) {
705 name = string_table_.GetAnnotatedString(*frame.deobfuscated_name(),
706 annotation);
707 } else if (!system_name.empty()) {
708 std::unique_ptr<char, base::FreeDeleter> demangled =
709 demangle::Demangle(system_name.c_str());
710 if (demangled) {
711 name = string_table_.GetAnnotatedString(demangled.get(), annotation);
712 } else {
713 // demangling failed, expected if the name wasn't mangled. In any case
714 // reuse the system_name as this is what UI will usually display.
715 name = string_table_.GetAnnotatedString(frame.name(), annotation);
716 }
717 }
718 return name;
719 }
720
GetSystemNameForFrame(const tables::StackProfileFrameTable::ConstRowReference & frame)721 int64_t GProfileBuilder::GetSystemNameForFrame(
722 const tables::StackProfileFrameTable::ConstRowReference& frame) {
723 return string_table_.InternString(frame.name());
724 }
725
WriteFunctionIfNeeded(const tables::StackProfileFrameTable::ConstRowReference & frame,CallsiteAnnotation annotation,uint64_t mapping_id)726 uint64_t GProfileBuilder::WriteFunctionIfNeeded(
727 const tables::StackProfileFrameTable::ConstRowReference& frame,
728 CallsiteAnnotation annotation,
729 uint64_t mapping_id) {
730 AnnotatedFrameId key{frame.id(), annotation};
731 auto it = seen_functions_.find(key);
732 if (it != seen_functions_.end()) {
733 return it->second;
734 }
735
736 auto ins = functions_.insert(
737 {Function{GetNameForFrame(frame, annotation),
738 GetSystemNameForFrame(frame), kEmptyStringIndex},
739 functions_.size() + 1});
740 uint64_t id = ins.first->second;
741 seen_functions_.insert({key, id});
742
743 if (ins.second && (ins.first->first.name != kEmptyStringIndex ||
744 ins.first->first.system_name != kEmptyStringIndex)) {
745 GetMapping(mapping_id).debug_info.has_functions = true;
746 }
747
748 return id;
749 }
750
WriteFunctions()751 void GProfileBuilder::WriteFunctions() {
752 for (const auto& entry : functions_) {
753 if (entry.second == kNullFunctionId) {
754 continue;
755 }
756 auto* func = result_->add_function();
757 func->set_id(entry.second);
758 if (entry.first.name != 0) {
759 func->set_name(entry.first.name);
760 }
761 if (entry.first.system_name != 0) {
762 func->set_system_name(entry.first.system_name);
763 }
764 if (entry.first.filename != 0) {
765 func->set_filename(entry.first.filename);
766 }
767 }
768 }
769
WriteMappingIfNeeded(const tables::StackProfileMappingTable::ConstRowReference & mapping_ref)770 uint64_t GProfileBuilder::WriteMappingIfNeeded(
771 const tables::StackProfileMappingTable::ConstRowReference& mapping_ref) {
772 auto it = seen_mappings_.find(mapping_ref.id());
773 if (it != seen_mappings_.end()) {
774 return it->second;
775 }
776
777 auto ins = mapping_keys_.insert(
778 {MappingKey(mapping_ref, string_table_), mapping_keys_.size() + 1});
779
780 if (ins.second) {
781 mappings_.emplace_back(mapping_ref, context_.storage->string_pool(),
782 string_table_);
783 }
784
785 return ins.first->second;
786 }
787
WriteMapping(uint64_t mapping_id)788 void GProfileBuilder::WriteMapping(uint64_t mapping_id) {
789 const Mapping& mapping = GetMapping(mapping_id);
790 auto* m = result_->add_mapping();
791 m->set_id(mapping_id);
792 m->set_memory_start(mapping.memory_start);
793 m->set_memory_limit(mapping.memory_limit);
794 m->set_file_offset(mapping.file_offset);
795 m->set_filename(mapping.filename);
796 m->set_build_id(mapping.build_id);
797 m->set_has_functions(mapping.debug_info.has_functions);
798 m->set_has_filenames(mapping.debug_info.has_filenames);
799 m->set_has_line_numbers(mapping.debug_info.has_line_numbers);
800 m->set_has_inline_frames(mapping.debug_info.has_inline_frames);
801 }
802
WriteMappings()803 void GProfileBuilder::WriteMappings() {
804 // The convention in pprof files is to write the mapping for the main
805 // binary first. So lets do just that.
806 std::optional<uint64_t> main_mapping_id = GuessMainBinary();
807 if (main_mapping_id) {
808 WriteMapping(*main_mapping_id);
809 }
810
811 for (size_t i = 0; i < mappings_.size(); ++i) {
812 uint64_t mapping_id = i + 1;
813 if (main_mapping_id && *main_mapping_id == mapping_id) {
814 continue;
815 }
816 WriteMapping(mapping_id);
817 }
818 }
819
GuessMainBinary() const820 std::optional<uint64_t> GProfileBuilder::GuessMainBinary() const {
821 std::vector<int64_t> mapping_scores;
822 mapping_scores.reserve(mappings_.size());
823
824 for (const auto& mapping : mappings_) {
825 mapping_scores.push_back(mapping.ComputeMainBinaryScore());
826 }
827
828 auto it = std::max_element(mapping_scores.begin(), mapping_scores.end());
829
830 if (it == mapping_scores.end()) {
831 return std::nullopt;
832 }
833
834 return static_cast<uint64_t>(std::distance(mapping_scores.begin(), it) + 1);
835 }
836
837 } // namespace perfetto::trace_processor
838