[cobalt][LocalAggregationRedesign] Create an AggregateStore
- Moves non-EventAggregator/EventAggregatorManager functionality outside of the EventAggregator and into an AggregateStore
- The AggregateStore contains the functionality of AggregateStore, MemoryManager, ObservationGenerator
- Does not change the functionality of the methods to reduce the size and complexity of this change. Code style improvements and and new methods will be added in following CLs
- Divides the event_aggregator_tests.cc into two files. All methods that test more than the EventAggregator methods are moved to the aggregate_store_tests.cc
- Removes unused methods in the event_aggregator_tests.cc
- The tests will be reevaluated to match the current class layout in following CLs
- New tests will be added in following CLs
For more information see go/cobalt-local-aggregation-redesign
Bug: 40853
Change-Id: Id6c5639b15cfd1495e52fc5ad6637caaa4c24d3f
diff --git a/src/local_aggregation/BUILD.gn b/src/local_aggregation/BUILD.gn
index a148730..a73a40c 100644
--- a/src/local_aggregation/BUILD.gn
+++ b/src/local_aggregation/BUILD.gn
@@ -71,17 +71,40 @@
]
public_deps = [
+ ":aggregate_store",
+ "$cobalt_root/src:logging",
+ "$cobalt_root/src/lib/util:clock",
+ "$cobalt_root/src/lib/util:datetime_util",
+ "$cobalt_root/src/lib/util:proto_util",
+ "$cobalt_root/src/logger:encoder",
+ "$cobalt_root/src/logger:event_record",
+ "$cobalt_root/src/logger:observation_writer",
+ "$cobalt_root/src/logger:status",
+ "$cobalt_root/src/pb",
+ "$cobalt_root/src/registry:packed_event_codes",
+ ]
+}
+
+source_set("aggregate_store") {
+ sources = [
+ "aggregate_store.cc",
+ "aggregate_store.h",
+ ]
+
+ public_configs = [
+ "$cobalt_root:cobalt_config",
+ "$cobalt_root/src/registry:proto_config",
+ ]
+
+ public_deps = [
":aggregation_utils",
":cobalt_local_aggregation_proto",
- "$cobalt_root/src:logging",
"$cobalt_root/src/algorithms/rappor:rappor_encoder",
- "$cobalt_root/src/lib/util:clock",
"$cobalt_root/src/lib/util:consistent_proto_store",
"$cobalt_root/src/lib/util:datetime_util",
"$cobalt_root/src/lib/util:protected_fields",
"$cobalt_root/src/lib/util:proto_util",
"$cobalt_root/src/logger:encoder",
- "$cobalt_root/src/logger:event_record",
"$cobalt_root/src/logger:observation_writer",
"$cobalt_root/src/logger:status",
"$cobalt_root/src/pb",
@@ -121,6 +144,21 @@
]
}
+source_set("aggregate_store_test") {
+ testonly = true
+ sources = [
+ "aggregate_store_test.cc",
+ ]
+ public_deps = [
+ ":aggregate_store",
+ ":aggregation_utils",
+ ":event_aggregator",
+ "$cobalt_root/src/logger:logger_test_utils",
+ "$cobalt_root/src/logger:testing_constants",
+ "//third_party/googletest:gtest",
+ ]
+}
+
source_set("event_aggregator_mgr_test") {
testonly = true
sources = [
@@ -138,6 +176,7 @@
testonly = true
deps = [
+ ":aggregate_store_test",
":aggregation_utils_test",
":event_aggregator_mgr_test",
":event_aggregator_test",
diff --git a/src/local_aggregation/aggregate_store.cc b/src/local_aggregation/aggregate_store.cc
new file mode 100644
index 0000000..c7eee52
--- /dev/null
+++ b/src/local_aggregation/aggregate_store.cc
@@ -0,0 +1,965 @@
+// 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/local_aggregation/aggregate_store.h"
+
+#include <algorithm>
+#include <map>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "src/algorithms/rappor/rappor_config_helper.h"
+#include "src/lib/util/datetime_util.h"
+#include "src/lib/util/proto_util.h"
+#include "src/lib/util/status.h"
+#include "src/local_aggregation/aggregation_utils.h"
+#include "src/registry/packed_event_codes.h"
+
+namespace cobalt::local_aggregation {
+
+using google::protobuf::RepeatedField;
+using logger::Encoder;
+using logger::kInvalidArguments;
+using logger::kOK;
+using logger::kOther;
+using logger::MetricRef;
+using logger::ObservationWriter;
+using logger::ProjectContext;
+using logger::Status;
+using rappor::RapporConfigHelper;
+using util::ConsistentProtoStore;
+using util::SerializeToBase64;
+using util::StatusCode;
+
+namespace {
+
+////// General helper functions.
+
+// Populates a ReportAggregationKey proto message and then populates a string
+// with the base64 encoding of the serialized proto.
+bool PopulateReportKey(uint32_t customer_id, uint32_t project_id, uint32_t metric_id,
+ uint32_t report_id, std::string* key) {
+ ReportAggregationKey key_data;
+ key_data.set_customer_id(customer_id);
+ key_data.set_project_id(project_id);
+ key_data.set_metric_id(metric_id);
+ key_data.set_report_id(report_id);
+ return SerializeToBase64(key_data, key);
+}
+
+////// Helper functions used by the constructor and UpdateAggregationConfigs().
+
+// Gets and validates the window sizes and/or aggregation windows from a ReportDefinition, converts
+// window sizes to daily aggregation windows, sorts the aggregation windows in increasing order, and
+// adds them to an AggregationConfig.
+//
+// TODO(pesk): Stop looking at the window_size field of |report| once all reports have been updated
+// to have OnDeviceAggregationWindows only.
+bool GetSortedAggregationWindowsFromReport(const ReportDefinition& report,
+ AggregationConfig* aggregation_config) {
+ if (report.window_size_size() == 0 && report.aggregation_window_size() == 0) {
+ LOG(ERROR) << "Report must have at least one window size or aggregation window.";
+ return false;
+ }
+ std::vector<uint32_t> aggregation_days;
+ std::vector<uint32_t> aggregation_hours;
+ for (const uint32_t window_size : report.window_size()) {
+ if (window_size == 0 || window_size > kMaxAllowedAggregationDays) {
+ LOG(ERROR) << "Window size must be positive and cannot exceed " << kMaxAllowedAggregationDays;
+ return false;
+ }
+ aggregation_days.push_back(window_size);
+ }
+ for (const auto& window : report.aggregation_window()) {
+ switch (window.units_case()) {
+ case OnDeviceAggregationWindow::kDays: {
+ uint32_t num_days = window.days();
+ if (num_days == 0 || num_days > kMaxAllowedAggregationDays) {
+ LOG(ERROR) << "Daily windows must contain at least 1 and no more than "
+ << kMaxAllowedAggregationDays << " days";
+ return false;
+ }
+ aggregation_days.push_back(num_days);
+ break;
+ }
+ case OnDeviceAggregationWindow::kHours: {
+ uint32_t num_hours = window.hours();
+ if (num_hours == 0 || num_hours > kMaxAllowedAggregationHours) {
+ LOG(ERROR) << "Hourly windows must contain at least 1 and no more than "
+ << kMaxAllowedAggregationHours << " hours";
+ return false;
+ }
+ aggregation_hours.push_back(num_hours);
+ break;
+ }
+ default:
+ LOG(ERROR) << "Invalid OnDeviceAggregationWindow type " << window.units_case();
+ }
+ }
+ std::sort(aggregation_hours.begin(), aggregation_hours.end());
+ std::sort(aggregation_days.begin(), aggregation_days.end());
+ for (auto num_hours : aggregation_hours) {
+ *aggregation_config->add_aggregation_window() = MakeHourWindow(num_hours);
+ }
+ for (auto num_days : aggregation_days) {
+ *aggregation_config->add_aggregation_window() = MakeDayWindow(num_days);
+ }
+ return true;
+}
+
+// Creates an AggregationConfig from a ProjectContext, MetricDefinition, and
+// ReportDefinition and populates the aggregation_config field of a specified
+// ReportAggregates. Also sets the type of the ReportAggregates based on the
+// ReportDefinition's type.
+//
+// Accepts ReportDefinitions with either at least one WindowSize, or at least one
+// OnDeviceAggregationWindow with units in days.
+bool PopulateReportAggregates(const ProjectContext& project_context, const MetricDefinition& metric,
+ const ReportDefinition& report, ReportAggregates* report_aggregates) {
+ if (report.window_size_size() == 0 && report.aggregation_window_size() == 0) {
+ }
+ AggregationConfig* aggregation_config = report_aggregates->mutable_aggregation_config();
+ *aggregation_config->mutable_project() = project_context.project();
+ *aggregation_config->mutable_metric() = *project_context.GetMetric(metric.id());
+ *aggregation_config->mutable_report() = report;
+ if (!GetSortedAggregationWindowsFromReport(report, aggregation_config)) {
+ return false;
+ }
+ switch (report.report_type()) {
+ case ReportDefinition::UNIQUE_N_DAY_ACTIVES: {
+ report_aggregates->set_allocated_unique_actives_aggregates(new UniqueActivesReportAggregates);
+ return true;
+ }
+ case ReportDefinition::PER_DEVICE_NUMERIC_STATS:
+ case ReportDefinition::PER_DEVICE_HISTOGRAM: {
+ report_aggregates->set_allocated_numeric_aggregates(new PerDeviceNumericAggregates);
+ return true;
+ }
+ default:
+ return false;
+ }
+}
+
+// Move all items from the |window_size| field to the |aggregation_window| field
+// of each AggregationConfig, preserving the order of the items. The |aggregation_window| field
+// should be empty if the |window_size| field is nonempty. If for some reason this is not true, log
+// an error and discard the contents of |aggregation_window| and replace them with the migrated
+// |window_size| values.
+void ConvertWindowSizesToAggregationDays(LocalAggregateStore* store) {
+ for (auto [key, aggregates] : store->by_report_key()) {
+ auto config = (*store->mutable_by_report_key())[key].mutable_aggregation_config();
+ if (config->window_size_size() > 0 && config->aggregation_window_size() > 0) {
+ LOG(ERROR) << "Config has both a window_size and an aggregation_window; discarding all "
+ "aggregation_windows";
+ config->clear_aggregation_window();
+ }
+ for (auto window_size : config->window_size()) {
+ *config->add_aggregation_window() = MakeDayWindow(window_size);
+ }
+ config->clear_window_size();
+ }
+}
+
+// Upgrades the LocalAggregateStore from version 0 to |kCurrentLocalAggregateStoreVersion|.
+Status UpgradeLocalAggregateStoreFromVersion0(LocalAggregateStore* store) {
+ ConvertWindowSizesToAggregationDays(store);
+ store->set_version(kCurrentLocalAggregateStoreVersion);
+ return kOK;
+}
+
+} // namespace
+
+AggregateStore::AggregateStore(const Encoder* encoder, const ObservationWriter* observation_writer,
+ ConsistentProtoStore* local_aggregate_proto_store,
+ ConsistentProtoStore* obs_history_proto_store,
+ const size_t backfill_days)
+ : encoder_(encoder),
+ observation_writer_(observation_writer),
+ local_aggregate_proto_store_(local_aggregate_proto_store),
+ obs_history_proto_store_(obs_history_proto_store) {
+ CHECK_LE(backfill_days, kMaxAllowedBackfillDays)
+ << "backfill_days must be less than or equal to " << kMaxAllowedBackfillDays;
+ backfill_days_ = backfill_days;
+ auto locked = protected_aggregate_store_.lock();
+ auto restore_aggregates_status =
+ local_aggregate_proto_store_->Read(&(locked->local_aggregate_store));
+ switch (restore_aggregates_status.error_code()) {
+ case StatusCode::OK: {
+ VLOG(4) << "Read LocalAggregateStore from disk.";
+ break;
+ }
+ case StatusCode::NOT_FOUND: {
+ VLOG(4) << "No file found for local_aggregate_proto_store. Proceeding "
+ "with empty LocalAggregateStore. File will be created on "
+ "first snapshot of the LocalAggregateStore.";
+ locked->local_aggregate_store = MakeNewLocalAggregateStore();
+ break;
+ }
+ default: {
+ LOG(ERROR) << "Read to local_aggregate_proto_store failed with status code: "
+ << restore_aggregates_status.error_code()
+ << "\nError message: " << restore_aggregates_status.error_message()
+ << "\nError details: " << restore_aggregates_status.error_details()
+ << "\nProceeding with empty LocalAggregateStore.";
+ locked->local_aggregate_store = MakeNewLocalAggregateStore();
+ }
+ }
+ if (auto status = MaybeUpgradeLocalAggregateStore(&(locked->local_aggregate_store));
+ status != kOK) {
+ LOG(ERROR) << "Failed to upgrade LocalAggregateStore to current version with status " << status
+ << ".\nProceeding with empty "
+ "LocalAggregateStore.";
+ locked->local_aggregate_store = MakeNewLocalAggregateStore();
+ }
+
+ auto restore_history_status = obs_history_proto_store_->Read(&obs_history_);
+ switch (restore_history_status.error_code()) {
+ case StatusCode::OK: {
+ VLOG(4) << "Read AggregatedObservationHistoryStore from disk.";
+ break;
+ }
+ case StatusCode::NOT_FOUND: {
+ VLOG(4) << "No file found for obs_history_proto_store. Proceeding "
+ "with empty AggregatedObservationHistoryStore. File will be "
+ "created on first snapshot of the AggregatedObservationHistoryStore.";
+ break;
+ }
+ default: {
+ LOG(ERROR) << "Read to obs_history_proto_store failed with status code: "
+ << restore_history_status.error_code()
+ << "\nError message: " << restore_history_status.error_message()
+ << "\nError details: " << restore_history_status.error_details()
+ << "\nProceeding with empty AggregatedObservationHistoryStore.";
+ obs_history_ = MakeNewObservationHistoryStore();
+ }
+ }
+ if (auto status = MaybeUpgradeObservationHistoryStore(&obs_history_); status != kOK) {
+ LOG(ERROR)
+ << "Failed to upgrade AggregatedObservationHistoryStore to current version with status "
+ << status << ".\nProceeding with empty AggregatedObservationHistoryStore.";
+ obs_history_ = MakeNewObservationHistoryStore();
+ }
+}
+
+// Given a ProjectContext, MetricDefinition, and ReportDefinition and a pointer
+// to the LocalAggregateStore, checks whether a key with the same customer,
+// project, metric, and report ID already exists in the LocalAggregateStore. If
+// not, creates and inserts a new key and value. Returns kInvalidArguments if
+// creation of the key or value fails, and kOK otherwise. The caller should hold
+// the mutex protecting the LocalAggregateStore.
+Status AggregateStore::MaybeInsertReportConfigLocked(const ProjectContext& project_context,
+ const MetricDefinition& metric,
+ const ReportDefinition& report,
+ LocalAggregateStore* store) {
+ std::string key;
+ if (!PopulateReportKey(project_context.project().customer_id(),
+ project_context.project().project_id(), metric.id(), report.id(), &key)) {
+ return kInvalidArguments;
+ }
+ ReportAggregates report_aggregates;
+ if (store->by_report_key().count(key) == 0) {
+ if (!PopulateReportAggregates(project_context, metric, report, &report_aggregates)) {
+ return kInvalidArguments;
+ }
+ (*store->mutable_by_report_key())[key] = report_aggregates;
+ }
+ return kOK;
+}
+
+RepeatedField<uint32_t> UnpackEventCodesProto(uint64_t packed_event_codes) {
+ RepeatedField<uint32_t> fields;
+ for (auto code : config::UnpackEventCodes(packed_event_codes)) {
+ *fields.Add() = code;
+ }
+ return fields;
+}
+
+Status AggregateStore::BackUpLocalAggregateStore() {
+ // Lock, copy the LocalAggregateStore, and release the lock. Write the copy
+ // to |local_aggregate_proto_store_|.
+ auto local_aggregate_store = CopyLocalAggregateStore();
+ auto status = local_aggregate_proto_store_->Write(local_aggregate_store);
+ if (!status.ok()) {
+ LOG(ERROR) << "Failed to back up the LocalAggregateStore with error code: "
+ << status.error_code() << "\nError message: " << status.error_message()
+ << "\nError details: " << status.error_details();
+ return kOther;
+ }
+ return kOK;
+}
+
+Status AggregateStore::BackUpObservationHistory() {
+ auto status = obs_history_proto_store_->Write(obs_history_);
+ if (!status.ok()) {
+ LOG(ERROR) << "Failed to back up the AggregatedObservationHistoryStore. "
+ "::cobalt::util::Status error code: "
+ << status.error_code() << "\nError message: " << status.error_message()
+ << "\nError details: " << status.error_details();
+ return kOther;
+ }
+ return kOK;
+}
+
+////////////////////// GarbageCollect and helper functions //////////////////
+
+namespace {
+
+void GarbageCollectUniqueActivesReportAggregates(uint32_t day_index, uint32_t max_aggregation_days,
+ uint32_t backfill_days,
+ UniqueActivesReportAggregates* report_aggregates) {
+ auto map_by_event_code = report_aggregates->mutable_by_event_code();
+ for (auto event_code = map_by_event_code->begin(); event_code != map_by_event_code->end();) {
+ auto map_by_day = event_code->second.mutable_by_day_index();
+ for (auto day = map_by_day->begin(); day != map_by_day->end();) {
+ if (day->first <= day_index - backfill_days - max_aggregation_days) {
+ day = map_by_day->erase(day);
+ } else {
+ ++day;
+ }
+ }
+ if (map_by_day->empty()) {
+ event_code = map_by_event_code->erase(event_code);
+ } else {
+ ++event_code;
+ }
+ }
+}
+
+void GarbageCollectNumericReportAggregates(uint32_t day_index, uint32_t max_aggregation_days,
+ uint32_t backfill_days,
+ PerDeviceNumericAggregates* report_aggregates) {
+ auto map_by_component = report_aggregates->mutable_by_component();
+ for (auto component = map_by_component->begin(); component != map_by_component->end();) {
+ auto map_by_event_code = component->second.mutable_by_event_code();
+ for (auto event_code = map_by_event_code->begin(); event_code != map_by_event_code->end();) {
+ auto map_by_day = event_code->second.mutable_by_day_index();
+ for (auto day = map_by_day->begin(); day != map_by_day->end();) {
+ if (day->first <= day_index - backfill_days - max_aggregation_days) {
+ day = map_by_day->erase(day);
+ } else {
+ ++day;
+ }
+ }
+ if (map_by_day->empty()) {
+ event_code = map_by_event_code->erase(event_code);
+ } else {
+ ++event_code;
+ }
+ }
+ if (map_by_event_code->empty()) {
+ component = map_by_component->erase(component);
+ } else {
+ ++component;
+ }
+ }
+}
+
+} // namespace
+
+Status AggregateStore::GarbageCollect(uint32_t day_index_utc, uint32_t day_index_local) {
+ if (day_index_local == 0u) {
+ day_index_local = day_index_utc;
+ }
+ CHECK_LT(day_index_utc, UINT32_MAX);
+ CHECK_LT(day_index_local, UINT32_MAX);
+ CHECK_GE(day_index_utc, kMaxAllowedAggregationDays + backfill_days_);
+ CHECK_GE(day_index_local, kMaxAllowedAggregationDays + backfill_days_);
+
+ auto locked = protected_aggregate_store_.lock();
+ for (const auto& [report_key, aggregates] : locked->local_aggregate_store.by_report_key()) {
+ uint32_t day_index;
+ const auto& config = aggregates.aggregation_config();
+ switch (config.metric().time_zone_policy()) {
+ case MetricDefinition::UTC: {
+ day_index = day_index_utc;
+ break;
+ }
+ case MetricDefinition::LOCAL: {
+ day_index = day_index_local;
+ break;
+ }
+ default:
+ LOG_FIRST_N(ERROR, 10) << "The TimeZonePolicy of this MetricDefinition is invalid.";
+ continue;
+ }
+ if (aggregates.aggregation_config().aggregation_window_size() == 0) {
+ LOG_FIRST_N(ERROR, 10) << "This ReportDefinition does not have an aggregation window.";
+ continue;
+ }
+ // PopulateReportAggregates ensured that aggregation_window has at least one element, that all
+ // aggregation windows are <= kMaxAllowedAggregationDays, and that config.aggregation_window()
+ // is sorted in increasing order.
+ uint32_t max_aggregation_days = 1u;
+ const OnDeviceAggregationWindow& largest_window =
+ config.aggregation_window(config.aggregation_window_size() - 1);
+ if (largest_window.units_case() == OnDeviceAggregationWindow::kDays) {
+ max_aggregation_days = largest_window.days();
+ }
+ if (max_aggregation_days == 0u || max_aggregation_days > day_index) {
+ LOG_FIRST_N(ERROR, 10) << "The maximum number of aggregation days " << max_aggregation_days
+ << " of this ReportDefinition is out of range.";
+ continue;
+ }
+ // For each ReportAggregates, descend to and iterate over the sub-map of
+ // local aggregates keyed by day index. Keep buckets with day indices
+ // greater than |day_index| - |backfill_days_| - |max_aggregation_days|, and
+ // remove all buckets with smaller day indices.
+ switch (aggregates.type_case()) {
+ case ReportAggregates::kUniqueActivesAggregates: {
+ GarbageCollectUniqueActivesReportAggregates(
+ day_index, max_aggregation_days, backfill_days_,
+ locked->local_aggregate_store.mutable_by_report_key()
+ ->at(report_key)
+ .mutable_unique_actives_aggregates());
+ break;
+ }
+ case ReportAggregates::kNumericAggregates: {
+ GarbageCollectNumericReportAggregates(day_index, max_aggregation_days, backfill_days_,
+ locked->local_aggregate_store.mutable_by_report_key()
+ ->at(report_key)
+ .mutable_numeric_aggregates());
+ break;
+ }
+ default:
+ continue;
+ }
+ }
+ return kOK;
+}
+
+Status AggregateStore::GenerateObservations(uint32_t final_day_index_utc,
+ uint32_t final_day_index_local) {
+ if (final_day_index_local == 0u) {
+ final_day_index_local = final_day_index_utc;
+ }
+ CHECK_LT(final_day_index_utc, UINT32_MAX);
+ CHECK_LT(final_day_index_local, UINT32_MAX);
+ CHECK_GE(final_day_index_utc, kMaxAllowedAggregationDays + backfill_days_);
+ CHECK_GE(final_day_index_local, kMaxAllowedAggregationDays + backfill_days_);
+
+ // Lock, copy the LocalAggregateStore, and release the lock. Use the copy to
+ // generate observations.
+ auto local_aggregate_store = CopyLocalAggregateStore();
+ for (const auto& [report_key, aggregates] : local_aggregate_store.by_report_key()) {
+ const auto& config = aggregates.aggregation_config();
+
+ const auto& metric = config.metric();
+ auto metric_ref = MetricRef(&config.project(), &metric);
+ uint32_t final_day_index;
+ switch (metric.time_zone_policy()) {
+ case MetricDefinition::UTC: {
+ final_day_index = final_day_index_utc;
+ break;
+ }
+ case MetricDefinition::LOCAL: {
+ final_day_index = final_day_index_local;
+ break;
+ }
+ default:
+ LOG_FIRST_N(ERROR, 10) << "The TimeZonePolicy of this MetricDefinition is invalid.";
+ continue;
+ }
+
+ const auto& report = config.report();
+ // PopulateReportAggregates ensured that aggregation_window has at least one element, that all
+ // aggregation windows are <= kMaxAllowedAggregationDays, and that config.aggregation_window()
+ // is sorted in increasing order.
+ if (config.aggregation_window_size() == 0u) {
+ LOG_FIRST_N(ERROR, 10) << "No aggregation_window found for this report.";
+ continue;
+ }
+ uint32_t max_aggregation_days = 1u;
+ const OnDeviceAggregationWindow& largest_window =
+ config.aggregation_window(config.aggregation_window_size() - 1);
+ if (largest_window.units_case() == OnDeviceAggregationWindow::kDays) {
+ max_aggregation_days = largest_window.days();
+ }
+ if (max_aggregation_days == 0u || max_aggregation_days > final_day_index) {
+ LOG_FIRST_N(ERROR, 10) << "The maximum number of aggregation days " << max_aggregation_days
+ << " of this ReportDefinition is out of range.";
+ continue;
+ }
+ switch (metric.metric_type()) {
+ case MetricDefinition::EVENT_OCCURRED: {
+ auto num_event_codes = RapporConfigHelper::BasicRapporNumCategories(metric);
+
+ switch (report.report_type()) {
+ case ReportDefinition::UNIQUE_N_DAY_ACTIVES: {
+ auto status = GenerateUniqueActivesObservations(metric_ref, report_key, aggregates,
+ num_event_codes, final_day_index);
+ if (status != kOK) {
+ return status;
+ }
+ break;
+ }
+ default:
+ continue;
+ }
+ break;
+ }
+ case MetricDefinition::EVENT_COUNT:
+ case MetricDefinition::ELAPSED_TIME:
+ case MetricDefinition::FRAME_RATE:
+ case MetricDefinition::MEMORY_USAGE: {
+ switch (report.report_type()) {
+ case ReportDefinition::PER_DEVICE_NUMERIC_STATS:
+ case ReportDefinition::PER_DEVICE_HISTOGRAM: {
+ auto status = GenerateObsFromNumericAggregates(metric_ref, report_key, aggregates,
+ final_day_index);
+ if (status != kOK) {
+ return status;
+ }
+ break;
+ }
+ default:
+ continue;
+ }
+ break;
+ }
+ default:
+ continue;
+ }
+ }
+ return kOK;
+}
+
+////////// GenerateUniqueActivesObservations and helper methods ////////////////
+
+namespace {
+
+// Given the set of daily aggregates for a fixed event code, and the size and
+// end date of an aggregation window, returns the first day index within that
+// window on which the event code occurred. Returns 0 if the event code did
+// not occur within the window.
+uint32_t FirstActiveDayIndexInWindow(const DailyAggregates& daily_aggregates,
+ uint32_t obs_day_index, uint32_t aggregation_days) {
+ for (uint32_t day_index = obs_day_index - aggregation_days + 1; day_index <= obs_day_index;
+ day_index++) {
+ auto day_aggregate = daily_aggregates.by_day_index().find(day_index);
+ if (day_aggregate != daily_aggregates.by_day_index().end() &&
+ day_aggregate->second.activity_daily_aggregate().activity_indicator()) {
+ return day_index;
+ }
+ }
+ return 0u;
+}
+
+// Given the day index of an event occurrence and the size and end date
+// of an aggregation window, returns true if the occurrence falls within
+// the window and false if not.
+bool IsActivityInWindow(uint32_t active_day_index, uint32_t obs_day_index,
+ uint32_t aggregation_days) {
+ return (active_day_index <= obs_day_index && active_day_index > obs_day_index - aggregation_days);
+}
+
+} // namespace
+
+uint32_t AggregateStore::UniqueActivesLastGeneratedDayIndex(const std::string& report_key,
+ uint32_t event_code,
+ uint32_t aggregation_days) const {
+ auto report_history = obs_history_.by_report_key().find(report_key);
+ if (report_history == obs_history_.by_report_key().end()) {
+ return 0u;
+ }
+ auto event_code_history =
+ report_history->second.unique_actives_history().by_event_code().find(event_code);
+ if (event_code_history == report_history->second.unique_actives_history().by_event_code().end()) {
+ return 0u;
+ }
+ auto window_history = event_code_history->second.by_window_size().find(aggregation_days);
+ if (window_history == event_code_history->second.by_window_size().end()) {
+ return 0u;
+ }
+ return window_history->second;
+}
+
+Status AggregateStore::GenerateSingleUniqueActivesObservation(
+ const MetricRef metric_ref, const ReportDefinition* report, uint32_t obs_day_index,
+ uint32_t event_code, const OnDeviceAggregationWindow& window, bool was_active) const {
+ auto encoder_result = encoder_->EncodeUniqueActivesObservation(metric_ref, report, obs_day_index,
+ event_code, was_active, window);
+ if (encoder_result.status != kOK) {
+ return encoder_result.status;
+ }
+ if (encoder_result.observation == nullptr || encoder_result.metadata == nullptr) {
+ LOG(ERROR) << "Failed to encode UniqueActivesObservation";
+ return kOther;
+ }
+
+ auto writer_status = observation_writer_->WriteObservation(*encoder_result.observation,
+ std::move(encoder_result.metadata));
+ if (writer_status != kOK) {
+ return writer_status;
+ }
+ return kOK;
+}
+
+Status AggregateStore::GenerateUniqueActivesObservations(const MetricRef metric_ref,
+ const std::string& report_key,
+ const ReportAggregates& report_aggregates,
+ uint32_t num_event_codes,
+ uint32_t final_day_index) {
+ CHECK_GT(final_day_index, backfill_days_);
+ // The earliest day index for which we might need to generate an
+ // Observation.
+ auto backfill_period_start = uint32_t(final_day_index - backfill_days_);
+
+ for (uint32_t event_code = 0; event_code < num_event_codes; event_code++) {
+ auto daily_aggregates =
+ report_aggregates.unique_actives_aggregates().by_event_code().find(event_code);
+ // Have any events ever been logged for this report and event code?
+ bool found_event_code =
+ (daily_aggregates != report_aggregates.unique_actives_aggregates().by_event_code().end());
+ for (const auto& window : report_aggregates.aggregation_config().aggregation_window()) {
+ // Skip all hourly windows, and all daily windows which are larger than
+ // kMaxAllowedAggregationDays.
+ //
+ // TODO(pesk): Generate observations for hourly windows.
+ if (window.units_case() != OnDeviceAggregationWindow::kDays) {
+ LOG(INFO) << "Skipping unsupported aggregation window.";
+ continue;
+ }
+ if (window.days() > kMaxAllowedAggregationDays) {
+ LOG(WARNING) << "GenerateUniqueActivesObservations ignoring a window "
+ "size exceeding the maximum allowed value";
+ continue;
+ }
+ // Find the earliest day index for which an Observation has not yet
+ // been generated for this report, event code, and window size. If
+ // that day index is later than |final_day_index|, no Observation is
+ // generated on this invocation.
+ auto last_gen = UniqueActivesLastGeneratedDayIndex(report_key, event_code, window.days());
+ auto first_day_index = std::max(last_gen + 1, backfill_period_start);
+ // The latest day index on which |event_type| is known to have
+ // occurred, so far. This value will be updated as we search
+ // forward from the earliest day index belonging to a window of
+ // interest.
+ uint32_t active_day_index = 0u;
+ // Iterate over the day indices |obs_day_index| for which we need
+ // to generate Observations. On each iteration, generate an
+ // Observation for the |window| ending on |obs_day_index|.
+ for (uint32_t obs_day_index = first_day_index; obs_day_index <= final_day_index;
+ obs_day_index++) {
+ bool was_active = false;
+ if (found_event_code) {
+ // If the current value of |active_day_index| falls within the
+ // window, generate an Observation of activity. If not, search
+ // forward in the window, update |active_day_index|, and generate an
+ // Observation of activity or inactivity depending on the result of
+ // the search.
+ if (IsActivityInWindow(active_day_index, obs_day_index, window.days())) {
+ was_active = true;
+ } else {
+ active_day_index =
+ FirstActiveDayIndexInWindow(daily_aggregates->second, obs_day_index, window.days());
+ was_active = IsActivityInWindow(active_day_index, obs_day_index, window.days());
+ }
+ }
+ auto status = GenerateSingleUniqueActivesObservation(
+ metric_ref, &report_aggregates.aggregation_config().report(), obs_day_index, event_code,
+ window, was_active);
+ if (status != kOK) {
+ return status;
+ }
+ // Update |obs_history_| with the latest date of Observation
+ // generation for this report, event code, and window size.
+ (*(*(*obs_history_.mutable_by_report_key())[report_key]
+ .mutable_unique_actives_history()
+ ->mutable_by_event_code())[event_code]
+ .mutable_by_window_size())[window.days()] = obs_day_index;
+ }
+ }
+ }
+ return kOK;
+}
+
+////////// GenerateObsFromNumericAggregates and helper methods /////////////
+
+uint32_t AggregateStore::PerDeviceNumericLastGeneratedDayIndex(const std::string& report_key,
+ const std::string& component,
+ uint32_t event_code,
+ uint32_t aggregation_days) const {
+ const auto& report_history = obs_history_.by_report_key().find(report_key);
+ if (report_history == obs_history_.by_report_key().end()) {
+ return 0u;
+ }
+ if (!report_history->second.has_per_device_numeric_history()) {
+ return 0u;
+ }
+ const auto& component_history =
+ report_history->second.per_device_numeric_history().by_component().find(component);
+ if (component_history ==
+ report_history->second.per_device_numeric_history().by_component().end()) {
+ return 0u;
+ }
+ const auto& event_code_history = component_history->second.by_event_code().find(event_code);
+ if (event_code_history == component_history->second.by_event_code().end()) {
+ return 0u;
+ }
+ const auto& window_history = event_code_history->second.by_window_size().find(aggregation_days);
+ if (window_history == event_code_history->second.by_window_size().end()) {
+ return 0u;
+ }
+ return window_history->second;
+}
+
+uint32_t AggregateStore::ReportParticipationLastGeneratedDayIndex(
+ const std::string& report_key) const {
+ const auto& report_history = obs_history_.by_report_key().find(report_key);
+ if (report_history == obs_history_.by_report_key().end()) {
+ return 0u;
+ }
+ return report_history->second.report_participation_history().last_generated();
+}
+
+Status AggregateStore::GenerateSinglePerDeviceNumericObservation(
+ const MetricRef metric_ref, const ReportDefinition* report, uint32_t obs_day_index,
+ const std::string& component, uint32_t event_code, const OnDeviceAggregationWindow& window,
+ int64_t value) const {
+ Encoder::Result encoder_result =
+ encoder_->EncodePerDeviceNumericObservation(metric_ref, report, obs_day_index, component,
+ UnpackEventCodesProto(event_code), value, window);
+ if (encoder_result.status != kOK) {
+ return encoder_result.status;
+ }
+ if (encoder_result.observation == nullptr || encoder_result.metadata == nullptr) {
+ LOG(ERROR) << "Failed to encode PerDeviceNumericObservation";
+ return kOther;
+ }
+
+ const auto& writer_status = observation_writer_->WriteObservation(
+ *encoder_result.observation, std::move(encoder_result.metadata));
+ if (writer_status != kOK) {
+ return writer_status;
+ }
+ return kOK;
+}
+
+Status AggregateStore::GenerateSinglePerDeviceHistogramObservation(
+ const MetricRef metric_ref, const ReportDefinition* report, uint32_t obs_day_index,
+ const std::string& component, uint32_t event_code, const OnDeviceAggregationWindow& window,
+ int64_t value) const {
+ Encoder::Result encoder_result = encoder_->EncodePerDeviceHistogramObservation(
+ metric_ref, report, obs_day_index, component, UnpackEventCodesProto(event_code), value,
+ window);
+
+ if (encoder_result.status != kOK) {
+ return encoder_result.status;
+ }
+ if (encoder_result.observation == nullptr || encoder_result.metadata == nullptr) {
+ LOG(ERROR) << "Failed to encode PerDeviceNumericObservation";
+ return kOther;
+ }
+
+ const auto& writer_status = observation_writer_->WriteObservation(
+ *encoder_result.observation, std::move(encoder_result.metadata));
+ if (writer_status != kOK) {
+ return writer_status;
+ }
+ return kOK;
+}
+
+Status AggregateStore::GenerateSingleReportParticipationObservation(const MetricRef metric_ref,
+ const ReportDefinition* report,
+ uint32_t obs_day_index) const {
+ auto encoder_result =
+ encoder_->EncodeReportParticipationObservation(metric_ref, report, obs_day_index);
+ if (encoder_result.status != kOK) {
+ return encoder_result.status;
+ }
+ if (encoder_result.observation == nullptr || encoder_result.metadata == nullptr) {
+ LOG(ERROR) << "Failed to encode ReportParticipationObservation";
+ return kOther;
+ }
+
+ const auto& writer_status = observation_writer_->WriteObservation(
+ *encoder_result.observation, std::move(encoder_result.metadata));
+ if (writer_status != kOK) {
+ return writer_status;
+ }
+ return kOK;
+}
+
+Status AggregateStore::GenerateObsFromNumericAggregates(const MetricRef metric_ref,
+ const std::string& report_key,
+ const ReportAggregates& report_aggregates,
+ uint32_t final_day_index) {
+ CHECK_GT(final_day_index, backfill_days_);
+ // The first day index for which we might have to generate an Observation.
+ auto backfill_period_start = uint32_t(final_day_index - backfill_days_);
+
+ // Generate any necessary PerDeviceNumericObservations for this report.
+ for (const auto& [component, event_code_aggregates] :
+ report_aggregates.numeric_aggregates().by_component()) {
+ for (const auto& [event_code, daily_aggregates] : event_code_aggregates.by_event_code()) {
+ // Populate a helper map keyed by day indices which belong to the range
+ // [|backfill_period_start|, |final_day_index|]. The value at a day
+ // index is the list of windows, in increasing size order, for which an
+ // Observation should be generated for that day index.
+ std::map<uint32_t, std::vector<OnDeviceAggregationWindow>> windows_by_obs_day;
+ for (const auto& window : report_aggregates.aggregation_config().aggregation_window()) {
+ if (window.units_case() != OnDeviceAggregationWindow::kDays) {
+ LOG(INFO) << "Skipping unsupported aggregation window.";
+ continue;
+ }
+ auto last_gen =
+ PerDeviceNumericLastGeneratedDayIndex(report_key, component, event_code, window.days());
+ auto first_day_index = std::max(last_gen + 1, backfill_period_start);
+ for (auto obs_day_index = first_day_index; obs_day_index <= final_day_index;
+ obs_day_index++) {
+ windows_by_obs_day[obs_day_index].push_back(window);
+ }
+ }
+ // Iterate over the day indices |obs_day_index| for which we might need
+ // to generate an Observation. For each day index, generate an
+ // Observation for each needed window.
+ //
+ // More precisely, for each needed window, iterate over the days within that window. If at
+ // least one numeric event was logged during the window, compute the aggregate of the numeric
+ // values over the window and generate a PerDeviceNumericObservation. Whether or not a numeric
+ // event was found, update the AggregatedObservationHistory for this report, component, event
+ // code, and window size with |obs_day_index| as the most recent date of Observation
+ // generation. This reflects the fact that all needed Observations were generated for the
+ // window ending on that date.
+ for (auto obs_day_index = backfill_period_start; obs_day_index <= final_day_index;
+ obs_day_index++) {
+ const auto& windows = windows_by_obs_day.find(obs_day_index);
+ if (windows == windows_by_obs_day.end()) {
+ continue;
+ }
+ bool found_value_for_window = false;
+ int64_t window_aggregate = 0;
+ uint32_t num_days = 0;
+ for (const auto& window : windows->second) {
+ while (num_days < window.days()) {
+ bool found_value_for_day = false;
+ const auto& day_aggregates =
+ daily_aggregates.by_day_index().find(obs_day_index - num_days);
+ if (day_aggregates != daily_aggregates.by_day_index().end()) {
+ found_value_for_day = true;
+ }
+ const auto& aggregation_type =
+ report_aggregates.aggregation_config().report().aggregation_type();
+ switch (aggregation_type) {
+ case ReportDefinition::SUM:
+ if (found_value_for_day) {
+ window_aggregate += day_aggregates->second.numeric_daily_aggregate().value();
+ found_value_for_window = true;
+ }
+ break;
+ case ReportDefinition::MAX:
+ if (found_value_for_day) {
+ window_aggregate = std::max(
+ window_aggregate, day_aggregates->second.numeric_daily_aggregate().value());
+ found_value_for_window = true;
+ }
+ break;
+ case ReportDefinition::MIN:
+ if (found_value_for_day && !found_value_for_window) {
+ window_aggregate = day_aggregates->second.numeric_daily_aggregate().value();
+ found_value_for_window = true;
+ } else if (found_value_for_day) {
+ window_aggregate = std::min(
+ window_aggregate, day_aggregates->second.numeric_daily_aggregate().value());
+ }
+ break;
+ default:
+ LOG(ERROR) << "Unexpected aggregation type " << aggregation_type;
+ return kInvalidArguments;
+ }
+ num_days++;
+ }
+ if (found_value_for_window) {
+ Status status;
+ const ReportDefinition* report = &report_aggregates.aggregation_config().report();
+ switch (report->report_type()) {
+ case ReportDefinition::PER_DEVICE_NUMERIC_STATS: {
+ status = GenerateSinglePerDeviceNumericObservation(
+ metric_ref, report, obs_day_index, component, event_code, window,
+ window_aggregate);
+ if (status != kOK) {
+ return status;
+ }
+ break;
+ }
+ case ReportDefinition::PER_DEVICE_HISTOGRAM: {
+ auto status = GenerateSinglePerDeviceHistogramObservation(
+ metric_ref, report, obs_day_index, component, event_code, window,
+ window_aggregate);
+ if (status != kOK) {
+ return status;
+ }
+ break;
+ }
+ default:
+ LOG(ERROR) << "Unexpected report type " << report->report_type();
+ return kInvalidArguments;
+ }
+ }
+ // Update |obs_history_| with the latest date of Observation
+ // generation for this report, component, event code, and window.
+ (*(*(*(*obs_history_.mutable_by_report_key())[report_key]
+ .mutable_per_device_numeric_history()
+ ->mutable_by_component())[component]
+ .mutable_by_event_code())[event_code]
+ .mutable_by_window_size())[window.days()] = obs_day_index;
+ }
+ }
+ }
+ }
+ // Generate any necessary ReportParticipationObservations for this report.
+ auto participation_last_gen = ReportParticipationLastGeneratedDayIndex(report_key);
+ auto participation_first_day_index = std::max(participation_last_gen + 1, backfill_period_start);
+ for (auto obs_day_index = participation_first_day_index; obs_day_index <= final_day_index;
+ obs_day_index++) {
+ GenerateSingleReportParticipationObservation(
+ metric_ref, &report_aggregates.aggregation_config().report(), obs_day_index);
+ (*obs_history_.mutable_by_report_key())[report_key]
+ .mutable_report_participation_history()
+ ->set_last_generated(obs_day_index);
+ }
+ return kOK;
+}
+
+LocalAggregateStore AggregateStore::MakeNewLocalAggregateStore(uint32_t version) {
+ LocalAggregateStore store;
+ store.set_version(version);
+ return store;
+}
+
+AggregatedObservationHistoryStore AggregateStore::MakeNewObservationHistoryStore(uint32_t version) {
+ AggregatedObservationHistoryStore store;
+ store.set_version(version);
+ return store;
+}
+
+// We can upgrade from v0, but no other versions.
+Status AggregateStore::MaybeUpgradeLocalAggregateStore(LocalAggregateStore* store) {
+ uint32_t version = store->version();
+ if (version == kCurrentLocalAggregateStoreVersion) {
+ return kOK;
+ }
+ VLOG(4) << "Attempting to upgrade LocalAggregateStore from version " << version;
+ switch (version) {
+ case 0u:
+ return UpgradeLocalAggregateStoreFromVersion0(store);
+ default:
+ LOG(ERROR) << "Cannot upgrade LocalAggregateStore from version " << version;
+ return kInvalidArguments;
+ }
+}
+
+// The current version is the earliest version, so no other versions are accepted.
+Status AggregateStore::MaybeUpgradeObservationHistoryStore(
+ AggregatedObservationHistoryStore* store) {
+ uint32_t version = store->version();
+ if (version == kCurrentObservationHistoryStoreVersion) {
+ return kOK;
+ }
+ LOG(ERROR) << "Cannot upgrade AggregatedObservationHistoryStore from version " << version;
+ return kInvalidArguments;
+}
+
+} // namespace cobalt::local_aggregation
diff --git a/src/local_aggregation/aggregate_store.h b/src/local_aggregation/aggregate_store.h
new file mode 100644
index 0000000..67e3fb7
--- /dev/null
+++ b/src/local_aggregation/aggregate_store.h
@@ -0,0 +1,295 @@
+// 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.
+
+#ifndef COBALT_SRC_LOCAL_AGGREGATION_AGGREGATE_STORE_H_
+#define COBALT_SRC_LOCAL_AGGREGATION_AGGREGATE_STORE_H_
+
+#include <condition_variable>
+#include <memory>
+#include <string>
+#include <thread>
+
+#include "src/lib/util/consistent_proto_store.h"
+#include "src/lib/util/protected_fields.h"
+#include "src/local_aggregation/local_aggregation.pb.h"
+#include "src/logger/encoder.h"
+#include "src/logger/observation_writer.h"
+#include "src/logger/project_context.h"
+#include "src/logger/status.h"
+
+namespace cobalt {
+
+// Forward declaration used for friend tests. This will be removed.
+// TODO(ninai): remove this
+namespace logger {
+
+class TestEventAggregator;
+
+} // namespace logger
+
+namespace local_aggregation {
+
+const std::chrono::hours kOneDay(24);
+
+// Maximum value of |backfill_days| allowed by the constructor.
+constexpr size_t kMaxAllowedBackfillDays = 1000;
+// All aggregation windows larger than this number of days are ignored.
+constexpr uint32_t kMaxAllowedAggregationDays = 365;
+// All hourly aggregation windows larger than this number of hours are ignored.
+constexpr uint32_t kMaxAllowedAggregationHours = 23;
+
+// The current version number of the LocalAggregateStore.
+constexpr uint32_t kCurrentLocalAggregateStoreVersion = 1;
+// The current version number of the AggregatedObservationHistoryStore.
+constexpr uint32_t kCurrentObservationHistoryStoreVersion = 0;
+
+// The AggregateStore manages an in-memory store of aggregated Event values, indexed by report,
+// day index, and other dimensions specific to the report type (e.g. event code).
+//
+// When GenerateObservations() is called, this data is used to generate Observations representing
+// aggregates of Event values over a day, week, month, etc.
+//
+// This class also exposes a GarbageCollect*() and Backup*() functionality which deletes
+// unnecessary data and backs up the store respectively.
+class AggregateStore {
+ public:
+ // Constructs an AggregateStore.
+ //
+ // An AggregateStore maintains daily aggregates of Events in a
+ // LocalAggregateStore, uses an Encoder to generate Observations for rolling
+ // windows ending on a specified day index, and writes the Observations to
+ // an ObservationStore using an ObservationWriter.
+ //
+ // encoder: the singleton instance of an Encoder on the system.
+ //
+ // local_aggregate_proto_store: A ConsistentProtoStore to be used by the
+ // AggregateStore to store snapshots of its in-memory store of event
+ // aggregates.
+ //
+ // obs_history_proto_store: A ConsistentProtoStore to be used by the
+ // AggregateStore to store snapshots of its in-memory history of generated
+ // Observations.
+ //
+ // backfill_days: the number of past days for which the AggregateStore
+ // generates and sends Observations, in addition to a requested day index.
+ // See the comment above GenerateObservations for more detail. The constructor CHECK-fails if a
+ // value larger than |kMaxAllowedBackfillDays| is passed.
+ AggregateStore(const logger::Encoder* encoder,
+ const logger::ObservationWriter* observation_writer,
+ util::ConsistentProtoStore* local_aggregate_proto_store,
+ util::ConsistentProtoStore* obs_history_proto_store, size_t backfill_days = 0);
+
+ // Given a ProjectContext, MetricDefinition, and ReportDefinition and a pointer
+ // to the LocalAggregateStore, checks whether a key with the same customer,
+ // project, metric, and report ID already exists in the LocalAggregateStore. If
+ // not, creates and inserts a new key and value. Returns kInvalidArguments if
+ // creation of the key or value fails, and kOK otherwise. The caller should hold
+ // the mutex protecting the LocalAggregateStore.
+ logger::Status MaybeInsertReportConfigLocked(const logger::ProjectContext& project_context,
+ const MetricDefinition& metric,
+ const ReportDefinition& report,
+ LocalAggregateStore* store);
+
+ // Writes a snapshot of the LocalAggregateStore to
+ // |local_aggregate_proto_store_|.
+ logger::Status BackUpLocalAggregateStore();
+
+ // Writes a snapshot of |obs_history_|to |obs_history_proto_store_|.
+ logger::Status BackUpObservationHistory();
+
+ // Removes from the LocalAggregateStore all daily aggregates that are too
+ // old to contribute to their parent report's largest rolling window on the
+ // day which is |backfill_days| before |day_index_utc| (if the parent
+ // MetricDefinitions' time zone policy is UTC) or which is |backfill_days|
+ // before |day_index_local| (if the parent MetricDefinition's time zone policy
+ // is LOCAL). If |day_index_local| is 0, then we set |day_index_local| =
+ // |day_index_utc|.
+ //
+ // If the time zone policy of a report's parent metric is UTC (resp., LOCAL)
+ // and if day_index is the largest value of the |day_index_utc| (resp.,
+ // |day_index_local|) argument with which GarbageCollect() has been called,
+ // then the LocalAggregateStore contains the data needed to generate
+ // Observations for that report for day index (day_index + k) for any k >= 0.
+ logger::Status GarbageCollect(uint32_t day_index_utc, uint32_t day_index_local = 0u);
+
+ // Generates one or more Observations for all of the registered locally
+ // aggregated reports known to this AggregateStore, for rolling windows
+ // ending on specified day indices.
+ //
+ // Each MetricDefinition specifies a time zone policy, which determines the
+ // day index for which an Event associated with that MetricDefinition is
+ // logged. For all MetricDefinitions whose Events are logged with respect to
+ // UTC, this method generates Observations for rolling windows ending on
+ // |final_day_index_utc|. For all MetricDefinitions whose Events are logged
+ // with respect to local time, this method generates Observations for rolling
+ // windows ending on |final_day_index_local|. If |final_day_index_local| is
+ // 0, then we set |final_day_index_local| = |final_day_index_utc|.
+ //
+ // The generated Observations are written to the |observation_writer| passed
+ // to the constructor.
+ //
+ // This class maintains a history of generated Observations and this method
+ // additionally performs backfill: Observations are also generated for
+ // rolling windows ending on any day in the interval [final_day_index -
+ // backfill_days, final_day_index] (where final_day_index is either
+ // final_day_index_utc or final_day_index_local, depending on the time zone
+ // policy of the associated MetricDefinition), if this history indicates they
+ // were not previously generated. Does not generate duplicate Observations
+ // when called multiple times with the same day index.
+ //
+ // Observations are not generated for aggregation windows larger than
+ // |kMaxAllowedAggregationDays|. Hourly windows are not yet supported.
+ logger::Status GenerateObservations(uint32_t final_day_index_utc,
+ uint32_t final_day_index_local = 0u);
+
+ private:
+ friend class EventAggregator; // used for transition during redesign.
+ friend class AggregateStoreTest;
+ friend class EventAggregatorTest;
+ friend class EventAggregatorManagerTest;
+ friend class logger::TestEventAggregator;
+
+ // Make a LocalAggregateStore which is empty except that its version number is set to |version|.
+ LocalAggregateStore MakeNewLocalAggregateStore(
+ uint32_t version = kCurrentLocalAggregateStoreVersion);
+
+ // Make an AggregatedObservationHistoryStore which is empty except that its version number is set
+ // to |version|.
+ AggregatedObservationHistoryStore MakeNewObservationHistoryStore(
+ uint32_t version = kCurrentObservationHistoryStoreVersion);
+
+ // The LocalAggregateStore or AggregatedObservationHistoryStore may need to be changed in ways
+ // which are structurally but not semantically backwards-compatible. In other words, the meaning
+ // to the AggregateStore of a field in the LocalAggregateStore might change. An example is that
+ // we might deprecate one field while introducing a new one.
+ //
+ // The MaybeUpgrade*Store methods allow the AggregateStore to update the contents of its stored
+ // protos from previously meaningful values to currently meaningful values. (For example, a
+ // possible implementation would move the contents of a deprecated field to the replacement
+ // field.)
+ //
+ // These methods are called by the AggregateStore constructor immediately after reading in stored
+ // protos from disk in order to ensure that proto contents have the expected semantics.
+ //
+ // The method first checks the version number of the store. If the version number is equal to
+ // |kCurrentLocalAggregateStoreVersion| or |kCurrentObservationHistoryStoreVersion|
+ // (respectively), returns an OK status. Otherwise, if it is possible to upgrade the store to the
+ // current version, does so and returns an OK status. If not, logs an error and returns
+ // kInvalidArguments. If a non-OK status is returned, the caller should discard the contents of
+ // |store| and replace it with an empty store at the current version. The MakeNew*Store() methods
+ // may be used to create the new store.
+ logger::Status MaybeUpgradeLocalAggregateStore(LocalAggregateStore* store);
+ logger::Status MaybeUpgradeObservationHistoryStore(AggregatedObservationHistoryStore* store);
+
+ // Returns the most recent day index for which an Observation was generated
+ // for a given UNIQUE_N_DAY_ACTIVES report, event code, and day-based aggregation window,
+ // according to |obs_history_|. Returns 0 if no Observation has been generated
+ // for the given arguments.
+ uint32_t UniqueActivesLastGeneratedDayIndex(const std::string& report_key, uint32_t event_code,
+ uint32_t aggregation_days) const;
+
+ // Returns the most recent day index for which an Observation was generated for a given
+ // PER_DEVICE_NUMERIC_STATS or PER_DEVICE_HISTOGRAM report, component, event code, and day-based
+ // aggregation window, according to |obs_history_|. Returns 0 if no Observation has been generated
+ // for the given arguments.
+ uint32_t PerDeviceNumericLastGeneratedDayIndex(const std::string& report_key,
+ const std::string& component, uint32_t event_code,
+ uint32_t aggregation_days) const;
+
+ // Returns the most recent day index for which a
+ // ReportParticipationObservation was generated for a given report, according
+ // to |obs_history_|. Returns 0 if no Observation has been generated for the
+ // given arguments.
+ uint32_t ReportParticipationLastGeneratedDayIndex(const std::string& report_key) const;
+
+ // For a fixed report of type UNIQUE_N_DAY_ACTIVES, generates an Observation
+ // for each event code of the parent metric, for each day-based aggregation window of the
+ // report ending on |final_day_index|, unless an Observation with those parameters was generated
+ // in the past. Also generates Observations for days in the backfill period if needed. Writes the
+ // Observations to an ObservationStore via the ObservationWriter that was passed to the
+ // constructor.
+ //
+ // Observations are not generated for aggregation windows larger than
+ // |kMaxAllowedAggregationDays|. Hourly windows are not yet supported.
+ logger::Status GenerateUniqueActivesObservations(logger::MetricRef metric_ref,
+ const std::string& report_key,
+ const ReportAggregates& report_aggregates,
+ uint32_t num_event_codes,
+ uint32_t final_day_index);
+
+ // Helper method called by GenerateUniqueActivesObservations() to generate
+ // and write a single Observation.
+ logger::Status GenerateSingleUniqueActivesObservation(logger::MetricRef metric_ref,
+ const ReportDefinition* report,
+ uint32_t obs_day_index, uint32_t event_code,
+ const OnDeviceAggregationWindow& window,
+ bool was_active) const;
+
+ // For a fixed report of type PER_DEVICE_NUMERIC_STATS or PER_DEVICE_HISTOGRAM, generates a
+ // PerDeviceNumericObservation and PerDeviceHistogramObservation respectively for each
+ // tuple (component, event code, aggregation_window) for which a numeric event was logged for that
+ // event code and component during the window of that size ending on |final_day_index|, unless an
+ // Observation with those parameters has been generated in the past. The value of the Observation
+ // is the sum, max, or min (depending on the aggregation_type field of the report definition) of
+ // all numeric events logged for that report during the window. Also generates observations for
+ // days in the backfill period if needed.
+ //
+ // In addition to PerDeviceNumericObservations or PerDeviceHistogramObservation , generates
+ // a ReportParticipationObservation for |final_day_index| and any needed days in the backfill
+ // period. These ReportParticipationObservations are used by the report generators to infer the
+ // fleet-wide number of devices for which the sum of numeric events associated to each tuple
+ // (component, event code, window size) was zero.
+ //
+ // Observations are not generated for aggregation windows larger than
+ // |kMaxAllowedAggregationWindowSize|.
+ logger::Status GenerateObsFromNumericAggregates(logger::MetricRef metric_ref,
+ const std::string& report_key,
+ const ReportAggregates& report_aggregates,
+ uint32_t final_day_index);
+
+ // Helper method called by GenerateObsFromNumericAggregates() to generate and write a single
+ // Observation with value |value|. The method will produce a PerDeviceNumericObservation or
+ // PerDeviceHistogramObservation depending on whether the report type is
+ // PER_DEVICE_NUMERIC_STATS or PER_DEVICE_HISTOGRAM respectively.
+ logger::Status GenerateSinglePerDeviceNumericObservation(
+ logger::MetricRef metric_ref, const ReportDefinition* report, uint32_t obs_day_index,
+ const std::string& component, uint32_t event_code, const OnDeviceAggregationWindow& window,
+ int64_t value) const;
+
+ // Helper method called by GenerateObsFromNumericAggregates() to generate and write a single
+ // Observation with value |value|.
+ logger::Status GenerateSinglePerDeviceHistogramObservation(
+ logger::MetricRef metric_ref, const ReportDefinition* report, uint32_t obs_day_index,
+ const std::string& component, uint32_t event_code, const OnDeviceAggregationWindow& window,
+ int64_t value) const;
+
+ // Helper method called by GenerateObsFromNumericAggregates() to generate and write a single
+ // ReportParticipationObservation.
+ logger::Status GenerateSingleReportParticipationObservation(logger::MetricRef metric_ref,
+ const ReportDefinition* report,
+ uint32_t obs_day_index) const;
+
+ LocalAggregateStore CopyLocalAggregateStore() {
+ auto local_aggregate_store = protected_aggregate_store_.lock()->local_aggregate_store;
+ return local_aggregate_store;
+ }
+
+ struct AggregateStoreFields {
+ LocalAggregateStore local_aggregate_store;
+ };
+
+ const logger::Encoder* encoder_;
+ const logger::ObservationWriter* observation_writer_;
+ util::ConsistentProtoStore* local_aggregate_proto_store_;
+ util::ConsistentProtoStore* obs_history_proto_store_;
+ util::ProtectedFields<AggregateStoreFields> protected_aggregate_store_;
+ // Not protected by a mutex. Should only be accessed by the Event Aggregator's |worker_thread_|.
+ AggregatedObservationHistoryStore obs_history_;
+ size_t backfill_days_ = 0;
+};
+
+} // namespace local_aggregation
+} // namespace cobalt
+
+#endif // COBALT_SRC_LOCAL_AGGREGATION_AGGREGATE_STORE_H_
diff --git a/src/local_aggregation/aggregate_store_test.cc b/src/local_aggregation/aggregate_store_test.cc
new file mode 100644
index 0000000..fc3425f
--- /dev/null
+++ b/src/local_aggregation/aggregate_store_test.cc
@@ -0,0 +1,3357 @@
+// Copyright 2019 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/local_aggregation/aggregate_store.h"
+
+#include <algorithm>
+#include <map>
+#include <memory>
+#include <set>
+#include <utility>
+#include <vector>
+
+#include <google/protobuf/io/coded_stream.h>
+#include <google/protobuf/io/zero_copy_stream_impl_lite.h>
+
+#include "src/lib/util/clock.h"
+#include "src/lib/util/datetime_util.h"
+#include "src/lib/util/proto_util.h"
+#include "src/local_aggregation/aggregation_utils.h"
+#include "src/local_aggregation/event_aggregator.h"
+#include "src/logger/logger_test_utils.h"
+#include "src/logger/testing_constants.h"
+#include "src/pb/event.pb.h"
+#include "src/registry/packed_event_codes.h"
+#include "src/registry/project_configs.h"
+#include "third_party/googletest/googletest/include/gtest/gtest.h"
+
+namespace cobalt {
+
+using config::PackEventCodes;
+using encoder::ClientSecret;
+using encoder::SystemDataInterface;
+using logger::Encoder;
+using logger::EventRecord;
+using logger::kInvalidArguments;
+using logger::kOK;
+using logger::MetricReportId;
+using logger::ObservationWriter;
+using logger::ProjectContext;
+using logger::Status;
+using logger::testing::CheckPerDeviceNumericObservations;
+using logger::testing::CheckUniqueActivesObservations;
+using logger::testing::ExpectedAggregationParams;
+using logger::testing::ExpectedPerDeviceNumericObservations;
+using logger::testing::ExpectedReportParticipationObservations;
+using logger::testing::ExpectedUniqueActivesObservations;
+using logger::testing::FakeObservationStore;
+using logger::testing::FetchAggregatedObservations;
+using logger::testing::GetTestProject;
+using logger::testing::MakeAggregationKey;
+using logger::testing::MakeExpectedReportParticipationObservations;
+using logger::testing::MakeNullExpectedUniqueActivesObservations;
+using logger::testing::MockConsistentProtoStore;
+using logger::testing::TestUpdateRecipient;
+using util::EncryptedMessageMaker;
+using util::IncrementingSteadyClock;
+using util::IncrementingSystemClock;
+using util::SerializeToBase64;
+using util::TimeToDayIndex;
+
+namespace local_aggregation {
+
+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;
+
+template <typename T>
+std::string SerializeAsStringDeterministic(const T& message) {
+ std::string s;
+ {
+ google::protobuf::io::StringOutputStream output(&s);
+ google::protobuf::io::CodedOutputStream out(&output);
+ out.SetSerializationDeterministic(true);
+ message.SerializePartialToCodedStream(&out);
+ }
+ return s;
+}
+
+// Filenames for constructors of ConsistentProtoStores
+constexpr char kAggregateStoreFilename[] = "local_aggregate_store_backup";
+constexpr char kObsHistoryFilename[] = "obs_history_backup";
+
+// A map keyed by base64-encoded, serialized ReportAggregationKeys. The value at
+// a key is a map of event codes to sets of day indices. Used in tests as
+// a record, external to the LocalAggregateStore, of the activity logged for
+// UNIQUE_N_DAY_ACTIVES reports.
+using LoggedActivity = std::map<std::string, std::map<uint32_t, std::set<uint32_t>>>;
+
+// A map used in tests as a record, external to the LocalAggregateStore, of the
+// activity logged for PER_DEVICE_NUMERIC_STATS reports. The keys are, in
+// descending order, serialized ReportAggregationKeys, components, event codes,
+// and day indices. Each day index maps to a vector of numeric values that were
+// logged for that day index..
+using LoggedValues =
+ std::map<std::string,
+ std::map<std::string, std::map<uint32_t, std::map<uint32_t, std::vector<int64_t>>>>>;
+
+} // namespace
+
+// AggregateStoreTest creates an EventAggregator which sends its Observations
+// to a FakeObservationStore. The EventAggregator is not pre-populated with
+// aggregation configurations.
+class AggregateStoreTest : public ::testing::Test {
+ protected:
+ void SetUp() override {
+ 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());
+ encoder_ = std::make_unique<Encoder>(ClientSecret::GenerateNewSecret(), system_data_.get());
+ local_aggregate_proto_store_ =
+ std::make_unique<MockConsistentProtoStore>(kAggregateStoreFilename);
+ obs_history_proto_store_ = std::make_unique<MockConsistentProtoStore>(kObsHistoryFilename);
+ ResetEventAggregator();
+ }
+
+ void ResetEventAggregator() {
+ event_aggregator_ = std::make_unique<EventAggregator>(encoder_.get(), observation_writer_.get(),
+ local_aggregate_proto_store_.get(),
+ obs_history_proto_store_.get());
+ // Pass this clock to the EventAggregator::Start method, if it is called.
+ test_clock_ = std::make_unique<IncrementingSystemClock>(std::chrono::system_clock::duration(0));
+ // Initilize it to 10 years after the beginning of time.
+ test_clock_->set_time(std::chrono::system_clock::time_point(std::chrono::seconds(10 * kYear)));
+ // Use this to advance the clock in the tests.
+ unowned_test_clock_ = test_clock_.get();
+ day_store_created_ = CurrentDayIndex();
+ test_steady_clock_ = new IncrementingSteadyClock(std::chrono::system_clock::duration(0));
+ event_aggregator_->SetSteadyClock(test_steady_clock_);
+ }
+
+ // Destruct the EventAggregator (thus calling EventAggregator::ShutDown())
+ // before destructing the objects which the EventAggregator points to but does
+ // not own.
+ void TearDown() override { event_aggregator_.reset(); }
+
+ // Advances |test_clock_| by |num_seconds| seconds.
+ void AdvanceClock(int num_seconds) {
+ unowned_test_clock_->increment_by(std::chrono::seconds(num_seconds));
+ test_steady_clock_->increment_by(std::chrono::seconds(num_seconds));
+ }
+
+ // Returns the day index of the current day according to |test_clock_|, in
+ // |time_zone|, without incrementing the clock.
+ uint32_t CurrentDayIndex(MetricDefinition::TimeZonePolicy time_zone = MetricDefinition::UTC) {
+ return TimeToDayIndex(std::chrono::system_clock::to_time_t(unowned_test_clock_->peek_now()),
+ time_zone);
+ }
+
+ size_t GetBackfillDays() { return event_aggregator_->aggregate_store_->backfill_days_; }
+
+ void SetBackfillDays(size_t num_days) {
+ event_aggregator_->aggregate_store_->backfill_days_ = num_days;
+ }
+
+ Status BackUpLocalAggregateStore() {
+ return event_aggregator_->aggregate_store_->BackUpLocalAggregateStore();
+ }
+
+ Status BackUpObservationHistory() {
+ return event_aggregator_->aggregate_store_->BackUpObservationHistory();
+ }
+
+ LocalAggregateStore MakeNewLocalAggregateStore(
+ uint32_t version = kCurrentLocalAggregateStoreVersion) {
+ return event_aggregator_->aggregate_store_->MakeNewLocalAggregateStore(version);
+ }
+
+ AggregatedObservationHistoryStore MakeNewObservationHistoryStore(
+ uint32_t version = kCurrentObservationHistoryStoreVersion) {
+ return event_aggregator_->aggregate_store_->MakeNewObservationHistoryStore(version);
+ }
+
+ Status MaybeUpgradeLocalAggregateStore(LocalAggregateStore* store) {
+ return event_aggregator_->aggregate_store_->MaybeUpgradeLocalAggregateStore(store);
+ }
+
+ Status MaybeUpgradeObservationHistoryStore(AggregatedObservationHistoryStore* store) {
+ return event_aggregator_->aggregate_store_->MaybeUpgradeObservationHistoryStore(store);
+ }
+
+ LocalAggregateStore CopyLocalAggregateStore() {
+ return event_aggregator_->aggregate_store_->CopyLocalAggregateStore();
+ }
+
+ Status GenerateObservations(uint32_t final_day_index_utc, uint32_t final_day_index_local = 0u) {
+ return event_aggregator_->GenerateObservationsNoWorker(final_day_index_utc,
+ final_day_index_local);
+ }
+
+ Status GarbageCollect(uint32_t day_index_utc, uint32_t day_index_local = 0u) {
+ return event_aggregator_->aggregate_store_->GarbageCollect(day_index_utc, day_index_local);
+ }
+
+ void DoScheduledTasksNow() {
+ // Steady values don't matter, just tell DoScheduledTasks to run everything.
+ auto steady_time = std::chrono::steady_clock::now();
+ event_aggregator_->next_generate_obs_ = steady_time;
+ event_aggregator_->next_gc_ = steady_time;
+ event_aggregator_->DoScheduledTasks(unowned_test_clock_->now(), steady_time);
+ }
+
+ // Clears the FakeObservationStore and resets the counts of Observations
+ // received by the FakeObservationStore and the TestUpdateRecipient.
+ void ResetObservationStore() {
+ observation_store_->messages_received.clear();
+ observation_store_->metadata_received.clear();
+ observation_store_->ResetObservationCounter();
+ update_recipient_->invocation_count = 0;
+ }
+
+ // Given a ProjectContext |project_context| and the MetricReportId of a
+ // UNIQUE_N_DAY_ACTIVES report in |project_context|, as well as a day index
+ // and an event code, logs an OccurrenceEvent to the EventAggregator for
+ // that report, day index, and event code. If a non-null LoggedActivity map is
+ // provided, updates the map with information about the logged Event.
+ Status LogUniqueActivesEvent(std::shared_ptr<const ProjectContext> project_context,
+ const MetricReportId& metric_report_id, uint32_t day_index,
+ uint32_t event_code, LoggedActivity* logged_activity = nullptr) {
+ EventRecord event_record(std::move(project_context), metric_report_id.first);
+ event_record.event()->set_day_index(day_index);
+ event_record.event()->mutable_occurrence_event()->set_event_code(event_code);
+ auto status = event_aggregator_->LogUniqueActivesEvent(metric_report_id.second, event_record);
+ if (logged_activity == nullptr) {
+ return status;
+ }
+ std::string key;
+ if (!SerializeToBase64(MakeAggregationKey(*event_record.project_context(), metric_report_id),
+ &key)) {
+ return kInvalidArguments;
+ }
+ (*logged_activity)[key][event_code].insert(day_index);
+ return status;
+ }
+
+ // Given a ProjectContext |project_context| and the MetricReportId of an
+ // EVENT_COUNT metric with a PER_DEVICE_NUMERIC_STATS report in
+ // |project_context|, as well as a day index, a component string, and an event
+ // code, logs a CountEvent to the EventAggregator for that report, day
+ // index, component, and event code. If a non-null LoggedValues map is
+ // provided, updates the map with information about the logged Event.
+ Status LogPerDeviceCountEvent(std::shared_ptr<const ProjectContext> project_context,
+ const MetricReportId& metric_report_id, uint32_t day_index,
+ const std::string& component, uint32_t event_code, int64_t count,
+ LoggedValues* logged_values = nullptr) {
+ EventRecord event_record(std::move(project_context), metric_report_id.first);
+ event_record.event()->set_day_index(day_index);
+ auto count_event = event_record.event()->mutable_count_event();
+ count_event->set_component(component);
+ count_event->add_event_code(event_code);
+ count_event->set_count(count);
+ auto status = event_aggregator_->LogCountEvent(metric_report_id.second, event_record);
+ if (logged_values == nullptr) {
+ return status;
+ }
+ std::string key;
+ if (!SerializeToBase64(MakeAggregationKey(*event_record.project_context(), metric_report_id),
+ &key)) {
+ return kInvalidArguments;
+ }
+ (*logged_values)[key][component][event_code][day_index].push_back(count);
+ return status;
+ }
+
+ // Given a ProjectContext |project_context| and the MetricReportId of an
+ // ELAPSED_TIME metric with a PER_DEVICE_NUMERIC_STATS report in
+ // |project_context|, as well as a day index, a component string, and an event
+ // code, logs an ElapsedTimeEvent to the EventAggregator for that report, day
+ // index, component, and event code. If a non-null LoggedValues map is
+ // provided, updates the map with information about the logged Event.
+ Status LogPerDeviceElapsedTimeEvent(std::shared_ptr<const ProjectContext> project_context,
+ const MetricReportId& metric_report_id, uint32_t day_index,
+ const std::string& component, uint32_t event_code,
+ int64_t micros, LoggedValues* logged_values = nullptr) {
+ EventRecord event_record(std::move(project_context), metric_report_id.first);
+ event_record.event()->set_day_index(day_index);
+ auto elapsed_time_event = event_record.event()->mutable_elapsed_time_event();
+ elapsed_time_event->set_component(component);
+ elapsed_time_event->add_event_code(event_code);
+ elapsed_time_event->set_elapsed_micros(micros);
+ auto status = event_aggregator_->LogElapsedTimeEvent(metric_report_id.second, event_record);
+ if (logged_values == nullptr) {
+ return status;
+ }
+ std::string key;
+ if (!SerializeToBase64(MakeAggregationKey(*event_record.project_context(), metric_report_id),
+ &key)) {
+ return kInvalidArguments;
+ }
+ (*logged_values)[key][component][event_code][day_index].push_back(micros);
+ return status;
+ }
+
+ // Given a ProjectContext |project_context| and the MetricReportId of a
+ // FRAME_RATE metric with a PER_DEVICE_NUMERIC_STATS report in
+ // |project_context|, as well as a day index, a component string, and an event
+ // code, logs a FrameRateEvent to the EventAggregator for that report, day
+ // index, component, and event code. If a non-null LoggedValues map is
+ // provided, updates the map with information about the logged Event.
+ Status LogPerDeviceFrameRateEvent(std::shared_ptr<const ProjectContext> project_context,
+ const MetricReportId& metric_report_id, uint32_t day_index,
+ const std::string& component, uint32_t event_code, float fps,
+ LoggedValues* logged_values = nullptr) {
+ EventRecord event_record(std::move(project_context), metric_report_id.first);
+ event_record.event()->set_day_index(day_index);
+ auto frame_rate_event = event_record.event()->mutable_frame_rate_event();
+ frame_rate_event->set_component(component);
+ frame_rate_event->add_event_code(event_code);
+ int64_t frames_per_1000_seconds = std::round(fps * 1000.0);
+ frame_rate_event->set_frames_per_1000_seconds(frames_per_1000_seconds);
+ auto status = event_aggregator_->LogFrameRateEvent(metric_report_id.second, event_record);
+ if (logged_values == nullptr) {
+ return status;
+ }
+ std::string key;
+ if (!SerializeToBase64(MakeAggregationKey(*event_record.project_context(), metric_report_id),
+ &key)) {
+ return kInvalidArguments;
+ }
+ (*logged_values)[key][component][event_code][day_index].push_back(frames_per_1000_seconds);
+ return status;
+ }
+
+ // Given a ProjectContext |project_context| and the MetricReportId of a
+ // MEMORY_USAGE metric with a PER_DEVICE_NUMERIC_STATS report in
+ // |project_context|, as well as a day index, a component string, and an event
+ // code, logs a MemoryUsageEvent to the EventAggregator for that report, day
+ // index, component, and event code. If a non-null LoggedValues map is
+ // provided, updates the map with information about the logged Event.
+ Status LogPerDeviceMemoryUsageEvent(std::shared_ptr<const ProjectContext> project_context,
+ const MetricReportId& metric_report_id, uint32_t day_index,
+ const std::string& component,
+ const std::vector<uint32_t>& event_codes, int64_t bytes,
+ LoggedValues* logged_values = nullptr) {
+ EventRecord event_record(std::move(project_context), metric_report_id.first);
+ event_record.event()->set_day_index(day_index);
+ auto memory_usage_event = event_record.event()->mutable_memory_usage_event();
+ memory_usage_event->set_component(component);
+ for (auto event_code : event_codes) {
+ memory_usage_event->add_event_code(event_code);
+ }
+ memory_usage_event->set_bytes(bytes);
+ auto status = event_aggregator_->LogMemoryUsageEvent(metric_report_id.second, event_record);
+ if (logged_values == nullptr) {
+ return status;
+ }
+ std::string key;
+ if (!SerializeToBase64(MakeAggregationKey(*event_record.project_context(), metric_report_id),
+ &key)) {
+ return kInvalidArguments;
+ }
+ (*logged_values)[key][component][PackEventCodes(event_codes)][day_index].push_back(bytes);
+ return status;
+ }
+
+ // Given a LoggedActivity map describing the events that have been logged
+ // to the EventAggregator, checks whether the contents of the
+ // LocalAggregateStore are as expected, accounting for any garbage
+ // collection.
+ //
+ // logged_activity: a LoggedActivity representing event occurrences
+ // since the LocalAggregateStore was created. All day indices should be
+ // greater than or equal to |day_store_created_| and less than or equal to
+ // |current_day_index|.
+ //
+ // current_day_index: The day index of the current day in the test's frame
+ // of reference.
+ bool CheckUniqueActivesAggregates(const LoggedActivity& logged_activity,
+ uint32_t /*current_day_index*/) {
+ auto local_aggregate_store = event_aggregator_->aggregate_store_->CopyLocalAggregateStore();
+ // Check that the LocalAggregateStore contains no more UniqueActives
+ // aggregates than |logged_activity| and |day_last_garbage_collected_|
+ // should imply.
+ for (const auto& report_pair : local_aggregate_store.by_report_key()) {
+ const auto& aggregates = report_pair.second;
+ if (aggregates.type_case() != ReportAggregates::kUniqueActivesAggregates) {
+ continue;
+ }
+ const auto& report_key = report_pair.first;
+ // Check whether this ReportAggregationKey is in |logged_activity|. If
+ // not, expect that its by_event_code map is empty.
+ auto report_activity = logged_activity.find(report_key);
+ if (report_activity == logged_activity.end()) {
+ EXPECT_TRUE(aggregates.unique_actives_aggregates().by_event_code().empty());
+ if (!aggregates.unique_actives_aggregates().by_event_code().empty()) {
+ return false;
+ }
+ break;
+ }
+ auto expected_events = report_activity->second;
+ for (const auto& event_pair : aggregates.unique_actives_aggregates().by_event_code()) {
+ // Check that this event code is in |logged_activity| under this
+ // ReportAggregationKey.
+ auto event_code = event_pair.first;
+ auto event_activity = expected_events.find(event_code);
+ EXPECT_NE(event_activity, expected_events.end());
+ if (event_activity == expected_events.end()) {
+ return false;
+ }
+ const auto& expected_days = event_activity->second;
+ for (const auto& day_pair : event_pair.second.by_day_index()) {
+ // Check that this day index is in |logged_activity| under this
+ // ReportAggregationKey and event code.
+ const auto& day_index = day_pair.first;
+ auto day_activity = expected_days.find(day_index);
+ EXPECT_NE(day_activity, expected_days.end());
+ if (day_activity == expected_days.end()) {
+ return false;
+ }
+ // Check that the day index is no earlier than is implied by the
+ // dates of store creation and garbage collection.
+ EXPECT_GE(day_index, EarliestAllowedDayIndex(aggregates.aggregation_config()));
+ if (day_index < EarliestAllowedDayIndex(aggregates.aggregation_config())) {
+ return false;
+ }
+ }
+ }
+ }
+
+ // Check that the LocalAggregateStore contains aggregates for all events in
+ // |logged_activity|, as long as they are recent enough to have survived any
+ // garbage collection.
+ for (const auto& logged_pair : logged_activity) {
+ const auto& logged_key = logged_pair.first;
+ const auto& logged_event_map = logged_pair.second;
+ // Check that this ReportAggregationKey is in the LocalAggregateStore, and
+ // that the aggregates are of the expected type.
+ auto report_aggregates = local_aggregate_store.by_report_key().find(logged_key);
+ EXPECT_NE(report_aggregates, local_aggregate_store.by_report_key().end());
+ if (report_aggregates == local_aggregate_store.by_report_key().end()) {
+ return false;
+ }
+ if (report_aggregates->second.type_case() != ReportAggregates::kUniqueActivesAggregates) {
+ return false;
+ }
+ // Compute the earliest day index that should appear among the aggregates
+ // for this report.
+ auto earliest_allowed =
+ EarliestAllowedDayIndex(report_aggregates->second.aggregation_config());
+ for (const auto& logged_event_pair : logged_event_map) {
+ const auto& logged_event_code = logged_event_pair.first;
+ const auto& logged_days = logged_event_pair.second;
+ // Check whether this event code is in the LocalAggregateStore
+ // under this ReportAggregationKey. If not, check that all day indices
+ // for this event code are smaller than the day index of the earliest
+ // allowed aggregate.
+ auto event_code_aggregates =
+ report_aggregates->second.unique_actives_aggregates().by_event_code().find(
+ logged_event_code);
+ if (event_code_aggregates ==
+ report_aggregates->second.unique_actives_aggregates().by_event_code().end()) {
+ for (auto day_index : logged_days) {
+ EXPECT_LT(day_index, earliest_allowed);
+ if (day_index >= earliest_allowed) {
+ return false;
+ }
+ }
+ break;
+ }
+ // Check that all of the day indices in |logged_activity| under this
+ // ReportAggregationKey and event code are in the
+ // LocalAggregateStore, as long as they are recent enough to have
+ // survived any garbage collection. Check that each aggregate has its
+ // activity field set to true.
+ for (const auto& logged_day_index : logged_days) {
+ auto day_aggregate = event_code_aggregates->second.by_day_index().find(logged_day_index);
+ if (logged_day_index >= earliest_allowed) {
+ EXPECT_NE(day_aggregate, event_code_aggregates->second.by_day_index().end());
+ if (day_aggregate == event_code_aggregates->second.by_day_index().end()) {
+ return false;
+ }
+ EXPECT_TRUE(day_aggregate->second.activity_daily_aggregate().activity_indicator());
+ if (!day_aggregate->second.activity_daily_aggregate().activity_indicator()) {
+ return false;
+ }
+ }
+ }
+ }
+ }
+ return true;
+ }
+
+ bool CheckPerDeviceNumericAggregates(const LoggedValues& logged_values,
+ uint32_t /*current_day_index*/) {
+ auto local_aggregate_store = event_aggregator_->aggregate_store_->CopyLocalAggregateStore();
+ // Check that the LocalAggregateStore contains no more PerDeviceNumeric
+ // aggregates than |logged_values| and |day_last_garbage_collected_| should
+ // imply.
+ for (const auto& report_pair : local_aggregate_store.by_report_key()) {
+ const auto& aggregates = report_pair.second;
+ if (aggregates.type_case() != ReportAggregates::kNumericAggregates) {
+ continue;
+ }
+ const auto& report_key = report_pair.first;
+ // Check whether this ReportAggregationKey is in |logged_values|. If not,
+ // expect that its by_component map is empty.
+ auto report_values = logged_values.find(report_key);
+ if (report_values == logged_values.end()) {
+ EXPECT_TRUE(aggregates.numeric_aggregates().by_component().empty());
+ if (!aggregates.numeric_aggregates().by_component().empty()) {
+ return false;
+ }
+ break;
+ }
+ auto expected_components = report_values->second;
+ for (const auto& component_pair : aggregates.numeric_aggregates().by_component()) {
+ // Check that this component is in |logged_values| under this
+ // ReportAggregationKey.
+ auto component = component_pair.first;
+ auto component_values = expected_components.find(component);
+ EXPECT_NE(component_values, expected_components.end());
+ if (component_values == expected_components.end()) {
+ return false;
+ }
+ const auto& expected_events = component_values->second;
+ for (const auto& event_pair : component_pair.second.by_event_code()) {
+ // Check that this event code is in |logged_values| under this
+ // ReportAggregationKey and component.
+ const auto& event_code = event_pair.first;
+ auto event_values = expected_events.find(event_code);
+ EXPECT_NE(event_values, expected_events.end());
+ if (event_values == expected_events.end()) {
+ return false;
+ }
+ const auto& expected_days = event_values->second;
+ for (const auto& day_pair : event_pair.second.by_day_index()) {
+ // Check that this day index is in |logged_values| under this
+ // ReportAggregationKey, component, and event code.
+ const auto& day_index = day_pair.first;
+ auto day_value = expected_days.find(day_index);
+ EXPECT_NE(day_value, expected_days.end());
+ if (day_value == expected_days.end()) {
+ return false;
+ }
+ // Check that the day index is no earlier than is implied by the
+ // dates of store creation and garbage collection.
+ EXPECT_GE(day_index, EarliestAllowedDayIndex(aggregates.aggregation_config()));
+ if (day_index < EarliestAllowedDayIndex(aggregates.aggregation_config())) {
+ return false;
+ }
+ }
+ }
+ }
+ }
+
+ // Check that the LocalAggregateStore contains aggregates for all values in
+ // |logged_values|, as long as they are recent enough to have survived any
+ // garbage collection.
+ for (const auto& logged_pair : logged_values) {
+ const auto& logged_key = logged_pair.first;
+ const auto& logged_component_map = logged_pair.second;
+ // Check that this ReportAggregationKey is in the LocalAggregateStore, and
+ // that the aggregates are of the expected type.
+ auto report_aggregates = local_aggregate_store.by_report_key().find(logged_key);
+ EXPECT_NE(report_aggregates, local_aggregate_store.by_report_key().end());
+ if (report_aggregates == local_aggregate_store.by_report_key().end()) {
+ return false;
+ }
+ if (report_aggregates->second.type_case() != ReportAggregates::kNumericAggregates) {
+ return false;
+ }
+ const auto& aggregation_type =
+ report_aggregates->second.aggregation_config().report().aggregation_type();
+ // Compute the earliest day index that should appear among the aggregates
+ // for this report.
+ auto earliest_allowed =
+ EarliestAllowedDayIndex(report_aggregates->second.aggregation_config());
+ for (const auto& logged_component_pair : logged_component_map) {
+ const auto& logged_component = logged_component_pair.first;
+ const auto& logged_event_code_map = logged_component_pair.second;
+ // Check whether this component is in the LocalAggregateStore under this
+ // ReportAggregationKey. If not, check that all day indices for all
+ // entries in |logged_values| under this component are smaller than the
+ // day index of the earliest allowed aggregate.
+ bool component_found = false;
+ auto component_aggregates =
+ report_aggregates->second.numeric_aggregates().by_component().find(logged_component);
+ if (component_aggregates !=
+ report_aggregates->second.numeric_aggregates().by_component().end()) {
+ component_found = true;
+ }
+ for (const auto& logged_event_pair : logged_event_code_map) {
+ const auto& logged_event_code = logged_event_pair.first;
+ const auto& logged_day_map = logged_event_pair.second;
+ // Check whether this event code is in the LocalAggregateStore under
+ // this ReportAggregationKey. If not, check that all day indices in
+ // |logged_values| under this component are smaller than the day index
+ // of the earliest allowed aggregate.
+ bool event_code_found = false;
+ if (component_found) {
+ auto event_code_aggregates =
+ component_aggregates->second.by_event_code().find(logged_event_code);
+ if (event_code_aggregates != component_aggregates->second.by_event_code().end()) {
+ event_code_found = true;
+ }
+ if (event_code_found) {
+ // Check that all of the day indices in |logged_values| under this
+ // ReportAggregationKey, component, and event code are in the
+ // LocalAggregateStore, as long as they are recent enough to have
+ // survived any garbage collection. Check that each aggregate has
+ // the expected value.
+ for (const auto& logged_day_pair : logged_day_map) {
+ auto logged_day_index = logged_day_pair.first;
+ auto logged_values = logged_day_pair.second;
+ auto day_aggregate =
+ event_code_aggregates->second.by_day_index().find(logged_day_index);
+ if (logged_day_index >= earliest_allowed) {
+ EXPECT_NE(day_aggregate, event_code_aggregates->second.by_day_index().end());
+ if (day_aggregate == event_code_aggregates->second.by_day_index().end()) {
+ return false;
+ }
+ int64_t aggregate_from_logged_values = 0;
+ for (size_t index = 0; index < logged_values.size(); index++) {
+ switch (aggregation_type) {
+ case ReportDefinition::SUM:
+ aggregate_from_logged_values += logged_values[index];
+ break;
+ case ReportDefinition::MAX:
+ aggregate_from_logged_values =
+ std::max(aggregate_from_logged_values, logged_values[index]);
+ break;
+ case ReportDefinition::MIN:
+ if (index == 0) {
+ aggregate_from_logged_values = logged_values[0];
+ }
+ aggregate_from_logged_values =
+ std::min(aggregate_from_logged_values, logged_values[index]);
+ break;
+ default:
+ return false;
+ }
+ }
+ EXPECT_EQ(day_aggregate->second.numeric_daily_aggregate().value(),
+ aggregate_from_logged_values);
+ if (day_aggregate->second.numeric_daily_aggregate().value() !=
+ aggregate_from_logged_values) {
+ return false;
+ }
+ }
+ }
+ }
+ }
+ if (!component_found | !event_code_found) {
+ for (const auto& logged_day_pair : logged_day_map) {
+ auto logged_day_index = logged_day_pair.first;
+ EXPECT_LT(logged_day_index, earliest_allowed);
+ if (logged_day_index >= earliest_allowed) {
+ return false;
+ }
+ }
+ break;
+ }
+ }
+ }
+ }
+
+ return true;
+ }
+
+ // Given the AggregationConfig of a locally aggregated report, returns the
+ // earliest (smallest) day index for which an aggregate may exist in the
+ // LocalAggregateStore for that report, accounting for garbage
+ // collection and the number of backfill days.
+ uint32_t EarliestAllowedDayIndex(const AggregationConfig& config) {
+ // If the LocalAggregateStore has never been garbage-collected, then the
+ // earliest allowed day index is just the day when the store was created,
+ // minus the number of backfill days.
+ auto backfill_days = GetBackfillDays();
+ EXPECT_GE(day_store_created_, backfill_days)
+ << "The day index of store creation must be larger than the number "
+ "of backfill days.";
+ if (day_last_garbage_collected_ == 0u) {
+ return day_store_created_ - backfill_days;
+ }
+ uint32_t max_aggregation_days = 1;
+ for (const auto& window : config.aggregation_window()) {
+ if (window.units_case() == OnDeviceAggregationWindow::kDays &&
+ window.days() > max_aggregation_days) {
+ max_aggregation_days = window.days();
+ }
+ }
+ // Otherwise, it is the later of:
+ // (a) The day index on which the store was created minus the number
+ // of backfill days.
+ // (b) The day index for which the store was last garbage-collected
+ // minus the number of backfill days, minus the largest window size in
+ // the report associated to |config|, plus 1.
+ EXPECT_GE(day_last_garbage_collected_, backfill_days)
+ << "The day index of last garbage collection must be larger than "
+ "the number of backfill days.";
+
+ if (day_last_garbage_collected_ - backfill_days < (max_aggregation_days + 1)) {
+ return day_store_created_ - backfill_days;
+ }
+ return (day_store_created_ < (day_last_garbage_collected_ - max_aggregation_days + 1))
+ ? (day_last_garbage_collected_ - backfill_days - max_aggregation_days + 1)
+ : day_store_created_ - backfill_days;
+ }
+
+ std::unique_ptr<EventAggregator> event_aggregator_;
+ std::unique_ptr<MockConsistentProtoStore> local_aggregate_proto_store_;
+ std::unique_ptr<MockConsistentProtoStore> obs_history_proto_store_;
+ std::unique_ptr<ObservationWriter> observation_writer_;
+ std::unique_ptr<Encoder> encoder_;
+ std::unique_ptr<EncryptedMessageMaker> observation_encrypter_;
+ std::unique_ptr<TestUpdateRecipient> update_recipient_;
+ std::unique_ptr<FakeObservationStore> observation_store_;
+ std::unique_ptr<IncrementingSystemClock> test_clock_;
+ IncrementingSystemClock* unowned_test_clock_;
+ IncrementingSteadyClock* test_steady_clock_;
+ // The day index on which the LocalAggregateStore was last
+ // garbage-collected. A value of 0 indicates that the store has never been
+ // garbage-collected.
+ uint32_t day_last_garbage_collected_ = 0u;
+ // The day index on which the LocalAggregateStore was created.
+ uint32_t day_store_created_ = 0u;
+
+ private:
+ std::unique_ptr<SystemDataInterface> system_data_;
+};
+
+// Creates an EventAggregator and provides it with a ProjectContext generated
+// from a registry.
+class AggregateStoreTestWithProjectContext : public AggregateStoreTest {
+ protected:
+ explicit AggregateStoreTestWithProjectContext(const std::string& registry_var_name) {
+ project_context_ = GetTestProject(registry_var_name);
+ }
+
+ void SetUp() override {
+ AggregateStoreTest::SetUp();
+ event_aggregator_->UpdateAggregationConfigs(*project_context_);
+ }
+
+ // Logs an OccurrenceEvent for the MetricReportId of a locally
+ // aggregated report of the ProjectContext. Overrides the method
+ // AggregateStoreTest::LogUniqueActivesEvent.
+ Status LogUniqueActivesEvent(const MetricReportId& metric_report_id, uint32_t day_index,
+ uint32_t event_code, LoggedActivity* logged_activity = nullptr) {
+ return AggregateStoreTest::LogUniqueActivesEvent(project_context_, metric_report_id, day_index,
+ event_code, logged_activity);
+ }
+
+ // Logs a CountEvent for the MetricReportId of a locally
+ // aggregated report of the ProjectContext. Overrides the method
+ // AggregateStoreTest::LogPerDeviceCountEvent.
+ Status LogPerDeviceCountEvent(const MetricReportId& metric_report_id, uint32_t day_index,
+ const std::string& component, uint32_t event_code, int64_t count,
+ LoggedValues* logged_values = nullptr) {
+ return AggregateStoreTest::LogPerDeviceCountEvent(project_context_, metric_report_id, day_index,
+ component, event_code, count, logged_values);
+ }
+
+ // Logs an ElapsedTimeEvent for the MetricReportId of a locally
+ // aggregated report of the ProjectContext. Overrides the method
+ // AggregateStoreTest::LogPerDeviceElapsedTimeEvent.
+ Status LogPerDeviceElapsedTimeEvent(const MetricReportId& metric_report_id, uint32_t day_index,
+ const std::string& component, uint32_t event_code,
+ int64_t micros, LoggedValues* logged_values = nullptr) {
+ return AggregateStoreTest::LogPerDeviceElapsedTimeEvent(project_context_, metric_report_id,
+ day_index, component, event_code,
+ micros, logged_values);
+ }
+
+ // Logs a FrameRateEvent for the MetricReportId of a locally
+ // aggregated report of the ProjectContext. Overrides the method
+ // AggregateStoreTest::LogPerDeviceFrameRateEvent.
+ Status LogPerDeviceFrameRateEvent(const MetricReportId& metric_report_id, uint32_t day_index,
+ const std::string& component, uint32_t event_code, float fps,
+ LoggedValues* logged_values = nullptr) {
+ return AggregateStoreTest::LogPerDeviceFrameRateEvent(
+ project_context_, metric_report_id, day_index, component, event_code, fps, logged_values);
+ }
+
+ // Logs a MemoryUsageEvent for the MetricReportId of a locally
+ // aggregated report of the ProjectContext. Overrides the method
+ // AggregateStoreTest::LogPerDeviceMemoryUsageEvent.
+ Status LogPerDeviceMemoryUsageEvent(const MetricReportId& metric_report_id, uint32_t day_index,
+ const std::string& component,
+ const std::vector<uint32_t>& event_codes, int64_t bytes,
+ LoggedValues* logged_values = nullptr) {
+ return AggregateStoreTest::LogPerDeviceMemoryUsageEvent(project_context_, metric_report_id,
+ day_index, component, event_codes,
+ bytes, logged_values);
+ }
+
+ private:
+ // A ProjectContext wrapping the MetricDefinitions passed to the
+ // constructor in |metric_string|.
+ std::shared_ptr<ProjectContext> project_context_;
+};
+
+// Creates an EventAggregator and provides it with a ProjectContext generated
+// from test_registries/unique_actives_test_registry.yaml. All metrics in this
+// registry are of type EVENT_OCCURRED and have a UNIQUE_N_DAY_ACTIVES report.
+class UniqueActivesAggregateStoreTest : public AggregateStoreTestWithProjectContext {
+ protected:
+ UniqueActivesAggregateStoreTest()
+ : AggregateStoreTestWithProjectContext(
+ logger::testing::unique_actives::kCobaltRegistryBase64) {}
+};
+
+// Creates an EventAggregator and provides it with a ProjectContext generated
+// from test_registries/unique_actives_noise_free_test_registry.yaml. All
+// metrics in this registry are of type EVENT_OCCURRED and have a
+// UNIQUE_N_DAY_ACTIVES report with local_privacy_noise_level NONE.
+class UniqueActivesNoiseFreeAggregateStoreTest : public AggregateStoreTestWithProjectContext {
+ protected:
+ UniqueActivesNoiseFreeAggregateStoreTest()
+ : AggregateStoreTestWithProjectContext(
+ logger::testing::unique_actives_noise_free::kCobaltRegistryBase64) {}
+};
+
+// Creates an EventAggregator and provides it with a ProjectContext generated
+// from test_registries/per_device_numeric_stats_test_registry.yaml. All metrics
+// in this registry are of type EVENT_COUNT and have a PER_DEVICE_NUMERIC_STATS
+// report.
+class PerDeviceNumericAggregateStoreTest : public AggregateStoreTestWithProjectContext {
+ protected:
+ PerDeviceNumericAggregateStoreTest()
+ : AggregateStoreTestWithProjectContext(
+ logger::testing::per_device_numeric_stats::kCobaltRegistryBase64) {}
+};
+
+// Creates an EventAggregator and provides it with a ProjectContext generated
+// from test_registries/mixed_time_zone_test_registry.yaml. This registry
+// contains multiple MetricDefinitions with different time zone policies.
+class NoiseFreeMixedTimeZoneAggregateStoreTest : public AggregateStoreTestWithProjectContext {
+ protected:
+ NoiseFreeMixedTimeZoneAggregateStoreTest()
+ : AggregateStoreTestWithProjectContext(
+ logger::testing::mixed_time_zone::kCobaltRegistryBase64) {}
+};
+
+class PerDeviceHistogramAggregateStoreTest : public AggregateStoreTestWithProjectContext {
+ protected:
+ PerDeviceHistogramAggregateStoreTest()
+ : AggregateStoreTestWithProjectContext(
+ logger::testing::per_device_histogram::kCobaltRegistryBase64) {}
+};
+
+class AggregateStoreWorkerTest : public AggregateStoreTest {
+ protected:
+ void SetUp() override { AggregateStoreTest::SetUp(); }
+
+ void ShutDownWorkerThread() { event_aggregator_->ShutDown(); }
+
+ bool in_shutdown_state() { return (shutdown_flag_set() && !worker_joinable()); }
+
+ bool in_run_state() { return (!shutdown_flag_set() && worker_joinable()); }
+
+ bool shutdown_flag_set() {
+ return event_aggregator_->protected_worker_thread_controller_.const_lock()->shut_down;
+ }
+
+ bool worker_joinable() { return event_aggregator_->worker_thread_.joinable(); }
+};
+
+// Tests that the Read() method of each ConsistentProtoStore is called once
+// during construction of the EventAggregator.
+TEST_F(AggregateStoreTest, ReadProtosFromFiles) {
+ EXPECT_EQ(1, local_aggregate_proto_store_->read_count_);
+ EXPECT_EQ(1, obs_history_proto_store_->read_count_);
+}
+
+// Tests that the BackUp*() methods return a positive status, and checks that
+// the Write() method of a ConsistentProtoStore is called once when its
+// respective BackUp*() method is called.
+TEST_F(AggregateStoreTest, BackUpProtos) {
+ EXPECT_EQ(kOK, BackUpLocalAggregateStore());
+ EXPECT_EQ(kOK, BackUpObservationHistory());
+ EXPECT_EQ(1, local_aggregate_proto_store_->write_count_);
+ EXPECT_EQ(1, obs_history_proto_store_->write_count_);
+}
+
+// MaybeUpgradeLocalAggregateStore should return an OK status if the version is current. The store
+// should not change.
+TEST_F(AggregateStoreTest, MaybeUpgradeLocalAggregateStoreCurrent) {
+ auto store = MakeNewLocalAggregateStore();
+ std::string store_before = SerializeAsStringDeterministic(store);
+ ASSERT_EQ(kCurrentLocalAggregateStoreVersion, store.version());
+ EXPECT_EQ(kOK, MaybeUpgradeLocalAggregateStore(&store));
+ EXPECT_EQ(store_before, SerializeAsStringDeterministic(store));
+}
+
+// MaybeUpgradeLocalAggregateStore should return kInvalidArguments if it is not possible to upgrade
+// to the current version.
+TEST_F(AggregateStoreTest, MaybeUpgradeLocalAggregateStoreUnsupported) {
+ const uint32_t kFutureVersion = kCurrentLocalAggregateStoreVersion + 1;
+ auto store = MakeNewLocalAggregateStore(kFutureVersion);
+ ASSERT_EQ(kFutureVersion, store.version());
+ EXPECT_EQ(kInvalidArguments, MaybeUpgradeLocalAggregateStore(&store));
+}
+
+// It should be possible to upgrade the LocalAggregateStore from v0 to the current version. The
+// version number should be updated and the contents of window_size in each AggregationConfigs
+// should be moved to aggregation_window, preserving their order.
+TEST_F(AggregateStoreTest, MaybeUpgradeLocalAggregateStoreFromV0) {
+ const uint32_t kVersionZero = 0;
+ const std::vector<uint32_t> kWindowSizes = {1, 7, 30};
+ const std::string kKey = "some_report_key";
+
+ // Make a v0 LocalAggregateStore with one report.
+ auto store = MakeNewLocalAggregateStore(kVersionZero);
+ ReportAggregates report_aggregates;
+ for (auto window_size : kWindowSizes) {
+ report_aggregates.mutable_aggregation_config()->add_window_size(window_size);
+ }
+ (*store.mutable_by_report_key())[kKey] = report_aggregates;
+
+ // Make the expected upgraded store.
+ auto expected_store = MakeNewLocalAggregateStore(kCurrentLocalAggregateStoreVersion);
+ ReportAggregates expected_report_aggregates;
+ for (auto window_size : kWindowSizes) {
+ *expected_report_aggregates.mutable_aggregation_config()->add_aggregation_window() =
+ MakeDayWindow(window_size);
+ }
+ (*expected_store.mutable_by_report_key())[kKey] = expected_report_aggregates;
+
+ // Upgrade and check that the upgraded store is as expected.
+ EXPECT_EQ(kOK, MaybeUpgradeLocalAggregateStore(&store));
+ EXPECT_EQ(SerializeAsStringDeterministic(expected_store), SerializeAsStringDeterministic(store));
+}
+
+// MaybeUpgradeObservationHistoryStore should return an OK status if the version is current. The
+// store should not change.
+TEST_F(AggregateStoreTest, MaybeUpgradeObservationHistoryStoreCurrent) {
+ auto store = MakeNewObservationHistoryStore();
+ std::string store_before = SerializeAsStringDeterministic(store);
+ ASSERT_EQ(kCurrentObservationHistoryStoreVersion, store.version());
+ EXPECT_EQ(kOK, MaybeUpgradeObservationHistoryStore(&store));
+ EXPECT_EQ(store_before, SerializeAsStringDeterministic(store));
+}
+
+// MaybeUpgradeObservationHistoryStore should return kInvalidArguments if it is not possible to
+// upgrade to the current version.
+TEST_F(AggregateStoreTest, MaybeUpgradeObservationHistoryStoreUnsupported) {
+ const uint32_t kFutureVersion = kCurrentObservationHistoryStoreVersion + 1;
+ auto store = MakeNewObservationHistoryStore(kFutureVersion);
+ ASSERT_EQ(kFutureVersion, store.version());
+ EXPECT_EQ(kInvalidArguments, MaybeUpgradeObservationHistoryStore(&store));
+}
+
+// Tests that EventAggregator::GenerateObservations() returns a positive
+// status and that the expected number of Observations is generated when no
+// Events have been logged to the EventAggregator.
+TEST_F(AggregateStoreTest, GenerateObservationsNoEvents) {
+ // Provide the all_report_types test registry to the EventAggregator.
+ auto project_context = GetTestProject(logger::testing::all_report_types::kCobaltRegistryBase64);
+ EXPECT_EQ(kOK, event_aggregator_->UpdateAggregationConfigs(*project_context));
+ // Generate locally aggregated Observations for the current day index.
+ EXPECT_EQ(kOK, GenerateObservations(CurrentDayIndex()));
+ std::vector<Observation2> observations(0);
+ EXPECT_TRUE(FetchAggregatedObservations(
+ &observations, logger::testing::all_report_types::kExpectedAggregationParams,
+ observation_store_.get(), update_recipient_.get()));
+}
+
+// Tests that EventAggregator::GenerateObservations() only generates
+// Observations the first time it is called for a given day index.
+TEST_F(AggregateStoreTest, GenerateObservationsTwice) {
+ // Provide the all_report_types test registry to the EventAggregator.
+ auto project_context = GetTestProject(logger::testing::all_report_types::kCobaltRegistryBase64);
+ EXPECT_EQ(kOK, event_aggregator_->UpdateAggregationConfigs(*project_context));
+ // Check that Observations are generated when GenerateObservations is called
+ // for the current day index for the first time.
+ auto current_day_index = CurrentDayIndex();
+ EXPECT_EQ(kOK, GenerateObservations(current_day_index));
+ std::vector<Observation2> observations(0);
+ EXPECT_TRUE(FetchAggregatedObservations(
+ &observations, logger::testing::all_report_types::kExpectedAggregationParams,
+ observation_store_.get(), update_recipient_.get()));
+ // Check that no Observations are generated when GenerateObservations is
+ // called for the currentday index for the second time.
+ ResetObservationStore();
+ EXPECT_EQ(kOK, GenerateObservations(current_day_index));
+ EXPECT_EQ(0u, observation_store_->messages_received.size());
+}
+
+// When the LocalAggregateStore contains one ReportAggregates proto and that
+// proto is empty, GenerateObservations should return success but generate no
+// observations.
+TEST_F(AggregateStoreTest, GenerateObservationsFromBadStore) {
+ auto bad_store = std::make_unique<LocalAggregateStore>();
+ (*bad_store->mutable_by_report_key())["some_key"] = ReportAggregates();
+ local_aggregate_proto_store_->set_stored_proto(std::move(bad_store));
+ // Read the bad store in to the EventAggregator.
+ ResetEventAggregator();
+ EXPECT_EQ(kOK, GenerateObservations(CurrentDayIndex()));
+ EXPECT_EQ(0u, observation_store_->messages_received.size());
+}
+
+// When the LocalAggregateStore contains one empty ReportAggregates proto and
+// some valid ReportAggregates, GenerateObservations should produce observations
+// for the valid ReportAggregates.
+TEST_F(AggregateStoreTest, GenerateObservationsFromBadStoreMultiReport) {
+ auto bad_store = std::make_unique<LocalAggregateStore>();
+ (*bad_store->mutable_by_report_key())["some_key"] = ReportAggregates();
+ local_aggregate_proto_store_->set_stored_proto(std::move(bad_store));
+ // Read the bad store in to the EventAggregator.
+ ResetEventAggregator();
+ // Provide the all_report_types test registry to the EventAggregator.
+ auto project_context = GetTestProject(logger::testing::all_report_types::kCobaltRegistryBase64);
+ EXPECT_EQ(kOK, event_aggregator_->UpdateAggregationConfigs(*project_context));
+ EXPECT_EQ(kOK, GenerateObservations(CurrentDayIndex()));
+ std::vector<Observation2> observations(0);
+ EXPECT_TRUE(FetchAggregatedObservations(
+ &observations, logger::testing::all_report_types::kExpectedAggregationParams,
+ observation_store_.get(), update_recipient_.get()));
+}
+
+// When the LocalAggregateStore contains one ReportAggregates proto and that
+// proto is empty, GarbageCollect should return success.
+TEST_F(AggregateStoreTest, GarbageCollectBadStore) {
+ auto bad_store = std::make_unique<LocalAggregateStore>();
+ (*bad_store->mutable_by_report_key())["some_key"] = ReportAggregates();
+ local_aggregate_proto_store_->set_stored_proto(std::move(bad_store));
+ // Read the bad store in to the EventAggregator.
+ ResetEventAggregator();
+ EXPECT_EQ(kOK, GarbageCollect(CurrentDayIndex()));
+}
+
+// Tests GarbageCollect() for UniqueActivesReportAggregates.
+//
+// For each value of N in the range [0, 34], logs some UniqueActivesEvents
+// each day for N consecutive days and then garbage-collects the
+// LocalAggregateStore. After garbage collection, verifies the contents of
+// the LocalAggregateStore.
+TEST_F(UniqueActivesAggregateStoreTest, GarbageCollect) {
+ uint32_t max_days_before_gc = 35;
+ for (uint32_t days_before_gc = 0; days_before_gc < max_days_before_gc; days_before_gc++) {
+ SetUp();
+ day_last_garbage_collected_ = 0u;
+ LoggedActivity logged_activity;
+ for (uint32_t offset = 0; offset < days_before_gc; offset++) {
+ auto day_index = CurrentDayIndex();
+ for (const auto& metric_report_id :
+ logger::testing::unique_actives::kExpectedAggregationParams.metric_report_ids) {
+ // Log 2 events with event code 0.
+ EXPECT_EQ(kOK, LogUniqueActivesEvent(metric_report_id, day_index, 0u, &logged_activity));
+ EXPECT_EQ(kOK, LogUniqueActivesEvent(metric_report_id, day_index, 0u, &logged_activity));
+ if (offset < 3) {
+ // Log 1 event with event code 1.
+ EXPECT_EQ(kOK, LogUniqueActivesEvent(metric_report_id, day_index, 1u, &logged_activity));
+ }
+ }
+ AdvanceClock(kDay);
+ }
+ auto end_day_index = CurrentDayIndex();
+ EXPECT_EQ(kOK, GarbageCollect(end_day_index));
+ day_last_garbage_collected_ = end_day_index;
+ EXPECT_TRUE(CheckUniqueActivesAggregates(logged_activity, end_day_index));
+ TearDown();
+ }
+}
+
+// Tests that EventAggregator::GenerateObservations() returns a positive
+// status and that the expected number of Observations is generated after
+// some UniqueActivesEvents have been logged, without any garbage
+// collection.
+//
+// For 35 days, logs 2 events each day for the NetworkActivity_UniqueDevices
+// reports and 2 events for the FeaturesActive_UniqueDevices report, all
+// with event code 0.
+//
+// Each day, calls GenerateObservations() with the day index of the previous
+// day. Checks that a positive status is returned and that the
+// FakeObservationStore has received the expected number of new observations
+// for each locally aggregated report ID in the unique_actives registry.
+TEST_F(UniqueActivesAggregateStoreTest, GenerateObservations) {
+ int num_days = 35;
+ std::vector<Observation2> observations(0);
+ for (int offset = 0; offset < num_days; offset++) {
+ auto day_index = CurrentDayIndex();
+ observations.clear();
+ ResetObservationStore();
+ EXPECT_EQ(kOK, GenerateObservations(day_index - 1));
+ EXPECT_TRUE(FetchAggregatedObservations(
+ &observations, logger::testing::unique_actives::kExpectedAggregationParams,
+ observation_store_.get(), update_recipient_.get()));
+ for (int i = 0; i < 2; i++) {
+ EXPECT_EQ(kOK, LogUniqueActivesEvent(
+ logger::testing::unique_actives::kNetworkActivityWindowSizeMetricReportId,
+ day_index, 0u));
+ EXPECT_EQ(
+ kOK, LogUniqueActivesEvent(
+ logger::testing::unique_actives::kNetworkActivityAggregationWindowMetricReportId,
+ day_index, 0u));
+ EXPECT_EQ(
+ kOK, LogUniqueActivesEvent(logger::testing::unique_actives::kFeaturesActiveMetricReportId,
+ day_index, 0u));
+ }
+ AdvanceClock(kDay);
+ }
+ observations.clear();
+ ResetObservationStore();
+ EXPECT_EQ(kOK, GenerateObservations(CurrentDayIndex() - 1));
+ EXPECT_TRUE(FetchAggregatedObservations(
+ &observations, logger::testing::unique_actives::kExpectedAggregationParams,
+ observation_store_.get(), update_recipient_.get()));
+}
+
+// Tests that GenerateObservations() returns a positive status and that the
+// expected number of Observations is generated each day when Events are
+// logged for UNIQUE_N_DAY_ACTIVES reports over multiple days, and when the
+// LocalAggregateStore is garbage-collected each day.
+//
+// For 35 days, logs 2 events each day for the NetworkActivity_UniqueDevices
+// reports and 2 events for the FeaturesActive_UniqueDevices report, all
+// with event code 0.
+//
+// Each day following the first day, calls GenerateObservations() and then
+// GarbageCollect() with the day index of the current day. Checks that
+// positive statuses are returned and that the FakeObservationStore has
+// received the expected number of new observations for each locally
+// aggregated report ID in the unique_actives registry.
+TEST_F(UniqueActivesAggregateStoreTest, GenerateObservationsWithGc) {
+ int num_days = 35;
+ std::vector<Observation2> observations(0);
+ for (int offset = 0; offset < num_days; offset++) {
+ auto day_index = CurrentDayIndex();
+ observations.clear();
+ ResetObservationStore();
+ EXPECT_EQ(kOK, GenerateObservations(day_index - 1));
+ EXPECT_TRUE(FetchAggregatedObservations(
+ &observations, logger::testing::unique_actives::kExpectedAggregationParams,
+ observation_store_.get(), update_recipient_.get()));
+ EXPECT_EQ(kOK, GarbageCollect(day_index));
+ for (int i = 0; i < 2; i++) {
+ EXPECT_EQ(kOK, LogUniqueActivesEvent(
+ logger::testing::unique_actives::kNetworkActivityWindowSizeMetricReportId,
+ day_index, 0u));
+ EXPECT_EQ(
+ kOK, LogUniqueActivesEvent(
+ logger::testing::unique_actives::kNetworkActivityAggregationWindowMetricReportId,
+ day_index, 0u));
+ EXPECT_EQ(
+ kOK, LogUniqueActivesEvent(logger::testing::unique_actives::kFeaturesActiveMetricReportId,
+ day_index, 0u));
+ }
+ AdvanceClock(kDay);
+ }
+ observations.clear();
+ ResetObservationStore();
+ auto day_index = CurrentDayIndex();
+ EXPECT_EQ(kOK, GenerateObservations(day_index - 1));
+ EXPECT_TRUE(FetchAggregatedObservations(
+ &observations, logger::testing::unique_actives::kExpectedAggregationParams,
+ observation_store_.get(), update_recipient_.get()));
+ EXPECT_EQ(kOK, GarbageCollect(day_index));
+}
+
+// Tests that GenerateObservations() returns a positive status and that the
+// expected number of Observations is generated when events are logged over
+// multiple days and some of those days' Observations are backfilled, without
+// any garbage collection of the LocalAggregateStore.
+//
+// Sets the |backfill_days_| field of the EventAggregator to 3.
+//
+// Logging pattern:
+// For 35 days, logs 2 events each day for the
+// NetworkActivity_UniqueDevices reports and 2 events for the
+// FeaturesActive_UniqueDevices report, all with event code 0.
+//
+// Observation generation pattern:
+// Calls GenerateObservations() on the 1st through 5th and the 7th out of
+// every 10 days, for 35 days.
+//
+// Expected numbers of Observations:
+// It is expected that 4 days' worth of Observations are generated on
+// the first day of every 10 (the day index for which GenerateObservations()
+// was called, plus 3 days of backfill), that 1 day's worth of Observations
+// are generated on the 2nd through 5th day of every 10, that 2 days'
+// worth of Observations are generated on the 7th day of every 10 (the
+// day index for which GenerateObservations() was called, plus 1 day of
+// backfill), and that no Observations are generated on the remaining days.
+TEST_F(UniqueActivesAggregateStoreTest, GenerateObservationsWithBackfill) {
+ // Set |backfill_days_| to 3.
+ size_t backfill_days = 3;
+ SetBackfillDays(backfill_days);
+ // Log 2 events each day for 35 days. Call GenerateObservations() on the
+ // first 5 day indices, and the 7th, out of every 10.
+ for (int offset = 0; offset < 35; offset++) {
+ auto day_index = CurrentDayIndex();
+ for (int i = 0; i < 2; i++) {
+ EXPECT_EQ(kOK, LogUniqueActivesEvent(
+ logger::testing::unique_actives::kNetworkActivityWindowSizeMetricReportId,
+ day_index, 0u));
+ EXPECT_EQ(
+ kOK, LogUniqueActivesEvent(
+ logger::testing::unique_actives::kNetworkActivityAggregationWindowMetricReportId,
+ day_index, 0u));
+ EXPECT_EQ(
+ kOK, LogUniqueActivesEvent(logger::testing::unique_actives::kFeaturesActiveMetricReportId,
+ day_index, 0u));
+ }
+ observation_store_->ResetObservationCounter();
+ if (offset % 10 < 5 || offset % 10 == 6) {
+ EXPECT_EQ(kOK, GenerateObservations(day_index));
+ }
+ auto num_new_obs = observation_store_->num_observations_added();
+ EXPECT_GE(num_new_obs, 0u);
+ // Check that the expected daily number of Observations was generated.
+ switch (offset % 10) {
+ case 0:
+ EXPECT_EQ(logger::testing::unique_actives::kExpectedAggregationParams.daily_num_obs *
+ (backfill_days + 1),
+ num_new_obs);
+ break;
+ case 1:
+ case 2:
+ case 3:
+ case 4:
+ EXPECT_EQ(logger::testing::unique_actives::kExpectedAggregationParams.daily_num_obs,
+ num_new_obs);
+ break;
+ case 6:
+ EXPECT_EQ(logger::testing::unique_actives::kExpectedAggregationParams.daily_num_obs * 2,
+ num_new_obs);
+ break;
+ default:
+ EXPECT_EQ(0u, num_new_obs);
+ }
+ AdvanceClock(kDay);
+ }
+}
+
+// Tests that GenerateObservations() returns a positive status and that the
+// expected number of Observations is generated when events are logged over
+// multiple days and some of those days' Observations are backfilled, and when
+// the LocalAggregateStore is garbage-collected after each call to
+// GenerateObservations().
+//
+// Sets the |backfill_days_| field of the EventAggregator to 3.
+//
+// Logging pattern:
+// For 35 days, logs 2 events each day for the
+// NetworkActivity_UniqueDevices reports and 2 events for the
+// FeaturesActive_Unique_Devices report, all with event code 0.
+//
+// Observation generation pattern:
+// Calls GenerateObservations() on the 1st through 5th and the 7th out of
+// every 10 days, for 35 days. Garbage-collects the LocalAggregateStore after
+// each call.
+//
+// Expected numbers of Observations:
+// It is expected that 4 days' worth of Observations are generated on
+// the first day of every 10 (the day index for which GenerateObservations()
+// was called, plus 3 days of backfill), that 1 day's worth of Observations
+// are generated on the 2nd through 5th day of every 10, that 2 days'
+// worth of Observations are generated on the 7th day of every 10 (the
+// day index for which GenerateObservations() was called, plus 1 day of
+// backfill), and that no Observations are generated on the remaining days.
+TEST_F(UniqueActivesAggregateStoreTest, GenerateObservationsWithBackfillAndGc) {
+ int num_days = 35;
+ // Set |backfill_days_| to 3.
+ size_t backfill_days = 3;
+ SetBackfillDays(backfill_days);
+ // Log 2 events each day for 35 days. Call GenerateObservations() on the
+ // first 5 day indices, and the 7th, out of every 10.
+ for (int offset = 0; offset < num_days; offset++) {
+ auto day_index = CurrentDayIndex();
+ for (int i = 0; i < 2; i++) {
+ EXPECT_EQ(kOK, LogUniqueActivesEvent(
+ logger::testing::unique_actives::kNetworkActivityWindowSizeMetricReportId,
+ day_index, 0u));
+ EXPECT_EQ(
+ kOK, LogUniqueActivesEvent(
+ logger::testing::unique_actives::kNetworkActivityAggregationWindowMetricReportId,
+ day_index, 0u));
+ EXPECT_EQ(
+ kOK, LogUniqueActivesEvent(logger::testing::unique_actives::kFeaturesActiveMetricReportId,
+ day_index, 0u));
+ }
+ observation_store_->ResetObservationCounter();
+ if (offset % 10 < 5 || offset % 10 == 6) {
+ EXPECT_EQ(kOK, GenerateObservations(day_index));
+ EXPECT_EQ(kOK, GarbageCollect(day_index));
+ }
+ auto num_new_obs = observation_store_->num_observations_added();
+ EXPECT_GE(num_new_obs, 0u);
+ // Check that the expected daily number of Observations was generated.
+ // This expected number is some multiple of the daily_num_obs field of
+ // |kUniqueActivesExpectedParams|, depending on the number of days which
+ // should have been backfilled when GenerateObservations() was called.
+ switch (offset % 10) {
+ case 0:
+ EXPECT_EQ(logger::testing::unique_actives::kExpectedAggregationParams.daily_num_obs *
+ (backfill_days + 1),
+ num_new_obs);
+ break;
+ case 1:
+ case 2:
+ case 3:
+ case 4:
+ EXPECT_EQ(logger::testing::unique_actives::kExpectedAggregationParams.daily_num_obs,
+ num_new_obs);
+ break;
+ case 6:
+ EXPECT_EQ(logger::testing::unique_actives::kExpectedAggregationParams.daily_num_obs * 2,
+ num_new_obs);
+ break;
+ default:
+ EXPECT_EQ(0u, num_new_obs);
+ }
+ AdvanceClock(kDay);
+ }
+}
+
+// Checks that UniqueActivesObservations with the expected values (i.e.,
+// non-active for all UNIQUE_N_DAY_ACTIVES reports, for all window sizes and
+// event codes) are generated when no Events have been logged to the
+// EventAggregator.
+TEST_F(UniqueActivesNoiseFreeAggregateStoreTest, CheckObservationValuesNoEvents) {
+ auto current_day_index = CurrentDayIndex();
+ EXPECT_EQ(kOK, GenerateObservations(current_day_index));
+ auto expected_obs = MakeNullExpectedUniqueActivesObservations(
+ logger::testing::unique_actives_noise_free::kExpectedAggregationParams, current_day_index);
+ EXPECT_TRUE(CheckUniqueActivesObservations(expected_obs, observation_store_.get(),
+ update_recipient_.get()));
+}
+
+// Checks that UniqueActivesObservations with the expected values are
+// generated when GenerateObservations() is called for a single day index
+// after logging some events for UNIQUE_N_DAY_ACTIVES reports for that day
+// index, without any garbage collection or backfill.
+//
+// Logging pattern:
+// Logs 2 occurrences of event code 0 for the FeaturesActives_UniqueDevices
+// report, and 1 occurrence of event code 1 for the
+// EventsOccurred_UniqueDevices report, all on the same day.
+//
+// Observation generation pattern:
+// Calls GenerateObservations() after logging all events.
+//
+// Expected numbers of Observations:
+// The expected number of Observations is the daily_num_obs field of
+// |logger::testing::unique_actives_noise_free::kExpectedAggregationParams|.
+//
+// Expected Observation values:
+// All Observations should be labeled with the day index on which the events
+// were logged.
+//
+// For the FeaturesActive_UniqueDevices report, expect activity indicators:
+//
+// window size active for event codes
+// ------------------------------------------
+// 1 0
+// 7 0
+// 30 0
+//
+// For the EventsOccurred_UniqueDevices report, expected activity indicators:
+// window size active for event codes
+// ------------------------------------------
+// 1 1
+// 7 1
+//
+// All other Observations should be of inactivity.
+TEST_F(UniqueActivesNoiseFreeAggregateStoreTest, CheckObservationValuesSingleDay) {
+ auto day_index = CurrentDayIndex();
+ // Log several events on |day_index|.
+ EXPECT_EQ(kOK, LogUniqueActivesEvent(
+ logger::testing::unique_actives_noise_free::kFeaturesActiveMetricReportId,
+ day_index, 0u));
+ EXPECT_EQ(kOK, LogUniqueActivesEvent(
+ logger::testing::unique_actives_noise_free::kFeaturesActiveMetricReportId,
+ day_index, 0u));
+ EXPECT_EQ(kOK, LogUniqueActivesEvent(
+ logger::testing::unique_actives_noise_free::kEventsOccurredMetricReportId,
+ day_index, 1u));
+ // Generate locally aggregated Observations for |day_index|.
+ EXPECT_EQ(kOK, GenerateObservations(day_index));
+
+ // Form the expected observations.
+ auto expected_obs = MakeNullExpectedUniqueActivesObservations(
+ logger::testing::unique_actives_noise_free::kExpectedAggregationParams, day_index);
+ expected_obs[{logger::testing::unique_actives_noise_free::kFeaturesActiveMetricReportId,
+ day_index}] = {{1, {true, false, false, false, false}},
+ {7, {true, false, false, false, false}},
+ {30, {true, false, false, false, false}}};
+ expected_obs[{logger::testing::unique_actives_noise_free::kEventsOccurredMetricReportId,
+ day_index}] = {{1, {false, true, false, false, false}},
+ {7, {false, true, false, false, false}}};
+
+ // Check the contents of the FakeObservationStore.
+ EXPECT_TRUE(CheckUniqueActivesObservations(expected_obs, observation_store_.get(),
+ update_recipient_.get()));
+}
+
+// Checks that UniqueActivesObservations with the expected values are
+// generated when some events have been logged for a UNIQUE_N_DAY_ACTIVES
+// report over multiple days and GenerateObservations() is called each
+// day, without garbage collection or backfill.
+//
+// Logging pattern:
+// Logs events for the EventsOccurred_UniqueDevices report (whose parent
+// metric has max_event_code = 4) for 10 days, according to the following
+// pattern:
+//
+// * Never log event code 0.
+// * On the i-th day (0-indexed) of logging, log an event for event code k,
+// 1 <= k < 5, if 3*k divides i.
+//
+// Observation generation pattern:
+// Each day following the first day, generates Observations for the previous
+// day index.
+//
+// Expected number of Observations:
+// Each call to GenerateObservations should generate a number of Observations
+// equal to the daily_num_obs field of
+// |testing::unique_actives_noise_free::kExpectedAggregationParams|.
+//
+// Expected Observation values:
+// The EventsOccurred_UniqueDevices report has window sizes 1 and 7, and
+// the expected activity indicators of Observations for that report on the
+// i-th day are:
+//
+// (i, window size) active for event codes
+// ------------------------------------------------------
+// (0, 1) 1, 2, 3, 4
+// (0, 7) 1, 2, 3, 4
+// (1, 1) ---
+// (1, 7) 1, 2, 3, 4
+// (2, 1) ---
+// (2, 7) 1, 2, 3, 4
+// (3, 1) 1
+// (3, 7) 1, 2, 3, 4
+// (4, 1) ---
+// (4, 7) 1, 2, 3, 4
+// (5, 1) ---
+// (5, 7) 1, 2, 3, 4
+// (6, 1) 1, 2
+// (6, 7) 1, 2, 3, 4
+// (7, 1) ---
+// (7, 7) 1, 2
+// (8, 1) ---
+// (8, 7) 1, 2
+// (9, 1) 1, 3
+// (9, 7) 1, 2, 3
+//
+// All Observations for all other locally aggregated reports should be
+// observations of non-occurrence.
+TEST_F(UniqueActivesNoiseFreeAggregateStoreTest, CheckObservationValuesMultiDay) {
+ auto start_day_index = CurrentDayIndex();
+ // Form expected Obsevations for the 10 days of logging.
+ uint32_t num_days = 10;
+ std::vector<ExpectedUniqueActivesObservations> expected_obs(num_days);
+ const auto& expected_id =
+ logger::testing::unique_actives_noise_free::kEventsOccurredMetricReportId;
+ for (uint32_t offset = 0; offset < num_days; offset++) {
+ expected_obs[offset] = MakeNullExpectedUniqueActivesObservations(
+ logger::testing::unique_actives_noise_free::kExpectedAggregationParams,
+ start_day_index + offset);
+ }
+ expected_obs[0][{expected_id, start_day_index}] = {{1, {false, true, true, true, true}},
+ {7, {false, true, true, true, true}}};
+ expected_obs[1][{expected_id, start_day_index + 1}] = {{1, {false, false, false, false, false}},
+ {7, {false, true, true, true, true}}};
+ expected_obs[2][{expected_id, start_day_index + 2}] = {{1, {false, false, false, false, false}},
+ {7, {false, true, true, true, true}}};
+ expected_obs[3][{expected_id, start_day_index + 3}] = {{1, {false, true, false, false, false}},
+ {7, {false, true, true, true, true}}};
+ expected_obs[4][{expected_id, start_day_index + 4}] = {{1, {false, false, false, false, false}},
+ {7, {false, true, true, true, true}}};
+ expected_obs[5][{expected_id, start_day_index + 5}] = {{1, {false, false, false, false, false}},
+ {7, {false, true, true, true, true}}};
+ expected_obs[6][{expected_id, start_day_index + 6}] = {{1, {false, true, true, false, false}},
+ {7, {false, true, true, true, true}}};
+ expected_obs[7][{expected_id, start_day_index + 7}] = {{1, {false, false, false, false, false}},
+ {7, {false, true, true, false, false}}};
+ expected_obs[8][{expected_id, start_day_index + 8}] = {{1, {false, false, false, false, false}},
+ {7, {false, true, true, false, false}}};
+ expected_obs[9][{expected_id, start_day_index + 9}] = {{1, {false, true, false, true, false}},
+ {7, {false, true, true, true, false}}};
+
+ for (uint32_t offset = 0; offset < num_days; offset++) {
+ auto day_index = CurrentDayIndex();
+ for (uint32_t event_code = 1;
+ event_code <
+ logger::testing::unique_actives_noise_free::kExpectedAggregationParams.num_event_codes.at(
+ expected_id);
+ event_code++) {
+ if (offset % (3 * event_code) == 0) {
+ EXPECT_EQ(kOK, LogUniqueActivesEvent(expected_id, day_index, event_code));
+ }
+ }
+ // Clear the FakeObservationStore.
+ ResetObservationStore();
+ // Generate locally aggregated Observations.
+ EXPECT_EQ(kOK, GenerateObservations(day_index));
+ // Check the generated Observations against the expectation.
+ EXPECT_TRUE(CheckUniqueActivesObservations(expected_obs[offset], observation_store_.get(),
+ update_recipient_.get()));
+ AdvanceClock(kDay);
+ }
+}
+
+// Checks that UniqueActivesObservations with the expected values are
+// generated when some events have been logged for a UNIQUE_N_DAY_ACTIVES
+// report over multiple days and GenerateObservations() is called each
+// day, and when the LocalAggregateStore is garbage-collected after each call
+// to GenerateObservations().
+//
+// Logging pattern:
+// Logs events for the EventsOccurred_UniqueDevices report (whose parent
+// metric has max_event_code = 4) for 10 days, according to the following
+// pattern:
+//
+// * Never log event code 0.
+// * On the i-th day (0-indexed) of logging, log an event for event code k,
+// 1 <= k < 5, if 3*k divides i.
+//
+// Observation generation pattern:
+// Each day following the first day, generates Observations for the previous
+// day index.
+//
+// Expected number of Observations:
+// Each call to GenerateObservations should generate a number of Observations
+// equal to the daily_num_obs field of
+// |logger::testing::unique_actives_noise_free::kExpectedAggregationParams|.
+//
+// Expected Observation values:
+// The EventsOccurred_UniqueDevices report has window sizes 1 and 7, and
+// the expected activity indicators of Observations for that report on the
+// i-th day are:
+//
+// (i, window size) active for event codes
+// ------------------------------------------------------
+// (0, 1) 1, 2, 3, 4
+// (0, 7) 1, 2, 3, 4
+// (1, 1) ---
+// (1, 7) 1, 2, 3, 4
+// (2, 1) ---
+// (2, 7) 1, 2, 3, 4
+// (3, 1) 1
+// (3, 7) 1, 2, 3, 4
+// (4, 1) ---
+// (4, 7) 1, 2, 3, 4
+// (5, 1) ---
+// (5, 7) 1, 2, 3, 4
+// (6, 1) 1, 2
+// (6, 7) 1, 2, 3, 4
+// (7, 1) ---
+// (7, 7) 1, 2
+// (8, 1) ---
+// (8, 7) 1, 2
+// (9, 1) 1, 3
+// (9, 7) 1, 2, 3
+//
+// All Observations for all other locally aggregated reports should be
+// observations of non-occurrence.
+TEST_F(UniqueActivesNoiseFreeAggregateStoreTest,
+ CheckObservationValuesMultiDayWithGarbageCollection) {
+ auto start_day_index = CurrentDayIndex();
+ // Form expected Observations for the 10 days of logging.
+ uint32_t num_days = 10;
+ std::vector<ExpectedUniqueActivesObservations> expected_obs(num_days);
+ const auto& expected_id =
+ logger::testing::unique_actives_noise_free::kEventsOccurredMetricReportId;
+
+ for (uint32_t offset = 0; offset < num_days; offset++) {
+ expected_obs[offset] = MakeNullExpectedUniqueActivesObservations(
+ logger::testing::unique_actives_noise_free::kExpectedAggregationParams,
+ start_day_index + offset);
+ }
+ expected_obs[0][{expected_id, start_day_index}] = {{1, {false, true, true, true, true}},
+ {7, {false, true, true, true, true}}};
+ expected_obs[1][{expected_id, start_day_index + 1}] = {{1, {false, false, false, false, false}},
+ {7, {false, true, true, true, true}}};
+ expected_obs[2][{expected_id, start_day_index + 2}] = {{1, {false, false, false, false, false}},
+ {7, {false, true, true, true, true}}};
+ expected_obs[3][{expected_id, start_day_index + 3}] = {{1, {false, true, false, false, false}},
+ {7, {false, true, true, true, true}}};
+ expected_obs[4][{expected_id, start_day_index + 4}] = {{1, {false, false, false, false, false}},
+ {7, {false, true, true, true, true}}};
+ expected_obs[5][{expected_id, start_day_index + 5}] = {{1, {false, false, false, false, false}},
+ {7, {false, true, true, true, true}}};
+ expected_obs[6][{expected_id, start_day_index + 6}] = {{1, {false, true, true, false, false}},
+ {7, {false, true, true, true, true}}};
+ expected_obs[7][{expected_id, start_day_index + 7}] = {{1, {false, false, false, false, false}},
+ {7, {false, true, true, false, false}}};
+ expected_obs[8][{expected_id, start_day_index + 8}] = {{1, {false, false, false, false, false}},
+ {7, {false, true, true, false, false}}};
+ expected_obs[9][{expected_id, start_day_index + 9}] = {{1, {false, true, false, true, false}},
+ {7, {false, true, true, true, false}}};
+
+ for (uint32_t offset = 0; offset < num_days; offset++) {
+ auto day_index = CurrentDayIndex();
+ for (uint32_t event_code = 1;
+ event_code <
+ logger::testing::unique_actives_noise_free::kExpectedAggregationParams.num_event_codes.at(
+ expected_id);
+ event_code++) {
+ if (offset % (3 * event_code) == 0) {
+ EXPECT_EQ(kOK, LogUniqueActivesEvent(expected_id, day_index, event_code));
+ }
+ }
+ // Advance |test_clock_| by 1 day.
+ AdvanceClock(kDay);
+ // Clear the FakeObservationStore.
+ ResetObservationStore();
+ // Generate locally aggregated Observations and garbage-collect the
+ // LocalAggregateStore, both for the previous day as measured by
+ // |test_clock_|. Back up the LocalAggregateStore and
+ // AggregatedObservationHistoryStore.
+ DoScheduledTasksNow();
+ // Check the generated Observations against the expectation.
+ EXPECT_TRUE(CheckUniqueActivesObservations(expected_obs[offset], observation_store_.get(),
+ update_recipient_.get()));
+ }
+}
+
+// Tests that the expected UniqueActivesObservations are generated when events
+// are logged over multiple days and when Observations are backfilled for some
+// days during that period, without any garbage-collection of the
+// LocalAggregateStore.
+//
+// The test sets the number of backfill days to 3.
+//
+// Logging pattern:
+// Events for the EventsOccurred_UniqueDevices report are logged over the days
+// |start_day_index| to |start_day_index + 8| according to the following
+// pattern:
+//
+// * For i = 0 to i = 4, log an event with event code i on day
+// |start_day_index + i| and |start_day_index + 2*i|.
+//
+// Observation generation pattern:
+// The test calls GenerateObservations() on day |start_day_index + i| for i =
+// 0 through i = 5 and for i = 8, skipping the days |start_day_index + 6| and
+// |start_day_index + 7|.
+//
+// Expected numbers of Observations:
+// It is expected that 4 days' worth of Observations are generated on the
+// first day (the day index for which GenerateObservations() was called, plus
+// 3 days of backfill), that 1 day's worth of Observations is generated on the
+// 2nd through 6th day, that 3 days' worth of Observations are generated on
+// the 9th day (the day index for which GenerateObservations() was called,
+// plus 2 days of backfill), and that no Observations are generated on the
+// remaining days.
+//
+// Expected Observation values:
+// The expected activity indicators of Observations for the
+// EventsOccurred_UniqueDevices report for the i-th day of logging are:
+//
+// (i, window size) active for event codes
+// -------------------------------------------------------------------------
+// (0, 1) 0
+// (0, 7) 0
+// (1, 1) 1
+// (1, 7) 0, 1
+// (2, 1) 1, 2
+// (2, 7) 0, 1, 2
+// (3, 1) 3
+// (3, 7) 0, 1, 2, 3
+// (4, 1) 2, 4
+// (4, 7) 0, 1, 2, 3, 4
+// (5, 1) ---
+// (5, 7) 0, 1, 2, 3, 4
+// (6, 1) 3
+// (6, 7) 0, 1, 2, 3, 4
+// (7, 1) ---
+// (7, 7) 1, 2, 3, 4
+// (8, 1) 4
+// (8, 7) 1, 2, 3, 4
+//
+// All other Observations should be of non-activity.
+TEST_F(UniqueActivesNoiseFreeAggregateStoreTest, CheckObservationValuesWithBackfill) {
+ auto start_day_index = CurrentDayIndex();
+ // Set |backfill_days_| to 3.
+ size_t backfill_days = 3;
+ SetBackfillDays(backfill_days);
+ const auto& expected_id =
+ logger::testing::unique_actives_noise_free::kEventsOccurredMetricReportId;
+ const auto& expected_params =
+ logger::testing::unique_actives_noise_free::kExpectedAggregationParams;
+ // Log events for 9 days. Call GenerateObservations() on the first 6 day
+ // indices, and the 9th.
+ for (uint32_t offset = 0; offset < 9; offset++) {
+ auto day_index = CurrentDayIndex();
+ ResetObservationStore();
+ for (uint32_t event_code = 0; event_code < expected_params.num_event_codes.at(expected_id);
+ event_code++) {
+ if (event_code == offset || (2 * event_code) == offset) {
+ EXPECT_EQ(kOK, LogUniqueActivesEvent(expected_id, day_index, event_code));
+ }
+ }
+ if (offset < 6 || offset == 8) {
+ EXPECT_EQ(kOK, GenerateObservations(day_index));
+ }
+ // Make the set of Observations which are expected to be generated on
+ // |start_day_index + offset| and check it against the contents of the
+ // FakeObservationStore.
+ ExpectedUniqueActivesObservations expected_obs;
+ switch (offset) {
+ case 0: {
+ for (uint32_t day_index = start_day_index - backfill_days; day_index <= start_day_index;
+ day_index++) {
+ for (const auto& pair :
+ MakeNullExpectedUniqueActivesObservations(expected_params, day_index)) {
+ expected_obs.insert(pair);
+ }
+ }
+ expected_obs[{expected_id, start_day_index}] = {{1, {true, false, false, false, false}},
+ {7, {true, false, false, false, false}}};
+ EXPECT_TRUE(CheckUniqueActivesObservations(expected_obs, observation_store_.get(),
+ update_recipient_.get()));
+ break;
+ }
+ case 1: {
+ expected_obs =
+ MakeNullExpectedUniqueActivesObservations(expected_params, start_day_index + 1);
+ expected_obs[{expected_id, start_day_index + 1}] = {{1, {false, true, false, false, false}},
+ {7, {true, true, false, false, false}}};
+ EXPECT_TRUE(CheckUniqueActivesObservations(expected_obs, observation_store_.get(),
+ update_recipient_.get()));
+ break;
+ }
+ case 2: {
+ expected_obs =
+ MakeNullExpectedUniqueActivesObservations(expected_params, start_day_index + 2);
+ expected_obs[{expected_id, start_day_index + 2}] = {{1, {false, true, true, false, false}},
+ {7, {true, true, true, false, false}}};
+ EXPECT_TRUE(CheckUniqueActivesObservations(expected_obs, observation_store_.get(),
+ update_recipient_.get()));
+ break;
+ }
+ case 3: {
+ expected_obs =
+ MakeNullExpectedUniqueActivesObservations(expected_params, start_day_index + 3);
+ expected_obs[{expected_id, start_day_index + 3}] = {{1, {false, false, false, true, false}},
+ {7, {true, true, true, true, false}}};
+ EXPECT_TRUE(CheckUniqueActivesObservations(expected_obs, observation_store_.get(),
+ update_recipient_.get()));
+ break;
+ }
+ case 4: {
+ expected_obs =
+ MakeNullExpectedUniqueActivesObservations(expected_params, start_day_index + 4);
+ expected_obs[{expected_id, start_day_index + 4}] = {{1, {false, false, true, false, true}},
+ {7, {true, true, true, true, true}}};
+ EXPECT_TRUE(CheckUniqueActivesObservations(expected_obs, observation_store_.get(),
+ update_recipient_.get()));
+ break;
+ }
+ case 5: {
+ expected_obs =
+ MakeNullExpectedUniqueActivesObservations(expected_params, start_day_index + 5);
+ expected_obs[{expected_id, start_day_index + 5}] = {
+ {1, {false, false, false, false, false}}, {7, {true, true, true, true, true}}};
+ EXPECT_TRUE(CheckUniqueActivesObservations(expected_obs, observation_store_.get(),
+ update_recipient_.get()));
+ break;
+ }
+ case 8: {
+ for (uint32_t day_index = start_day_index + 6; day_index <= start_day_index + 8;
+ day_index++) {
+ for (const auto& pair :
+ MakeNullExpectedUniqueActivesObservations(expected_params, day_index)) {
+ expected_obs.insert(pair);
+ }
+ }
+ expected_obs[{expected_id, start_day_index + 6}] = {{1, {false, false, false, true, false}},
+ {7, {true, true, true, true, true}}};
+ expected_obs[{expected_id, start_day_index + 7}] = {
+ {1, {false, false, false, false, false}}, {7, {false, true, true, true, true}}};
+ expected_obs[{expected_id, start_day_index + 8}] = {{1, {false, false, false, false, true}},
+ {7, {false, true, true, true, true}}};
+ EXPECT_TRUE(CheckUniqueActivesObservations(expected_obs, observation_store_.get(),
+ update_recipient_.get()));
+ break;
+ }
+ default:
+ EXPECT_TRUE(CheckUniqueActivesObservations(expected_obs, observation_store_.get(),
+ update_recipient_.get()));
+ }
+ AdvanceClock(kDay);
+ }
+}
+
+// Tests that the expected UniqueActivesObservations are generated when events
+// are logged over multiple days and when Observations are backfilled for some
+// days during that period, and when the LocalAggregateStore is
+// garbage-collected after each call to GenerateObservations().
+//
+// The test sets the number of backfill days to 3.
+//
+// Logging pattern:
+// Events for the EventsOccurred_UniqueDevices report are logged over the days
+// |start_day_index| to |start_day_index + 8| according to the following
+// pattern:
+//
+// * For i = 0 to i = 4, log an event with event code i on day
+// |start_day_index + i| and |start_day_index + 2*i|.
+//
+// Observation generation pattern:
+// The test calls GenerateObservations() on day |start_day_index + i| for i =
+// 0 through i = 5 and for i = 8, skipping the days |start_day_index + 6| and
+// |start_day_index + 7|.
+//
+// Expected numbers of Observations:
+// It is expected that 4 days' worth of Observations are generated on the
+// first day (the day index for which GenerateObservations() was called, plus
+// 3 days of backfill), that 1 day's worth of Observations is generated on the
+// 2nd through 6th day, that 3 days' worth of Observations are generated on
+// the 9th day (the day index for which GenerateObservations() was called,
+// plus 2 days of backfill), and that no Observations are generated on the
+// remaining days.
+//
+// Expected Observation values:
+// The expected activity indicators of Observations for the
+// EventsOccurred_UniqueDevices report for the i-th day of logging are:
+//
+// (i, window size) active for event codes
+// -------------------------------------------------------------------------
+// (0, 1) 0
+// (0, 7) 0
+// (1, 1) 1
+// (1, 7) 0, 1
+// (2, 1) 1, 2
+// (2, 7) 0, 1, 2
+// (3, 1) 3
+// (3, 7) 0, 1, 2, 3
+// (4, 1) 2, 4
+// (4, 7) 0, 1, 2, 3, 4
+// (5, 1) ---
+// (5, 7) 0, 1, 2, 3, 4
+// (6, 1) 3
+// (6, 7) 0, 1, 2, 3, 4
+// (7, 1) ---
+// (7, 7) 1, 2, 3, 4
+// (8, 1) 4
+// (8, 7) 1, 2, 3, 4
+//
+// All other Observations should be of non-activity.
+TEST_F(UniqueActivesNoiseFreeAggregateStoreTest, CheckObservationValuesWithBackfillAndGc) {
+ auto start_day_index = CurrentDayIndex();
+ // Set |backfill_days_| to 3.
+ size_t backfill_days = 3;
+ SetBackfillDays(backfill_days);
+
+ const auto& expected_id =
+ logger::testing::unique_actives_noise_free::kEventsOccurredMetricReportId;
+ const auto& expected_params =
+ logger::testing::unique_actives_noise_free::kExpectedAggregationParams;
+
+ // Log events for 9 days. Call GenerateObservations() on the first 6 day
+ // indices, and the 9th.
+ for (uint32_t offset = 0; offset < 8; offset++) {
+ auto day_index = CurrentDayIndex();
+ ResetObservationStore();
+ for (uint32_t event_code = 0; event_code < expected_params.num_event_codes.at(expected_id);
+ event_code++) {
+ if (event_code == offset || (2 * event_code) == offset) {
+ EXPECT_EQ(kOK, LogUniqueActivesEvent(expected_id, day_index, event_code));
+ }
+ }
+ // Advance |test_clock_| by 1 day.
+ AdvanceClock(kDay);
+ if (offset < 6 || offset == 9) {
+ // Generate Observations and garbage-collect, both for the previous day
+ // index according to |test_clock_|. Back up the LocalAggregateStore and
+ // the AggregatedObservationHistoryStore.
+ DoScheduledTasksNow();
+ }
+ // Make the set of Observations which are expected to be generated on
+ // |start_day_index + offset| and check it against the contents of the
+ // FakeObservationStore.
+ ExpectedUniqueActivesObservations expected_obs;
+ switch (offset) {
+ case 0: {
+ for (uint32_t day_index = start_day_index - backfill_days; day_index <= start_day_index;
+ day_index++) {
+ for (const auto& pair :
+ MakeNullExpectedUniqueActivesObservations(expected_params, day_index)) {
+ expected_obs.insert(pair);
+ }
+ }
+ expected_obs[{expected_id, start_day_index}] = {{1, {true, false, false, false, false}},
+ {7, {true, false, false, false, false}}};
+ EXPECT_TRUE(CheckUniqueActivesObservations(expected_obs, observation_store_.get(),
+ update_recipient_.get()));
+ break;
+ }
+ case 1: {
+ expected_obs =
+ MakeNullExpectedUniqueActivesObservations(expected_params, start_day_index + 1);
+ expected_obs[{expected_id, start_day_index + 1}] = {{1, {false, true, false, false, false}},
+ {7, {true, true, false, false, false}}};
+ EXPECT_TRUE(CheckUniqueActivesObservations(expected_obs, observation_store_.get(),
+ update_recipient_.get()));
+ break;
+ }
+ case 2: {
+ expected_obs =
+ MakeNullExpectedUniqueActivesObservations(expected_params, start_day_index + 2);
+ expected_obs[{expected_id, start_day_index + 2}] = {{1, {false, true, true, false, false}},
+ {7, {true, true, true, false, false}}};
+ EXPECT_TRUE(CheckUniqueActivesObservations(expected_obs, observation_store_.get(),
+ update_recipient_.get()));
+ break;
+ }
+ case 3: {
+ expected_obs =
+ MakeNullExpectedUniqueActivesObservations(expected_params, start_day_index + 3);
+ expected_obs[{expected_id, start_day_index + 3}] = {{1, {false, false, false, true, false}},
+ {7, {true, true, true, true, false}}};
+ EXPECT_TRUE(CheckUniqueActivesObservations(expected_obs, observation_store_.get(),
+ update_recipient_.get()));
+ break;
+ }
+ case 4: {
+ expected_obs =
+ MakeNullExpectedUniqueActivesObservations(expected_params, start_day_index + 4);
+ expected_obs[{expected_id, start_day_index + 4}] = {{1, {false, false, true, false, true}},
+ {7, {true, true, true, true, true}}};
+ EXPECT_TRUE(CheckUniqueActivesObservations(expected_obs, observation_store_.get(),
+ update_recipient_.get()));
+ break;
+ }
+ case 5: {
+ expected_obs =
+ MakeNullExpectedUniqueActivesObservations(expected_params, start_day_index + 5);
+ expected_obs[{expected_id, start_day_index + 5}] = {
+ {1, {false, false, false, false, false}}, {7, {true, true, true, true, true}}};
+ EXPECT_TRUE(CheckUniqueActivesObservations(expected_obs, observation_store_.get(),
+ update_recipient_.get()));
+ break;
+ }
+ case 8: {
+ for (uint32_t day_index = start_day_index + 6; day_index <= start_day_index + 8;
+ day_index++) {
+ for (const auto& pair :
+ MakeNullExpectedUniqueActivesObservations(expected_params, day_index)) {
+ expected_obs.insert(pair);
+ }
+ }
+ expected_obs[{expected_id, start_day_index + 6}] = {{1, {false, false, false, true, false}},
+ {7, {true, true, true, true, true}}};
+ expected_obs[{expected_id, start_day_index + 7}] = {
+ {1, {false, false, false, false, false}}, {7, {false, true, true, true, true}}};
+ expected_obs[{expected_id, start_day_index + 8}] = {{1, {false, false, false, false, true}},
+ {7, {false, true, true, true, true}}};
+ EXPECT_TRUE(CheckUniqueActivesObservations(expected_obs, observation_store_.get(),
+ update_recipient_.get()));
+ break;
+ }
+ default:
+ EXPECT_TRUE(CheckUniqueActivesObservations(expected_obs, observation_store_.get(),
+ update_recipient_.get()));
+ }
+ }
+}
+
+// Tests GarbageCollect() for PerDeviceNumericReportAggregates.
+//
+// For each value of N in the range [0, 34], logs some events for
+// PerDeviceNumeric reports each day for N consecutive days, and then
+// garbage-collects the LocalAggregateStore. After garbage collection, verifies
+// the contents of the LocalAggregateStore.
+TEST_F(PerDeviceNumericAggregateStoreTest, GarbageCollect) {
+ uint32_t max_days_before_gc = 35;
+ for (uint32_t days_before_gc = 0; days_before_gc < max_days_before_gc; days_before_gc++) {
+ SetUp();
+ day_last_garbage_collected_ = 0u;
+ LoggedValues logged_values;
+ std::vector<MetricReportId> count_metric_report_ids = {
+ logger::testing::per_device_numeric_stats::kSettingsChangedWindowSizeMetricReportId,
+ logger::testing::per_device_numeric_stats::kSettingsChangedAggregationWindowMetricReportId,
+ logger::testing::per_device_numeric_stats::kConnectionFailuresMetricReportId};
+ std::vector<MetricReportId> elapsed_time_metric_report_ids = {
+ logger::testing::per_device_numeric_stats::kStreamingTimeTotalMetricReportId,
+ logger::testing::per_device_numeric_stats::kStreamingTimeMinMetricReportId,
+ logger::testing::per_device_numeric_stats::kStreamingTimeMaxMetricReportId};
+ MetricReportId frame_rate_metric_report_id =
+ logger::testing::per_device_numeric_stats::kLoginModuleFrameRateMinMetricReportId;
+ MetricReportId memory_usage_metric_report_id =
+ logger::testing::per_device_numeric_stats::kLedgerMemoryUsageMaxMetricReportId;
+ for (uint32_t offset = 0; offset < days_before_gc; offset++) {
+ auto day_index = CurrentDayIndex();
+ for (const auto& id : count_metric_report_ids) {
+ for (const auto& component : {"component_A", "component_B", "component_C"}) {
+ // Log 2 events with event code 0, for each component A, B, C.
+ EXPECT_EQ(kOK, LogPerDeviceCountEvent(id, day_index, component, 0u, 2, &logged_values));
+ EXPECT_EQ(kOK, LogPerDeviceCountEvent(id, day_index, component, 0u, 3, &logged_values));
+ }
+ if (offset < 3) {
+ // Log 1 event for component D and event code 1.
+ EXPECT_EQ(kOK,
+ LogPerDeviceCountEvent(id, day_index, "component_D", 1u, 4, &logged_values));
+ }
+ }
+ for (const auto& id : elapsed_time_metric_report_ids) {
+ for (const auto& component : {"component_A", "component_B", "component_C"}) {
+ // Log 2 events with event code 0, for each component A, B, C.
+ EXPECT_EQ(kOK,
+ LogPerDeviceElapsedTimeEvent(id, day_index, component, 0u, 2, &logged_values));
+ EXPECT_EQ(kOK,
+ LogPerDeviceElapsedTimeEvent(id, day_index, component, 0u, 3, &logged_values));
+ }
+ if (offset < 3) {
+ // Log 1 event for component D and event code 1.
+ EXPECT_EQ(kOK, LogPerDeviceElapsedTimeEvent(id, day_index, "component_D", 1u, 4,
+ &logged_values));
+ }
+ }
+ for (const auto& component : {"component_A", "component_B"}) {
+ EXPECT_EQ(kOK, LogPerDeviceFrameRateEvent(frame_rate_metric_report_id, day_index, component,
+ 0u, 2.25, &logged_values));
+ EXPECT_EQ(kOK, LogPerDeviceFrameRateEvent(frame_rate_metric_report_id, day_index, component,
+ 0u, 1.75, &logged_values));
+ EXPECT_EQ(kOK,
+ LogPerDeviceMemoryUsageEvent(memory_usage_metric_report_id, day_index, component,
+ std::vector<uint32_t>{0u, 0u}, 300, &logged_values));
+ EXPECT_EQ(kOK,
+ LogPerDeviceMemoryUsageEvent(memory_usage_metric_report_id, day_index, component,
+ std::vector<uint32_t>{1u, 0u}, 300, &logged_values));
+ }
+ AdvanceClock(kDay);
+ }
+ auto end_day_index = CurrentDayIndex();
+ EXPECT_EQ(kOK, GarbageCollect(end_day_index));
+ day_last_garbage_collected_ = end_day_index;
+ EXPECT_TRUE(CheckPerDeviceNumericAggregates(logged_values, end_day_index));
+ TearDown();
+ }
+}
+
+// Tests that EventAggregator::GenerateObservations() returns a positive
+// status and that the expected number of Observations is generated after
+// some CountEvents have been logged for PerDeviceNumericStats reports, without
+// any garbage collection.
+//
+// For 35 days, logs a positive number of events each day for the
+// ConnectionFailures_PerDeviceNumericStats report with "component_A" and for
+// the SettingsChanged_PerDeviceNumericStats reports with "component_B", all with
+// event code 0.
+//
+// Each day, calls GenerateObservations() with the day index of the previous
+// day. Checks that a positive status is returned and that the
+// FakeObservationStore has received the expected number of new observations
+// for each locally aggregated report ID in the per_device_numeric_stats test
+// registry.
+TEST_F(PerDeviceNumericAggregateStoreTest, GenerateObservations) {
+ int num_days = 1;
+ std::vector<Observation2> observations(0);
+ ExpectedAggregationParams expected_params =
+ logger::testing::per_device_numeric_stats::kExpectedAggregationParams;
+ for (int offset = 0; offset < num_days; offset++) {
+ auto day_index = CurrentDayIndex();
+ observations.clear();
+ ResetObservationStore();
+ EXPECT_EQ(kOK, GenerateObservations(day_index - 1));
+ EXPECT_TRUE(FetchAggregatedObservations(&observations, expected_params,
+ observation_store_.get(), update_recipient_.get()));
+ for (int i = 0; i < 2; i++) {
+ EXPECT_EQ(kOK,
+ LogPerDeviceCountEvent(
+ logger::testing::per_device_numeric_stats::kConnectionFailuresMetricReportId,
+ day_index, "component_A", 0u, 1));
+ EXPECT_EQ(kOK,
+ LogPerDeviceCountEvent(
+ logger::testing::per_device_numeric_stats::kConnectionFailuresMetricReportId,
+ day_index, "component_A", 0u, 1));
+ EXPECT_EQ(
+ kOK,
+ LogPerDeviceCountEvent(
+ logger::testing::per_device_numeric_stats::kSettingsChangedWindowSizeMetricReportId,
+ day_index, "component_B", 0u, 5));
+ EXPECT_EQ(kOK, LogPerDeviceCountEvent(logger::testing::per_device_numeric_stats::
+ kSettingsChangedAggregationWindowMetricReportId,
+ day_index, "component_B", 0u, 5));
+ }
+ // If this is the first time we're logging events, update the expected
+ // numbers of generated Observations to account for the logged events.
+ // For each report, for each aggregation window, expect 1 Observation more than if
+ // no events had been logged.
+ if (offset == 0) {
+ expected_params.daily_num_obs += 5;
+ expected_params.num_obs_per_report
+ [logger::testing::per_device_numeric_stats::kConnectionFailuresMetricReportId] += 1;
+ expected_params.num_obs_per_report
+ [logger::testing::per_device_numeric_stats::kSettingsChangedWindowSizeMetricReportId] +=
+ 2;
+ expected_params.num_obs_per_report[logger::testing::per_device_numeric_stats::
+ kSettingsChangedAggregationWindowMetricReportId] += 2;
+ }
+ AdvanceClock(kDay);
+ }
+ observations.clear();
+ ResetObservationStore();
+ EXPECT_EQ(kOK, GenerateObservations(CurrentDayIndex() - 1));
+ EXPECT_TRUE(FetchAggregatedObservations(&observations, expected_params, observation_store_.get(),
+ update_recipient_.get()));
+}
+
+// Tests that EventAggregator::GenerateObservations() returns a positive
+// status and that the expected number of Observations is generated after
+// some CountEvents have been logged for PerDeviceNumeric reports over multiple
+// days, and when the LocalAggregateStore is garbage-collected each day.
+//
+// For 35 days, logs a positive number of events each day for the
+// ConnectionFailures_PerDeviceNumeric report with "component_A" and for
+// the SettingsChanged_PerDeviceNumeric report with "component_B", all with
+// event code 0.
+//
+// Each day, calls GenerateObservations() with the day index of the previous
+// day. Checks that a positive status is returned and that the
+// FakeObservationStore has received the expected number of new observations
+// for each locally aggregated report ID in the per_device_numeric_stats test
+// registry.
+TEST_F(PerDeviceNumericAggregateStoreTest, GenerateObservationsWithGc) {
+ int num_days = 35;
+ std::vector<Observation2> observations(0);
+ ExpectedAggregationParams expected_params =
+ logger::testing::per_device_numeric_stats::kExpectedAggregationParams;
+ for (int offset = 0; offset < num_days; offset++) {
+ auto day_index = CurrentDayIndex();
+ observations.clear();
+ ResetObservationStore();
+ EXPECT_EQ(kOK, GenerateObservations(day_index - 1));
+ EXPECT_TRUE(FetchAggregatedObservations(&observations, expected_params,
+ observation_store_.get(), update_recipient_.get()));
+ EXPECT_EQ(kOK, GarbageCollect(day_index));
+ for (int i = 0; i < 2; i++) {
+ EXPECT_EQ(kOK,
+ LogPerDeviceCountEvent(
+ logger::testing::per_device_numeric_stats::kConnectionFailuresMetricReportId,
+ day_index, "component_A", 0u, 1));
+ EXPECT_EQ(kOK,
+ LogPerDeviceCountEvent(
+ logger::testing::per_device_numeric_stats::kConnectionFailuresMetricReportId,
+ day_index, "component_A", 0u, 1));
+ EXPECT_EQ(
+ kOK,
+ LogPerDeviceCountEvent(
+ logger::testing::per_device_numeric_stats::kSettingsChangedWindowSizeMetricReportId,
+ day_index, "component_B", 0u, 5));
+ EXPECT_EQ(kOK, LogPerDeviceCountEvent(logger::testing::per_device_numeric_stats::
+ kSettingsChangedAggregationWindowMetricReportId,
+ day_index, "component_B", 0u, 5));
+ }
+ // If this is the first time we're logging events, update the expected
+ // numbers of generated Observations to account for the logged events.
+ // For each report, for each window size, expect 1 Observation more than if
+ // no events had been logged.
+ if (offset == 0) {
+ expected_params.daily_num_obs += 5;
+ expected_params.num_obs_per_report
+ [logger::testing::per_device_numeric_stats::kConnectionFailuresMetricReportId] += 1;
+ expected_params.num_obs_per_report
+ [logger::testing::per_device_numeric_stats::kSettingsChangedWindowSizeMetricReportId] +=
+ 2;
+ expected_params.num_obs_per_report[logger::testing::per_device_numeric_stats::
+ kSettingsChangedAggregationWindowMetricReportId] += 2;
+ }
+ AdvanceClock(kDay);
+ }
+ observations.clear();
+ ResetObservationStore();
+ auto day_index = CurrentDayIndex();
+ EXPECT_EQ(kOK, GenerateObservations(day_index - 1));
+ EXPECT_TRUE(FetchAggregatedObservations(&observations, expected_params, observation_store_.get(),
+ update_recipient_.get()));
+ EXPECT_EQ(kOK, GarbageCollect(day_index));
+}
+
+// Tests that GenerateObservations() returns a positive status and that the
+// expected number of Observations is generated when events are logged over
+// multiple days and some of those days' Observations are backfilled, without
+// any garbage collection of the LocalAggregateStore.
+//
+// Sets the |backfill_days_| field of the EventAggregator to 3.
+//
+// Logging pattern:
+// For 35 days, logs 2 events each day for the
+// ConnectionFailures_PerDeviceCount report and 2 events for the
+// SettingsChanged_PerDeviceCount report, all with event code 0.
+//
+// Observation generation pattern:
+// Calls GenerateObservations() on the 1st through 5th and the 7th out of
+// every 10 days, for 35 days.
+//
+// Expected numbers of Observations:
+// It is expected that 4 days' worth of Observations are generated on
+// the first day of every 10 (the day index for which GenerateObservations()
+// was called, plus 3 days of backfill), that 1 day's worth of Observations
+// are generated on the 2nd through 5th day of every 10, that 2 days'
+// worth of Observations are generated on the 7th day of every 10 (the
+// day index for which GenerateObservations() was called, plus 1 day of
+// backfill), and that no Observations are generated on the remaining days.
+TEST_F(PerDeviceNumericAggregateStoreTest, GenerateObservationsWithBackfill) {
+ const auto& expected_params =
+ logger::testing::per_device_numeric_stats::kExpectedAggregationParams;
+ // Set |backfill_days_| to 3.
+ size_t backfill_days = 3;
+ SetBackfillDays(backfill_days);
+ // Log 2 events each day for 35 days. Call GenerateObservations() on the
+ // first 5 day indices, and the 7th, out of every 10.
+ for (int offset = 0; offset < 35; offset++) {
+ auto day_index = CurrentDayIndex();
+ for (int i = 0; i < 2; i++) {
+ EXPECT_EQ(kOK,
+ LogPerDeviceCountEvent(
+ logger::testing::per_device_numeric_stats::kConnectionFailuresMetricReportId,
+ day_index, "component_A", 0u, 1));
+ EXPECT_EQ(kOK,
+ LogPerDeviceCountEvent(
+ logger::testing::per_device_numeric_stats::kConnectionFailuresMetricReportId,
+ day_index, "component_A", 0u, 1));
+ EXPECT_EQ(
+ kOK,
+ LogPerDeviceCountEvent(
+ logger::testing::per_device_numeric_stats::kSettingsChangedWindowSizeMetricReportId,
+ day_index, "component_B", 0u, 5));
+ EXPECT_EQ(kOK, LogPerDeviceCountEvent(logger::testing::per_device_numeric_stats::
+ kSettingsChangedAggregationWindowMetricReportId,
+ day_index, "component_B", 0u, 5));
+ }
+ auto num_obs_before = observation_store_->messages_received.size();
+ if (offset % 10 < 5 || offset % 10 == 6) {
+ EXPECT_EQ(kOK, GenerateObservations(day_index));
+ }
+ auto num_obs_after = observation_store_->messages_received.size();
+ EXPECT_GE(num_obs_after, num_obs_before);
+ // Check that the expected daily number of Observations was generated.
+ switch (offset % 10) {
+ case 0:
+ // If this is the first day of logging, expect 3 Observations for each
+ // day in the backfill period and 8 Observations for the current day.
+ if (offset == 0) {
+ EXPECT_EQ(
+ (expected_params.daily_num_obs * backfill_days) + expected_params.daily_num_obs + 5,
+ num_obs_after - num_obs_before);
+ } else {
+ // If this is another day whose offset is a multiple of 10, expect 8
+ // Observations for each day in the backfill period as well as the
+ // current day.
+ EXPECT_EQ((expected_params.daily_num_obs + 5) * (backfill_days + 1),
+ num_obs_after - num_obs_before);
+ }
+ break;
+ case 1:
+ case 2:
+ case 3:
+ case 4:
+ // Expect 8 Observations for this day.
+ EXPECT_EQ(expected_params.daily_num_obs + 5, num_obs_after - num_obs_before);
+ break;
+ case 6:
+ // Expect 8 Observations for each of today and yesterday.
+ EXPECT_EQ((expected_params.daily_num_obs + 5) * 2, num_obs_after - num_obs_before);
+ break;
+ default:
+ EXPECT_EQ(num_obs_after, num_obs_before);
+ }
+ AdvanceClock(kDay);
+ }
+}
+
+// Tests that GenerateObservations() returns a positive status and that the
+// expected number of Observations is generated when events are logged over
+// multiple days and some of those days' Observations are backfilled, and when
+// the LocalAggregateStore is garbage-collected after each call to
+// GenerateObservations().
+//
+// Sets the |backfill_days_| field of the EventAggregator to 3.
+//
+// Logging pattern:
+// For 35 days, logs 2 events each day for the
+// ConnectionFailures_PerDeviceNumeric report with "component_A" and 2 events
+// for the SettingsChanged_PerDeviceNumeric reports with "component_B", all with
+// event code 0.
+//
+// Observation generation pattern:
+// Calls GenerateObservations() on the 1st through 5th and the 7th out of
+// every 10 days, for 35 days. Garbage-collects the LocalAggregateStore after
+// each call.
+//
+// Expected numbers of Observations:
+// It is expected that 4 days' worth of Observations are generated on
+// the first day of every 10 (the day index for which GenerateObservations()
+// was called, plus 3 days of backfill), that 1 day's worth of Observations
+// are generated on the 2nd through 5th day of every 10, that 2 days'
+// worth of Observations are generated on the 7th day of every 10 (the
+// day index for which GenerateObservations() was called, plus 1 day of
+// backfill), and that no Observations are generated on the remaining days.
+TEST_F(PerDeviceNumericAggregateStoreTest, GenerateObservationsWithBackfillAndGc) {
+ int num_days = 35;
+ const auto& expected_params =
+ logger::testing::per_device_numeric_stats::kExpectedAggregationParams;
+ // Set |backfill_days_| to 3.
+ size_t backfill_days = 3;
+ SetBackfillDays(backfill_days);
+ // Log 2 events each day for 35 days. Call GenerateObservations() on the
+ // first 5 day indices, and the 7th, out of every 10.
+ for (int offset = 0; offset < num_days; offset++) {
+ auto day_index = CurrentDayIndex();
+ for (int i = 0; i < 2; i++) {
+ EXPECT_EQ(kOK,
+ LogPerDeviceCountEvent(
+ logger::testing::per_device_numeric_stats::kConnectionFailuresMetricReportId,
+ day_index, "component_A", 0u, 1));
+ EXPECT_EQ(
+ kOK,
+ LogPerDeviceCountEvent(
+ logger::testing::per_device_numeric_stats::kSettingsChangedWindowSizeMetricReportId,
+ day_index, "component_B", 0u, 5));
+ EXPECT_EQ(kOK, LogPerDeviceCountEvent(logger::testing::per_device_numeric_stats::
+ kSettingsChangedAggregationWindowMetricReportId,
+ day_index, "component_B", 0u, 5));
+ }
+ auto num_obs_before = observation_store_->messages_received.size();
+ if (offset % 10 < 5 || offset % 10 == 6) {
+ EXPECT_EQ(kOK, GenerateObservations(day_index));
+ EXPECT_EQ(kOK, GarbageCollect(day_index));
+ }
+ auto num_obs_after = observation_store_->messages_received.size();
+ EXPECT_GE(num_obs_after, num_obs_before);
+ // Check that the expected daily number of Observations was generated.
+ switch (offset % 10) {
+ case 0:
+ // If this is the first day of logging, expect 3 Observations for each
+ // day in the backfill period and 8 Observations for the current day.
+ if (offset == 0) {
+ EXPECT_EQ(
+ (expected_params.daily_num_obs * backfill_days) + expected_params.daily_num_obs + 5,
+ num_obs_after - num_obs_before);
+ } else {
+ // If this is another day whose offset is a multiple of 10, expect 8
+ // Observations for each day in the backfill period as well as the
+ // current day.
+ EXPECT_EQ((expected_params.daily_num_obs + 5) * (backfill_days + 1),
+ num_obs_after - num_obs_before);
+ }
+ break;
+ case 1:
+ case 2:
+ case 3:
+ case 4:
+ // Expect 8 Observations for this day.
+ EXPECT_EQ(expected_params.daily_num_obs + 5, num_obs_after - num_obs_before);
+ break;
+ case 6:
+ // Expect 6 Observations for each of today and yesterday.
+ EXPECT_EQ((expected_params.daily_num_obs + 5) * 2, num_obs_after - num_obs_before);
+ break;
+ default:
+ EXPECT_EQ(num_obs_after, num_obs_before);
+ }
+ AdvanceClock(kDay);
+ }
+}
+
+// Generate Observations without logging any events, and check that the
+// resulting Observations are as expected: 1 ReportParticipationObservation for
+// each PER_DEVICE_NUMERIC_STATS report in the config, and no
+// PerDeviceNumericObservations.
+TEST_F(PerDeviceNumericAggregateStoreTest, CheckObservationValuesNoEvents) {
+ const auto current_day_index = CurrentDayIndex();
+ EXPECT_EQ(kOK, GenerateObservations(current_day_index));
+ const auto& expected_report_participation_obs = MakeExpectedReportParticipationObservations(
+ logger::testing::per_device_numeric_stats::kExpectedAggregationParams, current_day_index);
+ EXPECT_TRUE(CheckPerDeviceNumericObservations({}, expected_report_participation_obs,
+ observation_store_.get(), update_recipient_.get()));
+}
+
+// Check that the expected PerDeviceNumericObservations and
+// ReportParticipationObservations are generated when GenerateObservations() is
+// called after logging some CountEvents and ElapsedTimeEvents for
+// PER_DEVICE_NUMERIC_STATS reports over a single day index.
+TEST_F(PerDeviceNumericAggregateStoreTest, CheckObservationValuesSingleDay) {
+ const auto day_index = CurrentDayIndex();
+ // Log several events on |day_index|.
+ EXPECT_EQ(kOK, LogPerDeviceCountEvent(
+ logger::testing::per_device_numeric_stats::kConnectionFailuresMetricReportId,
+ day_index, "component_A", 0u, 5));
+ EXPECT_EQ(kOK, LogPerDeviceCountEvent(
+ logger::testing::per_device_numeric_stats::kConnectionFailuresMetricReportId,
+ day_index, "component_B", 0u, 5));
+ EXPECT_EQ(kOK, LogPerDeviceCountEvent(
+ logger::testing::per_device_numeric_stats::kConnectionFailuresMetricReportId,
+ day_index, "component_A", 0u, 5));
+ EXPECT_EQ(kOK, LogPerDeviceCountEvent(
+ logger::testing::per_device_numeric_stats::kConnectionFailuresMetricReportId,
+ day_index, "component_A", 1u, 5));
+ EXPECT_EQ(kOK,
+ LogPerDeviceCountEvent(
+ logger::testing::per_device_numeric_stats::kSettingsChangedWindowSizeMetricReportId,
+ day_index, "component_C", 0u, 5));
+ EXPECT_EQ(kOK,
+ LogPerDeviceCountEvent(
+ logger::testing::per_device_numeric_stats::kSettingsChangedWindowSizeMetricReportId,
+ day_index, "component_C", 0u, 5));
+ EXPECT_EQ(kOK, LogPerDeviceCountEvent(logger::testing::per_device_numeric_stats::
+ kSettingsChangedAggregationWindowMetricReportId,
+ day_index, "component_C", 0u, 5));
+ EXPECT_EQ(kOK, LogPerDeviceCountEvent(logger::testing::per_device_numeric_stats::
+ kSettingsChangedAggregationWindowMetricReportId,
+ day_index, "component_C", 0u, 5));
+
+ std::vector<MetricReportId> streaming_time_ids = {
+ logger::testing::per_device_numeric_stats::kStreamingTimeTotalMetricReportId,
+ logger::testing::per_device_numeric_stats::kStreamingTimeMinMetricReportId,
+ logger::testing::per_device_numeric_stats::kStreamingTimeMaxMetricReportId};
+ for (const auto& id : streaming_time_ids) {
+ EXPECT_EQ(kOK, LogPerDeviceElapsedTimeEvent(id, day_index, "component_D", 0u, 15));
+ EXPECT_EQ(kOK, LogPerDeviceElapsedTimeEvent(id, day_index, "component_D", 1u, 5));
+ EXPECT_EQ(kOK, LogPerDeviceElapsedTimeEvent(id, day_index, "component_D", 0u, 10));
+ }
+ // Generate locally aggregated Observations for |day_index|.
+ EXPECT_EQ(kOK, GenerateObservations(day_index));
+
+ // Form the expected Observations.
+ auto expected_report_participation_obs = MakeExpectedReportParticipationObservations(
+ logger::testing::per_device_numeric_stats::kExpectedAggregationParams, day_index);
+ ExpectedPerDeviceNumericObservations expected_per_device_numeric_obs;
+ expected_per_device_numeric_obs[{
+ logger::testing::per_device_numeric_stats::kConnectionFailuresMetricReportId, day_index}][1] =
+ {{"component_A", 0u, 10}, {"component_A", 1u, 5}, {"component_B", 0u, 5}};
+ expected_per_device_numeric_obs[{
+ logger::testing::per_device_numeric_stats::kSettingsChangedWindowSizeMetricReportId,
+ day_index}][7] = {{"component_C", 0u, 10}};
+ expected_per_device_numeric_obs[{
+ logger::testing::per_device_numeric_stats::kSettingsChangedWindowSizeMetricReportId,
+ day_index}][30] = {{"component_C", 0u, 10}};
+ expected_per_device_numeric_obs[{
+ logger::testing::per_device_numeric_stats::kSettingsChangedAggregationWindowMetricReportId,
+ day_index}][7] = {{"component_C", 0u, 10}};
+ expected_per_device_numeric_obs[{
+ logger::testing::per_device_numeric_stats::kSettingsChangedAggregationWindowMetricReportId,
+ day_index}][30] = {{"component_C", 0u, 10}};
+ expected_per_device_numeric_obs[{
+ logger::testing::per_device_numeric_stats::kStreamingTimeTotalMetricReportId, day_index}][1] =
+ {{"component_D", 0u, 25}, {"component_D", 1u, 5}};
+ expected_per_device_numeric_obs[{
+ logger::testing::per_device_numeric_stats::kStreamingTimeTotalMetricReportId, day_index}][7] =
+ {{"component_D", 0u, 25}, {"component_D", 1u, 5}};
+ // The 7-day minimum value for the StreamingTime metric is 0 for all event
+ // codes and components, so we don't expect a PerDeviceNumericObservation with
+ // a 7-day window for the StreamingTime_PerDeviceMin report.
+ expected_per_device_numeric_obs[{
+ logger::testing::per_device_numeric_stats::kStreamingTimeMinMetricReportId, day_index}][1] = {
+ {"component_D", 0u, 10}, {"component_D", 1u, 5}};
+ expected_per_device_numeric_obs[{
+ logger::testing::per_device_numeric_stats::kStreamingTimeMinMetricReportId, day_index}][7] = {
+ {"component_D", 0u, 10}, {"component_D", 1u, 5}};
+ expected_per_device_numeric_obs[{
+ logger::testing::per_device_numeric_stats::kStreamingTimeMaxMetricReportId, day_index}][1] = {
+ {"component_D", 0u, 15}, {"component_D", 1u, 5}};
+ expected_per_device_numeric_obs[{
+ logger::testing::per_device_numeric_stats::kStreamingTimeMaxMetricReportId, day_index}][7] = {
+ {"component_D", 0u, 15}, {"component_D", 1u, 5}};
+
+ EXPECT_TRUE(CheckPerDeviceNumericObservations(expected_per_device_numeric_obs,
+ expected_report_participation_obs,
+ observation_store_.get(), update_recipient_.get()));
+}
+
+// Checks that PerDeviceNumericObservations with the expected values are
+// generated when some events have been logged for an EVENT_COUNT metric with
+// a PER_DEVICE_NUMERIC_STATS report over multiple days and
+// GenerateObservations() is called each day, without garbage collection or
+// backfill.
+//
+// Logged events for the SettingsChanged_PerDeviceCount report on the i-th
+// day:
+//
+// i (component, event code, count)
+// -----------------------------------------------------------------------
+// 0
+// 1 ("A", 1, 3)
+// 2 ("A", 1, 3), ("A", 2, 3), ("B", 1, 2)
+// 3 ("A", 1, 3)
+// 4 ("A", 1, 3), ("A", 2, 3), ("B", 1, 2), ("B", 2, 2)
+// 5 ("A", 1, 3)
+// 6 ("A", 1, 3), ("A", 2, 3), ("B", 1, 2)
+// 7 ("A", 1, 3)
+// 8 ("A", 1, 3), ("A", 2, 3), ("B", 1, 2), ("B", 2, 2)
+// 9 ("A", 1, 3)
+//
+// Expected PerDeviceNumericObservations for the
+// SettingsChanged_PerDeviceNumeric report on the i-th day:
+//
+// (i, window size) (component, event code, count)
+// -----------------------------------------------------------------------
+// (0, 7)
+// (0, 30)
+// (1, 7) ("A", 1, 3)
+// (1, 30) ("A", 1, 3)
+// (2, 7) ("A", 1, 6), ("A", 2, 3), ("B", 1, 2)
+// (2, 30) ("A", 1, 6), ("A", 2, 3), ("B", 1, 2)
+// (3, 7) ("A", 1, 9), ("A", 2, 3), ("B", 1, 2)
+// (3, 30) ("A", 1, 9), ("A", 2, 3), ("B", 1, 2)
+// (4, 7) ("A", 1, 12), ("A", 2, 6), ("B", 1, 4), ("B", 2, 2)
+// (4, 30) ("A", 1, 12), ("A", 2, 6), ("B", 1, 4), ("B", 2, 2)
+// (5, 7) ("A", 1, 15), ("A", 2, 6), ("B", 1, 4), ("B", 2, 2)
+// (5, 30) ("A", 1, 15), ("A", 2, 6), ("B", 1, 4), ("B", 2, 2)
+// (6, 7) ("A", 1, 18), ("A", 2, 9), ("B", 1, 6), ("B", 2, 2)
+// (6, 30) ("A", 1, 18), ("A", 2, 9), ("B", 1, 6), ("B", 2, 2)
+// (7, 7) ("A", 1, 21), ("A", 2, 9), ("B", 1, 6), ("B", 2, 2)
+// (7, 30) ("A", 1, 21), ("A", 2, 9), ("B", 1, 6), ("B", 2, 2)
+// (8, 7) ("A", 1, 21), ("A", 2, 12), ("B", 1, 8), ("B", 2, 4)
+// (8, 30) ("A", 1, 24), ("A", 2, 12), ("B", 1, 8), ("B", 2, 4)
+// (9, 7) ("A", 1, 21), ("A", 2, 9), ("B", 1, 6), ("B", 2, 4)
+// (9, 30) ("A", 1, 27), ("A", 2, 12), ("B", 1, 8), ("B", 2, 4)
+//
+// In addition, expect 1 ReportParticipationObservation each day for each of
+// the reports in the registry.
+TEST_F(PerDeviceNumericAggregateStoreTest, CheckObservationValuesMultiDay) {
+ auto start_day_index = CurrentDayIndex();
+ const auto& expected_id =
+ logger::testing::per_device_numeric_stats::kSettingsChangedAggregationWindowMetricReportId;
+ const auto& expected_params =
+ logger::testing::per_device_numeric_stats::kExpectedAggregationParams;
+ // Form expected Observations for the 10 days of logging.
+ uint32_t num_days = 10;
+ std::vector<ExpectedPerDeviceNumericObservations> expected_per_device_numeric_obs(num_days);
+ std::vector<ExpectedReportParticipationObservations> expected_report_participation_obs(num_days);
+ for (uint32_t offset = 0; offset < num_days; offset++) {
+ expected_report_participation_obs[offset] =
+ MakeExpectedReportParticipationObservations(expected_params, start_day_index + offset);
+ }
+ expected_per_device_numeric_obs[0] = {};
+ expected_per_device_numeric_obs[1][{expected_id, start_day_index + 1}] = {{7, {{"A", 1u, 3}}},
+ {30, {{"A", 1u, 3}}}};
+ expected_per_device_numeric_obs[2][{expected_id, start_day_index + 2}] = {
+ {7, {{"A", 1u, 6}, {"A", 2u, 3}, {"B", 1u, 2}}},
+ {30, {{"A", 1u, 6}, {"A", 2u, 3}, {"B", 1u, 2}}}};
+ expected_per_device_numeric_obs[3][{expected_id, start_day_index + 3}] = {
+ {7, {{"A", 1u, 9}, {"A", 2u, 3}, {"B", 1u, 2}}},
+ {30, {{"A", 1u, 9}, {"A", 2u, 3}, {"B", 1u, 2}}}};
+ expected_per_device_numeric_obs[4][{expected_id, start_day_index + 4}] = {
+ {7, {{"A", 1u, 12}, {"A", 2u, 6}, {"B", 1u, 4}, {"B", 2u, 2}}},
+ {30, {{"A", 1u, 12}, {"A", 2u, 6}, {"B", 1u, 4}, {"B", 2u, 2}}}};
+ expected_per_device_numeric_obs[5][{expected_id, start_day_index + 5}] = {
+ {7, {{"A", 1u, 15}, {"A", 2u, 6}, {"B", 1u, 4}, {"B", 2u, 2}}},
+ {30, {{"A", 1u, 15}, {"A", 2u, 6}, {"B", 1u, 4}, {"B", 2u, 2}}}};
+ expected_per_device_numeric_obs[6][{expected_id, start_day_index + 6}] = {
+ {7, {{"A", 1u, 18}, {"A", 2u, 9}, {"B", 1u, 6}, {"B", 2u, 2}}},
+ {30, {{"A", 1u, 18}, {"A", 2u, 9}, {"B", 1u, 6}, {"B", 2u, 2}}}};
+ expected_per_device_numeric_obs[7][{expected_id, start_day_index + 7}] = {
+ {7, {{"A", 1u, 21}, {"A", 2u, 9}, {"B", 1u, 6}, {"B", 2u, 2}}},
+ {30, {{"A", 1u, 21}, {"A", 2u, 9}, {"B", 1u, 6}, {"B", 2u, 2}}}};
+ expected_per_device_numeric_obs[8][{expected_id, start_day_index + 8}] = {
+ {7, {{"A", 1u, 21}, {"A", 2u, 12}, {"B", 1u, 8}, {"B", 2u, 4}}},
+ {30, {{"A", 1u, 24}, {"A", 2u, 12}, {"B", 1u, 8}, {"B", 2u, 4}}}};
+ expected_per_device_numeric_obs[9][{expected_id, start_day_index + 9}] = {
+ {7, {{"A", 1u, 21}, {"A", 2u, 9}, {"B", 1u, 6}, {"B", 2u, 4}}},
+ {30, {{"A", 1u, 27}, {"A", 2u, 12}, {"B", 1u, 8}, {"B", 2u, 4}}}};
+
+ for (uint32_t offset = 0; offset < 1; offset++) {
+ auto day_index = CurrentDayIndex();
+ for (uint32_t event_code = 1; event_code < 3; event_code++) {
+ if (offset > 0 && offset % event_code == 0) {
+ EXPECT_EQ(kOK, LogPerDeviceCountEvent(expected_id, day_index, "A", event_code, 3));
+ }
+ if (offset > 0 && offset % (2 * event_code) == 0) {
+ EXPECT_EQ(kOK, LogPerDeviceCountEvent(expected_id, day_index, "B", event_code, 2));
+ }
+ }
+ // Clear the FakeObservationStore.
+ ResetObservationStore();
+ // Generate locally aggregated Observations.
+ EXPECT_EQ(kOK, GenerateObservations(day_index));
+ EXPECT_TRUE(CheckPerDeviceNumericObservations(
+ expected_per_device_numeric_obs[offset], expected_report_participation_obs[offset],
+ observation_store_.get(), update_recipient_.get()))
+ << "offset = " << offset;
+ AdvanceClock(kDay);
+ }
+}
+
+// Repeat the CheckObservationValuesMultiDay test, this time calling
+// GarbageCollect() after each call to GenerateObservations.
+//
+// The logging pattern and set of Observations for each day index is the same
+// as in PerDeviceNumericAggregateStoreTest::CheckObservationValuesMultiDay.
+// See that test for documentation.
+TEST_F(PerDeviceNumericAggregateStoreTest, CheckObservationValuesMultiDayWithGarbageCollection) {
+ auto start_day_index = CurrentDayIndex();
+ const auto& expected_id =
+ logger::testing::per_device_numeric_stats::kSettingsChangedAggregationWindowMetricReportId;
+ const auto& expected_params =
+ logger::testing::per_device_numeric_stats::kExpectedAggregationParams;
+ // Form expected Observations for the 10 days of logging.
+ uint32_t num_days = 10;
+ std::vector<ExpectedPerDeviceNumericObservations> expected_per_device_numeric_obs(num_days);
+ std::vector<ExpectedReportParticipationObservations> expected_report_participation_obs(num_days);
+ for (uint32_t offset = 0; offset < num_days; offset++) {
+ expected_report_participation_obs[offset] =
+ MakeExpectedReportParticipationObservations(expected_params, start_day_index + offset);
+ }
+ expected_per_device_numeric_obs[0] = {};
+ expected_per_device_numeric_obs[1][{expected_id, start_day_index + 1}] = {{7, {{"A", 1u, 3}}},
+ {30, {{"A", 1u, 3}}}};
+ expected_per_device_numeric_obs[2][{expected_id, start_day_index + 2}] = {
+ {7, {{"A", 1u, 6}, {"A", 2u, 3}, {"B", 1u, 2}}},
+ {30, {{"A", 1u, 6}, {"A", 2u, 3}, {"B", 1u, 2}}}};
+ expected_per_device_numeric_obs[3][{expected_id, start_day_index + 3}] = {
+ {7, {{"A", 1u, 9}, {"A", 2u, 3}, {"B", 1u, 2}}},
+ {30, {{"A", 1u, 9}, {"A", 2u, 3}, {"B", 1u, 2}}}};
+ expected_per_device_numeric_obs[4][{expected_id, start_day_index + 4}] = {
+ {7, {{"A", 1u, 12}, {"A", 2u, 6}, {"B", 1u, 4}, {"B", 2u, 2}}},
+ {30, {{"A", 1u, 12}, {"A", 2u, 6}, {"B", 1u, 4}, {"B", 2u, 2}}}};
+ expected_per_device_numeric_obs[5][{expected_id, start_day_index + 5}] = {
+ {7, {{"A", 1u, 15}, {"A", 2u, 6}, {"B", 1u, 4}, {"B", 2u, 2}}},
+ {30, {{"A", 1u, 15}, {"A", 2u, 6}, {"B", 1u, 4}, {"B", 2u, 2}}}};
+ expected_per_device_numeric_obs[6][{expected_id, start_day_index + 6}] = {
+ {7, {{"A", 1u, 18}, {"A", 2u, 9}, {"B", 1u, 6}, {"B", 2u, 2}}},
+ {30, {{"A", 1u, 18}, {"A", 2u, 9}, {"B", 1u, 6}, {"B", 2u, 2}}}};
+ expected_per_device_numeric_obs[7][{expected_id, start_day_index + 7}] = {
+ {7, {{"A", 1u, 21}, {"A", 2u, 9}, {"B", 1u, 6}, {"B", 2u, 2}}},
+ {30, {{"A", 1u, 21}, {"A", 2u, 9}, {"B", 1u, 6}, {"B", 2u, 2}}}};
+ expected_per_device_numeric_obs[8][{expected_id, start_day_index + 8}] = {
+ {7, {{"A", 1u, 21}, {"A", 2u, 12}, {"B", 1u, 8}, {"B", 2u, 4}}},
+ {30, {{"A", 1u, 24}, {"A", 2u, 12}, {"B", 1u, 8}, {"B", 2u, 4}}}};
+ expected_per_device_numeric_obs[9][{expected_id, start_day_index + 9}] = {
+ {7, {{"A", 1u, 21}, {"A", 2u, 9}, {"B", 1u, 6}, {"B", 2u, 4}}},
+ {30, {{"A", 1u, 27}, {"A", 2u, 12}, {"B", 1u, 8}, {"B", 2u, 4}}}};
+
+ for (uint32_t offset = 0; offset < 10; offset++) {
+ auto day_index = CurrentDayIndex();
+ for (uint32_t event_code = 1; event_code < 3; event_code++) {
+ if (offset > 0 && offset % event_code == 0) {
+ EXPECT_EQ(kOK, LogPerDeviceCountEvent(expected_id, day_index, "A", event_code, 3));
+ }
+ if (offset > 0 && offset % (2 * event_code) == 0) {
+ EXPECT_EQ(kOK, LogPerDeviceCountEvent(expected_id, day_index, "B", event_code, 2));
+ }
+ }
+ // Advance |test_clock_| by 1 day.
+ AdvanceClock(kDay);
+ // Clear the FakeObservationStore.
+ ResetObservationStore();
+ // Generate locally aggregated Observations and garbage-collect the
+ // LocalAggregateStore, both for the previous day as measured by
+ // |test_clock_|. Back up the LocalAggregateStore and
+ // AggregatedObservationHistoryStore.
+ DoScheduledTasksNow();
+ EXPECT_TRUE(CheckPerDeviceNumericObservations(
+ expected_per_device_numeric_obs[offset], expected_report_participation_obs[offset],
+ observation_store_.get(), update_recipient_.get()));
+ }
+}
+
+// Tests that the expected PerDeviceNumericObservations are generated when
+// events are logged over multiple days for an EVENT_COUNT
+// metric with a PER_DEVICE_NUMERIC_STATS report, when Observations are
+// backfilled for some days during that period, without any garbage-collection
+// of the LocalAggregateStore.
+//
+// The logging pattern and set of Observations for each day index is the same
+// as in PerDeviceNumericAggregateStoreTest::CheckObservationValuesMultiDay.
+// See that test for documentation.
+TEST_F(PerDeviceNumericAggregateStoreTest, CheckObservationValuesWithBackfill) {
+ auto start_day_index = CurrentDayIndex();
+ const auto& expected_id =
+ logger::testing::per_device_numeric_stats::kSettingsChangedAggregationWindowMetricReportId;
+ const auto& expected_params =
+ logger::testing::per_device_numeric_stats::kExpectedAggregationParams;
+ // Set |backfill_days_| to 3.
+ size_t backfill_days = 3;
+ SetBackfillDays(backfill_days);
+ // Log events for 9 days. Call GenerateObservations() on the first 6 day
+ // indices, and the 9th.
+ uint32_t num_days = 9;
+ for (uint32_t offset = 0; offset < num_days; offset++) {
+ auto day_index = CurrentDayIndex();
+ ResetObservationStore();
+ for (uint32_t event_code = 1; event_code < 3; event_code++) {
+ if (offset > 0 && (offset % event_code == 0)) {
+ EXPECT_EQ(kOK, LogPerDeviceCountEvent(expected_id, day_index, "A", event_code, 3));
+ }
+ if (offset > 0 && offset % (2 * event_code) == 0) {
+ EXPECT_EQ(kOK, LogPerDeviceCountEvent(expected_id, day_index, "B", event_code, 2));
+ }
+ }
+ if (offset < 6 || offset == 8) {
+ EXPECT_EQ(kOK, GenerateObservations(day_index));
+ }
+ // Make the set of Observations which are expected to be generated on
+ // |start_day_index + offset| and check it against the contents of the
+ // FakeObservationStore.
+ ExpectedPerDeviceNumericObservations expected_per_device_numeric_obs;
+ ExpectedReportParticipationObservations expected_report_participation_obs;
+ switch (offset) {
+ case 0: {
+ for (uint32_t day_index = start_day_index - backfill_days; day_index <= start_day_index;
+ day_index++) {
+ for (const auto& pair :
+ MakeExpectedReportParticipationObservations(expected_params, day_index)) {
+ expected_report_participation_obs.insert(pair);
+ }
+ }
+ EXPECT_TRUE(CheckPerDeviceNumericObservations(
+ expected_per_device_numeric_obs, expected_report_participation_obs,
+ observation_store_.get(), update_recipient_.get()));
+ break;
+ }
+ case 1: {
+ expected_per_device_numeric_obs[{expected_id, day_index}] = {{7, {{"A", 1u, 3}}},
+ {30, {{"A", 1u, 3}}}};
+ expected_report_participation_obs =
+ MakeExpectedReportParticipationObservations(expected_params, day_index);
+ EXPECT_TRUE(CheckPerDeviceNumericObservations(
+ expected_per_device_numeric_obs, expected_report_participation_obs,
+ observation_store_.get(), update_recipient_.get()));
+ break;
+ }
+ case 2: {
+ expected_per_device_numeric_obs[{expected_id, day_index}] = {
+ {7, {{"A", 1u, 6}, {"A", 2u, 3}, {"B", 1u, 2}}},
+ {30, {{"A", 1u, 6}, {"A", 2u, 3}, {"B", 1u, 2}}}};
+ expected_report_participation_obs =
+ MakeExpectedReportParticipationObservations(expected_params, day_index);
+ EXPECT_TRUE(CheckPerDeviceNumericObservations(
+ expected_per_device_numeric_obs, expected_report_participation_obs,
+ observation_store_.get(), update_recipient_.get()));
+ break;
+ }
+ case 3: {
+ expected_per_device_numeric_obs[{expected_id, day_index}] = {
+ {7, {{"A", 1u, 9}, {"A", 2u, 3}, {"B", 1u, 2}}},
+ {30, {{"A", 1u, 9}, {"A", 2u, 3}, {"B", 1u, 2}}}};
+ expected_report_participation_obs =
+ MakeExpectedReportParticipationObservations(expected_params, day_index);
+ EXPECT_TRUE(CheckPerDeviceNumericObservations(
+ expected_per_device_numeric_obs, expected_report_participation_obs,
+ observation_store_.get(), update_recipient_.get()));
+ break;
+ }
+ case 4: {
+ expected_per_device_numeric_obs[{expected_id, day_index}] = {
+ {7, {{"A", 1u, 12}, {"A", 2u, 6}, {"B", 1u, 4}, {"B", 2u, 2}}},
+ {30, {{"A", 1u, 12}, {"A", 2u, 6}, {"B", 1u, 4}, {"B", 2u, 2}}}};
+ expected_report_participation_obs =
+ MakeExpectedReportParticipationObservations(expected_params, day_index);
+ EXPECT_TRUE(CheckPerDeviceNumericObservations(
+ expected_per_device_numeric_obs, expected_report_participation_obs,
+ observation_store_.get(), update_recipient_.get()));
+ break;
+ }
+ case 5: {
+ expected_per_device_numeric_obs[{expected_id, day_index}] = {
+ {7, {{"A", 1u, 15}, {"A", 2u, 6}, {"B", 1u, 4}, {"B", 2u, 2}}},
+ {30, {{"A", 1u, 15}, {"A", 2u, 6}, {"B", 1u, 4}, {"B", 2u, 2}}}};
+ expected_report_participation_obs =
+ MakeExpectedReportParticipationObservations(expected_params, day_index);
+ EXPECT_TRUE(CheckPerDeviceNumericObservations(
+ expected_per_device_numeric_obs, expected_report_participation_obs,
+ observation_store_.get(), update_recipient_.get()));
+ break;
+ }
+ case 8: {
+ expected_per_device_numeric_obs[{expected_id, start_day_index + 6}] = {
+ {7, {{"A", 1u, 18}, {"A", 2u, 9}, {"B", 1u, 6}, {"B", 2u, 2}}},
+ {30, {{"A", 1u, 18}, {"A", 2u, 9}, {"B", 1u, 6}, {"B", 2u, 2}}}};
+ expected_per_device_numeric_obs[{expected_id, start_day_index + 7}] = {
+ {7, {{"A", 1u, 21}, {"A", 2u, 9}, {"B", 1u, 6}, {"B", 2u, 2}}},
+ {30, {{"A", 1u, 21}, {"A", 2u, 9}, {"B", 1u, 6}, {"B", 2u, 2}}}};
+ expected_per_device_numeric_obs[{expected_id, start_day_index + 8}] = {
+ {7, {{"A", 1u, 21}, {"A", 2u, 12}, {"B", 1u, 8}, {"B", 2u, 4}}},
+ {30, {{"A", 1u, 24}, {"A", 2u, 12}, {"B", 1u, 8}, {"B", 2u, 4}}}};
+ for (uint32_t day_index = start_day_index + 6; day_index <= start_day_index + 8;
+ day_index++) {
+ for (const auto& pair :
+ MakeExpectedReportParticipationObservations(expected_params, day_index)) {
+ expected_report_participation_obs.insert(pair);
+ }
+ }
+ EXPECT_TRUE(CheckPerDeviceNumericObservations(
+ expected_per_device_numeric_obs, expected_report_participation_obs,
+ observation_store_.get(), update_recipient_.get()));
+ break;
+ }
+ default:
+ EXPECT_TRUE(CheckPerDeviceNumericObservations(
+ expected_per_device_numeric_obs, expected_report_participation_obs,
+ observation_store_.get(), update_recipient_.get()));
+ }
+ AdvanceClock(kDay);
+ }
+}
+
+// Tests that the expected Observations are generated for
+// PerDeviceNumericStats reports when events are logged for over multiple days
+// for an EVENT_COUNT metric with a PER_DEVICE_NUMERIC_STATS report, when
+// Observations are backfilled for some days during that period, and when the
+// LocalAggregatedStore is garbage-collected after each call to
+// GenerateObservations().
+//
+// The logging pattern and set of Observations for each day index is the same
+// as in PerDeviceNumericAggregateStoreTest::CheckObservationValuesMultiDay.
+// See that test for documentation.
+TEST_F(PerDeviceNumericAggregateStoreTest, EventCountCheckObservationValuesWithBackfillAndGc) {
+ auto start_day_index = CurrentDayIndex();
+ const auto& expected_id =
+ logger::testing::per_device_numeric_stats::kSettingsChangedAggregationWindowMetricReportId;
+ const auto& expected_params =
+ logger::testing::per_device_numeric_stats::kExpectedAggregationParams;
+ // Set |backfill_days_| to 3.
+ size_t backfill_days = 3;
+ SetBackfillDays(backfill_days);
+ // Log events for 9 days. Call GenerateObservations() on the first 6 day
+ // indices, and the 9th.
+ uint32_t num_days = 9;
+ for (uint32_t offset = 0; offset < num_days; offset++) {
+ auto day_index = CurrentDayIndex();
+ ResetObservationStore();
+ for (uint32_t event_code = 1; event_code < 3; event_code++) {
+ if (offset > 0 && (offset % event_code == 0)) {
+ EXPECT_EQ(kOK, LogPerDeviceCountEvent(expected_id, day_index, "A", event_code, 3));
+ }
+ if (offset > 0 && offset % (2 * event_code) == 0) {
+ EXPECT_EQ(kOK, LogPerDeviceCountEvent(expected_id, day_index, "B", event_code, 2));
+ }
+ }
+ // Advance |test_clock_| by 1 day.
+ AdvanceClock(kDay);
+ if (offset < 6 || offset == 8) {
+ // Generate Observations and garbage-collect, both for the previous day
+ // index according to |test_clock_|. Back up the LocalAggregateStore and
+ // the AggregatedObservationHistoryStore.
+ DoScheduledTasksNow();
+ }
+ // Make the set of Observations which are expected to be generated on
+ // |start_day_index + offset| and check it against the contents of the
+ // FakeObservationStore.
+ ExpectedPerDeviceNumericObservations expected_per_device_numeric_obs;
+ ExpectedReportParticipationObservations expected_report_participation_obs;
+ switch (offset) {
+ case 0: {
+ for (uint32_t day_index = start_day_index - backfill_days; day_index <= start_day_index;
+ day_index++) {
+ for (const auto& pair :
+ MakeExpectedReportParticipationObservations(expected_params, day_index)) {
+ expected_report_participation_obs.insert(pair);
+ }
+ }
+ EXPECT_TRUE(CheckPerDeviceNumericObservations(
+ expected_per_device_numeric_obs, expected_report_participation_obs,
+ observation_store_.get(), update_recipient_.get()));
+ break;
+ }
+ case 1: {
+ expected_per_device_numeric_obs[{expected_id, day_index}] = {{7, {{"A", 1u, 3}}},
+ {30, {{"A", 1u, 3}}}};
+ expected_report_participation_obs =
+ MakeExpectedReportParticipationObservations(expected_params, day_index);
+ EXPECT_TRUE(CheckPerDeviceNumericObservations(
+ expected_per_device_numeric_obs, expected_report_participation_obs,
+ observation_store_.get(), update_recipient_.get()));
+ break;
+ }
+ case 2: {
+ expected_per_device_numeric_obs[{expected_id, day_index}] = {
+ {7, {{"A", 1u, 6}, {"A", 2u, 3}, {"B", 1u, 2}}},
+ {30, {{"A", 1u, 6}, {"A", 2u, 3}, {"B", 1u, 2}}}};
+ expected_report_participation_obs =
+ MakeExpectedReportParticipationObservations(expected_params, day_index);
+ EXPECT_TRUE(CheckPerDeviceNumericObservations(
+ expected_per_device_numeric_obs, expected_report_participation_obs,
+ observation_store_.get(), update_recipient_.get()));
+ break;
+ }
+ case 3: {
+ expected_per_device_numeric_obs[{expected_id, day_index}] = {
+ {7, {{"A", 1u, 9}, {"A", 2u, 3}, {"B", 1u, 2}}},
+ {30, {{"A", 1u, 9}, {"A", 2u, 3}, {"B", 1u, 2}}}};
+ expected_report_participation_obs =
+ MakeExpectedReportParticipationObservations(expected_params, day_index);
+ EXPECT_TRUE(CheckPerDeviceNumericObservations(
+ expected_per_device_numeric_obs, expected_report_participation_obs,
+ observation_store_.get(), update_recipient_.get()));
+ break;
+ }
+ case 4: {
+ expected_per_device_numeric_obs[{expected_id, day_index}] = {
+ {7, {{"A", 1u, 12}, {"A", 2u, 6}, {"B", 1u, 4}, {"B", 2u, 2}}},
+ {30, {{"A", 1u, 12}, {"A", 2u, 6}, {"B", 1u, 4}, {"B", 2u, 2}}}};
+ expected_report_participation_obs =
+ MakeExpectedReportParticipationObservations(expected_params, day_index);
+ EXPECT_TRUE(CheckPerDeviceNumericObservations(
+ expected_per_device_numeric_obs, expected_report_participation_obs,
+ observation_store_.get(), update_recipient_.get()));
+ break;
+ }
+ case 5: {
+ expected_per_device_numeric_obs[{expected_id, day_index}] = {
+ {7, {{"A", 1u, 15}, {"A", 2u, 6}, {"B", 1u, 4}, {"B", 2u, 2}}},
+ {30, {{"A", 1u, 15}, {"A", 2u, 6}, {"B", 1u, 4}, {"B", 2u, 2}}}};
+ expected_report_participation_obs =
+ MakeExpectedReportParticipationObservations(expected_params, day_index);
+ EXPECT_TRUE(CheckPerDeviceNumericObservations(
+ expected_per_device_numeric_obs, expected_report_participation_obs,
+ observation_store_.get(), update_recipient_.get()));
+ break;
+ }
+ case 8: {
+ expected_per_device_numeric_obs[{expected_id, start_day_index + 6}] = {
+ {7, {{"A", 1u, 18}, {"A", 2u, 9}, {"B", 1u, 6}, {"B", 2u, 2}}},
+ {30, {{"A", 1u, 18}, {"A", 2u, 9}, {"B", 1u, 6}, {"B", 2u, 2}}}};
+ expected_per_device_numeric_obs[{expected_id, start_day_index + 7}] = {
+ {7, {{"A", 1u, 21}, {"A", 2u, 9}, {"B", 1u, 6}, {"B", 2u, 2}}},
+ {30, {{"A", 1u, 21}, {"A", 2u, 9}, {"B", 1u, 6}, {"B", 2u, 2}}}};
+ expected_per_device_numeric_obs[{expected_id, start_day_index + 8}] = {
+ {7, {{"A", 1u, 21}, {"A", 2u, 12}, {"B", 1u, 8}, {"B", 2u, 4}}},
+ {30, {{"A", 1u, 24}, {"A", 2u, 12}, {"B", 1u, 8}, {"B", 2u, 4}}}};
+ for (uint32_t day_index = start_day_index + 6; day_index <= start_day_index + 8;
+ day_index++) {
+ for (const auto& pair :
+ MakeExpectedReportParticipationObservations(expected_params, day_index)) {
+ expected_report_participation_obs.insert(pair);
+ }
+ }
+ EXPECT_TRUE(CheckPerDeviceNumericObservations(
+ expected_per_device_numeric_obs, expected_report_participation_obs,
+ observation_store_.get(), update_recipient_.get()));
+ break;
+ }
+ default:
+ EXPECT_TRUE(CheckPerDeviceNumericObservations(
+ expected_per_device_numeric_obs, expected_report_participation_obs,
+ observation_store_.get(), update_recipient_.get()));
+ }
+ }
+}
+
+// Tests that the expected Observations are generated for
+// PerDeviceNumericStats reports when events are logged for over multiple days
+// for an ELAPSED_TIME metric with PER_DEVICE_NUMERIC_STATS reports with
+// multiple aggregation types, when Observations are backfilled for some days
+// during that period, and when the LocalAggregatedStore is garbage-collected
+// after each call to GenerateObservations().
+//
+// Logged events for the StreamingTime_PerDevice{Total, Min, Max} reports on the
+// i-th day:
+//
+// i (component, event code, count)
+// -----------------------------------------------------------------------
+// 0
+// 1 ("A", 1, 3)
+// 2 ("A", 1, 3), ("A", 2, 3), ("B", 1, 2)
+// 3 ("A", 1, 3)
+// 4 ("A", 1, 3), ("A", 2, 3), ("B", 1, 2), ("B", 2, 2)
+// 5 ("A", 1, 3)
+// 6 ("A", 1, 3), ("A", 2, 3), ("B", 1, 2)
+// 7 ("A", 1, 3)
+// 8 ("A", 1, 3), ("A", 2, 3), ("B", 1, 2), ("B", 2, 2)
+//
+// Expected PerDeviceNumericObservations for the
+// StreamingTime_PerDeviceTotal report on the i-th day:
+//
+// (day, window size) (event code, component, total)
+// ---------------------------------------------------------------------------
+// (0, 1)
+// (0, 7)
+// (1, 1) ("A", 1, 3)
+// (1, 7) ("A", 1, 3)
+// (2, 1) ("A", 1, 3), ("A", 2, 3), ("B", 1, 2)
+// (2, 7) ("A", 1, 6), ("A", 2, 3), ("B", 1, 2)
+// (3, 1) ("A", 1, 3)
+// (3, 7) ("A", 1, 9), ("A", 2, 3), ("B", 1, 2)
+// (4, 1) ("A", 1, 3), ("A", 2, 3), ("B", 1, 2), ("B", 2, 2)
+// (4, 7) ("A", 1, 12), ("A", 2, 6), ("B", 1, 4), ("B", 2, 2)
+// (5, 1) ("A", 1, 3)
+// (5, 7) ("A", 1, 15), ("A", 2, 6), ("B", 1, 4), ("B", 2, 2)
+// (6, 1) ("A", 1, 3), ("A", 2, 3), ("B", 1, 2)
+// (6, 7) ("A", 1, 18), ("A", 2, 9), ("B", 1, 6), ("B", 2, 2)
+// (7, 1) ("A", 1, 3)
+// (7, 7) ("A", 1, 21), ("A", 2, 9), ("B", 1, 6), ("B", 2, 2)
+// (8, 1) ("A", 1, 3), ("A", 2, 3), ("B", 1, 2), ("B", 2, 2)
+// (8, 7) ("A", 1, 21), ("A", 2, 12), ("B", 1, 8), ("B", 2, 4)
+//
+// Expected PerDeviceNumericObservations for the
+// StreamingTime_PerDeviceMin report on the i-th day:
+//
+// (day, window size) (event code, component, total)
+// ---------------------------------------------------------------------------
+// (0, 1)
+// (0. 7)
+// (1, 1) ("A", 1, 3)
+// (1, 7) ("A", 1, 3)
+// (2, 1) ("A", 1, 3), ("A", 2, 3), ("B", 1, 2)
+// (2, 7) ("A", 1, 3), ("A", 2, 3), ("B", 1, 2)
+// (3, 1) ("A", 1, 3)
+// (3, 7) ("A", 1, 3), ("A", 2, 3), ("B", 1, 2)
+// (4, 1) ("A", 1, 3), ("A", 2, 3), ("B", 1, 2), ("B", 2, 2)
+// (4, 7) ("A", 1, 3), ("A", 2, 3), ("B", 1, 2), ("B", 2, 2)
+// (5, 1) ("A", 1, 3)
+// (5, 7) ("A", 1, 3), ("A", 2, 3), ("B", 1, 2), ("B", 2, 2)
+// (6, 1) ("A", 1, 3), ("A", 2, 3), ("B", 1, 2)
+// (6, 7) ("A", 1, 3), ("A", 2, 3), ("B", 1, 2), ("B", 2, 2)
+// (7, 1) ("A", 1, 3)
+// (7, 7) ("A", 1, 3), ("A", 2, 3), ("B", 1, 2), ("B", 2, 2)
+// (8, 1) ("A", 1, 3), ("A", 2, 3), ("B", 1, 2), ("B", 2, 2)
+// (8, 7) ("A", 1, 3), ("A", 2, 3), ("B", 1, 2), ("B", 2, 2)
+//
+// Expected PerDeviceNumericObservations for the
+// StreamingTime_PerDeviceMax report on the i-th day:
+//
+// (day, window size) (event code, component, total)
+// ---------------------------------------------------------------------------
+// (0, 1)
+// (0. 7)
+// (1, 1) ("A", 1, 3)
+// (1, 7) ("A", 1, 3)
+// (2, 1) ("A", 1, 3), ("A", 2, 3), ("B", 1, 2)
+// (2, 7) ("A", 1, 3), ("A", 2, 3), ("B", 1, 2)
+// (3, 1) ("A", 1, 3)
+// (3, 7) ("A", 1, 3), ("A", 2, 3), ("B", 1, 2)
+// (4, 1) ("A", 1, 3), ("A", 2, 3), ("B", 1, 2), ("B", 2, 2)
+// (4, 7) ("A", 1, 3), ("A", 2, 3), ("B", 1, 2), ("B", 2, 2)
+// (5, 1) ("A", 1, 3)
+// (5, 7) ("A", 1, 3), ("A", 2, 3), ("B", 1, 2), ("B", 2, 2)
+// (6, 1) ("A", 1, 3), ("A", 2, 3), ("B", 1, 2)
+// (6, 7) ("A", 1, 3), ("A", 2, 3), ("B", 1, 2), ("B", 2, 2)
+// (7, 1) ("A", 1, 3)
+// (7, 7) ("A", 1, 3), ("A", 2, 3), ("B", 1, 2), ("B", 2, 2)
+// (8, 1) ("A", 1, 3), ("A", 2, 3), ("B", 1, 2), ("B", 2, 2)
+// (8, 7) ("A", 1, 3), ("A", 2, 3), ("B", 1, 2), ("B", 2, 2)
+//
+// In addition, expect 1 ReportParticipationObservation each day for each
+// report in the registry.
+TEST_F(PerDeviceNumericAggregateStoreTest, ElapsedTimeCheckObservationValuesWithBackfillAndGc) {
+ auto start_day_index = CurrentDayIndex();
+ const auto& total_report_id =
+ logger::testing::per_device_numeric_stats::kStreamingTimeTotalMetricReportId;
+ const auto& min_report_id =
+ logger::testing::per_device_numeric_stats::kStreamingTimeMinMetricReportId;
+ const auto& max_report_id =
+ logger::testing::per_device_numeric_stats::kStreamingTimeMaxMetricReportId;
+ std::vector<MetricReportId> streaming_time_ids = {total_report_id, min_report_id, max_report_id};
+ const auto& expected_params =
+ logger::testing::per_device_numeric_stats::kExpectedAggregationParams;
+ // Set |backfill_days_| to 3.
+ size_t backfill_days = 3;
+ SetBackfillDays(backfill_days);
+ // Log events for 9 days. Call GenerateObservations() on the first 6 day
+ // indices, and the 9th.
+ uint32_t num_days = 9;
+ for (uint32_t offset = 0; offset < num_days; offset++) {
+ auto day_index = CurrentDayIndex();
+ ResetObservationStore();
+ for (uint32_t event_code = 1; event_code < 3; event_code++) {
+ for (const auto& report_id : streaming_time_ids) {
+ if (offset > 0 && (offset % event_code == 0)) {
+ EXPECT_EQ(kOK, LogPerDeviceElapsedTimeEvent(report_id, day_index, "A", event_code, 3));
+ }
+ if (offset > 0 && offset % (2 * event_code) == 0) {
+ EXPECT_EQ(kOK, LogPerDeviceElapsedTimeEvent(report_id, day_index, "B", event_code, 2));
+ }
+ }
+ }
+
+ // Advance |test_clock_| by 1 day.
+ AdvanceClock(kDay);
+ if (offset < 6 || offset == 8) {
+ // Generate Observations and garbage-collect, both for the previous day
+ // index according to |test_clock_|. Back up the LocalAggregateStore and
+ // the AggregatedObservationHistoryStore.
+ DoScheduledTasksNow();
+ }
+ // Make the set of Observations which are expected to be generated on
+ // |start_day_index + offset| and check it against the contents of the
+ // FakeObservationStore.
+ ExpectedPerDeviceNumericObservations expected_per_device_numeric_obs;
+ ExpectedReportParticipationObservations expected_report_participation_obs;
+ switch (offset) {
+ case 0: {
+ for (uint32_t day_index = start_day_index - backfill_days; day_index <= start_day_index;
+ day_index++) {
+ for (const auto& pair :
+ MakeExpectedReportParticipationObservations(expected_params, day_index)) {
+ expected_report_participation_obs.insert(pair);
+ }
+ }
+ EXPECT_TRUE(CheckPerDeviceNumericObservations(
+ expected_per_device_numeric_obs, expected_report_participation_obs,
+ observation_store_.get(), update_recipient_.get()));
+ break;
+ }
+ case 1: {
+ expected_per_device_numeric_obs[{total_report_id, day_index}] = {{1, {{"A", 1u, 3}}},
+ {7, {{"A", 1u, 3}}}};
+ expected_per_device_numeric_obs[{min_report_id, day_index}] = {{1, {{"A", 1u, 3}}},
+ {7, {{"A", 1u, 3}}}};
+ expected_per_device_numeric_obs[{max_report_id, day_index}] = {{1, {{"A", 1u, 3}}},
+ {7, {{"A", 1u, 3}}}};
+ expected_report_participation_obs =
+ MakeExpectedReportParticipationObservations(expected_params, day_index);
+ EXPECT_TRUE(CheckPerDeviceNumericObservations(
+ expected_per_device_numeric_obs, expected_report_participation_obs,
+ observation_store_.get(), update_recipient_.get()))
+ << "day 1";
+ break;
+ }
+ case 2: {
+ expected_per_device_numeric_obs[{total_report_id, day_index}] = {
+ {1, {{"A", 1u, 3}, {"A", 2u, 3}, {"B", 1u, 2}}},
+ {7, {{"A", 1u, 6}, {"A", 2u, 3}, {"B", 1u, 2}}}};
+ expected_per_device_numeric_obs[{min_report_id, day_index}] = {
+ {1, {{"A", 1u, 3}, {"A", 2u, 3}, {"B", 1u, 2}}},
+ {7, {{"A", 1u, 3}, {"A", 2u, 3}, {"B", 1u, 2}}}};
+ expected_per_device_numeric_obs[{max_report_id, day_index}] = {
+ {1, {{"A", 1u, 3}, {"A", 2u, 3}, {"B", 1u, 2}}},
+ {7, {{"A", 1u, 3}, {"A", 2u, 3}, {"B", 1u, 2}}}};
+ expected_report_participation_obs =
+ MakeExpectedReportParticipationObservations(expected_params, day_index);
+ EXPECT_TRUE(CheckPerDeviceNumericObservations(
+ expected_per_device_numeric_obs, expected_report_participation_obs,
+ observation_store_.get(), update_recipient_.get()));
+ break;
+ }
+ case 3: {
+ expected_per_device_numeric_obs[{total_report_id, day_index}] = {
+ {1, {{"A", 1u, 3}}}, {7, {{"A", 1u, 9}, {"A", 2u, 3}, {"B", 1u, 2}}}};
+ expected_per_device_numeric_obs[{min_report_id, day_index}] = {
+ {1, {{"A", 1u, 3}}}, {7, {{"A", 1u, 3}, {"A", 2u, 3}, {"B", 1u, 2}}}};
+ expected_per_device_numeric_obs[{max_report_id, day_index}] = {
+ {1, {{"A", 1u, 3}}}, {7, {{"A", 1u, 3}, {"A", 2u, 3}, {"B", 1u, 2}}}};
+ expected_report_participation_obs =
+ MakeExpectedReportParticipationObservations(expected_params, day_index);
+ EXPECT_TRUE(CheckPerDeviceNumericObservations(
+ expected_per_device_numeric_obs, expected_report_participation_obs,
+ observation_store_.get(), update_recipient_.get()));
+ break;
+ }
+ case 4: {
+ expected_per_device_numeric_obs[{total_report_id, day_index}] = {
+ {1, {{"A", 1u, 3}, {"A", 2u, 3}, {"B", 1u, 2}, {"B", 2u, 2}}},
+ {7, {{"A", 1u, 12}, {"A", 2u, 6}, {"B", 1u, 4}, {"B", 2u, 2}}}};
+ expected_per_device_numeric_obs[{min_report_id, day_index}] = {
+ {1, {{"A", 1u, 3}, {"A", 2u, 3}, {"B", 1u, 2}, {"B", 2u, 2}}},
+ {7, {{"A", 1u, 3}, {"A", 2u, 3}, {"B", 1u, 2}, {"B", 2u, 2}}}};
+ expected_per_device_numeric_obs[{max_report_id, day_index}] = {
+ {1, {{"A", 1u, 3}, {"A", 2u, 3}, {"B", 1u, 2}, {"B", 2u, 2}}},
+ {7, {{"A", 1u, 3}, {"A", 2u, 3}, {"B", 1u, 2}, {"B", 2u, 2}}}};
+ expected_report_participation_obs =
+ MakeExpectedReportParticipationObservations(expected_params, day_index);
+ EXPECT_TRUE(CheckPerDeviceNumericObservations(
+ expected_per_device_numeric_obs, expected_report_participation_obs,
+ observation_store_.get(), update_recipient_.get()));
+ break;
+ }
+ case 5: {
+ expected_per_device_numeric_obs[{total_report_id, day_index}] = {
+ {1, {{"A", 1u, 3}}}, {7, {{"A", 1u, 15}, {"A", 2u, 6}, {"B", 1u, 4}, {"B", 2u, 2}}}};
+ expected_per_device_numeric_obs[{min_report_id, day_index}] = {
+ {1, {{"A", 1u, 3}}}, {7, {{"A", 1u, 3}, {"A", 2u, 3}, {"B", 1u, 2}, {"B", 2u, 2}}}};
+ expected_per_device_numeric_obs[{max_report_id, day_index}] = {
+ {1, {{"A", 1u, 3}}}, {7, {{"A", 1u, 3}, {"A", 2u, 3}, {"B", 1u, 2}, {"B", 2u, 2}}}};
+ expected_report_participation_obs =
+ MakeExpectedReportParticipationObservations(expected_params, day_index);
+ EXPECT_TRUE(CheckPerDeviceNumericObservations(
+ expected_per_device_numeric_obs, expected_report_participation_obs,
+ observation_store_.get(), update_recipient_.get()));
+ break;
+ }
+ case 8: {
+ expected_per_device_numeric_obs[{total_report_id, start_day_index + 6}] = {
+ {1, {{"A", 1u, 3}, {"A", 2u, 3}, {"B", 1u, 2}}},
+ {7, {{"A", 1u, 18}, {"A", 2u, 9}, {"B", 1u, 6}, {"B", 2u, 2}}}};
+ expected_per_device_numeric_obs[{total_report_id, start_day_index + 7}] = {
+ {1, {{"A", 1u, 3}}}, {7, {{"A", 1u, 21}, {"A", 2u, 9}, {"B", 1u, 6}, {"B", 2u, 2}}}};
+ expected_per_device_numeric_obs[{total_report_id, start_day_index + 8}] = {
+ {1, {{"A", 1u, 3}, {"A", 2u, 3}, {"B", 1u, 2}, {"B", 2u, 2}}},
+ {7, {{"A", 1u, 21}, {"A", 2u, 12}, {"B", 1u, 8}, {"B", 2u, 4}}}};
+
+ expected_per_device_numeric_obs[{min_report_id, start_day_index + 6}] = {
+ {1, {{"A", 1u, 3}, {"A", 2u, 3}, {"B", 1u, 2}}},
+ {7, {{"A", 1u, 3}, {"A", 2u, 3}, {"B", 1u, 2}, {"B", 2u, 2}}}};
+ expected_per_device_numeric_obs[{min_report_id, start_day_index + 7}] = {
+ {1, {{"A", 1u, 3}}}, {7, {{"A", 1u, 3}, {"A", 2u, 3}, {"B", 1u, 2}, {"B", 2u, 2}}}};
+ expected_per_device_numeric_obs[{min_report_id, start_day_index + 8}] = {
+ {1, {{"A", 1u, 3}, {"A", 2u, 3}, {"B", 1u, 2}, {"B", 2u, 2}}},
+ {7, {{"A", 1u, 3}, {"A", 2u, 3}, {"B", 1u, 2}, {"B", 2u, 2}}}};
+
+ expected_per_device_numeric_obs[{max_report_id, start_day_index + 6}] = {
+ {1, {{"A", 1u, 3}, {"A", 2u, 3}, {"B", 1u, 2}}},
+ {7, {{"A", 1u, 3}, {"A", 2u, 3}, {"B", 1u, 2}, {"B", 2u, 2}}}};
+ expected_per_device_numeric_obs[{max_report_id, start_day_index + 7}] = {
+ {1, {{"A", 1u, 3}}}, {7, {{"A", 1u, 3}, {"A", 2u, 3}, {"B", 1u, 2}, {"B", 2u, 2}}}};
+ expected_per_device_numeric_obs[{max_report_id, start_day_index + 8}] = {
+ {1, {{"A", 1u, 3}, {"A", 2u, 3}, {"B", 1u, 2}, {"B", 2u, 2}}},
+ {7, {{"A", 1u, 3}, {"A", 2u, 3}, {"B", 1u, 2}, {"B", 2u, 2}}}};
+
+ for (uint32_t day_index = start_day_index + 6; day_index <= start_day_index + 8;
+ day_index++) {
+ for (const auto& pair :
+ MakeExpectedReportParticipationObservations(expected_params, day_index)) {
+ expected_report_participation_obs.insert(pair);
+ }
+ }
+ EXPECT_TRUE(CheckPerDeviceNumericObservations(
+ expected_per_device_numeric_obs, expected_report_participation_obs,
+ observation_store_.get(), update_recipient_.get()));
+ break;
+ }
+ default:
+ EXPECT_TRUE(CheckPerDeviceNumericObservations(
+ expected_per_device_numeric_obs, expected_report_participation_obs,
+ observation_store_.get(), update_recipient_.get()));
+ }
+ }
+}
+
+// Check that GenerateObservations returns an OK status after some events have been logged for a
+// PerDeviceHistogram report.
+TEST_F(PerDeviceHistogramAggregateStoreTest, GenerateObservations) {
+ const auto day_index = CurrentDayIndex();
+ // Log several events on |day_index|.
+ EXPECT_EQ(kOK, LogPerDeviceCountEvent(
+ logger::testing::per_device_histogram::kSettingsChangedMetricReportId,
+ day_index, "component_C", 0u, 5));
+ EXPECT_EQ(kOK, LogPerDeviceCountEvent(
+ logger::testing::per_device_histogram::kSettingsChangedMetricReportId,
+ day_index, "component_C", 0u, 5));
+
+ // Generate locally aggregated Observations for |day_index|.
+ EXPECT_EQ(kOK, GenerateObservations(day_index));
+}
+
+// Tests GenerateObservations() and GarbageCollect() in the case where the
+// LocalAggregateStore contains aggregates for metrics with both UTC and LOCAL
+// time zone policies, and where the day index in local time may be less than
+// the day index in UTC.
+TEST_F(NoiseFreeMixedTimeZoneAggregateStoreTest, LocalBeforeUTC) {
+ std::vector<ExpectedUniqueActivesObservations> expected_obs(3);
+ // Begin at a time when the current day index is the same in both UTC and
+ // local time. Log 1 event for event code 0 for each of the 2 reports, then
+ // generate Observations and garbage-collect for the previous day index in
+ // each of UTC and local time.
+ auto start_day_index = CurrentDayIndex();
+ LogUniqueActivesEvent(logger::testing::mixed_time_zone::kDeviceBootsMetricReportId,
+ start_day_index, 0u);
+ LogUniqueActivesEvent(logger::testing::mixed_time_zone::kFeaturesActiveMetricReportId,
+ start_day_index, 0u);
+ GenerateObservations(start_day_index - 1, start_day_index - 1);
+ GarbageCollect(start_day_index - 1, start_day_index - 1);
+ // Form the expected contents of the FakeObservationStore.
+ // Since no events were logged on the previous day and no Observations have
+ // been generated for that day yet, expect Observations of non-activity for
+ // all event codes, for both reports.
+ expected_obs[0] = MakeNullExpectedUniqueActivesObservations(
+ logger::testing::mixed_time_zone::kExpectedAggregationParams, start_day_index - 1);
+ EXPECT_TRUE(CheckUniqueActivesObservations(expected_obs[0], observation_store_.get(),
+ update_recipient_.get()));
+ ResetObservationStore();
+ // Advance the day index in UTC, but not in local time, and log 1 event for
+ // event code 1 for each of the 2 reports. Generate Observations and
+ // garbage-collect for the previous day in each of UTC and local time.
+ LogUniqueActivesEvent(logger::testing::mixed_time_zone::kDeviceBootsMetricReportId,
+ start_day_index, 1u);
+ LogUniqueActivesEvent(logger::testing::mixed_time_zone::kFeaturesActiveMetricReportId,
+ start_day_index + 1, 1u);
+ GenerateObservations(start_day_index, start_day_index - 1);
+ GarbageCollect(start_day_index, start_day_index - 1);
+ // Form the expected contents of the FakeObservationStore. Since
+ // Observations have already been generated for the
+ // DeviceBoots_UniqueDevices report for |start_day_index - 1|, expect no
+ // Observations for that report.
+ expected_obs[1][{logger::testing::mixed_time_zone::kFeaturesActiveMetricReportId,
+ start_day_index}] = {{1, {true, false, false}}};
+ EXPECT_TRUE(CheckUniqueActivesObservations(expected_obs[1], observation_store_.get(),
+ update_recipient_.get()));
+ ResetObservationStore();
+ // Advance the day index in local time so that it is equal to the day index
+ // in UTC. Log 1 event for event code 2 for each of the 2 reports, then
+ // generate Observations and garbage-collect for the previous day in each of
+ // UTC and local time.
+ LogUniqueActivesEvent(logger::testing::mixed_time_zone::kDeviceBootsMetricReportId,
+ start_day_index + 1, 2u);
+ LogUniqueActivesEvent(logger::testing::mixed_time_zone::kFeaturesActiveMetricReportId,
+ start_day_index + 1, 2u);
+ GenerateObservations(start_day_index, start_day_index);
+ GarbageCollect(start_day_index, start_day_index);
+ // Form the expected contents of the FakeObservationStore. Since
+ // Observations have already been generated for the
+ // FeaturesActive_UniqueDevices report for day |start_day_index|, expect no
+ // Observations for that report.
+ expected_obs[2][{logger::testing::mixed_time_zone::kDeviceBootsMetricReportId, start_day_index}] =
+ {{1, {true, true, false}}};
+ EXPECT_TRUE(CheckUniqueActivesObservations(expected_obs[2], observation_store_.get(),
+ update_recipient_.get()));
+}
+
+// Tests GenerateObservations() and GarbageCollect() in the case where the
+// LocalAggregateStore contains aggregates for metrics with both UTC and LOCAL
+// time zone policies, and where the day index in UTC may be less than
+// the day index in local time.
+TEST_F(NoiseFreeMixedTimeZoneAggregateStoreTest, LocalAfterUTC) {
+ std::vector<ExpectedUniqueActivesObservations> expected_obs(3);
+ // Begin at a time when the current day index is the same in both UTC and
+ // local time. Log 1 event for event code 0 for each of the 2 reports, then
+ // generate Observations and garbage-collect for the previous day index in
+ // each of UTC and local time.
+ auto start_day_index = CurrentDayIndex();
+ LogUniqueActivesEvent(logger::testing::mixed_time_zone::kDeviceBootsMetricReportId,
+ start_day_index, 0u);
+ LogUniqueActivesEvent(logger::testing::mixed_time_zone::kFeaturesActiveMetricReportId,
+ start_day_index, 0u);
+ GenerateObservations(start_day_index - 1, start_day_index - 1);
+ GarbageCollect(start_day_index - 1, start_day_index - 1);
+ // Form the expected contents of the FakeObservationStore.
+ // Since no events were logged on the previous day and no Observations have
+ // been generated for that day yet, expect Observations of non-activity for
+ // all event codes, for both reports.
+ expected_obs[0] = MakeNullExpectedUniqueActivesObservations(
+ logger::testing::mixed_time_zone::kExpectedAggregationParams, start_day_index - 1);
+ EXPECT_TRUE(CheckUniqueActivesObservations(expected_obs[0], observation_store_.get(),
+ update_recipient_.get()));
+ ResetObservationStore();
+ // Advance the day index in local time, but not in UTC, and log 1 event for
+ // event code 1 for each of the 2 reports. Generate Observations and
+ // garbage-collect for the previous day in each of UTC and local time.
+ LogUniqueActivesEvent(logger::testing::mixed_time_zone::kDeviceBootsMetricReportId,
+ start_day_index + 1, 1u);
+ LogUniqueActivesEvent(logger::testing::mixed_time_zone::kFeaturesActiveMetricReportId,
+ start_day_index, 1u);
+ GenerateObservations(start_day_index - 1, start_day_index);
+ GarbageCollect(start_day_index - 1, start_day_index);
+ // Form the expected contents of the FakeObservationStore. Since
+ // Observations have already been generated for the
+ // FeaturesActive_UniqueDevices report for |start_day_index - 1|, expect no
+ // Observations for that report.
+ expected_obs[1][{logger::testing::mixed_time_zone::kDeviceBootsMetricReportId, start_day_index}] =
+ {{1, {true, false, false}}};
+ EXPECT_TRUE(CheckUniqueActivesObservations(expected_obs[1], observation_store_.get(),
+ update_recipient_.get()));
+ ResetObservationStore();
+ // Advance the day index in UTC so that it is equal to the day index in
+ // local time. Log 1 event for event code 2 for each of the 2 reports, then
+ // generate Observations and garbage-collect for the previous day in each of
+ // UTC and local time.
+ LogUniqueActivesEvent(logger::testing::mixed_time_zone::kDeviceBootsMetricReportId,
+ start_day_index + 1, 2u);
+ LogUniqueActivesEvent(logger::testing::mixed_time_zone::kFeaturesActiveMetricReportId,
+ start_day_index + 1, 2u);
+ GenerateObservations(start_day_index, start_day_index);
+ GarbageCollect(start_day_index, start_day_index);
+ // Form the expected contents of the FakeObservationStore. Since
+ // Observations have already been generated for the
+ // DeviceBoots_UniqueDevices report for day |start_day_index|, expect no
+ // Observations for that report.
+ expected_obs[2][{logger::testing::mixed_time_zone::kFeaturesActiveMetricReportId,
+ start_day_index}] = {{1, {true, true, false}}};
+ EXPECT_TRUE(CheckUniqueActivesObservations(expected_obs[2], observation_store_.get(),
+ update_recipient_.get()));
+}
+} // namespace local_aggregation
+} // namespace cobalt
diff --git a/src/local_aggregation/event_aggregator.cc b/src/local_aggregation/event_aggregator.cc
index f556f58..c126127 100644
--- a/src/local_aggregation/event_aggregator.cc
+++ b/src/local_aggregation/event_aggregator.cc
@@ -10,38 +10,27 @@
#include <utility>
#include <vector>
-#include "src/algorithms/rappor/rappor_config_helper.h"
#include "src/lib/util/datetime_util.h"
#include "src/lib/util/proto_util.h"
-#include "src/lib/util/status.h"
-#include "src/local_aggregation/aggregation_utils.h"
-#include "src/logger/project_context.h"
-#include "src/registry/metric_definition.pb.h"
#include "src/registry/packed_event_codes.h"
namespace cobalt::local_aggregation {
-using google::protobuf::RepeatedField;
using logger::Encoder;
using logger::EventRecord;
using logger::kInvalidArguments;
using logger::kOK;
using logger::kOther;
-using logger::MetricRef;
using logger::ObservationWriter;
using logger::ProjectContext;
using logger::Status;
-using rappor::RapporConfigHelper;
using util::ConsistentProtoStore;
using util::SerializeToBase64;
-using util::StatusCode;
using util::SteadyClock;
using util::TimeToDayIndex;
namespace {
-////// General helper functions.
-
// Populates a ReportAggregationKey proto message and then populates a string
// with the base64 encoding of the serialized proto.
bool PopulateReportKey(uint32_t customer_id, uint32_t project_id, uint32_t metric_id,
@@ -54,201 +43,8 @@
return SerializeToBase64(key_data, key);
}
-////// Helper functions used by the constructor and UpdateAggregationConfigs().
-
-// Gets and validates the window sizes and/or aggregation windows from a ReportDefinition, converts
-// window sizes to daily aggregation windows, sorts the aggregation windows in increasing order, and
-// adds them to an AggregationConfig.
-//
-// TODO(pesk): Stop looking at the window_size field of |report| once all reports have been updated
-// to have OnDeviceAggregationWindows only.
-bool GetSortedAggregationWindowsFromReport(const ReportDefinition& report,
- AggregationConfig* aggregation_config) {
- if (report.window_size_size() == 0 && report.aggregation_window_size() == 0) {
- LOG(ERROR) << "Report must have at least one window size or aggregation window.";
- return false;
- }
- std::vector<uint32_t> aggregation_days;
- std::vector<uint32_t> aggregation_hours;
- for (const uint32_t window_size : report.window_size()) {
- if (window_size == 0 || window_size > EventAggregator::kMaxAllowedAggregationDays) {
- LOG(ERROR) << "Window size must be positive and cannot exceed "
- << EventAggregator::kMaxAllowedAggregationDays;
- return false;
- }
- aggregation_days.push_back(window_size);
- }
- for (const auto& window : report.aggregation_window()) {
- switch (window.units_case()) {
- case OnDeviceAggregationWindow::kDays: {
- uint32_t num_days = window.days();
- if (num_days == 0 || num_days > EventAggregator::kMaxAllowedAggregationDays) {
- LOG(ERROR) << "Daily windows must contain at least 1 and no more than "
- << EventAggregator::kMaxAllowedAggregationDays << " days";
- return false;
- }
- aggregation_days.push_back(num_days);
- break;
- }
- case OnDeviceAggregationWindow::kHours: {
- uint32_t num_hours = window.hours();
- if (num_hours == 0 || num_hours > EventAggregator::kMaxAllowedAggregationHours) {
- LOG(ERROR) << "Hourly windows must contain at least 1 and no more than "
- << EventAggregator::kMaxAllowedAggregationHours << " hours";
- return false;
- }
- aggregation_hours.push_back(num_hours);
- break;
- }
- default:
- LOG(ERROR) << "Invalid OnDeviceAggregationWindow type " << window.units_case();
- }
- }
- std::sort(aggregation_hours.begin(), aggregation_hours.end());
- std::sort(aggregation_days.begin(), aggregation_days.end());
- for (auto num_hours : aggregation_hours) {
- *aggregation_config->add_aggregation_window() = MakeHourWindow(num_hours);
- }
- for (auto num_days : aggregation_days) {
- *aggregation_config->add_aggregation_window() = MakeDayWindow(num_days);
- }
- return true;
-}
-
-// Creates an AggregationConfig from a ProjectContext, MetricDefinition, and
-// ReportDefinition and populates the aggregation_config field of a specified
-// ReportAggregates. Also sets the type of the ReportAggregates based on the
-// ReportDefinition's type.
-//
-// Accepts ReportDefinitions with either at least one WindowSize, or at least one
-// OnDeviceAggregationWindow with units in days.
-bool PopulateReportAggregates(const ProjectContext& project_context, const MetricDefinition& metric,
- const ReportDefinition& report, ReportAggregates* report_aggregates) {
- if (report.window_size_size() == 0 && report.aggregation_window_size() == 0) {
- }
- AggregationConfig* aggregation_config = report_aggregates->mutable_aggregation_config();
- *aggregation_config->mutable_project() = project_context.project();
- *aggregation_config->mutable_metric() = *project_context.GetMetric(metric.id());
- *aggregation_config->mutable_report() = report;
- if (!GetSortedAggregationWindowsFromReport(report, aggregation_config)) {
- return false;
- }
- switch (report.report_type()) {
- case ReportDefinition::UNIQUE_N_DAY_ACTIVES: {
- report_aggregates->set_allocated_unique_actives_aggregates(new UniqueActivesReportAggregates);
- return true;
- }
- case ReportDefinition::PER_DEVICE_NUMERIC_STATS:
- case ReportDefinition::PER_DEVICE_HISTOGRAM: {
- report_aggregates->set_allocated_numeric_aggregates(new PerDeviceNumericAggregates);
- return true;
- }
- default:
- return false;
- }
-}
-
-// Given a ProjectContext, MetricDefinition, and ReportDefinition and a pointer
-// to the LocalAggregateStore, checks whether a key with the same customer,
-// project, metric, and report ID already exists in the LocalAggregateStore. If
-// not, creates and inserts a new key and value. Returns kInvalidArguments if
-// creation of the key or value fails, and kOK otherwise. The caller should hold
-// the mutex protecting the LocalAggregateStore.
-Status MaybeInsertReportConfigLocked(const ProjectContext& project_context,
- const MetricDefinition& metric, const ReportDefinition& report,
- LocalAggregateStore* store) {
- std::string key;
- if (!PopulateReportKey(project_context.project().customer_id(),
- project_context.project().project_id(), metric.id(), report.id(), &key)) {
- return kInvalidArguments;
- }
- ReportAggregates report_aggregates;
- if (store->by_report_key().count(key) == 0) {
- if (!PopulateReportAggregates(project_context, metric, report, &report_aggregates)) {
- return kInvalidArguments;
- }
- (*store->mutable_by_report_key())[key] = report_aggregates;
- }
- return kOK;
-}
-
-RepeatedField<uint32_t> UnpackEventCodesProto(uint64_t packed_event_codes) {
- RepeatedField<uint32_t> fields;
- for (auto code : config::UnpackEventCodes(packed_event_codes)) {
- *fields.Add() = code;
- }
- return fields;
-}
-
-// Move all items from the |window_size| field to the |aggregation_window| field
-// of each AggregationConfig, preserving the order of the items. The |aggregation_window| field
-// should be empty if the |window_size| field is nonempty. If for some reason this is not true, log
-// an error and discard the contents of |aggregation_window| and replace them with the migrated
-// |window_size| values.
-void ConvertWindowSizesToAggregationDays(LocalAggregateStore* store) {
- for (auto [key, aggregates] : store->by_report_key()) {
- auto config = (*store->mutable_by_report_key())[key].mutable_aggregation_config();
- if (config->window_size_size() > 0 && config->aggregation_window_size() > 0) {
- LOG(ERROR) << "Config has both a window_size and an aggregation_window; discarding all "
- "aggregation_windows";
- config->clear_aggregation_window();
- }
- for (auto window_size : config->window_size()) {
- *config->add_aggregation_window() = MakeDayWindow(window_size);
- }
- config->clear_window_size();
- }
-}
-
-// Upgrades the LocalAggregateStore from version 0 to |kCurrentLocalAggregateStoreVersion|.
-Status UpgradeLocalAggregateStoreFromVersion0(LocalAggregateStore* store) {
- ConvertWindowSizesToAggregationDays(store);
- store->set_version(EventAggregator::kCurrentLocalAggregateStoreVersion);
- return kOK;
-}
-
} // namespace
-LocalAggregateStore EventAggregator::MakeNewLocalAggregateStore(uint32_t version) {
- LocalAggregateStore store;
- store.set_version(version);
- return store;
-}
-
-AggregatedObservationHistoryStore EventAggregator::MakeNewObservationHistoryStore(
- uint32_t version) {
- AggregatedObservationHistoryStore store;
- store.set_version(version);
- return store;
-}
-
-// We can upgrade from v0, but no other versions.
-Status EventAggregator::MaybeUpgradeLocalAggregateStore(LocalAggregateStore* store) {
- uint32_t version = store->version();
- if (version == kCurrentLocalAggregateStoreVersion) {
- return kOK;
- }
- VLOG(4) << "Attempting to upgrade LocalAggregateStore from version " << version;
- switch (version) {
- case 0u:
- return UpgradeLocalAggregateStoreFromVersion0(store);
- default:
- LOG(ERROR) << "Cannot upgrade LocalAggregateStore from version " << version;
- return kInvalidArguments;
- }
-}
-
-// The current version is the earliest version, so no other versions are accepted.
-Status EventAggregator::MaybeUpgradeObservationHistoryStore(
- AggregatedObservationHistoryStore* store) {
- uint32_t version = store->version();
- if (version == kCurrentObservationHistoryStoreVersion) {
- return kOK;
- }
- LOG(ERROR) << "Cannot upgrade AggregatedObservationHistoryStore from version " << version;
- return kInvalidArguments;
-}
-
EventAggregator::EventAggregator(const Encoder* encoder,
const ObservationWriter* observation_writer,
ConsistentProtoStore* local_aggregate_proto_store,
@@ -256,81 +52,19 @@
const size_t backfill_days,
const std::chrono::seconds aggregate_backup_interval,
const std::chrono::seconds generate_obs_interval,
- const std::chrono::seconds gc_interval)
- : encoder_(encoder),
- observation_writer_(observation_writer),
- local_aggregate_proto_store_(local_aggregate_proto_store),
- obs_history_proto_store_(obs_history_proto_store) {
+ const std::chrono::seconds gc_interval) {
CHECK_LE(aggregate_backup_interval.count(), generate_obs_interval.count())
<< "aggregate_backup_interval must be less than or equal to "
"generate_obs_interval";
CHECK_LE(aggregate_backup_interval.count(), gc_interval.count())
<< "aggregate_backup_interval must be less than or equal to gc_interval";
- CHECK_LE(backfill_days, kMaxAllowedBackfillDays)
- << "backfill_days must be less than or equal to " << kMaxAllowedBackfillDays;
aggregate_backup_interval_ = aggregate_backup_interval;
generate_obs_interval_ = generate_obs_interval;
gc_interval_ = gc_interval;
- backfill_days_ = backfill_days;
- auto locked = protected_aggregate_store_.lock();
- auto restore_aggregates_status =
- local_aggregate_proto_store_->Read(&(locked->local_aggregate_store));
- switch (restore_aggregates_status.error_code()) {
- case StatusCode::OK: {
- VLOG(4) << "Read LocalAggregateStore from disk.";
- break;
- }
- case StatusCode::NOT_FOUND: {
- VLOG(4) << "No file found for local_aggregate_proto_store. Proceeding "
- "with empty LocalAggregateStore. File will be created on "
- "first snapshot of the LocalAggregateStore.";
- locked->local_aggregate_store = MakeNewLocalAggregateStore();
- break;
- }
- default: {
- LOG(ERROR) << "Read to local_aggregate_proto_store failed with status code: "
- << restore_aggregates_status.error_code()
- << "\nError message: " << restore_aggregates_status.error_message()
- << "\nError details: " << restore_aggregates_status.error_details()
- << "\nProceeding with empty LocalAggregateStore.";
- locked->local_aggregate_store = MakeNewLocalAggregateStore();
- }
- }
- if (auto status = MaybeUpgradeLocalAggregateStore(&(locked->local_aggregate_store));
- status != kOK) {
- LOG(ERROR) << "Failed to upgrade LocalAggregateStore to current version with status " << status
- << ".\nProceeding with empty "
- "LocalAggregateStore.";
- locked->local_aggregate_store = MakeNewLocalAggregateStore();
- }
- auto restore_history_status = obs_history_proto_store_->Read(&obs_history_);
- switch (restore_history_status.error_code()) {
- case StatusCode::OK: {
- VLOG(4) << "Read AggregatedObservationHistoryStore from disk.";
- break;
- }
- case StatusCode::NOT_FOUND: {
- VLOG(4) << "No file found for obs_history_proto_store. Proceeding "
- "with empty AggregatedObservationHistoryStore. File will be "
- "created on first snapshot of the AggregatedObservationHistoryStore.";
- break;
- }
- default: {
- LOG(ERROR) << "Read to obs_history_proto_store failed with status code: "
- << restore_history_status.error_code()
- << "\nError message: " << restore_history_status.error_message()
- << "\nError details: " << restore_history_status.error_details()
- << "\nProceeding with empty AggregatedObservationHistoryStore.";
- obs_history_ = MakeNewObservationHistoryStore();
- }
- }
- if (auto status = MaybeUpgradeObservationHistoryStore(&obs_history_); status != kOK) {
- LOG(ERROR)
- << "Failed to upgrade AggregatedObservationHistoryStore to current version with status "
- << status << ".\nProceeding with empty AggregatedObservationHistoryStore.";
- obs_history_ = MakeNewObservationHistoryStore();
- }
+ aggregate_store_ =
+ std::make_unique<AggregateStore>(encoder, observation_writer, local_aggregate_proto_store,
+ obs_history_proto_store, backfill_days);
steady_clock_ = std::make_unique<SteadyClock>();
}
@@ -347,7 +81,7 @@
// TODO(pesk): update the EventAggregator's view of a Metric
// or ReportDefinition when appropriate.
Status EventAggregator::UpdateAggregationConfigs(const ProjectContext& project_context) {
- auto locked = protected_aggregate_store_.lock();
+ auto locked = aggregate_store_->protected_aggregate_store_.lock();
Status status;
for (const auto& metric : project_context.metrics()) {
switch (metric.metric_type()) {
@@ -355,8 +89,8 @@
for (const auto& report : metric.reports()) {
switch (report.report_type()) {
case ReportDefinition::UNIQUE_N_DAY_ACTIVES: {
- status = MaybeInsertReportConfigLocked(project_context, metric, report,
- &(locked->local_aggregate_store));
+ status = aggregate_store_->MaybeInsertReportConfigLocked(
+ project_context, metric, report, &(locked->local_aggregate_store));
if (status != kOK) {
return status;
}
@@ -374,8 +108,8 @@
switch (report.report_type()) {
case ReportDefinition::PER_DEVICE_NUMERIC_STATS:
case ReportDefinition::PER_DEVICE_HISTOGRAM: {
- status = MaybeInsertReportConfigLocked(project_context, metric, report,
- &(locked->local_aggregate_store));
+ status = aggregate_store_->MaybeInsertReportConfigLocked(
+ project_context, metric, report, &(locked->local_aggregate_store));
if (status != kOK) {
return status;
}
@@ -419,7 +153,7 @@
&key)) {
return kInvalidArguments;
}
- auto locked = protected_aggregate_store_.lock();
+ auto locked = aggregate_store_->protected_aggregate_store_.lock();
auto aggregates = locked->local_aggregate_store.mutable_by_report_key()->find(key);
if (aggregates == locked->local_aggregate_store.mutable_by_report_key()->end()) {
LOG(ERROR) << "The Local Aggregate Store received an unexpected key.";
@@ -508,7 +242,7 @@
Status EventAggregator::LogNumericEvent(const std::string& report_key, uint32_t day_index,
const std::string& component, uint64_t event_code,
int64_t value) {
- auto locked = protected_aggregate_store_.lock();
+ auto locked = aggregate_store_->protected_aggregate_store_.lock();
auto aggregates = locked->local_aggregate_store.mutable_by_report_key()->find(report_key);
if (aggregates == locked->local_aggregate_store.mutable_by_report_key()->end()) {
LOG(ERROR) << "The Local Aggregate Store received an unexpected key.";
@@ -554,33 +288,7 @@
"worker thread was running.";
return kOther;
}
- return GenerateObservations(final_day_index_utc, final_day_index_local);
-}
-
-Status EventAggregator::BackUpLocalAggregateStore() {
- // Lock, copy the LocalAggregateStore, and release the lock. Write the copy
- // to |local_aggregate_proto_store_|.
- auto local_aggregate_store = CopyLocalAggregateStore();
- auto status = local_aggregate_proto_store_->Write(local_aggregate_store);
- if (!status.ok()) {
- LOG(ERROR) << "Failed to back up the LocalAggregateStore with error code: "
- << status.error_code() << "\nError message: " << status.error_message()
- << "\nError details: " << status.error_details();
- return kOther;
- }
- return kOK;
-}
-
-Status EventAggregator::BackUpObservationHistory() {
- auto status = obs_history_proto_store_->Write(obs_history_);
- if (!status.ok()) {
- LOG(ERROR) << "Failed to back up the AggregatedObservationHistoryStore. "
- "::cobalt::util::Status error code: "
- << status.error_code() << "\nError message: " << status.error_message()
- << "\nError details: " << status.error_details();
- return kOther;
- }
- return kOK;
+ return aggregate_store_->GenerateObservations(final_day_index_utc, final_day_index_local);
}
void EventAggregator::ShutDown() {
@@ -608,7 +316,7 @@
// If shutdown has been requested, back up the LocalAggregateStore and
// exit.
if (locked->shut_down) {
- BackUpLocalAggregateStore();
+ aggregate_store_->BackUpLocalAggregateStore();
return;
}
// Sleep until the next scheduled backup of the LocalAggregateStore or
@@ -621,7 +329,7 @@
}
return locked->shut_down;
});
- BackUpLocalAggregateStore();
+ aggregate_store_->BackUpLocalAggregateStore();
// If the worker thread was woken up by a shutdown request, exit.
// Otherwise, complete any scheduled Observation generation and garbage
// collection.
@@ -642,7 +350,7 @@
auto yesterday_local_time = TimeToDayIndex(current_time_t, MetricDefinition::LOCAL) - 1;
// Skip the tasks (but do schedule a retry) if either day index is too small.
- uint32_t min_allowed_day_index = kMaxAllowedAggregationDays + backfill_days_;
+ uint32_t min_allowed_day_index = kMaxAllowedAggregationDays + aggregate_store_->backfill_days_;
bool skip_tasks =
(yesterday_utc < min_allowed_day_index || yesterday_local_time < min_allowed_day_index);
if (steady_time >= next_generate_obs_) {
@@ -651,9 +359,9 @@
LOG_FIRST_N(ERROR, 10) << "EventAggregator is skipping Observation generation because the "
"current day index is too small.";
} else {
- auto obs_status = GenerateObservations(yesterday_utc, yesterday_local_time);
+ auto obs_status = aggregate_store_->GenerateObservations(yesterday_utc, yesterday_local_time);
if (obs_status == kOK) {
- BackUpObservationHistory();
+ aggregate_store_->BackUpObservationHistory();
} else {
LOG(ERROR) << "GenerateObservations failed with status: " << obs_status;
}
@@ -665,9 +373,9 @@
LOG_FIRST_N(ERROR, 10) << "EventAggregator is skipping garbage collection because the "
"current day index is too small.";
} else {
- auto gc_status = GarbageCollect(yesterday_utc, yesterday_local_time);
+ auto gc_status = aggregate_store_->GarbageCollect(yesterday_utc, yesterday_local_time);
if (gc_status == kOK) {
- BackUpLocalAggregateStore();
+ aggregate_store_->BackUpLocalAggregateStore();
} else {
LOG(ERROR) << "GarbageCollect failed with status: " << gc_status;
}
@@ -675,625 +383,4 @@
}
}
-////////////////////// GarbageCollect and helper functions //////////////////
-
-namespace {
-
-void GarbageCollectUniqueActivesReportAggregates(uint32_t day_index, uint32_t max_aggregation_days,
- uint32_t backfill_days,
- UniqueActivesReportAggregates* report_aggregates) {
- auto map_by_event_code = report_aggregates->mutable_by_event_code();
- for (auto event_code = map_by_event_code->begin(); event_code != map_by_event_code->end();) {
- auto map_by_day = event_code->second.mutable_by_day_index();
- for (auto day = map_by_day->begin(); day != map_by_day->end();) {
- if (day->first <= day_index - backfill_days - max_aggregation_days) {
- day = map_by_day->erase(day);
- } else {
- ++day;
- }
- }
- if (map_by_day->empty()) {
- event_code = map_by_event_code->erase(event_code);
- } else {
- ++event_code;
- }
- }
-}
-
-void GarbageCollectNumericReportAggregates(uint32_t day_index, uint32_t max_aggregation_days,
- uint32_t backfill_days,
- PerDeviceNumericAggregates* report_aggregates) {
- auto map_by_component = report_aggregates->mutable_by_component();
- for (auto component = map_by_component->begin(); component != map_by_component->end();) {
- auto map_by_event_code = component->second.mutable_by_event_code();
- for (auto event_code = map_by_event_code->begin(); event_code != map_by_event_code->end();) {
- auto map_by_day = event_code->second.mutable_by_day_index();
- for (auto day = map_by_day->begin(); day != map_by_day->end();) {
- if (day->first <= day_index - backfill_days - max_aggregation_days) {
- day = map_by_day->erase(day);
- } else {
- ++day;
- }
- }
- if (map_by_day->empty()) {
- event_code = map_by_event_code->erase(event_code);
- } else {
- ++event_code;
- }
- }
- if (map_by_event_code->empty()) {
- component = map_by_component->erase(component);
- } else {
- ++component;
- }
- }
-}
-
-} // namespace
-
-Status EventAggregator::GarbageCollect(uint32_t day_index_utc, uint32_t day_index_local) {
- if (day_index_local == 0u) {
- day_index_local = day_index_utc;
- }
- CHECK_LT(day_index_utc, UINT32_MAX);
- CHECK_LT(day_index_local, UINT32_MAX);
- CHECK_GE(day_index_utc, kMaxAllowedAggregationDays + backfill_days_);
- CHECK_GE(day_index_local, kMaxAllowedAggregationDays + backfill_days_);
-
- auto locked = protected_aggregate_store_.lock();
- for (const auto& [report_key, aggregates] : locked->local_aggregate_store.by_report_key()) {
- uint32_t day_index;
- const auto& config = aggregates.aggregation_config();
- switch (config.metric().time_zone_policy()) {
- case MetricDefinition::UTC: {
- day_index = day_index_utc;
- break;
- }
- case MetricDefinition::LOCAL: {
- day_index = day_index_local;
- break;
- }
- default:
- LOG_FIRST_N(ERROR, 10) << "The TimeZonePolicy of this MetricDefinition is invalid.";
- continue;
- }
- if (aggregates.aggregation_config().aggregation_window_size() == 0) {
- LOG_FIRST_N(ERROR, 10) << "This ReportDefinition does not have an aggregation window.";
- continue;
- }
- // PopulateReportAggregates ensured that aggregation_window has at least one element, that all
- // aggregation windows are <= kMaxAllowedAggregationDays, and that config.aggregation_window()
- // is sorted in increasing order.
- uint32_t max_aggregation_days = 1u;
- const OnDeviceAggregationWindow& largest_window =
- config.aggregation_window(config.aggregation_window_size() - 1);
- if (largest_window.units_case() == OnDeviceAggregationWindow::kDays) {
- max_aggregation_days = largest_window.days();
- }
- if (max_aggregation_days == 0u || max_aggregation_days > day_index) {
- LOG_FIRST_N(ERROR, 10) << "The maximum number of aggregation days " << max_aggregation_days
- << " of this ReportDefinition is out of range.";
- continue;
- }
- // For each ReportAggregates, descend to and iterate over the sub-map of
- // local aggregates keyed by day index. Keep buckets with day indices
- // greater than |day_index| - |backfill_days_| - |max_aggregation_days|, and
- // remove all buckets with smaller day indices.
- switch (aggregates.type_case()) {
- case ReportAggregates::kUniqueActivesAggregates: {
- GarbageCollectUniqueActivesReportAggregates(
- day_index, max_aggregation_days, backfill_days_,
- locked->local_aggregate_store.mutable_by_report_key()
- ->at(report_key)
- .mutable_unique_actives_aggregates());
- break;
- }
- case ReportAggregates::kNumericAggregates: {
- GarbageCollectNumericReportAggregates(day_index, max_aggregation_days, backfill_days_,
- locked->local_aggregate_store.mutable_by_report_key()
- ->at(report_key)
- .mutable_numeric_aggregates());
- break;
- }
- default:
- continue;
- }
- }
- return kOK;
-}
-
-Status EventAggregator::GenerateObservations(uint32_t final_day_index_utc,
- uint32_t final_day_index_local) {
- if (final_day_index_local == 0u) {
- final_day_index_local = final_day_index_utc;
- }
- CHECK_LT(final_day_index_utc, UINT32_MAX);
- CHECK_LT(final_day_index_local, UINT32_MAX);
- CHECK_GE(final_day_index_utc, kMaxAllowedAggregationDays + backfill_days_);
- CHECK_GE(final_day_index_local, kMaxAllowedAggregationDays + backfill_days_);
-
- // Lock, copy the LocalAggregateStore, and release the lock. Use the copy to
- // generate observations.
- auto local_aggregate_store = CopyLocalAggregateStore();
- for (const auto& [report_key, aggregates] : local_aggregate_store.by_report_key()) {
- const auto& config = aggregates.aggregation_config();
-
- const auto& metric = config.metric();
- auto metric_ref = MetricRef(&config.project(), &metric);
- uint32_t final_day_index;
- switch (metric.time_zone_policy()) {
- case MetricDefinition::UTC: {
- final_day_index = final_day_index_utc;
- break;
- }
- case MetricDefinition::LOCAL: {
- final_day_index = final_day_index_local;
- break;
- }
- default:
- LOG_FIRST_N(ERROR, 10) << "The TimeZonePolicy of this MetricDefinition is invalid.";
- continue;
- }
-
- const auto& report = config.report();
- // PopulateReportAggregates ensured that aggregation_window has at least one element, that all
- // aggregation windows are <= kMaxAllowedAggregationDays, and that config.aggregation_window()
- // is sorted in increasing order.
- if (config.aggregation_window_size() == 0u) {
- LOG_FIRST_N(ERROR, 10) << "No aggregation_window found for this report.";
- continue;
- }
- uint32_t max_aggregation_days = 1u;
- const OnDeviceAggregationWindow& largest_window =
- config.aggregation_window(config.aggregation_window_size() - 1);
- if (largest_window.units_case() == OnDeviceAggregationWindow::kDays) {
- max_aggregation_days = largest_window.days();
- }
- if (max_aggregation_days == 0u || max_aggregation_days > final_day_index) {
- LOG_FIRST_N(ERROR, 10) << "The maximum number of aggregation days " << max_aggregation_days
- << " of this ReportDefinition is out of range.";
- continue;
- }
- switch (metric.metric_type()) {
- case MetricDefinition::EVENT_OCCURRED: {
- auto num_event_codes = RapporConfigHelper::BasicRapporNumCategories(metric);
-
- switch (report.report_type()) {
- case ReportDefinition::UNIQUE_N_DAY_ACTIVES: {
- auto status = GenerateUniqueActivesObservations(metric_ref, report_key, aggregates,
- num_event_codes, final_day_index);
- if (status != kOK) {
- return status;
- }
- break;
- }
- default:
- continue;
- }
- break;
- }
- case MetricDefinition::EVENT_COUNT:
- case MetricDefinition::ELAPSED_TIME:
- case MetricDefinition::FRAME_RATE:
- case MetricDefinition::MEMORY_USAGE: {
- switch (report.report_type()) {
- case ReportDefinition::PER_DEVICE_NUMERIC_STATS:
- case ReportDefinition::PER_DEVICE_HISTOGRAM: {
- auto status = GenerateObsFromNumericAggregates(metric_ref, report_key, aggregates,
- final_day_index);
- if (status != kOK) {
- return status;
- }
- break;
- }
- default:
- continue;
- }
- break;
- }
- default:
- continue;
- }
- }
- return kOK;
-}
-
-////////// GenerateUniqueActivesObservations and helper methods ////////////////
-
-namespace {
-
-// Given the set of daily aggregates for a fixed event code, and the size and
-// end date of an aggregation window, returns the first day index within that
-// window on which the event code occurred. Returns 0 if the event code did
-// not occur within the window.
-uint32_t FirstActiveDayIndexInWindow(const DailyAggregates& daily_aggregates,
- uint32_t obs_day_index, uint32_t aggregation_days) {
- for (uint32_t day_index = obs_day_index - aggregation_days + 1; day_index <= obs_day_index;
- day_index++) {
- auto day_aggregate = daily_aggregates.by_day_index().find(day_index);
- if (day_aggregate != daily_aggregates.by_day_index().end() &&
- day_aggregate->second.activity_daily_aggregate().activity_indicator()) {
- return day_index;
- }
- }
- return 0u;
-}
-
-// Given the day index of an event occurrence and the size and end date
-// of an aggregation window, returns true if the occurrence falls within
-// the window and false if not.
-bool IsActivityInWindow(uint32_t active_day_index, uint32_t obs_day_index,
- uint32_t aggregation_days) {
- return (active_day_index <= obs_day_index && active_day_index > obs_day_index - aggregation_days);
-}
-
-} // namespace
-
-uint32_t EventAggregator::UniqueActivesLastGeneratedDayIndex(const std::string& report_key,
- uint32_t event_code,
- uint32_t aggregation_days) const {
- auto report_history = obs_history_.by_report_key().find(report_key);
- if (report_history == obs_history_.by_report_key().end()) {
- return 0u;
- }
- auto event_code_history =
- report_history->second.unique_actives_history().by_event_code().find(event_code);
- if (event_code_history == report_history->second.unique_actives_history().by_event_code().end()) {
- return 0u;
- }
- auto window_history = event_code_history->second.by_window_size().find(aggregation_days);
- if (window_history == event_code_history->second.by_window_size().end()) {
- return 0u;
- }
- return window_history->second;
-}
-
-Status EventAggregator::GenerateSingleUniqueActivesObservation(
- const MetricRef metric_ref, const ReportDefinition* report, uint32_t obs_day_index,
- uint32_t event_code, const OnDeviceAggregationWindow& window, bool was_active) const {
- auto encoder_result = encoder_->EncodeUniqueActivesObservation(metric_ref, report, obs_day_index,
- event_code, was_active, window);
- if (encoder_result.status != kOK) {
- return encoder_result.status;
- }
- if (encoder_result.observation == nullptr || encoder_result.metadata == nullptr) {
- LOG(ERROR) << "Failed to encode UniqueActivesObservation";
- return kOther;
- }
-
- auto writer_status = observation_writer_->WriteObservation(*encoder_result.observation,
- std::move(encoder_result.metadata));
- if (writer_status != kOK) {
- return writer_status;
- }
- return kOK;
-}
-
-Status EventAggregator::GenerateUniqueActivesObservations(const MetricRef metric_ref,
- const std::string& report_key,
- const ReportAggregates& report_aggregates,
- uint32_t num_event_codes,
- uint32_t final_day_index) {
- CHECK_GT(final_day_index, backfill_days_);
- // The earliest day index for which we might need to generate an
- // Observation.
- auto backfill_period_start = uint32_t(final_day_index - backfill_days_);
-
- for (uint32_t event_code = 0; event_code < num_event_codes; event_code++) {
- auto daily_aggregates =
- report_aggregates.unique_actives_aggregates().by_event_code().find(event_code);
- // Have any events ever been logged for this report and event code?
- bool found_event_code =
- (daily_aggregates != report_aggregates.unique_actives_aggregates().by_event_code().end());
- for (const auto& window : report_aggregates.aggregation_config().aggregation_window()) {
- // Skip all hourly windows, and all daily windows which are larger than
- // kMaxAllowedAggregationDays.
- //
- // TODO(pesk): Generate observations for hourly windows.
- if (window.units_case() != OnDeviceAggregationWindow::kDays) {
- LOG(INFO) << "Skipping unsupported aggregation window.";
- continue;
- }
- if (window.days() > kMaxAllowedAggregationDays) {
- LOG(WARNING) << "GenerateUniqueActivesObservations ignoring a window "
- "size exceeding the maximum allowed value";
- continue;
- }
- // Find the earliest day index for which an Observation has not yet
- // been generated for this report, event code, and window size. If
- // that day index is later than |final_day_index|, no Observation is
- // generated on this invocation.
- auto last_gen = UniqueActivesLastGeneratedDayIndex(report_key, event_code, window.days());
- auto first_day_index = std::max(last_gen + 1, backfill_period_start);
- // The latest day index on which |event_type| is known to have
- // occurred, so far. This value will be updated as we search
- // forward from the earliest day index belonging to a window of
- // interest.
- uint32_t active_day_index = 0u;
- // Iterate over the day indices |obs_day_index| for which we need
- // to generate Observations. On each iteration, generate an
- // Observation for the |window| ending on |obs_day_index|.
- for (uint32_t obs_day_index = first_day_index; obs_day_index <= final_day_index;
- obs_day_index++) {
- bool was_active = false;
- if (found_event_code) {
- // If the current value of |active_day_index| falls within the
- // window, generate an Observation of activity. If not, search
- // forward in the window, update |active_day_index|, and generate an
- // Observation of activity or inactivity depending on the result of
- // the search.
- if (IsActivityInWindow(active_day_index, obs_day_index, window.days())) {
- was_active = true;
- } else {
- active_day_index =
- FirstActiveDayIndexInWindow(daily_aggregates->second, obs_day_index, window.days());
- was_active = IsActivityInWindow(active_day_index, obs_day_index, window.days());
- }
- }
- auto status = GenerateSingleUniqueActivesObservation(
- metric_ref, &report_aggregates.aggregation_config().report(), obs_day_index, event_code,
- window, was_active);
- if (status != kOK) {
- return status;
- }
- // Update |obs_history_| with the latest date of Observation
- // generation for this report, event code, and window size.
- (*(*(*obs_history_.mutable_by_report_key())[report_key]
- .mutable_unique_actives_history()
- ->mutable_by_event_code())[event_code]
- .mutable_by_window_size())[window.days()] = obs_day_index;
- }
- }
- }
- return kOK;
-}
-
-////////// GenerateObsFromNumericAggregates and helper methods /////////////
-
-uint32_t EventAggregator::PerDeviceNumericLastGeneratedDayIndex(const std::string& report_key,
- const std::string& component,
- uint32_t event_code,
- uint32_t aggregation_days) const {
- const auto& report_history = obs_history_.by_report_key().find(report_key);
- if (report_history == obs_history_.by_report_key().end()) {
- return 0u;
- }
- if (!report_history->second.has_per_device_numeric_history()) {
- return 0u;
- }
- const auto& component_history =
- report_history->second.per_device_numeric_history().by_component().find(component);
- if (component_history ==
- report_history->second.per_device_numeric_history().by_component().end()) {
- return 0u;
- }
- const auto& event_code_history = component_history->second.by_event_code().find(event_code);
- if (event_code_history == component_history->second.by_event_code().end()) {
- return 0u;
- }
- const auto& window_history = event_code_history->second.by_window_size().find(aggregation_days);
- if (window_history == event_code_history->second.by_window_size().end()) {
- return 0u;
- }
- return window_history->second;
-}
-
-uint32_t EventAggregator::ReportParticipationLastGeneratedDayIndex(
- const std::string& report_key) const {
- const auto& report_history = obs_history_.by_report_key().find(report_key);
- if (report_history == obs_history_.by_report_key().end()) {
- return 0u;
- }
- return report_history->second.report_participation_history().last_generated();
-}
-
-Status EventAggregator::GenerateSinglePerDeviceNumericObservation(
- const MetricRef metric_ref, const ReportDefinition* report, uint32_t obs_day_index,
- const std::string& component, uint32_t event_code, const OnDeviceAggregationWindow& window,
- int64_t value) const {
- Encoder::Result encoder_result =
- encoder_->EncodePerDeviceNumericObservation(metric_ref, report, obs_day_index, component,
- UnpackEventCodesProto(event_code), value, window);
- if (encoder_result.status != kOK) {
- return encoder_result.status;
- }
- if (encoder_result.observation == nullptr || encoder_result.metadata == nullptr) {
- LOG(ERROR) << "Failed to encode PerDeviceNumericObservation";
- return kOther;
- }
-
- const auto& writer_status = observation_writer_->WriteObservation(
- *encoder_result.observation, std::move(encoder_result.metadata));
- if (writer_status != kOK) {
- return writer_status;
- }
- return kOK;
-}
-
-Status EventAggregator::GenerateSinglePerDeviceHistogramObservation(
- const MetricRef metric_ref, const ReportDefinition* report, uint32_t obs_day_index,
- const std::string& component, uint32_t event_code, const OnDeviceAggregationWindow& window,
- int64_t value) const {
- Encoder::Result encoder_result = encoder_->EncodePerDeviceHistogramObservation(
- metric_ref, report, obs_day_index, component, UnpackEventCodesProto(event_code), value,
- window);
-
- if (encoder_result.status != kOK) {
- return encoder_result.status;
- }
- if (encoder_result.observation == nullptr || encoder_result.metadata == nullptr) {
- LOG(ERROR) << "Failed to encode PerDeviceNumericObservation";
- return kOther;
- }
-
- const auto& writer_status = observation_writer_->WriteObservation(
- *encoder_result.observation, std::move(encoder_result.metadata));
- if (writer_status != kOK) {
- return writer_status;
- }
- return kOK;
-}
-
-Status EventAggregator::GenerateSingleReportParticipationObservation(const MetricRef metric_ref,
- const ReportDefinition* report,
- uint32_t obs_day_index) const {
- auto encoder_result =
- encoder_->EncodeReportParticipationObservation(metric_ref, report, obs_day_index);
- if (encoder_result.status != kOK) {
- return encoder_result.status;
- }
- if (encoder_result.observation == nullptr || encoder_result.metadata == nullptr) {
- LOG(ERROR) << "Failed to encode ReportParticipationObservation";
- return kOther;
- }
-
- const auto& writer_status = observation_writer_->WriteObservation(
- *encoder_result.observation, std::move(encoder_result.metadata));
- if (writer_status != kOK) {
- return writer_status;
- }
- return kOK;
-}
-
-Status EventAggregator::GenerateObsFromNumericAggregates(const MetricRef metric_ref,
- const std::string& report_key,
- const ReportAggregates& report_aggregates,
- uint32_t final_day_index) {
- CHECK_GT(final_day_index, backfill_days_);
- // The first day index for which we might have to generate an Observation.
- auto backfill_period_start = uint32_t(final_day_index - backfill_days_);
-
- // Generate any necessary PerDeviceNumericObservations for this report.
- for (const auto& [component, event_code_aggregates] :
- report_aggregates.numeric_aggregates().by_component()) {
- for (const auto& [event_code, daily_aggregates] : event_code_aggregates.by_event_code()) {
- // Populate a helper map keyed by day indices which belong to the range
- // [|backfill_period_start|, |final_day_index|]. The value at a day
- // index is the list of windows, in increasing size order, for which an
- // Observation should be generated for that day index.
- std::map<uint32_t, std::vector<OnDeviceAggregationWindow>> windows_by_obs_day;
- for (const auto& window : report_aggregates.aggregation_config().aggregation_window()) {
- if (window.units_case() != OnDeviceAggregationWindow::kDays) {
- LOG(INFO) << "Skipping unsupported aggregation window.";
- continue;
- }
- auto last_gen =
- PerDeviceNumericLastGeneratedDayIndex(report_key, component, event_code, window.days());
- auto first_day_index = std::max(last_gen + 1, backfill_period_start);
- for (auto obs_day_index = first_day_index; obs_day_index <= final_day_index;
- obs_day_index++) {
- windows_by_obs_day[obs_day_index].push_back(window);
- }
- }
- // Iterate over the day indices |obs_day_index| for which we might need
- // to generate an Observation. For each day index, generate an
- // Observation for each needed window.
- //
- // More precisely, for each needed window, iterate over the days within that window. If at
- // least one numeric event was logged during the window, compute the aggregate of the numeric
- // values over the window and generate a PerDeviceNumericObservation. Whether or not a numeric
- // event was found, update the AggregatedObservationHistory for this report, component, event
- // code, and window size with |obs_day_index| as the most recent date of Observation
- // generation. This reflects the fact that all needed Observations were generated for the
- // window ending on that date.
- for (auto obs_day_index = backfill_period_start; obs_day_index <= final_day_index;
- obs_day_index++) {
- const auto& windows = windows_by_obs_day.find(obs_day_index);
- if (windows == windows_by_obs_day.end()) {
- continue;
- }
- bool found_value_for_window = false;
- int64_t window_aggregate = 0;
- uint32_t num_days = 0;
- for (const auto& window : windows->second) {
- while (num_days < window.days()) {
- bool found_value_for_day = false;
- const auto& day_aggregates =
- daily_aggregates.by_day_index().find(obs_day_index - num_days);
- if (day_aggregates != daily_aggregates.by_day_index().end()) {
- found_value_for_day = true;
- }
- const auto& aggregation_type =
- report_aggregates.aggregation_config().report().aggregation_type();
- switch (aggregation_type) {
- case ReportDefinition::SUM:
- if (found_value_for_day) {
- window_aggregate += day_aggregates->second.numeric_daily_aggregate().value();
- found_value_for_window = true;
- }
- break;
- case ReportDefinition::MAX:
- if (found_value_for_day) {
- window_aggregate = std::max(
- window_aggregate, day_aggregates->second.numeric_daily_aggregate().value());
- found_value_for_window = true;
- }
- break;
- case ReportDefinition::MIN:
- if (found_value_for_day && !found_value_for_window) {
- window_aggregate = day_aggregates->second.numeric_daily_aggregate().value();
- found_value_for_window = true;
- } else if (found_value_for_day) {
- window_aggregate = std::min(
- window_aggregate, day_aggregates->second.numeric_daily_aggregate().value());
- }
- break;
- default:
- LOG(ERROR) << "Unexpected aggregation type " << aggregation_type;
- return kInvalidArguments;
- }
- num_days++;
- }
- if (found_value_for_window) {
- Status status;
- const ReportDefinition* report = &report_aggregates.aggregation_config().report();
- switch (report->report_type()) {
- case ReportDefinition::PER_DEVICE_NUMERIC_STATS: {
- status = GenerateSinglePerDeviceNumericObservation(
- metric_ref, report, obs_day_index, component, event_code, window,
- window_aggregate);
- if (status != kOK) {
- return status;
- }
- break;
- }
- case ReportDefinition::PER_DEVICE_HISTOGRAM: {
- auto status = GenerateSinglePerDeviceHistogramObservation(
- metric_ref, report, obs_day_index, component, event_code, window,
- window_aggregate);
- if (status != kOK) {
- return status;
- }
- break;
- }
- default:
- LOG(ERROR) << "Unexpected report type " << report->report_type();
- return kInvalidArguments;
- }
- }
- // Update |obs_history_| with the latest date of Observation
- // generation for this report, component, event code, and window.
- (*(*(*(*obs_history_.mutable_by_report_key())[report_key]
- .mutable_per_device_numeric_history()
- ->mutable_by_component())[component]
- .mutable_by_event_code())[event_code]
- .mutable_by_window_size())[window.days()] = obs_day_index;
- }
- }
- }
- }
- // Generate any necessary ReportParticipationObservations for this report.
- auto participation_last_gen = ReportParticipationLastGeneratedDayIndex(report_key);
- auto participation_first_day_index = std::max(participation_last_gen + 1, backfill_period_start);
- for (auto obs_day_index = participation_first_day_index; obs_day_index <= final_day_index;
- obs_day_index++) {
- GenerateSingleReportParticipationObservation(
- metric_ref, &report_aggregates.aggregation_config().report(), obs_day_index);
- (*obs_history_.mutable_by_report_key())[report_key]
- .mutable_report_participation_history()
- ->set_last_generated(obs_day_index);
- }
- return kOK;
-}
-
} // namespace cobalt::local_aggregation
diff --git a/src/local_aggregation/event_aggregator.h b/src/local_aggregation/event_aggregator.h
index 6a6009c..0c08041 100644
--- a/src/local_aggregation/event_aggregator.h
+++ b/src/local_aggregation/event_aggregator.h
@@ -12,17 +12,11 @@
#include "src/lib/util/clock.h"
#include "src/lib/util/consistent_proto_store.h"
-#include "src/lib/util/protected_fields.h"
-#include "src/local_aggregation/local_aggregation.pb.h"
+#include "src/local_aggregation/aggregate_store.h"
#include "src/logger/encoder.h"
#include "src/logger/event_record.h"
#include "src/logger/observation_writer.h"
#include "src/logger/status.h"
-#include "src/logging.h"
-#include "src/pb/event.pb.h"
-#include "src/pb/observation2.pb.h"
-#include "src/registry/metric_definition.pb.h"
-#include "src/registry/report_definition.pb.h"
namespace cobalt {
@@ -38,65 +32,42 @@
namespace local_aggregation {
-const std::chrono::hours kOneDay(24);
-
-// The EventAggregator manages an in-memory store of aggregated Event values,
-// indexed by report, day index, and other dimensions specific to the report
-// type (e.g. event code). Periodically, this data is used to generate
-// Observations representing aggregates of Event values over a day, week, month,
-// etc.
+// The EventAggregator manages the Loggers' interactions with the local aggregation.
//
// Each Logger interacts with the EventAggregator in the following way:
// (1) When the Logger is created, it calls UpdateAggregationConfigs() to
// provide the EventAggregator with its view of Cobalt's metric and report
// registry.
// (2) When logging an Event for a locally aggregated report, a Logger
-// calls an Update*Aggregation() method with the Event and the
-// ReportAggregationKey of the report.
+// calls a Log*() method with the EventRecord and the report id for the event.
//
-// A worker thread does the following tasks at intervals specified in the
-// EventAggregator's constructor:
-// (1) Backs up the EventAggregator's state to the file system.
-// (2) Calls GenerateObservations() with the previous day's day index to
-// generate all Observations for rolling windows ending on that day index,
-// as well as any missing Observations for a specified number of days in the
-// past.
-// (3) Calls GarbageCollect() to delete daily aggregates which are not
-// needed to compute aggregates for any windows of interest in the future.
+// Functionality that the EventAggregator curently has but will be moved to the
+// EventAggregatorManager:
+// A worker thread calls on AggregateStore methods to do the following tasks at intervals
+// specified in the EventAggregator's constructor:
+// (1) Calls BackUp*() to back up the EventAggregator's state to the file system.
+// (2) Calls GenerateObservations() with the previous day's day index to generate all Observations
+// for rolling windows ending on that day index, as well as any missing Observations for a specified
+// number of days in the past.
+// (3) Calls GarbageCollect() to delete daily aggregates which are not needed to compute aggregates
+// for any windows of interest in the future.
class EventAggregator {
public:
- // Maximum value of |backfill_days| allowed by the constructor.
- static const size_t kMaxAllowedBackfillDays = 1000;
- // All aggregation windows larger than this number of days are ignored.
- static const uint32_t kMaxAllowedAggregationDays = 365;
- // All hourly aggregation windows larger than this number of hours are ignored.
- static const uint32_t kMaxAllowedAggregationHours = 23;
-
- // The current version number of the LocalAggregateStore.
- static const uint32_t kCurrentLocalAggregateStoreVersion = 1;
- // The current version number of the AggregatedObservationHistoryStore.
- static const uint32_t kCurrentObservationHistoryStoreVersion = 0;
-
// Constructs an EventAggregator.
//
- // An EventAggregator maintains daily aggregates of Events in a
- // LocalAggregateStore, uses an Encoder to generate Observations for rolling
- // windows ending on a specified day index, and writes the Observations to
- // an ObservationStore using an ObservationWriter.
- //
// encoder: the singleton instance of an Encoder on the system.
//
// local_aggregate_proto_store: A ConsistentProtoStore to be used by the
- // EventAggregator to store snapshots of its in-memory store of event
+ // AggregateStore to store snapshots of its in-memory store of event
// aggregates.
//
// obs_history_proto_store: A ConsistentProtoStore to be used by the
- // EventAggregator to store snapshots of its in-memory history of generated
+ // AggregateStore to store snapshots of its in-memory history of generated
// Observations.
//
- // backfill_days: the number of past days for which the EventAggregator
+ // backfill_days: the number of past days for which the AggregateStore
// generates and sends Observations, in addition to a requested day index.
- // See the comment above EventAggregator::GenerateObservations for more
+ // See the comment above AggreateStoe::GenerateObservations for more
// detail. The constructor CHECK-fails if a value larger than
// |kEventAggregatorMaxAllowedBackfillDays| is passed.
//
@@ -148,8 +119,8 @@
// ReportDefinition differ from those of the existing report.
logger::Status UpdateAggregationConfigs(const logger::ProjectContext& project_context);
- // Logs an Event associated to a report of type UNIQUE_N_DAY_ACTIVES to the
- // EventAggregator.
+ // Adds an Event associated to a report of type UNIQUE_N_DAY_ACTIVES to the
+ // AggregateStore.
//
// report_id: the ID of the report associated to the logged Event.
//
@@ -170,10 +141,11 @@
// together with valid aggregates when EventAggregator::GarbageCollect() is
// called.
logger::Status LogUniqueActivesEvent(uint32_t report_id, const logger::EventRecord& event_record);
+
// LogCountEvent, LogElapsedTimeEvent:
//
- // Logs an Event associated to a report of type PER_DEVICE_NUMERIC_STATS to
- // the EventAggregator.
+ // Adds an Event associated to a report of type PER_DEVICE_NUMERIC_STATS to
+ // the AggregateStore.
//
// report_id: the ID of the report associated to the logged Event.
//
@@ -194,9 +166,9 @@
// LogMemoryUsageEvent: |event_record| should wrap a MemoryUsageEvent.
logger::Status LogMemoryUsageEvent(uint32_t report_id, const logger::EventRecord& event_record);
- // Checks that the worker thread is shut down, and if so, calls the private
- // method GenerateObservations() and returns its result. Returns kOther if the
- // worker thread is joinable. See the documentation on GenerateObservations()
+ // Checks that the worker thread is shut down, and if so, calls
+ // AggregateStore::GenerateObservations() and returns its result. Returns kOther if the
+ // worker thread is joinable. See the documentation on AggregateStore::GenerateObservations()
// for a description of the parameters.
//
// This method is intended for use in tests which require a single thread to
@@ -206,43 +178,13 @@
private:
friend class EventAggregatorManager; // used for transition during redesign.
- friend class EventAggregatorManagerTest;
- friend class logger::UniqueActivesLoggerTest;
+ friend class AggregateStoreTest;
+ friend class AggregateStoreWorkerTest;
friend class EventAggregatorTest;
+ friend class EventAggregatorManagerTest;
friend class EventAggregatorWorkerTest;
friend class logger::TestEventAggregator;
-
- // Make a LocalAggregateStore which is empty except that its version number is set to |version|.
- LocalAggregateStore MakeNewLocalAggregateStore(
- uint32_t version = kCurrentLocalAggregateStoreVersion);
-
- // Make an AggregatedObservationHistoryStore which is empty except that its version number is set
- // to |version|.
- AggregatedObservationHistoryStore MakeNewObservationHistoryStore(
- uint32_t version = kCurrentObservationHistoryStoreVersion);
-
- // The LocalAggregateStore or AggregatedObservationHistoryStore may need to be changed in ways
- // which are structurally but not semantically backwards-compatible. In other words, the meaning
- // to the EventAggregator of a field in the LocalAggregateStore might change. An example is that
- // we might deprecate one field while introducing a new one.
- //
- // The MaybeUpgrade*Store methods allow the EventAggregator to update the contents of its stored
- // protos from previously meaningful values to currently meaningful values. (For example, a
- // possible implementation would move the contents of a deprecated field to the replacement
- // field.)
- //
- // These methods are called by the EventAggregator constructor immediately after reading in stored
- // protos from disk in order to ensure that proto contents have the expected semantics.
- //
- // The method first checks the version number of the store. If the version number is equal to
- // |kCurrentLocalAggregateStoreVersion| or |kCurrentObservationHistoryStoreVersion|
- // (respectively), returns an OK status. Otherwise, if it is possible to upgrade the store to the
- // current version, does so and returns an OK status. If not, logs an error and returns
- // kInvalidArguments. If a non-OK status is returned, the caller should discard the contents of
- // |store| and replace it with an empty store at the current version. The MakeNew*Store() methods
- // may be used to create the new store.
- logger::Status MaybeUpgradeLocalAggregateStore(LocalAggregateStore* store);
- logger::Status MaybeUpgradeObservationHistoryStore(AggregatedObservationHistoryStore* store);
+ friend class logger::UniqueActivesLoggerTest;
// Request that the worker thread shut down and wait for it to exit. The
// worker thread backs up the LocalAggregateStore before exiting.
@@ -255,69 +197,16 @@
// tasks.
void Run(std::unique_ptr<util::SystemClockInterface> system_clock);
- // Helper method called by Run(). If |next_generate_obs_| is less than or
- // equal to |steady_time|, calls GenerateObservations() with the day index of
- // the previous day from |system_time| in each of UTC and local time, and then
- // backs up the history of generated Observations. If |next_gc_| is less than
- // or equal to |steady_time|, calls GarbageCollect() with the day index of the
- // previous day from |system_time| in each of UTC and local time and then
- // backs up the LocalAggregateStore. In each case, an error is logged and
- // execution continues if the operation fails.
+ // Helper method called by Run(). If |next_generate_obs_| is less than or equal to |steady_time|,
+ // calls AggregateStore::GenerateObservations() with the day index of the previous day from
+ // |system_time| in each of UTC and local time, and then backs up the history of generated
+ // Observations. If |next_gc_| is less than or equal to |steady_time|, calls
+ // AggregateStore::GarbageCollect() with the day index of the previous day from |system_time| in
+ // each of UTC and local time and then backs up the LocalAggregateStore. In each case, an error is
+ // logged and execution continues if the operation fails.
void DoScheduledTasks(std::chrono::system_clock::time_point system_time,
std::chrono::steady_clock::time_point steady_time);
- // Writes a snapshot of the LocalAggregateStore to
- // |local_aggregate_proto_store_|.
- logger::Status BackUpLocalAggregateStore();
-
- // Writes a snapshot of |obs_history_|to |obs_history_proto_store_|.
- logger::Status BackUpObservationHistory();
-
- // Removes from the LocalAggregateStore all daily aggregates that are too
- // old to contribute to their parent report's largest rolling window on the
- // day which is |backfill_days| before |day_index_utc| (if the parent
- // MetricDefinitions' time zone policy is UTC) or which is |backfill_days|
- // before |day_index_local| (if the parent MetricDefinition's time zone policy
- // is LOCAL). If |day_index_local| is 0, then we set |day_index_local| =
- // |day_index_utc|.
- //
- // If the time zone policy of a report's parent metric is UTC (resp., LOCAL)
- // and if day_index is the largest value of the |day_index_utc| (resp.,
- // |day_index_local|) argument with which GarbageCollect() has been called,
- // then the LocalAggregateStore contains the data needed to generate
- // Observations for that report for day index (day_index + k) for any k >= 0.
- logger::Status GarbageCollect(uint32_t day_index_utc, uint32_t day_index_local = 0u);
-
- // Generates one or more Observations for all of the registered locally
- // aggregated reports known to this EventAggregator, for rolling windows
- // ending on specified day indices.
- //
- // Each MetricDefinition specifies a time zone policy, which determines the
- // day index for which an Event associated with that MetricDefinition is
- // logged. For all MetricDefinitions whose Events are logged with respect to
- // UTC, this method generates Observations for rolling windows ending on
- // |final_day_index_utc|. For all MetricDefinitions whose Events are logged
- // with respect to local time, this method generates Observations for rolling
- // windows ending on |final_day_index_local|. If |final_day_index_local| is
- // 0, then we set |final_day_index_local| = |final_day_index_utc|.
- //
- // The generated Observations are written to the |observation_writer| passed
- // to the constructor.
- //
- // This class maintains a history of generated Observations and this method
- // additionally performs backfill: Observations are also generated for
- // rolling windows ending on any day in the interval [final_day_index -
- // backfill_days, final_day_index] (where final_day_index is either
- // final_day_index_utc or final_day_index_local, depending on the time zone
- // policy of the associated MetricDefinition), if this history indicates they
- // were not previously generated. Does not generate duplicate Observations
- // when called multiple times with the same day index.
- //
- // Observations are not generated for aggregation windows larger than
- // |kMaxAllowedAggregationDays|. Hourly windows are not yet supported.
- logger::Status GenerateObservations(uint32_t final_day_index_utc,
- uint32_t final_day_index_local = 0u);
-
// Logs a numeric value to the LocalAggregateStore by adding |value| to the
// current daily aggregate in the bucket indexed by |report_key|, |day_index|,
// |component|, and |event_code|. This is a helper method called by
@@ -325,103 +214,6 @@
logger::Status LogNumericEvent(const std::string& report_key, uint32_t day_index,
const std::string& component, uint64_t event_code, int64_t value);
- // Returns the most recent day index for which an Observation was generated
- // for a given UNIQUE_N_DAY_ACTIVES report, event code, and day-based aggregation window,
- // according to |obs_history_|. Returns 0 if no Observation has been generated
- // for the given arguments.
- uint32_t UniqueActivesLastGeneratedDayIndex(const std::string& report_key, uint32_t event_code,
- uint32_t aggregation_days) const;
-
- // Returns the most recent day index for which an Observation was generated for a given
- // PER_DEVICE_NUMERIC_STATS or PER_DEVICE_HISTOGRAM report, component, event code, and day-based
- // aggregation window, according to |obs_history_|. Returns 0 if no Observation has been generated
- // for the given arguments.
- uint32_t PerDeviceNumericLastGeneratedDayIndex(const std::string& report_key,
- const std::string& component, uint32_t event_code,
- uint32_t aggregation_days) const;
-
- // Returns the most recent day index for which a
- // ReportParticipationObservation was generated for a given report, according
- // to |obs_history_|. Returns 0 if no Observation has been generated for the
- // given arguments.
- uint32_t ReportParticipationLastGeneratedDayIndex(const std::string& report_key) const;
-
- // For a fixed report of type UNIQUE_N_DAY_ACTIVES, generates an Observation
- // for each event code of the parent metric, for each day-based aggregation window of the
- // report ending on |final_day_index|, unless an Observation with those parameters was generated
- // in the past. Also generates Observations for days in the backfill period if needed. Writes the
- // Observations to an ObservationStore via the ObservationWriter that was passed to the
- // constructor.
- //
- // Observations are not generated for aggregation windows larger than
- // |kMaxAllowedAggregationDays|. Hourly windows are not yet supported.
- logger::Status GenerateUniqueActivesObservations(logger::MetricRef metric_ref,
- const std::string& report_key,
- const ReportAggregates& report_aggregates,
- uint32_t num_event_codes,
- uint32_t final_day_index);
-
- // Helper method called by GenerateUniqueActivesObservations() to generate
- // and write a single Observation.
- logger::Status GenerateSingleUniqueActivesObservation(logger::MetricRef metric_ref,
- const ReportDefinition* report,
- uint32_t obs_day_index, uint32_t event_code,
- const OnDeviceAggregationWindow& window,
- bool was_active) const;
-
- // For a fixed report of type PER_DEVICE_NUMERIC_STATS or PER_DEVICE_HISTOGRAM, generates a
- // PerDeviceNumericObservation and PerDeviceHistogramObservation respectively for each
- // tuple (component, event code, aggregation_window) for which a numeric event was logged for that
- // event code and component during the window of that size ending on |final_day_index|, unless an
- // Observation with those parameters has been generated in the past. The value of the Observation
- // is the sum, max, or min (depending on the aggregation_type field of the report definition) of
- // all numeric events logged for that report during the window. Also generates observations for
- // days in the backfill period if needed.
- //
- // In addition to PerDeviceNumericObservations or PerDeviceHistogramObservation , generates
- // a ReportParticipationObservation for |final_day_index| and any needed days in the backfill
- // period. These ReportParticipationObservations are used by the report generators to infer the
- // fleet-wide number of devices for which the sum of numeric events associated to each tuple
- // (component, event code, window size) was zero.
- //
- // Observations are not generated for aggregation windows larger than
- // |kMaxAllowedAggregationWindowSize|.
- logger::Status GenerateObsFromNumericAggregates(logger::MetricRef metric_ref,
- const std::string& report_key,
- const ReportAggregates& report_aggregates,
- uint32_t final_day_index);
-
- // Helper method called by GenerateObsFromNumericAggregates() to generate and write a single
- // Observation with value |value|. The method will produce a PerDeviceNumericObservation or
- // PerDeviceHistogramObservation depending on whether the report type is
- // PER_DEVICE_NUMERIC_STATS or PER_DEVICE_HISTOGRAM respectively.
- logger::Status GenerateSinglePerDeviceNumericObservation(
- logger::MetricRef metric_ref, const ReportDefinition* report, uint32_t obs_day_index,
- const std::string& component, uint32_t event_code, const OnDeviceAggregationWindow& window,
- int64_t value) const;
-
- // Helper method called by GenerateObsFromNumericAggregates() to generate and write a single
- // Observation with value |value|.
- logger::Status GenerateSinglePerDeviceHistogramObservation(
- logger::MetricRef metric_ref, const ReportDefinition* report, uint32_t obs_day_index,
- const std::string& component, uint32_t event_code, const OnDeviceAggregationWindow& window,
- int64_t value) const;
-
- // Helper method called by GenerateObsFromNumericAggregates() to generate and write a single
- // ReportParticipationObservation.
- logger::Status GenerateSingleReportParticipationObservation(logger::MetricRef metric_ref,
- const ReportDefinition* report,
- uint32_t obs_day_index) const;
-
- LocalAggregateStore CopyLocalAggregateStore() {
- auto local_aggregate_store = protected_aggregate_store_.lock()->local_aggregate_store;
- return local_aggregate_store;
- }
-
- struct AggregateStoreFields {
- LocalAggregateStore local_aggregate_store;
- };
-
// Sets the EventAggregator's SteadyClockInterface. Only for use in tests.
void SetSteadyClock(util::SteadyClockInterface* clock) { steady_clock_.reset(clock); }
@@ -447,14 +239,7 @@
std::condition_variable_any shutdown_notifier;
};
- const logger::Encoder* encoder_;
- const logger::ObservationWriter* observation_writer_;
- util::ConsistentProtoStore* local_aggregate_proto_store_;
- util::ConsistentProtoStore* obs_history_proto_store_;
- util::ProtectedFields<AggregateStoreFields> protected_aggregate_store_;
- // Not protected by a mutex. Should only be accessed by |worker_thread_|.
- AggregatedObservationHistoryStore obs_history_;
- size_t backfill_days_ = 0;
+ std::unique_ptr<AggregateStore> aggregate_store_;
std::thread worker_thread_;
util::ProtectedFields<WorkerThreadController> protected_worker_thread_controller_;
diff --git a/src/local_aggregation/event_aggregator_mgr_test.cc b/src/local_aggregation/event_aggregator_mgr_test.cc
index 7bdee94..099e708 100644
--- a/src/local_aggregation/event_aggregator_mgr_test.cc
+++ b/src/local_aggregation/event_aggregator_mgr_test.cc
@@ -122,7 +122,7 @@
uint32_t NumberOfKVPairsInStore(EventAggregatorManager* event_aggregator_mgr) {
return event_aggregator_mgr->GetEventAggregator()
- ->CopyLocalAggregateStore()
+ ->aggregate_store_->CopyLocalAggregateStore()
.by_report_key()
.size();
}
@@ -145,8 +145,7 @@
uint32_t GetNumberOfUniqueActivesAggregates(EventAggregatorManager* event_aggregator_mgr) {
auto local_aggregate_store =
- event_aggregator_mgr->GetEventAggregator()->CopyLocalAggregateStore();
-
+ event_aggregator_mgr->GetEventAggregator()->aggregate_store_->CopyLocalAggregateStore();
uint32_t num_aggregates = 0;
for (const auto& [report_key, aggregates] : local_aggregate_store.by_report_key()) {
if (aggregates.type_case() != ReportAggregates::kUniqueActivesAggregates) {
@@ -166,8 +165,7 @@
const std::shared_ptr<const ProjectContext>& project_context,
const MetricReportId& metric_report_id, uint32_t day_index, uint32_t event_code) {
auto local_aggregate_store =
- event_aggregator_mgr->GetEventAggregator()->CopyLocalAggregateStore();
-
+ event_aggregator_mgr->GetEventAggregator()->aggregate_store_->CopyLocalAggregateStore();
std::string key;
if (!SerializeToBase64(MakeAggregationKey(*project_context, metric_report_id), &key)) {
return AssertionFailure() << "Could not serialize key with metric id "
diff --git a/src/local_aggregation/event_aggregator_test.cc b/src/local_aggregation/event_aggregator_test.cc
index a6635f0..d5c8653 100644
--- a/src/local_aggregation/event_aggregator_test.cc
+++ b/src/local_aggregation/event_aggregator_test.cc
@@ -25,7 +25,7 @@
#include "src/registry/project_configs.h"
#include "third_party/googletest/googletest/include/gtest/gtest.h"
-namespace cobalt {
+namespace cobalt::local_aggregation {
using config::PackEventCodes;
using logger::Encoder;
@@ -36,18 +36,12 @@
using logger::ObservationWriter;
using logger::ProjectContext;
using logger::Status;
-using logger::testing::CheckPerDeviceNumericObservations;
using logger::testing::CheckUniqueActivesObservations;
-using logger::testing::ExpectedAggregationParams;
-using logger::testing::ExpectedPerDeviceNumericObservations;
-using logger::testing::ExpectedReportParticipationObservations;
using logger::testing::ExpectedUniqueActivesObservations;
using logger::testing::FakeObservationStore;
-using logger::testing::FetchAggregatedObservations;
using logger::testing::GetTestProject;
using logger::testing::MakeAggregationConfig;
using logger::testing::MakeAggregationKey;
-using logger::testing::MakeExpectedReportParticipationObservations;
using logger::testing::MakeNullExpectedUniqueActivesObservations;
using logger::testing::MockConsistentProtoStore;
using logger::testing::TestUpdateRecipient;
@@ -59,8 +53,6 @@
using util::SerializeToBase64;
using util::TimeToDayIndex;
-namespace local_aggregation {
-
namespace {
// Number of seconds in a day
constexpr int kDay = 60 * 60 * 24;
@@ -105,12 +97,6 @@
// aggregation configurations.
class EventAggregatorTest : public ::testing::Test {
protected:
- const uint32_t kCurrentLocalAggregateStoreVersion =
- EventAggregator::kCurrentLocalAggregateStoreVersion;
-
- const uint32_t kCurrentObservationHistoryStoreVersion =
- EventAggregator::kCurrentObservationHistoryStoreVersion;
-
void SetUp() override {
observation_store_ = std::make_unique<FakeObservationStore>();
update_recipient_ = std::make_unique<TestUpdateRecipient>();
@@ -158,43 +144,10 @@
time_zone);
}
- size_t GetBackfillDays() { return event_aggregator_->backfill_days_; }
-
- void SetBackfillDays(size_t num_days) { event_aggregator_->backfill_days_ = num_days; }
-
- Status BackUpLocalAggregateStore() { return event_aggregator_->BackUpLocalAggregateStore(); }
-
- Status BackUpObservationHistory() { return event_aggregator_->BackUpObservationHistory(); }
-
- LocalAggregateStore MakeNewLocalAggregateStore(
- uint32_t version = EventAggregator::kCurrentLocalAggregateStoreVersion) {
- return event_aggregator_->MakeNewLocalAggregateStore(version);
- }
-
- AggregatedObservationHistoryStore MakeNewObservationHistoryStore(
- uint32_t version = EventAggregator::kCurrentObservationHistoryStoreVersion) {
- return event_aggregator_->MakeNewObservationHistoryStore(version);
- }
-
- Status MaybeUpgradeLocalAggregateStore(LocalAggregateStore* store) {
- return event_aggregator_->MaybeUpgradeLocalAggregateStore(store);
- }
-
- Status MaybeUpgradeObservationHistoryStore(AggregatedObservationHistoryStore* store) {
- return event_aggregator_->MaybeUpgradeObservationHistoryStore(store);
- }
+ size_t GetBackfillDays() { return event_aggregator_->aggregate_store_->backfill_days_; }
LocalAggregateStore CopyLocalAggregateStore() {
- return event_aggregator_->CopyLocalAggregateStore();
- }
-
- Status GenerateObservations(uint32_t final_day_index_utc, uint32_t final_day_index_local = 0u) {
- return event_aggregator_->GenerateObservationsNoWorker(final_day_index_utc,
- final_day_index_local);
- }
-
- Status GarbageCollect(uint32_t day_index_utc, uint32_t day_index_local = 0u) {
- return event_aggregator_->GarbageCollect(day_index_utc, day_index_local);
+ return event_aggregator_->aggregate_store_->CopyLocalAggregateStore();
}
void TriggerAndWaitForDoScheduledTasks() {
@@ -214,14 +167,6 @@
}
}
- void DoScheduledTasksNow() {
- // Steady values don't matter, just tell DoScheduledTasks to run everything.
- auto steady_time = std::chrono::steady_clock::now();
- event_aggregator_->next_generate_obs_ = steady_time;
- event_aggregator_->next_gc_ = steady_time;
- event_aggregator_->DoScheduledTasks(unowned_test_clock_->now(), steady_time);
- }
-
// Clears the FakeObservationStore and resets the counts of Observations
// received by the FakeObservationStore and the TestUpdateRecipient.
void ResetObservationStore() {
@@ -389,7 +334,7 @@
// of reference.
bool CheckUniqueActivesAggregates(const LoggedActivity& logged_activity,
uint32_t /*current_day_index*/) {
- auto local_aggregate_store = event_aggregator_->CopyLocalAggregateStore();
+ auto local_aggregate_store = event_aggregator_->aggregate_store_->CopyLocalAggregateStore();
// Check that the LocalAggregateStore contains no more UniqueActives
// aggregates than |logged_activity| and |day_last_garbage_collected_|
// should imply.
@@ -504,7 +449,7 @@
bool CheckPerDeviceNumericAggregates(const LoggedValues& logged_values,
uint32_t /*current_day_index*/) {
- auto local_aggregate_store = event_aggregator_->CopyLocalAggregateStore();
+ auto local_aggregate_store = event_aggregator_->aggregate_store_->CopyLocalAggregateStore();
// Check that the LocalAggregateStore contains no more PerDeviceNumeric
// aggregates than |logged_values| and |day_last_garbage_collected_| should
// imply.
@@ -844,16 +789,6 @@
logger::testing::per_device_numeric_stats::kCobaltRegistryBase64) {}
};
-// Creates an EventAggregator and provides it with a ProjectContext generated
-// from test_registries/mixed_time_zone_test_registry.yaml. This registry
-// contains multiple MetricDefinitions with different time zone policies.
-class NoiseFreeMixedTimeZoneEventAggregatorTest : public EventAggregatorTestWithProjectContext {
- protected:
- NoiseFreeMixedTimeZoneEventAggregatorTest()
- : EventAggregatorTestWithProjectContext(
- logger::testing::mixed_time_zone::kCobaltRegistryBase64) {}
-};
-
class PerDeviceHistogramEventAggregatorTest : public EventAggregatorTestWithProjectContext {
protected:
PerDeviceHistogramEventAggregatorTest()
@@ -878,92 +813,6 @@
bool worker_joinable() { return event_aggregator_->worker_thread_.joinable(); }
};
-// Tests that the Read() method of each ConsistentProtoStore is called once
-// during construction of the EventAggregator.
-TEST_F(EventAggregatorTest, ReadProtosFromFiles) {
- EXPECT_EQ(1, local_aggregate_proto_store_->read_count_);
- EXPECT_EQ(1, obs_history_proto_store_->read_count_);
-}
-
-// Tests that the BackUp*() methods return a positive status, and checks that
-// the Write() method of a ConsistentProtoStore is called once when its
-// respective BackUp*() method is called.
-TEST_F(EventAggregatorTest, BackUpProtos) {
- EXPECT_EQ(kOK, BackUpLocalAggregateStore());
- EXPECT_EQ(kOK, BackUpObservationHistory());
- EXPECT_EQ(1, local_aggregate_proto_store_->write_count_);
- EXPECT_EQ(1, obs_history_proto_store_->write_count_);
-}
-
-// MaybeUpgradeLocalAggregateStore should return an OK status if the version is current. The store
-// should not change.
-TEST_F(EventAggregatorTest, MaybeUpgradeLocalAggregateStoreCurrent) {
- auto store = MakeNewLocalAggregateStore();
- std::string store_before = SerializeAsStringDeterministic(store);
- ASSERT_EQ(kCurrentLocalAggregateStoreVersion, store.version());
- EXPECT_EQ(kOK, MaybeUpgradeLocalAggregateStore(&store));
- EXPECT_EQ(store_before, SerializeAsStringDeterministic(store));
-}
-
-// MaybeUpgradeLocalAggregateStore should return kInvalidArguments if it is not possible to upgrade
-// to the current version.
-TEST_F(EventAggregatorTest, MaybeUpgradeLocalAggregateStoreUnsupported) {
- const uint32_t kFutureVersion = kCurrentLocalAggregateStoreVersion + 1;
- auto store = MakeNewLocalAggregateStore(kFutureVersion);
- ASSERT_EQ(kFutureVersion, store.version());
- EXPECT_EQ(kInvalidArguments, MaybeUpgradeLocalAggregateStore(&store));
-}
-
-// It should be possible to upgrade the LocalAggregateStore from v0 to the current version. The
-// version number should be updated and the contents of window_size in each AggregationConfigs
-// should be moved to aggregation_window, preserving their order.
-TEST_F(EventAggregatorTest, MaybeUpgradeLocalAggregateStoreFromV0) {
- const uint32_t kVersionZero = 0;
- const std::vector<uint32_t> kWindowSizes = {1, 7, 30};
- const std::string kKey = "some_report_key";
-
- // Make a v0 LocalAggregateStore with one report.
- auto store = MakeNewLocalAggregateStore(kVersionZero);
- ReportAggregates report_aggregates;
- for (auto window_size : kWindowSizes) {
- report_aggregates.mutable_aggregation_config()->add_window_size(window_size);
- }
- (*store.mutable_by_report_key())[kKey] = report_aggregates;
-
- // Make the expected upgraded store.
- auto expected_store =
- MakeNewLocalAggregateStore(EventAggregator::kCurrentLocalAggregateStoreVersion);
- ReportAggregates expected_report_aggregates;
- for (auto window_size : kWindowSizes) {
- *expected_report_aggregates.mutable_aggregation_config()->add_aggregation_window() =
- MakeDayWindow(window_size);
- }
- (*expected_store.mutable_by_report_key())[kKey] = expected_report_aggregates;
-
- // Upgrade and check that the upgraded store is as expected.
- EXPECT_EQ(kOK, MaybeUpgradeLocalAggregateStore(&store));
- EXPECT_EQ(SerializeAsStringDeterministic(expected_store), SerializeAsStringDeterministic(store));
-}
-
-// MaybeUpgradeObservationHistoryStore should return an OK status if the version is current. The
-// store should not change.
-TEST_F(EventAggregatorTest, MaybeUpgradeObservationHistoryStoreCurrent) {
- auto store = MakeNewObservationHistoryStore();
- std::string store_before = SerializeAsStringDeterministic(store);
- ASSERT_EQ(kCurrentObservationHistoryStoreVersion, store.version());
- EXPECT_EQ(kOK, MaybeUpgradeObservationHistoryStore(&store));
- EXPECT_EQ(store_before, SerializeAsStringDeterministic(store));
-}
-
-// MaybeUpgradeObservationHistoryStore should return kInvalidArguments if it is not possible to
-// upgrade to the current version.
-TEST_F(EventAggregatorTest, MaybeUpgradeObservationHistoryStoreUnsupported) {
- const uint32_t kFutureVersion = kCurrentObservationHistoryStoreVersion + 1;
- auto store = MakeNewObservationHistoryStore(kFutureVersion);
- ASSERT_EQ(kFutureVersion, store.version());
- EXPECT_EQ(kInvalidArguments, MaybeUpgradeObservationHistoryStore(&store));
-}
-
// Tests that an empty LocalAggregateStore is updated with
// ReportAggregationKeys and AggregationConfigs as expected when
// EventAggregator::UpdateAggregationConfigs is called with a ProjectContext
@@ -1101,85 +950,6 @@
bad_event_record3));
}
-// Tests that EventAggregator::GenerateObservations() returns a positive
-// status and that the expected number of Observations is generated when no
-// Events have been logged to the EventAggregator.
-TEST_F(EventAggregatorTest, GenerateObservationsNoEvents) {
- // Provide the all_report_types test registry to the EventAggregator.
- auto project_context = GetTestProject(logger::testing::all_report_types::kCobaltRegistryBase64);
- EXPECT_EQ(kOK, event_aggregator_->UpdateAggregationConfigs(*project_context));
- // Generate locally aggregated Observations for the current day index.
- EXPECT_EQ(kOK, GenerateObservations(CurrentDayIndex()));
- std::vector<Observation2> observations(0);
- EXPECT_TRUE(FetchAggregatedObservations(
- &observations, logger::testing::all_report_types::kExpectedAggregationParams,
- observation_store_.get(), update_recipient_.get()));
-}
-
-// Tests that EventAggregator::GenerateObservations() only generates
-// Observations the first time it is called for a given day index.
-TEST_F(EventAggregatorTest, GenerateObservationsTwice) {
- // Provide the all_report_types test registry to the EventAggregator.
- auto project_context = GetTestProject(logger::testing::all_report_types::kCobaltRegistryBase64);
- EXPECT_EQ(kOK, event_aggregator_->UpdateAggregationConfigs(*project_context));
- // Check that Observations are generated when GenerateObservations is called
- // for the current day index for the first time.
- auto current_day_index = CurrentDayIndex();
- EXPECT_EQ(kOK, GenerateObservations(current_day_index));
- std::vector<Observation2> observations(0);
- EXPECT_TRUE(FetchAggregatedObservations(
- &observations, logger::testing::all_report_types::kExpectedAggregationParams,
- observation_store_.get(), update_recipient_.get()));
- // Check that no Observations are generated when GenerateObservations is
- // called for the currentday index for the second time.
- ResetObservationStore();
- EXPECT_EQ(kOK, GenerateObservations(current_day_index));
- EXPECT_EQ(0u, observation_store_->messages_received.size());
-}
-
-// When the LocalAggregateStore contains one ReportAggregates proto and that
-// proto is empty, GenerateObservations should return success but generate no
-// observations.
-TEST_F(EventAggregatorTest, GenerateObservationsFromBadStore) {
- auto bad_store = std::make_unique<LocalAggregateStore>();
- (*bad_store->mutable_by_report_key())["some_key"] = ReportAggregates();
- local_aggregate_proto_store_->set_stored_proto(std::move(bad_store));
- // Read the bad store in to the EventAggregator.
- ResetEventAggregator();
- EXPECT_EQ(kOK, GenerateObservations(CurrentDayIndex()));
- EXPECT_EQ(0u, observation_store_->messages_received.size());
-}
-
-// When the LocalAggregateStore contains one empty ReportAggregates proto and
-// some valid ReportAggregates, GenerateObservations should produce observations
-// for the valid ReportAggregates.
-TEST_F(EventAggregatorTest, GenerateObservationsFromBadStoreMultiReport) {
- auto bad_store = std::make_unique<LocalAggregateStore>();
- (*bad_store->mutable_by_report_key())["some_key"] = ReportAggregates();
- local_aggregate_proto_store_->set_stored_proto(std::move(bad_store));
- // Read the bad store in to the EventAggregator.
- ResetEventAggregator();
- // Provide the all_report_types test registry to the EventAggregator.
- auto project_context = GetTestProject(logger::testing::all_report_types::kCobaltRegistryBase64);
- EXPECT_EQ(kOK, event_aggregator_->UpdateAggregationConfigs(*project_context));
- EXPECT_EQ(kOK, GenerateObservations(CurrentDayIndex()));
- std::vector<Observation2> observations(0);
- EXPECT_TRUE(FetchAggregatedObservations(
- &observations, logger::testing::all_report_types::kExpectedAggregationParams,
- observation_store_.get(), update_recipient_.get()));
-}
-
-// When the LocalAggregateStore contains one ReportAggregates proto and that
-// proto is empty, GarbageCollect should return success.
-TEST_F(EventAggregatorTest, GarbageCollectBadStore) {
- auto bad_store = std::make_unique<LocalAggregateStore>();
- (*bad_store->mutable_by_report_key())["some_key"] = ReportAggregates();
- local_aggregate_proto_store_->set_stored_proto(std::move(bad_store));
- // Read the bad store in to the EventAggregator.
- ResetEventAggregator();
- EXPECT_EQ(kOK, GarbageCollect(CurrentDayIndex()));
-}
-
// Tests that the LocalAggregateStore is updated as expected when
// EventAggregator::LogUniqueActivesEvent() is called with valid arguments;
// i.e., with a report ID associated to an existing key of the
@@ -1225,938 +995,6 @@
}
}
-// Tests GarbageCollect() for UniqueActivesReportAggregates.
-//
-// For each value of N in the range [0, 34], logs some UniqueActivesEvents
-// each day for N consecutive days and then garbage-collects the
-// LocalAggregateStore. After garbage collection, verifies the contents of
-// the LocalAggregateStore.
-TEST_F(UniqueActivesEventAggregatorTest, GarbageCollect) {
- uint32_t max_days_before_gc = 35;
- for (uint32_t days_before_gc = 0; days_before_gc < max_days_before_gc; days_before_gc++) {
- SetUp();
- day_last_garbage_collected_ = 0u;
- LoggedActivity logged_activity;
- for (uint32_t offset = 0; offset < days_before_gc; offset++) {
- auto day_index = CurrentDayIndex();
- for (const auto& metric_report_id :
- logger::testing::unique_actives::kExpectedAggregationParams.metric_report_ids) {
- // Log 2 events with event code 0.
- EXPECT_EQ(kOK, LogUniqueActivesEvent(metric_report_id, day_index, 0u, &logged_activity));
- EXPECT_EQ(kOK, LogUniqueActivesEvent(metric_report_id, day_index, 0u, &logged_activity));
- if (offset < 3) {
- // Log 1 event with event code 1.
- EXPECT_EQ(kOK, LogUniqueActivesEvent(metric_report_id, day_index, 1u, &logged_activity));
- }
- }
- AdvanceClock(kDay);
- }
- auto end_day_index = CurrentDayIndex();
- EXPECT_EQ(kOK, GarbageCollect(end_day_index));
- day_last_garbage_collected_ = end_day_index;
- EXPECT_TRUE(CheckUniqueActivesAggregates(logged_activity, end_day_index));
- TearDown();
- }
-}
-
-// Tests that EventAggregator::GenerateObservations() returns a positive
-// status and that the expected number of Observations is generated after
-// some UniqueActivesEvents have been logged, without any garbage
-// collection.
-//
-// For 35 days, logs 2 events each day for the NetworkActivity_UniqueDevices
-// reports and 2 events for the FeaturesActive_UniqueDevices report, all
-// with event code 0.
-//
-// Each day, calls GenerateObservations() with the day index of the previous
-// day. Checks that a positive status is returned and that the
-// FakeObservationStore has received the expected number of new observations
-// for each locally aggregated report ID in the unique_actives registry.
-TEST_F(UniqueActivesEventAggregatorTest, GenerateObservations) {
- int num_days = 35;
- std::vector<Observation2> observations(0);
- for (int offset = 0; offset < num_days; offset++) {
- auto day_index = CurrentDayIndex();
- observations.clear();
- ResetObservationStore();
- EXPECT_EQ(kOK, GenerateObservations(day_index - 1));
- EXPECT_TRUE(FetchAggregatedObservations(
- &observations, logger::testing::unique_actives::kExpectedAggregationParams,
- observation_store_.get(), update_recipient_.get()));
- for (int i = 0; i < 2; i++) {
- EXPECT_EQ(kOK, LogUniqueActivesEvent(
- logger::testing::unique_actives::kNetworkActivityWindowSizeMetricReportId,
- day_index, 0u));
- EXPECT_EQ(
- kOK, LogUniqueActivesEvent(
- logger::testing::unique_actives::kNetworkActivityAggregationWindowMetricReportId,
- day_index, 0u));
- EXPECT_EQ(
- kOK, LogUniqueActivesEvent(logger::testing::unique_actives::kFeaturesActiveMetricReportId,
- day_index, 0u));
- }
- AdvanceClock(kDay);
- }
- observations.clear();
- ResetObservationStore();
- EXPECT_EQ(kOK, GenerateObservations(CurrentDayIndex() - 1));
- EXPECT_TRUE(FetchAggregatedObservations(
- &observations, logger::testing::unique_actives::kExpectedAggregationParams,
- observation_store_.get(), update_recipient_.get()));
-}
-
-// Tests that GenerateObservations() returns a positive status and that the
-// expected number of Observations is generated each day when Events are
-// logged for UNIQUE_N_DAY_ACTIVES reports over multiple days, and when the
-// LocalAggregateStore is garbage-collected each day.
-//
-// For 35 days, logs 2 events each day for the NetworkActivity_UniqueDevices
-// reports and 2 events for the FeaturesActive_UniqueDevices report, all
-// with event code 0.
-//
-// Each day following the first day, calls GenerateObservations() and then
-// GarbageCollect() with the day index of the current day. Checks that
-// positive statuses are returned and that the FakeObservationStore has
-// received the expected number of new observations for each locally
-// aggregated report ID in the unique_actives registry.
-TEST_F(UniqueActivesEventAggregatorTest, GenerateObservationsWithGc) {
- int num_days = 35;
- std::vector<Observation2> observations(0);
- for (int offset = 0; offset < num_days; offset++) {
- auto day_index = CurrentDayIndex();
- observations.clear();
- ResetObservationStore();
- EXPECT_EQ(kOK, GenerateObservations(day_index - 1));
- EXPECT_TRUE(FetchAggregatedObservations(
- &observations, logger::testing::unique_actives::kExpectedAggregationParams,
- observation_store_.get(), update_recipient_.get()));
- EXPECT_EQ(kOK, GarbageCollect(day_index));
- for (int i = 0; i < 2; i++) {
- EXPECT_EQ(kOK, LogUniqueActivesEvent(
- logger::testing::unique_actives::kNetworkActivityWindowSizeMetricReportId,
- day_index, 0u));
- EXPECT_EQ(
- kOK, LogUniqueActivesEvent(
- logger::testing::unique_actives::kNetworkActivityAggregationWindowMetricReportId,
- day_index, 0u));
- EXPECT_EQ(
- kOK, LogUniqueActivesEvent(logger::testing::unique_actives::kFeaturesActiveMetricReportId,
- day_index, 0u));
- }
- AdvanceClock(kDay);
- }
- observations.clear();
- ResetObservationStore();
- auto day_index = CurrentDayIndex();
- EXPECT_EQ(kOK, GenerateObservations(day_index - 1));
- EXPECT_TRUE(FetchAggregatedObservations(
- &observations, logger::testing::unique_actives::kExpectedAggregationParams,
- observation_store_.get(), update_recipient_.get()));
- EXPECT_EQ(kOK, GarbageCollect(day_index));
-}
-
-// Tests that GenerateObservations() returns a positive status and that the
-// expected number of Observations is generated when events are logged over
-// multiple days and some of those days' Observations are backfilled, without
-// any garbage collection of the LocalAggregateStore.
-//
-// Sets the |backfill_days_| field of the EventAggregator to 3.
-//
-// Logging pattern:
-// For 35 days, logs 2 events each day for the
-// NetworkActivity_UniqueDevices reports and 2 events for the
-// FeaturesActive_UniqueDevices report, all with event code 0.
-//
-// Observation generation pattern:
-// Calls GenerateObservations() on the 1st through 5th and the 7th out of
-// every 10 days, for 35 days.
-//
-// Expected numbers of Observations:
-// It is expected that 4 days' worth of Observations are generated on
-// the first day of every 10 (the day index for which GenerateObservations()
-// was called, plus 3 days of backfill), that 1 day's worth of Observations
-// are generated on the 2nd through 5th day of every 10, that 2 days'
-// worth of Observations are generated on the 7th day of every 10 (the
-// day index for which GenerateObservations() was called, plus 1 day of
-// backfill), and that no Observations are generated on the remaining days.
-TEST_F(UniqueActivesEventAggregatorTest, GenerateObservationsWithBackfill) {
- // Set |backfill_days_| to 3.
- size_t backfill_days = 3;
- SetBackfillDays(backfill_days);
- // Log 2 events each day for 35 days. Call GenerateObservations() on the
- // first 5 day indices, and the 7th, out of every 10.
- for (int offset = 0; offset < 35; offset++) {
- auto day_index = CurrentDayIndex();
- for (int i = 0; i < 2; i++) {
- EXPECT_EQ(kOK, LogUniqueActivesEvent(
- logger::testing::unique_actives::kNetworkActivityWindowSizeMetricReportId,
- day_index, 0u));
- EXPECT_EQ(
- kOK, LogUniqueActivesEvent(
- logger::testing::unique_actives::kNetworkActivityAggregationWindowMetricReportId,
- day_index, 0u));
- EXPECT_EQ(
- kOK, LogUniqueActivesEvent(logger::testing::unique_actives::kFeaturesActiveMetricReportId,
- day_index, 0u));
- }
- observation_store_->ResetObservationCounter();
- if (offset % 10 < 5 || offset % 10 == 6) {
- EXPECT_EQ(kOK, GenerateObservations(day_index));
- }
- auto num_new_obs = observation_store_->num_observations_added();
- EXPECT_GE(num_new_obs, 0u);
- // Check that the expected daily number of Observations was generated.
- switch (offset % 10) {
- case 0:
- EXPECT_EQ(logger::testing::unique_actives::kExpectedAggregationParams.daily_num_obs *
- (backfill_days + 1),
- num_new_obs);
- break;
- case 1:
- case 2:
- case 3:
- case 4:
- EXPECT_EQ(logger::testing::unique_actives::kExpectedAggregationParams.daily_num_obs,
- num_new_obs);
- break;
- case 6:
- EXPECT_EQ(logger::testing::unique_actives::kExpectedAggregationParams.daily_num_obs * 2,
- num_new_obs);
- break;
- default:
- EXPECT_EQ(0u, num_new_obs);
- }
- AdvanceClock(kDay);
- }
-}
-
-// Tests that GenerateObservations() returns a positive status and that the
-// expected number of Observations is generated when events are logged over
-// multiple days and some of those days' Observations are backfilled, and when
-// the LocalAggregateStore is garbage-collected after each call to
-// GenerateObservations().
-//
-// Sets the |backfill_days_| field of the EventAggregator to 3.
-//
-// Logging pattern:
-// For 35 days, logs 2 events each day for the
-// NetworkActivity_UniqueDevices reports and 2 events for the
-// FeaturesActive_Unique_Devices report, all with event code 0.
-//
-// Observation generation pattern:
-// Calls GenerateObservations() on the 1st through 5th and the 7th out of
-// every 10 days, for 35 days. Garbage-collects the LocalAggregateStore after
-// each call.
-//
-// Expected numbers of Observations:
-// It is expected that 4 days' worth of Observations are generated on
-// the first day of every 10 (the day index for which GenerateObservations()
-// was called, plus 3 days of backfill), that 1 day's worth of Observations
-// are generated on the 2nd through 5th day of every 10, that 2 days'
-// worth of Observations are generated on the 7th day of every 10 (the
-// day index for which GenerateObservations() was called, plus 1 day of
-// backfill), and that no Observations are generated on the remaining days.
-TEST_F(UniqueActivesEventAggregatorTest, GenerateObservationsWithBackfillAndGc) {
- int num_days = 35;
- // Set |backfill_days_| to 3.
- size_t backfill_days = 3;
- SetBackfillDays(backfill_days);
- // Log 2 events each day for 35 days. Call GenerateObservations() on the
- // first 5 day indices, and the 7th, out of every 10.
- for (int offset = 0; offset < num_days; offset++) {
- auto day_index = CurrentDayIndex();
- for (int i = 0; i < 2; i++) {
- EXPECT_EQ(kOK, LogUniqueActivesEvent(
- logger::testing::unique_actives::kNetworkActivityWindowSizeMetricReportId,
- day_index, 0u));
- EXPECT_EQ(
- kOK, LogUniqueActivesEvent(
- logger::testing::unique_actives::kNetworkActivityAggregationWindowMetricReportId,
- day_index, 0u));
- EXPECT_EQ(
- kOK, LogUniqueActivesEvent(logger::testing::unique_actives::kFeaturesActiveMetricReportId,
- day_index, 0u));
- }
- observation_store_->ResetObservationCounter();
- if (offset % 10 < 5 || offset % 10 == 6) {
- EXPECT_EQ(kOK, GenerateObservations(day_index));
- EXPECT_EQ(kOK, GarbageCollect(day_index));
- }
- auto num_new_obs = observation_store_->num_observations_added();
- EXPECT_GE(num_new_obs, 0u);
- // Check that the expected daily number of Observations was generated.
- // This expected number is some multiple of the daily_num_obs field of
- // |kUniqueActivesExpectedParams|, depending on the number of days which
- // should have been backfilled when GenerateObservations() was called.
- switch (offset % 10) {
- case 0:
- EXPECT_EQ(logger::testing::unique_actives::kExpectedAggregationParams.daily_num_obs *
- (backfill_days + 1),
- num_new_obs);
- break;
- case 1:
- case 2:
- case 3:
- case 4:
- EXPECT_EQ(logger::testing::unique_actives::kExpectedAggregationParams.daily_num_obs,
- num_new_obs);
- break;
- case 6:
- EXPECT_EQ(logger::testing::unique_actives::kExpectedAggregationParams.daily_num_obs * 2,
- num_new_obs);
- break;
- default:
- EXPECT_EQ(0u, num_new_obs);
- }
- AdvanceClock(kDay);
- }
-}
-
-// Checks that UniqueActivesObservations with the expected values (i.e.,
-// non-active for all UNIQUE_N_DAY_ACTIVES reports, for all window sizes and
-// event codes) are generated when no Events have been logged to the
-// EventAggregator.
-TEST_F(UniqueActivesNoiseFreeEventAggregatorTest, CheckObservationValuesNoEvents) {
- auto current_day_index = CurrentDayIndex();
- EXPECT_EQ(kOK, GenerateObservations(current_day_index));
- auto expected_obs = MakeNullExpectedUniqueActivesObservations(
- logger::testing::unique_actives_noise_free::kExpectedAggregationParams, current_day_index);
- EXPECT_TRUE(CheckUniqueActivesObservations(expected_obs, observation_store_.get(),
- update_recipient_.get()));
-}
-
-// Checks that UniqueActivesObservations with the expected values are
-// generated when GenerateObservations() is called for a single day index
-// after logging some events for UNIQUE_N_DAY_ACTIVES reports for that day
-// index, without any garbage collection or backfill.
-//
-// Logging pattern:
-// Logs 2 occurrences of event code 0 for the FeaturesActives_UniqueDevices
-// report, and 1 occurrence of event code 1 for the
-// EventsOccurred_UniqueDevices report, all on the same day.
-//
-// Observation generation pattern:
-// Calls GenerateObservations() after logging all events.
-//
-// Expected numbers of Observations:
-// The expected number of Observations is the daily_num_obs field of
-// |logger::testing::unique_actives_noise_free::kExpectedAggregationParams|.
-//
-// Expected Observation values:
-// All Observations should be labeled with the day index on which the events
-// were logged.
-//
-// For the FeaturesActive_UniqueDevices report, expect activity indicators:
-//
-// window size active for event codes
-// ------------------------------------------
-// 1 0
-// 7 0
-// 30 0
-//
-// For the EventsOccurred_UniqueDevices report, expected activity indicators:
-// window size active for event codes
-// ------------------------------------------
-// 1 1
-// 7 1
-//
-// All other Observations should be of inactivity.
-TEST_F(UniqueActivesNoiseFreeEventAggregatorTest, CheckObservationValuesSingleDay) {
- auto day_index = CurrentDayIndex();
- // Log several events on |day_index|.
- EXPECT_EQ(kOK, LogUniqueActivesEvent(
- logger::testing::unique_actives_noise_free::kFeaturesActiveMetricReportId,
- day_index, 0u));
- EXPECT_EQ(kOK, LogUniqueActivesEvent(
- logger::testing::unique_actives_noise_free::kFeaturesActiveMetricReportId,
- day_index, 0u));
- EXPECT_EQ(kOK, LogUniqueActivesEvent(
- logger::testing::unique_actives_noise_free::kEventsOccurredMetricReportId,
- day_index, 1u));
- // Generate locally aggregated Observations for |day_index|.
- EXPECT_EQ(kOK, GenerateObservations(day_index));
-
- // Form the expected observations.
- auto expected_obs = MakeNullExpectedUniqueActivesObservations(
- logger::testing::unique_actives_noise_free::kExpectedAggregationParams, day_index);
- expected_obs[{logger::testing::unique_actives_noise_free::kFeaturesActiveMetricReportId,
- day_index}] = {{1, {true, false, false, false, false}},
- {7, {true, false, false, false, false}},
- {30, {true, false, false, false, false}}};
- expected_obs[{logger::testing::unique_actives_noise_free::kEventsOccurredMetricReportId,
- day_index}] = {{1, {false, true, false, false, false}},
- {7, {false, true, false, false, false}}};
-
- // Check the contents of the FakeObservationStore.
- EXPECT_TRUE(CheckUniqueActivesObservations(expected_obs, observation_store_.get(),
- update_recipient_.get()));
-}
-
-// Checks that UniqueActivesObservations with the expected values are
-// generated when some events have been logged for a UNIQUE_N_DAY_ACTIVES
-// report over multiple days and GenerateObservations() is called each
-// day, without garbage collection or backfill.
-//
-// Logging pattern:
-// Logs events for the EventsOccurred_UniqueDevices report (whose parent
-// metric has max_event_code = 4) for 10 days, according to the following
-// pattern:
-//
-// * Never log event code 0.
-// * On the i-th day (0-indexed) of logging, log an event for event code k,
-// 1 <= k < 5, if 3*k divides i.
-//
-// Observation generation pattern:
-// Each day following the first day, generates Observations for the previous
-// day index.
-//
-// Expected number of Observations:
-// Each call to GenerateObservations should generate a number of Observations
-// equal to the daily_num_obs field of
-// |testing::unique_actives_noise_free::kExpectedAggregationParams|.
-//
-// Expected Observation values:
-// The EventsOccurred_UniqueDevices report has window sizes 1 and 7, and
-// the expected activity indicators of Observations for that report on the
-// i-th day are:
-//
-// (i, window size) active for event codes
-// ------------------------------------------------------
-// (0, 1) 1, 2, 3, 4
-// (0, 7) 1, 2, 3, 4
-// (1, 1) ---
-// (1, 7) 1, 2, 3, 4
-// (2, 1) ---
-// (2, 7) 1, 2, 3, 4
-// (3, 1) 1
-// (3, 7) 1, 2, 3, 4
-// (4, 1) ---
-// (4, 7) 1, 2, 3, 4
-// (5, 1) ---
-// (5, 7) 1, 2, 3, 4
-// (6, 1) 1, 2
-// (6, 7) 1, 2, 3, 4
-// (7, 1) ---
-// (7, 7) 1, 2
-// (8, 1) ---
-// (8, 7) 1, 2
-// (9, 1) 1, 3
-// (9, 7) 1, 2, 3
-//
-// All Observations for all other locally aggregated reports should be
-// observations of non-occurrence.
-TEST_F(UniqueActivesNoiseFreeEventAggregatorTest, CheckObservationValuesMultiDay) {
- auto start_day_index = CurrentDayIndex();
- // Form expected Obsevations for the 10 days of logging.
- uint32_t num_days = 10;
- std::vector<ExpectedUniqueActivesObservations> expected_obs(num_days);
- const auto& expected_id =
- logger::testing::unique_actives_noise_free::kEventsOccurredMetricReportId;
- for (uint32_t offset = 0; offset < num_days; offset++) {
- expected_obs[offset] = MakeNullExpectedUniqueActivesObservations(
- logger::testing::unique_actives_noise_free::kExpectedAggregationParams,
- start_day_index + offset);
- }
- expected_obs[0][{expected_id, start_day_index}] = {{1, {false, true, true, true, true}},
- {7, {false, true, true, true, true}}};
- expected_obs[1][{expected_id, start_day_index + 1}] = {{1, {false, false, false, false, false}},
- {7, {false, true, true, true, true}}};
- expected_obs[2][{expected_id, start_day_index + 2}] = {{1, {false, false, false, false, false}},
- {7, {false, true, true, true, true}}};
- expected_obs[3][{expected_id, start_day_index + 3}] = {{1, {false, true, false, false, false}},
- {7, {false, true, true, true, true}}};
- expected_obs[4][{expected_id, start_day_index + 4}] = {{1, {false, false, false, false, false}},
- {7, {false, true, true, true, true}}};
- expected_obs[5][{expected_id, start_day_index + 5}] = {{1, {false, false, false, false, false}},
- {7, {false, true, true, true, true}}};
- expected_obs[6][{expected_id, start_day_index + 6}] = {{1, {false, true, true, false, false}},
- {7, {false, true, true, true, true}}};
- expected_obs[7][{expected_id, start_day_index + 7}] = {{1, {false, false, false, false, false}},
- {7, {false, true, true, false, false}}};
- expected_obs[8][{expected_id, start_day_index + 8}] = {{1, {false, false, false, false, false}},
- {7, {false, true, true, false, false}}};
- expected_obs[9][{expected_id, start_day_index + 9}] = {{1, {false, true, false, true, false}},
- {7, {false, true, true, true, false}}};
-
- for (uint32_t offset = 0; offset < num_days; offset++) {
- auto day_index = CurrentDayIndex();
- for (uint32_t event_code = 1;
- event_code <
- logger::testing::unique_actives_noise_free::kExpectedAggregationParams.num_event_codes.at(
- expected_id);
- event_code++) {
- if (offset % (3 * event_code) == 0) {
- EXPECT_EQ(kOK, LogUniqueActivesEvent(expected_id, day_index, event_code));
- }
- }
- // Clear the FakeObservationStore.
- ResetObservationStore();
- // Generate locally aggregated Observations.
- EXPECT_EQ(kOK, GenerateObservations(day_index));
- // Check the generated Observations against the expectation.
- EXPECT_TRUE(CheckUniqueActivesObservations(expected_obs[offset], observation_store_.get(),
- update_recipient_.get()));
- AdvanceClock(kDay);
- }
-}
-
-// Checks that UniqueActivesObservations with the expected values are
-// generated when some events have been logged for a UNIQUE_N_DAY_ACTIVES
-// report over multiple days and GenerateObservations() is called each
-// day, and when the LocalAggregateStore is garbage-collected after each call
-// to GenerateObservations().
-//
-// Logging pattern:
-// Logs events for the EventsOccurred_UniqueDevices report (whose parent
-// metric has max_event_code = 4) for 10 days, according to the following
-// pattern:
-//
-// * Never log event code 0.
-// * On the i-th day (0-indexed) of logging, log an event for event code k,
-// 1 <= k < 5, if 3*k divides i.
-//
-// Observation generation pattern:
-// Each day following the first day, generates Observations for the previous
-// day index.
-//
-// Expected number of Observations:
-// Each call to GenerateObservations should generate a number of Observations
-// equal to the daily_num_obs field of
-// |logger::testing::unique_actives_noise_free::kExpectedAggregationParams|.
-//
-// Expected Observation values:
-// The EventsOccurred_UniqueDevices report has window sizes 1 and 7, and
-// the expected activity indicators of Observations for that report on the
-// i-th day are:
-//
-// (i, window size) active for event codes
-// ------------------------------------------------------
-// (0, 1) 1, 2, 3, 4
-// (0, 7) 1, 2, 3, 4
-// (1, 1) ---
-// (1, 7) 1, 2, 3, 4
-// (2, 1) ---
-// (2, 7) 1, 2, 3, 4
-// (3, 1) 1
-// (3, 7) 1, 2, 3, 4
-// (4, 1) ---
-// (4, 7) 1, 2, 3, 4
-// (5, 1) ---
-// (5, 7) 1, 2, 3, 4
-// (6, 1) 1, 2
-// (6, 7) 1, 2, 3, 4
-// (7, 1) ---
-// (7, 7) 1, 2
-// (8, 1) ---
-// (8, 7) 1, 2
-// (9, 1) 1, 3
-// (9, 7) 1, 2, 3
-//
-// All Observations for all other locally aggregated reports should be
-// observations of non-occurrence.
-TEST_F(UniqueActivesNoiseFreeEventAggregatorTest,
- CheckObservationValuesMultiDayWithGarbageCollection) {
- auto start_day_index = CurrentDayIndex();
- // Form expected Observations for the 10 days of logging.
- uint32_t num_days = 10;
- std::vector<ExpectedUniqueActivesObservations> expected_obs(num_days);
- const auto& expected_id =
- logger::testing::unique_actives_noise_free::kEventsOccurredMetricReportId;
-
- for (uint32_t offset = 0; offset < num_days; offset++) {
- expected_obs[offset] = MakeNullExpectedUniqueActivesObservations(
- logger::testing::unique_actives_noise_free::kExpectedAggregationParams,
- start_day_index + offset);
- }
- expected_obs[0][{expected_id, start_day_index}] = {{1, {false, true, true, true, true}},
- {7, {false, true, true, true, true}}};
- expected_obs[1][{expected_id, start_day_index + 1}] = {{1, {false, false, false, false, false}},
- {7, {false, true, true, true, true}}};
- expected_obs[2][{expected_id, start_day_index + 2}] = {{1, {false, false, false, false, false}},
- {7, {false, true, true, true, true}}};
- expected_obs[3][{expected_id, start_day_index + 3}] = {{1, {false, true, false, false, false}},
- {7, {false, true, true, true, true}}};
- expected_obs[4][{expected_id, start_day_index + 4}] = {{1, {false, false, false, false, false}},
- {7, {false, true, true, true, true}}};
- expected_obs[5][{expected_id, start_day_index + 5}] = {{1, {false, false, false, false, false}},
- {7, {false, true, true, true, true}}};
- expected_obs[6][{expected_id, start_day_index + 6}] = {{1, {false, true, true, false, false}},
- {7, {false, true, true, true, true}}};
- expected_obs[7][{expected_id, start_day_index + 7}] = {{1, {false, false, false, false, false}},
- {7, {false, true, true, false, false}}};
- expected_obs[8][{expected_id, start_day_index + 8}] = {{1, {false, false, false, false, false}},
- {7, {false, true, true, false, false}}};
- expected_obs[9][{expected_id, start_day_index + 9}] = {{1, {false, true, false, true, false}},
- {7, {false, true, true, true, false}}};
-
- for (uint32_t offset = 0; offset < num_days; offset++) {
- auto day_index = CurrentDayIndex();
- for (uint32_t event_code = 1;
- event_code <
- logger::testing::unique_actives_noise_free::kExpectedAggregationParams.num_event_codes.at(
- expected_id);
- event_code++) {
- if (offset % (3 * event_code) == 0) {
- EXPECT_EQ(kOK, LogUniqueActivesEvent(expected_id, day_index, event_code));
- }
- }
- // Advance |test_clock_| by 1 day.
- AdvanceClock(kDay);
- // Clear the FakeObservationStore.
- ResetObservationStore();
- // Generate locally aggregated Observations and garbage-collect the
- // LocalAggregateStore, both for the previous day as measured by
- // |test_clock_|. Back up the LocalAggregateStore and
- // AggregatedObservationHistoryStore.
- DoScheduledTasksNow();
- // Check the generated Observations against the expectation.
- EXPECT_TRUE(CheckUniqueActivesObservations(expected_obs[offset], observation_store_.get(),
- update_recipient_.get()));
- }
-}
-
-// Tests that the expected UniqueActivesObservations are generated when events
-// are logged over multiple days and when Observations are backfilled for some
-// days during that period, without any garbage-collection of the
-// LocalAggregateStore.
-//
-// The test sets the number of backfill days to 3.
-//
-// Logging pattern:
-// Events for the EventsOccurred_UniqueDevices report are logged over the days
-// |start_day_index| to |start_day_index + 8| according to the following
-// pattern:
-//
-// * For i = 0 to i = 4, log an event with event code i on day
-// |start_day_index + i| and |start_day_index + 2*i|.
-//
-// Observation generation pattern:
-// The test calls GenerateObservations() on day |start_day_index + i| for i =
-// 0 through i = 5 and for i = 8, skipping the days |start_day_index + 6| and
-// |start_day_index + 7|.
-//
-// Expected numbers of Observations:
-// It is expected that 4 days' worth of Observations are generated on the
-// first day (the day index for which GenerateObservations() was called, plus
-// 3 days of backfill), that 1 day's worth of Observations is generated on the
-// 2nd through 6th day, that 3 days' worth of Observations are generated on
-// the 9th day (the day index for which GenerateObservations() was called,
-// plus 2 days of backfill), and that no Observations are generated on the
-// remaining days.
-//
-// Expected Observation values:
-// The expected activity indicators of Observations for the
-// EventsOccurred_UniqueDevices report for the i-th day of logging are:
-//
-// (i, window size) active for event codes
-// -------------------------------------------------------------------------
-// (0, 1) 0
-// (0, 7) 0
-// (1, 1) 1
-// (1, 7) 0, 1
-// (2, 1) 1, 2
-// (2, 7) 0, 1, 2
-// (3, 1) 3
-// (3, 7) 0, 1, 2, 3
-// (4, 1) 2, 4
-// (4, 7) 0, 1, 2, 3, 4
-// (5, 1) ---
-// (5, 7) 0, 1, 2, 3, 4
-// (6, 1) 3
-// (6, 7) 0, 1, 2, 3, 4
-// (7, 1) ---
-// (7, 7) 1, 2, 3, 4
-// (8, 1) 4
-// (8, 7) 1, 2, 3, 4
-//
-// All other Observations should be of non-activity.
-TEST_F(UniqueActivesNoiseFreeEventAggregatorTest, CheckObservationValuesWithBackfill) {
- auto start_day_index = CurrentDayIndex();
- // Set |backfill_days_| to 3.
- size_t backfill_days = 3;
- SetBackfillDays(backfill_days);
- const auto& expected_id =
- logger::testing::unique_actives_noise_free::kEventsOccurredMetricReportId;
- const auto& expected_params =
- logger::testing::unique_actives_noise_free::kExpectedAggregationParams;
- // Log events for 9 days. Call GenerateObservations() on the first 6 day
- // indices, and the 9th.
- for (uint32_t offset = 0; offset < 9; offset++) {
- auto day_index = CurrentDayIndex();
- ResetObservationStore();
- for (uint32_t event_code = 0; event_code < expected_params.num_event_codes.at(expected_id);
- event_code++) {
- if (event_code == offset || (2 * event_code) == offset) {
- EXPECT_EQ(kOK, LogUniqueActivesEvent(expected_id, day_index, event_code));
- }
- }
- if (offset < 6 || offset == 8) {
- EXPECT_EQ(kOK, GenerateObservations(day_index));
- }
- // Make the set of Observations which are expected to be generated on
- // |start_day_index + offset| and check it against the contents of the
- // FakeObservationStore.
- ExpectedUniqueActivesObservations expected_obs;
- switch (offset) {
- case 0: {
- for (uint32_t day_index = start_day_index - backfill_days; day_index <= start_day_index;
- day_index++) {
- for (const auto& pair :
- MakeNullExpectedUniqueActivesObservations(expected_params, day_index)) {
- expected_obs.insert(pair);
- }
- }
- expected_obs[{expected_id, start_day_index}] = {{1, {true, false, false, false, false}},
- {7, {true, false, false, false, false}}};
- EXPECT_TRUE(CheckUniqueActivesObservations(expected_obs, observation_store_.get(),
- update_recipient_.get()));
- break;
- }
- case 1: {
- expected_obs =
- MakeNullExpectedUniqueActivesObservations(expected_params, start_day_index + 1);
- expected_obs[{expected_id, start_day_index + 1}] = {{1, {false, true, false, false, false}},
- {7, {true, true, false, false, false}}};
- EXPECT_TRUE(CheckUniqueActivesObservations(expected_obs, observation_store_.get(),
- update_recipient_.get()));
- break;
- }
- case 2: {
- expected_obs =
- MakeNullExpectedUniqueActivesObservations(expected_params, start_day_index + 2);
- expected_obs[{expected_id, start_day_index + 2}] = {{1, {false, true, true, false, false}},
- {7, {true, true, true, false, false}}};
- EXPECT_TRUE(CheckUniqueActivesObservations(expected_obs, observation_store_.get(),
- update_recipient_.get()));
- break;
- }
- case 3: {
- expected_obs =
- MakeNullExpectedUniqueActivesObservations(expected_params, start_day_index + 3);
- expected_obs[{expected_id, start_day_index + 3}] = {{1, {false, false, false, true, false}},
- {7, {true, true, true, true, false}}};
- EXPECT_TRUE(CheckUniqueActivesObservations(expected_obs, observation_store_.get(),
- update_recipient_.get()));
- break;
- }
- case 4: {
- expected_obs =
- MakeNullExpectedUniqueActivesObservations(expected_params, start_day_index + 4);
- expected_obs[{expected_id, start_day_index + 4}] = {{1, {false, false, true, false, true}},
- {7, {true, true, true, true, true}}};
- EXPECT_TRUE(CheckUniqueActivesObservations(expected_obs, observation_store_.get(),
- update_recipient_.get()));
- break;
- }
- case 5: {
- expected_obs =
- MakeNullExpectedUniqueActivesObservations(expected_params, start_day_index + 5);
- expected_obs[{expected_id, start_day_index + 5}] = {
- {1, {false, false, false, false, false}}, {7, {true, true, true, true, true}}};
- EXPECT_TRUE(CheckUniqueActivesObservations(expected_obs, observation_store_.get(),
- update_recipient_.get()));
- break;
- }
- case 8: {
- for (uint32_t day_index = start_day_index + 6; day_index <= start_day_index + 8;
- day_index++) {
- for (const auto& pair :
- MakeNullExpectedUniqueActivesObservations(expected_params, day_index)) {
- expected_obs.insert(pair);
- }
- }
- expected_obs[{expected_id, start_day_index + 6}] = {{1, {false, false, false, true, false}},
- {7, {true, true, true, true, true}}};
- expected_obs[{expected_id, start_day_index + 7}] = {
- {1, {false, false, false, false, false}}, {7, {false, true, true, true, true}}};
- expected_obs[{expected_id, start_day_index + 8}] = {{1, {false, false, false, false, true}},
- {7, {false, true, true, true, true}}};
- EXPECT_TRUE(CheckUniqueActivesObservations(expected_obs, observation_store_.get(),
- update_recipient_.get()));
- break;
- }
- default:
- EXPECT_TRUE(CheckUniqueActivesObservations(expected_obs, observation_store_.get(),
- update_recipient_.get()));
- }
- AdvanceClock(kDay);
- }
-}
-
-// Tests that the expected UniqueActivesObservations are generated when events
-// are logged over multiple days and when Observations are backfilled for some
-// days during that period, and when the LocalAggregateStore is
-// garbage-collected after each call to GenerateObservations().
-//
-// The test sets the number of backfill days to 3.
-//
-// Logging pattern:
-// Events for the EventsOccurred_UniqueDevices report are logged over the days
-// |start_day_index| to |start_day_index + 8| according to the following
-// pattern:
-//
-// * For i = 0 to i = 4, log an event with event code i on day
-// |start_day_index + i| and |start_day_index + 2*i|.
-//
-// Observation generation pattern:
-// The test calls GenerateObservations() on day |start_day_index + i| for i =
-// 0 through i = 5 and for i = 8, skipping the days |start_day_index + 6| and
-// |start_day_index + 7|.
-//
-// Expected numbers of Observations:
-// It is expected that 4 days' worth of Observations are generated on the
-// first day (the day index for which GenerateObservations() was called, plus
-// 3 days of backfill), that 1 day's worth of Observations is generated on the
-// 2nd through 6th day, that 3 days' worth of Observations are generated on
-// the 9th day (the day index for which GenerateObservations() was called,
-// plus 2 days of backfill), and that no Observations are generated on the
-// remaining days.
-//
-// Expected Observation values:
-// The expected activity indicators of Observations for the
-// EventsOccurred_UniqueDevices report for the i-th day of logging are:
-//
-// (i, window size) active for event codes
-// -------------------------------------------------------------------------
-// (0, 1) 0
-// (0, 7) 0
-// (1, 1) 1
-// (1, 7) 0, 1
-// (2, 1) 1, 2
-// (2, 7) 0, 1, 2
-// (3, 1) 3
-// (3, 7) 0, 1, 2, 3
-// (4, 1) 2, 4
-// (4, 7) 0, 1, 2, 3, 4
-// (5, 1) ---
-// (5, 7) 0, 1, 2, 3, 4
-// (6, 1) 3
-// (6, 7) 0, 1, 2, 3, 4
-// (7, 1) ---
-// (7, 7) 1, 2, 3, 4
-// (8, 1) 4
-// (8, 7) 1, 2, 3, 4
-//
-// All other Observations should be of non-activity.
-TEST_F(UniqueActivesNoiseFreeEventAggregatorTest, CheckObservationValuesWithBackfillAndGc) {
- auto start_day_index = CurrentDayIndex();
- // Set |backfill_days_| to 3.
- size_t backfill_days = 3;
- SetBackfillDays(backfill_days);
-
- const auto& expected_id =
- logger::testing::unique_actives_noise_free::kEventsOccurredMetricReportId;
- const auto& expected_params =
- logger::testing::unique_actives_noise_free::kExpectedAggregationParams;
-
- // Log events for 9 days. Call GenerateObservations() on the first 6 day
- // indices, and the 9th.
- for (uint32_t offset = 0; offset < 8; offset++) {
- auto day_index = CurrentDayIndex();
- ResetObservationStore();
- for (uint32_t event_code = 0; event_code < expected_params.num_event_codes.at(expected_id);
- event_code++) {
- if (event_code == offset || (2 * event_code) == offset) {
- EXPECT_EQ(kOK, LogUniqueActivesEvent(expected_id, day_index, event_code));
- }
- }
- // Advance |test_clock_| by 1 day.
- AdvanceClock(kDay);
- if (offset < 6 || offset == 9) {
- // Generate Observations and garbage-collect, both for the previous day
- // index according to |test_clock_|. Back up the LocalAggregateStore and
- // the AggregatedObservationHistoryStore.
- DoScheduledTasksNow();
- }
- // Make the set of Observations which are expected to be generated on
- // |start_day_index + offset| and check it against the contents of the
- // FakeObservationStore.
- ExpectedUniqueActivesObservations expected_obs;
- switch (offset) {
- case 0: {
- for (uint32_t day_index = start_day_index - backfill_days; day_index <= start_day_index;
- day_index++) {
- for (const auto& pair :
- MakeNullExpectedUniqueActivesObservations(expected_params, day_index)) {
- expected_obs.insert(pair);
- }
- }
- expected_obs[{expected_id, start_day_index}] = {{1, {true, false, false, false, false}},
- {7, {true, false, false, false, false}}};
- EXPECT_TRUE(CheckUniqueActivesObservations(expected_obs, observation_store_.get(),
- update_recipient_.get()));
- break;
- }
- case 1: {
- expected_obs =
- MakeNullExpectedUniqueActivesObservations(expected_params, start_day_index + 1);
- expected_obs[{expected_id, start_day_index + 1}] = {{1, {false, true, false, false, false}},
- {7, {true, true, false, false, false}}};
- EXPECT_TRUE(CheckUniqueActivesObservations(expected_obs, observation_store_.get(),
- update_recipient_.get()));
- break;
- }
- case 2: {
- expected_obs =
- MakeNullExpectedUniqueActivesObservations(expected_params, start_day_index + 2);
- expected_obs[{expected_id, start_day_index + 2}] = {{1, {false, true, true, false, false}},
- {7, {true, true, true, false, false}}};
- EXPECT_TRUE(CheckUniqueActivesObservations(expected_obs, observation_store_.get(),
- update_recipient_.get()));
- break;
- }
- case 3: {
- expected_obs =
- MakeNullExpectedUniqueActivesObservations(expected_params, start_day_index + 3);
- expected_obs[{expected_id, start_day_index + 3}] = {{1, {false, false, false, true, false}},
- {7, {true, true, true, true, false}}};
- EXPECT_TRUE(CheckUniqueActivesObservations(expected_obs, observation_store_.get(),
- update_recipient_.get()));
- break;
- }
- case 4: {
- expected_obs =
- MakeNullExpectedUniqueActivesObservations(expected_params, start_day_index + 4);
- expected_obs[{expected_id, start_day_index + 4}] = {{1, {false, false, true, false, true}},
- {7, {true, true, true, true, true}}};
- EXPECT_TRUE(CheckUniqueActivesObservations(expected_obs, observation_store_.get(),
- update_recipient_.get()));
- break;
- }
- case 5: {
- expected_obs =
- MakeNullExpectedUniqueActivesObservations(expected_params, start_day_index + 5);
- expected_obs[{expected_id, start_day_index + 5}] = {
- {1, {false, false, false, false, false}}, {7, {true, true, true, true, true}}};
- EXPECT_TRUE(CheckUniqueActivesObservations(expected_obs, observation_store_.get(),
- update_recipient_.get()));
- break;
- }
- case 8: {
- for (uint32_t day_index = start_day_index + 6; day_index <= start_day_index + 8;
- day_index++) {
- for (const auto& pair :
- MakeNullExpectedUniqueActivesObservations(expected_params, day_index)) {
- expected_obs.insert(pair);
- }
- }
- expected_obs[{expected_id, start_day_index + 6}] = {{1, {false, false, false, true, false}},
- {7, {true, true, true, true, true}}};
- expected_obs[{expected_id, start_day_index + 7}] = {
- {1, {false, false, false, false, false}}, {7, {false, true, true, true, true}}};
- expected_obs[{expected_id, start_day_index + 8}] = {{1, {false, false, false, false, true}},
- {7, {false, true, true, true, true}}};
- EXPECT_TRUE(CheckUniqueActivesObservations(expected_obs, observation_store_.get(),
- update_recipient_.get()));
- break;
- }
- default:
- EXPECT_TRUE(CheckUniqueActivesObservations(expected_obs, observation_store_.get(),
- update_recipient_.get()));
- }
- }
-}
-
// Checks that UniqueActivesObservations with the expected values are
// generated by the the scheduled Observation generation when some events
// have been logged for a UNIQUE_N_DAY_ACTIVES.
@@ -2287,1260 +1125,6 @@
EXPECT_TRUE(CheckPerDeviceNumericAggregates(logged_values, day_index));
AdvanceClock(kDay);
}
-} // namespace local_aggregation
-
-// Tests GarbageCollect() for PerDeviceNumericReportAggregates.
-//
-// For each value of N in the range [0, 34], logs some events for
-// PerDeviceNumeric reports each day for N consecutive days, and then
-// garbage-collects the LocalAggregateStore. After garbage collection, verifies
-// the contents of the LocalAggregateStore.
-TEST_F(PerDeviceNumericEventAggregatorTest, GarbageCollect) {
- uint32_t max_days_before_gc = 35;
- for (uint32_t days_before_gc = 0; days_before_gc < max_days_before_gc; days_before_gc++) {
- SetUp();
- day_last_garbage_collected_ = 0u;
- LoggedValues logged_values;
- std::vector<MetricReportId> count_metric_report_ids = {
- logger::testing::per_device_numeric_stats::kSettingsChangedWindowSizeMetricReportId,
- logger::testing::per_device_numeric_stats::kSettingsChangedAggregationWindowMetricReportId,
- logger::testing::per_device_numeric_stats::kConnectionFailuresMetricReportId};
- std::vector<MetricReportId> elapsed_time_metric_report_ids = {
- logger::testing::per_device_numeric_stats::kStreamingTimeTotalMetricReportId,
- logger::testing::per_device_numeric_stats::kStreamingTimeMinMetricReportId,
- logger::testing::per_device_numeric_stats::kStreamingTimeMaxMetricReportId};
- MetricReportId frame_rate_metric_report_id =
- logger::testing::per_device_numeric_stats::kLoginModuleFrameRateMinMetricReportId;
- MetricReportId memory_usage_metric_report_id =
- logger::testing::per_device_numeric_stats::kLedgerMemoryUsageMaxMetricReportId;
- for (uint32_t offset = 0; offset < days_before_gc; offset++) {
- auto day_index = CurrentDayIndex();
- for (const auto& id : count_metric_report_ids) {
- for (const auto& component : {"component_A", "component_B", "component_C"}) {
- // Log 2 events with event code 0, for each component A, B, C.
- EXPECT_EQ(kOK, LogPerDeviceCountEvent(id, day_index, component, 0u, 2, &logged_values));
- EXPECT_EQ(kOK, LogPerDeviceCountEvent(id, day_index, component, 0u, 3, &logged_values));
- }
- if (offset < 3) {
- // Log 1 event for component D and event code 1.
- EXPECT_EQ(kOK,
- LogPerDeviceCountEvent(id, day_index, "component_D", 1u, 4, &logged_values));
- }
- }
- for (const auto& id : elapsed_time_metric_report_ids) {
- for (const auto& component : {"component_A", "component_B", "component_C"}) {
- // Log 2 events with event code 0, for each component A, B, C.
- EXPECT_EQ(kOK,
- LogPerDeviceElapsedTimeEvent(id, day_index, component, 0u, 2, &logged_values));
- EXPECT_EQ(kOK,
- LogPerDeviceElapsedTimeEvent(id, day_index, component, 0u, 3, &logged_values));
- }
- if (offset < 3) {
- // Log 1 event for component D and event code 1.
- EXPECT_EQ(kOK, LogPerDeviceElapsedTimeEvent(id, day_index, "component_D", 1u, 4,
- &logged_values));
- }
- }
- for (const auto& component : {"component_A", "component_B"}) {
- EXPECT_EQ(kOK, LogPerDeviceFrameRateEvent(frame_rate_metric_report_id, day_index, component,
- 0u, 2.25, &logged_values));
- EXPECT_EQ(kOK, LogPerDeviceFrameRateEvent(frame_rate_metric_report_id, day_index, component,
- 0u, 1.75, &logged_values));
- EXPECT_EQ(kOK,
- LogPerDeviceMemoryUsageEvent(memory_usage_metric_report_id, day_index, component,
- std::vector<uint32_t>{0u, 0u}, 300, &logged_values));
- EXPECT_EQ(kOK,
- LogPerDeviceMemoryUsageEvent(memory_usage_metric_report_id, day_index, component,
- std::vector<uint32_t>{1u, 0u}, 300, &logged_values));
- }
- AdvanceClock(kDay);
- }
- auto end_day_index = CurrentDayIndex();
- EXPECT_EQ(kOK, GarbageCollect(end_day_index));
- day_last_garbage_collected_ = end_day_index;
- EXPECT_TRUE(CheckPerDeviceNumericAggregates(logged_values, end_day_index));
- TearDown();
- }
-}
-
-// Tests that EventAggregator::GenerateObservations() returns a positive
-// status and that the expected number of Observations is generated after
-// some CountEvents have been logged for PerDeviceNumericStats reports, without
-// any garbage collection.
-//
-// For 35 days, logs a positive number of events each day for the
-// ConnectionFailures_PerDeviceNumericStats report with "component_A" and for
-// the SettingsChanged_PerDeviceNumericStats reports with "component_B", all with
-// event code 0.
-//
-// Each day, calls GenerateObservations() with the day index of the previous
-// day. Checks that a positive status is returned and that the
-// FakeObservationStore has received the expected number of new observations
-// for each locally aggregated report ID in the per_device_numeric_stats test
-// registry.
-TEST_F(PerDeviceNumericEventAggregatorTest, GenerateObservations) {
- int num_days = 1;
- std::vector<Observation2> observations(0);
- ExpectedAggregationParams expected_params =
- logger::testing::per_device_numeric_stats::kExpectedAggregationParams;
- for (int offset = 0; offset < num_days; offset++) {
- auto day_index = CurrentDayIndex();
- observations.clear();
- ResetObservationStore();
- EXPECT_EQ(kOK, GenerateObservations(day_index - 1));
- EXPECT_TRUE(FetchAggregatedObservations(&observations, expected_params,
- observation_store_.get(), update_recipient_.get()));
- for (int i = 0; i < 2; i++) {
- EXPECT_EQ(kOK,
- LogPerDeviceCountEvent(
- logger::testing::per_device_numeric_stats::kConnectionFailuresMetricReportId,
- day_index, "component_A", 0u, 1));
- EXPECT_EQ(kOK,
- LogPerDeviceCountEvent(
- logger::testing::per_device_numeric_stats::kConnectionFailuresMetricReportId,
- day_index, "component_A", 0u, 1));
- EXPECT_EQ(
- kOK,
- LogPerDeviceCountEvent(
- logger::testing::per_device_numeric_stats::kSettingsChangedWindowSizeMetricReportId,
- day_index, "component_B", 0u, 5));
- EXPECT_EQ(kOK, LogPerDeviceCountEvent(logger::testing::per_device_numeric_stats::
- kSettingsChangedAggregationWindowMetricReportId,
- day_index, "component_B", 0u, 5));
- }
- // If this is the first time we're logging events, update the expected
- // numbers of generated Observations to account for the logged events.
- // For each report, for each aggregation window, expect 1 Observation more than if
- // no events had been logged.
- if (offset == 0) {
- expected_params.daily_num_obs += 5;
- expected_params.num_obs_per_report
- [logger::testing::per_device_numeric_stats::kConnectionFailuresMetricReportId] += 1;
- expected_params.num_obs_per_report
- [logger::testing::per_device_numeric_stats::kSettingsChangedWindowSizeMetricReportId] +=
- 2;
- expected_params.num_obs_per_report[logger::testing::per_device_numeric_stats::
- kSettingsChangedAggregationWindowMetricReportId] += 2;
- }
- AdvanceClock(kDay);
- }
- observations.clear();
- ResetObservationStore();
- EXPECT_EQ(kOK, GenerateObservations(CurrentDayIndex() - 1));
- EXPECT_TRUE(FetchAggregatedObservations(&observations, expected_params, observation_store_.get(),
- update_recipient_.get()));
-}
-
-// Tests that EventAggregator::GenerateObservations() returns a positive
-// status and that the expected number of Observations is generated after
-// some CountEvents have been logged for PerDeviceNumeric reports over multiple
-// days, and when the LocalAggregateStore is garbage-collected each day.
-//
-// For 35 days, logs a positive number of events each day for the
-// ConnectionFailures_PerDeviceNumeric report with "component_A" and for
-// the SettingsChanged_PerDeviceNumeric report with "component_B", all with
-// event code 0.
-//
-// Each day, calls GenerateObservations() with the day index of the previous
-// day. Checks that a positive status is returned and that the
-// FakeObservationStore has received the expected number of new observations
-// for each locally aggregated report ID in the per_device_numeric_stats test
-// registry.
-TEST_F(PerDeviceNumericEventAggregatorTest, GenerateObservationsWithGc) {
- int num_days = 35;
- std::vector<Observation2> observations(0);
- ExpectedAggregationParams expected_params =
- logger::testing::per_device_numeric_stats::kExpectedAggregationParams;
- for (int offset = 0; offset < num_days; offset++) {
- auto day_index = CurrentDayIndex();
- observations.clear();
- ResetObservationStore();
- EXPECT_EQ(kOK, GenerateObservations(day_index - 1));
- EXPECT_TRUE(FetchAggregatedObservations(&observations, expected_params,
- observation_store_.get(), update_recipient_.get()));
- EXPECT_EQ(kOK, GarbageCollect(day_index));
- for (int i = 0; i < 2; i++) {
- EXPECT_EQ(kOK,
- LogPerDeviceCountEvent(
- logger::testing::per_device_numeric_stats::kConnectionFailuresMetricReportId,
- day_index, "component_A", 0u, 1));
- EXPECT_EQ(kOK,
- LogPerDeviceCountEvent(
- logger::testing::per_device_numeric_stats::kConnectionFailuresMetricReportId,
- day_index, "component_A", 0u, 1));
- EXPECT_EQ(
- kOK,
- LogPerDeviceCountEvent(
- logger::testing::per_device_numeric_stats::kSettingsChangedWindowSizeMetricReportId,
- day_index, "component_B", 0u, 5));
- EXPECT_EQ(kOK, LogPerDeviceCountEvent(logger::testing::per_device_numeric_stats::
- kSettingsChangedAggregationWindowMetricReportId,
- day_index, "component_B", 0u, 5));
- }
- // If this is the first time we're logging events, update the expected
- // numbers of generated Observations to account for the logged events.
- // For each report, for each window size, expect 1 Observation more than if
- // no events had been logged.
- if (offset == 0) {
- expected_params.daily_num_obs += 5;
- expected_params.num_obs_per_report
- [logger::testing::per_device_numeric_stats::kConnectionFailuresMetricReportId] += 1;
- expected_params.num_obs_per_report
- [logger::testing::per_device_numeric_stats::kSettingsChangedWindowSizeMetricReportId] +=
- 2;
- expected_params.num_obs_per_report[logger::testing::per_device_numeric_stats::
- kSettingsChangedAggregationWindowMetricReportId] += 2;
- }
- AdvanceClock(kDay);
- }
- observations.clear();
- ResetObservationStore();
- auto day_index = CurrentDayIndex();
- EXPECT_EQ(kOK, GenerateObservations(day_index - 1));
- EXPECT_TRUE(FetchAggregatedObservations(&observations, expected_params, observation_store_.get(),
- update_recipient_.get()));
- EXPECT_EQ(kOK, GarbageCollect(day_index));
-}
-
-// Tests that GenerateObservations() returns a positive status and that the
-// expected number of Observations is generated when events are logged over
-// multiple days and some of those days' Observations are backfilled, without
-// any garbage collection of the LocalAggregateStore.
-//
-// Sets the |backfill_days_| field of the EventAggregator to 3.
-//
-// Logging pattern:
-// For 35 days, logs 2 events each day for the
-// ConnectionFailures_PerDeviceCount report and 2 events for the
-// SettingsChanged_PerDeviceCount report, all with event code 0.
-//
-// Observation generation pattern:
-// Calls GenerateObservations() on the 1st through 5th and the 7th out of
-// every 10 days, for 35 days.
-//
-// Expected numbers of Observations:
-// It is expected that 4 days' worth of Observations are generated on
-// the first day of every 10 (the day index for which GenerateObservations()
-// was called, plus 3 days of backfill), that 1 day's worth of Observations
-// are generated on the 2nd through 5th day of every 10, that 2 days'
-// worth of Observations are generated on the 7th day of every 10 (the
-// day index for which GenerateObservations() was called, plus 1 day of
-// backfill), and that no Observations are generated on the remaining days.
-TEST_F(PerDeviceNumericEventAggregatorTest, GenerateObservationsWithBackfill) {
- const auto& expected_params =
- logger::testing::per_device_numeric_stats::kExpectedAggregationParams;
- // Set |backfill_days_| to 3.
- size_t backfill_days = 3;
- SetBackfillDays(backfill_days);
- // Log 2 events each day for 35 days. Call GenerateObservations() on the
- // first 5 day indices, and the 7th, out of every 10.
- for (int offset = 0; offset < 35; offset++) {
- auto day_index = CurrentDayIndex();
- for (int i = 0; i < 2; i++) {
- EXPECT_EQ(kOK,
- LogPerDeviceCountEvent(
- logger::testing::per_device_numeric_stats::kConnectionFailuresMetricReportId,
- day_index, "component_A", 0u, 1));
- EXPECT_EQ(kOK,
- LogPerDeviceCountEvent(
- logger::testing::per_device_numeric_stats::kConnectionFailuresMetricReportId,
- day_index, "component_A", 0u, 1));
- EXPECT_EQ(
- kOK,
- LogPerDeviceCountEvent(
- logger::testing::per_device_numeric_stats::kSettingsChangedWindowSizeMetricReportId,
- day_index, "component_B", 0u, 5));
- EXPECT_EQ(kOK, LogPerDeviceCountEvent(logger::testing::per_device_numeric_stats::
- kSettingsChangedAggregationWindowMetricReportId,
- day_index, "component_B", 0u, 5));
- }
- auto num_obs_before = observation_store_->messages_received.size();
- if (offset % 10 < 5 || offset % 10 == 6) {
- EXPECT_EQ(kOK, GenerateObservations(day_index));
- }
- auto num_obs_after = observation_store_->messages_received.size();
- EXPECT_GE(num_obs_after, num_obs_before);
- // Check that the expected daily number of Observations was generated.
- switch (offset % 10) {
- case 0:
- // If this is the first day of logging, expect 3 Observations for each
- // day in the backfill period and 8 Observations for the current day.
- if (offset == 0) {
- EXPECT_EQ(
- (expected_params.daily_num_obs * backfill_days) + expected_params.daily_num_obs + 5,
- num_obs_after - num_obs_before);
- } else {
- // If this is another day whose offset is a multiple of 10, expect 8
- // Observations for each day in the backfill period as well as the
- // current day.
- EXPECT_EQ((expected_params.daily_num_obs + 5) * (backfill_days + 1),
- num_obs_after - num_obs_before);
- }
- break;
- case 1:
- case 2:
- case 3:
- case 4:
- // Expect 8 Observations for this day.
- EXPECT_EQ(expected_params.daily_num_obs + 5, num_obs_after - num_obs_before);
- break;
- case 6:
- // Expect 8 Observations for each of today and yesterday.
- EXPECT_EQ((expected_params.daily_num_obs + 5) * 2, num_obs_after - num_obs_before);
- break;
- default:
- EXPECT_EQ(num_obs_after, num_obs_before);
- }
- AdvanceClock(kDay);
- }
-}
-
-// Tests that GenerateObservations() returns a positive status and that the
-// expected number of Observations is generated when events are logged over
-// multiple days and some of those days' Observations are backfilled, and when
-// the LocalAggregateStore is garbage-collected after each call to
-// GenerateObservations().
-//
-// Sets the |backfill_days_| field of the EventAggregator to 3.
-//
-// Logging pattern:
-// For 35 days, logs 2 events each day for the
-// ConnectionFailures_PerDeviceNumeric report with "component_A" and 2 events
-// for the SettingsChanged_PerDeviceNumeric reports with "component_B", all with
-// event code 0.
-//
-// Observation generation pattern:
-// Calls GenerateObservations() on the 1st through 5th and the 7th out of
-// every 10 days, for 35 days. Garbage-collects the LocalAggregateStore after
-// each call.
-//
-// Expected numbers of Observations:
-// It is expected that 4 days' worth of Observations are generated on
-// the first day of every 10 (the day index for which GenerateObservations()
-// was called, plus 3 days of backfill), that 1 day's worth of Observations
-// are generated on the 2nd through 5th day of every 10, that 2 days'
-// worth of Observations are generated on the 7th day of every 10 (the
-// day index for which GenerateObservations() was called, plus 1 day of
-// backfill), and that no Observations are generated on the remaining days.
-TEST_F(PerDeviceNumericEventAggregatorTest, GenerateObservationsWithBackfillAndGc) {
- int num_days = 35;
- const auto& expected_params =
- logger::testing::per_device_numeric_stats::kExpectedAggregationParams;
- // Set |backfill_days_| to 3.
- size_t backfill_days = 3;
- SetBackfillDays(backfill_days);
- // Log 2 events each day for 35 days. Call GenerateObservations() on the
- // first 5 day indices, and the 7th, out of every 10.
- for (int offset = 0; offset < num_days; offset++) {
- auto day_index = CurrentDayIndex();
- for (int i = 0; i < 2; i++) {
- EXPECT_EQ(kOK,
- LogPerDeviceCountEvent(
- logger::testing::per_device_numeric_stats::kConnectionFailuresMetricReportId,
- day_index, "component_A", 0u, 1));
- EXPECT_EQ(
- kOK,
- LogPerDeviceCountEvent(
- logger::testing::per_device_numeric_stats::kSettingsChangedWindowSizeMetricReportId,
- day_index, "component_B", 0u, 5));
- EXPECT_EQ(kOK, LogPerDeviceCountEvent(logger::testing::per_device_numeric_stats::
- kSettingsChangedAggregationWindowMetricReportId,
- day_index, "component_B", 0u, 5));
- }
- auto num_obs_before = observation_store_->messages_received.size();
- if (offset % 10 < 5 || offset % 10 == 6) {
- EXPECT_EQ(kOK, GenerateObservations(day_index));
- EXPECT_EQ(kOK, GarbageCollect(day_index));
- }
- auto num_obs_after = observation_store_->messages_received.size();
- EXPECT_GE(num_obs_after, num_obs_before);
- // Check that the expected daily number of Observations was generated.
- switch (offset % 10) {
- case 0:
- // If this is the first day of logging, expect 3 Observations for each
- // day in the backfill period and 8 Observations for the current day.
- if (offset == 0) {
- EXPECT_EQ(
- (expected_params.daily_num_obs * backfill_days) + expected_params.daily_num_obs + 5,
- num_obs_after - num_obs_before);
- } else {
- // If this is another day whose offset is a multiple of 10, expect 8
- // Observations for each day in the backfill period as well as the
- // current day.
- EXPECT_EQ((expected_params.daily_num_obs + 5) * (backfill_days + 1),
- num_obs_after - num_obs_before);
- }
- break;
- case 1:
- case 2:
- case 3:
- case 4:
- // Expect 8 Observations for this day.
- EXPECT_EQ(expected_params.daily_num_obs + 5, num_obs_after - num_obs_before);
- break;
- case 6:
- // Expect 6 Observations for each of today and yesterday.
- EXPECT_EQ((expected_params.daily_num_obs + 5) * 2, num_obs_after - num_obs_before);
- break;
- default:
- EXPECT_EQ(num_obs_after, num_obs_before);
- }
- AdvanceClock(kDay);
- }
-}
-
-// Generate Observations without logging any events, and check that the
-// resulting Observations are as expected: 1 ReportParticipationObservation for
-// each PER_DEVICE_NUMERIC_STATS report in the config, and no
-// PerDeviceNumericObservations.
-TEST_F(PerDeviceNumericEventAggregatorTest, CheckObservationValuesNoEvents) {
- const auto current_day_index = CurrentDayIndex();
- EXPECT_EQ(kOK, GenerateObservations(current_day_index));
- const auto& expected_report_participation_obs = MakeExpectedReportParticipationObservations(
- logger::testing::per_device_numeric_stats::kExpectedAggregationParams, current_day_index);
- EXPECT_TRUE(CheckPerDeviceNumericObservations({}, expected_report_participation_obs,
- observation_store_.get(), update_recipient_.get()));
-}
-
-// Check that the expected PerDeviceNumericObservations and
-// ReportParticipationObservations are generated when GenerateObservations() is
-// called after logging some CountEvents and ElapsedTimeEvents for
-// PER_DEVICE_NUMERIC_STATS reports over a single day index.
-TEST_F(PerDeviceNumericEventAggregatorTest, CheckObservationValuesSingleDay) {
- const auto day_index = CurrentDayIndex();
- // Log several events on |day_index|.
- EXPECT_EQ(kOK, LogPerDeviceCountEvent(
- logger::testing::per_device_numeric_stats::kConnectionFailuresMetricReportId,
- day_index, "component_A", 0u, 5));
- EXPECT_EQ(kOK, LogPerDeviceCountEvent(
- logger::testing::per_device_numeric_stats::kConnectionFailuresMetricReportId,
- day_index, "component_B", 0u, 5));
- EXPECT_EQ(kOK, LogPerDeviceCountEvent(
- logger::testing::per_device_numeric_stats::kConnectionFailuresMetricReportId,
- day_index, "component_A", 0u, 5));
- EXPECT_EQ(kOK, LogPerDeviceCountEvent(
- logger::testing::per_device_numeric_stats::kConnectionFailuresMetricReportId,
- day_index, "component_A", 1u, 5));
- EXPECT_EQ(kOK,
- LogPerDeviceCountEvent(
- logger::testing::per_device_numeric_stats::kSettingsChangedWindowSizeMetricReportId,
- day_index, "component_C", 0u, 5));
- EXPECT_EQ(kOK,
- LogPerDeviceCountEvent(
- logger::testing::per_device_numeric_stats::kSettingsChangedWindowSizeMetricReportId,
- day_index, "component_C", 0u, 5));
- EXPECT_EQ(kOK, LogPerDeviceCountEvent(logger::testing::per_device_numeric_stats::
- kSettingsChangedAggregationWindowMetricReportId,
- day_index, "component_C", 0u, 5));
- EXPECT_EQ(kOK, LogPerDeviceCountEvent(logger::testing::per_device_numeric_stats::
- kSettingsChangedAggregationWindowMetricReportId,
- day_index, "component_C", 0u, 5));
-
- std::vector<MetricReportId> streaming_time_ids = {
- logger::testing::per_device_numeric_stats::kStreamingTimeTotalMetricReportId,
- logger::testing::per_device_numeric_stats::kStreamingTimeMinMetricReportId,
- logger::testing::per_device_numeric_stats::kStreamingTimeMaxMetricReportId};
- for (const auto& id : streaming_time_ids) {
- EXPECT_EQ(kOK, LogPerDeviceElapsedTimeEvent(id, day_index, "component_D", 0u, 15));
- EXPECT_EQ(kOK, LogPerDeviceElapsedTimeEvent(id, day_index, "component_D", 1u, 5));
- EXPECT_EQ(kOK, LogPerDeviceElapsedTimeEvent(id, day_index, "component_D", 0u, 10));
- }
- // Generate locally aggregated Observations for |day_index|.
- EXPECT_EQ(kOK, GenerateObservations(day_index));
-
- // Form the expected Observations.
- auto expected_report_participation_obs = MakeExpectedReportParticipationObservations(
- logger::testing::per_device_numeric_stats::kExpectedAggregationParams, day_index);
- ExpectedPerDeviceNumericObservations expected_per_device_numeric_obs;
- expected_per_device_numeric_obs[{
- logger::testing::per_device_numeric_stats::kConnectionFailuresMetricReportId, day_index}][1] =
- {{"component_A", 0u, 10}, {"component_A", 1u, 5}, {"component_B", 0u, 5}};
- expected_per_device_numeric_obs[{
- logger::testing::per_device_numeric_stats::kSettingsChangedWindowSizeMetricReportId,
- day_index}][7] = {{"component_C", 0u, 10}};
- expected_per_device_numeric_obs[{
- logger::testing::per_device_numeric_stats::kSettingsChangedWindowSizeMetricReportId,
- day_index}][30] = {{"component_C", 0u, 10}};
- expected_per_device_numeric_obs[{
- logger::testing::per_device_numeric_stats::kSettingsChangedAggregationWindowMetricReportId,
- day_index}][7] = {{"component_C", 0u, 10}};
- expected_per_device_numeric_obs[{
- logger::testing::per_device_numeric_stats::kSettingsChangedAggregationWindowMetricReportId,
- day_index}][30] = {{"component_C", 0u, 10}};
- expected_per_device_numeric_obs[{
- logger::testing::per_device_numeric_stats::kStreamingTimeTotalMetricReportId, day_index}][1] =
- {{"component_D", 0u, 25}, {"component_D", 1u, 5}};
- expected_per_device_numeric_obs[{
- logger::testing::per_device_numeric_stats::kStreamingTimeTotalMetricReportId, day_index}][7] =
- {{"component_D", 0u, 25}, {"component_D", 1u, 5}};
- // The 7-day minimum value for the StreamingTime metric is 0 for all event
- // codes and components, so we don't expect a PerDeviceNumericObservation with
- // a 7-day window for the StreamingTime_PerDeviceMin report.
- expected_per_device_numeric_obs[{
- logger::testing::per_device_numeric_stats::kStreamingTimeMinMetricReportId, day_index}][1] = {
- {"component_D", 0u, 10}, {"component_D", 1u, 5}};
- expected_per_device_numeric_obs[{
- logger::testing::per_device_numeric_stats::kStreamingTimeMinMetricReportId, day_index}][7] = {
- {"component_D", 0u, 10}, {"component_D", 1u, 5}};
- expected_per_device_numeric_obs[{
- logger::testing::per_device_numeric_stats::kStreamingTimeMaxMetricReportId, day_index}][1] = {
- {"component_D", 0u, 15}, {"component_D", 1u, 5}};
- expected_per_device_numeric_obs[{
- logger::testing::per_device_numeric_stats::kStreamingTimeMaxMetricReportId, day_index}][7] = {
- {"component_D", 0u, 15}, {"component_D", 1u, 5}};
-
- EXPECT_TRUE(CheckPerDeviceNumericObservations(expected_per_device_numeric_obs,
- expected_report_participation_obs,
- observation_store_.get(), update_recipient_.get()));
-}
-
-// Checks that PerDeviceNumericObservations with the expected values are
-// generated when some events have been logged for an EVENT_COUNT metric with
-// a PER_DEVICE_NUMERIC_STATS report over multiple days and
-// GenerateObservations() is called each day, without garbage collection or
-// backfill.
-//
-// Logged events for the SettingsChanged_PerDeviceCount report on the i-th
-// day:
-//
-// i (component, event code, count)
-// -----------------------------------------------------------------------
-// 0
-// 1 ("A", 1, 3)
-// 2 ("A", 1, 3), ("A", 2, 3), ("B", 1, 2)
-// 3 ("A", 1, 3)
-// 4 ("A", 1, 3), ("A", 2, 3), ("B", 1, 2), ("B", 2, 2)
-// 5 ("A", 1, 3)
-// 6 ("A", 1, 3), ("A", 2, 3), ("B", 1, 2)
-// 7 ("A", 1, 3)
-// 8 ("A", 1, 3), ("A", 2, 3), ("B", 1, 2), ("B", 2, 2)
-// 9 ("A", 1, 3)
-//
-// Expected PerDeviceNumericObservations for the
-// SettingsChanged_PerDeviceNumeric report on the i-th day:
-//
-// (i, window size) (component, event code, count)
-// -----------------------------------------------------------------------
-// (0, 7)
-// (0, 30)
-// (1, 7) ("A", 1, 3)
-// (1, 30) ("A", 1, 3)
-// (2, 7) ("A", 1, 6), ("A", 2, 3), ("B", 1, 2)
-// (2, 30) ("A", 1, 6), ("A", 2, 3), ("B", 1, 2)
-// (3, 7) ("A", 1, 9), ("A", 2, 3), ("B", 1, 2)
-// (3, 30) ("A", 1, 9), ("A", 2, 3), ("B", 1, 2)
-// (4, 7) ("A", 1, 12), ("A", 2, 6), ("B", 1, 4), ("B", 2, 2)
-// (4, 30) ("A", 1, 12), ("A", 2, 6), ("B", 1, 4), ("B", 2, 2)
-// (5, 7) ("A", 1, 15), ("A", 2, 6), ("B", 1, 4), ("B", 2, 2)
-// (5, 30) ("A", 1, 15), ("A", 2, 6), ("B", 1, 4), ("B", 2, 2)
-// (6, 7) ("A", 1, 18), ("A", 2, 9), ("B", 1, 6), ("B", 2, 2)
-// (6, 30) ("A", 1, 18), ("A", 2, 9), ("B", 1, 6), ("B", 2, 2)
-// (7, 7) ("A", 1, 21), ("A", 2, 9), ("B", 1, 6), ("B", 2, 2)
-// (7, 30) ("A", 1, 21), ("A", 2, 9), ("B", 1, 6), ("B", 2, 2)
-// (8, 7) ("A", 1, 21), ("A", 2, 12), ("B", 1, 8), ("B", 2, 4)
-// (8, 30) ("A", 1, 24), ("A", 2, 12), ("B", 1, 8), ("B", 2, 4)
-// (9, 7) ("A", 1, 21), ("A", 2, 9), ("B", 1, 6), ("B", 2, 4)
-// (9, 30) ("A", 1, 27), ("A", 2, 12), ("B", 1, 8), ("B", 2, 4)
-//
-// In addition, expect 1 ReportParticipationObservation each day for each of
-// the reports in the registry.
-TEST_F(PerDeviceNumericEventAggregatorTest, CheckObservationValuesMultiDay) {
- auto start_day_index = CurrentDayIndex();
- const auto& expected_id =
- logger::testing::per_device_numeric_stats::kSettingsChangedAggregationWindowMetricReportId;
- const auto& expected_params =
- logger::testing::per_device_numeric_stats::kExpectedAggregationParams;
- // Form expected Observations for the 10 days of logging.
- uint32_t num_days = 10;
- std::vector<ExpectedPerDeviceNumericObservations> expected_per_device_numeric_obs(num_days);
- std::vector<ExpectedReportParticipationObservations> expected_report_participation_obs(num_days);
- for (uint32_t offset = 0; offset < num_days; offset++) {
- expected_report_participation_obs[offset] =
- MakeExpectedReportParticipationObservations(expected_params, start_day_index + offset);
- }
- expected_per_device_numeric_obs[0] = {};
- expected_per_device_numeric_obs[1][{expected_id, start_day_index + 1}] = {{7, {{"A", 1u, 3}}},
- {30, {{"A", 1u, 3}}}};
- expected_per_device_numeric_obs[2][{expected_id, start_day_index + 2}] = {
- {7, {{"A", 1u, 6}, {"A", 2u, 3}, {"B", 1u, 2}}},
- {30, {{"A", 1u, 6}, {"A", 2u, 3}, {"B", 1u, 2}}}};
- expected_per_device_numeric_obs[3][{expected_id, start_day_index + 3}] = {
- {7, {{"A", 1u, 9}, {"A", 2u, 3}, {"B", 1u, 2}}},
- {30, {{"A", 1u, 9}, {"A", 2u, 3}, {"B", 1u, 2}}}};
- expected_per_device_numeric_obs[4][{expected_id, start_day_index + 4}] = {
- {7, {{"A", 1u, 12}, {"A", 2u, 6}, {"B", 1u, 4}, {"B", 2u, 2}}},
- {30, {{"A", 1u, 12}, {"A", 2u, 6}, {"B", 1u, 4}, {"B", 2u, 2}}}};
- expected_per_device_numeric_obs[5][{expected_id, start_day_index + 5}] = {
- {7, {{"A", 1u, 15}, {"A", 2u, 6}, {"B", 1u, 4}, {"B", 2u, 2}}},
- {30, {{"A", 1u, 15}, {"A", 2u, 6}, {"B", 1u, 4}, {"B", 2u, 2}}}};
- expected_per_device_numeric_obs[6][{expected_id, start_day_index + 6}] = {
- {7, {{"A", 1u, 18}, {"A", 2u, 9}, {"B", 1u, 6}, {"B", 2u, 2}}},
- {30, {{"A", 1u, 18}, {"A", 2u, 9}, {"B", 1u, 6}, {"B", 2u, 2}}}};
- expected_per_device_numeric_obs[7][{expected_id, start_day_index + 7}] = {
- {7, {{"A", 1u, 21}, {"A", 2u, 9}, {"B", 1u, 6}, {"B", 2u, 2}}},
- {30, {{"A", 1u, 21}, {"A", 2u, 9}, {"B", 1u, 6}, {"B", 2u, 2}}}};
- expected_per_device_numeric_obs[8][{expected_id, start_day_index + 8}] = {
- {7, {{"A", 1u, 21}, {"A", 2u, 12}, {"B", 1u, 8}, {"B", 2u, 4}}},
- {30, {{"A", 1u, 24}, {"A", 2u, 12}, {"B", 1u, 8}, {"B", 2u, 4}}}};
- expected_per_device_numeric_obs[9][{expected_id, start_day_index + 9}] = {
- {7, {{"A", 1u, 21}, {"A", 2u, 9}, {"B", 1u, 6}, {"B", 2u, 4}}},
- {30, {{"A", 1u, 27}, {"A", 2u, 12}, {"B", 1u, 8}, {"B", 2u, 4}}}};
-
- for (uint32_t offset = 0; offset < 1; offset++) {
- auto day_index = CurrentDayIndex();
- for (uint32_t event_code = 1; event_code < 3; event_code++) {
- if (offset > 0 && offset % event_code == 0) {
- EXPECT_EQ(kOK, LogPerDeviceCountEvent(expected_id, day_index, "A", event_code, 3));
- }
- if (offset > 0 && offset % (2 * event_code) == 0) {
- EXPECT_EQ(kOK, LogPerDeviceCountEvent(expected_id, day_index, "B", event_code, 2));
- }
- }
- // Clear the FakeObservationStore.
- ResetObservationStore();
- // Generate locally aggregated Observations.
- EXPECT_EQ(kOK, GenerateObservations(day_index));
- EXPECT_TRUE(CheckPerDeviceNumericObservations(
- expected_per_device_numeric_obs[offset], expected_report_participation_obs[offset],
- observation_store_.get(), update_recipient_.get()))
- << "offset = " << offset;
- AdvanceClock(kDay);
- }
-}
-
-// Repeat the CheckObservationValuesMultiDay test, this time calling
-// GarbageCollect() after each call to GenerateObservations.
-//
-// The logging pattern and set of Observations for each day index is the same
-// as in PerDeviceNumericEventAggregatorTest::CheckObservationValuesMultiDay.
-// See that test for documentation.
-TEST_F(PerDeviceNumericEventAggregatorTest, CheckObservationValuesMultiDayWithGarbageCollection) {
- auto start_day_index = CurrentDayIndex();
- const auto& expected_id =
- logger::testing::per_device_numeric_stats::kSettingsChangedAggregationWindowMetricReportId;
- const auto& expected_params =
- logger::testing::per_device_numeric_stats::kExpectedAggregationParams;
- // Form expected Observations for the 10 days of logging.
- uint32_t num_days = 10;
- std::vector<ExpectedPerDeviceNumericObservations> expected_per_device_numeric_obs(num_days);
- std::vector<ExpectedReportParticipationObservations> expected_report_participation_obs(num_days);
- for (uint32_t offset = 0; offset < num_days; offset++) {
- expected_report_participation_obs[offset] =
- MakeExpectedReportParticipationObservations(expected_params, start_day_index + offset);
- }
- expected_per_device_numeric_obs[0] = {};
- expected_per_device_numeric_obs[1][{expected_id, start_day_index + 1}] = {{7, {{"A", 1u, 3}}},
- {30, {{"A", 1u, 3}}}};
- expected_per_device_numeric_obs[2][{expected_id, start_day_index + 2}] = {
- {7, {{"A", 1u, 6}, {"A", 2u, 3}, {"B", 1u, 2}}},
- {30, {{"A", 1u, 6}, {"A", 2u, 3}, {"B", 1u, 2}}}};
- expected_per_device_numeric_obs[3][{expected_id, start_day_index + 3}] = {
- {7, {{"A", 1u, 9}, {"A", 2u, 3}, {"B", 1u, 2}}},
- {30, {{"A", 1u, 9}, {"A", 2u, 3}, {"B", 1u, 2}}}};
- expected_per_device_numeric_obs[4][{expected_id, start_day_index + 4}] = {
- {7, {{"A", 1u, 12}, {"A", 2u, 6}, {"B", 1u, 4}, {"B", 2u, 2}}},
- {30, {{"A", 1u, 12}, {"A", 2u, 6}, {"B", 1u, 4}, {"B", 2u, 2}}}};
- expected_per_device_numeric_obs[5][{expected_id, start_day_index + 5}] = {
- {7, {{"A", 1u, 15}, {"A", 2u, 6}, {"B", 1u, 4}, {"B", 2u, 2}}},
- {30, {{"A", 1u, 15}, {"A", 2u, 6}, {"B", 1u, 4}, {"B", 2u, 2}}}};
- expected_per_device_numeric_obs[6][{expected_id, start_day_index + 6}] = {
- {7, {{"A", 1u, 18}, {"A", 2u, 9}, {"B", 1u, 6}, {"B", 2u, 2}}},
- {30, {{"A", 1u, 18}, {"A", 2u, 9}, {"B", 1u, 6}, {"B", 2u, 2}}}};
- expected_per_device_numeric_obs[7][{expected_id, start_day_index + 7}] = {
- {7, {{"A", 1u, 21}, {"A", 2u, 9}, {"B", 1u, 6}, {"B", 2u, 2}}},
- {30, {{"A", 1u, 21}, {"A", 2u, 9}, {"B", 1u, 6}, {"B", 2u, 2}}}};
- expected_per_device_numeric_obs[8][{expected_id, start_day_index + 8}] = {
- {7, {{"A", 1u, 21}, {"A", 2u, 12}, {"B", 1u, 8}, {"B", 2u, 4}}},
- {30, {{"A", 1u, 24}, {"A", 2u, 12}, {"B", 1u, 8}, {"B", 2u, 4}}}};
- expected_per_device_numeric_obs[9][{expected_id, start_day_index + 9}] = {
- {7, {{"A", 1u, 21}, {"A", 2u, 9}, {"B", 1u, 6}, {"B", 2u, 4}}},
- {30, {{"A", 1u, 27}, {"A", 2u, 12}, {"B", 1u, 8}, {"B", 2u, 4}}}};
-
- for (uint32_t offset = 0; offset < 10; offset++) {
- auto day_index = CurrentDayIndex();
- for (uint32_t event_code = 1; event_code < 3; event_code++) {
- if (offset > 0 && offset % event_code == 0) {
- EXPECT_EQ(kOK, LogPerDeviceCountEvent(expected_id, day_index, "A", event_code, 3));
- }
- if (offset > 0 && offset % (2 * event_code) == 0) {
- EXPECT_EQ(kOK, LogPerDeviceCountEvent(expected_id, day_index, "B", event_code, 2));
- }
- }
- // Advance |test_clock_| by 1 day.
- AdvanceClock(kDay);
- // Clear the FakeObservationStore.
- ResetObservationStore();
- // Generate locally aggregated Observations and garbage-collect the
- // LocalAggregateStore, both for the previous day as measured by
- // |test_clock_|. Back up the LocalAggregateStore and
- // AggregatedObservationHistoryStore.
- DoScheduledTasksNow();
- EXPECT_TRUE(CheckPerDeviceNumericObservations(
- expected_per_device_numeric_obs[offset], expected_report_participation_obs[offset],
- observation_store_.get(), update_recipient_.get()));
- }
-}
-
-// Tests that the expected PerDeviceNumericObservations are generated when
-// events are logged over multiple days for an EVENT_COUNT
-// metric with a PER_DEVICE_NUMERIC_STATS report, when Observations are
-// backfilled for some days during that period, without any garbage-collection
-// of the LocalAggregateStore.
-//
-// The logging pattern and set of Observations for each day index is the same
-// as in PerDeviceNumericEventAggregatorTest::CheckObservationValuesMultiDay.
-// See that test for documentation.
-TEST_F(PerDeviceNumericEventAggregatorTest, CheckObservationValuesWithBackfill) {
- auto start_day_index = CurrentDayIndex();
- const auto& expected_id =
- logger::testing::per_device_numeric_stats::kSettingsChangedAggregationWindowMetricReportId;
- const auto& expected_params =
- logger::testing::per_device_numeric_stats::kExpectedAggregationParams;
- // Set |backfill_days_| to 3.
- size_t backfill_days = 3;
- SetBackfillDays(backfill_days);
- // Log events for 9 days. Call GenerateObservations() on the first 6 day
- // indices, and the 9th.
- uint32_t num_days = 9;
- for (uint32_t offset = 0; offset < num_days; offset++) {
- auto day_index = CurrentDayIndex();
- ResetObservationStore();
- for (uint32_t event_code = 1; event_code < 3; event_code++) {
- if (offset > 0 && (offset % event_code == 0)) {
- EXPECT_EQ(kOK, LogPerDeviceCountEvent(expected_id, day_index, "A", event_code, 3));
- }
- if (offset > 0 && offset % (2 * event_code) == 0) {
- EXPECT_EQ(kOK, LogPerDeviceCountEvent(expected_id, day_index, "B", event_code, 2));
- }
- }
- if (offset < 6 || offset == 8) {
- EXPECT_EQ(kOK, GenerateObservations(day_index));
- }
- // Make the set of Observations which are expected to be generated on
- // |start_day_index + offset| and check it against the contents of the
- // FakeObservationStore.
- ExpectedPerDeviceNumericObservations expected_per_device_numeric_obs;
- ExpectedReportParticipationObservations expected_report_participation_obs;
- switch (offset) {
- case 0: {
- for (uint32_t day_index = start_day_index - backfill_days; day_index <= start_day_index;
- day_index++) {
- for (const auto& pair :
- MakeExpectedReportParticipationObservations(expected_params, day_index)) {
- expected_report_participation_obs.insert(pair);
- }
- }
- EXPECT_TRUE(CheckPerDeviceNumericObservations(
- expected_per_device_numeric_obs, expected_report_participation_obs,
- observation_store_.get(), update_recipient_.get()));
- break;
- }
- case 1: {
- expected_per_device_numeric_obs[{expected_id, day_index}] = {{7, {{"A", 1u, 3}}},
- {30, {{"A", 1u, 3}}}};
- expected_report_participation_obs =
- MakeExpectedReportParticipationObservations(expected_params, day_index);
- EXPECT_TRUE(CheckPerDeviceNumericObservations(
- expected_per_device_numeric_obs, expected_report_participation_obs,
- observation_store_.get(), update_recipient_.get()));
- break;
- }
- case 2: {
- expected_per_device_numeric_obs[{expected_id, day_index}] = {
- {7, {{"A", 1u, 6}, {"A", 2u, 3}, {"B", 1u, 2}}},
- {30, {{"A", 1u, 6}, {"A", 2u, 3}, {"B", 1u, 2}}}};
- expected_report_participation_obs =
- MakeExpectedReportParticipationObservations(expected_params, day_index);
- EXPECT_TRUE(CheckPerDeviceNumericObservations(
- expected_per_device_numeric_obs, expected_report_participation_obs,
- observation_store_.get(), update_recipient_.get()));
- break;
- }
- case 3: {
- expected_per_device_numeric_obs[{expected_id, day_index}] = {
- {7, {{"A", 1u, 9}, {"A", 2u, 3}, {"B", 1u, 2}}},
- {30, {{"A", 1u, 9}, {"A", 2u, 3}, {"B", 1u, 2}}}};
- expected_report_participation_obs =
- MakeExpectedReportParticipationObservations(expected_params, day_index);
- EXPECT_TRUE(CheckPerDeviceNumericObservations(
- expected_per_device_numeric_obs, expected_report_participation_obs,
- observation_store_.get(), update_recipient_.get()));
- break;
- }
- case 4: {
- expected_per_device_numeric_obs[{expected_id, day_index}] = {
- {7, {{"A", 1u, 12}, {"A", 2u, 6}, {"B", 1u, 4}, {"B", 2u, 2}}},
- {30, {{"A", 1u, 12}, {"A", 2u, 6}, {"B", 1u, 4}, {"B", 2u, 2}}}};
- expected_report_participation_obs =
- MakeExpectedReportParticipationObservations(expected_params, day_index);
- EXPECT_TRUE(CheckPerDeviceNumericObservations(
- expected_per_device_numeric_obs, expected_report_participation_obs,
- observation_store_.get(), update_recipient_.get()));
- break;
- }
- case 5: {
- expected_per_device_numeric_obs[{expected_id, day_index}] = {
- {7, {{"A", 1u, 15}, {"A", 2u, 6}, {"B", 1u, 4}, {"B", 2u, 2}}},
- {30, {{"A", 1u, 15}, {"A", 2u, 6}, {"B", 1u, 4}, {"B", 2u, 2}}}};
- expected_report_participation_obs =
- MakeExpectedReportParticipationObservations(expected_params, day_index);
- EXPECT_TRUE(CheckPerDeviceNumericObservations(
- expected_per_device_numeric_obs, expected_report_participation_obs,
- observation_store_.get(), update_recipient_.get()));
- break;
- }
- case 8: {
- expected_per_device_numeric_obs[{expected_id, start_day_index + 6}] = {
- {7, {{"A", 1u, 18}, {"A", 2u, 9}, {"B", 1u, 6}, {"B", 2u, 2}}},
- {30, {{"A", 1u, 18}, {"A", 2u, 9}, {"B", 1u, 6}, {"B", 2u, 2}}}};
- expected_per_device_numeric_obs[{expected_id, start_day_index + 7}] = {
- {7, {{"A", 1u, 21}, {"A", 2u, 9}, {"B", 1u, 6}, {"B", 2u, 2}}},
- {30, {{"A", 1u, 21}, {"A", 2u, 9}, {"B", 1u, 6}, {"B", 2u, 2}}}};
- expected_per_device_numeric_obs[{expected_id, start_day_index + 8}] = {
- {7, {{"A", 1u, 21}, {"A", 2u, 12}, {"B", 1u, 8}, {"B", 2u, 4}}},
- {30, {{"A", 1u, 24}, {"A", 2u, 12}, {"B", 1u, 8}, {"B", 2u, 4}}}};
- for (uint32_t day_index = start_day_index + 6; day_index <= start_day_index + 8;
- day_index++) {
- for (const auto& pair :
- MakeExpectedReportParticipationObservations(expected_params, day_index)) {
- expected_report_participation_obs.insert(pair);
- }
- }
- EXPECT_TRUE(CheckPerDeviceNumericObservations(
- expected_per_device_numeric_obs, expected_report_participation_obs,
- observation_store_.get(), update_recipient_.get()));
- break;
- }
- default:
- EXPECT_TRUE(CheckPerDeviceNumericObservations(
- expected_per_device_numeric_obs, expected_report_participation_obs,
- observation_store_.get(), update_recipient_.get()));
- }
- AdvanceClock(kDay);
- }
-}
-
-// Tests that the expected Observations are generated for
-// PerDeviceNumericStats reports when events are logged for over multiple days
-// for an EVENT_COUNT metric with a PER_DEVICE_NUMERIC_STATS report, when
-// Observations are backfilled for some days during that period, and when the
-// LocalAggregatedStore is garbage-collected after each call to
-// GenerateObservations().
-//
-// The logging pattern and set of Observations for each day index is the same
-// as in PerDeviceNumericEventAggregatorTest::CheckObservationValuesMultiDay.
-// See that test for documentation.
-TEST_F(PerDeviceNumericEventAggregatorTest, EventCountCheckObservationValuesWithBackfillAndGc) {
- auto start_day_index = CurrentDayIndex();
- const auto& expected_id =
- logger::testing::per_device_numeric_stats::kSettingsChangedAggregationWindowMetricReportId;
- const auto& expected_params =
- logger::testing::per_device_numeric_stats::kExpectedAggregationParams;
- // Set |backfill_days_| to 3.
- size_t backfill_days = 3;
- SetBackfillDays(backfill_days);
- // Log events for 9 days. Call GenerateObservations() on the first 6 day
- // indices, and the 9th.
- uint32_t num_days = 9;
- for (uint32_t offset = 0; offset < num_days; offset++) {
- auto day_index = CurrentDayIndex();
- ResetObservationStore();
- for (uint32_t event_code = 1; event_code < 3; event_code++) {
- if (offset > 0 && (offset % event_code == 0)) {
- EXPECT_EQ(kOK, LogPerDeviceCountEvent(expected_id, day_index, "A", event_code, 3));
- }
- if (offset > 0 && offset % (2 * event_code) == 0) {
- EXPECT_EQ(kOK, LogPerDeviceCountEvent(expected_id, day_index, "B", event_code, 2));
- }
- }
- // Advance |test_clock_| by 1 day.
- AdvanceClock(kDay);
- if (offset < 6 || offset == 8) {
- // Generate Observations and garbage-collect, both for the previous day
- // index according to |test_clock_|. Back up the LocalAggregateStore and
- // the AggregatedObservationHistoryStore.
- DoScheduledTasksNow();
- }
- // Make the set of Observations which are expected to be generated on
- // |start_day_index + offset| and check it against the contents of the
- // FakeObservationStore.
- ExpectedPerDeviceNumericObservations expected_per_device_numeric_obs;
- ExpectedReportParticipationObservations expected_report_participation_obs;
- switch (offset) {
- case 0: {
- for (uint32_t day_index = start_day_index - backfill_days; day_index <= start_day_index;
- day_index++) {
- for (const auto& pair :
- MakeExpectedReportParticipationObservations(expected_params, day_index)) {
- expected_report_participation_obs.insert(pair);
- }
- }
- EXPECT_TRUE(CheckPerDeviceNumericObservations(
- expected_per_device_numeric_obs, expected_report_participation_obs,
- observation_store_.get(), update_recipient_.get()));
- break;
- }
- case 1: {
- expected_per_device_numeric_obs[{expected_id, day_index}] = {{7, {{"A", 1u, 3}}},
- {30, {{"A", 1u, 3}}}};
- expected_report_participation_obs =
- MakeExpectedReportParticipationObservations(expected_params, day_index);
- EXPECT_TRUE(CheckPerDeviceNumericObservations(
- expected_per_device_numeric_obs, expected_report_participation_obs,
- observation_store_.get(), update_recipient_.get()));
- break;
- }
- case 2: {
- expected_per_device_numeric_obs[{expected_id, day_index}] = {
- {7, {{"A", 1u, 6}, {"A", 2u, 3}, {"B", 1u, 2}}},
- {30, {{"A", 1u, 6}, {"A", 2u, 3}, {"B", 1u, 2}}}};
- expected_report_participation_obs =
- MakeExpectedReportParticipationObservations(expected_params, day_index);
- EXPECT_TRUE(CheckPerDeviceNumericObservations(
- expected_per_device_numeric_obs, expected_report_participation_obs,
- observation_store_.get(), update_recipient_.get()));
- break;
- }
- case 3: {
- expected_per_device_numeric_obs[{expected_id, day_index}] = {
- {7, {{"A", 1u, 9}, {"A", 2u, 3}, {"B", 1u, 2}}},
- {30, {{"A", 1u, 9}, {"A", 2u, 3}, {"B", 1u, 2}}}};
- expected_report_participation_obs =
- MakeExpectedReportParticipationObservations(expected_params, day_index);
- EXPECT_TRUE(CheckPerDeviceNumericObservations(
- expected_per_device_numeric_obs, expected_report_participation_obs,
- observation_store_.get(), update_recipient_.get()));
- break;
- }
- case 4: {
- expected_per_device_numeric_obs[{expected_id, day_index}] = {
- {7, {{"A", 1u, 12}, {"A", 2u, 6}, {"B", 1u, 4}, {"B", 2u, 2}}},
- {30, {{"A", 1u, 12}, {"A", 2u, 6}, {"B", 1u, 4}, {"B", 2u, 2}}}};
- expected_report_participation_obs =
- MakeExpectedReportParticipationObservations(expected_params, day_index);
- EXPECT_TRUE(CheckPerDeviceNumericObservations(
- expected_per_device_numeric_obs, expected_report_participation_obs,
- observation_store_.get(), update_recipient_.get()));
- break;
- }
- case 5: {
- expected_per_device_numeric_obs[{expected_id, day_index}] = {
- {7, {{"A", 1u, 15}, {"A", 2u, 6}, {"B", 1u, 4}, {"B", 2u, 2}}},
- {30, {{"A", 1u, 15}, {"A", 2u, 6}, {"B", 1u, 4}, {"B", 2u, 2}}}};
- expected_report_participation_obs =
- MakeExpectedReportParticipationObservations(expected_params, day_index);
- EXPECT_TRUE(CheckPerDeviceNumericObservations(
- expected_per_device_numeric_obs, expected_report_participation_obs,
- observation_store_.get(), update_recipient_.get()));
- break;
- }
- case 8: {
- expected_per_device_numeric_obs[{expected_id, start_day_index + 6}] = {
- {7, {{"A", 1u, 18}, {"A", 2u, 9}, {"B", 1u, 6}, {"B", 2u, 2}}},
- {30, {{"A", 1u, 18}, {"A", 2u, 9}, {"B", 1u, 6}, {"B", 2u, 2}}}};
- expected_per_device_numeric_obs[{expected_id, start_day_index + 7}] = {
- {7, {{"A", 1u, 21}, {"A", 2u, 9}, {"B", 1u, 6}, {"B", 2u, 2}}},
- {30, {{"A", 1u, 21}, {"A", 2u, 9}, {"B", 1u, 6}, {"B", 2u, 2}}}};
- expected_per_device_numeric_obs[{expected_id, start_day_index + 8}] = {
- {7, {{"A", 1u, 21}, {"A", 2u, 12}, {"B", 1u, 8}, {"B", 2u, 4}}},
- {30, {{"A", 1u, 24}, {"A", 2u, 12}, {"B", 1u, 8}, {"B", 2u, 4}}}};
- for (uint32_t day_index = start_day_index + 6; day_index <= start_day_index + 8;
- day_index++) {
- for (const auto& pair :
- MakeExpectedReportParticipationObservations(expected_params, day_index)) {
- expected_report_participation_obs.insert(pair);
- }
- }
- EXPECT_TRUE(CheckPerDeviceNumericObservations(
- expected_per_device_numeric_obs, expected_report_participation_obs,
- observation_store_.get(), update_recipient_.get()));
- break;
- }
- default:
- EXPECT_TRUE(CheckPerDeviceNumericObservations(
- expected_per_device_numeric_obs, expected_report_participation_obs,
- observation_store_.get(), update_recipient_.get()));
- }
- }
-}
-
-// Tests that the expected Observations are generated for
-// PerDeviceNumericStats reports when events are logged for over multiple days
-// for an ELAPSED_TIME metric with PER_DEVICE_NUMERIC_STATS reports with
-// multiple aggregation types, when Observations are backfilled for some days
-// during that period, and when the LocalAggregatedStore is garbage-collected
-// after each call to GenerateObservations().
-//
-// Logged events for the StreamingTime_PerDevice{Total, Min, Max} reports on the
-// i-th day:
-//
-// i (component, event code, count)
-// -----------------------------------------------------------------------
-// 0
-// 1 ("A", 1, 3)
-// 2 ("A", 1, 3), ("A", 2, 3), ("B", 1, 2)
-// 3 ("A", 1, 3)
-// 4 ("A", 1, 3), ("A", 2, 3), ("B", 1, 2), ("B", 2, 2)
-// 5 ("A", 1, 3)
-// 6 ("A", 1, 3), ("A", 2, 3), ("B", 1, 2)
-// 7 ("A", 1, 3)
-// 8 ("A", 1, 3), ("A", 2, 3), ("B", 1, 2), ("B", 2, 2)
-//
-// Expected PerDeviceNumericObservations for the
-// StreamingTime_PerDeviceTotal report on the i-th day:
-//
-// (day, window size) (event code, component, total)
-// ---------------------------------------------------------------------------
-// (0, 1)
-// (0, 7)
-// (1, 1) ("A", 1, 3)
-// (1, 7) ("A", 1, 3)
-// (2, 1) ("A", 1, 3), ("A", 2, 3), ("B", 1, 2)
-// (2, 7) ("A", 1, 6), ("A", 2, 3), ("B", 1, 2)
-// (3, 1) ("A", 1, 3)
-// (3, 7) ("A", 1, 9), ("A", 2, 3), ("B", 1, 2)
-// (4, 1) ("A", 1, 3), ("A", 2, 3), ("B", 1, 2), ("B", 2, 2)
-// (4, 7) ("A", 1, 12), ("A", 2, 6), ("B", 1, 4), ("B", 2, 2)
-// (5, 1) ("A", 1, 3)
-// (5, 7) ("A", 1, 15), ("A", 2, 6), ("B", 1, 4), ("B", 2, 2)
-// (6, 1) ("A", 1, 3), ("A", 2, 3), ("B", 1, 2)
-// (6, 7) ("A", 1, 18), ("A", 2, 9), ("B", 1, 6), ("B", 2, 2)
-// (7, 1) ("A", 1, 3)
-// (7, 7) ("A", 1, 21), ("A", 2, 9), ("B", 1, 6), ("B", 2, 2)
-// (8, 1) ("A", 1, 3), ("A", 2, 3), ("B", 1, 2), ("B", 2, 2)
-// (8, 7) ("A", 1, 21), ("A", 2, 12), ("B", 1, 8), ("B", 2, 4)
-//
-// Expected PerDeviceNumericObservations for the
-// StreamingTime_PerDeviceMin report on the i-th day:
-//
-// (day, window size) (event code, component, total)
-// ---------------------------------------------------------------------------
-// (0, 1)
-// (0. 7)
-// (1, 1) ("A", 1, 3)
-// (1, 7) ("A", 1, 3)
-// (2, 1) ("A", 1, 3), ("A", 2, 3), ("B", 1, 2)
-// (2, 7) ("A", 1, 3), ("A", 2, 3), ("B", 1, 2)
-// (3, 1) ("A", 1, 3)
-// (3, 7) ("A", 1, 3), ("A", 2, 3), ("B", 1, 2)
-// (4, 1) ("A", 1, 3), ("A", 2, 3), ("B", 1, 2), ("B", 2, 2)
-// (4, 7) ("A", 1, 3), ("A", 2, 3), ("B", 1, 2), ("B", 2, 2)
-// (5, 1) ("A", 1, 3)
-// (5, 7) ("A", 1, 3), ("A", 2, 3), ("B", 1, 2), ("B", 2, 2)
-// (6, 1) ("A", 1, 3), ("A", 2, 3), ("B", 1, 2)
-// (6, 7) ("A", 1, 3), ("A", 2, 3), ("B", 1, 2), ("B", 2, 2)
-// (7, 1) ("A", 1, 3)
-// (7, 7) ("A", 1, 3), ("A", 2, 3), ("B", 1, 2), ("B", 2, 2)
-// (8, 1) ("A", 1, 3), ("A", 2, 3), ("B", 1, 2), ("B", 2, 2)
-// (8, 7) ("A", 1, 3), ("A", 2, 3), ("B", 1, 2), ("B", 2, 2)
-//
-// Expected PerDeviceNumericObservations for the
-// StreamingTime_PerDeviceMax report on the i-th day:
-//
-// (day, window size) (event code, component, total)
-// ---------------------------------------------------------------------------
-// (0, 1)
-// (0. 7)
-// (1, 1) ("A", 1, 3)
-// (1, 7) ("A", 1, 3)
-// (2, 1) ("A", 1, 3), ("A", 2, 3), ("B", 1, 2)
-// (2, 7) ("A", 1, 3), ("A", 2, 3), ("B", 1, 2)
-// (3, 1) ("A", 1, 3)
-// (3, 7) ("A", 1, 3), ("A", 2, 3), ("B", 1, 2)
-// (4, 1) ("A", 1, 3), ("A", 2, 3), ("B", 1, 2), ("B", 2, 2)
-// (4, 7) ("A", 1, 3), ("A", 2, 3), ("B", 1, 2), ("B", 2, 2)
-// (5, 1) ("A", 1, 3)
-// (5, 7) ("A", 1, 3), ("A", 2, 3), ("B", 1, 2), ("B", 2, 2)
-// (6, 1) ("A", 1, 3), ("A", 2, 3), ("B", 1, 2)
-// (6, 7) ("A", 1, 3), ("A", 2, 3), ("B", 1, 2), ("B", 2, 2)
-// (7, 1) ("A", 1, 3)
-// (7, 7) ("A", 1, 3), ("A", 2, 3), ("B", 1, 2), ("B", 2, 2)
-// (8, 1) ("A", 1, 3), ("A", 2, 3), ("B", 1, 2), ("B", 2, 2)
-// (8, 7) ("A", 1, 3), ("A", 2, 3), ("B", 1, 2), ("B", 2, 2)
-//
-// In addition, expect 1 ReportParticipationObservation each day for each
-// report in the registry.
-TEST_F(PerDeviceNumericEventAggregatorTest, ElapsedTimeCheckObservationValuesWithBackfillAndGc) {
- auto start_day_index = CurrentDayIndex();
- const auto& total_report_id =
- logger::testing::per_device_numeric_stats::kStreamingTimeTotalMetricReportId;
- const auto& min_report_id =
- logger::testing::per_device_numeric_stats::kStreamingTimeMinMetricReportId;
- const auto& max_report_id =
- logger::testing::per_device_numeric_stats::kStreamingTimeMaxMetricReportId;
- std::vector<MetricReportId> streaming_time_ids = {total_report_id, min_report_id, max_report_id};
- const auto& expected_params =
- logger::testing::per_device_numeric_stats::kExpectedAggregationParams;
- // Set |backfill_days_| to 3.
- size_t backfill_days = 3;
- SetBackfillDays(backfill_days);
- // Log events for 9 days. Call GenerateObservations() on the first 6 day
- // indices, and the 9th.
- uint32_t num_days = 9;
- for (uint32_t offset = 0; offset < num_days; offset++) {
- auto day_index = CurrentDayIndex();
- ResetObservationStore();
- for (uint32_t event_code = 1; event_code < 3; event_code++) {
- for (const auto& report_id : streaming_time_ids) {
- if (offset > 0 && (offset % event_code == 0)) {
- EXPECT_EQ(kOK, LogPerDeviceElapsedTimeEvent(report_id, day_index, "A", event_code, 3));
- }
- if (offset > 0 && offset % (2 * event_code) == 0) {
- EXPECT_EQ(kOK, LogPerDeviceElapsedTimeEvent(report_id, day_index, "B", event_code, 2));
- }
- }
- }
-
- // Advance |test_clock_| by 1 day.
- AdvanceClock(kDay);
- if (offset < 6 || offset == 8) {
- // Generate Observations and garbage-collect, both for the previous day
- // index according to |test_clock_|. Back up the LocalAggregateStore and
- // the AggregatedObservationHistoryStore.
- DoScheduledTasksNow();
- }
- // Make the set of Observations which are expected to be generated on
- // |start_day_index + offset| and check it against the contents of the
- // FakeObservationStore.
- ExpectedPerDeviceNumericObservations expected_per_device_numeric_obs;
- ExpectedReportParticipationObservations expected_report_participation_obs;
- switch (offset) {
- case 0: {
- for (uint32_t day_index = start_day_index - backfill_days; day_index <= start_day_index;
- day_index++) {
- for (const auto& pair :
- MakeExpectedReportParticipationObservations(expected_params, day_index)) {
- expected_report_participation_obs.insert(pair);
- }
- }
- EXPECT_TRUE(CheckPerDeviceNumericObservations(
- expected_per_device_numeric_obs, expected_report_participation_obs,
- observation_store_.get(), update_recipient_.get()));
- break;
- }
- case 1: {
- expected_per_device_numeric_obs[{total_report_id, day_index}] = {{1, {{"A", 1u, 3}}},
- {7, {{"A", 1u, 3}}}};
- expected_per_device_numeric_obs[{min_report_id, day_index}] = {{1, {{"A", 1u, 3}}},
- {7, {{"A", 1u, 3}}}};
- expected_per_device_numeric_obs[{max_report_id, day_index}] = {{1, {{"A", 1u, 3}}},
- {7, {{"A", 1u, 3}}}};
- expected_report_participation_obs =
- MakeExpectedReportParticipationObservations(expected_params, day_index);
- EXPECT_TRUE(CheckPerDeviceNumericObservations(
- expected_per_device_numeric_obs, expected_report_participation_obs,
- observation_store_.get(), update_recipient_.get()))
- << "day 1";
- break;
- }
- case 2: {
- expected_per_device_numeric_obs[{total_report_id, day_index}] = {
- {1, {{"A", 1u, 3}, {"A", 2u, 3}, {"B", 1u, 2}}},
- {7, {{"A", 1u, 6}, {"A", 2u, 3}, {"B", 1u, 2}}}};
- expected_per_device_numeric_obs[{min_report_id, day_index}] = {
- {1, {{"A", 1u, 3}, {"A", 2u, 3}, {"B", 1u, 2}}},
- {7, {{"A", 1u, 3}, {"A", 2u, 3}, {"B", 1u, 2}}}};
- expected_per_device_numeric_obs[{max_report_id, day_index}] = {
- {1, {{"A", 1u, 3}, {"A", 2u, 3}, {"B", 1u, 2}}},
- {7, {{"A", 1u, 3}, {"A", 2u, 3}, {"B", 1u, 2}}}};
- expected_report_participation_obs =
- MakeExpectedReportParticipationObservations(expected_params, day_index);
- EXPECT_TRUE(CheckPerDeviceNumericObservations(
- expected_per_device_numeric_obs, expected_report_participation_obs,
- observation_store_.get(), update_recipient_.get()));
- break;
- }
- case 3: {
- expected_per_device_numeric_obs[{total_report_id, day_index}] = {
- {1, {{"A", 1u, 3}}}, {7, {{"A", 1u, 9}, {"A", 2u, 3}, {"B", 1u, 2}}}};
- expected_per_device_numeric_obs[{min_report_id, day_index}] = {
- {1, {{"A", 1u, 3}}}, {7, {{"A", 1u, 3}, {"A", 2u, 3}, {"B", 1u, 2}}}};
- expected_per_device_numeric_obs[{max_report_id, day_index}] = {
- {1, {{"A", 1u, 3}}}, {7, {{"A", 1u, 3}, {"A", 2u, 3}, {"B", 1u, 2}}}};
- expected_report_participation_obs =
- MakeExpectedReportParticipationObservations(expected_params, day_index);
- EXPECT_TRUE(CheckPerDeviceNumericObservations(
- expected_per_device_numeric_obs, expected_report_participation_obs,
- observation_store_.get(), update_recipient_.get()));
- break;
- }
- case 4: {
- expected_per_device_numeric_obs[{total_report_id, day_index}] = {
- {1, {{"A", 1u, 3}, {"A", 2u, 3}, {"B", 1u, 2}, {"B", 2u, 2}}},
- {7, {{"A", 1u, 12}, {"A", 2u, 6}, {"B", 1u, 4}, {"B", 2u, 2}}}};
- expected_per_device_numeric_obs[{min_report_id, day_index}] = {
- {1, {{"A", 1u, 3}, {"A", 2u, 3}, {"B", 1u, 2}, {"B", 2u, 2}}},
- {7, {{"A", 1u, 3}, {"A", 2u, 3}, {"B", 1u, 2}, {"B", 2u, 2}}}};
- expected_per_device_numeric_obs[{max_report_id, day_index}] = {
- {1, {{"A", 1u, 3}, {"A", 2u, 3}, {"B", 1u, 2}, {"B", 2u, 2}}},
- {7, {{"A", 1u, 3}, {"A", 2u, 3}, {"B", 1u, 2}, {"B", 2u, 2}}}};
- expected_report_participation_obs =
- MakeExpectedReportParticipationObservations(expected_params, day_index);
- EXPECT_TRUE(CheckPerDeviceNumericObservations(
- expected_per_device_numeric_obs, expected_report_participation_obs,
- observation_store_.get(), update_recipient_.get()));
- break;
- }
- case 5: {
- expected_per_device_numeric_obs[{total_report_id, day_index}] = {
- {1, {{"A", 1u, 3}}}, {7, {{"A", 1u, 15}, {"A", 2u, 6}, {"B", 1u, 4}, {"B", 2u, 2}}}};
- expected_per_device_numeric_obs[{min_report_id, day_index}] = {
- {1, {{"A", 1u, 3}}}, {7, {{"A", 1u, 3}, {"A", 2u, 3}, {"B", 1u, 2}, {"B", 2u, 2}}}};
- expected_per_device_numeric_obs[{max_report_id, day_index}] = {
- {1, {{"A", 1u, 3}}}, {7, {{"A", 1u, 3}, {"A", 2u, 3}, {"B", 1u, 2}, {"B", 2u, 2}}}};
- expected_report_participation_obs =
- MakeExpectedReportParticipationObservations(expected_params, day_index);
- EXPECT_TRUE(CheckPerDeviceNumericObservations(
- expected_per_device_numeric_obs, expected_report_participation_obs,
- observation_store_.get(), update_recipient_.get()));
- break;
- }
- case 8: {
- expected_per_device_numeric_obs[{total_report_id, start_day_index + 6}] = {
- {1, {{"A", 1u, 3}, {"A", 2u, 3}, {"B", 1u, 2}}},
- {7, {{"A", 1u, 18}, {"A", 2u, 9}, {"B", 1u, 6}, {"B", 2u, 2}}}};
- expected_per_device_numeric_obs[{total_report_id, start_day_index + 7}] = {
- {1, {{"A", 1u, 3}}}, {7, {{"A", 1u, 21}, {"A", 2u, 9}, {"B", 1u, 6}, {"B", 2u, 2}}}};
- expected_per_device_numeric_obs[{total_report_id, start_day_index + 8}] = {
- {1, {{"A", 1u, 3}, {"A", 2u, 3}, {"B", 1u, 2}, {"B", 2u, 2}}},
- {7, {{"A", 1u, 21}, {"A", 2u, 12}, {"B", 1u, 8}, {"B", 2u, 4}}}};
-
- expected_per_device_numeric_obs[{min_report_id, start_day_index + 6}] = {
- {1, {{"A", 1u, 3}, {"A", 2u, 3}, {"B", 1u, 2}}},
- {7, {{"A", 1u, 3}, {"A", 2u, 3}, {"B", 1u, 2}, {"B", 2u, 2}}}};
- expected_per_device_numeric_obs[{min_report_id, start_day_index + 7}] = {
- {1, {{"A", 1u, 3}}}, {7, {{"A", 1u, 3}, {"A", 2u, 3}, {"B", 1u, 2}, {"B", 2u, 2}}}};
- expected_per_device_numeric_obs[{min_report_id, start_day_index + 8}] = {
- {1, {{"A", 1u, 3}, {"A", 2u, 3}, {"B", 1u, 2}, {"B", 2u, 2}}},
- {7, {{"A", 1u, 3}, {"A", 2u, 3}, {"B", 1u, 2}, {"B", 2u, 2}}}};
-
- expected_per_device_numeric_obs[{max_report_id, start_day_index + 6}] = {
- {1, {{"A", 1u, 3}, {"A", 2u, 3}, {"B", 1u, 2}}},
- {7, {{"A", 1u, 3}, {"A", 2u, 3}, {"B", 1u, 2}, {"B", 2u, 2}}}};
- expected_per_device_numeric_obs[{max_report_id, start_day_index + 7}] = {
- {1, {{"A", 1u, 3}}}, {7, {{"A", 1u, 3}, {"A", 2u, 3}, {"B", 1u, 2}, {"B", 2u, 2}}}};
- expected_per_device_numeric_obs[{max_report_id, start_day_index + 8}] = {
- {1, {{"A", 1u, 3}, {"A", 2u, 3}, {"B", 1u, 2}, {"B", 2u, 2}}},
- {7, {{"A", 1u, 3}, {"A", 2u, 3}, {"B", 1u, 2}, {"B", 2u, 2}}}};
-
- for (uint32_t day_index = start_day_index + 6; day_index <= start_day_index + 8;
- day_index++) {
- for (const auto& pair :
- MakeExpectedReportParticipationObservations(expected_params, day_index)) {
- expected_report_participation_obs.insert(pair);
- }
- }
- EXPECT_TRUE(CheckPerDeviceNumericObservations(
- expected_per_device_numeric_obs, expected_report_participation_obs,
- observation_store_.get(), update_recipient_.get()));
- break;
- }
- default:
- EXPECT_TRUE(CheckPerDeviceNumericObservations(
- expected_per_device_numeric_obs, expected_report_participation_obs,
- observation_store_.get(), update_recipient_.get()));
- }
- }
}
// Tests that the LocalAggregateStore is updated as expected when
@@ -3607,150 +1191,6 @@
}
}
-// Check that GenerateObservations returns an OK status after some events have been logged for a
-// PerDeviceHistogram report.
-TEST_F(PerDeviceHistogramEventAggregatorTest, GenerateObservations) {
- const auto day_index = CurrentDayIndex();
- // Log several events on |day_index|.
- EXPECT_EQ(kOK, LogPerDeviceCountEvent(
- logger::testing::per_device_histogram::kSettingsChangedMetricReportId,
- day_index, "component_C", 0u, 5));
- EXPECT_EQ(kOK, LogPerDeviceCountEvent(
- logger::testing::per_device_histogram::kSettingsChangedMetricReportId,
- day_index, "component_C", 0u, 5));
-
- // Generate locally aggregated Observations for |day_index|.
- EXPECT_EQ(kOK, GenerateObservations(day_index));
-}
-
-// Tests GenerateObservations() and GarbageCollect() in the case where the
-// LocalAggregateStore contains aggregates for metrics with both UTC and LOCAL
-// time zone policies, and where the day index in local time may be less than
-// the day index in UTC.
-TEST_F(NoiseFreeMixedTimeZoneEventAggregatorTest, LocalBeforeUTC) {
- std::vector<ExpectedUniqueActivesObservations> expected_obs(3);
- // Begin at a time when the current day index is the same in both UTC and
- // local time. Log 1 event for event code 0 for each of the 2 reports, then
- // generate Observations and garbage-collect for the previous day index in
- // each of UTC and local time.
- auto start_day_index = CurrentDayIndex();
- LogUniqueActivesEvent(logger::testing::mixed_time_zone::kDeviceBootsMetricReportId,
- start_day_index, 0u);
- LogUniqueActivesEvent(logger::testing::mixed_time_zone::kFeaturesActiveMetricReportId,
- start_day_index, 0u);
- GenerateObservations(start_day_index - 1, start_day_index - 1);
- GarbageCollect(start_day_index - 1, start_day_index - 1);
- // Form the expected contents of the FakeObservationStore.
- // Since no events were logged on the previous day and no Observations have
- // been generated for that day yet, expect Observations of non-activity for
- // all event codes, for both reports.
- expected_obs[0] = MakeNullExpectedUniqueActivesObservations(
- logger::testing::mixed_time_zone::kExpectedAggregationParams, start_day_index - 1);
- EXPECT_TRUE(CheckUniqueActivesObservations(expected_obs[0], observation_store_.get(),
- update_recipient_.get()));
- ResetObservationStore();
- // Advance the day index in UTC, but not in local time, and log 1 event for
- // event code 1 for each of the 2 reports. Generate Observations and
- // garbage-collect for the previous day in each of UTC and local time.
- LogUniqueActivesEvent(logger::testing::mixed_time_zone::kDeviceBootsMetricReportId,
- start_day_index, 1u);
- LogUniqueActivesEvent(logger::testing::mixed_time_zone::kFeaturesActiveMetricReportId,
- start_day_index + 1, 1u);
- GenerateObservations(start_day_index, start_day_index - 1);
- GarbageCollect(start_day_index, start_day_index - 1);
- // Form the expected contents of the FakeObservationStore. Since
- // Observations have already been generated for the
- // DeviceBoots_UniqueDevices report for |start_day_index - 1|, expect no
- // Observations for that report.
- expected_obs[1][{logger::testing::mixed_time_zone::kFeaturesActiveMetricReportId,
- start_day_index}] = {{1, {true, false, false}}};
- EXPECT_TRUE(CheckUniqueActivesObservations(expected_obs[1], observation_store_.get(),
- update_recipient_.get()));
- ResetObservationStore();
- // Advance the day index in local time so that it is equal to the day index
- // in UTC. Log 1 event for event code 2 for each of the 2 reports, then
- // generate Observations and garbage-collect for the previous day in each of
- // UTC and local time.
- LogUniqueActivesEvent(logger::testing::mixed_time_zone::kDeviceBootsMetricReportId,
- start_day_index + 1, 2u);
- LogUniqueActivesEvent(logger::testing::mixed_time_zone::kFeaturesActiveMetricReportId,
- start_day_index + 1, 2u);
- GenerateObservations(start_day_index, start_day_index);
- GarbageCollect(start_day_index, start_day_index);
- // Form the expected contents of the FakeObservationStore. Since
- // Observations have already been generated for the
- // FeaturesActive_UniqueDevices report for day |start_day_index|, expect no
- // Observations for that report.
- expected_obs[2][{logger::testing::mixed_time_zone::kDeviceBootsMetricReportId, start_day_index}] =
- {{1, {true, true, false}}};
- EXPECT_TRUE(CheckUniqueActivesObservations(expected_obs[2], observation_store_.get(),
- update_recipient_.get()));
-}
-
-// Tests GenerateObservations() and GarbageCollect() in the case where the
-// LocalAggregateStore contains aggregates for metrics with both UTC and LOCAL
-// time zone policies, and where the day index in UTC may be less than
-// the day index in local time.
-TEST_F(NoiseFreeMixedTimeZoneEventAggregatorTest, LocalAfterUTC) {
- std::vector<ExpectedUniqueActivesObservations> expected_obs(3);
- // Begin at a time when the current day index is the same in both UTC and
- // local time. Log 1 event for event code 0 for each of the 2 reports, then
- // generate Observations and garbage-collect for the previous day index in
- // each of UTC and local time.
- auto start_day_index = CurrentDayIndex();
- LogUniqueActivesEvent(logger::testing::mixed_time_zone::kDeviceBootsMetricReportId,
- start_day_index, 0u);
- LogUniqueActivesEvent(logger::testing::mixed_time_zone::kFeaturesActiveMetricReportId,
- start_day_index, 0u);
- GenerateObservations(start_day_index - 1, start_day_index - 1);
- GarbageCollect(start_day_index - 1, start_day_index - 1);
- // Form the expected contents of the FakeObservationStore.
- // Since no events were logged on the previous day and no Observations have
- // been generated for that day yet, expect Observations of non-activity for
- // all event codes, for both reports.
- expected_obs[0] = MakeNullExpectedUniqueActivesObservations(
- logger::testing::mixed_time_zone::kExpectedAggregationParams, start_day_index - 1);
- EXPECT_TRUE(CheckUniqueActivesObservations(expected_obs[0], observation_store_.get(),
- update_recipient_.get()));
- ResetObservationStore();
- // Advance the day index in local time, but not in UTC, and log 1 event for
- // event code 1 for each of the 2 reports. Generate Observations and
- // garbage-collect for the previous day in each of UTC and local time.
- LogUniqueActivesEvent(logger::testing::mixed_time_zone::kDeviceBootsMetricReportId,
- start_day_index + 1, 1u);
- LogUniqueActivesEvent(logger::testing::mixed_time_zone::kFeaturesActiveMetricReportId,
- start_day_index, 1u);
- GenerateObservations(start_day_index - 1, start_day_index);
- GarbageCollect(start_day_index - 1, start_day_index);
- // Form the expected contents of the FakeObservationStore. Since
- // Observations have already been generated for the
- // FeaturesActive_UniqueDevices report for |start_day_index - 1|, expect no
- // Observations for that report.
- expected_obs[1][{logger::testing::mixed_time_zone::kDeviceBootsMetricReportId, start_day_index}] =
- {{1, {true, false, false}}};
- EXPECT_TRUE(CheckUniqueActivesObservations(expected_obs[1], observation_store_.get(),
- update_recipient_.get()));
- ResetObservationStore();
- // Advance the day index in UTC so that it is equal to the day index in
- // local time. Log 1 event for event code 2 for each of the 2 reports, then
- // generate Observations and garbage-collect for the previous day in each of
- // UTC and local time.
- LogUniqueActivesEvent(logger::testing::mixed_time_zone::kDeviceBootsMetricReportId,
- start_day_index + 1, 2u);
- LogUniqueActivesEvent(logger::testing::mixed_time_zone::kFeaturesActiveMetricReportId,
- start_day_index + 1, 2u);
- GenerateObservations(start_day_index, start_day_index);
- GarbageCollect(start_day_index, start_day_index);
- // Form the expected contents of the FakeObservationStore. Since
- // Observations have already been generated for the
- // DeviceBoots_UniqueDevices report for day |start_day_index|, expect no
- // Observations for that report.
- expected_obs[2][{logger::testing::mixed_time_zone::kFeaturesActiveMetricReportId,
- start_day_index}] = {{1, {true, true, false}}};
- EXPECT_TRUE(CheckUniqueActivesObservations(expected_obs[2], observation_store_.get(),
- update_recipient_.get()));
-}
-
// Starts the worker thread, and destructs the EventAggregator without
// explicitly shutting down the worker thread. Checks that the shutdown flag
// and worker thread are in the expected states before and after the thread is
@@ -3832,5 +1272,4 @@
EXPECT_GE(local_aggregate_proto_store_->write_count_, 1);
}
-} // namespace local_aggregation
-} // namespace cobalt
+} // namespace cobalt::local_aggregation
diff --git a/src/logger/event_loggers_test.cc b/src/logger/event_loggers_test.cc
index 7cc7268..e02cc16 100644
--- a/src/logger/event_loggers_test.cc
+++ b/src/logger/event_loggers_test.cc
@@ -303,7 +303,7 @@
}
Status GarbageCollectAggregateStore(uint32_t day_index) {
- return event_aggregator_->GarbageCollect(day_index);
+ return event_aggregator_->aggregate_store_->GarbageCollect(day_index);
}
};
@@ -1356,8 +1356,8 @@
uint32_t NumPerDeviceNumericAggregatesInStore() {
int count = 0;
- for (const auto& aggregates :
- protected_aggregate_store_.lock()->local_aggregate_store.by_report_key()) {
+ for (const auto& aggregates : aggregate_store_->protected_aggregate_store_.lock()
+ ->local_aggregate_store.by_report_key()) {
if (aggregates.second.has_numeric_aggregates()) {
count += aggregates.second.numeric_aggregates().by_component().size();
}