// Copyright (C) 2017 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include "src/guardrail/StatsdStats.h"

#include <gtest/gtest.h>

#include <vector>

#include "gtest_matchers.h"
#include "src/metrics/parsing_utils/metrics_manager_util.h"
#include "src/shell/ShellSubscriber.h"
#include "src/stats_log.pb.h"
#include "statslog_statsdtest.h"
#include "tests/statsd_test_util.h"

#ifdef __ANDROID__

namespace std {
void PrintTo(const tuple<int, size_t>& atomIdDimensionLimitTuple, ostream* os) {
    *os << get<0>(atomIdDimensionLimitTuple) << "_" << get<1>(atomIdDimensionLimitTuple);
}
}  // namespace std

namespace android {
namespace os {
namespace statsd {
namespace {

using namespace testing;
using PerSubscriptionStats = StatsdStatsReport_SubscriptionStats_PerSubscriptionStats;
using std::tuple;
using std::unordered_map;
using std::vector;

class StatsdStatsTest_GetAtomDimensionKeySizeLimit_InMap
    : public TestWithParam<tuple<int, size_t>> {};
INSTANTIATE_TEST_SUITE_P(StatsdStatsTest_GetAtomDimensionKeySizeLimit_InMap,
                         StatsdStatsTest_GetAtomDimensionKeySizeLimit_InMap,
                         Combine(Values(10022 /* BINDER_CALLS */, 10024 /* LOOPER_STATS */,
                                        10010 /* CPU_TIME_PER_UID_FREQ */),
                                 Values(-1, 0, 500, 800, 1000, 3000, 3300)),
                         PrintToStringParamName());

class StatsdStatsTest_GetAtomDimensionKeySizeLimit_NotInMap
    : public StatsdStatsTest_GetAtomDimensionKeySizeLimit_InMap {};

INSTANTIATE_TEST_SUITE_P(StatsdStatsTest_GetAtomDimensionKeySizeLimit_NotInMap,
                         StatsdStatsTest_GetAtomDimensionKeySizeLimit_NotInMap,
                         Combine(Values(util::TEST_ATOM_REPORTED, util::SCREEN_STATE_CHANGED,
                                        util::SUBSYSTEM_SLEEP_STATE),
                                 Values(-1, 0, 500, 800, 1000, 3000, 3300)),
                         PrintToStringParamName());

}  // anonymous namespace

TEST(StatsdStatsTest, TestValidConfigAdd) {
    StatsdStats stats;
    ConfigKey key(0, 12345);
    const int metricsCount = 10;
    const int conditionsCount = 20;
    const int matchersCount = 30;
    const int alertsCount = 10;
    stats.noteConfigReceived(key, metricsCount, conditionsCount, matchersCount, alertsCount, {},
                             nullopt /*valid config*/);

    StatsdStatsReport report = getStatsdStatsReport(stats, /* reset stats */ false);
    ASSERT_EQ(1, report.config_stats_size());
    const auto& configReport = report.config_stats(0);
    EXPECT_EQ(0, configReport.uid());
    EXPECT_EQ(12345, configReport.id());
    EXPECT_EQ(metricsCount, configReport.metric_count());
    EXPECT_EQ(conditionsCount, configReport.condition_count());
    EXPECT_EQ(matchersCount, configReport.matcher_count());
    EXPECT_EQ(alertsCount, configReport.alert_count());
    EXPECT_EQ(true, configReport.is_valid());
    EXPECT_FALSE(configReport.has_invalid_config_reason());
    EXPECT_FALSE(configReport.has_deletion_time_sec());
}

TEST(StatsdStatsTest, TestConfigMetadataProviderPromotionFailed) {
    StatsdStats stats;
    ConfigKey key(0, 12345);
    stats.noteConfigReceived(key, /*metricsCount=*/0, /*conditionsCount=*/0, /*matchersCount=*/0,
                             /*alertCount=*/0, /*annotations=*/{}, nullopt /*valid config*/);

    stats.noteConfigMetadataProviderPromotionFailed(key);

    StatsdStatsReport report = getStatsdStatsReport(stats, /* reset stats */ false);
    ASSERT_EQ(1, report.config_stats_size());
    const auto& configReport = report.config_stats(0);
    EXPECT_EQ(1, configReport.config_metadata_provider_promotion_failed());
}

TEST(StatsdStatsTest, TestInvalidConfigAdd) {
    StatsdStats stats;
    ConfigKey key(0, 12345);
    const int metricsCount = 10;
    const int conditionsCount = 20;
    const int matchersCount = 30;
    const int alertsCount = 10;
    optional<InvalidConfigReason> invalidConfigReason =
            InvalidConfigReason(INVALID_CONFIG_REASON_UNKNOWN, 1);
    invalidConfigReason->stateId = 2;
    invalidConfigReason->alertId = 3;
    invalidConfigReason->alarmId = 4;
    invalidConfigReason->subscriptionId = 5;
    invalidConfigReason->matcherIds.push_back(6);
    invalidConfigReason->matcherIds.push_back(7);
    invalidConfigReason->conditionIds.push_back(8);
    invalidConfigReason->conditionIds.push_back(9);
    invalidConfigReason->conditionIds.push_back(10);
    stats.noteConfigReceived(key, metricsCount, conditionsCount, matchersCount, alertsCount, {},
                             invalidConfigReason /*bad config*/);

    StatsdStatsReport report = getStatsdStatsReport(stats, /* reset stats */ false);
    ASSERT_EQ(1, report.config_stats_size());
    const auto& configReport = report.config_stats(0);
    // The invalid config should be put into icebox with a deletion time.
    EXPECT_TRUE(configReport.has_deletion_time_sec());
    EXPECT_TRUE(configReport.has_invalid_config_reason());
    EXPECT_EQ(configReport.invalid_config_reason().reason(), INVALID_CONFIG_REASON_UNKNOWN);
    EXPECT_EQ(configReport.invalid_config_reason().metric_id(), 1);
    EXPECT_EQ(configReport.invalid_config_reason().state_id(), 2);
    EXPECT_EQ(configReport.invalid_config_reason().alert_id(), 3);
    EXPECT_EQ(configReport.invalid_config_reason().alarm_id(), 4);
    EXPECT_EQ(configReport.invalid_config_reason().subscription_id(), 5);
    EXPECT_EQ(configReport.invalid_config_reason().matcher_id_size(), 2);
    EXPECT_EQ(configReport.invalid_config_reason().matcher_id(0), 6);
    EXPECT_EQ(configReport.invalid_config_reason().matcher_id(1), 7);
    EXPECT_EQ(configReport.invalid_config_reason().condition_id_size(), 3);
    EXPECT_EQ(configReport.invalid_config_reason().condition_id(0), 8);
    EXPECT_EQ(configReport.invalid_config_reason().condition_id(1), 9);
    EXPECT_EQ(configReport.invalid_config_reason().condition_id(2), 10);
}

TEST(StatsdStatsTest, TestInvalidConfigMissingMetricId) {
    StatsdStats stats;
    ConfigKey key(0, 12345);
    const int metricsCount = 10;
    const int conditionsCount = 20;
    const int matchersCount = 30;
    const int alertsCount = 10;
    optional<InvalidConfigReason> invalidConfigReason =
            InvalidConfigReason(INVALID_CONFIG_REASON_SUBSCRIPTION_SUBSCRIBER_INFO_MISSING);
    invalidConfigReason->stateId = 1;
    invalidConfigReason->alertId = 2;
    invalidConfigReason->alarmId = 3;
    invalidConfigReason->subscriptionId = 4;
    invalidConfigReason->matcherIds.push_back(5);
    invalidConfigReason->conditionIds.push_back(6);
    invalidConfigReason->conditionIds.push_back(7);
    stats.noteConfigReceived(key, metricsCount, conditionsCount, matchersCount, alertsCount, {},
                             invalidConfigReason /*bad config*/);

    StatsdStatsReport report = getStatsdStatsReport(stats, /* reset stats */ false);
    ASSERT_EQ(1, report.config_stats_size());
    const auto& configReport = report.config_stats(0);
    // The invalid config should be put into icebox with a deletion time.
    EXPECT_TRUE(configReport.has_deletion_time_sec());
    EXPECT_TRUE(configReport.has_invalid_config_reason());
    EXPECT_EQ(configReport.invalid_config_reason().reason(),
              INVALID_CONFIG_REASON_SUBSCRIPTION_SUBSCRIBER_INFO_MISSING);
    EXPECT_FALSE(configReport.invalid_config_reason().has_metric_id());
    EXPECT_EQ(configReport.invalid_config_reason().state_id(), 1);
    EXPECT_EQ(configReport.invalid_config_reason().alert_id(), 2);
    EXPECT_EQ(configReport.invalid_config_reason().alarm_id(), 3);
    EXPECT_EQ(configReport.invalid_config_reason().subscription_id(), 4);
    EXPECT_EQ(configReport.invalid_config_reason().matcher_id_size(), 1);
    EXPECT_EQ(configReport.invalid_config_reason().matcher_id(0), 5);
    EXPECT_EQ(configReport.invalid_config_reason().condition_id_size(), 2);
    EXPECT_EQ(configReport.invalid_config_reason().condition_id(0), 6);
    EXPECT_EQ(configReport.invalid_config_reason().condition_id(1), 7);
}

TEST(StatsdStatsTest, TestInvalidConfigOnlyMetricId) {
    StatsdStats stats;
    ConfigKey key(0, 12345);
    const int metricsCount = 10;
    const int conditionsCount = 20;
    const int matchersCount = 30;
    const int alertsCount = 10;
    optional<InvalidConfigReason> invalidConfigReason =
            InvalidConfigReason(INVALID_CONFIG_REASON_METRIC_NOT_IN_PREV_CONFIG, 1);
    stats.noteConfigReceived(key, metricsCount, conditionsCount, matchersCount, alertsCount, {},
                             invalidConfigReason /*bad config*/);

    StatsdStatsReport report = getStatsdStatsReport(stats, /* reset stats */ false);
    ASSERT_EQ(1, report.config_stats_size());
    const auto& configReport = report.config_stats(0);
    // The invalid config should be put into icebox with a deletion time.
    EXPECT_TRUE(configReport.has_deletion_time_sec());
    EXPECT_TRUE(configReport.has_invalid_config_reason());
    EXPECT_EQ(configReport.invalid_config_reason().reason(),
              INVALID_CONFIG_REASON_METRIC_NOT_IN_PREV_CONFIG);
    EXPECT_EQ(configReport.invalid_config_reason().metric_id(), 1);
    EXPECT_FALSE(configReport.invalid_config_reason().has_state_id());
    EXPECT_FALSE(configReport.invalid_config_reason().has_alert_id());
    EXPECT_FALSE(configReport.invalid_config_reason().has_alarm_id());
    EXPECT_FALSE(configReport.invalid_config_reason().has_subscription_id());
    EXPECT_EQ(configReport.invalid_config_reason().matcher_id_size(), 0);
    EXPECT_EQ(configReport.invalid_config_reason().condition_id_size(), 0);
}

TEST(StatsdStatsTest, TestConfigRemove) {
    StatsdStats stats;
    ConfigKey key(0, 12345);
    const int metricsCount = 10;
    const int conditionsCount = 20;
    const int matchersCount = 30;
    const int alertsCount = 10;
    stats.noteConfigReceived(key, metricsCount, conditionsCount, matchersCount, alertsCount, {},
                             nullopt);

    StatsdStatsReport report = getStatsdStatsReport(stats, /* reset stats */ false);
    ASSERT_EQ(1, report.config_stats_size());
    const auto& configReport = report.config_stats(0);
    EXPECT_FALSE(configReport.has_deletion_time_sec());

    stats.noteConfigRemoved(key);

    report = getStatsdStatsReport(stats, /* reset stats */ false);
    ASSERT_EQ(1, report.config_stats_size());
    const auto& configReport2 = report.config_stats(0);
    EXPECT_TRUE(configReport2.has_deletion_time_sec());
}

TEST(StatsdStatsTest, TestSubStats) {
    StatsdStats stats;
    ConfigKey key(0, 12345);
    stats.noteConfigReceived(key, 2, 3, 4, 5, {std::make_pair(123, 456)}, nullopt);

    stats.noteMatcherMatched(key, StringToId("matcher1"));
    stats.noteMatcherMatched(key, StringToId("matcher1"));
    stats.noteMatcherMatched(key, StringToId("matcher2"));

    stats.noteConditionDimensionSize(key, StringToId("condition1"), 250);
    stats.noteConditionDimensionSize(key, StringToId("condition1"), 240);

    stats.noteMetricDimensionSize(key, StringToId("metric1"), 201);
    stats.noteMetricDimensionSize(key, StringToId("metric1"), 202);

    stats.noteAnomalyDeclared(key, StringToId("alert1"));
    stats.noteAnomalyDeclared(key, StringToId("alert1"));
    stats.noteAnomalyDeclared(key, StringToId("alert2"));

    // broadcast-> 2
    stats.noteBroadcastSent(key);
    stats.noteBroadcastSent(key);

    // data drop -> 1
    stats.noteDataDropped(key, 123);

    // dump report -> 3
    stats.noteMetricsReportSent(key, 0, 1);
    stats.noteMetricsReportSent(key, 0, 2);
    stats.noteMetricsReportSent(key, 0, 3);

    // activation_time_sec -> 2
    stats.noteActiveStatusChanged(key, true);
    stats.noteActiveStatusChanged(key, true);

    // deactivation_time_sec -> 1
    stats.noteActiveStatusChanged(key, false);

    StatsdStatsReport report = getStatsdStatsReport(stats, /* reset stats */ true);
    ASSERT_EQ(1, report.config_stats_size());
    const auto& configReport = report.config_stats(0);
    ASSERT_EQ(2, configReport.broadcast_sent_time_sec_size());
    ASSERT_EQ(1, configReport.data_drop_time_sec_size());
    ASSERT_EQ(1, configReport.data_drop_bytes_size());
    EXPECT_EQ(123, configReport.data_drop_bytes(0));
    ASSERT_EQ(3, configReport.dump_report_time_sec_size());
    ASSERT_EQ(3, configReport.dump_report_data_size_size());
    ASSERT_EQ(3, configReport.dump_report_number_size());
    EXPECT_EQ(1, configReport.dump_report_number(0));
    EXPECT_EQ(2, configReport.dump_report_number(1));
    EXPECT_EQ(3, configReport.dump_report_number(2));
    ASSERT_EQ(2, configReport.activation_time_sec_size());
    ASSERT_EQ(1, configReport.deactivation_time_sec_size());
    ASSERT_EQ(1, configReport.annotation_size());
    EXPECT_EQ(123, configReport.annotation(0).field_int64());
    EXPECT_EQ(456, configReport.annotation(0).field_int32());

    ASSERT_EQ(2, configReport.matcher_stats_size());
    // matcher1 is the first in the list
    if (configReport.matcher_stats(0).id() == StringToId("matcher1")) {
        EXPECT_EQ(2, configReport.matcher_stats(0).matched_times());
        EXPECT_EQ(1, configReport.matcher_stats(1).matched_times());
        EXPECT_EQ(StringToId("matcher2"), configReport.matcher_stats(1).id());
    } else {
        // matcher1 is the second in the list.
        EXPECT_EQ(1, configReport.matcher_stats(0).matched_times());
        EXPECT_EQ(StringToId("matcher2"), configReport.matcher_stats(0).id());

        EXPECT_EQ(2, configReport.matcher_stats(1).matched_times());
        EXPECT_EQ(StringToId("matcher1"), configReport.matcher_stats(1).id());
    }

    ASSERT_EQ(2, configReport.alert_stats_size());
    bool alert1first = configReport.alert_stats(0).id() == StringToId("alert1");
    EXPECT_EQ(StringToId("alert1"), configReport.alert_stats(alert1first ? 0 : 1).id());
    EXPECT_EQ(2, configReport.alert_stats(alert1first ? 0 : 1).alerted_times());
    EXPECT_EQ(StringToId("alert2"), configReport.alert_stats(alert1first ? 1 : 0).id());
    EXPECT_EQ(1, configReport.alert_stats(alert1first ? 1 : 0).alerted_times());

    ASSERT_EQ(1, configReport.condition_stats_size());
    EXPECT_EQ(StringToId("condition1"), configReport.condition_stats(0).id());
    EXPECT_EQ(250, configReport.condition_stats(0).max_tuple_counts());

    ASSERT_EQ(1, configReport.metric_stats_size());
    EXPECT_EQ(StringToId("metric1"), configReport.metric_stats(0).id());
    EXPECT_EQ(202, configReport.metric_stats(0).max_tuple_counts());

    // after resetting the stats, some new events come
    stats.noteMatcherMatched(key, StringToId("matcher99"));
    stats.noteConditionDimensionSize(key, StringToId("condition99"), 300);
    stats.noteMetricDimensionSize(key, StringToId("metric99tion99"), 270);
    stats.noteAnomalyDeclared(key, StringToId("alert99"));

    // now the config stats should only contain the stats about the new event.
    report = getStatsdStatsReport(stats, /* reset stats */ false);
    ASSERT_EQ(1, report.config_stats_size());
    const auto& configReport2 = report.config_stats(0);
    ASSERT_EQ(1, configReport2.matcher_stats_size());
    EXPECT_EQ(StringToId("matcher99"), configReport2.matcher_stats(0).id());
    EXPECT_EQ(1, configReport2.matcher_stats(0).matched_times());

    ASSERT_EQ(1, configReport2.condition_stats_size());
    EXPECT_EQ(StringToId("condition99"), configReport2.condition_stats(0).id());
    EXPECT_EQ(300, configReport2.condition_stats(0).max_tuple_counts());

    ASSERT_EQ(1, configReport2.metric_stats_size());
    EXPECT_EQ(StringToId("metric99tion99"), configReport2.metric_stats(0).id());
    EXPECT_EQ(270, configReport2.metric_stats(0).max_tuple_counts());

    ASSERT_EQ(1, configReport2.alert_stats_size());
    EXPECT_EQ(StringToId("alert99"), configReport2.alert_stats(0).id());
    EXPECT_EQ(1, configReport2.alert_stats(0).alerted_times());
}

TEST(StatsdStatsTest, TestAtomLog) {
    StatsdStats stats;
    time_t now = time(nullptr);
    // old event, we get it from the stats buffer. should be ignored.
    stats.noteAtomLogged(util::SENSOR_STATE_CHANGED, 1000, false);

    stats.noteAtomLogged(util::SENSOR_STATE_CHANGED, now + 1, false);
    stats.noteAtomLogged(util::SENSOR_STATE_CHANGED, now + 2, false);
    stats.noteAtomLogged(util::APP_CRASH_OCCURRED, now + 3, false);

    StatsdStatsReport report = getStatsdStatsReport(stats, /* reset stats */ false);
    ASSERT_EQ(2, report.atom_stats_size());
    bool sensorAtomGood = false;
    bool dropboxAtomGood = false;

    for (const auto& atomStats : report.atom_stats()) {
        if (atomStats.tag() == util::SENSOR_STATE_CHANGED && atomStats.count() == 3) {
            sensorAtomGood = true;
        }
        if (atomStats.tag() == util::APP_CRASH_OCCURRED && atomStats.count() == 1) {
            dropboxAtomGood = true;
        }
        EXPECT_FALSE(atomStats.has_dropped_count());
        EXPECT_FALSE(atomStats.has_skip_count());
    }

    EXPECT_TRUE(dropboxAtomGood);
    EXPECT_TRUE(sensorAtomGood);
}

TEST(StatsdStatsTest, TestNonPlatformAtomLog) {
    StatsdStats stats;
    time_t now = time(nullptr);
    int newAtom1 = StatsdStats::kMaxPushedAtomId + 1;
    int newAtom2 = StatsdStats::kMaxPushedAtomId + 2;

    stats.noteAtomLogged(newAtom1, now + 1, false);
    stats.noteAtomLogged(newAtom1, now + 2, false);
    stats.noteAtomLogged(newAtom2, now + 3, false);

    StatsdStatsReport report = getStatsdStatsReport(stats, /* reset stats */ false);
    ASSERT_EQ(2, report.atom_stats_size());
    bool newAtom1Good = false;
    bool newAtom2Good = false;

    for (const auto& atomStats : report.atom_stats()) {
        if (atomStats.tag() == newAtom1 && atomStats.count() == 2) {
            newAtom1Good = true;
        }
        if (atomStats.tag() == newAtom2 && atomStats.count() == 1) {
            newAtom2Good = true;
        }
        EXPECT_FALSE(atomStats.has_dropped_count());
        EXPECT_FALSE(atomStats.has_skip_count());
    }

    EXPECT_TRUE(newAtom1Good);
    EXPECT_TRUE(newAtom2Good);
}

TEST(StatsdStatsTest, TestPullAtomStats) {
    StatsdStats stats;

    stats.updateMinPullIntervalSec(util::DISK_SPACE, 3333L);
    stats.updateMinPullIntervalSec(util::DISK_SPACE, 2222L);
    stats.updateMinPullIntervalSec(util::DISK_SPACE, 4444L);

    stats.notePull(util::DISK_SPACE);
    stats.notePullTime(util::DISK_SPACE, 1111L);
    stats.notePullDelay(util::DISK_SPACE, 1111L);
    stats.notePull(util::DISK_SPACE);
    stats.notePullTime(util::DISK_SPACE, 3333L);
    stats.notePullDelay(util::DISK_SPACE, 3335L);
    stats.notePull(util::DISK_SPACE);
    stats.notePullFromCache(util::DISK_SPACE);
    stats.notePullerCallbackRegistrationChanged(util::DISK_SPACE, true);
    stats.notePullerCallbackRegistrationChanged(util::DISK_SPACE, false);
    stats.notePullerCallbackRegistrationChanged(util::DISK_SPACE, true);
    stats.notePullBinderCallFailed(util::DISK_SPACE);
    stats.notePullUidProviderNotFound(util::DISK_SPACE);
    stats.notePullerNotFound(util::DISK_SPACE);
    stats.notePullerNotFound(util::DISK_SPACE);
    stats.notePullTimeout(util::DISK_SPACE, 3000L, 6000L);
    stats.notePullTimeout(util::DISK_SPACE, 4000L, 7000L);

    StatsdStatsReport report = getStatsdStatsReport(stats, /* reset stats */ false);
    ASSERT_EQ(1, report.pulled_atom_stats_size());

    EXPECT_EQ(util::DISK_SPACE, report.pulled_atom_stats(0).atom_id());
    EXPECT_EQ(3, report.pulled_atom_stats(0).total_pull());
    EXPECT_EQ(1, report.pulled_atom_stats(0).total_pull_from_cache());
    EXPECT_EQ(2222L, report.pulled_atom_stats(0).min_pull_interval_sec());
    EXPECT_EQ(2222L, report.pulled_atom_stats(0).average_pull_time_nanos());
    EXPECT_EQ(3333L, report.pulled_atom_stats(0).max_pull_time_nanos());
    EXPECT_EQ(2223L, report.pulled_atom_stats(0).average_pull_delay_nanos());
    EXPECT_EQ(3335L, report.pulled_atom_stats(0).max_pull_delay_nanos());
    EXPECT_EQ(2L, report.pulled_atom_stats(0).registered_count());
    EXPECT_EQ(1L, report.pulled_atom_stats(0).unregistered_count());
    EXPECT_EQ(1L, report.pulled_atom_stats(0).binder_call_failed());
    EXPECT_EQ(1L, report.pulled_atom_stats(0).failed_uid_provider_not_found());
    EXPECT_EQ(2L, report.pulled_atom_stats(0).puller_not_found());
    ASSERT_EQ(2, report.pulled_atom_stats(0).pull_atom_metadata_size());
    EXPECT_EQ(3000L, report.pulled_atom_stats(0).pull_atom_metadata(0).pull_timeout_uptime_millis());
    EXPECT_EQ(4000L, report.pulled_atom_stats(0).pull_atom_metadata(1).pull_timeout_uptime_millis());
    EXPECT_EQ(6000L, report.pulled_atom_stats(0).pull_atom_metadata(0)
            .pull_timeout_elapsed_millis());
    EXPECT_EQ(7000L, report.pulled_atom_stats(0).pull_atom_metadata(1)
            .pull_timeout_elapsed_millis());
}

TEST(StatsdStatsTest, TestAtomMetricsStats) {
    StatsdStats stats;
    time_t now = time(nullptr);
    // old event, we get it from the stats buffer. should be ignored.
    stats.noteBucketDropped(10000000000LL);

    stats.noteBucketBoundaryDelayNs(10000000000LL, -1L);
    stats.noteBucketBoundaryDelayNs(10000000000LL, -10L);
    stats.noteBucketBoundaryDelayNs(10000000000LL, 2L);

    stats.noteBucketBoundaryDelayNs(10000000001LL, 1L);

    StatsdStatsReport report = getStatsdStatsReport(stats, /* reset stats */ false);
    ASSERT_EQ(2, report.atom_metric_stats().size());

    auto atomStats = report.atom_metric_stats(0);
    EXPECT_EQ(10000000000LL, atomStats.metric_id());
    EXPECT_EQ(1L, atomStats.bucket_dropped());
    EXPECT_EQ(-10L, atomStats.min_bucket_boundary_delay_ns());
    EXPECT_EQ(2L, atomStats.max_bucket_boundary_delay_ns());

    auto atomStats2 = report.atom_metric_stats(1);
    EXPECT_EQ(10000000001LL, atomStats2.metric_id());
    EXPECT_EQ(0L, atomStats2.bucket_dropped());
    EXPECT_EQ(0L, atomStats2.min_bucket_boundary_delay_ns());
    EXPECT_EQ(1L, atomStats2.max_bucket_boundary_delay_ns());
}

TEST(StatsdStatsTest, TestRestrictedMetricsStats) {
    StatsdStats stats;
    const int64_t metricId = -1234556L;
    ConfigKey key(0, 12345);
    stats.noteConfigReceived(key, 2, 3, 4, 5, {}, nullopt);
    stats.noteRestrictedMetricInsertError(key, metricId);
    stats.noteRestrictedMetricTableCreationError(key, metricId);
    stats.noteRestrictedMetricTableDeletionError(key, metricId);
    stats.noteDeviceInfoTableCreationFailed(key);
    stats.noteRestrictedMetricFlushLatency(key, metricId, 3000);
    stats.noteRestrictedMetricFlushLatency(key, metricId, 3001);
    stats.noteRestrictedMetricCategoryChanged(key, metricId);
    stats.noteRestrictedConfigFlushLatency(key, 4000);
    ConfigKey configKeyWithoutError(0, 666);
    stats.noteConfigReceived(configKeyWithoutError, 2, 3, 4, 5, {}, nullopt);
    stats.noteDbCorrupted(key);
    stats.noteDbCorrupted(key);
    stats.noteDbSizeExceeded(key);
    stats.noteDbStatFailed(key);
    stats.noteDbConfigInvalid(key);
    stats.noteDbTooOld(key);
    stats.noteDbDeletionConfigRemoved(key);
    stats.noteDbDeletionConfigUpdated(key);
    stats.noteRestrictedConfigDbSize(key, 999, 111);

    StatsdStatsReport report = getStatsdStatsReport(stats, /* reset stats */ false);
    ASSERT_EQ(2, report.config_stats().size());
    ASSERT_EQ(0, report.config_stats(0).restricted_metric_stats().size());
    ASSERT_EQ(1, report.config_stats(1).restricted_metric_stats().size());
    EXPECT_EQ(1, report.config_stats(1).restricted_metric_stats(0).insert_error());
    EXPECT_EQ(1, report.config_stats(1).restricted_metric_stats(0).table_creation_error());
    EXPECT_EQ(1, report.config_stats(1).restricted_metric_stats(0).table_deletion_error());
    EXPECT_EQ(1, report.config_stats(1).restricted_metric_stats(0).category_changed_count());
    ASSERT_EQ(2, report.config_stats(1).restricted_metric_stats(0).flush_latency_ns().size());
    EXPECT_EQ(3000, report.config_stats(1).restricted_metric_stats(0).flush_latency_ns(0));
    EXPECT_EQ(3001, report.config_stats(1).restricted_metric_stats(0).flush_latency_ns(1));
    ASSERT_EQ(1, report.config_stats(1).restricted_db_size_time_sec().size());
    EXPECT_EQ(999, report.config_stats(1).restricted_db_size_time_sec(0));
    ASSERT_EQ(1, report.config_stats(1).restricted_db_size_bytes().size());
    EXPECT_EQ(111, report.config_stats(1).restricted_db_size_bytes(0));
    ASSERT_EQ(1, report.config_stats(1).restricted_flush_latency().size());
    EXPECT_EQ(4000, report.config_stats(1).restricted_flush_latency(0));
    EXPECT_TRUE(report.config_stats(1).device_info_table_creation_failed());
    EXPECT_EQ(metricId, report.config_stats(1).restricted_metric_stats(0).restricted_metric_id());
    EXPECT_EQ(2, report.config_stats(1).restricted_db_corrupted_count());
    EXPECT_EQ(1, report.config_stats(1).db_deletion_stat_failed());
    EXPECT_EQ(1, report.config_stats(1).db_deletion_size_exceeded_limit());
    EXPECT_EQ(1, report.config_stats(1).db_deletion_config_invalid());
    EXPECT_EQ(1, report.config_stats(1).db_deletion_too_old());
    EXPECT_EQ(1, report.config_stats(1).db_deletion_config_removed());
    EXPECT_EQ(1, report.config_stats(1).db_deletion_config_updated());
}

TEST(StatsdStatsTest, TestRestrictedMetricsQueryStats) {
    StatsdStats stats;
    const int32_t callingUid = 100;
    ConfigKey configKey(0, 12345);
    const string configPackage = "com.google.android.gm";
    int64_t beforeNoteMetricSucceed = getWallClockNs();
    stats.noteQueryRestrictedMetricSucceed(configKey.GetId(), configPackage, configKey.GetUid(),
                                           callingUid, /*queryLatencyNs=*/5 * NS_PER_SEC);
    int64_t afterNoteMetricSucceed = getWallClockNs();

    const int64_t configIdWithError = 111;
    stats.noteQueryRestrictedMetricFailed(configIdWithError, configPackage, std::nullopt,
                                          callingUid, InvalidQueryReason(AMBIGUOUS_CONFIG_KEY));
    stats.noteQueryRestrictedMetricFailed(configIdWithError, configPackage, std::nullopt,
                                          callingUid, InvalidQueryReason(AMBIGUOUS_CONFIG_KEY),
                                          "error_message");

    StatsdStatsReport report = getStatsdStatsReport(stats, /* reset stats */ false);
    ASSERT_EQ(3, report.restricted_metric_query_stats().size());
    EXPECT_EQ(configKey.GetId(), report.restricted_metric_query_stats(0).config_id());
    EXPECT_EQ(configKey.GetUid(), report.restricted_metric_query_stats(0).config_uid());
    EXPECT_EQ(callingUid, report.restricted_metric_query_stats(0).calling_uid());
    EXPECT_EQ(configPackage, report.restricted_metric_query_stats(0).config_package());
    EXPECT_FALSE(report.restricted_metric_query_stats(0).has_query_error());
    EXPECT_LT(beforeNoteMetricSucceed,
              report.restricted_metric_query_stats(0).query_wall_time_ns());
    EXPECT_GT(afterNoteMetricSucceed, report.restricted_metric_query_stats(0).query_wall_time_ns());
    EXPECT_EQ(5 * NS_PER_SEC, report.restricted_metric_query_stats(0).query_latency_ns());
    EXPECT_EQ(configIdWithError, report.restricted_metric_query_stats(1).config_id());
    EXPECT_EQ(AMBIGUOUS_CONFIG_KEY, report.restricted_metric_query_stats(1).invalid_query_reason());
    EXPECT_EQ(false, report.restricted_metric_query_stats(1).has_config_uid());
    EXPECT_FALSE(report.restricted_metric_query_stats(1).has_query_error());
    EXPECT_FALSE(report.restricted_metric_query_stats(1).has_query_latency_ns());
    EXPECT_EQ("error_message", report.restricted_metric_query_stats(2).query_error());
    EXPECT_FALSE(report.restricted_metric_query_stats(2).has_query_latency_ns());
    EXPECT_NE(report.restricted_metric_query_stats(1).query_wall_time_ns(),
              report.restricted_metric_query_stats(0).query_wall_time_ns());
}

TEST(StatsdStatsTest, TestAnomalyMonitor) {
    StatsdStats stats;
    stats.noteRegisteredAnomalyAlarmChanged();
    stats.noteRegisteredAnomalyAlarmChanged();

    StatsdStatsReport report = getStatsdStatsReport(stats, /* reset stats */ false);
    EXPECT_EQ(2, report.anomaly_alarm_stats().alarms_registered());
}

TEST(StatsdStatsTest, TestTimestampThreshold) {
    StatsdStats stats;
    vector<int32_t> timestamps;
    for (int i = 0; i < StatsdStats::kMaxTimestampCount; i++) {
        timestamps.push_back(i);
    }
    ConfigKey key(0, 12345);
    stats.noteConfigReceived(key, 2, 3, 4, 5, {}, nullopt);

    for (int i = 0; i < StatsdStats::kMaxTimestampCount; i++) {
        stats.noteDataDropped(key, timestamps[i]);
        stats.noteBroadcastSent(key, timestamps[i]);
        stats.noteMetricsReportSent(key, 0, timestamps[i], i + 1);
        stats.noteActiveStatusChanged(key, true, timestamps[i]);
        stats.noteActiveStatusChanged(key, false, timestamps[i]);
    }

    int32_t newTimestamp = 10000;

    // now it should trigger removing oldest timestamp
    stats.noteDataDropped(key, 123, 10000);
    stats.noteBroadcastSent(key, 10000);
    stats.noteMetricsReportSent(key, 0, 10000, 21);
    stats.noteActiveStatusChanged(key, true, 10000);
    stats.noteActiveStatusChanged(key, false, 10000);

    EXPECT_TRUE(stats.mConfigStats.find(key) != stats.mConfigStats.end());
    const auto& configStats = stats.mConfigStats[key];

    size_t maxCount = StatsdStats::kMaxTimestampCount;
    ASSERT_EQ(maxCount, configStats->broadcast_sent_time_sec.size());
    ASSERT_EQ(maxCount, configStats->data_drop_time_sec.size());
    ASSERT_EQ(maxCount, configStats->dump_report_stats.size());
    ASSERT_EQ(maxCount, configStats->activation_time_sec.size());
    ASSERT_EQ(maxCount, configStats->deactivation_time_sec.size());

    // the oldest timestamp is the second timestamp in history
    EXPECT_EQ(1, configStats->broadcast_sent_time_sec.front());
    EXPECT_EQ(1, configStats->data_drop_bytes.front());
    EXPECT_EQ(1, configStats->dump_report_stats.front().mDumpReportTimeSec);
    EXPECT_EQ(1, configStats->activation_time_sec.front());
    EXPECT_EQ(1, configStats->deactivation_time_sec.front());

    // the last timestamp is the newest timestamp.
    EXPECT_EQ(newTimestamp, configStats->broadcast_sent_time_sec.back());
    EXPECT_EQ(newTimestamp, configStats->data_drop_time_sec.back());
    EXPECT_EQ(123, configStats->data_drop_bytes.back());
    EXPECT_EQ(newTimestamp, configStats->dump_report_stats.back().mDumpReportTimeSec);
    EXPECT_EQ(newTimestamp, configStats->activation_time_sec.back());
    EXPECT_EQ(newTimestamp, configStats->deactivation_time_sec.back());
}

TEST(StatsdStatsTest, TestSystemServerCrash) {
    StatsdStats stats;
    vector<int32_t> timestamps;
    for (int i = 0; i < StatsdStats::kMaxSystemServerRestarts; i++) {
        timestamps.push_back(i);
        stats.noteSystemServerRestart(timestamps[i]);
    }

    StatsdStatsReport report = getStatsdStatsReport(stats, /* reset stats */ false);
    const int maxCount = StatsdStats::kMaxSystemServerRestarts;
    ASSERT_EQ(maxCount, (int)report.system_restart_sec_size());

    stats.noteSystemServerRestart(StatsdStats::kMaxSystemServerRestarts + 1);

    report = getStatsdStatsReport(stats, /* reset stats */ false);
    ASSERT_EQ(maxCount, (int)report.system_restart_sec_size());
    EXPECT_EQ(StatsdStats::kMaxSystemServerRestarts + 1, report.system_restart_sec(maxCount - 1));
}

TEST(StatsdStatsTest, TestActivationBroadcastGuardrailHit) {
    StatsdStats stats;
    int uid1 = 1;
    int uid2 = 2;
    stats.noteActivationBroadcastGuardrailHit(uid1, 10);
    stats.noteActivationBroadcastGuardrailHit(uid1, 20);

    // Test that we only keep 20 timestamps.
    for (int i = 0; i < 100; i++) {
        stats.noteActivationBroadcastGuardrailHit(uid2, i);
    }

    StatsdStatsReport report = getStatsdStatsReport(stats, /* reset stats */ false);
    ASSERT_EQ(2, report.activation_guardrail_stats_size());
    bool uid1Good = false;
    bool uid2Good = false;
    for (const auto& guardrailTimes : report.activation_guardrail_stats()) {
        if (uid1 == guardrailTimes.uid()) {
            uid1Good = true;
            ASSERT_EQ(2, guardrailTimes.guardrail_met_sec_size());
            EXPECT_EQ(10, guardrailTimes.guardrail_met_sec(0));
            EXPECT_EQ(20, guardrailTimes.guardrail_met_sec(1));
        } else if (uid2 == guardrailTimes.uid()) {
            int maxCount = StatsdStats::kMaxTimestampCount;
            uid2Good = true;
            ASSERT_EQ(maxCount, guardrailTimes.guardrail_met_sec_size());
            for (int i = 0; i < maxCount; i++) {
                EXPECT_EQ(100 - maxCount + i, guardrailTimes.guardrail_met_sec(i));
            }
        } else {
            FAIL() << "Unexpected uid.";
        }
    }
    EXPECT_TRUE(uid1Good);
    EXPECT_TRUE(uid2Good);
}

TEST(StatsdStatsTest, TestAtomErrorStats) {
    StatsdStats stats;

    int pushAtomTag = 100;
    int pullAtomTag = 1000;
    int numErrors = 10;

    for (int i = 0; i < numErrors; i++) {
        // We must call noteAtomLogged as well because only those pushed atoms
        // that have been logged will have stats printed about them in the
        // proto.
        stats.noteAtomLogged(pushAtomTag, /*timeSec=*/0, false);
        stats.noteAtomError(pushAtomTag, /*pull=*/false);

        stats.noteAtomError(pullAtomTag, /*pull=*/true);
    }

    StatsdStatsReport report = getStatsdStatsReport(stats, /* reset stats */ false);

    // Check error count = numErrors for push atom
    ASSERT_EQ(1, report.atom_stats_size());
    const auto& pushedAtomStats = report.atom_stats(0);
    EXPECT_EQ(pushAtomTag, pushedAtomStats.tag());
    EXPECT_EQ(numErrors, pushedAtomStats.error_count());
    EXPECT_FALSE(pushedAtomStats.has_dropped_count());
    EXPECT_FALSE(pushedAtomStats.has_skip_count());

    // Check error count = numErrors for pull atom
    ASSERT_EQ(1, report.pulled_atom_stats_size());
    const auto& pulledAtomStats = report.pulled_atom_stats(0);
    EXPECT_EQ(pullAtomTag, pulledAtomStats.atom_id());
    EXPECT_EQ(numErrors, pulledAtomStats.atom_error_count());
}

TEST(StatsdStatsTest, TestAtomDroppedStats) {
    StatsdStats stats;

    const int pushAtomTag = 100;
    const int nonPlatformPushAtomTag = StatsdStats::kMaxPushedAtomId + 100;

    const int numDropped = 10;
    for (int i = 0; i < numDropped; i++) {
        stats.noteEventQueueOverflow(/*oldestEventTimestampNs*/ 0, pushAtomTag, false);
        stats.noteEventQueueOverflow(/*oldestEventTimestampNs*/ 0, nonPlatformPushAtomTag, false);
    }

    StatsdStatsReport report = getStatsdStatsReport(stats, /* reset stats */ true);

    ASSERT_EQ(0, stats.mPushedAtomDropsStats.size());

    // Check dropped_count = numDropped for push atoms
    ASSERT_EQ(2, report.atom_stats_size());

    const auto& pushedAtomStats = report.atom_stats(0);
    EXPECT_EQ(pushAtomTag, pushedAtomStats.tag());
    EXPECT_EQ(numDropped, pushedAtomStats.count());
    EXPECT_EQ(numDropped, pushedAtomStats.dropped_count());
    EXPECT_FALSE(pushedAtomStats.has_error_count());
    EXPECT_FALSE(pushedAtomStats.has_skip_count());

    const auto& nonPlatformPushedAtomStats = report.atom_stats(1);
    EXPECT_EQ(nonPlatformPushAtomTag, nonPlatformPushedAtomStats.tag());
    EXPECT_EQ(numDropped, nonPlatformPushedAtomStats.count());
    EXPECT_EQ(numDropped, nonPlatformPushedAtomStats.dropped_count());
    EXPECT_FALSE(nonPlatformPushedAtomStats.has_error_count());
    EXPECT_FALSE(nonPlatformPushedAtomStats.has_skip_count());
}

TEST(StatsdStatsTest, TestQueueStats) {
    StatsdStats stats;

    stats.noteEventQueueSize(100, 1000);
    StatsdStatsReport report = getStatsdStatsReport(stats, /* reset stats */ true);

    ASSERT_EQ(100, report.event_queue_stats().max_size_observed());
    ASSERT_EQ(1000, report.event_queue_stats().max_size_observed_elapsed_nanos());
}

TEST(StatsdStatsTest, TestAtomLoggedAndDroppedStats) {
    StatsdStats stats;

    const int pushAtomTag = 100;
    const int nonPlatformPushAtomTag = StatsdStats::kMaxPushedAtomId + 100;

    const int numLogged = 10;
    for (int i = 0; i < numLogged; i++) {
        stats.noteAtomLogged(pushAtomTag, /*timeSec*/ 0, false);
        stats.noteAtomLogged(nonPlatformPushAtomTag, /*timeSec*/ 0, false);
    }

    const int numDropped = 10;
    for (int i = 0; i < numDropped; i++) {
        stats.noteEventQueueOverflow(/*oldestEventTimestampNs*/ 0, pushAtomTag, false);
        stats.noteEventQueueOverflow(/*oldestEventTimestampNs*/ 0, nonPlatformPushAtomTag, false);
    }

    StatsdStatsReport report = getStatsdStatsReport(stats, /* reset stats */ false);

    // Check dropped_count = numDropped for push atoms
    ASSERT_EQ(2, report.atom_stats_size());

    const auto& pushedAtomStats = report.atom_stats(0);
    EXPECT_EQ(pushAtomTag, pushedAtomStats.tag());
    EXPECT_EQ(numLogged + numDropped, pushedAtomStats.count());
    EXPECT_EQ(numDropped, pushedAtomStats.dropped_count());
    EXPECT_FALSE(pushedAtomStats.has_error_count());
    EXPECT_FALSE(pushedAtomStats.has_skip_count());

    const auto& nonPlatformPushedAtomStats = report.atom_stats(1);
    EXPECT_EQ(nonPlatformPushAtomTag, nonPlatformPushedAtomStats.tag());
    EXPECT_EQ(numLogged + numDropped, nonPlatformPushedAtomStats.count());
    EXPECT_EQ(numDropped, nonPlatformPushedAtomStats.dropped_count());
    EXPECT_FALSE(nonPlatformPushedAtomStats.has_error_count());
    EXPECT_FALSE(nonPlatformPushedAtomStats.has_skip_count());
}

TEST(StatsdStatsTest, TestAtomSkippedStats) {
    StatsdStats stats;

    const int pushAtomTag = 100;
    const int nonPlatformPushAtomTag = StatsdStats::kMaxPushedAtomId + 100;
    const int numSkipped = 10;

    for (int i = 0; i < numSkipped; i++) {
        stats.noteAtomLogged(pushAtomTag, /*timeSec=*/0, /*isSkipped*/ true);
        stats.noteAtomLogged(nonPlatformPushAtomTag, /*timeSec=*/0, /*isSkipped*/ true);
    }

    StatsdStatsReport report = getStatsdStatsReport(stats, /* reset stats */ false);

    // Check skip_count = numSkipped for push atoms
    ASSERT_EQ(2, report.atom_stats_size());

    const auto& pushedAtomStats = report.atom_stats(0);
    EXPECT_EQ(pushAtomTag, pushedAtomStats.tag());
    EXPECT_EQ(numSkipped, pushedAtomStats.count());
    EXPECT_EQ(numSkipped, pushedAtomStats.skip_count());
    EXPECT_FALSE(pushedAtomStats.has_error_count());

    const auto& nonPlatformPushedAtomStats = report.atom_stats(1);
    EXPECT_EQ(nonPlatformPushAtomTag, nonPlatformPushedAtomStats.tag());
    EXPECT_EQ(numSkipped, nonPlatformPushedAtomStats.count());
    EXPECT_EQ(numSkipped, nonPlatformPushedAtomStats.skip_count());
    EXPECT_FALSE(nonPlatformPushedAtomStats.has_error_count());
}

TEST(StatsdStatsTest, TestAtomLoggedAndDroppedAndSkippedStats) {
    StatsdStats stats;

    const int pushAtomTag = 100;
    const int nonPlatformPushAtomTag = StatsdStats::kMaxPushedAtomId + 100;

    const int numLogged = 10;
    for (int i = 0; i < numLogged; i++) {
        stats.noteAtomLogged(pushAtomTag, /*timeSec*/ 0, false);
        stats.noteAtomLogged(nonPlatformPushAtomTag, /*timeSec*/ 0, false);
    }

    const int numDropped = 10;
    for (int i = 0; i < numDropped; i++) {
        stats.noteEventQueueOverflow(/*oldestEventTimestampNs*/ 0, pushAtomTag, true);
        stats.noteEventQueueOverflow(/*oldestEventTimestampNs*/ 0, nonPlatformPushAtomTag, true);
    }

    StatsdStatsReport report = getStatsdStatsReport(stats, /* reset stats */ false);

    // Check dropped_count = numDropped for push atoms
    ASSERT_EQ(2, report.atom_stats_size());

    const auto& pushedAtomStats = report.atom_stats(0);
    EXPECT_EQ(pushAtomTag, pushedAtomStats.tag());
    EXPECT_EQ(numLogged + numDropped, pushedAtomStats.count());
    EXPECT_EQ(numDropped, pushedAtomStats.dropped_count());
    EXPECT_EQ(numDropped, pushedAtomStats.skip_count());
    EXPECT_FALSE(pushedAtomStats.has_error_count());

    const auto& nonPlatformPushedAtomStats = report.atom_stats(1);
    EXPECT_EQ(nonPlatformPushAtomTag, nonPlatformPushedAtomStats.tag());
    EXPECT_EQ(numLogged + numDropped, nonPlatformPushedAtomStats.count());
    EXPECT_EQ(numDropped, nonPlatformPushedAtomStats.dropped_count());
    EXPECT_EQ(numDropped, nonPlatformPushedAtomStats.skip_count());
    EXPECT_FALSE(nonPlatformPushedAtomStats.has_error_count());
}

TEST(StatsdStatsTest, TestShardOffsetProvider) {
    StatsdStats stats;
    ShardOffsetProvider::getInstance().setShardOffset(15);

    StatsdStatsReport report = getStatsdStatsReport(stats, /* reset stats */ false);
    EXPECT_EQ(report.shard_offset(), 15);
}

TEST(StatsdStatsTest, TestHasHitDimensionGuardrail) {
    StatsdStats stats;
    int metricId1 = 1;
    int metricId2 = 2;
    int metricId3 = 3;

    stats.noteBucketCount(metricId2);
    stats.noteHardDimensionLimitReached(metricId3);

    // No AtomMetricStats.
    EXPECT_FALSE(stats.hasHitDimensionGuardrail(metricId1));

    // Has AtomMetricStats but hasn't hit dimension guardrail.
    EXPECT_FALSE(stats.hasHitDimensionGuardrail(metricId2));

    // Has hit dimension guardrail.
    EXPECT_TRUE(stats.hasHitDimensionGuardrail(metricId3));
}

TEST(StatsdStatsTest, TestSubscriptionStarted) {
    StatsdStats stats;

    stats.noteSubscriptionStarted(/* id */ 1, /* pushedCount */ 3, /* pulledCount */ 1);

    StatsdStatsReport report = getStatsdStatsReport(stats, /* reset stats */ false);

    auto subscriptionStats = report.subscription_stats();
    EXPECT_EQ(subscriptionStats.pull_thread_wakeup_count(), 0);
    ASSERT_EQ(subscriptionStats.per_subscription_stats_size(), 1);
    auto perSubscriptionStats = subscriptionStats.per_subscription_stats(0);
    EXPECT_EQ(perSubscriptionStats.pushed_atom_count(), 3);
    EXPECT_EQ(perSubscriptionStats.pulled_atom_count(), 1);
    EXPECT_GT(perSubscriptionStats.start_time_sec(), 0);
    EXPECT_FALSE(perSubscriptionStats.has_end_time_sec());
    EXPECT_EQ(perSubscriptionStats.flush_count(), 0);
}

TEST(StatsdStatsTest, TestSubscriptionFlushed) {
    StatsdStats stats;

    stats.noteSubscriptionStarted(/* id */ 1, /* pushedCount */ 3, /* pulledCount */ 1);
    stats.noteSubscriptionFlushed(/* id */ 1);

    StatsdStatsReport report = getStatsdStatsReport(stats, /* reset stats */ false);

    auto subscriptionStats = report.subscription_stats();
    ASSERT_EQ(subscriptionStats.per_subscription_stats_size(), 1);
    auto perSubscriptionStats = subscriptionStats.per_subscription_stats(0);
    EXPECT_EQ(perSubscriptionStats.flush_count(), 1);
}

TEST(StatsdStatsTest, TestSubscriptionEnded) {
    StatsdStats stats;

    stats.noteSubscriptionStarted(/* id */ 1, /* pushedCount */ 3, /* pulledCount */ 1);
    stats.noteSubscriptionEnded(/* id */ 1);

    StatsdStatsReport report = getStatsdStatsReport(stats, /* reset stats */ false);

    auto subscriptionStats = report.subscription_stats();
    ASSERT_EQ(subscriptionStats.per_subscription_stats_size(), 1);
    EXPECT_GT(subscriptionStats.per_subscription_stats(0).end_time_sec(), 0);
}

TEST(StatsdStatsTest, TestSubscriptionAtomPulled) {
    StatsdStats stats;

    stats.noteSubscriptionAtomPulled(/* atomId */ 10'001);

    StatsdStatsReport report = getStatsdStatsReport(stats, /* reset stats */ false);

    ASSERT_EQ(report.pulled_atom_stats_size(), 1);
    auto pulledAtomStats = report.pulled_atom_stats(0);
    EXPECT_EQ(pulledAtomStats.subscription_pull_count(), 1);
}

TEST(StatsdStatsTest, TestSubscriptionPullThreadWakeup) {
    StatsdStats stats;

    stats.noteSubscriptionPullThreadWakeup();

    StatsdStatsReport report = getStatsdStatsReport(stats, /* reset stats */ false);

    auto subscriptionStats = report.subscription_stats();
    EXPECT_EQ(subscriptionStats.pull_thread_wakeup_count(), 1);
}

TEST(StatsdStatsTest, TestSubscriptionStartedMaxActiveSubscriptions) {
    StatsdStats stats;

    const int maxSubs = ShellSubscriber::getMaxSubscriptions();

    // Start more than max # of allowed subscriptions.
    // maxSub + 1th subscriptions should not have been added.
    for (int id = 1; id <= maxSubs + 1; id++) {
        stats.noteSubscriptionStarted(id, /* pushedCount */ 3, /* pulledCount */ 1);
    }

    StatsdStatsReport report = getStatsdStatsReport(stats, /* reset stats */ false);

    auto subscriptionStats = report.subscription_stats();
    ASSERT_EQ(subscriptionStats.per_subscription_stats_size(), maxSubs);
    EXPECT_THAT(subscriptionStats.per_subscription_stats(),
                Not(Contains(Property(&PerSubscriptionStats::id, Eq(maxSubs + 1)))));
}

TEST(StatsdStatsTest, TestSubscriptionStartedRemoveFinishedSubscription) {
    StatsdStats stats;

    const int maxSubs = ShellSubscriber::getMaxSubscriptions();

    // Start max # of allowed subscriptions
    for (int id = 1; id <= maxSubs; id++) {
        stats.noteSubscriptionStarted(id, /* pushedCount */ 3, /* pulledCount */ 1);
    }

    // End subscription with id 5.
    stats.noteSubscriptionEnded(/* id */ 5);

    // Add one more subscription after we've added max # of subscriptions.
    // Subscription wth id 5 should be removed and the new subscription added here should be
    // accepted.
    stats.noteSubscriptionStarted(maxSubs + 1, /* pushedCount */ 3, /* pulledCount */ 1);

    StatsdStatsReport report = getStatsdStatsReport(stats, /* reset stats */ false);

    auto subscriptionStats = report.subscription_stats();
    ASSERT_EQ(subscriptionStats.per_subscription_stats_size(), maxSubs);
    EXPECT_THAT(subscriptionStats.per_subscription_stats(),
                Not(Contains(Property(&PerSubscriptionStats::id, Eq(5)))));
    EXPECT_THAT(subscriptionStats.per_subscription_stats(),
                Contains(Property(&PerSubscriptionStats::id, Eq(maxSubs + 1))));
}

TEST(StatsdStatsTest, TestEnforceDimensionKeySizeLimit) {
    EXPECT_EQ(StatsdStats::clampDimensionKeySizeLimit(-1),
              StatsdStats::kDimensionKeySizeHardLimitMin);
    EXPECT_EQ(StatsdStats::clampDimensionKeySizeLimit(0),
              StatsdStats::kDimensionKeySizeHardLimitMin);
    EXPECT_EQ(StatsdStats::clampDimensionKeySizeLimit(500),
              StatsdStats::kDimensionKeySizeHardLimitMin);
    EXPECT_EQ(StatsdStats::clampDimensionKeySizeLimit(1000), 1000);
    EXPECT_EQ(StatsdStats::clampDimensionKeySizeLimit(3500),
              StatsdStats::kDimensionKeySizeHardLimitMax);
}

TEST(StatsdStatsTest, TestSocketLossStats) {
    StatsdStats stats;

    const int maxLossEvents = StatsdStats::kMaxSocketLossStatsSize;

    // Note maxLossEvents + 1
    for (int eventId = 0; eventId <= maxLossEvents; eventId++) {
        SocketLossInfo info;

        info.uid = eventId;
        info.firstLossTsNanos = 10 * eventId;
        info.lastLossTsNanos = 10 * eventId + 1;

        info.atomIds.push_back(eventId * 10);
        info.errors.push_back(eventId * 20);
        info.counts.push_back(eventId * 30);

        stats.noteAtomSocketLoss(info);
    }

    StatsdStatsReport report = getStatsdStatsReport(stats, /* reset stats */ false);

    auto socketLossStats = report.socket_loss_stats();
    ASSERT_EQ(socketLossStats.loss_stats_per_uid().size(), maxLossEvents);

    for (int i = 0; i < socketLossStats.loss_stats_per_uid().size(); i++) {
        const auto& info = report.socket_loss_stats().loss_stats_per_uid(i);

        // due to the very first one with id 0 is popped out from the list ids (index) start from 1
        const int index = i + 1;

        ASSERT_EQ(info.uid(), index);
        ASSERT_EQ(info.first_timestamp_nanos(), 10 * index);
        ASSERT_EQ(info.last_timestamp_nanos(), 10 * index + 1);

        ASSERT_EQ(info.atom_id_loss_stats().size(), 1);

        ASSERT_EQ(info.atom_id_loss_stats(0).atom_id(), index * 10);
        ASSERT_EQ(info.atom_id_loss_stats(0).error(), index * 20);
        ASSERT_EQ(info.atom_id_loss_stats(0).count(), index * 30);
    }
}

TEST(StatsdStatsTest, TestSocketLossStatsOverflowCounter) {
    StatsdStats stats;

    const int uidsCount = 5;
    const int lossEventCount = 5;

    for (int uid = 0; uid < uidsCount; uid++) {
        for (int eventId = 0; eventId < lossEventCount; eventId++) {
            SocketLossInfo info;

            info.uid = uid;
            info.firstLossTsNanos = 10 * eventId;
            info.lastLossTsNanos = 10 * eventId + 1;
            // the counter value will be accumulated
            info.overflowCounter = 1;

            info.atomIds.push_back(eventId * 10);
            info.errors.push_back(eventId * 20);
            info.counts.push_back(eventId * 30);

            stats.noteAtomSocketLoss(info);
        }
    }
    StatsdStatsReport report = getStatsdStatsReport(stats, /* reset stats */ false);

    auto socketLossStatsOverflowCounters =
            report.socket_loss_stats().loss_stats_overflow_counters();
    ASSERT_EQ(socketLossStatsOverflowCounters.size(), uidsCount);

    for (int i = 0; i < socketLossStatsOverflowCounters.size(); i++) {
        const auto& counters = report.socket_loss_stats().loss_stats_overflow_counters(i);

        ASSERT_EQ(counters.uid(), i);
        ASSERT_EQ(counters.count(), lossEventCount);
    }
}

TEST(StatsdStatsTest, TestSocketBatchReadStats) {
    unordered_map<int32_t, int32_t> empty;
    unordered_map<int32_t, int32_t> m1199 = {{1, 1}, {2, 1190}, {3, 8}};  // only 2 should show up
    unordered_map<int32_t, int32_t> m1200 = {{1, 1}, {2, 4}, {3, 8}};     // none should show up
    unordered_map<int32_t, int32_t> m120000 = {{1, 1000}, {2, 2000}, {3, 800}};  // all show up
    StatsdStats stats;
    stats.noteBatchSocketRead(1, 0, 0, 0, 0, empty);                           // bin 1
    stats.noteBatchSocketRead(2, 0, 0, 0, 0, empty);                           // bin 2
    stats.noteBatchSocketRead(2, 0, 0, 0, 0, empty);                           // bin 2
    stats.noteBatchSocketRead(4, 0, 0, 0, 0, empty);                           // bin 4
    stats.noteBatchSocketRead(5, 0, 0, 0, 0, empty);                           // bin 5
    stats.noteBatchSocketRead(9, 0, 0, 0, 0, empty);                           // bin 5
    stats.noteBatchSocketRead(9, 0, 0, 0, 0, empty);                           // bin 5
    stats.noteBatchSocketRead(10, 0, 0, 0, 0, empty);                          // bin 6
    stats.noteBatchSocketRead(19, 0, 0, 0, 0, empty);                          // bin 6
    stats.noteBatchSocketRead(30, 0, 0, 0, 0, empty);                          // bin 8
    stats.noteBatchSocketRead(32, 0, 0, 0, 0, empty);                          // bin 8
    stats.noteBatchSocketRead(39, 0, 0, 0, 0, empty);                          // bin 8
    stats.noteBatchSocketRead(90, 0, 0, 0, 0, empty);                          // bin 14
    stats.noteBatchSocketRead(99, 0, 0, 0, 0, empty);                          // bin 14
    stats.noteBatchSocketRead(100, 0, 0, 0, 0, empty);                         // bin 15
    stats.noteBatchSocketRead(100, 0, 0, 0, 0, empty);                         // bin 15
    stats.noteBatchSocketRead(199, 0, 0, 0, 0, empty);                         // bin 15
    stats.noteBatchSocketRead(200, 0, 0, 0, 0, empty);                         // bin 16
    stats.noteBatchSocketRead(299, 0, 0, 0, 0, empty);                         // bin 16
    stats.noteBatchSocketRead(999, 0, 0, 0, 0, empty);                         // bin 23
    stats.noteBatchSocketRead(1000, 0, 0, 0, 0, empty);                        // bin 24
    stats.noteBatchSocketRead(1199, 1, 2, 3, 4, m1199);                        // bin 24
    stats.noteBatchSocketRead(1200, 5, 6, 7, 8, m1200);                        // bin 25
    stats.noteBatchSocketRead(1800, 0, 0, 0, 0, empty);                        // bin 28
    stats.noteBatchSocketRead(1999, 0, 0, 0, 0, empty);                        // bin 28
    stats.noteBatchSocketRead(2000, 0, 0, 0, 0, empty);                        // bin 29
    stats.noteBatchSocketRead(120000, 10, INT64_MAX, 50, INT64_MAX, m120000);  // bin 29

    StatsdStatsReport report = getStatsdStatsReport(stats, /* reset stats */ false);
    EXPECT_THAT(report.socket_read_stats().batched_read_size(),
                ElementsAre(0, 1, 2, 0, 1, 3, 2, 0, 3, 0, 0, 0, 0, 0, 2, 3, 2, 0, 0, 0, 0, 0, 0, 1,
                            2, 1, 0, 0, 2, 2));

    // Check the large batch stats
    ASSERT_EQ(report.socket_read_stats().large_batch_read_stats_size(), 7);
    auto largeBatchStats = report.socket_read_stats().large_batch_read_stats(1);  // 1199
    EXPECT_EQ(largeBatchStats.total_atoms_read(), 1199);
    EXPECT_EQ(largeBatchStats.last_read_time_elapsed_ns(), 1);
    EXPECT_EQ(largeBatchStats.curr_read_time_elapsed_ns(), 2);
    EXPECT_EQ(largeBatchStats.min_atom_time_elapsed_ns(), 3);
    EXPECT_EQ(largeBatchStats.max_atom_time_elapsed_ns(), 4);
    ASSERT_EQ(largeBatchStats.atom_stats_size(), 1);
    EXPECT_EQ(largeBatchStats.atom_stats(0).atom_id(), 2);
    EXPECT_EQ(largeBatchStats.atom_stats(0).count(), 1190);

    largeBatchStats = report.socket_read_stats().large_batch_read_stats(2);  // 1200
    EXPECT_EQ(largeBatchStats.total_atoms_read(), 1200);
    EXPECT_EQ(largeBatchStats.last_read_time_elapsed_ns(), 5);
    EXPECT_EQ(largeBatchStats.curr_read_time_elapsed_ns(), 6);
    EXPECT_EQ(largeBatchStats.min_atom_time_elapsed_ns(), 7);
    EXPECT_EQ(largeBatchStats.max_atom_time_elapsed_ns(), 8);
    ASSERT_EQ(largeBatchStats.atom_stats_size(), 0);

    largeBatchStats = report.socket_read_stats().large_batch_read_stats(6);  // 120000
    EXPECT_EQ(largeBatchStats.total_atoms_read(), 120000);
    EXPECT_EQ(largeBatchStats.last_read_time_elapsed_ns(), 10);
    EXPECT_EQ(largeBatchStats.curr_read_time_elapsed_ns(), INT64_MAX);
    EXPECT_EQ(largeBatchStats.min_atom_time_elapsed_ns(), 50);
    EXPECT_EQ(largeBatchStats.max_atom_time_elapsed_ns(), INT64_MAX);
    ASSERT_EQ(largeBatchStats.atom_stats_size(), 3);
    EXPECT_EQ(largeBatchStats.atom_stats(0).atom_id(), 3);
    EXPECT_EQ(largeBatchStats.atom_stats(0).count(), 800);
    EXPECT_EQ(largeBatchStats.atom_stats(1).atom_id(), 2);
    EXPECT_EQ(largeBatchStats.atom_stats(1).count(), 2000);
    EXPECT_EQ(largeBatchStats.atom_stats(2).atom_id(), 1);
    EXPECT_EQ(largeBatchStats.atom_stats(2).count(), 1000);

    stats.reset();
    report = getStatsdStatsReport(stats, /* reset stats */ false);
    EXPECT_THAT(report.socket_read_stats().batched_read_size(),
                AllOf(SizeIs(StatsdStats::kNumBinsInSocketBatchReadHistogram), Each(0)));
    ASSERT_EQ(report.socket_read_stats().large_batch_read_stats_size(), 0);
}

TEST_P(StatsdStatsTest_GetAtomDimensionKeySizeLimit_InMap, TestGetAtomDimensionKeySizeLimits) {
    const auto& [atomId, defaultHardLimit] = GetParam();
    EXPECT_EQ(StatsdStats::getAtomDimensionKeySizeLimits(atomId, defaultHardLimit),
              StatsdStats::kAtomDimensionKeySizeLimitMap.at(atomId));
}

TEST_P(StatsdStatsTest_GetAtomDimensionKeySizeLimit_NotInMap, TestGetAtomDimensionKeySizeLimits) {
    const auto& [atomId, defaultHardLimit] = GetParam();
    EXPECT_EQ(
            StatsdStats::getAtomDimensionKeySizeLimits(atomId, defaultHardLimit),
            (std::pair<size_t, size_t>(StatsdStats::kDimensionKeySizeSoftLimit, defaultHardLimit)));
}

}  // namespace statsd
}  // namespace os
}  // namespace android
#else
GTEST_LOG_(INFO) << "This test does nothing.\n";
#endif
