xref: /aosp_15_r20/external/perfetto/src/trace_processor/util/profile_builder.cc (revision 6dbdd20afdafa5e3ca9b8809fa73465d530080dc)
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