shill: Add support for device metrics.

Also add first device metrics, TimeToInitialize, which is the time
between when the kernel notifies shill about a link to when a Device
object is created for that link.

BUG=chromium-os:38086
TEST=New unit test and checking chrome://histograms for new metrics

Change-Id: If42081a42eb39b1782e0d7603e5d79116645ebad
Reviewed-on: https://gerrit.chromium.org/gerrit/41794
Reviewed-by: Thieu Le <thieule@chromium.org>
Tested-by: Thieu Le <thieule@chromium.org>
Commit-Queue: Thieu Le <thieule@chromium.org>
diff --git a/device_info.cc b/device_info.cc
index 2e8df1b..75ab479 100644
--- a/device_info.cc
+++ b/device_info.cc
@@ -149,6 +149,7 @@
   if (Technology::IsPrimaryConnectivityTechnology(device->technology())) {
     manager_->RegisterDevice(device);
   }
+  metrics_->NotifyDeviceInitialized(device->interface_index());
 }
 
 void DeviceInfo::DeregisterDevice(const DeviceRefPtr &device) {
@@ -169,6 +170,7 @@
     // for the index.  That will be cleaned up by an RTNL message.
     iter->second.device = NULL;
   }
+  metrics_->DeregisterDevice(device->interface_index());
 }
 
 FilePath DeviceInfo::GetDeviceInfoPath(const string &iface_name,
@@ -528,6 +530,7 @@
       LOG(ERROR) << "Add Link message does not have IFLA_ADDRESS!";
       return;
     }
+    metrics_->RegisterDevice(dev_index, technology);
     device = CreateDevice(link_name, address, dev_index, technology);
     if (device) {
       RegisterDevice(device);
diff --git a/metrics.cc b/metrics.cc
index 2d59720..541f761 100644
--- a/metrics.cc
+++ b/metrics.cc
@@ -57,6 +57,12 @@
 const int Metrics::kMetricTimeToDropSecondsMax = 8 * 60 * 60;  // 8 hours
 const int Metrics::kMetricTimeToDropSecondsMin = 1;
 
+const char Metrics::kMetricTimeToInitializeMilliseconds[] =
+    "Network.Shill.%s.TimeToInitialize";
+const int Metrics::kMetricTimeToInitializeMillisecondsMin = 1;
+const int Metrics::kMetricTimeToInitializeMillisecondsMax = 20 * 1000;
+const int Metrics::kMetricTimeToInitializeMillisecondsNumBuckets = 20;
+
 const char Metrics::kMetricTimeResumeToReadyMilliseconds[] =
     "Network.Shill.%s.TimeResumeToReady";
 const char Metrics::kMetricTimeToConfigMilliseconds[] =
@@ -554,6 +560,37 @@
   SendEnumToUMA(metric_disconnect_type, type, kStatusCodeTypeMax);
 }
 
+void Metrics::RegisterDevice(int interface_index,
+                             Technology::Identifier technology) {
+  shared_ptr<DeviceMetrics> device_metrics(new DeviceMetrics);
+  devices_metrics_[interface_index] = device_metrics;
+  string histogram = GetFullMetricName(kMetricTimeToInitializeMilliseconds,
+                                       technology);
+  device_metrics->initialization_timer.reset(
+      new chromeos_metrics::TimerReporter(
+          histogram,
+          kMetricTimeToInitializeMillisecondsMin,
+          kMetricTimeToInitializeMillisecondsMax,
+          kMetricTimeToInitializeMillisecondsNumBuckets));
+  device_metrics->initialization_timer->Start();
+}
+
+void Metrics::DeregisterDevice(int interface_index) {
+  devices_metrics_.erase(interface_index);
+}
+
+void Metrics::NotifyDeviceInitialized(int interface_index) {
+  DeviceMetricsLookupMap::iterator it = devices_metrics_.find(interface_index);
+  if (it == devices_metrics_.end()) {
+    SLOG(Metrics, 1) << "device " << interface_index << " not found";
+    DCHECK(false);
+    return;
+  }
+  DeviceMetrics *device_metrics = it->second.get();
+  device_metrics->initialization_timer->Stop();
+  device_metrics->initialization_timer->ReportMilliseconds();
+}
+
 bool Metrics::SendEnumToUMA(const string &name, int sample, int max) {
   return library_->SendEnumToUMA(name, sample, max);
 }
