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.state == DurationState::kStopped) {
142         if (duration.lastDuration > mDuration) {
143             mDuration = duration.lastDuration;
144             VLOG("Max: new max duration: %lld", (long long)mDuration);
145         }
146 
147         // Once an atom duration ends, we erase it. Next time, if we see another atom event with the
148         // same name, they are still considered as different atom durations.
149         mInfos.erase(key);
150     }
151 }
152 
hasStartedDuration() const153 bool MaxDurationTracker::hasStartedDuration() const {
154     for (auto& pair : mInfos) {
155         if (pair.second.state == kStarted) {
156             return true;
157         }
158     }
159     return false;
160 }
161 
hasAccumulatedDuration() const162 bool MaxDurationTracker::hasAccumulatedDuration() const {
163     // When DurationState is changed to kStopped, we remove its entry from mInfos. Thus, mInfos
164     // will be empty when all entries are stopped.
165     return !mInfos.empty() || mDuration != 0;
166 }
167 
noteStopAll(const int64_t eventTime)168 void MaxDurationTracker::noteStopAll(const int64_t eventTime) {
169     std::set<HashableDimensionKey> keys;
170     for (const auto& pair : mInfos) {
171         keys.insert(pair.first);
172     }
173     for (auto& key : keys) {
174         noteStop(key, eventTime, true);
175     }
176 }
177 
flushCurrentBucket(const int64_t eventTimeNs,const optional<UploadThreshold> & uploadThreshold,const int64_t globalConditionTrueNs,std::unordered_map<MetricDimensionKey,std::vector<DurationBucket>> * output)178 bool MaxDurationTracker::flushCurrentBucket(
179         const int64_t eventTimeNs, const optional<UploadThreshold>& uploadThreshold,
180         const int64_t globalConditionTrueNs,
181         std::unordered_map<MetricDimensionKey, std::vector<DurationBucket>>* output) {
182     VLOG("MaxDurationTracker flushing.....");
183 
184     // adjust the bucket start time
185     int numBucketsForward = 0;
186     int64_t fullBucketEnd = getCurrentBucketEndTimeNs();
187     int64_t currentBucketEndTimeNs;
188     if (eventTimeNs >= fullBucketEnd) {
189         numBucketsForward = 1 + (eventTimeNs - fullBucketEnd) / mBucketSizeNs;
190         currentBucketEndTimeNs = fullBucketEnd;
191     } else {
192         // This must be a partial bucket.
193         currentBucketEndTimeNs = eventTimeNs;
194     }
195 
196     bool hasPendingEvent =
197             false;  // has either a kStarted or kPaused event across bucket boundaries
198     // meaning we need to carry them over to the new bucket.
199     for (auto it = mInfos.begin(); it != mInfos.end();) {
200         if (it->second.state == DurationState::kStopped) {
201             // No need to keep buckets for events that were stopped before.
202             it = mInfos.erase(it);
203         } else {
204             ++it;
205             hasPendingEvent = true;
206         }
207     }
208 
209     // mDuration is updated in noteStop to the maximum duration that ended in the current bucket.
210     if (durationPassesThreshold(uploadThreshold, mDuration)) {
211         DurationBucket info;
212         info.mBucketStartNs = mCurrentBucketStartTimeNs;
213         info.mBucketEndNs = currentBucketEndTimeNs;
214         info.mDuration = mDuration;
215         info.mConditionTrueNs = globalConditionTrueNs;
216         (*output)[mEventKey].push_back(info);
217         VLOG("  final duration for last bucket: %lld", (long long)mDuration);
218     } else {
219         VLOG("  duration: %lld does not pass set threshold", (long long)mDuration);
220     }
221 
222     if (numBucketsForward > 0) {
223         mCurrentBucketStartTimeNs = fullBucketEnd + (numBucketsForward - 1) * mBucketSizeNs;
224         mCurrentBucketNum += numBucketsForward;
225     } else {  // We must be forming a partial bucket.
226         mCurrentBucketStartTimeNs = eventTimeNs;
227     }
228 
229     mDuration = 0;
230     // Reset mHasHitGuardrail boolean since bucket was reset
231     mHasHitGuardrail = false;
232     // If this tracker has no pending events, tell owner to remove.
233     return !hasPendingEvent;
234 }
235 
flushIfNeeded(int64_t eventTimeNs,const optional<UploadThreshold> & uploadThreshold,unordered_map<MetricDimensionKey,vector<DurationBucket>> * output)236 bool MaxDurationTracker::flushIfNeeded(
237         int64_t eventTimeNs, const optional<UploadThreshold>& uploadThreshold,
238         unordered_map<MetricDimensionKey, vector<DurationBucket>>* output) {
239     if (eventTimeNs < getCurrentBucketEndTimeNs()) {
240         return false;
241     }
242     return flushCurrentBucket(eventTimeNs, uploadThreshold, /*globalConditionTrueNs*/ 0, output);
243 }
244 
onSlicedConditionMayChange(const int64_t timestamp)245 void MaxDurationTracker::onSlicedConditionMayChange(const int64_t timestamp) {
246     // Now for each of the on-going event, check if the condition has changed for them.
247     for (auto& pair : mInfos) {
248         if (pair.second.state == kStopped) {
249             continue;
250         }
251         ConditionState conditionState = mWizard->query(
252             mConditionTrackerIndex, pair.second.conditionKeys,
253             !mHasLinksToAllConditionDimensionsInTracker);
254         bool conditionMet = (conditionState == ConditionState::kTrue);
255 
256         VLOG("key: %s, condition: %d", pair.first.toString().c_str(), conditionMet);
257         noteConditionChanged(pair.first, conditionMet, timestamp);
258     }
259 }
260 
onStateChanged(const int64_t timestamp,const int32_t atomId,const FieldValue & newState)261 void MaxDurationTracker::onStateChanged(const int64_t timestamp, const int32_t atomId,
262                                         const FieldValue& newState) {
263     ALOGE("MaxDurationTracker does not handle sliced state changes.");
264 }
265 
onConditionChanged(bool condition,const int64_t timestamp)266 void MaxDurationTracker::onConditionChanged(bool condition, const int64_t timestamp) {
267     for (auto& pair : mInfos) {
268         noteConditionChanged(pair.first, condition, timestamp);
269     }
270 }
271 
noteConditionChanged(const HashableDimensionKey & key,bool conditionMet,const int64_t timestamp)272 void MaxDurationTracker::noteConditionChanged(const HashableDimensionKey& key, bool conditionMet,
273                                               const int64_t timestamp) {
274     auto it = mInfos.find(key);
275     if (it == mInfos.end()) {
276         return;
277     }
278 
279     switch (it->second.state) {
280         case kStarted:
281             // If condition becomes false, kStarted -> kPaused. Record the current duration and
282             // stop anomaly alarm.
283             if (!conditionMet) {
284                 stopAnomalyAlarm(timestamp);
285                 it->second.state = DurationState::kPaused;
286                 it->second.lastDuration += (timestamp - it->second.lastStartTime);
287                 if (hasStartedDuration()) {
288                     // In case any other dimensions are still started, we need to set the alarm.
289                     startAnomalyAlarm(timestamp);
290                 }
291                 VLOG("MaxDurationTracker Key: %s Started->Paused ", key.toString().c_str());
292             }
293             break;
294         case kStopped:
295             // Nothing to do if it's stopped.
296             break;
297         case kPaused:
298             // If condition becomes true, kPaused -> kStarted. and the start time is the condition
299             // change time.
300             if (conditionMet) {
301                 it->second.state = DurationState::kStarted;
302                 it->second.lastStartTime = timestamp;
303                 startAnomalyAlarm(timestamp);
304                 VLOG("MaxDurationTracker Key: %s Paused->Started", key.toString().c_str());
305             }
306             break;
307     }
308     // Note that we don't update mDuration here since it's only updated during noteStop.
309 }
310 
predictAnomalyTimestampNs(const AnomalyTracker & anomalyTracker,const int64_t currentTimestamp) const311 int64_t MaxDurationTracker::predictAnomalyTimestampNs(const AnomalyTracker& anomalyTracker,
312                                                       const int64_t currentTimestamp) const {
313     // The allowed time we can continue in the current state is the
314     // (anomaly threshold) - max(elapsed time of the started mInfos).
315     int64_t maxElapsed = 0;
316     for (auto it = mInfos.begin(); it != mInfos.end(); ++it) {
317         if (it->second.state == DurationState::kStarted) {
318             int64_t duration =
319                     it->second.lastDuration + (currentTimestamp - it->second.lastStartTime);
320             if (duration > maxElapsed) {
321                 maxElapsed = duration;
322             }
323         }
324     }
325     int64_t anomalyTimeNs = currentTimestamp + anomalyTracker.getAnomalyThreshold() - maxElapsed;
326     int64_t refractoryEndNs = anomalyTracker.getRefractoryPeriodEndsSec(mEventKey) * NS_PER_SEC;
327     return std::max(anomalyTimeNs, refractoryEndNs);
328 }
329 
dumpStates(int out,bool verbose) const330 void MaxDurationTracker::dumpStates(int out, bool verbose) const {
331     dprintf(out, "\t\t sub-durations %lu\n", (unsigned long)mInfos.size());
332     dprintf(out, "\t\t current duration %lld\n", (long long)mDuration);
333 }
334 
getCurrentStateKeyDuration() const335 int64_t MaxDurationTracker::getCurrentStateKeyDuration() const {
336     ALOGE("MaxDurationTracker does not handle sliced state changes.");
337     return -1;
338 }
339 
getCurrentStateKeyFullBucketDuration() const340 int64_t MaxDurationTracker::getCurrentStateKeyFullBucketDuration() const {
341     ALOGE("MaxDurationTracker does not handle sliced state changes.");
342     return -1;
343 }
344 
updateCurrentStateKey(const int32_t atomId,const FieldValue & newState)345 void MaxDurationTracker::updateCurrentStateKey(const int32_t atomId, const FieldValue& newState) {
346     ALOGE("MaxDurationTracker does not handle sliced state changes.");
347 }
348 
349 }  // namespace statsd
350 }  // namespace os
351 }  // namespace android
352