blob: a590bc856966682920eaa2ee2b634cd7f1bc82eb [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
Yao Chenb7041772017-10-20 16:59:25 -0700140void DurationMetricProducer::onMatchedLogEventInternal(
141 const size_t matcherIndex, const HashableDimensionKey& eventKey,
142 const map<string, HashableDimensionKey>& conditionKeys, bool condition,
143 const LogEvent& event) {
Yao Chen729093d2017-10-16 10:33:26 -0700144 flushDurationIfNeeded(event.GetTimestampNs());
145
146 if (matcherIndex == mStopAllIndex) {
147 noteStopAll(event.GetTimestampNs());
148 return;
149 }
150
Yao Chenb7041772017-10-20 16:59:25 -0700151 if (mCurrentSlicedDuration.find(eventKey) == mCurrentSlicedDuration.end() && mConditionSliced) {
Yao Chen729093d2017-10-16 10:33:26 -0700152 // add the durationInfo for the current bucket.
Yao Chenb7041772017-10-20 16:59:25 -0700153 auto& durationInfo = mCurrentSlicedDuration[eventKey];
154 durationInfo.conditionKeys = conditionKeys;
Yao Chen729093d2017-10-16 10:33:26 -0700155 }
156
157 if (matcherIndex == mStartIndex) {
Yao Chenb7041772017-10-20 16:59:25 -0700158 VLOG("Metric %lld Key: %s Start, Condition %d", mMetric.metric_id(), eventKey.c_str(),
159 condition);
160 noteStart(eventKey, condition, event.GetTimestampNs());
Yao Chen729093d2017-10-16 10:33:26 -0700161 } else if (matcherIndex == mStopIndex) {
Yao Chenb7041772017-10-20 16:59:25 -0700162 VLOG("Metric %lld Key: %s Stop, Condition %d", mMetric.metric_id(), eventKey.c_str(),
163 condition);
164 noteStop(eventKey, event.GetTimestampNs());
Yao Chen729093d2017-10-16 10:33:26 -0700165 }
166}
167
168void DurationMetricProducer::noteConditionChanged(const HashableDimensionKey& key,
169 const bool conditionMet,
170 const uint64_t eventTime) {
171 flushDurationIfNeeded(eventTime);
172
173 auto it = mCurrentSlicedDuration.find(key);
174 if (it == mCurrentSlicedDuration.end()) {
175 return;
176 }
177
178 switch (it->second.state) {
179 case kStarted:
180 // if condition becomes false, kStarted -> kPaused. Record the current duration.
181 if (!conditionMet) {
182 it->second.state = DurationState::kPaused;
183 it->second.lastDuration =
184 updateDuration(it->second.lastDuration,
185 eventTime - it->second.lastStartTime, mMetric.type());
186 VLOG("Metric %lld Key: %s Paused because condition is false ", mMetric.metric_id(),
187 key.c_str());
188 }
189 break;
190 case kStopped:
191 // nothing to do if it's stopped.
192 break;
193 case kPaused:
194 // if condition becomes true, kPaused -> kStarted. and the start time is the condition
195 // change time.
196 if (conditionMet) {
197 it->second.state = DurationState::kStarted;
198 it->second.lastStartTime = eventTime;
199 VLOG("Metric %lld Key: %s Paused->Started", mMetric.metric_id(), key.c_str());
200 }
201 break;
202 }
203}
204
205void DurationMetricProducer::noteStart(const HashableDimensionKey& key, const bool conditionMet,
206 const uint64_t eventTime) {
207 // this will add an empty bucket for this key if it didn't exist before.
208 DurationInfo& duration = mCurrentSlicedDuration[key];
209
210 switch (duration.state) {
211 case kStarted:
212 // It's safe to do nothing here. even if condition is not true, it means we are about
213 // to receive the condition change event.
214 break;
215 case kPaused:
216 // Safe to do nothing here. kPaused is waiting for the condition change.
217 break;
218 case kStopped:
219 if (!conditionMet) {
220 // event started, but we need to wait for the condition to become true.
221 duration.state = DurationState::kPaused;
222 break;
223 }
224 duration.state = DurationState::kStarted;
225 duration.lastStartTime = eventTime;
226 break;
227 }
228}
229
230void DurationMetricProducer::noteStop(const HashableDimensionKey& key, const uint64_t eventTime) {
231 if (mCurrentSlicedDuration.find(key) == mCurrentSlicedDuration.end()) {
232 // we didn't see a start event before. do nothing.
233 return;
234 }
235 DurationInfo& duration = mCurrentSlicedDuration[key];
236
237 switch (duration.state) {
238 case DurationState::kStopped:
239 // already stopped, do nothing.
240 break;
241 case DurationState::kStarted: {
242 duration.state = DurationState::kStopped;
243 int64_t durationTime = eventTime - duration.lastStartTime;
244 VLOG("Metric %lld, key %s, Stop %lld %lld %lld", mMetric.metric_id(), key.c_str(),
245 (long long)duration.lastStartTime, (long long)eventTime, (long long)durationTime);
246 duration.lastDuration =
247 updateDuration(duration.lastDuration, durationTime, mMetric.type());
248 VLOG(" record duration: %lld ", (long long)duration.lastDuration);
249 break;
250 }
251 case DurationState::kPaused: {
252 duration.state = DurationState::kStopped;
253 break;
254 }
255 }
256}
257
258int64_t DurationMetricProducer::updateDuration(const int64_t lastDuration,
259 const int64_t durationTime,
260 const DurationMetric_AggregationType type) {
261 int64_t result = lastDuration;
262 switch (type) {
263 case DurationMetric_AggregationType_DURATION_SUM:
264 result += durationTime;
265 break;
266 case DurationMetric_AggregationType_DURATION_MAX_SPARSE:
267 if (lastDuration < durationTime) {
268 result = durationTime;
269 }
270 break;
271 case DurationMetric_AggregationType_DURATION_MIN_SPARSE:
272 if (lastDuration > durationTime) {
273 result = durationTime;
274 }
275 break;
276 }
277 return result;
278}
279
280void DurationMetricProducer::noteStopAll(const uint64_t eventTime) {
281 for (auto& duration : mCurrentSlicedDuration) {
282 noteStop(duration.first, eventTime);
283 }
284}
285
286// When a new matched event comes in, we check if event falls into the current
287// bucket. If not, flush the old counter to past buckets and initialize the current buckt.
288void DurationMetricProducer::flushDurationIfNeeded(const uint64_t eventTime) {
289 if (mCurrentBucketStartTimeNs + mBucketSizeNs > eventTime) {
290 return;
291 }
292
293 // adjust the bucket start time
294 int numBucketsForward = (eventTime - mCurrentBucketStartTimeNs) / mBucketSizeNs;
295
296 DurationBucketInfo info;
297 uint64_t endTime = mCurrentBucketStartTimeNs + mBucketSizeNs;
298 info.set_start_bucket_nanos(mCurrentBucketStartTimeNs);
299 info.set_end_bucket_nanos(endTime);
300
301 uint64_t oldBucketStartTimeNs = mCurrentBucketStartTimeNs;
302 mCurrentBucketStartTimeNs += (numBucketsForward)*mBucketSizeNs;
303 VLOG("Metric %lld: new bucket start time: %lld", mMetric.metric_id(),
304 (long long)mCurrentBucketStartTimeNs);
305
306 for (auto it = mCurrentSlicedDuration.begin(); it != mCurrentSlicedDuration.end(); ++it) {
307 int64_t finalDuration = it->second.lastDuration;
308 if (it->second.state == kStarted) {
309 // the event is still on-going, duration needs to be updated.
310 int64_t durationTime = endTime - it->second.lastStartTime;
311 finalDuration = updateDuration(it->second.lastDuration, durationTime, mMetric.type());
312 }
313
314 VLOG(" final duration for last bucket: %lld", (long long)finalDuration);
315
316 // Don't record empty bucket.
317 if (finalDuration != 0) {
318 info.set_duration_nanos(finalDuration);
319 // it will auto create new vector of CountbucketInfo if the key is not found.
320 auto& bucketList = mPastBuckets[it->first];
321 bucketList.push_back(info);
322 }
323
324 // if the event is still on-going, add the buckets between previous bucket and now. Because
325 // the event has been going on across all the buckets in between.
326 // |prev_bucket|...|..|...|now_bucket|
327 if (it->second.state == kStarted) {
328 for (int i = 1; i < numBucketsForward; i++) {
329 DurationBucketInfo info;
330 info.set_start_bucket_nanos(oldBucketStartTimeNs + mBucketSizeNs * i);
331 info.set_end_bucket_nanos(endTime + mBucketSizeNs * i);
332 info.set_duration_nanos(mBucketSizeNs);
333 auto& bucketList = mPastBuckets[it->first];
334 bucketList.push_back(info);
335 VLOG(" add filling bucket with duration %lld", (long long)mBucketSizeNs);
336 }
337 }
338
339 if (it->second.state == DurationState::kStopped) {
340 // No need to keep buckets for events that were stopped before. If the event starts
341 // again, we will add it back.
342 mCurrentSlicedDuration.erase(it);
343 } else {
344 // for kPaused, and kStarted event, we will keep the buckets, and reset the start time
345 // and duration.
346 it->second.lastStartTime = mCurrentBucketStartTimeNs;
347 it->second.lastDuration = 0;
348 }
349 }
350}
351
yro69007c82017-10-26 20:42:57 -0700352size_t DurationMetricProducer::byteSize() {
353// TODO: return actual proto size when ProtoOutputStream is ready for use for
354// DurationMetricsProducer.
355// return mProto->size();
356 return 0;
357}
358
Yao Chen729093d2017-10-16 10:33:26 -0700359} // namespace statsd
360} // namespace os
361} // namespace android