// Copyright 2020 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/average_lag_tracking_manager.h"

#include <algorithm>
#include <memory>

#include "base/test/metrics/histogram_tester.h"
#include "components/viz/common/frame_timing_details.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/latency/latency_info.h"

namespace cc {
namespace {

using base::Bucket;
using testing::ElementsAre;

// Helper for TimeTicks usage
base::TimeTicks MillisecondsToTimeTicks(float t_ms) {
  return base::TimeTicks() + base::TimeDelta::FromMilliseconds(t_ms);
}

// Helper for FrameTimingDetails usage in DidPresentCompositorFrame
viz::FrameTimingDetails PrepareFrameDetails(base::TimeTicks swap_time,
                                            base::TimeTicks presentation_time) {
  gfx::SwapTimings timings;
  timings.swap_start = swap_time;
  viz::FrameTimingDetails details;
  details.presentation_feedback.timestamp = presentation_time;
  details.swap_timings = timings;
  return details;
}

class AverageLagTrackingManagerTest : public testing::Test {
 protected:
  AverageLagTrackingManagerTest() = default;

  void SetUp() override { ResetHistograms(); }

  void ResetHistograms() {
    histogram_tester_ = std::make_unique<base::HistogramTester>();
  }

  // Creates a scroll event each |scroll_rate| (in ms) of |scroll_delta| px.
  // Collect events at the expected |gpu_swap_times|.
  void SimulateConstantScroll(const std::vector<unsigned int>& gpu_swap_times,
                              float scroll_delta,
                              unsigned int scroll_rate) {
    if (gpu_swap_times.size() == 0 || gpu_swap_times[0] < scroll_rate)
      return;

    // Creates 1st frame with scroll begin
    std::vector<ui::LatencyInfo> events(gpu_swap_times[0] / scroll_rate);
    base::TimeTicks event_time = MillisecondsToTimeTicks(scroll_rate);
    events[0] = PrepareScrollEvent(AverageLagTracker::EventType::ScrollBegin,
                                   event_time, 0, scroll_delta);
    for (size_t i = 1; i < events.size(); i++) {
      event_time += base::TimeDelta::FromMilliseconds(scroll_rate);
      events[i] = PrepareScrollEvent(AverageLagTracker::EventType::ScrollUpdate,
                                     event_time, i, scroll_delta);
    }
    average_lag_tracking_manager_.CollectScrollEventsFromFrame(0, events);

    // Creates remaining frames
    for (size_t frame = 1; frame < gpu_swap_times.size(); frame++) {
      unsigned int time_delta =
          gpu_swap_times[frame] - gpu_swap_times[frame - 1];
      events = std::vector<ui::LatencyInfo>(time_delta / scroll_rate);
      for (size_t i = 0; i < events.size(); i++) {
        event_time += base::TimeDelta::FromMilliseconds(scroll_rate);
        events[i] =
            PrepareScrollEvent(AverageLagTracker::EventType::ScrollUpdate,
                               event_time, i, scroll_delta);
      }
      average_lag_tracking_manager_.CollectScrollEventsFromFrame(frame, events);
    }
  }

  // Prepares a ui::LatencyInfo object for a ScrollEvent
  ui::LatencyInfo PrepareScrollEvent(AverageLagTracker::EventType event_type,
                                     base::TimeTicks event_time,
                                     int trace_id,
                                     float delta,
                                     float predicted_delta = 0) {
    ui::LatencyInfo info;
    info.set_trace_id(trace_id);
    info.set_source_event_type(ui::SourceEventType::TOUCH);

    info.AddLatencyNumberWithTimestamp(
        event_type == AverageLagTracker::EventType::ScrollBegin
            ? ui::INPUT_EVENT_LATENCY_FIRST_SCROLL_UPDATE_ORIGINAL_COMPONENT
            : ui::INPUT_EVENT_LATENCY_SCROLL_UPDATE_ORIGINAL_COMPONENT,
        event_time);

    info.AddLatencyNumberWithTimestamp(
        ui::INPUT_EVENT_LATENCY_SCROLL_UPDATE_LAST_EVENT_COMPONENT, event_time);

    info.set_scroll_update_delta(delta);
    info.set_predicted_scroll_update_delta(
        predicted_delta != 0 ? predicted_delta : delta);

    return info;
  }

