1 /* 2 * Copyright (C) 2020 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 #ifndef SRC_PROFILING_COMMON_CALLSTACK_TRIE_H_ 18 #define SRC_PROFILING_COMMON_CALLSTACK_TRIE_H_ 19 20 #include <string> 21 #include <typeindex> 22 #include <vector> 23 24 #include "perfetto/ext/base/lookup_set.h" 25 #include "src/profiling/common/interner.h" 26 #include "src/profiling/common/unwind_support.h" 27 28 namespace perfetto { 29 namespace profiling { 30 31 struct Mapping { MappingMapping32 Mapping(Interned<std::string> b) : build_id(std::move(b)) {} 33 34 Interned<std::string> build_id; 35 uint64_t exact_offset = 0; 36 uint64_t start_offset = 0; 37 uint64_t start = 0; 38 uint64_t end = 0; 39 uint64_t load_bias = 0; 40 std::vector<Interned<std::string>> path_components{}; 41 42 bool operator<(const Mapping& other) const { 43 return std::tie(build_id, exact_offset, start_offset, start, end, load_bias, 44 path_components) < 45 std::tie(other.build_id, other.exact_offset, other.start_offset, 46 other.start, other.end, other.load_bias, 47 other.path_components); 48 } 49 bool operator==(const Mapping& other) const { 50 return std::tie(build_id, exact_offset, start_offset, start, end, load_bias, 51 path_components) == 52 std::tie(other.build_id, other.exact_offset, other.start_offset, 53 other.start, other.end, other.load_bias, 54 other.path_components); 55 } 56 }; 57 58 struct Frame { FrameFrame59 Frame(Interned<Mapping> m, Interned<std::string> fn_name, uint64_t pc) 60 : mapping(m), function_name(fn_name), rel_pc(pc) {} 61 Interned<Mapping> mapping; 62 Interned<std::string> function_name; 63 uint64_t rel_pc; 64 65 bool operator<(const Frame& other) const { 66 return std::tie(mapping, function_name, rel_pc) < 67 std::tie(other.mapping, other.function_name, other.rel_pc); 68 } 69 70 bool operator==(const Frame& other) const { 71 return std::tie(mapping, function_name, rel_pc) == 72 std::tie(other.mapping, other.function_name, other.rel_pc); 73 } 74 }; 75 76 // Graph of function callsites. A single instance can be used for callsites from 77 // different processes. Each call site is represented by a 78 // GlobalCallstackTrie::Node that is owned by the parent callsite. Each node has 79 // a pointer to its parent, which means the function call-graph can be 80 // reconstructed from a GlobalCallstackTrie::Node by walking down the parent 81 // chain. 82 // 83 // For the following two callstacks: 84 // * libc_init -> main -> foo -> alloc_buf 85 // * libc_init -> main -> bar -> alloc_buf 86 // The tree looks as following: 87 // alloc_buf alloc_buf 88 // | | 89 // foo bar 90 // \ / 91 // main 92 // | 93 // libc_init 94 // | 95 // [root_] 96 class GlobalCallstackTrie { 97 public: 98 // Optionally, Nodes can be externally refcounted via |IncrementNode| and 99 // |DecrementNode|. In which case, the orphaned nodes are deleted when the 100 // last reference is decremented. 101 class Node { 102 public: 103 // This is opaque except to GlobalCallstackTrie. 104 friend class GlobalCallstackTrie; 105 106 // Allow building a node out of a frame for base::LookupSet. Node(Interned<Frame> frame)107 Node(Interned<Frame> frame) : Node(frame, 0, nullptr) {} Node(Interned<Frame> frame,uint64_t id)108 Node(Interned<Frame> frame, uint64_t id) 109 : Node(std::move(frame), id, nullptr) {} Node(Interned<Frame> frame,uint64_t id,Node * parent)110 Node(Interned<Frame> frame, uint64_t id, Node* parent) 111 : id_(id), parent_(parent), location_(frame) {} 112 ~Node()113 ~Node() { PERFETTO_DCHECK(!ref_count_); } 114 id()115 uint64_t id() const { return id_; } 116 117 private: 118 Node* GetOrCreateChild(const Interned<Frame>& loc); 119 // Deletes all descendant nodes, regardless of |ref_count_|. DeleteChildren()120 void DeleteChildren() { children_.Clear(); } 121 122 uint64_t ref_count_ = 0; 123 uint64_t id_; 124 Node* const parent_; 125 const Interned<Frame> location_; 126 base::LookupSet<Node, const Interned<Frame>, &Node::location_> children_; 127 }; 128 129 GlobalCallstackTrie() = default; 130 ~GlobalCallstackTrie() = default; 131 GlobalCallstackTrie(const GlobalCallstackTrie&) = delete; 132 GlobalCallstackTrie& operator=(const GlobalCallstackTrie&) = delete; 133 134 // Moving this would invalidate the back pointers to the root_ node. 135 GlobalCallstackTrie(GlobalCallstackTrie&&) = delete; 136 GlobalCallstackTrie& operator=(GlobalCallstackTrie&&) = delete; 137 138 Interned<Frame> InternCodeLocation(const FrameData& loc); 139 140 Node* CreateCallsite(const std::vector<FrameData>& callstack); 141 Node* CreateCallsite(const std::vector<Interned<Frame>>& callstack); 142 143 static void IncrementNode(Node* node); 144 static void DecrementNode(Node* node); 145 146 std::vector<Interned<Frame>> BuildInverseCallstack(const Node* node) const; 147 148 // Purges all interned callstacks (and the associated internings), without 149 // restarting any interning sequences. Incompatible with external refcounting 150 // of nodes (Node.ref_count_). ClearTrie()151 void ClearTrie() { 152 PERFETTO_DLOG("Clearing trie"); 153 root_.DeleteChildren(); 154 } 155 156 private: 157 Node* GetOrCreateChild(Node* self, const Interned<Frame>& loc); 158 159 Interned<Frame> MakeRootFrame(); 160 161 Interner<std::string> string_interner_; 162 Interner<Mapping> mapping_interner_; 163 Interner<Frame> frame_interner_; 164 165 uint64_t next_callstack_id_ = 0; 166 167 Node root_{MakeRootFrame(), ++next_callstack_id_}; 168 }; 169 170 } // namespace profiling 171 } // namespace perfetto 172 173 template <> 174 struct std::hash<::perfetto::profiling::Mapping> { 175 using argument_type = ::perfetto::profiling::Mapping; 176 using result_type = size_t; 177 result_type operator()(const argument_type& mapping) { 178 size_t h = 179 std::hash<::perfetto::profiling::InternID>{}(mapping.build_id.id()); 180 h ^= std::hash<uint64_t>{}(mapping.exact_offset); 181 h ^= std::hash<uint64_t>{}(mapping.start_offset); 182 h ^= std::hash<uint64_t>{}(mapping.start); 183 h ^= std::hash<uint64_t>{}(mapping.end); 184 h ^= std::hash<uint64_t>{}(mapping.load_bias); 185 for (const auto& path : mapping.path_components) 186 h ^= std::hash<uint64_t>{}(path.id()); 187 return h; 188 } 189 }; 190 191 template <> 192 struct std::hash<::perfetto::profiling::Frame> { 193 using argument_type = ::perfetto::profiling::Frame; 194 using result_type = size_t; 195 result_type operator()(const argument_type& frame) { 196 size_t h = std::hash<::perfetto::profiling::InternID>{}(frame.mapping.id()); 197 h ^= std::hash<::perfetto::profiling::InternID>{}(frame.function_name.id()); 198 h ^= std::hash<uint64_t>{}(frame.rel_pc); 199 return h; 200 } 201 }; 202 203 #endif // SRC_PROFILING_COMMON_CALLSTACK_TRIE_H_ 204