sampling-heap-profiler.cc 11 KB
Newer Older
1 2 3 4 5 6 7 8
// Copyright 2015 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "src/profiler/sampling-heap-profiler.h"

#include <stdint.h>
#include <memory>
9

10
#include "src/api/api-inl.h"
11
#include "src/base/ieee754.h"
12
#include "src/base/utils/random-number-generator.h"
13 14
#include "src/execution/frames-inl.h"
#include "src/execution/isolate.h"
15 16 17 18 19 20
#include "src/heap/heap.h"
#include "src/profiler/strings-storage.h"

namespace v8 {
namespace internal {

21 22 23 24 25 26
// We sample with a Poisson process, with constant average sampling interval.
// This follows the exponential probability distribution with parameter
// λ = 1/rate where rate is the average number of bytes between samples.
//
// Let u be a uniformly distributed random number between 0 and 1, then
// next_sample = (- ln u) / λ
27 28
intptr_t SamplingHeapProfiler::Observer::GetNextSampleInterval(uint64_t rate) {
  if (FLAG_sampling_heap_profiler_suppress_randomness)
29
    return static_cast<intptr_t>(rate);
30
  double u = random_->NextDouble();
31
  double next = (-base::ieee754::log(u)) * rate;
32 33
  return next < kTaggedSize
             ? kTaggedSize
34 35 36
             : (next > INT_MAX ? INT_MAX : static_cast<intptr_t>(next));
}

37 38 39 40 41 42 43 44
// Samples were collected according to a poisson process. Since we have not
// recorded all allocations, we must approximate the shape of the underlying
// space of allocations based on the samples we have collected. Given that
// we sample at rate R, the probability that an allocation of size S will be
// sampled is 1-exp(-S/R). This function uses the above probability to
// approximate the true number of allocations with size *size* given that
// *count* samples were observed.
v8::AllocationProfile::Allocation SamplingHeapProfiler::ScaleSample(
45
    size_t size, unsigned int count) const {
46 47 48 49 50
  double scale = 1.0 / (1.0 - std::exp(-static_cast<double>(size) / rate_));
  // Round count instead of truncating.
  return {size, static_cast<unsigned int>(count * scale + 0.5)};
}

51 52 53
SamplingHeapProfiler::SamplingHeapProfiler(
    Heap* heap, StringsStorage* names, uint64_t rate, int stack_depth,
    v8::HeapProfiler::SamplingFlags flags)
54
    : isolate_(Isolate::FromHeap(heap)),
55
      heap_(heap),
56 57
      allocation_observer_(heap_, static_cast<intptr_t>(rate), rate, this,
                           isolate_->random_number_generator()),
58
      names_(names),
59 60
      profile_root_(nullptr, "(root)", v8::UnboundScript::kNoScriptId, 0,
                    next_node_id()),
61
      stack_depth_(stack_depth),
62 63
      rate_(rate),
      flags_(flags) {
64
  CHECK_GT(rate_, 0u);
65 66
  heap_->AddAllocationObserversToAllSpaces(&allocation_observer_,
                                           &allocation_observer_);
67 68 69
}

SamplingHeapProfiler::~SamplingHeapProfiler() {
70 71
  heap_->RemoveAllocationObserversFromAllSpaces(&allocation_observer_,
                                                &allocation_observer_);
72 73 74
}

void SamplingHeapProfiler::SampleObject(Address soon_object, size_t size) {
75
  DisallowGarbageCollection no_gc;
76

77
  // Check if the area is iterable by confirming that it starts with a map.
78
  DCHECK(HeapObject::FromAddress(soon_object).map(isolate_).IsMap(isolate_));
79

80
  HandleScope scope(isolate_);
81
  HeapObject heap_object = HeapObject::FromAddress(soon_object);
82 83 84 85
  Handle<Object> obj(heap_object, isolate_);

  Local<v8::Value> loc = v8::Utils::ToLocal(obj);

86 87
  AllocationNode* node = AddStack();
  node->allocations_[size]++;
88
  auto sample =
89
      std::make_unique<Sample>(size, node, loc, this, next_sample_id());
90 91 92
  sample->global.SetWeak(sample.get(), OnWeakCallback,
                         WeakCallbackType::kParameter);
  samples_.emplace(sample.get(), std::move(sample));
93 94
}

95 96 97 98
void SamplingHeapProfiler::OnWeakCallback(
    const WeakCallbackInfo<Sample>& data) {
  Sample* sample = data.GetParameter();
  AllocationNode* node = sample->owner;
99
  DCHECK_GT(node->allocations_[sample->size], 0);
100
  node->allocations_[sample->size]--;
101 102
  if (node->allocations_[sample->size] == 0) {
    node->allocations_.erase(sample->size);
103 104 105
    while (node->allocations_.empty() && node->children_.empty() &&
           node->parent_ && !node->parent_->pinned_) {
      AllocationNode* parent = node->parent_;
106 107 108
      AllocationNode::FunctionId id = AllocationNode::function_id(
          node->script_id_, node->script_position_, node->name_);
      parent->children_.erase(id);
109 110
      node = parent;
    }
111
  }
112
  sample->profiler->samples_.erase(sample);
113
  // sample is deleted because its unique ptr was erased from samples_.
114 115
}

116 117 118 119 120 121 122 123 124
SamplingHeapProfiler::AllocationNode* SamplingHeapProfiler::FindOrAddChildNode(
    AllocationNode* parent, const char* name, int script_id,
    int start_position) {
  AllocationNode::FunctionId id =
      AllocationNode::function_id(script_id, start_position, name);
  AllocationNode* child = parent->FindChildNode(id);
  if (child) {
    DCHECK_EQ(strcmp(child->name_, name), 0);
    return child;
125
  }
126
  auto new_child = std::make_unique<AllocationNode>(
127 128
      parent, name, script_id, start_position, next_node_id());
  return parent->AddChildNode(id, std::move(new_child));
129 130
}

131 132
SamplingHeapProfiler::AllocationNode* SamplingHeapProfiler::AddStack() {
  AllocationNode* node = &profile_root_;
133

134
  std::vector<SharedFunctionInfo> stack;
135
  JavaScriptFrameIterator frame_it(isolate_);
136
  int frames_captured = 0;
137
  bool found_arguments_marker_frames = false;
138 139
  while (!frame_it.done() && frames_captured < stack_depth_) {
    JavaScriptFrame* frame = frame_it.frame();
140 141 142 143 144
    // If we are materializing objects during deoptimization, inlined
    // closures may not yet be materialized, and this includes the
    // closure on the stack. Skip over any such frames (they'll be
    // in the top frames of the stack). The allocations made in this
    // sensitive moment belong to the formerly optimized frame anyway.
145 146
    if (frame->unchecked_function().IsJSFunction()) {
      SharedFunctionInfo shared = frame->function().shared();
147 148
      stack.push_back(shared);
      frames_captured++;
149 150
    } else {
      found_arguments_marker_frames = true;
151
    }
152
    frame_it.Advance();
153 154 155 156
  }

  if (frames_captured == 0) {
    const char* name = nullptr;
157
    switch (isolate_->current_vm_state()) {
158 159 160
      case GC:
        name = "(GC)";
        break;
161 162 163
      case PARSER:
        name = "(PARSER)";
        break;
164 165 166
      case COMPILER:
        name = "(COMPILER)";
        break;
167 168 169
      case BYTECODE_COMPILER:
        name = "(BYTECODE_COMPILER)";
        break;
170 171 172 173 174 175 176 177 178
      case OTHER:
        name = "(V8 API)";
        break;
      case EXTERNAL:
        name = "(EXTERNAL)";
        break;
      case IDLE:
        name = "(IDLE)";
        break;
179 180 181
      // Treat atomics wait as a normal JS event; we don't care about the
      // difference for allocations.
      case ATOMICS_WAIT:
182 183 184 185
      case JS:
        name = "(JS)";
        break;
    }
186
    return FindOrAddChildNode(node, name, v8::UnboundScript::kNoScriptId, 0);
187
  }
188

189 190 191
  // We need to process the stack in reverse order as the top of the stack is
  // the first element in the list.
  for (auto it = stack.rbegin(); it != stack.rend(); ++it) {
192
    SharedFunctionInfo shared = *it;
193
    const char* name = this->names()->GetCopy(shared.DebugNameCStr().get());
194
    int script_id = v8::UnboundScript::kNoScriptId;
195 196 197
    if (shared.script().IsScript()) {
      Script script = Script::cast(shared.script());
      script_id = script.id();
198
    }
199
    node = FindOrAddChildNode(node, name, script_id, shared.StartPosition());
200
  }
201 202 203

  if (found_arguments_marker_frames) {
    node =
204
        FindOrAddChildNode(node, "(deopt)", v8::UnboundScript::kNoScriptId, 0);
205 206
  }

207 208
  return node;
}
209

210 211
v8::AllocationProfile::Node* SamplingHeapProfiler::TranslateAllocationNode(
    AllocationProfile* profile, SamplingHeapProfiler::AllocationNode* node,
212
    const std::map<int, Handle<Script>>& scripts) {
213 214 215
  // By pinning the node we make sure its children won't get disposed if
  // a GC kicks in during the tree retrieval.
  node->pinned_ = true;
216 217
  Local<v8::String> script_name =
      ToApiHandle<v8::String>(isolate_->factory()->InternalizeUtf8String(""));
218 219
  int line = v8::AllocationProfile::kNoLineNumberInfo;
  int column = v8::AllocationProfile::kNoColumnNumberInfo;
220
  std::vector<v8::AllocationProfile::Allocation> allocations;
221
  allocations.reserve(node->allocations_.size());
222 223 224 225
  if (node->script_id_ != v8::UnboundScript::kNoScriptId) {
    auto script_iterator = scripts.find(node->script_id_);
    if (script_iterator != scripts.end()) {
      Handle<Script> script = script_iterator->second;
226
      if (script->name().IsName()) {
227
        Name name = Name::cast(script->name());
228 229 230
        script_name = ToApiHandle<v8::String>(
            isolate_->factory()->InternalizeUtf8String(names_->GetName(name)));
      }
231 232
      line = 1 + Script::GetLineNumber(script, node->script_position_);
      column = 1 + Script::GetColumnNumber(script, node->script_position_);
233
    }
234 235 236
  }
  for (auto alloc : node->allocations_) {
    allocations.push_back(ScaleSample(alloc.first, alloc.second));
237 238
  }

239
  profile->nodes_.push_back(v8::AllocationProfile::Node{
240 241 242
      ToApiHandle<v8::String>(
          isolate_->factory()->InternalizeUtf8String(node->name_)),
      script_name, node->script_id_, node->script_position_, line, column,
243 244
      node->id_, std::vector<v8::AllocationProfile::Node*>(), allocations});
  v8::AllocationProfile::Node* current = &profile->nodes_.back();
245
  // The |children_| map may have nodes inserted into it during translation
246
  // because the translation may allocate strings on the JS heap that have
247 248
  // the potential to be sampled. That's ok since map iterators are not
  // invalidated upon std::map insertion.
249
  for (const auto& it : node->children_) {
250
    current->children.push_back(
251
        TranslateAllocationNode(profile, it.second.get(), scripts));
252
  }
253
  node->pinned_ = false;
254
  return current;
255 256 257
}

v8::AllocationProfile* SamplingHeapProfiler::GetAllocationProfile() {
258
  if (flags_ & v8::HeapProfiler::kSamplingForceGC) {
259 260
    isolate_->heap()->CollectAllGarbage(
        Heap::kNoGCFlags, GarbageCollectionReason::kSamplingProfiler);
261
  }
262 263
  // To resolve positions to line/column numbers, we will need to look up
  // scripts. Build a map to allow fast mapping from script id to script.
264
  std::map<int, Handle<Script>> scripts;
265 266
  {
    Script::Iterator iterator(isolate_);
267 268
    for (Script script = iterator.Next(); !script.is_null();
         script = iterator.Next()) {
269
      scripts[script.id()] = handle(script, isolate_);
270 271 272
    }
  }
  auto profile = new v8::internal::AllocationProfile();
273
  TranslateAllocationNode(profile, &profile_root_, scripts);
274
  profile->samples_ = BuildSamples();
275

276 277 278
  return profile;
}

279 280 281 282 283 284 285 286 287 288 289 290 291
const std::vector<v8::AllocationProfile::Sample>
SamplingHeapProfiler::BuildSamples() const {
  std::vector<v8::AllocationProfile::Sample> samples;
  samples.reserve(samples_.size());
  for (const auto& it : samples_) {
    const Sample* sample = it.second.get();
    samples.emplace_back(v8::AllocationProfile::Sample{
        sample->owner->id_, sample->size, ScaleSample(sample->size, 1).count,
        sample->sample_id});
  }
  return samples;
}

292 293
}  // namespace internal
}  // namespace v8