blob: 40f3e6880f558cf5fa7dec8b0d12217967fee316 [file] [log] [blame]
// Copyright 2020 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_1_1/aggregation_procedures/numeric_stat_aggregation_procedure.h"
#include <limits>
#include <memory>
#include <gtest/gtest.h>
#include "src/lib/util/datetime_util.h"
#include "src/local_aggregation_1_1/aggregation_procedures/aggregation_procedure.h"
#include "src/local_aggregation_1_1/aggregation_procedures/testing/test_aggregation_procedure.h"
#include "src/local_aggregation_1_1/local_aggregation.pb.h"
#include "src/local_aggregation_1_1/testing/test_registry.cb.h"
#include "src/logger/project_context.h"
#include "src/logger/project_context_factory.h"
#include "src/pb/observation.pb.h"
#include "src/registry/report_definition.pb.h"
namespace cobalt::local_aggregation {
using TimeInfo = util::TimeInfo;
class NumericStatAggregationProcedureTest
: public testing::TestAggregationProcedure,
public ::testing::WithParamInterface<
std::tuple<std::string, int64_t, int64_t, std::vector<int64_t>, int64_t>> {
public:
void LogIntegerSequence(const std::vector<int64_t> &values, util::TimeInfo time_info,
AggregationProcedure *procedure, ReportAggregate *aggregate) {
logger::EventRecord record = MakeEventRecord(time_info);
IntegerEvent *event = record.event()->mutable_integer_event();
for (int64_t value : values) {
event->set_value(value);
procedure->UpdateAggregate(record, aggregate);
}
event->add_event_code(1);
for (int64_t value : values) {
event->set_value(value);
procedure->UpdateAggregate(record, aggregate);
}
}
};
namespace {
struct TestCase {
std::string name;
std::vector<int64_t> sequence;
int64_t sum;
int64_t min;
int64_t max;
int64_t mean;
int64_t median;
int64_t percentile_75;
int64_t percentile_99;
};
::testing::internal::ParamGenerator<NumericStatAggregationProcedureTest::ParamType> MakeSuite(
const std::vector<TestCase> &test_cases) {
std::vector<NumericStatAggregationProcedureTest::ParamType> cases;
for (const auto &test_case : test_cases) {
cases.emplace_back(std::make_tuple(
test_case.name, kIntegerMetricUniqueDeviceNumericStatsReport7DaySumReportIndex,
kIntegerMetricHourlyValueNumericStatsSumReportIndex, test_case.sequence, test_case.sum));
cases.emplace_back(std::make_tuple(
test_case.name, kIntegerMetricUniqueDeviceNumericStatsReport7DayMinReportIndex,
kIntegerMetricHourlyValueNumericStatsMinReportIndex, test_case.sequence, test_case.min));
cases.emplace_back(std::make_tuple(
test_case.name, kIntegerMetricUniqueDeviceNumericStatsReport7DayMaxReportIndex,
kIntegerMetricHourlyValueNumericStatsMaxReportIndex, test_case.sequence, test_case.max));
cases.emplace_back(std::make_tuple(
test_case.name, kIntegerMetricUniqueDeviceNumericStatsReport7DayMeanReportIndex,
kIntegerMetricHourlyValueNumericStatsMeanReportIndex, test_case.sequence, test_case.mean));
cases.emplace_back(std::make_tuple(
test_case.name, kIntegerMetricUniqueDeviceNumericStatsReport7DayMedianReportIndex,
kIntegerMetricHourlyValueNumericStatsMedianReportIndex, test_case.sequence,
test_case.median));
cases.emplace_back(std::make_tuple(
test_case.name, kIntegerMetricUniqueDeviceNumericStatsReport7Day75thPercentileReportIndex,
kIntegerMetricHourlyValueNumericStats75thPercentileReportIndex, test_case.sequence,
test_case.percentile_75));
cases.emplace_back(std::make_tuple(
test_case.name, kIntegerMetricUniqueDeviceNumericStatsReport7Day99thPercentileReportIndex,
kIntegerMetricHourlyValueNumericStats99thPercentileReportIndex, test_case.sequence,
test_case.percentile_99));
}
return ::testing::ValuesIn(cases);
}
std::string TestName(
const ::testing::TestParamInfo<NumericStatAggregationProcedureTest::ParamType> &info) {
std::string name = std::get<0>(info.param) + "_";
switch (std::get<1>(info.param)) {
case kIntegerMetricUniqueDeviceNumericStatsReport7DaySumReportIndex:
name += "Sum";
break;
case kIntegerMetricUniqueDeviceNumericStatsReport7DayMinReportIndex:
name += "Min";
break;
case kIntegerMetricUniqueDeviceNumericStatsReport7DayMaxReportIndex:
name += "Max";
break;
case kIntegerMetricUniqueDeviceNumericStatsReport7DayMeanReportIndex:
name += "Mean";
break;
case kIntegerMetricUniqueDeviceNumericStatsReport7DayMedianReportIndex:
name += "Median";
break;
case kIntegerMetricUniqueDeviceNumericStatsReport7Day75thPercentileReportIndex:
name += "75th_Percentile";
break;
case kIntegerMetricUniqueDeviceNumericStatsReport7Day99thPercentileReportIndex:
name += "99th_Percentile";
break;
}
return name;
};
void CheckDebugString(int64_t daily_report_type, AggregationProcedure *procedure) {
switch (daily_report_type) {
case kIntegerMetricUniqueDeviceNumericStatsReport7DaySumReportIndex:
ASSERT_EQ(procedure->DebugString(), "Sum NUMERIC_STAT");
break;
case kIntegerMetricUniqueDeviceNumericStatsReport7DayMinReportIndex:
ASSERT_EQ(procedure->DebugString(), "Min NUMERIC_STAT");
break;
case kIntegerMetricUniqueDeviceNumericStatsReport7DayMaxReportIndex:
ASSERT_EQ(procedure->DebugString(), "Max NUMERIC_STAT");
break;
case kIntegerMetricUniqueDeviceNumericStatsReport7DayMeanReportIndex:
ASSERT_EQ(procedure->DebugString(), "Mean NUMERIC_STAT");
break;
case kIntegerMetricUniqueDeviceNumericStatsReport7DayMedianReportIndex:
ASSERT_EQ(procedure->DebugString(), "Median NUMERIC_STAT");
break;
case kIntegerMetricUniqueDeviceNumericStatsReport7Day75thPercentileReportIndex:
case kIntegerMetricUniqueDeviceNumericStatsReport7Day99thPercentileReportIndex:
ASSERT_EQ(procedure->DebugString(), "PercentileN NUMERIC_STAT");
break;
}
}
} // namespace
TEST_P(NumericStatAggregationProcedureTest, Generate7DayObservations) {
auto [_, daily_report_type, hourly_report_type, integer_sequence, expected_value] = GetParam();
uint32_t metric_id = kIntegerMetricMetricId;
auto procedure = GetProcedureFor(metric_id, daily_report_type);
CheckDebugString(daily_report_type, procedure.get());
ReportAggregate report_aggregate;
const uint32_t kDayIndex = 7;
uint32_t window_size = integer_sequence.size() / kDayIndex;
std::vector<int64_t> window;
for (uint32_t day = 0; day < kDayIndex; day++) {
if (day != kDayIndex - 1) {
window.assign(integer_sequence.begin() + (window_size * day),
integer_sequence.begin() + (window_size * (day + 1)));
} else {
window.assign(integer_sequence.begin() + (window_size * day), integer_sequence.end());
}
LogIntegerSequence(window, TimeInfo::FromDayIndex(day + 1), procedure.get(), &report_aggregate);
}
auto status_or_observation = GenerateObservation(util::TimeInfo::FromDayIndex(kDayIndex),
procedure.get(), &report_aggregate);
ASSERT_TRUE(status_or_observation.ok());
auto observation = std::move(status_or_observation.ValueOrDie());
auto *obs = observation.get();
ASSERT_TRUE(obs->has_integer());
const auto &integer_obs = obs->integer();
ASSERT_EQ(integer_obs.values_size(), 2);
ASSERT_EQ(integer_obs.values(0).value(), expected_value);
ASSERT_EQ(integer_obs.values(1).event_codes(0), 1);
ASSERT_EQ(integer_obs.values(1).value(), expected_value);
// Check that obsolete aggregates were cleaned up.
EXPECT_EQ(report_aggregate.daily().by_day_index().count(kDayIndex - 6), 0u);
}
TEST_P(NumericStatAggregationProcedureTest, GenerateHourlyObservations) {
auto [_, daily_report_type, hourly_report_type, integer_sequence, expected_value] = GetParam();
uint32_t metric_id = kIntegerMetricMetricId;
auto procedure = GetProcedureFor(metric_id, hourly_report_type);
CheckDebugString(daily_report_type, procedure.get());
ReportAggregate report_aggregate;
const uint32_t kHourId = 20;
for (uint32_t hour = 0; hour <= kHourId; hour += 2) {
LogIntegerSequence(integer_sequence, TimeInfo::FromHourId(hour), procedure.get(),
&report_aggregate);
}
auto status_or_observation =
GenerateObservation(util::TimeInfo::FromHourId(kHourId), procedure.get(), &report_aggregate);
ASSERT_TRUE(status_or_observation.ok());
auto observation = std::move(status_or_observation.ValueOrDie());
auto *obs = observation.get();
ASSERT_TRUE(obs->has_integer());
const auto &integer_obs = obs->integer();
ASSERT_EQ(integer_obs.values_size(), 2);
ASSERT_EQ(integer_obs.values(0).value(), expected_value);
ASSERT_EQ(integer_obs.values(1).event_codes(0), 1);
ASSERT_EQ(integer_obs.values(1).value(), expected_value);
// Check that obsolete aggregates were cleaned up.
EXPECT_EQ(report_aggregate.hourly().by_hour_id().count(kHourId), 0u);
}
INSTANTIATE_TEST_SUITE_P(Parameterized, NumericStatAggregationProcedureTest,
MakeSuite({
{.name = "Increasing",
.sequence = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10},
.sum = 55,
.min = 1,
.max = 10,
.mean = 5,
.median = 5,
.percentile_75 = 8,
.percentile_99 = 10},
{.name = "Negative",
.sequence = {-1, -2, -3, -4, -5, -6, -7, -8, -9, -10},
.sum = -55,
.min = -10,
.max = -1,
.mean = -5,
.median = -5,
.percentile_75 = -3,
.percentile_99 = -1},
{.name = "Net_Zero",
.sequence = {-4, -3, -2, -1, 0, 1, 2, 3, 4},
.sum = 0,
.min = -4,
.max = 4,
.mean = 0,
.median = 0,
.percentile_75 = 2,
.percentile_99 = 4},
{.name = "Random",
.sequence =
{
-96, -93, -90, -86, -75, -62, -58, -57, -56, -42,
-38, -33, -28, -22, -20, -5, 8, 10, 20, 21,
30, 31, 32, 52, 59, 65, 66, 67, 69, 74,
77, 84, 85, 87, 88, 89, 98,
},
.sum = 351,
.min = -96,
.max = 98,
.mean = 9,
.median = 20,
.percentile_75 = 67,
.percentile_99 = 98},
}),
TestName);
} // namespace cobalt::local_aggregation