Add UpdateEngine.Attempt.ConnectionType metric.

This adds a new metric so we can track the how the device is connected
to the Internet when an attempt starts.

BUG=chromium:358339
TEST=New unit test + unit tests pass.

Change-Id: Ic5c2f50e2396e6baa288aca70906f7112ef7bca9
Reviewed-on: https://chromium-review.googlesource.com/192864
Reviewed-by: Chris Sosa <sosa@chromium.org>
Reviewed-by: Alex Deymo <deymo@chromium.org>
Tested-by: David Zeuthen <zeuthen@chromium.org>
Commit-Queue: David Zeuthen <zeuthen@chromium.org>
diff --git a/metrics.cc b/metrics.cc
index b784b61..1584120 100644
--- a/metrics.cc
+++ b/metrics.cc
@@ -39,6 +39,8 @@
     "UpdateEngine.Attempt.PayloadType";
 const char kMetricAttemptPayloadSizeMiB[] =
     "UpdateEngine.Attempt.PayloadSizeMiB";
+const char kMetricAttemptConnectionType[] =
+    "UpdateEngine.Attempt.ConnectionType";
 const char kMetricAttemptDurationMinutes[] =
     "UpdateEngine.Attempt.DurationMinutes";
 const char kMetricAttemptDurationUptimeMinutes[] =
@@ -180,7 +182,8 @@
     DownloadSource download_source,
     AttemptResult attempt_result,
     ErrorCode internal_error_code,
-    DownloadErrorCode payload_download_error_code) {
+    DownloadErrorCode payload_download_error_code,
+    ConnectionType connection_type) {
   string metric;
 
   metric = metrics::kMetricAttemptNumber;
@@ -311,6 +314,14 @@
         30*24*60,  // max: 30 days
         50);       // num_buckets
   }
+
+  metric = metrics::kMetricAttemptConnectionType;
+  LOG(INFO) << "Uploading " << static_cast<int>(connection_type)
+            << " for metric " <<  metric;
+  system_state->metrics_lib()->SendEnumToUMA(
+      metric,
+      static_cast<int>(connection_type),
+      static_cast<int>(ConnectionType::kNumConstants));
 }
 
 
diff --git a/metrics.h b/metrics.h
index 4e6ed04..b5dd5f1 100644
--- a/metrics.h
+++ b/metrics.h
@@ -30,6 +30,7 @@
 extern const char kMetricAttemptNumber[];
 extern const char kMetricAttemptPayloadType[];
 extern const char kMetricAttemptPayloadSizeMiB[];
+extern const char kMetricAttemptConnectionType[];
 extern const char kMetricAttemptDurationMinutes[];
 extern const char kMetricAttemptDurationUptimeMinutes[];
 extern const char kMetricAttemptTimeSinceLastAttemptSeconds[];
@@ -140,6 +141,23 @@
   kUnset = -1
 };
 
+// Possible ways the device is connected to the Internet.
+//
+// This is used in the UpdateEngine.Attempt.ConnectionType histogram.
+enum class ConnectionType {
+  kUnknown,           // Unknown.
+  kEthernet,          // Ethernet.
+  kWifi,              // Wireless.
+  kWimax,             // WiMax.
+  kBluetooth,         // Bluetooth.
+  kCellular,          // Cellular.
+  kTetheredEthernet,  // Tethered (Ethernet).
+  kTetheredWifi,      // Tethered (Wifi).
+
+  kNumConstants,
+  kUnset = -1
+};
+
 // Helper function to report metrics reported once a day. The
 // following metrics are reported:
 //
@@ -215,7 +233,8 @@
     DownloadSource download_source,
     AttemptResult attempt_result,
     ErrorCode internal_error_code,
-    DownloadErrorCode payload_download_error_code);
+    DownloadErrorCode payload_download_error_code,
+    ConnectionType connection_type);
 
 // Helper function to report the after the completion of a successful
 // update attempt. The following metrics are reported:
