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