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 "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   bool GetCallEntry(const ThreadEntry* thread, bool in_kernel, uint64_t ip, bool omit_unknown_dso,
     92                     uint64_t* pvaddr_in_file, uint32_t* pfile_id, int32_t* psymbol_id);
     93   bool GetCallEntry(const ThreadEntry* thread, bool in_kernel, uint64_t ip, bool omit_unknown_dso,
     94                     uint64_t* pvaddr_in_file, Dso** pdso, const Symbol** psymbol);
     95   bool WriteRecordInProtobuf(proto::Record& proto_record);
     96   bool PrintLostSituationInProtobuf();
     97   bool PrintFileInfoInProtobuf();
     98   bool PrintThreadInfoInProtobuf();
     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     if (!PrintThreadInfoInProtobuf()) {
    184       return false;
    185     }
    186     coded_os_->WriteLittleEndian32(0);
    187     if (coded_os_->HadError()) {
    188       LOG(ERROR) << "print protobuf report failed";
    189       return false;
    190     }
    191     protobuf_coded_os.reset(nullptr);
    192   } else {
    193     PrintLostSituation();
    194     fflush(report_fp_);
    195   }
    196   if (ferror(report_fp_) != 0) {
    197     PLOG(ERROR) << "print report failed";
    198     return false;
    199   }
    200   return true;
    201 }
    202 
    203 bool ReportSampleCommand::ParseOptions(const std::vector<std::string>& args) {
    204   for (size_t i = 0; i < args.size(); ++i) {
    205     if (args[i] == "--dump-protobuf-report") {
    206       if (!NextArgumentOrError(args, &i)) {
    207         return false;
    208       }
    209       dump_protobuf_report_file_ = args[i];
    210     } else if (args[i] == "-i") {
    211       if (!NextArgumentOrError(args, &i)) {
    212         return false;
    213       }
    214       record_filename_ = args[i];
    215     } else if (args[i] == "-o") {
    216       if (!NextArgumentOrError(args, &i)) {
    217         return false;
    218       }
    219       report_filename_ = args[i];
    220     } else if (args[i] == "--protobuf") {
    221       use_protobuf_ = true;
    222     } else if (args[i] == "--show-callchain") {
    223       show_callchain_ = true;
    224     } else {
    225       ReportUnknownOption(args, i);
    226       return false;
    227     }
    228   }
    229 
    230   if (use_protobuf_ && report_filename_.empty()) {
    231     LOG(ERROR) << "please specify a report filename to write protobuf data";
    232     return false;
    233   }
    234   return true;
    235 }
    236 
    237 bool ReportSampleCommand::DumpProtobufReport(const std::string& filename) {
    238   GOOGLE_PROTOBUF_VERIFY_VERSION;
    239   std::unique_ptr<FILE, decltype(&fclose)> fp(fopen(filename.c_str(), "rb"),
    240                                               fclose);
    241   if (fp == nullptr) {
    242     PLOG(ERROR) << "failed to open " << filename;
    243     return false;
    244   }
    245   ProtobufFileReader protobuf_reader(fp.get());
    246   google::protobuf::io::CopyingInputStreamAdaptor adaptor(&protobuf_reader);
    247   google::protobuf::io::CodedInputStream coded_is(&adaptor);
    248   // map from file_id to max_symbol_id requested on the file.
    249   std::unordered_map<uint32_t, int32_t> max_symbol_id_map;
    250   // files[file_id] is the number of symbols in the file.
    251   std::vector<uint32_t> files;
    252   uint32_t max_message_size = 64 * (1 << 20);
    253   uint32_t warning_message_size = 512 * (1 << 20);
    254   coded_is.SetTotalBytesLimit(max_message_size, warning_message_size);
    255   while (true) {
    256     uint32_t size;
    257     if (!coded_is.ReadLittleEndian32(&size)) {
    258       PLOG(ERROR) << "failed to read " << filename;
    259       return false;
    260     }
    261     if (size == 0) {
    262       break;
    263     }
    264     // Handle files having large symbol table.
    265     if (size > max_message_size) {
    266       max_message_size = size;
    267       coded_is.SetTotalBytesLimit(max_message_size, warning_message_size);
    268     }
    269     auto limit = coded_is.PushLimit(size);
    270     proto::Record proto_record;
    271     if (!proto_record.ParseFromCodedStream(&coded_is)) {
    272       PLOG(ERROR) << "failed to read " << filename;
    273       return false;
    274     }
    275     coded_is.PopLimit(limit);
    276     if (proto_record.has_sample()) {
    277       auto& sample = proto_record.sample();
    278       static size_t sample_count = 0;
    279       FprintIndented(report_fp_, 0, "sample %zu:\n", ++sample_count);
    280       FprintIndented(report_fp_, 1, "time: %" PRIu64 "\n", sample.time());
    281       FprintIndented(report_fp_, 1, "event_count: %" PRIu64 "\n", sample.event_count());
    282       FprintIndented(report_fp_, 1, "thread_id: %d\n", sample.thread_id());
    283       FprintIndented(report_fp_, 1, "callchain:\n");
    284       for (int i = 0; i < sample.callchain_size(); ++i) {
    285         const proto::Sample_CallChainEntry& callchain = sample.callchain(i);
    286         FprintIndented(report_fp_, 2, "vaddr_in_file: %" PRIx64 "\n",
    287                        callchain.vaddr_in_file());
    288         FprintIndented(report_fp_, 2, "file_id: %u\n", callchain.file_id());
    289         int32_t symbol_id = callchain.symbol_id();
    290         FprintIndented(report_fp_, 2, "symbol_id: %d\n", symbol_id);
    291         if (symbol_id < -1) {
    292           LOG(ERROR) << "unexpected symbol_id " << symbol_id;
    293           return false;
    294         }
    295         if (symbol_id != -1) {
    296           max_symbol_id_map[callchain.file_id()] =
    297               std::max(max_symbol_id_map[callchain.file_id()], symbol_id);
    298         }
    299       }
    300     } else if (proto_record.has_lost()) {
    301       auto& lost = proto_record.lost();
    302       FprintIndented(report_fp_, 0, "lost_situation:\n");
    303       FprintIndented(report_fp_, 1, "sample_count: %" PRIu64 "\n",
    304                      lost.sample_count());
    305       FprintIndented(report_fp_, 1, "lost_count: %" PRIu64 "\n",
    306                      lost.lost_count());
    307     } else if (proto_record.has_file()) {
    308       auto& file = proto_record.file();
    309       FprintIndented(report_fp_, 0, "file:\n");
    310       FprintIndented(report_fp_, 1, "id: %u\n", file.id());
    311       FprintIndented(report_fp_, 1, "path: %s\n", file.path().c_str());
    312       for (int i = 0; i < file.symbol_size(); ++i) {
    313         FprintIndented(report_fp_, 1, "symbol: %s\n", file.symbol(i).c_str());
    314       }
    315       if (file.id() != files.size()) {
    316         LOG(ERROR) << "file id doesn't increase orderly, expected "
    317                    << files.size() << ", really " << file.id();
    318         return false;
    319       }
    320       files.push_back(file.symbol_size());
    321     } else if (proto_record.has_thread()) {
    322       auto& thread = proto_record.thread();
    323       FprintIndented(report_fp_, 0, "thread:\n");
    324       FprintIndented(report_fp_, 1, "thread_id: %u\n", thread.thread_id());
    325       FprintIndented(report_fp_, 1, "process_id: %u\n", thread.process_id());
    326       FprintIndented(report_fp_, 1, "thread_name: %s\n", thread.thread_name().c_str());
    327     } else {
    328       LOG(ERROR) << "unexpected record type ";
    329       return false;
    330     }
    331   }
    332   for (auto pair : max_symbol_id_map) {
    333     if (pair.first >= files.size()) {
    334       LOG(ERROR) << "file_id(" << pair.first << ") >= file count ("
    335                  << files.size() << ")";
    336       return false;
    337     }
    338     if (static_cast<uint32_t>(pair.second) >= files[pair.first]) {
    339       LOG(ERROR) << "symbol_id(" << pair.second << ") >= symbol count ("
    340                  << files[pair.first] << ") in file_id( " << pair.first << ")";
    341       return false;
    342     }
    343   }
    344   return true;
    345 }
    346 
    347 bool ReportSampleCommand::ProcessRecord(std::unique_ptr<Record> record) {
    348   thread_tree_.Update(*record);
    349   if (record->type() == PERF_RECORD_SAMPLE) {
    350     sample_count_++;
    351     auto& r = *static_cast<const SampleRecord*>(record.get());
    352     if (use_protobuf_) {
    353       return PrintSampleRecordInProtobuf(r);
    354     } else {
    355       return PrintSampleRecord(r);
    356     }
    357   } else if (record->type() == PERF_RECORD_LOST) {
    358     lost_count_ += static_cast<const LostRecord*>(record.get())->lost;
    359   }
    360   return true;
    361 }
    362 
    363 bool ReportSampleCommand::PrintSampleRecordInProtobuf(const SampleRecord& r) {
    364   uint64_t vaddr_in_file;
    365   uint32_t file_id;
    366   int32_t symbol_id;
    367   proto::Record proto_record;
    368   proto::Sample* sample = proto_record.mutable_sample();
    369   sample->set_time(r.time_data.time);
    370   sample->set_event_count(r.period_data.period);
    371   sample->set_thread_id(r.tid_data.tid);
    372 
    373   bool in_kernel = r.InKernel();
    374   const ThreadEntry* thread =
    375       thread_tree_.FindThreadOrNew(r.tid_data.pid, r.tid_data.tid);
    376   bool ret = GetCallEntry(thread, in_kernel, r.ip_data.ip, false, &vaddr_in_file, &file_id,
    377                           &symbol_id);
    378   CHECK(ret);
    379   proto::Sample_CallChainEntry* callchain = sample->add_callchain();
    380   callchain->set_vaddr_in_file(vaddr_in_file);
    381   callchain->set_file_id(file_id);
    382   callchain->set_symbol_id(symbol_id);
    383 
    384   if (show_callchain_ && (r.sample_type & PERF_SAMPLE_CALLCHAIN)) {
    385     bool first_ip = true;
    386     for (uint64_t i = 0; i < r.callchain_data.ip_nr; ++i) {
    387       uint64_t ip = r.callchain_data.ips[i];
    388       if (ip >= PERF_CONTEXT_MAX) {
    389         switch (ip) {
    390           case PERF_CONTEXT_KERNEL:
    391             in_kernel = true;
    392             break;
    393           case PERF_CONTEXT_USER:
    394             in_kernel = false;
    395             break;
    396           default:
    397             LOG(DEBUG) << "Unexpected perf_context in callchain: " << std::hex
    398                        << ip << std::dec;
    399         }
    400       } else {
    401         if (first_ip) {
    402           first_ip = false;
    403           // Remove duplication with sample ip.
    404           if (ip == r.ip_data.ip) {
    405             continue;
    406           }
    407         }
    408         if (!GetCallEntry(thread, in_kernel, ip, true, &vaddr_in_file, &file_id, &symbol_id)) {
    409           break;
    410         }
    411         callchain = sample->add_callchain();
    412         callchain->set_vaddr_in_file(vaddr_in_file);
    413         callchain->set_file_id(file_id);
    414         callchain->set_symbol_id(symbol_id);
    415       }
    416     }
    417   }
    418   return WriteRecordInProtobuf(proto_record);
    419 }
    420 
    421 bool ReportSampleCommand::WriteRecordInProtobuf(proto::Record& proto_record) {
    422   coded_os_->WriteLittleEndian32(proto_record.ByteSize());
    423   if (!proto_record.SerializeToCodedStream(coded_os_)) {
    424     LOG(ERROR) << "failed to write record to protobuf";
    425     return false;
    426   }
    427   return true;
    428 }
    429 
    430 bool ReportSampleCommand::GetCallEntry(const ThreadEntry* thread,
    431                                        bool in_kernel, uint64_t ip,
    432                                        bool omit_unknown_dso,
    433                                        uint64_t* pvaddr_in_file,
    434                                        uint32_t* pfile_id,
    435                                        int32_t* psymbol_id) {
    436   Dso* dso;
    437   const Symbol* symbol;
    438   if (!GetCallEntry(thread, in_kernel, ip, omit_unknown_dso, pvaddr_in_file, &dso, &symbol)) {
    439     return false;
    440   }
    441   if (!dso->GetDumpId(pfile_id)) {
    442     *pfile_id = dso->CreateDumpId();
    443   }
    444   if (symbol != thread_tree_.UnknownSymbol()) {
    445     if (!symbol->GetDumpId(reinterpret_cast<uint32_t*>(psymbol_id))) {
    446       *psymbol_id = dso->CreateSymbolDumpId(symbol);
    447     }
    448   } else {
    449     *psymbol_id = -1;
    450   }
    451   return true;
    452 }
    453 
    454 bool ReportSampleCommand::GetCallEntry(const ThreadEntry* thread,
    455                                        bool in_kernel, uint64_t ip,
    456                                        bool omit_unknown_dso,
    457                                        uint64_t* pvaddr_in_file, Dso** pdso,
    458                                        const Symbol** psymbol) {
    459   const MapEntry* map = thread_tree_.FindMap(thread, ip, in_kernel);
    460   if (omit_unknown_dso && thread_tree_.IsUnknownDso(map->dso)) {
    461     return false;
    462   }
    463   *psymbol = thread_tree_.FindSymbol(map, ip, pvaddr_in_file, pdso);
    464   // If we can't find symbol, use the dso shown in the map.
    465   if (*psymbol == thread_tree_.UnknownSymbol()) {
    466     *pdso = map->dso;
    467   }
    468   return true;
    469 }
    470 
    471 bool ReportSampleCommand::PrintLostSituationInProtobuf() {
    472   proto::Record proto_record;
    473   proto::LostSituation* lost = proto_record.mutable_lost();
    474   lost->set_sample_count(sample_count_);
    475   lost->set_lost_count(lost_count_);
    476   return WriteRecordInProtobuf(proto_record);
    477 }
    478 
    479 static bool CompareDsoByDumpId(Dso* d1, Dso* d2) {
    480   uint32_t id1 = UINT_MAX;
    481   d1->GetDumpId(&id1);
    482   uint32_t id2 = UINT_MAX;
    483   d2->GetDumpId(&id2);
    484   return id1 < id2;
    485 }
    486 
    487 bool ReportSampleCommand::PrintFileInfoInProtobuf() {
    488   std::vector<Dso*> dsos = thread_tree_.GetAllDsos();
    489   std::sort(dsos.begin(), dsos.end(), CompareDsoByDumpId);
    490   for (Dso* dso : dsos) {
    491     uint32_t file_id;
    492     if (!dso->GetDumpId(&file_id)) {
    493       continue;
    494     }
    495     proto::Record proto_record;
    496     proto::File* file = proto_record.mutable_file();
    497     file->set_id(file_id);
    498     file->set_path(dso->Path());
    499     const std::vector<Symbol>& symbols = dso->GetSymbols();
    500     std::vector<const Symbol*> dump_symbols;
    501     for (const auto& sym : symbols) {
    502       if (sym.HasDumpId()) {
    503         dump_symbols.push_back(&sym);
    504       }
    505     }
    506     std::sort(dump_symbols.begin(), dump_symbols.end(),
    507               Symbol::CompareByDumpId);
    508 
    509     for (const auto& sym : dump_symbols) {
    510       std::string* symbol = file->add_symbol();
    511       *symbol = sym->DemangledName();
    512     }
    513     if (!WriteRecordInProtobuf(proto_record)) {
    514       return false;
    515     }
    516   }
    517   return true;
    518 }
    519 
    520 bool ReportSampleCommand::PrintThreadInfoInProtobuf() {
    521   std::vector<const ThreadEntry*> threads = thread_tree_.GetAllThreads();
    522   auto compare_thread_id = [](const ThreadEntry* t1, const ThreadEntry* t2) {
    523     return t1->tid < t2->tid;
    524   };
    525   std::sort(threads.begin(), threads.end(), compare_thread_id);
    526   for (auto& thread : threads) {
    527     proto::Record proto_record;
    528     proto::Thread* proto_thread = proto_record.mutable_thread();
    529     proto_thread->set_thread_id(thread->tid);
    530     proto_thread->set_process_id(thread->pid);
    531     proto_thread->set_thread_name(thread->comm);
    532     if (!WriteRecordInProtobuf(proto_record)) {
    533       return false;
    534     }
    535   }
    536   return true;
    537 }
    538 
    539 bool ReportSampleCommand::PrintSampleRecord(const SampleRecord& r) {
    540   uint64_t vaddr_in_file;
    541   Dso* dso;
    542   const Symbol* symbol;
    543 
    544   FprintIndented(report_fp_, 0, "sample:\n");
    545   FprintIndented(report_fp_, 1, "time: %" PRIu64 "\n", r.time_data.time);
    546   FprintIndented(report_fp_, 1, "event_count: %" PRIu64 "\n", r.period_data.period);
    547   FprintIndented(report_fp_, 1, "thread_id: %d\n", r.tid_data.tid);
    548   bool in_kernel = r.InKernel();
    549   const ThreadEntry* thread =
    550       thread_tree_.FindThreadOrNew(r.tid_data.pid, r.tid_data.tid);
    551   bool ret = GetCallEntry(thread, in_kernel, r.ip_data.ip, false, &vaddr_in_file, &dso, &symbol);
    552   CHECK(ret);
    553   FprintIndented(report_fp_, 1, "vaddr_in_file: %" PRIx64 "\n", vaddr_in_file);
    554   FprintIndented(report_fp_, 1, "file: %s\n", dso->Path().c_str());
    555   FprintIndented(report_fp_, 1, "symbol: %s\n", symbol->DemangledName());
    556 
    557   if (show_callchain_ && (r.sample_type & PERF_SAMPLE_CALLCHAIN)) {
    558     FprintIndented(report_fp_, 1, "callchain:\n");
    559     bool first_ip = true;
    560     for (uint64_t i = 0; i < r.callchain_data.ip_nr; ++i) {
    561       uint64_t ip = r.callchain_data.ips[i];
    562       if (ip >= PERF_CONTEXT_MAX) {
    563         switch (ip) {
    564           case PERF_CONTEXT_KERNEL:
    565             in_kernel = true;
    566             break;
    567           case PERF_CONTEXT_USER:
    568             in_kernel = false;
    569             break;
    570           default:
    571             LOG(DEBUG) << "Unexpected perf_context in callchain: " << std::hex
    572                        << ip;
    573         }
    574       } else {
    575         if (first_ip) {
    576           first_ip = false;
    577           // Remove duplication with sample ip.
    578           if (ip == r.ip_data.ip) {
    579             continue;
    580           }
    581         }
    582         if (!GetCallEntry(thread, in_kernel, ip, true, &vaddr_in_file, &dso, &symbol)) {
    583           break;
    584         }
    585         FprintIndented(report_fp_, 2, "vaddr_in_file: %" PRIx64 "\n",
    586                        vaddr_in_file);
    587         FprintIndented(report_fp_, 2, "file: %s\n", dso->Path().c_str());
    588         FprintIndented(report_fp_, 2, "symbol: %s\n", symbol->DemangledName());
    589       }
    590     }
    591   }
    592   return true;
    593 }
    594 
    595 void ReportSampleCommand::PrintLostSituation() {
    596   FprintIndented(report_fp_, 0, "lost_situation:\n");
    597   FprintIndented(report_fp_, 1, "sample_count: %" PRIu64 "\n", sample_count_);
    598   FprintIndented(report_fp_, 1, "lost_count: %" PRIu64 "\n", sample_count_);
    599 }
    600 
    601 }  // namespace
    602 
    603 void RegisterReportSampleCommand() {
    604   RegisterCommand("report-sample", [] {
    605     return std::unique_ptr<Command>(new ReportSampleCommand());
    606   });
    607 }
    608