OSDN Git Service

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