blob: aa597f421cb6ac67743fbefd1bad4e14636141e3 [file] [log] [blame]
Yao Chen729093d2017-10-16 10:33:26 -07001/*
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 true
18#include "DurationMetricProducer.h"
19#include "Log.h"
20#include "stats_util.h"
21
22#include <cutils/log.h>
23#include <limits.h>
24#include <stdlib.h>
25
26using std::string;
27using std::unordered_map;
28using std::vector;
29
30namespace android {
31namespace os {
32namespace statsd {
33
34DurationMetricProducer::DurationMetricProducer(const DurationMetric& metric,
35 const int conditionIndex, const size_t startIndex,
36 const size_t stopIndex, const size_t stopAllIndex,
37 const sp<ConditionWizard>& wizard)
38 // TODO: Pass in the start time from MetricsManager, instead of calling time() here.
39 : MetricProducer(time(nullptr) * NANO_SECONDS_IN_A_SECOND, conditionIndex, wizard),
40 mMetric(metric),
41 mStartIndex(startIndex),
42 mStopIndex(stopIndex),
43 mStopAllIndex(stopAllIndex) {
44 // TODO: The following boiler plate code appears in all MetricProducers, but we can't abstract
45 // them in the base class, because the proto generated CountMetric, and DurationMetric are
46 // not related. Maybe we should add a template in the future??
47 if (metric.has_bucket() && metric.bucket().has_bucket_size_millis()) {
48 mBucketSizeNs = metric.bucket().bucket_size_millis() * 1000000;
49 } else {
50 mBucketSizeNs = LLONG_MAX;
51 }
52
53 // TODO: use UidMap if uid->pkg_name is required
54 mDimension.insert(mDimension.begin(), metric.dimension().begin(), metric.dimension().end());
55
56 if (metric.links().size() > 0) {
57 mConditionLinks.insert(mConditionLinks.begin(), metric.links().begin(),
58 metric.links().end());
59 mConditionSliced = true;
60 }
61
62 VLOG("metric %lld created. bucket size %lld start_time: %lld", metric.metric_id(),
63 (long long)mBucketSizeNs, (long long)mStartTimeNs);
64}
65
66DurationMetricProducer::~DurationMetricProducer() {
67 VLOG("~DurationMetric() called");
68}
69
70void DurationMetricProducer::finish() {
71 // TODO: write the StatsLogReport to dropbox using
72 // DropboxWriter.
73}
74
75void DurationMetricProducer::onSlicedConditionMayChange() {
76 VLOG("Metric %lld onSlicedConditionMayChange", mMetric.metric_id());
77 // Now for each of the on-going event, check if the condition has changed for them.
78 for (auto& pair : mCurrentSlicedDuration) {
79 VLOG("Metric %lld current %s state: %d", mMetric.metric_id(), pair.first.c_str(),
80 pair.second.state);
81 if (pair.second.state == kStopped) {
82 continue;
83 }
84 bool conditionMet = mWizard->query(mConditionTrackerIndex, pair.second.conditionKeys) ==
85 ConditionState::kTrue;
86 VLOG("key: %s, condition: %d", pair.first.c_str(), conditionMet);
87 noteConditionChanged(pair.first, conditionMet, time(nullptr) * 1000000000);
88 }
89}
90
91void DurationMetricProducer::onConditionChanged(const bool conditionMet) {
92 VLOG("Metric %lld onConditionChanged", mMetric.metric_id());
93 mCondition = conditionMet;
94 // TODO: need to populate the condition change time from the event which triggers the condition
95 // change, instead of using current time.
96 for (auto& pair : mCurrentSlicedDuration) {
97 noteConditionChanged(pair.first, conditionMet, time(nullptr) * 1000000000);
98 }
99}
100
101static void addDurationBucketsToReport(StatsLogReport_DurationMetricDataWrapper& wrapper,
102 const vector<KeyValuePair>& key,
103 const vector<DurationBucketInfo>& buckets) {
104 DurationMetricData* data = wrapper.add_data();
105 for (const auto& kv : key) {
106 data->add_dimension()->CopyFrom(kv);
107 }
108 for (const auto& bucket : buckets) {
109 data->add_bucket_info()->CopyFrom(bucket);
110 VLOG("\t bucket [%lld - %lld] count: %lld", bucket.start_bucket_nanos(),
111 bucket.end_bucket_nanos(), bucket.duration_nanos());
112 }
113}
114
115StatsLogReport DurationMetricProducer::onDumpReport() {
116 VLOG("metric %lld dump report now...", mMetric.metric_id());
117 StatsLogReport report;
118 report.set_metric_id(mMetric.metric_id());
119 report.set_start_report_nanos(mStartTimeNs);
120 // Dump current bucket if it's stale.
121 // If current bucket is still on-going, don't force dump current bucket.
122 // In finish(), We can force dump current bucket.
123 flushDurationIfNeeded(time(nullptr) * NANO_SECONDS_IN_A_SECOND);
124 report.set_end_report_nanos(mCurrentBucketStartTimeNs);
125
126 StatsLogReport_DurationMetricDataWrapper* wrapper = report.mutable_duration_metrics();
127 for (const auto& pair : mPastBuckets) {
128 const HashableDimensionKey& hashableKey = pair.first;
129 auto it = mDimensionKeyMap.find(hashableKey);
130 if (it == mDimensionKeyMap.end()) {
131 ALOGW("Dimension key %s not found?!?! skip...", hashableKey.c_str());
132 continue;
133 }
134 VLOG(" dimension key %s", hashableKey.c_str());
135 addDurationBucketsToReport(*wrapper, it->second, pair.second);
136 }
137 return report;
138};
139
140void DurationMetricProducer::onMatchedLogEvent(const size_t matcherIndex, const LogEvent& event) {
141 if (event.GetTimestampNs() < mStartTimeNs) {
142 return;
143 }
144
145 flushDurationIfNeeded(event.GetTimestampNs());
146
147 if (matcherIndex == mStopAllIndex) {
148 noteStopAll(event.GetTimestampNs());
149 return;
150 }
151
152 HashableDimensionKey hashableKey;
153 if (mDimension.size() > 0) {
154 // hook up sliced counter with AnomalyMonitor.
155 vector<KeyValuePair> key = getDimensionKey(event, mDimension);
156 hashableKey = getHashableKey(key);
157 // Add the HashableDimensionKey->DimensionKey to the map, because StatsLogReport expects
158 // vector<KeyValuePair>.
159 if (mDimensionKeyMap.find(hashableKey) == mDimensionKeyMap.end()) {
160 mDimensionKeyMap[hashableKey] = key;
161 }
162 } else {
163 hashableKey = DEFAULT_DIMENSION_KEY;
164 }
165
166 if (mCurrentSlicedDuration.find(hashableKey) == mCurrentSlicedDuration.end() &&
167 mConditionSliced) {
168 // add the durationInfo for the current bucket.
169 auto& durationInfo = mCurrentSlicedDuration[hashableKey];
170 auto& conditionKeys = durationInfo.conditionKeys;
171 // get and cache the keys for query condition.
172 for (const auto& link : mConditionLinks) {
173 HashableDimensionKey conditionKey = getDimensionKeyForCondition(event, link);
174 conditionKeys[link.condition()] = conditionKey;
175 }
176 }
177
178 bool conditionMet;
179 if (mConditionSliced) {
180 const auto& conditionKeys = mCurrentSlicedDuration[hashableKey].conditionKeys;
181 conditionMet =
182 mWizard->query(mConditionTrackerIndex, conditionKeys) == ConditionState::kTrue;
183 } else {
184 conditionMet = mCondition;
185 }
186
187 if (matcherIndex == mStartIndex) {
188 VLOG("Metric %lld Key: %s Start, Condition %d", mMetric.metric_id(), hashableKey.c_str(),
189 conditionMet);
190 noteStart(hashableKey, conditionMet, event.GetTimestampNs());
191 } else if (matcherIndex == mStopIndex) {
192 VLOG("Metric %lld Key: %s Stop, Condition %d", mMetric.metric_id(), hashableKey.c_str(),
193 conditionMet);
194 noteStop(hashableKey, event.GetTimestampNs());
195 }
196}
197
198void DurationMetricProducer::noteConditionChanged(const HashableDimensionKey& key,
199 const bool conditionMet,
200 const uint64_t eventTime) {
201 flushDurationIfNeeded(eventTime);
202
203 auto it = mCurrentSlicedDuration.find(key);
204 if (it == mCurrentSlicedDuration.end()) {
205 return;
206 }
207
208 switch (it->second.state) {
209 case kStarted:
210 // if condition becomes false, kStarted -> kPaused. Record the current duration.
211 if (!conditionMet) {
212 it->second.state = DurationState::kPaused;
213 it->second.lastDuration =
214 updateDuration(it->second.lastDuration,
215 eventTime - it->second.lastStartTime, mMetric.type());
216 VLOG("Metric %lld Key: %s Paused because condition is false ", mMetric.metric_id(),
217 key.c_str());
218 }
219 break;
220 case kStopped:
221 // nothing to do if it's stopped.
222 break;
223 case kPaused:
224 // if condition becomes true, kPaused -> kStarted. and the start time is the condition
225 // change time.
226 if (conditionMet) {
227 it->second.state = DurationState::kStarted;
228 it->second.lastStartTime = eventTime;
229 VLOG("Metric %lld Key: %s Paused->Started", mMetric.metric_id(), key.c_str());
230 }
231 break;
232 }
233}
234
235void DurationMetricProducer::noteStart(const HashableDimensionKey& key, const bool conditionMet,
236 const uint64_t eventTime) {
237 // this will add an empty bucket for this key if it didn't exist before.
238 DurationInfo& duration = mCurrentSlicedDuration[key];
239
240 switch (duration.state) {
241 case kStarted:
242 // It's safe to do nothing here. even if condition is not true, it means we are about
243 // to receive the condition change event.
244 break;
245 case kPaused:
246 // Safe to do nothing here. kPaused is waiting for the condition change.
247 break;
248 case kStopped:
249 if (!conditionMet) {
250 // event started, but we need to wait for the condition to become true.
251 duration.state = DurationState::kPaused;
252 break;
253 }
254 duration.state = DurationState::kStarted;
255 duration.lastStartTime = eventTime;
256 break;
257 }
258}
259
260void DurationMetricProducer::noteStop(const HashableDimensionKey& key, const uint64_t eventTime) {
261 if (mCurrentSlicedDuration.find(key) == mCurrentSlicedDuration.end()) {
262 // we didn't see a start event before. do nothing.
263 return;
264 }
265 DurationInfo& duration = mCurrentSlicedDuration[key];
266
267 switch (duration.state) {
268 case DurationState::kStopped:
269 // already stopped, do nothing.
270 break;
271 case DurationState::kStarted: {
272 duration.state = DurationState::kStopped;
273 int64_t durationTime = eventTime - duration.lastStartTime;
274 VLOG("Metric %lld, key %s, Stop %lld %lld %lld", mMetric.metric_id(), key.c_str(),
275 (long long)duration.lastStartTime, (long long)eventTime, (long long)durationTime);
276 duration.lastDuration =
277 updateDuration(duration.lastDuration, durationTime, mMetric.type());
278 VLOG(" record duration: %lld ", (long long)duration.lastDuration);
279 break;
280 }
281 case DurationState::kPaused: {
282 duration.state = DurationState::kStopped;
283 break;
284 }
285 }
286}
287
288int64_t DurationMetricProducer::updateDuration(const int64_t lastDuration,
289 const int64_t durationTime,
290 const DurationMetric_AggregationType type) {
291 int64_t result = lastDuration;
292 switch (type) {
293 case DurationMetric_AggregationType_DURATION_SUM:
294 result += durationTime;
295 break;
296 case DurationMetric_AggregationType_DURATION_MAX_SPARSE:
297 if (lastDuration < durationTime) {
298 result = durationTime;
299 }
300 break;
301 case DurationMetric_AggregationType_DURATION_MIN_SPARSE:
302 if (lastDuration > durationTime) {
303 result = durationTime;
304 }
305 break;
306 }
307 return result;
308}
309
310void DurationMetricProducer::noteStopAll(const uint64_t eventTime) {
311 for (auto& duration : mCurrentSlicedDuration) {
312 noteStop(duration.first, eventTime);
313 }
314}
315
316// When a new matched event comes in, we check if event falls into the current
317// bucket. If not, flush the old counter to past buckets and initialize the current buckt.
318void DurationMetricProducer::flushDurationIfNeeded(const uint64_t eventTime) {
319 if (mCurrentBucketStartTimeNs + mBucketSizeNs > eventTime) {
320 return;
321 }
322
323 // adjust the bucket start time
324 int numBucketsForward = (eventTime - mCurrentBucketStartTimeNs) / mBucketSizeNs;
325
326 DurationBucketInfo info;
327 uint64_t endTime = mCurrentBucketStartTimeNs + mBucketSizeNs;
328 info.set_start_bucket_nanos(mCurrentBucketStartTimeNs);
329 info.set_end_bucket_nanos(endTime);
330
331 uint64_t oldBucketStartTimeNs = mCurrentBucketStartTimeNs;
332 mCurrentBucketStartTimeNs += (numBucketsForward)*mBucketSizeNs;
333 VLOG("Metric %lld: new bucket start time: %lld", mMetric.metric_id(),
334 (long long)mCurrentBucketStartTimeNs);
335
336 for (auto it = mCurrentSlicedDuration.begin(); it != mCurrentSlicedDuration.end(); ++it) {
337 int64_t finalDuration = it->second.lastDuration;
338 if (it->second.state == kStarted) {
339 // the event is still on-going, duration needs to be updated.
340 int64_t durationTime = endTime - it->second.lastStartTime;
341 finalDuration = updateDuration(it->second.lastDuration, durationTime, mMetric.type());
342 }
343
344 VLOG(" final duration for last bucket: %lld", (long long)finalDuration);
345
346 // Don't record empty bucket.
347 if (finalDuration != 0) {
348 info.set_duration_nanos(finalDuration);
349 // it will auto create new vector of CountbucketInfo if the key is not found.
350 auto& bucketList = mPastBuckets[it->first];
351 bucketList.push_back(info);
352 }
353
354 // if the event is still on-going, add the buckets between previous bucket and now. Because
355 // the event has been going on across all the buckets in between.
356 // |prev_bucket|...|..|...|now_bucket|
357 if (it->second.state == kStarted) {
358 for (int i = 1; i < numBucketsForward; i++) {
359 DurationBucketInfo info;
360 info.set_start_bucket_nanos(oldBucketStartTimeNs + mBucketSizeNs * i);
361 info.set_end_bucket_nanos(endTime + mBucketSizeNs * i);
362 info.set_duration_nanos(mBucketSizeNs);
363 auto& bucketList = mPastBuckets[it->first];
364 bucketList.push_back(info);
365 VLOG(" add filling bucket with duration %lld", (long long)mBucketSizeNs);
366 }
367 }
368
369 if (it->second.state == DurationState::kStopped) {
370 // No need to keep buckets for events that were stopped before. If the event starts
371 // again, we will add it back.
372 mCurrentSlicedDuration.erase(it);
373 } else {
374 // for kPaused, and kStarted event, we will keep the buckets, and reset the start time
375 // and duration.
376 it->second.lastStartTime = mCurrentBucketStartTimeNs;
377 it->second.lastDuration = 0;
378 }
379 }
380}
381
382} // namespace statsd
383} // namespace os
384} // namespace android