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 "tools/trace_to_text/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__anon820d025a0111::Function72 Function(StringId n, StringId s, StringId f)
73 : name_id(n), system_name_id(s), filename_id(f) {}
74
operator ==__anon820d025a0111::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__anon820d025a0111::Line86 Line(int64_t func, int64_t line) : function_id(func), line_no(line) {}
87
operator ==__anon820d025a0111::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__anon820d025a0111::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 ==__anon820d025a0111::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::Hash 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::Hash 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,base::Optional<uint64_t> idx=base::nullopt)162 base::Optional<int64_t> GetStatsEntry(
163 trace_processor::TraceProcessor* tp,
164 const std::string& name,
165 base::Optional<uint64_t> idx = base::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 base::nullopt;
176 }
177 // some stats are not present unless non-zero
178 return base::make_optional(0);
179 }
180 return base::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::__anon820d025a0211::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 base::Optional<int64_t> symbol_set_id =
344 c_it.Get(5).is_null() ? base::nullopt
345 : base::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 const View kSpaceView{"space", "bytes", "SUM(size)", nullptr};
646 const View kAllocSpaceView{"alloc_space", "bytes", "SUM(size)", "size >= 0"};
647 const View kAllocObjectsView{"alloc_objects", "count", "sum(count)",
648 "size >= 0"};
649 const View kObjectsView{"objects", "count", "SUM(count)", nullptr};
650
651 const View kViews[] = {kAllocObjectsView, kObjectsView, kAllocSpaceView,
652 kSpaceView};
653
VerifyPIDStats(trace_processor::TraceProcessor * tp,uint64_t pid)654 static bool VerifyPIDStats(trace_processor::TraceProcessor* tp, uint64_t pid) {
655 bool success = true;
656 base::Optional<int64_t> stat =
657 GetStatsEntry(tp, "heapprofd_buffer_corrupted", base::make_optional(pid));
658 if (!stat.has_value()) {
659 PERFETTO_DFATAL_OR_ELOG("Failed to get heapprofd_buffer_corrupted stat");
660 } else if (stat.value() > 0) {
661 success = false;
662 PERFETTO_ELOG("WARNING: The profile for %" PRIu64
663 " ended early due to a buffer corruption."
664 " THIS IS ALWAYS A BUG IN HEAPPROFD OR"
665 " CLIENT MEMORY CORRUPTION.",
666 pid);
667 }
668 stat =
669 GetStatsEntry(tp, "heapprofd_buffer_overran", base::make_optional(pid));
670 if (!stat.has_value()) {
671 PERFETTO_DFATAL_OR_ELOG("Failed to get heapprofd_buffer_overran stat");
672 } else if (stat.value() > 0) {
673 success = false;
674 PERFETTO_ELOG("WARNING: The profile for %" PRIu64
675 " ended early due to a buffer overrun.",
676 pid);
677 }
678
679 stat = GetStatsEntry(tp, "heapprofd_rejected_concurrent", pid);
680 if (!stat.has_value()) {
681 PERFETTO_DFATAL_OR_ELOG("Failed to get heapprofd_rejected_concurrent stat");
682 } else if (stat.value() > 0) {
683 success = false;
684 PERFETTO_ELOG("WARNING: The profile for %" PRIu64
685 " was rejected due to a concurrent profile.",
686 pid);
687 }
688 return success;
689 }
690
BuildViewIterators(trace_processor::TraceProcessor * tp,uint64_t upid,uint64_t ts,const char * heap_name)691 static std::vector<Iterator> BuildViewIterators(
692 trace_processor::TraceProcessor* tp,
693 uint64_t upid,
694 uint64_t ts,
695 const char* heap_name) {
696 std::vector<Iterator> view_its;
697 for (size_t i = 0; i < base::ArraySize(kViews); ++i) {
698 const View& v = kViews[i];
699 std::string query = "SELECT hpa.callsite_id ";
700 query +=
701 ", " + std::string(v.aggregator) + " FROM heap_profile_allocation hpa ";
702 // TODO(fmayer): Figure out where negative callsite_id comes from.
703 query += "WHERE hpa.callsite_id >= 0 ";
704 query += "AND hpa.upid = " + std::to_string(upid) + " ";
705 query += "AND hpa.ts <= " + std::to_string(ts) + " ";
706 query += "AND hpa.heap_name = '" + std::string(heap_name) + "' ";
707 if (v.filter)
708 query += "AND " + std::string(v.filter) + " ";
709 query += "GROUP BY hpa.callsite_id;";
710 view_its.emplace_back(tp->ExecuteQuery(query));
711 }
712 return view_its;
713 }
714
WriteAllocations(GProfileBuilder * builder,std::vector<Iterator> * view_its)715 static bool WriteAllocations(GProfileBuilder* builder,
716 std::vector<Iterator>* view_its) {
717 for (;;) {
718 bool all_next = true;
719 bool any_next = false;
720 for (size_t i = 0; i < base::ArraySize(kViews); ++i) {
721 Iterator& it = (*view_its)[i];
722 bool next = it.Next();
723 if (!it.Status().ok()) {
724 PERFETTO_DFATAL_OR_ELOG("Invalid view iterator: %s",
725 it.Status().message().c_str());
726 return false;
727 }
728 all_next = all_next && next;
729 any_next = any_next || next;
730 }
731
732 if (!all_next) {
733 PERFETTO_CHECK(!any_next);
734 break;
735 }
736
737 protozero::PackedVarInt sample_values;
738 int64_t callstack_id = -1;
739 for (size_t i = 0; i < base::ArraySize(kViews); ++i) {
740 if (i == 0) {
741 callstack_id = (*view_its)[i].Get(0).AsLong();
742 } else if (callstack_id != (*view_its)[i].Get(0).AsLong()) {
743 PERFETTO_DFATAL_OR_ELOG("Wrong callstack.");
744 return false;
745 }
746 sample_values.Append((*view_its)[i].Get(1).AsLong());
747 }
748
749 if (!builder->AddSample(sample_values, callstack_id))
750 return false;
751 }
752 return true;
753 }
754
TraceToHeapPprof(trace_processor::TraceProcessor * tp,std::vector<SerializedProfile> * output,bool annotate_frames,uint64_t target_pid,const std::vector<uint64_t> & target_timestamps)755 static bool TraceToHeapPprof(trace_processor::TraceProcessor* tp,
756 std::vector<SerializedProfile>* output,
757 bool annotate_frames,
758 uint64_t target_pid,
759 const std::vector<uint64_t>& target_timestamps) {
760 trace_processor::StringPool interner;
761 LocationTracker locations =
762 PreprocessLocations(tp, &interner, annotate_frames);
763
764 bool any_fail = false;
765 Iterator it = tp->ExecuteQuery(
766 "select distinct hpa.upid, hpa.ts, p.pid, hpa.heap_name "
767 "from heap_profile_allocation hpa, "
768 "process p where p.upid = hpa.upid;");
769 while (it.Next()) {
770 GProfileBuilder builder(locations, &interner);
771 uint64_t upid = static_cast<uint64_t>(it.Get(0).AsLong());
772 uint64_t ts = static_cast<uint64_t>(it.Get(1).AsLong());
773 uint64_t profile_pid = static_cast<uint64_t>(it.Get(2).AsLong());
774 const char* heap_name = it.Get(3).AsString();
775 if ((target_pid > 0 && profile_pid != target_pid) ||
776 (!target_timestamps.empty() &&
777 std::find(target_timestamps.begin(), target_timestamps.end(), ts) ==
778 target_timestamps.end())) {
779 continue;
780 }
781
782 if (!VerifyPIDStats(tp, profile_pid))
783 any_fail = true;
784
785 std::vector<std::pair<std::string, std::string>> sample_types;
786 for (size_t i = 0; i < base::ArraySize(kViews); ++i) {
787 sample_types.emplace_back(std::string(kViews[i].type),
788 std::string(kViews[i].unit));
789 }
790 builder.WriteSampleTypes(sample_types);
791
792 std::vector<Iterator> view_its =
793 BuildViewIterators(tp, upid, ts, heap_name);
794 std::string profile_proto;
795 if (WriteAllocations(&builder, &view_its)) {
796 profile_proto = builder.CompleteProfile(tp);
797 }
798 output->emplace_back(
799 SerializedProfile{ProfileType::kHeapProfile, profile_pid,
800 std::move(profile_proto), heap_name});
801 }
802
803 if (!it.Status().ok()) {
804 PERFETTO_DFATAL_OR_ELOG("Invalid iterator: %s",
805 it.Status().message().c_str());
806 return false;
807 }
808 if (any_fail) {
809 PERFETTO_ELOG(
810 "One or more of your profiles had an issue. Please consult "
811 "https://perfetto.dev/docs/data-sources/"
812 "native-heap-profiler#troubleshooting");
813 }
814 return true;
815 }
816 } // namespace heap_profile
817
818 namespace perf_profile {
819 struct ProcessInfo {
820 uint64_t pid;
821 std::vector<uint64_t> utids;
822 };
823
824 // Returns a map of upid -> {pid, utids[]} for sampled processes.
GetProcessMap(trace_processor::TraceProcessor * tp)825 static std::map<uint64_t, ProcessInfo> GetProcessMap(
826 trace_processor::TraceProcessor* tp) {
827 Iterator it = tp->ExecuteQuery(
828 "select distinct process.upid, process.pid, thread.utid from perf_sample "
829 "join thread using (utid) join process using (upid) where callsite_id is "
830 "not null order by process.upid asc");
831 std::map<uint64_t, ProcessInfo> process_map;
832 while (it.Next()) {
833 uint64_t upid = static_cast<uint64_t>(it.Get(0).AsLong());
834 uint64_t pid = static_cast<uint64_t>(it.Get(1).AsLong());
835 uint64_t utid = static_cast<uint64_t>(it.Get(2).AsLong());
836 process_map[upid].pid = pid;
837 process_map[upid].utids.push_back(utid);
838 }
839 if (!it.Status().ok()) {
840 PERFETTO_DFATAL_OR_ELOG("Invalid iterator: %s",
841 it.Status().message().c_str());
842 return {};
843 }
844 return process_map;
845 }
846
LogTracePerfEventIssues(trace_processor::TraceProcessor * tp)847 static void LogTracePerfEventIssues(trace_processor::TraceProcessor* tp) {
848 base::Optional<int64_t> stat = GetStatsEntry(tp, "perf_samples_skipped");
849 if (!stat.has_value()) {
850 PERFETTO_DFATAL_OR_ELOG("Failed to look up perf_samples_skipped stat");
851 } else if (stat.value() > 0) {
852 PERFETTO_ELOG(
853 "Warning: the trace recorded %" PRIi64
854 " skipped samples, which otherwise matched the tracing config. This "
855 "would cause a process to be completely absent from the trace, but "
856 "does *not* imply data loss in any of the output profiles.",
857 stat.value());
858 }
859
860 stat = GetStatsEntry(tp, "perf_samples_skipped_dataloss");
861 if (!stat.has_value()) {
862 PERFETTO_DFATAL_OR_ELOG(
863 "Failed to look up perf_samples_skipped_dataloss stat");
864 } else if (stat.value() > 0) {
865 PERFETTO_ELOG("DATA LOSS: the trace recorded %" PRIi64
866 " lost perf samples (within traced_perf). This means that "
867 "the trace is missing information, but it is not known "
868 "which profile that affected.",
869 stat.value());
870 }
871
872 // Check if any per-cpu ringbuffers encountered dataloss (as recorded by the
873 // kernel).
874 Iterator it = tp->ExecuteQuery(
875 "select idx, value from stats where name == 'perf_cpu_lost_records' and "
876 "value > 0 order by idx asc");
877 while (it.Next()) {
878 PERFETTO_ELOG(
879 "DATA LOSS: during the trace, the per-cpu kernel ring buffer for cpu "
880 "%" PRIi64 " recorded %" PRIi64
881 " lost samples. This means that the trace is missing information, "
882 "but it is not known which profile that affected.",
883 static_cast<int64_t>(it.Get(0).AsLong()),
884 static_cast<int64_t>(it.Get(1).AsLong()));
885 }
886 if (!it.Status().ok()) {
887 PERFETTO_DFATAL_OR_ELOG("Invalid iterator: %s",
888 it.Status().message().c_str());
889 }
890 }
891
892 // TODO(rsavitski): decide whether errors in |AddSample| should result in an
893 // empty profile (and/or whether they should make the overall conversion
894 // unsuccessful). Furthermore, clarify the return value's semantics for both
895 // perf and heap profiles.
TraceToPerfPprof(trace_processor::TraceProcessor * tp,std::vector<SerializedProfile> * output,bool annotate_frames,uint64_t target_pid)896 static bool TraceToPerfPprof(trace_processor::TraceProcessor* tp,
897 std::vector<SerializedProfile>* output,
898 bool annotate_frames,
899 uint64_t target_pid) {
900 trace_processor::StringPool interner;
901 LocationTracker locations =
902 PreprocessLocations(tp, &interner, annotate_frames);
903
904 LogTracePerfEventIssues(tp);
905
906 // Aggregate samples by upid when building profiles.
907 std::map<uint64_t, ProcessInfo> process_map = GetProcessMap(tp);
908 for (const auto& p : process_map) {
909 const ProcessInfo& process = p.second;
910
911 if (target_pid != 0 && process.pid != target_pid)
912 continue;
913
914 GProfileBuilder builder(locations, &interner);
915 builder.WriteSampleTypes({{"samples", "count"}});
916
917 std::string query = "select callsite_id from perf_sample where utid in (" +
918 AsCsvString(process.utids) +
919 ") and callsite_id is not null order by ts asc;";
920
921 protozero::PackedVarInt single_count_value;
922 single_count_value.Append(1);
923
924 Iterator it = tp->ExecuteQuery(query);
925 while (it.Next()) {
926 int64_t callsite_id = static_cast<int64_t>(it.Get(0).AsLong());
927 builder.AddSample(single_count_value, callsite_id);
928 }
929 if (!it.Status().ok()) {
930 PERFETTO_DFATAL_OR_ELOG("Failed to iterate over samples: %s",
931 it.Status().c_message());
932 return false;
933 }
934
935 std::string profile_proto = builder.CompleteProfile(tp);
936 output->emplace_back(SerializedProfile{
937 ProfileType::kPerfProfile, process.pid, std::move(profile_proto), ""});
938 }
939 return true;
940 }
941 } // namespace perf_profile
942 } // namespace
943
TraceToPprof(trace_processor::TraceProcessor * tp,std::vector<SerializedProfile> * output,ConversionMode mode,uint64_t flags,uint64_t pid,const std::vector<uint64_t> & timestamps)944 bool TraceToPprof(trace_processor::TraceProcessor* tp,
945 std::vector<SerializedProfile>* output,
946 ConversionMode mode,
947 uint64_t flags,
948 uint64_t pid,
949 const std::vector<uint64_t>& timestamps) {
950 bool annotate_frames =
951 flags & static_cast<uint64_t>(ConversionFlags::kAnnotateFrames);
952 switch (mode) {
953 case (ConversionMode::kHeapProfile):
954 return heap_profile::TraceToHeapPprof(tp, output, annotate_frames, pid,
955 timestamps);
956 case (ConversionMode::kPerfProfile):
957 return perf_profile::TraceToPerfPprof(tp, output, annotate_frames, pid);
958 }
959 PERFETTO_FATAL("unknown conversion option"); // for gcc
960 }
961
962 } // namespace trace_to_text
963 } // namespace perfetto
964