Yao Chen | 729093d | 2017-10-16 10:33:26 -0700 | [diff] [blame^] | 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 | #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 | |
| 26 | using std::string; |
| 27 | using std::unordered_map; |
| 28 | using std::vector; |
| 29 | |
| 30 | namespace android { |
| 31 | namespace os { |
| 32 | namespace statsd { |
| 33 | |
| 34 | DurationMetricProducer::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 | |
| 66 | DurationMetricProducer::~DurationMetricProducer() { |
| 67 | VLOG("~DurationMetric() called"); |
| 68 | } |
| 69 | |
| 70 | void DurationMetricProducer::finish() { |
| 71 | // TODO: write the StatsLogReport to dropbox using |
| 72 | // DropboxWriter. |
| 73 | } |
| 74 | |
| 75 | void 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 | |
| 91 | void 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 | |
| 101 | static 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 | |
| 115 | StatsLogReport 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 | |
| 140 | void 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 | |
| 198 | void 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 | |
| 235 | void 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 | |
| 260 | void 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 | |
| 288 | int64_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 | |
| 310 | void 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. |
| 318 | void 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 |