1 // Copyright 2015 the V8 project authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 #ifndef V8_PROFILER_SAMPLING_HEAP_PROFILER_H_ 6 #define V8_PROFILER_SAMPLING_HEAP_PROFILER_H_ 7 8 #include <deque> 9 #include <map> 10 #include <memory> 11 #include <unordered_map> 12 13 #include "include/v8-profiler.h" 14 #include "src/heap/heap.h" 15 #include "src/profiler/strings-storage.h" 16 17 namespace v8 { 18 19 namespace base { 20 class RandomNumberGenerator; 21 } // namespace base 22 23 namespace internal { 24 25 class AllocationProfile : public v8::AllocationProfile { 26 public: 27 AllocationProfile() = default; 28 AllocationProfile(const AllocationProfile&) = delete; 29 AllocationProfile& operator=(const AllocationProfile&) = delete; 30 GetRootNode()31 v8::AllocationProfile::Node* GetRootNode() override { 32 return nodes_.size() == 0 ? nullptr : &nodes_.front(); 33 } 34 GetSamples()35 const std::vector<v8::AllocationProfile::Sample>& GetSamples() override { 36 return samples_; 37 } 38 39 private: 40 std::deque<v8::AllocationProfile::Node> nodes_; 41 std::vector<v8::AllocationProfile::Sample> samples_; 42 43 friend class SamplingHeapProfiler; 44 }; 45 46 class SamplingHeapProfiler { 47 public: 48 class AllocationNode { 49 public: 50 using FunctionId = uint64_t; AllocationNode(AllocationNode * parent,const char * name,int script_id,int start_position,uint32_t id)51 AllocationNode(AllocationNode* parent, const char* name, int script_id, 52 int start_position, uint32_t id) 53 : parent_(parent), 54 script_id_(script_id), 55 script_position_(start_position), 56 name_(name), 57 id_(id) {} 58 AllocationNode(const AllocationNode&) = delete; 59 AllocationNode& operator=(const AllocationNode&) = delete; 60 FindChildNode(FunctionId id)61 AllocationNode* FindChildNode(FunctionId id) { 62 auto it = children_.find(id); 63 return it != children_.end() ? it->second.get() : nullptr; 64 } 65 AddChildNode(FunctionId id,std::unique_ptr<AllocationNode> node)66 AllocationNode* AddChildNode(FunctionId id, 67 std::unique_ptr<AllocationNode> node) { 68 return children_.emplace(id, std::move(node)).first->second.get(); 69 } 70 function_id(int script_id,int start_position,const char * name)71 static FunctionId function_id(int script_id, int start_position, 72 const char* name) { 73 // script_id == kNoScriptId case: 74 // Use function name pointer as an id. Names derived from VM state 75 // must not collide with the builtin names. The least significant bit 76 // of the id is set to 1. 77 if (script_id == v8::UnboundScript::kNoScriptId) { 78 return reinterpret_cast<intptr_t>(name) | 1; 79 } 80 // script_id != kNoScriptId case: 81 // Use script_id, start_position pair to uniquelly identify the node. 82 // The least significant bit of the id is set to 0. 83 DCHECK(static_cast<unsigned>(start_position) < (1u << 31)); 84 return (static_cast<uint64_t>(script_id) << 32) + (start_position << 1); 85 } 86 87 private: 88 // TODO(alph): make use of unordered_map's here. Pay attention to 89 // iterator invalidation during TranslateAllocationNode. 90 std::map<size_t, unsigned int> allocations_; 91 std::map<FunctionId, std::unique_ptr<AllocationNode>> children_; 92 AllocationNode* const parent_; 93 const int script_id_; 94 const int script_position_; 95 const char* const name_; 96 uint32_t id_; 97 bool pinned_ = false; 98 99 friend class SamplingHeapProfiler; 100 }; 101 102 struct Sample { SampleSample103 Sample(size_t size_, AllocationNode* owner_, Local<Value> local_, 104 SamplingHeapProfiler* profiler_, uint64_t sample_id) 105 : size(size_), 106 owner(owner_), 107 global(reinterpret_cast<v8::Isolate*>(profiler_->isolate_), local_), 108 profiler(profiler_), 109 sample_id(sample_id) {} 110 Sample(const Sample&) = delete; 111 Sample& operator=(const Sample&) = delete; 112 const size_t size; 113 AllocationNode* const owner; 114 Global<Value> global; 115 SamplingHeapProfiler* const profiler; 116 const uint64_t sample_id; 117 }; 118 119 SamplingHeapProfiler(Heap* heap, StringsStorage* names, uint64_t rate, 120 int stack_depth, v8::HeapProfiler::SamplingFlags flags); 121 ~SamplingHeapProfiler(); 122 SamplingHeapProfiler(const SamplingHeapProfiler&) = delete; 123 SamplingHeapProfiler& operator=(const SamplingHeapProfiler&) = delete; 124 125 v8::AllocationProfile* GetAllocationProfile(); names()126 StringsStorage* names() const { return names_; } 127 128 private: 129 class Observer : public AllocationObserver { 130 public: Observer(Heap * heap,intptr_t step_size,uint64_t rate,SamplingHeapProfiler * profiler,base::RandomNumberGenerator * random)131 Observer(Heap* heap, intptr_t step_size, uint64_t rate, 132 SamplingHeapProfiler* profiler, 133 base::RandomNumberGenerator* random) 134 : AllocationObserver(step_size), 135 profiler_(profiler), 136 heap_(heap), 137 random_(random), 138 rate_(rate) {} 139 140 protected: Step(int bytes_allocated,Address soon_object,size_t size)141 void Step(int bytes_allocated, Address soon_object, size_t size) override { 142 USE(heap_); 143 DCHECK(heap_->gc_state() == Heap::NOT_IN_GC); 144 if (soon_object) { 145 // TODO(ofrobots): it would be better to sample the next object rather 146 // than skipping this sample epoch if soon_object happens to be null. 147 profiler_->SampleObject(soon_object, size); 148 } 149 } 150 GetNextStepSize()151 intptr_t GetNextStepSize() override { return GetNextSampleInterval(rate_); } 152 153 private: 154 intptr_t GetNextSampleInterval(uint64_t rate); 155 SamplingHeapProfiler* const profiler_; 156 Heap* const heap_; 157 base::RandomNumberGenerator* const random_; 158 uint64_t const rate_; 159 }; 160 161 void SampleObject(Address soon_object, size_t size); 162 163 const std::vector<v8::AllocationProfile::Sample> BuildSamples() const; 164 165 AllocationNode* FindOrAddChildNode(AllocationNode* parent, const char* name, 166 int script_id, int start_position); 167 static void OnWeakCallback(const WeakCallbackInfo<Sample>& data); 168 next_node_id()169 uint32_t next_node_id() { return ++last_node_id_; } next_sample_id()170 uint64_t next_sample_id() { return ++last_sample_id_; } 171 172 // Methods that construct v8::AllocationProfile. 173 174 // Translates the provided AllocationNode *node* returning an equivalent 175 // AllocationProfile::Node. The newly created AllocationProfile::Node is added 176 // to the provided AllocationProfile *profile*. Line numbers, column numbers, 177 // and script names are resolved using *scripts* which maps all currently 178 // loaded scripts keyed by their script id. 179 v8::AllocationProfile::Node* TranslateAllocationNode( 180 AllocationProfile* profile, SamplingHeapProfiler::AllocationNode* node, 181 const std::map<int, Handle<Script>>& scripts); 182 v8::AllocationProfile::Allocation ScaleSample(size_t size, 183 unsigned int count) const; 184 AllocationNode* AddStack(); 185 186 Isolate* const isolate_; 187 Heap* const heap_; 188 uint64_t last_sample_id_ = 0; 189 uint32_t last_node_id_ = 0; 190 Observer allocation_observer_; 191 StringsStorage* const names_; 192 AllocationNode profile_root_; 193 std::unordered_map<Sample*, std::unique_ptr<Sample>> samples_; 194 const int stack_depth_; 195 const uint64_t rate_; 196 v8::HeapProfiler::SamplingFlags flags_; 197 }; 198 199 } // namespace internal 200 } // namespace v8 201 202 #endif // V8_PROFILER_SAMPLING_HEAP_PROFILER_H_ 203