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

#include "cc/metrics/latency_ukm_reporter.h"

#include <climits>
#include <memory>

#include "base/rand_util.h"
#include "cc/trees/ukm_manager.h"

namespace cc {

// We use a Poisson process with an exponential decay multiplier. The goal is to
// get many randomly distributed samples early during page load and initial
// interaction, then samples at an exponentially decreasing rate to effectively
// cap the number of samples. The particular parameters chosen here give roughly
// 5-10 samples in the first 100 frames, decaying to several hours between
// samples by the 40th sample. The multiplier value should be tuned to achieve a
// total sample count that avoids throttling by the UKM system.
class LatencyUkmReporter::SamplingController {
 public:
  SamplingController() = default;
  ~SamplingController() = default;

  // When a new UKM event is issued, this function should be called (once and
  // only once) by the client to determine whether that event should be recorded
  // or ignored, according to the sampling parameters. The sampling state will
  // be updated to be ready for the next UKM event.
  bool ShouldRecordNextEvent() {
    bool should_record = false;
    if (!frames_to_next_event_) {
      should_record = true;
      frames_to_next_event_ = SampleFramesToNextEvent();
    }
    DCHECK_GT(frames_to_next_event_, 0);
    --frames_to_next_event_;
    return should_record;
  }

 private:
  // The |kSampleRateMultiplier| and |kSampleDecayRate| have been set to meet
  // UKM goals for data volume.
  const double kSampleDecayRate = 1.0;
  const double kSampleRateMultiplier = 2.0;

  int SampleFramesToNextEvent() {
    // Sample from an exponential distribution to give a Poisson distribution
    // of samples per time unit, then weigh it with an exponential multiplier
    // to give a few samples in rapid succession (for frames early in the
    // page's life) then exponentially fewer as the page lives longer.
    // RandDouble() returns [0,1), but we need (0,1]. If RandDouble() is
    // uniformly random, so is 1-RandDouble(), so use it to adjust the range.
    // When RandDouble() returns 0.0, as it could, we will get a float_sample
    // of 0, causing underflow. So rejection sample until we get a positive
    // count.
    double float_sample = 0.0;
    do {
      float_sample = -(kSampleRateMultiplier *
                       std::exp(samples_so_far_ * kSampleDecayRate) *
                       std::log(1.0 - base::RandDouble()));
    } while (float_sample == 0.0);
    // float_sample is positive, so we don't need to worry about underflow.
    // After around 30 samples we will end up with a super high sample. That's
    // OK because it just means we'll stop reporting metrics for that session,
    // but we do need to be careful about overflow and NaN.
    samples_so_far_++;
    int integer_sample =
        std::isnan(float_sample)
            ? INT_MAX
            : base::saturated_cast<int>(std::ceil(float_sample));
    return integer_sample;
  }

  int samples_so_far_ = 0;
  int frames_to_next_event_ = 0;
};

LatencyUkmReporter::LatencyUkmReporter()
    : compositor_latency_sampling_controller_(
          std::make_unique<SamplingController>()),
      event_latency_sampling_controller_(
          std::make_unique<SamplingController>()) {}

LatencyUkmReporter::~LatencyUkmReporter() = default;

void LatencyUkmReporter::ReportCompositorLatencyUkm(
    CompositorFrameReporter::FrameReportType report_type,
    const std::vector<CompositorFrameReporter::StageData>& stage_history,
    const CompositorFrameReporter::ActiveTrackers& active_trackers,
    const viz::FrameTimingDetails& viz_breakdown) {
  if (ukm_manager_ &&
      compositor_latency_sampling_controller_->ShouldRecordNextEvent()) {
    ukm_manager_->RecordCompositorLatencyUKM(report_type, stage_history,
                                             active_trackers, viz_breakdown);
  }
}

void LatencyUkmReporter::ReportEventLatencyUkm(
    const std::vector<EventMetrics>& events_metrics,
    const std::vector<CompositorFrameReporter::StageData>& stage_history,
    const viz::FrameTimingDetails& viz_breakdown) {
  if (ukm_manager_ &&
      event_latency_sampling_controller_->ShouldRecordNextEvent()) {
    ukm_manager_->RecordEventLatencyUKM(events_metrics, stage_history,
                                        viz_breakdown);
  }
}

}  // namespace cc
