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