blob: c347d5ca0f8bfd8618a00e65697b36841a59e437 [file] [log] [blame]
Yao Chen5154a372017-10-30 22:57:06 -07001/*
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 */
Yao Chen3c0b95c2017-12-16 14:34:20 -080016#define DEBUG false
Yao Chen5154a372017-10-30 22:57:06 -070017#include "Log.h"
18#include "OringDurationTracker.h"
Yao Chenb3561512017-11-21 18:07:17 -080019#include "guardrail/StatsdStats.h"
Yao Chen5154a372017-10-30 22:57:06 -070020
21namespace android {
22namespace os {
23namespace statsd {
Yao Chen0ea19902017-11-15 15:44:45 -080024
25using std::pair;
26
Bookatz857aaa52017-12-19 15:29:06 -080027OringDurationTracker::OringDurationTracker(
28 const ConfigKey& key, const string& name, const HashableDimensionKey& eventKey,
29 sp<ConditionWizard> wizard, int conditionIndex, bool nesting, uint64_t currentBucketStartNs,
30 uint64_t bucketSizeNs, const vector<sp<DurationAnomalyTracker>>& anomalyTrackers)
31
Yao Chenb3561512017-11-21 18:07:17 -080032 : DurationTracker(key, name, eventKey, wizard, conditionIndex, nesting, currentBucketStartNs,
Yao Chenf60e0ba2017-11-29 15:06:41 -080033 bucketSizeNs, anomalyTrackers),
Yao Chen5154a372017-10-30 22:57:06 -070034 mStarted(),
35 mPaused() {
36 mLastStartTime = 0;
37}
38
Yao Chenb3561512017-11-21 18:07:17 -080039bool OringDurationTracker::hitGuardRail(const HashableDimensionKey& newKey) {
40 // ===========GuardRail==============
41 // 1. Report the tuple count if the tuple count > soft limit
42 if (mConditionKeyMap.find(newKey) != mConditionKeyMap.end()) {
43 return false;
44 }
45 if (mConditionKeyMap.size() > StatsdStats::kDimensionKeySizeSoftLimit - 1) {
46 size_t newTupleCount = mConditionKeyMap.size() + 1;
Yao Chend5aa01b32017-12-19 16:46:36 -080047 StatsdStats::getInstance().noteMetricDimensionSize(mConfigKey, mName + mEventKey.toString(),
Yao Chenb3561512017-11-21 18:07:17 -080048 newTupleCount);
49 // 2. Don't add more tuples, we are above the allowed threshold. Drop the data.
50 if (newTupleCount > StatsdStats::kDimensionKeySizeHardLimit) {
51 ALOGE("OringDurTracker %s dropping data for dimension key %s", mName.c_str(),
52 newKey.c_str());
53 return true;
54 }
55 }
56 return false;
57}
58
Yao Chen5154a372017-10-30 22:57:06 -070059void OringDurationTracker::noteStart(const HashableDimensionKey& key, bool condition,
60 const uint64_t eventTime, const ConditionKey& conditionKey) {
Yao Chenb3561512017-11-21 18:07:17 -080061 if (hitGuardRail(key)) {
62 return;
63 }
Yao Chen5154a372017-10-30 22:57:06 -070064 if (condition) {
65 if (mStarted.size() == 0) {
66 mLastStartTime = eventTime;
67 VLOG("record first start....");
Yangster-mace2cd6d52017-11-09 20:38:30 -080068 startAnomalyAlarm(eventTime);
Yao Chen5154a372017-10-30 22:57:06 -070069 }
Yao Chen0ea19902017-11-15 15:44:45 -080070 mStarted[key]++;
Yao Chen5154a372017-10-30 22:57:06 -070071 } else {
Yao Chen0ea19902017-11-15 15:44:45 -080072 mPaused[key]++;
Yao Chen5154a372017-10-30 22:57:06 -070073 }
74
75 if (mConditionKeyMap.find(key) == mConditionKeyMap.end()) {
76 mConditionKeyMap[key] = conditionKey;
77 }
78
79 VLOG("Oring: %s start, condition %d", key.c_str(), condition);
80}
81
Yao Chen0ea19902017-11-15 15:44:45 -080082void OringDurationTracker::noteStop(const HashableDimensionKey& key, const uint64_t timestamp,
83 const bool stopAll) {
Yangster-mace2cd6d52017-11-09 20:38:30 -080084 declareAnomalyIfAlarmExpired(timestamp);
Yao Chen5154a372017-10-30 22:57:06 -070085 VLOG("Oring: %s stop", key.c_str());
86 auto it = mStarted.find(key);
87 if (it != mStarted.end()) {
Yao Chen0ea19902017-11-15 15:44:45 -080088 (it->second)--;
89 if (stopAll || !mNested || it->second <= 0) {
90 mStarted.erase(it);
91 mConditionKeyMap.erase(key);
92 }
Yao Chen5154a372017-10-30 22:57:06 -070093 if (mStarted.empty()) {
94 mDuration += (timestamp - mLastStartTime);
Yangster-mace2cd6d52017-11-09 20:38:30 -080095 detectAndDeclareAnomaly(timestamp, mCurrentBucketNum, mDuration);
Yao Chen5154a372017-10-30 22:57:06 -070096 VLOG("record duration %lld, total %lld ", (long long)timestamp - mLastStartTime,
97 (long long)mDuration);
98 }
99 }
100
Yao Chen0ea19902017-11-15 15:44:45 -0800101 auto pausedIt = mPaused.find(key);
102 if (pausedIt != mPaused.end()) {
103 (pausedIt->second)--;
104 if (stopAll || !mNested || pausedIt->second <= 0) {
105 mPaused.erase(pausedIt);
106 mConditionKeyMap.erase(key);
107 }
Yangster-mace2cd6d52017-11-09 20:38:30 -0800108 }
109 if (mStarted.empty()) {
110 stopAnomalyAlarm();
111 }
Yao Chen5154a372017-10-30 22:57:06 -0700112}
Yangster-mace2cd6d52017-11-09 20:38:30 -0800113
Yao Chen5154a372017-10-30 22:57:06 -0700114void OringDurationTracker::noteStopAll(const uint64_t timestamp) {
Yangster-mace2cd6d52017-11-09 20:38:30 -0800115 declareAnomalyIfAlarmExpired(timestamp);
Yao Chen5154a372017-10-30 22:57:06 -0700116 if (!mStarted.empty()) {
117 mDuration += (timestamp - mLastStartTime);
118 VLOG("Oring Stop all: record duration %lld %lld ", (long long)timestamp - mLastStartTime,
119 (long long)mDuration);
Yangster-mace2cd6d52017-11-09 20:38:30 -0800120 detectAndDeclareAnomaly(timestamp, mCurrentBucketNum, mDuration);
Yao Chen5154a372017-10-30 22:57:06 -0700121 }
122
Yangster-mace2cd6d52017-11-09 20:38:30 -0800123 stopAnomalyAlarm();
Yao Chen5154a372017-10-30 22:57:06 -0700124 mStarted.clear();
125 mPaused.clear();
126 mConditionKeyMap.clear();
127}
128
Yao Chenf60e0ba2017-11-29 15:06:41 -0800129bool OringDurationTracker::flushIfNeeded(
130 uint64_t eventTime, unordered_map<HashableDimensionKey, vector<DurationBucket>>* output) {
Yangster-mace2cd6d52017-11-09 20:38:30 -0800131 if (eventTime < mCurrentBucketStartTimeNs + mBucketSizeNs) {
Yao Chen5154a372017-10-30 22:57:06 -0700132 return false;
133 }
134 VLOG("OringDurationTracker Flushing.............");
135 // adjust the bucket start time
136 int numBucketsForward = (eventTime - mCurrentBucketStartTimeNs) / mBucketSizeNs;
Yangster-mace2cd6d52017-11-09 20:38:30 -0800137 DurationBucket current_info;
138 current_info.mBucketStartNs = mCurrentBucketStartTimeNs;
139 current_info.mBucketEndNs = current_info.mBucketStartNs + mBucketSizeNs;
140 current_info.mBucketNum = mCurrentBucketNum;
141 // Process the current bucket.
Yao Chen5154a372017-10-30 22:57:06 -0700142 if (mStarted.size() > 0) {
Yangster-mace2cd6d52017-11-09 20:38:30 -0800143 mDuration += (current_info.mBucketEndNs - mLastStartTime);
Yao Chen5154a372017-10-30 22:57:06 -0700144 }
Yangster-mace2cd6d52017-11-09 20:38:30 -0800145 if (mDuration > 0) {
146 current_info.mDuration = mDuration;
Yao Chenf60e0ba2017-11-29 15:06:41 -0800147 (*output)[mEventKey].push_back(current_info);
Yangster-mace2cd6d52017-11-09 20:38:30 -0800148 addPastBucketToAnomalyTrackers(current_info.mDuration, current_info.mBucketNum);
149 VLOG(" duration: %lld", (long long)current_info.mDuration);
Yao Chen5154a372017-10-30 22:57:06 -0700150 }
151
152 if (mStarted.size() > 0) {
153 for (int i = 1; i < numBucketsForward; i++) {
yro2b0f8862017-11-06 14:27:31 -0800154 DurationBucket info;
Yangster-mace2cd6d52017-11-09 20:38:30 -0800155 info.mBucketStartNs = mCurrentBucketStartTimeNs + mBucketSizeNs * i;
156 info.mBucketEndNs = info.mBucketStartNs + mBucketSizeNs;
157 info.mBucketNum = mCurrentBucketNum + i;
yro2b0f8862017-11-06 14:27:31 -0800158 info.mDuration = mBucketSizeNs;
Yao Chenf60e0ba2017-11-29 15:06:41 -0800159 (*output)[mEventKey].push_back(info);
160 addPastBucketToAnomalyTrackers(info.mDuration, info.mBucketNum);
161 VLOG(" add filling bucket with duration %lld", (long long)info.mDuration);
Yao Chen5154a372017-10-30 22:57:06 -0700162 }
163 }
Yangster-mace2cd6d52017-11-09 20:38:30 -0800164 mCurrentBucketStartTimeNs += numBucketsForward * mBucketSizeNs;
165 mCurrentBucketNum += numBucketsForward;
166
Yao Chen09294ef2017-11-25 19:54:01 -0800167 mLastStartTime = mCurrentBucketStartTimeNs;
Yao Chen5154a372017-10-30 22:57:06 -0700168 mDuration = 0;
169
170 // if all stopped, then tell owner it's safe to remove this tracker.
171 return mStarted.empty() && mPaused.empty();
172}
173
174void OringDurationTracker::onSlicedConditionMayChange(const uint64_t timestamp) {
Yangster-mace2cd6d52017-11-09 20:38:30 -0800175 declareAnomalyIfAlarmExpired(timestamp);
Yao Chen0ea19902017-11-15 15:44:45 -0800176 vector<pair<HashableDimensionKey, int>> startedToPaused;
177 vector<pair<HashableDimensionKey, int>> pausedToStarted;
Yao Chen5154a372017-10-30 22:57:06 -0700178 if (!mStarted.empty()) {
179 for (auto it = mStarted.begin(); it != mStarted.end();) {
Yao Chen0ea19902017-11-15 15:44:45 -0800180 const auto& key = it->first;
Yao Chen5154a372017-10-30 22:57:06 -0700181 if (mConditionKeyMap.find(key) == mConditionKeyMap.end()) {
182 VLOG("Key %s dont have condition key", key.c_str());
183 ++it;
184 continue;
185 }
186 if (mWizard->query(mConditionTrackerIndex, mConditionKeyMap[key]) !=
187 ConditionState::kTrue) {
Yao Chen0ea19902017-11-15 15:44:45 -0800188 startedToPaused.push_back(*it);
Yao Chen5154a372017-10-30 22:57:06 -0700189 it = mStarted.erase(it);
Yao Chen5154a372017-10-30 22:57:06 -0700190 VLOG("Key %s started -> paused", key.c_str());
191 } else {
192 ++it;
193 }
194 }
195
196 if (mStarted.empty()) {
Yao Chen09294ef2017-11-25 19:54:01 -0800197 mDuration += (timestamp - mLastStartTime);
Yao Chen5154a372017-10-30 22:57:06 -0700198 VLOG("Duration add %lld , to %lld ", (long long)(timestamp - mLastStartTime),
199 (long long)mDuration);
Yangster-mace2cd6d52017-11-09 20:38:30 -0800200 detectAndDeclareAnomaly(timestamp, mCurrentBucketNum, mDuration);
Yao Chen5154a372017-10-30 22:57:06 -0700201 }
202 }
203
204 if (!mPaused.empty()) {
205 for (auto it = mPaused.begin(); it != mPaused.end();) {
Yao Chen0ea19902017-11-15 15:44:45 -0800206 const auto& key = it->first;
Yao Chen5154a372017-10-30 22:57:06 -0700207 if (mConditionKeyMap.find(key) == mConditionKeyMap.end()) {
208 VLOG("Key %s dont have condition key", key.c_str());
209 ++it;
210 continue;
211 }
212 if (mWizard->query(mConditionTrackerIndex, mConditionKeyMap[key]) ==
213 ConditionState::kTrue) {
Yao Chen0ea19902017-11-15 15:44:45 -0800214 pausedToStarted.push_back(*it);
Yao Chen5154a372017-10-30 22:57:06 -0700215 it = mPaused.erase(it);
Yao Chen5154a372017-10-30 22:57:06 -0700216 VLOG("Key %s paused -> started", key.c_str());
217 } else {
218 ++it;
219 }
220 }
221
222 if (mStarted.empty() && pausedToStarted.size() > 0) {
223 mLastStartTime = timestamp;
224 }
225 }
226
Yangster-mace2cd6d52017-11-09 20:38:30 -0800227 if (mStarted.empty() && !pausedToStarted.empty()) {
228 startAnomalyAlarm(timestamp);
229 }
Yao Chen5154a372017-10-30 22:57:06 -0700230 mStarted.insert(pausedToStarted.begin(), pausedToStarted.end());
231 mPaused.insert(startedToPaused.begin(), startedToPaused.end());
Yangster-mace2cd6d52017-11-09 20:38:30 -0800232
233 if (mStarted.empty()) {
234 stopAnomalyAlarm();
235 }
Yao Chen5154a372017-10-30 22:57:06 -0700236}
237
238void OringDurationTracker::onConditionChanged(bool condition, const uint64_t timestamp) {
Yangster-mace2cd6d52017-11-09 20:38:30 -0800239 declareAnomalyIfAlarmExpired(timestamp);
Yao Chen5154a372017-10-30 22:57:06 -0700240 if (condition) {
241 if (!mPaused.empty()) {
242 VLOG("Condition true, all started");
243 if (mStarted.empty()) {
Yao Chen09294ef2017-11-25 19:54:01 -0800244 mLastStartTime = timestamp;
Yangster-mace2cd6d52017-11-09 20:38:30 -0800245 }
246 if (mStarted.empty() && !mPaused.empty()) {
247 startAnomalyAlarm(timestamp);
Yao Chen5154a372017-10-30 22:57:06 -0700248 }
249 mStarted.insert(mPaused.begin(), mPaused.end());
Yangster-mace2cd6d52017-11-09 20:38:30 -0800250 mPaused.clear();
Yao Chen5154a372017-10-30 22:57:06 -0700251 }
252 } else {
253 if (!mStarted.empty()) {
254 VLOG("Condition false, all paused");
Yao Chen09294ef2017-11-25 19:54:01 -0800255 mDuration += (timestamp - mLastStartTime);
Yao Chen5154a372017-10-30 22:57:06 -0700256 mPaused.insert(mStarted.begin(), mStarted.end());
Yangster-mace2cd6d52017-11-09 20:38:30 -0800257 mStarted.clear();
258 detectAndDeclareAnomaly(timestamp, mCurrentBucketNum, mDuration);
Yao Chen5154a372017-10-30 22:57:06 -0700259 }
260 }
Yangster-mace2cd6d52017-11-09 20:38:30 -0800261 if (mStarted.empty()) {
262 stopAnomalyAlarm();
263 }
264}
265
Bookatz857aaa52017-12-19 15:29:06 -0800266int64_t OringDurationTracker::predictAnomalyTimestampNs(
267 const DurationAnomalyTracker& anomalyTracker, const uint64_t eventTimestampNs) const {
Yangster-mace2cd6d52017-11-09 20:38:30 -0800268 // TODO: Unit-test this and see if it can be done more efficiently (e.g. use int32).
269 // All variables below represent durations (not timestamps).
270
271 // The time until the current bucket ends. This is how much more 'space' it can hold.
272 const int64_t currRemainingBucketSizeNs =
273 mBucketSizeNs - (eventTimestampNs - mCurrentBucketStartTimeNs);
274 // TODO: This should never be < 0. Document/guard against possible failures if it is.
275
276 const int64_t thresholdNs = anomalyTracker.getAnomalyThreshold();
277
278 // As we move into the future, old buckets get overwritten (so their old data is erased).
279
280 // Sum of past durations. Will change as we overwrite old buckets.
281 int64_t pastNs = mDuration;
282 pastNs += anomalyTracker.getSumOverPastBuckets(mEventKey);
283
284 // How much of the threshold is still unaccounted after considering pastNs.
285 int64_t leftNs = thresholdNs - pastNs;
286
287 // First deal with the remainder of the current bucket.
288 if (leftNs <= currRemainingBucketSizeNs) { // Predict the anomaly will occur in this bucket.
289 return eventTimestampNs + leftNs;
290 }
291 // The remainder of this bucket contributes, but we must then move to the next bucket.
292 pastNs += currRemainingBucketSizeNs;
293
294 // Now deal with the past buckets, starting with the oldest.
Bookatzcc5adef2017-11-21 14:36:23 -0800295 for (int futBucketIdx = 0; futBucketIdx < anomalyTracker.getNumOfPastBuckets();
Yangster-mace2cd6d52017-11-09 20:38:30 -0800296 futBucketIdx++) {
297 // We now overwrite the oldest bucket with the previous 'current', and start a new
298 // 'current'.
299 pastNs -= anomalyTracker.getPastBucketValue(
Bookatzcc5adef2017-11-21 14:36:23 -0800300 mEventKey, mCurrentBucketNum - anomalyTracker.getNumOfPastBuckets() + futBucketIdx);
Yangster-mace2cd6d52017-11-09 20:38:30 -0800301 leftNs = thresholdNs - pastNs;
302 if (leftNs <= mBucketSizeNs) { // Predict anomaly will occur in this bucket.
303 return eventTimestampNs + currRemainingBucketSizeNs + (futBucketIdx * mBucketSizeNs) +
304 leftNs;
305 } else { // This bucket would be entirely filled, and we'll need to move to the next
306 // bucket.
307 pastNs += mBucketSizeNs;
308 }
309 }
310
311 // If we have reached this point, we even have to overwrite the the original current bucket.
312 // Thus, none of the past data will still be extant - pastNs is now 0.
313 return eventTimestampNs + thresholdNs;
Yao Chen5154a372017-10-30 22:57:06 -0700314}
315
316} // namespace statsd
317} // namespace os
318} // namespace android