blob: f8a94136264853439d0e2b85e36d879c96232e38 [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: Get rid of bucketNumbers, and return to the original circular array method.
Bookatz8f2f3d82017-12-07 13:53:21 -080034AnomalyTracker::AnomalyTracker(const Alert& alert, const ConfigKey& configKey)
Yangster-mace2cd6d52017-11-09 20:38:30 -080035 : mAlert(alert),
Bookatz8f2f3d82017-12-07 13:53:21 -080036 mConfigKey(configKey),
Bookatzcc5adef2017-11-21 14:36:23 -080037 mNumOfPastBuckets(mAlert.number_of_buckets() - 1) {
Yangster-mace2cd6d52017-11-09 20:38:30 -080038 VLOG("AnomalyTracker() called");
39 if (mAlert.number_of_buckets() <= 0) {
Bookatzcc5adef2017-11-21 14:36:23 -080040 ALOGE("Cannot create AnomalyTracker with %lld buckets",
Yangster-mace2cd6d52017-11-09 20:38:30 -080041 (long long)mAlert.number_of_buckets());
42 return;
43 }
Yangster-mace2cd6d52017-11-09 20:38:30 -080044 if (!mAlert.has_trigger_if_sum_gt()) {
Bookatzcc5adef2017-11-21 14:36:23 -080045 ALOGE("Cannot create AnomalyTracker without threshold");
Yangster-mace2cd6d52017-11-09 20:38:30 -080046 return;
47 }
Bookatzcc5adef2017-11-21 14:36:23 -080048 resetStorage(); // initialization
Yangster-mace2cd6d52017-11-09 20:38:30 -080049}
50
51AnomalyTracker::~AnomalyTracker() {
52 VLOG("~AnomalyTracker() called");
Yangster-mace2cd6d52017-11-09 20:38:30 -080053}
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();
Yangster-mace2cd6d52017-11-09 20:38:30 -080061}
62
63size_t AnomalyTracker::index(int64_t bucketNum) const {
Bookatzcc5adef2017-11-21 14:36:23 -080064 return bucketNum % mNumOfPastBuckets;
Yangster-mace2cd6d52017-11-09 20:38:30 -080065}
66
67void AnomalyTracker::flushPastBuckets(const int64_t& latestPastBucketNum) {
68 VLOG("addPastBucket() called.");
Bookatzcc5adef2017-11-21 14:36:23 -080069 if (latestPastBucketNum <= mMostRecentBucketNum - mNumOfPastBuckets) {
Yangster-mace2cd6d52017-11-09 20:38:30 -080070 ALOGE("Cannot add a past bucket %lld units in past", (long long)latestPastBucketNum);
71 return;
72 }
73
74 // The past packets are ancient. Empty out old mPastBuckets[i] values and reset
75 // mSumOverPastBuckets.
Bookatzcc5adef2017-11-21 14:36:23 -080076 if (latestPastBucketNum - mMostRecentBucketNum >= mNumOfPastBuckets) {
Yangster-mace2cd6d52017-11-09 20:38:30 -080077 mPastBuckets.clear();
Bookatzcc5adef2017-11-21 14:36:23 -080078 mPastBuckets.resize(mNumOfPastBuckets);
Yangster-mace2cd6d52017-11-09 20:38:30 -080079 mSumOverPastBuckets.clear();
80 } else {
Bookatzcc5adef2017-11-21 14:36:23 -080081 for (int64_t i = std::max(0LL, (long long)(mMostRecentBucketNum - mNumOfPastBuckets + 1));
82 i <= latestPastBucketNum - mNumOfPastBuckets; i++) {
Yangster-mace2cd6d52017-11-09 20:38:30 -080083 const int idx = index(i);
84 subtractBucketFromSum(mPastBuckets[idx]);
85 mPastBuckets[idx] = nullptr; // release (but not clear) the old bucket.
86 }
87 }
88
89 // It is an update operation.
90 if (latestPastBucketNum <= mMostRecentBucketNum &&
Bookatzcc5adef2017-11-21 14:36:23 -080091 latestPastBucketNum > mMostRecentBucketNum - mNumOfPastBuckets) {
Yangster-mace2cd6d52017-11-09 20:38:30 -080092 subtractBucketFromSum(mPastBuckets[index(latestPastBucketNum)]);
93 }
94}
95
96void AnomalyTracker::addPastBucket(const HashableDimensionKey& key, const int64_t& bucketValue,
97 const int64_t& bucketNum) {
98 flushPastBuckets(bucketNum);
99
100 auto& bucket = mPastBuckets[index(bucketNum)];
101 if (bucket == nullptr) {
102 bucket = std::make_shared<DimToValMap>();
103 }
104 bucket->insert({key, bucketValue});
105 addBucketToSum(bucket);
106 mMostRecentBucketNum = std::max(mMostRecentBucketNum, bucketNum);
107}
108
109void AnomalyTracker::addPastBucket(std::shared_ptr<DimToValMap> bucketValues,
110 const int64_t& bucketNum) {
111 VLOG("addPastBucket() called.");
112 flushPastBuckets(bucketNum);
113 // Replace the oldest bucket with the new bucket we are adding.
114 mPastBuckets[index(bucketNum)] = bucketValues;
115 addBucketToSum(bucketValues);
116 mMostRecentBucketNum = std::max(mMostRecentBucketNum, bucketNum);
117}
118
119void AnomalyTracker::subtractBucketFromSum(const shared_ptr<DimToValMap>& bucket) {
120 if (bucket == nullptr) {
121 return;
122 }
123 // For each dimension present in the bucket, subtract its value from its corresponding sum.
124 for (const auto& keyValuePair : *bucket) {
125 auto itr = mSumOverPastBuckets.find(keyValuePair.first);
126 if (itr == mSumOverPastBuckets.end()) {
127 continue;
128 }
129 itr->second -= keyValuePair.second;
130 // TODO: No need to look up the object twice like this. Use a var.
131 if (itr->second == 0) {
132 mSumOverPastBuckets.erase(itr);
133 }
134 }
135}
136
137void AnomalyTracker::addBucketToSum(const shared_ptr<DimToValMap>& bucket) {
138 if (bucket == nullptr) {
139 return;
140 }
141 // For each dimension present in the bucket, add its value to its corresponding sum.
142 for (const auto& keyValuePair : *bucket) {
143 mSumOverPastBuckets[keyValuePair.first] += keyValuePair.second;
144 }
145}
146
147int64_t AnomalyTracker::getPastBucketValue(const HashableDimensionKey& key,
148 const int64_t& bucketNum) const {
149 const auto& bucket = mPastBuckets[index(bucketNum)];
150 if (bucket == nullptr) {
151 return 0;
152 }
153 const auto& itr = bucket->find(key);
154 return itr == bucket->end() ? 0 : itr->second;
155}
156
157int64_t AnomalyTracker::getSumOverPastBuckets(const HashableDimensionKey& key) const {
158 const auto& itr = mSumOverPastBuckets.find(key);
159 if (itr != mSumOverPastBuckets.end()) {
160 return itr->second;
161 }
162 return 0;
163}
164
165bool AnomalyTracker::detectAnomaly(const int64_t& currentBucketNum,
166 const DimToValMap& currentBucket) {
167 if (currentBucketNum > mMostRecentBucketNum + 1) {
168 addPastBucket(nullptr, currentBucketNum - 1);
169 }
170 for (auto itr = currentBucket.begin(); itr != currentBucket.end(); itr++) {
171 if (itr->second + getSumOverPastBuckets(itr->first) > mAlert.trigger_if_sum_gt()) {
172 return true;
173 }
174 }
175 // In theory, we also need to check the dimsions not in the current bucket. In single-thread
176 // mode, usually we could avoid the following loops.
177 for (auto itr = mSumOverPastBuckets.begin(); itr != mSumOverPastBuckets.end(); itr++) {
178 if (itr->second > mAlert.trigger_if_sum_gt()) {
179 return true;
180 }
181 }
182 return false;
183}
184
185bool AnomalyTracker::detectAnomaly(const int64_t& currentBucketNum, const HashableDimensionKey& key,
186 const int64_t& currentBucketValue) {
187 if (currentBucketNum > mMostRecentBucketNum + 1) {
188 addPastBucket(key, 0, currentBucketNum - 1);
189 }
Bookatzcc5adef2017-11-21 14:36:23 -0800190 return mAlert.has_trigger_if_sum_gt()
191 && getSumOverPastBuckets(key) + currentBucketValue > mAlert.trigger_if_sum_gt();
Yangster-mace2cd6d52017-11-09 20:38:30 -0800192}
193
Bookatzcc5adef2017-11-21 14:36:23 -0800194void AnomalyTracker::declareAnomaly(const uint64_t& timestampNs) {
195 // TODO: This should also take in the const HashableDimensionKey& key, to pass
196 // more details to incidentd and to make mRefractoryPeriodEndsSec key-specific.
197 // TODO: Why receive timestamp? RefractoryPeriod should always be based on real time right now.
198 if (isInRefractoryPeriod(timestampNs)) {
199 VLOG("Skipping anomaly declaration since within refractory period");
Yangster-mace2cd6d52017-11-09 20:38:30 -0800200 return;
201 }
202 // TODO(guardrail): Consider guarding against too short refractory periods.
Bookatz857aaa52017-12-19 15:29:06 -0800203 mLastAnomalyTimestampNs = timestampNs;
Bookatzcc5adef2017-11-21 14:36:23 -0800204
205 // TODO: If we had access to the bucket_size_millis, consider calling resetStorage()
206 // if (mAlert.refractory_period_secs() > mNumOfPastBuckets * bucketSizeNs) { resetStorage(); }
Yangster-mace2cd6d52017-11-09 20:38:30 -0800207
208 if (mAlert.has_incidentd_details()) {
Bookatzcc5adef2017-11-21 14:36:23 -0800209 if (mAlert.has_name()) {
Bookatz857aaa52017-12-19 15:29:06 -0800210 ALOGI("An anomaly (%s) has occurred! Informing incidentd.",
Bookatzcc5adef2017-11-21 14:36:23 -0800211 mAlert.name().c_str());
212 } else {
213 // TODO: Can construct a name based on the criteria (and/or relay the criteria).
Bookatz857aaa52017-12-19 15:29:06 -0800214 ALOGI("An anomaly (nameless) has occurred! Informing incidentd.");
Bookatzcc5adef2017-11-21 14:36:23 -0800215 }
Bookatzd1fd2422017-11-22 15:21:03 -0800216 informIncidentd();
Yangster-mace2cd6d52017-11-09 20:38:30 -0800217 } else {
Bookatz857aaa52017-12-19 15:29:06 -0800218 ALOGI("An anomaly has occurred! (But informing incidentd not requested.)");
Yangster-mace2cd6d52017-11-09 20:38:30 -0800219 }
Bookatz8f2f3d82017-12-07 13:53:21 -0800220
221 StatsdStats::getInstance().noteAnomalyDeclared(mConfigKey, mAlert.name());
Bookatz8fcd09a2017-12-18 13:01:10 -0800222
223 android::util::stats_write(android::util::ANOMALY_DETECTED, mConfigKey.GetUid(),
224 mConfigKey.GetName().c_str(), mAlert.name().c_str());
Yangster-mace2cd6d52017-11-09 20:38:30 -0800225}
226
Bookatzcc5adef2017-11-21 14:36:23 -0800227void AnomalyTracker::detectAndDeclareAnomaly(const uint64_t& timestampNs,
Yangster-mace2cd6d52017-11-09 20:38:30 -0800228 const int64_t& currBucketNum,
229 const HashableDimensionKey& key,
230 const int64_t& currentBucketValue) {
231 if (detectAnomaly(currBucketNum, key, currentBucketValue)) {
Bookatzcc5adef2017-11-21 14:36:23 -0800232 declareAnomaly(timestampNs);
Yangster-mace2cd6d52017-11-09 20:38:30 -0800233 }
234}
235
Bookatzcc5adef2017-11-21 14:36:23 -0800236void AnomalyTracker::detectAndDeclareAnomaly(const uint64_t& timestampNs,
Yangster-mace2cd6d52017-11-09 20:38:30 -0800237 const int64_t& currBucketNum,
238 const DimToValMap& currentBucket) {
239 if (detectAnomaly(currBucketNum, currentBucket)) {
Bookatzcc5adef2017-11-21 14:36:23 -0800240 declareAnomaly(timestampNs);
Yangster-mace2cd6d52017-11-09 20:38:30 -0800241 }
242}
243
Bookatz857aaa52017-12-19 15:29:06 -0800244bool AnomalyTracker::isInRefractoryPeriod(const uint64_t& timestampNs) const {
245 return mLastAnomalyTimestampNs >= 0 &&
246 timestampNs - mLastAnomalyTimestampNs <= mAlert.refractory_period_secs() * NS_PER_SEC;
Bookatzcc5adef2017-11-21 14:36:23 -0800247}
248
Bookatzd1fd2422017-11-22 15:21:03 -0800249void AnomalyTracker::informIncidentd() {
250 VLOG("informIncidentd called.");
251 if (!mAlert.has_incidentd_details()) {
252 ALOGE("Attempted to call incidentd without any incidentd_details.");
253 return;
254 }
255 sp<IIncidentManager> service = interface_cast<IIncidentManager>(
256 defaultServiceManager()->getService(android::String16("incident")));
257 if (service == NULL) {
258 ALOGW("Couldn't get the incident service.");
259 return;
260 }
261
262 IncidentReportArgs incidentReport;
263 const Alert::IncidentdDetails& details = mAlert.incidentd_details();
264 for (int i = 0; i < details.section_size(); i++) {
265 incidentReport.addSection(details.section(i));
266 }
267 // TODO: Pass in mAlert.name() into the addHeader?
268
269 service->reportIncident(incidentReport);
270}
271
Yangster-mace2cd6d52017-11-09 20:38:30 -0800272} // namespace statsd
273} // namespace os
274} // namespace android