Commit 1847de40 authored by Ulan Degenbaev's avatar Ulan Degenbaev Committed by Commit Bot

Replace map<int64, PerFrameV8MemoryUsageData> with an array

The move of V8PerFrameMemoryReporterImpl from content to blink
introduced a bug where the PerFrameV8MemoryUsageData::associated_memory
mojo map started to use WTF::HashMap as the backing, which makes key=0
invalid because the hash traits for int64_t reserve that value for the
deleted sentinel.

This CL also fixes another bug of dereferencing a non-existent entry
in the |frames| HashMap.

Additionally this CL adds a test for V8PerFrameMemoryReporterImpl to
make sure that the fixes work.

Bug: 1085129
Change-Id: I04b0b7230fde191571f407b4931d6dba543bd2d8
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2339317
Commit-Queue: Ulan Degenbaev <ulan@chromium.org>
Reviewed-by: default avatarChris Hamilton <chrisha@chromium.org>
Reviewed-by: default avatarKentaro Hara <haraken@chromium.org>
Reviewed-by: default avatarDaniel Cheng <dcheng@chromium.org>
Reviewed-by: default avatarJoe Mason <joenotcharles@chromium.org>
Cr-Commit-Position: refs/heads/master@{#795937}
parent ee8e46f5
...@@ -237,6 +237,7 @@ void NodeAttachedProcessData::StartMeasurement() { ...@@ -237,6 +237,7 @@ void NodeAttachedProcessData::StartMeasurement() {
// NodeAttachedProcessData when the last V8PerFrameMemoryRequest is deleted, // NodeAttachedProcessData when the last V8PerFrameMemoryRequest is deleted,
// which could happen at any time. // which could happen at any time.
resource_usage_reporter_->GetPerFrameV8MemoryUsageData( resource_usage_reporter_->GetPerFrameV8MemoryUsageData(
blink::mojom::V8PerFrameMemoryReporter::Mode::DEFAULT,
base::BindOnce(&NodeAttachedProcessData::OnPerFrameV8MemoryUsageData, base::BindOnce(&NodeAttachedProcessData::OnPerFrameV8MemoryUsageData,
weak_factory_.GetWeakPtr())); weak_factory_.GetWeakPtr()));
} }
...@@ -274,20 +275,21 @@ void NodeAttachedProcessData::OnPerFrameV8MemoryUsageData( ...@@ -274,20 +275,21 @@ void NodeAttachedProcessData::OnPerFrameV8MemoryUsageData(
// No data for this node, clear any data associated with it. // No data for this node, clear any data associated with it.
NodeAttachedFrameData::Destroy(frame_node); NodeAttachedFrameData::Destroy(frame_node);
} else { } else {
// There should always be data for the main isolated world for each frame. bool found_main_world = false;
DCHECK(base::Contains(it->second->associated_bytes, 0));
NodeAttachedFrameData* frame_data = NodeAttachedFrameData* frame_data =
NodeAttachedFrameData::GetOrCreate(frame_node); NodeAttachedFrameData::GetOrCreate(frame_node);
for (const auto& kv : it->second->associated_bytes) { for (const auto& entry : it->second->associated_bytes) {
if (kv.first == 0) { if (entry->world_id ==
blink::mojom::V8IsolatedWorldMemoryUsage::kMainWorldId) {
frame_data->data_available_ = true; frame_data->data_available_ = true;
frame_data->data_.set_v8_bytes_used(kv.second->bytes_used); frame_data->data_.set_v8_bytes_used(entry->bytes_used);
found_main_world = true;
} else { } else {
// TODO(crbug.com/1080672): Where to stash the rest of the data? // TODO(crbug.com/1080672): Where to stash the rest of the data?
} }
} }
// There should always be data for the main isolated world for each frame.
DCHECK(found_main_world);
// Clear this datum as its usage has been consumed. // Clear this datum as its usage has been consumed.
associated_memory.erase(it); associated_memory.erase(it);
} }
......
...@@ -51,7 +51,7 @@ class LenientMockV8PerFrameMemoryReporter ...@@ -51,7 +51,7 @@ class LenientMockV8PerFrameMemoryReporter
MOCK_METHOD(void, MOCK_METHOD(void,
GetPerFrameV8MemoryUsageData, GetPerFrameV8MemoryUsageData,
(GetPerFrameV8MemoryUsageDataCallback callback), (Mode mode, GetPerFrameV8MemoryUsageDataCallback callback),
(override)); (override));
void Bind(mojo::PendingReceiver<blink::mojom::V8PerFrameMemoryReporter> void Bind(mojo::PendingReceiver<blink::mojom::V8PerFrameMemoryReporter>
...@@ -142,8 +142,9 @@ class V8PerFrameMemoryDecoratorTestBase { ...@@ -142,8 +142,9 @@ class V8PerFrameMemoryDecoratorTestBase {
base::RepeatingCallback<void( base::RepeatingCallback<void(
MockV8PerFrameMemoryReporter::GetPerFrameV8MemoryUsageDataCallback MockV8PerFrameMemoryReporter::GetPerFrameV8MemoryUsageDataCallback
callback)> responder) { callback)> responder) {
EXPECT_CALL(*mock_reporter, GetPerFrameV8MemoryUsageData(_)) EXPECT_CALL(*mock_reporter, GetPerFrameV8MemoryUsageData(_, _))
.WillOnce([this, responder]( .WillOnce([this, responder](
MockV8PerFrameMemoryReporter::Mode mode,
MockV8PerFrameMemoryReporter:: MockV8PerFrameMemoryReporter::
GetPerFrameV8MemoryUsageDataCallback callback) { GetPerFrameV8MemoryUsageDataCallback callback) {
this->last_query_time_ = base::TimeTicks::Now(); this->last_query_time_ = base::TimeTicks::Now();
...@@ -230,11 +231,14 @@ void AddPerFrameIsolateMemoryUsage( ...@@ -230,11 +231,14 @@ void AddPerFrameIsolateMemoryUsage(
per_frame_data = datum.get(); per_frame_data = datum.get();
data->associated_memory.push_back(std::move(datum)); data->associated_memory.push_back(std::move(datum));
} }
ASSERT_FALSE(base::Contains(per_frame_data->associated_bytes, world_id)); for (const auto& entry : per_frame_data->associated_bytes) {
EXPECT_NE(world_id, entry->world_id);
}
auto isolated_world_usage = blink::mojom::V8IsolatedWorldMemoryUsage::New(); auto isolated_world_usage = blink::mojom::V8IsolatedWorldMemoryUsage::New();
isolated_world_usage->bytes_used = bytes_used; isolated_world_usage->bytes_used = bytes_used;
per_frame_data->associated_bytes[world_id] = std::move(isolated_world_usage); isolated_world_usage->world_id = world_id;
per_frame_data->associated_bytes.push_back(std::move(isolated_world_usage));
} }
} // namespace } // namespace
......
...@@ -8,6 +8,9 @@ import "mojo/public/mojom/base/unguessable_token.mojom"; ...@@ -8,6 +8,9 @@ import "mojo/public/mojom/base/unguessable_token.mojom";
// The amount of heap memory used by V8 in the context of a frame. // The amount of heap memory used by V8 in the context of a frame.
struct V8IsolatedWorldMemoryUsage { struct V8IsolatedWorldMemoryUsage {
const int64 kMainWorldId = 0;
int64 world_id;
// The number of v8 heap bytes used by a V8 isolated world. // The number of v8 heap bytes used by a V8 isolated world.
uint64 bytes_used = 0; uint64 bytes_used = 0;
...@@ -18,11 +21,11 @@ struct V8IsolatedWorldMemoryUsage { ...@@ -18,11 +21,11 @@ struct V8IsolatedWorldMemoryUsage {
// For example Chrome extensions use the host ID, as per // For example Chrome extensions use the host ID, as per
// extensions::ScriptInjection::GetHostIdForIsolatedWorld. Some types of // extensions::ScriptInjection::GetHostIdForIsolatedWorld. Some types of
// isolated world will not have a suitable tag so will leave this empty. // isolated world will not have a suitable tag so will leave this empty.
string stable_id; string? stable_id;
// An optional human readable name for the world, for debugging. Unlike // An optional human readable name for the world, for debugging. Unlike
// stable_id this might not be unique. // stable_id this might not be unique.
string human_readable_name; string? human_readable_name;
}; };
// Returns the number of bytes used by the v8 heap per frame. // Returns the number of bytes used by the v8 heap per frame.
...@@ -30,9 +33,9 @@ struct PerFrameV8MemoryUsageData { ...@@ -30,9 +33,9 @@ struct PerFrameV8MemoryUsageData {
// The frame-unique token. // The frame-unique token.
mojo_base.mojom.UnguessableToken frame_token; mojo_base.mojom.UnguessableToken frame_token;
// The resources used by this frame, mapped on the isolated world ID. // This should actually be a map with world ID keys, but due to limitations
// World ID 0 is the main world. // of WTF::HashMap around keys with value = 0, we have to use an array.
map<int64, V8IsolatedWorldMemoryUsage> associated_bytes; array<V8IsolatedWorldMemoryUsage> associated_bytes;
}; };
// Returns the number of bytes used by the v8 heap in a process. // Returns the number of bytes used by the v8 heap in a process.
...@@ -54,9 +57,18 @@ struct PerProcessV8MemoryUsageData { ...@@ -54,9 +57,18 @@ struct PerProcessV8MemoryUsageData {
// Allows a browser to query the resource usage of sub-processes. // Allows a browser to query the resource usage of sub-processes.
interface V8PerFrameMemoryReporter { interface V8PerFrameMemoryReporter {
// The mode for performing memory measurement.
enum Mode {
DEFAULT, // Perform memory measurement on the next garbage collection
// and force garbage collection after some timeout.
EAGER, // Force immediate garbage collection and memory measurement.
LAZY, // Perform memory measurement on the next garbage collection.
};
// Requests a per-frame estimate of v8 heap byte usage on the next garbage // Requests a per-frame estimate of v8 heap byte usage on the next garbage
// collection. Note that this causes extra cost for the next garbage // collection. Note that this causes extra cost for the next garbage
// collection, which can be on the order of 10-20%. // collection, which can be on the order of 10-20%.
GetPerFrameV8MemoryUsageData() => (PerProcessV8MemoryUsageData data); GetPerFrameV8MemoryUsageData(Mode mode) => (PerProcessV8MemoryUsageData data);
}; };
...@@ -179,6 +179,9 @@ source_set("blink_unittests_sources") { ...@@ -179,6 +179,9 @@ source_set("blink_unittests_sources") {
"oom_intervention_impl_test.cc", "oom_intervention_impl_test.cc",
"user_level_memory_pressure_signal_generator_test.cc", "user_level_memory_pressure_signal_generator_test.cc",
] ]
} else {
sources +=
[ "performance_manager/v8_per_frame_memory_reporter_impl_test.cc" ]
} }
if (is_linux || is_chromeos || is_android || is_mac || is_win) { if (is_linux || is_chromeos || is_android || is_mac || is_win) {
......
...@@ -21,6 +21,7 @@ struct HashTraits<::blink::mojom::blink::PerFrameV8MemoryUsageDataPtr> ...@@ -21,6 +21,7 @@ struct HashTraits<::blink::mojom::blink::PerFrameV8MemoryUsageDataPtr>
typedef const ::blink::mojom::blink::PerFrameV8MemoryUsageDataPtr& typedef const ::blink::mojom::blink::PerFrameV8MemoryUsageDataPtr&
PeekOutType; PeekOutType;
}; };
} // namespace WTF } // namespace WTF
namespace blink { namespace blink {
...@@ -76,9 +77,11 @@ class FrameAssociatedMeasurementDelegate : public v8::MeasureMemoryDelegate { ...@@ -76,9 +77,11 @@ class FrameAssociatedMeasurementDelegate : public v8::MeasureMemoryDelegate {
++(result->num_unassociated_contexts); ++(result->num_unassociated_contexts);
result->unassociated_context_bytes_used += size; result->unassociated_context_bytes_used += size;
} else { } else {
mojom::blink::PerFrameV8MemoryUsageData* per_frame_resources = auto it = frames.find(frame);
frames.at(frame).get(); mojom::blink::PerFrameV8MemoryUsageData* per_frame_resources;
if (!per_frame_resources) { if (it != frames.end()) {
per_frame_resources = it->value.get();
} else {
#if DCHECK_IS_ON() #if DCHECK_IS_ON()
// Check that the frame token didn't already occur. // Check that the frame token didn't already occur.
for (const auto& entry : frames) { for (const auto& entry : frames) {
...@@ -97,19 +100,29 @@ class FrameAssociatedMeasurementDelegate : public v8::MeasureMemoryDelegate { ...@@ -97,19 +100,29 @@ class FrameAssociatedMeasurementDelegate : public v8::MeasureMemoryDelegate {
mojom::blink::V8IsolatedWorldMemoryUsagePtr isolated_world_usage = mojom::blink::V8IsolatedWorldMemoryUsagePtr isolated_world_usage =
mojom::blink::V8IsolatedWorldMemoryUsage::New(); mojom::blink::V8IsolatedWorldMemoryUsage::New();
isolated_world_usage->bytes_used = size; isolated_world_usage->bytes_used = size;
int32_t world_id = frame->GetScriptContextWorldId(context); isolated_world_usage->world_id =
frame->GetScriptContextWorldId(context);
if (world_id != DOMWrapperWorld::WorldId::kMainWorldId) { static_assert(
DOMWrapperWorld::WorldId::kMainWorldId ==
mojom::blink::V8IsolatedWorldMemoryUsage::kMainWorldId,
"The main world IDs must match.");
if (isolated_world_usage->world_id !=
DOMWrapperWorld::WorldId::kMainWorldId) {
isolated_world_usage->stable_id = isolated_world_usage->stable_id =
blink::GetIsolatedWorldStableId(context); blink::GetIsolatedWorldStableId(context);
isolated_world_usage->human_readable_name = isolated_world_usage->human_readable_name =
blink::GetIsolatedWorldHumanReadableName(context); blink::GetIsolatedWorldHumanReadableName(context);
} }
#if DCHECK_IS_ON()
// Check that the world id didn't already occur.
for (const auto& entry : per_frame_resources->associated_bytes) {
DCHECK_NE(entry->world_id, isolated_world_usage->world_id);
}
#endif
DCHECK( per_frame_resources->associated_bytes.push_back(
!base::Contains(per_frame_resources->associated_bytes, world_id)); std::move(isolated_world_usage));
per_frame_resources->associated_bytes.Set(
world_id, std::move(isolated_world_usage));
} }
} }
// Move the per-frame memory values to the result. // Move the per-frame memory values to the result.
...@@ -122,6 +135,19 @@ class FrameAssociatedMeasurementDelegate : public v8::MeasureMemoryDelegate { ...@@ -122,6 +135,19 @@ class FrameAssociatedMeasurementDelegate : public v8::MeasureMemoryDelegate {
GetPerFrameV8MemoryUsageDataCallback callback_; GetPerFrameV8MemoryUsageDataCallback callback_;
}; };
v8::MeasureMemoryExecution ToV8MeasureMemoryExecution(
V8PerFrameMemoryReporterImpl::Mode mode) {
switch (mode) {
case V8PerFrameMemoryReporterImpl::Mode::DEFAULT:
return v8::MeasureMemoryExecution::kDefault;
case V8PerFrameMemoryReporterImpl::Mode::EAGER:
return v8::MeasureMemoryExecution::kEager;
case V8PerFrameMemoryReporterImpl::Mode::LAZY:
return v8::MeasureMemoryExecution::kLazy;
}
NOTREACHED();
}
} // namespace } // namespace
// static // static
...@@ -132,6 +158,7 @@ void V8PerFrameMemoryReporterImpl::Create( ...@@ -132,6 +158,7 @@ void V8PerFrameMemoryReporterImpl::Create(
} }
void V8PerFrameMemoryReporterImpl::GetPerFrameV8MemoryUsageData( void V8PerFrameMemoryReporterImpl::GetPerFrameV8MemoryUsageData(
V8PerFrameMemoryReporterImpl::Mode mode,
GetPerFrameV8MemoryUsageDataCallback callback) { GetPerFrameV8MemoryUsageDataCallback callback) {
v8::Isolate* isolate = v8::Isolate::GetCurrent(); v8::Isolate* isolate = v8::Isolate::GetCurrent();
if (!isolate) { if (!isolate) {
...@@ -141,7 +168,8 @@ void V8PerFrameMemoryReporterImpl::GetPerFrameV8MemoryUsageData( ...@@ -141,7 +168,8 @@ void V8PerFrameMemoryReporterImpl::GetPerFrameV8MemoryUsageData(
std::make_unique<FrameAssociatedMeasurementDelegate>( std::make_unique<FrameAssociatedMeasurementDelegate>(
std::move(callback)); std::move(callback));
isolate->MeasureMemory(std::move(delegate)); isolate->MeasureMemory(std::move(delegate),
ToV8MeasureMemoryExecution(mode));
} }
} }
......
...@@ -6,17 +6,19 @@ ...@@ -6,17 +6,19 @@
#define THIRD_PARTY_BLINK_RENDERER_CONTROLLER_PERFORMANCE_MANAGER_V8_PER_FRAME_MEMORY_REPORTER_IMPL_H_ #define THIRD_PARTY_BLINK_RENDERER_CONTROLLER_PERFORMANCE_MANAGER_V8_PER_FRAME_MEMORY_REPORTER_IMPL_H_
#include "third_party/blink/public/mojom/performance_manager/v8_per_frame_memory.mojom-blink.h" #include "third_party/blink/public/mojom/performance_manager/v8_per_frame_memory.mojom-blink.h"
#include "third_party/blink/renderer/controller/controller_export.h"
namespace blink { namespace blink {
// Exposes V8 per-frame associated memory metrics to the browser. // Exposes V8 per-frame associated memory metrics to the browser.
class V8PerFrameMemoryReporterImpl class CONTROLLER_EXPORT V8PerFrameMemoryReporterImpl
: public mojom::blink::V8PerFrameMemoryReporter { : public mojom::blink::V8PerFrameMemoryReporter {
public: public:
static void Create( static void Create(
mojo::PendingReceiver<mojom::blink::V8PerFrameMemoryReporter> receiver); mojo::PendingReceiver<mojom::blink::V8PerFrameMemoryReporter> receiver);
void GetPerFrameV8MemoryUsageData( void GetPerFrameV8MemoryUsageData(
Mode mode,
GetPerFrameV8MemoryUsageDataCallback callback) override; GetPerFrameV8MemoryUsageDataCallback callback) override;
}; };
......
// 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_per_frame_memory_reporter_impl.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/renderer/core/testing/sim/sim_compositor.h"
#include "third_party/blink/renderer/core/testing/sim/sim_request.h"
#include "third_party/blink/renderer/core/testing/sim/sim_test.h"
#include "third_party/blink/renderer/platform/testing/unit_test_helpers.h"
#include "v8/include/v8.h"
namespace blink {
class V8PerFrameMemoryReporterImplTest : public SimTest {};
namespace {
class MemoryUsageChecker {
public:
void Callback(mojom::blink::PerProcessV8MemoryUsageDataPtr result) {
EXPECT_EQ(2u, result->associated_memory.size());
for (const auto& frame_memory : result->associated_memory) {
for (const auto& entry : frame_memory->associated_bytes) {
EXPECT_EQ(0u, entry->world_id);
EXPECT_LT(4000000u, entry->bytes_used);
}
}
called_ = true;
}
bool IsCalled() { return called_; }
private:
bool called_ = false;
};
} // anonymous namespace
TEST_F(V8PerFrameMemoryReporterImplTest, GetPerFrameV8MemoryUsageData) {
SimRequest main_resource("https://example.com/", "text/html");
SimRequest child_frame_resource("https://example.com/subframe.html",
"text/html");
LoadURL("https://example.com/");
main_resource.Complete(String::Format(R"HTML(
<script>
window.onload = function () {
globalThis.array = new Array(1000000).fill(0);
console.log("main loaded");
}
</script>
<body>
<iframe src='https://example.com/subframe.html'></iframe>
</body>)HTML"));
test::RunPendingTasks();
child_frame_resource.Complete(String::Format(R"HTML(
<script>
window.onload = function () {
globalThis.array = new Array(1000000).fill(0);
console.log("iframe loaded");
}
</script>
<body>
</body>)HTML"));
test::RunPendingTasks();
// Ensure that main frame and subframe are loaded before measuring memory
// usage.
EXPECT_TRUE(ConsoleMessages().Contains("main loaded"));
EXPECT_TRUE(ConsoleMessages().Contains("iframe loaded"));
V8PerFrameMemoryReporterImpl reporter;
MemoryUsageChecker checker;
reporter.GetPerFrameV8MemoryUsageData(
V8PerFrameMemoryReporterImpl::Mode::EAGER,
WTF::Bind(&MemoryUsageChecker::Callback, WTF::Unretained(&checker)));
test::RunPendingTasks();
EXPECT_TRUE(checker.IsCalled());
}
} // namespace blink
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