Line data Source code
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 : #include "src/profiler/sampling-heap-profiler.h"
6 :
7 : #include <stdint.h>
8 : #include <memory>
9 : #include "src/api.h"
10 : #include "src/base/ieee754.h"
11 : #include "src/base/utils/random-number-generator.h"
12 : #include "src/frames-inl.h"
13 : #include "src/heap/heap.h"
14 : #include "src/isolate.h"
15 : #include "src/profiler/strings-storage.h"
16 :
17 : namespace v8 {
18 : namespace internal {
19 :
20 : // We sample with a Poisson process, with constant average sampling interval.
21 : // This follows the exponential probability distribution with parameter
22 : // λ = 1/rate where rate is the average number of bytes between samples.
23 : //
24 : // Let u be a uniformly distributed random number between 0 and 1, then
25 : // next_sample = (- ln u) / λ
26 49397 : intptr_t SamplingAllocationObserver::GetNextSampleInterval(uint64_t rate) {
27 49397 : if (FLAG_sampling_heap_profiler_suppress_randomness) {
28 49397 : return static_cast<intptr_t>(rate);
29 : }
30 0 : double u = random_->NextDouble();
31 0 : double next = (-base::ieee754::log(u)) * rate;
32 : return next < kPointerSize
33 : ? kPointerSize
34 0 : : (next > INT_MAX ? INT_MAX : static_cast<intptr_t>(next));
35 : }
36 :
37 : // Samples were collected according to a poisson process. Since we have not
38 : // recorded all allocations, we must approximate the shape of the underlying
39 : // space of allocations based on the samples we have collected. Given that
40 : // we sample at rate R, the probability that an allocation of size S will be
41 : // sampled is 1-exp(-S/R). This function uses the above probability to
42 : // approximate the true number of allocations with size *size* given that
43 : // *count* samples were observed.
44 655 : v8::AllocationProfile::Allocation SamplingHeapProfiler::ScaleSample(
45 : size_t size, unsigned int count) {
46 655 : double scale = 1.0 / (1.0 - std::exp(-static_cast<double>(size) / rate_));
47 : // Round count instead of truncating.
48 655 : return {size, static_cast<unsigned int>(count * scale + 0.5)};
49 : }
50 :
51 28 : SamplingHeapProfiler::SamplingHeapProfiler(
52 168 : Heap* heap, StringsStorage* names, uint64_t rate, int stack_depth,
53 : v8::HeapProfiler::SamplingFlags flags)
54 : : isolate_(heap->isolate()),
55 : heap_(heap),
56 : new_space_observer_(new SamplingAllocationObserver(
57 : heap_, static_cast<intptr_t>(rate), rate, this,
58 28 : heap->isolate()->random_number_generator())),
59 : other_spaces_observer_(new SamplingAllocationObserver(
60 : heap_, static_cast<intptr_t>(rate), rate, this,
61 28 : heap->isolate()->random_number_generator())),
62 : names_(names),
63 : profile_root_(nullptr, "(root)", v8::UnboundScript::kNoScriptId, 0),
64 : samples_(),
65 : stack_depth_(stack_depth),
66 : rate_(rate),
67 112 : flags_(flags) {
68 28 : CHECK_GT(rate_, 0u);
69 28 : heap->new_space()->AddAllocationObserver(new_space_observer_.get());
70 : AllSpaces spaces(heap);
71 168 : for (Space* space = spaces.next(); space != nullptr; space = spaces.next()) {
72 140 : if (space != heap->new_space()) {
73 112 : space->AddAllocationObserver(other_spaces_observer_.get());
74 : }
75 : }
76 28 : }
77 :
78 :
79 56 : SamplingHeapProfiler::~SamplingHeapProfiler() {
80 168 : heap_->new_space()->RemoveAllocationObserver(new_space_observer_.get());
81 28 : AllSpaces spaces(heap_);
82 168 : for (Space* space = spaces.next(); space != nullptr; space = spaces.next()) {
83 280 : if (space != heap_->new_space()) {
84 112 : space->RemoveAllocationObserver(other_spaces_observer_.get());
85 : }
86 : }
87 :
88 38690 : for (auto sample : samples_) {
89 38634 : delete sample;
90 : }
91 : std::set<Sample*> empty;
92 : samples_.swap(empty);
93 28 : }
94 :
95 :
96 98794 : void SamplingHeapProfiler::SampleObject(Address soon_object, size_t size) {
97 : DisallowHeapAllocation no_allocation;
98 :
99 49397 : HandleScope scope(isolate_);
100 49397 : HeapObject* heap_object = HeapObject::FromAddress(soon_object);
101 49397 : Handle<Object> obj(heap_object, isolate_);
102 :
103 : // Mark the new block as FreeSpace to make sure the heap is iterable while we
104 : // are taking the sample.
105 : heap()->CreateFillerObjectAt(soon_object, static_cast<int>(size),
106 98794 : ClearRecordedSlots::kNo);
107 :
108 : Local<v8::Value> loc = v8::Utils::ToLocal(obj);
109 :
110 49397 : AllocationNode* node = AddStack();
111 49397 : node->allocations_[size]++;
112 98794 : Sample* sample = new Sample(size, node, loc, this);
113 : samples_.insert(sample);
114 49397 : sample->global.SetWeak(sample, OnWeakCallback, WeakCallbackType::kParameter);
115 49397 : sample->global.MarkIndependent();
116 49397 : }
117 :
118 10763 : void SamplingHeapProfiler::OnWeakCallback(
119 10763 : const WeakCallbackInfo<Sample>& data) {
120 10763 : Sample* sample = data.GetParameter();
121 10763 : AllocationNode* node = sample->owner;
122 : DCHECK_GT(node->allocations_[sample->size], 0);
123 10763 : node->allocations_[sample->size]--;
124 10763 : if (node->allocations_[sample->size] == 0) {
125 60 : node->allocations_.erase(sample->size);
126 68 : while (node->allocations_.empty() && node->children_.empty() &&
127 68 : node->parent_ && !node->parent_->pinned_) {
128 4 : AllocationNode* parent = node->parent_;
129 : AllocationNode::FunctionId id = AllocationNode::function_id(
130 8 : node->script_id_, node->script_position_, node->name_);
131 : parent->children_.erase(id);
132 4 : delete node;
133 : node = parent;
134 : }
135 : }
136 10763 : sample->profiler->samples_.erase(sample);
137 10763 : delete sample;
138 10763 : }
139 :
140 : SamplingHeapProfiler::AllocationNode*
141 74481 : SamplingHeapProfiler::AllocationNode::FindOrAddChildNode(const char* name,
142 : int script_id,
143 : int start_position) {
144 74481 : FunctionId id = function_id(script_id, start_position, name);
145 : auto it = children_.find(id);
146 74481 : if (it != children_.end()) {
147 : DCHECK_EQ(strcmp(it->second->name_, name), 0);
148 74297 : return it->second;
149 : }
150 184 : auto child = new AllocationNode(this, name, script_id, start_position);
151 368 : children_.insert(std::make_pair(id, child));
152 184 : return child;
153 : }
154 :
155 115451 : SamplingHeapProfiler::AllocationNode* SamplingHeapProfiler::AddStack() {
156 49397 : AllocationNode* node = &profile_root_;
157 :
158 : std::vector<SharedFunctionInfo*> stack;
159 57824 : JavaScriptFrameIterator it(isolate_);
160 : int frames_captured = 0;
161 164848 : while (!it.done() && frames_captured < stack_depth_) {
162 : JavaScriptFrame* frame = it.frame();
163 132108 : SharedFunctionInfo* shared = frame->function()->shared();
164 66054 : stack.push_back(shared);
165 :
166 66054 : frames_captured++;
167 66054 : it.Advance();
168 : }
169 :
170 49397 : if (frames_captured == 0) {
171 : const char* name = nullptr;
172 16854 : switch (isolate_->current_vm_state()) {
173 : case GC:
174 : name = "(GC)";
175 0 : break;
176 : case PARSER:
177 : name = "(PARSER)";
178 0 : break;
179 : case COMPILER:
180 : name = "(COMPILER)";
181 6 : break;
182 : case BYTECODE_COMPILER:
183 : name = "(BYTECODE_COMPILER)";
184 98 : break;
185 : case OTHER:
186 : name = "(V8 API)";
187 8273 : break;
188 : case EXTERNAL:
189 : name = "(EXTERNAL)";
190 50 : break;
191 : case IDLE:
192 : name = "(IDLE)";
193 0 : break;
194 : case JS:
195 : name = "(JS)";
196 0 : break;
197 : }
198 8427 : return node->FindOrAddChildNode(name, v8::UnboundScript::kNoScriptId, 0);
199 : }
200 :
201 : // We need to process the stack in reverse order as the top of the stack is
202 : // the first element in the list.
203 107024 : for (auto it = stack.rbegin(); it != stack.rend(); ++it) {
204 66054 : SharedFunctionInfo* shared = *it;
205 132108 : const char* name = this->names()->GetFunctionName(shared->DebugName());
206 : int script_id = v8::UnboundScript::kNoScriptId;
207 66054 : if (shared->script()->IsScript()) {
208 : Script* script = Script::cast(shared->script());
209 : script_id = script->id();
210 : }
211 66054 : node = node->FindOrAddChildNode(name, script_id, shared->start_position());
212 : }
213 : return node;
214 : }
215 :
216 182 : v8::AllocationProfile::Node* SamplingHeapProfiler::TranslateAllocationNode(
217 : AllocationProfile* profile, SamplingHeapProfiler::AllocationNode* node,
218 : const std::map<int, Handle<Script>>& scripts) {
219 : // By pinning the node we make sure its children won't get disposed if
220 : // a GC kicks in during the tree retrieval.
221 182 : node->pinned_ = true;
222 : Local<v8::String> script_name =
223 182 : ToApiHandle<v8::String>(isolate_->factory()->InternalizeUtf8String(""));
224 : int line = v8::AllocationProfile::kNoLineNumberInfo;
225 : int column = v8::AllocationProfile::kNoColumnNumberInfo;
226 : std::vector<v8::AllocationProfile::Allocation> allocations;
227 182 : allocations.reserve(node->allocations_.size());
228 288 : if (node->script_id_ != v8::UnboundScript::kNoScriptId &&
229 106 : scripts.find(node->script_id_) != scripts.end()) {
230 : // Cannot use std::map<T>::at because it is not available on android.
231 : auto non_const_scripts =
232 : const_cast<std::map<int, Handle<Script>>&>(scripts);
233 106 : Handle<Script> script = non_const_scripts[node->script_id_];
234 106 : if (!script.is_null()) {
235 106 : if (script->name()->IsName()) {
236 : Name* name = Name::cast(script->name());
237 : script_name = ToApiHandle<v8::String>(
238 20 : isolate_->factory()->InternalizeUtf8String(names_->GetName(name)));
239 : }
240 106 : line = 1 + Script::GetLineNumber(script, node->script_position_);
241 106 : column = 1 + Script::GetColumnNumber(script, node->script_position_);
242 : }
243 : }
244 1019 : for (auto alloc : node->allocations_) {
245 1310 : allocations.push_back(ScaleSample(alloc.first, alloc.second));
246 : }
247 :
248 182 : profile->nodes().push_back(v8::AllocationProfile::Node(
249 : {ToApiHandle<v8::String>(
250 182 : isolate_->factory()->InternalizeUtf8String(node->name_)),
251 : script_name, node->script_id_, node->script_position_, line, column,
252 546 : std::vector<v8::AllocationProfile::Node*>(), allocations}));
253 182 : v8::AllocationProfile::Node* current = &profile->nodes().back();
254 : // The children map may have nodes inserted into it during translation
255 : // because the translation may allocate strings on the JS heap that have
256 : // the potential to be sampled. That's ok since map iterators are not
257 : // invalidated upon std::map insertion.
258 523 : for (auto it : node->children_) {
259 : current->children.push_back(
260 318 : TranslateAllocationNode(profile, it.second, scripts));
261 : }
262 182 : node->pinned_ = false;
263 182 : return current;
264 : }
265 :
266 23 : v8::AllocationProfile* SamplingHeapProfiler::GetAllocationProfile() {
267 23 : if (flags_ & v8::HeapProfiler::kSamplingForceGC) {
268 : isolate_->heap()->CollectAllGarbage(
269 0 : Heap::kNoGCFlags, GarbageCollectionReason::kSamplingProfiler);
270 : }
271 : // To resolve positions to line/column numbers, we will need to look up
272 : // scripts. Build a map to allow fast mapping from script id to script.
273 : std::map<int, Handle<Script>> scripts;
274 : {
275 23 : Script::Iterator iterator(isolate_);
276 343 : while (Script* script = iterator.Next()) {
277 640 : scripts[script->id()] = handle(script);
278 320 : }
279 : }
280 23 : auto profile = new v8::internal::AllocationProfile();
281 23 : TranslateAllocationNode(profile, &profile_root_, scripts);
282 23 : return profile;
283 : }
284 :
285 :
286 : } // namespace internal
287 : } // namespace v8
|