blob: da92fe51e652fd9058a5ca2464e781c6aed3efdf [file] [log] [blame]
// Copyright 2020 The Chromium 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 "third_party/blink/renderer/controller/performance_manager/v8_detailed_memory_reporter_impl.h"
#include <memory>
#include <utility>
#include <vector>
#include "base/callback.h"
#include "base/check.h"
#include "base/memory/scoped_refptr.h"
#include "base/notreached.h"
#include "mojo/public/cpp/bindings/self_owned_receiver.h"
#include "third_party/blink/public/common/tokens/tokens.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_binding_for_core.h"
#include "third_party/blink/renderer/controller/performance_manager/v8_worker_memory_reporter.h"
#include "third_party/blink/renderer/core/frame/local_dom_window.h"
#include "third_party/blink/renderer/core/frame/local_frame.h"
#include "third_party/blink/renderer/platform/bindings/dom_wrapper_world.h"
#include "third_party/blink/renderer/platform/heap/thread_state.h"
#include "third_party/blink/renderer/platform/wtf/functional.h"
#include "third_party/blink/renderer/platform/wtf/ref_counted.h"
#include "v8/include/v8.h"
namespace blink {
namespace {
class FrameAssociatedMeasurementDelegate : public v8::MeasureMemoryDelegate {
public:
using ResultCallback =
base::OnceCallback<void(mojom::blink::PerIsolateV8MemoryUsagePtr)>;
explicit FrameAssociatedMeasurementDelegate(ResultCallback&& callback)
: callback_(std::move(callback)) {}
~FrameAssociatedMeasurementDelegate() override {
if (callback_) {
std::move(callback_).Run(mojom::blink::PerIsolateV8MemoryUsage::New());
}
}
private:
bool ShouldMeasure(v8::Local<v8::Context> context) override {
// Measure all contexts.
return true;
}
void MeasurementComplete(
const std::vector<std::pair<v8::Local<v8::Context>, size_t>>&
context_sizes_in_bytes,
size_t unattributed_size_in_bytes) override {
DCHECK(IsMainThread());
mojom::blink::PerIsolateV8MemoryUsagePtr isolate_memory_usage =
mojom::blink::PerIsolateV8MemoryUsage::New();
for (const auto& context_and_size : context_sizes_in_bytes) {
const v8::Local<v8::Context>& context = context_and_size.first;
const size_t size = context_and_size.second;
LocalFrame* frame = ToLocalFrameIfNotDetached(context);
if (!frame) {
// TODO(crbug.com/1080672): It would be prefereable to count the
// V8SchemaRegistry context's overhead with unassociated_bytes, but at
// present there isn't a public API that allows this distinction.
++(isolate_memory_usage->num_detached_contexts);
isolate_memory_usage->detached_bytes_used += size;
continue;
}
if (DOMWrapperWorld::World(context).GetWorldId() !=
DOMWrapperWorld::kMainWorldId) {
// TODO(crbug.com/1085129): Handle extension contexts once they get
// their own V8ContextToken.
continue;
}
auto context_memory_usage = mojom::blink::PerContextV8MemoryUsage::New();
context_memory_usage->token =
frame->DomWindow()->GetExecutionContextToken();
context_memory_usage->bytes_used = size;
#if DCHECK_IS_ON()
// Check that the token didn't already occur.
for (const auto& entry : isolate_memory_usage->contexts) {
DCHECK_NE(entry->token, context_memory_usage->token);
}
#endif
isolate_memory_usage->contexts.push_back(std::move(context_memory_usage));
}
isolate_memory_usage->shared_bytes_used = unattributed_size_in_bytes;
std::move(callback_).Run(std::move(isolate_memory_usage));
}
private:
ResultCallback callback_;
};
v8::MeasureMemoryExecution ToV8MeasureMemoryExecution(
V8DetailedMemoryReporterImpl::Mode mode) {
switch (mode) {
case V8DetailedMemoryReporterImpl::Mode::DEFAULT:
return v8::MeasureMemoryExecution::kDefault;
case V8DetailedMemoryReporterImpl::Mode::EAGER:
return v8::MeasureMemoryExecution::kEager;
case V8DetailedMemoryReporterImpl::Mode::LAZY:
return v8::MeasureMemoryExecution::kLazy;
}
NOTREACHED();
}
ExecutionContextToken ToExecutionContextToken(WorkerToken token) {
if (token.Is<DedicatedWorkerToken>())
return ExecutionContextToken(token.GetAs<DedicatedWorkerToken>());
if (token.Is<SharedWorkerToken>())
return ExecutionContextToken(token.GetAs<SharedWorkerToken>());
return ExecutionContextToken(token.GetAs<ServiceWorkerToken>());
}
// A helper class that runs two async functions, combines their
// results, and invokes the given callback. The async functions are:
// - v8::Isolate::MeasureMemory - for the main V8 isolate.
// - V8WorkerMemoryReporter::GetMemoryUsage - for all worker isolates.
class V8ProcessMemoryReporter : public RefCounted<V8ProcessMemoryReporter> {
public:
using GetV8MemoryUsageCallback =
mojom::blink::V8DetailedMemoryReporter::GetV8MemoryUsageCallback;
explicit V8ProcessMemoryReporter(GetV8MemoryUsageCallback&& callback)
: callback_(std::move(callback)),
result_(mojom::blink::PerProcessV8MemoryUsage::New()) {}
void StartMeasurements(V8DetailedMemoryReporterImpl::Mode mode) {
DCHECK(IsMainThread());
v8::Isolate* isolate = v8::Isolate::GetCurrent();
// 1. Start measurement of the main V8 isolate.
if (!isolate) {
// This can happen in tests that do not set up the main V8 isolate
// or during setup/teardown of the process.
MainMeasurementComplete(mojom::blink::PerIsolateV8MemoryUsage::New());
} else {
auto delegate = std::make_unique<FrameAssociatedMeasurementDelegate>(
WTF::Bind(&V8ProcessMemoryReporter::MainV8MeasurementComplete,
scoped_refptr<V8ProcessMemoryReporter>(this)));
isolate->MeasureMemory(std::move(delegate),
ToV8MeasureMemoryExecution(mode));
}
// 2. Start measurement of all worker isolates.
V8WorkerMemoryReporter::GetMemoryUsage(
WTF::Bind(&V8ProcessMemoryReporter::WorkerMeasurementComplete,
scoped_refptr<V8ProcessMemoryReporter>(this)),
ToV8MeasureMemoryExecution(mode));
}
private:
void MainV8MeasurementComplete(
mojom::blink::PerIsolateV8MemoryUsagePtr isolate_memory_usage) {
// At this point measurement of the main V8 isolate is done and we
// can measure the corresponding Blink memory. Note that the order
// of the measurements is important because the V8 measurement does
// a GC and we want to get the Blink memory after the GC.
// This function and V8ProcessMemoryReporter::StartMeasurements both
// run on the main thread of the renderer. This means that the Blink
// heap given by ThreadState::Current() is attached to the main V8
// isolate given by v8::Isolate::GetCurrent().
ThreadState::Current()->CollectNodeAndCssStatistics(
WTF::Bind(&V8ProcessMemoryReporter::MainBlinkMeasurementComplete,
scoped_refptr<V8ProcessMemoryReporter>(this),
std::move(isolate_memory_usage)));
}
void MainBlinkMeasurementComplete(
mojom::blink::PerIsolateV8MemoryUsagePtr isolate_memory_usage,
size_t node_bytes,
size_t css_bytes) {
isolate_memory_usage->blink_bytes_used = node_bytes + css_bytes;
MainMeasurementComplete(std::move(isolate_memory_usage));
}
void MainMeasurementComplete(
mojom::blink::PerIsolateV8MemoryUsagePtr isolate_memory_usage) {
result_->isolates.push_back(std::move(isolate_memory_usage));
main_measurement_done_ = true;
MaybeInvokeCallback();
}
void WorkerMeasurementComplete(const V8WorkerMemoryReporter::Result& result) {
for (auto& worker : result.workers) {
auto worker_memory_usage = mojom::blink::PerIsolateV8MemoryUsage::New();
auto context_memory_usage = mojom::blink::PerContextV8MemoryUsage::New();
context_memory_usage->token = ToExecutionContextToken(worker.token);
context_memory_usage->bytes_used = worker.bytes;
if (!worker.url.IsNull()) {
context_memory_usage->url = worker.url.GetString();
}
worker_memory_usage->contexts.push_back(std::move(context_memory_usage));
result_->isolates.push_back(std::move(worker_memory_usage));
}
worker_measurement_done_ = true;
MaybeInvokeCallback();
}
void MaybeInvokeCallback() {
if (!main_measurement_done_ || !worker_measurement_done_)
return;
std::move(callback_).Run(std::move(result_));
}
GetV8MemoryUsageCallback callback_;
mojom::blink::PerProcessV8MemoryUsagePtr result_;
bool main_measurement_done_ = false;
bool worker_measurement_done_ = false;
};
} // namespace
// static
void V8DetailedMemoryReporterImpl::Create(
mojo::PendingReceiver<mojom::blink::V8DetailedMemoryReporter> receiver) {
mojo::MakeSelfOwnedReceiver(std::make_unique<V8DetailedMemoryReporterImpl>(),
std::move(receiver));
}
void V8DetailedMemoryReporterImpl::GetV8MemoryUsage(
V8DetailedMemoryReporterImpl::Mode mode,
GetV8MemoryUsageCallback callback) {
auto v8_process_memory_reporter =
base::MakeRefCounted<V8ProcessMemoryReporter>(std::move(callback));
// Start async measurements. The lifetime of the reporter is extended
// using more shared pointers until the measuremnts complete.
v8_process_memory_reporter->StartMeasurements(mode);
}
} // namespace blink