Home | History | Annotate | Download | only in metrics
      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 #include "guardrail/StatsdStats.h"
     22 #include "stats_util.h"
     23 #include "stats_log_util.h"
     24 
     25 #include <limits.h>
     26 #include <stdlib.h>
     27 
     28 using android::util::FIELD_COUNT_REPEATED;
     29 using android::util::FIELD_TYPE_BOOL;
     30 using android::util::FIELD_TYPE_FLOAT;
     31 using android::util::FIELD_TYPE_INT32;
     32 using android::util::FIELD_TYPE_INT64;
     33 using android::util::FIELD_TYPE_MESSAGE;
     34 using android::util::FIELD_TYPE_STRING;
     35 using android::util::ProtoOutputStream;
     36 using std::map;
     37 using std::string;
     38 using std::unordered_map;
     39 using std::vector;
     40 
     41 namespace android {
     42 namespace os {
     43 namespace statsd {
     44 
     45 // for StatsLogReport
     46 const int FIELD_ID_ID = 1;
     47 const int FIELD_ID_COUNT_METRICS = 5;
     48 const int FIELD_ID_TIME_BASE = 9;
     49 const int FIELD_ID_BUCKET_SIZE = 10;
     50 const int FIELD_ID_DIMENSION_PATH_IN_WHAT = 11;
     51 const int FIELD_ID_DIMENSION_PATH_IN_CONDITION = 12;
     52 const int FIELD_ID_IS_ACTIVE = 14;
     53 
     54 // for CountMetricDataWrapper
     55 const int FIELD_ID_DATA = 1;
     56 // for CountMetricData
     57 const int FIELD_ID_DIMENSION_IN_WHAT = 1;
     58 const int FIELD_ID_DIMENSION_IN_CONDITION = 2;
     59 const int FIELD_ID_BUCKET_INFO = 3;
     60 const int FIELD_ID_DIMENSION_LEAF_IN_WHAT = 4;
     61 const int FIELD_ID_DIMENSION_LEAF_IN_CONDITION = 5;
     62 // for CountBucketInfo
     63 const int FIELD_ID_COUNT = 3;
     64 const int FIELD_ID_BUCKET_NUM = 4;
     65 const int FIELD_ID_START_BUCKET_ELAPSED_MILLIS = 5;
     66 const int FIELD_ID_END_BUCKET_ELAPSED_MILLIS = 6;
     67 
     68 CountMetricProducer::CountMetricProducer(const ConfigKey& key, const CountMetric& metric,
     69                                          const int conditionIndex,
     70                                          const sp<ConditionWizard>& wizard,
     71                                          const int64_t timeBaseNs, const int64_t startTimeNs)
     72     : MetricProducer(metric.id(), key, timeBaseNs, conditionIndex, wizard) {
     73     if (metric.has_bucket()) {
     74         mBucketSizeNs =
     75                 TimeUnitToBucketSizeInMillisGuardrailed(key.GetUid(), metric.bucket()) * 1000000;
     76     } else {
     77         mBucketSizeNs = LLONG_MAX;
     78     }
     79 
     80     if (metric.has_dimensions_in_what()) {
     81         translateFieldMatcher(metric.dimensions_in_what(), &mDimensionsInWhat);
     82         mContainANYPositionInDimensionsInWhat = HasPositionANY(metric.dimensions_in_what());
     83     }
     84 
     85     mSliceByPositionALL = HasPositionALL(metric.dimensions_in_what()) ||
     86             HasPositionALL(metric.dimensions_in_condition());
     87 
     88     if (metric.has_dimensions_in_condition()) {
     89         translateFieldMatcher(metric.dimensions_in_condition(), &mDimensionsInCondition);
     90     }
     91 
     92     if (metric.links().size() > 0) {
     93         for (const auto& link : metric.links()) {
     94             Metric2Condition mc;
     95             mc.conditionId = link.condition();
     96             translateFieldMatcher(link.fields_in_what(), &mc.metricFields);
     97             translateFieldMatcher(link.fields_in_condition(), &mc.conditionFields);
     98             mMetric2ConditionLinks.push_back(mc);
     99         }
    100         mConditionSliced = true;
    101     }
    102 
    103     mConditionSliced = (metric.links().size() > 0) || (mDimensionsInCondition.size() > 0);
    104 
    105     flushIfNeededLocked(startTimeNs);
    106     // Adjust start for partial bucket
    107     mCurrentBucketStartTimeNs = startTimeNs;
    108 
    109     VLOG("metric %lld created. bucket size %lld start_time: %lld", (long long)metric.id(),
    110          (long long)mBucketSizeNs, (long long)mTimeBaseNs);
    111 }
    112 
    113 CountMetricProducer::~CountMetricProducer() {
    114     VLOG("~CountMetricProducer() called");
    115 }
    116 
    117 void CountMetricProducer::dumpStatesLocked(FILE* out, bool verbose) const {
    118     if (mCurrentSlicedCounter == nullptr ||
    119         mCurrentSlicedCounter->size() == 0) {
    120         return;
    121     }
    122 
    123     fprintf(out, "CountMetric %lld dimension size %lu\n", (long long)mMetricId,
    124             (unsigned long)mCurrentSlicedCounter->size());
    125     if (verbose) {
    126         for (const auto& it : *mCurrentSlicedCounter) {
    127             fprintf(out, "\t(what)%s\t(condition)%s  %lld\n",
    128                 it.first.getDimensionKeyInWhat().toString().c_str(),
    129                 it.first.getDimensionKeyInCondition().toString().c_str(),
    130                 (unsigned long long)it.second);
    131         }
    132     }
    133 }
    134 
    135 void CountMetricProducer::onSlicedConditionMayChangeLocked(bool overallCondition,
    136                                                            const int64_t eventTime) {
    137     VLOG("Metric %lld onSlicedConditionMayChange", (long long)mMetricId);
    138 }
    139 
    140 
    141 void CountMetricProducer::clearPastBucketsLocked(const int64_t dumpTimeNs) {
    142     mPastBuckets.clear();
    143 }
    144 
    145 void CountMetricProducer::onDumpReportLocked(const int64_t dumpTimeNs,
    146                                              const bool include_current_partial_bucket,
    147                                              const bool erase_data,
    148                                              const DumpLatency dumpLatency,
    149                                              std::set<string> *str_set,
    150                                              ProtoOutputStream* protoOutput) {
    151     if (include_current_partial_bucket) {
    152         flushLocked(dumpTimeNs);
    153     } else {
    154         flushIfNeededLocked(dumpTimeNs);
    155     }
    156     protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_ID, (long long)mMetricId);
    157     protoOutput->write(FIELD_TYPE_BOOL | FIELD_ID_IS_ACTIVE, isActiveLocked());
    158 
    159 
    160     if (mPastBuckets.empty()) {
    161         return;
    162     }
    163     protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_TIME_BASE, (long long)mTimeBaseNs);
    164     protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_BUCKET_SIZE, (long long)mBucketSizeNs);
    165 
    166     // Fills the dimension path if not slicing by ALL.
    167     if (!mSliceByPositionALL) {
    168         if (!mDimensionsInWhat.empty()) {
    169             uint64_t dimenPathToken = protoOutput->start(
    170                     FIELD_TYPE_MESSAGE | FIELD_ID_DIMENSION_PATH_IN_WHAT);
    171             writeDimensionPathToProto(mDimensionsInWhat, protoOutput);
    172             protoOutput->end(dimenPathToken);
    173         }
    174         if (!mDimensionsInCondition.empty()) {
    175             uint64_t dimenPathToken = protoOutput->start(
    176                     FIELD_TYPE_MESSAGE | FIELD_ID_DIMENSION_PATH_IN_CONDITION);
    177             writeDimensionPathToProto(mDimensionsInCondition, protoOutput);
    178             protoOutput->end(dimenPathToken);
    179         }
    180 
    181     }
    182 
    183     uint64_t protoToken = protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_ID_COUNT_METRICS);
    184 
    185     for (const auto& counter : mPastBuckets) {
    186         const MetricDimensionKey& dimensionKey = counter.first;
    187         VLOG("  dimension key %s", dimensionKey.toString().c_str());
    188 
    189         uint64_t wrapperToken =
    190                 protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_DATA);
    191 
    192         // First fill dimension.
    193         if (mSliceByPositionALL) {
    194             uint64_t dimensionToken = protoOutput->start(
    195                     FIELD_TYPE_MESSAGE | FIELD_ID_DIMENSION_IN_WHAT);
    196             writeDimensionToProto(dimensionKey.getDimensionKeyInWhat(), str_set, protoOutput);
    197             protoOutput->end(dimensionToken);
    198 
    199             if (dimensionKey.hasDimensionKeyInCondition()) {
    200                 uint64_t dimensionInConditionToken = protoOutput->start(
    201                         FIELD_TYPE_MESSAGE | FIELD_ID_DIMENSION_IN_CONDITION);
    202                 writeDimensionToProto(dimensionKey.getDimensionKeyInCondition(),
    203                                       str_set, protoOutput);
    204                 protoOutput->end(dimensionInConditionToken);
    205             }
    206         } else {
    207             writeDimensionLeafNodesToProto(dimensionKey.getDimensionKeyInWhat(),
    208                                            FIELD_ID_DIMENSION_LEAF_IN_WHAT, str_set, protoOutput);
    209             if (dimensionKey.hasDimensionKeyInCondition()) {
    210                 writeDimensionLeafNodesToProto(dimensionKey.getDimensionKeyInCondition(),
    211                                                FIELD_ID_DIMENSION_LEAF_IN_CONDITION,
    212                                                str_set, protoOutput);
    213             }
    214         }
    215         // Then fill bucket_info (CountBucketInfo).
    216         for (const auto& bucket : counter.second) {
    217             uint64_t bucketInfoToken = protoOutput->start(
    218                     FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_BUCKET_INFO);
    219             // Partial bucket.
    220             if (bucket.mBucketEndNs - bucket.mBucketStartNs != mBucketSizeNs) {
    221                 protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_START_BUCKET_ELAPSED_MILLIS,
    222                                    (long long)NanoToMillis(bucket.mBucketStartNs));
    223                 protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_END_BUCKET_ELAPSED_MILLIS,
    224                                    (long long)NanoToMillis(bucket.mBucketEndNs));
    225             } else {
    226                 protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_BUCKET_NUM,
    227                                    (long long)(getBucketNumFromEndTimeNs(bucket.mBucketEndNs)));
    228             }
    229             protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_COUNT, (long long)bucket.mCount);
    230             protoOutput->end(bucketInfoToken);
    231             VLOG("\t bucket [%lld - %lld] count: %lld", (long long)bucket.mBucketStartNs,
    232                  (long long)bucket.mBucketEndNs, (long long)bucket.mCount);
    233         }
    234         protoOutput->end(wrapperToken);
    235     }
    236 
    237     protoOutput->end(protoToken);
    238 
    239     if (erase_data) {
    240         mPastBuckets.clear();
    241     }
    242 }
    243 
    244 void CountMetricProducer::dropDataLocked(const int64_t dropTimeNs) {
    245     flushIfNeededLocked(dropTimeNs);
    246     StatsdStats::getInstance().noteBucketDropped(mMetricId);
    247     mPastBuckets.clear();
    248 }
    249 
    250 void CountMetricProducer::onConditionChangedLocked(const bool conditionMet,
    251                                                    const int64_t eventTime) {
    252     VLOG("Metric %lld onConditionChanged", (long long)mMetricId);
    253     mCondition = conditionMet ? ConditionState::kTrue : ConditionState::kFalse;
    254 }
    255 
    256 bool CountMetricProducer::hitGuardRailLocked(const MetricDimensionKey& newKey) {
    257     if (mCurrentSlicedCounter->find(newKey) != mCurrentSlicedCounter->end()) {
    258         return false;
    259     }
    260     // ===========GuardRail==============
    261     // 1. Report the tuple count if the tuple count > soft limit
    262     if (mCurrentSlicedCounter->size() > StatsdStats::kDimensionKeySizeSoftLimit - 1) {
    263         size_t newTupleCount = mCurrentSlicedCounter->size() + 1;
    264         StatsdStats::getInstance().noteMetricDimensionSize(mConfigKey, mMetricId, newTupleCount);
    265         // 2. Don't add more tuples, we are above the allowed threshold. Drop the data.
    266         if (newTupleCount > StatsdStats::kDimensionKeySizeHardLimit) {
    267             ALOGE("CountMetric %lld dropping data for dimension key %s",
    268                 (long long)mMetricId, newKey.toString().c_str());
    269             return true;
    270         }
    271     }
    272 
    273     return false;
    274 }
    275 
    276 void CountMetricProducer::onMatchedLogEventInternalLocked(
    277         const size_t matcherIndex, const MetricDimensionKey& eventKey,
    278         const ConditionKey& conditionKey, bool condition,
    279         const LogEvent& event) {
    280     int64_t eventTimeNs = event.GetElapsedTimestampNs();
    281     flushIfNeededLocked(eventTimeNs);
    282 
    283     if (condition == false) {
    284         return;
    285     }
    286 
    287     auto it = mCurrentSlicedCounter->find(eventKey);
    288     if (it == mCurrentSlicedCounter->end()) {
    289         // ===========GuardRail==============
    290         if (hitGuardRailLocked(eventKey)) {
    291             return;
    292         }
    293         // create a counter for the new key
    294         (*mCurrentSlicedCounter)[eventKey] = 1;
    295     } else {
    296         // increment the existing value
    297         auto& count = it->second;
    298         count++;
    299     }
    300     for (auto& tracker : mAnomalyTrackers) {
    301         int64_t countWholeBucket = mCurrentSlicedCounter->find(eventKey)->second;
    302         auto prev = mCurrentFullCounters->find(eventKey);
    303         if (prev != mCurrentFullCounters->end()) {
    304             countWholeBucket += prev->second;
    305         }
    306         tracker->detectAndDeclareAnomaly(eventTimeNs, mCurrentBucketNum, mMetricId, eventKey,
    307                                          countWholeBucket);
    308     }
    309 
    310     VLOG("metric %lld %s->%lld", (long long)mMetricId, eventKey.toString().c_str(),
    311          (long long)(*mCurrentSlicedCounter)[eventKey]);
    312 }
    313 
    314 // When a new matched event comes in, we check if event falls into the current
    315 // bucket. If not, flush the old counter to past buckets and initialize the new bucket.
    316 void CountMetricProducer::flushIfNeededLocked(const int64_t& eventTimeNs) {
    317     int64_t currentBucketEndTimeNs = getCurrentBucketEndTimeNs();
    318     if (eventTimeNs < currentBucketEndTimeNs) {
    319         return;
    320     }
    321 
    322     // Setup the bucket start time and number.
    323     int64_t numBucketsForward = 1 + (eventTimeNs - currentBucketEndTimeNs) / mBucketSizeNs;
    324     int64_t nextBucketNs = currentBucketEndTimeNs + (numBucketsForward - 1) * mBucketSizeNs;
    325     flushCurrentBucketLocked(eventTimeNs, nextBucketNs);
    326 
    327     mCurrentBucketNum += numBucketsForward;
    328     VLOG("metric %lld: new bucket start time: %lld", (long long)mMetricId,
    329          (long long)mCurrentBucketStartTimeNs);
    330 }
    331 
    332 void CountMetricProducer::flushCurrentBucketLocked(const int64_t& eventTimeNs,
    333                                                    const int64_t& nextBucketStartTimeNs) {
    334     int64_t fullBucketEndTimeNs = getCurrentBucketEndTimeNs();
    335     CountBucket info;
    336     info.mBucketStartNs = mCurrentBucketStartTimeNs;
    337     if (eventTimeNs < fullBucketEndTimeNs) {
    338         info.mBucketEndNs = eventTimeNs;
    339     } else {
    340         info.mBucketEndNs = fullBucketEndTimeNs;
    341     }
    342     for (const auto& counter : *mCurrentSlicedCounter) {
    343         info.mCount = counter.second;
    344         auto& bucketList = mPastBuckets[counter.first];
    345         bucketList.push_back(info);
    346         VLOG("metric %lld, dump key value: %s -> %lld", (long long)mMetricId,
    347              counter.first.toString().c_str(),
    348              (long long)counter.second);
    349     }
    350 
    351     // If we have finished a full bucket, then send this to anomaly tracker.
    352     if (eventTimeNs > fullBucketEndTimeNs) {
    353         // Accumulate partial buckets with current value and then send to anomaly tracker.
    354         if (mCurrentFullCounters->size() > 0) {
    355             for (const auto& keyValuePair : *mCurrentSlicedCounter) {
    356                 (*mCurrentFullCounters)[keyValuePair.first] += keyValuePair.second;
    357             }
    358             for (auto& tracker : mAnomalyTrackers) {
    359                 tracker->addPastBucket(mCurrentFullCounters, mCurrentBucketNum);
    360             }
    361             mCurrentFullCounters = std::make_shared<DimToValMap>();
    362         } else {
    363             // Skip aggregating the partial buckets since there's no previous partial bucket.
    364             for (auto& tracker : mAnomalyTrackers) {
    365                 tracker->addPastBucket(mCurrentSlicedCounter, mCurrentBucketNum);
    366             }
    367         }
    368     } else {
    369         // Accumulate partial bucket.
    370         for (const auto& keyValuePair : *mCurrentSlicedCounter) {
    371             (*mCurrentFullCounters)[keyValuePair.first] += keyValuePair.second;
    372         }
    373     }
    374 
    375     StatsdStats::getInstance().noteBucketCount(mMetricId);
    376     // Only resets the counters, but doesn't setup the times nor numbers.
    377     // (Do not clear since the old one is still referenced in mAnomalyTrackers).
    378     mCurrentSlicedCounter = std::make_shared<DimToValMap>();
    379     mCurrentBucketStartTimeNs = nextBucketStartTimeNs;
    380 }
    381 
    382 // Rough estimate of CountMetricProducer buffer stored. This number will be
    383 // greater than actual data size as it contains each dimension of
    384 // CountMetricData is  duplicated.
    385 size_t CountMetricProducer::byteSizeLocked() const {
    386     size_t totalSize = 0;
    387     for (const auto& pair : mPastBuckets) {
    388         totalSize += pair.second.size() * kBucketSize;
    389     }
    390     return totalSize;
    391 }
    392 
    393 }  // namespace statsd
    394 }  // namespace os
    395 }  // namespace android
    396