Merge "Flush the past buckets in anomaly tracker when time jumps forward" into pi-dev

This commit is contained in:
TreeHugger Robot
2018-03-27 07:21:21 +00:00
committed by Android (Google) Code Review
19 changed files with 963 additions and 69 deletions

View File

@@ -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) \

View File

@@ -69,6 +69,11 @@ public:
void dumpStates(FILE* out, bool verbose);
private:
// For testing only.
inline sp<AlarmMonitor> getAnomalyAlarmMonitor() const {
return mAnomalyAlarmMonitor;
}
mutable mutex mMetricsMutex;
std::unordered_map<ConfigKey, sp<MetricsManager>> 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

View File

@@ -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<DimToValMap>& 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;
}

View File

@@ -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

View File

@@ -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<uint32_t>((timestampNs -1)/ NS_PER_SEC) + 1; // round up
uint32_t timestampSec = static_cast<uint32_t>((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);

View File

@@ -52,6 +52,13 @@ public:
unordered_set<sp<const InternalAlarm>, SpHash<InternalAlarm>>& 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<MetricDimensionKey, sp<const InternalAlarm>> mAlarms;

View File

@@ -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;
}

View File

@@ -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

View File

@@ -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);
}
}
}
}

View File

@@ -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;

View File

@@ -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:

View File

@@ -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 {

View File

@@ -56,7 +56,7 @@ public:
std::unordered_map<MetricDimensionKey, std::vector<DurationBucket>>* 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:

View File

@@ -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 <gtest/gtest.h>
#include "src/StatsLogProcessor.h"
#include "src/stats_log_util.h"
#include "tests/statsd_test_util.h"
#include <vector>
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> anomalyTracker =
processor->mMetricsManagers.begin()->second->mAllAnomalyTrackers[0];
std::vector<AttributionNodeInternal> attributions1 = {CreateAttribution(111, "App1")};
std::vector<AttributionNodeInternal> attributions2 = {
CreateAttribution(111, "App1"), CreateAttribution(222, "GMSCoreModule1")};
std::vector<AttributionNodeInternal> attributions3 = {
CreateAttribution(111, "App1"), CreateAttribution(333, "App3")};
std::vector<AttributionNodeInternal> attributions4 = {
CreateAttribution(222, "GMSCoreModule1"), CreateAttribution(333, "App3")};
std::vector<AttributionNodeInternal> 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> anomalyTracker =
processor->mMetricsManagers.begin()->second->mAllAnomalyTrackers[0];
std::vector<AttributionNodeInternal> attributions1 = {CreateAttribution(111, "App1")};
std::vector<AttributionNodeInternal> attributions2 = {
CreateAttribution(111, "App1"), CreateAttribution(222, "GMSCoreModule1")};
std::vector<AttributionNodeInternal> attributions3 = {
CreateAttribution(111, "App1"), CreateAttribution(333, "App3")};
std::vector<AttributionNodeInternal> attributions4 = {
CreateAttribution(222, "GMSCoreModule1"), CreateAttribution(333, "App3")};
std::vector<AttributionNodeInternal> 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

View File

@@ -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 <gtest/gtest.h>
#include "src/anomaly/DurationAnomalyTracker.h"
#include "src/StatsLogProcessor.h"
#include "src/stats_log_util.h"
#include "tests/statsd_test_util.h"
#include <vector>
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<AttributionNodeInternal> attributions1 = {CreateAttribution(111, "App1"),
CreateAttribution(222, "GMSCoreModule1")};
std::vector<AttributionNodeInternal> attributions2 = {CreateAttribution(111, "App2"),
CreateAttribution(222, "GMSCoreModule1")};
std::vector<AttributionNodeInternal> 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> 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<uint32_t>(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<uint32_t>(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> 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> 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<uint32_t>(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

View File

@@ -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;

View File

@@ -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;

View File

@@ -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<Matcher> 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<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
sp<AlarmMonitor> alarmMonitor;
sp<DurationAnomalyTracker> 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<Matcher> 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<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
sp<AlarmMonitor> alarmMonitor;
sp<DurationAnomalyTracker> 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");

View File

@@ -447,7 +447,9 @@ std::unique_ptr<LogEvent> CreateIsolatedUidChangedEvent(
sp<StatsLogProcessor> CreateStatsLogProcessor(const long timeBaseSec, const StatsdConfig& config,
const ConfigKey& key) {
sp<UidMap> uidMap = new UidMap();
sp<AlarmMonitor> anomalyAlarmMonitor;
sp<AlarmMonitor> anomalyAlarmMonitor =
new AlarmMonitor(1, [](const sp<IStatsCompanionService>&, int64_t){},
[](const sp<IStatsCompanionService>&){});
sp<AlarmMonitor> periodicAlarmMonitor;
sp<StatsLogProcessor> processor = new StatsLogProcessor(
uidMap, anomalyAlarmMonitor, periodicAlarmMonitor, timeBaseSec, [](const ConfigKey&){});