// Copyright 2018 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 "ui/latency/stream_analyzer.h"

#include "base/time/time.h"
#include "testing/gtest/include/gtest/gtest.h"

#include "ui/latency/frame_metrics_test_common.h"

namespace ui {
namespace frame_metrics {
namespace {

class StreamAnalyzerTest : public testing::Test {
 public:
  StreamAnalyzerTest() { NewAnalyzer(10, {2, 7, 10}); }

  void SetUp() override {}

  StreamAnalyzer* analyzer() { return analyzer_.get(); }

  void NewAnalyzer(size_t window_size, std::vector<uint32_t> thresholds) {
    shared_client_.max_window_size = window_size;
    for (auto& t : thresholds) {
      t *= kFixedPointMultiplier;
    }
    thresholds_ = std::move(thresholds);
    std::unique_ptr<TestHistogram> histogram =
        std::make_unique<TestHistogram>();
    histogram_ = histogram.get();
    analyzer_ = std::make_unique<StreamAnalyzer>(
        &client_, &shared_client_, thresholds_, std::move(histogram));
  }

 protected:
  size_t window_size;
  TestStreamAnalyzerClient client_;
  SharedWindowedAnalyzerClient shared_client_;
  std::vector<uint32_t> thresholds_;
  TestHistogram* histogram_;
  std::unique_ptr<StreamAnalyzer> analyzer_;
};

TEST_F(StreamAnalyzerTest, AllResultsTheSame) {
  // Try adding a single sample vs. multiple samples.
  for (size_t samples : {1u, 100u}) {
    // A power of 2 sweep for both the value and weight dimensions.
    for (uint64_t value = 1; value < 0x100000000ULL; value *= 2) {
      // Adding too many samples can result in overflow when multiplied by the
      // weight. Divide by samples to avoid overflow.
      for (uint64_t weight = 1; weight < 0x100000000ULL / samples;
           weight *= 2) {
        analyzer()->Reset();
        AddSamplesHelper(analyzer(), value, weight, samples);
        uint64_t expected_value =
            value * TestStreamAnalyzerClient::result_scale;
        EXPECT_EQ(expected_value, analyzer_->ComputeMean());
        EXPECT_EQ(expected_value, analyzer_->ComputeRMS());
        EXPECT_NEAR_SMR(analyzer_->ComputeSMR(), expected_value, weight);
        EXPECT_DOUBLE_EQ(0, analyzer_->ComputeStdDev());
        EXPECT_NEAR_VARIANCE_OF_ROOT(0, analyzer_->ComputeVarianceOfRoots(),
                                     expected_value, weight);

        // Verify values are forwarded to the WindowedAnalyzer.
        EXPECT_EQ(expected_value, analyzer_->window().ComputeWorstMean().value);
        EXPECT_EQ(expected_value, analyzer_->window().ComputeWorstRMS().value);
        EXPECT_NEAR_SMR(expected_value,
                        analyzer_->window().ComputeWorstSMR().value, weight);
      }
    }
  }

  // All min/max combinations of value and weight.
  for (uint64_t value : {0u, 0xFFFFFFFFu}) {
    for (uint64_t weight : {1u, 0xFFFFFFFFu}) {
      const size_t kSamplesToAdd = weight == 1 ? 100 : 1;
      analyzer()->Reset();
      AddSamplesHelper(analyzer(), value, weight, kSamplesToAdd);

      // TestWindowedAnalyzerClient scales the result by 2.
      uint64_t expected_value = value * TestStreamAnalyzerClient::result_scale;
      // Makes sure our precision is good enough.
      EXPECT_EQ(expected_value, analyzer_->ComputeMean());
      EXPECT_EQ(expected_value, analyzer_->ComputeRMS());
      EXPECT_NEAR_SMR(expected_value, analyzer_->ComputeSMR(), weight);
      EXPECT_DOUBLE_EQ(0, analyzer_->ComputeStdDev());
      EXPECT_NEAR_VARIANCE_OF_ROOT(0, analyzer_->ComputeVarianceOfRoots(),
                                   expected_value, weight);

      // Verify values are forwarded to the WindowedAnalyzer.
      EXPECT_EQ(expected_value, analyzer_->window().ComputeWorstMean().value);
      EXPECT_EQ(expected_value, analyzer_->window().ComputeWorstRMS().value);
      EXPECT_NEAR_SMR(expected_value,
                      analyzer_->window().ComputeWorstSMR().value, weight);
    }
  }
}

// This applies a pattern of 2 values that are easy to calculate the expected
// results for. It verifies the mean, rms, smr, standard deviation,
// variance of the roots, and thresholds are calculated properly.
// This doesn't check histogram or windowed analyzer related values since they
// are tested separately and other unit tests verify their interactions
// with StreamAnalyzer.
TEST_F(StreamAnalyzerTest, AllResultsDifferent) {
  const uint32_t kSampleWeight = 100;

  const std::vector<uint32_t> pattern49 = {4, 9, 4, 9, 4, 9};
  const std::vector<uint32_t> pattern4 = {4, 4, 4, 4, 4, 4};
  const std::vector<uint32_t> pattern9 = {9, 9, 9, 9, 9, 9};

  // Calculate the expected values for an equal number of 4's and 9's.
  const double expected_mean = (4 + 9) * .5 * kFixedPointMultiplier *
                               TestStreamAnalyzerClient::result_scale;
  const double expected_rms = std::sqrt((16 + 81) * .5) *
                              kFixedPointMultiplier *
                              TestStreamAnalyzerClient::result_scale;
  const double mean_root = (2 + 3) * .5;
  const double expected_smr = mean_root * mean_root * kFixedPointMultiplier *
                              TestStreamAnalyzerClient::result_scale;
  const double expected_std_dev = (9 - 4) * .5 * kFixedPointMultiplier *
                                  TestStreamAnalyzerClient::result_scale;
  const double std_dev_of_roots = (3 - 2) * .5;
  const double expected_variance_of_roots =
      std_dev_of_roots * std_dev_of_roots * kFixedPointMultiplier *
      TestStreamAnalyzerClient::result_scale;

  std::vector<ThresholdResult> thresholds;

  // Alternate 4 and 9.
  for (size_t i = 0; i < 1000; i++) {
    AddPatternHelper(&shared_client_, analyzer(), pattern49, kSampleWeight);
    EXPECT_DOUBLE_EQ(expected_mean, analyzer_->ComputeMean());
    EXPECT_NEAR_SMR(expected_smr, analyzer_->ComputeSMR(), kSampleWeight);
    EXPECT_DOUBLE_EQ(expected_rms, analyzer_->ComputeRMS());
    EXPECT_DOUBLE_EQ(expected_std_dev, analyzer_->ComputeStdDev());
    EXPECT_DOUBLE_EQ(expected_variance_of_roots,
                     analyzer_->ComputeVarianceOfRoots());
  }
  thresholds = analyzer_->ComputeThresholds();
  ASSERT_EQ(3u, thresholds.size());
  EXPECT_EQ(client_.TransformResult(thresholds_[0]), thresholds[0].threshold);
  EXPECT_EQ(client_.TransformResult(thresholds_[1]), thresholds[1].threshold);
  EXPECT_EQ(client_.TransformResult(thresholds_[2]), thresholds[2].threshold);
  EXPECT_EQ(1.0, thresholds[0].ge_fraction);
  EXPECT_EQ(0.5, thresholds[1].ge_fraction);
  EXPECT_EQ(0.0, thresholds[2].ge_fraction);

  // 4's then 9's.
  analyzer()->Reset();
  for (size_t i = 0; i < 500; i++) {
    AddPatternHelper(&shared_client_, analyzer(), pattern4, kSampleWeight);
  }
  for (size_t i = 0; i < 500; i++) {
    AddPatternHelper(&shared_client_, analyzer(), pattern9, kSampleWeight);
  }
  thresholds = analyzer_->ComputeThresholds();
  EXPECT_DOUBLE_EQ(expected_mean, analyzer_->ComputeMean());
  EXPECT_NEAR_SMR(expected_smr, analyzer_->ComputeSMR(), kSampleWeight);
  EXPECT_DOUBLE_EQ(expected_rms, analyzer_->ComputeRMS());
  EXPECT_DOUBLE_EQ(expected_std_dev, analyzer_->ComputeStdDev());
  EXPECT_DOUBLE_EQ(expected_variance_of_roots,
                   analyzer_->ComputeVarianceOfRoots());
  EXPECT_EQ(client_.TransformResult(thresholds_[0]), thresholds[0].threshold);
  EXPECT_EQ(client_.TransformResult(thresholds_[1]), thresholds[1].threshold);
  EXPECT_EQ(client_.TransformResult(thresholds_[2]), thresholds[2].threshold);
  EXPECT_EQ(1.0, thresholds[0].ge_fraction);
  EXPECT_EQ(0.5, thresholds[1].ge_fraction);
  EXPECT_EQ(0.0, thresholds[2].ge_fraction);

  // 9's then 4's.
  analyzer()->Reset();
  for (size_t i = 0; i < 500; i++) {
    AddPatternHelper(&shared_client_, analyzer(), pattern9, kSampleWeight);
  }
  for (size_t i = 0; i < 500; i++) {
    AddPatternHelper(&shared_client_, analyzer(), pattern4, kSampleWeight);
  }
  thresholds = analyzer_->ComputeThresholds();
  EXPECT_DOUBLE_EQ(expected_mean, analyzer_->ComputeMean());
  EXPECT_NEAR_SMR(expected_smr, analyzer_->ComputeSMR(), kSampleWeight);
  EXPECT_DOUBLE_EQ(expected_rms, analyzer_->ComputeRMS());
  EXPECT_DOUBLE_EQ(expected_std_dev, analyzer_->ComputeStdDev());
  EXPECT_DOUBLE_EQ(expected_variance_of_roots,
                   analyzer_->ComputeVarianceOfRoots());
  EXPECT_EQ(client_.TransformResult(thresholds_[0]), thresholds[0].threshold);
  EXPECT_EQ(client_.TransformResult(thresholds_[1]), thresholds[1].threshold);
  EXPECT_EQ(client_.TransformResult(thresholds_[2]), thresholds[2].threshold);
  EXPECT_EQ(1.0, thresholds[0].ge_fraction);
  EXPECT_EQ(0.5, thresholds[1].ge_fraction);
  EXPECT_EQ(0.0, thresholds[2].ge_fraction);
}

TEST_F(StreamAnalyzerTest, SamplesForwardedToHistogram) {
  const uint32_t kSampleWeight = 123;
  const std::vector<uint32_t> pattern = {4, 9, 16, 25, 36, 49};
  AddPatternHelper(&shared_client_, analyzer(), pattern, kSampleWeight);
  std::vector<TestHistogram::ValueWeightPair> samples(
      histogram_->GetAndResetAllAddedSamples());
  ASSERT_EQ(pattern.size(), samples.size());
  for (size_t i = 0; i < samples.size(); i++) {
    EXPECT_EQ(pattern[i] * kFixedPointMultiplier, samples[i].value);
    EXPECT_EQ(kSampleWeight, samples[i].weight);
  }
}

TEST_F(StreamAnalyzerTest, PercentilesModifiedByClient) {
  double result0 = 7;
  double result1 = 11;
  histogram_->SetResults({{result0, result1}});
  PercentileResults results = analyzer()->ComputePercentiles();
  EXPECT_EQ(client_.TransformResult(result0), results.values[0]);
  EXPECT_EQ(client_.TransformResult(result1), results.values[1]);
}

// StreamAnalyzerNaive is a subset of stream analyzer that only uses single
// precision floating point accumulators and can accumulate error.
// This is used to verify patterns that accumulate error, so we can then verify
// those patterns don't result in acculated error in the actual implementation.
struct StreamAnalyzerNaive {
  void AddSample(uint32_t value,
                 uint32_t weight,
                 uint64_t weighted_value,
                 uint64_t weighted_root,
                 const Accumulator96b& weighted_square) {
    accumulator_ += static_cast<double>(weight) * value;
    root_accumulator_ += static_cast<double>(weight) * std::sqrt(value);
    square_accumulator_ += static_cast<double>(weight) * value * value;
    total_weight_ += weight;
  }

