• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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