| // 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); |
| } |
| } |