| // 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/lib/util/clock.h" |
| #include "src/lib/util/encrypted_message_util.h" |
| #include "src/lib/util/testing/test_with_files.h" |
| #include "src/local_aggregation/testing/test_local_aggregation.h" |
| #include "src/logger/fake_logger.h" |
| #include "src/logger/internal_metrics.h" |
| #include "src/logger/internal_metrics_config.cb.h" |
| #include "src/logger/logger_interface.h" |
| #include "src/logger/logger_test_utils.h" |
| #include "src/logger/project_context.h" |
| #include "src/pb/metadata_builder.h" |
| #include "src/pb/observation.pb.h" |
| #include "src/system_data/client_secret.h" |
| #include "src/system_data/fake_system_data.h" |
| // Generated from all_reports_test_registry.yaml |
| // Namespace: cobalt::logger::testing::all_report_types |
| #include "src/logger/test_registries/all_report_types_test_registry.cb.h" |
| |
| namespace cobalt::logger { |
| |
| using local_aggregation::TestLocalAggregation; |
| 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)); |
| |
| // 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(); |
| |
| mock_system_clock_ = |
| std::make_unique<IncrementingSystemClock>(std::chrono::system_clock::duration(0)); |
| mock_system_clock_->set_time(kSystemTimePoint); |
| mock_steady_clock_ = new IncrementingSteadyClock(); |
| validated_clock_ = std::make_unique<util::FakeValidatedClock>(mock_system_clock_.get()); |
| validated_clock_->SetAccurate(true); |
| civil_time_converter_ = std::make_unique<util::UtcTimeConverter>(); |
| |
| observation_store_ = std::make_unique<FakeObservationStore>(); |
| update_recipient_ = std::make_unique<TestUpdateRecipient>(); |
| observation_encrypter_ = EncryptedMessageMaker::MakeUnencrypted().Unwrap(); |
| observation_writer_ = std::make_unique<ObservationWriter>( |
| *observation_store_, update_recipient_.get(), observation_encrypter_.get()); |
| metadata_builder_ = std::make_unique<MetadataBuilder>(system_data_, validated_clock_.get(), |
| system_data_cache_path(), fs()); |
| metadata_builder_->SnapshotSystemData(); |
| CobaltConfig cfg = {.client_secret = system_data::ClientSecret::GenerateNewSecret()}; |
| |
| cfg.local_aggregation_backfill_days = 0; |
| cfg.local_aggregate_store_dir = local_aggregation_store_path(); |
| |
| project_context_factory_ = |
| GetTestProjectContextFactory(testing::all_report_types::kCobaltRegistryBase64); |
| |
| local_aggregation_ = std::make_unique<TestLocalAggregation>( |
| cfg, *project_context_factory_, system_data_, *metadata_builder_, fs(), |
| *observation_writer_, *civil_time_converter_); |
| |
| internal_logger_ = std::make_unique<testing::FakeLogger>(); |
| internal_metrics_ = |
| logger::InternalMetrics::NewWithLogger(internal_logger_.get(), nullptr).Unwrap(); |
| |
| undated_event_manager_ = std::make_unique<UndatedEventManager>( |
| *local_aggregation_, *observation_writer_, system_data_, *civil_time_converter_, |
| kMaxSavedEvents); |
| undated_event_manager_->SetSteadyClock(mock_steady_clock_); |
| } |
| |
| void TearDown() override { undated_event_manager_.reset(); } |
| |
| std::unique_ptr<UndatedEventManager> undated_event_manager_; |
| std::unique_ptr<testing::FakeLogger> internal_logger_; |
| std::unique_ptr<logger::InternalMetrics> internal_metrics_; |
| std::unique_ptr<ObservationWriter> observation_writer_; |
| std::unique_ptr<TestUpdateRecipient> update_recipient_; |
| std::unique_ptr<IncrementingSystemClock> mock_system_clock_; |
| IncrementingSteadyClock* mock_steady_clock_; |
| std::unique_ptr<util::FakeValidatedClock> validated_clock_; |
| std::unique_ptr<TestLocalAggregation> local_aggregation_; |
| |
| private: |
| std::unique_ptr<FakeObservationStore> observation_store_; |
| std::unique_ptr<util::UtcTimeConverter> civil_time_converter_; |
| std::unique_ptr<MetadataBuilder> metadata_builder_; |
| std::unique_ptr<ProjectContextFactory> project_context_factory_; |
| std::unique_ptr<EncryptedMessageMaker> observation_encrypter_; |
| system_data::FakeSystemData 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 = EventRecord::MakeEventRecord( |
| project_context, testing::all_report_types::kNewReadCacheHitsMetricId) |
| .value(); |
| event_record->event()->mutable_occurrence_event()->add_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(StatusCode::OK, undated_event_manager_->Save(std::move(event_record)).error_code()); |
| |
| // Check that no immediate Observations were generated. |
| ASSERT_EQ(0, local_aggregation_->num_events_added()); |
| EXPECT_EQ(1, undated_event_manager_->NumSavedEvents()); |
| |
| // Flush the saved events, this will generate immediate observations. |
| ASSERT_EQ(StatusCode::OK, |
| undated_event_manager_->Flush(mock_system_clock_.get(), internal_metrics_.get()) |
| .error_code()); |
| EXPECT_EQ(0, undated_event_manager_->NumSavedEvents()); |
| |
| // Now there should be an observation. |
| ASSERT_EQ(1, local_aggregation_->num_events_added()); |
| // Monotonic clock was not advanced, so the day index is the current one. |
| ASSERT_EQ(kSystemTimePoint, local_aggregation_->last_timestamp()); |
| // One internal metric call for the number of cached events. |
| EXPECT_EQ(1, internal_logger_->call_count()); |
| EXPECT_EQ(internal_logger_->last_event_logged().metric_id(), |
| kInaccurateClockEventsCachedMigratedMetricId); |
| } |
| |
| 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 = EventRecord::MakeEventRecord( |
| project_context, testing::all_report_types::kNewReadCacheHitsMetricId) |
| .value(); |
| event_record->event()->mutable_occurrence_event()->add_event_code(42); |
| ASSERT_NE(nullptr, event_record->metric()); |
| ASSERT_EQ(StatusCode::OK, undated_event_manager_->Save(std::move(event_record)).error_code()); |
| |
| // Check that no events were added. |
| EXPECT_EQ(0, local_aggregation_->num_events_added()); |
| EXPECT_EQ(1, undated_event_manager_->NumSavedEvents()); |
| |
| // Flush the saved events, this will generate immediate observations. |
| ASSERT_EQ(StatusCode::OK, |
| undated_event_manager_->Flush(mock_system_clock_.get(), internal_metrics_.get()) |
| .error_code()); |
| EXPECT_EQ(0, undated_event_manager_->NumSavedEvents()); |
| |
| // Now there should be an observation. |
| ASSERT_EQ(1, local_aggregation_->num_events_added()); |
| // Monotonic clock was not advanced, so the day index is the current one. |
| ASSERT_EQ(kSystemTimePoint, local_aggregation_->last_timestamp()); |
| // One internal metric call for the number of cached events. |
| EXPECT_EQ(1, internal_logger_->call_count()); |
| EXPECT_EQ(internal_logger_->last_event_logged().metric_id(), |
| kInaccurateClockEventsCachedMigratedMetricId); |
| } |
| |
| 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 = EventRecord::MakeEventRecord( |
| project_context, testing::all_report_types::kNewReadCacheHitsMetricId) |
| .value(); |
| event_record->event()->mutable_occurrence_event()->add_event_code(42); |
| ASSERT_NE(nullptr, event_record->metric()); |
| ASSERT_EQ(StatusCode::OK, undated_event_manager_->Save(std::move(event_record)).error_code()); |
| |
| // Check that no immediate Observations were generated. |
| EXPECT_EQ(0, local_aggregation_->num_events_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(StatusCode::OK, |
| undated_event_manager_->Flush(mock_system_clock_.get(), internal_metrics_.get()) |
| .error_code()); |
| EXPECT_EQ(0, undated_event_manager_->NumSavedEvents()); |
| |
| // Now there should be an observation. |
| ASSERT_EQ(1, local_aggregation_->num_events_added()); |
| // Day index is from the previous day, due to the monotonic time advancing one day. |
| ASSERT_EQ(kSystemTimePoint - std::chrono::hours(24), local_aggregation_->last_timestamp()); |
| // One internal metric call for the number of cached events. |
| EXPECT_EQ(1, internal_logger_->call_count()); |
| EXPECT_EQ(internal_logger_->last_event_logged().metric_id(), |
| kInaccurateClockEventsCachedMigratedMetricId); |
| } |
| |
| 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 = |
| EventRecord::MakeEventRecord(project_context, testing::all_report_types::kDeviceBootsMetricId) |
| .value(); |
| event_record->event()->mutable_occurrence_event()->add_event_code(1); |
| ASSERT_NE(nullptr, event_record->metric()); |
| ASSERT_EQ(StatusCode::OK, undated_event_manager_->Save(std::move(event_record)).error_code()); |
| event_record = EventRecord::MakeEventRecord(project_context, |
| testing::all_report_types::kNewReadCacheHitsMetricId) |
| .value(); |
| event_record->event()->mutable_occurrence_event()->add_event_code(99); |
| ASSERT_NE(nullptr, event_record->metric()); |
| ASSERT_EQ(StatusCode::OK, undated_event_manager_->Save(std::move(event_record)).error_code()); |
| event_record = EventRecord::MakeEventRecord(project_context, |
| testing::all_report_types::kFeaturesActiveMetricId) |
| .value(); |
| event_record->event()->mutable_occurrence_event()->add_event_code(98); |
| ASSERT_NE(nullptr, event_record->metric()); |
| ASSERT_EQ(StatusCode::OK, undated_event_manager_->Save(std::move(event_record)).error_code()); |
| event_record = EventRecord::MakeEventRecord(project_context, |
| testing::all_report_types::kNewReadCacheHitsMetricId) |
| .value(); |
| event_record->event()->mutable_occurrence_event()->add_event_code(97); |
| ASSERT_NE(nullptr, event_record->metric()); |
| ASSERT_EQ(StatusCode::OK, undated_event_manager_->Save(std::move(event_record)).error_code()); |
| |
| // 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 = EventRecord::MakeEventRecord( |
| project_context, testing::all_report_types::kNewReadCacheHitsMetricId) |
| .value(); |
| event_record->event()->mutable_occurrence_event()->add_event_code(i); |
| ASSERT_NE(nullptr, event_record->metric()); |
| ASSERT_EQ(StatusCode::OK, undated_event_manager_->Save(std::move(event_record)).error_code()); |
| } |
| |
| // Check that no immediate Observations were generated and only the max were saved. |
| ASSERT_EQ(0, local_aggregation_->num_events_added()); |
| EXPECT_EQ(kMaxSavedEvents, undated_event_manager_->NumSavedEvents()); |
| |
| // Flush the saved events, this will generate immediate observations. |
| ASSERT_EQ(StatusCode::OK, |
| undated_event_manager_->Flush(mock_system_clock_.get(), internal_metrics_.get()) |
| .error_code()); |
| EXPECT_EQ(0, undated_event_manager_->NumSavedEvents()); |
| |
| // Now there should be the maximum cache size of observation. |
| ASSERT_EQ(kMaxSavedEvents, local_aggregation_->num_events_added()); |
| |
| // Day index starts from 9 days ago and ends at today. |
| std::chrono::system_clock::time_point start_time = |
| kSystemTimePoint - std::chrono::hours(24 * (kMaxSavedEvents - 1)); |
| for (int i = 0; i < kMaxSavedEvents; i++) { |
| EXPECT_EQ(testing::all_report_types::kNewReadCacheHitsMetricId, |
| local_aggregation_->metric_id(i)); |
| EXPECT_EQ(start_time + std::chrono::hours(24 * i), local_aggregation_->timestamp(i)); |
| } |
| // Two internal metric calls for the number of cached and dropped events. |
| EXPECT_EQ(2, internal_logger_->call_count()); |
| EXPECT_EQ(internal_logger_->last_event_logged().metric_id(), |
| kInaccurateClockEventsDroppedMigratedMetricId); |
| } |
| |
| 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(StatusCode::OK, |
| undated_event_manager_->Flush(mock_system_clock_.get(), internal_metrics_.get()) |
| .error_code()); |
| EXPECT_EQ(0, local_aggregation_->num_events_added()); |
| EXPECT_EQ(0, undated_event_manager_->NumSavedEvents()); |
| |
| // Create an event and try to save it. |
| auto event_record = EventRecord::MakeEventRecord( |
| project_context, testing::all_report_types::kNewReadCacheHitsMetricId) |
| .value(); |
| event_record->event()->mutable_occurrence_event()->add_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(StatusCode::OK, undated_event_manager_->Save(std::move(event_record)).error_code()); |
| EXPECT_EQ(0, undated_event_manager_->NumSavedEvents()); |
| // Now there should be an observation. |
| ASSERT_EQ(1, local_aggregation_->num_events_added()); |
| // Day index is from the previous day, due to the monotonic time advancing one day. |
| ASSERT_EQ(kSystemTimePoint + std::chrono::hours(24), local_aggregation_->last_timestamp()); |
| // No internal metric calls for the number of cached events. |
| EXPECT_EQ(0, internal_logger_->call_count()); |
| } |
| |
| } // namespace cobalt::logger |