chromium/chromeos/ash/components/tether/host_connection_metrics_logger.h

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

#ifndef CHROMEOS_ASH_COMPONENTS_TETHER_HOST_CONNECTION_METRICS_LOGGER_H_
#define CHROMEOS_ASH_COMPONENTS_TETHER_HOST_CONNECTION_METRICS_LOGGER_H_

#include <string>

#include "base/gtest_prod_util.h"
#include "base/memory/raw_ptr.h"
#include "base/time/time.h"
#include "chromeos/ash/components/tether/active_host.h"

namespace base {
class Clock;
}  // namespace base

namespace ash {

namespace tether {

// Wrapper around metrics reporting for host connection results. Clients are
// expected to report the result of a host connection attempt once it has
// concluded.
class HostConnectionMetricsLogger : public ActiveHost::Observer {
 public:
  enum class ConnectionToHostResult {
    SUCCESS = 0,
    INTERNAL_ERROR = 1,
    USER_CANCELLATION = 2,
    PROVISIONING_FAILURE = 3,
    NO_CELLULAR_DATA = 4,
    TETHERING_UNSUPPORTED = 5,
    CANCELLED_FOR_NEWER_CONNECTION = 6,
    TETHER_SHUTDOWN_DURING_CONNECTION = 7,
    CONNECTION_TO_HOST_RESULT_MAX,
  };

  enum class ConnectionToHostInternalError {
    UNKNOWN_ERROR,
    CLIENT_CONNECTION_TIMEOUT,
    CLIENT_CONNECTION_INTERNAL_ERROR,
    CLIENT_CONNECTION_NETWORK_CONNECTION_HANDLER_FAILED,
    CLIENT_CONNECTION_NETWORK_STATE_WAS_NULL,
    CLIENT_CONNECTION_WIFI_FAILED_TO_ENABLE,
    TETHERING_TIMED_OUT_FIRST_TIME_SETUP_REQUIRED,
    TETHERING_TIMED_OUT_FIRST_TIME_SETUP_NOT_REQUIRED,
    ENABLING_HOTSPOT_FAILED,
    ENABLING_HOTSPOT_TIMEOUT,
    NO_RESPONSE,
    INVALID_HOTSPOT_CREDENTIALS,
    SUCCESSFUL_REQUEST_BUT_NO_RESPONSE,
    UNRECOGNIZED_RESPONSE_ERROR,
    INVALID_ACTIVE_EXISTING_SOFT_AP_CONFIG,
    INVALID_NEW_SOFT_AP_CONFIG,
    INVALID_WIFI_AP_CONFIG,
  };

  // Record the result of an attempted host connection.
  virtual void RecordConnectionToHostResult(
      ConnectionToHostResult result,
      const std::string& device_id,
      std::optional<ConnectionToHostInternalError> internal_error);

  HostConnectionMetricsLogger(ActiveHost* active_host);

  HostConnectionMetricsLogger(const HostConnectionMetricsLogger&) = delete;
  HostConnectionMetricsLogger& operator=(const HostConnectionMetricsLogger&) =
      delete;

  virtual ~HostConnectionMetricsLogger();

 protected:
  // ActiveHost::Observer:
  void OnActiveHostChanged(
      const ActiveHost::ActiveHostChangeInfo& change_info) override;

