| // 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 |