blob: bc20581ee485b3f4191b48e48adec2a580f1ad2b [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 "components/omnibox/browser/autocomplete_controller_metrics.h"
#include <memory>
#include <string>
#include <vector>
#include "base/ranges/algorithm.h"
#include "base/strings/string_number_conversions.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/task_environment.h"
#include "base/time/time.h"
#include "components/omnibox/browser/autocomplete_controller.h"
#include "components/omnibox/browser/autocomplete_provider_client.h"
#include "components/omnibox/browser/autocomplete_result.h"
#include "components/omnibox/browser/autocomplete_scheme_classifier.h"
#include "components/omnibox/browser/fake_autocomplete_controller.h"
#include "components/omnibox/browser/fake_autocomplete_provider.h"
#include "components/omnibox/browser/fake_autocomplete_provider_client.h"
#include "components/omnibox/browser/fake_tab_matcher.h"
#include "components/omnibox/browser/tab_matcher.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/metrics_proto/omnibox_input_type.pb.h"
#include "url/gurl.h"
using testing::ElementsAre;
using testing::ElementsAreArray;
class AutocompleteControllerMetricsTest : public testing::Test {
public:
AutocompleteControllerMetricsTest()
: metrics_(controller_.metrics_),
histogram_tester_(std::make_unique<base::HistogramTester>()){};
// By default, the controller wants async matches. `SetInputSync()` will
// explicitly set this behavior.
void SetInputSync(bool sync) {
controller_.input_.set_want_asynchronous_matches(!sync);
}
// Mimics `AutocompleteController::Start()`'s behavior. `sync_results_only`
// determines whether the controller should be done after the sync pass.
// `sync_milliseconds` is how many seconds the sync pass should take.
// `old_results` and `sync_results` are passed to
// `AutocompleteControllerMetrics::OnUpdateResult()` to determine which
// matches changed.
void SimulateStart(
bool sync_results_only,
int sync_milliseconds,
std::vector<AutocompleteResult::MatchDedupComparator> old_results,
std::vector<AutocompleteResult::MatchDedupComparator> sync_results) {
metrics_.OnStart();
controller_.in_start_ = true;
controller_.done_ = sync_results_only;
task_environment_.FastForwardBy(base::Milliseconds(sync_milliseconds));
metrics_.OnUpdateResult(old_results, sync_results);
controller_.in_start_ = false;
}
// Convenience function to be called before/after EXPECT'ing histograms to
// avoid old logs converting new logs.
void ResetHistogramTester() {
histogram_tester_ = std::make_unique<base::HistogramTester>();
}
// Convenience function to call AutocompleteControllerMetrics::OnStop()` and
// expects it to not log any suggestion finalization metrics. Should be used
// to simulate the controller completing in which case metrics should have
// already been logged earlier during `::OnUpdateResult()`. Should not be used
// when the controller is interrupted in which case metrics are expected to be
// logged. Does not check provider or cross stability metrics.
void StopAndExpectNoSuggestionFinalizationMetrics() {
ResetHistogramTester();
metrics_.OnStop();
ExpectNoSuggestionFinalizationMetrics();
}
// Convenience method to check the buckets of a single metric.
void ExpectMetrics(const std::string metric_name,
std::vector<base::Bucket> expected_buckets) {
const std::string prefix = "Omnibox.AsyncAutocompletionTime.";
EXPECT_THAT(
histogram_tester_->GetAllSamples(prefix + metric_name),
ElementsAreArray(expected_buckets.data(), expected_buckets.size()))
<< metric_name;
}
// Convenience method to check the buckets of 3 metrics:
// - '...<metric>' will be expected to have `buckets`.
// - '...<metric>.[Completed]' will be expected to either have
// `buckets` or be empty, depending on `completed`.
void ExpectSlicedMetrics(const std::string& metric,
std::vector<base::Bucket> buckets,
bool completed) {
const std::vector<base::Bucket> empty_buckets = {};
ExpectMetrics(metric, buckets);
ExpectMetrics(metric + ".Completed", completed ? buckets : empty_buckets);
}
// Convenience method to check all 9 suggestion finalization metrics are
// empty. '...[Done|LastChange|LastDefaultChange][|.Completed]'
void ExpectNoSuggestionFinalizationMetrics() {
ExpectSlicedMetrics("Done", {}, false);
ExpectSlicedMetrics("LastChange", {}, false);
ExpectSlicedMetrics("LastDefaultChange", {}, false);
}
// Convince method to check all 9 suggestion finalization metrics have been
// logged exactly once with the correct bucket. This should be used if
// there's been a single controller request logged since the last
// `ResetHistogramTester()`. If there have been none,
// `ExpectNoSuggestionFinalizationMetrics()` should be used.
void ExpectSingleCountSuggestionFinalizationMetrics(
int done_bucket_min,
int last_change_bucket_min,
int last_default_change_bucket_min,
bool completed) {
ExpectSlicedMetrics("Done", {{done_bucket_min, 1}}, completed);
ExpectSlicedMetrics("LastChange", {{last_change_bucket_min, 1}}, completed);
ExpectSlicedMetrics("LastDefaultChange",
{{last_default_change_bucket_min, 1}}, completed);
}
// Convenience method to check the 3 metrics for a specific provider are
// empty. '...Provider.<Provider>[|.Completed]'
void ExpectNoProviderMetrics(const std::string& provider) {
ExpectSlicedMetrics("Provider." + provider, {}, false);
}
// Convenience method to check the 3 metrics for a specific provider have
// been logged exactly once with the correct bucket. This should be used if
// there's been a single log for the specified provider since the last
// `ResetHistogramTester()`. If there have been none,
// `ExpectNoProviderMetrics()` should be used.
void ExpectProviderMetrics(const std::string& provider,
int bucket_min,
bool completed) {
ExpectSlicedMetrics("Provider." + provider, {{bucket_min, 1}}, completed);
}
base::test::SingleThreadTaskEnvironment task_environment_{
base::test::TaskEnvironment::TimeSource::MOCK_TIME};
// Used to control time passed between calls. Many metrics tested are timing
// metrics.
FakeAutocompleteController controller_;
// A convenience reference to `controller_.metrics_`.
AutocompleteControllerMetrics& metrics_;
std::unique_ptr<base::HistogramTester> histogram_tester_;
};
TEST_F(AutocompleteControllerMetricsTest, SuggestionFinalization_SyncInput) {
// Sync inputs should not log metrics.
SetInputSync(true);
SimulateStart(true, 1, {}, {{}});
metrics_.OnStop();
ExpectNoSuggestionFinalizationMetrics();
}
TEST_F(AutocompleteControllerMetricsTest,
SuggestionFinalization_OnlySyncUpdate) {
// Sync updates should log metrics.
SimulateStart(true, 1, {}, {{}});
ExpectSingleCountSuggestionFinalizationMetrics(1, 1, 1, true);
StopAndExpectNoSuggestionFinalizationMetrics();
}
TEST_F(AutocompleteControllerMetricsTest,
SuggestionFinalization_OnlySyncUpdateWithNoChanges) {
// Sync updates without changes should log metrics.
SimulateStart(true, 1, {{}}, {{}});
ExpectSingleCountSuggestionFinalizationMetrics(1, 0, 0, true);
StopAndExpectNoSuggestionFinalizationMetrics();
}
TEST_F(AutocompleteControllerMetricsTest,
SuggestionFinalization_SyncAnd3AsyncUpdate) {
// This is the typical flow: 1 sync update followed by multiple async updates.
SimulateStart(false, 1, {}, {{}});
// 1st async update.
task_environment_.FastForwardBy(base::Milliseconds(1));
metrics_.OnUpdateResult({}, {{}});
// 2nd async update.
task_environment_.FastForwardBy(base::Milliseconds(1));
metrics_.OnUpdateResult({}, {{}});
// Last async update.
task_environment_.FastForwardBy(base::Milliseconds(1));
controller_.done_ = true;
metrics_.OnUpdateResult({}, {{}});
ExpectSingleCountSuggestionFinalizationMetrics(4, 4, 4, true);
StopAndExpectNoSuggestionFinalizationMetrics();
}
TEST_F(AutocompleteControllerMetricsTest,
SuggestionFinalization_SyncAnd3AsyncUpdateWithNoChanges) {
// 1 sync and 3 async updates, none of the 4 has a change.
SimulateStart(false, 1, {{}}, {{}});
// 1st async update.
task_environment_.FastForwardBy(base::Milliseconds(1));
metrics_.OnUpdateResult({{}}, {{}});
// 2nd async update.
task_environment_.FastForwardBy(base::Milliseconds(1));
metrics_.OnUpdateResult({{}}, {{}});
// Last async update.
task_environment_.FastForwardBy(base::Milliseconds(1));
controller_.done_ = true;
metrics_.OnUpdateResult({{}}, {{}});
ExpectSingleCountSuggestionFinalizationMetrics(4, 0, 0, true);
StopAndExpectNoSuggestionFinalizationMetrics();
}
TEST_F(
AutocompleteControllerMetricsTest,
SuggestionFinalization_UnchangedSyncAnd2UnchangedAnd1ChangedAsyncUpdates) {
// 1 sync and 3 async updates, only the last of the 4 has a change.
SimulateStart(false, 1, {{}}, {{}});
// 1st async update.
task_environment_.FastForwardBy(base::Milliseconds(1));
metrics_.OnUpdateResult({{}}, {{}});
// 2nd async update.
task_environment_.FastForwardBy(base::Milliseconds(1));
metrics_.OnUpdateResult({{}}, {{}});
// Last async update.
task_environment_.FastForwardBy(base::Milliseconds(1));
controller_.done_ = true;
metrics_.OnUpdateResult({}, {{}});
ExpectSingleCountSuggestionFinalizationMetrics(4, 4, 4, true);
StopAndExpectNoSuggestionFinalizationMetrics();
}
TEST_F(
AutocompleteControllerMetricsTest,
SuggestionFinalization_UnchangedSyncAnd1ChangedAnd2UnchangedAsyncUpdates) {
// 1 sync and 3 async updates, only the 2nd of the 4 has a change.
SimulateStart(false, 1, {{}}, {{}});
// 1st async update.
task_environment_.FastForwardBy(base::Milliseconds(1));
metrics_.OnUpdateResult({}, {{}});
// 2nd async update.
task_environment_.FastForwardBy(base::Milliseconds(1));
metrics_.OnUpdateResult({{}}, {{}});
// Last async update.
task_environment_.FastForwardBy(base::Milliseconds(1));
controller_.done_ = true;
metrics_.OnUpdateResult({{}}, {{}});
ExpectSingleCountSuggestionFinalizationMetrics(4, 2, 2, true);
StopAndExpectNoSuggestionFinalizationMetrics();
}
TEST_F(AutocompleteControllerMetricsTest,
SuggestionFinalization_ChangedSyncAnd3UnchangedAsyncUpdates) {
// 1 sync and 3 async updates, only the 1st of the 4 has a change.
SimulateStart(false, 1, {}, {{}});
// 1st async update.
task_environment_.FastForwardBy(base::Milliseconds(1));
metrics_.OnUpdateResult({{}}, {{}});
// 2nd async update.
task_environment_.FastForwardBy(base::Milliseconds(1));
metrics_.OnUpdateResult({{}}, {{}});
// Last async update.
task_environment_.FastForwardBy(base::Milliseconds(1));
controller_.done_ = true;
metrics_.OnUpdateResult({{}}, {{}});
ExpectSingleCountSuggestionFinalizationMetrics(4, 1, 1, true);
StopAndExpectNoSuggestionFinalizationMetrics();
}
TEST_F(AutocompleteControllerMetricsTest,
SuggestionFinalization_StopTimerReached) {
// Simulates the case where the async updates take longer than the 1.5s stop
// timer. It's not possible for the sync update to take longer, as the stop
// timer only starts after the sync update. The logged times should however
// measure starting from before the sync update.
SimulateStart(false, 1, {}, {{}});
// 1st async update.
task_environment_.FastForwardBy(base::Milliseconds(1));
metrics_.OnUpdateResult({{}}, {{}});
// Stop timer.
task_environment_.FastForwardBy(base::Milliseconds(1));
metrics_.OnStop();
ExpectSingleCountSuggestionFinalizationMetrics(3, 1, 1, false);
controller_.done_ = true;
StopAndExpectNoSuggestionFinalizationMetrics();
}
TEST_F(AutocompleteControllerMetricsTest, SuggestionFinalization_Interrupted) {
// Start 1st input.
SimulateStart(false, 1, {}, {{}});
// 1 async update for 1st input.
task_environment_.FastForwardBy(base::Milliseconds(1));
metrics_.OnUpdateResult({}, {{}});
ExpectNoSuggestionFinalizationMetrics();
// Interrupted by 2nd input. The log should include the time until
// interruption.
task_environment_.FastForwardBy(base::Milliseconds(1));
SimulateStart(false, 1, {}, {{}});
ExpectSingleCountSuggestionFinalizationMetrics(3, 2, 2, false);
// Interrupted by 3rd input.
ResetHistogramTester();
task_environment_.FastForwardBy(base::Milliseconds(1));
SimulateStart(false, 1, {}, {{}});
ExpectSingleCountSuggestionFinalizationMetrics(2, 1, 1, false);
// 1 async update for 3rd input. Controller completes with the 2nd update.
ResetHistogramTester();
task_environment_.FastForwardBy(base::Milliseconds(1));
metrics_.OnUpdateResult({}, {{}});
ExpectNoSuggestionFinalizationMetrics();
// 2nd and last async update for 3rd input.
controller_.done_ = true;
task_environment_.FastForwardBy(base::Milliseconds(1));
metrics_.OnUpdateResult({}, {{}});
ExpectSingleCountSuggestionFinalizationMetrics(3, 3, 3, true);
StopAndExpectNoSuggestionFinalizationMetrics();
}
TEST_F(AutocompleteControllerMetricsTest,
SuggestionFinalization_DefaultUnchanged) {
// Sync update.
SimulateStart(false, 1, {}, {{}});
// 1st async update with non-default match changed.
task_environment_.FastForwardBy(base::Milliseconds(1));
metrics_.OnUpdateResult({{}}, {{}, {}});
// 2nd async update with no matches changed.
task_environment_.FastForwardBy(base::Milliseconds(1));
controller_.done_ = true;
metrics_.OnUpdateResult({{}}, {{}});
ExpectSingleCountSuggestionFinalizationMetrics(3, 2, 1, true);
StopAndExpectNoSuggestionFinalizationMetrics();
}
TEST_F(AutocompleteControllerMetricsTest, Provider_SyncAndAsyncCompletion) {
scoped_refptr<FakeAutocompleteProvider> async_provider_done_sync =
new FakeAutocompleteProvider(AutocompleteProvider::Type::TYPE_BOOKMARK);
scoped_refptr<FakeAutocompleteProvider> async_provider_done_not_last =
new FakeAutocompleteProvider(AutocompleteProvider::Type::TYPE_KEYWORD);
scoped_refptr<FakeAutocompleteProvider> async_provider_done_last =
new FakeAutocompleteProvider(AutocompleteProvider::Type::TYPE_BUILTIN);
// Sync update with `async_provider_done_sync` completing.
metrics_.OnStart();
controller_.in_start_ = true;
task_environment_.FastForwardBy(base::Milliseconds(1));
metrics_.OnProviderUpdate(*async_provider_done_sync);
ExpectProviderMetrics(async_provider_done_sync->GetName(), 1, true);
controller_.done_ = false;
metrics_.OnUpdateResult({{}}, {{}});
controller_.in_start_ = false;
ExpectNoSuggestionFinalizationMetrics();
ResetHistogramTester();
// 1st async update with `async_provider_done_not_last` completing.
task_environment_.FastForwardBy(base::Milliseconds(1));
metrics_.OnProviderUpdate(*async_provider_done_not_last);
ExpectProviderMetrics(async_provider_done_not_last->GetName(), 2, true);
metrics_.OnUpdateResult({{}}, {{}});
ExpectNoSuggestionFinalizationMetrics();
ResetHistogramTester();
// Last async update with `async_provider_done_last` completing.
task_environment_.FastForwardBy(base::Milliseconds(1));
metrics_.OnProviderUpdate(*async_provider_done_last);
controller_.done_ = true;
ExpectProviderMetrics(async_provider_done_last->GetName(), 3, true);
metrics_.OnUpdateResult({{}}, {{}});
ExpectSingleCountSuggestionFinalizationMetrics(3, 0, 0, true);
StopAndExpectNoSuggestionFinalizationMetrics();
ExpectNoProviderMetrics(async_provider_done_sync->GetName());
ExpectNoProviderMetrics(async_provider_done_not_last->GetName());
ExpectNoProviderMetrics(async_provider_done_last->GetName());
}
TEST_F(AutocompleteControllerMetricsTest,
Provider_1ProviderWithMultipleUpdates) {
scoped_refptr<FakeAutocompleteProvider> provider =
new FakeAutocompleteProvider(AutocompleteProvider::Type::TYPE_BOOKMARK);
// Sync update without completion.
metrics_.OnStart();
controller_.in_start_ = true;
provider->done_ = false;
metrics_.OnProviderUpdate(*provider);
controller_.done_ = false;
task_environment_.FastForwardBy(base::Milliseconds(1));
metrics_.OnUpdateResult({{}}, {{}});
controller_.in_start_ = false;
// 1st async update without completion.
task_environment_.FastForwardBy(base::Milliseconds(1));
metrics_.OnProviderUpdate(*provider);
metrics_.OnUpdateResult({{}}, {{}});
// Last async update with completion.
task_environment_.FastForwardBy(base::Milliseconds(1));
provider->done_ = true;
controller_.done_ = true;
metrics_.OnProviderUpdate(*provider);
metrics_.OnUpdateResult({{}}, {{}});
ExpectProviderMetrics(provider->GetName(), 3, true);
ExpectSingleCountSuggestionFinalizationMetrics(3, 0, 0, true);
StopAndExpectNoSuggestionFinalizationMetrics();
ExpectNoProviderMetrics(provider->GetName());
}
TEST_F(AutocompleteControllerMetricsTest, Provider_Interrupted) {
scoped_refptr<FakeAutocompleteProvider> provider_started =
new FakeAutocompleteProvider(AutocompleteProvider::Type::TYPE_BOOKMARK);
scoped_refptr<FakeAutocompleteProvider> provider_not_started =
new FakeAutocompleteProvider(AutocompleteProvider::Type::TYPE_SEARCH);
controller_.providers_.push_back(provider_started);
controller_.providers_.push_back(provider_not_started);
// Sync update.
SimulateStart(false, 1, {{}}, {{}});
// Simulate stopping while `provider_started` is still ongoing.
task_environment_.FastForwardBy(base::Milliseconds(1));
provider_started->done_ = false;
metrics_.OnStop();
provider_started->done_ = true;
controller_.done_ = true;
ExpectProviderMetrics(provider_started->GetName(), 2, false);
ExpectNoProviderMetrics(provider_not_started->GetName());
ExpectSingleCountSuggestionFinalizationMetrics(2, 0, 0, false);
}
TEST_F(AutocompleteControllerMetricsTest, MatchStability) {
auto create_result = [&](std::vector<int> ids) {
std::vector<AutocompleteResult::MatchDedupComparator> result;
base::ranges::transform(ids, std::back_inserter(result), [](int id) {
return std::pair<GURL, bool>{"http://" + base::NumberToString(id), false};
});
return result;
};
const auto first_result = create_result({0, 1, 2, 3, 4});
// Same as `first_result`, but with these changes:
// - Last two matches removed.
// - Default match updated to a new URL.
// - Third match updated to a new URL.
const auto second_result = create_result({10, 1, 11});
// Same as `second_result`, but with these changes:
// - 2 new matches appended to the bottom.
const auto third_result = create_result({10, 1, 11, 10, 2});
// Verify logging to the Async* histograms.
controller_.in_start_ = false;
metrics_.OnUpdateResult(first_result, second_result);
// Expect the default match, third match, and last two matches to be logged
// as changed, and nothing else.
EXPECT_THAT(histogram_tester_->GetAllSamples(
"Omnibox.MatchStability.AsyncMatchChange2"),
testing::ElementsAre(base::Bucket(0, 1), base::Bucket(2, 1),
base::Bucket(3, 1), base::Bucket(4, 1)));
// Expect that we log that at least one of the matches has changed.
EXPECT_THAT(histogram_tester_->GetAllSamples(
"Omnibox.MatchStability.AsyncMatchChangedInAnyPosition"),
testing::ElementsAre(base::Bucket(1, 1)));
// Expect that we don't log async updates to the sync histograms.
EXPECT_THAT(histogram_tester_->GetAllSamples(
"Omnibox.CrossInputMatchStability.MatchChange"),
testing::ElementsAre());
EXPECT_THAT(histogram_tester_->GetAllSamples(
"Omnibox.CrossInputMatchStability.MatchChangedInAnyPosition"),
testing::ElementsAre());
ResetHistogramTester();
// Verify logging to the CrossInput* histograms.
controller_.in_start_ = true;
metrics_.OnUpdateResult(first_result, second_result);
// Expect the default match, third match, and last two matches to be logged
// as changed, and nothing else.
EXPECT_THAT(histogram_tester_->GetAllSamples(
"Omnibox.CrossInputMatchStability.MatchChange"),
testing::ElementsAre(base::Bucket(0, 1), base::Bucket(2, 1),
base::Bucket(3, 1), base::Bucket(4, 1)));
// Expect that we log that at least one of the matches has changed.
EXPECT_THAT(histogram_tester_->GetAllSamples(
"Omnibox.CrossInputMatchStability.MatchChangedInAnyPosition"),
testing::ElementsAre(base::Bucket(1, 1)));
// Expect that we don't log sync updates to the async histograms.
EXPECT_THAT(histogram_tester_->GetAllSamples(
"Omnibox.MatchStability.AsyncMatchChange2"),
testing::ElementsAre());
EXPECT_THAT(histogram_tester_->GetAllSamples(
"Omnibox.MatchStability.AsyncMatchChangedInAnyPosition"),
testing::ElementsAre());
ResetHistogramTester();
// Verify no logging when appending matches.
controller_.in_start_ = false;
metrics_.OnUpdateResult(second_result, third_result);
controller_.in_start_ = true;
metrics_.OnUpdateResult(second_result, third_result);
// Expect no changes logged; expect 1 false logged to
// *MatchChangedInAnyPosition.
EXPECT_THAT(histogram_tester_->GetAllSamples(
"Omnibox.MatchStability.AsyncMatchChange2"),
testing::ElementsAre());
EXPECT_THAT(histogram_tester_->GetAllSamples(
"Omnibox.MatchStability.AsyncMatchChangedInAnyPosition"),
testing::ElementsAre(base::Bucket(0, 1)));
EXPECT_THAT(histogram_tester_->GetAllSamples(
"Omnibox.CrossInputMatchStability.MatchChange"),
testing::ElementsAre());
EXPECT_THAT(histogram_tester_->GetAllSamples(
"Omnibox.CrossInputMatchStability.MatchChangedInAnyPosition"),
testing::ElementsAre(base::Bucket(0, 1)));
ResetHistogramTester();
}