blob: f7c16d2aea9d83bfa0814a3fd447b9b96ec36204 [file] [log] [blame]
// Copyright 2017 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 "chrome/browser/metrics/process_memory_metrics_emitter.h"
#include "base/memory/ref_counted.h"
#include "base/message_loop/message_loop.h"
#include "base/run_loop.h"
#include "base/test/histogram_tester.h"
#include "base/test/trace_event_analyzer.h"
#include "base/trace_event/memory_dump_manager.h"
#include "base/trace_event/trace_config_memory_test_util.h"
#include "chrome/browser/extensions/extension_browsertest.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "chrome/test/base/tracing.h"
#include "chrome/test/base/ui_test_utils.h"
#include "components/ukm/test_ukm_recorder.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/common/content_switches.h"
#include "content/public/test/test_utils.h"
#include "extensions/features/features.h"
#include "net/dns/mock_host_resolver.h"
#include "services/metrics/public/cpp/ukm_builders.h"
#include "services/resource_coordinator/public/cpp/memory_instrumentation/memory_instrumentation.h"
#include "url/gurl.h"
#include "url/url_constants.h"
#if BUILDFLAG(ENABLE_EXTENSIONS)
#include "chrome/browser/extensions/test_extension_dir.h"
#include "extensions/browser/process_manager.h"
#include "extensions/common/extension.h"
#include "extensions/test/background_page_watcher.h"
#endif
namespace {
using base::trace_event::MemoryDumpType;
using memory_instrumentation::mojom::ProcessType;
#if BUILDFLAG(ENABLE_EXTENSIONS)
using extensions::BackgroundPageWatcher;
using extensions::Extension;
using extensions::ProcessManager;
using extensions::TestExtensionDir;
#endif
using UkmEntry = ukm::builders::Memory_Experimental;
void RequestGlobalDumpCallback(base::Closure quit_closure,
bool success,
uint64_t) {
base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE, quit_closure);
ASSERT_TRUE(success);
}
void OnStartTracingDoneCallback(
base::trace_event::MemoryDumpLevelOfDetail explicit_dump_type,
base::Closure quit_closure) {
memory_instrumentation::MemoryInstrumentation::GetInstance()
->RequestGlobalDumpAndAppendToTrace(
MemoryDumpType::EXPLICITLY_TRIGGERED, explicit_dump_type,
Bind(&RequestGlobalDumpCallback, quit_closure));
}
class ProcessMemoryMetricsEmitterFake : public ProcessMemoryMetricsEmitter {
public:
explicit ProcessMemoryMetricsEmitterFake(base::RunLoop* run_loop,
ukm::TestUkmRecorder* recorder)
: run_loop_(run_loop), recorder_(recorder) {}
private:
~ProcessMemoryMetricsEmitterFake() override {}
void ReceivedMemoryDump(
bool success,
uint64_t dump_guid,
memory_instrumentation::mojom::GlobalMemoryDumpPtr ptr) override {
EXPECT_TRUE(success);
ProcessMemoryMetricsEmitter::ReceivedMemoryDump(success, dump_guid,
std::move(ptr));
finished_memory_dump_ = true;
QuitIfFinished();
}
void ReceivedProcessInfos(
std::vector<resource_coordinator::mojom::ProcessInfoPtr> process_infos)
override {
ProcessMemoryMetricsEmitter::ReceivedProcessInfos(std::move(process_infos));
finished_process_info_ = true;
QuitIfFinished();
}
void QuitIfFinished() {
if (!finished_memory_dump_ || !finished_process_info_)
return;
if (run_loop_)
run_loop_->Quit();
}
ukm::UkmRecorder* GetUkmRecorder() override { return recorder_; }
base::RunLoop* run_loop_;
bool finished_memory_dump_ = false;
bool finished_process_info_ = false;
ukm::TestUkmRecorder* recorder_;
DISALLOW_COPY_AND_ASSIGN(ProcessMemoryMetricsEmitterFake);
};
void CheckMemoryMetric(const std::string& name,
const base::HistogramTester& histogram_tester,
int count,
bool check_minimum,
int number_of_processes = 1u) {
std::unique_ptr<base::HistogramSamples> samples(
histogram_tester.GetHistogramSamplesSinceCreation(name));
ASSERT_TRUE(samples);
bool count_matches = samples->TotalCount() == count * number_of_processes;
// The exact number of renderers present at the time the metrics are emitted
// is not deterministic. Sometimes there is an extra renderer.
if (name.find("Renderer") != std::string::npos) {
count_matches = samples->TotalCount() >= (count * number_of_processes) &&
samples->TotalCount() <= (number_of_processes + 1) * count;
}
EXPECT_TRUE(count_matches);
if (check_minimum)
EXPECT_GT(samples->sum(), 0u) << name;
// As a sanity check, no memory stat should exceed 4 GB.
int64_t maximum_expected_size = 1ll << 32;
EXPECT_LT(samples->sum(), maximum_expected_size) << name;
}
void CheckAllMemoryMetrics(const base::HistogramTester& histogram_tester,
int count,
int number_of_renderer_processes = 1u,
int number_of_extenstion_processes = 0u) {
CheckMemoryMetric("Memory.Experimental.Browser2.Malloc", histogram_tester,
count, true);
CheckMemoryMetric("Memory.Experimental.Browser2.Resident", histogram_tester,
count, true);
CheckMemoryMetric("Memory.Experimental.Browser2.PrivateMemoryFootprint",
histogram_tester, count, true);
if (number_of_renderer_processes) {
CheckMemoryMetric("Memory.Experimental.Renderer2.Malloc", histogram_tester,
count, true, number_of_renderer_processes);
CheckMemoryMetric("Memory.Experimental.Renderer2.Resident",
histogram_tester, count, true,
number_of_renderer_processes);
CheckMemoryMetric("Memory.Experimental.Renderer2.BlinkGC", histogram_tester,
count, false, number_of_renderer_processes);
CheckMemoryMetric("Memory.Experimental.Renderer2.PartitionAlloc",
histogram_tester, count, false,
number_of_renderer_processes);
CheckMemoryMetric("Memory.Experimental.Renderer2.V8", histogram_tester,
count, true, number_of_renderer_processes);
CheckMemoryMetric("Memory.Experimental.Renderer2.PrivateMemoryFootprint",
histogram_tester, count, true,
number_of_renderer_processes);
}
if (number_of_extenstion_processes) {
CheckMemoryMetric("Memory.Experimental.Extension2.Malloc", histogram_tester,
count, true, number_of_extenstion_processes);
CheckMemoryMetric("Memory.Experimental.Extension2.Resident",
histogram_tester, count, true,
number_of_extenstion_processes);
CheckMemoryMetric("Memory.Experimental.Extension2.BlinkGC",
histogram_tester, count, false,
number_of_extenstion_processes);
CheckMemoryMetric("Memory.Experimental.Extension2.PartitionAlloc",
histogram_tester, count, false,
number_of_extenstion_processes);
CheckMemoryMetric("Memory.Experimental.Extension2.V8", histogram_tester,
count, true, number_of_extenstion_processes);
CheckMemoryMetric("Memory.Experimental.Extension2.PrivateMemoryFootprint",
histogram_tester, count, true,
number_of_extenstion_processes);
}
CheckMemoryMetric("Memory.Experimental.Total2.PrivateMemoryFootprint",
histogram_tester, count, true);
}
} // namespace
class ProcessMemoryMetricsEmitterTest : public ExtensionBrowserTest {
public:
ProcessMemoryMetricsEmitterTest() {}
~ProcessMemoryMetricsEmitterTest() override {}
void SetUpOnMainThread() override {
ExtensionBrowserTest::SetUpOnMainThread();
host_resolver()->AddRule("*", "127.0.0.1");
}
void PreRunTestOnMainThread() override {
InProcessBrowserTest::PreRunTestOnMainThread();
// UKM DCHECKs if the active UkmRecorder is changed from one instance
// to another, rather than being changed from a nullptr; browser_tests
// need to circumvent that to be able to intercept UKM calls with its
// own TestUkmRecorder instance rather than the default UkmRecorder.
ukm::UkmRecorder::Set(nullptr);
test_ukm_recorder_ = base::MakeUnique<ukm::TestAutoSetUkmRecorder>();
}
void SetUpCommandLine(base::CommandLine* command_line) override {
InProcessBrowserTest::SetUpCommandLine(command_line);
command_line->AppendSwitchASCII(switches::kEnableFeatures,
ukm::kUkmFeature.name);
}
protected:
std::unique_ptr<ukm::TestAutoSetUkmRecorder> test_ukm_recorder_;
void CheckAllUkmSources(size_t metric_count = 1u) {
std::set<ukm::SourceId> source_ids = test_ukm_recorder_->GetSourceIds();
for (auto source_id : source_ids) {
if (ProcessHasTypeForSource(source_id, ProcessType::BROWSER)) {
CheckUkmBrowserSource(source_id, 1);
} else if (ProcessHasTypeForSource(source_id, ProcessType::RENDERER)) {
// Renderer metrics associate with navigation's source, instead of
// creating a new one.
CheckUkmRendererSource(source_id, metric_count);
} else if (ProcessHasTypeForSource(source_id, ProcessType::GPU)) {
// Not checked yet.
} else {
// This must be Total2.
CheckMemoryMetricWithName(
source_id, UkmEntry::kTotal2_PrivateMemoryFootprintName, false, 1);
}
}
}
void CheckMemoryMetricWithName(ukm::SourceId source_id,
const char* name,
bool can_be_zero,
size_t metric_count = 1u) {
std::vector<int64_t> metrics = test_ukm_recorder_->GetMetricValues(
source_id, UkmEntry::kEntryName, name);
EXPECT_EQ(metric_count, metrics.size()) << name;
if (metrics.size() > 0) {
int64_t metric = *metrics.begin();
EXPECT_GE(metric, can_be_zero ? 0 : 1) << name;
EXPECT_LE(metric, 4000) << name;
}
}
void CheckUkmRendererSource(ukm::SourceId source_id,
size_t metric_count = 1u) {
CheckMemoryMetricWithName(source_id, UkmEntry::kMallocName, false,
metric_count);
CheckMemoryMetricWithName(source_id, UkmEntry::kResidentName, false,
metric_count);
CheckMemoryMetricWithName(source_id, UkmEntry::kPrivateMemoryFootprintName,
false, metric_count);
CheckMemoryMetricWithName(source_id, UkmEntry::kBlinkGCName, true,
metric_count);
CheckMemoryMetricWithName(source_id, UkmEntry::kPartitionAllocName, true,
metric_count);
CheckMemoryMetricWithName(source_id, UkmEntry::kV8Name, true, metric_count);
CheckMemoryMetricWithName(source_id, UkmEntry::kNumberOfExtensionsName,
true, metric_count);
}
void CheckUkmBrowserSource(ukm::SourceId source_id,
size_t metric_count = 1u) {
CheckMemoryMetricWithName(source_id, UkmEntry::kMallocName, false,
metric_count);
CheckMemoryMetricWithName(source_id, UkmEntry::kResidentName, false,
metric_count);
CheckMemoryMetricWithName(source_id, UkmEntry::kPrivateMemoryFootprintName,
false, metric_count);
}
bool ProcessHasTypeForSource(ukm::SourceId source_id,
ProcessType process_type) {
std::vector<int64_t> metrics = test_ukm_recorder_->GetMetricValues(
source_id, UkmEntry::kEntryName, UkmEntry::kProcessTypeName);
return std::find(metrics.begin(), metrics.end(),
static_cast<int64_t>(process_type)) != metrics.end();
}
#if BUILDFLAG(ENABLE_EXTENSIONS)
// Create an barebones extension with a background page for the given name.
const Extension* CreateExtension(const std::string& name) {
auto dir = base::MakeUnique<TestExtensionDir>();
dir->WriteManifestWithSingleQuotes(
base::StringPrintf("{"
"'name': '%s',"
"'version': '1',"
"'manifest_version': 2,"
"'background': {'page': 'bg.html'}"
"}",
name.c_str()));
dir->WriteFile(FILE_PATH_LITERAL("bg.html"), "");
const Extension* extension = LoadExtension(dir->UnpackedPath());
EXPECT_TRUE(extension);
temp_dirs_.push_back(std::move(dir));
return extension;
}
const Extension* CreateHostedApp(const std::string& name,
const GURL& app_url) {
std::unique_ptr<TestExtensionDir> dir(new TestExtensionDir);
dir->WriteManifestWithSingleQuotes(base::StringPrintf(
"{"
"'name': '%s',"
"'version': '1',"
"'manifest_version': 2,"
"'app': {'urls': ['%s'], 'launch': {'web_url': '%s'}}"
"}",
name.c_str(), app_url.spec().c_str(), app_url.spec().c_str()));
const Extension* extension = LoadExtension(dir->UnpackedPath());
EXPECT_TRUE(extension);
temp_dirs_.push_back(std::move(dir));
return extension;
}
#endif
private:
#if BUILDFLAG(ENABLE_EXTENSIONS)
std::vector<std::unique_ptr<TestExtensionDir>> temp_dirs_;
#endif
DISALLOW_COPY_AND_ASSIGN(ProcessMemoryMetricsEmitterTest);
};
#if defined(ADDRESS_SANITIZER) || defined(MEMORY_SANITIZER)
#define MAYBE_FetchAndEmitMetrics DISABLED_FetchAndEmitMetrics
#else
#define MAYBE_FetchAndEmitMetrics FetchAndEmitMetrics
#endif
IN_PROC_BROWSER_TEST_F(ProcessMemoryMetricsEmitterTest,
MAYBE_FetchAndEmitMetrics) {
GURL url1(url::kAboutBlankURL);
ui_test_utils::NavigateToURLWithDisposition(
browser(), url1, WindowOpenDisposition::NEW_FOREGROUND_TAB,
ui_test_utils::BROWSER_TEST_WAIT_FOR_NAVIGATION);
base::HistogramTester histogram_tester;
base::RunLoop run_loop;
// Intentionally let emitter leave scope to check that it correctly keeps
// itself alive.
{
scoped_refptr<ProcessMemoryMetricsEmitterFake> emitter(
new ProcessMemoryMetricsEmitterFake(&run_loop,
test_ukm_recorder_.get()));
emitter->FetchAndEmitProcessMemoryMetrics();
}
run_loop.Run();
CheckAllMemoryMetrics(histogram_tester, 1);
CheckAllUkmSources();
}
#if BUILDFLAG(ENABLE_EXTENSIONS)
#if defined(ADDRESS_SANITIZER) || defined(MEMORY_SANITIZER)
#define MAYBE_FetchAndEmitMetricsWithExtensions \
DISABLED_FetchAndEmitMetricsWithExtensions
#else
#define MAYBE_FetchAndEmitMetricsWithExtensions \
FetchAndEmitMetricsWithExtensions
#endif
IN_PROC_BROWSER_TEST_F(ProcessMemoryMetricsEmitterTest,
MAYBE_FetchAndEmitMetricsWithExtensions) {
const Extension* extension1 = CreateExtension("Extension 1");
const Extension* extension2 = CreateExtension("Extension 2");
ProcessManager* pm = ProcessManager::Get(profile());
// Verify that the extensions has loaded.
BackgroundPageWatcher(pm, extension1).WaitForOpen();
BackgroundPageWatcher(pm, extension2).WaitForOpen();
EXPECT_EQ(1u, pm->GetRenderFrameHostsForExtension(extension1->id()).size());
EXPECT_EQ(1u, pm->GetRenderFrameHostsForExtension(extension2->id()).size());
GURL url1(url::kAboutBlankURL);
ui_test_utils::NavigateToURLWithDisposition(
browser(), url1, WindowOpenDisposition::NEW_FOREGROUND_TAB,
ui_test_utils::BROWSER_TEST_WAIT_FOR_NAVIGATION);
base::HistogramTester histogram_tester;
base::RunLoop run_loop;
// Intentionally let emitter leave scope to check that it correctly keeps
// itself alive.
{
scoped_refptr<ProcessMemoryMetricsEmitterFake> emitter(
new ProcessMemoryMetricsEmitterFake(&run_loop,
test_ukm_recorder_.get()));
emitter->FetchAndEmitProcessMemoryMetrics();
}
run_loop.Run();
CheckAllMemoryMetrics(histogram_tester, 1, 1, 2);
// Extensions do not get a UKM record.
CheckAllUkmSources();
}
#if defined(ADDRESS_SANITIZER) || defined(MEMORY_SANITIZER)
#define MAYBE_FetchAndEmitMetricsWithHostedApps \
DISABLED_FetchAndEmitMetricsWithHostedApps
#else
#define MAYBE_FetchAndEmitMetricsWithHostedApps \
FetchAndEmitMetricsWithHostedApps
#endif
IN_PROC_BROWSER_TEST_F(ProcessMemoryMetricsEmitterTest,
MAYBE_FetchAndEmitMetricsWithHostedApps) {
ASSERT_TRUE(embedded_test_server()->Start());
const GURL app_url = embedded_test_server()->GetURL("app.org", "/empty.html");
const Extension* app = CreateHostedApp("App", GURL("http://app.org"));
ui_test_utils::NavigateToURL(browser(), app_url);
// Verify that the hosted app has loaded.
ProcessManager* pm = ProcessManager::Get(profile());
EXPECT_EQ(1u, pm->GetRenderFrameHostsForExtension(app->id()).size());
GURL url1(url::kAboutBlankURL);
ui_test_utils::NavigateToURLWithDisposition(
browser(), url1, WindowOpenDisposition::NEW_FOREGROUND_TAB,
ui_test_utils::BROWSER_TEST_WAIT_FOR_NAVIGATION);
base::HistogramTester histogram_tester;
base::RunLoop run_loop;
// Intentionally let emitter leave scope to check that it correctly keeps
// itself alive.
{
scoped_refptr<ProcessMemoryMetricsEmitterFake> emitter(
new ProcessMemoryMetricsEmitterFake(&run_loop,
test_ukm_recorder_.get()));
emitter->FetchAndEmitProcessMemoryMetrics();
}
run_loop.Run();
// No extensions should be observed
CheckAllMemoryMetrics(histogram_tester, 1, 1, 0);
CheckAllUkmSources();
}
#if defined(ADDRESS_SANITIZER) || defined(MEMORY_SANITIZER)
#define MAYBE_FetchAndEmitMetricsWithExtensionsAndHostReuse \
DISABLED_FetchAndEmitMetricsWithExtensionsAndHostReuse
#else
#define MAYBE_FetchAndEmitMetricsWithExtensionsAndHostReuse \
FetchAndEmitMetricsWithExtensionsAndHostReuse
#endif
IN_PROC_BROWSER_TEST_F(ProcessMemoryMetricsEmitterTest,
MAYBE_FetchAndEmitMetricsWithExtensionsAndHostReuse) {
// This test does not work with --site-per-process flag since this test
// combines multiple extensions in the same process.
if (content::AreAllSitesIsolatedForTesting())
return;
// Limit the number of renderer processes to force reuse.
content::RenderProcessHost::SetMaxRendererProcessCount(1);
const Extension* extension1 = CreateExtension("Extension 1");
const Extension* extension2 = CreateExtension("Extension 2");
ProcessManager* pm = ProcessManager::Get(profile());
// Verify that the extensions has loaded.
BackgroundPageWatcher(pm, extension1).WaitForOpen();
BackgroundPageWatcher(pm, extension2).WaitForOpen();
EXPECT_EQ(1u, pm->GetRenderFrameHostsForExtension(extension1->id()).size());
EXPECT_EQ(1u, pm->GetRenderFrameHostsForExtension(extension2->id()).size());
GURL url1(url::kAboutBlankURL);
ui_test_utils::NavigateToURLWithDisposition(
browser(), url1, WindowOpenDisposition::NEW_FOREGROUND_TAB,
ui_test_utils::BROWSER_TEST_WAIT_FOR_NAVIGATION);
base::HistogramTester histogram_tester;
base::RunLoop run_loop;
// Intentionally let emitter leave scope to check that it correctly keeps
// itself alive.
{
scoped_refptr<ProcessMemoryMetricsEmitterFake> emitter(
new ProcessMemoryMetricsEmitterFake(&run_loop,
test_ukm_recorder_.get()));
emitter->FetchAndEmitProcessMemoryMetrics();
}
run_loop.Run();
// When hosts share a process, no unique URL is identified, therefore no UKM.
CheckAllMemoryMetrics(histogram_tester, 1, 1, 1);
}
#endif // BUILDFLAG(ENABLE_EXTENSIONS)
#if defined(ADDRESS_SANITIZER) || defined(MEMORY_SANITIZER)
#define MAYBE_FetchDuringTrace DISABLED_FetchDuringTrace
#else
#define MAYBE_FetchDuringTrace FetchDuringTrace
#endif
IN_PROC_BROWSER_TEST_F(ProcessMemoryMetricsEmitterTest,
MAYBE_FetchDuringTrace) {
GURL url1(url::kAboutBlankURL);
ui_test_utils::NavigateToURLWithDisposition(
browser(), url1, WindowOpenDisposition::NEW_FOREGROUND_TAB,
ui_test_utils::BROWSER_TEST_WAIT_FOR_NAVIGATION);
base::HistogramTester histogram_tester;
{
base::RunLoop run_loop;
base::trace_event::TraceConfig trace_config(
base::trace_event::TraceConfigMemoryTestUtil::
GetTraceConfig_EmptyTriggers());
ASSERT_TRUE(tracing::BeginTracingWithTraceConfig(
trace_config, Bind(&OnStartTracingDoneCallback,
base::trace_event::MemoryDumpLevelOfDetail::DETAILED,
run_loop.QuitClosure())));
run_loop.Run();
}
{
base::RunLoop run_loop;
scoped_refptr<ProcessMemoryMetricsEmitterFake> emitter(
new ProcessMemoryMetricsEmitterFake(&run_loop,
test_ukm_recorder_.get()));
emitter->FetchAndEmitProcessMemoryMetrics();
run_loop.Run();
}
std::string json_events;
ASSERT_TRUE(tracing::EndTracing(&json_events));
trace_analyzer::TraceEventVector events;
std::unique_ptr<trace_analyzer::TraceAnalyzer> analyzer(
trace_analyzer::TraceAnalyzer::Create(json_events));
analyzer->FindEvents(
trace_analyzer::Query::EventPhaseIs(TRACE_EVENT_PHASE_MEMORY_DUMP),
&events);
ASSERT_GT(events.size(), 1u);
ASSERT_TRUE(trace_analyzer::CountMatches(
events, trace_analyzer::Query::EventNameIs(MemoryDumpTypeToString(
MemoryDumpType::EXPLICITLY_TRIGGERED))));
CheckAllMemoryMetrics(histogram_tester, 1);
CheckAllUkmSources();
}
#if defined(ADDRESS_SANITIZER) || defined(MEMORY_SANITIZER)
#define MAYBE_FetchThreeTimes DISABLED_FetchThreeTimes
#else
#define MAYBE_FetchThreeTimes FetchThreeTimes
#endif
IN_PROC_BROWSER_TEST_F(ProcessMemoryMetricsEmitterTest, MAYBE_FetchThreeTimes) {
GURL url1(url::kAboutBlankURL);
ui_test_utils::NavigateToURLWithDisposition(
browser(), url1, WindowOpenDisposition::NEW_FOREGROUND_TAB,
ui_test_utils::BROWSER_TEST_WAIT_FOR_NAVIGATION);
base::HistogramTester histogram_tester;
base::RunLoop run_loop;
int count = 3;
for (int i = 0; i < count; ++i) {
// Only the last emitter should stop the run loop.
auto emitter = base::MakeRefCounted<ProcessMemoryMetricsEmitterFake>(
(i == count - 1) ? &run_loop : nullptr, test_ukm_recorder_.get());
emitter->FetchAndEmitProcessMemoryMetrics();
}
run_loop.Run();
CheckAllMemoryMetrics(histogram_tester, count);
CheckAllUkmSources(count);
}