LCOV - code coverage report
Current view: top level - src/profiler - sampling-heap-profiler.cc (source / functions) Hit Total Coverage
Test: app.info Lines: 112 116 96.6 %
Date: 2019-01-20 Functions: 13 13 100.0 %

          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             : 
      10             : #include "src/api-inl.h"
      11             : #include "src/base/ieee754.h"
      12             : #include "src/base/template-utils.h"
      13             : #include "src/base/utils/random-number-generator.h"
      14             : #include "src/frames-inl.h"
      15             : #include "src/heap/heap.h"
      16             : #include "src/isolate.h"
      17             : #include "src/profiler/strings-storage.h"
      18             : 
      19             : namespace v8 {
      20             : namespace internal {
      21             : 
      22             : // We sample with a Poisson process, with constant average sampling interval.
      23             : // This follows the exponential probability distribution with parameter
      24             : // λ = 1/rate where rate is the average number of bytes between samples.
      25             : //
      26             : // Let u be a uniformly distributed random number between 0 and 1, then
      27             : // next_sample = (- ln u) / λ
      28      104788 : intptr_t SamplingAllocationObserver::GetNextSampleInterval(uint64_t rate) {
      29      104788 :   if (FLAG_sampling_heap_profiler_suppress_randomness) {
      30       54652 :     return static_cast<intptr_t>(rate);
      31             :   }
      32       50136 :   double u = random_->NextDouble();
      33       50136 :   double next = (-base::ieee754::log(u)) * rate;
      34             :   return next < kTaggedSize
      35             :              ? kTaggedSize
      36       50136 :              : (next > INT_MAX ? INT_MAX : static_cast<intptr_t>(next));
      37             : }
      38             : 
      39             : // Samples were collected according to a poisson process. Since we have not
      40             : // recorded all allocations, we must approximate the shape of the underlying
      41             : // space of allocations based on the samples we have collected. Given that
      42             : // we sample at rate R, the probability that an allocation of size S will be
      43             : // sampled is 1-exp(-S/R). This function uses the above probability to
      44             : // approximate the true number of allocations with size *size* given that
      45             : // *count* samples were observed.
      46       44687 : v8::AllocationProfile::Allocation SamplingHeapProfiler::ScaleSample(
      47             :     size_t size, unsigned int count) const {
      48       44687 :   double scale = 1.0 / (1.0 - std::exp(-static_cast<double>(size) / rate_));
      49             :   // Round count instead of truncating.
      50       44687 :   return {size, static_cast<unsigned int>(count * scale + 0.5)};
      51             : }
      52             : 
      53          59 : SamplingHeapProfiler::SamplingHeapProfiler(
      54             :     Heap* heap, StringsStorage* names, uint64_t rate, int stack_depth,
      55             :     v8::HeapProfiler::SamplingFlags flags)
      56             :     : isolate_(heap->isolate()),
      57             :       heap_(heap),
      58             :       new_space_observer_(new SamplingAllocationObserver(
      59             :           heap_, static_cast<intptr_t>(rate), rate, this,
      60          59 :           heap->isolate()->random_number_generator())),
      61             :       other_spaces_observer_(new SamplingAllocationObserver(
      62             :           heap_, static_cast<intptr_t>(rate), rate, this,
      63          59 :           heap->isolate()->random_number_generator())),
      64             :       names_(names),
      65             :       profile_root_(nullptr, "(root)", v8::UnboundScript::kNoScriptId, 0,
      66             :                     next_node_id()),
      67             :       stack_depth_(stack_depth),
      68             :       rate_(rate),
      69         236 :       flags_(flags) {
      70          59 :   CHECK_GT(rate_, 0u);
      71             :   heap_->AddAllocationObserversToAllSpaces(other_spaces_observer_.get(),
      72          59 :                                            new_space_observer_.get());
      73          59 : }
      74             : 
      75         118 : SamplingHeapProfiler::~SamplingHeapProfiler() {
      76             :   heap_->RemoveAllocationObserversFromAllSpaces(other_spaces_observer_.get(),
      77          59 :                                                 new_space_observer_.get());
      78          59 : }
      79             : 
      80      104783 : void SamplingHeapProfiler::SampleObject(Address soon_object, size_t size) {
      81             :   DisallowHeapAllocation no_allocation;
      82             : 
      83      104783 :   HandleScope scope(isolate_);
      84             :   HeapObject heap_object = HeapObject::FromAddress(soon_object);
      85      104783 :   Handle<Object> obj(heap_object, isolate_);
      86             : 
      87             :   // Mark the new block as FreeSpace to make sure the heap is iterable while we
      88             :   // are taking the sample.
      89             :   heap_->CreateFillerObjectAt(soon_object, static_cast<int>(size),
      90      104783 :                               ClearRecordedSlots::kNo);
      91             : 
      92      104783 :   Local<v8::Value> loc = v8::Utils::ToLocal(obj);
      93             : 
      94      104783 :   AllocationNode* node = AddStack();
      95      104783 :   node->allocations_[size]++;
      96             :   auto sample =
      97      104783 :       base::make_unique<Sample>(size, node, loc, this, next_sample_id());
      98             :   sample->global.SetWeak(sample.get(), OnWeakCallback,
      99             :                          WeakCallbackType::kParameter);
     100             : #if __clang__
     101             : #pragma clang diagnostic push
     102             : #pragma clang diagnostic ignored "-Wdeprecated"
     103             : #endif
     104             :   // MarkIndependent is marked deprecated but we still rely on it here
     105             :   // temporarily.
     106             :   sample->global.MarkIndependent();
     107             : #if __clang__
     108             : #pragma clang diagnostic pop
     109             : #endif
     110      209566 :   samples_.emplace(sample.get(), std::move(sample));
     111      104783 : }
     112             : 
     113       17880 : void SamplingHeapProfiler::OnWeakCallback(
     114       17880 :     const WeakCallbackInfo<Sample>& data) {
     115       17880 :   Sample* sample = data.GetParameter();
     116       17880 :   AllocationNode* node = sample->owner;
     117             :   DCHECK_GT(node->allocations_[sample->size], 0);
     118       17880 :   node->allocations_[sample->size]--;
     119       17880 :   if (node->allocations_[sample->size] == 0) {
     120          68 :     node->allocations_.erase(sample->size);
     121          77 :     while (node->allocations_.empty() && node->children_.empty() &&
     122          76 :            node->parent_ && !node->parent_->pinned_) {
     123           4 :       AllocationNode* parent = node->parent_;
     124             :       AllocationNode::FunctionId id = AllocationNode::function_id(
     125           8 :           node->script_id_, node->script_position_, node->name_);
     126             :       parent->children_.erase(id);
     127             :       node = parent;
     128             :     }
     129             :   }
     130       17880 :   sample->profiler->samples_.erase(sample);
     131             :   // sample is deleted because its unique ptr was erased from samples_.
     132       17880 : }
     133             : 
     134      156239 : SamplingHeapProfiler::AllocationNode* SamplingHeapProfiler::FindOrAddChildNode(
     135             :     AllocationNode* parent, const char* name, int script_id,
     136             :     int start_position) {
     137             :   AllocationNode::FunctionId id =
     138      156239 :       AllocationNode::function_id(script_id, start_position, name);
     139      156239 :   AllocationNode* child = parent->FindChildNode(id);
     140      156239 :   if (child) {
     141             :     DCHECK_EQ(strcmp(child->name_, name), 0);
     142             :     return child;
     143             :   }
     144             :   auto new_child = base::make_unique<AllocationNode>(
     145         283 :       parent, name, script_id, start_position, next_node_id());
     146         566 :   return parent->AddChildNode(id, std::move(new_child));
     147             : }
     148             : 
     149      226884 : SamplingHeapProfiler::AllocationNode* SamplingHeapProfiler::AddStack() {
     150      104783 :   AllocationNode* node = &profile_root_;
     151             : 
     152             :   std::vector<SharedFunctionInfo> stack;
     153      138918 :   JavaScriptFrameIterator it(isolate_);
     154             :   int frames_captured = 0;
     155             :   bool found_arguments_marker_frames = false;
     156      331670 :   while (!it.done() && frames_captured < stack_depth_) {
     157             :     JavaScriptFrame* frame = it.frame();
     158             :     // If we are materializing objects during deoptimization, inlined
     159             :     // closures may not yet be materialized, and this includes the
     160             :     // closure on the stack. Skip over any such frames (they'll be
     161             :     // in the top frames of the stack). The allocations made in this
     162             :     // sensitive moment belong to the formerly optimized frame anyway.
     163      244208 :     if (frame->unchecked_function()->IsJSFunction()) {
     164      122101 :       SharedFunctionInfo shared = frame->function()->shared();
     165      122101 :       stack.push_back(shared);
     166      122101 :       frames_captured++;
     167             :     } else {
     168             :       found_arguments_marker_frames = true;
     169             :     }
     170      122104 :     it.Advance();
     171             :   }
     172             : 
     173      104783 :   if (frames_captured == 0) {
     174             :     const char* name = nullptr;
     175       68270 :     switch (isolate_->current_vm_state()) {
     176             :       case GC:
     177             :         name = "(GC)";
     178           0 :         break;
     179             :       case PARSER:
     180             :         name = "(PARSER)";
     181           0 :         break;
     182             :       case COMPILER:
     183             :         name = "(COMPILER)";
     184        5037 :         break;
     185             :       case BYTECODE_COMPILER:
     186             :         name = "(BYTECODE_COMPILER)";
     187         277 :         break;
     188             :       case OTHER:
     189             :         name = "(V8 API)";
     190       15020 :         break;
     191             :       case EXTERNAL:
     192             :         name = "(EXTERNAL)";
     193       13801 :         break;
     194             :       case IDLE:
     195             :         name = "(IDLE)";
     196           0 :         break;
     197             :       case JS:
     198             :         name = "(JS)";
     199           0 :         break;
     200             :     }
     201       34135 :     return FindOrAddChildNode(node, name, v8::UnboundScript::kNoScriptId, 0);
     202             :   }
     203             : 
     204             :   // We need to process the stack in reverse order as the top of the stack is
     205             :   // the first element in the list.
     206      192749 :   for (auto it = stack.rbegin(); it != stack.rend(); ++it) {
     207      122101 :     SharedFunctionInfo shared = *it;
     208      244202 :     const char* name = this->names()->GetName(shared->DebugName());
     209             :     int script_id = v8::UnboundScript::kNoScriptId;
     210      244202 :     if (shared->script()->IsScript()) {
     211      107762 :       Script script = Script::cast(shared->script());
     212             :       script_id = script->id();
     213             :     }
     214      122101 :     node = FindOrAddChildNode(node, name, script_id, shared->StartPosition());
     215             :   }
     216             : 
     217       70648 :   if (found_arguments_marker_frames) {
     218             :     node =
     219           3 :         FindOrAddChildNode(node, "(deopt)", v8::UnboundScript::kNoScriptId, 0);
     220             :   }
     221             : 
     222       70648 :   return node;
     223             : }
     224             : 
     225         316 : v8::AllocationProfile::Node* SamplingHeapProfiler::TranslateAllocationNode(
     226             :     AllocationProfile* profile, SamplingHeapProfiler::AllocationNode* node,
     227             :     const std::map<int, Handle<Script>>& scripts) {
     228             :   // By pinning the node we make sure its children won't get disposed if
     229             :   // a GC kicks in during the tree retrieval.
     230         316 :   node->pinned_ = true;
     231             :   Local<v8::String> script_name =
     232         316 :       ToApiHandle<v8::String>(isolate_->factory()->InternalizeUtf8String(""));
     233             :   int line = v8::AllocationProfile::kNoLineNumberInfo;
     234             :   int column = v8::AllocationProfile::kNoColumnNumberInfo;
     235             :   std::vector<v8::AllocationProfile::Allocation> allocations;
     236         316 :   allocations.reserve(node->allocations_.size());
     237         465 :   if (node->script_id_ != v8::UnboundScript::kNoScriptId &&
     238         149 :       scripts.find(node->script_id_) != scripts.end()) {
     239             :     // Cannot use std::map<T>::at because it is not available on android.
     240             :     auto non_const_scripts =
     241             :         const_cast<std::map<int, Handle<Script>>&>(scripts);
     242         149 :     Handle<Script> script = non_const_scripts[node->script_id_];
     243         149 :     if (!script.is_null()) {
     244         298 :       if (script->name()->IsName()) {
     245          60 :         Name name = Name::cast(script->name());
     246             :         script_name = ToApiHandle<v8::String>(
     247          30 :             isolate_->factory()->InternalizeUtf8String(names_->GetName(name)));
     248             :       }
     249         149 :       line = 1 + Script::GetLineNumber(script, node->script_position_);
     250         149 :       column = 1 + Script::GetColumnNumber(script, node->script_position_);
     251             :     }
     252             :   }
     253        1506 :   for (auto alloc : node->allocations_) {
     254        1748 :     allocations.push_back(ScaleSample(alloc.first, alloc.second));
     255             :   }
     256             : 
     257             :   profile->nodes_.push_back(v8::AllocationProfile::Node{
     258             :       ToApiHandle<v8::String>(
     259         316 :           isolate_->factory()->InternalizeUtf8String(node->name_)),
     260             :       script_name, node->script_id_, node->script_position_, line, column,
     261         948 :       node->id_, std::vector<v8::AllocationProfile::Node*>(), allocations});
     262         316 :   v8::AllocationProfile::Node* current = &profile->nodes_.back();
     263             :   // The |children_| map may have nodes inserted into it during translation
     264             :   // because the translation may allocate strings on the JS heap that have
     265             :   // the potential to be sampled. That's ok since map iterators are not
     266             :   // invalidated upon std::map insertion.
     267         884 :   for (const auto& it : node->children_) {
     268             :     current->children.push_back(
     269         504 :         TranslateAllocationNode(profile, it.second.get(), scripts));
     270             :   }
     271         316 :   node->pinned_ = false;
     272         316 :   return current;
     273             : }
     274             : 
     275          64 : v8::AllocationProfile* SamplingHeapProfiler::GetAllocationProfile() {
     276          64 :   if (flags_ & v8::HeapProfiler::kSamplingForceGC) {
     277             :     isolate_->heap()->CollectAllGarbage(
     278          20 :         Heap::kNoGCFlags, GarbageCollectionReason::kSamplingProfiler);
     279             :   }
     280             :   // To resolve positions to line/column numbers, we will need to look up
     281             :   // scripts. Build a map to allow fast mapping from script id to script.
     282             :   std::map<int, Handle<Script>> scripts;
     283             :   {
     284          64 :     Script::Iterator iterator(isolate_);
     285         361 :     for (Script script = iterator.Next(); !script.is_null();
     286             :          script = iterator.Next()) {
     287         594 :       scripts[script->id()] = handle(script, isolate_);
     288             :     }
     289             :   }
     290          64 :   auto profile = new v8::internal::AllocationProfile();
     291          64 :   TranslateAllocationNode(profile, &profile_root_, scripts);
     292         128 :   profile->samples_ = SamplingHeapProfiler::BuildSamples();
     293             : 
     294          64 :   return profile;
     295             : }
     296             : 
     297             : const std::vector<v8::AllocationProfile::Sample>
     298          64 : SamplingHeapProfiler::BuildSamples() const {
     299             :   std::vector<v8::AllocationProfile::Sample> samples;
     300          64 :   samples.reserve(samples_.size());
     301       43941 :   for (const auto& it : samples_) {
     302             :     const Sample* sample = it.second.get();
     303             :     samples.emplace_back(v8::AllocationProfile::Sample{
     304       43813 :         sample->owner->id_, sample->size, ScaleSample(sample->size, 1).count,
     305       87626 :         sample->sample_id});
     306             :   }
     307          64 :   return samples;
     308             : }
     309             : 
     310             : }  // namespace internal
     311      183867 : }  // namespace v8

Generated by: LCOV version 1.10