1 /* 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 false // STOPSHIP if true 18 #include "Log.h" 19 20 #include "AnomalyTracker.h" 21 #include "subscriber_util.h" 22 #include "external/Perfetto.h" 23 #include "guardrail/StatsdStats.h" 24 #include "subscriber/IncidentdReporter.h" 25 #include "subscriber/SubscriberReporter.h" 26 27 #include <statslog.h> 28 #include <time.h> 29 30 namespace android { 31 namespace os { 32 namespace statsd { 33 34 AnomalyTracker::AnomalyTracker(const Alert& alert, const ConfigKey& configKey) 35 : mAlert(alert), mConfigKey(configKey), mNumOfPastBuckets(mAlert.num_buckets() - 1) { 36 VLOG("AnomalyTracker() called"); 37 if (mAlert.num_buckets() <= 0) { 38 ALOGE("Cannot create AnomalyTracker with %lld buckets", (long long)mAlert.num_buckets()); 39 return; 40 } 41 if (!mAlert.has_trigger_if_sum_gt()) { 42 ALOGE("Cannot create AnomalyTracker without threshold"); 43 return; 44 } 45 resetStorage(); // initialization 46 } 47 48 AnomalyTracker::~AnomalyTracker() { 49 VLOG("~AnomalyTracker() called"); 50 } 51 52 void AnomalyTracker::resetStorage() { 53 VLOG("resetStorage() called."); 54 mPastBuckets.clear(); 55 // Excludes the current bucket. 56 mPastBuckets.resize(mNumOfPastBuckets); 57 mSumOverPastBuckets.clear(); 58 } 59 60 size_t AnomalyTracker::index(int64_t bucketNum) const { 61 if (bucketNum < 0) { 62 ALOGE("index() was passed a negative bucket number (%lld)!", (long long)bucketNum); 63 } 64 return bucketNum % mNumOfPastBuckets; 65 } 66 67 void AnomalyTracker::advanceMostRecentBucketTo(const int64_t& bucketNum) { 68 VLOG("advanceMostRecentBucketTo() called."); 69 if (mNumOfPastBuckets <= 0) { 70 return; 71 } 72 if (bucketNum <= mMostRecentBucketNum) { 73 ALOGW("Cannot advance buckets backwards (bucketNum=%lld but mMostRecentBucketNum=%lld)", 74 (long long)bucketNum, (long long)mMostRecentBucketNum); 75 return; 76 } 77 // If in the future (i.e. buckets are ancient), just empty out all past info. 78 if (bucketNum >= mMostRecentBucketNum + mNumOfPastBuckets) { 79 resetStorage(); 80 mMostRecentBucketNum = bucketNum; 81 return; 82 } 83 84 // Clear out space by emptying out old mPastBuckets[i] values and update mSumOverPastBuckets. 85 for (int64_t i = mMostRecentBucketNum + 1; i <= bucketNum; i++) { 86 const int idx = index(i); 87 subtractBucketFromSum(mPastBuckets[idx]); 88 mPastBuckets[idx] = nullptr; // release (but not clear) the old bucket. 89 } 90 mMostRecentBucketNum = bucketNum; 91 } 92 93 void AnomalyTracker::addPastBucket(const MetricDimensionKey& key, 94 const int64_t& bucketValue, 95 const int64_t& bucketNum) { 96 VLOG("addPastBucket(bucketValue) called."); 97 if (mNumOfPastBuckets == 0 || 98 bucketNum < 0 || bucketNum <= mMostRecentBucketNum - mNumOfPastBuckets) { 99 return; 100 } 101 102 const int bucketIndex = index(bucketNum); 103 if (bucketNum <= mMostRecentBucketNum && (mPastBuckets[bucketIndex] != nullptr)) { 104 // We need to insert into an already existing past bucket. 105 std::shared_ptr<DimToValMap>& bucket = mPastBuckets[bucketIndex]; 106 auto itr = bucket->find(key); 107 if (itr != bucket->end()) { 108 // Old entry already exists; update it. 109 subtractValueFromSum(key, itr->second); 110 itr->second = bucketValue; 111 } else { 112 bucket->insert({key, bucketValue}); 113 } 114 mSumOverPastBuckets[key] += bucketValue; 115 } else { 116 // Bucket does not exist yet (in future or was never made), so we must make it. 117 std::shared_ptr<DimToValMap> bucket = std::make_shared<DimToValMap>(); 118 bucket->insert({key, bucketValue}); 119 addPastBucket(bucket, bucketNum); 120 } 121 } 122 123 void AnomalyTracker::addPastBucket(std::shared_ptr<DimToValMap> bucket, 124 const int64_t& bucketNum) { 125 VLOG("addPastBucket(bucket) called."); 126 if (mNumOfPastBuckets == 0 || 127 bucketNum < 0 || bucketNum <= mMostRecentBucketNum - mNumOfPastBuckets) { 128 return; 129 } 130 131 if (bucketNum <= mMostRecentBucketNum) { 132 // We are updating an old bucket, not adding a new one. 133 subtractBucketFromSum(mPastBuckets[index(bucketNum)]); 134 } else { 135 // Clear space for the new bucket to be at bucketNum. 136 advanceMostRecentBucketTo(bucketNum); 137 } 138 mPastBuckets[index(bucketNum)] = bucket; 139 addBucketToSum(bucket); 140 } 141 142 void AnomalyTracker::subtractBucketFromSum(const shared_ptr<DimToValMap>& bucket) { 143 if (bucket == nullptr) { 144 return; 145 } 146 for (const auto& keyValuePair : *bucket) { 147 subtractValueFromSum(keyValuePair.first, keyValuePair.second); 148 } 149 } 150 151 152 void AnomalyTracker::subtractValueFromSum(const MetricDimensionKey& key, 153 const int64_t& bucketValue) { 154 auto itr = mSumOverPastBuckets.find(key); 155 if (itr == mSumOverPastBuckets.end()) { 156 return; 157 } 158 itr->second -= bucketValue; 159 if (itr->second == 0) { 160 mSumOverPastBuckets.erase(itr); 161 } 162 } 163 164 void AnomalyTracker::addBucketToSum(const shared_ptr<DimToValMap>& bucket) { 165 if (bucket == nullptr) { 166 return; 167 } 168 // For each dimension present in the bucket, add its value to its corresponding sum. 169 for (const auto& keyValuePair : *bucket) { 170 mSumOverPastBuckets[keyValuePair.first] += keyValuePair.second; 171 } 172 } 173 174 int64_t AnomalyTracker::getPastBucketValue(const MetricDimensionKey& key, 175 const int64_t& bucketNum) const { 176 if (bucketNum < 0 || mMostRecentBucketNum < 0 177 || bucketNum <= mMostRecentBucketNum - mNumOfPastBuckets 178 || bucketNum > mMostRecentBucketNum) { 179 return 0; 180 } 181 182 const auto& bucket = mPastBuckets[index(bucketNum)]; 183 if (bucket == nullptr) { 184 return 0; 185 } 186 const auto& itr = bucket->find(key); 187 return itr == bucket->end() ? 0 : itr->second; 188 } 189 190 int64_t AnomalyTracker::getSumOverPastBuckets(const MetricDimensionKey& key) const { 191 const auto& itr = mSumOverPastBuckets.find(key); 192 if (itr != mSumOverPastBuckets.end()) { 193 return itr->second; 194 } 195 return 0; 196 } 197 198 bool AnomalyTracker::detectAnomaly(const int64_t& currentBucketNum, 199 const MetricDimensionKey& key, 200 const int64_t& currentBucketValue) { 201 202 // currentBucketNum should be the next bucket after pastBuckets. If not, advance so that it is. 203 if (currentBucketNum > mMostRecentBucketNum + 1) { 204 advanceMostRecentBucketTo(currentBucketNum - 1); 205 } 206 return mAlert.has_trigger_if_sum_gt() && 207 getSumOverPastBuckets(key) + currentBucketValue > mAlert.trigger_if_sum_gt(); 208 } 209 210 void AnomalyTracker::declareAnomaly(const int64_t& timestampNs, const MetricDimensionKey& key) { 211 // TODO: Why receive timestamp? RefractoryPeriod should always be based on real time right now. 212 if (isInRefractoryPeriod(timestampNs, key)) { 213 VLOG("Skipping anomaly declaration since within refractory period"); 214 return; 215 } 216 if (mAlert.has_refractory_period_secs()) { 217 mRefractoryPeriodEndsSec[key] = ((timestampNs + NS_PER_SEC - 1) / NS_PER_SEC) // round up 218 + mAlert.refractory_period_secs(); 219 // TODO: If we had access to the bucket_size_millis, consider calling resetStorage() 220 // if (mAlert.refractory_period_secs() > mNumOfPastBuckets * bucketSizeNs) {resetStorage();} 221 } 222 223 if (!mSubscriptions.empty()) { 224 ALOGI("An anomaly (%lld) %s has occurred! Informing subscribers.", 225 mAlert.id(), key.toString().c_str()); 226 informSubscribers(key); 227 } else { 228 ALOGI("An anomaly has occurred! (But no subscriber for that alert.)"); 229 } 230 231 StatsdStats::getInstance().noteAnomalyDeclared(mConfigKey, mAlert.id()); 232 233 // TODO: This should also take in the const MetricDimensionKey& key? 234 android::util::stats_write(android::util::ANOMALY_DETECTED, mConfigKey.GetUid(), 235 mConfigKey.GetId(), mAlert.id()); 236 } 237 238 void AnomalyTracker::detectAndDeclareAnomaly(const int64_t& timestampNs, 239 const int64_t& currBucketNum, 240 const MetricDimensionKey& key, 241 const int64_t& currentBucketValue) { 242 if (detectAnomaly(currBucketNum, key, currentBucketValue)) { 243 declareAnomaly(timestampNs, key); 244 } 245 } 246 247 bool AnomalyTracker::isInRefractoryPeriod(const int64_t& timestampNs, 248 const MetricDimensionKey& key) const { 249 const auto& it = mRefractoryPeriodEndsSec.find(key); 250 if (it != mRefractoryPeriodEndsSec.end()) { 251 return timestampNs < (it->second * (int64_t)NS_PER_SEC); 252 } 253 return false; 254 } 255 256 void AnomalyTracker::informSubscribers(const MetricDimensionKey& key) { 257 triggerSubscribers(mAlert.id(), key, mConfigKey, mSubscriptions); 258 } 259 260 } // namespace statsd 261 } // namespace os 262 } // namespace android 263