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