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