blob: 9c9bde9a459eb83719e065e1bd499705bddc8145 [file] [log] [blame]
/*
* 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()) {
const Alert_IncidentdDetails& incident = mAlert.incidentd_details();
if (incident.has_alert_name()) {
ALOGW("An anomaly (%s) has occurred! Informing incidentd.",
incident.alert_name().c_str());
} else {
// 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