%PDF- <> %âãÏÓ endobj 2 0 obj <> endobj 3 0 obj <>/ExtGState<>/ProcSet[/PDF/Text/ImageB/ImageC/ImageI] >>/Annots[ 28 0 R 29 0 R] /MediaBox[ 0 0 595.5 842.25] /Contents 4 0 R/Group<>/Tabs/S>> endobj ºaâÚÎΞ-ÌE1ÍØÄ÷{òò2ÿ ÛÖ^ÔÀá TÎ{¦?§®¥kuµùÕ5sLOšuY>endobj 2 0 obj<>endobj 2 0 obj<>endobj 2 0 obj<>endobj 2 0 obj<> endobj 2 0 obj<>endobj 2 0 obj<>es 3 0 R>> endobj 2 0 obj<> ox[ 0.000000 0.000000 609.600000 935.600000]/Fi endobj 3 0 obj<> endobj 7 1 obj<>/ProcSet[/PDF/Text/ImageB/ImageC/ImageI]>>/Subtype/Form>> stream
// 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/execution/isolate-inl.h" #include "src/heap/factory-inl.h" #include "src/heap/incremental-marking.h" #include "src/heap/marking-worklist.h" #include "src/logging/counters.h" #include "src/objects/js-array-buffer-inl.h" #include "src/objects/js-promise-inl.h" #include "src/objects/smi.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.map(kAcquireLoad); 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, Smi::uninitialized_deserialization_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