blob: 7bacb441ee48affbb6bc10abb6aae71417858b43 [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
Bookatzd1fd2422017-11-22 15:21:03 -080022#include <android/os/IIncidentManager.h>
23#include <android/os/IncidentReportArgs.h>
24#include <binder/IServiceManager.h>
Yangster-mace2cd6d52017-11-09 20:38:30 -080025#include <time.h>
26
27namespace android {
28namespace os {
29namespace statsd {
30
Bookatzcc5adef2017-11-21 14:36:23 -080031// TODO: Separate DurationAnomalyTracker as a separate subclass and let each MetricProducer
32// decide and let which one it wants.
33// TODO: Get rid of bucketNumbers, and return to the original circular array method.
34AnomalyTracker::AnomalyTracker(const Alert& alert)
Yangster-mace2cd6d52017-11-09 20:38:30 -080035 : mAlert(alert),
Bookatzcc5adef2017-11-21 14:36:23 -080036 mNumOfPastBuckets(mAlert.number_of_buckets() - 1) {
Yangster-mace2cd6d52017-11-09 20:38:30 -080037 VLOG("AnomalyTracker() called");
38 if (mAlert.number_of_buckets() <= 0) {
Bookatzcc5adef2017-11-21 14:36:23 -080039 ALOGE("Cannot create AnomalyTracker with %lld buckets",
Yangster-mace2cd6d52017-11-09 20:38:30 -080040 (long long)mAlert.number_of_buckets());
41 return;
42 }
Yangster-mace2cd6d52017-11-09 20:38:30 -080043 if (!mAlert.has_trigger_if_sum_gt()) {
Bookatzcc5adef2017-11-21 14:36:23 -080044 ALOGE("Cannot create AnomalyTracker without threshold");
Yangster-mace2cd6d52017-11-09 20:38:30 -080045 return;
46 }
Bookatzcc5adef2017-11-21 14:36:23 -080047 resetStorage(); // initialization
Yangster-mace2cd6d52017-11-09 20:38:30 -080048}
49
50AnomalyTracker::~AnomalyTracker() {
51 VLOG("~AnomalyTracker() called");
52 stopAllAlarms();
53}
54
Bookatzcc5adef2017-11-21 14:36:23 -080055void AnomalyTracker::resetStorage() {
56 VLOG("resetStorage() called.");
Yangster-mace2cd6d52017-11-09 20:38:30 -080057 mPastBuckets.clear();
58 // Excludes the current bucket.
Bookatzcc5adef2017-11-21 14:36:23 -080059 mPastBuckets.resize(mNumOfPastBuckets);
Yangster-mace2cd6d52017-11-09 20:38:30 -080060 mSumOverPastBuckets.clear();
Bookatzcc5adef2017-11-21 14:36:23 -080061
62 if (!mAlarms.empty()) VLOG("AnomalyTracker.resetStorage() called but mAlarms is NOT empty!");
Yangster-mace2cd6d52017-11-09 20:38:30 -080063}
64
65size_t AnomalyTracker::index(int64_t bucketNum) const {
Bookatzcc5adef2017-11-21 14:36:23 -080066 return bucketNum % mNumOfPastBuckets;
Yangster-mace2cd6d52017-11-09 20:38:30 -080067}
68
69void AnomalyTracker::flushPastBuckets(const int64_t& latestPastBucketNum) {
70 VLOG("addPastBucket() called.");
Bookatzcc5adef2017-11-21 14:36:23 -080071 if (latestPastBucketNum <= mMostRecentBucketNum - mNumOfPastBuckets) {
Yangster-mace2cd6d52017-11-09 20:38:30 -080072 ALOGE("Cannot add a past bucket %lld units in past", (long long)latestPastBucketNum);
73 return;
74 }
75
76 // The past packets are ancient. Empty out old mPastBuckets[i] values and reset
77 // mSumOverPastBuckets.
Bookatzcc5adef2017-11-21 14:36:23 -080078 if (latestPastBucketNum - mMostRecentBucketNum >= mNumOfPastBuckets) {
Yangster-mace2cd6d52017-11-09 20:38:30 -080079 mPastBuckets.clear();
Bookatzcc5adef2017-11-21 14:36:23 -080080 mPastBuckets.resize(mNumOfPastBuckets);
Yangster-mace2cd6d52017-11-09 20:38:30 -080081 mSumOverPastBuckets.clear();
82 } else {
Bookatzcc5adef2017-11-21 14:36:23 -080083 for (int64_t i = std::max(0LL, (long long)(mMostRecentBucketNum - mNumOfPastBuckets + 1));
84 i <= latestPastBucketNum - mNumOfPastBuckets; i++) {
Yangster-mace2cd6d52017-11-09 20:38:30 -080085 const int idx = index(i);
86 subtractBucketFromSum(mPastBuckets[idx]);
87 mPastBuckets[idx] = nullptr; // release (but not clear) the old bucket.
88 }
89 }
90
91 // It is an update operation.
92 if (latestPastBucketNum <= mMostRecentBucketNum &&
Bookatzcc5adef2017-11-21 14:36:23 -080093 latestPastBucketNum > mMostRecentBucketNum - mNumOfPastBuckets) {
Yangster-mace2cd6d52017-11-09 20:38:30 -080094 subtractBucketFromSum(mPastBuckets[index(latestPastBucketNum)]);
95 }
96}
97
98void AnomalyTracker::addPastBucket(const HashableDimensionKey& key, const int64_t& bucketValue,
99 const int64_t& bucketNum) {
100 flushPastBuckets(bucketNum);
101
102 auto& bucket = mPastBuckets[index(bucketNum)];
103 if (bucket == nullptr) {
104 bucket = std::make_shared<DimToValMap>();
105 }
106 bucket->insert({key, bucketValue});
107 addBucketToSum(bucket);
108 mMostRecentBucketNum = std::max(mMostRecentBucketNum, bucketNum);
109}
110
111void AnomalyTracker::addPastBucket(std::shared_ptr<DimToValMap> bucketValues,
112 const int64_t& bucketNum) {
113 VLOG("addPastBucket() called.");
114 flushPastBuckets(bucketNum);
115 // Replace the oldest bucket with the new bucket we are adding.
116 mPastBuckets[index(bucketNum)] = bucketValues;
117 addBucketToSum(bucketValues);
118 mMostRecentBucketNum = std::max(mMostRecentBucketNum, bucketNum);
119}
120
121void AnomalyTracker::subtractBucketFromSum(const shared_ptr<DimToValMap>& bucket) {
122 if (bucket == nullptr) {
123 return;
124 }
125 // For each dimension present in the bucket, subtract its value from its corresponding sum.
126 for (const auto& keyValuePair : *bucket) {
127 auto itr = mSumOverPastBuckets.find(keyValuePair.first);
128 if (itr == mSumOverPastBuckets.end()) {
129 continue;
130 }
131 itr->second -= keyValuePair.second;
132 // TODO: No need to look up the object twice like this. Use a var.
133 if (itr->second == 0) {
134 mSumOverPastBuckets.erase(itr);
135 }
136 }
137}
138
139void AnomalyTracker::addBucketToSum(const shared_ptr<DimToValMap>& bucket) {
140 if (bucket == nullptr) {
141 return;
142 }
143 // For each dimension present in the bucket, add its value to its corresponding sum.
144 for (const auto& keyValuePair : *bucket) {
145 mSumOverPastBuckets[keyValuePair.first] += keyValuePair.second;
146 }
147}
148
149int64_t AnomalyTracker::getPastBucketValue(const HashableDimensionKey& key,
150 const int64_t& bucketNum) const {
151 const auto& bucket = mPastBuckets[index(bucketNum)];
152 if (bucket == nullptr) {
153 return 0;
154 }
155 const auto& itr = bucket->find(key);
156 return itr == bucket->end() ? 0 : itr->second;
157}
158
159int64_t AnomalyTracker::getSumOverPastBuckets(const HashableDimensionKey& key) const {
160 const auto& itr = mSumOverPastBuckets.find(key);
161 if (itr != mSumOverPastBuckets.end()) {
162 return itr->second;
163 }
164 return 0;
165}
166
167bool AnomalyTracker::detectAnomaly(const int64_t& currentBucketNum,
168 const DimToValMap& currentBucket) {
169 if (currentBucketNum > mMostRecentBucketNum + 1) {
170 addPastBucket(nullptr, currentBucketNum - 1);
171 }
172 for (auto itr = currentBucket.begin(); itr != currentBucket.end(); itr++) {
173 if (itr->second + getSumOverPastBuckets(itr->first) > mAlert.trigger_if_sum_gt()) {
174 return true;
175 }
176 }
177 // In theory, we also need to check the dimsions not in the current bucket. In single-thread
178 // mode, usually we could avoid the following loops.
179 for (auto itr = mSumOverPastBuckets.begin(); itr != mSumOverPastBuckets.end(); itr++) {
180 if (itr->second > mAlert.trigger_if_sum_gt()) {
181 return true;
182 }
183 }
184 return false;
185}
186
187bool AnomalyTracker::detectAnomaly(const int64_t& currentBucketNum, const HashableDimensionKey& key,
188 const int64_t& currentBucketValue) {
189 if (currentBucketNum > mMostRecentBucketNum + 1) {
190 addPastBucket(key, 0, currentBucketNum - 1);
191 }
Bookatzcc5adef2017-11-21 14:36:23 -0800192 return mAlert.has_trigger_if_sum_gt()
193 && getSumOverPastBuckets(key) + currentBucketValue > mAlert.trigger_if_sum_gt();
Yangster-mace2cd6d52017-11-09 20:38:30 -0800194}
195
Bookatzcc5adef2017-11-21 14:36:23 -0800196void AnomalyTracker::declareAnomaly(const uint64_t& timestampNs) {
197 // TODO: This should also take in the const HashableDimensionKey& key, to pass
198 // more details to incidentd and to make mRefractoryPeriodEndsSec key-specific.
199 // TODO: Why receive timestamp? RefractoryPeriod should always be based on real time right now.
200 if (isInRefractoryPeriod(timestampNs)) {
201 VLOG("Skipping anomaly declaration since within refractory period");
Yangster-mace2cd6d52017-11-09 20:38:30 -0800202 return;
203 }
204 // TODO(guardrail): Consider guarding against too short refractory periods.
Bookatzcc5adef2017-11-21 14:36:23 -0800205 mLastAlarmTimestampNs = timestampNs;
206
207
208 // TODO: If we had access to the bucket_size_millis, consider calling resetStorage()
209 // if (mAlert.refractory_period_secs() > mNumOfPastBuckets * bucketSizeNs) { resetStorage(); }
Yangster-mace2cd6d52017-11-09 20:38:30 -0800210
211 if (mAlert.has_incidentd_details()) {
Bookatzcc5adef2017-11-21 14:36:23 -0800212 if (mAlert.has_name()) {
213 ALOGW("An anomaly (%s) has occurred! Informing incidentd.",
214 mAlert.name().c_str());
215 } else {
216 // TODO: Can construct a name based on the criteria (and/or relay the criteria).
217 ALOGW("An anomaly (nameless) has occurred! Informing incidentd.");
218 }
Bookatzd1fd2422017-11-22 15:21:03 -0800219 informIncidentd();
Yangster-mace2cd6d52017-11-09 20:38:30 -0800220 } else {
221 ALOGW("An anomaly has occurred! (But informing incidentd not requested.)");
222 }
223}
224
225void AnomalyTracker::declareAnomalyIfAlarmExpired(const HashableDimensionKey& dimensionKey,
Bookatzcc5adef2017-11-21 14:36:23 -0800226 const uint64_t& timestampNs) {
Yangster-mace2cd6d52017-11-09 20:38:30 -0800227 auto itr = mAlarms.find(dimensionKey);
228 if (itr == mAlarms.end()) {
229 return;
230 }
231
232 if (itr->second != nullptr &&
Bookatzcc5adef2017-11-21 14:36:23 -0800233 static_cast<uint32_t>(timestampNs / NS_PER_SEC) >= itr->second->timestampSec) {
234 declareAnomaly(timestampNs);
Yangster-mace2cd6d52017-11-09 20:38:30 -0800235 stopAlarm(dimensionKey);
236 }
237}
238
Bookatzcc5adef2017-11-21 14:36:23 -0800239void AnomalyTracker::detectAndDeclareAnomaly(const uint64_t& timestampNs,
Yangster-mace2cd6d52017-11-09 20:38:30 -0800240 const int64_t& currBucketNum,
241 const HashableDimensionKey& key,
242 const int64_t& currentBucketValue) {
243 if (detectAnomaly(currBucketNum, key, currentBucketValue)) {
Bookatzcc5adef2017-11-21 14:36:23 -0800244 declareAnomaly(timestampNs);
Yangster-mace2cd6d52017-11-09 20:38:30 -0800245 }
246}
247
Bookatzcc5adef2017-11-21 14:36:23 -0800248void AnomalyTracker::detectAndDeclareAnomaly(const uint64_t& timestampNs,
Yangster-mace2cd6d52017-11-09 20:38:30 -0800249 const int64_t& currBucketNum,
250 const DimToValMap& currentBucket) {
251 if (detectAnomaly(currBucketNum, currentBucket)) {
Bookatzcc5adef2017-11-21 14:36:23 -0800252 declareAnomaly(timestampNs);
Yangster-mace2cd6d52017-11-09 20:38:30 -0800253 }
254}
255
256void AnomalyTracker::startAlarm(const HashableDimensionKey& dimensionKey,
Bookatzcc5adef2017-11-21 14:36:23 -0800257 const uint64_t& timestampNs) {
258 uint32_t timestampSec = static_cast<uint32_t>(timestampNs / NS_PER_SEC);
259 if (isInRefractoryPeriod(timestampNs)) {
260 VLOG("Skipping setting anomaly alarm since it'd fall in the refractory period");
261 return;
262 }
263
264 sp<const AnomalyAlarm> alarm = new AnomalyAlarm{timestampSec};
Yangster-mace2cd6d52017-11-09 20:38:30 -0800265 mAlarms.insert({dimensionKey, alarm});
266 if (mAnomalyMonitor != nullptr) {
267 mAnomalyMonitor->add(alarm);
268 }
269}
270
271void AnomalyTracker::stopAlarm(const HashableDimensionKey& dimensionKey) {
272 auto itr = mAlarms.find(dimensionKey);
273 if (itr != mAlarms.end()) {
274 mAlarms.erase(dimensionKey);
Bookatzcc5adef2017-11-21 14:36:23 -0800275 if (mAnomalyMonitor != nullptr) {
276 mAnomalyMonitor->remove(itr->second);
277 }
Yangster-mace2cd6d52017-11-09 20:38:30 -0800278 }
279}
280
281void AnomalyTracker::stopAllAlarms() {
282 std::set<HashableDimensionKey> keys;
283 for (auto itr = mAlarms.begin(); itr != mAlarms.end(); ++itr) {
284 keys.insert(itr->first);
285 }
286 for (auto key : keys) {
287 stopAlarm(key);
288 }
289}
290
Bookatzcc5adef2017-11-21 14:36:23 -0800291bool AnomalyTracker::isInRefractoryPeriod(const uint64_t& timestampNs) {
292 return mLastAlarmTimestampNs >= 0 &&
293 timestampNs - mLastAlarmTimestampNs <= mAlert.refractory_period_secs() * NS_PER_SEC;
294}
295
296void AnomalyTracker::informAlarmsFired(const uint64_t& timestampNs,
297 unordered_set<sp<const AnomalyAlarm>, SpHash<AnomalyAlarm>>& firedAlarms) {
298
299 if (firedAlarms.empty() || mAlarms.empty()) return;
300 // Find the intersection of firedAlarms and mAlarms.
301 // The for loop is inefficient, since it loops over all keys, but that's okay since it is very
302 // seldomly called. The alternative would be having AnomalyAlarms store information about the
303 // AnomalyTracker and key, but that's a lot of data overhead to speed up something that is
304 // rarely ever called.
305 unordered_map<HashableDimensionKey, sp<const AnomalyAlarm>> matchedAlarms;
306 for (const auto& kv : mAlarms) {
307 if (firedAlarms.count(kv.second) > 0) {
308 matchedAlarms.insert({kv.first, kv.second});
309 }
310 }
311
312 // Now declare each of these alarms to have fired.
313 for (const auto& kv : matchedAlarms) {
314 declareAnomaly(timestampNs /* TODO: , kv.first */);
315 mAlarms.erase(kv.first);
316 firedAlarms.erase(kv.second); // No one else can also own it, so we're done with it.
317 }
318}
319
Bookatzd1fd2422017-11-22 15:21:03 -0800320void AnomalyTracker::informIncidentd() {
321 VLOG("informIncidentd called.");
322 if (!mAlert.has_incidentd_details()) {
323 ALOGE("Attempted to call incidentd without any incidentd_details.");
324 return;
325 }
326 sp<IIncidentManager> service = interface_cast<IIncidentManager>(
327 defaultServiceManager()->getService(android::String16("incident")));
328 if (service == NULL) {
329 ALOGW("Couldn't get the incident service.");
330 return;
331 }
332
333 IncidentReportArgs incidentReport;
334 const Alert::IncidentdDetails& details = mAlert.incidentd_details();
335 for (int i = 0; i < details.section_size(); i++) {
336 incidentReport.addSection(details.section(i));
337 }
338 // TODO: Pass in mAlert.name() into the addHeader?
339
340 service->reportIncident(incidentReport);
341}
342
Yangster-mace2cd6d52017-11-09 20:38:30 -0800343} // namespace statsd
344} // namespace os
345} // namespace android