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