前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >V8 Heap Profiler 的实现

V8 Heap Profiler 的实现

作者头像
theanarkh
发布2022-05-16 15:57:22
4170
发布2022-05-16 15:57:22
举报
文章被收录于专栏:原创分享原创分享

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

入口函数是 StartSamplingHeapProfiler。

代码语言:javascript
复制
  bool StartSamplingHeapProfiler(uint64_t sample_interval, int stack_depth, v8::HeapProfiler::SamplingFlags);

主要的参数是 sample_interval。

代码语言:javascript
复制
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 对象。来看一下这个对象的构造函数。

代码语言:javascript
复制
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 对象。

代码语言:javascript
复制
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 注册到堆中。

代码语言:javascript
复制
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。

代码语言:javascript
复制
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 函数。

代码语言:javascript
复制
// 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 函数。

代码语言:javascript
复制
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 首先处理了当前调用栈,这样才知道是谁申请了该内存。

代码语言:javascript
复制
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 获取收集到的信息。

代码语言:javascript
复制
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 对收集到的数据进行处理。

代码语言:javascript
复制
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。

代码语言:javascript
复制
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 的数据。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2022-05-06,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 编程杂技 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
腾讯云代码分析
腾讯云代码分析(内部代号CodeDog)是集众多代码分析工具的云原生、分布式、高性能的代码综合分析跟踪管理平台,其主要功能是持续跟踪分析代码,观测项目代码质量,支撑团队传承代码文化。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档