 private:
  friend class HostConnectionMetricsLoggerTest;
  FRIEND_TEST_ALL_PREFIXES(HostConnectionMetricsLoggerTest,
                           RecordConnectionResultFailure_InvalidWifiApConfig);
  FRIEND_TEST_ALL_PREFIXES(HostConnectionMetricsLoggerTest,
                           RecordConnectionResultProvisioningFailure);
  FRIEND_TEST_ALL_PREFIXES(HostConnectionMetricsLoggerTest,
                           RecordConnectionResultSuccess);
  FRIEND_TEST_ALL_PREFIXES(HostConnectionMetricsLoggerTest,
                           RecordConnectionResultSuccess_Background);
  FRIEND_TEST_ALL_PREFIXES(HostConnectionMetricsLoggerTest,
                           RecordConnectionResultSuccess_MultiDeviceApiEnabled);
  FRIEND_TEST_ALL_PREFIXES(
      HostConnectionMetricsLoggerTest,
      RecordConnectionResultSuccess_Background_DifferentDevice);
  FRIEND_TEST_ALL_PREFIXES(HostConnectionMetricsLoggerTest,
                           RecordConnectionResultFailure);
  FRIEND_TEST_ALL_PREFIXES(HostConnectionMetricsLoggerTest,
                           RecordConnectionResultFailure_Background);
  FRIEND_TEST_ALL_PREFIXES(HostConnectionMetricsLoggerTest,
                           RecordConnectionResultFailure_MultiDeviceApiEnabled);
  FRIEND_TEST_ALL_PREFIXES(
      HostConnectionMetricsLoggerTest,
      RecordConnectionResultFailure_Background_DifferentDevice);
  FRIEND_TEST_ALL_PREFIXES(
      HostConnectionMetricsLoggerTest,
      RecordConnectionResultFailureClientConnection_Timeout);
  FRIEND_TEST_ALL_PREFIXES(
      HostConnectionMetricsLoggerTest,
      RecordConnectionResultFailureClientConnection_CanceledByUser);
  FRIEND_TEST_ALL_PREFIXES(
      HostConnectionMetricsLoggerTest,
      RecordConnectionResultFailure_InvalidNewSoftApConfig);
  FRIEND_TEST_ALL_PREFIXES(
      HostConnectionMetricsLoggerTest,
      RecordConnectionResultFailure_InvalidActiveExistingSoftApConfig);
  FRIEND_TEST_ALL_PREFIXES(
      HostConnectionMetricsLoggerTest,
      RecordConnectionResultFailureClientConnection_InternalError);
  FRIEND_TEST_ALL_PREFIXES(
      HostConnectionMetricsLoggerTest,
      RecordConnectionResultFailureTetheringTimeout_SetupRequired);
  FRIEND_TEST_ALL_PREFIXES(
      HostConnectionMetricsLoggerTest,
      RecordConnectionResultFailureTetheringTimeout_SetupNotRequired);
  FRIEND_TEST_ALL_PREFIXES(HostConnectionMetricsLoggerTest,
                           RecordConnectionResultFailureTetheringUnsupported);
  FRIEND_TEST_ALL_PREFIXES(HostConnectionMetricsLoggerTest,
                           RecordConnectionResultFailureNoCellData);
  FRIEND_TEST_ALL_PREFIXES(HostConnectionMetricsLoggerTest,
                           RecordConnectionResultFailureEnablingHotspotFailed);
  FRIEND_TEST_ALL_PREFIXES(
      HostConnectionMetricsLoggerTest,
      RecordConnectionResultFailureShutDownDuringConnectionAttempt);
  FRIEND_TEST_ALL_PREFIXES(
      HostConnectionMetricsLoggerTest,
      RecordConnectionResultFailureClientConnection_WifiFailedToEnable);
  FRIEND_TEST_ALL_PREFIXES(
      HostConnectionMetricsLoggerTest,
      RecordConnectionResultFailureClientConnection_NetworkConnectionHandlerFailed);
  FRIEND_TEST_ALL_PREFIXES(
      HostConnectionMetricsLoggerTest,
      RecordConnectionResultFailureClientConnection_NetworkStateWasNull);
  FRIEND_TEST_ALL_PREFIXES(HostConnectionMetricsLoggerTest,
                           RecordConnectionResultFailureEnablingHotspotTimeout);
  FRIEND_TEST_ALL_PREFIXES(
      HostConnectionMetricsLoggerTest,
      RecordConnectionResultFailureCancelledForNewerConnection);
  FRIEND_TEST_ALL_PREFIXES(HostConnectionMetricsLoggerTest,
                           RecordConnectionResult);
  FRIEND_TEST_ALL_PREFIXES(HostConnectionMetricsLoggerTest,
                           RecordConnectToHostDuration);
  FRIEND_TEST_ALL_PREFIXES(HostConnectionMetricsLoggerTest,
                           RecordConnectToHostDuration_Background);
  FRIEND_TEST_ALL_PREFIXES(HostConnectionMetricsLoggerTest,
                           RecordConnectToHostDuration_MultiDeviceApiEnabled);
  FRIEND_TEST_ALL_PREFIXES(HostConnectionMetricsLoggerTest,
                           RecordConnectionResultFailureNoResponse);
  FRIEND_TEST_ALL_PREFIXES(
      HostConnectionMetricsLoggerTest,
      RecordConnectionResultFailureInvalidHotspotCredentials);

  void RecordInternalError(ConnectionToHostInternalError internal_error);

  void RecordUnavoidableError(ConnectionToHostResult result);

