shill: cellular: Add LTE drop metrics.

Network.Shill.Cellular.Drop and
Network.Shill.Cellular.SignalStrengthBeforeDrop.

BUG=chromium-os:38165
TEST=Drop cellular signal and check chrome://histograms

Change-Id: Iddf25c903e07d55486c77cadd9b1d5898d40de0a
Reviewed-on: https://gerrit.chromium.org/gerrit/42329
Reviewed-by: Thieu Le <thieule@chromium.org>
Tested-by: Thieu Le <thieule@chromium.org>
Commit-Queue: Thieu Le <thieule@chromium.org>
diff --git a/cellular.cc b/cellular.cc
index dee04da..6a685a5 100644
--- a/cellular.cc
+++ b/cellular.cc
@@ -117,7 +117,8 @@
       cellular_operator_info_(cellular_operator_info),
       provider_db_(provider_db),
       proxy_factory_(proxy_factory),
-      allow_roaming_(false) {
+      allow_roaming_(false),
+      explicit_disconnect_(false) {
   PropertyStore *store = this->mutable_store();
   // TODO(jglasgow): kDBusConnectionProperty is deprecated.
   store->RegisterConstString(flimflam::kDBusConnectionProperty, &dbus_owner_);
@@ -215,6 +216,7 @@
 void Cellular::Stop(Error *error,
                     const EnabledStateChangedCallback &callback) {
   SLOG(Cellular, 2) << __func__ << ": " << GetStateString(state_);
+  explicit_disconnect_ = true;
   ResultCallback cb = Bind(&Cellular::StopModemCallback,
                            weak_ptr_factory_.GetWeakPtr(),
                            callback);
@@ -267,6 +269,7 @@
 void Cellular::StopModemCallback(const EnabledStateChangedCallback &callback,
                                  const Error &error) {
   SLOG(Cellular, 2) << __func__ << ": " << GetStateString(state_);
+  explicit_disconnect_ = false;
   // Destroy the cellular service regardless of any errors that occur during
   // the stop process since we do not know the state of the modem at this
   // point.
@@ -361,6 +364,11 @@
     return;
   }
   if (!capability_->IsRegistered()) {
+    if (!explicit_disconnect_ &&
+        (state_ == kStateLinked || state_ == kStateConnected) &&
+        service_.get())
+      metrics()->NotifyCellularDeviceDrop(
+        capability_->GetNetworkTechnologyString(), service_->strength());
     DestroyService();
     if (state_ == kStateLinked ||
         state_ == kStateConnected ||
@@ -491,6 +499,7 @@
         error, Error::kNotConnected, "Not connected; request ignored.");
     return;
   }
+  explicit_disconnect_ = true;
   ResultCallback cb = Bind(&Cellular::OnDisconnectReply,
                            weak_ptr_factory_.GetWeakPtr());
   capability_->Disconnect(error, cb);
@@ -498,6 +507,7 @@
 
 void Cellular::OnDisconnectReply(const Error &error) {
   SLOG(Cellular, 2) << __func__ << "(" << error << ")";
+  explicit_disconnect_ = false;
   if (error.IsSuccess())
     OnDisconnected();
   else
diff --git a/cellular.h b/cellular.h
index 3aec582..9ced902 100644
--- a/cellular.h
+++ b/cellular.h
@@ -15,6 +15,7 @@
 #include "shill/dbus_properties.h"
 #include "shill/device.h"
 #include "shill/event_dispatcher.h"
+#include "shill/metrics.h"
 #include "shill/modem_proxy_interface.h"
 #include "shill/refptr_types.h"
 
@@ -335,6 +336,9 @@
   // User preference to allow or disallow roaming
   bool allow_roaming_;
 
+  // Flag indicating that a disconnect has been explicitly requested.
+  bool explicit_disconnect_;
+
   DISALLOW_COPY_AND_ASSIGN(Cellular);
 };
 
diff --git a/metrics.cc b/metrics.cc
index 2d9e290..abd5c7a 100644
--- a/metrics.cc
+++ b/metrics.cc
@@ -190,6 +190,15 @@
 const char Metrics::kMetricLinkApDisconnectType[] =
     "Network.Shill.WiFi.ApDisconnectType";
 
+// static
+const char Metrics::kMetricCellularDrop[] =
+    "Network.Shill.Cellular.Drop";
+const char Metrics::kMetricCellularSignalStrengthBeforeDrop[] =
+    "Network.Shill.Cellular.SignalStrengthBeforeDrop";
+const int Metrics::kMetricCellularSignalStrengthBeforeDropMax = 100;
+const int Metrics::kMetricCellularSignalStrengthBeforeDropMin = 0;
+const int Metrics::kMetricCellularSignalStrengthBeforeDropNumBuckets = 10;
+
 
 Metrics::Metrics()
     : library_(&metrics_library_),
@@ -738,6 +747,40 @@
   device_metrics->connect_timer->ReportMilliseconds();
 }
 
+void Metrics::NotifyCellularDeviceDrop(const string &network_technology,
+                                       uint16 signal_strength) {
+  SLOG(Metrics, 2) << __func__ << ": " << network_technology
+                               << ", " << signal_strength;
+  CellularDropTechnology drop_technology = kCellularDropTechnologyUnknown;
+  if (network_technology == flimflam::kNetworkTechnology1Xrtt) {
+    drop_technology = kCellularDropTechnology1Xrtt;
+  } else if (network_technology == flimflam::kNetworkTechnologyEdge) {
+    drop_technology = kCellularDropTechnologyEdge;
+  } else if (network_technology == flimflam::kNetworkTechnologyEvdo) {
+    drop_technology = kCellularDropTechnologyEvdo;
+  } else if (network_technology == flimflam::kNetworkTechnologyGprs) {
+    drop_technology = kCellularDropTechnologyGprs;
+  } else if (network_technology == flimflam::kNetworkTechnologyGsm) {
+    drop_technology = kCellularDropTechnologyGsm;
+  } else if (network_technology == flimflam::kNetworkTechnologyHspa) {
+    drop_technology = kCellularDropTechnologyHspa;
+  } else if (network_technology == flimflam::kNetworkTechnologyHspaPlus) {
+    drop_technology = kCellularDropTechnologyHspaPlus;
+  } else if (network_technology == flimflam::kNetworkTechnologyLte) {
+    drop_technology = kCellularDropTechnologyLte;
+  } else if (network_technology == flimflam::kNetworkTechnologyUmts) {
+    drop_technology = kCellularDropTechnologyUmts;
+  }
+  SendEnumToUMA(kMetricCellularDrop,
+                drop_technology,
+                kCellularDropTechnologyMax);
+  SendToUMA(kMetricCellularSignalStrengthBeforeDrop,
+            signal_strength,
+            kMetricCellularSignalStrengthBeforeDropMin,
+            kMetricCellularSignalStrengthBeforeDropMax,
+            kMetricCellularSignalStrengthBeforeDropNumBuckets);
+}
+
 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 1136ca1..bd81924 100644
--- a/metrics.h
+++ b/metrics.h
@@ -157,6 +157,20 @@
     kTerminationActionReasonTerminate
   };
 
+  enum CellularDropTechnology {
+    kCellularDropTechnology1Xrtt = 0,
+    kCellularDropTechnologyEdge = 1,
+    kCellularDropTechnologyEvdo = 2,
+    kCellularDropTechnologyGprs = 3,
+    kCellularDropTechnologyGsm = 4,
+    kCellularDropTechnologyHspa = 5,
+    kCellularDropTechnologyHspaPlus = 6,
+    kCellularDropTechnologyLte = 7,
+    kCellularDropTechnologyUmts = 8,
+    kCellularDropTechnologyUnknown = 9,
+    kCellularDropTechnologyMax
+  };
+
   static const char kMetricDisconnect[];
   static const int kMetricDisconnectMax;
   static const int kMetricDisconnectMin;
@@ -262,6 +276,13 @@
   // WiFiService Entry Fixup.
   static const char kMetricServiceFixupEntries[];
 
+  // Cellular specific statistics.
+  static const char kMetricCellularDrop[];
+  static const char kMetricCellularSignalStrengthBeforeDrop[];
+  static const int kMetricCellularSignalStrengthBeforeDropMax;
+  static const int kMetricCellularSignalStrengthBeforeDropMin;
+  static const int kMetricCellularSignalStrengthBeforeDropNumBuckets;
+
   Metrics();
   virtual ~Metrics();
 
@@ -374,6 +395,11 @@
   // Notifies this object that a device has completed the connect process.
   void NotifyDeviceConnectFinished(int interface_index);
 
+  // Notifies this object that a cellular device has been dropped by the
+  // network.
+  void NotifyCellularDeviceDrop(const std::string &network_technology,
+                                uint16 signal_strength);
+
   // Sends linear histogram data to UMA.
   virtual bool SendEnumToUMA(const std::string &name, int sample, int max);
 
diff --git a/metrics_unittest.cc b/metrics_unittest.cc
index 4e13811..cf99050 100644
--- a/metrics_unittest.cc
+++ b/metrics_unittest.cc
@@ -4,6 +4,8 @@
 
 #include "shill/metrics.h"
 
+#include <string>
+
 #include <chromeos/dbus/service_constants.h>
 #include <metrics/metrics_library_mock.h>
 #include <metrics/timer_mock.h>
@@ -12,6 +14,8 @@
 #include "shill/mock_wifi_service.h"
 #include "shill/property_store_unittest.h"
 
+using std::string;
+
 using testing::_;
 using testing::DoAll;
 using testing::Ge;
@@ -413,6 +417,37 @@
   metrics_.NotifyDeviceScanFinished(kInterfaceIndex);
 }
 
