xref: /aosp_15_r20/external/cronet/components/metrics/structured/external_metrics.cc (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
1 // Copyright 2021 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/external_metrics.h"
6 
7 #include <string_view>
8 
9 #include "base/containers/fixed_flat_set.h"
10 #include "base/files/dir_reader_posix.h"
11 #include "base/files/file.h"
12 #include "base/files/file_util.h"
13 #include "base/logging.h"
14 #include "base/strings/string_number_conversions.h"
15 #include "base/strings/string_split.h"
16 #include "base/task/sequenced_task_runner.h"
17 #include "base/task/task_traits.h"
18 #include "base/task/thread_pool.h"
19 #include "base/threading/scoped_blocking_call.h"
20 #include "components/metrics/structured/histogram_util.h"
21 #include "components/metrics/structured/proto/event_storage.pb.h"
22 #include "components/metrics/structured/structured_metrics_features.h"
23 
24 namespace metrics::structured {
25 namespace {
26 
FilterEvents(google::protobuf::RepeatedPtrField<metrics::StructuredEventProto> * events,const base::flat_set<uint64_t> & disallowed_projects)27 void FilterEvents(
28     google::protobuf::RepeatedPtrField<metrics::StructuredEventProto>* events,
29     const base::flat_set<uint64_t>& disallowed_projects) {
30   auto it = events->begin();
31   while (it != events->end()) {
32     if (disallowed_projects.contains(it->project_name_hash())) {
33       it = events->erase(it);
34     } else {
35       ++it;
36     }
37   }
38 }
39 
Platform2ProjectName(uint64_t project_name_hash)40 std::string_view Platform2ProjectName(uint64_t project_name_hash) {
41   switch (project_name_hash) {
42     case UINT64_C(827233605053062635):
43       return "AudioPeripheral";
44     case UINT64_C(524369188505453537):
45       return "AudioPeripheralInfo";
46     case UINT64_C(9074739597929991885):
47       return "Bluetooth";
48     case UINT64_C(1745381000935843040):
49       return "BluetoothDevice";
50     case UINT64_C(11181229631788078243):
51       return "BluetoothChipset";
52     case UINT64_C(8206859287963243715):
53       return "Cellular";
54     case UINT64_C(11294265225635075664):
55       return "HardwareVerifier";
56     case UINT64_C(4905803635010729907):
57       return "RollbackEnterprise";
58     case UINT64_C(9675127341789951965):
59       return "Rmad";
60     case UINT64_C(4690103929823698613):
61       return "WiFiChipset";
62     case UINT64_C(17922303533051575891):
63       return "UsbDevice";
64     case UINT64_C(1370722622176744014):
65       return "UsbError";
66     case UINT64_C(17319042894491683836):
67       return "UsbPdDevice";
68     case UINT64_C(6962789877417678651):
69       return "UsbSession";
70     case UINT64_C(4320592646346933548):
71       return "WiFi";
72     case UINT64_C(7302676440391025918):
73       return "WiFiAP";
74     default:
75       return "UNKNOWN";
76   }
77 }
78 
IncrementProjectCount(base::flat_map<uint64_t,int> & project_count_map,uint64_t project_name_hash)79 void IncrementProjectCount(base::flat_map<uint64_t, int>& project_count_map,
80                            uint64_t project_name_hash) {
81   if (project_count_map.contains(project_name_hash)) {
82     project_count_map[project_name_hash] += 1;
83   } else {
84     project_count_map[project_name_hash] = 1;
85   }
86 }
87 
ProcessEventProtosProjectCounts(base::flat_map<uint64_t,int> & project_count_map,const EventsProto & proto)88 void ProcessEventProtosProjectCounts(
89     base::flat_map<uint64_t, int>& project_count_map,
90     const EventsProto& proto) {
91   // Process all events that were packed in the proto.
92   for (const auto& event : proto.uma_events()) {
93     IncrementProjectCount(project_count_map, event.project_name_hash());
94   }
95 
96   for (const auto& event : proto.events()) {
97     IncrementProjectCount(project_count_map, event.project_name_hash());
98   }
99 }
100 
101 // TODO(b/181724341): Remove this once the bluetooth metrics are fully enabled.
MaybeFilterBluetoothEvents(google::protobuf::RepeatedPtrField<metrics::StructuredEventProto> * events)102 void MaybeFilterBluetoothEvents(
103     google::protobuf::RepeatedPtrField<metrics::StructuredEventProto>* events) {
104   // Event name hashes of all bluetooth events listed in
105   // src/platform2/metrics/structured/structured.xml.
106   static constexpr auto kBluetoothEventHashes =
107       base::MakeFixedFlatSet<uint64_t>(
108           {// BluetoothAdapterStateChanged
109            UINT64_C(959829856916771459),
110            // BluetoothPairingStateChanged
111            UINT64_C(11839023048095184048),
112            // BluetoothAclConnectionStateChanged
113            UINT64_C(1880220404408566268),
114            // BluetoothProfileConnectionStateChanged
115            UINT64_C(7217682640379679663),
116            // BluetoothDeviceInfoReport
117            UINT64_C(1506471670382892394)});
118 
119   if (base::FeatureList::IsEnabled(kBluetoothSessionizedMetrics)) {
120     return;
121   }
122 
123   // Remove all bluetooth events.
124   auto it = events->begin();
125   while (it != events->end()) {
126     if (kBluetoothEventHashes.contains(it->event_name_hash())) {
127       it = events->erase(it);
128     } else {
129       ++it;
130     }
131   }
132 }
133 
FilterProto(EventsProto * proto,const base::flat_set<uint64_t> & disallowed_projects)134 bool FilterProto(EventsProto* proto,
135                  const base::flat_set<uint64_t>& disallowed_projects) {
136   FilterEvents(proto->mutable_uma_events(), disallowed_projects);
137   FilterEvents(proto->mutable_events(), disallowed_projects);
138   return proto->uma_events_size() > 0 || proto->events_size() > 0;
139 }
140 
ReadAndDeleteEvents(const base::FilePath & directory,const base::flat_set<uint64_t> & disallowed_projects,bool recording_enabled)141 EventsProto ReadAndDeleteEvents(
142     const base::FilePath& directory,
143     const base::flat_set<uint64_t>& disallowed_projects,
144     bool recording_enabled) {
145   base::ScopedBlockingCall scoped_blocking_call(FROM_HERE,
146                                                 base::BlockingType::MAY_BLOCK);
147   EventsProto result;
148   if (!base::DirectoryExists(directory)) {
149     return result;
150   }
151 
152   base::DirReaderPosix dir_reader(directory.value().c_str());
153   if (!dir_reader.IsValid()) {
154     VLOG(2) << "Failed to load External Metrics directory: " << directory;
155     return result;
156   }
157 
158   int file_counter = 0;
159   int dropped_events = 0;
160   base::flat_map<uint64_t, int> dropped_projects_count, produced_projects_count;
161 
162   while (dir_reader.Next()) {
163     base::FilePath path = directory.Append(dir_reader.name());
164     base::File file(path, base::File::FLAG_OPEN | base::File::FLAG_OPEN_ALWAYS);
165 
166     // This will fail on '.' and '..' files.
167     if (!file.IsValid()) {
168       continue;
169     }
170 
171     // Ignore any directory.
172     base::File::Info info;
173     if (!file.GetInfo(&info) || info.is_directory) {
174       continue;
175     }
176 
177     // If recording is disabled delete the file.
178     if (!recording_enabled) {
179       base::DeleteFile(path);
180       continue;
181     }
182 
183     ++file_counter;
184 
185     std::string proto_str;
186     int64_t file_size;
187     EventsProto proto;
188 
189     // If an event is abnormally large, ignore it to prevent OOM.
190     bool fs_ok = base::GetFileSize(path, &file_size);
191 
192     // If file size get is successful, log the file size.
193     if (fs_ok) {
194       LogEventFileSizeKB(static_cast<int>(file_size / 1024));
195     }
196 
197     if (!fs_ok || file_size > GetFileSizeByteLimit()) {
198       base::DeleteFile(path);
199       continue;
200     }
201 
202     bool read_ok = base::ReadFileToString(path, &proto_str) &&
203                    proto.ParseFromString(proto_str);
204     base::DeleteFile(path);
205 
206     // Process all events that were packed in the proto.
207     ProcessEventProtosProjectCounts(produced_projects_count, proto);
208 
209     // There may be too many messages in the directory to hold in-memory.
210     // This could happen if the process in which Structured metrics resides
211     // is either crash-looping or taking too long to process externally
212     // recorded events.
213     //
214     // Events will be dropped in that case so that more recent events can be
215     // processed. Events will be dropped if recording has been disabled.
216     if (file_counter > GetFileLimitPerScan()) {
217       ++dropped_events;
218 
219       // Process all events that were packed in the proto.
220       ProcessEventProtosProjectCounts(dropped_projects_count, proto);
221       continue;
222     }
223 
224     if (!read_ok) {
225       continue;
226     }
227 
228     if (!FilterProto(&proto, disallowed_projects)) {
229       continue;
230     }
231 
232     // MergeFrom performs a copy that could be a move if done manually. But
233     // all the protos here are expected to be small, so let's keep it simple.
234     result.mutable_uma_events()->MergeFrom(proto.uma_events());
235     result.mutable_events()->MergeFrom(proto.events());
236   }
237 
238   if (recording_enabled) {
239     LogDroppedExternalMetrics(dropped_events);
240 
241     // Log histograms for each project with their appropriate counts.
242     // If a project isn't seen then it will not be logged.
243     for (const auto& project_counts : produced_projects_count) {
244       LogProducedProjectExternalMetrics(
245           Platform2ProjectName(project_counts.first), project_counts.second);
246     }
247 
248     for (const auto& project_counts : dropped_projects_count) {
249       LogDroppedProjectExternalMetrics(
250           Platform2ProjectName(project_counts.first), project_counts.second);
251     }
252   }
253 
254   LogNumFilesPerExternalMetricsScan(file_counter);
255 
256   MaybeFilterBluetoothEvents(result.mutable_uma_events());
257   MaybeFilterBluetoothEvents(result.mutable_events());
258 
259   return result;
260 }
261 
262 }  // namespace
263 
ExternalMetrics(const base::FilePath & events_directory,const base::TimeDelta & collection_interval,MetricsCollectedCallback callback)264 ExternalMetrics::ExternalMetrics(const base::FilePath& events_directory,
265                                  const base::TimeDelta& collection_interval,
266                                  MetricsCollectedCallback callback)
267     : events_directory_(events_directory),
268       collection_interval_(collection_interval),
269       callback_(std::move(callback)),
270       task_runner_(base::ThreadPool::CreateSequencedTaskRunner(
271           {base::TaskPriority::BEST_EFFORT, base::MayBlock(),
272            base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN})) {
273   ScheduleCollector();
274   CacheDisallowedProjectsSet();
275 }
276 
277 ExternalMetrics::~ExternalMetrics() = default;
278 
CollectEventsAndReschedule()279 void ExternalMetrics::CollectEventsAndReschedule() {
280   CollectEvents();
281   ScheduleCollector();
282 }
283 
ScheduleCollector()284 void ExternalMetrics::ScheduleCollector() {
285   base::SequencedTaskRunner::GetCurrentDefault()->PostDelayedTask(
286       FROM_HERE,
287       base::BindOnce(&ExternalMetrics::CollectEventsAndReschedule,
288                      weak_factory_.GetWeakPtr()),
289       collection_interval_);
290 }
291 
CollectEvents()292 void ExternalMetrics::CollectEvents() {
293   task_runner_->PostTaskAndReplyWithResult(
294       FROM_HERE,
295       base::BindOnce(&ReadAndDeleteEvents, events_directory_,
296                      disallowed_projects_, recording_enabled_),
297       base::BindOnce(callback_));
298 }
299 
CacheDisallowedProjectsSet()300 void ExternalMetrics::CacheDisallowedProjectsSet() {
301   const std::string& disallowed_list = GetDisabledProjects();
302   if (disallowed_list.empty()) {
303     return;
304   }
305 
306   for (const auto& value :
307        base::SplitString(disallowed_list, ",", base::TRIM_WHITESPACE,
308                          base::SPLIT_WANT_NONEMPTY)) {
309     uint64_t project_name_hash;
310     // Parse the string and keep only perfect conversions.
311     if (base::StringToUint64(value, &project_name_hash)) {
312       disallowed_projects_.insert(project_name_hash);
313     }
314   }
315 }
316 
AddDisallowedProjectForTest(uint64_t project_name_hash)317 void ExternalMetrics::AddDisallowedProjectForTest(uint64_t project_name_hash) {
318   disallowed_projects_.insert(project_name_hash);
319 }
320 
EnableRecording()321 void ExternalMetrics::EnableRecording() {
322   recording_enabled_ = true;
323 }
324 
DisableRecording()325 void ExternalMetrics::DisableRecording() {
326   recording_enabled_ = false;
327 }
328 
329 }  // namespace metrics::structured
330