blob: 9b65acf86604509c26a1a9c97e02ee8907ddcc34 [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"
Bookatz8f2f3d82017-12-07 13:53:21 -080022#include "guardrail/StatsdStats.h"
Yangster-mace2cd6d52017-11-09 20:38:30 -080023
Bookatzd1fd2422017-11-22 15:21:03 -080024#include <android/os/IIncidentManager.h>
25#include <android/os/IncidentReportArgs.h>
26#include <binder/IServiceManager.h>
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)
Yangster-mace2cd6d52017-11-09 20:38:30 -080036 : mAlert(alert),
Bookatz8f2f3d82017-12-07 13:53:21 -080037 mConfigKey(configKey),
Yangster-maca7fb12d2018-01-03 17:17:20 -080038 mNumOfPastBuckets(mAlert.num_buckets() - 1) {
Yangster-mace2cd6d52017-11-09 20:38:30 -080039 VLOG("AnomalyTracker() called");
Yangster-maca7fb12d2018-01-03 17:17:20 -080040 if (mAlert.num_buckets() <= 0) {
Bookatzcc5adef2017-11-21 14:36:23 -080041 ALOGE("Cannot create AnomalyTracker with %lld buckets",
Yangster-maca7fb12d2018-01-03 17:17:20 -080042 (long long)mAlert.num_buckets());
Yangster-mace2cd6d52017-11-09 20:38:30 -080043 return;
44 }
Yangster-mace2cd6d52017-11-09 20:38:30 -080045 if (!mAlert.has_trigger_if_sum_gt()) {
Bookatzcc5adef2017-11-21 14:36:23 -080046 ALOGE("Cannot create AnomalyTracker without threshold");
Yangster-mace2cd6d52017-11-09 20:38:30 -080047 return;
48 }
Bookatzcc5adef2017-11-21 14:36:23 -080049 resetStorage(); // initialization
Yangster-mace2cd6d52017-11-09 20:38:30 -080050}
51
52AnomalyTracker::~AnomalyTracker() {
53 VLOG("~AnomalyTracker() called");
Yangster-mace2cd6d52017-11-09 20:38:30 -080054}
55
Bookatzcc5adef2017-11-21 14:36:23 -080056void AnomalyTracker::resetStorage() {
57 VLOG("resetStorage() called.");
Yangster-mace2cd6d52017-11-09 20:38:30 -080058 mPastBuckets.clear();
59 // Excludes the current bucket.
Bookatzcc5adef2017-11-21 14:36:23 -080060 mPastBuckets.resize(mNumOfPastBuckets);
Yangster-mace2cd6d52017-11-09 20:38:30 -080061 mSumOverPastBuckets.clear();
Yangster-mace2cd6d52017-11-09 20:38:30 -080062}
63
64size_t AnomalyTracker::index(int64_t bucketNum) const {
Bookatzcc5adef2017-11-21 14:36:23 -080065 return bucketNum % mNumOfPastBuckets;
Yangster-mace2cd6d52017-11-09 20:38:30 -080066}
67
68void AnomalyTracker::flushPastBuckets(const int64_t& latestPastBucketNum) {
69 VLOG("addPastBucket() called.");
Bookatzcc5adef2017-11-21 14:36:23 -080070 if (latestPastBucketNum <= mMostRecentBucketNum - mNumOfPastBuckets) {
Yangster-mace2cd6d52017-11-09 20:38:30 -080071 ALOGE("Cannot add a past bucket %lld units in past", (long long)latestPastBucketNum);
72 return;
73 }
74
75 // The past packets are ancient. Empty out old mPastBuckets[i] values and reset
76 // mSumOverPastBuckets.
Bookatzcc5adef2017-11-21 14:36:23 -080077 if (latestPastBucketNum - mMostRecentBucketNum >= mNumOfPastBuckets) {
Yangster-mace2cd6d52017-11-09 20:38:30 -080078 mPastBuckets.clear();
Bookatzcc5adef2017-11-21 14:36:23 -080079 mPastBuckets.resize(mNumOfPastBuckets);
Yangster-mace2cd6d52017-11-09 20:38:30 -080080 mSumOverPastBuckets.clear();
81 } else {
Bookatzcc5adef2017-11-21 14:36:23 -080082 for (int64_t i = std::max(0LL, (long long)(mMostRecentBucketNum - mNumOfPastBuckets + 1));
83 i <= latestPastBucketNum - mNumOfPastBuckets; i++) {
Yangster-mace2cd6d52017-11-09 20:38:30 -080084 const int idx = index(i);
85 subtractBucketFromSum(mPastBuckets[idx]);
86 mPastBuckets[idx] = nullptr; // release (but not clear) the old bucket.
87 }
88 }
89
90 // It is an update operation.
91 if (latestPastBucketNum <= mMostRecentBucketNum &&
Bookatzcc5adef2017-11-21 14:36:23 -080092 latestPastBucketNum > mMostRecentBucketNum - mNumOfPastBuckets) {
Yangster-mace2cd6d52017-11-09 20:38:30 -080093 subtractBucketFromSum(mPastBuckets[index(latestPastBucketNum)]);
94 }
95}
96
97void AnomalyTracker::addPastBucket(const HashableDimensionKey& key, const int64_t& bucketValue,
98 const int64_t& bucketNum) {
99 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.");
113 flushPastBuckets(bucketNum);
114 // Replace the oldest bucket with the new bucket we are adding.
115 mPastBuckets[index(bucketNum)] = bucketValues;
116 addBucketToSum(bucketValues);
117 mMostRecentBucketNum = std::max(mMostRecentBucketNum, bucketNum);
118}
119
120void AnomalyTracker::subtractBucketFromSum(const shared_ptr<DimToValMap>& bucket) {
121 if (bucket == nullptr) {
122 return;
123 }
124 // For each dimension present in the bucket, subtract its value from its corresponding sum.
125 for (const auto& keyValuePair : *bucket) {
126 auto itr = mSumOverPastBuckets.find(keyValuePair.first);
127 if (itr == mSumOverPastBuckets.end()) {
128 continue;
129 }
130 itr->second -= keyValuePair.second;
131 // TODO: No need to look up the object twice like this. Use a var.
132 if (itr->second == 0) {
133 mSumOverPastBuckets.erase(itr);
134 }
135 }
136}
137
138void AnomalyTracker::addBucketToSum(const shared_ptr<DimToValMap>& bucket) {
139 if (bucket == nullptr) {
140 return;
141 }
142 // For each dimension present in the bucket, add its value to its corresponding sum.
143 for (const auto& keyValuePair : *bucket) {
144 mSumOverPastBuckets[keyValuePair.first] += keyValuePair.second;
145 }
146}
147
148int64_t AnomalyTracker::getPastBucketValue(const HashableDimensionKey& key,
149 const int64_t& bucketNum) const {
150 const auto& bucket = mPastBuckets[index(bucketNum)];
151 if (bucket == nullptr) {
152 return 0;
153 }
154 const auto& itr = bucket->find(key);
155 return itr == bucket->end() ? 0 : itr->second;
156}
157
158int64_t AnomalyTracker::getSumOverPastBuckets(const HashableDimensionKey& key) const {
159 const auto& itr = mSumOverPastBuckets.find(key);
160 if (itr != mSumOverPastBuckets.end()) {
161 return itr->second;
162 }
163 return 0;
164}
165
Yangster-mace2cd6d52017-11-09 20:38:30 -0800166bool AnomalyTracker::detectAnomaly(const int64_t& currentBucketNum, const HashableDimensionKey& key,
167 const int64_t& currentBucketValue) {
168 if (currentBucketNum > mMostRecentBucketNum + 1) {
Bookatz1bf94382018-01-04 11:43:20 -0800169 // TODO: This creates a needless 0 entry in mSumOverPastBuckets. Fix this.
Yangster-mace2cd6d52017-11-09 20:38:30 -0800170 addPastBucket(key, 0, currentBucketNum - 1);
171 }
Bookatzcc5adef2017-11-21 14:36:23 -0800172 return mAlert.has_trigger_if_sum_gt()
173 && getSumOverPastBuckets(key) + currentBucketValue > mAlert.trigger_if_sum_gt();
Yangster-mace2cd6d52017-11-09 20:38:30 -0800174}
175
Bookatz1bf94382018-01-04 11:43:20 -0800176void AnomalyTracker::declareAnomaly(const uint64_t& timestampNs, const HashableDimensionKey& key) {
Bookatzcc5adef2017-11-21 14:36:23 -0800177 // TODO: Why receive timestamp? RefractoryPeriod should always be based on real time right now.
Bookatz1bf94382018-01-04 11:43:20 -0800178 if (isInRefractoryPeriod(timestampNs, key)) {
Bookatzcc5adef2017-11-21 14:36:23 -0800179 VLOG("Skipping anomaly declaration since within refractory period");
Yangster-mace2cd6d52017-11-09 20:38:30 -0800180 return;
181 }
Bookatz1bf94382018-01-04 11:43:20 -0800182 mRefractoryPeriodEndsSec[key] = (timestampNs / NS_PER_SEC) + mAlert.refractory_period_secs();
Bookatzcc5adef2017-11-21 14:36:23 -0800183
184 // TODO: If we had access to the bucket_size_millis, consider calling resetStorage()
185 // if (mAlert.refractory_period_secs() > mNumOfPastBuckets * bucketSizeNs) { resetStorage(); }
Yangster-mace2cd6d52017-11-09 20:38:30 -0800186
Yangster-mac94e197c2018-01-02 16:03:03 -0800187 if (!mSubscriptions.empty()) {
188 if (mAlert.has_id()) {
189 ALOGI("An anomaly (%llu) has occurred! Informing subscribers.",mAlert.id());
Bookatz1bf94382018-01-04 11:43:20 -0800190 informSubscribers(key);
Bookatzcc5adef2017-11-21 14:36:23 -0800191 } else {
Yangster-mac94e197c2018-01-02 16:03:03 -0800192 ALOGI("An anomaly (with no id) has occurred! Not informing any subscribers.");
Bookatzcc5adef2017-11-21 14:36:23 -0800193 }
Yangster-mace2cd6d52017-11-09 20:38:30 -0800194 } else {
Yangster-mac94e197c2018-01-02 16:03:03 -0800195 ALOGI("An anomaly has occurred! (But no subscriber for that alert.)");
Yangster-mace2cd6d52017-11-09 20:38:30 -0800196 }
Bookatz8f2f3d82017-12-07 13:53:21 -0800197
Yangster-mac94e197c2018-01-02 16:03:03 -0800198 StatsdStats::getInstance().noteAnomalyDeclared(mConfigKey, mAlert.id());
Bookatz8fcd09a2017-12-18 13:01:10 -0800199
Bookatz1bf94382018-01-04 11:43:20 -0800200 // TODO: This should also take in the const HashableDimensionKey& key?
Bookatz8fcd09a2017-12-18 13:01:10 -0800201 android::util::stats_write(android::util::ANOMALY_DETECTED, mConfigKey.GetUid(),
Yangster-mac94e197c2018-01-02 16:03:03 -0800202 mConfigKey.GetId(), mAlert.id());
Yangster-mace2cd6d52017-11-09 20:38:30 -0800203}
204
Bookatzcc5adef2017-11-21 14:36:23 -0800205void AnomalyTracker::detectAndDeclareAnomaly(const uint64_t& timestampNs,
Yangster-mace2cd6d52017-11-09 20:38:30 -0800206 const int64_t& currBucketNum,
207 const HashableDimensionKey& key,
208 const int64_t& currentBucketValue) {
209 if (detectAnomaly(currBucketNum, key, currentBucketValue)) {
Bookatz1bf94382018-01-04 11:43:20 -0800210 declareAnomaly(timestampNs, key);
Yangster-mace2cd6d52017-11-09 20:38:30 -0800211 }
212}
213
Bookatz1bf94382018-01-04 11:43:20 -0800214bool AnomalyTracker::isInRefractoryPeriod(const uint64_t& timestampNs,
215 const HashableDimensionKey& key) {
216 const auto& it = mRefractoryPeriodEndsSec.find(key);
217 if (it != mRefractoryPeriodEndsSec.end()) {
218 if ((timestampNs / NS_PER_SEC) <= it->second) {
219 return true;
220 } else {
221 mRefractoryPeriodEndsSec.erase(key);
222 }
Yangster-mace2cd6d52017-11-09 20:38:30 -0800223 }
Bookatz1bf94382018-01-04 11:43:20 -0800224 return false;
Yangster-mace2cd6d52017-11-09 20:38:30 -0800225}
226
Bookatz1bf94382018-01-04 11:43:20 -0800227void AnomalyTracker::informSubscribers(const HashableDimensionKey& key) {
Yangster-mac94e197c2018-01-02 16:03:03 -0800228 VLOG("informSubscribers called.");
229 if (mSubscriptions.empty()) {
230 ALOGE("Attempt to call with no subscribers.");
Bookatzd1fd2422017-11-22 15:21:03 -0800231 return;
232 }
233
Yangster-mac94e197c2018-01-02 16:03:03 -0800234 std::set<int> incidentdSections;
235 for (const Subscription& subscription : mSubscriptions) {
236 switch (subscription.subscriber_information_case()) {
237 case Subscription::SubscriberInformationCase::kIncidentdDetails:
238 for (int i = 0; i < subscription.incidentd_details().section_size(); i++) {
239 incidentdSections.insert(subscription.incidentd_details().section(i));
240 }
241 break;
242 case Subscription::SubscriberInformationCase::kPerfettoDetails:
Primiano Tuccie4d44912018-01-10 12:14:50 +0000243 CollectPerfettoTraceAndUploadToDropbox(subscription.perfetto_details());
Yangster-mac94e197c2018-01-02 16:03:03 -0800244 break;
245 default:
246 break;
247 }
Bookatzd1fd2422017-11-22 15:21:03 -0800248 }
Yangster-mac94e197c2018-01-02 16:03:03 -0800249 if (!incidentdSections.empty()) {
250 sp<IIncidentManager> service = interface_cast<IIncidentManager>(
251 defaultServiceManager()->getService(android::String16("incident")));
252 if (service != NULL) {
253 IncidentReportArgs incidentReport;
254 for (const auto section : incidentdSections) {
255 incidentReport.addSection(section);
256 }
257 int64_t alertId = mAlert.id();
258 std::vector<uint8_t> header;
259 uint8_t* src = static_cast<uint8_t*>(static_cast<void*>(&alertId));
260 header.insert(header.end(), src, src + sizeof(int64_t));
261 incidentReport.addHeader(header);
262 service->reportIncident(incidentReport);
263 } else {
264 ALOGW("Couldn't get the incident service.");
265 }
266 }
Bookatzd1fd2422017-11-22 15:21:03 -0800267}
268
Yangster-mace2cd6d52017-11-09 20:38:30 -0800269} // namespace statsd
270} // namespace os
271} // namespace android