blob: 162a34b957f8b20c3a5e4896a80f85c53c79fcdd [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>
Bookatz8fcd09a2017-12-18 13:01:10 -080026#include <statslog.h>
Yangster-mace2cd6d52017-11-09 20:38:30 -080027#include <time.h>
28
29namespace android {
30namespace os {
31namespace statsd {
32
Bookatzcc5adef2017-11-21 14:36:23 -080033// TODO: Separate DurationAnomalyTracker as a separate subclass and let each MetricProducer
34// decide and let which one it wants.
35// TODO: Get rid of bucketNumbers, and return to the original circular array method.
Bookatz8f2f3d82017-12-07 13:53:21 -080036AnomalyTracker::AnomalyTracker(const Alert& alert, const ConfigKey& configKey)
Yangster-mace2cd6d52017-11-09 20:38:30 -080037 : mAlert(alert),
Bookatz8f2f3d82017-12-07 13:53:21 -080038 mConfigKey(configKey),
Bookatzcc5adef2017-11-21 14:36:23 -080039 mNumOfPastBuckets(mAlert.number_of_buckets() - 1) {
Yangster-mace2cd6d52017-11-09 20:38:30 -080040 VLOG("AnomalyTracker() called");
41 if (mAlert.number_of_buckets() <= 0) {
Bookatzcc5adef2017-11-21 14:36:23 -080042 ALOGE("Cannot create AnomalyTracker with %lld buckets",
Yangster-mace2cd6d52017-11-09 20:38:30 -080043 (long long)mAlert.number_of_buckets());
44 return;
45 }
Yangster-mace2cd6d52017-11-09 20:38:30 -080046 if (!mAlert.has_trigger_if_sum_gt()) {
Bookatzcc5adef2017-11-21 14:36:23 -080047 ALOGE("Cannot create AnomalyTracker without threshold");
Yangster-mace2cd6d52017-11-09 20:38:30 -080048 return;
49 }
Bookatzcc5adef2017-11-21 14:36:23 -080050 resetStorage(); // initialization
Yangster-mace2cd6d52017-11-09 20:38:30 -080051}
52
53AnomalyTracker::~AnomalyTracker() {
54 VLOG("~AnomalyTracker() called");
55 stopAllAlarms();
56}
57
Bookatzcc5adef2017-11-21 14:36:23 -080058void AnomalyTracker::resetStorage() {
59 VLOG("resetStorage() called.");
Yangster-mace2cd6d52017-11-09 20:38:30 -080060 mPastBuckets.clear();
61 // Excludes the current bucket.
Bookatzcc5adef2017-11-21 14:36:23 -080062 mPastBuckets.resize(mNumOfPastBuckets);
Yangster-mace2cd6d52017-11-09 20:38:30 -080063 mSumOverPastBuckets.clear();
Bookatzcc5adef2017-11-21 14:36:23 -080064
65 if (!mAlarms.empty()) VLOG("AnomalyTracker.resetStorage() called but mAlarms is NOT empty!");
Yangster-mace2cd6d52017-11-09 20:38:30 -080066}
67
68size_t AnomalyTracker::index(int64_t bucketNum) const {
Bookatzcc5adef2017-11-21 14:36:23 -080069 return bucketNum % mNumOfPastBuckets;
Yangster-mace2cd6d52017-11-09 20:38:30 -080070}
71
72void AnomalyTracker::flushPastBuckets(const int64_t& latestPastBucketNum) {
73 VLOG("addPastBucket() called.");
Bookatzcc5adef2017-11-21 14:36:23 -080074 if (latestPastBucketNum <= mMostRecentBucketNum - mNumOfPastBuckets) {
Yangster-mace2cd6d52017-11-09 20:38:30 -080075 ALOGE("Cannot add a past bucket %lld units in past", (long long)latestPastBucketNum);
76 return;
77 }
78
79 // The past packets are ancient. Empty out old mPastBuckets[i] values and reset
80 // mSumOverPastBuckets.
Bookatzcc5adef2017-11-21 14:36:23 -080081 if (latestPastBucketNum - mMostRecentBucketNum >= mNumOfPastBuckets) {
Yangster-mace2cd6d52017-11-09 20:38:30 -080082 mPastBuckets.clear();
Bookatzcc5adef2017-11-21 14:36:23 -080083 mPastBuckets.resize(mNumOfPastBuckets);
Yangster-mace2cd6d52017-11-09 20:38:30 -080084 mSumOverPastBuckets.clear();
85 } else {
Bookatzcc5adef2017-11-21 14:36:23 -080086 for (int64_t i = std::max(0LL, (long long)(mMostRecentBucketNum - mNumOfPastBuckets + 1));
87 i <= latestPastBucketNum - mNumOfPastBuckets; i++) {
Yangster-mace2cd6d52017-11-09 20:38:30 -080088 const int idx = index(i);
89 subtractBucketFromSum(mPastBuckets[idx]);
90 mPastBuckets[idx] = nullptr; // release (but not clear) the old bucket.
91 }
92 }
93
94 // It is an update operation.
95 if (latestPastBucketNum <= mMostRecentBucketNum &&
Bookatzcc5adef2017-11-21 14:36:23 -080096 latestPastBucketNum > mMostRecentBucketNum - mNumOfPastBuckets) {
Yangster-mace2cd6d52017-11-09 20:38:30 -080097 subtractBucketFromSum(mPastBuckets[index(latestPastBucketNum)]);
98 }
99}
100
101void AnomalyTracker::addPastBucket(const HashableDimensionKey& key, const int64_t& bucketValue,
102 const int64_t& bucketNum) {
103 flushPastBuckets(bucketNum);
104
105 auto& bucket = mPastBuckets[index(bucketNum)];
106 if (bucket == nullptr) {
107 bucket = std::make_shared<DimToValMap>();
108 }
109 bucket->insert({key, bucketValue});
110 addBucketToSum(bucket);
111 mMostRecentBucketNum = std::max(mMostRecentBucketNum, bucketNum);
112}
113
114void AnomalyTracker::addPastBucket(std::shared_ptr<DimToValMap> bucketValues,
115 const int64_t& bucketNum) {
116 VLOG("addPastBucket() called.");
117 flushPastBuckets(bucketNum);
118 // Replace the oldest bucket with the new bucket we are adding.
119 mPastBuckets[index(bucketNum)] = bucketValues;
120 addBucketToSum(bucketValues);
121 mMostRecentBucketNum = std::max(mMostRecentBucketNum, bucketNum);
122}
123
124void AnomalyTracker::subtractBucketFromSum(const shared_ptr<DimToValMap>& bucket) {
125 if (bucket == nullptr) {
126 return;
127 }
128 // For each dimension present in the bucket, subtract its value from its corresponding sum.
129 for (const auto& keyValuePair : *bucket) {
130 auto itr = mSumOverPastBuckets.find(keyValuePair.first);
131 if (itr == mSumOverPastBuckets.end()) {
132 continue;
133 }
134 itr->second -= keyValuePair.second;
135 // TODO: No need to look up the object twice like this. Use a var.
136 if (itr->second == 0) {
137 mSumOverPastBuckets.erase(itr);
138 }
139 }
140}
141
142void AnomalyTracker::addBucketToSum(const shared_ptr<DimToValMap>& bucket) {
143 if (bucket == nullptr) {
144 return;
145 }
146 // For each dimension present in the bucket, add its value to its corresponding sum.
147 for (const auto& keyValuePair : *bucket) {
148 mSumOverPastBuckets[keyValuePair.first] += keyValuePair.second;
149 }
150}
151
152int64_t AnomalyTracker::getPastBucketValue(const HashableDimensionKey& key,
153 const int64_t& bucketNum) const {
154 const auto& bucket = mPastBuckets[index(bucketNum)];
155 if (bucket == nullptr) {
156 return 0;
157 }
158 const auto& itr = bucket->find(key);
159 return itr == bucket->end() ? 0 : itr->second;
160}
161
162int64_t AnomalyTracker::getSumOverPastBuckets(const HashableDimensionKey& key) const {
163 const auto& itr = mSumOverPastBuckets.find(key);
164 if (itr != mSumOverPastBuckets.end()) {
165 return itr->second;
166 }
167 return 0;
168}
169
170bool AnomalyTracker::detectAnomaly(const int64_t& currentBucketNum,
171 const DimToValMap& currentBucket) {
172 if (currentBucketNum > mMostRecentBucketNum + 1) {
173 addPastBucket(nullptr, currentBucketNum - 1);
174 }
175 for (auto itr = currentBucket.begin(); itr != currentBucket.end(); itr++) {
176 if (itr->second + getSumOverPastBuckets(itr->first) > mAlert.trigger_if_sum_gt()) {
177 return true;
178 }
179 }
180 // In theory, we also need to check the dimsions not in the current bucket. In single-thread
181 // mode, usually we could avoid the following loops.
182 for (auto itr = mSumOverPastBuckets.begin(); itr != mSumOverPastBuckets.end(); itr++) {
183 if (itr->second > mAlert.trigger_if_sum_gt()) {
184 return true;
185 }
186 }
187 return false;
188}
189
190bool AnomalyTracker::detectAnomaly(const int64_t& currentBucketNum, const HashableDimensionKey& key,
191 const int64_t& currentBucketValue) {
192 if (currentBucketNum > mMostRecentBucketNum + 1) {
193 addPastBucket(key, 0, currentBucketNum - 1);
194 }
Bookatzcc5adef2017-11-21 14:36:23 -0800195 return mAlert.has_trigger_if_sum_gt()
196 && getSumOverPastBuckets(key) + currentBucketValue > mAlert.trigger_if_sum_gt();
Yangster-mace2cd6d52017-11-09 20:38:30 -0800197}
198
Bookatzcc5adef2017-11-21 14:36:23 -0800199void AnomalyTracker::declareAnomaly(const uint64_t& timestampNs) {
200 // TODO: This should also take in the const HashableDimensionKey& key, to pass
201 // more details to incidentd and to make mRefractoryPeriodEndsSec key-specific.
202 // TODO: Why receive timestamp? RefractoryPeriod should always be based on real time right now.
203 if (isInRefractoryPeriod(timestampNs)) {
204 VLOG("Skipping anomaly declaration since within refractory period");
Yangster-mace2cd6d52017-11-09 20:38:30 -0800205 return;
206 }
207 // TODO(guardrail): Consider guarding against too short refractory periods.
Bookatzcc5adef2017-11-21 14:36:23 -0800208 mLastAlarmTimestampNs = timestampNs;
209
210
211 // TODO: If we had access to the bucket_size_millis, consider calling resetStorage()
212 // if (mAlert.refractory_period_secs() > mNumOfPastBuckets * bucketSizeNs) { resetStorage(); }
Yangster-mace2cd6d52017-11-09 20:38:30 -0800213
214 if (mAlert.has_incidentd_details()) {
Bookatzcc5adef2017-11-21 14:36:23 -0800215 if (mAlert.has_name()) {
216 ALOGW("An anomaly (%s) has occurred! Informing incidentd.",
217 mAlert.name().c_str());
218 } else {
219 // TODO: Can construct a name based on the criteria (and/or relay the criteria).
220 ALOGW("An anomaly (nameless) has occurred! Informing incidentd.");
221 }
Bookatzd1fd2422017-11-22 15:21:03 -0800222 informIncidentd();
Yangster-mace2cd6d52017-11-09 20:38:30 -0800223 } else {
224 ALOGW("An anomaly has occurred! (But informing incidentd not requested.)");
225 }
Bookatz8f2f3d82017-12-07 13:53:21 -0800226
227 StatsdStats::getInstance().noteAnomalyDeclared(mConfigKey, mAlert.name());
Bookatz8fcd09a2017-12-18 13:01:10 -0800228
229 android::util::stats_write(android::util::ANOMALY_DETECTED, mConfigKey.GetUid(),
230 mConfigKey.GetName().c_str(), mAlert.name().c_str());
Yangster-mace2cd6d52017-11-09 20:38:30 -0800231}
232
233void AnomalyTracker::declareAnomalyIfAlarmExpired(const HashableDimensionKey& dimensionKey,
Bookatzcc5adef2017-11-21 14:36:23 -0800234 const uint64_t& timestampNs) {
Yangster-mace2cd6d52017-11-09 20:38:30 -0800235 auto itr = mAlarms.find(dimensionKey);
236 if (itr == mAlarms.end()) {
237 return;
238 }
239
240 if (itr->second != nullptr &&
Bookatzcc5adef2017-11-21 14:36:23 -0800241 static_cast<uint32_t>(timestampNs / NS_PER_SEC) >= itr->second->timestampSec) {
242 declareAnomaly(timestampNs);
Yangster-mace2cd6d52017-11-09 20:38:30 -0800243 stopAlarm(dimensionKey);
244 }
245}
246
Bookatzcc5adef2017-11-21 14:36:23 -0800247void AnomalyTracker::detectAndDeclareAnomaly(const uint64_t& timestampNs,
Yangster-mace2cd6d52017-11-09 20:38:30 -0800248 const int64_t& currBucketNum,
249 const HashableDimensionKey& key,
250 const int64_t& currentBucketValue) {
251 if (detectAnomaly(currBucketNum, key, currentBucketValue)) {
Bookatzcc5adef2017-11-21 14:36:23 -0800252 declareAnomaly(timestampNs);
Yangster-mace2cd6d52017-11-09 20:38:30 -0800253 }
254}
255
Bookatzcc5adef2017-11-21 14:36:23 -0800256void AnomalyTracker::detectAndDeclareAnomaly(const uint64_t& timestampNs,
Yangster-mace2cd6d52017-11-09 20:38:30 -0800257 const int64_t& currBucketNum,
258 const DimToValMap& currentBucket) {
259 if (detectAnomaly(currBucketNum, currentBucket)) {
Bookatzcc5adef2017-11-21 14:36:23 -0800260 declareAnomaly(timestampNs);
Yangster-mace2cd6d52017-11-09 20:38:30 -0800261 }
262}
263
264void AnomalyTracker::startAlarm(const HashableDimensionKey& dimensionKey,
Bookatzcc5adef2017-11-21 14:36:23 -0800265 const uint64_t& timestampNs) {
266 uint32_t timestampSec = static_cast<uint32_t>(timestampNs / NS_PER_SEC);
267 if (isInRefractoryPeriod(timestampNs)) {
268 VLOG("Skipping setting anomaly alarm since it'd fall in the refractory period");
269 return;
270 }
271
272 sp<const AnomalyAlarm> alarm = new AnomalyAlarm{timestampSec};
Yangster-mace2cd6d52017-11-09 20:38:30 -0800273 mAlarms.insert({dimensionKey, alarm});
274 if (mAnomalyMonitor != nullptr) {
275 mAnomalyMonitor->add(alarm);
276 }
277}
278
279void AnomalyTracker::stopAlarm(const HashableDimensionKey& dimensionKey) {
280 auto itr = mAlarms.find(dimensionKey);
281 if (itr != mAlarms.end()) {
282 mAlarms.erase(dimensionKey);
Bookatzcc5adef2017-11-21 14:36:23 -0800283 if (mAnomalyMonitor != nullptr) {
284 mAnomalyMonitor->remove(itr->second);
285 }
Yangster-mace2cd6d52017-11-09 20:38:30 -0800286 }
287}
288
289void AnomalyTracker::stopAllAlarms() {
290 std::set<HashableDimensionKey> keys;
291 for (auto itr = mAlarms.begin(); itr != mAlarms.end(); ++itr) {
292 keys.insert(itr->first);
293 }
294 for (auto key : keys) {
295 stopAlarm(key);
296 }
297}
298
Bookatzcc5adef2017-11-21 14:36:23 -0800299bool AnomalyTracker::isInRefractoryPeriod(const uint64_t& timestampNs) {
300 return mLastAlarmTimestampNs >= 0 &&
301 timestampNs - mLastAlarmTimestampNs <= mAlert.refractory_period_secs() * NS_PER_SEC;
302}
303
304void AnomalyTracker::informAlarmsFired(const uint64_t& timestampNs,
305 unordered_set<sp<const AnomalyAlarm>, SpHash<AnomalyAlarm>>& firedAlarms) {
306
307 if (firedAlarms.empty() || mAlarms.empty()) return;
308 // Find the intersection of firedAlarms and mAlarms.
309 // The for loop is inefficient, since it loops over all keys, but that's okay since it is very
310 // seldomly called. The alternative would be having AnomalyAlarms store information about the
311 // AnomalyTracker and key, but that's a lot of data overhead to speed up something that is
312 // rarely ever called.
313 unordered_map<HashableDimensionKey, sp<const AnomalyAlarm>> matchedAlarms;
314 for (const auto& kv : mAlarms) {
315 if (firedAlarms.count(kv.second) > 0) {
316 matchedAlarms.insert({kv.first, kv.second});
317 }
318 }
319
320 // Now declare each of these alarms to have fired.
321 for (const auto& kv : matchedAlarms) {
322 declareAnomaly(timestampNs /* TODO: , kv.first */);
323 mAlarms.erase(kv.first);
Yao Chenf09569f2017-12-13 17:00:51 -0800324 firedAlarms.erase(kv.second); // No one else can also own it, so we're done with it.
Bookatzcc5adef2017-11-21 14:36:23 -0800325 }
326}
327
Bookatzd1fd2422017-11-22 15:21:03 -0800328void AnomalyTracker::informIncidentd() {
329 VLOG("informIncidentd called.");
330 if (!mAlert.has_incidentd_details()) {
331 ALOGE("Attempted to call incidentd without any incidentd_details.");
332 return;
333 }
334 sp<IIncidentManager> service = interface_cast<IIncidentManager>(
335 defaultServiceManager()->getService(android::String16("incident")));
336 if (service == NULL) {
337 ALOGW("Couldn't get the incident service.");
338 return;
339 }
340
341 IncidentReportArgs incidentReport;
342 const Alert::IncidentdDetails& details = mAlert.incidentd_details();
343 for (int i = 0; i < details.section_size(); i++) {
344 incidentReport.addSection(details.section(i));
345 }
346 // TODO: Pass in mAlert.name() into the addHeader?
347
348 service->reportIncident(incidentReport);
349}
350
Yangster-mace2cd6d52017-11-09 20:38:30 -0800351} // namespace statsd
352} // namespace os
353} // namespace android