blob: d3c3ec087e6a1c1414a3505cdd7979f332073d1d [file] [log] [blame]
// Copyright 2018 The Chromium OS 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 "metrics/process_meter.h"
#include <memory>
#include <gtest/gtest.h>
#include <base/check.h>
#include <base/check_op.h>
#include <base/files/file_path.h>
#include <base/files/file_util.h>
#include <base/files/scoped_temp_dir.h>
#include <base/stl_util.h>
#include <base/strings/string_util.h>
#include <base/strings/stringprintf.h>
namespace chromeos_metrics {
class ProcessMeterTest : public testing::Test {};
void CreateFile(const base::FilePath& path, std::string content) {
if (base::WriteFile(path, content.c_str(), content.length()) !=
content.length()) {
LOG(FATAL) << "cannot write to " << path.MaybeAsASCII();
}
}
void CreateProcEntry(const base::FilePath& procfs_path,
int pid,
int ppid,
const char* name,
const char* cmdline,
int total_mib,
int anon_mib,
int file_mib,
int shmem_mib,
int swap_mib) {
base::FilePath proc_pid_path(
procfs_path.Append(base::StringPrintf("%d", pid)));
base::FilePath totmaps_path(proc_pid_path.Append("totmaps"));
base::FilePath stat_path(proc_pid_path.Append("stat"));
base::FilePath cmdline_path(proc_pid_path.Append("cmdline"));
std::string stat_content =
base::StringPrintf("%d (%s) R %d 33 44 blah blah \n", pid, name, ppid);
bool is_kdaemon = total_mib == 0;
std::string totmaps_content =
is_kdaemon ? "blah\nblah\nblah"
: base::StringPrintf(
"blah\nblah\nblah\n"
"Pss: %d kB\n"
"Pss_Anon: %d kB\n"
"Pss_File: %d kB\n"
"Pss_Shmem: %d kB\n"
"blah\nblah\nblah\n"
"Swap: %d kB\n"
"blah\nblah\nblah\n",
total_mib * 1024, anon_mib * 1024, file_mib * 1024,
shmem_mib * 1024, swap_mib * 1024);
CHECK(CreateDirectory(proc_pid_path));
CreateFile(stat_path, stat_content);
CreateFile(totmaps_path, totmaps_content);
CreateFile(cmdline_path, std::string(cmdline));
}
// Test that we're classifying processes and adding up their sizes correctly.
TEST_F(ProcessMeterTest, ReportProcessStats) {
base::FilePath temp_dir;
EXPECT_TRUE(base::CreateNewTempDirectory("", &temp_dir));
base::FilePath run_path = temp_dir.Append("run");
base::FilePath procfs_path = temp_dir.Append("proc");
// Create arc init PID file in mock /run.
const int arc_init_pid = 22;
base::FilePath arc_init_path = run_path.Append(kMetricsARCInitPIDFile);
CHECK(CreateDirectory(arc_init_path.DirName()));
const std::string arc_init_pid_string =
base::StringPrintf("%d", arc_init_pid);
const char* s = arc_init_pid_string.c_str();
CreateFile(arc_init_path, s);
// Create mock /proc.
CHECK(CreateDirectory(procfs_path));
// Fill /proc with entries for a few processes.
// clang-format off
// init.
CreateProcEntry(procfs_path, 1, 0, "init", "/sbin/init",
10, 5, 5, 0, 7);
// ARC init.
CreateProcEntry(procfs_path, arc_init_pid, 1, "arc-init", "/blah/arc/init",
10, 5, 5, 0, 1);
// kthreadd (kernel daemon)
CreateProcEntry(procfs_path, 2, 0, "kthreadd", "",
0, 0, 0, 0, 0);
// kworker with a space in its name
CreateProcEntry(procfs_path, 2, 0, "kworker/0:0-My worker", "",
0, 0, 0, 0, 0);
// Browser processes.
CreateProcEntry(procfs_path, 100, 1, "chrome",
"/opt/google/chrome/chrome blah",
300, 200, 90, 10, 2);
CreateProcEntry(procfs_path, 101, 100, "chrome",
"/opt/google/chrome/chrome --type=broker",
5, 4, 3, 2, 1);
// GPU.
CreateProcEntry(procfs_path, 110, 100, "chrome",
"/opt/google/chrome/chrome --type=gpu-process",
400, 70, 30, 300, 3);
// Renderers.
CreateProcEntry(procfs_path, 120, 100, "chrome",
"/opt/google/chrome/chrome --type=renderer",
500, 450, 30, 20, 13);
CreateProcEntry(procfs_path, 121, 100, "chrome",
"/opt/google/chrome/chrome --type=renderer",
500, 450, 30, 20, 13);
// Daemons.
CreateProcEntry(procfs_path, 200, 1, "shill", "/usr/bin/shill",
100, 30, 70, 0, 0);
// clang-format on
// Get process info from mocked /proc.
ProcessInfo info(procfs_path, run_path);
info.Collect();
info.Classify();
const uint64_t mib = 1 << 20;
// clang-format off
const ProcessMemoryStats expected_stats[PG_KINDS_COUNT] = {
// browser
{{ 305 * mib, 204 * mib, 93 * mib, 12 * mib, 3 * mib}},
// gpu
{{ 400 * mib, 70 * mib, 30 * mib, 300 * mib, 3 * mib}},
// renderers
{{1000 * mib, 900 * mib, 60 * mib, 40 * mib, 26 * mib}},
// arc
{{ 10 * mib, 5 * mib, 5 * mib, 0, 1 * mib}},
// vms
{{ 0, 0, 0, 0, 0}},
// daemons
{{110 * mib, 35 * mib, 75 * mib, 0, 7 * mib}},
};
// clang-format on
for (int i = 0; i < PG_KINDS_COUNT; i++) {
ProcessMemoryStats stats;
ProcessGroupKind kind = static_cast<ProcessGroupKind>(i);
AccumulateProcessGroupStats(procfs_path, info.GetGroup(kind), &stats);
for (int j = 0; j < MEM_KINDS_COUNT; j++) {
EXPECT_EQ(stats.rss_sizes[j], expected_stats[i].rss_sizes[j]);
}
}
}
void CheckPG(int pg, const char* field) {
for (int i = 0; i < MEM_KINDS_COUNT; i++) {
CHECK(strcasestr(kProcessMemoryUMANames[pg][i], field) != NULL);
}
}
void CheckMem(int mem, const char* field) {
for (int i = 0; i < PG_KINDS_COUNT; i++) {
CHECK(strcasestr(kProcessMemoryUMANames[i][mem], field) != NULL);
}
}
// Test that the enum constants for process kind and memory kind match the UMA
// histogram names.
TEST_F(ProcessMeterTest, CheckUMANames) {
CheckPG(PG_BROWSER, "browser");
CheckPG(PG_GPU, "gpu");
CheckPG(PG_RENDERERS, "renderers");
CheckPG(PG_ARC, "arc");
CheckPG(PG_VMS, "vms");
CheckPG(PG_DAEMONS, "daemons");
CheckMem(MEM_TOTAL, "total");
CheckMem(MEM_ANON, "anon");
CheckMem(MEM_FILE, "file");
CheckMem(MEM_SHMEM, "shmem");
CheckMem(MEM_SWAP, "swap");
// Extra consistency checks.
ProcessMemoryStats stats;
CHECK_EQ(base::size(stats.rss_sizes), base::size(kProcessMemoryUMANames[0]));
CHECK_EQ(base::size(kProcessMemoryUMANames), PG_KINDS_COUNT);
}
} // namespace chromeos_metrics