// 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.

#include "cc/metrics/predictor_jank_tracker.h"

#include <array>
#include <memory>
#include <string>
#include <vector>

#include "base/strings/string_number_conversions.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/tracing/test_trace_processor.h"
#include "base/time/time.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace cc {

class PredictorJankTrackerTest : public testing::Test {
 public:
  PredictorJankTrackerTest() = default;

  void SetUp() override {
    trace_id_ = 0;
    histogram_tester_ = std::make_unique<base::HistogramTester>();
    predictor_jank_tracker_ = std::make_unique<PredictorJankTracker>();
    base_presentation_ts_ = base::TimeTicks::Min();
  }

  void MockFrameProduction(double delta, base::TimeTicks presentation_ts) {
    predictor_jank_tracker_->ReportLatestScrollDelta(
        delta, presentation_ts, vsync_interval,
        EventMetrics::TraceId(++trace_id_));
  }

  int64_t trace_id_ = 0;
  std::unique_ptr<base::HistogramTester> histogram_tester_;
  std::unique_ptr<PredictorJankTracker> predictor_jank_tracker_;
  base::TimeTicks base_presentation_ts_;
  ::base::test::TracingEnvironment tracing_environment_;
  const char* missed_fast_histogram_name =
      "Event.Jank.ScrollUpdate.FastScroll.MissedVsync."
      "FrameAboveJankyThreshold2";
  const char* fast_histogram_name =
      "Event.Jank.ScrollUpdate.FastScroll.NoMissedVsync."
      "FrameAboveJankyThreshold2";
  const char* missed_slow_histogram_name =
      "Event.Jank.ScrollUpdate.SlowScroll.MissedVsync."
      "FrameAboveJankyThreshold2";
  const char* slow_histogram_name =
      "Event.Jank.ScrollUpdate.SlowScroll.NoMissedVsync."
      "FrameAboveJankyThreshold2";
  const char* janky_percentage_name =
      "Event.Jank.PredictorJankyFramePercentage2";
  constexpr static base::TimeDelta vsync_interval = base::Milliseconds(16);
};

TEST_F(PredictorJankTrackerTest, BasicNonMissedUpperJankCase) {
  // 50 / 10 = 5.0, > janky threshold and should be reporteed.
  // No dropped frame as frames are separated by 16ms.
  MockFrameProduction(10, base_presentation_ts_);
  MockFrameProduction(50, base_presentation_ts_ + base::Milliseconds(16));
  MockFrameProduction(10, base_presentation_ts_ + base::Milliseconds(32));

  histogram_tester_->ExpectTotalCount(fast_histogram_name, 1);
  // (50 / 10 - 1.2) * 100
  EXPECT_EQ(histogram_tester_->GetTotalSum(fast_histogram_name), 380);
}

TEST_F(PredictorJankTrackerTest, BasicNoMissedSlowUpperJankCase) {
  // 5 / 1 = 5.0, > janky threshold and should be reporteed.
  // No dropped frame as frames are separated by 16ms.
  MockFrameProduction(1, base_presentation_ts_);
  MockFrameProduction(5, base_presentation_ts_ + base::Milliseconds(16));
  MockFrameProduction(1, base_presentation_ts_ + base::Milliseconds(32));

  histogram_tester_->ExpectTotalCount(slow_histogram_name, 1);
  // (5 / 14 - 1.4) * 100
  EXPECT_EQ(histogram_tester_->GetTotalSum(slow_histogram_name), 360);
}

TEST_F(PredictorJankTrackerTest, BasicMissedSlowUpperJankCase) {
  // 5 / 1 = 5.0, > janky threshold and should be reporteed.
  // No dropped frame as frames are separated by 16ms.
  MockFrameProduction(1, base_presentation_ts_);
  MockFrameProduction(5, base_presentation_ts_ + base::Milliseconds(32));
  MockFrameProduction(1, base_presentation_ts_ + base::Milliseconds(48));

  histogram_tester_->ExpectTotalCount(missed_slow_histogram_name, 1);
  // (5 / 1 - 1.4) * 100
  EXPECT_EQ(histogram_tester_->GetTotalSum(missed_slow_histogram_name), 360);
}

