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