Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 8accd33d authored by Luigi Semenzato's avatar Luigi Semenzato
Browse files

Add memuse stats and fix disk I/O computations.

This changes adds memory usage stats after 1  5, 30, 150, and 750
minutes of active use (approximated by wall
clock time minus sleep time).  We log only the anonymous memory
usage (active + inactive) as we expect that other types of
allocations would not convey much additional information.

This also fixes the disk I/O computation to use actual active
time elapsed between callbacks, instead of assuming the expected
interval has elapsed.  The existing code causes errors in
both directions when a suspend/resume cycle occurs between callbacks.

BUG=14209
TEST=verify that about:histograms page contains Platform.MemuseAnon1 after 1 minute and Platform.MemuseAnon2 after 5 minutes.

Change-Id: Ib32d915fac7766a9fca7125105224889ea93050e
Reviewed-on: http://gerrit.chromium.org/gerrit/1132


Reviewed-by: default avatarDarin Petkov <petkov@chromium.org>
Tested-by: default avatarLuigi Semenzato <semenzato@chromium.org>
parent b7780ce4
Loading
Loading
Loading
Loading
+195 −74
Original line number Original line Diff line number Diff line
@@ -5,7 +5,9 @@
#include "metrics_daemon.h"
#include "metrics_daemon.h"


#include <fcntl.h>
#include <fcntl.h>
#include <math.h>
#include <string.h>
#include <string.h>
#include <time.h>


#include <base/file_util.h>
#include <base/file_util.h>
#include <base/logging.h>
#include <base/logging.h>
@@ -17,7 +19,10 @@
using base::Time;
using base::Time;
using base::TimeDelta;
using base::TimeDelta;
using base::TimeTicks;
using base::TimeTicks;
using std::map;
using std::string;
using std::string;
using std::vector;



#define SAFE_MESSAGE(e) (e.message ? e.message : "unknown error")
#define SAFE_MESSAGE(e) (e.message ? e.message : "unknown error")
#define DBUS_IFACE_CRASH_REPORTER "org.chromium.CrashReporter"
#define DBUS_IFACE_CRASH_REPORTER "org.chromium.CrashReporter"
@@ -144,17 +149,46 @@ const char* MetricsDaemon::kSessionStates_[] = {
#include "session_states.h"
#include "session_states.h"
};
};


// 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
// after that.
static const int kMemuseIntervals[] = {
  1 * kSecondsPerMinute,    // 1 minute mark
  4 * kSecondsPerMinute,    // 5 minute mark
  25 * kSecondsPerMinute,   // 0.5 hour mark
  120 * kSecondsPerMinute,  // 2.5 hour mark
  600 * kSecondsPerMinute,  // 12.5 hour mark
};

MetricsDaemon::MetricsDaemon()
MetricsDaemon::MetricsDaemon()
    : power_state_(kUnknownPowerState),
    : power_state_(kUnknownPowerState),
      session_state_(kUnknownSessionState),
      session_state_(kUnknownSessionState),
      user_active_(false),
      user_active_(false),
      usemon_interval_(0),
      usemon_interval_(0),
      usemon_source_(NULL) {}
      usemon_source_(NULL),
      memuse_initial_time_(0),
      memuse_interval_index_(0),
      read_sectors_(0),
      write_sectors_(0),
      diskstats_state_(kDiskStatsShort),
      diskstats_initial_time_(0) {}


MetricsDaemon::~MetricsDaemon() {
MetricsDaemon::~MetricsDaemon() {
  DeleteFrequencyCounters();
  DeleteFrequencyCounters();
}
}


double MetricsDaemon::GetActiveTime() {
  struct timespec ts;
  int r = clock_gettime(CLOCK_MONOTONIC, &ts);
  if (r < 0) {
    PLOG(WARNING) << "clock_gettime(CLOCK_MONOTONIC) failed";
    return 0;
  } else {
    return ts.tv_sec + ((double) ts.tv_nsec) / (1000 * 1000 * 1000);
  }
}

void MetricsDaemon::DeleteFrequencyCounters() {
void MetricsDaemon::DeleteFrequencyCounters() {
  for (FrequencyCounters::iterator i = frequency_counters_.begin();
  for (FrequencyCounters::iterator i = frequency_counters_.begin();
       i != frequency_counters_.end(); ++i) {
       i != frequency_counters_.end(); ++i) {
@@ -217,7 +251,7 @@ void MetricsDaemon::ConfigureCrashFrequencyReporter(
}
}


void MetricsDaemon::Init(bool testing, MetricsLibraryInterface* metrics_lib,
void MetricsDaemon::Init(bool testing, MetricsLibraryInterface* metrics_lib,
                         string diskstats_path) {
                         const string& diskstats_path) {
  testing_ = testing;
  testing_ = testing;
  DCHECK(metrics_lib != NULL);
  DCHECK(metrics_lib != NULL);
  metrics_lib_ = metrics_lib;
  metrics_lib_ = metrics_lib;
@@ -253,6 +287,7 @@ void MetricsDaemon::Init(bool testing, MetricsLibraryInterface* metrics_lib,


  // Start collecting meminfo stats.
  // Start collecting meminfo stats.
  ScheduleMeminfoCallback(kMetricMeminfoInterval);
  ScheduleMeminfoCallback(kMetricMeminfoInterval);
  ScheduleMemuseCallback(true, 0);


  // Don't setup D-Bus and GLib in test mode.
  // Don't setup D-Bus and GLib in test mode.
  if (testing)
  if (testing)
@@ -457,7 +492,7 @@ void MetricsDaemon::ProcessUncleanShutdown() {
  frequency_counters_[kMetricAnyCrashesWeeklyName]->Update(1);
  frequency_counters_[kMetricAnyCrashesWeeklyName]->Update(1);
}
}


bool MetricsDaemon::CheckSystemCrash(const std::string& crash_file) {
bool MetricsDaemon::CheckSystemCrash(const string& crash_file) {
  FilePath crash_detected(crash_file);
  FilePath crash_detected(crash_file);
  if (!file_util::PathExists(crash_detected))
  if (!file_util::PathExists(crash_detected))
    return false;
    return false;
@@ -533,8 +568,13 @@ void MetricsDaemon::DiskStatsReporterInit() {
  DiskStatsReadStats(&read_sectors_, &write_sectors_);
  DiskStatsReadStats(&read_sectors_, &write_sectors_);
  // The first time around just run the long stat, so we don't delay boot.
  // The first time around just run the long stat, so we don't delay boot.
  diskstats_state_ = kDiskStatsLong;
  diskstats_state_ = kDiskStatsLong;
  diskstats_initial_time_ = GetActiveTime();
  if (diskstats_initial_time_ < 0) {
    LOG(WARNING) << "not collecting disk stats";
  } else {
    ScheduleDiskStatsCallback(kMetricDiskStatsLongInterval);
    ScheduleDiskStatsCallback(kMetricDiskStatsLongInterval);
  }
  }
}


void MetricsDaemon::ScheduleDiskStatsCallback(int wait) {
void MetricsDaemon::ScheduleDiskStatsCallback(int wait) {
  if (testing_) {
  if (testing_) {
@@ -574,21 +614,32 @@ gboolean MetricsDaemon::DiskStatsCallbackStatic(void* handle) {
  return false;  // one-time callback
  return false;  // one-time callback
}
}


// Collects disk stats alternating over a short and a long interval.

void MetricsDaemon::DiskStatsCallback() {
void MetricsDaemon::DiskStatsCallback() {
  long int read_sectors_now, write_sectors_now;
  long int read_sectors_now, write_sectors_now;
  double time_now = GetActiveTime();
  double delta_time = time_now - diskstats_initial_time_;
  if (testing_) {
    // Fake the time when testing.
    delta_time = diskstats_state_ == kDiskStatsShort ?
        kMetricDiskStatsShortInterval : kMetricDiskStatsLongInterval;
  }
  DiskStatsReadStats(&read_sectors_now, &write_sectors_now);
  DiskStatsReadStats(&read_sectors_now, &write_sectors_now);
  int delta_read = read_sectors_now - read_sectors_;
  int delta_write = write_sectors_now - write_sectors_;
  int read_sectors_per_second = delta_read / delta_time;
  int write_sectors_per_second = delta_write / delta_time;


  switch (diskstats_state_) {
  switch (diskstats_state_) {
    case kDiskStatsShort:
    case kDiskStatsShort:
      SendMetric(kMetricReadSectorsShortName,
      SendMetric(kMetricReadSectorsShortName,
                 (int) (read_sectors_now - read_sectors_) /
                 read_sectors_per_second,
                 kMetricDiskStatsShortInterval,
                 1,
                 1,
                 kMetricSectorsIOMax,
                 kMetricSectorsIOMax,
                 kMetricSectorsBuckets);
                 kMetricSectorsBuckets);
      SendMetric(kMetricWriteSectorsShortName,
      SendMetric(kMetricWriteSectorsShortName,
                 (int) (write_sectors_now - write_sectors_) /
                 write_sectors_per_second,
                 kMetricDiskStatsShortInterval,
                 1,
                 1,
                 kMetricSectorsIOMax,
                 kMetricSectorsIOMax,
                 kMetricSectorsBuckets);
                 kMetricSectorsBuckets);
@@ -599,20 +650,20 @@ void MetricsDaemon::DiskStatsCallback() {
      break;
      break;
    case kDiskStatsLong:
    case kDiskStatsLong:
      SendMetric(kMetricReadSectorsLongName,
      SendMetric(kMetricReadSectorsLongName,
                 (int) (read_sectors_now - read_sectors_) /
                 read_sectors_per_second,
                 kMetricDiskStatsLongInterval,
                 1,
                 1,
                 kMetricSectorsIOMax,
                 kMetricSectorsIOMax,
                 kMetricSectorsBuckets);
                 kMetricSectorsBuckets);
      SendMetric(kMetricWriteSectorsLongName,
      SendMetric(kMetricWriteSectorsLongName,
                 (int) (write_sectors_now - write_sectors_) /
                 write_sectors_per_second,
                 kMetricDiskStatsLongInterval,
                 1,
                 1,
                 kMetricSectorsIOMax,
                 kMetricSectorsIOMax,
                 kMetricSectorsBuckets);
                 kMetricSectorsBuckets);
      // Reset sector counters
      // Reset sector counters.
      read_sectors_ = read_sectors_now;
      read_sectors_ = read_sectors_now;
      write_sectors_ = write_sectors_now;
      write_sectors_ = write_sectors_now;
      // Set start time for new cycle.
      diskstats_initial_time_ = time_now;
      // Schedule short callback.
      // Schedule short callback.
      diskstats_state_ = kDiskStatsShort;
      diskstats_state_ = kDiskStatsShort;
      ScheduleDiskStatsCallback(kMetricDiskStatsShortInterval);
      ScheduleDiskStatsCallback(kMetricDiskStatsShortInterval);
@@ -635,24 +686,17 @@ gboolean MetricsDaemon::MeminfoCallbackStatic(void* handle) {
}
}


gboolean MetricsDaemon::MeminfoCallback() {
gboolean MetricsDaemon::MeminfoCallback() {
  std::string meminfo;
  string meminfo_raw;
  const FilePath meminfo_path("/proc/meminfo");
  const FilePath meminfo_path("/proc/meminfo");
  if (!file_util::ReadFileToString(meminfo_path, &meminfo)) {
  if (!file_util::ReadFileToString(meminfo_path, &meminfo_raw)) {
    LOG(WARNING) << "cannot read " << meminfo_path.value().c_str();
    LOG(WARNING) << "cannot read " << meminfo_path.value().c_str();
    return false;
    return false;
  }
  }
  return ProcessMeminfo(meminfo);
  return ProcessMeminfo(meminfo_raw);
}
}


gboolean MetricsDaemon::ProcessMeminfo(std::string meminfo) {
gboolean MetricsDaemon::ProcessMeminfo(const string& meminfo_raw) {
  // This array has one element for every item of /proc/meminfo that we want to
  static const MeminfoRecord fields_array[] = {
  // 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
    { "MemTotal", "MemTotal" },  // SPECIAL CASE: total system memory
    { "MemFree", "MemFree" },
    { "MemFree", "MemFree" },
    { "Buffers", "Buffers" },
    { "Buffers", "Buffers" },
@@ -677,67 +721,144 @@ gboolean MetricsDaemon::ProcessMeminfo(std::string meminfo) {
    // { "SReclaimable", "SReclaimable" },
    // { "SReclaimable", "SReclaimable" },
    // { "SUnreclaim", "SUnreclaim" },
    // { "SUnreclaim", "SUnreclaim" },
  };
  };
  // arraysize doesn't work here, probably can't handle anonymous structs
  vector<MeminfoRecord> fields(fields_array,
  const int nfields = sizeof(fields) / sizeof(fields[0]);
                               fields_array + arraysize(fields_array));
  int total_memory = 0;
  if (!FillMeminfo(meminfo_raw, &fields)) {
  std::vector<std::string> lines;
    return false;
  int nlines = Tokenize(meminfo, "\n", &lines);
  }
  int total_memory = fields[0].value;
  if (total_memory == 0) {
    // this "cannot happen"
    LOG(WARNING) << "borked meminfo parser";
    return false;
  }
  // Send all fields retrieved, except total memory.
  for (unsigned int i = 1; i < fields.size(); i++) {
    string metrics_name = StringPrintf("Platform.Meminfo%s", fields[i].name);
    if (fields[i].log_scale) {
      // report value in kbytes, log scale, 4Gb max
      SendMetric(metrics_name, fields[i].value, 1, 4 * 1000 * 1000, 100);
    } else {
      // report value as percent of total memory
      int percent = fields[i].value * 100 / total_memory;
      SendLinearMetric(metrics_name, percent, 100, 101);
    }
  }
  return true;
}

gboolean MetricsDaemon::FillMeminfo(const string& meminfo_raw,
                                    vector<MeminfoRecord>* fields) {
  vector<string> lines;
  unsigned int nlines = Tokenize(meminfo_raw, "\n", &lines);


  // Scan meminfo output and collect field values.  Each field name has to
  // Scan meminfo output and collect field values.  Each field name has to
  // match a meminfo entry (case insensitive) after removing non-alpha
  // match a meminfo entry (case insensitive) after removing non-alpha
  // characters from the entry.
  // characters from the entry.
  int i = 0;
  unsigned int ifield = 0;
  int iline = 0;
  for (unsigned int iline = 0;
  for (;;) {
       iline < nlines && ifield < fields->size();
    if (i == nfields) {
       iline++) {
      // all fields are matched
    vector<string> tokens;
      return true;
    Tokenize(lines[iline], ": ", &tokens);
    if (strcmp((*fields)[ifield].match, tokens[0].c_str()) == 0) {
      // Name matches. Parse value and save.
      char* rest;
      (*fields)[ifield].value =
          static_cast<int>(strtol(tokens[1].c_str(), &rest, 10));
      if (*rest != '\0') {
        LOG(WARNING) << "missing meminfo value";
        return false;
      }
      }
    if (iline == nlines) {
      ifield++;
      // end of input reached while scanning
    }
      LOG(WARNING) << "cannot find field " << fields[i].match
  }
  if (ifield < fields->size()) {
    // End of input reached while scanning.
    LOG(WARNING) << "cannot find field " << (*fields)[ifield].match
                 << " and following";
                 << " and following";
    return false;
    return false;
  }
  }
  return true;
}


    std::vector<std::string> tokens;
void MetricsDaemon::ScheduleMemuseCallback(gboolean new_callback,
    Tokenize(lines[iline], ": ", &tokens);
                                           double time_elapsed) {
  if (testing_) {
    return;
  }
  int interval = kMemuseIntervals[memuse_interval_index_];
  int wait;
  if (new_callback) {
    memuse_initial_time_ = GetActiveTime();
    wait = interval;
  } else {
    wait = ceil(interval - time_elapsed);  // round up
  }
  g_timeout_add_seconds(wait, MemuseCallbackStatic, this);
}


    if (strcmp(fields[i].match, tokens[0].c_str()) == 0) {
// static
      // name matches: parse value and report
gboolean MetricsDaemon::MemuseCallbackStatic(void* handle) {
      int meminfo_value;
  MetricsDaemon* daemon = static_cast<MetricsDaemon*>(handle);
      char metrics_name[128];
  daemon->MemuseCallback();
      char* rest;
      meminfo_value = static_cast<int>(strtol(tokens[1].c_str(), &rest, 10));
      if (*rest != '\0') {
        LOG(WARNING) << "missing meminfo value";
  return false;
  return false;
}
}
      if (i == 0) {

        // special case: total memory
void MetricsDaemon::MemuseCallback() {
        total_memory = meminfo_value;
  // Since we only care about active time (i.e. uptime minus sleep time) but
      } else {
  // the callbacks are driven by real time (uptime), we check if we should
        snprintf(metrics_name, sizeof(metrics_name),
  // reschedule this callback due to intervening sleep periods.
                 "Platform.Meminfo%s", fields[i].name);
  double now = GetActiveTime();
        if (fields[i].log_scale) {
  double active_time = now - memuse_initial_time_;
          // report value in kbytes, log scale, 4Gb max
  if (active_time < kMemuseIntervals[memuse_interval_index_]) {
          SendMetric(metrics_name, meminfo_value, 1, 4 * 1000 * 1000, 100);
    // Not enough active time has passed.  Reschedule the callback.
    ScheduleMemuseCallback(false, active_time);
  } else {
  } else {
          // report value as percent of total memory
    // Enough active time has passed.  Do the work, and (if we succeed) see if
          if (total_memory == 0) {
    // we need to do more.
            // this "cannot happen"
    if (MemuseCallbackWork() &&
            LOG(WARNING) << "borked meminfo parser";
        memuse_interval_index_ < arraysize(kMemuseIntervals)) {
            return false;
      memuse_interval_index_++;
      ScheduleMemuseCallback(true, 0);
    }
    }
          int percent = meminfo_value * 100 / total_memory;
          SendLinearMetric(metrics_name, percent, 100, 101);
  }
  }
}
}
      // start looking for next field

      i++;
gboolean MetricsDaemon::MemuseCallbackWork() {
  string meminfo_raw;
  const FilePath meminfo_path("/proc/meminfo");
  if (!file_util::ReadFileToString(meminfo_path, &meminfo_raw)) {
    LOG(WARNING) << "cannot read " << meminfo_path.value().c_str();
    return false;
  }
  }
    iline++;
  return ProcessMemuse(meminfo_raw);
}
}

gboolean MetricsDaemon::ProcessMemuse(const string& meminfo_raw) {
  static const MeminfoRecord fields_array[] = {
    { "MemTotal", "MemTotal" },  // SPECIAL CASE: total system memory
    { "ActiveAnon", "Active(anon)" },
    { "InactiveAnon", "Inactive(anon)" },
  };
  vector<MeminfoRecord> fields(fields_array,
                               fields_array + arraysize(fields_array));
  if (!FillMeminfo(meminfo_raw, &fields)) {
    return false;
  }
  int total = fields[0].value;
  int active_anon = fields[1].value;
  int inactive_anon = fields[2].value;
  if (total == 0) {
    // this "cannot happen"
    LOG(WARNING) << "borked meminfo parser";
    return false;
  }
  string metrics_name = StringPrintf("Platform.MemuseAnon%d",
                                     memuse_interval_index_);
  SendLinearMetric(metrics_name, (active_anon + inactive_anon) * 100 / total,
                   100, 101);
  return true;
}
}


// static
// static
+50 −3
Original line number Original line Diff line number Diff line
@@ -30,7 +30,7 @@ class MetricsDaemon {


  // Initializes.
  // Initializes.
  void Init(bool testing, MetricsLibraryInterface* metrics_lib,
  void Init(bool testing, MetricsLibraryInterface* metrics_lib,
            std::string diskstats_path);
            const std::string& diskstats_path);


  // Does all the work. If |run_as_daemon| is true, daemonizes by
  // Does all the work. If |run_as_daemon| is true, daemonizes by
  // forking.
  // forking.
@@ -95,6 +95,14 @@ class MetricsDaemon {
    int seconds_;
    int seconds_;
  };
  };


  // Record for retrieving and reporting values from /proc/meminfo.
  struct MeminfoRecord {
    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
    int value;             // value from /proc/meminfo
  };

  typedef std::map<std::string, chromeos_metrics::FrequencyCounter*>
  typedef std::map<std::string, chromeos_metrics::FrequencyCounter*>
    FrequencyCounters;
    FrequencyCounters;


@@ -141,6 +149,9 @@ class MetricsDaemon {
  // Array of user session states.
  // Array of user session states.
  static const char* kSessionStates_[kNumberSessionStates];
  static const char* kSessionStates_[kNumberSessionStates];


  // Returns the active time since boot (uptime minus sleep time) in seconds.
  double GetActiveTime();

  // Clears and deletes the data contained in frequency_counters_.
  // Clears and deletes the data contained in frequency_counters_.
  void DeleteFrequencyCounters();
  void DeleteFrequencyCounters();


@@ -270,8 +281,37 @@ class MetricsDaemon {
  gboolean MeminfoCallback();
  gboolean MeminfoCallback();


  // Parses content of /proc/meminfo and sends fields of interest to UMA.
  // Parses content of /proc/meminfo and sends fields of interest to UMA.
  // Returns false on errors.
  // Returns false on errors.  |meminfo_raw| contains the content of
  gboolean ProcessMeminfo(std::string meminfo);
  // /proc/meminfo.
  gboolean ProcessMeminfo(const std::string& meminfo_raw);

  // Parses meminfo data from |meminfo_raw|.  |fields| is a vector containing
  // the fields of interest.  The order of the fields must be the same in which
  // /proc/meminfo prints them.  The result of parsing fields[i] is placed in
  // fields[i].value.
  gboolean FillMeminfo(const std::string& meminfo_raw,
                       std::vector<MeminfoRecord>* fields);

  // Schedule a memory use callback.  |new_callback| is true when this callback
  // is scheduled for the first time.  When |new_callback| is false,
  // |time_elapsed| is the active (non-sleep) time that has passed between now
  // and the original callback scheduling time.  We use it to reschedule a
  // callback that fired too early because we slept.
  void ScheduleMemuseCallback(gboolean new_callback, double time_elapsed);

  // Static wrapper for MemuseCallback.  Always returns false.
  static gboolean MemuseCallbackStatic(void* handle);

  // Calls MemuseCallbackWork, and possibly schedules next callback, if enough
  // active time has passed.  Otherwise reschedules itself to simulate active
  // time callbacks (i.e. wall clock time minus sleep time).
  void MemuseCallback();

  // Reads /proc/meminfo and sends total anonymous memory usage to UMA.
  gboolean MemuseCallbackWork();

  // Parse meminfo data and send to UMA.
  gboolean ProcessMemuse(const std::string& meminfo_raw);


  // Test mode.
  // Test mode.
  bool testing_;
  bool testing_;
@@ -322,12 +362,19 @@ class MetricsDaemon {
  // Scheduled daily use monitor source (see ScheduleUseMonitor).
  // Scheduled daily use monitor source (see ScheduleUseMonitor).
  GSource* usemon_source_;
  GSource* usemon_source_;


  // Time of initial scheduling of memuse callback
  double memuse_initial_time_;

  // Selects the wait time for the next memory use callback.
  unsigned int memuse_interval_index_;

  // Contains the most recent disk stats.
  // Contains the most recent disk stats.
  long int read_sectors_;
  long int read_sectors_;
  long int write_sectors_;
  long int write_sectors_;


  DiskStatsState diskstats_state_;
  DiskStatsState diskstats_state_;
  std::string diskstats_path_;
  std::string diskstats_path_;
  double diskstats_initial_time_;
};
};


#endif  // METRICS_DAEMON_H_
#endif  // METRICS_DAEMON_H_
+12 −8
Original line number Original line Diff line number Diff line
@@ -580,10 +580,10 @@ TEST_F(MetricsDaemonTest, ReportDiskStats) {
}
}


TEST_F(MetricsDaemonTest, ProcessMeminfo) {
TEST_F(MetricsDaemonTest, ProcessMeminfo) {
  const char* meminfo = "\
  string meminfo = "\
MemTotal:        2000000 kB\n\
MemTotal:        2000000 kB\n\
MemFree:         1000000 kB\n\
MemFree:          500000 kB\n\
Buffers:           10492 kB\n\
Buffers:         1000000 kB\n\
Cached:           213652 kB\n\
Cached:           213652 kB\n\
SwapCached:            0 kB\n\
SwapCached:            0 kB\n\
Active:           133400 kB\n\
Active:           133400 kB\n\
@@ -617,25 +617,29 @@ VmallocChunk: 103824 kB\n\
DirectMap4k:        9636 kB\n\
DirectMap4k:        9636 kB\n\
DirectMap2M:     1955840 kB\n\
DirectMap2M:     1955840 kB\n\
";
";
  // All enum calls must report percents.
  EXPECT_CALL(metrics_lib_, SendEnumToUMA(_, _, 100))
  EXPECT_CALL(metrics_lib_, SendEnumToUMA(_, _, 100))
      .Times(AtLeast(1));
      .Times(AtLeast(1));
  // Check that MemFree is correctly computed at 25%.
  EXPECT_CALL(metrics_lib_, SendEnumToUMA("Platform.MeminfoMemFree", 25, 100))
      .Times(AtLeast(1));
  // Check that we call SendToUma at least once (log histogram).
  EXPECT_CALL(metrics_lib_, SendToUMA(_, _, _, _, _))
  EXPECT_CALL(metrics_lib_, SendToUMA(_, _, _, _, _))
      .Times(AtLeast(1));
      .Times(AtLeast(1));
  EXPECT_CALL(metrics_lib_, SendToUMA("NFS_Unstable", _, _, _, _))
  // Make sure we don't report fields not in the list.
  EXPECT_CALL(metrics_lib_, SendToUMA("Platform.MeminfoMlocked", _, _, _, _))
      .Times(0);
      .Times(0);
  EXPECT_CALL(metrics_lib_, SendEnumToUMA("NFS_Unstable", _, _))
  EXPECT_CALL(metrics_lib_, SendEnumToUMA("Platform.MeminfoMlocked", _, _))
      .Times(0);
      .Times(0);
  EXPECT_TRUE(daemon_.ProcessMeminfo(meminfo));
  EXPECT_TRUE(daemon_.ProcessMeminfo(meminfo));
}
}


TEST_F(MetricsDaemonTest, ProcessMeminfo2) {
TEST_F(MetricsDaemonTest, ProcessMeminfo2) {
  const char* meminfo = "\
  string meminfo = "\
MemTotal:        2000000 kB\n\
MemTotal:        2000000 kB\n\
MemFree:         1000000 kB\n\
MemFree:         1000000 kB\n\
";
";
  /* Not enough fields */
  /* Not enough fields */
  EXPECT_CALL(metrics_lib_, SendEnumToUMA(_, 50, 100))
      .Times(1);
  EXPECT_FALSE(daemon_.ProcessMeminfo(meminfo));
  EXPECT_FALSE(daemon_.ProcessMeminfo(meminfo));
}
}