TEST_F(PredictorJankTrackerTest, BasicMissedUpperJankCase) {
  // 50 / 10 = 5.0, > janky threshold and should be reporteed.
  // There are dropped frames as the first and second frames are 32 ms apart.
  MockFrameProduction(10, base_presentation_ts_);
  MockFrameProduction(50, base_presentation_ts_ + base::Milliseconds(32));
  MockFrameProduction(10, base_presentation_ts_ + base::Milliseconds(48));

  histogram_tester_->ExpectTotalCount(missed_fast_histogram_name, 1);
  // (50 / 10 - 1.2) * 100
  EXPECT_EQ(histogram_tester_->GetTotalSum(missed_fast_histogram_name), 380);
}

TEST_F(PredictorJankTrackerTest, NegativeNoJankFrame) {
  // [-10, -5, -1] is an increasing non janky sequence.
  MockFrameProduction(-10, base_presentation_ts_);
  MockFrameProduction(-5, base_presentation_ts_ + base::Milliseconds(32));
  MockFrameProduction(-1, base_presentation_ts_ + base::Milliseconds(48));

  histogram_tester_->ExpectTotalCount(missed_fast_histogram_name, 0);
}

TEST_F(PredictorJankTrackerTest, PositiveNoJankFrame) {
  // [10, 5, 1] is a decreasing non janky sequence.
  MockFrameProduction(10, base_presentation_ts_);
  MockFrameProduction(5, base_presentation_ts_ + base::Milliseconds(32));
  MockFrameProduction(1, base_presentation_ts_ + base::Milliseconds(48));

  histogram_tester_->ExpectTotalCount(missed_fast_histogram_name, 0);
}

TEST_F(PredictorJankTrackerTest, BasicNonMissedLowerJankCase) {
  // 50 / 10 = 5.0, > janky threshold and should be reporteed.
  // There are dropped frames as the first and seonc frames are 32 ms apart.
  MockFrameProduction(50, base_presentation_ts_);
  MockFrameProduction(10, base_presentation_ts_ + base::Milliseconds(16));
  MockFrameProduction(50, base_presentation_ts_ + base::Milliseconds(32));

  histogram_tester_->ExpectTotalCount(fast_histogram_name, 1);
  // (50 / 10 - 1.2) * 100
  EXPECT_EQ(histogram_tester_->GetTotalSum(fast_histogram_name), 380);
}

TEST_F(PredictorJankTrackerTest, BasicMissedLowerJankCase) {
  // 50 / 10 = 5.0, > janky threshold and should be reporteed.
  // There are dropped frames as the first and seonc frames are 32 ms apart.
  MockFrameProduction(50, base_presentation_ts_);
  MockFrameProduction(10, base_presentation_ts_ + base::Milliseconds(32));
  MockFrameProduction(50, base_presentation_ts_ + base::Milliseconds(48));

  histogram_tester_->ExpectTotalCount(missed_fast_histogram_name, 1);
  // (50 / 10 - 1.2) * 100
  EXPECT_EQ(histogram_tester_->GetTotalSum(missed_fast_histogram_name), 380);
}

TEST_F(PredictorJankTrackerTest, BasicNonMissedUpperJankCaseWithTracing) {
  base::test::TestTraceProcessor ttp;
  ttp.StartTrace("input.scrolling");
  // 50 / 10 = 5.0, > janky threshold and should be reporteed.
  // No dropped frame as frames are separated by 16ms.
  MockFrameProduction(10, base_presentation_ts_);
  MockFrameProduction(50, base_presentation_ts_ + base::Milliseconds(16));
  MockFrameProduction(11, base_presentation_ts_ + base::Milliseconds(32));

  absl::Status status = ttp.StopAndParseTrace();
  ASSERT_TRUE(status.ok()) << status.message();
  std::string query =
      R"(
      SELECT
        EXTRACT_ARG(arg_set_id,
          "scroll_predictor_metrics.prev_event_frame_value.delta_value_pixels"
        ) AS prev_delta,
        EXTRACT_ARG(arg_set_id,
          "scroll_predictor_metrics.cur_event_frame_value.delta_value_pixels"
        ) AS cur_delta,
        EXTRACT_ARG(arg_set_id,
          "scroll_predictor_metrics.next_event_frame_value.delta_value_pixels"
        ) AS next_delta
      FROM
        slice
      WHERE
        name = "PredictorJankTracker::ReportJankyFrame"
      )";
  auto result = ttp.RunQuery(query);
  ASSERT_TRUE(result.has_value()) << result.error();
  EXPECT_THAT(result.value(), ::testing::ElementsAre(
                                  std::vector<std::string>{
                                      "prev_delta", "cur_delta", "next_delta"},
                                  std::vector<std::string>{"10", "50", "11"}));
}

