diff --git a/cmds/statsd/Android.mk b/cmds/statsd/Android.mk index 556709b686b0a..1aef0c4c43c51 100644 --- a/cmds/statsd/Android.mk +++ b/cmds/statsd/Android.mk @@ -206,7 +206,9 @@ LOCAL_SRC_FILES := \ tests/e2e/GaugeMetric_e2e_push_test.cpp \ tests/e2e/DimensionInCondition_e2e_combination_AND_cond_test.cpp \ tests/e2e/DimensionInCondition_e2e_combination_OR_cond_test.cpp \ - tests/e2e/DimensionInCondition_e2e_simple_cond_test.cpp + tests/e2e/DimensionInCondition_e2e_simple_cond_test.cpp \ + tests/e2e/Anomaly_count_e2e_test.cpp \ + tests/e2e/Anomaly_duration_sum_e2e_test.cpp LOCAL_STATIC_LIBRARIES := \ $(statsd_common_static_libraries) \ diff --git a/cmds/statsd/src/StatsLogProcessor.h b/cmds/statsd/src/StatsLogProcessor.h index 1be4dc5d58c72..a07a35587b11f 100644 --- a/cmds/statsd/src/StatsLogProcessor.h +++ b/cmds/statsd/src/StatsLogProcessor.h @@ -69,6 +69,11 @@ public: void dumpStates(FILE* out, bool verbose); private: + // For testing only. + inline sp getAnomalyAlarmMonitor() const { + return mAnomalyAlarmMonitor; + } + mutable mutex mMetricsMutex; std::unordered_map> mMetricsManagers; @@ -133,13 +138,15 @@ private: FRIEND_TEST(DimensionInConditionE2eTest, TestDurationMetric_Link_SimpleCondition); FRIEND_TEST(DimensionInConditionE2eTest, TestDurationMetric_PartialLink_SimpleCondition); - - FRIEND_TEST(DimensionInConditionE2eTest, TestDurationMetric_PartialLink_AND_CombinationCondition); FRIEND_TEST(DimensionInConditionE2eTest, TestDurationMetric_NoLink_AND_CombinationCondition); FRIEND_TEST(DimensionInConditionE2eTest, TestDurationMetric_Link_AND_CombinationCondition); - + FRIEND_TEST(AnomalyDetectionE2eTest, TestSlicedCountMetric_single_bucket); + FRIEND_TEST(AnomalyDetectionE2eTest, TestSlicedCountMetric_multiple_buckets); + FRIEND_TEST(AnomalyDetectionE2eTest, TestDurationMetric_SUM_single_bucket); + FRIEND_TEST(AnomalyDetectionE2eTest, TestDurationMetric_SUM_multiple_buckets); + FRIEND_TEST(AnomalyDetectionE2eTest, TestDurationMetric_SUM_long_refractory_period); }; } // namespace statsd diff --git a/cmds/statsd/src/anomaly/AnomalyTracker.cpp b/cmds/statsd/src/anomaly/AnomalyTracker.cpp index 49de1ac417bc7..f0960e3c8cc4a 100644 --- a/cmds/statsd/src/anomaly/AnomalyTracker.cpp +++ b/cmds/statsd/src/anomaly/AnomalyTracker.cpp @@ -66,6 +66,9 @@ size_t AnomalyTracker::index(int64_t bucketNum) const { void AnomalyTracker::advanceMostRecentBucketTo(const int64_t& bucketNum) { VLOG("advanceMostRecentBucketTo() called."); + if (mNumOfPastBuckets <= 0) { + return; + } if (bucketNum <= mMostRecentBucketNum) { ALOGW("Cannot advance buckets backwards (bucketNum=%lld but mMostRecentBucketNum=%lld)", (long long)bucketNum, (long long)mMostRecentBucketNum); @@ -170,7 +173,8 @@ void AnomalyTracker::addBucketToSum(const shared_ptr& bucket) { int64_t AnomalyTracker::getPastBucketValue(const MetricDimensionKey& key, const int64_t& bucketNum) const { - if (bucketNum < 0 || bucketNum <= mMostRecentBucketNum - mNumOfPastBuckets + if (bucketNum < 0 || mMostRecentBucketNum < 0 + || bucketNum <= mMostRecentBucketNum - mNumOfPastBuckets || bucketNum > mMostRecentBucketNum) { return 0; } @@ -241,14 +245,10 @@ void AnomalyTracker::detectAndDeclareAnomaly(const uint64_t& timestampNs, } bool AnomalyTracker::isInRefractoryPeriod(const uint64_t& timestampNs, - const MetricDimensionKey& key) { + const MetricDimensionKey& key) const { const auto& it = mRefractoryPeriodEndsSec.find(key); if (it != mRefractoryPeriodEndsSec.end()) { - if (timestampNs < it->second * NS_PER_SEC) { - return true; - } else { - mRefractoryPeriodEndsSec.erase(key); - } + return timestampNs < it->second * NS_PER_SEC; } return false; } diff --git a/cmds/statsd/src/anomaly/AnomalyTracker.h b/cmds/statsd/src/anomaly/AnomalyTracker.h index d3da7dcf241b9..ae0af64cfdd3c 100644 --- a/cmds/statsd/src/anomaly/AnomalyTracker.h +++ b/cmds/statsd/src/anomaly/AnomalyTracker.h @@ -113,6 +113,13 @@ public: } protected: + // For testing only. + // Returns the alarm timestamp in seconds for the query dimension if it exists. Otherwise + // returns 0. + virtual uint32_t getAlarmTimestampSec(const MetricDimensionKey& dimensionKey) const { + return 0; // The base AnomalyTracker class doesn't have alarms. + } + // statsd_config.proto Alert message that defines this tracker. const Alert mAlert; @@ -159,8 +166,7 @@ protected: void subtractValueFromSum(const MetricDimensionKey& key, const int64_t& bucketValue); // Returns true if in the refractory period, else false. - // If there is a stored refractory period but it ended prior to timestampNs, it is removed. - bool isInRefractoryPeriod(const uint64_t& timestampNs, const MetricDimensionKey& key); + bool isInRefractoryPeriod(const uint64_t& timestampNs, const MetricDimensionKey& key) const; // Calculates the corresponding bucket index within the circular array. // Requires bucketNum >= 0. @@ -176,6 +182,9 @@ protected: FRIEND_TEST(AnomalyTrackerTest, TestSparseBuckets); FRIEND_TEST(GaugeMetricProducerTest, TestAnomalyDetection); FRIEND_TEST(CountMetricProducerTest, TestAnomalyDetectionUnSliced); + FRIEND_TEST(AnomalyDetectionE2eTest, TestDurationMetric_SUM_single_bucket); + FRIEND_TEST(AnomalyDetectionE2eTest, TestDurationMetric_SUM_multiple_buckets); + FRIEND_TEST(AnomalyDetectionE2eTest, TestDurationMetric_SUM_long_refractory_period); }; } // namespace statsd diff --git a/cmds/statsd/src/anomaly/DurationAnomalyTracker.cpp b/cmds/statsd/src/anomaly/DurationAnomalyTracker.cpp index 79067eb982bff..cdc4251247088 100644 --- a/cmds/statsd/src/anomaly/DurationAnomalyTracker.cpp +++ b/cmds/statsd/src/anomaly/DurationAnomalyTracker.cpp @@ -38,11 +38,10 @@ DurationAnomalyTracker::~DurationAnomalyTracker() { void DurationAnomalyTracker::startAlarm(const MetricDimensionKey& dimensionKey, const uint64_t& timestampNs) { // Alarms are stored in secs. Must round up, since if it fires early, it is ignored completely. - uint32_t timestampSec = static_cast((timestampNs -1)/ NS_PER_SEC) + 1; // round up + uint32_t timestampSec = static_cast((timestampNs -1) / NS_PER_SEC) + 1; // round up if (isInRefractoryPeriod(timestampNs, dimensionKey)) { - // TODO: Bug! By the refractory's end, the data might be erased and the alarm inapplicable. - VLOG("Setting a delayed anomaly alarm lest it fall in the refractory period"); - timestampSec = getRefractoryPeriodEndsSec(dimensionKey) + 1; + VLOG("Not setting anomaly alarm since it would fall in the refractory period."); + return; } auto itr = mAlarms.find(dimensionKey); diff --git a/cmds/statsd/src/anomaly/DurationAnomalyTracker.h b/cmds/statsd/src/anomaly/DurationAnomalyTracker.h index 92bb2bc1515b1..53155d9a5240e 100644 --- a/cmds/statsd/src/anomaly/DurationAnomalyTracker.h +++ b/cmds/statsd/src/anomaly/DurationAnomalyTracker.h @@ -52,6 +52,13 @@ public: unordered_set, SpHash>& firedAlarms) override; protected: + // Returns the alarm timestamp in seconds for the query dimension if it exists. Otherwise + // returns 0. + uint32_t getAlarmTimestampSec(const MetricDimensionKey& dimensionKey) const override { + auto it = mAlarms.find(dimensionKey); + return it == mAlarms.end() ? 0 : it->second->timestampSec; + } + // The alarms owned by this tracker. The alarm monitor also shares the alarm pointers when they // are still active. std::unordered_map> mAlarms; diff --git a/cmds/statsd/src/metrics/MetricProducer.h b/cmds/statsd/src/metrics/MetricProducer.h index ea45f43a2d29c..4983f96eff03c 100644 --- a/cmds/statsd/src/metrics/MetricProducer.h +++ b/cmds/statsd/src/metrics/MetricProducer.h @@ -188,7 +188,7 @@ protected: // Convenience to compute the current bucket's end time, which is always aligned with the // start time of the metric. - uint64_t getCurrentBucketEndTimeNs() { + uint64_t getCurrentBucketEndTimeNs() const { return mStartTimeNs + (mCurrentBucketNum + 1) * mBucketSizeNs; } diff --git a/cmds/statsd/src/metrics/MetricsManager.h b/cmds/statsd/src/metrics/MetricsManager.h index 46a9b34c21a13..05ce84d7ea8f9 100644 --- a/cmds/statsd/src/metrics/MetricsManager.h +++ b/cmds/statsd/src/metrics/MetricsManager.h @@ -178,6 +178,12 @@ private: FRIEND_TEST(DimensionInConditionE2eTest, TestDurationMetric_Link_AND_CombinationCondition); FRIEND_TEST(DimensionInConditionE2eTest, TestDurationMetric_PartialLink_AND_CombinationCondition); + FRIEND_TEST(AnomalyDetectionE2eTest, TestSlicedCountMetric_single_bucket); + FRIEND_TEST(AnomalyDetectionE2eTest, TestSlicedCountMetric_multiple_buckets); + FRIEND_TEST(AnomalyDetectionE2eTest, TestDurationMetric_SUM_single_bucket); + FRIEND_TEST(AnomalyDetectionE2eTest, TestDurationMetric_SUM_multiple_buckets); + FRIEND_TEST(AnomalyDetectionE2eTest, TestDurationMetric_SUM_long_refractory_period); + }; } // namespace statsd diff --git a/cmds/statsd/src/metrics/duration_helper/DurationTracker.h b/cmds/statsd/src/metrics/duration_helper/DurationTracker.h index 991a76a0d34a5..ddfb8cc0a5d3a 100644 --- a/cmds/statsd/src/metrics/duration_helper/DurationTracker.h +++ b/cmds/statsd/src/metrics/duration_helper/DurationTracker.h @@ -109,7 +109,7 @@ public: // Predict the anomaly timestamp given the current status. virtual int64_t predictAnomalyTimestampNs(const DurationAnomalyTracker& anomalyTracker, - const uint64_t currentTimestamp) const = 0; + const int64_t currentTimestamp) const = 0; // Dump internal states for debugging virtual void dumpStates(FILE* out, bool verbose) const = 0; @@ -118,12 +118,19 @@ public: } protected: + uint64_t getCurrentBucketEndTimeNs() const { + return mStartTimeNs + (mCurrentBucketNum + 1) * mBucketSizeNs; + } + // Starts the anomaly alarm. void startAnomalyAlarm(const uint64_t eventTime) { for (auto& anomalyTracker : mAnomalyTrackers) { if (anomalyTracker != nullptr) { - anomalyTracker->startAlarm(mEventKey, - predictAnomalyTimestampNs(*anomalyTracker, eventTime)); + const uint64_t alarmTimestampNs = + predictAnomalyTimestampNs(*anomalyTracker, eventTime); + if (alarmTimestampNs > 0) { + anomalyTracker->startAlarm(mEventKey, alarmTimestampNs); + } } } } diff --git a/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.cpp b/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.cpp index c9547cf4bf41c..df9e6aea578b9 100644 --- a/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.cpp +++ b/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.cpp @@ -313,7 +313,7 @@ void MaxDurationTracker::noteConditionChanged(const HashableDimensionKey& key, b } int64_t MaxDurationTracker::predictAnomalyTimestampNs(const DurationAnomalyTracker& anomalyTracker, - const uint64_t currentTimestamp) const { + const int64_t currentTimestamp) const { // The allowed time we can continue in the current state is the // (anomaly threshold) - max(elapsed time of the started mInfos). int64_t maxElapsed = 0; diff --git a/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.h b/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.h index 0452d373d413c..32d42fad138eb 100644 --- a/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.h +++ b/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.h @@ -57,7 +57,7 @@ public: void onConditionChanged(bool condition, const uint64_t timestamp) override; int64_t predictAnomalyTimestampNs(const DurationAnomalyTracker& anomalyTracker, - const uint64_t currentTimestamp) const override; + const int64_t currentTimestamp) const override; void dumpStates(FILE* out, bool verbose) const override; private: diff --git a/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.cpp b/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.cpp index b418a85a045cd..da79217567c78 100644 --- a/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.cpp +++ b/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.cpp @@ -166,13 +166,13 @@ bool OringDurationTracker::flushCurrentBucket( current_info.mDuration = mDuration; (*output)[mEventKey].push_back(current_info); mDurationFullBucket += mDuration; - if (eventTimeNs > fullBucketEnd) { - // End of full bucket, can send to anomaly tracker now. - addPastBucketToAnomalyTrackers(mDurationFullBucket, mCurrentBucketNum); - mDurationFullBucket = 0; - } VLOG(" duration: %lld", (long long)current_info.mDuration); } + if (eventTimeNs > fullBucketEnd) { + // End of full bucket, can send to anomaly tracker now. + addPastBucketToAnomalyTrackers(mDurationFullBucket, mCurrentBucketNum); + mDurationFullBucket = 0; + } if (mStarted.size() > 0) { for (int i = 1; i < numBucketsForward; i++) { @@ -186,6 +186,10 @@ bool OringDurationTracker::flushCurrentBucket( addPastBucketToAnomalyTrackers(info.mDuration, mCurrentBucketNum + i); VLOG(" add filling bucket with duration %lld", (long long)info.mDuration); } + } else { + if (numBucketsForward >= 2) { + addPastBucketToAnomalyTrackers(0, mCurrentBucketNum + numBucketsForward - 1); + } } mDuration = 0; @@ -320,57 +324,84 @@ void OringDurationTracker::onConditionChanged(bool condition, const uint64_t tim } int64_t OringDurationTracker::predictAnomalyTimestampNs( - const DurationAnomalyTracker& anomalyTracker, const uint64_t eventTimestampNs) const { + const DurationAnomalyTracker& anomalyTracker, const int64_t eventTimestampNs) const { // TODO: Unit-test this and see if it can be done more efficiently (e.g. use int32). - // All variables below represent durations (not timestamps). + // The anomaly threshold. const int64_t thresholdNs = anomalyTracker.getAnomalyThreshold(); - // The time until the current bucket ends. This is how much more 'space' it can hold. - const int64_t currRemainingBucketSizeNs = - mBucketSizeNs - (eventTimestampNs - mCurrentBucketStartTimeNs); - if (currRemainingBucketSizeNs < 0) { - ALOGE("OringDurationTracker currRemainingBucketSizeNs < 0"); - // This should never happen. Return the safest thing possible given that data is corrupt. - return eventTimestampNs + thresholdNs; - } + // The timestamp of the current bucket end. + const int64_t currentBucketEndNs = getCurrentBucketEndTimeNs(); + + // The past duration ns for the current bucket. + int64_t currentBucketPastNs = mDuration + mDurationFullBucket; // As we move into the future, old buckets get overwritten (so their old data is erased). - // Sum of past durations. Will change as we overwrite old buckets. - int64_t pastNs = mDuration + mDurationFullBucket; - pastNs += anomalyTracker.getSumOverPastBuckets(mEventKey); + int64_t pastNs = currentBucketPastNs + anomalyTracker.getSumOverPastBuckets(mEventKey); - // How much of the threshold is still unaccounted after considering pastNs. - int64_t leftNs = thresholdNs - pastNs; + // The refractory period end timestamp for dimension mEventKey. + const int64_t refractoryPeriodEndNs = + anomalyTracker.getRefractoryPeriodEndsSec(mEventKey) * NS_PER_SEC; - // First deal with the remainder of the current bucket. - if (leftNs <= currRemainingBucketSizeNs) { // Predict the anomaly will occur in this bucket. - return eventTimestampNs + leftNs; + // The anomaly should happen when accumulated wakelock duration is above the threshold and + // not within the refractory period. + int64_t anomalyTimestampNs = + std::max(eventTimestampNs + thresholdNs - pastNs, refractoryPeriodEndNs); + // If the predicted the anomaly timestamp is within the current bucket, return it directly. + if (anomalyTimestampNs <= currentBucketEndNs) { + return std::max(eventTimestampNs, anomalyTimestampNs); } - // The remainder of this bucket contributes, but we must then move to the next bucket. - pastNs += currRemainingBucketSizeNs; - // Now deal with the past buckets, starting with the oldest. - for (int futBucketIdx = 0; futBucketIdx < anomalyTracker.getNumOfPastBuckets(); - futBucketIdx++) { - // We now overwrite the oldest bucket with the previous 'current', and start a new - // 'current'. + // Remove the old bucket. + if (anomalyTracker.getNumOfPastBuckets() > 0) { pastNs -= anomalyTracker.getPastBucketValue( - mEventKey, mCurrentBucketNum - anomalyTracker.getNumOfPastBuckets() + futBucketIdx); - leftNs = thresholdNs - pastNs; - if (leftNs <= mBucketSizeNs) { // Predict anomaly will occur in this bucket. - return eventTimestampNs + currRemainingBucketSizeNs + (futBucketIdx * mBucketSizeNs) + - leftNs; - } else { // This bucket would be entirely filled, and we'll need to move to the next - // bucket. - pastNs += mBucketSizeNs; + mEventKey, + mCurrentBucketNum - anomalyTracker.getNumOfPastBuckets()); + // Add the remaining of the current bucket to the accumulated wakelock duration. + pastNs += (currentBucketEndNs - eventTimestampNs); + } else { + // The anomaly depends on only one bucket. + pastNs = 0; + } + + // The anomaly will not happen in the current bucket. We need to iterate over the future buckets + // to predict the accumulated wakelock duration and determine the anomaly timestamp accordingly. + for (int futureBucketIdx = 1; futureBucketIdx <= anomalyTracker.getNumOfPastBuckets() + 1; + futureBucketIdx++) { + // The alarm candidate timestamp should meet two requirements: + // 1. the accumulated wakelock duration is above the threshold. + // 2. it is not within the refractory period. + // 3. the alarm timestamp falls in this bucket. Otherwise we need to flush the past buckets, + // find the new alarm candidate timestamp and check these requirements again. + const int64_t bucketEndNs = currentBucketEndNs + futureBucketIdx * mBucketSizeNs; + int64_t anomalyTimestampNs = + std::max(bucketEndNs - mBucketSizeNs + thresholdNs - pastNs, refractoryPeriodEndNs); + if (anomalyTimestampNs <= bucketEndNs) { + return anomalyTimestampNs; + } + if (anomalyTracker.getNumOfPastBuckets() <= 0) { + continue; + } + + // No valid alarm timestamp is found in this bucket. The clock moves to the end of the + // bucket. Update the pastNs. + pastNs += mBucketSizeNs; + // 1. If the oldest past bucket is still in the past bucket window, we could fetch the past + // bucket and erase it from pastNs. + // 2. If the oldest past bucket is the current bucket, we should compute the + // wakelock duration in the current bucket and erase it from pastNs. + // 3. Otherwise all othe past buckets are ancient. + if (futureBucketIdx < anomalyTracker.getNumOfPastBuckets()) { + pastNs -= anomalyTracker.getPastBucketValue( + mEventKey, + mCurrentBucketNum - anomalyTracker.getNumOfPastBuckets() + futureBucketIdx); + } else if (futureBucketIdx == anomalyTracker.getNumOfPastBuckets()) { + pastNs -= (currentBucketPastNs + (currentBucketEndNs - eventTimestampNs)); } } - // If we have reached this point, we even have to overwrite the the original current bucket. - // Thus, none of the past data will still be extant - pastNs is now 0. - return eventTimestampNs + thresholdNs; + return std::max(eventTimestampNs + thresholdNs, refractoryPeriodEndNs); } void OringDurationTracker::dumpStates(FILE* out, bool verbose) const { diff --git a/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.h b/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.h index 610e3ea79aa12..ca8abfe1e82d1 100644 --- a/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.h +++ b/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.h @@ -56,7 +56,7 @@ public: std::unordered_map>* output) override; int64_t predictAnomalyTimestampNs(const DurationAnomalyTracker& anomalyTracker, - const uint64_t currentTimestamp) const override; + const int64_t currentTimestamp) const override; void dumpStates(FILE* out, bool verbose) const override; private: diff --git a/cmds/statsd/tests/e2e/Anomaly_count_e2e_test.cpp b/cmds/statsd/tests/e2e/Anomaly_count_e2e_test.cpp new file mode 100644 index 0000000000000..93ecde50955b5 --- /dev/null +++ b/cmds/statsd/tests/e2e/Anomaly_count_e2e_test.cpp @@ -0,0 +1,241 @@ +// Copyright (C) 2018 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 + +#include "src/StatsLogProcessor.h" +#include "src/stats_log_util.h" +#include "tests/statsd_test_util.h" + +#include + +namespace android { +namespace os { +namespace statsd { + +#ifdef __ANDROID__ + +namespace { + +StatsdConfig CreateStatsdConfig(int num_buckets, int threshold) { + StatsdConfig config; + config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root. + auto wakelockAcquireMatcher = CreateAcquireWakelockAtomMatcher(); + + *config.add_atom_matcher() = wakelockAcquireMatcher; + + auto countMetric = config.add_count_metric(); + countMetric->set_id(123456); + countMetric->set_what(wakelockAcquireMatcher.id()); + *countMetric->mutable_dimensions_in_what() = CreateAttributionUidDimensions( + android::util::WAKELOCK_STATE_CHANGED, {Position::FIRST}); + countMetric->set_bucket(FIVE_MINUTES); + + auto alert = config.add_alert(); + alert->set_id(StringToId("alert")); + alert->set_metric_id(123456); + alert->set_num_buckets(num_buckets); + alert->set_refractory_period_secs(10); + alert->set_trigger_if_sum_gt(threshold); + return config; +} + +} // namespace + +TEST(AnomalyDetectionE2eTest, TestSlicedCountMetric_single_bucket) { + const int num_buckets = 1; + const int threshold = 3; + auto config = CreateStatsdConfig(num_buckets, threshold); + const uint64_t alert_id = config.alert(0).id(); + const uint32_t refractory_period_sec = config.alert(0).refractory_period_secs(); + + int64_t bucketStartTimeNs = 10000000000; + int64_t bucketSizeNs = + TimeUnitToBucketSizeInMillis(config.count_metric(0).bucket()) * 1000000; + + ConfigKey cfgKey; + auto processor = CreateStatsLogProcessor(bucketStartTimeNs / NS_PER_SEC, config, cfgKey); + EXPECT_EQ(processor->mMetricsManagers.size(), 1u); + EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid()); + EXPECT_EQ(1u, processor->mMetricsManagers.begin()->second->mAllAnomalyTrackers.size()); + + sp anomalyTracker = + processor->mMetricsManagers.begin()->second->mAllAnomalyTrackers[0]; + + std::vector attributions1 = {CreateAttribution(111, "App1")}; + std::vector attributions2 = { + CreateAttribution(111, "App1"), CreateAttribution(222, "GMSCoreModule1")}; + std::vector attributions3 = { + CreateAttribution(111, "App1"), CreateAttribution(333, "App3")}; + std::vector attributions4 = { + CreateAttribution(222, "GMSCoreModule1"), CreateAttribution(333, "App3")}; + std::vector attributions5 = { + CreateAttribution(222, "GMSCoreModule1") }; + + FieldValue fieldValue1(Field(android::util::WAKELOCK_STATE_CHANGED, (int32_t)0x02010101), + Value((int32_t)111)); + HashableDimensionKey whatKey1({fieldValue1}); + MetricDimensionKey dimensionKey1(whatKey1, DEFAULT_DIMENSION_KEY); + + FieldValue fieldValue2(Field(android::util::WAKELOCK_STATE_CHANGED, (int32_t)0x02010101), + Value((int32_t)222)); + HashableDimensionKey whatKey2({fieldValue2}); + MetricDimensionKey dimensionKey2(whatKey2, DEFAULT_DIMENSION_KEY); + + auto event = CreateAcquireWakelockEvent(attributions1, "wl1", bucketStartTimeNs + 2); + processor->OnLogEvent(event.get()); + EXPECT_EQ(0u, anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1)); + + event = CreateAcquireWakelockEvent(attributions4, "wl2", bucketStartTimeNs + 2); + processor->OnLogEvent(event.get()); + EXPECT_EQ(0u, anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey2)); + + event = CreateAcquireWakelockEvent(attributions2, "wl1", bucketStartTimeNs + 3); + processor->OnLogEvent(event.get()); + EXPECT_EQ(0u, anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1)); + + event = CreateAcquireWakelockEvent(attributions5, "wl2", bucketStartTimeNs + 3); + processor->OnLogEvent(event.get()); + EXPECT_EQ(0u, anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey2)); + + event = CreateAcquireWakelockEvent(attributions3, "wl1", bucketStartTimeNs + 4); + processor->OnLogEvent(event.get()); + EXPECT_EQ(0u, anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1)); + + event = CreateAcquireWakelockEvent(attributions5, "wl2", bucketStartTimeNs + 4); + processor->OnLogEvent(event.get()); + EXPECT_EQ(0u, anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey2)); + + // Fired alarm and refractory period end timestamp updated. + event = CreateAcquireWakelockEvent(attributions1, "wl1", bucketStartTimeNs + 5); + processor->OnLogEvent(event.get()); + EXPECT_EQ(refractory_period_sec + bucketStartTimeNs / NS_PER_SEC + 1, + anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1)); + + event = CreateAcquireWakelockEvent(attributions1, "wl1", bucketStartTimeNs + 100); + processor->OnLogEvent(event.get()); + EXPECT_EQ(refractory_period_sec + bucketStartTimeNs / NS_PER_SEC + 1, + anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1)); + + event = CreateAcquireWakelockEvent(attributions1, "wl1", bucketStartTimeNs + bucketSizeNs - 1); + processor->OnLogEvent(event.get()); + EXPECT_EQ(refractory_period_sec + (bucketStartTimeNs + bucketSizeNs - 1) / NS_PER_SEC + 1, + anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1)); + + event = CreateAcquireWakelockEvent(attributions1, "wl1", bucketStartTimeNs + bucketSizeNs + 1); + processor->OnLogEvent(event.get()); + EXPECT_EQ(refractory_period_sec + (bucketStartTimeNs + bucketSizeNs - 1) / NS_PER_SEC + 1, + anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1)); + + event = CreateAcquireWakelockEvent(attributions4, "wl2", bucketStartTimeNs + bucketSizeNs + 1); + processor->OnLogEvent(event.get()); + EXPECT_EQ(0u, anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey2)); + + event = CreateAcquireWakelockEvent(attributions5, "wl2", bucketStartTimeNs + bucketSizeNs + 2); + processor->OnLogEvent(event.get()); + EXPECT_EQ(0u, anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey2)); + + event = CreateAcquireWakelockEvent(attributions5, "wl2", bucketStartTimeNs + bucketSizeNs + 3); + processor->OnLogEvent(event.get()); + EXPECT_EQ(0u, anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey2)); + + event = CreateAcquireWakelockEvent(attributions5, "wl2", bucketStartTimeNs + bucketSizeNs + 4); + processor->OnLogEvent(event.get()); + EXPECT_EQ(refractory_period_sec + (bucketStartTimeNs + bucketSizeNs + 4) / NS_PER_SEC + 1, + anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey2)); +} + +TEST(AnomalyDetectionE2eTest, TestSlicedCountMetric_multiple_buckets) { + const int num_buckets = 3; + const int threshold = 3; + auto config = CreateStatsdConfig(num_buckets, threshold); + const uint64_t alert_id = config.alert(0).id(); + const uint32_t refractory_period_sec = config.alert(0).refractory_period_secs(); + + int64_t bucketStartTimeNs = 10000000000; + int64_t bucketSizeNs = + TimeUnitToBucketSizeInMillis(config.count_metric(0).bucket()) * 1000000; + + ConfigKey cfgKey; + auto processor = CreateStatsLogProcessor(bucketStartTimeNs / NS_PER_SEC, config, cfgKey); + EXPECT_EQ(processor->mMetricsManagers.size(), 1u); + EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid()); + EXPECT_EQ(1u, processor->mMetricsManagers.begin()->second->mAllAnomalyTrackers.size()); + + sp anomalyTracker = + processor->mMetricsManagers.begin()->second->mAllAnomalyTrackers[0]; + + std::vector attributions1 = {CreateAttribution(111, "App1")}; + std::vector attributions2 = { + CreateAttribution(111, "App1"), CreateAttribution(222, "GMSCoreModule1")}; + std::vector attributions3 = { + CreateAttribution(111, "App1"), CreateAttribution(333, "App3")}; + std::vector attributions4 = { + CreateAttribution(222, "GMSCoreModule1"), CreateAttribution(333, "App3")}; + std::vector attributions5 = { + CreateAttribution(222, "GMSCoreModule1") }; + + FieldValue fieldValue1(Field(android::util::WAKELOCK_STATE_CHANGED, (int32_t)0x02010101), + Value((int32_t)111)); + HashableDimensionKey whatKey1({fieldValue1}); + MetricDimensionKey dimensionKey1(whatKey1, DEFAULT_DIMENSION_KEY); + + FieldValue fieldValue2(Field(android::util::WAKELOCK_STATE_CHANGED, (int32_t)0x02010101), + Value((int32_t)222)); + HashableDimensionKey whatKey2({fieldValue2}); + MetricDimensionKey dimensionKey2(whatKey2, DEFAULT_DIMENSION_KEY); + + auto event = CreateAcquireWakelockEvent(attributions1, "wl1", bucketStartTimeNs + 2); + processor->OnLogEvent(event.get()); + EXPECT_EQ(0u, anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1)); + + event = CreateAcquireWakelockEvent(attributions2, "wl1", bucketStartTimeNs + 3); + processor->OnLogEvent(event.get()); + EXPECT_EQ(0u, anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1)); + + // Fired alarm and refractory period end timestamp updated. + event = CreateAcquireWakelockEvent(attributions1, "wl1", bucketStartTimeNs + 4); + processor->OnLogEvent(event.get()); + EXPECT_EQ(0u, anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1)); + + event = CreateAcquireWakelockEvent(attributions1, "wl1", bucketStartTimeNs + bucketSizeNs + 1); + processor->OnLogEvent(event.get()); + EXPECT_EQ(refractory_period_sec + (bucketStartTimeNs + bucketSizeNs + 1) / NS_PER_SEC + 1, + anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1)); + + event = CreateAcquireWakelockEvent(attributions2, "wl1", bucketStartTimeNs + bucketSizeNs + 2); + processor->OnLogEvent(event.get()); + EXPECT_EQ(refractory_period_sec + (bucketStartTimeNs + bucketSizeNs + 1) / NS_PER_SEC + 1, + anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1)); + + event = CreateAcquireWakelockEvent( + attributions2, "wl1", bucketStartTimeNs + 3 * bucketSizeNs + 1); + processor->OnLogEvent(event.get()); + EXPECT_EQ(refractory_period_sec + (bucketStartTimeNs + bucketSizeNs + 1) / NS_PER_SEC + 1, + anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1)); + + event = CreateAcquireWakelockEvent( + attributions2, "wl1", bucketStartTimeNs + 3 * bucketSizeNs + 2); + processor->OnLogEvent(event.get()); + EXPECT_EQ(refractory_period_sec + (bucketStartTimeNs + 3 * bucketSizeNs + 2) / NS_PER_SEC + 1, + anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1)); +} + +#else +GTEST_LOG_(INFO) << "This test does nothing.\n"; +#endif + +} // namespace statsd +} // namespace os +} // namespace android diff --git a/cmds/statsd/tests/e2e/Anomaly_duration_sum_e2e_test.cpp b/cmds/statsd/tests/e2e/Anomaly_duration_sum_e2e_test.cpp new file mode 100644 index 0000000000000..e924b03039467 --- /dev/null +++ b/cmds/statsd/tests/e2e/Anomaly_duration_sum_e2e_test.cpp @@ -0,0 +1,486 @@ +// Copyright (C) 2018 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 + +#include "src/anomaly/DurationAnomalyTracker.h" +#include "src/StatsLogProcessor.h" +#include "src/stats_log_util.h" +#include "tests/statsd_test_util.h" + +#include + +namespace android { +namespace os { +namespace statsd { + +#ifdef __ANDROID__ + +namespace { + +StatsdConfig CreateStatsdConfig(int num_buckets, + uint64_t threshold_ns, + DurationMetric::AggregationType aggregationType, + bool nesting) { + StatsdConfig config; + config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root. + *config.add_atom_matcher() = CreateScreenTurnedOnAtomMatcher(); + *config.add_atom_matcher() = CreateScreenTurnedOffAtomMatcher(); + *config.add_atom_matcher() = CreateAcquireWakelockAtomMatcher(); + *config.add_atom_matcher() = CreateReleaseWakelockAtomMatcher(); + + auto screenIsOffPredicate = CreateScreenIsOffPredicate(); + *config.add_predicate() = screenIsOffPredicate; + + auto holdingWakelockPredicate = CreateHoldingWakelockPredicate(); + FieldMatcher dimensions = CreateAttributionUidDimensions( + android::util::WAKELOCK_STATE_CHANGED, {Position::FIRST}); + dimensions.add_child()->set_field(3); // The wakelock tag is set in field 3 of the wakelock. + *holdingWakelockPredicate.mutable_simple_predicate()->mutable_dimensions() = dimensions; + holdingWakelockPredicate.mutable_simple_predicate()->set_count_nesting(nesting); + *config.add_predicate() = holdingWakelockPredicate; + + auto durationMetric = config.add_duration_metric(); + durationMetric->set_id(StringToId("WakelockDuration")); + durationMetric->set_what(holdingWakelockPredicate.id()); + durationMetric->set_condition(screenIsOffPredicate.id()); + durationMetric->set_aggregation_type(aggregationType); + *durationMetric->mutable_dimensions_in_what() = + CreateAttributionUidDimensions(android::util::WAKELOCK_STATE_CHANGED, {Position::FIRST}); + durationMetric->set_bucket(FIVE_MINUTES); + + auto alert = config.add_alert(); + alert->set_id(StringToId("alert")); + alert->set_metric_id(StringToId("WakelockDuration")); + alert->set_num_buckets(num_buckets); + alert->set_refractory_period_secs(2); + alert->set_trigger_if_sum_gt(threshold_ns); + return config; +} + +} // namespace + +std::vector attributions1 = {CreateAttribution(111, "App1"), + CreateAttribution(222, "GMSCoreModule1")}; + +std::vector attributions2 = {CreateAttribution(111, "App2"), + CreateAttribution(222, "GMSCoreModule1")}; + +std::vector attributions3 = {CreateAttribution(222, "GMSCoreModule1")}; + +MetricDimensionKey dimensionKey( + HashableDimensionKey({FieldValue(Field(android::util::WAKELOCK_STATE_CHANGED, + (int32_t)0x02010101), Value((int32_t)111))}), + DEFAULT_DIMENSION_KEY); + +MetricDimensionKey dimensionKey2( + HashableDimensionKey({FieldValue(Field(android::util::WAKELOCK_STATE_CHANGED, + (int32_t)0x02010101), Value((int32_t)222))}), + DEFAULT_DIMENSION_KEY); + +TEST(AnomalyDetectionE2eTest, TestDurationMetric_SUM_single_bucket) { + const int num_buckets = 1; + const uint64_t threshold_ns = NS_PER_SEC; + auto config = CreateStatsdConfig(num_buckets, threshold_ns, DurationMetric::SUM, true); + const uint64_t alert_id = config.alert(0).id(); + const uint32_t refractory_period_sec = config.alert(0).refractory_period_secs(); + + int64_t bucketStartTimeNs = 10 * NS_PER_SEC; + int64_t bucketSizeNs = + TimeUnitToBucketSizeInMillis(config.duration_metric(0).bucket()) * 1000000; + + ConfigKey cfgKey; + auto processor = CreateStatsLogProcessor(bucketStartTimeNs / NS_PER_SEC, config, cfgKey); + EXPECT_EQ(processor->mMetricsManagers.size(), 1u); + EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid()); + EXPECT_EQ(1u, processor->mMetricsManagers.begin()->second->mAllAnomalyTrackers.size()); + + sp anomalyTracker = + processor->mMetricsManagers.begin()->second->mAllAnomalyTrackers[0]; + + auto screen_on_event = CreateScreenStateChangedEvent( + android::view::DisplayStateEnum::DISPLAY_STATE_ON, bucketStartTimeNs + 1); + auto screen_off_event = CreateScreenStateChangedEvent( + android::view::DisplayStateEnum::DISPLAY_STATE_OFF, bucketStartTimeNs + 10); + processor->OnLogEvent(screen_on_event.get()); + processor->OnLogEvent(screen_off_event.get()); + + // Acquire wakelock wl1. + auto acquire_event = CreateAcquireWakelockEvent(attributions1, "wl1", bucketStartTimeNs + 11); + processor->OnLogEvent(acquire_event.get()); + EXPECT_EQ((bucketStartTimeNs + 11 + threshold_ns) / NS_PER_SEC + 1, + anomalyTracker->getAlarmTimestampSec(dimensionKey)); + EXPECT_EQ(0u, anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey)); + + // Release wakelock wl1. No anomaly detected. Alarm cancelled at the "release" event. + auto release_event = CreateReleaseWakelockEvent(attributions1, "wl1", bucketStartTimeNs + 101); + processor->OnLogEvent(release_event.get()); + EXPECT_EQ(0u, anomalyTracker->getAlarmTimestampSec(dimensionKey)); + EXPECT_EQ(0u, anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey)); + + // Acquire wakelock wl1 within bucket #0. + acquire_event = CreateAcquireWakelockEvent(attributions2, "wl1", bucketStartTimeNs + 110); + processor->OnLogEvent(acquire_event.get()); + EXPECT_EQ((bucketStartTimeNs + 110 + threshold_ns - 90) / NS_PER_SEC + 1, + anomalyTracker->getAlarmTimestampSec(dimensionKey)); + EXPECT_EQ(0u, anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey)); + + // Release wakelock wl1. One anomaly detected. + release_event = CreateReleaseWakelockEvent( + attributions2, "wl1", bucketStartTimeNs + NS_PER_SEC + 109); + processor->OnLogEvent(release_event.get()); + EXPECT_EQ(0u, anomalyTracker->getAlarmTimestampSec(dimensionKey)); + EXPECT_EQ(refractory_period_sec + (bucketStartTimeNs + NS_PER_SEC + 109) / NS_PER_SEC + 1, + anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey)); + + // Acquire wakelock wl1. + acquire_event = CreateAcquireWakelockEvent( + attributions1, "wl1", bucketStartTimeNs + NS_PER_SEC + 112); + processor->OnLogEvent(acquire_event.get()); + // Wakelock has been hold longer than the threshold in bucket #0. The alarm is set at the + // end of the refractory period. + const int64_t alarmFiredTimestampSec0 = anomalyTracker->getAlarmTimestampSec(dimensionKey); + EXPECT_EQ(refractory_period_sec + (bucketStartTimeNs + NS_PER_SEC + 109) / NS_PER_SEC + 1, + (uint32_t)alarmFiredTimestampSec0); + + // Anomaly alarm fired. + auto alarmSet = processor->getAnomalyAlarmMonitor()->popSoonerThan( + static_cast(alarmFiredTimestampSec0)); + EXPECT_EQ(1u, alarmSet.size()); + processor->onAnomalyAlarmFired(alarmFiredTimestampSec0 * NS_PER_SEC, alarmSet); + EXPECT_EQ(0u, anomalyTracker->getAlarmTimestampSec(dimensionKey)); + EXPECT_EQ(refractory_period_sec + alarmFiredTimestampSec0, + anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey)); + + // Release wakelock wl1. + release_event = CreateReleaseWakelockEvent( + attributions1, "wl1", alarmFiredTimestampSec0 * NS_PER_SEC + NS_PER_SEC + 1); + processor->OnLogEvent(release_event.get()); + EXPECT_EQ(0u, anomalyTracker->getAlarmTimestampSec(dimensionKey)); + // Within refractory period. No more anomaly detected. + EXPECT_EQ(refractory_period_sec + alarmFiredTimestampSec0, + anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey)); + + // Acquire wakelock wl1. + acquire_event = CreateAcquireWakelockEvent( + attributions2, "wl1", bucketStartTimeNs + bucketSizeNs - 5 * NS_PER_SEC - 11); + processor->OnLogEvent(acquire_event.get()); + const int64_t alarmFiredTimestampSec1 = anomalyTracker->getAlarmTimestampSec(dimensionKey); + EXPECT_EQ((bucketStartTimeNs + bucketSizeNs - 5 * NS_PER_SEC) / NS_PER_SEC, + (uint64_t)alarmFiredTimestampSec1); + + // Release wakelock wl1. + release_event = CreateReleaseWakelockEvent( + attributions2, "wl1", bucketStartTimeNs + bucketSizeNs - 4 * NS_PER_SEC - 10); + processor->OnLogEvent(release_event.get()); + EXPECT_EQ(0u, anomalyTracker->getAlarmTimestampSec(dimensionKey)); + EXPECT_EQ(refractory_period_sec + + (bucketStartTimeNs + bucketSizeNs - 4 * NS_PER_SEC - 10) / NS_PER_SEC + 1, + anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey)); + + alarmSet = processor->getAnomalyAlarmMonitor()->popSoonerThan( + static_cast(alarmFiredTimestampSec1)); + EXPECT_EQ(0u, alarmSet.size()); + + // Acquire wakelock wl1 near the end of bucket #0. + acquire_event = CreateAcquireWakelockEvent( + attributions1, "wl1", bucketStartTimeNs + bucketSizeNs - 2); + processor->OnLogEvent(acquire_event.get()); + EXPECT_EQ((bucketStartTimeNs + bucketSizeNs) / NS_PER_SEC, + anomalyTracker->getAlarmTimestampSec(dimensionKey)); + + // Release the event at early bucket #1. + release_event = CreateReleaseWakelockEvent( + attributions1, "wl1", bucketStartTimeNs + bucketSizeNs + NS_PER_SEC - 1); + processor->OnLogEvent(release_event.get()); + EXPECT_EQ(0u, anomalyTracker->getAlarmTimestampSec(dimensionKey)); + // Anomaly detected when stopping the alarm. The refractory period does not change. + EXPECT_EQ(refractory_period_sec + + (bucketStartTimeNs + bucketSizeNs + NS_PER_SEC) / NS_PER_SEC, + anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey)); + + // Condition changes to false. + screen_on_event = CreateScreenStateChangedEvent( + android::view::DisplayStateEnum::DISPLAY_STATE_ON, + bucketStartTimeNs + 2 * bucketSizeNs + 20); + processor->OnLogEvent(screen_on_event.get()); + EXPECT_EQ(refractory_period_sec + + (bucketStartTimeNs + bucketSizeNs + NS_PER_SEC) / NS_PER_SEC, + anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey)); + EXPECT_EQ(0u, anomalyTracker->getAlarmTimestampSec(dimensionKey)); + + acquire_event = CreateAcquireWakelockEvent( + attributions2, "wl1", bucketStartTimeNs + 2 * bucketSizeNs + 30); + processor->OnLogEvent(acquire_event.get()); + // The condition is false. Do not start the alarm. + EXPECT_EQ(0u, anomalyTracker->getAlarmTimestampSec(dimensionKey)); + EXPECT_EQ(refractory_period_sec + + (bucketStartTimeNs + bucketSizeNs + NS_PER_SEC) / NS_PER_SEC, + anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey)); + + // Condition turns true. + screen_off_event = CreateScreenStateChangedEvent( + android::view::DisplayStateEnum::DISPLAY_STATE_OFF, + bucketStartTimeNs + 2 * bucketSizeNs + NS_PER_SEC); + processor->OnLogEvent(screen_off_event.get()); + EXPECT_EQ((bucketStartTimeNs + 2 * bucketSizeNs + NS_PER_SEC + threshold_ns) / NS_PER_SEC, + anomalyTracker->getAlarmTimestampSec(dimensionKey)); + + // Condition turns to false. + screen_on_event = CreateScreenStateChangedEvent( + android::view::DisplayStateEnum::DISPLAY_STATE_ON, + bucketStartTimeNs + 2 * bucketSizeNs + 2 * NS_PER_SEC + 1); + processor->OnLogEvent(screen_on_event.get()); + // Condition turns to false. Cancelled the alarm. + EXPECT_EQ(0u, anomalyTracker->getAlarmTimestampSec(dimensionKey)); + // Detected one anomaly. + EXPECT_EQ(refractory_period_sec + + (bucketStartTimeNs + 2 * bucketSizeNs + 2 * NS_PER_SEC + 1) / NS_PER_SEC + 1, + anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey)); + + // Condition turns to true again. + screen_off_event = CreateScreenStateChangedEvent( + android::view::DisplayStateEnum::DISPLAY_STATE_OFF, + bucketStartTimeNs + 2 * bucketSizeNs + 2 * NS_PER_SEC + 2); + processor->OnLogEvent(screen_off_event.get()); + EXPECT_EQ((bucketStartTimeNs + 2 * bucketSizeNs) / NS_PER_SEC + 2 + 2 + 1, + anomalyTracker->getAlarmTimestampSec(dimensionKey)); + + release_event = CreateReleaseWakelockEvent( + attributions2, "wl1", bucketStartTimeNs + 2 * bucketSizeNs + 5 * NS_PER_SEC); + processor->OnLogEvent(release_event.get()); + EXPECT_EQ(refractory_period_sec + + (bucketStartTimeNs + 2 * bucketSizeNs + 5 * NS_PER_SEC) / NS_PER_SEC, + anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey)); + EXPECT_EQ(0u, anomalyTracker->getAlarmTimestampSec(dimensionKey)); +} + +TEST(AnomalyDetectionE2eTest, TestDurationMetric_SUM_multiple_buckets) { + const int num_buckets = 3; + const uint64_t threshold_ns = NS_PER_SEC; + auto config = CreateStatsdConfig(num_buckets, threshold_ns, DurationMetric::SUM, true); + const uint64_t alert_id = config.alert(0).id(); + const uint32_t refractory_period_sec = config.alert(0).refractory_period_secs(); + + int64_t bucketStartTimeNs = 10 * NS_PER_SEC; + int64_t bucketSizeNs = + TimeUnitToBucketSizeInMillis(config.duration_metric(0).bucket()) * 1000000; + + ConfigKey cfgKey; + auto processor = CreateStatsLogProcessor(bucketStartTimeNs / NS_PER_SEC, config, cfgKey); + EXPECT_EQ(processor->mMetricsManagers.size(), 1u); + EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid()); + EXPECT_EQ(1u, processor->mMetricsManagers.begin()->second->mAllAnomalyTrackers.size()); + + sp anomalyTracker = + processor->mMetricsManagers.begin()->second->mAllAnomalyTrackers[0]; + + auto screen_off_event = CreateScreenStateChangedEvent( + android::view::DisplayStateEnum::DISPLAY_STATE_OFF, bucketStartTimeNs + 1); + processor->OnLogEvent(screen_off_event.get()); + + // Acquire wakelock "wc1" in bucket #0. + auto acquire_event = CreateAcquireWakelockEvent( + attributions1, "wl1", bucketStartTimeNs + bucketSizeNs - NS_PER_SEC / 2 - 1); + processor->OnLogEvent(acquire_event.get()); + EXPECT_EQ((bucketStartTimeNs + bucketSizeNs) / NS_PER_SEC + 1, + anomalyTracker->getAlarmTimestampSec(dimensionKey)); + EXPECT_EQ(0u, anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey)); + + // Release wakelock "wc1" in bucket #0. + auto release_event = CreateReleaseWakelockEvent( + attributions1, "wl1", bucketStartTimeNs + bucketSizeNs - 1); + processor->OnLogEvent(release_event.get()); + EXPECT_EQ(0u, anomalyTracker->getAlarmTimestampSec(dimensionKey)); + EXPECT_EQ(0u, anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey)); + + // Acquire wakelock "wc1" in bucket #1. + acquire_event = CreateAcquireWakelockEvent( + attributions2, "wl1", bucketStartTimeNs + bucketSizeNs + 1); + processor->OnLogEvent(acquire_event.get()); + EXPECT_EQ((bucketStartTimeNs + bucketSizeNs) / NS_PER_SEC + 1, + anomalyTracker->getAlarmTimestampSec(dimensionKey)); + EXPECT_EQ(0u, anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey)); + + release_event = CreateReleaseWakelockEvent( + attributions2, "wl1", bucketStartTimeNs + bucketSizeNs + 100); + processor->OnLogEvent(release_event.get()); + EXPECT_EQ(0u, anomalyTracker->getAlarmTimestampSec(dimensionKey)); + EXPECT_EQ(0u, anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey)); + + // Acquire wakelock "wc2" in bucket #2. + acquire_event = CreateAcquireWakelockEvent( + attributions3, "wl2", bucketStartTimeNs + 2 * bucketSizeNs + 1); + processor->OnLogEvent(acquire_event.get()); + EXPECT_EQ((bucketStartTimeNs + 2 * bucketSizeNs) / NS_PER_SEC + 2, + anomalyTracker->getAlarmTimestampSec(dimensionKey2)); + EXPECT_EQ(0u, anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey2)); + + // Release wakelock "wc2" in bucket #2. + release_event = CreateReleaseWakelockEvent( + attributions3, "wl2", bucketStartTimeNs + 2 * bucketSizeNs + 2 * NS_PER_SEC); + processor->OnLogEvent(release_event.get()); + EXPECT_EQ(0u, anomalyTracker->getAlarmTimestampSec(dimensionKey2)); + EXPECT_EQ(refractory_period_sec + + (bucketStartTimeNs + 2 * bucketSizeNs + 2 * NS_PER_SEC) / NS_PER_SEC, + anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey2)); + + // Acquire wakelock "wc1" in bucket #2. + acquire_event = CreateAcquireWakelockEvent( + attributions2, "wl1", bucketStartTimeNs + 2 * bucketSizeNs + 2 * NS_PER_SEC); + processor->OnLogEvent(acquire_event.get()); + EXPECT_EQ((bucketStartTimeNs + 2 * bucketSizeNs) / NS_PER_SEC + 2 + 1, + anomalyTracker->getAlarmTimestampSec(dimensionKey)); + EXPECT_EQ(0u, anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey)); + + // Release wakelock "wc1" in bucket #2. + release_event = CreateReleaseWakelockEvent( + attributions2, "wl1", bucketStartTimeNs + 2 * bucketSizeNs + 2.5 * NS_PER_SEC); + processor->OnLogEvent(release_event.get()); + EXPECT_EQ(0u, anomalyTracker->getAlarmTimestampSec(dimensionKey)); + EXPECT_EQ(refractory_period_sec + + (int64_t)(bucketStartTimeNs + 2 * bucketSizeNs + 2.5 * NS_PER_SEC) / NS_PER_SEC + 1, + anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey)); + + acquire_event = CreateAcquireWakelockEvent( + attributions3, "wl2", bucketStartTimeNs + 6 * bucketSizeNs - NS_PER_SEC + 4); + processor->OnLogEvent(acquire_event.get()); + acquire_event = CreateAcquireWakelockEvent( + attributions1, "wl1", bucketStartTimeNs + 6 * bucketSizeNs - NS_PER_SEC + 5); + processor->OnLogEvent(acquire_event.get()); + EXPECT_EQ((bucketStartTimeNs + 6 * bucketSizeNs) / NS_PER_SEC + 1, + anomalyTracker->getAlarmTimestampSec(dimensionKey)); + EXPECT_EQ((bucketStartTimeNs + 6 * bucketSizeNs) / NS_PER_SEC + 1, + anomalyTracker->getAlarmTimestampSec(dimensionKey2)); + + release_event = CreateReleaseWakelockEvent( + attributions3, "wl2", bucketStartTimeNs + 6 * bucketSizeNs + 2); + processor->OnLogEvent(release_event.get()); + release_event = CreateReleaseWakelockEvent( + attributions1, "wl1", bucketStartTimeNs + 6 * bucketSizeNs + 6); + processor->OnLogEvent(release_event.get()); + EXPECT_EQ(0u, anomalyTracker->getAlarmTimestampSec(dimensionKey)); + EXPECT_EQ(0u, anomalyTracker->getAlarmTimestampSec(dimensionKey2)); + // The buckets are not messed up across dimensions. Only one dimension has anomaly triggered. + EXPECT_EQ(refractory_period_sec + + (int64_t)(bucketStartTimeNs + 6 * bucketSizeNs) / NS_PER_SEC + 1, + anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey)); +} + +TEST(AnomalyDetectionE2eTest, TestDurationMetric_SUM_long_refractory_period) { + const int num_buckets = 2; + const uint64_t threshold_ns = 3 * NS_PER_SEC; + auto config = CreateStatsdConfig(num_buckets, threshold_ns, DurationMetric::SUM, false); + int64_t bucketStartTimeNs = 10 * NS_PER_SEC; + int64_t bucketSizeNs = + TimeUnitToBucketSizeInMillis(config.duration_metric(0).bucket()) * 1000000; + + const uint64_t alert_id = config.alert(0).id(); + const uint32_t refractory_period_sec = 3 * bucketSizeNs / NS_PER_SEC; + config.mutable_alert(0)->set_refractory_period_secs(refractory_period_sec); + + ConfigKey cfgKey; + auto processor = CreateStatsLogProcessor(bucketStartTimeNs / NS_PER_SEC, config, cfgKey); + EXPECT_EQ(processor->mMetricsManagers.size(), 1u); + EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid()); + EXPECT_EQ(1u, processor->mMetricsManagers.begin()->second->mAllAnomalyTrackers.size()); + + sp anomalyTracker = + processor->mMetricsManagers.begin()->second->mAllAnomalyTrackers[0]; + + auto screen_off_event = CreateScreenStateChangedEvent( + android::view::DisplayStateEnum::DISPLAY_STATE_OFF, bucketStartTimeNs + 1); + processor->OnLogEvent(screen_off_event.get()); + + // Acquire wakelock "wc1" in bucket #0. + auto acquire_event = CreateAcquireWakelockEvent( + attributions1, "wl1", bucketStartTimeNs + bucketSizeNs - 100); + processor->OnLogEvent(acquire_event.get()); + EXPECT_EQ((bucketStartTimeNs + bucketSizeNs) / NS_PER_SEC + 3, + anomalyTracker->getAlarmTimestampSec(dimensionKey)); + EXPECT_EQ(0u, anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey)); + + // Acquire the wakelock "wc1" again. + acquire_event = CreateAcquireWakelockEvent( + attributions1, "wl1", bucketStartTimeNs + bucketSizeNs + 2 * NS_PER_SEC + 1); + processor->OnLogEvent(acquire_event.get()); + // The alarm does not change. + EXPECT_EQ((bucketStartTimeNs + bucketSizeNs) / NS_PER_SEC + 3, + anomalyTracker->getAlarmTimestampSec(dimensionKey)); + EXPECT_EQ(0u, anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey)); + + // Anomaly alarm fired late. + const int64_t firedAlarmTimestampNs = bucketStartTimeNs + 2 * bucketSizeNs - NS_PER_SEC; + auto alarmSet = processor->getAnomalyAlarmMonitor()->popSoonerThan( + static_cast(firedAlarmTimestampNs / NS_PER_SEC)); + EXPECT_EQ(1u, alarmSet.size()); + processor->onAnomalyAlarmFired(firedAlarmTimestampNs, alarmSet); + EXPECT_EQ(0u, anomalyTracker->getAlarmTimestampSec(dimensionKey)); + EXPECT_EQ(refractory_period_sec + firedAlarmTimestampNs / NS_PER_SEC, + anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey)); + + acquire_event = CreateAcquireWakelockEvent( + attributions1, "wl1", bucketStartTimeNs + 2 * bucketSizeNs - 100); + processor->OnLogEvent(acquire_event.get()); + EXPECT_EQ(0u, anomalyTracker->getAlarmTimestampSec(dimensionKey)); + EXPECT_EQ(refractory_period_sec + firedAlarmTimestampNs / NS_PER_SEC, + anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey)); + + auto release_event = CreateReleaseWakelockEvent( + attributions1, "wl1", bucketStartTimeNs + 2 * bucketSizeNs + 1); + processor->OnLogEvent(release_event.get()); + EXPECT_EQ(0u, anomalyTracker->getAlarmTimestampSec(dimensionKey)); + // Within the refractory period. No anomaly. + EXPECT_EQ(refractory_period_sec + firedAlarmTimestampNs / NS_PER_SEC, + anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey)); + + // A new wakelock, but still within refractory period. + acquire_event = CreateAcquireWakelockEvent( + attributions1, "wl1", bucketStartTimeNs + 2 * bucketSizeNs + 10 * NS_PER_SEC); + processor->OnLogEvent(acquire_event.get()); + EXPECT_EQ(refractory_period_sec + firedAlarmTimestampNs / NS_PER_SEC, + anomalyTracker->getAlarmTimestampSec(dimensionKey)); + + release_event = CreateReleaseWakelockEvent( + attributions1, "wl1", bucketStartTimeNs + 3 * bucketSizeNs - NS_PER_SEC); + // Still in the refractory period. No anomaly. + processor->OnLogEvent(release_event.get()); + EXPECT_EQ(refractory_period_sec + firedAlarmTimestampNs / NS_PER_SEC, + anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey)); + + acquire_event = CreateAcquireWakelockEvent( + attributions1, "wl1", bucketStartTimeNs + 5 * bucketSizeNs - 3 * NS_PER_SEC - 5); + processor->OnLogEvent(acquire_event.get()); + EXPECT_EQ((bucketStartTimeNs + 5 * bucketSizeNs) / NS_PER_SEC, + anomalyTracker->getAlarmTimestampSec(dimensionKey)); + + release_event = CreateReleaseWakelockEvent( + attributions1, "wl1", bucketStartTimeNs + 5 * bucketSizeNs - 3 * NS_PER_SEC - 4); + processor->OnLogEvent(release_event.get()); + EXPECT_EQ(0u, anomalyTracker->getAlarmTimestampSec(dimensionKey)); + + acquire_event = CreateAcquireWakelockEvent( + attributions1, "wl1", bucketStartTimeNs + 5 * bucketSizeNs - 3 * NS_PER_SEC - 3); + processor->OnLogEvent(acquire_event.get()); + EXPECT_EQ((bucketStartTimeNs + 5 * bucketSizeNs) / NS_PER_SEC, + anomalyTracker->getAlarmTimestampSec(dimensionKey)); +} + +#else +GTEST_LOG_(INFO) << "This test does nothing.\n"; +#endif + +} // namespace statsd +} // namespace os +} // namespace android diff --git a/cmds/statsd/tests/e2e/DimensionInCondition_e2e_combination_OR_cond_test.cpp b/cmds/statsd/tests/e2e/DimensionInCondition_e2e_combination_OR_cond_test.cpp index 2287c2bfc8903..c2334d8a3bed8 100644 --- a/cmds/statsd/tests/e2e/DimensionInCondition_e2e_combination_OR_cond_test.cpp +++ b/cmds/statsd/tests/e2e/DimensionInCondition_e2e_combination_OR_cond_test.cpp @@ -458,7 +458,7 @@ StatsdConfig CreateDurationMetricConfig_NoLink_CombinationCondition( } // namespace TEST(DimensionInConditionE2eTest, TestDurationMetric_NoLink_OR_CombinationCondition) { - for (auto aggregationType : { DurationMetric::MAX_SPARSE}) { // DurationMetric::SUM, + for (auto aggregationType : { DurationMetric::MAX_SPARSE, DurationMetric::SUM}) { ConfigKey cfgKey; auto config = CreateDurationMetricConfig_NoLink_CombinationCondition(aggregationType); int64_t bucketStartTimeNs = 10000000000; diff --git a/cmds/statsd/tests/metrics/GaugeMetricProducer_test.cpp b/cmds/statsd/tests/metrics/GaugeMetricProducer_test.cpp index 5ef84e6ac6ce9..a75d6c81e9e95 100644 --- a/cmds/statsd/tests/metrics/GaugeMetricProducer_test.cpp +++ b/cmds/statsd/tests/metrics/GaugeMetricProducer_test.cpp @@ -40,7 +40,7 @@ namespace statsd { const ConfigKey kConfigKey(0, 12345); const int tagId = 1; const int64_t metricId = 123; -const int64_t bucketStartTimeNs = 10000000000; +const int64_t bucketStartTimeNs = 10 * NS_PER_SEC; const int64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(ONE_MINUTE) * 1000000LL; const int64_t bucket2StartTimeNs = bucketStartTimeNs + bucketSizeNs; const int64_t bucket3StartTimeNs = bucketStartTimeNs + 2 * bucketSizeNs; diff --git a/cmds/statsd/tests/metrics/OringDurationTracker_test.cpp b/cmds/statsd/tests/metrics/OringDurationTracker_test.cpp index 9b27f3cde3bd3..13cdb0b7cb207 100644 --- a/cmds/statsd/tests/metrics/OringDurationTracker_test.cpp +++ b/cmds/statsd/tests/metrics/OringDurationTracker_test.cpp @@ -44,7 +44,7 @@ const HashableDimensionKey eventKey = getMockedDimensionKey(TagId, 0, "event"); const HashableDimensionKey kConditionKey1 = getMockedDimensionKey(TagId, 1, "maps"); const HashableDimensionKey kEventKey1 = getMockedDimensionKey(TagId, 2, "maps"); const HashableDimensionKey kEventKey2 = getMockedDimensionKey(TagId, 3, "maps"); -const uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL; +const uint64_t bucketSizeNs = 30 * NS_PER_SEC; TEST(OringDurationTrackerTest, TestDurationOverlap) { const MetricDimensionKey eventKey = getMockedMetricDimensionKey(TagId, 0, "event"); @@ -370,6 +370,103 @@ TEST(OringDurationTrackerTest, TestPredictAnomalyTimestamp) { tracker.predictAnomalyTimestampNs(*anomalyTracker, event3StartTimeNs)); } +TEST(OringDurationTrackerTest, TestPredictAnomalyTimestamp2) { + vector dimensionInCondition; + Alert alert; + alert.set_id(101); + alert.set_metric_id(1); + alert.set_trigger_if_sum_gt(5 * NS_PER_SEC); + alert.set_num_buckets(1); + alert.set_refractory_period_secs(20); + + uint64_t bucketStartTimeNs = 10 * NS_PER_SEC; + uint64_t bucketNum = 0; + + sp wizard = new NaggyMock(); + sp alarmMonitor; + sp anomalyTracker = + new DurationAnomalyTracker(alert, kConfigKey, alarmMonitor); + OringDurationTracker tracker(kConfigKey, metricId, DEFAULT_METRIC_DIMENSION_KEY, wizard, 1, + dimensionInCondition, + true, bucketStartTimeNs, bucketNum, bucketStartTimeNs, + bucketSizeNs, true, false, {anomalyTracker}); + + uint64_t eventStartTimeNs = bucketStartTimeNs + 9 * NS_PER_SEC; + tracker.noteStart(DEFAULT_DIMENSION_KEY, true, eventStartTimeNs, ConditionKey()); + // Anomaly happens in the bucket #1. + EXPECT_EQ((long long)(bucketStartTimeNs + 14 * NS_PER_SEC), + tracker.predictAnomalyTimestampNs(*anomalyTracker, eventStartTimeNs)); + + tracker.noteStop(DEFAULT_DIMENSION_KEY, bucketStartTimeNs + 14 * NS_PER_SEC, false); + + EXPECT_EQ((long long)(bucketStartTimeNs + 34 * NS_PER_SEC) / NS_PER_SEC, + anomalyTracker->getRefractoryPeriodEndsSec(DEFAULT_METRIC_DIMENSION_KEY)); + + uint64_t event2StartTimeNs = bucketStartTimeNs + 22 * NS_PER_SEC; + EXPECT_EQ((long long)(bucketStartTimeNs + 34 * NS_PER_SEC) / NS_PER_SEC, + anomalyTracker->getRefractoryPeriodEndsSec(DEFAULT_METRIC_DIMENSION_KEY)); + EXPECT_EQ((long long)(bucketStartTimeNs + 35 * NS_PER_SEC), + tracker.predictAnomalyTimestampNs(*anomalyTracker, event2StartTimeNs)); +} + +TEST(OringDurationTrackerTest, TestPredictAnomalyTimestamp3) { + // Test the cases where the refractory period is smaller than the bucket size, longer than + // the bucket size, and longer than 2x of the anomaly detection window. + for (int j = 0; j < 3; j++) { + uint64_t thresholdNs = j * bucketSizeNs + 5 * NS_PER_SEC; + for (int i = 0; i <= 7; ++i) { + vector dimensionInCondition; + Alert alert; + alert.set_id(101); + alert.set_metric_id(1); + alert.set_trigger_if_sum_gt(thresholdNs); + alert.set_num_buckets(3); + alert.set_refractory_period_secs( + bucketSizeNs / NS_PER_SEC / 2 + i * bucketSizeNs / NS_PER_SEC); + + uint64_t bucketStartTimeNs = 10 * NS_PER_SEC; + uint64_t bucketNum = 101; + + sp wizard = new NaggyMock(); + sp alarmMonitor; + sp anomalyTracker = + new DurationAnomalyTracker(alert, kConfigKey, alarmMonitor); + OringDurationTracker tracker(kConfigKey, metricId, DEFAULT_METRIC_DIMENSION_KEY, + wizard, 1, dimensionInCondition, + true, bucketStartTimeNs, bucketNum, bucketStartTimeNs, + bucketSizeNs, true, false, {anomalyTracker}); + + uint64_t eventStartTimeNs = bucketStartTimeNs + 9 * NS_PER_SEC; + tracker.noteStart(DEFAULT_DIMENSION_KEY, true, eventStartTimeNs, ConditionKey()); + EXPECT_EQ((long long)(eventStartTimeNs + thresholdNs), + tracker.predictAnomalyTimestampNs(*anomalyTracker, eventStartTimeNs)); + uint64_t eventStopTimeNs = eventStartTimeNs + thresholdNs + NS_PER_SEC; + tracker.noteStop(DEFAULT_DIMENSION_KEY, eventStopTimeNs, false); + + uint64_t refractoryPeriodEndSec = + anomalyTracker->getRefractoryPeriodEndsSec(DEFAULT_METRIC_DIMENSION_KEY); + EXPECT_EQ((long long)(eventStopTimeNs) / NS_PER_SEC + alert.refractory_period_secs(), + refractoryPeriodEndSec); + + // Acquire and release a wakelock in the next bucket. + uint64_t event2StartTimeNs = eventStopTimeNs + bucketSizeNs; + tracker.noteStart(DEFAULT_DIMENSION_KEY, true, event2StartTimeNs, ConditionKey()); + uint64_t event2StopTimeNs = event2StartTimeNs + 4 * NS_PER_SEC; + tracker.noteStop(DEFAULT_DIMENSION_KEY, event2StopTimeNs, false); + + // Test the alarm prediction works well when seeing another wakelock start event. + for (int k = 0; k <= 2; ++k) { + uint64_t event3StartTimeNs = event2StopTimeNs + NS_PER_SEC + k * bucketSizeNs; + uint64_t alarmTimestampNs = + tracker.predictAnomalyTimestampNs(*anomalyTracker, event3StartTimeNs); + EXPECT_GT(alarmTimestampNs, 0u); + EXPECT_GE(alarmTimestampNs, event3StartTimeNs); + EXPECT_GE(alarmTimestampNs, refractoryPeriodEndSec * NS_PER_SEC); + } + } + } +} + TEST(OringDurationTrackerTest, TestAnomalyDetectionExpiredAlarm) { const MetricDimensionKey eventKey = getMockedMetricDimensionKey(TagId, 0, "event"); diff --git a/cmds/statsd/tests/statsd_test_util.cpp b/cmds/statsd/tests/statsd_test_util.cpp index 0f785dff8e812..ce44a35cbf212 100644 --- a/cmds/statsd/tests/statsd_test_util.cpp +++ b/cmds/statsd/tests/statsd_test_util.cpp @@ -447,7 +447,9 @@ std::unique_ptr CreateIsolatedUidChangedEvent( sp CreateStatsLogProcessor(const long timeBaseSec, const StatsdConfig& config, const ConfigKey& key) { sp uidMap = new UidMap(); - sp anomalyAlarmMonitor; + sp anomalyAlarmMonitor = + new AlarmMonitor(1, [](const sp&, int64_t){}, + [](const sp&){}); sp periodicAlarmMonitor; sp processor = new StatsLogProcessor( uidMap, anomalyAlarmMonitor, periodicAlarmMonitor, timeBaseSec, [](const ConfigKey&){});