blob: 3759664a80307881267a8a25aed1f9187b9f4709 [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/core/timing/measure_memory/measure_memory_controller.h"
#include <algorithm>
#include "base/rand_util.h"
#include "components/performance_manager/public/mojom/coordination_unit.mojom-blink.h"
#include "third_party/blink/public/common/features.h"
#include "third_party/blink/public/platform/platform.h"
#include "third_party/blink/renderer/bindings/core/v8/script_promise.h"
#include "third_party/blink/renderer/bindings/core/v8/to_v8_for_core.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_memory_attribution.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_memory_attribution_container.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_memory_breakdown_entry.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_memory_measurement.h"
#include "third_party/blink/renderer/core/execution_context/execution_context.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/core/frame/settings.h"
#include "third_party/blink/renderer/platform/bindings/exception_state.h"
#include "third_party/blink/renderer/platform/bindings/script_state.h"
#include "third_party/blink/renderer/platform/heap/heap.h"
#include "third_party/blink/renderer/platform/heap/heap_allocator.h"
#include "third_party/blink/renderer/platform/heap/member.h"
#include "third_party/blink/renderer/platform/heap/persistent.h"
#include "third_party/blink/renderer/platform/instrumentation/resource_coordinator/document_resource_coordinator.h"
#include "third_party/blink/renderer/platform/runtime_enabled_features.h"
#include "third_party/blink/renderer/platform/wtf/functional.h"
#include "v8/include/v8.h"
using performance_manager::mojom::blink::WebMemoryAttribution;
using performance_manager::mojom::blink::WebMemoryAttributionPtr;
using performance_manager::mojom::blink::WebMemoryBreakdownEntryPtr;
using performance_manager::mojom::blink::WebMemoryMeasurement;
using performance_manager::mojom::blink::WebMemoryMeasurementPtr;
using performance_manager::mojom::blink::WebMemoryUsagePtr;
namespace blink {
namespace {
// String constants used for building the result.
constexpr const char* kCrossOriginUrl = "cross-origin-url";
constexpr const char* kMemoryTypeDom = "DOM";
constexpr const char* kMemoryTypeJavaScript = "JavaScript";
constexpr const char* kMemoryTypeShared = "Shared";
constexpr const char* kScopeCrossOriginAggregated = "cross-origin-aggregated";
constexpr const char* kScopeDedicatedWorker = "DedicatedWorker";
constexpr const char* kScopeWindow = "Window";
} // anonymous namespace
MeasureMemoryController::MeasureMemoryController(
base::PassKey<MeasureMemoryController>,
v8::Isolate* isolate,
v8::Local<v8::Context> context,
v8::Local<v8::Promise::Resolver> promise_resolver)
: isolate_(isolate),
context_(isolate, context),
promise_resolver_(isolate, promise_resolver) {
context_.SetPhantom();
// TODO(ulan): Currently we keep a strong reference to the promise resolver.
// This may prolong the lifetime of the context by one more GC in the worst
// case as JSPromise keeps its context alive.
// To avoid that we should use an ephemeron context_ => promise_resolver_.
}
void MeasureMemoryController::Trace(Visitor* visitor) const {
visitor->Trace(promise_resolver_);
}
namespace {
enum class ApiStatus {
kAvailable,
kNotAvailableDueToFlag,
kNotAvailableDueToDetachedContext,
kNotAvailableDueToCrossOriginContext,
kNotAvailableDueToCrossOriginIsolation,
kNotAvailableDueToResourceCoordinator,
};
ApiStatus CheckMeasureMemoryAvailability(LocalDOMWindow* window) {
if (!base::FeatureList::IsEnabled(
features::kWebMeasureMemoryViaPerformanceManager)) {
return ApiStatus::kNotAvailableDueToFlag;
}
if (!window) {
return ApiStatus::kNotAvailableDueToDetachedContext;
}
LocalFrame* local_frame = window->GetFrame();
if (!local_frame) {
return ApiStatus::kNotAvailableDueToDetachedContext;
}
if (!window->CrossOriginIsolatedCapability() &&
local_frame->GetSettings()->GetWebSecurityEnabled()) {
return ApiStatus::kNotAvailableDueToCrossOriginIsolation;
}
// We need DocumentResourceCoordinator to query PerformanceManager.
if (!window->document()) {
return ApiStatus::kNotAvailableDueToDetachedContext;
}
if (!window->document()->GetResourceCoordinator()) {
return ApiStatus::kNotAvailableDueToResourceCoordinator;
}
return ApiStatus::kAvailable;
}
} // anonymous namespace
ScriptPromise MeasureMemoryController::StartMeasurement(
ScriptState* script_state,
ExceptionState& exception_state) {
switch (auto status = CheckMeasureMemoryAvailability(
LocalDOMWindow::From(script_state))) {
case ApiStatus::kAvailable:
break;
case ApiStatus::kNotAvailableDueToFlag:
case ApiStatus::kNotAvailableDueToResourceCoordinator:
exception_state.ThrowSecurityError(
"performance.measureUserAgentSpecificMemory is not available.");
return ScriptPromise();
case ApiStatus::kNotAvailableDueToDetachedContext:
exception_state.ThrowSecurityError(
"performance.measureUserAgentSpecificMemory is not supported"
" in detached iframes.");
return ScriptPromise();
case ApiStatus::kNotAvailableDueToCrossOriginContext:
exception_state.ThrowSecurityError(
"performance.measureUserAgentSpecificMemory is not supported"
" in cross-origin iframes.");
return ScriptPromise();
case ApiStatus::kNotAvailableDueToCrossOriginIsolation:
exception_state.ThrowSecurityError(
"performance.measureUserAgentSpecificMemory requires"
" cross-origin isolation.");
return ScriptPromise();
}
v8::Isolate* isolate = script_state->GetIsolate();
v8::TryCatch try_catch(isolate);
v8::Local<v8::Context> context = script_state->GetContext();
v8::Local<v8::Promise::Resolver> promise_resolver;
if (!v8::Promise::Resolver::New(context).ToLocal(&promise_resolver)) {
exception_state.RethrowV8Exception(try_catch.Exception());
return ScriptPromise();
}
auto measurement_mode =
RuntimeEnabledFeatures::ForceEagerMeasureMemoryEnabled(
ExecutionContext::From(script_state))
? WebMemoryMeasurement::Mode::kEager
: WebMemoryMeasurement::Mode::kDefault;
auto* impl = MakeGarbageCollected<MeasureMemoryController>(
base::PassKey<MeasureMemoryController>(), isolate, context,
promise_resolver);
Document* document = LocalDOMWindow::From(script_state)->document();
document->GetResourceCoordinator()->OnWebMemoryMeasurementRequested(
measurement_mode, WTF::Bind(&MeasureMemoryController::MeasurementComplete,
WrapPersistent(impl)));
return ScriptPromise(script_state, promise_resolver->GetPromise());
}
namespace {
// Satisfies the requirements of UniformRandomBitGenerator from C++ standard.
// It is used in std::shuffle calls below.
struct RandomBitGenerator {
using result_type = size_t;
static constexpr size_t min() { return 0; }
static constexpr size_t max() {
return static_cast<size_t>(std::numeric_limits<int>::max());
}
size_t operator()() {
return static_cast<size_t>(base::RandInt(min(), max()));
}
};
// These functions convert WebMemory* mojo structs to IDL and JS values.
WTF::AtomicString ConvertScope(WebMemoryAttribution::Scope scope) {
using Scope = WebMemoryAttribution::Scope;
switch (scope) {
case Scope::kDedicatedWorker:
return kScopeDedicatedWorker;
case Scope::kWindow:
return kScopeWindow;
case Scope::kCrossOriginAggregated:
return kScopeCrossOriginAggregated;
}
}
MemoryAttributionContainer* ConvertContainer(
const WebMemoryAttributionPtr& attribution) {
if (!attribution->src && !attribution->id) {
return nullptr;
}
auto* result = MemoryAttributionContainer::Create();
result->setSrc(attribution->src);
result->setId(attribution->id);
return result;
}
MemoryAttribution* ConvertAttribution(
const WebMemoryAttributionPtr& attribution) {
auto* result = MemoryAttribution::Create();
if (attribution->url) {
result->setUrl(attribution->url);
} else {
result->setUrl(kCrossOriginUrl);
}
result->setScope(ConvertScope(attribution->scope));
result->setContainer(ConvertContainer(attribution));
return result;
}
MemoryBreakdownEntry* ConvertBreakdown(
const WebMemoryBreakdownEntryPtr& breakdown_entry) {
auto* result = MemoryBreakdownEntry::Create();
DCHECK(breakdown_entry->memory);
result->setBytes(breakdown_entry->memory->bytes);
HeapVector<Member<MemoryAttribution>> attribution;
for (const auto& entry : breakdown_entry->attribution) {
attribution.push_back(ConvertAttribution(entry));
}
result->setAttribution(attribution);
result->setTypes({WTF::AtomicString(kMemoryTypeJavaScript)});
return result;
}
MemoryBreakdownEntry* CreateUnattributedBreakdown(
const WebMemoryUsagePtr& memory,
const WTF::String& memory_type) {
auto* result = MemoryBreakdownEntry::Create();
DCHECK(memory);
result->setBytes(memory->bytes);
result->setAttribution({});
Vector<String> types;
types.push_back(memory_type);
result->setTypes(types);
return result;
}
MemoryBreakdownEntry* EmptyBreakdown() {
auto* result = MemoryBreakdownEntry::Create();
result->setBytes(0);
result->setAttribution({});
result->setTypes({});
return result;
}
MemoryMeasurement* ConvertResult(const WebMemoryMeasurementPtr& measurement) {
HeapVector<Member<MemoryBreakdownEntry>> breakdown;
for (const auto& entry : measurement->breakdown) {
// Skip breakdowns that didn't get a measurement.
if (entry->memory)
breakdown.push_back(ConvertBreakdown(entry));
}
// Add breakdowns for memory that isn't attributed to an execution context.
breakdown.push_back(CreateUnattributedBreakdown(measurement->shared_memory,
kMemoryTypeShared));
breakdown.push_back(
CreateUnattributedBreakdown(measurement->blink_memory, kMemoryTypeDom));
// TODO(1085129): Report memory usage of detached frames once implemented.
// Add an empty breakdown entry as required by the spec.
// See https://github.com/WICG/performance-measure-memory/issues/10.
breakdown.push_back(EmptyBreakdown());
// Randomize the order of the entries as required by the spec.
std::shuffle(breakdown.begin(), breakdown.end(), RandomBitGenerator{});
size_t bytes = 0;
for (auto entry : breakdown) {
bytes += entry->bytes();
}
auto* result = MemoryMeasurement::Create();
result->setBreakdown(breakdown);
result->setBytes(bytes);
return result;
}
} // anonymous namespace
void MeasureMemoryController::MeasurementComplete(
WebMemoryMeasurementPtr measurement) {
if (context_.IsEmpty()) {
// The context was garbage collected in the meantime.
return;
}
v8::HandleScope handle_scope(isolate_);
v8::Local<v8::Context> context = context_.NewLocal(isolate_);
v8::Context::Scope context_scope(context);
auto* result = ConvertResult(measurement);
v8::Local<v8::Promise::Resolver> promise_resolver =
promise_resolver_.NewLocal(isolate_);
promise_resolver->Resolve(context, ToV8(result, promise_resolver, isolate_))
.ToChecked();
promise_resolver_.Clear();
}
} // namespace blink