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 <cxxabi.h>
20 #include <inttypes.h>
21
22 #include <algorithm>
23 #include <map>
24 #include <set>
25 #include <utility>
26 #include <vector>
27
28 #include "tools/trace_to_text/utils.h"
29
30 #include "perfetto/base/logging.h"
31 #include "perfetto/base/time.h"
32 #include "perfetto/ext/base/string_utils.h"
33 #include "perfetto/ext/base/utils.h"
34 #include "perfetto/protozero/packed_repeated_fields.h"
35 #include "perfetto/protozero/scattered_heap_buffer.h"
36 #include "perfetto/trace_processor/trace_processor.h"
37
38 #include "src/profiling/symbolizer/symbolize_database.h"
39 #include "src/profiling/symbolizer/symbolizer.h"
40
41 #include "protos/perfetto/trace/trace.pbzero.h"
42 #include "protos/perfetto/trace/trace_packet.pbzero.h"
43 #include "protos/third_party/pprof/profile.pbzero.h"
44
45 namespace perfetto {
46 namespace trace_to_text {
47
48 namespace {
49
50 using ::protozero::proto_utils::kMessageLengthFieldSize;
51 using ::protozero::proto_utils::MakeTagLengthDelimited;
52 using ::protozero::proto_utils::WriteVarInt;
53
54 struct View {
55 const char* type;
56 const char* unit;
57 const char* aggregator;
58 const char* filter;
59 };
60
MaybeDemangle(std::string * name)61 void MaybeDemangle(std::string* name) {
62 int ignored;
63 char* data = abi::__cxa_demangle(name->c_str(), nullptr, nullptr, &ignored);
64 if (data) {
65 *name = data;
66 free(data);
67 }
68 }
69
70 const View kSpaceView{"space", "bytes", "SUM(size)", nullptr};
71 const View kAllocSpaceView{"alloc_space", "bytes", "SUM(size)", "size > 0"};
72 const View kAllocObjectsView{"alloc_objects", "count", "sum(count)",
73 "size > 0"};
74 const View kObjectsView{"objects", "count", "SUM(count)", nullptr};
75
76 const View kViews[] = {kAllocObjectsView, kObjectsView, kAllocSpaceView,
77 kSpaceView};
78
79 using Iterator = trace_processor::TraceProcessor::Iterator;
80
81 constexpr const char* kQueryProfiles =
82 "select distinct hpa.upid, hpa.ts, p.pid from heap_profile_allocation hpa, "
83 "process p where p.upid = hpa.upid;";
84
GetStatsInt(trace_processor::TraceProcessor * tp,const std::string & name,uint64_t pid)85 int64_t GetStatsInt(trace_processor::TraceProcessor* tp,
86 const std::string& name,
87 uint64_t pid) {
88 auto it = tp->ExecuteQuery("SELECT value from stats where name = '" + name +
89 "' AND idx = " + std::to_string(pid));
90 if (!it.Next()) {
91 if (!it.Status().ok()) {
92 PERFETTO_DFATAL_OR_ELOG("Invalid iterator: %s",
93 it.Status().message().c_str());
94 return -1;
95 }
96 // TODO(fmayer): Remove this case once we always get an entry in the stats
97 // table.
98 return 0;
99 }
100 return it.Get(0).AsLong();
101 }
102
VerifyPIDStats(trace_processor::TraceProcessor * tp,uint64_t pid)103 bool VerifyPIDStats(trace_processor::TraceProcessor* tp, uint64_t pid) {
104 bool success = true;
105 int64_t stat = GetStatsInt(tp, "heapprofd_buffer_corrupted", pid);
106 if (stat == -1) {
107 PERFETTO_DFATAL_OR_ELOG("Failed to get heapprofd_buffer_corrupted stat");
108 } else if (stat > 0) {
109 success = false;
110 PERFETTO_ELOG("WARNING: The profile for %" PRIu64
111 " ended early due to a buffer corruption."
112 " THIS IS ALWAYS A BUG IN HEAPPROFD OR"
113 " CLIENT MEMORY CORRUPTION.",
114 pid);
115 }
116 stat = GetStatsInt(tp, "heapprofd_buffer_overran", pid);
117 if (stat == -1) {
118 PERFETTO_DFATAL_OR_ELOG("Failed to get heapprofd_buffer_overran stat");
119 } else if (stat > 0) {
120 success = false;
121 PERFETTO_ELOG("WARNING: The profile for %" PRIu64
122 " ended early due to a buffer overrun.",
123 pid);
124 }
125
126 stat = GetStatsInt(tp, "heapprofd_rejected_concurrent", pid);
127 if (stat == -1) {
128 PERFETTO_DFATAL_OR_ELOG("Failed to get heapprofd_rejected_concurrent stat");
129 } else if (stat > 0) {
130 success = false;
131 PERFETTO_ELOG("WARNING: The profile for %" PRIu64
132 " was rejected due to a concurrent profile.",
133 pid);
134 }
135 return success;
136 }
137
138 struct Callsite {
139 int64_t id;
140 int64_t frame_id;
141 };
142
143 // Return map from callsite_id to list of frame_ids that make up the callstack.
GetCallsiteToFrames(trace_processor::TraceProcessor * tp)144 std::vector<std::vector<int64_t>> GetCallsiteToFrames(
145 trace_processor::TraceProcessor* tp) {
146 Iterator count_it =
147 tp->ExecuteQuery("select count(*) from stack_profile_callsite;");
148 if (!count_it.Next()) {
149 PERFETTO_DFATAL_OR_ELOG("Failed to get number of callsites: %s",
150 count_it.Status().message().c_str());
151 return {};
152 }
153 int64_t count = count_it.Get(0).AsLong();
154
155 Iterator it = tp->ExecuteQuery(
156 "select id, parent_id, frame_id from stack_profile_callsite order by "
157 "depth;");
158 std::vector<std::vector<int64_t>> result(static_cast<size_t>(count));
159 while (it.Next()) {
160 int64_t id = it.Get(0).AsLong();
161 int64_t frame_id = it.Get(2).AsLong();
162 std::vector<int64_t>& path = result[static_cast<size_t>(id)];
163 path.push_back(frame_id);
164
165 auto parent_id_value = it.Get(1);
166 if (!parent_id_value.is_null()) {
167 const std::vector<int64_t>& parent_path =
168 result[static_cast<size_t>(parent_id_value.AsLong())];
169 path.insert(path.end(), parent_path.begin(), parent_path.end());
170 }
171 }
172
173 if (!it.Status().ok()) {
174 PERFETTO_DFATAL_OR_ELOG("Invalid iterator: %s",
175 it.Status().message().c_str());
176 return {};
177 }
178 return result;
179 }
180
181 struct Line {
182 int64_t symbol_id;
183 uint32_t line_number;
184 };
185
GetSymbolSetIdToLines(trace_processor::TraceProcessor * tp)186 std::map<int64_t, std::vector<Line>> GetSymbolSetIdToLines(
187 trace_processor::TraceProcessor* tp) {
188 std::map<int64_t, std::vector<Line>> result;
189 Iterator it = tp->ExecuteQuery(
190 "SELECT symbol_set_id, id, line_number FROM stack_profile_symbol;");
191 while (it.Next()) {
192 int64_t symbol_set_id = it.Get(0).AsLong();
193 int64_t id = it.Get(1).AsLong();
194 int64_t line_number = it.Get(2).AsLong();
195 result[symbol_set_id].emplace_back(
196 Line{id, static_cast<uint32_t>(line_number)});
197 }
198
199 if (!it.Status().ok()) {
200 PERFETTO_DFATAL_OR_ELOG("Invalid iterator: %s",
201 it.Status().message().c_str());
202 return {};
203 }
204 return result;
205 }
206
207 class GProfileBuilder {
208 public:
GProfileBuilder(const std::vector<std::vector<int64_t>> & callsite_to_frames,const std::map<int64_t,std::vector<Line>> & symbol_set_id_to_lines,int64_t max_symbol_id)209 GProfileBuilder(
210 const std::vector<std::vector<int64_t>>& callsite_to_frames,
211 const std::map<int64_t, std::vector<Line>>& symbol_set_id_to_lines,
212 int64_t max_symbol_id)
213 : callsite_to_frames_(callsite_to_frames),
214 symbol_set_id_to_lines_(symbol_set_id_to_lines),
215 max_symbol_id_(max_symbol_id) {
216 // The pprof format expects the first entry in the string table to be the
217 // empty string.
218 int64_t empty_id = Intern("");
219 PERFETTO_CHECK(empty_id == 0);
220 }
221
BuildViewIterators(trace_processor::TraceProcessor * tp,uint64_t upid,uint64_t ts)222 std::vector<Iterator> BuildViewIterators(trace_processor::TraceProcessor* tp,
223 uint64_t upid,
224 uint64_t ts) {
225 std::vector<Iterator> view_its;
226 for (size_t i = 0; i < base::ArraySize(kViews); ++i) {
227 const View& v = kViews[i];
228 std::string query = "SELECT hpa.callsite_id ";
229 query += ", " + std::string(v.aggregator) +
230 " FROM heap_profile_allocation hpa ";
231 // TODO(fmayer): Figure out where negative callsite_id comes from.
232 query += "WHERE hpa.callsite_id >= 0 ";
233 query += "AND hpa.upid = " + std::to_string(upid) + " ";
234 query += "AND hpa.ts <= " + std::to_string(ts) + " ";
235 if (v.filter)
236 query += "AND " + std::string(v.filter) + " ";
237 query += "GROUP BY hpa.callsite_id;";
238 view_its.emplace_back(tp->ExecuteQuery(query));
239 }
240 return view_its;
241 }
242
WriteAllocations(std::vector<Iterator> * view_its,std::set<int64_t> * seen_frames)243 bool WriteAllocations(std::vector<Iterator>* view_its,
244 std::set<int64_t>* seen_frames) {
245 for (;;) {
246 bool all_next = true;
247 bool any_next = false;
248 for (size_t i = 0; i < base::ArraySize(kViews); ++i) {
249 Iterator& it = (*view_its)[i];
250 bool next = it.Next();
251 if (!it.Status().ok()) {
252 PERFETTO_DFATAL_OR_ELOG("Invalid view iterator: %s",
253 it.Status().message().c_str());
254 return false;
255 }
256 all_next = all_next && next;
257 any_next = any_next || next;
258 }
259
260 if (!all_next) {
261 PERFETTO_DCHECK(!any_next);
262 break;
263 }
264
265 auto* gsample = result_->add_sample();
266 protozero::PackedVarInt sample_values;
267 for (size_t i = 0; i < base::ArraySize(kViews); ++i) {
268 int64_t callstack_id = (*view_its)[i].Get(0).AsLong();
269 if (i == 0) {
270 auto frames = FramesForCallstack(callstack_id);
271 if (frames.empty())
272 return false;
273 protozero::PackedVarInt location_ids;
274 for (int64_t frame : frames)
275 location_ids.Append(ToPprofId(frame));
276 gsample->set_location_id(location_ids);
277 seen_frames->insert(frames.cbegin(), frames.cend());
278 } else {
279 if (callstack_id != (*view_its)[i].Get(0).AsLong()) {
280 PERFETTO_DFATAL_OR_ELOG("Wrong callstack.");
281 return false;
282 }
283 }
284 sample_values.Append((*view_its)[i].Get(1).AsLong());
285 }
286 gsample->set_value(sample_values);
287 }
288 return true;
289 }
290
WriteMappings(trace_processor::TraceProcessor * tp,const std::set<int64_t> seen_mappings)291 bool WriteMappings(trace_processor::TraceProcessor* tp,
292 const std::set<int64_t> seen_mappings) {
293 Iterator mapping_it = tp->ExecuteQuery(
294 "SELECT id, exact_offset, start, end, name "
295 "FROM stack_profile_mapping;");
296 size_t mappings_no = 0;
297 while (mapping_it.Next()) {
298 int64_t id = mapping_it.Get(0).AsLong();
299 if (seen_mappings.find(id) == seen_mappings.end())
300 continue;
301 ++mappings_no;
302 auto interned_filename = Intern(mapping_it.Get(4).AsString());
303 auto* gmapping = result_->add_mapping();
304 gmapping->set_id(ToPprofId(id));
305 // Do not set the build_id here to avoid downstream services
306 // trying to symbolize (e.g. b/141735056)
307 gmapping->set_file_offset(
308 static_cast<uint64_t>(mapping_it.Get(1).AsLong()));
309 gmapping->set_memory_start(
310 static_cast<uint64_t>(mapping_it.Get(2).AsLong()));
311 gmapping->set_memory_limit(
312 static_cast<uint64_t>(mapping_it.Get(3).AsLong()));
313 gmapping->set_filename(interned_filename);
314 }
315 if (!mapping_it.Status().ok()) {
316 PERFETTO_DFATAL_OR_ELOG("Invalid mapping iterator: %s",
317 mapping_it.Status().message().c_str());
318 return false;
319 }
320 if (mappings_no != seen_mappings.size()) {
321 PERFETTO_DFATAL_OR_ELOG("Missing mappings.");
322 return false;
323 }
324 return true;
325 }
326
WriteSymbols(trace_processor::TraceProcessor * tp,const std::set<int64_t> & seen_symbol_ids)327 bool WriteSymbols(trace_processor::TraceProcessor* tp,
328 const std::set<int64_t>& seen_symbol_ids) {
329 Iterator symbol_it = tp->ExecuteQuery(
330 "SELECT id, name, source_file FROM stack_profile_symbol");
331 size_t symbols_no = 0;
332 while (symbol_it.Next()) {
333 int64_t id = symbol_it.Get(0).AsLong();
334 if (seen_symbol_ids.find(id) == seen_symbol_ids.end())
335 continue;
336 ++symbols_no;
337 const std::string& name = symbol_it.Get(1).AsString();
338 std::string demangled_name = name;
339 MaybeDemangle(&demangled_name);
340
341 auto interned_demangled_name = Intern(demangled_name);
342 auto interned_system_name = Intern(name);
343 auto interned_filename = Intern(symbol_it.Get(2).AsString());
344 auto* gfunction = result_->add_function();
345 gfunction->set_id(ToPprofId(id));
346 gfunction->set_name(interned_demangled_name);
347 gfunction->set_system_name(interned_system_name);
348 gfunction->set_filename(interned_filename);
349 }
350
351 if (!symbol_it.Status().ok()) {
352 PERFETTO_DFATAL_OR_ELOG("Invalid iterator: %s",
353 symbol_it.Status().message().c_str());
354 return false;
355 }
356
357 if (symbols_no != seen_symbol_ids.size()) {
358 PERFETTO_DFATAL_OR_ELOG("Missing symbols.");
359 return false;
360 }
361 return true;
362 }
363
WriteFrames(trace_processor::TraceProcessor * tp,const std::set<int64_t> & seen_frames,std::set<int64_t> * seen_mappings,std::set<int64_t> * seen_symbol_ids)364 bool WriteFrames(trace_processor::TraceProcessor* tp,
365 const std::set<int64_t>& seen_frames,
366 std::set<int64_t>* seen_mappings,
367 std::set<int64_t>* seen_symbol_ids) {
368 Iterator frame_it = tp->ExecuteQuery(
369 "SELECT spf.id, spf.name, spf.mapping, spf.rel_pc, spf.symbol_set_id "
370 "FROM stack_profile_frame spf;");
371 size_t frames_no = 0;
372 while (frame_it.Next()) {
373 int64_t frame_id = frame_it.Get(0).AsLong();
374 if (seen_frames.find(frame_id) == seen_frames.end())
375 continue;
376 frames_no++;
377 std::string frame_name = frame_it.Get(1).AsString();
378 int64_t mapping_id = frame_it.Get(2).AsLong();
379 int64_t rel_pc = frame_it.Get(3).AsLong();
380 base::Optional<int64_t> symbol_set_id;
381 if (!frame_it.Get(4).is_null())
382 symbol_set_id = frame_it.Get(4).AsLong();
383
384 seen_mappings->emplace(mapping_id);
385 auto* glocation = result_->add_location();
386 glocation->set_id(ToPprofId(frame_id));
387 glocation->set_mapping_id(ToPprofId(mapping_id));
388 // TODO(fmayer): Convert to abspc.
389 // relpc + (mapping.start - (mapping.exact_offset -
390 // mapping.start_offset)).
391 glocation->set_address(static_cast<uint64_t>(rel_pc));
392 if (symbol_set_id) {
393 for (const Line& line : LineForSymbolSetId(*symbol_set_id)) {
394 seen_symbol_ids->emplace(line.symbol_id);
395 auto* gline = glocation->add_line();
396 gline->set_line(line.line_number);
397 gline->set_function_id(ToPprofId(line.symbol_id));
398 }
399 } else {
400 int64_t synthesized_symbol_id = ++max_symbol_id_;
401 std::string demangled_name = frame_name;
402 MaybeDemangle(&demangled_name);
403
404 auto* gline = glocation->add_line();
405 gline->set_line(0);
406 gline->set_function_id(ToPprofId(synthesized_symbol_id));
407
408 auto interned_demangled_name = Intern(demangled_name);
409 auto interned_system_name = Intern(frame_name);
410 auto* gfunction = result_->add_function();
411 gfunction->set_id(ToPprofId(synthesized_symbol_id));
412 gfunction->set_name(interned_demangled_name);
413 gfunction->set_system_name(interned_system_name);
414 }
415 }
416
417 if (!frame_it.Status().ok()) {
418 PERFETTO_DFATAL_OR_ELOG("Invalid iterator: %s",
419 frame_it.Status().message().c_str());
420 return false;
421 }
422 if (frames_no != seen_frames.size()) {
423 PERFETTO_DFATAL_OR_ELOG("Missing frames.");
424 return false;
425 }
426 return true;
427 }
428
ToPprofId(int64_t id)429 uint64_t ToPprofId(int64_t id) {
430 PERFETTO_DCHECK(id >= 0);
431 return static_cast<uint64_t>(id) + 1;
432 }
433
WriteSampleTypes()434 void WriteSampleTypes() {
435 for (size_t i = 0; i < base::ArraySize(kViews); ++i) {
436 Intern(kViews[i].type);
437 Intern(kViews[i].unit);
438 }
439
440 for (size_t i = 0; i < base::ArraySize(kViews); ++i) {
441 auto* sample_type = result_->add_sample_type();
442 sample_type->set_type(Intern(kViews[i].type));
443 sample_type->set_unit(Intern(kViews[i].unit));
444 }
445 }
446
GenerateGProfile(trace_processor::TraceProcessor * tp,uint64_t upid,uint64_t ts)447 std::string GenerateGProfile(trace_processor::TraceProcessor* tp,
448 uint64_t upid,
449 uint64_t ts) {
450 std::set<int64_t> seen_frames;
451 std::set<int64_t> seen_mappings;
452 std::set<int64_t> seen_symbol_ids;
453
454 std::vector<Iterator> view_its = BuildViewIterators(tp, upid, ts);
455
456 WriteSampleTypes();
457 if (!WriteAllocations(&view_its, &seen_frames))
458 return {};
459 if (!WriteFrames(tp, seen_frames, &seen_mappings, &seen_symbol_ids))
460 return {};
461 if (!WriteMappings(tp, seen_mappings))
462 return {};
463 if (!WriteSymbols(tp, seen_symbol_ids))
464 return {};
465 return result_.SerializeAsString();
466 }
467
FramesForCallstack(int64_t callstack_id)468 const std::vector<int64_t>& FramesForCallstack(int64_t callstack_id) {
469 size_t callsite_idx = static_cast<size_t>(callstack_id);
470 PERFETTO_CHECK(callstack_id >= 0 &&
471 callsite_idx < callsite_to_frames_.size());
472 return callsite_to_frames_[callsite_idx];
473 }
474
LineForSymbolSetId(int64_t symbol_set_id)475 const std::vector<Line>& LineForSymbolSetId(int64_t symbol_set_id) {
476 auto it = symbol_set_id_to_lines_.find(symbol_set_id);
477 if (it == symbol_set_id_to_lines_.end())
478 return empty_line_vector_;
479 return it->second;
480 }
481
Intern(const std::string & s)482 int64_t Intern(const std::string& s) {
483 auto it = string_table_.find(s);
484 if (it == string_table_.end()) {
485 std::tie(it, std::ignore) =
486 string_table_.emplace(s, string_table_.size());
487 result_->add_string_table(s);
488 }
489 return it->second;
490 }
491
492 private:
493 protozero::HeapBuffered<third_party::perftools::profiles::pbzero::Profile>
494 result_;
495 std::map<std::string, int64_t> string_table_;
496 const std::vector<std::vector<int64_t>>& callsite_to_frames_;
497 const std::map<int64_t, std::vector<Line>>& symbol_set_id_to_lines_;
498 const std::vector<Line> empty_line_vector_;
499 int64_t max_symbol_id_;
500 };
501
502 } // namespace
503
TraceToPprof(std::istream * input,std::vector<SerializedProfile> * output,profiling::Symbolizer * symbolizer,uint64_t pid,const std::vector<uint64_t> & timestamps)504 bool TraceToPprof(std::istream* input,
505 std::vector<SerializedProfile>* output,
506 profiling::Symbolizer* symbolizer,
507 uint64_t pid,
508 const std::vector<uint64_t>& timestamps) {
509 trace_processor::Config config;
510 std::unique_ptr<trace_processor::TraceProcessor> tp =
511 trace_processor::TraceProcessor::CreateInstance(config);
512
513 if (!ReadTrace(tp.get(), input))
514 return false;
515
516 tp->NotifyEndOfFile();
517 return TraceToPprof(tp.get(), output, symbolizer, pid, timestamps);
518 }
519
TraceToPprof(trace_processor::TraceProcessor * tp,std::vector<SerializedProfile> * output,profiling::Symbolizer * symbolizer,uint64_t pid,const std::vector<uint64_t> & timestamps)520 bool TraceToPprof(trace_processor::TraceProcessor* tp,
521 std::vector<SerializedProfile>* output,
522 profiling::Symbolizer* symbolizer,
523 uint64_t pid,
524 const std::vector<uint64_t>& timestamps) {
525 if (symbolizer) {
526 profiling::SymbolizeDatabase(
527 tp, symbolizer, [tp](const std::string& trace_proto) {
528 std::unique_ptr<uint8_t[]> buf(new uint8_t[trace_proto.size()]);
529 memcpy(buf.get(), trace_proto.data(), trace_proto.size());
530 auto status = tp->Parse(std::move(buf), trace_proto.size());
531 if (!status.ok()) {
532 PERFETTO_DFATAL_OR_ELOG("Failed to parse: %s",
533 status.message().c_str());
534 return;
535 }
536 });
537 }
538
539 tp->NotifyEndOfFile();
540 auto max_symbol_id_it =
541 tp->ExecuteQuery("SELECT MAX(id) from stack_profile_symbol");
542 if (!max_symbol_id_it.Next()) {
543 PERFETTO_DFATAL_OR_ELOG("Failed to get max symbol set id: %s",
544 max_symbol_id_it.Status().message().c_str());
545 return false;
546 }
547
548 int64_t max_symbol_id = max_symbol_id_it.Get(0).AsLong();
549 const auto callsite_to_frames = GetCallsiteToFrames(tp);
550 const auto symbol_set_id_to_lines = GetSymbolSetIdToLines(tp);
551
552 bool any_fail = false;
553 Iterator it = tp->ExecuteQuery(kQueryProfiles);
554 while (it.Next()) {
555 GProfileBuilder builder(callsite_to_frames, symbol_set_id_to_lines,
556 max_symbol_id);
557 uint64_t upid = static_cast<uint64_t>(it.Get(0).AsLong());
558 uint64_t ts = static_cast<uint64_t>(it.Get(1).AsLong());
559 uint64_t profile_pid = static_cast<uint64_t>(it.Get(2).AsLong());
560 if ((pid > 0 && profile_pid != pid) ||
561 (!timestamps.empty() && std::find(timestamps.begin(), timestamps.end(),
562 ts) == timestamps.end())) {
563 continue;
564 }
565
566 if (!VerifyPIDStats(tp, pid))
567 any_fail = true;
568
569 std::string pid_query = "select pid from process where upid = ";
570 pid_query += std::to_string(upid) + ";";
571 Iterator pid_it = tp->ExecuteQuery(pid_query);
572 PERFETTO_CHECK(pid_it.Next());
573
574 std::string profile_proto = builder.GenerateGProfile(tp, upid, ts);
575 output->emplace_back(SerializedProfile{
576 static_cast<uint64_t>(pid_it.Get(0).AsLong()), profile_proto});
577 }
578 if (any_fail) {
579 PERFETTO_ELOG(
580 "One or more of your profiles had an issue. Please consult "
581 "https://docs.perfetto.dev/#/heapprofd?id=troubleshooting.");
582 }
583 if (!it.Status().ok()) {
584 PERFETTO_DFATAL_OR_ELOG("Invalid iterator: %s",
585 it.Status().message().c_str());
586 return false;
587 }
588 return true;
589 }
590
TraceToPprof(std::istream * input,std::vector<SerializedProfile> * output,uint64_t pid,const std::vector<uint64_t> & timestamps)591 bool TraceToPprof(std::istream* input,
592 std::vector<SerializedProfile>* output,
593 uint64_t pid,
594 const std::vector<uint64_t>& timestamps) {
595 return TraceToPprof(input, output, nullptr, pid, timestamps);
596 }
597
598 } // namespace trace_to_text
599 } // namespace perfetto
600