  double ComputeMean() {
    return client_.TransformResult(accumulator_ / total_weight_);
  }
  double ComputeRMS() {
    return client_.TransformResult(
        std::sqrt(square_accumulator_ / total_weight_));
  }
  double ComputeSMR() {
    double mean_root = root_accumulator_ / total_weight_;
    return client_.TransformResult(mean_root * mean_root);
  }

  float total_weight_ = 0;
  float accumulator_ = 0;
  float root_accumulator_ = 0;
  float square_accumulator_ = 0;

  TestStreamAnalyzerClient client_;
};

// Unlike the WindowedAnalyzer, there aren't patterns of input that would
// affect the precision of our results very much with double precision floating
// point accumulators. This is because we aren't subtracting values like the
// WindowedAnalyzer does. Nevertheless, there can be issues if the accumulators
// are only single precision.
TEST_F(StreamAnalyzerTest, Precision) {
  StreamAnalyzerNaive naive_analyzer;

  uint32_t large_value = 20 * base::TimeTicks::kMicrosecondsPerSecond;
  uint32_t large_weight = large_value;
  size_t large_sample_count = 1;
  AddSamplesHelper(&naive_analyzer, large_value, large_weight,
                   large_sample_count);
  AddSamplesHelper(analyzer(), large_value, large_weight, large_sample_count);

  uint32_t small_value = 1 * base::TimeTicks::kMicrosecondsPerMillisecond;
  uint32_t small_weight = small_value;
  size_t small_sample_count = 60 * 60 * 60;  // 1hr of 60Hz frames.
  AddSamplesHelper(&naive_analyzer, small_value, small_weight,
                   small_sample_count);
  AddSamplesHelper(analyzer(), small_value, small_weight, small_sample_count);

  double total_weight = static_cast<double>(large_sample_count) * large_weight +
                        static_cast<double>(small_sample_count) * small_weight;

  double large_value_f = large_value;
  double small_value_f = small_value;

  double expected_mean = client_.TransformResult(
      (large_value_f * large_weight +
       small_sample_count * small_value_f * small_weight) /
      total_weight);
  EXPECT_ABS_LT(expected_mean * .001,
                expected_mean - naive_analyzer.ComputeMean());
  EXPECT_DOUBLE_EQ(expected_mean, analyzer_->ComputeMean());

  double large_value_squared = large_value_f * large_value_f * large_weight;
  double small_value_squared = small_value_f * small_value_f * small_weight;
  double mean_square =
      (large_value_squared + small_sample_count * small_value_squared) /
      total_weight;
  double expected_rms = client_.TransformResult(std::sqrt(mean_square));
  EXPECT_ABS_LT(expected_rms * .001,
                expected_rms - naive_analyzer.ComputeRMS());
  EXPECT_DOUBLE_EQ(expected_rms, analyzer_->ComputeRMS());

  double large_value_root = std::sqrt(large_value_f) * large_weight;
  double small_value_root = std::sqrt(small_value_f) * small_weight;
  double mean_root =
      (large_value_root + small_sample_count * small_value_root) / total_weight;
  double expected_smr = client_.TransformResult(mean_root * mean_root);
  EXPECT_ABS_LT(expected_smr * .001,
                expected_smr - naive_analyzer.ComputeSMR());
  EXPECT_NEAR_SMR(expected_smr, analyzer_->ComputeSMR(), 1);
}

}  // namespace
}  // namespace frame_metrics
}  // namespace ui
