V8 Heap Profiler 的实现

2022-05-16 15:57:22 浏览数 (1)

前言: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 的数据。

0 人点赞