1 // Copyright 2023 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/structured/structured_metrics_recorder.h"
6
7 #include <sstream>
8 #include <utility>
9
10 #include "base/feature_list.h"
11 #include "base/logging.h"
12 #include "base/metrics/histogram_macros.h"
13 #include "base/metrics/metrics_hashes.h"
14 #include "base/strings/string_number_conversions.h"
15 #include "base/strings/string_split.h"
16 #include "base/task/current_thread.h"
17 #include "components/metrics/metrics_features.h"
18 #include "components/metrics/structured/enums.h"
19 #include "components/metrics/structured/histogram_util.h"
20 #include "components/metrics/structured/project_validator.h"
21 #include "components/metrics/structured/storage.pb.h"
22 #include "components/metrics/structured/structured_metrics_features.h"
23 #include "components/metrics/structured/structured_metrics_validator.h"
24 #include "structured_metrics_recorder.h"
25 #include "third_party/metrics_proto/chrome_user_metrics_extension.pb.h"
26
27 namespace metrics::structured {
28
StructuredMetricsRecorder(std::unique_ptr<KeyDataProvider> key_data_provider,std::unique_ptr<EventStorage> event_storage)29 StructuredMetricsRecorder::StructuredMetricsRecorder(
30 std::unique_ptr<KeyDataProvider> key_data_provider,
31 std::unique_ptr<EventStorage> event_storage)
32 : key_data_provider_(std::move(key_data_provider)),
33 event_storage_(std::move(event_storage)) {
34 CHECK(key_data_provider_);
35 CHECK(event_storage_);
36 Recorder::GetInstance()->AddObserver(this);
37 key_data_provider_->AddObserver(this);
38 }
39
~StructuredMetricsRecorder()40 StructuredMetricsRecorder::~StructuredMetricsRecorder() {
41 Recorder::GetInstance()->RemoveObserver(this);
42 key_data_provider_->RemoveObserver(this);
43 }
44
EnableRecording()45 void StructuredMetricsRecorder::EnableRecording() {
46 DCHECK(base::CurrentUIThread::IsSet());
47 // Enable recording only if structured metrics' feature flag is enabled.
48 recording_enabled_ =
49 base::FeatureList::IsEnabled(features::kStructuredMetrics);
50 if (recording_enabled_) {
51 CacheDisallowedProjectsSet();
52 }
53 }
54
DisableRecording()55 void StructuredMetricsRecorder::DisableRecording() {
56 DCHECK(base::CurrentUIThread::IsSet());
57 recording_enabled_ = false;
58 disallowed_projects_.clear();
59 }
60
OnKeyReady()61 void StructuredMetricsRecorder::OnKeyReady() {
62 DCHECK(base::CurrentUIThread::IsSet());
63
64 // If key data has not been initialized, it is highly likely that the key data
65 // is initialized.
66 if (!init_state_.Has(State::kKeyDataInitialized)) {
67 init_state_.Put(State::kKeyDataInitialized);
68 }
69
70 // If kKeyDataInitialized, then this is the second time this callback is being
71 // called, which must be the profile keys.
72 else if (init_state_.Has(State::kProfileAdded)) {
73 init_state_.Put(State::kProfileKeyDataInitialized);
74 }
75
76 // If recorder is now ready then hash events in-memory and store them in
77 // persistent storage.
78 if (CanProvideMetrics()) {
79 HashUnhashedEventsAndPersist();
80 if (!on_ready_callback_.is_null()) {
81 std::move(on_ready_callback_).Run();
82 }
83 }
84 }
85
ProvideUmaEventMetrics(ChromeUserMetricsExtension & uma_proto)86 void StructuredMetricsRecorder::ProvideUmaEventMetrics(
87 ChromeUserMetricsExtension& uma_proto) {
88 // no-op
89 }
90
ProvideEventMetrics(ChromeUserMetricsExtension & uma_proto)91 void StructuredMetricsRecorder::ProvideEventMetrics(
92 ChromeUserMetricsExtension& uma_proto) {
93 if (!CanProvideMetrics()) {
94 return;
95 }
96
97 // Get the events from event storage.
98 event_storage_->MoveEvents(uma_proto);
99
100 const auto& structured_data = uma_proto.structured_data();
101 LogUploadSizeBytes(structured_data.ByteSizeLong());
102 LogNumEventsInUpload(structured_data.events_size());
103
104 // Applies custom metadata providers.
105 Recorder::GetInstance()->OnProvideIndependentMetrics(&uma_proto);
106 }
107
IsInitialized()108 bool StructuredMetricsRecorder::IsInitialized() {
109 return init_state_.Has(State::kKeyDataInitialized);
110 }
111
IsProfileInitialized()112 bool StructuredMetricsRecorder::IsProfileInitialized() {
113 return init_state_.Has(State::kProfileKeyDataInitialized);
114 }
115
Purge()116 void StructuredMetricsRecorder::Purge() {
117 CHECK(event_storage_);
118 event_storage_->Purge();
119 key_data_provider_->Purge();
120 }
121
OnProfileAdded(const base::FilePath & profile_path)122 void StructuredMetricsRecorder::OnProfileAdded(
123 const base::FilePath& profile_path) {
124 DCHECK(base::CurrentUIThread::IsSet());
125
126 // We do not handle multiprofile, instead initializing with the state stored
127 // in the first logged-in user's cryptohome. So if a second profile is added
128 // we should ignore it.
129 if (init_state_.Has(State::kProfileAdded)) {
130 return;
131 }
132 key_data_provider_->OnProfileAdded(profile_path);
133 init_state_.Put(State::kProfileAdded);
134
135 event_storage_->OnProfileAdded(profile_path);
136
137 // See DisableRecording for more information.
138 if (purge_state_on_init_) {
139 Purge();
140 purge_state_on_init_ = false;
141 }
142 }
143
OnEventRecord(const Event & event)144 void StructuredMetricsRecorder::OnEventRecord(const Event& event) {
145 DCHECK(base::CurrentUIThread::IsSet());
146
147 // One more state for the EventRecordingState exists: kMetricsProviderMissing.
148 // This is recorded in Recorder::Record.
149 if (!recording_enabled_ && !CanForceRecord(event)) {
150 // Events should be ignored if recording is disabled.
151 LogEventRecordingState(EventRecordingState::kRecordingDisabled);
152 return;
153 }
154
155 if (IsDeviceEvent(event) && !IsInitialized()) {
156 RecordEventBeforeInitialization(event);
157 return;
158 }
159
160 if (IsProfileEvent(event) && !IsProfileInitialized()) {
161 RecordProfileEventBeforeInitialization(event);
162 return;
163 }
164
165 RecordEvent(event);
166
167 test_callback_on_record_.Run();
168 }
169
HasState(State state) const170 bool StructuredMetricsRecorder::HasState(State state) const {
171 return init_state_.Has(state);
172 }
173
OnReportingStateChanged(bool enabled)174 void StructuredMetricsRecorder::OnReportingStateChanged(bool enabled) {
175 DCHECK(base::CurrentUIThread::IsSet());
176
177 // When reporting is enabled, OnRecordingEnabled is also called. Let that
178 // handle enabling.
179 if (enabled) {
180 return;
181 }
182
183 // Clean up any events that were recording during the pre-user.
184 if (!recording_enabled_ && !enabled) {
185 Purge();
186 }
187
188 // When reporting is disabled, OnRecordingDisabled is also called. Disabling
189 // here is redundant but done for clarity.
190 recording_enabled_ = false;
191
192 // Delete keys and unsent logs. We need to handle two cases:
193 //
194 // 1. A profile hasn't been added yet and we can't delete the files
195 // immediately. In this case set |purge_state_on_init_| and let
196 // OnProfileAdded call Purge after initialization.
197 //
198 // 2. A profile has been added and so the backing PersistentProtos have been
199 // constructed. In this case just call Purge directly.
200 //
201 // Note that Purge will ensure the events are deleted from disk even if the
202 // PersistentProto hasn't itself finished being read.
203 if (!IsInitialized()) {
204 purge_state_on_init_ = true;
205 } else {
206 Purge();
207 }
208 }
209
SetOnReadyToRecord(base::OnceClosure callback)210 void StructuredMetricsRecorder::SetOnReadyToRecord(base::OnceClosure callback) {
211 on_ready_callback_ = std::move(callback);
212
213 if (IsInitialized()) {
214 std::move(on_ready_callback_).Run();
215 }
216 }
217
RecordEventBeforeInitialization(const Event & event)218 void StructuredMetricsRecorder::RecordEventBeforeInitialization(
219 const Event& event) {
220 DCHECK(!IsInitialized());
221 unhashed_events_.emplace_back(event.Clone());
222 }
223
RecordProfileEventBeforeInitialization(const Event & event)224 void StructuredMetricsRecorder::RecordProfileEventBeforeInitialization(
225 const Event& event) {
226 DCHECK(!IsProfileInitialized());
227 unhashed_profile_events_.emplace_back(event.Clone());
228 }
229
RecordEvent(const Event & event)230 void StructuredMetricsRecorder::RecordEvent(const Event& event) {
231 DCHECK(IsKeyDataInitialized());
232
233 // Retrieve key for the project.
234 KeyData* key_data = key_data_provider_->GetKeyData(event.project_name());
235 if (!key_data) {
236 return;
237 }
238
239 // Validates the event. If valid, retrieve the metadata associated
240 // with the event.
241 const auto validators = GetEventValidators(event);
242 if (!validators) {
243 return;
244 }
245
246 const auto* project_validator = validators->first;
247 const auto* event_validator = validators->second;
248
249 if (!CanUploadProject(project_validator->project_hash())) {
250 LogEventRecordingState(EventRecordingState::kProjectDisallowed);
251 return;
252 }
253
254 LogEventRecordingState(EventRecordingState::kRecorded);
255
256 // Events associated with UMA are deprecated.
257 if (!IsIndependentMetricsUploadEnabled() ||
258 project_validator->id_type() == IdType::kUmaId) {
259 return;
260 }
261
262 StructuredEventProto event_proto;
263
264 // Initialize event proto from validator metadata.
265 InitializeEventProto(&event_proto, event, *project_validator,
266 *event_validator);
267
268 // Sequence-related metadata.
269 if (project_validator->event_type() == StructuredEventProto::SEQUENCE &&
270 base::FeatureList::IsEnabled(kEventSequenceLogging)) {
271 AddSequenceMetadata(&event_proto, event, *project_validator, *key_data);
272 }
273
274 // Populate the metrics and add to proto.
275 AddMetricsToProto(&event_proto, event, *project_validator, *event_validator);
276
277 // Log size information about the event.
278 LogEventSerializedSizeBytes(event_proto.ByteSizeLong());
279
280 Recorder::GetInstance()->OnEventRecorded(&event_proto);
281
282 // Add new event to storage.
283 event_storage_->AddEvent(std::move(event_proto));
284 }
285
InitializeEventProto(StructuredEventProto * proto,const Event & event,const ProjectValidator & project_validator,const EventValidator & event_validator)286 void StructuredMetricsRecorder::InitializeEventProto(
287 StructuredEventProto* proto,
288 const Event& event,
289 const ProjectValidator& project_validator,
290 const EventValidator& event_validator) {
291 proto->set_project_name_hash(project_validator.project_hash());
292 proto->set_event_name_hash(event_validator.event_hash());
293
294 // Set the event type. Do this with a switch statement to catch when the
295 // event type is UNKNOWN or uninitialized.
296 CHECK_NE(project_validator.event_type(), StructuredEventProto::UNKNOWN);
297
298 proto->set_event_type(project_validator.event_type());
299
300 // Set the ID for this event, if any.
301 switch (project_validator.id_type()) {
302 case IdType::kProjectId: {
303 absl::optional<uint64_t> primary_id =
304 key_data_provider_->GetId(event.project_name());
305 if (primary_id.has_value()) {
306 proto->set_profile_event_id(primary_id.value());
307 }
308 } break;
309 case IdType::kUmaId:
310 // TODO(crbug.com/1148168): Unimplemented.
311 break;
312 case IdType::kUnidentified:
313 // Do nothing.
314 break;
315 }
316 }
317
AddMetricsToProto(StructuredEventProto * proto,const Event & event,const ProjectValidator & project_validator,const EventValidator & event_validator)318 void StructuredMetricsRecorder::AddMetricsToProto(
319 StructuredEventProto* proto,
320 const Event& event,
321 const ProjectValidator& project_validator,
322 const EventValidator& event_validator) {
323 KeyData* key = key_data_provider_->GetKeyData(event.project_name());
324
325 // Key is checked by the calling function.
326 CHECK(key);
327
328 // Set each metric's name hash and value.
329 for (const auto& metric : event.metric_values()) {
330 const std::string& metric_name = metric.first;
331 const Event::MetricValue& metric_value = metric.second;
332
333 // Validate that both name and metric type are valid structured metrics.
334 // If a metric is invalid, then ignore the metric so that other valid
335 // metrics are added to the proto.
336 absl::optional<EventValidator::MetricMetadata> metadata =
337 event_validator.GetMetricMetadata(metric_name);
338
339 // Checks that the metrics defined are valid. If not valid, then the
340 // metric will be ignored.
341 bool is_valid =
342 metadata.has_value() && metadata->metric_type == metric_value.type;
343 DCHECK(is_valid);
344 if (!is_valid) {
345 continue;
346 }
347
348 StructuredEventProto::Metric* metric_proto = proto->add_metrics();
349 int64_t metric_name_hash = metadata->metric_name_hash;
350 metric_proto->set_name_hash(metric_name_hash);
351
352 const auto& value = metric_value.value;
353 switch (metadata->metric_type) {
354 case Event::MetricType::kHmac:
355 metric_proto->set_value_hmac(key->HmacMetric(
356 project_validator.project_hash(), metric_name_hash,
357 value.GetString(), project_validator.key_rotation_period()));
358 break;
359 case Event::MetricType::kLong:
360 int64_t long_value;
361 base::StringToInt64(value.GetString(), &long_value);
362 metric_proto->set_value_int64(long_value);
363 break;
364 case Event::MetricType::kRawString:
365 metric_proto->set_value_string(value.GetString());
366 break;
367 case Event::MetricType::kDouble:
368 metric_proto->set_value_double(value.GetDouble());
369 break;
370 // Not supported yet.
371 case Event::MetricType::kInt:
372 case Event::MetricType::kBoolean:
373 break;
374 }
375 }
376 }
377
HashUnhashedEventsAndPersist()378 void StructuredMetricsRecorder::HashUnhashedEventsAndPersist() {
379 if (IsInitialized()) {
380 LogNumEventsRecordedBeforeInit(unhashed_events_.size());
381 while (!unhashed_events_.empty()) {
382 RecordEvent(unhashed_events_.front());
383 unhashed_events_.pop_front();
384 }
385 }
386 if (IsProfileInitialized()) {
387 LogNumEventsRecordedBeforeInit(unhashed_profile_events_.size());
388 while (!unhashed_profile_events_.empty()) {
389 RecordEvent(unhashed_profile_events_.front());
390 unhashed_profile_events_.pop_front();
391 }
392 }
393 }
394
CanUploadProject(uint64_t project_name_hash) const395 bool StructuredMetricsRecorder::CanUploadProject(
396 uint64_t project_name_hash) const {
397 return !disallowed_projects_.contains(project_name_hash);
398 }
399
CacheDisallowedProjectsSet()400 void StructuredMetricsRecorder::CacheDisallowedProjectsSet() {
401 const std::string& disallowed_list = GetDisabledProjects();
402 if (disallowed_list.empty()) {
403 return;
404 }
405
406 for (const auto& value :
407 base::SplitString(disallowed_list, ",", base::TRIM_WHITESPACE,
408 base::SPLIT_WANT_NONEMPTY)) {
409 uint64_t project_name_hash;
410 // Parse the string and keep only perfect conversions.
411 if (base::StringToUint64(value, &project_name_hash)) {
412 disallowed_projects_.insert(project_name_hash);
413 }
414 }
415 }
416
AddDisallowedProjectForTest(uint64_t project_name_hash)417 void StructuredMetricsRecorder::AddDisallowedProjectForTest(
418 uint64_t project_name_hash) {
419 disallowed_projects_.insert(project_name_hash);
420 }
421
IsKeyDataInitialized()422 bool StructuredMetricsRecorder::IsKeyDataInitialized() {
423 return key_data_provider_->IsReady();
424 }
425
SetEventRecordCallbackForTest(base::RepeatingClosure callback)426 void StructuredMetricsRecorder::SetEventRecordCallbackForTest(
427 base::RepeatingClosure callback) {
428 test_callback_on_record_ = std::move(callback);
429 }
430
CanForceRecord(const Event & event) const431 bool StructuredMetricsRecorder::CanForceRecord(const Event& event) const {
432 const auto validators = GetEventValidators(event);
433 if (!validators) {
434 return false;
435 }
436 return validators->second->can_force_record();
437 }
438
439 absl::optional<std::pair<const ProjectValidator*, const EventValidator*>>
GetEventValidators(const Event & event) const440 StructuredMetricsRecorder::GetEventValidators(const Event& event) const {
441 auto maybe_project_validator =
442 validator::Validators::Get()->GetProjectValidator(event.project_name());
443
444 DCHECK(maybe_project_validator.has_value());
445 if (!maybe_project_validator.has_value()) {
446 return {};
447 }
448 const auto* project_validator = maybe_project_validator.value();
449 const auto maybe_event_validator =
450 project_validator->GetEventValidator(event.event_name());
451 DCHECK(maybe_event_validator.has_value());
452 if (!maybe_event_validator.has_value()) {
453 return {};
454 }
455 const auto* event_validator = maybe_event_validator.value();
456 return std::make_pair(project_validator, event_validator);
457 }
458
IsDeviceEvent(const Event & event) const459 bool StructuredMetricsRecorder::IsDeviceEvent(const Event& event) const {
460 // Validates the event. If valid, retrieve the metadata associated
461 // with the event.
462 const auto validators = GetEventValidators(event);
463 if (!validators) {
464 return false;
465 }
466 const auto* project_validator = validators->first;
467
468 // Sequence events are marked as per-device but use the profile keys.
469 return !event.IsEventSequenceType() &&
470 project_validator->id_scope() == IdScope::kPerDevice;
471 }
472
IsProfileEvent(const Event & event) const473 bool StructuredMetricsRecorder::IsProfileEvent(const Event& event) const {
474 // Validates the event. If valid, retrieve the metadata associated
475 // with the event.
476 const auto validators = GetEventValidators(event);
477 if (!validators) {
478 return false;
479 }
480 const auto* project_validator = validators->first;
481
482 // Sequence events are marked as per-device but use the profile keys.
483 return event.IsEventSequenceType() ||
484 project_validator->id_scope() == IdScope::kPerProfile;
485 }
486
CanProvideMetrics()487 bool StructuredMetricsRecorder::CanProvideMetrics() {
488 return recording_enabled() && (IsInitialized() || IsProfileInitialized());
489 }
490
491 } // namespace metrics::structured
492