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