blob: 49de1ac417bc76949d889dfc939b5d11d48741a0 [file] [log] [blame]
Yangster-mace2cd6d52017-11-09 20:38:30 -08001/*
2 * Copyright (C) 2017 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
Tej Singh484524a2018-02-01 15:10:05 -080017#define DEBUG false // STOPSHIP if true
Yangster-mace2cd6d52017-11-09 20:38:30 -080018#include "Log.h"
19
20#include "AnomalyTracker.h"
Yangster-mac932ecec2018-02-01 10:23:52 -080021#include "subscriber_util.h"
Primiano Tuccie4d44912018-01-10 12:14:50 +000022#include "external/Perfetto.h"
Yi Jinafb36062018-01-31 19:14:25 -080023#include "guardrail/StatsdStats.h"
24#include "subscriber/IncidentdReporter.h"
Bookatzc6977972018-01-16 16:55:05 -080025#include "subscriber/SubscriberReporter.h"
Yangster-mace2cd6d52017-11-09 20:38:30 -080026
Bookatz8fcd09a2017-12-18 13:01:10 -080027#include <statslog.h>
Yangster-mace2cd6d52017-11-09 20:38:30 -080028#include <time.h>
29
30namespace android {
31namespace os {
32namespace statsd {
33
Bookatz8f2f3d82017-12-07 13:53:21 -080034AnomalyTracker::AnomalyTracker(const Alert& alert, const ConfigKey& configKey)
Bookatz6bf98252018-03-14 10:44:24 -070035 : mAlert(alert), mConfigKey(configKey), mNumOfPastBuckets(mAlert.num_buckets() - 1) {
Yangster-mace2cd6d52017-11-09 20:38:30 -080036 VLOG("AnomalyTracker() called");
Yangster-maca7fb12d2018-01-03 17:17:20 -080037 if (mAlert.num_buckets() <= 0) {
Yi Jinafb36062018-01-31 19:14:25 -080038 ALOGE("Cannot create AnomalyTracker with %lld buckets", (long long)mAlert.num_buckets());
Yangster-mace2cd6d52017-11-09 20:38:30 -080039 return;
40 }
Yangster-mace2cd6d52017-11-09 20:38:30 -080041 if (!mAlert.has_trigger_if_sum_gt()) {
Bookatzcc5adef2017-11-21 14:36:23 -080042 ALOGE("Cannot create AnomalyTracker without threshold");
Yangster-mace2cd6d52017-11-09 20:38:30 -080043 return;
44 }
Yi Jinafb36062018-01-31 19:14:25 -080045 resetStorage(); // initialization
Yangster-mace2cd6d52017-11-09 20:38:30 -080046}
47
48AnomalyTracker::~AnomalyTracker() {
49 VLOG("~AnomalyTracker() called");
Yangster-mace2cd6d52017-11-09 20:38:30 -080050}
51
Bookatzcc5adef2017-11-21 14:36:23 -080052void AnomalyTracker::resetStorage() {
53 VLOG("resetStorage() called.");
Yangster-mace2cd6d52017-11-09 20:38:30 -080054 mPastBuckets.clear();
55 // Excludes the current bucket.
Bookatzcc5adef2017-11-21 14:36:23 -080056 mPastBuckets.resize(mNumOfPastBuckets);
Yangster-mace2cd6d52017-11-09 20:38:30 -080057 mSumOverPastBuckets.clear();
Yangster-mace2cd6d52017-11-09 20:38:30 -080058}
59
60size_t AnomalyTracker::index(int64_t bucketNum) const {
Bookatz2fb56532018-03-08 11:16:48 -080061 if (bucketNum < 0) {
Bookatz2fb56532018-03-08 11:16:48 -080062 ALOGE("index() was passed a negative bucket number (%lld)!", (long long)bucketNum);
63 }
Bookatzcc5adef2017-11-21 14:36:23 -080064 return bucketNum % mNumOfPastBuckets;
Yangster-mace2cd6d52017-11-09 20:38:30 -080065}
66
Bookatz6bf98252018-03-14 10:44:24 -070067void AnomalyTracker::advanceMostRecentBucketTo(const int64_t& bucketNum) {
68 VLOG("advanceMostRecentBucketTo() called.");
69 if (bucketNum <= mMostRecentBucketNum) {
70 ALOGW("Cannot advance buckets backwards (bucketNum=%lld but mMostRecentBucketNum=%lld)",
71 (long long)bucketNum, (long long)mMostRecentBucketNum);
Yangster-mace2cd6d52017-11-09 20:38:30 -080072 return;
73 }
Bookatz6bf98252018-03-14 10:44:24 -070074 // If in the future (i.e. buckets are ancient), just empty out all past info.
75 if (bucketNum >= mMostRecentBucketNum + mNumOfPastBuckets) {
Bookatz2fb56532018-03-08 11:16:48 -080076 resetStorage();
Bookatz6bf98252018-03-14 10:44:24 -070077 mMostRecentBucketNum = bucketNum;
78 return;
79 }
80
81 // Clear out space by emptying out old mPastBuckets[i] values and update mSumOverPastBuckets.
82 for (int64_t i = mMostRecentBucketNum + 1; i <= bucketNum; i++) {
83 const int idx = index(i);
84 subtractBucketFromSum(mPastBuckets[idx]);
85 mPastBuckets[idx] = nullptr; // release (but not clear) the old bucket.
86 }
87 mMostRecentBucketNum = bucketNum;
88}
89
90void AnomalyTracker::addPastBucket(const MetricDimensionKey& key,
91 const int64_t& bucketValue,
92 const int64_t& bucketNum) {
93 VLOG("addPastBucket(bucketValue) called.");
94 if (mNumOfPastBuckets == 0 ||
95 bucketNum < 0 || bucketNum <= mMostRecentBucketNum - mNumOfPastBuckets) {
96 return;
97 }
98
99 const int bucketIndex = index(bucketNum);
100 if (bucketNum <= mMostRecentBucketNum && (mPastBuckets[bucketIndex] != nullptr)) {
101 // We need to insert into an already existing past bucket.
102 std::shared_ptr<DimToValMap>& bucket = mPastBuckets[bucketIndex];
103 auto itr = bucket->find(key);
104 if (itr != bucket->end()) {
105 // Old entry already exists; update it.
106 subtractValueFromSum(key, itr->second);
107 itr->second = bucketValue;
108 } else {
109 bucket->insert({key, bucketValue});
Yangster-mace2cd6d52017-11-09 20:38:30 -0800110 }
Bookatz6bf98252018-03-14 10:44:24 -0700111 mSumOverPastBuckets[key] += bucketValue;
112 } else {
113 // Bucket does not exist yet (in future or was never made), so we must make it.
114 std::shared_ptr<DimToValMap> bucket = std::make_shared<DimToValMap>();
115 bucket->insert({key, bucketValue});
116 addPastBucket(bucket, bucketNum);
Yangster-mace2cd6d52017-11-09 20:38:30 -0800117 }
118}
119
Bookatz6bf98252018-03-14 10:44:24 -0700120void AnomalyTracker::addPastBucket(std::shared_ptr<DimToValMap> bucket,
Yangster-mace2cd6d52017-11-09 20:38:30 -0800121 const int64_t& bucketNum) {
Bookatz6bf98252018-03-14 10:44:24 -0700122 VLOG("addPastBucket(bucket) called.");
123 if (mNumOfPastBuckets == 0 ||
124 bucketNum < 0 || bucketNum <= mMostRecentBucketNum - mNumOfPastBuckets) {
David Chenc189bdcb2018-02-09 16:09:26 -0800125 return;
126 }
Yangster-mace2cd6d52017-11-09 20:38:30 -0800127
Bookatz6bf98252018-03-14 10:44:24 -0700128 if (bucketNum <= mMostRecentBucketNum) {
129 // We are updating an old bucket, not adding a new one.
130 subtractBucketFromSum(mPastBuckets[index(bucketNum)]);
131 } else {
132 // Clear space for the new bucket to be at bucketNum.
133 advanceMostRecentBucketTo(bucketNum);
Yangster-mace2cd6d52017-11-09 20:38:30 -0800134 }
Bookatz6bf98252018-03-14 10:44:24 -0700135 mPastBuckets[index(bucketNum)] = bucket;
Yangster-mace2cd6d52017-11-09 20:38:30 -0800136 addBucketToSum(bucket);
Yangster-mace2cd6d52017-11-09 20:38:30 -0800137}
138
139void AnomalyTracker::subtractBucketFromSum(const shared_ptr<DimToValMap>& bucket) {
140 if (bucket == nullptr) {
141 return;
142 }
Yangster-mace2cd6d52017-11-09 20:38:30 -0800143 for (const auto& keyValuePair : *bucket) {
Bookatz6bf98252018-03-14 10:44:24 -0700144 subtractValueFromSum(keyValuePair.first, keyValuePair.second);
145 }
146}
147
148
149void AnomalyTracker::subtractValueFromSum(const MetricDimensionKey& key,
150 const int64_t& bucketValue) {
151 auto itr = mSumOverPastBuckets.find(key);
152 if (itr == mSumOverPastBuckets.end()) {
153 return;
154 }
155 itr->second -= bucketValue;
156 if (itr->second == 0) {
157 mSumOverPastBuckets.erase(itr);
Yangster-mace2cd6d52017-11-09 20:38:30 -0800158 }
159}
160
161void AnomalyTracker::addBucketToSum(const shared_ptr<DimToValMap>& bucket) {
162 if (bucket == nullptr) {
163 return;
164 }
165 // For each dimension present in the bucket, add its value to its corresponding sum.
166 for (const auto& keyValuePair : *bucket) {
167 mSumOverPastBuckets[keyValuePair.first] += keyValuePair.second;
168 }
169}
170
Yangster-mac93694462018-01-22 20:49:31 -0800171int64_t AnomalyTracker::getPastBucketValue(const MetricDimensionKey& key,
Yangster-mace2cd6d52017-11-09 20:38:30 -0800172 const int64_t& bucketNum) const {
Bookatz6bf98252018-03-14 10:44:24 -0700173 if (bucketNum < 0 || bucketNum <= mMostRecentBucketNum - mNumOfPastBuckets
174 || bucketNum > mMostRecentBucketNum) {
David Chenebe7e2372018-02-20 13:22:53 -0800175 return 0;
176 }
177
Yangster-mace2cd6d52017-11-09 20:38:30 -0800178 const auto& bucket = mPastBuckets[index(bucketNum)];
179 if (bucket == nullptr) {
180 return 0;
181 }
182 const auto& itr = bucket->find(key);
183 return itr == bucket->end() ? 0 : itr->second;
184}
185
Yangster-mac93694462018-01-22 20:49:31 -0800186int64_t AnomalyTracker::getSumOverPastBuckets(const MetricDimensionKey& key) const {
Yangster-mace2cd6d52017-11-09 20:38:30 -0800187 const auto& itr = mSumOverPastBuckets.find(key);
188 if (itr != mSumOverPastBuckets.end()) {
189 return itr->second;
190 }
191 return 0;
192}
193
Bookatz6bf98252018-03-14 10:44:24 -0700194bool AnomalyTracker::detectAnomaly(const int64_t& currentBucketNum,
195 const MetricDimensionKey& key,
Yangster-mace2cd6d52017-11-09 20:38:30 -0800196 const int64_t& currentBucketValue) {
Bookatz6bf98252018-03-14 10:44:24 -0700197
198 // currentBucketNum should be the next bucket after pastBuckets. If not, advance so that it is.
Yangster-mace2cd6d52017-11-09 20:38:30 -0800199 if (currentBucketNum > mMostRecentBucketNum + 1) {
Bookatz6bf98252018-03-14 10:44:24 -0700200 advanceMostRecentBucketTo(currentBucketNum - 1);
Yangster-mace2cd6d52017-11-09 20:38:30 -0800201 }
Yi Jinafb36062018-01-31 19:14:25 -0800202 return mAlert.has_trigger_if_sum_gt() &&
203 getSumOverPastBuckets(key) + currentBucketValue > mAlert.trigger_if_sum_gt();
Yangster-mace2cd6d52017-11-09 20:38:30 -0800204}
205
Yangster-mac93694462018-01-22 20:49:31 -0800206void AnomalyTracker::declareAnomaly(const uint64_t& timestampNs, const MetricDimensionKey& key) {
Bookatzcc5adef2017-11-21 14:36:23 -0800207 // TODO: Why receive timestamp? RefractoryPeriod should always be based on real time right now.
Bookatz1bf94382018-01-04 11:43:20 -0800208 if (isInRefractoryPeriod(timestampNs, key)) {
Bookatzcc5adef2017-11-21 14:36:23 -0800209 VLOG("Skipping anomaly declaration since within refractory period");
Yangster-mace2cd6d52017-11-09 20:38:30 -0800210 return;
211 }
Bookatz6bf98252018-03-14 10:44:24 -0700212 if (mAlert.has_refractory_period_secs()) {
213 mRefractoryPeriodEndsSec[key] = ((timestampNs + NS_PER_SEC - 1) / NS_PER_SEC) // round up
214 + mAlert.refractory_period_secs();
215 // TODO: If we had access to the bucket_size_millis, consider calling resetStorage()
216 // if (mAlert.refractory_period_secs() > mNumOfPastBuckets * bucketSizeNs) {resetStorage();}
217 }
Yangster-mace2cd6d52017-11-09 20:38:30 -0800218
Yangster-mac94e197c2018-01-02 16:03:03 -0800219 if (!mSubscriptions.empty()) {
Bookatz6bf98252018-03-14 10:44:24 -0700220 ALOGI("An anomaly (%lld) %s has occurred! Informing subscribers.",
221 mAlert.id(), key.toString().c_str());
222 informSubscribers(key);
Yangster-mace2cd6d52017-11-09 20:38:30 -0800223 } else {
Yangster-mac94e197c2018-01-02 16:03:03 -0800224 ALOGI("An anomaly has occurred! (But no subscriber for that alert.)");
Yangster-mace2cd6d52017-11-09 20:38:30 -0800225 }
Bookatz8f2f3d82017-12-07 13:53:21 -0800226
Yangster-mac94e197c2018-01-02 16:03:03 -0800227 StatsdStats::getInstance().noteAnomalyDeclared(mConfigKey, mAlert.id());
Bookatz8fcd09a2017-12-18 13:01:10 -0800228
Yangster-mac93694462018-01-22 20:49:31 -0800229 // TODO: This should also take in the const MetricDimensionKey& key?
Bookatz8fcd09a2017-12-18 13:01:10 -0800230 android::util::stats_write(android::util::ANOMALY_DETECTED, mConfigKey.GetUid(),
Yangster-mac94e197c2018-01-02 16:03:03 -0800231 mConfigKey.GetId(), mAlert.id());
Yangster-mace2cd6d52017-11-09 20:38:30 -0800232}
233
Bookatzcc5adef2017-11-21 14:36:23 -0800234void AnomalyTracker::detectAndDeclareAnomaly(const uint64_t& timestampNs,
Yangster-mace2cd6d52017-11-09 20:38:30 -0800235 const int64_t& currBucketNum,
Yangster-mac93694462018-01-22 20:49:31 -0800236 const MetricDimensionKey& key,
Yangster-mace2cd6d52017-11-09 20:38:30 -0800237 const int64_t& currentBucketValue) {
238 if (detectAnomaly(currBucketNum, key, currentBucketValue)) {
Bookatz1bf94382018-01-04 11:43:20 -0800239 declareAnomaly(timestampNs, key);
Yangster-mace2cd6d52017-11-09 20:38:30 -0800240 }
241}
242
Bookatz1bf94382018-01-04 11:43:20 -0800243bool AnomalyTracker::isInRefractoryPeriod(const uint64_t& timestampNs,
Yangster-mac93694462018-01-22 20:49:31 -0800244 const MetricDimensionKey& key) {
Bookatz1bf94382018-01-04 11:43:20 -0800245 const auto& it = mRefractoryPeriodEndsSec.find(key);
246 if (it != mRefractoryPeriodEndsSec.end()) {
Bookatz6bf98252018-03-14 10:44:24 -0700247 if (timestampNs < it->second * NS_PER_SEC) {
Bookatz1bf94382018-01-04 11:43:20 -0800248 return true;
249 } else {
250 mRefractoryPeriodEndsSec.erase(key);
251 }
Yangster-mace2cd6d52017-11-09 20:38:30 -0800252 }
Bookatz1bf94382018-01-04 11:43:20 -0800253 return false;
Yangster-mace2cd6d52017-11-09 20:38:30 -0800254}
255
Yangster-mac93694462018-01-22 20:49:31 -0800256void AnomalyTracker::informSubscribers(const MetricDimensionKey& key) {
Yangster-mac932ecec2018-02-01 10:23:52 -0800257 triggerSubscribers(mAlert.id(), key, mConfigKey, mSubscriptions);
Bookatzd1fd2422017-11-22 15:21:03 -0800258}
259
Yangster-mace2cd6d52017-11-09 20:38:30 -0800260} // namespace statsd
261} // namespace os
262} // namespace android