blob: ded6c4c660be5940c57cc6d6a8dd5d59237f47ae [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"
Yi Jin437aa6e2018-01-10 11:34:26 -080023#include "frameworks/base/libs/incident/proto/android/os/header.pb.h"
Bookatzc6977972018-01-16 16:55:05 -080024#include "subscriber/SubscriberReporter.h"
Yangster-mace2cd6d52017-11-09 20:38:30 -080025
Bookatzd1fd2422017-11-22 15:21:03 -080026#include <android/os/IIncidentManager.h>
27#include <android/os/IncidentReportArgs.h>
28#include <binder/IServiceManager.h>
Bookatz8fcd09a2017-12-18 13:01:10 -080029#include <statslog.h>
Yangster-mace2cd6d52017-11-09 20:38:30 -080030#include <time.h>
31
32namespace android {
33namespace os {
34namespace statsd {
35
Bookatzcc5adef2017-11-21 14:36:23 -080036// TODO: Get rid of bucketNumbers, and return to the original circular array method.
Bookatz8f2f3d82017-12-07 13:53:21 -080037AnomalyTracker::AnomalyTracker(const Alert& alert, const ConfigKey& configKey)
Yangster-mace2cd6d52017-11-09 20:38:30 -080038 : mAlert(alert),
Bookatz8f2f3d82017-12-07 13:53:21 -080039 mConfigKey(configKey),
Yangster-maca7fb12d2018-01-03 17:17:20 -080040 mNumOfPastBuckets(mAlert.num_buckets() - 1) {
Yangster-mace2cd6d52017-11-09 20:38:30 -080041 VLOG("AnomalyTracker() called");
Yangster-maca7fb12d2018-01-03 17:17:20 -080042 if (mAlert.num_buckets() <= 0) {
Bookatzcc5adef2017-11-21 14:36:23 -080043 ALOGE("Cannot create AnomalyTracker with %lld buckets",
Yangster-maca7fb12d2018-01-03 17:17:20 -080044 (long long)mAlert.num_buckets());
Yangster-mace2cd6d52017-11-09 20:38:30 -080045 return;
46 }
Yangster-mace2cd6d52017-11-09 20:38:30 -080047 if (!mAlert.has_trigger_if_sum_gt()) {
Bookatzcc5adef2017-11-21 14:36:23 -080048 ALOGE("Cannot create AnomalyTracker without threshold");
Yangster-mace2cd6d52017-11-09 20:38:30 -080049 return;
50 }
Bookatzcc5adef2017-11-21 14:36:23 -080051 resetStorage(); // initialization
Yangster-mace2cd6d52017-11-09 20:38:30 -080052}
53
54AnomalyTracker::~AnomalyTracker() {
55 VLOG("~AnomalyTracker() called");
Yangster-mace2cd6d52017-11-09 20:38:30 -080056}
57
Bookatzcc5adef2017-11-21 14:36:23 -080058void AnomalyTracker::resetStorage() {
59 VLOG("resetStorage() called.");
Yangster-mace2cd6d52017-11-09 20:38:30 -080060 mPastBuckets.clear();
61 // Excludes the current bucket.
Bookatzcc5adef2017-11-21 14:36:23 -080062 mPastBuckets.resize(mNumOfPastBuckets);
Yangster-mace2cd6d52017-11-09 20:38:30 -080063 mSumOverPastBuckets.clear();
Yangster-mace2cd6d52017-11-09 20:38:30 -080064}
65
66size_t AnomalyTracker::index(int64_t bucketNum) const {
Bookatzcc5adef2017-11-21 14:36:23 -080067 return bucketNum % mNumOfPastBuckets;
Yangster-mace2cd6d52017-11-09 20:38:30 -080068}
69
70void AnomalyTracker::flushPastBuckets(const int64_t& latestPastBucketNum) {
71 VLOG("addPastBucket() called.");
Bookatzcc5adef2017-11-21 14:36:23 -080072 if (latestPastBucketNum <= mMostRecentBucketNum - mNumOfPastBuckets) {
Yangster-mace2cd6d52017-11-09 20:38:30 -080073 ALOGE("Cannot add a past bucket %lld units in past", (long long)latestPastBucketNum);
74 return;
75 }
76
77 // The past packets are ancient. Empty out old mPastBuckets[i] values and reset
78 // mSumOverPastBuckets.
Bookatzcc5adef2017-11-21 14:36:23 -080079 if (latestPastBucketNum - mMostRecentBucketNum >= mNumOfPastBuckets) {
Yangster-mace2cd6d52017-11-09 20:38:30 -080080 mPastBuckets.clear();
Bookatzcc5adef2017-11-21 14:36:23 -080081 mPastBuckets.resize(mNumOfPastBuckets);
Yangster-mace2cd6d52017-11-09 20:38:30 -080082 mSumOverPastBuckets.clear();
83 } else {
Bookatzcc5adef2017-11-21 14:36:23 -080084 for (int64_t i = std::max(0LL, (long long)(mMostRecentBucketNum - mNumOfPastBuckets + 1));
85 i <= latestPastBucketNum - mNumOfPastBuckets; i++) {
Yangster-mace2cd6d52017-11-09 20:38:30 -080086 const int idx = index(i);
87 subtractBucketFromSum(mPastBuckets[idx]);
88 mPastBuckets[idx] = nullptr; // release (but not clear) the old bucket.
89 }
90 }
91
92 // It is an update operation.
93 if (latestPastBucketNum <= mMostRecentBucketNum &&
Bookatzcc5adef2017-11-21 14:36:23 -080094 latestPastBucketNum > mMostRecentBucketNum - mNumOfPastBuckets) {
Yangster-mace2cd6d52017-11-09 20:38:30 -080095 subtractBucketFromSum(mPastBuckets[index(latestPastBucketNum)]);
96 }
97}
98
99void AnomalyTracker::addPastBucket(const HashableDimensionKey& key, const int64_t& bucketValue,
100 const int64_t& bucketNum) {
101 flushPastBuckets(bucketNum);
102
103 auto& bucket = mPastBuckets[index(bucketNum)];
104 if (bucket == nullptr) {
105 bucket = std::make_shared<DimToValMap>();
106 }
107 bucket->insert({key, bucketValue});
108 addBucketToSum(bucket);
109 mMostRecentBucketNum = std::max(mMostRecentBucketNum, bucketNum);
110}
111
112void AnomalyTracker::addPastBucket(std::shared_ptr<DimToValMap> bucketValues,
113 const int64_t& bucketNum) {
114 VLOG("addPastBucket() called.");
115 flushPastBuckets(bucketNum);
116 // Replace the oldest bucket with the new bucket we are adding.
117 mPastBuckets[index(bucketNum)] = bucketValues;
118 addBucketToSum(bucketValues);
119 mMostRecentBucketNum = std::max(mMostRecentBucketNum, bucketNum);
120}
121
122void AnomalyTracker::subtractBucketFromSum(const shared_ptr<DimToValMap>& bucket) {
123 if (bucket == nullptr) {
124 return;
125 }
126 // For each dimension present in the bucket, subtract its value from its corresponding sum.
127 for (const auto& keyValuePair : *bucket) {
128 auto itr = mSumOverPastBuckets.find(keyValuePair.first);
129 if (itr == mSumOverPastBuckets.end()) {
130 continue;
131 }
132 itr->second -= keyValuePair.second;
133 // TODO: No need to look up the object twice like this. Use a var.
134 if (itr->second == 0) {
135 mSumOverPastBuckets.erase(itr);
136 }
137 }
138}
139
140void AnomalyTracker::addBucketToSum(const shared_ptr<DimToValMap>& bucket) {
141 if (bucket == nullptr) {
142 return;
143 }
144 // For each dimension present in the bucket, add its value to its corresponding sum.
145 for (const auto& keyValuePair : *bucket) {
146 mSumOverPastBuckets[keyValuePair.first] += keyValuePair.second;
147 }
148}
149
150int64_t AnomalyTracker::getPastBucketValue(const HashableDimensionKey& key,
151 const int64_t& bucketNum) const {
152 const auto& bucket = mPastBuckets[index(bucketNum)];
153 if (bucket == nullptr) {
154 return 0;
155 }
156 const auto& itr = bucket->find(key);
157 return itr == bucket->end() ? 0 : itr->second;
158}
159
160int64_t AnomalyTracker::getSumOverPastBuckets(const HashableDimensionKey& key) const {
161 const auto& itr = mSumOverPastBuckets.find(key);
162 if (itr != mSumOverPastBuckets.end()) {
163 return itr->second;
164 }
165 return 0;
166}
167
Yangster-mace2cd6d52017-11-09 20:38:30 -0800168bool AnomalyTracker::detectAnomaly(const int64_t& currentBucketNum, const HashableDimensionKey& key,
169 const int64_t& currentBucketValue) {
170 if (currentBucketNum > mMostRecentBucketNum + 1) {
Bookatz1bf94382018-01-04 11:43:20 -0800171 // TODO: This creates a needless 0 entry in mSumOverPastBuckets. Fix this.
Yangster-mace2cd6d52017-11-09 20:38:30 -0800172 addPastBucket(key, 0, currentBucketNum - 1);
173 }
Bookatzcc5adef2017-11-21 14:36:23 -0800174 return mAlert.has_trigger_if_sum_gt()
175 && getSumOverPastBuckets(key) + currentBucketValue > mAlert.trigger_if_sum_gt();
Yangster-mace2cd6d52017-11-09 20:38:30 -0800176}
177
Bookatz1bf94382018-01-04 11:43:20 -0800178void AnomalyTracker::declareAnomaly(const uint64_t& timestampNs, const HashableDimensionKey& key) {
Bookatzcc5adef2017-11-21 14:36:23 -0800179 // TODO: Why receive timestamp? RefractoryPeriod should always be based on real time right now.
Bookatz1bf94382018-01-04 11:43:20 -0800180 if (isInRefractoryPeriod(timestampNs, key)) {
Bookatzcc5adef2017-11-21 14:36:23 -0800181 VLOG("Skipping anomaly declaration since within refractory period");
Yangster-mace2cd6d52017-11-09 20:38:30 -0800182 return;
183 }
Bookatz1bf94382018-01-04 11:43:20 -0800184 mRefractoryPeriodEndsSec[key] = (timestampNs / NS_PER_SEC) + mAlert.refractory_period_secs();
Bookatzcc5adef2017-11-21 14:36:23 -0800185
186 // TODO: If we had access to the bucket_size_millis, consider calling resetStorage()
187 // if (mAlert.refractory_period_secs() > mNumOfPastBuckets * bucketSizeNs) { resetStorage(); }
Yangster-mace2cd6d52017-11-09 20:38:30 -0800188
Yangster-mac94e197c2018-01-02 16:03:03 -0800189 if (!mSubscriptions.empty()) {
190 if (mAlert.has_id()) {
191 ALOGI("An anomaly (%llu) has occurred! Informing subscribers.",mAlert.id());
Bookatz1bf94382018-01-04 11:43:20 -0800192 informSubscribers(key);
Bookatzcc5adef2017-11-21 14:36:23 -0800193 } else {
Yangster-mac94e197c2018-01-02 16:03:03 -0800194 ALOGI("An anomaly (with no id) has occurred! Not informing any subscribers.");
Bookatzcc5adef2017-11-21 14:36:23 -0800195 }
Yangster-mace2cd6d52017-11-09 20:38:30 -0800196 } else {
Yangster-mac94e197c2018-01-02 16:03:03 -0800197 ALOGI("An anomaly has occurred! (But no subscriber for that alert.)");
Yangster-mace2cd6d52017-11-09 20:38:30 -0800198 }
Bookatz8f2f3d82017-12-07 13:53:21 -0800199
Yangster-mac94e197c2018-01-02 16:03:03 -0800200 StatsdStats::getInstance().noteAnomalyDeclared(mConfigKey, mAlert.id());
Bookatz8fcd09a2017-12-18 13:01:10 -0800201
Bookatz1bf94382018-01-04 11:43:20 -0800202 // TODO: This should also take in the const HashableDimensionKey& key?
Bookatz8fcd09a2017-12-18 13:01:10 -0800203 android::util::stats_write(android::util::ANOMALY_DETECTED, mConfigKey.GetUid(),
Yangster-mac94e197c2018-01-02 16:03:03 -0800204 mConfigKey.GetId(), mAlert.id());
Yangster-mace2cd6d52017-11-09 20:38:30 -0800205}
206
Bookatzcc5adef2017-11-21 14:36:23 -0800207void AnomalyTracker::detectAndDeclareAnomaly(const uint64_t& timestampNs,
Yangster-mace2cd6d52017-11-09 20:38:30 -0800208 const int64_t& currBucketNum,
209 const HashableDimensionKey& key,
210 const int64_t& currentBucketValue) {
211 if (detectAnomaly(currBucketNum, key, currentBucketValue)) {
Bookatz1bf94382018-01-04 11:43:20 -0800212 declareAnomaly(timestampNs, key);
Yangster-mace2cd6d52017-11-09 20:38:30 -0800213 }
214}
215
Bookatz1bf94382018-01-04 11:43:20 -0800216bool AnomalyTracker::isInRefractoryPeriod(const uint64_t& timestampNs,
217 const HashableDimensionKey& key) {
218 const auto& it = mRefractoryPeriodEndsSec.find(key);
219 if (it != mRefractoryPeriodEndsSec.end()) {
220 if ((timestampNs / NS_PER_SEC) <= it->second) {
221 return true;
222 } else {
223 mRefractoryPeriodEndsSec.erase(key);
224 }
Yangster-mace2cd6d52017-11-09 20:38:30 -0800225 }
Bookatz1bf94382018-01-04 11:43:20 -0800226 return false;
Yangster-mace2cd6d52017-11-09 20:38:30 -0800227}
228
Bookatz1bf94382018-01-04 11:43:20 -0800229void AnomalyTracker::informSubscribers(const HashableDimensionKey& key) {
Yangster-mac94e197c2018-01-02 16:03:03 -0800230 VLOG("informSubscribers called.");
231 if (mSubscriptions.empty()) {
232 ALOGE("Attempt to call with no subscribers.");
Bookatzd1fd2422017-11-22 15:21:03 -0800233 return;
234 }
235
Yangster-mac94e197c2018-01-02 16:03:03 -0800236 std::set<int> incidentdSections;
Bookatzc6977972018-01-16 16:55:05 -0800237
Yangster-mac94e197c2018-01-02 16:03:03 -0800238 for (const Subscription& subscription : mSubscriptions) {
239 switch (subscription.subscriber_information_case()) {
240 case Subscription::SubscriberInformationCase::kIncidentdDetails:
241 for (int i = 0; i < subscription.incidentd_details().section_size(); i++) {
242 incidentdSections.insert(subscription.incidentd_details().section(i));
243 }
244 break;
245 case Subscription::SubscriberInformationCase::kPerfettoDetails:
Primiano Tuccie4d44912018-01-10 12:14:50 +0000246 CollectPerfettoTraceAndUploadToDropbox(subscription.perfetto_details());
Yangster-mac94e197c2018-01-02 16:03:03 -0800247 break;
Bookatzc6977972018-01-16 16:55:05 -0800248 case Subscription::SubscriberInformationCase::kBroadcastSubscriberDetails:
249 SubscriberReporter::getInstance()
250 .alertBroadcastSubscriber(mConfigKey, subscription, key);
251 break;
Yangster-mac94e197c2018-01-02 16:03:03 -0800252 default:
253 break;
254 }
Bookatzd1fd2422017-11-22 15:21:03 -0800255 }
Yangster-mac94e197c2018-01-02 16:03:03 -0800256 if (!incidentdSections.empty()) {
257 sp<IIncidentManager> service = interface_cast<IIncidentManager>(
258 defaultServiceManager()->getService(android::String16("incident")));
259 if (service != NULL) {
260 IncidentReportArgs incidentReport;
261 for (const auto section : incidentdSections) {
262 incidentReport.addSection(section);
263 }
Yi Jin437aa6e2018-01-10 11:34:26 -0800264 android::os::IncidentHeaderProto header;
265 header.set_alert_id(mAlert.id());
266 header.mutable_config_key()->set_uid(mConfigKey.GetUid());
267 header.mutable_config_key()->set_id(mConfigKey.GetId());
Yangster-mac94e197c2018-01-02 16:03:03 -0800268 incidentReport.addHeader(header);
269 service->reportIncident(incidentReport);
270 } else {
271 ALOGW("Couldn't get the incident service.");
272 }
273 }
Bookatzd1fd2422017-11-22 15:21:03 -0800274}
275
Yangster-mace2cd6d52017-11-09 20:38:30 -0800276} // namespace statsd
277} // namespace os
278} // namespace android