| // Copyright 2018 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "src/logger/undated_event_manager.h" |
| |
| #include <memory> |
| #include <string> |
| |
| #include <google/protobuf/text_format.h> |
| #include <google/protobuf/util/message_differencer.h> |
| |
| #include "gtest/gtest.h" |
| #include "src/algorithms/rappor/rappor_encoder.h" |
| #include "src/lib/util/clock.h" |
| #include "src/lib/util/datetime_util.h" |
| #include "src/lib/util/encrypted_message_util.h" |
| #include "src/lib/util/testing/test_with_files.h" |
| #include "src/local_aggregation/event_aggregator_mgr.h" |
| #include "src/local_aggregation_1_1/local_aggregation.h" |
| #include "src/logger/encoder.h" |
| #include "src/logger/fake_logger.h" |
| #include "src/logger/internal_metrics_config.cb.h" |
| #include "src/logger/logger_test_utils.h" |
| #include "src/logger/project_context.h" |
| #include "src/logger/status.h" |
| #include "src/logger/testing_constants.h" |
| #include "src/pb/metadata_builder.h" |
| #include "src/pb/observation.pb.h" |
| #include "src/registry/packed_event_codes.h" |
| #include "src/system_data/client_secret.h" |
| |
| namespace cobalt::logger { |
| |
| using local_aggregation::EventAggregatorManager; |
| using local_aggregation::LocalAggregation; |
| using system_data::ClientSecret; |
| using system_data::SystemDataInterface; |
| using testing::FakeObservationStore; |
| using testing::GetTestProject; |
| using testing::GetTestProjectContextFactory; |
| using testing::TestUpdateRecipient; |
| using util::EncryptedMessageMaker; |
| using util::IncrementingSteadyClock; |
| using util::IncrementingSystemClock; |
| |
| namespace { |
| std::tm kSystemTime = { |
| .tm_sec = 30, |
| .tm_min = 20, |
| .tm_hour = 10, |
| .tm_mday = 2, // 2nd |
| .tm_mon = 7, // August |
| .tm_year = 119, // 2019 |
| }; |
| const auto kSystemTimePoint = std::chrono::system_clock::from_time_t(std::mktime(&kSystemTime)); |
| constexpr int kDayIndex = 18110; // 2019-08-02 |
| |
| // Use a smaller max saved size for testing. |
| constexpr size_t kMaxSavedEvents = 10; |
| |
| } // namespace |
| |
| class UndatedEventManagerTest : public util::testing::TestWithFiles { |
| protected: |
| void SetUp() override { |
| MakeTestFolder(); |
| observation_store_ = std::make_unique<FakeObservationStore>(); |
| update_recipient_ = std::make_unique<TestUpdateRecipient>(); |
| observation_encrypter_ = EncryptedMessageMaker::MakeUnencrypted(); |
| observation_writer_ = std::make_unique<ObservationWriter>( |
| observation_store_.get(), update_recipient_.get(), observation_encrypter_.get()); |
| metadata_builder_ = |
| std::make_unique<MetadataBuilder>(system_data_.get(), system_data_cache_path(), fs()); |
| encoder_ = |
| std::make_unique<Encoder>(ClientSecret::GenerateNewSecret(), metadata_builder_.get()); |
| CobaltConfig cfg = {.client_secret = system_data::ClientSecret::GenerateNewSecret()}; |
| |
| cfg.local_aggregation_backfill_days = 0; |
| cfg.local_aggregate_proto_store_path = aggregate_store_path(); |
| cfg.obs_history_proto_store_path = obs_history_path(); |
| |
| project_context_factory_ = |
| GetTestProjectContextFactory(testing::all_report_types::kCobaltRegistryBase64); |
| |
| event_aggregator_mgr_ = std::make_unique<EventAggregatorManager>(cfg, fs(), encoder_.get(), |
| observation_writer_.get()); |
| local_aggregation_ = std::make_unique<LocalAggregation>(cfg, project_context_factory_.get(), |
| metadata_builder_.get(), fs(), |
| observation_writer_.get()); |
| |
| internal_logger_ = std::make_unique<testing::FakeLogger>(); |
| |
| mock_system_clock_ = |
| std::make_unique<IncrementingSystemClock>(std::chrono::system_clock::duration(0)); |
| mock_system_clock_->set_time(kSystemTimePoint); |
| mock_steady_clock_ = new IncrementingSteadyClock(); |
| |
| undated_event_manager_ = std::make_unique<UndatedEventManager>( |
| encoder_.get(), event_aggregator_mgr_->GetEventAggregator(), local_aggregation_.get(), |
| observation_writer_.get(), system_data_.get(), kMaxSavedEvents); |
| undated_event_manager_->SetSteadyClock(mock_steady_clock_); |
| } |
| |
| void TearDown() override { |
| event_aggregator_mgr_.reset(); |
| undated_event_manager_.reset(); |
| } |
| |
| std::unique_ptr<UndatedEventManager> undated_event_manager_; |
| std::unique_ptr<testing::FakeLogger> internal_logger_; |
| std::unique_ptr<ObservationWriter> observation_writer_; |
| std::unique_ptr<FakeObservationStore> observation_store_; |
| std::unique_ptr<TestUpdateRecipient> update_recipient_; |
| std::unique_ptr<IncrementingSystemClock> mock_system_clock_; |
| IncrementingSteadyClock* mock_steady_clock_; |
| |
| private: |
| std::unique_ptr<MetadataBuilder> metadata_builder_; |
| std::unique_ptr<Encoder> encoder_; |
| std::unique_ptr<ProjectContextFactory> project_context_factory_; |
| std::unique_ptr<EventAggregatorManager> event_aggregator_mgr_; |
| std::unique_ptr<LocalAggregation> local_aggregation_; |
| std::unique_ptr<EncryptedMessageMaker> observation_encrypter_; |
| std::unique_ptr<SystemDataInterface> system_data_; |
| }; |
| |
| // Test that the UndatedEventManager can outlive the source of the ProjectContext. |
| TEST_F(UndatedEventManagerTest, DeletedProjectContext) { |
| std::shared_ptr<ProjectContext> project_context = |
| GetTestProject(testing::all_report_types::kCobaltRegistryBase64); |
| |
| // Create an event and save it. |
| auto event_record = std::make_unique<EventRecord>( |
| project_context, testing::all_report_types::kErrorOccurredMetricId); |
| event_record->event()->mutable_event_occurred_event()->set_event_code(42); |
| ASSERT_NE(nullptr, event_record->metric()); |
| |
| // Drop the ProjectContext. If no one else has retained a copy, it will be deleted. |
| project_context.reset(); |
| |
| ASSERT_EQ(kOK, undated_event_manager_->Save(std::move(event_record))); |
| |
| // Check that no immediate Observations were generated. |
| EXPECT_EQ(0, observation_store_->num_observations_added()); |
| EXPECT_EQ(1, undated_event_manager_->NumSavedEvents()); |
| |
| // Flush the saved events, this will generate immediate observations. |
| ASSERT_EQ(kOK, undated_event_manager_->Flush(mock_system_clock_.get(), internal_logger_.get())); |
| EXPECT_EQ(0, undated_event_manager_->NumSavedEvents()); |
| |
| // Now there should be an observation. |
| EXPECT_EQ(1, observation_store_->num_observations_added()); |
| // Monotonic clock was not advanced, so the day index is the current one. |
| EXPECT_EQ(kDayIndex, observation_store_->metadata_received[0]->day_index()); |
| // One internal metric call for the number of cached events. |
| EXPECT_EQ(1, internal_logger_->call_count()); |
| EXPECT_EQ(kInaccurateClockEventsCachedMetricId, |
| internal_logger_->last_event_logged().metric_id()); |
| } |
| |
| TEST_F(UndatedEventManagerTest, OneImmediatelyFlushedEvent) { |
| std::shared_ptr<ProjectContext> project_context = |
| GetTestProject(testing::all_report_types::kCobaltRegistryBase64); |
| |
| // Create an event and save it. |
| auto event_record = std::make_unique<EventRecord>( |
| project_context, testing::all_report_types::kErrorOccurredMetricId); |
| event_record->event()->mutable_event_occurred_event()->set_event_code(42); |
| ASSERT_NE(nullptr, event_record->metric()); |
| ASSERT_EQ(kOK, undated_event_manager_->Save(std::move(event_record))); |
| |
| // Check that no immediate Observations were generated. |
| EXPECT_EQ(0, observation_store_->num_observations_added()); |
| EXPECT_EQ(1, undated_event_manager_->NumSavedEvents()); |
| |
| // Flush the saved events, this will generate immediate observations. |
| ASSERT_EQ(kOK, undated_event_manager_->Flush(mock_system_clock_.get(), internal_logger_.get())); |
| EXPECT_EQ(0, undated_event_manager_->NumSavedEvents()); |
| |
| // Now there should be an observation. |
| EXPECT_EQ(1, observation_store_->num_observations_added()); |
| // Monotonic clock was not advanced, so the day index is the current one. |
| EXPECT_EQ(kDayIndex, observation_store_->metadata_received[0]->day_index()); |
| // One internal metric call for the number of cached events. |
| EXPECT_EQ(1, internal_logger_->call_count()); |
| EXPECT_EQ(kInaccurateClockEventsCachedMetricId, |
| internal_logger_->last_event_logged().metric_id()); |
| } |
| |
| TEST_F(UndatedEventManagerTest, OneDayDelayInSystemClockAccuracy) { |
| std::shared_ptr<ProjectContext> project_context = |
| GetTestProject(testing::all_report_types::kCobaltRegistryBase64); |
| |
| // Create an event and save it. |
| auto event_record = std::make_unique<EventRecord>( |
| project_context, testing::all_report_types::kErrorOccurredMetricId); |
| event_record->event()->mutable_event_occurred_event()->set_event_code(42); |
| ASSERT_NE(nullptr, event_record->metric()); |
| ASSERT_EQ(kOK, undated_event_manager_->Save(std::move(event_record))); |
| |
| // Check that no immediate Observations were generated. |
| EXPECT_EQ(0, observation_store_->num_observations_added()); |
| EXPECT_EQ(1, undated_event_manager_->NumSavedEvents()); |
| |
| // One day later, the clock is finally accurate, flush the saved events, this will generate |
| // observations. |
| mock_steady_clock_->increment_by(std::chrono::hours(24)); |
| ASSERT_EQ(kOK, undated_event_manager_->Flush(mock_system_clock_.get(), internal_logger_.get())); |
| EXPECT_EQ(0, undated_event_manager_->NumSavedEvents()); |
| |
| // Now there should be an observation. |
| EXPECT_EQ(1, observation_store_->num_observations_added()); |
| // Day index is from the previous day, due to the monotonic time advancing one day. |
| EXPECT_EQ(kDayIndex - 1, observation_store_->metadata_received[0]->day_index()); |
| // One internal metric call for the number of cached events. |
| EXPECT_EQ(1, internal_logger_->call_count()); |
| EXPECT_EQ(kInaccurateClockEventsCachedMetricId, |
| internal_logger_->last_event_logged().metric_id()); |
| } |
| |
| TEST_F(UndatedEventManagerTest, TooManyEventsToStore) { |
| std::shared_ptr<ProjectContext> project_context = |
| GetTestProject(testing::all_report_types::kCobaltRegistryBase64); |
| |
| // Create some events and save them. These will be dropped |
| auto event_record = std::make_unique<EventRecord>( |
| project_context, testing::all_report_types::kDeviceBootsMetricId); |
| event_record->event()->mutable_event_occurred_event()->set_event_code(1); |
| ASSERT_NE(nullptr, event_record->metric()); |
| ASSERT_EQ(kOK, undated_event_manager_->Save(std::move(event_record))); |
| event_record = std::make_unique<EventRecord>(project_context, |
| testing::all_report_types::kErrorOccurredMetricId); |
| event_record->event()->mutable_event_occurred_event()->set_event_code(99); |
| ASSERT_NE(nullptr, event_record->metric()); |
| ASSERT_EQ(kOK, undated_event_manager_->Save(std::move(event_record))); |
| event_record = std::make_unique<EventRecord>(project_context, |
| testing::all_report_types::kFeaturesActiveMetricId); |
| event_record->event()->mutable_event_occurred_event()->set_event_code(98); |
| ASSERT_NE(nullptr, event_record->metric()); |
| ASSERT_EQ(kOK, undated_event_manager_->Save(std::move(event_record))); |
| event_record = std::make_unique<EventRecord>(project_context, |
| testing::all_report_types::kErrorOccurredMetricId); |
| event_record->event()->mutable_event_occurred_event()->set_event_code(97); |
| ASSERT_NE(nullptr, event_record->metric()); |
| ASSERT_EQ(kOK, undated_event_manager_->Save(std::move(event_record))); |
| |
| // Save events beyond the max size of the cache. Only 10 events should fit in memory. |
| // Each event is on a different day. |
| for (int i = kMaxSavedEvents; i > 0; i--) { |
| mock_steady_clock_->increment_by(std::chrono::hours(24)); |
| event_record = std::make_unique<EventRecord>(project_context, |
| testing::all_report_types::kErrorOccurredMetricId); |
| event_record->event()->mutable_event_occurred_event()->set_event_code(i); |
| ASSERT_NE(nullptr, event_record->metric()); |
| ASSERT_EQ(kOK, undated_event_manager_->Save(std::move(event_record))); |
| } |
| |
| // Check that no immediate Observations were generated and only the max were saved. |
| ASSERT_EQ(0, observation_store_->num_observations_added()); |
| EXPECT_EQ(kMaxSavedEvents, undated_event_manager_->NumSavedEvents()); |
| |
| // Flush the saved events, this will generate immediate observations. |
| ASSERT_EQ(kOK, undated_event_manager_->Flush(mock_system_clock_.get(), internal_logger_.get())); |
| EXPECT_EQ(0, undated_event_manager_->NumSavedEvents()); |
| |
| // Now there should be the maximum cache size of observation. |
| EXPECT_EQ(kMaxSavedEvents, observation_store_->num_observations_added()); |
| |
| for (int i = 0; i < kMaxSavedEvents; i++) { |
| EXPECT_EQ(testing::all_report_types::kErrorOccurredMetricId, |
| observation_store_->metadata_received[i]->metric_id()); |
| // Day index starts from 9 days ago and ends at today. |
| EXPECT_EQ(kDayIndex - (kMaxSavedEvents - 1) + i, |
| observation_store_->metadata_received[i]->day_index()); |
| } |
| // Two internal metric calls for the number of cached and dropped events. |
| EXPECT_EQ(2, internal_logger_->call_count()); |
| EXPECT_EQ(kInaccurateClockEventsDroppedMetricId, |
| internal_logger_->last_event_logged().metric_id()); |
| } |
| |
| TEST_F(UndatedEventManagerTest, SaveAfterFlush) { |
| std::shared_ptr<ProjectContext> project_context = |
| GetTestProject(testing::all_report_types::kCobaltRegistryBase64); |
| |
| // Flush the empty manager, this will not generate any observations. |
| ASSERT_EQ(kOK, undated_event_manager_->Flush(mock_system_clock_.get(), internal_logger_.get())); |
| EXPECT_EQ(0, observation_store_->num_observations_added()); |
| EXPECT_EQ(0, undated_event_manager_->NumSavedEvents()); |
| |
| // Create an event and try to save it. |
| auto event_record = std::make_unique<EventRecord>( |
| project_context, testing::all_report_types::kErrorOccurredMetricId); |
| event_record->event()->mutable_event_occurred_event()->set_event_code(42); |
| ASSERT_NE(nullptr, event_record->metric()); |
| |
| // One day later, the clock is finally accurate, try to save an event, this will generate |
| // observations. (This scenario is not very likely, as the clock should report accurate around the |
| // same time as the flush, but this allows us to verify the steady clock time being after the |
| // flush time results in a correct date setting.) |
| mock_steady_clock_->increment_by(std::chrono::hours(24)); |
| ASSERT_EQ(kOK, undated_event_manager_->Save(std::move(event_record))); |
| EXPECT_EQ(0, undated_event_manager_->NumSavedEvents()); |
| // Now there should be an observation. |
| EXPECT_EQ(1, observation_store_->num_observations_added()); |
| // Day index is from the previous day, due to the monotonic time advancing one day. |
| EXPECT_EQ(kDayIndex + 1, observation_store_->metadata_received[0]->day_index()); |
| // No internal metric calls for the number of cached events. |
| EXPECT_EQ(0, internal_logger_->call_count()); |
| } |
| |
| } // namespace cobalt::logger |