1 /*
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 STATSD_DEBUG false
18
19 #include "Log.h"
20 #include "MaxDurationTracker.h"
21 #include "guardrail/StatsdStats.h"
22
23 namespace android {
24 namespace os {
25 namespace statsd {
26
MaxDurationTracker(const ConfigKey & key,const int64_t id,const MetricDimensionKey & eventKey,const sp<ConditionWizard> & wizard,int conditionIndex,bool nesting,int64_t currentBucketStartNs,int64_t currentBucketNum,int64_t startTimeNs,int64_t bucketSizeNs,bool conditionSliced,bool fullLink,const vector<sp<AnomalyTracker>> & anomalyTrackers)27 MaxDurationTracker::MaxDurationTracker(const ConfigKey& key, const int64_t id,
28 const MetricDimensionKey& eventKey,
29 const sp<ConditionWizard>& wizard, int conditionIndex,
30 bool nesting, int64_t currentBucketStartNs,
31 int64_t currentBucketNum, int64_t startTimeNs,
32 int64_t bucketSizeNs, bool conditionSliced, bool fullLink,
33 const vector<sp<AnomalyTracker>>& anomalyTrackers)
34 : DurationTracker(key, id, eventKey, wizard, conditionIndex, nesting, currentBucketStartNs,
35 currentBucketNum, startTimeNs, bucketSizeNs, conditionSliced, fullLink,
36 anomalyTrackers) {
37 mDuration = 0;
38 }
39
hitGuardRail(const HashableDimensionKey & newKey,size_t dimensionHardLimit) const40 bool MaxDurationTracker::hitGuardRail(const HashableDimensionKey& newKey,
41 size_t dimensionHardLimit) const {
42 // ===========GuardRail==============
43 if (mInfos.find(newKey) != mInfos.end()) {
44 // if the key existed, we are good!
45 return false;
46 }
47 // 1. Report the tuple count if the tuple count > soft limit
48 if (mInfos.size() >= StatsdStats::kDimensionKeySizeSoftLimit) {
49 size_t newTupleCount = mInfos.size() + 1;
50 StatsdStats::getInstance().noteMetricDimensionSize(mConfigKey, mTrackerId, newTupleCount);
51 // 2. Don't add more tuples, we are above the allowed threshold. Drop the data.
52 if (newTupleCount > dimensionHardLimit) {
53 if (!mHasHitGuardrail) {
54 ALOGE("MaxDurTracker %lld dropping data for dimension key %s",
55 (long long)mTrackerId, newKey.toString().c_str());
56 mHasHitGuardrail = true;
57 }
58 StatsdStats::getInstance().noteHardDimensionLimitReached(mTrackerId);
59 return true;
60 }
61 }
62 return false;
63 }
64
noteStart(const HashableDimensionKey & key,bool condition,const int64_t eventTime,const ConditionKey & conditionKey,size_t dimensionHardLimit)65 void MaxDurationTracker::noteStart(const HashableDimensionKey& key, bool condition,
66 const int64_t eventTime, const ConditionKey& conditionKey,
67 size_t dimensionHardLimit) {
68 // this will construct a new DurationInfo if this key didn't exist.
69 if (hitGuardRail(key, dimensionHardLimit)) {
70 return;
71 }
72
73 DurationInfo& duration = mInfos[key];
74 if (mConditionSliced) {
75 duration.conditionKeys = conditionKey;
76 }
77 VLOG("MaxDuration: key %s start condition %d", key.toString().c_str(), condition);
78
79 switch (duration.state) {
80 case kStarted:
81 duration.startCount++;
82 break;
83 case kPaused:
84 duration.startCount++;
85 break;
86 case kStopped:
87 if (!condition) {
88 // event started, but we need to wait for the condition to become true.
89 duration.state = DurationState::kPaused;
90 } else {
91 duration.state = DurationState::kStarted;
92 duration.lastStartTime = eventTime;
93 startAnomalyAlarm(eventTime);
94 }
95 duration.startCount = 1;
96 break;
97 }
98 }
99
noteStop(const HashableDimensionKey & key,const int64_t eventTime,bool forceStop)100 void MaxDurationTracker::noteStop(const HashableDimensionKey& key, const int64_t eventTime,
101 bool forceStop) {
102 VLOG("MaxDuration: key %s stop", key.toString().c_str());
103 if (mInfos.find(key) == mInfos.end()) {
104 // we didn't see a start event before. do nothing.
105 return;
106 }
107 DurationInfo& duration = mInfos[key];
108
109 switch (duration.state) {
110 case DurationState::kStopped:
111 // already stopped, do nothing.
112 break;
113 case DurationState::kStarted: {
114 duration.startCount--;
115 if (forceStop || !mNested || duration.startCount <= 0) {
116 stopAnomalyAlarm(eventTime);
117 duration.state = DurationState::kStopped;
118 int64_t durationTime = eventTime - duration.lastStartTime;
119 VLOG("Max, key %s, Stop %lld %lld %lld", key.toString().c_str(),
120 (long long)duration.lastStartTime, (long long)eventTime,
121 (long long)durationTime);
122 duration.lastDuration += durationTime;
123 if (hasStartedDuration()) {
124 // In case any other dimensions are still started, we need to keep the alarm
125 // set.
126 startAnomalyAlarm(eventTime);
127 }
128 VLOG(" record duration: %lld ", (long long)duration.lastDuration);
129 }
130 break;
131 }
132 case DurationState::kPaused: {
133 duration.startCount--;
134 if (forceStop || !mNested || duration.startCount <= 0) {
135 duration.state = DurationState::kStopped;
136 }
137 break;
138 }
139 }
140
141 if (duration.lastDuration > mDuration) {
142 mDuration = duration.lastDuration;
143 VLOG("Max: new max duration: %lld", (long long)mDuration);
144 }
145 // Once an atom duration ends, we erase it. Next time, if we see another atom event with the
146 // same name, they are still considered as different atom durations.
147 if (duration.state == DurationState::kStopped) {
148 mInfos.erase(key);
149 }
150 }
151
hasStartedDuration() const152 bool MaxDurationTracker::hasStartedDuration() const {
153 for (auto& pair : mInfos) {
154 if (pair.second.state == kStarted) {
155 return true;
156 }
157 }
158 return false;
159 }
160
hasAccumulatedDuration() const161 bool MaxDurationTracker::hasAccumulatedDuration() const {
162 // When DurationState is changed to kStopped, we remove its entry from mInfos. Thus, mInfos
163 // will be empty when all entries are stopped.
164 return !mInfos.empty() || mDuration != 0;
165 }
166
noteStopAll(const int64_t eventTime)167 void MaxDurationTracker::noteStopAll(const int64_t eventTime) {
168 std::set<HashableDimensionKey> keys;
169 for (const auto& pair : mInfos) {
170 keys.insert(pair.first);
171 }
172 for (auto& key : keys) {
173 noteStop(key, eventTime, true);
174 }
175 }
176
flushCurrentBucket(const int64_t eventTimeNs,const optional<UploadThreshold> & uploadThreshold,const int64_t globalConditionTrueNs,std::unordered_map<MetricDimensionKey,std::vector<DurationBucket>> * output)177 bool MaxDurationTracker::flushCurrentBucket(
178 const int64_t eventTimeNs, const optional<UploadThreshold>& uploadThreshold,
179 const int64_t globalConditionTrueNs,
180 std::unordered_map<MetricDimensionKey, std::vector<DurationBucket>>* output) {
181 VLOG("MaxDurationTracker flushing.....");
182
183 // adjust the bucket start time
184 int numBucketsForward = 0;
185 int64_t fullBucketEnd = getCurrentBucketEndTimeNs();
186 int64_t currentBucketEndTimeNs;
187 if (eventTimeNs >= fullBucketEnd) {
188 numBucketsForward = 1 + (eventTimeNs - fullBucketEnd) / mBucketSizeNs;
189 currentBucketEndTimeNs = fullBucketEnd;
190 } else {
191 // This must be a partial bucket.
192 currentBucketEndTimeNs = eventTimeNs;
193 }
194
195 bool hasPendingEvent =
196 false; // has either a kStarted or kPaused event across bucket boundaries
197 // meaning we need to carry them over to the new bucket.
198 for (auto it = mInfos.begin(); it != mInfos.end();) {
199 if (it->second.state == DurationState::kStopped) {
200 // No need to keep buckets for events that were stopped before.
201 it = mInfos.erase(it);
202 } else {
203 ++it;
204 hasPendingEvent = true;
205 }
206 }
207
208 // mDuration is updated in noteStop to the maximum duration that ended in the current bucket.
209 if (durationPassesThreshold(uploadThreshold, mDuration)) {
210 DurationBucket info;
211 info.mBucketStartNs = mCurrentBucketStartTimeNs;
212 info.mBucketEndNs = currentBucketEndTimeNs;
213 info.mDuration = mDuration;
214 info.mConditionTrueNs = globalConditionTrueNs;
215 (*output)[mEventKey].push_back(info);
216 VLOG(" final duration for last bucket: %lld", (long long)mDuration);
217 } else {
218 VLOG(" duration: %lld does not pass set threshold", (long long)mDuration);
219 }
220
221 if (numBucketsForward > 0) {
222 mCurrentBucketStartTimeNs = fullBucketEnd + (numBucketsForward - 1) * mBucketSizeNs;
223 mCurrentBucketNum += numBucketsForward;
224 } else { // We must be forming a partial bucket.
225 mCurrentBucketStartTimeNs = eventTimeNs;
226 }
227
228 mDuration = 0;
229 // Reset mHasHitGuardrail boolean since bucket was reset
230 mHasHitGuardrail = false;
231 // If this tracker has no pending events, tell owner to remove.
232 return !hasPendingEvent;
233 }
234
flushIfNeeded(int64_t eventTimeNs,const optional<UploadThreshold> & uploadThreshold,unordered_map<MetricDimensionKey,vector<DurationBucket>> * output)235 bool MaxDurationTracker::flushIfNeeded(
236 int64_t eventTimeNs, const optional<UploadThreshold>& uploadThreshold,
237 unordered_map<MetricDimensionKey, vector<DurationBucket>>* output) {
238 if (eventTimeNs < getCurrentBucketEndTimeNs()) {
239 return false;
240 }
241 return flushCurrentBucket(eventTimeNs, uploadThreshold, /*globalConditionTrueNs*/ 0, output);
242 }
243
onSlicedConditionMayChange(const int64_t timestamp)244 void MaxDurationTracker::onSlicedConditionMayChange(const int64_t timestamp) {
245 // Now for each of the on-going event, check if the condition has changed for them.
246 for (auto& pair : mInfos) {
247 if (pair.second.state == kStopped) {
248 continue;
249 }
250 ConditionState conditionState = mWizard->query(
251 mConditionTrackerIndex, pair.second.conditionKeys,
252 !mHasLinksToAllConditionDimensionsInTracker);
253 bool conditionMet = (conditionState == ConditionState::kTrue);
254
255 VLOG("key: %s, condition: %d", pair.first.toString().c_str(), conditionMet);
256 noteConditionChanged(pair.first, conditionMet, timestamp);
257 }
258 }
259
onStateChanged(const int64_t timestamp,const int32_t atomId,const FieldValue & newState)260 void MaxDurationTracker::onStateChanged(const int64_t timestamp, const int32_t atomId,
261 const FieldValue& newState) {
262 ALOGE("MaxDurationTracker does not handle sliced state changes.");
263 }
264
onConditionChanged(bool condition,const int64_t timestamp)265 void MaxDurationTracker::onConditionChanged(bool condition, const int64_t timestamp) {
266 for (auto& pair : mInfos) {
267 noteConditionChanged(pair.first, condition, timestamp);
268 }
269 }
270
noteConditionChanged(const HashableDimensionKey & key,bool conditionMet,const int64_t timestamp)271 void MaxDurationTracker::noteConditionChanged(const HashableDimensionKey& key, bool conditionMet,
272 const int64_t timestamp) {
273 auto it = mInfos.find(key);
274 if (it == mInfos.end()) {
275 return;
276 }
277
278 switch (it->second.state) {
279 case kStarted:
280 // If condition becomes false, kStarted -> kPaused. Record the current duration and
281 // stop anomaly alarm.
282 if (!conditionMet) {
283 stopAnomalyAlarm(timestamp);
284 it->second.state = DurationState::kPaused;
285 it->second.lastDuration += (timestamp - it->second.lastStartTime);
286 if (hasStartedDuration()) {
287 // In case any other dimensions are still started, we need to set the alarm.
288 startAnomalyAlarm(timestamp);
289 }
290 VLOG("MaxDurationTracker Key: %s Started->Paused ", key.toString().c_str());
291 }
292 break;
293 case kStopped:
294 // Nothing to do if it's stopped.
295 break;
296 case kPaused:
297 // If condition becomes true, kPaused -> kStarted. and the start time is the condition
298 // change time.
299 if (conditionMet) {
300 it->second.state = DurationState::kStarted;
301 it->second.lastStartTime = timestamp;
302 startAnomalyAlarm(timestamp);
303 VLOG("MaxDurationTracker Key: %s Paused->Started", key.toString().c_str());
304 }
305 break;
306 }
307 // Note that we don't update mDuration here since it's only updated during noteStop.
308 }
309
predictAnomalyTimestampNs(const AnomalyTracker & anomalyTracker,const int64_t currentTimestamp) const310 int64_t MaxDurationTracker::predictAnomalyTimestampNs(const AnomalyTracker& anomalyTracker,
311 const int64_t currentTimestamp) const {
312 // The allowed time we can continue in the current state is the
313 // (anomaly threshold) - max(elapsed time of the started mInfos).
314 int64_t maxElapsed = 0;
315 for (auto it = mInfos.begin(); it != mInfos.end(); ++it) {
316 if (it->second.state == DurationState::kStarted) {
317 int64_t duration =
318 it->second.lastDuration + (currentTimestamp - it->second.lastStartTime);
319 if (duration > maxElapsed) {
320 maxElapsed = duration;
321 }
322 }
323 }
324 int64_t anomalyTimeNs = currentTimestamp + anomalyTracker.getAnomalyThreshold() - maxElapsed;
325 int64_t refractoryEndNs = anomalyTracker.getRefractoryPeriodEndsSec(mEventKey) * NS_PER_SEC;
326 return std::max(anomalyTimeNs, refractoryEndNs);
327 }
328
dumpStates(int out,bool verbose) const329 void MaxDurationTracker::dumpStates(int out, bool verbose) const {
330 dprintf(out, "\t\t sub-durations %lu\n", (unsigned long)mInfos.size());
331 dprintf(out, "\t\t current duration %lld\n", (long long)mDuration);
332 }
333
getCurrentStateKeyDuration() const334 int64_t MaxDurationTracker::getCurrentStateKeyDuration() const {
335 ALOGE("MaxDurationTracker does not handle sliced state changes.");
336 return -1;
337 }
338
getCurrentStateKeyFullBucketDuration() const339 int64_t MaxDurationTracker::getCurrentStateKeyFullBucketDuration() const {
340 ALOGE("MaxDurationTracker does not handle sliced state changes.");
341 return -1;
342 }
343
updateCurrentStateKey(const int32_t atomId,const FieldValue & newState)344 void MaxDurationTracker::updateCurrentStateKey(const int32_t atomId, const FieldValue& newState) {
345 ALOGE("MaxDurationTracker does not handle sliced state changes.");
346 }
347
348 } // namespace statsd
349 } // namespace os
350 } // namespace android
351