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();