• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2019 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 "perfetto/profiling/pprof_builder.h"
18 
19 #include "perfetto/base/build_config.h"
20 
21 #if !PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
22 #include <cxxabi.h>
23 #endif
24 
25 #include <algorithm>
26 #include <cinttypes>
27 #include <map>
28 #include <set>
29 #include <unordered_map>
30 #include <utility>
31 #include <vector>
32 
33 #include "perfetto/base/logging.h"
34 #include "perfetto/ext/base/hash.h"
35 #include "perfetto/ext/base/string_utils.h"
36 #include "perfetto/ext/base/utils.h"
37 #include "perfetto/protozero/packed_repeated_fields.h"
38 #include "perfetto/protozero/scattered_heap_buffer.h"
39 #include "perfetto/trace_processor/trace_processor.h"
40 #include "src/profiling/symbolizer/symbolize_database.h"
41 #include "src/profiling/symbolizer/symbolizer.h"
42 #include "src/trace_processor/containers/string_pool.h"
43 #include "src/traceconv/utils.h"
44 
45 #include "protos/perfetto/trace/trace.pbzero.h"
46 #include "protos/perfetto/trace/trace_packet.pbzero.h"
47 #include "protos/third_party/pprof/profile.pbzero.h"
48 
49 // Quick hint on navigating the file:
50 // Conversions for both perf and heap profiles start with |TraceToPprof|.
51 // Non-shared logic is in the |heap_profile| and |perf_profile| namespaces.
52 //
53 // To build one or more profiles, first the callstack information is queried
54 // from the SQL tables, and converted into an in-memory representation by
55 // |PreprocessLocations|. Then an instance of |GProfileBuilder| is used to
56 // accumulate samples for that profile, and emit all additional information as a
57 // serialized proto. Only the entities referenced by that particular
58 // |GProfileBuilder| instance are emitted.
59 //
60 // See protos/third_party/pprof/profile.proto for the meaning of terms like
61 // function/location/line.
62 
63 namespace {
64 using StringId = ::perfetto::trace_processor::StringPool::Id;
65 
66 // In-memory representation of a Profile.Function.
67 struct Function {
68   StringId name_id = StringId::Null();
69   StringId system_name_id = StringId::Null();
70   StringId filename_id = StringId::Null();
71 
Function__anon5a71a37a0111::Function72   Function(StringId n, StringId s, StringId f)
73       : name_id(n), system_name_id(s), filename_id(f) {}
74 
operator ==__anon5a71a37a0111::Function75   bool operator==(const Function& other) const {
76     return std::tie(name_id, system_name_id, filename_id) ==
77            std::tie(other.name_id, other.system_name_id, other.filename_id);
78   }
79 };
80 
81 // In-memory representation of a Profile.Line.
82 struct Line {
83   int64_t function_id = 0;  // LocationTracker's interned Function id
84   int64_t line_no = 0;
85 
Line__anon5a71a37a0111::Line86   Line(int64_t func, int64_t line) : function_id(func), line_no(line) {}
87 
operator ==__anon5a71a37a0111::Line88   bool operator==(const Line& other) const {
89     return function_id == other.function_id && line_no == other.line_no;
90   }
91 };
92 
93 // In-memory representation of a Profile.Location.
94 struct Location {
95   int64_t mapping_id = 0;  // sqlite row id
96   // Common case: location references a single function.
97   int64_t single_function_id = 0;  // interned Function id
98   // Alternatively: multiple inlined functions, recovered via offline
99   // symbolisation. Leaf-first ordering.
100   std::vector<Line> inlined_functions;
101 
Location__anon5a71a37a0111::Location102   Location(int64_t map, int64_t func, std::vector<Line> inlines)
103       : mapping_id(map),
104         single_function_id(func),
105         inlined_functions(std::move(inlines)) {}
106 
operator ==__anon5a71a37a0111::Location107   bool operator==(const Location& other) const {
108     return std::tie(mapping_id, single_function_id, inlined_functions) ==
109            std::tie(other.mapping_id, other.single_function_id,
110                     other.inlined_functions);
111   }
112 };
113 }  // namespace
114 
115 template <>
116 struct std::hash<Function> {
operator ()std::hash117   size_t operator()(const Function& loc) const {
118     perfetto::base::Hasher hasher;
119     hasher.Update(loc.name_id.raw_id());
120     hasher.Update(loc.system_name_id.raw_id());
121     hasher.Update(loc.filename_id.raw_id());
122     return static_cast<size_t>(hasher.digest());
123   }
124 };
125 
126 template <>
127 struct std::hash<Location> {
operator ()std::hash128   size_t operator()(const Location& loc) const {
129     perfetto::base::Hasher hasher;
130     hasher.Update(loc.mapping_id);
131     hasher.Update(loc.single_function_id);
132     for (auto line : loc.inlined_functions) {
133       hasher.Update(line.function_id);
134       hasher.Update(line.line_no);
135     }
136     return static_cast<size_t>(hasher.digest());
137   }
138 };
139 
140 namespace perfetto {
141 namespace trace_to_text {
142 namespace {
143 
144 using ::perfetto::trace_processor::Iterator;
145 
ToPprofId(int64_t id)146 uint64_t ToPprofId(int64_t id) {
147   PERFETTO_DCHECK(id >= 0);
148   return static_cast<uint64_t>(id) + 1;
149 }
150 
AsCsvString(std::vector<uint64_t> vals)151 std::string AsCsvString(std::vector<uint64_t> vals) {
152   std::string ret;
153   for (size_t i = 0; i < vals.size(); i++) {
154     if (i != 0) {
155       ret += ",";
156     }
157     ret += std::to_string(vals[i]);
158   }
159   return ret;
160 }
161 
GetStatsEntry(trace_processor::TraceProcessor * tp,const std::string & name,std::optional<uint64_t> idx=std::nullopt)162 std::optional<int64_t> GetStatsEntry(
163     trace_processor::TraceProcessor* tp,
164     const std::string& name,
165     std::optional<uint64_t> idx = std::nullopt) {
166   std::string query = "select value from stats where name == '" + name + "'";
167   if (idx.has_value())
168     query += " and idx == " + std::to_string(idx.value());
169 
170   auto it = tp->ExecuteQuery(query);
171   if (!it.Next()) {
172     if (!it.Status().ok()) {
173       PERFETTO_DFATAL_OR_ELOG("Invalid iterator: %s",
174                               it.Status().message().c_str());
175       return std::nullopt;
176     }
177     // some stats are not present unless non-zero
178     return std::make_optional(0);
179   }
180   return std::make_optional(it.Get(0).AsLong());
181 }
182 
183 // Interns Locations, Lines, and Functions. Interning is done by the entity's
184 // contents, and has no relation to the row ids in the SQL tables.
185 // Contains all data for the trace, so can be reused when emitting multiple
186 // profiles.
187 //
188 // TODO(rsavitski): consider moving mappings into here as well. For now, they're
189 // still emitted in a single scan during profile building. Mappings should be
190 // unique-enough already in the SQL tables, with only incremental state clearing
191 // duplicating entries.
192 class LocationTracker {
193  public:
InternLocation(Location loc)194   int64_t InternLocation(Location loc) {
195     auto it = locations_.find(loc);
196     if (it == locations_.end()) {
197       bool inserted = false;
198       std::tie(it, inserted) = locations_.emplace(
199           std::move(loc), static_cast<int64_t>(locations_.size()));
200       PERFETTO_DCHECK(inserted);
201     }
202     return it->second;
203   }
204 
InternFunction(Function func)205   int64_t InternFunction(Function func) {
206     auto it = functions_.find(func);
207     if (it == functions_.end()) {
208       bool inserted = false;
209       std::tie(it, inserted) =
210           functions_.emplace(func, static_cast<int64_t>(functions_.size()));
211       PERFETTO_DCHECK(inserted);
212     }
213     return it->second;
214   }
215 
IsCallsiteProcessed(int64_t callstack_id) const216   bool IsCallsiteProcessed(int64_t callstack_id) const {
217     return callsite_to_locations_.find(callstack_id) !=
218            callsite_to_locations_.end();
219   }
220 
MaybeSetCallsiteLocations(int64_t callstack_id,const std::vector<int64_t> & locs)221   void MaybeSetCallsiteLocations(int64_t callstack_id,
222                                  const std::vector<int64_t>& locs) {
223     // nop if already set
224     callsite_to_locations_.emplace(callstack_id, locs);
225   }
226 
LocationsForCallstack(int64_t callstack_id) const227   const std::vector<int64_t>& LocationsForCallstack(
228       int64_t callstack_id) const {
229     auto it = callsite_to_locations_.find(callstack_id);
230     PERFETTO_CHECK(callstack_id >= 0 && it != callsite_to_locations_.end());
231     return it->second;
232   }
233 
AllLocations() const234   const std::unordered_map<Location, int64_t>& AllLocations() const {
235     return locations_;
236   }
AllFunctions() const237   const std::unordered_map<Function, int64_t>& AllFunctions() const {
238     return functions_;
239   }
240 
241  private:
242   // Root-first location ids for a given callsite id.
243   std::unordered_map<int64_t, std::vector<int64_t>> callsite_to_locations_;
244   std::unordered_map<Location, int64_t> locations_;
245   std::unordered_map<Function, int64_t> functions_;
246 };
247 
248 struct PreprocessedInline {
249   // |name_id| is already demangled
250   StringId name_id = StringId::Null();
251   StringId filename_id = StringId::Null();
252   int64_t line_no = 0;
253 
PreprocessedInlineperfetto::trace_to_text::__anon5a71a37a0211::PreprocessedInline254   PreprocessedInline(StringId s, StringId f, int64_t line)
255       : name_id(s), filename_id(f), line_no(line) {}
256 };
257 
258 std::unordered_map<int64_t, std::vector<PreprocessedInline>>
PreprocessInliningInfo(trace_processor::TraceProcessor * tp,trace_processor::StringPool * interner)259 PreprocessInliningInfo(trace_processor::TraceProcessor* tp,
260                        trace_processor::StringPool* interner) {
261   std::unordered_map<int64_t, std::vector<PreprocessedInline>> inlines;
262 
263   // Most-inlined function (leaf) has the lowest id within a symbol set. Query
264   // such that the per-set line vectors are built up leaf-first.
265   Iterator it = tp->ExecuteQuery(
266       "select symbol_set_id, name, source_file, line_number from "
267       "stack_profile_symbol order by symbol_set_id asc, id asc;");
268   while (it.Next()) {
269     int64_t symbol_set_id = it.Get(0).AsLong();
270     auto func_sysname = it.Get(1).is_null() ? "" : it.Get(1).AsString();
271     auto filename = it.Get(2).is_null() ? "" : it.Get(2).AsString();
272     int64_t line_no = it.Get(3).AsLong();
273 
274     inlines[symbol_set_id].emplace_back(interner->InternString(func_sysname),
275                                         interner->InternString(filename),
276                                         line_no);
277   }
278 
279   if (!it.Status().ok()) {
280     PERFETTO_DFATAL_OR_ELOG("Invalid iterator: %s",
281                             it.Status().message().c_str());
282     return {};
283   }
284   return inlines;
285 }
286 
287 // Extracts and interns the unique frames and locations (as defined by the proto
288 // format) from the callstack SQL tables.
289 //
290 // Approach:
291 //   * for each callstack (callsite ids of the leaves):
292 //     * use experimental_annotated_callstack to build the full list of
293 //       constituent frames
294 //     * for each frame (root to leaf):
295 //         * intern the location and function(s)
296 //         * remember the mapping from callsite_id to the callstack so far (from
297 //            the root and including the frame being considered)
298 //
299 // Optionally mixes in the annotations as a frame name suffix (since there's no
300 // good way to attach extra info to locations in the proto format). This relies
301 // on the annotations (produced by experimental_annotated_callstack) to be
302 // stable for a given callsite (equivalently: dependent only on their parents).
PreprocessLocations(trace_processor::TraceProcessor * tp,trace_processor::StringPool * interner,bool annotate_frames)303 LocationTracker PreprocessLocations(trace_processor::TraceProcessor* tp,
304                                     trace_processor::StringPool* interner,
305                                     bool annotate_frames) {
306   LocationTracker tracker;
307 
308   // Keyed by symbol_set_id, discarded once this function converts the inlines
309   // into Line and Function entries.
310   std::unordered_map<int64_t, std::vector<PreprocessedInline>> inlining_info =
311       PreprocessInliningInfo(tp, interner);
312 
313   // Higher callsite ids most likely correspond to the deepest stacks, so we'll
314   // fill more of the overall callsite->location map by visiting the callsited
315   // in decreasing id order. Since processing a callstack also fills in the data
316   // for all parent callsites.
317   Iterator cid_it = tp->ExecuteQuery(
318       "select id from stack_profile_callsite order by id desc;");
319   while (cid_it.Next()) {
320     int64_t query_cid = cid_it.Get(0).AsLong();
321 
322     // If the leaf has been processed, the rest of the stack is already known.
323     if (tracker.IsCallsiteProcessed(query_cid))
324       continue;
325 
326     std::string annotated_query =
327         "select sp.id, sp.annotation, spf.mapping, spf.name, "
328         "coalesce(spf.deobfuscated_name, demangle(spf.name), spf.name), "
329         "spf.symbol_set_id from "
330         "experimental_annotated_callstack(" +
331         std::to_string(query_cid) +
332         ") sp join stack_profile_frame spf on (sp.frame_id == spf.id) "
333         "order by depth asc";
334     Iterator c_it = tp->ExecuteQuery(annotated_query);
335 
336     std::vector<int64_t> callstack_loc_ids;
337     while (c_it.Next()) {
338       int64_t cid = c_it.Get(0).AsLong();
339       auto annotation = c_it.Get(1).is_null() ? "" : c_it.Get(1).AsString();
340       int64_t mapping_id = c_it.Get(2).AsLong();
341       auto func_sysname = c_it.Get(3).is_null() ? "" : c_it.Get(3).AsString();
342       auto func_name = c_it.Get(4).is_null() ? "" : c_it.Get(4).AsString();
343       std::optional<int64_t> symbol_set_id =
344           c_it.Get(5).is_null() ? std::nullopt
345                                 : std::make_optional(c_it.Get(5).AsLong());
346 
347       Location loc(mapping_id, /*single_function_id=*/-1, {});
348 
349       auto intern_function = [interner, &tracker, annotate_frames](
350                                  StringId func_sysname_id,
351                                  StringId original_func_name_id,
352                                  StringId filename_id,
353                                  const std::string& anno) {
354         std::string fname = interner->Get(original_func_name_id).ToStdString();
355         if (annotate_frames && !anno.empty() && !fname.empty())
356           fname = fname + " [" + anno + "]";
357         StringId func_name_id = interner->InternString(base::StringView(fname));
358         Function func(func_name_id, func_sysname_id, filename_id);
359         return tracker.InternFunction(func);
360       };
361 
362       // Inlining information available
363       if (symbol_set_id.has_value()) {
364         auto it = inlining_info.find(*symbol_set_id);
365         if (it == inlining_info.end()) {
366           PERFETTO_DFATAL_OR_ELOG(
367               "Failed to find stack_profile_symbol entry for symbol_set_id "
368               "%" PRIi64 "",
369               *symbol_set_id);
370           return {};
371         }
372 
373         // N inlined functions
374         // The symbolised packets currently assume pre-demangled data (as that's
375         // the default of llvm-symbolizer), so we don't have a system name for
376         // each deinlined frame. Set the human-readable name for both fields. We
377         // can change this, but there's no demand for accurate system names in
378         // pprofs.
379         for (const auto& line : it->second) {
380           int64_t func_id = intern_function(line.name_id, line.name_id,
381                                             line.filename_id, annotation);
382 
383           loc.inlined_functions.emplace_back(func_id, line.line_no);
384         }
385       } else {
386         // Otherwise - single function
387         int64_t func_id =
388             intern_function(interner->InternString(func_sysname),
389                             interner->InternString(func_name),
390                             /*filename_id=*/StringId::Null(), annotation);
391         loc.single_function_id = func_id;
392       }
393 
394       int64_t loc_id = tracker.InternLocation(std::move(loc));
395 
396       // Update the tracker with the locations so far (for example, at depth 2,
397       // we'll have 3 root-most locations in |callstack_loc_ids|).
398       callstack_loc_ids.push_back(loc_id);
399       tracker.MaybeSetCallsiteLocations(cid, callstack_loc_ids);
400     }
401 
402     if (!c_it.Status().ok()) {
403       PERFETTO_DFATAL_OR_ELOG("Invalid iterator: %s",
404                               c_it.Status().message().c_str());
405       return {};
406     }
407   }
408 
409   if (!cid_it.Status().ok()) {
410     PERFETTO_DFATAL_OR_ELOG("Invalid iterator: %s",
411                             cid_it.Status().message().c_str());
412     return {};
413   }
414 
415   return tracker;
416 }
417 
418 // Builds the |perftools.profiles.Profile| proto.
419 class GProfileBuilder {
420  public:
GProfileBuilder(const LocationTracker & locations,trace_processor::StringPool * interner)421   GProfileBuilder(const LocationTracker& locations,
422                   trace_processor::StringPool* interner)
423       : locations_(locations), interner_(interner) {
424     // The pprof format requires the first entry in the string table to be the
425     // empty string.
426     int64_t empty_id = ToStringTableId(StringId::Null());
427     PERFETTO_CHECK(empty_id == 0);
428   }
429 
WriteSampleTypes(const std::vector<std::pair<std::string,std::string>> & sample_types)430   void WriteSampleTypes(
431       const std::vector<std::pair<std::string, std::string>>& sample_types) {
432     for (const auto& st : sample_types) {
433       auto* sample_type = result_->add_sample_type();
434       sample_type->set_type(
435           ToStringTableId(interner_->InternString(base::StringView(st.first))));
436       sample_type->set_unit(ToStringTableId(
437           interner_->InternString(base::StringView(st.second))));
438     }
439   }
440 
AddSample(const protozero::PackedVarInt & values,int64_t callstack_id)441   bool AddSample(const protozero::PackedVarInt& values, int64_t callstack_id) {
442     const auto& location_ids = locations_.LocationsForCallstack(callstack_id);
443     if (location_ids.empty()) {
444       PERFETTO_DFATAL_OR_ELOG(
445           "Failed to find frames for callstack id %" PRIi64 "", callstack_id);
446       return false;
447     }
448 
449     // LocationTracker stores location lists root-first, but the pprof format
450     // requires leaf-first.
451     protozero::PackedVarInt packed_locs;
452     for (auto it = location_ids.rbegin(); it != location_ids.rend(); ++it)
453       packed_locs.Append(ToPprofId(*it));
454 
455     auto* gsample = result_->add_sample();
456     gsample->set_value(values);
457     gsample->set_location_id(packed_locs);
458 
459     // Remember the locations s.t. we only serialize the referenced ones.
460     seen_locations_.insert(location_ids.cbegin(), location_ids.cend());
461     return true;
462   }
463 
CompleteProfile(trace_processor::TraceProcessor * tp)464   std::string CompleteProfile(trace_processor::TraceProcessor* tp) {
465     std::set<int64_t> seen_mappings;
466     std::set<int64_t> seen_functions;
467 
468     if (!WriteLocations(&seen_mappings, &seen_functions))
469       return {};
470     if (!WriteFunctions(seen_functions))
471       return {};
472     if (!WriteMappings(tp, seen_mappings))
473       return {};
474 
475     WriteStringTable();
476     return result_.SerializeAsString();
477   }
478 
479  private:
480   // Serializes the Profile.Location entries referenced by this profile.
WriteLocations(std::set<int64_t> * seen_mappings,std::set<int64_t> * seen_functions)481   bool WriteLocations(std::set<int64_t>* seen_mappings,
482                       std::set<int64_t>* seen_functions) {
483     const std::unordered_map<Location, int64_t>& locations =
484         locations_.AllLocations();
485 
486     size_t written_locations = 0;
487     for (const auto& loc_and_id : locations) {
488       const auto& loc = loc_and_id.first;
489       int64_t id = loc_and_id.second;
490 
491       if (seen_locations_.find(id) == seen_locations_.end())
492         continue;
493 
494       written_locations += 1;
495       seen_mappings->emplace(loc.mapping_id);
496 
497       auto* glocation = result_->add_location();
498       glocation->set_id(ToPprofId(id));
499       glocation->set_mapping_id(ToPprofId(loc.mapping_id));
500 
501       if (!loc.inlined_functions.empty()) {
502         for (const auto& line : loc.inlined_functions) {
503           seen_functions->insert(line.function_id);
504 
505           auto* gline = glocation->add_line();
506           gline->set_function_id(ToPprofId(line.function_id));
507           gline->set_line(line.line_no);
508         }
509       } else {
510         seen_functions->insert(loc.single_function_id);
511 
512         glocation->add_line()->set_function_id(
513             ToPprofId(loc.single_function_id));
514       }
515     }
516 
517     if (written_locations != seen_locations_.size()) {
518       PERFETTO_DFATAL_OR_ELOG(
519           "Found only %zu/%zu locations during serialization.",
520           written_locations, seen_locations_.size());
521       return false;
522     }
523     return true;
524   }
525 
526   // Serializes the Profile.Function entries referenced by this profile.
WriteFunctions(const std::set<int64_t> & seen_functions)527   bool WriteFunctions(const std::set<int64_t>& seen_functions) {
528     const std::unordered_map<Function, int64_t>& functions =
529         locations_.AllFunctions();
530 
531     size_t written_functions = 0;
532     for (const auto& func_and_id : functions) {
533       const auto& func = func_and_id.first;
534       int64_t id = func_and_id.second;
535 
536       if (seen_functions.find(id) == seen_functions.end())
537         continue;
538 
539       written_functions += 1;
540 
541       auto* gfunction = result_->add_function();
542       gfunction->set_id(ToPprofId(id));
543       gfunction->set_name(ToStringTableId(func.name_id));
544       gfunction->set_system_name(ToStringTableId(func.system_name_id));
545       if (!func.filename_id.is_null())
546         gfunction->set_filename(ToStringTableId(func.filename_id));
547     }
548 
549     if (written_functions != seen_functions.size()) {
550       PERFETTO_DFATAL_OR_ELOG(
551           "Found only %zu/%zu functions during serialization.",
552           written_functions, seen_functions.size());
553       return false;
554     }
555     return true;
556   }
557 
558   // Serializes the Profile.Mapping entries referenced by this profile.
WriteMappings(trace_processor::TraceProcessor * tp,const std::set<int64_t> & seen_mappings)559   bool WriteMappings(trace_processor::TraceProcessor* tp,
560                      const std::set<int64_t>& seen_mappings) {
561     Iterator mapping_it = tp->ExecuteQuery(
562         "SELECT id, exact_offset, start, end, name "
563         "FROM stack_profile_mapping;");
564     size_t mappings_no = 0;
565     while (mapping_it.Next()) {
566       int64_t id = mapping_it.Get(0).AsLong();
567       if (seen_mappings.find(id) == seen_mappings.end())
568         continue;
569       ++mappings_no;
570       auto interned_filename = ToStringTableId(
571           interner_->InternString(mapping_it.Get(4).AsString()));
572       auto* gmapping = result_->add_mapping();
573       gmapping->set_id(ToPprofId(id));
574       // Do not set the build_id here to avoid downstream services
575       // trying to symbolize (e.g. b/141735056)
576       gmapping->set_file_offset(
577           static_cast<uint64_t>(mapping_it.Get(1).AsLong()));
578       gmapping->set_memory_start(
579           static_cast<uint64_t>(mapping_it.Get(2).AsLong()));
580       gmapping->set_memory_limit(
581           static_cast<uint64_t>(mapping_it.Get(3).AsLong()));
582       gmapping->set_filename(interned_filename);
583     }
584     if (!mapping_it.Status().ok()) {
585       PERFETTO_DFATAL_OR_ELOG("Invalid mapping iterator: %s",
586                               mapping_it.Status().message().c_str());
587       return false;
588     }
589     if (mappings_no != seen_mappings.size()) {
590       PERFETTO_DFATAL_OR_ELOG("Missing mappings.");
591       return false;
592     }
593     return true;
594   }
595 
WriteStringTable()596   void WriteStringTable() {
597     for (StringId id : string_table_) {
598       trace_processor::NullTermStringView s = interner_->Get(id);
599       result_->add_string_table(s.data(), s.size());
600     }
601   }
602 
ToStringTableId(StringId interned_id)603   int64_t ToStringTableId(StringId interned_id) {
604     auto it = interning_remapper_.find(interned_id);
605     if (it == interning_remapper_.end()) {
606       int64_t table_id = static_cast<int64_t>(string_table_.size());
607       string_table_.push_back(interned_id);
608       bool inserted = false;
609       std::tie(it, inserted) =
610           interning_remapper_.emplace(interned_id, table_id);
611       PERFETTO_DCHECK(inserted);
612     }
613     return it->second;
614   }
615 
616   // Contains all locations, lines, functions (in memory):
617   const LocationTracker& locations_;
618 
619   // String interner, strings referenced by LocationTracker are already
620   // interned. The new internings will come from mappings, and sample types.
621   trace_processor::StringPool* interner_;
622 
623   // The profile format uses the repeated string_table field's index as an
624   // implicit id, so these structures remap the interned strings into sequential
625   // ids. Only the strings referenced by this GProfileBuilder instance will be
626   // added to the table.
627   std::unordered_map<StringId, int64_t> interning_remapper_;
628   std::vector<StringId> string_table_;
629 
630   // Profile proto being serialized.
631   protozero::HeapBuffered<third_party::perftools::profiles::pbzero::Profile>
632       result_;
633 
634   // Set of locations referenced by the added samples.
635   std::set<int64_t> seen_locations_;
636 };
637 
638 namespace heap_profile {
639 struct View {
640   const char* type;
641   const char* unit;
642   const char* aggregator;
643   const char* filter;
644 };
645 
646 const View kMallocViews[] = {
647     {"Total malloc count", "count", "sum(count)", "size >= 0"},
648     {"Total malloc size", "bytes", "SUM(size)", "size >= 0"},
649     {"Unreleased malloc count", "count", "SUM(count)", nullptr},
650     {"Unreleased malloc size", "bytes", "SUM(size)", nullptr}};
651 
652 const View kGenericViews[] = {
653     {"Total count", "count", "sum(count)", "size >= 0"},
654     {"Total size", "bytes", "SUM(size)", "size >= 0"},
655     {"Unreleased count", "count", "SUM(count)", nullptr},
656     {"Unreleased size", "bytes", "SUM(size)", nullptr}};
657 
658 const View kJavaSamplesViews[] = {
659     {"Total allocation count", "count", "SUM(count)", nullptr},
660     {"Total allocation size", "bytes", "SUM(size)", nullptr}};
661 
VerifyPIDStats(trace_processor::TraceProcessor * tp,uint64_t pid)662 static bool VerifyPIDStats(trace_processor::TraceProcessor* tp, uint64_t pid) {
663   bool success = true;
664   std::optional<int64_t> stat =
665       GetStatsEntry(tp, "heapprofd_buffer_corrupted", std::make_optional(pid));
666   if (!stat.has_value()) {
667     PERFETTO_DFATAL_OR_ELOG("Failed to get heapprofd_buffer_corrupted stat");
668   } else if (stat.value() > 0) {
669     success = false;
670     PERFETTO_ELOG("WARNING: The profile for %" PRIu64
671                   " ended early due to a buffer corruption."
672                   " THIS IS ALWAYS A BUG IN HEAPPROFD OR"
673                   " CLIENT MEMORY CORRUPTION.",
674                   pid);
675   }
676   stat = GetStatsEntry(tp, "heapprofd_buffer_overran", std::make_optional(pid));
677   if (!stat.has_value()) {
678     PERFETTO_DFATAL_OR_ELOG("Failed to get heapprofd_buffer_overran stat");
679   } else if (stat.value() > 0) {
680     success = false;
681     PERFETTO_ELOG("WARNING: The profile for %" PRIu64
682                   " ended early due to a buffer overrun.",
683                   pid);
684   }
685 
686   stat = GetStatsEntry(tp, "heapprofd_rejected_concurrent", pid);
687   if (!stat.has_value()) {
688     PERFETTO_DFATAL_OR_ELOG("Failed to get heapprofd_rejected_concurrent stat");
689   } else if (stat.value() > 0) {
690     success = false;
691     PERFETTO_ELOG("WARNING: The profile for %" PRIu64
692                   " was rejected due to a concurrent profile.",
693                   pid);
694   }
695   return success;
696 }
697 
BuildViewIterators(trace_processor::TraceProcessor * tp,uint64_t upid,uint64_t ts,const char * heap_name,const std::vector<View> & views)698 static std::vector<Iterator> BuildViewIterators(
699     trace_processor::TraceProcessor* tp,
700     uint64_t upid,
701     uint64_t ts,
702     const char* heap_name,
703     const std::vector<View>& views) {
704   std::vector<Iterator> view_its;
705   for (const View& v : views) {
706     std::string query = "SELECT hpa.callsite_id ";
707     query +=
708         ", " + std::string(v.aggregator) + " FROM heap_profile_allocation hpa ";
709     // TODO(fmayer): Figure out where negative callsite_id comes from.
710     query += "WHERE hpa.callsite_id >= 0 ";
711     query += "AND hpa.upid = " + std::to_string(upid) + " ";
712     query += "AND hpa.ts <= " + std::to_string(ts) + " ";
713     query += "AND hpa.heap_name = '" + std::string(heap_name) + "' ";
714     if (v.filter)
715       query += "AND " + std::string(v.filter) + " ";
716     query += "GROUP BY hpa.callsite_id;";
717     view_its.emplace_back(tp->ExecuteQuery(query));
718   }
719   return view_its;
720 }
721 
WriteAllocations(GProfileBuilder * builder,std::vector<Iterator> * view_its)722 static bool WriteAllocations(GProfileBuilder* builder,
723                              std::vector<Iterator>* view_its) {
724   for (;;) {
725     bool all_next = true;
726     bool any_next = false;
727     for (size_t i = 0; i < view_its->size(); ++i) {
728       Iterator& it = (*view_its)[i];
729       bool next = it.Next();
730       if (!it.Status().ok()) {
731         PERFETTO_DFATAL_OR_ELOG("Invalid view iterator: %s",
732                                 it.Status().message().c_str());
733         return false;
734       }
735       all_next = all_next && next;
736       any_next = any_next || next;
737     }
738 
739     if (!all_next) {
740       PERFETTO_CHECK(!any_next);
741       break;
742     }
743 
744     protozero::PackedVarInt sample_values;
745     int64_t callstack_id = -1;
746     for (size_t i = 0; i < view_its->size(); ++i) {
747       if (i == 0) {
748         callstack_id = (*view_its)[i].Get(0).AsLong();
749       } else if (callstack_id != (*view_its)[i].Get(0).AsLong()) {
750         PERFETTO_DFATAL_OR_ELOG("Wrong callstack.");
751         return false;
752       }
753       sample_values.Append((*view_its)[i].Get(1).AsLong());
754     }
755 
756     if (!builder->AddSample(sample_values, callstack_id))
757       return false;
758   }
759   return true;
760 }
761 
TraceToHeapPprof(trace_processor::TraceProcessor * tp,std::vector<SerializedProfile> * output,bool annotate_frames,uint64_t target_pid,const std::vector<uint64_t> & target_timestamps)762 static bool TraceToHeapPprof(trace_processor::TraceProcessor* tp,
763                              std::vector<SerializedProfile>* output,
764                              bool annotate_frames,
765                              uint64_t target_pid,
766                              const std::vector<uint64_t>& target_timestamps) {
767   trace_processor::StringPool interner;
768   LocationTracker locations =
769       PreprocessLocations(tp, &interner, annotate_frames);
770 
771   bool any_fail = false;
772   Iterator it = tp->ExecuteQuery(
773       "select distinct hpa.upid, hpa.ts, p.pid, hpa.heap_name "
774       "from heap_profile_allocation hpa, "
775       "process p where p.upid = hpa.upid;");
776   while (it.Next()) {
777     GProfileBuilder builder(locations, &interner);
778     uint64_t upid = static_cast<uint64_t>(it.Get(0).AsLong());
779     uint64_t ts = static_cast<uint64_t>(it.Get(1).AsLong());
780     uint64_t profile_pid = static_cast<uint64_t>(it.Get(2).AsLong());
781     const char* heap_name = it.Get(3).AsString();
782     if ((target_pid > 0 && profile_pid != target_pid) ||
783         (!target_timestamps.empty() &&
784          std::find(target_timestamps.begin(), target_timestamps.end(), ts) ==
785              target_timestamps.end())) {
786       continue;
787     }
788 
789     if (!VerifyPIDStats(tp, profile_pid))
790       any_fail = true;
791 
792     std::vector<View> views;
793     if (base::StringView(heap_name) == "libc.malloc") {
794       views.assign(std::begin(kMallocViews), std::end(kMallocViews));
795     } else if (base::StringView(heap_name) == "com.android.art") {
796       views.assign(std::begin(kJavaSamplesViews), std::end(kJavaSamplesViews));
797     } else {
798       views.assign(std::begin(kGenericViews), std::end(kGenericViews));
799     }
800 
801     std::vector<std::pair<std::string, std::string>> sample_types;
802     for (const View& view : views) {
803       sample_types.emplace_back(view.type, view.unit);
804     }
805     builder.WriteSampleTypes(sample_types);
806 
807     std::vector<Iterator> view_its =
808         BuildViewIterators(tp, upid, ts, heap_name, views);
809     std::string profile_proto;
810     if (WriteAllocations(&builder, &view_its)) {
811       profile_proto = builder.CompleteProfile(tp);
812     }
813     output->emplace_back(
814         SerializedProfile{ProfileType::kHeapProfile, profile_pid,
815                           std::move(profile_proto), heap_name});
816   }
817 
818   if (!it.Status().ok()) {
819     PERFETTO_DFATAL_OR_ELOG("Invalid iterator: %s",
820                             it.Status().message().c_str());
821     return false;
822   }
823   if (any_fail) {
824     PERFETTO_ELOG(
825         "One or more of your profiles had an issue. Please consult "
826         "https://perfetto.dev/docs/data-sources/"
827         "native-heap-profiler#troubleshooting");
828   }
829   return true;
830 }
831 }  // namespace heap_profile
832 
833 namespace perf_profile {
834 struct ProcessInfo {
835   uint64_t pid;
836   std::vector<uint64_t> utids;
837 };
838 
839 // Returns a map of upid -> {pid, utids[]} for sampled processes.
GetProcessMap(trace_processor::TraceProcessor * tp)840 static std::map<uint64_t, ProcessInfo> GetProcessMap(
841     trace_processor::TraceProcessor* tp) {
842   Iterator it = tp->ExecuteQuery(
843       "select distinct process.upid, process.pid, thread.utid from perf_sample "
844       "join thread using (utid) join process using (upid) where callsite_id is "
845       "not null order by process.upid asc");
846   std::map<uint64_t, ProcessInfo> process_map;
847   while (it.Next()) {
848     uint64_t upid = static_cast<uint64_t>(it.Get(0).AsLong());
849     uint64_t pid = static_cast<uint64_t>(it.Get(1).AsLong());
850     uint64_t utid = static_cast<uint64_t>(it.Get(2).AsLong());
851     process_map[upid].pid = pid;
852     process_map[upid].utids.push_back(utid);
853   }
854   if (!it.Status().ok()) {
855     PERFETTO_DFATAL_OR_ELOG("Invalid iterator: %s",
856                             it.Status().message().c_str());
857     return {};
858   }
859   return process_map;
860 }
861 
LogTracePerfEventIssues(trace_processor::TraceProcessor * tp)862 static void LogTracePerfEventIssues(trace_processor::TraceProcessor* tp) {
863   std::optional<int64_t> stat = GetStatsEntry(tp, "perf_samples_skipped");
864   if (!stat.has_value()) {
865     PERFETTO_DFATAL_OR_ELOG("Failed to look up perf_samples_skipped stat");
866   } else if (stat.value() > 0) {
867     PERFETTO_ELOG(
868         "Warning: the trace recorded %" PRIi64
869         " skipped samples, which otherwise matched the tracing config. This "
870         "would cause a process to be completely absent from the trace, but "
871         "does *not* imply data loss in any of the output profiles.",
872         stat.value());
873   }
874 
875   stat = GetStatsEntry(tp, "perf_samples_skipped_dataloss");
876   if (!stat.has_value()) {
877     PERFETTO_DFATAL_OR_ELOG(
878         "Failed to look up perf_samples_skipped_dataloss stat");
879   } else if (stat.value() > 0) {
880     PERFETTO_ELOG("DATA LOSS: the trace recorded %" PRIi64
881                   " lost perf samples (within traced_perf). This means that "
882                   "the trace is missing information, but it is not known "
883                   "which profile that affected.",
884                   stat.value());
885   }
886 
887   // Check if any per-cpu ringbuffers encountered dataloss (as recorded by the
888   // kernel).
889   Iterator it = tp->ExecuteQuery(
890       "select idx, value from stats where name == 'perf_cpu_lost_records' and "
891       "value > 0 order by idx asc");
892   while (it.Next()) {
893     PERFETTO_ELOG(
894         "DATA LOSS: during the trace, the per-cpu kernel ring buffer for cpu "
895         "%" PRIi64 " recorded %" PRIi64
896         " lost samples. This means that the trace is missing information, "
897         "but it is not known which profile that affected.",
898         static_cast<int64_t>(it.Get(0).AsLong()),
899         static_cast<int64_t>(it.Get(1).AsLong()));
900   }
901   if (!it.Status().ok()) {
902     PERFETTO_DFATAL_OR_ELOG("Invalid iterator: %s",
903                             it.Status().message().c_str());
904   }
905 }
906 
907 // TODO(rsavitski): decide whether errors in |AddSample| should result in an
908 // empty profile (and/or whether they should make the overall conversion
909 // unsuccessful). Furthermore, clarify the return value's semantics for both
910 // perf and heap profiles.
TraceToPerfPprof(trace_processor::TraceProcessor * tp,std::vector<SerializedProfile> * output,bool annotate_frames,uint64_t target_pid)911 static bool TraceToPerfPprof(trace_processor::TraceProcessor* tp,
912                              std::vector<SerializedProfile>* output,
913                              bool annotate_frames,
914                              uint64_t target_pid) {
915   trace_processor::StringPool interner;
916   LocationTracker locations =
917       PreprocessLocations(tp, &interner, annotate_frames);
918 
919   LogTracePerfEventIssues(tp);
920 
921   // Aggregate samples by upid when building profiles.
922   std::map<uint64_t, ProcessInfo> process_map = GetProcessMap(tp);
923   for (const auto& p : process_map) {
924     const ProcessInfo& process = p.second;
925 
926     if (target_pid != 0 && process.pid != target_pid)
927       continue;
928 
929     GProfileBuilder builder(locations, &interner);
930     builder.WriteSampleTypes({{"samples", "count"}});
931 
932     std::string query = "select callsite_id from perf_sample where utid in (" +
933                         AsCsvString(process.utids) +
934                         ") and callsite_id is not null order by ts asc;";
935 
936     protozero::PackedVarInt single_count_value;
937     single_count_value.Append(1);
938 
939     Iterator it = tp->ExecuteQuery(query);
940     while (it.Next()) {
941       int64_t callsite_id = static_cast<int64_t>(it.Get(0).AsLong());
942       builder.AddSample(single_count_value, callsite_id);
943     }
944     if (!it.Status().ok()) {
945       PERFETTO_DFATAL_OR_ELOG("Failed to iterate over samples: %s",
946                               it.Status().c_message());
947       return false;
948     }
949 
950     std::string profile_proto = builder.CompleteProfile(tp);
951     output->emplace_back(SerializedProfile{
952         ProfileType::kPerfProfile, process.pid, std::move(profile_proto), ""});
953   }
954   return true;
955 }
956 }  // namespace perf_profile
957 }  // namespace
958 
TraceToPprof(trace_processor::TraceProcessor * tp,std::vector<SerializedProfile> * output,ConversionMode mode,uint64_t flags,uint64_t pid,const std::vector<uint64_t> & timestamps)959 bool TraceToPprof(trace_processor::TraceProcessor* tp,
960                   std::vector<SerializedProfile>* output,
961                   ConversionMode mode,
962                   uint64_t flags,
963                   uint64_t pid,
964                   const std::vector<uint64_t>& timestamps) {
965   bool annotate_frames =
966       flags & static_cast<uint64_t>(ConversionFlags::kAnnotateFrames);
967   switch (mode) {
968     case (ConversionMode::kHeapProfile):
969       return heap_profile::TraceToHeapPprof(tp, output, annotate_frames, pid,
970                                             timestamps);
971     case (ConversionMode::kPerfProfile):
972       return perf_profile::TraceToPerfPprof(tp, output, annotate_frames, pid);
973   }
974   PERFETTO_FATAL("unknown conversion option");  // for gcc
975 }
976 
977 }  // namespace trace_to_text
978 }  // namespace perfetto
979