• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2019 the V8 project authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "src/heap/memory-measurement.h"
6 
7 #include "include/v8-local-handle.h"
8 #include "src/api/api-inl.h"
9 #include "src/execution/isolate-inl.h"
10 #include "src/handles/global-handles-inl.h"
11 #include "src/heap/factory-inl.h"
12 #include "src/heap/incremental-marking.h"
13 #include "src/heap/marking-worklist.h"
14 #include "src/logging/counters.h"
15 #include "src/objects/js-array-buffer-inl.h"
16 #include "src/objects/js-promise-inl.h"
17 #include "src/objects/smi.h"
18 #include "src/tasks/task-utils.h"
19 
20 namespace v8 {
21 namespace internal {
22 
23 namespace {
24 class MemoryMeasurementResultBuilder {
25  public:
MemoryMeasurementResultBuilder(Isolate * isolate,Factory * factory)26   MemoryMeasurementResultBuilder(Isolate* isolate, Factory* factory)
27       : isolate_(isolate), factory_(factory) {
28     result_ = NewJSObject();
29   }
AddTotal(size_t estimate,size_t lower_bound,size_t upper_bound)30   void AddTotal(size_t estimate, size_t lower_bound, size_t upper_bound) {
31     AddProperty(result_, factory_->total_string(),
32                 NewResult(estimate, lower_bound, upper_bound));
33   }
AddCurrent(size_t estimate,size_t lower_bound,size_t upper_bound)34   void AddCurrent(size_t estimate, size_t lower_bound, size_t upper_bound) {
35     detailed_ = true;
36     AddProperty(result_, factory_->current_string(),
37                 NewResult(estimate, lower_bound, upper_bound));
38   }
AddOther(size_t estimate,size_t lower_bound,size_t upper_bound)39   void AddOther(size_t estimate, size_t lower_bound, size_t upper_bound) {
40     detailed_ = true;
41     other_.push_back(NewResult(estimate, lower_bound, upper_bound));
42   }
Build()43   Handle<JSObject> Build() {
44     if (detailed_) {
45       int length = static_cast<int>(other_.size());
46       Handle<FixedArray> other = factory_->NewFixedArray(length);
47       for (int i = 0; i < length; i++) {
48         other->set(i, *other_[i]);
49       }
50       AddProperty(result_, factory_->other_string(),
51                   factory_->NewJSArrayWithElements(other));
52     }
53     return result_;
54   }
55 
56  private:
NewResult(size_t estimate,size_t lower_bound,size_t upper_bound)57   Handle<JSObject> NewResult(size_t estimate, size_t lower_bound,
58                              size_t upper_bound) {
59     Handle<JSObject> result = NewJSObject();
60     Handle<Object> estimate_obj = NewNumber(estimate);
61     AddProperty(result, factory_->jsMemoryEstimate_string(), estimate_obj);
62     Handle<Object> range = NewRange(lower_bound, upper_bound);
63     AddProperty(result, factory_->jsMemoryRange_string(), range);
64     return result;
65   }
NewNumber(size_t value)66   Handle<Object> NewNumber(size_t value) {
67     return factory_->NewNumberFromSize(value);
68   }
NewJSObject()69   Handle<JSObject> NewJSObject() {
70     return factory_->NewJSObject(isolate_->object_function());
71   }
NewRange(size_t lower_bound,size_t upper_bound)72   Handle<JSArray> NewRange(size_t lower_bound, size_t upper_bound) {
73     Handle<Object> lower = NewNumber(lower_bound);
74     Handle<Object> upper = NewNumber(upper_bound);
75     Handle<FixedArray> elements = factory_->NewFixedArray(2);
76     elements->set(0, *lower);
77     elements->set(1, *upper);
78     return factory_->NewJSArrayWithElements(elements);
79   }
AddProperty(Handle<JSObject> object,Handle<String> name,Handle<Object> value)80   void AddProperty(Handle<JSObject> object, Handle<String> name,
81                    Handle<Object> value) {
82     JSObject::AddProperty(isolate_, object, name, value, NONE);
83   }
84   Isolate* isolate_;
85   Factory* factory_;
86   Handle<JSObject> result_;
87   std::vector<Handle<JSObject>> other_;
88   bool detailed_ = false;
89 };
90 }  // anonymous namespace
91 
92 class V8_EXPORT_PRIVATE MeasureMemoryDelegate
93     : public v8::MeasureMemoryDelegate {
94  public:
95   MeasureMemoryDelegate(Isolate* isolate, Handle<NativeContext> context,
96                         Handle<JSPromise> promise, v8::MeasureMemoryMode mode);
97   ~MeasureMemoryDelegate() override;
98 
99   // v8::MeasureMemoryDelegate overrides:
100   bool ShouldMeasure(v8::Local<v8::Context> context) override;
101   void MeasurementComplete(
102       const std::vector<std::pair<v8::Local<v8::Context>, size_t>>&
103           context_sizes_in_bytes,
104       size_t unattributed_size_in_bytes) override;
105 
106  private:
107   Isolate* isolate_;
108   Handle<JSPromise> promise_;
109   Handle<NativeContext> context_;
110   v8::MeasureMemoryMode mode_;
111 };
112 
MeasureMemoryDelegate(Isolate * isolate,Handle<NativeContext> context,Handle<JSPromise> promise,v8::MeasureMemoryMode mode)113 MeasureMemoryDelegate::MeasureMemoryDelegate(Isolate* isolate,
114                                              Handle<NativeContext> context,
115                                              Handle<JSPromise> promise,
116                                              v8::MeasureMemoryMode mode)
117     : isolate_(isolate), mode_(mode) {
118   context_ = isolate->global_handles()->Create(*context);
119   promise_ = isolate->global_handles()->Create(*promise);
120 }
121 
~MeasureMemoryDelegate()122 MeasureMemoryDelegate::~MeasureMemoryDelegate() {
123   isolate_->global_handles()->Destroy(promise_.location());
124   isolate_->global_handles()->Destroy(context_.location());
125 }
126 
ShouldMeasure(v8::Local<v8::Context> context)127 bool MeasureMemoryDelegate::ShouldMeasure(v8::Local<v8::Context> context) {
128   Handle<NativeContext> native_context =
129       Handle<NativeContext>::cast(Utils::OpenHandle(*context));
130   return context_->security_token() == native_context->security_token();
131 }
132 
MeasurementComplete(const std::vector<std::pair<v8::Local<v8::Context>,size_t>> & context_sizes_in_bytes,size_t shared_size)133 void MeasureMemoryDelegate::MeasurementComplete(
134     const std::vector<std::pair<v8::Local<v8::Context>, size_t>>&
135         context_sizes_in_bytes,
136     size_t shared_size) {
137   v8::Local<v8::Context> v8_context =
138       Utils::Convert<HeapObject, v8::Context>(context_);
139   v8::Context::Scope scope(v8_context);
140   size_t total_size = 0;
141   size_t current_size = 0;
142   for (const auto& context_and_size : context_sizes_in_bytes) {
143     total_size += context_and_size.second;
144     if (*Utils::OpenHandle(*context_and_size.first) == *context_) {
145       current_size = context_and_size.second;
146     }
147   }
148   MemoryMeasurementResultBuilder result_builder(isolate_, isolate_->factory());
149   result_builder.AddTotal(total_size, total_size, total_size + shared_size);
150 
151   if (mode_ == v8::MeasureMemoryMode::kDetailed) {
152     result_builder.AddCurrent(current_size, current_size,
153                               current_size + shared_size);
154     for (const auto& context_and_size : context_sizes_in_bytes) {
155       if (*Utils::OpenHandle(*context_and_size.first) != *context_) {
156         size_t other_size = context_and_size.second;
157         result_builder.AddOther(other_size, other_size,
158                                 other_size + shared_size);
159       }
160     }
161   }
162 
163   Handle<JSObject> result = result_builder.Build();
164   JSPromise::Resolve(promise_, result).ToHandleChecked();
165 }
166 
MemoryMeasurement(Isolate * isolate)167 MemoryMeasurement::MemoryMeasurement(Isolate* isolate)
168     : isolate_(isolate), random_number_generator_() {
169   if (FLAG_random_seed) {
170     random_number_generator_.SetSeed(FLAG_random_seed);
171   }
172 }
173 
EnqueueRequest(std::unique_ptr<v8::MeasureMemoryDelegate> delegate,v8::MeasureMemoryExecution execution,const std::vector<Handle<NativeContext>> contexts)174 bool MemoryMeasurement::EnqueueRequest(
175     std::unique_ptr<v8::MeasureMemoryDelegate> delegate,
176     v8::MeasureMemoryExecution execution,
177     const std::vector<Handle<NativeContext>> contexts) {
178   int length = static_cast<int>(contexts.size());
179   Handle<WeakFixedArray> weak_contexts =
180       isolate_->factory()->NewWeakFixedArray(length);
181   for (int i = 0; i < length; ++i) {
182     weak_contexts->Set(i, HeapObjectReference::Weak(*contexts[i]));
183   }
184   Handle<WeakFixedArray> global_weak_contexts =
185       isolate_->global_handles()->Create(*weak_contexts);
186   Request request = {std::move(delegate),
187                      global_weak_contexts,
188                      std::vector<size_t>(length),
189                      0u,
190                      {}};
191   request.timer.Start();
192   received_.push_back(std::move(request));
193   ScheduleGCTask(execution);
194   return true;
195 }
196 
StartProcessing()197 std::vector<Address> MemoryMeasurement::StartProcessing() {
198   if (received_.empty()) return {};
199   std::unordered_set<Address> unique_contexts;
200   DCHECK(processing_.empty());
201   processing_ = std::move(received_);
202   for (const auto& request : processing_) {
203     Handle<WeakFixedArray> contexts = request.contexts;
204     for (int i = 0; i < contexts->length(); i++) {
205       HeapObject context;
206       if (contexts->Get(i).GetHeapObject(&context)) {
207         unique_contexts.insert(context.ptr());
208       }
209     }
210   }
211   return std::vector<Address>(unique_contexts.begin(), unique_contexts.end());
212 }
213 
FinishProcessing(const NativeContextStats & stats)214 void MemoryMeasurement::FinishProcessing(const NativeContextStats& stats) {
215   if (processing_.empty()) return;
216 
217   while (!processing_.empty()) {
218     Request request = std::move(processing_.front());
219     processing_.pop_front();
220     for (int i = 0; i < static_cast<int>(request.sizes.size()); i++) {
221       HeapObject context;
222       if (!request.contexts->Get(i).GetHeapObject(&context)) {
223         continue;
224       }
225       request.sizes[i] = stats.Get(context.ptr());
226     }
227     request.shared = stats.Get(MarkingWorklists::kSharedContext);
228     done_.push_back(std::move(request));
229   }
230   ScheduleReportingTask();
231 }
232 
ScheduleReportingTask()233 void MemoryMeasurement::ScheduleReportingTask() {
234   if (reporting_task_pending_) return;
235   reporting_task_pending_ = true;
236   auto taskrunner = V8::GetCurrentPlatform()->GetForegroundTaskRunner(
237       reinterpret_cast<v8::Isolate*>(isolate_));
238   taskrunner->PostTask(MakeCancelableTask(isolate_, [this] {
239     reporting_task_pending_ = false;
240     ReportResults();
241   }));
242 }
243 
IsGCTaskPending(v8::MeasureMemoryExecution execution)244 bool MemoryMeasurement::IsGCTaskPending(v8::MeasureMemoryExecution execution) {
245   DCHECK(execution == v8::MeasureMemoryExecution::kEager ||
246          execution == v8::MeasureMemoryExecution::kDefault);
247   return execution == v8::MeasureMemoryExecution::kEager
248              ? eager_gc_task_pending_
249              : delayed_gc_task_pending_;
250 }
251 
SetGCTaskPending(v8::MeasureMemoryExecution execution)252 void MemoryMeasurement::SetGCTaskPending(v8::MeasureMemoryExecution execution) {
253   DCHECK(execution == v8::MeasureMemoryExecution::kEager ||
254          execution == v8::MeasureMemoryExecution::kDefault);
255   if (execution == v8::MeasureMemoryExecution::kEager) {
256     eager_gc_task_pending_ = true;
257   } else {
258     delayed_gc_task_pending_ = true;
259   }
260 }
261 
SetGCTaskDone(v8::MeasureMemoryExecution execution)262 void MemoryMeasurement::SetGCTaskDone(v8::MeasureMemoryExecution execution) {
263   DCHECK(execution == v8::MeasureMemoryExecution::kEager ||
264          execution == v8::MeasureMemoryExecution::kDefault);
265   if (execution == v8::MeasureMemoryExecution::kEager) {
266     eager_gc_task_pending_ = false;
267   } else {
268     delayed_gc_task_pending_ = false;
269   }
270 }
271 
ScheduleGCTask(v8::MeasureMemoryExecution execution)272 void MemoryMeasurement::ScheduleGCTask(v8::MeasureMemoryExecution execution) {
273   if (execution == v8::MeasureMemoryExecution::kLazy) return;
274   if (IsGCTaskPending(execution)) return;
275   SetGCTaskPending(execution);
276   auto taskrunner = V8::GetCurrentPlatform()->GetForegroundTaskRunner(
277       reinterpret_cast<v8::Isolate*>(isolate_));
278   auto task = MakeCancelableTask(isolate_, [this, execution] {
279     SetGCTaskDone(execution);
280     if (received_.empty()) return;
281     Heap* heap = isolate_->heap();
282     if (FLAG_incremental_marking) {
283       if (heap->incremental_marking()->IsStopped()) {
284         heap->StartIncrementalMarking(Heap::kNoGCFlags,
285                                       GarbageCollectionReason::kMeasureMemory);
286       } else {
287         if (execution == v8::MeasureMemoryExecution::kEager) {
288           heap->FinalizeIncrementalMarkingAtomically(
289               GarbageCollectionReason::kMeasureMemory);
290         }
291         ScheduleGCTask(execution);
292       }
293     } else {
294       heap->CollectGarbage(OLD_SPACE, GarbageCollectionReason::kMeasureMemory);
295     }
296   });
297   if (execution == v8::MeasureMemoryExecution::kEager) {
298     taskrunner->PostTask(std::move(task));
299   } else {
300     taskrunner->PostDelayedTask(std::move(task), NextGCTaskDelayInSeconds());
301   }
302 }
303 
NextGCTaskDelayInSeconds()304 int MemoryMeasurement::NextGCTaskDelayInSeconds() {
305   return kGCTaskDelayInSeconds +
306          random_number_generator_.NextInt(kGCTaskDelayInSeconds);
307 }
308 
ReportResults()309 void MemoryMeasurement::ReportResults() {
310   while (!done_.empty()) {
311     Request request = std::move(done_.front());
312     done_.pop_front();
313     HandleScope handle_scope(isolate_);
314     std::vector<std::pair<v8::Local<v8::Context>, size_t>> sizes;
315     DCHECK_EQ(request.sizes.size(),
316               static_cast<size_t>(request.contexts->length()));
317     for (int i = 0; i < request.contexts->length(); i++) {
318       HeapObject raw_context;
319       if (!request.contexts->Get(i).GetHeapObject(&raw_context)) {
320         continue;
321       }
322       v8::Local<v8::Context> context = Utils::Convert<HeapObject, v8::Context>(
323           handle(raw_context, isolate_));
324       sizes.push_back(std::make_pair(context, request.sizes[i]));
325     }
326     request.delegate->MeasurementComplete(sizes, request.shared);
327     isolate_->counters()->measure_memory_delay_ms()->AddSample(
328         static_cast<int>(request.timer.Elapsed().InMilliseconds()));
329   }
330 }
331 
DefaultDelegate(Isolate * isolate,Handle<NativeContext> context,Handle<JSPromise> promise,v8::MeasureMemoryMode mode)332 std::unique_ptr<v8::MeasureMemoryDelegate> MemoryMeasurement::DefaultDelegate(
333     Isolate* isolate, Handle<NativeContext> context, Handle<JSPromise> promise,
334     v8::MeasureMemoryMode mode) {
335   return std::make_unique<MeasureMemoryDelegate>(isolate, context, promise,
336                                                  mode);
337 }
338 
InferForContext(Isolate * isolate,Context context,Address * native_context)339 bool NativeContextInferrer::InferForContext(Isolate* isolate, Context context,
340                                             Address* native_context) {
341   PtrComprCageBase cage_base(isolate);
342   Map context_map = context.map(cage_base, kAcquireLoad);
343   Object maybe_native_context =
344       TaggedField<Object, Map::kConstructorOrBackPointerOrNativeContextOffset>::
345           Acquire_Load(cage_base, context_map);
346   if (maybe_native_context.IsNativeContext(cage_base)) {
347     *native_context = maybe_native_context.ptr();
348     return true;
349   }
350   return false;
351 }
352 
InferForJSFunction(Isolate * isolate,JSFunction function,Address * native_context)353 bool NativeContextInferrer::InferForJSFunction(Isolate* isolate,
354                                                JSFunction function,
355                                                Address* native_context) {
356   Object maybe_context =
357       TaggedField<Object, JSFunction::kContextOffset>::Acquire_Load(isolate,
358                                                                     function);
359   // The context may be a smi during deserialization.
360   if (maybe_context.IsSmi()) {
361     DCHECK_EQ(maybe_context, Smi::uninitialized_deserialization_value());
362     return false;
363   }
364   if (!maybe_context.IsContext()) {
365     // The function does not have a context.
366     return false;
367   }
368   return InferForContext(isolate, Context::cast(maybe_context), native_context);
369 }
370 
InferForJSObject(Isolate * isolate,Map map,JSObject object,Address * native_context)371 bool NativeContextInferrer::InferForJSObject(Isolate* isolate, Map map,
372                                              JSObject object,
373                                              Address* native_context) {
374   if (map.instance_type() == JS_GLOBAL_OBJECT_TYPE) {
375     Object maybe_context =
376         JSGlobalObject::cast(object).native_context_unchecked(isolate);
377     if (maybe_context.IsNativeContext()) {
378       *native_context = maybe_context.ptr();
379       return true;
380     }
381   }
382   // The maximum number of steps to perform when looking for the context.
383   const int kMaxSteps = 3;
384   Object maybe_constructor = map.TryGetConstructor(isolate, kMaxSteps);
385   if (maybe_constructor.IsJSFunction()) {
386     return InferForJSFunction(isolate, JSFunction::cast(maybe_constructor),
387                               native_context);
388   }
389   return false;
390 }
391 
Clear()392 void NativeContextStats::Clear() { size_by_context_.clear(); }
393 
Merge(const NativeContextStats & other)394 void NativeContextStats::Merge(const NativeContextStats& other) {
395   for (const auto& it : other.size_by_context_) {
396     size_by_context_[it.first] += it.second;
397   }
398 }
399 
IncrementExternalSize(Address context,Map map,HeapObject object)400 void NativeContextStats::IncrementExternalSize(Address context, Map map,
401                                                HeapObject object) {
402   InstanceType instance_type = map.instance_type();
403   size_t external_size = 0;
404   if (instance_type == JS_ARRAY_BUFFER_TYPE) {
405     external_size = JSArrayBuffer::cast(object).GetByteLength();
406   } else {
407     DCHECK(InstanceTypeChecker::IsExternalString(instance_type));
408     external_size = ExternalString::cast(object).ExternalPayloadSize();
409   }
410   size_by_context_[context] += external_size;
411 }
412 
413 }  // namespace internal
414 }  // namespace v8
415