Commit ec83d167 authored by Ulan Degenbaev's avatar Ulan Degenbaev Committed by Commit Bot

Update performance.measureMemory to the latest proposal

This changes the result format of the API to the latest version of
the proposal at https://github.com/ulan/performance-measure-memory.
Specifically, the result format changes from JS only and per-frame:
{
  total: {
    jsMemoryEstimate: 200*MB,
    jsMemoryRange: [100*MB, 300*MB]
  },
  current: {..},
  other: [..]
}

to more a generic format with breakdown:
{
  bytes: 70*MB,
  breakdown: [
    {bytes: 40*MB, globals: 2, type: 'js', origins: ['foo.com']},
    {bytes: 30*MB, globals: 1, type: 'js', origins: ['bar.com']}
  ]
}

Additionally this patch skips extension contexts and relaxes
cross-origin memory measurement for site-isolated pages.

Bug: 1049093
Change-Id: I997bfe9f6008eaedd0ce5010839232ef3540cad0
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2047028
Commit-Queue: Ulan Degenbaev <ulan@chromium.org>
Reviewed-by: default avatarKentaro Hara <haraken@chromium.org>
Cr-Commit-Position: refs/heads/master@{#740671}
parent 261c5c36
......@@ -613,8 +613,7 @@ static_idl_files_in_core = get_path_info(
"//third_party/blink/renderer/core/timing/largest_contentful_paint.idl",
"//third_party/blink/renderer/core/timing/layout_shift.idl",
"//third_party/blink/renderer/core/timing/measure_memory/measure_memory.idl",
"//third_party/blink/renderer/core/timing/measure_memory/measure_memory_entry.idl",
"//third_party/blink/renderer/core/timing/measure_memory/measure_memory_options.idl",
"//third_party/blink/renderer/core/timing/measure_memory/measure_memory_breakdown.idl",
"//third_party/blink/renderer/core/timing/memory_info.idl",
"//third_party/blink/renderer/core/timing/performance.idl",
"//third_party/blink/renderer/core/timing/performance_element_timing.idl",
......
......@@ -711,8 +711,7 @@ core_dictionary_idl_files =
"resize_observer/resize_observer_options.idl",
"streams/queuing_strategy_init.idl",
"timing/measure_memory/measure_memory.idl",
"timing/measure_memory/measure_memory_entry.idl",
"timing/measure_memory/measure_memory_options.idl",
"timing/measure_memory/measure_memory_breakdown.idl",
"timing/performance_mark_options.idl",
"timing/performance_measure_options.idl",
"timing/performance_observer_init.idl",
......
......@@ -2,11 +2,10 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// https://github.com/ulan/javascript-agent-memory/blob/master/explainer.md
// https://github.com/ulan/performance-measure-memory
// The result of performance.measureMemory().
dictionary MeasureMemory {
required MeasureMemoryEntry total;
MeasureMemoryEntry current;
sequence<MeasureMemoryEntry> other;
required unsigned long long bytes;
required sequence<MeasureMemoryBreakdown> breakdown;
};
// Copyright 2019 The Chromium Authors. All rights reserved.
// 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.
// https://github.com/ulan/javascript-agent-memory/blob/master/explainer.md
// https://github.com/ulan/performance-measure-memory.
// A single entry of performance.measureMemory() result.
dictionary MeasureMemoryEntry {
unsigned long long jsMemoryEstimate;
sequence<unsigned long long> jsMemoryRange;
DOMString url;
dictionary MeasureMemoryBreakdown {
unsigned long long bytes;
unsigned long long globals;
sequence<DOMString> origins;
DOMString type;
};
......@@ -4,9 +4,9 @@
#include "third_party/blink/renderer/core/timing/measure_memory/measure_memory_delegate.h"
#include "third_party/blink/public/platform/platform.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_measure_memory.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_measure_memory_entry.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_measure_memory_options.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_measure_memory_breakdown.h"
#include "third_party/blink/renderer/core/execution_context/execution_context.h"
#include "third_party/blink/renderer/platform/bindings/script_state.h"
#include "third_party/blink/renderer/platform/weborigin/kurl.h"
......@@ -17,12 +17,10 @@ namespace blink {
MeasureMemoryDelegate::MeasureMemoryDelegate(
v8::Isolate* isolate,
v8::Local<v8::Context> context,
v8::Local<v8::Promise::Resolver> promise_resolver,
v8::MeasureMemoryMode mode)
v8::Local<v8::Promise::Resolver> promise_resolver)
: isolate_(isolate),
context_(isolate, context),
promise_resolver_(isolate, promise_resolver),
mode_(mode) {
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
......@@ -52,6 +50,10 @@ bool MeasureMemoryDelegate::ShouldMeasure(v8::Local<v8::Context> context) {
// Context do not belong to the same JavaScript agent.
return false;
}
if (ScriptState::From(context)->World().IsIsolatedWorld()) {
// Context belongs to an extension. Skip it.
return false;
}
const SecurityOrigin* original_security_origin =
original_execution_context->GetSecurityContext().GetSecurityOrigin();
const SecurityOrigin* security_origin =
......@@ -59,7 +61,9 @@ bool MeasureMemoryDelegate::ShouldMeasure(v8::Local<v8::Context> context) {
if (!original_security_origin->IsSameOriginWith(security_origin)) {
// TODO(ulan): Check for COOP/COEP and allow cross-origin contexts that
// opted in for memory measurement.
return false;
// Until then we allow cross-origin measurement only for site-isolated
// web pages.
return Platform::Current()->IsLockedToSite();
}
return true;
}
......@@ -67,34 +71,52 @@ bool MeasureMemoryDelegate::ShouldMeasure(v8::Local<v8::Context> context) {
namespace {
// Helper functions for constructing a memory measurement result.
String GetUrl(v8::Local<v8::Context> context) {
String GetOrigin(v8::Local<v8::Context> context) {
ExecutionContext* execution_context = ExecutionContext::From(context);
if (!execution_context) {
// TODO(ulan): Store URL in v8::Context, so that it is available
// event for detached contexts.
return String("detached");
}
return execution_context->Url().GetString();
const SecurityOrigin* security_origin =
execution_context->GetSecurityContext().GetSecurityOrigin();
return security_origin->ToString();
}
MeasureMemoryEntry* CreateMeasureMemoryEntry(size_t estimate,
size_t unattributed) {
MeasureMemoryEntry* result = MeasureMemoryEntry::Create();
result->setJsMemoryEstimate(estimate);
Vector<uint64_t> range;
range.push_back(estimate);
range.push_back(estimate + unattributed);
result->setJsMemoryRange(range);
MeasureMemoryBreakdown* CreateMeasureMemoryBreakdown(size_t bytes,
size_t globals,
const String& type,
const String& origin) {
MeasureMemoryBreakdown* result = MeasureMemoryBreakdown::Create();
result->setBytes(bytes);
result->setGlobals(globals);
result->setType(type);
result->setOrigins(Vector<String>{origin});
return result;
}
MeasureMemoryEntry* CreateMeasureMemoryEntry(size_t estimate,
size_t unattributed,
const String& url) {
MeasureMemoryEntry* result = CreateMeasureMemoryEntry(estimate, unattributed);
result->setUrl(url);
return result;
struct BytesAndGlobals {
size_t bytes;
size_t globals;
};
HashMap<String, BytesAndGlobals> GroupByOrigin(
const std::vector<std::pair<v8::Local<v8::Context>, size_t>>&
context_sizes) {
HashMap<String, BytesAndGlobals> per_origin;
for (const auto& context_size : context_sizes) {
const String origin = GetOrigin(context_size.first);
auto it = per_origin.find(origin);
if (it == per_origin.end()) {
per_origin.insert(origin, BytesAndGlobals{context_size.second, 1});
} else {
it->value.bytes += context_size.second;
++it->value.globals;
}
}
return per_origin;
}
} // anonymous namespace
// Constructs a memory measurement result based on the given list of (context,
......@@ -114,29 +136,20 @@ void MeasureMemoryDelegate::MeasurementComplete(
}
v8::Context::Scope context_scope(context);
size_t total_size = 0;
size_t current_size = 0;
for (const auto& context_size : context_sizes) {
total_size += context_size.second;
if (context == context_size.first) {
current_size = context_size.second;
}
}
MeasureMemory* result = MeasureMemory::Create();
result->setTotal(CreateMeasureMemoryEntry(total_size, unattributed_size));
if (mode_ == v8::MeasureMemoryMode::kDetailed) {
result->setCurrent(CreateMeasureMemoryEntry(current_size, unattributed_size,
GetUrl(context)));
HeapVector<Member<MeasureMemoryEntry>> other;
for (const auto& context_size : context_sizes) {
if (context_size.first == context) {
// The current context was already reported. Skip it.
continue;
}
other.push_back(CreateMeasureMemoryEntry(
context_size.second, unattributed_size, GetUrl(context_size.first)));
}
result->setOther(other);
result->setBytes(total_size + unattributed_size);
HeapVector<Member<MeasureMemoryBreakdown>> breakdown;
HashMap<String, BytesAndGlobals> per_origin(GroupByOrigin(context_sizes));
for (const auto& it : per_origin) {
breakdown.push_back(CreateMeasureMemoryBreakdown(
it.value.bytes, it.value.globals, "js", it.key));
}
breakdown.push_back(CreateMeasureMemoryBreakdown(
unattributed_size, context_sizes.size(), "js", "shared"));
result->setBreakdown(breakdown);
v8::Local<v8::Promise::Resolver> promise_resolver =
promise_resolver_.NewLocal(isolate_);
promise_resolver
......
......@@ -17,8 +17,7 @@ class MeasureMemoryDelegate : public v8::MeasureMemoryDelegate {
public:
MeasureMemoryDelegate(v8::Isolate* isolate,
v8::Local<v8::Context> context,
v8::Local<v8::Promise::Resolver> promise_resolver,
v8::MeasureMemoryMode mode);
v8::Local<v8::Promise::Resolver> promise_resolver);
// v8::MeasureMemoryDelegate overrides.
bool ShouldMeasure(v8::Local<v8::Context> context) override;
......@@ -31,7 +30,6 @@ class MeasureMemoryDelegate : public v8::MeasureMemoryDelegate {
v8::Isolate* isolate_;
ScopedPersistent<v8::Context> context_;
ScopedPersistent<v8::Promise::Resolver> promise_resolver_;
v8::MeasureMemoryMode mode_;
};
} // namespace blink
......
// Copyright 2019 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.
// https://github.com/ulan/javascript-agent-memory/blob/master/explainer.md
// Options for performance.measureMemory().
dictionary MeasureMemoryOptions {
boolean detailed;
};
......@@ -40,7 +40,6 @@
#include "third_party/blink/renderer/bindings/core/v8/script_promise.h"
#include "third_party/blink/renderer/bindings/core/v8/script_promise_resolver.h"
#include "third_party/blink/renderer/bindings/core/v8/string_or_performance_measure_options.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_measure_memory_options.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_object_builder.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_performance_mark_options.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_performance_measure_options.h"
......@@ -149,7 +148,6 @@ MemoryInfo* Performance::memory() const {
ScriptPromise Performance::measureMemory(
ScriptState* script_state,
MeasureMemoryOptions* options,
ExceptionState& exception_state) const {
if (!Platform::Current()->IsLockedToSite()) {
// TODO(ulan): We should check for COOP and COEP here when they ship.
......@@ -167,11 +165,6 @@ ScriptPromise Performance::measureMemory(
exception_state.RethrowV8Exception(try_catch.Exception());
return ScriptPromise();
}
v8::MeasureMemoryMode mode =
options && options->hasDetailed() && options->detailed()
? v8::MeasureMemoryMode::kDetailed
: v8::MeasureMemoryMode::kSummary;
v8::MeasureMemoryExecution execution =
RuntimeEnabledFeatures::ForceEagerMeasureMemoryEnabled(
ExecutionContext::From(script_state))
......@@ -179,7 +172,7 @@ ScriptPromise Performance::measureMemory(
: v8::MeasureMemoryExecution::kDefault;
isolate->MeasureMemory(std::make_unique<MeasureMemoryDelegate>(
isolate, context, promise_resolver, mode),
isolate, context, promise_resolver),
execution);
return ScriptPromise(script_state, promise_resolver->GetPromise());
}
......
......@@ -61,7 +61,6 @@ class PerformanceMarkOptions;
class ExceptionState;
class LargestContentfulPaint;
class LayoutShift;
class MeasureMemoryOptions;
class MemoryInfo;
class PerformanceElementTiming;
class PerformanceEventTiming;
......@@ -97,7 +96,6 @@ class CORE_EXPORT Performance : public EventTargetWithInlineData {
virtual PerformanceNavigation* navigation() const;
virtual MemoryInfo* memory() const;
virtual ScriptPromise measureMemory(ScriptState*,
MeasureMemoryOptions*,
ExceptionState& exception_state) const;
// Reduce the resolution to prevent timing attacks. See:
......
......@@ -71,7 +71,7 @@ interface Performance : EventTarget {
// https://groups.google.com/a/chromium.org/d/msg/blink-dev/g5YRCGpC9vs/b4OJz71NmPwJ
[Exposed=Window, Measure] readonly attribute MemoryInfo memory;
[MeasureAs=MeasureMemory, Exposed=(Window,Worker), CallWith=ScriptState, RuntimeEnabled=MeasureMemory, RaisesException] Promise<MeasureMemory> measureMemory(optional MeasureMemoryOptions options = {});
[MeasureAs=MeasureMemory, Exposed=(Window,Worker), CallWith=ScriptState, RuntimeEnabled=MeasureMemory, RaisesException] Promise<MeasureMemory> measureMemory();
// JS Self-Profiling API
// https://github.com/WICG/js-self-profiling/
......
......@@ -6173,6 +6173,8 @@ http/tests/devtools/wasm-isolated-code-cache/wasm-cache-test.js [ Skip ]
# Memory measurement tests are run as virtual tests.
external/wpt/measure-memory/measure-memory.tentative.any.html [ Skip ]
external/wpt/measure-memory/measure-memory.tentative.any.worker.html [ Skip ]
external/wpt/measure-memory/measure-memory-cross-origin-iframe.tentative.window.html [ Skip ]
external/wpt/measure-memory/measure-memory-same-origin-iframe.tentative.window.html [ Skip ]
# Assertion errors need to be fixed
crbug.com/1034492 http/tests/devtools/unit/filtered-item-selection-dialog-filtering.js [ Pass Failure ]
......
......@@ -4,6 +4,5 @@ Tests in this directory are for the proposed performance.measureMemory API.
This is not yet standardised and browsers should not be expected to pass
these tests.
See the explainer at
https://github.com/ulan/javascript-agent-memory/blob/master/explainer.md
See the explainer at https://github.com/ulan/performance-measure-memory
for more information about the API.
// META: script=/common/get-host-info.sub.js
// META: script=./resources/common.js
'use strict';
promise_test(async testCase => {
const frame = document.createElement("iframe");
const path = new URL("resources/iframe.sub.html", window.location).pathname;
frame.src = `${CROSS_ORIGIN}${path}`;
document.body.append(frame);
try {
let result = await performance.measureMemory();
checkMeasureMemory(result);
} catch (error) {
if (!(error instanceof DOMException)) {
throw error;
}
assert_equals(error.name, 'SecurityError');
}
}, 'Well-formed result of performance.measureMemory with cross-origin iframe.');
// META: script=/common/get-host-info.sub.js
// META: script=./resources/common.js
'use strict';
promise_test(async testCase => {
const frame = document.createElement("iframe");
const path = new URL("resources/iframe.sub.html", window.location).pathname;
frame.src = `${SAME_ORIGIN}${path}`;
document.body.append(frame);
try {
let result = await performance.measureMemory();
checkMeasureMemory(result);
} catch (error) {
if (!(error instanceof DOMException)) {
throw error;
}
assert_equals(error.name, 'SecurityError');
}
}, 'Well-formed result of performance.measureMemory with same-origin iframe.');
function checkMeasureMemoryResultEntry(entry, checkUrl) {
assert_own_property(entry, "jsMemoryEstimate");
assert_own_property(entry, "jsMemoryRange");
assert_equals(entry.jsMemoryRange.length, 2);
assert_greater_than_equal(entry.jsMemoryRange[1], entry.jsMemoryRange[0]);
assert_greater_than_equal(entry.jsMemoryEstimate, entry.jsMemoryRange[0]);
assert_greater_than_equal(entry.jsMemoryRange[1], entry.jsMemoryEstimate);
if (checkUrl) {
assert_own_property(entry, "url");
}
}
function checkMeasureMemoryResultSummary(result) {
assert_own_property(result, "total");
checkMeasureMemoryResultEntry(result.total, false);
}
function checkMeasureMemoryResultDetails(result) {
assert_own_property(result, "current");
checkMeasureMemoryResultEntry(result.current, true);
assert_own_property(result, "other");
for (other of result.other) {
checkMeasureMemoryResultEntry(other, true);
}
}
// META: script=/common/get-host-info.sub.js
// META: script=./resources/common.js
'use strict';
promise_test(async testCase => {
try {
let result = await performance.measureMemory();
checkMeasureMemoryResultSummary(result);
checkMeasureMemory(result);
} catch (error) {
if (!(error instanceof DOMException)) {
throw error;
}
assert_equals(error.name, "SecurityError");
assert_equals(error.name, 'SecurityError');
}
}, 'Well-formed result of performance.measureMemory with default arguments.');
}, 'Well-formed result of performance.measureMemory.');
promise_test(async testcase => {
try {
let result = await performance.measureMemory({detailed: false});
checkMeasureMemoryResultSummary(result);
} catch (error) {
if (!(error instanceof DOMException)) {
throw error;
}
assert_equals(error.name, "SecurityError");
}
}, 'well-formed result of performance.measureMemory with detailed=false.');
promise_test(async testcase => {
try {
let result = await performance.measureMemory({detailed: true});
checkMeasureMemoryResultSummary(result);
checkMeasureMemoryResultDetails(result);
} catch (error) {
if (!(error instanceof DOMException)) {
throw error;
}
assert_equals(error.name, "SecurityError");
}
}, 'well-formed result of performance.measureMemory with detailed=true.');
const SAME_ORIGIN = {origin: get_host_info().HTTPS_ORIGIN, name: "SAME_ORIGIN"};
const CROSS_ORIGIN = {origin: get_host_info().HTTPS_NOTSAMESITE_ORIGIN, name: "CROSS_ORIGIN"}
function checkMeasureMemoryBreakdown(breakdown) {
assert_own_property(breakdown, 'bytes');
assert_greater_than_equal(breakdown.bytes, 0);
assert_own_property(breakdown, 'globals');
assert_greater_than_equal(breakdown.globals, 0);
assert_own_property(breakdown, 'type');
assert_equals(typeof breakdown.type, 'string');
assert_own_property(breakdown, 'origins');
assert_greater_than_equal(breakdown.origins.length, 1);
for (let origin of breakdown.origins) {
assert_equals(typeof origin, 'string');
}
}
function checkMeasureMemory(result) {
assert_own_property(result, 'bytes');
assert_own_property(result, 'breakdown');
let bytes = 0;
for (let breakdown of result.breakdown) {
checkMeasureMemoryBreakdown(breakdown);
bytes += breakdown.bytes;
}
assert_equals(bytes, result.bytes);
}
\ No newline at end of file
<!doctype html>
<meta charset=utf-8>
<html>
<body>
Hello from iframe.
</body>
</html>
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment