blob: 16559a29dae1933ef8657bf71e0272c8619a75a2 [file] [log] [blame]
/*
* 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) {}
};
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<AttrWithId> 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() {
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()); });
}