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
This commit is contained in:
Luigi Semenzato 2011-02-17 10:21:16 -08:00
parent be2e13b32b
commit c88e42dea9
4 changed files with 216 additions and 7 deletions

View file

@ -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)

View file

@ -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_

View file

@ -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);
}

View file

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