blob: c71e86c21d8e20f63ab20981964971f4639a08d4 [file] [log] [blame]
// Copyright 2015 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 "components/scheduler/renderer/user_model.h"
#include "base/metrics/histogram_macros.h"
namespace scheduler {
namespace {
// This enum is used to back a histogram, and should therefore be treated as
// append-only.
enum GesturePredictionResult {
GESTURE_OCCURED_WAS_PREDICTED = 0,
GESTURE_OCCURED_BUT_NOT_PREDICTED = 1,
GESTURE_PREDICTED_BUT_DID_NOT_OCCUR = 2,
GESTURE_PREDICTION_RESULT_COUNT = 3
};
void RecordGesturePrediction(GesturePredictionResult result) {
UMA_HISTOGRAM_ENUMERATION(
"RendererScheduler.UserModel.GesturePredictedCorrectly", result,
GESTURE_PREDICTION_RESULT_COUNT);
}
} // namespace
UserModel::UserModel()
: pending_input_event_count_(0),
is_gesture_active_(false),
is_gesture_expected_(false) {}
UserModel::~UserModel() {}
void UserModel::DidStartProcessingInputEvent(blink::WebInputEvent::Type type,
const base::TimeTicks now) {
last_input_signal_time_ = now;
if (type == blink::WebInputEvent::TouchStart ||
type == blink::WebInputEvent::GestureScrollBegin ||
type == blink::WebInputEvent::GesturePinchBegin) {
// Only update stats once per gesture.
if (!is_gesture_active_) {
last_gesture_start_time_ = now;
RecordGesturePrediction(is_gesture_expected_
? GESTURE_OCCURED_WAS_PREDICTED
: GESTURE_OCCURED_BUT_NOT_PREDICTED);
if (!last_reset_time_.is_null()) {
base::TimeDelta time_since_reset = now - last_reset_time_;
UMA_HISTOGRAM_MEDIUM_TIMES(
"RendererScheduler.UserModel.GestureStartTimeSinceModelReset",
time_since_reset);
}
// If there has been a previous gesture, record a UMA metric for the time
// interval between then and now.
if (!last_continuous_gesture_time_.is_null()) {
base::TimeDelta time_since_last_gesture =
now - last_continuous_gesture_time_;
UMA_HISTOGRAM_MEDIUM_TIMES(
"RendererScheduler.UserModel.TimeBetweenGestures",
time_since_last_gesture);
}
}
is_gesture_active_ = true;
}
// We need to track continuous gestures seperatly for scroll detection
// because taps should not be confused with scrolls.
if (type == blink::WebInputEvent::GestureScrollBegin ||
type == blink::WebInputEvent::GestureScrollEnd ||
type == blink::WebInputEvent::GestureScrollUpdate ||
type == blink::WebInputEvent::GestureFlingStart ||
type == blink::WebInputEvent::GestureFlingCancel ||
type == blink::WebInputEvent::GesturePinchBegin ||
type == blink::WebInputEvent::GesturePinchEnd ||
type == blink::WebInputEvent::GesturePinchUpdate) {
last_continuous_gesture_time_ = now;
}
// If the gesture has ended, clear |is_gesture_active_| and record a UMA
// metric that tracks its duration.
if (type == blink::WebInputEvent::GestureScrollEnd ||
type == blink::WebInputEvent::GesturePinchEnd ||
type == blink::WebInputEvent::GestureFlingStart ||
type == blink::WebInputEvent::TouchEnd) {
// Only update stats once per gesture.
if (is_gesture_active_) {
base::TimeDelta duration = now - last_gesture_start_time_;
UMA_HISTOGRAM_TIMES("RendererScheduler.UserModel.GestureDuration",
duration);
}
is_gesture_active_ = false;
}
TRACE_COUNTER1(TRACE_DISABLED_BY_DEFAULT("renderer.scheduler"),
"is_gesture_active", is_gesture_active_);
pending_input_event_count_++;
}
void UserModel::DidFinishProcessingInputEvent(const base::TimeTicks now) {
last_input_signal_time_ = now;
if (pending_input_event_count_ > 0)
pending_input_event_count_--;
}
base::TimeDelta UserModel::TimeLeftInUserGesture(base::TimeTicks now) const {
base::TimeDelta escalated_priority_duration =
base::TimeDelta::FromMilliseconds(kGestureEstimationLimitMillis);
// If the input event is still pending, go into input prioritized policy and
// check again later.
if (pending_input_event_count_ > 0)
return escalated_priority_duration;
if (last_input_signal_time_.is_null() ||
last_input_signal_time_ + escalated_priority_duration < now) {
return base::TimeDelta();
}
return last_input_signal_time_ + escalated_priority_duration - now;
}
bool UserModel::IsGestureExpectedSoon(
const base::TimeTicks now,
base::TimeDelta* prediction_valid_duration) {
bool was_gesture_expected = is_gesture_expected_;
is_gesture_expected_ =
IsGestureExpectedSoonImpl(now, prediction_valid_duration);
// Track when we start expecting a gesture so we can work out later if a
// gesture actually happened.
if (!was_gesture_expected && is_gesture_expected_)
last_gesture_expected_start_time_ = now;
if (was_gesture_expected && !is_gesture_expected_ &&
last_gesture_expected_start_time_ > last_gesture_start_time_) {
RecordGesturePrediction(GESTURE_PREDICTED_BUT_DID_NOT_OCCUR);
}
return is_gesture_expected_;
}
bool UserModel::IsGestureExpectedSoonImpl(
const base::TimeTicks now,
base::TimeDelta* prediction_valid_duration) const {
if (is_gesture_active_) {
if (IsGestureExpectedToContinue(now, prediction_valid_duration)) {
return false;
} else {
// If a gesture is not expected to continue then we expect a subsequent
// gesture soon.
*prediction_valid_duration =
base::TimeDelta::FromMilliseconds(kExpectSubsequentGestureMillis);
return true;
}
} else {
// If we've have a finished a gesture then a subsequent gesture is deemed
// likely.
base::TimeDelta expect_subsequent_gesture_for =
base::TimeDelta::FromMilliseconds(kExpectSubsequentGestureMillis);
if (last_continuous_gesture_time_.is_null() ||
last_continuous_gesture_time_ + expect_subsequent_gesture_for <= now) {
return false;
}
*prediction_valid_duration =
last_continuous_gesture_time_ + expect_subsequent_gesture_for - now;
return true;
}
}
bool UserModel::IsGestureExpectedToContinue(
const base::TimeTicks now,
base::TimeDelta* prediction_valid_duration) const {
if (!is_gesture_active_)
return false;
base::TimeDelta median_gesture_duration =
base::TimeDelta::FromMilliseconds(kMedianGestureDurationMillis);
base::TimeTicks expected_gesture_end_time =
last_gesture_start_time_ + median_gesture_duration;
if (expected_gesture_end_time > now) {
*prediction_valid_duration = expected_gesture_end_time - now;
return true;
}
return false;
}
void UserModel::Reset(base::TimeTicks now) {
last_input_signal_time_ = base::TimeTicks();
last_gesture_start_time_ = base::TimeTicks();
last_continuous_gesture_time_ = base::TimeTicks();
last_gesture_expected_start_time_ = base::TimeTicks();
last_reset_time_ = now;
is_gesture_active_ = false;
is_gesture_expected_ = false;
}
void UserModel::AsValueInto(base::trace_event::TracedValue* state) const {
state->BeginDictionary("user_model");
state->SetInteger("pending_input_event_count", pending_input_event_count_);
state->SetDouble(
"last_input_signal_time",
(last_input_signal_time_ - base::TimeTicks()).InMillisecondsF());
state->SetDouble(
"last_gesture_start_time",
(last_gesture_start_time_ - base::TimeTicks()).InMillisecondsF());
state->SetDouble(
"last_continuous_gesture_time",
(last_continuous_gesture_time_ - base::TimeTicks()).InMillisecondsF());
state->SetDouble("last_gesture_expected_start_time",
(last_gesture_expected_start_time_ - base::TimeTicks())
.InMillisecondsF());
state->SetDouble("last_reset_time",
(last_reset_time_ - base::TimeTicks()).InMillisecondsF());
state->SetBoolean("is_gesture_expected", is_gesture_expected_);
state->SetBoolean("is_gesture_active", is_gesture_active_);
state->EndDictionary();
}
} // namespace scheduler