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