1 /*
2 * Copyright (C) 2016 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 <inttypes.h>
18
19 #include <memory>
20
21 #include "system/extras/simpleperf/report_sample.pb.h"
22
23 #include <google/protobuf/io/coded_stream.h>
24 #include <google/protobuf/io/zero_copy_stream_impl_lite.h>
25
26 #include "command.h"
27 #include "event_attr.h"
28 #include "event_type.h"
29 #include "record_file.h"
30 #include "thread_tree.h"
31 #include "utils.h"
32
33 namespace proto = simpleperf_report_proto;
34
35 namespace {
36
37 class ProtobufFileWriter : public google::protobuf::io::CopyingOutputStream {
38 public:
ProtobufFileWriter(FILE * out_fp)39 explicit ProtobufFileWriter(FILE* out_fp) : out_fp_(out_fp) {}
40
Write(const void * buffer,int size)41 bool Write(const void* buffer, int size) override {
42 return fwrite(buffer, size, 1, out_fp_) == 1;
43 }
44
45 private:
46 FILE* out_fp_;
47 };
48
49 class ProtobufFileReader : public google::protobuf::io::CopyingInputStream {
50 public:
ProtobufFileReader(FILE * in_fp)51 explicit ProtobufFileReader(FILE* in_fp) : in_fp_(in_fp) {}
52
Read(void * buffer,int size)53 int Read(void* buffer, int size) override {
54 return fread(buffer, 1, size, in_fp_);
55 }
56
57 private:
58 FILE* in_fp_;
59 };
60
61 class ReportSampleCommand : public Command {
62 public:
ReportSampleCommand()63 ReportSampleCommand()
64 : Command(
65 "report-sample", "report raw sample information in perf.data",
66 // clang-format off
67 "Usage: simpleperf report-sample [options]\n"
68 "--dump-protobuf-report <file>\n"
69 " Dump report file generated by\n"
70 " `simpleperf report-sample --protobuf -o <file>`.\n"
71 "-i <file> Specify path of record file, default is perf.data.\n"
72 "-o report_file_name Set report file name, default is stdout.\n"
73 "--protobuf Use protobuf format in report_sample.proto to output samples.\n"
74 " Need to set a report_file_name when using this option.\n"
75 "--show-callchain Print callchain samples.\n"
76 // clang-format on
77 ),
78 record_filename_("perf.data"),
79 show_callchain_(false),
80 use_protobuf_(false),
81 report_fp_(nullptr),
82 coded_os_(nullptr),
83 sample_count_(0),
84 lost_count_(0),
85 trace_offcpu_(false) {}
86
87 bool Run(const std::vector<std::string>& args) override;
88
89 private:
90 bool ParseOptions(const std::vector<std::string>& args);
91 bool DumpProtobufReport(const std::string& filename);
92 bool OpenRecordFile();
93 bool PrintMetaInfo();
94 bool ProcessRecord(std::unique_ptr<Record> record);
95 bool PrintSampleRecordInProtobuf(const SampleRecord& record);
96 bool GetCallEntry(const ThreadEntry* thread, bool in_kernel, uint64_t ip, bool omit_unknown_dso,
97 uint64_t* pvaddr_in_file, Dso** pdso, const Symbol** psymbol);
98 bool WriteRecordInProtobuf(proto::Record& proto_record);
99 bool PrintLostSituationInProtobuf();
100 bool PrintFileInfoInProtobuf();
101 bool PrintThreadInfoInProtobuf();
102 bool PrintSampleRecord(const SampleRecord& record);
103 void PrintLostSituation();
104
105 std::string record_filename_;
106 std::unique_ptr<RecordFileReader> record_file_reader_;
107 std::string dump_protobuf_report_file_;
108 bool show_callchain_;
109 bool use_protobuf_;
110 ThreadTree thread_tree_;
111 std::string report_filename_;
112 FILE* report_fp_;
113 google::protobuf::io::CodedOutputStream* coded_os_;
114 size_t sample_count_;
115 size_t lost_count_;
116 bool trace_offcpu_;
117 std::unique_ptr<ScopedEventTypes> scoped_event_types_;
118 std::vector<std::string> event_types_;
119 };
120
Run(const std::vector<std::string> & args)121 bool ReportSampleCommand::Run(const std::vector<std::string>& args) {
122 // 1. Parse options.
123 if (!ParseOptions(args)) {
124 return false;
125 }
126 // 2. Prepare report fp.
127 report_fp_ = stdout;
128 std::unique_ptr<FILE, decltype(&fclose)> fp(nullptr, fclose);
129 if (!report_filename_.empty()) {
130 const char* open_mode = "w";
131 if (!dump_protobuf_report_file_.empty() && use_protobuf_) {
132 open_mode = "wb";
133 }
134 fp.reset(fopen(report_filename_.c_str(), open_mode));
135 if (fp == nullptr) {
136 PLOG(ERROR) << "failed to open " << report_filename_;
137 return false;
138 }
139 report_fp_ = fp.get();
140 }
141
142 // 3. Dump protobuf report.
143 if (!dump_protobuf_report_file_.empty()) {
144 return DumpProtobufReport(dump_protobuf_report_file_);
145 }
146
147 // 4. Open record file.
148 if (!OpenRecordFile()) {
149 return false;
150 }
151 if (use_protobuf_) {
152 GOOGLE_PROTOBUF_VERIFY_VERSION;
153 } else {
154 thread_tree_.ShowMarkForUnknownSymbol();
155 thread_tree_.ShowIpForUnknownSymbol();
156 }
157
158 // 5. Prepare protobuf output stream.
159 std::unique_ptr<ProtobufFileWriter> protobuf_writer;
160 std::unique_ptr<google::protobuf::io::CopyingOutputStreamAdaptor> protobuf_os;
161 std::unique_ptr<google::protobuf::io::CodedOutputStream> protobuf_coded_os;
162 if (use_protobuf_) {
163 protobuf_writer.reset(new ProtobufFileWriter(report_fp_));
164 protobuf_os.reset(new google::protobuf::io::CopyingOutputStreamAdaptor(
165 protobuf_writer.get()));
166 protobuf_coded_os.reset(
167 new google::protobuf::io::CodedOutputStream(protobuf_os.get()));
168 coded_os_ = protobuf_coded_os.get();
169 }
170
171 // 6. Read record file, and print samples online.
172 if (!PrintMetaInfo()) {
173 return false;
174 }
175 if (!record_file_reader_->ReadDataSection(
176 [this](std::unique_ptr<Record> record) {
177 return ProcessRecord(std::move(record));
178 })) {
179 return false;
180 }
181
182 if (use_protobuf_) {
183 if (!PrintLostSituationInProtobuf()) {
184 return false;
185 }
186 if (!PrintFileInfoInProtobuf()) {
187 return false;
188 }
189 if (!PrintThreadInfoInProtobuf()) {
190 return false;
191 }
192 coded_os_->WriteLittleEndian32(0);
193 if (coded_os_->HadError()) {
194 LOG(ERROR) << "print protobuf report failed";
195 return false;
196 }
197 protobuf_coded_os.reset(nullptr);
198 } else {
199 PrintLostSituation();
200 fflush(report_fp_);
201 }
202 if (ferror(report_fp_) != 0) {
203 PLOG(ERROR) << "print report failed";
204 return false;
205 }
206 return true;
207 }
208
ParseOptions(const std::vector<std::string> & args)209 bool ReportSampleCommand::ParseOptions(const std::vector<std::string>& args) {
210 for (size_t i = 0; i < args.size(); ++i) {
211 if (args[i] == "--dump-protobuf-report") {
212 if (!NextArgumentOrError(args, &i)) {
213 return false;
214 }
215 dump_protobuf_report_file_ = args[i];
216 } else if (args[i] == "-i") {
217 if (!NextArgumentOrError(args, &i)) {
218 return false;
219 }
220 record_filename_ = args[i];
221 } else if (args[i] == "-o") {
222 if (!NextArgumentOrError(args, &i)) {
223 return false;
224 }
225 report_filename_ = args[i];
226 } else if (args[i] == "--protobuf") {
227 use_protobuf_ = true;
228 } else if (args[i] == "--show-callchain") {
229 show_callchain_ = true;
230 } else {
231 ReportUnknownOption(args, i);
232 return false;
233 }
234 }
235
236 if (use_protobuf_ && report_filename_.empty()) {
237 LOG(ERROR) << "please specify a report filename to write protobuf data";
238 return false;
239 }
240 return true;
241 }
242
DumpProtobufReport(const std::string & filename)243 bool ReportSampleCommand::DumpProtobufReport(const std::string& filename) {
244 GOOGLE_PROTOBUF_VERIFY_VERSION;
245 std::unique_ptr<FILE, decltype(&fclose)> fp(fopen(filename.c_str(), "rb"),
246 fclose);
247 if (fp == nullptr) {
248 PLOG(ERROR) << "failed to open " << filename;
249 return false;
250 }
251 ProtobufFileReader protobuf_reader(fp.get());
252 google::protobuf::io::CopyingInputStreamAdaptor adaptor(&protobuf_reader);
253 google::protobuf::io::CodedInputStream coded_is(&adaptor);
254 // map from file_id to max_symbol_id requested on the file.
255 std::unordered_map<uint32_t, int32_t> max_symbol_id_map;
256 // files[file_id] is the number of symbols in the file.
257 std::vector<uint32_t> files;
258 uint32_t max_message_size = 64 * (1 << 20);
259 uint32_t warning_message_size = 512 * (1 << 20);
260 coded_is.SetTotalBytesLimit(max_message_size, warning_message_size);
261 while (true) {
262 uint32_t size;
263 if (!coded_is.ReadLittleEndian32(&size)) {
264 PLOG(ERROR) << "failed to read " << filename;
265 return false;
266 }
267 if (size == 0) {
268 break;
269 }
270 // Handle files having large symbol table.
271 if (size > max_message_size) {
272 max_message_size = size;
273 coded_is.SetTotalBytesLimit(max_message_size, warning_message_size);
274 }
275 auto limit = coded_is.PushLimit(size);
276 proto::Record proto_record;
277 if (!proto_record.ParseFromCodedStream(&coded_is)) {
278 PLOG(ERROR) << "failed to read " << filename;
279 return false;
280 }
281 coded_is.PopLimit(limit);
282 if (proto_record.has_sample()) {
283 auto& sample = proto_record.sample();
284 static size_t sample_count = 0;
285 FprintIndented(report_fp_, 0, "sample %zu:\n", ++sample_count);
286 FprintIndented(report_fp_, 1, "event_type_id: %zu\n", sample.event_type_id());
287 FprintIndented(report_fp_, 1, "time: %" PRIu64 "\n", sample.time());
288 FprintIndented(report_fp_, 1, "event_count: %" PRIu64 "\n", sample.event_count());
289 FprintIndented(report_fp_, 1, "thread_id: %d\n", sample.thread_id());
290 FprintIndented(report_fp_, 1, "callchain:\n");
291 for (int i = 0; i < sample.callchain_size(); ++i) {
292 const proto::Sample_CallChainEntry& callchain = sample.callchain(i);
293 FprintIndented(report_fp_, 2, "vaddr_in_file: %" PRIx64 "\n",
294 callchain.vaddr_in_file());
295 FprintIndented(report_fp_, 2, "file_id: %u\n", callchain.file_id());
296 int32_t symbol_id = callchain.symbol_id();
297 FprintIndented(report_fp_, 2, "symbol_id: %d\n", symbol_id);
298 if (symbol_id < -1) {
299 LOG(ERROR) << "unexpected symbol_id " << symbol_id;
300 return false;
301 }
302 if (symbol_id != -1) {
303 max_symbol_id_map[callchain.file_id()] =
304 std::max(max_symbol_id_map[callchain.file_id()], symbol_id);
305 }
306 }
307 } else if (proto_record.has_lost()) {
308 auto& lost = proto_record.lost();
309 FprintIndented(report_fp_, 0, "lost_situation:\n");
310 FprintIndented(report_fp_, 1, "sample_count: %" PRIu64 "\n",
311 lost.sample_count());
312 FprintIndented(report_fp_, 1, "lost_count: %" PRIu64 "\n",
313 lost.lost_count());
314 } else if (proto_record.has_file()) {
315 auto& file = proto_record.file();
316 FprintIndented(report_fp_, 0, "file:\n");
317 FprintIndented(report_fp_, 1, "id: %u\n", file.id());
318 FprintIndented(report_fp_, 1, "path: %s\n", file.path().c_str());
319 for (int i = 0; i < file.symbol_size(); ++i) {
320 FprintIndented(report_fp_, 1, "symbol: %s\n", file.symbol(i).c_str());
321 }
322 if (file.id() != files.size()) {
323 LOG(ERROR) << "file id doesn't increase orderly, expected "
324 << files.size() << ", really " << file.id();
325 return false;
326 }
327 files.push_back(file.symbol_size());
328 } else if (proto_record.has_thread()) {
329 auto& thread = proto_record.thread();
330 FprintIndented(report_fp_, 0, "thread:\n");
331 FprintIndented(report_fp_, 1, "thread_id: %u\n", thread.thread_id());
332 FprintIndented(report_fp_, 1, "process_id: %u\n", thread.process_id());
333 FprintIndented(report_fp_, 1, "thread_name: %s\n", thread.thread_name().c_str());
334 } else if (proto_record.has_meta_info()) {
335 auto& meta_info = proto_record.meta_info();
336 FprintIndented(report_fp_, 0, "meta_info:\n");
337 for (int i = 0; i < meta_info.event_type_size(); ++i) {
338 FprintIndented(report_fp_, 1, "event_type: %s\n", meta_info.event_type(i).c_str());
339 }
340 } else {
341 LOG(ERROR) << "unexpected record type ";
342 return false;
343 }
344 }
345 for (auto pair : max_symbol_id_map) {
346 if (pair.first >= files.size()) {
347 LOG(ERROR) << "file_id(" << pair.first << ") >= file count ("
348 << files.size() << ")";
349 return false;
350 }
351 if (static_cast<uint32_t>(pair.second) >= files[pair.first]) {
352 LOG(ERROR) << "symbol_id(" << pair.second << ") >= symbol count ("
353 << files[pair.first] << ") in file_id( " << pair.first << ")";
354 return false;
355 }
356 }
357 return true;
358 }
359
OpenRecordFile()360 bool ReportSampleCommand::OpenRecordFile() {
361 record_file_reader_ = RecordFileReader::CreateInstance(record_filename_);
362 if (record_file_reader_ == nullptr) {
363 return false;
364 }
365 record_file_reader_->LoadBuildIdAndFileFeatures(thread_tree_);
366 if (record_file_reader_->HasFeature(PerfFileFormat::FEAT_META_INFO)) {
367 std::unordered_map<std::string, std::string> meta_info;
368 if (!record_file_reader_->ReadMetaInfoFeature(&meta_info)) {
369 return false;
370 }
371 auto it = meta_info.find("event_type_info");
372 if (it != meta_info.end()) {
373 scoped_event_types_.reset(new ScopedEventTypes(it->second));
374 }
375 it = meta_info.find("trace_offcpu");
376 if (it != meta_info.end()) {
377 trace_offcpu_ = it->second == "true";
378 }
379 }
380 for (EventAttrWithId& attr : record_file_reader_->AttrSection()) {
381 event_types_.push_back(GetEventNameByAttr(*attr.attr));
382 }
383 return true;
384 }
385
PrintMetaInfo()386 bool ReportSampleCommand::PrintMetaInfo() {
387 if (use_protobuf_) {
388 proto::Record proto_record;
389 proto::MetaInfo* meta_info = proto_record.mutable_meta_info();
390 for (auto& event_type : event_types_) {
391 *(meta_info->add_event_type()) = event_type;
392 }
393 return WriteRecordInProtobuf(proto_record);
394 }
395 FprintIndented(report_fp_, 0, "meta_info:\n");
396 FprintIndented(report_fp_, 1, "trace_offcpu: %s\n", trace_offcpu_ ? "true" : "false");
397 for (auto& event_type : event_types_) {
398 FprintIndented(report_fp_, 1, "event_type: %s\n", event_type.c_str());
399 }
400 return true;
401 }
402
ProcessRecord(std::unique_ptr<Record> record)403 bool ReportSampleCommand::ProcessRecord(std::unique_ptr<Record> record) {
404 thread_tree_.Update(*record);
405 if (record->type() == PERF_RECORD_SAMPLE) {
406 sample_count_++;
407 auto& r = *static_cast<const SampleRecord*>(record.get());
408 if (use_protobuf_) {
409 return PrintSampleRecordInProtobuf(r);
410 } else {
411 return PrintSampleRecord(r);
412 }
413 } else if (record->type() == PERF_RECORD_LOST) {
414 lost_count_ += static_cast<const LostRecord*>(record.get())->lost;
415 }
416 return true;
417 }
418
419
PrintSampleRecordInProtobuf(const SampleRecord & r)420 bool ReportSampleCommand::PrintSampleRecordInProtobuf(const SampleRecord& r) {
421 struct Node {
422 Dso* dso;
423 const Symbol* symbol;
424 uint64_t vaddr_in_file;
425 };
426 std::vector<Node> nodes;
427 Node node;
428 proto::Record proto_record;
429 proto::Sample* sample = proto_record.mutable_sample();
430 sample->set_time(r.time_data.time);
431 sample->set_event_count(r.period_data.period);
432 sample->set_thread_id(r.tid_data.tid);
433 sample->set_event_type_id(record_file_reader_->GetAttrIndexOfRecord(&r));
434
435 bool in_kernel = r.InKernel();
436 const ThreadEntry* thread =
437 thread_tree_.FindThreadOrNew(r.tid_data.pid, r.tid_data.tid);
438 bool ret = GetCallEntry(thread, in_kernel, r.ip_data.ip, false, &node.vaddr_in_file,
439 &node.dso, &node.symbol);
440 CHECK(ret);
441 nodes.push_back(node);
442 if (show_callchain_ && (r.sample_type & PERF_SAMPLE_CALLCHAIN)) {
443 bool first_ip = true;
444 for (uint64_t i = 0; i < r.callchain_data.ip_nr; ++i) {
445 uint64_t ip = r.callchain_data.ips[i];
446 if (ip >= PERF_CONTEXT_MAX) {
447 switch (ip) {
448 case PERF_CONTEXT_KERNEL:
449 in_kernel = true;
450 break;
451 case PERF_CONTEXT_USER:
452 in_kernel = false;
453 break;
454 default:
455 LOG(DEBUG) << "Unexpected perf_context in callchain: " << std::hex
456 << ip << std::dec;
457 }
458 } else {
459 if (first_ip) {
460 first_ip = false;
461 // Remove duplication with sample ip.
462 if (ip == r.ip_data.ip) {
463 continue;
464 }
465 }
466 if (!GetCallEntry(thread, in_kernel, ip, true, &node.vaddr_in_file, &node.dso,
467 &node.symbol)) {
468 break;
469 }
470 nodes.push_back(node);
471 }
472 }
473 }
474
475 for (const Node& node : nodes) {
476 proto::Sample_CallChainEntry* callchain = sample->add_callchain();
477 uint32_t file_id;
478 if (!node.dso->GetDumpId(&file_id)) {
479 file_id = node.dso->CreateDumpId();
480 }
481 int32_t symbol_id = -1;
482 if (node.symbol != thread_tree_.UnknownSymbol()) {
483 if (!node.symbol->GetDumpId(reinterpret_cast<uint32_t*>(&symbol_id))) {
484 symbol_id = node.dso->CreateSymbolDumpId(node.symbol);
485 }
486 }
487 callchain->set_vaddr_in_file(node.vaddr_in_file);
488 callchain->set_file_id(file_id);
489 callchain->set_symbol_id(symbol_id);
490
491 // Android studio wants a clear call chain end to notify whether a call chain is complete.
492 // For the main thread, the call chain ends at __libc_init in libc.so. For other threads,
493 // the call chain ends at __start_thread in libc.so.
494 // The call chain of the main thread can go beyond __libc_init, to _start (<= android O) or
495 // _start_main (> android O).
496 if (node.dso->FileName() == "libc.so" &&
497 (strcmp(node.symbol->Name(), "__libc_init") == 0 ||
498 strcmp(node.symbol->Name(), "__start_thread") == 0)) {
499 break;
500 }
501 }
502 return WriteRecordInProtobuf(proto_record);
503 }
504
WriteRecordInProtobuf(proto::Record & proto_record)505 bool ReportSampleCommand::WriteRecordInProtobuf(proto::Record& proto_record) {
506 coded_os_->WriteLittleEndian32(proto_record.ByteSize());
507 if (!proto_record.SerializeToCodedStream(coded_os_)) {
508 LOG(ERROR) << "failed to write record to protobuf";
509 return false;
510 }
511 return true;
512 }
513
GetCallEntry(const ThreadEntry * thread,bool in_kernel,uint64_t ip,bool omit_unknown_dso,uint64_t * pvaddr_in_file,Dso ** pdso,const Symbol ** psymbol)514 bool ReportSampleCommand::GetCallEntry(const ThreadEntry* thread,
515 bool in_kernel, uint64_t ip,
516 bool omit_unknown_dso,
517 uint64_t* pvaddr_in_file, Dso** pdso,
518 const Symbol** psymbol) {
519 const MapEntry* map = thread_tree_.FindMap(thread, ip, in_kernel);
520 if (omit_unknown_dso && thread_tree_.IsUnknownDso(map->dso)) {
521 return false;
522 }
523 *psymbol = thread_tree_.FindSymbol(map, ip, pvaddr_in_file, pdso);
524 // If we can't find symbol, use the dso shown in the map.
525 if (*psymbol == thread_tree_.UnknownSymbol()) {
526 *pdso = map->dso;
527 }
528 return true;
529 }
530
PrintLostSituationInProtobuf()531 bool ReportSampleCommand::PrintLostSituationInProtobuf() {
532 proto::Record proto_record;
533 proto::LostSituation* lost = proto_record.mutable_lost();
534 lost->set_sample_count(sample_count_);
535 lost->set_lost_count(lost_count_);
536 return WriteRecordInProtobuf(proto_record);
537 }
538
CompareDsoByDumpId(Dso * d1,Dso * d2)539 static bool CompareDsoByDumpId(Dso* d1, Dso* d2) {
540 uint32_t id1 = UINT_MAX;
541 d1->GetDumpId(&id1);
542 uint32_t id2 = UINT_MAX;
543 d2->GetDumpId(&id2);
544 return id1 < id2;
545 }
546
PrintFileInfoInProtobuf()547 bool ReportSampleCommand::PrintFileInfoInProtobuf() {
548 std::vector<Dso*> dsos = thread_tree_.GetAllDsos();
549 std::sort(dsos.begin(), dsos.end(), CompareDsoByDumpId);
550 for (Dso* dso : dsos) {
551 uint32_t file_id;
552 if (!dso->GetDumpId(&file_id)) {
553 continue;
554 }
555 proto::Record proto_record;
556 proto::File* file = proto_record.mutable_file();
557 file->set_id(file_id);
558 file->set_path(dso->Path());
559 const std::vector<Symbol>& symbols = dso->GetSymbols();
560 std::vector<const Symbol*> dump_symbols;
561 for (const auto& sym : symbols) {
562 if (sym.HasDumpId()) {
563 dump_symbols.push_back(&sym);
564 }
565 }
566 std::sort(dump_symbols.begin(), dump_symbols.end(),
567 Symbol::CompareByDumpId);
568
569 for (const auto& sym : dump_symbols) {
570 std::string* symbol = file->add_symbol();
571 *symbol = sym->DemangledName();
572 }
573 if (!WriteRecordInProtobuf(proto_record)) {
574 return false;
575 }
576 }
577 return true;
578 }
579
PrintThreadInfoInProtobuf()580 bool ReportSampleCommand::PrintThreadInfoInProtobuf() {
581 std::vector<const ThreadEntry*> threads = thread_tree_.GetAllThreads();
582 auto compare_thread_id = [](const ThreadEntry* t1, const ThreadEntry* t2) {
583 return t1->tid < t2->tid;
584 };
585 std::sort(threads.begin(), threads.end(), compare_thread_id);
586 for (auto& thread : threads) {
587 proto::Record proto_record;
588 proto::Thread* proto_thread = proto_record.mutable_thread();
589 proto_thread->set_thread_id(thread->tid);
590 proto_thread->set_process_id(thread->pid);
591 proto_thread->set_thread_name(thread->comm);
592 if (!WriteRecordInProtobuf(proto_record)) {
593 return false;
594 }
595 }
596 return true;
597 }
598
PrintSampleRecord(const SampleRecord & r)599 bool ReportSampleCommand::PrintSampleRecord(const SampleRecord& r) {
600 uint64_t vaddr_in_file;
601 Dso* dso;
602 const Symbol* symbol;
603
604 FprintIndented(report_fp_, 0, "sample:\n");
605 FprintIndented(report_fp_, 1, "event_type: %s\n",
606 event_types_[record_file_reader_->GetAttrIndexOfRecord(&r)].c_str());
607 FprintIndented(report_fp_, 1, "time: %" PRIu64 "\n", r.time_data.time);
608 FprintIndented(report_fp_, 1, "event_count: %" PRIu64 "\n", r.period_data.period);
609 FprintIndented(report_fp_, 1, "thread_id: %d\n", r.tid_data.tid);
610 const char* thread_name = thread_tree_.FindThreadOrNew(r.tid_data.pid, r.tid_data.tid)->comm;
611 FprintIndented(report_fp_, 1, "thread_name: %s\n", thread_name);
612 bool in_kernel = r.InKernel();
613 const ThreadEntry* thread =
614 thread_tree_.FindThreadOrNew(r.tid_data.pid, r.tid_data.tid);
615 bool ret = GetCallEntry(thread, in_kernel, r.ip_data.ip, false, &vaddr_in_file, &dso, &symbol);
616 CHECK(ret);
617 FprintIndented(report_fp_, 1, "vaddr_in_file: %" PRIx64 "\n", vaddr_in_file);
618 FprintIndented(report_fp_, 1, "file: %s\n", dso->Path().c_str());
619 FprintIndented(report_fp_, 1, "symbol: %s\n", symbol->DemangledName());
620
621 if (show_callchain_ && (r.sample_type & PERF_SAMPLE_CALLCHAIN)) {
622 FprintIndented(report_fp_, 1, "callchain:\n");
623 bool first_ip = true;
624 for (uint64_t i = 0; i < r.callchain_data.ip_nr; ++i) {
625 uint64_t ip = r.callchain_data.ips[i];
626 if (ip >= PERF_CONTEXT_MAX) {
627 switch (ip) {
628 case PERF_CONTEXT_KERNEL:
629 in_kernel = true;
630 break;
631 case PERF_CONTEXT_USER:
632 in_kernel = false;
633 break;
634 default:
635 LOG(DEBUG) << "Unexpected perf_context in callchain: " << std::hex
636 << ip;
637 }
638 } else {
639 if (first_ip) {
640 first_ip = false;
641 // Remove duplication with sample ip.
642 if (ip == r.ip_data.ip) {
643 continue;
644 }
645 }
646 if (!GetCallEntry(thread, in_kernel, ip, true, &vaddr_in_file, &dso, &symbol)) {
647 break;
648 }
649 FprintIndented(report_fp_, 2, "vaddr_in_file: %" PRIx64 "\n",
650 vaddr_in_file);
651 FprintIndented(report_fp_, 2, "file: %s\n", dso->Path().c_str());
652 FprintIndented(report_fp_, 2, "symbol: %s\n", symbol->DemangledName());
653 }
654 }
655 }
656 return true;
657 }
658
PrintLostSituation()659 void ReportSampleCommand::PrintLostSituation() {
660 FprintIndented(report_fp_, 0, "lost_situation:\n");
661 FprintIndented(report_fp_, 1, "sample_count: %" PRIu64 "\n", sample_count_);
662 FprintIndented(report_fp_, 1, "lost_count: %" PRIu64 "\n", lost_count_);
663 }
664
665 } // namespace
666
RegisterReportSampleCommand()667 void RegisterReportSampleCommand() {
668 RegisterCommand("report-sample", [] {
669 return std::unique_ptr<Command>(new ReportSampleCommand());
670 });
671 }
672