Statsd Anomaly tracking for CountMetricProducer

CountMetricProducer now has a CountAnomalyTracker which stores past
bucket information. Anomalies can be determined by seeing if the
information from the past and current buckets exeeds a threshold.

Test: manual
Change-Id: I35103c01dd32dcc31cb155f5685161cbaf969d03
diff --git a/cmds/statsd/src/metrics/CountAnomalyTracker.cpp b/cmds/statsd/src/metrics/CountAnomalyTracker.cpp
new file mode 100644
index 0000000..ebd53e0
--- /dev/null
+++ b/cmds/statsd/src/metrics/CountAnomalyTracker.cpp
@@ -0,0 +1,108 @@
+/*
+ * 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 LOG_TAG "CountAnomaly"
+#define DEBUG true  // STOPSHIP if true
+#define VLOG(...) \
+    if (DEBUG) ALOGD(__VA_ARGS__);
+
+#include "CountAnomalyTracker.h"
+
+#include <cutils/log.h>
+
+namespace android {
+namespace os {
+namespace statsd {
+
+CountAnomalyTracker::CountAnomalyTracker(size_t numBuckets, int thresholdGt)
+    : mNumPastBuckets(numBuckets > 0 ? numBuckets - 1 : 0),
+      mPastBuckets(mNumPastBuckets > 0 ? (new int[mNumPastBuckets]) : nullptr),
+      mThresholdGt(thresholdGt) {
+
+    VLOG("CountAnomalyTracker() called");
+    if (numBuckets < 1) {
+        ALOGE("Cannot create CountAnomalyTracker with %zu buckets", numBuckets);
+    }
+    reset(); // initialization
+}
+
+CountAnomalyTracker::~CountAnomalyTracker() {
+    VLOG("~CountAnomalyTracker() called");
+}
+
+void CountAnomalyTracker::addPastBucket(int pastBucketCount,
+                                        time_t numberOfBucketsAgo) {
+    VLOG("addPastBucket() called.");
+    if (numberOfBucketsAgo < 1) {
+        ALOGE("Cannot add a past bucket %ld units in past", numberOfBucketsAgo);
+        return;
+    }
+    // If past bucket was ancient, just empty out all past info.
+    // This always applies if mNumPastBuckets == 0 (i.e. store no past buckets).
+    if (numberOfBucketsAgo > (time_t) mNumPastBuckets) {
+        reset();
+        return;
+    }
+
+    // Empty out old mPastBuckets[i] values and update mSumPastCounters.
+    for (size_t i = mOldestBucketIndex;
+                        i < mOldestBucketIndex + numberOfBucketsAgo; i++) {
+        mSumPastCounters -= mPastBuckets[index(i)];
+        mPastBuckets[index(i)] = 0;
+    }
+
+    // Replace the oldest bucket with the new bucket we are adding.
+    mPastBuckets[mOldestBucketIndex] = pastBucketCount;
+    mSumPastCounters += pastBucketCount;
+
+    // Advance the oldest bucket index by numberOfBucketsAgo units.
+    mOldestBucketIndex = index(mOldestBucketIndex + numberOfBucketsAgo);
+
+    // TODO: Once dimensions are added to mSumPastCounters:
+    // iterate through mSumPastCounters and remove any entries that are 0.
+}
+
+void CountAnomalyTracker::reset() {
+    VLOG("reset() called.");
+    for (size_t i = 0; i < mNumPastBuckets; i++) {
+        mPastBuckets[i] = 0;
+    }
+    mSumPastCounters = 0;
+    mOldestBucketIndex = 0;
+}
+
+void CountAnomalyTracker::checkAnomaly(int currentCount) {
+    // Note that this works even if mNumPastBuckets < 1 (since then
+    // mSumPastCounters = 0 so the comparison is based only on currentCount).
+
+    // TODO: Remove these extremely verbose debugging log.
+    VLOG("Checking whether %d + %d > %d",
+         mSumPastCounters, currentCount, mThresholdGt);
+
+    if (mSumPastCounters + currentCount > mThresholdGt) {
+        declareAnomaly();
+    }
+}
+
+void CountAnomalyTracker::declareAnomaly() {
+    // TODO: check that not in refractory period.
+    // TODO: Do something.
+    ALOGI("An anomaly has occurred!");
+}
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android