blob: 6aa410b180b81bdeedfc43322146b711c42bc168 [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
Tej Singh484524a2018-02-01 15:10:05 -080017#define DEBUG false // STOPSHIP if true
Yangster-mace2cd6d52017-11-09 20:38:30 -080018#include "Log.h"
19
20#include "AnomalyTracker.h"
Primiano Tuccie4d44912018-01-10 12:14:50 +000021#include "external/Perfetto.h"
Yi Jinafb36062018-01-31 19:14:25 -080022#include "guardrail/StatsdStats.h"
Jeffrey Huangb8f54032020-03-23 13:42:42 -070023#include "metadata_util.h"
24#include "stats_log_util.h"
25#include "subscriber_util.h"
Yi Jinafb36062018-01-31 19:14:25 -080026#include "subscriber/IncidentdReporter.h"
Bookatzc6977972018-01-16 16:55:05 -080027#include "subscriber/SubscriberReporter.h"
Yangster-mace2cd6d52017-11-09 20:38:30 -080028
Colin Crossd013a882018-10-26 13:04:41 -070029#include <inttypes.h>
Jeffrey Huang74fc4352020-03-06 15:18:33 -080030#include <statslog_statsd.h>
Yangster-mace2cd6d52017-11-09 20:38:30 -080031#include <time.h>
32
33namespace android {
34namespace os {
35namespace statsd {
36
Bookatz8f2f3d82017-12-07 13:53:21 -080037AnomalyTracker::AnomalyTracker(const Alert& alert, const ConfigKey& configKey)
Bookatz6bf98252018-03-14 10:44:24 -070038 : mAlert(alert), mConfigKey(configKey), mNumOfPastBuckets(mAlert.num_buckets() - 1) {
Yangster-mace2cd6d52017-11-09 20:38:30 -080039 VLOG("AnomalyTracker() called");
Yi Jinafb36062018-01-31 19:14:25 -080040 resetStorage(); // initialization
Yangster-mace2cd6d52017-11-09 20:38:30 -080041}
42
43AnomalyTracker::~AnomalyTracker() {
44 VLOG("~AnomalyTracker() called");
Yangster-mace2cd6d52017-11-09 20:38:30 -080045}
46
Tej Singhd07d0ff2020-10-27 20:56:11 -070047void AnomalyTracker::onConfigUpdated() {
48 mSubscriptions.clear();
49}
50
Bookatzcc5adef22017-11-21 14:36:23 -080051void AnomalyTracker::resetStorage() {
52 VLOG("resetStorage() called.");
Yangster-mace2cd6d52017-11-09 20:38:30 -080053 mPastBuckets.clear();
54 // Excludes the current bucket.
Bookatzcc5adef22017-11-21 14:36:23 -080055 mPastBuckets.resize(mNumOfPastBuckets);
Yangster-mace2cd6d52017-11-09 20:38:30 -080056 mSumOverPastBuckets.clear();
Yangster-mace2cd6d52017-11-09 20:38:30 -080057}
58
59size_t AnomalyTracker::index(int64_t bucketNum) const {
Bookatz2fb56532018-03-08 11:16:48 -080060 if (bucketNum < 0) {
Bookatz2fb56532018-03-08 11:16:48 -080061 ALOGE("index() was passed a negative bucket number (%lld)!", (long long)bucketNum);
62 }
Bookatzcc5adef22017-11-21 14:36:23 -080063 return bucketNum % mNumOfPastBuckets;
Yangster-mace2cd6d52017-11-09 20:38:30 -080064}
65
Bookatz6bf98252018-03-14 10:44:24 -070066void AnomalyTracker::advanceMostRecentBucketTo(const int64_t& bucketNum) {
67 VLOG("advanceMostRecentBucketTo() called.");
Yangster-macbe10ddf2018-03-13 15:39:51 -070068 if (mNumOfPastBuckets <= 0) {
69 return;
70 }
Bookatz6bf98252018-03-14 10:44:24 -070071 if (bucketNum <= mMostRecentBucketNum) {
72 ALOGW("Cannot advance buckets backwards (bucketNum=%lld but mMostRecentBucketNum=%lld)",
73 (long long)bucketNum, (long long)mMostRecentBucketNum);
Yangster-mace2cd6d52017-11-09 20:38:30 -080074 return;
75 }
Bookatz6bf98252018-03-14 10:44:24 -070076 // If in the future (i.e. buckets are ancient), just empty out all past info.
77 if (bucketNum >= mMostRecentBucketNum + mNumOfPastBuckets) {
Bookatz2fb56532018-03-08 11:16:48 -080078 resetStorage();
Bookatz6bf98252018-03-14 10:44:24 -070079 mMostRecentBucketNum = bucketNum;
80 return;
81 }
82
83 // Clear out space by emptying out old mPastBuckets[i] values and update mSumOverPastBuckets.
84 for (int64_t i = mMostRecentBucketNum + 1; i <= bucketNum; i++) {
85 const int idx = index(i);
86 subtractBucketFromSum(mPastBuckets[idx]);
87 mPastBuckets[idx] = nullptr; // release (but not clear) the old bucket.
88 }
89 mMostRecentBucketNum = bucketNum;
90}
91
92void AnomalyTracker::addPastBucket(const MetricDimensionKey& key,
93 const int64_t& bucketValue,
94 const int64_t& bucketNum) {
95 VLOG("addPastBucket(bucketValue) called.");
96 if (mNumOfPastBuckets == 0 ||
97 bucketNum < 0 || bucketNum <= mMostRecentBucketNum - mNumOfPastBuckets) {
98 return;
99 }
100
101 const int bucketIndex = index(bucketNum);
102 if (bucketNum <= mMostRecentBucketNum && (mPastBuckets[bucketIndex] != nullptr)) {
103 // We need to insert into an already existing past bucket.
104 std::shared_ptr<DimToValMap>& bucket = mPastBuckets[bucketIndex];
105 auto itr = bucket->find(key);
106 if (itr != bucket->end()) {
107 // Old entry already exists; update it.
108 subtractValueFromSum(key, itr->second);
109 itr->second = bucketValue;
110 } else {
111 bucket->insert({key, bucketValue});
Yangster-mace2cd6d52017-11-09 20:38:30 -0800112 }
Bookatz6bf98252018-03-14 10:44:24 -0700113 mSumOverPastBuckets[key] += bucketValue;
114 } else {
115 // Bucket does not exist yet (in future or was never made), so we must make it.
116 std::shared_ptr<DimToValMap> bucket = std::make_shared<DimToValMap>();
117 bucket->insert({key, bucketValue});
118 addPastBucket(bucket, bucketNum);
Yangster-mace2cd6d52017-11-09 20:38:30 -0800119 }
120}
121
Bookatz6bf98252018-03-14 10:44:24 -0700122void AnomalyTracker::addPastBucket(std::shared_ptr<DimToValMap> bucket,
Yangster-mace2cd6d52017-11-09 20:38:30 -0800123 const int64_t& bucketNum) {
Bookatz6bf98252018-03-14 10:44:24 -0700124 VLOG("addPastBucket(bucket) called.");
125 if (mNumOfPastBuckets == 0 ||
126 bucketNum < 0 || bucketNum <= mMostRecentBucketNum - mNumOfPastBuckets) {
David Chenc189bdcb2018-02-09 16:09:26 -0800127 return;
128 }
Yangster-mace2cd6d52017-11-09 20:38:30 -0800129
Bookatz6bf98252018-03-14 10:44:24 -0700130 if (bucketNum <= mMostRecentBucketNum) {
131 // We are updating an old bucket, not adding a new one.
132 subtractBucketFromSum(mPastBuckets[index(bucketNum)]);
133 } else {
134 // Clear space for the new bucket to be at bucketNum.
135 advanceMostRecentBucketTo(bucketNum);
Yangster-mace2cd6d52017-11-09 20:38:30 -0800136 }
Bookatz6bf98252018-03-14 10:44:24 -0700137 mPastBuckets[index(bucketNum)] = bucket;
Yangster-mace2cd6d52017-11-09 20:38:30 -0800138 addBucketToSum(bucket);
Yangster-mace2cd6d52017-11-09 20:38:30 -0800139}
140
141void AnomalyTracker::subtractBucketFromSum(const shared_ptr<DimToValMap>& bucket) {
142 if (bucket == nullptr) {
143 return;
144 }
Yangster-mace2cd6d52017-11-09 20:38:30 -0800145 for (const auto& keyValuePair : *bucket) {
Bookatz6bf98252018-03-14 10:44:24 -0700146 subtractValueFromSum(keyValuePair.first, keyValuePair.second);
147 }
148}
149
150
151void AnomalyTracker::subtractValueFromSum(const MetricDimensionKey& key,
152 const int64_t& bucketValue) {
153 auto itr = mSumOverPastBuckets.find(key);
154 if (itr == mSumOverPastBuckets.end()) {
155 return;
156 }
157 itr->second -= bucketValue;
158 if (itr->second == 0) {
159 mSumOverPastBuckets.erase(itr);
Yangster-mace2cd6d52017-11-09 20:38:30 -0800160 }
161}
162
163void AnomalyTracker::addBucketToSum(const shared_ptr<DimToValMap>& bucket) {
164 if (bucket == nullptr) {
165 return;
166 }
167 // For each dimension present in the bucket, add its value to its corresponding sum.
168 for (const auto& keyValuePair : *bucket) {
169 mSumOverPastBuckets[keyValuePair.first] += keyValuePair.second;
170 }
171}
172
Yangster-mac93694462018-01-22 20:49:31 -0800173int64_t AnomalyTracker::getPastBucketValue(const MetricDimensionKey& key,
Yangster-mace2cd6d52017-11-09 20:38:30 -0800174 const int64_t& bucketNum) const {
Yangster-macbe10ddf2018-03-13 15:39:51 -0700175 if (bucketNum < 0 || mMostRecentBucketNum < 0
176 || bucketNum <= mMostRecentBucketNum - mNumOfPastBuckets
Bookatz6bf98252018-03-14 10:44:24 -0700177 || bucketNum > mMostRecentBucketNum) {
David Chenebe7e2372018-02-20 13:22:53 -0800178 return 0;
179 }
180
Yangster-mace2cd6d52017-11-09 20:38:30 -0800181 const auto& bucket = mPastBuckets[index(bucketNum)];
182 if (bucket == nullptr) {
183 return 0;
184 }
185 const auto& itr = bucket->find(key);
186 return itr == bucket->end() ? 0 : itr->second;
187}
188
Yangster-mac93694462018-01-22 20:49:31 -0800189int64_t AnomalyTracker::getSumOverPastBuckets(const MetricDimensionKey& key) const {
Yangster-mace2cd6d52017-11-09 20:38:30 -0800190 const auto& itr = mSumOverPastBuckets.find(key);
191 if (itr != mSumOverPastBuckets.end()) {
192 return itr->second;
193 }
194 return 0;
195}
196
Bookatz6bf98252018-03-14 10:44:24 -0700197bool AnomalyTracker::detectAnomaly(const int64_t& currentBucketNum,
198 const MetricDimensionKey& key,
Yangster-mace2cd6d52017-11-09 20:38:30 -0800199 const int64_t& currentBucketValue) {
Bookatz6bf98252018-03-14 10:44:24 -0700200
201 // currentBucketNum should be the next bucket after pastBuckets. If not, advance so that it is.
Yangster-mace2cd6d52017-11-09 20:38:30 -0800202 if (currentBucketNum > mMostRecentBucketNum + 1) {
Bookatz6bf98252018-03-14 10:44:24 -0700203 advanceMostRecentBucketTo(currentBucketNum - 1);
Yangster-mace2cd6d52017-11-09 20:38:30 -0800204 }
Yi Jinafb36062018-01-31 19:14:25 -0800205 return mAlert.has_trigger_if_sum_gt() &&
206 getSumOverPastBuckets(key) + currentBucketValue > mAlert.trigger_if_sum_gt();
Yangster-mace2cd6d52017-11-09 20:38:30 -0800207}
208
Yao Chen4ce07292019-02-13 13:06:36 -0800209void AnomalyTracker::declareAnomaly(const int64_t& timestampNs, int64_t metricId,
210 const MetricDimensionKey& key, int64_t metricValue) {
Yao Chen5bfffb52018-06-21 16:58:51 -0700211 // TODO(b/110563466): Why receive timestamp? RefractoryPeriod should always be based on
212 // real time right now.
Bookatz1bf94382018-01-04 11:43:20 -0800213 if (isInRefractoryPeriod(timestampNs, key)) {
Bookatzcc5adef22017-11-21 14:36:23 -0800214 VLOG("Skipping anomaly declaration since within refractory period");
Yangster-mace2cd6d52017-11-09 20:38:30 -0800215 return;
216 }
Bookatz6bf98252018-03-14 10:44:24 -0700217 if (mAlert.has_refractory_period_secs()) {
218 mRefractoryPeriodEndsSec[key] = ((timestampNs + NS_PER_SEC - 1) / NS_PER_SEC) // round up
219 + mAlert.refractory_period_secs();
Yao Chen5bfffb52018-06-21 16:58:51 -0700220 // TODO(b/110563466): If we had access to the bucket_size_millis, consider
221 // calling resetStorage()
Bookatz6bf98252018-03-14 10:44:24 -0700222 // if (mAlert.refractory_period_secs() > mNumOfPastBuckets * bucketSizeNs) {resetStorage();}
223 }
Yangster-mace2cd6d52017-11-09 20:38:30 -0800224
Yangster-mac94e197c2018-01-02 16:03:03 -0800225 if (!mSubscriptions.empty()) {
Colin Crossd013a882018-10-26 13:04:41 -0700226 ALOGI("An anomaly (%" PRId64 ") %s has occurred! Informing subscribers.",
Bookatz6bf98252018-03-14 10:44:24 -0700227 mAlert.id(), key.toString().c_str());
Yao Chen4ce07292019-02-13 13:06:36 -0800228 informSubscribers(key, metricId, metricValue);
Yangster-mace2cd6d52017-11-09 20:38:30 -0800229 } else {
Yangster-mac94e197c2018-01-02 16:03:03 -0800230 ALOGI("An anomaly has occurred! (But no subscriber for that alert.)");
Yangster-mace2cd6d52017-11-09 20:38:30 -0800231 }
Bookatz8f2f3d82017-12-07 13:53:21 -0800232
Yangster-mac94e197c2018-01-02 16:03:03 -0800233 StatsdStats::getInstance().noteAnomalyDeclared(mConfigKey, mAlert.id());
Bookatz8fcd09a2017-12-18 13:01:10 -0800234
Yao Chen5bfffb52018-06-21 16:58:51 -0700235 // TODO(b/110564268): This should also take in the const MetricDimensionKey& key?
Jeffrey Huang74fc4352020-03-06 15:18:33 -0800236 util::stats_write(util::ANOMALY_DETECTED, mConfigKey.GetUid(),
237 mConfigKey.GetId(), mAlert.id());
Yangster-mace2cd6d52017-11-09 20:38:30 -0800238}
239
Yangster-macb142cc82018-03-30 15:22:08 -0700240void AnomalyTracker::detectAndDeclareAnomaly(const int64_t& timestampNs,
Yao Chen4ce07292019-02-13 13:06:36 -0800241 const int64_t& currBucketNum, int64_t metricId,
Yangster-mac93694462018-01-22 20:49:31 -0800242 const MetricDimensionKey& key,
Yangster-mace2cd6d52017-11-09 20:38:30 -0800243 const int64_t& currentBucketValue) {
244 if (detectAnomaly(currBucketNum, key, currentBucketValue)) {
Yao Chen4ce07292019-02-13 13:06:36 -0800245 declareAnomaly(timestampNs, metricId, key, currentBucketValue);
Yangster-mace2cd6d52017-11-09 20:38:30 -0800246 }
247}
248
Yangster-macb142cc82018-03-30 15:22:08 -0700249bool AnomalyTracker::isInRefractoryPeriod(const int64_t& timestampNs,
Yangster-macbe10ddf2018-03-13 15:39:51 -0700250 const MetricDimensionKey& key) const {
Bookatz1bf94382018-01-04 11:43:20 -0800251 const auto& it = mRefractoryPeriodEndsSec.find(key);
252 if (it != mRefractoryPeriodEndsSec.end()) {
Yangster-macb142cc82018-03-30 15:22:08 -0700253 return timestampNs < (it->second * (int64_t)NS_PER_SEC);
Yangster-mace2cd6d52017-11-09 20:38:30 -0800254 }
Bookatz1bf94382018-01-04 11:43:20 -0800255 return false;
Yangster-mace2cd6d52017-11-09 20:38:30 -0800256}
257
Tej Singhd07d0ff2020-10-27 20:56:11 -0700258std::pair<bool, uint64_t> AnomalyTracker::getProtoHash() const {
259 string serializedAlert;
260 if (!mAlert.SerializeToString(&serializedAlert)) {
261 ALOGW("Unable to serialize alert %lld", (long long)mAlert.id());
262 return {false, 0};
263 }
264 return {true, Hash64(serializedAlert)};
265}
266
Yao Chen4ce07292019-02-13 13:06:36 -0800267void AnomalyTracker::informSubscribers(const MetricDimensionKey& key, int64_t metric_id,
268 int64_t metricValue) {
269 triggerSubscribers(mAlert.id(), metric_id, key, metricValue, mConfigKey, mSubscriptions);
Bookatzd1fd2422017-11-22 15:21:03 -0800270}
271
Jeffrey Huangb8f54032020-03-23 13:42:42 -0700272bool AnomalyTracker::writeAlertMetadataToProto(int64_t currentWallClockTimeNs,
273 int64_t systemElapsedTimeNs,
274 metadata::AlertMetadata* alertMetadata) {
275 bool metadataWritten = false;
276
277 if (mRefractoryPeriodEndsSec.empty()) {
278 return false;
279 }
280
281 for (const auto& it: mRefractoryPeriodEndsSec) {
282 // Do not write the timestamp to disk if it has already expired
283 if (it.second < systemElapsedTimeNs / NS_PER_SEC) {
284 continue;
285 }
286
287 metadataWritten = true;
288 if (alertMetadata->alert_dim_keyed_data_size() == 0) {
289 alertMetadata->set_alert_id(mAlert.id());
290 }
291
292 metadata::AlertDimensionKeyedData* keyedData = alertMetadata->add_alert_dim_keyed_data();
293 // We convert and write the refractory_end_sec to wall clock time because we do not know
294 // when statsd will start again.
295 int32_t refractoryEndWallClockSec = (int32_t) ((currentWallClockTimeNs / NS_PER_SEC) +
296 (it.second - systemElapsedTimeNs / NS_PER_SEC));
297
298 keyedData->set_last_refractory_ends_sec(refractoryEndWallClockSec);
299 writeMetricDimensionKeyToMetadataDimensionKey(
300 it.first, keyedData->mutable_dimension_key());
301 }
302
303 return metadataWritten;
304}
305
Jeffrey Huang475677e2020-03-30 19:52:07 -0700306void AnomalyTracker::loadAlertMetadata(
307 const metadata::AlertMetadata& alertMetadata,
308 int64_t currentWallClockTimeNs,
309 int64_t systemElapsedTimeNs) {
310 for (const metadata::AlertDimensionKeyedData& keyedData :
311 alertMetadata.alert_dim_keyed_data()) {
312 if ((uint64_t) keyedData.last_refractory_ends_sec() < currentWallClockTimeNs / NS_PER_SEC) {
313 // Do not update the timestamp if it has already expired.
314 continue;
315 }
316 MetricDimensionKey metricKey = loadMetricDimensionKeyFromProto(
317 keyedData.dimension_key());
318 int32_t refractoryPeriodEndsSec = (int32_t) keyedData.last_refractory_ends_sec() -
319 currentWallClockTimeNs / NS_PER_SEC + systemElapsedTimeNs / NS_PER_SEC;
320 mRefractoryPeriodEndsSec[metricKey] = refractoryPeriodEndsSec;
321 }
322}
323
Yangster-mace2cd6d52017-11-09 20:38:30 -0800324} // namespace statsd
325} // namespace os
326} // namespace android