TEST_F(PredictorJankTrackerTest, NoReportingDirectionChange) {
  // [50, -100, 50] means the user changed their scrolling direction
  // and no predictor performance should be reported.
  MockFrameProduction(50, base_presentation_ts_);
  MockFrameProduction(-100, base_presentation_ts_ + base::Milliseconds(16));
  MockFrameProduction(50, base_presentation_ts_ + base::Milliseconds(32));

  histogram_tester_->ExpectTotalCount(missed_fast_histogram_name, 0);
  histogram_tester_->ExpectTotalCount(fast_histogram_name, 0);
  histogram_tester_->ExpectTotalCount(janky_percentage_name, 0);
}

TEST_F(PredictorJankTrackerTest, JankyFramePercentageEmitted) {
  // a sequence of 50 frames, with 10 irregular jumps means 20%
  // of the frames were janky, and should be reported since frame count
  // is more than 50.
  std::array<double, 5> pattern = {50, 50, 50, 50, 10};
  for (int i = 1; i <= 64; i++, base_presentation_ts_ += vsync_interval) {
    MockFrameProduction(pattern[i % 5], base_presentation_ts_);
  }

  histogram_tester_->ExpectTotalCount(janky_percentage_name, 1);
  EXPECT_EQ(histogram_tester_->GetTotalSum(janky_percentage_name), 18);
}

TEST_F(PredictorJankTrackerTest, JankyFramePercentageNotEmitted) {
  // a sequence of 49 frames with 20% janky jumps, but the percentage isn't
  // reported because we only report the percentage when more than 50 frames
  // exist in the sequence.
  std::array<double, 5> pattern = {50, 50, 50, 50, 10};
  for (int i = 1; i <= 63; i++, base_presentation_ts_ += vsync_interval) {
    MockFrameProduction(pattern[i % 5], base_presentation_ts_);
  }
  histogram_tester_->ExpectTotalCount(janky_percentage_name, 0);
}

TEST_F(PredictorJankTrackerTest, JankyFramePercentageEmittedTwice) {
  // Janky frames percentage should be emitted twice, 20% each
  // since we have 100 frames with 20% jank in each scroll.
  std::array<double, 5> pattern = {50, 50, 50, 50, 10};
  for (int i = 1; i <= 128; i++, base_presentation_ts_ += vsync_interval) {
    MockFrameProduction(pattern[i % 5], base_presentation_ts_);
  }
  histogram_tester_->ExpectTotalCount(janky_percentage_name, 2);
  EXPECT_EQ(histogram_tester_->GetTotalSum(janky_percentage_name), 38);
}

TEST_F(PredictorJankTrackerTest, JankyFramePercentageEmittedWhenReset) {
  // Janky sequence with 20% janky frames, reporting should happen even if
  // the scroll was reset to catch smaller scrolls and residue frames from
  // previous scrolls.
  std::array<double, 5> pattern = {50, 50, 50, 50, 10};
  for (int i = 1; i <= 64; i++, base_presentation_ts_ += vsync_interval) {
    MockFrameProduction(pattern[i % 5], base_presentation_ts_);
    if (i == 25) {
      predictor_jank_tracker_->ResetCurrentScrollReporting();
    }
  }
  histogram_tester_->ExpectTotalCount(janky_percentage_name, 1);
  EXPECT_EQ(histogram_tester_->GetTotalSum(janky_percentage_name), 18);
}

