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