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

Commit c88e42de authored by Luigi Semenzato's avatar Luigi Semenzato
Browse files

Collect some disk statistics.

Change-Id: Id30f4b7e5d121f2632592ebacf47a18ea1d89fec

BUG=chromium-os:12171
TEST=ran on target and observed that stats are generated

Review URL: http://codereview.chromium.org/6486021
parent be2e13b3
Loading
Loading
Loading
Loading
+123 −2
Original line number Diff line number Diff line
@@ -4,6 +4,7 @@

#include "metrics_daemon.h"

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

#include <base/file_util.h>
@@ -84,6 +85,28 @@ const char MetricsDaemon::kMetricCrashFrequencyMin = 1;
const char MetricsDaemon::kMetricCrashFrequencyMax = 100;
const char MetricsDaemon::kMetricCrashFrequencyBuckets = 50;

// disk stats metrics

// The {Read,Write}Sectors numbers are in sectors/second.
// A sector is usually 512 bytes.

const char MetricsDaemon::kMetricReadSectorsLongName[] =
    "Platform.ReadSectorsLong";
const char MetricsDaemon::kMetricWriteSectorsLongName[] =
    "Platform.WriteSectorsLong";
const char MetricsDaemon::kMetricReadSectorsShortName[] =
    "Platform.ReadSectorsShort";
const char MetricsDaemon::kMetricWriteSectorsShortName[] =
    "Platform.WriteSectorsShort";

const int MetricsDaemon::kMetricDiskStatsShortInterval = 1;  // seconds
const int MetricsDaemon::kMetricDiskStatsLongInterval = 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
const int MetricsDaemon::kMetricSectorsBuckets = 50;    // buckets

// persistent metrics path
const char MetricsDaemon::kMetricsPath[] = "/var/log/metrics";

@@ -123,7 +146,8 @@ MetricsDaemon::MetricsDaemon()
      session_state_(kUnknownSessionState),
      user_active_(false),
      usemon_interval_(0),
      usemon_source_(NULL) {}
      usemon_source_(NULL),
      diskstats_path_(NULL) {}

MetricsDaemon::~MetricsDaemon() {
  DeleteFrequencyCounters();
@@ -190,7 +214,8 @@ void MetricsDaemon::ConfigureCrashFrequencyReporter(
  frequency_counters_[histogram_name] = new_counter.release();
}

void MetricsDaemon::Init(bool testing, MetricsLibraryInterface* metrics_lib) {
void MetricsDaemon::Init(bool testing, MetricsLibraryInterface* metrics_lib,
                         const char* diskstats_path) {
  testing_ = testing;
  DCHECK(metrics_lib != NULL);
  metrics_lib_ = metrics_lib;
@@ -218,6 +243,9 @@ void MetricsDaemon::Init(bool testing, MetricsLibraryInterface* metrics_lib) {
  ConfigureCrashFrequencyReporter(kMetricUserCrashesDailyName);
  ConfigureCrashFrequencyReporter(kMetricUserCrashesWeeklyName);

  diskstats_path_ = diskstats_path;
  DiskStatsReporterInit();

  // Don't setup D-Bus and GLib in test mode.
  if (testing)
    return;
@@ -494,6 +522,99 @@ void MetricsDaemon::UnscheduleUseMonitor() {
  usemon_interval_ = 0;
}

void MetricsDaemon::DiskStatsReporterInit() {
  DiskStatsReadStats(&read_sectors_, &write_sectors_);
  // The first time around just run the long stat, so we don't delay boot.
  diskstats_state_ = kDiskStatsLong;
  ScheduleDiskStatsCallback(kMetricDiskStatsLongInterval);
}

void MetricsDaemon::ScheduleDiskStatsCallback(int wait) {
  if (testing_) {
    return;
  }
  g_timeout_add_seconds(wait, DiskStatsCallbackStatic, this);
}

void MetricsDaemon::DiskStatsReadStats(long int* read_sectors,
                                       long int* write_sectors) {
  int nchars;
  int nitems;
  char line[200];
  int file = HANDLE_EINTR(open(diskstats_path_, O_RDONLY));
  if (file < 0) {
    PLOG(WARNING) << "cannot open " << diskstats_path_;
    return;
  }
  nchars = HANDLE_EINTR(read(file, line, sizeof(line)));
  if (nchars < 0) {
    PLOG(WARNING) << "cannot read from " << diskstats_path_;
  } else {
    LOG_IF(WARNING, nchars == sizeof(line)) << "line too long in "
                                            << diskstats_path_;
    line[nchars] = '\0';
    nitems = sscanf(line, "%*d %*d %ld %*d %*d %*d %ld",
                    read_sectors, write_sectors);
    LOG_IF(WARNING, nitems != 2) << "found " << nitems << " items in "
                                 << diskstats_path_ << ", expected 2";
  }
  HANDLE_EINTR(close(file));
}

// static
gboolean MetricsDaemon::DiskStatsCallbackStatic(void* handle) {
  (static_cast<MetricsDaemon*>(handle))->DiskStatsCallback();
  return false;  // one-time callback
}

void MetricsDaemon::DiskStatsCallback() {
  long int read_sectors_now, write_sectors_now;
  DiskStatsReadStats(&read_sectors_now, &write_sectors_now);

  switch (diskstats_state_) {
    case kDiskStatsShort:
      SendMetric(kMetricReadSectorsShortName,
                 (int) (read_sectors_now - read_sectors_) /
                 kMetricDiskStatsShortInterval,
                 1,
                 kMetricSectorsIOMax,
                 kMetricSectorsBuckets);
      SendMetric(kMetricWriteSectorsShortName,
                 (int) (write_sectors_now - write_sectors_) /
                 kMetricDiskStatsShortInterval,
                 1,
                 kMetricSectorsIOMax,
                 kMetricSectorsBuckets);
      // Schedule long callback.
      diskstats_state_ = kDiskStatsLong;
      ScheduleDiskStatsCallback(kMetricDiskStatsLongInterval -
                                kMetricDiskStatsShortInterval);
      break;
    case kDiskStatsLong:
      SendMetric(kMetricReadSectorsLongName,
                 (int) (read_sectors_now - read_sectors_) /
                 kMetricDiskStatsLongInterval,
                 1,
                 kMetricSectorsIOMax,
                 kMetricSectorsBuckets);
      SendMetric(kMetricWriteSectorsLongName,
                 (int) (write_sectors_now - write_sectors_) /
                 kMetricDiskStatsLongInterval,
                 1,
                 kMetricSectorsIOMax,
                 kMetricSectorsBuckets);
      // Reset sector counters
      read_sectors_ = read_sectors_now;
      write_sectors_ = write_sectors_now;
      // Schedule short callback.
      diskstats_state_ = kDiskStatsShort;
      ScheduleDiskStatsCallback(kMetricDiskStatsShortInterval);
      break;
    default:
      LOG(FATAL) << "Invalid disk stats state";
  }
}

// static
void MetricsDaemon::ReportDailyUse(void* handle, int tag, int count) {
  if (count <= 0)
+41 −1
Original line number Diff line number Diff line
@@ -29,7 +29,8 @@ class MetricsDaemon {
  ~MetricsDaemon();

  // Initializes.
  void Init(bool testing, MetricsLibraryInterface* metrics_lib);
  void Init(bool testing, MetricsLibraryInterface* metrics_lib,
            const char* diskstats_path);

  // Does all the work. If |run_as_daemon| is true, daemonizes by
  // forking.
@@ -52,6 +53,7 @@ class MetricsDaemon {
  FRIEND_TEST(MetricsDaemonTest, ProcessUserCrash);
  FRIEND_TEST(MetricsDaemonTest, ReportCrashesDailyFrequency);
  FRIEND_TEST(MetricsDaemonTest, ReportDailyUse);
  FRIEND_TEST(MetricsDaemonTest, ReportDiskStats);
  FRIEND_TEST(MetricsDaemonTest, ReportKernelCrashInterval);
  FRIEND_TEST(MetricsDaemonTest, ReportUncleanShutdownInterval);
  FRIEND_TEST(MetricsDaemonTest, ReportUserCrashInterval);
@@ -77,6 +79,12 @@ class MetricsDaemon {
    kNumberSessionStates
  };

  // State for disk stats collector callback.
  enum DiskStatsState {
    kDiskStatsShort,    // short wait before short interval collection
    kDiskStatsLong,     // final wait before new collection
  };

  // Data record for aggregating daily usage.
  class UseRecord {
   public:
@@ -111,6 +119,15 @@ class MetricsDaemon {
  static const char kMetricUserCrashesDailyName[];
  static const char kMetricUserCrashesWeeklyName[];
  static const char kMetricUserCrashIntervalName[];
  static const char kMetricReadSectorsLongName[];
  static const char kMetricReadSectorsShortName[];
  static const char kMetricWriteSectorsLongName[];
  static const char kMetricWriteSectorsShortName[];
  static const int kMetricDiskStatsShortInterval;
  static const int kMetricDiskStatsLongInterval;
  static const int kMetricSectorsIOMax;
  static const int kMetricSectorsBuckets;
  static const char kMetricsDiskStatsPath[];

  // D-Bus message match strings.
  static const char* kDBusMatches_[];
@@ -217,6 +234,22 @@ class MetricsDaemon {
  void SendMetric(const std::string& name, int sample,
                  int min, int max, int nbuckets);

  // Initializes disk stats reporting.
  void DiskStatsReporterInit();

  // Schedules a callback for the next disk stats collection.
  void ScheduleDiskStatsCallback(int wait);

  // Reads cumulative disk statistics from sysfs.
  void DiskStatsReadStats(long int* read_sectors, long int* write_sectors);

  // Reports disk statistics (static version for glib).  Arguments are a glib
  // artifact.
  static gboolean DiskStatsCallbackStatic(void* handle);

  // Reports disk statistics.
  void DiskStatsCallback();

  // Test mode.
  bool testing_;

@@ -265,6 +298,13 @@ class MetricsDaemon {

  // Scheduled daily use monitor source (see ScheduleUseMonitor).
  GSource* usemon_source_;

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

  DiskStatsState diskstats_state_;
  const char* diskstats_path_;
};

#endif  // METRICS_DAEMON_H_
+4 −1
Original line number Diff line number Diff line
@@ -9,11 +9,14 @@

DEFINE_bool(daemon, true, "run as daemon (use -nodaemon for debugging)");

// Path to disk stats.  This may be system dependent.
const char kMetricsMainDiskStatsPath[] = "/sys/class/block/sda/stat";

int main(int argc, char** argv) {
  google::ParseCommandLineFlags(&argc, &argv, true);
  MetricsLibrary metrics_lib;
  metrics_lib.Init();
  MetricsDaemon daemon;
  daemon.Init(false, &metrics_lib);
  daemon.Init(false, &metrics_lib, kMetricsMainDiskStatsPath);
  daemon.Run(FLAGS_daemon);
}
+48 −3
Original line number Diff line number Diff line
@@ -8,6 +8,7 @@
#include <vector>

#include <base/file_util.h>
#include <base/stringprintf.h>
#include <gtest/gtest.h>

#include "counter_mock.h"
@@ -32,6 +33,14 @@ static const int kSecondsPerDay = 24 * 60 * 60;
static const char kTestDir[] = "test";
static const char kLastFile[] = "test/last";
static const char kCurrentFile[] = "test/current";
static const char kFakeDiskStatsPath[] = "fake-disk-stats";
static const char kFakeDiskStatsFormat[] =
    "    1793     1788    %d   105580    "
    "    196      175     %d    30290    "
    "    0    44060   135850\n";
static string kFakeDiskStats[2];
static const int kFakeReadSectors[] = {80000, 100000};
static const int kFakeWriteSectors[] = {3000, 4000};

// This class allows a TimeTicks object to be initialized with seconds
// (rather than microseconds) through the protected TimeTicks(int64)
@@ -54,7 +63,12 @@ class MetricsDaemonTest : public testing::Test {
    EXPECT_EQ(NULL, daemon_.daily_use_.get());
    EXPECT_EQ(NULL, daemon_.kernel_crash_interval_.get());
    EXPECT_EQ(NULL, daemon_.user_crash_interval_.get());
    daemon_.Init(true, &metrics_lib_);
    kFakeDiskStats[0] = StringPrintf(kFakeDiskStatsFormat,
                                     kFakeReadSectors[0], kFakeWriteSectors[0]);
    kFakeDiskStats[1] = StringPrintf(kFakeDiskStatsFormat,
                                     kFakeReadSectors[1], kFakeWriteSectors[1]);
    CreateFakeDiskStatsFile(kFakeDiskStats[0].c_str());
    daemon_.Init(true, &metrics_lib_, kFakeDiskStatsPath);

    // Check configuration of a few histograms.
    FrequencyCounter* frequency_counter =
@@ -120,7 +134,9 @@ class MetricsDaemonTest : public testing::Test {
    file_util::CreateDirectory(FilePath(kTestDir));
  }

  virtual void TearDown() {}
  virtual void TearDown() {
    EXPECT_EQ(unlink(kFakeDiskStatsPath), 0);
  }

  const TaggedCounterReporter*
  GetReporter(FrequencyCounter* frequency_counter) const {
@@ -222,12 +238,22 @@ class MetricsDaemonTest : public testing::Test {
    dbus_message_unref(msg);
  }

  // Get the frequency counter for the given name.
  // Gets the frequency counter for the given name.
  FrequencyCounterMock& GetFrequencyMock(const char* histogram_name) {
    return *static_cast<FrequencyCounterMock*>(
        daemon_.frequency_counters_[histogram_name]);
  }

  // Creates or overwrites an input file containing fake disk stats.
  void CreateFakeDiskStatsFile(const char* fake_stats) {
    if (unlink(kFakeDiskStatsPath) < 0) {
      EXPECT_EQ(errno, ENOENT);
    }
    FILE* f = fopen(kFakeDiskStatsPath, "w");
    EXPECT_EQ(1, fwrite(fake_stats, strlen(fake_stats), 1, f));
    EXPECT_EQ(0, fclose(f));
  }

  // The MetricsDaemon under test.
  MetricsDaemon daemon_;

@@ -533,6 +559,25 @@ TEST_F(MetricsDaemonTest, GetHistogramPath) {
                MetricsDaemon::kMetricAnyCrashesDailyName).value());
}

TEST_F(MetricsDaemonTest, ReportDiskStats) {
  long int read_sectors_now, write_sectors_now;

  CreateFakeDiskStatsFile(kFakeDiskStats[1].c_str());
  daemon_.DiskStatsReadStats(&read_sectors_now, &write_sectors_now);
  EXPECT_EQ(read_sectors_now, kFakeReadSectors[1]);
  EXPECT_EQ(write_sectors_now, kFakeWriteSectors[1]);

  MetricsDaemon::DiskStatsState ds_state = daemon_.diskstats_state_;
  EXPECT_CALL(metrics_lib_,
              SendToUMA(_, (kFakeReadSectors[1] - kFakeReadSectors[0]) / 30,
                        _, _, _));
  EXPECT_CALL(metrics_lib_,
              SendToUMA(_, (kFakeWriteSectors[1] - kFakeWriteSectors[0]) / 30,
                        _, _, _));
  daemon_.DiskStatsCallback();
  EXPECT_TRUE(ds_state != daemon_.diskstats_state_);
}

int main(int argc, char** argv) {
  testing::InitGoogleTest(&argc, argv);
  return RUN_ALL_TESTS();