blob: d8a8e238fa5ccc265536d7a9a9428d0bfe1b72b6 [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 */
16
Yao Chen3c0b95c2017-12-16 14:34:20 -080017#define DEBUG false
Yao Chen5154a372017-10-30 22:57:06 -070018
19#include "Log.h"
20#include "MaxDurationTracker.h"
Yao Chenb3561512017-11-21 18:07:17 -080021#include "guardrail/StatsdStats.h"
Yao Chen5154a372017-10-30 22:57:06 -070022
23namespace android {
24namespace os {
25namespace statsd {
26
Yao Chenb3561512017-11-21 18:07:17 -080027MaxDurationTracker::MaxDurationTracker(const ConfigKey& key, const string& name,
28 const HashableDimensionKey& eventKey,
Yangster-mace2cd6d52017-11-09 20:38:30 -080029 sp<ConditionWizard> wizard, int conditionIndex, bool nesting,
Yao Chen5154a372017-10-30 22:57:06 -070030 uint64_t currentBucketStartNs, uint64_t bucketSizeNs,
Bookatz857aaa52017-12-19 15:29:06 -080031 const vector<sp<DurationAnomalyTracker>>& anomalyTrackers)
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 Chenb3561512017-11-21 18:07:17 -080034}
35
36bool MaxDurationTracker::hitGuardRail(const HashableDimensionKey& newKey) {
37 // ===========GuardRail==============
38 if (mInfos.find(newKey) != mInfos.end()) {
39 // if the key existed, we are good!
40 return false;
41 }
42 // 1. Report the tuple count if the tuple count > soft limit
43 if (mInfos.size() > StatsdStats::kDimensionKeySizeSoftLimit - 1) {
44 size_t newTupleCount = mInfos.size() + 1;
Yao Chend5aa01b32017-12-19 16:46:36 -080045 StatsdStats::getInstance().noteMetricDimensionSize(mConfigKey, mName + mEventKey.toString(),
Yao Chenb3561512017-11-21 18:07:17 -080046 newTupleCount);
47 // 2. Don't add more tuples, we are above the allowed threshold. Drop the data.
48 if (newTupleCount > StatsdStats::kDimensionKeySizeHardLimit) {
49 ALOGE("MaxDurTracker %s dropping data for dimension key %s", mName.c_str(),
50 newKey.c_str());
51 return true;
52 }
53 }
54 return false;
Yao Chen5154a372017-10-30 22:57:06 -070055}
56
57void MaxDurationTracker::noteStart(const HashableDimensionKey& key, bool condition,
58 const uint64_t eventTime, const ConditionKey& conditionKey) {
59 // this will construct a new DurationInfo if this key didn't exist.
Yao Chenb3561512017-11-21 18:07:17 -080060 if (hitGuardRail(key)) {
61 return;
62 }
63
Yao Chen5154a372017-10-30 22:57:06 -070064 DurationInfo& duration = mInfos[key];
65 duration.conditionKeys = conditionKey;
66 VLOG("MaxDuration: key %s start condition %d", key.c_str(), condition);
67
68 switch (duration.state) {
69 case kStarted:
Yao Chen0ea19902017-11-15 15:44:45 -080070 duration.startCount++;
Yao Chen5154a372017-10-30 22:57:06 -070071 break;
72 case kPaused:
Yao Chen0ea19902017-11-15 15:44:45 -080073 duration.startCount++;
Yao Chen5154a372017-10-30 22:57:06 -070074 break;
75 case kStopped:
76 if (!condition) {
77 // event started, but we need to wait for the condition to become true.
78 duration.state = DurationState::kPaused;
79 } else {
80 duration.state = DurationState::kStarted;
81 duration.lastStartTime = eventTime;
82 }
Yao Chen0ea19902017-11-15 15:44:45 -080083 duration.startCount = 1;
Yao Chen5154a372017-10-30 22:57:06 -070084 break;
85 }
86}
87
Yangster-mace2cd6d52017-11-09 20:38:30 -080088
Yao Chen0ea19902017-11-15 15:44:45 -080089void MaxDurationTracker::noteStop(const HashableDimensionKey& key, const uint64_t eventTime,
90 bool forceStop) {
Yangster-mace2cd6d52017-11-09 20:38:30 -080091 declareAnomalyIfAlarmExpired(eventTime);
Yao Chen5154a372017-10-30 22:57:06 -070092 VLOG("MaxDuration: key %s stop", key.c_str());
93 if (mInfos.find(key) == mInfos.end()) {
94 // we didn't see a start event before. do nothing.
95 return;
96 }
97 DurationInfo& duration = mInfos[key];
98
99 switch (duration.state) {
100 case DurationState::kStopped:
101 // already stopped, do nothing.
102 break;
103 case DurationState::kStarted: {
Yao Chen0ea19902017-11-15 15:44:45 -0800104 duration.startCount--;
105 if (forceStop || !mNested || duration.startCount <= 0) {
106 duration.state = DurationState::kStopped;
107 int64_t durationTime = eventTime - duration.lastStartTime;
108 VLOG("Max, key %s, Stop %lld %lld %lld", key.c_str(),
109 (long long)duration.lastStartTime, (long long)eventTime,
110 (long long)durationTime);
111 duration.lastDuration = duration.lastDuration + durationTime;
112 VLOG(" record duration: %lld ", (long long)duration.lastDuration);
113 }
Yao Chen5154a372017-10-30 22:57:06 -0700114 break;
115 }
116 case DurationState::kPaused: {
Yao Chen0ea19902017-11-15 15:44:45 -0800117 duration.startCount--;
118 if (forceStop || !mNested || duration.startCount <= 0) {
119 duration.state = DurationState::kStopped;
120 }
Yao Chen5154a372017-10-30 22:57:06 -0700121 break;
122 }
123 }
124
125 if (duration.lastDuration > mDuration) {
126 mDuration = duration.lastDuration;
Yangster-mace2cd6d52017-11-09 20:38:30 -0800127 detectAndDeclareAnomaly(eventTime, mCurrentBucketNum, mDuration);
Yao Chen5154a372017-10-30 22:57:06 -0700128 VLOG("Max: new max duration: %lld", (long long)mDuration);
129 }
130 // Once an atom duration ends, we erase it. Next time, if we see another atom event with the
131 // same name, they are still considered as different atom durations.
Yao Chen0ea19902017-11-15 15:44:45 -0800132 if (duration.state == DurationState::kStopped) {
133 mInfos.erase(key);
134 }
Yao Chen5154a372017-10-30 22:57:06 -0700135}
Yangster-mace2cd6d52017-11-09 20:38:30 -0800136
Yao Chen5154a372017-10-30 22:57:06 -0700137void MaxDurationTracker::noteStopAll(const uint64_t eventTime) {
Yangster-mace2cd6d52017-11-09 20:38:30 -0800138 std::set<HashableDimensionKey> keys;
139 for (const auto& pair : mInfos) {
140 keys.insert(pair.first);
141 }
142 for (auto& key : keys) {
143 noteStop(key, eventTime, true);
Yao Chen5154a372017-10-30 22:57:06 -0700144 }
145}
146
Yao Chenf60e0ba2017-11-29 15:06:41 -0800147bool MaxDurationTracker::flushIfNeeded(
148 uint64_t eventTime, unordered_map<HashableDimensionKey, vector<DurationBucket>>* output) {
Yao Chen5154a372017-10-30 22:57:06 -0700149 if (mCurrentBucketStartTimeNs + mBucketSizeNs > eventTime) {
150 return false;
151 }
152
153 VLOG("MaxDurationTracker flushing.....");
154
155 // adjust the bucket start time
156 int numBucketsForward = (eventTime - mCurrentBucketStartTimeNs) / mBucketSizeNs;
157
Yao Chen5154a372017-10-30 22:57:06 -0700158 uint64_t endTime = mCurrentBucketStartTimeNs + mBucketSizeNs;
yro2b0f8862017-11-06 14:27:31 -0800159
160 DurationBucket info;
161 info.mBucketStartNs = mCurrentBucketStartTimeNs;
162 info.mBucketEndNs = endTime;
Yangster-mace2cd6d52017-11-09 20:38:30 -0800163 info.mBucketNum = mCurrentBucketNum;
Yao Chen5154a372017-10-30 22:57:06 -0700164
165 uint64_t oldBucketStartTimeNs = mCurrentBucketStartTimeNs;
166 mCurrentBucketStartTimeNs += (numBucketsForward)*mBucketSizeNs;
167
168 bool hasOnGoingStartedEvent = false; // a kStarted event last across bucket boundaries.
169 bool hasPendingEvent =
170 false; // has either a kStarted or kPaused event across bucket boundaries
171 // meaning we need to carry them over to the new bucket.
172 for (auto it = mInfos.begin(); it != mInfos.end(); ++it) {
173 int64_t finalDuration = it->second.lastDuration;
174 if (it->second.state == kStarted) {
175 // the event is still on-going, duration needs to be updated.
176 // |..lastDurationTime_recorded...last_start -----|bucket_end. We need to record the
177 // duration between lastStartTime and bucketEnd.
178 int64_t durationTime = endTime - it->second.lastStartTime;
179
180 finalDuration += durationTime;
181 VLOG(" unrecorded %lld -> %lld", (long long)(durationTime), (long long)finalDuration);
182 // if the event is still on-going, we need to fill the buckets between prev_bucket and
183 // now_bucket. |prev_bucket|...|..|...|now_bucket|
184 hasOnGoingStartedEvent = true;
185 }
186
187 if (finalDuration > mDuration) {
188 mDuration = finalDuration;
189 }
190
191 if (it->second.state == DurationState::kStopped) {
192 // No need to keep buckets for events that were stopped before.
193 mInfos.erase(it);
194 } else {
195 hasPendingEvent = true;
196 // for kPaused, and kStarted event, we will keep track of them, and reset the start time
197 // and duration.
198 it->second.lastStartTime = mCurrentBucketStartTimeNs;
199 it->second.lastDuration = 0;
200 }
201 }
202
203 if (mDuration != 0) {
yro2b0f8862017-11-06 14:27:31 -0800204 info.mDuration = mDuration;
Yao Chenf60e0ba2017-11-29 15:06:41 -0800205 (*output)[mEventKey].push_back(info);
Yangster-mace2cd6d52017-11-09 20:38:30 -0800206 addPastBucketToAnomalyTrackers(info.mDuration, info.mBucketNum);
Yao Chen5154a372017-10-30 22:57:06 -0700207 VLOG(" final duration for last bucket: %lld", (long long)mDuration);
208 }
209
210 mDuration = 0;
211 if (hasOnGoingStartedEvent) {
212 for (int i = 1; i < numBucketsForward; i++) {
yro2b0f8862017-11-06 14:27:31 -0800213 DurationBucket info;
214 info.mBucketStartNs = oldBucketStartTimeNs + mBucketSizeNs * i;
215 info.mBucketEndNs = endTime + mBucketSizeNs * i;
Yangster-mace2cd6d52017-11-09 20:38:30 -0800216 info.mBucketNum = mCurrentBucketNum + i;
yro2b0f8862017-11-06 14:27:31 -0800217 info.mDuration = mBucketSizeNs;
Yao Chenf60e0ba2017-11-29 15:06:41 -0800218 (*output)[mEventKey].push_back(info);
Yangster-mace2cd6d52017-11-09 20:38:30 -0800219 addPastBucketToAnomalyTrackers(info.mDuration, info.mBucketNum);
Yao Chen5154a372017-10-30 22:57:06 -0700220 VLOG(" filling gap bucket with duration %lld", (long long)mBucketSizeNs);
221 }
222 }
Yangster-mace2cd6d52017-11-09 20:38:30 -0800223
224 mCurrentBucketNum += numBucketsForward;
Yao Chen5154a372017-10-30 22:57:06 -0700225 // If this tracker has no pending events, tell owner to remove.
226 return !hasPendingEvent;
227}
228
229void MaxDurationTracker::onSlicedConditionMayChange(const uint64_t timestamp) {
Yao Chen5154a372017-10-30 22:57:06 -0700230 // Now for each of the on-going event, check if the condition has changed for them.
231 for (auto& pair : mInfos) {
232 if (pair.second.state == kStopped) {
233 continue;
234 }
235 bool conditionMet = mWizard->query(mConditionTrackerIndex, pair.second.conditionKeys) ==
236 ConditionState::kTrue;
237 VLOG("key: %s, condition: %d", pair.first.c_str(), conditionMet);
238 noteConditionChanged(pair.first, conditionMet, timestamp);
239 }
240}
241
242void MaxDurationTracker::onConditionChanged(bool condition, const uint64_t timestamp) {
243 for (auto& pair : mInfos) {
244 noteConditionChanged(pair.first, condition, timestamp);
245 }
246}
247
248void MaxDurationTracker::noteConditionChanged(const HashableDimensionKey& key, bool conditionMet,
249 const uint64_t timestamp) {
Yangster-mace2cd6d52017-11-09 20:38:30 -0800250 declareAnomalyIfAlarmExpired(timestamp);
Yao Chen5154a372017-10-30 22:57:06 -0700251 auto it = mInfos.find(key);
252 if (it == mInfos.end()) {
253 return;
254 }
255
256 switch (it->second.state) {
257 case kStarted:
258 // if condition becomes false, kStarted -> kPaused. Record the current duration.
259 if (!conditionMet) {
260 it->second.state = DurationState::kPaused;
261 it->second.lastDuration += (timestamp - it->second.lastStartTime);
Yao Chen5154a372017-10-30 22:57:06 -0700262 VLOG("MaxDurationTracker Key: %s Started->Paused ", key.c_str());
263 }
264 break;
265 case kStopped:
266 // nothing to do if it's stopped.
267 break;
268 case kPaused:
269 // if condition becomes true, kPaused -> kStarted. and the start time is the condition
270 // change time.
271 if (conditionMet) {
272 it->second.state = DurationState::kStarted;
273 it->second.lastStartTime = timestamp;
274 VLOG("MaxDurationTracker Key: %s Paused->Started", key.c_str());
275 }
276 break;
277 }
Yangster-mace2cd6d52017-11-09 20:38:30 -0800278 if (it->second.lastDuration > mDuration) {
279 mDuration = it->second.lastDuration;
280 detectAndDeclareAnomaly(timestamp, mCurrentBucketNum, mDuration);
281 }
282}
283
Bookatz857aaa52017-12-19 15:29:06 -0800284int64_t MaxDurationTracker::predictAnomalyTimestampNs(const DurationAnomalyTracker& anomalyTracker,
Yangster-mace2cd6d52017-11-09 20:38:30 -0800285 const uint64_t currentTimestamp) const {
286 ALOGE("Max duration producer does not support anomaly timestamp prediction!!!");
287 return currentTimestamp;
Yao Chen5154a372017-10-30 22:57:06 -0700288}
289
290} // namespace statsd
291} // namespace os
yro2b0f8862017-11-06 14:27:31 -0800292} // namespace android