  // An Instant Tethering connection can fail for several different reasons.
  // Though traditionally success and each failure case would be logged to a
  // single enum, we have chosen to start at a top-level of view of simply
  // "success vs failure", and then iteratively breaking down the failure count
  // into its types (and possibly sub-types). Because of this cascading nature,
  // when a failure sub-type occurs, the code path in question must report that
  // sub-type as well as all the super-types above it. This would be cumbersome,
  // and thus HostConnectionMetricsLogger exists to provide a simple API which
  // handles all the other metrics that need to be reported.
  //
  // The most top-level metric is
  // InstantTethering.ConnectionToHostResult.ProvisioningFailureRate. Its
  // breakdown, and the breakdown of its subsquent metrics, is described in
  // tools/metrics/histograms/histograms.xml.
  enum class ConnectionToHostResult_ProvisioningFailureEventType {
    PROVISIONING_FAILED = 0,
    OTHER = 1,
    PROVISIONING_FAILURE_MAX
  };

  enum class ConnectionToHostResult_SuccessEventType {
    SUCCESS = 0,
    FAILURE = 1,
    SUCCESS_MAX
  };

  enum class ConnectionToHostResult_UnavoidableErrorEventType {
    OTHER = 0,
    PROVISIONING_FAILED = 1,
    USER_CANCELLATION = 2,
    TETHERING_UNSUPPORTED = 3,
    NO_CELLULAR_DATA = 4,
    SHUT_DOWN_DURING_CONNECTION = 5,
    CANCELLED_FOR_NEWER_CONNECTION_ATTEMPT = 6,
    kMax
  };

  enum class ConnectionToHostResult_FailureEventType {
    UNKNOWN_ERROR = 0,
    TETHERING_TIMED_OUT = 1,
    CLIENT_CONNECTION_ERROR = 2,
    TETHERING_UNSUPPORTED = 3,
    NO_CELL_DATA = 4,
    ENABLING_HOTSPOT_FAILED = 5,
    ENABLING_HOTSPOT_TIMEOUT = 6,
    NO_RESPONSE = 7,
    INVALID_HOTSPOT_CREDENTIALS = 8,
    SUCCESSFUL_REQUEST_BUT_NO_RESPONSE = 9,
    UNRECOGNIZED_RESPONSE_ERROR = 10,
    INVALID_ACTIVE_EXISTING_SOFT_AP_CONFIG = 11,
    INVALID_NEW_SOFT_AP_CONFIG = 12,
    INVALID_WIFI_AP_CONFIG = 13,
    FAILURE_MAX
  };

  enum class ConnectionToHostResult_FailureClientConnectionEventType {
    TIMEOUT = 0,
    CANCELED_BY_USER = 1,  // Obsolete
    INTERNAL_ERROR = 2,
    NETWORK_CONNECTION_HANDLER_FAILED = 3,
    NETWORK_STATE_WAS_NULL = 4,
    WIFI_FAILED_TO_ENABLED = 5,
    FAILURE_CLIENT_CONNECTION_MAX
  };

  enum class ConnectionToHostResult_FailureTetheringTimeoutEventType {
    FIRST_TIME_SETUP_WAS_REQUIRED = 0,
    FIRST_TIME_SETUP_WAS_NOT_REQUIRED = 1,
    FAILURE_TETHERING_TIMEOUT_MAX
  };

  // Record how a host connection attempt failed. Failure due to client error or
  // tethering timeout is covered by the
  // RecordConnectionResultFailureClientConnection() or
  // RecordConnectionResultFailureTetheringTimeout() methods, respectively.
  void RecordConnectionResultFailure(
      ConnectionToHostResult_FailureEventType event_type);

  // Record how a host connection attempt failed due to a client error.
  void RecordConnectionResultFailureClientConnection(
      ConnectionToHostResult_FailureClientConnectionEventType event_type);

  // Record the conditions of when host connection attempt failed due to
  // the host timing out during tethering.
  void RecordConnectionResultFailureTetheringTimeout(
      ConnectionToHostResult_FailureTetheringTimeoutEventType event_type);

  void RecordConnectToHostDuration(const std::string device_id);

  void SetClockForTesting(base::Clock* test_clock);

  raw_ptr<ActiveHost> active_host_;
  raw_ptr<base::Clock> clock_;

  base::Time connect_to_host_start_time_;
  std::string active_host_device_id_;
};

}  // namespace tether

}  // namespace ash

#endif  // CHROMEOS_ASH_COMPONENTS_TETHER_HOST_CONNECTION_METRICS_LOGGER_H_