chromium/chromeos/ash/components/network/metrics/connection_info_metrics_logger.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/connection_info_metrics_logger.h"

#include "base/containers/contains.h"
#include "base/containers/flat_set.h"
#include "chromeos/ash/components/network/metrics/connection_results.h"
#include "chromeos/ash/components/network/metrics/network_metrics_helper.h"
#include "chromeos/ash/components/network/network_connection_handler.h"
#include "chromeos/ash/components/network/network_state.h"
#include "chromeos/ash/components/network/network_state_handler.h"
#include "third_party/cros_system_api/dbus/shill/dbus-constants.h"

namespace ash {

ConnectionInfoMetricsLogger::ConnectionInfo::ConnectionInfo(
    const NetworkState* network,
    bool is_user_initiated)
    : guid(network->guid()),
      shill_error(network->GetError()),
      is_user_initiated(is_user_initiated) {
  if (network->IsConnectedState()) {
    status = Status::kConnected;
  } else if (network->IsConnectingState()) {
    status = Status::kConnecting;
  } else if (network->connection_state() == shill::kStateDisconnecting) {
    status = Status::kDisconnecting;
  } else if (network->connection_state() == shill::kStateFailure) {
    status = Status::kFailure;
  } else {
    status = Status::kDisconnected;
  }
}

ConnectionInfoMetricsLogger::ConnectionInfo::~ConnectionInfo() = default;

bool ConnectionInfoMetricsLogger::ConnectionInfo::operator==(
    const ConnectionInfoMetricsLogger::ConnectionInfo& other) const {
  return status == other.status && guid == other.guid &&
         is_user_initiated == other.is_user_initiated &&
         shill_error == other.shill_error;
}

ConnectionInfoMetricsLogger::ConnectionInfoMetricsLogger() = default;

ConnectionInfoMetricsLogger::~ConnectionInfoMetricsLogger() {
  if (network_connection_handler_)
    network_connection_handler_->RemoveObserver(this);
}

void ConnectionInfoMetricsLogger::Init(
    NetworkStateHandler* network_state_handler,
    NetworkConnectionHandler* network_connection_handler) {
  if (network_connection_handler) {
    network_connection_handler_ = network_connection_handler;
    network_connection_handler_->AddObserver(this);
  }

  if (network_state_handler) {
    network_state_handler_ = network_state_handler;
    network_state_handler_observer_.Observe(network_state_handler_.get());
    NetworkListChanged();
  }
}

void ConnectionInfoMetricsLogger::AddObserver(Observer* observer) {
  observers_.AddObserver(observer);
}

void ConnectionInfoMetricsLogger::RemoveObserver(Observer* observer) {
  observers_.RemoveObserver(observer);
}

void ConnectionInfoMetricsLogger::NetworkListChanged() {
  NetworkStateHandler::NetworkStateList network_list;
  network_state_handler_->GetVisibleNetworkList(&network_list);

  base::flat_set<std::string> visible_guids;
  for (const auto* network : network_list) {
    UpdateConnectionInfo(network);
    visible_guids.insert(network->guid());
  }

  base::flat_map<std::string, ConnectionInfo> old_guid_to_connection_info;
  old_guid_to_connection_info.swap(guid_to_connection_info_);

  // Only store visible network ConnectionInfo in |guid_to_connection_info_|.
  for (const auto& connection_info : old_guid_to_connection_info) {
    const std::string& guid = connection_info.first;
    if (!base::Contains(visible_guids, guid))
      continue;
    guid_to_connection_info_.insert_or_assign(
        guid, old_guid_to_connection_info.find(guid)->second);
  }
}

void ConnectionInfoMetricsLogger::NetworkConnectionStateChanged(
    const NetworkState* network) {
  UpdateConnectionInfo(network);
}

void ConnectionInfoMetricsLogger::ConnectToNetworkRequested(
    const std::string& service_path) {
  if (!network_state_handler_) {
    return;
  }

  const NetworkState* network =
      network_state_handler_->GetNetworkState(service_path);

  if (!network) {
    return;
  }

  guid_to_connection_info_.insert_or_assign(
      network->guid(), ConnectionInfo(network,
                                      /*is_user_initiated=*/true));
}

void ConnectionInfoMetricsLogger::OnShuttingDown() {
  network_state_handler_observer_.Reset();
}

void ConnectionInfoMetricsLogger::ConnectSucceeded(
    const std::string& service_path) {
  const NetworkState* network =
      network_state_handler_->GetNetworkState(service_path);

  if (!network)
    return;

  // Update the connection request to no longer be "user initiated" so that we
  // don't continue to emit subsequent connections as user initiated.
  guid_to_connection_info_.insert_or_assign(
      network->guid(), ConnectionInfo(network, /*is_user_initiated=*/false));
  NetworkMetricsHelper::LogUserInitiatedConnectionResult(network->guid());
}

void ConnectionInfoMetricsLogger::ConnectFailed(const std::string& service_path,
                                                const std::string& error_name) {
  const NetworkState* network =
      network_state_handler_->GetNetworkState(service_path);

  if (!network)
    return;

  // Update the connection request to no longer be "user initiated" so that we
  // don't continue to emit subsequent connections as user initiated.
  guid_to_connection_info_.insert_or_assign(
      network->guid(), ConnectionInfo(network, /*is_user_initiated=*/false));
  NetworkMetricsHelper::LogUserInitiatedConnectionResult(network->guid(),
                                                         error_name);
}

void ConnectionInfoMetricsLogger::UpdateConnectionInfo(
    const NetworkState* network) {
  const std::optional<ConnectionInfo> prev_info =
      GetCachedInfo(network->guid());
  // If a connect has been requested, maintain the connect request until the
  // connect succeeded or failed.
  const ConnectionInfo curr_info =
      ConnectionInfo(network, prev_info && prev_info->is_user_initiated);

  // No updates if the ConnectionInfo did not change.
  if (prev_info == curr_info)
    return;

  // If the connection status has changed, attempt to log automatic connection
  // and disconnection metrics.
  if (!prev_info || prev_info->status != curr_info.status) {
    ConnectionAttemptFinished(prev_info, curr_info);
    AttemptLogConnectionStateResult(prev_info, curr_info);
  }
  guid_to_connection_info_.insert_or_assign(network->guid(), curr_info);
}

void ConnectionInfoMetricsLogger::ConnectionAttemptFinished(
    const std::optional<ConnectionInfo>& prev_info,
    const ConnectionInfo& curr_info) const {
  DCHECK(!prev_info || prev_info && prev_info->guid == curr_info.guid);

  if (curr_info.status == ConnectionInfo::Status::kConnected) {
    NetworkMetricsHelper::LogAllConnectionResult(curr_info.guid,
                                                 !curr_info.is_user_initiated,
                                                 /*is_repeated_error=*/false);
    NotifyConnectionResult(curr_info.guid, /*shill_error=*/std::nullopt);
  }

  // If the network goes from connecting or disconnecting state to the
  // disconnected state, log the shill error if it's valid.
  if (prev_info &&
      (prev_info->status == ConnectionInfo::Status::kConnecting ||
       prev_info->status == ConnectionInfo::Status::kDisconnecting) &&
      curr_info.status == ConnectionInfo::Status::kDisconnected &&
      NetworkState::ErrorIsValid(curr_info.shill_error)) {
    NetworkMetricsHelper::LogAllConnectionResult(
        curr_info.guid, !curr_info.is_user_initiated,
        /*is_repeated_error=*/prev_info->shill_error == curr_info.shill_error,
        curr_info.shill_error);
    NotifyConnectionResult(curr_info.guid, curr_info.shill_error);
  }
}

void ConnectionInfoMetricsLogger::AttemptLogConnectionStateResult(
    const std::optional<ConnectionInfo>& prev_info,
    const ConnectionInfo& curr_info) const {
  if (curr_info.status == ConnectionInfo::Status::kConnected) {
    NetworkMetricsHelper::LogConnectionStateResult(
        curr_info.guid, NetworkMetricsHelper::ConnectionState::kConnected,
        /*shill_error=*/std::nullopt);
    return;
  }

  // If the network becomes disconnected or disconnecting from a connected state
  // as a result of a shill error.
  if (prev_info && prev_info->status == ConnectionInfo::Status::kConnected &&
      (curr_info.status == ConnectionInfo::Status::kDisconnected ||
       curr_info.status == ConnectionInfo::Status::kFailure) &&
      NetworkState::ErrorIsValid(curr_info.shill_error)) {
    NetworkMetricsHelper::LogConnectionStateResult(
        curr_info.guid,
        NetworkMetricsHelper::ConnectionState::kDisconnectedWithoutUserAction,
        ShillErrorToConnectResult(curr_info.shill_error));
    return;
  }
}

std::optional<ConnectionInfoMetricsLogger::ConnectionInfo>
ConnectionInfoMetricsLogger::GetCachedInfo(const std::string& guid) const {
  const auto prev_info_it = guid_to_connection_info_.find(guid);
  if (prev_info_it == guid_to_connection_info_.end())
    return std::nullopt;
  return prev_info_it->second;
}

void ConnectionInfoMetricsLogger::NotifyConnectionResult(
    const std::string& guid,
    const std::optional<std::string>& shill_error) const {
  for (auto& observer : observers_)
    observer.OnConnectionResult(guid, shill_error);
}

}  // namespace ash