Home | History | Annotate | Download | only in simpleperf
      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:
     39   explicit ProtobufFileWriter(FILE* out_fp) : out_fp_(out_fp) {}
     40 
     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:
     51   explicit ProtobufFileReader(FILE* in_fp) : in_fp_(in_fp) {}
     52 
     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:
     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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    667 void RegisterReportSampleCommand() {
    668   RegisterCommand("report-sample", [] {
    669     return std::unique_ptr<Command>(new ReportSampleCommand());
    670   });
    671 }
    672