From 9369446f0b04945d6674550728ae81196d6fb5c2 Mon Sep 17 00:00:00 2001 From: Yangster-mac Date: Mon, 22 Jan 2018 20:49:31 -0800 Subject: [PATCH] Support dimension in condition in metric producers. Test: added e2e tests for count/duration metrics sliced by fields in condition and with/without links. Change-Id: Ie34deba68e6780abdde458be3f0ce5284e76a1a2 --- cmds/statsd/Android.mk | 4 +- cmds/statsd/src/HashableDimensionKey.cpp | 26 +- cmds/statsd/src/HashableDimensionKey.h | 61 +- cmds/statsd/src/StatsLogProcessor.h | 5 +- cmds/statsd/src/anomaly/AnomalyTracker.cpp | 18 +- cmds/statsd/src/anomaly/AnomalyTracker.h | 20 +- .../src/anomaly/DurationAnomalyTracker.cpp | 12 +- .../src/anomaly/DurationAnomalyTracker.h | 8 +- .../condition/CombinationConditionTracker.cpp | 32 +- .../condition/CombinationConditionTracker.h | 14 +- cmds/statsd/src/condition/ConditionTracker.h | 16 +- cmds/statsd/src/condition/ConditionWizard.cpp | 19 +- cmds/statsd/src/condition/ConditionWizard.h | 9 +- .../src/condition/SimpleConditionTracker.cpp | 127 ++- .../src/condition/SimpleConditionTracker.h | 12 +- cmds/statsd/src/condition/condition_util.cpp | 3 + cmds/statsd/src/dimension.cpp | 43 ++ cmds/statsd/src/dimension.h | 3 + .../src/metrics/CountMetricProducer.cpp | 35 +- cmds/statsd/src/metrics/CountMetricProducer.h | 6 +- .../src/metrics/DurationMetricProducer.cpp | 100 ++- .../src/metrics/DurationMetricProducer.h | 12 +- .../src/metrics/EventMetricProducer.cpp | 2 +- cmds/statsd/src/metrics/EventMetricProducer.h | 2 +- .../src/metrics/GaugeMetricProducer.cpp | 25 +- cmds/statsd/src/metrics/GaugeMetricProducer.h | 8 +- cmds/statsd/src/metrics/MetricProducer.cpp | 48 +- cmds/statsd/src/metrics/MetricProducer.h | 6 +- cmds/statsd/src/metrics/MetricsManager.h | 4 + .../src/metrics/ValueMetricProducer.cpp | 29 +- cmds/statsd/src/metrics/ValueMetricProducer.h | 8 +- .../metrics/duration_helper/DurationTracker.h | 18 +- .../duration_helper/MaxDurationTracker.cpp | 32 +- .../duration_helper/MaxDurationTracker.h | 13 +- .../duration_helper/OringDurationTracker.cpp | 41 +- .../duration_helper/OringDurationTracker.h | 12 +- cmds/statsd/src/stats_log_util.cpp | 6 + cmds/statsd/src/stats_util.h | 3 +- .../src/subscriber/SubscriberReporter.cpp | 8 +- .../src/subscriber/SubscriberReporter.h | 6 +- .../tests/anomaly/AnomalyTracker_test.cpp | 36 +- .../condition/SimpleConditionTracker_test.cpp | 37 +- cmds/statsd/tests/dimension_test.cpp | 149 ++++ .../e2e/DimensionInCondition_e2e_test.cpp | 725 ++++++++++++++++++ .../e2e/MetricConditionLink_e2e_test.cpp | 22 +- .../metrics/CountMetricProducer_test.cpp | 34 +- .../metrics/DurationMetricProducer_test.cpp | 8 +- .../metrics/EventMetricProducer_test.cpp | 4 +- .../metrics/GaugeMetricProducer_test.cpp | 6 +- .../tests/metrics/MaxDurationTracker_test.cpp | 79 +- .../metrics/OringDurationTracker_test.cpp | 141 +++- .../metrics/ValueMetricProducer_test.cpp | 10 +- .../tests/metrics/metrics_test_helper.cpp | 7 + .../tests/metrics/metrics_test_helper.h | 7 +- cmds/statsd/tests/statsd_test_util.cpp | 87 ++- cmds/statsd/tests/statsd_test_util.h | 36 +- 56 files changed, 1892 insertions(+), 352 deletions(-) create mode 100644 cmds/statsd/tests/dimension_test.cpp create mode 100644 cmds/statsd/tests/e2e/DimensionInCondition_e2e_test.cpp diff --git a/cmds/statsd/Android.mk b/cmds/statsd/Android.mk index eabbb96a392ef..76dbd6a1dd01b 100644 --- a/cmds/statsd/Android.mk +++ b/cmds/statsd/Android.mk @@ -165,6 +165,7 @@ LOCAL_CFLAGS += \ LOCAL_SRC_FILES := \ $(statsd_common_src) \ + tests/dimension_test.cpp \ tests/AnomalyMonitor_test.cpp \ tests/anomaly/AnomalyTracker_test.cpp \ tests/ConfigManager_test.cpp \ @@ -190,7 +191,8 @@ LOCAL_SRC_FILES := \ tests/e2e/WakelockDuration_e2e_test.cpp \ tests/e2e/MetricConditionLink_e2e_test.cpp \ tests/e2e/Attribution_e2e_test.cpp \ - tests/e2e/GaugeMetric_e2e_test.cpp + tests/e2e/GaugeMetric_e2e_test.cpp \ + tests/e2e/DimensionInCondition_e2e_test.cpp LOCAL_STATIC_LIBRARIES := \ $(statsd_common_static_libraries) \ diff --git a/cmds/statsd/src/HashableDimensionKey.cpp b/cmds/statsd/src/HashableDimensionKey.cpp index 857a6ddad0bea..f0eaeff88d34f 100644 --- a/cmds/statsd/src/HashableDimensionKey.cpp +++ b/cmds/statsd/src/HashableDimensionKey.cpp @@ -67,12 +67,16 @@ android::hash_t hashDimensionsValue(const DimensionsValue& value) { return hashDimensionsValue(0, value); } +android::hash_t hashMetricDimensionKey(int64_t seed, const MetricDimensionKey& dimensionKey) { + android::hash_t hash = seed; + hash = android::JenkinsHashMix(hash, std::hash{}(dimensionKey)); + return JenkinsHashWhiten(hash); +} + using std::string; string HashableDimensionKey::toString() const { - string flattened; - DimensionsValueToString(getDimensionsValue(), &flattened); - return flattened; + return DimensionsValueToString(getDimensionsValue()); } bool EqualsTo(const DimensionsValue& s1, const DimensionsValue& s2) { @@ -162,6 +166,22 @@ bool HashableDimensionKey::operator<(const HashableDimensionKey& that) const { return LessThan(getDimensionsValue(), that.getDimensionsValue()); }; +string MetricDimensionKey::toString() const { + string flattened = mDimensionKeyInWhat.toString(); + flattened += mDimensionKeyInCondition.toString(); + return flattened; +} + +bool MetricDimensionKey::operator==(const MetricDimensionKey& that) const { + return mDimensionKeyInWhat == that.getDimensionKeyInWhat() && + mDimensionKeyInCondition == that.getDimensionKeyInCondition(); +}; + +bool MetricDimensionKey::operator<(const MetricDimensionKey& that) const { + return toString().compare(that.toString()) < 0; +}; + + } // namespace statsd } // namespace os } // namespace android \ No newline at end of file diff --git a/cmds/statsd/src/HashableDimensionKey.h b/cmds/statsd/src/HashableDimensionKey.h index 85c317f8cf1f3..a31d7a6d85c6c 100644 --- a/cmds/statsd/src/HashableDimensionKey.h +++ b/cmds/statsd/src/HashableDimensionKey.h @@ -41,6 +41,10 @@ public: return mDimensionsValue; } + inline DimensionsValue* getMutableDimensionsValue() { + return &mDimensionsValue; + } + bool operator==(const HashableDimensionKey& that) const; bool operator<(const HashableDimensionKey& that) const; @@ -53,8 +57,52 @@ private: DimensionsValue mDimensionsValue; }; +class MetricDimensionKey { + public: + explicit MetricDimensionKey(const HashableDimensionKey& dimensionKeyInWhat, + const HashableDimensionKey& dimensionKeyInCondition) + : mDimensionKeyInWhat(dimensionKeyInWhat), + mDimensionKeyInCondition(dimensionKeyInCondition) {}; + + MetricDimensionKey(){}; + + MetricDimensionKey(const MetricDimensionKey& that) + : mDimensionKeyInWhat(that.getDimensionKeyInWhat()), + mDimensionKeyInCondition(that.getDimensionKeyInCondition()) {}; + + MetricDimensionKey& operator=(const MetricDimensionKey& from) = default; + + std::string toString() const; + + inline const HashableDimensionKey& getDimensionKeyInWhat() const { + return mDimensionKeyInWhat; + } + + inline const HashableDimensionKey& getDimensionKeyInCondition() const { + return mDimensionKeyInCondition; + } + + bool hasDimensionKeyInCondition() const { + return mDimensionKeyInCondition.getDimensionsValue().has_field(); + } + + bool operator==(const MetricDimensionKey& that) const; + + bool operator<(const MetricDimensionKey& that) const; + + inline const char* c_str() const { + return toString().c_str(); + } + private: + HashableDimensionKey mDimensionKeyInWhat; + HashableDimensionKey mDimensionKeyInCondition; +}; + +bool compareDimensionsValue(const DimensionsValue& s1, const DimensionsValue& s2); + android::hash_t hashDimensionsValue(int64_t seed, const DimensionsValue& value); android::hash_t hashDimensionsValue(const DimensionsValue& value); +android::hash_t hashMetricDimensionKey(int64_t see, const MetricDimensionKey& dimensionKey); } // namespace statsd } // namespace os @@ -63,6 +111,7 @@ android::hash_t hashDimensionsValue(const DimensionsValue& value); namespace std { using android::os::statsd::HashableDimensionKey; +using android::os::statsd::MetricDimensionKey; template <> struct hash { @@ -71,4 +120,14 @@ struct hash { } }; -} // namespace std +template <> +struct hash { + std::size_t operator()(const MetricDimensionKey& key) const { + android::hash_t hash = hashDimensionsValue( + key.getDimensionKeyInWhat().getDimensionsValue()); + hash = android::JenkinsHashMix(hash, + hashDimensionsValue(key.getDimensionKeyInCondition().getDimensionsValue())); + return android::JenkinsHashWhiten(hash); + } +}; +} // namespace std \ No newline at end of file diff --git a/cmds/statsd/src/StatsLogProcessor.h b/cmds/statsd/src/StatsLogProcessor.h index c19ff63e28588..7642aafaab437 100644 --- a/cmds/statsd/src/StatsLogProcessor.h +++ b/cmds/statsd/src/StatsLogProcessor.h @@ -104,7 +104,10 @@ private: FRIEND_TEST(MetricConditionLinkE2eTest, TestMultiplePredicatesAndLinks); FRIEND_TEST(AttributionE2eTest, TestAttributionMatchAndSlice); FRIEND_TEST(GaugeMetricE2eTest, TestMultipleFieldsForPushedEvent); - + FRIEND_TEST(DimensionInConditionE2eTest, TestCountMetricNoLink); + FRIEND_TEST(DimensionInConditionE2eTest, TestCountMetricWithLink); + FRIEND_TEST(DimensionInConditionE2eTest, TestDurationMetricNoLink); + FRIEND_TEST(DimensionInConditionE2eTest, TestDurationMetricWithLink); }; } // namespace statsd diff --git a/cmds/statsd/src/anomaly/AnomalyTracker.cpp b/cmds/statsd/src/anomaly/AnomalyTracker.cpp index ded6c4c660be5..c84a5b4509b03 100644 --- a/cmds/statsd/src/anomaly/AnomalyTracker.cpp +++ b/cmds/statsd/src/anomaly/AnomalyTracker.cpp @@ -96,7 +96,7 @@ void AnomalyTracker::flushPastBuckets(const int64_t& latestPastBucketNum) { } } -void AnomalyTracker::addPastBucket(const HashableDimensionKey& key, const int64_t& bucketValue, +void AnomalyTracker::addPastBucket(const MetricDimensionKey& key, const int64_t& bucketValue, const int64_t& bucketNum) { flushPastBuckets(bucketNum); @@ -147,7 +147,7 @@ void AnomalyTracker::addBucketToSum(const shared_ptr& bucket) { } } -int64_t AnomalyTracker::getPastBucketValue(const HashableDimensionKey& key, +int64_t AnomalyTracker::getPastBucketValue(const MetricDimensionKey& key, const int64_t& bucketNum) const { const auto& bucket = mPastBuckets[index(bucketNum)]; if (bucket == nullptr) { @@ -157,7 +157,7 @@ int64_t AnomalyTracker::getPastBucketValue(const HashableDimensionKey& key, return itr == bucket->end() ? 0 : itr->second; } -int64_t AnomalyTracker::getSumOverPastBuckets(const HashableDimensionKey& key) const { +int64_t AnomalyTracker::getSumOverPastBuckets(const MetricDimensionKey& key) const { const auto& itr = mSumOverPastBuckets.find(key); if (itr != mSumOverPastBuckets.end()) { return itr->second; @@ -165,7 +165,7 @@ int64_t AnomalyTracker::getSumOverPastBuckets(const HashableDimensionKey& key) c return 0; } -bool AnomalyTracker::detectAnomaly(const int64_t& currentBucketNum, const HashableDimensionKey& key, +bool AnomalyTracker::detectAnomaly(const int64_t& currentBucketNum, const MetricDimensionKey& key, const int64_t& currentBucketValue) { if (currentBucketNum > mMostRecentBucketNum + 1) { // TODO: This creates a needless 0 entry in mSumOverPastBuckets. Fix this. @@ -175,7 +175,7 @@ bool AnomalyTracker::detectAnomaly(const int64_t& currentBucketNum, const Hashab && getSumOverPastBuckets(key) + currentBucketValue > mAlert.trigger_if_sum_gt(); } -void AnomalyTracker::declareAnomaly(const uint64_t& timestampNs, const HashableDimensionKey& key) { +void AnomalyTracker::declareAnomaly(const uint64_t& timestampNs, const MetricDimensionKey& key) { // TODO: Why receive timestamp? RefractoryPeriod should always be based on real time right now. if (isInRefractoryPeriod(timestampNs, key)) { VLOG("Skipping anomaly declaration since within refractory period"); @@ -199,14 +199,14 @@ void AnomalyTracker::declareAnomaly(const uint64_t& timestampNs, const HashableD StatsdStats::getInstance().noteAnomalyDeclared(mConfigKey, mAlert.id()); - // TODO: This should also take in the const HashableDimensionKey& key? + // TODO: This should also take in the const MetricDimensionKey& key? android::util::stats_write(android::util::ANOMALY_DETECTED, mConfigKey.GetUid(), mConfigKey.GetId(), mAlert.id()); } void AnomalyTracker::detectAndDeclareAnomaly(const uint64_t& timestampNs, const int64_t& currBucketNum, - const HashableDimensionKey& key, + const MetricDimensionKey& key, const int64_t& currentBucketValue) { if (detectAnomaly(currBucketNum, key, currentBucketValue)) { declareAnomaly(timestampNs, key); @@ -214,7 +214,7 @@ void AnomalyTracker::detectAndDeclareAnomaly(const uint64_t& timestampNs, } bool AnomalyTracker::isInRefractoryPeriod(const uint64_t& timestampNs, - const HashableDimensionKey& key) { + const MetricDimensionKey& key) { const auto& it = mRefractoryPeriodEndsSec.find(key); if (it != mRefractoryPeriodEndsSec.end()) { if ((timestampNs / NS_PER_SEC) <= it->second) { @@ -226,7 +226,7 @@ bool AnomalyTracker::isInRefractoryPeriod(const uint64_t& timestampNs, return false; } -void AnomalyTracker::informSubscribers(const HashableDimensionKey& key) { +void AnomalyTracker::informSubscribers(const MetricDimensionKey& key) { VLOG("informSubscribers called."); if (mSubscriptions.empty()) { ALOGE("Attempt to call with no subscribers."); diff --git a/cmds/statsd/src/anomaly/AnomalyTracker.h b/cmds/statsd/src/anomaly/AnomalyTracker.h index 472c02c7ee7f2..f01a97f86cf6c 100644 --- a/cmds/statsd/src/anomaly/AnomalyTracker.h +++ b/cmds/statsd/src/anomaly/AnomalyTracker.h @@ -48,19 +48,19 @@ public: // Adds a bucket. // Bucket index starts from 0. void addPastBucket(std::shared_ptr bucketValues, const int64_t& bucketNum); - void addPastBucket(const HashableDimensionKey& key, const int64_t& bucketValue, + void addPastBucket(const MetricDimensionKey& key, const int64_t& bucketValue, const int64_t& bucketNum); // Returns true if detected anomaly for the existing buckets on one or more dimension keys. - bool detectAnomaly(const int64_t& currBucketNum, const HashableDimensionKey& key, + bool detectAnomaly(const int64_t& currBucketNum, const MetricDimensionKey& key, const int64_t& currentBucketValue); // Informs incidentd about the detected alert. - void declareAnomaly(const uint64_t& timestampNs, const HashableDimensionKey& key); + void declareAnomaly(const uint64_t& timestampNs, const MetricDimensionKey& key); // Detects the alert and informs the incidentd when applicable. void detectAndDeclareAnomaly(const uint64_t& timestampNs, const int64_t& currBucketNum, - const HashableDimensionKey& key, + const MetricDimensionKey& key, const int64_t& currentBucketValue); // Init the AnomalyMonitor which is shared across anomaly trackers. @@ -69,10 +69,10 @@ public: } // Helper function to return the sum value of past buckets at given dimension. - int64_t getSumOverPastBuckets(const HashableDimensionKey& key) const; + int64_t getSumOverPastBuckets(const MetricDimensionKey& key) const; // Helper function to return the value for a past bucket. - int64_t getPastBucketValue(const HashableDimensionKey& key, const int64_t& bucketNum) const; + int64_t getPastBucketValue(const MetricDimensionKey& key, const int64_t& bucketNum) const; // Returns the anomaly threshold. inline int64_t getAnomalyThreshold() const { @@ -81,7 +81,7 @@ public: // Returns the refractory period timestamp (in seconds) for the given key. // If there is no stored refractory period ending timestamp, returns 0. - uint32_t getRefractoryPeriodEndsSec(const HashableDimensionKey& key) const { + uint32_t getRefractoryPeriodEndsSec(const MetricDimensionKey& key) const { const auto& it = mRefractoryPeriodEndsSec.find(key); return it != mRefractoryPeriodEndsSec.end() ? it->second : 0; } @@ -124,7 +124,7 @@ protected: // declared for that dimension) ends, in seconds. Only anomalies that occur after this period // ends will be declared. // Entries may be, but are not guaranteed to be, removed after the period is finished. - unordered_map mRefractoryPeriodEndsSec; + unordered_map mRefractoryPeriodEndsSec; void flushPastBuckets(const int64_t& currBucketNum); @@ -135,7 +135,7 @@ protected: // and remove any items with value 0. void subtractBucketFromSum(const shared_ptr& bucket); - bool isInRefractoryPeriod(const uint64_t& timestampNs, const HashableDimensionKey& key); + bool isInRefractoryPeriod(const uint64_t& timestampNs, const MetricDimensionKey& key); // Calculates the corresponding bucket index within the circular array. size_t index(int64_t bucketNum) const; @@ -144,7 +144,7 @@ protected: virtual void resetStorage(); // Informs the subscribers that an anomaly has occurred. - void informSubscribers(const HashableDimensionKey& key); + void informSubscribers(const MetricDimensionKey& key); FRIEND_TEST(AnomalyTrackerTest, TestConsecutiveBuckets); FRIEND_TEST(AnomalyTrackerTest, TestSparseBuckets); diff --git a/cmds/statsd/src/anomaly/DurationAnomalyTracker.cpp b/cmds/statsd/src/anomaly/DurationAnomalyTracker.cpp index 7576a38db51d7..bbee9fa5358c2 100644 --- a/cmds/statsd/src/anomaly/DurationAnomalyTracker.cpp +++ b/cmds/statsd/src/anomaly/DurationAnomalyTracker.cpp @@ -37,8 +37,8 @@ void DurationAnomalyTracker::resetStorage() { if (!mAlarms.empty()) VLOG("AnomalyTracker.resetStorage() called but mAlarms is NOT empty!"); } -void DurationAnomalyTracker::declareAnomalyIfAlarmExpired(const HashableDimensionKey& dimensionKey, - const uint64_t& timestampNs) { +void DurationAnomalyTracker::declareAnomalyIfAlarmExpired(const MetricDimensionKey& dimensionKey, + const uint64_t& timestampNs) { auto itr = mAlarms.find(dimensionKey); if (itr == mAlarms.end()) { return; @@ -51,7 +51,7 @@ void DurationAnomalyTracker::declareAnomalyIfAlarmExpired(const HashableDimensio } } -void DurationAnomalyTracker::startAlarm(const HashableDimensionKey& dimensionKey, +void DurationAnomalyTracker::startAlarm(const MetricDimensionKey& dimensionKey, const uint64_t& timestampNs) { uint32_t timestampSec = static_cast(timestampNs / NS_PER_SEC); @@ -66,7 +66,7 @@ void DurationAnomalyTracker::startAlarm(const HashableDimensionKey& dimensionKey } } -void DurationAnomalyTracker::stopAlarm(const HashableDimensionKey& dimensionKey) { +void DurationAnomalyTracker::stopAlarm(const MetricDimensionKey& dimensionKey) { auto itr = mAlarms.find(dimensionKey); if (itr != mAlarms.end()) { mAlarms.erase(dimensionKey); @@ -77,7 +77,7 @@ void DurationAnomalyTracker::stopAlarm(const HashableDimensionKey& dimensionKey) } void DurationAnomalyTracker::stopAllAlarms() { - std::set keys; + std::set keys; for (auto itr = mAlarms.begin(); itr != mAlarms.end(); ++itr) { keys.insert(itr->first); } @@ -95,7 +95,7 @@ void DurationAnomalyTracker::informAlarmsFired(const uint64_t& timestampNs, // seldomly called. The alternative would be having AnomalyAlarms store information about the // DurationAnomalyTracker and key, but that's a lot of data overhead to speed up something that is // rarely ever called. - unordered_map> matchedAlarms; + unordered_map> matchedAlarms; for (const auto& kv : mAlarms) { if (firedAlarms.count(kv.second) > 0) { matchedAlarms.insert({kv.first, kv.second}); diff --git a/cmds/statsd/src/anomaly/DurationAnomalyTracker.h b/cmds/statsd/src/anomaly/DurationAnomalyTracker.h index 33e55ab850c90..052fdf5762892 100644 --- a/cmds/statsd/src/anomaly/DurationAnomalyTracker.h +++ b/cmds/statsd/src/anomaly/DurationAnomalyTracker.h @@ -32,10 +32,10 @@ public: virtual ~DurationAnomalyTracker(); // Starts the alarm at the given timestamp. - void startAlarm(const HashableDimensionKey& dimensionKey, const uint64_t& eventTime); + void startAlarm(const MetricDimensionKey& dimensionKey, const uint64_t& eventTime); // Stops the alarm. - void stopAlarm(const HashableDimensionKey& dimensionKey); + void stopAlarm(const MetricDimensionKey& dimensionKey); // Stop all the alarms owned by this tracker. void stopAllAlarms(); @@ -46,7 +46,7 @@ public: } // Declares the anomaly when the alarm expired given the current timestamp. - void declareAnomalyIfAlarmExpired(const HashableDimensionKey& dimensionKey, + void declareAnomalyIfAlarmExpired(const MetricDimensionKey& dimensionKey, const uint64_t& timestampNs); // Declares an anomaly for each alarm in firedAlarms that belongs to this DurationAnomalyTracker @@ -59,7 +59,7 @@ public: protected: // The alarms owned by this tracker. The alarm monitor also shares the alarm pointers when they // are still active. - std::unordered_map> mAlarms; + std::unordered_map> mAlarms; // Anomaly alarm monitor. sp mAnomalyMonitor; diff --git a/cmds/statsd/src/condition/CombinationConditionTracker.cpp b/cmds/statsd/src/condition/CombinationConditionTracker.cpp index ea6586c25d807..4c20ccb61afe5 100644 --- a/cmds/statsd/src/condition/CombinationConditionTracker.cpp +++ b/cmds/statsd/src/condition/CombinationConditionTracker.cpp @@ -78,6 +78,7 @@ bool CombinationConditionTracker::init(const vector& allConditionConf return false; } + bool initChildSucceeded = childTracker->init(allConditionConfig, allConditionTrackers, conditionIdIndexMap, stack); @@ -88,8 +89,10 @@ bool CombinationConditionTracker::init(const vector& allConditionConf ALOGW("Child initialization success %lld ", (long long)child); } + if (allConditionTrackers[childIndex]->isSliced()) { + setSliced(true); + } mChildren.push_back(childIndex); - mTrackerIndex.insert(childTracker->getLogTrackerIndex().begin(), childTracker->getLogTrackerIndex().end()); } @@ -105,11 +108,15 @@ bool CombinationConditionTracker::init(const vector& allConditionConf void CombinationConditionTracker::isConditionMet( const ConditionKey& conditionParameters, const vector>& allConditions, - vector& conditionCache) const { + const FieldMatcher& dimensionFields, + vector& conditionCache, + std::unordered_set &dimensionsKeySet) const { + // So far, this is fine as there is at most one child having sliced output. for (const int childIndex : mChildren) { if (conditionCache[childIndex] == ConditionState::kNotEvaluated) { allConditions[childIndex]->isConditionMet(conditionParameters, allConditions, - conditionCache); + dimensionFields, conditionCache, + dimensionsKeySet); } } conditionCache[mIndex] = @@ -127,6 +134,7 @@ void CombinationConditionTracker::evaluateCondition( } for (const int childIndex : mChildren) { + // So far, this is fine as there is at most one child having sliced output. if (nonSlicedConditionCache[childIndex] == ConditionState::kNotEvaluated) { const sp& child = mAllConditions[childIndex]; child->evaluateCondition(event, eventMatcherValues, mAllConditions, @@ -159,6 +167,24 @@ void CombinationConditionTracker::evaluateCondition( } } +ConditionState CombinationConditionTracker::getMetConditionDimension( + const std::vector>& allConditions, + const FieldMatcher& dimensionFields, + std::unordered_set &dimensionsKeySet) const { + vector conditionCache(allConditions.size(), ConditionState::kNotEvaluated); + // So far, this is fine as there is at most one child having sliced output. + for (const int childIndex : mChildren) { + conditionCache[childIndex] = conditionCache[childIndex] | + allConditions[childIndex]->getMetConditionDimension( + allConditions, dimensionFields, dimensionsKeySet); + } + evaluateCombinationCondition(mChildren, mLogicalOperation, conditionCache); + if (conditionCache[mIndex] == ConditionState::kTrue && dimensionsKeySet.empty()) { + dimensionsKeySet.insert(DEFAULT_DIMENSION_KEY); + } + return conditionCache[mIndex]; +} + } // namespace statsd } // namespace os } // namespace android diff --git a/cmds/statsd/src/condition/CombinationConditionTracker.h b/cmds/statsd/src/condition/CombinationConditionTracker.h index dfd3837f31f49..0b7f9492d628d 100644 --- a/cmds/statsd/src/condition/CombinationConditionTracker.h +++ b/cmds/statsd/src/condition/CombinationConditionTracker.h @@ -41,12 +41,20 @@ public: std::vector& conditionCache, std::vector& changedCache) override; - void isConditionMet(const ConditionKey& conditionParameters, - const std::vector>& allConditions, - std::vector& conditionCache) const override; + void isConditionMet( + const ConditionKey& conditionParameters, + const std::vector>& allConditions, + const FieldMatcher& dimensionFields, + std::vector& conditionCache, + std::unordered_set &dimensionsKeySet) const override; + ConditionState getMetConditionDimension( + const std::vector>& allConditions, + const FieldMatcher& dimensionFields, + std::unordered_set &dimensionsKeySet) const override; private: LogicalOperation mLogicalOperation; + // Store index of the children Predicates. // We don't store string name of the Children, because we want to get rid of the hash map to // map the name to object. We don't want to store smart pointers to children, because it diff --git a/cmds/statsd/src/condition/ConditionTracker.h b/cmds/statsd/src/condition/ConditionTracker.h index 773860f429b10..81abbdb36ee4a 100644 --- a/cmds/statsd/src/condition/ConditionTracker.h +++ b/cmds/statsd/src/condition/ConditionTracker.h @@ -24,6 +24,7 @@ #include #include +#include #include namespace android { @@ -84,10 +85,19 @@ public: // [allConditions]: all condition trackers. This is needed because the condition evaluation is // done recursively // [conditionCache]: the cache holding the condition evaluation values. + // [dimensionsKeySet]: the dimensions where the sliced condition is true. For combination + // condition, it assumes that only one child predicate is sliced. virtual void isConditionMet( const ConditionKey& conditionParameters, const std::vector>& allConditions, - std::vector& conditionCache) const = 0; + const FieldMatcher& dimensionFields, + std::vector& conditionCache, + std::unordered_set &dimensionsKeySet) const = 0; + + virtual ConditionState getMetConditionDimension( + const std::vector>& allConditions, + const FieldMatcher& dimensionFields, + std::unordered_set &dimensionsKeySet) const = 0; // return the list of LogMatchingTracker index that this ConditionTracker uses. virtual const std::set& getLogTrackerIndex() const { @@ -98,6 +108,10 @@ public: mSliced = mSliced | sliced; } + bool isSliced() const { + return mSliced; + } + protected: const int64_t mConditionId; diff --git a/cmds/statsd/src/condition/ConditionWizard.cpp b/cmds/statsd/src/condition/ConditionWizard.cpp index d99c2ccd1fdae..0427700fec91f 100644 --- a/cmds/statsd/src/condition/ConditionWizard.cpp +++ b/cmds/statsd/src/condition/ConditionWizard.cpp @@ -14,6 +14,7 @@ * limitations under the License. */ #include "ConditionWizard.h" +#include namespace android { namespace os { @@ -23,14 +24,26 @@ using std::map; using std::string; using std::vector; -ConditionState ConditionWizard::query(const int index, - const ConditionKey& parameters) { +ConditionState ConditionWizard::query( + const int index, const ConditionKey& parameters, + const FieldMatcher& dimensionFields, + std::unordered_set *dimensionKeySet) { + vector cache(mAllConditions.size(), ConditionState::kNotEvaluated); - mAllConditions[index]->isConditionMet(parameters, mAllConditions, cache); + mAllConditions[index]->isConditionMet( + parameters, mAllConditions, dimensionFields, cache, *dimensionKeySet); return cache[index]; } +ConditionState ConditionWizard::getMetConditionDimension( + const int index, const FieldMatcher& dimensionFields, + std::unordered_set *dimensionsKeySet) const { + + return mAllConditions[index]->getMetConditionDimension(mAllConditions, dimensionFields, + *dimensionsKeySet); +} + } // namespace statsd } // namespace os } // namespace android \ No newline at end of file diff --git a/cmds/statsd/src/condition/ConditionWizard.h b/cmds/statsd/src/condition/ConditionWizard.h index 4ff5c07e210f1..b38b59ff4cd0b 100644 --- a/cmds/statsd/src/condition/ConditionWizard.h +++ b/cmds/statsd/src/condition/ConditionWizard.h @@ -41,7 +41,14 @@ public: // the conditionParameters contains the parameters for it's children SimpleConditionTrackers. virtual ConditionState query( const int conditionIndex, - const ConditionKey& conditionParameters); + const ConditionKey& conditionParameters, + const FieldMatcher& dimensionFields, + std::unordered_set *dimensionKeySet); + + virtual ConditionState getMetConditionDimension( + const int index, + const FieldMatcher& dimensionFields, + std::unordered_set *dimensionsKeySet) const; private: std::vector> mAllConditions; diff --git a/cmds/statsd/src/condition/SimpleConditionTracker.cpp b/cmds/statsd/src/condition/SimpleConditionTracker.cpp index 5cfc349ea46a0..25265d5dabd74 100644 --- a/cmds/statsd/src/condition/SimpleConditionTracker.cpp +++ b/cmds/statsd/src/condition/SimpleConditionTracker.cpp @@ -104,6 +104,9 @@ bool SimpleConditionTracker::init(const vector& allConditionConfig, vector& stack) { // SimpleConditionTracker does not have dependency on other conditions, thus we just return // if the initialization was successful. + if (mOutputDimensions.has_field() || mOutputDimensions.child_size() > 0) { + setSliced(true); + } return mInitialized; } @@ -234,11 +237,12 @@ void SimpleConditionTracker::handleConditionEvent(const HashableDimensionKey& ou conditionChangedCache[mIndex] == true); } -void SimpleConditionTracker::evaluateCondition(const LogEvent& event, - const vector& eventMatcherValues, - const vector>& mAllConditions, - vector& conditionCache, - vector& conditionChangedCache) { +void SimpleConditionTracker::evaluateCondition( + const LogEvent& event, + const vector& eventMatcherValues, + const vector>& mAllConditions, + vector& conditionCache, + vector& conditionChangedCache) { if (conditionCache[mIndex] != ConditionState::kNotEvaluated) { // it has been evaluated. VLOG("Yes, already evaluated, %lld %d", @@ -271,7 +275,7 @@ void SimpleConditionTracker::evaluateCondition(const LogEvent& event, if (mSliced) { // if the condition result is sliced. metrics won't directly get value from the // cache, so just set any value other than kNotEvaluated. - conditionCache[mIndex] = ConditionState::kUnknown; + conditionCache[mIndex] = mInitialValue; } else { const auto& itr = mSlicedConditionState.find(DEFAULT_DIMENSION_KEY); if (itr == mSlicedConditionState.end()) { @@ -310,10 +314,8 @@ void SimpleConditionTracker::evaluateCondition(const LogEvent& event, vector dimensionalConditionCache(conditionCache.size(), ConditionState::kNotEvaluated); vector dimensionalConditionChangedCache(conditionChangedCache.size(), false); - handleConditionEvent(HashableDimensionKey(outputValue), matchedState == 1, dimensionalConditionCache, dimensionalConditionChangedCache); - OrConditionState(dimensionalConditionCache, &conditionCache); OrBooleanVector(dimensionalConditionChangedCache, &conditionChangedCache); } @@ -323,42 +325,73 @@ void SimpleConditionTracker::evaluateCondition(const LogEvent& event, void SimpleConditionTracker::isConditionMet( const ConditionKey& conditionParameters, const vector>& allConditions, - vector& conditionCache) const { - const auto pair = conditionParameters.find(mConditionId); - - if (pair == conditionParameters.end() && mOutputDimensions.child_size() > 0) { - ALOGE("Predicate %lld output has dimension, but it's not specified in the query!", - (long long)mConditionId); - conditionCache[mIndex] = mInitialValue; + const FieldMatcher& dimensionFields, + vector& conditionCache, + std::unordered_set &dimensionsKeySet) const { + if (conditionCache[mIndex] != ConditionState::kNotEvaluated) { + // it has been evaluated. + VLOG("Yes, already evaluated, %lld %d", + (long long)mConditionId, conditionCache[mIndex]); return; } - std::vector defaultKeys = {DEFAULT_DIMENSION_KEY}; + const auto pair = conditionParameters.find(mConditionId); + + if (pair == conditionParameters.end()) { + ConditionState conditionState = ConditionState::kNotEvaluated; + if (dimensionFields.has_field() && dimensionFields.child_size() > 0 && + dimensionFields.field() == mOutputDimensions.field()) { + conditionState = conditionState | getMetConditionDimension( + allConditions, dimensionFields, dimensionsKeySet); + } else { + conditionState = conditionState | mInitialValue; + if (!mSliced) { + const auto& itr = mSlicedConditionState.find(DEFAULT_DIMENSION_KEY); + if (itr != mSlicedConditionState.end()) { + ConditionState sliceState = + itr->second > 0 ? ConditionState::kTrue : ConditionState::kFalse; + conditionState = conditionState | sliceState; + } + } + } + conditionCache[mIndex] = conditionState; + return; + } + std::vector defaultKeys = { DEFAULT_DIMENSION_KEY }; const std::vector &keys = (pair == conditionParameters.end()) ? defaultKeys : pair->second; ConditionState conditionState = ConditionState::kNotEvaluated; - for (const auto& key : keys) { + for (size_t i = 0; i < keys.size(); ++i) { + const HashableDimensionKey& key = keys[i]; auto startedCountIt = mSlicedConditionState.find(key); if (startedCountIt != mSlicedConditionState.end()) { - conditionState = conditionState | - (startedCountIt->second > 0 ? ConditionState::kTrue : ConditionState::kFalse); + ConditionState sliceState = + startedCountIt->second > 0 ? ConditionState::kTrue : ConditionState::kFalse; + conditionState = conditionState | sliceState; + if (sliceState == ConditionState::kTrue && dimensionFields.has_field()) { + HashableDimensionKey dimensionKey; + if (getSubDimension(startedCountIt->first.getDimensionsValue(), dimensionFields, + dimensionKey.getMutableDimensionsValue())) { + dimensionsKeySet.insert(dimensionKey); + } + } } else { // For unseen key, check whether the require dimensions are subset of sliced condition // output. - bool seenDimension = false; + conditionState = conditionState | mInitialValue; for (const auto& slice : mSlicedConditionState) { - if (IsSubDimension(slice.first.getDimensionsValue(), - key.getDimensionsValue())) { - seenDimension = true; - conditionState = conditionState | - (slice.second > 0 ? ConditionState::kTrue : ConditionState::kFalse); + ConditionState sliceState = + slice.second > 0 ? ConditionState::kTrue : ConditionState::kFalse; + if (IsSubDimension(slice.first.getDimensionsValue(), key.getDimensionsValue())) { + conditionState = conditionState | sliceState; + if (sliceState == ConditionState::kTrue && dimensionFields.has_field()) { + HashableDimensionKey dimensionKey; + if (getSubDimension(slice.first.getDimensionsValue(), + dimensionFields, dimensionKey.getMutableDimensionsValue())) { + dimensionsKeySet.insert(dimensionKey); + } + } } - if (conditionState == ConditionState::kTrue) { - break; - } - } - if (!seenDimension) { - conditionState = conditionState | mInitialValue; } } } @@ -366,6 +399,38 @@ void SimpleConditionTracker::isConditionMet( VLOG("Predicate %lld return %d", (long long)mConditionId, conditionCache[mIndex]); } +ConditionState SimpleConditionTracker::getMetConditionDimension( + const std::vector>& allConditions, + const FieldMatcher& dimensionFields, + std::unordered_set &dimensionsKeySet) const { + ConditionState conditionState = mInitialValue; + if (!dimensionFields.has_field() || + !mOutputDimensions.has_field() || + dimensionFields.field() != mOutputDimensions.field()) { + const auto& itr = mSlicedConditionState.find(DEFAULT_DIMENSION_KEY); + if (itr != mSlicedConditionState.end()) { + ConditionState sliceState = + itr->second > 0 ? ConditionState::kTrue : ConditionState::kFalse; + conditionState = conditionState | sliceState; + } + return conditionState; + } + + for (const auto& slice : mSlicedConditionState) { + ConditionState sliceState = + slice.second > 0 ? ConditionState::kTrue : ConditionState::kFalse; + DimensionsValue dimensionsValue; + conditionState = conditionState | sliceState; + HashableDimensionKey dimensionKey; + if (sliceState == ConditionState::kTrue && + getSubDimension(slice.first.getDimensionsValue(), dimensionFields, + dimensionKey.getMutableDimensionsValue())) { + dimensionsKeySet.insert(dimensionKey); + } + } + return conditionState; +} + } // namespace statsd } // namespace os } // namespace android diff --git a/cmds/statsd/src/condition/SimpleConditionTracker.h b/cmds/statsd/src/condition/SimpleConditionTracker.h index 815b445a8c5bf..ce9a02d4a7953 100644 --- a/cmds/statsd/src/condition/SimpleConditionTracker.h +++ b/cmds/statsd/src/condition/SimpleConditionTracker.h @@ -48,7 +48,14 @@ public: void isConditionMet(const ConditionKey& conditionParameters, const std::vector>& allConditions, - std::vector& conditionCache) const override; + const FieldMatcher& dimensionFields, + std::vector& conditionCache, + std::unordered_set &dimensionsKeySet) const override; + + ConditionState getMetConditionDimension( + const std::vector>& allConditions, + const FieldMatcher& dimensionFields, + std::unordered_set &dimensionsKeySet) const override; private: const ConfigKey mConfigKey; @@ -73,7 +80,8 @@ private: void handleStopAll(std::vector& conditionCache, std::vector& changedCache); - void handleConditionEvent(const HashableDimensionKey& outputKey, bool matchStart, + void handleConditionEvent(const HashableDimensionKey& outputKey, + bool matchStart, std::vector& conditionCache, std::vector& changedCache); diff --git a/cmds/statsd/src/condition/condition_util.cpp b/cmds/statsd/src/condition/condition_util.cpp index 3b2d480b3ebf0..0ab33cfbaea11 100644 --- a/cmds/statsd/src/condition/condition_util.cpp +++ b/cmds/statsd/src/condition/condition_util.cpp @@ -118,6 +118,9 @@ void OrBooleanVector(const std::vector& ref, vector * ored) { void getFieldsFromFieldMatcher(const FieldMatcher& matcher, Field* rootField, Field* leafField, std::vector *allFields) { + if (matcher.has_position()) { + leafField->set_position_index(0); + } if (matcher.child_size() == 0) { allFields->push_back(*rootField); return; diff --git a/cmds/statsd/src/dimension.cpp b/cmds/statsd/src/dimension.cpp index 04445ca0e230b..8a2e87128319c 100644 --- a/cmds/statsd/src/dimension.cpp +++ b/cmds/statsd/src/dimension.cpp @@ -253,6 +253,9 @@ void buildAttributionFieldMatcher(const int tagId, const Position position, Fiel } void DimensionsValueToString(const DimensionsValue& value, std::string *flattened) { + if (!value.has_field()) { + return; + } *flattened += std::to_string(value.field()); *flattened += ":"; switch (value.value_case()) { @@ -352,6 +355,46 @@ long getLongFromDimenValue(const DimensionsValue& dimensionValue) { } } +bool getSubDimension(const DimensionsValue& dimension, const FieldMatcher& matcher, + DimensionsValue* subDimension) { + if (!matcher.has_field()) { + return false; + } + if (matcher.field() != dimension.field()) { + return false; + } + if (matcher.child_size() <= 0) { + if (dimension.value_case() == DimensionsValue::ValueCase::kValueTuple || + dimension.value_case() == DimensionsValue::ValueCase::VALUE_NOT_SET) { + return false; + } + *subDimension = dimension; + return true; + } else { + if (dimension.value_case() != DimensionsValue::ValueCase::kValueTuple) { + return false; + } + bool found_value = true; + auto value_tuple = dimension.value_tuple(); + subDimension->set_field(dimension.field()); + for (int i = 0; found_value && i < matcher.child_size(); ++i) { + int j = 0; + for (; j < value_tuple.dimensions_value_size(); ++j) { + if (value_tuple.dimensions_value(j).field() == matcher.child(i).field()) { + break; + } + } + if (j < value_tuple.dimensions_value_size()) { + found_value &= getSubDimension(value_tuple.dimensions_value(j), matcher.child(i), + subDimension->mutable_value_tuple()->add_dimensions_value()); + } else { + found_value = false; + } + } + return found_value; + } +} + } // namespace statsd } // namespace os } // namespace android diff --git a/cmds/statsd/src/dimension.h b/cmds/statsd/src/dimension.h index e900c5e872279..138c6e9b0160c 100644 --- a/cmds/statsd/src/dimension.h +++ b/cmds/statsd/src/dimension.h @@ -63,6 +63,9 @@ bool IsSubDimension(const DimensionsValue& dimension, const DimensionsValue& sub // Helper function to get long value from the DimensionsValue proto. long getLongFromDimenValue(const DimensionsValue& dimensionValue); + +bool getSubDimension(const DimensionsValue& dimension, const FieldMatcher& matcher, + DimensionsValue* subDimension); } // namespace statsd } // namespace os } // namespace android diff --git a/cmds/statsd/src/metrics/CountMetricProducer.cpp b/cmds/statsd/src/metrics/CountMetricProducer.cpp index 0455f6a210a9b..ae4df3ee92f07 100644 --- a/cmds/statsd/src/metrics/CountMetricProducer.cpp +++ b/cmds/statsd/src/metrics/CountMetricProducer.cpp @@ -21,6 +21,7 @@ #include "guardrail/StatsdStats.h" #include "stats_util.h" #include "stats_log_util.h" +#include "dimension.h" #include #include @@ -71,13 +72,15 @@ CountMetricProducer::CountMetricProducer(const ConfigKey& key, const CountMetric } // TODO: use UidMap if uid->pkg_name is required - mDimensions = metric.dimensions_in_what(); + mDimensionsInWhat = metric.dimensions_in_what(); + mDimensionsInCondition = metric.dimensions_in_condition(); if (metric.links().size() > 0) { mConditionLinks.insert(mConditionLinks.begin(), metric.links().begin(), metric.links().end()); - mConditionSliced = true; } + mConditionSliced = (metric.links().size() > 0)|| + (mDimensionsInCondition.has_field() && mDimensionsInCondition.child_size() > 0); VLOG("metric %lld created. bucket size %lld start_time: %lld", (long long)metric.id(), (long long)mBucketSizeNs, (long long)mStartTimeNs); @@ -99,7 +102,10 @@ void CountMetricProducer::onDumpReportLocked(const uint64_t dumpTimeNs, StatsLog auto count_metrics = report->mutable_count_metrics(); for (const auto& counter : mPastBuckets) { CountMetricData* metricData = count_metrics->add_data(); - *metricData->mutable_dimensions_in_what() = counter.first.getDimensionsValue(); + *metricData->mutable_dimensions_in_what() = + counter.first.getDimensionKeyInWhat().getDimensionsValue(); + *metricData->mutable_dimensions_in_condition() = + counter.first.getDimensionKeyInCondition().getDimensionsValue(); for (const auto& bucket : counter.second) { CountBucketInfo* bucketInfo = metricData->add_bucket_info(); bucketInfo->set_start_bucket_nanos(bucket.mBucketStartNs); @@ -123,17 +129,26 @@ void CountMetricProducer::onDumpReportLocked(const uint64_t dumpTimeNs, VLOG("metric %lld dump report now...",(long long)mMetricId); for (const auto& counter : mPastBuckets) { - const HashableDimensionKey& hashableKey = counter.first; - VLOG(" dimension key %s", hashableKey.c_str()); + const MetricDimensionKey& dimensionKey = counter.first; + VLOG(" dimension key %s", dimensionKey.c_str()); long long wrapperToken = protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_DATA); // First fill dimension. - long long dimensionToken = protoOutput->start( + long long dimensionInWhatToken = protoOutput->start( FIELD_TYPE_MESSAGE | FIELD_ID_DIMENSION_IN_WHAT); - writeDimensionsValueProtoToStream(hashableKey.getDimensionsValue(), protoOutput); - protoOutput->end(dimensionToken); + writeDimensionsValueProtoToStream( + dimensionKey.getDimensionKeyInWhat().getDimensionsValue(), protoOutput); + protoOutput->end(dimensionInWhatToken); + + if (dimensionKey.hasDimensionKeyInCondition()) { + long long dimensionInConditionToken = protoOutput->start( + FIELD_TYPE_MESSAGE | FIELD_ID_DIMENSION_IN_CONDITION); + writeDimensionsValueProtoToStream( + dimensionKey.getDimensionKeyInCondition().getDimensionsValue(), protoOutput); + protoOutput->end(dimensionInConditionToken); + } // Then fill bucket_info (CountBucketInfo). for (const auto& bucket : counter.second) { @@ -166,7 +181,7 @@ void CountMetricProducer::onConditionChangedLocked(const bool conditionMet, mCondition = conditionMet; } -bool CountMetricProducer::hitGuardRailLocked(const HashableDimensionKey& newKey) { +bool CountMetricProducer::hitGuardRailLocked(const MetricDimensionKey& newKey) { if (mCurrentSlicedCounter->find(newKey) != mCurrentSlicedCounter->end()) { return false; } @@ -187,7 +202,7 @@ bool CountMetricProducer::hitGuardRailLocked(const HashableDimensionKey& newKey) } void CountMetricProducer::onMatchedLogEventInternalLocked( - const size_t matcherIndex, const HashableDimensionKey& eventKey, + const size_t matcherIndex, const MetricDimensionKey& eventKey, const ConditionKey& conditionKey, bool condition, const LogEvent& event) { uint64_t eventTimeNs = event.GetTimestampNs(); diff --git a/cmds/statsd/src/metrics/CountMetricProducer.h b/cmds/statsd/src/metrics/CountMetricProducer.h index 061b7a36817c2..8659d47735681 100644 --- a/cmds/statsd/src/metrics/CountMetricProducer.h +++ b/cmds/statsd/src/metrics/CountMetricProducer.h @@ -50,7 +50,7 @@ public: protected: void onMatchedLogEventInternalLocked( - const size_t matcherIndex, const HashableDimensionKey& eventKey, + const size_t matcherIndex, const MetricDimensionKey& eventKey, const ConditionKey& conditionKey, bool condition, const LogEvent& event) override; @@ -74,14 +74,14 @@ private: void flushIfNeededLocked(const uint64_t& newEventTime); // TODO: Add a lock to mPastBuckets. - std::unordered_map> mPastBuckets; + std::unordered_map> mPastBuckets; // The current bucket. std::shared_ptr mCurrentSlicedCounter = std::make_shared(); static const size_t kBucketSize = sizeof(CountBucket{}); - bool hitGuardRailLocked(const HashableDimensionKey& newKey); + bool hitGuardRailLocked(const MetricDimensionKey& newKey); FRIEND_TEST(CountMetricProducerTest, TestNonDimensionalEvents); FRIEND_TEST(CountMetricProducerTest, TestEventsWithNonSlicedCondition); diff --git a/cmds/statsd/src/metrics/DurationMetricProducer.cpp b/cmds/statsd/src/metrics/DurationMetricProducer.cpp index 000874cf8f442..efbdae1507921 100644 --- a/cmds/statsd/src/metrics/DurationMetricProducer.cpp +++ b/cmds/statsd/src/metrics/DurationMetricProducer.cpp @@ -21,6 +21,7 @@ #include "guardrail/StatsdStats.h" #include "stats_util.h" #include "stats_log_util.h" +#include "dimension.h" #include #include @@ -81,13 +82,15 @@ DurationMetricProducer::DurationMetricProducer(const ConfigKey& key, const Durat } // TODO: use UidMap if uid->pkg_name is required - mDimensions = metric.dimensions_in_what(); + mDimensionsInWhat = metric.dimensions_in_what(); + mDimensionsInCondition = metric.dimensions_in_condition(); if (metric.links().size() > 0) { mConditionLinks.insert(mConditionLinks.begin(), metric.links().begin(), metric.links().end()); - mConditionSliced = true; } + mConditionSliced = (metric.links().size() > 0)|| + (mDimensionsInCondition.has_field() && mDimensionsInCondition.child_size() > 0); VLOG("metric %lld created. bucket size %lld start_time: %lld", (long long)metric.id(), (long long)mBucketSizeNs, (long long)mStartTimeNs); @@ -113,15 +116,17 @@ sp DurationMetricProducer::addAnomalyTracker(const Alert &alert) } unique_ptr DurationMetricProducer::createDurationTracker( - const HashableDimensionKey& eventKey) const { + const MetricDimensionKey& eventKey) const { switch (mAggregationType) { case DurationMetric_AggregationType_SUM: return make_unique( - mConfigKey, mMetricId, eventKey, mWizard, mConditionTrackerIndex, mNested, + mConfigKey, mMetricId, eventKey, mWizard, mConditionTrackerIndex, + mDimensionsInCondition, mNested, mCurrentBucketStartTimeNs, mBucketSizeNs, mConditionSliced, mAnomalyTrackers); case DurationMetric_AggregationType_MAX_SPARSE: return make_unique( - mConfigKey, mMetricId, eventKey, mWizard, mConditionTrackerIndex, mNested, + mConfigKey, mMetricId, eventKey, mWizard, mConditionTrackerIndex, + mDimensionsInCondition, mNested, mCurrentBucketStartTimeNs, mBucketSizeNs, mConditionSliced, mAnomalyTrackers); } } @@ -129,10 +134,34 @@ unique_ptr DurationMetricProducer::createDurationTracker( void DurationMetricProducer::onSlicedConditionMayChangeLocked(const uint64_t eventTime) { VLOG("Metric %lld onSlicedConditionMayChange", (long long)mMetricId); flushIfNeededLocked(eventTime); + // Now for each of the on-going event, check if the condition has changed for them. - for (auto& pair : mCurrentSlicedDuration) { + for (auto& pair : mCurrentSlicedDurationTrackerMap) { pair.second->onSlicedConditionMayChange(eventTime); } + + + std::unordered_set conditionDimensionsKeySet; + ConditionState conditionState = mWizard->getMetConditionDimension( + mConditionTrackerIndex, mDimensionsInCondition, &conditionDimensionsKeySet); + + bool condition = (conditionState == ConditionState::kTrue); + for (auto& pair : mCurrentSlicedDurationTrackerMap) { + conditionDimensionsKeySet.erase(pair.first.getDimensionKeyInCondition()); + } + std::unordered_set newKeys; + for (const auto& conditionDimensionsKey : conditionDimensionsKeySet) { + for (auto& pair : mCurrentSlicedDurationTrackerMap) { + auto newKey = + MetricDimensionKey(pair.first.getDimensionKeyInWhat(), conditionDimensionsKey); + if (newKeys.find(newKey) == newKeys.end()) { + mCurrentSlicedDurationTrackerMap[newKey] = pair.second->clone(eventTime); + mCurrentSlicedDurationTrackerMap[newKey]->setEventKey(newKey); + mCurrentSlicedDurationTrackerMap[newKey]->onSlicedConditionMayChange(eventTime); + } + newKeys.insert(newKey); + } + } } void DurationMetricProducer::onConditionChangedLocked(const bool conditionMet, @@ -142,7 +171,7 @@ void DurationMetricProducer::onConditionChangedLocked(const bool conditionMet, flushIfNeededLocked(eventTime); // TODO: need to populate the condition change time from the event which triggers the condition // change, instead of using current time. - for (auto& pair : mCurrentSlicedDuration) { + for (auto& pair : mCurrentSlicedDurationTrackerMap) { pair.second->onConditionChanged(conditionMet, eventTime); } } @@ -155,7 +184,10 @@ void DurationMetricProducer::onDumpReportLocked(const uint64_t dumpTimeNs, Stats auto duration_metrics = report->mutable_duration_metrics(); for (const auto& pair : mPastBuckets) { DurationMetricData* metricData = duration_metrics->add_data(); - *metricData->mutable_dimensions_in_what() = pair.first.getDimensionsValue(); + *metricData->mutable_dimensions_in_what() = + pair.first.getDimensionKeyInWhat().getDimensionsValue(); + *metricData->mutable_dimensions_in_condition() = + pair.first.getDimensionKeyInCondition().getDimensionsValue(); for (const auto& bucket : pair.second) { auto bucketInfo = metricData->add_bucket_info(); bucketInfo->set_start_bucket_nanos(bucket.mBucketStartNs); @@ -179,8 +211,8 @@ void DurationMetricProducer::onDumpReportLocked(const uint64_t dumpTimeNs, VLOG("metric %lld dump report now...", (long long)mMetricId); for (const auto& pair : mPastBuckets) { - const HashableDimensionKey& hashableKey = pair.first; - VLOG(" dimension key %s", hashableKey.c_str()); + const MetricDimensionKey& dimensionKey = pair.first; + VLOG(" dimension key %s", dimensionKey.c_str()); long long wrapperToken = protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_DATA); @@ -188,9 +220,18 @@ void DurationMetricProducer::onDumpReportLocked(const uint64_t dumpTimeNs, // First fill dimension. long long dimensionToken = protoOutput->start( FIELD_TYPE_MESSAGE | FIELD_ID_DIMENSION_IN_WHAT); - writeDimensionsValueProtoToStream(hashableKey.getDimensionsValue(), protoOutput); + writeDimensionsValueProtoToStream( + dimensionKey.getDimensionKeyInWhat().getDimensionsValue(), protoOutput); protoOutput->end(dimensionToken); + if (dimensionKey.hasDimensionKeyInCondition()) { + long long dimensionInConditionToken = protoOutput->start( + FIELD_TYPE_MESSAGE | FIELD_ID_DIMENSION_IN_CONDITION); + writeDimensionsValueProtoToStream( + dimensionKey.getDimensionKeyInCondition().getDimensionsValue(), protoOutput); + protoOutput->end(dimensionInConditionToken); + } + // Then fill bucket_info (DurationBucketInfo). for (const auto& bucket : pair.second) { long long bucketInfoToken = protoOutput->start( @@ -219,10 +260,11 @@ void DurationMetricProducer::flushIfNeededLocked(const uint64_t& eventTime) { return; } VLOG("flushing..........."); - for (auto it = mCurrentSlicedDuration.begin(); it != mCurrentSlicedDuration.end();) { + for (auto it = mCurrentSlicedDurationTrackerMap.begin(); + it != mCurrentSlicedDurationTrackerMap.end();) { if (it->second->flushIfNeeded(eventTime, &mPastBuckets)) { VLOG("erase bucket for key %s", it->first.c_str()); - it = mCurrentSlicedDuration.erase(it); + it = mCurrentSlicedDurationTrackerMap.erase(it); } else { ++it; } @@ -234,28 +276,28 @@ void DurationMetricProducer::flushIfNeededLocked(const uint64_t& eventTime) { } void DurationMetricProducer::dumpStatesLocked(FILE* out, bool verbose) const { - if (mCurrentSlicedDuration.size() == 0) { + if (mCurrentSlicedDurationTrackerMap.size() == 0) { return; } fprintf(out, "DurationMetric %lld dimension size %lu\n", (long long)mMetricId, - (unsigned long)mCurrentSlicedDuration.size()); + (unsigned long)mCurrentSlicedDurationTrackerMap.size()); if (verbose) { - for (const auto& slice : mCurrentSlicedDuration) { + for (const auto& slice : mCurrentSlicedDurationTrackerMap) { fprintf(out, "\t%s\n", slice.first.c_str()); slice.second->dumpStates(out, verbose); } } } -bool DurationMetricProducer::hitGuardRailLocked(const HashableDimensionKey& newKey) { +bool DurationMetricProducer::hitGuardRailLocked(const MetricDimensionKey& newKey) { // the key is not new, we are good. - if (mCurrentSlicedDuration.find(newKey) != mCurrentSlicedDuration.end()) { + if (mCurrentSlicedDurationTrackerMap.find(newKey) != mCurrentSlicedDurationTrackerMap.end()) { return false; } // 1. Report the tuple count if the tuple count > soft limit - if (mCurrentSlicedDuration.size() > StatsdStats::kDimensionKeySizeSoftLimit - 1) { - size_t newTupleCount = mCurrentSlicedDuration.size() + 1; + if (mCurrentSlicedDurationTrackerMap.size() > StatsdStats::kDimensionKeySizeSoftLimit - 1) { + size_t newTupleCount = mCurrentSlicedDurationTrackerMap.size() + 1; StatsdStats::getInstance().noteMetricDimensionSize(mConfigKey, mMetricId, newTupleCount); // 2. Don't add more tuples, we are above the allowed threshold. Drop the data. if (newTupleCount > StatsdStats::kDimensionKeySizeHardLimit) { @@ -268,27 +310,26 @@ bool DurationMetricProducer::hitGuardRailLocked(const HashableDimensionKey& newK } void DurationMetricProducer::onMatchedLogEventInternalLocked( - const size_t matcherIndex, const HashableDimensionKey& eventKey, + const size_t matcherIndex, const MetricDimensionKey& eventKey, const ConditionKey& conditionKeys, bool condition, const LogEvent& event) { flushIfNeededLocked(event.GetTimestampNs()); if (matcherIndex == mStopAllIndex) { - for (auto& pair : mCurrentSlicedDuration) { + for (auto& pair : mCurrentSlicedDurationTrackerMap) { pair.second->noteStopAll(event.GetTimestampNs()); } return; } - - if (mCurrentSlicedDuration.find(eventKey) == mCurrentSlicedDuration.end()) { + if (mCurrentSlicedDurationTrackerMap.find(eventKey) == mCurrentSlicedDurationTrackerMap.end()) { if (hitGuardRailLocked(eventKey)) { return; } - mCurrentSlicedDuration[eventKey] = createDurationTracker(eventKey); + mCurrentSlicedDurationTrackerMap[eventKey] = createDurationTracker(eventKey); } - auto it = mCurrentSlicedDuration.find(eventKey); + auto it = mCurrentSlicedDurationTrackerMap.find(eventKey); std::vector values; getDimensionKeys(event, mInternalDimensions, &values); @@ -302,10 +343,11 @@ void DurationMetricProducer::onMatchedLogEventInternalLocked( } else { for (const DimensionsValue& value : values) { if (matcherIndex == mStartIndex) { - it->second->noteStart(HashableDimensionKey(value), condition, - event.GetTimestampNs(), conditionKeys); + it->second->noteStart( + HashableDimensionKey(value), condition, event.GetTimestampNs(), conditionKeys); } else if (matcherIndex == mStopIndex) { - it->second->noteStop(HashableDimensionKey(value), event.GetTimestampNs(), false); + it->second->noteStop( + HashableDimensionKey(value), event.GetTimestampNs(), false); } } } diff --git a/cmds/statsd/src/metrics/DurationMetricProducer.h b/cmds/statsd/src/metrics/DurationMetricProducer.h index d8cab92a2b84d..152e570df4675 100644 --- a/cmds/statsd/src/metrics/DurationMetricProducer.h +++ b/cmds/statsd/src/metrics/DurationMetricProducer.h @@ -50,7 +50,7 @@ public: protected: void onMatchedLogEventInternalLocked( - const size_t matcherIndex, const HashableDimensionKey& eventKey, + const size_t matcherIndex, const MetricDimensionKey& eventKey, const ConditionKey& conditionKeys, bool condition, const LogEvent& event) override; @@ -92,21 +92,21 @@ private: // Save the past buckets and we can clear when the StatsLogReport is dumped. // TODO: Add a lock to mPastBuckets. - std::unordered_map> mPastBuckets; + std::unordered_map> mPastBuckets; // The current bucket. - std::unordered_map> - mCurrentSlicedDuration; + std::unordered_map> + mCurrentSlicedDurationTrackerMap; // Helper function to create a duration tracker given the metric aggregation type. std::unique_ptr createDurationTracker( - const HashableDimensionKey& eventKey) const; + const MetricDimensionKey& eventKey) const; // This hides the base class's std::vector> mAnomalyTrackers std::vector> mAnomalyTrackers; // Util function to check whether the specified dimension hits the guardrail. - bool hitGuardRailLocked(const HashableDimensionKey& newKey); + bool hitGuardRailLocked(const MetricDimensionKey& newKey); static const size_t kBucketSize = sizeof(DurationBucket{}); diff --git a/cmds/statsd/src/metrics/EventMetricProducer.cpp b/cmds/statsd/src/metrics/EventMetricProducer.cpp index 25c86d0d46f19..820d5918a92ae 100644 --- a/cmds/statsd/src/metrics/EventMetricProducer.cpp +++ b/cmds/statsd/src/metrics/EventMetricProducer.cpp @@ -128,7 +128,7 @@ void EventMetricProducer::onConditionChangedLocked(const bool conditionMet, } void EventMetricProducer::onMatchedLogEventInternalLocked( - const size_t matcherIndex, const HashableDimensionKey& eventKey, + const size_t matcherIndex, const MetricDimensionKey& eventKey, const ConditionKey& conditionKey, bool condition, const LogEvent& event) { if (!condition) { diff --git a/cmds/statsd/src/metrics/EventMetricProducer.h b/cmds/statsd/src/metrics/EventMetricProducer.h index 9da0dd0569d66..935f206017fa2 100644 --- a/cmds/statsd/src/metrics/EventMetricProducer.h +++ b/cmds/statsd/src/metrics/EventMetricProducer.h @@ -45,7 +45,7 @@ protected: private: void onMatchedLogEventInternalLocked( - const size_t matcherIndex, const HashableDimensionKey& eventKey, + const size_t matcherIndex, const MetricDimensionKey& eventKey, const ConditionKey& conditionKey, bool condition, const LogEvent& event) override; diff --git a/cmds/statsd/src/metrics/GaugeMetricProducer.cpp b/cmds/statsd/src/metrics/GaugeMetricProducer.cpp index 1072c5aae6e40..d6cb1891288af 100644 --- a/cmds/statsd/src/metrics/GaugeMetricProducer.cpp +++ b/cmds/statsd/src/metrics/GaugeMetricProducer.cpp @@ -82,13 +82,15 @@ GaugeMetricProducer::GaugeMetricProducer(const ConfigKey& key, const GaugeMetric mFieldFilter = metric.gauge_fields_filter(); // TODO: use UidMap if uid->pkg_name is required - mDimensions = metric.dimensions_in_what(); + mDimensionsInWhat = metric.dimensions_in_what(); + mDimensionsInCondition = metric.dimensions_in_condition(); if (metric.links().size() > 0) { mConditionLinks.insert(mConditionLinks.begin(), metric.links().begin(), metric.links().end()); - mConditionSliced = true; } + mConditionSliced = (metric.links().size() > 0)|| + (mDimensionsInCondition.has_field() && mDimensionsInCondition.child_size() > 0); // Kicks off the puller immediately. if (mPullTagId != -1 && mSamplingType == GaugeMetric::RANDOM_ONE_SAMPLE) { @@ -136,18 +138,27 @@ void GaugeMetricProducer::onDumpReportLocked(const uint64_t dumpTimeNs, long long protoToken = protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_ID_GAUGE_METRICS); for (const auto& pair : mPastBuckets) { - const HashableDimensionKey& hashableKey = pair.first; + const MetricDimensionKey& dimensionKey = pair.first; - VLOG(" dimension key %s", hashableKey.c_str()); + VLOG(" dimension key %s", dimensionKey.c_str()); long long wrapperToken = protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_DATA); // First fill dimension. long long dimensionToken = protoOutput->start( FIELD_TYPE_MESSAGE | FIELD_ID_DIMENSION_IN_WHAT); - writeDimensionsValueProtoToStream(hashableKey.getDimensionsValue(), protoOutput); + writeDimensionsValueProtoToStream( + dimensionKey.getDimensionKeyInWhat().getDimensionsValue(), protoOutput); protoOutput->end(dimensionToken); + if (dimensionKey.hasDimensionKeyInCondition()) { + long long dimensionInConditionToken = protoOutput->start( + FIELD_TYPE_MESSAGE | FIELD_ID_DIMENSION_IN_CONDITION); + writeDimensionsValueProtoToStream( + dimensionKey.getDimensionKeyInCondition().getDimensionsValue(), protoOutput); + protoOutput->end(dimensionInConditionToken); + } + // Then fill bucket_info (GaugeBucketInfo). for (const auto& bucket : pair.second) { long long bucketInfoToken = protoOutput->start( @@ -248,7 +259,7 @@ void GaugeMetricProducer::onDataPulled(const std::vectorfind(newKey) != mCurrentSlicedBucket->end()) { return false; } @@ -268,7 +279,7 @@ bool GaugeMetricProducer::hitGuardRailLocked(const HashableDimensionKey& newKey) } void GaugeMetricProducer::onMatchedLogEventInternalLocked( - const size_t matcherIndex, const HashableDimensionKey& eventKey, + const size_t matcherIndex, const MetricDimensionKey& eventKey, const ConditionKey& conditionKey, bool condition, const LogEvent& event) { if (condition == false) { diff --git a/cmds/statsd/src/metrics/GaugeMetricProducer.h b/cmds/statsd/src/metrics/GaugeMetricProducer.h index 6c013477af374..86d0ccd241ebf 100644 --- a/cmds/statsd/src/metrics/GaugeMetricProducer.h +++ b/cmds/statsd/src/metrics/GaugeMetricProducer.h @@ -44,7 +44,7 @@ struct GaugeBucket { uint64_t mBucketNum; }; -typedef std::unordered_map> +typedef std::unordered_map> DimToGaugeAtomsMap; // This gauge metric producer first register the puller to automatically pull the gauge at the @@ -64,7 +64,7 @@ public: protected: void onMatchedLogEventInternalLocked( - const size_t matcherIndex, const HashableDimensionKey& eventKey, + const size_t matcherIndex, const MetricDimensionKey& eventKey, const ConditionKey& conditionKey, bool condition, const LogEvent& event) override; @@ -99,7 +99,7 @@ private: // Save the past buckets and we can clear when the StatsLogReport is dumped. // TODO: Add a lock to mPastBuckets. - std::unordered_map> mPastBuckets; + std::unordered_map> mPastBuckets; // The current bucket. std::shared_ptr mCurrentSlicedBucket; @@ -119,7 +119,7 @@ private: std::shared_ptr getGaugeFields(const LogEvent& event); // Util function to check whether the specified dimension hits the guardrail. - bool hitGuardRailLocked(const HashableDimensionKey& newKey); + bool hitGuardRailLocked(const MetricDimensionKey& newKey); static const size_t kBucketSize = sizeof(GaugeBucket{}); diff --git a/cmds/statsd/src/metrics/MetricProducer.cpp b/cmds/statsd/src/metrics/MetricProducer.cpp index e74924a81fbf6..85e655b08f4d9 100644 --- a/cmds/statsd/src/metrics/MetricProducer.cpp +++ b/cmds/statsd/src/metrics/MetricProducer.cpp @@ -15,6 +15,8 @@ */ #include "MetricProducer.h" +#include "dimension.h" + namespace android { namespace os { namespace statsd { @@ -30,29 +32,51 @@ void MetricProducer::onMatchedLogEventLocked(const size_t matcherIndex, const Lo bool condition; ConditionKey conditionKey; + + std::unordered_set dimensionKeysInCondition; if (mConditionSliced) { for (const auto& link : mConditionLinks) { getDimensionKeysForCondition(event, link, &conditionKey[link.condition()]); } - if (mWizard->query(mConditionTrackerIndex, conditionKey) != ConditionState::kTrue) { - condition = false; - } else { - condition = true; - } + auto conditionState = + mWizard->query(mConditionTrackerIndex, conditionKey, mDimensionsInCondition, + &dimensionKeysInCondition); + condition = (conditionState == ConditionState::kTrue); } else { condition = mCondition; } - if (mDimensions.has_field() && mDimensions.child_size() > 0) { - vector dimensionValues; - getDimensionKeys(event, mDimensions, &dimensionValues); - for (const DimensionsValue& dimensionValue : dimensionValues) { + vector dimensionInWhatValues; + if (mDimensionsInWhat.has_field() && mDimensionsInWhat.child_size() > 0) { + getDimensionKeys(event, mDimensionsInWhat, &dimensionInWhatValues); + } + + if (dimensionInWhatValues.empty() && dimensionKeysInCondition.empty()) { + onMatchedLogEventInternalLocked( + matcherIndex, DEFAULT_METRIC_DIMENSION_KEY, conditionKey, condition, event); + } else if (dimensionKeysInCondition.empty()) { + for (const DimensionsValue& whatValue : dimensionInWhatValues) { onMatchedLogEventInternalLocked( - matcherIndex, HashableDimensionKey(dimensionValue), conditionKey, condition, event); + matcherIndex, + MetricDimensionKey(HashableDimensionKey(whatValue), DEFAULT_DIMENSION_KEY), + conditionKey, condition, event); + } + } else if (dimensionInWhatValues.empty()) { + for (const auto& conditionDimensionKey : dimensionKeysInCondition) { + onMatchedLogEventInternalLocked( + matcherIndex, + MetricDimensionKey(DEFAULT_DIMENSION_KEY, conditionDimensionKey), + conditionKey, condition, event); } } else { - onMatchedLogEventInternalLocked( - matcherIndex, DEFAULT_DIMENSION_KEY, conditionKey, condition, event); + for (const DimensionsValue& whatValue : dimensionInWhatValues) { + for (const auto& conditionDimensionKey : dimensionKeysInCondition) { + onMatchedLogEventInternalLocked( + matcherIndex, + MetricDimensionKey(HashableDimensionKey(whatValue), conditionDimensionKey), + conditionKey, condition, event); + } + } } } diff --git a/cmds/statsd/src/metrics/MetricProducer.h b/cmds/statsd/src/metrics/MetricProducer.h index 6f33073c633c9..3b1498f076329 100644 --- a/cmds/statsd/src/metrics/MetricProducer.h +++ b/cmds/statsd/src/metrics/MetricProducer.h @@ -91,6 +91,7 @@ public: std::lock_guard lock(mMutex); return onDumpReportLocked(dumpTimeNs, protoOutput); } + void onDumpReport(const uint64_t dumpTimeNs, StatsLogReport* report) { std::lock_guard lock(mMutex); return onDumpReportLocked(dumpTimeNs, report); @@ -156,7 +157,8 @@ protected: int mConditionTrackerIndex; - FieldMatcher mDimensions; // The dimension defined in statsd_config + FieldMatcher mDimensionsInWhat; // The dimensions_in_what defined in statsd_config + FieldMatcher mDimensionsInCondition; // The dimensions_in_condition defined in statsd_config std::vector mConditionLinks; @@ -178,7 +180,7 @@ protected: * [event]: the log event, just in case the metric needs its data, e.g., EventMetric. */ virtual void onMatchedLogEventInternalLocked( - const size_t matcherIndex, const HashableDimensionKey& eventKey, + const size_t matcherIndex, const MetricDimensionKey& eventKey, const ConditionKey& conditionKey, bool condition, const LogEvent& event) = 0; diff --git a/cmds/statsd/src/metrics/MetricsManager.h b/cmds/statsd/src/metrics/MetricsManager.h index 9cdbafc75fb15..d4b9102d5ddc7 100644 --- a/cmds/statsd/src/metrics/MetricsManager.h +++ b/cmds/statsd/src/metrics/MetricsManager.h @@ -143,6 +143,10 @@ private: FRIEND_TEST(MetricConditionLinkE2eTest, TestMultiplePredicatesAndLinks); FRIEND_TEST(AttributionE2eTest, TestAttributionMatchAndSlice); FRIEND_TEST(GaugeMetricE2eTest, TestMultipleFieldsForPushedEvent); + FRIEND_TEST(DimensionInConditionE2eTest, TestCountMetricNoLink); + FRIEND_TEST(DimensionInConditionE2eTest, TestCountMetricWithLink); + FRIEND_TEST(DimensionInConditionE2eTest, TestDurationMetricNoLink); + FRIEND_TEST(DimensionInConditionE2eTest, TestDurationMetricWithLink); }; } // namespace statsd diff --git a/cmds/statsd/src/metrics/ValueMetricProducer.cpp b/cmds/statsd/src/metrics/ValueMetricProducer.cpp index ae0c673e34907..c9cc7bb9c1d45 100644 --- a/cmds/statsd/src/metrics/ValueMetricProducer.cpp +++ b/cmds/statsd/src/metrics/ValueMetricProducer.cpp @@ -81,13 +81,15 @@ ValueMetricProducer::ValueMetricProducer(const ConfigKey& key, const ValueMetric } mBucketSizeNs = bucketSizeMills * 1000000; - mDimensions = metric.dimensions_in_what(); + mDimensionsInWhat = metric.dimensions_in_what(); + mDimensionsInCondition = metric.dimensions_in_condition(); if (metric.links().size() > 0) { mConditionLinks.insert(mConditionLinks.begin(), metric.links().begin(), metric.links().end()); - mConditionSliced = true; } + mConditionSliced = (metric.links().size() > 0)|| + (mDimensionsInCondition.has_field() && mDimensionsInCondition.child_size() > 0); if (!metric.has_condition() && mPullTagId != -1) { VLOG("Setting up periodic pulling for %d", mPullTagId); @@ -124,7 +126,10 @@ void ValueMetricProducer::onDumpReportLocked(const uint64_t dumpTimeNs, StatsLog auto value_metrics = report->mutable_value_metrics(); for (const auto& pair : mPastBuckets) { ValueMetricData* metricData = value_metrics->add_data(); - *metricData->mutable_dimensions_in_what() = pair.first.getDimensionsValue(); + *metricData->mutable_dimensions_in_what() = + pair.first.getDimensionKeyInWhat().getDimensionsValue(); + *metricData->mutable_dimensions_in_condition() = + pair.first.getDimensionKeyInCondition().getDimensionsValue(); for (const auto& bucket : pair.second) { ValueBucketInfo* bucketInfo = metricData->add_bucket_info(); bucketInfo->set_start_bucket_nanos(bucket.mBucketStartNs); @@ -146,16 +151,24 @@ void ValueMetricProducer::onDumpReportLocked(const uint64_t dumpTimeNs, long long protoToken = protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_ID_VALUE_METRICS); for (const auto& pair : mPastBuckets) { - const HashableDimensionKey& hashableKey = pair.first; - VLOG(" dimension key %s", hashableKey.c_str()); + const MetricDimensionKey& dimensionKey = pair.first; + VLOG(" dimension key %s", dimensionKey.c_str()); long long wrapperToken = protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_DATA); // First fill dimension. long long dimensionToken = protoOutput->start( FIELD_TYPE_MESSAGE | FIELD_ID_DIMENSION_IN_WHAT); - writeDimensionsValueProtoToStream(hashableKey.getDimensionsValue(), protoOutput); + writeDimensionsValueProtoToStream( + dimensionKey.getDimensionKeyInWhat().getDimensionsValue(), protoOutput); protoOutput->end(dimensionToken); + if (dimensionKey.hasDimensionKeyInCondition()) { + long long dimensionInConditionToken = protoOutput->start( + FIELD_TYPE_MESSAGE | FIELD_ID_DIMENSION_IN_CONDITION); + writeDimensionsValueProtoToStream( + dimensionKey.getDimensionKeyInCondition().getDimensionsValue(), protoOutput); + protoOutput->end(dimensionInConditionToken); + } // Then fill bucket_info (ValueBucketInfo). for (const auto& bucket : pair.second) { @@ -239,7 +252,7 @@ void ValueMetricProducer::onDataPulled(const std::vector soft limit if (mCurrentSlicedBucket.find(newKey) != mCurrentSlicedBucket.end()) { @@ -260,7 +273,7 @@ bool ValueMetricProducer::hitGuardRailLocked(const HashableDimensionKey& newKey) } void ValueMetricProducer::onMatchedLogEventInternalLocked( - const size_t matcherIndex, const HashableDimensionKey& eventKey, + const size_t matcherIndex, const MetricDimensionKey& eventKey, const ConditionKey& conditionKey, bool condition, const LogEvent& event) { uint64_t eventTimeNs = event.GetTimestampNs(); diff --git a/cmds/statsd/src/metrics/ValueMetricProducer.h b/cmds/statsd/src/metrics/ValueMetricProducer.h index 9f750cf419b5d..121ec7d185159 100644 --- a/cmds/statsd/src/metrics/ValueMetricProducer.h +++ b/cmds/statsd/src/metrics/ValueMetricProducer.h @@ -49,7 +49,7 @@ public: protected: void onMatchedLogEventInternalLocked( - const size_t matcherIndex, const HashableDimensionKey& eventKey, + const size_t matcherIndex, const MetricDimensionKey& eventKey, const ConditionKey& conditionKey, bool condition, const LogEvent& event) override; @@ -99,16 +99,16 @@ private: long sum; } Interval; - std::unordered_map mCurrentSlicedBucket; + std::unordered_map mCurrentSlicedBucket; // Save the past buckets and we can clear when the StatsLogReport is dumped. // TODO: Add a lock to mPastBuckets. - std::unordered_map> mPastBuckets; + std::unordered_map> mPastBuckets; std::shared_ptr getValueFields(const LogEvent& event); // Util function to check whether the specified dimension hits the guardrail. - bool hitGuardRailLocked(const HashableDimensionKey& newKey); + bool hitGuardRailLocked(const MetricDimensionKey& newKey); static const size_t kBucketSize = sizeof(ValueBucket{}); diff --git a/cmds/statsd/src/metrics/duration_helper/DurationTracker.h b/cmds/statsd/src/metrics/duration_helper/DurationTracker.h index c2d2cea2a1ff1..45735a8669785 100644 --- a/cmds/statsd/src/metrics/duration_helper/DurationTracker.h +++ b/cmds/statsd/src/metrics/duration_helper/DurationTracker.h @@ -60,8 +60,9 @@ struct DurationBucket { class DurationTracker { public: - DurationTracker(const ConfigKey& key, const int64_t& id, const HashableDimensionKey& eventKey, - sp wizard, int conditionIndex, bool nesting, + DurationTracker(const ConfigKey& key, const int64_t& id, const MetricDimensionKey& eventKey, + sp wizard, int conditionIndex, + const FieldMatcher& dimensionInCondition, bool nesting, uint64_t currentBucketStartNs, uint64_t bucketSizeNs, bool conditionSliced, const std::vector>& anomalyTrackers) : mConfigKey(key), @@ -70,6 +71,7 @@ public: mWizard(wizard), mConditionTrackerIndex(conditionIndex), mBucketSizeNs(bucketSizeNs), + mDimensionInCondition(dimensionInCondition), mNested(nesting), mCurrentBucketStartTimeNs(currentBucketStartNs), mDuration(0), @@ -79,6 +81,8 @@ public: virtual ~DurationTracker(){}; + virtual unique_ptr clone(const uint64_t eventTime) = 0; + virtual void noteStart(const HashableDimensionKey& key, bool condition, const uint64_t eventTime, const ConditionKey& conditionKey) = 0; virtual void noteStop(const HashableDimensionKey& key, const uint64_t eventTime, @@ -92,7 +96,7 @@ public: // events, so that the owner can safely remove the tracker. virtual bool flushIfNeeded( uint64_t timestampNs, - std::unordered_map>* output) = 0; + std::unordered_map>* output) = 0; // Predict the anomaly timestamp given the current status. virtual int64_t predictAnomalyTimestampNs(const DurationAnomalyTracker& anomalyTracker, @@ -100,6 +104,10 @@ public: // Dump internal states for debugging virtual void dumpStates(FILE* out, bool verbose) const = 0; + void setEventKey(const MetricDimensionKey& eventKey) { + mEventKey = eventKey; + } + protected: // Starts the anomaly alarm. void startAnomalyAlarm(const uint64_t eventTime) { @@ -150,7 +158,7 @@ protected: const int64_t mTrackerId; - HashableDimensionKey mEventKey; + MetricDimensionKey mEventKey; sp mWizard; @@ -158,6 +166,8 @@ protected: const int64_t mBucketSizeNs; + const FieldMatcher mDimensionInCondition; + const bool mNested; uint64_t mCurrentBucketStartTimeNs; diff --git a/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.cpp b/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.cpp index 412a0c9357663..db7dea4afd04d 100644 --- a/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.cpp +++ b/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.cpp @@ -25,13 +25,23 @@ namespace os { namespace statsd { MaxDurationTracker::MaxDurationTracker(const ConfigKey& key, const int64_t& id, - const HashableDimensionKey& eventKey, - sp wizard, int conditionIndex, bool nesting, + const MetricDimensionKey& eventKey, + sp wizard, int conditionIndex, + const FieldMatcher& dimensionInCondition, bool nesting, uint64_t currentBucketStartNs, uint64_t bucketSizeNs, bool conditionSliced, const vector>& anomalyTrackers) - : DurationTracker(key, id, eventKey, wizard, conditionIndex, nesting, currentBucketStartNs, - bucketSizeNs, conditionSliced, anomalyTrackers) { + : DurationTracker(key, id, eventKey, wizard, conditionIndex, dimensionInCondition, nesting, + currentBucketStartNs, bucketSizeNs, conditionSliced, anomalyTrackers) { +} + +unique_ptr MaxDurationTracker::clone(const uint64_t eventTime) { + auto clonedTracker = make_unique(*this); + for (auto it = clonedTracker->mInfos.begin(); it != clonedTracker->mInfos.end(); ++it) { + it->second.lastStartTime = eventTime; + it->second.lastDuration = 0; + } + return clonedTracker; } bool MaxDurationTracker::hitGuardRail(const HashableDimensionKey& newKey) { @@ -44,7 +54,7 @@ bool MaxDurationTracker::hitGuardRail(const HashableDimensionKey& newKey) { if (mInfos.size() > StatsdStats::kDimensionKeySizeSoftLimit - 1) { size_t newTupleCount = mInfos.size() + 1; StatsdStats::getInstance().noteMetricDimensionSize( - mConfigKey, hashDimensionsValue(mTrackerId, mEventKey.getDimensionsValue()), + mConfigKey, hashMetricDimensionKey(mTrackerId, mEventKey), newTupleCount); // 2. Don't add more tuples, we are above the allowed threshold. Drop the data. if (newTupleCount > StatsdStats::kDimensionKeySizeHardLimit) { @@ -149,7 +159,7 @@ void MaxDurationTracker::noteStopAll(const uint64_t eventTime) { } bool MaxDurationTracker::flushIfNeeded( - uint64_t eventTime, unordered_map>* output) { + uint64_t eventTime, unordered_map>* output) { if (mCurrentBucketStartTimeNs + mBucketSizeNs > eventTime) { return false; } @@ -236,8 +246,14 @@ void MaxDurationTracker::onSlicedConditionMayChange(const uint64_t timestamp) { if (pair.second.state == kStopped) { continue; } - bool conditionMet = mWizard->query(mConditionTrackerIndex, pair.second.conditionKeys) == - ConditionState::kTrue; + std::unordered_set conditionDimensionKeySet; + ConditionState conditionState = mWizard->query( + mConditionTrackerIndex, pair.second.conditionKeys, mDimensionInCondition, + &conditionDimensionKeySet); + bool conditionMet = (conditionState == ConditionState::kTrue) && + (!mDimensionInCondition.has_field() || + conditionDimensionKeySet.find(mEventKey.getDimensionKeyInCondition()) != + conditionDimensionKeySet.end()); VLOG("key: %s, condition: %d", pair.first.c_str(), conditionMet); noteConditionChanged(pair.first, conditionMet, timestamp); } diff --git a/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.h b/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.h index 661d1311293a9..4d32a0637d56d 100644 --- a/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.h +++ b/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.h @@ -29,10 +29,15 @@ namespace statsd { class MaxDurationTracker : public DurationTracker { public: MaxDurationTracker(const ConfigKey& key, const int64_t& id, - const HashableDimensionKey& eventKey, sp wizard, - int conditionIndex, bool nesting, uint64_t currentBucketStartNs, - uint64_t bucketSizeNs, bool conditionSliced, + const MetricDimensionKey& eventKey, sp wizard, + int conditionIndex, const FieldMatcher& dimensionInCondition, bool nesting, + uint64_t currentBucketStartNs, uint64_t bucketSizeNs, bool conditionSliced, const std::vector>& anomalyTrackers); + + MaxDurationTracker(const MaxDurationTracker& tracker) = default; + + unique_ptr clone(const uint64_t eventTime) override; + void noteStart(const HashableDimensionKey& key, bool condition, const uint64_t eventTime, const ConditionKey& conditionKey) override; void noteStop(const HashableDimensionKey& key, const uint64_t eventTime, @@ -41,7 +46,7 @@ public: bool flushIfNeeded( uint64_t timestampNs, - std::unordered_map>* output) override; + std::unordered_map>* output) override; void onSlicedConditionMayChange(const uint64_t timestamp) override; void onConditionChanged(bool condition, const uint64_t timestamp) override; diff --git a/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.cpp b/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.cpp index 75d7c0898d78d..0feae369b0d58 100644 --- a/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.cpp +++ b/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.cpp @@ -25,17 +25,25 @@ namespace statsd { using std::pair; OringDurationTracker::OringDurationTracker( - const ConfigKey& key, const int64_t& id, const HashableDimensionKey& eventKey, - sp wizard, int conditionIndex, bool nesting, uint64_t currentBucketStartNs, + const ConfigKey& key, const int64_t& id, const MetricDimensionKey& eventKey, + sp wizard, int conditionIndex, + const FieldMatcher& dimensionInCondition, bool nesting, uint64_t currentBucketStartNs, uint64_t bucketSizeNs, bool conditionSliced, const vector>& anomalyTrackers) - : DurationTracker(key, id, eventKey, wizard, conditionIndex, nesting, currentBucketStartNs, - bucketSizeNs, conditionSliced, anomalyTrackers), + : DurationTracker(key, id, eventKey, wizard, conditionIndex, dimensionInCondition, nesting, + currentBucketStartNs, bucketSizeNs, conditionSliced, anomalyTrackers), mStarted(), mPaused() { mLastStartTime = 0; } +unique_ptr OringDurationTracker::clone(const uint64_t eventTime) { + auto clonedTracker = make_unique(*this); + clonedTracker->mLastStartTime = eventTime; + clonedTracker->mDuration = 0; + return clonedTracker; +} + bool OringDurationTracker::hitGuardRail(const HashableDimensionKey& newKey) { // ===========GuardRail============== // 1. Report the tuple count if the tuple count > soft limit @@ -45,7 +53,7 @@ bool OringDurationTracker::hitGuardRail(const HashableDimensionKey& newKey) { if (mConditionKeyMap.size() > StatsdStats::kDimensionKeySizeSoftLimit - 1) { size_t newTupleCount = mConditionKeyMap.size() + 1; StatsdStats::getInstance().noteMetricDimensionSize( - mConfigKey, hashDimensionsValue(mTrackerId, mEventKey.getDimensionsValue()), + mConfigKey, hashMetricDimensionKey(mTrackerId, mEventKey), newTupleCount); // 2. Don't add more tuples, we are above the allowed threshold. Drop the data. if (newTupleCount > StatsdStats::kDimensionKeySizeHardLimit) { @@ -76,7 +84,6 @@ void OringDurationTracker::noteStart(const HashableDimensionKey& key, bool condi if (mConditionSliced && mConditionKeyMap.find(key) == mConditionKeyMap.end()) { mConditionKeyMap[key] = conditionKey; } - VLOG("Oring: %s start, condition %d", key.c_str(), condition); } @@ -128,7 +135,7 @@ void OringDurationTracker::noteStopAll(const uint64_t timestamp) { } bool OringDurationTracker::flushIfNeeded( - uint64_t eventTime, unordered_map>* output) { + uint64_t eventTime, unordered_map>* output) { if (eventTime < mCurrentBucketStartTimeNs + mBucketSizeNs) { return false; } @@ -184,8 +191,14 @@ void OringDurationTracker::onSlicedConditionMayChange(const uint64_t timestamp) ++it; continue; } - if (mWizard->query(mConditionTrackerIndex, mConditionKeyMap[key]) != - ConditionState::kTrue) { + std::unordered_set conditionDimensionKeySet; + ConditionState conditionState = + mWizard->query(mConditionTrackerIndex, mConditionKeyMap[key], + mDimensionInCondition, &conditionDimensionKeySet); + if (conditionState != ConditionState::kTrue || + (mDimensionInCondition.has_field() && + conditionDimensionKeySet.find(mEventKey.getDimensionKeyInCondition()) == + conditionDimensionKeySet.end())) { startedToPaused.push_back(*it); it = mStarted.erase(it); VLOG("Key %s started -> paused", key.c_str()); @@ -210,8 +223,14 @@ void OringDurationTracker::onSlicedConditionMayChange(const uint64_t timestamp) ++it; continue; } - if (mWizard->query(mConditionTrackerIndex, mConditionKeyMap[key]) == - ConditionState::kTrue) { + std::unordered_set conditionDimensionKeySet; + ConditionState conditionState = + mWizard->query(mConditionTrackerIndex, mConditionKeyMap[key], + mDimensionInCondition, &conditionDimensionKeySet); + if (conditionState == ConditionState::kTrue && + (!mDimensionInCondition.has_field() || + conditionDimensionKeySet.find(mEventKey.getDimensionKeyInCondition()) + != conditionDimensionKeySet.end())) { pausedToStarted.push_back(*it); it = mPaused.erase(it); VLOG("Key %s paused -> started", key.c_str()); diff --git a/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.h b/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.h index 43469ca9a5513..75b5a815de9cb 100644 --- a/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.h +++ b/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.h @@ -28,11 +28,15 @@ namespace statsd { class OringDurationTracker : public DurationTracker { public: OringDurationTracker(const ConfigKey& key, const int64_t& id, - const HashableDimensionKey& eventKey, sp wizard, - int conditionIndex, bool nesting, uint64_t currentBucketStartNs, - uint64_t bucketSizeNs, bool conditionSliced, + const MetricDimensionKey& eventKey, sp wizard, + int conditionIndex, const FieldMatcher& dimensionInCondition, bool nesting, + uint64_t currentBucketStartNs, uint64_t bucketSizeNs, bool conditionSliced, const std::vector>& anomalyTrackers); + OringDurationTracker(const OringDurationTracker& tracker) = default; + + unique_ptr clone(const uint64_t eventTime) override; + void noteStart(const HashableDimensionKey& key, bool condition, const uint64_t eventTime, const ConditionKey& conditionKey) override; void noteStop(const HashableDimensionKey& key, const uint64_t eventTime, @@ -44,7 +48,7 @@ public: bool flushIfNeeded( uint64_t timestampNs, - std::unordered_map>* output) override; + std::unordered_map>* output) override; int64_t predictAnomalyTimestampNs(const DurationAnomalyTracker& anomalyTracker, const uint64_t currentTimestamp) const override; diff --git a/cmds/statsd/src/stats_log_util.cpp b/cmds/statsd/src/stats_log_util.cpp index a41f30c2bece0..6c6140081bd81 100644 --- a/cmds/statsd/src/stats_log_util.cpp +++ b/cmds/statsd/src/stats_log_util.cpp @@ -54,6 +54,9 @@ const int FIELD_ID_MIN_PULL_INTERVAL_SEC = 4; void writeDimensionsValueProtoToStream(const DimensionsValue& dimensionsValue, ProtoOutputStream* protoOutput) { + if (!dimensionsValue.has_field()) { + return; + } protoOutput->write(FIELD_TYPE_INT32 | DIMENSIONS_VALUE_FIELD, dimensionsValue.field()); switch (dimensionsValue.value_case()) { case DimensionsValue::ValueCase::kValueStr: @@ -103,6 +106,9 @@ const int FIELD_CHILD = 3; void writeFieldProtoToStream( const Field& field, util::ProtoOutputStream* protoOutput) { + if (!field.has_field()) { + return; + } protoOutput->write(FIELD_TYPE_INT32 | FIELD_FIELD, field.field()); if (field.has_position_index()) { protoOutput->write(FIELD_TYPE_INT32 | FIELD_POSITION_INDEX, field.position_index()); diff --git a/cmds/statsd/src/stats_util.h b/cmds/statsd/src/stats_util.h index 160b1f40243f3..31f51a7ac0a43 100644 --- a/cmds/statsd/src/stats_util.h +++ b/cmds/statsd/src/stats_util.h @@ -28,13 +28,14 @@ namespace os { namespace statsd { const HashableDimensionKey DEFAULT_DIMENSION_KEY = HashableDimensionKey(); +const MetricDimensionKey DEFAULT_METRIC_DIMENSION_KEY = MetricDimensionKey(); // Minimum bucket size in seconds const long kMinBucketSizeSec = 5 * 60; typedef std::map> ConditionKey; -typedef std::unordered_map DimToValMap; +typedef std::unordered_map DimToValMap; } // namespace statsd } // namespace os diff --git a/cmds/statsd/src/subscriber/SubscriberReporter.cpp b/cmds/statsd/src/subscriber/SubscriberReporter.cpp index f912e4b2cd243..3af684fa60697 100644 --- a/cmds/statsd/src/subscriber/SubscriberReporter.cpp +++ b/cmds/statsd/src/subscriber/SubscriberReporter.cpp @@ -56,7 +56,7 @@ void SubscriberReporter::removeConfig(const ConfigKey& configKey) { void SubscriberReporter::alertBroadcastSubscriber(const ConfigKey& configKey, const Subscription& subscription, - const HashableDimensionKey& dimKey) const { + const MetricDimensionKey& dimKey) const { // Reminder about ids: // subscription id - name of the Subscription (that ties the Alert to the broadcast) // subscription rule_id - the name of the Alert (that triggers the broadcast) @@ -92,7 +92,7 @@ void SubscriberReporter::alertBroadcastSubscriber(const ConfigKey& configKey, void SubscriberReporter::sendBroadcastLocked(const sp& intentSender, const ConfigKey& configKey, const Subscription& subscription, - const HashableDimensionKey& dimKey) const { + const MetricDimensionKey& dimKey) const { VLOG("SubscriberReporter::sendBroadcastLocked called."); if (mStatsCompanionService == nullptr) { ALOGW("Failed to send subscriber broadcast: could not access StatsCompanionService."); @@ -107,8 +107,8 @@ void SubscriberReporter::sendBroadcastLocked(const sp& intentSender, } StatsDimensionsValue SubscriberReporter::protoToStatsDimensionsValue( - const HashableDimensionKey& dimKey) { - return protoToStatsDimensionsValue(dimKey.getDimensionsValue()); + const MetricDimensionKey& dimKey) { + return protoToStatsDimensionsValue(dimKey.getDimensionKeyInWhat().getDimensionsValue()); } StatsDimensionsValue SubscriberReporter::protoToStatsDimensionsValue( diff --git a/cmds/statsd/src/subscriber/SubscriberReporter.h b/cmds/statsd/src/subscriber/SubscriberReporter.h index 5bb458a8b1d8e..13fc7fd06279c 100644 --- a/cmds/statsd/src/subscriber/SubscriberReporter.h +++ b/cmds/statsd/src/subscriber/SubscriberReporter.h @@ -80,7 +80,7 @@ public: */ void alertBroadcastSubscriber(const ConfigKey& configKey, const Subscription& subscription, - const HashableDimensionKey& dimKey) const; + const MetricDimensionKey& dimKey) const; private: SubscriberReporter() {}; @@ -101,7 +101,7 @@ private: void sendBroadcastLocked(const sp& intentSender, const ConfigKey& configKey, const Subscription& subscription, - const HashableDimensionKey& dimKey) const; + const MetricDimensionKey& dimKey) const; /** Converts a stats_log.proto DimensionsValue to a StatsDimensionsValue. */ static StatsDimensionsValue protoToStatsDimensionsValue( @@ -109,7 +109,7 @@ private: /** Converts a HashableDimensionKey to a StatsDimensionsValue. */ static StatsDimensionsValue protoToStatsDimensionsValue( - const HashableDimensionKey& dimKey); + const MetricDimensionKey& dimKey); }; } // namespace statsd diff --git a/cmds/statsd/tests/anomaly/AnomalyTracker_test.cpp b/cmds/statsd/tests/anomaly/AnomalyTracker_test.cpp index 66bfa68ecc559..a415ea1a3069a 100644 --- a/cmds/statsd/tests/anomaly/AnomalyTracker_test.cpp +++ b/cmds/statsd/tests/anomaly/AnomalyTracker_test.cpp @@ -33,14 +33,14 @@ namespace statsd { const ConfigKey kConfigKey(0, 12345); -HashableDimensionKey getMockDimensionKey(int key, string value) { +MetricDimensionKey getMockMetricDimensionKey(int key, string value) { DimensionsValue dimensionsValue; dimensionsValue.set_field(key); dimensionsValue.set_value_str(value); - return HashableDimensionKey(dimensionsValue); + return MetricDimensionKey(HashableDimensionKey(dimensionsValue), DEFAULT_DIMENSION_KEY); } -void AddValueToBucket(const std::vector>& key_value_pair_list, +void AddValueToBucket(const std::vector>& key_value_pair_list, std::shared_ptr bucket) { for (auto itr = key_value_pair_list.begin(); itr != key_value_pair_list.end(); itr++) { (*bucket)[itr->first] += itr->second; @@ -48,7 +48,7 @@ void AddValueToBucket(const std::vector>& } std::shared_ptr MockBucket( - const std::vector>& key_value_pair_list) { + const std::vector>& key_value_pair_list) { std::shared_ptr bucket = std::make_shared(); AddValueToBucket(key_value_pair_list, bucket); return bucket; @@ -56,7 +56,7 @@ std::shared_ptr MockBucket( // Returns the value, for the given key, in that bucket, or 0 if not present. int64_t getBucketValue(const std::shared_ptr& bucket, - const HashableDimensionKey& key) { + const MetricDimensionKey& key) { const auto& itr = bucket->find(key); if (itr != bucket->end()) { return itr->second; @@ -68,14 +68,14 @@ int64_t getBucketValue(const std::shared_ptr& bucket, bool detectAnomaliesPass(AnomalyTracker& tracker, const int64_t& bucketNum, const std::shared_ptr& currentBucket, - const std::set& trueList, - const std::set& falseList) { - for (HashableDimensionKey key : trueList) { + const std::set& trueList, + const std::set& falseList) { + for (MetricDimensionKey key : trueList) { if (!tracker.detectAnomaly(bucketNum, key, getBucketValue(currentBucket, key))) { return false; } } - for (HashableDimensionKey key : falseList) { + for (MetricDimensionKey key : falseList) { if (tracker.detectAnomaly(bucketNum, key, getBucketValue(currentBucket, key))) { return false; } @@ -100,7 +100,7 @@ void detectAndDeclareAnomalies(AnomalyTracker& tracker, void checkRefractoryTimes(AnomalyTracker& tracker, const int64_t& currTimestampNs, const int32_t& refractoryPeriodSec, - const std::unordered_map& timestamps) { + const std::unordered_map& timestamps) { for (const auto& kv : timestamps) { if (kv.second < 0) { // Make sure that, if there is a refractory period, it is already past. @@ -124,9 +124,9 @@ TEST(AnomalyTrackerTest, TestConsecutiveBuckets) { alert.set_trigger_if_sum_gt(2); AnomalyTracker anomalyTracker(alert, kConfigKey); - HashableDimensionKey keyA = getMockDimensionKey(1, "a"); - HashableDimensionKey keyB = getMockDimensionKey(1, "b"); - HashableDimensionKey keyC = getMockDimensionKey(1, "c"); + MetricDimensionKey keyA = getMockMetricDimensionKey(1, "a"); + MetricDimensionKey keyB = getMockMetricDimensionKey(1, "b"); + MetricDimensionKey keyC = getMockMetricDimensionKey(1, "c"); int64_t eventTimestamp0 = 10 * NS_PER_SEC; int64_t eventTimestamp1 = bucketSizeNs + 11 * NS_PER_SEC; @@ -269,11 +269,11 @@ TEST(AnomalyTrackerTest, TestSparseBuckets) { alert.set_trigger_if_sum_gt(2); AnomalyTracker anomalyTracker(alert, kConfigKey); - HashableDimensionKey keyA = getMockDimensionKey(1, "a"); - HashableDimensionKey keyB = getMockDimensionKey(1, "b"); - HashableDimensionKey keyC = getMockDimensionKey(1, "c"); - HashableDimensionKey keyD = getMockDimensionKey(1, "d"); - HashableDimensionKey keyE = getMockDimensionKey(1, "e"); + MetricDimensionKey keyA = getMockMetricDimensionKey(1, "a"); + MetricDimensionKey keyB = getMockMetricDimensionKey(1, "b"); + MetricDimensionKey keyC = getMockMetricDimensionKey(1, "c"); + MetricDimensionKey keyD = getMockMetricDimensionKey(1, "d"); + MetricDimensionKey keyE = getMockMetricDimensionKey(1, "e"); std::shared_ptr bucket9 = MockBucket({{keyA, 1}, {keyB, 2}, {keyC, 1}}); std::shared_ptr bucket16 = MockBucket({{keyB, 4}}); diff --git a/cmds/statsd/tests/condition/SimpleConditionTracker_test.cpp b/cmds/statsd/tests/condition/SimpleConditionTracker_test.cpp index 819f2bebf327e..d1b7b2842fd2a 100644 --- a/cmds/statsd/tests/condition/SimpleConditionTracker_test.cpp +++ b/cmds/statsd/tests/condition/SimpleConditionTracker_test.cpp @@ -78,7 +78,7 @@ void makeWakeLockEvent( std::map> getWakeLockQueryKey( const Position position, const std::vector &uids, const string& conditionName) { - std::map> outputKeyMap; + std::map> outputKeyMap; std::vector uid_indexes; switch(position) { case Position::FIRST: @@ -265,6 +265,9 @@ TEST(SimpleConditionTrackerTest, TestNonSlicedConditionNestCounting) { TEST(SimpleConditionTrackerTest, TestSlicedCondition) { for (Position position : { Position::ANY, Position::FIRST, Position::LAST}) { + FieldMatcher dimensionInCondition; + std::unordered_set dimensionKeys; + SimplePredicate simplePredicate = getWakeLockHeldCondition( true /*nesting*/, true /*default to false*/, true /*output slice by uid*/, position); @@ -307,7 +310,8 @@ TEST(SimpleConditionTrackerTest, TestSlicedCondition) { const auto queryKey = getWakeLockQueryKey(position, uids, conditionName); conditionCache[0] = ConditionState::kNotEvaluated; - conditionTracker.isConditionMet(queryKey, allPredicates, conditionCache); + conditionTracker.isConditionMet(queryKey, allPredicates, dimensionInCondition, + conditionCache, dimensionKeys); EXPECT_EQ(ConditionState::kTrue, conditionCache[0]); // another wake lock acquired by this uid @@ -361,7 +365,8 @@ TEST(SimpleConditionTrackerTest, TestSlicedCondition) { // query again conditionCache[0] = ConditionState::kNotEvaluated; - conditionTracker.isConditionMet(queryKey, allPredicates, conditionCache); + conditionTracker.isConditionMet(queryKey, allPredicates, dimensionInCondition, + conditionCache, dimensionKeys); EXPECT_EQ(ConditionState::kFalse, conditionCache[0]); } @@ -369,6 +374,9 @@ TEST(SimpleConditionTrackerTest, TestSlicedCondition) { } TEST(SimpleConditionTrackerTest, TestSlicedWithNoOutputDim) { + FieldMatcher dimensionInCondition; + std::unordered_set dimensionKeys; + SimplePredicate simplePredicate = getWakeLockHeldCondition( true /*nesting*/, true /*default to false*/, false /*slice output by uid*/, Position::ANY /* position */); @@ -410,7 +418,8 @@ TEST(SimpleConditionTrackerTest, TestSlicedWithNoOutputDim) { ConditionKey queryKey; conditionCache[0] = ConditionState::kNotEvaluated; - conditionTracker.isConditionMet(queryKey, allPredicates, conditionCache); + conditionTracker.isConditionMet(queryKey, allPredicates, dimensionInCondition, + conditionCache, dimensionKeys); EXPECT_EQ(ConditionState::kTrue, conditionCache[0]); // another wake lock acquired by this uid @@ -452,13 +461,17 @@ TEST(SimpleConditionTrackerTest, TestSlicedWithNoOutputDim) { // query again conditionCache[0] = ConditionState::kNotEvaluated; - conditionTracker.isConditionMet(queryKey, allPredicates, conditionCache); + dimensionKeys.clear(); + conditionTracker.isConditionMet(queryKey, allPredicates, dimensionInCondition, + conditionCache, dimensionKeys); EXPECT_EQ(ConditionState::kFalse, conditionCache[0]); } TEST(SimpleConditionTrackerTest, TestStopAll) { for (Position position : {Position::ANY, Position::FIRST, Position::LAST}) { + FieldMatcher dimensionInCondition; + std::unordered_set dimensionKeys; SimplePredicate simplePredicate = getWakeLockHeldCondition( true /*nesting*/, true /*default to false*/, true /*output slice by uid*/, position); @@ -502,7 +515,8 @@ TEST(SimpleConditionTrackerTest, TestStopAll) { const auto queryKey = getWakeLockQueryKey(position, uid_list1, conditionName); conditionCache[0] = ConditionState::kNotEvaluated; - conditionTracker.isConditionMet(queryKey, allPredicates, conditionCache); + conditionTracker.isConditionMet(queryKey, allPredicates, dimensionInCondition, + conditionCache, dimensionKeys); EXPECT_EQ(ConditionState::kTrue, conditionCache[0]); // another wake lock acquired by uid2 @@ -528,8 +542,9 @@ TEST(SimpleConditionTrackerTest, TestStopAll) { // TEST QUERY const auto queryKey2 = getWakeLockQueryKey(position, uid_list2, conditionName); conditionCache[0] = ConditionState::kNotEvaluated; + conditionTracker.isConditionMet(queryKey, allPredicates, dimensionInCondition, + conditionCache, dimensionKeys); - conditionTracker.isConditionMet(queryKey, allPredicates, conditionCache); EXPECT_EQ(ConditionState::kTrue, conditionCache[0]); @@ -550,15 +565,15 @@ TEST(SimpleConditionTrackerTest, TestStopAll) { // TEST QUERY const auto queryKey3 = getWakeLockQueryKey(position, uid_list1, conditionName); conditionCache[0] = ConditionState::kNotEvaluated; - - conditionTracker.isConditionMet(queryKey, allPredicates, conditionCache); + conditionTracker.isConditionMet(queryKey, allPredicates, dimensionInCondition, + conditionCache, dimensionKeys); EXPECT_EQ(ConditionState::kFalse, conditionCache[0]); // TEST QUERY const auto queryKey4 = getWakeLockQueryKey(position, uid_list2, conditionName); conditionCache[0] = ConditionState::kNotEvaluated; - - conditionTracker.isConditionMet(queryKey, allPredicates, conditionCache); + conditionTracker.isConditionMet(queryKey, allPredicates, dimensionInCondition, + conditionCache, dimensionKeys); EXPECT_EQ(ConditionState::kFalse, conditionCache[0]); } diff --git a/cmds/statsd/tests/dimension_test.cpp b/cmds/statsd/tests/dimension_test.cpp new file mode 100644 index 0000000000000..678abaed5fd85 --- /dev/null +++ b/cmds/statsd/tests/dimension_test.cpp @@ -0,0 +1,149 @@ +// Copyright (C) 2017 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "dimension.h" + +#include + +using namespace android::os::statsd; + +#ifdef __ANDROID__ + +TEST(DimensionTest, subLeafNodes) { + DimensionsValue dimension; + int tagId = 100; + dimension.set_field(tagId); + auto child = dimension.mutable_value_tuple()->add_dimensions_value(); + child->set_field(1); + child->set_value_int(2000); + + child = dimension.mutable_value_tuple()->add_dimensions_value(); + child->set_field(3); + child->set_value_str("test"); + + child = dimension.mutable_value_tuple()->add_dimensions_value(); + child->set_field(4); + auto grandChild = child->mutable_value_tuple()->add_dimensions_value(); + grandChild->set_field(1); + grandChild->set_value_float(1.3f); + grandChild = child->mutable_value_tuple()->add_dimensions_value(); + grandChild->set_field(3); + grandChild->set_value_str("tag"); + + child = dimension.mutable_value_tuple()->add_dimensions_value(); + child->set_field(6); + child->set_value_bool(false); + + DimensionsValue sub_dimension; + FieldMatcher matcher; + + // Tag id not matched. + matcher.set_field(tagId + 1); + EXPECT_FALSE(getSubDimension(dimension, matcher, &sub_dimension)); + + // Field not exist. + matcher.Clear(); + matcher.set_field(tagId); + matcher.add_child()->set_field(5); + EXPECT_FALSE(getSubDimension(dimension, matcher, &sub_dimension)); + + // Field exists. + matcher.Clear(); + matcher.set_field(tagId); + matcher.add_child()->set_field(3); + EXPECT_TRUE(getSubDimension(dimension, matcher, &sub_dimension)); + + // Field exists. + matcher.Clear(); + sub_dimension.Clear(); + matcher.set_field(tagId); + matcher.add_child()->set_field(6); + EXPECT_TRUE(getSubDimension(dimension, matcher, &sub_dimension)); + + // Field exists. + matcher.Clear(); + sub_dimension.Clear(); + matcher.set_field(tagId); + matcher.add_child()->set_field(1); + EXPECT_TRUE(getSubDimension(dimension, matcher, &sub_dimension)); + + // Not leaf field. + matcher.Clear(); + sub_dimension.Clear(); + matcher.set_field(tagId); + matcher.add_child()->set_field(4); + EXPECT_FALSE(getSubDimension(dimension, matcher, &sub_dimension)); + + // Grand-child leaf field not exist. + matcher.Clear(); + sub_dimension.Clear(); + matcher.set_field(tagId); + auto childMatcher = matcher.add_child(); + childMatcher->set_field(4); + childMatcher->add_child()->set_field(2); + EXPECT_FALSE(getSubDimension(dimension, matcher, &sub_dimension)); + + // Grand-child leaf field. + matcher.Clear(); + sub_dimension.Clear(); + matcher.set_field(tagId); + childMatcher = matcher.add_child(); + childMatcher->set_field(4); + childMatcher->add_child()->set_field(1); + EXPECT_TRUE(getSubDimension(dimension, matcher, &sub_dimension)); + + matcher.Clear(); + sub_dimension.Clear(); + matcher.set_field(tagId); + childMatcher = matcher.add_child(); + childMatcher->set_field(4); + childMatcher->add_child()->set_field(3); + EXPECT_TRUE(getSubDimension(dimension, matcher, &sub_dimension)); + + // Multiple grand-child fields. + matcher.Clear(); + sub_dimension.Clear(); + matcher.set_field(tagId); + childMatcher = matcher.add_child(); + childMatcher->set_field(4); + childMatcher->add_child()->set_field(3); + childMatcher->add_child()->set_field(1); + EXPECT_TRUE(getSubDimension(dimension, matcher, &sub_dimension)); + + // Multiple fields. + matcher.Clear(); + sub_dimension.Clear(); + matcher.set_field(tagId); + childMatcher = matcher.add_child(); + childMatcher->set_field(4); + childMatcher->add_child()->set_field(3); + childMatcher->add_child()->set_field(1); + matcher.add_child()->set_field(3); + EXPECT_TRUE(getSubDimension(dimension, matcher, &sub_dimension)); + + // Subset of the fields not exist. + matcher.Clear(); + sub_dimension.Clear(); + matcher.set_field(tagId); + childMatcher = matcher.add_child(); + childMatcher->set_field(4); + childMatcher->add_child()->set_field(3); + childMatcher->add_child()->set_field(1); + matcher.add_child()->set_field(2); + EXPECT_FALSE(getSubDimension(dimension, matcher, &sub_dimension)); +} + +#else +GTEST_LOG_(INFO) << "This test does nothing.\n"; +#endif diff --git a/cmds/statsd/tests/e2e/DimensionInCondition_e2e_test.cpp b/cmds/statsd/tests/e2e/DimensionInCondition_e2e_test.cpp new file mode 100644 index 0000000000000..b5d48efb55f69 --- /dev/null +++ b/cmds/statsd/tests/e2e/DimensionInCondition_e2e_test.cpp @@ -0,0 +1,725 @@ +// Copyright (C) 2017 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include + +#include "src/StatsLogProcessor.h" +#include "src/stats_log_util.h" +#include "tests/statsd_test_util.h" + +#include + +namespace android { +namespace os { +namespace statsd { + +#ifdef __ANDROID__ + +namespace { + +StatsdConfig CreateCountMetricWithNoLinkConfig() { + StatsdConfig config; + auto screenBrightnessChangeAtomMatcher = CreateScreenBrightnessChangedAtomMatcher(); + *config.add_atom_matcher() = screenBrightnessChangeAtomMatcher; + *config.add_atom_matcher() = CreateScreenTurnedOnAtomMatcher(); + *config.add_atom_matcher() = CreateScreenTurnedOffAtomMatcher(); + *config.add_atom_matcher() = CreateAcquireWakelockAtomMatcher(); + *config.add_atom_matcher() = CreateReleaseWakelockAtomMatcher(); + + auto screenIsOffPredicate = CreateScreenIsOffPredicate(); + *config.add_predicate() = screenIsOffPredicate; + + auto holdingWakelockPredicate = CreateHoldingWakelockPredicate(); + // The predicate is dimensioning by any attribution node and both by uid and tag. + *holdingWakelockPredicate.mutable_simple_predicate()->mutable_dimensions() = + CreateAttributionUidAndTagDimensions( + android::util::WAKELOCK_STATE_CHANGED, {Position::FIRST}); + *config.add_predicate() = holdingWakelockPredicate; + + auto combinationPredicate = config.add_predicate(); + combinationPredicate->set_id(987654); + combinationPredicate->mutable_combination()->set_operation(LogicalOperation::OR); + addPredicateToPredicateCombination(screenIsOffPredicate, combinationPredicate); + addPredicateToPredicateCombination(holdingWakelockPredicate, combinationPredicate); + + auto metric = config.add_count_metric(); + metric->set_id(StringToId("ScreenBrightnessChangeMetric")); + metric->set_what(screenBrightnessChangeAtomMatcher.id()); + metric->set_condition(combinationPredicate->id()); + *metric->mutable_dimensions_in_what() = CreateDimensions( + android::util::SCREEN_BRIGHTNESS_CHANGED, {1 /* level */}); + *metric->mutable_dimensions_in_condition() = CreateAttributionUidDimensions( + android::util::WAKELOCK_STATE_CHANGED, {Position::FIRST}); + metric->set_bucket(ONE_MINUTE); + return config; +} + +} // namespace + +TEST(DimensionInConditionE2eTest, TestCountMetricNoLink) { + ConfigKey cfgKey; + auto config = CreateCountMetricWithNoLinkConfig(); + int64_t bucketStartTimeNs = 10000000000; + int64_t bucketSizeNs = + TimeUnitToBucketSizeInMillis(config.count_metric(0).bucket()) * 1000000LL; + + auto processor = CreateStatsLogProcessor(bucketStartTimeNs / NS_PER_SEC, config, cfgKey); + EXPECT_EQ(processor->mMetricsManagers.size(), 1u); + EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid()); + + std::vector attributions1 = + {CreateAttribution(111, "App1"), CreateAttribution(222, "GMSCoreModule1"), + CreateAttribution(222, "GMSCoreModule2")}; + + std::vector attributions2 = + {CreateAttribution(333, "App2"), CreateAttribution(222, "GMSCoreModule1"), + CreateAttribution(555, "GMSCoreModule2")}; + + std::vector> events; + events.push_back(CreateScreenStateChangedEvent( + android::view::DISPLAY_STATE_ON, bucketStartTimeNs + 10)); + events.push_back(CreateScreenStateChangedEvent( + android::view::DISPLAY_STATE_OFF, bucketStartTimeNs + 100)); + events.push_back(CreateScreenStateChangedEvent( + android::view::DISPLAY_STATE_ON, bucketStartTimeNs + bucketSizeNs + 1)); + events.push_back(CreateScreenStateChangedEvent( + android::view::DISPLAY_STATE_OFF, bucketStartTimeNs + 2 * bucketSizeNs - 10)); + + events.push_back(CreateAcquireWakelockEvent( + attributions1, "wl1", bucketStartTimeNs + 200)); + events.push_back(CreateReleaseWakelockEvent( + attributions1, "wl1", bucketStartTimeNs + bucketSizeNs + 1)); + + events.push_back(CreateAcquireWakelockEvent( + attributions2, "wl2", bucketStartTimeNs + bucketSizeNs - 100)); + events.push_back(CreateReleaseWakelockEvent( + attributions2, "wl2", bucketStartTimeNs + 2 * bucketSizeNs - 50)); + + events.push_back(CreateScreenBrightnessChangedEvent( + 123, bucketStartTimeNs + 11)); + events.push_back(CreateScreenBrightnessChangedEvent( + 123, bucketStartTimeNs + 101)); + events.push_back(CreateScreenBrightnessChangedEvent( + 123, bucketStartTimeNs + 201)); + events.push_back(CreateScreenBrightnessChangedEvent( + 456, bucketStartTimeNs + 203)); + events.push_back(CreateScreenBrightnessChangedEvent( + 456, bucketStartTimeNs + bucketSizeNs - 99)); + events.push_back(CreateScreenBrightnessChangedEvent( + 456, bucketStartTimeNs + bucketSizeNs - 2)); + events.push_back(CreateScreenBrightnessChangedEvent( + 789, bucketStartTimeNs + bucketSizeNs - 1)); + events.push_back(CreateScreenBrightnessChangedEvent( + 456, bucketStartTimeNs + bucketSizeNs + 2)); + events.push_back(CreateScreenBrightnessChangedEvent( + 789, bucketStartTimeNs + 2 * bucketSizeNs - 11)); + events.push_back(CreateScreenBrightnessChangedEvent( + 789, bucketStartTimeNs + 2 * bucketSizeNs - 9)); + events.push_back(CreateScreenBrightnessChangedEvent( + 789, bucketStartTimeNs + 2 * bucketSizeNs - 1)); + + sortLogEventsByTimestamp(&events); + + for (const auto& event : events) { + processor->OnLogEvent(event.get()); + } + + ConfigMetricsReportList reports; + processor->onDumpReport(cfgKey, bucketStartTimeNs + 2 * bucketSizeNs + 1, &reports); + + EXPECT_EQ(reports.reports_size(), 1); + EXPECT_EQ(reports.reports(0).metrics_size(), 1); + StatsLogReport::CountMetricDataWrapper countMetrics; + sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).count_metrics(), &countMetrics); + + EXPECT_EQ(countMetrics.data_size(), 7); + auto data = countMetrics.data(0); + EXPECT_EQ(data.bucket_info_size(), 1); + EXPECT_EQ(data.bucket_info(0).count(), 1); + EXPECT_EQ(data.bucket_info(0).start_bucket_nanos(), bucketStartTimeNs ); + EXPECT_EQ(data.bucket_info(0).end_bucket_nanos(), bucketStartTimeNs + bucketSizeNs); + EXPECT_EQ(data.dimensions_in_what().field(), android::util::SCREEN_BRIGHTNESS_CHANGED); + EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value_size(), 1); + EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(), 1); + EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).value_int(), 123); + EXPECT_FALSE(data.dimensions_in_condition().has_field()); + + data = countMetrics.data(1); + EXPECT_EQ(data.bucket_info_size(), 1); + EXPECT_EQ(data.bucket_info(0).count(), 1); + EXPECT_EQ(data.bucket_info(0).start_bucket_nanos(), bucketStartTimeNs); + EXPECT_EQ(data.bucket_info(0).end_bucket_nanos(), bucketStartTimeNs + bucketSizeNs); + EXPECT_EQ(data.dimensions_in_what().field(), android::util::SCREEN_BRIGHTNESS_CHANGED); + EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value_size(), 1); + EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(), 1); + EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).value_int(), 123); + ValidateAttributionUidDimension(data.dimensions_in_condition(), android::util::WAKELOCK_STATE_CHANGED, 111); + + data = countMetrics.data(2); + EXPECT_EQ(data.bucket_info_size(), 1); + EXPECT_EQ(data.bucket_info(0).count(), 3); + EXPECT_EQ(data.bucket_info(0).start_bucket_nanos(), bucketStartTimeNs); + EXPECT_EQ(data.bucket_info(0).end_bucket_nanos(), bucketStartTimeNs + bucketSizeNs); + EXPECT_EQ(data.dimensions_in_what().field(), android::util::SCREEN_BRIGHTNESS_CHANGED); + EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value_size(), 1); + EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(), 1); + EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).value_int(), 456); + ValidateAttributionUidDimension(data.dimensions_in_condition(), android::util::WAKELOCK_STATE_CHANGED, 111); + + data = countMetrics.data(3); + EXPECT_EQ(data.bucket_info_size(), 2); + EXPECT_EQ(data.bucket_info(0).count(), 2); + EXPECT_EQ(data.bucket_info(0).start_bucket_nanos(), bucketStartTimeNs); + EXPECT_EQ(data.bucket_info(0).end_bucket_nanos(), bucketStartTimeNs + bucketSizeNs); + EXPECT_EQ(data.bucket_info(1).count(), 1); + EXPECT_EQ(data.bucket_info(1).start_bucket_nanos(), bucketStartTimeNs + bucketSizeNs); + EXPECT_EQ(data.bucket_info(1).end_bucket_nanos(), bucketStartTimeNs + 2 * bucketSizeNs); + EXPECT_EQ(data.dimensions_in_what().field(), android::util::SCREEN_BRIGHTNESS_CHANGED); + EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value_size(), 1); + EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(), 1); + EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).value_int(), 456); + ValidateAttributionUidDimension(data.dimensions_in_condition(), android::util::WAKELOCK_STATE_CHANGED, 333); + + data = countMetrics.data(4); + EXPECT_EQ(data.bucket_info_size(), 1); + EXPECT_EQ(data.bucket_info(0).count(), 2); + EXPECT_EQ(data.bucket_info(0).start_bucket_nanos(), bucketStartTimeNs + bucketSizeNs); + EXPECT_EQ(data.bucket_info(0).end_bucket_nanos(), bucketStartTimeNs + 2 * bucketSizeNs); + EXPECT_EQ(data.dimensions_in_what().field(), android::util::SCREEN_BRIGHTNESS_CHANGED); + EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value_size(), 1); + EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(), 1); + EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).value_int(), 789); + EXPECT_FALSE(data.dimensions_in_condition().has_field()); + + data = countMetrics.data(5); + EXPECT_EQ(data.bucket_info_size(), 1); + EXPECT_EQ(data.bucket_info(0).count(), 1); + EXPECT_EQ(data.bucket_info(0).start_bucket_nanos(), bucketStartTimeNs); + EXPECT_EQ(data.bucket_info(0).end_bucket_nanos(), bucketStartTimeNs + bucketSizeNs); + EXPECT_EQ(data.dimensions_in_what().field(), android::util::SCREEN_BRIGHTNESS_CHANGED); + EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value_size(), 1); + EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(), 1); + EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).value_int(), 789); + ValidateAttributionUidDimension(data.dimensions_in_condition(), android::util::WAKELOCK_STATE_CHANGED, 111); + + data = countMetrics.data(6); + EXPECT_EQ(data.bucket_info_size(), 1); + EXPECT_EQ(data.bucket_info(0).count(), 1); + EXPECT_EQ(data.bucket_info(0).start_bucket_nanos(), bucketStartTimeNs); + EXPECT_EQ(data.bucket_info(0).end_bucket_nanos(), bucketStartTimeNs + bucketSizeNs); + EXPECT_EQ(data.dimensions_in_what().field(), android::util::SCREEN_BRIGHTNESS_CHANGED); + EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value_size(), 1); + EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(), 1); + EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).value_int(), 789); + ValidateAttributionUidDimension(data.dimensions_in_condition(), android::util::WAKELOCK_STATE_CHANGED, 333); +} + +namespace { + +StatsdConfig CreateCountMetricWithLinkConfig() { + StatsdConfig config; + auto appCrashMatcher = CreateProcessCrashAtomMatcher(); + *config.add_atom_matcher() = appCrashMatcher; + *config.add_atom_matcher() = CreateScreenTurnedOnAtomMatcher(); + *config.add_atom_matcher() = CreateScreenTurnedOffAtomMatcher(); + *config.add_atom_matcher() = CreateSyncStartAtomMatcher(); + *config.add_atom_matcher() = CreateSyncEndAtomMatcher(); + + auto screenIsOffPredicate = CreateScreenIsOffPredicate(); + auto isSyncingPredicate = CreateIsSyncingPredicate(); + auto syncDimension = isSyncingPredicate.mutable_simple_predicate()->mutable_dimensions(); + *syncDimension = CreateAttributionUidAndTagDimensions( + android::util::SYNC_STATE_CHANGED, {Position::FIRST}); + syncDimension->add_child()->set_field(2 /* name field*/); + + *config.add_predicate() = screenIsOffPredicate; + *config.add_predicate() = isSyncingPredicate; + auto combinationPredicate = config.add_predicate(); + combinationPredicate->set_id(987654); + combinationPredicate->mutable_combination()->set_operation(LogicalOperation::OR); + addPredicateToPredicateCombination(screenIsOffPredicate, combinationPredicate); + addPredicateToPredicateCombination(isSyncingPredicate, combinationPredicate); + + auto metric = config.add_count_metric(); + metric->set_bucket(ONE_MINUTE); + metric->set_id(StringToId("AppCrashMetric")); + metric->set_what(appCrashMatcher.id()); + metric->set_condition(combinationPredicate->id()); + *metric->mutable_dimensions_in_what() = CreateDimensions( + android::util::PROCESS_LIFE_CYCLE_STATE_CHANGED, {1 /* uid */}); + *metric->mutable_dimensions_in_condition() = CreateAttributionUidAndTagDimensions( + android::util::SYNC_STATE_CHANGED, {Position::FIRST}); + + // Links between crash atom and condition of app is in syncing. + auto links = metric->add_links(); + links->set_condition(isSyncingPredicate.id()); + auto dimensionWhat = links->mutable_fields_in_what(); + dimensionWhat->set_field(android::util::PROCESS_LIFE_CYCLE_STATE_CHANGED); + dimensionWhat->add_child()->set_field(1); // uid field. + *links->mutable_fields_in_condition() = CreateAttributionUidDimensions( + android::util::SYNC_STATE_CHANGED, {Position::FIRST}); + return config; +} + +} // namespace + +TEST(DimensionInConditionE2eTest, TestCountMetricWithLink) { + ConfigKey cfgKey; + auto config = CreateCountMetricWithLinkConfig(); + int64_t bucketStartTimeNs = 10000000000; + int64_t bucketSizeNs = + TimeUnitToBucketSizeInMillis(config.count_metric(0).bucket()) * 1000000LL; + + auto processor = CreateStatsLogProcessor(bucketStartTimeNs / NS_PER_SEC, config, cfgKey); + EXPECT_EQ(processor->mMetricsManagers.size(), 1u); + EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid()); + std::vector attributions1 = + {CreateAttribution(111, "App1"), CreateAttribution(222, "GMSCoreModule1"), + CreateAttribution(222, "GMSCoreModule2")}; + + std::vector attributions2 = + {CreateAttribution(333, "App2"), CreateAttribution(222, "GMSCoreModule1"), + CreateAttribution(555, "GMSCoreModule2")}; + + std::vector> events; + + events.push_back(CreateAppCrashEvent(111, bucketStartTimeNs + 11)); + events.push_back(CreateAppCrashEvent(111, bucketStartTimeNs + 101)); + events.push_back(CreateAppCrashEvent(222, bucketStartTimeNs + 101)); + + events.push_back(CreateAppCrashEvent(222, bucketStartTimeNs + 201)); + events.push_back(CreateAppCrashEvent(111, bucketStartTimeNs + 211)); + events.push_back(CreateAppCrashEvent(333, bucketStartTimeNs + 211)); + + events.push_back(CreateAppCrashEvent(111, bucketStartTimeNs + 401)); + events.push_back(CreateAppCrashEvent(333, bucketStartTimeNs + 401)); + events.push_back(CreateAppCrashEvent(555, bucketStartTimeNs + 401)); + + events.push_back(CreateAppCrashEvent(111, bucketStartTimeNs + bucketSizeNs + 301)); + events.push_back(CreateAppCrashEvent(333, bucketStartTimeNs + bucketSizeNs + 301)); + + events.push_back(CreateAppCrashEvent(777, bucketStartTimeNs + bucketSizeNs + 701)); + + events.push_back(CreateScreenStateChangedEvent( + android::view::DISPLAY_STATE_ON, bucketStartTimeNs + 10)); + events.push_back(CreateScreenStateChangedEvent( + android::view::DISPLAY_STATE_OFF, bucketStartTimeNs + 100)); + events.push_back(CreateScreenStateChangedEvent( + android::view::DISPLAY_STATE_ON, bucketStartTimeNs + 202)); + events.push_back(CreateScreenStateChangedEvent( + android::view::DISPLAY_STATE_OFF, bucketStartTimeNs + bucketSizeNs + 700)); + + events.push_back(CreateSyncStartEvent(attributions1, "ReadEmail", bucketStartTimeNs + 200)); + events.push_back(CreateSyncEndEvent(attributions1, "ReadEmail", + bucketStartTimeNs + bucketSizeNs + 300)); + + events.push_back(CreateSyncStartEvent(attributions1, "ReadDoc", bucketStartTimeNs + 400)); + events.push_back(CreateSyncEndEvent(attributions1, "ReadDoc", + bucketStartTimeNs + bucketSizeNs - 1)); + + events.push_back(CreateSyncStartEvent(attributions2, "ReadEmail", bucketStartTimeNs + 400)); + events.push_back(CreateSyncEndEvent(attributions2, "ReadEmail", + bucketStartTimeNs + bucketSizeNs + 600)); + + sortLogEventsByTimestamp(&events); + + for (const auto& event : events) { + processor->OnLogEvent(event.get()); + } + + ConfigMetricsReportList reports; + processor->onDumpReport(cfgKey, bucketStartTimeNs + 2 * bucketSizeNs + 1, &reports); + + EXPECT_EQ(reports.reports_size(), 1); + EXPECT_EQ(reports.reports(0).metrics_size(), 1); + StatsLogReport::CountMetricDataWrapper countMetrics; + sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).count_metrics(), &countMetrics); + + EXPECT_EQ(countMetrics.data_size(), 5); + auto data = countMetrics.data(0); + EXPECT_EQ(data.dimensions_in_what().field(), android::util::PROCESS_LIFE_CYCLE_STATE_CHANGED); + EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value_size(), 1); + EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(), 1); + EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).value_int(), 111); + EXPECT_FALSE(data.dimensions_in_condition().has_field()); + EXPECT_EQ(data.bucket_info_size(), 1); + EXPECT_EQ(data.bucket_info(0).count(), 1); + EXPECT_EQ(data.bucket_info(0).start_bucket_nanos(), bucketStartTimeNs); + EXPECT_EQ(data.bucket_info(0).end_bucket_nanos(), bucketStartTimeNs + bucketSizeNs); + + data = countMetrics.data(1); + EXPECT_EQ(data.dimensions_in_what().field(), android::util::PROCESS_LIFE_CYCLE_STATE_CHANGED); + EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value_size(), 1); + EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(), 1); + EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).value_int(), 111); + ValidateAttributionUidAndTagDimension( + data.dimensions_in_condition(), android::util::SYNC_STATE_CHANGED, 111, "App1"); + EXPECT_EQ(data.bucket_info_size(), 1); + EXPECT_EQ(data.bucket_info(0).count(), 2); + EXPECT_EQ(data.bucket_info(0).start_bucket_nanos(), bucketStartTimeNs); + EXPECT_EQ(data.bucket_info(0).end_bucket_nanos(), bucketStartTimeNs + bucketSizeNs); + + data = countMetrics.data(2); + EXPECT_EQ(data.dimensions_in_what().field(), android::util::PROCESS_LIFE_CYCLE_STATE_CHANGED); + EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value_size(), 1); + EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(), 1); + EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).value_int(), 222); + EXPECT_FALSE(data.dimensions_in_condition().has_field()); + EXPECT_EQ(data.bucket_info_size(), 1); + EXPECT_EQ(data.bucket_info(0).count(), 2); + EXPECT_EQ(data.bucket_info(0).start_bucket_nanos(), bucketStartTimeNs); + EXPECT_EQ(data.bucket_info(0).end_bucket_nanos(), bucketStartTimeNs + bucketSizeNs); + + data = countMetrics.data(3); + EXPECT_EQ(data.dimensions_in_what().field(), android::util::PROCESS_LIFE_CYCLE_STATE_CHANGED); + EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value_size(), 1); + EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(), 1); + EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).value_int(), 333); + ValidateAttributionUidAndTagDimension( + data.dimensions_in_condition(), android::util::SYNC_STATE_CHANGED, 333, "App2"); + EXPECT_EQ(data.bucket_info_size(), 2); + EXPECT_EQ(data.bucket_info(0).count(), 1); + EXPECT_EQ(data.bucket_info(0).start_bucket_nanos(), bucketStartTimeNs); + EXPECT_EQ(data.bucket_info(0).end_bucket_nanos(), bucketStartTimeNs + bucketSizeNs); + EXPECT_EQ(data.bucket_info(1).count(), 1); + EXPECT_EQ(data.bucket_info(1).start_bucket_nanos(), bucketStartTimeNs + bucketSizeNs); + EXPECT_EQ(data.bucket_info(1).end_bucket_nanos(), bucketStartTimeNs + 2 * bucketSizeNs); + + data = countMetrics.data(4); + EXPECT_EQ(data.dimensions_in_what().field(), android::util::PROCESS_LIFE_CYCLE_STATE_CHANGED); + EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value_size(), 1); + EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(), 1); + EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).value_int(), 777); + EXPECT_FALSE(data.dimensions_in_condition().has_field()); + EXPECT_EQ(data.bucket_info_size(), 1); + EXPECT_EQ(data.bucket_info(0).count(), 1); + EXPECT_EQ(data.bucket_info(0).start_bucket_nanos(), bucketStartTimeNs + bucketSizeNs); + EXPECT_EQ(data.bucket_info(0).end_bucket_nanos(), bucketStartTimeNs + 2 * bucketSizeNs); +} + +namespace { + +StatsdConfig CreateDurationMetricConfigNoLink(DurationMetric::AggregationType aggregationType) { + StatsdConfig config; + *config.add_atom_matcher() = CreateBatterySaverModeStartAtomMatcher(); + *config.add_atom_matcher() = CreateBatterySaverModeStopAtomMatcher(); + *config.add_atom_matcher() = CreateScreenTurnedOnAtomMatcher(); + *config.add_atom_matcher() = CreateScreenTurnedOffAtomMatcher(); + *config.add_atom_matcher() = CreateSyncStartAtomMatcher(); + *config.add_atom_matcher() = CreateSyncEndAtomMatcher(); + + auto inBatterySaverModePredicate = CreateBatterySaverModePredicate(); + + auto screenIsOffPredicate = CreateScreenIsOffPredicate(); + auto isSyncingPredicate = CreateIsSyncingPredicate(); + auto syncDimension = isSyncingPredicate.mutable_simple_predicate()->mutable_dimensions(); + *syncDimension = CreateAttributionUidAndTagDimensions( + android::util::SYNC_STATE_CHANGED, {Position::FIRST}); + syncDimension->add_child()->set_field(2 /* name field */); + + *config.add_predicate() = inBatterySaverModePredicate; + *config.add_predicate() = screenIsOffPredicate; + *config.add_predicate() = isSyncingPredicate; + auto combinationPredicate = config.add_predicate(); + combinationPredicate->set_id(987654); + combinationPredicate->mutable_combination()->set_operation(LogicalOperation::OR); + addPredicateToPredicateCombination(screenIsOffPredicate, combinationPredicate); + addPredicateToPredicateCombination(isSyncingPredicate, combinationPredicate); + + auto metric = config.add_duration_metric(); + metric->set_bucket(ONE_MINUTE); + metric->set_id(StringToId("BatterySaverModeDurationMetric")); + metric->set_what(inBatterySaverModePredicate.id()); + metric->set_condition(combinationPredicate->id()); + *metric->mutable_dimensions_in_condition() = CreateAttributionUidAndTagDimensions( + android::util::SYNC_STATE_CHANGED, {Position::FIRST}); + return config; +} + +} // namespace + + +TEST(DimensionInConditionE2eTest, TestDurationMetricNoLink) { + for (auto aggregationType : { DurationMetric::SUM, DurationMetric::MAX_SPARSE}) { + ConfigKey cfgKey; + auto config = CreateDurationMetricConfigNoLink(aggregationType); + int64_t bucketStartTimeNs = 10000000000; + int64_t bucketSizeNs = + TimeUnitToBucketSizeInMillis(config.duration_metric(0).bucket()) * 1000000LL; + + auto processor = CreateStatsLogProcessor(bucketStartTimeNs / NS_PER_SEC, config, cfgKey); + EXPECT_EQ(processor->mMetricsManagers.size(), 1u); + EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid()); + + std::vector attributions1 = + {CreateAttribution(111, "App1"), CreateAttribution(222, "GMSCoreModule1"), + CreateAttribution(222, "GMSCoreModule2")}; + + std::vector attributions2 = + {CreateAttribution(333, "App2"), CreateAttribution(222, "GMSCoreModule1"), + CreateAttribution(555, "GMSCoreModule2")}; + + std::vector> events; + + events.push_back(CreateBatterySaverOffEvent(bucketStartTimeNs + 1)); + events.push_back(CreateBatterySaverOnEvent(bucketStartTimeNs + 101)); + events.push_back(CreateBatterySaverOffEvent(bucketStartTimeNs + 110)); + + events.push_back(CreateBatterySaverOnEvent(bucketStartTimeNs + 201)); + events.push_back(CreateBatterySaverOffEvent(bucketStartTimeNs + 500)); + + events.push_back(CreateBatterySaverOnEvent(bucketStartTimeNs + 600)); + events.push_back(CreateBatterySaverOffEvent(bucketStartTimeNs + bucketSizeNs + 850)); + + events.push_back(CreateBatterySaverOnEvent(bucketStartTimeNs + bucketSizeNs + 870)); + events.push_back(CreateBatterySaverOffEvent(bucketStartTimeNs + bucketSizeNs + 900)); + + events.push_back(CreateScreenStateChangedEvent( + android::view::DISPLAY_STATE_ON, bucketStartTimeNs + 10)); + events.push_back(CreateScreenStateChangedEvent( + android::view::DISPLAY_STATE_OFF, bucketStartTimeNs + 100)); + events.push_back(CreateScreenStateChangedEvent( + android::view::DISPLAY_STATE_ON, bucketStartTimeNs + 202)); + events.push_back(CreateScreenStateChangedEvent( + android::view::DISPLAY_STATE_OFF, bucketStartTimeNs + bucketSizeNs + 800)); + + events.push_back(CreateSyncStartEvent(attributions1, "ReadEmail", bucketStartTimeNs + 200)); + events.push_back(CreateSyncEndEvent(attributions1, "ReadEmail", + bucketStartTimeNs + bucketSizeNs + 300)); + + events.push_back(CreateSyncStartEvent(attributions1, "ReadDoc", bucketStartTimeNs + 400)); + events.push_back(CreateSyncEndEvent(attributions1, "ReadDoc", + bucketStartTimeNs + bucketSizeNs - 1)); + + events.push_back(CreateSyncStartEvent(attributions2, "ReadEmail", bucketStartTimeNs + 401)); + events.push_back(CreateSyncEndEvent(attributions2, "ReadEmail", + bucketStartTimeNs + bucketSizeNs + 700)); + + sortLogEventsByTimestamp(&events); + + for (const auto& event : events) { + processor->OnLogEvent(event.get()); + } + + ConfigMetricsReportList reports; + processor->onDumpReport(cfgKey, bucketStartTimeNs + 2 * bucketSizeNs + 1, &reports); + + EXPECT_EQ(reports.reports_size(), 1); + EXPECT_EQ(reports.reports(0).metrics_size(), 1); + StatsLogReport::DurationMetricDataWrapper metrics; + sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).duration_metrics(), &metrics); + + EXPECT_EQ(metrics.data_size(), 3); + auto data = metrics.data(0); + EXPECT_FALSE(data.dimensions_in_what().has_field()); + EXPECT_FALSE(data.dimensions_in_condition().has_field()); + EXPECT_EQ(data.bucket_info_size(), 2); + EXPECT_EQ(data.bucket_info(0).duration_nanos(), 9); + EXPECT_EQ(data.bucket_info(0).start_bucket_nanos(), bucketStartTimeNs); + EXPECT_EQ(data.bucket_info(0).end_bucket_nanos(), bucketStartTimeNs + bucketSizeNs); + EXPECT_EQ(data.bucket_info(1).duration_nanos(), 30); + EXPECT_EQ(data.bucket_info(1).start_bucket_nanos(), bucketStartTimeNs + bucketSizeNs); + EXPECT_EQ(data.bucket_info(1).end_bucket_nanos(), bucketStartTimeNs + 2 * bucketSizeNs); + + data = metrics.data(1); + EXPECT_FALSE(data.dimensions_in_what().has_field()); + ValidateAttributionUidAndTagDimension( + data.dimensions_in_condition(), android::util::SYNC_STATE_CHANGED, 111, "App1"); + EXPECT_EQ(data.bucket_info_size(), 2); + EXPECT_EQ(data.bucket_info(0).duration_nanos(), 500 - 201 + bucketSizeNs - 600); + EXPECT_EQ(data.bucket_info(0).start_bucket_nanos(), bucketStartTimeNs); + EXPECT_EQ(data.bucket_info(0).end_bucket_nanos(), bucketStartTimeNs + bucketSizeNs); + EXPECT_EQ(data.bucket_info(1).duration_nanos(), 300); + EXPECT_EQ(data.bucket_info(1).start_bucket_nanos(), bucketStartTimeNs + bucketSizeNs); + EXPECT_EQ(data.bucket_info(1).end_bucket_nanos(), bucketStartTimeNs + 2 * bucketSizeNs); + + data = metrics.data(2); + EXPECT_FALSE(data.dimensions_in_what().has_field()); + ValidateAttributionUidAndTagDimension( + data.dimensions_in_condition(), android::util::SYNC_STATE_CHANGED, 333, "App2"); + EXPECT_EQ(data.bucket_info_size(), 2); + EXPECT_EQ(data.bucket_info(0).duration_nanos(), 500 - 401 + bucketSizeNs - 600); + EXPECT_EQ(data.bucket_info(0).start_bucket_nanos(), bucketStartTimeNs); + EXPECT_EQ(data.bucket_info(0).end_bucket_nanos(), bucketStartTimeNs + bucketSizeNs); + EXPECT_EQ(data.bucket_info(1).duration_nanos(), 700); + EXPECT_EQ(data.bucket_info(1).start_bucket_nanos(), bucketStartTimeNs + bucketSizeNs); + EXPECT_EQ(data.bucket_info(1).end_bucket_nanos(), bucketStartTimeNs + 2 * bucketSizeNs); + } +} + +namespace { + +StatsdConfig CreateDurationMetricConfigWithLink(DurationMetric::AggregationType aggregationType) { + StatsdConfig config; + *config.add_atom_matcher() = CreateMoveToBackgroundAtomMatcher(); + *config.add_atom_matcher() = CreateMoveToForegroundAtomMatcher(); + *config.add_atom_matcher() = CreateScreenTurnedOnAtomMatcher(); + *config.add_atom_matcher() = CreateScreenTurnedOffAtomMatcher(); + *config.add_atom_matcher() = CreateSyncStartAtomMatcher(); + *config.add_atom_matcher() = CreateSyncEndAtomMatcher(); + + auto screenIsOffPredicate = CreateScreenIsOffPredicate(); + auto isSyncingPredicate = CreateIsSyncingPredicate(); + auto syncDimension = isSyncingPredicate.mutable_simple_predicate()->mutable_dimensions(); + *syncDimension = CreateAttributionUidAndTagDimensions( + android::util::SYNC_STATE_CHANGED, {Position::FIRST}); + syncDimension->add_child()->set_field(2 /* name field */); + + auto isInBackgroundPredicate = CreateIsInBackgroundPredicate(); + *isInBackgroundPredicate.mutable_simple_predicate()->mutable_dimensions() = + CreateDimensions(android::util::ACTIVITY_FOREGROUND_STATE_CHANGED, {1 /* uid field */ }); + + *config.add_predicate() = screenIsOffPredicate; + *config.add_predicate() = isSyncingPredicate; + *config.add_predicate() = isInBackgroundPredicate; + auto combinationPredicate = config.add_predicate(); + combinationPredicate->set_id(987654); + combinationPredicate->mutable_combination()->set_operation(LogicalOperation::OR); + addPredicateToPredicateCombination(screenIsOffPredicate, combinationPredicate); + addPredicateToPredicateCombination(isSyncingPredicate, combinationPredicate); + + auto metric = config.add_duration_metric(); + metric->set_bucket(ONE_MINUTE); + metric->set_id(StringToId("AppInBackgroundMetric")); + metric->set_what(isInBackgroundPredicate.id()); + metric->set_condition(combinationPredicate->id()); + *metric->mutable_dimensions_in_what() = CreateDimensions( + android::util::ACTIVITY_FOREGROUND_STATE_CHANGED, {1 /* uid field */ }); + *metric->mutable_dimensions_in_condition() = CreateAttributionUidAndTagDimensions( + android::util::SYNC_STATE_CHANGED, {Position::FIRST}); + + // Links between crash atom and condition of app is in syncing. + auto links = metric->add_links(); + links->set_condition(isSyncingPredicate.id()); + auto dimensionWhat = links->mutable_fields_in_what(); + dimensionWhat->set_field(android::util::ACTIVITY_FOREGROUND_STATE_CHANGED); + dimensionWhat->add_child()->set_field(1); // uid field. + *links->mutable_fields_in_condition() = CreateAttributionUidDimensions( + android::util::SYNC_STATE_CHANGED, {Position::FIRST}); + return config; +} + +} // namespace + +TEST(DimensionInConditionE2eTest, TestDurationMetricWithLink) { + for (auto aggregationType : { DurationMetric::SUM, DurationMetric::MAX_SPARSE}) { + ConfigKey cfgKey; + auto config = CreateDurationMetricConfigWithLink(aggregationType); + int64_t bucketStartTimeNs = 10000000000; + int64_t bucketSizeNs = + TimeUnitToBucketSizeInMillis(config.duration_metric(0).bucket()) * 1000000LL; + + auto processor = CreateStatsLogProcessor(bucketStartTimeNs / NS_PER_SEC, config, cfgKey); + EXPECT_EQ(processor->mMetricsManagers.size(), 1u); + EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid()); + + std::vector attributions1 = + {CreateAttribution(111, "App1"), CreateAttribution(222, "GMSCoreModule1"), + CreateAttribution(222, "GMSCoreModule2")}; + + std::vector attributions2 = + {CreateAttribution(333, "App2"), CreateAttribution(222, "GMSCoreModule1"), + CreateAttribution(555, "GMSCoreModule2")}; + + std::vector> events; + + events.push_back(CreateMoveToBackgroundEvent(111, bucketStartTimeNs + 101)); + events.push_back(CreateMoveToForegroundEvent(111, bucketStartTimeNs + 110)); + + events.push_back(CreateMoveToBackgroundEvent(111, bucketStartTimeNs + 201)); + events.push_back(CreateMoveToForegroundEvent(111, bucketStartTimeNs + bucketSizeNs + 100)); + + events.push_back(CreateMoveToBackgroundEvent(333, bucketStartTimeNs + 399)); + events.push_back(CreateMoveToForegroundEvent(333, bucketStartTimeNs + bucketSizeNs + 800)); + + events.push_back(CreateScreenStateChangedEvent( + android::view::DISPLAY_STATE_ON, bucketStartTimeNs + 10)); + events.push_back(CreateScreenStateChangedEvent( + android::view::DISPLAY_STATE_OFF, bucketStartTimeNs + 100)); + events.push_back(CreateScreenStateChangedEvent( + android::view::DISPLAY_STATE_ON, bucketStartTimeNs + 202)); + events.push_back(CreateScreenStateChangedEvent( + android::view::DISPLAY_STATE_OFF, bucketStartTimeNs + bucketSizeNs + 801)); + + events.push_back(CreateSyncStartEvent(attributions1, "ReadEmail", bucketStartTimeNs + 200)); + events.push_back(CreateSyncEndEvent(attributions1, "ReadEmail", + bucketStartTimeNs + bucketSizeNs + 300)); + + events.push_back(CreateSyncStartEvent(attributions1, "ReadDoc", bucketStartTimeNs + 400)); + events.push_back(CreateSyncEndEvent(attributions1, "ReadDoc", + bucketStartTimeNs + bucketSizeNs - 1)); + + events.push_back(CreateSyncStartEvent(attributions2, "ReadEmail", bucketStartTimeNs + 401)); + events.push_back(CreateSyncEndEvent(attributions2, "ReadEmail", + bucketStartTimeNs + bucketSizeNs + 700)); + + sortLogEventsByTimestamp(&events); + + for (const auto& event : events) { + processor->OnLogEvent(event.get()); + } + + ConfigMetricsReportList reports; + processor->onDumpReport(cfgKey, bucketStartTimeNs + 2 * bucketSizeNs + 1, &reports); + + EXPECT_EQ(reports.reports_size(), 1); + EXPECT_EQ(reports.reports(0).metrics_size(), 1); + StatsLogReport::DurationMetricDataWrapper metrics; + sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).duration_metrics(), &metrics); + + EXPECT_EQ(metrics.data_size(), 3); + auto data = metrics.data(0); + EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(), 1); + EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).value_int(), 111); + EXPECT_FALSE(data.dimensions_in_condition().has_field()); + EXPECT_EQ(data.bucket_info_size(), 1); + EXPECT_EQ(data.bucket_info(0).duration_nanos(), 9); + EXPECT_EQ(data.bucket_info(0).start_bucket_nanos(), bucketStartTimeNs); + EXPECT_EQ(data.bucket_info(0).end_bucket_nanos(), bucketStartTimeNs + bucketSizeNs); + + data = metrics.data(1); + EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(), 1); + EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).value_int(), 111); + ValidateAttributionUidAndTagDimension( + data.dimensions_in_condition(), android::util::SYNC_STATE_CHANGED, 111, "App1"); + EXPECT_EQ(data.bucket_info_size(), 2); + EXPECT_EQ(data.bucket_info(0).duration_nanos(), bucketSizeNs - 201); + EXPECT_EQ(data.bucket_info(0).start_bucket_nanos(), bucketStartTimeNs); + EXPECT_EQ(data.bucket_info(0).end_bucket_nanos(), bucketStartTimeNs + bucketSizeNs); + EXPECT_EQ(data.bucket_info(1).duration_nanos(), 100); + EXPECT_EQ(data.bucket_info(1).start_bucket_nanos(), bucketStartTimeNs + bucketSizeNs); + EXPECT_EQ(data.bucket_info(1).end_bucket_nanos(), bucketStartTimeNs + 2 * bucketSizeNs); + + data = metrics.data(2); + EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(), 1); + EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).value_int(), 333); + ValidateAttributionUidAndTagDimension( + data.dimensions_in_condition(), android::util::SYNC_STATE_CHANGED, 333, "App2"); + EXPECT_EQ(data.bucket_info_size(), 2); + EXPECT_EQ(data.bucket_info(0).duration_nanos(), bucketSizeNs - 401); + EXPECT_EQ(data.bucket_info(0).start_bucket_nanos(), bucketStartTimeNs); + EXPECT_EQ(data.bucket_info(0).end_bucket_nanos(), bucketStartTimeNs + bucketSizeNs); + EXPECT_EQ(data.bucket_info(1).duration_nanos(), 700); + EXPECT_EQ(data.bucket_info(1).start_bucket_nanos(), bucketStartTimeNs + bucketSizeNs); + EXPECT_EQ(data.bucket_info(1).end_bucket_nanos(), bucketStartTimeNs + 2 * bucketSizeNs); + } +} + +#else +GTEST_LOG_(INFO) << "This test does nothing.\n"; +#endif + +} // namespace statsd +} // namespace os +} // namespace android \ No newline at end of file diff --git a/cmds/statsd/tests/e2e/MetricConditionLink_e2e_test.cpp b/cmds/statsd/tests/e2e/MetricConditionLink_e2e_test.cpp index 4504a95c8ef01..233031c5c8da4 100644 --- a/cmds/statsd/tests/e2e/MetricConditionLink_e2e_test.cpp +++ b/cmds/statsd/tests/e2e/MetricConditionLink_e2e_test.cpp @@ -44,9 +44,10 @@ StatsdConfig CreateStatsdConfig() { auto screenIsOffPredicate = CreateScreenIsOffPredicate(); auto isSyncingPredicate = CreateIsSyncingPredicate(); - *isSyncingPredicate.mutable_simple_predicate()->mutable_dimensions() = - CreateDimensions( - android::util::SYNC_STATE_CHANGED, {1 /* uid field */, 2 /* name field*/}); + auto syncDimension = isSyncingPredicate.mutable_simple_predicate()->mutable_dimensions(); + *syncDimension = CreateAttributionUidDimensions( + android::util::SYNC_STATE_CHANGED, {Position::FIRST}); + syncDimension->add_child()->set_field(2 /* name field*/); auto isInBackgroundPredicate = CreateIsInBackgroundPredicate(); *isInBackgroundPredicate.mutable_simple_predicate()->mutable_dimensions() = @@ -78,9 +79,8 @@ StatsdConfig CreateStatsdConfig() { auto dimensionWhat = links->mutable_fields_in_what(); dimensionWhat->set_field(android::util::PROCESS_LIFE_CYCLE_STATE_CHANGED); dimensionWhat->add_child()->set_field(1); // uid field. - auto dimensionCondition = links->mutable_fields_in_condition(); - dimensionCondition->set_field(android::util::SYNC_STATE_CHANGED); - dimensionCondition->add_child()->set_field(1); // uid field. + *links->mutable_fields_in_condition() = CreateAttributionUidDimensions( + android::util::SYNC_STATE_CHANGED, {Position::FIRST}); // Links between crash atom and condition of app is in background. links = countMetric->add_links(); @@ -88,7 +88,7 @@ StatsdConfig CreateStatsdConfig() { dimensionWhat = links->mutable_fields_in_what(); dimensionWhat->set_field(android::util::PROCESS_LIFE_CYCLE_STATE_CHANGED); dimensionWhat->add_child()->set_field(1); // uid field. - dimensionCondition = links->mutable_fields_in_condition(); + auto dimensionCondition = links->mutable_fields_in_condition(); dimensionCondition->set_field(android::util::ACTIVITY_FOREGROUND_STATE_CHANGED); dimensionCondition->add_child()->set_field(1); // uid field. return config; @@ -132,12 +132,14 @@ TEST(MetricConditionLinkE2eTest, TestMultiplePredicatesAndLinks) { CreateScreenStateChangedEvent(android::view::DisplayStateEnum::DISPLAY_STATE_ON, bucketStartTimeNs + 2 * bucketSizeNs - 100); + std::vector attributions = + {CreateAttribution(appUid, "App1"), CreateAttribution(appUid + 1, "GMSCoreModule1")}; auto syncOnEvent1 = - CreateSyncStartEvent(appUid, "ReadEmail", bucketStartTimeNs + 50); + CreateSyncStartEvent(attributions, "ReadEmail", bucketStartTimeNs + 50); auto syncOffEvent1 = - CreateSyncEndEvent(appUid, "ReadEmail", bucketStartTimeNs + bucketSizeNs + 300); + CreateSyncEndEvent(attributions, "ReadEmail", bucketStartTimeNs + bucketSizeNs + 300); auto syncOnEvent2 = - CreateSyncStartEvent(appUid, "ReadDoc", bucketStartTimeNs + bucketSizeNs + 2000); + CreateSyncStartEvent(attributions, "ReadDoc", bucketStartTimeNs + bucketSizeNs + 2000); auto moveToBackgroundEvent1 = CreateMoveToBackgroundEvent(appUid, bucketStartTimeNs + 15); diff --git a/cmds/statsd/tests/metrics/CountMetricProducer_test.cpp b/cmds/statsd/tests/metrics/CountMetricProducer_test.cpp index 4ad209712905e..50b3532e827a6 100644 --- a/cmds/statsd/tests/metrics/CountMetricProducer_test.cpp +++ b/cmds/statsd/tests/metrics/CountMetricProducer_test.cpp @@ -67,9 +67,9 @@ TEST(CountMetricProducerTest, TestNonDimensionalEvents) { // Flushes. countProducer.flushIfNeededLocked(bucketStartTimeNs + bucketSizeNs + 1); EXPECT_EQ(1UL, countProducer.mPastBuckets.size()); - EXPECT_TRUE(countProducer.mPastBuckets.find(DEFAULT_DIMENSION_KEY) != + EXPECT_TRUE(countProducer.mPastBuckets.find(DEFAULT_METRIC_DIMENSION_KEY) != countProducer.mPastBuckets.end()); - const auto& buckets = countProducer.mPastBuckets[DEFAULT_DIMENSION_KEY]; + const auto& buckets = countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY]; EXPECT_EQ(1UL, buckets.size()); EXPECT_EQ(bucketStartTimeNs, buckets[0].mBucketStartNs); EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, buckets[0].mBucketEndNs); @@ -80,10 +80,10 @@ TEST(CountMetricProducerTest, TestNonDimensionalEvents) { countProducer.onMatchedLogEvent(1 /*log matcher index*/, event3); countProducer.flushIfNeededLocked(bucketStartTimeNs + 2 * bucketSizeNs + 1); EXPECT_EQ(1UL, countProducer.mPastBuckets.size()); - EXPECT_TRUE(countProducer.mPastBuckets.find(DEFAULT_DIMENSION_KEY) != + EXPECT_TRUE(countProducer.mPastBuckets.find(DEFAULT_METRIC_DIMENSION_KEY) != countProducer.mPastBuckets.end()); - EXPECT_EQ(2UL, countProducer.mPastBuckets[DEFAULT_DIMENSION_KEY].size()); - const auto& bucketInfo2 = countProducer.mPastBuckets[DEFAULT_DIMENSION_KEY][1]; + EXPECT_EQ(2UL, countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size()); + const auto& bucketInfo2 = countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY][1]; EXPECT_EQ(bucket2StartTimeNs, bucketInfo2.mBucketStartNs); EXPECT_EQ(bucket2StartTimeNs + bucketSizeNs, bucketInfo2.mBucketEndNs); EXPECT_EQ(1LL, bucketInfo2.mCount); @@ -91,9 +91,9 @@ TEST(CountMetricProducerTest, TestNonDimensionalEvents) { // nothing happens in bucket 3. we should not record anything for bucket 3. countProducer.flushIfNeededLocked(bucketStartTimeNs + 3 * bucketSizeNs + 1); EXPECT_EQ(1UL, countProducer.mPastBuckets.size()); - EXPECT_TRUE(countProducer.mPastBuckets.find(DEFAULT_DIMENSION_KEY) != + EXPECT_TRUE(countProducer.mPastBuckets.find(DEFAULT_METRIC_DIMENSION_KEY) != countProducer.mPastBuckets.end()); - const auto& buckets3 = countProducer.mPastBuckets[DEFAULT_DIMENSION_KEY]; + const auto& buckets3 = countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY]; EXPECT_EQ(2UL, buckets3.size()); } @@ -124,10 +124,10 @@ TEST(CountMetricProducerTest, TestEventsWithNonSlicedCondition) { countProducer.flushIfNeededLocked(bucketStartTimeNs + bucketSizeNs + 1); EXPECT_EQ(1UL, countProducer.mPastBuckets.size()); - EXPECT_TRUE(countProducer.mPastBuckets.find(DEFAULT_DIMENSION_KEY) != + EXPECT_TRUE(countProducer.mPastBuckets.find(DEFAULT_METRIC_DIMENSION_KEY) != countProducer.mPastBuckets.end()); { - const auto& buckets = countProducer.mPastBuckets[DEFAULT_DIMENSION_KEY]; + const auto& buckets = countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY]; EXPECT_EQ(1UL, buckets.size()); const auto& bucketInfo = buckets[0]; EXPECT_EQ(bucketStartTimeNs, bucketInfo.mBucketStartNs); @@ -167,9 +167,9 @@ TEST(CountMetricProducerTest, TestEventsWithSlicedCondition) { {getMockedDimensionKey(conditionTagId, 2, "222")}; sp wizard = new NaggyMock(); - EXPECT_CALL(*wizard, query(_, key1)).WillOnce(Return(ConditionState::kFalse)); + EXPECT_CALL(*wizard, query(_, key1, _, _)).WillOnce(Return(ConditionState::kFalse)); - EXPECT_CALL(*wizard, query(_, key2)).WillOnce(Return(ConditionState::kTrue)); + EXPECT_CALL(*wizard, query(_, key2, _, _)).WillOnce(Return(ConditionState::kTrue)); CountMetricProducer countProducer(kConfigKey, metric, 1 /*condition tracker index*/, wizard, bucketStartTimeNs); @@ -181,9 +181,9 @@ TEST(CountMetricProducerTest, TestEventsWithSlicedCondition) { countProducer.onMatchedLogEvent(1 /*log matcher index*/, event2); countProducer.flushIfNeededLocked(bucketStartTimeNs + bucketSizeNs + 1); EXPECT_EQ(1UL, countProducer.mPastBuckets.size()); - EXPECT_TRUE(countProducer.mPastBuckets.find(DEFAULT_DIMENSION_KEY) != + EXPECT_TRUE(countProducer.mPastBuckets.find(DEFAULT_METRIC_DIMENSION_KEY) != countProducer.mPastBuckets.end()); - const auto& buckets = countProducer.mPastBuckets[DEFAULT_DIMENSION_KEY]; + const auto& buckets = countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY]; EXPECT_EQ(1UL, buckets.size()); const auto& bucketInfo = buckets[0]; EXPECT_EQ(bucketStartTimeNs, bucketInfo.mBucketStartNs); @@ -229,13 +229,13 @@ TEST(CountMetricProducerTest, TestAnomalyDetectionUnSliced) { EXPECT_EQ(1UL, countProducer.mCurrentSlicedCounter->size()); EXPECT_EQ(2L, countProducer.mCurrentSlicedCounter->begin()->second); - EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(DEFAULT_DIMENSION_KEY), 0U); + EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(DEFAULT_METRIC_DIMENSION_KEY), 0U); // One event in bucket #2. No alarm as bucket #0 is trashed out. countProducer.onMatchedLogEvent(1 /*log matcher index*/, event3); EXPECT_EQ(1UL, countProducer.mCurrentSlicedCounter->size()); EXPECT_EQ(1L, countProducer.mCurrentSlicedCounter->begin()->second); - EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(DEFAULT_DIMENSION_KEY), 0U); + EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(DEFAULT_METRIC_DIMENSION_KEY), 0U); // Two events in bucket #3. countProducer.onMatchedLogEvent(1 /*log matcher index*/, event4); @@ -244,13 +244,13 @@ TEST(CountMetricProducerTest, TestAnomalyDetectionUnSliced) { EXPECT_EQ(1UL, countProducer.mCurrentSlicedCounter->size()); EXPECT_EQ(3L, countProducer.mCurrentSlicedCounter->begin()->second); // Anomaly at event 6 is within refractory period. The alarm is at event 5 timestamp not event 6 - EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(DEFAULT_DIMENSION_KEY), + EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(DEFAULT_METRIC_DIMENSION_KEY), event5.GetTimestampNs() / NS_PER_SEC + refPeriodSec); countProducer.onMatchedLogEvent(1 /*log matcher index*/, event7); EXPECT_EQ(1UL, countProducer.mCurrentSlicedCounter->size()); EXPECT_EQ(4L, countProducer.mCurrentSlicedCounter->begin()->second); - EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(DEFAULT_DIMENSION_KEY), + EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(DEFAULT_METRIC_DIMENSION_KEY), event7.GetTimestampNs() / NS_PER_SEC + refPeriodSec); } diff --git a/cmds/statsd/tests/metrics/DurationMetricProducer_test.cpp b/cmds/statsd/tests/metrics/DurationMetricProducer_test.cpp index a59f1fe663545..c9fe2523c577d 100644 --- a/cmds/statsd/tests/metrics/DurationMetricProducer_test.cpp +++ b/cmds/statsd/tests/metrics/DurationMetricProducer_test.cpp @@ -62,9 +62,9 @@ TEST(DurationMetricTrackerTest, TestNoCondition) { durationProducer.onMatchedLogEvent(2 /* stop index*/, event2); durationProducer.flushIfNeededLocked(bucketStartTimeNs + 2 * bucketSizeNs + 1); EXPECT_EQ(1UL, durationProducer.mPastBuckets.size()); - EXPECT_TRUE(durationProducer.mPastBuckets.find(DEFAULT_DIMENSION_KEY) != + EXPECT_TRUE(durationProducer.mPastBuckets.find(DEFAULT_METRIC_DIMENSION_KEY) != durationProducer.mPastBuckets.end()); - const auto& buckets = durationProducer.mPastBuckets[DEFAULT_DIMENSION_KEY]; + const auto& buckets = durationProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY]; EXPECT_EQ(2UL, buckets.size()); EXPECT_EQ(bucketStartTimeNs, buckets[0].mBucketStartNs); EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, buckets[0].mBucketEndNs); @@ -107,9 +107,9 @@ TEST(DurationMetricTrackerTest, TestNonSlicedCondition) { durationProducer.onMatchedLogEvent(2 /* stop index*/, event4); durationProducer.flushIfNeededLocked(bucketStartTimeNs + 2 * bucketSizeNs + 1); EXPECT_EQ(1UL, durationProducer.mPastBuckets.size()); - EXPECT_TRUE(durationProducer.mPastBuckets.find(DEFAULT_DIMENSION_KEY) != + EXPECT_TRUE(durationProducer.mPastBuckets.find(DEFAULT_METRIC_DIMENSION_KEY) != durationProducer.mPastBuckets.end()); - const auto& buckets2 = durationProducer.mPastBuckets[DEFAULT_DIMENSION_KEY]; + const auto& buckets2 = durationProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY]; EXPECT_EQ(1UL, buckets2.size()); EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, buckets2[0].mBucketStartNs); EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs, buckets2[0].mBucketEndNs); diff --git a/cmds/statsd/tests/metrics/EventMetricProducer_test.cpp b/cmds/statsd/tests/metrics/EventMetricProducer_test.cpp index da00cae125c71..3deab3710dd10 100644 --- a/cmds/statsd/tests/metrics/EventMetricProducer_test.cpp +++ b/cmds/statsd/tests/metrics/EventMetricProducer_test.cpp @@ -114,9 +114,9 @@ TEST(EventMetricProducerTest, TestEventsWithSlicedCondition) { key2[StringToId("APP_IN_BACKGROUND_PER_UID")] = {getMockedDimensionKey(conditionTagId, 2, "222")}; sp wizard = new NaggyMock(); - EXPECT_CALL(*wizard, query(_, key1)).WillOnce(Return(ConditionState::kFalse)); + EXPECT_CALL(*wizard, query(_, key1, _, _)).WillOnce(Return(ConditionState::kFalse)); - EXPECT_CALL(*wizard, query(_, key2)).WillOnce(Return(ConditionState::kTrue)); + EXPECT_CALL(*wizard, query(_, key2, _, _)).WillOnce(Return(ConditionState::kTrue)); EventMetricProducer eventProducer(kConfigKey, metric, 1, wizard, bucketStartTimeNs); diff --git a/cmds/statsd/tests/metrics/GaugeMetricProducer_test.cpp b/cmds/statsd/tests/metrics/GaugeMetricProducer_test.cpp index 4533ac610057b..58be5b071b531 100644 --- a/cmds/statsd/tests/metrics/GaugeMetricProducer_test.cpp +++ b/cmds/statsd/tests/metrics/GaugeMetricProducer_test.cpp @@ -218,7 +218,7 @@ TEST(GaugeMetricProducerTest, TestAnomalyDetection) { EXPECT_EQ(13L, gaugeProducer.mCurrentSlicedBucket->begin()-> second.front().mFields->begin()->second.value_int()); - EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(DEFAULT_DIMENSION_KEY), 0U); + EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(DEFAULT_METRIC_DIMENSION_KEY), 0U); std::shared_ptr event2 = std::make_shared(tagId, bucketStartTimeNs + bucketSizeNs + 20); @@ -231,7 +231,7 @@ TEST(GaugeMetricProducerTest, TestAnomalyDetection) { EXPECT_EQ(15L, gaugeProducer.mCurrentSlicedBucket->begin()-> second.front().mFields->begin()->second.value_int()); - EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(DEFAULT_DIMENSION_KEY), + EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(DEFAULT_METRIC_DIMENSION_KEY), event2->GetTimestampNs() / NS_PER_SEC + refPeriodSec); std::shared_ptr event3 = @@ -245,7 +245,7 @@ TEST(GaugeMetricProducerTest, TestAnomalyDetection) { EXPECT_EQ(26L, gaugeProducer.mCurrentSlicedBucket->begin()-> second.front().mFields->begin()->second.value_int()); - EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(DEFAULT_DIMENSION_KEY), + EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(DEFAULT_METRIC_DIMENSION_KEY), event2->GetTimestampNs() / NS_PER_SEC + refPeriodSec); // The event4 does not have the gauge field. Thus the current bucket value is 0. diff --git a/cmds/statsd/tests/metrics/MaxDurationTracker_test.cpp b/cmds/statsd/tests/metrics/MaxDurationTracker_test.cpp index 0772b0d4001df..203f028080d2b 100644 --- a/cmds/statsd/tests/metrics/MaxDurationTracker_test.cpp +++ b/cmds/statsd/tests/metrics/MaxDurationTracker_test.cpp @@ -41,22 +41,24 @@ const ConfigKey kConfigKey(0, 12345); const int TagId = 1; -const HashableDimensionKey eventKey = getMockedDimensionKey(TagId, 0, "1"); -const std::vector conditionKey = {getMockedDimensionKey(TagId, 4, "1")}; -const HashableDimensionKey key1 = getMockedDimensionKey(TagId, 1, "1"); -const HashableDimensionKey key2 = getMockedDimensionKey(TagId, 1, "2"); TEST(MaxDurationTrackerTest, TestSimpleMaxDuration) { + const MetricDimensionKey eventKey = getMockedMetricDimensionKey(TagId, 0, "1"); + const std::vector conditionKey = {getMockedDimensionKey(TagId, 4, "1")}; + const HashableDimensionKey key1 = getMockedDimensionKey(TagId, 1, "1"); + const HashableDimensionKey key2 = getMockedDimensionKey(TagId, 1, "2"); + + FieldMatcher dimensionInCondition; sp wizard = new NaggyMock(); - unordered_map> buckets; + unordered_map> buckets; uint64_t bucketStartTimeNs = 10000000000; uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL; int64_t metricId = 1; - MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, -1, false, bucketStartTimeNs, - bucketSizeNs, false, {}); + MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, -1, dimensionInCondition, + false, bucketStartTimeNs, bucketSizeNs, false, {}); tracker.noteStart(key1, true, bucketStartTimeNs, ConditionKey()); // Event starts again. This would not change anything as it already starts. @@ -75,16 +77,22 @@ TEST(MaxDurationTrackerTest, TestSimpleMaxDuration) { } TEST(MaxDurationTrackerTest, TestStopAll) { + const MetricDimensionKey eventKey = getMockedMetricDimensionKey(TagId, 0, "1"); + const std::vector conditionKey = {getMockedDimensionKey(TagId, 4, "1")}; + const HashableDimensionKey key1 = getMockedDimensionKey(TagId, 1, "1"); + const HashableDimensionKey key2 = getMockedDimensionKey(TagId, 1, "2"); + + FieldMatcher dimensionInCondition; sp wizard = new NaggyMock(); - unordered_map> buckets; + unordered_map> buckets; uint64_t bucketStartTimeNs = 10000000000; uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL; int64_t metricId = 1; - MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, -1, false, bucketStartTimeNs, - bucketSizeNs, false, {}); + MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, -1, dimensionInCondition, + false, bucketStartTimeNs, bucketSizeNs, false, {}); tracker.noteStart(key1, true, bucketStartTimeNs + 1, ConditionKey()); @@ -105,21 +113,26 @@ TEST(MaxDurationTrackerTest, TestStopAll) { } TEST(MaxDurationTrackerTest, TestCrossBucketBoundary) { + const MetricDimensionKey eventKey = getMockedMetricDimensionKey(TagId, 0, "1"); + const std::vector conditionKey = {getMockedDimensionKey(TagId, 4, "1")}; + const HashableDimensionKey key1 = getMockedDimensionKey(TagId, 1, "1"); + const HashableDimensionKey key2 = getMockedDimensionKey(TagId, 1, "2"); + FieldMatcher dimensionInCondition; sp wizard = new NaggyMock(); - unordered_map> buckets; + unordered_map> buckets; uint64_t bucketStartTimeNs = 10000000000; uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL; int64_t metricId = 1; - MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, -1, false, bucketStartTimeNs, - bucketSizeNs, false, {}); + MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, -1, dimensionInCondition, + false, bucketStartTimeNs, bucketSizeNs, false, {}); // The event starts. tracker.noteStart(DEFAULT_DIMENSION_KEY, true, bucketStartTimeNs + 1, ConditionKey()); - // Starts again. Does not change anything. + // Starts again. Does not DEFAULT_DIMENSION_KEY anything. tracker.noteStart(DEFAULT_DIMENSION_KEY, true, bucketStartTimeNs + bucketSizeNs + 1, ConditionKey()); @@ -135,16 +148,21 @@ TEST(MaxDurationTrackerTest, TestCrossBucketBoundary) { } TEST(MaxDurationTrackerTest, TestCrossBucketBoundary_nested) { + const MetricDimensionKey eventKey = getMockedMetricDimensionKey(TagId, 0, "1"); + const std::vector conditionKey = {getMockedDimensionKey(TagId, 4, "1")}; + const HashableDimensionKey key1 = getMockedDimensionKey(TagId, 1, "1"); + const HashableDimensionKey key2 = getMockedDimensionKey(TagId, 1, "2"); + FieldMatcher dimensionInCondition; sp wizard = new NaggyMock(); - unordered_map> buckets; + unordered_map> buckets; uint64_t bucketStartTimeNs = 10000000000; uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL; int64_t metricId = 1; - MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, -1, true, bucketStartTimeNs, - bucketSizeNs, false, {}); + MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, -1, dimensionInCondition, + true, bucketStartTimeNs, bucketSizeNs, false, {}); // 2 starts tracker.noteStart(DEFAULT_DIMENSION_KEY, true, bucketStartTimeNs + 1, ConditionKey()); @@ -160,7 +178,8 @@ TEST(MaxDurationTrackerTest, TestCrossBucketBoundary_nested) { EXPECT_EQ(bucketSizeNs, buckets[eventKey][1].mDuration); // real stop now. - tracker.noteStop(DEFAULT_DIMENSION_KEY, bucketStartTimeNs + (2 * bucketSizeNs) + 5, false); + tracker.noteStop(DEFAULT_DIMENSION_KEY, + bucketStartTimeNs + (2 * bucketSizeNs) + 5, false); tracker.flushIfNeeded(bucketStartTimeNs + (3 * bucketSizeNs) + 1, &buckets); EXPECT_EQ(3u, buckets[eventKey].size()); @@ -170,16 +189,20 @@ TEST(MaxDurationTrackerTest, TestCrossBucketBoundary_nested) { } TEST(MaxDurationTrackerTest, TestMaxDurationWithCondition) { + const std::vector conditionKey = {getMockedDimensionKey(TagId, 4, "1")}; + const HashableDimensionKey key1 = getMockedDimensionKey(TagId, 1, "1"); + + FieldMatcher dimensionInCondition; sp wizard = new NaggyMock(); ConditionKey conditionKey1; - HashableDimensionKey eventKey = getMockedDimensionKey(TagId, 2, "maps"); + MetricDimensionKey eventKey = getMockedMetricDimensionKey(TagId, 2, "maps"); conditionKey1[StringToId("APP_BACKGROUND")] = conditionKey; - EXPECT_CALL(*wizard, query(_, conditionKey1)) // #4 + EXPECT_CALL(*wizard, query(_, conditionKey1, _, _)) // #4 .WillOnce(Return(ConditionState::kFalse)); - unordered_map> buckets; + unordered_map> buckets; uint64_t bucketStartTimeNs = 10000000000; uint64_t eventStartTimeNs = bucketStartTimeNs + 1; @@ -187,8 +210,8 @@ TEST(MaxDurationTrackerTest, TestMaxDurationWithCondition) { int64_t durationTimeNs = 2 * 1000; int64_t metricId = 1; - MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, false, bucketStartTimeNs, - bucketSizeNs, true, {}); + MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, dimensionInCondition, + false, bucketStartTimeNs, bucketSizeNs, true, {}); EXPECT_TRUE(tracker.mAnomalyTrackers.empty()); tracker.noteStart(key1, true, eventStartTimeNs, conditionKey1); @@ -204,6 +227,10 @@ TEST(MaxDurationTrackerTest, TestMaxDurationWithCondition) { } TEST(MaxDurationTrackerTest, TestAnomalyDetection) { + const MetricDimensionKey eventKey = getMockedMetricDimensionKey(TagId, 0, "1"); + const HashableDimensionKey key1 = getMockedDimensionKey(TagId, 1, "1"); + const HashableDimensionKey key2 = getMockedDimensionKey(TagId, 1, "2"); + FieldMatcher dimensionInCondition; int64_t metricId = 1; Alert alert; alert.set_id(101); @@ -213,7 +240,7 @@ TEST(MaxDurationTrackerTest, TestAnomalyDetection) { const int32_t refPeriodSec = 1; alert.set_refractory_period_secs(refPeriodSec); - unordered_map> buckets; + unordered_map> buckets; sp wizard = new NaggyMock(); uint64_t bucketStartTimeNs = 10 * NS_PER_SEC; @@ -221,8 +248,8 @@ TEST(MaxDurationTrackerTest, TestAnomalyDetection) { uint64_t bucketSizeNs = 30 * NS_PER_SEC; sp anomalyTracker = new DurationAnomalyTracker(alert, kConfigKey); - MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, -1, true, bucketStartTimeNs, - bucketSizeNs, false, {anomalyTracker}); + MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, -1, dimensionInCondition, + true, bucketStartTimeNs, bucketSizeNs, false, {anomalyTracker}); tracker.noteStart(key1, true, eventStartTimeNs, ConditionKey()); tracker.noteStop(key1, eventStartTimeNs + 10, false); diff --git a/cmds/statsd/tests/metrics/OringDurationTracker_test.cpp b/cmds/statsd/tests/metrics/OringDurationTracker_test.cpp index 6b8893e5973f9..80e16a1f3b555 100644 --- a/cmds/statsd/tests/metrics/OringDurationTracker_test.cpp +++ b/cmds/statsd/tests/metrics/OringDurationTracker_test.cpp @@ -38,24 +38,26 @@ namespace statsd { const ConfigKey kConfigKey(0, 12345); const int TagId = 1; const int64_t metricId = 123; -const HashableDimensionKey eventKey = getMockedDimensionKey(TagId, 0, "event"); - -const std::vector kConditionKey1 = {getMockedDimensionKey(TagId, 1, "maps")}; -const HashableDimensionKey kEventKey1 = getMockedDimensionKey(TagId, 2, "maps"); -const HashableDimensionKey kEventKey2 = getMockedDimensionKey(TagId, 3, "maps"); TEST(OringDurationTrackerTest, TestDurationOverlap) { + const MetricDimensionKey eventKey = getMockedMetricDimensionKey(TagId, 0, "event"); + + const std::vector kConditionKey1 = + {getMockedDimensionKey(TagId, 1, "maps")}; + const HashableDimensionKey kEventKey1 = getMockedDimensionKey(TagId, 2, "maps"); + const HashableDimensionKey kEventKey2 = getMockedDimensionKey(TagId, 3, "maps"); + FieldMatcher dimensionInCondition; sp wizard = new NaggyMock(); - unordered_map> buckets; + unordered_map> buckets; uint64_t bucketStartTimeNs = 10000000000; uint64_t eventStartTimeNs = bucketStartTimeNs + 1; uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL; uint64_t durationTimeNs = 2 * 1000; - OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, false, - bucketStartTimeNs, bucketSizeNs, false, {}); + OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, dimensionInCondition, + false, bucketStartTimeNs, bucketSizeNs, false, {}); tracker.noteStart(kEventKey1, true, eventStartTimeNs, ConditionKey()); EXPECT_EQ((long long)eventStartTimeNs, tracker.mLastStartTime); @@ -71,16 +73,23 @@ TEST(OringDurationTrackerTest, TestDurationOverlap) { } TEST(OringDurationTrackerTest, TestDurationNested) { + const MetricDimensionKey eventKey = getMockedMetricDimensionKey(TagId, 0, "event"); + + const std::vector kConditionKey1 = + {getMockedDimensionKey(TagId, 1, "maps")}; + const HashableDimensionKey kEventKey1 = getMockedDimensionKey(TagId, 2, "maps"); + const HashableDimensionKey kEventKey2 = getMockedDimensionKey(TagId, 3, "maps"); + FieldMatcher dimensionInCondition; sp wizard = new NaggyMock(); - unordered_map> buckets; + unordered_map> buckets; uint64_t bucketStartTimeNs = 10000000000; uint64_t eventStartTimeNs = bucketStartTimeNs + 1; uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL; - OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, true, bucketStartTimeNs, - bucketSizeNs, false, {}); + OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, dimensionInCondition, + true, bucketStartTimeNs, bucketSizeNs, false, {}); tracker.noteStart(kEventKey1, true, eventStartTimeNs, ConditionKey()); tracker.noteStart(kEventKey1, true, eventStartTimeNs + 10, ConditionKey()); // overlapping wl @@ -95,16 +104,23 @@ TEST(OringDurationTrackerTest, TestDurationNested) { } TEST(OringDurationTrackerTest, TestStopAll) { + const MetricDimensionKey eventKey = getMockedMetricDimensionKey(TagId, 0, "event"); + + const std::vector kConditionKey1 = + {getMockedDimensionKey(TagId, 1, "maps")}; + const HashableDimensionKey kEventKey1 = getMockedDimensionKey(TagId, 2, "maps"); + const HashableDimensionKey kEventKey2 = getMockedDimensionKey(TagId, 3, "maps"); + FieldMatcher dimensionInCondition; sp wizard = new NaggyMock(); - unordered_map> buckets; + unordered_map> buckets; uint64_t bucketStartTimeNs = 10000000000; uint64_t eventStartTimeNs = bucketStartTimeNs + 1; uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL; - OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, true, bucketStartTimeNs, - bucketSizeNs, false, {}); + OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, dimensionInCondition, + true, bucketStartTimeNs, bucketSizeNs, false, {}); tracker.noteStart(kEventKey1, true, eventStartTimeNs, ConditionKey()); tracker.noteStart(kEventKey2, true, eventStartTimeNs + 10, ConditionKey()); // overlapping wl @@ -118,17 +134,24 @@ TEST(OringDurationTrackerTest, TestStopAll) { } TEST(OringDurationTrackerTest, TestCrossBucketBoundary) { + const MetricDimensionKey eventKey = getMockedMetricDimensionKey(TagId, 0, "event"); + + const std::vector kConditionKey1 = + {getMockedDimensionKey(TagId, 1, "maps")}; + const HashableDimensionKey kEventKey1 = getMockedDimensionKey(TagId, 2, "maps"); + const HashableDimensionKey kEventKey2 = getMockedDimensionKey(TagId, 3, "maps"); + FieldMatcher dimensionInCondition; sp wizard = new NaggyMock(); - unordered_map> buckets; + unordered_map> buckets; uint64_t bucketStartTimeNs = 10000000000; uint64_t eventStartTimeNs = bucketStartTimeNs + 1; uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL; uint64_t durationTimeNs = 2 * 1000; - OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, true, bucketStartTimeNs, - bucketSizeNs, false, {}); + OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, dimensionInCondition, + true, bucketStartTimeNs, bucketSizeNs, false, {}); tracker.noteStart(kEventKey1, true, eventStartTimeNs, ConditionKey()); EXPECT_EQ((long long)eventStartTimeNs, tracker.mLastStartTime); @@ -150,23 +173,30 @@ TEST(OringDurationTrackerTest, TestCrossBucketBoundary) { } TEST(OringDurationTrackerTest, TestDurationConditionChange) { + const MetricDimensionKey eventKey = getMockedMetricDimensionKey(TagId, 0, "event"); + + const std::vector kConditionKey1 = + {getMockedDimensionKey(TagId, 1, "maps")}; + const HashableDimensionKey kEventKey1 = getMockedDimensionKey(TagId, 2, "maps"); + const HashableDimensionKey kEventKey2 = getMockedDimensionKey(TagId, 3, "maps"); + FieldMatcher dimensionInCondition; sp wizard = new NaggyMock(); ConditionKey key1; key1[StringToId("APP_BACKGROUND")] = kConditionKey1; - EXPECT_CALL(*wizard, query(_, key1)) // #4 + EXPECT_CALL(*wizard, query(_, key1, _, _)) // #4 .WillOnce(Return(ConditionState::kFalse)); - unordered_map> buckets; + unordered_map> buckets; uint64_t bucketStartTimeNs = 10000000000; uint64_t eventStartTimeNs = bucketStartTimeNs + 1; uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL; uint64_t durationTimeNs = 2 * 1000; - OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, false, - bucketStartTimeNs, bucketSizeNs, true, {}); + OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, dimensionInCondition, + false, bucketStartTimeNs, bucketSizeNs, true, {}); tracker.noteStart(kEventKey1, true, eventStartTimeNs, key1); @@ -181,25 +211,32 @@ TEST(OringDurationTrackerTest, TestDurationConditionChange) { } TEST(OringDurationTrackerTest, TestDurationConditionChange2) { + const MetricDimensionKey eventKey = getMockedMetricDimensionKey(TagId, 0, "event"); + + const std::vector kConditionKey1 = + {getMockedDimensionKey(TagId, 1, "maps")}; + const HashableDimensionKey kEventKey1 = getMockedDimensionKey(TagId, 2, "maps"); + const HashableDimensionKey kEventKey2 = getMockedDimensionKey(TagId, 3, "maps"); + FieldMatcher dimensionInCondition; sp wizard = new NaggyMock(); ConditionKey key1; key1[StringToId("APP_BACKGROUND")] = kConditionKey1; - EXPECT_CALL(*wizard, query(_, key1)) + EXPECT_CALL(*wizard, query(_, key1, _, _)) .Times(2) .WillOnce(Return(ConditionState::kFalse)) .WillOnce(Return(ConditionState::kTrue)); - unordered_map> buckets; + unordered_map> buckets; uint64_t bucketStartTimeNs = 10000000000; uint64_t eventStartTimeNs = bucketStartTimeNs + 1; uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL; uint64_t durationTimeNs = 2 * 1000; - OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, false, - bucketStartTimeNs, bucketSizeNs, true, {}); + OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, dimensionInCondition, + false, bucketStartTimeNs, bucketSizeNs, true, {}); tracker.noteStart(kEventKey1, true, eventStartTimeNs, key1); // condition to false; record duration 5n @@ -216,22 +253,29 @@ TEST(OringDurationTrackerTest, TestDurationConditionChange2) { } TEST(OringDurationTrackerTest, TestDurationConditionChangeNested) { + const MetricDimensionKey eventKey = getMockedMetricDimensionKey(TagId, 0, "event"); + + const std::vector kConditionKey1 = + {getMockedDimensionKey(TagId, 1, "maps")}; + const HashableDimensionKey kEventKey1 = getMockedDimensionKey(TagId, 2, "maps"); + const HashableDimensionKey kEventKey2 = getMockedDimensionKey(TagId, 3, "maps"); + FieldMatcher dimensionInCondition; sp wizard = new NaggyMock(); ConditionKey key1; key1[StringToId("APP_BACKGROUND")] = kConditionKey1; - EXPECT_CALL(*wizard, query(_, key1)) // #4 + EXPECT_CALL(*wizard, query(_, key1, _, _)) // #4 .WillOnce(Return(ConditionState::kFalse)); - unordered_map> buckets; + unordered_map> buckets; uint64_t bucketStartTimeNs = 10000000000; uint64_t eventStartTimeNs = bucketStartTimeNs + 1; uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL; - OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, true, bucketStartTimeNs, - bucketSizeNs, true, {}); + OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, dimensionInCondition, + true, bucketStartTimeNs, bucketSizeNs, true, {}); tracker.noteStart(kEventKey1, true, eventStartTimeNs, key1); tracker.noteStart(kEventKey1, true, eventStartTimeNs + 2, key1); @@ -249,6 +293,13 @@ TEST(OringDurationTrackerTest, TestDurationConditionChangeNested) { } TEST(OringDurationTrackerTest, TestPredictAnomalyTimestamp) { + const MetricDimensionKey eventKey = getMockedMetricDimensionKey(TagId, 0, "event"); + + const std::vector kConditionKey1 = + {getMockedDimensionKey(TagId, 1, "maps")}; + const HashableDimensionKey kEventKey1 = getMockedDimensionKey(TagId, 2, "maps"); + const HashableDimensionKey kEventKey2 = getMockedDimensionKey(TagId, 3, "maps"); + FieldMatcher dimensionInCondition; Alert alert; alert.set_id(101); alert.set_metric_id(1); @@ -256,7 +307,7 @@ TEST(OringDurationTrackerTest, TestPredictAnomalyTimestamp) { alert.set_num_buckets(2); alert.set_refractory_period_secs(1); - unordered_map> buckets; + unordered_map> buckets; sp wizard = new NaggyMock(); uint64_t bucketStartTimeNs = 10 * NS_PER_SEC; @@ -264,8 +315,8 @@ TEST(OringDurationTrackerTest, TestPredictAnomalyTimestamp) { uint64_t bucketSizeNs = 30 * NS_PER_SEC; sp anomalyTracker = new DurationAnomalyTracker(alert, kConfigKey); - OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, true, bucketStartTimeNs, - bucketSizeNs, true, {anomalyTracker}); + OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, dimensionInCondition, + true, bucketStartTimeNs, bucketSizeNs, true, {anomalyTracker}); // Nothing in the past bucket. tracker.noteStart(DEFAULT_DIMENSION_KEY, true, eventStartTimeNs, ConditionKey()); @@ -310,6 +361,12 @@ TEST(OringDurationTrackerTest, TestPredictAnomalyTimestamp) { } TEST(OringDurationTrackerTest, TestAnomalyDetectionExpiredAlarm) { + const MetricDimensionKey eventKey = getMockedMetricDimensionKey(TagId, 0, "event"); + + const std::vector kConditionKey1 = {getMockedDimensionKey(TagId, 1, "maps")}; + const HashableDimensionKey kEventKey1 = getMockedDimensionKey(TagId, 2, "maps"); + const HashableDimensionKey kEventKey2 = getMockedDimensionKey(TagId, 3, "maps"); + FieldMatcher dimensionInCondition; Alert alert; alert.set_id(101); alert.set_metric_id(1); @@ -318,7 +375,7 @@ TEST(OringDurationTrackerTest, TestAnomalyDetectionExpiredAlarm) { const int32_t refPeriodSec = 45; alert.set_refractory_period_secs(refPeriodSec); - unordered_map> buckets; + unordered_map> buckets; sp wizard = new NaggyMock(); uint64_t bucketStartTimeNs = 10 * NS_PER_SEC; @@ -326,8 +383,8 @@ TEST(OringDurationTrackerTest, TestAnomalyDetectionExpiredAlarm) { uint64_t bucketSizeNs = 30 * NS_PER_SEC; sp anomalyTracker = new DurationAnomalyTracker(alert, kConfigKey); - OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, true /*nesting*/, - bucketStartTimeNs, bucketSizeNs, false, {anomalyTracker}); + OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, dimensionInCondition, + true /*nesting*/, bucketStartTimeNs, bucketSizeNs, false, {anomalyTracker}); tracker.noteStart(kEventKey1, true, eventStartTimeNs, ConditionKey()); tracker.noteStop(kEventKey1, eventStartTimeNs + 10, false); @@ -352,6 +409,13 @@ TEST(OringDurationTrackerTest, TestAnomalyDetectionExpiredAlarm) { } TEST(OringDurationTrackerTest, TestAnomalyDetectionFiredAlarm) { + const MetricDimensionKey eventKey = getMockedMetricDimensionKey(TagId, 0, "event"); + + const std::vector kConditionKey1 = + {getMockedDimensionKey(TagId, 1, "maps")}; + const HashableDimensionKey kEventKey1 = getMockedDimensionKey(TagId, 2, "maps"); + const HashableDimensionKey kEventKey2 = getMockedDimensionKey(TagId, 3, "maps"); + FieldMatcher dimensionInCondition; Alert alert; alert.set_id(101); alert.set_metric_id(1); @@ -360,7 +424,7 @@ TEST(OringDurationTrackerTest, TestAnomalyDetectionFiredAlarm) { const int32_t refPeriodSec = 45; alert.set_refractory_period_secs(refPeriodSec); - unordered_map> buckets; + unordered_map> buckets; sp wizard = new NaggyMock(); ConditionKey conkey; conkey[StringToId("APP_BACKGROUND")] = kConditionKey1; @@ -369,8 +433,9 @@ TEST(OringDurationTrackerTest, TestAnomalyDetectionFiredAlarm) { uint64_t bucketSizeNs = 30 * NS_PER_SEC; sp anomalyTracker = new DurationAnomalyTracker(alert, kConfigKey); - OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, true /*nesting*/, - bucketStartTimeNs, bucketSizeNs, false, {anomalyTracker}); + OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, dimensionInCondition, + true /*nesting*/, bucketStartTimeNs, bucketSizeNs, false, + {anomalyTracker}); tracker.noteStart(kEventKey1, true, 15 * NS_PER_SEC, conkey); // start key1 EXPECT_EQ(1u, anomalyTracker->mAlarms.size()); diff --git a/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp b/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp index fff3dbf5428d9..55c078dcbab80 100644 --- a/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp +++ b/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp @@ -299,26 +299,26 @@ TEST(ValueMetricProducerTest, TestAnomalyDetection) { valueProducer.onMatchedLogEvent(1 /*log matcher index*/, *event1); valueProducer.onMatchedLogEvent(1 /*log matcher index*/, *event2); // Value sum == 30 <= 130. - EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(DEFAULT_DIMENSION_KEY), 0U); + EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(DEFAULT_METRIC_DIMENSION_KEY), 0U); // One event in bucket #2. No alarm as bucket #0 is trashed out. valueProducer.onMatchedLogEvent(1 /*log matcher index*/, *event3); // Value sum == 130 <= 130. - EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(DEFAULT_DIMENSION_KEY), 0U); + EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(DEFAULT_METRIC_DIMENSION_KEY), 0U); // Three events in bucket #3. valueProducer.onMatchedLogEvent(1 /*log matcher index*/, *event4); // Anomaly at event 4 since Value sum == 131 > 130! - EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(DEFAULT_DIMENSION_KEY), + EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(DEFAULT_METRIC_DIMENSION_KEY), event4->GetTimestampNs() / NS_PER_SEC + refPeriodSec); valueProducer.onMatchedLogEvent(1 /*log matcher index*/, *event5); // Event 5 is within 3 sec refractory period. Thus last alarm timestamp is still event4. - EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(DEFAULT_DIMENSION_KEY), + EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(DEFAULT_METRIC_DIMENSION_KEY), event4->GetTimestampNs() / NS_PER_SEC + refPeriodSec); valueProducer.onMatchedLogEvent(1 /*log matcher index*/, *event6); // Anomaly at event 6 since Value sum == 160 > 130 and after refractory period. - EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(DEFAULT_DIMENSION_KEY), + EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(DEFAULT_METRIC_DIMENSION_KEY), event6->GetTimestampNs() / NS_PER_SEC + refPeriodSec); } diff --git a/cmds/statsd/tests/metrics/metrics_test_helper.cpp b/cmds/statsd/tests/metrics/metrics_test_helper.cpp index fc7245ca54e32..ab9345af172e9 100644 --- a/cmds/statsd/tests/metrics/metrics_test_helper.cpp +++ b/cmds/statsd/tests/metrics/metrics_test_helper.cpp @@ -26,6 +26,13 @@ HashableDimensionKey getMockedDimensionKey(int tagId, int key, string value) { return HashableDimensionKey(dimensionsValue); } +MetricDimensionKey getMockedMetricDimensionKey(int tagId, int key, string value) { + DimensionsValue dimensionsValue; + dimensionsValue.set_field(tagId); + dimensionsValue.mutable_value_tuple()->add_dimensions_value()->set_field(key); + dimensionsValue.mutable_value_tuple()->mutable_dimensions_value(0)->set_value_str(value); + return MetricDimensionKey(HashableDimensionKey(dimensionsValue), DEFAULT_DIMENSION_KEY); +} } // namespace statsd } // namespace os } // namespace android \ No newline at end of file diff --git a/cmds/statsd/tests/metrics/metrics_test_helper.h b/cmds/statsd/tests/metrics/metrics_test_helper.h index 23e86f92f844b..0a97456c56847 100644 --- a/cmds/statsd/tests/metrics/metrics_test_helper.h +++ b/cmds/statsd/tests/metrics/metrics_test_helper.h @@ -25,10 +25,12 @@ namespace statsd { class MockConditionWizard : public ConditionWizard { public: - MOCK_METHOD2( + MOCK_METHOD4( query, ConditionState(const int conditionIndex, - const ConditionKey& conditionParameters)); + const ConditionKey& conditionParameters, + const FieldMatcher& dimensionFields, + std::unordered_set *dimensionKeySet)); }; class MockStatsPullerManager : public StatsPullerManager { @@ -39,6 +41,7 @@ public: }; HashableDimensionKey getMockedDimensionKey(int tagId, int key, std::string value); +MetricDimensionKey getMockedMetricDimensionKey(int tagId, int key, std::string value); } // namespace statsd } // namespace os diff --git a/cmds/statsd/tests/statsd_test_util.cpp b/cmds/statsd/tests/statsd_test_util.cpp index 9f4582dc6994e..13055cb9e7a30 100644 --- a/cmds/statsd/tests/statsd_test_util.cpp +++ b/cmds/statsd/tests/statsd_test_util.cpp @@ -12,7 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include #include "statsd_test_util.h" namespace android { @@ -27,6 +26,22 @@ AtomMatcher CreateSimpleAtomMatcher(const string& name, int atomId) { return atom_matcher; } +AtomMatcher CreateScreenBrightnessChangedAtomMatcher() { + AtomMatcher atom_matcher; + atom_matcher.set_id(StringToId("ScreenBrightnessChanged")); + auto simple_atom_matcher = atom_matcher.mutable_simple_atom_matcher(); + simple_atom_matcher->set_atom_id(android::util::SCREEN_BRIGHTNESS_CHANGED); + return atom_matcher; +} + +AtomMatcher CreateUidProcessStateChangedAtomMatcher() { + AtomMatcher atom_matcher; + atom_matcher.set_id(StringToId("UidProcessStateChanged")); + auto simple_atom_matcher = atom_matcher.mutable_simple_atom_matcher(); + simple_atom_matcher->set_atom_id(android::util::UID_PROCESS_STATE_CHANGED); + return atom_matcher; +} + AtomMatcher CreateWakelockStateChangedAtomMatcher(const string& name, WakelockStateChanged::State state) { AtomMatcher atom_matcher; @@ -47,6 +62,30 @@ AtomMatcher CreateReleaseWakelockAtomMatcher() { return CreateWakelockStateChangedAtomMatcher("ReleaseWakelock", WakelockStateChanged::RELEASE); } +AtomMatcher CreateBatterySaverModeStateChangedAtomMatcher( + const string& name, BatterySaverModeStateChanged::State state) { + AtomMatcher atom_matcher; + atom_matcher.set_id(StringToId(name)); + auto simple_atom_matcher = atom_matcher.mutable_simple_atom_matcher(); + simple_atom_matcher->set_atom_id(android::util::BATTERY_SAVER_MODE_STATE_CHANGED); + auto field_value_matcher = simple_atom_matcher->add_field_value_matcher(); + field_value_matcher->set_field(1); // State field. + field_value_matcher->set_eq_int(state); + return atom_matcher; +} + +AtomMatcher CreateBatterySaverModeStartAtomMatcher() { + return CreateBatterySaverModeStateChangedAtomMatcher( + "BatterySaverModeStart", BatterySaverModeStateChanged::ON); +} + + +AtomMatcher CreateBatterySaverModeStopAtomMatcher() { + return CreateBatterySaverModeStateChangedAtomMatcher( + "BatterySaverModeStop", BatterySaverModeStateChanged::OFF); +} + + AtomMatcher CreateScreenStateChangedAtomMatcher( const string& name, android::view::DisplayStateEnum state) { AtomMatcher atom_matcher; @@ -59,6 +98,7 @@ AtomMatcher CreateScreenStateChangedAtomMatcher( return atom_matcher; } + AtomMatcher CreateScreenTurnedOnAtomMatcher() { return CreateScreenStateChangedAtomMatcher("ScreenTurnedOn", android::view::DisplayStateEnum::DISPLAY_STATE_ON); @@ -128,6 +168,13 @@ AtomMatcher CreateProcessCrashAtomMatcher() { "ProcessCrashed", ProcessLifeCycleStateChanged::PROCESS_CRASHED); } +Predicate CreateBatterySaverModePredicate() { + Predicate predicate; + predicate.set_id(StringToId("BatterySaverIsOn")); + predicate.mutable_simple_predicate()->set_start(StringToId("BatterySaverModeStart")); + predicate.mutable_simple_predicate()->set_stop(StringToId("BatterySaverModeStop")); + return predicate; +} Predicate CreateScreenIsOnPredicate() { Predicate predicate; @@ -218,6 +265,31 @@ std::unique_ptr CreateScreenStateChangedEvent( return event; } +std::unique_ptr CreateBatterySaverOnEvent(uint64_t timestampNs) { + auto event = std::make_unique( + android::util::BATTERY_SAVER_MODE_STATE_CHANGED, timestampNs); + EXPECT_TRUE(event->write(BatterySaverModeStateChanged::ON)); + event->init(); + return event; +} + +std::unique_ptr CreateBatterySaverOffEvent(uint64_t timestampNs) { + auto event = std::make_unique( + android::util::BATTERY_SAVER_MODE_STATE_CHANGED, timestampNs); + EXPECT_TRUE(event->write(BatterySaverModeStateChanged::OFF)); + event->init(); + return event; +} + +std::unique_ptr CreateScreenBrightnessChangedEvent( + int level, uint64_t timestampNs) { + auto event = std::make_unique(android::util::SCREEN_BRIGHTNESS_CHANGED, timestampNs); + EXPECT_TRUE(event->write(level)); + event->init(); + return event; + +} + std::unique_ptr CreateWakelockStateChangedEvent( const std::vector& attributions, const string& wakelockName, const WakelockStateChanged::State state, uint64_t timestampNs) { @@ -267,9 +339,10 @@ std::unique_ptr CreateMoveToForegroundEvent(const int uid, uint64_t ti } std::unique_ptr CreateSyncStateChangedEvent( - const int uid, const string& name, const SyncStateChanged::State state, uint64_t timestampNs) { + const std::vector& attributions, + const string& name, const SyncStateChanged::State state, uint64_t timestampNs) { auto event = std::make_unique(android::util::SYNC_STATE_CHANGED, timestampNs); - event->write(uid); + event->write(attributions); event->write(name); event->write(state); event->init(); @@ -277,13 +350,13 @@ std::unique_ptr CreateSyncStateChangedEvent( } std::unique_ptr CreateSyncStartEvent( - const int uid, const string& name, uint64_t timestampNs){ - return CreateSyncStateChangedEvent(uid, name, SyncStateChanged::ON, timestampNs); + const std::vector& attributions, const string& name, uint64_t timestampNs){ + return CreateSyncStateChangedEvent(attributions, name, SyncStateChanged::ON, timestampNs); } std::unique_ptr CreateSyncEndEvent( - const int uid, const string& name, uint64_t timestampNs) { - return CreateSyncStateChangedEvent(uid, name, SyncStateChanged::OFF, timestampNs); + const std::vector& attributions, const string& name, uint64_t timestampNs) { + return CreateSyncStateChangedEvent(attributions, name, SyncStateChanged::OFF, timestampNs); } std::unique_ptr CreateProcessLifeCycleStateChangedEvent( diff --git a/cmds/statsd/tests/statsd_test_util.h b/cmds/statsd/tests/statsd_test_util.h index ff8fef0c46b61..6638893f6aebc 100644 --- a/cmds/statsd/tests/statsd_test_util.h +++ b/cmds/statsd/tests/statsd_test_util.h @@ -14,6 +14,7 @@ #pragma once +#include #include "frameworks/base/cmds/statsd/src/statsd_config.pb.h" #include "statslog.h" #include "src/logd/LogEvent.h" @@ -26,6 +27,18 @@ namespace statsd { // Create AtomMatcher proto to simply match a specific atom type. AtomMatcher CreateSimpleAtomMatcher(const string& name, int atomId); +// Create AtomMatcher proto for screen brightness state changed. +AtomMatcher CreateScreenBrightnessChangedAtomMatcher(); + +// Create AtomMatcher proto for starting battery save mode. +AtomMatcher CreateBatterySaverModeStartAtomMatcher(); + +// Create AtomMatcher proto for stopping battery save mode. +AtomMatcher CreateBatterySaverModeStopAtomMatcher(); + +// Create AtomMatcher proto for process state changed. +AtomMatcher CreateUidProcessStateChangedAtomMatcher(); + // Create AtomMatcher proto for acquiring wakelock. AtomMatcher CreateAcquireWakelockAtomMatcher(); @@ -59,6 +72,9 @@ Predicate CreateScreenIsOnPredicate(); // Create Predicate proto for screen is off. Predicate CreateScreenIsOffPredicate(); +// Create Predicate proto for battery saver mode. +Predicate CreateBatterySaverModePredicate(); + // Create Predicate proto for holding wakelock. Predicate CreateHoldingWakelockPredicate(); @@ -86,6 +102,15 @@ FieldMatcher CreateAttributionUidDimensions(const int atomId, std::unique_ptr CreateScreenStateChangedEvent( const android::view::DisplayStateEnum state, uint64_t timestampNs); +// Create log event for screen brightness state changed. +std::unique_ptr CreateScreenBrightnessChangedEvent( + int level, uint64_t timestampNs); + +// Create log event when battery saver starts. +std::unique_ptr CreateBatterySaverOnEvent(uint64_t timestampNs); +// Create log event when battery saver stops. +std::unique_ptr CreateBatterySaverOffEvent(uint64_t timestampNs); + // Create log event for app moving to background. std::unique_ptr CreateMoveToBackgroundEvent(const int uid, uint64_t timestampNs); @@ -94,11 +119,11 @@ std::unique_ptr CreateMoveToForegroundEvent(const int uid, uint64_t ti // Create log event when the app sync starts. std::unique_ptr CreateSyncStartEvent( - const int uid, const string& name, uint64_t timestampNs); + const std::vector& attributions, const string& name, uint64_t timestampNs); // Create log event when the app sync ends. std::unique_ptr CreateSyncEndEvent( - const int uid, const string& name, uint64_t timestampNs); + const std::vector& attributions, const string& name, uint64_t timestampNs); // Create log event when the app sync ends. std::unique_ptr CreateAppCrashEvent( @@ -136,9 +161,12 @@ void ValidateAttributionUidAndTagDimension( template void sortMetricDataByDimensionsValue(const T& metricData, T* sortedMetricData) { - std::map dimensionIndexMap; + std::map dimensionIndexMap; for (int i = 0; i < metricData.data_size(); ++i) { - dimensionIndexMap.insert(std::make_pair(metricData.data(i).dimensions_in_what(), i)); + dimensionIndexMap.insert(std::make_pair( + MetricDimensionKey(HashableDimensionKey(metricData.data(i).dimensions_in_what()), + HashableDimensionKey(metricData.data(i).dimensions_in_condition())), + i)); } for (const auto& itr : dimensionIndexMap) { *sortedMetricData->add_data() = metricData.data(itr.second);