blob: 94f98ada70147b018aeba3714b20c2b0b8954603 [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
Yangster-mac94e197c2018-01-02 16:03:03 -080027MaxDurationTracker::MaxDurationTracker(const ConfigKey& key, const int64_t& id,
Yao Chenb3561512017-11-21 18:07:17 -080028 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)
Yangster-mac94e197c2018-01-02 16:03:03 -080032 : DurationTracker(key, id, 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;
Yangster-mac94e197c2018-01-02 16:03:03 -080045 StatsdStats::getInstance().noteMetricDimensionSize(
46 mConfigKey, hashDimensionsValue(mTrackerId, mEventKey.getDimensionsValue()),
47 newTupleCount);
Yao Chenb3561512017-11-21 18:07:17 -080048 // 2. Don't add more tuples, we are above the allowed threshold. Drop the data.
49 if (newTupleCount > StatsdStats::kDimensionKeySizeHardLimit) {
Yangster-mac94e197c2018-01-02 16:03:03 -080050 ALOGE("MaxDurTracker %lld dropping data for dimension key %s",
51 (long long)mTrackerId, newKey.c_str());
Yao Chenb3561512017-11-21 18:07:17 -080052 return true;
53 }
54 }
55 return false;
Yao Chen5154a372017-10-30 22:57:06 -070056}
57
58void MaxDurationTracker::noteStart(const HashableDimensionKey& key, bool condition,
59 const uint64_t eventTime, const ConditionKey& conditionKey) {
60 // this will construct a new DurationInfo if this key didn't exist.
Yao Chenb3561512017-11-21 18:07:17 -080061 if (hitGuardRail(key)) {
62 return;
63 }
64
Yao Chen5154a372017-10-30 22:57:06 -070065 DurationInfo& duration = mInfos[key];
66 duration.conditionKeys = conditionKey;
67 VLOG("MaxDuration: key %s start condition %d", key.c_str(), condition);
68
69 switch (duration.state) {
70 case kStarted:
Yao Chen0ea19902017-11-15 15:44:45 -080071 duration.startCount++;
Yao Chen5154a372017-10-30 22:57:06 -070072 break;
73 case kPaused:
Yao Chen0ea19902017-11-15 15:44:45 -080074 duration.startCount++;
Yao Chen5154a372017-10-30 22:57:06 -070075 break;
76 case kStopped:
77 if (!condition) {
78 // event started, but we need to wait for the condition to become true.
79 duration.state = DurationState::kPaused;
80 } else {
81 duration.state = DurationState::kStarted;
82 duration.lastStartTime = eventTime;
83 }
Yao Chen0ea19902017-11-15 15:44:45 -080084 duration.startCount = 1;
Yao Chen5154a372017-10-30 22:57:06 -070085 break;
86 }
87}
88
Yangster-mace2cd6d52017-11-09 20:38:30 -080089
Yao Chen0ea19902017-11-15 15:44:45 -080090void MaxDurationTracker::noteStop(const HashableDimensionKey& key, const uint64_t eventTime,
91 bool forceStop) {
Yangster-mace2cd6d52017-11-09 20:38:30 -080092 declareAnomalyIfAlarmExpired(eventTime);
Yao Chen5154a372017-10-30 22:57:06 -070093 VLOG("MaxDuration: key %s stop", key.c_str());
94 if (mInfos.find(key) == mInfos.end()) {
95 // we didn't see a start event before. do nothing.
96 return;
97 }
98 DurationInfo& duration = mInfos[key];
99
100 switch (duration.state) {
101 case DurationState::kStopped:
102 // already stopped, do nothing.
103 break;
104 case DurationState::kStarted: {
Yao Chen0ea19902017-11-15 15:44:45 -0800105 duration.startCount--;
106 if (forceStop || !mNested || duration.startCount <= 0) {
107 duration.state = DurationState::kStopped;
108 int64_t durationTime = eventTime - duration.lastStartTime;
109 VLOG("Max, key %s, Stop %lld %lld %lld", key.c_str(),
110 (long long)duration.lastStartTime, (long long)eventTime,
111 (long long)durationTime);
112 duration.lastDuration = duration.lastDuration + durationTime;
113 VLOG(" record duration: %lld ", (long long)duration.lastDuration);
114 }
Yao Chen5154a372017-10-30 22:57:06 -0700115 break;
116 }
117 case DurationState::kPaused: {
Yao Chen0ea19902017-11-15 15:44:45 -0800118 duration.startCount--;
119 if (forceStop || !mNested || duration.startCount <= 0) {
120 duration.state = DurationState::kStopped;
121 }
Yao Chen5154a372017-10-30 22:57:06 -0700122 break;
123 }
124 }
125
126 if (duration.lastDuration > mDuration) {
127 mDuration = duration.lastDuration;
Yangster-mace2cd6d52017-11-09 20:38:30 -0800128 detectAndDeclareAnomaly(eventTime, mCurrentBucketNum, mDuration);
Yao Chen5154a372017-10-30 22:57:06 -0700129 VLOG("Max: new max duration: %lld", (long long)mDuration);
130 }
131 // Once an atom duration ends, we erase it. Next time, if we see another atom event with the
132 // same name, they are still considered as different atom durations.
Yao Chen0ea19902017-11-15 15:44:45 -0800133 if (duration.state == DurationState::kStopped) {
134 mInfos.erase(key);
135 }
Yao Chen5154a372017-10-30 22:57:06 -0700136}
Yangster-mace2cd6d52017-11-09 20:38:30 -0800137
Yao Chen5154a372017-10-30 22:57:06 -0700138void MaxDurationTracker::noteStopAll(const uint64_t eventTime) {
Yangster-mace2cd6d52017-11-09 20:38:30 -0800139 std::set<HashableDimensionKey> keys;
140 for (const auto& pair : mInfos) {
141 keys.insert(pair.first);
142 }
143 for (auto& key : keys) {
144 noteStop(key, eventTime, true);
Yao Chen5154a372017-10-30 22:57:06 -0700145 }
146}
147
Yao Chenf60e0ba2017-11-29 15:06:41 -0800148bool MaxDurationTracker::flushIfNeeded(
149 uint64_t eventTime, unordered_map<HashableDimensionKey, vector<DurationBucket>>* output) {
Yao Chen5154a372017-10-30 22:57:06 -0700150 if (mCurrentBucketStartTimeNs + mBucketSizeNs > eventTime) {
151 return false;
152 }
153
154 VLOG("MaxDurationTracker flushing.....");
155
156 // adjust the bucket start time
157 int numBucketsForward = (eventTime - mCurrentBucketStartTimeNs) / mBucketSizeNs;
158
Yao Chen5154a372017-10-30 22:57:06 -0700159 uint64_t endTime = mCurrentBucketStartTimeNs + mBucketSizeNs;
yro2b0f8862017-11-06 14:27:31 -0800160
161 DurationBucket info;
162 info.mBucketStartNs = mCurrentBucketStartTimeNs;
163 info.mBucketEndNs = endTime;
Yangster-mace2cd6d52017-11-09 20:38:30 -0800164 info.mBucketNum = mCurrentBucketNum;
Yao Chen5154a372017-10-30 22:57:06 -0700165
166 uint64_t oldBucketStartTimeNs = mCurrentBucketStartTimeNs;
167 mCurrentBucketStartTimeNs += (numBucketsForward)*mBucketSizeNs;
168
169 bool hasOnGoingStartedEvent = false; // a kStarted event last across bucket boundaries.
170 bool hasPendingEvent =
171 false; // has either a kStarted or kPaused event across bucket boundaries
172 // meaning we need to carry them over to the new bucket.
173 for (auto it = mInfos.begin(); it != mInfos.end(); ++it) {
174 int64_t finalDuration = it->second.lastDuration;
175 if (it->second.state == kStarted) {
176 // the event is still on-going, duration needs to be updated.
177 // |..lastDurationTime_recorded...last_start -----|bucket_end. We need to record the
178 // duration between lastStartTime and bucketEnd.
179 int64_t durationTime = endTime - it->second.lastStartTime;
180
181 finalDuration += durationTime;
182 VLOG(" unrecorded %lld -> %lld", (long long)(durationTime), (long long)finalDuration);
183 // if the event is still on-going, we need to fill the buckets between prev_bucket and
184 // now_bucket. |prev_bucket|...|..|...|now_bucket|
185 hasOnGoingStartedEvent = true;
186 }
187
188 if (finalDuration > mDuration) {
189 mDuration = finalDuration;
190 }
191
192 if (it->second.state == DurationState::kStopped) {
193 // No need to keep buckets for events that were stopped before.
194 mInfos.erase(it);
195 } else {
196 hasPendingEvent = true;
197 // for kPaused, and kStarted event, we will keep track of them, and reset the start time
198 // and duration.
199 it->second.lastStartTime = mCurrentBucketStartTimeNs;
200 it->second.lastDuration = 0;
201 }
202 }
203
204 if (mDuration != 0) {
yro2b0f8862017-11-06 14:27:31 -0800205 info.mDuration = mDuration;
Yao Chenf60e0ba2017-11-29 15:06:41 -0800206 (*output)[mEventKey].push_back(info);
Yangster-mace2cd6d52017-11-09 20:38:30 -0800207 addPastBucketToAnomalyTrackers(info.mDuration, info.mBucketNum);
Yao Chen5154a372017-10-30 22:57:06 -0700208 VLOG(" final duration for last bucket: %lld", (long long)mDuration);
209 }
210
211 mDuration = 0;
212 if (hasOnGoingStartedEvent) {
213 for (int i = 1; i < numBucketsForward; i++) {
yro2b0f8862017-11-06 14:27:31 -0800214 DurationBucket info;
215 info.mBucketStartNs = oldBucketStartTimeNs + mBucketSizeNs * i;
216 info.mBucketEndNs = endTime + mBucketSizeNs * i;
Yangster-mace2cd6d52017-11-09 20:38:30 -0800217 info.mBucketNum = mCurrentBucketNum + i;
yro2b0f8862017-11-06 14:27:31 -0800218 info.mDuration = mBucketSizeNs;
Yao Chenf60e0ba2017-11-29 15:06:41 -0800219 (*output)[mEventKey].push_back(info);
Yangster-mace2cd6d52017-11-09 20:38:30 -0800220 addPastBucketToAnomalyTrackers(info.mDuration, info.mBucketNum);
Yao Chen5154a372017-10-30 22:57:06 -0700221 VLOG(" filling gap bucket with duration %lld", (long long)mBucketSizeNs);
222 }
223 }
Yangster-mace2cd6d52017-11-09 20:38:30 -0800224
225 mCurrentBucketNum += numBucketsForward;
Yao Chen5154a372017-10-30 22:57:06 -0700226 // If this tracker has no pending events, tell owner to remove.
227 return !hasPendingEvent;
228}
229
230void MaxDurationTracker::onSlicedConditionMayChange(const uint64_t timestamp) {
Yao Chen5154a372017-10-30 22:57:06 -0700231 // Now for each of the on-going event, check if the condition has changed for them.
232 for (auto& pair : mInfos) {
233 if (pair.second.state == kStopped) {
234 continue;
235 }
236 bool conditionMet = mWizard->query(mConditionTrackerIndex, pair.second.conditionKeys) ==
237 ConditionState::kTrue;
238 VLOG("key: %s, condition: %d", pair.first.c_str(), conditionMet);
239 noteConditionChanged(pair.first, conditionMet, timestamp);
240 }
241}
242
243void MaxDurationTracker::onConditionChanged(bool condition, const uint64_t timestamp) {
244 for (auto& pair : mInfos) {
245 noteConditionChanged(pair.first, condition, timestamp);
246 }
247}
248
249void MaxDurationTracker::noteConditionChanged(const HashableDimensionKey& key, bool conditionMet,
250 const uint64_t timestamp) {
Yangster-mace2cd6d52017-11-09 20:38:30 -0800251 declareAnomalyIfAlarmExpired(timestamp);
Yao Chen5154a372017-10-30 22:57:06 -0700252 auto it = mInfos.find(key);
253 if (it == mInfos.end()) {
254 return;
255 }
256
257 switch (it->second.state) {
258 case kStarted:
259 // if condition becomes false, kStarted -> kPaused. Record the current duration.
260 if (!conditionMet) {
261 it->second.state = DurationState::kPaused;
262 it->second.lastDuration += (timestamp - it->second.lastStartTime);
Yao Chen5154a372017-10-30 22:57:06 -0700263 VLOG("MaxDurationTracker Key: %s Started->Paused ", key.c_str());
264 }
265 break;
266 case kStopped:
267 // nothing to do if it's stopped.
268 break;
269 case kPaused:
270 // if condition becomes true, kPaused -> kStarted. and the start time is the condition
271 // change time.
272 if (conditionMet) {
273 it->second.state = DurationState::kStarted;
274 it->second.lastStartTime = timestamp;
275 VLOG("MaxDurationTracker Key: %s Paused->Started", key.c_str());
276 }
277 break;
278 }
Yangster-mace2cd6d52017-11-09 20:38:30 -0800279 if (it->second.lastDuration > mDuration) {
280 mDuration = it->second.lastDuration;
281 detectAndDeclareAnomaly(timestamp, mCurrentBucketNum, mDuration);
282 }
283}
284
Bookatz857aaa52017-12-19 15:29:06 -0800285int64_t MaxDurationTracker::predictAnomalyTimestampNs(const DurationAnomalyTracker& anomalyTracker,
Yangster-mace2cd6d52017-11-09 20:38:30 -0800286 const uint64_t currentTimestamp) const {
287 ALOGE("Max duration producer does not support anomaly timestamp prediction!!!");
288 return currentTimestamp;
Yao Chen5154a372017-10-30 22:57:06 -0700289}
290
291} // namespace statsd
292} // namespace os
yro2b0f8862017-11-06 14:27:31 -0800293} // namespace android