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__anonb9473e550111::Function74 Function(StringId n, StringId s, StringId f)
75 : name_id(n), system_name_id(s), filename_id(f) {}
76
operator ==__anonb9473e550111::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__anonb9473e550111::Line88 Line(int64_t func, int64_t line) : function_id(func), line_no(line) {}
89
operator ==__anonb9473e550111::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__anonb9473e550111::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 ==__anonb9473e550111::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::__anonb9473e550211::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