blob: 5f79439b05606a0ac6941ae844de92e600dbd4b8 [file] [log] [blame]
// Copyright 2019 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.
#include "src/heap/memory-measurement.h"
#include "include/v8.h"
#include "src/api/api-inl.h"
#include "src/api/api.h"
#include "src/execution/isolate-inl.h"
#include "src/execution/isolate.h"
#include "src/heap/factory-inl.h"
#include "src/heap/factory.h"
#include "src/heap/incremental-marking.h"
#include "src/heap/marking-worklist.h"
#include "src/logging/counters.h"
#include "src/objects/js-promise-inl.h"
#include "src/objects/js-promise.h"
#include "src/tasks/task-utils.h"
namespace v8 {
namespace internal {
namespace {
class MemoryMeasurementResultBuilder {
public:
MemoryMeasurementResultBuilder(Isolate* isolate, Factory* factory)
: isolate_(isolate), factory_(factory) {
result_ = NewJSObject();
}
void AddTotal(size_t estimate, size_t lower_bound, size_t upper_bound) {
AddProperty(result_, factory_->total_string(),
NewResult(estimate, lower_bound, upper_bound));
}
void AddCurrent(size_t estimate, size_t lower_bound, size_t upper_bound) {
detailed_ = true;
AddProperty(result_, factory_->current_string(),
NewResult(estimate, lower_bound, upper_bound));
}
void AddOther(size_t estimate, size_t lower_bound, size_t upper_bound) {
detailed_ = true;
other_.push_back(NewResult(estimate, lower_bound, upper_bound));
}
Handle<JSObject> Build() {
if (detailed_) {
int length = static_cast<int>(other_.size());
Handle<FixedArray> other = factory_->NewFixedArray(length);
for (int i = 0; i < length; i++) {
other->set(i, *other_[i]);
}
AddProperty(result_, factory_->other_string(),
factory_->NewJSArrayWithElements(other));
}
return result_;
}
private:
Handle<JSObject> NewResult(size_t estimate, size_t lower_bound,
size_t upper_bound) {
Handle<JSObject> result = NewJSObject();
Handle<Object> estimate_obj = NewNumber(estimate);
AddProperty(result, factory_->jsMemoryEstimate_string(), estimate_obj);
Handle<Object> range = NewRange(lower_bound, upper_bound);
AddProperty(result, factory_->jsMemoryRange_string(), range);
return result;
}
Handle<Object> NewNumber(size_t value) {
return factory_->NewNumberFromSize(value);
}
Handle<JSObject> NewJSObject() {
return factory_->NewJSObject(isolate_->object_function());
}
Handle<JSArray> NewRange(size_t lower_bound, size_t upper_bound) {
Handle<Object> lower = NewNumber(lower_bound);
Handle<Object> upper = NewNumber(upper_bound);
Handle<FixedArray> elements = factory_->NewFixedArray(2);
elements->set(0, *lower);
elements->set(1, *upper);
return factory_->NewJSArrayWithElements(elements);
}
void AddProperty(Handle<JSObject> object, Handle<String> name,
Handle<Object> value) {
JSObject::AddProperty(isolate_, object, name, value, NONE);
}
Isolate* isolate_;
Factory* factory_;
Handle<JSObject> result_;
std::vector<Handle<JSObject>> other_;
bool detailed_ = false;
};
} // anonymous namespace
class V8_EXPORT_PRIVATE MeasureMemoryDelegate
: public v8::MeasureMemoryDelegate {
public:
MeasureMemoryDelegate(Isolate* isolate, Handle<NativeContext> context,
Handle<JSPromise> promise, v8::MeasureMemoryMode mode);
~MeasureMemoryDelegate() override;
// v8::MeasureMemoryDelegate overrides:
bool ShouldMeasure(v8::Local<v8::Context> context) override;
void MeasurementComplete(
const std::vector<std::pair<v8::Local<v8::Context>, size_t>>&
context_sizes_in_bytes,
size_t unattributed_size_in_bytes) override;
private:
Isolate* isolate_;
Handle<JSPromise> promise_;
Handle<NativeContext> context_;
v8::MeasureMemoryMode mode_;
};
MeasureMemoryDelegate::MeasureMemoryDelegate(Isolate* isolate,
Handle<NativeContext> context,
Handle<JSPromise> promise,
v8::MeasureMemoryMode mode)
: isolate_(isolate), mode_(mode) {
context_ = isolate->global_handles()->Create(*context);
promise_ = isolate->global_handles()->Create(*promise);
}
MeasureMemoryDelegate::~MeasureMemoryDelegate() {
isolate_->global_handles()->Destroy(promise_.location());
isolate_->global_handles()->Destroy(context_.location());
}
bool MeasureMemoryDelegate::ShouldMeasure(v8::Local<v8::Context> context) {
Handle<NativeContext> native_context =
Handle<NativeContext>::cast(Utils::OpenHandle(*context));
return context_->security_token() == native_context->security_token();
}
void MeasureMemoryDelegate::MeasurementComplete(
const std::vector<std::pair<v8::Local<v8::Context>, size_t>>&
context_sizes_in_bytes,
size_t shared_size) {
v8::Local<v8::Context> v8_context =
Utils::Convert<HeapObject, v8::Context>(context_);
v8::Context::Scope scope(v8_context);
size_t total_size = 0;
size_t current_size = 0;
for (const auto& context_and_size : context_sizes_in_bytes) {
total_size += context_and_size.second;
if (*Utils::OpenHandle(*context_and_size.first) == *context_) {
current_size = context_and_size.second;
}
}
MemoryMeasurementResultBuilder result_builder(isolate_, isolate_->factory());
result_builder.AddTotal(total_size, total_size, total_size + shared_size);
if (mode_ == v8::MeasureMemoryMode::kDetailed) {
result_builder.AddCurrent(current_size, current_size,
current_size + shared_size);
for (const auto& context_and_size : context_sizes_in_bytes) {
if (*Utils::OpenHandle(*context_and_size.first) != *context_) {
size_t other_size = context_and_size.second;
result_builder.AddOther(other_size, other_size,
other_size + shared_size);
}
}
}
Handle<JSObject> result = result_builder.Build();
JSPromise::Resolve(promise_, result).ToHandleChecked();
}
MemoryMeasurement::MemoryMeasurement(Isolate* isolate)
: isolate_(isolate), random_number_generator_() {
if (FLAG_random_seed) {
random_number_generator_.SetSeed(FLAG_random_seed);
}
}
bool MemoryMeasurement::EnqueueRequest(
std::unique_ptr<v8::MeasureMemoryDelegate> delegate,
v8::MeasureMemoryExecution execution,
const std::vector<Handle<NativeContext>> contexts) {
int length = static_cast<int>(contexts.size());
Handle<WeakFixedArray> weak_contexts =
isolate_->factory()->NewWeakFixedArray(length);
for (int i = 0; i < length; ++i) {
weak_contexts->Set(i, HeapObjectReference::Weak(*contexts[i]));
}
Handle<WeakFixedArray> global_weak_contexts =
isolate_->global_handles()->Create(*weak_contexts);
Request request = {std::move(delegate),
global_weak_contexts,
std::vector<size_t>(length),
0u,
{}};
request.timer.Start();
received_.push_back(std::move(request));
ScheduleGCTask(execution);
return true;
}
std::vector<Address> MemoryMeasurement::StartProcessing() {
if (received_.empty()) return {};
std::unordered_set<Address> unique_contexts;
DCHECK(processing_.empty());
processing_ = std::move(received_);
for (const auto& request : processing_) {
Handle<WeakFixedArray> contexts = request.contexts;
for (int i = 0; i < contexts->length(); i++) {
HeapObject context;
if (contexts->Get(i).GetHeapObject(&context)) {
unique_contexts.insert(context.ptr());
}
}
}
return std::vector<Address>(unique_contexts.begin(), unique_contexts.end());
}
void MemoryMeasurement::FinishProcessing(const NativeContextStats& stats) {
if (processing_.empty()) return;
while (!processing_.empty()) {
Request request = std::move(processing_.front());
processing_.pop_front();
for (int i = 0; i < static_cast<int>(request.sizes.size()); i++) {
HeapObject context;
if (!request.contexts->Get(i).GetHeapObject(&context)) {
continue;
}
request.sizes[i] = stats.Get(context.ptr());
}
request.shared = stats.Get(MarkingWorklists::kSharedContext);
done_.push_back(std::move(request));
}
ScheduleReportingTask();
}
void MemoryMeasurement::ScheduleReportingTask() {
if (reporting_task_pending_) return;
reporting_task_pending_ = true;
auto taskrunner = V8::GetCurrentPlatform()->GetForegroundTaskRunner(
reinterpret_cast<v8::Isolate*>(isolate_));
taskrunner->PostTask(MakeCancelableTask(isolate_, [this] {
reporting_task_pending_ = false;
ReportResults();
}));
}
bool MemoryMeasurement::IsGCTaskPending(v8::MeasureMemoryExecution execution) {
DCHECK(execution == v8::MeasureMemoryExecution::kEager ||
execution == v8::MeasureMemoryExecution::kDefault);
return execution == v8::MeasureMemoryExecution::kEager
? eager_gc_task_pending_
: delayed_gc_task_pending_;
}
void MemoryMeasurement::SetGCTaskPending(v8::MeasureMemoryExecution execution) {
DCHECK(execution == v8::MeasureMemoryExecution::kEager ||
execution == v8::MeasureMemoryExecution::kDefault);
if (execution == v8::MeasureMemoryExecution::kEager) {
eager_gc_task_pending_ = true;
} else {
delayed_gc_task_pending_ = true;
}
}
void MemoryMeasurement::SetGCTaskDone(v8::MeasureMemoryExecution execution) {
DCHECK(execution == v8::MeasureMemoryExecution::kEager ||
execution == v8::MeasureMemoryExecution::kDefault);
if (execution == v8::MeasureMemoryExecution::kEager) {
eager_gc_task_pending_ = false;
} else {
delayed_gc_task_pending_ = false;
}
}
void MemoryMeasurement::ScheduleGCTask(v8::MeasureMemoryExecution execution) {
if (execution == v8::MeasureMemoryExecution::kLazy) return;
if (IsGCTaskPending(execution)) return;
SetGCTaskPending(execution);
auto taskrunner = V8::GetCurrentPlatform()->GetForegroundTaskRunner(
reinterpret_cast<v8::Isolate*>(isolate_));
auto task = MakeCancelableTask(isolate_, [this, execution] {
SetGCTaskDone(execution);
if (received_.empty()) return;
Heap* heap = isolate_->heap();
if (FLAG_incremental_marking) {
if (heap->incremental_marking()->IsStopped()) {
heap->StartIncrementalMarking(Heap::kNoGCFlags,
GarbageCollectionReason::kMeasureMemory);
} else {
if (execution == v8::MeasureMemoryExecution::kEager) {
heap->FinalizeIncrementalMarkingAtomically(
GarbageCollectionReason::kMeasureMemory);
}
ScheduleGCTask(execution);
}
} else {
heap->CollectGarbage(OLD_SPACE, GarbageCollectionReason::kMeasureMemory);
}
});
if (execution == v8::MeasureMemoryExecution::kEager) {
taskrunner->PostTask(std::move(task));
} else {
taskrunner->PostDelayedTask(std::move(task), NextGCTaskDelayInSeconds());
}
}
int MemoryMeasurement::NextGCTaskDelayInSeconds() {
return kGCTaskDelayInSeconds +
random_number_generator_.NextInt(kGCTaskDelayInSeconds);
}
void MemoryMeasurement::ReportResults() {
while (!done_.empty()) {
Request request = std::move(done_.front());
done_.pop_front();
HandleScope handle_scope(isolate_);
std::vector<std::pair<v8::Local<v8::Context>, size_t>> sizes;
DCHECK_EQ(request.sizes.size(),
static_cast<size_t>(request.contexts->length()));
for (int i = 0; i < request.contexts->length(); i++) {
HeapObject raw_context;
if (!request.contexts->Get(i).GetHeapObject(&raw_context)) {
continue;
}
v8::Local<v8::Context> context = Utils::Convert<HeapObject, v8::Context>(
handle(raw_context, isolate_));
sizes.push_back(std::make_pair(context, request.sizes[i]));
}
request.delegate->MeasurementComplete(sizes, request.shared);
isolate_->counters()->measure_memory_delay_ms()->AddSample(
static_cast<int>(request.timer.Elapsed().InMilliseconds()));
}
}
std::unique_ptr<v8::MeasureMemoryDelegate> MemoryMeasurement::DefaultDelegate(
Isolate* isolate, Handle<NativeContext> context, Handle<JSPromise> promise,
v8::MeasureMemoryMode mode) {
return std::make_unique<MeasureMemoryDelegate>(isolate, context, promise,
mode);
}
bool NativeContextInferrer::InferForContext(Isolate* isolate, Context context,
Address* native_context) {
Map context_map = context.synchronized_map();
Object maybe_native_context =
TaggedField<Object, Map::kConstructorOrBackPointerOrNativeContextOffset>::
Acquire_Load(isolate, context_map);
if (maybe_native_context.IsNativeContext()) {
*native_context = maybe_native_context.ptr();
return true;
}
return false;
}
bool NativeContextInferrer::InferForJSFunction(Isolate* isolate,
JSFunction function,
Address* native_context) {
Object maybe_context =
TaggedField<Object, JSFunction::kContextOffset>::Acquire_Load(isolate,
function);
// The context may be a smi during deserialization.
if (maybe_context.IsSmi()) {
DCHECK_EQ(maybe_context, Deserializer::uninitialized_field_value());
return false;
}
if (!maybe_context.IsContext()) {
// The function does not have a context.
return false;
}
return InferForContext(isolate, Context::cast(maybe_context), native_context);
}
bool NativeContextInferrer::InferForJSObject(Isolate* isolate, Map map,
JSObject object,
Address* native_context) {
if (map.instance_type() == JS_GLOBAL_OBJECT_TYPE) {
Object maybe_context =
JSGlobalObject::cast(object).native_context_unchecked(isolate);
if (maybe_context.IsNativeContext()) {
*native_context = maybe_context.ptr();
return true;
}
}
// The maximum number of steps to perform when looking for the context.
const int kMaxSteps = 3;
Object maybe_constructor = map.TryGetConstructor(isolate, kMaxSteps);
if (maybe_constructor.IsJSFunction()) {
return InferForJSFunction(isolate, JSFunction::cast(maybe_constructor),
native_context);
}
return false;
}
void NativeContextStats::Clear() { size_by_context_.clear(); }
void NativeContextStats::Merge(const NativeContextStats& other) {
for (const auto& it : other.size_by_context_) {
size_by_context_[it.first] += it.second;
}
}
void NativeContextStats::IncrementExternalSize(Address context, Map map,
HeapObject object) {
InstanceType instance_type = map.instance_type();
size_t external_size = 0;
if (instance_type == JS_ARRAY_BUFFER_TYPE) {
external_size = JSArrayBuffer::cast(object).allocation_length();
} else {
DCHECK(InstanceTypeChecker::IsExternalString(instance_type));
external_size = ExternalString::cast(object).ExternalPayloadSize();
}
size_by_context_[context] += external_size;
}
} // namespace internal
} // namespace v8