Add meminfo UMA collection.
Change-Id: Ief779a5bdc68b8e5bf2f1ed979bf30b50aca8e0f
BUG=chromium-os:13747
TEST=verify that Platform.Meminfo* entries are in about:histograms.
Review URL: http://codereview.chromium.org/6804014
diff --git a/metrics_daemon.cc b/metrics_daemon.cc
index 4820590..273fa60 100644
--- a/metrics_daemon.cc
+++ b/metrics_daemon.cc
@@ -9,6 +9,7 @@
#include <base/file_util.h>
#include <base/logging.h>
+#include <base/string_util.h>
#include <dbus/dbus-glib-lowlevel.h>
#include "counter.h"
@@ -102,6 +103,8 @@
const int MetricsDaemon::kMetricDiskStatsShortInterval = 1; // seconds
const int MetricsDaemon::kMetricDiskStatsLongInterval = 30; // seconds
+const int MetricsDaemon::kMetricMeminfoInterval = 30; // seconds
+
// Assume a max rate of 250Mb/s for reads (worse for writes) and 512 byte
// sectors.
const int MetricsDaemon::kMetricSectorsIOMax = 500000; // sectors/second
@@ -248,6 +251,9 @@
DiskStatsReporterInit();
}
+ // Start collecting meminfo stats.
+ ScheduleMeminfoCallback(kMetricMeminfoInterval);
+
// Don't setup D-Bus and GLib in test mode.
if (testing)
return;
@@ -617,6 +623,124 @@
}
}
+void MetricsDaemon::ScheduleMeminfoCallback(int wait) {
+ if (testing_) {
+ return;
+ }
+ g_timeout_add_seconds(wait, MeminfoCallbackStatic, this);
+}
+
+// static
+gboolean MetricsDaemon::MeminfoCallbackStatic(void* handle) {
+ return (static_cast<MetricsDaemon*>(handle))->MeminfoCallback();
+}
+
+gboolean MetricsDaemon::MeminfoCallback() {
+ std::string meminfo;
+ const FilePath meminfo_path("/proc/meminfo");
+ if (!file_util::ReadFileToString(meminfo_path, &meminfo)) {
+ LOG(WARNING) << "cannot read " << meminfo_path.value().c_str();
+ return false;
+ }
+ return ProcessMeminfo(meminfo);
+}
+
+gboolean MetricsDaemon::ProcessMeminfo(std::string meminfo) {
+ // This array has one element for every item of /proc/meminfo that we want to
+ // report to UMA. They must be listed in the same order in which
+ // /proc/meminfo prints them.
+ struct {
+ const char* name; // print name
+ const char* match; // string to match in output of /proc/meminfo
+ int log_scale; // report with log scale instead of linear percent
+ } fields[] = {
+ { "MemTotal", "MemTotal" }, // SPECIAL CASE: total system memory
+ { "MemFree", "MemFree" },
+ { "Buffers", "Buffers" },
+ { "Cached", "Cached" },
+ // { "SwapCached", "SwapCached" },
+ { "Active", "Active" },
+ { "Inactive", "Inactive" },
+ { "ActiveAnon", "Active(anon)" },
+ { "InactiveAnon", "Inactive(anon)" },
+ { "ActiveFile" , "Active(file)" },
+ { "InactiveFile", "Inactive(file)" },
+ { "Unevictable", "Unevictable", 1 },
+ // { "Mlocked", "Mlocked" },
+ // { "SwapTotal", "SwapTotal" },
+ // { "SwapFree", "SwapFree" },
+ // { "Dirty", "Dirty" },
+ // { "Writeback", "Writeback" },
+ { "AnonPages", "AnonPages" },
+ { "Mapped", "Mapped" },
+ { "Shmem", "Shmem", 1 },
+ { "Slab", "Slab", 1 },
+ // { "SReclaimable", "SReclaimable" },
+ // { "SUnreclaim", "SUnreclaim" },
+ };
+ // arraysize doesn't work here, probably can't handle anonymous structs
+ const int nfields = sizeof(fields) / sizeof(fields[0]);
+ int total_memory = 0;
+ std::vector<std::string> lines;
+ int nlines = Tokenize(meminfo, "\n", &lines);
+
+ // Scan meminfo output and collect field values. Each field name has to
+ // match a meminfo entry (case insensitive) after removing non-alpha
+ // characters from the entry.
+ int i = 0;
+ int iline = 0;
+ for (;;) {
+ if (i == nfields) {
+ // all fields are matched
+ return true;
+ }
+ if (iline == nlines) {
+ // end of input reached while scanning
+ LOG(WARNING) << "cannot find field " << fields[i].match
+ << " and following";
+ return false;
+ }
+
+ std::vector<std::string> tokens;
+ Tokenize(lines[iline], ": ", &tokens);
+
+ if (strcmp(fields[i].match, tokens[0].c_str()) == 0) {
+ // name matches: parse value and report
+ int meminfo_value;
+ char metrics_name[128];
+ char* rest;
+ meminfo_value = static_cast<int>(strtol(tokens[1].c_str(), &rest, 10));
+ if (*rest != '\0') {
+ LOG(WARNING) << "missing meminfo value";
+ return false;
+ }
+ if (i == 0) {
+ // special case: total memory
+ total_memory = meminfo_value;
+ } else {
+ snprintf(metrics_name, sizeof(metrics_name),
+ "Platform.Meminfo%s", fields[i].name);
+ if (fields[i].log_scale) {
+ // report value in kbytes, log scale, 4Gb max
+ SendMetric(metrics_name, meminfo_value, 1, 4 * 1000 * 1000, 100);
+ } else {
+ // report value as percent of total memory
+ if (total_memory == 0) {
+ // this "cannot happen"
+ LOG(WARNING) << "borked meminfo parser";
+ return false;
+ }
+ int percent = meminfo_value * 100 / total_memory;
+ SendLinearMetric(metrics_name, percent, 100, 101);
+ }
+ }
+ // start looking for next field
+ i++;
+ }
+ iline++;
+ }
+}
+
// static
void MetricsDaemon::ReportDailyUse(void* handle, int tag, int count) {
if (count <= 0)
@@ -636,3 +760,13 @@
<< min << " " << max << " " << nbuckets;
metrics_lib_->SendToUMA(name, sample, min, max, nbuckets);
}
+
+void MetricsDaemon::SendLinearMetric(const string& name, int sample,
+ int max, int nbuckets) {
+ DLOG(INFO) << "received linear metric: " << name << " " << sample << " "
+ << max << " " << nbuckets;
+ // TODO(semenzato): add a proper linear histogram to the Chrome external
+ // metrics API.
+ LOG_IF(FATAL, nbuckets != max + 1) << "unsupported histogram scale";
+ metrics_lib_->SendEnumToUMA(name, sample, max);
+}
diff --git a/metrics_daemon.h b/metrics_daemon.h
index 5252518..e061445 100644
--- a/metrics_daemon.h
+++ b/metrics_daemon.h
@@ -49,6 +49,8 @@
FRIEND_TEST(MetricsDaemonTest, MessageFilter);
FRIEND_TEST(MetricsDaemonTest, PowerStateChanged);
FRIEND_TEST(MetricsDaemonTest, ProcessKernelCrash);
+ FRIEND_TEST(MetricsDaemonTest, ProcessMeminfo);
+ FRIEND_TEST(MetricsDaemonTest, ProcessMeminfo2);
FRIEND_TEST(MetricsDaemonTest, ProcessUncleanShutdown);
FRIEND_TEST(MetricsDaemonTest, ProcessUserCrash);
FRIEND_TEST(MetricsDaemonTest, ReportCrashesDailyFrequency);
@@ -125,6 +127,7 @@
static const char kMetricWriteSectorsShortName[];
static const int kMetricDiskStatsShortInterval;
static const int kMetricDiskStatsLongInterval;
+ static const int kMetricMeminfoInterval;
static const int kMetricSectorsIOMax;
static const int kMetricSectorsBuckets;
static const char kMetricsDiskStatsPath[];
@@ -234,6 +237,12 @@
void SendMetric(const std::string& name, int sample,
int min, int max, int nbuckets);
+ // Sends a linear histogram sample to Chrome for transport to UMA. See
+ // MetricsLibrary::SendToUMA in metrics_library.h for a description of the
+ // arguments.
+ void SendLinearMetric(const std::string& name, int sample,
+ int max, int nbuckets);
+
// Initializes disk stats reporting.
void DiskStatsReporterInit();
@@ -250,6 +259,20 @@
// Reports disk statistics.
void DiskStatsCallback();
+ // Schedules meminfo collection callback.
+ void ScheduleMeminfoCallback(int wait);
+
+ // Reports memory statistics (static version for glib). Argument is a glib
+ // artifact.
+ static gboolean MeminfoCallbackStatic(void* handle);
+
+ // Reports memory statistics. Returns false on failure.
+ gboolean MeminfoCallback();
+
+ // Parses content of /proc/meminfo and sends fields of interest to UMA.
+ // Returns false on errors.
+ gboolean ProcessMeminfo(std::string meminfo);
+
// Test mode.
bool testing_;
diff --git a/metrics_daemon_test.cc b/metrics_daemon_test.cc
index 208d8e5..476bf1c 100644
--- a/metrics_daemon_test.cc
+++ b/metrics_daemon_test.cc
@@ -27,6 +27,7 @@
using ::testing::_;
using ::testing::Return;
using ::testing::StrictMock;
+using ::testing::AtLeast;
static const int kSecondsPerDay = 24 * 60 * 60;
@@ -578,6 +579,66 @@
EXPECT_TRUE(ds_state != daemon_.diskstats_state_);
}
+TEST_F(MetricsDaemonTest, ProcessMeminfo) {
+ const char* meminfo = "\
+MemTotal: 2000000 kB\n\
+MemFree: 1000000 kB\n\
+Buffers: 10492 kB\n\
+Cached: 213652 kB\n\
+SwapCached: 0 kB\n\
+Active: 133400 kB\n\
+Inactive: 183396 kB\n\
+Active(anon): 92984 kB\n\
+Inactive(anon): 58860 kB\n\
+Active(file): 40416 kB\n\
+Inactive(file): 124536 kB\n\
+Unevictable: 0 kB\n\
+Mlocked: 0 kB\n\
+SwapTotal: 0 kB\n\
+SwapFree: 0 kB\n\
+Dirty: 40 kB\n\
+Writeback: 0 kB\n\
+AnonPages: 92652 kB\n\
+Mapped: 59716 kB\n\
+Shmem: 59196 kB\n\
+Slab: 16656 kB\n\
+SReclaimable: 6132 kB\n\
+SUnreclaim: 10524 kB\n\
+KernelStack: 1648 kB\n\
+PageTables: 2780 kB\n\
+NFS_Unstable: 0 kB\n\
+Bounce: 0 kB\n\
+WritebackTmp: 0 kB\n\
+CommitLimit: 970656 kB\n\
+Committed_AS: 1260528 kB\n\
+VmallocTotal: 122880 kB\n\
+VmallocUsed: 12144 kB\n\
+VmallocChunk: 103824 kB\n\
+DirectMap4k: 9636 kB\n\
+DirectMap2M: 1955840 kB\n\
+";
+ EXPECT_CALL(metrics_lib_, SendEnumToUMA(_, _, 100))
+ .Times(AtLeast(1));
+ EXPECT_CALL(metrics_lib_, SendToUMA(_, _, _, _, _))
+ .Times(AtLeast(1));
+ EXPECT_CALL(metrics_lib_, SendToUMA("NFS_Unstable", _, _, _, _))
+ .Times(0);
+ EXPECT_CALL(metrics_lib_, SendEnumToUMA("NFS_Unstable", _, _))
+ .Times(0);
+ EXPECT_TRUE(daemon_.ProcessMeminfo(meminfo));
+}
+
+TEST_F(MetricsDaemonTest, ProcessMeminfo2) {
+ const char* meminfo = "\
+MemTotal: 2000000 kB\n\
+MemFree: 1000000 kB\n\
+";
+ /* Not enough fields */
+ EXPECT_CALL(metrics_lib_, SendEnumToUMA(_, 50, 100))
+ .Times(1);
+ EXPECT_FALSE(daemon_.ProcessMeminfo(meminfo));
+}
+
int main(int argc, char** argv) {
testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();