blob: b6892a74836852b0329b2324c8438f93d50de310 [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 "components/performance_manager/public/v8_memory/v8_detailed_memory.h"
#include <memory>
#include <tuple>
#include <utility>
#include "base/bind.h"
#include "base/callback.h"
#include "base/command_line.h"
#include "base/memory/scoped_refptr.h"
#include "base/memory/weak_ptr.h"
#include "base/run_loop.h"
#include "base/single_thread_task_runner.h"
#include "base/test/bind.h"
#include "base/test/gtest_util.h"
#include "base/time/time.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/graph/worker_node_impl.h"
#include "components/performance_manager/public/performance_manager.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/v8_memory/v8_memory_test_helpers.h"
#include "content/public/browser/global_routing_id.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/browser/web_contents.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 {
using ::testing::_;
using ::testing::InSequence;
using ::testing::Mock;
using ::testing::StrictMock;
constexpr uint64_t kUnassociatedBytes = 0xABBA;
namespace {
class LenientMockV8DetailedMemoryObserver : public V8DetailedMemoryObserver {
public:
MOCK_METHOD(void,
OnV8MemoryMeasurementAvailable,
(const ProcessNode* process_node,
const V8DetailedMemoryProcessData* process_data),
(override));
void ExpectObservationOnProcess(
const ProcessNode* process_node,
uint64_t expected_unassociated_v8_bytes_used) {
using ::testing::Eq;
using ::testing::Property;
EXPECT_CALL(
*this,
OnV8MemoryMeasurementAvailable(
process_node,
Property(&V8DetailedMemoryProcessData::unassociated_v8_bytes_used,
Eq(expected_unassociated_v8_bytes_used))));
}
};
using MockV8DetailedMemoryObserver =
StrictMock<LenientMockV8DetailedMemoryObserver>;
class LenientMockV8DetailedMemoryObserverAnySeq
: public V8DetailedMemoryObserverAnySeq {
public:
MOCK_METHOD(void,
OnV8MemoryMeasurementAvailable,
(RenderProcessHostId render_process_host_id,
const V8DetailedMemoryProcessData& process_data,
const V8DetailedMemoryObserverAnySeq::FrameDataMap& frame_data),
(override));
};
using MockV8DetailedMemoryObserverAnySeq =
StrictMock<LenientMockV8DetailedMemoryObserverAnySeq>;
// The mode enum used in the API.
using MeasurementMode = V8DetailedMemoryRequest::MeasurementMode;
// An arbitrary object used to test object lifetimes with WeakPtr.
class LifetimeTestObject : public base::SupportsWeakPtr<LifetimeTestObject> {
public:
LifetimeTestObject() = default;
~LifetimeTestObject() = default;
};
constexpr base::TimeDelta kMinTimeBetweenRequests =
base::TimeDelta::FromSeconds(30);
} // namespace
class V8DetailedMemoryDecoratorTest : public GraphTestHarness,
public V8MemoryTestBase {
public:
V8DetailedMemoryDecoratorTest() {
GetGraphFeaturesHelper().EnableExecutionContextRegistry();
}
scoped_refptr<base::SingleThreadTaskRunner> GetMainThreadTaskRunner()
override {
return task_env().GetMainThreadTaskRunner();
}
};
// kBounded mode and kEagerForTesting mode behave identically as far as
// V8DetailedMemoryDecorator is concerned. (The differences are all on the
// renderer side.) So mode tests hardcode kLazy mode and use a parameter to
// choose which of the two to use for bounded mode.
class V8DetailedMemoryDecoratorModeTest
: public V8DetailedMemoryDecoratorTest,
public ::testing::WithParamInterface<
std::pair<MeasurementMode, ExpectedMode>> {
public:
V8DetailedMemoryDecoratorModeTest() {
internal::SetEagerMemoryMeasurementEnabledForTesting(true);
std::tie(bounded_mode_, expected_bounded_mode_) = GetParam();
}
~V8DetailedMemoryDecoratorModeTest() override {
internal::SetEagerMemoryMeasurementEnabledForTesting(false);
}
protected:
// The mode that will be used for bounded requests.
MeasurementMode bounded_mode_;
// The expected mojo mode parameter for bounded requests.
ExpectedMode expected_bounded_mode_;
};
class V8DetailedMemoryDecoratorSingleProcessModeTest
: public V8DetailedMemoryDecoratorTest,
public ::testing::WithParamInterface<MeasurementMode> {
public:
V8DetailedMemoryDecoratorSingleProcessModeTest()
: single_process_mode_(GetParam()) {}
protected:
// The mode that will be used for single-process requests.
MeasurementMode single_process_mode_;
};
using V8DetailedMemoryDecoratorDeathTest = V8DetailedMemoryDecoratorTest;
using V8DetailedMemoryRequestAnySeqTest = V8MemoryPerformanceManagerTestHarness;
TEST_F(V8DetailedMemoryDecoratorTest, InstantiateOnEmptyGraph) {
V8DetailedMemoryRequest memory_request(kMinTimeBetweenRequests, graph());
MockV8DetailedMemoryReporter mock_reporter;
auto data = NewPerProcessV8MemoryUsage(1);
data->isolates[0]->unassociated_bytes_used = kUnassociatedBytes;
ExpectBindAndRespondToQuery(&mock_reporter, std::move(data));
// Create a process node and validate that it gets a request.
auto process = CreateNode<ProcessNodeImpl>(
content::PROCESS_TYPE_RENDERER,
RenderProcessHostProxy::CreateForTesting(kTestProcessID));
// Data should not be available until the measurement is taken.
EXPECT_FALSE(V8DetailedMemoryProcessData::ForProcessNode(process.get()));
// Run until idle to make sure the measurement isn't a hard loop.
task_env().RunUntilIdle();
EXPECT_TRUE(V8DetailedMemoryProcessData::ForProcessNode(process.get()));
EXPECT_EQ(kUnassociatedBytes,
V8DetailedMemoryProcessData::ForProcessNode(process.get())
->unassociated_v8_bytes_used());
}
TEST_F(V8DetailedMemoryDecoratorTest, InstantiateOnNonEmptyGraph) {
// Instantiate the decorator with an existing process node and validate that
// it gets a request.
auto process = CreateNode<ProcessNodeImpl>(
content::PROCESS_TYPE_RENDERER,
RenderProcessHostProxy::CreateForTesting(kTestProcessID));
MockV8DetailedMemoryReporter mock_reporter;
auto data = NewPerProcessV8MemoryUsage(1);
data->isolates[0]->unassociated_bytes_used = kUnassociatedBytes;
ExpectBindAndRespondToQuery(&mock_reporter, std::move(data));
V8DetailedMemoryRequest memory_request(kMinTimeBetweenRequests, graph());
// Data should not be available until the measurement is taken.
EXPECT_FALSE(V8DetailedMemoryProcessData::ForProcessNode(process.get()));
// Run until idle to make sure the measurement isn't a hard loop.
task_env().RunUntilIdle();
EXPECT_TRUE(V8DetailedMemoryProcessData::ForProcessNode(process.get()));
EXPECT_EQ(kUnassociatedBytes,
V8DetailedMemoryProcessData::ForProcessNode(process.get())
->unassociated_v8_bytes_used());
}
TEST_F(V8DetailedMemoryDecoratorTest, OnlyMeasureRenderers) {
V8DetailedMemoryRequest memory_request(kMinTimeBetweenRequests, graph());
for (int type = content::PROCESS_TYPE_BROWSER;
type < content::PROCESS_TYPE_CONTENT_END; ++type) {
if (type == content::PROCESS_TYPE_RENDERER)
continue;
// Instantiate a non-renderer process node and validate that it causes no
// bind requests.
EXPECT_CALL(*this, BindReceiverWithProxyHost(_, _)).Times(0);
auto process = CreateNode<ProcessNodeImpl>(
static_cast<content::ProcessType>(type),
RenderProcessHostProxy::CreateForTesting(kTestProcessID));
task_env().RunUntilIdle();
Mock::VerifyAndClearExpectations(this);
}
}
TEST_F(V8DetailedMemoryDecoratorTest, OneShot) {
// Create 2 renderer processes. Create one request that measures both of
// them, and a one-shot request that measures only one.
constexpr RenderProcessHostId kProcessId1 = RenderProcessHostId(0xFAB);
auto process1 = CreateNode<ProcessNodeImpl>(
content::PROCESS_TYPE_RENDERER,
RenderProcessHostProxy::CreateForTesting(kProcessId1));
constexpr RenderProcessHostId kProcessId2 = RenderProcessHostId(0xBAF);
auto process2 = CreateNode<ProcessNodeImpl>(
content::PROCESS_TYPE_RENDERER,
RenderProcessHostProxy::CreateForTesting(kProcessId2));
// Set the all process request to only send once within the test.
V8DetailedMemoryRequest all_process_request(kMinTimeBetweenRequests * 100);
all_process_request.StartMeasurement(graph());
// Create a mock reporter for each process and expect a query and reply on
// each.
MockV8DetailedMemoryReporter mock_reporter1;
{
auto data = NewPerProcessV8MemoryUsage(1);
data->isolates[0]->unassociated_bytes_used = 1ULL;
ExpectBindAndRespondToQuery(&mock_reporter1, std::move(data), kProcessId1);
}
MockV8DetailedMemoryReporter mock_reporter2;
{
auto data = NewPerProcessV8MemoryUsage(1);
data->isolates[0]->unassociated_bytes_used = 2ULL;
ExpectBindAndRespondToQuery(&mock_reporter2, std::move(data), kProcessId2);
}
task_env().RunUntilIdle();
Mock::VerifyAndClearExpectations(&mock_reporter1);
Mock::VerifyAndClearExpectations(&mock_reporter2);
// Create a one-shot request for process1 and expect the callback to be
// called only for that process.
{
auto data = NewPerProcessV8MemoryUsage(1);
data->isolates[0]->unassociated_bytes_used = 3ULL;
ExpectQueryAndReply(&mock_reporter1, std::move(data));
}
uint64_t unassociated_v8_bytes_used = 0;
V8DetailedMemoryRequestOneShot process1_request;
process1_request.StartMeasurement(
process1.get(), base::BindLambdaForTesting(
[&unassociated_v8_bytes_used, &process1](
const ProcessNode* process_node,
const V8DetailedMemoryProcessData* process_data) {
ASSERT_TRUE(process_data);
EXPECT_EQ(process_node, process1.get());
unassociated_v8_bytes_used =
process_data->unassociated_v8_bytes_used();
}));
task_env().RunUntilIdle();
Mock::VerifyAndClearExpectations(&mock_reporter1);
Mock::VerifyAndClearExpectations(&mock_reporter2);
EXPECT_EQ(unassociated_v8_bytes_used, 3ULL);
}
TEST_F(V8DetailedMemoryDecoratorTest, OneShotLifetime) {
auto process = CreateNode<ProcessNodeImpl>(
content::PROCESS_TYPE_RENDERER,
RenderProcessHostProxy::CreateForTesting(kTestProcessID));
MockV8DetailedMemoryReporter mock_reporter;
{
InSequence seq;
ExpectBindReceiver(&mock_reporter);
auto data = NewPerProcessV8MemoryUsage(1);
data->isolates[0]->unassociated_bytes_used = 1ULL;
ExpectQueryAndDelayReply(&mock_reporter, base::TimeDelta::FromSeconds(10),
std::move(data));
}
// Create a one-shot request, but delete it before the result arrives.
auto doomed_request = std::make_unique<V8DetailedMemoryRequestOneShot>(
process.get(),
base::BindOnce([](const ProcessNode* process_node,
const V8DetailedMemoryProcessData* process_data) {
FAIL() << "Callback called after request deleted.";
}));
// Verify that the request is sent but the reply is not yet received.
task_env().FastForwardBy(base::TimeDelta::FromSeconds(5));
Mock::VerifyAndClearExpectations(&mock_reporter);
doomed_request.reset();
task_env().FastForwardBy(base::TimeDelta::FromSeconds(5));
// Create a request that is deleted from within its own callback and make
// sure nothing explodes.
{
auto data = NewPerProcessV8MemoryUsage(1);
data->isolates[0]->unassociated_bytes_used = 2ULL;
ExpectQueryAndReply(&mock_reporter, std::move(data));
}
uint64_t unassociated_v8_bytes_used = 0;
doomed_request = std::make_unique<V8DetailedMemoryRequestOneShot>(
process.get(), base::BindLambdaForTesting(
[&](const ProcessNode* process_node,
const V8DetailedMemoryProcessData* process_data) {
doomed_request.reset();
ASSERT_TRUE(process_data);
unassociated_v8_bytes_used =
process_data->unassociated_v8_bytes_used();
}));
task_env().RunUntilIdle();
Mock::VerifyAndClearExpectations(&mock_reporter);
EXPECT_EQ(unassociated_v8_bytes_used, 2ULL);
// Ensure that resource-owning callbacks are freed when there is no response
// because the process dies.
{
auto data = NewPerProcessV8MemoryUsage(1);
data->isolates[0]->unassociated_bytes_used = 3ULL;
ExpectQueryAndDelayReply(&mock_reporter, base::TimeDelta::FromSeconds(10),
std::move(data));
}
auto lifetime_test = std::make_unique<LifetimeTestObject>();
auto weak_lifetime_test = lifetime_test->AsWeakPtr();
V8DetailedMemoryRequestOneShot unfinished_request(
process.get(),
base::BindOnce(
[](std::unique_ptr<LifetimeTestObject>, const ProcessNode*,
const V8DetailedMemoryProcessData*) {
FAIL() << "Callback called after process deleted.";
},
// Pass ownership to the callback. The object should be deleted if the
// callback is not called.
std::move(lifetime_test)));
// Verify that requests are sent but reply is not yet received.
task_env().FastForwardBy(base::TimeDelta::FromSeconds(5));
Mock::VerifyAndClearExpectations(&mock_reporter);
ASSERT_TRUE(weak_lifetime_test);
process.reset();
task_env().FastForwardBy(base::TimeDelta::FromSeconds(5));
EXPECT_FALSE(weak_lifetime_test);
}
TEST_F(V8DetailedMemoryDecoratorTest, OneShotLifetimeAtExit) {
auto process = CreateNode<ProcessNodeImpl>(
content::PROCESS_TYPE_RENDERER,
RenderProcessHostProxy::CreateForTesting(kTestProcessID));
// Ensure that resource-owning callbacks are freed when there is no response
// because the browser is exiting (simulated by destroying the decorator).
MockV8DetailedMemoryReporter mock_reporter;
{
InSequence seq;
ExpectBindReceiver(&mock_reporter);
auto data = NewPerProcessV8MemoryUsage(1);
data->isolates[0]->unassociated_bytes_used = 1ULL;
ExpectQueryAndDelayReply(&mock_reporter, base::TimeDelta::FromSeconds(10),
std::move(data));
}
auto lifetime_test = std::make_unique<LifetimeTestObject>();
auto weak_lifetime_test = lifetime_test->AsWeakPtr();
V8DetailedMemoryRequestOneShot unfinished_request(
process.get(),
base::BindOnce(
[](std::unique_ptr<LifetimeTestObject>, const ProcessNode*,
const V8DetailedMemoryProcessData*) {
FAIL() << "Callback called after measurements cancelled.";
},
// Pass ownership to the callback. The object should be deleted if the
// callback is not called.
std::move(lifetime_test)));
// Verify that requests are sent but reply is not yet received.
task_env().FastForwardBy(base::TimeDelta::FromSeconds(5));
Mock::VerifyAndClearExpectations(&mock_reporter);
ASSERT_TRUE(weak_lifetime_test);
internal::DestroyV8DetailedMemoryDecoratorForTesting(graph());
task_env().FastForwardBy(base::TimeDelta::FromSeconds(5));
EXPECT_FALSE(weak_lifetime_test);
}
TEST_F(V8DetailedMemoryDecoratorTest, QueryRateIsLimited) {
auto process = CreateNode<ProcessNodeImpl>(
content::PROCESS_TYPE_RENDERER,
RenderProcessHostProxy::CreateForTesting(kTestProcessID));
MockV8DetailedMemoryReporter mock_reporter;
{
auto data = NewPerProcessV8MemoryUsage(1);
// Response to request 1.
data->isolates[0]->unassociated_bytes_used = 1;
ExpectBindAndRespondToQuery(&mock_reporter, std::move(data));
}
V8DetailedMemoryRequest memory_request(kMinTimeBetweenRequests, graph());
// Run until idle to make sure the measurement isn't a hard loop.
task_env().RunUntilIdle();
EXPECT_TRUE(V8DetailedMemoryProcessData::ForProcessNode(process.get()));
EXPECT_EQ(1u, V8DetailedMemoryProcessData::ForProcessNode(process.get())
->unassociated_v8_bytes_used());
// There shouldn't be an additional request this soon.
task_env().FastForwardBy(kMinTimeBetweenRequests / 2);
Mock::VerifyAndClearExpectations(&mock_reporter);
// Set up another request and capture the callback for later invocation.
MockV8DetailedMemoryReporter::GetV8MemoryUsageCallback callback;
ExpectQuery(
&mock_reporter,
base::BindLambdaForTesting(
[&callback](MockV8DetailedMemoryReporter::GetV8MemoryUsageCallback
result_callback) {
callback = std::move(result_callback);
}));
// Skip forward to when another request should be issued.
task_env().FastForwardBy(kMinTimeBetweenRequests);
ASSERT_FALSE(callback.is_null());
// Skip forward a long while, and validate that no additional requests are
// issued until the pending request has completed.
task_env().FastForwardBy(10 * kMinTimeBetweenRequests);
Mock::VerifyAndClearExpectations(&mock_reporter);
EXPECT_TRUE(V8DetailedMemoryProcessData::ForProcessNode(process.get()));
EXPECT_EQ(1u, V8DetailedMemoryProcessData::ForProcessNode(process.get())
->unassociated_v8_bytes_used());
// Expect another query once completing the query above.
{
auto data = NewPerProcessV8MemoryUsage(1);
// Response to request 3.
data->isolates[0]->unassociated_bytes_used = 3;
ExpectQueryAndReply(&mock_reporter, std::move(data));
}
// Reply to the request above.
{
auto data = NewPerProcessV8MemoryUsage(1);
// Response to request 2.
data->isolates[0]->unassociated_bytes_used = 2;
std::move(callback).Run(std::move(data));
}
task_env().RunUntilIdle();
// This should have updated all the way to the third response.
EXPECT_TRUE(V8DetailedMemoryProcessData::ForProcessNode(process.get()));
EXPECT_EQ(3u, V8DetailedMemoryProcessData::ForProcessNode(process.get())
->unassociated_v8_bytes_used());
// Despite the long delay to respond to request 2, there shouldn't be another
// request until kMinTimeBetweenRequests has expired.
task_env().FastForwardBy(kMinTimeBetweenRequests / 2);
Mock::VerifyAndClearExpectations(&mock_reporter);
}
TEST_F(V8DetailedMemoryDecoratorTest, MultipleProcessesHaveDistinctSchedules) {
V8DetailedMemoryRequest memory_request(kMinTimeBetweenRequests, graph());
// Create a process node and validate that it gets a request.
MockV8DetailedMemoryReporter reporter1;
{
auto data = NewPerProcessV8MemoryUsage(1);
data->isolates[0]->unassociated_bytes_used = 1;
ExpectBindAndRespondToQuery(&reporter1, std::move(data));
}
auto process1 = CreateNode<ProcessNodeImpl>(
content::PROCESS_TYPE_RENDERER,
RenderProcessHostProxy::CreateForTesting(kTestProcessID));
task_env().FastForwardBy(kMinTimeBetweenRequests / 4);
Mock::VerifyAndClearExpectations(&reporter1);
// Create a second process node and validate that it gets a request.
MockV8DetailedMemoryReporter reporter2;
{
auto data = NewPerProcessV8MemoryUsage(1);
data->isolates[0]->unassociated_bytes_used = 2;
ExpectBindAndRespondToQuery(&reporter2, std::move(data));
}
auto process2 = CreateNode<ProcessNodeImpl>(
content::PROCESS_TYPE_RENDERER,
RenderProcessHostProxy::CreateForTesting(kTestProcessID));
task_env().RunUntilIdle();
Mock::VerifyAndClearExpectations(&reporter2);
EXPECT_TRUE(V8DetailedMemoryProcessData::ForProcessNode(process1.get()));
EXPECT_EQ(1u, V8DetailedMemoryProcessData::ForProcessNode(process1.get())
->unassociated_v8_bytes_used());
EXPECT_TRUE(V8DetailedMemoryProcessData::ForProcessNode(process2.get()));
EXPECT_EQ(2u, V8DetailedMemoryProcessData::ForProcessNode(process2.get())
->unassociated_v8_bytes_used());
// Capture the request time from each process.
auto capture_time_lambda =
[](base::TimeTicks* request_time,
MockV8DetailedMemoryReporter::GetV8MemoryUsageCallback callback) {
*request_time = base::TimeTicks::Now();
std::move(callback).Run(NewPerProcessV8MemoryUsage(1));
};
base::TimeTicks process1_request_time;
ExpectQuery(&reporter1,
base::BindRepeating(capture_time_lambda,
base::Unretained(&process1_request_time)));
base::TimeTicks process2_request_time;
ExpectQuery(&reporter2,
base::BindRepeating(capture_time_lambda,
base::Unretained(&process2_request_time)));
task_env().FastForwardBy(kMinTimeBetweenRequests * 1.25);
// Check that both processes got polled, and that process2 was polled after
// process1.
EXPECT_FALSE(process1_request_time.is_null());
EXPECT_FALSE(process2_request_time.is_null());
EXPECT_GT(process2_request_time, process1_request_time);
}
TEST_F(V8DetailedMemoryDecoratorTest, MultipleIsolatesInRenderer) {
V8DetailedMemoryRequest memory_request(kMinTimeBetweenRequests, graph());
MockV8DetailedMemoryReporter reporter;
auto process = CreateNode<ProcessNodeImpl>(
content::PROCESS_TYPE_RENDERER,
RenderProcessHostProxy::CreateForTesting(kTestProcessID));
// Create a couple of frames with specified IDs.
auto page = CreateNode<PageNodeImpl>();
blink::LocalFrameToken frame1_id = blink::LocalFrameToken();
auto frame1 = CreateNode<FrameNodeImpl>(process.get(), page.get(), nullptr, 1,
2, frame1_id);
blink::LocalFrameToken frame2_id = blink::LocalFrameToken();
auto frame2 = CreateNode<FrameNodeImpl>(process.get(), page.get(), nullptr, 3,
4, frame2_id);
{
auto data = NewPerProcessV8MemoryUsage(2);
AddIsolateMemoryUsage(frame1_id, 1001u, data->isolates[0].get());
AddIsolateMemoryUsage(frame2_id, 1002u, data->isolates[1].get());
ExpectBindAndRespondToQuery(&reporter, std::move(data));
}
task_env().RunUntilIdle();
Mock::VerifyAndClearExpectations(&reporter);
ASSERT_TRUE(V8DetailedMemoryExecutionContextData::ForFrameNode(frame1.get()));
EXPECT_EQ(1001u,
V8DetailedMemoryExecutionContextData::ForFrameNode(frame1.get())
->v8_bytes_used());
ASSERT_TRUE(V8DetailedMemoryExecutionContextData::ForFrameNode(frame2.get()));
EXPECT_EQ(1002u,
V8DetailedMemoryExecutionContextData::ForFrameNode(frame2.get())
->v8_bytes_used());
}
TEST_F(V8DetailedMemoryDecoratorTest, DataIsDistributed) {
V8DetailedMemoryRequest memory_request(kMinTimeBetweenRequests, graph());
MockV8DetailedMemoryReporter reporter;
{
auto data = NewPerProcessV8MemoryUsage(1);
// Add data for an unknown frame.
AddIsolateMemoryUsage(blink::LocalFrameToken(), 1024u,
data->isolates[0].get());
ExpectBindAndRespondToQuery(&reporter, std::move(data));
}
auto process = CreateNode<ProcessNodeImpl>(
content::PROCESS_TYPE_RENDERER,
RenderProcessHostProxy::CreateForTesting(kTestProcessID));
task_env().RunUntilIdle();
Mock::VerifyAndClearExpectations(&reporter);
// Since the frame was unknown, the usage should have accrued to unassociated.
EXPECT_TRUE(V8DetailedMemoryProcessData::ForProcessNode(process.get()));
EXPECT_EQ(1024u, V8DetailedMemoryProcessData::ForProcessNode(process.get())
->unassociated_v8_bytes_used());
// Create a couple of frames with specified IDs.
auto page = CreateNode<PageNodeImpl>();
blink::LocalFrameToken frame1_id = blink::LocalFrameToken();
auto frame1 = CreateNode<FrameNodeImpl>(process.get(), page.get(), nullptr, 1,
2, frame1_id);
blink::LocalFrameToken frame2_id = blink::LocalFrameToken();
auto frame2 = CreateNode<FrameNodeImpl>(process.get(), page.get(), nullptr, 3,
4, frame2_id);
{
auto data = NewPerProcessV8MemoryUsage(1);
AddIsolateMemoryUsage(frame1_id, 1001u, data->isolates[0].get());
AddIsolateMemoryUsage(frame2_id, 1002u, data->isolates[0].get());
ExpectQueryAndReply(&reporter, std::move(data));
}
task_env().FastForwardBy(kMinTimeBetweenRequests * 1.5);
Mock::VerifyAndClearExpectations(&reporter);
ASSERT_TRUE(V8DetailedMemoryExecutionContextData::ForFrameNode(frame1.get()));
EXPECT_EQ(1001u,
V8DetailedMemoryExecutionContextData::ForFrameNode(frame1.get())
->v8_bytes_used());
ASSERT_TRUE(V8DetailedMemoryExecutionContextData::ForFrameNode(frame2.get()));
EXPECT_EQ(1002u,
V8DetailedMemoryExecutionContextData::ForFrameNode(frame2.get())
->v8_bytes_used());
// Now verify that data is cleared for any frame that doesn't get an update,
// plus verify that unknown frame data toes to unassociated bytes.
{
auto data = NewPerProcessV8MemoryUsage(1);
AddIsolateMemoryUsage(frame1_id, 1003u, data->isolates[0].get());
AddIsolateMemoryUsage(blink::LocalFrameToken(), 2233u,
data->isolates[0].get());
ExpectQueryAndReply(&reporter, std::move(data));
}
task_env().FastForwardBy(kMinTimeBetweenRequests);
Mock::VerifyAndClearExpectations(&reporter);
ASSERT_TRUE(V8DetailedMemoryExecutionContextData::ForFrameNode(frame1.get()));
EXPECT_EQ(1003u,
V8DetailedMemoryExecutionContextData::ForFrameNode(frame1.get())
->v8_bytes_used());
EXPECT_FALSE(
V8DetailedMemoryExecutionContextData::ForFrameNode(frame2.get()));
ASSERT_TRUE(V8DetailedMemoryProcessData::ForProcessNode(process.get()));
EXPECT_EQ(2233u, V8DetailedMemoryProcessData::ForProcessNode(process.get())
->unassociated_v8_bytes_used());
}
TEST_P(V8DetailedMemoryDecoratorModeTest, LazyRequests) {
constexpr base::TimeDelta kLazyRequestLength =
base::TimeDelta::FromSeconds(30);
V8DetailedMemoryRequest lazy_request(kLazyRequestLength,
MeasurementMode::kLazy, graph());
MockV8DetailedMemoryReporter reporter;
{
auto data = NewPerProcessV8MemoryUsage(1);
ExpectBindAndRespondToQuery(&reporter, std::move(data), kTestProcessID,
ExpectedMode::LAZY);
}
auto process = CreateNode<ProcessNodeImpl>(
content::PROCESS_TYPE_RENDERER,
RenderProcessHostProxy::CreateForTesting(kTestProcessID));
task_env().FastForwardBy(base::TimeDelta::FromSeconds(1));
Mock::VerifyAndClearExpectations(&reporter);
// If a lazy request takes too long to respond it should be upgraded to a
// bounded request if one is in the queue.
constexpr base::TimeDelta kLongBoundedRequestLength =
base::TimeDelta::FromSeconds(45);
V8DetailedMemoryRequest long_bounded_request(kLongBoundedRequestLength,
bounded_mode_, graph());
auto* decorator = V8DetailedMemoryDecorator::GetFromGraph(graph());
ASSERT_TRUE(decorator);
ASSERT_TRUE(decorator->GetNextRequest());
EXPECT_EQ(decorator->GetNextRequest()->min_time_between_requests(),
kLazyRequestLength);
EXPECT_EQ(decorator->GetNextRequest()->mode(), MeasurementMode::kLazy);
{
// Next lazy request sent after 30 sec + 10 sec delay until reply = 40 sec
// until reply arrives. kLongBoundedRequestLength > 40 sec so the reply
// should arrive in time to prevent upgrading the request.
auto data = NewPerProcessV8MemoryUsage(1);
data->isolates[0]->unassociated_bytes_used = 1U;
ExpectQueryAndDelayReply(&reporter, base::TimeDelta::FromSeconds(10),
std::move(data), ExpectedMode::LAZY);
}
// Wait long enough for the upgraded request to be sent, to verify that it
// wasn't sent.
task_env().FastForwardBy(kLongBoundedRequestLength);
Mock::VerifyAndClearExpectations(&reporter);
constexpr base::TimeDelta kUpgradeRequestLength =
base::TimeDelta::FromSeconds(40);
V8DetailedMemoryRequest bounded_request_upgrade(kUpgradeRequestLength,
bounded_mode_, graph());
ASSERT_TRUE(decorator->GetNextRequest());
EXPECT_EQ(decorator->GetNextRequest()->min_time_between_requests(),
kLazyRequestLength);
EXPECT_EQ(decorator->GetNextRequest()->mode(), MeasurementMode::kLazy);
{
InSequence seq;
// Again, 40 sec total until reply arrives. kUpgradeRequestLength <= 40 sec
// so a second upgraded request should be sent.
auto data = NewPerProcessV8MemoryUsage(1);
data->isolates[0]->unassociated_bytes_used = 2U;
ExpectQueryAndDelayReply(&reporter, base::TimeDelta::FromSeconds(10),
std::move(data), ExpectedMode::LAZY);
auto data2 = NewPerProcessV8MemoryUsage(1);
data2->isolates[0]->unassociated_bytes_used = 3U;
ExpectQueryAndReply(&reporter, std::move(data2), expected_bounded_mode_);
}
// Wait long enough for the upgraded request to be sent.
task_env().FastForwardBy(kUpgradeRequestLength);
Mock::VerifyAndClearExpectations(&reporter);
EXPECT_TRUE(V8DetailedMemoryProcessData::ForProcessNode(process.get()));
EXPECT_EQ(3u, V8DetailedMemoryProcessData::ForProcessNode(process.get())
->unassociated_v8_bytes_used());
// Bounded requests should be preferred over lazy requests with the same
// min_time_between_requests.
V8DetailedMemoryRequest short_bounded_request(kLazyRequestLength,
bounded_mode_, graph());
ASSERT_TRUE(decorator->GetNextRequest());
EXPECT_EQ(decorator->GetNextRequest()->min_time_between_requests(),
kLazyRequestLength);
EXPECT_EQ(decorator->GetNextRequest()->mode(), bounded_mode_);
}
INSTANTIATE_TEST_SUITE_P(
AllBoundedModes,
V8DetailedMemoryDecoratorModeTest,
::testing::Values(std::make_pair(MeasurementMode::kBounded,
ExpectedMode::DEFAULT),
std::make_pair(MeasurementMode::kEagerForTesting,
ExpectedMode::EAGER)));
TEST_F(V8DetailedMemoryDecoratorTest, MeasurementRequestsSorted) {
// Create some queries with different sample frequencies.
constexpr base::TimeDelta kShortInterval(kMinTimeBetweenRequests);
constexpr base::TimeDelta kMediumInterval(2 * kMinTimeBetweenRequests);
constexpr base::TimeDelta kLongInterval(3 * kMinTimeBetweenRequests);
// Create longer requests first to be sure they sort correctly.
auto medium_memory_request =
std::make_unique<V8DetailedMemoryRequest>(kMediumInterval, graph());
auto short_memory_request =
std::make_unique<V8DetailedMemoryRequest>(kShortInterval, graph());
auto long_memory_request =
std::make_unique<V8DetailedMemoryRequest>(kLongInterval, graph());
auto* decorator = V8DetailedMemoryDecorator::GetFromGraph(graph());
ASSERT_TRUE(decorator);
// A single measurement should be taken immediately regardless of the overall
// frequency.
MockV8DetailedMemoryReporter mock_reporter;
{
auto data = NewPerProcessV8MemoryUsage(1);
data->isolates[0]->unassociated_bytes_used = 1U;
ExpectBindAndRespondToQuery(&mock_reporter, std::move(data));
}
auto process = CreateNode<ProcessNodeImpl>(
content::PROCESS_TYPE_RENDERER,
RenderProcessHostProxy::CreateForTesting(kTestProcessID));
EXPECT_FALSE(V8DetailedMemoryProcessData::ForProcessNode(process.get()));
task_env().FastForwardBy(base::TimeDelta::FromSeconds(1));
// All the following FastForwardBy calls will place the clock 1 sec after a
// measurement is expected.
ASSERT_TRUE(V8DetailedMemoryProcessData::ForProcessNode(process.get()));
EXPECT_EQ(1U, V8DetailedMemoryProcessData::ForProcessNode(process.get())
->unassociated_v8_bytes_used());
// Another measurement should be taken after the shortest interval.
ASSERT_TRUE(decorator->GetNextRequest());
EXPECT_EQ(kShortInterval,
decorator->GetNextRequest()->min_time_between_requests());
{
auto data = NewPerProcessV8MemoryUsage(1);
data->isolates[0]->unassociated_bytes_used = 2U;
ExpectQueryAndReply(&mock_reporter, std::move(data));
task_env().FastForwardBy(kShortInterval);
EXPECT_EQ(2U, V8DetailedMemoryProcessData::ForProcessNode(process.get())
->unassociated_v8_bytes_used());
}
// Remove the shortest request. Now a measurement should be taken after the
// medium interval, which is twice the short interval.
short_memory_request.reset();
ASSERT_TRUE(decorator->GetNextRequest());
EXPECT_EQ(kMediumInterval,
decorator->GetNextRequest()->min_time_between_requests());
{
auto data = NewPerProcessV8MemoryUsage(1);
data->isolates[0]->unassociated_bytes_used = 3U;
ExpectQueryAndReply(&mock_reporter, std::move(data));
task_env().FastForwardBy(kShortInterval);
EXPECT_EQ(2U, V8DetailedMemoryProcessData::ForProcessNode(process.get())
->unassociated_v8_bytes_used());
task_env().FastForwardBy(kShortInterval);
EXPECT_EQ(3U, V8DetailedMemoryProcessData::ForProcessNode(process.get())
->unassociated_v8_bytes_used());
}
// Remove the longest request. A measurement should still be taken after the
// medium interval.
long_memory_request.reset();
ASSERT_TRUE(decorator->GetNextRequest());
EXPECT_EQ(kMediumInterval,
decorator->GetNextRequest()->min_time_between_requests());
{
auto data = NewPerProcessV8MemoryUsage(1);
data->isolates[0]->unassociated_bytes_used = 4U;
ExpectQueryAndReply(&mock_reporter, std::move(data));
task_env().FastForwardBy(kMediumInterval);
EXPECT_EQ(4U, V8DetailedMemoryProcessData::ForProcessNode(process.get())
->unassociated_v8_bytes_used());
}
// Remove the medium request, making the queue empty.
medium_memory_request.reset();
EXPECT_FALSE(decorator->GetNextRequest());
{
auto data = NewPerProcessV8MemoryUsage(1);
data->isolates[0]->unassociated_bytes_used = 5U;
ExpectQueryAndReply(&mock_reporter, std::move(data));
task_env().FastForwardBy(kLongInterval);
EXPECT_EQ(4U, V8DetailedMemoryProcessData::ForProcessNode(process.get())
->unassociated_v8_bytes_used());
}
// Create another request. Since this is the first request in an empty queue
// the measurement should be taken immediately.
long_memory_request =
std::make_unique<V8DetailedMemoryRequest>(kLongInterval, graph());
ASSERT_TRUE(decorator->GetNextRequest());
EXPECT_EQ(kLongInterval,
decorator->GetNextRequest()->min_time_between_requests());
task_env().FastForwardBy(base::TimeDelta::FromSeconds(1));
EXPECT_EQ(5U, V8DetailedMemoryProcessData::ForProcessNode(process.get())
->unassociated_v8_bytes_used());
{
auto data = NewPerProcessV8MemoryUsage(1);
data->isolates[0]->unassociated_bytes_used = 6U;
ExpectQueryAndReply(&mock_reporter, std::move(data));
task_env().FastForwardBy(kLongInterval);
EXPECT_EQ(6U, V8DetailedMemoryProcessData::ForProcessNode(process.get())
->unassociated_v8_bytes_used());
}
// Now there should be kLongInterval - 1 sec until the next measurement.
// Make sure a shorter request replaces this (the new interval should cause a
// measurement and the old interval should not).
medium_memory_request =
std::make_unique<V8DetailedMemoryRequest>(kMediumInterval, graph());
ASSERT_TRUE(decorator->GetNextRequest());
EXPECT_EQ(kMediumInterval,
decorator->GetNextRequest()->min_time_between_requests());
{
auto data = NewPerProcessV8MemoryUsage(1);
data->isolates[0]->unassociated_bytes_used = 7U;
ExpectQueryAndReply(&mock_reporter, std::move(data));
task_env().FastForwardBy(kMediumInterval);
EXPECT_EQ(7U, V8DetailedMemoryProcessData::ForProcessNode(process.get())
->unassociated_v8_bytes_used());
}
{
auto data = NewPerProcessV8MemoryUsage(1);
data->isolates[0]->unassociated_bytes_used = 8U;
ExpectQueryAndReply(&mock_reporter, std::move(data));
constexpr base::TimeDelta kRestOfLongInterval =
kLongInterval - kMediumInterval;
task_env().FastForwardBy(kRestOfLongInterval);
EXPECT_EQ(7U, V8DetailedMemoryProcessData::ForProcessNode(process.get())
->unassociated_v8_bytes_used());
task_env().FastForwardBy(kMediumInterval - kRestOfLongInterval);
EXPECT_EQ(8U, V8DetailedMemoryProcessData::ForProcessNode(process.get())
->unassociated_v8_bytes_used());
}
// Remove the medium request and add it back. The measurement interval should
// not change.
medium_memory_request.reset();
ASSERT_TRUE(decorator->GetNextRequest());
EXPECT_EQ(kLongInterval,
decorator->GetNextRequest()->min_time_between_requests());
medium_memory_request =
std::make_unique<V8DetailedMemoryRequest>(kMediumInterval, graph());
ASSERT_TRUE(decorator->GetNextRequest());
EXPECT_EQ(kMediumInterval,
decorator->GetNextRequest()->min_time_between_requests());
{
auto data = NewPerProcessV8MemoryUsage(1);
data->isolates[0]->unassociated_bytes_used = 9U;
ExpectQueryAndReply(&mock_reporter, std::move(data));
task_env().FastForwardBy(kMediumInterval);
EXPECT_EQ(9U, V8DetailedMemoryProcessData::ForProcessNode(process.get())
->unassociated_v8_bytes_used());
}
// Add another long request. There should still be requests after the medium
// interval.
auto long_memory_request2 =
std::make_unique<V8DetailedMemoryRequest>(kLongInterval, graph());
ASSERT_TRUE(decorator->GetNextRequest());
EXPECT_EQ(kMediumInterval,
decorator->GetNextRequest()->min_time_between_requests());
{
auto data = NewPerProcessV8MemoryUsage(1);
data->isolates[0]->unassociated_bytes_used = 10U;
ExpectQueryAndReply(&mock_reporter, std::move(data));
task_env().FastForwardBy(kMediumInterval);
EXPECT_EQ(10U, V8DetailedMemoryProcessData::ForProcessNode(process.get())
->unassociated_v8_bytes_used());
}
// Remove the medium request. Now there are 2 requests which should cause
// measurements at the same interval. Make sure only 1 measurement is taken.
medium_memory_request.reset();
ASSERT_TRUE(decorator->GetNextRequest());
EXPECT_EQ(kLongInterval,
decorator->GetNextRequest()->min_time_between_requests());
{
auto data = NewPerProcessV8MemoryUsage(1);
data->isolates[0]->unassociated_bytes_used = 11U;
ExpectQueryAndReply(&mock_reporter, std::move(data));
task_env().FastForwardBy(kLongInterval);
EXPECT_EQ(11U, V8DetailedMemoryProcessData::ForProcessNode(process.get())
->unassociated_v8_bytes_used());
}
// Remove 1 of the 2 long requests. Measurements should not change.
long_memory_request2.reset();
ASSERT_TRUE(decorator->GetNextRequest());
EXPECT_EQ(kLongInterval,
decorator->GetNextRequest()->min_time_between_requests());
{
auto data = NewPerProcessV8MemoryUsage(1);
data->isolates[0]->unassociated_bytes_used = 12U;
ExpectQueryAndReply(&mock_reporter, std::move(data));
task_env().FastForwardBy(kLongInterval);
EXPECT_EQ(12U, V8DetailedMemoryProcessData::ForProcessNode(process.get())
->unassociated_v8_bytes_used());
}
}
TEST_F(V8DetailedMemoryDecoratorTest, MeasurementRequestsWithDelay) {
// Create some queries with different sample frequencies.
constexpr base::TimeDelta kShortInterval(kMinTimeBetweenRequests);
constexpr base::TimeDelta kMediumInterval(2 * kMinTimeBetweenRequests);
constexpr base::TimeDelta kLongInterval(3 * kMinTimeBetweenRequests);
// Make measurements take long enough that a second request could be sent.
constexpr base::TimeDelta kMeasurementLength(1.5 * kShortInterval);
constexpr base::TimeDelta kOneSecond = base::TimeDelta::FromSeconds(1);
auto long_memory_request =
std::make_unique<V8DetailedMemoryRequest>(kLongInterval, graph());
auto* decorator = V8DetailedMemoryDecorator::GetFromGraph(graph());
ASSERT_TRUE(decorator);
// Move past the first request since it's complicated to untangle the Bind
// and QueryAndDelayReply expectations.
MockV8DetailedMemoryReporter mock_reporter;
{
auto data = NewPerProcessV8MemoryUsage(1);
data->isolates[0]->unassociated_bytes_used = 0U;
ExpectBindAndRespondToQuery(&mock_reporter, std::move(data));
}
auto process = CreateNode<ProcessNodeImpl>(
content::PROCESS_TYPE_RENDERER,
RenderProcessHostProxy::CreateForTesting(kTestProcessID));
task_env().FastForwardBy(kOneSecond);
// All the following FastForwardBy calls will place the clock 1 sec after a
// measurement is expected.
// Advance to the middle of a measurement and create a new request. Should
// update min_time_between_requests but not start a new measurement until the
// existing measurement finishes.
{
auto data = NewPerProcessV8MemoryUsage(1);
data->isolates[0]->unassociated_bytes_used = 1U;
ExpectQueryAndDelayReply(&mock_reporter, kMeasurementLength,
std::move(data));
}
task_env().FastForwardBy(kLongInterval);
EXPECT_EQ(last_query_time(), task_env().NowTicks() - kOneSecond)
<< "Measurement didn't start when expected";
EXPECT_EQ(0U, V8DetailedMemoryProcessData::ForProcessNode(process.get())
->unassociated_v8_bytes_used())
<< "Measurement ended early";
base::TimeTicks measurement_start_time = last_query_time();
auto medium_memory_request =
std::make_unique<V8DetailedMemoryRequest>(kMediumInterval, graph());
ASSERT_TRUE(decorator->GetNextRequest());
EXPECT_EQ(kMediumInterval,
decorator->GetNextRequest()->min_time_between_requests());
task_env().FastForwardBy(kMeasurementLength);
ASSERT_EQ(1U, V8DetailedMemoryProcessData::ForProcessNode(process.get())
->unassociated_v8_bytes_used())
<< "Measurement didn't end when expected";
EXPECT_EQ(last_query_time(), measurement_start_time);
// Next measurement should start kMediumInterval secs after the START of the
// last measurement.
{
auto data = NewPerProcessV8MemoryUsage(1);
data->isolates[0]->unassociated_bytes_used = 2U;
ExpectQueryAndDelayReply(&mock_reporter, kMeasurementLength,
std::move(data));
}
task_env().FastForwardBy(kMediumInterval - kMeasurementLength);
EXPECT_EQ(last_query_time(), task_env().NowTicks() - kOneSecond)
<< "Measurement didn't start when expected";
EXPECT_EQ(1U, V8DetailedMemoryProcessData::ForProcessNode(process.get())
->unassociated_v8_bytes_used())
<< "Measurement ended early";
measurement_start_time = last_query_time();
task_env().FastForwardBy(kMeasurementLength);
EXPECT_EQ(2U, V8DetailedMemoryProcessData::ForProcessNode(process.get())
->unassociated_v8_bytes_used())
<< "Measurement didn't end when expected";
EXPECT_EQ(last_query_time(), measurement_start_time);
// Create a request that would be sent in the middle of a measurement. It
// should start immediately after the measurement finishes.
{
auto data = NewPerProcessV8MemoryUsage(1);
data->isolates[0]->unassociated_bytes_used = 3U;
ExpectQueryAndDelayReply(&mock_reporter, kMeasurementLength,
std::move(data));
}
task_env().FastForwardBy(kMediumInterval - kMeasurementLength);
EXPECT_EQ(last_query_time(), task_env().NowTicks() - kOneSecond)
<< "Measurement didn't start when expected";
EXPECT_EQ(2U, V8DetailedMemoryProcessData::ForProcessNode(process.get())
->unassociated_v8_bytes_used())
<< "Measurement ended early";
measurement_start_time = last_query_time();
auto short_memory_request =
std::make_unique<V8DetailedMemoryRequest>(kShortInterval, graph());
ASSERT_TRUE(decorator->GetNextRequest());
EXPECT_EQ(kShortInterval,
decorator->GetNextRequest()->min_time_between_requests());
EXPECT_EQ(last_query_time(), measurement_start_time);
{
auto data = NewPerProcessV8MemoryUsage(1);
data->isolates[0]->unassociated_bytes_used = 4U;
ExpectQueryAndDelayReply(&mock_reporter, kMeasurementLength,
std::move(data));
}
task_env().FastForwardBy(kMeasurementLength);
EXPECT_EQ(last_query_time(), task_env().NowTicks() - kOneSecond)
<< "Measurement didn't start when expected";
EXPECT_EQ(3U, V8DetailedMemoryProcessData::ForProcessNode(process.get())
->unassociated_v8_bytes_used())
<< "Measurement ended early";
measurement_start_time = last_query_time();
// Delete the short request. Should update min_time_between_requests but not
// start a new measurement until the existing measurement finishes.
short_memory_request.reset();
ASSERT_TRUE(decorator->GetNextRequest());
EXPECT_EQ(kMediumInterval,
decorator->GetNextRequest()->min_time_between_requests());
task_env().FastForwardBy(kMeasurementLength);
EXPECT_EQ(4U, V8DetailedMemoryProcessData::ForProcessNode(process.get())
->unassociated_v8_bytes_used())
<< "Measurement didn't end when expected";
EXPECT_EQ(last_query_time(), measurement_start_time);
// Delete the last request while a measurement is in process. The
// measurement should finish successfully but no more should be sent.
{
auto data = NewPerProcessV8MemoryUsage(1);
data->isolates[0]->unassociated_bytes_used = 5U;
ExpectQueryAndDelayReply(&mock_reporter, kMeasurementLength,
std::move(data));
}
task_env().FastForwardBy(kMediumInterval - kMeasurementLength);
EXPECT_EQ(last_query_time(), task_env().NowTicks() - kOneSecond)
<< "Measurement didn't start when expected";
EXPECT_EQ(4U, V8DetailedMemoryProcessData::ForProcessNode(process.get())
->unassociated_v8_bytes_used())
<< "Measurement ended early";
measurement_start_time = last_query_time();
medium_memory_request.reset();
long_memory_request.reset();
EXPECT_FALSE(decorator->GetNextRequest());
task_env().FastForwardBy(kMeasurementLength);
EXPECT_EQ(5U, V8DetailedMemoryProcessData::ForProcessNode(process.get())
->unassociated_v8_bytes_used())
<< "Measurement didn't end when expected";
EXPECT_EQ(last_query_time(), measurement_start_time);
// No more requests should be sent.
Mock::VerifyAndClearExpectations(this);
task_env().FastForwardBy(kLongInterval);
}
TEST_F(V8DetailedMemoryDecoratorTest, MeasurementRequestOutlivesDecorator) {
V8DetailedMemoryRequest memory_request(kMinTimeBetweenRequests, graph());
auto* decorator = V8DetailedMemoryDecorator::GetFromGraph(graph());
ASSERT_TRUE(decorator);
MockV8DetailedMemoryReporter mock_reporter;
{
auto data = NewPerProcessV8MemoryUsage(1);
data->isolates[0]->unassociated_bytes_used = 1U;
ExpectBindAndRespondToQuery(&mock_reporter, std::move(data));
}
auto process = CreateNode<ProcessNodeImpl>(
content::PROCESS_TYPE_RENDERER,
RenderProcessHostProxy::CreateForTesting(kTestProcessID));
task_env().FastForwardBy(base::TimeDelta::FromSeconds(1));
ASSERT_EQ(1U, V8DetailedMemoryProcessData::ForProcessNode(process.get())
->unassociated_v8_bytes_used())
<< "First measurement didn't happen when expected";
graph()->TakeFromGraph(decorator);
// No request should be sent, and the decorator destructor should not DCHECK.
Mock::VerifyAndClearExpectations(this);
task_env().FastForwardBy(kMinTimeBetweenRequests);
}
TEST_F(V8DetailedMemoryDecoratorTest, NotifyObservers) {
V8DetailedMemoryRequest memory_request(kMinTimeBetweenRequests, graph());
MockV8DetailedMemoryObserver observer1;
MockV8DetailedMemoryObserver observer2;
memory_request.AddObserver(&observer1);
memory_request.AddObserver(&observer2);
// Create a process node and validate that all observers are notified when a
// measurement is available for it.
MockV8DetailedMemoryReporter reporter1;
{
auto data = NewPerProcessV8MemoryUsage(1);
data->isolates[0]->unassociated_bytes_used = 1U;
ExpectBindAndRespondToQuery(&reporter1, std::move(data));
}
auto process1 = CreateNode<ProcessNodeImpl>(
content::PROCESS_TYPE_RENDERER,
RenderProcessHostProxy::CreateForTesting(kTestProcessID));
observer1.ExpectObservationOnProcess(process1.get(), 1U);
observer2.ExpectObservationOnProcess(process1.get(), 1U);
task_env().FastForwardBy(kMinTimeBetweenRequests / 2);
Mock::VerifyAndClearExpectations(&reporter1);
Mock::VerifyAndClearExpectations(&observer1);
Mock::VerifyAndClearExpectations(&observer2);
// Create a process node and validate that all observers are notified when
// any measurement is available. After fast-forwarding the first measurement
// for process2 and the second measurement for process1 will arrive.
MockV8DetailedMemoryReporter reporter2;
{
auto data = NewPerProcessV8MemoryUsage(1);
data->isolates[0]->unassociated_bytes_used = 2U;
ExpectBindAndRespondToQuery(&reporter2, std::move(data));
}
{
auto data = NewPerProcessV8MemoryUsage(1);
data->isolates[0]->unassociated_bytes_used = 3U;
ExpectQueryAndReply(&reporter1, std::move(data));
}
auto process2 = CreateNode<ProcessNodeImpl>(
content::PROCESS_TYPE_RENDERER,
RenderProcessHostProxy::CreateForTesting(kTestProcessID));
observer1.ExpectObservationOnProcess(process2.get(), 2U);
observer2.ExpectObservationOnProcess(process2.get(), 2U);
observer1.ExpectObservationOnProcess(process1.get(), 3U);
observer2.ExpectObservationOnProcess(process1.get(), 3U);
task_env().FastForwardBy(kMinTimeBetweenRequests / 2);
Mock::VerifyAndClearExpectations(&reporter1);
Mock::VerifyAndClearExpectations(&reporter2);
Mock::VerifyAndClearExpectations(&observer1);
Mock::VerifyAndClearExpectations(&observer2);
// Remove an observer and make sure the other is still notified after the
// next measurement.
{
auto data = NewPerProcessV8MemoryUsage(1);
data->isolates[0]->unassociated_bytes_used = 4U;
ExpectQueryAndReply(&reporter1, std::move(data));
}
{
auto data = NewPerProcessV8MemoryUsage(1);
data->isolates[0]->unassociated_bytes_used = 5U;
ExpectQueryAndReply(&reporter2, std::move(data));
}
memory_request.RemoveObserver(&observer1);
observer2.ExpectObservationOnProcess(process1.get(), 4U);
observer2.ExpectObservationOnProcess(process2.get(), 5U);
task_env().FastForwardBy(kMinTimeBetweenRequests);
Mock::VerifyAndClearExpectations(&reporter1);
Mock::VerifyAndClearExpectations(&reporter2);
Mock::VerifyAndClearExpectations(&observer1);
Mock::VerifyAndClearExpectations(&observer2);
// Must remove the observer before destroying the request to avoid a DCHECK
// from ObserverList.
memory_request.RemoveObserver(&observer2);
}
TEST_F(V8DetailedMemoryDecoratorTest, ObserverOutlivesDecorator) {
V8DetailedMemoryRequest memory_request(kMinTimeBetweenRequests, graph());
MockV8DetailedMemoryObserver observer;
memory_request.AddObserver(&observer);
// Create a process node and move past the initial request to it.
MockV8DetailedMemoryReporter reporter;
{
auto data = NewPerProcessV8MemoryUsage(1);
data->isolates[0]->unassociated_bytes_used = 1U;
ExpectBindAndRespondToQuery(&reporter, std::move(data));
}
auto process = CreateNode<ProcessNodeImpl>(
content::PROCESS_TYPE_RENDERER,
RenderProcessHostProxy::CreateForTesting(kTestProcessID));
observer.ExpectObservationOnProcess(process.get(), 1U);
task_env().FastForwardBy(base::TimeDelta::FromSeconds(1));
Mock::VerifyAndClearExpectations(&reporter);
Mock::VerifyAndClearExpectations(&observer);
// Start the next measurement.
{
auto data = NewPerProcessV8MemoryUsage(1);
data->isolates[0]->unassociated_bytes_used = 2U;
ExpectQueryAndDelayReply(&reporter, kMinTimeBetweenRequests,
std::move(data));
}
task_env().FastForwardBy(kMinTimeBetweenRequests);
// Destroy the decorator before the measurement completes. The observer
// should not be notified.
auto* decorator = V8DetailedMemoryDecorator::GetFromGraph(graph());
ASSERT_TRUE(decorator);
graph()->TakeFromGraph(decorator);
task_env().FastForwardBy(kMinTimeBetweenRequests);
// Must remove the observer before destroying the request to avoid a DCHECK
// from ObserverList.
memory_request.RemoveObserver(&observer);
}
TEST_F(V8DetailedMemoryDecoratorTest, SingleProcessRequest) {
// Create 2 renderer processes. Create one request that measures both of
// them, and one request that measures only one.
constexpr RenderProcessHostId kProcessId1 = RenderProcessHostId(0xFAB);
auto process1 = CreateNode<ProcessNodeImpl>(
content::PROCESS_TYPE_RENDERER,
RenderProcessHostProxy::CreateForTesting(kProcessId1));
constexpr RenderProcessHostId kProcessId2 = RenderProcessHostId(0xBAF);
auto process2 = CreateNode<ProcessNodeImpl>(
content::PROCESS_TYPE_RENDERER,
RenderProcessHostProxy::CreateForTesting(kProcessId2));
// Set the all process request to only send once within the test.
V8DetailedMemoryRequest all_process_request(kMinTimeBetweenRequests * 100);
all_process_request.StartMeasurement(graph());
auto process1_request =
std::make_unique<V8DetailedMemoryRequest>(kMinTimeBetweenRequests);
process1_request->StartMeasurementForProcess(process1.get());
MockV8DetailedMemoryReporter mock_reporter1;
MockV8DetailedMemoryReporter mock_reporter2;
{
// Response to initial request in process 1.
auto data = NewPerProcessV8MemoryUsage(1);
data->isolates[0]->unassociated_bytes_used = 1U;
ExpectBindAndRespondToQuery(&mock_reporter1, std::move(data), kProcessId1);
// Response to initial request in process 2.
data = NewPerProcessV8MemoryUsage(1);
data->isolates[0]->unassociated_bytes_used = 2U;
ExpectBindAndRespondToQuery(&mock_reporter2, std::move(data), kProcessId2);
}
// All the following FastForwardBy calls will place the clock 1 sec after a
// measurement is expected.
task_env().FastForwardBy(base::TimeDelta::FromSeconds(1));
testing::Mock::VerifyAndClearExpectations(&mock_reporter1);
testing::Mock::VerifyAndClearExpectations(&mock_reporter2);
ASSERT_TRUE(V8DetailedMemoryProcessData::ForProcessNode(process1.get()));
EXPECT_EQ(1U, V8DetailedMemoryProcessData::ForProcessNode(process1.get())
->unassociated_v8_bytes_used());
ASSERT_TRUE(V8DetailedMemoryProcessData::ForProcessNode(process2.get()));
EXPECT_EQ(2U, V8DetailedMemoryProcessData::ForProcessNode(process2.get())
->unassociated_v8_bytes_used());
// After kMinTimeBetweenRequests another request should be sent to process1,
// but not process2.
{
auto data = NewPerProcessV8MemoryUsage(1);
data->isolates[0]->unassociated_bytes_used = 3U;
ExpectQueryAndDelayReply(&mock_reporter1, kMinTimeBetweenRequests,
std::move(data));
}
task_env().FastForwardBy(kMinTimeBetweenRequests);
testing::Mock::VerifyAndClearExpectations(&mock_reporter1);
testing::Mock::VerifyAndClearExpectations(&mock_reporter2);
// Delete process1 request while waiting for measurement result.
process1_request.reset();
task_env().FastForwardBy(kMinTimeBetweenRequests);
testing::Mock::VerifyAndClearExpectations(&mock_reporter1);
testing::Mock::VerifyAndClearExpectations(&mock_reporter2);
ASSERT_TRUE(V8DetailedMemoryProcessData::ForProcessNode(process1.get()));
EXPECT_EQ(3U, V8DetailedMemoryProcessData::ForProcessNode(process1.get())
->unassociated_v8_bytes_used());
// Recreate process1 request. The new request will be sent immediately since
// enough time has passed since the last request.
{
auto data = NewPerProcessV8MemoryUsage(1);
data->isolates[0]->unassociated_bytes_used = 4U;
ExpectQueryAndReply(&mock_reporter1, std::move(data));
}
process1_request =
std::make_unique<V8DetailedMemoryRequest>(kMinTimeBetweenRequests);
process1_request->StartMeasurementForProcess(process1.get());
// Test observers of single-process requests.
MockV8DetailedMemoryObserver mock_observer;
process1_request->AddObserver(&mock_observer);
mock_observer.ExpectObservationOnProcess(process1.get(), 4U);
task_env().FastForwardBy(base::TimeDelta::FromSeconds(1));
testing::Mock::VerifyAndClearExpectations(&mock_reporter1);
testing::Mock::VerifyAndClearExpectations(&mock_reporter2);
testing::Mock::VerifyAndClearExpectations(&mock_observer);
ASSERT_TRUE(V8DetailedMemoryProcessData::ForProcessNode(process1.get()));
EXPECT_EQ(4U, V8DetailedMemoryProcessData::ForProcessNode(process1.get())
->unassociated_v8_bytes_used());
// Delete process1 while the request still exists. Nothing should crash.
process1.reset();
task_env().FastForwardBy(kMinTimeBetweenRequests);
testing::Mock::VerifyAndClearExpectations(&mock_reporter1);
testing::Mock::VerifyAndClearExpectations(&mock_reporter2);
testing::Mock::VerifyAndClearExpectations(&mock_observer);
// Clean up.
process1_request->RemoveObserver(&mock_observer);
}
TEST_P(V8DetailedMemoryDecoratorSingleProcessModeTest,
SingleProcessLazyRequest) {
// Create a single process node so both "all process" and "single process"
// requests will have a single expectation, which reduces boilerplate.
auto process = CreateNode<ProcessNodeImpl>(
content::PROCESS_TYPE_RENDERER,
RenderProcessHostProxy::CreateForTesting(kTestProcessID));
V8DetailedMemoryRequest lazy_request(kMinTimeBetweenRequests,
MeasurementMode::kLazy);
V8DetailedMemoryRequest bounded_request(kMinTimeBetweenRequests * 2,
MeasurementMode::kBounded);
if (single_process_mode_ == MeasurementMode::kLazy) {
// Test that lazy single-process requests can't starve bounded all-process
// requests.
lazy_request.StartMeasurementForProcess(process.get());
bounded_request.StartMeasurement(graph());
} else {
// Test that lazy all-process requests can't starve bounded single-process
// requests.
lazy_request.StartMeasurement(graph());
bounded_request.StartMeasurementForProcess(process.get());
}
MockV8DetailedMemoryReporter mock_reporter;
{
// Response to initial request which is sent immediately. This will use the
// LAZY mode from |lazy_request| because it has a lower frequency.
auto data = NewPerProcessV8MemoryUsage(1);
data->isolates[0]->unassociated_bytes_used = 1U;
ExpectBindAndRespondToQuery(&mock_reporter, std::move(data), kTestProcessID,
ExpectedMode::LAZY);
}
// All the following FastForwardBy calls will place the clock 1 sec after a
// measurement is expected.
task_env().FastForwardBy(base::TimeDelta::FromSeconds(1));
testing::Mock::VerifyAndClearExpectations(&mock_reporter);
// Delay next lazy reply and expect |bounded_request| to be sent while
// waiting.
{
auto data = NewPerProcessV8MemoryUsage(1);
data->isolates[0]->unassociated_bytes_used = 3U;
ExpectQueryAndDelayReply(&mock_reporter, 2 * kMinTimeBetweenRequests,
std::move(data), ExpectedMode::LAZY);
}
task_env().FastForwardBy(kMinTimeBetweenRequests);
testing::Mock::VerifyAndClearExpectations(&mock_reporter);
// Lazy request sent, now 2*kMinTimeBetweenRequests until reply and
// 3*kMinTimeBetweenRequests until next lazy request. Advancing the clock
// should send |bounded_request| to both processes.
{
auto data = NewPerProcessV8MemoryUsage(1);
data->isolates[0]->unassociated_bytes_used = 4U;
ExpectQueryAndReply(&mock_reporter, std::move(data), ExpectedMode::DEFAULT);
}
task_env().FastForwardBy(kMinTimeBetweenRequests);
testing::Mock::VerifyAndClearExpectations(&mock_reporter);
ASSERT_TRUE(V8DetailedMemoryProcessData::ForProcessNode(process.get()));
EXPECT_EQ(4U, V8DetailedMemoryProcessData::ForProcessNode(process.get())
->unassociated_v8_bytes_used());
}
INSTANTIATE_TEST_SUITE_P(SingleProcessLazyOrBounded,
V8DetailedMemoryDecoratorSingleProcessModeTest,
::testing::Values(MeasurementMode::kLazy,
MeasurementMode::kBounded));
TEST_F(V8DetailedMemoryDecoratorDeathTest, MultipleStartMeasurement) {
EXPECT_DCHECK_DEATH({
V8DetailedMemoryRequest request(kMinTimeBetweenRequests);
request.StartMeasurement(graph());
request.StartMeasurement(graph());
});
EXPECT_DCHECK_DEATH({
V8DetailedMemoryRequest request(kMinTimeBetweenRequests, graph());
request.StartMeasurement(graph());
});
}
TEST_F(V8DetailedMemoryDecoratorDeathTest, EnforceObserversRemoved) {
// Declare the observers before the requests to ensure they're deleted
// afterwards.
EXPECT_DCHECK_DEATH({
MockV8DetailedMemoryObserver observer;
V8DetailedMemoryRequest memory_request(kMinTimeBetweenRequests);
memory_request.AddObserver(&observer);
// Request should explode if it still has observers registered when it goes
// out of scope.
});
EXPECT_DCHECK_DEATH({
MockV8DetailedMemoryObserverAnySeq observer;
V8DetailedMemoryRequestAnySeq memory_request(kMinTimeBetweenRequests);
memory_request.AddObserver(&observer);
// Request should explode if it still has observers registered when it goes
// out of scope.
});
}
TEST_F(V8DetailedMemoryDecoratorDeathTest, InvalidParameters) {
// Not allowed to use kEagerForTesting mode without calling
// SetEagerMemoryMeasurementEnabledForTesting.
EXPECT_DCHECK_DEATH({
V8DetailedMemoryRequest memory_request(kMinTimeBetweenRequests,
MeasurementMode::kEagerForTesting);
});
EXPECT_DCHECK_DEATH({
V8DetailedMemoryRequestAnySeq memory_request(
kMinTimeBetweenRequests, MeasurementMode::kEagerForTesting);
});
// Zero, negative and infinite TimeDelta's are disallowed.
EXPECT_DCHECK_DEATH({
base::TimeDelta zero;
V8DetailedMemoryRequestAnySeq memory_request(zero);
});
EXPECT_DCHECK_DEATH({
V8DetailedMemoryRequestAnySeq memory_request(base::TimeDelta::Min());
});
EXPECT_DCHECK_DEATH({
V8DetailedMemoryRequestAnySeq memory_request(base::TimeDelta::Max());
});
EXPECT_DCHECK_DEATH({
V8DetailedMemoryRequestAnySeq memory_request(kMinTimeBetweenRequests * -1);
});
}
TEST_F(V8DetailedMemoryRequestAnySeqTest, RequestIsSequenceSafe) {
// Create some test data to return for a measurement request.
constexpr uint64_t kAssociatedBytes = 0x123;
const blink::LocalFrameToken frame_token(main_frame()->GetFrameToken());
const content::GlobalFrameRoutingId frame_id(main_process_id().value(),
main_frame()->GetRoutingID());
V8DetailedMemoryProcessData expected_process_data;
expected_process_data.set_unassociated_v8_bytes_used(kUnassociatedBytes);
V8DetailedMemoryObserverAnySeq::FrameDataMap expected_frame_data;
expected_frame_data[frame_id].set_v8_bytes_used(kAssociatedBytes);
MockV8DetailedMemoryReporter reporter;
{
auto data = NewPerProcessV8MemoryUsage(1);
data->isolates[0]->unassociated_bytes_used = kUnassociatedBytes;
AddIsolateMemoryUsage(frame_token, kAssociatedBytes,
data->isolates[0].get());
ExpectBindAndRespondToQuery(&reporter, std::move(data), main_process_id());
}
// Decorator should not exist before creating a request.
PerformanceManager::CallOnGraph(
FROM_HERE, base::BindOnce([](Graph* graph) {
EXPECT_FALSE(V8DetailedMemoryDecorator::GetFromGraph(graph));
}));
// This object is created on the main sequence but should cause a
// V8DetailedMemoryRequest to be created on the graph sequence after the
// above task.
auto request =
std::make_unique<V8DetailedMemoryRequestAnySeq>(kMinTimeBetweenRequests);
MockV8DetailedMemoryObserverAnySeq observer;
request->AddObserver(&observer);
// Decorator now exists and has the request frequency set, proving that the
// V8DetailedMemoryRequest was created.
PerformanceManager::CallOnGraph(
FROM_HERE, base::BindOnce([](Graph* graph) {
auto* decorator = V8DetailedMemoryDecorator::GetFromGraph(graph);
ASSERT_TRUE(decorator);
ASSERT_TRUE(decorator->GetNextRequest());
EXPECT_EQ(kMinTimeBetweenRequests,
decorator->GetNextRequest()->min_time_between_requests());
}));
// The observer should be invoked on the main sequence when a measurement is
// available. Exit the RunLoop when this happens.
base::RunLoop run_loop;
EXPECT_CALL(observer, OnV8MemoryMeasurementAvailable(main_process_id(),
expected_process_data,
expected_frame_data))
.WillOnce([&]() {
run_loop.Quit();
ASSERT_TRUE(
this->GetMainThreadTaskRunner()->RunsTasksInCurrentSequence())
<< "Observer invoked on wrong sequence";
// Verify that the notification parameters can be used to retrieve a
// RenderFrameHost and RenderProcessHost. This is safe on the main
// thread.
EXPECT_NE(nullptr, content::RenderProcessHost::FromID(
main_process_id().value()));
const content::GlobalFrameRoutingId frame_id =
expected_frame_data.cbegin()->first;
EXPECT_NE(nullptr, content::RenderFrameHost::FromID(frame_id));
});
// Now execute all the above tasks.
run_loop.Run();
Mock::VerifyAndClearExpectations(this);
Mock::VerifyAndClearExpectations(&reporter);
Mock::VerifyAndClearExpectations(&observer);
// Destroying the object on the main sequence should cause the wrapped
// V8DetailedMemoryRequest to be destroyed on the graph sequence after any
// scheduled tasks, which resets the request frequency to zero.
PerformanceManager::CallOnGraph(
FROM_HERE, base::BindOnce([](Graph* graph) {
auto* decorator = V8DetailedMemoryDecorator::GetFromGraph(graph);
ASSERT_TRUE(decorator);
ASSERT_TRUE(decorator->GetNextRequest());
EXPECT_EQ(kMinTimeBetweenRequests,
decorator->GetNextRequest()->min_time_between_requests());
}));
// Must remove the observer before destroying the request to avoid a DCHECK
// from ObserverList.
request->RemoveObserver(&observer);
request.reset();
PerformanceManager::CallOnGraph(
FROM_HERE, base::BindOnce([](Graph* graph) {
auto* decorator = V8DetailedMemoryDecorator::GetFromGraph(graph);
ASSERT_TRUE(decorator);
EXPECT_FALSE(decorator->GetNextRequest());
}));
// Execute the above tasks and exit.
base::RunLoop run_loop2;
PerformanceManager::CallOnGraph(FROM_HERE, run_loop2.QuitClosure());
run_loop2.Run();
}
TEST_F(V8DetailedMemoryRequestAnySeqTest, SingleProcessRequest) {
CreateCrossProcessChildFrame();
V8DetailedMemoryProcessData expected_process_data1;
expected_process_data1.set_unassociated_v8_bytes_used(1U);
V8DetailedMemoryProcessData expected_process_data2;
expected_process_data2.set_unassociated_v8_bytes_used(2U);
MockV8DetailedMemoryReporter mock_reporter1;
MockV8DetailedMemoryReporter mock_reporter2;
{
auto data = NewPerProcessV8MemoryUsage(1);
data->isolates[0]->unassociated_bytes_used = 1U;
ExpectBindAndRespondToQuery(&mock_reporter1, std::move(data),
main_process_id());
data = NewPerProcessV8MemoryUsage(1);
data->isolates[0]->unassociated_bytes_used = 2U;
ExpectBindAndRespondToQuery(&mock_reporter2, std::move(data),
child_process_id());
}
// Create one request that measures both processes, and one request that
// measures only one.
V8DetailedMemoryRequestAnySeq all_process_request(kMinTimeBetweenRequests);
MockV8DetailedMemoryObserverAnySeq all_process_observer;
all_process_request.AddObserver(&all_process_observer);
V8DetailedMemoryRequestAnySeq single_process_request(
kMinTimeBetweenRequests, MeasurementMode::kBounded, main_process_id());
MockV8DetailedMemoryObserverAnySeq single_process_observer;
single_process_request.AddObserver(&single_process_observer);
// When a measurement is available the all process observer should be invoked
// for both processes, and the single process observer only for process 1.
EXPECT_CALL(all_process_observer,
OnV8MemoryMeasurementAvailable(main_process_id(),
expected_process_data1, _));
EXPECT_CALL(all_process_observer,
OnV8MemoryMeasurementAvailable(child_process_id(),
expected_process_data2, _));
EXPECT_CALL(single_process_observer,
OnV8MemoryMeasurementAvailable(main_process_id(),
expected_process_data1, _));
// Now execute all the above tasks.
task_environment()->RunUntilIdle();
Mock::VerifyAndClearExpectations(&mock_reporter1);
Mock::VerifyAndClearExpectations(&mock_reporter2);
Mock::VerifyAndClearExpectations(&all_process_observer);
Mock::VerifyAndClearExpectations(&single_process_observer);
// Must remove the observer before destroying the request to avoid a DCHECK
// from ObserverList.
all_process_request.RemoveObserver(&all_process_observer);
single_process_request.RemoveObserver(&single_process_observer);
}
TEST_F(V8DetailedMemoryRequestAnySeqTest, OneShot) {
CreateCrossProcessChildFrame();
// Set the all process request to only send once within the test.
V8DetailedMemoryRequestAnySeq all_process_request(kMinTimeBetweenRequests *
100);
// Create a mock reporter for each process and expect a query and reply on
// each.
MockV8DetailedMemoryReporter mock_reporter1;
{
auto data = NewPerProcessV8MemoryUsage(1);
data->isolates[0]->unassociated_bytes_used = 1ULL;
ExpectBindAndRespondToQuery(&mock_reporter1, std::move(data),
main_process_id());
}
MockV8DetailedMemoryReporter mock_reporter2;
{
auto data = NewPerProcessV8MemoryUsage(1);
data->isolates[0]->unassociated_bytes_used = 2ULL;
ExpectBindAndRespondToQuery(&mock_reporter2, std::move(data),
child_process_id());
}
task_environment()->RunUntilIdle();
Mock::VerifyAndClearExpectations(&mock_reporter1);
Mock::VerifyAndClearExpectations(&mock_reporter2);
// Create a one-shot request for process1 and expect the callback to be
// called only for that process.
{
auto data = NewPerProcessV8MemoryUsage(1);
data->isolates[0]->unassociated_bytes_used = 3ULL;
ExpectQueryAndReply(&mock_reporter1, std::move(data));
}
uint64_t unassociated_v8_bytes_used = 0;
V8DetailedMemoryRequestOneShotAnySeq process1_request;
process1_request.StartMeasurement(
main_process_id(),
base::BindLambdaForTesting(
[&](RenderProcessHostId process_id,
const V8DetailedMemoryProcessData& process_data,
const V8DetailedMemoryRequestOneShotAnySeq::FrameDataMap&
frame_data) {
EXPECT_EQ(process_id, main_process_id());
unassociated_v8_bytes_used =
process_data.unassociated_v8_bytes_used();
}));
task_environment()->RunUntilIdle();
Mock::VerifyAndClearExpectations(&mock_reporter1);
Mock::VerifyAndClearExpectations(&mock_reporter2);
EXPECT_EQ(unassociated_v8_bytes_used, 3ULL);
}
TEST_F(V8DetailedMemoryRequestAnySeqTest, OneShotLifetime) {
// Measure a child frame so that it can be detached.
CreateCrossProcessChildFrame();
// Create a one-shot request, but delete it before the result arrives.
MockV8DetailedMemoryReporter mock_reporter;
{
InSequence seq;
ExpectBindReceiver(&mock_reporter, child_process_id());
auto data = NewPerProcessV8MemoryUsage(1);
data->isolates[0]->unassociated_bytes_used = 1ULL;
ExpectQueryAndDelayReply(&mock_reporter, base::TimeDelta::FromSeconds(10),
std::move(data));
}
auto doomed_request = std::make_unique<V8DetailedMemoryRequestOneShotAnySeq>(
child_process_id(),
base::BindOnce(
[](RenderProcessHostId process_id,
const V8DetailedMemoryProcessData& process_data,
const V8DetailedMemoryRequestOneShotAnySeq::FrameDataMap&
frame_data) {
FAIL() << "Callback called after request deleted.";
}));
// Verify that requests are sent but reply is not received.
task_environment()->FastForwardBy(base::TimeDelta::FromSeconds(5));
Mock::VerifyAndClearExpectations(&mock_reporter);
doomed_request.reset();
task_environment()->FastForwardBy(base::TimeDelta::FromSeconds(5));
// Create a request that is deleted from within its own callback and make
// sure nothing explodes.
{
auto data = NewPerProcessV8MemoryUsage(1);
data->isolates[0]->unassociated_bytes_used = 2ULL;
ExpectQueryAndReply(&mock_reporter, std::move(data));
}
uint64_t unassociated_v8_bytes_used = 0;
doomed_request = std::make_unique<V8DetailedMemoryRequestOneShotAnySeq>(
child_process_id(),
base::BindLambdaForTesting(
[&](RenderProcessHostId process_id,
const V8DetailedMemoryProcessData& process_data,
const V8DetailedMemoryRequestOneShotAnySeq::FrameDataMap&
frame_data) {
doomed_request.reset();
unassociated_v8_bytes_used =
process_data.unassociated_v8_bytes_used();
}));
task_environment()->RunUntilIdle();
Mock::VerifyAndClearExpectations(&mock_reporter);
EXPECT_EQ(unassociated_v8_bytes_used, 2ULL);
// Ensure that resource-owning callbacks are freed when there is no response
// because the process dies.
{
auto data = NewPerProcessV8MemoryUsage(1);
data->isolates[0]->unassociated_bytes_used = 3ULL;
ExpectQueryAndDelayReply(&mock_reporter, base::TimeDelta::FromSeconds(10),
std::move(data));
}
auto lifetime_test = std::make_unique<LifetimeTestObject>();
auto weak_lifetime_test = lifetime_test->AsWeakPtr();
V8DetailedMemoryRequestOneShotAnySeq unfinished_request(
child_process_id(),
base::BindOnce(
[](std::unique_ptr<LifetimeTestObject>, RenderProcessHostId,
const V8DetailedMemoryProcessData&,
const V8DetailedMemoryRequestOneShotAnySeq::FrameDataMap&) {
FAIL() << "Callback called after process deleted.";
},
// Pass ownership to the callback. The object should be deleted if the
// callback is not called.
std::move(lifetime_test)));
// Verify that requests are sent but reply is not yet received.
task_environment()->FastForwardBy(base::TimeDelta::FromSeconds(5));
Mock::VerifyAndClearExpectations(&mock_reporter);
ASSERT_TRUE(weak_lifetime_test);
content::RenderFrameHostTester::For(child_frame())->Detach();
task_environment()->FastForwardBy(base::TimeDelta::FromSeconds(5));
EXPECT_FALSE(weak_lifetime_test);
}
TEST_F(V8DetailedMemoryRequestAnySeqTest, OneShotLifetimeAtExit) {
// Ensure that resource-owning callbacks are freed when there is no response
// because the browser is exiting (simulated by destroying the decorator).
MockV8DetailedMemoryReporter mock_reporter;
{
InSequence seq;
ExpectBindReceiver(&mock_reporter, main_process_id());
auto data = NewPerProcessV8MemoryUsage(1);
data->isolates[0]->unassociated_bytes_used = 1ULL;
ExpectQueryAndDelayReply(&mock_reporter, base::TimeDelta::FromSeconds(10),
std::move(data));
}
auto lifetime_test = std::make_unique<LifetimeTestObject>();
auto weak_lifetime_test = lifetime_test->AsWeakPtr();
V8DetailedMemoryRequestOneShotAnySeq unfinished_request(
main_process_id(),
base::BindOnce(
[](std::unique_ptr<LifetimeTestObject>, RenderProcessHostId,
const V8DetailedMemoryProcessData&,
const V8DetailedMemoryRequestOneShotAnySeq::FrameDataMap&) {
FAIL() << "Callback called after measurements cancelled.";
},
// Pass ownership to the callback. The object should be deleted if the
// callback is not called.
std::move(lifetime_test)));
// Verify that requests are sent but reply is not yet received.
task_environment()->FastForwardBy(base::TimeDelta::FromSeconds(5));
Mock::VerifyAndClearExpectations(&mock_reporter);
ASSERT_TRUE(weak_lifetime_test);
base::RunLoop run_loop;
PerformanceManager::CallOnGraph(
FROM_HERE,
base::BindOnce(&internal::DestroyV8DetailedMemoryDecoratorForTesting));
// Block in the run loop until the destroy task runs on the PM sequence.
PerformanceManager::CallOnGraph(FROM_HERE, run_loop.QuitClosure());
run_loop.Run();
task_environment()->FastForwardBy(base::TimeDelta::FromSeconds(5));
EXPECT_FALSE(weak_lifetime_test);
}
TEST_F(V8DetailedMemoryDecoratorTest, DedicatedWorkers) {
V8DetailedMemoryRequest memory_request(kMinTimeBetweenRequests, graph());
MockV8DetailedMemoryReporter reporter;
auto process = CreateNode<ProcessNodeImpl>(
content::PROCESS_TYPE_RENDERER,
RenderProcessHostProxy::CreateForTesting(kTestProcessID));
// Create a couple of frames with specified IDs.
auto page = CreateNode<PageNodeImpl>();
blink::LocalFrameToken frame_id = blink::LocalFrameToken();
auto frame = CreateNode<FrameNodeImpl>(process.get(), page.get(), nullptr, 1,
2, frame_id);
blink::DedicatedWorkerToken worker_id = blink::DedicatedWorkerToken();
auto worker = CreateNode<WorkerNodeImpl>(
WorkerNode::WorkerType::kDedicated, process.get(),
page->browser_context_id(), worker_id);
worker->AddClientFrame(frame.get());
{
auto data = NewPerProcessV8MemoryUsage(2);
AddIsolateMemoryUsage(frame_id, 1001u, data->isolates[0].get());
AddIsolateMemoryUsage(worker_id, 1002u, data->isolates[1].get());
ExpectBindAndRespondToQuery(&reporter, std::move(data));
}
task_env().RunUntilIdle();
Mock::VerifyAndClearExpectations(&reporter);
ASSERT_TRUE(V8DetailedMemoryExecutionContextData::ForFrameNode(frame.get()));
EXPECT_EQ(1001u,
V8DetailedMemoryExecutionContextData::ForFrameNode(frame.get())
->v8_bytes_used());
ASSERT_TRUE(
V8DetailedMemoryExecutionContextData::ForWorkerNode(worker.get()));
EXPECT_EQ(1002u,
V8DetailedMemoryExecutionContextData::ForWorkerNode(worker.get())
->v8_bytes_used());
worker->RemoveClientFrame(frame.get());
}
} // namespace v8_memory
} // namespace performance_manager