Commit f6e88454 authored by Yao Xiao's avatar Yao Xiao Committed by Chromium LUCI CQ

[floc] If finch version param mismatches, invalidate & immediately recompute.

What: Introduce the kFlocIdFinchConfigVersion feature param. On browser
start, invalidate floc and trigger an immediate recompute if the
version mismatches with the one when computing the last floc.

Why: This allows us to configure an immediate recompute when we feel
it's necessary.

How:
- Add the finch_config_version to FlocId, that allows it to be saved to
prefs at each computation along with other fields. On browser start,
check for version mismatch and handle it accordingly.
- In the InterestCohort string, this version replaces the position of
the previous constant kChromeFlocIdVersion. We expect most params to go
through finch in the near future so replacing the "chrome floc version"
part for now.
- Also move the compute_time to FlocId, as it's also logically fit
there, and we can keep the load/save function to one place.
- Move the floc features to //components as now the it's being checked
by the components.

Bug: 1159235
Change-Id: I0c80ed35ac5f181b18b860eb2f1a73acf6fd1432
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2595595Reviewed-by: default avatarJosh Karlin <jkarlin@chromium.org>
Reviewed-by: default avatarMarc Treib <treib@chromium.org>
Commit-Queue: Yao Xiao <yaoxia@chromium.org>
Cr-Commit-Position: refs/heads/master@{#838626}
parent 5cbaaabe
......@@ -21,10 +21,10 @@
#include "chrome/browser/sync/profile_sync_service_factory.h"
#include "chrome/browser/sync/user_event_service_factory.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/common/chrome_features.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "chrome/test/base/ui_test_utils.h"
#include "components/content_settings/core/browser/cookie_settings.h"
#include "components/federated_learning/features/features.h"
#include "components/federated_learning/floc_constants.h"
#include "components/history/core/test/fake_web_history_service.h"
#include "components/keyed_service/content/browser_context_dependency_manager.h"
......@@ -152,7 +152,7 @@ class FlocIdProviderWithCustomizedServicesBrowserTest
public:
FlocIdProviderWithCustomizedServicesBrowserTest() {
scoped_feature_list_.InitAndEnableFeatureWithParameters(
features::kFederatedLearningOfCohorts,
kFederatedLearningOfCohorts,
{{"minimum_history_domain_size_required", "1"}});
}
......@@ -224,38 +224,6 @@ class FlocIdProviderWithCustomizedServicesBrowserTest
return enumerator.urls();
}
std::pair<base::Time, base::Time> GetHistoryTimeRange() {
history::QueryOptions options;
options.duplicate_policy = history::QueryOptions::KEEP_ALL_DUPLICATES;
base::Time history_begin_time = base::Time::Max();
base::Time history_end_time = base::Time::Min();
base::RunLoop run_loop;
base::CancelableTaskTracker tracker;
HistoryServiceFactory::GetForProfile(browser()->profile(),
ServiceAccessType::EXPLICIT_ACCESS)
->QueryHistory(
base::string16(), options,
base::BindLambdaForTesting([&](history::QueryResults results) {
for (const history::URLResult& url_result : results) {
if (!url_result.floc_allowed())
continue;
if (url_result.visit_time() < history_begin_time)
history_begin_time = url_result.visit_time();
if (url_result.visit_time() > history_end_time)
history_end_time = url_result.visit_time();
}
run_loop.Quit();
}),
&tracker);
run_loop.Run();
return {history_begin_time, history_end_time};
}
void FinishOutstandingRemotePermissionQueries() {
base::RunLoop run_loop;
FlocRemotePermissionServiceFactory::GetForProfile(browser()->profile())
......@@ -341,10 +309,8 @@ class FlocIdProviderWithCustomizedServicesBrowserTest
FinishOutstandingRemotePermissionQueries();
FinishOutstandingHistoryQueries();
if (base::FeatureList::IsEnabled(
features::kFlocIdSortingLshBasedComputation)) {
if (base::FeatureList::IsEnabled(kFlocIdSortingLshBasedComputation))
FinishOutstandingSortingLshQueries();
}
}
// Turn on sync-history.
......@@ -575,10 +541,10 @@ IN_PROC_BROWSER_TEST_F(FlocIdProviderWithCustomizedServicesBrowserTest,
InitializeHistorySync();
// Promise resolved with the expected string.
EXPECT_EQ(FlocId(FlocId::SimHashHistory({test_host()}), base::Time(),
base::Time(), 0)
.ToStringForJsApi(),
InvokeInterestCohortJsApi(web_contents()));
EXPECT_EQ(
base::StrCat({base::NumberToString(FlocId::SimHashHistory({test_host()})),
".1.0"}),
InvokeInterestCohortJsApi(web_contents()));
}
IN_PROC_BROWSER_TEST_F(FlocIdProviderWithCustomizedServicesBrowserTest,
......@@ -604,10 +570,10 @@ IN_PROC_BROWSER_TEST_F(FlocIdProviderWithCustomizedServicesBrowserTest,
content::ChildFrameAt(web_contents()->GetMainFrame(), 0);
// Promise resolved with the expected string.
EXPECT_EQ(FlocId(FlocId::SimHashHistory({test_host()}), base::Time(),
base::Time(), 0)
.ToStringForJsApi(),
InvokeInterestCohortJsApi(child));
EXPECT_EQ(
base::StrCat({base::NumberToString(FlocId::SimHashHistory({test_host()})),
".1.0"}),
InvokeInterestCohortJsApi(child));
}
IN_PROC_BROWSER_TEST_F(FlocIdProviderWithCustomizedServicesBrowserTest,
......@@ -633,10 +599,10 @@ IN_PROC_BROWSER_TEST_F(FlocIdProviderWithCustomizedServicesBrowserTest,
content::ChildFrameAt(web_contents()->GetMainFrame(), 0);
// Promise resolved with the expected string.
EXPECT_EQ(FlocId(FlocId::SimHashHistory({test_host()}), base::Time(),
base::Time(), 0)
.ToStringForJsApi(),
InvokeInterestCohortJsApi(child));
EXPECT_EQ(
base::StrCat({base::NumberToString(FlocId::SimHashHistory({test_host()})),
".1.0"}),
InvokeInterestCohortJsApi(child));
}
IN_PROC_BROWSER_TEST_F(FlocIdProviderWithCustomizedServicesBrowserTest,
......@@ -671,10 +637,10 @@ IN_PROC_BROWSER_TEST_F(FlocIdProviderWithCustomizedServicesBrowserTest,
EXPECT_EQ("rejected", InvokeInterestCohortJsApi(child));
// Promise resolved with the expected string.
EXPECT_EQ(FlocId(FlocId::SimHashHistory({test_host()}), base::Time(),
base::Time(), 0)
.ToStringForJsApi(),
InvokeInterestCohortJsApi(web_contents()));
EXPECT_EQ(
base::StrCat({base::NumberToString(FlocId::SimHashHistory({test_host()})),
".1.0"}),
InvokeInterestCohortJsApi(web_contents()));
}
class FlocIdProviderSortingLshEnabledBrowserTest
......@@ -683,9 +649,9 @@ class FlocIdProviderSortingLshEnabledBrowserTest
FlocIdProviderSortingLshEnabledBrowserTest() {
scoped_feature_list_.Reset();
scoped_feature_list_.InitWithFeaturesAndParameters(
{{features::kFederatedLearningOfCohorts,
{{kFederatedLearningOfCohorts,
{{"minimum_history_domain_size_required", "1"}}},
{features::kFlocIdSortingLshBasedComputation, {}}},
{kFlocIdSortingLshBasedComputation, {}}},
{});
}
};
......@@ -701,9 +667,6 @@ IN_PROC_BROWSER_TEST_F(FlocIdProviderSortingLshEnabledBrowserTest,
browser(), https_server_.GetURL(test_host(), cookies_to_set));
EXPECT_EQ(1u, GetHistoryUrls().size());
auto p = GetHistoryTimeRange();
base::Time history_begin_time = p.first;
base::Time history_end_time = p.second;
EXPECT_FALSE(GetFlocId().IsValid());
......@@ -717,7 +680,7 @@ IN_PROC_BROWSER_TEST_F(FlocIdProviderSortingLshEnabledBrowserTest,
EXPECT_NE(0u, FlocId::SimHashHistory({test_host()}));
// Expect that the final id is 0 because the sorting-lsh was applied.
EXPECT_EQ(FlocId(0, history_begin_time, history_end_time, 9), GetFlocId());
EXPECT_EQ("0.1.9", InvokeInterestCohortJsApi(web_contents()));
}
IN_PROC_BROWSER_TEST_F(FlocIdProviderSortingLshEnabledBrowserTest,
......
......@@ -14,8 +14,8 @@
#include "chrome/browser/net/profile_network_context_service_factory.h"
#include "chrome/browser/sync/profile_sync_service_factory.h"
#include "chrome/browser/sync/user_event_service_factory.h"
#include "chrome/common/chrome_features.h"
#include "components/content_settings/core/browser/cookie_settings.h"
#include "components/federated_learning/features/features.h"
#include "components/history/core/browser/history_service.h"
#include "components/prefs/pref_service.h"
#include "components/sync/driver/profile_sync_service.h"
......@@ -31,8 +31,48 @@ constexpr int kQueryHistoryWindowInDays = 7;
// The placeholder sorting-lsh version when the sorting-lsh feature is disabled.
constexpr uint32_t kSortingLshVersionPlaceholder = 0;
base::TimeDelta GetFlocIdScheduledUpdateInterval() {
return features::kFlocIdScheduledUpdateInterval.Get();
// Checks whether we can keep using the previous floc. If so, write to
// |next_compute_delay| the time period we should wait until the floc needs to
// be recomputed.
bool ShouldKeepUsingPreviousFloc(const FlocId& last_floc,
base::TimeDelta* next_compute_delay) {
// The floc has never been computed. This could happen with a fresh profile,
// or some early trigger conditions were never met (e.g. sync has been
// disabled).
if (last_floc.compute_time().is_null())
return false;
// The browser started with a kFlocIdFinchConfigVersion param different from
// the param when floc was computed last time.
//
// TODO(yaoxia): Ideally we want to compare the entire version that also
// includes the sorting-lsh version. We'll need to postpone those checks to
// a point where an existing sorting-lsh file would have been loaded, i.e. not
// too soon when the file is not ready yet, but not too late if the file
// wouldn't arrive due to e.g. component updater issue.
if (last_floc.finch_config_version() !=
static_cast<uint32_t>(kFlocIdFinchConfigVersion.Get())) {
return false;
}
base::TimeDelta presumed_next_compute_delay =
kFlocIdScheduledUpdateInterval.Get() + last_floc.compute_time() -
base::Time::Now();
// The last floc has expired.
if (presumed_next_compute_delay <= base::TimeDelta())
return false;
// This could happen if the machine time has changed since the last
// computation. Return false in order to keep computing the floc at the
// anticipated schedule rather than potentially stop computing for a very long
// time.
if (presumed_next_compute_delay >= 2 * kFlocIdScheduledUpdateInterval.Get())
return false;
*next_compute_delay = presumed_next_compute_delay;
return true;
}
} // namespace
......@@ -49,23 +89,22 @@ FlocIdProviderImpl::FlocIdProviderImpl(
cookie_settings_(std::move(cookie_settings)),
floc_remote_permission_service_(floc_remote_permission_service),
history_service_(history_service),
user_event_service_(user_event_service) {
user_event_service_(user_event_service),
floc_id_(FlocId::ReadFromPrefs(prefs_)) {
history_service->AddObserver(this);
sync_service_->AddObserver(this);
g_browser_process->floc_sorting_lsh_clusters_service()->AddObserver(this);
base::Time last_compute_time = FlocId::ReadComputeTimeFromPrefs(prefs_);
if (!last_compute_time.is_null()) {
base::TimeDelta time_since_last_compute =
base::Time::Now() - last_compute_time;
if (time_since_last_compute < GetFlocIdScheduledUpdateInterval()) {
// Keep using the last floc. Schedule a recompute event when it's
// |GetFlocIdScheduledUpdateInterval()| from the last compute time.
floc_id_ = FlocId::ReadFromPrefs(prefs_);
ScheduleFlocComputation(GetFlocIdScheduledUpdateInterval() -
time_since_last_compute);
}
// If the previous floc has expired, invalidate it. The next computation will
// be "immediate", i.e. will occur after we first observe that sync &
// sync-history is enabled and the SortingLSH file is loaded; otherwise, keep
// using the last floc (which may still have be invalid), and schedule a
// recompute event with the desired delay.
base::TimeDelta next_compute_delay;
if (ShouldKeepUsingPreviousFloc(floc_id_, &next_compute_delay)) {
ScheduleFlocComputation(next_compute_delay);
} else {
floc_id_.InvalidateIdAndSaveToPrefs(prefs_);
}
OnStateChanged(sync_service);
......@@ -113,16 +152,15 @@ void FlocIdProviderImpl::OnComputeFlocCompleted(ComputeFlocResult result) {
}
LogFlocComputedEvent(result);
floc_id_ = result.floc_id;
floc_id_ = result.floc_id;
floc_id_.SaveToPrefs(prefs_);
FlocId::SaveComputeTimeToPrefs(base::Time::Now(), prefs_);
ScheduleFlocComputation(GetFlocIdScheduledUpdateInterval());
ScheduleFlocComputation(kFlocIdScheduledUpdateInterval.Get());
}
void FlocIdProviderImpl::LogFlocComputedEvent(const ComputeFlocResult& result) {
if (!base::FeatureList::IsEnabled(features::kFlocIdComputedEventLogging))
if (!base::FeatureList::IsEnabled(kFlocIdComputedEventLogging))
return;
auto specifics = std::make_unique<sync_pb::UserEventSpecifics>();
......@@ -179,8 +217,8 @@ void FlocIdProviderImpl::OnURLsDeleted(
// It'd give us a better idea how often the floc is invalidated due to
// history-delete.
LogFlocComputedEvent(ComputeFlocResult());
floc_id_ = FlocId();
floc_id_.SaveToPrefs(prefs_);
floc_id_.InvalidateIdAndSaveToPrefs(prefs_);
}
void FlocIdProviderImpl::OnSortingLshClustersFileReady() {
......@@ -212,8 +250,7 @@ void FlocIdProviderImpl::MaybeTriggerImmediateComputation() {
return;
bool sorting_lsh_ready_or_not_required =
!base::FeatureList::IsEnabled(
features::kFlocIdSortingLshBasedComputation) ||
!base::FeatureList::IsEnabled(kFlocIdSortingLshBasedComputation) ||
first_sorting_lsh_file_ready_seen_;
if (!first_sync_history_enabled_seen_ || !sorting_lsh_ready_or_not_required)
......@@ -341,8 +378,7 @@ void FlocIdProviderImpl::OnGetRecentlyVisitedURLsCompleted(
}
if (domains.size() <
static_cast<size_t>(
features::kFlocIdMinimumHistoryDomainSizeRequired.Get())) {
static_cast<size_t>(kFlocIdMinimumHistoryDomainSizeRequired.Get())) {
std::move(callback).Run(ComputeFlocResult());
return;
}
......@@ -357,8 +393,7 @@ void FlocIdProviderImpl::ApplySortingLshPostProcessing(
uint64_t sim_hash,
base::Time history_begin_time,
base::Time history_end_time) {
if (!base::FeatureList::IsEnabled(
features::kFlocIdSortingLshBasedComputation)) {
if (!base::FeatureList::IsEnabled(kFlocIdSortingLshBasedComputation)) {
std::move(callback).Run(ComputeFlocResult(
sim_hash, FlocId(sim_hash, history_begin_time, history_end_time,
kSortingLshVersionPlaceholder)));
......
......@@ -168,7 +168,22 @@ class FlocIdProviderImpl : public FlocIdProvider,
// |delay|.
void ScheduleFlocComputation(base::TimeDelta delay);
// The id to be exposed to the JS API.
// The following raw pointer references are guaranteed to outlive this object.
// |prefs_| is owned by Profile, and it won't be destroyed until the
// destructor of Profile is called, where all the profile-keyed services
// including this object will be destroyed. Other services are all created by
// profile-keyed service factories, and the dependency declared in
// FlocIdProviderFactory::FlocIdProviderFactory() guarantees that this object
// will be destroyed first among those services.
PrefService* prefs_;
syncer::SyncService* sync_service_;
scoped_refptr<content_settings::CookieSettings> cookie_settings_;
FlocRemotePermissionService* floc_remote_permission_service_;
history::HistoryService* history_service_;
syncer::UserEventService* user_event_service_;
// The id to be exposed to the JS API. It will always be in sync with the one
// stored in prefs.
FlocId floc_id_;
bool floc_computation_in_progress_ = false;
......@@ -184,20 +199,6 @@ class FlocIdProviderImpl : public FlocIdProvider,
bool first_sorting_lsh_file_ready_seen_ = false;
bool first_sync_history_enabled_seen_ = false;
// The following raw pointer references are guaranteed to outlive this object.
// |prefs_| is owned by Profile, and it won't be destroyed until the
// destructor of Profile is called, where all the profile-keyed services
// including this object will be destroyed. Other services are all created by
// profile-keyed service factories, and the dependency declared in
// FlocIdProviderFactory::FlocIdProviderFactory() guarantees that this object
// will be destroyed first among those services.
PrefService* prefs_;
syncer::SyncService* sync_service_;
scoped_refptr<content_settings::CookieSettings> cookie_settings_;
FlocRemotePermissionService* floc_remote_permission_service_;
history::HistoryService* history_service_;
syncer::UserEventService* user_event_service_;
// Used for the async tasks querying the HistoryService.
base::CancelableTaskTracker history_task_tracker_;
......
......@@ -17,7 +17,7 @@
#include "chrome/browser/sync/test/integration/sync_test.h"
#include "chrome/browser/sync/test/integration/user_events_helper.h"
#include "chrome/browser/sync/user_event_service_factory.h"
#include "chrome/common/chrome_features.h"
#include "components/federated_learning/features/features.h"
#include "components/sync/protocol/user_event_specifics.pb.h"
#include "components/sync_user_events/user_event_service.h"
#include "content/public/test/browser_test.h"
......@@ -42,7 +42,8 @@ class SingleClientUserEventsSyncTest : public SyncTest {
// logged right after sync gets enabled, and could mess up with the test
// expectations. The scenario when the floc-event-logging is enabled is
// tested separately.
feature_list_.InitAndDisableFeature(features::kFlocIdComputedEventLogging);
feature_list_.InitAndDisableFeature(
federated_learning::kFlocIdComputedEventLogging);
}
~SingleClientUserEventsSyncTest() override = default;
......@@ -243,7 +244,8 @@ class SingleClientUserEventsSyncTestFlocEventLoggingEnabled
public:
SingleClientUserEventsSyncTestFlocEventLoggingEnabled() {
feature_list_.Reset();
feature_list_.InitAndEnableFeature(features::kFlocIdComputedEventLogging);
feature_list_.InitAndEnableFeature(
federated_learning::kFlocIdComputedEventLogging);
}
};
......
......@@ -11,7 +11,7 @@
#include "chrome/browser/sync/test/integration/updated_progress_marker_checker.h"
#include "chrome/browser/sync/test/integration/user_events_helper.h"
#include "chrome/browser/sync/user_event_service_factory.h"
#include "chrome/common/chrome_features.h"
#include "components/federated_learning/features/features.h"
#include "components/sync/protocol/user_event_specifics.pb.h"
#include "components/sync_user_events/user_event_service.h"
#include "content/public/test/browser_test.h"
......@@ -31,7 +31,8 @@ class TwoClientUserEventsSyncTest : public SyncTest {
// logged right after sync gets enabled, and could mess up with the test
// expectations. The scenario when the floc-event-logging is enabled is
// tested separately.
feature_list_.InitAndDisableFeature(features::kFlocIdComputedEventLogging);
feature_list_.InitAndDisableFeature(
federated_learning::kFlocIdComputedEventLogging);
}
~TwoClientUserEventsSyncTest() override = default;
......
......@@ -392,29 +392,6 @@ const base::Feature kFlashDeprecationWarning{"FlashDeprecationWarning",
base::FEATURE_ENABLED_BY_DEFAULT};
#endif
// Enables or disables the FlocIdComputed event logging, which happens when a
// floc id is first computed for a browsing session or is refreshed due to a
// long period of time has passed since the last computation.
const base::Feature kFlocIdComputedEventLogging{
"FlocIdComputedEventLogging", base::FEATURE_ENABLED_BY_DEFAULT};
// If enabled, the sim-hash floc computed from history will be further encoded
// based on the sorting-lsh.
const base::Feature kFlocIdSortingLshBasedComputation{
"FlocIdSortingLshBasedComputation", base::FEATURE_DISABLED_BY_DEFAULT};
// The main floc feature for all the subsidiary control and setting params. It's
// controlling the floc update rate, and the minimum history domain size
// required.
// TODO(yaoxia): merge other floc features into this one.
const base::Feature kFederatedLearningOfCohorts{
"FederatedLearningOfCohorts", base::FEATURE_ENABLED_BY_DEFAULT};
constexpr base::FeatureParam<base::TimeDelta> kFlocIdScheduledUpdateInterval{
&kFederatedLearningOfCohorts, "update_interval",
base::TimeDelta::FromDays(7)};
constexpr base::FeatureParam<int> kFlocIdMinimumHistoryDomainSizeRequired{
&kFederatedLearningOfCohorts, "minimum_history_domain_size_required", 3};
// Enables Focus Mode which brings up a PWA-like window look.
const base::Feature kFocusMode{"FocusMode", base::FEATURE_DISABLED_BY_DEFAULT};
......
......@@ -268,19 +268,6 @@ COMPONENT_EXPORT(CHROME_FEATURES)
extern const base::Feature kFlashDeprecationWarning;
#endif
COMPONENT_EXPORT(CHROME_FEATURES)
extern const base::Feature kFlocIdComputedEventLogging;
COMPONENT_EXPORT(CHROME_FEATURES)
extern const base::Feature kFlocIdSortingLshBasedComputation;
COMPONENT_EXPORT(CHROME_FEATURES)
extern const base::Feature kFederatedLearningOfCohorts;
COMPONENT_EXPORT(CHROME_FEATURES)
extern const base::FeatureParam<base::TimeDelta> kFlocIdScheduledUpdateInterval;
COMPONENT_EXPORT(CHROME_FEATURES)
extern const base::FeatureParam<int> kFlocIdMinimumHistoryDomainSizeRequired;
COMPONENT_EXPORT(CHROME_FEATURES)
extern const base::Feature kFocusMode;
......
......@@ -4,6 +4,8 @@
static_library("federated_learning") {
sources = [
"features/features.cc",
"features/features.h",
"floc_constants.cc",
"floc_constants.h",
"floc_id.cc",
......
// Copyright 2020 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/federated_learning/features/features.h"
#include "base/feature_list.h"
namespace federated_learning {
// Enables or disables the FlocIdComputed event logging, which happens when a
// floc id is first computed for a browsing session or is refreshed due to a
// long period of time has passed since the last computation.
const base::Feature kFlocIdComputedEventLogging{
"FlocIdComputedEventLogging", base::FEATURE_ENABLED_BY_DEFAULT};
// If enabled, the sim-hash floc computed from history will be further encoded
// based on the sorting-lsh.
const base::Feature kFlocIdSortingLshBasedComputation{
"FlocIdSortingLshBasedComputation", base::FEATURE_DISABLED_BY_DEFAULT};
// The main floc feature for all the subsidiary control and setting params. It's
// controlling the floc update rate, and the minimum history domain size
// required.
// TODO(yaoxia): merge other floc features into this one.
const base::Feature kFederatedLearningOfCohorts{
"FederatedLearningOfCohorts", base::FEATURE_ENABLED_BY_DEFAULT};
constexpr base::FeatureParam<base::TimeDelta> kFlocIdScheduledUpdateInterval{
&kFederatedLearningOfCohorts, "update_interval",
base::TimeDelta::FromDays(7)};
constexpr base::FeatureParam<int> kFlocIdMinimumHistoryDomainSizeRequired{
&kFederatedLearningOfCohorts, "minimum_history_domain_size_required", 3};
constexpr base::FeatureParam<int> kFlocIdFinchConfigVersion{
&kFederatedLearningOfCohorts, "finch_config_version", 1};
} // namespace federated_learning
// Copyright 2020 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.
#ifndef COMPONENTS_FEDERATED_LEARNING_FEATURES_FEATURES_H_
#define COMPONENTS_FEDERATED_LEARNING_FEATURES_FEATURES_H_
#include "base/feature_list.h"
namespace federated_learning {
extern const base::Feature kFlocIdComputedEventLogging;
extern const base::Feature kFlocIdSortingLshBasedComputation;
extern const base::Feature kFederatedLearningOfCohorts;
extern const base::FeatureParam<base::TimeDelta> kFlocIdScheduledUpdateInterval;
extern const base::FeatureParam<int> kFlocIdMinimumHistoryDomainSizeRequired;
extern const base::FeatureParam<int> kFlocIdFinchConfigVersion;
} // namespace federated_learning
#endif // COMPONENTS_FEDERATED_LEARNING_FEATURES_FEATURES_H_
\ No newline at end of file
......@@ -19,16 +19,19 @@ static_assert(kMaxNumberOfBitsInFloc > 0 &&
const char kFlocIdValuePrefKey[] = "federated_learning.floc_id.value";
const char kFlocIdHistoryBeginTimePrefKey[] =
"federated_learning.floc_id_.history_begin_time";
"federated_learning.floc_id.history_begin_time";
const char kFlocIdHistoryEndTimePrefKey[] =
"federated_learning.floc_id_.history_end_time";
"federated_learning.floc_id.history_end_time";
const char kFlocIdFinchConfigVersionPrefKey[] =
"federated_learning.floc_id.finch_config_version";
const char kFlocIdSortingLshVersionPrefKey[] =
"federated_learning.floc_id_.sorting_lsh_version";
"federated_learning.floc_id.sorting_lsh_version";
const char kFlocIdComputeTimePrefKey[] =
"federated_learning.floc_id_.compute_time";
"federated_learning.floc_id.compute_time";
const char kManifestFlocComponentFormatKey[] = "floc_component_format";
......
......@@ -17,6 +17,7 @@ extern const uint8_t kMaxNumberOfBitsInFloc;
extern const char kFlocIdValuePrefKey[];
extern const char kFlocIdHistoryBeginTimePrefKey[];
extern const char kFlocIdHistoryEndTimePrefKey[];
extern const char kFlocIdFinchConfigVersionPrefKey[];
extern const char kFlocIdSortingLshVersionPrefKey[];
extern const char kFlocIdComputeTimePrefKey[];
extern const char kManifestFlocComponentFormatKey[];
......
......@@ -6,6 +6,7 @@
#include "base/strings/strcat.h"
#include "base/strings/string_number_conversions.h"
#include "components/federated_learning/features/features.h"
#include "components/federated_learning/floc_constants.h"
#include "components/federated_learning/sim_hash.h"
#include "components/prefs/pref_registry_simple.h"
......@@ -13,20 +14,15 @@
namespace federated_learning {
namespace {
// Domain one-hot + sim hash + sorting-lsh (behind a feature flag)
const uint32_t kChromeFlocIdVersion = 1;
} // namespace
// static
uint64_t FlocId::SimHashHistory(
const std::unordered_set<std::string>& domains) {
return SimHashStrings(domains, kMaxNumberOfBitsInFloc);
}
FlocId::FlocId() = default;
FlocId::FlocId()
: finch_config_version_(kFlocIdFinchConfigVersion.Get()),
compute_time_(base::Time::Now()) {}
FlocId::FlocId(uint64_t id,
base::Time history_begin_time,
......@@ -35,7 +31,9 @@ FlocId::FlocId(uint64_t id,
: id_(id),
history_begin_time_(history_begin_time),
history_end_time_(history_end_time),
sorting_lsh_version_(sorting_lsh_version) {}
finch_config_version_(kFlocIdFinchConfigVersion.Get()),
sorting_lsh_version_(sorting_lsh_version),
compute_time_(base::Time::Now()) {}
FlocId::FlocId(const FlocId& id) = default;
......@@ -52,7 +50,9 @@ bool FlocId::IsValid() const {
bool FlocId::operator==(const FlocId& other) const {
return id_ == other.id_ && history_begin_time_ == other.history_begin_time_ &&
history_end_time_ == other.history_end_time_ &&
sorting_lsh_version_ == other.sorting_lsh_version_;
finch_config_version_ == other.finch_config_version_ &&
sorting_lsh_version_ == other.sorting_lsh_version_ &&
compute_time_ == other.compute_time_;
}
bool FlocId::operator!=(const FlocId& other) const {
......@@ -62,8 +62,10 @@ bool FlocId::operator!=(const FlocId& other) const {
std::string FlocId::ToStringForJsApi() const {
DCHECK(id_.has_value());
// TODO(yaoxia): consider returning the version part even when floc is
// invalid.
return base::StrCat({base::NumberToString(id_.value()), ".",
base::NumberToString(kChromeFlocIdVersion), ".",
base::NumberToString(finch_config_version_), ".",
base::NumberToString(sorting_lsh_version_)});
}
......@@ -72,6 +74,7 @@ void FlocId::RegisterPrefs(PrefRegistrySimple* registry) {
registry->RegisterUint64Pref(kFlocIdValuePrefKey, 0);
registry->RegisterTimePref(kFlocIdHistoryBeginTimePrefKey, base::Time());
registry->RegisterTimePref(kFlocIdHistoryEndTimePrefKey, base::Time());
registry->RegisterUint64Pref(kFlocIdFinchConfigVersionPrefKey, 0);
registry->RegisterUint64Pref(kFlocIdSortingLshVersionPrefKey, 0);
registry->RegisterTimePref(kFlocIdComputeTimePrefKey, base::Time());
}
......@@ -79,35 +82,49 @@ void FlocId::RegisterPrefs(PrefRegistrySimple* registry) {
void FlocId::SaveToPrefs(PrefService* prefs) {
if (!id_.has_value()) {
prefs->ClearPref(kFlocIdValuePrefKey);
return;
} else {
prefs->SetUint64(kFlocIdValuePrefKey, id_.value());
}
prefs->SetUint64(kFlocIdValuePrefKey, id_.value());
prefs->SetTime(kFlocIdHistoryBeginTimePrefKey, history_begin_time_);
prefs->SetTime(kFlocIdHistoryEndTimePrefKey, history_end_time_);
prefs->SetUint64(kFlocIdFinchConfigVersionPrefKey, finch_config_version_);
prefs->SetUint64(kFlocIdSortingLshVersionPrefKey, sorting_lsh_version_);
prefs->SetTime(kFlocIdComputeTimePrefKey, compute_time_);
}
void FlocId::InvalidateIdAndSaveToPrefs(PrefService* prefs) {
id_.reset();
prefs->ClearPref(kFlocIdValuePrefKey);
}
// static
FlocId FlocId::ReadFromPrefs(PrefService* prefs) {
if (!prefs->HasPrefPath(kFlocIdValuePrefKey))
return FlocId();
base::Optional<uint64_t> id;
if (prefs->HasPrefPath(kFlocIdValuePrefKey))
id = prefs->GetUint64(kFlocIdValuePrefKey);
return FlocId(prefs->GetUint64(kFlocIdValuePrefKey),
prefs->GetTime(kFlocIdHistoryBeginTimePrefKey),
return FlocId(id, prefs->GetTime(kFlocIdHistoryBeginTimePrefKey),
prefs->GetTime(kFlocIdHistoryEndTimePrefKey),
prefs->GetUint64(kFlocIdSortingLshVersionPrefKey));
}
// static
void FlocId::SaveComputeTimeToPrefs(base::Time compute_time,
PrefService* prefs) {
prefs->SetTime(kFlocIdComputeTimePrefKey, compute_time);
prefs->GetUint64(kFlocIdFinchConfigVersionPrefKey),
prefs->GetUint64(kFlocIdSortingLshVersionPrefKey),
prefs->GetTime(kFlocIdComputeTimePrefKey));
}
// static
base::Time FlocId::ReadComputeTimeFromPrefs(PrefService* prefs) {
return prefs->GetTime(kFlocIdComputeTimePrefKey);
FlocId::FlocId(base::Optional<uint64_t> id,
base::Time history_begin_time,
base::Time history_end_time,
uint32_t finch_config_version,
uint32_t sorting_lsh_version,
base::Time compute_time)
: id_(id),
history_begin_time_(history_begin_time),
history_end_time_(history_end_time),
finch_config_version_(finch_config_version),
sorting_lsh_version_(sorting_lsh_version),
compute_time_(compute_time) {
// If the floc is never computed, the id should be invalid.
DCHECK(!compute_time.is_null() || !id.has_value());
}
} // namespace federated_learning
......@@ -28,8 +28,13 @@ class FlocId {
static uint64_t SimHashHistory(
const std::unordered_set<std::string>& domains);
// Create a newly computed but invalid floc (which implies permission errors,
// insufficient eligible history, or blocked floc). The
// |finch_config_version_| and the |compute_time_| will be set to the current.
FlocId();
// Create a newly computed and valid floc. The |finch_config_version_| and
// the |compute_time_| will be set to the current.
explicit FlocId(uint64_t id,
base::Time history_begin_time,
base::Time history_end_time,
......@@ -43,27 +48,49 @@ class FlocId {
bool operator==(const FlocId& other) const;
bool operator!=(const FlocId& other) const;
// True if the |id_| is successfully computed and hasn't been invalidated
// since the last computation. Note that an invalid FlocId still often has a
// legitimate compute time and finch config version, unless it's read from a
// fresh profile prefs.
bool IsValid() const;
// The id, followed by the chrome floc version, followed by the async floc
// component versions (i.e. model and sorting-lsh). This is the format to be
// exposed to the JS API. Precondition: |id_| must be valid.
// Dot-separated string of floc, finch config version, and sorting-lsh
// version. This is the format to be exposed to the JS API. Precondition:
// |id_| must be valid.
std::string ToStringForJsApi() const;
base::Time history_begin_time() const { return history_begin_time_; }
base::Time history_end_time() const { return history_end_time_; }
uint32_t finch_config_version() const { return finch_config_version_; }
uint32_t sorting_lsh_version() const { return sorting_lsh_version_; }
base::Time compute_time() const { return compute_time_; }
static void RegisterPrefs(PrefRegistrySimple* registry);
void SaveToPrefs(PrefService* prefs);
static FlocId ReadFromPrefs(PrefService* prefs);
static void SaveComputeTimeToPrefs(base::Time compute_time,
PrefService* prefs);
static base::Time ReadComputeTimeFromPrefs(PrefService* prefs);
// Reset |id_| and clear the prefs corresponding to the id. This assumes the
// current floc is already in sync with the prefs and we don't need to save
// other unaffected field.
void InvalidateIdAndSaveToPrefs(PrefService* prefs);
private:
friend class FlocIdTester;
// Create a floc with stated params. This will only be used to create a floc
// read from prefs.
explicit FlocId(base::Optional<uint64_t> id,
base::Time history_begin_time,
base::Time history_end_time,
uint32_t finch_config_version,
uint32_t sorting_lsh_version,
base::Time compute_time);
base::Optional<uint64_t> id_;
// The time range of the actual history used to compute the floc. This should
......@@ -71,8 +98,16 @@ class FlocId {
base::Time history_begin_time_;
base::Time history_end_time_;
// The kFlocIdFinchConfigVersion feature param. When floc is loaded from
// prefs, this could be different from the current feature param state.
uint32_t finch_config_version_ = 0;
// The main version (i.e. 1st int) of the sorting lsh component version.
uint32_t sorting_lsh_version_ = 0;
// The time when the floc was computed. compute_time_.is_null() means the
// floc has never been computed before, and implies |id_| is also invalid.
base::Time compute_time_;
};
} // namespace federated_learning
......
......@@ -4,6 +4,9 @@
#include "components/federated_learning/floc_id.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/task_environment.h"
#include "components/federated_learning/features/features.h"
#include "components/federated_learning/floc_constants.h"
#include "components/prefs/testing_pref_service.h"
#include "testing/gtest/include/gtest/gtest.h"
......@@ -14,13 +17,24 @@ const base::Time kTime0 = base::Time();
const base::Time kTime1 = base::Time::FromTimeT(1);
const base::Time kTime2 = base::Time::FromTimeT(2);
TEST(FlocIdTest, IsValid) {
class FlocIdUnitTest : public testing::Test {
public:
FlocIdUnitTest()
: task_environment_(base::test::TaskEnvironment::TimeSource::MOCK_TIME) {}
~FlocIdUnitTest() override = default;
protected:
base::test::SingleThreadTaskEnvironment task_environment_;
};
TEST_F(FlocIdUnitTest, IsValid) {
EXPECT_FALSE(FlocId().IsValid());
EXPECT_TRUE(FlocId(0, kTime0, kTime0, 0).IsValid());
EXPECT_TRUE(FlocId(0, kTime1, kTime2, 1).IsValid());
}
TEST(FlocIdTest, Comparison) {
TEST_F(FlocIdUnitTest, Comparison) {
EXPECT_EQ(FlocId(), FlocId());
EXPECT_EQ(FlocId(0, kTime0, kTime0, 0), FlocId(0, kTime0, kTime0, 0));
......@@ -33,59 +47,76 @@ TEST(FlocIdTest, Comparison) {
EXPECT_NE(FlocId(0, kTime0, kTime0, 0), FlocId(0, kTime0, kTime0, 1));
}
TEST(FlocIdTest, ToStringForJsApi) {
TEST_F(FlocIdUnitTest, ToStringForJsApi) {
EXPECT_EQ("0.1.0", FlocId(0, kTime0, kTime0, 0).ToStringForJsApi());
EXPECT_EQ("12345.1.0", FlocId(12345, kTime0, kTime0, 0).ToStringForJsApi());
EXPECT_EQ("12345.1.2", FlocId(12345, kTime1, kTime1, 2).ToStringForJsApi());
base::test::ScopedFeatureList feature_list;
feature_list.InitAndEnableFeatureWithParameters(
kFederatedLearningOfCohorts, {{"finch_config_version", "99"}});
EXPECT_EQ("0.99.0", FlocId(0, kTime0, kTime0, 0).ToStringForJsApi());
EXPECT_EQ("12345.99.0", FlocId(12345, kTime0, kTime0, 0).ToStringForJsApi());
EXPECT_EQ("12345.99.2", FlocId(12345, kTime1, kTime1, 2).ToStringForJsApi());
}
TEST(FlocIdTest, ReadFromPrefs_DefaultInvalid) {
TEST_F(FlocIdUnitTest, ReadFromPrefs_DefaultInvalid) {
TestingPrefServiceSimple prefs;
FlocId::RegisterPrefs(prefs.registry());
FlocId floc_id = FlocId::ReadFromPrefs(&prefs);
EXPECT_FALSE(floc_id.IsValid());
base::Time compute_time = FlocId::ReadComputeTimeFromPrefs(&prefs);
EXPECT_TRUE(compute_time.is_null());
EXPECT_FALSE(floc_id.IsValid());
EXPECT_TRUE(floc_id.history_begin_time().is_null());
EXPECT_TRUE(floc_id.history_end_time().is_null());
EXPECT_EQ(0u, floc_id.finch_config_version());
EXPECT_EQ(0u, floc_id.sorting_lsh_version());
EXPECT_TRUE(floc_id.compute_time().is_null());
}
TEST(FlocIdTest, ReadFromPrefs_SavedInvalid) {
TEST_F(FlocIdUnitTest, ReadFromPrefs_SavedInvalid) {
TestingPrefServiceSimple prefs;
FlocId::RegisterPrefs(prefs.registry());
prefs.ClearPref(kFlocIdValuePrefKey);
prefs.SetTime(kFlocIdHistoryBeginTimePrefKey, base::Time::FromTimeT(1));
prefs.SetTime(kFlocIdHistoryEndTimePrefKey, base::Time::FromTimeT(2));
prefs.SetUint64(kFlocIdSortingLshVersionPrefKey, 2);
prefs.SetUint64(kFlocIdFinchConfigVersionPrefKey, 3);
prefs.SetUint64(kFlocIdSortingLshVersionPrefKey, 4);
prefs.SetTime(kFlocIdComputeTimePrefKey, base::Time::FromTimeT(5));
FlocId floc_id = FlocId::ReadFromPrefs(&prefs);
EXPECT_FALSE(floc_id.IsValid());
prefs.SetTime(kFlocIdComputeTimePrefKey, base::Time());
base::Time compute_time = FlocId::ReadComputeTimeFromPrefs(&prefs);
EXPECT_TRUE(compute_time.is_null());
EXPECT_EQ(base::Time::FromTimeT(1), floc_id.history_begin_time());
EXPECT_EQ(base::Time::FromTimeT(2), floc_id.history_end_time());
EXPECT_EQ(3u, floc_id.finch_config_version());
EXPECT_EQ(4u, floc_id.sorting_lsh_version());
EXPECT_EQ(base::Time::FromTimeT(5), floc_id.compute_time());
}
TEST(FlocIdTest, ReadFromPrefs_SavedValid) {
TEST_F(FlocIdUnitTest, ReadFromPrefs_SavedValid) {
TestingPrefServiceSimple prefs;
FlocId::RegisterPrefs(prefs.registry());
prefs.SetUint64(kFlocIdValuePrefKey, 123);
prefs.SetTime(kFlocIdHistoryBeginTimePrefKey, base::Time::FromTimeT(1));
prefs.SetTime(kFlocIdHistoryEndTimePrefKey, base::Time::FromTimeT(2));
prefs.SetUint64(kFlocIdSortingLshVersionPrefKey, 2);
prefs.SetUint64(kFlocIdFinchConfigVersionPrefKey, 3);
prefs.SetUint64(kFlocIdSortingLshVersionPrefKey, 4);
prefs.SetTime(kFlocIdComputeTimePrefKey, base::Time::FromTimeT(5));
FlocId floc_id = FlocId::ReadFromPrefs(&prefs);
EXPECT_EQ(floc_id,
FlocId(123, base::Time::FromTimeT(1), base::Time::FromTimeT(2), 2));
prefs.SetTime(kFlocIdComputeTimePrefKey, base::Time::FromTimeT(3));
base::Time compute_time = FlocId::ReadComputeTimeFromPrefs(&prefs);
EXPECT_EQ(compute_time, base::Time::FromTimeT(3));
EXPECT_TRUE(floc_id.IsValid());
EXPECT_EQ(base::Time::FromTimeT(1), floc_id.history_begin_time());
EXPECT_EQ(base::Time::FromTimeT(2), floc_id.history_end_time());
EXPECT_EQ(3u, floc_id.finch_config_version());
EXPECT_EQ(4u, floc_id.sorting_lsh_version());
EXPECT_EQ(base::Time::FromTimeT(5), floc_id.compute_time());
EXPECT_EQ("123.3.4", floc_id.ToStringForJsApi());
}
TEST(FlocIdTest, SaveToPrefs_InvalidFloc) {
TEST_F(FlocIdUnitTest, SaveToPrefs_InvalidFloc) {
TestingPrefServiceSimple prefs;
FlocId::RegisterPrefs(prefs.registry());
......@@ -93,41 +124,64 @@ TEST(FlocIdTest, SaveToPrefs_InvalidFloc) {
floc_id.SaveToPrefs(&prefs);
EXPECT_FALSE(prefs.HasPrefPath(kFlocIdValuePrefKey));
EXPECT_FALSE(prefs.HasPrefPath(kFlocIdHistoryBeginTimePrefKey));
EXPECT_FALSE(prefs.HasPrefPath(kFlocIdHistoryEndTimePrefKey));
EXPECT_FALSE(prefs.HasPrefPath(kFlocIdSortingLshVersionPrefKey));
EXPECT_TRUE(prefs.HasPrefPath(kFlocIdHistoryBeginTimePrefKey));
EXPECT_TRUE(prefs.HasPrefPath(kFlocIdHistoryEndTimePrefKey));
EXPECT_TRUE(prefs.HasPrefPath(kFlocIdFinchConfigVersionPrefKey));
EXPECT_TRUE(prefs.HasPrefPath(kFlocIdSortingLshVersionPrefKey));
EXPECT_TRUE(prefs.HasPrefPath(kFlocIdComputeTimePrefKey));
EXPECT_EQ(0u, prefs.GetUint64(kFlocIdValuePrefKey));
EXPECT_TRUE(prefs.GetTime(kFlocIdHistoryBeginTimePrefKey).is_null());
EXPECT_TRUE(prefs.GetTime(kFlocIdHistoryEndTimePrefKey).is_null());
EXPECT_EQ(1u, prefs.GetUint64(kFlocIdFinchConfigVersionPrefKey));
EXPECT_EQ(0u, prefs.GetUint64(kFlocIdSortingLshVersionPrefKey));
FlocId::SaveComputeTimeToPrefs(base::Time(), &prefs);
EXPECT_TRUE(prefs.GetTime(kFlocIdComputeTimePrefKey).is_null());
EXPECT_EQ(base::Time::Now(), prefs.GetTime(kFlocIdComputeTimePrefKey));
}
TEST(FlocIdTest, SaveToPrefs_ValidFloc) {
TEST_F(FlocIdUnitTest, SaveToPrefs_ValidFloc) {
TestingPrefServiceSimple prefs;
FlocId::RegisterPrefs(prefs.registry());
FlocId floc_id =
FlocId(123, base::Time::FromTimeT(1), base::Time::FromTimeT(2), 2);
FlocId(123, base::Time::FromTimeT(1), base::Time::FromTimeT(2), 3);
floc_id.SaveToPrefs(&prefs);
EXPECT_TRUE(prefs.HasPrefPath(kFlocIdValuePrefKey));
EXPECT_TRUE(prefs.HasPrefPath(kFlocIdHistoryBeginTimePrefKey));
EXPECT_TRUE(prefs.HasPrefPath(kFlocIdHistoryEndTimePrefKey));
EXPECT_TRUE(prefs.HasPrefPath(kFlocIdFinchConfigVersionPrefKey));
EXPECT_TRUE(prefs.HasPrefPath(kFlocIdSortingLshVersionPrefKey));
EXPECT_TRUE(prefs.HasPrefPath(kFlocIdComputeTimePrefKey));
EXPECT_EQ(123u, prefs.GetUint64(kFlocIdValuePrefKey));
EXPECT_EQ(base::Time::FromTimeT(1),
prefs.GetTime(kFlocIdHistoryBeginTimePrefKey));
EXPECT_EQ(base::Time::FromTimeT(2),
prefs.GetTime(kFlocIdHistoryEndTimePrefKey));
EXPECT_EQ(2u, prefs.GetUint64(kFlocIdSortingLshVersionPrefKey));
EXPECT_EQ(1u, prefs.GetUint64(kFlocIdFinchConfigVersionPrefKey));
EXPECT_EQ(3u, prefs.GetUint64(kFlocIdSortingLshVersionPrefKey));
EXPECT_EQ(base::Time::Now(), prefs.GetTime(kFlocIdComputeTimePrefKey));
}
FlocId::SaveComputeTimeToPrefs(base::Time::FromTimeT(3), &prefs);
EXPECT_EQ(base::Time::FromTimeT(3), prefs.GetTime(kFlocIdComputeTimePrefKey));
TEST_F(FlocIdUnitTest, InvalidateIdAndSaveToPrefs) {
TestingPrefServiceSimple prefs;
FlocId::RegisterPrefs(prefs.registry());
FlocId floc_id =
FlocId(123, base::Time::FromTimeT(1), base::Time::FromTimeT(2), 3);
floc_id.SaveToPrefs(&prefs);
floc_id.InvalidateIdAndSaveToPrefs(&prefs);
EXPECT_FALSE(floc_id.IsValid());
EXPECT_FALSE(prefs.HasPrefPath(kFlocIdValuePrefKey));
EXPECT_EQ(base::Time::FromTimeT(1),
prefs.GetTime(kFlocIdHistoryBeginTimePrefKey));
EXPECT_EQ(base::Time::FromTimeT(2),
prefs.GetTime(kFlocIdHistoryEndTimePrefKey));
EXPECT_EQ(1u, prefs.GetUint64(kFlocIdFinchConfigVersionPrefKey));
EXPECT_EQ(3u, prefs.GetUint64(kFlocIdSortingLshVersionPrefKey));
EXPECT_EQ(base::Time::Now(), prefs.GetTime(kFlocIdComputeTimePrefKey));
}
} // namespace federated_learning
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment