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