• 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_recorder.h"
6 
7 #include <algorithm>
8 #include <numeric>
9 
10 #include "base/feature_list.h"
11 #include "base/metrics/field_trial.h"
12 #include "base/metrics/metrics_hashes.h"
13 #include "base/no_destructor.h"
14 
15 namespace metrics::dwa {
16 
17 BASE_FEATURE(kDwaFeature, "DwaFeature", base::FEATURE_DISABLED_BY_DEFAULT);
18 
19 namespace {
20 
21 // Populates |dwa_event|.field_trials with the field trial/group name hashes
22 // for the field_trials we are interested in, |studies_of_interest|.
23 // |active_field_trial_groups| contains a mapping of field trial names to
24 // group names that we are currently part of.
PopulateFieldTrialsForDwaEvent(const base::flat_map<std::string,bool> & studies_of_interest,const std::unordered_map<std::string,std::string> & active_field_trial_groups,::dwa::DeidentifiedWebAnalyticsEvent & dwa_event)25 void PopulateFieldTrialsForDwaEvent(
26     const base::flat_map<std::string, bool>& studies_of_interest,
27     const std::unordered_map<std::string, std::string>&
28         active_field_trial_groups,
29     ::dwa::DeidentifiedWebAnalyticsEvent& dwa_event) {
30   // Determine the study groups that is part of |studies_of_interest| in the
31   // current session. If we are in a study part of |study_of_interest| in the
32   // current session, Hash the study and group names and populate a new repeated
33   // field_trials in |dwa_event|.
34   for (const auto& [trial_name, _] : studies_of_interest) {
35     auto it = active_field_trial_groups.find(trial_name);
36     if (it != active_field_trial_groups.end()) {
37       const auto& group_name = it->second;
38       ::metrics::SystemProfileProto::FieldTrial* field_trial =
39           dwa_event.add_field_trials();
40       field_trial->set_name_id(base::HashFieldTrialName(trial_name));
41       field_trial->set_group_id(base::HashFieldTrialName(group_name));
42     }
43   }
44 }
45 
46 // Takes |raw_entries_metrics|, a vector of metric_hash to metric_value maps,
47 // and returns it as a vector of EntryMetrics as defined in
48 // deidentified_web_analytics.proto.
49 std::vector<::dwa::DeidentifiedWebAnalyticsEvent::ContentMetric::EntryMetrics>
TransformEntriesMetrics(const std::vector<base::flat_map<uint64_t,int64_t>> & raw_entries_metrics)50 TransformEntriesMetrics(
51     const std::vector<base::flat_map<uint64_t, int64_t>>& raw_entries_metrics) {
52   std::vector<::dwa::DeidentifiedWebAnalyticsEvent::ContentMetric::EntryMetrics>
53       entries_metrics;
54   entries_metrics.reserve(raw_entries_metrics.size());
55 
56   for (const auto& raw_entry_metrics : raw_entries_metrics) {
57     ::dwa::DeidentifiedWebAnalyticsEvent::ContentMetric::EntryMetrics
58         entry_metric;
59     for (const auto& [metric_hash, metric_value] : raw_entry_metrics) {
60       ::dwa::DeidentifiedWebAnalyticsEvent::ContentMetric::EntryMetrics::Metric*
61           metric = entry_metric.add_metric();
62       metric->set_name_hash(metric_hash);
63       metric->set_value(metric_value);
64     }
65     entries_metrics.push_back(std::move(entry_metric));
66   }
67 
68   return entries_metrics;
69 }
70 
71 // Takes a vector of entries, aggregates them, and then returns a vector of
72 // dwa events. The contents of |entries| are moved in this function and
73 // should not be used after.
BuildDwaEvents(const std::vector<::metrics::dwa::mojom::DwaEntryPtr> & entries)74 std::vector<::dwa::DeidentifiedWebAnalyticsEvent> BuildDwaEvents(
75     const std::vector<::metrics::dwa::mojom::DwaEntryPtr>& entries) {
76   base::FieldTrial::ActiveGroups active_groups;
77   base::FieldTrialList::GetActiveFieldTrialGroups(&active_groups);
78   std::unordered_map<std::string, std::string> active_field_trial_groups;
79 
80   for (const auto& active_group : active_groups) {
81     active_field_trial_groups.insert(
82         std::make_pair(active_group.trial_name, active_group.group_name));
83   }
84 
85   // Maps {event_hash: {content_hash: vector<metrics>}}.
86   std::unordered_map<
87       uint64_t, std::unordered_map<
88                     uint64_t, std::vector<base::flat_map<uint64_t, int64_t>>>>
89       dwa_events_aggregation;
90   // Maps {event_hash: vector<field_trials>}.
91   std::unordered_map<uint64_t, base::flat_map<std::string, bool>>
92       dwa_events_field_trials;
93 
94   for (const auto& entry : entries) {
95     dwa_events_aggregation[entry->event_hash][entry->content_hash].push_back(
96         std::move(entry->metrics));
97     dwa_events_field_trials.try_emplace(entry->event_hash,
98                                         std::move(entry->studies_of_interest));
99   }
100 
101   std::vector<::dwa::DeidentifiedWebAnalyticsEvent> dwa_events;
102 
103   for (const auto& [event_hash, content_and_metrics] : dwa_events_aggregation) {
104     ::dwa::DeidentifiedWebAnalyticsEvent event;
105 
106     event.set_event_hash(event_hash);
107     for (const auto& [content_hash, raw_entries_metrics] :
108          content_and_metrics) {
109       ::dwa::DeidentifiedWebAnalyticsEvent::ContentMetric* content_metric =
110           event.add_content_metrics();
111       content_metric->set_content_type(::dwa::DeidentifiedWebAnalyticsEvent::
112                                            ContentMetric::CONTENT_TYPE_URL);
113       content_metric->set_content_hash(content_hash);
114 
115       std::vector<
116           ::dwa::DeidentifiedWebAnalyticsEvent::ContentMetric::EntryMetrics>
117           entries_metrics = TransformEntriesMetrics(raw_entries_metrics);
118       content_metric->mutable_metrics()->Add(
119           std::make_move_iterator(entries_metrics.begin()),
120           std::make_move_iterator(entries_metrics.end()));
121     }
122 
123     PopulateFieldTrialsForDwaEvent(dwa_events_field_trials[event_hash],
124                                    active_field_trial_groups, event);
125 
126     dwa_events.push_back(std::move(event));
127   }
128 
129   return dwa_events;
130 }
131 
132 }  // namespace
133 
134 DwaRecorder::DwaRecorder() = default;
135 
136 DwaRecorder::~DwaRecorder() = default;
137 
EnableRecording()138 void DwaRecorder::EnableRecording() {
139   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
140   recorder_enabled_ = base::FeatureList::IsEnabled(kDwaFeature);
141 }
142 
DisableRecording()143 void DwaRecorder::DisableRecording() {
144   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
145   recorder_enabled_ = false;
146 }
147 
Purge()148 void DwaRecorder::Purge() {
149   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
150   entries_.clear();
151   page_load_events_.clear();
152 }
153 
IsEnabled()154 bool DwaRecorder::IsEnabled() {
155   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
156   return recorder_enabled_;
157 }
158 
159 // static
Get()160 DwaRecorder* DwaRecorder::Get() {
161   static base::NoDestructor<DwaRecorder> recorder;
162   return recorder.get();
163 }
164 
AddEntry(metrics::dwa::mojom::DwaEntryPtr entry)165 void DwaRecorder::AddEntry(metrics::dwa::mojom::DwaEntryPtr entry) {
166   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
167   if (!recorder_enabled_) {
168     return;
169   }
170 
171   entries_.push_back(std::move(entry));
172 }
173 
HasEntries()174 bool DwaRecorder::HasEntries() {
175   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
176   return !entries_.empty();
177 }
178 
OnPageLoad()179 void DwaRecorder::OnPageLoad() {
180   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
181   if (!recorder_enabled_) {
182     return;
183   }
184 
185   // No entries, so there's nothing to do.
186   if (entries_.empty()) {
187     return;
188   }
189 
190   std::vector<::dwa::DeidentifiedWebAnalyticsEvent> dwa_events =
191       BuildDwaEvents(entries_);
192   entries_.clear();
193 
194   if (dwa_events.empty()) {
195     return;
196   }
197 
198   // Puts existing |dwa_events_| into a page load event.
199   ::dwa::PageLoadEvents page_load_event;
200   page_load_event.mutable_events()->Add(
201       std::make_move_iterator(dwa_events.begin()),
202       std::make_move_iterator(dwa_events.end()));
203 
204   // Add the page load event to the list of page load events.
205   page_load_events_.push_back(std::move(page_load_event));
206 }
207 
TakePageLoadEvents()208 std::vector<::dwa::PageLoadEvents> DwaRecorder::TakePageLoadEvents() {
209   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
210   std::vector<::dwa::PageLoadEvents> results = std::move(page_load_events_);
211   page_load_events_.clear();
212   return results;
213 }
214 
HasPageLoadEvents()215 bool DwaRecorder::HasPageLoadEvents() {
216   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
217   return !page_load_events_.empty();
218 }
219 
220 }  // namespace metrics::dwa
221