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