blob: 273fa60a64ee86728522fd2b83dd631225e203f6 [file] [log] [blame]
Darin Petkov65b01462010-04-14 13:32:20 -07001// Copyright (c) 2010 The Chromium OS Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "metrics_daemon.h"
Darin Petkov65b01462010-04-14 13:32:20 -07006
Luigi Semenzatoc88e42d2011-02-17 10:21:16 -08007#include <fcntl.h>
Ken Mixter4c5daa42010-08-26 18:35:06 -07008#include <string.h>
Darin Petkov65b01462010-04-14 13:32:20 -07009
Darin Petkov38d5cb02010-06-24 12:10:26 -070010#include <base/file_util.h>
Darin Petkov65b01462010-04-14 13:32:20 -070011#include <base/logging.h>
Luigi Semenzato29c7ef92011-04-12 14:12:35 -070012#include <base/string_util.h>
Ken Mixter4c5daa42010-08-26 18:35:06 -070013#include <dbus/dbus-glib-lowlevel.h>
Darin Petkov65b01462010-04-14 13:32:20 -070014
Darin Petkovf1e85e42010-06-10 15:59:53 -070015#include "counter.h"
16
Darin Petkovf27f0362010-06-04 13:14:19 -070017using base::Time;
18using base::TimeDelta;
19using base::TimeTicks;
Darin Petkov38d5cb02010-06-24 12:10:26 -070020using std::string;
Darin Petkovf27f0362010-06-04 13:14:19 -070021
Darin Petkov703ec972010-04-27 11:02:18 -070022#define SAFE_MESSAGE(e) (e.message ? e.message : "unknown error")
Darin Petkov1bb904e2010-06-16 15:58:06 -070023#define DBUS_IFACE_CRASH_REPORTER "org.chromium.CrashReporter"
David James6bf6e252010-06-06 18:52:40 -070024#define DBUS_IFACE_POWER_MANAGER "org.chromium.PowerManager"
Darin Petkov41e06232010-05-03 16:45:37 -070025#define DBUS_IFACE_SESSION_MANAGER "org.chromium.SessionManagerInterface"
26
Darin Petkov41e06232010-05-03 16:45:37 -070027static const int kSecondsPerMinute = 60;
28static const int kMinutesPerHour = 60;
29static const int kHoursPerDay = 24;
30static const int kMinutesPerDay = kHoursPerDay * kMinutesPerHour;
Darin Petkov1bb904e2010-06-16 15:58:06 -070031static const int kSecondsPerDay = kSecondsPerMinute * kMinutesPerDay;
32static const int kDaysPerWeek = 7;
33static const int kSecondsPerWeek = kSecondsPerDay * kDaysPerWeek;
Darin Petkov41e06232010-05-03 16:45:37 -070034
35// The daily use monitor is scheduled to a 1-minute interval after
36// initial user activity and then it's exponentially backed off to
37// 10-minute intervals. Although not required, the back off is
38// implemented because the histogram buckets are spaced exponentially
39// anyway and to avoid too frequent metrics daemon process wake-ups
40// and file I/O.
41static const int kUseMonitorIntervalInit = 1 * kSecondsPerMinute;
42static const int kUseMonitorIntervalMax = 10 * kSecondsPerMinute;
Darin Petkov65b01462010-04-14 13:32:20 -070043
Ken Mixterccd84c02010-08-16 19:57:13 -070044const char kKernelCrashDetectedFile[] = "/tmp/kernel-crash-detected";
45static const char kUncleanShutdownDetectedFile[] =
46 "/tmp/unclean-shutdown-detected";
47
Ken Mixter4c5daa42010-08-26 18:35:06 -070048// static metrics parameters
Darin Petkov2ccef012010-05-05 16:06:37 -070049const char MetricsDaemon::kMetricDailyUseTimeName[] =
50 "Logging.DailyUseTime";
51const int MetricsDaemon::kMetricDailyUseTimeMin = 1;
52const int MetricsDaemon::kMetricDailyUseTimeMax = kMinutesPerDay;
53const int MetricsDaemon::kMetricDailyUseTimeBuckets = 50;
54
Ken Mixterccd84c02010-08-16 19:57:13 -070055// crash interval metrics
56const char MetricsDaemon::kMetricKernelCrashIntervalName[] =
57 "Logging.KernelCrashInterval";
58const char MetricsDaemon::kMetricUncleanShutdownIntervalName[] =
59 "Logging.UncleanShutdownInterval";
Darin Petkov1bb904e2010-06-16 15:58:06 -070060const char MetricsDaemon::kMetricUserCrashIntervalName[] =
61 "Logging.UserCrashInterval";
Ken Mixterccd84c02010-08-16 19:57:13 -070062
63const int MetricsDaemon::kMetricCrashIntervalMin = 1;
64const int MetricsDaemon::kMetricCrashIntervalMax =
65 4 * kSecondsPerWeek;
66const int MetricsDaemon::kMetricCrashIntervalBuckets = 50;
67
68// crash frequency metrics
69const char MetricsDaemon::kMetricAnyCrashesDailyName[] =
70 "Logging.AnyCrashesDaily";
Ken Mixter4c5daa42010-08-26 18:35:06 -070071const char MetricsDaemon::kMetricAnyCrashesWeeklyName[] =
72 "Logging.AnyCrashesWeekly";
Ken Mixterccd84c02010-08-16 19:57:13 -070073const char MetricsDaemon::kMetricKernelCrashesDailyName[] =
74 "Logging.KernelCrashesDaily";
Ken Mixter4c5daa42010-08-26 18:35:06 -070075const char MetricsDaemon::kMetricKernelCrashesWeeklyName[] =
76 "Logging.KernelCrashesWeekly";
Ken Mixterccd84c02010-08-16 19:57:13 -070077const char MetricsDaemon::kMetricUncleanShutdownsDailyName[] =
78 "Logging.UncleanShutdownsDaily";
Ken Mixter4c5daa42010-08-26 18:35:06 -070079const char MetricsDaemon::kMetricUncleanShutdownsWeeklyName[] =
80 "Logging.UncleanShutdownsWeekly";
Ken Mixterccd84c02010-08-16 19:57:13 -070081const char MetricsDaemon::kMetricUserCrashesDailyName[] =
82 "Logging.UserCrashesDaily";
Ken Mixter4c5daa42010-08-26 18:35:06 -070083const char MetricsDaemon::kMetricUserCrashesWeeklyName[] =
84 "Logging.UserCrashesWeekly";
85const char MetricsDaemon::kMetricCrashFrequencyMin = 1;
86const char MetricsDaemon::kMetricCrashFrequencyMax = 100;
87const char MetricsDaemon::kMetricCrashFrequencyBuckets = 50;
Ken Mixterccd84c02010-08-16 19:57:13 -070088
Luigi Semenzatoc88e42d2011-02-17 10:21:16 -080089// disk stats metrics
90
91// The {Read,Write}Sectors numbers are in sectors/second.
92// A sector is usually 512 bytes.
93
94const char MetricsDaemon::kMetricReadSectorsLongName[] =
95 "Platform.ReadSectorsLong";
96const char MetricsDaemon::kMetricWriteSectorsLongName[] =
97 "Platform.WriteSectorsLong";
98const char MetricsDaemon::kMetricReadSectorsShortName[] =
99 "Platform.ReadSectorsShort";
100const char MetricsDaemon::kMetricWriteSectorsShortName[] =
101 "Platform.WriteSectorsShort";
102
103const int MetricsDaemon::kMetricDiskStatsShortInterval = 1; // seconds
104const int MetricsDaemon::kMetricDiskStatsLongInterval = 30; // seconds
105
Luigi Semenzato29c7ef92011-04-12 14:12:35 -0700106const int MetricsDaemon::kMetricMeminfoInterval = 30; // seconds
107
Luigi Semenzatoc88e42d2011-02-17 10:21:16 -0800108// Assume a max rate of 250Mb/s for reads (worse for writes) and 512 byte
109// sectors.
110const int MetricsDaemon::kMetricSectorsIOMax = 500000; // sectors/second
111const int MetricsDaemon::kMetricSectorsBuckets = 50; // buckets
112
Ken Mixter4c5daa42010-08-26 18:35:06 -0700113// persistent metrics path
114const char MetricsDaemon::kMetricsPath[] = "/var/log/metrics";
Ken Mixterccd84c02010-08-16 19:57:13 -0700115
Darin Petkov1bb904e2010-06-16 15:58:06 -0700116
Darin Petkov703ec972010-04-27 11:02:18 -0700117// static
Darin Petkov41e06232010-05-03 16:45:37 -0700118const char* MetricsDaemon::kDBusMatches_[] = {
Darin Petkov703ec972010-04-27 11:02:18 -0700119 "type='signal',"
Darin Petkov1bb904e2010-06-16 15:58:06 -0700120 "interface='" DBUS_IFACE_CRASH_REPORTER "',"
121 "path='/',"
122 "member='UserCrash'",
123
124 "type='signal',"
Darin Petkov703ec972010-04-27 11:02:18 -0700125 "interface='" DBUS_IFACE_POWER_MANAGER "',"
Benson Leung53faeb02010-06-08 15:59:13 -0700126 "path='/'",
Darin Petkov41e06232010-05-03 16:45:37 -0700127
128 "type='signal',"
129 "sender='org.chromium.SessionManager',"
130 "interface='" DBUS_IFACE_SESSION_MANAGER "',"
131 "path='/org/chromium/SessionManager',"
132 "member='SessionStateChanged'",
Darin Petkov703ec972010-04-27 11:02:18 -0700133};
134
135// static
Darin Petkov41e06232010-05-03 16:45:37 -0700136const char* MetricsDaemon::kPowerStates_[] = {
Darin Petkov703ec972010-04-27 11:02:18 -0700137#define STATE(name, capname) #name,
138#include "power_states.h"
139};
140
Darin Petkov41e06232010-05-03 16:45:37 -0700141// static
Darin Petkov41e06232010-05-03 16:45:37 -0700142const char* MetricsDaemon::kSessionStates_[] = {
143#define STATE(name, capname) #name,
144#include "session_states.h"
145};
146
Darin Petkovf1e85e42010-06-10 15:59:53 -0700147MetricsDaemon::MetricsDaemon()
Sam Leffler239b8262010-08-30 08:56:58 -0700148 : power_state_(kUnknownPowerState),
Darin Petkovf1e85e42010-06-10 15:59:53 -0700149 session_state_(kUnknownSessionState),
150 user_active_(false),
151 usemon_interval_(0),
Luigi Semenzato0f132bb2011-02-28 11:17:43 -0800152 usemon_source_(NULL) {}
Darin Petkovf1e85e42010-06-10 15:59:53 -0700153
Ken Mixter4c5daa42010-08-26 18:35:06 -0700154MetricsDaemon::~MetricsDaemon() {
155 DeleteFrequencyCounters();
156}
157
158void MetricsDaemon::DeleteFrequencyCounters() {
159 for (FrequencyCounters::iterator i = frequency_counters_.begin();
160 i != frequency_counters_.end(); ++i) {
161 delete i->second;
162 i->second = NULL;
163 }
164}
Darin Petkovf1e85e42010-06-10 15:59:53 -0700165
Darin Petkov2ccef012010-05-05 16:06:37 -0700166void MetricsDaemon::Run(bool run_as_daemon) {
Darin Petkov38d5cb02010-06-24 12:10:26 -0700167 if (run_as_daemon && daemon(0, 0) != 0)
168 return;
169
Ken Mixterccd84c02010-08-16 19:57:13 -0700170 if (CheckSystemCrash(kKernelCrashDetectedFile)) {
171 ProcessKernelCrash();
172 }
173
174 if (CheckSystemCrash(kUncleanShutdownDetectedFile)) {
175 ProcessUncleanShutdown();
176 }
177
Darin Petkov38d5cb02010-06-24 12:10:26 -0700178 Loop();
Darin Petkov65b01462010-04-14 13:32:20 -0700179}
180
Ken Mixter4c5daa42010-08-26 18:35:06 -0700181FilePath MetricsDaemon::GetHistogramPath(const char* histogram_name) {
182 return FilePath(kMetricsPath).Append(histogram_name);
183}
184
185void MetricsDaemon::ConfigureCrashIntervalReporter(
186 const char* histogram_name,
187 scoped_ptr<chromeos_metrics::TaggedCounterReporter>* reporter) {
188 reporter->reset(new chromeos_metrics::TaggedCounterReporter());
189 FilePath file_path = GetHistogramPath(histogram_name);
190 (*reporter)->Init(file_path.value().c_str(),
191 histogram_name,
192 kMetricCrashIntervalMin,
193 kMetricCrashIntervalMax,
194 kMetricCrashIntervalBuckets);
195}
196
197void MetricsDaemon::ConfigureCrashFrequencyReporter(
198 const char* histogram_name) {
199 scoped_ptr<chromeos_metrics::TaggedCounterReporter> reporter(
200 new chromeos_metrics::TaggedCounterReporter());
201 FilePath file_path = GetHistogramPath(histogram_name);
202 reporter->Init(file_path.value().c_str(),
203 histogram_name,
204 kMetricCrashFrequencyMin,
205 kMetricCrashFrequencyMax,
206 kMetricCrashFrequencyBuckets);
207 scoped_ptr<chromeos_metrics::FrequencyCounter> new_counter(
208 new chromeos_metrics::FrequencyCounter());
209 time_t cycle_duration = strstr(histogram_name, "Weekly") != NULL ?
210 chromeos_metrics::kSecondsPerWeek :
211 chromeos_metrics::kSecondsPerDay;
212 new_counter->Init(
213 static_cast<chromeos_metrics::TaggedCounterInterface*>(
214 reporter.release()),
215 cycle_duration);
216 frequency_counters_[histogram_name] = new_counter.release();
217}
218
Luigi Semenzatoc88e42d2011-02-17 10:21:16 -0800219void MetricsDaemon::Init(bool testing, MetricsLibraryInterface* metrics_lib,
Luigi Semenzato0f132bb2011-02-28 11:17:43 -0800220 string diskstats_path) {
Darin Petkov65b01462010-04-14 13:32:20 -0700221 testing_ = testing;
Darin Petkovfc91b422010-05-12 13:05:45 -0700222 DCHECK(metrics_lib != NULL);
223 metrics_lib_ = metrics_lib;
Ken Mixter4c5daa42010-08-26 18:35:06 -0700224 chromeos_metrics::TaggedCounterReporter::
225 SetMetricsLibraryInterface(metrics_lib);
Darin Petkov38d5cb02010-06-24 12:10:26 -0700226
227 static const char kDailyUseRecordFile[] = "/var/log/metrics/daily-usage";
Darin Petkovf1e85e42010-06-10 15:59:53 -0700228 daily_use_.reset(new chromeos_metrics::TaggedCounter());
Ken Mixterccd84c02010-08-16 19:57:13 -0700229 daily_use_->Init(kDailyUseRecordFile, &ReportDailyUse, this);
Darin Petkov38d5cb02010-06-24 12:10:26 -0700230
Ken Mixter4c5daa42010-08-26 18:35:06 -0700231 ConfigureCrashIntervalReporter(kMetricKernelCrashIntervalName,
232 &kernel_crash_interval_);
233 ConfigureCrashIntervalReporter(kMetricUncleanShutdownIntervalName,
234 &unclean_shutdown_interval_);
235 ConfigureCrashIntervalReporter(kMetricUserCrashIntervalName,
236 &user_crash_interval_);
Darin Petkov2ccef012010-05-05 16:06:37 -0700237
Ken Mixter4c5daa42010-08-26 18:35:06 -0700238 DeleteFrequencyCounters();
239 ConfigureCrashFrequencyReporter(kMetricAnyCrashesDailyName);
Ken Mixter4c5daa42010-08-26 18:35:06 -0700240 ConfigureCrashFrequencyReporter(kMetricAnyCrashesWeeklyName);
241 ConfigureCrashFrequencyReporter(kMetricKernelCrashesDailyName);
242 ConfigureCrashFrequencyReporter(kMetricKernelCrashesWeeklyName);
243 ConfigureCrashFrequencyReporter(kMetricUncleanShutdownsDailyName);
244 ConfigureCrashFrequencyReporter(kMetricUncleanShutdownsWeeklyName);
245 ConfigureCrashFrequencyReporter(kMetricUserCrashesDailyName);
246 ConfigureCrashFrequencyReporter(kMetricUserCrashesWeeklyName);
Darin Petkov38d5cb02010-06-24 12:10:26 -0700247
Luigi Semenzato0f132bb2011-02-28 11:17:43 -0800248 // Don't attempt to collect disk stats if there is no disk stats file.
249 if (!diskstats_path.empty()) {
250 diskstats_path_ = diskstats_path;
251 DiskStatsReporterInit();
252 }
Luigi Semenzatoc88e42d2011-02-17 10:21:16 -0800253
Luigi Semenzato29c7ef92011-04-12 14:12:35 -0700254 // Start collecting meminfo stats.
255 ScheduleMeminfoCallback(kMetricMeminfoInterval);
256
Darin Petkov2ccef012010-05-05 16:06:37 -0700257 // Don't setup D-Bus and GLib in test mode.
258 if (testing)
259 return;
Darin Petkov65b01462010-04-14 13:32:20 -0700260
Darin Petkov703ec972010-04-27 11:02:18 -0700261 g_thread_init(NULL);
262 g_type_init();
263 dbus_g_thread_init();
Darin Petkov65b01462010-04-14 13:32:20 -0700264
Darin Petkov703ec972010-04-27 11:02:18 -0700265 DBusError error;
266 dbus_error_init(&error);
Darin Petkov65b01462010-04-14 13:32:20 -0700267
David James3b3add52010-06-04 15:01:19 -0700268 DBusConnection* connection = dbus_bus_get(DBUS_BUS_SYSTEM, &error);
Darin Petkov703ec972010-04-27 11:02:18 -0700269 LOG_IF(FATAL, dbus_error_is_set(&error)) <<
270 "No D-Bus connection: " << SAFE_MESSAGE(error);
Darin Petkov65b01462010-04-14 13:32:20 -0700271
Darin Petkov703ec972010-04-27 11:02:18 -0700272 dbus_connection_setup_with_g_main(connection, NULL);
Darin Petkov65b01462010-04-14 13:32:20 -0700273
Darin Petkov703ec972010-04-27 11:02:18 -0700274 // Registers D-Bus matches for the signals we would like to catch.
Darin Petkov1bb904e2010-06-16 15:58:06 -0700275 for (unsigned int m = 0; m < arraysize(kDBusMatches_); m++) {
Darin Petkov41e06232010-05-03 16:45:37 -0700276 const char* match = kDBusMatches_[m];
277 DLOG(INFO) << "adding dbus match: " << match;
Darin Petkov703ec972010-04-27 11:02:18 -0700278 dbus_bus_add_match(connection, match, &error);
279 LOG_IF(FATAL, dbus_error_is_set(&error)) <<
280 "unable to add a match: " << SAFE_MESSAGE(error);
281 }
282
283 // Adds the D-Bus filter routine to be called back whenever one of
284 // the registered D-Bus matches is successful. The daemon is not
285 // activated for D-Bus messages that don't match.
286 CHECK(dbus_connection_add_filter(connection, MessageFilter, this, NULL));
Darin Petkov65b01462010-04-14 13:32:20 -0700287}
288
289void MetricsDaemon::Loop() {
Darin Petkov703ec972010-04-27 11:02:18 -0700290 GMainLoop* loop = g_main_loop_new(NULL, false);
291 g_main_loop_run(loop);
Darin Petkov65b01462010-04-14 13:32:20 -0700292}
293
Darin Petkov703ec972010-04-27 11:02:18 -0700294// static
295DBusHandlerResult MetricsDaemon::MessageFilter(DBusConnection* connection,
296 DBusMessage* message,
297 void* user_data) {
Darin Petkovf27f0362010-06-04 13:14:19 -0700298 Time now = Time::Now();
299 TimeTicks ticks = TimeTicks::Now();
300 DLOG(INFO) << "message intercepted @ " << now.ToInternalValue();
Darin Petkov703ec972010-04-27 11:02:18 -0700301
302 int message_type = dbus_message_get_type(message);
303 if (message_type != DBUS_MESSAGE_TYPE_SIGNAL) {
Darin Petkov41e06232010-05-03 16:45:37 -0700304 DLOG(WARNING) << "unexpected message type " << message_type;
Darin Petkov703ec972010-04-27 11:02:18 -0700305 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
306 }
307
308 // Signal messages always have interfaces.
309 const char* interface = dbus_message_get_interface(message);
310 CHECK(interface != NULL);
311
312 MetricsDaemon* daemon = static_cast<MetricsDaemon*>(user_data);
313
314 DBusMessageIter iter;
315 dbus_message_iter_init(message, &iter);
Darin Petkov1bb904e2010-06-16 15:58:06 -0700316 if (strcmp(interface, DBUS_IFACE_CRASH_REPORTER) == 0) {
317 CHECK(strcmp(dbus_message_get_member(message),
318 "UserCrash") == 0);
319 daemon->ProcessUserCrash();
Darin Petkov703ec972010-04-27 11:02:18 -0700320 } else if (strcmp(interface, DBUS_IFACE_POWER_MANAGER) == 0) {
David James6bf6e252010-06-06 18:52:40 -0700321 const char* member = dbus_message_get_member(message);
322 if (strcmp(member, "ScreenIsLocked") == 0) {
323 daemon->SetUserActiveState(false, now);
324 } else if (strcmp(member, "ScreenIsUnlocked") == 0) {
325 daemon->SetUserActiveState(true, now);
326 } else if (strcmp(member, "PowerStateChanged") == 0) {
327 char* state_name;
328 dbus_message_iter_get_basic(&iter, &state_name);
329 daemon->PowerStateChanged(state_name, now);
330 }
Darin Petkov41e06232010-05-03 16:45:37 -0700331 } else if (strcmp(interface, DBUS_IFACE_SESSION_MANAGER) == 0) {
332 CHECK(strcmp(dbus_message_get_member(message),
333 "SessionStateChanged") == 0);
334
David James3b3add52010-06-04 15:01:19 -0700335 char* state_name;
Darin Petkov41e06232010-05-03 16:45:37 -0700336 dbus_message_iter_get_basic(&iter, &state_name);
337 daemon->SessionStateChanged(state_name, now);
Darin Petkov703ec972010-04-27 11:02:18 -0700338 } else {
Darin Petkov41e06232010-05-03 16:45:37 -0700339 DLOG(WARNING) << "unexpected interface: " << interface;
Darin Petkov703ec972010-04-27 11:02:18 -0700340 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
341 }
342
343 return DBUS_HANDLER_RESULT_HANDLED;
Darin Petkov65b01462010-04-14 13:32:20 -0700344}
345
Darin Petkovf27f0362010-06-04 13:14:19 -0700346void MetricsDaemon::PowerStateChanged(const char* state_name, Time now) {
Darin Petkov41e06232010-05-03 16:45:37 -0700347 DLOG(INFO) << "power state: " << state_name;
Darin Petkov703ec972010-04-27 11:02:18 -0700348 power_state_ = LookupPowerState(state_name);
Darin Petkov41e06232010-05-03 16:45:37 -0700349
350 if (power_state_ != kPowerStateOn)
351 SetUserActiveState(false, now);
Darin Petkov703ec972010-04-27 11:02:18 -0700352}
353
354MetricsDaemon::PowerState
355MetricsDaemon::LookupPowerState(const char* state_name) {
356 for (int i = 0; i < kNumberPowerStates; i++) {
Darin Petkov41e06232010-05-03 16:45:37 -0700357 if (strcmp(state_name, kPowerStates_[i]) == 0) {
Darin Petkov703ec972010-04-27 11:02:18 -0700358 return static_cast<PowerState>(i);
359 }
360 }
Darin Petkov41e06232010-05-03 16:45:37 -0700361 DLOG(WARNING) << "unknown power state: " << state_name;
Darin Petkov703ec972010-04-27 11:02:18 -0700362 return kUnknownPowerState;
Darin Petkov65b01462010-04-14 13:32:20 -0700363}
364
Darin Petkovf27f0362010-06-04 13:14:19 -0700365void MetricsDaemon::SessionStateChanged(const char* state_name, Time now) {
Darin Petkov41e06232010-05-03 16:45:37 -0700366 DLOG(INFO) << "user session state: " << state_name;
367 session_state_ = LookupSessionState(state_name);
368 SetUserActiveState(session_state_ == kSessionStateStarted, now);
369}
370
371MetricsDaemon::SessionState
372MetricsDaemon::LookupSessionState(const char* state_name) {
373 for (int i = 0; i < kNumberSessionStates; i++) {
374 if (strcmp(state_name, kSessionStates_[i]) == 0) {
375 return static_cast<SessionState>(i);
376 }
377 }
378 DLOG(WARNING) << "unknown user session state: " << state_name;
379 return kUnknownSessionState;
380}
381
Darin Petkovf27f0362010-06-04 13:14:19 -0700382void MetricsDaemon::SetUserActiveState(bool active, Time now) {
Darin Petkov41e06232010-05-03 16:45:37 -0700383 DLOG(INFO) << "user: " << (active ? "active" : "inactive");
384
385 // Calculates the seconds of active use since the last update and
Darin Petkovf27f0362010-06-04 13:14:19 -0700386 // the day since Epoch, and logs the usage data. Guards against the
387 // time jumping back and forth due to the user changing it by
388 // discarding the new use time.
389 int seconds = 0;
390 if (user_active_ && now > user_active_last_) {
391 TimeDelta since_active = now - user_active_last_;
392 if (since_active < TimeDelta::FromSeconds(
393 kUseMonitorIntervalMax + kSecondsPerMinute)) {
394 seconds = static_cast<int>(since_active.InSeconds());
395 }
396 }
397 TimeDelta since_epoch = now - Time();
398 int day = since_epoch.InDays();
Darin Petkovf1e85e42010-06-10 15:59:53 -0700399 daily_use_->Update(day, seconds);
Darin Petkov1bb904e2010-06-16 15:58:06 -0700400 user_crash_interval_->Update(0, seconds);
Darin Petkov38d5cb02010-06-24 12:10:26 -0700401 kernel_crash_interval_->Update(0, seconds);
Darin Petkov41e06232010-05-03 16:45:37 -0700402
Ken Mixter4c5daa42010-08-26 18:35:06 -0700403 // Flush finished cycles of all frequency counters.
404 for (FrequencyCounters::iterator i = frequency_counters_.begin();
405 i != frequency_counters_.end(); ++i) {
406 i->second->FlushFinishedCycles();
407 }
408
Darin Petkov41e06232010-05-03 16:45:37 -0700409 // Schedules a use monitor on inactive->active transitions and
410 // unschedules it on active->inactive transitions.
411 if (!user_active_ && active)
412 ScheduleUseMonitor(kUseMonitorIntervalInit, /* backoff */ false);
413 else if (user_active_ && !active)
414 UnscheduleUseMonitor();
415
416 // Remembers the current active state and the time of the last
417 // activity update.
418 user_active_ = active;
419 user_active_last_ = now;
420}
421
Darin Petkov1bb904e2010-06-16 15:58:06 -0700422void MetricsDaemon::ProcessUserCrash() {
423 // Counts the active use time up to now.
424 SetUserActiveState(user_active_, Time::Now());
425
426 // Reports the active use time since the last crash and resets it.
427 user_crash_interval_->Flush();
Ken Mixterccd84c02010-08-16 19:57:13 -0700428
Ken Mixter4c5daa42010-08-26 18:35:06 -0700429 frequency_counters_[kMetricUserCrashesDailyName]->Update(1);
430 frequency_counters_[kMetricUserCrashesWeeklyName]->Update(1);
431 frequency_counters_[kMetricAnyCrashesDailyName]->Update(1);
432 frequency_counters_[kMetricAnyCrashesWeeklyName]->Update(1);
Darin Petkov1bb904e2010-06-16 15:58:06 -0700433}
434
Darin Petkov38d5cb02010-06-24 12:10:26 -0700435void MetricsDaemon::ProcessKernelCrash() {
436 // Counts the active use time up to now.
437 SetUserActiveState(user_active_, Time::Now());
438
439 // Reports the active use time since the last crash and resets it.
440 kernel_crash_interval_->Flush();
Ken Mixterccd84c02010-08-16 19:57:13 -0700441
Ken Mixter4c5daa42010-08-26 18:35:06 -0700442 frequency_counters_[kMetricKernelCrashesDailyName]->Update(1);
443 frequency_counters_[kMetricKernelCrashesWeeklyName]->Update(1);
444 frequency_counters_[kMetricAnyCrashesDailyName]->Update(1);
445 frequency_counters_[kMetricAnyCrashesWeeklyName]->Update(1);
Darin Petkov38d5cb02010-06-24 12:10:26 -0700446}
447
Ken Mixterccd84c02010-08-16 19:57:13 -0700448void MetricsDaemon::ProcessUncleanShutdown() {
449 // Counts the active use time up to now.
450 SetUserActiveState(user_active_, Time::Now());
451
452 // Reports the active use time since the last crash and resets it.
453 unclean_shutdown_interval_->Flush();
454
Ken Mixter4c5daa42010-08-26 18:35:06 -0700455 frequency_counters_[kMetricUncleanShutdownsDailyName]->Update(1);
456 frequency_counters_[kMetricUncleanShutdownsWeeklyName]->Update(1);
457 frequency_counters_[kMetricAnyCrashesDailyName]->Update(1);
458 frequency_counters_[kMetricAnyCrashesWeeklyName]->Update(1);
Ken Mixterccd84c02010-08-16 19:57:13 -0700459}
460
461bool MetricsDaemon::CheckSystemCrash(const std::string& crash_file) {
Darin Petkov38d5cb02010-06-24 12:10:26 -0700462 FilePath crash_detected(crash_file);
463 if (!file_util::PathExists(crash_detected))
Ken Mixterccd84c02010-08-16 19:57:13 -0700464 return false;
Darin Petkov38d5cb02010-06-24 12:10:26 -0700465
466 // Deletes the crash-detected file so that the daemon doesn't report
467 // another kernel crash in case it's restarted.
468 file_util::Delete(crash_detected,
469 false); // recursive
Ken Mixterccd84c02010-08-16 19:57:13 -0700470 return true;
Darin Petkov38d5cb02010-06-24 12:10:26 -0700471}
472
Darin Petkov41e06232010-05-03 16:45:37 -0700473// static
474gboolean MetricsDaemon::UseMonitorStatic(gpointer data) {
475 return static_cast<MetricsDaemon*>(data)->UseMonitor() ? TRUE : FALSE;
476}
477
478bool MetricsDaemon::UseMonitor() {
Darin Petkovf27f0362010-06-04 13:14:19 -0700479 SetUserActiveState(user_active_, Time::Now());
Darin Petkov41e06232010-05-03 16:45:37 -0700480
481 // If a new monitor source/instance is scheduled, returns false to
482 // tell GLib to destroy this monitor source/instance. Returns true
483 // otherwise to keep calling back this monitor.
484 return !ScheduleUseMonitor(usemon_interval_ * 2, /* backoff */ true);
485}
486
487bool MetricsDaemon::ScheduleUseMonitor(int interval, bool backoff)
488{
Darin Petkov2ccef012010-05-05 16:06:37 -0700489 if (testing_)
490 return false;
491
Darin Petkov41e06232010-05-03 16:45:37 -0700492 // Caps the interval -- the bigger the interval, the more active use
493 // time will be potentially dropped on system shutdown.
494 if (interval > kUseMonitorIntervalMax)
495 interval = kUseMonitorIntervalMax;
496
497 if (backoff) {
498 // Back-off mode is used by the use monitor to reschedule itself
499 // with exponential back-off in time. This mode doesn't create a
500 // new timeout source if the new interval is the same as the old
501 // one. Also, if a new timeout source is created, the old one is
502 // not destroyed explicitly here -- it will be destroyed by GLib
503 // when the monitor returns FALSE (see UseMonitor and
504 // UseMonitorStatic).
505 if (interval == usemon_interval_)
506 return false;
507 } else {
508 UnscheduleUseMonitor();
509 }
510
511 // Schedules a new use monitor for |interval| seconds from now.
512 DLOG(INFO) << "scheduling use monitor in " << interval << " seconds";
513 usemon_source_ = g_timeout_source_new_seconds(interval);
514 g_source_set_callback(usemon_source_, UseMonitorStatic, this,
515 NULL); // No destroy notification.
516 g_source_attach(usemon_source_,
517 NULL); // Default context.
518 usemon_interval_ = interval;
519 return true;
520}
521
522void MetricsDaemon::UnscheduleUseMonitor() {
523 // If there's a use monitor scheduled already, destroys it.
524 if (usemon_source_ == NULL)
525 return;
526
527 DLOG(INFO) << "destroying use monitor";
528 g_source_destroy(usemon_source_);
529 usemon_source_ = NULL;
530 usemon_interval_ = 0;
531}
532
Luigi Semenzatoc88e42d2011-02-17 10:21:16 -0800533void MetricsDaemon::DiskStatsReporterInit() {
534 DiskStatsReadStats(&read_sectors_, &write_sectors_);
535 // The first time around just run the long stat, so we don't delay boot.
536 diskstats_state_ = kDiskStatsLong;
537 ScheduleDiskStatsCallback(kMetricDiskStatsLongInterval);
538}
539
540void MetricsDaemon::ScheduleDiskStatsCallback(int wait) {
541 if (testing_) {
542 return;
543 }
544 g_timeout_add_seconds(wait, DiskStatsCallbackStatic, this);
545}
546
547void MetricsDaemon::DiskStatsReadStats(long int* read_sectors,
548 long int* write_sectors) {
549 int nchars;
550 int nitems;
551 char line[200];
Luigi Semenzato0f132bb2011-02-28 11:17:43 -0800552 int file = HANDLE_EINTR(open(diskstats_path_.c_str(), O_RDONLY));
Luigi Semenzatoc88e42d2011-02-17 10:21:16 -0800553 if (file < 0) {
554 PLOG(WARNING) << "cannot open " << diskstats_path_;
555 return;
556 }
557 nchars = HANDLE_EINTR(read(file, line, sizeof(line)));
558 if (nchars < 0) {
559 PLOG(WARNING) << "cannot read from " << diskstats_path_;
560 } else {
561 LOG_IF(WARNING, nchars == sizeof(line)) << "line too long in "
562 << diskstats_path_;
563 line[nchars] = '\0';
564 nitems = sscanf(line, "%*d %*d %ld %*d %*d %*d %ld",
565 read_sectors, write_sectors);
566 LOG_IF(WARNING, nitems != 2) << "found " << nitems << " items in "
567 << diskstats_path_ << ", expected 2";
568 }
569 HANDLE_EINTR(close(file));
570}
571
572// static
573gboolean MetricsDaemon::DiskStatsCallbackStatic(void* handle) {
574 (static_cast<MetricsDaemon*>(handle))->DiskStatsCallback();
575 return false; // one-time callback
576}
577
578void MetricsDaemon::DiskStatsCallback() {
579 long int read_sectors_now, write_sectors_now;
580 DiskStatsReadStats(&read_sectors_now, &write_sectors_now);
581
582 switch (diskstats_state_) {
583 case kDiskStatsShort:
584 SendMetric(kMetricReadSectorsShortName,
585 (int) (read_sectors_now - read_sectors_) /
586 kMetricDiskStatsShortInterval,
587 1,
588 kMetricSectorsIOMax,
589 kMetricSectorsBuckets);
590 SendMetric(kMetricWriteSectorsShortName,
591 (int) (write_sectors_now - write_sectors_) /
592 kMetricDiskStatsShortInterval,
593 1,
594 kMetricSectorsIOMax,
595 kMetricSectorsBuckets);
596 // Schedule long callback.
597 diskstats_state_ = kDiskStatsLong;
598 ScheduleDiskStatsCallback(kMetricDiskStatsLongInterval -
599 kMetricDiskStatsShortInterval);
600 break;
601 case kDiskStatsLong:
602 SendMetric(kMetricReadSectorsLongName,
603 (int) (read_sectors_now - read_sectors_) /
604 kMetricDiskStatsLongInterval,
605 1,
606 kMetricSectorsIOMax,
607 kMetricSectorsBuckets);
608 SendMetric(kMetricWriteSectorsLongName,
609 (int) (write_sectors_now - write_sectors_) /
610 kMetricDiskStatsLongInterval,
611 1,
612 kMetricSectorsIOMax,
613 kMetricSectorsBuckets);
614 // Reset sector counters
615 read_sectors_ = read_sectors_now;
616 write_sectors_ = write_sectors_now;
617 // Schedule short callback.
618 diskstats_state_ = kDiskStatsShort;
619 ScheduleDiskStatsCallback(kMetricDiskStatsShortInterval);
620 break;
621 default:
622 LOG(FATAL) << "Invalid disk stats state";
623 }
624}
625
Luigi Semenzato29c7ef92011-04-12 14:12:35 -0700626void MetricsDaemon::ScheduleMeminfoCallback(int wait) {
627 if (testing_) {
628 return;
629 }
630 g_timeout_add_seconds(wait, MeminfoCallbackStatic, this);
631}
632
633// static
634gboolean MetricsDaemon::MeminfoCallbackStatic(void* handle) {
635 return (static_cast<MetricsDaemon*>(handle))->MeminfoCallback();
636}
637
638gboolean MetricsDaemon::MeminfoCallback() {
639 std::string meminfo;
640 const FilePath meminfo_path("/proc/meminfo");
641 if (!file_util::ReadFileToString(meminfo_path, &meminfo)) {
642 LOG(WARNING) << "cannot read " << meminfo_path.value().c_str();
643 return false;
644 }
645 return ProcessMeminfo(meminfo);
646}
647
648gboolean MetricsDaemon::ProcessMeminfo(std::string meminfo) {
649 // This array has one element for every item of /proc/meminfo that we want to
650 // report to UMA. They must be listed in the same order in which
651 // /proc/meminfo prints them.
652 struct {
653 const char* name; // print name
654 const char* match; // string to match in output of /proc/meminfo
655 int log_scale; // report with log scale instead of linear percent
656 } fields[] = {
657 { "MemTotal", "MemTotal" }, // SPECIAL CASE: total system memory
658 { "MemFree", "MemFree" },
659 { "Buffers", "Buffers" },
660 { "Cached", "Cached" },
661 // { "SwapCached", "SwapCached" },
662 { "Active", "Active" },
663 { "Inactive", "Inactive" },
664 { "ActiveAnon", "Active(anon)" },
665 { "InactiveAnon", "Inactive(anon)" },
666 { "ActiveFile" , "Active(file)" },
667 { "InactiveFile", "Inactive(file)" },
668 { "Unevictable", "Unevictable", 1 },
669 // { "Mlocked", "Mlocked" },
670 // { "SwapTotal", "SwapTotal" },
671 // { "SwapFree", "SwapFree" },
672 // { "Dirty", "Dirty" },
673 // { "Writeback", "Writeback" },
674 { "AnonPages", "AnonPages" },
675 { "Mapped", "Mapped" },
676 { "Shmem", "Shmem", 1 },
677 { "Slab", "Slab", 1 },
678 // { "SReclaimable", "SReclaimable" },
679 // { "SUnreclaim", "SUnreclaim" },
680 };
681 // arraysize doesn't work here, probably can't handle anonymous structs
682 const int nfields = sizeof(fields) / sizeof(fields[0]);
683 int total_memory = 0;
684 std::vector<std::string> lines;
685 int nlines = Tokenize(meminfo, "\n", &lines);
686
687 // Scan meminfo output and collect field values. Each field name has to
688 // match a meminfo entry (case insensitive) after removing non-alpha
689 // characters from the entry.
690 int i = 0;
691 int iline = 0;
692 for (;;) {
693 if (i == nfields) {
694 // all fields are matched
695 return true;
696 }
697 if (iline == nlines) {
698 // end of input reached while scanning
699 LOG(WARNING) << "cannot find field " << fields[i].match
700 << " and following";
701 return false;
702 }
703
704 std::vector<std::string> tokens;
705 Tokenize(lines[iline], ": ", &tokens);
706
707 if (strcmp(fields[i].match, tokens[0].c_str()) == 0) {
708 // name matches: parse value and report
709 int meminfo_value;
710 char metrics_name[128];
711 char* rest;
712 meminfo_value = static_cast<int>(strtol(tokens[1].c_str(), &rest, 10));
713 if (*rest != '\0') {
714 LOG(WARNING) << "missing meminfo value";
715 return false;
716 }
717 if (i == 0) {
718 // special case: total memory
719 total_memory = meminfo_value;
720 } else {
721 snprintf(metrics_name, sizeof(metrics_name),
722 "Platform.Meminfo%s", fields[i].name);
723 if (fields[i].log_scale) {
724 // report value in kbytes, log scale, 4Gb max
725 SendMetric(metrics_name, meminfo_value, 1, 4 * 1000 * 1000, 100);
726 } else {
727 // report value as percent of total memory
728 if (total_memory == 0) {
729 // this "cannot happen"
730 LOG(WARNING) << "borked meminfo parser";
731 return false;
732 }
733 int percent = meminfo_value * 100 / total_memory;
734 SendLinearMetric(metrics_name, percent, 100, 101);
735 }
736 }
737 // start looking for next field
738 i++;
739 }
740 iline++;
741 }
742}
743
Darin Petkovf1e85e42010-06-10 15:59:53 -0700744// static
Ken Mixterccd84c02010-08-16 19:57:13 -0700745void MetricsDaemon::ReportDailyUse(void* handle, int tag, int count) {
Darin Petkov1bb904e2010-06-16 15:58:06 -0700746 if (count <= 0)
747 return;
748
Darin Petkovf1e85e42010-06-10 15:59:53 -0700749 MetricsDaemon* daemon = static_cast<MetricsDaemon*>(handle);
750 int minutes = (count + kSecondsPerMinute / 2) / kSecondsPerMinute;
751 daemon->SendMetric(kMetricDailyUseTimeName, minutes,
752 kMetricDailyUseTimeMin,
753 kMetricDailyUseTimeMax,
754 kMetricDailyUseTimeBuckets);
755}
756
Darin Petkov38d5cb02010-06-24 12:10:26 -0700757void MetricsDaemon::SendMetric(const string& name, int sample,
Darin Petkov11b8eb32010-05-18 11:00:59 -0700758 int min, int max, int nbuckets) {
Darin Petkovfc91b422010-05-12 13:05:45 -0700759 DLOG(INFO) << "received metric: " << name << " " << sample << " "
760 << min << " " << max << " " << nbuckets;
761 metrics_lib_->SendToUMA(name, sample, min, max, nbuckets);
Darin Petkov65b01462010-04-14 13:32:20 -0700762}
Luigi Semenzato29c7ef92011-04-12 14:12:35 -0700763
764void MetricsDaemon::SendLinearMetric(const string& name, int sample,
765 int max, int nbuckets) {
766 DLOG(INFO) << "received linear metric: " << name << " " << sample << " "
767 << max << " " << nbuckets;
768 // TODO(semenzato): add a proper linear histogram to the Chrome external
769 // metrics API.
770 LOG_IF(FATAL, nbuckets != max + 1) << "unsupported histogram scale";
771 metrics_lib_->SendEnumToUMA(name, sample, max);
772}