Commit f921047b authored by Joe Mason's avatar Joe Mason Committed by Chromium LUCI CQ

[PM] Split WebMemoryAggregator and WebMemoryMeasurer into separate files

An upcoming patch will add another helper class to web_memory.h, which
will overload web_memory_aggregator.cc if it's added there. So, move
the implementation of all the web_memory.h helper classes (currently
just WebMemoryMeasurer) to web_memory_impl.*

This also splits the unit tests into web_memory_aggregator_unittest.cc
and web_memory_impl_unittest.cc, moving shared code into
v8_memory_test_helpers.*.

R=siggi

Bug: 1085129
Change-Id: Idd03b15440f78441d224a7ea08967faf404215ea
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2586144Reviewed-by: default avatarFrançois Doray <fdoray@chromium.org>
Commit-Queue: Joe Mason <joenotcharles@chromium.org>
Cr-Commit-Position: refs/heads/master@{#836160}
parent dfcd9c84
......@@ -164,6 +164,8 @@ static_library("performance_manager") {
"v8_memory/v8_detailed_memory.cc",
"v8_memory/web_memory_aggregator.cc",
"v8_memory/web_memory_aggregator.h",
"v8_memory/web_memory_impl.cc",
"v8_memory/web_memory_impl.h",
"web_contents_proxy.cc",
"web_contents_proxy_impl.cc",
"web_contents_proxy_impl.h",
......@@ -273,6 +275,7 @@ source_set("unit_tests") {
"v8_memory/v8_memory_test_helpers.cc",
"v8_memory/v8_memory_test_helpers.h",
"v8_memory/web_memory_aggregator_unittest.cc",
"v8_memory/web_memory_impl_unittest.cc",
"voting_unittest.cc",
"web_contents_proxy_unittest.cc",
"worker_watcher_unittest.cc",
......
......@@ -306,7 +306,8 @@ class V8DetailedMemoryExecutionContextData {
const execution_context::ExecutionContext* ec);
private:
friend class WebMemoryAggregatorTest;
friend class WebMemoryTestHarness;
// Creates frame data for the given node.
static V8DetailedMemoryExecutionContextData* CreateForTesting(
const FrameNode* node);
......
......@@ -8,10 +8,16 @@
#include "base/bind.h"
#include "base/callback.h"
#include "base/check.h"
#include "base/command_line.h"
#include "base/run_loop.h"
#include "base/test/bind.h"
#include "base/test/task_environment.h"
#include "components/performance_manager/embedder/graph_features_helper.h"
#include "components/performance_manager/graph/frame_node_impl.h"
#include "components/performance_manager/graph/page_node_impl.h"
#include "components/performance_manager/graph/process_node_impl.h"
#include "components/performance_manager/public/mojom/v8_contexts.mojom.h"
#include "components/performance_manager/public/performance_manager.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/test/navigation_simulator.h"
......@@ -209,6 +215,80 @@ V8MemoryPerformanceManagerTestHarness::GetMainThreadTaskRunner() {
constexpr char V8MemoryPerformanceManagerTestHarness::kMainFrameUrl[];
constexpr char V8MemoryPerformanceManagerTestHarness::kChildFrameUrl[];
////////////////////////////////////////////////////////////////////////////////
// WebMemoryTestHarness
WebMemoryTestHarness::WebMemoryTestHarness() = default;
WebMemoryTestHarness::~WebMemoryTestHarness() = default;
void WebMemoryTestHarness::SetUp() {
GetGraphFeaturesHelper().EnableV8ContextTracker();
Super::SetUp();
process_ = CreateNode<ProcessNodeImpl>();
pages_.push_back(CreateNode<PageNodeImpl>());
}
int WebMemoryTestHarness::GetNextUniqueId() {
return next_unique_id_++;
}
FrameNodeImpl* WebMemoryTestHarness::AddFrameNodeImpl(
std::string url,
int browsing_instance_id,
Bytes memory_usage,
FrameNodeImpl* parent,
FrameNodeImpl* opener,
base::Optional<std::string> id_attribute,
base::Optional<std::string> src_attribute) {
// If there's an opener, the new frame is also a new page.
auto* page = pages_.front().get();
if (opener) {
pages_.push_back(CreateNode<PageNodeImpl>());
page = pages_.back().get();
page->SetOpenerFrameNodeAndOpenedType(opener, PageNode::OpenedType::kPopup);
}
int frame_tree_node_id = GetNextUniqueId();
int frame_routing_id = GetNextUniqueId();
auto frame_token = blink::LocalFrameToken();
auto frame = CreateNode<FrameNodeImpl>(process_.get(), page, parent,
frame_tree_node_id, frame_routing_id,
frame_token, browsing_instance_id);
frame->OnNavigationCommitted(GURL(url), /*same document*/ true);
V8DetailedMemoryExecutionContextData::CreateForTesting(frame.get())
->set_v8_bytes_used(memory_usage.bytes);
frames_.push_back(std::move(frame));
FrameNodeImpl* frame_impl = frames_.back().get();
// Create a V8ContextDescription with attribution data for this frame. (In
// production this is done by PerformanceManager monitoring frame lifetime
// events.)
auto description = mojom::V8ContextDescription::New();
description->token = blink::V8ContextToken();
description->world_type = mojom::V8ContextWorldType::kMain;
description->execution_context_token = frame_token;
mojom::IframeAttributionDataPtr attribution;
if (parent) {
// Frame attribution attributes come from the frame's parent node, so
// V8ContextTracker expects an IframeAttributionData. The attribute values
// may be empty.
attribution = mojom::IframeAttributionData::New();
attribution->id = id_attribute;
attribution->src = src_attribute;
} else {
// V8ContextTracker expects no IframeAttributionData.
DCHECK(!id_attribute);
DCHECK(!src_attribute);
}
DCHECK(frame_impl->process_node());
frame_impl->process_node()->OnV8ContextCreated(std::move(description),
std::move(attribution));
return frame_impl;
}
////////////////////////////////////////////////////////////////////////////////
// Free functions
......
......@@ -7,12 +7,17 @@
#include "components/performance_manager/public/v8_memory/v8_detailed_memory.h"
#include <string>
#include <vector>
#include "base/callback_forward.h"
#include "base/memory/scoped_refptr.h"
#include "base/optional.h"
#include "base/single_thread_task_runner.h"
#include "base/time/time.h"
#include "components/performance_manager/public/render_process_host_id.h"
#include "components/performance_manager/public/render_process_host_proxy.h"
#include "components/performance_manager/test_support/graph_test_harness.h"
#include "components/performance_manager/test_support/performance_manager_test_harness.h"
#include "mojo/public/cpp/bindings/pending_receiver.h"
#include "mojo/public/cpp/bindings/receiver.h"
......@@ -26,6 +31,11 @@ class RenderFrameHost;
}
namespace performance_manager {
class FrameNodeImpl;
class PageNodeImpl;
class ProcessNodeImpl;
namespace v8_memory {
// A fake implementation of the mojo interface that reports memory measurement
......@@ -208,6 +218,85 @@ class V8MemoryPerformanceManagerTestHarness
RenderProcessHostId child_process_id_;
};
// A GraphTestHarness that adds convenience functions used by both
// WebMemoryImplTest and WebMemoryAggregatorTest.
class WebMemoryTestHarness : public GraphTestHarness {
public:
using Super = GraphTestHarness;
// Wrapper for memory usage bytes to improve test readability.
struct Bytes {
uint64_t bytes;
bool operator==(const Bytes& other) const { return bytes == other.bytes; }
};
WebMemoryTestHarness();
~WebMemoryTestHarness() override;
void SetUp() override;
// Creates and adds a new frame node to the graph.
FrameNodeImpl* AddFrameNode(
std::string url,
Bytes bytes,
FrameNodeImpl* parent = nullptr,
base::Optional<std::string> id_attribute = base::nullopt,
base::Optional<std::string> src_attribute = base::nullopt) {
return AddFrameNodeImpl(url, kDefaultBrowsingInstanceId, bytes, parent,
/*opener=*/nullptr, id_attribute, src_attribute);
}
// Creates a frame node as if from window.open and adds it to the graph.
FrameNodeImpl* AddFrameNodeFromOpener(std::string url,
Bytes bytes,
FrameNodeImpl* opener) {
return AddFrameNodeImpl(url, kDefaultBrowsingInstanceId, bytes,
/*parent=*/nullptr, opener);
}
// Creates a frame node in a different browsing instance and adds it to the
// graph.
FrameNodeImpl* AddCrossBrowsingInstanceFrameNode(
std::string url,
Bytes bytes,
FrameNodeImpl* parent = nullptr,
base::Optional<std::string> id_attribute = base::nullopt,
base::Optional<std::string> src_attribute = base::nullopt) {
return AddFrameNodeImpl(url, kDefaultBrowsingInstanceId + 1, bytes, parent,
/*opener=*/nullptr, id_attribute, src_attribute);
}
// Creates a frame node in a different browsing instance as if from
// window.open and adds it to the graph.
FrameNodeImpl* AddCrossBrowsingInstanceFrameNodeFromOpener(
std::string url,
Bytes bytes,
FrameNodeImpl* opener) {
return AddFrameNodeImpl(url, kDefaultBrowsingInstanceId + 1, bytes,
/*parent=*/nullptr, opener);
}
ProcessNode* process_node() const { return process_.get(); }
private:
static constexpr int kDefaultBrowsingInstanceId = 0;
// Creates and adds a new frame node to the graph.
FrameNodeImpl* AddFrameNodeImpl(
std::string url,
int browsing_instance_id,
Bytes bytes,
FrameNodeImpl* parent = nullptr,
FrameNodeImpl* opener = nullptr,
base::Optional<std::string> id_attribute = base::nullopt,
base::Optional<std::string> src_attribute = base::nullopt);
int GetNextUniqueId();
TestNodeWrapper<ProcessNodeImpl> process_;
std::vector<TestNodeWrapper<PageNodeImpl>> pages_;
std::vector<TestNodeWrapper<FrameNodeImpl>> frames_;
int next_unique_id_ = 0;
};
// Returns a new mojom::PerProcessV8MemoryUsage struct with
// |number_of_isolates| empty isolates.
blink::mojom::PerProcessV8MemoryUsagePtr NewPerProcessV8MemoryUsage(
......
......@@ -4,18 +4,15 @@
#include "components/performance_manager/v8_memory/web_memory_aggregator.h"
#include <memory>
#include <utility>
#include <vector>
#include "base/bind.h"
#include "base/callback.h"
#include "base/check.h"
#include "base/optional.h"
#include "base/stl_util.h"
#include "components/performance_manager/public/graph/frame_node.h"
#include "components/performance_manager/public/graph/page_node.h"
#include "components/performance_manager/public/graph/process_node.h"
#include "components/performance_manager/public/v8_memory/web_memory.h"
#include "components/performance_manager/public/v8_memory/v8_detailed_memory.h"
#include "components/performance_manager/v8_memory/v8_context_tracker.h"
#include "url/gurl.h"
......@@ -27,64 +24,6 @@ namespace {
using AttributionScope = mojom::WebMemoryAttribution::Scope;
mojom::WebMemoryMeasurementPtr BuildMemoryUsageResult(
const blink::LocalFrameToken& frame_token,
const ProcessNode* process_node) {
const auto& frame_nodes = process_node->GetFrameNodes();
// Find the frame that made the request.
const FrameNode* requesting_frame = nullptr;
for (auto* frame_node : frame_nodes) {
if (frame_node->GetFrameToken() == frame_token) {
requesting_frame = frame_node;
break;
}
}
if (!requesting_frame) {
// The frame no longer exists.
return mojom::WebMemoryMeasurement::New();
}
auto result = mojom::WebMemoryMeasurement::New();
for (const FrameNode* frame_node : frame_nodes) {
if (frame_node->GetBrowsingInstanceId() !=
requesting_frame->GetBrowsingInstanceId()) {
continue;
}
if (frame_node->GetURL().GetOrigin() !=
requesting_frame->GetURL().GetOrigin()) {
continue;
}
auto* data = v8_memory::V8DetailedMemoryExecutionContextData::ForFrameNode(
frame_node);
if (!data) {
continue;
}
auto attribution = mojom::WebMemoryAttribution::New();
attribution->url = frame_node->GetURL().spec();
attribution->scope = mojom::WebMemoryAttribution::Scope::kWindow;
auto entry = mojom::WebMemoryBreakdownEntry::New();
entry->bytes = data->v8_bytes_used();
entry->attribution.push_back(std::move(attribution));
result->breakdown.push_back(std::move(entry));
}
return result;
}
v8_memory::V8DetailedMemoryRequest::MeasurementMode
WebMeasurementModeToRequestMeasurementMode(
mojom::WebMemoryMeasurement::Mode mode) {
switch (mode) {
case mojom::WebMemoryMeasurement::Mode::kDefault:
return v8_memory::V8DetailedMemoryRequest::MeasurementMode::kDefault;
case mojom::WebMemoryMeasurement::Mode::kEager:
return v8_memory::V8DetailedMemoryRequest::MeasurementMode::
kEagerForTesting;
}
}
// Returns true if |page_node| has an opener that should be followed by the
// aggregation algorithm.
bool ShouldFollowOpenerLink(const PageNode* page_node) {
......@@ -134,26 +73,6 @@ const mojom::WebMemoryAttribution* GetAttributionFromBreakdown(
} // anonymous namespace
////////////////////////////////////////////////////////////////////////////////
// WebMemoryMeasurer
WebMemoryMeasurer::WebMemoryMeasurer(
const blink::LocalFrameToken& frame_token,
V8DetailedMemoryRequest::MeasurementMode mode,
MeasurementCallback callback)
: frame_token_(frame_token),
callback_(std::move(callback)),
request_(std::make_unique<V8DetailedMemoryRequestOneShot>(mode)) {}
WebMemoryMeasurer::~WebMemoryMeasurer() = default;
void WebMemoryMeasurer::MeasurementComplete(
const ProcessNode* process_node,
const V8DetailedMemoryProcessData*) {
// TODO(crbug.com/1085129): Call AggregateMemoryResult here instead of
// BuildMemoryUsageResult.
std::move(callback_).Run(BuildMemoryUsageResult(frame_token_, process_node));
}
////////////////////////////////////////////////////////////////////////////////
// WebMemoryAggregator
......@@ -318,26 +237,6 @@ bool WebMemoryAggregator::VisitOpenedPage(
////////////////////////////////////////////////////////////////////////////////
// Free functions
// Implements the public function in public/v8_memory/web_memory.h
void WebMeasureMemory(
const FrameNode* frame_node,
mojom::WebMemoryMeasurement::Mode mode,
base::OnceCallback<void(mojom::WebMemoryMeasurementPtr)> callback) {
auto measurer = std::make_unique<WebMemoryMeasurer>(
frame_node->GetFrameToken(),
WebMeasurementModeToRequestMeasurementMode(mode), std::move(callback));
// Create a measurement complete callback to own |measurer|. It
// will be deleted when the callback is executed or dropped.
V8DetailedMemoryRequestOneShot* request = measurer->request();
auto measurement_complete_callback = base::BindOnce(
&WebMemoryMeasurer::MeasurementComplete, std::move(measurer));
// Start memory measurement for the process of the given frame.
request->StartMeasurement(frame_node->GetProcessNode(),
std::move(measurement_complete_callback));
}
namespace internal {
const FrameNode* GetSameOriginParentOrOpener(const FrameNode* frame_node,
......
......@@ -5,51 +5,19 @@
#ifndef COMPONENTS_PERFORMANCE_MANAGER_V8_MEMORY_WEB_MEMORY_AGGREGATOR_H_
#define COMPONENTS_PERFORMANCE_MANAGER_V8_MEMORY_WEB_MEMORY_AGGREGATOR_H_
#include <memory>
#include <string>
#include "base/callback.h"
#include "base/optional.h"
#include "components/performance_manager/public/mojom/web_memory.mojom.h"
#include "components/performance_manager/public/v8_memory/v8_detailed_memory.h"
#include "third_party/blink/public/common/tokens/tokens.h"
#include "url/origin.h"
namespace performance_manager {
class FrameNode;
class ProcessNode;
class PageNode;
namespace v8_memory {
// A helper class for implementing WebMeasureMemory(). This manages a request
// object that sends a V8 detailed memory request to the renderer, and formats
// the result into a mojom::WebMemoryMeasurement.
// TODO(crbug.com/1085129): Extend this to measure all renderers that are
// reachable from the requesting node.
class WebMemoryMeasurer {
public:
using MeasurementCallback =
base::OnceCallback<void(mojom::WebMemoryMeasurementPtr)>;
WebMemoryMeasurer(const blink::LocalFrameToken&,
V8DetailedMemoryRequest::MeasurementMode,
MeasurementCallback);
~WebMemoryMeasurer();
WebMemoryMeasurer(const WebMemoryMeasurer& other) = delete;
WebMemoryMeasurer& operator=(const WebMemoryMeasurer& other) = delete;
V8DetailedMemoryRequestOneShot* request() const { return request_.get(); }
// A callback for V8DetailedMemoryRequestOneShot.
void MeasurementComplete(const ProcessNode*,
const V8DetailedMemoryProcessData*);
private:
blink::LocalFrameToken frame_token_;
MeasurementCallback callback_;
std::unique_ptr<V8DetailedMemoryRequestOneShot> request_;
};
// Traverses the graph of execution contexts to find the results of the last
// memory measurement and aggregates them according to the rules defined in
// https://wicg.github.io/performance-measure-memory. (This implements the
......
......@@ -5,210 +5,29 @@
#include "components/performance_manager/v8_memory/web_memory_aggregator.h"
#include <algorithm>
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include "base/bind.h"
#include "base/check.h"
#include "base/containers/flat_map.h"
#include "base/memory/weak_ptr.h"
#include "base/optional.h"
#include "base/test/bind.h"
#include "base/test/task_environment.h"
#include "base/trace_event/traced_value.h"
#include "components/performance_manager/graph/frame_node_impl.h"
#include "components/performance_manager/graph/page_node_impl.h"
#include "components/performance_manager/graph/process_node_impl.h"
#include "components/performance_manager/public/graph/graph.h"
#include "components/performance_manager/public/mojom/v8_contexts.mojom.h"
#include "components/performance_manager/public/mojom/web_memory.mojom.h"
#include "components/performance_manager/public/performance_manager.h"
#include "components/performance_manager/public/v8_memory/v8_detailed_memory.h"
#include "components/performance_manager/public/v8_memory/web_memory.h"
#include "components/performance_manager/test_support/graph_test_harness.h"
#include "components/performance_manager/v8_memory/v8_memory_test_helpers.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/common/tokens/tokens.h"
#include "url/gurl.h"
#include "url/origin.h"
namespace performance_manager {
namespace v8_memory {
namespace {
using AttributionScope = mojom::WebMemoryAttribution::Scope;
using NodeAggregationType = WebMemoryAggregator::NodeAggregationType;
using WebMemoryAggregatorPMTest = V8MemoryPerformanceManagerTestHarness;
class WebMemoryAggregatorTest : public GraphTestHarness {
public:
using Super = GraphTestHarness;
// Wrapper for memory usage bytes to improve test readability.
struct Bytes {
uint64_t bytes;
bool operator==(const Bytes& other) const { return bytes == other.bytes; }
};
void SetUp() override;
// Creates and adds a new frame node to the graph.
FrameNodeImpl* AddFrameNode(
std::string url,
Bytes bytes,
FrameNodeImpl* parent = nullptr,
base::Optional<std::string> id_attribute = base::nullopt,
base::Optional<std::string> src_attribute = base::nullopt) {
return AddFrameNodeImpl(url, kDefaultBrowsingInstanceId, bytes, parent,
/*opener=*/nullptr, id_attribute, src_attribute);
}
// Creates a frame node as if from window.open and adds it to the graph.
FrameNodeImpl* AddFrameNodeFromOpener(std::string url,
Bytes bytes,
FrameNodeImpl* opener) {
return AddFrameNodeImpl(url, kDefaultBrowsingInstanceId, bytes,
/*parent=*/nullptr, opener);
}
// Creates a frame node in a different browsing instance and adds it to the
// graph.
FrameNodeImpl* AddCrossBrowsingInstanceFrameNode(
std::string url,
Bytes bytes,
FrameNodeImpl* parent = nullptr,
base::Optional<std::string> id_attribute = base::nullopt,
base::Optional<std::string> src_attribute = base::nullopt) {
return AddFrameNodeImpl(url, kDefaultBrowsingInstanceId + 1, bytes, parent,
/*opener=*/nullptr, id_attribute, src_attribute);
}
// Creates a frame node in a different browsing instance as if from
// window.open and adds it to the graph.
FrameNodeImpl* AddCrossBrowsingInstanceFrameNodeFromOpener(
std::string url,
Bytes bytes,
FrameNodeImpl* opener) {
return AddFrameNodeImpl(url, kDefaultBrowsingInstanceId + 1, bytes,
/*parent=*/nullptr, opener);
}
// Invokes memory measurement and verifies that the result matches the
// expected memory usage that is provided as a table from a frame URL to
// bytes.
void MeasureAndVerify(FrameNodeImpl* frame,
base::flat_map<std::string, Bytes> expected);
private:
static constexpr int kDefaultBrowsingInstanceId = 0;
// Creates and adds a new frame node to the graph.
FrameNodeImpl* AddFrameNodeImpl(
std::string url,
int browsing_instance_id,
Bytes bytes,
FrameNodeImpl* parent = nullptr,
FrameNodeImpl* opener = nullptr,
base::Optional<std::string> id_attribute = base::nullopt,
base::Optional<std::string> src_attribute = base::nullopt);
int GetNextUniqueId();
TestNodeWrapper<ProcessNodeImpl> process_;
std::vector<TestNodeWrapper<PageNodeImpl>> pages_;
std::vector<TestNodeWrapper<FrameNodeImpl>> frames_;
int next_unique_id_ = 0;
};
void WebMemoryAggregatorTest::SetUp() {
GetGraphFeaturesHelper().EnableV8ContextTracker();
Super::SetUp();
process_ = CreateNode<ProcessNodeImpl>();
pages_.push_back(CreateNode<PageNodeImpl>());
}
int WebMemoryAggregatorTest::GetNextUniqueId() {
return next_unique_id_++;
}
FrameNodeImpl* WebMemoryAggregatorTest::AddFrameNodeImpl(
std::string url,
int browsing_instance_id,
Bytes memory_usage,
FrameNodeImpl* parent,
FrameNodeImpl* opener,
base::Optional<std::string> id_attribute,
base::Optional<std::string> src_attribute) {
// If there's an opener, the new frame is also a new page.
auto* page = pages_.front().get();
if (opener) {
pages_.push_back(CreateNode<PageNodeImpl>());
page = pages_.back().get();
page->SetOpenerFrameNodeAndOpenedType(opener, PageNode::OpenedType::kPopup);
}
int frame_tree_node_id = GetNextUniqueId();
int frame_routing_id = GetNextUniqueId();
auto frame_token = blink::LocalFrameToken();
auto frame = CreateNode<FrameNodeImpl>(process_.get(), page, parent,
frame_tree_node_id, frame_routing_id,
frame_token, browsing_instance_id);
frame->OnNavigationCommitted(GURL(url), /*same document*/ true);
V8DetailedMemoryExecutionContextData::CreateForTesting(frame.get())
->set_v8_bytes_used(memory_usage.bytes);
frames_.push_back(std::move(frame));
FrameNodeImpl* frame_impl = frames_.back().get();
// Create a V8ContextDescription with attribution data for this frame. (In
// production this is done by PerformanceManager monitoring frame lifetime
// events.)
auto description = mojom::V8ContextDescription::New();
description->token = blink::V8ContextToken();
description->world_type = mojom::V8ContextWorldType::kMain;
description->execution_context_token = frame_token;
mojom::IframeAttributionDataPtr attribution;
if (parent) {
// Frame attribution attributes come from the frame's parent node, so
// V8ContextTracker expects an IframeAttributionData. The attribute values
// may be empty.
attribution = mojom::IframeAttributionData::New();
attribution->id = id_attribute;
attribution->src = src_attribute;
} else {
// V8ContextTracker expects no IframeAttributionData.
DCHECK(!id_attribute);
DCHECK(!src_attribute);
}
DCHECK(frame_impl->process_node());
frame_impl->process_node()->OnV8ContextCreated(std::move(description),
std::move(attribution));
return frame_impl;
}
void WebMemoryAggregatorTest::MeasureAndVerify(
FrameNodeImpl* frame,
base::flat_map<std::string, Bytes> expected) {
bool measurement_done = false;
WebMemoryMeasurer web_memory(
frame->frame_token(), V8DetailedMemoryRequest::MeasurementMode::kDefault,
base::BindLambdaForTesting([&measurement_done, &expected](
mojom::WebMemoryMeasurementPtr result) {
base::flat_map<std::string, Bytes> actual;
for (const auto& entry : result->breakdown) {
EXPECT_EQ(1u, entry->attribution.size());
EXPECT_EQ(AttributionScope::kWindow, entry->attribution[0]->scope);
actual[*entry->attribution[0]->url] = Bytes{entry->bytes};
}
EXPECT_EQ(expected, actual);
measurement_done = true;
}));
V8DetailedMemoryProcessData process_data;
web_memory.MeasurementComplete(process_.get(), &process_data);
EXPECT_TRUE(measurement_done);
}
using WebMemoryAggregatorTest = WebMemoryTestHarness;
struct ExpectedMemoryBreakdown {
uint64_t bytes = 0U;
......@@ -275,125 +94,6 @@ std::string MeasurementToJSON(
return json_value.ToJSON();
}
TEST_F(WebMemoryAggregatorTest, MeasurerIncludesSameOriginRelatedFrames) {
auto* main = AddFrameNode("http://foo.com/", Bytes{10u});
AddFrameNode("http://foo.com/iframe", Bytes{20}, main);
MeasureAndVerify(main, {
{"http://foo.com/", Bytes{10u}},
{"http://foo.com/iframe", Bytes{20u}},
});
}
// TODO(b/1085129): Currently WebMemoryMeasurer only includes the results for a
// single process. Once it invokes WebMemoryAggregator, update this test to
// expect cross-origin frames to be included in the aggregation.
TEST_F(WebMemoryAggregatorTest, MeasurerSkipsCrossOriginFrames) {
auto* main = AddFrameNode("http://foo.com", Bytes{10u});
AddFrameNode("http://bar.com/iframe", Bytes{20}, main);
MeasureAndVerify(main, {{"http://foo.com/", Bytes{10u}}});
}
TEST_F(WebMemoryAggregatorTest, MeasurerSkipsCrossBrowserContextGroupFrames) {
auto* main = AddFrameNode("http://foo.com", Bytes{10u});
AddCrossBrowsingInstanceFrameNode("http://foo.com/unrelated", Bytes{20});
MeasureAndVerify(main, {{"http://foo.com/", Bytes{10u}}});
}
TEST_F(WebMemoryAggregatorPMTest, WebMeasureMemory) {
blink::LocalFrameToken frame_token =
blink::LocalFrameToken(main_frame()->GetFrameToken());
// Call WebMeasureMemory on the performance manager sequence and verify that
// the result matches the data provided by the mock reporter.
base::RunLoop run_loop;
auto measurement_callback =
base::BindLambdaForTesting([&](mojom::WebMemoryMeasurementPtr result) {
EXPECT_EQ(1u, result->breakdown.size());
const auto& entry = result->breakdown[0];
EXPECT_EQ(1u, entry->attribution.size());
EXPECT_EQ(kMainFrameUrl, *(entry->attribution[0]->url));
EXPECT_EQ(1001u, entry->bytes);
run_loop.Quit();
});
base::WeakPtr<FrameNode> frame_node_wrapper =
PerformanceManager::GetFrameNodeForRenderFrameHost(main_frame());
PerformanceManager::CallOnGraph(
FROM_HERE, base::BindLambdaForTesting([&]() {
ASSERT_TRUE(frame_node_wrapper);
FrameNode* frame_node = frame_node_wrapper.get();
WebMeasureMemory(frame_node,
mojom::WebMemoryMeasurement::Mode::kDefault,
std::move(measurement_callback));
}));
// Set up and bind the mock reporter.
MockV8DetailedMemoryReporter mock_reporter;
{
auto data = NewPerProcessV8MemoryUsage(1);
AddIsolateMemoryUsage(frame_token, 1001u, data->isolates[0].get());
ExpectBindAndRespondToQuery(&mock_reporter, std::move(data),
main_process_id());
}
// Finally, run all tasks to verify that the memory measurement callback
// is actually invoked. The test will time out if not.
run_loop.Run();
}
TEST_F(WebMemoryAggregatorPMTest, MeasurementInterrupted) {
CreateCrossProcessChildFrame();
blink::LocalFrameToken frame_token =
blink::LocalFrameToken(child_frame()->GetFrameToken());
// Call WebMeasureMemory on the performance manager sequence but delete the
// process being measured before the result arrives.
auto measurement_callback =
base::BindOnce([](mojom::WebMemoryMeasurementPtr result) {
FAIL() << "Measurement callback ran unexpectedly";
});
base::WeakPtr<FrameNode> frame_node_wrapper =
PerformanceManager::GetFrameNodeForRenderFrameHost(child_frame());
PerformanceManager::CallOnGraph(
FROM_HERE, base::BindLambdaForTesting([&]() {
ASSERT_TRUE(frame_node_wrapper);
FrameNode* frame_node = frame_node_wrapper.get();
WebMeasureMemory(frame_node,
mojom::WebMemoryMeasurement::Mode::kDefault,
std::move(measurement_callback));
}));
// Set up and bind the mock reporter.
MockV8DetailedMemoryReporter mock_reporter;
{
::testing::InSequence seq;
ExpectBindReceiver(&mock_reporter, child_process_id());
auto data = NewPerProcessV8MemoryUsage(1);
AddIsolateMemoryUsage(frame_token, 1001u, data->isolates[0].get());
ExpectQueryAndDelayReply(&mock_reporter, base::TimeDelta::FromSeconds(10),
std::move(data));
}
// Verify that requests are sent but reply is not yet received.
task_environment()->FastForwardBy(base::TimeDelta::FromSeconds(5));
::testing::Mock::VerifyAndClearExpectations(&mock_reporter);
// Remove the child frame, which will destroy the child process.
content::RenderFrameHostTester::For(child_frame())->Detach();
// Advance until the reply is expected to make sure nothing explodes.
task_environment()->FastForwardBy(base::TimeDelta::FromSeconds(5));
}
TEST_F(WebMemoryAggregatorTest, CreateBreakdownEntry) {
auto measurement = mojom::WebMemoryMeasurement::New();
auto* breakdown_with_no_url =
......@@ -806,6 +506,8 @@ TEST_F(WebMemoryAggregatorTest, AggregateWindowOpener) {
}
}
} // namespace
} // namespace v8_memory
} // namespace performance_manager
// 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 "components/performance_manager/v8_memory/web_memory_impl.h"
#include <memory>
#include <utility>
#include "base/bind.h"
#include "base/callback.h"
#include "base/check.h"
#include "components/performance_manager/public/graph/frame_node.h"
#include "components/performance_manager/public/graph/process_node.h"
#include "components/performance_manager/public/v8_memory/web_memory.h"
#include "url/gurl.h"
#include "url/origin.h"
namespace performance_manager {
namespace v8_memory {
namespace {
mojom::WebMemoryMeasurementPtr BuildMemoryUsageResult(
const blink::LocalFrameToken& frame_token,
const ProcessNode* process_node) {
const auto& frame_nodes = process_node->GetFrameNodes();
// Find the frame that made the request.
const FrameNode* requesting_frame = nullptr;
for (auto* frame_node : frame_nodes) {
if (frame_node->GetFrameToken() == frame_token) {
requesting_frame = frame_node;
break;
}
}
if (!requesting_frame) {
// The frame no longer exists.
return mojom::WebMemoryMeasurement::New();
}
auto result = mojom::WebMemoryMeasurement::New();
for (const FrameNode* frame_node : frame_nodes) {
if (frame_node->GetBrowsingInstanceId() !=
requesting_frame->GetBrowsingInstanceId()) {
continue;
}
if (frame_node->GetURL().GetOrigin() !=
requesting_frame->GetURL().GetOrigin()) {
continue;
}
auto* data = v8_memory::V8DetailedMemoryExecutionContextData::ForFrameNode(
frame_node);
if (!data) {
continue;
}
auto attribution = mojom::WebMemoryAttribution::New();
attribution->url = frame_node->GetURL().spec();
attribution->scope = mojom::WebMemoryAttribution::Scope::kWindow;
auto entry = mojom::WebMemoryBreakdownEntry::New();
entry->bytes = data->v8_bytes_used();
entry->attribution.push_back(std::move(attribution));
result->breakdown.push_back(std::move(entry));
}
return result;
}
v8_memory::V8DetailedMemoryRequest::MeasurementMode
WebMeasurementModeToRequestMeasurementMode(
mojom::WebMemoryMeasurement::Mode mode) {
switch (mode) {
case mojom::WebMemoryMeasurement::Mode::kDefault:
return v8_memory::V8DetailedMemoryRequest::MeasurementMode::kDefault;
case mojom::WebMemoryMeasurement::Mode::kEager:
return v8_memory::V8DetailedMemoryRequest::MeasurementMode::
kEagerForTesting;
}
}
} // anonymous namespace
////////////////////////////////////////////////////////////////////////////////
// WebMemoryMeasurer
WebMemoryMeasurer::WebMemoryMeasurer(
const blink::LocalFrameToken& frame_token,
V8DetailedMemoryRequest::MeasurementMode mode,
MeasurementCallback callback)
: frame_token_(frame_token),
callback_(std::move(callback)),
request_(std::make_unique<V8DetailedMemoryRequestOneShot>(mode)) {}
WebMemoryMeasurer::~WebMemoryMeasurer() = default;
void WebMemoryMeasurer::MeasurementComplete(
const ProcessNode* process_node,
const V8DetailedMemoryProcessData*) {
// TODO(crbug.com/1085129): Use WebMemoryAggregator here instead of
// BuildMemoryUsageResult.
std::move(callback_).Run(BuildMemoryUsageResult(frame_token_, process_node));
}
////////////////////////////////////////////////////////////////////////////////
// Free functions
// Implements the public function in public/v8_memory/web_memory.h
void WebMeasureMemory(
const FrameNode* frame_node,
mojom::WebMemoryMeasurement::Mode mode,
base::OnceCallback<void(mojom::WebMemoryMeasurementPtr)> callback) {
auto measurer = std::make_unique<WebMemoryMeasurer>(
frame_node->GetFrameToken(),
WebMeasurementModeToRequestMeasurementMode(mode), std::move(callback));
// Create a measurement complete callback to own |measurer|. It
// will be deleted when the callback is executed or dropped.
V8DetailedMemoryRequestOneShot* request = measurer->request();
auto measurement_complete_callback = base::BindOnce(
&WebMemoryMeasurer::MeasurementComplete, std::move(measurer));
// Start memory measurement for the process of the given frame.
request->StartMeasurement(frame_node->GetProcessNode(),
std::move(measurement_complete_callback));
}
} // namespace v8_memory
} // namespace performance_manager
// 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.
#ifndef COMPONENTS_PERFORMANCE_MANAGER_V8_MEMORY_WEB_MEMORY_IMPL_H_
#define COMPONENTS_PERFORMANCE_MANAGER_V8_MEMORY_WEB_MEMORY_IMPL_H_
#include <memory>
#include "base/callback.h"
#include "components/performance_manager/public/mojom/web_memory.mojom.h"
#include "components/performance_manager/public/v8_memory/v8_detailed_memory.h"
#include "third_party/blink/public/common/tokens/tokens.h"
namespace performance_manager {
class ProcessNode;
namespace v8_memory {
// A helper class for implementing WebMeasureMemory(). This manages a request
// object that sends a V8 detailed memory request to the renderer, and formats
// the result into a mojom::WebMemoryMeasurement.
// TODO(crbug.com/1085129): Extend this to measure all renderers that are
// reachable from the requesting node.
class WebMemoryMeasurer {
public:
using MeasurementCallback =
base::OnceCallback<void(mojom::WebMemoryMeasurementPtr)>;
WebMemoryMeasurer(const blink::LocalFrameToken&,
V8DetailedMemoryRequest::MeasurementMode,
MeasurementCallback);
~WebMemoryMeasurer();
WebMemoryMeasurer(const WebMemoryMeasurer& other) = delete;
WebMemoryMeasurer& operator=(const WebMemoryMeasurer& other) = delete;
V8DetailedMemoryRequestOneShot* request() const { return request_.get(); }
// A callback for V8DetailedMemoryRequestOneShot.
void MeasurementComplete(const ProcessNode*,
const V8DetailedMemoryProcessData*);
private:
blink::LocalFrameToken frame_token_;
MeasurementCallback callback_;
std::unique_ptr<V8DetailedMemoryRequestOneShot> request_;
};
} // namespace v8_memory
} // namespace performance_manager
#endif // COMPONENTS_PERFORMANCE_MANAGER_V8_MEMORY_WEB_MEMORY_IMPL_H_
// 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 "components/performance_manager/v8_memory/web_memory_impl.h"
#include <memory>
#include <string>
#include <utility>
#include "base/containers/flat_map.h"
#include "base/memory/weak_ptr.h"
#include "base/run_loop.h"
#include "base/test/bind.h"
#include "base/test/task_environment.h"
#include "base/time/time.h"
#include "components/performance_manager/graph/frame_node_impl.h"
#include "components/performance_manager/public/graph/frame_node.h"
#include "components/performance_manager/public/mojom/web_memory.mojom.h"
#include "components/performance_manager/public/performance_manager.h"
#include "components/performance_manager/public/v8_memory/v8_detailed_memory.h"
#include "components/performance_manager/public/v8_memory/web_memory.h"
#include "components/performance_manager/v8_memory/v8_memory_test_helpers.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/test/test_renderer_host.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/common/tokens/tokens.h"
namespace performance_manager {
namespace v8_memory {
namespace {
using WebMemoryImplPMTest = V8MemoryPerformanceManagerTestHarness;
class WebMemoryImplTest : public WebMemoryTestHarness {
protected:
void MeasureAndVerify(FrameNodeImpl* frame,
base::flat_map<std::string, Bytes> expected);
};
void WebMemoryImplTest::MeasureAndVerify(
FrameNodeImpl* frame,
base::flat_map<std::string, Bytes> expected) {
bool measurement_done = false;
WebMemoryMeasurer web_memory(
frame->frame_token(), V8DetailedMemoryRequest::MeasurementMode::kDefault,
base::BindLambdaForTesting([&measurement_done, &expected](
mojom::WebMemoryMeasurementPtr result) {
base::flat_map<std::string, Bytes> actual;
for (const auto& entry : result->breakdown) {
EXPECT_EQ(1u, entry->attribution.size());
EXPECT_EQ(mojom::WebMemoryAttribution::Scope::kWindow,
entry->attribution[0]->scope);
actual[*entry->attribution[0]->url] = Bytes{entry->bytes};
}
EXPECT_EQ(expected, actual);
measurement_done = true;
}));
V8DetailedMemoryProcessData process_data;
web_memory.MeasurementComplete(process_node(), &process_data);
EXPECT_TRUE(measurement_done);
}
TEST_F(WebMemoryImplTest, MeasurerIncludesSameOriginRelatedFrames) {
auto* main = AddFrameNode("http://foo.com/", Bytes{10u});
AddFrameNode("http://foo.com/iframe", Bytes{20}, main);
MeasureAndVerify(main, {
{"http://foo.com/", Bytes{10u}},
{"http://foo.com/iframe", Bytes{20u}},
});
}
// TODO(b/1085129): Currently WebMemoryMeasurer only includes the results for a
// single process. Once it invokes WebMemoryAggregator, update this test to
// expect cross-origin frames to be included in the aggregation.
TEST_F(WebMemoryImplTest, MeasurerSkipsCrossOriginFrames) {
auto* main = AddFrameNode("http://foo.com", Bytes{10u});
AddFrameNode("http://bar.com/iframe", Bytes{20}, main);
MeasureAndVerify(main, {{"http://foo.com/", Bytes{10u}}});
}
TEST_F(WebMemoryImplTest, MeasurerSkipsCrossBrowserContextGroupFrames) {
auto* main = AddFrameNode("http://foo.com", Bytes{10u});
AddCrossBrowsingInstanceFrameNode("http://foo.com/unrelated", Bytes{20});
MeasureAndVerify(main, {{"http://foo.com/", Bytes{10u}}});
}
TEST_F(WebMemoryImplPMTest, WebMeasureMemory) {
blink::LocalFrameToken frame_token =
blink::LocalFrameToken(main_frame()->GetFrameToken());
// Call WebMeasureMemory on the performance manager sequence and verify that
// the result matches the data provided by the mock reporter.
base::RunLoop run_loop;
auto measurement_callback =
base::BindLambdaForTesting([&](mojom::WebMemoryMeasurementPtr result) {
EXPECT_EQ(1u, result->breakdown.size());
const auto& entry = result->breakdown[0];
EXPECT_EQ(1u, entry->attribution.size());
EXPECT_EQ(kMainFrameUrl, *(entry->attribution[0]->url));
EXPECT_EQ(1001u, entry->bytes);
run_loop.Quit();
});
base::WeakPtr<FrameNode> frame_node_wrapper =
PerformanceManager::GetFrameNodeForRenderFrameHost(main_frame());
PerformanceManager::CallOnGraph(
FROM_HERE, base::BindLambdaForTesting([&]() {
ASSERT_TRUE(frame_node_wrapper);
FrameNode* frame_node = frame_node_wrapper.get();
WebMeasureMemory(frame_node,
mojom::WebMemoryMeasurement::Mode::kDefault,
std::move(measurement_callback));
}));
// Set up and bind the mock reporter.
MockV8DetailedMemoryReporter mock_reporter;
{
auto data = NewPerProcessV8MemoryUsage(1);
AddIsolateMemoryUsage(frame_token, 1001u, data->isolates[0].get());
ExpectBindAndRespondToQuery(&mock_reporter, std::move(data),
main_process_id());
}
// Finally, run all tasks to verify that the memory measurement callback
// is actually invoked. The test will time out if not.
run_loop.Run();
}
TEST_F(WebMemoryImplPMTest, MeasurementInterrupted) {
CreateCrossProcessChildFrame();
blink::LocalFrameToken frame_token =
blink::LocalFrameToken(child_frame()->GetFrameToken());
// Call WebMeasureMemory on the performance manager sequence but delete the
// process being measured before the result arrives.
auto measurement_callback =
base::BindOnce([](mojom::WebMemoryMeasurementPtr result) {
FAIL() << "Measurement callback ran unexpectedly";
});
base::WeakPtr<FrameNode> frame_node_wrapper =
PerformanceManager::GetFrameNodeForRenderFrameHost(child_frame());
PerformanceManager::CallOnGraph(
FROM_HERE, base::BindLambdaForTesting([&]() {
ASSERT_TRUE(frame_node_wrapper);
FrameNode* frame_node = frame_node_wrapper.get();
WebMeasureMemory(frame_node,
mojom::WebMemoryMeasurement::Mode::kDefault,
std::move(measurement_callback));
}));
// Set up and bind the mock reporter.
MockV8DetailedMemoryReporter mock_reporter;
{
::testing::InSequence seq;
ExpectBindReceiver(&mock_reporter, child_process_id());
auto data = NewPerProcessV8MemoryUsage(1);
AddIsolateMemoryUsage(frame_token, 1001u, data->isolates[0].get());
ExpectQueryAndDelayReply(&mock_reporter, base::TimeDelta::FromSeconds(10),
std::move(data));
}
// Verify that requests are sent but reply is not yet received.
task_environment()->FastForwardBy(base::TimeDelta::FromSeconds(5));
::testing::Mock::VerifyAndClearExpectations(&mock_reporter);
// Remove the child frame, which will destroy the child process.
content::RenderFrameHostTester::For(child_frame())->Detach();
// Advance until the reply is expected to make sure nothing explodes.
task_environment()->FastForwardBy(base::TimeDelta::FromSeconds(5));
}
} // namespace
} // namespace v8_memory
} // namespace performance_manager
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