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