1 // Copyright 2023 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/structured_metrics_recorder.h"
6
7 #include <cstdint>
8 #include <memory>
9
10 #include "base/files/file_path.h"
11 #include "base/files/file_util.h"
12 #include "base/files/scoped_temp_dir.h"
13 #include "base/strings/string_number_conversions.h"
14 #include "base/test/metrics/histogram_tester.h"
15 #include "base/test/scoped_feature_list.h"
16 #include "base/test/task_environment.h"
17 #include "base/threading/scoped_blocking_call.h"
18 #include "components/metrics/structured/event.h"
19 #include "components/metrics/structured/recorder.h"
20 #include "components/metrics/structured/storage.pb.h"
21 #include "components/metrics/structured/structured_events.h"
22 #include "components/metrics/structured/structured_metrics_features.h"
23 #include "components/metrics/structured/test/test_event_storage.h"
24 #include "components/metrics/structured/test/test_key_data_provider.h"
25 #include "testing/gtest/include/gtest/gtest.h"
26 #include "third_party/metrics_proto/chrome_user_metrics_extension.pb.h"
27
28 namespace metrics::structured {
29
30 namespace {
31
32 // These project, event, and metric names are used for testing.
33
34 // The name hash of "TestProjectOne".
35 constexpr uint64_t kProjectOneHash = UINT64_C(16881314472396226433);
36 // The name hash of "TestProjectTwo".
37 constexpr uint64_t kProjectTwoHash = UINT64_C(5876808001962504629);
38 // The name hash of "TestProjectThree".
39 constexpr uint64_t kProjectThreeHash = UINT64_C(10860358748803291132);
40 // The name hash of "TestProjectFour".
41 constexpr uint64_t kProjectFourHash = UINT64_C(6801665881746546626);
42 // The name hash of "TestProjectFive"
43 constexpr uint64_t kProjectFiveHash = UINT64_C(3960582687892677139);
44 // The name hash of "TestProjectSix"
45 constexpr uint64_t kProjectSixHash = UINT64_C(6972396123792667134);
46 // The name hash of "CrOSEvents"
47 constexpr uint64_t kCrOSEventsProjectHash = UINT64_C(12657197978410187837);
48
49 // The name hash of "chrome::TestProjectOne::TestEventOne".
50 constexpr uint64_t kEventOneHash = UINT64_C(13593049295042080097);
51 // The name hash of "chrome::TestProjectTwo::TestEventTwo".
52 constexpr uint64_t kEventTwoHash = UINT64_C(8995967733561999410);
53 // The name hash of "chrome::TestProjectTwo::TestEventThree".
54 constexpr uint64_t kEventThreeHash = UINT64_C(5848687377041124372);
55 // The name hash of "chrome::TestProjectFour::TestEventFive".
56 constexpr uint64_t kEventFiveHash = UINT64_C(7045523601811399253);
57 // The name hash of "chrome::TestProjectFour::TestEventSix".
58 constexpr uint64_t kEventSixHash = UINT64_C(2873337042686447043);
59 // The name hash of "chrome::TestProjectSix::TestEventSeven".
60 constexpr uint64_t kEventSevenHash = UINT64_C(16749091071228286247);
61 // The name hash of "chrome::CrOSEvents::NoMetricsEvent".
62 constexpr uint64_t kNoMetricsEventHash = UINT64_C(5106854608989380457);
63 // The name has for "chrome::TestProjectSevent::TestEventEight".
64 const uint64_t kEventEightHash = UINT64_C(16290206418240617738);
65
66 // The name hash of "TestMetricOne".
67 constexpr uint64_t kMetricOneHash = UINT64_C(637929385654885975);
68 // The name hash of "TestMetricTwo".
69 constexpr uint64_t kMetricTwoHash = UINT64_C(14083999144141567134);
70 // The name hash of "TestMetricThree".
71 constexpr uint64_t kMetricThreeHash = UINT64_C(13469300759843809564);
72 // The name hash of "TestMetricFive".
73 constexpr uint64_t kMetricFiveHash = UINT64_C(8665976921794972190);
74 // The name hash of "TestMetricSix".
75 constexpr uint64_t kMetricSixHash = UINT64_C(3431522567539822144);
76 // The name hash of "TestMetricSeven".
77 constexpr uint64_t kMetricSevenHash = UINT64_C(8395865158198697574);
78
79 // The hex-encoded first 8 bytes of SHA256("aaa...a")
80 constexpr char kProjectOneId[] = "3BA3F5F43B926026";
81 // The hex-encoded first 8 bytes of SHA256("bbb...b")
82 constexpr char kProjectTwoId[] = "BDB339768BC5E4FE";
83 // The hex-encoded first 8 bytes of SHA256("ddd...d")
84 constexpr char kProjectFourId[] = "FBBBB6DE2AA74C3C";
85
86 // Test values.
87 constexpr char kValueOne[] = "value one";
88 constexpr char kValueTwo[] = "value two";
89
HashToHex(const uint64_t hash)90 std::string HashToHex(const uint64_t hash) {
91 return base::HexEncode(&hash, sizeof(uint64_t));
92 }
93
94 class TestRecorder : public StructuredMetricsClient::RecordingDelegate {
95 public:
96 TestRecorder() = default;
97 TestRecorder(const TestRecorder& recorder) = delete;
98 TestRecorder& operator=(const TestRecorder& recorder) = delete;
99 ~TestRecorder() override = default;
100
RecordEvent(Event && event)101 void RecordEvent(Event&& event) override {
102 Recorder::GetInstance()->RecordEvent(std::move(event));
103 }
104
IsReadyToRecord() const105 bool IsReadyToRecord() const override { return true; }
106 };
107
108 } // namespace
109
110 class TestStructuredMetricsRecorder : public StructuredMetricsRecorder {
111 public:
TestStructuredMetricsRecorder(const base::FilePath & device_key_path,const base::FilePath & profile_key_path)112 TestStructuredMetricsRecorder(const base::FilePath& device_key_path,
113 const base::FilePath& profile_key_path)
114 : StructuredMetricsRecorder(
115 std::make_unique<TestKeyDataProvider>(device_key_path,
116 profile_key_path),
117 std::make_unique<TestEventStorage>()) {}
118
119 using StructuredMetricsRecorder::StructuredMetricsRecorder;
120 };
121
122 class StructuredMetricsRecorderTest : public testing::Test {
123 protected:
SetUp()124 void SetUp() override {
125 ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
126
127 // Fixed paths to store keys for test.
128 device_key_path_ = temp_dir_.GetPath()
129 .Append(FILE_PATH_LITERAL("structured_metrics"))
130 .Append(FILE_PATH_LITERAL("device_keys"));
131 profile_key_path_ = temp_dir_.GetPath()
132 .Append(FILE_PATH_LITERAL("structured_metrics"))
133 .Append(FILE_PATH_LITERAL("profile_keys"));
134
135 Recorder::GetInstance()->SetUiTaskRunner(
136 task_environment_.GetMainThreadTaskRunner());
137 StructuredMetricsClient::Get()->SetDelegate(&test_recorder_);
138 // Move the mock date forward from day 0, because KeyData assumes that day 0
139 // is a bug.
140 task_environment_.AdvanceClock(base::Days(1000));
141 }
142
TempDirPath()143 base::FilePath TempDirPath() { return temp_dir_.GetPath(); }
144
ProfileKeyFilePath()145 base::FilePath ProfileKeyFilePath() { return profile_key_path_; }
146
DeviceKeyFilePath()147 base::FilePath DeviceKeyFilePath() { return device_key_path_; }
148
TearDown()149 void TearDown() override { StructuredMetricsClient::Get()->UnsetDelegate(); }
150
Wait()151 void Wait() { task_environment_.RunUntilIdle(); }
152
153 // Adds a project to the disallowed projects list.
AddDisallowedProject(uint64_t project_name_hash)154 void AddDisallowedProject(uint64_t project_name_hash) {
155 recorder_->AddDisallowedProjectForTest(project_name_hash);
156 }
157
WriteTestingProfileKeys()158 void WriteTestingProfileKeys() {
159 const int today = (base::Time::Now() - base::Time::UnixEpoch()).InDays();
160
161 KeyDataProto proto;
162 KeyProto& key_one = (*proto.mutable_keys())[kProjectOneHash];
163 key_one.set_key("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa");
164 key_one.set_last_rotation(today);
165 key_one.set_rotation_period(90);
166
167 KeyProto& key_two = (*proto.mutable_keys())[kProjectTwoHash];
168 key_two.set_key("bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb");
169 key_two.set_last_rotation(today);
170 key_two.set_rotation_period(90);
171
172 KeyProto& key_three = (*proto.mutable_keys())[kProjectThreeHash];
173 key_three.set_key("cccccccccccccccccccccccccccccccc");
174 key_three.set_last_rotation(today);
175 key_three.set_rotation_period(90);
176
177 KeyProto& cros_events = (*proto.mutable_keys())[kCrOSEventsProjectHash];
178 cros_events.set_key("cccccccccccccccccccccccccccccccc");
179 cros_events.set_last_rotation(today);
180 cros_events.set_rotation_period(90);
181
182 base::CreateDirectory(ProfileKeyFilePath().DirName());
183 ASSERT_TRUE(
184 base::WriteFile(ProfileKeyFilePath(), proto.SerializeAsString()));
185 Wait();
186 }
187
WriteTestingDeviceKeys()188 void WriteTestingDeviceKeys() {
189 const int today = (base::Time::Now() - base::Time::UnixEpoch()).InDays();
190
191 KeyDataProto proto;
192 KeyProto& key = (*proto.mutable_keys())[kProjectFourHash];
193 key.set_key("dddddddddddddddddddddddddddddddd");
194 key.set_last_rotation(today);
195 key.set_rotation_period(90);
196
197 base::CreateDirectory(DeviceKeyFilePath().DirName());
198 ASSERT_TRUE(
199 base::WriteFile(DeviceKeyFilePath(), proto.SerializeAsString()));
200 Wait();
201 }
202
ReadKeys(const base::FilePath & filepath)203 KeyDataProto ReadKeys(const base::FilePath& filepath) {
204 base::ScopedBlockingCall scoped_blocking_call(
205 FROM_HERE, base::BlockingType::MAY_BLOCK);
206 Wait();
207 CHECK(base::PathExists(filepath));
208
209 std::string proto_str;
210 CHECK(base::ReadFileToString(filepath, &proto_str));
211
212 KeyDataProto proto;
213 CHECK(proto.ParseFromString(proto_str));
214 return proto;
215 }
216
217 // Simulates the three external events that the structure metrics system cares
218 // about: the metrics service initializing and enabling its providers, and a
219 // user logging in.
Init()220 void Init() {
221 // Create the provider, normally done by the ChromeMetricsServiceClient.
222 recorder_ = std::make_unique<TestStructuredMetricsRecorder>(
223 device_key_path_, profile_key_path_);
224 // Enable recording, normally done after the metrics service has checked
225 // consent allows recording.
226 recorder_->EnableRecording();
227 // Add a profile, normally done by the ChromeMetricsServiceClient after a
228 // user logs in.
229 recorder_->OnProfileAdded(TempDirPath());
230 Wait();
231 }
232
233 // Enables recording without adding a profile.
InitWithoutLogin()234 void InitWithoutLogin() {
235 // Create the provider, normally done by the ChromeMetricsServiceClient.
236 recorder_ = std::make_unique<TestStructuredMetricsRecorder>(
237 device_key_path_, profile_key_path_);
238 // Enable recording, normally done after the metrics service has checked
239 // consent allows recording.
240 recorder_->EnableRecording();
241 }
242
243 // Sets up StructuredMetricsRecorder.
InitWithoutEnabling()244 void InitWithoutEnabling() {
245 // Create the provider, normally done by the ChromeMetricsServiceClient.
246 recorder_ = std::make_unique<TestStructuredMetricsRecorder>(
247 device_key_path_, profile_key_path_);
248 }
249
is_initialized()250 bool is_initialized() { return recorder_->IsInitialized(); }
251
is_recording_enabled()252 bool is_recording_enabled() { return recorder_->recording_enabled_; }
253
OnRecordingEnabled()254 void OnRecordingEnabled() { recorder_->EnableRecording(); }
255
OnRecordingDisabled()256 void OnRecordingDisabled() { recorder_->DisableRecording(); }
257
OnReportingStateChanged(bool enabled)258 void OnReportingStateChanged(bool enabled) {
259 recorder_->OnReportingStateChanged(enabled);
260 }
261
OnProfileAdded(const base::FilePath & path)262 void OnProfileAdded(const base::FilePath& path) {
263 recorder_->OnProfileAdded(path);
264 }
265
GetUMAEventMetrics()266 StructuredDataProto GetUMAEventMetrics() {
267 ChromeUserMetricsExtension uma_proto;
268 recorder_->ProvideUmaEventMetrics(uma_proto);
269 Wait();
270 return uma_proto.structured_data();
271 }
272
GetEventMetrics()273 StructuredDataProto GetEventMetrics() {
274 ChromeUserMetricsExtension uma_proto;
275 recorder_->ProvideEventMetrics(uma_proto);
276 Wait();
277 return uma_proto.structured_data();
278 }
279
ExpectNoErrors()280 void ExpectNoErrors() {
281 histogram_tester_.ExpectTotalCount("UMA.StructuredMetrics.InternalError",
282 0);
283 }
284
285 protected:
286 std::unique_ptr<TestStructuredMetricsRecorder> recorder_;
287 // Feature list should be constructed before task environment.
288 base::test::ScopedFeatureList scoped_feature_list_;
289 base::test::TaskEnvironment task_environment_{
290 base::test::TaskEnvironment::MainThreadType::UI,
291 base::test::TaskEnvironment::ThreadPoolExecutionMode::QUEUED,
292 base::test::TaskEnvironment::TimeSource::MOCK_TIME};
293 base::HistogramTester histogram_tester_;
294 base::ScopedTempDir temp_dir_;
295
296 private:
297 TestRecorder test_recorder_;
298
299 base::FilePath device_key_path_;
300 base::FilePath profile_key_path_;
301 };
302
303 // Simple test to ensure initialization works correctly in the case of a
304 // first-time run.
TEST_F(StructuredMetricsRecorderTest,RecorderInitializesFromBlankSlate)305 TEST_F(StructuredMetricsRecorderTest, RecorderInitializesFromBlankSlate) {
306 Init();
307 EXPECT_TRUE(is_initialized());
308 EXPECT_TRUE(is_recording_enabled());
309 ExpectNoErrors();
310 }
311
312 // Ensure a call to OnRecordingDisabled prevents reporting.
TEST_F(StructuredMetricsRecorderTest,EventsNotReportedWhenRecordingDisabled)313 TEST_F(StructuredMetricsRecorderTest, EventsNotReportedWhenRecordingDisabled) {
314 Init();
315 OnRecordingDisabled();
316 events::v2::test_project_one::TestEventOne().SetTestMetricTwo(1).Record();
317 events::v2::test_project_three::TestEventFour().SetTestMetricFour(1).Record();
318 EXPECT_EQ(GetUMAEventMetrics().events_size(), 0);
319 EXPECT_EQ(GetEventMetrics().events_size(), 0);
320 ExpectNoErrors();
321 }
322
323 // Ensure that disabling the structured metrics feature flag prevents all
324 // structured metrics reporting.
TEST_F(StructuredMetricsRecorderTest,EventsNotReportedWhenFeatureDisabled)325 TEST_F(StructuredMetricsRecorderTest, EventsNotReportedWhenFeatureDisabled) {
326 scoped_feature_list_.InitAndDisableFeature(features::kStructuredMetrics);
327
328 Init();
329 // OnRecordingEnabled should not actually enable recording because the flag is
330 // disabled.
331 OnRecordingEnabled();
332 events::v2::test_project_one::TestEventOne().SetTestMetricTwo(1).Record();
333 events::v2::test_project_three::TestEventFour().SetTestMetricFour(1).Record();
334 EXPECT_EQ(GetUMAEventMetrics().events_size(), 0);
335 EXPECT_EQ(GetEventMetrics().events_size(), 0);
336 ExpectNoErrors();
337 }
338
339 // Ensure that keys and unsent logs are deleted when reporting is disabled, and
340 // that reporting resumes when re-enabled.
TEST_F(StructuredMetricsRecorderTest,ReportingStateChangesHandledCorrectly)341 TEST_F(StructuredMetricsRecorderTest, ReportingStateChangesHandledCorrectly) {
342 Init();
343
344 // Record an event and read the keys, there should be one.
345 events::v2::test_project_one::TestEventOne().Record();
346 EXPECT_EQ(GetEventMetrics().events_size(), 1);
347
348 const KeyDataProto enabled_proto = ReadKeys(ProfileKeyFilePath());
349 EXPECT_EQ(enabled_proto.keys_size(), 1);
350
351 // Record an event, disable reporting, then record another event. Both of
352 // these events should have been ignored.
353 events::v2::test_project_one::TestEventOne().Record();
354 OnReportingStateChanged(false);
355 events::v2::test_project_one::TestEventOne().Record();
356 EXPECT_EQ(GetEventMetrics().events_size(), 0);
357
358 // Read the keys again, it should be empty.
359 const KeyDataProto disabled_proto = ReadKeys(ProfileKeyFilePath());
360 EXPECT_EQ(disabled_proto.keys_size(), 0);
361
362 // Enable reporting again, and record an event.
363 OnReportingStateChanged(true);
364 OnRecordingEnabled();
365 events::v2::test_project_one::TestEventOne().Record();
366 EXPECT_EQ(GetEventMetrics().events_size(), 1);
367 const KeyDataProto reenabled_proto = ReadKeys(ProfileKeyFilePath());
368 EXPECT_EQ(reenabled_proto.keys_size(), 1);
369
370 ExpectNoErrors();
371 }
372
373 // Ensure that, if recording is disabled part-way through initialization, the
374 // initialization still completes correctly, but recording is correctly set to
375 // disabled.
TEST_F(StructuredMetricsRecorderTest,RecordingDisabledDuringInitialization)376 TEST_F(StructuredMetricsRecorderTest, RecordingDisabledDuringInitialization) {
377 InitWithoutEnabling();
378
379 OnProfileAdded(TempDirPath());
380 OnRecordingDisabled();
381 EXPECT_FALSE(is_initialized());
382 EXPECT_FALSE(is_recording_enabled());
383
384 Wait();
385 EXPECT_TRUE(is_initialized());
386 EXPECT_FALSE(is_recording_enabled());
387
388 ExpectNoErrors();
389 }
390
391 // Ensure that recording is disabled until explicitly enabled with a call to
392 // OnRecordingEnabled.
TEST_F(StructuredMetricsRecorderTest,RecordingDisabledByDefault)393 TEST_F(StructuredMetricsRecorderTest, RecordingDisabledByDefault) {
394 InitWithoutEnabling();
395
396 OnProfileAdded(TempDirPath());
397 Wait();
398 EXPECT_TRUE(is_initialized());
399 EXPECT_FALSE(is_recording_enabled());
400
401 OnRecordingEnabled();
402 EXPECT_TRUE(is_recording_enabled());
403
404 ExpectNoErrors();
405 }
406
TEST_F(StructuredMetricsRecorderTest,RecordedEventAppearsInReport)407 TEST_F(StructuredMetricsRecorderTest, RecordedEventAppearsInReport) {
408 Init();
409
410 events::v2::test_project_one::TestEventOne()
411 .SetTestMetricOne("a string")
412 .SetTestMetricTwo(12345)
413 .Record();
414 events::v2::test_project_one::TestEventOne()
415 .SetTestMetricOne("a string")
416 .SetTestMetricTwo(12345)
417 .Record();
418 events::v2::test_project_one::TestEventOne()
419 .SetTestMetricOne("a string")
420 .SetTestMetricTwo(12345)
421 .Record();
422
423 EXPECT_EQ(GetUMAEventMetrics().events_size(), 0);
424 EXPECT_EQ(GetEventMetrics().events_size(), 3);
425 ExpectNoErrors();
426 }
427
TEST_F(StructuredMetricsRecorderTest,EventMetricsReportedCorrectly)428 TEST_F(StructuredMetricsRecorderTest, EventMetricsReportedCorrectly) {
429 WriteTestingProfileKeys();
430 Init();
431
432 events::v2::test_project_one::TestEventOne()
433 .SetTestMetricOne(kValueOne)
434 .SetTestMetricTwo(12345)
435 .Record();
436 events::v2::test_project_two::TestEventTwo()
437 .SetTestMetricThree(kValueTwo)
438 .Record();
439
440 const auto data = GetEventMetrics();
441 ASSERT_EQ(data.events_size(), 2);
442
443 { // First event
444 const auto& event = data.events(0);
445 EXPECT_EQ(event.event_name_hash(), kEventOneHash);
446 EXPECT_EQ(event.project_name_hash(), kProjectOneHash);
447 EXPECT_EQ(HashToHex(event.profile_event_id()), kProjectOneId);
448 ASSERT_EQ(event.metrics_size(), 2);
449
450 { // First metric
451 const auto& metric = event.metrics(0);
452 EXPECT_EQ(metric.name_hash(), kMetricOneHash);
453 EXPECT_EQ(HashToHex(metric.value_hmac()),
454 // Value of HMAC_256("aaa...a", concat(hex(kMetricOneHash),
455 // kValueOne))
456 "8C2469269D142715");
457 }
458
459 { // Second metric
460 const auto& metric = event.metrics(1);
461 EXPECT_EQ(metric.name_hash(), kMetricTwoHash);
462 EXPECT_EQ(metric.value_int64(), 12345);
463 }
464 }
465
466 { // Second event
467 const auto& event = data.events(1);
468 EXPECT_EQ(event.event_name_hash(), kEventTwoHash);
469 EXPECT_EQ(event.project_name_hash(), kProjectTwoHash);
470 EXPECT_EQ(HashToHex(event.profile_event_id()), kProjectTwoId);
471 ASSERT_EQ(event.metrics_size(), 1);
472
473 { // First metric
474 const auto& metric = event.metrics(0);
475 EXPECT_EQ(metric.name_hash(), kMetricThreeHash);
476 EXPECT_EQ(HashToHex(metric.value_hmac()),
477 // Value of HMAC_256("bbb...b", concat(hex(kProjectTwoHash),
478 // kValueTwo))
479 "86F0169868588DC7");
480 }
481 }
482
483 histogram_tester_.ExpectTotalCount("UMA.StructuredMetrics.InternalError", 0);
484 }
485
486 // Ensure that events containing raw string metrics are reported correctly.
TEST_F(StructuredMetricsRecorderTest,RawStringMetricsReportedCorrectly)487 TEST_F(StructuredMetricsRecorderTest, RawStringMetricsReportedCorrectly) {
488 Init();
489
490 const std::string test_string = "a raw string value";
491 events::v2::test_project_five::TestEventSix()
492 .SetTestMetricSix(test_string)
493 .Record();
494
495 const auto data = GetEventMetrics();
496 ASSERT_EQ(data.events_size(), 1);
497
498 const auto& event = data.events(0);
499 EXPECT_EQ(event.event_name_hash(), kEventSixHash);
500 EXPECT_EQ(event.project_name_hash(), kProjectFiveHash);
501 EXPECT_FALSE(event.has_profile_event_id());
502 EXPECT_EQ(event.event_type(), StructuredEventProto_EventType_RAW_STRING);
503
504 ASSERT_EQ(event.metrics_size(), 1);
505 const auto& metric = event.metrics(0);
506
507 EXPECT_EQ(metric.name_hash(), kMetricSixHash);
508 EXPECT_EQ(metric.value_string(), test_string);
509 }
510
TEST_F(StructuredMetricsRecorderTest,FloatMetricsReportedCorrectly)511 TEST_F(StructuredMetricsRecorderTest, FloatMetricsReportedCorrectly) {
512 Init();
513
514 const float test_float = 3.4;
515 const float test_float2 = 3.14e-8;
516
517 events::v2::test_project_six::TestEventSeven()
518 .SetTestMetricSeven(test_float)
519 .Record();
520
521 events::v2::test_project_six::TestEventSeven()
522 .SetTestMetricSeven(test_float2)
523 .Record();
524
525 const auto data = GetEventMetrics();
526 ASSERT_EQ(data.events_size(), 2);
527
528 const auto& event = data.events(0);
529 EXPECT_EQ(event.event_name_hash(), kEventSevenHash);
530 EXPECT_EQ(event.project_name_hash(), kProjectSixHash);
531 EXPECT_FALSE(event.has_profile_event_id());
532
533 ASSERT_EQ(event.metrics_size(), 1);
534 const auto& metric = event.metrics(0);
535
536 EXPECT_EQ(metric.name_hash(), kMetricSevenHash);
537 EXPECT_EQ(metric.value_double(), test_float);
538
539 const auto& event2 = data.events(1);
540 EXPECT_EQ(event2.event_name_hash(), kEventSevenHash);
541 EXPECT_EQ(event2.project_name_hash(), kProjectSixHash);
542 EXPECT_FALSE(event2.has_profile_event_id());
543
544 ASSERT_EQ(event2.metrics_size(), 1);
545 const auto& metric2 = event2.metrics(0);
546
547 EXPECT_EQ(metric2.name_hash(), kMetricSevenHash);
548 EXPECT_EQ(metric2.value_double(), test_float2);
549 }
550
551 // TODO: Test copied in AshStructuredMetricsRecorder unit tests, remove this one
552 // once the test key provider is simplified.
TEST_F(StructuredMetricsRecorderTest,DeviceKeysUsedForDeviceScopedProjects)553 TEST_F(StructuredMetricsRecorderTest, DeviceKeysUsedForDeviceScopedProjects) {
554 WriteTestingProfileKeys();
555 WriteTestingDeviceKeys();
556 Init();
557
558 // This event's project has device scope set, so should use the per-device
559 // keys set by WriteTestingDeviceKeys. In this case the expected key is
560 // "ddd...d", which we observe by checking the ID and HMAC have the correct
561 // value given that key.
562 events::v2::test_project_four::TestEventFive()
563 .SetTestMetricFive("value")
564 .Record();
565
566 const auto data = GetEventMetrics();
567 ASSERT_EQ(data.events_size(), 1);
568
569 const auto& event = data.events(0);
570 EXPECT_EQ(event.event_name_hash(), kEventFiveHash);
571 EXPECT_EQ(event.project_name_hash(), kProjectFourHash);
572 // The hex-encoded first 8 bytes of SHA256("ddd...d").
573 EXPECT_EQ(HashToHex(event.profile_event_id()), kProjectFourId);
574 ASSERT_EQ(event.metrics_size(), 1);
575
576 const auto& metric = event.metrics(0);
577 EXPECT_EQ(metric.name_hash(), kMetricFiveHash);
578 EXPECT_EQ(HashToHex(metric.value_hmac()),
579 // Value of HMAC_256("ddd...d", concat(hex(kMetricFiveHash),
580 // "value"))
581 "4CC202FAA78FDC7A");
582
583 histogram_tester_.ExpectTotalCount("UMA.StructuredMetrics.InternalError", 0);
584 }
585
586 // Check that a full int64 can be recorded, and is not truncated to an int32.
TEST_F(StructuredMetricsRecorderTest,Int64MetricsNotTruncated)587 TEST_F(StructuredMetricsRecorderTest, Int64MetricsNotTruncated) {
588 Init();
589 const int64_t big = 1ll << 60;
590 events::v2::test_project_one::TestEventOne().SetTestMetricTwo(big).Record();
591
592 const auto data = GetEventMetrics();
593 ASSERT_EQ(data.events_size(), 1);
594 const auto& event = data.events(0);
595 ASSERT_EQ(event.metrics_size(), 1);
596 const auto& metric = event.metrics(0);
597 EXPECT_EQ(metric.value_int64(), big);
598 }
599
TEST_F(StructuredMetricsRecorderTest,EventsWithinProjectReportedWithSameID)600 TEST_F(StructuredMetricsRecorderTest, EventsWithinProjectReportedWithSameID) {
601 WriteTestingProfileKeys();
602 Init();
603
604 events::v2::test_project_one::TestEventOne().Record();
605 events::v2::test_project_two::TestEventTwo().Record();
606 events::v2::test_project_two::TestEventThree().Record();
607
608 const auto data = GetEventMetrics();
609 ASSERT_EQ(data.events_size(), 3);
610
611 const auto& event_one = data.events(0);
612 const auto& event_two = data.events(1);
613 const auto& event_three = data.events(2);
614
615 // Check events are in the right order.
616 EXPECT_EQ(event_one.event_name_hash(), kEventOneHash);
617 EXPECT_EQ(event_two.event_name_hash(), kEventTwoHash);
618 EXPECT_EQ(event_three.event_name_hash(), kEventThreeHash);
619
620 // Events two and three share a project, so should have the same project
621 // name hash. Event one should have its own project name hash.
622 EXPECT_EQ(event_one.project_name_hash(), kProjectOneHash);
623 EXPECT_EQ(event_two.project_name_hash(), kProjectTwoHash);
624 EXPECT_EQ(event_three.project_name_hash(), kProjectTwoHash);
625
626 // Events two and three share a project, so should have the same ID. Event
627 // one should have its own ID.
628 EXPECT_EQ(HashToHex(event_one.profile_event_id()), kProjectOneId);
629 EXPECT_EQ(HashToHex(event_two.profile_event_id()), kProjectTwoId);
630 EXPECT_EQ(HashToHex(event_three.profile_event_id()), kProjectTwoId);
631
632 histogram_tester_.ExpectTotalCount("UMA.StructuredMetrics.InternalError", 0);
633 }
634
TEST_F(StructuredMetricsRecorderTest,EventWithoutMetricsReportCorrectly)635 TEST_F(StructuredMetricsRecorderTest, EventWithoutMetricsReportCorrectly) {
636 Init();
637
638 const int test_time = 50;
639
640 events::v2::cr_os_events::NoMetricsEvent test_event;
641 EXPECT_TRUE(test_event.IsEventSequenceType());
642 test_event.SetEventSequenceMetadata(Event::EventSequenceMetadata(1));
643 test_event.SetRecordedTimeSinceBoot(base::Milliseconds(test_time));
644 test_event.Record();
645
646 const auto data = GetEventMetrics();
647
648 EXPECT_EQ(GetUMAEventMetrics().events_size(), 0);
649 EXPECT_EQ(data.events_size(), 1);
650
651 const auto& event = data.events(0);
652
653 EXPECT_EQ(event.project_name_hash(), kCrOSEventsProjectHash);
654 EXPECT_EQ(event.event_name_hash(), kNoMetricsEventHash);
655 }
656
657 // Test that events reported before recording is enabled are ignored.
TEST_F(StructuredMetricsRecorderTest,EventsNotRecordedBeforeRecordingEnabled)658 TEST_F(StructuredMetricsRecorderTest, EventsNotRecordedBeforeRecordingEnabled) {
659 // Manually create and initialize the provider, adding recording calls between
660 // each step. All of these events should be ignored.
661 events::v2::test_project_one::TestEventOne().SetTestMetricTwo(1).Record();
662 InitWithoutEnabling();
663 events::v2::test_project_one::TestEventOne().SetTestMetricTwo(1).Record();
664 OnRecordingEnabled();
665 Wait();
666
667 EXPECT_EQ(GetUMAEventMetrics().events_size(), 0);
668 EXPECT_EQ(GetEventMetrics().events_size(), 0);
669
670 ExpectNoErrors();
671 }
672
673 // Test that events reported after recording is enabled but before the keys are
674 // loaded are hashed and stored after keys are loaded.
TEST_F(StructuredMetricsRecorderTest,EventsRecordedBeforeKeysInitialized)675 TEST_F(StructuredMetricsRecorderTest, EventsRecordedBeforeKeysInitialized) {
676 InitWithoutLogin();
677 // Emulate metric before login.
678 events::v2::test_project_one::TestEventOne().SetTestMetricTwo(1).Record();
679
680 OnProfileAdded(TempDirPath());
681
682 // Called before user key is loaded.
683 events::v2::test_project_one::TestEventOne().SetTestMetricTwo(1).Record();
684 Wait();
685
686 EXPECT_EQ(GetUMAEventMetrics().events_size(), 0);
687 EXPECT_EQ(GetEventMetrics().events_size(), 2);
688
689 ExpectNoErrors();
690 }
691
692 // Ensure a call to OnRecordingDisabled not only prevents the reporting of new
693 // events, but also clears the cache of any existing events that haven't yet
694 // been reported.
TEST_F(StructuredMetricsRecorderTest,ExistingEventsClearedWhenRecordingDisabled)695 TEST_F(StructuredMetricsRecorderTest,
696 ExistingEventsClearedWhenRecordingDisabled) {
697 Init();
698 events::v2::test_project_one::TestEventOne().SetTestMetricTwo(1).Record();
699 events::v2::test_project_one::TestEventOne().SetTestMetricTwo(1).Record();
700 events::v2::test_project_three::TestEventFour().SetTestMetricFour(1).Record();
701 OnRecordingDisabled();
702 events::v2::test_project_one::TestEventOne().SetTestMetricTwo(1).Record();
703 events::v2::test_project_three::TestEventFour().SetTestMetricFour(1).Record();
704 EXPECT_EQ(GetUMAEventMetrics().events_size(), 0);
705 EXPECT_EQ(GetEventMetrics().events_size(), 0);
706
707 ExpectNoErrors();
708 }
709
710 // Ensure that recording and reporting is re-enabled after recording is disabled
711 // and then enabled again.
TEST_F(StructuredMetricsRecorderTest,ReportingResumesWhenEnabled)712 TEST_F(StructuredMetricsRecorderTest, ReportingResumesWhenEnabled) {
713 Init();
714 events::v2::test_project_one::TestEventOne().SetTestMetricTwo(1).Record();
715 events::v2::test_project_one::TestEventOne().SetTestMetricTwo(1).Record();
716 events::v2::test_project_two::TestEventThree()
717 .SetTestMetricFour("test-string")
718 .Record();
719
720 OnRecordingDisabled();
721 OnRecordingEnabled();
722
723 events::v2::test_project_one::TestEventOne().SetTestMetricTwo(1).Record();
724 events::v2::test_project_one::TestEventOne().SetTestMetricTwo(1).Record();
725 events::v2::test_project_two::TestEventThree()
726 .SetTestMetricFour("test-string")
727 .Record();
728
729 EXPECT_EQ(GetUMAEventMetrics().events_size(), 0);
730 EXPECT_EQ(GetEventMetrics().events_size(), 6);
731
732 ExpectNoErrors();
733 }
734
735 // Ensure that a call to ProvideCurrentSessionData before initialization
736 // completes returns no events.
TEST_F(StructuredMetricsRecorderTest,ReportsNothingBeforeInitializationComplete)737 TEST_F(StructuredMetricsRecorderTest,
738 ReportsNothingBeforeInitializationComplete) {
739 InitWithoutEnabling();
740
741 EXPECT_EQ(GetUMAEventMetrics().events_size(), 0);
742 EXPECT_EQ(GetEventMetrics().events_size(), 0);
743 OnRecordingEnabled();
744 EXPECT_EQ(GetUMAEventMetrics().events_size(), 0);
745 EXPECT_EQ(GetEventMetrics().events_size(), 0);
746 OnProfileAdded(TempDirPath());
747 EXPECT_EQ(GetUMAEventMetrics().events_size(), 0);
748 EXPECT_EQ(GetEventMetrics().events_size(), 0);
749 }
750
TEST_F(StructuredMetricsRecorderTest,EventsClone)751 TEST_F(StructuredMetricsRecorderTest, EventsClone) {
752 Init();
753
754 events::v2::cr_os_events::Test1 event;
755
756 const int test_time = 50;
757 const double test_metric = 1.0;
758
759 event.SetEventSequenceMetadata(Event::EventSequenceMetadata(1));
760 event.SetRecordedTimeSinceBoot(base::Milliseconds(test_time));
761 event.SetMetric1(test_metric);
762
763 auto cloned_event = event.Clone();
764
765 EXPECT_EQ(event.event_sequence_metadata().reset_counter,
766 cloned_event.event_sequence_metadata().reset_counter);
767 EXPECT_EQ(event.project_name(), cloned_event.project_name());
768 EXPECT_EQ(event.event_name(), cloned_event.event_name());
769 EXPECT_EQ(event.is_event_sequence(), cloned_event.is_event_sequence());
770 EXPECT_EQ(event.recorded_time_since_boot(),
771 cloned_event.recorded_time_since_boot());
772 EXPECT_EQ(event.metric_values(), cloned_event.metric_values());
773 }
774
TEST_F(StructuredMetricsRecorderTest,DisallowedProjectAreDropped)775 TEST_F(StructuredMetricsRecorderTest, DisallowedProjectAreDropped) {
776 Init();
777
778 AddDisallowedProject(kProjectOneHash);
779
780 events::v2::test_project_one::TestEventOne().SetTestMetricTwo(1).Record();
781 events::v2::test_project_one::TestEventOne().SetTestMetricTwo(1).Record();
782 events::v2::test_project_two::TestEventThree()
783 .SetTestMetricFour("value")
784 .Record();
785
786 const auto data = GetEventMetrics();
787 ASSERT_EQ(data.events_size(), 1);
788 ASSERT_EQ(data.events(0).project_name_hash(), kProjectTwoHash);
789 }
790
791 class TestProcessor : public EventsProcessorInterface {
ShouldProcessOnEventRecord(const Event & event)792 bool ShouldProcessOnEventRecord(const Event& event) override { return true; }
793
794 // no-op
OnEventsRecord(Event * event)795 void OnEventsRecord(Event* event) override {}
OnEventRecorded(StructuredEventProto * event)796 void OnEventRecorded(StructuredEventProto* event) override {}
797
OnProvideIndependentMetrics(ChromeUserMetricsExtension * uma_proto)798 void OnProvideIndependentMetrics(
799 ChromeUserMetricsExtension* uma_proto) override {
800 uma_proto->mutable_structured_data()->set_is_device_enrolled(true);
801 }
802 };
803
TEST_F(StructuredMetricsRecorderTest,AppliesProcessorCorrectly)804 TEST_F(StructuredMetricsRecorderTest, AppliesProcessorCorrectly) {
805 Init();
806
807 // Processor that sets |is_device_enrolled| to true.
808 Recorder::GetInstance()->AddEventsProcessor(
809 std::make_unique<TestProcessor>());
810
811 events::v2::test_project_one::TestEventOne().SetTestMetricTwo(1).Record();
812 const auto data = GetEventMetrics();
813
814 EXPECT_TRUE(data.is_device_enrolled());
815 }
816
TEST_F(StructuredMetricsRecorderTest,ForceRecordedEvents)817 TEST_F(StructuredMetricsRecorderTest, ForceRecordedEvents) {
818 // Init and disable recorder.
819 Init();
820 OnRecordingDisabled();
821
822 events::v2::test_project_seven::TestEventEight().Record();
823
824 OnRecordingEnabled();
825 const auto data = GetEventMetrics();
826
827 ASSERT_EQ(data.events_size(), 1);
828 ASSERT_EQ(data.events(0).event_name_hash(), kEventEightHash);
829 }
830
TEST_F(StructuredMetricsRecorderTest,PurgeForceRecordedEvents)831 TEST_F(StructuredMetricsRecorderTest, PurgeForceRecordedEvents) {
832 // Init and disable recorder.
833 Init();
834 OnRecordingDisabled();
835
836 events::v2::test_project_seven::TestEventEight().Record();
837
838 OnReportingStateChanged(false);
839
840 OnRecordingEnabled();
841
842 const auto data = GetEventMetrics();
843
844 ASSERT_EQ(data.events_size(), 0);
845 }
846
847 } // namespace metrics::structured
848