metrics_daemon: add zram stats collection
Memory compression stats are being collected by Chrome, but it
is more natural to do it here since they are system-wide rather than
Chrome-specific.
In addition, this provides better granularity for the compression ratio
(percents, from 100% to 600%) since we're especially interested in the
distribution of values between 1 and 2, and currently these all fall
in the same bucket.
Finally, we collect more interesting stats on zero pages.
BUG=chromium:315113
TEST=unit testing, checked about:histograms
Change-Id: I09c974989661d42f45d44afd428e8114e4ee1dbd
Reviewed-on: https://chromium-review.googlesource.com/202587
Reviewed-by: Luigi Semenzato <semenzato@chromium.org>
Commit-Queue: Luigi Semenzato <semenzato@chromium.org>
Tested-by: Luigi Semenzato <semenzato@chromium.org>
diff --git a/metrics_daemon.cc b/metrics_daemon.cc
index e3f6bf7..9b66877 100644
--- a/metrics_daemon.cc
+++ b/metrics_daemon.cc
@@ -111,6 +111,12 @@
const char MetricsDaemon::kMetricScaledCpuFrequencyName[] =
"Platform.CpuFrequencyThermalScaling";
+// Zram sysfs entries.
+
+const char MetricsDaemon::kComprDataSizeName[] = "compr_data_size";
+const char MetricsDaemon::kOrigDataSizeName[] = "orig_data_size";
+const char MetricsDaemon::kZeroPagesName[] = "zero_pages";
+
// Memory use stats collection intervals. We collect some memory use interval
// at these intervals after boot, and we stop collecting after the last one,
// with the assumption that in most cases the memory use won't change much
@@ -737,7 +743,62 @@
LOG(WARNING) << "cannot read " << meminfo_path.value().c_str();
return false;
}
- return ProcessMeminfo(meminfo_raw);
+ // Make both calls even if the first one fails.
+ bool success = ProcessMeminfo(meminfo_raw);
+ return ReportZram(base::FilePath(FILE_PATH_LITERAL("/sys/block/zram0"))) &&
+ success;
+}
+
+// static
+bool MetricsDaemon::ReadFileToUint64(const base::FilePath& path,
+ uint64* value) {
+ std::string content;
+ if (!base::ReadFileToString(path, &content)) {
+ PLOG(WARNING) << "cannot read " << path.MaybeAsASCII();
+ return false;
+ }
+ if (!base::StringToUint64(content, value)) {
+ LOG(WARNING) << "invalid integer: " << content;
+ return false;
+ }
+ return true;
+}
+
+bool MetricsDaemon::ReportZram(const base::FilePath& zram_dir) {
+ // Data sizes are in bytes. |zero_pages| is in number of pages.
+ uint64 compr_data_size, orig_data_size, zero_pages;
+ const size_t page_size = 4096;
+
+ if (!ReadFileToUint64(zram_dir.Append(kComprDataSizeName),
+ &compr_data_size) ||
+ !ReadFileToUint64(zram_dir.Append(kOrigDataSizeName), &orig_data_size) ||
+ !ReadFileToUint64(zram_dir.Append(kZeroPagesName), &zero_pages)) {
+ return false;
+ }
+
+ // |orig_data_size| does not include zero-filled pages.
+ orig_data_size += zero_pages * page_size;
+
+ const int compr_data_size_mb = compr_data_size >> 20;
+ const int savings_mb = (orig_data_size - compr_data_size) >> 20;
+ const int zero_ratio_percent = zero_pages * page_size * 100 / orig_data_size;
+
+ // Report compressed size in megabytes. 100 MB or less has little impact.
+ SendSample("Platform.ZramCompressedSize", compr_data_size_mb, 100, 4000, 50);
+ SendSample("Platform.ZramSavings", savings_mb, 100, 4000, 50);
+ // The compression ratio is multiplied by 100 for better resolution. The
+ // ratios of interest are between 1 and 6 (100% and 600% as reported). We
+ // don't want samples when very little memory is being compressed.
+ if (compr_data_size_mb >= 1) {
+ SendSample("Platform.ZramCompressionRatioPercent",
+ orig_data_size * 100 / compr_data_size, 100, 600, 50);
+ }
+ // The values of interest for zero_pages are between 1MB and 1GB. The units
+ // are number of pages.
+ SendSample("Platform.ZramZeroPages", zero_pages, 256, 256 * 1024, 50);
+ SendSample("Platform.ZramZeroRatioPercent", zero_ratio_percent, 1, 50, 50);
+
+ return true;
}
bool MetricsDaemon::ProcessMeminfo(const string& meminfo_raw) {
diff --git a/metrics_daemon.h b/metrics_daemon.h
index 81e5386..df85a2f 100644
--- a/metrics_daemon.h
+++ b/metrics_daemon.h
@@ -36,6 +36,12 @@
// forking.
void Run(bool run_as_daemon);
+ protected:
+ // Used also by the unit tests.
+ static const char kComprDataSizeName[];
+ static const char kOrigDataSizeName[];
+ static const char kZeroPagesName[];
+
private:
friend class MetricsDaemonTest;
FRIEND_TEST(MetricsDaemonTest, CheckSystemCrash);
@@ -59,6 +65,7 @@
FRIEND_TEST(MetricsDaemonTest, ReportUserCrashInterval);
FRIEND_TEST(MetricsDaemonTest, SendSample);
FRIEND_TEST(MetricsDaemonTest, SendCpuThrottleMetrics);
+ FRIEND_TEST(MetricsDaemonTest, SendZramMetrics);
// State for disk stats collector callback.
enum StatsState {
@@ -270,6 +277,14 @@
// Invoked periodically by |update_stats_timeout_id_| to call UpdateStats().
static gboolean HandleUpdateStatsTimeout(gpointer data);
+ // Reports zram statistics.
+ bool ReportZram(const base::FilePath& zram_dir);
+
+ // Reads a string from a file and converts it to uint64.
+ static bool ReadFileToUint64(const base::FilePath& path, uint64* value);
+
+ // VARIABLES
+
// Test mode.
bool testing_;
diff --git a/metrics_daemon_test.cc b/metrics_daemon_test.cc
index 33d0d11..3d5f5d7 100644
--- a/metrics_daemon_test.cc
+++ b/metrics_daemon_test.cc
@@ -9,6 +9,7 @@
#include <base/at_exit.h>
#include <base/file_util.h>
+#include <base/strings/string_number_conversions.h>
#include <base/strings/stringprintf.h>
#include <chromeos/dbus/service_constants.h>
#include <gtest/gtest.h>
@@ -30,8 +31,7 @@
using ::testing::StrictMock;
using chromeos_metrics::PersistentIntegerMock;
-static const char kTestDir[] = "test";
-static const char kFakeDiskStatsPath[] = "fake-disk-stats";
+static const char kFakeDiskStatsName[] = "fake-disk-stats";
static const char kFakeDiskStatsFormat[] =
" 1793 1788 %d 105580 "
" 196 175 %d 30290 "
@@ -40,7 +40,7 @@
static const int kFakeReadSectors[] = {80000, 100000};
static const int kFakeWriteSectors[] = {3000, 4000};
-static const char kFakeVmStatsPath[] = "fake-vm-stats";
+static const char kFakeVmStatsName[] = "fake-vm-stats";
static const char kFakeScalingMaxFreqPath[] = "fake-scaling-max-freq";
static const char kFakeCpuinfoMaxFreqPath[] = "fake-cpuinfo-max-freq";
@@ -54,16 +54,13 @@
kFakeReadSectors[1],
kFakeWriteSectors[1]);
CreateFakeDiskStatsFile(kFakeDiskStats[0].c_str());
- CreateFakeCpuFrequencyFile(kFakeCpuinfoMaxFreqPath, 10000000);
- CreateFakeCpuFrequencyFile(kFakeScalingMaxFreqPath, 10000000);
+ CreateUint64ValueFile(base::FilePath(kFakeCpuinfoMaxFreqPath), 10000000);
+ CreateUint64ValueFile(base::FilePath(kFakeScalingMaxFreqPath), 10000000);
chromeos_metrics::PersistentInteger::SetTestingMode(true);
- daemon_.Init(true, &metrics_lib_, kFakeDiskStatsPath, kFakeVmStatsPath,
+ daemon_.Init(true, &metrics_lib_, kFakeDiskStatsName, kFakeVmStatsName,
kFakeScalingMaxFreqPath, kFakeCpuinfoMaxFreqPath);
- base::DeleteFile(FilePath(kTestDir), true);
- base::CreateDirectory(FilePath(kTestDir));
-
// Replace original persistent values with mock ones.
daily_active_use_mock_ =
new StrictMock<PersistentIntegerMock>("1.mock");
@@ -84,7 +81,7 @@
}
virtual void TearDown() {
- EXPECT_EQ(0, unlink(kFakeDiskStatsPath));
+ EXPECT_EQ(0, unlink(kFakeDiskStatsName));
EXPECT_EQ(0, unlink(kFakeScalingMaxFreqPath));
EXPECT_EQ(0, unlink(kFakeCpuinfoMaxFreqPath));
}
@@ -157,23 +154,22 @@
// Creates or overwrites an input file containing fake disk stats.
void CreateFakeDiskStatsFile(const char* fake_stats) {
- if (unlink(kFakeDiskStatsPath) < 0) {
+ if (unlink(kFakeDiskStatsName) < 0) {
EXPECT_EQ(errno, ENOENT);
}
- FILE* f = fopen(kFakeDiskStatsPath, "w");
+ FILE* f = fopen(kFakeDiskStatsName, "w");
EXPECT_EQ(1, fwrite(fake_stats, strlen(fake_stats), 1, f));
EXPECT_EQ(0, fclose(f));
}
- // Creates or overwrites an input file containing a fake CPU frequency.
- void CreateFakeCpuFrequencyFile(const char* filename, int frequency) {
- FilePath path(filename);
+ // Creates or overwrites the file in |path| so that it contains the printable
+ // representation of |value|.
+ void CreateUint64ValueFile(const base::FilePath& path, uint64 value) {
base::DeleteFile(path, false);
- std::string frequency_string = StringPrintf("%d\n", frequency);
- int frequency_string_length = frequency_string.length();
- EXPECT_EQ(frequency_string.length(),
- base::WriteFile(path, frequency_string.c_str(),
- frequency_string_length));
+ std::string value_string = base::Uint64ToString(value);
+ ASSERT_EQ(value_string.length(),
+ base::WriteFile(path, value_string.c_str(),
+ value_string.length()));
}
// The MetricsDaemon under test.
@@ -351,8 +347,9 @@
const int fake_max_freq = 2000000;
int scaled_freq = 0;
int max_freq = 0;
- CreateFakeCpuFrequencyFile(kFakeScalingMaxFreqPath, fake_scaled_freq);
- CreateFakeCpuFrequencyFile(kFakeCpuinfoMaxFreqPath, fake_max_freq);
+ CreateUint64ValueFile(base::FilePath(kFakeScalingMaxFreqPath),
+ fake_scaled_freq);
+ CreateUint64ValueFile(base::FilePath(kFakeCpuinfoMaxFreqPath), fake_max_freq);
EXPECT_TRUE(daemon_.testing_);
EXPECT_TRUE(daemon_.ReadFreqToInt(kFakeScalingMaxFreqPath, &scaled_freq));
EXPECT_TRUE(daemon_.ReadFreqToInt(kFakeCpuinfoMaxFreqPath, &max_freq));
@@ -361,17 +358,51 @@
}
TEST_F(MetricsDaemonTest, SendCpuThrottleMetrics) {
- CreateFakeCpuFrequencyFile(kFakeCpuinfoMaxFreqPath, 2001000);
+ CreateUint64ValueFile(base::FilePath(kFakeCpuinfoMaxFreqPath), 2001000);
// Test the 101% and 100% cases.
- CreateFakeCpuFrequencyFile(kFakeScalingMaxFreqPath, 2001000);
+ CreateUint64ValueFile(base::FilePath(kFakeScalingMaxFreqPath), 2001000);
EXPECT_TRUE(daemon_.testing_);
EXPECT_CALL(metrics_lib_, SendEnumToUMA(_, 101, 101));
daemon_.SendCpuThrottleMetrics();
- CreateFakeCpuFrequencyFile(kFakeScalingMaxFreqPath, 2000000);
+ CreateUint64ValueFile(base::FilePath(kFakeScalingMaxFreqPath), 2000000);
EXPECT_CALL(metrics_lib_, SendEnumToUMA(_, 100, 101));
daemon_.SendCpuThrottleMetrics();
}
+TEST_F(MetricsDaemonTest, SendZramMetrics) {
+ EXPECT_TRUE(daemon_.testing_);
+
+ // |compr_data_size| is the size in bytes of compressed data.
+ const uint64 compr_data_size = 50 * 1000 * 1000;
+ // The constant '3' is a realistic but random choice.
+ // |orig_data_size| does not include zero pages.
+ const uint64 orig_data_size = compr_data_size * 3;
+ const uint64 page_size = 4096;
+ const uint64 zero_pages = 10 * 1000 * 1000 / page_size;
+
+ CreateUint64ValueFile(base::FilePath(MetricsDaemon::kComprDataSizeName),
+ compr_data_size);
+ CreateUint64ValueFile(base::FilePath(MetricsDaemon::kOrigDataSizeName),
+ orig_data_size);
+ CreateUint64ValueFile(base::FilePath(MetricsDaemon::kZeroPagesName),
+ zero_pages);
+
+ const uint64 real_orig_size = orig_data_size + zero_pages * page_size;
+ const uint64 zero_ratio_percent =
+ zero_pages * page_size * 100 / real_orig_size;
+ // Ratio samples are in percents.
+ const uint64 actual_ratio_sample = real_orig_size * 100 / compr_data_size;
+
+ EXPECT_CALL(metrics_lib_, SendToUMA(_, compr_data_size >> 20, _, _, _));
+ EXPECT_CALL(metrics_lib_,
+ SendToUMA(_, (real_orig_size - compr_data_size) >> 20, _, _, _));
+ EXPECT_CALL(metrics_lib_, SendToUMA(_, actual_ratio_sample, _, _, _));
+ EXPECT_CALL(metrics_lib_, SendToUMA(_, zero_pages, _, _, _));
+ EXPECT_CALL(metrics_lib_, SendToUMA(_, zero_ratio_percent, _, _, _));
+
+ EXPECT_TRUE(daemon_.ReportZram(base::FilePath(".")));
+}
+
int main(int argc, char** argv) {
testing::InitGoogleTest(&argc, argv);
// Some libchrome calls need this.