| /* |
| * 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_ |