| /* |
| * Copyright (C) 2015 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| #include <inttypes.h> |
| |
| #include <map> |
| #include <string> |
| #include <vector> |
| |
| #include <android-base/logging.h> |
| #include <android-base/stringprintf.h> |
| #include <android-base/strings.h> |
| |
| #include "command.h" |
| #include "event_attr.h" |
| #include "event_type.h" |
| #include "perf_regs.h" |
| #include "record.h" |
| #include "record_file.h" |
| #include "utils.h" |
| |
| using namespace PerfFileFormat; |
| |
| class DumpRecordCommand : public Command { |
| public: |
| DumpRecordCommand() |
| : Command("dump", "dump perf record file", |
| "Usage: simpleperf dumprecord [options] [perf_record_file]\n" |
| " Dump different parts of a perf record file. Default file is perf.data.\n"), |
| record_filename_("perf.data"), record_file_arch_(GetBuildArch()) { |
| } |
| |
| bool Run(const std::vector<std::string>& args); |
| |
| private: |
| bool ParseOptions(const std::vector<std::string>& args); |
| void DumpFileHeader(); |
| void DumpAttrSection(); |
| bool DumpDataSection(); |
| bool DumpFeatureSection(); |
| |
| std::string record_filename_; |
| std::unique_ptr<RecordFileReader> record_file_reader_; |
| ArchType record_file_arch_; |
| }; |
| |
| bool DumpRecordCommand::Run(const std::vector<std::string>& args) { |
| if (!ParseOptions(args)) { |
| return false; |
| } |
| record_file_reader_ = RecordFileReader::CreateInstance(record_filename_); |
| if (record_file_reader_ == nullptr) { |
| return false; |
| } |
| std::string arch = record_file_reader_->ReadFeatureString(FEAT_ARCH); |
| if (!arch.empty()) { |
| record_file_arch_ = GetArchType(arch); |
| if (record_file_arch_ == ARCH_UNSUPPORTED) { |
| return false; |
| } |
| } |
| ScopedCurrentArch scoped_arch(record_file_arch_); |
| std::unique_ptr<ScopedEventTypes> scoped_event_types; |
| if (record_file_reader_->HasFeature(PerfFileFormat::FEAT_META_INFO)) { |
| std::unordered_map<std::string, std::string> meta_info; |
| if (!record_file_reader_->ReadMetaInfoFeature(&meta_info)) { |
| return false; |
| } |
| auto it = meta_info.find("event_type_info"); |
| if (it != meta_info.end()) { |
| scoped_event_types.reset(new ScopedEventTypes(it->second)); |
| } |
| } |
| DumpFileHeader(); |
| DumpAttrSection(); |
| if (!DumpDataSection()) { |
| return false; |
| } |
| return DumpFeatureSection(); |
| } |
| |
| bool DumpRecordCommand::ParseOptions(const std::vector<std::string>& args) { |
| if (args.size() == 1) { |
| record_filename_ = args[0]; |
| } else if (args.size() > 1) { |
| ReportUnknownOption(args, 1); |
| return false; |
| } |
| return true; |
| } |
| |
| static const std::string GetFeatureNameOrUnknown(int feature) { |
| std::string name = GetFeatureName(feature); |
| return name.empty() ? android::base::StringPrintf("unknown_feature(%d)", feature) : name; |
| } |
| |
| void DumpRecordCommand::DumpFileHeader() { |
| const FileHeader& header = record_file_reader_->FileHeader(); |
| printf("magic: "); |
| for (size_t i = 0; i < 8; ++i) { |
| printf("%c", header.magic[i]); |
| } |
| printf("\n"); |
| printf("header_size: %" PRId64 "\n", header.header_size); |
| if (header.header_size != sizeof(header)) { |
| PLOG(WARNING) << "record file header size " << header.header_size |
| << "doesn't match expected header size " << sizeof(header); |
| } |
| printf("attr_size: %" PRId64 "\n", header.attr_size); |
| if (header.attr_size != sizeof(FileAttr)) { |
| PLOG(WARNING) << "record file attr size " << header.attr_size |
| << " doesn't match expected attr size " << sizeof(FileAttr); |
| } |
| printf("attrs[file section]: offset %" PRId64 ", size %" PRId64 "\n", header.attrs.offset, |
| header.attrs.size); |
| printf("data[file section]: offset %" PRId64 ", size %" PRId64 "\n", header.data.offset, |
| header.data.size); |
| printf("event_types[file section]: offset %" PRId64 ", size %" PRId64 "\n", |
| header.event_types.offset, header.event_types.size); |
| |
| std::vector<int> features; |
| for (size_t i = 0; i < FEAT_MAX_NUM; ++i) { |
| size_t j = i / 8; |
| size_t k = i % 8; |
| if ((header.features[j] & (1 << k)) != 0) { |
| features.push_back(i); |
| } |
| } |
| for (auto& feature : features) { |
| printf("feature: %s\n", GetFeatureNameOrUnknown(feature).c_str()); |
| } |
| } |
| |
| void DumpRecordCommand::DumpAttrSection() { |
| std::vector<EventAttrWithId> attrs = record_file_reader_->AttrSection(); |
| for (size_t i = 0; i < attrs.size(); ++i) { |
| const auto& attr = attrs[i]; |
| printf("attr %zu:\n", i + 1); |
| DumpPerfEventAttr(*attr.attr, 1); |
| if (!attr.ids.empty()) { |
| printf(" ids:"); |
| for (const auto& id : attr.ids) { |
| printf(" %" PRId64, id); |
| } |
| printf("\n"); |
| } |
| } |
| } |
| |
| bool DumpRecordCommand::DumpDataSection() { |
| ThreadTree thread_tree; |
| thread_tree.ShowIpForUnknownSymbol(); |
| record_file_reader_->LoadBuildIdAndFileFeatures(thread_tree); |
| |
| auto get_symbol_function = [&](uint32_t pid, uint32_t tid, uint64_t ip, std::string& dso_name, |
| std::string& symbol_name, uint64_t& vaddr_in_file, |
| bool in_kernel) { |
| ThreadEntry* thread = thread_tree.FindThreadOrNew(pid, tid); |
| const MapEntry* map = thread_tree.FindMap(thread, ip, in_kernel); |
| Dso* dso; |
| const Symbol* symbol = thread_tree.FindSymbol(map, ip, &vaddr_in_file, &dso); |
| dso_name = dso->Path(); |
| symbol_name = symbol->DemangledName(); |
| }; |
| |
| auto record_callback = [&](std::unique_ptr<Record> r) { |
| r->Dump(); |
| thread_tree.Update(*r); |
| if (r->type() == PERF_RECORD_SAMPLE) { |
| SampleRecord& sr = *static_cast<SampleRecord*>(r.get()); |
| bool in_kernel = sr.InKernel(); |
| if (sr.sample_type & PERF_SAMPLE_CALLCHAIN) { |
| PrintIndented(1, "callchain:\n"); |
| for (size_t i = 0; i < sr.callchain_data.ip_nr; ++i) { |
| if (sr.callchain_data.ips[i] >= PERF_CONTEXT_MAX) { |
| if (sr.callchain_data.ips[i] == PERF_CONTEXT_USER) { |
| in_kernel = false; |
| } |
| continue; |
| } |
| std::string dso_name; |
| std::string symbol_name; |
| uint64_t vaddr_in_file; |
| get_symbol_function(sr.tid_data.pid, sr.tid_data.tid, sr.callchain_data.ips[i], |
| dso_name, symbol_name, vaddr_in_file, in_kernel); |
| PrintIndented(2, "%s (%s[+%" PRIx64 "])\n", symbol_name.c_str(), dso_name.c_str(), |
| vaddr_in_file); |
| } |
| } |
| } else if (r->type() == SIMPLE_PERF_RECORD_CALLCHAIN) { |
| CallChainRecord& cr = *static_cast<CallChainRecord*>(r.get()); |
| PrintIndented(1, "callchain:\n"); |
| for (size_t i = 0; i < cr.ip_nr; ++i) { |
| std::string dso_name; |
| std::string symbol_name; |
| uint64_t vaddr_in_file; |
| get_symbol_function(cr.pid, cr.tid, cr.ips[i], dso_name, symbol_name, vaddr_in_file, |
| false); |
| PrintIndented(2, "%s (%s[+%" PRIx64 "])\n", symbol_name.c_str(), dso_name.c_str(), |
| vaddr_in_file); |
| } |
| } |
| return true; |
| }; |
| return record_file_reader_->ReadDataSection(record_callback); |
| } |
| |
| bool DumpRecordCommand::DumpFeatureSection() { |
| std::map<int, SectionDesc> section_map = record_file_reader_->FeatureSectionDescriptors(); |
| for (const auto& pair : section_map) { |
| int feature = pair.first; |
| const auto& section = pair.second; |
| printf("feature section for %s: offset %" PRId64 ", size %" PRId64 "\n", |
| GetFeatureNameOrUnknown(feature).c_str(), section.offset, section.size); |
| if (feature == FEAT_BUILD_ID) { |
| std::vector<BuildIdRecord> records = record_file_reader_->ReadBuildIdFeature(); |
| for (auto& r : records) { |
| r.Dump(1); |
| } |
| } else if (feature == FEAT_OSRELEASE) { |
| std::string s = record_file_reader_->ReadFeatureString(feature); |
| PrintIndented(1, "osrelease: %s\n", s.c_str()); |
| } else if (feature == FEAT_ARCH) { |
| std::string s = record_file_reader_->ReadFeatureString(feature); |
| PrintIndented(1, "arch: %s\n", s.c_str()); |
| } else if (feature == FEAT_CMDLINE) { |
| std::vector<std::string> cmdline = record_file_reader_->ReadCmdlineFeature(); |
| PrintIndented(1, "cmdline: %s\n", android::base::Join(cmdline, ' ').c_str()); |
| } else if (feature == FEAT_FILE) { |
| std::string file_path; |
| uint32_t file_type; |
| uint64_t min_vaddr; |
| std::vector<Symbol> symbols; |
| std::vector<uint64_t> dex_file_offsets; |
| size_t read_pos = 0; |
| PrintIndented(1, "file:\n"); |
| while (record_file_reader_->ReadFileFeature(read_pos, &file_path, |
| &file_type, &min_vaddr, |
| &symbols, &dex_file_offsets)) { |
| PrintIndented(2, "file_path %s\n", file_path.c_str()); |
| PrintIndented(2, "file_type %s\n", DsoTypeToString(static_cast<DsoType>(file_type))); |
| PrintIndented(2, "min_vaddr 0x%" PRIx64 "\n", min_vaddr); |
| PrintIndented(2, "symbols:\n"); |
| for (const auto& symbol : symbols) { |
| PrintIndented(3, "%s [0x%" PRIx64 "-0x%" PRIx64 "]\n", symbol.DemangledName(), |
| symbol.addr, symbol.addr + symbol.len); |
| } |
| if (file_type == static_cast<uint32_t>(DSO_DEX_FILE)) { |
| PrintIndented(2, "dex_file_offsets:\n"); |
| for (uint64_t offset : dex_file_offsets) { |
| PrintIndented(3, "0x%" PRIx64 "\n", offset); |
| } |
| } |
| } |
| } else if (feature == FEAT_META_INFO) { |
| std::unordered_map<std::string, std::string> info_map; |
| if (!record_file_reader_->ReadMetaInfoFeature(&info_map)) { |
| return false; |
| } |
| PrintIndented(1, "meta_info:\n"); |
| for (auto& pair : info_map) { |
| PrintIndented(2, "%s = %s\n", pair.first.c_str(), pair.second.c_str()); |
| } |
| } |
| } |
| return true; |
| } |
| |
| void RegisterDumpRecordCommand() { |
| RegisterCommand("dump", [] { return std::unique_ptr<Command>(new DumpRecordCommand); }); |
| } |