chromium/chrome/browser/ui/webui/ash/cloud_upload/cloud_open_metrics.h

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

#ifndef CHROME_BROWSER_UI_WEBUI_ASH_CLOUD_UPLOAD_CLOUD_OPEN_METRICS_H_
#define CHROME_BROWSER_UI_WEBUI_ASH_CLOUD_UPLOAD_CLOUD_OPEN_METRICS_H_

#include "base/memory/safe_ref.h"
#include "base/metrics/histogram_functions.h"
#include "chrome/browser/ui/webui/ash/cloud_upload/cloud_upload_util.h"

namespace ash::cloud_upload {

enum class MetricState {
  // Not logged and it shouldn’t have been.
  kCorrectlyNotLogged = 0,
  // Logged when it should have been.
  kCorrectlyLogged = 1,
  // Not logged when it should have been.
  kIncorrectlyNotLogged = 2,
  // Logged when it shouldn’t have been.
  kIncorrectlyLogged = 3,
  // Logged more than once.
  kIncorrectlyLoggedMultipleTimes = 4,
  // An unexpected value was logged.
  kWrongValueLogged = 5,
  kMaxValue = kWrongValueLogged,
};

// Represents a metric identified by `metric_name` that logs value of type
// `MetricType`. Log the metric through this class. Keeps track of the value
// logged and the `MetricState` of the metric.
template <typename MetricType>
class Metric {
  static_assert(std::is_same_v<std::underlying_type_t<MetricType>, int>,
                "The underlying type of the MetricType must be an int");

 public:
  Metric(std::string metric_name_to_set,
         std::string companion_metric_name_to_set)
      : metric_name(metric_name_to_set),
        companion_metric_name_(companion_metric_name_to_set) {}
  ~Metric() = default;

  // Logs a `new_value` to the metric with `metric_name` and saves it to
  // `value`. Update the state:
  //   kCorrectlyNotLogged  -> kCorrectlyLogged
  //   !kCorrectlyNotLogged -> kIncorrectlyLoggedMultipleTimes
  // Return false if there was a metric inconsistency. That is, if the latter
  // state change occurred.
  bool Log(MetricType new_value) {
    LogMetric(new_value);
    if (state == MetricState::kCorrectlyNotLogged) {
      state = MetricState::kCorrectlyLogged;
    } else {
      state = MetricState::kIncorrectlyLoggedMultipleTimes;
    }
    old_value = value;
    value = new_value;
    return state == MetricState::kCorrectlyLogged;
  }

  // Return true if the `state` is a logged state.
  bool logged() {
    switch (state) {
      case MetricState::kCorrectlyNotLogged:
      case MetricState::kIncorrectlyNotLogged:
        return false;
      case MetricState::kCorrectlyLogged:
      case MetricState::kIncorrectlyLogged:
      case MetricState::kIncorrectlyLoggedMultipleTimes:
      case MetricState::kWrongValueLogged:
        return true;
    }
  }

  // Check metric is not logged, otherwise mark the metric as inconsistent and
  // return false.
  bool IsNotLogged() {
    if (logged()) {
      state = MetricState::kIncorrectlyLogged;
      return false;
    }
    return true;
  }

  // Check metric is logged, otherwise mark the metric as inconsistent and
  // return false.
  bool IsLogged() {
    if (!logged()) {
      state = MetricState::kIncorrectlyNotLogged;
      return false;
    }
    return true;
  }

  void LogCompanionMetric() {
    base::UmaHistogramEnumeration(companion_metric_name_, state);
  }

  void set_state(MetricState new_state) { state = new_state; }

  std::string metric_name;
  MetricState state = MetricState::kCorrectlyNotLogged;
  MetricType value;
  MetricType old_value;

 private:
  void LogMetric(MetricType new_value);

  std::string companion_metric_name_;
};

// Specialise for base::File::Error.
template <>
inline void Metric<base::File::Error>::LogMetric(base::File::Error new_value) {
  base::UmaHistogramExactLinear(metric_name, -new_value,
                                -base::File::FILE_ERROR_MAX);
}

template <class MetricType>
inline void Metric<MetricType>::LogMetric(MetricType new_value) {
  base::UmaHistogramEnumeration(metric_name, new_value);
}

// Passed through the cloud upload and open flow. Accessed as a `unique_ptr` or
// a SafeRef. Log metrics through this class. Track the state of each metric in
// the flow and detect inconsistencies.
class CloudOpenMetrics {
 public:
  explicit CloudOpenMetrics(CloudProvider cloud_provider, size_t file_count);
  ~CloudOpenMetrics();

  // Not copyable. Create a SafeRef instead.
  CloudOpenMetrics(const CloudOpenMetrics&) = delete;
  CloudOpenMetrics& operator=(const CloudOpenMetrics&) = delete;

  // Not movable. Move the `unique_ptr` owning `CloudOpenMetrics` instead.
  CloudOpenMetrics(const CloudOpenMetrics&&) = delete;
  CloudOpenMetrics& operator=(CloudOpenMetrics&&) = delete;

