chromium/chromeos/ash/components/network/metrics/network_metrics_helper.cc

// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chromeos/ash/components/network/metrics/network_metrics_helper.h"

#include "base/metrics/histogram_functions.h"
#include "base/notreached.h"
#include "base/strings/strcat.h"
#include "chromeos/ash/components/network/cellular_metrics_logger.h"
#include "chromeos/ash/components/network/metrics/connection_results.h"
#include "chromeos/ash/components/network/network_event_log.h"
#include "chromeos/ash/components/network/network_handler.h"
#include "chromeos/ash/components/network/network_state.h"
#include "chromeos/services/network_config/public/mojom/cros_network_config.mojom.h"
#include "third_party/cros_system_api/dbus/shill/dbus-constants.h"

namespace ash {

namespace {

const char kNetworkMetricsPrefix[] = "Network.Ash.";
const char kAllConnectionResultSuffix[] = ".ConnectionResult.All";
const char kFilteredConnectionResultSuffix[] = ".ConnectionResult.Filtered";
const char kNonUserInitiatedConnectionResultSuffix[] =
    ".ConnectionResult.NonUserInitiated";
const char kUserInitiatedConnectionResultSuffix[] =
    ".ConnectionResult.UserInitiated";
const char kDisconnectionsWithoutUserActionSuffix[] =
    ".DisconnectionsWithoutUserAction";
const char kDisconnectionsWithoutUserActionShillErrorSuffix[] =
    ".DisconnectionsWithoutUserAction.ShillError";

const char kEnableTechnologyResultSuffix[] = ".EnabledState.Enable.Result";
const char kEnableTechnologyResultCodeSuffix[] =
    ".EnabledState.Enable.ResultCode";
const char kDisableTechnologyResultSuffix[] = ".EnabledState.Disable.Result";
const char kDisableTechnologyResultCodeSuffix[] =
    ".EnabledState.Disable.ResultCode";

const char kCellular[] = "Cellular";
const char kCellularESim[] = "Cellular.ESim";
const char kCellularPSim[] = "Cellular.PSim";
const char kPolicySuffix[] = ".Policy";

const char kEthernet[] = "Ethernet";
const char kEthernetEap[] = "Ethernet.Eap";
const char kEthernetNoEap[] = "Ethernet.NoEap";

const char kTether[] = "Tether";

const char kVPN[] = "VPN";
const char kVPNBuiltIn[] = "VPN.TypeBuiltIn";
const char kVPNThirdParty[] = "VPN.TypeThirdParty";
const char kVPNUnknown[] = "VPN.TypeUnknown";

const char kWifi[] = "WiFi";
const char kWifiOpen[] = "WiFi.SecurityOpen";
const char kWifiPasswordProtected[] = "WiFi.SecurityPasswordProtected";

NetworkStateHandler* GetNetworkStateHandler() {
  return NetworkHandler::Get()->network_state_handler();
}

const std::optional<const std::string> GetTechnologyTypeSuffix(
    const std::string& technology) {
  // Note that Tether is a fake technology that does not correspond to shill
  // technology type.
  if (technology == shill::kTypeWifi)
    return kWifi;
  else if (technology == shill::kTypeEthernet)
    return kEthernet;
  else if (technology == shill::kTypeCellular)
    return kCellular;
  else if (technology == shill::kTypeVPN)
    return kVPN;
  return std::nullopt;
}

const std::vector<std::string> GetCellularNetworkTypeHistogams(
    const NetworkState* network_state) {
  std::vector<std::string> cellular_histograms{kCellular};

  const char* cellular_sim_type =
      network_state->eid().empty() ? kCellularPSim : kCellularESim;
  cellular_histograms.emplace_back(cellular_sim_type);
  if (network_state->IsManagedByPolicy()) {
    cellular_histograms.emplace_back(
        base::StrCat({cellular_sim_type, kPolicySuffix}));
  }
  return cellular_histograms;
}

const std::vector<std::string> GetEthernetNetworkTypeHistograms(
    const NetworkState* network_state) {
  std::vector<std::string> ethernet_histograms{kEthernet};
  if (GetNetworkStateHandler()->GetEAPForEthernet(network_state->path(),
                                                  /*connected_only=*/true)) {
    ethernet_histograms.emplace_back(kEthernetEap);
  } else {
    ethernet_histograms.emplace_back(kEthernetNoEap);
  }

  return ethernet_histograms;
}

const std::vector<std::string> GetWifiNetworkTypeHistograms(
    const NetworkState* network_state) {
  std::vector<std::string> wifi_histograms{kWifi};

  if (network_state->GetMojoSecurity() ==
      chromeos::network_config::mojom::SecurityType::kNone) {
    wifi_histograms.emplace_back(kWifiOpen);
  } else {
    wifi_histograms.emplace_back(kWifiPasswordProtected);
  }

  return wifi_histograms;
}

const std::vector<std::string> GetTetherNetworkTypeHistograms(
    const NetworkState* network_state) {
  return {kTether};
}

const std::vector<std::string> GetVpnNetworkTypeHistograms(
    const NetworkState* network_state) {
  const std::string& vpn_provider_type = network_state->GetVpnProviderType();

  if (vpn_provider_type.empty())
    return {};

  std::vector<std::string> vpn_histograms{kVPN};

  if (vpn_provider_type == shill::kProviderThirdPartyVpn ||
      vpn_provider_type == shill::kProviderArcVpn) {
    vpn_histograms.emplace_back(kVPNThirdParty);
  } else if (vpn_provider_type == shill::kProviderIKEv2 ||
             vpn_provider_type == shill::kProviderL2tpIpsec ||
             vpn_provider_type == shill::kProviderOpenVpn ||
             vpn_provider_type == shill::kProviderWireGuard) {
    vpn_histograms.emplace_back(kVPNBuiltIn);
  } else {
    DUMP_WILL_BE_NOTREACHED();
    vpn_histograms.emplace_back(kVPNUnknown);
  }
  return vpn_histograms;
}

const std::vector<std::string> GetNetworkTypeHistogramNames(
    const NetworkState* network_state) {
  switch (network_state->GetNetworkTechnologyType()) {
    case NetworkState::NetworkTechnologyType::kCellular:
      return GetCellularNetworkTypeHistogams(network_state);
    case NetworkState::NetworkTechnologyType::kEthernet:
      return GetEthernetNetworkTypeHistograms(network_state);
    case NetworkState::NetworkTechnologyType::kWiFi:
      return GetWifiNetworkTypeHistograms(network_state);
    case NetworkState::NetworkTechnologyType::kTether:
      return GetTetherNetworkTypeHistograms(network_state);
    case NetworkState::NetworkTechnologyType::kVPN:
      return GetVpnNetworkTypeHistograms(network_state);

    // There should not be connections requests for kEthernetEap type service.
    // kEthernetEap exists only to store auth details for ethernet.
    case NetworkState::NetworkTechnologyType::kEthernetEap:
      [[fallthrough]];
    case NetworkState::NetworkTechnologyType::kUnknown:
      [[fallthrough]];
    default:
      return {};
  }
}

}  // namespace

// static
void NetworkMetricsHelper::LogAllConnectionResult(
    const std::string& guid,
    bool is_auto_connect,
    bool is_repeated_error,
    const std::optional<std::string>& shill_error) {
  DCHECK(GetNetworkStateHandler());
  const NetworkState* network_state =
      GetNetworkStateHandler()->GetNetworkStateFromGuid(guid);
  if (!network_state)
    return;

  ShillConnectResult connect_result =
      shill_error ? ShillErrorToConnectResult(*shill_error)
                  : ShillConnectResult::kSuccess;

  const bool is_not_repeated_error =
      !is_repeated_error || connect_result == ShillConnectResult::kSuccess;

  for (const auto& network_type : GetNetworkTypeHistogramNames(network_state)) {
    base::UmaHistogramEnumeration(
        base::StrCat(
            {kNetworkMetricsPrefix, network_type, kAllConnectionResultSuffix}),
        connect_result);
    if (is_auto_connect) {
      base::UmaHistogramEnumeration(
          base::StrCat({kNetworkMetricsPrefix, network_type,
                        kNonUserInitiatedConnectionResultSuffix}),
          connect_result);
    }

    if (is_not_repeated_error) {
      base::UmaHistogramEnumeration(
          base::StrCat({kNetworkMetricsPrefix, network_type,
                        kFilteredConnectionResultSuffix}),
          connect_result);
    }
  }
}

// static
void NetworkMetricsHelper::LogUserInitiatedConnectionResult(
    const std::string& guid,
    const std::optional<std::string>& network_connection_error) {
  DCHECK(GetNetworkStateHandler());
  const NetworkState* network_state =
      GetNetworkStateHandler()->GetNetworkStateFromGuid(guid);
  if (!network_state)
    return;

  UserInitiatedConnectResult connect_result =
      network_connection_error
          ? NetworkConnectionErrorToConnectResult(*network_connection_error,
                                                  network_state->GetError())
          : UserInitiatedConnectResult::kSuccess;

  for (const auto& network_type : GetNetworkTypeHistogramNames(network_state)) {
    base::UmaHistogramEnumeration(
        base::StrCat({kNetworkMetricsPrefix, network_type,
                      kUserInitiatedConnectionResultSuffix}),
        connect_result);
  }
}

// static
void NetworkMetricsHelper::LogConnectionStateResult(
    const std::string& guid,
    const ConnectionState connection_state,
    const std::optional<ShillConnectResult> shill_error) {
  DCHECK(GetNetworkStateHandler());
  const NetworkState* network_state =
      GetNetworkStateHandler()->GetNetworkStateFromGuid(guid);
  if (!network_state) {
    return;
  }

  // Only when WiFi network becomes "failure" from a connected state indicates
  // there's a real disconnection without user action. If the network becomes
  // "idle" from a connected state with a shill error, it usually indicates the
  // disconnections are triggered by device suspend. See
  // go/cros-wifi-disconnection-metrics for details.
  if (network_state->GetNetworkTechnologyType() ==
          NetworkState::NetworkTechnologyType::kWiFi &&
      connection_state == ConnectionState::kDisconnectedWithoutUserAction &&
      network_state->connection_state() != shill::kStateFailure) {
    return;
  }

  for (const auto& network_type : GetNetworkTypeHistogramNames(network_state)) {
    base::UmaHistogramEnumeration(kNetworkMetricsPrefix + network_type +
                                      kDisconnectionsWithoutUserActionSuffix,
                                  connection_state);
    if (connection_state == ConnectionState::kDisconnectedWithoutUserAction) {
      DCHECK(shill_error.has_value());
      base::UmaHistogramEnumeration(
          kNetworkMetricsPrefix + network_type +
              kDisconnectionsWithoutUserActionShillErrorSuffix,
          *shill_error);
    }
  }
}

void NetworkMetricsHelper::LogEnableTechnologyResult(
    const std::string& technology,
    bool success,
    const std::optional<std::string>& shill_error) {
  std::optional<const std::string> suffix = GetTechnologyTypeSuffix(technology);

  if (!suffix)
    return;

  if (success == shill_error.has_value()) {
    if (shill_error.has_value()) {
      NET_LOG(ERROR) << "Error code: " << *shill_error
                     << " for successful enable operation on: " << technology;
    } else {
      NET_LOG(ERROR)
          << "Missing error code for unsuccessful enable operation on: "
          << technology;
    }
  }

  ShillConnectResult result = shill_error
                                  ? ShillErrorToConnectResult(*shill_error)
                                  : ShillConnectResult::kSuccess;
  base::UmaHistogramEnumeration(
      base::StrCat(
          {kNetworkMetricsPrefix, *suffix, kEnableTechnologyResultCodeSuffix}),
      result);

  base::UmaHistogramBoolean(base::StrCat({kNetworkMetricsPrefix, *suffix,
                                          kEnableTechnologyResultSuffix}),
                            success);
}

// static
void NetworkMetricsHelper::LogDisableTechnologyResult(
    const std::string& technology,
    bool success,
    const std::optional<std::string>& shill_error) {
  std::optional<const std::string> suffix = GetTechnologyTypeSuffix(technology);

  if (!suffix)
    return;

  if (success == shill_error.has_value()) {
    if (shill_error.has_value()) {
      NET_LOG(ERROR) << "Error code: " << *shill_error
                     << " for successful disable operation on: " << technology;
    } else {
      NET_LOG(ERROR)
          << "Missing error code for unsuccessful disable operation on: "
          << technology;
    }
  }

  ShillConnectResult result = shill_error
                                  ? ShillErrorToConnectResult(*shill_error)
                                  : ShillConnectResult::kSuccess;
  base::UmaHistogramEnumeration(
      base::StrCat(
          {kNetworkMetricsPrefix, *suffix, kDisableTechnologyResultCodeSuffix}),
      result);

  base::UmaHistogramBoolean(base::StrCat({kNetworkMetricsPrefix, *suffix,
                                          kDisableTechnologyResultSuffix}),
                            success);
}

NetworkMetricsHelper::NetworkMetricsHelper() = default;

NetworkMetricsHelper::~NetworkMetricsHelper() = default;

}  // namespace ash