| // 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/event_loggers.h" |
| |
| #include <chrono> |
| #include <memory> |
| #include <string> |
| |
| #include <google/protobuf/repeated_field.h> |
| #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/datetime_util.h" |
| #include "src/lib/util/encrypted_message_util.h" |
| #include "src/lib/util/testing/test_with_files.h" |
| #include "src/local_aggregation/local_aggregate_storage/immediate_local_aggregate_storage.h" |
| #include "src/local_aggregation/local_aggregate_storage/local_aggregate_storage.h" |
| #include "src/local_aggregation/local_aggregation.h" |
| #include "src/local_aggregation/local_aggregation.pb.h" |
| #include "src/logger/logger_test_utils.h" |
| #include "src/logger/project_context.h" |
| #include "src/logger/test_registries/test_registry.cb.h" |
| #include "src/pb/observation.pb.h" |
| #include "src/public/lib/status.h" |
| #include "src/system_data/client_secret.h" |
| #include "src/system_data/fake_system_data.h" |
| |
| namespace cobalt::logger { |
| |
| using internal::EventLogger; |
| using local_aggregation::LocalAggregation; |
| using testing::ExpectedReportParticipationObservations; |
| using testing::FakeObservationStore; |
| using testing::GetTestProject; |
| using testing::GetTestProjectContextFactory; |
| using testing::TestUpdateRecipient; |
| using util::EncryptedMessageMaker; |
| using util::IncrementingSystemClock; |
| |
| namespace { |
| // Number of seconds in a day |
| constexpr int kDay = 60 * 60 * 24; |
| // Number of seconds in an ideal year |
| constexpr int kYear = kDay * 365; |
| |
| } // namespace |
| |
| // needed for Friend class status |
| namespace internal { |
| |
| class EventLoggersLocalAggregationTest : public util::testing::TestWithFiles { |
| public: |
| void SetUp() override { |
| MakeTestFolder(); |
| 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()); |
| project_context_factory_ = |
| GetTestProjectContextFactory(testing::cobalt_new::kCobaltRegistryBase64); |
| project_context_ = GetTestProject(testing::cobalt_new::kCobaltRegistryBase64); |
| // Create a mock clock which does not increment by default when called. |
| // Set the time to 1 year after the start of Unix time so that the start |
| // date of any aggregation window falls after the start of time. |
| mock_clock_ = std::make_unique<IncrementingSystemClock>(std::chrono::system_clock::duration(0)); |
| start_time_ = std::chrono::system_clock::time_point(std::chrono::seconds(kYear)); |
| mock_clock_->set_time(start_time_); |
| // Set the test local time converter to switch time zones 1 hour after `start_time_`. |
| // This should only affect metrics with the OTHER_TIME_ZONE TimeZonePolicy. |
| civil_time_converter_ = std::make_unique<util::FakeCivilTimeConverter>( |
| /*start_utc_offset=*/0, /*start_isdst=*/false, /*end_utc_offset=*/1, /*end_isdst=*/true, |
| /*threshold=*/start_time_ + util::kOneHour); |
| |
| CobaltConfig cfg = {.client_secret = system_data::ClientSecret::GenerateNewSecret()}; |
| |
| cfg.local_aggregation_backfill_days = 0; |
| cfg.local_aggregate_store_dir = local_aggregation_store_path(); |
| |
| local_aggregation_ = |
| std::make_unique<LocalAggregation>(cfg, *project_context_factory_, system_data_, fs(), |
| *observation_writer_, *civil_time_converter_); |
| } |
| |
| local_aggregation::ImmediateLocalAggregateStorage GetLocalAggregateStorage() { |
| return local_aggregation::ImmediateLocalAggregateStorage( |
| local_aggregation_store_path(), fs(), *project_context_factory_, system_data_, 0); |
| } |
| |
| local_aggregation::ReportAggregate GetReportAggregate( |
| uint32_t metric_id, MetricDefinition::MetricType metric_type, uint32_t report_id, |
| local_aggregation::LocalAggregateStorage* store) { |
| std::unique_ptr<EventRecord> event_record = |
| GetEventRecordWithMetricType(metric_id, metric_type); |
| local_aggregation::LocalAggregateStorage::MetricAggregateRef aggregate = |
| store->GetMetricAggregate(event_record->MetricIdentifier()).value(); |
| return aggregate.aggregate()->by_report_id().at(report_id); |
| } |
| |
| std::unique_ptr<EventLogger> GetEventLoggerForMetricType( |
| MetricDefinition::MetricType metric_type) { |
| auto logger = EventLogger::Create(metric_type, *local_aggregation_, *observation_writer_, |
| system_data_, *civil_time_converter_); |
| return logger; |
| } |
| |
| std::unique_ptr<EventRecord> GetEventRecordWithMetricType( |
| uint32_t metric_id, MetricDefinition::MetricType metric_type) { |
| auto event_record = EventRecord::MakeEventRecord(project_context_, metric_id).value(); |
| |
| switch (metric_type) { |
| case (MetricDefinition::OCCURRENCE): { |
| OccurrenceEvent* event = event_record->event()->mutable_occurrence_event(); |
| event->set_count(1); |
| break; |
| } |
| case (MetricDefinition::INTEGER): { |
| IntegerEvent* event = event_record->event()->mutable_integer_event(); |
| event->set_value(10); |
| break; |
| } |
| case (MetricDefinition::INTEGER_HISTOGRAM): { |
| IntegerHistogramEvent* event = event_record->event()->mutable_integer_histogram_event(); |
| event->mutable_buckets()->Swap( |
| testing::NewHistogram({0, 1, 2, 3}, {100, 101, 102, 103}).get()); |
| break; |
| } |
| case (MetricDefinition::STRING): { |
| StringEvent* event = event_record->event()->mutable_string_event(); |
| event->set_string_value("component4"); |
| break; |
| } |
| default: { |
| } |
| } |
| |
| return event_record; |
| } |
| |
| static ReportDefinition ReportDefinitionWithReportType(uint32_t report_id, |
| ReportDefinition::ReportType report_type) { |
| ReportDefinition report_definition; |
| report_definition.set_report_type(report_type); |
| report_definition.set_id(report_id); |
| return report_definition; |
| } |
| |
| protected: |
| std::unique_ptr<ObservationWriter> observation_writer_; |
| std::unique_ptr<FakeObservationStore> observation_store_; |
| std::unique_ptr<TestUpdateRecipient> update_recipient_; |
| std::unique_ptr<EncryptedMessageMaker> observation_encrypter_; |
| system_data::FakeSystemData system_data_; |
| std::unique_ptr<IncrementingSystemClock> mock_clock_; |
| std::chrono::system_clock::time_point start_time_; |
| std::unique_ptr<util::FakeCivilTimeConverter> civil_time_converter_; |
| std::unique_ptr<ProjectContextFactory> project_context_factory_; |
| std::shared_ptr<ProjectContext> project_context_; |
| std::unique_ptr<LocalAggregation> local_aggregation_; |
| }; |
| |
| TEST_F(EventLoggersLocalAggregationTest, OccurrenceUniqueDeviceHistogramsValidation) { |
| auto event_record = GetEventRecordWithMetricType(testing::cobalt_new::kSettingsChangedMetricId, |
| MetricDefinition::OCCURRENCE); |
| auto event_logger = GetEventLoggerForMetricType(MetricDefinition::OCCURRENCE); |
| EXPECT_EQ(StatusCode::OK, |
| event_logger |
| ->PrepareAndValidateEvent(testing::cobalt_new::kSettingsChangedMetricId, |
| MetricDefinition::OCCURRENCE, event_record.get()) |
| .error_code()); |
| } |
| |
| TEST_F(EventLoggersLocalAggregationTest, OccurrenceUniqueDeviceHistogramsValidationWrongType) { |
| auto event_record = GetEventRecordWithMetricType(testing::cobalt_new::kSettingsChangedMetricId, |
| MetricDefinition::INTEGER); |
| auto event_logger = GetEventLoggerForMetricType(MetricDefinition::OCCURRENCE); |
| EXPECT_EQ(StatusCode::INVALID_ARGUMENT, |
| event_logger |
| ->PrepareAndValidateEvent(testing::cobalt_new::kSettingsChangedMetricId, |
| MetricDefinition::OCCURRENCE, event_record.get()) |
| .error_code()); |
| } |
| |
| TEST_F(EventLoggersLocalAggregationTest, OccurrenceUniqueDeviceHistograms) { |
| ReportDefinition report = ReportDefinitionWithReportType( |
| testing::cobalt_new::kSettingsChangedSettingsChangedUniqueDeviceHistogramsReportId, |
| ReportDefinition::UNIQUE_DEVICE_HISTOGRAMS); |
| auto event_record = GetEventRecordWithMetricType(testing::cobalt_new::kSettingsChangedMetricId, |
| MetricDefinition::OCCURRENCE); |
| auto event_logger = GetEventLoggerForMetricType(MetricDefinition::OCCURRENCE); |
| EXPECT_EQ( |
| StatusCode::OK, |
| event_logger->Log(std::move(event_record), std::chrono::system_clock::now()).error_code()); |
| |
| event_record = GetEventRecordWithMetricType(testing::cobalt_new::kSettingsChangedMetricId, |
| MetricDefinition::OCCURRENCE); |
| local_aggregation::ImmediateLocalAggregateStorage store = GetLocalAggregateStorage(); |
| local_aggregation::ReportAggregate report_aggregate = GetReportAggregate( |
| testing::cobalt_new::kSettingsChangedMetricId, MetricDefinition::OCCURRENCE, |
| testing::cobalt_new::kSettingsChangedSettingsChangedUniqueDeviceHistogramsReportId, &store); |
| ASSERT_TRUE(report_aggregate.has_daily()); |
| local_aggregation::AggregationPeriodBucket daily_bucket = |
| report_aggregate.daily().by_day_index().begin()->second; |
| local_aggregation::AggregateData data = |
| daily_bucket.system_profile_aggregates(0).by_event_code().at(0).data(); |
| EXPECT_EQ(data.count(), 1); |
| } |
| |
| TEST_F(EventLoggersLocalAggregationTest, IntegerFleetwideMeansValidation) { |
| auto event_record = GetEventRecordWithMetricType(testing::cobalt_new::kStreamingTimeMetricId, |
| MetricDefinition::INTEGER); |
| auto event_logger = GetEventLoggerForMetricType(MetricDefinition::INTEGER); |
| EXPECT_EQ(StatusCode::OK, |
| event_logger |
| ->PrepareAndValidateEvent(testing::cobalt_new::kStreamingTimeMetricId, |
| MetricDefinition::INTEGER, event_record.get()) |
| .error_code()); |
| } |
| |
| TEST_F(EventLoggersLocalAggregationTest, IntegerFleetwideMeansValidationWrongType) { |
| auto event_record = GetEventRecordWithMetricType(testing::cobalt_new::kStreamingTimeMetricId, |
| MetricDefinition::OCCURRENCE); |
| auto event_logger = GetEventLoggerForMetricType(MetricDefinition::INTEGER); |
| EXPECT_EQ(StatusCode::INVALID_ARGUMENT, |
| event_logger |
| ->PrepareAndValidateEvent(testing::cobalt_new::kStreamingTimeMetricId, |
| MetricDefinition::INTEGER, event_record.get()) |
| .error_code()); |
| } |
| |
| TEST_F(EventLoggersLocalAggregationTest, IntegerFleetwideMeans) { |
| ReportDefinition report = ReportDefinitionWithReportType( |
| testing::cobalt_new::kStreamingTimeStreamingTimeAggregationReportId, |
| ReportDefinition::FLEETWIDE_MEANS); |
| auto event_record = GetEventRecordWithMetricType(testing::cobalt_new::kStreamingTimeMetricId, |
| MetricDefinition::INTEGER); |
| |
| auto event_logger = GetEventLoggerForMetricType(MetricDefinition::INTEGER); |
| EXPECT_EQ( |
| StatusCode::OK, |
| event_logger->Log(std::move(event_record), std::chrono::system_clock::now()).error_code()); |
| |
| event_record = GetEventRecordWithMetricType(testing::cobalt_new::kStreamingTimeMetricId, |
| MetricDefinition::INTEGER); |
| local_aggregation::ImmediateLocalAggregateStorage store = GetLocalAggregateStorage(); |
| local_aggregation::ReportAggregate report_aggregate = GetReportAggregate( |
| testing::cobalt_new::kStreamingTimeMetricId, MetricDefinition::INTEGER, |
| testing::cobalt_new::kStreamingTimeStreamingTimeAggregationReportId, &store); |
| ASSERT_TRUE(report_aggregate.has_hourly()); |
| local_aggregation::AggregationPeriodBucket hourly_bucket = |
| report_aggregate.hourly().by_hour_id().begin()->second; |
| local_aggregation::AggregateData data = |
| hourly_bucket.system_profile_aggregates(0).by_event_code().at(0).data(); |
| ASSERT_TRUE(data.has_sum_and_count()); |
| EXPECT_EQ(data.sum_and_count().sum(), 10); |
| } |
| |
| TEST_F(EventLoggersLocalAggregationTest, IntegerHistogramFleetwideHistogramsValidation) { |
| auto event_record = GetEventRecordWithMetricType( |
| testing::cobalt_new::kFileSystemWriteTimesMetricId, MetricDefinition::INTEGER_HISTOGRAM); |
| auto event_logger = GetEventLoggerForMetricType(MetricDefinition::INTEGER_HISTOGRAM); |
| EXPECT_EQ(StatusCode::OK, |
| event_logger |
| ->PrepareAndValidateEvent(testing::cobalt_new::kFileSystemWriteTimesMetricId, |
| MetricDefinition::INTEGER_HISTOGRAM, event_record.get()) |
| .error_code()); |
| } |
| |
| TEST_F(EventLoggersLocalAggregationTest, IntegerHistogramFleetwideHistogramsValidationWrongType) { |
| auto event_record = GetEventRecordWithMetricType( |
| testing::cobalt_new::kFileSystemWriteTimesMetricId, MetricDefinition::INTEGER); |
| auto event_logger = GetEventLoggerForMetricType(MetricDefinition::INTEGER_HISTOGRAM); |
| EXPECT_EQ(StatusCode::INVALID_ARGUMENT, |
| event_logger |
| ->PrepareAndValidateEvent(testing::cobalt_new::kFileSystemWriteTimesMetricId, |
| MetricDefinition::INTEGER_HISTOGRAM, event_record.get()) |
| .error_code()); |
| } |
| |
| TEST_F(EventLoggersLocalAggregationTest, |
| IntegerHistogramFleetwideHistogramsValidationWrongBuckets) { |
| auto event_record = GetEventRecordWithMetricType( |
| testing::cobalt_new::kFileSystemWriteTimesMetricId, MetricDefinition::INTEGER_HISTOGRAM); |
| // Replace with a histogram containing an invalid bucket index. The metric FileSystemWriteTimes |
| // has a num_buckets = 10, with 0 being the underflow bucket and 10+1=11 being the overflow |
| // bucket. So the first invalid bucket index is 12. |
| event_record->event()->mutable_integer_histogram_event()->mutable_buckets()->Swap( |
| testing::NewHistogram({0, 1, 2, 12}, {100, 101, 102, 103}).get()); |
| auto event_logger = GetEventLoggerForMetricType(MetricDefinition::INTEGER_HISTOGRAM); |
| EXPECT_EQ(StatusCode::INVALID_ARGUMENT, |
| event_logger |
| ->PrepareAndValidateEvent(testing::cobalt_new::kFileSystemWriteTimesMetricId, |
| MetricDefinition::INTEGER_HISTOGRAM, event_record.get()) |
| .error_code()); |
| } |
| |
| TEST_F(EventLoggersLocalAggregationTest, IntegerHistogramFleetwideHistograms) { |
| ReportDefinition report = ReportDefinitionWithReportType( |
| testing::cobalt_new::kFileSystemWriteTimesFileSystemWriteTimesHistogramReportId, |
| ReportDefinition::FLEETWIDE_HISTOGRAMS); |
| auto event_record = GetEventRecordWithMetricType( |
| testing::cobalt_new::kFileSystemWriteTimesMetricId, MetricDefinition::INTEGER_HISTOGRAM); |
| |
| auto event_logger = GetEventLoggerForMetricType(MetricDefinition::INTEGER_HISTOGRAM); |
| EXPECT_EQ( |
| StatusCode::OK, |
| event_logger->Log(std::move(event_record), std::chrono::system_clock::now()).error_code()); |
| |
| event_record = GetEventRecordWithMetricType(testing::cobalt_new::kFileSystemWriteTimesMetricId, |
| MetricDefinition::INTEGER_HISTOGRAM); |
| local_aggregation::ImmediateLocalAggregateStorage store = GetLocalAggregateStorage(); |
| local_aggregation::ReportAggregate report_aggregate = GetReportAggregate( |
| testing::cobalt_new::kFileSystemWriteTimesMetricId, MetricDefinition::INTEGER_HISTOGRAM, |
| testing::cobalt_new::kFileSystemWriteTimesFileSystemWriteTimesHistogramReportId, &store); |
| ASSERT_TRUE(report_aggregate.has_hourly()); |
| local_aggregation::AggregationPeriodBucket hourly_bucket = |
| report_aggregate.hourly().by_hour_id().begin()->second; |
| local_aggregation::AggregateData data = |
| hourly_bucket.system_profile_aggregates(0).by_event_code().at(0).data(); |
| ASSERT_TRUE(data.has_integer_histogram()); |
| const auto& histogram = data.integer_histogram(); |
| EXPECT_EQ(histogram.histogram().at(0), 100); |
| EXPECT_EQ(histogram.histogram().at(1), 101); |
| EXPECT_EQ(histogram.histogram().at(2), 102); |
| EXPECT_EQ(histogram.histogram().at(3), 103); |
| } |
| |
| TEST_F(EventLoggersLocalAggregationTest, StringStringHistogramsValidation) { |
| auto event_record = GetEventRecordWithMetricType(testing::cobalt_new::kComponentMetricId, |
| MetricDefinition::STRING); |
| auto event_logger = GetEventLoggerForMetricType(MetricDefinition::STRING); |
| EXPECT_EQ(StatusCode::OK, |
| event_logger |
| ->PrepareAndValidateEvent(testing::cobalt_new::kComponentMetricId, |
| MetricDefinition::STRING, event_record.get()) |
| .error_code()); |
| } |
| |
| TEST_F(EventLoggersLocalAggregationTest, StringStringHistogramsValidationWrongType) { |
| auto event_record = GetEventRecordWithMetricType(testing::cobalt_new::kComponentMetricId, |
| MetricDefinition::INTEGER); |
| auto event_logger = GetEventLoggerForMetricType(MetricDefinition::STRING); |
| EXPECT_EQ(StatusCode::INVALID_ARGUMENT, |
| event_logger |
| ->PrepareAndValidateEvent(testing::cobalt_new::kComponentMetricId, |
| MetricDefinition::STRING, event_record.get()) |
| .error_code()); |
| } |
| |
| TEST_F(EventLoggersLocalAggregationTest, StringStringHistograms) { |
| ReportDefinition report = ReportDefinitionWithReportType( |
| testing::cobalt_new::kComponentComponentHistogramReportId, ReportDefinition::STRING_COUNTS); |
| auto event_record = GetEventRecordWithMetricType(testing::cobalt_new::kComponentMetricId, |
| MetricDefinition::STRING); |
| |
| auto event_logger = GetEventLoggerForMetricType(MetricDefinition::STRING); |
| EXPECT_EQ( |
| StatusCode::OK, |
| event_logger->Log(std::move(event_record), std::chrono::system_clock::now()).error_code()); |
| |
| event_record = GetEventRecordWithMetricType(testing::cobalt_new::kComponentMetricId, |
| MetricDefinition::STRING); |
| local_aggregation::ImmediateLocalAggregateStorage store = GetLocalAggregateStorage(); |
| local_aggregation::ReportAggregate report_aggregate = |
| GetReportAggregate(testing::cobalt_new::kComponentMetricId, MetricDefinition::STRING, |
| testing::cobalt_new::kComponentComponentHistogramReportId, &store); |
| ASSERT_TRUE(report_aggregate.has_hourly()); |
| local_aggregation::AggregationPeriodBucket hourly_bucket = |
| report_aggregate.hourly().by_hour_id().begin()->second; |
| local_aggregation::AggregateData data = |
| hourly_bucket.system_profile_aggregates(0).by_event_code().at(0).data(); |
| ASSERT_TRUE(data.has_string_histogram()); |
| const auto& histogram = data.string_histogram(); |
| EXPECT_EQ(histogram.histogram().at(0), 1); |
| } |
| |
| TEST_F(EventLoggersLocalAggregationTest, OccurrenceOtherTimeZone) { |
| ReportDefinition report = ReportDefinitionWithReportType( |
| testing::cobalt_new::kOccurrenceOtherTzFleetwideOccurrenceCountsReportId, |
| ReportDefinition::FLEETWIDE_OCCURRENCE_COUNTS); |
| std::unique_ptr<EventRecord> event_record = GetEventRecordWithMetricType( |
| testing::cobalt_new::kOccurrenceOtherTzMetricId, MetricDefinition::OCCURRENCE); |
| std::unique_ptr<EventLogger> event_logger = |
| GetEventLoggerForMetricType(MetricDefinition::OCCURRENCE); |
| |
| // Log a count of 1 for start_time_, which has hour ID 17521 in the initial time zone of |
| // `civil_time_converter_`. |
| EXPECT_EQ(StatusCode::OK, event_logger->Log(std::move(event_record), start_time_).error_code()); |
| |
| event_record = GetEventRecordWithMetricType(testing::cobalt_new::kOccurrenceOtherTzMetricId, |
| MetricDefinition::OCCURRENCE); |
| event_record->event()->mutable_occurrence_event()->set_count(2); |
| |
| // Log a count of 2 for `start_time_ + 1` hour, which has hour ID 17524 in the final time zone of |
| // `civil_time_converter_`. |
| EXPECT_EQ(StatusCode::OK, |
| event_logger->Log(std::move(event_record), start_time_ + util::kOneHour).error_code()); |
| |
| local_aggregation::ImmediateLocalAggregateStorage store = GetLocalAggregateStorage(); |
| local_aggregation::ReportAggregate report_aggregate = GetReportAggregate( |
| testing::cobalt_new::kOccurrenceOtherTzMetricId, MetricDefinition::OCCURRENCE, |
| testing::cobalt_new::kOccurrenceOtherTzFleetwideOccurrenceCountsReportId, &store); |
| |
| // Check that the EventLogger recorded the correct hour IDs and event counts. |
| ASSERT_TRUE(report_aggregate.has_hourly()); |
| ASSERT_EQ(report_aggregate.hourly().by_hour_id_size(), 2); |
| |
| ASSERT_EQ(report_aggregate.hourly().by_hour_id().count(17521), 1); |
| EXPECT_EQ(report_aggregate.hourly() |
| .by_hour_id() |
| .at(17521) |
| .system_profile_aggregates(0) |
| .by_event_code() |
| .at(0) |
| .data() |
| .count(), |
| 1); |
| |
| ASSERT_EQ(report_aggregate.hourly().by_hour_id().count(17524), 1); |
| EXPECT_EQ(report_aggregate.hourly() |
| .by_hour_id() |
| .at(17524) |
| .system_profile_aggregates(0) |
| .by_event_code() |
| .at(0) |
| .data() |
| .count(), |
| 2); |
| } |
| |
| } // namespace internal |
| } // namespace cobalt::logger |