chromium/chromecast/base/metrics/cast_metrics_helper.h

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

#ifndef CHROMECAST_BASE_METRICS_CAST_METRICS_HELPER_H_
#define CHROMECAST_BASE_METRICS_CAST_METRICS_HELPER_H_

#include <memory>
#include <string>

#include "base/containers/flat_map.h"
#include "base/functional/callback.h"
#include "base/memory/ref_counted.h"
#include "base/no_destructor.h"
#include "base/task/sequenced_task_runner.h"
#include "base/time/time.h"
#include "base/values.h"

namespace base {
class SequencedTaskRunner;
class TickClock;
}  // namespace base

namespace chromecast {
namespace metrics {

// Helper class for tracking complex metrics. This particularly includes
// playback metrics that span events across time, such as "time from app launch
// to video being rendered."
// Currently, browser startup code should instantiate this once; it can be
// accessed thereafter through GetInstance. It's not deleted since it may be
// called during teardown of other global objects.
// TODO(halliwell,guohuideng): convert to mojo service, eliminate singleton
// pattern.
class CastMetricsHelper {
 public:
  enum BufferingType {
    kInitialBuffering,
    kBufferingAfterUnderrun,
    kAbortedBuffering,
  };

  using RecordActionCallback =
      base::RepeatingCallback<void(const std::string&)>;

  class MetricsSink {
   public:
    virtual ~MetricsSink() = default;

    virtual void OnAction(const std::string& action) = 0;
    virtual void OnEnumerationEvent(const std::string& name,
                                    int value, int num_buckets) = 0;
    virtual void OnTimeEvent(const std::string& name,
                             base::TimeDelta value,
                             base::TimeDelta min,
                             base::TimeDelta max,
                             int num_buckets) = 0;
  };

  // Decodes action_name/app_id/session_id/sdk_version from metrics name.
  // Return false if the metrics name is not generated from
  // EncodeAppInfoIntoMetricsName() with correct format.
  static bool DecodeAppInfoFromMetricsName(
      const std::string& metrics_name,
      std::string* action_name,
      std::string* app_id,
      std::string* session_id,
      std::string* sdk_version);

  static CastMetricsHelper* GetInstance();

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

  // This records the startup time of an app load (note: another app
  // may be running and still collecting metrics).
  virtual void DidStartLoad(const std::string& app_id);
  // This function marks the completion of a successful app load. It switches
  // metric collection to this app.
  virtual void DidCompleteLoad(const std::string& app_id,
                               const std::string& session_id);
  // This function updates the sdk version of the current active application
  virtual void UpdateSDKInfo(const std::string& sdk_version);

  // Logs UMA record for media play/pause user actions.
  virtual void LogMediaPlay();
  virtual void LogMediaPause();

  // Logs a simple UMA user action.
  // This is used as an in-place replacement of content::RecordComputedAction().
  virtual void RecordSimpleAction(const std::string& action);

  // Logs a generic event.
  virtual void RecordEventWithValue(const std::string& action, int value);

  // Logs application specific events.
  virtual void RecordApplicationEvent(const std::string& event);
  virtual void RecordApplicationEvent(const std::string& app_id,
                                      const std::string& session_id,
                                      const std::string& sdk_version,
                                      const std::string& event);
  virtual void RecordApplicationEventWithValue(const std::string& event,
                                               int value);
  virtual void RecordApplicationEventWithValue(const std::string& app_id,
                                               const std::string& session_id,
                                               const std::string& sdk_version,
                                               const std::string& event,
                                               int value);

  // Logs UMA record of the time the app made its first paint.
  virtual void LogTimeToFirstPaint();

  // Logs UMA record of the time the app pushed its first audio frame.
  virtual void LogTimeToFirstAudio();

  // Logs UMA record of the time needed to re-buffer A/V.
  virtual void LogTimeToBufferAv(BufferingType buffering_type,
                                 base::TimeDelta time);

  // Returns metrics name with app name between prefix and suffix.
  virtual std::string GetMetricsNameWithAppName(
      const std::string& prefix,
      const std::string& suffix) const;

  // Provides a MetricsSink instance to delegate UMA event logging.
  // Once the delegate interface is set, CastMetricsHelper will not log UMA
  // events internally unless SetMetricsSink(NULL) is called.
  // CastMetricsHelper can only hold one MetricsSink instance.
  // Caller retains ownership of MetricsSink.
  virtual void SetMetricsSink(MetricsSink* delegate);

  // Sets a default callback to record user action when MetricsSink is not set.
  // This function could be called multiple times (in unittests), and
  // CastMetricsHelper only honors the last one.
  virtual void SetRecordActionCallback(RecordActionCallback callback);

  // Sets an all-0's session ID for running browser tests.
  void SetDummySessionIdForTesting();

 private:
  static std::string EncodeAppInfoIntoMetricsName(
      const std::string& action_name,
      const std::string& app_id,
      const std::string& session_id,
      const std::string& sdk_version);

  friend class base::NoDestructor<CastMetricsHelper>;
  friend class CastMetricsHelperTest;
  friend class MockCastMetricsHelper;

  // |tick_clock| just provided for unit test to construct; normally it should
  // be nullptr when accessed through GetInstance.
  CastMetricsHelper(scoped_refptr<base::SequencedTaskRunner> task_runner =
                        base::SequencedTaskRunner::GetCurrentDefault(),
                    const base::TickClock* tick_clock = nullptr);
  virtual ~CastMetricsHelper();

  void LogEnumerationHistogramEvent(const std::string& name,
                                    int value, int num_buckets);
  void LogTimeHistogramEvent(const std::string& name,
                             base::TimeDelta value,
                             base::TimeDelta min,
                             base::TimeDelta max,
                             int num_buckets);
  void LogMediumTimeHistogramEvent(const std::string& name,
                                   base::TimeDelta value);
  base::Value::Dict CreateEventBase(const std::string& name);
  base::TimeTicks Now();

  const scoped_refptr<base::SequencedTaskRunner> task_runner_;
  const base::TickClock* const tick_clock_;

  // Start times for loading the next apps.
  base::flat_map<std::string /* app_id */, base::TimeTicks>
      app_load_start_times_;

  // Start time for the currently running app.
  base::TimeTicks app_start_time_;

  // Currently running app id. Used to construct histogram name.
  std::string app_id_;
  std::string session_id_;
  std::string sdk_version_;

  MetricsSink* metrics_sink_;

  bool logged_first_audio_;

  // Default RecordAction callback when metrics_sink_ is not set.
  RecordActionCallback record_action_callback_;
};

}  // namespace metrics
}  // namespace chromecast

#endif  // CHROMECAST_BASE_METRICS_CAST_METRICS_HELPER_H_