+TEST_F(MetricsTest, CellularDrop) {
+  const char *kUMATechnologyStrings[] = {
+      flimflam::kNetworkTechnology1Xrtt,
+      flimflam::kNetworkTechnologyEdge,
+      flimflam::kNetworkTechnologyEvdo,
+      flimflam::kNetworkTechnologyGprs,
+      flimflam::kNetworkTechnologyGsm,
+      flimflam::kNetworkTechnologyHspa,
+      flimflam::kNetworkTechnologyHspaPlus,
+      flimflam::kNetworkTechnologyLte,
+      flimflam::kNetworkTechnologyUmts,
+      "Unknown" };
+
+  const uint16 signal_strength = 100;
+  for (size_t index = 0; index < arraysize(kUMATechnologyStrings); ++index) {
+    EXPECT_CALL(library_,
+        SendEnumToUMA(Metrics::kMetricCellularDrop,
+                      index,
+                      Metrics::kCellularDropTechnologyMax));
+    EXPECT_CALL(library_,
+        SendToUMA(Metrics::kMetricCellularSignalStrengthBeforeDrop,
+                  signal_strength,
+                  Metrics::kMetricCellularSignalStrengthBeforeDropMin,
+                  Metrics::kMetricCellularSignalStrengthBeforeDropMax,
+                  Metrics::kMetricCellularSignalStrengthBeforeDropNumBuckets));
+    metrics_.NotifyCellularDeviceDrop(kUMATechnologyStrings[index],
+                                      signal_strength);
+    Mock::VerifyAndClearExpectations(&library_);
+  }
+}
+
 #ifndef NDEBUG
 
 typedef MetricsTest MetricsDeathTest;