xref: /aosp_15_r20/external/cronet/components/metrics/serialization/serialization_utils.cc (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
1 // Copyright 2014 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/serialization/serialization_utils.h"
6 
7 #include <errno.h>
8 #include <stdint.h>
9 #include <sys/file.h>
10 #include <unistd.h>
11 
12 #include <utility>
13 
14 #include "base/containers/span.h"
15 #include "base/files/file_path.h"
16 #include "base/files/file_util.h"
17 #include "base/files/scoped_file.h"
18 #include "base/logging.h"
19 #include "base/metrics/histogram_functions.h"
20 #include "base/numerics/safe_math.h"
21 #include "base/strings/string_split.h"
22 #include "base/strings/string_util.h"
23 #include "components/metrics/serialization/metric_sample.h"
24 
25 #define READ_WRITE_ALL_FILE_FLAGS \
26   (S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH)
27 
28 namespace metrics {
29 namespace {
30 // Reads the next message from |file_descriptor| into |message|.
31 //
32 // |message| will be set to the empty string if no message could be read (EOF)
33 // or the message was badly constructed.
34 //
35 // Returns false if no message can be read from this file anymore (EOF or
36 // unrecoverable error).
ReadMessage(int fd,std::string * message)37 bool ReadMessage(int fd, std::string* message) {
38   CHECK(message);
39 
40   int result;
41   uint32_t encoded_size;
42   const size_t message_header_size = sizeof(uint32_t);
43   // The file containing the metrics does not leave the device so the writer and
44   // the reader will always have the same endianness.
45   result = HANDLE_EINTR(read(fd, &encoded_size, message_header_size));
46   if (result < 0) {
47     DPLOG(ERROR) << "reading metrics message header";
48     return false;
49   }
50   if (result == 0) {
51     // This indicates a normal EOF.
52     return false;
53   }
54   if (base::checked_cast<size_t>(result) < message_header_size) {
55     DLOG(ERROR) << "bad read size " << result << ", expecting "
56                 << message_header_size;
57     return false;
58   }
59 
60   // kMessageMaxLength applies to the entire message: the 4-byte
61   // length field and the content.
62   size_t message_size = base::checked_cast<size_t>(encoded_size);
63   if (message_size > SerializationUtils::kMessageMaxLength) {
64     DLOG(ERROR) << "message too long : " << message_size;
65     if (HANDLE_EINTR(lseek(fd, message_size - message_header_size, SEEK_CUR)) ==
66         -1) {
67       DLOG(ERROR) << "error while skipping message. abort";
68       return false;
69     }
70     // Badly formatted message was skipped. Treat the badly formatted sample as
71     // an empty sample.
72     message->clear();
73     return true;
74   }
75 
76   if (message_size < message_header_size) {
77     DLOG(ERROR) << "message too short : " << message_size;
78     return false;
79   }
80 
81   message_size -= message_header_size;  // The message size includes itself.
82   char buffer[SerializationUtils::kMessageMaxLength];
83   if (!base::ReadFromFD(fd, base::make_span(buffer, message_size))) {
84     DPLOG(ERROR) << "reading metrics message body";
85     return false;
86   }
87   *message = std::string(buffer, message_size);
88   return true;
89 }
90 
91 // Reads all samples from a file and when done:
92 //  1) deletes the file if |delete_file| is true.
93 //  2) truncates the file if |delete_file| is false.
94 //
95 // This method is the implementation of ReadAndTruncateMetricsFromFile() and
96 // ReadAndDeleteMetricsFromFile().
ReadAndTruncateOrDeleteMetricsFromFile(const std::string & filename,bool delete_file,std::vector<std::unique_ptr<MetricSample>> * metrics)97 void ReadAndTruncateOrDeleteMetricsFromFile(
98     const std::string& filename,
99     bool delete_file,
100     std::vector<std::unique_ptr<MetricSample>>* metrics) {
101   struct stat stat_buf;
102   int result;
103 
104   result = stat(filename.c_str(), &stat_buf);
105   if (result < 0) {
106     if (errno == ENOENT) {
107       // File doesn't exist, nothing to collect. This isn't an error, it just
108       // means nothing on the ChromeOS side has written to the file yet.
109     } else {
110       DPLOG(ERROR) << "bad metrics file stat: " << filename;
111     }
112     return;
113   }
114   if (stat_buf.st_size == 0) {
115     // Also nothing to collect.
116     return;
117   }
118   // Only need to read/write if we're truncating.
119   int flag = delete_file ? O_RDONLY : O_RDWR;
120   base::ScopedFD fd(open(filename.c_str(), flag));
121   if (fd.get() < 0) {
122     DPLOG(ERROR) << "cannot open: " << filename;
123     return;
124   }
125   result = flock(fd.get(), LOCK_EX);
126   if (result < 0) {
127     DPLOG(ERROR) << "cannot lock: " << filename;
128     return;
129   }
130 
131   // This processes all messages in the log. When all messages are
132   // read and processed, or an error occurs, or we've read so many that the
133   // buffer is at risk of overflowing, delete the file or truncate the file to
134   // zero size according to |delete_file|. If we hit kMaxMessagesPerRead, don't
135   // add them to the vector to avoid memory overflow.
136   while (metrics->size() <
137          static_cast<size_t>(SerializationUtils::kMaxMessagesPerRead)) {
138     std::string message;
139 
140     if (!ReadMessage(fd.get(), &message)) {
141       break;
142     }
143 
144     std::unique_ptr<MetricSample> sample =
145         SerializationUtils::ParseSample(message);
146     if (sample) {
147       metrics->push_back(std::move(sample));
148     }
149   }
150 
151   base::UmaHistogramCustomCounts(
152       "Platform.ExternalMetrics.SamplesRead", metrics->size(), 1,
153       SerializationUtils::kMaxMessagesPerRead - 1, 50);
154 
155   if (delete_file) {
156     result = unlink(filename.c_str());
157     if (result < 0) {
158       DPLOG(ERROR) << "error deleting metrics log: " << filename;
159     }
160   } else {
161     result = ftruncate(fd.get(), 0);
162     if (result < 0) {
163       DPLOG(ERROR) << "error truncating metrics log: " << filename;
164     }
165   }
166 
167   result = flock(fd.get(), LOCK_UN);
168   if (result < 0) {
169     DPLOG(ERROR) << "error unlocking metrics log: " << filename;
170   }
171 }
172 
173 }  // namespace
174 
175 // This value is used as a max value in a histogram,
176 // Platform.ExternalMetrics.SamplesRead. If it changes, the histogram will need
177 // to be renamed.
178 const int SerializationUtils::kMaxMessagesPerRead = 100000;
179 
ParseSample(const std::string & sample)180 std::unique_ptr<MetricSample> SerializationUtils::ParseSample(
181     const std::string& sample) {
182   if (sample.empty()) {
183     return nullptr;
184   }
185 
186   std::vector<std::string> parts =
187       base::SplitString(sample, std::string(1, '\0'), base::TRIM_WHITESPACE,
188                         base::SPLIT_WANT_ALL);
189   // We should have two null terminated strings so split should produce
190   // three chunks.
191   if (parts.size() != 3) {
192     DLOG(ERROR) << "splitting message on \\0 produced " << parts.size()
193                 << " parts (expected 3)";
194     return nullptr;
195   }
196   const std::string& name = parts[0];
197   const std::string& value = parts[1];
198 
199   if (base::EqualsCaseInsensitiveASCII(name, "crash")) {
200     return MetricSample::ParseCrash(value);
201   }
202   if (base::EqualsCaseInsensitiveASCII(name, "histogram")) {
203     return MetricSample::ParseHistogram(value);
204   }
205   if (base::EqualsCaseInsensitiveASCII(name, "linearhistogram")) {
206     return MetricSample::ParseLinearHistogram(value);
207   }
208   if (base::EqualsCaseInsensitiveASCII(name, "sparsehistogram")) {
209     return MetricSample::ParseSparseHistogram(value);
210   }
211   if (base::EqualsCaseInsensitiveASCII(name, "useraction")) {
212     return MetricSample::ParseUserAction(value);
213   }
214   DLOG(ERROR) << "invalid event type: " << name << ", value: " << value;
215   return nullptr;
216 }
217 
ReadAndTruncateMetricsFromFile(const std::string & filename,std::vector<std::unique_ptr<MetricSample>> * metrics)218 void SerializationUtils::ReadAndTruncateMetricsFromFile(
219     const std::string& filename,
220     std::vector<std::unique_ptr<MetricSample>>* metrics) {
221   ReadAndTruncateOrDeleteMetricsFromFile(filename, /*delete_file=*/false,
222                                          metrics);
223 }
224 
ReadAndDeleteMetricsFromFile(const std::string & filename,std::vector<std::unique_ptr<MetricSample>> * metrics)225 void SerializationUtils::ReadAndDeleteMetricsFromFile(
226     const std::string& filename,
227     std::vector<std::unique_ptr<MetricSample>>* metrics) {
228   ReadAndTruncateOrDeleteMetricsFromFile(filename, /*delete_file=*/true,
229                                          metrics);
230 }
231 
WriteMetricToFile(const MetricSample & sample,const std::string & filename)232 bool SerializationUtils::WriteMetricToFile(const MetricSample& sample,
233                                            const std::string& filename) {
234   if (!sample.IsValid()) {
235     return false;
236   }
237 
238   base::ScopedFD file_descriptor(open(filename.c_str(),
239                                       O_WRONLY | O_APPEND | O_CREAT | O_CLOEXEC,
240                                       READ_WRITE_ALL_FILE_FLAGS));
241 
242   if (file_descriptor.get() < 0) {
243     DPLOG(ERROR) << "error opening the file: " << filename;
244     return false;
245   }
246 
247   fchmod(file_descriptor.get(), READ_WRITE_ALL_FILE_FLAGS);
248   // Grab a lock to avoid chrome truncating the file underneath us. Keep the
249   // file locked as briefly as possible. Freeing file_descriptor will close the
250   // file and remove the lock IFF the process was not forked in the meantime,
251   // which will leave the flock hanging and deadlock the reporting until the
252   // forked process is killed otherwise. Thus we have to explicitly unlock the
253   // file below.
254   if (HANDLE_EINTR(flock(file_descriptor.get(), LOCK_EX)) < 0) {
255     DPLOG(ERROR) << "error locking: " << filename;
256     return false;
257   }
258 
259   std::string msg = sample.ToString();
260   size_t size = 0;
261   if (!base::CheckAdd(msg.length(), sizeof(uint32_t)).AssignIfValid(&size) ||
262       size > kMessageMaxLength) {
263     DPLOG(ERROR) << "cannot write message: too long: " << filename;
264     std::ignore = flock(file_descriptor.get(), LOCK_UN);
265     return false;
266   }
267 
268   // The file containing the metrics samples will only be read by programs on
269   // the same device so we do not check endianness.
270   uint32_t encoded_size = base::checked_cast<uint32_t>(size);
271   if (!base::WriteFileDescriptor(
272           file_descriptor.get(),
273           base::as_bytes(base::make_span(&encoded_size, 1u)))) {
274     DPLOG(ERROR) << "error writing message length: " << filename;
275     std::ignore = flock(file_descriptor.get(), LOCK_UN);
276     return false;
277   }
278 
279   if (!base::WriteFileDescriptor(file_descriptor.get(), msg)) {
280     DPLOG(ERROR) << "error writing message: " << filename;
281     std::ignore = flock(file_descriptor.get(), LOCK_UN);
282     return false;
283   }
284 
285   return true;
286 }
287 
288 }  // namespace metrics
289