blob: e6d5a965e00c5622957615076b3c9a29fadf27d4 [file] [log] [blame]
// Copyright 2022 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 "autocomplete_controller_metrics.h"
#include <string>
#include "base/metrics/histogram_functions.h"
#include "base/metrics/histogram_macros.h"
#include "base/time/time.h"
#include "components/omnibox/browser/autocomplete_controller.h"
#include "components/omnibox/browser/autocomplete_provider.h"
#include "components/omnibox/browser/autocomplete_result.h"
AutocompleteControllerMetrics::AutocompleteControllerMetrics(
const AutocompleteController& controller)
: controller_(controller) {}
void AutocompleteControllerMetrics::OnStart() {
OnStop();
logged_finalization_metrics_ = false;
start_time_ = base::TimeTicks::Now();
last_change_time_ = start_time_;
last_default_change_time_ = start_time_;
}
void AutocompleteControllerMetrics::OnUpdateResult(
std::vector<AutocompleteResult::MatchDedupComparator> last_result,
std::vector<AutocompleteResult::MatchDedupComparator> new_result) {
// If results are empty then the omnibox is likely closed, and clearing old
// results won't be user visible. E.g., this occurs when opening a new tab
// while the popup was open.
if (new_result.empty())
return;
// Log suggestion changes.
bool any_match_changed_or_removed = false;
for (size_t i = 0; i < last_result.size(); ++i) {
// Log changed or removed matches. Don't log for matches appended to the
// bottom since that's less disruptive.
if (i >= new_result.size() || last_result[i] != new_result[i]) {
LogSuggestionChangedMetrics(i);
any_match_changed_or_removed = true;
}
}
LogAnySuggestionChangedMetrics(any_match_changed_or_removed);
// Log suggestion finalization times.
// Only log suggestion finalization metrics for async requests. This handles
// logging as soon as the final update occurs, while `OnStop()` handles the
// case where the final update never occurs because of interruptions.
// TODO(manukh): Consider adding this filter to the above metrics as well.
if (!controller_.input().want_asynchronous_matches())
return;
// E.g., suggestion deletion can call `OnUpdateResult()` after the controller
// is done and finalization metrics have been logged. They shouldn't be
// re-logged.
if (logged_finalization_metrics_)
return;
const bool any_match_changed_or_removed_or_added =
any_match_changed_or_removed || last_result.size() != new_result.size();
const bool default_match_changed_or_removed_or_added =
last_result.empty() || last_result[0] != new_result[0];
if (any_match_changed_or_removed_or_added)
last_change_time_ = base::TimeTicks::Now();
if (default_match_changed_or_removed_or_added) {
DCHECK(any_match_changed_or_removed_or_added);
last_default_change_time_ = last_change_time_;
}
// It's common to have multiple async updates per input. Only log the final
// update.
if (ControllerIdle())
LogSuggestionFinalizationMetrics();
}
void AutocompleteControllerMetrics::OnProviderUpdate(
const AutocompleteProvider& provider) const {
// Some async providers may produce multiple updates. Only log the final async
// update.
if (provider.done())
LogProviderTimeMetrics(provider);
}
void AutocompleteControllerMetrics::OnStop() {
// Done providers should already be logged by `OnProviderUpdate()`.
for (const auto& provider : controller_.providers()) {
if (!provider->done()) {
DCHECK(!controller_.done() || controller_.in_start());
LogProviderTimeMetrics(*provider);
}
}
// If the controller is done, `OnUpdateResult()` should have already logged
// finalization metrics. This case, i.e. `OnStop()` invoked even though the
// controller is done, is possible because 1) `OnStart()` calls `OnStop()`
// and 2) `AutocompleteController::stop_timer_` may fire after the controller
// completes. Checking `!logged_finalization_metrics_` isn't sufficient, as
// that would log synchronous inputs.
if (!ControllerIdle())
LogSuggestionFinalizationMetrics();
}
bool AutocompleteControllerMetrics::ControllerIdle() {
return controller_.done() && controller_.expire_timer_done();
}
void AutocompleteControllerMetrics::LogSuggestionFinalizationMetrics() {
// Should be logged once only, either when all async providers complete
// or they're interrupted before completion.
DCHECK(!logged_finalization_metrics_);
logged_finalization_metrics_ = true;
const bool done = ControllerIdle();
LogAsyncAutocompletionTimeMetrics("Done", done, base::TimeTicks::Now());
LogAsyncAutocompletionTimeMetrics("LastChange", done, last_change_time_);
LogAsyncAutocompletionTimeMetrics("LastDefaultChange", done,
last_default_change_time_);
}
void AutocompleteControllerMetrics::LogProviderTimeMetrics(
const AutocompleteProvider& provider) const {
LogAsyncAutocompletionTimeMetrics(
std::string("Provider.") + provider.GetName(), provider.done(),
base::TimeTicks::Now());
}
void AutocompleteControllerMetrics::LogAsyncAutocompletionTimeMetrics(
const std::string& name,
bool completed,
const base::TimeTicks end_time) const {
const auto name_prefix = "Omnibox.AsyncAutocompletionTime." + name;
const auto elapsed_time = end_time - start_time_;
// These metrics are logged up to about 40 times per omnibox keystroke. But
// use UMA functions as the names are dynamic.
base::UmaHistogramTimes(name_prefix, elapsed_time);
if (completed)
base::UmaHistogramTimes(name_prefix + ".Completed", elapsed_time);
else
base::UmaHistogramTimes(name_prefix + ".Interrupted", elapsed_time);
}
void AutocompleteControllerMetrics::LogSuggestionChangedMetrics(
size_t change_index) const {
// These metrics are logged up to about 50 times per omnibox keystroke, so use
// UMA macros for efficiency.
if (controller_.in_start()) {
UMA_HISTOGRAM_EXACT_LINEAR(
"Omnibox.CrossInputMatchStability.MatchChange", change_index,
AutocompleteResult::kMaxAutocompletePositionValue);
} else {
UMA_HISTOGRAM_EXACT_LINEAR(
"Omnibox.MatchStability.AsyncMatchChange2", change_index,
AutocompleteResult::kMaxAutocompletePositionValue);
}
}
void AutocompleteControllerMetrics::LogAnySuggestionChangedMetrics(
bool changed) const {
// These metrics are logged up to about 5 times per omnibox keystroke, so use
// UMA macros for efficiency.
if (controller_.in_start()) {
UMA_HISTOGRAM_BOOLEAN(
"Omnibox.CrossInputMatchStability.MatchChangedInAnyPosition", changed);
} else {
UMA_HISTOGRAM_BOOLEAN(
"Omnibox.MatchStability.AsyncMatchChangedInAnyPosition", changed);
}
}