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