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/Android.mk b/cmds/statsd/Android.mk
index 45ae56f..1b2f9da 100644
--- a/cmds/statsd/Android.mk
+++ b/cmds/statsd/Android.mk
@@ -21,6 +21,7 @@
src/statsd_config.proto \
src/atoms_copy.proto \
src/anomaly/AnomalyMonitor.cpp \
+ src/anomaly/AnomalyTracker.cpp \
src/condition/CombinationConditionTracker.cpp \
src/condition/condition_util.cpp \
src/condition/SimpleConditionTracker.cpp \
@@ -39,7 +40,6 @@
src/matchers/CombinationLogMatchingTracker.cpp \
src/matchers/matcher_util.cpp \
src/matchers/SimpleLogMatchingTracker.cpp \
- src/anomaly/DiscreteAnomalyTracker.cpp \
src/metrics/MetricProducer.cpp \
src/metrics/EventMetricProducer.cpp \
src/metrics/CountMetricProducer.cpp \
@@ -178,4 +178,3 @@
statsd_common_c_includes:=
include $(BUILD_NATIVE_TEST)
-
diff --git a/cmds/statsd/src/StatsLogProcessor.cpp b/cmds/statsd/src/StatsLogProcessor.cpp
index abd2a35..fcb5107 100644
--- a/cmds/statsd/src/StatsLogProcessor.cpp
+++ b/cmds/statsd/src/StatsLogProcessor.cpp
@@ -50,13 +50,24 @@
const int FIELD_ID_NAME = 2;
StatsLogProcessor::StatsLogProcessor(const sp<UidMap>& uidMap,
+ const sp<AnomalyMonitor>& anomalyMonitor,
const std::function<void(const ConfigKey&)>& sendBroadcast)
- : mUidMap(uidMap), mSendBroadcast(sendBroadcast) {
+ : mUidMap(uidMap), mAnomalyMonitor(anomalyMonitor), mSendBroadcast(sendBroadcast) {
}
StatsLogProcessor::~StatsLogProcessor() {
}
+void StatsLogProcessor::onAnomalyAlarmFired(
+ const uint64_t timestampNs,
+ unordered_set<sp<const AnomalyAlarm>, SpHash<AnomalyAlarm>> anomalySet) {
+ for (const auto& anomaly : anomalySet) {
+ for (const auto& itr : mMetricsManagers) {
+ itr.second->onAnomalyAlarmFired(timestampNs, anomaly);
+ }
+ }
+}
+
// TODO: what if statsd service restarts? How do we know what logs are already processed before?
void StatsLogProcessor::OnLogEvent(const LogEvent& msg) {
// pass the event to metrics managers.
@@ -93,6 +104,7 @@
unique_ptr<MetricsManager> newMetricsManager = std::make_unique<MetricsManager>(config);
if (newMetricsManager->isConfigValid()) {
mUidMap->OnConfigUpdated(key);
+ newMetricsManager->setAnomalyMonitor(mAnomalyMonitor);
mMetricsManagers[key] = std::move(newMetricsManager);
// Why doesn't this work? mMetricsManagers.insert({key, std::move(newMetricsManager)});
ALOGD("StatsdConfig valid");
diff --git a/cmds/statsd/src/StatsLogProcessor.h b/cmds/statsd/src/StatsLogProcessor.h
index 2091774..510dc51 100644
--- a/cmds/statsd/src/StatsLogProcessor.h
+++ b/cmds/statsd/src/StatsLogProcessor.h
@@ -32,7 +32,7 @@
class StatsLogProcessor : public ConfigListener {
public:
- StatsLogProcessor(const sp<UidMap>& uidMap,
+ StatsLogProcessor(const sp<UidMap>& uidMap, const sp<AnomalyMonitor>& anomalyMonitor,
const std::function<void(const ConfigKey&)>& sendBroadcast);
virtual ~StatsLogProcessor();
@@ -42,8 +42,11 @@
void OnConfigRemoved(const ConfigKey& key);
size_t GetMetricsSize(const ConfigKey& key);
-
+
void onDumpReport(const ConfigKey& key, vector<uint8_t>* outData);
+ void onAnomalyAlarmFired(
+ const uint64_t timestampNs,
+ unordered_set<sp<const AnomalyAlarm>, SpHash<AnomalyAlarm>> anomalySet);
private:
mutable mutex mBroadcastTimesMutex;
@@ -54,6 +57,8 @@
sp<UidMap> mUidMap; // Reference to the UidMap to lookup app name and version for each uid.
+ sp<AnomalyMonitor> mAnomalyMonitor;
+
/* Max *serialized* size of the logs kept in memory before flushing through binder call.
Proto lite does not implement the SpaceUsed() function which gives the in memory byte size.
So we cap memory usage by limiting the serialized size. Note that protobuf's in memory size
diff --git a/cmds/statsd/src/StatsService.cpp b/cmds/statsd/src/StatsService.cpp
index 31d32b4..1a056df 100644
--- a/cmds/statsd/src/StatsService.cpp
+++ b/cmds/statsd/src/StatsService.cpp
@@ -73,7 +73,7 @@
{
mUidMap = new UidMap();
mConfigManager = new ConfigManager();
- mProcessor = new StatsLogProcessor(mUidMap, [this](const ConfigKey& key) {
+ mProcessor = new StatsLogProcessor(mUidMap, mAnomalyMonitor, [this](const ConfigKey& key) {
auto sc = getStatsCompanionService();
auto receiver = mConfigManager->GetConfigReceiver(key);
if (sc == nullptr) {
@@ -554,7 +554,10 @@
if (DEBUG) ALOGD("StatsService::informAnomalyAlarmFired succeeded");
// TODO: check through all counters/timers and see if an anomaly has indeed occurred.
-
+ uint64_t currentTimeNs = time(nullptr) * NS_PER_SEC;
+ std::unordered_set<sp<const AnomalyAlarm>, SpHash<AnomalyAlarm>> anomalySet =
+ mAnomalyMonitor->onAlarmFired(currentTimeNs);
+ mProcessor->onAnomalyAlarmFired(currentTimeNs, anomalySet);
return Status::ok();
}
diff --git a/cmds/statsd/src/anomaly/AnomalyMonitor.cpp b/cmds/statsd/src/anomaly/AnomalyMonitor.cpp
index 7a46410..da52a9d 100644
--- a/cmds/statsd/src/anomaly/AnomalyMonitor.cpp
+++ b/cmds/statsd/src/anomaly/AnomalyMonitor.cpp
@@ -129,6 +129,11 @@
return ((int64_t)timeSec) * 1000;
}
+unordered_set<sp<const AnomalyAlarm>, SpHash<AnomalyAlarm>> AnomalyMonitor::onAlarmFired(
+ uint64_t timestampNs) {
+ return popSoonerThan(static_cast<uint32_t>(timestampNs));
+}
+
} // namespace statsd
} // namespace os
} // namespace android
diff --git a/cmds/statsd/src/anomaly/AnomalyMonitor.h b/cmds/statsd/src/anomaly/AnomalyMonitor.h
index d9207e9..0bd5055 100644
--- a/cmds/statsd/src/anomaly/AnomalyMonitor.h
+++ b/cmds/statsd/src/anomaly/AnomalyMonitor.h
@@ -14,8 +14,7 @@
* limitations under the License.
*/
-#ifndef ANOMALY_MONITOR_H
-#define ANOMALY_MONITOR_H
+#pragma once
#include "anomaly/indexed_priority_queue.h"
@@ -23,6 +22,8 @@
#include <utils/RefBase.h>
#include <queue>
+#include <set>
+#include <unordered_map>
#include <unordered_set>
#include <vector>
@@ -114,6 +115,8 @@
return mRegisteredAlarmTimeSec;
}
+ unordered_set<sp<const AnomalyAlarm>, SpHash<AnomalyAlarm>> onAlarmFired(uint64_t timestampNs);
+
private:
std::mutex mLock;
@@ -154,5 +157,3 @@
} // namespace statsd
} // namespace os
} // namespace android
-
-#endif // ANOMALY_MONITOR_H
diff --git a/cmds/statsd/src/anomaly/AnomalyTracker.cpp b/cmds/statsd/src/anomaly/AnomalyTracker.cpp
new file mode 100644
index 0000000..0904a04
--- /dev/null
+++ b/cmds/statsd/src/anomaly/AnomalyTracker.cpp
@@ -0,0 +1,276 @@
+/*
+ * 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.
+ */
+
+#define DEBUG true // STOPSHIP if true
+#include "Log.h"
+
+#include "AnomalyTracker.h"
+
+#include <time.h>
+
+namespace android {
+namespace os {
+namespace statsd {
+
+AnomalyTracker::AnomalyTracker(const Alert& alert, const int64_t& bucketSizeNs)
+ : mAlert(alert),
+ mBucketSizeNs(bucketSizeNs),
+ mNumOfPastPackets(mAlert.number_of_buckets() - 1) {
+ VLOG("AnomalyTracker() called");
+ if (mAlert.number_of_buckets() <= 0) {
+ ALOGE("Cannot create DiscreteAnomalyTracker with %lld buckets",
+ (long long)mAlert.number_of_buckets());
+ return;
+ }
+ if (mBucketSizeNs <= 0) {
+ ALOGE("Cannot create DiscreteAnomalyTracker with bucket size %lld ",
+ (long long)mBucketSizeNs);
+ return;
+ }
+ if (!mAlert.has_trigger_if_sum_gt()) {
+ ALOGE("Cannot create DiscreteAnomalyTracker without threshold");
+ return;
+ }
+ reset(); // initialization
+}
+
+AnomalyTracker::~AnomalyTracker() {
+ VLOG("~AnomalyTracker() called");
+ stopAllAlarms();
+}
+
+void AnomalyTracker::reset() {
+ VLOG("reset() called.");
+ stopAllAlarms();
+ mPastBuckets.clear();
+ // Excludes the current bucket.
+ mPastBuckets.resize(mNumOfPastPackets);
+ mSumOverPastBuckets.clear();
+ mMostRecentBucketNum = -1;
+ mLastAlarmTimestampNs = -1;
+}
+
+size_t AnomalyTracker::index(int64_t bucketNum) const {
+ return bucketNum % mNumOfPastPackets;
+}
+
+void AnomalyTracker::flushPastBuckets(const int64_t& latestPastBucketNum) {
+ VLOG("addPastBucket() called.");
+ if (latestPastBucketNum <= mMostRecentBucketNum - mNumOfPastPackets) {
+ ALOGE("Cannot add a past bucket %lld units in past", (long long)latestPastBucketNum);
+ return;
+ }
+
+ // The past packets are ancient. Empty out old mPastBuckets[i] values and reset
+ // mSumOverPastBuckets.
+ if (latestPastBucketNum - mMostRecentBucketNum >= mNumOfPastPackets) {
+ mPastBuckets.clear();
+ mPastBuckets.resize(mNumOfPastPackets);
+ mSumOverPastBuckets.clear();
+ } else {
+ for (int64_t i = std::max(0LL, (long long)(mMostRecentBucketNum - mNumOfPastPackets + 1));
+ i <= latestPastBucketNum - mNumOfPastPackets; i++) {
+ const int idx = index(i);
+ subtractBucketFromSum(mPastBuckets[idx]);
+ mPastBuckets[idx] = nullptr; // release (but not clear) the old bucket.
+ }
+ }
+
+ // It is an update operation.
+ if (latestPastBucketNum <= mMostRecentBucketNum &&
+ latestPastBucketNum > mMostRecentBucketNum - mNumOfPastPackets) {
+ subtractBucketFromSum(mPastBuckets[index(latestPastBucketNum)]);
+ }
+}
+
+void AnomalyTracker::addPastBucket(const HashableDimensionKey& key, const int64_t& bucketValue,
+ const int64_t& bucketNum) {
+ flushPastBuckets(bucketNum);
+
+ auto& bucket = mPastBuckets[index(bucketNum)];
+ if (bucket == nullptr) {
+ bucket = std::make_shared<DimToValMap>();
+ }
+ bucket->insert({key, bucketValue});
+ addBucketToSum(bucket);
+ mMostRecentBucketNum = std::max(mMostRecentBucketNum, bucketNum);
+}
+
+void AnomalyTracker::addPastBucket(std::shared_ptr<DimToValMap> bucketValues,
+ const int64_t& bucketNum) {
+ VLOG("addPastBucket() called.");
+ flushPastBuckets(bucketNum);
+ // Replace the oldest bucket with the new bucket we are adding.
+ mPastBuckets[index(bucketNum)] = bucketValues;
+ addBucketToSum(bucketValues);
+ mMostRecentBucketNum = std::max(mMostRecentBucketNum, bucketNum);
+}
+
+void AnomalyTracker::subtractBucketFromSum(const shared_ptr<DimToValMap>& bucket) {
+ if (bucket == nullptr) {
+ return;
+ }
+ // For each dimension present in the bucket, subtract its value from its corresponding sum.
+ for (const auto& keyValuePair : *bucket) {
+ auto itr = mSumOverPastBuckets.find(keyValuePair.first);
+ if (itr == mSumOverPastBuckets.end()) {
+ continue;
+ }
+ itr->second -= keyValuePair.second;
+ // TODO: No need to look up the object twice like this. Use a var.
+ if (itr->second == 0) {
+ mSumOverPastBuckets.erase(itr);
+ }
+ }
+}
+
+void AnomalyTracker::addBucketToSum(const shared_ptr<DimToValMap>& bucket) {
+ if (bucket == nullptr) {
+ return;
+ }
+ // For each dimension present in the bucket, add its value to its corresponding sum.
+ for (const auto& keyValuePair : *bucket) {
+ mSumOverPastBuckets[keyValuePair.first] += keyValuePair.second;
+ }
+}
+
+int64_t AnomalyTracker::getPastBucketValue(const HashableDimensionKey& key,
+ const int64_t& bucketNum) const {
+ const auto& bucket = mPastBuckets[index(bucketNum)];
+ if (bucket == nullptr) {
+ return 0;
+ }
+ const auto& itr = bucket->find(key);
+ return itr == bucket->end() ? 0 : itr->second;
+}
+
+int64_t AnomalyTracker::getSumOverPastBuckets(const HashableDimensionKey& key) const {
+ const auto& itr = mSumOverPastBuckets.find(key);
+ if (itr != mSumOverPastBuckets.end()) {
+ return itr->second;
+ }
+ return 0;
+}
+
+bool AnomalyTracker::detectAnomaly(const int64_t& currentBucketNum,
+ const DimToValMap& currentBucket) {
+ if (currentBucketNum > mMostRecentBucketNum + 1) {
+ addPastBucket(nullptr, currentBucketNum - 1);
+ }
+ for (auto itr = currentBucket.begin(); itr != currentBucket.end(); itr++) {
+ if (itr->second + getSumOverPastBuckets(itr->first) > mAlert.trigger_if_sum_gt()) {
+ return true;
+ }
+ }
+ // In theory, we also need to check the dimsions not in the current bucket. In single-thread
+ // mode, usually we could avoid the following loops.
+ for (auto itr = mSumOverPastBuckets.begin(); itr != mSumOverPastBuckets.end(); itr++) {
+ if (itr->second > mAlert.trigger_if_sum_gt()) {
+ return true;
+ }
+ }
+ return false;
+}
+
+bool AnomalyTracker::detectAnomaly(const int64_t& currentBucketNum, const HashableDimensionKey& key,
+ const int64_t& currentBucketValue) {
+ if (currentBucketNum > mMostRecentBucketNum + 1) {
+ addPastBucket(key, 0, currentBucketNum - 1);
+ }
+ return getSumOverPastBuckets(key) + currentBucketValue > mAlert.trigger_if_sum_gt();
+}
+
+void AnomalyTracker::declareAnomaly(const uint64_t& timestamp) {
+ if (mLastAlarmTimestampNs >= 0 &&
+ timestamp - mLastAlarmTimestampNs <= mAlert.refractory_period_secs() * NS_PER_SEC) {
+ VLOG("Skipping anomaly check since within refractory period");
+ return;
+ }
+ // TODO(guardrail): Consider guarding against too short refractory periods.
+ mLastAlarmTimestampNs = timestamp;
+
+ if (mAlert.has_incidentd_details()) {
+ // TODO: Can construct a name based on the criteria (and/or relay the criteria).
+ ALOGW("An anomaly (nameless) has occurred! Informing incidentd.");
+ // TODO: Send incidentd_details.name and incidentd_details.incidentd_sections to incidentd
+ } else {
+ ALOGW("An anomaly has occurred! (But informing incidentd not requested.)");
+ }
+}
+
+void AnomalyTracker::declareAnomalyIfAlarmExpired(const HashableDimensionKey& dimensionKey,
+ const uint64_t& timestamp) {
+ auto itr = mAlarms.find(dimensionKey);
+ if (itr == mAlarms.end()) {
+ return;
+ }
+
+ if (itr->second != nullptr &&
+ static_cast<uint32_t>(timestamp / NS_PER_SEC) >= itr->second->timestampSec) {
+ declareAnomaly(timestamp);
+ stopAlarm(dimensionKey);
+ }
+}
+
+void AnomalyTracker::detectAndDeclareAnomaly(const uint64_t& timestamp,
+ const int64_t& currBucketNum,
+ const HashableDimensionKey& key,
+ const int64_t& currentBucketValue) {
+ if (detectAnomaly(currBucketNum, key, currentBucketValue)) {
+ declareAnomaly(timestamp);
+ }
+}
+
+void AnomalyTracker::detectAndDeclareAnomaly(const uint64_t& timestamp,
+ const int64_t& currBucketNum,
+ const DimToValMap& currentBucket) {
+ if (detectAnomaly(currBucketNum, currentBucket)) {
+ declareAnomaly(timestamp);
+ }
+}
+
+void AnomalyTracker::startAlarm(const HashableDimensionKey& dimensionKey,
+ const uint64_t& timestamp) {
+ sp<const AnomalyAlarm> alarm = new AnomalyAlarm{static_cast<uint32_t>(timestamp / NS_PER_SEC)};
+ mAlarms.insert({dimensionKey, alarm});
+ if (mAnomalyMonitor != nullptr) {
+ mAnomalyMonitor->add(alarm);
+ }
+}
+
+void AnomalyTracker::stopAlarm(const HashableDimensionKey& dimensionKey) {
+ auto itr = mAlarms.find(dimensionKey);
+ if (itr != mAlarms.end()) {
+ mAlarms.erase(dimensionKey);
+ }
+ if (mAnomalyMonitor != nullptr) {
+ mAnomalyMonitor->remove(itr->second);
+ }
+}
+
+void AnomalyTracker::stopAllAlarms() {
+ std::set<HashableDimensionKey> keys;
+ for (auto itr = mAlarms.begin(); itr != mAlarms.end(); ++itr) {
+ keys.insert(itr->first);
+ }
+ for (auto key : keys) {
+ stopAlarm(key);
+ }
+}
+
+} // namespace statsd
+} // namespace os
+} // namespace android
diff --git a/cmds/statsd/src/anomaly/AnomalyTracker.h b/cmds/statsd/src/anomaly/AnomalyTracker.h
new file mode 100644
index 0000000..ce6c995
--- /dev/null
+++ b/cmds/statsd/src/anomaly/AnomalyTracker.h
@@ -0,0 +1,156 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include <gtest/gtest_prod.h>
+#include "AnomalyMonitor.h"
+#include "frameworks/base/cmds/statsd/src/statsd_config.pb.h" // Alert
+#include "stats_util.h" // HashableDimensionKey and DimToValMap
+
+#include <memory> // unique_ptr
+#include <stdlib.h>
+#include <utils/RefBase.h>
+
+namespace android {
+namespace os {
+namespace statsd {
+
+using std::unordered_map;
+using std::shared_ptr;
+
+// This anomaly track assmues that all values are non-negative.
+class AnomalyTracker : public virtual RefBase {
+public:
+ AnomalyTracker(const Alert& alert, const int64_t& bucketSizeNs);
+
+ virtual ~AnomalyTracker();
+
+ // Adds a bucket.
+ // Bucket index starts from 0.
+ void addPastBucket(std::shared_ptr<DimToValMap> bucketValues, const int64_t& bucketNum);
+ void addPastBucket(const HashableDimensionKey& key, const int64_t& bucketValue,
+ const int64_t& bucketNum);
+
+ // Returns true if detected anomaly for the existing buckets on one or more dimension keys.
+ bool detectAnomaly(const int64_t& currBucketNum, const DimToValMap& currentBucket);
+ bool detectAnomaly(const int64_t& currBucketNum, const HashableDimensionKey& key,
+ const int64_t& currentBucketValue);
+
+ // Informs incidentd about the detected alert.
+ void declareAnomaly(const uint64_t& timestamp);
+
+ // Detects the alert and informs the incidentd when applicable.
+ void detectAndDeclareAnomaly(const uint64_t& timestamp, const int64_t& currBucketNum,
+ const DimToValMap& currentBucket);
+ void detectAndDeclareAnomaly(const uint64_t& timestamp, const int64_t& currBucketNum,
+ const HashableDimensionKey& key,
+ const int64_t& currentBucketValue);
+
+ // Starts the alarm at the given timestamp.
+ void startAlarm(const HashableDimensionKey& dimensionKey, const uint64_t& eventTime);
+ // Stops the alarm.
+ void stopAlarm(const HashableDimensionKey& dimensionKey);
+
+ // Stop all the alarms owned by this tracker.
+ void stopAllAlarms();
+
+ // Init the anmaly monitor which is shared across anomaly trackers.
+ inline void setAnomalyMonitor(const sp<AnomalyMonitor>& anomalyMonitor) {
+ mAnomalyMonitor = anomalyMonitor;
+ }
+
+ // Declares the anomaly when the alarm expired given the current timestamp.
+ void declareAnomalyIfAlarmExpired(const HashableDimensionKey& dimensionKey,
+ const uint64_t& timestamp);
+
+ // Helper function to return the sum value of past buckets at given dimension.
+ int64_t getSumOverPastBuckets(const HashableDimensionKey& key) const;
+
+ // Helper function to return the value for a past bucket.
+ int64_t getPastBucketValue(const HashableDimensionKey& key, const int64_t& bucketNum) const;
+
+ // Returns the anomaly threshold.
+ inline int64_t getAnomalyThreshold() const {
+ return mAlert.trigger_if_sum_gt();
+ }
+
+ // Helper function to return the last alarm timestamp.
+ inline int64_t getLastAlarmTimestampNs() const {
+ return mLastAlarmTimestampNs;
+ }
+
+ inline int getNumOfPastPackets() const {
+ return mNumOfPastPackets;
+ }
+
+protected:
+ void flushPastBuckets(const int64_t& currBucketNum);
+ // statsd_config.proto Alert message that defines this tracker.
+ const Alert mAlert;
+
+ // Bucket duration in ns.
+ int64_t mBucketSizeNs = 0;
+
+ // The number of past packets to track in the anomaly detection.
+ int mNumOfPastPackets = 0;
+
+ // The alarms owned by this tracker. The alarm monitor also shares the alarm pointers when they
+ // are still active.
+ std::unordered_map<HashableDimensionKey, sp<const AnomalyAlarm>> mAlarms;
+
+ // Anomaly alarm monitor.
+ sp<AnomalyMonitor> mAnomalyMonitor;
+
+ // The exisiting bucket list.
+ std::vector<shared_ptr<DimToValMap>> mPastBuckets;
+
+ // Sum over all existing buckets cached in mPastBuckets.
+ DimToValMap mSumOverPastBuckets;
+
+ // The bucket number of the last added bucket.
+ int64_t mMostRecentBucketNum = -1;
+
+ // The timestamp when the last anomaly was declared.
+ int64_t mLastAlarmTimestampNs = -1;
+
+ // Add the information in the given bucket to mSumOverPastBuckets.
+ void addBucketToSum(const shared_ptr<DimToValMap>& bucket);
+
+ // Subtract the information in the given bucket from mSumOverPastBuckets
+ // and remove any items with value 0.
+ void subtractBucketFromSum(const shared_ptr<DimToValMap>& bucket);
+
+ // Calculates the corresponding bucket index within the circular array.
+ size_t index(int64_t bucketNum) const;
+
+ // Resets all data. For use when all the data gets stale.
+ void reset();
+
+ FRIEND_TEST(AnomalyTrackerTest, TestConsecutiveBuckets);
+ FRIEND_TEST(AnomalyTrackerTest, TestSparseBuckets);
+ FRIEND_TEST(GaugeMetricProducerTest, TestAnomalyDetection);
+ FRIEND_TEST(CountMetricProducerTest, TestAnomalyDetection);
+ FRIEND_TEST(OringDurationTrackerTest, TestPredictAnomalyTimestamp);
+ FRIEND_TEST(OringDurationTrackerTest, TestAnomalyDetection);
+ FRIEND_TEST(MaxDurationTrackerTest, TestAnomalyDetection);
+ FRIEND_TEST(MaxDurationTrackerTest, TestAnomalyDetection);
+ FRIEND_TEST(OringDurationTrackerTest, TestAnomalyDetection);
+};
+
+} // namespace statsd
+} // namespace os
+} // namespace android
diff --git a/cmds/statsd/src/anomaly/DiscreteAnomalyTracker.cpp b/cmds/statsd/src/anomaly/DiscreteAnomalyTracker.cpp
deleted file mode 100644
index 6492177..0000000
--- a/cmds/statsd/src/anomaly/DiscreteAnomalyTracker.cpp
+++ /dev/null
@@ -1,153 +0,0 @@
-/*
- * 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.
- */
-
-#define DEBUG true // STOPSHIP if true
-#include "Log.h"
-
-#include "DiscreteAnomalyTracker.h"
-
-#include <time.h>
-
-namespace android {
-namespace os {
-namespace statsd {
-
-DiscreteAnomalyTracker::DiscreteAnomalyTracker(const Alert& alert) : mAlert(alert) {
- VLOG("DiscreteAnomalyTracker() called");
- if (mAlert.number_of_buckets() <= 0) {
- ALOGE("Cannot create DiscreteAnomalyTracker with %lld buckets",
- (long long)mAlert.number_of_buckets());
- return;
- }
- mPastBuckets.resize(mAlert.number_of_buckets());
- reset(); // initialization
-}
-
-DiscreteAnomalyTracker::~DiscreteAnomalyTracker() {
- VLOG("~DiscreteAnomalyTracker() called");
-}
-
-void DiscreteAnomalyTracker::reset() {
- VLOG("reset() called.");
- mPastBuckets.clear();
- mPastBuckets.resize(mAlert.number_of_buckets());
- mSumOverPastBuckets.clear();
- mCurrentBucketIndex = -1;
- mLastAlarmAtBucketIndex = -1;
- mAnomalyDeclared = 0;
-}
-
-size_t DiscreteAnomalyTracker::index(int64_t bucketNum) {
- return bucketNum % mAlert.number_of_buckets();
-}
-
-void DiscreteAnomalyTracker::addOrUpdateBucket(std::shared_ptr<const DimToValMap> BucketValues,
- int64_t bucketIndex) {
- VLOG("addPastBucket() called.");
- if (bucketIndex <= mCurrentBucketIndex - mAlert.number_of_buckets()) {
- ALOGE("Cannot add a past bucket %lld units in past", (long long)bucketIndex);
- return;
- }
-
- // Empty out old mPastBuckets[i] values and update mSumOverPastBuckets.
- if (bucketIndex - mCurrentBucketIndex >= mAlert.number_of_buckets()) {
- mPastBuckets.clear();
- mPastBuckets.resize(mAlert.number_of_buckets());
- mSumOverPastBuckets.clear();
- } else {
- for (int64_t i = std::max(
- 0LL, (long long)(mCurrentBucketIndex - mAlert.number_of_buckets() + 1));
- i < bucketIndex - mAlert.number_of_buckets(); i++) {
- const int idx = index(i);
- subtractBucketFromSum(mPastBuckets[idx]);
- mPastBuckets[idx] = nullptr; // release (but not clear) the old bucket.
- }
- }
- subtractBucketFromSum(mPastBuckets[index(bucketIndex)]);
- mPastBuckets[index(bucketIndex)] = nullptr; // release (but not clear) the old bucket.
-
- // Replace the oldest bucket with the new bucket we are adding.
- mPastBuckets[index(bucketIndex)] = BucketValues;
- addBucketToSum(BucketValues);
-
- mCurrentBucketIndex = std::max(mCurrentBucketIndex, bucketIndex);
-}
-
-void DiscreteAnomalyTracker::subtractBucketFromSum(const shared_ptr<const DimToValMap>& bucket) {
- if (bucket == nullptr) {
- return;
- }
- // For each dimension present in the bucket, subtract its value from its corresponding sum.
- for (const auto& keyValuePair : *bucket) {
- auto itr = mSumOverPastBuckets.find(keyValuePair.first);
- if (itr == mSumOverPastBuckets.end()) {
- continue;
- }
- itr->second -= keyValuePair.second;
- // TODO: No need to look up the object twice like this. Use a var.
- if (itr->second == 0) {
- mSumOverPastBuckets.erase(itr);
- }
- }
-}
-
-void DiscreteAnomalyTracker::addBucketToSum(const shared_ptr<const DimToValMap>& bucket) {
- if (bucket == nullptr) {
- return;
- }
- // For each dimension present in the bucket, add its value to its corresponding sum.
- for (const auto& keyValuePair : *bucket) {
- mSumOverPastBuckets[keyValuePair.first] += keyValuePair.second;
- }
-}
-
-bool DiscreteAnomalyTracker::detectAnomaly() {
- for (auto itr = mSumOverPastBuckets.begin(); itr != mSumOverPastBuckets.end(); itr++) {
- if (mAlert.has_trigger_if_sum_gt() && itr->second > mAlert.trigger_if_sum_gt()) {
- return true;
- }
- }
- return false;
-}
-
-void DiscreteAnomalyTracker::declareAndDeclareAnomaly() {
- if (detectAnomaly()) {
- declareAnomaly();
- }
-}
-
-void DiscreteAnomalyTracker::declareAnomaly() {
- if (mLastAlarmAtBucketIndex >= 0 && mCurrentBucketIndex - mLastAlarmAtBucketIndex <=
- (long long)mAlert.refractory_period_in_buckets()) {
- VLOG("Skipping anomaly check since within refractory period");
- return;
- }
- mAnomalyDeclared++;
- // TODO(guardrail): Consider guarding against too short refractory periods.
- mLastAlarmAtBucketIndex = mCurrentBucketIndex;
-
- if (mAlert.has_incidentd_details()) {
- // TODO: Can construct a name based on the criteria (and/or relay the criteria).
- ALOGW("An anomaly (nameless) has occurred! Informing incidentd.");
- // TODO: Send incidentd_details.name and incidentd_details.incidentd_sections to incidentd
- } else {
- ALOGW("An anomaly has occurred! (But informing incidentd not requested.)");
- }
-}
-
-} // namespace statsd
-} // namespace os
-} // namespace android
diff --git a/cmds/statsd/src/anomaly/DiscreteAnomalyTracker.h b/cmds/statsd/src/anomaly/DiscreteAnomalyTracker.h
deleted file mode 100644
index ed7d5d7..0000000
--- a/cmds/statsd/src/anomaly/DiscreteAnomalyTracker.h
+++ /dev/null
@@ -1,91 +0,0 @@
-/*
- * 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.
- */
-
-#pragma once
-
-#include <gtest/gtest_prod.h>
-#include "frameworks/base/cmds/statsd/src/statsd_config.pb.h" // Alert
-#include "stats_util.h" // HashableDimensionKey and DimToValMap
-
-#include <memory> // unique_ptr
-#include <stdlib.h>
-
-namespace android {
-namespace os {
-namespace statsd {
-
-using std::unordered_map;
-using std::shared_ptr;
-
-// This anomaly track assmues that all values are non-negative.
-class DiscreteAnomalyTracker {
- public:
- DiscreteAnomalyTracker(const Alert& alert);
-
- virtual ~DiscreteAnomalyTracker();
-
- // Adds a new bucket or updates an existing bucket.
- // Bucket index starts from 0.
- void addOrUpdateBucket(std::shared_ptr<const DimToValMap> BucketValues, int64_t bucketIndex);
-
- // Returns true if detected anomaly for the existing buckets on one or more dimension keys.
- bool detectAnomaly();
-
- // Informs incidentd about the detected alert.
- void declareAnomaly();
-
- // Detects the alert and informs the incidentd when applicable.
- void declareAndDeclareAnomaly();
-
-private:
- // statsd_config.proto Alert message that defines this tracker.
- const Alert mAlert;
-
- // The exisiting bucket list.
- std::vector<shared_ptr<const DimToValMap>> mPastBuckets;
-
- // Sum over all existing buckets cached in mPastBuckets.
- DimToValMap mSumOverPastBuckets;
-
- // Current bucket index of the current anomaly detection window. Bucket index starts from 0.
- int64_t mCurrentBucketIndex = -1;
-
- // The bucket index when the last anomaly was declared.
- int64_t mLastAlarmAtBucketIndex = -1;
-
- // The total number of declared anomalies.
- int64_t mAnomalyDeclared = 0;
-
- // Add the information in the given bucket to mSumOverPastBuckets.
- void addBucketToSum(const shared_ptr<const DimToValMap>& bucket);
-
- // Subtract the information in the given bucket from mSumOverPastBuckets
- // and remove any items with value 0.
- void subtractBucketFromSum(const shared_ptr<const DimToValMap>& bucket);
-
- // Calculates the corresponding bucket index within the circular array.
- size_t index(int64_t bucketNum);
-
- // Resets all data. For use when all the data gets stale.
- void reset();
-
- FRIEND_TEST(AnomalyTrackerTest, TestConsecutiveBuckets);
- FRIEND_TEST(AnomalyTrackerTest, TestSparseBuckets);
-};
-
-} // namespace statsd
-} // namespace os
-} // namespace android
diff --git a/cmds/statsd/src/anomaly/indexed_priority_queue.h b/cmds/statsd/src/anomaly/indexed_priority_queue.h
index 1a2e9c2..4982d4b 100644
--- a/cmds/statsd/src/anomaly/indexed_priority_queue.h
+++ b/cmds/statsd/src/anomaly/indexed_priority_queue.h
@@ -16,8 +16,6 @@
#pragma once
-#include "Log.h"
-
#include <utils/RefBase.h>
#include <unordered_map>
#include <vector>
@@ -28,7 +26,7 @@
namespace os {
namespace statsd {
-/** Defines a hash function for sp<AA>, returning the hash of the underlying pointer. */
+/** Defines a hash function for sp<const AA>, returning the hash of the underlying pointer. */
template <class AA>
struct SpHash {
size_t operator()(const sp<const AA>& k) const {
@@ -39,7 +37,7 @@
/**
* Min priority queue for generic type AA.
* Unlike a regular priority queue, this class is also capable of removing interior elements.
- * @tparam Comparator must implement [bool operator()(sp<const AA> a, sp<const AA> b)], returning
+ * @tparam Comparator must implement [bool operator()(sp< AA> a, sp< AA> b)], returning
* whether a should be closer to the top of the queue than b.
*/
template <class AA, class Comparator>
@@ -104,7 +102,6 @@
if (!contains(a)) return;
size_t idx = indices[a];
if (idx >= pq.size()) {
- ALOGE("indexed_priority_queue: Invalid index in map of indices.");
return;
}
if (idx == size()) { // if a is the last element, i.e. at index idx == size() == (pq.size()-1)
@@ -193,7 +190,6 @@
template <class AA, class Comparator>
bool indexed_priority_queue<AA, Comparator>::higher(size_t idx1, size_t idx2) const {
if (!(0u < idx1 && idx1 < pq.size() && 0u < idx2 && idx2 < pq.size())) {
- ALOGE("indexed_priority_queue: Attempting to access invalid index");
return false; // got to do something.
}
return Comparator()(pq[idx1], pq[idx2]);
@@ -208,7 +204,6 @@
template <class AA, class Comparator>
void indexed_priority_queue<AA, Comparator>::swap_indices(size_t i, size_t j) {
if (!(0u < i && i < pq.size() && 0u < j && j < pq.size())) {
- ALOGE("indexed_priority_queue: Attempting to swap invalid index");
return;
}
sp<const AA> val_i = pq[i];
diff --git a/cmds/statsd/src/metrics/CountMetricProducer.cpp b/cmds/statsd/src/metrics/CountMetricProducer.cpp
index f9da68e..d47bd4f 100644
--- a/cmds/statsd/src/metrics/CountMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/CountMetricProducer.cpp
@@ -17,7 +17,6 @@
#define DEBUG true // STOPSHIP if true
#include "Log.h"
-#include "../anomaly/DiscreteAnomalyTracker.h"
#include "CountMetricProducer.h"
#include "stats_util.h"
@@ -114,7 +113,7 @@
// Dump current bucket if it's stale.
// If current bucket is still on-going, don't force dump current bucket.
// In finish(), We can force dump current bucket.
- flushCounterIfNeeded(endTime);
+ flushIfNeeded(endTime);
VLOG("metric %s dump report now...", mMetric.name().c_str());
for (const auto& counter : mPastBuckets) {
@@ -170,7 +169,6 @@
startNewProtoOutputStream(endTime);
mPastBuckets.clear();
- mByteSize = 0;
return buffer;
@@ -188,7 +186,7 @@
const LogEvent& event, bool scheduledPull) {
uint64_t eventTimeNs = event.GetTimestampNs();
- flushCounterIfNeeded(eventTimeNs);
+ flushIfNeeded(eventTimeNs);
if (condition == false) {
return;
@@ -205,41 +203,41 @@
count++;
}
- VLOG("metric %s %s->%d", mMetric.name().c_str(), eventKey.c_str(),
- (*mCurrentSlicedCounter)[eventKey]);
+ for (auto& tracker : mAnomalyTrackers) {
+ tracker->detectAndDeclareAnomaly(eventTimeNs, mCurrentBucketNum, eventKey,
+ mCurrentSlicedCounter->find(eventKey)->second);
+ }
+
+ VLOG("metric %s %s->%lld", mMetric.name().c_str(), eventKey.c_str(),
+ (long long)(*mCurrentSlicedCounter)[eventKey]);
}
// When a new matched event comes in, we check if event falls into the current
// bucket. If not, flush the old counter to past buckets and initialize the new bucket.
-void CountMetricProducer::flushCounterIfNeeded(const uint64_t eventTimeNs) {
- if (mCurrentBucketStartTimeNs + mBucketSizeNs > eventTimeNs) {
+void CountMetricProducer::flushIfNeeded(const uint64_t eventTimeNs) {
+ if (eventTimeNs < mCurrentBucketStartTimeNs + mBucketSizeNs) {
return;
}
- // adjust the bucket start time
- // TODO: This (and addPastBucket to which it goes) doesn't really need to be an int64.
- uint64_t numBucketsForward = (eventTimeNs - mCurrentBucketStartTimeNs) / mBucketSizeNs;
-
CountBucket info;
info.mBucketStartNs = mCurrentBucketStartTimeNs;
info.mBucketEndNs = mCurrentBucketStartTimeNs + mBucketSizeNs;
+ info.mBucketNum = mCurrentBucketNum;
for (const auto& counter : *mCurrentSlicedCounter) {
info.mCount = counter.second;
auto& bucketList = mPastBuckets[counter.first];
bucketList.push_back(info);
- VLOG("metric %s, dump key value: %s -> %d", mMetric.name().c_str(), counter.first.c_str(),
- counter.second);
- mByteSize += sizeof(info);
+ VLOG("metric %s, dump key value: %s -> %lld", mMetric.name().c_str(), counter.first.c_str(),
+ (long long)counter.second);
}
for (auto& tracker : mAnomalyTrackers) {
- tracker->addOrUpdateBucket(mCurrentSlicedCounter, mCurrentBucketNum);
- tracker->declareAndDeclareAnomaly();
+ tracker->addPastBucket(mCurrentSlicedCounter, mCurrentBucketNum);
}
// Reset counters (do not clear, since the old one is still referenced in mAnomalyTrackers).
mCurrentSlicedCounter = std::make_shared<DimToValMap>();
-
+ uint64_t numBucketsForward = (eventTimeNs - mCurrentBucketStartTimeNs) / mBucketSizeNs;
mCurrentBucketStartTimeNs = mCurrentBucketStartTimeNs + numBucketsForward * mBucketSizeNs;
mCurrentBucketNum += numBucketsForward;
VLOG("metric %s: new bucket start time: %lld", mMetric.name().c_str(),
@@ -250,7 +248,11 @@
// greater than actual data size as it contains each dimension of
// CountMetricData is duplicated.
size_t CountMetricProducer::byteSize() {
- return mByteSize;
+ size_t totalSize = 0;
+ for (const auto& pair : mPastBuckets) {
+ totalSize += pair.second.size() * kBucketSize;
+ }
+ return totalSize;
}
} // namespace statsd
diff --git a/cmds/statsd/src/metrics/CountMetricProducer.h b/cmds/statsd/src/metrics/CountMetricProducer.h
index b7e480c..b3f8ee3 100644
--- a/cmds/statsd/src/metrics/CountMetricProducer.h
+++ b/cmds/statsd/src/metrics/CountMetricProducer.h
@@ -21,9 +21,9 @@
#include <android/util/ProtoOutputStream.h>
#include <gtest/gtest_prod.h>
+#include "../anomaly/AnomalyTracker.h"
#include "../condition/ConditionTracker.h"
#include "../matchers/matcher_util.h"
-#include "../anomaly/DiscreteAnomalyTracker.h"
#include "MetricProducer.h"
#include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"
#include "stats_util.h"
@@ -36,6 +36,7 @@
int64_t mBucketStartNs;
int64_t mBucketEndNs;
int64_t mCount;
+ uint64_t mBucketNum;
};
class CountMetricProducer : public MetricProducer {
@@ -50,6 +51,8 @@
void finish() override;
+ void flushIfNeeded(const uint64_t newEventTime) override;
+
// TODO: Pass a timestamp as a parameter in onDumpReport.
std::unique_ptr<std::vector<uint8_t>> onDumpReport() override;
@@ -76,18 +79,15 @@
// TODO: Add a lock to mPastBuckets.
std::unordered_map<HashableDimensionKey, std::vector<CountBucket>> mPastBuckets;
- size_t mByteSize;
-
// The current bucket.
std::shared_ptr<DimToValMap> mCurrentSlicedCounter = std::make_shared<DimToValMap>();
- vector<std::unique_ptr<DiscreteAnomalyTracker>> mAnomalyTrackers;
-
- void flushCounterIfNeeded(const uint64_t newEventTime);
+ static const size_t kBucketSize = sizeof(CountBucket{});
FRIEND_TEST(CountMetricProducerTest, TestNonDimensionalEvents);
FRIEND_TEST(CountMetricProducerTest, TestEventsWithNonSlicedCondition);
FRIEND_TEST(CountMetricProducerTest, TestEventsWithSlicedCondition);
+ FRIEND_TEST(CountMetricProducerTest, TestAnomalyDetection);
};
} // namespace statsd
diff --git a/cmds/statsd/src/metrics/DurationMetricProducer.cpp b/cmds/statsd/src/metrics/DurationMetricProducer.cpp
index eba2e06..b0a97b1 100644
--- a/cmds/statsd/src/metrics/DurationMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/DurationMetricProducer.cpp
@@ -110,16 +110,16 @@
}
unique_ptr<DurationTracker> DurationMetricProducer::createDurationTracker(
- vector<DurationBucket>& bucket) {
+ const HashableDimensionKey& eventKey, vector<DurationBucket>& bucket) {
switch (mMetric.aggregation_type()) {
case DurationMetric_AggregationType_SUM:
- return make_unique<OringDurationTracker>(mWizard, mConditionTrackerIndex, mNested,
- mCurrentBucketStartTimeNs, mBucketSizeNs,
- bucket);
+ return make_unique<OringDurationTracker>(eventKey, mWizard, mConditionTrackerIndex,
+ mNested, mCurrentBucketStartTimeNs,
+ mBucketSizeNs, mAnomalyTrackers, bucket);
case DurationMetric_AggregationType_MAX_SPARSE:
- return make_unique<MaxDurationTracker>(mWizard, mConditionTrackerIndex, mNested,
- mCurrentBucketStartTimeNs, mBucketSizeNs,
- bucket);
+ return make_unique<MaxDurationTracker>(eventKey, mWizard, mConditionTrackerIndex,
+ mNested, mCurrentBucketStartTimeNs,
+ mBucketSizeNs, mAnomalyTrackers, bucket);
}
}
@@ -131,7 +131,6 @@
void DurationMetricProducer::onSlicedConditionMayChange(const uint64_t eventTime) {
VLOG("Metric %s onSlicedConditionMayChange", mMetric.name().c_str());
// Now for each of the on-going event, check if the condition has changed for them.
- flushIfNeeded(eventTime);
for (auto& pair : mCurrentSlicedDuration) {
pair.second->onSlicedConditionMayChange(eventTime);
}
@@ -142,27 +141,11 @@
mCondition = conditionMet;
// TODO: need to populate the condition change time from the event which triggers the condition
// change, instead of using current time.
-
- flushIfNeeded(eventTime);
for (auto& pair : mCurrentSlicedDuration) {
pair.second->onConditionChanged(conditionMet, eventTime);
}
}
-static void addDurationBucketsToReport(StatsLogReport_DurationMetricDataWrapper& wrapper,
- const vector<KeyValuePair>& key,
- const vector<DurationBucketInfo>& buckets) {
- DurationMetricData* data = wrapper.add_data();
- for (const auto& kv : key) {
- data->add_dimension()->CopyFrom(kv);
- }
- for (const auto& bucket : buckets) {
- data->add_bucket_info()->CopyFrom(bucket);
- VLOG("\t bucket [%lld - %lld] duration(ns): %lld", bucket.start_bucket_nanos(),
- bucket.end_bucket_nanos(), bucket.duration_nanos());
- }
-}
-
std::unique_ptr<std::vector<uint8_t>> DurationMetricProducer::onDumpReport() {
long long endTime = time(nullptr) * NS_PER_SEC;
@@ -231,7 +214,6 @@
if (mCurrentBucketStartTimeNs + mBucketSizeNs > eventTime) {
return;
}
-
VLOG("flushing...........");
for (auto it = mCurrentSlicedDuration.begin(); it != mCurrentSlicedDuration.end();) {
if (it->second->flushIfNeeded(eventTime)) {
@@ -244,6 +226,7 @@
int numBucketsForward = (eventTime - mCurrentBucketStartTimeNs) / mBucketSizeNs;
mCurrentBucketStartTimeNs += numBucketsForward * mBucketSizeNs;
+ mCurrentBucketNum += numBucketsForward;
}
void DurationMetricProducer::onMatchedLogEventInternal(
@@ -262,7 +245,7 @@
HashableDimensionKey atomKey = getHashableKey(getDimensionKey(event, mInternalDimension));
if (mCurrentSlicedDuration.find(eventKey) == mCurrentSlicedDuration.end()) {
- mCurrentSlicedDuration[eventKey] = createDurationTracker(mPastBuckets[eventKey]);
+ mCurrentSlicedDuration[eventKey] = createDurationTracker(eventKey, mPastBuckets[eventKey]);
}
auto it = mCurrentSlicedDuration.find(eventKey);
diff --git a/cmds/statsd/src/metrics/DurationMetricProducer.h b/cmds/statsd/src/metrics/DurationMetricProducer.h
index bb5d4d9..4c8dbcb 100644
--- a/cmds/statsd/src/metrics/DurationMetricProducer.h
+++ b/cmds/statsd/src/metrics/DurationMetricProducer.h
@@ -14,8 +14,8 @@
* limitations under the License.
*/
-#ifndef DURATION_METRIC_PRODUCER_H
-#define DURATION_METRIC_PRODUCER_H
+#pragma once
+
#include <unordered_map>
@@ -48,6 +48,7 @@
void onConditionChanged(const bool conditionMet, const uint64_t eventTime) override;
void finish() override;
+ void flushIfNeeded(const uint64_t newEventTime) override;
// TODO: Pass a timestamp as a parameter in onDumpReport.
std::unique_ptr<std::vector<uint8_t>> onDumpReport() override;
@@ -95,11 +96,8 @@
std::unordered_map<HashableDimensionKey, std::unique_ptr<DurationTracker>>
mCurrentSlicedDuration;
- void flushDurationIfNeeded(const uint64_t newEventTime);
-
- std::unique_ptr<DurationTracker> createDurationTracker(std::vector<DurationBucket>& bucket);
-
- void flushIfNeeded(uint64_t timestamp);
+ std::unique_ptr<DurationTracker> createDurationTracker(const HashableDimensionKey& eventKey,
+ std::vector<DurationBucket>& bucket);
static const size_t kBucketSize = sizeof(DurationBucket{});
};
@@ -108,4 +106,3 @@
} // namespace os
} // namespace android
-#endif // DURATION_METRIC_PRODUCER_H
diff --git a/cmds/statsd/src/metrics/EventMetricProducer.h b/cmds/statsd/src/metrics/EventMetricProducer.h
index 0dccdf4..0c453cd 100644
--- a/cmds/statsd/src/metrics/EventMetricProducer.h
+++ b/cmds/statsd/src/metrics/EventMetricProducer.h
@@ -47,6 +47,8 @@
void onConditionChanged(const bool conditionMet, const uint64_t eventTime) override;
void finish() override;
+ void flushIfNeeded(const uint64_t newEventTime) override {
+ }
// TODO: Pass a timestamp as a parameter in onDumpReport.
std::unique_ptr<std::vector<uint8_t>> onDumpReport() override;
diff --git a/cmds/statsd/src/metrics/GaugeMetricProducer.cpp b/cmds/statsd/src/metrics/GaugeMetricProducer.cpp
index ed18f89..42ac1a2 100644
--- a/cmds/statsd/src/metrics/GaugeMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/GaugeMetricProducer.cpp
@@ -63,8 +63,9 @@
const int FIELD_ID_GAUGE = 3;
GaugeMetricProducer::GaugeMetricProducer(const GaugeMetric& metric, const int conditionIndex,
- const sp<ConditionWizard>& wizard, const int pullTagId)
- : MetricProducer((time(nullptr) * NS_PER_SEC), conditionIndex, wizard),
+ const sp<ConditionWizard>& wizard, const int pullTagId,
+ const int64_t startTimeNs)
+ : MetricProducer(startTimeNs, conditionIndex, wizard),
mMetric(metric),
mPullTagId(pullTagId) {
if (metric.has_bucket() && metric.bucket().has_bucket_size_millis()) {
@@ -114,7 +115,7 @@
// Dump current bucket if it's stale.
// If current bucket is still on-going, don't force dump current bucket.
// In finish(), We can force dump current bucket.
- flushGaugeIfNeededLocked(time(nullptr) * NS_PER_SEC);
+ flushIfNeeded(time(nullptr) * NS_PER_SEC);
for (const auto& pair : mPastBuckets) {
const HashableDimensionKey& hashableKey = pair.first;
@@ -168,7 +169,6 @@
startNewProtoOutputStream(time(nullptr) * NS_PER_SEC);
mPastBuckets.clear();
- mByteSize = 0;
return buffer;
@@ -178,15 +178,18 @@
void GaugeMetricProducer::onConditionChanged(const bool conditionMet, const uint64_t eventTime) {
AutoMutex _l(mLock);
VLOG("Metric %s onConditionChanged", mMetric.name().c_str());
+ flushIfNeeded(eventTime);
mCondition = conditionMet;
- // Push mode. Nothing to do.
+ // Push mode. No need to proactively pull the gauge data.
if (mPullTagId == -1) {
return;
}
- // If (1) the condition is not met or (2) we already pulled the gauge metric in the current
- // bucket, do not pull gauge again.
- if (!mCondition || mCurrentSlicedBucket.size() > 0) {
+ if (!mCondition) {
+ return;
+ }
+ // Already have gauge metric for the current bucket, do not do it again.
+ if (mCurrentSlicedBucket->size() > 0) {
return;
}
vector<std::shared_ptr<LogEvent>> allData;
@@ -197,16 +200,16 @@
for (const auto& data : allData) {
onMatchedLogEvent(0, *data, false /*scheduledPull*/);
}
- flushGaugeIfNeededLocked(eventTime);
+ flushIfNeeded(eventTime);
}
void GaugeMetricProducer::onSlicedConditionMayChange(const uint64_t eventTime) {
VLOG("Metric %s onSlicedConditionMayChange", mMetric.name().c_str());
}
-long GaugeMetricProducer::getGauge(const LogEvent& event) {
+int64_t GaugeMetricProducer::getGauge(const LogEvent& event) {
status_t err = NO_ERROR;
- long val = event.GetLong(mMetric.gauge_field(), &err);
+ int64_t val = event.GetLong(mMetric.gauge_field(), &err);
if (err == NO_ERROR) {
return val;
} else {
@@ -217,14 +220,9 @@
void GaugeMetricProducer::onDataPulled(const std::vector<std::shared_ptr<LogEvent>>& allData) {
AutoMutex mutex(mLock);
- if (allData.size() == 0) {
- return;
- }
for (const auto& data : allData) {
onMatchedLogEvent(0, *data, true /*scheduledPull*/);
}
- uint64_t eventTime = allData.at(0)->GetTimestampNs();
- flushGaugeIfNeededLocked(eventTime);
}
void GaugeMetricProducer::onMatchedLogEventInternal(
@@ -241,15 +239,21 @@
return;
}
- // For gauge metric, we just simply use the latest guage in the given bucket.
- const long gauge = getGauge(event);
- if (gauge < 0) {
- VLOG("Invalid gauge at event Time: %lld", (long long)eventTimeNs);
+ // When the event happens in a new bucket, flush the old buckets.
+ if (eventTimeNs >= mCurrentBucketStartTimeNs + mBucketSizeNs) {
+ flushIfNeeded(eventTimeNs);
+ }
+
+ // For gauge metric, we just simply use the first guage in the given bucket.
+ if (!mCurrentSlicedBucket->empty()) {
return;
}
- mCurrentSlicedBucket[eventKey] = gauge;
- if (mPullTagId < 0) {
- flushGaugeIfNeededLocked(eventTimeNs);
+ const long gauge = getGauge(event);
+ if (gauge >= 0) {
+ (*mCurrentSlicedBucket)[eventKey] = gauge;
+ }
+ for (auto& tracker : mAnomalyTrackers) {
+ tracker->detectAndDeclareAnomaly(eventTimeNs, mCurrentBucketNum, eventKey, gauge);
}
}
@@ -258,39 +262,45 @@
// bucket.
// if data is pushed, onMatchedLogEvent will only be called through onConditionChanged() inside
// the GaugeMetricProducer while holding the lock.
-void GaugeMetricProducer::flushGaugeIfNeededLocked(const uint64_t eventTimeNs) {
- if (mCurrentBucketStartTimeNs + mBucketSizeNs > eventTimeNs) {
- VLOG("event time is %lld, less than next bucket start time %lld", (long long)eventTimeNs,
- (long long)(mCurrentBucketStartTimeNs + mBucketSizeNs));
+void GaugeMetricProducer::flushIfNeeded(const uint64_t eventTimeNs) {
+ if (eventTimeNs < mCurrentBucketStartTimeNs + mBucketSizeNs) {
return;
}
- // Adjusts the bucket start time
- int64_t numBucketsForward = (eventTimeNs - mCurrentBucketStartTimeNs) / mBucketSizeNs;
-
GaugeBucket info;
info.mBucketStartNs = mCurrentBucketStartTimeNs;
info.mBucketEndNs = mCurrentBucketStartTimeNs + mBucketSizeNs;
+ info.mBucketNum = mCurrentBucketNum;
- for (const auto& slice : mCurrentSlicedBucket) {
+ for (const auto& slice : *mCurrentSlicedBucket) {
info.mGauge = slice.second;
auto& bucketList = mPastBuckets[slice.first];
bucketList.push_back(info);
- mByteSize += sizeof(info);
-
- VLOG("gauge metric %s, dump key value: %s -> %ld", mMetric.name().c_str(),
- slice.first.c_str(), slice.second);
+ VLOG("gauge metric %s, dump key value: %s -> %lld", mMetric.name().c_str(),
+ slice.first.c_str(), (long long)slice.second);
}
- // Reset counters
- mCurrentSlicedBucket.clear();
+ // Reset counters
+ for (auto& tracker : mAnomalyTrackers) {
+ tracker->addPastBucket(mCurrentSlicedBucket, mCurrentBucketNum);
+ }
+
+ mCurrentSlicedBucket = std::make_shared<DimToValMap>();
+
+ // Adjusts the bucket start time
+ int64_t numBucketsForward = (eventTimeNs - mCurrentBucketStartTimeNs) / mBucketSizeNs;
mCurrentBucketStartTimeNs = mCurrentBucketStartTimeNs + numBucketsForward * mBucketSizeNs;
+ mCurrentBucketNum += numBucketsForward;
VLOG("metric %s: new bucket start time: %lld", mMetric.name().c_str(),
(long long)mCurrentBucketStartTimeNs);
}
size_t GaugeMetricProducer::byteSize() {
- return mByteSize;
+ size_t totalSize = 0;
+ for (const auto& pair : mPastBuckets) {
+ totalSize += pair.second.size() * kBucketSize;
+ }
+ return totalSize;
}
} // namespace statsd
diff --git a/cmds/statsd/src/metrics/GaugeMetricProducer.h b/cmds/statsd/src/metrics/GaugeMetricProducer.h
index f9e4deb..930afb2 100644
--- a/cmds/statsd/src/metrics/GaugeMetricProducer.h
+++ b/cmds/statsd/src/metrics/GaugeMetricProducer.h
@@ -19,6 +19,7 @@
#include <unordered_map>
#include <android/util/ProtoOutputStream.h>
+#include <gtest/gtest_prod.h>
#include "../condition/ConditionTracker.h"
#include "../external/PullDataReceiver.h"
#include "../external/StatsPullerManager.h"
@@ -35,6 +36,7 @@
int64_t mBucketStartNs;
int64_t mBucketEndNs;
int64_t mGauge;
+ uint64_t mBucketNum;
};
// This gauge metric producer first register the puller to automatically pull the gauge at the
@@ -46,7 +48,8 @@
// TODO: Pass in the start time from MetricsManager, it should be consistent
// for all metrics.
GaugeMetricProducer(const GaugeMetric& countMetric, const int conditionIndex,
- const sp<ConditionWizard>& wizard, const int pullTagId);
+ const sp<ConditionWizard>& wizard, const int pullTagId,
+ const int64_t startTimeNs);
virtual ~GaugeMetricProducer();
@@ -57,6 +60,7 @@
void onSlicedConditionMayChange(const uint64_t eventTime) override;
void finish() override;
+ void flushIfNeeded(const uint64_t newEventTime) override;
// TODO: Pass a timestamp as a parameter in onDumpReport.
std::unique_ptr<std::vector<uint8_t>> onDumpReport() override;
@@ -92,13 +96,15 @@
std::unordered_map<HashableDimensionKey, std::vector<GaugeBucket>> mPastBuckets;
// The current bucket.
- std::unordered_map<HashableDimensionKey, long> mCurrentSlicedBucket;
+ std::shared_ptr<DimToValMap> mCurrentSlicedBucket = std::make_shared<DimToValMap>();
- void flushGaugeIfNeededLocked(const uint64_t newEventTime);
+ int64_t getGauge(const LogEvent& event);
- long getGauge(const LogEvent& event);
+ static const size_t kBucketSize = sizeof(GaugeBucket{});
- size_t mByteSize;
+ FRIEND_TEST(GaugeMetricProducerTest, TestWithCondition);
+ FRIEND_TEST(GaugeMetricProducerTest, TestNoCondition);
+ FRIEND_TEST(GaugeMetricProducerTest, TestAnomalyDetection);
};
} // namespace statsd
diff --git a/cmds/statsd/src/metrics/MetricProducer.h b/cmds/statsd/src/metrics/MetricProducer.h
index c7982a8..0f93744 100644
--- a/cmds/statsd/src/metrics/MetricProducer.h
+++ b/cmds/statsd/src/metrics/MetricProducer.h
@@ -17,6 +17,7 @@
#ifndef METRIC_PRODUCER_H
#define METRIC_PRODUCER_H
+#include "anomaly/AnomalyTracker.h"
#include "condition/ConditionWizard.h"
#include "matchers/matcher_util.h"
#include "packages/PackageInfoListener.h"
@@ -58,6 +59,7 @@
// This is called when the metric collecting is done, e.g., when there is a new configuration
// coming. MetricProducer should do the clean up, and dump existing data to dropbox.
virtual void finish() = 0;
+ virtual void flushIfNeeded(const uint64_t newEventTime) = 0;
// TODO: Pass a timestamp as a parameter in onDumpReport and update all its
// implementations.
@@ -72,6 +74,14 @@
// state.
virtual size_t byteSize() = 0;
+ void addAnomalyTracker(sp<AnomalyTracker> tracker) {
+ mAnomalyTrackers.push_back(tracker);
+ }
+
+ int64_t getBuckeSizeInNs() const {
+ return mBucketSizeNs;
+ }
+
protected:
const uint64_t mStartTimeNs;
@@ -97,6 +107,8 @@
std::vector<EventConditionLink> mConditionLinks;
+ std::vector<sp<AnomalyTracker>> mAnomalyTrackers;
+
/*
* Individual metrics can implement their own business logic here. All pre-processing is done.
*
diff --git a/cmds/statsd/src/metrics/MetricsManager.cpp b/cmds/statsd/src/metrics/MetricsManager.cpp
index e8a862f..5916b040 100644
--- a/cmds/statsd/src/metrics/MetricsManager.cpp
+++ b/cmds/statsd/src/metrics/MetricsManager.cpp
@@ -38,8 +38,8 @@
MetricsManager::MetricsManager(const StatsdConfig& config) {
mConfigValid = initStatsdConfig(config, mTagIds, mAllLogEntryMatchers, mAllConditionTrackers,
- mAllMetricProducers, mConditionToMetricMap, mTrackerToMetricMap,
- mTrackerToConditionMap);
+ mAllMetricProducers, mAllAnomalyTrackers, mConditionToMetricMap,
+ mTrackerToMetricMap, mTrackerToConditionMap);
}
MetricsManager::~MetricsManager() {
@@ -150,6 +150,19 @@
}
}
+void MetricsManager::onAnomalyAlarmFired(const uint64_t timestampNs,
+ sp<const AnomalyAlarm> anomaly) {
+ for (const auto& itr : mAllAnomalyTrackers) {
+ itr->declareAnomaly(timestampNs);
+ }
+}
+
+void MetricsManager::setAnomalyMonitor(const sp<AnomalyMonitor>& anomalyMonitor) {
+ for (auto& itr : mAllAnomalyTrackers) {
+ itr->setAnomalyMonitor(anomalyMonitor);
+ }
+}
+
// Returns the total byte size of all metrics managed by a single config source.
size_t MetricsManager::byteSize() {
size_t totalSize = 0;
diff --git a/cmds/statsd/src/metrics/MetricsManager.h b/cmds/statsd/src/metrics/MetricsManager.h
index fb16779..59ade7c 100644
--- a/cmds/statsd/src/metrics/MetricsManager.h
+++ b/cmds/statsd/src/metrics/MetricsManager.h
@@ -16,6 +16,8 @@
#pragma once
+#include "anomaly/AnomalyMonitor.h"
+#include "anomaly/AnomalyTracker.h"
#include "condition/ConditionTracker.h"
#include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"
#include "logd/LogEvent.h"
@@ -43,6 +45,10 @@
// Called when everything should wrap up. We are about to finish (e.g., new config comes).
void finish();
+ void onAnomalyAlarmFired(const uint64_t timestampNs, sp<const AnomalyAlarm> anomaly);
+
+ void setAnomalyMonitor(const sp<AnomalyMonitor>& anomalyMonitor);
+
// Config source owner can call onDumpReport() to get all the metrics collected.
std::vector<std::unique_ptr<std::vector<uint8_t>>> onDumpReport();
@@ -70,6 +76,9 @@
// Hold all metrics from the config.
std::vector<sp<MetricProducer>> mAllMetricProducers;
+ // Hold all alert trackers.
+ std::vector<sp<AnomalyTracker>> mAllAnomalyTrackers;
+
// To make the log processing more efficient, we want to do as much filtering as possible
// before we go into individual trackers and conditions to match.
diff --git a/cmds/statsd/src/metrics/ValueMetricProducer.cpp b/cmds/statsd/src/metrics/ValueMetricProducer.cpp
index a35070b..9cbe6f6 100644
--- a/cmds/statsd/src/metrics/ValueMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/ValueMetricProducer.cpp
@@ -188,7 +188,6 @@
startNewProtoOutputStream(time(nullptr) * NS_PER_SEC);
mPastBuckets.clear();
- mByteSize = 0;
return buffer;
@@ -215,7 +214,7 @@
for (const auto& data : allData) {
onMatchedLogEvent(0, *data, false);
}
- flush_if_needed(eventTime);
+ flushIfNeeded(eventTime);
}
return;
}
@@ -230,12 +229,12 @@
uint64_t eventTime = allData.at(0)->GetTimestampNs();
// alarm is not accurate and might drift.
if (eventTime > mCurrentBucketStartTimeNs + mBucketSizeNs * 3 / 2) {
- flush_if_needed(eventTime);
+ flushIfNeeded(eventTime);
}
for (const auto& data : allData) {
onMatchedLogEvent(0, *data, true);
}
- flush_if_needed(eventTime);
+ flushIfNeeded(eventTime);
}
}
@@ -282,7 +281,7 @@
}
}
} else {
- flush_if_needed(eventTimeNs);
+ flushIfNeeded(eventTimeNs);
interval.raw.push_back(make_pair(value, 0));
}
}
@@ -298,18 +297,18 @@
}
}
-void ValueMetricProducer::flush_if_needed(const uint64_t eventTimeNs) {
+void ValueMetricProducer::flushIfNeeded(const uint64_t eventTimeNs) {
if (mCurrentBucketStartTimeNs + mBucketSizeNs > eventTimeNs) {
VLOG("eventTime is %lld, less than next bucket start time %lld", (long long)eventTimeNs,
(long long)(mCurrentBucketStartTimeNs + mBucketSizeNs));
return;
}
-
VLOG("finalizing bucket for %ld, dumping %d slices", (long)mCurrentBucketStartTimeNs,
(int)mCurrentSlicedBucket.size());
ValueBucket info;
info.mBucketStartNs = mCurrentBucketStartTimeNs;
info.mBucketEndNs = mCurrentBucketStartTimeNs + mBucketSizeNs;
+ info.mBucketNum = mCurrentBucketNum;
int tainted = 0;
for (const auto& slice : mCurrentSlicedBucket) {
@@ -329,23 +328,29 @@
// it will auto create new vector of ValuebucketInfo if the key is not found.
auto& bucketList = mPastBuckets[slice.first];
bucketList.push_back(info);
- mByteSize += sizeof(info);
}
// Reset counters
mCurrentSlicedBucket.swap(mNextSlicedBucket);
mNextSlicedBucket.clear();
+
int64_t numBucketsForward = (eventTimeNs - mCurrentBucketStartTimeNs) / mBucketSizeNs;
+ mCurrentBucketStartTimeNs = mCurrentBucketStartTimeNs + numBucketsForward * mBucketSizeNs;
+ mCurrentBucketNum += numBucketsForward;
+
if (numBucketsForward > 1) {
VLOG("Skipping forward %lld buckets", (long long)numBucketsForward);
}
- mCurrentBucketStartTimeNs = mCurrentBucketStartTimeNs + numBucketsForward * mBucketSizeNs;
VLOG("metric %s: new bucket start time: %lld", mMetric.name().c_str(),
(long long)mCurrentBucketStartTimeNs);
}
size_t ValueMetricProducer::byteSize() {
- return mByteSize;
+ size_t totalSize = 0;
+ for (const auto& pair : mPastBuckets) {
+ totalSize += pair.second.size() * kBucketSize;
+ }
+ return totalSize;
}
} // namespace statsd
diff --git a/cmds/statsd/src/metrics/ValueMetricProducer.h b/cmds/statsd/src/metrics/ValueMetricProducer.h
index c6c87f5..2b0b0ad 100644
--- a/cmds/statsd/src/metrics/ValueMetricProducer.h
+++ b/cmds/statsd/src/metrics/ValueMetricProducer.h
@@ -33,6 +33,7 @@
int64_t mBucketStartNs;
int64_t mBucketEndNs;
int64_t mValue;
+ uint64_t mBucketNum;
};
class ValueMetricProducer : public virtual MetricProducer, public virtual PullDataReceiver {
@@ -46,6 +47,7 @@
void onConditionChanged(const bool condition, const uint64_t eventTime) override;
void finish() override;
+ void flushIfNeeded(const uint64_t eventTimeNs) override;
// TODO: Pass a timestamp as a parameter in onDumpReport.
std::unique_ptr<std::vector<uint8_t>> onDumpReport() override;
@@ -102,9 +104,7 @@
long get_value(const LogEvent& event);
- void flush_if_needed(const uint64_t eventTimeNs);
-
- size_t mByteSize;
+ static const size_t kBucketSize = sizeof(ValueBucket{});
FRIEND_TEST(ValueMetricProducerTest, TestNonDimensionalEvents);
FRIEND_TEST(ValueMetricProducerTest, TestEventsWithNonSlicedCondition);
diff --git a/cmds/statsd/src/metrics/duration_helper/DurationTracker.h b/cmds/statsd/src/metrics/duration_helper/DurationTracker.h
index 18b3349..c91ea0f 100644
--- a/cmds/statsd/src/metrics/duration_helper/DurationTracker.h
+++ b/cmds/statsd/src/metrics/duration_helper/DurationTracker.h
@@ -17,6 +17,7 @@
#ifndef DURATION_TRACKER_H
#define DURATION_TRACKER_H
+#include "anomaly/AnomalyTracker.h"
#include "condition/ConditionWizard.h"
#include "stats_util.h"
@@ -50,23 +51,28 @@
};
struct DurationBucket {
- int64_t mBucketStartNs;
- int64_t mBucketEndNs;
- int64_t mDuration;
+ uint64_t mBucketStartNs;
+ uint64_t mBucketEndNs;
+ uint64_t mDuration;
+ uint64_t mBucketNum;
};
class DurationTracker {
public:
- DurationTracker(sp<ConditionWizard> wizard, int conditionIndex, bool nesting,
- uint64_t currentBucketStartNs, uint64_t bucketSizeNs,
+ DurationTracker(const HashableDimensionKey& eventKey, sp<ConditionWizard> wizard,
+ int conditionIndex, bool nesting, uint64_t currentBucketStartNs,
+ uint64_t bucketSizeNs, const std::vector<sp<AnomalyTracker>>& anomalyTrackers,
std::vector<DurationBucket>& bucket)
- : mWizard(wizard),
+ : mEventKey(eventKey),
+ mWizard(wizard),
mConditionTrackerIndex(conditionIndex),
mBucketSizeNs(bucketSizeNs),
mNested(nesting),
mCurrentBucketStartTimeNs(currentBucketStartNs),
mBucket(bucket),
- mDuration(0){};
+ mDuration(0),
+ mCurrentBucketNum(0),
+ mAnomalyTrackers(anomalyTrackers){};
virtual ~DurationTracker(){};
virtual void noteStart(const HashableDimensionKey& key, bool condition,
const uint64_t eventTime, const ConditionKey& conditionKey) = 0;
@@ -79,7 +85,58 @@
// events, so that the owner can safely remove the tracker.
virtual bool flushIfNeeded(uint64_t timestampNs) = 0;
+ // Predict the anomaly timestamp given the current status.
+ virtual int64_t predictAnomalyTimestampNs(const AnomalyTracker& anomalyTracker,
+ const uint64_t currentTimestamp) const = 0;
+
protected:
+ // Starts the anomaly alarm.
+ void startAnomalyAlarm(const uint64_t eventTime) {
+ for (auto& anomalyTracker : mAnomalyTrackers) {
+ if (anomalyTracker != nullptr) {
+ anomalyTracker->startAlarm(mEventKey,
+ predictAnomalyTimestampNs(*anomalyTracker, eventTime));
+ }
+ }
+ }
+
+ // Stops the anomaly alarm.
+ void stopAnomalyAlarm() {
+ for (auto& anomalyTracker : mAnomalyTrackers) {
+ if (anomalyTracker != nullptr) {
+ anomalyTracker->stopAlarm(mEventKey);
+ }
+ }
+ }
+
+ void addPastBucketToAnomalyTrackers(const int64_t& bucketValue, const int64_t& bucketNum) {
+ for (auto& anomalyTracker : mAnomalyTrackers) {
+ if (anomalyTracker != nullptr) {
+ anomalyTracker->addPastBucket(mEventKey, bucketValue, bucketNum);
+ }
+ }
+ }
+
+ void detectAndDeclareAnomaly(const uint64_t& timestamp, const int64_t& currBucketNum,
+ const int64_t& currentBucketValue) {
+ for (auto& anomalyTracker : mAnomalyTrackers) {
+ if (anomalyTracker != nullptr) {
+ anomalyTracker->detectAndDeclareAnomaly(timestamp, currBucketNum, mEventKey,
+ currentBucketValue);
+ }
+ }
+ }
+
+ void declareAnomalyIfAlarmExpired(const uint64_t& timestamp) {
+ for (auto& anomalyTracker : mAnomalyTrackers) {
+ if (anomalyTracker != nullptr) {
+ anomalyTracker->declareAnomalyIfAlarmExpired(mEventKey, timestamp);
+ }
+ }
+ }
+
+ HashableDimensionKey mEventKey;
+
sp<ConditionWizard> mWizard;
const int mConditionTrackerIndex;
@@ -93,6 +150,13 @@
std::vector<DurationBucket>& mBucket; // where to write output
int64_t mDuration; // current recorded duration result
+
+ uint64_t mCurrentBucketNum;
+
+ std::vector<sp<AnomalyTracker>> mAnomalyTrackers;
+
+ FRIEND_TEST(OringDurationTrackerTest, TestPredictAnomalyTimestamp);
+ FRIEND_TEST(OringDurationTrackerTest, TestAnomalyDetection);
};
} // namespace statsd
diff --git a/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.cpp b/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.cpp
index 8c7bfb6..06e743d 100644
--- a/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.cpp
+++ b/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.cpp
@@ -23,14 +23,18 @@
namespace os {
namespace statsd {
-MaxDurationTracker::MaxDurationTracker(sp<ConditionWizard> wizard, int conditionIndex, bool nesting,
+MaxDurationTracker::MaxDurationTracker(const HashableDimensionKey& eventKey,
+ sp<ConditionWizard> wizard, int conditionIndex, bool nesting,
uint64_t currentBucketStartNs, uint64_t bucketSizeNs,
+ const std::vector<sp<AnomalyTracker>>& anomalyTrackers,
std::vector<DurationBucket>& bucket)
- : DurationTracker(wizard, conditionIndex, nesting, currentBucketStartNs, bucketSizeNs, bucket) {
+ : DurationTracker(eventKey, wizard, conditionIndex, nesting, currentBucketStartNs, bucketSizeNs,
+ anomalyTrackers, bucket) {
}
void MaxDurationTracker::noteStart(const HashableDimensionKey& key, bool condition,
const uint64_t eventTime, const ConditionKey& conditionKey) {
+ flushIfNeeded(eventTime);
// this will construct a new DurationInfo if this key didn't exist.
DurationInfo& duration = mInfos[key];
duration.conditionKeys = conditionKey;
@@ -56,8 +60,11 @@
}
}
+
void MaxDurationTracker::noteStop(const HashableDimensionKey& key, const uint64_t eventTime,
bool forceStop) {
+ flushIfNeeded(eventTime);
+ declareAnomalyIfAlarmExpired(eventTime);
VLOG("MaxDuration: key %s stop", key.c_str());
if (mInfos.find(key) == mInfos.end()) {
// we didn't see a start event before. do nothing.
@@ -78,6 +85,7 @@
(long long)duration.lastStartTime, (long long)eventTime,
(long long)durationTime);
duration.lastDuration = duration.lastDuration + durationTime;
+ duration.lastStartTime = -1;
VLOG(" record duration: %lld ", (long long)duration.lastDuration);
}
break;
@@ -93,6 +101,7 @@
if (duration.lastDuration > mDuration) {
mDuration = duration.lastDuration;
+ detectAndDeclareAnomaly(eventTime, mCurrentBucketNum, mDuration);
VLOG("Max: new max duration: %lld", (long long)mDuration);
}
// Once an atom duration ends, we erase it. Next time, if we see another atom event with the
@@ -101,9 +110,14 @@
mInfos.erase(key);
}
}
+
void MaxDurationTracker::noteStopAll(const uint64_t eventTime) {
- for (auto& pair : mInfos) {
- noteStop(pair.first, eventTime, true);
+ std::set<HashableDimensionKey> keys;
+ for (const auto& pair : mInfos) {
+ keys.insert(pair.first);
+ }
+ for (auto& key : keys) {
+ noteStop(key, eventTime, true);
}
}
@@ -122,7 +136,7 @@
DurationBucket info;
info.mBucketStartNs = mCurrentBucketStartTimeNs;
info.mBucketEndNs = endTime;
-
+ info.mBucketNum = mCurrentBucketNum;
uint64_t oldBucketStartTimeNs = mCurrentBucketStartTimeNs;
mCurrentBucketStartTimeNs += (numBucketsForward)*mBucketSizeNs;
@@ -165,6 +179,7 @@
if (mDuration != 0) {
info.mDuration = mDuration;
mBucket.push_back(info);
+ addPastBucketToAnomalyTrackers(info.mDuration, info.mBucketNum);
VLOG(" final duration for last bucket: %lld", (long long)mDuration);
}
@@ -174,11 +189,15 @@
DurationBucket info;
info.mBucketStartNs = oldBucketStartTimeNs + mBucketSizeNs * i;
info.mBucketEndNs = endTime + mBucketSizeNs * i;
+ info.mBucketNum = mCurrentBucketNum + i;
info.mDuration = mBucketSizeNs;
mBucket.push_back(info);
+ addPastBucketToAnomalyTrackers(info.mDuration, info.mBucketNum);
VLOG(" filling gap bucket with duration %lld", (long long)mBucketSizeNs);
}
}
+
+ mCurrentBucketNum += numBucketsForward;
// If this tracker has no pending events, tell owner to remove.
return !hasPendingEvent;
}
@@ -204,6 +223,8 @@
void MaxDurationTracker::noteConditionChanged(const HashableDimensionKey& key, bool conditionMet,
const uint64_t timestamp) {
+ flushIfNeeded(timestamp);
+ declareAnomalyIfAlarmExpired(timestamp);
auto it = mInfos.find(key);
if (it == mInfos.end()) {
return;
@@ -215,7 +236,6 @@
if (!conditionMet) {
it->second.state = DurationState::kPaused;
it->second.lastDuration += (timestamp - it->second.lastStartTime);
-
VLOG("MaxDurationTracker Key: %s Started->Paused ", key.c_str());
}
break;
@@ -232,6 +252,16 @@
}
break;
}
+ if (it->second.lastDuration > mDuration) {
+ mDuration = it->second.lastDuration;
+ detectAndDeclareAnomaly(timestamp, mCurrentBucketNum, mDuration);
+ }
+}
+
+int64_t MaxDurationTracker::predictAnomalyTimestampNs(const AnomalyTracker& anomalyTracker,
+ const uint64_t currentTimestamp) const {
+ ALOGE("Max duration producer does not support anomaly timestamp prediction!!!");
+ return currentTimestamp;
}
} // namespace statsd
diff --git a/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.h b/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.h
index 167f81e..ca10210 100644
--- a/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.h
+++ b/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.h
@@ -28,8 +28,10 @@
// they stop or bucket expires.
class MaxDurationTracker : public DurationTracker {
public:
- MaxDurationTracker(sp<ConditionWizard> wizard, int conditionIndex, bool nesting,
- uint64_t currentBucketStartNs, uint64_t bucketSizeNs,
+ MaxDurationTracker(const HashableDimensionKey& eventKey, sp<ConditionWizard> wizard,
+ int conditionIndex, bool nesting, uint64_t currentBucketStartNs,
+ uint64_t bucketSizeNs,
+ const std::vector<sp<AnomalyTracker>>& anomalyTrackers,
std::vector<DurationBucket>& bucket);
void noteStart(const HashableDimensionKey& key, bool condition, const uint64_t eventTime,
const ConditionKey& conditionKey) override;
@@ -40,11 +42,20 @@
void onSlicedConditionMayChange(const uint64_t timestamp) override;
void onConditionChanged(bool condition, const uint64_t timestamp) override;
+ int64_t predictAnomalyTimestampNs(const AnomalyTracker& anomalyTracker,
+ const uint64_t currentTimestamp) const override;
+
private:
std::map<HashableDimensionKey, DurationInfo> mInfos;
void noteConditionChanged(const HashableDimensionKey& key, bool conditionMet,
const uint64_t timestamp);
+
+ FRIEND_TEST(MaxDurationTrackerTest, TestSimpleMaxDuration);
+ FRIEND_TEST(MaxDurationTrackerTest, TestCrossBucketBoundary);
+ FRIEND_TEST(MaxDurationTrackerTest, TestMaxDurationWithCondition);
+ FRIEND_TEST(MaxDurationTrackerTest, TestStopAll);
+ FRIEND_TEST(MaxDurationTrackerTest, TestAnomalyDetection);
};
} // namespace statsd
diff --git a/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.cpp b/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.cpp
index faf5ce5..29b6c89 100644
--- a/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.cpp
+++ b/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.cpp
@@ -23,11 +23,14 @@
using std::pair;
-OringDurationTracker::OringDurationTracker(sp<ConditionWizard> wizard, int conditionIndex,
+OringDurationTracker::OringDurationTracker(const HashableDimensionKey& eventKey,
+ sp<ConditionWizard> wizard, int conditionIndex,
bool nesting, uint64_t currentBucketStartNs,
uint64_t bucketSizeNs,
+ const std::vector<sp<AnomalyTracker>>& anomalyTrackers,
std::vector<DurationBucket>& bucket)
- : DurationTracker(wizard, conditionIndex, nesting, currentBucketStartNs, bucketSizeNs, bucket),
+ : DurationTracker(eventKey, wizard, conditionIndex, nesting, currentBucketStartNs, bucketSizeNs,
+ anomalyTrackers, bucket),
mStarted(),
mPaused() {
mLastStartTime = 0;
@@ -35,10 +38,12 @@
void OringDurationTracker::noteStart(const HashableDimensionKey& key, bool condition,
const uint64_t eventTime, const ConditionKey& conditionKey) {
+ flushIfNeeded(eventTime);
if (condition) {
if (mStarted.size() == 0) {
mLastStartTime = eventTime;
VLOG("record first start....");
+ startAnomalyAlarm(eventTime);
}
mStarted[key]++;
} else {
@@ -54,6 +59,8 @@
void OringDurationTracker::noteStop(const HashableDimensionKey& key, const uint64_t timestamp,
const bool stopAll) {
+ flushIfNeeded(timestamp);
+ declareAnomalyIfAlarmExpired(timestamp);
VLOG("Oring: %s stop", key.c_str());
auto it = mStarted.find(key);
if (it != mStarted.end()) {
@@ -64,6 +71,8 @@
}
if (mStarted.empty()) {
mDuration += (timestamp - mLastStartTime);
+ detectAndDeclareAnomaly(timestamp, mCurrentBucketNum, mDuration);
+ mLastStartTime = -1;
VLOG("record duration %lld, total %lld ", (long long)timestamp - mLastStartTime,
(long long)mDuration);
}
@@ -76,56 +85,70 @@
mPaused.erase(pausedIt);
mConditionKeyMap.erase(key);
}
- }
+ }
+ if (mStarted.empty()) {
+ stopAnomalyAlarm();
+ }
}
+
void OringDurationTracker::noteStopAll(const uint64_t timestamp) {
+ flushIfNeeded(timestamp);
+ declareAnomalyIfAlarmExpired(timestamp);
if (!mStarted.empty()) {
mDuration += (timestamp - mLastStartTime);
VLOG("Oring Stop all: record duration %lld %lld ", (long long)timestamp - mLastStartTime,
(long long)mDuration);
+ detectAndDeclareAnomaly(timestamp, mCurrentBucketNum, mDuration);
}
+ stopAnomalyAlarm();
mStarted.clear();
mPaused.clear();
mConditionKeyMap.clear();
+ mLastStartTime = -1;
}
bool OringDurationTracker::flushIfNeeded(uint64_t eventTime) {
- if (mCurrentBucketStartTimeNs + mBucketSizeNs > eventTime) {
+ if (eventTime < mCurrentBucketStartTimeNs + mBucketSizeNs) {
return false;
}
VLOG("OringDurationTracker Flushing.............");
// adjust the bucket start time
int numBucketsForward = (eventTime - mCurrentBucketStartTimeNs) / mBucketSizeNs;
- DurationBucket info;
- uint64_t endTime = mCurrentBucketStartTimeNs + mBucketSizeNs;
- info.mBucketStartNs = mCurrentBucketStartTimeNs;
- info.mBucketEndNs = endTime;
-
- uint64_t oldBucketStartTimeNs = mCurrentBucketStartTimeNs;
- mCurrentBucketStartTimeNs += (numBucketsForward)*mBucketSizeNs;
-
+ DurationBucket current_info;
+ current_info.mBucketStartNs = mCurrentBucketStartTimeNs;
+ current_info.mBucketEndNs = current_info.mBucketStartNs + mBucketSizeNs;
+ current_info.mBucketNum = mCurrentBucketNum;
+ // Process the current bucket.
if (mStarted.size() > 0) {
- mDuration += (endTime - mLastStartTime);
+ mDuration += (current_info.mBucketEndNs - mLastStartTime);
+ mLastStartTime = current_info.mBucketEndNs;
}
- if (mDuration != 0) {
- info.mDuration = mDuration;
- // it will auto create new vector of CountbucketInfo if the key is not found.
- mBucket.push_back(info);
- VLOG(" duration: %lld", (long long)mDuration);
+ if (mDuration > 0) {
+ current_info.mDuration = mDuration;
+ mBucket.push_back(current_info);
+ addPastBucketToAnomalyTrackers(current_info.mDuration, current_info.mBucketNum);
+ VLOG(" duration: %lld", (long long)current_info.mDuration);
}
if (mStarted.size() > 0) {
for (int i = 1; i < numBucketsForward; i++) {
DurationBucket info;
- info.mBucketStartNs = oldBucketStartTimeNs + mBucketSizeNs * i;
- info.mBucketEndNs = endTime + mBucketSizeNs * i;
+ info.mBucketStartNs = mCurrentBucketStartTimeNs + mBucketSizeNs * i;
+ info.mBucketEndNs = info.mBucketStartNs + mBucketSizeNs;
+ info.mBucketNum = mCurrentBucketNum + i;
info.mDuration = mBucketSizeNs;
- mBucket.push_back(info);
- VLOG(" add filling bucket with duration %lld", (long long)mBucketSizeNs);
+ mLastStartTime = info.mBucketEndNs;
+ if (info.mDuration > 0) {
+ mBucket.push_back(info);
+ addPastBucketToAnomalyTrackers(info.mDuration, info.mBucketNum);
+ VLOG(" add filling bucket with duration %lld", (long long)info.mDuration);
+ }
}
}
- mLastStartTime = mCurrentBucketStartTimeNs;
+ mCurrentBucketStartTimeNs += numBucketsForward * mBucketSizeNs;
+ mCurrentBucketNum += numBucketsForward;
+
mDuration = 0;
// if all stopped, then tell owner it's safe to remove this tracker.
@@ -133,6 +156,8 @@
}
void OringDurationTracker::onSlicedConditionMayChange(const uint64_t timestamp) {
+ flushIfNeeded(timestamp);
+ declareAnomalyIfAlarmExpired(timestamp);
vector<pair<HashableDimensionKey, int>> startedToPaused;
vector<pair<HashableDimensionKey, int>> pausedToStarted;
if (!mStarted.empty()) {
@@ -154,9 +179,11 @@
}
if (mStarted.empty()) {
- mDuration += (timestamp - mLastStartTime);
+ mDuration = (timestamp - mLastStartTime);
+ mLastStartTime = -1;
VLOG("Duration add %lld , to %lld ", (long long)(timestamp - mLastStartTime),
(long long)mDuration);
+ detectAndDeclareAnomaly(timestamp, mCurrentBucketNum, mDuration);
}
}
@@ -183,26 +210,95 @@
}
}
+ if (mStarted.empty() && !pausedToStarted.empty()) {
+ startAnomalyAlarm(timestamp);
+ }
mStarted.insert(pausedToStarted.begin(), pausedToStarted.end());
mPaused.insert(startedToPaused.begin(), startedToPaused.end());
+
+ if (mStarted.empty()) {
+ stopAnomalyAlarm();
+ }
}
void OringDurationTracker::onConditionChanged(bool condition, const uint64_t timestamp) {
+ flushIfNeeded(timestamp);
+ declareAnomalyIfAlarmExpired(timestamp);
if (condition) {
if (!mPaused.empty()) {
VLOG("Condition true, all started");
if (mStarted.empty()) {
- mLastStartTime = timestamp;
+ mLastStartTime = -1;
+ }
+ if (mStarted.empty() && !mPaused.empty()) {
+ startAnomalyAlarm(timestamp);
}
mStarted.insert(mPaused.begin(), mPaused.end());
+ mPaused.clear();
}
} else {
if (!mStarted.empty()) {
VLOG("Condition false, all paused");
- mDuration += (timestamp - mLastStartTime);
+ mDuration = (timestamp - mLastStartTime);
+ mLastStartTime = -1;
mPaused.insert(mStarted.begin(), mStarted.end());
+ mStarted.clear();
+ detectAndDeclareAnomaly(timestamp, mCurrentBucketNum, mDuration);
}
}
+ if (mStarted.empty()) {
+ stopAnomalyAlarm();
+ }
+}
+
+int64_t OringDurationTracker::predictAnomalyTimestampNs(const AnomalyTracker& anomalyTracker,
+ const uint64_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 time until the current bucket ends. This is how much more 'space' it can hold.
+ const int64_t currRemainingBucketSizeNs =
+ mBucketSizeNs - (eventTimestampNs - mCurrentBucketStartTimeNs);
+ // TODO: This should never be < 0. Document/guard against possible failures if it is.
+
+ const int64_t thresholdNs = anomalyTracker.getAnomalyThreshold();
+
+ // 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;
+ pastNs += anomalyTracker.getSumOverPastBuckets(mEventKey);
+
+ // How much of the threshold is still unaccounted after considering pastNs.
+ int64_t leftNs = thresholdNs - pastNs;
+
+ // First deal with the remainder of the current bucket.
+ if (leftNs <= currRemainingBucketSizeNs) { // Predict the anomaly will occur in this bucket.
+ return eventTimestampNs + leftNs;
+ }
+ // 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.getNumOfPastPackets();
+ futBucketIdx++) {
+ // We now overwrite the oldest bucket with the previous 'current', and start a new
+ // 'current'.
+ pastNs -= anomalyTracker.getPastBucketValue(
+ mEventKey, mCurrentBucketNum - anomalyTracker.getNumOfPastPackets() + 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;
+ }
+ }
+
+ // 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;
}
} // namespace statsd
diff --git a/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.h b/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.h
index 78760ba..6f92113 100644
--- a/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.h
+++ b/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.h
@@ -27,8 +27,10 @@
// Tracks the "Or'd" duration -- if 2 durations are overlapping, they won't be double counted.
class OringDurationTracker : public DurationTracker {
public:
- OringDurationTracker(sp<ConditionWizard> wizard, int conditionIndex, bool nesting,
- uint64_t currentBucketStartNs, uint64_t bucketSizeNs,
+ OringDurationTracker(const HashableDimensionKey& eventKey, sp<ConditionWizard> wizard,
+ int conditionIndex, bool nesting, uint64_t currentBucketStartNs,
+ uint64_t bucketSizeNs,
+ const std::vector<sp<AnomalyTracker>>& anomalyTrackers,
std::vector<DurationBucket>& bucket);
void noteStart(const HashableDimensionKey& key, bool condition, const uint64_t eventTime,
const ConditionKey& conditionKey) override;
@@ -39,6 +41,9 @@
void onConditionChanged(bool condition, const uint64_t timestamp) override;
bool flushIfNeeded(uint64_t timestampNs) override;
+ int64_t predictAnomalyTimestampNs(const AnomalyTracker& anomalyTracker,
+ const uint64_t currentTimestamp) const override;
+
private:
// We don't need to keep track of individual durations. The information that's needed is:
// 1) which keys are started. We record the first start time.
@@ -49,6 +54,12 @@
std::map<HashableDimensionKey, int> mPaused;
int64_t mLastStartTime;
std::map<HashableDimensionKey, ConditionKey> mConditionKeyMap;
+
+ FRIEND_TEST(OringDurationTrackerTest, TestDurationOverlap);
+ FRIEND_TEST(OringDurationTrackerTest, TestCrossBucketBoundary);
+ FRIEND_TEST(OringDurationTrackerTest, TestDurationConditionChange);
+ FRIEND_TEST(OringDurationTrackerTest, TestPredictAnomalyTimestamp);
+ FRIEND_TEST(OringDurationTrackerTest, TestAnomalyDetection);
};
} // namespace statsd
diff --git a/cmds/statsd/src/metrics/metrics_manager_util.cpp b/cmds/statsd/src/metrics/metrics_manager_util.cpp
index d83c144..2344cb4 100644
--- a/cmds/statsd/src/metrics/metrics_manager_util.cpp
+++ b/cmds/statsd/src/metrics/metrics_manager_util.cpp
@@ -190,7 +190,8 @@
vector<sp<ConditionTracker>>& allConditionTrackers,
vector<sp<MetricProducer>>& allMetricProducers,
unordered_map<int, std::vector<int>>& conditionToMetricMap,
- unordered_map<int, std::vector<int>>& trackerToMetricMap) {
+ unordered_map<int, std::vector<int>>& trackerToMetricMap,
+ unordered_map<string, int>& metricMap) {
sp<ConditionWizard> wizard = new ConditionWizard(allConditionTrackers);
const int allMetricsCount = config.count_metric_size() + config.duration_metric_size() +
config.event_metric_size() + config.value_metric_size();
@@ -208,6 +209,7 @@
}
int metricIndex = allMetricProducers.size();
+ metricMap.insert({metric.name(), metricIndex});
int trackerIndex;
if (!handleMetricWithLogTrackers(metric.what(), metricIndex, metric.dimension_size() > 0,
allLogEntryMatchers, logTrackerMap, trackerToMetricMap,
@@ -236,6 +238,7 @@
for (int i = 0; i < config.duration_metric_size(); i++) {
int metricIndex = allMetricProducers.size();
const DurationMetric& metric = config.duration_metric(i);
+ metricMap.insert({metric.name(), metricIndex});
auto what_it = conditionTrackerMap.find(metric.what());
if (what_it == conditionTrackerMap.end()) {
@@ -305,6 +308,7 @@
for (int i = 0; i < config.event_metric_size(); i++) {
int metricIndex = allMetricProducers.size();
const EventMetric& metric = config.event_metric(i);
+ metricMap.insert({metric.name(), metricIndex});
if (!metric.has_name() || !metric.has_what()) {
ALOGW("cannot find the metric name or what in config");
return false;
@@ -342,6 +346,7 @@
}
int metricIndex = allMetricProducers.size();
+ metricMap.insert({metric.name(), metricIndex});
int trackerIndex;
if (!handleMetricWithLogTrackers(metric.what(), metricIndex, metric.dimension_size() > 0,
allLogEntryMatchers, logTrackerMap, trackerToMetricMap,
@@ -387,6 +392,7 @@
}
int metricIndex = allMetricProducers.size();
+ metricMap.insert({metric.name(), metricIndex});
int trackerIndex;
if (!handleMetricWithLogTrackers(metric.what(), metricIndex, metric.dimension_size() > 0,
allLogEntryMatchers, logTrackerMap, trackerToMetricMap,
@@ -419,21 +425,43 @@
}
sp<MetricProducer> gaugeProducer =
- new GaugeMetricProducer(metric, conditionIndex, wizard, pullTagId);
+ new GaugeMetricProducer(metric, conditionIndex, wizard, pullTagId, startTimeNs);
allMetricProducers.push_back(gaugeProducer);
}
return true;
}
+bool initAlerts(const StatsdConfig& config, const unordered_map<string, int>& metricProducerMap,
+ vector<sp<MetricProducer>>& allMetricProducers,
+ vector<sp<AnomalyTracker>>& allAnomalyTrackers) {
+ for (int i = 0; i < config.alert_size(); i++) {
+ const Alert& alert = config.alert(i);
+ const auto& itr = metricProducerMap.find(alert.metric_name());
+ if (itr == metricProducerMap.end()) {
+ ALOGW("alert has unknown metric name: %s %s", alert.name().c_str(),
+ alert.metric_name().c_str());
+ return false;
+ }
+ const int metricIndex = itr->second;
+ sp<AnomalyTracker> anomalyTracker =
+ new AnomalyTracker(alert, allMetricProducers[metricIndex]->getBuckeSizeInNs());
+ allMetricProducers[metricIndex]->addAnomalyTracker(anomalyTracker);
+ allAnomalyTrackers.push_back(anomalyTracker);
+ }
+ return true;
+}
+
bool initStatsdConfig(const StatsdConfig& config, set<int>& allTagIds,
vector<sp<LogMatchingTracker>>& allLogEntryMatchers,
vector<sp<ConditionTracker>>& allConditionTrackers,
vector<sp<MetricProducer>>& allMetricProducers,
+ 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) {
unordered_map<string, int> logTrackerMap;
unordered_map<string, int> conditionTrackerMap;
+ unordered_map<string, int> metricProducerMap;
if (!initLogTrackers(config, logTrackerMap, allLogEntryMatchers, allTagIds)) {
ALOGE("initLogMatchingTrackers failed");
@@ -449,10 +477,14 @@
if (!initMetrics(config, logTrackerMap, conditionTrackerMap, allLogEntryMatchers,
allConditionTrackers, allMetricProducers, conditionToMetricMap,
- trackerToMetricMap)) {
+ trackerToMetricMap, metricProducerMap)) {
ALOGE("initMetricProducers failed");
return false;
}
+ if (!initAlerts(config, metricProducerMap, allMetricProducers, allAnomalyTrackers)) {
+ ALOGE("initAlerts failed");
+ return false;
+ }
return true;
}
diff --git a/cmds/statsd/src/metrics/metrics_manager_util.h b/cmds/statsd/src/metrics/metrics_manager_util.h
index edf3af0..7d7e0c3 100644
--- a/cmds/statsd/src/metrics/metrics_manager_util.h
+++ b/cmds/statsd/src/metrics/metrics_manager_util.h
@@ -88,6 +88,7 @@
std::vector<sp<LogMatchingTracker>>& allLogEntryMatchers,
std::vector<sp<ConditionTracker>>& allConditionTrackers,
std::vector<sp<MetricProducer>>& allMetricProducers,
+ vector<sp<AnomalyTracker>>& allAnomalyTrackers,
std::unordered_map<int, std::vector<int>>& conditionToMetricMap,
std::unordered_map<int, std::vector<int>>& trackerToMetricMap,
std::unordered_map<int, std::vector<int>>& trackerToConditionMap);
diff --git a/cmds/statsd/src/stats_util.h b/cmds/statsd/src/stats_util.h
index e1d0aceb..b7d8f97 100644
--- a/cmds/statsd/src/stats_util.h
+++ b/cmds/statsd/src/stats_util.h
@@ -35,15 +35,14 @@
typedef std::map<std::string, HashableDimensionKey> ConditionKey;
-// TODO: For P, change int to int64_t.
-// TODO: Should HashableDimensionKey be marked here as const?
-typedef std::unordered_map<HashableDimensionKey, int> DimToValMap;
+typedef std::unordered_map<HashableDimensionKey, int64_t> DimToValMap;
EventMetricData parse(log_msg msg);
int getTagId(log_msg msg);
std::string getHashableKey(std::vector<KeyValuePair> key);
+
} // namespace statsd
} // namespace os
} // namespace android
diff --git a/cmds/statsd/src/statsd_config.proto b/cmds/statsd/src/statsd_config.proto
index d3b04ba..e75a37f 100644
--- a/cmds/statsd/src/statsd_config.proto
+++ b/cmds/statsd/src/statsd_config.proto
@@ -113,11 +113,11 @@
}
message EventConditionLink {
- optional string condition = 1;
+ optional string condition = 1;
- repeated KeyMatcher key_in_main = 2;
+ repeated KeyMatcher key_in_main = 2;
- repeated KeyMatcher key_in_condition = 3;
+ repeated KeyMatcher key_in_condition = 3;
}
message EventMetric {
@@ -217,8 +217,6 @@
optional int32 refractory_period_secs = 5;
optional int64 trigger_if_sum_gt = 6;
-
- optional int32 refractory_period_in_buckets = 7;
}
message StatsdConfig {
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);