Merge "Flush the past buckets in anomaly tracker when time jumps forward" into pi-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
cdc9d9008b
@@ -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) \
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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:
|
||||
|
||||
241
cmds/statsd/tests/e2e/Anomaly_count_e2e_test.cpp
Normal file
241
cmds/statsd/tests/e2e/Anomaly_count_e2e_test.cpp
Normal 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
|
||||
486
cmds/statsd/tests/e2e/Anomaly_duration_sum_e2e_test.cpp
Normal file
486
cmds/statsd/tests/e2e/Anomaly_duration_sum_e2e_test.cpp
Normal 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
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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");
|
||||
|
||||
|
||||
@@ -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&){});
|
||||
|
||||
Reference in New Issue
Block a user