// Verify that the stdlib implementation of this metric is consistent with
// PredictorJankTracker.
TEST_F(PredictorJankTrackerTest, VerifySqlThresholds) {
  base::test::TestTraceProcessor ttp;
  ttp.StartTrace("");
  absl::Status status = ttp.StopAndParseTrace();
  ASSERT_TRUE(status.ok()) << status.message();
  std::string query =
      R"(
      INCLUDE PERFETTO MODULE chrome.scroll_jank.predictor_error;

      SELECT
        _get_slow_scroll_delta_threshold()
          AS slow_scroll_delta_threshold,
        _get_slow_scroll_janky_threshold()
          AS slow_scroll_janky_threshold,
        _get_fast_scroll_janky_threshold()
          AS fast_scroll_janky_threshold;
      )";
  auto result = ttp.RunQuery(query);
  ASSERT_TRUE(result.has_value()) << result.error();

  auto thresholds = result.value()[1];
  ASSERT_TRUE(thresholds.size() == 3);

  double slow_scroll_delta_threshold;
  base::StringToDouble(thresholds[0], &slow_scroll_delta_threshold);
  EXPECT_EQ(static_cast<float>(slow_scroll_delta_threshold),
            PredictorJankTracker::GetSlowScrollDeltaThreshold());

  double slow_scroll_janky_threshold;
  base::StringToDouble(thresholds[1], &slow_scroll_janky_threshold);
  EXPECT_EQ(static_cast<float>(slow_scroll_janky_threshold),
            PredictorJankTracker::GetSlowScrollJankyThreshold());

  double fast_scroll_janky_threshold;
  base::StringToDouble(thresholds[2], &fast_scroll_janky_threshold);
  EXPECT_EQ(static_cast<float>(fast_scroll_janky_threshold),
            PredictorJankTracker::GetFastScrollJankyThreshold());
}

TEST_F(PredictorJankTrackerTest, VerifySqlPredictorJank) {
  base::test::TestTraceProcessor ttp;
  ttp.StartTrace("input.scrolling");
  // 50 / 10 = 5.0, > janky threshold and should be reported as a fast scroll
  // threshold using the frame_janky_upper ratio. 55 / 11 = 5, > janky threshold
  // and should be reported as a fast scroll threshold using the
  // frame_janky_lower ratio.
  MockFrameProduction(10, base_presentation_ts_);
  MockFrameProduction(50, base_presentation_ts_ + base::Milliseconds(16));
  MockFrameProduction(11, base_presentation_ts_ + base::Milliseconds(32));
  MockFrameProduction(55, base_presentation_ts_ + base::Milliseconds(48));

  absl::Status status = ttp.StopAndParseTrace();
  ASSERT_TRUE(status.ok()) << status.message();
  std::string query =
      R"(
      INCLUDE PERFETTO MODULE chrome.scroll_jank.predictor_error;

      WITH predictor_metrics AS (
        SELECT
          EXTRACT_ARG(arg_set_id,
            "scroll_predictor_metrics.prev_event_frame_value.delta_value_pixels"
          ) AS prev_delta,
          EXTRACT_ARG(arg_set_id,
            "scroll_predictor_metrics.cur_event_frame_value.delta_value_pixels"
          ) AS cur_delta,
          EXTRACT_ARG(arg_set_id,
            "scroll_predictor_metrics.next_event_frame_value.delta_value_pixels"
          ) AS next_delta,
          EXTRACT_ARG(arg_set_id,
            "scroll_predictor_metrics.janky_value_pixels"
          ) AS janky_value_pixels
        FROM slice
        WHERE name = "PredictorJankTracker::ReportJankyFrame")

      SELECT
        prev_delta,
        cur_delta,
        next_delta,

        -- The jank as calculated in PredictorJankTracker.
        janky_value_pixels,

        -- Calculate the jank using the definitions in SQL.
        _get_predictor_jank(
          ABS(prev_delta),
          ABS(cur_delta),
          ABS(next_delta),
          _get_scroll_jank_threshold(
            ABS(prev_delta),
            ABS(cur_delta),
            ABS(next_delta)
          )) AS sql_janky_value_pixels
      FROM predictor_metrics
      )";
  auto result = ttp.RunQuery(query);
  ASSERT_TRUE(result.has_value()) << result.error();
  EXPECT_THAT(
      result.value(),
      ::testing::ElementsAre(
          std::vector<std::string>{"prev_delta", "cur_delta", "next_delta",
                                   "janky_value_pixels",
                                   "sql_janky_value_pixels"},
          std::vector<std::string>{"10", "50", "11", "3.34545", "3.34545"},
          std::vector<std::string>{"50", "11", "55", "3.34545", "3.34545"}));
}

}  // namespace cc
