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 <stddef.h>
8 #include <stdint.h>
9
10 #include "base/check.h"
11 #include "base/containers/span.h"
12 #include "base/files/file.h"
13 #include "base/files/file_util.h"
14 #include "base/files/scoped_temp_dir.h"
15 #include "base/strings/stringprintf.h"
16 #include "base/test/metrics/histogram_tester.h"
17 #include "components/metrics/serialization/metric_sample.h"
18 #include "testing/gmock/include/gmock/gmock.h"
19 #include "testing/gtest/include/gtest/gtest.h"
20
21 namespace metrics {
22 namespace {
23
24 using ::testing::IsEmpty;
25
26 class SerializationUtilsTest : public testing::Test {
27 protected:
SerializationUtilsTest()28 SerializationUtilsTest() {
29 bool success = temporary_dir_.CreateUniqueTempDir();
30 if (success) {
31 base::FilePath dir_path = temporary_dir_.GetPath();
32 filename_ = dir_path.value() + "chromeossampletest";
33 filepath_ = base::FilePath(filename_);
34 }
35 }
36
SetUp()37 void SetUp() override { base::DeleteFile(filepath_); }
38
TestSerialization(MetricSample * sample)39 void TestSerialization(MetricSample* sample) {
40 std::string serialized(sample->ToString());
41 ASSERT_EQ('\0', serialized.back());
42 std::unique_ptr<MetricSample> deserialized =
43 SerializationUtils::ParseSample(serialized);
44 ASSERT_TRUE(deserialized);
45 EXPECT_TRUE(sample->IsEqual(*deserialized.get()));
46 }
47
filename() const48 const std::string& filename() const { return filename_; }
filepath() const49 const base::FilePath& filepath() const { return filepath_; }
50
51 private:
52 std::string filename_;
53 base::ScopedTempDir temporary_dir_;
54 base::FilePath filepath_;
55 };
56
TEST_F(SerializationUtilsTest,CrashSerializeTest)57 TEST_F(SerializationUtilsTest, CrashSerializeTest) {
58 // Should work with both 1 and non-1 values
59 TestSerialization(MetricSample::CrashSample("test", /*num_samples=*/1).get());
60 TestSerialization(
61 MetricSample::CrashSample("test", /*num_samples=*/10).get());
62 }
63
TEST_F(SerializationUtilsTest,HistogramSerializeTest)64 TEST_F(SerializationUtilsTest, HistogramSerializeTest) {
65 TestSerialization(MetricSample::HistogramSample(
66 "myhist", /*sample=*/13, /*min=*/1, /*max=*/100,
67 /*bucket_count=*/10, /*num_samples=*/1)
68 .get());
69 TestSerialization(MetricSample::HistogramSample(
70 "myhist", /*sample=*/13, /*min=*/1, /*max=*/100,
71 /*bucket_count=*/10, /*num_samples=*/2)
72 .get());
73 }
74
TEST_F(SerializationUtilsTest,LinearSerializeTest)75 TEST_F(SerializationUtilsTest, LinearSerializeTest) {
76 TestSerialization(
77 MetricSample::LinearHistogramSample("linearhist", /*sample=*/12,
78 /*max=*/30, /*num_samples=*/1)
79 .get());
80 TestSerialization(
81 MetricSample::LinearHistogramSample("linearhist", /*sample=*/12,
82 /*max=*/30, /*num_samples=*/10)
83 .get());
84 }
85
TEST_F(SerializationUtilsTest,SparseSerializeTest)86 TEST_F(SerializationUtilsTest, SparseSerializeTest) {
87 TestSerialization(MetricSample::SparseHistogramSample(
88 "mysparse", /*sample=*/30, /*num_samples=*/1)
89 .get());
90 TestSerialization(MetricSample::SparseHistogramSample(
91 "mysparse", /*sample=*/30, /*num_samples=*/10)
92 .get());
93 }
94
TEST_F(SerializationUtilsTest,UserActionSerializeTest)95 TEST_F(SerializationUtilsTest, UserActionSerializeTest) {
96 TestSerialization(
97 MetricSample::UserActionSample("myaction", /*num_samples=*/1).get());
98 TestSerialization(
99 MetricSample::UserActionSample("myaction", /*num_samples=*/10).get());
100 }
101
TEST_F(SerializationUtilsTest,InvalidCrashSerialize)102 TEST_F(SerializationUtilsTest, InvalidCrashSerialize) {
103 // No name
104 EXPECT_EQ(nullptr, MetricSample::ParseCrash(""));
105 // Empty name
106 EXPECT_EQ(nullptr, MetricSample::ParseCrash(" "));
107 // num_samples is not a number
108 EXPECT_EQ(nullptr, MetricSample::ParseCrash("kernel asdf"));
109 // Too many numbers
110 EXPECT_EQ(nullptr, MetricSample::ParseCrash("kernel 1 2"));
111 // Negative num_samples
112 EXPECT_EQ(nullptr, MetricSample::ParseCrash("kernel -1"));
113 }
114
TEST_F(SerializationUtilsTest,InvalidHistogramSample)115 TEST_F(SerializationUtilsTest, InvalidHistogramSample) {
116 // Too few parts
117 EXPECT_EQ(nullptr, MetricSample::ParseHistogram("hist 1 2 3"));
118 // Too many parts
119 EXPECT_EQ(nullptr, MetricSample::ParseHistogram("hist 1 2 3 4 5 6"));
120 // Empty hist name
121 EXPECT_EQ(nullptr, MetricSample::ParseHistogram(" 1 2 3 4 5"));
122 // sample is not a number
123 EXPECT_EQ(nullptr, MetricSample::ParseHistogram("hist a 2 3 4 5"));
124 // min is not a number
125 EXPECT_EQ(nullptr, MetricSample::ParseHistogram("hist 1 a 3 4 5"));
126 // max is not a number
127 EXPECT_EQ(nullptr, MetricSample::ParseHistogram("hist 1 2 a 4 5"));
128 // buckets is not a number
129 EXPECT_EQ(nullptr, MetricSample::ParseHistogram("hist 1 2 3 a 5"));
130 // num_samples is not a number
131 EXPECT_EQ(nullptr, MetricSample::ParseHistogram("hist 1 2 3 4 a"));
132 // Negative num_samples
133 EXPECT_EQ(nullptr, MetricSample::ParseHistogram("hist 1 2 3 4 -1"));
134 }
135
TEST_F(SerializationUtilsTest,InvalidSparseHistogramSample)136 TEST_F(SerializationUtilsTest, InvalidSparseHistogramSample) {
137 // Too few fields
138 EXPECT_EQ(nullptr, MetricSample::ParseSparseHistogram("name"));
139 // Too many fields
140 EXPECT_EQ(nullptr, MetricSample::ParseSparseHistogram("name 1 2 3"));
141 // No name
142 EXPECT_EQ(nullptr, MetricSample::ParseSparseHistogram(" 1 2"));
143 // Invalid sample
144 EXPECT_EQ(nullptr, MetricSample::ParseSparseHistogram("name a 2"));
145 // Invalid num_samples
146 EXPECT_EQ(nullptr, MetricSample::ParseSparseHistogram("name 1 a"));
147 // Negative num_samples
148 EXPECT_EQ(nullptr, MetricSample::ParseSparseHistogram("name 1 -1"));
149 }
150
TEST_F(SerializationUtilsTest,InvalidLinearHistogramSample)151 TEST_F(SerializationUtilsTest, InvalidLinearHistogramSample) {
152 // Too few fields
153 EXPECT_EQ(nullptr, MetricSample::ParseLinearHistogram("name 1"));
154 // Too many fields
155 EXPECT_EQ(nullptr, MetricSample::ParseLinearHistogram("name 1 2 3 4"));
156 // No name
157 EXPECT_EQ(nullptr, MetricSample::ParseLinearHistogram(" 1 2 3"));
158 // Invalid sample
159 EXPECT_EQ(nullptr, MetricSample::ParseLinearHistogram("name a 2 3"));
160 // Invalid max
161 EXPECT_EQ(nullptr, MetricSample::ParseLinearHistogram("name 1 a 3"));
162 // Invalid num_samples
163 EXPECT_EQ(nullptr, MetricSample::ParseLinearHistogram("name 1 2 a"));
164 // Negative num_samples
165 EXPECT_EQ(nullptr, MetricSample::ParseLinearHistogram("name 1 2 -1"));
166 }
167
TEST_F(SerializationUtilsTest,InvalidUserAction)168 TEST_F(SerializationUtilsTest, InvalidUserAction) {
169 // Too few fields
170 EXPECT_EQ(nullptr, MetricSample::ParseUserAction(""));
171 // Too many fields
172 EXPECT_EQ(nullptr, MetricSample::ParseUserAction("name 1 2"));
173 // No name
174 EXPECT_EQ(nullptr, MetricSample::ParseUserAction(" 1"));
175 // Invalid num_samples
176 EXPECT_EQ(nullptr, MetricSample::ParseUserAction("name a"));
177 // Negative num_samples
178 EXPECT_EQ(nullptr, MetricSample::ParseUserAction("name -1"));
179 }
180
TEST_F(SerializationUtilsTest,IllegalNameAreFilteredTest)181 TEST_F(SerializationUtilsTest, IllegalNameAreFilteredTest) {
182 std::unique_ptr<MetricSample> sample1 = MetricSample::SparseHistogramSample(
183 "no space", /*sample=*/10, /*num_samples=*/1);
184 std::unique_ptr<MetricSample> sample2 = MetricSample::LinearHistogramSample(
185 base::StringPrintf("here%cbhe", '\0'), /*sample=*/1, /*max=*/3,
186 /*num_samples=*/2);
187
188 EXPECT_FALSE(
189 SerializationUtils::WriteMetricToFile(*sample1.get(), filename()));
190 EXPECT_FALSE(
191 SerializationUtils::WriteMetricToFile(*sample2.get(), filename()));
192
193 ASSERT_FALSE(base::PathExists(filepath()));
194 }
195
TEST_F(SerializationUtilsTest,BadInputIsCaughtTest)196 TEST_F(SerializationUtilsTest, BadInputIsCaughtTest) {
197 std::string input(
198 base::StringPrintf("sparsehistogram%cname foo%c1", '\0', '\0'));
199 EXPECT_EQ(nullptr, MetricSample::ParseSparseHistogram(input).get());
200 }
201
TEST_F(SerializationUtilsTest,MessageSeparatedByZero)202 TEST_F(SerializationUtilsTest, MessageSeparatedByZero) {
203 std::unique_ptr<MetricSample> crash =
204 MetricSample::CrashSample("mycrash", /*num_samples=*/10);
205
206 SerializationUtils::WriteMetricToFile(*crash.get(), filename());
207 std::optional<int64_t> size = base::GetFileSize(filepath());
208
209 ASSERT_TRUE(size.has_value());
210 // 4 bytes for the size
211 // 5 bytes for crash
212 // 1 byte for \0
213 // 7 bytes for mycrash
214 // 3 bytes for " 10"
215 // 1 byte for \0
216 // -> total of 21
217 EXPECT_EQ(size.value(), 21);
218 }
219
TEST_F(SerializationUtilsTest,MessagesTooLongAreDiscardedTest)220 TEST_F(SerializationUtilsTest, MessagesTooLongAreDiscardedTest) {
221 // Creates a message that is bigger than the maximum allowed size.
222 // As we are adding extra character (crash, \0s, etc), if the name is
223 // kMessageMaxLength long, it will be too long.
224 std::string name(SerializationUtils::kMessageMaxLength, 'c');
225
226 std::unique_ptr<MetricSample> crash =
227 MetricSample::CrashSample(name, /*num_samples=*/10);
228 EXPECT_FALSE(SerializationUtils::WriteMetricToFile(*crash.get(), filename()));
229
230 std::optional<int64_t> size = base::GetFileSize(filepath());
231 ASSERT_TRUE(size.has_value());
232 EXPECT_EQ(0, size.value());
233 }
234
TEST_F(SerializationUtilsTest,ReadLongMessageTest)235 TEST_F(SerializationUtilsTest, ReadLongMessageTest) {
236 base::File test_file(filepath(),
237 base::File::FLAG_OPEN_ALWAYS | base::File::FLAG_APPEND);
238 std::string message(SerializationUtils::kMessageMaxLength + 1, 'c');
239
240 int32_t message_size = message.length() + sizeof(int32_t);
241 test_file.WriteAtCurrentPos(base::byte_span_from_ref(message_size));
242 test_file.WriteAtCurrentPos(base::as_byte_span(message));
243 test_file.Close();
244
245 std::unique_ptr<MetricSample> crash =
246 MetricSample::CrashSample("test", /*num_samples=*/10);
247 SerializationUtils::WriteMetricToFile(*crash.get(), filename());
248
249 std::vector<std::unique_ptr<MetricSample>> samples;
250 SerializationUtils::ReadAndTruncateMetricsFromFile(filename(), &samples);
251 ASSERT_EQ(size_t(1), samples.size());
252 ASSERT_TRUE(samples[0].get() != nullptr);
253 EXPECT_TRUE(crash->IsEqual(*samples[0]));
254 }
255
TEST_F(SerializationUtilsTest,NegativeLengthTest)256 TEST_F(SerializationUtilsTest, NegativeLengthTest) {
257 // This input is specifically constructed to yield a single crash sample when
258 // parsed by a buggy version of the code but fails to parse and doesn't yield
259 // samples when parsed by a correct implementation.
260 constexpr uint8_t kInput[] = {
261 // Length indicating that next length field is the negative one below.
262 // This sample is invalid as it contains more than three null bytes.
263 0x14,
264 0x00,
265 0x00,
266 0x00,
267 // Encoding of a valid crash sample.
268 0x0c,
269 0x00,
270 0x00,
271 0x00,
272 0x63,
273 0x72,
274 0x61,
275 0x73,
276 0x68,
277 0x00,
278 0x61,
279 0x00,
280 // Invalid sample that jumps past the negative length bytes below.
281 0x08,
282 0x00,
283 0x00,
284 0x00,
285 // This is -16 in two's complement interpretation, pointing to the valid
286 // crash sample before.
287 0xf0,
288 0xff,
289 0xff,
290 0xff,
291 };
292 ASSERT_TRUE(base::WriteFile(filepath(), base::span(kInput)));
293
294 std::vector<std::unique_ptr<MetricSample>> samples;
295 SerializationUtils::ReadAndTruncateMetricsFromFile(filename(), &samples);
296 ASSERT_EQ(0U, samples.size());
297 }
298
TEST_F(SerializationUtilsTest,WriteReadTest_TruncateFile)299 TEST_F(SerializationUtilsTest, WriteReadTest_TruncateFile) {
300 std::unique_ptr<MetricSample> hist = MetricSample::HistogramSample(
301 "myhist", /*sample=*/1, /*min=*/2, /*max=*/3, /*bucket_count=*/4,
302 /*num_samples=*/5);
303 std::unique_ptr<MetricSample> crash =
304 MetricSample::CrashSample("mycrash", /*num_samples=*/10);
305 std::unique_ptr<MetricSample> lhist = MetricSample::LinearHistogramSample(
306 "linear", /*sample=*/1, /*max=*/10, /*num_samples=*/10);
307 std::unique_ptr<MetricSample> shist = MetricSample::SparseHistogramSample(
308 "mysparse", /*sample=*/30, /*num_samples=*/10);
309 std::unique_ptr<MetricSample> action =
310 MetricSample::UserActionSample("myaction", /*num_samples=*/1);
311
312 SerializationUtils::WriteMetricToFile(*hist.get(), filename());
313 SerializationUtils::WriteMetricToFile(*crash.get(), filename());
314 SerializationUtils::WriteMetricToFile(*lhist.get(), filename());
315 SerializationUtils::WriteMetricToFile(*shist.get(), filename());
316 SerializationUtils::WriteMetricToFile(*action.get(), filename());
317 std::vector<std::unique_ptr<MetricSample>> vect;
318 SerializationUtils::ReadAndTruncateMetricsFromFile(filename(), &vect);
319 // NOTE: Should *not* have an entry for each repeated sample.
320 ASSERT_EQ(vect.size(), size_t(5));
321 for (auto& sample : vect) {
322 ASSERT_NE(nullptr, sample.get());
323 }
324 EXPECT_TRUE(hist->IsEqual(*vect[0]));
325 EXPECT_TRUE(crash->IsEqual(*vect[1]));
326 EXPECT_TRUE(lhist->IsEqual(*vect[2]));
327 EXPECT_TRUE(shist->IsEqual(*vect[3]));
328 EXPECT_TRUE(action->IsEqual(*vect[4]));
329
330 std::optional<int64_t> size = base::GetFileSize(filepath());
331 ASSERT_TRUE(size.has_value());
332 ASSERT_EQ(0, size.value());
333 }
334
TEST_F(SerializationUtilsTest,WriteReadTest_DeleteFile)335 TEST_F(SerializationUtilsTest, WriteReadTest_DeleteFile) {
336 std::unique_ptr<MetricSample> hist = MetricSample::HistogramSample(
337 "myhist", /*sample=*/1, /*min=*/2, /*max=*/3, /*bucket_count=*/4,
338 /*num_samples=*/5);
339 std::unique_ptr<MetricSample> crash =
340 MetricSample::CrashSample("mycrash", /*num_samples=*/10);
341 std::unique_ptr<MetricSample> lhist = MetricSample::LinearHistogramSample(
342 "linear", /*sample=*/1, /*max=*/10, /*num_samples=*/10);
343 std::unique_ptr<MetricSample> shist = MetricSample::SparseHistogramSample(
344 "mysparse", /*sample=*/30, /*num_samples=*/10);
345 std::unique_ptr<MetricSample> action =
346 MetricSample::UserActionSample("myaction", /*num_samples=*/1);
347
348 SerializationUtils::WriteMetricToFile(*hist.get(), filename());
349 SerializationUtils::WriteMetricToFile(*crash.get(), filename());
350 SerializationUtils::WriteMetricToFile(*lhist.get(), filename());
351 SerializationUtils::WriteMetricToFile(*shist.get(), filename());
352 SerializationUtils::WriteMetricToFile(*action.get(), filename());
353 std::vector<std::unique_ptr<MetricSample>> vect;
354 SerializationUtils::ReadAndDeleteMetricsFromFile(filename(), &vect);
355 // NOTE: Should *not* have an entry for each repeated sample.
356 ASSERT_EQ(vect.size(), size_t(5));
357 for (auto& sample : vect) {
358 ASSERT_NE(nullptr, sample.get());
359 }
360 EXPECT_TRUE(hist->IsEqual(*vect[0]));
361 EXPECT_TRUE(crash->IsEqual(*vect[1]));
362 EXPECT_TRUE(lhist->IsEqual(*vect[2]));
363 EXPECT_TRUE(shist->IsEqual(*vect[3]));
364 EXPECT_TRUE(action->IsEqual(*vect[4]));
365
366 EXPECT_FALSE(base::PathExists(filepath()));
367 }
368
TEST_F(SerializationUtilsTest,TooManyMessagesTest)369 TEST_F(SerializationUtilsTest, TooManyMessagesTest) {
370 std::unique_ptr<MetricSample> hist = MetricSample::HistogramSample(
371 "myhist", /*sample=*/1, /*min=*/2, /*max=*/3, /*bucket_count=*/4,
372 /*num_samples=*/5);
373
374 constexpr int kDiscardedSamples = 50000;
375 for (int i = 0;
376 i < SerializationUtils::kMaxMessagesPerRead + kDiscardedSamples; i++) {
377 SerializationUtils::WriteMetricToFile(*hist.get(), filename());
378 }
379
380 std::vector<std::unique_ptr<MetricSample>> vect;
381 SerializationUtils::ReadAndTruncateMetricsFromFile(filename(), &vect);
382 ASSERT_EQ(SerializationUtils::kMaxMessagesPerRead,
383 static_cast<int>(vect.size()));
384 for (auto& sample : vect) {
385 ASSERT_NE(nullptr, sample.get());
386 EXPECT_TRUE(hist->IsEqual(*sample));
387 }
388
389 std::optional<int64_t> size = base::GetFileSize(filepath());
390 ASSERT_TRUE(size.has_value());
391 ASSERT_EQ(0, size.value());
392 }
393
TEST_F(SerializationUtilsTest,ReadEmptyFile)394 TEST_F(SerializationUtilsTest, ReadEmptyFile) {
395 {
396 // Create a zero-length file and then close file descriptor.
397 base::File file(filepath(),
398 base::File::FLAG_CREATE | base::File::FLAG_WRITE);
399 ASSERT_TRUE(file.IsValid());
400 }
401
402 std::vector<std::unique_ptr<MetricSample>> vect;
403 SerializationUtils::ReadAndTruncateMetricsFromFile(filename(), &vect);
404 EXPECT_THAT(vect, IsEmpty());
405 }
406
TEST_F(SerializationUtilsTest,ReadNonExistentFile)407 TEST_F(SerializationUtilsTest, ReadNonExistentFile) {
408 base::DeleteFile(filepath()); // Ensure non-existance.
409 base::HistogramTester histogram_tester;
410 std::vector<std::unique_ptr<MetricSample>> vect;
411 SerializationUtils::ReadAndTruncateMetricsFromFile(filename(), &vect);
412 EXPECT_THAT(vect, IsEmpty());
413 }
414
TEST_F(SerializationUtilsTest,ParseInvalidType)415 TEST_F(SerializationUtilsTest, ParseInvalidType) {
416 // Verify that parsing of an invalid sample type fails.
417 EXPECT_EQ(nullptr, SerializationUtils::ParseSample(base::StringPrintf(
418 "not_a_type%cvalue%c", '\0', '\0')));
419 }
420
421 } // namespace
422 } // namespace metrics
423