diff --git a/payload_state.cc b/payload_state.cc
index 779f708..2685130 100644
--- a/payload_state.cc
+++ b/payload_state.cc
@@ -16,6 +16,7 @@
 #include "update_engine/hardware_interface.h"
 #include "update_engine/install_plan.h"
 #include "update_engine/prefs.h"
+#include "update_engine/real_dbus_wrapper.h"
 #include "update_engine/system_state.h"
 #include "update_engine/utils.h"
 
@@ -158,8 +159,22 @@
   ClockInterface *clock = system_state_->clock();
   attempt_start_time_boot_ = clock->GetBootTime();
   attempt_start_time_monotonic_ = clock->GetMonotonicTime();
-
   attempt_num_bytes_downloaded_ = 0;
+
+  metrics::ConnectionType type;
+  NetworkConnectionType network_connection_type;
+  NetworkTethering tethering;
+  RealDBusWrapper dbus_iface;
+  ConnectionManager* connection_manager = system_state_->connection_manager();
+  if (!connection_manager->GetConnectionProperties(&dbus_iface,
+                                                   &network_connection_type,
+                                                   &tethering)) {
+    LOG(ERROR) << "Failed to determine connection type.";
+    type = metrics::ConnectionType::kUnknown;
+  } else {
+    type = utils::GetConnectionType(network_connection_type, tethering);
+  }
+  attempt_connection_type_ = type;
 }
 
 void PayloadState::UpdateResumed() {
@@ -583,7 +598,8 @@
                                       download_source,
                                       attempt_result,
                                       internal_error_code,
-                                      payload_download_error_code);
+                                      payload_download_error_code,
+                                      attempt_connection_type_);
 }
 
 void PayloadState::CollectAndReportSuccessfulUpdateMetrics() {
diff --git a/payload_state.h b/payload_state.h
index 1035404..d0211c5 100644
--- a/payload_state.h
+++ b/payload_state.h
@@ -8,6 +8,7 @@
 #include <base/time/time.h>
 #include <gtest/gtest_prod.h>  // for FRIEND_TEST
 
+#include "update_engine/metrics.h"
 #include "update_engine/payload_state_interface.h"
 #include "update_engine/prefs_interface.h"
 
@@ -502,6 +503,9 @@
   // The monotonic time when the attempt was started.
   base::Time attempt_start_time_monotonic_;
 
+  // The connection type when the attempt started.
+  metrics::ConnectionType attempt_connection_type_;
+
   DISALLOW_COPY_AND_ASSIGN(PayloadState);
 };
 
diff --git a/utils.cc b/utils.cc
index 0cde048..6d2abac 100644
--- a/utils.cc
+++ b/utils.cc
@@ -1125,6 +1125,41 @@
   return metrics::DownloadErrorCode::kInputMalformed;
 }
 
+metrics::ConnectionType GetConnectionType(
+    NetworkConnectionType type,
+    NetworkTethering tethering) {
+  switch (type) {
+    case kNetUnknown:
+      return metrics::ConnectionType::kUnknown;
+
+    case kNetEthernet:
+      if (tethering == NetworkTethering::kConfirmed)
+        return metrics::ConnectionType::kTetheredEthernet;
+      else
+        return metrics::ConnectionType::kEthernet;
+
+    case kNetWifi:
+      if (tethering == NetworkTethering::kConfirmed)
+        return metrics::ConnectionType::kTetheredWifi;
+      else
+        return metrics::ConnectionType::kWifi;
+
+    case kNetWimax:
+      return metrics::ConnectionType::kWimax;
+
+    case kNetBluetooth:
+      return metrics::ConnectionType::kBluetooth;
+
+    case kNetCellular:
+      return metrics::ConnectionType::kCellular;
+  }
+
+  LOG(ERROR) << "Unexpected network connection type: type=" << type
+             << ", tethering=" << static_cast<int>(tethering);
+
+  return metrics::ConnectionType::kUnknown;
+}
+
 // Returns a printable version of the various flags denoted in the higher order
 // bits of the given code. Returns an empty string if none of those bits are
 // set.
