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().value_or(0)});
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 = symbol.source_file().has_value()
506 ? string_table_.InternString(*symbol.source_file())
507 : kEmptyStringIndex;
508
509 auto ins = functions_.insert(
510 {Function{name, kEmptyStringIndex, filename}, functions_.size() + 1});
511 uint64_t id = ins.first->second;
512
513 if (ins.second) {
514 if (name != kEmptyStringIndex) {
515 GetMapping(mapping_id).debug_info.has_functions = true;
516 }
517 if (filename != kEmptyStringIndex) {
518 GetMapping(mapping_id).debug_info.has_filenames = true;
519 }
520 }
521
522 return id;
523 }
524
GetNameForFrame(const tables::StackProfileFrameTable::ConstRowReference & frame,CallsiteAnnotation annotation)525 int64_t GProfileBuilder::GetNameForFrame(
526 const tables::StackProfileFrameTable::ConstRowReference& frame,
527 CallsiteAnnotation annotation) {
528 NullTermStringView system_name = context_.storage->GetString(frame.name());
529 int64_t name = kEmptyStringIndex;
530 if (frame.deobfuscated_name()) {
531 name = string_table_.GetAnnotatedString(*frame.deobfuscated_name(),
532 annotation);
533 } else if (!system_name.empty()) {
534 std::unique_ptr<char, base::FreeDeleter> demangled =
535 demangle::Demangle(system_name.c_str());
536 if (demangled) {
537 name = string_table_.GetAnnotatedString(demangled.get(), annotation);
538 } else {
539 // demangling failed, expected if the name wasn't mangled. In any case
540 // reuse the system_name as this is what UI will usually display.
541 name = string_table_.GetAnnotatedString(frame.name(), annotation);
542 }
543 }
544 return name;
545 }
546
GetSystemNameForFrame(const tables::StackProfileFrameTable::ConstRowReference & frame)547 int64_t GProfileBuilder::GetSystemNameForFrame(
548 const tables::StackProfileFrameTable::ConstRowReference& frame) {
549 return string_table_.InternString(frame.name());
550 }
551
WriteFunctionIfNeeded(const tables::StackProfileFrameTable::ConstRowReference & frame,CallsiteAnnotation annotation,uint64_t mapping_id)552 uint64_t GProfileBuilder::WriteFunctionIfNeeded(
553 const tables::StackProfileFrameTable::ConstRowReference& frame,
554 CallsiteAnnotation annotation,
555 uint64_t mapping_id) {
556 AnnotatedFrameId key{frame.id(), annotation};
557 auto it = seen_functions_.find(key);
558 if (it != seen_functions_.end()) {
559 return it->second;
560 }
561
562 auto ins = functions_.insert(
563 {Function{GetNameForFrame(frame, annotation),
564 GetSystemNameForFrame(frame), kEmptyStringIndex},
565 functions_.size() + 1});
566 uint64_t id = ins.first->second;
567 seen_functions_.insert({key, id});
568
569 if (ins.second && (ins.first->first.name != kEmptyStringIndex ||
570 ins.first->first.system_name != kEmptyStringIndex)) {
571 GetMapping(mapping_id).debug_info.has_functions = true;
572 }
573
574 return id;
575 }
576
WriteFunctions()577 void GProfileBuilder::WriteFunctions() {
578 for (const auto& entry : functions_) {
579 if (entry.second == kNullFunctionId) {
580 continue;
581 }
582 auto* func = result_->add_function();
583 func->set_id(entry.second);
584 if (entry.first.name != 0) {
585 func->set_name(entry.first.name);
586 }
587 if (entry.first.system_name != 0) {
588 func->set_system_name(entry.first.system_name);
589 }
590 if (entry.first.filename != 0) {
591 func->set_filename(entry.first.filename);
592 }
593 }
594 }
595
WriteMappingIfNeeded(const tables::StackProfileMappingTable::ConstRowReference & mapping_ref)596 uint64_t GProfileBuilder::WriteMappingIfNeeded(
597 const tables::StackProfileMappingTable::ConstRowReference& mapping_ref) {
598 auto it = seen_mappings_.find(mapping_ref.id());
599 if (it != seen_mappings_.end()) {
600 return it->second;
601 }
602
603 auto ins = mapping_keys_.insert(
604 {MappingKey(mapping_ref, string_table_), mapping_keys_.size() + 1});
605
606 if (ins.second) {
607 mappings_.push_back(
608 Mapping(mapping_ref, context_.storage->string_pool(), string_table_));
609 }
610
611 return ins.first->second;
612 }
613
WriteMapping(uint64_t mapping_id)614 void GProfileBuilder::WriteMapping(uint64_t mapping_id) {
615 const Mapping& mapping = GetMapping(mapping_id);
616 auto m = result_->add_mapping();
617 m->set_id(mapping_id);
618 m->set_memory_start(mapping.memory_start);
619 m->set_memory_limit(mapping.memory_limit);
620 m->set_file_offset(mapping.file_offset);
621 m->set_filename(mapping.filename);
622 m->set_build_id(mapping.build_id);
623 m->set_has_functions(mapping.debug_info.has_functions);
624 m->set_has_filenames(mapping.debug_info.has_filenames);
625 m->set_has_line_numbers(mapping.debug_info.has_line_numbers);
626 m->set_has_inline_frames(mapping.debug_info.has_inline_frames);
627 }
628
WriteMappings()629 void GProfileBuilder::WriteMappings() {
630 // The convention in pprof files is to write the mapping for the main
631 // binary first. So lets do just that.
632 std::optional<uint64_t> main_mapping_id = GuessMainBinary();
633 if (main_mapping_id) {
634 WriteMapping(*main_mapping_id);
635 }
636
637 for (size_t i = 0; i < mappings_.size(); ++i) {
638 uint64_t mapping_id = i + 1;
639 if (main_mapping_id && *main_mapping_id == mapping_id) {
640 continue;
641 }
642 WriteMapping(mapping_id);
643 }
644 }
645
GuessMainBinary() const646 std::optional<uint64_t> GProfileBuilder::GuessMainBinary() const {
647 std::vector<int64_t> mapping_scores;
648
649 for (const auto& mapping : mappings_) {
650 mapping_scores.push_back(mapping.ComputeMainBinaryScore());
651 }
652
653 auto it = std::max_element(mapping_scores.begin(), mapping_scores.end());
654
655 if (it == mapping_scores.end()) {
656 return std::nullopt;
657 }
658
659 return static_cast<uint64_t>(std::distance(mapping_scores.begin(), it) + 1);
660 }
661
662 } // namespace trace_processor
663 } // namespace perfetto
664