blob: db9214d14d7f90f7d93dfad5e40c4febba893f10 [file] [log] [blame]
Ben Murdoch097c5b22016-05-18 11:27:45 +01001// 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/utils/random-number-generator.h"
11#include "src/frames-inl.h"
12#include "src/heap/heap.h"
13#include "src/isolate.h"
14#include "src/profiler/strings-storage.h"
15
16namespace v8 {
17namespace internal {
18
19// We sample with a Poisson process, with constant average sampling interval.
20// This follows the exponential probability distribution with parameter
21// λ = 1/rate where rate is the average number of bytes between samples.
22//
23// Let u be a uniformly distributed random number between 0 and 1, then
24// next_sample = (- ln u) / λ
25intptr_t SamplingAllocationObserver::GetNextSampleInterval(uint64_t rate) {
26 if (FLAG_sampling_heap_profiler_suppress_randomness) {
27 return static_cast<intptr_t>(rate);
28 }
29 double u = random_->NextDouble();
30 double next = (-std::log(u)) * rate;
31 return next < kPointerSize
32 ? kPointerSize
33 : (next > INT_MAX ? INT_MAX : static_cast<intptr_t>(next));
34}
35
36// Samples were collected according to a poisson process. Since we have not
37// recorded all allocations, we must approximate the shape of the underlying
38// space of allocations based on the samples we have collected. Given that
39// we sample at rate R, the probability that an allocation of size S will be
40// sampled is 1-exp(-S/R). This function uses the above probability to
41// approximate the true number of allocations with size *size* given that
42// *count* samples were observed.
43v8::AllocationProfile::Allocation SamplingHeapProfiler::ScaleSample(
44 size_t size, unsigned int count) {
45 double scale = 1.0 / (1.0 - std::exp(-static_cast<double>(size) / rate_));
46 // Round count instead of truncating.
47 return {size, static_cast<unsigned int>(count * scale + 0.5)};
48}
49
Ben Murdochc5610432016-08-08 18:44:38 +010050SamplingHeapProfiler::SamplingHeapProfiler(
51 Heap* heap, StringsStorage* names, uint64_t rate, int stack_depth,
52 v8::HeapProfiler::SamplingFlags flags)
Ben Murdoch097c5b22016-05-18 11:27:45 +010053 : isolate_(heap->isolate()),
54 heap_(heap),
55 new_space_observer_(new SamplingAllocationObserver(
56 heap_, static_cast<intptr_t>(rate), rate, this,
57 heap->isolate()->random_number_generator())),
58 other_spaces_observer_(new SamplingAllocationObserver(
59 heap_, static_cast<intptr_t>(rate), rate, this,
60 heap->isolate()->random_number_generator())),
61 names_(names),
Ben Murdochc5610432016-08-08 18:44:38 +010062 profile_root_(nullptr, "(root)", v8::UnboundScript::kNoScriptId, 0),
Ben Murdoch097c5b22016-05-18 11:27:45 +010063 samples_(),
64 stack_depth_(stack_depth),
Ben Murdochc5610432016-08-08 18:44:38 +010065 rate_(rate),
66 flags_(flags) {
Ben Murdoch097c5b22016-05-18 11:27:45 +010067 CHECK_GT(rate_, 0);
68 heap->new_space()->AddAllocationObserver(new_space_observer_.get());
69 AllSpaces spaces(heap);
Ben Murdochc5610432016-08-08 18:44:38 +010070 for (Space* space = spaces.next(); space != nullptr; space = spaces.next()) {
Ben Murdoch097c5b22016-05-18 11:27:45 +010071 if (space != heap->new_space()) {
72 space->AddAllocationObserver(other_spaces_observer_.get());
73 }
74 }
75}
76
77
78SamplingHeapProfiler::~SamplingHeapProfiler() {
79 heap_->new_space()->RemoveAllocationObserver(new_space_observer_.get());
80 AllSpaces spaces(heap_);
Ben Murdochc5610432016-08-08 18:44:38 +010081 for (Space* space = spaces.next(); space != nullptr; space = spaces.next()) {
Ben Murdoch097c5b22016-05-18 11:27:45 +010082 if (space != heap_->new_space()) {
83 space->RemoveAllocationObserver(other_spaces_observer_.get());
84 }
85 }
86
87 for (auto sample : samples_) {
88 delete sample;
89 }
90 std::set<Sample*> empty;
91 samples_.swap(empty);
92}
93
94
95void SamplingHeapProfiler::SampleObject(Address soon_object, size_t size) {
96 DisallowHeapAllocation no_allocation;
97
98 HandleScope scope(isolate_);
99 HeapObject* heap_object = HeapObject::FromAddress(soon_object);
100 Handle<Object> obj(heap_object, isolate_);
101
102 // Mark the new block as FreeSpace to make sure the heap is iterable while we
103 // are taking the sample.
Ben Murdochda12d292016-06-02 14:46:10 +0100104 heap()->CreateFillerObjectAt(soon_object, static_cast<int>(size),
105 ClearRecordedSlots::kNo);
Ben Murdoch097c5b22016-05-18 11:27:45 +0100106
107 Local<v8::Value> loc = v8::Utils::ToLocal(obj);
108
109 AllocationNode* node = AddStack();
110 node->allocations_[size]++;
111 Sample* sample = new Sample(size, node, loc, this);
112 samples_.insert(sample);
113 sample->global.SetWeak(sample, OnWeakCallback, WeakCallbackType::kParameter);
Ben Murdochc5610432016-08-08 18:44:38 +0100114 sample->global.MarkIndependent();
Ben Murdoch097c5b22016-05-18 11:27:45 +0100115}
116
117void SamplingHeapProfiler::OnWeakCallback(
118 const WeakCallbackInfo<Sample>& data) {
119 Sample* sample = data.GetParameter();
120 AllocationNode* node = sample->owner;
121 DCHECK(node->allocations_[sample->size] > 0);
122 node->allocations_[sample->size]--;
Ben Murdochc5610432016-08-08 18:44:38 +0100123 if (node->allocations_[sample->size] == 0) {
124 node->allocations_.erase(sample->size);
125 while (node->allocations_.empty() && node->children_.empty() &&
126 node->parent_ && !node->parent_->pinned_) {
127 AllocationNode* parent = node->parent_;
128 AllocationNode::FunctionId id = AllocationNode::function_id(
129 node->script_id_, node->script_position_, node->name_);
130 parent->children_.erase(id);
131 delete node;
132 node = parent;
133 }
134 }
Ben Murdoch097c5b22016-05-18 11:27:45 +0100135 sample->profiler->samples_.erase(sample);
136 delete sample;
137}
138
Ben Murdochc5610432016-08-08 18:44:38 +0100139SamplingHeapProfiler::AllocationNode*
140SamplingHeapProfiler::AllocationNode::FindOrAddChildNode(const char* name,
141 int script_id,
142 int start_position) {
143 FunctionId id = function_id(script_id, start_position, name);
144 auto it = children_.find(id);
145 if (it != children_.end()) {
146 DCHECK(strcmp(it->second->name_, name) == 0);
147 return it->second;
Ben Murdoch097c5b22016-05-18 11:27:45 +0100148 }
Ben Murdochc5610432016-08-08 18:44:38 +0100149 auto child = new AllocationNode(this, name, script_id, start_position);
150 children_.insert(std::make_pair(id, child));
Ben Murdoch097c5b22016-05-18 11:27:45 +0100151 return child;
152}
153
154SamplingHeapProfiler::AllocationNode* SamplingHeapProfiler::AddStack() {
155 AllocationNode* node = &profile_root_;
156
157 std::vector<SharedFunctionInfo*> stack;
Ben Murdochc5610432016-08-08 18:44:38 +0100158 JavaScriptFrameIterator it(isolate_);
Ben Murdoch097c5b22016-05-18 11:27:45 +0100159 int frames_captured = 0;
160 while (!it.done() && frames_captured < stack_depth_) {
161 JavaScriptFrame* frame = it.frame();
162 SharedFunctionInfo* shared = frame->function()->shared();
163 stack.push_back(shared);
164
165 frames_captured++;
166 it.Advance();
167 }
168
169 if (frames_captured == 0) {
170 const char* name = nullptr;
171 switch (isolate_->current_vm_state()) {
172 case GC:
173 name = "(GC)";
174 break;
175 case COMPILER:
176 name = "(COMPILER)";
177 break;
178 case OTHER:
179 name = "(V8 API)";
180 break;
181 case EXTERNAL:
182 name = "(EXTERNAL)";
183 break;
184 case IDLE:
185 name = "(IDLE)";
186 break;
187 case JS:
188 name = "(JS)";
189 break;
190 }
Ben Murdochc5610432016-08-08 18:44:38 +0100191 return node->FindOrAddChildNode(name, v8::UnboundScript::kNoScriptId, 0);
Ben Murdoch097c5b22016-05-18 11:27:45 +0100192 }
193
194 // We need to process the stack in reverse order as the top of the stack is
195 // the first element in the list.
196 for (auto it = stack.rbegin(); it != stack.rend(); ++it) {
197 SharedFunctionInfo* shared = *it;
198 const char* name = this->names()->GetFunctionName(shared->DebugName());
199 int script_id = v8::UnboundScript::kNoScriptId;
200 if (shared->script()->IsScript()) {
201 Script* script = Script::cast(shared->script());
202 script_id = script->id();
203 }
Ben Murdochc5610432016-08-08 18:44:38 +0100204 node = node->FindOrAddChildNode(name, script_id, shared->start_position());
Ben Murdoch097c5b22016-05-18 11:27:45 +0100205 }
206 return node;
207}
208
209v8::AllocationProfile::Node* SamplingHeapProfiler::TranslateAllocationNode(
210 AllocationProfile* profile, SamplingHeapProfiler::AllocationNode* node,
Ben Murdochc5610432016-08-08 18:44:38 +0100211 const std::map<int, Handle<Script>>& scripts) {
212 // By pinning the node we make sure its children won't get disposed if
213 // a GC kicks in during the tree retrieval.
214 node->pinned_ = true;
Ben Murdoch097c5b22016-05-18 11:27:45 +0100215 Local<v8::String> script_name =
216 ToApiHandle<v8::String>(isolate_->factory()->InternalizeUtf8String(""));
217 int line = v8::AllocationProfile::kNoLineNumberInfo;
218 int column = v8::AllocationProfile::kNoColumnNumberInfo;
219 std::vector<v8::AllocationProfile::Allocation> allocations;
220 allocations.reserve(node->allocations_.size());
Ben Murdochda12d292016-06-02 14:46:10 +0100221 if (node->script_id_ != v8::UnboundScript::kNoScriptId &&
222 scripts.find(node->script_id_) != scripts.end()) {
Ben Murdoch097c5b22016-05-18 11:27:45 +0100223 // Cannot use std::map<T>::at because it is not available on android.
Ben Murdochc5610432016-08-08 18:44:38 +0100224 auto non_const_scripts =
225 const_cast<std::map<int, Handle<Script>>&>(scripts);
226 Handle<Script> script = non_const_scripts[node->script_id_];
227 if (!script.is_null()) {
Ben Murdochda12d292016-06-02 14:46:10 +0100228 if (script->name()->IsName()) {
229 Name* name = Name::cast(script->name());
230 script_name = ToApiHandle<v8::String>(
231 isolate_->factory()->InternalizeUtf8String(names_->GetName(name)));
232 }
Ben Murdochc5610432016-08-08 18:44:38 +0100233 line = 1 + Script::GetLineNumber(script, node->script_position_);
234 column = 1 + Script::GetColumnNumber(script, node->script_position_);
Ben Murdoch097c5b22016-05-18 11:27:45 +0100235 }
Ben Murdochc5610432016-08-08 18:44:38 +0100236 }
237 for (auto alloc : node->allocations_) {
238 allocations.push_back(ScaleSample(alloc.first, alloc.second));
Ben Murdoch097c5b22016-05-18 11:27:45 +0100239 }
240
241 profile->nodes().push_back(v8::AllocationProfile::Node(
242 {ToApiHandle<v8::String>(
243 isolate_->factory()->InternalizeUtf8String(node->name_)),
244 script_name, node->script_id_, node->script_position_, line, column,
245 std::vector<v8::AllocationProfile::Node*>(), allocations}));
246 v8::AllocationProfile::Node* current = &profile->nodes().back();
Ben Murdochc5610432016-08-08 18:44:38 +0100247 // The children map may have nodes inserted into it during translation
Ben Murdoch097c5b22016-05-18 11:27:45 +0100248 // because the translation may allocate strings on the JS heap that have
Ben Murdochc5610432016-08-08 18:44:38 +0100249 // the potential to be sampled. That's ok since map iterators are not
250 // invalidated upon std::map insertion.
251 for (auto it : node->children_) {
Ben Murdoch097c5b22016-05-18 11:27:45 +0100252 current->children.push_back(
Ben Murdochc5610432016-08-08 18:44:38 +0100253 TranslateAllocationNode(profile, it.second, scripts));
Ben Murdoch097c5b22016-05-18 11:27:45 +0100254 }
Ben Murdochc5610432016-08-08 18:44:38 +0100255 node->pinned_ = false;
Ben Murdoch097c5b22016-05-18 11:27:45 +0100256 return current;
257}
258
259v8::AllocationProfile* SamplingHeapProfiler::GetAllocationProfile() {
Ben Murdochc5610432016-08-08 18:44:38 +0100260 if (flags_ & v8::HeapProfiler::kSamplingForceGC) {
261 isolate_->heap()->CollectAllGarbage(Heap::kNoGCFlags,
262 "SamplingHeapProfiler");
263 }
Ben Murdoch097c5b22016-05-18 11:27:45 +0100264 // To resolve positions to line/column numbers, we will need to look up
265 // scripts. Build a map to allow fast mapping from script id to script.
Ben Murdochc5610432016-08-08 18:44:38 +0100266 std::map<int, Handle<Script>> scripts;
Ben Murdoch097c5b22016-05-18 11:27:45 +0100267 {
268 Script::Iterator iterator(isolate_);
Ben Murdochc5610432016-08-08 18:44:38 +0100269 while (Script* script = iterator.Next()) {
270 scripts[script->id()] = handle(script);
Ben Murdoch097c5b22016-05-18 11:27:45 +0100271 }
272 }
Ben Murdoch097c5b22016-05-18 11:27:45 +0100273 auto profile = new v8::internal::AllocationProfile();
Ben Murdoch097c5b22016-05-18 11:27:45 +0100274 TranslateAllocationNode(profile, &profile_root_, scripts);
Ben Murdoch097c5b22016-05-18 11:27:45 +0100275 return profile;
276}
277
278
279} // namespace internal
280} // namespace v8