blob: 6bf42287e6dd9e0b3c9894d81b44f13056cae374 [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(
Yangster-mac94e197c2018-01-02 16:03:03 -080028 const ConfigKey& key, const int64_t& id, const HashableDimensionKey& eventKey,
Bookatz857aaa52017-12-19 15:29:06 -080029 sp<ConditionWizard> wizard, int conditionIndex, bool nesting, uint64_t currentBucketStartNs,
Yao Chend59a6582018-01-08 11:17:11 -080030 uint64_t bucketSizeNs, bool conditionSliced,
31 const vector<sp<DurationAnomalyTracker>>& anomalyTrackers)
Yangster-mac94e197c2018-01-02 16:03:03 -080032 : DurationTracker(key, id, eventKey, wizard, conditionIndex, nesting, currentBucketStartNs,
Yao Chend59a6582018-01-08 11:17:11 -080033 bucketSizeNs, conditionSliced, 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;
Yangster-mac94e197c2018-01-02 16:03:03 -080047 StatsdStats::getInstance().noteMetricDimensionSize(
48 mConfigKey, hashDimensionsValue(mTrackerId, mEventKey.getDimensionsValue()),
49 newTupleCount);
Yao Chenb3561512017-11-21 18:07:17 -080050 // 2. Don't add more tuples, we are above the allowed threshold. Drop the data.
51 if (newTupleCount > StatsdStats::kDimensionKeySizeHardLimit) {
Yangster-mac94e197c2018-01-02 16:03:03 -080052 ALOGE("OringDurTracker %lld dropping data for dimension key %s",
53 (long long)mTrackerId, newKey.c_str());
Yao Chenb3561512017-11-21 18:07:17 -080054 return true;
55 }
56 }
57 return false;
58}
59
Yao Chen5154a372017-10-30 22:57:06 -070060void OringDurationTracker::noteStart(const HashableDimensionKey& key, bool condition,
61 const uint64_t eventTime, const ConditionKey& conditionKey) {
Yao Chenb3561512017-11-21 18:07:17 -080062 if (hitGuardRail(key)) {
63 return;
64 }
Yao Chen5154a372017-10-30 22:57:06 -070065 if (condition) {
66 if (mStarted.size() == 0) {
67 mLastStartTime = eventTime;
68 VLOG("record first start....");
Yangster-mace2cd6d52017-11-09 20:38:30 -080069 startAnomalyAlarm(eventTime);
Yao Chen5154a372017-10-30 22:57:06 -070070 }
Yao Chen0ea19902017-11-15 15:44:45 -080071 mStarted[key]++;
Yao Chen5154a372017-10-30 22:57:06 -070072 } else {
Yao Chen0ea19902017-11-15 15:44:45 -080073 mPaused[key]++;
Yao Chen5154a372017-10-30 22:57:06 -070074 }
75
Yao Chend59a6582018-01-08 11:17:11 -080076 if (mConditionSliced && mConditionKeyMap.find(key) == mConditionKeyMap.end()) {
Yao Chen5154a372017-10-30 22:57:06 -070077 mConditionKeyMap[key] = conditionKey;
78 }
79
80 VLOG("Oring: %s start, condition %d", key.c_str(), condition);
81}
82
Yao Chen0ea19902017-11-15 15:44:45 -080083void OringDurationTracker::noteStop(const HashableDimensionKey& key, const uint64_t timestamp,
84 const bool stopAll) {
Yangster-mace2cd6d52017-11-09 20:38:30 -080085 declareAnomalyIfAlarmExpired(timestamp);
Yao Chen5154a372017-10-30 22:57:06 -070086 VLOG("Oring: %s stop", key.c_str());
87 auto it = mStarted.find(key);
88 if (it != mStarted.end()) {
Yao Chen0ea19902017-11-15 15:44:45 -080089 (it->second)--;
90 if (stopAll || !mNested || it->second <= 0) {
91 mStarted.erase(it);
92 mConditionKeyMap.erase(key);
93 }
Yao Chen5154a372017-10-30 22:57:06 -070094 if (mStarted.empty()) {
95 mDuration += (timestamp - mLastStartTime);
Yangster-mace2cd6d52017-11-09 20:38:30 -080096 detectAndDeclareAnomaly(timestamp, mCurrentBucketNum, mDuration);
Yao Chen5154a372017-10-30 22:57:06 -070097 VLOG("record duration %lld, total %lld ", (long long)timestamp - mLastStartTime,
98 (long long)mDuration);
99 }
100 }
101
Yao Chen0ea19902017-11-15 15:44:45 -0800102 auto pausedIt = mPaused.find(key);
103 if (pausedIt != mPaused.end()) {
104 (pausedIt->second)--;
105 if (stopAll || !mNested || pausedIt->second <= 0) {
106 mPaused.erase(pausedIt);
107 mConditionKeyMap.erase(key);
108 }
Yangster-mace2cd6d52017-11-09 20:38:30 -0800109 }
110 if (mStarted.empty()) {
111 stopAnomalyAlarm();
112 }
Yao Chen5154a372017-10-30 22:57:06 -0700113}
Yangster-mace2cd6d52017-11-09 20:38:30 -0800114
Yao Chen5154a372017-10-30 22:57:06 -0700115void OringDurationTracker::noteStopAll(const uint64_t timestamp) {
Yangster-mace2cd6d52017-11-09 20:38:30 -0800116 declareAnomalyIfAlarmExpired(timestamp);
Yao Chen5154a372017-10-30 22:57:06 -0700117 if (!mStarted.empty()) {
118 mDuration += (timestamp - mLastStartTime);
119 VLOG("Oring Stop all: record duration %lld %lld ", (long long)timestamp - mLastStartTime,
120 (long long)mDuration);
Yangster-mace2cd6d52017-11-09 20:38:30 -0800121 detectAndDeclareAnomaly(timestamp, mCurrentBucketNum, mDuration);
Yao Chen5154a372017-10-30 22:57:06 -0700122 }
123
Yangster-mace2cd6d52017-11-09 20:38:30 -0800124 stopAnomalyAlarm();
Yao Chen5154a372017-10-30 22:57:06 -0700125 mStarted.clear();
126 mPaused.clear();
127 mConditionKeyMap.clear();
128}
129
Yao Chenf60e0ba2017-11-29 15:06:41 -0800130bool OringDurationTracker::flushIfNeeded(
131 uint64_t eventTime, unordered_map<HashableDimensionKey, vector<DurationBucket>>* output) {
Yangster-mace2cd6d52017-11-09 20:38:30 -0800132 if (eventTime < mCurrentBucketStartTimeNs + mBucketSizeNs) {
Yao Chen5154a372017-10-30 22:57:06 -0700133 return false;
134 }
135 VLOG("OringDurationTracker Flushing.............");
136 // adjust the bucket start time
137 int numBucketsForward = (eventTime - mCurrentBucketStartTimeNs) / mBucketSizeNs;
Yangster-mace2cd6d52017-11-09 20:38:30 -0800138 DurationBucket current_info;
139 current_info.mBucketStartNs = mCurrentBucketStartTimeNs;
140 current_info.mBucketEndNs = current_info.mBucketStartNs + mBucketSizeNs;
141 current_info.mBucketNum = mCurrentBucketNum;
142 // Process the current bucket.
Yao Chen5154a372017-10-30 22:57:06 -0700143 if (mStarted.size() > 0) {
Yangster-mace2cd6d52017-11-09 20:38:30 -0800144 mDuration += (current_info.mBucketEndNs - mLastStartTime);
Yao Chen5154a372017-10-30 22:57:06 -0700145 }
Yangster-mace2cd6d52017-11-09 20:38:30 -0800146 if (mDuration > 0) {
147 current_info.mDuration = mDuration;
Yao Chenf60e0ba2017-11-29 15:06:41 -0800148 (*output)[mEventKey].push_back(current_info);
Yangster-mace2cd6d52017-11-09 20:38:30 -0800149 addPastBucketToAnomalyTrackers(current_info.mDuration, current_info.mBucketNum);
150 VLOG(" duration: %lld", (long long)current_info.mDuration);
Yao Chen5154a372017-10-30 22:57:06 -0700151 }
152
153 if (mStarted.size() > 0) {
154 for (int i = 1; i < numBucketsForward; i++) {
yro2b0f8862017-11-06 14:27:31 -0800155 DurationBucket info;
Yangster-mace2cd6d52017-11-09 20:38:30 -0800156 info.mBucketStartNs = mCurrentBucketStartTimeNs + mBucketSizeNs * i;
157 info.mBucketEndNs = info.mBucketStartNs + mBucketSizeNs;
158 info.mBucketNum = mCurrentBucketNum + i;
yro2b0f8862017-11-06 14:27:31 -0800159 info.mDuration = mBucketSizeNs;
Yao Chenf60e0ba2017-11-29 15:06:41 -0800160 (*output)[mEventKey].push_back(info);
161 addPastBucketToAnomalyTrackers(info.mDuration, info.mBucketNum);
162 VLOG(" add filling bucket with duration %lld", (long long)info.mDuration);
Yao Chen5154a372017-10-30 22:57:06 -0700163 }
164 }
Yangster-mace2cd6d52017-11-09 20:38:30 -0800165 mCurrentBucketStartTimeNs += numBucketsForward * mBucketSizeNs;
166 mCurrentBucketNum += numBucketsForward;
167
Yao Chen09294ef2017-11-25 19:54:01 -0800168 mLastStartTime = mCurrentBucketStartTimeNs;
Yao Chen5154a372017-10-30 22:57:06 -0700169 mDuration = 0;
170
171 // if all stopped, then tell owner it's safe to remove this tracker.
172 return mStarted.empty() && mPaused.empty();
173}
174
175void OringDurationTracker::onSlicedConditionMayChange(const uint64_t timestamp) {
Yangster-mace2cd6d52017-11-09 20:38:30 -0800176 declareAnomalyIfAlarmExpired(timestamp);
Yao Chen0ea19902017-11-15 15:44:45 -0800177 vector<pair<HashableDimensionKey, int>> startedToPaused;
178 vector<pair<HashableDimensionKey, int>> pausedToStarted;
Yao Chen5154a372017-10-30 22:57:06 -0700179 if (!mStarted.empty()) {
180 for (auto it = mStarted.begin(); it != mStarted.end();) {
Yao Chen0ea19902017-11-15 15:44:45 -0800181 const auto& key = it->first;
Yao Chen5154a372017-10-30 22:57:06 -0700182 if (mConditionKeyMap.find(key) == mConditionKeyMap.end()) {
183 VLOG("Key %s dont have condition key", key.c_str());
184 ++it;
185 continue;
186 }
187 if (mWizard->query(mConditionTrackerIndex, mConditionKeyMap[key]) !=
188 ConditionState::kTrue) {
Yao Chen0ea19902017-11-15 15:44:45 -0800189 startedToPaused.push_back(*it);
Yao Chen5154a372017-10-30 22:57:06 -0700190 it = mStarted.erase(it);
Yao Chen5154a372017-10-30 22:57:06 -0700191 VLOG("Key %s started -> paused", key.c_str());
192 } else {
193 ++it;
194 }
195 }
196
197 if (mStarted.empty()) {
Yao Chen09294ef2017-11-25 19:54:01 -0800198 mDuration += (timestamp - mLastStartTime);
Yao Chen5154a372017-10-30 22:57:06 -0700199 VLOG("Duration add %lld , to %lld ", (long long)(timestamp - mLastStartTime),
200 (long long)mDuration);
Yangster-mace2cd6d52017-11-09 20:38:30 -0800201 detectAndDeclareAnomaly(timestamp, mCurrentBucketNum, mDuration);
Yao Chen5154a372017-10-30 22:57:06 -0700202 }
203 }
204
205 if (!mPaused.empty()) {
206 for (auto it = mPaused.begin(); it != mPaused.end();) {
Yao Chen0ea19902017-11-15 15:44:45 -0800207 const auto& key = it->first;
Yao Chen5154a372017-10-30 22:57:06 -0700208 if (mConditionKeyMap.find(key) == mConditionKeyMap.end()) {
209 VLOG("Key %s dont have condition key", key.c_str());
210 ++it;
211 continue;
212 }
213 if (mWizard->query(mConditionTrackerIndex, mConditionKeyMap[key]) ==
214 ConditionState::kTrue) {
Yao Chen0ea19902017-11-15 15:44:45 -0800215 pausedToStarted.push_back(*it);
Yao Chen5154a372017-10-30 22:57:06 -0700216 it = mPaused.erase(it);
Yao Chen5154a372017-10-30 22:57:06 -0700217 VLOG("Key %s paused -> started", key.c_str());
218 } else {
219 ++it;
220 }
221 }
222
223 if (mStarted.empty() && pausedToStarted.size() > 0) {
224 mLastStartTime = timestamp;
225 }
226 }
227
Yangster-mace2cd6d52017-11-09 20:38:30 -0800228 if (mStarted.empty() && !pausedToStarted.empty()) {
229 startAnomalyAlarm(timestamp);
230 }
Yao Chen5154a372017-10-30 22:57:06 -0700231 mStarted.insert(pausedToStarted.begin(), pausedToStarted.end());
232 mPaused.insert(startedToPaused.begin(), startedToPaused.end());
Yangster-mace2cd6d52017-11-09 20:38:30 -0800233
234 if (mStarted.empty()) {
235 stopAnomalyAlarm();
236 }
Yao Chen5154a372017-10-30 22:57:06 -0700237}
238
239void OringDurationTracker::onConditionChanged(bool condition, const uint64_t timestamp) {
Yangster-mace2cd6d52017-11-09 20:38:30 -0800240 declareAnomalyIfAlarmExpired(timestamp);
Yao Chen5154a372017-10-30 22:57:06 -0700241 if (condition) {
242 if (!mPaused.empty()) {
243 VLOG("Condition true, all started");
244 if (mStarted.empty()) {
Yao Chen09294ef2017-11-25 19:54:01 -0800245 mLastStartTime = timestamp;
Yangster-mace2cd6d52017-11-09 20:38:30 -0800246 }
247 if (mStarted.empty() && !mPaused.empty()) {
248 startAnomalyAlarm(timestamp);
Yao Chen5154a372017-10-30 22:57:06 -0700249 }
250 mStarted.insert(mPaused.begin(), mPaused.end());
Yangster-mace2cd6d52017-11-09 20:38:30 -0800251 mPaused.clear();
Yao Chen5154a372017-10-30 22:57:06 -0700252 }
253 } else {
254 if (!mStarted.empty()) {
255 VLOG("Condition false, all paused");
Yao Chen09294ef2017-11-25 19:54:01 -0800256 mDuration += (timestamp - mLastStartTime);
Yao Chen5154a372017-10-30 22:57:06 -0700257 mPaused.insert(mStarted.begin(), mStarted.end());
Yangster-mace2cd6d52017-11-09 20:38:30 -0800258 mStarted.clear();
259 detectAndDeclareAnomaly(timestamp, mCurrentBucketNum, mDuration);
Yao Chen5154a372017-10-30 22:57:06 -0700260 }
261 }
Yangster-mace2cd6d52017-11-09 20:38:30 -0800262 if (mStarted.empty()) {
263 stopAnomalyAlarm();
264 }
265}
266
Bookatz857aaa52017-12-19 15:29:06 -0800267int64_t OringDurationTracker::predictAnomalyTimestampNs(
268 const DurationAnomalyTracker& anomalyTracker, const uint64_t eventTimestampNs) const {
Yangster-mace2cd6d52017-11-09 20:38:30 -0800269 // TODO: Unit-test this and see if it can be done more efficiently (e.g. use int32).
270 // All variables below represent durations (not timestamps).
271
272 // The time until the current bucket ends. This is how much more 'space' it can hold.
273 const int64_t currRemainingBucketSizeNs =
274 mBucketSizeNs - (eventTimestampNs - mCurrentBucketStartTimeNs);
275 // TODO: This should never be < 0. Document/guard against possible failures if it is.
276
277 const int64_t thresholdNs = anomalyTracker.getAnomalyThreshold();
278
279 // As we move into the future, old buckets get overwritten (so their old data is erased).
280
281 // Sum of past durations. Will change as we overwrite old buckets.
282 int64_t pastNs = mDuration;
283 pastNs += anomalyTracker.getSumOverPastBuckets(mEventKey);
284
285 // How much of the threshold is still unaccounted after considering pastNs.
286 int64_t leftNs = thresholdNs - pastNs;
287
288 // First deal with the remainder of the current bucket.
289 if (leftNs <= currRemainingBucketSizeNs) { // Predict the anomaly will occur in this bucket.
290 return eventTimestampNs + leftNs;
291 }
292 // The remainder of this bucket contributes, but we must then move to the next bucket.
293 pastNs += currRemainingBucketSizeNs;
294
295 // Now deal with the past buckets, starting with the oldest.
Bookatzcc5adef2017-11-21 14:36:23 -0800296 for (int futBucketIdx = 0; futBucketIdx < anomalyTracker.getNumOfPastBuckets();
Yangster-mace2cd6d52017-11-09 20:38:30 -0800297 futBucketIdx++) {
298 // We now overwrite the oldest bucket with the previous 'current', and start a new
299 // 'current'.
300 pastNs -= anomalyTracker.getPastBucketValue(
Bookatzcc5adef2017-11-21 14:36:23 -0800301 mEventKey, mCurrentBucketNum - anomalyTracker.getNumOfPastBuckets() + futBucketIdx);
Yangster-mace2cd6d52017-11-09 20:38:30 -0800302 leftNs = thresholdNs - pastNs;
303 if (leftNs <= mBucketSizeNs) { // Predict anomaly will occur in this bucket.
304 return eventTimestampNs + currRemainingBucketSizeNs + (futBucketIdx * mBucketSizeNs) +
305 leftNs;
306 } else { // This bucket would be entirely filled, and we'll need to move to the next
307 // bucket.
308 pastNs += mBucketSizeNs;
309 }
310 }
311
312 // If we have reached this point, we even have to overwrite the the original current bucket.
313 // Thus, none of the past data will still be extant - pastNs is now 0.
314 return eventTimestampNs + thresholdNs;
Yao Chen5154a372017-10-30 22:57:06 -0700315}
316
317} // namespace statsd
318} // namespace os
319} // namespace android