You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
715 lines
26 KiB
715 lines
26 KiB
/*
|
|
* 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.
|
|
reader_->LoadBuildIdAndFileFeatures(thread_tree_);
|
|
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)
|
|
: RecordFileProcessor(output_filename, false), sample_times_(sample_times) {}
|
|
|
|
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 {
|
|
recording_file_dso_ = Dso::CreateDso(DSO_ELF_FILE, record_filename_);
|
|
|
|
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) {
|
|
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;
|
|
}
|
|
|
|
bool UnwindRecord(const SampleRecord& r, const PerfSampleRegsUserType& regs,
|
|
const PerfSampleStackUserType& stack) {
|
|
ThreadEntry* thread = thread_tree_.FindThreadOrNew(r.tid_data.pid, r.tid_data.tid);
|
|
ThreadEntry thread_with_new_maps = CreateThreadWithUpdatedMaps(*thread);
|
|
|
|
RegSet reg_set(regs.abi, regs.reg_mask, regs.regs);
|
|
std::vector<uint64_t> ips;
|
|
std::vector<uint64_t> sps;
|
|
if (!unwinder_->UnwindCallChain(thread_with_new_maps, reg_set, stack.data, stack.size, &ips,
|
|
&sps)) {
|
|
return false;
|
|
}
|
|
stat_.AddUnwindingResult(unwinder_->GetUnwindingResult());
|
|
|
|
// 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;
|
|
const 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]);
|
|
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());
|
|
}
|
|
fprintf(out_fp_, "\n");
|
|
return true;
|
|
}
|
|
|
|
// To use files stored in DEBUG_UNWIND_FILE feature section, create maps mapping to them.
|
|
ThreadEntry CreateThreadWithUpdatedMaps(const ThreadEntry& thread) {
|
|
ThreadEntry new_thread = thread;
|
|
new_thread.maps.reset(new MapSet);
|
|
new_thread.maps->version = thread.maps->version;
|
|
for (auto& p : thread.maps->maps) {
|
|
const MapEntry* old_map = p.second;
|
|
MapEntry* map = nullptr;
|
|
const std::string& path = old_map->dso->Path();
|
|
if (auto it = debug_unwind_files_.find(path); it != debug_unwind_files_.end()) {
|
|
map_storage_.emplace_back(new MapEntry);
|
|
map = map_storage_.back().get();
|
|
*map = *old_map;
|
|
map->dso = recording_file_dso_.get();
|
|
if (JITDebugReader::IsPathInJITSymFile(old_map->dso->Path())) {
|
|
map->pgoff = it->second.offset;
|
|
} else {
|
|
map->pgoff += it->second.offset;
|
|
}
|
|
} else {
|
|
map = const_cast<MapEntry*>(p.second);
|
|
}
|
|
new_thread.maps->maps[p.first] = map;
|
|
}
|
|
return new_thread;
|
|
}
|
|
|
|
private:
|
|
const std::unordered_set<uint64_t> sample_times_;
|
|
std::unique_ptr<Dso> recording_file_dso_;
|
|
std::vector<std::unique_ptr<MapEntry>> map_storage_;
|
|
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) {
|
|
auto attr = reader_->AttrSection()[0].attr;
|
|
auto event_id = reader_->AttrSection()[0].ids[0];
|
|
|
|
size_t kernel_ip_count;
|
|
std::vector<uint64_t> ips = r.GetCallChain(&kernel_ip_count);
|
|
for (size_t i = kernel_ip_count; i < ips.size(); i++) {
|
|
const MapEntry* map = thread_tree_.FindMap(thread, ips[i], false);
|
|
if (!thread_tree_.IsUnknownDso(map->dso)) {
|
|
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 * 1024;
|
|
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) {
|
|
size_t read_pos = 0;
|
|
FileFeature file_feature;
|
|
while (reader_->ReadFileFeature(read_pos, &file_feature)) {
|
|
if (kept_binaries_.count(file_feature.path) && !writer_->WriteFileFeature(file_feature)) {
|
|
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"
|
|
"\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 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_);
|
|
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}},
|
|
{"--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());
|
|
}
|
|
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
|