diff --git a/utils.h b/utils.h
index 482f81a..77ed9d9 100644
--- a/utils.h
+++ b/utils.h
@@ -20,9 +20,10 @@
 #include "metrics/metrics_library.h"
 
 #include "update_engine/action.h"
+#include "update_engine/action_processor.h"
+#include "update_engine/connection_manager.h"
 #include "update_engine/constants.h"
 #include "update_engine/metrics.h"
-#include "update_engine/action_processor.h"
 
 namespace chromeos_update_engine {
 
@@ -350,6 +351,10 @@
 // can use utils::GetDownloadError() to get more detail.
 metrics::AttemptResult GetAttemptResult(ErrorCode code);
 
+// Calculates the internet connection type given |type| and |tethering|.
+metrics::ConnectionType GetConnectionType(NetworkConnectionType type,
+                                          NetworkTethering tethering);
+
 // Sends the error code to UMA using the metrics interface object in the given
 // system state. It also uses the system state to determine the right UMA
 // bucket for the error code.
diff --git a/utils_unittest.cc b/utils_unittest.cc
index 5e9d098..84c20cc 100644
--- a/utils_unittest.cc
+++ b/utils_unittest.cc
@@ -682,4 +682,54 @@
   EXPECT_EQ(duration.InSeconds(), 0);
 }
 
+TEST(UtilsTest, GetConnectionType) {
+  // Check that expected combinations map to the right value.
+  EXPECT_EQ(metrics::ConnectionType::kUnknown,
+            utils::GetConnectionType(kNetUnknown,
+                                     NetworkTethering::kUnknown));
+  EXPECT_EQ(metrics::ConnectionType::kEthernet,
+            utils::GetConnectionType(kNetEthernet,
+                                     NetworkTethering::kUnknown));
+  EXPECT_EQ(metrics::ConnectionType::kWifi,
+            utils::GetConnectionType(kNetWifi,
+                                     NetworkTethering::kUnknown));
+  EXPECT_EQ(metrics::ConnectionType::kWimax,
+            utils::GetConnectionType(kNetWimax,
+                                     NetworkTethering::kUnknown));
+  EXPECT_EQ(metrics::ConnectionType::kBluetooth,
+            utils::GetConnectionType(kNetBluetooth,
+                                     NetworkTethering::kUnknown));
+  EXPECT_EQ(metrics::ConnectionType::kCellular,
+            utils::GetConnectionType(kNetCellular,
+                                     NetworkTethering::kUnknown));
+  EXPECT_EQ(metrics::ConnectionType::kTetheredEthernet,
+            utils::GetConnectionType(kNetEthernet,
+                                     NetworkTethering::kConfirmed));
+  EXPECT_EQ(metrics::ConnectionType::kTetheredWifi,
+            utils::GetConnectionType(kNetWifi,
+                                     NetworkTethering::kConfirmed));
+
+  // Ensure that we don't report tethered ethernet unless it's confirmed.
+  EXPECT_EQ(metrics::ConnectionType::kEthernet,
+            utils::GetConnectionType(kNetEthernet,
+                                     NetworkTethering::kNotDetected));
+  EXPECT_EQ(metrics::ConnectionType::kEthernet,
+            utils::GetConnectionType(kNetEthernet,
+                                     NetworkTethering::kSuspected));
+  EXPECT_EQ(metrics::ConnectionType::kEthernet,
+            utils::GetConnectionType(kNetEthernet,
+                                     NetworkTethering::kUnknown));
+
+  // Ditto for tethered wifi.
+  EXPECT_EQ(metrics::ConnectionType::kWifi,
+            utils::GetConnectionType(kNetWifi,
+                                     NetworkTethering::kNotDetected));
+  EXPECT_EQ(metrics::ConnectionType::kWifi,
+            utils::GetConnectionType(kNetWifi,
+                                     NetworkTethering::kSuspected));
+  EXPECT_EQ(metrics::ConnectionType::kWifi,
+            utils::GetConnectionType(kNetWifi,
+                                     NetworkTethering::kUnknown));
+}
+
 }  // namespace chromeos_update_engine