  // Log the `value` for the CopyError metric.
  void LogCopyError(base::File::Error value);

  // Log the `value` for the MoveError metric.
  void LogMoveError(base::File::Error value);

  // Log the `value` for the DriveOpenError metric.
  void LogGoogleDriveOpenError(OfficeDriveOpenErrors value);

  // Log the `value` for the OneDriveOpenError metric.
  void LogOneDriveOpenError(OfficeOneDriveOpenErrors value);

  // Log the `value` for the SourceVolume metric.
  void LogSourceVolume(OfficeFilesSourceVolume value);

  // Log the `value` for the TaskResult metric.
  void LogTaskResult(OfficeTaskResult value);

  // Log the `value` for the TransferRequired metric.
  void LogTransferRequired(OfficeFilesTransferRequired value);

  // Log the `value` for the UploadResult metric.
  void LogUploadResult(OfficeFilesUploadResult value);

  // Updates the cloud provider for the cloud upload flow.
  void set_cloud_provider(CloudProvider cloud_provider);

  base::SafeRef<CloudOpenMetrics> GetSafeRef() const;

  // For testing.
  base::WeakPtr<CloudOpenMetrics> GetWeakPtr();

 private:
  // `DumpWithoutCrashing()` using a unique key-value pair representing the last
  // seen inconsistency.
  void DumpState();

  // Print debug information about the detected inconsistency and every metric.
  // If `immediately_dump`, `DumpState()` with a key-value pair set representing
  // the inconsistency, otherwise set `delayed_dump_`.
  template <typename MetricType>
  void OnInconsistencyFound(Metric<MetricType>& metric,
                            bool immediately_dump = true);

  // Expect that the `metric` is not logged. Otherwise update the state and
  // call `OnInconsistencyFound()` with `immediately_dump` as false.
  template <typename MetricType>
  void ExpectNotLogged(Metric<MetricType>& metric);

  // Expect that the `metric` metric is logged with a value. Otherwise update
  // the state and call `OnInconsistencyFound()` with `immediately_dump` as
  // false.
  template <typename MetricType>
  void ExpectLogged(Metric<MetricType>& metric);

  // Update the `metric` state to `kWrongValueLogged` and call
  // `OnInconsistencyFound()` with `immediately_dump` as false.
  template <typename MetricType>
  void SetWrongValueLogged(Metric<MetricType>& metric);

  // Check metric consistency and update metric states as required.
  void CheckForInconsistencies(
      Metric<base::File::Error>& copy_error,
      Metric<base::File::Error>& move_error,
      Metric<OfficeDriveOpenErrors>& drive_open_error,
      Metric<OfficeOneDriveOpenErrors>& one_drive_open_error,
      Metric<OfficeFilesSourceVolume>& source_volume,
      Metric<OfficeTaskResult>& task_result,
      Metric<OfficeFilesTransferRequired>& transfer_required,
      Metric<OfficeFilesUploadResult>& upload_result);

  // Log the `value` to the metric corresponding to the `cloud_provider_`. If
  // there is an inconsistency, call `OnInconsistencyFound()`.
  template <typename MetricType>
  void LogAndCheckForInconsistency(Metric<MetricType>& drive_metric,
                                   Metric<MetricType>& one_drive_metric,
                                   MetricType value);

  bool multiple_files_;
  // Whether to `DumpState()` at the end of the destructor.
  bool delayed_dump_ = false;
  // The last detected inconsistent metric name to use when dumping.
  std::string inconsistent_metric_name_;
  // The last detected inconsistent metric state to use when dumping.
  MetricState inconsistent_state_;
  CloudProvider cloud_provider_;
  Metric<base::File::Error> drive_copy_error_;
  Metric<base::File::Error> one_drive_copy_error_;
  Metric<base::File::Error> drive_move_error_;
  Metric<base::File::Error> one_drive_move_error_;
  Metric<OfficeDriveOpenErrors> drive_open_error_;
  Metric<OfficeOneDriveOpenErrors> one_drive_open_error_;
  Metric<OfficeFilesSourceVolume> drive_source_volume_;
  Metric<OfficeFilesSourceVolume> one_drive_source_volume_;
  Metric<OfficeTaskResult> drive_task_result_;
  Metric<OfficeTaskResult> one_drive_task_result_;
  Metric<OfficeFilesTransferRequired> drive_transfer_required_;
  Metric<OfficeFilesTransferRequired> one_drive_transfer_required_;
  Metric<OfficeFilesUploadResult> drive_upload_result_;
  Metric<OfficeFilesUploadResult> one_drive_upload_result_;
  base::WeakPtrFactory<CloudOpenMetrics> weak_ptr_factory_{this};
};

}  // namespace ash::cloud_upload

#endif  // CHROME_BROWSER_UI_WEBUI_ASH_CLOUD_UPLOAD_CLOUD_OPEN_METRICS_H_