blob: 0904a04987e70c9591355fbe5e5047cfd0e04d4d [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
17#define DEBUG true // STOPSHIP if true
18#include "Log.h"
19
20#include "AnomalyTracker.h"
21
22#include <time.h>
23
24namespace android {
25namespace os {
26namespace statsd {
27
28AnomalyTracker::AnomalyTracker(const Alert& alert, const int64_t& bucketSizeNs)
29 : mAlert(alert),
30 mBucketSizeNs(bucketSizeNs),
31 mNumOfPastPackets(mAlert.number_of_buckets() - 1) {
32 VLOG("AnomalyTracker() called");
33 if (mAlert.number_of_buckets() <= 0) {
34 ALOGE("Cannot create DiscreteAnomalyTracker with %lld buckets",
35 (long long)mAlert.number_of_buckets());
36 return;
37 }
38 if (mBucketSizeNs <= 0) {
39 ALOGE("Cannot create DiscreteAnomalyTracker with bucket size %lld ",
40 (long long)mBucketSizeNs);
41 return;
42 }
43 if (!mAlert.has_trigger_if_sum_gt()) {
44 ALOGE("Cannot create DiscreteAnomalyTracker without threshold");
45 return;
46 }
47 reset(); // initialization
48}
49
50AnomalyTracker::~AnomalyTracker() {
51 VLOG("~AnomalyTracker() called");
52 stopAllAlarms();
53}
54
55void AnomalyTracker::reset() {
56 VLOG("reset() called.");
57 stopAllAlarms();
58 mPastBuckets.clear();
59 // Excludes the current bucket.
60 mPastBuckets.resize(mNumOfPastPackets);
61 mSumOverPastBuckets.clear();
62 mMostRecentBucketNum = -1;
63 mLastAlarmTimestampNs = -1;
64}
65
66size_t AnomalyTracker::index(int64_t bucketNum) const {
67 return bucketNum % mNumOfPastPackets;
68}
69
70void AnomalyTracker::flushPastBuckets(const int64_t& latestPastBucketNum) {
71 VLOG("addPastBucket() called.");
72 if (latestPastBucketNum <= mMostRecentBucketNum - mNumOfPastPackets) {
73 ALOGE("Cannot add a past bucket %lld units in past", (long long)latestPastBucketNum);
74 return;
75 }
76
77 // The past packets are ancient. Empty out old mPastBuckets[i] values and reset
78 // mSumOverPastBuckets.
79 if (latestPastBucketNum - mMostRecentBucketNum >= mNumOfPastPackets) {
80 mPastBuckets.clear();
81 mPastBuckets.resize(mNumOfPastPackets);
82 mSumOverPastBuckets.clear();
83 } else {
84 for (int64_t i = std::max(0LL, (long long)(mMostRecentBucketNum - mNumOfPastPackets + 1));
85 i <= latestPastBucketNum - mNumOfPastPackets; i++) {
86 const int idx = index(i);
87 subtractBucketFromSum(mPastBuckets[idx]);
88 mPastBuckets[idx] = nullptr; // release (but not clear) the old bucket.
89 }
90 }
91
92 // It is an update operation.
93 if (latestPastBucketNum <= mMostRecentBucketNum &&
94 latestPastBucketNum > mMostRecentBucketNum - mNumOfPastPackets) {
95 subtractBucketFromSum(mPastBuckets[index(latestPastBucketNum)]);
96 }
97}
98
99void AnomalyTracker::addPastBucket(const HashableDimensionKey& key, const int64_t& bucketValue,
100 const int64_t& bucketNum) {
101 flushPastBuckets(bucketNum);
102
103 auto& bucket = mPastBuckets[index(bucketNum)];
104 if (bucket == nullptr) {
105 bucket = std::make_shared<DimToValMap>();
106 }
107 bucket->insert({key, bucketValue});
108 addBucketToSum(bucket);
109 mMostRecentBucketNum = std::max(mMostRecentBucketNum, bucketNum);
110}
111
112void AnomalyTracker::addPastBucket(std::shared_ptr<DimToValMap> bucketValues,
113 const int64_t& bucketNum) {
114 VLOG("addPastBucket() called.");
115 flushPastBuckets(bucketNum);
116 // Replace the oldest bucket with the new bucket we are adding.
117 mPastBuckets[index(bucketNum)] = bucketValues;
118 addBucketToSum(bucketValues);
119 mMostRecentBucketNum = std::max(mMostRecentBucketNum, bucketNum);
120}
121
122void AnomalyTracker::subtractBucketFromSum(const shared_ptr<DimToValMap>& bucket) {
123 if (bucket == nullptr) {
124 return;
125 }
126 // For each dimension present in the bucket, subtract its value from its corresponding sum.
127 for (const auto& keyValuePair : *bucket) {
128 auto itr = mSumOverPastBuckets.find(keyValuePair.first);
129 if (itr == mSumOverPastBuckets.end()) {
130 continue;
131 }
132 itr->second -= keyValuePair.second;
133 // TODO: No need to look up the object twice like this. Use a var.
134 if (itr->second == 0) {
135 mSumOverPastBuckets.erase(itr);
136 }
137 }
138}
139
140void AnomalyTracker::addBucketToSum(const shared_ptr<DimToValMap>& bucket) {
141 if (bucket == nullptr) {
142 return;
143 }
144 // For each dimension present in the bucket, add its value to its corresponding sum.
145 for (const auto& keyValuePair : *bucket) {
146 mSumOverPastBuckets[keyValuePair.first] += keyValuePair.second;
147 }
148}
149
150int64_t AnomalyTracker::getPastBucketValue(const HashableDimensionKey& key,
151 const int64_t& bucketNum) const {
152 const auto& bucket = mPastBuckets[index(bucketNum)];
153 if (bucket == nullptr) {
154 return 0;
155 }
156 const auto& itr = bucket->find(key);
157 return itr == bucket->end() ? 0 : itr->second;
158}
159
160int64_t AnomalyTracker::getSumOverPastBuckets(const HashableDimensionKey& key) const {
161 const auto& itr = mSumOverPastBuckets.find(key);
162 if (itr != mSumOverPastBuckets.end()) {
163 return itr->second;
164 }
165 return 0;
166}
167
168bool AnomalyTracker::detectAnomaly(const int64_t& currentBucketNum,
169 const DimToValMap& currentBucket) {
170 if (currentBucketNum > mMostRecentBucketNum + 1) {
171 addPastBucket(nullptr, currentBucketNum - 1);
172 }
173 for (auto itr = currentBucket.begin(); itr != currentBucket.end(); itr++) {
174 if (itr->second + getSumOverPastBuckets(itr->first) > mAlert.trigger_if_sum_gt()) {
175 return true;
176 }
177 }
178 // In theory, we also need to check the dimsions not in the current bucket. In single-thread
179 // mode, usually we could avoid the following loops.
180 for (auto itr = mSumOverPastBuckets.begin(); itr != mSumOverPastBuckets.end(); itr++) {
181 if (itr->second > mAlert.trigger_if_sum_gt()) {
182 return true;
183 }
184 }
185 return false;
186}
187
188bool AnomalyTracker::detectAnomaly(const int64_t& currentBucketNum, const HashableDimensionKey& key,
189 const int64_t& currentBucketValue) {
190 if (currentBucketNum > mMostRecentBucketNum + 1) {
191 addPastBucket(key, 0, currentBucketNum - 1);
192 }
193 return getSumOverPastBuckets(key) + currentBucketValue > mAlert.trigger_if_sum_gt();
194}
195
196void AnomalyTracker::declareAnomaly(const uint64_t& timestamp) {
197 if (mLastAlarmTimestampNs >= 0 &&
198 timestamp - mLastAlarmTimestampNs <= mAlert.refractory_period_secs() * NS_PER_SEC) {
199 VLOG("Skipping anomaly check since within refractory period");
200 return;
201 }
202 // TODO(guardrail): Consider guarding against too short refractory periods.
203 mLastAlarmTimestampNs = timestamp;
204
205 if (mAlert.has_incidentd_details()) {
206 // TODO: Can construct a name based on the criteria (and/or relay the criteria).
207 ALOGW("An anomaly (nameless) has occurred! Informing incidentd.");
208 // TODO: Send incidentd_details.name and incidentd_details.incidentd_sections to incidentd
209 } else {
210 ALOGW("An anomaly has occurred! (But informing incidentd not requested.)");
211 }
212}
213
214void AnomalyTracker::declareAnomalyIfAlarmExpired(const HashableDimensionKey& dimensionKey,
215 const uint64_t& timestamp) {
216 auto itr = mAlarms.find(dimensionKey);
217 if (itr == mAlarms.end()) {
218 return;
219 }
220
221 if (itr->second != nullptr &&
222 static_cast<uint32_t>(timestamp / NS_PER_SEC) >= itr->second->timestampSec) {
223 declareAnomaly(timestamp);
224 stopAlarm(dimensionKey);
225 }
226}
227
228void AnomalyTracker::detectAndDeclareAnomaly(const uint64_t& timestamp,
229 const int64_t& currBucketNum,
230 const HashableDimensionKey& key,
231 const int64_t& currentBucketValue) {
232 if (detectAnomaly(currBucketNum, key, currentBucketValue)) {
233 declareAnomaly(timestamp);
234 }
235}
236
237void AnomalyTracker::detectAndDeclareAnomaly(const uint64_t& timestamp,
238 const int64_t& currBucketNum,
239 const DimToValMap& currentBucket) {
240 if (detectAnomaly(currBucketNum, currentBucket)) {
241 declareAnomaly(timestamp);
242 }
243}
244
245void AnomalyTracker::startAlarm(const HashableDimensionKey& dimensionKey,
246 const uint64_t& timestamp) {
247 sp<const AnomalyAlarm> alarm = new AnomalyAlarm{static_cast<uint32_t>(timestamp / NS_PER_SEC)};
248 mAlarms.insert({dimensionKey, alarm});
249 if (mAnomalyMonitor != nullptr) {
250 mAnomalyMonitor->add(alarm);
251 }
252}
253
254void AnomalyTracker::stopAlarm(const HashableDimensionKey& dimensionKey) {
255 auto itr = mAlarms.find(dimensionKey);
256 if (itr != mAlarms.end()) {
257 mAlarms.erase(dimensionKey);
258 }
259 if (mAnomalyMonitor != nullptr) {
260 mAnomalyMonitor->remove(itr->second);
261 }
262}
263
264void AnomalyTracker::stopAllAlarms() {
265 std::set<HashableDimensionKey> keys;
266 for (auto itr = mAlarms.begin(); itr != mAlarms.end(); ++itr) {
267 keys.insert(itr->first);
268 }
269 for (auto key : keys) {
270 stopAlarm(key);
271 }
272}
273
274} // namespace statsd
275} // namespace os
276} // namespace android