blob: 94d4592885a463d793d38a56f353da40ce9a447f [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/containers/flat_map.h"
#include "base/memory/ref_counted.h"
#include "base/process/process_handle.h"
#include "components/ukm/test_ukm_recorder.h"
#include "services/metrics/public/cpp/ukm_recorder.h"
#include "testing/gtest/include/gtest/gtest.h"
using GlobalMemoryDumpPtr = memory_instrumentation::mojom::GlobalMemoryDumpPtr;
using ProcessMemoryDumpPtr =
memory_instrumentation::mojom::ProcessMemoryDumpPtr;
using OSMemDumpPtr = memory_instrumentation::mojom::OSMemDumpPtr;
using ProcessType = memory_instrumentation::mojom::ProcessType;
using ProcessInfoPtr = resource_coordinator::mojom::ProcessInfoPtr;
using ProcessInfoVector = std::vector<ProcessInfoPtr>;
namespace {
// Provide fake to surface ReceivedMemoryDump and ReceivedProcessInfos to public
// visibility.
class ProcessMemoryMetricsEmitterFake : public ProcessMemoryMetricsEmitter {
public:
ProcessMemoryMetricsEmitterFake(
ukm::TestAutoSetUkmRecorder& test_ukm_recorder)
: ukm_recorder_(&test_ukm_recorder) {
MarkServiceRequestsInProgress();
}
void ReceivedMemoryDump(
bool success,
uint64_t dump_guid,
memory_instrumentation::mojom::GlobalMemoryDumpPtr ptr) override {
ProcessMemoryMetricsEmitter::ReceivedMemoryDump(success, dump_guid,
std::move(ptr));
}
void ReceivedProcessInfos(ProcessInfoVector process_infos) override {
ProcessMemoryMetricsEmitter::ReceivedProcessInfos(std::move(process_infos));
}
bool IsResourceCoordinatorEnabled() override { return true; }
ukm::UkmRecorder* GetUkmRecorder() override { return ukm_recorder_; }
int GetNumberOfExtensions(base::ProcessId pid) override {
switch (pid) {
case 401:
return 1;
default:
return 0;
}
}
private:
~ProcessMemoryMetricsEmitterFake() override {}
ukm::UkmRecorder* ukm_recorder_;
DISALLOW_COPY_AND_ASSIGN(ProcessMemoryMetricsEmitterFake);
};
OSMemDumpPtr GetFakeOSMemDump(uint32_t resident_set_kb,
uint32_t private_footprint_kb) {
using memory_instrumentation::mojom::VmRegion;
std::vector<memory_instrumentation::mojom::VmRegionPtr> vm_regions;
vm_regions.emplace_back(
VmRegion::New(0xdeadbeef, // start address
0x4000, // size_in_bytes
0x1234, // module_timestamp
VmRegion::kProtectionFlagsRead, // protection_flags
"dummy_file", // mapped_file
100, // byte_stats_private_dirty_resident
200, // byte_stats_private_clean_resident
300, // byte_stats_shared_dirty_resident
400, // byte_stats_shared_clean_resident
500, // byte_stats_swapped,
200)); // byte_stats_proportional_resident
return memory_instrumentation::mojom::OSMemDump::New(
resident_set_kb, private_footprint_kb, std::move(vm_regions));
}
void PopulateBrowserMetrics(GlobalMemoryDumpPtr& global_dump,
base::flat_map<const char*, int64_t>& metrics_mb) {
ProcessMemoryDumpPtr pmd(
memory_instrumentation::mojom::ProcessMemoryDump::New());
pmd->process_type = ProcessType::BROWSER;
pmd->chrome_dump = memory_instrumentation::mojom::ChromeMemDump::New();
pmd->chrome_dump->malloc_total_kb = metrics_mb["Malloc"] * 1024;
OSMemDumpPtr os_dump =
GetFakeOSMemDump(metrics_mb["Resident"] * 1024,
metrics_mb["PrivateMemoryFootprint"] * 1024);
pmd->os_dump = std::move(os_dump);
global_dump->process_dumps.push_back(std::move(pmd));
}
base::flat_map<const char*, int64_t> GetExpectedBrowserMetrics() {
return base::flat_map<const char*, int64_t>(
{
{"ProcessType", static_cast<int64_t>(ProcessType::BROWSER)},
{"Resident", 10},
{"Malloc", 20},
{"PrivateMemoryFootprint", 30},
},
base::KEEP_FIRST_OF_DUPES);
}
void PopulateRendererMetrics(GlobalMemoryDumpPtr& global_dump,
base::flat_map<const char*, int64_t>& metrics_mb,
base::ProcessId pid) {
ProcessMemoryDumpPtr pmd(
memory_instrumentation::mojom::ProcessMemoryDump::New());
pmd->process_type = ProcessType::RENDERER;
pmd->chrome_dump = memory_instrumentation::mojom::ChromeMemDump::New();
pmd->chrome_dump->malloc_total_kb = metrics_mb["Malloc"] * 1024;
pmd->chrome_dump->partition_alloc_total_kb =
metrics_mb["PartitionAlloc"] * 1024;
pmd->chrome_dump->blink_gc_total_kb = metrics_mb["BlinkGC"] * 1024;
pmd->chrome_dump->v8_total_kb = metrics_mb["V8"] * 1024;
OSMemDumpPtr os_dump =
GetFakeOSMemDump(metrics_mb["Resident"] * 1024,
metrics_mb["PrivateMemoryFootprint"] * 1024);
pmd->os_dump = std::move(os_dump);
pmd->pid = pid;
global_dump->process_dumps.push_back(std::move(pmd));
}
base::flat_map<const char*, int64_t> GetExpectedRendererMetrics() {
return base::flat_map<const char*, int64_t>(
{
{"ProcessType", static_cast<int64_t>(ProcessType::RENDERER)},
{"Resident", 110},
{"Malloc", 120},
{"PrivateMemoryFootprint", 130},
{"PartitionAlloc", 140},
{"BlinkGC", 150},
{"V8", 160},
{"NumberOfExtensions", 0},
},
base::KEEP_FIRST_OF_DUPES);
}
void PopulateGpuMetrics(GlobalMemoryDumpPtr& global_dump,
base::flat_map<const char*, int64_t>& metrics_mb) {
ProcessMemoryDumpPtr pmd(
memory_instrumentation::mojom::ProcessMemoryDump::New());
pmd->process_type = ProcessType::GPU;
pmd->chrome_dump = memory_instrumentation::mojom::ChromeMemDump::New();
pmd->chrome_dump->malloc_total_kb = metrics_mb["Malloc"] * 1024;
pmd->chrome_dump->command_buffer_total_kb =
metrics_mb["CommandBuffer"] * 1024;
OSMemDumpPtr os_dump =
GetFakeOSMemDump(metrics_mb["Resident"] * 1024,
metrics_mb["PrivateMemoryFootprint"] * 1024);
pmd->os_dump = std::move(os_dump);
global_dump->process_dumps.push_back(std::move(pmd));
}
base::flat_map<const char*, int64_t> GetExpectedGpuMetrics() {
return base::flat_map<const char*, int64_t>(
{
{"ProcessType", static_cast<int64_t>(ProcessType::GPU)},
{"Resident", 210},
{"Malloc", 220},
{"PrivateMemoryFootprint", 230},
{"CommandBuffer", 240},
},
base::KEEP_FIRST_OF_DUPES);
}
void PopulateMetrics(GlobalMemoryDumpPtr& global_dump,
ProcessType ptype,
base::flat_map<const char*, int64_t>& metrics_mb) {
switch (ptype) {
case ProcessType::BROWSER:
PopulateBrowserMetrics(global_dump, metrics_mb);
return;
case ProcessType::RENDERER:
PopulateRendererMetrics(global_dump, metrics_mb, 101);
return;
case ProcessType::GPU:
PopulateGpuMetrics(global_dump, metrics_mb);
return;
case ProcessType::UTILITY:
case ProcessType::PLUGIN:
case ProcessType::OTHER:
break;
}
// We shouldn't reach here.
FAIL() << "Unknown process type case " << ptype << ".";
}
base::flat_map<const char*, int64_t> GetExpectedProcessMetrics(
ProcessType ptype) {
switch (ptype) {
case ProcessType::BROWSER:
return GetExpectedBrowserMetrics();
case ProcessType::RENDERER:
return GetExpectedRendererMetrics();
case ProcessType::GPU:
return GetExpectedGpuMetrics();
case ProcessType::UTILITY:
case ProcessType::PLUGIN:
case ProcessType::OTHER:
break;
}
// We shouldn't reach here.
CHECK(false);
return base::flat_map<const char*, int64_t>();
}
ProcessInfoVector GetProcessInfo(ukm::UkmRecorder& ukm_recorder) {
ProcessInfoVector process_infos;
// Process 200 always has no URLs.
{
ProcessInfoPtr process_info(
resource_coordinator::mojom::ProcessInfo::New());
process_info->pid = 200;
process_infos.push_back(std::move(process_info));
}
// Process 201 always has 1 URL
{
ProcessInfoPtr process_info(
resource_coordinator::mojom::ProcessInfo::New());
process_info->pid = 201;
ukm::SourceId first_source_id = ukm::UkmRecorder::GetNewSourceID();
ukm_recorder.UpdateSourceURL(first_source_id,
GURL("http://www.url201.com/"));
process_info->ukm_source_ids.push_back(first_source_id);
process_infos.push_back(std::move(process_info));
}
// Process 202 always has 2 URL
{
ProcessInfoPtr process_info(
resource_coordinator::mojom::ProcessInfo::New());
process_info->pid = 202;
ukm::SourceId first_source_id = ukm::UkmRecorder::GetNewSourceID();
ukm::SourceId second_source_id = ukm::UkmRecorder::GetNewSourceID();
ukm_recorder.UpdateSourceURL(first_source_id,
GURL("http://www.url2021.com/"));
ukm_recorder.UpdateSourceURL(second_source_id,
GURL("http://www.url2022.com/"));
process_info->ukm_source_ids.push_back(first_source_id);
process_info->ukm_source_ids.push_back(second_source_id);
process_infos.push_back(std::move(process_info));
}
return process_infos;
}
} // namespace
class ProcessMemoryMetricsEmitterTest
: public testing::TestWithParam<ProcessType> {
public:
ProcessMemoryMetricsEmitterTest() {}
~ProcessMemoryMetricsEmitterTest() override {}
protected:
void CheckMemoryUkmEntryMetrics(
size_t entry_num,
base::flat_map<const char*, int64_t> expected) {
const ukm::mojom::UkmEntry* entry = test_ukm_recorder_.GetEntry(entry_num);
CHECK(entry != nullptr);
EXPECT_EQ(expected.size(), entry->metrics.size());
for (auto it = expected.begin(); it != expected.end(); ++it) {
const ukm::mojom::UkmMetric* actual =
test_ukm_recorder_.FindMetric(entry, it->first);
CHECK(actual != nullptr);
EXPECT_EQ(it->second, actual->value);
}
}
ukm::TestAutoSetUkmRecorder test_ukm_recorder_;
private:
DISALLOW_COPY_AND_ASSIGN(ProcessMemoryMetricsEmitterTest);
};
TEST_P(ProcessMemoryMetricsEmitterTest, CollectsSingleProcessUKMs) {
base::flat_map<const char*, int64_t> expected_metrics =
GetExpectedProcessMetrics(GetParam());
uint64_t dump_guid = 333;
GlobalMemoryDumpPtr global_dump(
memory_instrumentation::mojom::GlobalMemoryDump::New());
PopulateMetrics(global_dump, GetParam(), expected_metrics);
scoped_refptr<ProcessMemoryMetricsEmitterFake> emitter(
new ProcessMemoryMetricsEmitterFake(test_ukm_recorder_));
emitter->ReceivedProcessInfos(ProcessInfoVector());
emitter->ReceivedMemoryDump(true, dump_guid, std::move(global_dump));
EXPECT_EQ(2u, test_ukm_recorder_.entries_count());
CheckMemoryUkmEntryMetrics(0, expected_metrics);
}
INSTANTIATE_TEST_CASE_P(SinglePtype,
ProcessMemoryMetricsEmitterTest,
testing::Values(ProcessType::BROWSER,
ProcessType::RENDERER,
ProcessType::GPU));
TEST_F(ProcessMemoryMetricsEmitterTest, CollectsExtensionProcessUKMs) {
base::flat_map<const char*, int64_t> expected_metrics =
GetExpectedRendererMetrics();
expected_metrics["NumberOfExtensions"] = 1;
uint64_t dump_guid = 333;
GlobalMemoryDumpPtr global_dump(
memory_instrumentation::mojom::GlobalMemoryDump::New());
PopulateRendererMetrics(global_dump, expected_metrics, 401);
scoped_refptr<ProcessMemoryMetricsEmitterFake> emitter(
new ProcessMemoryMetricsEmitterFake(test_ukm_recorder_));
emitter->ReceivedProcessInfos(ProcessInfoVector());
emitter->ReceivedMemoryDump(true, dump_guid, std::move(global_dump));
EXPECT_EQ(2u, test_ukm_recorder_.entries_count());
CheckMemoryUkmEntryMetrics(0, expected_metrics);
}
TEST_F(ProcessMemoryMetricsEmitterTest, CollectsManyProcessUKMsSingleDump) {
std::vector<ProcessType> entries_ptypes = {
ProcessType::BROWSER, ProcessType::RENDERER, ProcessType::GPU,
ProcessType::GPU, ProcessType::RENDERER, ProcessType::BROWSER,
};
uint64_t dump_guid = 333;
GlobalMemoryDumpPtr global_dump(
memory_instrumentation::mojom::GlobalMemoryDump::New());
std::vector<base::flat_map<const char*, int64_t>> entries_metrics;
for (const auto& ptype : entries_ptypes) {
auto expected_metrics = GetExpectedProcessMetrics(ptype);
PopulateMetrics(global_dump, ptype, expected_metrics);
entries_metrics.push_back(expected_metrics);
}
scoped_refptr<ProcessMemoryMetricsEmitterFake> emitter(
new ProcessMemoryMetricsEmitterFake(test_ukm_recorder_));
emitter->ReceivedProcessInfos(ProcessInfoVector());
emitter->ReceivedMemoryDump(true, dump_guid, std::move(global_dump));
EXPECT_EQ(7u, test_ukm_recorder_.entries_count());
for (size_t i = 0; i < entries_ptypes.size(); ++i) {
CheckMemoryUkmEntryMetrics(i, entries_metrics[i]);
}
}
TEST_F(ProcessMemoryMetricsEmitterTest, CollectsManyProcessUKMsManyDumps) {
std::vector<std::vector<ProcessType>> entries_ptypes = {
{ProcessType::BROWSER, ProcessType::RENDERER, ProcessType::GPU},
{ProcessType::GPU, ProcessType::RENDERER, ProcessType::BROWSER},
};
std::vector<base::flat_map<const char*, int64_t>> entries_metrics;
for (int i = 0; i < 2; ++i) {
scoped_refptr<ProcessMemoryMetricsEmitterFake> emitter(
new ProcessMemoryMetricsEmitterFake(test_ukm_recorder_));
GlobalMemoryDumpPtr global_dump(
memory_instrumentation::mojom::GlobalMemoryDump::New());
for (const auto& ptype : entries_ptypes[i]) {
auto expected_metrics = GetExpectedProcessMetrics(ptype);
PopulateMetrics(global_dump, ptype, expected_metrics);
entries_metrics.push_back(expected_metrics);
}
emitter->ReceivedProcessInfos(ProcessInfoVector());
emitter->ReceivedMemoryDump(true, i, std::move(global_dump));
}
EXPECT_EQ(8u, test_ukm_recorder_.entries_count());
for (size_t i = 0; i < entries_ptypes.size(); ++i) {
CheckMemoryUkmEntryMetrics(i, entries_metrics[i]);
}
}
TEST_F(ProcessMemoryMetricsEmitterTest, ReceiveProcessInfoFirst) {
GlobalMemoryDumpPtr global_dump(
memory_instrumentation::mojom::GlobalMemoryDump::New());
base::flat_map<const char*, int64_t> expected_metrics =
GetExpectedRendererMetrics();
PopulateRendererMetrics(global_dump, expected_metrics, 201);
scoped_refptr<ProcessMemoryMetricsEmitterFake> emitter(
new ProcessMemoryMetricsEmitterFake(test_ukm_recorder_));
emitter->ReceivedProcessInfos(GetProcessInfo(test_ukm_recorder_));
emitter->ReceivedMemoryDump(true, 0xBEEF, std::move(global_dump));
EXPECT_EQ(1,
test_ukm_recorder_.CountEntries(
*test_ukm_recorder_.GetSourceForUrl("http://www.url201.com/"),
"Memory.Experimental"));
EXPECT_EQ(0,
test_ukm_recorder_.CountEntries(
*test_ukm_recorder_.GetSourceForUrl("http://www.url2021.com/"),
"Memory.Experimental"));
EXPECT_EQ(0,
test_ukm_recorder_.CountEntries(
*test_ukm_recorder_.GetSourceForUrl("http://www.url2022.com/"),
"Memory.Experimental"));
// The second entry is for total memory, which we don't care about in this
// test.
EXPECT_EQ(2u, test_ukm_recorder_.entries_count());
CheckMemoryUkmEntryMetrics(0, expected_metrics);
}
TEST_F(ProcessMemoryMetricsEmitterTest, ReceiveProcessInfoSecond) {
GlobalMemoryDumpPtr global_dump(
memory_instrumentation::mojom::GlobalMemoryDump::New());
base::flat_map<const char*, int64_t> expected_metrics =
GetExpectedRendererMetrics();
PopulateRendererMetrics(global_dump, expected_metrics, 201);
scoped_refptr<ProcessMemoryMetricsEmitterFake> emitter(
new ProcessMemoryMetricsEmitterFake(test_ukm_recorder_));
emitter->ReceivedMemoryDump(true, 0xBEEF, std::move(global_dump));
emitter->ReceivedProcessInfos(GetProcessInfo(test_ukm_recorder_));
EXPECT_EQ(1,
test_ukm_recorder_.CountEntries(
*test_ukm_recorder_.GetSourceForUrl("http://www.url201.com/"),
"Memory.Experimental"));
EXPECT_EQ(0,
test_ukm_recorder_.CountEntries(
*test_ukm_recorder_.GetSourceForUrl("http://www.url2021.com/"),
"Memory.Experimental"));
EXPECT_EQ(0,
test_ukm_recorder_.CountEntries(
*test_ukm_recorder_.GetSourceForUrl("http://www.url2022.com/"),
"Memory.Experimental"));
// The second entry is for total memory, which we don't care about in this
// test.
EXPECT_EQ(2u, test_ukm_recorder_.entries_count());
CheckMemoryUkmEntryMetrics(0, expected_metrics);
}
TEST_F(ProcessMemoryMetricsEmitterTest, ProcessInfoHasTwoURLs) {
GlobalMemoryDumpPtr global_dump(
memory_instrumentation::mojom::GlobalMemoryDump::New());
base::flat_map<const char*, int64_t> expected_metrics =
GetExpectedRendererMetrics();
PopulateRendererMetrics(global_dump, expected_metrics, 200);
PopulateRendererMetrics(global_dump, expected_metrics, 201);
PopulateRendererMetrics(global_dump, expected_metrics, 202);
scoped_refptr<ProcessMemoryMetricsEmitterFake> emitter(
new ProcessMemoryMetricsEmitterFake(test_ukm_recorder_));
emitter->ReceivedMemoryDump(true, 0xBEEF, std::move(global_dump));
emitter->ReceivedProcessInfos(GetProcessInfo(test_ukm_recorder_));
// Check that if there are two URLs, neither is emitted.
EXPECT_EQ(1,
test_ukm_recorder_.CountEntries(
*test_ukm_recorder_.GetSourceForUrl("http://www.url201.com/"),
"Memory.Experimental"));
EXPECT_EQ(0,
test_ukm_recorder_.CountEntries(
*test_ukm_recorder_.GetSourceForUrl("http://www.url2021.com/"),
"Memory.Experimental"));
EXPECT_EQ(0,
test_ukm_recorder_.CountEntries(
*test_ukm_recorder_.GetSourceForUrl("http://www.url2022.com/"),
"Memory.Experimental"));
}