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:
parent
be2e13b32b
commit
c88e42dea9
4 changed files with 216 additions and 7 deletions
|
@ -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)
|
||||
|
|
|
@ -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_
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
Loading…
Reference in a new issue