#include "ui/base/prediction/prediction_metrics_handler.h"
#include <string_view>
#include <utility>
#include "base/metrics/histogram.h"
#include "base/strings/strcat.h"
namespace ui {
namespace {
base::HistogramBase* GetHistogram(std::string_view name,
std::string_view suffix) {
return base::Histogram::FactoryGet(
base::StrCat({name, ".", suffix}), 1, 1000, 50,
base::HistogramBase::kUmaTargetedHistogramFlag);
}
}
PredictionMetricsHandler::PredictionMetricsHandler(std::string histogram_name)
: histogram_name_(std::move(histogram_name)),
over_prediction_histogram_(
*GetHistogram(histogram_name_, "OverPrediction")),
under_prediction_histogram_(
*GetHistogram(histogram_name_, "UnderPrediction")),
prediction_score_histogram_(
*GetHistogram(histogram_name_, "PredictionScore")),
frame_over_prediction_histogram_(
*GetHistogram(histogram_name_, "FrameOverPrediction")),
frame_under_prediction_histogram_(
*GetHistogram(histogram_name_, "FrameUnderPrediction")),
frame_prediction_score_histogram_(
*GetHistogram(histogram_name_, "FramePredictionScore")),
prediction_jitter_histogram_(
*GetHistogram(histogram_name_, "PredictionJitter")),
visual_jitter_histogram_(*GetHistogram(histogram_name_, "VisualJitter")) {
}
PredictionMetricsHandler::~PredictionMetricsHandler() = default;
void PredictionMetricsHandler::AddRealEvent(const gfx::PointF& pos,
const base::TimeTicks& time_stamp,
const base::TimeTicks& frame_time,
bool scrolling) {
if (!events_queue_.empty() && time_stamp <= events_queue_.back().time_stamp) {
if (time_stamp < events_queue_.front().time_stamp)
return;
bool event_exists = false;
for (uint64_t i = 0; i < events_queue_.size() && !event_exists; ++i) {
if (events_queue_[i].time_stamp == time_stamp)
event_exists = true;
}
DCHECK(event_exists);
return;
}
EventData e;
if (scrolling)
e.pos = gfx::PointF(0, pos.y());
else
e.pos = pos;
e.time_stamp = time_stamp;
e.frame_time = frame_time;
events_queue_.push_back(e);
}
void PredictionMetricsHandler::AddPredictedEvent(
const gfx::PointF& pos,
const base::TimeTicks& time_stamp,
const base::TimeTicks& frame_time,
bool scrolling) {
if (events_queue_.empty()) {
return;
}
if (time_stamp < events_queue_.front().time_stamp)
return;
bool needs_sorting = false;
if (!predicted_events_queue_.empty() &&
time_stamp < predicted_events_queue_.back().time_stamp)
needs_sorting = true;
EventData e;
if (scrolling)
e.pos = gfx::PointF(0, pos.y());
else
e.pos = pos;
e.time_stamp = time_stamp;
e.frame_time = frame_time;
predicted_events_queue_.push_back(e);
if (needs_sorting) {
std::sort(predicted_events_queue_.begin(), predicted_events_queue_.end(),
[](const EventData& a, const EventData& b) {
return a.time_stamp < b.time_stamp;
});
}
}
void PredictionMetricsHandler::EvaluatePrediction() {
while (!predicted_events_queue_.empty()) {
if (events_queue_.size() < 2 ||
events_queue_.back().time_stamp <=
predicted_events_queue_.front().time_stamp ||
events_queue_.back().time_stamp <=
predicted_events_queue_.front().frame_time) {
return;
}
ComputeMetrics();
last_predicted_ = predicted_events_queue_.front().pos;
last_interpolated_ = interpolated_;
last_frame_interpolated_ = frame_interpolated_;
predicted_events_queue_.pop_front();
}
}
void PredictionMetricsHandler::Reset() {
events_queue_.clear();
predicted_events_queue_.clear();
last_predicted_ = std::nullopt;
}
int PredictionMetricsHandler::GetInterpolatedEventForPredictedEvent(
const base::TimeTicks& interpolation_timestamp,
gfx::PointF* interpolated) {
size_t idx = 0;
while (idx < events_queue_.size() &&
interpolation_timestamp >= events_queue_[idx].time_stamp)
idx++;
if (idx == 0 || idx == events_queue_.size())
return -1;
const float alpha =
(interpolation_timestamp - events_queue_[idx - 1].time_stamp) /
(events_queue_[idx].time_stamp - events_queue_[idx - 1].time_stamp);
*interpolated =
events_queue_[idx - 1].pos +
ScaleVector2d(events_queue_[idx].pos - events_queue_[idx - 1].pos, alpha);
return idx - 1;
}
void PredictionMetricsHandler::ComputeMetrics() {
int low_idx_interpolated = GetInterpolatedEventForPredictedEvent(
predicted_events_queue_.front().time_stamp, &interpolated_);
int low_idx_frame_interpolated = GetInterpolatedEventForPredictedEvent(
predicted_events_queue_.front().frame_time, &frame_interpolated_);
next_real_ = events_queue_[low_idx_interpolated + 1].pos;
next_real_point_after_frame_ =
events_queue_[low_idx_frame_interpolated + 1].pos;
int first_needed_event =
std::min(low_idx_interpolated, low_idx_frame_interpolated);
if (first_needed_event == -1)
return;
for (int i = 0; i < first_needed_event - 1; i++)
events_queue_.pop_front();
double score = ComputeOverUnderPredictionMetric();
if (score >= 0) {
over_prediction_histogram_->Add(score);
} else {
under_prediction_histogram_->Add(-score);
}
prediction_score_histogram_->Add(std::abs(score));
double frame_score = ComputeFrameOverUnderPredictionMetric();
if (frame_score >= 0) {
frame_over_prediction_histogram_->Add(frame_score);
} else {
frame_under_prediction_histogram_->Add(-frame_score);
}
frame_prediction_score_histogram_->Add(std::abs(frame_score));
if (!last_predicted_.has_value())
return;
prediction_jitter_histogram_->Add(ComputePredictionJitterMetric());
visual_jitter_histogram_->Add(ComputeVisualJitterMetric());
}
double PredictionMetricsHandler::ComputeOverUnderPredictionMetric() const {
gfx::Vector2dF real_direction = next_real_ - interpolated_;
gfx::Vector2dF relative_direction =
predicted_events_queue_.front().pos - interpolated_;
if (gfx::DotProduct(real_direction, relative_direction) >= 0)
return relative_direction.Length();
else
return -relative_direction.Length();
}
double PredictionMetricsHandler::ComputeFrameOverUnderPredictionMetric() const {
gfx::Vector2dF real_direction =
next_real_point_after_frame_ - frame_interpolated_;
gfx::Vector2dF relative_direction =
predicted_events_queue_.front().pos - frame_interpolated_;
if (gfx::DotProduct(real_direction, relative_direction) >= 0)
return relative_direction.Length();
else
return -relative_direction.Length();
}
double PredictionMetricsHandler::ComputePredictionJitterMetric() {
gfx::Vector2dF delta = interpolated_ - predicted_events_queue_.front().pos;
gfx::Vector2dF last_delta = last_interpolated_ - last_predicted_.value();
return (delta - last_delta).Length();
}
double PredictionMetricsHandler::ComputeVisualJitterMetric() {
gfx::Vector2dF delta =
frame_interpolated_ - predicted_events_queue_.front().pos;
gfx::Vector2dF last_delta =
last_frame_interpolated_ - last_predicted_.value();
return (delta - last_delta).Length();
}
}