blob: 52d3c96c3decff2e61c72eb460f722649aeddbe4 [file] [log] [blame]
/*
* 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 <stdio.h>
#include <algorithm>
#include <memory>
#include <string>
#include <unordered_map>
#include <unordered_set>
#include <vector>
#include <android-base/file.h>
#include <android-base/logging.h>
#include <android-base/parseint.h>
#include <android-base/stringprintf.h>
#include <android-base/strings.h>
#include "JITDebugReader.h"
#include "OfflineUnwinder.h"
#include "command.h"
#include "environment.h"
#include "perf_regs.h"
#include "record_file.h"
#include "report_utils.h"
#include "thread_tree.h"
#include "utils.h"
namespace simpleperf {
namespace {
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<std::string> 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;
}
struct UnwindingStat {
// 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;
void AddUnwindingResult(const UnwindingResult& result) {
unwinding_sample_count++;
total_unwinding_time_in_ns += result.used_time;
max_unwinding_time_in_ns = std::max(max_unwinding_time_in_ns, result.used_time);
}
void Dump(FILE* fp) {
if (unwinding_sample_count == 0) {
return;
}
fprintf(fp, "unwinding_sample_count: %" PRIu64 "\n", unwinding_sample_count);
fprintf(fp, "average_unwinding_time: %.3f us\n",
total_unwinding_time_in_ns / 1e3 / unwinding_sample_count);
fprintf(fp, "max_unwinding_time: %.3f us\n", max_unwinding_time_in_ns / 1e3);
if (!mem_before_unwinding.vm_peak.empty()) {
fprintf(fp, "memory_change_VmPeak: %s -> %s\n", mem_before_unwinding.vm_peak.c_str(),
mem_after_unwinding.vm_peak.c_str());
fprintf(fp, "memory_change_VmSize: %s -> %s\n", mem_before_unwinding.vm_size.c_str(),
mem_after_unwinding.vm_size.c_str());
fprintf(fp, "memory_change_VmHwM: %s -> %s\n", mem_before_unwinding.vm_hwm.c_str(),
mem_after_unwinding.vm_hwm.c_str());
fprintf(fp, "memory_change_VmRSS: %s -> %s\n", mem_before_unwinding.vm_rss.c_str(),
mem_after_unwinding.vm_rss.c_str());
}
}
};
class RecordFileProcessor {
public:
RecordFileProcessor(const std::string& output_filename, bool output_binary_mode)
: output_filename_(output_filename),
output_binary_mode_(output_binary_mode),
unwinder_(OfflineUnwinder::Create(true)),
callchain_report_builder_(thread_tree_) {}
virtual ~RecordFileProcessor() {
if (out_fp_ != nullptr && out_fp_ != stdout) {
fclose(out_fp_);
}
}
bool ProcessFile(const std::string& input_filename) {
// 1. Check input file.
record_filename_ = input_filename;
reader_ = RecordFileReader::CreateInstance(record_filename_);
if (!reader_) {
return false;
}
std::string record_cmd = android::base::Join(reader_->ReadCmdlineFeature(), " ");
if (record_cmd.find("-g") == std::string::npos &&
record_cmd.find("--call-graph dwarf") == std::string::npos) {
LOG(ERROR) << "file isn't recorded with dwarf call graph: " << record_filename_;
return false;
}
if (!CheckRecordCmd(record_cmd)) {
return false;
}
// 2. Load feature sections.
if (!reader_->LoadBuildIdAndFileFeatures(thread_tree_)) {
return false;
}
ScopedCurrentArch scoped_arch(
GetArchType(reader_->ReadFeatureString(PerfFileFormat::FEAT_ARCH)));
unwinder_->LoadMetaInfo(reader_->GetMetaInfoFeature());
if (reader_->HasFeature(PerfFileFormat::FEAT_DEBUG_UNWIND) &&
reader_->HasFeature(PerfFileFormat::FEAT_DEBUG_UNWIND_FILE)) {
auto debug_unwind_feature = reader_->ReadDebugUnwindFeature();
if (!debug_unwind_feature.has_value()) {
return false;
}
uint64_t offset =
reader_->FeatureSectionDescriptors().at(PerfFileFormat::FEAT_DEBUG_UNWIND_FILE).offset;
for (DebugUnwindFile& file : debug_unwind_feature.value()) {
auto& loc = debug_unwind_files_[file.path];
loc.offset = offset;
loc.size = file.size;
offset += file.size;
}
}
callchain_report_builder_.SetRemoveArtFrame(false);
callchain_report_builder_.SetConvertJITFrame(false);
// 3. Open output file.
if (output_filename_.empty()) {
out_fp_ = stdout;
} else {
out_fp_ = fopen(output_filename_.c_str(), output_binary_mode_ ? "web+" : "we+");
if (out_fp_ == nullptr) {
PLOG(ERROR) << "failed to write to " << output_filename_;
return false;
}
}
// 4. Process records.
return Process();
}
protected:
struct DebugUnwindFileLocation {
uint64_t offset;
uint64_t size;
};
virtual bool CheckRecordCmd(const std::string& record_cmd) = 0;
virtual bool Process() = 0;
std::string record_filename_;
std::unique_ptr<RecordFileReader> reader_;
std::string output_filename_;
bool output_binary_mode_;
FILE* out_fp_ = nullptr;
ThreadTree thread_tree_;
std::unique_ptr<OfflineUnwinder> unwinder_;
// Files stored in DEBUG_UNWIND_FILE feature section in the recording file.
// Map from file path to offset in the recording file.
std::unordered_map<std::string, DebugUnwindFileLocation> debug_unwind_files_;
CallChainReportBuilder callchain_report_builder_;
};
static void DumpUnwindingResult(const UnwindingResult& result, FILE* fp) {
fprintf(fp, "unwinding_used_time: %.3f us\n", result.used_time / 1e3);
fprintf(fp, "unwinding_error_code: %" PRIu64 "\n", result.error_code);
fprintf(fp, "unwinding_error_addr: 0x%" PRIx64 "\n", result.error_addr);
fprintf(fp, "stack_start: 0x%" PRIx64 "\n", result.stack_start);
fprintf(fp, "stack_end: 0x%" PRIx64 "\n", result.stack_end);
}
class SampleUnwinder : public RecordFileProcessor {
public:
SampleUnwinder(const std::string& output_filename,
const std::unordered_set<uint64_t>& sample_times, bool skip_sample_print)
: RecordFileProcessor(output_filename, false),
sample_times_(sample_times),
skip_sample_print_(skip_sample_print) {}
protected:
bool CheckRecordCmd(const std::string& record_cmd) override {
if (record_cmd.find("--no-unwind") == std::string::npos &&
record_cmd.find("--keep-failed-unwinding-debug-info") == std::string::npos) {
LOG(ERROR) << "file isn't record with --no-unwind or --keep-failed-unwinding-debug-info: "
<< record_filename_;
return false;
}
return true;
}
bool Process() override {
if (!GetMemStat(&stat_.mem_before_unwinding)) {
return false;
}
if (!reader_->ReadDataSection(
[&](std::unique_ptr<Record> r) { return ProcessRecord(std::move(r)); })) {
return false;
}
if (!GetMemStat(&stat_.mem_after_unwinding)) {
return false;
}
stat_.Dump(out_fp_);
return true;
}
bool ProcessRecord(std::unique_ptr<Record> r) {
UpdateRecord(r.get());
thread_tree_.Update(*r);
if (r->type() == SIMPLE_PERF_RECORD_UNWINDING_RESULT) {
last_unwinding_result_.reset(static_cast<UnwindingResultRecord*>(r.release()));
} else if (r->type() == PERF_RECORD_SAMPLE) {
if (sample_times_.empty() || sample_times_.count(r->Timestamp())) {
auto& sr = *static_cast<SampleRecord*>(r.get());
const PerfSampleStackUserType* stack = &sr.stack_user_data;
const PerfSampleRegsUserType* regs = &sr.regs_user_data;
if (last_unwinding_result_ && last_unwinding_result_->Timestamp() == sr.Timestamp()) {
stack = &last_unwinding_result_->stack_user_data;
regs = &last_unwinding_result_->regs_user_data;
}
if (stack->size > 0 || regs->reg_mask > 0) {
if (!UnwindRecord(sr, *regs, *stack)) {
return false;
}
}
}
last_unwinding_result_.reset();
}
return true;
}
void UpdateRecord(Record* record) {
if (record->type() == PERF_RECORD_MMAP) {
UpdateMmapRecordForEmbeddedFiles(*static_cast<MmapRecord*>(record));
} else if (record->type() == PERF_RECORD_MMAP2) {
UpdateMmapRecordForEmbeddedFiles(*static_cast<Mmap2Record*>(record));
}
}
template <typename MmapRecordType>
void UpdateMmapRecordForEmbeddedFiles(MmapRecordType& record) {
// Modify mmap records to point to files stored in DEBUG_UNWIND_FILE feature section.
std::string filename = record.filename;
if (auto it = debug_unwind_files_.find(filename); it != debug_unwind_files_.end()) {
auto data = *record.data;
uint64_t old_pgoff = data.pgoff;
if (JITDebugReader::IsPathInJITSymFile(filename)) {
data.pgoff = it->second.offset;
} else {
data.pgoff += it->second.offset;
}
debug_unwind_dsos_[data.pgoff] =
std::make_pair(thread_tree_.FindUserDsoOrNew(filename), old_pgoff);
record.SetDataAndFilename(data, record_filename_);
}
}
bool UnwindRecord(const SampleRecord& r, const PerfSampleRegsUserType& regs,
const PerfSampleStackUserType& stack) {
ThreadEntry* thread = thread_tree_.FindThreadOrNew(r.tid_data.pid, r.tid_data.tid);
RegSet reg_set(regs.abi, regs.reg_mask, regs.regs);
std::vector<uint64_t> ips;
std::vector<uint64_t> sps;
if (!unwinder_->UnwindCallChain(*thread, reg_set, stack.data, stack.size, &ips, &sps)) {
return false;
}
stat_.AddUnwindingResult(unwinder_->GetUnwindingResult());
if (!skip_sample_print_) {
// Print unwinding result.
fprintf(out_fp_, "sample_time: %" PRIu64 "\n", r.Timestamp());
DumpUnwindingResult(unwinder_->GetUnwindingResult(), out_fp_);
std::vector<CallChainReportEntry> entries = callchain_report_builder_.Build(thread, ips, 0);
for (size_t i = 0; i < entries.size(); i++) {
size_t id = i + 1;
auto& entry = entries[i];
fprintf(out_fp_, "ip_%zu: 0x%" PRIx64 "\n", id, entry.ip);
fprintf(out_fp_, "sp_%zu: 0x%" PRIx64 "\n", id, sps[i]);
Dso* dso = entry.map->dso;
uint64_t pgoff = entry.map->pgoff;
if (dso->Path() == record_filename_) {
auto it = debug_unwind_dsos_.find(entry.map->pgoff);
CHECK(it != debug_unwind_dsos_.end());
const auto& p = it->second;
dso = p.first;
pgoff = p.second;
if (!JITDebugReader::IsPathInJITSymFile(dso->Path())) {
entry.vaddr_in_file = dso->IpToVaddrInFile(entry.ip, entry.map->start_addr, pgoff);
}
entry.symbol = dso->FindSymbol(entry.vaddr_in_file);
}
fprintf(out_fp_, "map_%zu: [0x%" PRIx64 "-0x%" PRIx64 "], pgoff 0x%" PRIx64 "\n", id,
entry.map->start_addr, entry.map->get_end_addr(), pgoff);
fprintf(out_fp_, "dso_%zu: %s\n", id, dso->Path().c_str());
fprintf(out_fp_, "vaddr_in_file_%zu: 0x%" PRIx64 "\n", id, entry.vaddr_in_file);
fprintf(out_fp_, "symbol_%zu: %s\n", id, entry.symbol->DemangledName());
}
fprintf(out_fp_, "\n");
}
return true;
}
private:
const std::unordered_set<uint64_t> sample_times_;
bool skip_sample_print_;
// Map from offset in recording file to the corresponding debug_unwind_file.
std::unordered_map<uint64_t, std::pair<Dso*, uint64_t>> debug_unwind_dsos_;
UnwindingStat stat_;
std::unique_ptr<UnwindingResultRecord> last_unwinding_result_;
};
class TestFileGenerator : public RecordFileProcessor {
public:
TestFileGenerator(const std::string& output_filename,
const std::unordered_set<uint64_t>& sample_times,
const std::unordered_set<std::string>& kept_binaries)
: RecordFileProcessor(output_filename, true),
sample_times_(sample_times),
kept_binaries_(kept_binaries) {}
protected:
bool CheckRecordCmd(const std::string&) override { return true; }
bool Process() override {
writer_.reset(new RecordFileWriter(output_filename_, out_fp_, false));
if (!writer_ || !writer_->WriteAttrSection(reader_->AttrSection())) {
return false;
}
if (!reader_->ReadDataSection(
[&](std::unique_ptr<Record> r) { return ProcessRecord(std::move(r)); })) {
return false;
}
return WriteFeatureSections();
}
bool ProcessRecord(std::unique_ptr<Record> r) {
thread_tree_.Update(*r);
bool keep_record = false;
if (r->type() == SIMPLE_PERF_RECORD_UNWINDING_RESULT) {
keep_record = (sample_times_.count(r->Timestamp()) > 0);
} else if (r->type() == PERF_RECORD_SAMPLE) {
keep_record = (sample_times_.count(r->Timestamp()) > 0);
if (keep_record) {
// Dump maps needed to unwind this sample.
if (!WriteMapsForSample(*static_cast<SampleRecord*>(r.get()))) {
return false;
}
}
}
if (keep_record) {
return writer_->WriteRecord(*r);
}
return true;
}
bool WriteMapsForSample(const SampleRecord& r) {
ThreadEntry* thread = thread_tree_.FindThread(r.tid_data.tid);
if (thread != nullptr && thread->maps) {
const EventAttrIds& attrs = reader_->AttrSection();
const perf_event_attr& attr = attrs[0].attr;
uint64_t event_id = attrs[0].ids[0];
for (const auto& p : thread->maps->maps) {
const MapEntry* map = p.second;
Mmap2Record map_record(attr, false, r.tid_data.pid, r.tid_data.tid, map->start_addr,
map->len, map->pgoff, map->flags, map->dso->Path(), event_id,
r.Timestamp());
if (!writer_->WriteRecord(map_record)) {
return false;
}
}
}
return true;
}
bool WriteFeatureSections() {
if (!writer_->BeginWriteFeatures(reader_->FeatureSectionDescriptors().size())) {
return false;
}
std::unordered_set<int> feature_types_to_copy = {
PerfFileFormat::FEAT_ARCH, PerfFileFormat::FEAT_CMDLINE, PerfFileFormat::FEAT_META_INFO};
const size_t BUFFER_SIZE = 64 * kKilobyte;
std::string buffer(BUFFER_SIZE, '\0');
for (const auto& p : reader_->FeatureSectionDescriptors()) {
auto feat_type = p.first;
if (feat_type == PerfFileFormat::FEAT_DEBUG_UNWIND) {
DebugUnwindFeature feature;
buffer.resize(BUFFER_SIZE);
for (const auto& file_p : debug_unwind_files_) {
if (kept_binaries_.count(file_p.first)) {
feature.resize(feature.size() + 1);
feature.back().path = file_p.first;
feature.back().size = file_p.second.size;
if (!CopyDebugUnwindFile(file_p.second, buffer)) {
return false;
}
}
}
if (!writer_->WriteDebugUnwindFeature(feature)) {
return false;
}
} else if (feat_type == PerfFileFormat::FEAT_FILE ||
feat_type == PerfFileFormat::FEAT_FILE2) {
uint64_t read_pos = 0;
FileFeature file_feature;
bool error = false;
while (reader_->ReadFileFeature(read_pos, file_feature, error)) {
if (kept_binaries_.count(file_feature.path) && !writer_->WriteFileFeature(file_feature)) {
return false;
}
}
if (error) {
return false;
}
} else if (feat_type == PerfFileFormat::FEAT_BUILD_ID) {
std::vector<BuildIdRecord> build_ids = reader_->ReadBuildIdFeature();
std::vector<BuildIdRecord> write_build_ids;
for (auto& build_id : build_ids) {
if (kept_binaries_.count(build_id.filename)) {
write_build_ids.emplace_back(std::move(build_id));
}
}
if (!writer_->WriteBuildIdFeature(write_build_ids)) {
return false;
}
} else if (feature_types_to_copy.count(feat_type)) {
if (!reader_->ReadFeatureSection(feat_type, &buffer) ||
!writer_->WriteFeature(feat_type, buffer.data(), buffer.size())) {
return false;
}
}
}
return writer_->EndWriteFeatures() && writer_->Close();
}
bool CopyDebugUnwindFile(const DebugUnwindFileLocation& loc, std::string& buffer) {
uint64_t offset = loc.offset;
uint64_t left_size = loc.size;
while (left_size > 0) {
size_t nread = std::min<size_t>(left_size, buffer.size());
if (!reader_->ReadAtOffset(offset, buffer.data(), nread) ||
!writer_->WriteFeature(PerfFileFormat::FEAT_DEBUG_UNWIND_FILE, buffer.data(), nread)) {
return false;
}
offset += nread;
left_size -= nread;
}
return true;
}
private:
const std::unordered_set<uint64_t> sample_times_;
const std::unordered_set<std::string> kept_binaries_;
std::unique_ptr<RecordFileWriter> writer_;
};
class ReportGenerator : public RecordFileProcessor {
public:
ReportGenerator(const std::string& output_filename)
: RecordFileProcessor(output_filename, false) {}
protected:
bool CheckRecordCmd(const std::string& record_cmd) override {
if (record_cmd.find("--keep-failed-unwinding-debug-info") == std::string::npos &&
record_cmd.find("--keep-failed-unwinding-result") == std::string::npos) {
LOG(ERROR) << "file isn't record with --keep-failed-unwinding-debug-info or "
<< "--keep-failed-unwinding-result: " << record_filename_;
return false;
}
return true;
}
bool Process() override {
if (!reader_->ReadDataSection(
[&](std::unique_ptr<Record> r) { return ProcessRecord(std::move(r)); })) {
return false;
}
return true;
}
private:
bool ProcessRecord(std::unique_ptr<Record> r) {
thread_tree_.Update(*r);
if (r->type() == SIMPLE_PERF_RECORD_UNWINDING_RESULT) {
last_unwinding_result_.reset(static_cast<UnwindingResultRecord*>(r.release()));
} else if (r->type() == PERF_RECORD_SAMPLE) {
if (last_unwinding_result_) {
ReportUnwindingResult(*static_cast<SampleRecord*>(r.get()), *last_unwinding_result_);
last_unwinding_result_.reset();
}
}
return true;
}
void ReportUnwindingResult(const SampleRecord& sr, const UnwindingResultRecord& unwinding_r) {
ThreadEntry* thread = thread_tree_.FindThreadOrNew(sr.tid_data.pid, sr.tid_data.tid);
size_t kernel_ip_count;
std::vector<uint64_t> ips = sr.GetCallChain(&kernel_ip_count);
if (kernel_ip_count != 0) {
ips.erase(ips.begin(), ips.begin() + kernel_ip_count);
}
fprintf(out_fp_, "sample_time: %" PRIu64 "\n", sr.Timestamp());
DumpUnwindingResult(unwinding_r.unwinding_result, out_fp_);
// Print callchain.
std::vector<CallChainReportEntry> entries = callchain_report_builder_.Build(thread, ips, 0);
for (size_t i = 0; i < entries.size(); i++) {
size_t id = i + 1;
const auto& entry = entries[i];
fprintf(out_fp_, "ip_%zu: 0x%" PRIx64 "\n", id, entry.ip);
if (i < unwinding_r.callchain.length) {
fprintf(out_fp_, "unwinding_ip_%zu: 0x%" PRIx64 "\n", id, unwinding_r.callchain.ips[i]);
fprintf(out_fp_, "unwinding_sp_%zu: 0x%" PRIx64 "\n", id, unwinding_r.callchain.sps[i]);
}
fprintf(out_fp_, "map_%zu: [0x%" PRIx64 "-0x%" PRIx64 "], pgoff 0x%" PRIx64 "\n", id,
entry.map->start_addr, entry.map->get_end_addr(), entry.map->pgoff);
fprintf(out_fp_, "dso_%zu: %s\n", id, entry.map->dso->Path().c_str());
fprintf(out_fp_, "vaddr_in_file_%zu: 0x%" PRIx64 "\n", id, entry.vaddr_in_file);
fprintf(out_fp_, "symbol_%zu: %s\n", id, entry.symbol->DemangledName());
}
// Print regs.
uint64_t stack_addr = 0;
if (unwinding_r.regs_user_data.reg_nr > 0) {
auto& reg_data = unwinding_r.regs_user_data;
RegSet regs(reg_data.abi, reg_data.reg_mask, reg_data.regs);
uint64_t value;
if (regs.GetSpRegValue(&value)) {
stack_addr = value;
for (size_t i = 0; i < 64; i++) {
if (regs.GetRegValue(i, &value)) {
fprintf(out_fp_, "reg_%s: 0x%" PRIx64 "\n", GetRegName(i, regs.arch).c_str(), value);
}
}
}
}
// Print stack.
if (unwinding_r.stack_user_data.size > 0) {
auto& stack = unwinding_r.stack_user_data;
const char* p = stack.data;
const char* end = stack.data + stack.size;
uint64_t value;
while (p + 8 <= end) {
fprintf(out_fp_, "stack_%" PRIx64 ":", stack_addr);
for (size_t i = 0; i < 4 && p + 8 <= end; ++i) {
MoveFromBinaryFormat(value, p);
fprintf(out_fp_, " %016" PRIx64, value);
}
fprintf(out_fp_, "\n");
stack_addr += 32;
}
fprintf(out_fp_, "\n");
}
}
std::unique_ptr<UnwindingResultRecord> last_unwinding_result_;
};
class DebugUnwindCommand : public Command {
public:
DebugUnwindCommand()
: Command(
"debug-unwind", "Debug/test offline unwinding.",
// clang-format off
"Usage: simpleperf debug-unwind [options]\n"
"--generate-report Generate a failed unwinding report.\n"
"--generate-test-file Generate a test file with only one sample.\n"
"-i <file> Input recording file. Default is perf.data.\n"
"-o <file> Output file. Default is stdout.\n"
"--keep-binaries-in-test-file binary1,binary2... Keep binaries in test file.\n"
"--sample-time time1,time2... Only process samples recorded at selected times.\n"
"--symfs <dir> Look for files with symbols relative to this directory.\n"
"--unwind-sample Unwind samples.\n"
"--skip-sample-print Skip printing unwound samples.\n"
"\n"
"Examples:\n"
"1. Unwind a sample.\n"
"$ simpleperf debug-unwind -i perf.data --unwind-sample --sample-time 626970493946976\n"
" perf.data should be generated with \"--no-unwind\" or \"--keep-failed-unwinding-debug-info\".\n"
"2. Generate a test file.\n"
"$ simpleperf debug-unwind -i perf.data --generate-test-file -o test.data --sample-time \\\n"
" 626970493946976 --keep-binaries-in-test-file perf.data_jit_app_cache:255984-259968\n"
"3. Generate a failed unwinding report.\n"
"$ simpleperf debug-unwind -i perf.data --generate-report -o report.txt\n"
" perf.data should be generated with \"--keep-failed-unwinding-debug-info\" or \\\n"
" \"--keep-failed-unwinding-result\".\n"
"\n"
// clang-format on
) {}
bool Run(const std::vector<std::string>& args);
private:
bool ParseOptions(const std::vector<std::string>& args);
std::string input_filename_ = "perf.data";
std::string output_filename_;
bool unwind_sample_ = false;
bool skip_sample_print_ = false;
bool generate_report_ = false;
bool generate_test_file_;
std::unordered_set<std::string> kept_binaries_in_test_file_;
std::unordered_set<uint64_t> sample_times_;
};
bool DebugUnwindCommand::Run(const std::vector<std::string>& args) {
// 1. Parse options.
if (!ParseOptions(args)) {
return false;
}
// 2. Distribute sub commands.
if (unwind_sample_) {
SampleUnwinder sample_unwinder(output_filename_, sample_times_, skip_sample_print_);
return sample_unwinder.ProcessFile(input_filename_);
}
if (generate_test_file_) {
TestFileGenerator test_file_generator(output_filename_, sample_times_,
kept_binaries_in_test_file_);
return test_file_generator.ProcessFile(input_filename_);
}
if (generate_report_) {
ReportGenerator report_generator(output_filename_);
return report_generator.ProcessFile(input_filename_);
}
return true;
}
bool DebugUnwindCommand::ParseOptions(const std::vector<std::string>& args) {
const OptionFormatMap option_formats = {
{"--generate-report", {OptionValueType::NONE, OptionType::SINGLE}},
{"--generate-test-file", {OptionValueType::NONE, OptionType::SINGLE}},
{"-i", {OptionValueType::STRING, OptionType::SINGLE}},
{"--keep-binaries-in-test-file", {OptionValueType::STRING, OptionType::MULTIPLE}},
{"-o", {OptionValueType::STRING, OptionType::SINGLE}},
{"--sample-time", {OptionValueType::STRING, OptionType::MULTIPLE}},
{"--skip-sample-print", {OptionValueType::NONE, OptionType::SINGLE}},
{"--symfs", {OptionValueType::STRING, OptionType::MULTIPLE}},
{"--unwind-sample", {OptionValueType::NONE, OptionType::SINGLE}},
};
OptionValueMap options;
std::vector<std::pair<OptionName, OptionValue>> ordered_options;
if (!PreprocessOptions(args, option_formats, &options, &ordered_options)) {
return false;
}
generate_report_ = options.PullBoolValue("--generate-report");
generate_test_file_ = options.PullBoolValue("--generate-test-file");
options.PullStringValue("-i", &input_filename_);
for (auto& value : options.PullValues("--keep-binaries-in-test-file")) {
std::vector<std::string> binaries = android::base::Split(*value.str_value, ",");
kept_binaries_in_test_file_.insert(binaries.begin(), binaries.end());
}
skip_sample_print_ = options.PullBoolValue("--skip-sample-print");
options.PullStringValue("-o", &output_filename_);
for (auto& value : options.PullValues("--sample-time")) {
auto times = ParseUintVector<uint64_t>(*value.str_value);
if (!times) {
return false;
}
sample_times_.insert(times.value().begin(), times.value().end());
}
if (auto value = options.PullValue("--symfs"); value) {
if (!Dso::SetSymFsDir(*value->str_value)) {
return false;
}
}
unwind_sample_ = options.PullBoolValue("--unwind-sample");
CHECK(options.values.empty());
if (generate_test_file_) {
if (output_filename_.empty()) {
LOG(ERROR) << "no output path for generated test file";
return false;
}
if (sample_times_.empty()) {
LOG(ERROR) << "no samples are selected via --sample-time";
return false;
}
}
return true;
}
} // namespace
void RegisterDebugUnwindCommand() {
RegisterCommand("debug-unwind",
[] { return std::unique_ptr<Command>(new DebugUnwindCommand()); });
}
} // namespace simpleperf