  AverageLagTrackingManager average_lag_tracking_manager_;
  std::unique_ptr<base::HistogramTester> histogram_tester_;
};

// Simulate a simple situation that events at every 10ms and start at t=15ms,
// frame swaps at every 10ms too and start at t=20ms and test we record one
// UMA for ScrollUpdate in one second. Tests using CollectScrollEventAtFrame
// (1 event per collection)
TEST_F(AverageLagTrackingManagerTest, OneSecondInterval) {
  base::TimeTicks event_time = MillisecondsToTimeTicks(5);
  base::TimeTicks gpu_swap_time = MillisecondsToTimeTicks(10);
  base::TimeTicks presentation_time = MillisecondsToTimeTicks(13);
  float scroll_delta = 10;
  int frame_id = 1;

  // ScrollBegin
  event_time += base::TimeDelta::FromMilliseconds(10);  // 15ms
  gpu_swap_time += base::TimeDelta::FromMilliseconds(10);      // 20ms
  presentation_time += base::TimeDelta::FromMilliseconds(10);  // 23ms
  ui::LatencyInfo evt = PrepareScrollEvent(
      AverageLagTracker::EventType::ScrollBegin, event_time, 1, scroll_delta);
  average_lag_tracking_manager_.CollectScrollEventsFromFrame(
      frame_id, std::vector<ui::LatencyInfo>{evt});
  average_lag_tracking_manager_.DidPresentCompositorFrame(
      frame_id, PrepareFrameDetails(gpu_swap_time, presentation_time));

  // Send 101 ScrollUpdate events to verify that there is 1 AverageLag record
  // per 1 second.
  const int kUpdates = 101;
  for (int i = 0; i < kUpdates; i++) {
    event_time += base::TimeDelta::FromMilliseconds(10);
    gpu_swap_time += base::TimeDelta::FromMilliseconds(10);
    presentation_time += base::TimeDelta::FromMilliseconds(10);
    // First 50 has positive delta, others negative delta.
    const int sign = (i < kUpdates / 2) ? 1 : -1;

    evt = PrepareScrollEvent(AverageLagTracker::EventType::ScrollUpdate,
                             event_time, 1, sign * scroll_delta);
    average_lag_tracking_manager_.CollectScrollEventsFromFrame(
        frame_id, std::vector<ui::LatencyInfo>{evt});
    average_lag_tracking_manager_.DidPresentCompositorFrame(
        frame_id, PrepareFrameDetails(gpu_swap_time, presentation_time));
  }

  // ScrollBegin report_time is at 20ms, so the next ScrollUpdate report_time is
  // at 1020ms. The last event_time that finish this report should be later than
  // 1020ms.
  EXPECT_EQ(event_time, MillisecondsToTimeTicks(1025));
  EXPECT_EQ(gpu_swap_time, MillisecondsToTimeTicks(1030));
  EXPECT_EQ(presentation_time, MillisecondsToTimeTicks(1033));

  // ScrollBegin AverageLag are the area between the event original component
  // (time=15ms, delta=10px) to the gpu swap time (time=20ms, expect finger
  // position at delta=15px). The AverageLag scaled to 1 second is
  // (0.5*(10px+15px)*5ms)/5ms = 12.5px.
  EXPECT_THAT(histogram_tester_->GetAllSamples(
                  "Event.Latency.ScrollBegin.Touch.AverageLag"),
              ElementsAre(Bucket(12, 1)));

  // Using the presentation time (25ms) instead of gpu swap (20ms) the expected
  // finger position is delta = 16px. Then (0.5*(10px+18px)*10ms)/10ms = 14px.
  // UmaHistogramCounts1000's binning will round it to 12.
  EXPECT_THAT(histogram_tester_->GetAllSamples(
                  "Event.Latency.ScrollBegin.Touch.AverageLagPresentation"),
              ElementsAre(Bucket(14, 1)));

  // This ScrollUpdate AverageLag are calculated as the finger uniformly scroll
  // 10px each frame. For scroll up/down frame, the Lag at the last frame swap
  // is 5px, and Lag at this frame swap is 15px. For the one changing direction,
  // the Lag is from 5 to 10 and down to 5 again. So total LagArea is 99 * 100,
  // plus 75. the AverageLag in 1 second is 9.975px.
  EXPECT_THAT(histogram_tester_->GetAllSamples(
                  "Event.Latency.ScrollUpdate.Touch.AverageLag"),
              ElementsAre(Bucket(9, 1)));
  EXPECT_THAT(
      histogram_tester_->GetAllSamples(
          "Event.Latency.ScrollUpdate.Touch.AverageLag.PredictionPositive"),
      ElementsAre(Bucket(0, 1)));
  histogram_tester_->ExpectTotalCount(
      "Event.Latency.ScrollUpdate.Touch.AverageLag.PredictionNegative", 0);

  // As the presentation times are at 80% of the gap between 2 scroll events,
  // the Lag Area between 2 frames is define by the trapezoids: (time=event-2,
  // delta=8px), (time=event, delta=10px), (time=event+8, delta=18). This makes
  // 99 trapezois with an area of 0.5*2*(8+10) + 0.5*8*(10+18) = 130px.
  // For scroll up/down frame, the Lag at the last frame swap is 2px, and Lag
  // at this frame swap is 12px. For the one changing direction, the Lag is
  // from 8 to 10 and down to 8 again. So total LagArea is 99 * 130, plus
  // 0.5*8*(10+2) + 0.5*2*(8+10) = 66. This makes 12,936. Scaled by 1 sec
  // and binned: 12.
  EXPECT_THAT(histogram_tester_->GetAllSamples(
                  "Event.Latency.ScrollUpdate.Touch.AverageLagPresentation"),
              ElementsAre(Bucket(12, 1)));
  EXPECT_THAT(histogram_tester_->GetAllSamples(
                  "Event.Latency.ScrollUpdate.Touch.AverageLagPresentation."
                  "PredictionPositive"),
              ElementsAre(Bucket(0, 1)));
  histogram_tester_->ExpectTotalCount(
      "Event.Latency.ScrollUpdate.Touch.AverageLagPresentation."
      "PredictionNegative",
      0);

  ResetHistograms();

  // Send another ScrollBegin to end the unfinished ScrollUpdate report.
  event_time += base::TimeDelta::FromMilliseconds(10);
  gpu_swap_time += base::TimeDelta::FromMilliseconds(10);
  presentation_time += base::TimeDelta::FromMilliseconds(10);
  evt = PrepareScrollEvent(AverageLagTracker::EventType::ScrollBegin,
                           event_time, 1, scroll_delta);
  average_lag_tracking_manager_.CollectScrollEventsFromFrame(
      frame_id, std::vector<ui::LatencyInfo>{evt});
  average_lag_tracking_manager_.DidPresentCompositorFrame(
      frame_id, PrepareFrameDetails(gpu_swap_time, presentation_time));

  // The last ScrollUpdate's lag is 8.75px and truncated to 8.
  EXPECT_THAT(histogram_tester_->GetAllSamples(
                  "Event.Latency.ScrollUpdate.Touch.AverageLag"),
              ElementsAre(Bucket(8, 1)));
  EXPECT_THAT(
      histogram_tester_->GetAllSamples(
          "Event.Latency.ScrollUpdate.Touch.AverageLag.PredictionPositive"),
      ElementsAre(Bucket(0, 1)));
  histogram_tester_->ExpectTotalCount(
      "Event.Latency.ScrollUpdate.Touch.AverageLag.PredictionNegative", 0);
}

// This test creates 3 frames in order to check the submission of ScrollBegin
// and ScrollUpdate events sent using CollectScrollEventsAtFrame (multiple
// events per collection)
TEST_F(AverageLagTrackingManagerTest, MultipleEventsInSameFrame) {
  std::vector<unsigned int> gpu_swap_times = {400, 1400, 1600};
  std::vector<unsigned int> presentation_times = {500, 1500, 1700};
  SimulateConstantScroll(gpu_swap_times, 10, 100);
  for (size_t frame = 0; frame < gpu_swap_times.size(); frame++) {
    average_lag_tracking_manager_.DidPresentCompositorFrame(
        frame, PrepareFrameDetails(
                   MillisecondsToTimeTicks(gpu_swap_times[frame]),
                   MillisecondsToTimeTicks(presentation_times[frame])));
  }

  // As the first frame is the ScrollBegin frame, the average lag is, using the
  // gpu swap time, 0.5*(10 + 40) * 30 / 30 = 25. But UmaHistogramCounts1000's
  // binning will round it to 23.
  EXPECT_THAT(histogram_tester_->GetAllSamples(
                  "Event.Latency.ScrollBegin.Touch.AverageLag"),
              ElementsAre(Bucket(23, 1)));

  // As the first frame is the ScrollBegin frame, the average lag is, using the
  // presentation time, 0.5*(10 + 50) * 40 / 40 = 30. But
  // UmaHistogramCounts1000's binning will round it to 29.
  EXPECT_THAT(histogram_tester_->GetAllSamples(
                  "Event.Latency.ScrollBegin.Touch.AverageLagPresentation"),
              ElementsAre(Bucket(29, 1)));

  // Only the ScrollUpdate events from frame 2 are sent (as the frame 3 is
  // waiting for the next frame for sumission).
  // As there is a scroll update right at the same time as the frame submission,
  // using gpu swap time, frame 2 starts with 0 lag at 0.4s and finishes with
  // 100 at 1.4, thus: 0.5 * (0 + 100) / 2 = 50. It gets into the same bin as
  // 47.
  EXPECT_THAT(histogram_tester_->GetAllSamples(
                  "Event.Latency.ScrollUpdate.Touch.AverageLag"),
              ElementsAre(Bucket(47, 1)));

  // Only the ScrollUpdate events from frame 2 are sent (as the frame 3 is
  // waiting for the next frame for sumission).
  // As there is a scroll update right at the same time as the frame submission,
  // using presentation time, frame 2 starts with 10 lag at 0.5s and finishes
  // with 110 at 1.5, thus: 0.5 * (10 + 110) / 2 = 60.
  EXPECT_THAT(histogram_tester_->GetAllSamples(
                  "Event.Latency.ScrollUpdate.Touch.AverageLagPresentation"),
              ElementsAre(Bucket(60, 1)));
}

}  // namespace
}  // namespace cc
