/* * Copyright (C) 2018 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 #include #include #include #include #include #include #include #include #include #include "CallChainJoiner.h" #include "command.h" #include "environment.h" #include "OfflineUnwinder.h" #include "perf_regs.h" #include "record_file.h" #include "thread_tree.h" #include "utils.h" #include "workload.h" using namespace simpleperf; // Cache size used by CallChainJoiner to cache call chains in memory. constexpr size_t DEFAULT_CALL_CHAIN_JOINER_CACHE_SIZE = 8 * 1024 * 1024; struct MemStat { std::string vm_peak; std::string vm_size; std::string vm_hwm; std::string vm_rss; std::string ToString() const { return android::base::StringPrintf("VmPeak:%s;VmSize:%s;VmHWM:%s;VmRSS:%s", vm_peak.c_str(), vm_size.c_str(), vm_hwm.c_str(), vm_rss.c_str()); } }; static bool GetMemStat(MemStat* stat) { std::string s; if (!android::base::ReadFileToString(android::base::StringPrintf("/proc/%d/status", getpid()), &s)) { PLOG(ERROR) << "Failed to read process status"; return false; } std::vector lines = android::base::Split(s, "\n"); for (auto& line : lines) { if (android::base::StartsWith(line, "VmPeak:")) { stat->vm_peak = android::base::Trim(line.substr(strlen("VmPeak:"))); } else if (android::base::StartsWith(line, "VmSize:")) { stat->vm_size = android::base::Trim(line.substr(strlen("VmSize:"))); } else if (android::base::StartsWith(line, "VmHWM:")) { stat->vm_hwm = android::base::Trim(line.substr(strlen("VmHWM:"))); } else if (android::base::StartsWith(line, "VmRSS:")) { stat->vm_rss = android::base::Trim(line.substr(strlen("VmRSS:"))); } } return true; } class DebugUnwindCommand : public Command { public: DebugUnwindCommand() : Command("debug-unwind", "Debug/test offline unwinding.", // clang-format off "Usage: simpleperf debug-unwind [options]\n" " Given a perf.data generated with \"-g --no-unwind\", it converts\n" " regs/stack data of samples into callchains, and write result into\n" " a new perf.data. The new perf.data can be passed to\n" " unwind_result_reporter.py to generate a text report.\n" "-i The path of record file generated with \"-g --no-unwind\".\n" " Default is perf.data.\n" "-o The path ot write new perf.data. Default is perf.data.debug.\n" "--symfs Look for files with symbols relative to this directory.\n" "--time time Only unwind samples recorded at selected time.\n" // clang-format on ), input_filename_("perf.data"), output_filename_("perf.data.debug"), offline_unwinder_(OfflineUnwinder::Create(true)), callchain_joiner_(DEFAULT_CALL_CHAIN_JOINER_CACHE_SIZE, 1, true), selected_time_(0) { } bool Run(const std::vector& args); private: bool ParseOptions(const std::vector& args); bool UnwindRecordFile(); bool ProcessRecord(Record* record); void CollectHitFileInfo(const SampleRecord& r, const std::vector& ips); bool JoinCallChains(); bool WriteFeatureSections(); void PrintStat(); struct Stat { // For testing unwinding performance. uint64_t unwinding_sample_count = 0u; uint64_t total_unwinding_time_in_ns = 0u; uint64_t max_unwinding_time_in_ns = 0u; // For memory consumption. MemStat mem_before_unwinding; MemStat mem_after_unwinding; }; std::string input_filename_; std::string output_filename_; std::unique_ptr reader_; std::unique_ptr writer_; ThreadTree thread_tree_; std::unique_ptr offline_unwinder_; CallChainJoiner callchain_joiner_; Stat stat_; uint64_t selected_time_; }; bool DebugUnwindCommand::Run(const std::vector& args) { // 1. Parse options. if (!ParseOptions(args)) { return false; } ScopedTempFiles scoped_temp_files(android::base::Dirname(output_filename_)); // 2. Read input perf.data, and generate new perf.data. if (!UnwindRecordFile()) { return false; } // 3. Show stat of unwinding. PrintStat(); return true; } bool DebugUnwindCommand::ParseOptions(const std::vector& args) { for (size_t i = 0; i < args.size(); ++i) { if (args[i] == "-i") { if (!NextArgumentOrError(args, &i)) { return false; } input_filename_ = args[i]; } else if (args[i] == "-o") { if (!NextArgumentOrError(args, &i)) { return false; } output_filename_ = args[i]; } else if (args[i] == "--symfs") { if (!NextArgumentOrError(args, &i)) { return false; } if (!Dso::SetSymFsDir(args[i])) { return false; } } else if (args[i] == "--time") { if (!GetUintOption(args, &i, &selected_time_)) { return false; } } else { ReportUnknownOption(args, i); return false; } } return true; } bool DebugUnwindCommand::UnwindRecordFile() { // 1. Check input file. reader_ = RecordFileReader::CreateInstance(input_filename_); if (!reader_) { return false; } reader_->LoadBuildIdAndFileFeatures(thread_tree_); std::string record_cmd = android::base::Join(reader_->ReadCmdlineFeature(), " "); if (record_cmd.find("--no-unwind") == std::string::npos || (record_cmd.find("-g") == std::string::npos && record_cmd.find("--call-graph dwarf") == std::string::npos)) { LOG(ERROR) << input_filename_ << " isn't recorded with \"-g --no-unwind\""; return false; } ScopedCurrentArch scoped_arch(GetArchType(reader_->ReadFeatureString(PerfFileFormat::FEAT_ARCH))); offline_unwinder_->LoadMetaInfo(reader_->GetMetaInfoFeature()); // 2. Copy attr section. writer_ = RecordFileWriter::CreateInstance(output_filename_); if (!writer_ || !writer_->WriteAttrSection(reader_->AttrSection())) { return false; } // 3. Process records in data section. if (!GetMemStat(&stat_.mem_before_unwinding)) { return false; } auto callback = [this](std::unique_ptr record) { return ProcessRecord(record.get()); }; if (!reader_->ReadDataSection(callback)) { return false; } if (!JoinCallChains()) { return false; } if (!GetMemStat(&stat_.mem_after_unwinding)) { return false; } // 4. Write feature sections. return WriteFeatureSections(); } bool DebugUnwindCommand::ProcessRecord(Record* record) { if (record->type() == PERF_RECORD_SAMPLE) { auto& r = *static_cast(record); if (selected_time_ != 0u && r.Timestamp() != selected_time_) { return true; } uint64_t need_type = PERF_SAMPLE_CALLCHAIN | PERF_SAMPLE_REGS_USER | PERF_SAMPLE_STACK_USER; if ((r.sample_type & need_type) == need_type && r.regs_user_data.reg_mask != 0 && r.GetValidStackSize() > 0) { ThreadEntry* thread = thread_tree_.FindThreadOrNew(r.tid_data.pid, r.tid_data.tid); RegSet regs(r.regs_user_data.abi, r.regs_user_data.reg_mask, r.regs_user_data.regs); std::vector ips; std::vector sps; if (!offline_unwinder_->UnwindCallChain(*thread, regs, r.stack_user_data.data, r.GetValidStackSize(), &ips, &sps)) { return false; } const UnwindingResult& unwinding_result = offline_unwinder_->GetUnwindingResult(); stat_.unwinding_sample_count++; stat_.total_unwinding_time_in_ns += unwinding_result.used_time; stat_.max_unwinding_time_in_ns = std::max(stat_.max_unwinding_time_in_ns, unwinding_result.used_time); if (!writer_->WriteRecord(UnwindingResultRecord(r.time_data.time, unwinding_result))) { return false; } // We want to keep both reg/stack data and callchain of a sample. However, storing both // can exceed the size limit of a SampleRecord. So instead we store one sample with reg/stack // data and one sample with callchain. if (!writer_->WriteRecord(r)) { return false; } r.ReplaceRegAndStackWithCallChain(ips); if (!callchain_joiner_.AddCallChain(r.tid_data.pid, r.tid_data.tid, CallChainJoiner::ORIGINAL_OFFLINE, ips, sps)) { return false; } CollectHitFileInfo(r, ips); } } else { thread_tree_.Update(*record); } return writer_->WriteRecord(*record); } void DebugUnwindCommand::CollectHitFileInfo(const SampleRecord& r, const std::vector& ips) { const ThreadEntry* thread = thread_tree_.FindThreadOrNew(r.tid_data.pid, r.tid_data.tid); for (auto ip : ips) { const MapEntry* map = thread_tree_.FindMap(thread, ip, false); Dso* dso = map->dso; if (!dso->HasDumpId() && dso->type() != DSO_UNKNOWN_FILE) { dso->CreateDumpId(); } const Symbol* symbol = thread_tree_.FindSymbol(map, ip, nullptr, &dso); if (!symbol->HasDumpId()) { dso->CreateSymbolDumpId(symbol); } } } bool DebugUnwindCommand::JoinCallChains() { // 1. Prepare joined callchains. if (!callchain_joiner_.JoinCallChains()) { return false; } // 2. Move records from record_filename_ to a temporary file. if (!writer_->Close()) { return false; } writer_.reset(); std::unique_ptr tmp_file = ScopedTempFiles::CreateTempFile(); if (!Workload::RunCmd({"mv", output_filename_, tmp_file->path})) { return false; } // 3. Read records from the temporary file, and write records with joined call chains back // to record_filename_. std::unique_ptr reader = RecordFileReader::CreateInstance(tmp_file->path); if (!reader) { return false; } writer_ = RecordFileWriter::CreateInstance(output_filename_); if (!writer_ || !writer_->WriteAttrSection(reader->AttrSection())) { return false; } auto record_callback = [&](std::unique_ptr r) { if (r->type() != PERF_RECORD_SAMPLE) { return writer_->WriteRecord(*r); } SampleRecord& sr = *static_cast(r.get()); if (!sr.HasUserCallChain()) { return writer_->WriteRecord(sr); } pid_t pid; pid_t tid; CallChainJoiner::ChainType type; std::vector ips; std::vector sps; do { if (!callchain_joiner_.GetNextCallChain(pid, tid, type, ips, sps)) { return false; } if (!writer_->WriteRecord(CallChainRecord(pid, tid, type, sr.Timestamp(), ips, sps))) { return false; } } while (type != CallChainJoiner::JOINED_OFFLINE); CHECK_EQ(pid, static_cast(sr.tid_data.pid)); CHECK_EQ(tid, static_cast(sr.tid_data.tid)); sr.UpdateUserCallChain(ips); return writer_->WriteRecord(sr); }; return reader->ReadDataSection(record_callback); } bool DebugUnwindCommand::WriteFeatureSections() { // Add debug_unwind info in META_INFO section, and add symbol info in FILE section. const std::map& features = reader_->FeatureSectionDescriptors(); size_t new_feature_count = features.size(); for (int feature : {PerfFileFormat::FEAT_FILE, PerfFileFormat::FEAT_META_INFO}) { if (features.find(feature) == features.end()) { new_feature_count++; } } if (!writer_->BeginWriteFeatures(new_feature_count)) { return false; } auto it = features.begin(); // Copy all feature sections except FEAT_FILE and FEAT_META_INFO, which require special handling. while (it != features.end() && it->first < PerfFileFormat::FEAT_FILE) { std::vector data; if (!reader_->ReadFeatureSection(it->first, &data) || !writer_->WriteFeature(it->first, data)) { return false; } ++it; } // Write a new file section. if (it != features.end() && it->first == PerfFileFormat::FEAT_FILE) { ++it; } if (!writer_->WriteFileFeatures(thread_tree_.GetAllDsos())) { return false; } // Write meta_info section. std::unordered_map info_map; if (it != features.end() && it->first == PerfFileFormat::FEAT_META_INFO) { info_map = reader_->GetMetaInfoFeature(); ++it; } info_map["debug_unwind"] = "true"; info_map["debug_unwind_mem_before"] = stat_.mem_before_unwinding.ToString(); info_map["debug_unwind_mem_after"] = stat_.mem_after_unwinding.ToString(); if (!writer_->WriteMetaInfoFeature(info_map)) { return false; } CHECK(it == features.end()); return writer_->EndWriteFeatures() && writer_->Close(); } void DebugUnwindCommand::PrintStat() { printf("Unwinding sample count: %" PRIu64 "\n", stat_.unwinding_sample_count); if (stat_.unwinding_sample_count > 0u) { printf("Average unwinding time: %f us\n", static_cast(stat_.total_unwinding_time_in_ns) / 1000 / stat_.unwinding_sample_count); printf("Max unwinding time: %f us\n", static_cast(stat_.max_unwinding_time_in_ns) / 1000); } printf("Memory change:\n"); PrintIndented(1, "VmPeak: %s -> %s\n", stat_.mem_before_unwinding.vm_peak.c_str(), stat_.mem_after_unwinding.vm_peak.c_str()); PrintIndented(1, "VmSize: %s -> %s\n", stat_.mem_before_unwinding.vm_size.c_str(), stat_.mem_after_unwinding.vm_size.c_str()); PrintIndented(1, "VmHWM: %s -> %s\n", stat_.mem_before_unwinding.vm_hwm.c_str(), stat_.mem_after_unwinding.vm_hwm.c_str()); PrintIndented(1, "VmRSS: %s -> %s\n", stat_.mem_before_unwinding.vm_rss.c_str(), stat_.mem_after_unwinding.vm_rss.c_str()); callchain_joiner_.DumpStat(); printf("Please use debug_unwind_reporter.py to get a report in details.\n"); } namespace simpleperf { void RegisterDebugUnwindCommand() { RegisterCommand("debug-unwind", []{ return std::unique_ptr(new DebugUnwindCommand()); }); } } // namespace simpleperf