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.h"
8 #include "src/api/api-inl.h"
9 #include "src/api/api.h"
10 #include "src/execution/isolate-inl.h"
11 #include "src/execution/isolate.h"
12 #include "src/heap/factory-inl.h"
13 #include "src/heap/factory.h"
14 #include "src/heap/incremental-marking.h"
15 #include "src/heap/marking-worklist.h"
16 #include "src/logging/counters.h"
17 #include "src/objects/js-promise-inl.h"
18 #include "src/objects/js-promise.h"
19 #include "src/tasks/task-utils.h"
20 
21 namespace v8 {
22 namespace internal {
23 
24 namespace {
25 class MemoryMeasurementResultBuilder {
26  public:
MemoryMeasurementResultBuilder(Isolate * isolate,Factory * factory)27   MemoryMeasurementResultBuilder(Isolate* isolate, Factory* factory)
28       : isolate_(isolate), factory_(factory) {
29     result_ = NewJSObject();
30   }
AddTotal(size_t estimate,size_t lower_bound,size_t upper_bound)31   void AddTotal(size_t estimate, size_t lower_bound, size_t upper_bound) {
32     AddProperty(result_, factory_->total_string(),
33                 NewResult(estimate, lower_bound, upper_bound));
34   }
AddCurrent(size_t estimate,size_t lower_bound,size_t upper_bound)35   void AddCurrent(size_t estimate, size_t lower_bound, size_t upper_bound) {
36     detailed_ = true;
37     AddProperty(result_, factory_->current_string(),
38                 NewResult(estimate, lower_bound, upper_bound));
39   }
AddOther(size_t estimate,size_t lower_bound,size_t upper_bound)40   void AddOther(size_t estimate, size_t lower_bound, size_t upper_bound) {
41     detailed_ = true;
42     other_.push_back(NewResult(estimate, lower_bound, upper_bound));
43   }
Build()44   Handle<JSObject> Build() {
45     if (detailed_) {
46       int length = static_cast<int>(other_.size());
47       Handle<FixedArray> other = factory_->NewFixedArray(length);
48       for (int i = 0; i < length; i++) {
49         other->set(i, *other_[i]);
50       }
51       AddProperty(result_, factory_->other_string(),
52                   factory_->NewJSArrayWithElements(other));
53     }
54     return result_;
55   }
56 
57  private:
NewResult(size_t estimate,size_t lower_bound,size_t upper_bound)58   Handle<JSObject> NewResult(size_t estimate, size_t lower_bound,
59                              size_t upper_bound) {
60     Handle<JSObject> result = NewJSObject();
61     Handle<Object> estimate_obj = NewNumber(estimate);
62     AddProperty(result, factory_->jsMemoryEstimate_string(), estimate_obj);
63     Handle<Object> range = NewRange(lower_bound, upper_bound);
64     AddProperty(result, factory_->jsMemoryRange_string(), range);
65     return result;
66   }
NewNumber(size_t value)67   Handle<Object> NewNumber(size_t value) {
68     return factory_->NewNumberFromSize(value);
69   }
NewJSObject()70   Handle<JSObject> NewJSObject() {
71     return factory_->NewJSObject(isolate_->object_function());
72   }
NewRange(size_t lower_bound,size_t upper_bound)73   Handle<JSArray> NewRange(size_t lower_bound, size_t upper_bound) {
74     Handle<Object> lower = NewNumber(lower_bound);
75     Handle<Object> upper = NewNumber(upper_bound);
76     Handle<FixedArray> elements = factory_->NewFixedArray(2);
77     elements->set(0, *lower);
78     elements->set(1, *upper);
79     return factory_->NewJSArrayWithElements(elements);
80   }
AddProperty(Handle<JSObject> object,Handle<String> name,Handle<Object> value)81   void AddProperty(Handle<JSObject> object, Handle<String> name,
82                    Handle<Object> value) {
83     JSObject::AddProperty(isolate_, object, name, value, NONE);
84   }
85   Isolate* isolate_;
86   Factory* factory_;
87   Handle<JSObject> result_;
88   std::vector<Handle<JSObject>> other_;
89   bool detailed_ = false;
90 };
91 }  // anonymous namespace
92 
93 class V8_EXPORT_PRIVATE MeasureMemoryDelegate
94     : public v8::MeasureMemoryDelegate {
95  public:
96   MeasureMemoryDelegate(Isolate* isolate, Handle<NativeContext> context,
97                         Handle<JSPromise> promise, v8::MeasureMemoryMode mode);
98   ~MeasureMemoryDelegate() override;
99 
100   // v8::MeasureMemoryDelegate overrides:
101   bool ShouldMeasure(v8::Local<v8::Context> context) override;
102   void MeasurementComplete(
103       const std::vector<std::pair<v8::Local<v8::Context>, size_t>>&
104           context_sizes_in_bytes,
105       size_t unattributed_size_in_bytes) override;
106 
107  private:
108   Isolate* isolate_;
109   Handle<JSPromise> promise_;
110   Handle<NativeContext> context_;
111   v8::MeasureMemoryMode mode_;
112 };
113 
MeasureMemoryDelegate(Isolate * isolate,Handle<NativeContext> context,Handle<JSPromise> promise,v8::MeasureMemoryMode mode)114 MeasureMemoryDelegate::MeasureMemoryDelegate(Isolate* isolate,
115                                              Handle<NativeContext> context,
116                                              Handle<JSPromise> promise,
117                                              v8::MeasureMemoryMode mode)
118     : isolate_(isolate), mode_(mode) {
119   context_ = isolate->global_handles()->Create(*context);
120   promise_ = isolate->global_handles()->Create(*promise);
121 }
122 
~MeasureMemoryDelegate()123 MeasureMemoryDelegate::~MeasureMemoryDelegate() {
124   isolate_->global_handles()->Destroy(promise_.location());
125   isolate_->global_handles()->Destroy(context_.location());
126 }
127 
ShouldMeasure(v8::Local<v8::Context> context)128 bool MeasureMemoryDelegate::ShouldMeasure(v8::Local<v8::Context> context) {
129   Handle<NativeContext> native_context =
130       Handle<NativeContext>::cast(Utils::OpenHandle(*context));
131   return context_->security_token() == native_context->security_token();
132 }
133 
MeasurementComplete(const std::vector<std::pair<v8::Local<v8::Context>,size_t>> & context_sizes_in_bytes,size_t shared_size)134 void MeasureMemoryDelegate::MeasurementComplete(
135     const std::vector<std::pair<v8::Local<v8::Context>, size_t>>&
136         context_sizes_in_bytes,
137     size_t shared_size) {
138   v8::Local<v8::Context> v8_context =
139       Utils::Convert<HeapObject, v8::Context>(context_);
140   v8::Context::Scope scope(v8_context);
141   size_t total_size = 0;
142   size_t current_size = 0;
143   for (const auto& context_and_size : context_sizes_in_bytes) {
144     total_size += context_and_size.second;
145     if (*Utils::OpenHandle(*context_and_size.first) == *context_) {
146       current_size = context_and_size.second;
147     }
148   }
149   MemoryMeasurementResultBuilder result_builder(isolate_, isolate_->factory());
150   result_builder.AddTotal(total_size, total_size, total_size + shared_size);
151 
152   if (mode_ == v8::MeasureMemoryMode::kDetailed) {
153     result_builder.AddCurrent(current_size, current_size,
154                               current_size + shared_size);
155     for (const auto& context_and_size : context_sizes_in_bytes) {
156       if (*Utils::OpenHandle(*context_and_size.first) != *context_) {
157         size_t other_size = context_and_size.second;
158         result_builder.AddOther(other_size, other_size,
159                                 other_size + shared_size);
160       }
161     }
162   }
163 
164   Handle<JSObject> result = result_builder.Build();
165   JSPromise::Resolve(promise_, result).ToHandleChecked();
166 }
167 
MemoryMeasurement(Isolate * isolate)168 MemoryMeasurement::MemoryMeasurement(Isolate* isolate)
169     : isolate_(isolate), random_number_generator_() {
170   if (FLAG_random_seed) {
171     random_number_generator_.SetSeed(FLAG_random_seed);
172   }
173 }
174 
EnqueueRequest(std::unique_ptr<v8::MeasureMemoryDelegate> delegate,v8::MeasureMemoryExecution execution,const std::vector<Handle<NativeContext>> contexts)175 bool MemoryMeasurement::EnqueueRequest(
176     std::unique_ptr<v8::MeasureMemoryDelegate> delegate,
177     v8::MeasureMemoryExecution execution,
178     const std::vector<Handle<NativeContext>> contexts) {
179   int length = static_cast<int>(contexts.size());
180   Handle<WeakFixedArray> weak_contexts =
181       isolate_->factory()->NewWeakFixedArray(length);
182   for (int i = 0; i < length; ++i) {
183     weak_contexts->Set(i, HeapObjectReference::Weak(*contexts[i]));
184   }
185   Handle<WeakFixedArray> global_weak_contexts =
186       isolate_->global_handles()->Create(*weak_contexts);
187   Request request = {std::move(delegate),
188                      global_weak_contexts,
189                      std::vector<size_t>(length),
190                      0u,
191                      {}};
192   request.timer.Start();
193   received_.push_back(std::move(request));
194   ScheduleGCTask(execution);
195   return true;
196 }
197 
StartProcessing()198 std::vector<Address> MemoryMeasurement::StartProcessing() {
199   if (received_.empty()) return {};
200   std::unordered_set<Address> unique_contexts;
201   DCHECK(processing_.empty());
202   processing_ = std::move(received_);
203   for (const auto& request : processing_) {
204     Handle<WeakFixedArray> contexts = request.contexts;
205     for (int i = 0; i < contexts->length(); i++) {
206       HeapObject context;
207       if (contexts->Get(i).GetHeapObject(&context)) {
208         unique_contexts.insert(context.ptr());
209       }
210     }
211   }
212   return std::vector<Address>(unique_contexts.begin(), unique_contexts.end());
213 }
214 
FinishProcessing(const NativeContextStats & stats)215 void MemoryMeasurement::FinishProcessing(const NativeContextStats& stats) {
216   if (processing_.empty()) return;
217 
218   while (!processing_.empty()) {
219     Request request = std::move(processing_.front());
220     processing_.pop_front();
221     for (int i = 0; i < static_cast<int>(request.sizes.size()); i++) {
222       HeapObject context;
223       if (!request.contexts->Get(i).GetHeapObject(&context)) {
224         continue;
225       }
226       request.sizes[i] = stats.Get(context.ptr());
227     }
228     request.shared = stats.Get(MarkingWorklists::kSharedContext);
229     done_.push_back(std::move(request));
230   }
231   ScheduleReportingTask();
232 }
233 
ScheduleReportingTask()234 void MemoryMeasurement::ScheduleReportingTask() {
235   if (reporting_task_pending_) return;
236   reporting_task_pending_ = true;
237   auto taskrunner = V8::GetCurrentPlatform()->GetForegroundTaskRunner(
238       reinterpret_cast<v8::Isolate*>(isolate_));
239   taskrunner->PostTask(MakeCancelableTask(isolate_, [this] {
240     reporting_task_pending_ = false;
241     ReportResults();
242   }));
243 }
244 
IsGCTaskPending(v8::MeasureMemoryExecution execution)245 bool MemoryMeasurement::IsGCTaskPending(v8::MeasureMemoryExecution execution) {
246   DCHECK(execution == v8::MeasureMemoryExecution::kEager ||
247          execution == v8::MeasureMemoryExecution::kDefault);
248   return execution == v8::MeasureMemoryExecution::kEager
249              ? eager_gc_task_pending_
250              : delayed_gc_task_pending_;
251 }
252 
SetGCTaskPending(v8::MeasureMemoryExecution execution)253 void MemoryMeasurement::SetGCTaskPending(v8::MeasureMemoryExecution execution) {
254   DCHECK(execution == v8::MeasureMemoryExecution::kEager ||
255          execution == v8::MeasureMemoryExecution::kDefault);
256   if (execution == v8::MeasureMemoryExecution::kEager) {
257     eager_gc_task_pending_ = true;
258   } else {
259     delayed_gc_task_pending_ = true;
260   }
261 }
262 
SetGCTaskDone(v8::MeasureMemoryExecution execution)263 void MemoryMeasurement::SetGCTaskDone(v8::MeasureMemoryExecution execution) {
264   DCHECK(execution == v8::MeasureMemoryExecution::kEager ||
265          execution == v8::MeasureMemoryExecution::kDefault);
266   if (execution == v8::MeasureMemoryExecution::kEager) {
267     eager_gc_task_pending_ = false;
268   } else {
269     delayed_gc_task_pending_ = false;
270   }
271 }
272 
ScheduleGCTask(v8::MeasureMemoryExecution execution)273 void MemoryMeasurement::ScheduleGCTask(v8::MeasureMemoryExecution execution) {
274   if (execution == v8::MeasureMemoryExecution::kLazy) return;
275   if (IsGCTaskPending(execution)) return;
276   SetGCTaskPending(execution);
277   auto taskrunner = V8::GetCurrentPlatform()->GetForegroundTaskRunner(
278       reinterpret_cast<v8::Isolate*>(isolate_));
279   auto task = MakeCancelableTask(isolate_, [this, execution] {
280     SetGCTaskDone(execution);
281     if (received_.empty()) return;
282     Heap* heap = isolate_->heap();
283     if (FLAG_incremental_marking) {
284       if (heap->incremental_marking()->IsStopped()) {
285         heap->StartIncrementalMarking(Heap::kNoGCFlags,
286                                       GarbageCollectionReason::kMeasureMemory);
287       } else {
288         if (execution == v8::MeasureMemoryExecution::kEager) {
289           heap->FinalizeIncrementalMarkingAtomically(
290               GarbageCollectionReason::kMeasureMemory);
291         }
292         ScheduleGCTask(execution);
293       }
294     } else {
295       heap->CollectGarbage(OLD_SPACE, GarbageCollectionReason::kMeasureMemory);
296     }
297   });
298   if (execution == v8::MeasureMemoryExecution::kEager) {
299     taskrunner->PostTask(std::move(task));
300   } else {
301     taskrunner->PostDelayedTask(std::move(task), NextGCTaskDelayInSeconds());
302   }
303 }
304 
NextGCTaskDelayInSeconds()305 int MemoryMeasurement::NextGCTaskDelayInSeconds() {
306   return kGCTaskDelayInSeconds +
307          random_number_generator_.NextInt(kGCTaskDelayInSeconds);
308 }
309 
ReportResults()310 void MemoryMeasurement::ReportResults() {
311   while (!done_.empty()) {
312     Request request = std::move(done_.front());
313     done_.pop_front();
314     HandleScope handle_scope(isolate_);
315     std::vector<std::pair<v8::Local<v8::Context>, size_t>> sizes;
316     DCHECK_EQ(request.sizes.size(),
317               static_cast<size_t>(request.contexts->length()));
318     for (int i = 0; i < request.contexts->length(); i++) {
319       HeapObject raw_context;
320       if (!request.contexts->Get(i).GetHeapObject(&raw_context)) {
321         continue;
322       }
323       v8::Local<v8::Context> context = Utils::Convert<HeapObject, v8::Context>(
324           handle(raw_context, isolate_));
325       sizes.push_back(std::make_pair(context, request.sizes[i]));
326     }
327     request.delegate->MeasurementComplete(sizes, request.shared);
328     isolate_->counters()->measure_memory_delay_ms()->AddSample(
329         static_cast<int>(request.timer.Elapsed().InMilliseconds()));
330   }
331 }
332 
DefaultDelegate(Isolate * isolate,Handle<NativeContext> context,Handle<JSPromise> promise,v8::MeasureMemoryMode mode)333 std::unique_ptr<v8::MeasureMemoryDelegate> MemoryMeasurement::DefaultDelegate(
334     Isolate* isolate, Handle<NativeContext> context, Handle<JSPromise> promise,
335     v8::MeasureMemoryMode mode) {
336   return std::make_unique<MeasureMemoryDelegate>(isolate, context, promise,
337                                                  mode);
338 }
339 
InferForContext(Isolate * isolate,Context context,Address * native_context)340 bool NativeContextInferrer::InferForContext(Isolate* isolate, Context context,
341                                             Address* native_context) {
342   Map context_map = context.synchronized_map();
343   Object maybe_native_context =
344       TaggedField<Object, Map::kConstructorOrBackPointerOrNativeContextOffset>::
345           Acquire_Load(isolate, context_map);
346   if (maybe_native_context.IsNativeContext()) {
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, Deserializer::uninitialized_field_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).allocation_length();
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