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 // STOPSHIP if true 18 #include "Log.h" 19 20 #include "CountMetricProducer.h" 21 22 #include <inttypes.h> 23 #include <limits.h> 24 #include <stdlib.h> 25 26 #include "guardrail/StatsdStats.h" 27 #include "metrics/parsing_utils/metrics_manager_util.h" 28 #include "stats_log_util.h" 29 #include "stats_util.h" 30 31 using android::util::FIELD_COUNT_REPEATED; 32 using android::util::FIELD_TYPE_BOOL; 33 using android::util::FIELD_TYPE_FLOAT; 34 using android::util::FIELD_TYPE_INT32; 35 using android::util::FIELD_TYPE_INT64; 36 using android::util::FIELD_TYPE_MESSAGE; 37 using android::util::FIELD_TYPE_STRING; 38 using android::util::ProtoOutputStream; 39 using std::map; 40 using std::string; 41 using std::unordered_map; 42 using std::vector; 43 using std::shared_ptr; 44 45 namespace android { 46 namespace os { 47 namespace statsd { 48 49 // for StatsLogReport 50 const int FIELD_ID_ID = 1; 51 const int FIELD_ID_COUNT_METRICS = 5; 52 const int FIELD_ID_TIME_BASE = 9; 53 const int FIELD_ID_BUCKET_SIZE = 10; 54 const int FIELD_ID_DIMENSION_PATH_IN_WHAT = 11; 55 const int FIELD_ID_IS_ACTIVE = 14; 56 57 // for CountMetricDataWrapper 58 const int FIELD_ID_DATA = 1; 59 // for CountMetricData 60 const int FIELD_ID_DIMENSION_IN_WHAT = 1; 61 const int FIELD_ID_SLICE_BY_STATE = 6; 62 const int FIELD_ID_BUCKET_INFO = 3; 63 const int FIELD_ID_DIMENSION_LEAF_IN_WHAT = 4; 64 // for CountBucketInfo 65 const int FIELD_ID_COUNT = 3; 66 const int FIELD_ID_BUCKET_NUM = 4; 67 const int FIELD_ID_START_BUCKET_ELAPSED_MILLIS = 5; 68 const int FIELD_ID_END_BUCKET_ELAPSED_MILLIS = 6; 69 70 CountMetricProducer::CountMetricProducer( 71 const ConfigKey& key, const CountMetric& metric, const int conditionIndex, 72 const vector<ConditionState>& initialConditionCache, const sp<ConditionWizard>& wizard, 73 const uint64_t protoHash, const int64_t timeBaseNs, const int64_t startTimeNs, 74 const unordered_map<int, shared_ptr<Activation>>& eventActivationMap, 75 const unordered_map<int, vector<shared_ptr<Activation>>>& eventDeactivationMap, 76 const vector<int>& slicedStateAtoms, 77 const unordered_map<int, unordered_map<int, int64_t>>& stateGroupMap) 78 : MetricProducer(metric.id(), key, timeBaseNs, conditionIndex, initialConditionCache, wizard, 79 protoHash, eventActivationMap, eventDeactivationMap, slicedStateAtoms, 80 stateGroupMap) { 81 if (metric.has_bucket()) { 82 mBucketSizeNs = 83 TimeUnitToBucketSizeInMillisGuardrailed(key.GetUid(), metric.bucket()) * 1000000; 84 } else { 85 mBucketSizeNs = LLONG_MAX; 86 } 87 88 if (metric.has_dimensions_in_what()) { 89 translateFieldMatcher(metric.dimensions_in_what(), &mDimensionsInWhat); 90 mContainANYPositionInDimensionsInWhat = HasPositionANY(metric.dimensions_in_what()); 91 } 92 93 mSliceByPositionALL = HasPositionALL(metric.dimensions_in_what()); 94 95 if (metric.links().size() > 0) { 96 for (const auto& link : metric.links()) { 97 Metric2Condition mc; 98 mc.conditionId = link.condition(); 99 translateFieldMatcher(link.fields_in_what(), &mc.metricFields); 100 translateFieldMatcher(link.fields_in_condition(), &mc.conditionFields); 101 mMetric2ConditionLinks.push_back(mc); 102 } 103 mConditionSliced = true; 104 } 105 106 for (const auto& stateLink : metric.state_link()) { 107 Metric2State ms; 108 ms.stateAtomId = stateLink.state_atom_id(); 109 translateFieldMatcher(stateLink.fields_in_what(), &ms.metricFields); 110 translateFieldMatcher(stateLink.fields_in_state(), &ms.stateFields); 111 mMetric2StateLinks.push_back(ms); 112 } 113 114 if (metric.has_threshold()) { 115 mUploadThreshold = metric.threshold(); 116 } 117 118 flushIfNeededLocked(startTimeNs); 119 // Adjust start for partial bucket 120 mCurrentBucketStartTimeNs = startTimeNs; 121 122 VLOG("metric %lld created. bucket size %lld start_time: %lld", (long long)metric.id(), 123 (long long)mBucketSizeNs, (long long)mTimeBaseNs); 124 } 125 126 CountMetricProducer::~CountMetricProducer() { 127 VLOG("~CountMetricProducer() called"); 128 } 129 130 bool CountMetricProducer::onConfigUpdatedLocked( 131 const StatsdConfig& config, const int configIndex, const int metricIndex, 132 const vector<sp<AtomMatchingTracker>>& allAtomMatchingTrackers, 133 const unordered_map<int64_t, int>& oldAtomMatchingTrackerMap, 134 const unordered_map<int64_t, int>& newAtomMatchingTrackerMap, 135 const sp<EventMatcherWizard>& matcherWizard, 136 const vector<sp<ConditionTracker>>& allConditionTrackers, 137 const unordered_map<int64_t, int>& conditionTrackerMap, const sp<ConditionWizard>& wizard, 138 const unordered_map<int64_t, int>& metricToActivationMap, 139 unordered_map<int, vector<int>>& trackerToMetricMap, 140 unordered_map<int, vector<int>>& conditionToMetricMap, 141 unordered_map<int, vector<int>>& activationAtomTrackerToMetricMap, 142 unordered_map<int, vector<int>>& deactivationAtomTrackerToMetricMap, 143 vector<int>& metricsWithActivation) { 144 if (!MetricProducer::onConfigUpdatedLocked( 145 config, configIndex, metricIndex, allAtomMatchingTrackers, 146 oldAtomMatchingTrackerMap, newAtomMatchingTrackerMap, matcherWizard, 147 allConditionTrackers, conditionTrackerMap, wizard, metricToActivationMap, 148 trackerToMetricMap, conditionToMetricMap, activationAtomTrackerToMetricMap, 149 deactivationAtomTrackerToMetricMap, metricsWithActivation)) { 150 return false; 151 } 152 153 const CountMetric& metric = config.count_metric(configIndex); 154 int trackerIndex; 155 // Update appropriate indices, specifically mConditionIndex and MetricsManager maps. 156 if (!handleMetricWithAtomMatchingTrackers(metric.what(), metricIndex, false, 157 allAtomMatchingTrackers, newAtomMatchingTrackerMap, 158 trackerToMetricMap, trackerIndex)) { 159 return false; 160 } 161 162 if (metric.has_condition() && 163 !handleMetricWithConditions(metric.condition(), metricIndex, conditionTrackerMap, 164 metric.links(), allConditionTrackers, mConditionTrackerIndex, 165 conditionToMetricMap)) { 166 return false; 167 } 168 return true; 169 } 170 171 void CountMetricProducer::onStateChanged(const int64_t eventTimeNs, const int32_t atomId, 172 const HashableDimensionKey& primaryKey, 173 const FieldValue& oldState, const FieldValue& newState) { 174 VLOG("CountMetric %lld onStateChanged time %lld, State%d, key %s, %d -> %d", 175 (long long)mMetricId, (long long)eventTimeNs, atomId, primaryKey.toString().c_str(), 176 oldState.mValue.int_value, newState.mValue.int_value); 177 } 178 179 void CountMetricProducer::dumpStatesLocked(FILE* out, bool verbose) const { 180 if (mCurrentSlicedCounter == nullptr || 181 mCurrentSlicedCounter->size() == 0) { 182 return; 183 } 184 185 fprintf(out, "CountMetric %lld dimension size %lu\n", (long long)mMetricId, 186 (unsigned long)mCurrentSlicedCounter->size()); 187 if (verbose) { 188 for (const auto& it : *mCurrentSlicedCounter) { 189 fprintf(out, "\t(what)%s\t(state)%s %lld\n", 190 it.first.getDimensionKeyInWhat().toString().c_str(), 191 it.first.getStateValuesKey().toString().c_str(), (unsigned long long)it.second); 192 } 193 } 194 } 195 196 void CountMetricProducer::onSlicedConditionMayChangeLocked(bool overallCondition, 197 const int64_t eventTime) { 198 VLOG("Metric %lld onSlicedConditionMayChange", (long long)mMetricId); 199 } 200 201 202 void CountMetricProducer::clearPastBucketsLocked(const int64_t dumpTimeNs) { 203 mPastBuckets.clear(); 204 } 205 206 void CountMetricProducer::onDumpReportLocked(const int64_t dumpTimeNs, 207 const bool include_current_partial_bucket, 208 const bool erase_data, 209 const DumpLatency dumpLatency, 210 std::set<string> *str_set, 211 ProtoOutputStream* protoOutput) { 212 if (include_current_partial_bucket) { 213 flushLocked(dumpTimeNs); 214 } else { 215 flushIfNeededLocked(dumpTimeNs); 216 } 217 protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_ID, (long long)mMetricId); 218 protoOutput->write(FIELD_TYPE_BOOL | FIELD_ID_IS_ACTIVE, isActiveLocked()); 219 220 221 if (mPastBuckets.empty()) { 222 return; 223 } 224 protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_TIME_BASE, (long long)mTimeBaseNs); 225 protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_BUCKET_SIZE, (long long)mBucketSizeNs); 226 227 // Fills the dimension path if not slicing by ALL. 228 if (!mSliceByPositionALL) { 229 if (!mDimensionsInWhat.empty()) { 230 uint64_t dimenPathToken = protoOutput->start( 231 FIELD_TYPE_MESSAGE | FIELD_ID_DIMENSION_PATH_IN_WHAT); 232 writeDimensionPathToProto(mDimensionsInWhat, protoOutput); 233 protoOutput->end(dimenPathToken); 234 } 235 } 236 237 uint64_t protoToken = protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_ID_COUNT_METRICS); 238 239 for (const auto& counter : mPastBuckets) { 240 const MetricDimensionKey& dimensionKey = counter.first; 241 VLOG(" dimension key %s", dimensionKey.toString().c_str()); 242 243 uint64_t wrapperToken = 244 protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_DATA); 245 246 // First fill dimension. 247 if (mSliceByPositionALL) { 248 uint64_t dimensionToken = protoOutput->start( 249 FIELD_TYPE_MESSAGE | FIELD_ID_DIMENSION_IN_WHAT); 250 writeDimensionToProto(dimensionKey.getDimensionKeyInWhat(), str_set, protoOutput); 251 protoOutput->end(dimensionToken); 252 } else { 253 writeDimensionLeafNodesToProto(dimensionKey.getDimensionKeyInWhat(), 254 FIELD_ID_DIMENSION_LEAF_IN_WHAT, str_set, protoOutput); 255 } 256 // Then fill slice_by_state. 257 for (auto state : dimensionKey.getStateValuesKey().getValues()) { 258 uint64_t stateToken = protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | 259 FIELD_ID_SLICE_BY_STATE); 260 writeStateToProto(state, protoOutput); 261 protoOutput->end(stateToken); 262 } 263 // Then fill bucket_info (CountBucketInfo). 264 for (const auto& bucket : counter.second) { 265 uint64_t bucketInfoToken = protoOutput->start( 266 FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_BUCKET_INFO); 267 // Partial bucket. 268 if (bucket.mBucketEndNs - bucket.mBucketStartNs != mBucketSizeNs) { 269 protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_START_BUCKET_ELAPSED_MILLIS, 270 (long long)NanoToMillis(bucket.mBucketStartNs)); 271 protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_END_BUCKET_ELAPSED_MILLIS, 272 (long long)NanoToMillis(bucket.mBucketEndNs)); 273 } else { 274 protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_BUCKET_NUM, 275 (long long)(getBucketNumFromEndTimeNs(bucket.mBucketEndNs))); 276 } 277 protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_COUNT, (long long)bucket.mCount); 278 protoOutput->end(bucketInfoToken); 279 VLOG("\t bucket [%lld - %lld] count: %lld", (long long)bucket.mBucketStartNs, 280 (long long)bucket.mBucketEndNs, (long long)bucket.mCount); 281 } 282 protoOutput->end(wrapperToken); 283 } 284 285 protoOutput->end(protoToken); 286 287 if (erase_data) { 288 mPastBuckets.clear(); 289 } 290 } 291 292 void CountMetricProducer::dropDataLocked(const int64_t dropTimeNs) { 293 flushIfNeededLocked(dropTimeNs); 294 StatsdStats::getInstance().noteBucketDropped(mMetricId); 295 mPastBuckets.clear(); 296 } 297 298 void CountMetricProducer::onConditionChangedLocked(const bool conditionMet, 299 const int64_t eventTime) { 300 VLOG("Metric %lld onConditionChanged", (long long)mMetricId); 301 mCondition = conditionMet ? ConditionState::kTrue : ConditionState::kFalse; 302 } 303 304 bool CountMetricProducer::hitGuardRailLocked(const MetricDimensionKey& newKey) { 305 if (mCurrentSlicedCounter->find(newKey) != mCurrentSlicedCounter->end()) { 306 return false; 307 } 308 // ===========GuardRail============== 309 // 1. Report the tuple count if the tuple count > soft limit 310 if (mCurrentSlicedCounter->size() > StatsdStats::kDimensionKeySizeSoftLimit - 1) { 311 size_t newTupleCount = mCurrentSlicedCounter->size() + 1; 312 StatsdStats::getInstance().noteMetricDimensionSize(mConfigKey, mMetricId, newTupleCount); 313 // 2. Don't add more tuples, we are above the allowed threshold. Drop the data. 314 if (newTupleCount > StatsdStats::kDimensionKeySizeHardLimit) { 315 ALOGE("CountMetric %lld dropping data for dimension key %s", 316 (long long)mMetricId, newKey.toString().c_str()); 317 StatsdStats::getInstance().noteHardDimensionLimitReached(mMetricId); 318 return true; 319 } 320 } 321 322 return false; 323 } 324 325 void CountMetricProducer::onMatchedLogEventInternalLocked( 326 const size_t matcherIndex, const MetricDimensionKey& eventKey, 327 const ConditionKey& conditionKey, bool condition, const LogEvent& event, 328 const map<int, HashableDimensionKey>& statePrimaryKeys) { 329 int64_t eventTimeNs = event.GetElapsedTimestampNs(); 330 flushIfNeededLocked(eventTimeNs); 331 332 if (!condition) { 333 return; 334 } 335 336 auto it = mCurrentSlicedCounter->find(eventKey); 337 if (it == mCurrentSlicedCounter->end()) { 338 // ===========GuardRail============== 339 if (hitGuardRailLocked(eventKey)) { 340 return; 341 } 342 // create a counter for the new key 343 (*mCurrentSlicedCounter)[eventKey] = 1; 344 } else { 345 // increment the existing value 346 auto& count = it->second; 347 count++; 348 } 349 for (auto& tracker : mAnomalyTrackers) { 350 int64_t countWholeBucket = mCurrentSlicedCounter->find(eventKey)->second; 351 auto prev = mCurrentFullCounters->find(eventKey); 352 if (prev != mCurrentFullCounters->end()) { 353 countWholeBucket += prev->second; 354 } 355 tracker->detectAndDeclareAnomaly(eventTimeNs, mCurrentBucketNum, mMetricId, eventKey, 356 countWholeBucket); 357 } 358 359 VLOG("metric %lld %s->%lld", (long long)mMetricId, eventKey.toString().c_str(), 360 (long long)(*mCurrentSlicedCounter)[eventKey]); 361 } 362 363 // When a new matched event comes in, we check if event falls into the current 364 // bucket. If not, flush the old counter to past buckets and initialize the new bucket. 365 void CountMetricProducer::flushIfNeededLocked(const int64_t& eventTimeNs) { 366 int64_t currentBucketEndTimeNs = getCurrentBucketEndTimeNs(); 367 if (eventTimeNs < currentBucketEndTimeNs) { 368 return; 369 } 370 371 // Setup the bucket start time and number. 372 int64_t numBucketsForward = 1 + (eventTimeNs - currentBucketEndTimeNs) / mBucketSizeNs; 373 int64_t nextBucketNs = currentBucketEndTimeNs + (numBucketsForward - 1) * mBucketSizeNs; 374 flushCurrentBucketLocked(eventTimeNs, nextBucketNs); 375 376 mCurrentBucketNum += numBucketsForward; 377 VLOG("metric %lld: new bucket start time: %lld", (long long)mMetricId, 378 (long long)mCurrentBucketStartTimeNs); 379 } 380 381 bool CountMetricProducer::countPassesThreshold(const int64_t& count) { 382 if (mUploadThreshold == nullopt) { 383 return true; 384 } 385 386 switch (mUploadThreshold->value_comparison_case()) { 387 case UploadThreshold::kLtInt: 388 return count < mUploadThreshold->lt_int(); 389 case UploadThreshold::kGtInt: 390 return count > mUploadThreshold->gt_int(); 391 case UploadThreshold::kLteInt: 392 return count <= mUploadThreshold->lte_int(); 393 case UploadThreshold::kGteInt: 394 return count >= mUploadThreshold->gte_int(); 395 default: 396 ALOGE("Count metric incorrect upload threshold type used"); 397 return false; 398 } 399 } 400 401 void CountMetricProducer::flushCurrentBucketLocked(const int64_t& eventTimeNs, 402 const int64_t& nextBucketStartTimeNs) { 403 int64_t fullBucketEndTimeNs = getCurrentBucketEndTimeNs(); 404 CountBucket info; 405 info.mBucketStartNs = mCurrentBucketStartTimeNs; 406 if (eventTimeNs < fullBucketEndTimeNs) { 407 info.mBucketEndNs = eventTimeNs; 408 } else { 409 info.mBucketEndNs = fullBucketEndTimeNs; 410 } 411 for (const auto& counter : *mCurrentSlicedCounter) { 412 if (countPassesThreshold(counter.second)) { 413 info.mCount = counter.second; 414 auto& bucketList = mPastBuckets[counter.first]; 415 bucketList.push_back(info); 416 VLOG("metric %lld, dump key value: %s -> %lld", (long long)mMetricId, 417 counter.first.toString().c_str(), (long long)counter.second); 418 } 419 } 420 421 // If we have finished a full bucket, then send this to anomaly tracker. 422 if (eventTimeNs > fullBucketEndTimeNs) { 423 // Accumulate partial buckets with current value and then send to anomaly tracker. 424 if (mCurrentFullCounters->size() > 0) { 425 for (const auto& keyValuePair : *mCurrentSlicedCounter) { 426 (*mCurrentFullCounters)[keyValuePair.first] += keyValuePair.second; 427 } 428 for (auto& tracker : mAnomalyTrackers) { 429 tracker->addPastBucket(mCurrentFullCounters, mCurrentBucketNum); 430 } 431 mCurrentFullCounters = std::make_shared<DimToValMap>(); 432 } else { 433 // Skip aggregating the partial buckets since there's no previous partial bucket. 434 for (auto& tracker : mAnomalyTrackers) { 435 tracker->addPastBucket(mCurrentSlicedCounter, mCurrentBucketNum); 436 } 437 } 438 } else { 439 // Accumulate partial bucket. 440 for (const auto& keyValuePair : *mCurrentSlicedCounter) { 441 (*mCurrentFullCounters)[keyValuePair.first] += keyValuePair.second; 442 } 443 } 444 445 StatsdStats::getInstance().noteBucketCount(mMetricId); 446 // Only resets the counters, but doesn't setup the times nor numbers. 447 // (Do not clear since the old one is still referenced in mAnomalyTrackers). 448 mCurrentSlicedCounter = std::make_shared<DimToValMap>(); 449 mCurrentBucketStartTimeNs = nextBucketStartTimeNs; 450 } 451 452 // Rough estimate of CountMetricProducer buffer stored. This number will be 453 // greater than actual data size as it contains each dimension of 454 // CountMetricData is duplicated. 455 size_t CountMetricProducer::byteSizeLocked() const { 456 size_t totalSize = 0; 457 for (const auto& pair : mPastBuckets) { 458 totalSize += pair.second.size() * kBucketSize; 459 } 460 return totalSize; 461 } 462 463 } // namespace statsd 464 } // namespace os 465 } // namespace android 466