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