diff --git a/metrics.h b/metrics.h
index ec40c2e..1cad04a 100644
--- a/metrics.h
+++ b/metrics.h
@@ -176,6 +176,10 @@
   static const char kMetricTimeToDropSeconds[];
   static const int kMetricTimeToDropSecondsMax;
   static const int kMetricTimeToDropSecondsMin;
+  static const char kMetricTimeToInitializeMilliseconds[];
+  static const int kMetricTimeToInitializeMillisecondsMin;
+  static const int kMetricTimeToInitializeMillisecondsMax;
+  static const int kMetricTimeToInitializeMillisecondsNumBuckets;
   static const char kMetricTimeToJoinMilliseconds[];
   static const char kMetricTimeToOnlineMilliseconds[];
   static const char kMetricTimeToPortalMilliseconds[];
@@ -305,6 +309,18 @@
   void Notify80211Disconnect(WiFiDisconnectByWhom by_whom,
                              IEEE_80211::WiFiReasonCode reason);
 
+  // Registers a device with this object so the device can use the timers to
+  // track state transition metrics.
+  void RegisterDevice(int interface_index,
+                      Technology::Identifier technology);
+
+  // Deregisters the device from this class.  All state transition timers
+  // will be removed.
+  void DeregisterDevice(int interface_index);
+
+  // Notifies this object that a device has been initialized.
+  void NotifyDeviceInitialized(int interface_index);
+
   // Sends linear histogram data to UMA.
   virtual bool SendEnumToUMA(const std::string &name, int sample, int max);
 
@@ -343,6 +359,13 @@
   typedef std::map<const Service *, std::tr1::shared_ptr<ServiceMetrics> >
       ServiceMetricsLookupMap;
 
+  struct DeviceMetrics {
+    DeviceMetrics() {}
+    scoped_ptr<chromeos_metrics::TimerReporter> initialization_timer;
+  };
+  typedef std::map<const int, std::tr1::shared_ptr<DeviceMetrics> >
+      DeviceMetricsLookupMap;
+
   static const uint16 kWiFiBandwidth5MHz;
   static const uint16 kWiFiBandwidth20MHz;
   static const uint16 kWiFiFrequency2412;
@@ -392,6 +415,7 @@
   scoped_ptr<chromeos_metrics::Timer> time_resume_to_ready_timer_;
   scoped_ptr<chromeos_metrics::Timer> time_termination_actions_timer;
   bool collect_bootstats_;
+  DeviceMetricsLookupMap devices_metrics_;
 
   DISALLOW_COPY_AND_ASSIGN(Metrics);
 };
diff --git a/metrics_unittest.cc b/metrics_unittest.cc
index 864b121..9988e09 100644
--- a/metrics_unittest.cc
+++ b/metrics_unittest.cc
@@ -330,6 +330,18 @@
             Metrics::PortalDetectionResultToEnum(result));
 }
 
+TEST_F(MetricsTest, TimeToInitialize) {
+  EXPECT_CALL(library_,
+      SendToUMA("Network.Shill.Cellular.TimeToInitialize",
+                Ge(0),
+                Metrics::kMetricTimeToInitializeMillisecondsMin,
+                Metrics::kMetricTimeToInitializeMillisecondsMax,
+                Metrics::kMetricTimeToInitializeMillisecondsNumBuckets));
+  const int kInterfaceIndex = 1;
+  metrics_.RegisterDevice(kInterfaceIndex, Technology::kCellular);
+  metrics_.NotifyDeviceInitialized(kInterfaceIndex);
+}
+
 #ifndef NDEBUG
 
 typedef MetricsTest MetricsDeathTest;