blob: 5a611e8ba76a8c8bfc102571000a7bf95e0792eb [file] [log] [blame]
// 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.
#ifndef V8_PROFILER_SAMPLING_HEAP_PROFILER_H_
#define V8_PROFILER_SAMPLING_HEAP_PROFILER_H_
#include <deque>
#include <map>
#include <memory>
#include <unordered_map>
#include "include/v8-profiler.h"
#include "src/heap/heap.h"
#include "src/profiler/strings-storage.h"
namespace v8 {
namespace base {
class RandomNumberGenerator;
} // namespace base
namespace internal {
class AllocationProfile : public v8::AllocationProfile {
public:
AllocationProfile() = default;
v8::AllocationProfile::Node* GetRootNode() override {
return nodes_.size() == 0 ? nullptr : &nodes_.front();
}
const std::vector<v8::AllocationProfile::Sample>& GetSamples() override {
return samples_;
}
private:
std::deque<v8::AllocationProfile::Node> nodes_;
std::vector<v8::AllocationProfile::Sample> samples_;
friend class SamplingHeapProfiler;
DISALLOW_COPY_AND_ASSIGN(AllocationProfile);
};
class SamplingHeapProfiler {
public:
class AllocationNode {
public:
using FunctionId = uint64_t;
AllocationNode(AllocationNode* parent, const char* name, int script_id,
int start_position, uint32_t id)
: parent_(parent),
script_id_(script_id),
script_position_(start_position),
name_(name),
id_(id) {}
AllocationNode* FindChildNode(FunctionId id) {
auto it = children_.find(id);
return it != children_.end() ? it->second.get() : nullptr;
}
AllocationNode* AddChildNode(FunctionId id,
std::unique_ptr<AllocationNode> node) {
return children_.emplace(id, std::move(node)).first->second.get();
}
static FunctionId function_id(int script_id, int start_position,
const char* name) {
// script_id == kNoScriptId case:
// Use function name pointer as an id. Names derived from VM state
// must not collide with the builtin names. The least significant bit
// of the id is set to 1.
if (script_id == v8::UnboundScript::kNoScriptId) {
return reinterpret_cast<intptr_t>(name) | 1;
}
// script_id != kNoScriptId case:
// Use script_id, start_position pair to uniquelly identify the node.
// The least significant bit of the id is set to 0.
DCHECK(static_cast<unsigned>(start_position) < (1u << 31));
return (static_cast<uint64_t>(script_id) << 32) + (start_position << 1);
}
private:
// TODO(alph): make use of unordered_map's here. Pay attention to
// iterator invalidation during TranslateAllocationNode.
std::map<size_t, unsigned int> allocations_;
std::map<FunctionId, std::unique_ptr<AllocationNode>> children_;
AllocationNode* const parent_;
const int script_id_;
const int script_position_;
const char* const name_;
uint32_t id_;
bool pinned_ = false;
friend class SamplingHeapProfiler;
DISALLOW_COPY_AND_ASSIGN(AllocationNode);
};
struct Sample {
Sample(size_t size_, AllocationNode* owner_, Local<Value> local_,
SamplingHeapProfiler* profiler_, uint64_t sample_id)
: size(size_),
owner(owner_),
global(reinterpret_cast<v8::Isolate*>(profiler_->isolate_), local_),
profiler(profiler_),
sample_id(sample_id) {}
const size_t size;
AllocationNode* const owner;
Global<Value> global;
SamplingHeapProfiler* const profiler;
const uint64_t sample_id;
private:
DISALLOW_COPY_AND_ASSIGN(Sample);
};
SamplingHeapProfiler(Heap* heap, StringsStorage* names, uint64_t rate,
int stack_depth, v8::HeapProfiler::SamplingFlags flags);
~SamplingHeapProfiler();
v8::AllocationProfile* GetAllocationProfile();
StringsStorage* names() const { return names_; }
private:
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 {
USE(heap_);
DCHECK(heap_->gc_state() == Heap::NOT_IN_GC);
if (soon_object) {
// TODO(ofrobots): it would be better to sample the next object rather
// than skipping this sample epoch if soon_object happens to be null.
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_;
};
void SampleObject(Address soon_object, size_t size);
const std::vector<v8::AllocationProfile::Sample> BuildSamples() const;
AllocationNode* FindOrAddChildNode(AllocationNode* parent, const char* name,
int script_id, int start_position);
static void OnWeakCallback(const WeakCallbackInfo<Sample>& data);
uint32_t next_node_id() { return ++last_node_id_; }
uint64_t next_sample_id() { return ++last_sample_id_; }
// Methods that construct v8::AllocationProfile.
// Translates the provided AllocationNode *node* returning an equivalent
// AllocationProfile::Node. The newly created AllocationProfile::Node is added
// to the provided AllocationProfile *profile*. Line numbers, column numbers,
// and script names are resolved using *scripts* which maps all currently
// loaded scripts keyed by their script id.
v8::AllocationProfile::Node* TranslateAllocationNode(
AllocationProfile* profile, SamplingHeapProfiler::AllocationNode* node,
const std::map<int, Handle<Script>>& scripts);
v8::AllocationProfile::Allocation ScaleSample(size_t size,
unsigned int count) const;
AllocationNode* AddStack();
Isolate* const isolate_;
Heap* const heap_;
uint64_t last_sample_id_ = 0;
uint32_t last_node_id_ = 0;
Observer allocation_observer_;
StringsStorage* const names_;
AllocationNode profile_root_;
std::unordered_map<Sample*, std::unique_ptr<Sample>> samples_;
const int stack_depth_;
const uint64_t rate_;
v8::HeapProfiler::SamplingFlags flags_;
DISALLOW_COPY_AND_ASSIGN(SamplingHeapProfiler);
};
} // namespace internal
} // namespace v8
#endif // V8_PROFILER_SAMPLING_HEAP_PROFILER_H_