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.
182 lines
5.9 KiB
182 lines
5.9 KiB
4 months ago
|
/*
|
||
|
* 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.
|
||
|
*/
|
||
|
|
||
|
#ifndef SIMPLE_PERF_CALLCHAIN_JOINER_H_
|
||
|
#define SIMPLE_PERF_CALLCHAIN_JOINER_H_
|
||
|
|
||
|
#include <inttypes.h>
|
||
|
#include <stdio.h>
|
||
|
#include <unistd.h>
|
||
|
|
||
|
#include <unordered_set>
|
||
|
#include <vector>
|
||
|
|
||
|
namespace simpleperf {
|
||
|
|
||
|
namespace call_chain_joiner_impl {
|
||
|
|
||
|
// Each node represents a function in a call chain, having a tuple info (tid, ip, sp).
|
||
|
// A node remembers its parent in the call chain.
|
||
|
struct CacheNode {
|
||
|
uint64_t ip;
|
||
|
uint64_t sp;
|
||
|
uint32_t tid;
|
||
|
// Whether the node is at the bottom of a call chain.
|
||
|
uint32_t is_leaf : 1;
|
||
|
// The index of the parent node in a call chain.
|
||
|
uint32_t parent_index : 31;
|
||
|
// When is_leaf = false, children_count remembers how many nodes have current node as parent.
|
||
|
// When is_leaf = true, leaf_link_prev and leaf_link_next keeps current node in a LRU linked list.
|
||
|
union {
|
||
|
uint32_t children_count;
|
||
|
struct {
|
||
|
uint32_t leaf_link_prev;
|
||
|
uint32_t leaf_link_next;
|
||
|
};
|
||
|
};
|
||
|
};
|
||
|
|
||
|
static_assert(sizeof(CacheNode) == 32, "");
|
||
|
|
||
|
struct LRUCacheStat {
|
||
|
size_t cache_size = 0u;
|
||
|
size_t matched_node_count_to_extend_callchain = 0u;
|
||
|
size_t max_node_count = 0u;
|
||
|
size_t used_node_count = 0u;
|
||
|
size_t recycled_node_count = 0u;
|
||
|
};
|
||
|
|
||
|
// LRUCache caches call chains in memory, and supports extending a call chain if the (tid, ip, sp)
|
||
|
// tuples of its top [matched_node_count_to_extend_callchain] appear in the cache.
|
||
|
class LRUCache {
|
||
|
public:
|
||
|
// cache_size is the bytes of memory we want to use in this cache.
|
||
|
// matched_node_count_to_extend_callchain decides how many nodes we need to match to extend a
|
||
|
// call chain. Higher value means more strict.
|
||
|
LRUCache(size_t cache_size = 8 * 1024 * 1024, size_t matched_node_count_to_extend_callchain = 1);
|
||
|
~LRUCache();
|
||
|
|
||
|
// Add a call chain in the cache, and extend it if possible.
|
||
|
void AddCallChain(pid_t tid, std::vector<uint64_t>& ips, std::vector<uint64_t>& sps);
|
||
|
|
||
|
const LRUCacheStat& Stat() { return cache_stat_; }
|
||
|
|
||
|
CacheNode* FindNode(uint32_t tid, uint64_t ip, uint64_t sp) {
|
||
|
CacheNode key;
|
||
|
key.tid = tid;
|
||
|
key.ip = ip;
|
||
|
key.sp = sp;
|
||
|
auto it = node_set_.find(&key);
|
||
|
return it != node_set_.end() ? *it : nullptr;
|
||
|
}
|
||
|
|
||
|
private:
|
||
|
static bool CacheNodeEqual(const CacheNode* n1, const CacheNode* n2);
|
||
|
static size_t CacheNodeHash(const CacheNode* n);
|
||
|
|
||
|
typedef std::unordered_set<CacheNode*, decltype(&CacheNodeHash), decltype(&CacheNodeEqual)>
|
||
|
set_type;
|
||
|
|
||
|
CacheNode* GetParent(CacheNode* node) {
|
||
|
return node->parent_index == 0u ? nullptr : nodes_ + node->parent_index;
|
||
|
}
|
||
|
|
||
|
int GetNodeIndex(CacheNode* node) { return node - nodes_; }
|
||
|
|
||
|
void RemoveNodeFromLRUList(CacheNode* node) {
|
||
|
CacheNode* prev = &nodes_[node->leaf_link_prev];
|
||
|
CacheNode* next = &nodes_[node->leaf_link_next];
|
||
|
prev->leaf_link_next = node->leaf_link_next;
|
||
|
next->leaf_link_prev = node->leaf_link_prev;
|
||
|
}
|
||
|
|
||
|
void AppendNodeToLRUList(CacheNode* node) {
|
||
|
CacheNode* next = &nodes_[0];
|
||
|
CacheNode* prev = &nodes_[next->leaf_link_prev];
|
||
|
node->leaf_link_next = 0;
|
||
|
node->leaf_link_prev = next->leaf_link_prev;
|
||
|
next->leaf_link_prev = prev->leaf_link_next = GetNodeIndex(node);
|
||
|
}
|
||
|
|
||
|
void DecreaseChildCountOfNode(CacheNode* node) {
|
||
|
if (--node->children_count == 0u) {
|
||
|
node->is_leaf = true;
|
||
|
AppendNodeToLRUList(node);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
CacheNode* GetNode(uint32_t tid, uint64_t ip, uint64_t sp);
|
||
|
CacheNode* AllocNode();
|
||
|
void LinkParent(CacheNode* child, CacheNode* new_parent);
|
||
|
void UnlinkParent(CacheNode* child);
|
||
|
|
||
|
CacheNode* nodes_;
|
||
|
set_type node_set_;
|
||
|
LRUCacheStat cache_stat_;
|
||
|
};
|
||
|
|
||
|
} // namespace call_chain_joiner_impl
|
||
|
|
||
|
// CallChainJoiner is used to join callchains of samples in the same thread, in order to get
|
||
|
// complete call graph. For example, if we have two samples for a thread:
|
||
|
// sample 1: (ip A, sp A) -> (ip B, sp B) -> (ip C, sp C) -> ...
|
||
|
// sample 2: (ip B, sp B) -> (ip C, sp C) -> ...
|
||
|
// Because we know (ip A, sp A) calls (ip B, sp B) in sample 1, then we can guess the (ip B, sp B)
|
||
|
// in sample 2 is also called from (ip A, sp A). So we can add (ip A, sp A) in sample 2 as below.
|
||
|
// sample 2: (ip A, sp A) -> (ip B, sp B) -> (ip C, sp C) -> ...
|
||
|
class CallChainJoiner {
|
||
|
public:
|
||
|
// The parameters are used in LRUCache.
|
||
|
CallChainJoiner(size_t cache_size, size_t matched_node_count_to_extend_callchain,
|
||
|
bool keep_original_callchains);
|
||
|
~CallChainJoiner();
|
||
|
|
||
|
enum ChainType {
|
||
|
ORIGINAL_OFFLINE,
|
||
|
ORIGINAL_REMOTE,
|
||
|
JOINED_OFFLINE,
|
||
|
JOINED_REMOTE,
|
||
|
};
|
||
|
|
||
|
bool AddCallChain(pid_t pid, pid_t tid, ChainType type, const std::vector<uint64_t>& ips,
|
||
|
const std::vector<uint64_t>& sps);
|
||
|
bool JoinCallChains();
|
||
|
bool GetNextCallChain(pid_t& pid, pid_t& tid, ChainType& type, std::vector<uint64_t>& ips,
|
||
|
std::vector<uint64_t>& sps);
|
||
|
|
||
|
struct Stat {
|
||
|
size_t chain_count = 0u;
|
||
|
size_t before_join_node_count = 0u;
|
||
|
size_t after_join_node_count = 0u;
|
||
|
size_t after_join_max_chain_length = 0u;
|
||
|
};
|
||
|
void DumpStat();
|
||
|
const Stat& GetStat() { return stat_; }
|
||
|
const call_chain_joiner_impl::LRUCacheStat& GetCacheStat() { return cache_stat_; }
|
||
|
|
||
|
private:
|
||
|
bool keep_original_callchains_;
|
||
|
FILE* original_chains_fp_;
|
||
|
FILE* joined_chains_fp_;
|
||
|
size_t next_chain_index_;
|
||
|
call_chain_joiner_impl::LRUCacheStat cache_stat_;
|
||
|
Stat stat_;
|
||
|
};
|
||
|
|
||
|
} // namespace simpleperf
|
||
|
|
||
|
#endif // SIMPLE_PERF_CALLCHAIN_JOINER_H_
|