| /* |
| * Copyright (C) 2020 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 SRC_PROFILING_COMMON_CALLSTACK_TRIE_H_ |
| #define SRC_PROFILING_COMMON_CALLSTACK_TRIE_H_ |
| |
| #include <set> |
| #include <string> |
| #include <typeindex> |
| #include <vector> |
| |
| #include <unwindstack/Unwinder.h> |
| |
| #include "src/profiling/common/interner.h" |
| #include "src/profiling/common/unwind_support.h" |
| |
| namespace perfetto { |
| namespace profiling { |
| |
| struct Mapping { |
| explicit Mapping(Interned<std::string> b) : build_id(std::move(b)) {} |
| |
| Interned<std::string> build_id; |
| uint64_t exact_offset = 0; |
| uint64_t start_offset = 0; |
| uint64_t start = 0; |
| uint64_t end = 0; |
| uint64_t load_bias = 0; |
| std::vector<Interned<std::string>> path_components{}; |
| |
| bool operator<(const Mapping& other) const { |
| return std::tie(build_id, exact_offset, start_offset, start, end, load_bias, |
| path_components) < |
| std::tie(other.build_id, other.exact_offset, other.start_offset, |
| other.start, other.end, other.load_bias, |
| other.path_components); |
| } |
| bool operator==(const Mapping& other) const { |
| return std::tie(build_id, exact_offset, start_offset, start, end, load_bias, |
| path_components) == |
| std::tie(other.build_id, other.exact_offset, other.start_offset, |
| other.start, other.end, other.load_bias, |
| other.path_components); |
| } |
| }; |
| |
| struct Frame { |
| Frame(Interned<Mapping> m, Interned<std::string> fn_name, uint64_t pc) |
| : mapping(m), function_name(fn_name), rel_pc(pc) {} |
| Interned<Mapping> mapping; |
| Interned<std::string> function_name; |
| uint64_t rel_pc; |
| |
| bool operator<(const Frame& other) const { |
| return std::tie(mapping, function_name, rel_pc) < |
| std::tie(other.mapping, other.function_name, other.rel_pc); |
| } |
| |
| bool operator==(const Frame& other) const { |
| return std::tie(mapping, function_name, rel_pc) == |
| std::tie(other.mapping, other.function_name, other.rel_pc); |
| } |
| }; |
| |
| // Graph of function callsites. A single instance can be used for callsites from |
| // different processes. Each call site is represented by a |
| // GlobalCallstackTrie::Node that is owned by the parent callsite. Each node has |
| // a pointer to its parent, which means the function call-graph can be |
| // reconstructed from a GlobalCallstackTrie::Node by walking down the parent |
| // chain. |
| // |
| // For the following two callstacks: |
| // * libc_init -> main -> foo -> alloc_buf |
| // * libc_init -> main -> bar -> alloc_buf |
| // The tree looks as following: |
| // alloc_buf alloc_buf |
| // | | |
| // foo bar |
| // \ / |
| // main |
| // | |
| // libc_init |
| // | |
| // [root_] |
| class GlobalCallstackTrie { |
| public: |
| // Optionally, Nodes can be externally refcounted via |IncrementNode| and |
| // |DecrementNode|. In which case, the orphaned nodes are deleted when the |
| // last reference is decremented. |
| class Node { |
| public: |
| // This is opaque except to GlobalCallstackTrie. |
| friend class GlobalCallstackTrie; |
| |
| // Allow building a node out of a frame for GetChild. |
| explicit Node(Interned<Frame> frame) : Node(frame, 0, nullptr) {} |
| Node(const Node&) = default; |
| Node(Node&&) = default; |
| |
| Node(Interned<Frame> frame, uint64_t id) |
| : Node(std::move(frame), id, nullptr) {} |
| Node(Interned<Frame> frame, uint64_t id, Node* parent) |
| : id_(id), parent_(parent), location_(frame) {} |
| |
| ~Node() { PERFETTO_DCHECK(!ref_count_); } |
| |
| uint64_t id() const { return id_; } |
| |
| private: |
| Node* GetOrCreateChild(const Interned<Frame>& loc); |
| // Deletes all descendant nodes, regardless of |ref_count_|. |
| void DeleteChildren() { children_.clear(); } |
| |
| uint64_t ref_count_ = 0; |
| uint64_t id_; |
| Node* const parent_; |
| const Interned<Frame> location_; |
| |
| class NodeComparator { |
| public: |
| bool operator()(const Node& one, const Node& other) const { |
| return one.location_ < other.location_; |
| } |
| }; |
| Node* AddChild(const Interned<Frame>& loc, |
| uint64_t next_callstack_id_, |
| Node* parent); |
| void RemoveChild(Node* node); |
| Node* GetChild(const Interned<Frame>& loc); |
| |
| std::set<Node, NodeComparator> children_; |
| }; |
| |
| GlobalCallstackTrie() = default; |
| ~GlobalCallstackTrie() = default; |
| GlobalCallstackTrie(const GlobalCallstackTrie&) = delete; |
| GlobalCallstackTrie& operator=(const GlobalCallstackTrie&) = delete; |
| |
| // Moving this would invalidate the back pointers to the root_ node. |
| GlobalCallstackTrie(GlobalCallstackTrie&&) = delete; |
| GlobalCallstackTrie& operator=(GlobalCallstackTrie&&) = delete; |
| |
| Interned<Frame> InternCodeLocation(const unwindstack::FrameData& loc, |
| const std::string& build_id); |
| |
| Node* CreateCallsite(const std::vector<unwindstack::FrameData>& callstack, |
| const std::vector<std::string>& build_ids); |
| Node* CreateCallsite(const std::vector<Interned<Frame>>& callstack); |
| |
| static void IncrementNode(Node* node); |
| static void DecrementNode(Node* node); |
| |
| std::vector<Interned<Frame>> BuildInverseCallstack(const Node* node) const; |
| |
| // Purges all interned callstacks (and the associated internings), without |
| // restarting any interning sequences. Incompatible with external refcounting |
| // of nodes (Node.ref_count_). |
| void ClearTrie() { |
| PERFETTO_DLOG("Clearing trie"); |
| root_.DeleteChildren(); |
| } |
| |
| private: |
| Node* GetOrCreateChild(Node* self, const Interned<Frame>& loc); |
| |
| Interned<Frame> MakeRootFrame(); |
| |
| Interner<std::string> string_interner_; |
| Interner<Mapping> mapping_interner_; |
| Interner<Frame> frame_interner_; |
| |
| uint64_t next_callstack_id_ = 0; |
| |
| // Note: profile_module in trace processor relies on the value of this root |
| // callsite being exactly "1". See the perf_sample parsing code. |
| Node root_{MakeRootFrame(), ++next_callstack_id_}; |
| }; |
| |
| } // namespace profiling |
| } // namespace perfetto |
| |
| template <> |
| struct std::hash<::perfetto::profiling::Mapping> { |
| using argument_type = ::perfetto::profiling::Mapping; |
| using result_type = size_t; |
| result_type operator()(const argument_type& mapping) { |
| size_t h = |
| std::hash<::perfetto::profiling::InternID>{}(mapping.build_id.id()); |
| h ^= std::hash<uint64_t>{}(mapping.exact_offset); |
| h ^= std::hash<uint64_t>{}(mapping.start_offset); |
| h ^= std::hash<uint64_t>{}(mapping.start); |
| h ^= std::hash<uint64_t>{}(mapping.end); |
| h ^= std::hash<uint64_t>{}(mapping.load_bias); |
| for (const auto& path : mapping.path_components) |
| h ^= std::hash<uint64_t>{}(path.id()); |
| return h; |
| } |
| }; |
| |
| template <> |
| struct std::hash<::perfetto::profiling::Frame> { |
| using argument_type = ::perfetto::profiling::Frame; |
| using result_type = size_t; |
| result_type operator()(const argument_type& frame) { |
| size_t h = std::hash<::perfetto::profiling::InternID>{}(frame.mapping.id()); |
| h ^= std::hash<::perfetto::profiling::InternID>{}(frame.function_name.id()); |
| h ^= std::hash<uint64_t>{}(frame.rel_pc); |
| return h; |
| } |
| }; |
| |
| #endif // SRC_PROFILING_COMMON_CALLSTACK_TRIE_H_ |