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.
428 lines
14 KiB
428 lines
14 KiB
/*
|
|
* Copyright (C) 2017 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 "CallChainJoiner.h"
|
|
|
|
#include <android-base/logging.h>
|
|
|
|
#include "environment.h"
|
|
#include "utils.h"
|
|
|
|
namespace simpleperf {
|
|
namespace call_chain_joiner_impl {
|
|
|
|
LRUCache::LRUCache(size_t cache_size, size_t matched_node_count_to_extend_callchain)
|
|
: node_set_(1024 * 16, CacheNodeHash, CacheNodeEqual) {
|
|
cache_stat_.cache_size = cache_size;
|
|
cache_stat_.max_node_count = cache_size / sizeof(CacheNode);
|
|
CHECK_GE(cache_stat_.max_node_count, 2u);
|
|
CHECK_GE(matched_node_count_to_extend_callchain, 1u);
|
|
cache_stat_.matched_node_count_to_extend_callchain = matched_node_count_to_extend_callchain;
|
|
nodes_ = new CacheNode[cache_stat_.max_node_count + 1]; // with 1 sentinel node
|
|
// nodes_[0] is the sentinel node of the LRU linked list.
|
|
nodes_[0].is_leaf = 1;
|
|
nodes_[0].parent_index = 0;
|
|
nodes_[0].leaf_link_prev = nodes_[0].leaf_link_next = 0;
|
|
}
|
|
|
|
LRUCache::~LRUCache() {
|
|
delete[] nodes_;
|
|
}
|
|
|
|
void LRUCache::AddCallChain(pid_t tid, std::vector<uint64_t>& ips, std::vector<uint64_t>& sps) {
|
|
// 1. Build current call chain.
|
|
std::vector<CacheNode*> chain;
|
|
for (size_t i = 0; i < ips.size(); ++i) {
|
|
CacheNode* node = GetNode(tid, ips[i], sps[i]);
|
|
chain.push_back(node);
|
|
}
|
|
|
|
// 2. Check if the (tid, ip, sp) tuples of the top [matched_node_count_to_extend_callchain]
|
|
// nodes of current call chain exist in the cache and have the same relation as in current
|
|
// call chain. If true, we can extend current call chain.
|
|
bool can_extend = true;
|
|
if (chain.size() < cache_stat_.matched_node_count_to_extend_callchain) {
|
|
can_extend = false;
|
|
} else {
|
|
size_t chain_pos = chain.size() - 2;
|
|
for (size_t i = 1; i < cache_stat_.matched_node_count_to_extend_callchain; ++i) {
|
|
if (GetParent(chain[chain_pos]) != chain[chain_pos + 1]) {
|
|
can_extend = false;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
std::vector<uint64_t> origin_ip = ips;
|
|
|
|
// 3. Add current call chain to the cache.
|
|
for (size_t i = 0; i + 1 < chain.size(); ++i) {
|
|
LinkParent(chain[i], chain[i + 1]);
|
|
}
|
|
// 4. Optionally extend current call chain.
|
|
if (can_extend) {
|
|
CacheNode* top = chain.back();
|
|
while ((top = GetParent(top)) != nullptr) {
|
|
if (top->sp == chain.back()->sp) {
|
|
// This is to prevent a loop case as below, which is unlikely to happen.
|
|
// The cache has node A.parent = B, while current call chain has node B.parent = A.
|
|
// This makes a loop of A -> B -> A -> B...
|
|
bool has_loop = false;
|
|
for (auto it = chain.rbegin(); it != chain.rend() && (*it)->sp == top->sp; ++it) {
|
|
if ((*it)->ip == top->ip) {
|
|
has_loop = true;
|
|
break;
|
|
}
|
|
}
|
|
if (has_loop) {
|
|
UnlinkParent(chain.back());
|
|
ips.resize(chain.size());
|
|
sps.resize(chain.size());
|
|
break;
|
|
}
|
|
}
|
|
ips.push_back(top->ip);
|
|
sps.push_back(top->sp);
|
|
}
|
|
} else {
|
|
UnlinkParent(chain.back());
|
|
}
|
|
}
|
|
|
|
bool LRUCache::CacheNodeEqual(const CacheNode* n1, const CacheNode* n2) {
|
|
return n1->tid == n2->tid && n1->ip == n2->ip && n1->sp == n2->sp;
|
|
}
|
|
|
|
size_t LRUCache::CacheNodeHash(const CacheNode* n) {
|
|
return static_cast<size_t>(n->tid ^ n->ip ^ n->sp);
|
|
}
|
|
|
|
CacheNode* LRUCache::GetNode(uint32_t tid, uint64_t ip, uint64_t sp) {
|
|
CacheNode* node = FindNode(tid, ip, sp);
|
|
if (node != nullptr) {
|
|
if (node->is_leaf) {
|
|
// Update the node's position in the LRU linked list.
|
|
RemoveNodeFromLRUList(node);
|
|
AppendNodeToLRUList(node);
|
|
}
|
|
return node;
|
|
}
|
|
node = AllocNode();
|
|
node->tid = tid;
|
|
node->ip = ip;
|
|
node->sp = sp;
|
|
node->is_leaf = 1;
|
|
node->parent_index = 0;
|
|
node->leaf_link_prev = node->leaf_link_next = GetNodeIndex(node);
|
|
node_set_.insert(node);
|
|
AppendNodeToLRUList(node);
|
|
return node;
|
|
}
|
|
|
|
CacheNode* LRUCache::AllocNode() {
|
|
if (cache_stat_.used_node_count < cache_stat_.max_node_count) {
|
|
return &nodes_[++cache_stat_.used_node_count];
|
|
}
|
|
// Recycle the node at the front of the LRU linked list.
|
|
CacheNode* node = &nodes_[nodes_->leaf_link_next];
|
|
RemoveNodeFromLRUList(node);
|
|
node_set_.erase(node);
|
|
CacheNode* parent = GetParent(node);
|
|
if (parent != nullptr) {
|
|
DecreaseChildCountOfNode(parent);
|
|
}
|
|
cache_stat_.recycled_node_count++;
|
|
return node;
|
|
}
|
|
|
|
void LRUCache::LinkParent(CacheNode* child, CacheNode* new_parent) {
|
|
CacheNode* old_parent = GetParent(child);
|
|
if (old_parent != nullptr) {
|
|
DecreaseChildCountOfNode(old_parent);
|
|
}
|
|
child->parent_index = GetNodeIndex(new_parent);
|
|
if (new_parent->is_leaf) {
|
|
RemoveNodeFromLRUList(new_parent);
|
|
new_parent->is_leaf = 0;
|
|
new_parent->children_count = 1;
|
|
} else {
|
|
new_parent->children_count++;
|
|
}
|
|
}
|
|
|
|
void LRUCache::UnlinkParent(CacheNode* child) {
|
|
CacheNode* old_parent = GetParent(child);
|
|
if (old_parent != nullptr) {
|
|
DecreaseChildCountOfNode(old_parent);
|
|
}
|
|
child->parent_index = 0;
|
|
}
|
|
|
|
} // namespace call_chain_joiner_impl
|
|
|
|
using namespace call_chain_joiner_impl;
|
|
|
|
static bool WriteCallChain(FILE* fp, pid_t pid, pid_t tid, CallChainJoiner::ChainType type,
|
|
const std::vector<uint64_t>& ips, const std::vector<uint64_t>& sps,
|
|
size_t ip_count) {
|
|
// Below is the content of a call chain stored in file.
|
|
// uint32_t pid;
|
|
// uint32_t tid;
|
|
// uint32_t chain_type;
|
|
// uint32_t ip_count;
|
|
// uint64_t ips[];
|
|
// uint64_t sps[];
|
|
// uint32_t size;
|
|
uint32_t size = 4 * sizeof(uint32_t) + sizeof(uint64_t) * ip_count * 2 + sizeof(uint32_t);
|
|
std::vector<char> data(size);
|
|
char* p = data.data();
|
|
MoveToBinaryFormat(pid, p);
|
|
MoveToBinaryFormat(tid, p);
|
|
MoveToBinaryFormat(type, p);
|
|
MoveToBinaryFormat(static_cast<uint32_t>(ip_count), p);
|
|
MoveToBinaryFormat(ips.data(), ip_count, p);
|
|
MoveToBinaryFormat(sps.data(), ip_count, p);
|
|
MoveToBinaryFormat(size, p);
|
|
if (fwrite(data.data(), size, 1, fp) != 1) {
|
|
PLOG(ERROR) << "fwrite";
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static bool ReadCallChain(FILE* fp, pid_t& pid, pid_t& tid, CallChainJoiner::ChainType& type,
|
|
std::vector<uint64_t>& ips, std::vector<uint64_t>& sps) {
|
|
std::vector<char> data(4 * sizeof(uint32_t));
|
|
if (fread(data.data(), data.size(), 1, fp) != 1) {
|
|
PLOG(ERROR) << "fread";
|
|
return false;
|
|
}
|
|
const char* p = data.data();
|
|
MoveFromBinaryFormat(pid, p);
|
|
MoveFromBinaryFormat(tid, p);
|
|
MoveFromBinaryFormat(type, p);
|
|
uint32_t ip_count;
|
|
MoveFromBinaryFormat(ip_count, p);
|
|
data.resize(sizeof(uint64_t) * ip_count * 2 + sizeof(uint32_t));
|
|
if (fread(data.data(), data.size(), 1, fp) != 1) {
|
|
PLOG(ERROR) << "fread";
|
|
return false;
|
|
}
|
|
p = data.data();
|
|
ips.resize(ip_count);
|
|
MoveFromBinaryFormat(ips.data(), ip_count, p);
|
|
sps.resize(ip_count);
|
|
MoveFromBinaryFormat(sps.data(), ip_count, p);
|
|
return true;
|
|
}
|
|
|
|
static bool ReadCallChainInReverseOrder(FILE* fp, pid_t& pid, pid_t& tid,
|
|
CallChainJoiner::ChainType& type,
|
|
std::vector<uint64_t>& ips, std::vector<uint64_t>& sps) {
|
|
uint32_t size;
|
|
if (fseek(fp, -4, SEEK_CUR) != 0 || fread(&size, sizeof(size), 1, fp) != 1) {
|
|
PLOG(ERROR) << "fread";
|
|
return false;
|
|
}
|
|
std::vector<char> data(size - 4);
|
|
if (fseek(fp, -static_cast<int>(size), SEEK_CUR) != 0 ||
|
|
fread(data.data(), data.size(), 1, fp) != 1 ||
|
|
fseek(fp, -static_cast<int>(data.size()), SEEK_CUR) != 0) {
|
|
PLOG(ERROR) << "fread";
|
|
return false;
|
|
}
|
|
const char* p = data.data();
|
|
MoveFromBinaryFormat(pid, p);
|
|
MoveFromBinaryFormat(tid, p);
|
|
MoveFromBinaryFormat(type, p);
|
|
uint32_t ip_count;
|
|
MoveFromBinaryFormat(ip_count, p);
|
|
ips.resize(ip_count);
|
|
MoveFromBinaryFormat(ips.data(), ip_count, p);
|
|
sps.resize(ip_count);
|
|
MoveFromBinaryFormat(sps.data(), ip_count, p);
|
|
return true;
|
|
}
|
|
|
|
static FILE* CreateTempFp() {
|
|
std::unique_ptr<TemporaryFile> tmpfile = ScopedTempFiles::CreateTempFile();
|
|
FILE* fp = fdopen(tmpfile->release(), "web+");
|
|
if (fp == nullptr) {
|
|
PLOG(ERROR) << "fdopen";
|
|
return nullptr;
|
|
}
|
|
return fp;
|
|
}
|
|
|
|
CallChainJoiner::CallChainJoiner(size_t cache_size, size_t matched_node_count_to_extend_callchain,
|
|
bool keep_original_callchains)
|
|
: keep_original_callchains_(keep_original_callchains),
|
|
original_chains_fp_(nullptr),
|
|
joined_chains_fp_(nullptr),
|
|
next_chain_index_(0u) {
|
|
cache_stat_.cache_size = cache_size;
|
|
cache_stat_.matched_node_count_to_extend_callchain = matched_node_count_to_extend_callchain;
|
|
}
|
|
|
|
CallChainJoiner::~CallChainJoiner() {
|
|
if (original_chains_fp_ != nullptr) {
|
|
fclose(original_chains_fp_);
|
|
}
|
|
if (joined_chains_fp_ != nullptr) {
|
|
fclose(joined_chains_fp_);
|
|
}
|
|
}
|
|
|
|
bool CallChainJoiner::AddCallChain(pid_t pid, pid_t tid, ChainType type,
|
|
const std::vector<uint64_t>& ips,
|
|
const std::vector<uint64_t>& sps) {
|
|
CHECK_EQ(ips.size(), sps.size());
|
|
CHECK_GT(ips.size(), 0u);
|
|
CHECK(type == ORIGINAL_OFFLINE || type == ORIGINAL_REMOTE);
|
|
// Make sure sps are in non-decreasing order, and there is no duplicated items.
|
|
size_t ip_count = ips.size();
|
|
for (size_t i = 1; i < ips.size(); ++i) {
|
|
if (sps[i] < sps[i - 1]) {
|
|
ip_count = i;
|
|
break;
|
|
} else if (sps[i] == sps[i - 1]) {
|
|
bool has_duplicated_items = false;
|
|
for (size_t j = i; j > 0 && sps[j - 1] == sps[i]; --j) {
|
|
if (ips[j - 1] == ips[i]) {
|
|
has_duplicated_items = true;
|
|
break;
|
|
}
|
|
}
|
|
if (has_duplicated_items) {
|
|
ip_count = i;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (original_chains_fp_ == nullptr) {
|
|
original_chains_fp_ = CreateTempFp();
|
|
if (original_chains_fp_ == nullptr) {
|
|
return false;
|
|
}
|
|
}
|
|
stat_.chain_count++;
|
|
return WriteCallChain(original_chains_fp_, pid, tid, type, ips, sps, ip_count);
|
|
}
|
|
|
|
bool CallChainJoiner::JoinCallChains() {
|
|
if (stat_.chain_count == 0u) {
|
|
return true;
|
|
}
|
|
LRUCache cache(cache_stat_.cache_size, cache_stat_.matched_node_count_to_extend_callchain);
|
|
std::unique_ptr<FILE, decltype(&fclose)> tmp_fp(CreateTempFp(), fclose);
|
|
if (!tmp_fp) {
|
|
return false;
|
|
}
|
|
joined_chains_fp_ = CreateTempFp();
|
|
if (joined_chains_fp_ == nullptr) {
|
|
return false;
|
|
}
|
|
pid_t pid;
|
|
pid_t tid;
|
|
ChainType type;
|
|
std::vector<uint64_t> ips;
|
|
std::vector<uint64_t> sps;
|
|
if (fseek(original_chains_fp_, 0, SEEK_END) != 0) {
|
|
PLOG(ERROR) << "fseek";
|
|
return false;
|
|
}
|
|
std::vector<std::pair<FILE*, FILE*>> file_pairs = {
|
|
std::make_pair(original_chains_fp_, tmp_fp.get()),
|
|
std::make_pair(tmp_fp.get(), joined_chains_fp_)};
|
|
for (size_t pass = 0; pass < 2u; ++pass) {
|
|
auto& pair = file_pairs[pass];
|
|
for (size_t i = 0; i < stat_.chain_count; ++i) {
|
|
if (!ReadCallChainInReverseOrder(pair.first, pid, tid, type, ips, sps)) {
|
|
return false;
|
|
}
|
|
if (pass == 0u) {
|
|
if (type == ORIGINAL_OFFLINE) {
|
|
type = JOINED_OFFLINE;
|
|
} else if (type == ORIGINAL_REMOTE) {
|
|
type = JOINED_REMOTE;
|
|
}
|
|
stat_.before_join_node_count += ips.size();
|
|
}
|
|
|
|
cache.AddCallChain(tid, ips, sps);
|
|
|
|
if (pass == 1u) {
|
|
stat_.after_join_node_count += ips.size();
|
|
stat_.after_join_max_chain_length = std::max(stat_.after_join_max_chain_length, ips.size());
|
|
}
|
|
|
|
if (!WriteCallChain(pair.second, pid, tid, type, ips, sps, ips.size())) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
cache_stat_ = cache.Stat();
|
|
return true;
|
|
}
|
|
|
|
bool CallChainJoiner::GetNextCallChain(pid_t& pid, pid_t& tid, ChainType& type,
|
|
std::vector<uint64_t>& ips, std::vector<uint64_t>& sps) {
|
|
if (next_chain_index_ == stat_.chain_count * 2) {
|
|
// No more chains.
|
|
return false;
|
|
}
|
|
if (next_chain_index_ == 0u) {
|
|
if (fseek(original_chains_fp_, 0, SEEK_SET) != 0 ||
|
|
fseek(joined_chains_fp_, 0, SEEK_SET) != 0) {
|
|
PLOG(ERROR) << "fseek";
|
|
return false;
|
|
}
|
|
}
|
|
FILE* fp;
|
|
if (keep_original_callchains_) {
|
|
fp = (next_chain_index_ & 1) ? joined_chains_fp_ : original_chains_fp_;
|
|
next_chain_index_++;
|
|
} else {
|
|
fp = joined_chains_fp_;
|
|
next_chain_index_ += 2;
|
|
}
|
|
return ReadCallChain(fp, pid, tid, type, ips, sps);
|
|
}
|
|
|
|
void CallChainJoiner::DumpStat() {
|
|
LOG(DEBUG) << "call chain joiner stat:";
|
|
LOG(DEBUG) << " cache_size: " << cache_stat_.cache_size;
|
|
LOG(DEBUG) << " matched_node_count_to_extend_callchain: "
|
|
<< cache_stat_.matched_node_count_to_extend_callchain;
|
|
LOG(DEBUG) << " max_node_count in cache: " << cache_stat_.max_node_count;
|
|
LOG(DEBUG) << " used_node_count in cache: " << cache_stat_.used_node_count;
|
|
LOG(DEBUG) << " recycled_node_count in cache: " << cache_stat_.recycled_node_count;
|
|
LOG(DEBUG) << " call_chain_count: " << stat_.chain_count;
|
|
LOG(DEBUG) << " before_join_node_count: " << stat_.before_join_node_count;
|
|
if (stat_.chain_count > 0u) {
|
|
LOG(DEBUG) << " before_join_average_chain_length: "
|
|
<< (stat_.before_join_node_count * 1.0 / stat_.chain_count);
|
|
}
|
|
LOG(DEBUG) << " after_join_node_count: " << stat_.after_join_node_count;
|
|
if (stat_.chain_count > 0u) {
|
|
LOG(DEBUG) << " after_join_average_chain_length: "
|
|
<< (stat_.after_join_node_count * 1.0 / stat_.chain_count);
|
|
}
|
|
LOG(DEBUG) << " after_join_max_chain_length: " << stat_.after_join_max_chain_length;
|
|
}
|
|
|
|
} // namespace simpleperf
|