DurationMetricProducer.cpp revision 2b0f88678b2877a8e9f83cea60f097322b078367
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 true 18 19#include "Log.h" 20#include "DurationMetricProducer.h" 21#include "stats_util.h" 22 23#include <limits.h> 24#include <stdlib.h> 25 26using android::util::FIELD_TYPE_BOOL; 27using android::util::FIELD_TYPE_FLOAT; 28using android::util::FIELD_TYPE_INT32; 29using android::util::FIELD_TYPE_INT64; 30using android::util::FIELD_TYPE_MESSAGE; 31using android::util::ProtoOutputStream; 32using std::string; 33using std::unordered_map; 34using std::vector; 35 36namespace android { 37namespace os { 38namespace statsd { 39 40// for StatsLogReport 41const int FIELD_ID_METRIC_ID = 1; 42const int FIELD_ID_START_REPORT_NANOS = 2; 43const int FIELD_ID_END_REPORT_NANOS = 3; 44const int FIELD_ID_DURATION_METRICS = 6; 45// for DurationMetricDataWrapper 46const int FIELD_ID_DATA = 1; 47// for DurationMetricData 48const int FIELD_ID_DIMENSION = 1; 49const int FIELD_ID_BUCKET_INFO = 2; 50// for KeyValuePair 51const int FIELD_ID_KEY = 1; 52const int FIELD_ID_VALUE_STR = 2; 53const int FIELD_ID_VALUE_INT = 3; 54const int FIELD_ID_VALUE_BOOL = 4; 55const int FIELD_ID_VALUE_FLOAT = 5; 56// for DurationBucketInfo 57const int FIELD_ID_START_BUCKET_NANOS = 1; 58const int FIELD_ID_END_BUCKET_NANOS = 2; 59const int FIELD_ID_DURATION = 3; 60 61DurationMetricProducer::DurationMetricProducer(const DurationMetric& metric, 62 const int conditionIndex, const size_t startIndex, 63 const size_t stopIndex, const size_t stopAllIndex, 64 const sp<ConditionWizard>& wizard, 65 const vector<KeyMatcher>& internalDimension, 66 const uint64_t startTimeNs) 67 : MetricProducer(startTimeNs, conditionIndex, wizard), 68 mMetric(metric), 69 mStartIndex(startIndex), 70 mStopIndex(stopIndex), 71 mStopAllIndex(stopAllIndex), 72 mInternalDimension(internalDimension) { 73 // TODO: The following boiler plate code appears in all MetricProducers, but we can't abstract 74 // them in the base class, because the proto generated CountMetric, and DurationMetric are 75 // not related. Maybe we should add a template in the future?? 76 if (metric.has_bucket() && metric.bucket().has_bucket_size_millis()) { 77 mBucketSizeNs = metric.bucket().bucket_size_millis() * 1000000; 78 } else { 79 mBucketSizeNs = LLONG_MAX; 80 } 81 82 // TODO: use UidMap if uid->pkg_name is required 83 mDimension.insert(mDimension.begin(), metric.dimension().begin(), metric.dimension().end()); 84 85 if (metric.links().size() > 0) { 86 mConditionLinks.insert(mConditionLinks.begin(), metric.links().begin(), 87 metric.links().end()); 88 mConditionSliced = true; 89 } 90 91 startNewProtoOutputStream(mStartTimeNs); 92 93 VLOG("metric %lld created. bucket size %lld start_time: %lld", metric.metric_id(), 94 (long long)mBucketSizeNs, (long long)mStartTimeNs); 95} 96 97DurationMetricProducer::~DurationMetricProducer() { 98 VLOG("~DurationMetric() called"); 99} 100 101void DurationMetricProducer::startNewProtoOutputStream(long long startTime) { 102 mProto = std::make_unique<ProtoOutputStream>(); 103 mProto->write(FIELD_TYPE_INT32 | FIELD_ID_METRIC_ID, mMetric.metric_id()); 104 mProto->write(FIELD_TYPE_INT64 | FIELD_ID_START_REPORT_NANOS, startTime); 105 mProtoToken = mProto->start(FIELD_TYPE_MESSAGE | FIELD_ID_DURATION_METRICS); 106} 107 108unique_ptr<DurationTracker> DurationMetricProducer::createDurationTracker( 109 vector<DurationBucket>& bucket) { 110 switch (mMetric.type()) { 111 case DurationMetric_AggregationType_DURATION_SUM: 112 return make_unique<OringDurationTracker>(mWizard, mConditionTrackerIndex, 113 mCurrentBucketStartTimeNs, mBucketSizeNs, 114 bucket); 115 case DurationMetric_AggregationType_DURATION_MAX_SPARSE: 116 return make_unique<MaxDurationTracker>(mWizard, mConditionTrackerIndex, 117 mCurrentBucketStartTimeNs, mBucketSizeNs, 118 bucket); 119 } 120} 121 122void DurationMetricProducer::finish() { 123 // TODO: write the StatsLogReport to dropbox using 124 // DropboxWriter. 125} 126 127void DurationMetricProducer::onSlicedConditionMayChange(const uint64_t eventTime) { 128 VLOG("Metric %lld onSlicedConditionMayChange", mMetric.metric_id()); 129 // Now for each of the on-going event, check if the condition has changed for them. 130 flushIfNeeded(eventTime); 131 for (auto& pair : mCurrentSlicedDuration) { 132 pair.second->onSlicedConditionMayChange(eventTime); 133 } 134} 135 136void DurationMetricProducer::onConditionChanged(const bool conditionMet, const uint64_t eventTime) { 137 VLOG("Metric %lld onConditionChanged", mMetric.metric_id()); 138 mCondition = conditionMet; 139 // TODO: need to populate the condition change time from the event which triggers the condition 140 // change, instead of using current time. 141 142 flushIfNeeded(eventTime); 143 for (auto& pair : mCurrentSlicedDuration) { 144 pair.second->onConditionChanged(conditionMet, eventTime); 145 } 146} 147 148static void addDurationBucketsToReport(StatsLogReport_DurationMetricDataWrapper& wrapper, 149 const vector<KeyValuePair>& key, 150 const vector<DurationBucketInfo>& buckets) { 151 DurationMetricData* data = wrapper.add_data(); 152 for (const auto& kv : key) { 153 data->add_dimension()->CopyFrom(kv); 154 } 155 for (const auto& bucket : buckets) { 156 data->add_bucket_info()->CopyFrom(bucket); 157 VLOG("\t bucket [%lld - %lld] duration(ns): %lld", bucket.start_bucket_nanos(), 158 bucket.end_bucket_nanos(), bucket.duration_nanos()); 159 } 160} 161 162StatsLogReport DurationMetricProducer::onDumpReport() { 163 long long endTime = time(nullptr) * NS_PER_SEC; 164 165 // Dump current bucket if it's stale. 166 // If current bucket is still on-going, don't force dump current bucket. 167 // In finish(), We can force dump current bucket. 168 flushIfNeeded(endTime); 169 VLOG("metric %lld dump report now...", mMetric.metric_id()); 170 171 for (const auto& pair : mPastBuckets) { 172 const HashableDimensionKey& hashableKey = pair.first; 173 VLOG(" dimension key %s", hashableKey.c_str()); 174 auto it = mDimensionKeyMap.find(hashableKey); 175 if (it == mDimensionKeyMap.end()) { 176 ALOGW("Dimension key %s not found?!?! skip...", hashableKey.c_str()); 177 continue; 178 } 179 long long wrapperToken = mProto->start(FIELD_TYPE_MESSAGE | FIELD_ID_DATA); 180 181 // First fill dimension (KeyValuePairs). 182 for (const auto& kv : it->second) { 183 long long dimensionToken = mProto->start(FIELD_TYPE_MESSAGE | FIELD_ID_DIMENSION); 184 mProto->write(FIELD_TYPE_INT32 | FIELD_ID_KEY, kv.key()); 185 if (kv.has_value_str()) { 186 mProto->write(FIELD_TYPE_INT32 | FIELD_ID_VALUE_STR, kv.value_str()); 187 } else if (kv.has_value_int()) { 188 mProto->write(FIELD_TYPE_INT64 | FIELD_ID_VALUE_INT, kv.value_int()); 189 } else if (kv.has_value_bool()) { 190 mProto->write(FIELD_TYPE_BOOL | FIELD_ID_VALUE_BOOL, kv.value_bool()); 191 } else if (kv.has_value_float()) { 192 mProto->write(FIELD_TYPE_FLOAT | FIELD_ID_VALUE_FLOAT, kv.value_float()); 193 } 194 mProto->end(dimensionToken); 195 } 196 197 // Then fill bucket_info (DurationBucketInfo). 198 for (const auto& bucket : pair.second) { 199 long long bucketInfoToken = mProto->start(FIELD_TYPE_MESSAGE | FIELD_ID_BUCKET_INFO); 200 mProto->write(FIELD_TYPE_INT64 | FIELD_ID_START_BUCKET_NANOS, 201 (long long)bucket.mBucketStartNs); 202 mProto->write(FIELD_TYPE_INT64 | FIELD_ID_END_BUCKET_NANOS, 203 (long long)bucket.mBucketEndNs); 204 mProto->write(FIELD_TYPE_INT64 | FIELD_ID_DURATION, (long long)bucket.mDuration); 205 mProto->end(bucketInfoToken); 206 VLOG("\t bucket [%lld - %lld] duration: %lld", (long long)bucket.mBucketStartNs, 207 (long long)bucket.mBucketEndNs, (long long)bucket.mDuration); 208 } 209 210 mProto->end(wrapperToken); 211 } 212 213 mProto->end(mProtoToken); 214 mProto->write(FIELD_TYPE_INT64 | FIELD_ID_END_REPORT_NANOS, 215 (long long)mCurrentBucketStartTimeNs); 216 217 std::unique_ptr<uint8_t[]> buffer = serializeProto(); 218 219 startNewProtoOutputStream(endTime); 220 mPastBuckets.clear(); 221 222 // TODO: Once we migrate all MetricProducers to use ProtoOutputStream, we should return this: 223 // return std::move(buffer); 224 return StatsLogReport(); 225} 226 227void DurationMetricProducer::flushIfNeeded(uint64_t eventTime) { 228 if (mCurrentBucketStartTimeNs + mBucketSizeNs > eventTime) { 229 return; 230 } 231 232 VLOG("flushing..........."); 233 for (auto it = mCurrentSlicedDuration.begin(); it != mCurrentSlicedDuration.end(); ++it) { 234 if (it->second->flushIfNeeded(eventTime)) { 235 VLOG("erase bucket for key %s", it->first.c_str()); 236 mCurrentSlicedDuration.erase(it); 237 } 238 } 239 240 int numBucketsForward = (eventTime - mCurrentBucketStartTimeNs) / mBucketSizeNs; 241 mCurrentBucketStartTimeNs += numBucketsForward * mBucketSizeNs; 242} 243 244void DurationMetricProducer::onMatchedLogEventInternal( 245 const size_t matcherIndex, const HashableDimensionKey& eventKey, 246 const map<string, HashableDimensionKey>& conditionKeys, bool condition, 247 const LogEvent& event, bool scheduledPull) { 248 flushIfNeeded(event.GetTimestampNs()); 249 250 if (matcherIndex == mStopAllIndex) { 251 for (auto& pair : mCurrentSlicedDuration) { 252 pair.second->noteStopAll(event.GetTimestampNs()); 253 } 254 return; 255 } 256 257 HashableDimensionKey atomKey = getHashableKey(getDimensionKey(event, mInternalDimension)); 258 259 if (mCurrentSlicedDuration.find(eventKey) == mCurrentSlicedDuration.end()) { 260 mCurrentSlicedDuration[eventKey] = createDurationTracker(mPastBuckets[eventKey]); 261 } 262 263 auto it = mCurrentSlicedDuration.find(eventKey); 264 265 if (matcherIndex == mStartIndex) { 266 it->second->noteStart(atomKey, condition, event.GetTimestampNs(), conditionKeys); 267 } else if (matcherIndex == mStopIndex) { 268 it->second->noteStop(atomKey, event.GetTimestampNs()); 269 } 270} 271 272size_t DurationMetricProducer::byteSize() { 273 size_t totalSize = 0; 274 for (const auto& pair : mPastBuckets) { 275 totalSize += pair.second.size() * kBucketSize; 276 } 277 return totalSize; 278} 279 280} // namespace statsd 281} // namespace os 282} // namespace android 283