1/ Duration anomaly tracker with alarm.
2/ Init anomaly from config based on the public language.
3/ Unit tests for anomaly detection in count/gauge producer.
4/ Revisit the duration tracker logic.
Test: unit test passed.
Change-Id: I2423c0e0f05b1e37626954de9e749303423963f2
diff --git a/cmds/statsd/tests/AnomalyMonitor_test.cpp b/cmds/statsd/tests/AnomalyMonitor_test.cpp
index 59fa160..920ca08 100644
--- a/cmds/statsd/tests/AnomalyMonitor_test.cpp
+++ b/cmds/statsd/tests/AnomalyMonitor_test.cpp
@@ -20,6 +20,8 @@
#ifdef __ANDROID__
TEST(AnomalyMonitor, popSoonerThan) {
+ std::string emptyMetricId;
+ std::string emptyDimensionId;
unordered_set<sp<const AnomalyAlarm>, SpHash<AnomalyAlarm>> set;
AnomalyMonitor am(2);
diff --git a/cmds/statsd/tests/MetricsManager_test.cpp b/cmds/statsd/tests/MetricsManager_test.cpp
index caa1cf4..3dd4e70 100644
--- a/cmds/statsd/tests/MetricsManager_test.cpp
+++ b/cmds/statsd/tests/MetricsManager_test.cpp
@@ -34,6 +34,7 @@
using std::set;
using std::unordered_map;
using std::vector;
+using android::os::statsd::Condition;
#ifdef __ANDROID__
@@ -71,6 +72,19 @@
combination->add_matcher("SCREEN_IS_ON");
combination->add_matcher("SCREEN_IS_OFF");
+ CountMetric* metric = config.add_count_metric();
+ metric->set_name("3");
+ metric->set_what("SCREEN_IS_ON");
+ metric->mutable_bucket()->set_bucket_size_millis(30 * 1000L);
+ KeyMatcher* keyMatcher = metric->add_dimension();
+ keyMatcher->set_key(1);
+
+ auto alert = config.add_alert();
+ alert->set_name("3");
+ alert->set_metric_name("3");
+ alert->set_number_of_buckets(10);
+ alert->set_refractory_period_secs(100);
+ alert->set_trigger_if_sum_gt(100);
return config;
}
@@ -100,6 +114,29 @@
return config;
}
+StatsdConfig buildAlertWithUnknownMetric() {
+ StatsdConfig config;
+ config.set_name("12345");
+
+ LogEntryMatcher* eventMatcher = config.add_log_entry_matcher();
+ eventMatcher->set_name("SCREEN_IS_ON");
+
+ CountMetric* metric = config.add_count_metric();
+ metric->set_name("3");
+ metric->set_what("SCREEN_IS_ON");
+ metric->mutable_bucket()->set_bucket_size_millis(30 * 1000L);
+ KeyMatcher* keyMatcher = metric->add_dimension();
+ keyMatcher->set_key(1);
+
+ auto alert = config.add_alert();
+ alert->set_name("3");
+ alert->set_metric_name("2");
+ alert->set_number_of_buckets(10);
+ alert->set_refractory_period_secs(100);
+ alert->set_trigger_if_sum_gt(100);
+ return config;
+}
+
StatsdConfig buildMissingMatchers() {
StatsdConfig config;
config.set_name("12345");
@@ -156,6 +193,12 @@
KeyMatcher* keyMatcher = metric->add_dimension();
keyMatcher->set_key(1);
+ auto alert = config.add_alert();
+ alert->set_name("3");
+ alert->set_metric_name("3");
+ alert->set_number_of_buckets(10);
+ alert->set_refractory_period_secs(100);
+ alert->set_trigger_if_sum_gt(100);
return config;
}
@@ -183,7 +226,7 @@
simpleLogEntryMatcher->mutable_key_value_matcher(0)->set_eq_int(
1 /*SCREEN_STATE_CHANGE__DISPLAY_STATE__STATE_OFF*/);
- Condition* condition = config.add_condition();
+ auto condition = config.add_condition();
condition->set_name("SCREEN_IS_ON");
SimpleCondition* simpleCondition = condition->mutable_simple_condition();
simpleCondition->set_start("SCREEN_IS_ON");
@@ -206,13 +249,16 @@
vector<sp<LogMatchingTracker>> allLogEntryMatchers;
vector<sp<ConditionTracker>> allConditionTrackers;
vector<sp<MetricProducer>> allMetricProducers;
+ std::vector<sp<AnomalyTracker>> allAnomalyTrackers;
unordered_map<int, std::vector<int>> conditionToMetricMap;
unordered_map<int, std::vector<int>> trackerToMetricMap;
unordered_map<int, std::vector<int>> trackerToConditionMap;
EXPECT_TRUE(initStatsdConfig(config, allTagIds, allLogEntryMatchers, allConditionTrackers,
- allMetricProducers, conditionToMetricMap, trackerToMetricMap,
- trackerToConditionMap));
+ allMetricProducers, allAnomalyTrackers, conditionToMetricMap,
+ trackerToMetricMap, trackerToConditionMap));
+ EXPECT_EQ(1u, allMetricProducers.size());
+ EXPECT_EQ(1u, allAnomalyTrackers.size());
}
TEST(MetricsManagerTest, TestDimensionMetricsWithMultiTags) {
@@ -221,13 +267,14 @@
vector<sp<LogMatchingTracker>> allLogEntryMatchers;
vector<sp<ConditionTracker>> allConditionTrackers;
vector<sp<MetricProducer>> allMetricProducers;
+ std::vector<sp<AnomalyTracker>> allAnomalyTrackers;
unordered_map<int, std::vector<int>> conditionToMetricMap;
unordered_map<int, std::vector<int>> trackerToMetricMap;
unordered_map<int, std::vector<int>> trackerToConditionMap;
EXPECT_FALSE(initStatsdConfig(config, allTagIds, allLogEntryMatchers, allConditionTrackers,
- allMetricProducers, conditionToMetricMap, trackerToMetricMap,
- trackerToConditionMap));
+ allMetricProducers, allAnomalyTrackers, conditionToMetricMap,
+ trackerToMetricMap, trackerToConditionMap));
}
TEST(MetricsManagerTest, TestCircleLogMatcherDependency) {
@@ -236,13 +283,14 @@
vector<sp<LogMatchingTracker>> allLogEntryMatchers;
vector<sp<ConditionTracker>> allConditionTrackers;
vector<sp<MetricProducer>> allMetricProducers;
+ std::vector<sp<AnomalyTracker>> allAnomalyTrackers;
unordered_map<int, std::vector<int>> conditionToMetricMap;
unordered_map<int, std::vector<int>> trackerToMetricMap;
unordered_map<int, std::vector<int>> trackerToConditionMap;
EXPECT_FALSE(initStatsdConfig(config, allTagIds, allLogEntryMatchers, allConditionTrackers,
- allMetricProducers, conditionToMetricMap, trackerToMetricMap,
- trackerToConditionMap));
+ allMetricProducers, allAnomalyTrackers, conditionToMetricMap,
+ trackerToMetricMap, trackerToConditionMap));
}
TEST(MetricsManagerTest, TestMissingMatchers) {
@@ -251,13 +299,13 @@
vector<sp<LogMatchingTracker>> allLogEntryMatchers;
vector<sp<ConditionTracker>> allConditionTrackers;
vector<sp<MetricProducer>> allMetricProducers;
+ std::vector<sp<AnomalyTracker>> allAnomalyTrackers;
unordered_map<int, std::vector<int>> conditionToMetricMap;
unordered_map<int, std::vector<int>> trackerToMetricMap;
unordered_map<int, std::vector<int>> trackerToConditionMap;
-
EXPECT_FALSE(initStatsdConfig(config, allTagIds, allLogEntryMatchers, allConditionTrackers,
- allMetricProducers, conditionToMetricMap, trackerToMetricMap,
- trackerToConditionMap));
+ allMetricProducers, allAnomalyTrackers, conditionToMetricMap,
+ trackerToMetricMap, trackerToConditionMap));
}
TEST(MetricsManagerTest, TestCircleConditionDependency) {
@@ -266,13 +314,30 @@
vector<sp<LogMatchingTracker>> allLogEntryMatchers;
vector<sp<ConditionTracker>> allConditionTrackers;
vector<sp<MetricProducer>> allMetricProducers;
+ std::vector<sp<AnomalyTracker>> allAnomalyTrackers;
unordered_map<int, std::vector<int>> conditionToMetricMap;
unordered_map<int, std::vector<int>> trackerToMetricMap;
unordered_map<int, std::vector<int>> trackerToConditionMap;
EXPECT_FALSE(initStatsdConfig(config, allTagIds, allLogEntryMatchers, allConditionTrackers,
- allMetricProducers, conditionToMetricMap, trackerToMetricMap,
- trackerToConditionMap));
+ allMetricProducers, allAnomalyTrackers, conditionToMetricMap,
+ trackerToMetricMap, trackerToConditionMap));
+}
+
+TEST(MetricsManagerTest, testAlertWithUnknownMetric) {
+ StatsdConfig config = buildAlertWithUnknownMetric();
+ set<int> allTagIds;
+ vector<sp<LogMatchingTracker>> allLogEntryMatchers;
+ vector<sp<ConditionTracker>> allConditionTrackers;
+ vector<sp<MetricProducer>> allMetricProducers;
+ std::vector<sp<AnomalyTracker>> allAnomalyTrackers;
+ unordered_map<int, std::vector<int>> conditionToMetricMap;
+ unordered_map<int, std::vector<int>> trackerToMetricMap;
+ unordered_map<int, std::vector<int>> trackerToConditionMap;
+
+ EXPECT_FALSE(initStatsdConfig(config, allTagIds, allLogEntryMatchers, allConditionTrackers,
+ allMetricProducers, allAnomalyTrackers, conditionToMetricMap,
+ trackerToMetricMap, trackerToConditionMap));
}
#else
diff --git a/cmds/statsd/tests/UidMap_test.cpp b/cmds/statsd/tests/UidMap_test.cpp
index 4c12b03..0c19468 100644
--- a/cmds/statsd/tests/UidMap_test.cpp
+++ b/cmds/statsd/tests/UidMap_test.cpp
@@ -34,7 +34,8 @@
TEST(UidMapTest, TestIsolatedUID) {
sp<UidMap> m = new UidMap();
- StatsLogProcessor p(m, nullptr);
+ sp<AnomalyMonitor> anomalyMonitor;
+ StatsLogProcessor p(m, anomalyMonitor, nullptr);
LogEvent addEvent(android::util::ISOLATED_UID_CHANGED, 1);
addEvent.write(100); // parent UID
addEvent.write(101); // isolated UID
diff --git a/cmds/statsd/tests/anomaly/AnomalyTracker_test.cpp b/cmds/statsd/tests/anomaly/AnomalyTracker_test.cpp
index b8150d0..e0200f27 100644
--- a/cmds/statsd/tests/anomaly/AnomalyTracker_test.cpp
+++ b/cmds/statsd/tests/anomaly/AnomalyTracker_test.cpp
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-#include "src/anomaly/DiscreteAnomalyTracker.h"
+#include "src/anomaly/AnomalyTracker.h"
#include <gtest/gtest.h>
#include <stdio.h>
@@ -37,7 +37,7 @@
}
}
-std::shared_ptr<DimToValMap> MockeBucket(
+std::shared_ptr<DimToValMap> MockBucket(
const std::vector<std::pair<string, long>>& key_value_pair_list) {
std::shared_ptr<DimToValMap> bucket = std::make_shared<DimToValMap>();
AddValueToBucket(key_value_pair_list, bucket);
@@ -45,190 +45,240 @@
}
TEST(AnomalyTrackerTest, TestConsecutiveBuckets) {
+ const int64_t bucketSizeNs = 30 * NS_PER_SEC;
Alert alert;
alert.set_number_of_buckets(3);
- alert.set_refractory_period_in_buckets(3);
+ alert.set_refractory_period_secs(2 * bucketSizeNs / NS_PER_SEC);
alert.set_trigger_if_sum_gt(2);
- DiscreteAnomalyTracker anomaly_tracker(alert);
+ AnomalyTracker anomalyTracker(alert, bucketSizeNs);
- std::shared_ptr<DimToValMap> bucket0 = MockeBucket({{"a", 1}, {"b", 2}, {"c", 1}});
- // Adds bucket #0
- anomaly_tracker.addOrUpdateBucket(bucket0, 0);
- EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.size(), 3UL);
- EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.find("a")->second, 1);
- EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.find("b")->second, 2);
- EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.find("c")->second, 1);
- EXPECT_FALSE(anomaly_tracker.detectAnomaly());
- anomaly_tracker.declareAndDeclareAnomaly();
- EXPECT_EQ(anomaly_tracker.mAnomalyDeclared, 0L);
+ std::shared_ptr<DimToValMap> bucket0 = MockBucket({{"a", 1}, {"b", 2}, {"c", 1}});
+ int64_t eventTimestamp0 = 10;
+ std::shared_ptr<DimToValMap> bucket1 = MockBucket({{"a", 1}});
+ int64_t eventTimestamp1 = bucketSizeNs + 11;
+ std::shared_ptr<DimToValMap> bucket2 = MockBucket({{"b", 1}});
+ int64_t eventTimestamp2 = 2 * bucketSizeNs + 12;
+ std::shared_ptr<DimToValMap> bucket3 = MockBucket({{"a", 2}});
+ int64_t eventTimestamp3 = 3 * bucketSizeNs + 13;
+ std::shared_ptr<DimToValMap> bucket4 = MockBucket({{"b", 1}});
+ int64_t eventTimestamp4 = 4 * bucketSizeNs + 14;
+ std::shared_ptr<DimToValMap> bucket5 = MockBucket({{"a", 2}});
+ int64_t eventTimestamp5 = 5 * bucketSizeNs + 15;
+ std::shared_ptr<DimToValMap> bucket6 = MockBucket({{"a", 2}});
+ int64_t eventTimestamp6 = 6 * bucketSizeNs + 16;
- // Adds bucket #0 again. The sum does not change.
- anomaly_tracker.addOrUpdateBucket(bucket0, 0);
- EXPECT_EQ(anomaly_tracker.mCurrentBucketIndex, 0L);
- EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.size(), 3UL);
- EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.find("a")->second, 1);
- EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.find("b")->second, 2);
- EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.find("c")->second, 1);
- EXPECT_FALSE(anomaly_tracker.detectAnomaly());
- anomaly_tracker.declareAndDeclareAnomaly();
- EXPECT_EQ(anomaly_tracker.mAnomalyDeclared, 0L);
- EXPECT_EQ(anomaly_tracker.mLastAlarmAtBucketIndex, -1L);
+ EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 0u);
+ EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, -1LL);
+ EXPECT_FALSE(anomalyTracker.detectAnomaly(0, *bucket0));
+ anomalyTracker.detectAndDeclareAnomaly(eventTimestamp0, 0, *bucket0);
+ EXPECT_EQ(anomalyTracker.mLastAlarmTimestampNs, -1L);
- // Adds bucket #1.
- std::shared_ptr<DimToValMap> bucket1 = MockeBucket({{"b", 2}});
- anomaly_tracker.addOrUpdateBucket(bucket1, 1);
- EXPECT_EQ(anomaly_tracker.mCurrentBucketIndex, 1L);
- EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.size(), 3UL);
- EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.find("a")->second, 1);
- EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.find("b")->second, 4);
- EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.find("c")->second, 1);
- // Alarm.
- EXPECT_TRUE(anomaly_tracker.detectAnomaly());
- anomaly_tracker.declareAndDeclareAnomaly();
- EXPECT_EQ(anomaly_tracker.mAnomalyDeclared, 1L);
- EXPECT_EQ(anomaly_tracker.mLastAlarmAtBucketIndex, 1L);
+ // Adds past bucket #0
+ anomalyTracker.addPastBucket(bucket0, 0);
+ EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 3u);
+ EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("a"), 1LL);
+ EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("b"), 2LL);
+ EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("c"), 1LL);
+ EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 0LL);
+ EXPECT_FALSE(anomalyTracker.detectAnomaly(1, *bucket1));
+ anomalyTracker.detectAndDeclareAnomaly(eventTimestamp1, 1, *bucket1);
+ EXPECT_EQ(anomalyTracker.mLastAlarmTimestampNs, -1L);
- // Adds bucket #1 again. The sum does not change.
- anomaly_tracker.addOrUpdateBucket(bucket1, 1);
- EXPECT_EQ(anomaly_tracker.mCurrentBucketIndex, 1L);
- EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.size(), 3UL);
- EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.find("a")->second, 1);
- EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.find("b")->second, 4);
- EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.find("c")->second, 1);
- // Alarm.
- EXPECT_TRUE(anomaly_tracker.detectAnomaly());
- anomaly_tracker.declareAndDeclareAnomaly();
- EXPECT_EQ(anomaly_tracker.mAnomalyDeclared, 1L);
- EXPECT_EQ(anomaly_tracker.mLastAlarmAtBucketIndex, 1L);
+ // Adds past bucket #0 again. The sum does not change.
+ anomalyTracker.addPastBucket(bucket0, 0);
+ EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 3u);
+ EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("a"), 1LL);
+ EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("b"), 2LL);
+ EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("c"), 1LL);
+ EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 0LL);
+ EXPECT_FALSE(anomalyTracker.detectAnomaly(1, *bucket1));
+ anomalyTracker.detectAndDeclareAnomaly(eventTimestamp1 + 1, 1, *bucket1);
+ EXPECT_EQ(anomalyTracker.mLastAlarmTimestampNs, -1L);
- // Adds bucket #2.
- anomaly_tracker.addOrUpdateBucket(MockeBucket({{"a", 1}}), 2);
- EXPECT_EQ(anomaly_tracker.mCurrentBucketIndex, 2L);
- EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.size(), 3UL);
- EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.find("a")->second, 2);
- EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.find("b")->second, 4);
- EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.find("c")->second, 1);
- EXPECT_TRUE(anomaly_tracker.detectAnomaly());
+ // Adds past bucket #1.
+ anomalyTracker.addPastBucket(bucket1, 1);
+ EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 1L);
+ EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 3UL);
+ EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("a"), 2LL);
+ EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("b"), 2LL);
+ EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("c"), 1LL);
+ EXPECT_TRUE(anomalyTracker.detectAnomaly(2, *bucket2));
+ anomalyTracker.detectAndDeclareAnomaly(eventTimestamp2, 2, *bucket2);
+ EXPECT_EQ(anomalyTracker.mLastAlarmTimestampNs, eventTimestamp2);
+
+ // Adds past bucket #1 again. Nothing changes.
+ anomalyTracker.addPastBucket(bucket1, 1);
+ EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 1L);
+ EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 3UL);
+ EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("a"), 2LL);
+ EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("b"), 2LL);
+ EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("c"), 1LL);
+ EXPECT_TRUE(anomalyTracker.detectAnomaly(2, *bucket2));
+ anomalyTracker.detectAndDeclareAnomaly(eventTimestamp2 + 1, 2, *bucket2);
+ EXPECT_EQ(anomalyTracker.mLastAlarmTimestampNs, eventTimestamp2);
+
+ // Adds past bucket #2.
+ anomalyTracker.addPastBucket(bucket2, 2);
+ EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 2L);
+ EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 2UL);
+ EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("a"), 1LL);
+ EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("b"), 1LL);
+ EXPECT_TRUE(anomalyTracker.detectAnomaly(3, *bucket3));
+ anomalyTracker.detectAndDeclareAnomaly(eventTimestamp3, 3, *bucket3);
// Within refractory period.
- anomaly_tracker.declareAndDeclareAnomaly();
- EXPECT_EQ(anomaly_tracker.mAnomalyDeclared, 1L);
- EXPECT_EQ(anomaly_tracker.mLastAlarmAtBucketIndex, 1L);
+ EXPECT_EQ(anomalyTracker.mLastAlarmTimestampNs, eventTimestamp2);
// Adds bucket #3.
- anomaly_tracker.addOrUpdateBucket(MockeBucket({{"a", 1}}), 3);
- EXPECT_EQ(anomaly_tracker.mCurrentBucketIndex, 3L);
- EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.size(), 2UL);
- EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.find("a")->second, 2);
- EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.find("b")->second, 2);
- EXPECT_FALSE(anomaly_tracker.detectAnomaly());
+ anomalyTracker.addPastBucket(bucket3, 3L);
+ EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 3L);
+ EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 2UL);
+ EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("a"), 2LL);
+ EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("b"), 1LL);
+ EXPECT_FALSE(anomalyTracker.detectAnomaly(4, *bucket4));
+ anomalyTracker.detectAndDeclareAnomaly(eventTimestamp4, 4, *bucket4);
+ EXPECT_EQ(anomalyTracker.mLastAlarmTimestampNs, eventTimestamp2);
- // Adds bucket #3.
- anomaly_tracker.addOrUpdateBucket(MockeBucket({{"a", 2}}), 4);
- EXPECT_EQ(anomaly_tracker.mCurrentBucketIndex, 4L);
- EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.size(), 1UL);
- EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.find("a")->second, 4);
- EXPECT_TRUE(anomaly_tracker.detectAnomaly());
- // Within refractory period.
- anomaly_tracker.declareAndDeclareAnomaly();
- EXPECT_EQ(anomaly_tracker.mAnomalyDeclared, 1L);
- EXPECT_EQ(anomaly_tracker.mLastAlarmAtBucketIndex, 1L);
+ // Adds bucket #4.
+ anomalyTracker.addPastBucket(bucket4, 4);
+ EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 4L);
+ EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 2UL);
+ EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("a"), 2LL);
+ EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("b"), 1LL);
+ EXPECT_TRUE(anomalyTracker.detectAnomaly(5, *bucket5));
+ anomalyTracker.detectAndDeclareAnomaly(eventTimestamp5, 5, *bucket5);
+ EXPECT_EQ(anomalyTracker.mLastAlarmTimestampNs, eventTimestamp5);
- anomaly_tracker.addOrUpdateBucket(MockeBucket({{"a", 1}}), 5);
- EXPECT_EQ(anomaly_tracker.mCurrentBucketIndex, 5L);
- EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.size(), 1UL);
- EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.find("a")->second, 4);
- EXPECT_TRUE(anomaly_tracker.detectAnomaly());
+ // Adds bucket #5.
+ anomalyTracker.addPastBucket(bucket5, 5);
+ EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 5L);
+ EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 2UL);
+ EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("a"), 2LL);
+ EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("b"), 1LL);
+ EXPECT_TRUE(anomalyTracker.detectAnomaly(6, *bucket6));
// Within refractory period.
- anomaly_tracker.declareAndDeclareAnomaly();
- EXPECT_EQ(anomaly_tracker.mAnomalyDeclared, 2L);
- EXPECT_EQ(anomaly_tracker.mLastAlarmAtBucketIndex, 5L);
+ anomalyTracker.detectAndDeclareAnomaly(eventTimestamp6, 6, *bucket6);
+ EXPECT_EQ(anomalyTracker.mLastAlarmTimestampNs, eventTimestamp5);
}
TEST(AnomalyTrackerTest, TestSparseBuckets) {
+ const int64_t bucketSizeNs = 30 * NS_PER_SEC;
Alert alert;
alert.set_number_of_buckets(3);
- alert.set_refractory_period_in_buckets(3);
+ alert.set_refractory_period_secs(2 * bucketSizeNs / NS_PER_SEC);
alert.set_trigger_if_sum_gt(2);
- DiscreteAnomalyTracker anomaly_tracker(alert);
+ AnomalyTracker anomalyTracker(alert, bucketSizeNs);
- // Add bucket #9
- anomaly_tracker.addOrUpdateBucket(MockeBucket({{"a", 1}, {"b", 2}, {"c", 1}}), 9);
- EXPECT_EQ(anomaly_tracker.mCurrentBucketIndex, 9L);
- EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.size(), 3UL);
- EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.find("a")->second, 1);
- EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.find("b")->second, 2);
- EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.find("c")->second, 1);
- EXPECT_FALSE(anomaly_tracker.detectAnomaly());
- anomaly_tracker.declareAndDeclareAnomaly();
- EXPECT_EQ(anomaly_tracker.mAnomalyDeclared, 0L);
- EXPECT_EQ(anomaly_tracker.mLastAlarmAtBucketIndex, -1L);
+ std::shared_ptr<DimToValMap> bucket9 = MockBucket({{"a", 1}, {"b", 2}, {"c", 1}});
+ std::shared_ptr<DimToValMap> bucket16 = MockBucket({{"b", 4}});
+ std::shared_ptr<DimToValMap> bucket18 = MockBucket({{"b", 1}, {"c", 1}});
+ std::shared_ptr<DimToValMap> bucket20 = MockBucket({{"b", 3}, {"c", 1}});
+ std::shared_ptr<DimToValMap> bucket25 = MockBucket({{"d", 1}});
+ std::shared_ptr<DimToValMap> bucket28 = MockBucket({{"e", 2}});
- // Add bucket #16
- anomaly_tracker.addOrUpdateBucket(MockeBucket({{"b", 4}}), 16);
- EXPECT_EQ(anomaly_tracker.mCurrentBucketIndex, 16L);
- EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.size(), 1UL);
- EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.find("b")->second, 4);
- EXPECT_TRUE(anomaly_tracker.detectAnomaly());
- anomaly_tracker.declareAndDeclareAnomaly();
- EXPECT_EQ(anomaly_tracker.mAnomalyDeclared, 1L);
- EXPECT_EQ(anomaly_tracker.mLastAlarmAtBucketIndex, 16L);
+ int64_t eventTimestamp1 = bucketSizeNs * 8 + 1;
+ int64_t eventTimestamp2 = bucketSizeNs * 15 + 11;
+ int64_t eventTimestamp3 = bucketSizeNs * 17 + 1;
+ int64_t eventTimestamp4 = bucketSizeNs * 19 + 2;
+ int64_t eventTimestamp5 = bucketSizeNs * 24 + 3;
+ int64_t eventTimestamp6 = bucketSizeNs * 27 + 3;
- // Add bucket #18
- anomaly_tracker.addOrUpdateBucket(MockeBucket({{"b", 1}, {"c", 1}}), 18);
- EXPECT_EQ(anomaly_tracker.mCurrentBucketIndex, 18L);
- EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.size(), 2UL);
- EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.find("b")->second, 5);
- EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.find("c")->second, 1);
- EXPECT_TRUE(anomaly_tracker.detectAnomaly());
- anomaly_tracker.declareAndDeclareAnomaly();
+ EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, -1LL);
+ EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 0UL);
+ EXPECT_FALSE(anomalyTracker.detectAnomaly(9, *bucket9));
+ anomalyTracker.detectAndDeclareAnomaly(eventTimestamp1, 9, *bucket9);
+ EXPECT_EQ(anomalyTracker.mLastAlarmTimestampNs, -1);
+
+ // Add past bucket #9
+ anomalyTracker.addPastBucket(bucket9, 9);
+ EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 9L);
+ EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 3UL);
+ EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("a"), 1LL);
+ EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("b"), 2LL);
+ EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("c"), 1LL);
+ EXPECT_TRUE(anomalyTracker.detectAnomaly(16, *bucket16));
+ EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 0UL);
+ EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 15L);
+ anomalyTracker.detectAndDeclareAnomaly(eventTimestamp2, 16, *bucket16);
+ EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 0UL);
+ EXPECT_EQ(anomalyTracker.mLastAlarmTimestampNs, eventTimestamp2);
+ EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 15L);
+
+ // Add past bucket #16
+ anomalyTracker.addPastBucket(bucket16, 16);
+ EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 16L);
+ EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 1UL);
+ EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("b"), 4LL);
+ EXPECT_TRUE(anomalyTracker.detectAnomaly(18, *bucket18));
+ EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 1UL);
+ EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("b"), 4LL);
// Within refractory period.
- EXPECT_EQ(anomaly_tracker.mAnomalyDeclared, 1L);
- EXPECT_EQ(anomaly_tracker.mLastAlarmAtBucketIndex, 16L);
+ anomalyTracker.detectAndDeclareAnomaly(eventTimestamp3, 18, *bucket18);
+ EXPECT_EQ(anomalyTracker.mLastAlarmTimestampNs, eventTimestamp2);
+ EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 1UL);
+ EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("b"), 4LL);
- // Add bucket #18 again.
- anomaly_tracker.addOrUpdateBucket(MockeBucket({{"b", 1}, {"c", 1}}), 18);
- EXPECT_EQ(anomaly_tracker.mCurrentBucketIndex, 18L);
- EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.size(), 2UL);
- EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.find("b")->second, 5);
- EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.find("c")->second, 1);
- EXPECT_TRUE(anomaly_tracker.detectAnomaly());
- anomaly_tracker.declareAndDeclareAnomaly();
- EXPECT_EQ(anomaly_tracker.mAnomalyDeclared, 1L);
- EXPECT_EQ(anomaly_tracker.mLastAlarmAtBucketIndex, 16L);
+ // Add past bucket #18
+ anomalyTracker.addPastBucket(bucket18, 18);
+ EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 18L);
+ EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 2UL);
+ EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("b"), 1LL);
+ EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("c"), 1LL);
+ EXPECT_TRUE(anomalyTracker.detectAnomaly(20, *bucket20));
+ EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 19L);
+ EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 2UL);
+ EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("b"), 1LL);
+ EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("c"), 1LL);
+ anomalyTracker.detectAndDeclareAnomaly(eventTimestamp4, 20, *bucket20);
+ EXPECT_EQ(anomalyTracker.mLastAlarmTimestampNs, eventTimestamp4);
- // Add bucket #20
- anomaly_tracker.addOrUpdateBucket(MockeBucket({{"b", 3}, {"d", 1}}), 20);
- EXPECT_EQ(anomaly_tracker.mCurrentBucketIndex, 20L);
- EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.size(), 3UL);
- EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.find("b")->second, 4);
- EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.find("c")->second, 1);
- EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.find("d")->second, 1);
- EXPECT_TRUE(anomaly_tracker.detectAnomaly());
- anomaly_tracker.declareAndDeclareAnomaly();
- EXPECT_EQ(anomaly_tracker.mAnomalyDeclared, 2L);
- EXPECT_EQ(anomaly_tracker.mLastAlarmAtBucketIndex, 20L);
+ // Add bucket #18 again. Nothing changes.
+ anomalyTracker.addPastBucket(bucket18, 18);
+ EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 19L);
+ EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 2UL);
+ EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("b"), 1LL);
+ EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("c"), 1LL);
+ EXPECT_TRUE(anomalyTracker.detectAnomaly(20, *bucket20));
+ EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 2UL);
+ EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("b"), 1LL);
+ EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("c"), 1LL);
+ anomalyTracker.detectAndDeclareAnomaly(eventTimestamp4 + 1, 20, *bucket20);
+ // Within refractory period.
+ EXPECT_EQ(anomalyTracker.mLastAlarmTimestampNs, eventTimestamp4);
- // Add bucket #25
- anomaly_tracker.addOrUpdateBucket(MockeBucket({{"d", 1}}), 25);
- EXPECT_EQ(anomaly_tracker.mCurrentBucketIndex, 25L);
- EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.size(), 1UL);
- EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.find("d")->second, 1L);
- EXPECT_FALSE(anomaly_tracker.detectAnomaly());
- anomaly_tracker.declareAndDeclareAnomaly();
- EXPECT_EQ(anomaly_tracker.mAnomalyDeclared, 2L);
- EXPECT_EQ(anomaly_tracker.mLastAlarmAtBucketIndex, 20L);
+ // Add past bucket #20
+ anomalyTracker.addPastBucket(bucket20, 20);
+ EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 20L);
+ EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 2UL);
+ EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("b"), 3LL);
+ EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("c"), 1LL);
+ EXPECT_FALSE(anomalyTracker.detectAnomaly(25, *bucket25));
+ EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 24L);
+ EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 0UL);
+ anomalyTracker.detectAndDeclareAnomaly(eventTimestamp5, 25, *bucket25);
+ EXPECT_EQ(anomalyTracker.mLastAlarmTimestampNs, eventTimestamp4);
- // Add bucket #28
- anomaly_tracker.addOrUpdateBucket(MockeBucket({{"e", 5}}), 28);
- EXPECT_EQ(anomaly_tracker.mCurrentBucketIndex, 28L);
- EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.size(), 1UL);
- EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.find("e")->second, 5L);
- EXPECT_TRUE(anomaly_tracker.detectAnomaly());
- anomaly_tracker.declareAndDeclareAnomaly();
- EXPECT_EQ(anomaly_tracker.mAnomalyDeclared, 3L);
- EXPECT_EQ(anomaly_tracker.mLastAlarmAtBucketIndex, 28L);
+ // Add past bucket #25
+ anomalyTracker.addPastBucket(bucket25, 25);
+ EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 25L);
+ EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 1UL);
+ EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("d"), 1LL);
+ EXPECT_FALSE(anomalyTracker.detectAnomaly(28, *bucket28));
+ EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 27L);
+ EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 0UL);
+ anomalyTracker.detectAndDeclareAnomaly(eventTimestamp6, 28, *bucket28);
+ EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 0UL);
+ EXPECT_EQ(anomalyTracker.mLastAlarmTimestampNs, eventTimestamp4);
+
+ // Updates current bucket #28.
+ (*bucket28)["e"] = 5;
+ EXPECT_TRUE(anomalyTracker.detectAnomaly(28, *bucket28));
+ EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 27L);
+ EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 0UL);
+ anomalyTracker.detectAndDeclareAnomaly(eventTimestamp6 + 7, 28, *bucket28);
+ EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 0UL);
+ EXPECT_EQ(anomalyTracker.mLastAlarmTimestampNs, eventTimestamp6 + 7);
}
} // namespace statsd
diff --git a/cmds/statsd/tests/indexed_priority_queue_test.cpp b/cmds/statsd/tests/indexed_priority_queue_test.cpp
index 600b953..d6cd876 100644
--- a/cmds/statsd/tests/indexed_priority_queue_test.cpp
+++ b/cmds/statsd/tests/indexed_priority_queue_test.cpp
@@ -22,10 +22,12 @@
/** struct for template in indexed_priority_queue */
struct AATest : public RefBase {
- AATest(uint32_t val) : val(val) {
+ AATest(uint32_t val, std::string a, std::string b) : val(val), a(a), b(b) {
}
const int val;
+ const std::string a;
+ const std::string b;
struct Smaller {
bool operator()(const sp<const AATest> a, const sp<const AATest> b) const {
@@ -36,9 +38,11 @@
#ifdef __ANDROID__
TEST(indexed_priority_queue, empty_and_size) {
+ std::string emptyMetricId;
+ std::string emptyDimensionId;
indexed_priority_queue<AATest, AATest::Smaller> ipq;
- sp<const AATest> aa4 = new AATest{4};
- sp<const AATest> aa8 = new AATest{8};
+ sp<const AATest> aa4 = new AATest{4, emptyMetricId, emptyDimensionId};
+ sp<const AATest> aa8 = new AATest{8, emptyMetricId, emptyDimensionId};
EXPECT_EQ(0u, ipq.size());
EXPECT_TRUE(ipq.empty());
@@ -61,13 +65,15 @@
}
TEST(indexed_priority_queue, top) {
+ std::string emptyMetricId;
+ std::string emptyDimensionId;
indexed_priority_queue<AATest, AATest::Smaller> ipq;
- sp<const AATest> aa2 = new AATest{2};
- sp<const AATest> aa4 = new AATest{4};
- sp<const AATest> aa8 = new AATest{8};
- sp<const AATest> aa12 = new AATest{12};
- sp<const AATest> aa16 = new AATest{16};
- sp<const AATest> aa20 = new AATest{20};
+ sp<const AATest> aa2 = new AATest{2, emptyMetricId, emptyDimensionId};
+ sp<const AATest> aa4 = new AATest{4, emptyMetricId, emptyDimensionId};
+ sp<const AATest> aa8 = new AATest{8, emptyMetricId, emptyDimensionId};
+ sp<const AATest> aa12 = new AATest{12, emptyMetricId, emptyDimensionId};
+ sp<const AATest> aa16 = new AATest{16, emptyMetricId, emptyDimensionId};
+ sp<const AATest> aa20 = new AATest{20, emptyMetricId, emptyDimensionId};
EXPECT_EQ(ipq.top(), nullptr);
@@ -113,9 +119,11 @@
}
TEST(indexed_priority_queue, push_same_aa) {
+ std::string emptyMetricId;
+ std::string emptyDimensionId;
indexed_priority_queue<AATest, AATest::Smaller> ipq;
- sp<const AATest> aa4_a = new AATest{4};
- sp<const AATest> aa4_b = new AATest{4};
+ sp<const AATest> aa4_a = new AATest{4, emptyMetricId, emptyDimensionId};
+ sp<const AATest> aa4_b = new AATest{4, emptyMetricId, emptyDimensionId};
ipq.push(aa4_a);
EXPECT_EQ(1u, ipq.size());
@@ -134,9 +142,11 @@
}
TEST(indexed_priority_queue, remove_nonexistant) {
+ std::string emptyMetricId;
+ std::string emptyDimensionId;
indexed_priority_queue<AATest, AATest::Smaller> ipq;
- sp<const AATest> aa4 = new AATest{4};
- sp<const AATest> aa5 = new AATest{5};
+ sp<const AATest> aa4 = new AATest{4, emptyMetricId, emptyDimensionId};
+ sp<const AATest> aa5 = new AATest{5, emptyMetricId, emptyDimensionId};
ipq.push(aa4);
ipq.remove(aa5);
@@ -147,8 +157,10 @@
TEST(indexed_priority_queue, remove_same_aa) {
indexed_priority_queue<AATest, AATest::Smaller> ipq;
- sp<const AATest> aa4_a = new AATest{4};
- sp<const AATest> aa4_b = new AATest{4};
+ std::string emptyMetricId;
+ std::string emptyDimensionId;
+ sp<const AATest> aa4_a = new AATest{4, emptyMetricId, emptyDimensionId};
+ sp<const AATest> aa4_b = new AATest{4, emptyMetricId, emptyDimensionId};
ipq.push(aa4_a);
ipq.push(aa4_b);
@@ -184,9 +196,11 @@
TEST(indexed_priority_queue, pop) {
indexed_priority_queue<AATest, AATest::Smaller> ipq;
- sp<const AATest> a = new AATest{1};
- sp<const AATest> b = new AATest{2};
- sp<const AATest> c = new AATest{3};
+ std::string emptyMetricId;
+ std::string emptyDimensionId;
+ sp<const AATest> a = new AATest{1, emptyMetricId, emptyDimensionId};
+ sp<const AATest> b = new AATest{2, emptyMetricId, emptyDimensionId};
+ sp<const AATest> c = new AATest{3, emptyMetricId, emptyDimensionId};
ipq.push(c);
ipq.push(b);
diff --git a/cmds/statsd/tests/metrics/CountMetricProducer_test.cpp b/cmds/statsd/tests/metrics/CountMetricProducer_test.cpp
index b7c9b40..35e08af 100644
--- a/cmds/statsd/tests/metrics/CountMetricProducer_test.cpp
+++ b/cmds/statsd/tests/metrics/CountMetricProducer_test.cpp
@@ -54,21 +54,26 @@
// 2 events in bucket 1.
countProducer.onMatchedLogEvent(1 /*log matcher index*/, event1, false);
countProducer.onMatchedLogEvent(1 /*log matcher index*/, event2, false);
- countProducer.flushCounterIfNeeded(bucketStartTimeNs + bucketSizeNs + 1);
+
+ // Flushes at event #2.
+ countProducer.flushIfNeeded(bucketStartTimeNs + 2);
+ EXPECT_EQ(0UL, countProducer.mPastBuckets.size());
+
+ // Flushes.
+ countProducer.flushIfNeeded(bucketStartTimeNs + bucketSizeNs + 1);
EXPECT_EQ(1UL, countProducer.mPastBuckets.size());
EXPECT_TRUE(countProducer.mPastBuckets.find(DEFAULT_DIMENSION_KEY) !=
countProducer.mPastBuckets.end());
const auto& buckets = countProducer.mPastBuckets[DEFAULT_DIMENSION_KEY];
EXPECT_EQ(1UL, buckets.size());
- const auto& bucketInfo = buckets[0];
- EXPECT_EQ(bucketStartTimeNs, bucketInfo.mBucketStartNs);
- EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, bucketInfo.mBucketEndNs);
- EXPECT_EQ(2LL, bucketInfo.mCount);
+ EXPECT_EQ(bucketStartTimeNs, buckets[0].mBucketStartNs);
+ EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, buckets[0].mBucketEndNs);
+ EXPECT_EQ(2LL, buckets[0].mCount);
// 1 matched event happens in bucket 2.
LogEvent event3(tagId, bucketStartTimeNs + bucketSizeNs + 2);
countProducer.onMatchedLogEvent(1 /*log matcher index*/, event3, false);
- countProducer.flushCounterIfNeeded(bucketStartTimeNs + 2 * bucketSizeNs + 1);
+ countProducer.flushIfNeeded(bucketStartTimeNs + 2 * bucketSizeNs + 1);
EXPECT_EQ(1UL, countProducer.mPastBuckets.size());
EXPECT_TRUE(countProducer.mPastBuckets.find(DEFAULT_DIMENSION_KEY) !=
countProducer.mPastBuckets.end());
@@ -79,7 +84,7 @@
EXPECT_EQ(1LL, bucketInfo2.mCount);
// nothing happens in bucket 3. we should not record anything for bucket 3.
- countProducer.flushCounterIfNeeded(bucketStartTimeNs + 3 * bucketSizeNs + 1);
+ countProducer.flushIfNeeded(bucketStartTimeNs + 3 * bucketSizeNs + 1);
EXPECT_EQ(1UL, countProducer.mPastBuckets.size());
EXPECT_TRUE(countProducer.mPastBuckets.find(DEFAULT_DIMENSION_KEY) !=
countProducer.mPastBuckets.end());
@@ -108,20 +113,22 @@
EXPECT_EQ(0UL, countProducer.mPastBuckets.size());
countProducer.onConditionChanged(false /*new condition*/, bucketStartTimeNs + 2);
+ // Upon this match event, the matched event1 is flushed.
countProducer.onMatchedLogEvent(1 /*matcher index*/, event2, false /*pulled*/);
EXPECT_EQ(0UL, countProducer.mPastBuckets.size());
- countProducer.flushCounterIfNeeded(bucketStartTimeNs + bucketSizeNs + 1);
-
+ countProducer.flushIfNeeded(bucketStartTimeNs + bucketSizeNs + 1);
EXPECT_EQ(1UL, countProducer.mPastBuckets.size());
EXPECT_TRUE(countProducer.mPastBuckets.find(DEFAULT_DIMENSION_KEY) !=
countProducer.mPastBuckets.end());
- const auto& buckets = countProducer.mPastBuckets[DEFAULT_DIMENSION_KEY];
- EXPECT_EQ(1UL, buckets.size());
- const auto& bucketInfo = buckets[0];
- EXPECT_EQ(bucketStartTimeNs, bucketInfo.mBucketStartNs);
- EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, bucketInfo.mBucketEndNs);
- EXPECT_EQ(1LL, bucketInfo.mCount);
+ {
+ const auto& buckets = countProducer.mPastBuckets[DEFAULT_DIMENSION_KEY];
+ EXPECT_EQ(1UL, buckets.size());
+ const auto& bucketInfo = buckets[0];
+ EXPECT_EQ(bucketStartTimeNs, bucketInfo.mBucketStartNs);
+ EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, bucketInfo.mBucketEndNs);
+ EXPECT_EQ(1LL, bucketInfo.mCount);
+ }
}
TEST(CountMetricProducerTest, TestEventsWithSlicedCondition) {
@@ -158,10 +165,11 @@
bucketStartTimeNs);
countProducer.onMatchedLogEvent(1 /*log matcher index*/, event1, false);
+ countProducer.flushIfNeeded(bucketStartTimeNs + 1);
+ EXPECT_EQ(0UL, countProducer.mPastBuckets.size());
+
countProducer.onMatchedLogEvent(1 /*log matcher index*/, event2, false);
-
- countProducer.flushCounterIfNeeded(bucketStartTimeNs + bucketSizeNs + 1);
-
+ countProducer.flushIfNeeded(bucketStartTimeNs + bucketSizeNs + 1);
EXPECT_EQ(1UL, countProducer.mPastBuckets.size());
EXPECT_TRUE(countProducer.mPastBuckets.find(DEFAULT_DIMENSION_KEY) !=
countProducer.mPastBuckets.end());
@@ -173,6 +181,68 @@
EXPECT_EQ(1LL, bucketInfo.mCount);
}
+TEST(CountMetricProducerTest, TestAnomalyDetection) {
+ Alert alert;
+ alert.set_name("alert");
+ alert.set_metric_name("1");
+ alert.set_trigger_if_sum_gt(2);
+ alert.set_number_of_buckets(2);
+ alert.set_refractory_period_secs(1);
+
+ int64_t bucketStartTimeNs = 10000000000;
+ int64_t bucketSizeNs = 30 * NS_PER_SEC;
+ int64_t bucket2StartTimeNs = bucketStartTimeNs + bucketSizeNs;
+ int64_t bucket3StartTimeNs = bucketStartTimeNs + 2 * bucketSizeNs;
+
+ sp<AnomalyTracker> anomalyTracker = new AnomalyTracker(alert, bucketSizeNs);
+
+ CountMetric metric;
+ metric.set_name("1");
+ metric.mutable_bucket()->set_bucket_size_millis(bucketSizeNs / 1000000);
+
+ sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
+ CountMetricProducer countProducer(metric, -1 /*-1 meaning no condition*/, wizard,
+ bucketStartTimeNs);
+ countProducer.addAnomalyTracker(anomalyTracker);
+
+ int tagId = 1;
+ LogEvent event1(tagId, bucketStartTimeNs + 1);
+ LogEvent event2(tagId, bucketStartTimeNs + 2);
+ LogEvent event3(tagId, bucketStartTimeNs + 2 * bucketSizeNs + 1);
+ LogEvent event4(tagId, bucketStartTimeNs + 3 * bucketSizeNs + 1);
+ LogEvent event5(tagId, bucketStartTimeNs + 3 * bucketSizeNs + 2);
+ LogEvent event6(tagId, bucketStartTimeNs + 3 * bucketSizeNs + 3);
+ LogEvent event7(tagId, bucketStartTimeNs + 3 * bucketSizeNs + 3 + NS_PER_SEC);
+
+ // Two events in bucket #0.
+ countProducer.onMatchedLogEvent(1 /*log matcher index*/, event1, false);
+ countProducer.onMatchedLogEvent(1 /*log matcher index*/, event2, false);
+
+ EXPECT_EQ(1UL, countProducer.mCurrentSlicedCounter->size());
+ EXPECT_EQ(2L, countProducer.mCurrentSlicedCounter->begin()->second);
+ EXPECT_EQ(anomalyTracker->getLastAlarmTimestampNs(), -1LL);
+
+ // One event in bucket #2. No alarm as bucket #0 is trashed out.
+ countProducer.onMatchedLogEvent(1 /*log matcher index*/, event3, false);
+ EXPECT_EQ(1UL, countProducer.mCurrentSlicedCounter->size());
+ EXPECT_EQ(1L, countProducer.mCurrentSlicedCounter->begin()->second);
+ EXPECT_EQ(anomalyTracker->getLastAlarmTimestampNs(), -1LL);
+
+ // Two events in bucket #3.
+ countProducer.onMatchedLogEvent(1 /*log matcher index*/, event4, false);
+ countProducer.onMatchedLogEvent(1 /*log matcher index*/, event5, false);
+ countProducer.onMatchedLogEvent(1 /*log matcher index*/, event6, false);
+ EXPECT_EQ(1UL, countProducer.mCurrentSlicedCounter->size());
+ EXPECT_EQ(3L, countProducer.mCurrentSlicedCounter->begin()->second);
+ // Anomaly at event 6 is within refractory period. The alarm is at event 5 timestamp not event 6
+ EXPECT_EQ(anomalyTracker->getLastAlarmTimestampNs(), (long long)event5.GetTimestampNs());
+
+ countProducer.onMatchedLogEvent(1 /*log matcher index*/, event7, false);
+ EXPECT_EQ(1UL, countProducer.mCurrentSlicedCounter->size());
+ EXPECT_EQ(4L, countProducer.mCurrentSlicedCounter->begin()->second);
+ EXPECT_EQ(anomalyTracker->getLastAlarmTimestampNs(), (long long)event7.GetTimestampNs());
+}
+
} // namespace statsd
} // namespace os
} // namespace android
diff --git a/cmds/statsd/tests/metrics/GaugeMetricProducer_test.cpp b/cmds/statsd/tests/metrics/GaugeMetricProducer_test.cpp
new file mode 100644
index 0000000..b9e2b8a
--- /dev/null
+++ b/cmds/statsd/tests/metrics/GaugeMetricProducer_test.cpp
@@ -0,0 +1,201 @@
+// Copyright (C) 2017 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "logd/LogEvent.h"
+#include "metrics_test_helper.h"
+#include "src/metrics/GaugeMetricProducer.h"
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <stdio.h>
+#include <vector>
+
+using namespace testing;
+using android::sp;
+using std::set;
+using std::unordered_map;
+using std::vector;
+
+#ifdef __ANDROID__
+
+namespace android {
+namespace os {
+namespace statsd {
+
+TEST(GaugeMetricProducerTest, TestWithCondition) {
+ int64_t bucketStartTimeNs = 10000000000;
+ int64_t bucketSizeNs = 60 * 1000 * 1000 * 1000LL;
+
+ GaugeMetric metric;
+ metric.set_name("1");
+ metric.mutable_bucket()->set_bucket_size_millis(bucketSizeNs / 1000000);
+ metric.set_gauge_field(2);
+
+ sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
+
+ GaugeMetricProducer gaugeProducer(metric, 1 /*has condition*/, wizard, -1, bucketStartTimeNs);
+
+ vector<std::shared_ptr<LogEvent>> allData;
+ std::shared_ptr<LogEvent> event1 = std::make_shared<LogEvent>(1, bucketStartTimeNs + 1);
+ event1->write(1);
+ event1->write(13);
+ event1->init();
+ allData.push_back(event1);
+
+ std::shared_ptr<LogEvent> event2 = std::make_shared<LogEvent>(1, bucketStartTimeNs + 10);
+ event2->write(1);
+ event2->write(15);
+ event2->init();
+ allData.push_back(event2);
+
+ gaugeProducer.onDataPulled(allData);
+ gaugeProducer.flushIfNeeded(event2->GetTimestampNs() + 1);
+ EXPECT_EQ(0UL, gaugeProducer.mCurrentSlicedBucket->size());
+ EXPECT_EQ(0UL, gaugeProducer.mPastBuckets.size());
+
+ gaugeProducer.onConditionChanged(true, bucketStartTimeNs + 11);
+ gaugeProducer.onConditionChanged(false, bucketStartTimeNs + 21);
+ gaugeProducer.onConditionChanged(true, bucketStartTimeNs + bucketSizeNs + 11);
+ std::shared_ptr<LogEvent> event3 =
+ std::make_shared<LogEvent>(1, bucketStartTimeNs + 2 * bucketSizeNs + 10);
+ event3->write(1);
+ event3->write(25);
+ event3->init();
+ allData.push_back(event3);
+ gaugeProducer.onDataPulled(allData);
+ gaugeProducer.flushIfNeeded(bucketStartTimeNs + 2 * bucketSizeNs + 10);
+ EXPECT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->size());
+ EXPECT_EQ(25L, gaugeProducer.mCurrentSlicedBucket->begin()->second);
+ // One dimension.
+ EXPECT_EQ(1UL, gaugeProducer.mPastBuckets.size());
+ EXPECT_EQ(1UL, gaugeProducer.mPastBuckets.begin()->second.size());
+ EXPECT_EQ(25L, gaugeProducer.mPastBuckets.begin()->second.front().mGauge);
+ EXPECT_EQ(2UL, gaugeProducer.mPastBuckets.begin()->second.front().mBucketNum);
+ EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs,
+ gaugeProducer.mPastBuckets.begin()->second.front().mBucketStartNs);
+}
+
+TEST(GaugeMetricProducerTest, TestNoCondition) {
+ int64_t bucketStartTimeNs = 10000000000;
+ int64_t bucketSizeNs = 60 * 1000 * 1000 * 1000LL;
+
+ GaugeMetric metric;
+ metric.set_name("1");
+ metric.mutable_bucket()->set_bucket_size_millis(bucketSizeNs / 1000000);
+ metric.set_gauge_field(2);
+
+ sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
+
+ GaugeMetricProducer gaugeProducer(metric, -1 /*no condition*/, wizard, -1, bucketStartTimeNs);
+
+ vector<std::shared_ptr<LogEvent>> allData;
+ std::shared_ptr<LogEvent> event1 = std::make_shared<LogEvent>(1, bucketStartTimeNs + 1);
+ event1->write(1);
+ event1->write(13);
+ event1->init();
+ allData.push_back(event1);
+
+ std::shared_ptr<LogEvent> event2 = std::make_shared<LogEvent>(1, bucketStartTimeNs + 10);
+ event2->write(1);
+ event2->write(15);
+ event2->init();
+ allData.push_back(event2);
+
+ std::shared_ptr<LogEvent> event3 =
+ std::make_shared<LogEvent>(1, bucketStartTimeNs + 2 * bucketSizeNs + 10);
+ event3->write(1);
+ event3->write(25);
+ event3->init();
+ allData.push_back(event3);
+
+ gaugeProducer.onDataPulled(allData);
+ // Has one slice
+ EXPECT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->size());
+ EXPECT_EQ(25L, gaugeProducer.mCurrentSlicedBucket->begin()->second);
+ EXPECT_EQ(2UL, gaugeProducer.mPastBuckets.begin()->second.size());
+ EXPECT_EQ(13L, gaugeProducer.mPastBuckets.begin()->second.front().mGauge);
+ EXPECT_EQ(0UL, gaugeProducer.mPastBuckets.begin()->second.front().mBucketNum);
+ EXPECT_EQ(25L, gaugeProducer.mPastBuckets.begin()->second.back().mGauge);
+ EXPECT_EQ(2UL, gaugeProducer.mPastBuckets.begin()->second.back().mBucketNum);
+ EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs,
+ gaugeProducer.mPastBuckets.begin()->second.back().mBucketStartNs);
+}
+
+TEST(GaugeMetricProducerTest, TestAnomalyDetection) {
+ int64_t bucketStartTimeNs = 10000000000;
+ int64_t bucketSizeNs = 60 * 1000 * 1000 * 1000LL;
+ sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
+
+ GaugeMetric metric;
+ metric.set_name("1");
+ metric.mutable_bucket()->set_bucket_size_millis(bucketSizeNs / 1000000);
+ metric.set_gauge_field(2);
+ GaugeMetricProducer gaugeProducer(metric, -1 /*no condition*/, wizard, -1, bucketStartTimeNs);
+
+ Alert alert;
+ alert.set_name("alert");
+ alert.set_metric_name("1");
+ alert.set_trigger_if_sum_gt(25);
+ alert.set_number_of_buckets(2);
+ sp<AnomalyTracker> anomalyTracker = new AnomalyTracker(alert, bucketSizeNs);
+ gaugeProducer.addAnomalyTracker(anomalyTracker);
+
+ std::shared_ptr<LogEvent> event1 = std::make_shared<LogEvent>(1, bucketStartTimeNs + 1);
+ event1->write(1);
+ event1->write(13);
+ event1->init();
+
+ gaugeProducer.onDataPulled({event1});
+ EXPECT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->size());
+ EXPECT_EQ(13L, gaugeProducer.mCurrentSlicedBucket->begin()->second);
+ EXPECT_EQ(anomalyTracker->getLastAlarmTimestampNs(), -1LL);
+
+ std::shared_ptr<LogEvent> event2 =
+ std::make_shared<LogEvent>(1, bucketStartTimeNs + bucketSizeNs + 10);
+ event2->write(1);
+ event2->write(15);
+ event2->init();
+
+ gaugeProducer.onDataPulled({event2});
+ EXPECT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->size());
+ EXPECT_EQ(15L, gaugeProducer.mCurrentSlicedBucket->begin()->second);
+ EXPECT_EQ(anomalyTracker->getLastAlarmTimestampNs(), (long long)event2->GetTimestampNs());
+
+ std::shared_ptr<LogEvent> event3 =
+ std::make_shared<LogEvent>(1, bucketStartTimeNs + 2 * bucketSizeNs + 10);
+ event3->write(1);
+ event3->write(24);
+ event3->init();
+
+ gaugeProducer.onDataPulled({event3});
+ EXPECT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->size());
+ EXPECT_EQ(24L, gaugeProducer.mCurrentSlicedBucket->begin()->second);
+ EXPECT_EQ(anomalyTracker->getLastAlarmTimestampNs(), (long long)event3->GetTimestampNs());
+
+ // The event4 does not have the gauge field. Thus the current bucket value is 0.
+ std::shared_ptr<LogEvent> event4 =
+ std::make_shared<LogEvent>(1, bucketStartTimeNs + 3 * bucketSizeNs + 10);
+ event4->write(1);
+ event4->init();
+ gaugeProducer.onDataPulled({event4});
+ EXPECT_EQ(0UL, gaugeProducer.mCurrentSlicedBucket->size());
+ EXPECT_EQ(anomalyTracker->getLastAlarmTimestampNs(), (long long)event3->GetTimestampNs());
+}
+
+} // namespace statsd
+} // namespace os
+} // namespace android
+#else
+GTEST_LOG_(INFO) << "This test does nothing.\n";
+#endif
diff --git a/cmds/statsd/tests/metrics/MaxDurationTracker_test.cpp b/cmds/statsd/tests/metrics/MaxDurationTracker_test.cpp
index 9cc184a..9e169bb 100644
--- a/cmds/statsd/tests/metrics/MaxDurationTracker_test.cpp
+++ b/cmds/statsd/tests/metrics/MaxDurationTracker_test.cpp
@@ -45,17 +45,49 @@
uint64_t bucketStartTimeNs = 10000000000;
uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
- MaxDurationTracker tracker(wizard, -1, false, bucketStartTimeNs, bucketSizeNs, buckets);
+ MaxDurationTracker tracker("event", wizard, -1, false, bucketStartTimeNs, bucketSizeNs, {},
+ buckets);
- tracker.noteStart("", true, bucketStartTimeNs, key1);
- tracker.noteStop("", bucketStartTimeNs + 10, false);
+ tracker.noteStart("1", true, bucketStartTimeNs, key1);
+ // Event starts again. This would not change anything as it already starts.
+ tracker.noteStart("1", true, bucketStartTimeNs + 3, key1);
+ // Stopped.
+ tracker.noteStop("1", bucketStartTimeNs + 10, false);
- tracker.noteStart("", true, bucketStartTimeNs + 20, key1);
- tracker.noteStop("", bucketStartTimeNs + 40, false);
+ // Another event starts in this bucket.
+ tracker.noteStart("2", true, bucketStartTimeNs + 20, key1);
+ tracker.noteStop("2", bucketStartTimeNs + 40, false /*stop all*/);
tracker.flushIfNeeded(bucketStartTimeNs + bucketSizeNs + 1);
EXPECT_EQ(1u, buckets.size());
- EXPECT_EQ(20, buckets[0].mDuration);
+ EXPECT_EQ(20ULL, buckets[0].mDuration);
+}
+
+TEST(MaxDurationTrackerTest, TestStopAll) {
+ sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
+
+ vector<DurationBucket> buckets;
+ ConditionKey key1;
+
+ uint64_t bucketStartTimeNs = 10000000000;
+ uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
+
+ MaxDurationTracker tracker("event", wizard, -1, false, bucketStartTimeNs, bucketSizeNs, {},
+ buckets);
+
+ tracker.noteStart("1", true, bucketStartTimeNs + 1, key1);
+
+ // Another event starts in this bucket.
+ tracker.noteStart("2", true, bucketStartTimeNs + 20, key1);
+ tracker.noteStopAll(bucketStartTimeNs + bucketSizeNs + 40);
+ EXPECT_TRUE(tracker.mInfos.empty());
+ EXPECT_EQ(1u, buckets.size());
+ EXPECT_EQ(bucketSizeNs - 1, buckets[0].mDuration);
+
+ tracker.flushIfNeeded(bucketStartTimeNs + 3 * bucketSizeNs + 40);
+ EXPECT_EQ(2u, buckets.size());
+ EXPECT_EQ(bucketSizeNs - 1, buckets[0].mDuration);
+ EXPECT_EQ(40ULL, buckets[1].mDuration);
}
TEST(MaxDurationTrackerTest, TestCrossBucketBoundary) {
@@ -67,14 +99,33 @@
uint64_t bucketStartTimeNs = 10000000000;
uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
- MaxDurationTracker tracker(wizard, -1, false, bucketStartTimeNs, bucketSizeNs, buckets);
+ MaxDurationTracker tracker("event", wizard, -1, false, bucketStartTimeNs, bucketSizeNs, {},
+ buckets);
+ // The event starts.
tracker.noteStart("", true, bucketStartTimeNs + 1, key1);
- tracker.flushIfNeeded(bucketStartTimeNs + (2 * bucketSizeNs) + 1);
+ // Starts again. Does not change anything.
+ tracker.noteStart("", true, bucketStartTimeNs + bucketSizeNs + 1, key1);
+
+ // Flushes at early 2nd bucket. The event still does not stop yet.
+ tracker.flushIfNeeded(bucketStartTimeNs + bucketSizeNs + 1);
+ EXPECT_EQ(1u, buckets.size());
+ EXPECT_EQ((unsigned long long)(bucketSizeNs - 1), buckets[0].mDuration);
+
+ // Flushes at the end of the 2nd bucket. The event still does not stop yet.
+ tracker.flushIfNeeded(bucketStartTimeNs + (2 * bucketSizeNs));
EXPECT_EQ(2u, buckets.size());
- EXPECT_EQ((long long)(bucketSizeNs - 1), buckets[0].mDuration);
- EXPECT_EQ((long long)bucketSizeNs, buckets[1].mDuration);
+ EXPECT_EQ((unsigned long long)(bucketSizeNs - 1), buckets[0].mDuration);
+ EXPECT_EQ((unsigned long long)bucketSizeNs, buckets[1].mDuration);
+
+ // The event stops at early 4th bucket.
+ tracker.noteStop("", bucketStartTimeNs + (3 * bucketSizeNs) + 20, false /*stop all*/);
+ tracker.flushIfNeeded(bucketStartTimeNs + (3 * bucketSizeNs) + 21);
+ EXPECT_EQ(3u, buckets.size());
+ EXPECT_EQ((unsigned long long)(bucketSizeNs - 1), buckets[0].mDuration);
+ EXPECT_EQ((unsigned long long)bucketSizeNs, buckets[1].mDuration);
+ EXPECT_EQ((unsigned long long)bucketSizeNs, buckets[2].mDuration);
}
TEST(MaxDurationTrackerTest, TestCrossBucketBoundary_nested) {
@@ -86,7 +137,8 @@
uint64_t bucketStartTimeNs = 10000000000;
uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
- MaxDurationTracker tracker(wizard, -1, true, bucketStartTimeNs, bucketSizeNs, buckets);
+ MaxDurationTracker tracker("event", wizard, -1, true, bucketStartTimeNs, bucketSizeNs, {},
+ buckets);
// 2 starts
tracker.noteStart("", true, bucketStartTimeNs + 1, key1);
@@ -97,15 +149,17 @@
tracker.flushIfNeeded(bucketStartTimeNs + (2 * bucketSizeNs) + 1);
EXPECT_EQ(2u, buckets.size());
- EXPECT_EQ((long long)(bucketSizeNs - 1), buckets[0].mDuration);
- EXPECT_EQ((long long)bucketSizeNs, buckets[1].mDuration);
+ EXPECT_EQ(bucketSizeNs - 1, buckets[0].mDuration);
+ EXPECT_EQ(bucketSizeNs, buckets[1].mDuration);
// real stop now.
tracker.noteStop("", bucketStartTimeNs + (2 * bucketSizeNs) + 5, false);
tracker.flushIfNeeded(bucketStartTimeNs + (3 * bucketSizeNs) + 1);
EXPECT_EQ(3u, buckets.size());
- EXPECT_EQ(5, buckets[2].mDuration);
+ EXPECT_EQ(bucketSizeNs - 1, buckets[0].mDuration);
+ EXPECT_EQ(bucketSizeNs, buckets[1].mDuration);
+ EXPECT_EQ(5ULL, buckets[2].mDuration);
}
TEST(MaxDurationTrackerTest, TestMaxDurationWithCondition) {
@@ -124,17 +178,65 @@
uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
int64_t durationTimeNs = 2 * 1000;
- MaxDurationTracker tracker(wizard, 1, false, bucketStartTimeNs, bucketSizeNs, buckets);
+ MaxDurationTracker tracker("event", wizard, 1, false, bucketStartTimeNs, bucketSizeNs, {},
+ buckets);
+ EXPECT_TRUE(tracker.mAnomalyTrackers.empty());
tracker.noteStart("2:maps", true, eventStartTimeNs, key1);
+ tracker.onSlicedConditionMayChange(eventStartTimeNs + 2 * bucketSizeNs + 5);
+ EXPECT_EQ(2u, buckets.size());
+ EXPECT_EQ(bucketSizeNs - 1, buckets[0].mDuration);
+ EXPECT_EQ(bucketSizeNs, buckets[1].mDuration);
- tracker.onSlicedConditionMayChange(eventStartTimeNs + 5);
+ tracker.noteStop("2:maps", eventStartTimeNs + 2 * bucketSizeNs + durationTimeNs, false);
+ EXPECT_EQ(2u, buckets.size());
+ EXPECT_EQ(bucketSizeNs - 1, buckets[0].mDuration);
+ EXPECT_EQ(bucketSizeNs, buckets[1].mDuration);
+ EXPECT_TRUE(tracker.mInfos.empty());
+ EXPECT_EQ(6LL, tracker.mDuration);
- tracker.noteStop("2:maps", eventStartTimeNs + durationTimeNs, false);
+ tracker.noteStart("2:maps", false, eventStartTimeNs + 3 * bucketSizeNs + 10, key1);
+ EXPECT_EQ(1u, tracker.mInfos.size());
+ for (const auto& itr : tracker.mInfos) {
+ EXPECT_EQ(DurationState::kPaused, itr.second.state);
+ EXPECT_EQ(0LL, itr.second.lastDuration);
+ }
+ EXPECT_EQ(3u, buckets.size());
+ EXPECT_EQ(bucketSizeNs - 1, buckets[0].mDuration);
+ EXPECT_EQ(bucketSizeNs, buckets[1].mDuration);
+ EXPECT_EQ(6ULL, buckets[2].mDuration);
+}
- tracker.flushIfNeeded(bucketStartTimeNs + bucketSizeNs + 1);
- EXPECT_EQ(1u, buckets.size());
- EXPECT_EQ(5, buckets[0].mDuration);
+TEST(MaxDurationTrackerTest, TestAnomalyDetection) {
+ Alert alert;
+ alert.set_name("alert");
+ alert.set_metric_name("1");
+ alert.set_trigger_if_sum_gt(32 * NS_PER_SEC);
+ alert.set_number_of_buckets(2);
+ alert.set_refractory_period_secs(1);
+
+ vector<DurationBucket> buckets;
+ sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
+ ConditionKey key1;
+ key1["APP_BACKGROUND"] = "1:maps|";
+ uint64_t bucketStartTimeNs = 10 * NS_PER_SEC;
+ uint64_t eventStartTimeNs = bucketStartTimeNs + NS_PER_SEC + 1;
+ uint64_t bucketSizeNs = 30 * NS_PER_SEC;
+
+ sp<AnomalyTracker> anomalyTracker = new AnomalyTracker(alert, bucketSizeNs);
+ MaxDurationTracker tracker("event", wizard, -1, true, bucketStartTimeNs, bucketSizeNs,
+ {anomalyTracker}, buckets);
+
+ tracker.noteStart("1", true, eventStartTimeNs, key1);
+ tracker.noteStop("1", eventStartTimeNs + 10, false);
+ EXPECT_EQ(anomalyTracker->mLastAlarmTimestampNs, -1);
+ EXPECT_EQ(10LL, tracker.mDuration);
+
+ tracker.noteStart("2", true, eventStartTimeNs + 20, key1);
+ tracker.noteStop("2", eventStartTimeNs + 2 * bucketSizeNs + 3 * NS_PER_SEC, false);
+ EXPECT_EQ((long long)(4 * NS_PER_SEC + 1LL), tracker.mDuration);
+ EXPECT_EQ(anomalyTracker->mLastAlarmTimestampNs,
+ (long long)(eventStartTimeNs + 2 * bucketSizeNs + 3 * NS_PER_SEC));
}
} // namespace statsd
diff --git a/cmds/statsd/tests/metrics/OringDurationTracker_test.cpp b/cmds/statsd/tests/metrics/OringDurationTracker_test.cpp
index f495d6b..f4edffd 100644
--- a/cmds/statsd/tests/metrics/OringDurationTracker_test.cpp
+++ b/cmds/statsd/tests/metrics/OringDurationTracker_test.cpp
@@ -45,16 +45,18 @@
uint64_t bucketStartTimeNs = 10000000000;
uint64_t eventStartTimeNs = bucketStartTimeNs + 1;
uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
- int64_t durationTimeNs = 2 * 1000;
+ uint64_t durationTimeNs = 2 * 1000;
- OringDurationTracker tracker(wizard, 1, false, bucketStartTimeNs, bucketSizeNs, buckets);
+ OringDurationTracker tracker("event", wizard, 1, false, bucketStartTimeNs, bucketSizeNs, {},
+ buckets);
tracker.noteStart("2:maps", true, eventStartTimeNs, key1);
+ EXPECT_EQ((long long)eventStartTimeNs, tracker.mLastStartTime);
tracker.noteStart("2:maps", true, eventStartTimeNs + 10, key1); // overlapping wl
+ EXPECT_EQ((long long)eventStartTimeNs, tracker.mLastStartTime);
tracker.noteStop("2:maps", eventStartTimeNs + durationTimeNs, false);
-
- tracker.flushIfNeeded(bucketStartTimeNs + bucketSizeNs + 1);
+ tracker.flushIfNeeded(eventStartTimeNs + bucketSizeNs + 1);
EXPECT_EQ(1u, buckets.size());
EXPECT_EQ(durationTimeNs, buckets[0].mDuration);
}
@@ -71,7 +73,8 @@
uint64_t eventStartTimeNs = bucketStartTimeNs + 1;
uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
- OringDurationTracker tracker(wizard, 1, true, bucketStartTimeNs, bucketSizeNs, buckets);
+ OringDurationTracker tracker("event", wizard, 1, true, bucketStartTimeNs, bucketSizeNs, {},
+ buckets);
tracker.noteStart("2:maps", true, eventStartTimeNs, key1);
tracker.noteStart("2:maps", true, eventStartTimeNs + 10, key1); // overlapping wl
@@ -81,7 +84,66 @@
tracker.flushIfNeeded(bucketStartTimeNs + bucketSizeNs + 1);
EXPECT_EQ(1u, buckets.size());
- EXPECT_EQ(2003, buckets[0].mDuration);
+ EXPECT_EQ(2003ULL, buckets[0].mDuration);
+}
+
+TEST(OringDurationTrackerTest, TestStopAll) {
+ sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
+
+ ConditionKey key1;
+ key1["APP_BACKGROUND"] = "1:maps|";
+
+ vector<DurationBucket> buckets;
+
+ uint64_t bucketStartTimeNs = 10000000000;
+ uint64_t eventStartTimeNs = bucketStartTimeNs + 1;
+ uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
+
+ OringDurationTracker tracker("event", wizard, 1, true, bucketStartTimeNs, bucketSizeNs, {},
+ buckets);
+
+ tracker.noteStart("2:maps", true, eventStartTimeNs, key1);
+ tracker.noteStart("3:maps", true, eventStartTimeNs + 10, key1); // overlapping wl
+
+ tracker.noteStopAll(eventStartTimeNs + 2003);
+
+ tracker.flushIfNeeded(bucketStartTimeNs + bucketSizeNs + 1);
+ EXPECT_EQ(1u, buckets.size());
+ EXPECT_EQ(2003ULL, buckets[0].mDuration);
+}
+
+TEST(OringDurationTrackerTest, TestCrossBucketBoundary) {
+ sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
+
+ ConditionKey key1;
+ key1["APP_BACKGROUND"] = "1:maps|";
+
+ vector<DurationBucket> buckets;
+
+ uint64_t bucketStartTimeNs = 10000000000;
+ uint64_t eventStartTimeNs = bucketStartTimeNs + 1;
+ uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
+ uint64_t durationTimeNs = 2 * 1000;
+
+ OringDurationTracker tracker("event", wizard, 1, true, bucketStartTimeNs, bucketSizeNs, {},
+ buckets);
+
+ tracker.noteStart("2:maps", true, eventStartTimeNs, key1);
+ EXPECT_EQ((long long)eventStartTimeNs, tracker.mLastStartTime);
+ tracker.noteStart("2:maps", true, eventStartTimeNs + 2 * bucketSizeNs, key1);
+ EXPECT_EQ((long long)(bucketStartTimeNs + 2 * bucketSizeNs), tracker.mLastStartTime);
+
+ tracker.flushIfNeeded(eventStartTimeNs + 2 * bucketSizeNs);
+ EXPECT_EQ(2u, buckets.size());
+ EXPECT_EQ(bucketSizeNs - 1, buckets[0].mDuration);
+ EXPECT_EQ(bucketSizeNs, buckets[1].mDuration);
+
+ tracker.noteStop("2:maps", eventStartTimeNs + 2 * bucketSizeNs + 10, false);
+ tracker.noteStop("2:maps", eventStartTimeNs + 2 * bucketSizeNs + 12, false);
+ tracker.flushIfNeeded(eventStartTimeNs + 2 * bucketSizeNs + 12);
+ EXPECT_EQ(2u, buckets.size());
+ EXPECT_EQ(bucketSizeNs - 1, buckets[0].mDuration);
+ EXPECT_EQ(bucketSizeNs, buckets[1].mDuration);
}
TEST(OringDurationTrackerTest, TestDurationConditionChange) {
@@ -98,19 +160,19 @@
uint64_t bucketStartTimeNs = 10000000000;
uint64_t eventStartTimeNs = bucketStartTimeNs + 1;
uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
- int64_t durationTimeNs = 2 * 1000;
+ uint64_t durationTimeNs = 2 * 1000;
- OringDurationTracker tracker(wizard, 1, false, bucketStartTimeNs, bucketSizeNs, buckets);
+ OringDurationTracker tracker("event", wizard, 1, false, bucketStartTimeNs, bucketSizeNs, {},
+ buckets);
tracker.noteStart("2:maps", true, eventStartTimeNs, key1);
- tracker.onSlicedConditionMayChange(eventStartTimeNs + 5);
-
- tracker.noteStop("2:maps", eventStartTimeNs + durationTimeNs, false);
-
- tracker.flushIfNeeded(bucketStartTimeNs + bucketSizeNs + 1);
- EXPECT_EQ(1u, buckets.size());
- EXPECT_EQ(5, buckets[0].mDuration);
+ tracker.onSlicedConditionMayChange(eventStartTimeNs + 2 * bucketSizeNs + 5);
+ tracker.noteStop("2:maps", eventStartTimeNs + 2 * bucketSizeNs + durationTimeNs, false);
+ tracker.flushIfNeeded(bucketStartTimeNs + 2 * bucketSizeNs + durationTimeNs);
+ EXPECT_EQ(2u, buckets.size());
+ EXPECT_EQ(bucketSizeNs - 1, buckets[0].mDuration);
+ EXPECT_EQ(bucketSizeNs, buckets[1].mDuration);
}
TEST(OringDurationTrackerTest, TestDurationConditionChangeNested) {
@@ -128,7 +190,8 @@
uint64_t eventStartTimeNs = bucketStartTimeNs + 1;
uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
- OringDurationTracker tracker(wizard, 1, true, bucketStartTimeNs, bucketSizeNs, buckets);
+ OringDurationTracker tracker("event", wizard, 1, true, bucketStartTimeNs, bucketSizeNs, {},
+ buckets);
tracker.noteStart("2:maps", true, eventStartTimeNs, key1);
tracker.noteStart("2:maps", true, eventStartTimeNs + 2, key1);
@@ -141,7 +204,105 @@
tracker.flushIfNeeded(bucketStartTimeNs + bucketSizeNs + 1);
EXPECT_EQ(1u, buckets.size());
- EXPECT_EQ(15, buckets[0].mDuration);
+ EXPECT_EQ(15ULL, buckets[0].mDuration);
+}
+
+TEST(OringDurationTrackerTest, TestPredictAnomalyTimestamp) {
+ Alert alert;
+ alert.set_name("alert");
+ alert.set_metric_name("1");
+ alert.set_trigger_if_sum_gt(40 * NS_PER_SEC);
+ alert.set_number_of_buckets(2);
+ alert.set_refractory_period_secs(1);
+
+ vector<DurationBucket> buckets;
+ sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
+ ConditionKey key1;
+ key1["APP_BACKGROUND"] = "1:maps|";
+ uint64_t bucketStartTimeNs = 10 * NS_PER_SEC;
+ uint64_t eventStartTimeNs = bucketStartTimeNs + NS_PER_SEC + 1;
+ uint64_t bucketSizeNs = 30 * NS_PER_SEC;
+
+ sp<AnomalyTracker> anomalyTracker = new AnomalyTracker(alert, bucketSizeNs);
+ OringDurationTracker tracker("event", wizard, 1, true, bucketStartTimeNs, bucketSizeNs,
+ {anomalyTracker}, buckets);
+
+ // Nothing in the past bucket.
+ tracker.noteStart("", true, eventStartTimeNs, key1);
+ EXPECT_EQ((long long)(alert.trigger_if_sum_gt() + eventStartTimeNs),
+ tracker.predictAnomalyTimestampNs(*anomalyTracker, eventStartTimeNs));
+
+ tracker.noteStop("", eventStartTimeNs + 3, false);
+ EXPECT_EQ(0u, buckets.size());
+
+ uint64_t event1StartTimeNs = eventStartTimeNs + 10;
+ tracker.noteStart("1", true, event1StartTimeNs, key1);
+ // No past buckets. The anomaly will happen in bucket #0.
+ EXPECT_EQ((long long)(event1StartTimeNs + alert.trigger_if_sum_gt() - 3),
+ tracker.predictAnomalyTimestampNs(*anomalyTracker, event1StartTimeNs));
+
+ uint64_t event1StopTimeNs = eventStartTimeNs + bucketSizeNs + 10;
+ tracker.noteStop("1", event1StopTimeNs, false);
+ EXPECT_EQ(1u, buckets.size());
+ EXPECT_EQ(3ULL + bucketStartTimeNs + bucketSizeNs - eventStartTimeNs - 10,
+ buckets[0].mDuration);
+
+ const int64_t bucket0Duration = 3ULL + bucketStartTimeNs + bucketSizeNs - eventStartTimeNs - 10;
+ const int64_t bucket1Duration = eventStartTimeNs + 10 - bucketStartTimeNs;
+
+ // One past buckets. The anomaly will happen in bucket #1.
+ uint64_t event2StartTimeNs = eventStartTimeNs + bucketSizeNs + 15;
+ tracker.noteStart("1", true, event2StartTimeNs, key1);
+ EXPECT_EQ((long long)(event2StartTimeNs + alert.trigger_if_sum_gt() - bucket0Duration -
+ bucket1Duration),
+ tracker.predictAnomalyTimestampNs(*anomalyTracker, event2StartTimeNs));
+ tracker.noteStop("1", event2StartTimeNs + 1, false);
+
+ // Only one past buckets is applicable. Bucket +0 should be trashed. The anomaly will happen in
+ // bucket #2.
+ uint64_t event3StartTimeNs = bucketStartTimeNs + 2 * bucketSizeNs - 9 * NS_PER_SEC;
+ tracker.noteStart("1", true, event3StartTimeNs, key1);
+ EXPECT_EQ((long long)(event3StartTimeNs + alert.trigger_if_sum_gt() - bucket1Duration - 1LL),
+ tracker.predictAnomalyTimestampNs(*anomalyTracker, event3StartTimeNs));
+}
+
+TEST(OringDurationTrackerTest, TestAnomalyDetection) {
+ Alert alert;
+ alert.set_name("alert");
+ alert.set_metric_name("1");
+ alert.set_trigger_if_sum_gt(40 * NS_PER_SEC);
+ alert.set_number_of_buckets(2);
+ alert.set_refractory_period_secs(1);
+
+ vector<DurationBucket> buckets;
+ sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
+ ConditionKey key1;
+ key1["APP_BACKGROUND"] = "1:maps|";
+ uint64_t bucketStartTimeNs = 10 * NS_PER_SEC;
+ uint64_t eventStartTimeNs = bucketStartTimeNs + NS_PER_SEC + 1;
+ uint64_t bucketSizeNs = 30 * NS_PER_SEC;
+
+ sp<AnomalyTracker> anomalyTracker = new AnomalyTracker(alert, bucketSizeNs);
+ OringDurationTracker tracker("event", wizard, 1, true /*nesting*/, bucketStartTimeNs,
+ bucketSizeNs, {anomalyTracker}, buckets);
+
+ tracker.noteStart("", true, eventStartTimeNs, key1);
+ tracker.noteStop("", eventStartTimeNs + 10, false);
+ EXPECT_EQ(anomalyTracker->mLastAlarmTimestampNs, -1);
+ EXPECT_TRUE(tracker.mStarted.empty());
+ EXPECT_EQ(-1LL, tracker.mLastStartTime);
+ EXPECT_EQ(10LL, tracker.mDuration);
+
+ EXPECT_EQ(0u, tracker.mStarted.size());
+
+ tracker.noteStart("", true, eventStartTimeNs + 20, key1);
+ EXPECT_EQ(1u, anomalyTracker->mAlarms.size());
+ EXPECT_EQ((long long)(51ULL * NS_PER_SEC),
+ (long long)(anomalyTracker->mAlarms.begin()->second->timestampSec * NS_PER_SEC));
+ tracker.noteStop("", eventStartTimeNs + 2 * bucketSizeNs + 25, false);
+ EXPECT_EQ(anomalyTracker->getSumOverPastBuckets("event"), (long long)(bucketSizeNs));
+ EXPECT_EQ((long long)(eventStartTimeNs + 2 * bucketSizeNs + 25),
+ anomalyTracker->mLastAlarmTimestampNs);
}
} // namespace statsd
diff --git a/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp b/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp
index cd647cb..1ed3636 100644
--- a/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp
+++ b/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp
@@ -277,7 +277,7 @@
EXPECT_EQ(20, curInterval.raw.back().first);
EXPECT_EQ(0UL, valueProducer.mNextSlicedBucket.size());
- valueProducer.flush_if_needed(bucket3StartTimeNs);
+ valueProducer.flushIfNeeded(bucket3StartTimeNs);
EXPECT_EQ(1UL, valueProducer.mPastBuckets.size());
EXPECT_EQ(1UL, valueProducer.mPastBuckets.begin()->second.size());
EXPECT_EQ(30, valueProducer.mPastBuckets.begin()->second.back().mValue);