blob: 7d045becc78b057cd995a92b30f10e285badc442 [file] [log] [blame]
// 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 "logger/event_aggregator.h"
#include <google/protobuf/text_format.h>
#include <google/protobuf/util/message_differencer.h>
#include <map>
#include <memory>
#include <set>
#include <utility>
#include <vector>
#include "./event.pb.h"
#include "./gtest.h"
#include "logger/logger_test_utils.h"
#include "util/proto_util.h"
using ::google::protobuf::util::MessageDifferencer;
namespace cobalt {
using encoder::ClientSecret;
using encoder::ObservationStoreUpdateRecipient;
using encoder::ObservationStoreWriterInterface;
using encoder::SystemDataInterface;
using util::EncryptedMessageMaker;
using util::SerializeToBase64;
namespace logger {
using testing::ExpectedActivity;
using testing::ExpectedAggregationParams;
using testing::FakeObservationStore;
using testing::FetchAggregatedObservations;
using testing::MakeAggregationConfig;
using testing::MakeAggregationKey;
using testing::MakeNullExpectedActivity;
using testing::PopulateMetricDefinitions;
using testing::TestUpdateRecipient;
namespace {
static const uint32_t kCustomerId = 1;
static const uint32_t kProjectId = 1;
static const char kCustomerName[] = "Fuchsia";
static const char kProjectName[] = "Cobalt";
static const uint32_t kStartDayIndex = 100;
// Pairs (metric ID, report ID) for the locally aggregated reports defined in
// |kMetricDefinitions| and |kNoiseFreeUniqueActivesMetricDefinitions|.
const MetricReportId kDeviceBootsMetricReportId = MetricReportId(10, 101);
const MetricReportId kFeaturesActiveMetricReportId = MetricReportId(20, 201);
const MetricReportId kErrorsOccurredMetricReportId = MetricReportId(30, 302);
const MetricReportId kEventsOccurredMetricReportId = MetricReportId(40, 402);
// A set of metric definitions of type EVENT_OCCURRED, each of which has a
// UNIQUE_N_DAY_ACTIVES report.
static const char kUniqueActivesMetricDefinitions[] = R"(
metric {
metric_name: "DeviceBoots"
metric_type: EVENT_OCCURRED
customer_id: 1
project_id: 1
id: 10
max_event_code: 1
reports: {
report_name: "DeviceBoots_UniqueDevices"
id: 101
report_type: UNIQUE_N_DAY_ACTIVES
local_privacy_noise_level: MEDIUM
window_size: 1
}
}
metric {
metric_name: "FeaturesActive"
metric_type: EVENT_OCCURRED
customer_id: 1
project_id: 1
id: 20
max_event_code: 4
reports: {
report_name: "FeaturesActive_UniqueDevices"
id: 201
report_type: UNIQUE_N_DAY_ACTIVES
local_privacy_noise_level: LARGE
window_size: 7
window_size: 30
}
}
metric {
metric_name: "ErrorsOccurred"
metric_type: EVENT_OCCURRED
customer_id: 1
project_id: 1
id: 30
max_event_code: 2
reports: {
report_name: "ErrorsOccurred_SimpleCount"
id: 301
report_type: SIMPLE_OCCURRENCE_COUNT
local_privacy_noise_level: NONE
}
reports: {
report_name: "ErrorsOccurred_UniqueDevices"
id: 302
report_type: UNIQUE_N_DAY_ACTIVES
local_privacy_noise_level: LARGE
window_size: 1
window_size: 7
window_size: 30
}
}
)";
// Properties of the locally aggregated reports in
// |kUniqueActivesMetricDefinitions|.
static const ExpectedAggregationParams kUniqueActivesExpectedParams = {
/* The total number of locally aggregated Observations which should be
generated for each day index. */
21,
/* The MetricReportIds of the locally aggregated reports in this
configuration. */
{kDeviceBootsMetricReportId, kFeaturesActiveMetricReportId,
kErrorsOccurredMetricReportId},
/* The number of Observations which should be generated for each day
index, broken down by MetricReportId. */
{{kDeviceBootsMetricReportId, 2},
{kFeaturesActiveMetricReportId, 10},
{kErrorsOccurredMetricReportId, 9}},
/* The number of event codes for each MetricReportId. */
{{kDeviceBootsMetricReportId, 2},
{kFeaturesActiveMetricReportId, 5},
{kErrorsOccurredMetricReportId, 3}},
/* The set of window sizes for each MetricReportId. */
{{kDeviceBootsMetricReportId, {1}},
{kFeaturesActiveMetricReportId, {7, 30}},
{kErrorsOccurredMetricReportId, {1, 7, 30}}}};
// A set of MetricDefinitions of type EVENT_OCCURRED, each of which has a
// UNIQUE_N_DAY_ACTIVES report with local_privacy_noise_level set to NONE.
static const char kNoiseFreeUniqueActivesMetricDefinitions[] = R"(
metric {
metric_name: "DeviceBoots"
metric_type: EVENT_OCCURRED
customer_id: 1
project_id: 1
id: 10
max_event_code: 1
reports: {
report_name: "DeviceBoots_UniqueDevices"
id: 101
report_type: UNIQUE_N_DAY_ACTIVES
local_privacy_noise_level: NONE
window_size: 1
}
}
metric {
metric_name: "FeaturesActive"
metric_type: EVENT_OCCURRED
customer_id: 1
project_id: 1
id: 20
max_event_code: 4
reports: {
report_name: "FeaturesActive_UniqueDevices"
id: 201
report_type: UNIQUE_N_DAY_ACTIVES
local_privacy_noise_level: NONE
window_size: 1
window_size: 7
window_size: 30
}
}
metric {
metric_name: "EventsOccurred"
metric_type: EVENT_OCCURRED
customer_id: 1
project_id: 1
id: 40
max_event_code: 4
reports: {
report_name: "EventsOccurred_SimpleCount"
id: 401
report_type: SIMPLE_OCCURRENCE_COUNT
local_privacy_noise_level: NONE
}
reports: {
report_name: "EventsOccurred_UniqueDevices"
id: 402
report_type: UNIQUE_N_DAY_ACTIVES
local_privacy_noise_level: NONE
window_size: 1
window_size: 7
}
}
)";
// Properties of the locally aggregated reports in
// |kNoiseFreeUniqueActivesMetricDefinitions|.
static const ExpectedAggregationParams kNoiseFreeUniqueActivesExpectedParams = {
/* The total number of locally aggregated Observations which should be
generated for each day index. */
27,
/* The MetricReportIds of the locally aggregated reports in this
configuration. */
{kDeviceBootsMetricReportId, kFeaturesActiveMetricReportId,
kEventsOccurredMetricReportId},
/* The number of Observations which should be generated for each day
index, broken down by MetricReportId. */
{{kDeviceBootsMetricReportId, 2},
{kFeaturesActiveMetricReportId, 15},
{kEventsOccurredMetricReportId, 10}},
/* The number of event codes for each MetricReportId. */
{{kDeviceBootsMetricReportId, 2},
{kFeaturesActiveMetricReportId, 5},
{kEventsOccurredMetricReportId, 5}},
/* The set of window sizes for each MetricReportId. */
{{kDeviceBootsMetricReportId, {1}},
{kFeaturesActiveMetricReportId, {1, 7, 30}},
{kEventsOccurredMetricReportId, {1, 7}}}};
// 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.
typedef std::map<std::string, std::map<uint32_t, std::set<uint32_t>>>
LoggedActivity;
// Given a string representing a MetricDefinitions proto message, creates a
// ProjectContext from that MetricDefinitions and returns a unique pointer.
std::unique_ptr<ProjectContext> MakeProjectContext(const char metric_string[]) {
auto metric_definitions = std::make_unique<MetricDefinitions>();
if (!PopulateMetricDefinitions(metric_string, metric_definitions.get())) {
return nullptr;
}
auto project_context = std::make_unique<ProjectContext>(
kCustomerId, kProjectId, kCustomerName, kProjectName,
std::move(metric_definitions));
return project_context;
}
} // namespace
// EventAggregatorTest creates an EventAggregator which sends its Observations
// to a FakeObservationStore. The EventAggregator is not pre-populated with
// aggregation configurations.
class EventAggregatorTest : public ::testing::Test {
protected:
void SetUp() {
observation_store_.reset(new FakeObservationStore);
update_recipient_.reset(new TestUpdateRecipient);
observation_encrypter_.reset(
new EncryptedMessageMaker("", EncryptedMessage::NONE));
observation_writer_.reset(
new ObservationWriter(observation_store_.get(), update_recipient_.get(),
observation_encrypter_.get()));
encoder_.reset(
new Encoder(ClientSecret::GenerateNewSecret(), system_data_.get()));
local_aggregate_store_.reset(new LocalAggregateStore);
event_aggregator_.reset(new EventAggregator(encoder_.get(),
observation_writer_.get(),
local_aggregate_store_.get()));
}
// Clears the FakeObservationStore and resets the TestUpdateRecipient's count
// of received observations.
void ResetObservationStore() {
observation_store_->messages_received.clear();
observation_store_->metadata_received.clear();
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 a UniqueActivesEvent 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(const ProjectContext& project_context,
const MetricReportId& metric_report_id,
uint32_t day_index, uint32_t event_code,
LoggedActivity* logged_activity = nullptr) {
EventRecord event_record;
event_record.metric = project_context.GetMetric(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(project_context, metric_report_id), &key)) {
return kInvalidArguments;
}
if (logged_activity->count(key) == 0) {
logged_activity->insert(
std::make_pair(key, std::map<uint32_t, std::set<uint32_t>>({})));
}
if (logged_activity->at(key).count(event_code) == 0) {
logged_activity->at(key).insert(
std::make_pair(event_code, std::set<uint32_t>({})));
}
(logged_activity->at(key).at(event_code)).insert(day_index);
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 |kStartDayIndex| 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 CheckAggregateStore(const LoggedActivity& logged_activity,
uint32_t current_day_index) {
// Check that the LocalAggregateStore contains no more aggregates than
// |logged_activity| and |day_last_garbage_collected_| should imply.
for (const auto& report_pair : local_aggregate_store_->aggregates()) {
const auto& report_key = report_pair.first;
const auto& aggregates = report_pair.second;
// Check whether this ReportAggregationKey is in |logged_activity|. If
// not, expect that its by_event_code map is empty.
if (logged_activity.count(report_key) == 0u) {
EXPECT_TRUE(aggregates.by_event_code().empty());
if (!aggregates.by_event_code().empty()) {
return false;
}
break;
}
auto expected_events = logged_activity.at(report_key);
for (const auto& event_pair : aggregates.by_event_code()) {
// Check that this event code is in |logged_activity| under this
// ReportAggregationKey.
auto event_code = event_pair.first;
EXPECT_GT(expected_events.count(event_code), 0u);
if (expected_events.count(event_code) == 0u) {
return false;
}
const auto& expected_days = expected_events[event_code];
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;
EXPECT_GT(expected_days.count(day_index), 0u);
if (expected_days.count(day_index) == 0u) {
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.
EXPECT_GT(local_aggregate_store_->aggregates().count(logged_key), 0u);
if (local_aggregate_store_->aggregates().count(logged_key) == 0u) {
return false;
}
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;
auto earliest_allowed =
EarliestAllowedDayIndex(local_aggregate_store_->aggregates()
.at(logged_key)
.aggregation_config());
// 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.
if (local_aggregate_store_->aggregates()
.at(logged_key)
.by_event_code()
.count(logged_event_code) == 0u) {
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) {
if (logged_day_index >= earliest_allowed) {
EXPECT_GT(local_aggregate_store_->aggregates()
.at(logged_key)
.by_event_code()
.at(logged_event_code)
.by_day_index()
.count(logged_day_index),
0u);
if (local_aggregate_store_->aggregates()
.at(logged_key)
.by_event_code()
.at(logged_event_code)
.by_day_index()
.count(logged_day_index) == 0u) {
return false;
}
EXPECT_TRUE(local_aggregate_store_->aggregates()
.at(logged_key)
.by_event_code()
.at(logged_event_code)
.by_day_index()
.at(logged_day_index)
.activity_daily_aggregate()
.activity_indicator());
if (!local_aggregate_store_->aggregates()
.at(logged_key)
.by_event_code()
.at(logged_event_code)
.by_day_index()
.at(logged_day_index)
.activity_daily_aggregate()
.activity_indicator()) {
return false;
}
}
}
}
}
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.
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;
// i.e., |kStartDayIndex|.
if (day_last_garbage_collected_ == 0u) {
return kStartDayIndex;
} else {
// Otherwise, it is the later of:
// (a) The day index on which the store was created.
// (b) The day index for which the store was last garbage-collected,
// minus the largest window size in the report associated to |config|,
// plus 1.
uint32_t max_window_size = 1;
for (uint32_t window_size : config.report().window_size()) {
if (window_size > max_window_size) {
max_window_size = window_size;
}
}
if (day_last_garbage_collected_ < (max_window_size + 1)) {
return kStartDayIndex;
}
return (kStartDayIndex <
(day_last_garbage_collected_ - max_window_size + 1))
? (day_last_garbage_collected_ - max_window_size + 1)
: kStartDayIndex;
}
}
std::unique_ptr<LocalAggregateStore> local_aggregate_store_;
std::unique_ptr<EventAggregator> event_aggregator_;
std::unique_ptr<TestUpdateRecipient> update_recipient_;
std::unique_ptr<FakeObservationStore> observation_store_;
// 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;
std::unique_ptr<ObservationWriter> observation_writer_;
std::unique_ptr<Encoder> encoder_;
std::unique_ptr<EncryptedMessageMaker> observation_encrypter_;
private:
std::unique_ptr<SystemDataInterface> system_data_;
};
// Creates an EventAggregator and provides it with MetricDefinitions from a
// serialized representation |metric_string|.
class EventAggregatorTestWithProjectContext : public EventAggregatorTest {
protected:
explicit EventAggregatorTestWithProjectContext(const char metric_string[]) {
project_context_ = MakeProjectContext(metric_string);
}
void SetUp() {
EventAggregatorTest::SetUp();
event_aggregator_->UpdateAggregationConfigs(*project_context_);
}
// Logs a UniqueActivesEvent for the MetricReportId of a locally aggregated
// report in |metric_string|. Overrides the method
// EventAggregatorTest::LogUniqueActivesEvent.
Status LogUniqueActivesEvent(const MetricReportId& metric_report_id,
uint32_t day_index, uint32_t event_code,
LoggedActivity* logged_activity = nullptr) {
return EventAggregatorTest::LogUniqueActivesEvent(
*project_context_, metric_report_id, day_index, event_code,
logged_activity);
}
private:
// A ProjectContext wrapping the MetricDefinitions passed to the constructor
// in |metric_string|.
std::unique_ptr<ProjectContext> project_context_;
};
// Creates an EventAggregator and provides it with
// |kUniqueActivesMetricDefinitions|.
class UniqueActivesEventAggregatorTest
: public EventAggregatorTestWithProjectContext {
protected:
UniqueActivesEventAggregatorTest()
: EventAggregatorTestWithProjectContext(kUniqueActivesMetricDefinitions) {
}
void SetUp() { EventAggregatorTestWithProjectContext::SetUp(); }
};
// Creates an EventAggregator as in EventAggregatorTest and provides it with
// |kNoiseFreeUniqueActivesMetricDefinitions|.
class NoiseFreeUniqueActivesEventAggregatorTest
: public EventAggregatorTestWithProjectContext {
protected:
NoiseFreeUniqueActivesEventAggregatorTest()
: EventAggregatorTestWithProjectContext(
kNoiseFreeUniqueActivesMetricDefinitions) {}
void SetUp() { EventAggregatorTestWithProjectContext::SetUp(); }
};
// Tests that an empty LocalAggregateStore is updated with ReportAggregationKeys
// and AggregationConfigs as expected when
// EventAggregator::UpdateAggregationConfigs is called.
TEST_F(EventAggregatorTest, UpdateAggregationConfigs) {
// Check that the LocalAggregateStore is empty.
EXPECT_EQ(0u, local_aggregate_store_->aggregates().size());
// Provide |kUniqueActivesMetricDefinitions| to the EventAggregator.
auto unique_actives_project_context =
MakeProjectContext(kUniqueActivesMetricDefinitions);
EXPECT_EQ(kOK, event_aggregator_->UpdateAggregationConfigs(
*unique_actives_project_context));
// Check that the number of key-value pairs in the LocalAggregateStore is
// now equal to the number of locally aggregated reports in
// |kUniqueActivesMetricDefinitions|.
EXPECT_EQ(kUniqueActivesExpectedParams.metric_report_ids.size(),
local_aggregate_store_->aggregates().size());
// Check that the LocalAggregateStore contains the expected
// ReportAggregationKey and AggregationConfig for each locally aggregated
// report in |kUniqueActivesMetricDefinitions|,
for (const auto& metric_report_id :
kUniqueActivesExpectedParams.metric_report_ids) {
std::string key;
SerializeToBase64(
MakeAggregationKey(*unique_actives_project_context, metric_report_id),
&key);
auto config = MakeAggregationConfig(*unique_actives_project_context,
metric_report_id);
EXPECT_NE(local_aggregate_store_->aggregates().end(),
local_aggregate_store_->aggregates().find(key));
EXPECT_TRUE(MessageDifferencer::Equals(
config,
local_aggregate_store_->aggregates().at(key).aggregation_config()));
}
}
// Tests two assumptions about the behavior of
// EventAggregator::UpdateAggregationConfigs when two projects with the same
// customer ID and project ID provide configurations to the EventAggregator.
// These assumptions are:
// (1) If the second project provides a report with a
// ReportAggregationKey which was not provided by the first project, then the
// EventAggregator accepts the new report.
// (2) If a report provided by the second project has a ReportAggregationKey
// which was already provided by the first project, then the EventAggregator
// rejects the new report, even if its ReportDefinition differs from that of
// existing report with the same ReportAggregationKey.
TEST_F(EventAggregatorTest, UpdateAggregationConfigsWithSameKey) {
// Provide the EventAggregator with |kUniqueActivesMetricDefinitions|.
auto unique_actives_project_context =
MakeProjectContext(kUniqueActivesMetricDefinitions);
EXPECT_EQ(kOK, event_aggregator_->UpdateAggregationConfigs(
*unique_actives_project_context));
// Check that the number of key-value pairs in the LocalAggregateStore is
// now equal to the number of locally aggregated reports in
// |kUniqueActivesMetricDefinitions|.
EXPECT_EQ(3u, local_aggregate_store_->aggregates().size());
// Provide the EventAggregator with
// |kNoiseFreeUniqueActivesMetricDefinitions|.
auto noise_free_unique_actives_project_context =
MakeProjectContext(kNoiseFreeUniqueActivesMetricDefinitions);
EXPECT_EQ(kOK, event_aggregator_->UpdateAggregationConfigs(
*noise_free_unique_actives_project_context));
// Check that the number of key-value pairs in the LocalAggregateStore is
// now equal to the number of distinct MetricReportIds of locally aggregated
// reports in |kUniqueActivesMetricDefinitions| and
// |kNoiseFreeUniqueActivesMetricDefinitions|.
EXPECT_EQ(4u, local_aggregate_store_->aggregates().size());
// The MetricReportId |kFeaturesActiveMetricReportId| appears in both
// |kUniqueActivesMetricDefinitions| and
// |kNoiseFreeUniqueActivesMetricDefinitions|. The associated
// ReportAggregationKeys are identical, but the AggregationConfigs are
// different.
//
// Check that the AggregationConfig stored in the LocalAggregateStore under
// the key associated to |kFeaturesActiveMetricReportId| is the first
// AggregationConfig that was provided for that key; i.e., is derived from
// |kUniqueActivesMetricDefinitions|.
std::string key;
EXPECT_TRUE(
SerializeToBase64(MakeAggregationKey(*unique_actives_project_context,
kFeaturesActiveMetricReportId),
&key));
auto unique_actives_config = MakeAggregationConfig(
*unique_actives_project_context, kFeaturesActiveMetricReportId);
EXPECT_NE(local_aggregate_store_->aggregates().end(),
local_aggregate_store_->aggregates().find(key));
EXPECT_TRUE(MessageDifferencer::Equals(
unique_actives_config,
local_aggregate_store_->aggregates().at(key).aggregation_config()));
auto noise_free_config =
MakeAggregationConfig(*noise_free_unique_actives_project_context,
kFeaturesActiveMetricReportId);
EXPECT_FALSE(MessageDifferencer::Equals(
noise_free_config,
local_aggregate_store_->aggregates().at(key).aggregation_config()));
}
// Tests that EventAggregator::LogUniqueActivesEvent returns
// |kInvalidArguments| when passed a report ID which is not associated to a
// key of the LocalAggregateStore, or when passed an EventRecord containing an
// Event proto message which is not of type OccurrenceEvent.
TEST_F(EventAggregatorTest, LogBadEvents) {
// Provide the EventAggregator with |kUniqueActivesMetricDefinitions|.
auto unique_actives_project_context =
MakeProjectContext(kUniqueActivesMetricDefinitions);
EXPECT_EQ(kOK, event_aggregator_->UpdateAggregationConfigs(
*unique_actives_project_context));
// Attempt to log a UniqueActivesEvent for |kEventsOccurredMetricReportId|,
// which is not in |kUniqueActivesMetricDefinitions|. Check that the result
// is |kInvalidArguments|.
auto noise_free_project_context =
MakeProjectContext(kNoiseFreeUniqueActivesMetricDefinitions);
EventRecord bad_event_record;
bad_event_record.metric = noise_free_project_context->GetMetric(
kEventsOccurredMetricReportId.first);
bad_event_record.event->set_day_index(kStartDayIndex);
bad_event_record.event->mutable_occurrence_event()->set_event_code(0u);
EXPECT_EQ(kInvalidArguments,
event_aggregator_->LogUniqueActivesEvent(
kEventsOccurredMetricReportId.second, &bad_event_record));
// Attempt to call LogUniqueActivesEvent() with a valid metric and report
// ID, but with an EventRecord wrapping an Event which is not an
// OccurrenceEvent. Check that the result is |kInvalidArguments|.
bad_event_record.metric = unique_actives_project_context->GetMetric(
kFeaturesActiveMetricReportId.first);
bad_event_record.event->mutable_count_event();
EXPECT_EQ(kInvalidArguments,
event_aggregator_->LogUniqueActivesEvent(
kFeaturesActiveMetricReportId.second, &bad_event_record));
}
// 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
// LocalAggregateStore, and with an EventRecord which wraps an OccurrenceEvent.
//
// Logs some valid events each day for 35 days, checking the contents of the
// LocalAggregateStore each day.
TEST_F(UniqueActivesEventAggregatorTest, LogUniqueActivesEvents) {
LoggedActivity logged_activity;
uint32_t num_days = 35;
for (uint32_t offset = 0; offset < num_days; offset++) {
// Log an event for the FeaturesActive_UniqueDevices report of
// |kUniqueActivesMetricDefinitions| with event code 0. Check the contents
// of the LocalAggregateStore.
EXPECT_EQ(kOK, LogUniqueActivesEvent(kFeaturesActiveMetricReportId,
kStartDayIndex + offset, 0u,
&logged_activity));
EXPECT_TRUE(CheckAggregateStore(logged_activity, kStartDayIndex));
// Log another event for the same report, event code, and day index.
// Check the contents of the LocalAggregateStore.
EXPECT_EQ(kOK, LogUniqueActivesEvent(kFeaturesActiveMetricReportId,
kStartDayIndex + offset, 0u,
&logged_activity));
EXPECT_TRUE(CheckAggregateStore(logged_activity, kStartDayIndex));
// Log several more events for various valid reports and event codes. Check
// the contents of the LocalAggregateStore.
EXPECT_EQ(kOK, LogUniqueActivesEvent(kDeviceBootsMetricReportId,
kStartDayIndex + offset, 0u,
&logged_activity));
EXPECT_EQ(kOK, LogUniqueActivesEvent(kFeaturesActiveMetricReportId,
kStartDayIndex + offset, 4u,
&logged_activity));
EXPECT_EQ(kOK, LogUniqueActivesEvent(kErrorsOccurredMetricReportId,
kStartDayIndex + offset, 1u,
&logged_activity));
EXPECT_TRUE(CheckAggregateStore(logged_activity, kStartDayIndex + offset));
}
}
// Tests the method EventAggregator::GarbageCollect().
//
// For each value of N in the range [0, 34], logs some UniqueActivesEvents each
// day for N consecutive days and then garbage-collect 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;
uint32_t end_day_index = kStartDayIndex + days_before_gc;
for (uint32_t day_index = kStartDayIndex; day_index < end_day_index;
day_index++) {
for (const auto& metric_report_id :
kUniqueActivesExpectedParams.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));
// Log 1 event with event code 1.
EXPECT_EQ(kOK, LogUniqueActivesEvent(metric_report_id, day_index, 1u,
&logged_activity));
}
}
EXPECT_EQ(kOK, event_aggregator_->GarbageCollect(end_day_index));
day_last_garbage_collected_ = end_day_index;
EXPECT_TRUE(CheckAggregateStore(logged_activity, end_day_index));
TearDown();
}
}
// 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(UniqueActivesEventAggregatorTest, GenerateObservationsNoEvents) {
EXPECT_EQ(kOK, event_aggregator_->GenerateObservations(kStartDayIndex));
std::vector<Observation2> observations(0);
EXPECT_TRUE(FetchAggregatedObservations(
&observations, kUniqueActivesExpectedParams, 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
// UniqueActivesEvents have been logged, without any garbage collection.
//
// For 35 days, logs 2 events each day for the ErrorsOccurred_UniqueDevices
// report and 2 events for the FeaturesActive_UniqueDevices report, all
// with event type index 0.
//
// Each day following the first 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
// |kUniqueActivesMetricDefinitions|.
TEST_F(UniqueActivesEventAggregatorTest, GenerateObservations) {
int num_days = 35;
std::vector<Observation2> observations(0);
for (uint32_t day_index = kStartDayIndex;
day_index < kStartDayIndex + num_days; day_index++) {
if (day_index > kStartDayIndex) {
observations.clear();
ResetObservationStore();
EXPECT_EQ(kOK, event_aggregator_->GenerateObservations(day_index - 1));
EXPECT_TRUE(FetchAggregatedObservations(
&observations, kUniqueActivesExpectedParams, observation_store_.get(),
update_recipient_.get()));
}
for (int i = 0; i < 2; i++) {
EXPECT_EQ(kOK, LogUniqueActivesEvent(kErrorsOccurredMetricReportId,
day_index, 0u));
EXPECT_EQ(kOK, LogUniqueActivesEvent(kFeaturesActiveMetricReportId,
day_index, 0u));
}
}
observations.clear();
ResetObservationStore();
EXPECT_EQ(kOK, event_aggregator_->GenerateObservations(kStartDayIndex +
num_days - 1));
EXPECT_TRUE(FetchAggregatedObservations(
&observations, kUniqueActivesExpectedParams, observation_store_.get(),
update_recipient_.get()));
}
// Tests that GenerateObservations() returns a positive status and that the
// expected number of Observations is generated after some UniqueActivesEvents
// have been logged, in the presence of daily garbage collection.
//
// For 35 days, logs 2 events each day for the ErrorsOccurred_UniqueDevices
// report and 2 events for the FeaturesActive_UniqueDevices report, all
// with event type index 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
// |kUniqueActivesMetricDefinitions|.
TEST_F(UniqueActivesEventAggregatorTest,
GenerateObservationWithGarbageCollection) {
int num_days = 35;
std::vector<Observation2> observations(0);
for (uint32_t day_index = kStartDayIndex;
day_index < kStartDayIndex + num_days; day_index++) {
if (day_index > kStartDayIndex) {
observations.clear();
ResetObservationStore();
EXPECT_EQ(kOK, event_aggregator_->GenerateObservations(day_index - 1));
EXPECT_TRUE(FetchAggregatedObservations(
&observations, kUniqueActivesExpectedParams, observation_store_.get(),
update_recipient_.get()));
EXPECT_EQ(kOK, event_aggregator_->GarbageCollect(day_index));
}
for (int i = 0; i < 2; i++) {
EXPECT_EQ(kOK, LogUniqueActivesEvent(kErrorsOccurredMetricReportId,
day_index, 0u));
EXPECT_EQ(kOK, LogUniqueActivesEvent(kFeaturesActiveMetricReportId,
day_index, 0u));
}
}
observations.clear();
ResetObservationStore();
EXPECT_EQ(kOK, event_aggregator_->GenerateObservations(kStartDayIndex +
num_days - 1));
EXPECT_TRUE(FetchAggregatedObservations(
&observations, kUniqueActivesExpectedParams, observation_store_.get(),
update_recipient_.get()));
EXPECT_EQ(kOK, event_aggregator_->GarbageCollect(kStartDayIndex + num_days));
}
// Checks that UniqueActivesObservations with the expected values (i.e.,
// non-active for all window sizes and event types) are generated when no Events
// have been logged to the EventAggregator.
TEST_F(NoiseFreeUniqueActivesEventAggregatorTest,
CheckObservationValuesNoEvents) {
EXPECT_EQ(kOK, event_aggregator_->GenerateObservations(kStartDayIndex));
auto expected_activity =
MakeNullExpectedActivity(kNoiseFreeUniqueActivesExpectedParams);
EXPECT_TRUE(CheckUniqueActivesObservations(
expected_activity, kNoiseFreeUniqueActivesExpectedParams,
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 for over
// multiple days, without garbage collection.
//
// Logs events for the SomeEventsOccurred_UniqueDevices report (whose parent
// metric has max_event_type_index = 4) for 10 days, according to the following
// pattern:
//
// * Never log event type 0.
// * On the i-th day (0-indexed) of logging, log an event for event type k,
// 1 <= k < 5, if 3*k divides i.
//
// Each day following the first day, generates Observations for the previous
// day index and check them against the expected set of Observations. Also
// generates and check Observations for the last day of logging.
//
// The SomeEventsOccurred_UniqueDevices report has window sizes 1 and 7, and
// the expected pattern of those Observations' values on the i-th day is:
//
// (i, window size) true for event types
// ------------------------------------------------------
// (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(NoiseFreeUniqueActivesEventAggregatorTest,
CheckObservationValuesSingleDay) {
// Log several events.
EXPECT_EQ(kOK, LogUniqueActivesEvent(kFeaturesActiveMetricReportId,
kStartDayIndex, 0u));
EXPECT_EQ(kOK, LogUniqueActivesEvent(kFeaturesActiveMetricReportId,
kStartDayIndex, 0u));
EXPECT_EQ(kOK, LogUniqueActivesEvent(kEventsOccurredMetricReportId,
kStartDayIndex, 1u));
// Generate locally aggregated Observations.
EXPECT_EQ(kOK, event_aggregator_->GenerateObservations(kStartDayIndex));
// Form the expected activity map.
auto expected_activity =
MakeNullExpectedActivity(kNoiseFreeUniqueActivesExpectedParams);
expected_activity[kFeaturesActiveMetricReportId] = {
{1, {true, false, false, false, false}},
{7, {true, false, false, false, false}},
{30, {true, false, false, false, false}}};
expected_activity[kEventsOccurredMetricReportId] = {
{1, {false, true, false, false, false}},
{7, {false, true, false, false, false}}};
// Check the contents of the FakeObservationStore.
EXPECT_TRUE(CheckUniqueActivesObservations(
expected_activity, kNoiseFreeUniqueActivesExpectedParams,
observation_store_.get(), update_recipient_.get()));
}
TEST_F(NoiseFreeUniqueActivesEventAggregatorTest,
CheckObservationValuesMultiDay) {
// Generate expected activity maps for the 10 days of logging.
uint32_t num_days = 10;
std::vector<ExpectedActivity> expected_activity(num_days);
for (uint32_t offset = 0; offset < num_days; offset++) {
expected_activity[offset] =
MakeNullExpectedActivity(kNoiseFreeUniqueActivesExpectedParams);
}
expected_activity[0][kEventsOccurredMetricReportId] = {
{1, {false, true, true, true, true}},
{7, {false, true, true, true, true}}};
expected_activity[1][kEventsOccurredMetricReportId] = {
{1, {false, false, false, false, false}},
{7, {false, true, true, true, true}}};
expected_activity[2][kEventsOccurredMetricReportId] = {
{1, {false, false, false, false, false}},
{7, {false, true, true, true, true}}};
expected_activity[3][kEventsOccurredMetricReportId] = {
{1, {false, true, false, false, false}},
{7, {false, true, true, true, true}}};
expected_activity[4][kEventsOccurredMetricReportId] = {
{1, {false, false, false, false, false}},
{7, {false, true, true, true, true}}};
expected_activity[5][kEventsOccurredMetricReportId] = {
{1, {false, false, false, false, false}},
{7, {false, true, true, true, true}}};
expected_activity[6][kEventsOccurredMetricReportId] = {
{1, {false, true, true, false, false}},
{7, {false, true, true, true, true}}};
expected_activity[7][kEventsOccurredMetricReportId] = {
{1, {false, false, false, false, false}},
{7, {false, true, true, false, false}}};
expected_activity[8][kEventsOccurredMetricReportId] = {
{1, {false, false, false, false, false}},
{7, {false, true, true, false, false}}};
expected_activity[9][kEventsOccurredMetricReportId] = {
{1, {false, true, false, true, false}},
{7, {false, true, true, true, false}}};
for (uint32_t offset = 0; offset < num_days; offset++) {
for (uint32_t event_code = 1;
event_code < kNoiseFreeUniqueActivesExpectedParams.num_event_codes.at(
kEventsOccurredMetricReportId);
event_code++) {
if (offset % (3 * event_code) == 0) {
EXPECT_EQ(kOK,
LogUniqueActivesEvent(kEventsOccurredMetricReportId,
kStartDayIndex + offset, event_code));
}
}
// Clear the FakeObservationStore.
ResetObservationStore();
// Generate locally aggregated Observations.
EXPECT_EQ(kOK,
event_aggregator_->GenerateObservations(kStartDayIndex + offset));
// Check the generated Observations against the expectation.
EXPECT_TRUE(CheckUniqueActivesObservations(
expected_activity[offset], kNoiseFreeUniqueActivesExpectedParams,
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 for over
// multiple days, with daily garbage collection.
//
// Logs events for the SomeEventsOccurred_UniqueDevices report (whose parent
// metric has max_event_type_index = 4) for 10 days, according to the following
// pattern:
//
// * Never log event type 0.
// * On the i-th day (0-indexed) of logging, log an event for event type k,
// 1 <= k < 5, if 3*k divides i.
//
// Each day following the first day, generates Observations for the previous
// day index and check them against the expected set of Observations. Also
// generates and check Observations for the last day of logging.
//
// The SomeEventsOccurred_UniqueDevices report has window sizes 1 and 7, and
// the expected pattern of those Observations' values on the i-th day is:
//
// (i, window size) true for event types
// ------------------------------------------------------
// (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(NoiseFreeUniqueActivesEventAggregatorTest,
CheckObservationValuesMultiDayWithGarbageCollection) {
// Generate expected activity maps for the 10 days of logging.
uint32_t num_days = 10;
std::vector<ExpectedActivity> expected_activity(num_days);
for (uint32_t offset = 0; offset < num_days; offset++) {
expected_activity[offset] =
MakeNullExpectedActivity(kNoiseFreeUniqueActivesExpectedParams);
}
expected_activity[0][kEventsOccurredMetricReportId] = {
{1, {false, true, true, true, true}},
{7, {false, true, true, true, true}}};
expected_activity[1][kEventsOccurredMetricReportId] = {
{1, {false, false, false, false, false}},
{7, {false, true, true, true, true}}};
expected_activity[2][kEventsOccurredMetricReportId] = {
{1, {false, false, false, false, false}},
{7, {false, true, true, true, true}}};
expected_activity[3][kEventsOccurredMetricReportId] = {
{1, {false, true, false, false, false}},
{7, {false, true, true, true, true}}};
expected_activity[4][kEventsOccurredMetricReportId] = {
{1, {false, false, false, false, false}},
{7, {false, true, true, true, true}}};
expected_activity[5][kEventsOccurredMetricReportId] = {
{1, {false, false, false, false, false}},
{7, {false, true, true, true, true}}};
expected_activity[6][kEventsOccurredMetricReportId] = {
{1, {false, true, true, false, false}},
{7, {false, true, true, true, true}}};
expected_activity[7][kEventsOccurredMetricReportId] = {
{1, {false, false, false, false, false}},
{7, {false, true, true, false, false}}};
expected_activity[8][kEventsOccurredMetricReportId] = {
{1, {false, false, false, false, false}},
{7, {false, true, true, false, false}}};
expected_activity[9][kEventsOccurredMetricReportId] = {
{1, {false, true, false, true, false}},
{7, {false, true, true, true, false}}};
for (uint32_t offset = 0; offset < num_days; offset++) {
for (uint32_t event_code = 1;
event_code < kNoiseFreeUniqueActivesExpectedParams.num_event_codes.at(
kEventsOccurredMetricReportId);
event_code++) {
if (offset % (3 * event_code) == 0) {
EXPECT_EQ(kOK,
LogUniqueActivesEvent(kEventsOccurredMetricReportId,
kStartDayIndex + offset, event_code));
}
}
// Clear the FakeObservationStore.
ResetObservationStore();
// Generate locally aggregated Observations.
EXPECT_EQ(kOK,
event_aggregator_->GenerateObservations(kStartDayIndex + offset));
// Check the generated Observations against the expectation.
EXPECT_TRUE(CheckUniqueActivesObservations(
expected_activity[offset], kNoiseFreeUniqueActivesExpectedParams,
observation_store_.get(), update_recipient_.get()));
// Garbage collect for the next day index.
EXPECT_EQ(kOK,
event_aggregator_->GarbageCollect(kStartDayIndex + offset + 1));
}
}
} // namespace logger
} // namespace cobalt