upload android base code part6
This commit is contained in:
parent
421e214c7d
commit
4e516ec6ed
35396 changed files with 9188716 additions and 0 deletions
716
android/system/extras/simpleperf/cmd_kmem.cpp
Normal file
716
android/system/extras/simpleperf/cmd_kmem.cpp
Normal file
|
@ -0,0 +1,716 @@
|
|||
/*
|
||||
* 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()); });
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue