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
diff --git a/metrics/metrics_daemon.cc b/metrics/metrics_daemon.cc
index a5059d9..1e15e1d 100644
--- a/metrics/metrics_daemon.cc
+++ b/metrics/metrics_daemon.cc
@@ -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::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 @@
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 @@
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 @@
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 @@
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)
diff --git a/metrics/metrics_daemon.h b/metrics/metrics_daemon.h
index 5f2e786..dd61322 100644
--- a/metrics/metrics_daemon.h
+++ b/metrics/metrics_daemon.h
@@ -29,7 +29,8 @@
~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 @@
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 @@
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 @@
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 @@
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 @@
// 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_
diff --git a/metrics/metrics_daemon_main.cc b/metrics/metrics_daemon_main.cc
index f3f0714..899256c 100644
--- a/metrics/metrics_daemon_main.cc
+++ b/metrics/metrics_daemon_main.cc
@@ -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);
}
diff --git a/metrics/metrics_daemon_test.cc b/metrics/metrics_daemon_test.cc
index 530f357..208d8e5 100644
--- a/metrics/metrics_daemon_test.cc
+++ b/metrics/metrics_daemon_test.cc
@@ -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 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 @@
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 @@
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 @@
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 @@
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();