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