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 #include <algorithm>
19 #include <cstdint>
20 #include <deque>
21 #include <iostream>
22 #include <iterator>
23 #include <optional>
24 #include <vector>
25
26 #include "perfetto/base/logging.h"
27 #include "perfetto/ext/base/string_utils.h"
28 #include "perfetto/ext/base/string_view.h"
29 #include "perfetto/ext/trace_processor/demangle.h"
30 #include "protos/third_party/pprof/profile.pbzero.h"
31 #include "src/trace_processor/containers/null_term_string_view.h"
32 #include "src/trace_processor/containers/string_pool.h"
33 #include "src/trace_processor/storage/trace_storage.h"
34 #include "src/trace_processor/types/trace_processor_context.h"
35 #include "src/trace_processor/util/annotated_callsites.h"
36
37 namespace perfetto {
38 namespace trace_processor {
39 namespace {
40
41 using protos::pbzero::Stack;
42 using third_party::perftools::profiles::pbzero::Profile;
43 using third_party::perftools::profiles::pbzero::Sample;
44
ToString(CallsiteAnnotation annotation)45 base::StringView ToString(CallsiteAnnotation annotation) {
46 switch (annotation) {
47 case CallsiteAnnotation::kNone:
48 return "";
49 case CallsiteAnnotation::kArtAot:
50 return "aot";
51 case CallsiteAnnotation::kArtInterpreted:
52 return "interp";
53 case CallsiteAnnotation::kArtJit:
54 return "jit";
55 case CallsiteAnnotation::kCommonFrame:
56 return "common-frame";
57 case CallsiteAnnotation::kCommonFrameInterp:
58 return "common-frame-interp";
59 }
60 PERFETTO_FATAL("For GCC");
61 }
62
63 } // namespace
64
StringTable(protozero::HeapBuffered<third_party::perftools::profiles::pbzero::Profile> * result,const StringPool * string_pool)65 GProfileBuilder::StringTable::StringTable(
66 protozero::HeapBuffered<third_party::perftools::profiles::pbzero::Profile>*
67 result,
68 const StringPool* string_pool)
69 : string_pool_(*string_pool), result_(*result) {
70 // String at index 0 of the string table must be the empty string (see
71 // profile.proto)
72 int64_t empty_index = WriteString("");
73 PERFETTO_CHECK(empty_index == kEmptyStringIndex);
74 }
75
InternString(base::StringView str)76 int64_t GProfileBuilder::StringTable::InternString(base::StringView str) {
77 if (str.empty()) {
78 return kEmptyStringIndex;
79 }
80 auto hash = str.Hash();
81 auto it = seen_strings_.find(hash);
82 if (it != seen_strings_.end()) {
83 return it->second;
84 }
85
86 auto pool_id = string_pool_.GetId(str);
87 int64_t index = pool_id ? InternString(*pool_id) : WriteString(str);
88
89 seen_strings_.insert({hash, index});
90 return index;
91 }
92
InternString(StringPool::Id string_pool_id)93 int64_t GProfileBuilder::StringTable::InternString(
94 StringPool::Id string_pool_id) {
95 auto it = seen_string_pool_ids_.find(string_pool_id);
96 if (it != seen_string_pool_ids_.end()) {
97 return it->second;
98 }
99
100 NullTermStringView str = string_pool_.Get(string_pool_id);
101
102 int64_t index = str.empty() ? kEmptyStringIndex : WriteString(str);
103 seen_string_pool_ids_.insert({string_pool_id, index});
104 return index;
105 }
106
GetAnnotatedString(StringPool::Id str,CallsiteAnnotation annotation)107 int64_t GProfileBuilder::StringTable::GetAnnotatedString(
108 StringPool::Id str,
109 CallsiteAnnotation annotation) {
110 if (str.is_null() || annotation == CallsiteAnnotation::kNone) {
111 return InternString(str);
112 }
113 return GetAnnotatedString(string_pool_.Get(str), annotation);
114 }
115
GetAnnotatedString(base::StringView str,CallsiteAnnotation annotation)116 int64_t GProfileBuilder::StringTable::GetAnnotatedString(
117 base::StringView str,
118 CallsiteAnnotation annotation) {
119 if (str.empty() || annotation == CallsiteAnnotation::kNone) {
120 return InternString(str);
121 }
122 return InternString(base::StringView(
123 str.ToStdString() + " [" + ToString(annotation).ToStdString() + "]"));
124 }
125
WriteString(base::StringView str)126 int64_t GProfileBuilder::StringTable::WriteString(base::StringView str) {
127 result_->add_string_table(str.data(), str.size());
128 return next_index_++;
129 }
130
MappingKey(const tables::StackProfileMappingTable::ConstRowReference & mapping,StringTable & string_table)131 GProfileBuilder::MappingKey::MappingKey(
132 const tables::StackProfileMappingTable::ConstRowReference& mapping,
133 StringTable& string_table) {
134 size = static_cast<uint64_t>(mapping.end() - mapping.start());
135 file_offset = static_cast<uint64_t>(mapping.exact_offset());
136 build_id_or_filename = string_table.InternString(mapping.build_id());
137 if (build_id_or_filename == kEmptyStringIndex) {
138 build_id_or_filename = string_table.InternString(mapping.name());
139 }
140 }
141
Mapping(const tables::StackProfileMappingTable::ConstRowReference & mapping,const StringPool & string_pool,StringTable & string_table)142 GProfileBuilder::Mapping::Mapping(
143 const tables::StackProfileMappingTable::ConstRowReference& mapping,
144 const StringPool& string_pool,
145 StringTable& string_table)
146 : memory_start(static_cast<uint64_t>(mapping.start())),
147 memory_limit(static_cast<uint64_t>(mapping.end())),
148 file_offset(static_cast<uint64_t>(mapping.exact_offset())),
149 filename(string_table.InternString(mapping.name())),
150 build_id(string_table.InternString(mapping.build_id())),
151 filename_str(string_pool.Get(mapping.name()).ToStdString()) {}
152
153 // Do some very basic scoring.
ComputeMainBinaryScore() const154 int64_t GProfileBuilder::Mapping::ComputeMainBinaryScore() const {
155 constexpr const char* kBadSuffixes[] = {".so"};
156 constexpr const char* kBadPrefixes[] = {"/apex", "/system", "/[", "["};
157
158 int64_t score = 0;
159 if (build_id != kEmptyStringIndex) {
160 score += 10;
161 }
162
163 if (filename != kEmptyStringIndex) {
164 score += 10;
165 }
166
167 if (debug_info.has_functions) {
168 score += 10;
169 }
170 if (debug_info.has_filenames) {
171 score += 10;
172 }
173 if (debug_info.has_line_numbers) {
174 score += 10;
175 }
176 if (debug_info.has_inline_frames) {
177 score += 10;
178 }
179
180 if (memory_limit == memory_start) {
181 score -= 1000;
182 }
183
184 for (const char* suffix : kBadSuffixes) {
185 if (base::EndsWith(filename_str, suffix)) {
186 score -= 1000;
187 break;
188 }
189 }
190
191 for (const char* prefix : kBadPrefixes) {
192 if (base::StartsWith(filename_str, prefix)) {
193 score -= 1000;
194 break;
195 }
196 }
197
198 return score;
199 }
200
AddSample(const protozero::PackedVarInt & location_ids,const std::vector<int64_t> & values)201 bool GProfileBuilder::SampleAggregator::AddSample(
202 const protozero::PackedVarInt& location_ids,
203 const std::vector<int64_t>& values) {
204 SerializedLocationId key(location_ids.data(),
205 location_ids.data() + location_ids.size());
206 std::vector<int64_t>* agg_values = samples_.Find(key);
207 if (!agg_values) {
208 samples_.Insert(std::move(key), values);
209 return true;
210 }
211 // All samples must have the same number of values.
212 if (values.size() != agg_values->size()) {
213 return false;
214 }
215 std::transform(values.begin(), values.end(), agg_values->begin(),
216 agg_values->begin(), std::plus<int64_t>());
217 return true;
218 }
219
WriteTo(Profile & profile)220 void GProfileBuilder::SampleAggregator::WriteTo(Profile& profile) {
221 protozero::PackedVarInt values;
222 for (auto it = samples_.GetIterator(); it; ++it) {
223 values.Reset();
224 for (int64_t value : it.value()) {
225 values.Append(value);
226 }
227 Sample* sample = profile.add_sample();
228 sample->set_value(values);
229 // Map key is the serialized varint. Just append the bytes.
230 sample->AppendBytes(Sample::kLocationIdFieldNumber, it.key().data(),
231 it.key().size());
232 }
233 }
234
GProfileBuilder(const TraceProcessorContext * context,const std::vector<ValueType> & sample_types)235 GProfileBuilder::GProfileBuilder(const TraceProcessorContext* context,
236 const std::vector<ValueType>& sample_types)
237 : context_(*context),
238 string_table_(&result_, &context->storage->string_pool()),
239 annotations_(context) {
240 // Make sure the empty function always gets id 0 which will be ignored
241 // when writing the proto file.
242 functions_.insert(
243 {Function{kEmptyStringIndex, kEmptyStringIndex, kEmptyStringIndex},
244 kNullFunctionId});
245 WriteSampleTypes(sample_types);
246 }
247
248 GProfileBuilder::~GProfileBuilder() = default;
249
WriteSampleTypes(const std::vector<ValueType> & sample_types)250 void GProfileBuilder::WriteSampleTypes(
251 const std::vector<ValueType>& sample_types) {
252 for (const auto& value_type : sample_types) {
253 // Write strings first
254 int64_t type =
255 string_table_.InternString(base::StringView(value_type.type));
256 int64_t unit =
257 string_table_.InternString(base::StringView(value_type.unit));
258 // Add message later, remember protozero does not allow you to
259 // interleave these write calls.
260 auto* sample_type = result_->add_sample_type();
261 sample_type->set_type(type);
262 sample_type->set_unit(unit);
263 }
264 }
265
AddSample(const Stack::Decoder & stack,const std::vector<int64_t> & values)266 bool GProfileBuilder::AddSample(const Stack::Decoder& stack,
267 const std::vector<int64_t>& values) {
268 PERFETTO_CHECK(!finalized_);
269
270 auto it = stack.entries();
271 if (!it) {
272 return true;
273 }
274
275 auto next = it;
276 ++next;
277 if (!next) {
278 Stack::Entry::Decoder entry(it->as_bytes());
279 if (entry.has_callsite_id() || entry.has_annotated_callsite_id()) {
280 bool annotated = entry.has_annotated_callsite_id();
281 uint32_t callsite_id = entry.has_callsite_id()
282 ? entry.callsite_id()
283 : entry.annotated_callsite_id();
284 return samples_.AddSample(
285 GetLocationIdsForCallsite(CallsiteId(callsite_id), annotated),
286 values);
287 }
288 }
289
290 // Note pprof orders the stacks leafs first. That is also the ordering
291 // StackBlob uses for entries
292 protozero::PackedVarInt location_ids;
293 for (; it; ++it) {
294 Stack::Entry::Decoder entry(it->as_bytes());
295 if (entry.has_name()) {
296 location_ids.Append(
297 WriteFakeLocationIfNeeded(entry.name().ToStdString()));
298 } else if (entry.has_callsite_id() || entry.has_annotated_callsite_id()) {
299 bool annotated = entry.has_annotated_callsite_id();
300 uint32_t callsite_id = entry.has_callsite_id()
301 ? entry.callsite_id()
302 : entry.annotated_callsite_id();
303 const protozero::PackedVarInt& ids =
304 GetLocationIdsForCallsite(CallsiteId(callsite_id), annotated);
305 for (auto* p = ids.data(); p < ids.data() + ids.size();) {
306 uint64_t location_id;
307 p = protozero::proto_utils::ParseVarInt(p, ids.data() + ids.size(),
308 &location_id);
309 location_ids.Append(location_id);
310 }
311 } else if (entry.has_frame_id()) {
312 location_ids.Append(WriteLocationIfNeeded(FrameId(entry.frame_id()),
313 CallsiteAnnotation::kNone));
314 }
315 }
316 return samples_.AddSample(location_ids, values);
317 }
318
Finalize()319 void GProfileBuilder::Finalize() {
320 if (finalized_) {
321 return;
322 }
323 WriteMappings();
324 WriteFunctions();
325 WriteLocations();
326 samples_.WriteTo(*result_.get());
327 finalized_ = true;
328 }
329
Build()330 std::string GProfileBuilder::Build() {
331 Finalize();
332 return result_.SerializeAsString();
333 }
334
GetLocationIdsForCallsite(const CallsiteId & callsite_id,bool annotated)335 const protozero::PackedVarInt& GProfileBuilder::GetLocationIdsForCallsite(
336 const CallsiteId& callsite_id,
337 bool annotated) {
338 auto it = cached_location_ids_.find({callsite_id, annotated});
339 if (it != cached_location_ids_.end()) {
340 return it->second;
341 }
342
343 protozero::PackedVarInt& location_ids =
344 cached_location_ids_[{callsite_id, annotated}];
345
346 const auto& cs_table = context_.storage->stack_profile_callsite_table();
347
348 std::optional<tables::StackProfileCallsiteTable::ConstRowReference>
349 start_ref = cs_table.FindById(callsite_id);
350 if (!start_ref) {
351 return location_ids;
352 }
353
354 location_ids.Append(WriteLocationIfNeeded(
355 start_ref->frame_id(), annotated ? annotations_.GetAnnotation(*start_ref)
356 : CallsiteAnnotation::kNone));
357
358 std::optional<CallsiteId> parent_id = start_ref->parent_id();
359 while (parent_id) {
360 auto parent_ref = cs_table.FindById(*parent_id);
361 location_ids.Append(WriteLocationIfNeeded(
362 parent_ref->frame_id(), annotated
363 ? annotations_.GetAnnotation(*parent_ref)
364 : CallsiteAnnotation::kNone));
365 parent_id = parent_ref->parent_id();
366 }
367
368 return location_ids;
369 }
370
WriteLocationIfNeeded(FrameId frame_id,CallsiteAnnotation annotation)371 uint64_t GProfileBuilder::WriteLocationIfNeeded(FrameId frame_id,
372 CallsiteAnnotation annotation) {
373 AnnotatedFrameId key{frame_id, annotation};
374 auto it = seen_locations_.find(key);
375 if (it != seen_locations_.end()) {
376 return it->second;
377 }
378
379 auto& frames = context_.storage->stack_profile_frame_table();
380 auto frame = *frames.FindById(key.frame_id);
381
382 const auto& mappings = context_.storage->stack_profile_mapping_table();
383 auto mapping = *mappings.FindById(frame.mapping());
384 uint64_t mapping_id = WriteMappingIfNeeded(mapping);
385
386 uint64_t& id =
387 locations_[Location{mapping_id, static_cast<uint64_t>(frame.rel_pc()),
388 GetLines(frame, key.annotation, mapping_id)}];
389
390 if (id == 0) {
391 id = locations_.size();
392 }
393
394 seen_locations_.insert({key, id});
395
396 return id;
397 }
398
WriteFakeLocationIfNeeded(const std::string & name)399 uint64_t GProfileBuilder::WriteFakeLocationIfNeeded(const std::string& name) {
400 int64_t name_id = string_table_.InternString(base::StringView(name));
401 auto it = seen_fake_locations_.find(name_id);
402 if (it != seen_fake_locations_.end()) {
403 return it->second;
404 }
405
406 uint64_t& id =
407 locations_[Location{0, 0, {{WriteFakeFunctionIfNeeded(name_id), 0}}}];
408
409 if (id == 0) {
410 id = locations_.size();
411 }
412
413 seen_fake_locations_.insert({name_id, id});
414
415 return id;
416 }
417
WriteLocations()418 void GProfileBuilder::WriteLocations() {
419 for (const auto& entry : locations_) {
420 auto* location = result_->add_location();
421 location->set_id(entry.second);
422 location->set_mapping_id(entry.first.mapping_id);
423 if (entry.first.mapping_id != 0) {
424 location->set_address(entry.first.rel_pc +
425 GetMapping(entry.first.mapping_id).memory_start);
426 }
427 for (const Line& line : entry.first.lines) {
428 auto* l = location->add_line();
429 l->set_function_id(line.function_id);
430 if (line.line != 0) {
431 l->set_line(line.line);
432 }
433 }
434 }
435 }
436
GetLines(const tables::StackProfileFrameTable::ConstRowReference & frame,CallsiteAnnotation annotation,uint64_t mapping_id)437 std::vector<GProfileBuilder::Line> GProfileBuilder::GetLines(
438 const tables::StackProfileFrameTable::ConstRowReference& frame,
439 CallsiteAnnotation annotation,
440 uint64_t mapping_id) {
441 std::vector<Line> lines =
442 GetLinesForSymbolSetId(frame.symbol_set_id(), annotation, mapping_id);
443 if (!lines.empty()) {
444 return lines;
445 }
446
447 if (uint64_t function_id =
448 WriteFunctionIfNeeded(frame, annotation, mapping_id);
449 function_id != kNullFunctionId) {
450 lines.push_back({function_id, 0});
451 }
452
453 return lines;
454 }
455
GetLinesForSymbolSetId(std::optional<uint32_t> symbol_set_id,CallsiteAnnotation annotation,uint64_t mapping_id)456 std::vector<GProfileBuilder::Line> GProfileBuilder::GetLinesForSymbolSetId(
457 std::optional<uint32_t> symbol_set_id,
458 CallsiteAnnotation annotation,
459 uint64_t mapping_id) {
460 if (!symbol_set_id) {
461 return {};
462 }
463
464 auto& symbols = context_.storage->symbol_table();
465
466 using RowRef =
467 perfetto::trace_processor::tables::SymbolTable::ConstRowReference;
468 std::vector<RowRef> symbol_set;
469 Query q;
470 q.constraints = {symbols.symbol_set_id().eq(*symbol_set_id)};
471 for (auto it = symbols.FilterToIterator(q); it; ++it) {
472 symbol_set.push_back(it.row_reference());
473 }
474
475 std::sort(symbol_set.begin(), symbol_set.end(),
476 [](const RowRef& a, const RowRef& b) { return a.id() < b.id(); });
477
478 std::vector<GProfileBuilder::Line> lines;
479 for (const RowRef& symbol : symbol_set) {
480 if (uint64_t function_id =
481 WriteFunctionIfNeeded(symbol, annotation, mapping_id);
482 function_id != kNullFunctionId) {
483 lines.push_back({function_id, symbol.line_number()});
484 }
485 }
486
487 GetMapping(mapping_id).debug_info.has_inline_frames = true;
488 GetMapping(mapping_id).debug_info.has_line_numbers = true;
489
490 return lines;
491 }
492
WriteFakeFunctionIfNeeded(int64_t name_id)493 uint64_t GProfileBuilder::WriteFakeFunctionIfNeeded(int64_t name_id) {
494 auto ins = functions_.insert(
495 {Function{name_id, kEmptyStringIndex, kEmptyStringIndex},
496 functions_.size() + 1});
497 return ins.first->second;
498 }
499
WriteFunctionIfNeeded(const tables::SymbolTable::ConstRowReference & symbol,CallsiteAnnotation annotation,uint64_t mapping_id)500 uint64_t GProfileBuilder::WriteFunctionIfNeeded(
501 const tables::SymbolTable::ConstRowReference& symbol,
502 CallsiteAnnotation annotation,
503 uint64_t mapping_id) {
504 int64_t name = string_table_.GetAnnotatedString(symbol.name(), annotation);
505 int64_t filename = string_table_.InternString(symbol.source_file());
506
507 auto ins = functions_.insert(
508 {Function{name, kEmptyStringIndex, filename}, functions_.size() + 1});
509 uint64_t id = ins.first->second;
510
511 if (ins.second) {
512 if (name != kEmptyStringIndex) {
513 GetMapping(mapping_id).debug_info.has_functions = true;
514 }
515 if (filename != kEmptyStringIndex) {
516 GetMapping(mapping_id).debug_info.has_filenames = true;
517 }
518 }
519
520 return id;
521 }
522
GetNameForFrame(const tables::StackProfileFrameTable::ConstRowReference & frame,CallsiteAnnotation annotation)523 int64_t GProfileBuilder::GetNameForFrame(
524 const tables::StackProfileFrameTable::ConstRowReference& frame,
525 CallsiteAnnotation annotation) {
526 NullTermStringView system_name = context_.storage->GetString(frame.name());
527 int64_t name = kEmptyStringIndex;
528 if (frame.deobfuscated_name()) {
529 name = string_table_.GetAnnotatedString(*frame.deobfuscated_name(),
530 annotation);
531 } else if (!system_name.empty()) {
532 std::unique_ptr<char, base::FreeDeleter> demangled =
533 demangle::Demangle(system_name.c_str());
534 if (demangled) {
535 name = string_table_.GetAnnotatedString(demangled.get(), annotation);
536 } else {
537 // demangling failed, expected if the name wasn't mangled. In any case
538 // reuse the system_name as this is what UI will usually display.
539 name = string_table_.GetAnnotatedString(frame.name(), annotation);
540 }
541 }
542 return name;
543 }
544
GetSystemNameForFrame(const tables::StackProfileFrameTable::ConstRowReference & frame)545 int64_t GProfileBuilder::GetSystemNameForFrame(
546 const tables::StackProfileFrameTable::ConstRowReference& frame) {
547 return string_table_.InternString(frame.name());
548 }
549
WriteFunctionIfNeeded(const tables::StackProfileFrameTable::ConstRowReference & frame,CallsiteAnnotation annotation,uint64_t mapping_id)550 uint64_t GProfileBuilder::WriteFunctionIfNeeded(
551 const tables::StackProfileFrameTable::ConstRowReference& frame,
552 CallsiteAnnotation annotation,
553 uint64_t mapping_id) {
554 AnnotatedFrameId key{frame.id(), annotation};
555 auto it = seen_functions_.find(key);
556 if (it != seen_functions_.end()) {
557 return it->second;
558 }
559
560 auto ins = functions_.insert(
561 {Function{GetNameForFrame(frame, annotation),
562 GetSystemNameForFrame(frame), kEmptyStringIndex},
563 functions_.size() + 1});
564 uint64_t id = ins.first->second;
565 seen_functions_.insert({key, id});
566
567 if (ins.second && (ins.first->first.name != kEmptyStringIndex ||
568 ins.first->first.system_name != kEmptyStringIndex)) {
569 GetMapping(mapping_id).debug_info.has_functions = true;
570 }
571
572 return id;
573 }
574
WriteFunctions()575 void GProfileBuilder::WriteFunctions() {
576 for (const auto& entry : functions_) {
577 if (entry.second == kNullFunctionId) {
578 continue;
579 }
580 auto* func = result_->add_function();
581 func->set_id(entry.second);
582 if (entry.first.name != 0) {
583 func->set_name(entry.first.name);
584 }
585 if (entry.first.system_name != 0) {
586 func->set_system_name(entry.first.system_name);
587 }
588 if (entry.first.filename != 0) {
589 func->set_filename(entry.first.filename);
590 }
591 }
592 }
593
WriteMappingIfNeeded(const tables::StackProfileMappingTable::ConstRowReference & mapping_ref)594 uint64_t GProfileBuilder::WriteMappingIfNeeded(
595 const tables::StackProfileMappingTable::ConstRowReference& mapping_ref) {
596 auto it = seen_mappings_.find(mapping_ref.id());
597 if (it != seen_mappings_.end()) {
598 return it->second;
599 }
600
601 auto ins = mapping_keys_.insert(
602 {MappingKey(mapping_ref, string_table_), mapping_keys_.size() + 1});
603
604 if (ins.second) {
605 mappings_.push_back(
606 Mapping(mapping_ref, context_.storage->string_pool(), string_table_));
607 }
608
609 return ins.first->second;
610 }
611
WriteMapping(uint64_t mapping_id)612 void GProfileBuilder::WriteMapping(uint64_t mapping_id) {
613 const Mapping& mapping = GetMapping(mapping_id);
614 auto m = result_->add_mapping();
615 m->set_id(mapping_id);
616 m->set_memory_start(mapping.memory_start);
617 m->set_memory_limit(mapping.memory_limit);
618 m->set_file_offset(mapping.file_offset);
619 m->set_filename(mapping.filename);
620 m->set_build_id(mapping.build_id);
621 m->set_has_functions(mapping.debug_info.has_functions);
622 m->set_has_filenames(mapping.debug_info.has_filenames);
623 m->set_has_line_numbers(mapping.debug_info.has_line_numbers);
624 m->set_has_inline_frames(mapping.debug_info.has_inline_frames);
625 }
626
WriteMappings()627 void GProfileBuilder::WriteMappings() {
628 // The convention in pprof files is to write the mapping for the main
629 // binary first. So lets do just that.
630 std::optional<uint64_t> main_mapping_id = GuessMainBinary();
631 if (main_mapping_id) {
632 WriteMapping(*main_mapping_id);
633 }
634
635 for (size_t i = 0; i < mappings_.size(); ++i) {
636 uint64_t mapping_id = i + 1;
637 if (main_mapping_id && *main_mapping_id == mapping_id) {
638 continue;
639 }
640 WriteMapping(mapping_id);
641 }
642 }
643
GuessMainBinary() const644 std::optional<uint64_t> GProfileBuilder::GuessMainBinary() const {
645 std::vector<int64_t> mapping_scores;
646
647 for (const auto& mapping : mappings_) {
648 mapping_scores.push_back(mapping.ComputeMainBinaryScore());
649 }
650
651 auto it = std::max_element(mapping_scores.begin(), mapping_scores.end());
652
653 if (it == mapping_scores.end()) {
654 return std::nullopt;
655 }
656
657 return static_cast<uint64_t>(std::distance(mapping_scores.begin(), it) + 1);
658 }
659
660 } // namespace trace_processor
661 } // namespace perfetto
662