blob: 3721dc584286169c1535c8f63a35c04c8bb2dcef [file] [log] [blame]
// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#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);
}
} // namespace
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) {
// Real events should arrive in order over time, and if they aren't then just
// bail. Early out instead of DCHECKing in order to handle delegated ink
// trails. Delegated ink trails may submit points out of order in a situation
// such as three points with timestamps = 1, 2, and 3 making up the trail on
// one frame, and then on the next frame only the points with timestamp 2 and
// 3 make up the trail. In this case, 2 would be added as a real point again,
// but it has a timestamp earlier than 3, so a DCHECK would fail. Early out
// here will not impact correctness since 2 already exists in |events_queue_|.
if (!events_queue_.empty() && time_stamp <= events_queue_.back().time_stamp) {
// There can be situations where the metadata does not arrive in time for
// the vsync. Rather than skipping drawing for that frame, the metadata is
// kept and the trail is drawn from the metadata point to the latest
// point in the trail. However, the metadata and points relatively near it
// can be cleared from events_queue_ during ComputeMetrics(). Therefore the
// following DCHECK is hit when the older points are re-added as real
// events. Since those points are not relevant to the front of the trail,
// where the prediction happens, they can safely be exempt from the
// following DCHECK. Only points that are at or later than the front of the
// events_queue_ need to be verified.
if (time_stamp < events_queue_.front().time_stamp)
return;
// Confirm that the above assertion is true, and that timestamp 2 (from
// the above example) exists in |events_queue_|.
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) {
DCHECK(!events_queue_.empty());
// If the predicted event is prior to the first real event, ignore it as we
// don't have enough data for interpolation.
if (time_stamp < events_queue_.front().time_stamp)
return;
// TODO(nzolghadr): The following DCHECK is commented out due to
// crbug.com/1017661. More investigation needs to be done as why this happens.
// DCHECK(predicted_events_queue_.empty() ||
// time_stamp >= predicted_events_queue_.back().time_stamp);
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);
// TODO(nzolghadr): This should never be needed. Something seems to be wrong
// in the tests. See crbug.com/1017661.
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()) {
// Not enough events to compute the metrics, do not compute for now.
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() {
// Compute interpolations at predicted time and frame time.
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);
// Return if any of the interpolation is not found.
if (first_needed_event == -1)
return;
// Clean real events queue.
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));
// Need |last_predicted_| to compute Jitter metrics.
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();
}
} // namespace ui