shill: cellular: Add Network.Shill.Cellular.FailureReason.

BUG=chromium-os:38422
TEST=Unit tests, Manually verify user action is in
/mnt/stateful_partition/encrypted/var/log/metrics/uma-events

Change-Id: I07e71f08b136581237233c9f29c8dd0c6d2db80e
Reviewed-on: https://gerrit.chromium.org/gerrit/42429
Reviewed-by: Ben Chan <benchan@chromium.org>
Commit-Queue: Thieu Le <thieule@chromium.org>
Tested-by: Thieu Le <thieule@chromium.org>
diff --git a/cellular.cc b/cellular.cc
index 6189942..4e5f8eb 100644
--- a/cellular.cc
+++ b/cellular.cc
@@ -462,6 +462,7 @@
     metrics()->NotifyDeviceConnectFinished(interface_index());
     OnConnected();
   } else {
+    metrics()->NotifyCellularDeviceFailure(error);
     OnConnectFailed(error);
   }
 }
@@ -511,10 +512,12 @@
 void Cellular::OnDisconnectReply(const Error &error) {
   SLOG(Cellular, 2) << __func__ << "(" << error << ")";
   explicit_disconnect_ = false;
-  if (error.IsSuccess())
+  if (error.IsSuccess()) {
     OnDisconnected();
-  else
+  } else {
+    metrics()->NotifyCellularDeviceFailure(error);
     OnDisconnectFailed();
+  }
   manager()->TerminationActionComplete(FriendlyName());
   manager()->RemoveTerminationAction(FriendlyName());
 }
diff --git a/metrics.cc b/metrics.cc
index 5220a25..9987e21 100644
--- a/metrics.cc
+++ b/metrics.cc
@@ -193,6 +193,11 @@
 // static
 const char Metrics::kMetricCellularDrop[] =
     "Network.Shill.Cellular.Drop";
+// The format of FailureReason is different to other metrics because this
+// name is prepended to the error message before the entire string is sent
+// via SendUserActionToUMA.
+const char Metrics::kMetricCellularFailureReason[] =
+    "Network.Shill.Cellular.FailureReason: ";
 const char Metrics::kMetricCellularSignalStrengthBeforeDrop[] =
     "Network.Shill.Cellular.SignalStrengthBeforeDrop";
 const int Metrics::kMetricCellularSignalStrengthBeforeDropMax = 100;
@@ -823,6 +828,11 @@
             kMetricCellularSignalStrengthBeforeDropNumBuckets);
 }
 
+void Metrics::NotifyCellularDeviceFailure(const Error &error) {
+  library_->SendUserActionToUMA(
+      kMetricCellularFailureReason + error.message());
+}
+
 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 57c1ce7..7bec934 100644
--- a/metrics.h
+++ b/metrics.h
@@ -278,6 +278,7 @@
 
   // Cellular specific statistics.
   static const char kMetricCellularDrop[];
+  static const char kMetricCellularFailureReason[];
   static const char kMetricCellularSignalStrengthBeforeDrop[];
   static const int kMetricCellularSignalStrengthBeforeDropMax;
   static const int kMetricCellularSignalStrengthBeforeDropMin;
@@ -409,6 +410,9 @@
   void NotifyCellularDeviceDrop(const std::string &network_technology,
                                 uint16 signal_strength);
 
+  // Notifies this object about a cellular device failure code.
+  void NotifyCellularDeviceFailure(const Error &error);
+
   // 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 ab8875f..da9236e 100644
--- a/metrics_unittest.cc
+++ b/metrics_unittest.cc
@@ -474,6 +474,22 @@
   }
 }
 
+TEST_F(MetricsTest, CellularDeviceFailure) {
+  const char kErrorMessage[] =
+      "org.chromium.flimflam.Error.Failure:"
+      "$NWQMISTATUS: QMI_RESULT_FAILURE:QMI_ERR_CALL_FAILED#015#012#011"
+      "QMI State: DISCONNECTED#015#012#011Call End Reason:1016#015#012#011"
+      "Call Duration: 0 seconds#015#015#012"
+      "$NWQMISTATUS: QMI_RESULT_SUCCESS:QMI_ERR_NONE#015#012#011"
+      "QMI State: DISCONNECTED#015#012#011Call End Reason:0#015#012#011"
+      "Call Duration: 0 seconds";
+  string expected_message =
+      string(Metrics::kMetricCellularFailureReason) + kErrorMessage;
+  EXPECT_CALL(library_, SendUserActionToUMA(expected_message));
+  Error error(Error::kOperationFailed, kErrorMessage);
+  metrics_.NotifyCellularDeviceFailure(error);
+}
+
 #ifndef NDEBUG
 
 typedef MetricsTest MetricsDeathTest;