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