blob: 0614e42857e46908dd45c982102b3a26f26364c8 [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"
Primiano Tuccie4d44912018-01-10 12:14:50 +000021#include "external/Perfetto.h"
Yi Jin437aa6e2018-01-10 11:34:26 -080022#include "frameworks/base/libs/incident/proto/android/os/header.pb.h"
Yi Jinafb36062018-01-31 19:14:25 -080023#include "guardrail/StatsdStats.h"
24#include "subscriber/IncidentdReporter.h"
Bookatzc6977972018-01-16 16:55:05 -080025#include "subscriber/SubscriberReporter.h"
Yangster-mace2cd6d52017-11-09 20:38:30 -080026
Bookatz8fcd09a2017-12-18 13:01:10 -080027#include <statslog.h>
Yangster-mace2cd6d52017-11-09 20:38:30 -080028#include <time.h>
29
30namespace android {
31namespace os {
32namespace statsd {
33
Bookatzcc5adef2017-11-21 14:36:23 -080034// 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)
Yi Jinafb36062018-01-31 19:14:25 -080036 : mAlert(alert), mConfigKey(configKey), mNumOfPastBuckets(mAlert.num_buckets() - 1) {
Yangster-mace2cd6d52017-11-09 20:38:30 -080037 VLOG("AnomalyTracker() called");
Yangster-maca7fb12d2018-01-03 17:17:20 -080038 if (mAlert.num_buckets() <= 0) {
Yi Jinafb36062018-01-31 19:14:25 -080039 ALOGE("Cannot create AnomalyTracker with %lld buckets", (long long)mAlert.num_buckets());
Yangster-mace2cd6d52017-11-09 20:38:30 -080040 return;
41 }
Yangster-mace2cd6d52017-11-09 20:38:30 -080042 if (!mAlert.has_trigger_if_sum_gt()) {
Bookatzcc5adef2017-11-21 14:36:23 -080043 ALOGE("Cannot create AnomalyTracker without threshold");
Yangster-mace2cd6d52017-11-09 20:38:30 -080044 return;
45 }
Yi Jinafb36062018-01-31 19:14:25 -080046 resetStorage(); // initialization
Yangster-mace2cd6d52017-11-09 20:38:30 -080047}
48
49AnomalyTracker::~AnomalyTracker() {
50 VLOG("~AnomalyTracker() called");
Yangster-mace2cd6d52017-11-09 20:38:30 -080051}
52
Bookatzcc5adef2017-11-21 14:36:23 -080053void AnomalyTracker::resetStorage() {
54 VLOG("resetStorage() called.");
Yangster-mace2cd6d52017-11-09 20:38:30 -080055 mPastBuckets.clear();
56 // Excludes the current bucket.
Bookatzcc5adef2017-11-21 14:36:23 -080057 mPastBuckets.resize(mNumOfPastBuckets);
Yangster-mace2cd6d52017-11-09 20:38:30 -080058 mSumOverPastBuckets.clear();
Yangster-mace2cd6d52017-11-09 20:38:30 -080059}
60
61size_t AnomalyTracker::index(int64_t bucketNum) const {
Bookatzcc5adef2017-11-21 14:36:23 -080062 return bucketNum % mNumOfPastBuckets;
Yangster-mace2cd6d52017-11-09 20:38:30 -080063}
64
65void AnomalyTracker::flushPastBuckets(const int64_t& latestPastBucketNum) {
66 VLOG("addPastBucket() called.");
Bookatzcc5adef2017-11-21 14:36:23 -080067 if (latestPastBucketNum <= mMostRecentBucketNum - mNumOfPastBuckets) {
Yangster-mace2cd6d52017-11-09 20:38:30 -080068 ALOGE("Cannot add a past bucket %lld units in past", (long long)latestPastBucketNum);
69 return;
70 }
71
72 // The past packets are ancient. Empty out old mPastBuckets[i] values and reset
73 // mSumOverPastBuckets.
Bookatzcc5adef2017-11-21 14:36:23 -080074 if (latestPastBucketNum - mMostRecentBucketNum >= mNumOfPastBuckets) {
Yangster-mace2cd6d52017-11-09 20:38:30 -080075 mPastBuckets.clear();
Bookatzcc5adef2017-11-21 14:36:23 -080076 mPastBuckets.resize(mNumOfPastBuckets);
Yangster-mace2cd6d52017-11-09 20:38:30 -080077 mSumOverPastBuckets.clear();
78 } else {
Bookatzcc5adef2017-11-21 14:36:23 -080079 for (int64_t i = std::max(0LL, (long long)(mMostRecentBucketNum - mNumOfPastBuckets + 1));
80 i <= latestPastBucketNum - mNumOfPastBuckets; i++) {
Yangster-mace2cd6d52017-11-09 20:38:30 -080081 const int idx = index(i);
82 subtractBucketFromSum(mPastBuckets[idx]);
83 mPastBuckets[idx] = nullptr; // release (but not clear) the old bucket.
84 }
85 }
86
87 // It is an update operation.
88 if (latestPastBucketNum <= mMostRecentBucketNum &&
Bookatzcc5adef2017-11-21 14:36:23 -080089 latestPastBucketNum > mMostRecentBucketNum - mNumOfPastBuckets) {
Yangster-mace2cd6d52017-11-09 20:38:30 -080090 subtractBucketFromSum(mPastBuckets[index(latestPastBucketNum)]);
91 }
92}
93
Yangster-mac93694462018-01-22 20:49:31 -080094void AnomalyTracker::addPastBucket(const MetricDimensionKey& key, const int64_t& bucketValue,
Yangster-mace2cd6d52017-11-09 20:38:30 -080095 const int64_t& bucketNum) {
David Chenc189bdcb2018-02-09 16:09:26 -080096 if (mNumOfPastBuckets == 0) {
97 return;
98 }
Yangster-mace2cd6d52017-11-09 20:38:30 -080099 flushPastBuckets(bucketNum);
100
101 auto& bucket = mPastBuckets[index(bucketNum)];
102 if (bucket == nullptr) {
103 bucket = std::make_shared<DimToValMap>();
104 }
105 bucket->insert({key, bucketValue});
106 addBucketToSum(bucket);
107 mMostRecentBucketNum = std::max(mMostRecentBucketNum, bucketNum);
108}
109
110void AnomalyTracker::addPastBucket(std::shared_ptr<DimToValMap> bucketValues,
111 const int64_t& bucketNum) {
112 VLOG("addPastBucket() called.");
David Chenc189bdcb2018-02-09 16:09:26 -0800113 if (mNumOfPastBuckets == 0) {
114 return;
115 }
Yangster-mace2cd6d52017-11-09 20:38:30 -0800116 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
Yangster-mac93694462018-01-22 20:49:31 -0800151int64_t AnomalyTracker::getPastBucketValue(const MetricDimensionKey& key,
Yangster-mace2cd6d52017-11-09 20:38:30 -0800152 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
Yangster-mac93694462018-01-22 20:49:31 -0800161int64_t AnomalyTracker::getSumOverPastBuckets(const MetricDimensionKey& key) const {
Yangster-mace2cd6d52017-11-09 20:38:30 -0800162 const auto& itr = mSumOverPastBuckets.find(key);
163 if (itr != mSumOverPastBuckets.end()) {
164 return itr->second;
165 }
166 return 0;
167}
168
Yangster-mac93694462018-01-22 20:49:31 -0800169bool AnomalyTracker::detectAnomaly(const int64_t& currentBucketNum, const MetricDimensionKey& key,
Yangster-mace2cd6d52017-11-09 20:38:30 -0800170 const int64_t& currentBucketValue) {
171 if (currentBucketNum > mMostRecentBucketNum + 1) {
Bookatz1bf94382018-01-04 11:43:20 -0800172 // TODO: This creates a needless 0 entry in mSumOverPastBuckets. Fix this.
Yangster-mace2cd6d52017-11-09 20:38:30 -0800173 addPastBucket(key, 0, currentBucketNum - 1);
174 }
Yi Jinafb36062018-01-31 19:14:25 -0800175 return mAlert.has_trigger_if_sum_gt() &&
176 getSumOverPastBuckets(key) + currentBucketValue > mAlert.trigger_if_sum_gt();
Yangster-mace2cd6d52017-11-09 20:38:30 -0800177}
178
Yangster-mac93694462018-01-22 20:49:31 -0800179void AnomalyTracker::declareAnomaly(const uint64_t& timestampNs, const MetricDimensionKey& key) {
Bookatzcc5adef2017-11-21 14:36:23 -0800180 // TODO: Why receive timestamp? RefractoryPeriod should always be based on real time right now.
Bookatz1bf94382018-01-04 11:43:20 -0800181 if (isInRefractoryPeriod(timestampNs, key)) {
Bookatzcc5adef2017-11-21 14:36:23 -0800182 VLOG("Skipping anomaly declaration since within refractory period");
Yangster-mace2cd6d52017-11-09 20:38:30 -0800183 return;
184 }
Bookatz1bf94382018-01-04 11:43:20 -0800185 mRefractoryPeriodEndsSec[key] = (timestampNs / NS_PER_SEC) + mAlert.refractory_period_secs();
Bookatzcc5adef2017-11-21 14:36:23 -0800186
187 // TODO: If we had access to the bucket_size_millis, consider calling resetStorage()
188 // if (mAlert.refractory_period_secs() > mNumOfPastBuckets * bucketSizeNs) { resetStorage(); }
Yangster-mace2cd6d52017-11-09 20:38:30 -0800189
Yangster-mac94e197c2018-01-02 16:03:03 -0800190 if (!mSubscriptions.empty()) {
191 if (mAlert.has_id()) {
Yi Jinafb36062018-01-31 19:14:25 -0800192 ALOGI("An anomaly (%llu) has occurred! Informing subscribers.", mAlert.id());
Bookatz1bf94382018-01-04 11:43:20 -0800193 informSubscribers(key);
Bookatzcc5adef2017-11-21 14:36:23 -0800194 } else {
Yangster-mac94e197c2018-01-02 16:03:03 -0800195 ALOGI("An anomaly (with no id) has occurred! Not informing any subscribers.");
Bookatzcc5adef2017-11-21 14:36:23 -0800196 }
Yangster-mace2cd6d52017-11-09 20:38:30 -0800197 } else {
Yangster-mac94e197c2018-01-02 16:03:03 -0800198 ALOGI("An anomaly has occurred! (But no subscriber for that alert.)");
Yangster-mace2cd6d52017-11-09 20:38:30 -0800199 }
Bookatz8f2f3d82017-12-07 13:53:21 -0800200
Yangster-mac94e197c2018-01-02 16:03:03 -0800201 StatsdStats::getInstance().noteAnomalyDeclared(mConfigKey, mAlert.id());
Bookatz8fcd09a2017-12-18 13:01:10 -0800202
Yangster-mac93694462018-01-22 20:49:31 -0800203 // TODO: This should also take in the const MetricDimensionKey& key?
Bookatz8fcd09a2017-12-18 13:01:10 -0800204 android::util::stats_write(android::util::ANOMALY_DETECTED, mConfigKey.GetUid(),
Yangster-mac94e197c2018-01-02 16:03:03 -0800205 mConfigKey.GetId(), mAlert.id());
Yangster-mace2cd6d52017-11-09 20:38:30 -0800206}
207
Bookatzcc5adef2017-11-21 14:36:23 -0800208void AnomalyTracker::detectAndDeclareAnomaly(const uint64_t& timestampNs,
Yangster-mace2cd6d52017-11-09 20:38:30 -0800209 const int64_t& currBucketNum,
Yangster-mac93694462018-01-22 20:49:31 -0800210 const MetricDimensionKey& key,
Yangster-mace2cd6d52017-11-09 20:38:30 -0800211 const int64_t& currentBucketValue) {
212 if (detectAnomaly(currBucketNum, key, currentBucketValue)) {
Bookatz1bf94382018-01-04 11:43:20 -0800213 declareAnomaly(timestampNs, key);
Yangster-mace2cd6d52017-11-09 20:38:30 -0800214 }
215}
216
Bookatz1bf94382018-01-04 11:43:20 -0800217bool AnomalyTracker::isInRefractoryPeriod(const uint64_t& timestampNs,
Yangster-mac93694462018-01-22 20:49:31 -0800218 const MetricDimensionKey& key) {
Bookatz1bf94382018-01-04 11:43:20 -0800219 const auto& it = mRefractoryPeriodEndsSec.find(key);
220 if (it != mRefractoryPeriodEndsSec.end()) {
221 if ((timestampNs / NS_PER_SEC) <= it->second) {
222 return true;
223 } else {
224 mRefractoryPeriodEndsSec.erase(key);
225 }
Yangster-mace2cd6d52017-11-09 20:38:30 -0800226 }
Bookatz1bf94382018-01-04 11:43:20 -0800227 return false;
Yangster-mace2cd6d52017-11-09 20:38:30 -0800228}
229
Yangster-mac93694462018-01-22 20:49:31 -0800230void AnomalyTracker::informSubscribers(const MetricDimensionKey& key) {
Yangster-mac94e197c2018-01-02 16:03:03 -0800231 VLOG("informSubscribers called.");
232 if (mSubscriptions.empty()) {
233 ALOGE("Attempt to call with no subscribers.");
Bookatzd1fd2422017-11-22 15:21:03 -0800234 return;
235 }
236
Yangster-mac94e197c2018-01-02 16:03:03 -0800237 for (const Subscription& subscription : mSubscriptions) {
238 switch (subscription.subscriber_information_case()) {
239 case Subscription::SubscriberInformationCase::kIncidentdDetails:
Yi Jinafb36062018-01-31 19:14:25 -0800240 if (!GenerateIncidentReport(subscription.incidentd_details(), mAlert, mConfigKey)) {
241 ALOGW("Failed to generate incident report.");
Yangster-mac94e197c2018-01-02 16:03:03 -0800242 }
243 break;
244 case Subscription::SubscriberInformationCase::kPerfettoDetails:
Yi Jinafb36062018-01-31 19:14:25 -0800245 if (!CollectPerfettoTraceAndUploadToDropbox(subscription.perfetto_details())) {
246 ALOGW("Failed to generate prefetto traces.");
247 }
Yangster-mac94e197c2018-01-02 16:03:03 -0800248 break;
Bookatzc6977972018-01-16 16:55:05 -0800249 case Subscription::SubscriberInformationCase::kBroadcastSubscriberDetails:
Yi Jinafb36062018-01-31 19:14:25 -0800250 SubscriberReporter::getInstance().alertBroadcastSubscriber(mConfigKey, subscription,
251 key);
Bookatzc6977972018-01-16 16:55:05 -0800252 break;
Yangster-mac94e197c2018-01-02 16:03:03 -0800253 default:
254 break;
255 }
Bookatzd1fd2422017-11-22 15:21:03 -0800256 }
Bookatzd1fd2422017-11-22 15:21:03 -0800257}
258
Yangster-mace2cd6d52017-11-09 20:38:30 -0800259} // namespace statsd
260} // namespace os
261} // namespace android