• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2024 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "components/metrics/dwa/dwa_service.h"
6 
7 #include <memory>
8 #include <string>
9 #include <string_view>
10 
11 #include "base/containers/fixed_flat_set.h"
12 #include "base/i18n/timezone.h"
13 #include "base/rand_util.h"
14 #include "base/strings/string_util.h"
15 #include "base/version.h"
16 #include "components/metrics/dwa/dwa_pref_names.h"
17 #include "components/metrics/metrics_log.h"
18 #include "components/metrics/metrics_pref_names.h"
19 #include "components/prefs/pref_service.h"
20 #include "components/version_info/version_info.h"
21 
22 namespace metrics::dwa {
23 
24 // Set of countries in the European Economic Area. Used by
25 // RecordCoarseSystemInformation to set geo_designation fields in
26 // CoarseSystemInfo. These will need to be manually updated using
27 // "IsEuropeanEconomicArea" from go/source/user_preference_country.impl.gcl.
28 constexpr auto kEuropeanEconomicAreaCountries =
29     base::MakeFixedFlatSet<std::string_view>({
30         "at",  // Austria
31         "be",  // Belgium
32         "bg",  // Bulgaria
33         "hr",  // Croatia
34         "cy",  // Cyprus
35         "cz",  // Czech Republic
36         "dk",  // Denmark
37         "ee",  // Estonia
38         "fi",  // Finland
39         "fr",  // France
40         "de",  // Germany
41         "gr",  // Greece
42         "hu",  // Hungary
43         "is",  // Iceland
44         "ie",  // Ireland
45         "it",  // Italy
46         "lv",  // Latvia
47         "li",  // Liechtenstein
48         "lt",  // Lithuania
49         "lu",  // Luxembourg
50         "mt",  // Malta
51         "nl",  // Netherlands
52         "no",  // Norway
53         "pl",  // Poland
54         "pt",  // Portugal
55         "ro",  // Romania
56         "sk",  // Slovakia
57         "si",  // Slovenia
58         "es",  // Spain
59         "se",  // Sweden
60         "uk",  // United Kingdom
61     });
62 
63 // Number of seconds in a week or seven days. (604800 = 7 * 24 * 60 * 60)
64 const int kOneWeekInSeconds = base::Days(7).InSeconds();
65 
66 const size_t kMinLogQueueCount = 10;
67 const size_t kMinLogQueueSizeBytes = 300 * 1024;  // 300 KiB
68 const size_t kMaxLogSizeBytes = 1024 * 1024;      // 1 MiB
69 
DwaService(MetricsServiceClient * client,PrefService * local_state)70 DwaService::DwaService(MetricsServiceClient* client, PrefService* local_state)
71     : recorder_(DwaRecorder::Get()),
72       client_(client),
73       pref_service_(local_state),
74       reporting_service_(client, local_state, GetLogStoreLimits()) {
75   reporting_service_.Initialize();
76   // Set up the scheduler for DwaService.
77   auto rotate_callback = base::BindRepeating(&DwaService::RotateLog,
78                                              self_ptr_factory_.GetWeakPtr());
79   auto get_upload_interval_callback =
80       base::BindRepeating(&metrics::MetricsServiceClient::GetUploadInterval,
81                           base::Unretained(client_));
82   bool fast_startup_for_testing = client_->ShouldStartUpFastForTesting();
83   scheduler_ = std::make_unique<MetricsRotationScheduler>(
84       rotate_callback, get_upload_interval_callback, fast_startup_for_testing);
85   scheduler_->InitTaskComplete();
86 }
87 
~DwaService()88 DwaService::~DwaService() {
89   recorder_->DisableRecording();
90   DisableReporting();
91 }
92 
EnableReporting()93 void DwaService::EnableReporting() {
94   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
95   if (reporting_service_.reporting_active()) {
96     return;
97   }
98 
99   scheduler_->Start();
100   reporting_service_.EnableReporting();
101   // Attempt to upload if there are unsent logs.
102   if (reporting_service_.unsent_log_store()->has_unsent_logs()) {
103     reporting_service_.Start();
104   }
105 }
106 
DisableReporting()107 void DwaService::DisableReporting() {
108   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
109   reporting_service_.DisableReporting();
110   scheduler_->Stop();
111   Flush(metrics::MetricsLogsEventManager::CreateReason::kServiceShutdown);
112 }
113 
Flush(metrics::MetricsLogsEventManager::CreateReason reason)114 void DwaService::Flush(metrics::MetricsLogsEventManager::CreateReason reason) {
115   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
116   // The log should not be built if there aren't any events to log.
117   if (!recorder_->HasPageLoadEvents()) {
118     return;
119   }
120 
121   BuildAndStoreLog(reason);
122   reporting_service_.unsent_log_store()->TrimAndPersistUnsentLogs(true);
123 }
124 
Purge()125 void DwaService::Purge() {
126   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
127   recorder_->Purge();
128   reporting_service_.unsent_log_store()->Purge();
129 }
130 
131 // static
GetLogStoreLimits()132 UnsentLogStore::UnsentLogStoreLimits DwaService::GetLogStoreLimits() {
133   return UnsentLogStore::UnsentLogStoreLimits{
134       .min_log_count = kMinLogQueueCount,
135       .min_queue_size_bytes = kMinLogQueueSizeBytes,
136       .max_log_size_bytes = kMaxLogSizeBytes,
137   };
138 }
139 
140 // static
RecordCoarseSystemInformation(MetricsServiceClient & client,const PrefService & local_state,::dwa::CoarseSystemInfo * coarse_system_info)141 void DwaService::RecordCoarseSystemInformation(
142     MetricsServiceClient& client,
143     const PrefService& local_state,
144     ::dwa::CoarseSystemInfo* coarse_system_info) {
145   switch (client.GetChannel()) {
146     case SystemProfileProto::CHANNEL_STABLE:
147       coarse_system_info->set_channel(::dwa::CoarseSystemInfo::CHANNEL_STABLE);
148       break;
149     case SystemProfileProto::CHANNEL_CANARY:
150     case SystemProfileProto::CHANNEL_DEV:
151     case SystemProfileProto::CHANNEL_BETA:
152       coarse_system_info->set_channel(
153           ::dwa::CoarseSystemInfo::CHANNEL_NOT_STABLE);
154       break;
155     case SystemProfileProto::CHANNEL_UNKNOWN:
156       coarse_system_info->set_channel(::dwa::CoarseSystemInfo::CHANNEL_INVALID);
157       break;
158   }
159 
160 #if BUILDFLAG(IS_WIN)
161   coarse_system_info->set_platform(::dwa::CoarseSystemInfo::PLATFORM_WINDOWS);
162 #elif BUILDFLAG(IS_MAC)
163   coarse_system_info->set_platform(::dwa::CoarseSystemInfo::PLATFORM_MACOS);
164 #elif BUILDFLAG(IS_LINUX)
165   coarse_system_info->set_platform(::dwa::CoarseSystemInfo::PLATFORM_LINUX);
166 #elif BUILDFLAG(IS_ANDROID)
167   // TODO(b/366276323): Populate set_platform using more granular
168   // PLATFORM_ANDROID enum.
169   coarse_system_info->set_platform(::dwa::CoarseSystemInfo::PLATFORM_ANDROID);
170 #elif BUILDFLAG(IS_IOS)
171   coarse_system_info->set_platform(::dwa::CoarseSystemInfo::PLATFORM_IOS);
172 #elif BUILDFLAG(IS_CHROMEOS_ASH) || BUILDFLAG(IS_CHROMEOS_LACROS)
173   coarse_system_info->set_platform(::dwa::CoarseSystemInfo::PLATFORM_CHROMEOS);
174 #else
175   coarse_system_info->set_platform(::dwa::CoarseSystemInfo::PLATFORM_OTHER);
176 #endif
177 
178   std::string country =
179       base::ToLowerASCII(base::CountryCodeForCurrentTimezone());
180   if (country == "") {
181     coarse_system_info->set_geo_designation(
182         ::dwa::CoarseSystemInfo::GEO_DESIGNATION_INVALID);
183   } else if (kEuropeanEconomicAreaCountries.contains(country)) {
184     coarse_system_info->set_geo_designation(
185         ::dwa::CoarseSystemInfo::GEO_DESIGNATION_EEA);
186   } else {
187     // GEO_DESIGNATION_ROW is the geo designation for "rest of the world".
188     coarse_system_info->set_geo_designation(
189         ::dwa::CoarseSystemInfo::GEO_DESIGNATION_ROW);
190   }
191 
192   int64_t seconds_since_install =
193       MetricsLog::GetCurrentTime() -
194       local_state.GetInt64(metrics::prefs::kInstallDate);
195   coarse_system_info->set_client_age(
196       seconds_since_install < kOneWeekInSeconds
197           ? ::dwa::CoarseSystemInfo::CLIENT_AGE_RECENT
198           : ::dwa::CoarseSystemInfo::CLIENT_AGE_NOT_RECENT);
199 
200   // GetVersion() returns base::Version, which represents a dotted version
201   // number, like "1.2.3.4". We %16 in milestone_prefix_trimmed because it is
202   // required by the DWA proto in
203   // //third_party/metrics_proto/dwa/deidentified_web_analytics.proto.
204   int milestone = version_info::GetVersion().components()[0];
205   coarse_system_info->set_milestone_prefix_trimmed(milestone % 16);
206 
207   coarse_system_info->set_is_ukm_enabled(client.IsUkmAllowedForAllProfiles());
208 }
209 
210 // static
GetEphemeralClientId(PrefService & local_state)211 uint64_t DwaService::GetEphemeralClientId(PrefService& local_state) {
212   // We want to update the client id once a day (measured in UTC), so our date
213   // should only contain information up to day level.
214   base::Time now_day_level = base::Time::Now().UTCMidnight();
215 
216   uint64_t client_id = local_state.GetUint64(prefs::kDwaClientId);
217   if (local_state.GetTime(prefs::kDwaClientIdLastUpdated) != now_day_level ||
218       client_id == 0u) {
219     client_id = 0u;
220     while (!client_id) {
221       client_id = base::RandUint64();
222     }
223     local_state.SetUint64(prefs::kDwaClientId, client_id);
224 
225     local_state.SetTime(prefs::kDwaClientIdLastUpdated, now_day_level);
226   }
227 
228   return client_id;
229 }
230 
RotateLog()231 void DwaService::RotateLog() {
232   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
233   if (!reporting_service_.unsent_log_store()->has_unsent_logs()) {
234     BuildAndStoreLog(metrics::MetricsLogsEventManager::CreateReason::kPeriodic);
235   }
236   reporting_service_.Start();
237   scheduler_->RotationFinished();
238 }
239 
BuildAndStoreLog(metrics::MetricsLogsEventManager::CreateReason reason)240 void DwaService::BuildAndStoreLog(
241     metrics::MetricsLogsEventManager::CreateReason reason) {
242   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
243   // There are no new page load events, so no new logs should be created.
244   if (!recorder_->HasPageLoadEvents()) {
245     return;
246   }
247 
248   ::dwa::DeidentifiedWebAnalyticsReport report;
249   RecordCoarseSystemInformation(*client_, *pref_service_,
250                                 report.mutable_coarse_system_info());
251   report.set_dwa_ephemeral_id(GetEphemeralClientId(*pref_service_));
252 
253   std::vector<::dwa::PageLoadEvents> page_load_events =
254       recorder_->TakePageLoadEvents();
255   report.mutable_page_load_events()->Add(
256       std::make_move_iterator(page_load_events.begin()),
257       std::make_move_iterator(page_load_events.end()));
258 
259   report.set_timestamp(MetricsLog::GetCurrentTime());
260 
261   std::string serialized_log;
262   report.SerializeToString(&serialized_log);
263 
264   LogMetadata metadata;
265   reporting_service_.unsent_log_store()->StoreLog(serialized_log, metadata,
266                                                   reason);
267 }
268 
269 // static
RegisterPrefs(PrefRegistrySimple * registry)270 void DwaService::RegisterPrefs(PrefRegistrySimple* registry) {
271   registry->RegisterUint64Pref(prefs::kDwaClientId, 0u);
272   registry->RegisterTimePref(prefs::kDwaClientIdLastUpdated, base::Time());
273   DwaReportingService::RegisterPrefs(registry);
274 }
275 
unsent_log_store()276 metrics::UnsentLogStore* DwaService::unsent_log_store() {
277   return reporting_service_.unsent_log_store();
278 }
279 
280 }  // namespace metrics::dwa
281