V8 Heap Profiler 的实现

前言:V8 Heap Profiler 用于收集哪些代码分析了多少内存的信息。本文介绍 V8 中关于这部分的实现,代码来自 V8 10.2。

入口函数是 StartSamplingHeapProfiler。

  bool StartSamplingHeapProfiler(uint64_t sample_interval, int stack_depth, v8::HeapProfiler::SamplingFlags);

主要的参数是 sample_interval。

bool HeapProfiler::StartSamplingHeapProfiler(
    uint64_t sample_interval, int stack_depth,
    v8::HeapProfiler::SamplingFlags flags) {
  if (sampling_heap_profiler_.get()) {
    return false;
  }
  sampling_heap_profiler_.reset(new SamplingHeapProfiler(
      heap(), names_.get(), sample_interval, stack_depth, flags));
  return true;
}

主要逻辑是创建一个 SamplingHeapProfiler 对象。来看一下这个对象的构造函数。

SamplingHeapProfiler::SamplingHeapProfiler(
    Heap* heap, StringsStorage* names, uint64_t rate, int stack_depth,
    v8::HeapProfiler::SamplingFlags flags)
    : isolate_(Isolate::FromHeap(heap)),
      heap_(heap),
      allocation_observer_(heap_, static_cast<intptr_t>(rate), rate, this,
                           isolate_->random_number_generator()),
      names_(names),
      profile_root_(nullptr, "(root)", v8::UnboundScript::kNoScriptId, 0,
                    next_node_id()),
      stack_depth_(stack_depth),
      rate_(rate),
      flags_(flags) {
  
  heap_->AddAllocationObserversToAllSpaces(&allocation_observer_,
                                           &allocation_observer_);
}

代码比较简单,主要是初始化一些字段,其中最重要的是 allocation_observer_ 字段,该字段是一个 Observer 对象。

class Observer : public AllocationObserver {
   public:
    Observer(Heap* heap, intptr_t step_size, uint64_t rate,
             SamplingHeapProfiler* profiler,
             base::RandomNumberGenerator* random)
        : AllocationObserver(step_size),
          profiler_(profiler),
          heap_(heap),
          random_(random),
          rate_(rate) {}

   protected:
    void Step(int bytes_allocated, Address soon_object, size_t size) override {
      if (soon_object) {
        profiler_->SampleObject(soon_object, size);
      }
    }

    intptr_t GetNextStepSize() override { return GetNextSampleInterval(rate_); }

   private:
    intptr_t GetNextSampleInterval(uint64_t rate);
    SamplingHeapProfiler* const profiler_;
    Heap* const heap_;
    base::RandomNumberGenerator* const random_;
    uint64_t const rate_;
  };

Observer 继承 AllocationObserver,AllocationObserver 是一个可以监听堆对象分配的接口。其中最重要的是 Step 方法,该方法在 V8 分配 n 个字节时被回调。创建完 Observer 对象后,V8 会把该对象通过 AddAllocationObserversToAllSpaces 注册到堆中。

void Heap::AddAllocationObserversToAllSpaces(
    AllocationObserver* observer, AllocationObserver* new_space_observer) {
  // 遍历各种堆,注册观察者
  for (SpaceIterator it(this); it.HasNext();) {
    Space* space = it.Next();
    if (space == new_space()) {
      space->AddAllocationObserver(new_space_observer);
    } else {
      space->AddAllocationObserver(observer);
    }
  }
}

AddAllocationObserversToAllSpaces 往新生代、老生代等堆内存管理对象中注册观察者,来看一下 AddAllocationObserver。

void Space::AddAllocationObserver(AllocationObserver* observer) {
  allocation_counter_.AddAllocationObserver(observer);
}

void AllocationCounter::AddAllocationObserver(AllocationObserver* observer) {
  intptr_t step_size = observer->GetNextStepSize();
  size_t observer_next_counter = current_counter_ + step_size;
  /*
	  struct AllocationObserverCounter final {
	    AllocationObserverCounter(AllocationObserver* observer, size_t prev_counter, size_t next_counter)
	        : observer_(observer),
	          prev_counter_(prev_counter),
	          next_counter_(next_counter) {}
	
	    AllocationObserver* observer_;
	    size_t prev_counter_;
	    size_t next_counter_;
	  };
  */
  intptr_t step_size = observer->GetNextStepSize();
  size_t observer_next_counter = current_counter_ + step_size;
  observers_.push_back(AllocationObserverCounter(observer, current_counter_, observer_next_counter));
}

这样就完成了观察者的注册,接着看调用观察者的逻辑。具体在分配内存时会调用 InvokeAllocationObservers,比如新生代的 AllocateRawUnaligned 函数。

// soon_object: 分配的地址,object_size 分配的大小
void AllocationCounter::InvokeAllocationObservers(Address soon_object,
                                                  size_t object_size,
                                                  size_t aligned_object_size) {
  bool step_run = false;
  step_in_progress_ = true;
  size_t step_size = 0;
  // 遍历观察者
  for (AllocationObserverCounter& aoc : observers_) {
    if (aoc.next_counter_ - current_counter_ <= aligned_object_size) {
      {
        DisallowGarbageCollection no_gc;
        aoc.observer_->Step(
            static_cast<int>(current_counter_ - aoc.prev_counter_), soon_object,
            object_size);
      }
    }
  }
}

接着看之前注册的观察者的 Step 函数。

void Step(int bytes_allocated, Address soon_object, size_t size) override {
    if (soon_object) {
      profiler_->SampleObject(soon_object, size);
    }
}

void SamplingHeapProfiler::SampleObject(Address soon_object, size_t size) {
  DisallowGarbageCollection no_gc;
  HandleScope scope(isolate_);
  HeapObject heap_object = HeapObject::FromAddress(soon_object);
  Handle<Object> obj(heap_object, isolate_);

  Local<v8::Value> loc = v8::Utils::ToLocal(obj);
  // 处理栈信息
  AllocationNode* node = AddStack();
  node->allocations_[size]++;
  // 记录信息
  auto sample = std::make_unique<Sample>(size, node, loc, this, next_sample_id());
  samples_.emplace(sample.get(), std::move(sample));
}

SampleObject 首先处理了当前调用栈,这样才知道是谁申请了该内存。

SamplingHeapProfiler::AllocationNode* SamplingHeapProfiler::AddStack() {
  AllocationNode* node = &profile_root_;
  std::vector<SharedFunctionInfo> stack;
  JavaScriptFrameIterator frame_it(isolate_);
  int frames_captured = 0;
  bool found_arguments_marker_frames = false;
  // 还有栈且没有达到需要捕获的栈深度
  while (!frame_it.done() && frames_captured < stack_depth_) {
    JavaScriptFrame* frame = frame_it.frame();
    // 记录栈
    if (frame->unchecked_function().IsJSFunction()) {
      SharedFunctionInfo shared = frame->function().shared();
      stack.push_back(shared);
      frames_captured++;
    } else {
      found_arguments_marker_frames = true;
    }
    frame_it.Advance();
  }
  // 遍历栈,并找到对应的代码信息
  for (auto it = stack.rbegin(); it != stack.rend(); ++it) {
    SharedFunctionInfo shared = *it;
    const char* name = this->names()->GetCopy(shared.DebugNameCStr().get());
    int script_id = v8::UnboundScript::kNoScriptId;
    if (shared.script().IsScript()) {
      Script script = Script::cast(shared.script());
      script_id = script.id();
    }
    // 构造树(层次)结构
    node = FindOrAddChildNode(node, name, script_id, shared.StartPosition());
  }
  
  return node;
}

AddStack 捕获了当前的栈信息并且构造了一个相应的树结构。紧接着可以调用 GetAllocationProfile 获取收集到的信息。

v8::AllocationProfile* SamplingHeapProfiler::GetAllocationProfile() {
  std::map<int, Handle<Script>> scripts;
  {
    Script::Iterator iterator(isolate_);
    for (Script script = iterator.Next(); !script.is_null();
         script = iterator.Next()) {
      scripts[script.id()] = handle(script, isolate_);
    }
  }
  auto profile = new v8::internal::AllocationProfile();
  TranslateAllocationNode(profile, &profile_root_, scripts);
  profile->samples_ = BuildSamples();

  return profile;
}

GetAllocationProfile 对收集到的数据进行处理。

v8::AllocationProfile::Node* SamplingHeapProfiler::TranslateAllocationNode(
    AllocationProfile* profile, SamplingHeapProfiler::AllocationNode* node,
    const std::map<int, Handle<Script>>& scripts) {
 
  node->pinned_ = true;
  Local<v8::String> script_name =
      ToApiHandle<v8::String>(isolate_->factory()->InternalizeUtf8String(""));
  int line = v8::AllocationProfile::kNoLineNumberInfo;
  int column = v8::AllocationProfile::kNoColumnNumberInfo;
  std::vector<v8::AllocationProfile::Allocation> allocations;
  allocations.reserve(node->allocations_.size());
  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;
      if (script->name().IsName()) {
        Name name = Name::cast(script->name());
        script_name = ToApiHandle<v8::String>(
            isolate_->factory()->InternalizeUtf8String(names_->GetName(name)));
      }
      line = 1 + Script::GetLineNumber(script, node->script_position_);
      column = 1 + Script::GetColumnNumber(script, node->script_position_);
    }
  }
  for (auto alloc : node->allocations_) {
    allocations.push_back(ScaleSample(alloc.first, alloc.second));
  }

  profile->nodes_.push_back(v8::AllocationProfile::Node{
      ToApiHandle<v8::String>(
          isolate_->factory()->InternalizeUtf8String(node->name_)),
      script_name, node->script_id_, node->script_position_, line, column,
      node->id_, std::vector<v8::AllocationProfile::Node*>(), allocations});
  v8::AllocationProfile::Node* current = &profile->nodes_.back();
  for (const auto& it : node->children_) {
  	// 递归处理
    current->children.push_back(
        TranslateAllocationNode(profile, it.second.get(), scripts));
  }
  node->pinned_ = false;
  return current;
}

然后构造 sample。

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;
}

细节比较多,大致流程已经分析完毕,最终就拿到了 Heap Profile 的数据。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值