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.
716 lines
26 KiB
716 lines
26 KiB
/* |
|
* Copyright (C) 2016 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 "command.h" |
|
|
|
#include <unordered_map> |
|
|
|
#include <android-base/logging.h> |
|
#include <android-base/strings.h> |
|
|
|
#include "callchain.h" |
|
#include "event_attr.h" |
|
#include "event_type.h" |
|
#include "record_file.h" |
|
#include "sample_tree.h" |
|
#include "tracing.h" |
|
#include "utils.h" |
|
|
|
namespace { |
|
|
|
struct SlabSample { |
|
const Symbol* symbol; // the function making allocation |
|
uint64_t ptr; // the start address of the allocated space |
|
uint64_t bytes_req; // requested space size |
|
uint64_t bytes_alloc; // allocated space size |
|
uint64_t sample_count; // count of allocations |
|
uint64_t gfp_flags; // flags used for allocation |
|
uint64_t cross_cpu_allocations; // count of allocations freed not on the |
|
// cpu allocating them |
|
CallChainRoot<SlabSample> callchain; // a callchain tree representing all |
|
// callchains in this sample |
|
SlabSample(const Symbol* symbol, uint64_t ptr, uint64_t bytes_req, |
|
uint64_t bytes_alloc, uint64_t sample_count, uint64_t gfp_flags, |
|
uint64_t cross_cpu_allocations) |
|
: symbol(symbol), |
|
ptr(ptr), |
|
bytes_req(bytes_req), |
|
bytes_alloc(bytes_alloc), |
|
sample_count(sample_count), |
|
gfp_flags(gfp_flags), |
|
cross_cpu_allocations(cross_cpu_allocations) {} |
|
|
|
uint64_t GetPeriod() const { |
|
return sample_count; |
|
} |
|
}; |
|
|
|
struct SlabAccumulateInfo { |
|
uint64_t bytes_req; |
|
uint64_t bytes_alloc; |
|
}; |
|
|
|
BUILD_COMPARE_VALUE_FUNCTION(ComparePtr, ptr); |
|
BUILD_COMPARE_VALUE_FUNCTION_REVERSE(CompareBytesReq, bytes_req); |
|
BUILD_COMPARE_VALUE_FUNCTION_REVERSE(CompareBytesAlloc, bytes_alloc); |
|
BUILD_COMPARE_VALUE_FUNCTION(CompareGfpFlags, gfp_flags); |
|
BUILD_COMPARE_VALUE_FUNCTION_REVERSE(CompareCrossCpuAllocations, |
|
cross_cpu_allocations); |
|
|
|
BUILD_DISPLAY_HEX64_FUNCTION(DisplayPtr, ptr); |
|
BUILD_DISPLAY_UINT64_FUNCTION(DisplayBytesReq, bytes_req); |
|
BUILD_DISPLAY_UINT64_FUNCTION(DisplayBytesAlloc, bytes_alloc); |
|
BUILD_DISPLAY_HEX64_FUNCTION(DisplayGfpFlags, gfp_flags); |
|
BUILD_DISPLAY_UINT64_FUNCTION(DisplayCrossCpuAllocations, |
|
cross_cpu_allocations); |
|
|
|
static int CompareFragment(const SlabSample* sample1, |
|
const SlabSample* sample2) { |
|
uint64_t frag1 = sample1->bytes_alloc - sample1->bytes_req; |
|
uint64_t frag2 = sample2->bytes_alloc - sample2->bytes_req; |
|
return Compare(frag2, frag1); |
|
} |
|
|
|
static std::string DisplayFragment(const SlabSample* sample) { |
|
return android::base::StringPrintf("%" PRIu64, |
|
sample->bytes_alloc - sample->bytes_req); |
|
} |
|
|
|
struct SlabSampleTree { |
|
std::vector<SlabSample*> samples; |
|
uint64_t total_requested_bytes; |
|
uint64_t total_allocated_bytes; |
|
uint64_t nr_allocations; |
|
uint64_t nr_frees; |
|
uint64_t nr_cross_cpu_allocations; |
|
}; |
|
|
|
struct SlabFormat { |
|
enum { |
|
KMEM_ALLOC, |
|
KMEM_FREE, |
|
} type; |
|
TracingFieldPlace call_site; |
|
TracingFieldPlace ptr; |
|
TracingFieldPlace bytes_req; |
|
TracingFieldPlace bytes_alloc; |
|
TracingFieldPlace gfp_flags; |
|
}; |
|
|
|
class SlabSampleTreeBuilder |
|
: public SampleTreeBuilder<SlabSample, SlabAccumulateInfo> { |
|
public: |
|
SlabSampleTreeBuilder(SampleComparator<SlabSample> sample_comparator, |
|
ThreadTree* thread_tree) |
|
: SampleTreeBuilder(sample_comparator), |
|
thread_tree_(thread_tree), |
|
total_requested_bytes_(0), |
|
total_allocated_bytes_(0), |
|
nr_allocations_(0), |
|
nr_cross_cpu_allocations_(0) {} |
|
|
|
SlabSampleTree GetSampleTree() const { |
|
SlabSampleTree sample_tree; |
|
sample_tree.samples = GetSamples(); |
|
sample_tree.total_requested_bytes = total_requested_bytes_; |
|
sample_tree.total_allocated_bytes = total_allocated_bytes_; |
|
sample_tree.nr_allocations = nr_allocations_; |
|
sample_tree.nr_frees = nr_frees_; |
|
sample_tree.nr_cross_cpu_allocations = nr_cross_cpu_allocations_; |
|
return sample_tree; |
|
} |
|
|
|
void AddSlabFormat(const std::vector<uint64_t>& event_ids, |
|
SlabFormat format) { |
|
std::unique_ptr<SlabFormat> p(new SlabFormat(format)); |
|
for (auto id : event_ids) { |
|
event_id_to_format_map_[id] = p.get(); |
|
} |
|
formats_.push_back(std::move(p)); |
|
} |
|
|
|
protected: |
|
SlabSample* CreateSample(const SampleRecord& r, bool in_kernel, |
|
SlabAccumulateInfo* acc_info) override { |
|
if (!in_kernel) { |
|
// Normally we don't parse records in user space because tracepoint |
|
// events all happen in kernel. But if r.ip_data.ip == 0, it may be |
|
// a kernel record failed to dump ip register and is still useful. |
|
if (r.ip_data.ip == 0) { |
|
// It seems we are on a kernel can't dump regset for tracepoint events |
|
// because of lacking perf_arch_fetch_caller_regs(). We can't get |
|
// callchain, but we can still do a normal report. |
|
static bool first = true; |
|
if (first) { |
|
first = false; |
|
if (accumulate_callchain_) { |
|
// The kernel doesn't seem to support dumping registers for |
|
// tracepoint events because of lacking |
|
// perf_arch_fetch_caller_regs(). |
|
LOG(WARNING) << "simpleperf may not get callchains for tracepoint" |
|
<< " events because of lacking kernel support."; |
|
} |
|
} |
|
} else { |
|
return nullptr; |
|
} |
|
} |
|
uint64_t id = r.id_data.id; |
|
auto it = event_id_to_format_map_.find(id); |
|
if (it == event_id_to_format_map_.end()) { |
|
return nullptr; |
|
} |
|
const char* raw_data = r.raw_data.data; |
|
SlabFormat* format = it->second; |
|
if (format->type == SlabFormat::KMEM_ALLOC) { |
|
uint64_t call_site = format->call_site.ReadFromData(raw_data); |
|
const Symbol* symbol = thread_tree_->FindKernelSymbol(call_site); |
|
uint64_t ptr = format->ptr.ReadFromData(raw_data); |
|
uint64_t bytes_req = format->bytes_req.ReadFromData(raw_data); |
|
uint64_t bytes_alloc = format->bytes_alloc.ReadFromData(raw_data); |
|
uint64_t gfp_flags = format->gfp_flags.ReadFromData(raw_data); |
|
SlabSample* sample = |
|
InsertSample(std::unique_ptr<SlabSample>(new SlabSample( |
|
symbol, ptr, bytes_req, bytes_alloc, 1, gfp_flags, 0))); |
|
alloc_cpu_record_map_.insert( |
|
std::make_pair(ptr, std::make_pair(r.cpu_data.cpu, sample))); |
|
acc_info->bytes_req = bytes_req; |
|
acc_info->bytes_alloc = bytes_alloc; |
|
return sample; |
|
} else if (format->type == SlabFormat::KMEM_FREE) { |
|
uint64_t ptr = format->ptr.ReadFromData(raw_data); |
|
auto it = alloc_cpu_record_map_.find(ptr); |
|
if (it != alloc_cpu_record_map_.end()) { |
|
SlabSample* sample = it->second.second; |
|
if (r.cpu_data.cpu != it->second.first) { |
|
sample->cross_cpu_allocations++; |
|
nr_cross_cpu_allocations_++; |
|
} |
|
alloc_cpu_record_map_.erase(it); |
|
} |
|
nr_frees_++; |
|
} |
|
return nullptr; |
|
} |
|
|
|
SlabSample* CreateBranchSample(const SampleRecord&, |
|
const BranchStackItemType&) override { |
|
return nullptr; |
|
} |
|
|
|
SlabSample* CreateCallChainSample( |
|
const SlabSample* sample, uint64_t ip, bool in_kernel, |
|
const std::vector<SlabSample*>& callchain, |
|
const SlabAccumulateInfo& acc_info) override { |
|
if (!in_kernel) { |
|
return nullptr; |
|
} |
|
const Symbol* symbol = thread_tree_->FindKernelSymbol(ip); |
|
return InsertCallChainSample( |
|
std::unique_ptr<SlabSample>( |
|
new SlabSample(symbol, sample->ptr, acc_info.bytes_req, |
|
acc_info.bytes_alloc, 1, sample->gfp_flags, 0)), |
|
callchain); |
|
} |
|
|
|
const ThreadEntry* GetThreadOfSample(SlabSample*) override { return nullptr; } |
|
|
|
uint64_t GetPeriodForCallChain(const SlabAccumulateInfo&) override { |
|
// Decide the percentage of callchain by the sample_count, so use 1 as the |
|
// period when calling AddCallChain(). |
|
return 1; |
|
} |
|
|
|
void UpdateSummary(const SlabSample* sample) override { |
|
total_requested_bytes_ += sample->bytes_req; |
|
total_allocated_bytes_ += sample->bytes_alloc; |
|
nr_allocations_++; |
|
} |
|
|
|
void MergeSample(SlabSample* sample1, SlabSample* sample2) override { |
|
sample1->bytes_req += sample2->bytes_req; |
|
sample1->bytes_alloc += sample2->bytes_alloc; |
|
sample1->sample_count += sample2->sample_count; |
|
} |
|
|
|
private: |
|
ThreadTree* thread_tree_; |
|
uint64_t total_requested_bytes_; |
|
uint64_t total_allocated_bytes_; |
|
uint64_t nr_allocations_; |
|
uint64_t nr_frees_; |
|
uint64_t nr_cross_cpu_allocations_; |
|
|
|
std::unordered_map<uint64_t, SlabFormat*> event_id_to_format_map_; |
|
std::vector<std::unique_ptr<SlabFormat>> formats_; |
|
std::unordered_map<uint64_t, std::pair<uint32_t, SlabSample*>> |
|
alloc_cpu_record_map_; |
|
}; |
|
|
|
using SlabSampleTreeSorter = SampleTreeSorter<SlabSample>; |
|
using SlabSampleTreeDisplayer = SampleTreeDisplayer<SlabSample, SlabSampleTree>; |
|
using SlabSampleCallgraphDisplayer = |
|
CallgraphDisplayer<SlabSample, CallChainNode<SlabSample>>; |
|
|
|
struct EventAttrWithName { |
|
perf_event_attr attr; |
|
std::string name; |
|
std::vector<uint64_t> event_ids; |
|
}; |
|
|
|
class KmemCommand : public Command { |
|
public: |
|
KmemCommand() |
|
: Command( |
|
"kmem", "collect kernel memory allocation information", |
|
// clang-format off |
|
"Usage: kmem (record [record options] | report [report options])\n" |
|
"kmem record\n" |
|
"-g Enable call graph recording. Same as '--call-graph fp'.\n" |
|
"--slab Collect slab allocation information. Default option.\n" |
|
"Other record options provided by simpleperf record command are also available.\n" |
|
"kmem report\n" |
|
"--children Print the accumulated allocation info appeared in the callchain.\n" |
|
" Can be used on perf.data recorded with `--call-graph fp` option.\n" |
|
"-g [callee|caller] Print call graph for perf.data recorded with\n" |
|
" `--call-graph fp` option. If callee mode is used, the graph\n" |
|
" shows how functions are called from others. Otherwise, the\n" |
|
" graph shows how functions call others. Default is callee\n" |
|
" mode. The percentage shown in the graph is determined by\n" |
|
" the hit count of the callchain.\n" |
|
"-i Specify path of record file, default is perf.data\n" |
|
"-o report_file_name Set report file name, default is stdout.\n" |
|
"--slab Report slab allocation information. Default option.\n" |
|
"--slab-sort key1,key2,...\n" |
|
" Select the keys to sort and print slab allocation information.\n" |
|
" Should be used with --slab option. Possible keys include:\n" |
|
" hit -- the allocation count.\n" |
|
" caller -- the function calling allocation.\n" |
|
" ptr -- the address of the allocated space.\n" |
|
" bytes_req -- the total requested space size.\n" |
|
" bytes_alloc -- the total allocated space size.\n" |
|
" fragment -- the extra allocated space size\n" |
|
" (bytes_alloc - bytes_req).\n" |
|
" gfp_flags -- the flags used for allocation.\n" |
|
" pingpong -- the count of allocations that are freed not on\n" |
|
" the cpu allocating them.\n" |
|
" The default slab sort keys are:\n" |
|
" hit,caller,bytes_req,bytes_alloc,fragment,pingpong.\n" |
|
// clang-format on |
|
), |
|
is_record_(false), |
|
use_slab_(false), |
|
accumulate_callchain_(false), |
|
print_callgraph_(false), |
|
callgraph_show_callee_(false), |
|
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, |
|
std::vector<std::string>* left_args); |
|
bool RecordKmemInfo(const std::vector<std::string>& record_args); |
|
bool ReportKmemInfo(); |
|
bool PrepareToBuildSampleTree(); |
|
void ReadEventAttrsFromRecordFile(); |
|
bool ReadFeaturesFromRecordFile(); |
|
bool ReadSampleTreeFromRecordFile(); |
|
bool ProcessRecord(std::unique_ptr<Record> record); |
|
void ProcessTracingData(const std::vector<char>& data); |
|
bool PrintReport(); |
|
void PrintReportContext(FILE* fp); |
|
void PrintSlabReportContext(FILE* fp); |
|
|
|
bool is_record_; |
|
bool use_slab_; |
|
std::vector<std::string> slab_sort_keys_; |
|
bool accumulate_callchain_; |
|
bool print_callgraph_; |
|
bool callgraph_show_callee_; |
|
|
|
std::string record_filename_; |
|
std::unique_ptr<RecordFileReader> record_file_reader_; |
|
std::vector<EventAttrWithName> event_attrs_; |
|
std::string record_cmdline_; |
|
ArchType record_file_arch_; |
|
|
|
ThreadTree thread_tree_; |
|
SlabSampleTree slab_sample_tree_; |
|
std::unique_ptr<SlabSampleTreeBuilder> slab_sample_tree_builder_; |
|
std::unique_ptr<SlabSampleTreeSorter> slab_sample_tree_sorter_; |
|
std::unique_ptr<SlabSampleTreeDisplayer> slab_sample_tree_displayer_; |
|
|
|
std::string report_filename_; |
|
}; |
|
|
|
bool KmemCommand::Run(const std::vector<std::string>& args) { |
|
std::vector<std::string> left_args; |
|
if (!ParseOptions(args, &left_args)) { |
|
return false; |
|
} |
|
if (!use_slab_) { |
|
use_slab_ = true; |
|
} |
|
if (is_record_) { |
|
return RecordKmemInfo(left_args); |
|
} |
|
return ReportKmemInfo(); |
|
} |
|
|
|
bool KmemCommand::ParseOptions(const std::vector<std::string>& args, |
|
std::vector<std::string>* left_args) { |
|
if (args.empty()) { |
|
LOG(ERROR) << "No subcommand specified"; |
|
return false; |
|
} |
|
if (args[0] == "record") { |
|
if (!IsRoot()) { |
|
LOG(ERROR) << "simpleperf kmem record command needs root privilege"; |
|
return false; |
|
} |
|
is_record_ = true; |
|
size_t i; |
|
for (i = 1; i < args.size() && !args[i].empty() && args[i][0] == '-'; ++i) { |
|
if (args[i] == "-g") { |
|
left_args->push_back("--call-graph"); |
|
left_args->push_back("fp"); |
|
} else if (args[i] == "--slab") { |
|
use_slab_ = true; |
|
} else { |
|
left_args->push_back(args[i]); |
|
} |
|
} |
|
left_args->insert(left_args->end(), args.begin() + i, args.end()); |
|
} else if (args[0] == "report") { |
|
is_record_ = false; |
|
for (size_t i = 1; i < args.size(); ++i) { |
|
if (args[i] == "--children") { |
|
accumulate_callchain_ = true; |
|
} else if (args[i] == "-g") { |
|
print_callgraph_ = true; |
|
accumulate_callchain_ = true; |
|
callgraph_show_callee_ = true; |
|
if (i + 1 < args.size() && args[i + 1][0] != '-') { |
|
++i; |
|
if (args[i] == "callee") { |
|
callgraph_show_callee_ = true; |
|
} else if (args[i] == "caller") { |
|
callgraph_show_callee_ = false; |
|
} else { |
|
LOG(ERROR) << "Unknown argument with -g option: " << args[i]; |
|
return false; |
|
} |
|
} |
|
} else if (args[i] == "-i") { |
|
if (!NextArgumentOrError(args, &i)) { |
|
return false; |
|
} |
|
record_filename_ = args[i]; |
|
} else if (args[i] == "-o") { |
|
if (!NextArgumentOrError(args, &i)) { |
|
return false; |
|
} |
|
report_filename_ = args[i]; |
|
} else if (args[i] == "--slab") { |
|
use_slab_ = true; |
|
} else if (args[i] == "--slab-sort") { |
|
if (!NextArgumentOrError(args, &i)) { |
|
return false; |
|
} |
|
slab_sort_keys_ = android::base::Split(args[i], ","); |
|
} else { |
|
ReportUnknownOption(args, i); |
|
return false; |
|
} |
|
} |
|
} else { |
|
LOG(ERROR) << "Unknown subcommand for " << Name() << ": " << args[0] |
|
<< ". Try `simpleperf help " << Name() << "`"; |
|
return false; |
|
} |
|
return true; |
|
} |
|
|
|
bool KmemCommand::RecordKmemInfo(const std::vector<std::string>& record_args) { |
|
std::vector<std::string> args; |
|
if (use_slab_) { |
|
std::vector<std::string> trace_events = { |
|
"kmem:kmalloc", "kmem:kmem_cache_alloc", |
|
"kmem:kmalloc_node", "kmem:kmem_cache_alloc_node", |
|
"kmem:kfree", "kmem:kmem_cache_free"}; |
|
for (const auto& name : trace_events) { |
|
if (ParseEventType(name)) { |
|
args.insert(args.end(), {"-e", name}); |
|
} |
|
} |
|
} |
|
if (args.empty()) { |
|
LOG(ERROR) << "Kernel allocation related trace events are not supported."; |
|
return false; |
|
} |
|
args.push_back("-a"); |
|
args.insert(args.end(), record_args.begin(), record_args.end()); |
|
std::unique_ptr<Command> record_cmd = CreateCommandInstance("record"); |
|
if (record_cmd == nullptr) { |
|
LOG(ERROR) << "record command isn't available"; |
|
return false; |
|
} |
|
return record_cmd->Run(args); |
|
} |
|
|
|
bool KmemCommand::ReportKmemInfo() { |
|
if (!PrepareToBuildSampleTree()) { |
|
return false; |
|
} |
|
record_file_reader_ = RecordFileReader::CreateInstance(record_filename_); |
|
if (record_file_reader_ == nullptr) { |
|
return false; |
|
} |
|
ReadEventAttrsFromRecordFile(); |
|
if (!ReadFeaturesFromRecordFile()) { |
|
return false; |
|
} |
|
if (!ReadSampleTreeFromRecordFile()) { |
|
return false; |
|
} |
|
if (!PrintReport()) { |
|
return false; |
|
} |
|
return true; |
|
} |
|
|
|
bool KmemCommand::PrepareToBuildSampleTree() { |
|
if (use_slab_) { |
|
if (slab_sort_keys_.empty()) { |
|
slab_sort_keys_ = {"hit", "caller", "bytes_req", |
|
"bytes_alloc", "fragment", "pingpong"}; |
|
} |
|
SampleComparator<SlabSample> comparator; |
|
SampleComparator<SlabSample> sort_comparator; |
|
SampleDisplayer<SlabSample, SlabSampleTree> displayer; |
|
std::string accumulated_name = accumulate_callchain_ ? "Accumulated_" : ""; |
|
|
|
if (print_callgraph_) { |
|
displayer.AddExclusiveDisplayFunction(SlabSampleCallgraphDisplayer()); |
|
} |
|
|
|
for (const auto& key : slab_sort_keys_) { |
|
if (key == "hit") { |
|
sort_comparator.AddCompareFunction(CompareSampleCount); |
|
displayer.AddDisplayFunction(accumulated_name + "Hit", |
|
DisplaySampleCount); |
|
} else if (key == "caller") { |
|
comparator.AddCompareFunction(CompareSymbol); |
|
displayer.AddDisplayFunction("Caller", DisplaySymbol); |
|
} else if (key == "ptr") { |
|
comparator.AddCompareFunction(ComparePtr); |
|
displayer.AddDisplayFunction("Ptr", DisplayPtr); |
|
} else if (key == "bytes_req") { |
|
sort_comparator.AddCompareFunction(CompareBytesReq); |
|
displayer.AddDisplayFunction(accumulated_name + "BytesReq", |
|
DisplayBytesReq); |
|
} else if (key == "bytes_alloc") { |
|
sort_comparator.AddCompareFunction(CompareBytesAlloc); |
|
displayer.AddDisplayFunction(accumulated_name + "BytesAlloc", |
|
DisplayBytesAlloc); |
|
} else if (key == "fragment") { |
|
sort_comparator.AddCompareFunction(CompareFragment); |
|
displayer.AddDisplayFunction(accumulated_name + "Fragment", |
|
DisplayFragment); |
|
} else if (key == "gfp_flags") { |
|
comparator.AddCompareFunction(CompareGfpFlags); |
|
displayer.AddDisplayFunction("GfpFlags", DisplayGfpFlags); |
|
} else if (key == "pingpong") { |
|
sort_comparator.AddCompareFunction(CompareCrossCpuAllocations); |
|
displayer.AddDisplayFunction("Pingpong", DisplayCrossCpuAllocations); |
|
} else { |
|
LOG(ERROR) << "Unknown sort key for slab allocation: " << key; |
|
return false; |
|
} |
|
slab_sample_tree_builder_.reset( |
|
new SlabSampleTreeBuilder(comparator, &thread_tree_)); |
|
slab_sample_tree_builder_->SetCallChainSampleOptions( |
|
accumulate_callchain_, print_callgraph_, !callgraph_show_callee_, |
|
false); |
|
sort_comparator.AddComparator(comparator); |
|
slab_sample_tree_sorter_.reset(new SlabSampleTreeSorter(sort_comparator)); |
|
slab_sample_tree_displayer_.reset(new SlabSampleTreeDisplayer(displayer)); |
|
} |
|
} |
|
return true; |
|
} |
|
|
|
void KmemCommand::ReadEventAttrsFromRecordFile() { |
|
std::vector<EventAttrWithId> attrs = record_file_reader_->AttrSection(); |
|
for (const auto& attr_with_id : attrs) { |
|
EventAttrWithName attr; |
|
attr.attr = *attr_with_id.attr; |
|
attr.event_ids = attr_with_id.ids; |
|
attr.name = GetEventNameByAttr(attr.attr); |
|
event_attrs_.push_back(attr); |
|
} |
|
} |
|
|
|
bool KmemCommand::ReadFeaturesFromRecordFile() { |
|
record_file_reader_->LoadBuildIdAndFileFeatures(thread_tree_); |
|
std::string arch = |
|
record_file_reader_->ReadFeatureString(PerfFileFormat::FEAT_ARCH); |
|
if (!arch.empty()) { |
|
record_file_arch_ = GetArchType(arch); |
|
if (record_file_arch_ == ARCH_UNSUPPORTED) { |
|
return false; |
|
} |
|
} |
|
std::vector<std::string> cmdline = record_file_reader_->ReadCmdlineFeature(); |
|
if (!cmdline.empty()) { |
|
record_cmdline_ = android::base::Join(cmdline, ' '); |
|
} |
|
if (record_file_reader_->HasFeature(PerfFileFormat::FEAT_TRACING_DATA)) { |
|
std::vector<char> tracing_data; |
|
if (!record_file_reader_->ReadFeatureSection( |
|
PerfFileFormat::FEAT_TRACING_DATA, &tracing_data)) { |
|
return false; |
|
} |
|
ProcessTracingData(tracing_data); |
|
} |
|
return true; |
|
} |
|
|
|
bool KmemCommand::ReadSampleTreeFromRecordFile() { |
|
if (!record_file_reader_->ReadDataSection( |
|
[this](std::unique_ptr<Record> record) { |
|
return ProcessRecord(std::move(record)); |
|
})) { |
|
return false; |
|
} |
|
if (use_slab_) { |
|
slab_sample_tree_ = slab_sample_tree_builder_->GetSampleTree(); |
|
slab_sample_tree_sorter_->Sort(slab_sample_tree_.samples, print_callgraph_); |
|
} |
|
return true; |
|
} |
|
|
|
bool KmemCommand::ProcessRecord(std::unique_ptr<Record> record) { |
|
thread_tree_.Update(*record); |
|
if (record->type() == PERF_RECORD_SAMPLE) { |
|
if (use_slab_) { |
|
slab_sample_tree_builder_->ProcessSampleRecord( |
|
*static_cast<const SampleRecord*>(record.get())); |
|
} |
|
} else if (record->type() == PERF_RECORD_TRACING_DATA) { |
|
const auto& r = *static_cast<TracingDataRecord*>(record.get()); |
|
ProcessTracingData(std::vector<char>(r.data, r.data + r.data_size)); |
|
} |
|
return true; |
|
} |
|
|
|
void KmemCommand::ProcessTracingData(const std::vector<char>& data) { |
|
Tracing tracing(data); |
|
for (auto& attr : event_attrs_) { |
|
if (attr.attr.type == PERF_TYPE_TRACEPOINT) { |
|
uint64_t trace_event_id = attr.attr.config; |
|
attr.name = tracing.GetTracingEventNameHavingId(trace_event_id); |
|
TracingFormat format = tracing.GetTracingFormatHavingId(trace_event_id); |
|
if (use_slab_) { |
|
if (format.name == "kmalloc" || format.name == "kmem_cache_alloc" || |
|
format.name == "kmalloc_node" || |
|
format.name == "kmem_cache_alloc_node") { |
|
SlabFormat f; |
|
f.type = SlabFormat::KMEM_ALLOC; |
|
format.GetField("call_site", f.call_site); |
|
format.GetField("ptr", f.ptr); |
|
format.GetField("bytes_req", f.bytes_req); |
|
format.GetField("bytes_alloc", f.bytes_alloc); |
|
format.GetField("gfp_flags", f.gfp_flags); |
|
slab_sample_tree_builder_->AddSlabFormat(attr.event_ids, f); |
|
} else if (format.name == "kfree" || format.name == "kmem_cache_free") { |
|
SlabFormat f; |
|
f.type = SlabFormat::KMEM_FREE; |
|
format.GetField("call_site", f.call_site); |
|
format.GetField("ptr", f.ptr); |
|
slab_sample_tree_builder_->AddSlabFormat(attr.event_ids, f); |
|
} |
|
} |
|
} |
|
} |
|
} |
|
|
|
bool KmemCommand::PrintReport() { |
|
std::unique_ptr<FILE, decltype(&fclose)> file_handler(nullptr, fclose); |
|
FILE* report_fp = stdout; |
|
if (!report_filename_.empty()) { |
|
file_handler.reset(fopen(report_filename_.c_str(), "w")); |
|
if (file_handler == nullptr) { |
|
PLOG(ERROR) << "failed to open " << report_filename_; |
|
return false; |
|
} |
|
report_fp = file_handler.get(); |
|
} |
|
PrintReportContext(report_fp); |
|
if (use_slab_) { |
|
fprintf(report_fp, "\n\n"); |
|
PrintSlabReportContext(report_fp); |
|
slab_sample_tree_displayer_->DisplaySamples( |
|
report_fp, slab_sample_tree_.samples, &slab_sample_tree_); |
|
} |
|
return true; |
|
} |
|
|
|
void KmemCommand::PrintReportContext(FILE* fp) { |
|
if (!record_cmdline_.empty()) { |
|
fprintf(fp, "Cmdline: %s\n", record_cmdline_.c_str()); |
|
} |
|
fprintf(fp, "Arch: %s\n", GetArchString(record_file_arch_).c_str()); |
|
for (const auto& attr : event_attrs_) { |
|
fprintf(fp, "Event: %s (type %u, config %llu)\n", attr.name.c_str(), |
|
attr.attr.type, attr.attr.config); |
|
} |
|
} |
|
|
|
void KmemCommand::PrintSlabReportContext(FILE* fp) { |
|
fprintf(fp, "Slab allocation information:\n"); |
|
fprintf(fp, "Total requested bytes: %" PRIu64 "\n", |
|
slab_sample_tree_.total_requested_bytes); |
|
fprintf(fp, "Total allocated bytes: %" PRIu64 "\n", |
|
slab_sample_tree_.total_allocated_bytes); |
|
uint64_t fragment = slab_sample_tree_.total_allocated_bytes - |
|
slab_sample_tree_.total_requested_bytes; |
|
double percentage = 0.0; |
|
if (slab_sample_tree_.total_allocated_bytes != 0) { |
|
percentage = 100.0 * fragment / slab_sample_tree_.total_allocated_bytes; |
|
} |
|
fprintf(fp, "Total fragment: %" PRIu64 ", %f%%\n", fragment, percentage); |
|
fprintf(fp, "Total allocations: %" PRIu64 "\n", |
|
slab_sample_tree_.nr_allocations); |
|
fprintf(fp, "Total frees: %" PRIu64 "\n", slab_sample_tree_.nr_frees); |
|
percentage = 0.0; |
|
if (slab_sample_tree_.nr_allocations != 0) { |
|
percentage = 100.0 * slab_sample_tree_.nr_cross_cpu_allocations / |
|
slab_sample_tree_.nr_allocations; |
|
} |
|
fprintf(fp, "Total cross cpu allocation/free: %" PRIu64 ", %f%%\n", |
|
slab_sample_tree_.nr_cross_cpu_allocations, percentage); |
|
fprintf(fp, "\n"); |
|
} |
|
|
|
} // namespace |
|
|
|
void RegisterKmemCommand() { |
|
RegisterCommand("kmem", |
|
[] { return std::unique_ptr<Command>(new KmemCommand()); }); |
|
}
|
|
|