From 8136e02c4bc2df112c14f7e80955312bb77fb2b5 Mon Sep 17 00:00:00 2001 From: Kelly Rossmoyer Date: Wed, 29 Jul 2020 21:22:15 +0000 Subject: Update language to comply with Android's inclusive language guidance See https://source.android.com/setup/contribute/respectful-code for reference BUG=161896447 Test: TH Change-Id: I7a081cb7385c6db047c4ba369a91ac409f3ab20f Merged-In: Ibb065d64be3775c699583215f7f376a1a5492ac1 --- bin/src/config/ConfigManager.cpp | 2 +- bin/src/config/ConfigManager.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/bin/src/config/ConfigManager.cpp b/bin/src/config/ConfigManager.cpp index bbae3fef..13020e06 100644 --- a/bin/src/config/ConfigManager.cpp +++ b/bin/src/config/ConfigManager.cpp @@ -128,7 +128,7 @@ void ConfigManager::Startup() { } void ConfigManager::StartupForTest() { - // Dummy function to avoid reading configs from disks for tests. + // No-op function to avoid reading configs from disks for tests. } void ConfigManager::AddListener(const sp& listener) { diff --git a/bin/src/config/ConfigManager.h b/bin/src/config/ConfigManager.h index 40146b1b..bef057f9 100644 --- a/bin/src/config/ConfigManager.h +++ b/bin/src/config/ConfigManager.h @@ -46,7 +46,7 @@ public: void Startup(); /* - * Dummy initializer for tests. + * No-op initializer for tests. */ void StartupForTest(); -- cgit v1.2.3 From 390b3b2eb6c8b5e1a5d71350ac1a9ecc732c2e55 Mon Sep 17 00:00:00 2001 From: Kelly Rossmoyer Date: Wed, 29 Jul 2020 22:21:31 +0000 Subject: Update language to comply with Android's inclusive language guidance See https://source.android.com/setup/contribute/respectful-code for reference BUG=161896447 Test: Build (all changes are comments + 1 error log message) Change-Id: I26ef8e079bf13f19bdc3bbc8219f572a43b68090 Merged-In: Ibb065d64be3775c699583215f7f376a1a5492ac1 --- bin/src/metrics/GaugeMetricProducer.h | 4 ++-- bin/src/stats_log_util.h | 2 +- bin/tests/StatsLogProcessor_test.cpp | 2 +- bin/tests/UidMap_test.cpp | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/bin/src/metrics/GaugeMetricProducer.h b/bin/src/metrics/GaugeMetricProducer.h index d60aa0af..751b4872 100644 --- a/bin/src/metrics/GaugeMetricProducer.h +++ b/bin/src/metrics/GaugeMetricProducer.h @@ -191,14 +191,14 @@ private: // for each slice with the latest value. void updateCurrentSlicedBucketForAnomaly(); - // Whitelist of fields to report. Empty means all are reported. + // Allowlist of fields to report. Empty means all are reported. std::vector mFieldMatchers; GaugeMetric::SamplingType mSamplingType; const int64_t mMaxPullDelayNs; - // apply a whitelist on the original input + // apply an allowlist on the original input std::shared_ptr> getGaugeFields(const LogEvent& event); // Util function to check whether the specified dimension hits the guardrail. diff --git a/bin/src/stats_log_util.h b/bin/src/stats_log_util.h index 769154bc..7fdde934 100644 --- a/bin/src/stats_log_util.h +++ b/bin/src/stats_log_util.h @@ -97,7 +97,7 @@ bool parseProtoOutputStream(ProtoOutputStream& protoOutput, T* message) { return message->ParseFromArray(pbBytes.c_str(), pbBytes.size()); } -// Checks the truncate timestamp annotation as well as the blacklisted range of 300,000 - 304,999. +// Checks the truncate timestamp annotation as well as the restricted range of 300,000 - 304,999. // Returns the truncated timestamp to the nearest 5 minutes if needed. int64_t truncateTimestampIfNecessary(const LogEvent& event); diff --git a/bin/tests/StatsLogProcessor_test.cpp b/bin/tests/StatsLogProcessor_test.cpp index 40909659..26b64f82 100644 --- a/bin/tests/StatsLogProcessor_test.cpp +++ b/bin/tests/StatsLogProcessor_test.cpp @@ -68,7 +68,7 @@ TEST(StatsLogProcessorTest, TestRateLimitByteSize) { sp pullerManager = new StatsPullerManager(); sp anomalyAlarmMonitor; sp periodicAlarmMonitor; - // Construct the processor with a dummy sendBroadcast function that does nothing. + // Construct the processor with a no-op sendBroadcast function that does nothing. StatsLogProcessor p(m, pullerManager, anomalyAlarmMonitor, periodicAlarmMonitor, 0, [](const ConfigKey& key) { return true; }, [](const int&, const vector&) {return true;}); diff --git a/bin/tests/UidMap_test.cpp b/bin/tests/UidMap_test.cpp index 293e8ed1..33bdc643 100644 --- a/bin/tests/UidMap_test.cpp +++ b/bin/tests/UidMap_test.cpp @@ -44,7 +44,7 @@ TEST(UidMapTest, TestIsolatedUID) { sp pullerManager = new StatsPullerManager(); sp anomalyAlarmMonitor; sp subscriberAlarmMonitor; - // Construct the processor with a dummy sendBroadcast function that does nothing. + // Construct the processor with a no-op sendBroadcast function that does nothing. StatsLogProcessor p( m, pullerManager, anomalyAlarmMonitor, subscriberAlarmMonitor, 0, [](const ConfigKey& key) { return true; }, -- cgit v1.2.3 From dbc4dc41b292438a37b5a64a41493d62d971fc2d Mon Sep 17 00:00:00 2001 From: Salud Lemus Date: Thu, 20 Aug 2020 23:14:26 +0000 Subject: Refactor Interval struct Create another struct that contains the vector of Intervals. This new struct will later contain a ConditionTimer to support slicing condition_true_nanos by state. Bug: 165817662 Bug: 165018838 Test: `m statsd` Test: `m statsd_test` Test: `m` Test: `atest statsd_test` Change-Id: Id1509ac04deee5719aaf2dc4073df62516a1edf2 Merged-In: Ibb065d64be3775c699583215f7f376a1a5492ac1 --- bin/src/metrics/ValueMetricProducer.cpp | 21 +- bin/src/metrics/ValueMetricProducer.h | 23 ++- bin/tests/metrics/ValueMetricProducer_test.cpp | 272 ++++++++++++------------- 3 files changed, 162 insertions(+), 154 deletions(-) diff --git a/bin/src/metrics/ValueMetricProducer.cpp b/bin/src/metrics/ValueMetricProducer.cpp index eb44d6ca..d16dfd11 100644 --- a/bin/src/metrics/ValueMetricProducer.cpp +++ b/bin/src/metrics/ValueMetricProducer.cpp @@ -697,7 +697,7 @@ void ValueMetricProducer::dumpStatesLocked(FILE* out, bool verbose) const { (unsigned long)mCurrentSlicedBucket.size()); if (verbose) { for (const auto& it : mCurrentSlicedBucket) { - for (const auto& interval : it.second) { + for (const auto& interval : it.second.intervals) { fprintf(out, "\t(what)%s\t(states)%s (value)%s\n", it.first.getDimensionKeyInWhat().toString().c_str(), it.first.getStateValuesKey().toString().c_str(), @@ -849,7 +849,8 @@ void ValueMetricProducer::onMatchedLogEventInternalLocked( // We need to get the intervals stored with the previous state key so we can // close these value intervals. const auto oldStateKey = baseInfos[0].currentState; - vector& intervals = mCurrentSlicedBucket[MetricDimensionKey(whatKey, oldStateKey)]; + vector& intervals = + mCurrentSlicedBucket[MetricDimensionKey(whatKey, oldStateKey)].intervals; if (intervals.size() < mFieldMatchers.size()) { VLOG("Resizing number of intervals to %d", (int)mFieldMatchers.size()); intervals.resize(mFieldMatchers.size()); @@ -1035,7 +1036,7 @@ void ValueMetricProducer::flushCurrentBucketLocked(const int64_t& eventTimeNs, bool bucketHasData = false; // The current bucket is large enough to keep. for (const auto& slice : mCurrentSlicedBucket) { - ValueBucket bucket = buildPartialBucket(bucketEndTime, slice.second); + PastValueBucket bucket = buildPartialBucket(bucketEndTime, slice.second.intervals); bucket.mConditionTrueNs = conditionTrueDuration; // it will auto create new vector of ValuebucketInfo if the key is not found. if (bucket.valueIndex.size() > 0) { @@ -1075,9 +1076,9 @@ void ValueMetricProducer::flushCurrentBucketLocked(const int64_t& eventTimeNs, mCurrentBucketNum += numBucketsForward; } -ValueBucket ValueMetricProducer::buildPartialBucket(int64_t bucketEndTime, - const std::vector& intervals) { - ValueBucket bucket; +PastValueBucket ValueMetricProducer::buildPartialBucket(int64_t bucketEndTime, + const std::vector& intervals) { + PastValueBucket bucket; bucket.mBucketStartNs = mCurrentBucketStartTimeNs; bucket.mBucketEndNs = bucketEndTime; for (const auto& interval : intervals) { @@ -1104,7 +1105,7 @@ void ValueMetricProducer::initCurrentSlicedBucket(int64_t nextBucketStartTimeNs) // Cleanup data structure to aggregate values. for (auto it = mCurrentSlicedBucket.begin(); it != mCurrentSlicedBucket.end();) { bool obsolete = true; - for (auto& interval : it->second) { + for (auto& interval : it->second.intervals) { interval.hasValue = false; interval.sampleSize = 0; if (interval.seenNewData) { @@ -1152,7 +1153,7 @@ void ValueMetricProducer::appendToFullBucket(const bool isFullBucketReached) { continue; } // TODO: fix this when anomaly can accept double values - auto& interval = slice.second[0]; + auto& interval = slice.second.intervals[0]; if (interval.hasValue) { mCurrentFullBucket[slice.first] += interval.value.long_value; } @@ -1171,7 +1172,7 @@ void ValueMetricProducer::appendToFullBucket(const bool isFullBucketReached) { for (auto& tracker : mAnomalyTrackers) { if (tracker != nullptr) { // TODO: fix this when anomaly can accept double values - auto& interval = slice.second[0]; + auto& interval = slice.second.intervals[0]; if (interval.hasValue) { tracker->addPastBucket(slice.first, interval.value.long_value, mCurrentBucketNum); @@ -1184,7 +1185,7 @@ void ValueMetricProducer::appendToFullBucket(const bool isFullBucketReached) { // Accumulate partial bucket. for (const auto& slice : mCurrentSlicedBucket) { // TODO: fix this when anomaly can accept double values - auto& interval = slice.second[0]; + auto& interval = slice.second.intervals[0]; if (interval.hasValue) { mCurrentFullBucket[slice.first] += interval.value.long_value; } diff --git a/bin/src/metrics/ValueMetricProducer.h b/bin/src/metrics/ValueMetricProducer.h index 7f22b10f..b394b81e 100644 --- a/bin/src/metrics/ValueMetricProducer.h +++ b/bin/src/metrics/ValueMetricProducer.h @@ -31,7 +31,7 @@ namespace android { namespace os { namespace statsd { -struct ValueBucket { +struct PastValueBucket { int64_t mBucketStartNs; int64_t mBucketEndNs; std::vector valueIndex; @@ -41,7 +41,6 @@ struct ValueBucket { int64_t mConditionTrueNs; }; - // Aggregates values within buckets. // // There are different events that might complete a bucket @@ -193,7 +192,7 @@ private: // if this is pulled metric const bool mIsPulled; - // internal state of an ongoing aggregation bucket. + // Tracks the value information of one value field. typedef struct { // Index in multi value aggregation. int valueIndex; @@ -208,6 +207,12 @@ private: bool seenNewData = false; } Interval; + // Internal state of an ongoing aggregation bucket. + typedef struct CurrentValueBucket { + // Value information for each value field of the metric. + std::vector intervals; + } CurrentValueBucket; + typedef struct { // Holds current base value of the dimension. Take diff and update if necessary. Value base; @@ -219,14 +224,16 @@ private: bool hasCurrentState; } BaseInfo; - std::unordered_map> mCurrentSlicedBucket; + // Tracks the internal state in the ongoing aggregation bucket for each DimensionsInWhat + // key and StateValuesKey pair. + std::unordered_map mCurrentSlicedBucket; std::unordered_map> mCurrentBaseInfo; std::unordered_map mCurrentFullBucket; // Save the past buckets and we can clear when the StatsLogReport is dumped. - std::unordered_map> mPastBuckets; + std::unordered_map> mPastBuckets; const int64_t mMinBucketSizeNs; @@ -244,8 +251,8 @@ private: void accumulateEvents(const std::vector>& allData, int64_t originalPullTimeNs, int64_t eventElapsedTimeNs); - ValueBucket buildPartialBucket(int64_t bucketEndTime, - const std::vector& intervals); + PastValueBucket buildPartialBucket(int64_t bucketEndTime, + const std::vector& intervals); void initCurrentSlicedBucket(int64_t nextBucketStartTimeNs); @@ -254,7 +261,7 @@ private: // Reset diff base and mHasGlobalBase void resetBase(); - static const size_t kBucketSize = sizeof(ValueBucket{}); + static const size_t kBucketSize = sizeof(PastValueBucket{}); const size_t mDimensionSoftLimit; diff --git a/bin/tests/metrics/ValueMetricProducer_test.cpp b/bin/tests/metrics/ValueMetricProducer_test.cpp index 8e78e3bb..803251f2 100644 --- a/bin/tests/metrics/ValueMetricProducer_test.cpp +++ b/bin/tests/metrics/ValueMetricProducer_test.cpp @@ -58,7 +58,7 @@ const int64_t bucket6StartTimeNs = bucketStartTimeNs + 5 * bucketSizeNs; double epsilon = 0.001; static void assertPastBucketValuesSingleKey( - const std::unordered_map>& mPastBuckets, + const std::unordered_map>& mPastBuckets, const std::initializer_list& expectedValuesList, const std::initializer_list& expectedDurationNsList, const std::initializer_list& expectedStartTimeNsList, @@ -80,7 +80,7 @@ static void assertPastBucketValuesSingleKey( ASSERT_EQ(1, mPastBuckets.size()); ASSERT_EQ(expectedValues.size(), mPastBuckets.begin()->second.size()); - const vector& buckets = mPastBuckets.begin()->second; + const vector& buckets = mPastBuckets.begin()->second; for (int i = 0; i < expectedValues.size(); i++) { EXPECT_EQ(expectedValues[i], buckets[i].values[0].long_value) << "Values differ at index " << i; @@ -290,7 +290,7 @@ TEST(ValueMetricProducerTest, TestPulledEventsNoCondition) { // has one slice ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); ValueMetricProducer::Interval curInterval = - valueProducer->mCurrentSlicedBucket.begin()->second[0]; + valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0]; ValueMetricProducer::BaseInfo curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0]; EXPECT_EQ(true, curBaseInfo.hasBase); @@ -306,7 +306,7 @@ TEST(ValueMetricProducerTest, TestPulledEventsNoCondition) { valueProducer->onDataPulled(allData, /** succeed */ true, bucket3StartTimeNs); // has one slice ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); - curInterval = valueProducer->mCurrentSlicedBucket.begin()->second[0]; + curInterval = valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0]; curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0]; EXPECT_EQ(true, curBaseInfo.hasBase); @@ -324,7 +324,7 @@ TEST(ValueMetricProducerTest, TestPulledEventsNoCondition) { allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket4StartTimeNs + 1, 36)); valueProducer->onDataPulled(allData, /** succeed */ true, bucket4StartTimeNs); ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); - curInterval = valueProducer->mCurrentSlicedBucket.begin()->second[0]; + curInterval = valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0]; curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0]; EXPECT_EQ(true, curBaseInfo.hasBase); @@ -429,7 +429,7 @@ TEST(ValueMetricProducerTest, TestPulledEventsWithFiltering) { // has one slice ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); ValueMetricProducer::Interval curInterval = - valueProducer->mCurrentSlicedBucket.begin()->second[0]; + valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0]; ValueMetricProducer::BaseInfo curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0]; EXPECT_EQ(true, curBaseInfo.hasBase); @@ -458,7 +458,7 @@ TEST(ValueMetricProducerTest, TestPulledEventsWithFiltering) { allData.push_back(CreateTwoValueLogEvent(tagId, bucket4StartTimeNs + 1, 3, 36)); valueProducer->onDataPulled(allData, /** succeed */ true, bucket4StartTimeNs); ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); - curInterval = valueProducer->mCurrentSlicedBucket.begin()->second[0]; + curInterval = valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0]; curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0]; // the base was reset @@ -492,7 +492,7 @@ TEST(ValueMetricProducerTest, TestPulledEventsTakeAbsoluteValueOnReset) { // has one slice ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); ValueMetricProducer::Interval curInterval = - valueProducer->mCurrentSlicedBucket.begin()->second[0]; + valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0]; ValueMetricProducer::BaseInfo curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0]; EXPECT_EQ(true, curBaseInfo.hasBase); @@ -505,7 +505,7 @@ TEST(ValueMetricProducerTest, TestPulledEventsTakeAbsoluteValueOnReset) { valueProducer->onDataPulled(allData, /** succeed */ true, bucket3StartTimeNs); // has one slice ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); - curInterval = valueProducer->mCurrentSlicedBucket.begin()->second[0]; + curInterval = valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0]; curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0]; EXPECT_EQ(true, curBaseInfo.hasBase); EXPECT_EQ(10, curBaseInfo.base.long_value); @@ -519,7 +519,7 @@ TEST(ValueMetricProducerTest, TestPulledEventsTakeAbsoluteValueOnReset) { allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket4StartTimeNs + 1, 36)); valueProducer->onDataPulled(allData, /** succeed */ true, bucket4StartTimeNs); ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); - curInterval = valueProducer->mCurrentSlicedBucket.begin()->second[0]; + curInterval = valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0]; curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0]; EXPECT_EQ(true, curBaseInfo.hasBase); EXPECT_EQ(36, curBaseInfo.base.long_value); @@ -552,7 +552,7 @@ TEST(ValueMetricProducerTest, TestPulledEventsTakeZeroOnReset) { // has one slice ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); ValueMetricProducer::Interval curInterval = - valueProducer->mCurrentSlicedBucket.begin()->second[0]; + valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0]; ValueMetricProducer::BaseInfo curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0]; EXPECT_EQ(true, curBaseInfo.hasBase); @@ -565,7 +565,7 @@ TEST(ValueMetricProducerTest, TestPulledEventsTakeZeroOnReset) { valueProducer->onDataPulled(allData, /** succeed */ true, bucket3StartTimeNs); // has one slice ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); - curInterval = valueProducer->mCurrentSlicedBucket.begin()->second[0]; + curInterval = valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0]; curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0]; EXPECT_EQ(true, curBaseInfo.hasBase); EXPECT_EQ(10, curBaseInfo.base.long_value); @@ -576,7 +576,7 @@ TEST(ValueMetricProducerTest, TestPulledEventsTakeZeroOnReset) { allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket4StartTimeNs + 1, 36)); valueProducer->onDataPulled(allData, /** succeed */ true, bucket4StartTimeNs); ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); - curInterval = valueProducer->mCurrentSlicedBucket.begin()->second[0]; + curInterval = valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0]; curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0]; EXPECT_EQ(true, curBaseInfo.hasBase); EXPECT_EQ(36, curBaseInfo.base.long_value); @@ -627,7 +627,7 @@ TEST(ValueMetricProducerTest, TestEventsWithNonSlicedCondition) { // has one slice ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); ValueMetricProducer::Interval curInterval = - valueProducer->mCurrentSlicedBucket.begin()->second[0]; + valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0]; ValueMetricProducer::BaseInfo curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0]; // startUpdated:false sum:0 start:100 EXPECT_EQ(true, curBaseInfo.hasBase); @@ -644,7 +644,7 @@ TEST(ValueMetricProducerTest, TestEventsWithNonSlicedCondition) { // has one slice ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); - curInterval = valueProducer->mCurrentSlicedBucket.begin()->second[0]; + curInterval = valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0]; curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0]; EXPECT_EQ(true, curBaseInfo.hasBase); EXPECT_EQ(110, curBaseInfo.base.long_value); @@ -657,7 +657,7 @@ TEST(ValueMetricProducerTest, TestEventsWithNonSlicedCondition) { // has one slice ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); - curInterval = valueProducer->mCurrentSlicedBucket.begin()->second[0]; + curInterval = valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0]; curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0]; EXPECT_EQ(true, curInterval.hasValue); EXPECT_EQ(20, curInterval.value.long_value); @@ -882,7 +882,7 @@ TEST(ValueMetricProducerTest, TestPushedEventsWithoutCondition) { // has one slice ASSERT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); ValueMetricProducer::Interval curInterval = - valueProducer.mCurrentSlicedBucket.begin()->second[0]; + valueProducer.mCurrentSlicedBucket.begin()->second.intervals[0]; ValueMetricProducer::BaseInfo curBaseInfo = valueProducer.mCurrentBaseInfo.begin()->second[0]; EXPECT_EQ(10, curInterval.value.long_value); EXPECT_EQ(true, curInterval.hasValue); @@ -891,7 +891,7 @@ TEST(ValueMetricProducerTest, TestPushedEventsWithoutCondition) { // has one slice ASSERT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); - curInterval = valueProducer.mCurrentSlicedBucket.begin()->second[0]; + curInterval = valueProducer.mCurrentSlicedBucket.begin()->second.intervals[0]; EXPECT_EQ(30, curInterval.value.long_value); valueProducer.flushIfNeededLocked(bucket2StartTimeNs); @@ -928,8 +928,8 @@ TEST(ValueMetricProducerTest, TestPushedEventsWithCondition) { // has one slice ASSERT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); ValueMetricProducer::Interval curInterval = - valueProducer.mCurrentSlicedBucket.begin()->second[0]; - curInterval = valueProducer.mCurrentSlicedBucket.begin()->second[0]; + valueProducer.mCurrentSlicedBucket.begin()->second.intervals[0]; + curInterval = valueProducer.mCurrentSlicedBucket.begin()->second.intervals[0]; EXPECT_EQ(20, curInterval.value.long_value); LogEvent event3(/*uid=*/0, /*pid=*/0); @@ -938,7 +938,7 @@ TEST(ValueMetricProducerTest, TestPushedEventsWithCondition) { // has one slice ASSERT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); - curInterval = valueProducer.mCurrentSlicedBucket.begin()->second[0]; + curInterval = valueProducer.mCurrentSlicedBucket.begin()->second.intervals[0]; EXPECT_EQ(50, curInterval.value.long_value); valueProducer.onConditionChangedLocked(false, bucketStartTimeNs + 35); @@ -949,7 +949,7 @@ TEST(ValueMetricProducerTest, TestPushedEventsWithCondition) { // has one slice ASSERT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); - curInterval = valueProducer.mCurrentSlicedBucket.begin()->second[0]; + curInterval = valueProducer.mCurrentSlicedBucket.begin()->second.intervals[0]; EXPECT_EQ(50, curInterval.value.long_value); valueProducer.flushIfNeededLocked(bucket2StartTimeNs); @@ -1092,7 +1092,7 @@ TEST(ValueMetricProducerTest, TestBucketBoundaryNoCondition) { // has one slice ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); ValueMetricProducer::Interval curInterval = - valueProducer->mCurrentSlicedBucket.begin()->second[0]; + valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0]; ValueMetricProducer::BaseInfo curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0]; // startUpdated:true sum:0 start:11 @@ -1107,7 +1107,7 @@ TEST(ValueMetricProducerTest, TestBucketBoundaryNoCondition) { valueProducer->onDataPulled(allData, /** succeed */ true, bucket3StartTimeNs); // has one slice ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); - curInterval = valueProducer->mCurrentSlicedBucket.begin()->second[0]; + curInterval = valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0]; curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0]; // tartUpdated:false sum:12 EXPECT_EQ(true, curBaseInfo.hasBase); @@ -1124,7 +1124,7 @@ TEST(ValueMetricProducerTest, TestBucketBoundaryNoCondition) { allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket6StartTimeNs + 1, 36)); valueProducer->onDataPulled(allData, /** succeed */ true, bucket6StartTimeNs); ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); - curInterval = valueProducer->mCurrentSlicedBucket.begin()->second[0]; + curInterval = valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0]; curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0]; // startUpdated:false sum:12 EXPECT_EQ(true, curBaseInfo.hasBase); @@ -1183,7 +1183,7 @@ TEST(ValueMetricProducerTest, TestBucketBoundaryWithCondition) { // has one slice ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); ValueMetricProducer::Interval curInterval = - valueProducer->mCurrentSlicedBucket.begin()->second[0]; + valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0]; ValueMetricProducer::BaseInfo curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0]; EXPECT_EQ(true, curBaseInfo.hasBase); EXPECT_EQ(100, curBaseInfo.base.long_value); @@ -1192,7 +1192,7 @@ TEST(ValueMetricProducerTest, TestBucketBoundaryWithCondition) { // pull on bucket boundary come late, condition change happens before it valueProducer->onConditionChanged(false, bucket2StartTimeNs + 1); - curInterval = valueProducer->mCurrentSlicedBucket.begin()->second[0]; + curInterval = valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0]; curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0]; assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {20}, {bucketSizeNs - 8}, {bucketStartTimeNs}, {bucket2StartTimeNs}); @@ -1206,7 +1206,7 @@ TEST(ValueMetricProducerTest, TestBucketBoundaryWithCondition) { assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {20}, {bucketSizeNs - 8}, {bucketStartTimeNs}, {bucket2StartTimeNs}); - curInterval = valueProducer->mCurrentSlicedBucket.begin()->second[0]; + curInterval = valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0]; curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0]; EXPECT_EQ(false, curBaseInfo.hasBase); EXPECT_EQ(false, curInterval.hasValue); @@ -1255,7 +1255,7 @@ TEST(ValueMetricProducerTest, TestBucketBoundaryWithCondition2) { // has one slice ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); ValueMetricProducer::Interval curInterval = - valueProducer->mCurrentSlicedBucket.begin()->second[0]; + valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0]; ValueMetricProducer::BaseInfo curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0]; // startUpdated:false sum:0 start:100 EXPECT_EQ(true, curBaseInfo.hasBase); @@ -1268,7 +1268,7 @@ TEST(ValueMetricProducerTest, TestBucketBoundaryWithCondition2) { assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {20}, {bucketSizeNs - 8}, {bucketStartTimeNs}, {bucket2StartTimeNs}); ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); - curInterval = valueProducer->mCurrentSlicedBucket.begin()->second[0]; + curInterval = valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0]; curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0]; EXPECT_EQ(false, curBaseInfo.hasBase); EXPECT_EQ(false, curInterval.hasValue); @@ -1277,7 +1277,7 @@ TEST(ValueMetricProducerTest, TestBucketBoundaryWithCondition2) { valueProducer->onConditionChanged(true, bucket2StartTimeNs + 25); assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {20}, {bucketSizeNs - 8}, {bucketStartTimeNs}, {bucket2StartTimeNs}); - curInterval = valueProducer->mCurrentSlicedBucket.begin()->second[0]; + curInterval = valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0]; curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0]; EXPECT_EQ(true, curBaseInfo.hasBase); EXPECT_EQ(130, curBaseInfo.base.long_value); @@ -1289,7 +1289,7 @@ TEST(ValueMetricProducerTest, TestBucketBoundaryWithCondition2) { allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs + 50, 140)); valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs + 50); - curInterval = valueProducer->mCurrentSlicedBucket.begin()->second[0]; + curInterval = valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0]; curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0]; EXPECT_EQ(true, curBaseInfo.hasBase); EXPECT_EQ(140, curBaseInfo.base.long_value); @@ -1330,7 +1330,7 @@ TEST(ValueMetricProducerTest, TestPushedAggregateMin) { // has one slice ASSERT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); ValueMetricProducer::Interval curInterval = - valueProducer.mCurrentSlicedBucket.begin()->second[0]; + valueProducer.mCurrentSlicedBucket.begin()->second.intervals[0]; EXPECT_EQ(10, curInterval.value.long_value); EXPECT_EQ(true, curInterval.hasValue); @@ -1338,7 +1338,7 @@ TEST(ValueMetricProducerTest, TestPushedAggregateMin) { // has one slice ASSERT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); - curInterval = valueProducer.mCurrentSlicedBucket.begin()->second[0]; + curInterval = valueProducer.mCurrentSlicedBucket.begin()->second.intervals[0]; EXPECT_EQ(10, curInterval.value.long_value); valueProducer.flushIfNeededLocked(bucket2StartTimeNs); @@ -1367,7 +1367,7 @@ TEST(ValueMetricProducerTest, TestPushedAggregateMax) { // has one slice ASSERT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); ValueMetricProducer::Interval curInterval = - valueProducer.mCurrentSlicedBucket.begin()->second[0]; + valueProducer.mCurrentSlicedBucket.begin()->second.intervals[0]; EXPECT_EQ(10, curInterval.value.long_value); EXPECT_EQ(true, curInterval.hasValue); @@ -1377,7 +1377,7 @@ TEST(ValueMetricProducerTest, TestPushedAggregateMax) { // has one slice ASSERT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); - curInterval = valueProducer.mCurrentSlicedBucket.begin()->second[0]; + curInterval = valueProducer.mCurrentSlicedBucket.begin()->second.intervals[0]; EXPECT_EQ(20, curInterval.value.long_value); valueProducer.flushIfNeededLocked(bucket2StartTimeNs); @@ -1408,7 +1408,7 @@ TEST(ValueMetricProducerTest, TestPushedAggregateAvg) { // has one slice ASSERT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); ValueMetricProducer::Interval curInterval; - curInterval = valueProducer.mCurrentSlicedBucket.begin()->second[0]; + curInterval = valueProducer.mCurrentSlicedBucket.begin()->second.intervals[0]; EXPECT_EQ(10, curInterval.value.long_value); EXPECT_EQ(true, curInterval.hasValue); EXPECT_EQ(1, curInterval.sampleSize); @@ -1417,7 +1417,7 @@ TEST(ValueMetricProducerTest, TestPushedAggregateAvg) { // has one slice ASSERT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); - curInterval = valueProducer.mCurrentSlicedBucket.begin()->second[0]; + curInterval = valueProducer.mCurrentSlicedBucket.begin()->second.intervals[0]; EXPECT_EQ(25, curInterval.value.long_value); EXPECT_EQ(2, curInterval.sampleSize); @@ -1452,7 +1452,7 @@ TEST(ValueMetricProducerTest, TestPushedAggregateSum) { // has one slice ASSERT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); ValueMetricProducer::Interval curInterval = - valueProducer.mCurrentSlicedBucket.begin()->second[0]; + valueProducer.mCurrentSlicedBucket.begin()->second.intervals[0]; EXPECT_EQ(10, curInterval.value.long_value); EXPECT_EQ(true, curInterval.hasValue); @@ -1460,7 +1460,7 @@ TEST(ValueMetricProducerTest, TestPushedAggregateSum) { // has one slice ASSERT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); - curInterval = valueProducer.mCurrentSlicedBucket.begin()->second[0]; + curInterval = valueProducer.mCurrentSlicedBucket.begin()->second.intervals[0]; EXPECT_EQ(25, curInterval.value.long_value); valueProducer.flushIfNeededLocked(bucket2StartTimeNs); @@ -1490,7 +1490,7 @@ TEST(ValueMetricProducerTest, TestSkipZeroDiffOutput) { // has one slice ASSERT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); ValueMetricProducer::Interval curInterval = - valueProducer.mCurrentSlicedBucket.begin()->second[0]; + valueProducer.mCurrentSlicedBucket.begin()->second.intervals[0]; ValueMetricProducer::BaseInfo curBaseInfo = valueProducer.mCurrentBaseInfo.begin()->second[0]; EXPECT_EQ(true, curBaseInfo.hasBase); EXPECT_EQ(10, curBaseInfo.base.long_value); @@ -1502,7 +1502,7 @@ TEST(ValueMetricProducerTest, TestSkipZeroDiffOutput) { // has one slice ASSERT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); - curInterval = valueProducer.mCurrentSlicedBucket.begin()->second[0]; + curInterval = valueProducer.mCurrentSlicedBucket.begin()->second.intervals[0]; EXPECT_EQ(true, curInterval.hasValue); EXPECT_EQ(5, curInterval.value.long_value); @@ -1512,7 +1512,7 @@ TEST(ValueMetricProducerTest, TestSkipZeroDiffOutput) { valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event3); ASSERT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); - curInterval = valueProducer.mCurrentSlicedBucket.begin()->second[0]; + curInterval = valueProducer.mCurrentSlicedBucket.begin()->second.intervals[0]; curBaseInfo = valueProducer.mCurrentBaseInfo.begin()->second[0]; EXPECT_EQ(true, curBaseInfo.hasBase); EXPECT_EQ(15, curBaseInfo.base.long_value); @@ -1523,7 +1523,7 @@ TEST(ValueMetricProducerTest, TestSkipZeroDiffOutput) { CreateRepeatedValueLogEvent(&event4, tagId, bucket2StartTimeNs + 15, 15); valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event4); ASSERT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); - curInterval = valueProducer.mCurrentSlicedBucket.begin()->second[0]; + curInterval = valueProducer.mCurrentSlicedBucket.begin()->second.intervals[0]; curBaseInfo = valueProducer.mCurrentBaseInfo.begin()->second[0]; EXPECT_EQ(true, curBaseInfo.hasBase); EXPECT_EQ(15, curBaseInfo.base.long_value); @@ -1561,7 +1561,7 @@ TEST(ValueMetricProducerTest, TestSkipZeroDiffOutputMultiValue) { // has one slice ASSERT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); ValueMetricProducer::Interval curInterval = - valueProducer.mCurrentSlicedBucket.begin()->second[0]; + valueProducer.mCurrentSlicedBucket.begin()->second.intervals[0]; ValueMetricProducer::BaseInfo curBaseInfo = valueProducer.mCurrentBaseInfo.begin()->second[0]; EXPECT_EQ(true, curBaseInfo.hasBase); EXPECT_EQ(10, curBaseInfo.base.long_value); @@ -1575,11 +1575,11 @@ TEST(ValueMetricProducerTest, TestSkipZeroDiffOutputMultiValue) { // has one slice ASSERT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); - curInterval = valueProducer.mCurrentSlicedBucket.begin()->second[0]; + curInterval = valueProducer.mCurrentSlicedBucket.begin()->second.intervals[0]; curBaseInfo = valueProducer.mCurrentBaseInfo.begin()->second[0]; EXPECT_EQ(true, curInterval.hasValue); EXPECT_EQ(5, curInterval.value.long_value); - curInterval = valueProducer.mCurrentSlicedBucket.begin()->second[1]; + curInterval = valueProducer.mCurrentSlicedBucket.begin()->second.intervals[1]; curBaseInfo = valueProducer.mCurrentBaseInfo.begin()->second[1]; EXPECT_EQ(true, curInterval.hasValue); EXPECT_EQ(2, curInterval.value.long_value); @@ -1590,13 +1590,13 @@ TEST(ValueMetricProducerTest, TestSkipZeroDiffOutputMultiValue) { valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event3); ASSERT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); - curInterval = valueProducer.mCurrentSlicedBucket.begin()->second[0]; + curInterval = valueProducer.mCurrentSlicedBucket.begin()->second.intervals[0]; curBaseInfo = valueProducer.mCurrentBaseInfo.begin()->second[0]; EXPECT_EQ(true, curBaseInfo.hasBase); EXPECT_EQ(15, curBaseInfo.base.long_value); EXPECT_EQ(true, curInterval.hasValue); - curInterval = valueProducer.mCurrentSlicedBucket.begin()->second[1]; + curInterval = valueProducer.mCurrentSlicedBucket.begin()->second.intervals[1]; curBaseInfo = valueProducer.mCurrentBaseInfo.begin()->second[1]; EXPECT_EQ(true, curBaseInfo.hasBase); EXPECT_EQ(25, curBaseInfo.base.long_value); @@ -1607,12 +1607,12 @@ TEST(ValueMetricProducerTest, TestSkipZeroDiffOutputMultiValue) { valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event4); ASSERT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); - curInterval = valueProducer.mCurrentSlicedBucket.begin()->second[0]; + curInterval = valueProducer.mCurrentSlicedBucket.begin()->second.intervals[0]; curBaseInfo = valueProducer.mCurrentBaseInfo.begin()->second[0]; EXPECT_EQ(true, curBaseInfo.hasBase); EXPECT_EQ(15, curBaseInfo.base.long_value); EXPECT_EQ(true, curInterval.hasValue); - curInterval = valueProducer.mCurrentSlicedBucket.begin()->second[1]; + curInterval = valueProducer.mCurrentSlicedBucket.begin()->second.intervals[1]; curBaseInfo = valueProducer.mCurrentBaseInfo.begin()->second[1]; EXPECT_EQ(true, curBaseInfo.hasBase); EXPECT_EQ(29, curBaseInfo.base.long_value); @@ -1659,7 +1659,7 @@ TEST(ValueMetricProducerTest, TestUseZeroDefaultBase) { ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); auto iter = valueProducer->mCurrentSlicedBucket.begin(); - auto& interval1 = iter->second[0]; + auto& interval1 = iter->second.intervals[0]; auto iterBase = valueProducer->mCurrentBaseInfo.begin(); auto& baseInfo1 = iterBase->second[0]; EXPECT_EQ(1, iter->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); @@ -1695,7 +1695,7 @@ TEST(ValueMetricProducerTest, TestUseZeroDefaultBase) { } EXPECT_TRUE(it != iter); EXPECT_TRUE(itBase != iterBase); - auto& interval2 = it->second[0]; + auto& interval2 = it->second.intervals[0]; auto& baseInfo2 = itBase->second[0]; EXPECT_EQ(2, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); EXPECT_EQ(true, baseInfo2.hasBase); @@ -1735,7 +1735,7 @@ TEST(ValueMetricProducerTest, TestUseZeroDefaultBaseWithPullFailures) { ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); const auto& it = valueProducer->mCurrentSlicedBucket.begin(); - ValueMetricProducer::Interval& interval1 = it->second[0]; + ValueMetricProducer::Interval& interval1 = it->second.intervals[0]; ValueMetricProducer::BaseInfo& baseInfo1 = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat())->second[0]; EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); @@ -1764,7 +1764,7 @@ TEST(ValueMetricProducerTest, TestUseZeroDefaultBaseWithPullFailures) { } } EXPECT_TRUE(it2 != it); - ValueMetricProducer::Interval& interval2 = it2->second[0]; + ValueMetricProducer::Interval& interval2 = it2->second.intervals[0]; ValueMetricProducer::BaseInfo& baseInfo2 = valueProducer->mCurrentBaseInfo.find(it2->first.getDimensionKeyInWhat())->second[0]; EXPECT_EQ(2, it2->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); @@ -1795,10 +1795,10 @@ TEST(ValueMetricProducerTest, TestUseZeroDefaultBaseWithPullFailures) { // Get new references now that entries have been deleted from the map const auto& it3 = valueProducer->mCurrentSlicedBucket.begin(); const auto& it4 = std::next(valueProducer->mCurrentSlicedBucket.begin()); - ASSERT_EQ(it3->second.size(), 1); - ASSERT_EQ(it4->second.size(), 1); - ValueMetricProducer::Interval& interval3 = it3->second[0]; - ValueMetricProducer::Interval& interval4 = it4->second[0]; + ASSERT_EQ(it3->second.intervals.size(), 1); + ASSERT_EQ(it4->second.intervals.size(), 1); + ValueMetricProducer::Interval& interval3 = it3->second.intervals[0]; + ValueMetricProducer::Interval& interval4 = it4->second.intervals[0]; ValueMetricProducer::BaseInfo& baseInfo3 = valueProducer->mCurrentBaseInfo.find(it3->first.getDimensionKeyInWhat())->second[0]; ValueMetricProducer::BaseInfo& baseInfo4 = @@ -1840,7 +1840,7 @@ TEST(ValueMetricProducerTest, TestTrimUnusedDimensionKey) { ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); auto iter = valueProducer->mCurrentSlicedBucket.begin(); - auto& interval1 = iter->second[0]; + auto& interval1 = iter->second.intervals[0]; auto iterBase = valueProducer->mCurrentBaseInfo.begin(); auto& baseInfo1 = iterBase->second[0]; EXPECT_EQ(1, iter->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); @@ -1878,7 +1878,7 @@ TEST(ValueMetricProducerTest, TestTrimUnusedDimensionKey) { } EXPECT_TRUE(it != iter); EXPECT_TRUE(itBase != iterBase); - auto interval2 = it->second[0]; + auto interval2 = it->second.intervals[0]; auto baseInfo2 = itBase->second[0]; EXPECT_EQ(2, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); EXPECT_EQ(true, baseInfo2.hasBase); @@ -1892,7 +1892,7 @@ TEST(ValueMetricProducerTest, TestTrimUnusedDimensionKey) { valueProducer->onDataPulled(allData, /** succeed */ true, bucket4StartTimeNs); // Only one interval left. One was trimmed. ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); - interval2 = valueProducer->mCurrentSlicedBucket.begin()->second[0]; + interval2 = valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0]; baseInfo2 = valueProducer->mCurrentBaseInfo.begin()->second[0]; EXPECT_EQ(2, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); EXPECT_EQ(true, baseInfo2.hasBase); @@ -1906,7 +1906,7 @@ TEST(ValueMetricProducerTest, TestTrimUnusedDimensionKey) { allData.push_back(CreateTwoValueLogEvent(tagId, bucket5StartTimeNs + 1, 2, 14)); valueProducer->onDataPulled(allData, /** succeed */ true, bucket5StartTimeNs); - interval2 = valueProducer->mCurrentSlicedBucket.begin()->second[0]; + interval2 = valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0]; baseInfo2 = valueProducer->mCurrentBaseInfo.begin()->second[0]; EXPECT_EQ(true, baseInfo2.hasBase); EXPECT_EQ(14, baseInfo2.base.long_value); @@ -1946,7 +1946,7 @@ TEST(ValueMetricProducerTest, TestResetBaseOnPullFailAfterConditionChange_EndOfB // has one slice ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); ValueMetricProducer::Interval& curInterval = - valueProducer->mCurrentSlicedBucket.begin()->second[0]; + valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0]; ValueMetricProducer::BaseInfo& curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0]; EXPECT_EQ(true, curBaseInfo.hasBase); EXPECT_EQ(100, curBaseInfo.base.long_value); @@ -1983,7 +1983,7 @@ TEST(ValueMetricProducerTest, TestResetBaseOnPullFailAfterConditionChange) { // has one slice ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); ValueMetricProducer::Interval& curInterval = - valueProducer->mCurrentSlicedBucket.begin()->second[0]; + valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0]; ValueMetricProducer::BaseInfo& curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0]; EXPECT_EQ(true, curBaseInfo.hasBase); EXPECT_EQ(100, curBaseInfo.base.long_value); @@ -2033,7 +2033,7 @@ TEST(ValueMetricProducerTest, TestResetBaseOnPullFailBeforeConditionChange) { valueProducer->onConditionChanged(false, bucketStartTimeNs + 1); ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); ValueMetricProducer::Interval& curInterval = - valueProducer->mCurrentSlicedBucket.begin()->second[0]; + valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0]; ValueMetricProducer::BaseInfo curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0]; EXPECT_EQ(false, curBaseInfo.hasBase); EXPECT_EQ(false, curInterval.hasValue); @@ -2106,7 +2106,7 @@ TEST(ValueMetricProducerTest, TestBaseSetOnConditionChange) { valueProducer->mHasGlobalBase = true; ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); ValueMetricProducer::Interval& curInterval = - valueProducer->mCurrentSlicedBucket.begin()->second[0]; + valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0]; ValueMetricProducer::BaseInfo curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0]; EXPECT_EQ(true, curBaseInfo.hasBase); EXPECT_EQ(100, curBaseInfo.base.long_value); @@ -2159,7 +2159,7 @@ TEST(ValueMetricProducerTest_BucketDrop, TestInvalidBucketWhenOneConditionFailed // Contains base from last pull which was successful. ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); ValueMetricProducer::Interval& curInterval = - valueProducer->mCurrentSlicedBucket.begin()->second[0]; + valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0]; ValueMetricProducer::BaseInfo curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0]; EXPECT_EQ(true, curBaseInfo.hasBase); EXPECT_EQ(140, curBaseInfo.base.long_value); @@ -2297,7 +2297,7 @@ TEST(ValueMetricProducerTest_BucketDrop, TestInvalidBucketWhenInitialPullFailed) // Contains base from last pull which was successful. ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); ValueMetricProducer::Interval& curInterval = - valueProducer->mCurrentSlicedBucket.begin()->second[0]; + valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0]; ValueMetricProducer::BaseInfo curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0]; EXPECT_EQ(true, curBaseInfo.hasBase); EXPECT_EQ(140, curBaseInfo.base.long_value); @@ -2376,7 +2376,7 @@ TEST(ValueMetricProducerTest_BucketDrop, TestInvalidBucketWhenLastPullFailed) { // Last pull failed so base has been reset. ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); ValueMetricProducer::Interval& curInterval = - valueProducer->mCurrentSlicedBucket.begin()->second[0]; + valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0]; ValueMetricProducer::BaseInfo curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0]; EXPECT_EQ(false, curBaseInfo.hasBase); EXPECT_EQ(false, curInterval.hasValue); @@ -2463,7 +2463,7 @@ TEST(ValueMetricProducerTest, TestEmptyDataResetsBase_onConditionChanged) { valueProducer->onConditionChanged(true, bucketStartTimeNs + 10); ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); ValueMetricProducer::Interval& curInterval = - valueProducer->mCurrentSlicedBucket.begin()->second[0]; + valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0]; ValueMetricProducer::BaseInfo curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0]; EXPECT_EQ(true, curBaseInfo.hasBase); EXPECT_EQ(false, curInterval.hasValue); @@ -2472,7 +2472,7 @@ TEST(ValueMetricProducerTest, TestEmptyDataResetsBase_onConditionChanged) { // Empty pull. valueProducer->onConditionChanged(false, bucketStartTimeNs + 10); ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); - curInterval = valueProducer->mCurrentSlicedBucket.begin()->second[0]; + curInterval = valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0]; curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0]; EXPECT_EQ(false, curBaseInfo.hasBase); EXPECT_EQ(false, curInterval.hasValue); @@ -2516,7 +2516,7 @@ TEST(ValueMetricProducerTest, TestEmptyDataResetsBase_onBucketBoundary) { valueProducer->onConditionChanged(true, bucketStartTimeNs + 12); ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); ValueMetricProducer::Interval& curInterval = - valueProducer->mCurrentSlicedBucket.begin()->second[0]; + valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0]; ValueMetricProducer::BaseInfo curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0]; EXPECT_EQ(true, curBaseInfo.hasBase); EXPECT_EQ(true, curInterval.hasValue); @@ -2527,7 +2527,7 @@ TEST(ValueMetricProducerTest, TestEmptyDataResetsBase_onBucketBoundary) { allData.clear(); valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs); ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); - curInterval = valueProducer->mCurrentSlicedBucket.begin()->second[0]; + curInterval = valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0]; curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0]; // Data is empty, base should be reset. EXPECT_EQ(false, curBaseInfo.hasBase); @@ -2575,12 +2575,12 @@ TEST(ValueMetricProducerTest, TestPartialResetOnBucketBoundaries) { auto baseInfoIter = valueProducer->mCurrentBaseInfo.begin(); EXPECT_EQ(true, baseInfoIter->second[0].hasBase); EXPECT_EQ(2, baseInfoIter->second[0].base.long_value); - EXPECT_EQ(false, iterator->second[0].hasValue); + EXPECT_EQ(false, iterator->second.intervals[0].hasValue); iterator++; baseInfoIter++; EXPECT_EQ(false, baseInfoIter->second[0].hasBase); EXPECT_EQ(1, baseInfoIter->second[0].base.long_value); - EXPECT_EQ(false, iterator->second[0].hasValue); + EXPECT_EQ(false, iterator->second.intervals[0].hasValue); EXPECT_EQ(true, valueProducer->mHasGlobalBase); } @@ -2679,7 +2679,7 @@ TEST(ValueMetricProducerTest, TestBucketBoundariesOnConditionChange) { valueProducer->onConditionChanged(true, bucket2StartTimeNs + 10); ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); - auto curInterval = valueProducer->mCurrentSlicedBucket.begin()->second[0]; + auto curInterval = valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0]; auto curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0]; EXPECT_EQ(true, curBaseInfo.hasBase); EXPECT_EQ(5, curBaseInfo.base.long_value); @@ -2814,7 +2814,7 @@ TEST(ValueMetricProducerTest, TestDataIsNotUpdatedWhenNoConditionChanged) { valueProducer->onConditionChanged(false, bucketStartTimeNs + 12); ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); - auto curInterval = valueProducer->mCurrentSlicedBucket.begin()->second[0]; + auto curInterval = valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0]; auto curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0]; EXPECT_EQ(true, curInterval.hasValue); EXPECT_EQ(2, curInterval.value.long_value); @@ -3048,7 +3048,7 @@ TEST(ValueMetricProducerTest, TestPulledData_noDiff_withMultipleConditionChanges // has one slice ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); ValueMetricProducer::Interval curInterval = - valueProducer->mCurrentSlicedBucket.begin()->second[0]; + valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0]; ValueMetricProducer::BaseInfo curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0]; EXPECT_EQ(false, curBaseInfo.hasBase); EXPECT_EQ(true, curInterval.hasValue); @@ -3061,7 +3061,7 @@ TEST(ValueMetricProducerTest, TestPulledData_noDiff_withMultipleConditionChanges assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {20}, {50 - 8}, {bucketStartTimeNs}, {bucket2StartTimeNs}); - curInterval = valueProducer->mCurrentSlicedBucket.begin()->second[0]; + curInterval = valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0]; curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0]; EXPECT_EQ(false, curBaseInfo.hasBase); EXPECT_EQ(false, curInterval.hasValue); @@ -3094,7 +3094,7 @@ TEST(ValueMetricProducerTest, TestPulledData_noDiff_bucketBoundaryTrue) { assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {30}, {bucketSizeNs - 8}, {bucketStartTimeNs}, {bucket2StartTimeNs}); ValueMetricProducer::Interval curInterval = - valueProducer->mCurrentSlicedBucket.begin()->second[0]; + valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0]; ValueMetricProducer::BaseInfo curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0]; EXPECT_EQ(false, curBaseInfo.hasBase); EXPECT_EQ(false, curInterval.hasValue); @@ -3998,7 +3998,7 @@ TEST(ValueMetricProducerTest, TestSlicedState) { ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, it->first.getStateValuesKey().getValues()[0].mValue.int_value); - EXPECT_FALSE(it->second[0].hasValue); + EXPECT_FALSE(it->second.intervals[0].hasValue); // Bucket status after screen state change kStateUnknown->ON. auto screenEvent = CreateScreenStateChangedEvent( @@ -4019,8 +4019,8 @@ TEST(ValueMetricProducerTest, TestSlicedState) { ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, it->first.getStateValuesKey().getValues()[0].mValue.int_value); - EXPECT_TRUE(it->second[0].hasValue); - EXPECT_EQ(2, it->second[0].value.long_value); + EXPECT_TRUE(it->second.intervals[0].hasValue); + EXPECT_EQ(2, it->second.intervals[0].value.long_value); // Bucket status after screen state change ON->OFF. screenEvent = CreateScreenStateChangedEvent(bucketStartTimeNs + 10, @@ -4040,16 +4040,16 @@ TEST(ValueMetricProducerTest, TestSlicedState) { ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); EXPECT_EQ(android::view::DisplayStateEnum::DISPLAY_STATE_ON, it->first.getStateValuesKey().getValues()[0].mValue.int_value); - EXPECT_TRUE(it->second[0].hasValue); - EXPECT_EQ(4, it->second[0].value.long_value); + EXPECT_TRUE(it->second.intervals[0].hasValue); + EXPECT_EQ(4, it->second.intervals[0].value.long_value); // Value for dimension, state key {{}, kStateUnknown} it++; EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, it->first.getStateValuesKey().getValues()[0].mValue.int_value); - EXPECT_TRUE(it->second[0].hasValue); - EXPECT_EQ(2, it->second[0].value.long_value); + EXPECT_TRUE(it->second.intervals[0].hasValue); + EXPECT_EQ(2, it->second.intervals[0].value.long_value); // Bucket status after screen state change OFF->ON. screenEvent = CreateScreenStateChangedEvent(bucketStartTimeNs + 15, @@ -4070,24 +4070,24 @@ TEST(ValueMetricProducerTest, TestSlicedState) { ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); EXPECT_EQ(android::view::DisplayStateEnum::DISPLAY_STATE_OFF, it->first.getStateValuesKey().getValues()[0].mValue.int_value); - EXPECT_TRUE(it->second[0].hasValue); - EXPECT_EQ(12, it->second[0].value.long_value); + EXPECT_TRUE(it->second.intervals[0].hasValue); + EXPECT_EQ(12, it->second.intervals[0].value.long_value); // Value for dimension, state key {{}, ON} it++; EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); EXPECT_EQ(android::view::DisplayStateEnum::DISPLAY_STATE_ON, it->first.getStateValuesKey().getValues()[0].mValue.int_value); - EXPECT_TRUE(it->second[0].hasValue); - EXPECT_EQ(4, it->second[0].value.long_value); + EXPECT_TRUE(it->second.intervals[0].hasValue); + EXPECT_EQ(4, it->second.intervals[0].value.long_value); // Value for dimension, state key {{}, kStateUnknown} it++; EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, it->first.getStateValuesKey().getValues()[0].mValue.int_value); - EXPECT_TRUE(it->second[0].hasValue); - EXPECT_EQ(2, it->second[0].value.long_value); + EXPECT_TRUE(it->second.intervals[0].hasValue); + EXPECT_EQ(2, it->second.intervals[0].value.long_value); // Start dump report and check output. ProtoOutputStream output; @@ -4209,7 +4209,7 @@ TEST(ValueMetricProducerTest, TestSlicedStateWithMap) { ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, it->first.getStateValuesKey().getValues()[0].mValue.int_value); - EXPECT_FALSE(it->second[0].hasValue); + EXPECT_FALSE(it->second.intervals[0].hasValue); // Bucket status after screen state change kStateUnknown->ON. auto screenEvent = CreateScreenStateChangedEvent( @@ -4230,8 +4230,8 @@ TEST(ValueMetricProducerTest, TestSlicedStateWithMap) { ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, it->first.getStateValuesKey().getValues()[0].mValue.int_value); - EXPECT_TRUE(it->second[0].hasValue); - EXPECT_EQ(2, it->second[0].value.long_value); + EXPECT_TRUE(it->second.intervals[0].hasValue); + EXPECT_EQ(2, it->second.intervals[0].value.long_value); // Bucket status after screen state change ON->VR. // Both ON and VR are in the same state group, so the base should not change. @@ -4253,8 +4253,8 @@ TEST(ValueMetricProducerTest, TestSlicedStateWithMap) { ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, it->first.getStateValuesKey().getValues()[0].mValue.int_value); - EXPECT_TRUE(it->second[0].hasValue); - EXPECT_EQ(2, it->second[0].value.long_value); + EXPECT_TRUE(it->second.intervals[0].hasValue); + EXPECT_EQ(2, it->second.intervals[0].value.long_value); // Bucket status after screen state change VR->ON. // Both ON and VR are in the same state group, so the base should not change. @@ -4276,8 +4276,8 @@ TEST(ValueMetricProducerTest, TestSlicedStateWithMap) { ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, it->first.getStateValuesKey().getValues()[0].mValue.int_value); - EXPECT_TRUE(it->second[0].hasValue); - EXPECT_EQ(2, it->second[0].value.long_value); + EXPECT_TRUE(it->second.intervals[0].hasValue); + EXPECT_EQ(2, it->second.intervals[0].value.long_value); // Bucket status after screen state change VR->OFF. screenEvent = CreateScreenStateChangedEvent(bucketStartTimeNs + 15, @@ -4298,16 +4298,16 @@ TEST(ValueMetricProducerTest, TestSlicedStateWithMap) { ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); EXPECT_EQ(screenOnGroup.group_id(), it->first.getStateValuesKey().getValues()[0].mValue.long_value); - EXPECT_TRUE(it->second[0].hasValue); - EXPECT_EQ(16, it->second[0].value.long_value); + EXPECT_TRUE(it->second.intervals[0].hasValue); + EXPECT_EQ(16, it->second.intervals[0].value.long_value); // Value for dimension, state key {{}, kStateUnknown} it++; EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, it->first.getStateValuesKey().getValues()[0].mValue.int_value); - EXPECT_TRUE(it->second[0].hasValue); - EXPECT_EQ(2, it->second[0].value.long_value); + EXPECT_TRUE(it->second.intervals[0].hasValue); + EXPECT_EQ(2, it->second.intervals[0].value.long_value); // Start dump report and check output. ProtoOutputStream output; @@ -4462,7 +4462,7 @@ TEST(ValueMetricProducerTest, TestSlicedStateWithPrimaryField_WithDimensions) { ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, it->first.getStateValuesKey().getValues()[0].mValue.int_value); - EXPECT_FALSE(it->second[0].hasValue); + EXPECT_FALSE(it->second.intervals[0].hasValue); // Base for dimension key {uid 2} it++; itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); @@ -4478,7 +4478,7 @@ TEST(ValueMetricProducerTest, TestSlicedStateWithPrimaryField_WithDimensions) { ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, it->first.getStateValuesKey().getValues()[0].mValue.int_value); - EXPECT_FALSE(it->second[0].hasValue); + EXPECT_FALSE(it->second.intervals[0].hasValue); // Bucket status after uid 1 process state change kStateUnknown -> Foreground. auto uidProcessEvent = CreateUidProcessStateChangedEvent( @@ -4500,8 +4500,8 @@ TEST(ValueMetricProducerTest, TestSlicedStateWithPrimaryField_WithDimensions) { ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, it->first.getStateValuesKey().getValues()[0].mValue.int_value); - EXPECT_TRUE(it->second[0].hasValue); - EXPECT_EQ(3, it->second[0].value.long_value); + EXPECT_TRUE(it->second.intervals[0].hasValue); + EXPECT_EQ(3, it->second.intervals[0].value.long_value); // Base for dimension key {uid 2} it++; @@ -4517,7 +4517,7 @@ TEST(ValueMetricProducerTest, TestSlicedStateWithPrimaryField_WithDimensions) { EXPECT_EQ(2, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); EXPECT_EQ(-1, it->first.getStateValuesKey().getValues()[0].mValue.int_value); - EXPECT_FALSE(it->second[0].hasValue); + EXPECT_FALSE(it->second.intervals[0].hasValue); // Bucket status after uid 2 process state change kStateUnknown -> Background. uidProcessEvent = CreateUidProcessStateChangedEvent( @@ -4538,8 +4538,8 @@ TEST(ValueMetricProducerTest, TestSlicedStateWithPrimaryField_WithDimensions) { EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); EXPECT_EQ(-1, it->first.getStateValuesKey().getValues()[0].mValue.int_value); - EXPECT_TRUE(it->second[0].hasValue); - EXPECT_EQ(3, it->second[0].value.long_value); + EXPECT_TRUE(it->second.intervals[0].hasValue); + EXPECT_EQ(3, it->second.intervals[0].value.long_value); // Base for dimension key {uid 2} it++; @@ -4555,8 +4555,8 @@ TEST(ValueMetricProducerTest, TestSlicedStateWithPrimaryField_WithDimensions) { EXPECT_EQ(2, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); EXPECT_EQ(-1, it->first.getStateValuesKey().getValues()[0].mValue.int_value); - EXPECT_TRUE(it->second[0].hasValue); - EXPECT_EQ(2, it->second[0].value.long_value); + EXPECT_TRUE(it->second.intervals[0].hasValue); + EXPECT_EQ(2, it->second.intervals[0].value.long_value); // Pull at end of first bucket. vector> allData; @@ -4585,7 +4585,7 @@ TEST(ValueMetricProducerTest, TestSlicedStateWithPrimaryField_WithDimensions) { ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND, it->first.getStateValuesKey().getValues()[0].mValue.int_value); - EXPECT_FALSE(it->second[0].hasValue); + EXPECT_FALSE(it->second.intervals[0].hasValue); // Base for dimension key {uid 1} it++; @@ -4602,7 +4602,7 @@ TEST(ValueMetricProducerTest, TestSlicedStateWithPrimaryField_WithDimensions) { ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); EXPECT_EQ(-1 /* kStateTracker::kUnknown */, it->first.getStateValuesKey().getValues()[0].mValue.int_value); - EXPECT_FALSE(it->second[0].hasValue); + EXPECT_FALSE(it->second.intervals[0].hasValue); // Value for key {uid 1, FOREGROUND} it++; @@ -4611,7 +4611,7 @@ TEST(ValueMetricProducerTest, TestSlicedStateWithPrimaryField_WithDimensions) { ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_FOREGROUND, it->first.getStateValuesKey().getValues()[0].mValue.int_value); - EXPECT_FALSE(it->second[0].hasValue); + EXPECT_FALSE(it->second.intervals[0].hasValue); // Value for key {uid 2, kStateUnknown} it++; @@ -4620,7 +4620,7 @@ TEST(ValueMetricProducerTest, TestSlicedStateWithPrimaryField_WithDimensions) { ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); EXPECT_EQ(-1 /* kStateTracker::kUnknown */, it->first.getStateValuesKey().getValues()[0].mValue.int_value); - EXPECT_FALSE(it->second[0].hasValue); + EXPECT_FALSE(it->second.intervals[0].hasValue); // Bucket status after uid 1 process state change from Foreground -> Background. uidProcessEvent = CreateUidProcessStateChangedEvent( @@ -4645,7 +4645,7 @@ TEST(ValueMetricProducerTest, TestSlicedStateWithPrimaryField_WithDimensions) { ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND, it->first.getStateValuesKey().getValues()[0].mValue.int_value); - EXPECT_FALSE(it->second[0].hasValue); + EXPECT_FALSE(it->second.intervals[0].hasValue); // Base for dimension key {uid 1} it++; itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); @@ -4661,7 +4661,7 @@ TEST(ValueMetricProducerTest, TestSlicedStateWithPrimaryField_WithDimensions) { ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, it->first.getStateValuesKey().getValues()[0].mValue.int_value); - EXPECT_FALSE(it->second[0].hasValue); + EXPECT_FALSE(it->second.intervals[0].hasValue); // Value for key {uid 1, FOREGROUND} it++; ASSERT_EQ(1, it->first.getDimensionKeyInWhat().getValues().size()); @@ -4669,8 +4669,8 @@ TEST(ValueMetricProducerTest, TestSlicedStateWithPrimaryField_WithDimensions) { ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_FOREGROUND, it->first.getStateValuesKey().getValues()[0].mValue.int_value); - EXPECT_TRUE(it->second[0].hasValue); - EXPECT_EQ(3, it->second[0].value.long_value); + EXPECT_TRUE(it->second.intervals[0].hasValue); + EXPECT_EQ(3, it->second.intervals[0].value.long_value); // Value for key {uid 2, kStateUnknown} it++; ASSERT_EQ(1, it->first.getDimensionKeyInWhat().getValues().size()); @@ -4678,7 +4678,7 @@ TEST(ValueMetricProducerTest, TestSlicedStateWithPrimaryField_WithDimensions) { ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, it->first.getStateValuesKey().getValues()[0].mValue.int_value); - EXPECT_FALSE(it->second[0].hasValue); + EXPECT_FALSE(it->second.intervals[0].hasValue); // Bucket status after uid 1 process state change Background->Foreground. uidProcessEvent = CreateUidProcessStateChangedEvent( @@ -4702,7 +4702,7 @@ TEST(ValueMetricProducerTest, TestSlicedStateWithPrimaryField_WithDimensions) { ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND, it->first.getStateValuesKey().getValues()[0].mValue.int_value); - EXPECT_FALSE(it->second[0].hasValue); + EXPECT_FALSE(it->second.intervals[0].hasValue); // Base for dimension key {uid 1} it++; @@ -4719,7 +4719,7 @@ TEST(ValueMetricProducerTest, TestSlicedStateWithPrimaryField_WithDimensions) { ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, it->first.getStateValuesKey().getValues()[0].mValue.int_value); - EXPECT_FALSE(it->second[0].hasValue); + EXPECT_FALSE(it->second.intervals[0].hasValue); // Value for key {uid 1, BACKGROUND} it++; @@ -4728,8 +4728,8 @@ TEST(ValueMetricProducerTest, TestSlicedStateWithPrimaryField_WithDimensions) { ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND, it->first.getStateValuesKey().getValues()[0].mValue.int_value); - EXPECT_TRUE(it->second[0].hasValue); - EXPECT_EQ(4, it->second[0].value.long_value); + EXPECT_TRUE(it->second.intervals[0].hasValue); + EXPECT_EQ(4, it->second.intervals[0].value.long_value); // Value for key {uid 1, FOREGROUND} it++; @@ -4738,8 +4738,8 @@ TEST(ValueMetricProducerTest, TestSlicedStateWithPrimaryField_WithDimensions) { ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_FOREGROUND, it->first.getStateValuesKey().getValues()[0].mValue.int_value); - EXPECT_TRUE(it->second[0].hasValue); - EXPECT_EQ(3, it->second[0].value.long_value); + EXPECT_TRUE(it->second.intervals[0].hasValue); + EXPECT_EQ(3, it->second.intervals[0].value.long_value); // Value for key {uid 2, kStateUnknown} it++; @@ -4870,13 +4870,13 @@ TEST(ValueMetricProducerTest, TestSlicedStateWithCondition) { itBase->second[0].currentState.getValues()[0].mValue.int_value); // Value for key {{}, -1} ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); - std::unordered_map>::iterator - it = valueProducer->mCurrentSlicedBucket.begin(); + std::unordered_map::iterator it = + valueProducer->mCurrentSlicedBucket.begin(); EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); EXPECT_EQ(-1 /*StateTracker::kUnknown*/, it->first.getStateValuesKey().getValues()[0].mValue.int_value); - EXPECT_FALSE(it->second[0].hasValue); + EXPECT_FALSE(it->second.intervals[0].hasValue); // Bucket status after battery saver mode OFF event. unique_ptr batterySaverOffEvent = @@ -4898,8 +4898,8 @@ TEST(ValueMetricProducerTest, TestSlicedStateWithCondition) { ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); EXPECT_EQ(BatterySaverModeStateChanged::ON, it->first.getStateValuesKey().getValues()[0].mValue.int_value); - EXPECT_TRUE(it->second[0].hasValue); - EXPECT_EQ(2, it->second[0].value.long_value); + EXPECT_TRUE(it->second.intervals[0].hasValue); + EXPECT_EQ(2, it->second.intervals[0].value.long_value); // Pull at end of first bucket. vector> allData; @@ -4936,8 +4936,8 @@ TEST(ValueMetricProducerTest, TestSlicedStateWithCondition) { ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); EXPECT_EQ(BatterySaverModeStateChanged::OFF, it->first.getStateValuesKey().getValues()[0].mValue.int_value); - EXPECT_TRUE(it->second[0].hasValue); - EXPECT_EQ(4, it->second[0].value.long_value); + EXPECT_TRUE(it->second.intervals[0].hasValue); + EXPECT_EQ(4, it->second.intervals[0].value.long_value); // Start dump report and check output. ProtoOutputStream output; -- cgit v1.2.3 From 71839ac6106da8aa7591dcd45e407bcb32af3de7 Mon Sep 17 00:00:00 2001 From: Salud Lemus Date: Fri, 21 Aug 2020 03:01:19 +0000 Subject: Refactor BaseInfo struct Create another struct that contains the vector of BaseInfos, currentState key, and hasCurrentState. Now there is a single currentState key and hasCurrentState per HashableDimensionKey. Bug: 165817484 Bug: 165018838 Test: `m statsd` Test: `m statsd_test` Test: `m` Test: `atest statsd_test` Change-Id: I3a541b255829e44d16cfcbe3f2ad14648b759224 Merged-In: Ibb065d64be3775c699583215f7f376a1a5492ac1 --- bin/src/metrics/ValueMetricProducer.cpp | 21 +- bin/src/metrics/ValueMetricProducer.h | 11 +- bin/tests/metrics/ValueMetricProducer_test.cpp | 406 +++++++++++++------------ 3 files changed, 235 insertions(+), 203 deletions(-) diff --git a/bin/src/metrics/ValueMetricProducer.cpp b/bin/src/metrics/ValueMetricProducer.cpp index d16dfd11..ce9e8a4a 100644 --- a/bin/src/metrics/ValueMetricProducer.cpp +++ b/bin/src/metrics/ValueMetricProducer.cpp @@ -456,7 +456,7 @@ void ValueMetricProducer::skipCurrentBucket(const int64_t dropTimeNs, void ValueMetricProducer::resetBase() { for (auto& slice : mCurrentBaseInfo) { - for (auto& baseInfo : slice.second) { + for (auto& baseInfo : slice.second.baseInfos) { baseInfo.hasBase = false; } } @@ -668,7 +668,7 @@ void ValueMetricProducer::accumulateEvents(const std::vectorsecond) { + for (auto& baseInfo : it->second.baseInfos) { baseInfo.hasBase = false; } } @@ -833,22 +833,21 @@ void ValueMetricProducer::onMatchedLogEventInternalLocked( return; } - vector& baseInfos = mCurrentBaseInfo[whatKey]; + DimensionsInWhatInfo& dimensionsInWhatInfo = mCurrentBaseInfo[whatKey]; + vector& baseInfos = dimensionsInWhatInfo.baseInfos; if (baseInfos.size() < mFieldMatchers.size()) { VLOG("Resizing number of intervals to %d", (int)mFieldMatchers.size()); baseInfos.resize(mFieldMatchers.size()); } - for (BaseInfo& baseInfo : baseInfos) { - if (!baseInfo.hasCurrentState) { - baseInfo.currentState = getUnknownStateKey(); - baseInfo.hasCurrentState = true; - } + if (!dimensionsInWhatInfo.hasCurrentState) { + dimensionsInWhatInfo.currentState = getUnknownStateKey(); + dimensionsInWhatInfo.hasCurrentState = true; } // We need to get the intervals stored with the previous state key so we can // close these value intervals. - const auto oldStateKey = baseInfos[0].currentState; + const auto oldStateKey = dimensionsInWhatInfo.currentState; vector& intervals = mCurrentSlicedBucket[MetricDimensionKey(whatKey, oldStateKey)].intervals; if (intervals.size() < mFieldMatchers.size()) { @@ -864,14 +863,14 @@ void ValueMetricProducer::onMatchedLogEventInternalLocked( // Discussion here: http://ag/6124370. bool useAnomalyDetection = true; + dimensionsInWhatInfo.hasCurrentState = true; + dimensionsInWhatInfo.currentState = stateKey; for (int i = 0; i < (int)mFieldMatchers.size(); i++) { const Matcher& matcher = mFieldMatchers[i]; BaseInfo& baseInfo = baseInfos[i]; Interval& interval = intervals[i]; interval.valueIndex = i; Value value; - baseInfo.hasCurrentState = true; - baseInfo.currentState = stateKey; if (!getDoubleOrLong(event, matcher, value)) { VLOG("Failed to get value %d from event %s", i, event.ToString().c_str()); StatsdStats::getInstance().noteBadValueType(mMetricId); diff --git a/bin/src/metrics/ValueMetricProducer.h b/bin/src/metrics/ValueMetricProducer.h index b394b81e..5f472d4f 100644 --- a/bin/src/metrics/ValueMetricProducer.h +++ b/bin/src/metrics/ValueMetricProducer.h @@ -213,22 +213,29 @@ private: std::vector intervals; } CurrentValueBucket; + // Holds base information for diffing values from one value field. typedef struct { // Holds current base value of the dimension. Take diff and update if necessary. Value base; // Whether there is a base to diff to. bool hasBase; + } BaseInfo; + + // State key and base information for a specific DimensionsInWhat key. + typedef struct { + std::vector baseInfos; // Last seen state value(s). HashableDimensionKey currentState; // Whether this dimensions in what key has a current state key. bool hasCurrentState; - } BaseInfo; + } DimensionsInWhatInfo; // Tracks the internal state in the ongoing aggregation bucket for each DimensionsInWhat // key and StateValuesKey pair. std::unordered_map mCurrentSlicedBucket; - std::unordered_map> mCurrentBaseInfo; + // Tracks current state key and base information for each DimensionsInWhat key. + std::unordered_map mCurrentBaseInfo; std::unordered_map mCurrentFullBucket; diff --git a/bin/tests/metrics/ValueMetricProducer_test.cpp b/bin/tests/metrics/ValueMetricProducer_test.cpp index 803251f2..b166cc1f 100644 --- a/bin/tests/metrics/ValueMetricProducer_test.cpp +++ b/bin/tests/metrics/ValueMetricProducer_test.cpp @@ -291,7 +291,8 @@ TEST(ValueMetricProducerTest, TestPulledEventsNoCondition) { ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); ValueMetricProducer::Interval curInterval = valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0]; - ValueMetricProducer::BaseInfo curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0]; + ValueMetricProducer::BaseInfo curBaseInfo = + valueProducer->mCurrentBaseInfo.begin()->second.baseInfos[0]; EXPECT_EQ(true, curBaseInfo.hasBase); EXPECT_EQ(11, curBaseInfo.base.long_value); @@ -307,7 +308,7 @@ TEST(ValueMetricProducerTest, TestPulledEventsNoCondition) { // has one slice ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); curInterval = valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0]; - curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0]; + curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second.baseInfos[0]; EXPECT_EQ(true, curBaseInfo.hasBase); EXPECT_EQ(23, curBaseInfo.base.long_value); @@ -325,7 +326,7 @@ TEST(ValueMetricProducerTest, TestPulledEventsNoCondition) { valueProducer->onDataPulled(allData, /** succeed */ true, bucket4StartTimeNs); ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); curInterval = valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0]; - curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0]; + curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second.baseInfos[0]; EXPECT_EQ(true, curBaseInfo.hasBase); EXPECT_EQ(36, curBaseInfo.base.long_value); @@ -430,7 +431,8 @@ TEST(ValueMetricProducerTest, TestPulledEventsWithFiltering) { ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); ValueMetricProducer::Interval curInterval = valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0]; - ValueMetricProducer::BaseInfo curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0]; + ValueMetricProducer::BaseInfo curBaseInfo = + valueProducer->mCurrentBaseInfo.begin()->second.baseInfos[0]; EXPECT_EQ(true, curBaseInfo.hasBase); EXPECT_EQ(11, curBaseInfo.base.long_value); @@ -459,7 +461,7 @@ TEST(ValueMetricProducerTest, TestPulledEventsWithFiltering) { valueProducer->onDataPulled(allData, /** succeed */ true, bucket4StartTimeNs); ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); curInterval = valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0]; - curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0]; + curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second.baseInfos[0]; // the base was reset EXPECT_EQ(true, curBaseInfo.hasBase); @@ -493,7 +495,8 @@ TEST(ValueMetricProducerTest, TestPulledEventsTakeAbsoluteValueOnReset) { ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); ValueMetricProducer::Interval curInterval = valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0]; - ValueMetricProducer::BaseInfo curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0]; + ValueMetricProducer::BaseInfo curBaseInfo = + valueProducer->mCurrentBaseInfo.begin()->second.baseInfos[0]; EXPECT_EQ(true, curBaseInfo.hasBase); EXPECT_EQ(11, curBaseInfo.base.long_value); @@ -506,7 +509,7 @@ TEST(ValueMetricProducerTest, TestPulledEventsTakeAbsoluteValueOnReset) { // has one slice ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); curInterval = valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0]; - curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0]; + curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second.baseInfos[0]; EXPECT_EQ(true, curBaseInfo.hasBase); EXPECT_EQ(10, curBaseInfo.base.long_value); EXPECT_EQ(false, curInterval.hasValue); @@ -520,7 +523,7 @@ TEST(ValueMetricProducerTest, TestPulledEventsTakeAbsoluteValueOnReset) { valueProducer->onDataPulled(allData, /** succeed */ true, bucket4StartTimeNs); ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); curInterval = valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0]; - curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0]; + curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second.baseInfos[0]; EXPECT_EQ(true, curBaseInfo.hasBase); EXPECT_EQ(36, curBaseInfo.base.long_value); EXPECT_EQ(false, curInterval.hasValue); @@ -553,7 +556,8 @@ TEST(ValueMetricProducerTest, TestPulledEventsTakeZeroOnReset) { ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); ValueMetricProducer::Interval curInterval = valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0]; - ValueMetricProducer::BaseInfo curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0]; + ValueMetricProducer::BaseInfo curBaseInfo = + valueProducer->mCurrentBaseInfo.begin()->second.baseInfos[0]; EXPECT_EQ(true, curBaseInfo.hasBase); EXPECT_EQ(11, curBaseInfo.base.long_value); @@ -566,7 +570,7 @@ TEST(ValueMetricProducerTest, TestPulledEventsTakeZeroOnReset) { // has one slice ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); curInterval = valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0]; - curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0]; + curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second.baseInfos[0]; EXPECT_EQ(true, curBaseInfo.hasBase); EXPECT_EQ(10, curBaseInfo.base.long_value); EXPECT_EQ(false, curInterval.hasValue); @@ -577,7 +581,7 @@ TEST(ValueMetricProducerTest, TestPulledEventsTakeZeroOnReset) { valueProducer->onDataPulled(allData, /** succeed */ true, bucket4StartTimeNs); ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); curInterval = valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0]; - curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0]; + curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second.baseInfos[0]; EXPECT_EQ(true, curBaseInfo.hasBase); EXPECT_EQ(36, curBaseInfo.base.long_value); EXPECT_EQ(false, curInterval.hasValue); @@ -628,7 +632,8 @@ TEST(ValueMetricProducerTest, TestEventsWithNonSlicedCondition) { ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); ValueMetricProducer::Interval curInterval = valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0]; - ValueMetricProducer::BaseInfo curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0]; + ValueMetricProducer::BaseInfo curBaseInfo = + valueProducer->mCurrentBaseInfo.begin()->second.baseInfos[0]; // startUpdated:false sum:0 start:100 EXPECT_EQ(true, curBaseInfo.hasBase); EXPECT_EQ(100, curBaseInfo.base.long_value); @@ -645,7 +650,7 @@ TEST(ValueMetricProducerTest, TestEventsWithNonSlicedCondition) { // has one slice ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); curInterval = valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0]; - curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0]; + curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second.baseInfos[0]; EXPECT_EQ(true, curBaseInfo.hasBase); EXPECT_EQ(110, curBaseInfo.base.long_value); EXPECT_EQ(false, curInterval.hasValue); @@ -658,7 +663,7 @@ TEST(ValueMetricProducerTest, TestEventsWithNonSlicedCondition) { // has one slice ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); curInterval = valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0]; - curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0]; + curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second.baseInfos[0]; EXPECT_EQ(true, curInterval.hasValue); EXPECT_EQ(20, curInterval.value.long_value); EXPECT_EQ(false, curBaseInfo.hasBase); @@ -883,7 +888,8 @@ TEST(ValueMetricProducerTest, TestPushedEventsWithoutCondition) { ASSERT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); ValueMetricProducer::Interval curInterval = valueProducer.mCurrentSlicedBucket.begin()->second.intervals[0]; - ValueMetricProducer::BaseInfo curBaseInfo = valueProducer.mCurrentBaseInfo.begin()->second[0]; + ValueMetricProducer::BaseInfo curBaseInfo = + valueProducer.mCurrentBaseInfo.begin()->second.baseInfos[0]; EXPECT_EQ(10, curInterval.value.long_value); EXPECT_EQ(true, curInterval.hasValue); @@ -1093,7 +1099,8 @@ TEST(ValueMetricProducerTest, TestBucketBoundaryNoCondition) { ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); ValueMetricProducer::Interval curInterval = valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0]; - ValueMetricProducer::BaseInfo curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0]; + ValueMetricProducer::BaseInfo curBaseInfo = + valueProducer->mCurrentBaseInfo.begin()->second.baseInfos[0]; // startUpdated:true sum:0 start:11 EXPECT_EQ(true, curBaseInfo.hasBase); @@ -1108,7 +1115,7 @@ TEST(ValueMetricProducerTest, TestBucketBoundaryNoCondition) { // has one slice ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); curInterval = valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0]; - curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0]; + curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second.baseInfos[0]; // tartUpdated:false sum:12 EXPECT_EQ(true, curBaseInfo.hasBase); EXPECT_EQ(23, curBaseInfo.base.long_value); @@ -1125,7 +1132,7 @@ TEST(ValueMetricProducerTest, TestBucketBoundaryNoCondition) { valueProducer->onDataPulled(allData, /** succeed */ true, bucket6StartTimeNs); ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); curInterval = valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0]; - curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0]; + curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second.baseInfos[0]; // startUpdated:false sum:12 EXPECT_EQ(true, curBaseInfo.hasBase); EXPECT_EQ(36, curBaseInfo.base.long_value); @@ -1184,7 +1191,8 @@ TEST(ValueMetricProducerTest, TestBucketBoundaryWithCondition) { ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); ValueMetricProducer::Interval curInterval = valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0]; - ValueMetricProducer::BaseInfo curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0]; + ValueMetricProducer::BaseInfo curBaseInfo = + valueProducer->mCurrentBaseInfo.begin()->second.baseInfos[0]; EXPECT_EQ(true, curBaseInfo.hasBase); EXPECT_EQ(100, curBaseInfo.base.long_value); EXPECT_EQ(false, curInterval.hasValue); @@ -1193,7 +1201,7 @@ TEST(ValueMetricProducerTest, TestBucketBoundaryWithCondition) { // pull on bucket boundary come late, condition change happens before it valueProducer->onConditionChanged(false, bucket2StartTimeNs + 1); curInterval = valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0]; - curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0]; + curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second.baseInfos[0]; assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {20}, {bucketSizeNs - 8}, {bucketStartTimeNs}, {bucket2StartTimeNs}); EXPECT_EQ(false, curBaseInfo.hasBase); @@ -1207,7 +1215,7 @@ TEST(ValueMetricProducerTest, TestBucketBoundaryWithCondition) { assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {20}, {bucketSizeNs - 8}, {bucketStartTimeNs}, {bucket2StartTimeNs}); curInterval = valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0]; - curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0]; + curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second.baseInfos[0]; EXPECT_EQ(false, curBaseInfo.hasBase); EXPECT_EQ(false, curInterval.hasValue); } @@ -1256,7 +1264,8 @@ TEST(ValueMetricProducerTest, TestBucketBoundaryWithCondition2) { ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); ValueMetricProducer::Interval curInterval = valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0]; - ValueMetricProducer::BaseInfo curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0]; + ValueMetricProducer::BaseInfo curBaseInfo = + valueProducer->mCurrentBaseInfo.begin()->second.baseInfos[0]; // startUpdated:false sum:0 start:100 EXPECT_EQ(true, curBaseInfo.hasBase); EXPECT_EQ(100, curBaseInfo.base.long_value); @@ -1269,7 +1278,7 @@ TEST(ValueMetricProducerTest, TestBucketBoundaryWithCondition2) { {bucketStartTimeNs}, {bucket2StartTimeNs}); ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); curInterval = valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0]; - curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0]; + curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second.baseInfos[0]; EXPECT_EQ(false, curBaseInfo.hasBase); EXPECT_EQ(false, curInterval.hasValue); @@ -1278,7 +1287,7 @@ TEST(ValueMetricProducerTest, TestBucketBoundaryWithCondition2) { assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {20}, {bucketSizeNs - 8}, {bucketStartTimeNs}, {bucket2StartTimeNs}); curInterval = valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0]; - curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0]; + curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second.baseInfos[0]; EXPECT_EQ(true, curBaseInfo.hasBase); EXPECT_EQ(130, curBaseInfo.base.long_value); EXPECT_EQ(false, curInterval.hasValue); @@ -1290,7 +1299,7 @@ TEST(ValueMetricProducerTest, TestBucketBoundaryWithCondition2) { valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs + 50); curInterval = valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0]; - curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0]; + curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second.baseInfos[0]; EXPECT_EQ(true, curBaseInfo.hasBase); EXPECT_EQ(140, curBaseInfo.base.long_value); EXPECT_EQ(true, curInterval.hasValue); @@ -1491,7 +1500,8 @@ TEST(ValueMetricProducerTest, TestSkipZeroDiffOutput) { ASSERT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); ValueMetricProducer::Interval curInterval = valueProducer.mCurrentSlicedBucket.begin()->second.intervals[0]; - ValueMetricProducer::BaseInfo curBaseInfo = valueProducer.mCurrentBaseInfo.begin()->second[0]; + ValueMetricProducer::BaseInfo curBaseInfo = + valueProducer.mCurrentBaseInfo.begin()->second.baseInfos[0]; EXPECT_EQ(true, curBaseInfo.hasBase); EXPECT_EQ(10, curBaseInfo.base.long_value); EXPECT_EQ(false, curInterval.hasValue); @@ -1513,7 +1523,7 @@ TEST(ValueMetricProducerTest, TestSkipZeroDiffOutput) { ASSERT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); curInterval = valueProducer.mCurrentSlicedBucket.begin()->second.intervals[0]; - curBaseInfo = valueProducer.mCurrentBaseInfo.begin()->second[0]; + curBaseInfo = valueProducer.mCurrentBaseInfo.begin()->second.baseInfos[0]; EXPECT_EQ(true, curBaseInfo.hasBase); EXPECT_EQ(15, curBaseInfo.base.long_value); EXPECT_EQ(true, curInterval.hasValue); @@ -1524,7 +1534,7 @@ TEST(ValueMetricProducerTest, TestSkipZeroDiffOutput) { valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event4); ASSERT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); curInterval = valueProducer.mCurrentSlicedBucket.begin()->second.intervals[0]; - curBaseInfo = valueProducer.mCurrentBaseInfo.begin()->second[0]; + curBaseInfo = valueProducer.mCurrentBaseInfo.begin()->second.baseInfos[0]; EXPECT_EQ(true, curBaseInfo.hasBase); EXPECT_EQ(15, curBaseInfo.base.long_value); EXPECT_EQ(true, curInterval.hasValue); @@ -1562,11 +1572,12 @@ TEST(ValueMetricProducerTest, TestSkipZeroDiffOutputMultiValue) { ASSERT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); ValueMetricProducer::Interval curInterval = valueProducer.mCurrentSlicedBucket.begin()->second.intervals[0]; - ValueMetricProducer::BaseInfo curBaseInfo = valueProducer.mCurrentBaseInfo.begin()->second[0]; + ValueMetricProducer::BaseInfo curBaseInfo = + valueProducer.mCurrentBaseInfo.begin()->second.baseInfos[0]; EXPECT_EQ(true, curBaseInfo.hasBase); EXPECT_EQ(10, curBaseInfo.base.long_value); EXPECT_EQ(false, curInterval.hasValue); - curBaseInfo = valueProducer.mCurrentBaseInfo.begin()->second[1]; + curBaseInfo = valueProducer.mCurrentBaseInfo.begin()->second.baseInfos[1]; EXPECT_EQ(true, curBaseInfo.hasBase); EXPECT_EQ(20, curBaseInfo.base.long_value); EXPECT_EQ(false, curInterval.hasValue); @@ -1576,11 +1587,11 @@ TEST(ValueMetricProducerTest, TestSkipZeroDiffOutputMultiValue) { // has one slice ASSERT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); curInterval = valueProducer.mCurrentSlicedBucket.begin()->second.intervals[0]; - curBaseInfo = valueProducer.mCurrentBaseInfo.begin()->second[0]; + curBaseInfo = valueProducer.mCurrentBaseInfo.begin()->second.baseInfos[0]; EXPECT_EQ(true, curInterval.hasValue); EXPECT_EQ(5, curInterval.value.long_value); curInterval = valueProducer.mCurrentSlicedBucket.begin()->second.intervals[1]; - curBaseInfo = valueProducer.mCurrentBaseInfo.begin()->second[1]; + curBaseInfo = valueProducer.mCurrentBaseInfo.begin()->second.baseInfos[1]; EXPECT_EQ(true, curInterval.hasValue); EXPECT_EQ(2, curInterval.value.long_value); @@ -1591,13 +1602,13 @@ TEST(ValueMetricProducerTest, TestSkipZeroDiffOutputMultiValue) { valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event3); ASSERT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); curInterval = valueProducer.mCurrentSlicedBucket.begin()->second.intervals[0]; - curBaseInfo = valueProducer.mCurrentBaseInfo.begin()->second[0]; + curBaseInfo = valueProducer.mCurrentBaseInfo.begin()->second.baseInfos[0]; EXPECT_EQ(true, curBaseInfo.hasBase); EXPECT_EQ(15, curBaseInfo.base.long_value); EXPECT_EQ(true, curInterval.hasValue); curInterval = valueProducer.mCurrentSlicedBucket.begin()->second.intervals[1]; - curBaseInfo = valueProducer.mCurrentBaseInfo.begin()->second[1]; + curBaseInfo = valueProducer.mCurrentBaseInfo.begin()->second.baseInfos[1]; EXPECT_EQ(true, curBaseInfo.hasBase); EXPECT_EQ(25, curBaseInfo.base.long_value); EXPECT_EQ(true, curInterval.hasValue); @@ -1608,12 +1619,12 @@ TEST(ValueMetricProducerTest, TestSkipZeroDiffOutputMultiValue) { valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event4); ASSERT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); curInterval = valueProducer.mCurrentSlicedBucket.begin()->second.intervals[0]; - curBaseInfo = valueProducer.mCurrentBaseInfo.begin()->second[0]; + curBaseInfo = valueProducer.mCurrentBaseInfo.begin()->second.baseInfos[0]; EXPECT_EQ(true, curBaseInfo.hasBase); EXPECT_EQ(15, curBaseInfo.base.long_value); EXPECT_EQ(true, curInterval.hasValue); curInterval = valueProducer.mCurrentSlicedBucket.begin()->second.intervals[1]; - curBaseInfo = valueProducer.mCurrentBaseInfo.begin()->second[1]; + curBaseInfo = valueProducer.mCurrentBaseInfo.begin()->second.baseInfos[1]; EXPECT_EQ(true, curBaseInfo.hasBase); EXPECT_EQ(29, curBaseInfo.base.long_value); EXPECT_EQ(true, curInterval.hasValue); @@ -1661,7 +1672,7 @@ TEST(ValueMetricProducerTest, TestUseZeroDefaultBase) { auto iter = valueProducer->mCurrentSlicedBucket.begin(); auto& interval1 = iter->second.intervals[0]; auto iterBase = valueProducer->mCurrentBaseInfo.begin(); - auto& baseInfo1 = iterBase->second[0]; + auto& baseInfo1 = iterBase->second.baseInfos[0]; EXPECT_EQ(1, iter->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); EXPECT_EQ(true, baseInfo1.hasBase); EXPECT_EQ(3, baseInfo1.base.long_value); @@ -1696,7 +1707,7 @@ TEST(ValueMetricProducerTest, TestUseZeroDefaultBase) { EXPECT_TRUE(it != iter); EXPECT_TRUE(itBase != iterBase); auto& interval2 = it->second.intervals[0]; - auto& baseInfo2 = itBase->second[0]; + auto& baseInfo2 = itBase->second.baseInfos[0]; EXPECT_EQ(2, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); EXPECT_EQ(true, baseInfo2.hasBase); EXPECT_EQ(4, baseInfo2.base.long_value); @@ -1737,7 +1748,8 @@ TEST(ValueMetricProducerTest, TestUseZeroDefaultBaseWithPullFailures) { const auto& it = valueProducer->mCurrentSlicedBucket.begin(); ValueMetricProducer::Interval& interval1 = it->second.intervals[0]; ValueMetricProducer::BaseInfo& baseInfo1 = - valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat())->second[0]; + valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()) + ->second.baseInfos[0]; EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); EXPECT_EQ(true, baseInfo1.hasBase); EXPECT_EQ(3, baseInfo1.base.long_value); @@ -1766,7 +1778,8 @@ TEST(ValueMetricProducerTest, TestUseZeroDefaultBaseWithPullFailures) { EXPECT_TRUE(it2 != it); ValueMetricProducer::Interval& interval2 = it2->second.intervals[0]; ValueMetricProducer::BaseInfo& baseInfo2 = - valueProducer->mCurrentBaseInfo.find(it2->first.getDimensionKeyInWhat())->second[0]; + valueProducer->mCurrentBaseInfo.find(it2->first.getDimensionKeyInWhat()) + ->second.baseInfos[0]; EXPECT_EQ(2, it2->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); EXPECT_EQ(true, baseInfo2.hasBase); EXPECT_EQ(4, baseInfo2.base.long_value); @@ -1800,9 +1813,11 @@ TEST(ValueMetricProducerTest, TestUseZeroDefaultBaseWithPullFailures) { ValueMetricProducer::Interval& interval3 = it3->second.intervals[0]; ValueMetricProducer::Interval& interval4 = it4->second.intervals[0]; ValueMetricProducer::BaseInfo& baseInfo3 = - valueProducer->mCurrentBaseInfo.find(it3->first.getDimensionKeyInWhat())->second[0]; + valueProducer->mCurrentBaseInfo.find(it3->first.getDimensionKeyInWhat()) + ->second.baseInfos[0]; ValueMetricProducer::BaseInfo& baseInfo4 = - valueProducer->mCurrentBaseInfo.find(it4->first.getDimensionKeyInWhat())->second[0]; + valueProducer->mCurrentBaseInfo.find(it4->first.getDimensionKeyInWhat()) + ->second.baseInfos[0]; EXPECT_EQ(true, baseInfo3.hasBase); EXPECT_EQ(5, baseInfo3.base.long_value); @@ -1842,7 +1857,7 @@ TEST(ValueMetricProducerTest, TestTrimUnusedDimensionKey) { auto iter = valueProducer->mCurrentSlicedBucket.begin(); auto& interval1 = iter->second.intervals[0]; auto iterBase = valueProducer->mCurrentBaseInfo.begin(); - auto& baseInfo1 = iterBase->second[0]; + auto& baseInfo1 = iterBase->second.baseInfos[0]; EXPECT_EQ(1, iter->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); EXPECT_EQ(true, baseInfo1.hasBase); EXPECT_EQ(3, baseInfo1.base.long_value); @@ -1879,7 +1894,7 @@ TEST(ValueMetricProducerTest, TestTrimUnusedDimensionKey) { EXPECT_TRUE(it != iter); EXPECT_TRUE(itBase != iterBase); auto interval2 = it->second.intervals[0]; - auto baseInfo2 = itBase->second[0]; + auto baseInfo2 = itBase->second.baseInfos[0]; EXPECT_EQ(2, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); EXPECT_EQ(true, baseInfo2.hasBase); EXPECT_EQ(4, baseInfo2.base.long_value); @@ -1893,7 +1908,7 @@ TEST(ValueMetricProducerTest, TestTrimUnusedDimensionKey) { // Only one interval left. One was trimmed. ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); interval2 = valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0]; - baseInfo2 = valueProducer->mCurrentBaseInfo.begin()->second[0]; + baseInfo2 = valueProducer->mCurrentBaseInfo.begin()->second.baseInfos[0]; EXPECT_EQ(2, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); EXPECT_EQ(true, baseInfo2.hasBase); EXPECT_EQ(5, baseInfo2.base.long_value); @@ -1907,7 +1922,7 @@ TEST(ValueMetricProducerTest, TestTrimUnusedDimensionKey) { valueProducer->onDataPulled(allData, /** succeed */ true, bucket5StartTimeNs); interval2 = valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0]; - baseInfo2 = valueProducer->mCurrentBaseInfo.begin()->second[0]; + baseInfo2 = valueProducer->mCurrentBaseInfo.begin()->second.baseInfos[0]; EXPECT_EQ(true, baseInfo2.hasBase); EXPECT_EQ(14, baseInfo2.base.long_value); EXPECT_EQ(false, interval2.hasValue); @@ -1947,7 +1962,8 @@ TEST(ValueMetricProducerTest, TestResetBaseOnPullFailAfterConditionChange_EndOfB ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); ValueMetricProducer::Interval& curInterval = valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0]; - ValueMetricProducer::BaseInfo& curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0]; + ValueMetricProducer::BaseInfo& curBaseInfo = + valueProducer->mCurrentBaseInfo.begin()->second.baseInfos[0]; EXPECT_EQ(true, curBaseInfo.hasBase); EXPECT_EQ(100, curBaseInfo.base.long_value); EXPECT_EQ(false, curInterval.hasValue); @@ -1984,7 +2000,8 @@ TEST(ValueMetricProducerTest, TestResetBaseOnPullFailAfterConditionChange) { ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); ValueMetricProducer::Interval& curInterval = valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0]; - ValueMetricProducer::BaseInfo& curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0]; + ValueMetricProducer::BaseInfo& curBaseInfo = + valueProducer->mCurrentBaseInfo.begin()->second.baseInfos[0]; EXPECT_EQ(true, curBaseInfo.hasBase); EXPECT_EQ(100, curBaseInfo.base.long_value); EXPECT_EQ(false, curInterval.hasValue); @@ -2034,7 +2051,8 @@ TEST(ValueMetricProducerTest, TestResetBaseOnPullFailBeforeConditionChange) { ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); ValueMetricProducer::Interval& curInterval = valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0]; - ValueMetricProducer::BaseInfo curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0]; + ValueMetricProducer::BaseInfo curBaseInfo = + valueProducer->mCurrentBaseInfo.begin()->second.baseInfos[0]; EXPECT_EQ(false, curBaseInfo.hasBase); EXPECT_EQ(false, curInterval.hasValue); EXPECT_EQ(false, valueProducer->mHasGlobalBase); @@ -2107,7 +2125,8 @@ TEST(ValueMetricProducerTest, TestBaseSetOnConditionChange) { ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); ValueMetricProducer::Interval& curInterval = valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0]; - ValueMetricProducer::BaseInfo curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0]; + ValueMetricProducer::BaseInfo curBaseInfo = + valueProducer->mCurrentBaseInfo.begin()->second.baseInfos[0]; EXPECT_EQ(true, curBaseInfo.hasBase); EXPECT_EQ(100, curBaseInfo.base.long_value); EXPECT_EQ(false, curInterval.hasValue); @@ -2160,7 +2179,8 @@ TEST(ValueMetricProducerTest_BucketDrop, TestInvalidBucketWhenOneConditionFailed ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); ValueMetricProducer::Interval& curInterval = valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0]; - ValueMetricProducer::BaseInfo curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0]; + ValueMetricProducer::BaseInfo curBaseInfo = + valueProducer->mCurrentBaseInfo.begin()->second.baseInfos[0]; EXPECT_EQ(true, curBaseInfo.hasBase); EXPECT_EQ(140, curBaseInfo.base.long_value); EXPECT_EQ(false, curInterval.hasValue); @@ -2298,7 +2318,8 @@ TEST(ValueMetricProducerTest_BucketDrop, TestInvalidBucketWhenInitialPullFailed) ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); ValueMetricProducer::Interval& curInterval = valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0]; - ValueMetricProducer::BaseInfo curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0]; + ValueMetricProducer::BaseInfo curBaseInfo = + valueProducer->mCurrentBaseInfo.begin()->second.baseInfos[0]; EXPECT_EQ(true, curBaseInfo.hasBase); EXPECT_EQ(140, curBaseInfo.base.long_value); EXPECT_EQ(false, curInterval.hasValue); @@ -2377,7 +2398,8 @@ TEST(ValueMetricProducerTest_BucketDrop, TestInvalidBucketWhenLastPullFailed) { ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); ValueMetricProducer::Interval& curInterval = valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0]; - ValueMetricProducer::BaseInfo curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0]; + ValueMetricProducer::BaseInfo curBaseInfo = + valueProducer->mCurrentBaseInfo.begin()->second.baseInfos[0]; EXPECT_EQ(false, curBaseInfo.hasBase); EXPECT_EQ(false, curInterval.hasValue); EXPECT_EQ(false, valueProducer->mHasGlobalBase); @@ -2464,7 +2486,8 @@ TEST(ValueMetricProducerTest, TestEmptyDataResetsBase_onConditionChanged) { ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); ValueMetricProducer::Interval& curInterval = valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0]; - ValueMetricProducer::BaseInfo curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0]; + ValueMetricProducer::BaseInfo curBaseInfo = + valueProducer->mCurrentBaseInfo.begin()->second.baseInfos[0]; EXPECT_EQ(true, curBaseInfo.hasBase); EXPECT_EQ(false, curInterval.hasValue); EXPECT_EQ(true, valueProducer->mHasGlobalBase); @@ -2473,7 +2496,7 @@ TEST(ValueMetricProducerTest, TestEmptyDataResetsBase_onConditionChanged) { valueProducer->onConditionChanged(false, bucketStartTimeNs + 10); ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); curInterval = valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0]; - curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0]; + curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second.baseInfos[0]; EXPECT_EQ(false, curBaseInfo.hasBase); EXPECT_EQ(false, curInterval.hasValue); EXPECT_EQ(false, valueProducer->mHasGlobalBase); @@ -2517,7 +2540,8 @@ TEST(ValueMetricProducerTest, TestEmptyDataResetsBase_onBucketBoundary) { ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); ValueMetricProducer::Interval& curInterval = valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0]; - ValueMetricProducer::BaseInfo curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0]; + ValueMetricProducer::BaseInfo curBaseInfo = + valueProducer->mCurrentBaseInfo.begin()->second.baseInfos[0]; EXPECT_EQ(true, curBaseInfo.hasBase); EXPECT_EQ(true, curInterval.hasValue); EXPECT_EQ(true, valueProducer->mHasGlobalBase); @@ -2528,7 +2552,7 @@ TEST(ValueMetricProducerTest, TestEmptyDataResetsBase_onBucketBoundary) { valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs); ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); curInterval = valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0]; - curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0]; + curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second.baseInfos[0]; // Data is empty, base should be reset. EXPECT_EQ(false, curBaseInfo.hasBase); EXPECT_EQ(5, curBaseInfo.base.long_value); @@ -2573,13 +2597,13 @@ TEST(ValueMetricProducerTest, TestPartialResetOnBucketBoundaries) { ASSERT_EQ(2UL, valueProducer->mCurrentSlicedBucket.size()); auto iterator = valueProducer->mCurrentSlicedBucket.begin(); auto baseInfoIter = valueProducer->mCurrentBaseInfo.begin(); - EXPECT_EQ(true, baseInfoIter->second[0].hasBase); - EXPECT_EQ(2, baseInfoIter->second[0].base.long_value); + EXPECT_EQ(true, baseInfoIter->second.baseInfos[0].hasBase); + EXPECT_EQ(2, baseInfoIter->second.baseInfos[0].base.long_value); EXPECT_EQ(false, iterator->second.intervals[0].hasValue); iterator++; baseInfoIter++; - EXPECT_EQ(false, baseInfoIter->second[0].hasBase); - EXPECT_EQ(1, baseInfoIter->second[0].base.long_value); + EXPECT_EQ(false, baseInfoIter->second.baseInfos[0].hasBase); + EXPECT_EQ(1, baseInfoIter->second.baseInfos[0].base.long_value); EXPECT_EQ(false, iterator->second.intervals[0].hasValue); EXPECT_EQ(true, valueProducer->mHasGlobalBase); @@ -2680,7 +2704,7 @@ TEST(ValueMetricProducerTest, TestBucketBoundariesOnConditionChange) { valueProducer->onConditionChanged(true, bucket2StartTimeNs + 10); ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); auto curInterval = valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0]; - auto curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0]; + auto curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second.baseInfos[0]; EXPECT_EQ(true, curBaseInfo.hasBase); EXPECT_EQ(5, curBaseInfo.base.long_value); EXPECT_EQ(false, curInterval.hasValue); @@ -2815,7 +2839,7 @@ TEST(ValueMetricProducerTest, TestDataIsNotUpdatedWhenNoConditionChanged) { ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); auto curInterval = valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0]; - auto curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0]; + auto curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second.baseInfos[0]; EXPECT_EQ(true, curInterval.hasValue); EXPECT_EQ(2, curInterval.value.long_value); @@ -3049,7 +3073,8 @@ TEST(ValueMetricProducerTest, TestPulledData_noDiff_withMultipleConditionChanges ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); ValueMetricProducer::Interval curInterval = valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0]; - ValueMetricProducer::BaseInfo curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0]; + ValueMetricProducer::BaseInfo curBaseInfo = + valueProducer->mCurrentBaseInfo.begin()->second.baseInfos[0]; EXPECT_EQ(false, curBaseInfo.hasBase); EXPECT_EQ(true, curInterval.hasValue); EXPECT_EQ(20, curInterval.value.long_value); @@ -3062,7 +3087,7 @@ TEST(ValueMetricProducerTest, TestPulledData_noDiff_withMultipleConditionChanges assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {20}, {50 - 8}, {bucketStartTimeNs}, {bucket2StartTimeNs}); curInterval = valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0]; - curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0]; + curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second.baseInfos[0]; EXPECT_EQ(false, curBaseInfo.hasBase); EXPECT_EQ(false, curInterval.hasValue); } @@ -3095,7 +3120,8 @@ TEST(ValueMetricProducerTest, TestPulledData_noDiff_bucketBoundaryTrue) { {bucketStartTimeNs}, {bucket2StartTimeNs}); ValueMetricProducer::Interval curInterval = valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0]; - ValueMetricProducer::BaseInfo curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0]; + ValueMetricProducer::BaseInfo curBaseInfo = + valueProducer->mCurrentBaseInfo.begin()->second.baseInfos[0]; EXPECT_EQ(false, curBaseInfo.hasBase); EXPECT_EQ(false, curInterval.hasValue); } @@ -3987,12 +4013,12 @@ TEST(ValueMetricProducerTest, TestSlicedState) { // Base for dimension key {} auto it = valueProducer->mCurrentSlicedBucket.begin(); auto itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); - EXPECT_TRUE(itBase->second[0].hasBase); - EXPECT_EQ(3, itBase->second[0].base.long_value); - EXPECT_TRUE(itBase->second[0].hasCurrentState); - ASSERT_EQ(1, itBase->second[0].currentState.getValues().size()); + EXPECT_TRUE(itBase->second.baseInfos[0].hasBase); + EXPECT_EQ(3, itBase->second.baseInfos[0].base.long_value); + EXPECT_TRUE(itBase->second.hasCurrentState); + ASSERT_EQ(1, itBase->second.currentState.getValues().size()); EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, - itBase->second[0].currentState.getValues()[0].mValue.int_value); + itBase->second.currentState.getValues()[0].mValue.int_value); // Value for dimension, state key {{}, kStateUnknown} EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); @@ -4008,12 +4034,12 @@ TEST(ValueMetricProducerTest, TestSlicedState) { // Base for dimension key {} it = valueProducer->mCurrentSlicedBucket.begin(); itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); - EXPECT_TRUE(itBase->second[0].hasBase); - EXPECT_EQ(5, itBase->second[0].base.long_value); - EXPECT_TRUE(itBase->second[0].hasCurrentState); - ASSERT_EQ(1, itBase->second[0].currentState.getValues().size()); + EXPECT_TRUE(itBase->second.baseInfos[0].hasBase); + EXPECT_EQ(5, itBase->second.baseInfos[0].base.long_value); + EXPECT_TRUE(itBase->second.hasCurrentState); + ASSERT_EQ(1, itBase->second.currentState.getValues().size()); EXPECT_EQ(android::view::DisplayStateEnum::DISPLAY_STATE_ON, - itBase->second[0].currentState.getValues()[0].mValue.int_value); + itBase->second.currentState.getValues()[0].mValue.int_value); // Value for dimension, state key {{}, kStateUnknown} EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); @@ -4030,11 +4056,11 @@ TEST(ValueMetricProducerTest, TestSlicedState) { // Base for dimension key {} it = valueProducer->mCurrentSlicedBucket.begin(); itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); - EXPECT_TRUE(itBase->second[0].hasBase); - EXPECT_EQ(9, itBase->second[0].base.long_value); - EXPECT_TRUE(itBase->second[0].hasCurrentState); + EXPECT_TRUE(itBase->second.baseInfos[0].hasBase); + EXPECT_EQ(9, itBase->second.baseInfos[0].base.long_value); + EXPECT_TRUE(itBase->second.hasCurrentState); EXPECT_EQ(android::view::DisplayStateEnum::DISPLAY_STATE_OFF, - itBase->second[0].currentState.getValues()[0].mValue.int_value); + itBase->second.currentState.getValues()[0].mValue.int_value); // Value for dimension, state key {{}, ON} EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); @@ -4059,12 +4085,12 @@ TEST(ValueMetricProducerTest, TestSlicedState) { // Base for dimension key {} it = valueProducer->mCurrentSlicedBucket.begin(); itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); - EXPECT_TRUE(itBase->second[0].hasBase); - EXPECT_EQ(21, itBase->second[0].base.long_value); - EXPECT_TRUE(itBase->second[0].hasCurrentState); - ASSERT_EQ(1, itBase->second[0].currentState.getValues().size()); + EXPECT_TRUE(itBase->second.baseInfos[0].hasBase); + EXPECT_EQ(21, itBase->second.baseInfos[0].base.long_value); + EXPECT_TRUE(itBase->second.hasCurrentState); + ASSERT_EQ(1, itBase->second.currentState.getValues().size()); EXPECT_EQ(android::view::DisplayStateEnum::DISPLAY_STATE_ON, - itBase->second[0].currentState.getValues()[0].mValue.int_value); + itBase->second.currentState.getValues()[0].mValue.int_value); // Value for dimension, state key {{}, OFF} EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); @@ -4198,12 +4224,12 @@ TEST(ValueMetricProducerTest, TestSlicedStateWithMap) { // Base for dimension key {} auto it = valueProducer->mCurrentSlicedBucket.begin(); auto itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); - EXPECT_TRUE(itBase->second[0].hasBase); - EXPECT_EQ(3, itBase->second[0].base.long_value); - EXPECT_TRUE(itBase->second[0].hasCurrentState); - ASSERT_EQ(1, itBase->second[0].currentState.getValues().size()); + EXPECT_TRUE(itBase->second.baseInfos[0].hasBase); + EXPECT_EQ(3, itBase->second.baseInfos[0].base.long_value); + EXPECT_TRUE(itBase->second.hasCurrentState); + ASSERT_EQ(1, itBase->second.currentState.getValues().size()); EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, - itBase->second[0].currentState.getValues()[0].mValue.int_value); + itBase->second.currentState.getValues()[0].mValue.int_value); // Value for dimension, state key {{}, {kStateUnknown}} EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); @@ -4219,12 +4245,12 @@ TEST(ValueMetricProducerTest, TestSlicedStateWithMap) { // Base for dimension key {} it = valueProducer->mCurrentSlicedBucket.begin(); itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); - EXPECT_TRUE(itBase->second[0].hasBase); - EXPECT_EQ(5, itBase->second[0].base.long_value); - EXPECT_TRUE(itBase->second[0].hasCurrentState); - ASSERT_EQ(1, itBase->second[0].currentState.getValues().size()); + EXPECT_TRUE(itBase->second.baseInfos[0].hasBase); + EXPECT_EQ(5, itBase->second.baseInfos[0].base.long_value); + EXPECT_TRUE(itBase->second.hasCurrentState); + ASSERT_EQ(1, itBase->second.currentState.getValues().size()); EXPECT_EQ(screenOnGroup.group_id(), - itBase->second[0].currentState.getValues()[0].mValue.long_value); + itBase->second.currentState.getValues()[0].mValue.long_value); // Value for dimension, state key {{}, kStateUnknown} EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); @@ -4242,12 +4268,12 @@ TEST(ValueMetricProducerTest, TestSlicedStateWithMap) { // Base for dimension key {} it = valueProducer->mCurrentSlicedBucket.begin(); itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); - EXPECT_TRUE(itBase->second[0].hasBase); - EXPECT_EQ(5, itBase->second[0].base.long_value); - EXPECT_TRUE(itBase->second[0].hasCurrentState); - ASSERT_EQ(1, itBase->second[0].currentState.getValues().size()); + EXPECT_TRUE(itBase->second.baseInfos[0].hasBase); + EXPECT_EQ(5, itBase->second.baseInfos[0].base.long_value); + EXPECT_TRUE(itBase->second.hasCurrentState); + ASSERT_EQ(1, itBase->second.currentState.getValues().size()); EXPECT_EQ(screenOnGroup.group_id(), - itBase->second[0].currentState.getValues()[0].mValue.int_value); + itBase->second.currentState.getValues()[0].mValue.int_value); // Value for dimension, state key {{}, kStateUnknown} EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); @@ -4265,12 +4291,12 @@ TEST(ValueMetricProducerTest, TestSlicedStateWithMap) { // Base for dimension key {} it = valueProducer->mCurrentSlicedBucket.begin(); itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); - EXPECT_TRUE(itBase->second[0].hasBase); - EXPECT_EQ(5, itBase->second[0].base.long_value); - EXPECT_TRUE(itBase->second[0].hasCurrentState); - ASSERT_EQ(1, itBase->second[0].currentState.getValues().size()); + EXPECT_TRUE(itBase->second.baseInfos[0].hasBase); + EXPECT_EQ(5, itBase->second.baseInfos[0].base.long_value); + EXPECT_TRUE(itBase->second.hasCurrentState); + ASSERT_EQ(1, itBase->second.currentState.getValues().size()); EXPECT_EQ(screenOnGroup.group_id(), - itBase->second[0].currentState.getValues()[0].mValue.int_value); + itBase->second.currentState.getValues()[0].mValue.int_value); // Value for dimension, state key {{}, kStateUnknown} EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); @@ -4287,12 +4313,12 @@ TEST(ValueMetricProducerTest, TestSlicedStateWithMap) { // Base for dimension key {} it = valueProducer->mCurrentSlicedBucket.begin(); itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); - EXPECT_TRUE(itBase->second[0].hasBase); - EXPECT_EQ(21, itBase->second[0].base.long_value); - EXPECT_TRUE(itBase->second[0].hasCurrentState); - ASSERT_EQ(1, itBase->second[0].currentState.getValues().size()); + EXPECT_TRUE(itBase->second.baseInfos[0].hasBase); + EXPECT_EQ(21, itBase->second.baseInfos[0].base.long_value); + EXPECT_TRUE(itBase->second.hasCurrentState); + ASSERT_EQ(1, itBase->second.currentState.getValues().size()); EXPECT_EQ(screenOffGroup.group_id(), - itBase->second[0].currentState.getValues()[0].mValue.int_value); + itBase->second.currentState.getValues()[0].mValue.int_value); // Value for dimension, state key {{}, ON GROUP} EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); @@ -4450,12 +4476,12 @@ TEST(ValueMetricProducerTest, TestSlicedStateWithPrimaryField_WithDimensions) { // Base for dimension key {uid 1}. auto it = valueProducer->mCurrentSlicedBucket.begin(); auto itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); - EXPECT_TRUE(itBase->second[0].hasBase); - EXPECT_EQ(3, itBase->second[0].base.long_value); - EXPECT_TRUE(itBase->second[0].hasCurrentState); - ASSERT_EQ(1, itBase->second[0].currentState.getValues().size()); + EXPECT_TRUE(itBase->second.baseInfos[0].hasBase); + EXPECT_EQ(3, itBase->second.baseInfos[0].base.long_value); + EXPECT_TRUE(itBase->second.hasCurrentState); + ASSERT_EQ(1, itBase->second.currentState.getValues().size()); EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, - itBase->second[0].currentState.getValues()[0].mValue.int_value); + itBase->second.currentState.getValues()[0].mValue.int_value); // Value for dimension, state key {{uid 1}, kStateUnknown} ASSERT_EQ(1, it->first.getDimensionKeyInWhat().getValues().size()); EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); @@ -4466,12 +4492,12 @@ TEST(ValueMetricProducerTest, TestSlicedStateWithPrimaryField_WithDimensions) { // Base for dimension key {uid 2} it++; itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); - EXPECT_TRUE(itBase->second[0].hasBase); - EXPECT_EQ(7, itBase->second[0].base.long_value); - EXPECT_TRUE(itBase->second[0].hasCurrentState); - ASSERT_EQ(1, itBase->second[0].currentState.getValues().size()); + EXPECT_TRUE(itBase->second.baseInfos[0].hasBase); + EXPECT_EQ(7, itBase->second.baseInfos[0].base.long_value); + EXPECT_TRUE(itBase->second.hasCurrentState); + ASSERT_EQ(1, itBase->second.currentState.getValues().size()); EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, - itBase->second[0].currentState.getValues()[0].mValue.int_value); + itBase->second.currentState.getValues()[0].mValue.int_value); // Value for dimension, state key {{uid 2}, kStateUnknown} ASSERT_EQ(1, it->first.getDimensionKeyInWhat().getValues().size()); EXPECT_EQ(2, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); @@ -4488,12 +4514,12 @@ TEST(ValueMetricProducerTest, TestSlicedStateWithPrimaryField_WithDimensions) { // Base for dimension key {uid 1}. it = valueProducer->mCurrentSlicedBucket.begin(); itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); - EXPECT_TRUE(itBase->second[0].hasBase); - EXPECT_EQ(6, itBase->second[0].base.long_value); - EXPECT_TRUE(itBase->second[0].hasCurrentState); - ASSERT_EQ(1, itBase->second[0].currentState.getValues().size()); + EXPECT_TRUE(itBase->second.baseInfos[0].hasBase); + EXPECT_EQ(6, itBase->second.baseInfos[0].base.long_value); + EXPECT_TRUE(itBase->second.hasCurrentState); + ASSERT_EQ(1, itBase->second.currentState.getValues().size()); EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_FOREGROUND, - itBase->second[0].currentState.getValues()[0].mValue.int_value); + itBase->second.currentState.getValues()[0].mValue.int_value); // Value for key {uid 1, kStateUnknown}. ASSERT_EQ(1, it->first.getDimensionKeyInWhat().getValues().size()); EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); @@ -4506,12 +4532,12 @@ TEST(ValueMetricProducerTest, TestSlicedStateWithPrimaryField_WithDimensions) { // Base for dimension key {uid 2} it++; itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); - EXPECT_TRUE(itBase->second[0].hasBase); - EXPECT_EQ(7, itBase->second[0].base.long_value); - EXPECT_TRUE(itBase->second[0].hasCurrentState); - ASSERT_EQ(1, itBase->second[0].currentState.getValues().size()); + EXPECT_TRUE(itBase->second.baseInfos[0].hasBase); + EXPECT_EQ(7, itBase->second.baseInfos[0].base.long_value); + EXPECT_TRUE(itBase->second.hasCurrentState); + ASSERT_EQ(1, itBase->second.currentState.getValues().size()); EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, - itBase->second[0].currentState.getValues()[0].mValue.int_value); + itBase->second.currentState.getValues()[0].mValue.int_value); // Value for key {uid 2, kStateUnknown} ASSERT_EQ(1, it->first.getDimensionKeyInWhat().getValues().size()); EXPECT_EQ(2, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); @@ -4527,12 +4553,12 @@ TEST(ValueMetricProducerTest, TestSlicedStateWithPrimaryField_WithDimensions) { // Base for dimension key {uid 1}. it = valueProducer->mCurrentSlicedBucket.begin(); itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); - EXPECT_TRUE(itBase->second[0].hasBase); - EXPECT_EQ(6, itBase->second[0].base.long_value); - EXPECT_TRUE(itBase->second[0].hasCurrentState); - ASSERT_EQ(1, itBase->second[0].currentState.getValues().size()); + EXPECT_TRUE(itBase->second.baseInfos[0].hasBase); + EXPECT_EQ(6, itBase->second.baseInfos[0].base.long_value); + EXPECT_TRUE(itBase->second.hasCurrentState); + ASSERT_EQ(1, itBase->second.currentState.getValues().size()); EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_FOREGROUND, - itBase->second[0].currentState.getValues()[0].mValue.int_value); + itBase->second.currentState.getValues()[0].mValue.int_value); // Value for key {uid 1, kStateUnknown}. ASSERT_EQ(1, it->first.getDimensionKeyInWhat().getValues().size()); EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); @@ -4544,12 +4570,12 @@ TEST(ValueMetricProducerTest, TestSlicedStateWithPrimaryField_WithDimensions) { // Base for dimension key {uid 2} it++; itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); - EXPECT_TRUE(itBase->second[0].hasBase); - EXPECT_EQ(9, itBase->second[0].base.long_value); - EXPECT_TRUE(itBase->second[0].hasCurrentState); - ASSERT_EQ(1, itBase->second[0].currentState.getValues().size()); + EXPECT_TRUE(itBase->second.baseInfos[0].hasBase); + EXPECT_EQ(9, itBase->second.baseInfos[0].base.long_value); + EXPECT_TRUE(itBase->second.hasCurrentState); + ASSERT_EQ(1, itBase->second.currentState.getValues().size()); EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND, - itBase->second[0].currentState.getValues()[0].mValue.int_value); + itBase->second.currentState.getValues()[0].mValue.int_value); // Value for key {uid 2, kStateUnknown} ASSERT_EQ(1, it->first.getDimensionKeyInWhat().getValues().size()); EXPECT_EQ(2, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); @@ -4573,12 +4599,12 @@ TEST(ValueMetricProducerTest, TestSlicedStateWithPrimaryField_WithDimensions) { it = valueProducer->mCurrentSlicedBucket.begin(); EXPECT_EQ(2, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); - EXPECT_TRUE(itBase->second[0].hasBase); - EXPECT_EQ(15, itBase->second[0].base.long_value); - EXPECT_TRUE(itBase->second[0].hasCurrentState); - ASSERT_EQ(1, itBase->second[0].currentState.getValues().size()); + EXPECT_TRUE(itBase->second.baseInfos[0].hasBase); + EXPECT_EQ(15, itBase->second.baseInfos[0].base.long_value); + EXPECT_TRUE(itBase->second.hasCurrentState); + ASSERT_EQ(1, itBase->second.currentState.getValues().size()); EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND, - itBase->second[0].currentState.getValues()[0].mValue.int_value); + itBase->second.currentState.getValues()[0].mValue.int_value); // Value for key {uid 2, BACKGROUND}. ASSERT_EQ(1, it->first.getDimensionKeyInWhat().getValues().size()); EXPECT_EQ(2, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); @@ -4590,12 +4616,12 @@ TEST(ValueMetricProducerTest, TestSlicedStateWithPrimaryField_WithDimensions) { // Base for dimension key {uid 1} it++; itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); - EXPECT_TRUE(itBase->second[0].hasBase); - EXPECT_EQ(10, itBase->second[0].base.long_value); - EXPECT_TRUE(itBase->second[0].hasCurrentState); - ASSERT_EQ(1, itBase->second[0].currentState.getValues().size()); + EXPECT_TRUE(itBase->second.baseInfos[0].hasBase); + EXPECT_EQ(10, itBase->second.baseInfos[0].base.long_value); + EXPECT_TRUE(itBase->second.hasCurrentState); + ASSERT_EQ(1, itBase->second.currentState.getValues().size()); EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_FOREGROUND, - itBase->second[0].currentState.getValues()[0].mValue.int_value); + itBase->second.currentState.getValues()[0].mValue.int_value); // Value for key {uid 1, kStateUnknown} ASSERT_EQ(1, it->first.getDimensionKeyInWhat().getValues().size()); EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); @@ -4633,12 +4659,12 @@ TEST(ValueMetricProducerTest, TestSlicedStateWithPrimaryField_WithDimensions) { // Base for dimension key {uid 2}. it = valueProducer->mCurrentSlicedBucket.begin(); itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); - EXPECT_TRUE(itBase->second[0].hasBase); - EXPECT_EQ(15, itBase->second[0].base.long_value); - EXPECT_TRUE(itBase->second[0].hasCurrentState); - ASSERT_EQ(1, itBase->second[0].currentState.getValues().size()); + EXPECT_TRUE(itBase->second.baseInfos[0].hasBase); + EXPECT_EQ(15, itBase->second.baseInfos[0].base.long_value); + EXPECT_TRUE(itBase->second.hasCurrentState); + ASSERT_EQ(1, itBase->second.currentState.getValues().size()); EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND, - itBase->second[0].currentState.getValues()[0].mValue.int_value); + itBase->second.currentState.getValues()[0].mValue.int_value); // Value for key {uid 2, BACKGROUND}. ASSERT_EQ(1, it->first.getDimensionKeyInWhat().getValues().size()); EXPECT_EQ(2, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); @@ -4649,12 +4675,12 @@ TEST(ValueMetricProducerTest, TestSlicedStateWithPrimaryField_WithDimensions) { // Base for dimension key {uid 1} it++; itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); - EXPECT_TRUE(itBase->second[0].hasBase); - EXPECT_EQ(13, itBase->second[0].base.long_value); - EXPECT_TRUE(itBase->second[0].hasCurrentState); - ASSERT_EQ(1, itBase->second[0].currentState.getValues().size()); + EXPECT_TRUE(itBase->second.baseInfos[0].hasBase); + EXPECT_EQ(13, itBase->second.baseInfos[0].base.long_value); + EXPECT_TRUE(itBase->second.hasCurrentState); + ASSERT_EQ(1, itBase->second.currentState.getValues().size()); EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND, - itBase->second[0].currentState.getValues()[0].mValue.int_value); + itBase->second.currentState.getValues()[0].mValue.int_value); // Value for key {uid 1, kStateUnknown} ASSERT_EQ(1, it->first.getDimensionKeyInWhat().getValues().size()); EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); @@ -4690,12 +4716,12 @@ TEST(ValueMetricProducerTest, TestSlicedStateWithPrimaryField_WithDimensions) { // Base for dimension key {uid 2} it = valueProducer->mCurrentSlicedBucket.begin(); itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); - EXPECT_TRUE(itBase->second[0].hasBase); - EXPECT_EQ(15, itBase->second[0].base.long_value); - EXPECT_TRUE(itBase->second[0].hasCurrentState); - ASSERT_EQ(1, itBase->second[0].currentState.getValues().size()); + EXPECT_TRUE(itBase->second.baseInfos[0].hasBase); + EXPECT_EQ(15, itBase->second.baseInfos[0].base.long_value); + EXPECT_TRUE(itBase->second.hasCurrentState); + ASSERT_EQ(1, itBase->second.currentState.getValues().size()); EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND, - itBase->second[0].currentState.getValues()[0].mValue.int_value); + itBase->second.currentState.getValues()[0].mValue.int_value); // Value for key {uid 2, BACKGROUND} ASSERT_EQ(1, it->first.getDimensionKeyInWhat().getValues().size()); EXPECT_EQ(2, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); @@ -4707,12 +4733,12 @@ TEST(ValueMetricProducerTest, TestSlicedStateWithPrimaryField_WithDimensions) { // Base for dimension key {uid 1} it++; itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); - EXPECT_TRUE(itBase->second[0].hasBase); - EXPECT_EQ(17, itBase->second[0].base.long_value); - EXPECT_TRUE(itBase->second[0].hasCurrentState); - ASSERT_EQ(1, itBase->second[0].currentState.getValues().size()); + EXPECT_TRUE(itBase->second.baseInfos[0].hasBase); + EXPECT_EQ(17, itBase->second.baseInfos[0].base.long_value); + EXPECT_TRUE(itBase->second.hasCurrentState); + ASSERT_EQ(1, itBase->second.currentState.getValues().size()); EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_FOREGROUND, - itBase->second[0].currentState.getValues()[0].mValue.int_value); + itBase->second.currentState.getValues()[0].mValue.int_value); // Value for key {uid 1, kStateUnknown} ASSERT_EQ(1, it->first.getDimensionKeyInWhat().getValues().size()); EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); @@ -4860,14 +4886,14 @@ TEST(ValueMetricProducerTest, TestSlicedStateWithCondition) { valueProducer->onConditionChanged(true, bucketStartTimeNs + 20 * NS_PER_SEC); // Base for dimension key {} ASSERT_EQ(1UL, valueProducer->mCurrentBaseInfo.size()); - std::unordered_map>::iterator + std::unordered_map::iterator itBase = valueProducer->mCurrentBaseInfo.find(DEFAULT_DIMENSION_KEY); - EXPECT_TRUE(itBase->second[0].hasBase); - EXPECT_EQ(3, itBase->second[0].base.long_value); - EXPECT_TRUE(itBase->second[0].hasCurrentState); - ASSERT_EQ(1, itBase->second[0].currentState.getValues().size()); + EXPECT_TRUE(itBase->second.baseInfos[0].hasBase); + EXPECT_EQ(3, itBase->second.baseInfos[0].base.long_value); + EXPECT_TRUE(itBase->second.hasCurrentState); + ASSERT_EQ(1, itBase->second.currentState.getValues().size()); EXPECT_EQ(BatterySaverModeStateChanged::ON, - itBase->second[0].currentState.getValues()[0].mValue.int_value); + itBase->second.currentState.getValues()[0].mValue.int_value); // Value for key {{}, -1} ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); std::unordered_map::iterator it = @@ -4885,12 +4911,12 @@ TEST(ValueMetricProducerTest, TestSlicedStateWithCondition) { // Base for dimension key {} ASSERT_EQ(1UL, valueProducer->mCurrentBaseInfo.size()); itBase = valueProducer->mCurrentBaseInfo.find(DEFAULT_DIMENSION_KEY); - EXPECT_TRUE(itBase->second[0].hasBase); - EXPECT_EQ(5, itBase->second[0].base.long_value); - EXPECT_TRUE(itBase->second[0].hasCurrentState); - ASSERT_EQ(1, itBase->second[0].currentState.getValues().size()); + EXPECT_TRUE(itBase->second.baseInfos[0].hasBase); + EXPECT_EQ(5, itBase->second.baseInfos[0].base.long_value); + EXPECT_TRUE(itBase->second.hasCurrentState); + ASSERT_EQ(1, itBase->second.currentState.getValues().size()); EXPECT_EQ(BatterySaverModeStateChanged::OFF, - itBase->second[0].currentState.getValues()[0].mValue.int_value); + itBase->second.currentState.getValues()[0].mValue.int_value); // Value for key {{}, ON} ASSERT_EQ(2UL, valueProducer->mCurrentSlicedBucket.size()); it = valueProducer->mCurrentSlicedBucket.begin(); @@ -4912,23 +4938,23 @@ TEST(ValueMetricProducerTest, TestSlicedStateWithCondition) { // Base for dimension key {} ASSERT_EQ(1UL, valueProducer->mCurrentBaseInfo.size()); itBase = valueProducer->mCurrentBaseInfo.find(DEFAULT_DIMENSION_KEY); - EXPECT_TRUE(itBase->second[0].hasBase); - EXPECT_EQ(11, itBase->second[0].base.long_value); - EXPECT_TRUE(itBase->second[0].hasCurrentState); - ASSERT_EQ(1, itBase->second[0].currentState.getValues().size()); + EXPECT_TRUE(itBase->second.baseInfos[0].hasBase); + EXPECT_EQ(11, itBase->second.baseInfos[0].base.long_value); + EXPECT_TRUE(itBase->second.hasCurrentState); + ASSERT_EQ(1, itBase->second.currentState.getValues().size()); EXPECT_EQ(BatterySaverModeStateChanged::OFF, - itBase->second[0].currentState.getValues()[0].mValue.int_value); + itBase->second.currentState.getValues()[0].mValue.int_value); // Bucket 2 status after condition change to false. valueProducer->onConditionChanged(false, bucket2StartTimeNs + 10 * NS_PER_SEC); // Base for dimension key {} ASSERT_EQ(1UL, valueProducer->mCurrentBaseInfo.size()); itBase = valueProducer->mCurrentBaseInfo.find(DEFAULT_DIMENSION_KEY); - EXPECT_FALSE(itBase->second[0].hasBase); - EXPECT_TRUE(itBase->second[0].hasCurrentState); - ASSERT_EQ(1, itBase->second[0].currentState.getValues().size()); + EXPECT_FALSE(itBase->second.baseInfos[0].hasBase); + EXPECT_TRUE(itBase->second.hasCurrentState); + ASSERT_EQ(1, itBase->second.currentState.getValues().size()); EXPECT_EQ(BatterySaverModeStateChanged::OFF, - itBase->second[0].currentState.getValues()[0].mValue.int_value); + itBase->second.currentState.getValues()[0].mValue.int_value); // Value for key {{}, OFF} ASSERT_EQ(3UL, valueProducer->mCurrentSlicedBucket.size()); it = valueProducer->mCurrentSlicedBucket.begin(); -- cgit v1.2.3 From c4d5fb0f13a05a8ff4e734c2920c4c63f411aa2c Mon Sep 17 00:00:00 2001 From: Salud Lemus Date: Tue, 25 Aug 2020 16:14:48 +0000 Subject: Add ConditionTimer to CurrentValueBucket struct Add the update condition timer logic based on state, condition, and active changes. Bug: 166159095 Bug: 166158610 Bug: 167458087 Bug: 166639935 Bug: 165018838 Test: `m statsd` Test: `m statsd_test` Test: `m` Test: `atest statsd_test` Change-Id: If7f0f66d971c6c5d78b1981ff875e466adb19a10 Merged-In: Ibb065d64be3775c699583215f7f376a1a5492ac1 --- bin/src/condition/ConditionTimer.h | 48 +- bin/src/metrics/ValueMetricProducer.cpp | 115 +- bin/src/metrics/ValueMetricProducer.h | 20 +- bin/tests/condition/ConditionTimer_test.cpp | 6 +- bin/tests/metrics/ValueMetricProducer_test.cpp | 2151 ++++++++++++++++++++++-- 5 files changed, 2169 insertions(+), 171 deletions(-) diff --git a/bin/src/condition/ConditionTimer.h b/bin/src/condition/ConditionTimer.h index 442bc119..1fbe2527 100644 --- a/bin/src/condition/ConditionTimer.h +++ b/bin/src/condition/ConditionTimer.h @@ -36,7 +36,7 @@ class ConditionTimer { public: explicit ConditionTimer(bool initCondition, int64_t bucketStartNs) : mCondition(initCondition) { if (initCondition) { - mLastConditionTrueTimestampNs = bucketStartNs; + mLastConditionChangeTimestampNs = bucketStartNs; } }; @@ -44,21 +44,46 @@ public: // When a new bucket is created, this value will be reset to 0. int64_t mTimerNs = 0; - // Last elapsed real timestamp when condition turned to true - // When a new bucket is created and the condition is true, then the timestamp is set - // to be the bucket start timestamp. - int64_t mLastConditionTrueTimestampNs = 0; + // Last elapsed real timestamp when condition changed. + int64_t mLastConditionChangeTimestampNs = 0; bool mCondition = false; int64_t newBucketStart(int64_t nextBucketStartNs) { if (mCondition) { - mTimerNs += (nextBucketStartNs - mLastConditionTrueTimestampNs); - mLastConditionTrueTimestampNs = nextBucketStartNs; + // Normally, the next bucket happens after the last condition + // change. In this case, add the time between the condition becoming + // true to the next bucket start time. + // Otherwise, the next bucket start time is before the last + // condition change time, this means that the condition was false at + // the bucket boundary before the condition became true, so the + // timer should not get updated and the last condition change time + // remains as is. + if (nextBucketStartNs >= mLastConditionChangeTimestampNs) { + mTimerNs += (nextBucketStartNs - mLastConditionChangeTimestampNs); + mLastConditionChangeTimestampNs = nextBucketStartNs; + } + } else if (mLastConditionChangeTimestampNs > nextBucketStartNs) { + // The next bucket start time is before the last condition change + // time, this means that the condition was true at the bucket + // boundary before the condition became false, so adjust the timer + // to match how long the condition was true to the bucket boundary. + // This means remove the amount the condition stayed true in the + // next bucket from the current bucket. + mTimerNs -= (mLastConditionChangeTimestampNs - nextBucketStartNs); } int64_t temp = mTimerNs; mTimerNs = 0; + + if (!mCondition && (mLastConditionChangeTimestampNs > nextBucketStartNs)) { + // The next bucket start time is before the last condition change + // time, this means that the condition was true at the bucket + // boundary and remained true in the next bucket up to the condition + // change to false, so adjust the timer to match how long the + // condition stayed true in the next bucket (now the current bucket). + mTimerNs = mLastConditionChangeTimestampNs - nextBucketStartNs; + } return temp; } @@ -67,11 +92,10 @@ public: return; } mCondition = newCondition; - if (newCondition) { - mLastConditionTrueTimestampNs = timestampNs; - } else { - mTimerNs += (timestampNs - mLastConditionTrueTimestampNs); + if (newCondition == false) { + mTimerNs += (timestampNs - mLastConditionChangeTimestampNs); } + mLastConditionChangeTimestampNs = timestampNs; } FRIEND_TEST(ConditionTimerTest, TestTimer_Inital_False); @@ -80,4 +104,4 @@ public: } // namespace statsd } // namespace os -} // namespace android \ No newline at end of file +} // namespace android diff --git a/bin/src/metrics/ValueMetricProducer.cpp b/bin/src/metrics/ValueMetricProducer.cpp index ce9e8a4a..e766289f 100644 --- a/bin/src/metrics/ValueMetricProducer.cpp +++ b/bin/src/metrics/ValueMetricProducer.cpp @@ -345,7 +345,6 @@ void ValueMetricProducer::onDumpReportLocked(const int64_t dumpTimeNs, protoOutput->write(FIELD_TYPE_INT32 | FIELD_ID_BUCKET_DROP_REASON, dropEvent.reason); protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_DROP_TIME, (long long)(NanoToMillis(dropEvent.dropTimeNs))); - ; protoOutput->end(dropEventToken); } protoOutput->end(wrapperToken); @@ -390,8 +389,11 @@ void ValueMetricProducer::onDumpReportLocked(const int64_t dumpTimeNs, protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_BUCKET_NUM, (long long)(getBucketNumFromEndTimeNs(bucket.mBucketEndNs))); } - // only write the condition timer value if the metric has a condition. - if (mConditionTrackerIndex >= 0) { + // We only write the condition timer value if the metric has a + // condition and/or is sliced by state. + // If the metric is sliced by state, the condition timer value is + // also sliced by state to reflect time spent in that state. + if (mConditionTrackerIndex >= 0 || !mSlicedStateAtoms.empty()) { protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_CONDITION_TRUE_NS, (long long)bucket.mConditionTrueNs); } @@ -498,6 +500,8 @@ void ValueMetricProducer::onActiveStateChangedLocked(const int64_t& eventTimeNs) // Let condition timer know of new active state. mConditionTimer.onConditionChanged(mIsActive, eventTimeNs); + + updateCurrentSlicedBucketConditionTimers(mIsActive, eventTimeNs); } void ValueMetricProducer::onConditionChangedLocked(const bool condition, @@ -520,6 +524,8 @@ void ValueMetricProducer::onConditionChangedLocked(const bool condition, invalidateCurrentBucket(eventTimeNs, BucketDropReason::EVENT_IN_WRONG_BUCKET); mCondition = ConditionState::kUnknown; mConditionTimer.onConditionChanged(mCondition, eventTimeNs); + + updateCurrentSlicedBucketConditionTimers(mCondition, eventTimeNs); return; } @@ -561,6 +567,29 @@ void ValueMetricProducer::onConditionChangedLocked(const bool condition, flushIfNeededLocked(eventTimeNs); mConditionTimer.onConditionChanged(mCondition, eventTimeNs); + + updateCurrentSlicedBucketConditionTimers(mCondition, eventTimeNs); +} + +void ValueMetricProducer::updateCurrentSlicedBucketConditionTimers(bool newCondition, + int64_t eventTimeNs) { + if (mSlicedStateAtoms.empty()) { + return; + } + + // Utilize the current state key of each DimensionsInWhat key to determine + // which condition timers to update. + // + // Assumes that the MetricDimensionKey exists in `mCurrentSlicedBucket`. + bool inPulledData; + for (const auto& [dimensionInWhatKey, dimensionInWhatInfo] : mCurrentBaseInfo) { + // If the new condition is true, turn ON the condition timer only if + // the DimensionInWhat key was present in the pulled data. + inPulledData = dimensionInWhatInfo.hasCurrentState; + mCurrentSlicedBucket[MetricDimensionKey(dimensionInWhatKey, + dimensionInWhatInfo.currentState)] + .conditionTimer.onConditionChanged(newCondition && inPulledData, eventTimeNs); + } } void ValueMetricProducer::prepareFirstBucketLocked() { @@ -662,8 +691,8 @@ void ValueMetricProducer::accumulateEvents(const std::vectorsecond.baseInfos) { baseInfo.hasBase = false; } + // Set to false when DimensionInWhat key is not present in a pull. + // Used in onMatchedLogEventInternalLocked() to ensure the condition + // timer is turned on the next pull when data is present. + it->second.hasCurrentState = false; + // Turn OFF condition timer for keys not present in pulled data. + currentValueBucket.conditionTimer.onConditionChanged(false, eventElapsedTimeNs); } } mMatchedMetricDimensionKeys.clear(); @@ -833,21 +868,26 @@ void ValueMetricProducer::onMatchedLogEventInternalLocked( return; } - DimensionsInWhatInfo& dimensionsInWhatInfo = mCurrentBaseInfo[whatKey]; + const auto& returnVal = + mCurrentBaseInfo.emplace(whatKey, DimensionsInWhatInfo(getUnknownStateKey())); + DimensionsInWhatInfo& dimensionsInWhatInfo = returnVal.first->second; + const HashableDimensionKey oldStateKey = dimensionsInWhatInfo.currentState; vector& baseInfos = dimensionsInWhatInfo.baseInfos; if (baseInfos.size() < mFieldMatchers.size()) { VLOG("Resizing number of intervals to %d", (int)mFieldMatchers.size()); baseInfos.resize(mFieldMatchers.size()); } + // Ensure we turn on the condition timer in the case where dimensions + // were missing on a previous pull due to a state change. + bool stateChange = oldStateKey != stateKey; if (!dimensionsInWhatInfo.hasCurrentState) { - dimensionsInWhatInfo.currentState = getUnknownStateKey(); + stateChange = true; dimensionsInWhatInfo.hasCurrentState = true; } // We need to get the intervals stored with the previous state key so we can // close these value intervals. - const auto oldStateKey = dimensionsInWhatInfo.currentState; vector& intervals = mCurrentSlicedBucket[MetricDimensionKey(whatKey, oldStateKey)].intervals; if (intervals.size() < mFieldMatchers.size()) { @@ -960,6 +1000,17 @@ void ValueMetricProducer::onMatchedLogEventInternalLocked( interval.sampleSize += 1; } + // State change. + if (!mSlicedStateAtoms.empty() && stateChange) { + // Turn OFF the condition timer for the previous state key. + mCurrentSlicedBucket[MetricDimensionKey(whatKey, oldStateKey)] + .conditionTimer.onConditionChanged(false, eventTimeNs); + + // Turn ON the condition timer for the new state key. + mCurrentSlicedBucket[MetricDimensionKey(whatKey, stateKey)] + .conditionTimer.onConditionChanged(true, eventTimeNs); + } + // Only trigger the tracker if all intervals are correct and we have not skipped the bucket due // to MULTIPLE_BUCKETS_SKIPPED. if (useAnomalyDetection && !multipleBucketsSkipped(calcBucketsForwardCount(eventTimeNs))) { @@ -1034,12 +1085,18 @@ void ValueMetricProducer::flushCurrentBucketLocked(const int64_t& eventTimeNs, if (!mCurrentBucketIsSkipped) { bool bucketHasData = false; // The current bucket is large enough to keep. - for (const auto& slice : mCurrentSlicedBucket) { - PastValueBucket bucket = buildPartialBucket(bucketEndTime, slice.second.intervals); - bucket.mConditionTrueNs = conditionTrueDuration; + for (auto& [metricDimensionKey, currentValueBucket] : mCurrentSlicedBucket) { + PastValueBucket bucket = + buildPartialBucket(bucketEndTime, currentValueBucket.intervals); + if (!mSlicedStateAtoms.empty()) { + bucket.mConditionTrueNs = + currentValueBucket.conditionTimer.newBucketStart(bucketEndTime); + } else { + bucket.mConditionTrueNs = conditionTrueDuration; + } // it will auto create new vector of ValuebucketInfo if the key is not found. if (bucket.valueIndex.size() > 0) { - auto& bucketList = mPastBuckets[slice.first]; + auto& bucketList = mPastBuckets[metricDimensionKey]; bucketList.push_back(bucket); bucketHasData = true; } @@ -1067,11 +1124,18 @@ void ValueMetricProducer::flushCurrentBucketLocked(const int64_t& eventTimeNs, buildDropEvent(eventTimeNs, BucketDropReason::NO_DATA)); mSkippedBuckets.emplace_back(bucketInGap); } - appendToFullBucket(eventTimeNs > fullBucketEndTimeNs); initCurrentSlicedBucket(nextBucketStartTimeNs); // Update the condition timer again, in case we skipped buckets. mConditionTimer.newBucketStart(nextBucketStartTimeNs); + + // NOTE: Update the condition timers in `mCurrentSlicedBucket` only when slicing + // by state. Otherwise, the "global" condition timer will be used. + if (!mSlicedStateAtoms.empty()) { + for (auto& [metricDimensionKey, currentValueBucket] : mCurrentSlicedBucket) { + currentValueBucket.conditionTimer.newBucketStart(nextBucketStartTimeNs); + } + } mCurrentBucketNum += numBucketsForward; } @@ -1113,6 +1177,17 @@ void ValueMetricProducer::initCurrentSlicedBucket(int64_t nextBucketStartTimeNs) interval.seenNewData = false; } + if (obsolete && !mSlicedStateAtoms.empty()) { + // When slicing by state, only delete the MetricDimensionKey when the + // state key in the MetricDimensionKey is not the current state key. + const HashableDimensionKey& dimensionInWhatKey = it->first.getDimensionKeyInWhat(); + const auto& currentBaseInfoItr = mCurrentBaseInfo.find(dimensionInWhatKey); + + if ((currentBaseInfoItr != mCurrentBaseInfo.end()) && + (it->first.getStateValuesKey() == currentBaseInfoItr->second.currentState)) { + obsolete = false; + } + } if (obsolete) { it = mCurrentSlicedBucket.erase(it); } else { @@ -1148,7 +1223,7 @@ void ValueMetricProducer::appendToFullBucket(const bool isFullBucketReached) { // Accumulate partial buckets with current value and then send to anomaly tracker. if (mCurrentFullBucket.size() > 0) { for (const auto& slice : mCurrentSlicedBucket) { - if (hitFullBucketGuardRailLocked(slice.first)) { + if (hitFullBucketGuardRailLocked(slice.first) || slice.second.intervals.empty()) { continue; } // TODO: fix this when anomaly can accept double values @@ -1169,7 +1244,7 @@ void ValueMetricProducer::appendToFullBucket(const bool isFullBucketReached) { // Skip aggregating the partial buckets since there's no previous partial bucket. for (const auto& slice : mCurrentSlicedBucket) { for (auto& tracker : mAnomalyTrackers) { - if (tracker != nullptr) { + if (tracker != nullptr && !slice.second.intervals.empty()) { // TODO: fix this when anomaly can accept double values auto& interval = slice.second.intervals[0]; if (interval.hasValue) { @@ -1183,10 +1258,12 @@ void ValueMetricProducer::appendToFullBucket(const bool isFullBucketReached) { } else { // Accumulate partial bucket. for (const auto& slice : mCurrentSlicedBucket) { - // TODO: fix this when anomaly can accept double values - auto& interval = slice.second.intervals[0]; - if (interval.hasValue) { - mCurrentFullBucket[slice.first] += interval.value.long_value; + if (!slice.second.intervals.empty()) { + // TODO: fix this when anomaly can accept double values + auto& interval = slice.second.intervals[0]; + if (interval.hasValue) { + mCurrentFullBucket[slice.first] += interval.value.long_value; + } } } } diff --git a/bin/src/metrics/ValueMetricProducer.h b/bin/src/metrics/ValueMetricProducer.h index 5f472d4f..1de05241 100644 --- a/bin/src/metrics/ValueMetricProducer.h +++ b/bin/src/metrics/ValueMetricProducer.h @@ -209,8 +209,14 @@ private: // Internal state of an ongoing aggregation bucket. typedef struct CurrentValueBucket { + // If the `MetricDimensionKey` state key is the current state key, then + // the condition timer will be updated later (e.g. condition/state/active + // state change) with the correct condition and time. + CurrentValueBucket() : intervals(), conditionTimer(ConditionTimer(false, 0)) {} // Value information for each value field of the metric. std::vector intervals; + // Tracks how long the condition is true. + ConditionTimer conditionTimer; } CurrentValueBucket; // Holds base information for diffing values from one value field. @@ -222,7 +228,10 @@ private: } BaseInfo; // State key and base information for a specific DimensionsInWhat key. - typedef struct { + typedef struct DimensionsInWhatInfo { + DimensionsInWhatInfo(const HashableDimensionKey& stateKey) + : baseInfos(), currentState(stateKey), hasCurrentState(false) { + } std::vector baseInfos; // Last seen state value(s). HashableDimensionKey currentState; @@ -268,6 +277,10 @@ private: // Reset diff base and mHasGlobalBase void resetBase(); + // Updates the condition timers in the current sliced bucket when there is a + // condition change or an active state change. + void updateCurrentSlicedBucketConditionTimers(bool newCondition, int64_t eventTimeNs); + static const size_t kBucketSize = sizeof(PastValueBucket{}); const size_t mDimensionSoftLimit; @@ -353,6 +366,11 @@ private: FRIEND_TEST(ValueMetricProducerTest, TestTrimUnusedDimensionKey); FRIEND_TEST(ValueMetricProducerTest, TestUseZeroDefaultBase); FRIEND_TEST(ValueMetricProducerTest, TestUseZeroDefaultBaseWithPullFailures); + FRIEND_TEST(ValueMetricProducerTest, TestSlicedStateWithMultipleDimensions); + FRIEND_TEST(ValueMetricProducerTest, TestSlicedStateWithMissingDataInStateChange); + FRIEND_TEST(ValueMetricProducerTest, TestSlicedStateWithDataMissingInConditionChange); + FRIEND_TEST(ValueMetricProducerTest, TestSlicedStateWithMissingDataThenFlushBucket); + FRIEND_TEST(ValueMetricProducerTest, TestSlicedStateWithNoPullOnBucketBoundary); FRIEND_TEST(ValueMetricProducerTest_BucketDrop, TestInvalidBucketWhenOneConditionFailed); FRIEND_TEST(ValueMetricProducerTest_BucketDrop, TestInvalidBucketWhenInitialPullFailed); diff --git a/bin/tests/condition/ConditionTimer_test.cpp b/bin/tests/condition/ConditionTimer_test.cpp index ea02cd3a..46dc9a9d 100644 --- a/bin/tests/condition/ConditionTimer_test.cpp +++ b/bin/tests/condition/ConditionTimer_test.cpp @@ -35,11 +35,11 @@ TEST(ConditionTimerTest, TestTimer_Inital_False) { EXPECT_EQ(0, timer.mTimerNs); timer.onConditionChanged(true, ct_start_time + 5); - EXPECT_EQ(ct_start_time + 5, timer.mLastConditionTrueTimestampNs); + EXPECT_EQ(ct_start_time + 5, timer.mLastConditionChangeTimestampNs); EXPECT_EQ(true, timer.mCondition); EXPECT_EQ(95, timer.newBucketStart(ct_start_time + 100)); - EXPECT_EQ(ct_start_time + 100, timer.mLastConditionTrueTimestampNs); + EXPECT_EQ(ct_start_time + 100, timer.mLastConditionChangeTimestampNs); EXPECT_EQ(true, timer.mCondition); } @@ -51,7 +51,7 @@ TEST(ConditionTimerTest, TestTimer_Inital_True) { EXPECT_EQ(ct_start_time - time_base, timer.newBucketStart(ct_start_time)); EXPECT_EQ(true, timer.mCondition); EXPECT_EQ(0, timer.mTimerNs); - EXPECT_EQ(ct_start_time, timer.mLastConditionTrueTimestampNs); + EXPECT_EQ(ct_start_time, timer.mLastConditionChangeTimestampNs); timer.onConditionChanged(false, ct_start_time + 5); EXPECT_EQ(5, timer.mTimerNs); diff --git a/bin/tests/metrics/ValueMetricProducer_test.cpp b/bin/tests/metrics/ValueMetricProducer_test.cpp index b166cc1f..6cf4192b 100644 --- a/bin/tests/metrics/ValueMetricProducer_test.cpp +++ b/bin/tests/metrics/ValueMetricProducer_test.cpp @@ -93,6 +93,13 @@ static void assertPastBucketValuesSingleKey( } } +static void assertConditionTimer(const ConditionTimer& conditionTimer, bool condition, + int64_t timerNs, int64_t lastConditionTrueTimestampNs) { + EXPECT_EQ(condition, conditionTimer.mCondition); + EXPECT_EQ(timerNs, conditionTimer.mTimerNs); + EXPECT_EQ(lastConditionTrueTimestampNs, conditionTimer.mLastConditionChangeTimestampNs); +} + } // anonymous namespace class ValueMetricProducerTestHelper { @@ -3967,33 +3974,37 @@ TEST(ValueMetricProducerTest, TestSlicedState) { // Screen state change to ON. .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, vector>* data) { - EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 5); + EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 5 * NS_PER_SEC); data->clear(); - data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 5, 5)); + data->push_back( + CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 5 * NS_PER_SEC, 5)); return true; })) // Screen state change to OFF. .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, vector>* data) { - EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 10); + EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 10 * NS_PER_SEC); data->clear(); - data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 10, 9)); + data->push_back( + CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 10 * NS_PER_SEC, 9)); return true; })) // Screen state change to ON. .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, vector>* data) { - EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 15); + EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 15 * NS_PER_SEC); data->clear(); - data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 15, 21)); + data->push_back(CreateRepeatedValueLogEvent( + tagId, bucketStartTimeNs + 15 * NS_PER_SEC, 21)); return true; })) // Dump report requested. .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, vector>* data) { - EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 50); + EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 50 * NS_PER_SEC); data->clear(); - data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 50, 30)); + data->push_back(CreateRepeatedValueLogEvent( + tagId, bucketStartTimeNs + 50 * NS_PER_SEC, 30)); return true; })); @@ -4025,12 +4036,13 @@ TEST(ValueMetricProducerTest, TestSlicedState) { EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, it->first.getStateValuesKey().getValues()[0].mValue.int_value); EXPECT_FALSE(it->second.intervals[0].hasValue); + assertConditionTimer(it->second.conditionTimer, true, 0, bucketStartTimeNs); // Bucket status after screen state change kStateUnknown->ON. auto screenEvent = CreateScreenStateChangedEvent( - bucketStartTimeNs + 5, android::view::DisplayStateEnum::DISPLAY_STATE_ON); + bucketStartTimeNs + 5 * NS_PER_SEC, android::view::DisplayStateEnum::DISPLAY_STATE_ON); StateManager::getInstance().onLogEvent(*screenEvent); - ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); + ASSERT_EQ(2UL, valueProducer->mCurrentSlicedBucket.size()); // Base for dimension key {} it = valueProducer->mCurrentSlicedBucket.begin(); itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); @@ -4040,19 +4052,29 @@ TEST(ValueMetricProducerTest, TestSlicedState) { ASSERT_EQ(1, itBase->second.currentState.getValues().size()); EXPECT_EQ(android::view::DisplayStateEnum::DISPLAY_STATE_ON, itBase->second.currentState.getValues()[0].mValue.int_value); + // Value for dimension, state key {{}, ON} + EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(android::view::DisplayStateEnum::DISPLAY_STATE_ON, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + EXPECT_EQ(0, it->second.intervals.size()); + assertConditionTimer(it->second.conditionTimer, true, 0, bucketStartTimeNs + 5 * NS_PER_SEC); // Value for dimension, state key {{}, kStateUnknown} + it++; EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, it->first.getStateValuesKey().getValues()[0].mValue.int_value); EXPECT_TRUE(it->second.intervals[0].hasValue); EXPECT_EQ(2, it->second.intervals[0].value.long_value); + assertConditionTimer(it->second.conditionTimer, false, 5 * NS_PER_SEC, + bucketStartTimeNs + 5 * NS_PER_SEC); // Bucket status after screen state change ON->OFF. - screenEvent = CreateScreenStateChangedEvent(bucketStartTimeNs + 10, + screenEvent = CreateScreenStateChangedEvent(bucketStartTimeNs + 10 * NS_PER_SEC, android::view::DisplayStateEnum::DISPLAY_STATE_OFF); StateManager::getInstance().onLogEvent(*screenEvent); - ASSERT_EQ(2UL, valueProducer->mCurrentSlicedBucket.size()); + ASSERT_EQ(3UL, valueProducer->mCurrentSlicedBucket.size()); // Base for dimension key {} it = valueProducer->mCurrentSlicedBucket.begin(); itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); @@ -4061,13 +4083,23 @@ TEST(ValueMetricProducerTest, TestSlicedState) { EXPECT_TRUE(itBase->second.hasCurrentState); EXPECT_EQ(android::view::DisplayStateEnum::DISPLAY_STATE_OFF, itBase->second.currentState.getValues()[0].mValue.int_value); + // Value for dimension, state key {{}, OFF} + EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(android::view::DisplayStateEnum::DISPLAY_STATE_OFF, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + EXPECT_EQ(0, it->second.intervals.size()); + assertConditionTimer(it->second.conditionTimer, true, 0, bucketStartTimeNs + 10 * NS_PER_SEC); // Value for dimension, state key {{}, ON} + it++; EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); EXPECT_EQ(android::view::DisplayStateEnum::DISPLAY_STATE_ON, it->first.getStateValuesKey().getValues()[0].mValue.int_value); EXPECT_TRUE(it->second.intervals[0].hasValue); EXPECT_EQ(4, it->second.intervals[0].value.long_value); + assertConditionTimer(it->second.conditionTimer, false, 5 * NS_PER_SEC, + bucketStartTimeNs + 10 * NS_PER_SEC); // Value for dimension, state key {{}, kStateUnknown} it++; EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); @@ -4076,9 +4108,11 @@ TEST(ValueMetricProducerTest, TestSlicedState) { it->first.getStateValuesKey().getValues()[0].mValue.int_value); EXPECT_TRUE(it->second.intervals[0].hasValue); EXPECT_EQ(2, it->second.intervals[0].value.long_value); + assertConditionTimer(it->second.conditionTimer, false, 5 * NS_PER_SEC, + bucketStartTimeNs + 5 * NS_PER_SEC); // Bucket status after screen state change OFF->ON. - screenEvent = CreateScreenStateChangedEvent(bucketStartTimeNs + 15, + screenEvent = CreateScreenStateChangedEvent(bucketStartTimeNs + 15 * NS_PER_SEC, android::view::DisplayStateEnum::DISPLAY_STATE_ON); StateManager::getInstance().onLogEvent(*screenEvent); ASSERT_EQ(3UL, valueProducer->mCurrentSlicedBucket.size()); @@ -4098,6 +4132,8 @@ TEST(ValueMetricProducerTest, TestSlicedState) { it->first.getStateValuesKey().getValues()[0].mValue.int_value); EXPECT_TRUE(it->second.intervals[0].hasValue); EXPECT_EQ(12, it->second.intervals[0].value.long_value); + assertConditionTimer(it->second.conditionTimer, false, 5 * NS_PER_SEC, + bucketStartTimeNs + 15 * NS_PER_SEC); // Value for dimension, state key {{}, ON} it++; EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); @@ -4106,6 +4142,8 @@ TEST(ValueMetricProducerTest, TestSlicedState) { it->first.getStateValuesKey().getValues()[0].mValue.int_value); EXPECT_TRUE(it->second.intervals[0].hasValue); EXPECT_EQ(4, it->second.intervals[0].value.long_value); + assertConditionTimer(it->second.conditionTimer, true, 5 * NS_PER_SEC, + bucketStartTimeNs + 15 * NS_PER_SEC); // Value for dimension, state key {{}, kStateUnknown} it++; EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); @@ -4114,37 +4152,46 @@ TEST(ValueMetricProducerTest, TestSlicedState) { it->first.getStateValuesKey().getValues()[0].mValue.int_value); EXPECT_TRUE(it->second.intervals[0].hasValue); EXPECT_EQ(2, it->second.intervals[0].value.long_value); + assertConditionTimer(it->second.conditionTimer, false, 5 * NS_PER_SEC, + bucketStartTimeNs + 5 * NS_PER_SEC); // Start dump report and check output. ProtoOutputStream output; std::set strSet; - valueProducer->onDumpReport(bucketStartTimeNs + 50, true /* include recent buckets */, true, - NO_TIME_CONSTRAINTS, &strSet, &output); + valueProducer->onDumpReport(bucketStartTimeNs + 50 * NS_PER_SEC, + true /* include recent buckets */, true, NO_TIME_CONSTRAINTS, + &strSet, &output); StatsLogReport report = outputStreamToProto(&output); EXPECT_TRUE(report.has_value_metrics()); ASSERT_EQ(3, report.value_metrics().data_size()); + // {{}, kStateUnknown} auto data = report.value_metrics().data(0); ASSERT_EQ(1, data.bucket_info_size()); EXPECT_EQ(2, report.value_metrics().data(0).bucket_info(0).values(0).value_long()); EXPECT_EQ(SCREEN_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); EXPECT_TRUE(data.slice_by_state(0).has_value()); EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, data.slice_by_state(0).value()); + EXPECT_EQ(5 * NS_PER_SEC, data.bucket_info(0).condition_true_nanos()); + // {{}, ON} data = report.value_metrics().data(1); ASSERT_EQ(1, report.value_metrics().data(1).bucket_info_size()); EXPECT_EQ(13, report.value_metrics().data(1).bucket_info(0).values(0).value_long()); EXPECT_EQ(SCREEN_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); EXPECT_TRUE(data.slice_by_state(0).has_value()); EXPECT_EQ(android::view::DisplayStateEnum::DISPLAY_STATE_ON, data.slice_by_state(0).value()); + EXPECT_EQ(40 * NS_PER_SEC, data.bucket_info(0).condition_true_nanos()); + // {{}, OFF} data = report.value_metrics().data(2); ASSERT_EQ(1, report.value_metrics().data(2).bucket_info_size()); EXPECT_EQ(12, report.value_metrics().data(2).bucket_info(0).values(0).value_long()); EXPECT_EQ(SCREEN_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); EXPECT_TRUE(data.slice_by_state(0).has_value()); EXPECT_EQ(android::view::DisplayStateEnum::DISPLAY_STATE_OFF, data.slice_by_state(0).value()); + EXPECT_EQ(5 * NS_PER_SEC, data.bucket_info(0).condition_true_nanos()); } /* @@ -4169,9 +4216,10 @@ TEST(ValueMetricProducerTest, TestSlicedStateWithMap) { // Screen state change to ON. .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, vector>* data) { - EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 5); + EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 5 * NS_PER_SEC); data->clear(); - data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 5, 5)); + data->push_back( + CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 5 * NS_PER_SEC, 5)); return true; })) // Screen state change to VR has no pull because it is in the same @@ -4183,17 +4231,19 @@ TEST(ValueMetricProducerTest, TestSlicedStateWithMap) { // Screen state change to OFF. .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, vector>* data) { - EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 15); + EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 15 * NS_PER_SEC); data->clear(); - data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 15, 21)); + data->push_back(CreateRepeatedValueLogEvent( + tagId, bucketStartTimeNs + 15 * NS_PER_SEC, 21)); return true; })) // Dump report requested. .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, vector>* data) { - EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 50); + EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 50 * NS_PER_SEC); data->clear(); - data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 50, 30)); + data->push_back(CreateRepeatedValueLogEvent( + tagId, bucketStartTimeNs + 50 * NS_PER_SEC, 30)); return true; })); @@ -4236,12 +4286,13 @@ TEST(ValueMetricProducerTest, TestSlicedStateWithMap) { EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, it->first.getStateValuesKey().getValues()[0].mValue.int_value); EXPECT_FALSE(it->second.intervals[0].hasValue); + assertConditionTimer(it->second.conditionTimer, true, 0, bucketStartTimeNs); // Bucket status after screen state change kStateUnknown->ON. auto screenEvent = CreateScreenStateChangedEvent( - bucketStartTimeNs + 5, android::view::DisplayStateEnum::DISPLAY_STATE_ON); + bucketStartTimeNs + 5 * NS_PER_SEC, android::view::DisplayStateEnum::DISPLAY_STATE_ON); StateManager::getInstance().onLogEvent(*screenEvent); - ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); + ASSERT_EQ(2UL, valueProducer->mCurrentSlicedBucket.size()); // Base for dimension key {} it = valueProducer->mCurrentSlicedBucket.begin(); itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); @@ -4251,20 +4302,29 @@ TEST(ValueMetricProducerTest, TestSlicedStateWithMap) { ASSERT_EQ(1, itBase->second.currentState.getValues().size()); EXPECT_EQ(screenOnGroup.group_id(), itBase->second.currentState.getValues()[0].mValue.long_value); + // Value for dimension, state key {{}, ON GROUP} + EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(screenOnGroup.group_id(), + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, true, 0, bucketStartTimeNs + 5 * NS_PER_SEC); // Value for dimension, state key {{}, kStateUnknown} + it++; EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, it->first.getStateValuesKey().getValues()[0].mValue.int_value); EXPECT_TRUE(it->second.intervals[0].hasValue); EXPECT_EQ(2, it->second.intervals[0].value.long_value); + assertConditionTimer(it->second.conditionTimer, false, 5 * NS_PER_SEC, + bucketStartTimeNs + 5 * NS_PER_SEC); // Bucket status after screen state change ON->VR. // Both ON and VR are in the same state group, so the base should not change. - screenEvent = CreateScreenStateChangedEvent(bucketStartTimeNs + 10, + screenEvent = CreateScreenStateChangedEvent(bucketStartTimeNs + 10 * NS_PER_SEC, android::view::DisplayStateEnum::DISPLAY_STATE_VR); StateManager::getInstance().onLogEvent(*screenEvent); - ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); + ASSERT_EQ(2UL, valueProducer->mCurrentSlicedBucket.size()); // Base for dimension key {} it = valueProducer->mCurrentSlicedBucket.begin(); itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); @@ -4274,20 +4334,29 @@ TEST(ValueMetricProducerTest, TestSlicedStateWithMap) { ASSERT_EQ(1, itBase->second.currentState.getValues().size()); EXPECT_EQ(screenOnGroup.group_id(), itBase->second.currentState.getValues()[0].mValue.int_value); + // Value for dimension, state key {{}, ON GROUP} + EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(screenOnGroup.group_id(), + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, true, 0, bucketStartTimeNs + 5 * NS_PER_SEC); // Value for dimension, state key {{}, kStateUnknown} + it++; EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, it->first.getStateValuesKey().getValues()[0].mValue.int_value); EXPECT_TRUE(it->second.intervals[0].hasValue); EXPECT_EQ(2, it->second.intervals[0].value.long_value); + assertConditionTimer(it->second.conditionTimer, false, 5 * NS_PER_SEC, + bucketStartTimeNs + 5 * NS_PER_SEC); // Bucket status after screen state change VR->ON. // Both ON and VR are in the same state group, so the base should not change. - screenEvent = CreateScreenStateChangedEvent(bucketStartTimeNs + 12, + screenEvent = CreateScreenStateChangedEvent(bucketStartTimeNs + 12 * NS_PER_SEC, android::view::DisplayStateEnum::DISPLAY_STATE_ON); StateManager::getInstance().onLogEvent(*screenEvent); - ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); + ASSERT_EQ(2UL, valueProducer->mCurrentSlicedBucket.size()); // Base for dimension key {} it = valueProducer->mCurrentSlicedBucket.begin(); itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); @@ -4297,19 +4366,28 @@ TEST(ValueMetricProducerTest, TestSlicedStateWithMap) { ASSERT_EQ(1, itBase->second.currentState.getValues().size()); EXPECT_EQ(screenOnGroup.group_id(), itBase->second.currentState.getValues()[0].mValue.int_value); + // Value for dimension, state key {{}, ON GROUP} + EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(screenOnGroup.group_id(), + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, true, 0, bucketStartTimeNs + 5 * NS_PER_SEC); // Value for dimension, state key {{}, kStateUnknown} + it++; EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, it->first.getStateValuesKey().getValues()[0].mValue.int_value); EXPECT_TRUE(it->second.intervals[0].hasValue); EXPECT_EQ(2, it->second.intervals[0].value.long_value); + assertConditionTimer(it->second.conditionTimer, false, 5 * NS_PER_SEC, + bucketStartTimeNs + 5 * NS_PER_SEC); // Bucket status after screen state change VR->OFF. - screenEvent = CreateScreenStateChangedEvent(bucketStartTimeNs + 15, + screenEvent = CreateScreenStateChangedEvent(bucketStartTimeNs + 15 * NS_PER_SEC, android::view::DisplayStateEnum::DISPLAY_STATE_OFF); StateManager::getInstance().onLogEvent(*screenEvent); - ASSERT_EQ(2UL, valueProducer->mCurrentSlicedBucket.size()); + ASSERT_EQ(3UL, valueProducer->mCurrentSlicedBucket.size()); // Base for dimension key {} it = valueProducer->mCurrentSlicedBucket.begin(); itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); @@ -4319,13 +4397,22 @@ TEST(ValueMetricProducerTest, TestSlicedStateWithMap) { ASSERT_EQ(1, itBase->second.currentState.getValues().size()); EXPECT_EQ(screenOffGroup.group_id(), itBase->second.currentState.getValues()[0].mValue.int_value); + // Value for dimension, state key {{}, OFF GROUP} + EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(screenOffGroup.group_id(), + it->first.getStateValuesKey().getValues()[0].mValue.long_value); + assertConditionTimer(it->second.conditionTimer, true, 0, bucketStartTimeNs + 15 * NS_PER_SEC); // Value for dimension, state key {{}, ON GROUP} + it++; EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); EXPECT_EQ(screenOnGroup.group_id(), it->first.getStateValuesKey().getValues()[0].mValue.long_value); EXPECT_TRUE(it->second.intervals[0].hasValue); EXPECT_EQ(16, it->second.intervals[0].value.long_value); + assertConditionTimer(it->second.conditionTimer, false, 10 * NS_PER_SEC, + bucketStartTimeNs + 15 * NS_PER_SEC); // Value for dimension, state key {{}, kStateUnknown} it++; EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); @@ -4334,37 +4421,46 @@ TEST(ValueMetricProducerTest, TestSlicedStateWithMap) { it->first.getStateValuesKey().getValues()[0].mValue.int_value); EXPECT_TRUE(it->second.intervals[0].hasValue); EXPECT_EQ(2, it->second.intervals[0].value.long_value); + assertConditionTimer(it->second.conditionTimer, false, 5 * NS_PER_SEC, + bucketStartTimeNs + 5 * NS_PER_SEC); // Start dump report and check output. ProtoOutputStream output; std::set strSet; - valueProducer->onDumpReport(bucketStartTimeNs + 50, true /* include recent buckets */, true, - NO_TIME_CONSTRAINTS, &strSet, &output); + valueProducer->onDumpReport(bucketStartTimeNs + 50 * NS_PER_SEC, + true /* include recent buckets */, true, NO_TIME_CONSTRAINTS, + &strSet, &output); StatsLogReport report = outputStreamToProto(&output); EXPECT_TRUE(report.has_value_metrics()); ASSERT_EQ(3, report.value_metrics().data_size()); + // {{}, kStateUnknown} auto data = report.value_metrics().data(0); ASSERT_EQ(1, data.bucket_info_size()); EXPECT_EQ(2, report.value_metrics().data(0).bucket_info(0).values(0).value_long()); EXPECT_EQ(SCREEN_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); EXPECT_TRUE(data.slice_by_state(0).has_value()); EXPECT_EQ(-1 /*StateTracker::kStateUnknown*/, data.slice_by_state(0).value()); + EXPECT_EQ(5 * NS_PER_SEC, data.bucket_info(0).condition_true_nanos()); + // {{}, ON GROUP} data = report.value_metrics().data(1); ASSERT_EQ(1, report.value_metrics().data(1).bucket_info_size()); EXPECT_EQ(16, report.value_metrics().data(1).bucket_info(0).values(0).value_long()); EXPECT_EQ(SCREEN_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); EXPECT_TRUE(data.slice_by_state(0).has_group_id()); EXPECT_EQ(screenOnGroup.group_id(), data.slice_by_state(0).group_id()); + EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).condition_true_nanos()); + // {{}, OFF GROUP} data = report.value_metrics().data(2); ASSERT_EQ(1, report.value_metrics().data(2).bucket_info_size()); EXPECT_EQ(9, report.value_metrics().data(2).bucket_info(0).values(0).value_long()); EXPECT_EQ(SCREEN_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); EXPECT_TRUE(data.slice_by_state(0).has_group_id()); EXPECT_EQ(screenOffGroup.group_id(), data.slice_by_state(0).group_id()); + EXPECT_EQ(35 * NS_PER_SEC, data.bucket_info(0).condition_true_nanos()); } /* @@ -4386,6 +4482,35 @@ TEST(ValueMetricProducerTest, TestSlicedStateWithPrimaryField_WithDimensions) { auto fieldsInState = stateLink->mutable_fields_in_state(); *fieldsInState = CreateDimensions(UID_PROCESS_STATE_ATOM_ID, {1 /* uid */}); + /* + NOTE: "1" denotes uid 1 and "2" denotes uid 2. + bucket # 1 bucket # 2 + 10 20 30 40 50 60 70 80 90 100 110 120 (seconds) + |------------------------------------------|---------------------------------|-- + + (kStateUnknown) + 1 + |-------------| + 20 + + 2 + |----------------------------| + 40 + + (FOREGROUND) + 1 1 + |----------------------------|-------------| |------| + 40 20 10 + + + (BACKGROUND) + 1 + |------------| + 20 + 2 + |-------------|---------------------------------| + 20 50 + */ sp pullerManager = new StrictMock(); EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _)) // ValueMetricProducer initialized. @@ -4400,64 +4525,64 @@ TEST(ValueMetricProducerTest, TestSlicedStateWithPrimaryField_WithDimensions) { // Uid 1 process state change from kStateUnknown -> Foreground .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, vector>* data) { - EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 20); + EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 20 * NS_PER_SEC); data->clear(); - data->push_back( - CreateTwoValueLogEvent(tagId, bucketStartTimeNs + 20, 1 /*uid*/, 6)); + data->push_back(CreateTwoValueLogEvent(tagId, bucketStartTimeNs + 20 * NS_PER_SEC, + 1 /*uid*/, 6)); // This event should be skipped. - data->push_back( - CreateTwoValueLogEvent(tagId, bucketStartTimeNs + 20, 2 /*uid*/, 8)); + data->push_back(CreateTwoValueLogEvent(tagId, bucketStartTimeNs + 20 * NS_PER_SEC, + 2 /*uid*/, 8)); return true; })) // Uid 2 process state change from kStateUnknown -> Background .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, vector>* data) { - EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 40); + EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 40 * NS_PER_SEC); data->clear(); - data->push_back( - CreateTwoValueLogEvent(tagId, bucketStartTimeNs + 40, 2 /*uid*/, 9)); + data->push_back(CreateTwoValueLogEvent(tagId, bucketStartTimeNs + 40 * NS_PER_SEC, + 2 /*uid*/, 9)); // This event should be skipped. - data->push_back( - CreateTwoValueLogEvent(tagId, bucketStartTimeNs + 40, 1 /*uid*/, 12)); + data->push_back(CreateTwoValueLogEvent(tagId, bucketStartTimeNs + 40 * NS_PER_SEC, + 1 /*uid*/, 12)); return true; })) // Uid 1 process state change from Foreground -> Background .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, vector>* data) { - EXPECT_EQ(eventTimeNs, bucket2StartTimeNs + 20); + EXPECT_EQ(eventTimeNs, bucket2StartTimeNs + 20 * NS_PER_SEC); data->clear(); - data->push_back( - CreateTwoValueLogEvent(tagId, bucket2StartTimeNs + 20, 1 /*uid*/, 13)); + data->push_back(CreateTwoValueLogEvent(tagId, bucket2StartTimeNs + 20 * NS_PER_SEC, + 1 /*uid*/, 13)); // This event should be skipped. - data->push_back( - CreateTwoValueLogEvent(tagId, bucket2StartTimeNs + 20, 2 /*uid*/, 11)); + data->push_back(CreateTwoValueLogEvent(tagId, bucket2StartTimeNs + 20 * NS_PER_SEC, + 2 /*uid*/, 11)); return true; })) // Uid 1 process state change from Background -> Foreground .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, vector>* data) { - EXPECT_EQ(eventTimeNs, bucket2StartTimeNs + 40); + EXPECT_EQ(eventTimeNs, bucket2StartTimeNs + 40 * NS_PER_SEC); data->clear(); - data->push_back( - CreateTwoValueLogEvent(tagId, bucket2StartTimeNs + 40, 1 /*uid*/, 17)); + data->push_back(CreateTwoValueLogEvent(tagId, bucket2StartTimeNs + 40 * NS_PER_SEC, + 1 /*uid*/, 17)); // This event should be skipped. - data->push_back( - CreateTwoValueLogEvent(tagId, bucket2StartTimeNs + 40, 2 /*uid */, 15)); + data->push_back(CreateTwoValueLogEvent(tagId, bucket2StartTimeNs + 40 * NS_PER_SEC, + 2 /*uid */, 15)); return true; })) // Dump report pull. .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, vector>* data) { - EXPECT_EQ(eventTimeNs, bucket2StartTimeNs + 50); + EXPECT_EQ(eventTimeNs, bucket2StartTimeNs + 50 * NS_PER_SEC); data->clear(); - data->push_back( - CreateTwoValueLogEvent(tagId, bucket2StartTimeNs + 50, 2 /*uid*/, 20)); - data->push_back( - CreateTwoValueLogEvent(tagId, bucket2StartTimeNs + 50, 1 /*uid*/, 21)); + data->push_back(CreateTwoValueLogEvent(tagId, bucket2StartTimeNs + 50 * NS_PER_SEC, + 2 /*uid*/, 20)); + data->push_back(CreateTwoValueLogEvent(tagId, bucket2StartTimeNs + 50 * NS_PER_SEC, + 1 /*uid*/, 21)); return true; })); @@ -4489,6 +4614,7 @@ TEST(ValueMetricProducerTest, TestSlicedStateWithPrimaryField_WithDimensions) { EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, it->first.getStateValuesKey().getValues()[0].mValue.int_value); EXPECT_FALSE(it->second.intervals[0].hasValue); + assertConditionTimer(it->second.conditionTimer, true, 0, bucketStartTimeNs); // Base for dimension key {uid 2} it++; itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); @@ -4505,12 +4631,14 @@ TEST(ValueMetricProducerTest, TestSlicedStateWithPrimaryField_WithDimensions) { EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, it->first.getStateValuesKey().getValues()[0].mValue.int_value); EXPECT_FALSE(it->second.intervals[0].hasValue); + assertConditionTimer(it->second.conditionTimer, true, 0, bucketStartTimeNs); // Bucket status after uid 1 process state change kStateUnknown -> Foreground. - auto uidProcessEvent = CreateUidProcessStateChangedEvent( - bucketStartTimeNs + 20, 1 /* uid */, android::app::PROCESS_STATE_IMPORTANT_FOREGROUND); + auto uidProcessEvent = + CreateUidProcessStateChangedEvent(bucketStartTimeNs + 20 * NS_PER_SEC, 1 /* uid */, + android::app::PROCESS_STATE_IMPORTANT_FOREGROUND); StateManager::getInstance().onLogEvent(*uidProcessEvent); - ASSERT_EQ(2UL, valueProducer->mCurrentSlicedBucket.size()); + ASSERT_EQ(3UL, valueProducer->mCurrentSlicedBucket.size()); // Base for dimension key {uid 1}. it = valueProducer->mCurrentSlicedBucket.begin(); itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); @@ -4528,8 +4656,18 @@ TEST(ValueMetricProducerTest, TestSlicedStateWithPrimaryField_WithDimensions) { it->first.getStateValuesKey().getValues()[0].mValue.int_value); EXPECT_TRUE(it->second.intervals[0].hasValue); EXPECT_EQ(3, it->second.intervals[0].value.long_value); + assertConditionTimer(it->second.conditionTimer, false, 20 * NS_PER_SEC, + bucketStartTimeNs + 20 * NS_PER_SEC); + // Value for key {uid 1, FOREGROUND}. + it++; + ASSERT_EQ(1, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_FOREGROUND, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, true, 0, bucketStartTimeNs + 20 * NS_PER_SEC); - // Base for dimension key {uid 2} + // Base for dimension key {uid 2}. it++; itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); EXPECT_TRUE(itBase->second.baseInfos[0].hasBase); @@ -4538,22 +4676,42 @@ TEST(ValueMetricProducerTest, TestSlicedStateWithPrimaryField_WithDimensions) { ASSERT_EQ(1, itBase->second.currentState.getValues().size()); EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, itBase->second.currentState.getValues()[0].mValue.int_value); - // Value for key {uid 2, kStateUnknown} + // Value for key {uid 2, kStateUnknown}. ASSERT_EQ(1, it->first.getDimensionKeyInWhat().getValues().size()); EXPECT_EQ(2, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); - EXPECT_EQ(-1, it->first.getStateValuesKey().getValues()[0].mValue.int_value); + EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); EXPECT_FALSE(it->second.intervals[0].hasValue); + assertConditionTimer(it->second.conditionTimer, true, 0, bucketStartTimeNs); // Bucket status after uid 2 process state change kStateUnknown -> Background. - uidProcessEvent = CreateUidProcessStateChangedEvent( - bucketStartTimeNs + 40, 2 /* uid */, android::app::PROCESS_STATE_IMPORTANT_BACKGROUND); + uidProcessEvent = + CreateUidProcessStateChangedEvent(bucketStartTimeNs + 40 * NS_PER_SEC, 2 /* uid */, + android::app::PROCESS_STATE_IMPORTANT_BACKGROUND); StateManager::getInstance().onLogEvent(*uidProcessEvent); - ASSERT_EQ(2UL, valueProducer->mCurrentSlicedBucket.size()); - // Base for dimension key {uid 1}. + ASSERT_EQ(4UL, valueProducer->mCurrentSlicedBucket.size()); + // Base for dimension key {uid 2}. it = valueProducer->mCurrentSlicedBucket.begin(); itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); EXPECT_TRUE(itBase->second.baseInfos[0].hasBase); + EXPECT_EQ(9, itBase->second.baseInfos[0].base.long_value); + EXPECT_TRUE(itBase->second.hasCurrentState); + ASSERT_EQ(1, itBase->second.currentState.getValues().size()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND, + itBase->second.currentState.getValues()[0].mValue.int_value); + // Value for key {uid 2, BACKGROUND}. + ASSERT_EQ(1, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(2, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, true, 0, bucketStartTimeNs + 40 * NS_PER_SEC); + + // Base for dimension key {uid 1}. + it++; + itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); + EXPECT_TRUE(itBase->second.baseInfos[0].hasBase); EXPECT_EQ(6, itBase->second.baseInfos[0].base.long_value); EXPECT_TRUE(itBase->second.hasCurrentState); ASSERT_EQ(1, itBase->second.currentState.getValues().size()); @@ -4563,26 +4721,33 @@ TEST(ValueMetricProducerTest, TestSlicedStateWithPrimaryField_WithDimensions) { ASSERT_EQ(1, it->first.getDimensionKeyInWhat().getValues().size()); EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); - EXPECT_EQ(-1, it->first.getStateValuesKey().getValues()[0].mValue.int_value); + EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); EXPECT_TRUE(it->second.intervals[0].hasValue); EXPECT_EQ(3, it->second.intervals[0].value.long_value); + assertConditionTimer(it->second.conditionTimer, false, 20 * NS_PER_SEC, + bucketStartTimeNs + 20 * NS_PER_SEC); - // Base for dimension key {uid 2} + // Value for key {uid 1, FOREGROUND}. it++; - itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); - EXPECT_TRUE(itBase->second.baseInfos[0].hasBase); - EXPECT_EQ(9, itBase->second.baseInfos[0].base.long_value); - EXPECT_TRUE(itBase->second.hasCurrentState); - ASSERT_EQ(1, itBase->second.currentState.getValues().size()); - EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND, - itBase->second.currentState.getValues()[0].mValue.int_value); + ASSERT_EQ(1, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_FOREGROUND, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, true, 0, bucketStartTimeNs + 20 * NS_PER_SEC); + // Value for key {uid 2, kStateUnknown} + it++; ASSERT_EQ(1, it->first.getDimensionKeyInWhat().getValues().size()); EXPECT_EQ(2, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); - EXPECT_EQ(-1, it->first.getStateValuesKey().getValues()[0].mValue.int_value); + EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); EXPECT_TRUE(it->second.intervals[0].hasValue); EXPECT_EQ(2, it->second.intervals[0].value.long_value); + assertConditionTimer(it->second.conditionTimer, false, 40 * NS_PER_SEC, + bucketStartTimeNs + 40 * NS_PER_SEC); // Pull at end of first bucket. vector> allData; @@ -4612,6 +4777,8 @@ TEST(ValueMetricProducerTest, TestSlicedStateWithPrimaryField_WithDimensions) { EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND, it->first.getStateValuesKey().getValues()[0].mValue.int_value); EXPECT_FALSE(it->second.intervals[0].hasValue); + assertConditionTimer(it->second.conditionTimer, true, 0, bucket2StartTimeNs); + EXPECT_EQ(20 * NS_PER_SEC, valueProducer->mPastBuckets[it->first][0].mConditionTrueNs); // Base for dimension key {uid 1} it++; @@ -4629,6 +4796,8 @@ TEST(ValueMetricProducerTest, TestSlicedStateWithPrimaryField_WithDimensions) { EXPECT_EQ(-1 /* kStateTracker::kUnknown */, it->first.getStateValuesKey().getValues()[0].mValue.int_value); EXPECT_FALSE(it->second.intervals[0].hasValue); + assertConditionTimer(it->second.conditionTimer, false, 0, bucketStartTimeNs + 20 * NS_PER_SEC); + EXPECT_EQ(20 * NS_PER_SEC, valueProducer->mPastBuckets[it->first][0].mConditionTrueNs); // Value for key {uid 1, FOREGROUND} it++; @@ -4638,6 +4807,8 @@ TEST(ValueMetricProducerTest, TestSlicedStateWithPrimaryField_WithDimensions) { EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_FOREGROUND, it->first.getStateValuesKey().getValues()[0].mValue.int_value); EXPECT_FALSE(it->second.intervals[0].hasValue); + assertConditionTimer(it->second.conditionTimer, true, 0, bucket2StartTimeNs); + EXPECT_EQ(40 * NS_PER_SEC, valueProducer->mPastBuckets[it->first][0].mConditionTrueNs); // Value for key {uid 2, kStateUnknown} it++; @@ -4647,13 +4818,16 @@ TEST(ValueMetricProducerTest, TestSlicedStateWithPrimaryField_WithDimensions) { EXPECT_EQ(-1 /* kStateTracker::kUnknown */, it->first.getStateValuesKey().getValues()[0].mValue.int_value); EXPECT_FALSE(it->second.intervals[0].hasValue); + assertConditionTimer(it->second.conditionTimer, false, 0, bucketStartTimeNs + 40 * NS_PER_SEC); + EXPECT_EQ(40 * NS_PER_SEC, valueProducer->mPastBuckets[it->first][0].mConditionTrueNs); // Bucket status after uid 1 process state change from Foreground -> Background. - uidProcessEvent = CreateUidProcessStateChangedEvent( - bucket2StartTimeNs + 20, 1 /* uid */, android::app::PROCESS_STATE_IMPORTANT_BACKGROUND); + uidProcessEvent = + CreateUidProcessStateChangedEvent(bucket2StartTimeNs + 20 * NS_PER_SEC, 1 /* uid */, + android::app::PROCESS_STATE_IMPORTANT_BACKGROUND); StateManager::getInstance().onLogEvent(*uidProcessEvent); - ASSERT_EQ(4UL, valueProducer->mCurrentSlicedBucket.size()); + ASSERT_EQ(5UL, valueProducer->mCurrentSlicedBucket.size()); ASSERT_EQ(4UL, valueProducer->mPastBuckets.size()); ASSERT_EQ(2UL, valueProducer->mCurrentBaseInfo.size()); // Base for dimension key {uid 2}. @@ -4672,6 +4846,8 @@ TEST(ValueMetricProducerTest, TestSlicedStateWithPrimaryField_WithDimensions) { EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND, it->first.getStateValuesKey().getValues()[0].mValue.int_value); EXPECT_FALSE(it->second.intervals[0].hasValue); + assertConditionTimer(it->second.conditionTimer, true, 0, bucket2StartTimeNs); + // Base for dimension key {uid 1} it++; itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); @@ -4688,6 +4864,17 @@ TEST(ValueMetricProducerTest, TestSlicedStateWithPrimaryField_WithDimensions) { EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, it->first.getStateValuesKey().getValues()[0].mValue.int_value); EXPECT_FALSE(it->second.intervals[0].hasValue); + assertConditionTimer(it->second.conditionTimer, false, 0, bucketStartTimeNs + 20 * NS_PER_SEC); + + // Value for key {uid 1, BACKGROUND} + it++; + ASSERT_EQ(1, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, true, 0, bucket2StartTimeNs + 20 * NS_PER_SEC); + // Value for key {uid 1, FOREGROUND} it++; ASSERT_EQ(1, it->first.getDimensionKeyInWhat().getValues().size()); @@ -4697,6 +4884,9 @@ TEST(ValueMetricProducerTest, TestSlicedStateWithPrimaryField_WithDimensions) { it->first.getStateValuesKey().getValues()[0].mValue.int_value); EXPECT_TRUE(it->second.intervals[0].hasValue); EXPECT_EQ(3, it->second.intervals[0].value.long_value); + assertConditionTimer(it->second.conditionTimer, false, 20 * NS_PER_SEC, + bucket2StartTimeNs + 20 * NS_PER_SEC); + // Value for key {uid 2, kStateUnknown} it++; ASSERT_EQ(1, it->first.getDimensionKeyInWhat().getValues().size()); @@ -4705,10 +4895,12 @@ TEST(ValueMetricProducerTest, TestSlicedStateWithPrimaryField_WithDimensions) { EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, it->first.getStateValuesKey().getValues()[0].mValue.int_value); EXPECT_FALSE(it->second.intervals[0].hasValue); + assertConditionTimer(it->second.conditionTimer, false, 0, bucketStartTimeNs + 40 * NS_PER_SEC); // Bucket status after uid 1 process state change Background->Foreground. - uidProcessEvent = CreateUidProcessStateChangedEvent( - bucket2StartTimeNs + 40, 1 /* uid */, android::app::PROCESS_STATE_IMPORTANT_FOREGROUND); + uidProcessEvent = + CreateUidProcessStateChangedEvent(bucket2StartTimeNs + 40 * NS_PER_SEC, 1 /* uid */, + android::app::PROCESS_STATE_IMPORTANT_FOREGROUND); StateManager::getInstance().onLogEvent(*uidProcessEvent); ASSERT_EQ(5UL, valueProducer->mCurrentSlicedBucket.size()); @@ -4729,6 +4921,7 @@ TEST(ValueMetricProducerTest, TestSlicedStateWithPrimaryField_WithDimensions) { EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND, it->first.getStateValuesKey().getValues()[0].mValue.int_value); EXPECT_FALSE(it->second.intervals[0].hasValue); + assertConditionTimer(it->second.conditionTimer, true, 0, bucket2StartTimeNs); // Base for dimension key {uid 1} it++; @@ -4746,6 +4939,7 @@ TEST(ValueMetricProducerTest, TestSlicedStateWithPrimaryField_WithDimensions) { EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, it->first.getStateValuesKey().getValues()[0].mValue.int_value); EXPECT_FALSE(it->second.intervals[0].hasValue); + assertConditionTimer(it->second.conditionTimer, false, 0, bucketStartTimeNs + 20 * NS_PER_SEC); // Value for key {uid 1, BACKGROUND} it++; @@ -4756,6 +4950,8 @@ TEST(ValueMetricProducerTest, TestSlicedStateWithPrimaryField_WithDimensions) { it->first.getStateValuesKey().getValues()[0].mValue.int_value); EXPECT_TRUE(it->second.intervals[0].hasValue); EXPECT_EQ(4, it->second.intervals[0].value.long_value); + assertConditionTimer(it->second.conditionTimer, false, 20 * NS_PER_SEC, + bucket2StartTimeNs + 40 * NS_PER_SEC); // Value for key {uid 1, FOREGROUND} it++; @@ -4766,6 +4962,8 @@ TEST(ValueMetricProducerTest, TestSlicedStateWithPrimaryField_WithDimensions) { it->first.getStateValuesKey().getValues()[0].mValue.int_value); EXPECT_TRUE(it->second.intervals[0].hasValue); EXPECT_EQ(3, it->second.intervals[0].value.long_value); + assertConditionTimer(it->second.conditionTimer, true, 20 * NS_PER_SEC, + bucket2StartTimeNs + 40 * NS_PER_SEC); // Value for key {uid 2, kStateUnknown} it++; @@ -4774,17 +4972,20 @@ TEST(ValueMetricProducerTest, TestSlicedStateWithPrimaryField_WithDimensions) { ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, false, 0, bucketStartTimeNs + 40 * NS_PER_SEC); // Start dump report and check output. ProtoOutputStream output; std::set strSet; - valueProducer->onDumpReport(bucket2StartTimeNs + 50, true /* include recent buckets */, true, - NO_TIME_CONSTRAINTS, &strSet, &output); + valueProducer->onDumpReport(bucket2StartTimeNs + 50 * NS_PER_SEC, + true /* include recent buckets */, true, NO_TIME_CONSTRAINTS, + &strSet, &output); StatsLogReport report = outputStreamToProto(&output); EXPECT_TRUE(report.has_value_metrics()); ASSERT_EQ(5, report.value_metrics().data_size()); + // {uid 1, BACKGROUND} auto data = report.value_metrics().data(0); ASSERT_EQ(1, data.bucket_info_size()); EXPECT_EQ(4, report.value_metrics().data(0).bucket_info(0).values(0).value_long()); @@ -4792,14 +4993,18 @@ TEST(ValueMetricProducerTest, TestSlicedStateWithPrimaryField_WithDimensions) { EXPECT_TRUE(data.slice_by_state(0).has_value()); EXPECT_EQ(android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_BACKGROUND, data.slice_by_state(0).value()); + EXPECT_EQ(20 * NS_PER_SEC, data.bucket_info(0).condition_true_nanos()); + // {uid 2, kStateUnknown} data = report.value_metrics().data(1); ASSERT_EQ(1, report.value_metrics().data(1).bucket_info_size()); EXPECT_EQ(2, report.value_metrics().data(1).bucket_info(0).values(0).value_long()); EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); EXPECT_TRUE(data.slice_by_state(0).has_value()); EXPECT_EQ(-1 /*StateTracker::kStateUnknown*/, data.slice_by_state(0).value()); + EXPECT_EQ(40 * NS_PER_SEC, data.bucket_info(0).condition_true_nanos()); + // {uid 1, FOREGROUND} data = report.value_metrics().data(2); EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); EXPECT_TRUE(data.slice_by_state(0).has_value()); @@ -4808,14 +5013,19 @@ TEST(ValueMetricProducerTest, TestSlicedStateWithPrimaryField_WithDimensions) { ASSERT_EQ(2, report.value_metrics().data(2).bucket_info_size()); EXPECT_EQ(4, report.value_metrics().data(2).bucket_info(0).values(0).value_long()); EXPECT_EQ(7, report.value_metrics().data(2).bucket_info(1).values(0).value_long()); + EXPECT_EQ(40 * NS_PER_SEC, data.bucket_info(0).condition_true_nanos()); + EXPECT_EQ(30 * NS_PER_SEC, data.bucket_info(1).condition_true_nanos()); + // {uid 1, kStateUnknown} data = report.value_metrics().data(3); ASSERT_EQ(1, report.value_metrics().data(3).bucket_info_size()); EXPECT_EQ(3, report.value_metrics().data(3).bucket_info(0).values(0).value_long()); EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); EXPECT_TRUE(data.slice_by_state(0).has_value()); EXPECT_EQ(-1 /*StateTracker::kStateUnknown*/, data.slice_by_state(0).value()); + EXPECT_EQ(20 * NS_PER_SEC, data.bucket_info(0).condition_true_nanos()); + // {uid 2, BACKGROUND} data = report.value_metrics().data(4); EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); EXPECT_TRUE(data.slice_by_state(0).has_value()); @@ -4824,47 +5034,84 @@ TEST(ValueMetricProducerTest, TestSlicedStateWithPrimaryField_WithDimensions) { ASSERT_EQ(2, report.value_metrics().data(4).bucket_info_size()); EXPECT_EQ(6, report.value_metrics().data(4).bucket_info(0).values(0).value_long()); EXPECT_EQ(5, report.value_metrics().data(4).bucket_info(1).values(0).value_long()); + EXPECT_EQ(20 * NS_PER_SEC, data.bucket_info(0).condition_true_nanos()); + EXPECT_EQ(50 * NS_PER_SEC, data.bucket_info(1).condition_true_nanos()); } -TEST(ValueMetricProducerTest, TestSlicedStateWithCondition) { +/* + * Test slicing condition_true_nanos by state for metric that slices by state when data is not + * present in pulled data during a state change. + */ +TEST(ValueMetricProducerTest, TestSlicedStateWithMissingDataInStateChange) { // Set up ValueMetricProducer. - ValueMetric metric = ValueMetricProducerTestHelper::createMetricWithConditionAndState( - "BATTERY_SAVER_MODE_STATE"); + ValueMetric metric = + ValueMetricProducerTestHelper::createMetricWithState("BATTERY_SAVER_MODE_STATE"); sp pullerManager = new StrictMock(); + /* + NOTE: "-" means that the data was not present in the pulled data. + + bucket # 1 + 10 20 30 40 50 60 (seconds) + |-------------------------------------------------------|-- + x (kStateUnknown) + |-----------| + 10 + + x x (ON) + |---------------------| |-----------| + 20 10 + + - (OFF) + */ EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _)) - // Condition changed to true. + // ValueMetricProducer initialized. .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, vector>* data) { - EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 20 * NS_PER_SEC); + EXPECT_EQ(eventTimeNs, bucketStartTimeNs); + data->clear(); + data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs, 3)); + return true; + })) + // Battery saver mode state changed to ON. + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector>* data) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 10 * NS_PER_SEC); data->clear(); data->push_back( - CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 20 * NS_PER_SEC, 3)); + CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 10 * NS_PER_SEC, 5)); return true; })) - // Battery saver mode state changed to OFF. + // Battery saver mode state changed to OFF but data for dimension key {} is not present + // in the pulled data. .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, vector>* data) { EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 30 * NS_PER_SEC); data->clear(); + return true; + })) + // Battery saver mode state changed to ON. + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector>* data) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 40 * NS_PER_SEC); + data->clear(); data->push_back( - CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 30 * NS_PER_SEC, 5)); + CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 40 * NS_PER_SEC, 7)); return true; })) - // Condition changed to false. + // Dump report pull. .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, vector>* data) { - EXPECT_EQ(eventTimeNs, bucket2StartTimeNs + 10 * NS_PER_SEC); + EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 50 * NS_PER_SEC); data->clear(); data->push_back(CreateRepeatedValueLogEvent( - tagId, bucket2StartTimeNs + 10 * NS_PER_SEC, 15)); + tagId, bucketStartTimeNs + 50 * NS_PER_SEC, 15)); return true; })); StateManager::getInstance().clear(); sp valueProducer = - ValueMetricProducerTestHelper::createValueProducerWithConditionAndState( - pullerManager, metric, {util::BATTERY_SAVER_MODE_STATE_CHANGED}, {}, - ConditionState::kFalse); + ValueMetricProducerTestHelper::createValueProducerWithState( + pullerManager, metric, {util::BATTERY_SAVER_MODE_STATE_CHANGED}, {}); EXPECT_EQ(1, valueProducer->mSlicedStateAtoms.size()); // Set up StateManager and check that StateTrackers are initialized. @@ -4874,80 +5121,1696 @@ TEST(ValueMetricProducerTest, TestSlicedStateWithCondition) { EXPECT_EQ(1, StateManager::getInstance().getListenersCount( util::BATTERY_SAVER_MODE_STATE_CHANGED)); + // Bucket status after metric initialized. + ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); + // Base for dimension key {} + auto it = valueProducer->mCurrentSlicedBucket.begin(); + auto itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); + EXPECT_TRUE(itBase->second.hasCurrentState); + ASSERT_EQ(1, itBase->second.currentState.getValues().size()); + EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, + itBase->second.currentState.getValues()[0].mValue.int_value); + // Value for dimension, state key {{}, kStateUnknown} + EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, true, 0, bucketStartTimeNs); + // Bucket status after battery saver mode ON event. - // Condition is false so we do nothing. unique_ptr batterySaverOnEvent = CreateBatterySaverOnEvent(/*timestamp=*/bucketStartTimeNs + 10 * NS_PER_SEC); StateManager::getInstance().onLogEvent(*batterySaverOnEvent); - EXPECT_EQ(0UL, valueProducer->mCurrentSlicedBucket.size()); - EXPECT_EQ(0UL, valueProducer->mCurrentBaseInfo.size()); - // Bucket status after condition change to true. - valueProducer->onConditionChanged(true, bucketStartTimeNs + 20 * NS_PER_SEC); // Base for dimension key {} + ASSERT_EQ(1UL, valueProducer->mCurrentBaseInfo.size()); - std::unordered_map::iterator - itBase = valueProducer->mCurrentBaseInfo.find(DEFAULT_DIMENSION_KEY); - EXPECT_TRUE(itBase->second.baseInfos[0].hasBase); - EXPECT_EQ(3, itBase->second.baseInfos[0].base.long_value); + ASSERT_EQ(2UL, valueProducer->mCurrentSlicedBucket.size()); + it = valueProducer->mCurrentSlicedBucket.begin(); + itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); EXPECT_TRUE(itBase->second.hasCurrentState); ASSERT_EQ(1, itBase->second.currentState.getValues().size()); EXPECT_EQ(BatterySaverModeStateChanged::ON, itBase->second.currentState.getValues()[0].mValue.int_value); + // Value for key {{}, ON} + EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(BatterySaverModeStateChanged::ON, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, true, 0, bucketStartTimeNs + 10 * NS_PER_SEC); + // Value for key {{}, -1} - ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); - std::unordered_map::iterator it = - valueProducer->mCurrentSlicedBucket.begin(); + it++; EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); EXPECT_EQ(-1 /*StateTracker::kUnknown*/, it->first.getStateValuesKey().getValues()[0].mValue.int_value); - EXPECT_FALSE(it->second.intervals[0].hasValue); + assertConditionTimer(it->second.conditionTimer, false, 10 * NS_PER_SEC, + bucketStartTimeNs + 10 * NS_PER_SEC); - // Bucket status after battery saver mode OFF event. + // Bucket status after battery saver mode OFF event which is not present + // in the pulled data. unique_ptr batterySaverOffEvent = CreateBatterySaverOffEvent(/*timestamp=*/bucketStartTimeNs + 30 * NS_PER_SEC); StateManager::getInstance().onLogEvent(*batterySaverOffEvent); + // Base for dimension key {} ASSERT_EQ(1UL, valueProducer->mCurrentBaseInfo.size()); - itBase = valueProducer->mCurrentBaseInfo.find(DEFAULT_DIMENSION_KEY); - EXPECT_TRUE(itBase->second.baseInfos[0].hasBase); - EXPECT_EQ(5, itBase->second.baseInfos[0].base.long_value); - EXPECT_TRUE(itBase->second.hasCurrentState); + ASSERT_EQ(2UL, valueProducer->mCurrentSlicedBucket.size()); + it = valueProducer->mCurrentSlicedBucket.begin(); + itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); + EXPECT_FALSE(itBase->second.hasCurrentState); ASSERT_EQ(1, itBase->second.currentState.getValues().size()); - EXPECT_EQ(BatterySaverModeStateChanged::OFF, + EXPECT_EQ(BatterySaverModeStateChanged::ON, itBase->second.currentState.getValues()[0].mValue.int_value); // Value for key {{}, ON} - ASSERT_EQ(2UL, valueProducer->mCurrentSlicedBucket.size()); - it = valueProducer->mCurrentSlicedBucket.begin(); EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); EXPECT_EQ(BatterySaverModeStateChanged::ON, it->first.getStateValuesKey().getValues()[0].mValue.int_value); - EXPECT_TRUE(it->second.intervals[0].hasValue); - EXPECT_EQ(2, it->second.intervals[0].value.long_value); + assertConditionTimer(it->second.conditionTimer, false, 20 * NS_PER_SEC, + bucketStartTimeNs + 30 * NS_PER_SEC); - // Pull at end of first bucket. - vector> allData; - allData.clear(); - allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs, 11)); - valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs); + // Value for key {{}, -1} + it++; + EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(-1 /*StateTracker::kUnknown*/, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, false, 10 * NS_PER_SEC, + bucketStartTimeNs + 10 * NS_PER_SEC); + + // Bucket status after battery saver mode ON event. + batterySaverOnEvent = + CreateBatterySaverOnEvent(/*timestamp=*/bucketStartTimeNs + 40 * NS_PER_SEC); + StateManager::getInstance().onLogEvent(*batterySaverOnEvent); - EXPECT_EQ(2UL, valueProducer->mPastBuckets.size()); - EXPECT_EQ(3UL, valueProducer->mCurrentSlicedBucket.size()); // Base for dimension key {} ASSERT_EQ(1UL, valueProducer->mCurrentBaseInfo.size()); - itBase = valueProducer->mCurrentBaseInfo.find(DEFAULT_DIMENSION_KEY); - EXPECT_TRUE(itBase->second.baseInfos[0].hasBase); - EXPECT_EQ(11, itBase->second.baseInfos[0].base.long_value); - EXPECT_TRUE(itBase->second.hasCurrentState); + ASSERT_EQ(2UL, valueProducer->mCurrentSlicedBucket.size()); + it = valueProducer->mCurrentSlicedBucket.begin(); + itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); ASSERT_EQ(1, itBase->second.currentState.getValues().size()); - EXPECT_EQ(BatterySaverModeStateChanged::OFF, + EXPECT_EQ(BatterySaverModeStateChanged::ON, itBase->second.currentState.getValues()[0].mValue.int_value); - - // Bucket 2 status after condition change to false. - valueProducer->onConditionChanged(false, bucket2StartTimeNs + 10 * NS_PER_SEC); - // Base for dimension key {} + // Value for key {{}, ON} + EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(BatterySaverModeStateChanged::ON, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, true, 20 * NS_PER_SEC, + bucketStartTimeNs + 40 * NS_PER_SEC); + + // Value for key {{}, -1} + it++; + EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(-1 /*StateTracker::kUnknown*/, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, false, 10 * NS_PER_SEC, + bucketStartTimeNs + 10 * NS_PER_SEC); + + // Start dump report and check output. + ProtoOutputStream output; + std::set strSet; + valueProducer->onDumpReport(bucketStartTimeNs + 50 * NS_PER_SEC, + true /* include recent buckets */, true, NO_TIME_CONSTRAINTS, + &strSet, &output); + + StatsLogReport report = outputStreamToProto(&output); + EXPECT_TRUE(report.has_value_metrics()); + ASSERT_EQ(2, report.value_metrics().data_size()); + + // {{}, kStateUnknown} + ValueMetricData data = report.value_metrics().data(0); + EXPECT_EQ(util::BATTERY_SAVER_MODE_STATE_CHANGED, data.slice_by_state(0).atom_id()); + EXPECT_EQ(-1 /*StateTracker::kUnknown*/, data.slice_by_state(0).value()); + ASSERT_EQ(1, data.bucket_info_size()); + EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).condition_true_nanos()); + + // {{}, ON} + data = report.value_metrics().data(1); + EXPECT_EQ(util::BATTERY_SAVER_MODE_STATE_CHANGED, data.slice_by_state(0).atom_id()); + EXPECT_EQ(BatterySaverModeStateChanged::ON, data.slice_by_state(0).value()); + ASSERT_EQ(1, data.bucket_info_size()); + EXPECT_EQ(30 * NS_PER_SEC, data.bucket_info(0).condition_true_nanos()); +} + +/* + * Test for metric that slices by state when data is not present in pulled data + * during an event and then a flush occurs for the current bucket. With the new + * condition timer behavior, a "new" MetricDimensionKey is inserted into + * `mCurrentSlicedBucket` before intervals are closed/added to that new + * MetricDimensionKey. + */ +TEST(ValueMetricProducerTest, TestSlicedStateWithMissingDataThenFlushBucket) { + // Set up ValueMetricProducer. + ValueMetric metric = + ValueMetricProducerTestHelper::createMetricWithState("BATTERY_SAVER_MODE_STATE"); + sp pullerManager = new StrictMock(); + /* + NOTE: "-" means that the data was not present in the pulled data. + + bucket # 1 + 10 20 30 40 50 60 (seconds) + |-------------------------------------------------------|-- + - (kStateUnknown) + + - (ON) + */ + EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _)) + // ValueMetricProducer initialized but data for dimension key {} is not present + // in the pulled data.. + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector>* data) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs); + data->clear(); + return true; + })) + // Battery saver mode state changed to ON but data for dimension key {} is not present + // in the pulled data. + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector>* data) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 10 * NS_PER_SEC); + data->clear(); + return true; + })) + // Dump report pull. + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector>* data) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 50 * NS_PER_SEC); + data->clear(); + data->push_back(CreateRepeatedValueLogEvent( + tagId, bucketStartTimeNs + 50 * NS_PER_SEC, 15)); + return true; + })); + + StateManager::getInstance().clear(); + sp valueProducer = + ValueMetricProducerTestHelper::createValueProducerWithState( + pullerManager, metric, {util::BATTERY_SAVER_MODE_STATE_CHANGED}, {}); + EXPECT_EQ(1, valueProducer->mSlicedStateAtoms.size()); + + // Set up StateManager and check that StateTrackers are initialized. + StateManager::getInstance().registerListener(util::BATTERY_SAVER_MODE_STATE_CHANGED, + valueProducer); + EXPECT_EQ(1, StateManager::getInstance().getStateTrackersCount()); + EXPECT_EQ(1, StateManager::getInstance().getListenersCount( + util::BATTERY_SAVER_MODE_STATE_CHANGED)); + + // Bucket status after metric initialized. + ASSERT_EQ(0UL, valueProducer->mCurrentSlicedBucket.size()); + ASSERT_EQ(0UL, valueProducer->mCurrentBaseInfo.size()); + + // Bucket status after battery saver mode ON event which is not present + // in the pulled data. + unique_ptr batterySaverOnEvent = + CreateBatterySaverOnEvent(/*timestamp=*/bucketStartTimeNs + 10 * NS_PER_SEC); + StateManager::getInstance().onLogEvent(*batterySaverOnEvent); + + ASSERT_EQ(0UL, valueProducer->mCurrentBaseInfo.size()); + ASSERT_EQ(0UL, valueProducer->mCurrentSlicedBucket.size()); + + // Start dump report and check output. + ProtoOutputStream output; + std::set strSet; + valueProducer->onDumpReport(bucketStartTimeNs + 50 * NS_PER_SEC, + true /* include recent buckets */, true, NO_TIME_CONSTRAINTS, + &strSet, &output); + + StatsLogReport report = outputStreamToProto(&output); + EXPECT_TRUE(report.has_value_metrics()); + ASSERT_EQ(0, report.value_metrics().data_size()); + ASSERT_EQ(2UL, valueProducer->mCurrentSlicedBucket.size()); + ASSERT_EQ(1UL, valueProducer->mCurrentBaseInfo.size()); +} + +TEST(ValueMetricProducerTest, TestSlicedStateWithNoPullOnBucketBoundary) { + // Set up ValueMetricProducer. + ValueMetric metric = + ValueMetricProducerTestHelper::createMetricWithState("BATTERY_SAVER_MODE_STATE"); + sp pullerManager = new StrictMock(); + /* + bucket # 1 bucket # 2 + 10 20 30 40 50 60 70 80 90 100 110 120 (seconds) + |------------------------------------|---------------------------|-- + x (kStateUnknown) + |-----| + 10 + x x (ON) + |-----| |-----------| + 10 20 + x (OFF) + |------------------------| + 40 + */ + EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _)) + // ValueMetricProducer initialized. + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector>* data) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs); + data->clear(); + data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs, 3)); + return true; + })) + // Battery saver mode state changed to ON. + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector>* data) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 10 * NS_PER_SEC); + data->clear(); + data->push_back( + CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 10 * NS_PER_SEC, 5)); + return true; + })) + // Battery saver mode state changed to OFF. + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector>* data) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 20 * NS_PER_SEC); + data->clear(); + data->push_back( + CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 20 * NS_PER_SEC, 7)); + return true; + })) + // Battery saver mode state changed to ON. + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector>* data) { + EXPECT_EQ(eventTimeNs, bucket2StartTimeNs + 30 * NS_PER_SEC); + data->clear(); + data->push_back(CreateRepeatedValueLogEvent( + tagId, bucket2StartTimeNs + 30 * NS_PER_SEC, 10)); + return true; + })) + // Dump report pull. + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector>* data) { + EXPECT_EQ(eventTimeNs, bucket2StartTimeNs + 50 * NS_PER_SEC); + data->clear(); + data->push_back(CreateRepeatedValueLogEvent( + tagId, bucket2StartTimeNs + 50 * NS_PER_SEC, 15)); + return true; + })); + + StateManager::getInstance().clear(); + sp valueProducer = + ValueMetricProducerTestHelper::createValueProducerWithState( + pullerManager, metric, {util::BATTERY_SAVER_MODE_STATE_CHANGED}, {}); + EXPECT_EQ(1, valueProducer->mSlicedStateAtoms.size()); + + // Set up StateManager and check that StateTrackers are initialized. + StateManager::getInstance().registerListener(util::BATTERY_SAVER_MODE_STATE_CHANGED, + valueProducer); + EXPECT_EQ(1, StateManager::getInstance().getStateTrackersCount()); + EXPECT_EQ(1, StateManager::getInstance().getListenersCount( + util::BATTERY_SAVER_MODE_STATE_CHANGED)); + + // Bucket status after metric initialized. + ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); + ASSERT_EQ(1UL, valueProducer->mCurrentBaseInfo.size()); + auto it = valueProducer->mCurrentSlicedBucket.begin(); + auto itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); + EXPECT_TRUE(itBase->second.hasCurrentState); + ASSERT_EQ(1, itBase->second.currentState.getValues().size()); + EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, + itBase->second.currentState.getValues()[0].mValue.int_value); + // Value for dimension, state key {{}, kStateUnknown} + EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, true, 0, bucketStartTimeNs); + + // Bucket status after battery saver mode ON event. + unique_ptr batterySaverOnEvent = + CreateBatterySaverOnEvent(/*timestamp=*/bucketStartTimeNs + 10 * NS_PER_SEC); + StateManager::getInstance().onLogEvent(*batterySaverOnEvent); + + ASSERT_EQ(1UL, valueProducer->mCurrentBaseInfo.size()); + ASSERT_EQ(2UL, valueProducer->mCurrentSlicedBucket.size()); + it = valueProducer->mCurrentSlicedBucket.begin(); + itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); + ASSERT_EQ(1, itBase->second.currentState.getValues().size()); + EXPECT_EQ(BatterySaverModeStateChanged::ON, + itBase->second.currentState.getValues()[0].mValue.int_value); + // Value for key {{}, ON} + EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(BatterySaverModeStateChanged::ON, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, true, 0, bucketStartTimeNs + 10 * NS_PER_SEC); + + // Value for key {{}, -1} + it++; + EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(-1 /*StateTracker::kUnknown*/, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, false, 10 * NS_PER_SEC, + bucketStartTimeNs + 10 * NS_PER_SEC); + + // Bucket status after battery saver mode OFF event. + unique_ptr batterySaverOffEvent = + CreateBatterySaverOffEvent(/*timestamp=*/bucketStartTimeNs + 20 * NS_PER_SEC); + StateManager::getInstance().onLogEvent(*batterySaverOffEvent); + + ASSERT_EQ(1UL, valueProducer->mCurrentBaseInfo.size()); + ASSERT_EQ(3UL, valueProducer->mCurrentSlicedBucket.size()); + it = valueProducer->mCurrentSlicedBucket.begin(); + itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); + ASSERT_EQ(1, itBase->second.currentState.getValues().size()); + EXPECT_EQ(BatterySaverModeStateChanged::OFF, + itBase->second.currentState.getValues()[0].mValue.int_value); + // Value for key {{}, OFF} + EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(BatterySaverModeStateChanged::OFF, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, true, 0, bucketStartTimeNs + 20 * NS_PER_SEC); + + // Value for key {{}, ON} + it++; + EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(BatterySaverModeStateChanged::ON, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, false, 10 * NS_PER_SEC, + bucketStartTimeNs + 20 * NS_PER_SEC); + + // Value for key {{}, -1} + it++; + EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(-1 /*StateTracker::kUnknown*/, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, false, 10 * NS_PER_SEC, + bucketStartTimeNs + 10 * NS_PER_SEC); + + // Bucket status after battery saver mode ON event. + batterySaverOnEvent = + CreateBatterySaverOnEvent(/*timestamp=*/bucket2StartTimeNs + 30 * NS_PER_SEC); + StateManager::getInstance().onLogEvent(*batterySaverOnEvent); + + ASSERT_EQ(1UL, valueProducer->mCurrentBaseInfo.size()); + ASSERT_EQ(3UL, valueProducer->mCurrentSlicedBucket.size()); + it = valueProducer->mCurrentSlicedBucket.begin(); + itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); + ASSERT_EQ(1, itBase->second.currentState.getValues().size()); + EXPECT_EQ(BatterySaverModeStateChanged::ON, + itBase->second.currentState.getValues()[0].mValue.int_value); + // Value for key {{}, OFF} + EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(BatterySaverModeStateChanged::OFF, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, false, 30 * NS_PER_SEC, + bucket2StartTimeNs + 30 * NS_PER_SEC); + + // Value for key {{}, ON} + it++; + EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(BatterySaverModeStateChanged::ON, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, true, 0, bucket2StartTimeNs + 30 * NS_PER_SEC); + + // Value for key {{}, -1} + it++; + EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(-1 /*StateTracker::kUnknown*/, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, false, 0, bucketStartTimeNs + 10 * NS_PER_SEC); + + // Start dump report and check output. + ProtoOutputStream output; + std::set strSet; + valueProducer->onDumpReport(bucket2StartTimeNs + 50 * NS_PER_SEC, + true /* include recent buckets */, true, NO_TIME_CONSTRAINTS, + &strSet, &output); + + StatsLogReport report = outputStreamToProto(&output); + EXPECT_TRUE(report.has_value_metrics()); + ASSERT_EQ(3, report.value_metrics().data_size()); + + // {{}, kStateUnknown} + ValueMetricData data = report.value_metrics().data(0); + EXPECT_EQ(util::BATTERY_SAVER_MODE_STATE_CHANGED, data.slice_by_state(0).atom_id()); + EXPECT_EQ(-1 /*StateTracker::kUnknown*/, data.slice_by_state(0).value()); + ASSERT_EQ(1, data.bucket_info_size()); + EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).condition_true_nanos()); + + // {{}, ON} + data = report.value_metrics().data(1); + EXPECT_EQ(util::BATTERY_SAVER_MODE_STATE_CHANGED, data.slice_by_state(0).atom_id()); + EXPECT_EQ(BatterySaverModeStateChanged::ON, data.slice_by_state(0).value()); + ASSERT_EQ(2, data.bucket_info_size()); + EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).condition_true_nanos()); + EXPECT_EQ(20 * NS_PER_SEC, data.bucket_info(1).condition_true_nanos()); + + // {{}, OFF} + data = report.value_metrics().data(2); + EXPECT_EQ(util::BATTERY_SAVER_MODE_STATE_CHANGED, data.slice_by_state(0).atom_id()); + EXPECT_EQ(BatterySaverModeStateChanged::OFF, data.slice_by_state(0).value()); + ASSERT_EQ(1, data.bucket_info_size()); + EXPECT_EQ(40 * NS_PER_SEC, data.bucket_info(0).condition_true_nanos()); +} + +/* + * Test slicing condition_true_nanos by state for metric that slices by state when data is not + * present in pulled data during a condition change. + */ +TEST(ValueMetricProducerTest, TestSlicedStateWithDataMissingInConditionChange) { + // Set up ValueMetricProducer. + ValueMetric metric = ValueMetricProducerTestHelper::createMetricWithConditionAndState( + "BATTERY_SAVER_MODE_STATE"); + sp pullerManager = new StrictMock(); + /* + NOTE: "-" means that the data was not present in the pulled data. + + bucket # 1 + 10 20 30 40 50 60 (seconds) + |-------------------------------------------------------|-- + + T F T (Condition) + x (ON) + |----------------------| - + 20 + */ + EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _)) + // Battery saver mode state changed to ON. + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector>* data) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 10 * NS_PER_SEC); + data->clear(); + data->push_back( + CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 10 * NS_PER_SEC, 3)); + return true; + })) + // Condition changed to false. + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector>* data) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 30 * NS_PER_SEC); + data->clear(); + data->push_back( + CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 30 * NS_PER_SEC, 5)); + return true; + })) + // Condition changed to true but data for dimension key {} is not present in the + // pulled data. + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector>* data) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 40 * NS_PER_SEC); + data->clear(); + return true; + })) + // Dump report pull. + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector>* data) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 50 * NS_PER_SEC); + data->clear(); + data->push_back(CreateRepeatedValueLogEvent( + tagId, bucketStartTimeNs + 50 * NS_PER_SEC, 20)); + return true; + })); + + StateManager::getInstance().clear(); + sp valueProducer = + ValueMetricProducerTestHelper::createValueProducerWithConditionAndState( + pullerManager, metric, {util::BATTERY_SAVER_MODE_STATE_CHANGED}, {}, + ConditionState::kTrue); + EXPECT_EQ(1, valueProducer->mSlicedStateAtoms.size()); + + // Set up StateManager and check that StateTrackers are initialized. + StateManager::getInstance().registerListener(util::BATTERY_SAVER_MODE_STATE_CHANGED, + valueProducer); + EXPECT_EQ(1, StateManager::getInstance().getStateTrackersCount()); + EXPECT_EQ(1, StateManager::getInstance().getListenersCount( + util::BATTERY_SAVER_MODE_STATE_CHANGED)); + + // Bucket status after battery saver mode ON event. + unique_ptr batterySaverOnEvent = + CreateBatterySaverOnEvent(/*timestamp=*/bucketStartTimeNs + 10 * NS_PER_SEC); + StateManager::getInstance().onLogEvent(*batterySaverOnEvent); + // Base for dimension key {} + ASSERT_EQ(1UL, valueProducer->mCurrentBaseInfo.size()); + ASSERT_EQ(2UL, valueProducer->mCurrentSlicedBucket.size()); + auto it = valueProducer->mCurrentSlicedBucket.begin(); + auto itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); + EXPECT_TRUE(itBase->second.hasCurrentState); + ASSERT_EQ(1, itBase->second.currentState.getValues().size()); + EXPECT_EQ(BatterySaverModeStateChanged::ON, + itBase->second.currentState.getValues()[0].mValue.int_value); + // Value for key {{}, ON} + EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(BatterySaverModeStateChanged::ON, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, true, 0, bucketStartTimeNs + 10 * NS_PER_SEC); + + // Value for key {{}, -1} + it++; + EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(-1 /*StateTracker::kUnknown*/, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, false, 0, 0); + + // Bucket status after condition change to false. + valueProducer->onConditionChanged(false, bucketStartTimeNs + 30 * NS_PER_SEC); + // Base for dimension key {} + ASSERT_EQ(1UL, valueProducer->mCurrentBaseInfo.size()); + ASSERT_EQ(2UL, valueProducer->mCurrentSlicedBucket.size()); + it = valueProducer->mCurrentSlicedBucket.begin(); + itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); + EXPECT_TRUE(itBase->second.hasCurrentState); + ASSERT_EQ(1, itBase->second.currentState.getValues().size()); + EXPECT_EQ(BatterySaverModeStateChanged::ON, + itBase->second.currentState.getValues()[0].mValue.int_value); + // Value for key {{}, ON} + EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(BatterySaverModeStateChanged::ON, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, false, 20 * NS_PER_SEC, + bucketStartTimeNs + 30 * NS_PER_SEC); + + // Value for key {{}, -1} + it++; + EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(-1 /*StateTracker::kUnknown*/, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, false, 0, 0); + + // Bucket status after condition change to true. + valueProducer->onConditionChanged(true, bucketStartTimeNs + 40 * NS_PER_SEC); + // Base for dimension key {} + ASSERT_EQ(1UL, valueProducer->mCurrentBaseInfo.size()); + ASSERT_EQ(2UL, valueProducer->mCurrentSlicedBucket.size()); + it = valueProducer->mCurrentSlicedBucket.begin(); + itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); + EXPECT_FALSE(itBase->second.hasCurrentState); + ASSERT_EQ(1, itBase->second.currentState.getValues().size()); + EXPECT_EQ(BatterySaverModeStateChanged::ON, + itBase->second.currentState.getValues()[0].mValue.int_value); + // Value for key {{}, ON} + EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(BatterySaverModeStateChanged::ON, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, false, 20 * NS_PER_SEC, + bucketStartTimeNs + 30 * NS_PER_SEC); + + // Value for key {{}, -1} + it++; + EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(-1 /*StateTracker::kUnknown*/, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, false, 0, 0); + + // Start dump report and check output. + ProtoOutputStream output; + std::set strSet; + valueProducer->onDumpReport(bucketStartTimeNs + 50 * NS_PER_SEC, + true /* include recent buckets */, true, NO_TIME_CONSTRAINTS, + &strSet, &output); + + StatsLogReport report = outputStreamToProto(&output); + EXPECT_TRUE(report.has_value_metrics()); + ASSERT_EQ(1, report.value_metrics().data_size()); + + // {{}, ON} + ValueMetricData data = report.value_metrics().data(0); + EXPECT_EQ(util::BATTERY_SAVER_MODE_STATE_CHANGED, data.slice_by_state(0).atom_id()); + EXPECT_EQ(BatterySaverModeStateChanged::ON, data.slice_by_state(0).value()); + ASSERT_EQ(1, data.bucket_info_size()); + EXPECT_EQ(20 * NS_PER_SEC, data.bucket_info(0).condition_true_nanos()); +} + +/* + * Test slicing condition_true_nanos by state for metric that slices by state with a primary field, + * condition, and has multiple dimensions. + */ +TEST(ValueMetricProducerTest, TestSlicedStateWithMultipleDimensions) { + // Set up ValueMetricProducer. + ValueMetric metric = + ValueMetricProducerTestHelper::createMetricWithConditionAndState("UID_PROCESS_STATE"); + metric.mutable_dimensions_in_what()->set_field(tagId); + metric.mutable_dimensions_in_what()->add_child()->set_field(1); + metric.mutable_dimensions_in_what()->add_child()->set_field(3); + + MetricStateLink* stateLink = metric.add_state_link(); + stateLink->set_state_atom_id(UID_PROCESS_STATE_ATOM_ID); + auto fieldsInWhat = stateLink->mutable_fields_in_what(); + *fieldsInWhat = CreateDimensions(tagId, {1 /* uid */}); + auto fieldsInState = stateLink->mutable_fields_in_state(); + *fieldsInState = CreateDimensions(UID_PROCESS_STATE_ATOM_ID, {1 /* uid */}); + + /* + bucket # 1 bucket # 2 + 10 20 30 40 50 60 70 80 90 100 110 120 (seconds) + |------------------------------------------|---------------------------------|-- + + T F T (Condition) + (FOREGROUND) + x {1, 14} + |------| + 10 + + x {1, 16} + |------| + 10 + x {2, 8} + |-------------| + 20 + + (BACKGROUND) + x {1, 14} + |-------------| |----------|---------------------------------| + 20 15 50 + + x {1, 16} + |-------------| |----------|---------------------------------| + 20 15 50 + + x {2, 8} + |----------| |----------|-------------------| + 15 15 30 + */ + sp pullerManager = new StrictMock(); + EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _)) + // Uid 1 process state change from kStateUnknown -> Foreground + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector>* data) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 10 * NS_PER_SEC); + data->clear(); + data->push_back(CreateThreeValueLogEvent(tagId, bucketStartTimeNs + 10 * NS_PER_SEC, + 1 /*uid*/, 3, 14 /*tag*/)); + data->push_back(CreateThreeValueLogEvent(tagId, bucketStartTimeNs + 10 * NS_PER_SEC, + 1 /*uid*/, 3, 16 /*tag*/)); + + // This event should be skipped. + data->push_back(CreateThreeValueLogEvent(tagId, bucketStartTimeNs + 10 * NS_PER_SEC, + 2 /*uid*/, 5, 8 /*tag*/)); + return true; + })) + // Uid 1 process state change from Foreground -> Background + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector>* data) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 20 * NS_PER_SEC); + data->clear(); + data->push_back(CreateThreeValueLogEvent(tagId, bucketStartTimeNs + 20 * NS_PER_SEC, + 1 /*uid*/, 5, 14 /*tag*/)); + data->push_back(CreateThreeValueLogEvent(tagId, bucketStartTimeNs + 20 * NS_PER_SEC, + 1 /*uid*/, 5, 16 /*tag*/)); + + // This event should be skipped. + data->push_back(CreateThreeValueLogEvent(tagId, bucketStartTimeNs + 20 * NS_PER_SEC, + 2 /*uid*/, 7, 8 /*tag*/)); + + return true; + })) + // Uid 2 process state change from kStateUnknown -> Background + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector>* data) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 25 * NS_PER_SEC); + data->clear(); + data->push_back(CreateThreeValueLogEvent(tagId, bucketStartTimeNs + 25 * NS_PER_SEC, + 2 /*uid*/, 9, 8 /*tag*/)); + + // This event should be skipped. + data->push_back(CreateThreeValueLogEvent(tagId, bucketStartTimeNs + 25 * NS_PER_SEC, + 1 /*uid*/, 9, 14 /* tag */)); + + // This event should be skipped. + data->push_back(CreateThreeValueLogEvent(tagId, bucketStartTimeNs + 25 * NS_PER_SEC, + 1 /*uid*/, 9, 16 /* tag */)); + + return true; + })) + // Condition changed to false. + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector>* data) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 40 * NS_PER_SEC); + data->clear(); + data->push_back(CreateThreeValueLogEvent(tagId, bucketStartTimeNs + 40 * NS_PER_SEC, + 1 /*uid*/, 11, 14 /* tag */)); + data->push_back(CreateThreeValueLogEvent(tagId, bucketStartTimeNs + 40 * NS_PER_SEC, + 1 /*uid*/, 11, 16 /* tag */)); + data->push_back(CreateThreeValueLogEvent(tagId, bucketStartTimeNs + 40 * NS_PER_SEC, + 2 /*uid*/, 11, 8 /*tag*/)); + + return true; + })) + // Condition changed to true. + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector>* data) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 45 * NS_PER_SEC); + data->clear(); + data->push_back(CreateThreeValueLogEvent(tagId, bucketStartTimeNs + 45 * NS_PER_SEC, + 1 /*uid*/, 13, 14 /* tag */)); + data->push_back(CreateThreeValueLogEvent(tagId, bucketStartTimeNs + 45 * NS_PER_SEC, + 1 /*uid*/, 13, 16 /* tag */)); + data->push_back(CreateThreeValueLogEvent(tagId, bucketStartTimeNs + 45 * NS_PER_SEC, + 2 /*uid*/, 13, 8 /*tag*/)); + return true; + })) + // Uid 2 process state change from Background -> Foreground + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector>* data) { + EXPECT_EQ(eventTimeNs, bucket2StartTimeNs + 30 * NS_PER_SEC); + data->clear(); + data->push_back(CreateThreeValueLogEvent( + tagId, bucket2StartTimeNs + 30 * NS_PER_SEC, 2 /*uid*/, 18, 8 /*tag*/)); + + // This event should be skipped. + data->push_back(CreateThreeValueLogEvent( + tagId, bucket2StartTimeNs + 30 * NS_PER_SEC, 1 /*uid*/, 18, 14 /* tag */)); + // This event should be skipped. + data->push_back(CreateThreeValueLogEvent( + tagId, bucket2StartTimeNs + 30 * NS_PER_SEC, 1 /*uid*/, 18, 16 /* tag */)); + + return true; + })) + // Dump report pull. + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector>* data) { + EXPECT_EQ(eventTimeNs, bucket2StartTimeNs + 50 * NS_PER_SEC); + data->clear(); + data->push_back(CreateThreeValueLogEvent( + tagId, bucket2StartTimeNs + 50 * NS_PER_SEC, 1 /*uid*/, 21, 14 /* tag */)); + data->push_back(CreateThreeValueLogEvent( + tagId, bucket2StartTimeNs + 50 * NS_PER_SEC, 1 /*uid*/, 21, 16 /* tag */)); + data->push_back(CreateThreeValueLogEvent( + tagId, bucket2StartTimeNs + 50 * NS_PER_SEC, 2 /*uid*/, 21, 8 /*tag*/)); + return true; + })); + + StateManager::getInstance().clear(); + sp valueProducer = + ValueMetricProducerTestHelper::createValueProducerWithConditionAndState( + pullerManager, metric, {UID_PROCESS_STATE_ATOM_ID}, {}, ConditionState::kTrue); + EXPECT_EQ(1, valueProducer->mSlicedStateAtoms.size()); + + // Set up StateManager and check that StateTrackers are initialized. + StateManager::getInstance().registerListener(UID_PROCESS_STATE_ATOM_ID, valueProducer); + EXPECT_EQ(1, StateManager::getInstance().getStateTrackersCount()); + EXPECT_EQ(1, StateManager::getInstance().getListenersCount(UID_PROCESS_STATE_ATOM_ID)); + + // Condition is true. + // Bucket status after uid 1 process state change kStateUnknown -> Foreground. + auto uidProcessEvent = + CreateUidProcessStateChangedEvent(bucketStartTimeNs + 10 * NS_PER_SEC, 1 /* uid */, + android::app::PROCESS_STATE_IMPORTANT_FOREGROUND); + StateManager::getInstance().onLogEvent(*uidProcessEvent); + ASSERT_EQ(2UL, valueProducer->mCurrentBaseInfo.size()); + ASSERT_EQ(4UL, valueProducer->mCurrentSlicedBucket.size()); + // Base for dimension {uid 1, tag 16}. + auto it = valueProducer->mCurrentSlicedBucket.begin(); + auto itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); + ASSERT_EQ(1, itBase->second.currentState.getValues().size()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_FOREGROUND, + itBase->second.currentState.getValues()[0].mValue.int_value); + // Value for key {{uid 1, uid 16}, FOREGROUND}. + ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + EXPECT_EQ(16, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_FOREGROUND, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, true, 0, bucketStartTimeNs + 10 * NS_PER_SEC); + // Value for key {{uid 1, tag 16}, kStateUnknown}. + it++; + ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + EXPECT_EQ(16, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, false, 0, 0); + + // Base for dimension key {uid 1, tag 14}. + it++; + itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); + ASSERT_EQ(1, itBase->second.currentState.getValues().size()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_FOREGROUND, + itBase->second.currentState.getValues()[0].mValue.int_value); + // Value for key {{uid 1, tag 14}, FOREGROUND}. + ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + EXPECT_EQ(14, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_FOREGROUND, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, true, 0, bucketStartTimeNs + 10 * NS_PER_SEC); + // Value for key {{uid 1, tag 14}, kStateUnknown}. + it++; + ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + EXPECT_EQ(14, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, false, 0, 0); + + // Bucket status after uid 1 process state change Foreground -> Background. + uidProcessEvent = + CreateUidProcessStateChangedEvent(bucketStartTimeNs + 20 * NS_PER_SEC, 1 /* uid */, + android::app::PROCESS_STATE_IMPORTANT_BACKGROUND); + StateManager::getInstance().onLogEvent(*uidProcessEvent); + ASSERT_EQ(2UL, valueProducer->mCurrentBaseInfo.size()); + ASSERT_EQ(6UL, valueProducer->mCurrentSlicedBucket.size()); + // Base for dimension {uid 1, tag 16}. + it = valueProducer->mCurrentSlicedBucket.begin(); + itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); + ASSERT_EQ(1, itBase->second.currentState.getValues().size()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND, + itBase->second.currentState.getValues()[0].mValue.int_value); + // Value for key {{uid 1, uid 16}, BACKGROUND}. + ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + EXPECT_EQ(16, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, true, 0, bucketStartTimeNs + 20 * NS_PER_SEC); + + // Base for dimension key {uid 1, tag 14}. + it++; + itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); + ASSERT_EQ(1, itBase->second.currentState.getValues().size()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND, + itBase->second.currentState.getValues()[0].mValue.int_value); + // Value for key {{uid 1, tag 14}, BACKGROUND}. + ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + EXPECT_EQ(14, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, true, 0, bucketStartTimeNs + 20 * NS_PER_SEC); + + // Value for key {{uid 1, uid 16}, FOREGROUND}. + it++; + ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + EXPECT_EQ(16, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_FOREGROUND, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, false, 10 * NS_PER_SEC, + bucketStartTimeNs + 20 * NS_PER_SEC); + + // Value for key {{uid 1, tag 16}, kStateUnknown}. + it++; + ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + EXPECT_EQ(16, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, false, 0, 0); + + // Value for key {{uid 1, tag 14}, FOREGROUND}. + it++; + ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + EXPECT_EQ(14, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_FOREGROUND, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, false, 10 * NS_PER_SEC, + bucketStartTimeNs + 20 * NS_PER_SEC); + + // Value for key {{uid 1, tag 14}, kStateUnknown}. + it++; + ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + EXPECT_EQ(14, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, false, 0, 0); + + // Bucket status after uid 2 process state change kStateUnknown -> Background. + uidProcessEvent = + CreateUidProcessStateChangedEvent(bucketStartTimeNs + 25 * NS_PER_SEC, 2 /* uid */, + android::app::PROCESS_STATE_IMPORTANT_BACKGROUND); + StateManager::getInstance().onLogEvent(*uidProcessEvent); + ASSERT_EQ(3UL, valueProducer->mCurrentBaseInfo.size()); + ASSERT_EQ(8UL, valueProducer->mCurrentSlicedBucket.size()); + // Base for dimension {uid 2, tag 8}. + it = valueProducer->mCurrentSlicedBucket.begin(); + itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); + ASSERT_EQ(1, itBase->second.currentState.getValues().size()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND, + itBase->second.currentState.getValues()[0].mValue.int_value); + // Value for key {{uid 2, uid 8}, BACKGROUND}. + ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(2, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + EXPECT_EQ(8, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, true, 0, bucketStartTimeNs + 25 * NS_PER_SEC); + + // Value for key {{uid 2, uid 8}, kStateUnknown}. + it++; + ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(2, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + EXPECT_EQ(8, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, false, 0, 0); + + // Base for dimension {uid 1, tag 16}. + it++; + itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); + ASSERT_EQ(1, itBase->second.currentState.getValues().size()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND, + itBase->second.currentState.getValues()[0].mValue.int_value); + // Value for key {{uid 1, uid 16}, BACKGROUND}. + ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + EXPECT_EQ(16, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, true, 0, bucketStartTimeNs + 20 * NS_PER_SEC); + + // Base for dimension key {uid 1, tag 14}. + it++; + itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); + ASSERT_EQ(1, itBase->second.currentState.getValues().size()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND, + itBase->second.currentState.getValues()[0].mValue.int_value); + // Value for key {{uid 1, tag 14}, BACKGROUND}. + ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + EXPECT_EQ(14, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, true, 0, bucketStartTimeNs + 20 * NS_PER_SEC); + + // Value for key {{uid 1, uid 16}, FOREGROUND}. + it++; + ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + EXPECT_EQ(16, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_FOREGROUND, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, false, 10 * NS_PER_SEC, + bucketStartTimeNs + 20 * NS_PER_SEC); + + // Value for key {{uid 1, tag 16}, kStateUnknown}. + it++; + ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + EXPECT_EQ(16, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, false, 0, 0); + + // Value for key {{uid 1, tag 14}, FOREGROUND}. + it++; + ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + EXPECT_EQ(14, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_FOREGROUND, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, false, 10 * NS_PER_SEC, + bucketStartTimeNs + 20 * NS_PER_SEC); + + // Value for key {{uid 1, tag 14}, kStateUnknown}. + it++; + ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + EXPECT_EQ(14, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, false, 0, 0); + + // Bucket 1 status after condition change to false. + // All condition timers should be turned off. + valueProducer->onConditionChanged(false, bucketStartTimeNs + 40 * NS_PER_SEC); + ASSERT_EQ(3UL, valueProducer->mCurrentBaseInfo.size()); + ASSERT_EQ(8UL, valueProducer->mCurrentSlicedBucket.size()); + // Base for dimension {uid 2, tag 8}. + it = valueProducer->mCurrentSlicedBucket.begin(); + itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); + ASSERT_EQ(1, itBase->second.currentState.getValues().size()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND, + itBase->second.currentState.getValues()[0].mValue.int_value); + // Value for key {{uid 2, uid 8}, BACKGROUND}. + ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(2, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + EXPECT_EQ(8, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, false, 15 * NS_PER_SEC, + bucketStartTimeNs + 40 * NS_PER_SEC); + + // Value for key {{uid 2, uid 8}, kStateUnknown}. + it++; + ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(2, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + EXPECT_EQ(8, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, false, 0, 0); + + // Base for dimension {uid 1, tag 16}. + it++; + itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); + ASSERT_EQ(1, itBase->second.currentState.getValues().size()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND, + itBase->second.currentState.getValues()[0].mValue.int_value); + // Value for key {{uid 1, uid 16}, BACKGROUND}. + ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + EXPECT_EQ(16, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, false, 20 * NS_PER_SEC, + bucketStartTimeNs + 40 * NS_PER_SEC); + + // Base for dimension key {uid 1, tag 14}. + it++; + itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); + ASSERT_EQ(1, itBase->second.currentState.getValues().size()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND, + itBase->second.currentState.getValues()[0].mValue.int_value); + // Value for key {{uid 1, tag 14}, BACKGROUND}. + ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + EXPECT_EQ(14, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, false, 20 * NS_PER_SEC, + bucketStartTimeNs + 40 * NS_PER_SEC); + + // Value for key {{uid 1, uid 16}, FOREGROUND}. + it++; + ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + EXPECT_EQ(16, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_FOREGROUND, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, false, 10 * NS_PER_SEC, + bucketStartTimeNs + 20 * NS_PER_SEC); + + // Value for key {{uid 1, tag 16}, kStateUnknown}. + it++; + ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + EXPECT_EQ(16, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, false, 0, 0); + + // Value for key {{uid 1, tag 14}, FOREGROUND}. + it++; + ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + EXPECT_EQ(14, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_FOREGROUND, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, false, 10 * NS_PER_SEC, + bucketStartTimeNs + 20 * NS_PER_SEC); + + // Value for key {{uid 1, tag 14}, kStateUnknown}. + it++; + ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + EXPECT_EQ(14, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, false, 0, 0); + + // Bucket 1 status after condition change to true. + valueProducer->onConditionChanged(true, bucketStartTimeNs + 45 * NS_PER_SEC); + ASSERT_EQ(3UL, valueProducer->mCurrentBaseInfo.size()); + ASSERT_EQ(8UL, valueProducer->mCurrentSlicedBucket.size()); + // Base for dimension {uid 2, tag 8}. + it = valueProducer->mCurrentSlicedBucket.begin(); + itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); + ASSERT_EQ(1, itBase->second.currentState.getValues().size()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND, + itBase->second.currentState.getValues()[0].mValue.int_value); + // Value for key {{uid 2, uid 8}, BACKGROUND}. + ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(2, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + EXPECT_EQ(8, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, true, 15 * NS_PER_SEC, + bucketStartTimeNs + 45 * NS_PER_SEC); + + // Value for key {{uid 2, uid 8}, kStateUnknown}. + it++; + ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(2, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + EXPECT_EQ(8, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, false, 0, 0); + + // Base for dimension {uid 1, tag 16}. + it++; + itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); + ASSERT_EQ(1, itBase->second.currentState.getValues().size()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND, + itBase->second.currentState.getValues()[0].mValue.int_value); + // Value for key {{uid 1, uid 16}, BACKGROUND}. + ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + EXPECT_EQ(16, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, true, 20 * NS_PER_SEC, + bucketStartTimeNs + 45 * NS_PER_SEC); + + // Base for dimension key {uid 1, tag 14}. + it++; + itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND, + itBase->second.currentState.getValues()[0].mValue.int_value); + // Value for key {{uid 1, tag 14}, BACKGROUND}. + ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + EXPECT_EQ(14, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, true, 20 * NS_PER_SEC, + bucketStartTimeNs + 45 * NS_PER_SEC); + + // Value for key {{uid 1, uid 16}, FOREGROUND}. + it++; + ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + EXPECT_EQ(16, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_FOREGROUND, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, false, 10 * NS_PER_SEC, + bucketStartTimeNs + 20 * NS_PER_SEC); + + // Value for key {{uid 1, tag 16}, kStateUnknown}. + it++; + ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + EXPECT_EQ(16, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, false, 0, 0); + + // Value for key {{uid 1, tag 14}, FOREGROUND}. + it++; + ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + EXPECT_EQ(14, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_FOREGROUND, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, false, 10 * NS_PER_SEC, + bucketStartTimeNs + 20 * NS_PER_SEC); + + // Value for key {{uid 1, tag 14}, kStateUnknown}. + it++; + ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + EXPECT_EQ(14, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, false, 0, 0); + + // Pull at end of first bucket. + vector> allData; + allData.push_back( + CreateThreeValueLogEvent(tagId, bucket2StartTimeNs, 1 /*uid*/, 13, 14 /* tag */)); + allData.push_back( + CreateThreeValueLogEvent(tagId, bucket2StartTimeNs, 1 /*uid*/, 13, 16 /* tag */)); + allData.push_back( + CreateThreeValueLogEvent(tagId, bucket2StartTimeNs, 2 /*uid*/, 13, 8 /*tag*/)); + valueProducer->onDataPulled(allData, /** succeeds */ true, bucket2StartTimeNs + 1); + + // Buckets flushed after end of first bucket. + // All condition timers' behavior should rollover to bucket 2. + ASSERT_EQ(8UL, valueProducer->mCurrentSlicedBucket.size()); + ASSERT_EQ(5UL, valueProducer->mPastBuckets.size()); + ASSERT_EQ(3UL, valueProducer->mCurrentBaseInfo.size()); + // Base for dimension {uid 2, tag 8}. + it = valueProducer->mCurrentSlicedBucket.begin(); + itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); + ASSERT_EQ(1, itBase->second.currentState.getValues().size()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND, + itBase->second.currentState.getValues()[0].mValue.int_value); + // Value for key {{uid 2, uid 8}, BACKGROUND}. + ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(2, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + EXPECT_EQ(8, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, true, 0, bucket2StartTimeNs); + ASSERT_EQ(1, valueProducer->mPastBuckets[it->first].size()); + EXPECT_EQ(30 * NS_PER_SEC, valueProducer->mPastBuckets[it->first][0].mConditionTrueNs); + + // Value for key {{uid 2, uid 8}, kStateUnknown}. + it++; + ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(2, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + EXPECT_EQ(8, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, false, 0, 0); + + // Base for dimension {uid 1, tag 16}. + it++; + itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); + ASSERT_EQ(1, itBase->second.currentState.getValues().size()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND, + itBase->second.currentState.getValues()[0].mValue.int_value); + // Value for key {{uid 1, uid 16}, BACKGROUND}. + ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + EXPECT_EQ(16, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, true, 0, bucket2StartTimeNs); + ASSERT_EQ(1, valueProducer->mPastBuckets[it->first].size()); + EXPECT_EQ(35 * NS_PER_SEC, valueProducer->mPastBuckets[it->first][0].mConditionTrueNs); + + // Base for dimension key {uid 1, tag 14}. + it++; + itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND, + itBase->second.currentState.getValues()[0].mValue.int_value); + // Value for key {{uid 1, tag 14}, BACKGROUND}. + ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + EXPECT_EQ(14, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, true, 0, bucket2StartTimeNs); + ASSERT_EQ(1, valueProducer->mPastBuckets[it->first].size()); + EXPECT_EQ(35 * NS_PER_SEC, valueProducer->mPastBuckets[it->first][0].mConditionTrueNs); + + // Value for key {{uid 1, uid 16}, FOREGROUND}. + it++; + ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + EXPECT_EQ(16, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_FOREGROUND, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, false, 0, bucketStartTimeNs + 20 * NS_PER_SEC); + ASSERT_EQ(1, valueProducer->mPastBuckets[it->first].size()); + EXPECT_EQ(10 * NS_PER_SEC, valueProducer->mPastBuckets[it->first][0].mConditionTrueNs); + + // Value for key {{uid 1, tag 16}, kStateUnknown}. + it++; + ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + EXPECT_EQ(16, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, false, 0, 0); + + // Value for key {{uid 1, tag 14}, FOREGROUND}. + it++; + ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + EXPECT_EQ(14, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_FOREGROUND, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, false, 0, bucketStartTimeNs + 20 * NS_PER_SEC); + ASSERT_EQ(1, valueProducer->mPastBuckets[it->first].size()); + EXPECT_EQ(10 * NS_PER_SEC, valueProducer->mPastBuckets[it->first][0].mConditionTrueNs); + + // Value for key {{uid 1, tag 14}, kStateUnknown}. + it++; + ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + EXPECT_EQ(14, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, false, 0, 0); + + // Bucket 2 status after uid 2 process state change Background->Foreground. + uidProcessEvent = + CreateUidProcessStateChangedEvent(bucket2StartTimeNs + 30 * NS_PER_SEC, 2 /* uid */, + android::app::PROCESS_STATE_IMPORTANT_FOREGROUND); + StateManager::getInstance().onLogEvent(*uidProcessEvent); + + ASSERT_EQ(9UL, valueProducer->mCurrentSlicedBucket.size()); + ASSERT_EQ(3UL, valueProducer->mCurrentBaseInfo.size()); + // Base for dimension {uid 2, tag 8}. + it = valueProducer->mCurrentSlicedBucket.begin(); + itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); + ASSERT_EQ(1, itBase->second.currentState.getValues().size()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_FOREGROUND, + itBase->second.currentState.getValues()[0].mValue.int_value); + // Value for key {{uid 2, uid 8}, FOREGROUND}. + ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(2, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + EXPECT_EQ(8, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_FOREGROUND, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, true, 0, bucket2StartTimeNs + 30 * NS_PER_SEC); + + // Value for key {{uid 2, uid 8}, BACKGROUND}. + it++; + ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(2, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + EXPECT_EQ(8, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, false, 30 * NS_PER_SEC, + bucket2StartTimeNs + 30 * NS_PER_SEC); + + // Value for key {{uid 2, uid 8}, kStateUnknown}. + it++; + ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(2, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + EXPECT_EQ(8, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, false, 0, 0); + + // Base for dimension {uid 1, tag 16}. + it++; + itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); + ASSERT_EQ(1, itBase->second.currentState.getValues().size()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND, + itBase->second.currentState.getValues()[0].mValue.int_value); + // Value for key {{uid 1, uid 16}, BACKGROUND}. + ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + EXPECT_EQ(16, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, true, 0, bucket2StartTimeNs); + + // Base for dimension key {uid 1, tag 14}. + it++; + itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); + ASSERT_EQ(1, itBase->second.currentState.getValues().size()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND, + itBase->second.currentState.getValues()[0].mValue.int_value); + // Value for key {{uid 1, tag 14}, BACKGROUND}. + ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + EXPECT_EQ(14, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, true, 0, bucket2StartTimeNs); + + // Value for key {{uid 1, uid 16}, FOREGROUND}. + it++; + ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + EXPECT_EQ(16, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_FOREGROUND, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, false, 0, bucketStartTimeNs + 20 * NS_PER_SEC); + + // Value for key {{uid 1, tag 16}, kStateUnknown}. + it++; + ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + EXPECT_EQ(16, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, false, 0, 0); + + // Value for key {{uid 1, tag 14}, FOREGROUND}. + it++; + ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + EXPECT_EQ(14, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_FOREGROUND, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, false, 0, bucketStartTimeNs + 20 * NS_PER_SEC); + + // Value for key {{uid 1, tag 14}, kStateUnknown}. + it++; + ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + EXPECT_EQ(14, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, false, 0, 0); + + // Start dump report and check output. + ProtoOutputStream output; + std::set strSet; + valueProducer->onDumpReport(bucket2StartTimeNs + 50 * NS_PER_SEC, + true /* include recent buckets */, true, NO_TIME_CONSTRAINTS, + &strSet, &output); + + StatsLogReport report = outputStreamToProto(&output); + EXPECT_TRUE(report.has_value_metrics()); + ASSERT_EQ(6, report.value_metrics().data_size()); + + // {{uid 1, tag 14}, FOREGROUND}. + auto data = report.value_metrics().data(0); + EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); + EXPECT_EQ(android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_FOREGROUND, + data.slice_by_state(0).value()); + ASSERT_EQ(1, data.bucket_info_size()); + EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).condition_true_nanos()); + + // {{uid 1, tag 16}, BACKGROUND}. + data = report.value_metrics().data(1); + EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); + EXPECT_EQ(android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_BACKGROUND, + data.slice_by_state(0).value()); + ASSERT_EQ(2, data.bucket_info_size()); + EXPECT_EQ(35 * NS_PER_SEC, data.bucket_info(0).condition_true_nanos()); + EXPECT_EQ(50 * NS_PER_SEC, data.bucket_info(1).condition_true_nanos()); + + // {{uid 1, tag 14}, BACKGROUND}. + data = report.value_metrics().data(2); + EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); + EXPECT_EQ(android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_BACKGROUND, + data.slice_by_state(0).value()); + ASSERT_EQ(2, data.bucket_info_size()); + EXPECT_EQ(35 * NS_PER_SEC, data.bucket_info(0).condition_true_nanos()); + EXPECT_EQ(50 * NS_PER_SEC, data.bucket_info(1).condition_true_nanos()); + + // {{uid 1, tag 16}, FOREGROUND}. + data = report.value_metrics().data(3); + EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); + EXPECT_EQ(android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_FOREGROUND, + data.slice_by_state(0).value()); + ASSERT_EQ(1, data.bucket_info_size()); + EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).condition_true_nanos()); + + // {{uid 2, tag 8}, FOREGROUND}. + data = report.value_metrics().data(4); + EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); + EXPECT_EQ(android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_FOREGROUND, + data.slice_by_state(0).value()); + ASSERT_EQ(1, data.bucket_info_size()); + EXPECT_EQ(20 * NS_PER_SEC, data.bucket_info(0).condition_true_nanos()); + + // {{uid 2, tag 8}, BACKGROUND}. + data = report.value_metrics().data(5); + EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); + EXPECT_EQ(android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_BACKGROUND, + data.slice_by_state(0).value()); + ASSERT_EQ(2, data.bucket_info_size()); + EXPECT_EQ(30 * NS_PER_SEC, data.bucket_info(0).condition_true_nanos()); + EXPECT_EQ(30 * NS_PER_SEC, data.bucket_info(1).condition_true_nanos()); +} + +TEST(ValueMetricProducerTest, TestSlicedStateWithCondition) { + // Set up ValueMetricProducer. + ValueMetric metric = ValueMetricProducerTestHelper::createMetricWithConditionAndState( + "BATTERY_SAVER_MODE_STATE"); + sp pullerManager = new StrictMock(); + EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _)) + // Condition changed to true. + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector>* data) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 20 * NS_PER_SEC); + data->clear(); + data->push_back( + CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 20 * NS_PER_SEC, 3)); + return true; + })) + // Battery saver mode state changed to OFF. + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector>* data) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 30 * NS_PER_SEC); + data->clear(); + data->push_back( + CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 30 * NS_PER_SEC, 5)); + return true; + })) + // Condition changed to false. + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector>* data) { + EXPECT_EQ(eventTimeNs, bucket2StartTimeNs + 10 * NS_PER_SEC); + data->clear(); + data->push_back(CreateRepeatedValueLogEvent( + tagId, bucket2StartTimeNs + 10 * NS_PER_SEC, 15)); + return true; + })); + + StateManager::getInstance().clear(); + sp valueProducer = + ValueMetricProducerTestHelper::createValueProducerWithConditionAndState( + pullerManager, metric, {util::BATTERY_SAVER_MODE_STATE_CHANGED}, {}, + ConditionState::kFalse); + EXPECT_EQ(1, valueProducer->mSlicedStateAtoms.size()); + + // Set up StateManager and check that StateTrackers are initialized. + StateManager::getInstance().registerListener(util::BATTERY_SAVER_MODE_STATE_CHANGED, + valueProducer); + EXPECT_EQ(1, StateManager::getInstance().getStateTrackersCount()); + EXPECT_EQ(1, StateManager::getInstance().getListenersCount( + util::BATTERY_SAVER_MODE_STATE_CHANGED)); + + // Bucket status after battery saver mode ON event. + // Condition is false so we do nothing. + unique_ptr batterySaverOnEvent = + CreateBatterySaverOnEvent(/*timestamp=*/bucketStartTimeNs + 10 * NS_PER_SEC); + StateManager::getInstance().onLogEvent(*batterySaverOnEvent); + EXPECT_EQ(0UL, valueProducer->mCurrentSlicedBucket.size()); + EXPECT_EQ(0UL, valueProducer->mCurrentBaseInfo.size()); + + // Bucket status after condition change to true. + valueProducer->onConditionChanged(true, bucketStartTimeNs + 20 * NS_PER_SEC); + // Base for dimension key {} + ASSERT_EQ(1UL, valueProducer->mCurrentBaseInfo.size()); + std::unordered_map::iterator + itBase = valueProducer->mCurrentBaseInfo.find(DEFAULT_DIMENSION_KEY); + EXPECT_TRUE(itBase->second.baseInfos[0].hasBase); + EXPECT_EQ(3, itBase->second.baseInfos[0].base.long_value); + EXPECT_TRUE(itBase->second.hasCurrentState); + ASSERT_EQ(1, itBase->second.currentState.getValues().size()); + EXPECT_EQ(BatterySaverModeStateChanged::ON, + itBase->second.currentState.getValues()[0].mValue.int_value); + // Value for key {{}, ON} + ASSERT_EQ(2UL, valueProducer->mCurrentSlicedBucket.size()); + std::unordered_map::iterator it = + valueProducer->mCurrentSlicedBucket.begin(); + EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(BatterySaverModeStateChanged::ON, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, true, 0, bucketStartTimeNs + 20 * NS_PER_SEC); + // Value for key {{}, -1} + it++; + EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(-1 /*StateTracker::kUnknown*/, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + EXPECT_FALSE(it->second.intervals[0].hasValue); + assertConditionTimer(it->second.conditionTimer, false, 0, 0); + + // Bucket status after battery saver mode OFF event. + unique_ptr batterySaverOffEvent = + CreateBatterySaverOffEvent(/*timestamp=*/bucketStartTimeNs + 30 * NS_PER_SEC); + StateManager::getInstance().onLogEvent(*batterySaverOffEvent); + // Base for dimension key {} + ASSERT_EQ(1UL, valueProducer->mCurrentBaseInfo.size()); + itBase = valueProducer->mCurrentBaseInfo.find(DEFAULT_DIMENSION_KEY); + EXPECT_TRUE(itBase->second.baseInfos[0].hasBase); + EXPECT_EQ(5, itBase->second.baseInfos[0].base.long_value); + EXPECT_TRUE(itBase->second.hasCurrentState); + ASSERT_EQ(1, itBase->second.currentState.getValues().size()); + EXPECT_EQ(BatterySaverModeStateChanged::OFF, + itBase->second.currentState.getValues()[0].mValue.int_value); + // Value for key {{}, OFF} + ASSERT_EQ(3UL, valueProducer->mCurrentSlicedBucket.size()); + it = valueProducer->mCurrentSlicedBucket.begin(); + EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(BatterySaverModeStateChanged::OFF, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, true, 0, bucketStartTimeNs + 30 * NS_PER_SEC); + // Value for key {{}, ON} + it++; + EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(BatterySaverModeStateChanged::ON, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + EXPECT_TRUE(it->second.intervals[0].hasValue); + EXPECT_EQ(2, it->second.intervals[0].value.long_value); + assertConditionTimer(it->second.conditionTimer, false, 10 * NS_PER_SEC, + bucketStartTimeNs + 30 * NS_PER_SEC); + // Value for key {{}, -1} + it++; + assertConditionTimer(it->second.conditionTimer, false, 0, 0); + + // Pull at end of first bucket. + vector> allData; + allData.clear(); + allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs, 11)); + valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs); + + EXPECT_EQ(2UL, valueProducer->mPastBuckets.size()); + EXPECT_EQ(3UL, valueProducer->mCurrentSlicedBucket.size()); + // Base for dimension key {} + ASSERT_EQ(1UL, valueProducer->mCurrentBaseInfo.size()); + itBase = valueProducer->mCurrentBaseInfo.find(DEFAULT_DIMENSION_KEY); + EXPECT_TRUE(itBase->second.baseInfos[0].hasBase); + EXPECT_EQ(11, itBase->second.baseInfos[0].base.long_value); + EXPECT_TRUE(itBase->second.hasCurrentState); + ASSERT_EQ(1, itBase->second.currentState.getValues().size()); + EXPECT_EQ(BatterySaverModeStateChanged::OFF, + itBase->second.currentState.getValues()[0].mValue.int_value); + // Value for key {{}, OFF} + it = valueProducer->mCurrentSlicedBucket.begin(); + assertConditionTimer(it->second.conditionTimer, true, 0, bucket2StartTimeNs); + // Value for key {{}, ON} + it++; + assertConditionTimer(it->second.conditionTimer, false, 0, bucketStartTimeNs + 30 * NS_PER_SEC); + // Value for key {{}, -1} + it++; + assertConditionTimer(it->second.conditionTimer, false, 0, 0); + + // Bucket 2 status after condition change to false. + valueProducer->onConditionChanged(false, bucket2StartTimeNs + 10 * NS_PER_SEC); + // Base for dimension key {} ASSERT_EQ(1UL, valueProducer->mCurrentBaseInfo.size()); itBase = valueProducer->mCurrentBaseInfo.find(DEFAULT_DIMENSION_KEY); EXPECT_FALSE(itBase->second.baseInfos[0].hasBase); @@ -4964,6 +6827,19 @@ TEST(ValueMetricProducerTest, TestSlicedStateWithCondition) { it->first.getStateValuesKey().getValues()[0].mValue.int_value); EXPECT_TRUE(it->second.intervals[0].hasValue); EXPECT_EQ(4, it->second.intervals[0].value.long_value); + assertConditionTimer(it->second.conditionTimer, false, 10 * NS_PER_SEC, + bucket2StartTimeNs + 10 * NS_PER_SEC); + // Value for key {{}, ON} + it++; + EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(BatterySaverModeStateChanged::ON, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + EXPECT_FALSE(it->second.intervals[0].hasValue); + assertConditionTimer(it->second.conditionTimer, false, 0, bucketStartTimeNs + 30 * NS_PER_SEC); + // Value for key {{}, -1} + it++; + assertConditionTimer(it->second.conditionTimer, false, 0, 0); // Start dump report and check output. ProtoOutputStream output; @@ -4982,6 +6858,7 @@ TEST(ValueMetricProducerTest, TestSlicedStateWithCondition) { EXPECT_EQ(BatterySaverModeStateChanged::ON, data.slice_by_state(0).value()); ASSERT_EQ(1, data.bucket_info_size()); EXPECT_EQ(2, data.bucket_info(0).values(0).value_long()); + EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).condition_true_nanos()); data = report.value_metrics().data(1); EXPECT_EQ(util::BATTERY_SAVER_MODE_STATE_CHANGED, data.slice_by_state(0).atom_id()); @@ -4990,6 +6867,8 @@ TEST(ValueMetricProducerTest, TestSlicedStateWithCondition) { ASSERT_EQ(2, data.bucket_info_size()); EXPECT_EQ(6, data.bucket_info(0).values(0).value_long()); EXPECT_EQ(4, data.bucket_info(1).values(0).value_long()); + EXPECT_EQ(30 * NS_PER_SEC, data.bucket_info(0).condition_true_nanos()); + EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(1).condition_true_nanos()); } /* -- cgit v1.2.3 From 93b8f7523c384af1ae3b8357330ef9185d96f3ce Mon Sep 17 00:00:00 2001 From: tsaichristine Date: Mon, 7 Dec 2020 14:23:14 -0800 Subject: Add StatsdStats logging for late events in duration metric Add logging to count the number of late events, the sum of all late event durations, and the max late event duration per metric. Test: atest statsd_test Bug: 175026771 Change-Id: I8312ddeb83d513a8d5f07dd51cd7990cdc88d38b Merged-In: Ibb065d64be3775c699583215f7f376a1a5492ac1 --- bin/src/guardrail/StatsdStats.cpp | 19 ++++++-- bin/src/guardrail/StatsdStats.h | 8 ++++ bin/src/metrics/DurationMetricProducer.cpp | 30 ++++++++++++ bin/src/stats_log.proto | 3 ++ bin/src/stats_log_util.cpp | 74 ++++++++++++++++++------------ bin/src/stats_log_util.h | 4 ++ bin/tests/guardrail/StatsdStats_test.cpp | 9 ++++ 7 files changed, 113 insertions(+), 34 deletions(-) diff --git a/bin/src/guardrail/StatsdStats.cpp b/bin/src/guardrail/StatsdStats.cpp index 6e89038f..f4e01ced 100644 --- a/bin/src/guardrail/StatsdStats.cpp +++ b/bin/src/guardrail/StatsdStats.cpp @@ -521,6 +521,15 @@ void StatsdStats::noteLateLogEventSkipped(int64_t metricId) { getAtomMetricStats(metricId).lateLogEventSkipped++; } +void StatsdStats::noteLateLogEvent(int64_t metricId, int64_t extraDurationNs) { + lock_guard lock(mLock); + AtomMetricStats& metricStats = getAtomMetricStats(metricId); + metricStats.lateLogEvent++; + metricStats.sumLateLogEventExtraDurationNs += extraDurationNs; + metricStats.maxLateLogEventExtraDurationNs = + std::max(metricStats.maxLateLogEventExtraDurationNs, extraDurationNs); +} + void StatsdStats::noteSkippedForwardBuckets(int64_t metricId) { lock_guard lock(mLock); getAtomMetricStats(metricId).skippedForwardBuckets++; @@ -558,11 +567,11 @@ void StatsdStats::noteBucketCount(int64_t metricId) { void StatsdStats::noteBucketBoundaryDelayNs(int64_t metricId, int64_t timeDelayNs) { lock_guard lock(mLock); - AtomMetricStats& pullStats = getAtomMetricStats(metricId); - pullStats.maxBucketBoundaryDelayNs = - std::max(pullStats.maxBucketBoundaryDelayNs, timeDelayNs); - pullStats.minBucketBoundaryDelayNs = - std::min(pullStats.minBucketBoundaryDelayNs, timeDelayNs); + AtomMetricStats& metricStats = getAtomMetricStats(metricId); + metricStats.maxBucketBoundaryDelayNs = + std::max(metricStats.maxBucketBoundaryDelayNs, timeDelayNs); + metricStats.minBucketBoundaryDelayNs = + std::min(metricStats.minBucketBoundaryDelayNs, timeDelayNs); } void StatsdStats::noteAtomError(int atomTag, bool pull) { diff --git a/bin/src/guardrail/StatsdStats.h b/bin/src/guardrail/StatsdStats.h index 8721905d..330bd8d9 100644 --- a/bin/src/guardrail/StatsdStats.h +++ b/bin/src/guardrail/StatsdStats.h @@ -418,6 +418,11 @@ public: */ void noteLateLogEventSkipped(int64_t metricId); + /** + * A log event was too late, arrived in the wrong bucket. + */ + void noteLateLogEvent(int64_t metricId, int64_t extraDurationNs); + /** * Buckets were skipped as time elapsed without any data for them */ @@ -542,6 +547,9 @@ public: int64_t maxBucketBoundaryDelayNs = 0; long bucketUnknownCondition = 0; long bucketCount = 0; + long lateLogEvent = 0; + int64_t sumLateLogEventExtraDurationNs = 0; + int64_t maxLateLogEventExtraDurationNs = 0; } AtomMetricStats; private: diff --git a/bin/src/metrics/DurationMetricProducer.cpp b/bin/src/metrics/DurationMetricProducer.cpp index 001ad361..fe92b08c 100644 --- a/bin/src/metrics/DurationMetricProducer.cpp +++ b/bin/src/metrics/DurationMetricProducer.cpp @@ -298,6 +298,12 @@ void DurationMetricProducer::onStateChanged(const int64_t eventTimeNs, const int flushIfNeededLocked(eventTimeNs); + // Log late event and extra duration. + if (eventTimeNs < mCurrentBucketStartTimeNs) { + StatsdStats::getInstance().noteLateLogEvent(mMetricId, + mCurrentBucketStartTimeNs - eventTimeNs); + } + // Each duration tracker is mapped to a different whatKey (a set of values from the // dimensionsInWhat fields). We notify all trackers iff the primaryKey field values from the // state change event are a subset of the tracker's whatKey field values. @@ -416,6 +422,12 @@ void DurationMetricProducer::onSlicedConditionMayChangeLocked(bool overallCondit return; } + // Log late event and extra duration. + if (eventTime < mCurrentBucketStartTimeNs) { + StatsdStats::getInstance().noteLateLogEvent(mMetricId, + mCurrentBucketStartTimeNs - eventTime); + } + flushIfNeededLocked(eventTime); if (!mConditionSliced) { @@ -433,6 +445,12 @@ void DurationMetricProducer::onActiveStateChangedLocked(const int64_t& eventTime return; } + // Log late event and extra duration. + if (eventTimeNs < mCurrentBucketStartTimeNs) { + StatsdStats::getInstance().noteLateLogEvent(mMetricId, + mCurrentBucketStartTimeNs - eventTimeNs); + } + if (mIsActive) { flushIfNeededLocked(eventTimeNs); } @@ -459,6 +477,12 @@ void DurationMetricProducer::onConditionChangedLocked(const bool conditionMet, return; } + // Log late event and extra duration. + if (eventTime < mCurrentBucketStartTimeNs) { + StatsdStats::getInstance().noteLateLogEvent(mMetricId, + mCurrentBucketStartTimeNs - eventTime); + } + flushIfNeededLocked(eventTime); for (auto& whatIt : mCurrentSlicedDurationTrackerMap) { whatIt.second->onConditionChanged(conditionMet, eventTime); @@ -675,6 +699,12 @@ void DurationMetricProducer::handleMatchedLogEventValuesLocked(const size_t matc return; } + // Log late event and extra duration. + if (eventTimeNs < mCurrentBucketStartTimeNs) { + StatsdStats::getInstance().noteLateLogEvent(mMetricId, + mCurrentBucketStartTimeNs - eventTimeNs); + } + if (mIsActive) { flushIfNeededLocked(eventTimeNs); } diff --git a/bin/src/stats_log.proto b/bin/src/stats_log.proto index bb079632..f91c4c08 100644 --- a/bin/src/stats_log.proto +++ b/bin/src/stats_log.proto @@ -488,6 +488,9 @@ message StatsdStatsReport { optional int64 max_bucket_boundary_delay_ns = 10; optional int64 bucket_unknown_condition = 11; optional int64 bucket_count = 12; + optional int64 late_log_event = 13; + optional int64 sum_late_log_event_extra_duration_ns = 14; + optional int64 max_late_log_event_extra_duration_ns = 15; } repeated AtomMetricStats atom_metric_stats = 17; diff --git a/bin/src/stats_log_util.cpp b/bin/src/stats_log_util.cpp index 423bae8b..d6e04f7b 100644 --- a/bin/src/stats_log_util.cpp +++ b/bin/src/stats_log_util.cpp @@ -99,6 +99,9 @@ const int FIELD_ID_MIN_BUCKET_BOUNDARY_DELAY_NS = 9; const int FIELD_ID_MAX_BUCKET_BOUNDARY_DELAY_NS = 10; const int FIELD_ID_BUCKET_UNKNOWN_CONDITION = 11; const int FIELD_ID_BUCKET_COUNT = 12; +const int FIELD_ID_LATE_LOG_EVENT = 13; +const int FIELD_ID_SUM_LATE_LOG_EVENT_EXTRA_DURATION_NS = 14; +const int FIELD_ID_MAX_LATE_LOG_EVENT_EXTRA_DURATION_NS = 15; namespace { @@ -462,6 +465,13 @@ int64_t TimeUnitToBucketSizeInMillis(TimeUnit unit) { } } +void writeNonZeroStatToStream(const uint64_t fieldId, const int value, + util::ProtoOutputStream* protoOutput) { + if (value != 0) { + protoOutput->write(fieldId, value); + } +} + void writePullerStatsToStream(const std::pair& pair, util::ProtoOutputStream* protoOutput) { uint64_t token = protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_ID_PULLED_ATOM_STATS | @@ -485,14 +495,12 @@ void writePullerStatsToStream(const std::pair (long long)pair.second.pullTimeout); protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_PULL_EXCEED_MAX_DELAY, (long long)pair.second.pullExceedMaxDelay); - protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_PULL_FAILED, - (long long)pair.second.pullFailed); - protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_EMPTY_DATA, - (long long)pair.second.emptyData); + protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_PULL_FAILED, (long long)pair.second.pullFailed); + protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_EMPTY_DATA, (long long)pair.second.emptyData); protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_PULL_REGISTERED_COUNT, - (long long) pair.second.registeredCount); + (long long)pair.second.registeredCount); protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_PULL_UNREGISTERED_COUNT, - (long long) pair.second.unregisteredCount); + (long long)pair.second.unregisteredCount); protoOutput->write(FIELD_TYPE_INT32 | FIELD_ID_ATOM_ERROR_COUNT, pair.second.atomErrorCount); protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_BINDER_CALL_FAIL_COUNT, (long long)pair.second.binderCallFailCount); @@ -517,29 +525,37 @@ void writeAtomMetricStatsToStream(const std::pairstart(FIELD_TYPE_MESSAGE | FIELD_ID_ATOM_METRIC_STATS | FIELD_COUNT_REPEATED); - protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_METRIC_ID, (long long)pair.first); - protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_HARD_DIMENSION_LIMIT_REACHED, - (long long)pair.second.hardDimensionLimitReached); - protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_LATE_LOG_EVENT_SKIPPED, - (long long)pair.second.lateLogEventSkipped); - protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_SKIPPED_FORWARD_BUCKETS, - (long long)pair.second.skippedForwardBuckets); - protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_BAD_VALUE_TYPE, - (long long)pair.second.badValueType); - protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_CONDITION_CHANGE_IN_NEXT_BUCKET, - (long long)pair.second.conditionChangeInNextBucket); - protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_INVALIDATED_BUCKET, - (long long)pair.second.invalidatedBucket); - protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_BUCKET_DROPPED, - (long long)pair.second.bucketDropped); - protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_MIN_BUCKET_BOUNDARY_DELAY_NS, - (long long)pair.second.minBucketBoundaryDelayNs); - protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_MAX_BUCKET_BOUNDARY_DELAY_NS, - (long long)pair.second.maxBucketBoundaryDelayNs); - protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_BUCKET_UNKNOWN_CONDITION, - (long long)pair.second.bucketUnknownCondition); - protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_BUCKET_COUNT, - (long long)pair.second.bucketCount); + + writeNonZeroStatToStream(FIELD_TYPE_INT64 | FIELD_ID_METRIC_ID, (long long)pair.first, + protoOutput); + writeNonZeroStatToStream(FIELD_TYPE_INT64 | FIELD_ID_HARD_DIMENSION_LIMIT_REACHED, + (long long)pair.second.hardDimensionLimitReached, protoOutput); + writeNonZeroStatToStream(FIELD_TYPE_INT64 | FIELD_ID_LATE_LOG_EVENT_SKIPPED, + (long long)pair.second.lateLogEventSkipped, protoOutput); + writeNonZeroStatToStream(FIELD_TYPE_INT64 | FIELD_ID_SKIPPED_FORWARD_BUCKETS, + (long long)pair.second.skippedForwardBuckets, protoOutput); + writeNonZeroStatToStream(FIELD_TYPE_INT64 | FIELD_ID_BAD_VALUE_TYPE, + (long long)pair.second.badValueType, protoOutput); + writeNonZeroStatToStream(FIELD_TYPE_INT64 | FIELD_ID_CONDITION_CHANGE_IN_NEXT_BUCKET, + (long long)pair.second.conditionChangeInNextBucket, protoOutput); + writeNonZeroStatToStream(FIELD_TYPE_INT64 | FIELD_ID_INVALIDATED_BUCKET, + (long long)pair.second.invalidatedBucket, protoOutput); + writeNonZeroStatToStream(FIELD_TYPE_INT64 | FIELD_ID_BUCKET_DROPPED, + (long long)pair.second.bucketDropped, protoOutput); + writeNonZeroStatToStream(FIELD_TYPE_INT64 | FIELD_ID_MIN_BUCKET_BOUNDARY_DELAY_NS, + (long long)pair.second.minBucketBoundaryDelayNs, protoOutput); + writeNonZeroStatToStream(FIELD_TYPE_INT64 | FIELD_ID_MAX_BUCKET_BOUNDARY_DELAY_NS, + (long long)pair.second.maxBucketBoundaryDelayNs, protoOutput); + writeNonZeroStatToStream(FIELD_TYPE_INT64 | FIELD_ID_BUCKET_UNKNOWN_CONDITION, + (long long)pair.second.bucketUnknownCondition, protoOutput); + writeNonZeroStatToStream(FIELD_TYPE_INT64 | FIELD_ID_BUCKET_COUNT, + (long long)pair.second.bucketCount, protoOutput); + writeNonZeroStatToStream(FIELD_TYPE_INT64 | FIELD_ID_LATE_LOG_EVENT, + (long long)pair.second.lateLogEvent, protoOutput); + writeNonZeroStatToStream(FIELD_TYPE_INT64 | FIELD_ID_SUM_LATE_LOG_EVENT_EXTRA_DURATION_NS, + (long long)pair.second.sumLateLogEventExtraDurationNs, protoOutput); + writeNonZeroStatToStream(FIELD_TYPE_INT64 | FIELD_ID_MAX_LATE_LOG_EVENT_EXTRA_DURATION_NS, + (long long)pair.second.maxLateLogEventExtraDurationNs, protoOutput); protoOutput->end(token); } diff --git a/bin/src/stats_log_util.h b/bin/src/stats_log_util.h index 7fdde934..dcfe0c7d 100644 --- a/bin/src/stats_log_util.h +++ b/bin/src/stats_log_util.h @@ -77,6 +77,10 @@ int64_t NanoToMillis(const int64_t nano); int64_t MillisToNano(const int64_t millis); +// Helper function to write a stats field to ProtoOutputStream if it's a non-zero value. +void writeNonZeroStatToStream(const uint64_t fieldId, const int value, + ProtoOutputStream* protoOutput); + // Helper function to write PulledAtomStats to ProtoOutputStream void writePullerStatsToStream(const std::pair& pair, ProtoOutputStream* protoOutput); diff --git a/bin/tests/guardrail/StatsdStats_test.cpp b/bin/tests/guardrail/StatsdStats_test.cpp index 428c46f8..16ae9b5b 100644 --- a/bin/tests/guardrail/StatsdStats_test.cpp +++ b/bin/tests/guardrail/StatsdStats_test.cpp @@ -345,6 +345,9 @@ TEST(StatsdStatsTest, TestAtomMetricsStats) { // old event, we get it from the stats buffer. should be ignored. stats.noteBucketDropped(1000L); + stats.noteLateLogEvent(1000L, 10L); + stats.noteLateLogEvent(1000L, 50L); + stats.noteBucketBoundaryDelayNs(1000L, -1L); stats.noteBucketBoundaryDelayNs(1000L, -10L); stats.noteBucketBoundaryDelayNs(1000L, 2L); @@ -364,12 +367,18 @@ TEST(StatsdStatsTest, TestAtomMetricsStats) { EXPECT_EQ(1L, atomStats.bucket_dropped()); EXPECT_EQ(-10L, atomStats.min_bucket_boundary_delay_ns()); EXPECT_EQ(2L, atomStats.max_bucket_boundary_delay_ns()); + EXPECT_EQ(2L, atomStats.late_log_event()); + EXPECT_EQ(60L, atomStats.sum_late_log_event_extra_duration_ns()); + EXPECT_EQ(50L, atomStats.max_late_log_event_extra_duration_ns()); auto atomStats2 = report.atom_metric_stats(1); EXPECT_EQ(1001L, atomStats2.metric_id()); EXPECT_EQ(0L, atomStats2.bucket_dropped()); EXPECT_EQ(0L, atomStats2.min_bucket_boundary_delay_ns()); EXPECT_EQ(1L, atomStats2.max_bucket_boundary_delay_ns()); + EXPECT_EQ(0L, atomStats2.late_log_event()); + EXPECT_EQ(0L, atomStats2.sum_late_log_event_extra_duration_ns()); + EXPECT_EQ(0L, atomStats2.max_late_log_event_extra_duration_ns()); } TEST(StatsdStatsTest, TestAnomalyMonitor) { -- cgit v1.2.3 From 4172cba95739445296efa2c94a453e1addda722f Mon Sep 17 00:00:00 2001 From: Jeffrey Huang Date: Thu, 3 Dec 2020 13:07:26 -0800 Subject: Update doc for new atoms.proto location Bug: 174514667 Test: m -j Change-Id: I1d21068423d6ebe7ef5e4ffc80c01952a04f2e1d Merged-In: Ibb065d64be3775c699583215f7f376a1a5492ac1 --- framework/java/android/util/StatsLog.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/framework/java/android/util/StatsLog.java b/framework/java/android/util/StatsLog.java index 0a9f4eba..112cd1be 100644 --- a/framework/java/android/util/StatsLog.java +++ b/framework/java/android/util/StatsLog.java @@ -107,7 +107,7 @@ public final class StatsLog { * 0x04: FLAG_REQUIRE_LOW_LATENCY_MONITOR * @param state current install state. Defined as State enums in * BinaryPushStateChanged atom in - * frameworks/base/cmds/statsd/src/atoms.proto + * frameworks/proto_logging/stats/atoms.proto * @param experimentIds experiment ids. * @return True if the log request was sent to statsd. */ -- cgit v1.2.3 From d5955baf8734d282d769409b68d64541189af7bd Mon Sep 17 00:00:00 2001 From: tsaichristine Date: Tue, 22 Dec 2020 00:10:00 -0800 Subject: Fix AtomMetricStats int64 field passed to helper function Test: atest statsd_test Bug: 175026771 Change-Id: Ie0a256ca64a06bbdc98b6fc0c6cdadba302d6f17 Merged-In: Ibb065d64be3775c699583215f7f376a1a5492ac1 --- bin/src/stats_log_util.cpp | 2 +- bin/src/stats_log_util.h | 2 +- bin/tests/guardrail/StatsdStats_test.cpp | 18 +++++++++--------- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/bin/src/stats_log_util.cpp b/bin/src/stats_log_util.cpp index d6e04f7b..a7e4d703 100644 --- a/bin/src/stats_log_util.cpp +++ b/bin/src/stats_log_util.cpp @@ -465,7 +465,7 @@ int64_t TimeUnitToBucketSizeInMillis(TimeUnit unit) { } } -void writeNonZeroStatToStream(const uint64_t fieldId, const int value, +void writeNonZeroStatToStream(const uint64_t fieldId, const int64_t value, util::ProtoOutputStream* protoOutput) { if (value != 0) { protoOutput->write(fieldId, value); diff --git a/bin/src/stats_log_util.h b/bin/src/stats_log_util.h index dcfe0c7d..1ba4c4ae 100644 --- a/bin/src/stats_log_util.h +++ b/bin/src/stats_log_util.h @@ -78,7 +78,7 @@ int64_t NanoToMillis(const int64_t nano); int64_t MillisToNano(const int64_t millis); // Helper function to write a stats field to ProtoOutputStream if it's a non-zero value. -void writeNonZeroStatToStream(const uint64_t fieldId, const int value, +void writeNonZeroStatToStream(const uint64_t fieldId, const int64_t value, ProtoOutputStream* protoOutput); // Helper function to write PulledAtomStats to ProtoOutputStream diff --git a/bin/tests/guardrail/StatsdStats_test.cpp b/bin/tests/guardrail/StatsdStats_test.cpp index 16ae9b5b..5a824c53 100644 --- a/bin/tests/guardrail/StatsdStats_test.cpp +++ b/bin/tests/guardrail/StatsdStats_test.cpp @@ -343,16 +343,16 @@ TEST(StatsdStatsTest, TestAtomMetricsStats) { StatsdStats stats; time_t now = time(nullptr); // old event, we get it from the stats buffer. should be ignored. - stats.noteBucketDropped(1000L); + stats.noteBucketDropped(10000000000LL); - stats.noteLateLogEvent(1000L, 10L); - stats.noteLateLogEvent(1000L, 50L); + stats.noteLateLogEvent(10000000000LL, 10L); + stats.noteLateLogEvent(10000000000LL, 50L); - stats.noteBucketBoundaryDelayNs(1000L, -1L); - stats.noteBucketBoundaryDelayNs(1000L, -10L); - stats.noteBucketBoundaryDelayNs(1000L, 2L); + stats.noteBucketBoundaryDelayNs(10000000000LL, -1L); + stats.noteBucketBoundaryDelayNs(10000000000LL, -10L); + stats.noteBucketBoundaryDelayNs(10000000000LL, 2L); - stats.noteBucketBoundaryDelayNs(1001L, 1L); + stats.noteBucketBoundaryDelayNs(10000000001LL, 1L); vector output; stats.dumpStats(&output, false); @@ -363,7 +363,7 @@ TEST(StatsdStatsTest, TestAtomMetricsStats) { ASSERT_EQ(2, report.atom_metric_stats().size()); auto atomStats = report.atom_metric_stats(0); - EXPECT_EQ(1000L, atomStats.metric_id()); + EXPECT_EQ(10000000000LL, atomStats.metric_id()); EXPECT_EQ(1L, atomStats.bucket_dropped()); EXPECT_EQ(-10L, atomStats.min_bucket_boundary_delay_ns()); EXPECT_EQ(2L, atomStats.max_bucket_boundary_delay_ns()); @@ -372,7 +372,7 @@ TEST(StatsdStatsTest, TestAtomMetricsStats) { EXPECT_EQ(50L, atomStats.max_late_log_event_extra_duration_ns()); auto atomStats2 = report.atom_metric_stats(1); - EXPECT_EQ(1001L, atomStats2.metric_id()); + EXPECT_EQ(10000000001LL, atomStats2.metric_id()); EXPECT_EQ(0L, atomStats2.bucket_dropped()); EXPECT_EQ(0L, atomStats2.min_bucket_boundary_delay_ns()); EXPECT_EQ(1L, atomStats2.max_bucket_boundary_delay_ns()); -- cgit v1.2.3 From 4fdff28a49e31c6bbe9ddccfb66cfc8845d80757 Mon Sep 17 00:00:00 2001 From: Lalit Maganti Date: Tue, 26 Jan 2021 18:07:34 +0000 Subject: StatsD: add AID_NOBODY mapping to uid map This is required for traced (which runs as AID nobody) to be able to correctly log to statsd. Test: TH Change-Id: I8dd953e8c17a7b2de7228d634585a76503d7c961 Merged-In: Ibb065d64be3775c699583215f7f376a1a5492ac1 --- bin/src/packages/UidMap.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/bin/src/packages/UidMap.cpp b/bin/src/packages/UidMap.cpp index acf40c88..32be1c6b 100644 --- a/bin/src/packages/UidMap.cpp +++ b/bin/src/packages/UidMap.cpp @@ -556,7 +556,8 @@ const std::map UidMap::sAidToUidMapping = {{"AID_ROOT", 0}, {"AID_CONTEXT_HUB", 1080}, {"AID_SHELL", 2000}, {"AID_CACHE", 2001}, - {"AID_DIAG", 2002}}; + {"AID_DIAG", 2002}, + {"AID_NOBODY", 9999}}; } // namespace statsd } // namespace os -- cgit v1.2.3 From 80588907bd6abf5c2a4dbd5bff5f00d41f101019 Mon Sep 17 00:00:00 2001 From: Tej Singh Date: Mon, 8 Feb 2021 18:39:47 -0800 Subject: Revert "Hardcode partial config updates to be on" This reverts commit 9cde38061d121b3c6cf35a9ec3bf223fc8d0aadd. Revert so we can continue flagged experiments. Test: atest statsd_test Bug: 179730702 Change-Id: I751e2c7c8bbbd819f216821cbf4f0ec315c4563b Merged-In: Ibb065d64be3775c699583215f7f376a1a5492ac1 --- bin/src/StatsLogProcessor.cpp | 3 ++- bin/src/StatsLogProcessor.h | 2 +- bin/src/config/ConfigListener.h | 2 +- bin/tests/ConfigManager_test.cpp | 14 ++++++------ bin/tests/e2e/ConfigUpdate_e2e_ab_test.cpp | 34 +++++++++++++++++++++++------- bin/tests/e2e/ConfigUpdate_e2e_test.cpp | 17 +++++++++++++++ 6 files changed, 54 insertions(+), 18 deletions(-) diff --git a/bin/src/StatsLogProcessor.cpp b/bin/src/StatsLogProcessor.cpp index f4ed1b8d..79842bfc 100644 --- a/bin/src/StatsLogProcessor.cpp +++ b/bin/src/StatsLogProcessor.cpp @@ -521,9 +521,10 @@ void StatsLogProcessor::GetActiveConfigsLocked(const int uid, vector& o } void StatsLogProcessor::OnConfigUpdated(const int64_t timestampNs, const ConfigKey& key, - const StatsdConfig& config, bool modularUpdate) { + const StatsdConfig& config) { std::lock_guard lock(mMetricsMutex); WriteDataToDiskLocked(key, timestampNs, CONFIG_UPDATED, NO_TIME_CONSTRAINTS); + bool modularUpdate = getFlagBool(PARTIAL_CONFIG_UPDATE_FLAG, "false"); OnConfigUpdatedLocked(timestampNs, key, config, modularUpdate); } diff --git a/bin/src/StatsLogProcessor.h b/bin/src/StatsLogProcessor.h index fd6b746c..fe20699c 100644 --- a/bin/src/StatsLogProcessor.h +++ b/bin/src/StatsLogProcessor.h @@ -48,7 +48,7 @@ public: void OnLogEvent(LogEvent* event); void OnConfigUpdated(const int64_t timestampNs, const ConfigKey& key, - const StatsdConfig& config, bool modularUpdate = true); + const StatsdConfig& config); void OnConfigRemoved(const ConfigKey& key); size_t GetMetricsSize(const ConfigKey& key) const; diff --git a/bin/src/config/ConfigListener.h b/bin/src/config/ConfigListener.h index 4f71a4e1..dcd5e52f 100644 --- a/bin/src/config/ConfigListener.h +++ b/bin/src/config/ConfigListener.h @@ -39,7 +39,7 @@ public: * A configuration was added or updated. */ virtual void OnConfigUpdated(const int64_t timestampNs, const ConfigKey& key, - const StatsdConfig& config, bool modularUpdate = true) = 0; + const StatsdConfig& config) = 0; /** * A configuration was removed. diff --git a/bin/tests/ConfigManager_test.cpp b/bin/tests/ConfigManager_test.cpp index 7b8f74d0..24c8f2b1 100644 --- a/bin/tests/ConfigManager_test.cpp +++ b/bin/tests/ConfigManager_test.cpp @@ -44,8 +44,8 @@ static ostream& operator<<(ostream& os, const StatsdConfig& config) { */ class MockListener : public ConfigListener { public: - MOCK_METHOD4(OnConfigUpdated, void(const int64_t timestampNs, const ConfigKey& key, - const StatsdConfig& config, bool modularUpdate)); + MOCK_METHOD3(OnConfigUpdated, + void(const int64_t timestampNs, const ConfigKey& key, const StatsdConfig& config)); MOCK_METHOD1(OnConfigRemoved, void(const ConfigKey& key)); }; @@ -90,25 +90,25 @@ TEST(ConfigManagerTest, TestAddUpdateRemove) { // Add another one EXPECT_CALL(*(listener.get()), - OnConfigUpdated(_, ConfigKeyEq(1, StringToId("zzz")), StatsdConfigEq(91), true)) + OnConfigUpdated(_, ConfigKeyEq(1, StringToId("zzz")), StatsdConfigEq(91))) .RetiresOnSaturation(); manager->UpdateConfig(ConfigKey(1, StringToId("zzz")), config91); // Update It EXPECT_CALL(*(listener.get()), - OnConfigUpdated(_, ConfigKeyEq(1, StringToId("zzz")), StatsdConfigEq(92), true)) + OnConfigUpdated(_, ConfigKeyEq(1, StringToId("zzz")), StatsdConfigEq(92))) .RetiresOnSaturation(); manager->UpdateConfig(ConfigKey(1, StringToId("zzz")), config92); // Add one with the same uid but a different name EXPECT_CALL(*(listener.get()), - OnConfigUpdated(_, ConfigKeyEq(1, StringToId("yyy")), StatsdConfigEq(93), true)) + OnConfigUpdated(_, ConfigKeyEq(1, StringToId("yyy")), StatsdConfigEq(93))) .RetiresOnSaturation(); manager->UpdateConfig(ConfigKey(1, StringToId("yyy")), config93); // Add one with the same name but a different uid EXPECT_CALL(*(listener.get()), - OnConfigUpdated(_, ConfigKeyEq(2, StringToId("zzz")), StatsdConfigEq(94), true)) + OnConfigUpdated(_, ConfigKeyEq(2, StringToId("zzz")), StatsdConfigEq(94))) .RetiresOnSaturation(); manager->UpdateConfig(ConfigKey(2, StringToId("zzz")), config94); @@ -143,7 +143,7 @@ TEST(ConfigManagerTest, TestRemoveUid) { StatsdConfig config; - EXPECT_CALL(*(listener.get()), OnConfigUpdated(_, _, _, true)).Times(5); + EXPECT_CALL(*(listener.get()), OnConfigUpdated(_, _, _)).Times(5); EXPECT_CALL(*(listener.get()), OnConfigRemoved(ConfigKeyEq(2, StringToId("xxx")))); EXPECT_CALL(*(listener.get()), OnConfigRemoved(ConfigKeyEq(2, StringToId("yyy")))); EXPECT_CALL(*(listener.get()), OnConfigRemoved(ConfigKeyEq(2, StringToId("zzz")))); diff --git a/bin/tests/e2e/ConfigUpdate_e2e_ab_test.cpp b/bin/tests/e2e/ConfigUpdate_e2e_ab_test.cpp index 9b1cb12d..17007f9a 100644 --- a/bin/tests/e2e/ConfigUpdate_e2e_ab_test.cpp +++ b/bin/tests/e2e/ConfigUpdate_e2e_ab_test.cpp @@ -53,6 +53,24 @@ StatsdConfig CreateSimpleConfig() { // Setup for parameterized tests. class ConfigUpdateE2eAbTest : public TestWithParam { +private: + string originalFlagValue; + +public: + void SetUp() override { + originalFlagValue = getFlagBool(PARTIAL_CONFIG_UPDATE_FLAG, ""); + string rawFlagName = + StringPrintf("persist.device_config.%s.%s", STATSD_NATIVE_NAMESPACE.c_str(), + PARTIAL_CONFIG_UPDATE_FLAG.c_str()); + SetProperty(rawFlagName, GetParam() ? "true" : "false"); + } + + void TearDown() override { + string rawFlagName = + StringPrintf("persist.device_config.%s.%s", STATSD_NATIVE_NAMESPACE.c_str(), + PARTIAL_CONFIG_UPDATE_FLAG.c_str()); + SetProperty(rawFlagName, originalFlagValue); + } }; INSTANTIATE_TEST_SUITE_P(ConfigUpdateE2eAbTest, ConfigUpdateE2eAbTest, testing::Bool()); @@ -81,7 +99,7 @@ TEST_P(ConfigUpdateE2eAbTest, TestUidMapVersionStringInstaller) { // Now update. config.set_version_strings_in_metric_report(false); config.set_installer_in_metric_report(true); - processor->OnConfigUpdated(baseTimeNs + 1000, cfgKey, config, GetParam()); + processor->OnConfigUpdated(baseTimeNs + 1000, cfgKey, config); EXPECT_EQ(processor->mMetricsManagers.size(), 1u); EXPECT_EQ(metricsManager == processor->mMetricsManagers.begin()->second, GetParam()); EXPECT_TRUE(metricsManager->isConfigValid()); @@ -122,7 +140,7 @@ TEST_P(ConfigUpdateE2eAbTest, TestHashStrings) { // Now update. config.set_hash_strings_in_metric_report(false); - processor->OnConfigUpdated(baseTimeNs + 1000, cfgKey, config, GetParam()); + processor->OnConfigUpdated(baseTimeNs + 1000, cfgKey, config); EXPECT_EQ(processor->mMetricsManagers.size(), 1u); EXPECT_EQ(metricsManager == processor->mMetricsManagers.begin()->second, GetParam()); EXPECT_TRUE(metricsManager->isConfigValid()); @@ -155,7 +173,7 @@ TEST_P(ConfigUpdateE2eAbTest, TestAnnotations) { annotation = config.add_annotation(); annotation->set_field_int64(22); annotation->set_field_int32(2); - processor->OnConfigUpdated(baseTimeNs + 1000, cfgKey, config, GetParam()); + processor->OnConfigUpdated(baseTimeNs + 1000, cfgKey, config); ConfigMetricsReportList reports; vector buffer; @@ -190,7 +208,7 @@ TEST_P(ConfigUpdateE2eAbTest, TestPersistLocally) { // Now update. config.set_persist_locally(true); - processor->OnConfigUpdated(baseTimeNs + 1000, cfgKey, config, GetParam()); + processor->OnConfigUpdated(baseTimeNs + 1000, cfgKey, config); // Should get 2: 1 in memory + 1 on disk. Both should be saved on disk. reports.Clear(); @@ -227,7 +245,7 @@ TEST_P(ConfigUpdateE2eAbTest, TestNoReportMetrics) { // Now update. config.clear_no_report_metric(); config.add_no_report_metric(config.count_metric(1).id()); - processor->OnConfigUpdated(baseTimeNs + 1000, cfgKey, config, GetParam()); + processor->OnConfigUpdated(baseTimeNs + 1000, cfgKey, config); ConfigMetricsReportList reports; vector buffer; @@ -264,7 +282,7 @@ TEST_P(ConfigUpdateE2eAbTest, TestAtomsAllowedFromAnyUid) { // Now update. Allow plugged state to be logged from any uid, so the atom will be counted. config.add_whitelisted_atom_ids(util::PLUGGED_STATE_CHANGED); - processor->OnConfigUpdated(baseTimeNs + 1000, cfgKey, config, GetParam()); + processor->OnConfigUpdated(baseTimeNs + 1000, cfgKey, config); unique_ptr event2 = CreateBatteryStateChangedEvent( baseTimeNs + 2000, BatteryPluggedStateEnum::BATTERY_PLUGGED_USB); processor->OnLogEvent(event.get()); @@ -293,7 +311,7 @@ TEST_P(ConfigUpdateE2eAbTest, TestConfigTtl) { EXPECT_EQ(metricsManager->getTtlEndNs(), baseTimeNs + NS_PER_SEC); config.set_ttl_in_seconds(5); - processor->OnConfigUpdated(baseTimeNs + 2 * NS_PER_SEC, cfgKey, config, GetParam()); + processor->OnConfigUpdated(baseTimeNs + 2 * NS_PER_SEC, cfgKey, config); metricsManager = processor->mMetricsManagers.begin()->second; EXPECT_EQ(metricsManager->getTtlEndNs(), baseTimeNs + 7 * NS_PER_SEC); @@ -326,7 +344,7 @@ TEST_P(ConfigUpdateE2eAbTest, TestExistingGaugePullRandomOneSample) { SharedRefBase::make(), util::SUBSYSTEM_SLEEP_STATE); uint64_t updateTimeNs = bucketStartTimeNs + 60 * NS_PER_SEC; - processor->OnConfigUpdated(updateTimeNs, key, config, GetParam()); + processor->OnConfigUpdated(updateTimeNs, key, config); uint64_t dumpTimeNs = bucketStartTimeNs + 90 * NS_PER_SEC; ConfigMetricsReportList reports; vector buffer; diff --git a/bin/tests/e2e/ConfigUpdate_e2e_test.cpp b/bin/tests/e2e/ConfigUpdate_e2e_test.cpp index 31b00971..95f4e7a3 100644 --- a/bin/tests/e2e/ConfigUpdate_e2e_test.cpp +++ b/bin/tests/e2e/ConfigUpdate_e2e_test.cpp @@ -39,6 +39,23 @@ using namespace std; namespace { // Setup for test fixture. class ConfigUpdateE2eTest : public ::testing::Test { +private: + string originalFlagValue; +public: + void SetUp() override { + originalFlagValue = getFlagBool(PARTIAL_CONFIG_UPDATE_FLAG, ""); + string rawFlagName = + StringPrintf("persist.device_config.%s.%s", STATSD_NATIVE_NAMESPACE.c_str(), + PARTIAL_CONFIG_UPDATE_FLAG.c_str()); + SetProperty(rawFlagName, "true"); + } + + void TearDown() override { + string rawFlagName = + StringPrintf("persist.device_config.%s.%s", STATSD_NATIVE_NAMESPACE.c_str(), + PARTIAL_CONFIG_UPDATE_FLAG.c_str()); + SetProperty(rawFlagName, originalFlagValue); + } }; void ValidateSubsystemSleepDimension(const DimensionsValue& value, string name) { -- cgit v1.2.3 From 74e20c55f1470e08e9984b9ba181de1b328fe9f4 Mon Sep 17 00:00:00 2001 From: Tej Singh Date: Wed, 17 Feb 2021 00:25:01 -0800 Subject: Fix memory management for subscriber death cookies The cookies were only deleted when the binder object death recipient was called, and the memory was not properly managed. Instead, we can just pass the raw pointer of the binder object and use that to find the object in the map. Note that this requires iterating through the map, but this should be rare enough, and the map is expected to be very small. Test: atest statsd_test Bug: 180471118 Change-Id: Ia71cf526d8627a575751b565a1dfce4e074fa21b Merged-In: Ibb065d64be3775c699583215f7f376a1a5492ac1 --- bin/src/subscriber/SubscriberReporter.cpp | 72 +++++++++++-------------------- bin/src/subscriber/SubscriberReporter.h | 6 +-- 2 files changed, 27 insertions(+), 51 deletions(-) diff --git a/bin/src/subscriber/SubscriberReporter.cpp b/bin/src/subscriber/SubscriberReporter.cpp index c915ef3b..c6bdc3fe 100644 --- a/bin/src/subscriber/SubscriberReporter.cpp +++ b/bin/src/subscriber/SubscriberReporter.cpp @@ -27,44 +27,28 @@ namespace statsd { using std::vector; -struct BroadcastSubscriberDeathCookie { - BroadcastSubscriberDeathCookie(const ConfigKey& configKey, int64_t subscriberId, - const shared_ptr& pir): - mConfigKey(configKey), - mSubscriberId(subscriberId), - mPir(pir) {} - - ConfigKey mConfigKey; - int64_t mSubscriberId; - shared_ptr mPir; -}; - -void SubscriberReporter::broadcastSubscriberDied(void* cookie) { - auto cookie_ = static_cast(cookie); - ConfigKey& configKey = cookie_->mConfigKey; - int64_t subscriberId = cookie_->mSubscriberId; - shared_ptr& pir = cookie_->mPir; - +void SubscriberReporter::broadcastSubscriberDied(void* rawPir) { SubscriberReporter& thiz = getInstance(); // Erase the mapping from a (config_key, subscriberId) to a pir if the - // mapping exists. + // mapping exists. This requires iterating over the map, but this operation + // should be rare and the map is expected to be small. lock_guard lock(thiz.mLock); - auto subscriberMapIt = thiz.mIntentMap.find(configKey); - if (subscriberMapIt != thiz.mIntentMap.end()) { - auto subscriberMap = subscriberMapIt->second; - auto pirIt = subscriberMap.find(subscriberId); - if (pirIt != subscriberMap.end() && pirIt->second == pir) { - subscriberMap.erase(subscriberId); - if (subscriberMap.empty()) { - thiz.mIntentMap.erase(configKey); + for (auto subscriberMapIt = thiz.mIntentMap.begin(); subscriberMapIt != thiz.mIntentMap.end(); + subscriberMapIt++) { + unordered_map>& subscriberMap = + subscriberMapIt->second; + for (auto pirIt = subscriberMap.begin(); pirIt != subscriberMap.end(); pirIt++) { + if (pirIt->second.get() == rawPir) { + subscriberMap.erase(pirIt); + if (subscriberMap.empty()) { + thiz.mIntentMap.erase(subscriberMapIt); + } + // pirIt and subscriberMapIt are now invalid. + return; } } } - - // The death recipient corresponding to this specific pir can never be - // triggered again, so free up resources. - delete cookie_; } SubscriberReporter::SubscriberReporter() : @@ -74,13 +58,21 @@ SubscriberReporter::SubscriberReporter() : void SubscriberReporter::setBroadcastSubscriber(const ConfigKey& configKey, int64_t subscriberId, const shared_ptr& pir) { - VLOG("SubscriberReporter::setBroadcastSubscriber called."); + VLOG("SubscriberReporter::setBroadcastSubscriber called with configKey %s and subscriberId " + "%lld.", + configKey.ToString().c_str(), (long long)subscriberId); { lock_guard lock(mLock); mIntentMap[configKey][subscriberId] = pir; } + // Pass the raw binder pointer address to be the cookie of the death recipient. While the death + // notification is fired, the cookie is used for identifying which binder was died. Because + // the NDK binder doesn't pass dead binder pointer to binder death handler, the binder death + // handler can't know who died. + // If a dedicated cookie is used to store metadata (config key, subscriber id) for direct + // lookup, a data structure is needed manage the cookies. AIBinder_linkToDeath(pir->asBinder().get(), mBroadcastSubscriberDeathRecipient.get(), - new BroadcastSubscriberDeathCookie(configKey, subscriberId, pir)); + pir.get()); } void SubscriberReporter::unsetBroadcastSubscriber(const ConfigKey& configKey, @@ -152,20 +144,6 @@ void SubscriberReporter::sendBroadcastLocked(const shared_ptr dimKey.getDimensionKeyInWhat().toStatsDimensionsValueParcel()); } -shared_ptr SubscriberReporter::getBroadcastSubscriber(const ConfigKey& configKey, - int64_t subscriberId) { - lock_guard lock(mLock); - auto subscriberMapIt = mIntentMap.find(configKey); - if (subscriberMapIt == mIntentMap.end()) { - return nullptr; - } - auto pirMapIt = subscriberMapIt->second.find(subscriberId); - if (pirMapIt == subscriberMapIt->second.end()) { - return nullptr; - } - return pirMapIt->second; -} - } // namespace statsd } // namespace os } // namespace android diff --git a/bin/src/subscriber/SubscriberReporter.h b/bin/src/subscriber/SubscriberReporter.h index 7699e18e..4946fea4 100644 --- a/bin/src/subscriber/SubscriberReporter.h +++ b/bin/src/subscriber/SubscriberReporter.h @@ -74,9 +74,6 @@ public: const Subscription& subscription, const MetricDimensionKey& dimKey) const; - shared_ptr getBroadcastSubscriber(const ConfigKey& configKey, - int64_t subscriberId); - private: SubscriberReporter(); @@ -99,7 +96,8 @@ private: /** * Death recipient callback that is called when a broadcast subscriber dies. - * The cookie is a pointer to a BroadcastSubscriberDeathCookie. + * The cookie is a raw pointer to a PendingIntentReference. It is only used for identifying + * which binder has died and must not be dereferenced. */ static void broadcastSubscriberDied(void* cookie); }; -- cgit v1.2.3 From 09eab7cadeebdb9f1b34d9c4d5a946be5c275e4a Mon Sep 17 00:00:00 2001 From: Tej Singh Date: Wed, 17 Feb 2021 00:32:42 -0800 Subject: E2e test for count metric anomaly on config update Mocks out IPendingIntentRef to properly e2e test alerts firing and informing the subscriptions. (The previous tests only asserted that the alert was in refractory period). Test preserving an alert preserves history and refractory period, that building a new alert on an existing metric works, and that removing/replacing alerts works. Test: atest statsd_test Bug: 174976680 Change-Id: I06f3b08068159d6ca123468578be167e29fefc1a Merged-In: Ibb065d64be3775c699583215f7f376a1a5492ac1 --- bin/src/HashableDimensionKey.cpp | 9 - bin/src/HashableDimensionKey.h | 9 + bin/tests/FieldValue_test.cpp | 6 - bin/tests/e2e/ConfigUpdate_e2e_test.cpp | 214 +++++++++++++++++++++ .../parsing_utils/config_update_utils_test.cpp | 16 -- bin/tests/statsd_test_util.cpp | 41 ++++ bin/tests/statsd_test_util.h | 20 ++ 7 files changed, 284 insertions(+), 31 deletions(-) diff --git a/bin/src/HashableDimensionKey.cpp b/bin/src/HashableDimensionKey.cpp index eba66e0c..037856aa 100644 --- a/bin/src/HashableDimensionKey.cpp +++ b/bin/src/HashableDimensionKey.cpp @@ -27,15 +27,6 @@ using std::string; using std::vector; using android::base::StringPrintf; -// These constants must be kept in sync with those in StatsDimensionsValue.java -const static int STATS_DIMENSIONS_VALUE_STRING_TYPE = 2; -const static int STATS_DIMENSIONS_VALUE_INT_TYPE = 3; -const static int STATS_DIMENSIONS_VALUE_LONG_TYPE = 4; -// const static int STATS_DIMENSIONS_VALUE_BOOL_TYPE = 5; (commented out because -// unused -- statsd does not correctly support bool types) -const static int STATS_DIMENSIONS_VALUE_FLOAT_TYPE = 6; -const static int STATS_DIMENSIONS_VALUE_TUPLE_TYPE = 7; - /** * Recursive helper function that populates a parent StatsDimensionsValueParcel * with children StatsDimensionsValueParcels. diff --git a/bin/src/HashableDimensionKey.h b/bin/src/HashableDimensionKey.h index bd011005..096de5d2 100644 --- a/bin/src/HashableDimensionKey.h +++ b/bin/src/HashableDimensionKey.h @@ -29,6 +29,15 @@ namespace statsd { using ::aidl::android::os::StatsDimensionsValueParcel; +// These constants must be kept in sync with those in StatsDimensionsValue.java +inline constexpr int STATS_DIMENSIONS_VALUE_STRING_TYPE = 2; +inline constexpr int STATS_DIMENSIONS_VALUE_INT_TYPE = 3; +inline constexpr int STATS_DIMENSIONS_VALUE_LONG_TYPE = 4; +// inline constexpr int STATS_DIMENSIONS_VALUE_BOOL_TYPE = 5; (commented out because +// unused -- statsd does not correctly support bool types) +inline constexpr int STATS_DIMENSIONS_VALUE_FLOAT_TYPE = 6; +inline constexpr int STATS_DIMENSIONS_VALUE_TUPLE_TYPE = 7; + struct Metric2Condition { int64_t conditionId; std::vector metricFields; diff --git a/bin/tests/FieldValue_test.cpp b/bin/tests/FieldValue_test.cpp index 300a454a..5af13492 100644 --- a/bin/tests/FieldValue_test.cpp +++ b/bin/tests/FieldValue_test.cpp @@ -33,12 +33,6 @@ namespace android { namespace os { namespace statsd { -// These constants must be kept in sync with those in StatsDimensionsValue.java. -const static int STATS_DIMENSIONS_VALUE_STRING_TYPE = 2; -const static int STATS_DIMENSIONS_VALUE_INT_TYPE = 3; -const static int STATS_DIMENSIONS_VALUE_FLOAT_TYPE = 6; -const static int STATS_DIMENSIONS_VALUE_TUPLE_TYPE = 7; - namespace { void makeLogEvent(LogEvent* logEvent, const int32_t atomId, const int64_t timestamp, const vector& attributionUids, const vector& attributionTags, diff --git a/bin/tests/e2e/ConfigUpdate_e2e_test.cpp b/bin/tests/e2e/ConfigUpdate_e2e_test.cpp index 95f4e7a3..d8f36ef1 100644 --- a/bin/tests/e2e/ConfigUpdate_e2e_test.cpp +++ b/bin/tests/e2e/ConfigUpdate_e2e_test.cpp @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +#include #include #include #include @@ -22,6 +23,7 @@ #include "flags/flags.h" #include "src/StatsLogProcessor.h" #include "src/storage/StorageManager.h" +#include "src/subscriber/SubscriberReporter.h" #include "tests/statsd_test_util.h" namespace android { @@ -30,6 +32,7 @@ namespace statsd { #ifdef __ANDROID__ +using aidl::android::os::StatsDimensionsValueParcel; using android::base::SetProperty; using android::base::StringPrintf; using ::ndk::SharedRefBase; @@ -1656,6 +1659,217 @@ TEST_F(ConfigUpdateE2eTest, TestMetricActivation) { ValidateCountBucket(data.bucket_info(0), bootTimeNs, deactivationTimeNs, 1); } +TEST_F(ConfigUpdateE2eTest, TestAnomalyCountMetric) { + StatsdConfig config; + config.add_allowed_log_source("AID_ROOT"); + + AtomMatcher syncStartMatcher = CreateSyncStartAtomMatcher(); + *config.add_atom_matcher() = syncStartMatcher; + AtomMatcher wakelockAcquireMatcher = CreateAcquireWakelockAtomMatcher(); + *config.add_atom_matcher() = wakelockAcquireMatcher; + + CountMetric countWakelock = + createCountMetric("CountWakelock", wakelockAcquireMatcher.id(), nullopt, {}); + *countWakelock.mutable_dimensions_in_what() = + CreateAttributionUidDimensions(util::WAKELOCK_STATE_CHANGED, {Position::FIRST}); + + CountMetric countSync = createCountMetric("CountSync", syncStartMatcher.id(), nullopt, {}); + *countSync.mutable_dimensions_in_what() = + CreateAttributionUidDimensions(util::SYNC_STATE_CHANGED, {Position::FIRST}); + + *config.add_count_metric() = countWakelock; + *config.add_count_metric() = countSync; + + Alert alertPreserve = + createAlert("AlertPreserve", countWakelock.id(), /*buckets=*/2, /*triggerSumGt=*/1); + alertPreserve.set_refractory_period_secs(20); + Alert alertReplace = createAlert("AlertReplace", countSync.id(), 1, 1); + alertReplace.set_refractory_period_secs(1); + Alert alertRemove = createAlert("AlertRemove", countWakelock.id(), 1, 0); + alertRemove.set_refractory_period_secs(1); + *config.add_alert() = alertReplace; + *config.add_alert() = alertPreserve; + *config.add_alert() = alertRemove; + + int preserveSubId = 1, replaceSubId = 2, removeSubId = 3; + Subscription preserveSub = createSubscription("S1", Subscription::ALERT, alertPreserve.id()); + preserveSub.mutable_broadcast_subscriber_details()->set_subscriber_id(preserveSubId); + Subscription replaceSub = createSubscription("S2", Subscription::ALERT, alertReplace.id()); + replaceSub.mutable_broadcast_subscriber_details()->set_subscriber_id(replaceSubId); + Subscription removeSub = createSubscription("S3", Subscription::ALERT, alertRemove.id()); + removeSub.mutable_broadcast_subscriber_details()->set_subscriber_id(removeSubId); + *config.add_subscription() = preserveSub; + *config.add_subscription() = removeSub; + *config.add_subscription() = replaceSub; + + int app1Uid = 123, app2Uid = 456; + vector attributionUids1 = {app1Uid}; + vector attributionTags1 = {"App1"}; + vector attributionUids2 = {app2Uid}; + vector attributionTags2 = {"App2"}; + int64_t configUid = 123, configId = 987; + ConfigKey key(configUid, configId); + + int alertPreserveCount = 0, alertRemoveCount = 0; + StatsDimensionsValueParcel alertPreserveDims; + StatsDimensionsValueParcel alertRemoveDims; + + // The binder calls here will happen synchronously because they are in-process. + shared_ptr preserveBroadcast = + SharedRefBase::make>(); + EXPECT_CALL(*preserveBroadcast, sendSubscriberBroadcast(configUid, configId, preserveSub.id(), + alertPreserve.id(), _, _)) + .Times(2) + .WillRepeatedly( + Invoke([&alertPreserveCount, &alertPreserveDims]( + int64_t, int64_t, int64_t, int64_t, const vector&, + const StatsDimensionsValueParcel& dimensionsValueParcel) { + alertPreserveCount++; + alertPreserveDims = dimensionsValueParcel; + return Status::ok(); + })); + + shared_ptr replaceBroadcast = + SharedRefBase::make>(); + EXPECT_CALL(*replaceBroadcast, sendSubscriberBroadcast(configUid, configId, replaceSub.id(), + alertReplace.id(), _, _)) + .Times(0); + + shared_ptr removeBroadcast = + SharedRefBase::make>(); + EXPECT_CALL(*removeBroadcast, sendSubscriberBroadcast(configUid, configId, removeSub.id(), + alertRemove.id(), _, _)) + .Times(3) + .WillRepeatedly( + Invoke([&alertRemoveCount, &alertRemoveDims]( + int64_t, int64_t, int64_t, int64_t, const vector&, + const StatsDimensionsValueParcel& dimensionsValueParcel) { + alertRemoveCount++; + alertRemoveDims = dimensionsValueParcel; + return Status::ok(); + })); + + SubscriberReporter::getInstance().setBroadcastSubscriber(key, preserveSubId, preserveBroadcast); + SubscriberReporter::getInstance().setBroadcastSubscriber(key, replaceSubId, replaceBroadcast); + SubscriberReporter::getInstance().setBroadcastSubscriber(key, removeSubId, removeBroadcast); + + uint64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(TEN_MINUTES) * 1000000LL; + uint64_t bucketStartTimeNs = 10000000000; // 0:10 + uint64_t bucket2StartTimeNs = bucketStartTimeNs + bucketSizeNs; + sp processor = + CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, key); + + StatsDimensionsValueParcel wlUid1 = + CreateAttributionUidDimensionsValueParcel(util::WAKELOCK_STATE_CHANGED, app1Uid); + StatsDimensionsValueParcel wlUid2 = + CreateAttributionUidDimensionsValueParcel(util::WAKELOCK_STATE_CHANGED, app2Uid); + + processor->OnLogEvent(CreateAcquireWakelockEvent(bucketStartTimeNs + 15 * NS_PER_SEC, + attributionUids1, attributionTags1, "wl1") + .get()); + EXPECT_EQ(alertPreserveCount, 0); + EXPECT_EQ(alertRemoveCount, 1); +// EXPECT_EQ(alertRemoveDims, wlUid1); + + processor->OnLogEvent(CreateAcquireWakelockEvent(bucketStartTimeNs + 20 * NS_PER_SEC, + attributionUids2, attributionTags2, "wl2") + .get()); + EXPECT_EQ(alertPreserveCount, 0); + EXPECT_EQ(alertRemoveCount, 2); +// EXPECT_EQ(alertRemoveDims, wlUid2); + + processor->OnLogEvent(CreateSyncStartEvent(bucket2StartTimeNs + 5 * NS_PER_SEC, + attributionUids1, attributionTags1, "sync1") + .get()); + EXPECT_EQ(alertPreserveCount, 0); + EXPECT_EQ(alertRemoveCount, 2); + + // AlertPreserve enters 30 sec refractory period for uid2. + processor->OnLogEvent(CreateAcquireWakelockEvent(bucket2StartTimeNs + 10 * NS_PER_SEC, + attributionUids2, attributionTags2, "wl2") + .get()); + EXPECT_EQ(alertPreserveCount, 1); +// EXPECT_EQ(alertPreserveDims, wlUid2); + EXPECT_EQ(alertRemoveCount, 3); +// EXPECT_EQ(alertRemoveDims, wlUid2); + + // Do config update. + StatsdConfig newConfig; + newConfig.add_allowed_log_source("AID_ROOT"); + *newConfig.add_atom_matcher() = wakelockAcquireMatcher; + *newConfig.add_atom_matcher() = syncStartMatcher; + + // Clear dims of sync metric, will result in alertReplace getting replaced. + countSync.clear_dimensions_in_what(); + *newConfig.add_count_metric() = countSync; + *newConfig.add_count_metric() = countWakelock; + + // New alert on existing metric. Should get current full bucket, but not history of 1st bucket. + Alert alertNew = createAlert("AlertNew", countWakelock.id(), /*buckets=*/1, /*triggerSumGt=*/1); + *newConfig.add_alert() = alertPreserve; + *newConfig.add_alert() = alertNew; + *newConfig.add_alert() = alertReplace; + + int newSubId = 4; + Subscription newSub = createSubscription("S4", Subscription::ALERT, alertNew.id()); + newSub.mutable_broadcast_subscriber_details()->set_subscriber_id(newSubId); + *newConfig.add_subscription() = newSub; + *newConfig.add_subscription() = replaceSub; + *newConfig.add_subscription() = preserveSub; + + int alertNewCount = 0; + StatsDimensionsValueParcel alertNewDims; + shared_ptr newBroadcast = + SharedRefBase::make>(); + EXPECT_CALL(*newBroadcast, + sendSubscriberBroadcast(configUid, configId, newSub.id(), alertNew.id(), _, _)) + .Times(1) + .WillRepeatedly( + Invoke([&alertNewCount, &alertNewDims]( + int64_t, int64_t, int64_t, int64_t, const vector&, + const StatsDimensionsValueParcel& dimensionsValueParcel) { + alertNewCount++; + alertNewDims = dimensionsValueParcel; + return Status::ok(); + })); + SubscriberReporter::getInstance().setBroadcastSubscriber(key, newSubId, newBroadcast); + + int64_t updateTimeNs = bucket2StartTimeNs + 15 * NS_PER_SEC; + processor->OnConfigUpdated(updateTimeNs, key, newConfig); + + // Within refractory of AlertPreserve, but AlertNew should fire since the full bucket has 2. + processor->OnLogEvent(CreateAcquireWakelockEvent(bucket2StartTimeNs + 20 * NS_PER_SEC, + attributionUids2, attributionTags2, "wl2") + .get()); + EXPECT_EQ(alertPreserveCount, 1); + EXPECT_EQ(alertNewCount, 1); +// EXPECT_EQ(alertNewDims, wlUid2); + + // Wakelock for uid1 fired in first bucket, alert preserve should keep the history and fire. + processor->OnLogEvent(CreateAcquireWakelockEvent(bucket2StartTimeNs + 25 * NS_PER_SEC, + attributionUids1, attributionTags1, "wl1") + .get()); + EXPECT_EQ(alertPreserveCount, 2); +// EXPECT_EQ(alertPreserveDims, wlUid1); + EXPECT_EQ(alertNewCount, 1); + + processor->OnLogEvent(CreateSyncStartEvent(bucket2StartTimeNs + 30 * NS_PER_SEC, + attributionUids1, attributionTags1, "sync1") + .get()); + EXPECT_EQ(alertPreserveCount, 2); + EXPECT_EQ(alertNewCount, 1); + EXPECT_EQ(alertRemoveCount, 3); + + // Clear data so it doesn't stay on disk. + vector buffer; + processor->onDumpReport(key, bucket2StartTimeNs + 100 * NS_PER_SEC, true, true, ADB_DUMP, FAST, + &buffer); + SubscriberReporter::getInstance().unsetBroadcastSubscriber(key, preserveSubId); + SubscriberReporter::getInstance().unsetBroadcastSubscriber(key, replaceSubId); + SubscriberReporter::getInstance().unsetBroadcastSubscriber(key, removeSubId); + SubscriberReporter::getInstance().unsetBroadcastSubscriber(key, newSubId); +} + TEST_F(ConfigUpdateE2eTest, TestNewDurationExistingWhat) { StatsdConfig config; config.add_allowed_log_source("AID_ROOT"); diff --git a/bin/tests/metrics/parsing_utils/config_update_utils_test.cpp b/bin/tests/metrics/parsing_utils/config_update_utils_test.cpp index 0dfdf021..fac506ed 100644 --- a/bin/tests/metrics/parsing_utils/config_update_utils_test.cpp +++ b/bin/tests/metrics/parsing_utils/config_update_utils_test.cpp @@ -129,23 +129,7 @@ EventMetric createEventMetric(string name, int64_t what, optional condi return metric; } -Alert createAlert(string name, int64_t metricId, int buckets, int64_t triggerSum) { - Alert alert; - alert.set_id(StringToId(name)); - alert.set_metric_id(metricId); - alert.set_num_buckets(buckets); - alert.set_trigger_if_sum_gt(triggerSum); - return alert; -} -Subscription createSubscription(string name, Subscription_RuleType type, int64_t ruleId) { - Subscription subscription; - subscription.set_id(StringToId(name)); - subscription.set_rule_type(type); - subscription.set_rule_id(ruleId); - subscription.mutable_broadcast_subscriber_details(); - return subscription; -} Alarm createAlarm(string name, int64_t offsetMillis, int64_t periodMillis) { Alarm alarm; diff --git a/bin/tests/statsd_test_util.cpp b/bin/tests/statsd_test_util.cpp index 0a622408..3ea520ef 100644 --- a/bin/tests/statsd_test_util.cpp +++ b/bin/tests/statsd_test_util.cpp @@ -512,6 +512,26 @@ ValueMetric createValueMetric(const string& name, const AtomMatcher& what, const return metric; } +Alert createAlert(const string& name, const int64_t metricId, const int buckets, + const int64_t triggerSum) { + Alert alert; + alert.set_id(StringToId(name)); + alert.set_metric_id(metricId); + alert.set_num_buckets(buckets); + alert.set_trigger_if_sum_gt(triggerSum); + return alert; +} + +Subscription createSubscription(const string& name, const Subscription_RuleType type, + const int64_t ruleId) { + Subscription subscription; + subscription.set_id(StringToId(name)); + subscription.set_rule_type(type); + subscription.set_rule_id(ruleId); + subscription.mutable_broadcast_subscriber_details(); + return subscription; +} + // START: get primary key functions void getUidProcessKey(int uid, HashableDimensionKey* key) { int pos1[] = {1, 0, 0}; @@ -1100,6 +1120,27 @@ sp createEventMatcherWizard( matcherId, matcherIndex, matcherHash, atomMatcher, uidMap)}); } +StatsDimensionsValueParcel CreateAttributionUidDimensionsValueParcel(const int atomId, + const int uid) { + StatsDimensionsValueParcel root; + root.field = atomId; + root.valueType = STATS_DIMENSIONS_VALUE_TUPLE_TYPE; + StatsDimensionsValueParcel attrNode; + attrNode.field = 1; + attrNode.valueType = STATS_DIMENSIONS_VALUE_TUPLE_TYPE; + StatsDimensionsValueParcel posInAttrChain; + posInAttrChain.field = 1; + posInAttrChain.valueType = STATS_DIMENSIONS_VALUE_TUPLE_TYPE; + StatsDimensionsValueParcel uidNode; + uidNode.field = 1; + uidNode.valueType = STATS_DIMENSIONS_VALUE_INT_TYPE; + uidNode.intValue = uid; + posInAttrChain.tupleValue.push_back(uidNode); + attrNode.tupleValue.push_back(posInAttrChain); + root.tupleValue.push_back(attrNode); + return root; +} + void ValidateUidDimension(const DimensionsValue& value, int atomId, int uid) { EXPECT_EQ(value.field(), atomId); ASSERT_EQ(value.value_tuple().dimensions_value_size(), 1); diff --git a/bin/tests/statsd_test_util.h b/bin/tests/statsd_test_util.h index 974bf0fe..b8406766 100644 --- a/bin/tests/statsd_test_util.h +++ b/bin/tests/statsd_test_util.h @@ -14,6 +14,7 @@ #pragma once +#include #include #include #include @@ -54,6 +55,16 @@ public: MOCK_METHOD(std::set, getAppUid, (const string& package), (const)); }; +class MockPendingIntentRef : public aidl::android::os::BnPendingIntentRef { +public: + MOCK_METHOD1(sendDataBroadcast, Status(int64_t lastReportTimeNs)); + MOCK_METHOD1(sendActiveConfigsChangedBroadcast, Status(const vector& configIds)); + MOCK_METHOD6(sendSubscriberBroadcast, + Status(int64_t configUid, int64_t configId, int64_t subscriptionId, + int64_t subscriptionRuleId, const vector& cookies, + const StatsDimensionsValueParcel& dimensionsValueParcel)); +}; + // Converts a ProtoOutputStream to a StatsLogReport proto. StatsLogReport outputStreamToProto(ProtoOutputStream* proto); @@ -207,6 +218,12 @@ GaugeMetric createGaugeMetric(const string& name, const int64_t what, ValueMetric createValueMetric(const string& name, const AtomMatcher& what, const int valueField, const optional& condition, const vector& states); +Alert createAlert(const string& name, const int64_t metricId, const int buckets, + const int64_t triggerSum); + +Subscription createSubscription(const string& name, const Subscription_RuleType type, + const int64_t ruleId); + // START: get primary key functions // These functions take in atom field information and create FieldValues which are stored in the // given HashableDimensionKey. @@ -359,6 +376,9 @@ int64_t StringToId(const string& str); sp createEventMatcherWizard( int tagId, int matcherIndex, const std::vector& fieldValueMatchers = {}); +StatsDimensionsValueParcel CreateAttributionUidDimensionsValueParcel(const int atomId, + const int uid); + void ValidateUidDimension(const DimensionsValue& value, int atomId, int uid); void ValidateWakelockAttributionUidAndTagDimension(const DimensionsValue& value, const int atomId, const int uid, const string& tag); -- cgit v1.2.3 From c1e7906b195634310453c1dfdbe12dac28f4beaf Mon Sep 17 00:00:00 2001 From: tsaichristine Date: Thu, 11 Feb 2021 10:39:31 -0800 Subject: Add upload threshold feature to DurationMetric - UploadThreshold proto added to StatsdConfig - UploadThreshold metadata stored in MetricProducers. For DurationMetric, this metadata is sent to DurationTrackers when flushCurrentBucket is called. Test: atest statsd_test Bug: 179087499 Bug: 179087962 Bug: 179087894 Bug: 179088663 Change-Id: I32324c50261eb306544ba21b58709c3edb2e318e Merged-In: Ibb065d64be3775c699583215f7f376a1a5492ac1 --- bin/src/StatsLogProcessor.h | 1 + bin/src/metrics/DurationMetricProducer.cpp | 6 +- bin/src/metrics/MetricProducer.h | 3 + bin/src/metrics/MetricsManager.h | 1 + bin/src/metrics/duration_helper/DurationTracker.h | 29 ++++++- .../metrics/duration_helper/MaxDurationTracker.cpp | 11 ++- .../metrics/duration_helper/MaxDurationTracker.h | 5 +- .../duration_helper/OringDurationTracker.cpp | 11 ++- .../metrics/duration_helper/OringDurationTracker.h | 5 +- .../metrics/parsing_utils/metrics_manager_util.cpp | 14 ++++ bin/src/statsd_config.proto | 13 +++ bin/tests/e2e/DurationMetric_e2e_test.cpp | 95 ++++++++++++++++++++++ bin/tests/metrics/MaxDurationTracker_test.cpp | 55 ++++++++++--- bin/tests/metrics/OringDurationTracker_test.cpp | 57 ++++++++++--- 14 files changed, 270 insertions(+), 36 deletions(-) diff --git a/bin/src/StatsLogProcessor.h b/bin/src/StatsLogProcessor.h index fe20699c..1dc9d8e5 100644 --- a/bin/src/StatsLogProcessor.h +++ b/bin/src/StatsLogProcessor.h @@ -361,6 +361,7 @@ private: FRIEND_TEST(DurationMetricE2eTest, TestWithSlicedStateMapped); FRIEND_TEST(DurationMetricE2eTest, TestSlicedStatePrimaryFieldsNotSubsetDimInWhat); FRIEND_TEST(DurationMetricE2eTest, TestWithSlicedStatePrimaryFieldsSubset); + FRIEND_TEST(DurationMetricE2eTest, TestUploadThreshold); FRIEND_TEST(ValueMetricE2eTest, TestInitialConditionChanges); FRIEND_TEST(ValueMetricE2eTest, TestPulledEvents); diff --git a/bin/src/metrics/DurationMetricProducer.cpp b/bin/src/metrics/DurationMetricProducer.cpp index fe92b08c..272936d7 100644 --- a/bin/src/metrics/DurationMetricProducer.cpp +++ b/bin/src/metrics/DurationMetricProducer.cpp @@ -91,6 +91,10 @@ DurationMetricProducer::DurationMetricProducer( mBucketSizeNs = LLONG_MAX; } + if (metric.has_threshold()) { + mUploadThreshold = metric.threshold(); + } + if (metric.has_dimensions_in_what()) { translateFieldMatcher(metric.dimensions_in_what(), &mDimensionsInWhat); mContainANYPositionInDimensionsInWhat = HasPositionANY(metric.dimensions_in_what()); @@ -605,7 +609,7 @@ void DurationMetricProducer::flushCurrentBucketLocked(const int64_t& eventTimeNs const int64_t& nextBucketStartTimeNs) { for (auto whatIt = mCurrentSlicedDurationTrackerMap.begin(); whatIt != mCurrentSlicedDurationTrackerMap.end();) { - if (whatIt->second->flushCurrentBucket(eventTimeNs, &mPastBuckets)) { + if (whatIt->second->flushCurrentBucket(eventTimeNs, mUploadThreshold, &mPastBuckets)) { VLOG("erase bucket for key %s", whatIt->first.toString().c_str()); whatIt = mCurrentSlicedDurationTrackerMap.erase(whatIt); } else { diff --git a/bin/src/metrics/MetricProducer.h b/bin/src/metrics/MetricProducer.h index 9a690282..01ed57af 100644 --- a/bin/src/metrics/MetricProducer.h +++ b/bin/src/metrics/MetricProducer.h @@ -531,6 +531,8 @@ protected: // atom to fields in the "what" atom. std::vector mMetric2StateLinks; + optional mUploadThreshold; + SkippedBucket mCurrentSkippedBucket; // Buckets that were invalidated and had their data dropped. std::vector mSkippedBuckets; @@ -552,6 +554,7 @@ protected: FRIEND_TEST(DurationMetricE2eTest, TestWithSlicedStateMapped); FRIEND_TEST(DurationMetricE2eTest, TestSlicedStatePrimaryFieldsNotSubsetDimInWhat); FRIEND_TEST(DurationMetricE2eTest, TestWithSlicedStatePrimaryFieldsSubset); + FRIEND_TEST(DurationMetricE2eTest, TestUploadThreshold); FRIEND_TEST(MetricActivationE2eTest, TestCountMetric); FRIEND_TEST(MetricActivationE2eTest, TestCountMetricWithOneDeactivation); diff --git a/bin/src/metrics/MetricsManager.h b/bin/src/metrics/MetricsManager.h index a7b6165d..304fda19 100644 --- a/bin/src/metrics/MetricsManager.h +++ b/bin/src/metrics/MetricsManager.h @@ -368,6 +368,7 @@ private: FRIEND_TEST(DurationMetricE2eTest, TestWithSlicedStateMapped); FRIEND_TEST(DurationMetricE2eTest, TestWithSlicedStatePrimaryFieldsSuperset); FRIEND_TEST(DurationMetricE2eTest, TestWithSlicedStatePrimaryFieldsSubset); + FRIEND_TEST(DurationMetricE2eTest, TestUploadThreshold); FRIEND_TEST(ValueMetricE2eTest, TestInitialConditionChanges); FRIEND_TEST(ValueMetricE2eTest, TestPulledEvents); diff --git a/bin/src/metrics/duration_helper/DurationTracker.h b/bin/src/metrics/duration_helper/DurationTracker.h index cf1f437c..e0787302 100644 --- a/bin/src/metrics/duration_helper/DurationTracker.h +++ b/bin/src/metrics/duration_helper/DurationTracker.h @@ -111,13 +111,13 @@ public: // Flush stale buckets if needed, and return true if the tracker has no on-going duration // events, so that the owner can safely remove the tracker. virtual bool flushIfNeeded( - int64_t timestampNs, + int64_t timestampNs, const optional& uploadThreshold, std::unordered_map>* output) = 0; // Should only be called during an app upgrade or from this tracker's flushIfNeeded. If from // an app upgrade, we assume that we're trying to form a partial bucket. virtual bool flushCurrentBucket( - const int64_t& eventTimeNs, + const int64_t& eventTimeNs, const optional& uploadThreshold, std::unordered_map>* output) = 0; // Predict the anomaly timestamp given the current status. @@ -193,6 +193,31 @@ protected: mEventKey = eventKey; } + bool durationPassesThreshold(const optional& uploadThreshold, + int64_t duration) { + if (duration <= 0) { + return false; + } + + if (uploadThreshold == nullopt) { + return true; + } + + switch (uploadThreshold->value_comparison_case()) { + case UploadThreshold::kLtInt: + return duration < uploadThreshold->lt_int(); + case UploadThreshold::kGtInt: + return duration > uploadThreshold->gt_int(); + case UploadThreshold::kLteInt: + return duration <= uploadThreshold->lte_int(); + case UploadThreshold::kGteInt: + return duration >= uploadThreshold->gte_int(); + default: + ALOGE("Duration metric incorrect upload threshold type used"); + return false; + } + } + // A reference to the DurationMetricProducer's config key. const ConfigKey& mConfigKey; diff --git a/bin/src/metrics/duration_helper/MaxDurationTracker.cpp b/bin/src/metrics/duration_helper/MaxDurationTracker.cpp index 62f49824..14492633 100644 --- a/bin/src/metrics/duration_helper/MaxDurationTracker.cpp +++ b/bin/src/metrics/duration_helper/MaxDurationTracker.cpp @@ -162,7 +162,7 @@ void MaxDurationTracker::noteStopAll(const int64_t eventTime) { } bool MaxDurationTracker::flushCurrentBucket( - const int64_t& eventTimeNs, + const int64_t& eventTimeNs, const optional& uploadThreshold, std::unordered_map>* output) { VLOG("MaxDurationTracker flushing....."); @@ -192,13 +192,15 @@ bool MaxDurationTracker::flushCurrentBucket( } // mDuration is updated in noteStop to the maximum duration that ended in the current bucket. - if (mDuration != 0) { + if (durationPassesThreshold(uploadThreshold, mDuration)) { DurationBucket info; info.mBucketStartNs = mCurrentBucketStartTimeNs; info.mBucketEndNs = currentBucketEndTimeNs; info.mDuration = mDuration; (*output)[mEventKey].push_back(info); VLOG(" final duration for last bucket: %lld", (long long)mDuration); + } else { + VLOG(" duration: %lld does not pass set threshold", (long long)mDuration); } if (numBucketsForward > 0) { @@ -214,11 +216,12 @@ bool MaxDurationTracker::flushCurrentBucket( } bool MaxDurationTracker::flushIfNeeded( - int64_t eventTimeNs, unordered_map>* output) { + int64_t eventTimeNs, const optional& uploadThreshold, + unordered_map>* output) { if (eventTimeNs < getCurrentBucketEndTimeNs()) { return false; } - return flushCurrentBucket(eventTimeNs, output); + return flushCurrentBucket(eventTimeNs, uploadThreshold, output); } void MaxDurationTracker::onSlicedConditionMayChange(bool overallCondition, diff --git a/bin/src/metrics/duration_helper/MaxDurationTracker.h b/bin/src/metrics/duration_helper/MaxDurationTracker.h index be2707c6..47cfbe6f 100644 --- a/bin/src/metrics/duration_helper/MaxDurationTracker.h +++ b/bin/src/metrics/duration_helper/MaxDurationTracker.h @@ -43,10 +43,10 @@ public: void noteStopAll(const int64_t eventTime) override; bool flushIfNeeded( - int64_t timestampNs, + int64_t timestampNs, const optional& uploadThreshold, std::unordered_map>* output) override; bool flushCurrentBucket( - const int64_t& eventTimeNs, + const int64_t& eventTimeNs, const optional& uploadThreshold, std::unordered_map>*) override; void onSlicedConditionMayChange(bool overallCondition, const int64_t timestamp) override; @@ -83,6 +83,7 @@ private: FRIEND_TEST(MaxDurationTrackerTest, TestStopAll); FRIEND_TEST(MaxDurationTrackerTest, TestAnomalyDetection); FRIEND_TEST(MaxDurationTrackerTest, TestAnomalyPredictedTimestamp); + FRIEND_TEST(MaxDurationTrackerTest, TestUploadThreshold); }; } // namespace statsd diff --git a/bin/src/metrics/duration_helper/OringDurationTracker.cpp b/bin/src/metrics/duration_helper/OringDurationTracker.cpp index 247e2e01..7e2ee5cf 100644 --- a/bin/src/metrics/duration_helper/OringDurationTracker.cpp +++ b/bin/src/metrics/duration_helper/OringDurationTracker.cpp @@ -132,7 +132,7 @@ void OringDurationTracker::noteStopAll(const int64_t timestamp) { } bool OringDurationTracker::flushCurrentBucket( - const int64_t& eventTimeNs, + const int64_t& eventTimeNs, const optional& uploadThreshold, std::unordered_map>* output) { VLOG("OringDurationTracker Flushing............."); @@ -163,7 +163,7 @@ bool OringDurationTracker::flushCurrentBucket( // store durations for each stateKey, so we need to flush the bucket by creating a // DurationBucket for each stateKey. for (auto& durationIt : mStateKeyDurationMap) { - if (durationIt.second.mDuration > 0) { + if (durationPassesThreshold(uploadThreshold, durationIt.second.mDuration)) { DurationBucket current_info; current_info.mBucketStartNs = mCurrentBucketStartTimeNs; current_info.mBucketEndNs = currentBucketEndTimeNs; @@ -173,6 +173,8 @@ bool OringDurationTracker::flushCurrentBucket( durationIt.second.mDurationFullBucket += durationIt.second.mDuration; VLOG(" duration: %lld", (long long)current_info.mDuration); + } else { + VLOG(" duration: %lld does not pass set threshold", (long long)mDuration); } if (eventTimeNs > fullBucketEnd) { @@ -217,11 +219,12 @@ bool OringDurationTracker::flushCurrentBucket( } bool OringDurationTracker::flushIfNeeded( - int64_t eventTimeNs, unordered_map>* output) { + int64_t eventTimeNs, const optional& uploadThreshold, + unordered_map>* output) { if (eventTimeNs < getCurrentBucketEndTimeNs()) { return false; } - return flushCurrentBucket(eventTimeNs, output); + return flushCurrentBucket(eventTimeNs, uploadThreshold, output); } void OringDurationTracker::onSlicedConditionMayChange(bool overallCondition, diff --git a/bin/src/metrics/duration_helper/OringDurationTracker.h b/bin/src/metrics/duration_helper/OringDurationTracker.h index 6eddee7d..ffe65ed2 100644 --- a/bin/src/metrics/duration_helper/OringDurationTracker.h +++ b/bin/src/metrics/duration_helper/OringDurationTracker.h @@ -48,10 +48,10 @@ public: const FieldValue& newState) override; bool flushCurrentBucket( - const int64_t& eventTimeNs, + const int64_t& eventTimeNs, const optional& uploadThreshold, std::unordered_map>* output) override; bool flushIfNeeded( - int64_t timestampNs, + int64_t timestampNs, const optional& uploadThreshold, std::unordered_map>* output) override; int64_t predictAnomalyTimestampNs(const AnomalyTracker& anomalyTracker, @@ -84,6 +84,7 @@ private: FRIEND_TEST(OringDurationTrackerTest, TestPredictAnomalyTimestamp); FRIEND_TEST(OringDurationTrackerTest, TestAnomalyDetectionExpiredAlarm); FRIEND_TEST(OringDurationTrackerTest, TestAnomalyDetectionFiredAlarm); + FRIEND_TEST(OringDurationTrackerTest, TestUploadThreshold); }; } // namespace statsd diff --git a/bin/src/metrics/parsing_utils/metrics_manager_util.cpp b/bin/src/metrics/parsing_utils/metrics_manager_util.cpp index bfa409cb..73e760a3 100644 --- a/bin/src/metrics/parsing_utils/metrics_manager_util.cpp +++ b/bin/src/metrics/parsing_utils/metrics_manager_util.cpp @@ -538,6 +538,20 @@ optional> createDurationMetricProducerAndUpdateMetadata( return nullopt; } + if (metric.has_threshold()) { + switch (metric.threshold().value_comparison_case()) { + case UploadThreshold::kLtInt: + case UploadThreshold::kGtInt: + case UploadThreshold::kLteInt: + case UploadThreshold::kGteInt: + break; + default: + ALOGE("Duration metric incorrect upload threshold type or no type used"); + return nullopt; + break; + } + } + sp producer = new DurationMetricProducer( key, metric, conditionIndex, initialConditionCache, whatIndex, startIndex, stopIndex, stopAllIndex, nesting, wizard, metricHash, internalDimensions, timeBaseNs, diff --git a/bin/src/statsd_config.proto b/bin/src/statsd_config.proto index acdffd3d..53bcba29 100644 --- a/bin/src/statsd_config.proto +++ b/bin/src/statsd_config.proto @@ -190,6 +190,17 @@ message FieldFilter { optional FieldMatcher fields = 2; } +message UploadThreshold { + oneof value_comparison { + int64 lt_int = 1; + int64 gt_int = 2; + float lt_float = 3; + float gt_float = 4; + int64 lte_int = 5; + int64 gte_int = 6; + } +} + message EventMetric { optional int64 id = 1; @@ -250,6 +261,8 @@ message DurationMetric { optional TimeUnit bucket = 7; + optional UploadThreshold threshold = 11; + optional FieldMatcher dimensions_in_condition = 8 [deprecated = true]; reserved 100; diff --git a/bin/tests/e2e/DurationMetric_e2e_test.cpp b/bin/tests/e2e/DurationMetric_e2e_test.cpp index 2473c1ca..8a4c9627 100644 --- a/bin/tests/e2e/DurationMetric_e2e_test.cpp +++ b/bin/tests/e2e/DurationMetric_e2e_test.cpp @@ -1467,6 +1467,101 @@ TEST(DurationMetricE2eTest, TestWithSlicedStatePrimaryFieldsSubset) { EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(0).end_bucket_elapsed_nanos()); } +TEST(DurationMetricE2eTest, TestUploadThreshold) { + StatsdConfig config; + config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root. + + auto screenOnMatcher = CreateScreenTurnedOnAtomMatcher(); + auto screenOffMatcher = CreateScreenTurnedOffAtomMatcher(); + *config.add_atom_matcher() = screenOnMatcher; + *config.add_atom_matcher() = screenOffMatcher; + + auto durationPredicate = CreateScreenIsOnPredicate(); + *config.add_predicate() = durationPredicate; + + int64_t thresholdDurationNs = 30 * 1000 * 1000 * 1000LL; // 30 seconds + UploadThreshold threshold; + threshold.set_gt_int(thresholdDurationNs); + + int64_t metricId = 123456; + auto durationMetric = config.add_duration_metric(); + durationMetric->set_id(metricId); + durationMetric->set_what(durationPredicate.id()); + durationMetric->set_bucket(FIVE_MINUTES); + durationMetric->set_aggregation_type(DurationMetric_AggregationType_SUM); + *durationMetric->mutable_threshold() = threshold; + + const int64_t baseTimeNs = 0; // 0:00 + const int64_t configAddedTimeNs = baseTimeNs + 1 * NS_PER_SEC; // 0:01 + const int64_t bucketSizeNs = + TimeUnitToBucketSizeInMillis(config.duration_metric(0).bucket()) * 1000LL * 1000LL; + + int uid = 12345; + int64_t cfgId = 98765; + ConfigKey cfgKey(uid, cfgId); + + auto processor = CreateStatsLogProcessor(baseTimeNs, configAddedTimeNs, config, cfgKey); + + ASSERT_EQ(processor->mMetricsManagers.size(), 1u); + sp metricsManager = processor->mMetricsManagers.begin()->second; + EXPECT_TRUE(metricsManager->isConfigValid()); + ASSERT_EQ(metricsManager->mAllMetricProducers.size(), 1); + sp metricProducer = metricsManager->mAllMetricProducers[0]; + EXPECT_TRUE(metricsManager->isActive()); + EXPECT_TRUE(metricProducer->mIsActive); + + std::unique_ptr event; + + // Screen is off at start of first bucket. + event = CreateScreenStateChangedEvent(configAddedTimeNs, + android::view::DISPLAY_STATE_OFF); // 0:01 + processor->OnLogEvent(event.get()); + + // Turn screen on. + const int64_t durationStartNs = configAddedTimeNs + 10 * NS_PER_SEC; // 0:11 + event = CreateScreenStateChangedEvent(durationStartNs, android::view::DISPLAY_STATE_ON); + processor->OnLogEvent(event.get()); + + // Turn off screen 30 seconds after turning on. + const int64_t durationEndNs = durationStartNs + 30 * NS_PER_SEC; // 0:41 + event = CreateScreenStateChangedEvent(durationEndNs, android::view::DISPLAY_STATE_OFF); + processor->OnLogEvent(event.get()); + + // Turn screen on in second bucket. + const int64_t duration2StartNs = configAddedTimeNs + bucketSizeNs + 10 * NS_PER_SEC; // 5:11 + event = CreateScreenStateChangedEvent(duration2StartNs, android::view::DISPLAY_STATE_ON); + processor->OnLogEvent(event.get()); + + // Turn off screen 31 seconds after turning on. + const int64_t duration2EndNs = duration2StartNs + 31 * NS_PER_SEC; // 5:42 + event = CreateScreenStateChangedEvent(duration2EndNs, android::view::DISPLAY_STATE_OFF); + processor->OnLogEvent(event.get()); + + ConfigMetricsReportList reports; + vector buffer; + processor->onDumpReport(cfgKey, configAddedTimeNs + bucketSizeNs * 2 + 1 * NS_PER_SEC, false, + true, ADB_DUMP, FAST, &buffer); // 10:01 + EXPECT_TRUE(buffer.size() > 0); + EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size())); + backfillDimensionPath(&reports); + backfillStartEndTimestamp(&reports); + ASSERT_EQ(1, reports.reports_size()); + ASSERT_EQ(1, reports.reports(0).metrics_size()); + EXPECT_EQ(metricId, reports.reports(0).metrics(0).metric_id()); + EXPECT_TRUE(reports.reports(0).metrics(0).has_duration_metrics()); + + StatsLogReport::DurationMetricDataWrapper durationMetrics; + sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).duration_metrics(), + &durationMetrics); + ASSERT_EQ(1, durationMetrics.data_size()); + + DurationMetricData data = durationMetrics.data(0); + ASSERT_EQ(1, data.bucket_info_size()); + EXPECT_EQ(duration2EndNs - duration2StartNs, data.bucket_info(0).duration_nanos()); + EXPECT_EQ(baseTimeNs + bucketSizeNs, data.bucket_info(0).start_bucket_elapsed_nanos()); + EXPECT_EQ(baseTimeNs + bucketSizeNs * 2, data.bucket_info(0).end_bucket_elapsed_nanos()); +} + #else GTEST_LOG_(INFO) << "This test does nothing.\n"; #endif diff --git a/bin/tests/metrics/MaxDurationTracker_test.cpp b/bin/tests/metrics/MaxDurationTracker_test.cpp index fda3daaa..25eaf887 100644 --- a/bin/tests/metrics/MaxDurationTracker_test.cpp +++ b/bin/tests/metrics/MaxDurationTracker_test.cpp @@ -40,8 +40,10 @@ namespace statsd { const ConfigKey kConfigKey(0, 12345); const int TagId = 1; +const int64_t metricId = 123; +const optional emptyThreshold; -const HashableDimensionKey eventKey = getMockedDimensionKey(TagId, 0, "1"); +const MetricDimensionKey eventKey = getMockedMetricDimensionKey(TagId, 0, "1"); const HashableDimensionKey conditionKey = getMockedDimensionKey(TagId, 4, "1"); const HashableDimensionKey key1 = getMockedDimensionKey(TagId, 1, "1"); const HashableDimensionKey key2 = getMockedDimensionKey(TagId, 1, "2"); @@ -75,7 +77,7 @@ TEST(MaxDurationTrackerTest, TestSimpleMaxDuration) { tracker.noteStart(key2, true, bucketStartTimeNs + 20, ConditionKey()); tracker.noteStop(key2, bucketStartTimeNs + 40, false /*stop all*/); - tracker.flushIfNeeded(bucketStartTimeNs + bucketSizeNs + 1, &buckets); + tracker.flushIfNeeded(bucketStartTimeNs + bucketSizeNs + 1, emptyThreshold, &buckets); EXPECT_TRUE(buckets.find(eventKey) != buckets.end()); ASSERT_EQ(1u, buckets[eventKey].size()); EXPECT_EQ(20LL, buckets[eventKey][0].mDuration); @@ -103,12 +105,12 @@ TEST(MaxDurationTrackerTest, TestStopAll) { // Another event starts in this bucket. tracker.noteStart(key2, true, bucketStartTimeNs + 20, ConditionKey()); - tracker.flushIfNeeded(bucketStartTimeNs + bucketSizeNs + 40, &buckets); + tracker.flushIfNeeded(bucketStartTimeNs + bucketSizeNs + 40, emptyThreshold, &buckets); tracker.noteStopAll(bucketStartTimeNs + bucketSizeNs + 40); EXPECT_TRUE(tracker.mInfos.empty()); EXPECT_TRUE(buckets.find(eventKey) == buckets.end()); - tracker.flushIfNeeded(bucketStartTimeNs + 3 * bucketSizeNs + 40, &buckets); + tracker.flushIfNeeded(bucketStartTimeNs + 3 * bucketSizeNs + 40, emptyThreshold, &buckets); EXPECT_TRUE(buckets.find(eventKey) != buckets.end()); ASSERT_EQ(1u, buckets[eventKey].size()); EXPECT_EQ(bucketSizeNs + 40 - 1, buckets[eventKey][0].mDuration); @@ -143,12 +145,12 @@ TEST(MaxDurationTrackerTest, TestCrossBucketBoundary) { // The event stops at early 4th bucket. // Notestop is called from DurationMetricProducer's onMatchedLogEvent, which calls // flushIfneeded. - tracker.flushIfNeeded(bucketStartTimeNs + (3 * bucketSizeNs) + 20, &buckets); + tracker.flushIfNeeded(bucketStartTimeNs + (3 * bucketSizeNs) + 20, emptyThreshold, &buckets); tracker.noteStop(DEFAULT_DIMENSION_KEY, bucketStartTimeNs + (3 * bucketSizeNs) + 20, false /*stop all*/); EXPECT_TRUE(buckets.find(eventKey) == buckets.end()); - tracker.flushIfNeeded(bucketStartTimeNs + 4 * bucketSizeNs, &buckets); + tracker.flushIfNeeded(bucketStartTimeNs + 4 * bucketSizeNs, emptyThreshold, &buckets); ASSERT_EQ(1u, buckets[eventKey].size()); EXPECT_EQ((3 * bucketSizeNs) + 20 - 1, buckets[eventKey][0].mDuration); EXPECT_EQ(bucketStartTimeNs + 3 * bucketSizeNs, buckets[eventKey][0].mBucketStartNs); @@ -178,14 +180,14 @@ TEST(MaxDurationTrackerTest, TestCrossBucketBoundary_nested) { // one stop tracker.noteStop(DEFAULT_DIMENSION_KEY, bucketStartTimeNs + 20, false /*stop all*/); - tracker.flushIfNeeded(bucketStartTimeNs + (2 * bucketSizeNs) + 1, &buckets); + tracker.flushIfNeeded(bucketStartTimeNs + (2 * bucketSizeNs) + 1, emptyThreshold, &buckets); // Because of nesting, still not stopped. EXPECT_TRUE(buckets.find(eventKey) == buckets.end()); // real stop now. tracker.noteStop(DEFAULT_DIMENSION_KEY, bucketStartTimeNs + (2 * bucketSizeNs) + 5, false); - tracker.flushIfNeeded(bucketStartTimeNs + (3 * bucketSizeNs) + 1, &buckets); + tracker.flushIfNeeded(bucketStartTimeNs + (3 * bucketSizeNs) + 1, emptyThreshold, &buckets); ASSERT_EQ(1u, buckets[eventKey].size()); EXPECT_EQ(2 * bucketSizeNs + 5 - 1, buckets[eventKey][0].mDuration); @@ -222,13 +224,13 @@ TEST(MaxDurationTrackerTest, TestMaxDurationWithCondition) { tracker.noteConditionChanged(key1, true, conditionStarts1); tracker.noteConditionChanged(key1, false, conditionStops1); unordered_map> buckets; - tracker.flushIfNeeded(bucketStartTimeNs + bucketSizeNs + 1, &buckets); + tracker.flushIfNeeded(bucketStartTimeNs + bucketSizeNs + 1, emptyThreshold, &buckets); ASSERT_EQ(0U, buckets.size()); tracker.noteConditionChanged(key1, true, conditionStarts2); tracker.noteConditionChanged(key1, false, conditionStops2); tracker.noteStop(key1, eventStopTimeNs, false); - tracker.flushIfNeeded(bucketStartTimeNs + 2 * bucketSizeNs + 1, &buckets); + tracker.flushIfNeeded(bucketStartTimeNs + 2 * bucketSizeNs + 1, emptyThreshold, &buckets); ASSERT_EQ(1U, buckets.size()); vector item = buckets.begin()->second; ASSERT_EQ(1UL, item.size()); @@ -416,6 +418,39 @@ TEST(MaxDurationTrackerTest, TestAnomalyPredictedTimestamp_UpdatedOnStop) { (unsigned long long)(alarm->timestampSec * NS_PER_SEC)); } +TEST(MaxDurationTrackerTest, TestUploadThreshold) { + sp wizard = new NaggyMock(); + + unordered_map> buckets; + + int64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL; + int64_t bucketStartTimeNs = 10000000000; + int64_t bucketNum = 0; + int64_t eventStartTimeNs = bucketStartTimeNs + 1; + int64_t event2StartTimeNs = bucketStartTimeNs + bucketSizeNs + 1; + int64_t thresholdDurationNs = 2000; + + UploadThreshold threshold; + threshold.set_gt_int(thresholdDurationNs); + + MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, false, bucketStartTimeNs, + bucketNum, bucketStartTimeNs, bucketSizeNs, false, false, {}); + + // Duration below the gt_int threshold should not be added to past buckets. + tracker.noteStart(key1, true, eventStartTimeNs, ConditionKey()); + tracker.noteStop(key1, eventStartTimeNs + thresholdDurationNs, false); + tracker.flushIfNeeded(eventStartTimeNs + bucketSizeNs + 1, threshold, &buckets); + EXPECT_TRUE(buckets.find(eventKey) == buckets.end()); + + // Duration above the gt_int threshold should be added to past buckets. + tracker.noteStart(key1, true, event2StartTimeNs, ConditionKey()); + tracker.noteStop(key1, event2StartTimeNs + thresholdDurationNs + 1, false); + tracker.flushIfNeeded(event2StartTimeNs + bucketSizeNs + 1, threshold, &buckets); + EXPECT_TRUE(buckets.find(eventKey) != buckets.end()); + ASSERT_EQ(1u, buckets[eventKey].size()); + EXPECT_EQ(thresholdDurationNs + 1, buckets[eventKey][0].mDuration); +} + } // namespace statsd } // namespace os } // namespace android diff --git a/bin/tests/metrics/OringDurationTracker_test.cpp b/bin/tests/metrics/OringDurationTracker_test.cpp index 1d6f7de5..369f4738 100644 --- a/bin/tests/metrics/OringDurationTracker_test.cpp +++ b/bin/tests/metrics/OringDurationTracker_test.cpp @@ -39,7 +39,8 @@ namespace statsd { const ConfigKey kConfigKey(0, 12345); const int TagId = 1; const int64_t metricId = 123; -const HashableDimensionKey eventKey = getMockedDimensionKey(TagId, 0, "event"); +const optional emptyThreshold; +const MetricDimensionKey eventKey = getMockedMetricDimensionKey(TagId, 0, "event"); const HashableDimensionKey kConditionKey1 = getMockedDimensionKey(TagId, 1, "maps"); const HashableDimensionKey kEventKey1 = getMockedDimensionKey(TagId, 2, "maps"); @@ -71,7 +72,7 @@ TEST(OringDurationTrackerTest, TestDurationOverlap) { EXPECT_EQ((long long)eventStartTimeNs, tracker.mLastStartTime); tracker.noteStop(kEventKey1, eventStartTimeNs + durationTimeNs, false); - tracker.flushIfNeeded(eventStartTimeNs + bucketSizeNs + 1, &buckets); + tracker.flushIfNeeded(eventStartTimeNs + bucketSizeNs + 1, emptyThreshold, &buckets); EXPECT_TRUE(buckets.find(eventKey) != buckets.end()); ASSERT_EQ(1u, buckets[eventKey].size()); @@ -101,7 +102,7 @@ TEST(OringDurationTrackerTest, TestDurationNested) { tracker.noteStop(kEventKey1, eventStartTimeNs + 2000, false); tracker.noteStop(kEventKey1, eventStartTimeNs + 2003, false); - tracker.flushIfNeeded(bucketStartTimeNs + bucketSizeNs + 1, &buckets); + tracker.flushIfNeeded(bucketStartTimeNs + bucketSizeNs + 1, emptyThreshold, &buckets); EXPECT_TRUE(buckets.find(eventKey) != buckets.end()); ASSERT_EQ(1u, buckets[eventKey].size()); EXPECT_EQ(2003LL, buckets[eventKey][0].mDuration); @@ -131,7 +132,7 @@ TEST(OringDurationTrackerTest, TestStopAll) { tracker.noteStopAll(eventStartTimeNs + 2003); - tracker.flushIfNeeded(bucketStartTimeNs + bucketSizeNs + 1, &buckets); + tracker.flushIfNeeded(bucketStartTimeNs + bucketSizeNs + 1, emptyThreshold, &buckets); EXPECT_TRUE(buckets.find(eventKey) != buckets.end()); ASSERT_EQ(1u, buckets[eventKey].size()); EXPECT_EQ(2003LL, buckets[eventKey][0].mDuration); @@ -157,7 +158,7 @@ TEST(OringDurationTrackerTest, TestCrossBucketBoundary) { tracker.noteStart(kEventKey1, true, eventStartTimeNs, ConditionKey()); EXPECT_EQ((long long)eventStartTimeNs, tracker.mLastStartTime); - tracker.flushIfNeeded(eventStartTimeNs + 2 * bucketSizeNs, &buckets); + tracker.flushIfNeeded(eventStartTimeNs + 2 * bucketSizeNs, emptyThreshold, &buckets); tracker.noteStart(kEventKey1, true, eventStartTimeNs + 2 * bucketSizeNs, ConditionKey()); EXPECT_EQ((long long)(bucketStartTimeNs + 2 * bucketSizeNs), tracker.mLastStartTime); @@ -167,7 +168,7 @@ TEST(OringDurationTrackerTest, TestCrossBucketBoundary) { tracker.noteStop(kEventKey1, eventStartTimeNs + 2 * bucketSizeNs + 10, false); tracker.noteStop(kEventKey1, eventStartTimeNs + 2 * bucketSizeNs + 12, false); - tracker.flushIfNeeded(eventStartTimeNs + 2 * bucketSizeNs + 12, &buckets); + tracker.flushIfNeeded(eventStartTimeNs + 2 * bucketSizeNs + 12, emptyThreshold, &buckets); EXPECT_TRUE(buckets.find(eventKey) != buckets.end()); ASSERT_EQ(2u, buckets[eventKey].size()); EXPECT_EQ(bucketSizeNs - 1, buckets[eventKey][0].mDuration); @@ -205,7 +206,7 @@ TEST(OringDurationTrackerTest, TestDurationConditionChange) { tracker.noteStop(kEventKey1, eventStartTimeNs + durationTimeNs, false); - tracker.flushIfNeeded(bucketStartTimeNs + bucketSizeNs + 1, &buckets); + tracker.flushIfNeeded(bucketStartTimeNs + bucketSizeNs + 1, emptyThreshold, &buckets); EXPECT_TRUE(buckets.find(eventKey) != buckets.end()); ASSERT_EQ(1u, buckets[eventKey].size()); EXPECT_EQ(5LL, buckets[eventKey][0].mDuration); @@ -246,7 +247,7 @@ TEST(OringDurationTrackerTest, TestDurationConditionChange2) { // 2nd duration: 1000ns tracker.noteStop(kEventKey1, eventStartTimeNs + durationTimeNs, false); - tracker.flushIfNeeded(bucketStartTimeNs + bucketSizeNs + 1, &buckets); + tracker.flushIfNeeded(bucketStartTimeNs + bucketSizeNs + 1, emptyThreshold, &buckets); EXPECT_TRUE(buckets.find(eventKey) != buckets.end()); ASSERT_EQ(1u, buckets[eventKey].size()); EXPECT_EQ(1005LL, buckets[eventKey][0].mDuration); @@ -284,7 +285,7 @@ TEST(OringDurationTrackerTest, TestDurationConditionChangeNested) { tracker.noteStop(kEventKey1, eventStartTimeNs + 2003, false); - tracker.flushIfNeeded(bucketStartTimeNs + bucketSizeNs + 1, &buckets); + tracker.flushIfNeeded(bucketStartTimeNs + bucketSizeNs + 1, emptyThreshold, &buckets); EXPECT_TRUE(buckets.find(eventKey) != buckets.end()); ASSERT_EQ(1u, buckets[eventKey].size()); EXPECT_EQ(15LL, buckets[eventKey][0].mDuration); @@ -331,7 +332,7 @@ TEST(OringDurationTrackerTest, TestPredictAnomalyTimestamp) { tracker.predictAnomalyTimestampNs(*anomalyTracker, event1StartTimeNs)); int64_t event1StopTimeNs = eventStartTimeNs + bucketSizeNs + 10; - tracker.flushIfNeeded(event1StopTimeNs, &buckets); + tracker.flushIfNeeded(event1StopTimeNs, emptyThreshold, &buckets); tracker.noteStop(kEventKey1, event1StopTimeNs, false); EXPECT_TRUE(buckets.find(eventKey) != buckets.end()); @@ -495,7 +496,7 @@ TEST(OringDurationTrackerTest, TestAnomalyDetectionExpiredAlarm) { // The alarm is set to fire at 52s, and when it does, an anomaly would be declared. However, // because this is a unit test, the alarm won't actually fire at all. Since the alarm fails // to fire in time, the anomaly is instead caught when noteStop is called, at around 71s. - tracker.flushIfNeeded(eventStartTimeNs + 2 * bucketSizeNs + 25, &buckets); + tracker.flushIfNeeded(eventStartTimeNs + 2 * bucketSizeNs + 25, emptyThreshold, &buckets); tracker.noteStop(kEventKey1, eventStartTimeNs + 2 * bucketSizeNs + 25, false); EXPECT_EQ(anomalyTracker->getSumOverPastBuckets(eventKey), (long long)(bucketSizeNs)); EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(eventKey), @@ -568,6 +569,40 @@ TEST(OringDurationTrackerTest, TestAnomalyDetectionFiredAlarm) { EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(eventKey), 62U + refPeriodSec); } +TEST(OringDurationTrackerTest, TestUploadThreshold) { + sp wizard = new NaggyMock(); + + unordered_map> buckets; + + int64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL; + int64_t bucketStartTimeNs = 10000000000; + int64_t bucketNum = 0; + int64_t eventStartTimeNs = bucketStartTimeNs + 1; + int64_t event2StartTimeNs = bucketStartTimeNs + bucketSizeNs + 1; + int64_t thresholdDurationNs = 2000; + + UploadThreshold threshold; + threshold.set_gt_int(thresholdDurationNs); + + OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, false, + bucketStartTimeNs, bucketNum, bucketStartTimeNs, bucketSizeNs, + false, false, {}); + + // Duration below the gt_int threshold should not be added to past buckets. + tracker.noteStart(kEventKey1, true, eventStartTimeNs, ConditionKey()); + tracker.noteStop(kEventKey1, eventStartTimeNs + thresholdDurationNs, false); + tracker.flushIfNeeded(eventStartTimeNs + bucketSizeNs + 1, threshold, &buckets); + EXPECT_TRUE(buckets.find(eventKey) == buckets.end()); + + // Duration above the gt_int threshold should be added to past buckets. + tracker.noteStart(kEventKey1, true, event2StartTimeNs, ConditionKey()); + tracker.noteStop(kEventKey1, event2StartTimeNs + thresholdDurationNs + 1, false); + tracker.flushIfNeeded(event2StartTimeNs + bucketSizeNs + 1, threshold, &buckets); + EXPECT_TRUE(buckets.find(eventKey) != buckets.end()); + ASSERT_EQ(1u, buckets[eventKey].size()); + EXPECT_EQ(thresholdDurationNs + 1, buckets[eventKey][0].mDuration); +} + } // namespace statsd } // namespace os } // namespace android -- cgit v1.2.3 From 8478e538b104c639e9860dd288bb7635743bedf2 Mon Sep 17 00:00:00 2001 From: Tej Singh Date: Sat, 20 Feb 2021 02:56:56 -0800 Subject: Subscriber reporter broadcast death tests Make sure the intnt map is updated appropriately and refcounts are correct. Test: atest statsd_test Bug: 180471118 Change-Id: I4cd282259d1eaa28da5e35dd51145d74edc568e7 Merged-In: Ibb065d64be3775c699583215f7f376a1a5492ac1 --- bin/Android.bp | 1 + bin/src/subscriber/SubscriberReporter.h | 15 ++-- bin/tests/subscriber/SubscriberReporter_test.cpp | 107 +++++++++++++++++++++++ 3 files changed, 118 insertions(+), 5 deletions(-) create mode 100644 bin/tests/subscriber/SubscriberReporter_test.cpp diff --git a/bin/Android.bp b/bin/Android.bp index 0e4acaa2..2f18d514 100644 --- a/bin/Android.bp +++ b/bin/Android.bp @@ -295,6 +295,7 @@ cc_test { "tests/metrics/ValueMetricProducer_test.cpp", "tests/metrics/parsing_utils/config_update_utils_test.cpp", "tests/metrics/parsing_utils/metrics_manager_util_test.cpp", + "tests/subscriber/SubscriberReporter_test.cpp", "tests/MetricsManager_test.cpp", "tests/shell/ShellSubscriber_test.cpp", "tests/state/StateTracker_test.cpp", diff --git a/bin/src/subscriber/SubscriberReporter.h b/bin/src/subscriber/SubscriberReporter.h index 4946fea4..6d7d2cb3 100644 --- a/bin/src/subscriber/SubscriberReporter.h +++ b/bin/src/subscriber/SubscriberReporter.h @@ -15,19 +15,19 @@ */ #pragma once - #include +#include #include #include -#include "config/ConfigKey.h" -#include "packages/modules/StatsD/bin/src/statsd_config.pb.h" // subscription -#include "HashableDimensionKey.h" - #include #include #include +#include "HashableDimensionKey.h" +#include "config/ConfigKey.h" +#include "packages/modules/StatsD/bin/src/statsd_config.pb.h" // subscription + using aidl::android::os::IPendingIntentRef; using std::mutex; using std::shared_ptr; @@ -100,6 +100,11 @@ private: * which binder has died and must not be dereferenced. */ static void broadcastSubscriberDied(void* cookie); + + friend class SubscriberReporterTest; + FRIEND_TEST(SubscriberReporterTest, TestBroadcastSubscriberDeathRemovesPir); + FRIEND_TEST(SubscriberReporterTest, TestBroadcastSubscriberDeathRemovesPirAndConfigKey); + FRIEND_TEST(SubscriberReporterTest, TestBroadcastSubscriberDeathKeepsReplacedPir); }; } // namespace statsd diff --git a/bin/tests/subscriber/SubscriberReporter_test.cpp b/bin/tests/subscriber/SubscriberReporter_test.cpp new file mode 100644 index 00000000..c6a888ff --- /dev/null +++ b/bin/tests/subscriber/SubscriberReporter_test.cpp @@ -0,0 +1,107 @@ +// Copyright (C) 2020 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 "subscriber/SubscriberReporter.h" + +#include +#include +#include + +#include "tests/statsd_test_util.h" + +using namespace testing; +using ::ndk::SharedRefBase; +using std::unordered_map; + +#ifdef __ANDROID__ + +namespace android { +namespace os { +namespace statsd { + +namespace { + +const ConfigKey configKey1(0, 12345); +const ConfigKey configKey2(0, 123456); +const int64_t subscriptionId1 = 1, subscriptionId2 = 2; + +} // anonymous namespace + +class SubscriberReporterTest : public ::testing::Test { +public: + SubscriberReporterTest() { + } + const shared_ptr pir1 = + SharedRefBase::make>(); + const shared_ptr pir2 = + SharedRefBase::make>(); + const shared_ptr pir3 = + SharedRefBase::make>(); + // Two subscribers on config key 1, one on config key 2. + void SetUp() override { + SubscriberReporter::getInstance().setBroadcastSubscriber(configKey1, subscriptionId1, pir1); + SubscriberReporter::getInstance().setBroadcastSubscriber(configKey1, subscriptionId2, pir2); + SubscriberReporter::getInstance().setBroadcastSubscriber(configKey2, subscriptionId1, pir3); + } + + void TearDown() override { + SubscriberReporter::getInstance(); + SubscriberReporter::getInstance().unsetBroadcastSubscriber(configKey1, subscriptionId1); + SubscriberReporter::getInstance().unsetBroadcastSubscriber(configKey1, subscriptionId2); + SubscriberReporter::getInstance().unsetBroadcastSubscriber(configKey2, subscriptionId1); + EXPECT_EQ(pir1.use_count(), 1); + EXPECT_EQ(pir2.use_count(), 1); + EXPECT_EQ(pir3.use_count(), 1); + ASSERT_TRUE(SubscriberReporter::getInstance().mIntentMap.empty()); + } +}; + +TEST_F(SubscriberReporterTest, TestBroadcastSubscriberDeathRemovesPir) { + SubscriberReporter::broadcastSubscriberDied(pir1.get()); + EXPECT_EQ(pir1.use_count(), 1); + + unordered_map>> + expectedIntentMap = {{configKey1, {{subscriptionId2, pir2}}}, + {configKey2, {{subscriptionId1, pir3}}}}; + EXPECT_THAT(SubscriberReporter::getInstance().mIntentMap, ContainerEq(expectedIntentMap)); +} + +TEST_F(SubscriberReporterTest, TestBroadcastSubscriberDeathRemovesPirAndConfigKey) { + SubscriberReporter::broadcastSubscriberDied(pir3.get()); + + EXPECT_EQ(pir3.use_count(), 1); + unordered_map>> + expectedIntentMap = {{configKey1, {{subscriptionId1, pir1}, {subscriptionId2, pir2}}}}; + EXPECT_THAT(SubscriberReporter::getInstance().mIntentMap, ContainerEq(expectedIntentMap)); +} + +TEST_F(SubscriberReporterTest, TestBroadcastSubscriberDeathKeepsReplacedPir) { + const shared_ptr pir4 = + SharedRefBase::make>(); + SubscriberReporter::getInstance().setBroadcastSubscriber(configKey1, subscriptionId1, pir4); + + // pir1 dies, but pir4 has replaced it with the same keys. The map should remain the same. + SubscriberReporter::broadcastSubscriberDied(pir1.get()); + + unordered_map>> + expectedIntentMap = {{configKey1, {{subscriptionId1, pir4}, {subscriptionId2, pir2}}}, + {configKey2, {{subscriptionId1, pir3}}}}; + EXPECT_THAT(SubscriberReporter::getInstance().mIntentMap, ContainerEq(expectedIntentMap)); +} +} // namespace statsd +} // namespace os +} // namespace android +#else +GTEST_LOG_(INFO) << "This test does nothing.\n"; +#endif -- cgit v1.2.3 From 24c7c1a399ce2b51c865b1d0f680c08b888c7ec3 Mon Sep 17 00:00:00 2001 From: Tej Singh Date: Fri, 19 Feb 2021 18:43:19 -0800 Subject: Duration Metric Anomaly Config Update Tests Test preserving w/ history & refractory periods and new alert based on existing metrics, as well as removing. There are a couple of outstanding issues on deleting durationtrackers before the full bucket ends, as well as kicking off alarms Test: atest statsd_test Bug: 174976680 Change-Id: Ibbd5fc818be8f8dab372e95c73b45fbb66ca7fc3 Merged-In: Ibb065d64be3775c699583215f7f376a1a5492ac1 --- bin/src/StatsLogProcessor.h | 1 + bin/src/StatsService.h | 2 + bin/tests/e2e/ConfigUpdate_e2e_test.cpp | 282 ++++++++++++++++++++++++++++++++ 3 files changed, 285 insertions(+) diff --git a/bin/src/StatsLogProcessor.h b/bin/src/StatsLogProcessor.h index 1dc9d8e5..35775633 100644 --- a/bin/src/StatsLogProcessor.h +++ b/bin/src/StatsLogProcessor.h @@ -340,6 +340,7 @@ private: FRIEND_TEST(ConfigUpdateE2eTest, TestGaugeMetric); FRIEND_TEST(ConfigUpdateE2eTest, TestValueMetric); + FRIEND_TEST(ConfigUpdateE2eTest, TestAnomalyDurationMetric); FRIEND_TEST(ConfigUpdateE2eAbTest, TestHashStrings); FRIEND_TEST(ConfigUpdateE2eAbTest, TestUidMapVersionStringInstaller); FRIEND_TEST(ConfigUpdateE2eAbTest, TestConfigTtl); diff --git a/bin/src/StatsService.h b/bin/src/StatsService.h index 6ec6f715..8efbd981 100644 --- a/bin/src/StatsService.h +++ b/bin/src/StatsService.h @@ -405,6 +405,8 @@ private: FRIEND_TEST(PartialBucketE2eTest, TestGaugeMetricWithoutMinPartialBucket); FRIEND_TEST(PartialBucketE2eTest, TestGaugeMetricWithMinPartialBucket); + FRIEND_TEST(ConfigUpdateE2eTest, TestAnomalyDurationMetric); + FRIEND_TEST(AnomalyDetectionE2eTest, TestDurationMetric_SUM_single_bucket); FRIEND_TEST(AnomalyDetectionE2eTest, TestDurationMetric_SUM_multiple_buckets); FRIEND_TEST(AnomalyDetectionE2eTest, TestDurationMetric_SUM_long_refractory_period); diff --git a/bin/tests/e2e/ConfigUpdate_e2e_test.cpp b/bin/tests/e2e/ConfigUpdate_e2e_test.cpp index d8f36ef1..7e0a65c6 100644 --- a/bin/tests/e2e/ConfigUpdate_e2e_test.cpp +++ b/bin/tests/e2e/ConfigUpdate_e2e_test.cpp @@ -22,6 +22,7 @@ #include "flags/flags.h" #include "src/StatsLogProcessor.h" +#include "src/StatsService.h" #include "src/storage/StorageManager.h" #include "src/subscriber/SubscriberReporter.h" #include "tests/statsd_test_util.h" @@ -1870,6 +1871,287 @@ TEST_F(ConfigUpdateE2eTest, TestAnomalyCountMetric) { SubscriberReporter::getInstance().unsetBroadcastSubscriber(key, newSubId); } +TEST_F(ConfigUpdateE2eTest, TestAnomalyDurationMetric) { + StatsdConfig config; + config.add_allowed_log_source("AID_ROOT"); + + AtomMatcher wakelockAcquireMatcher = CreateAcquireWakelockAtomMatcher(); + *config.add_atom_matcher() = wakelockAcquireMatcher; + AtomMatcher wakelockReleaseMatcher = CreateReleaseWakelockAtomMatcher(); + *config.add_atom_matcher() = wakelockReleaseMatcher; + AtomMatcher screenOnMatcher = CreateScreenTurnedOnAtomMatcher(); + *config.add_atom_matcher() = screenOnMatcher; + AtomMatcher screenOffMatcher = CreateScreenTurnedOffAtomMatcher(); + *config.add_atom_matcher() = screenOffMatcher; + + Predicate holdingWakelockPredicate = CreateHoldingWakelockPredicate(); + *holdingWakelockPredicate.mutable_simple_predicate()->mutable_dimensions() = + CreateAttributionUidDimensions(util::WAKELOCK_STATE_CHANGED, {Position::FIRST}); + *config.add_predicate() = holdingWakelockPredicate; + Predicate screenOnPredicate = CreateScreenIsOnPredicate(); + *config.add_predicate() = screenOnPredicate; + + DurationMetric durationWakelock = + createDurationMetric("DurWakelock", holdingWakelockPredicate.id(), nullopt, {}); + *durationWakelock.mutable_dimensions_in_what() = + CreateAttributionUidDimensions(util::WAKELOCK_STATE_CHANGED, {Position::FIRST}); + DurationMetric durationScreen = + createDurationMetric("DurScreen", screenOnPredicate.id(), nullopt, {}); + *config.add_duration_metric() = durationScreen; + *config.add_duration_metric() = durationWakelock; + + Alert alertPreserve = createAlert("AlertPreserve", durationWakelock.id(), /*buckets=*/2, + /*triggerSumGt=*/30 * NS_PER_SEC); + alertPreserve.set_refractory_period_secs(300); + Alert alertReplace = createAlert("AlertReplace", durationScreen.id(), 2, 30 * NS_PER_SEC); + alertReplace.set_refractory_period_secs(1); + Alert alertRemove = createAlert("AlertRemove", durationWakelock.id(), 5, 10 * NS_PER_SEC); + alertRemove.set_refractory_period_secs(1); + *config.add_alert() = alertReplace; + *config.add_alert() = alertPreserve; + *config.add_alert() = alertRemove; + + int preserveSubId = 1, replaceSubId = 2, removeSubId = 3; + Subscription preserveSub = createSubscription("S1", Subscription::ALERT, alertPreserve.id()); + preserveSub.mutable_broadcast_subscriber_details()->set_subscriber_id(preserveSubId); + Subscription replaceSub = createSubscription("S2", Subscription::ALERT, alertReplace.id()); + replaceSub.mutable_broadcast_subscriber_details()->set_subscriber_id(replaceSubId); + Subscription removeSub = createSubscription("S3", Subscription::ALERT, alertRemove.id()); + removeSub.mutable_broadcast_subscriber_details()->set_subscriber_id(removeSubId); + *config.add_subscription() = preserveSub; + *config.add_subscription() = removeSub; + *config.add_subscription() = replaceSub; + + int app1Uid = 123, app2Uid = 456; + vector attributionUids1 = {app1Uid}; + vector attributionTags1 = {"App1"}; + vector attributionUids2 = {app2Uid}; + vector attributionTags2 = {"App2"}; + int64_t configUid = 123, configId = 987; + ConfigKey key(configUid, configId); + + int alertPreserveCount = 0, alertRemoveCount = 0; + StatsDimensionsValueParcel alertPreserveDims; + StatsDimensionsValueParcel alertRemoveDims; + + // The binder calls here will happen synchronously because they are in-process. + shared_ptr preserveBroadcast = + SharedRefBase::make>(); + EXPECT_CALL(*preserveBroadcast, sendSubscriberBroadcast(configUid, configId, preserveSub.id(), + alertPreserve.id(), _, _)) + .Times(2) + .WillRepeatedly( + Invoke([&alertPreserveCount, &alertPreserveDims]( + int64_t, int64_t, int64_t, int64_t, const vector&, + const StatsDimensionsValueParcel& dimensionsValueParcel) { + alertPreserveCount++; + alertPreserveDims = dimensionsValueParcel; + return Status::ok(); + })); + + shared_ptr replaceBroadcast = + SharedRefBase::make>(); + EXPECT_CALL(*replaceBroadcast, sendSubscriberBroadcast(configUid, configId, replaceSub.id(), + alertReplace.id(), _, _)) + .Times(0); + + shared_ptr removeBroadcast = + SharedRefBase::make>(); + EXPECT_CALL(*removeBroadcast, sendSubscriberBroadcast(configUid, configId, removeSub.id(), + alertRemove.id(), _, _)) + .Times(2) + .WillRepeatedly( + Invoke([&alertRemoveCount, &alertRemoveDims]( + int64_t, int64_t, int64_t, int64_t, const vector&, + const StatsDimensionsValueParcel& dimensionsValueParcel) { + alertRemoveCount++; + alertRemoveDims = dimensionsValueParcel; + return Status::ok(); + })); + + SubscriberReporter::getInstance().setBroadcastSubscriber(key, preserveSubId, preserveBroadcast); + SubscriberReporter::getInstance().setBroadcastSubscriber(key, replaceSubId, replaceBroadcast); + SubscriberReporter::getInstance().setBroadcastSubscriber(key, removeSubId, removeBroadcast); + + shared_ptr service = SharedRefBase::make(nullptr, nullptr); + sp processor = service->mProcessor; + uint64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(TEN_MINUTES) * 1000000LL; + int64_t bucketStartTimeNs = processor->mTimeBaseNs; + int64_t roundedBucketStartTimeNs = bucketStartTimeNs / NS_PER_SEC * NS_PER_SEC; + uint64_t bucket2StartTimeNs = bucketStartTimeNs + bucketSizeNs; + processor->OnConfigUpdated(bucketStartTimeNs, key, config); + + StatsDimensionsValueParcel wlUid1 = + CreateAttributionUidDimensionsValueParcel(util::WAKELOCK_STATE_CHANGED, app1Uid); + StatsDimensionsValueParcel wlUid2 = + CreateAttributionUidDimensionsValueParcel(util::WAKELOCK_STATE_CHANGED, app2Uid); + + int64_t eventTimeNs = bucketStartTimeNs + 15 * NS_PER_SEC; + processor->OnLogEvent( + CreateAcquireWakelockEvent(eventTimeNs, attributionUids1, attributionTags1, "wl1") + .get(), + eventTimeNs); + EXPECT_EQ(alertPreserveCount, 0); + EXPECT_EQ(alertRemoveCount, 0); + + eventTimeNs = bucketStartTimeNs + 20 * NS_PER_SEC; + processor->OnLogEvent(CreateScreenStateChangedEvent( + eventTimeNs, android::view::DisplayStateEnum::DISPLAY_STATE_ON) + .get(), + eventTimeNs); + EXPECT_EQ(alertPreserveCount, 0); + EXPECT_EQ(alertRemoveCount, 0); + + eventTimeNs = bucketStartTimeNs + 30 * NS_PER_SEC; + processor->OnLogEvent( + CreateReleaseWakelockEvent(eventTimeNs, attributionUids1, attributionTags1, "wl1") + .get(), + eventTimeNs); + EXPECT_EQ(alertPreserveCount, 0); + EXPECT_EQ(alertRemoveCount, 1); + EXPECT_EQ(alertRemoveDims, wlUid1); + + eventTimeNs = bucketStartTimeNs + 40 * NS_PER_SEC; + processor->OnLogEvent(CreateScreenStateChangedEvent( + eventTimeNs, android::view::DisplayStateEnum::DISPLAY_STATE_OFF) + .get(), + eventTimeNs); + EXPECT_EQ(alertPreserveCount, 0); + EXPECT_EQ(alertRemoveCount, 1); + + eventTimeNs = bucket2StartTimeNs + 5 * NS_PER_SEC; + processor->OnLogEvent( + CreateAcquireWakelockEvent(eventTimeNs, attributionUids2, attributionTags2, "wl2") + .get(), + eventTimeNs); + EXPECT_EQ(alertPreserveCount, 0); + EXPECT_EQ(alertRemoveCount, 1); + + // Acts as the alarm for uid 2 wakelock, also starts the timer for screen on. + // Preserve enters 5 min refractory for uid 2. + eventTimeNs = bucket2StartTimeNs + 36 * NS_PER_SEC; + processor->OnLogEvent(CreateScreenStateChangedEvent( + eventTimeNs, android::view::DisplayStateEnum::DISPLAY_STATE_ON) + .get(), + eventTimeNs); + EXPECT_EQ(alertPreserveCount, 1); + EXPECT_EQ(alertPreserveDims, wlUid2); + EXPECT_EQ(alertRemoveCount, 2); + EXPECT_EQ(alertRemoveDims, wlUid2); + + eventTimeNs = bucket2StartTimeNs + 37 * NS_PER_SEC; + processor->OnLogEvent( + CreateReleaseWakelockEvent(eventTimeNs, attributionUids2, attributionTags2, "wl2") + .get(), + eventTimeNs); + EXPECT_EQ(alertPreserveCount, 1); + EXPECT_EQ(alertRemoveCount, 2); + + // Do config update. + StatsdConfig newConfig; + newConfig.add_allowed_log_source("AID_ROOT"); + *newConfig.add_atom_matcher() = wakelockAcquireMatcher; + *newConfig.add_atom_matcher() = screenOffMatcher; + *newConfig.add_atom_matcher() = wakelockReleaseMatcher; + *newConfig.add_atom_matcher() = screenOnMatcher; + *newConfig.add_predicate() = screenOnPredicate; + *newConfig.add_predicate() = holdingWakelockPredicate; + *newConfig.add_duration_metric() = durationWakelock; + *newConfig.add_duration_metric() = durationScreen; + + alertReplace.set_refractory_period_secs(2); // Causes replacement. + // New alert on existing metric. Should get current full bucket, but not history of 1st bucket. + Alert alertNew = createAlert("AlertNew", durationWakelock.id(), /*buckets=*/2, + /*triggerSumGt=*/40 * NS_PER_SEC); + *newConfig.add_alert() = alertPreserve; + *newConfig.add_alert() = alertNew; + *newConfig.add_alert() = alertReplace; + + int newSubId = 4; + Subscription newSub = createSubscription("S4", Subscription::ALERT, alertNew.id()); + newSub.mutable_broadcast_subscriber_details()->set_subscriber_id(newSubId); + *newConfig.add_subscription() = newSub; + *newConfig.add_subscription() = replaceSub; + *newConfig.add_subscription() = preserveSub; + + int alertNewCount = 0; + StatsDimensionsValueParcel alertNewDims; + shared_ptr newBroadcast = + SharedRefBase::make>(); + EXPECT_CALL(*newBroadcast, + sendSubscriberBroadcast(configUid, configId, newSub.id(), alertNew.id(), _, _)) + .Times(1) + .WillRepeatedly( + Invoke([&alertNewCount, &alertNewDims]( + int64_t, int64_t, int64_t, int64_t, const vector&, + const StatsDimensionsValueParcel& dimensionsValueParcel) { + alertNewCount++; + alertNewDims = dimensionsValueParcel; + return Status::ok(); + })); + SubscriberReporter::getInstance().setBroadcastSubscriber(key, newSubId, newBroadcast); + + int64_t updateTimeNs = bucket2StartTimeNs + 40 * NS_PER_SEC; + processor->OnConfigUpdated(updateTimeNs, key, newConfig); + + // Alert preserve will set alarm after the refractory period, but alert new will set it for + // 40s after this event. Note that it should be 8 seconds since 32 accumulated in the partial + // bucket before the update, but that is lost due to b/180992558. + eventTimeNs = bucket2StartTimeNs + 45 * NS_PER_SEC; + processor->OnLogEvent( + CreateAcquireWakelockEvent(eventTimeNs, attributionUids2, attributionTags2, "wl2") + .get(), + eventTimeNs); + EXPECT_EQ(alertPreserveCount, 1); + EXPECT_EQ(alertNewCount, 0); + + eventTimeNs = bucket2StartTimeNs + 50 * NS_PER_SEC; + // Alert replace doesn't fire because it has lost history. + processor->OnLogEvent(CreateScreenStateChangedEvent( + eventTimeNs, android::view::DisplayStateEnum::DISPLAY_STATE_OFF) + .get(), + eventTimeNs); + EXPECT_EQ(alertPreserveCount, 1); + EXPECT_EQ(alertNewCount, 0); + + // Alert preserve has 15 seconds from 1st bucket, so alert should fire at bucket2Start + 70. + eventTimeNs = bucket2StartTimeNs + 55 * NS_PER_SEC; + processor->OnLogEvent( + CreateAcquireWakelockEvent(eventTimeNs, attributionUids1, attributionTags1, "wl1") + .get(), + eventTimeNs); + EXPECT_EQ(alertPreserveCount, 1); + EXPECT_EQ(alertNewCount, 0); + + eventTimeNs = bucket2StartTimeNs + 71 * NS_PER_SEC; + processor->OnLogEvent( + CreateReleaseWakelockEvent(eventTimeNs, attributionUids1, attributionTags1, "wl1") + .get(), + eventTimeNs); + EXPECT_EQ(alertPreserveCount, 2); + EXPECT_EQ(alertPreserveDims, wlUid1); + EXPECT_EQ(alertNewCount, 0); + + // Random event to fire alert new for uid1. + eventTimeNs = bucket2StartTimeNs + 86 * NS_PER_SEC; + processor->OnLogEvent( + CreateScreenBrightnessChangedEvent(eventTimeNs, 50) + .get(), + eventTimeNs); + EXPECT_EQ(alertPreserveCount, 2); + EXPECT_EQ(alertNewCount, 1); + EXPECT_EQ(alertNewDims, wlUid2); + + // Clear data so it doesn't stay on disk. + vector buffer; + processor->onDumpReport(key, bucket2StartTimeNs + 100 * NS_PER_SEC, true, true, ADB_DUMP, FAST, + &buffer); + SubscriberReporter::getInstance().unsetBroadcastSubscriber(key, preserveSubId); + SubscriberReporter::getInstance().unsetBroadcastSubscriber(key, replaceSubId); + SubscriberReporter::getInstance().unsetBroadcastSubscriber(key, removeSubId); + SubscriberReporter::getInstance().unsetBroadcastSubscriber(key, newSubId); +} + TEST_F(ConfigUpdateE2eTest, TestNewDurationExistingWhat) { StatsdConfig config; config.add_allowed_log_source("AID_ROOT"); -- cgit v1.2.3 From c5b9b75d15be918219c92519d28479684f87bcd7 Mon Sep 17 00:00:00 2001 From: Tej Singh Date: Wed, 24 Feb 2021 19:54:49 -0800 Subject: Persist OringDurationTracker on partial bucket Perist OringDurationTracker on partial buckets when the metric has anomaly trackers. note that this isn't a perfect solution because a config update also involves a partial bucket split, and can add new anomaly trackers that didn't exist. However, this case is rare enough that the slight quality tradeoff is ok. Test: atest statsd_test Bug: 180992558 Change-Id: Ia8bdefde5ae1fc4d7beb3d0ed63f62795fc32571 Merged-In: Ibb065d64be3775c699583215f7f376a1a5492ac1 --- bin/src/StatsLogProcessor.h | 1 + bin/src/StatsService.h | 1 + bin/src/anomaly/AnomalyTracker.h | 1 + bin/src/metrics/MetricsManager.h | 1 + .../duration_helper/OringDurationTracker.cpp | 13 +++- bin/tests/e2e/Anomaly_duration_sum_e2e_test.cpp | 88 ++++++++++++++++++++++ bin/tests/e2e/ConfigUpdate_e2e_test.cpp | 17 +---- 7 files changed, 105 insertions(+), 17 deletions(-) diff --git a/bin/src/StatsLogProcessor.h b/bin/src/StatsLogProcessor.h index 35775633..67d834f6 100644 --- a/bin/src/StatsLogProcessor.h +++ b/bin/src/StatsLogProcessor.h @@ -327,6 +327,7 @@ private: FRIEND_TEST(AnomalyDetectionE2eTest, TestCountMetric_save_refractory_to_disk); FRIEND_TEST(AnomalyDetectionE2eTest, TestCountMetric_load_refractory_from_disk); FRIEND_TEST(AnomalyDetectionE2eTest, TestDurationMetric_SUM_single_bucket); + FRIEND_TEST(AnomalyDetectionE2eTest, TestDurationMetric_SUM_partial_bucket); FRIEND_TEST(AnomalyDetectionE2eTest, TestDurationMetric_SUM_multiple_buckets); FRIEND_TEST(AnomalyDetectionE2eTest, TestDurationMetric_SUM_long_refractory_period); diff --git a/bin/src/StatsService.h b/bin/src/StatsService.h index 8efbd981..c2b6a99a 100644 --- a/bin/src/StatsService.h +++ b/bin/src/StatsService.h @@ -408,6 +408,7 @@ private: FRIEND_TEST(ConfigUpdateE2eTest, TestAnomalyDurationMetric); FRIEND_TEST(AnomalyDetectionE2eTest, TestDurationMetric_SUM_single_bucket); + FRIEND_TEST(AnomalyDetectionE2eTest, TestDurationMetric_SUM_partial_bucket); FRIEND_TEST(AnomalyDetectionE2eTest, TestDurationMetric_SUM_multiple_buckets); FRIEND_TEST(AnomalyDetectionE2eTest, TestDurationMetric_SUM_long_refractory_period); }; diff --git a/bin/src/anomaly/AnomalyTracker.h b/bin/src/anomaly/AnomalyTracker.h index 7839e066..127a26f5 100644 --- a/bin/src/anomaly/AnomalyTracker.h +++ b/bin/src/anomaly/AnomalyTracker.h @@ -218,6 +218,7 @@ protected: FRIEND_TEST(GaugeMetricProducerTest, TestAnomalyDetection); FRIEND_TEST(CountMetricProducerTest, TestAnomalyDetectionUnSliced); FRIEND_TEST(AnomalyDetectionE2eTest, TestDurationMetric_SUM_single_bucket); + FRIEND_TEST(AnomalyDetectionE2eTest, TestDurationMetric_SUM_partial_bucket); FRIEND_TEST(AnomalyDetectionE2eTest, TestDurationMetric_SUM_multiple_buckets); FRIEND_TEST(AnomalyDetectionE2eTest, TestDurationMetric_SUM_long_refractory_period); diff --git a/bin/src/metrics/MetricsManager.h b/bin/src/metrics/MetricsManager.h index 304fda19..e6f3f606 100644 --- a/bin/src/metrics/MetricsManager.h +++ b/bin/src/metrics/MetricsManager.h @@ -329,6 +329,7 @@ private: FRIEND_TEST(AnomalyDetectionE2eTest, TestCountMetric_save_refractory_to_disk); FRIEND_TEST(AnomalyDetectionE2eTest, TestCountMetric_load_refractory_from_disk); FRIEND_TEST(AnomalyDetectionE2eTest, TestDurationMetric_SUM_single_bucket); + FRIEND_TEST(AnomalyDetectionE2eTest, TestDurationMetric_SUM_partial_bucket); FRIEND_TEST(AnomalyDetectionE2eTest, TestDurationMetric_SUM_multiple_buckets); FRIEND_TEST(AnomalyDetectionE2eTest, TestDurationMetric_SUM_long_refractory_period); diff --git a/bin/src/metrics/duration_helper/OringDurationTracker.cpp b/bin/src/metrics/duration_helper/OringDurationTracker.cpp index 7e2ee5cf..4a845560 100644 --- a/bin/src/metrics/duration_helper/OringDurationTracker.cpp +++ b/bin/src/metrics/duration_helper/OringDurationTracker.cpp @@ -143,7 +143,8 @@ bool OringDurationTracker::flushCurrentBucket( int64_t fullBucketEnd = getCurrentBucketEndTimeNs(); int64_t currentBucketEndTimeNs; - if (eventTimeNs >= fullBucketEnd) { + bool isFullBucket = eventTimeNs >= fullBucketEnd; + if (isFullBucket) { numBucketsForward = 1 + (eventTimeNs - fullBucketEnd) / mBucketSizeNs; currentBucketEndTimeNs = fullBucketEnd; } else { @@ -177,7 +178,7 @@ bool OringDurationTracker::flushCurrentBucket( VLOG(" duration: %lld does not pass set threshold", (long long)mDuration); } - if (eventTimeNs > fullBucketEnd) { + if (isFullBucket) { // End of full bucket, can send to anomaly tracker now. addPastBucketToAnomalyTrackers( MetricDimensionKey(mEventKey.getDimensionKeyInWhat(), durationIt.first), @@ -214,8 +215,12 @@ bool OringDurationTracker::flushCurrentBucket( } mLastStartTime = mCurrentBucketStartTimeNs; - // if all stopped, then tell owner it's safe to remove this tracker. - return mStarted.empty() && mPaused.empty(); + // If all stopped, then tell owner it's safe to remove this tracker on a full bucket. + // On a partial bucket, only clear if no anomaly trackers, as full bucket duration is used + // for anomaly detection. + // Note: Anomaly trackers can be added on config updates, in which case mAnomalyTrackers > 0 and + // the full bucket duration could be used, but this is very rare so it is okay to clear. + return mStarted.empty() && mPaused.empty() && (isFullBucket || mAnomalyTrackers.size() == 0); } bool OringDurationTracker::flushIfNeeded( diff --git a/bin/tests/e2e/Anomaly_duration_sum_e2e_test.cpp b/bin/tests/e2e/Anomaly_duration_sum_e2e_test.cpp index 06779aa8..e8014805 100644 --- a/bin/tests/e2e/Anomaly_duration_sum_e2e_test.cpp +++ b/bin/tests/e2e/Anomaly_duration_sum_e2e_test.cpp @@ -412,6 +412,94 @@ TEST(AnomalyDetectionE2eTest, TestDurationMetric_SUM_multiple_buckets) { anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1)); } +TEST(AnomalyDetectionE2eTest, TestDurationMetric_SUM_partial_bucket) { + const int num_buckets = 1; + const uint64_t threshold_ns = NS_PER_SEC; + auto config = CreateStatsdConfig(num_buckets, threshold_ns, DurationMetric::SUM, true); + const uint64_t alert_id = config.alert(0).id(); + const uint32_t refractory_period_sec = config.alert(0).refractory_period_secs(); + + shared_ptr service = SharedRefBase::make(nullptr, nullptr); + sendConfig(service, config); + + auto processor = service->mProcessor; + ASSERT_EQ(processor->mMetricsManagers.size(), 1u); + EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid()); + ASSERT_EQ(1u, processor->mMetricsManagers.begin()->second->mAllAnomalyTrackers.size()); + + int64_t bucketStartTimeNs = processor->mTimeBaseNs; + int64_t roundedBucketStartTimeNs = bucketStartTimeNs / NS_PER_SEC * NS_PER_SEC; + int64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(config.duration_metric(0).bucket()) * 1e6; + + service->mUidMap->updateMap(bucketStartTimeNs, {1}, {1}, {String16("v1")}, + {String16("randomApp")}, {String16("")}); + + sp anomalyTracker = + processor->mMetricsManagers.begin()->second->mAllAnomalyTrackers[0]; + + auto screen_off_event = CreateScreenStateChangedEvent( + bucketStartTimeNs + 10, android::view::DisplayStateEnum::DISPLAY_STATE_OFF); + processor->OnLogEvent(screen_off_event.get()); + + // Acquire wakelock wl1. + auto acquire_event = CreateAcquireWakelockEvent(bucketStartTimeNs + 11, attributionUids1, + attributionTags1, "wl1"); + processor->OnLogEvent(acquire_event.get()); + EXPECT_EQ((bucketStartTimeNs + 11 + threshold_ns) / NS_PER_SEC + 1, + anomalyTracker->getAlarmTimestampSec(dimensionKey1)); + EXPECT_EQ(0u, anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1)); + + // Release wakelock wl1. No anomaly detected. Alarm cancelled at the "release" event. + // 90 ns accumulated. + auto release_event = CreateReleaseWakelockEvent(bucketStartTimeNs + 101, attributionUids1, + attributionTags1, "wl1"); + processor->OnLogEvent(release_event.get()); + EXPECT_EQ(0u, anomalyTracker->getAlarmTimestampSec(dimensionKey1)); + EXPECT_EQ(0u, anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1)); + + // Acquire wakelock wl2. + acquire_event = CreateAcquireWakelockEvent(bucketStartTimeNs + 110, attributionUids3, + attributionTags3, "wl2"); + processor->OnLogEvent(acquire_event.get()); + int64_t wl2AlarmTimeNs = bucketStartTimeNs + 110 + threshold_ns; + EXPECT_EQ(wl2AlarmTimeNs / NS_PER_SEC + 1, anomalyTracker->getAlarmTimestampSec(dimensionKey2)); + EXPECT_EQ(0u, anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey2)); + + // Partial bucket split. + int64_t appUpgradeTimeNs = bucketStartTimeNs + 500; + service->mUidMap->updateApp(appUpgradeTimeNs, String16("randomApp"), 1, 2, String16("v2"), + String16("")); + EXPECT_EQ(0u, anomalyTracker->getAlarmTimestampSec(dimensionKey1)); + EXPECT_EQ(0u, anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1)); + EXPECT_EQ((bucketStartTimeNs + 110 + threshold_ns) / NS_PER_SEC + 1, + anomalyTracker->getAlarmTimestampSec(dimensionKey2)); + EXPECT_EQ(0u, anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey2)); + + // Acquire wakelock wl1. Subtract 100 ns since that accumulated before the bucket split. + acquire_event = CreateAcquireWakelockEvent(bucketStartTimeNs + 510, attributionUids1, + attributionTags1, "wl1"); + processor->OnLogEvent(acquire_event.get()); + int64_t wl1AlarmTimeNs = bucketStartTimeNs + 510 + threshold_ns - 90; + EXPECT_EQ(wl1AlarmTimeNs / NS_PER_SEC + 1, anomalyTracker->getAlarmTimestampSec(dimensionKey1)); + EXPECT_EQ(0u, anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1)); + + // Release wakelock wl1. One anomaly detected. + release_event = CreateReleaseWakelockEvent(wl1AlarmTimeNs + 1, attributionUids2, + attributionTags2, "wl1"); + processor->OnLogEvent(release_event.get()); + EXPECT_EQ(0u, anomalyTracker->getAlarmTimestampSec(dimensionKey1)); + EXPECT_EQ(refractory_period_sec + (wl1AlarmTimeNs + 1) / NS_PER_SEC + 1, + anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1)); + + // Anomaly alarm fired. + auto alarmTriggerEvent = CreateBatterySaverOnEvent(wl2AlarmTimeNs); + processor->OnLogEvent(alarmTriggerEvent.get(), wl2AlarmTimeNs); + + EXPECT_EQ(0u, anomalyTracker->getAlarmTimestampSec(dimensionKey1)); + EXPECT_EQ(refractory_period_sec + wl2AlarmTimeNs / NS_PER_SEC + 1, + anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1)); +} + TEST(AnomalyDetectionE2eTest, TestDurationMetric_SUM_long_refractory_period) { const int num_buckets = 2; const uint64_t threshold_ns = 3 * NS_PER_SEC; diff --git a/bin/tests/e2e/ConfigUpdate_e2e_test.cpp b/bin/tests/e2e/ConfigUpdate_e2e_test.cpp index 7e0a65c6..6881d74b 100644 --- a/bin/tests/e2e/ConfigUpdate_e2e_test.cpp +++ b/bin/tests/e2e/ConfigUpdate_e2e_test.cpp @@ -2095,8 +2095,7 @@ TEST_F(ConfigUpdateE2eTest, TestAnomalyDurationMetric) { processor->OnConfigUpdated(updateTimeNs, key, newConfig); // Alert preserve will set alarm after the refractory period, but alert new will set it for - // 40s after this event. Note that it should be 8 seconds since 32 accumulated in the partial - // bucket before the update, but that is lost due to b/180992558. + // 8 seconds after this event. eventTimeNs = bucket2StartTimeNs + 45 * NS_PER_SEC; processor->OnLogEvent( CreateAcquireWakelockEvent(eventTimeNs, attributionUids2, attributionTags2, "wl2") @@ -2115,13 +2114,15 @@ TEST_F(ConfigUpdateE2eTest, TestAnomalyDurationMetric) { EXPECT_EQ(alertNewCount, 0); // Alert preserve has 15 seconds from 1st bucket, so alert should fire at bucket2Start + 70. + // Serves as alarm for alert new for uid2. eventTimeNs = bucket2StartTimeNs + 55 * NS_PER_SEC; processor->OnLogEvent( CreateAcquireWakelockEvent(eventTimeNs, attributionUids1, attributionTags1, "wl1") .get(), eventTimeNs); EXPECT_EQ(alertPreserveCount, 1); - EXPECT_EQ(alertNewCount, 0); + EXPECT_EQ(alertNewCount, 1); + EXPECT_EQ(alertNewDims, wlUid2); eventTimeNs = bucket2StartTimeNs + 71 * NS_PER_SEC; processor->OnLogEvent( @@ -2130,17 +2131,7 @@ TEST_F(ConfigUpdateE2eTest, TestAnomalyDurationMetric) { eventTimeNs); EXPECT_EQ(alertPreserveCount, 2); EXPECT_EQ(alertPreserveDims, wlUid1); - EXPECT_EQ(alertNewCount, 0); - - // Random event to fire alert new for uid1. - eventTimeNs = bucket2StartTimeNs + 86 * NS_PER_SEC; - processor->OnLogEvent( - CreateScreenBrightnessChangedEvent(eventTimeNs, 50) - .get(), - eventTimeNs); - EXPECT_EQ(alertPreserveCount, 2); EXPECT_EQ(alertNewCount, 1); - EXPECT_EQ(alertNewDims, wlUid2); // Clear data so it doesn't stay on disk. vector buffer; -- cgit v1.2.3 From 6af0ad19d338193b718a10aa21756515f7517a7f Mon Sep 17 00:00:00 2001 From: Tej Singh Date: Mon, 1 Mar 2021 21:40:37 -0800 Subject: Duration metric new alerts: schedule alarms New alerts built on top of existing duration metric hit an edge case because duration metric uses predictivve anomaly detection - it will schedule an alarm to fire when the anomaly should go off. It is further complicated by the fact that anomaly detection works on full bucket durations, and config updates cause a partial bucket split. We can hit one of three interesting cases: 1. Full bucket duration is already > threshold. For this, schedule an alarm to occur immediately. Do this instead of directly declaring an anomaly because subscribers have not yet been initialized. 2. Duration is current accumulating (screen is on, etc). For this, schedule an alarm at the time the duration should cross the threshold. 3. Intersection of case 1 & 2. Duration is both accumulating and over the threshold. This is treated identically to case 1. Test: atest statsd_test Bug: 180992549 Change-Id: I0770b58972784d20fdf97c751ba8b6f1d5da4073 Merged-In: Ibb065d64be3775c699583215f7f376a1a5492ac1 --- bin/src/metrics/DurationMetricProducer.cpp | 17 ++- bin/src/metrics/DurationMetricProducer.h | 13 +- bin/src/metrics/MetricProducer.h | 8 +- bin/src/metrics/duration_helper/DurationTracker.h | 18 ++- .../metrics/duration_helper/MaxDurationTracker.cpp | 6 +- .../metrics/duration_helper/MaxDurationTracker.h | 5 +- .../duration_helper/OringDurationTracker.cpp | 3 + .../metrics/duration_helper/OringDurationTracker.h | 4 + .../metrics/parsing_utils/config_update_utils.cpp | 17 ++- .../metrics/parsing_utils/config_update_utils.h | 13 +- .../metrics/parsing_utils/metrics_manager_util.cpp | 14 +- .../metrics/parsing_utils/metrics_manager_util.h | 1 + bin/src/stats_util.h | 10 ++ bin/tests/e2e/ConfigUpdate_e2e_test.cpp | 147 ++++++++++++++++----- bin/tests/metrics/CountMetricProducer_test.cpp | 6 +- bin/tests/metrics/DurationMetricProducer_test.cpp | 3 +- bin/tests/metrics/GaugeMetricProducer_test.cpp | 6 +- bin/tests/metrics/ValueMetricProducer_test.cpp | 6 +- .../parsing_utils/config_update_utils_test.cpp | 9 +- .../parsing_utils/metrics_manager_util_test.cpp | 20 ++- 20 files changed, 232 insertions(+), 94 deletions(-) diff --git a/bin/src/metrics/DurationMetricProducer.cpp b/bin/src/metrics/DurationMetricProducer.cpp index 272936d7..733fb388 100644 --- a/bin/src/metrics/DurationMetricProducer.cpp +++ b/bin/src/metrics/DurationMetricProducer.cpp @@ -262,7 +262,8 @@ void DurationMetricProducer::initTrueDimensions(const int whatIndex, const int64 } sp DurationMetricProducer::addAnomalyTracker( - const Alert &alert, const sp& anomalyAlarmMonitor) { + const Alert& alert, const sp& anomalyAlarmMonitor, + const UpdateStatus& updateStatus, const int64_t updateTimeNs) { std::lock_guard lock(mMutex); if (mAggregationType == DurationMetric_AggregationType_SUM) { if (alert.trigger_if_sum_gt() > alert.num_buckets() * mBucketSizeNs) { @@ -273,22 +274,26 @@ sp DurationMetricProducer::addAnomalyTracker( } sp anomalyTracker = new DurationAnomalyTracker(alert, mConfigKey, anomalyAlarmMonitor); - addAnomalyTrackerLocked(anomalyTracker); + // The update status is either new or replaced. + addAnomalyTrackerLocked(anomalyTracker, updateStatus, updateTimeNs); return anomalyTracker; } // Adds an AnomalyTracker that has already been created. // Note: this gets called on config updates, and will only get called if the metric and the // associated alert are preserved, which means the AnomalyTracker must be a DurationAnomalyTracker. -void DurationMetricProducer::addAnomalyTracker(sp& anomalyTracker) { +void DurationMetricProducer::addAnomalyTracker(sp& anomalyTracker, + const int64_t updateTimeNs) { std::lock_guard lock(mMutex); - addAnomalyTrackerLocked(anomalyTracker); + addAnomalyTrackerLocked(anomalyTracker, UpdateStatus::UPDATE_PRESERVE, updateTimeNs); } -void DurationMetricProducer::addAnomalyTrackerLocked(sp& anomalyTracker) { +void DurationMetricProducer::addAnomalyTrackerLocked(sp& anomalyTracker, + const UpdateStatus& updateStatus, + const int64_t updateTimeNs) { mAnomalyTrackers.push_back(anomalyTracker); for (const auto& [_, durationTracker] : mCurrentSlicedDurationTrackerMap) { - durationTracker->addAnomalyTracker(anomalyTracker); + durationTracker->addAnomalyTracker(anomalyTracker, updateStatus, updateTimeNs); } } void DurationMetricProducer::onStateChanged(const int64_t eventTimeNs, const int32_t atomId, diff --git a/bin/src/metrics/DurationMetricProducer.h b/bin/src/metrics/DurationMetricProducer.h index 5d3cd112..100ea459 100644 --- a/bin/src/metrics/DurationMetricProducer.h +++ b/bin/src/metrics/DurationMetricProducer.h @@ -16,10 +16,10 @@ #pragma once +#include #include -#include #include "../anomaly/DurationAnomalyTracker.h" #include "../condition/ConditionTracker.h" #include "../matchers/matcher_util.h" @@ -52,10 +52,12 @@ public: virtual ~DurationMetricProducer(); - sp addAnomalyTracker(const Alert &alert, - const sp& anomalyAlarmMonitor) override; + sp addAnomalyTracker(const Alert& alert, + const sp& anomalyAlarmMonitor, + const UpdateStatus& updateStatus, + const int64_t updateTimeNs) override; - void addAnomalyTracker(sp& anomalyTracker) override; + void addAnomalyTracker(sp& anomalyTracker, const int64_t updateTimeNs) override; void onStateChanged(const int64_t eventTimeNs, const int32_t atomId, const HashableDimensionKey& primaryKey, const FieldValue& oldState, @@ -137,7 +139,8 @@ private: std::unordered_map>& deactivationAtomTrackerToMetricMap, std::vector& metricsWithActivation) override; - void addAnomalyTrackerLocked(sp& anomalyTracker); + void addAnomalyTrackerLocked(sp& anomalyTracker, + const UpdateStatus& updateStatus, const int64_t updateTimeNs); const DurationMetric_AggregationType mAggregationType; diff --git a/bin/src/metrics/MetricProducer.h b/bin/src/metrics/MetricProducer.h index 01ed57af..44b364ac 100644 --- a/bin/src/metrics/MetricProducer.h +++ b/bin/src/metrics/MetricProducer.h @@ -341,8 +341,10 @@ public: } /* Adds an AnomalyTracker and returns it. */ - virtual sp addAnomalyTracker(const Alert &alert, - const sp& anomalyAlarmMonitor) { + virtual sp addAnomalyTracker(const Alert& alert, + const sp& anomalyAlarmMonitor, + const UpdateStatus& updateStatus, + const int64_t updateTimeNs) { std::lock_guard lock(mMutex); sp anomalyTracker = new AnomalyTracker(alert, mConfigKey); mAnomalyTrackers.push_back(anomalyTracker); @@ -350,7 +352,7 @@ public: } /* Adds an AnomalyTracker that has already been created */ - virtual void addAnomalyTracker(sp& anomalyTracker) { + virtual void addAnomalyTracker(sp& anomalyTracker, const int64_t updateTimeNs) { std::lock_guard lock(mMutex); mAnomalyTrackers.push_back(anomalyTracker); } diff --git a/bin/src/metrics/duration_helper/DurationTracker.h b/bin/src/metrics/duration_helper/DurationTracker.h index e0787302..849effa1 100644 --- a/bin/src/metrics/duration_helper/DurationTracker.h +++ b/bin/src/metrics/duration_helper/DurationTracker.h @@ -20,6 +20,7 @@ #include "anomaly/DurationAnomalyTracker.h" #include "condition/ConditionWizard.h" #include "config/ConfigKey.h" +#include "metrics/parsing_utils/config_update_utils.h" #include "stats_util.h" namespace android { @@ -133,11 +134,26 @@ public: // Replace old value with new value for the given state atom. virtual void updateCurrentStateKey(const int32_t atomId, const FieldValue& newState) = 0; - void addAnomalyTracker(sp& anomalyTracker) { + void addAnomalyTracker(sp& anomalyTracker, const UpdateStatus& updateStatus, + const int64_t updateTimeNs) { mAnomalyTrackers.push_back(anomalyTracker); + // Preserved anomaly trackers will have the correct alarm times. + // New/replaced alerts will need to set alarms for pending durations, or may have already + // fired if the full bucket duration is high enough. + // NB: this depends on a config updating that splits a partial bucket having just happened. + // If this constraint changes, predict will return the wrong timestamp. + if (updateStatus == UpdateStatus::UPDATE_NEW || + updateStatus == UpdateStatus::UPDATE_PRESERVE) { + const int64_t alarmTimeNs = predictAnomalyTimestampNs(*anomalyTracker, updateTimeNs); + if (alarmTimeNs <= updateTimeNs || hasAccumulatingDuration()) { + anomalyTracker->startAlarm(mEventKey, std::max(alarmTimeNs, updateTimeNs)); + } + } } protected: + virtual bool hasAccumulatingDuration() = 0; + int64_t getCurrentBucketEndTimeNs() const { return mStartTimeNs + (mCurrentBucketNum + 1) * mBucketSizeNs; } diff --git a/bin/src/metrics/duration_helper/MaxDurationTracker.cpp b/bin/src/metrics/duration_helper/MaxDurationTracker.cpp index 14492633..0ee5e46d 100644 --- a/bin/src/metrics/duration_helper/MaxDurationTracker.cpp +++ b/bin/src/metrics/duration_helper/MaxDurationTracker.cpp @@ -113,7 +113,7 @@ void MaxDurationTracker::noteStop(const HashableDimensionKey& key, const int64_t (long long)duration.lastStartTime, (long long)eventTime, (long long)durationTime); duration.lastDuration += durationTime; - if (anyStarted()) { + if (hasAccumulatingDuration()) { // In case any other dimensions are still started, we need to keep the alarm // set. startAnomalyAlarm(eventTime); @@ -142,7 +142,7 @@ void MaxDurationTracker::noteStop(const HashableDimensionKey& key, const int64_t } } -bool MaxDurationTracker::anyStarted() { +bool MaxDurationTracker::hasAccumulatingDuration() { for (auto& pair : mInfos) { if (pair.second.state == kStarted) { return true; @@ -267,7 +267,7 @@ void MaxDurationTracker::noteConditionChanged(const HashableDimensionKey& key, b stopAnomalyAlarm(timestamp); it->second.state = DurationState::kPaused; it->second.lastDuration += (timestamp - it->second.lastStartTime); - if (anyStarted()) { + if (hasAccumulatingDuration()) { // In case any other dimensions are still started, we need to set the alarm. startAnomalyAlarm(timestamp); } diff --git a/bin/src/metrics/duration_helper/MaxDurationTracker.h b/bin/src/metrics/duration_helper/MaxDurationTracker.h index 47cfbe6f..b1fdd6e6 100644 --- a/bin/src/metrics/duration_helper/MaxDurationTracker.h +++ b/bin/src/metrics/duration_helper/MaxDurationTracker.h @@ -65,10 +65,11 @@ public: void updateCurrentStateKey(const int32_t atomId, const FieldValue& newState); -private: +protected: // Returns true if at least one of the mInfos is started. - bool anyStarted(); + bool hasAccumulatingDuration() override; +private: std::unordered_map mInfos; void noteConditionChanged(const HashableDimensionKey& key, bool conditionMet, diff --git a/bin/src/metrics/duration_helper/OringDurationTracker.cpp b/bin/src/metrics/duration_helper/OringDurationTracker.cpp index 4a845560..b67e25c4 100644 --- a/bin/src/metrics/duration_helper/OringDurationTracker.cpp +++ b/bin/src/metrics/duration_helper/OringDurationTracker.cpp @@ -352,6 +352,9 @@ void OringDurationTracker::onStateChanged(const int64_t timestamp, const int32_t updateCurrentStateKey(atomId, newState); } +bool OringDurationTracker::hasAccumulatingDuration() { + return !mStarted.empty(); +} int64_t OringDurationTracker::predictAnomalyTimestampNs(const AnomalyTracker& anomalyTracker, const int64_t eventTimestampNs) const { // The anomaly threshold. diff --git a/bin/src/metrics/duration_helper/OringDurationTracker.h b/bin/src/metrics/duration_helper/OringDurationTracker.h index ffe65ed2..8f8d5355 100644 --- a/bin/src/metrics/duration_helper/OringDurationTracker.h +++ b/bin/src/metrics/duration_helper/OringDurationTracker.h @@ -64,6 +64,10 @@ public: void updateCurrentStateKey(const int32_t atomId, const FieldValue& newState); +protected: + // Returns true if at least one of the mInfos is started. + bool hasAccumulatingDuration() override; + private: // We don't need to keep track of individual durations. The information that's needed is: // 1) which keys are started. We record the first start time. diff --git a/bin/src/metrics/parsing_utils/config_update_utils.cpp b/bin/src/metrics/parsing_utils/config_update_utils.cpp index 39789cd8..b8c505ee 100644 --- a/bin/src/metrics/parsing_utils/config_update_utils.cpp +++ b/bin/src/metrics/parsing_utils/config_update_utils.cpp @@ -959,7 +959,8 @@ bool determineAlertUpdateStatus(const Alert& alert, return true; } -bool updateAlerts(const StatsdConfig& config, const unordered_map& metricProducerMap, +bool updateAlerts(const StatsdConfig& config, const int64_t currentTimeNs, + const unordered_map& metricProducerMap, const set& replacedMetrics, const unordered_map& oldAlertTrackerMap, const vector>& oldAnomalyTrackers, @@ -998,14 +999,16 @@ bool updateAlerts(const StatsdConfig& config, const unordered_map& (long long)alert.metric_id()); return false; } - allMetricProducers[metricProducerIt->second]->addAnomalyTracker(anomalyTracker); + allMetricProducers[metricProducerIt->second]->addAnomalyTracker(anomalyTracker, + currentTimeNs); newAnomalyTrackers.push_back(anomalyTracker); break; } case UPDATE_REPLACE: case UPDATE_NEW: { - optional> anomalyTracker = createAnomalyTracker( - alert, anomalyAlarmMonitor, metricProducerMap, allMetricProducers); + optional> anomalyTracker = + createAnomalyTracker(alert, anomalyAlarmMonitor, alertUpdateStatuses[i], + currentTimeNs, metricProducerMap, allMetricProducers); if (!anomalyTracker) { return false; } @@ -1097,9 +1100,9 @@ bool updateStatsdConfig(const ConfigKey& key, const StatsdConfig& config, const return false; } - if (!updateAlerts(config, newMetricProducerMap, replacedMetrics, oldAlertTrackerMap, - oldAnomalyTrackers, anomalyAlarmMonitor, newMetricProducers, - newAlertTrackerMap, newAnomalyTrackers)) { + if (!updateAlerts(config, currentTimeNs, newMetricProducerMap, replacedMetrics, + oldAlertTrackerMap, oldAnomalyTrackers, anomalyAlarmMonitor, + newMetricProducers, newAlertTrackerMap, newAnomalyTrackers)) { ALOGE("updateAlerts failed"); return false; } diff --git a/bin/src/metrics/parsing_utils/config_update_utils.h b/bin/src/metrics/parsing_utils/config_update_utils.h index 8e2be689..24f8c850 100644 --- a/bin/src/metrics/parsing_utils/config_update_utils.h +++ b/bin/src/metrics/parsing_utils/config_update_utils.h @@ -33,16 +33,6 @@ namespace statsd { // *Note*: only updateStatsdConfig() should be called from outside this file. // All other functions are intermediate steps, created to make unit testing easier. -// Possible update states for a component. PRESERVE means we should keep the existing one. -// REPLACE means we should create a new one because the existing one changed -// NEW means we should create a new one because one does not currently exist. -enum UpdateStatus { - UPDATE_UNKNOWN = 0, - UPDATE_PRESERVE = 1, - UPDATE_REPLACE = 2, - UPDATE_NEW = 3, -}; - // Recursive function to determine if a matcher needs to be updated. // input: // [config]: the input StatsdConfig @@ -214,6 +204,7 @@ bool determineAlertUpdateStatus(const Alert& alert, // Update MetricProducers. // input: // [config]: the input config +// [currentTimeNs]: time of the config update // [metricProducerMap]: metric id to index mapping in the new config // [replacedMetrics]: set of metric ids that have changed and were replaced // [oldAlertTrackerMap]: alert id to index mapping in the existing MetricsManager. @@ -226,7 +217,7 @@ bool determineAlertUpdateStatus(const Alert& alert, // output: // [newAlertTrackerMap]: mapping of alert id to index in the new config // [newAnomalyTrackers]: contains the list of sp to the AnomalyTrackers created. -bool updateAlerts(const StatsdConfig& config, +bool updateAlerts(const StatsdConfig& config, const int64_t currentTimeNs, const std::unordered_map& metricProducerMap, const std::set& replacedMetrics, const std::unordered_map& oldAlertTrackerMap, diff --git a/bin/src/metrics/parsing_utils/metrics_manager_util.cpp b/bin/src/metrics/parsing_utils/metrics_manager_util.cpp index 73e760a3..1e230008 100644 --- a/bin/src/metrics/parsing_utils/metrics_manager_util.cpp +++ b/bin/src/metrics/parsing_utils/metrics_manager_util.cpp @@ -838,6 +838,7 @@ optional> createGaugeMetricProducerAndUpdateMetadata( optional> createAnomalyTracker( const Alert& alert, const sp& anomalyAlarmMonitor, + const UpdateStatus& updateStatus, const int64_t currentTimeNs, const unordered_map& metricProducerMap, vector>& allMetricProducers) { const auto& itr = metricProducerMap.find(alert.metric_id()); @@ -857,7 +858,8 @@ optional> createAnomalyTracker( } const int metricIndex = itr->second; sp metric = allMetricProducers[metricIndex]; - sp anomalyTracker = metric->addAnomalyTracker(alert, anomalyAlarmMonitor); + sp anomalyTracker = + metric->addAnomalyTracker(alert, anomalyAlarmMonitor, updateStatus, currentTimeNs); if (anomalyTracker == nullptr) { // The ALOGW for this invalid alert was already displayed in addAnomalyTracker(). return nullopt; @@ -1123,7 +1125,8 @@ bool initMetrics(const ConfigKey& key, const StatsdConfig& config, const int64_t return true; } -bool initAlerts(const StatsdConfig& config, const unordered_map& metricProducerMap, +bool initAlerts(const StatsdConfig& config, const int64_t currentTimeNs, + const unordered_map& metricProducerMap, unordered_map& alertTrackerMap, const sp& anomalyAlarmMonitor, vector>& allMetricProducers, @@ -1131,8 +1134,9 @@ bool initAlerts(const StatsdConfig& config, const unordered_map& m for (int i = 0; i < config.alert_size(); i++) { const Alert& alert = config.alert(i); alertTrackerMap.insert(std::make_pair(alert.id(), allAnomalyTrackers.size())); - optional> anomalyTracker = createAnomalyTracker( - alert, anomalyAlarmMonitor, metricProducerMap, allMetricProducers); + optional> anomalyTracker = + createAnomalyTracker(alert, anomalyAlarmMonitor, UpdateStatus::UPDATE_NEW, + currentTimeNs, metricProducerMap, allMetricProducers); if (!anomalyTracker) { return false; } @@ -1223,7 +1227,7 @@ bool initStatsdConfig(const ConfigKey& key, const StatsdConfig& config, const sp ALOGE("initMetricProducers failed"); return false; } - if (!initAlerts(config, metricProducerMap, alertTrackerMap, anomalyAlarmMonitor, + if (!initAlerts(config, currentTimeNs, metricProducerMap, alertTrackerMap, anomalyAlarmMonitor, allMetricProducers, allAnomalyTrackers)) { ALOGE("initAlerts failed"); return false; diff --git a/bin/src/metrics/parsing_utils/metrics_manager_util.h b/bin/src/metrics/parsing_utils/metrics_manager_util.h index 84e1e4e0..5d66a0f1 100644 --- a/bin/src/metrics/parsing_utils/metrics_manager_util.h +++ b/bin/src/metrics/parsing_utils/metrics_manager_util.h @@ -192,6 +192,7 @@ optional> createGaugeMetricProducerAndUpdateMetadata( // Returns an sp to the AnomalyTracker, or nullopt if there was an error. optional> createAnomalyTracker( const Alert& alert, const sp& anomalyAlarmMonitor, + const UpdateStatus& updateStatus, const int64_t currentTimeNs, const std::unordered_map& metricProducerMap, std::vector>& allMetricProducers); diff --git a/bin/src/stats_util.h b/bin/src/stats_util.h index cfc411fd..c60babcc 100644 --- a/bin/src/stats_util.h +++ b/bin/src/stats_util.h @@ -24,6 +24,16 @@ namespace android { namespace os { namespace statsd { +// Possible update states for a component. PRESERVE means we should keep the existing one. +// REPLACE means we should create a new one because the existing one changed +// NEW means we should create a new one because one does not currently exist. +enum UpdateStatus { + UPDATE_UNKNOWN = 0, + UPDATE_PRESERVE = 1, + UPDATE_REPLACE = 2, + UPDATE_NEW = 3, +}; + const HashableDimensionKey DEFAULT_DIMENSION_KEY = HashableDimensionKey(); const MetricDimensionKey DEFAULT_METRIC_DIMENSION_KEY = MetricDimensionKey(); diff --git a/bin/tests/e2e/ConfigUpdate_e2e_test.cpp b/bin/tests/e2e/ConfigUpdate_e2e_test.cpp index 6881d74b..555971a5 100644 --- a/bin/tests/e2e/ConfigUpdate_e2e_test.cpp +++ b/bin/tests/e2e/ConfigUpdate_e2e_test.cpp @@ -1922,11 +1922,12 @@ TEST_F(ConfigUpdateE2eTest, TestAnomalyDurationMetric) { *config.add_subscription() = removeSub; *config.add_subscription() = replaceSub; - int app1Uid = 123, app2Uid = 456; - vector attributionUids1 = {app1Uid}; - vector attributionTags1 = {"App1"}; - vector attributionUids2 = {app2Uid}; - vector attributionTags2 = {"App2"}; + int app1Uid = 123, app2Uid = 456, app3Uid = 789, app4Uid = 111; + vector attributionUids1 = {app1Uid}, attributionUids2 = {app2Uid}, + attributionUids3 = {app3Uid}, attributionUids4 = {app4Uid}; + vector attributionTags1 = {"App1"}, attributionTags2 = {"App2"}, + attributionTags3 = {"App3"}, attributionTags4 = {"App4"}; + int64_t configUid = 123, configId = 987; ConfigKey key(configUid, configId); @@ -1939,7 +1940,7 @@ TEST_F(ConfigUpdateE2eTest, TestAnomalyDurationMetric) { SharedRefBase::make>(); EXPECT_CALL(*preserveBroadcast, sendSubscriberBroadcast(configUid, configId, preserveSub.id(), alertPreserve.id(), _, _)) - .Times(2) + .Times(4) .WillRepeatedly( Invoke([&alertPreserveCount, &alertPreserveDims]( int64_t, int64_t, int64_t, int64_t, const vector&, @@ -1959,7 +1960,7 @@ TEST_F(ConfigUpdateE2eTest, TestAnomalyDurationMetric) { SharedRefBase::make>(); EXPECT_CALL(*removeBroadcast, sendSubscriberBroadcast(configUid, configId, removeSub.id(), alertRemove.id(), _, _)) - .Times(2) + .Times(6) .WillRepeatedly( Invoke([&alertRemoveCount, &alertRemoveDims]( int64_t, int64_t, int64_t, int64_t, const vector&, @@ -1985,6 +1986,10 @@ TEST_F(ConfigUpdateE2eTest, TestAnomalyDurationMetric) { CreateAttributionUidDimensionsValueParcel(util::WAKELOCK_STATE_CHANGED, app1Uid); StatsDimensionsValueParcel wlUid2 = CreateAttributionUidDimensionsValueParcel(util::WAKELOCK_STATE_CHANGED, app2Uid); + StatsDimensionsValueParcel wlUid3 = + CreateAttributionUidDimensionsValueParcel(util::WAKELOCK_STATE_CHANGED, app3Uid); + StatsDimensionsValueParcel wlUid4 = + CreateAttributionUidDimensionsValueParcel(util::WAKELOCK_STATE_CHANGED, app4Uid); int64_t eventTimeNs = bucketStartTimeNs + 15 * NS_PER_SEC; processor->OnLogEvent( @@ -2002,6 +2007,7 @@ TEST_F(ConfigUpdateE2eTest, TestAnomalyDurationMetric) { EXPECT_EQ(alertPreserveCount, 0); EXPECT_EQ(alertRemoveCount, 0); + // Uid 1 accumulates 15 seconds in bucket #1. eventTimeNs = bucketStartTimeNs + 30 * NS_PER_SEC; processor->OnLogEvent( CreateReleaseWakelockEvent(eventTimeNs, attributionUids1, attributionTags1, "wl1") @@ -2009,8 +2015,9 @@ TEST_F(ConfigUpdateE2eTest, TestAnomalyDurationMetric) { eventTimeNs); EXPECT_EQ(alertPreserveCount, 0); EXPECT_EQ(alertRemoveCount, 1); - EXPECT_EQ(alertRemoveDims, wlUid1); +// EXPECT_EQ(alertRemoveDims, wlUid1); + // 20 seconds accumulated in bucket #1. eventTimeNs = bucketStartTimeNs + 40 * NS_PER_SEC; processor->OnLogEvent(CreateScreenStateChangedEvent( eventTimeNs, android::view::DisplayStateEnum::DISPLAY_STATE_OFF) @@ -2019,6 +2026,14 @@ TEST_F(ConfigUpdateE2eTest, TestAnomalyDurationMetric) { EXPECT_EQ(alertPreserveCount, 0); EXPECT_EQ(alertRemoveCount, 1); + eventTimeNs = bucket2StartTimeNs + 2 * NS_PER_SEC; + processor->OnLogEvent( + CreateAcquireWakelockEvent(eventTimeNs, attributionUids4, attributionTags4, "wl4") + .get(), + eventTimeNs); + EXPECT_EQ(alertPreserveCount, 0); + EXPECT_EQ(alertRemoveCount, 1); + eventTimeNs = bucket2StartTimeNs + 5 * NS_PER_SEC; processor->OnLogEvent( CreateAcquireWakelockEvent(eventTimeNs, attributionUids2, attributionTags2, "wl2") @@ -2027,25 +2042,79 @@ TEST_F(ConfigUpdateE2eTest, TestAnomalyDurationMetric) { EXPECT_EQ(alertPreserveCount, 0); EXPECT_EQ(alertRemoveCount, 1); - // Acts as the alarm for uid 2 wakelock, also starts the timer for screen on. - // Preserve enters 5 min refractory for uid 2. - eventTimeNs = bucket2StartTimeNs + 36 * NS_PER_SEC; - processor->OnLogEvent(CreateScreenStateChangedEvent( - eventTimeNs, android::view::DisplayStateEnum::DISPLAY_STATE_ON) + // Alarm for alert remove for uid 4. + eventTimeNs = bucket2StartTimeNs + 13 * NS_PER_SEC; + processor->OnLogEvent(CreateBatteryStateChangedEvent( + eventTimeNs, BatteryPluggedStateEnum::BATTERY_PLUGGED_USB) .get(), eventTimeNs); - EXPECT_EQ(alertPreserveCount, 1); - EXPECT_EQ(alertPreserveDims, wlUid2); + EXPECT_EQ(alertPreserveCount, 0); EXPECT_EQ(alertRemoveCount, 2); - EXPECT_EQ(alertRemoveDims, wlUid2); +// EXPECT_EQ(alertRemoveDims, wlUid4); + + // Uid3 will be pending at the update. + // Also acts as the alarm for alert remove for uid 2. + eventTimeNs = bucket2StartTimeNs + 30 * NS_PER_SEC; + processor->OnLogEvent( + CreateAcquireWakelockEvent(eventTimeNs, attributionUids3, attributionTags3, "wl3") + .get(), + eventTimeNs); + EXPECT_EQ(alertPreserveCount, 0); + EXPECT_EQ(alertRemoveCount, 3); +// EXPECT_EQ(alertRemoveDims, wlUid2); + // Alarm for alert preserve for uid 4, enters 5 min refractory period. + eventTimeNs = bucket2StartTimeNs + 33 * NS_PER_SEC; + processor->OnLogEvent(CreateBatteryStateChangedEvent( + eventTimeNs, BatteryPluggedStateEnum::BATTERY_PLUGGED_USB) + .get(), + eventTimeNs); + EXPECT_EQ(alertPreserveCount, 1); +// EXPECT_EQ(alertPreserveDims, wlUid4); + EXPECT_EQ(alertRemoveCount, 3); + + // Uid 2 accumulates 32 seconds in partial bucket before the update. Alert preserve fires. + // Preserve enters 5 min refractory for uid 2. + // Alert remove fires again for uid 2 since the refractory has expired. eventTimeNs = bucket2StartTimeNs + 37 * NS_PER_SEC; processor->OnLogEvent( CreateReleaseWakelockEvent(eventTimeNs, attributionUids2, attributionTags2, "wl2") .get(), eventTimeNs); - EXPECT_EQ(alertPreserveCount, 1); - EXPECT_EQ(alertRemoveCount, 2); + EXPECT_EQ(alertPreserveCount, 2); +// EXPECT_EQ(alertPreserveDims, wlUid2); + EXPECT_EQ(alertRemoveCount, 4); +// EXPECT_EQ(alertRemoveDims, wlUid2); + + // Alarm for alert remove for uid 3. + eventTimeNs = bucket2StartTimeNs + 41 * NS_PER_SEC; + processor->OnLogEvent(CreateBatteryStateChangedEvent( + eventTimeNs, BatteryPluggedStateEnum::BATTERY_PLUGGED_USB) + .get(), + eventTimeNs); + EXPECT_EQ(alertPreserveCount, 2); + EXPECT_EQ(alertRemoveCount, 5); +// EXPECT_EQ(alertRemoveDims, wlUid3); + + // Release wl for uid 4, has accumulated 41 seconds in partial bucket before update. + // Acts as alarm for uid3 of alert remove. + eventTimeNs = bucket2StartTimeNs + 43 * NS_PER_SEC; + processor->OnLogEvent( + CreateReleaseWakelockEvent(eventTimeNs, attributionUids4, attributionTags4, "wl4") + .get(), + eventTimeNs); + EXPECT_EQ(alertPreserveCount, 2); + EXPECT_EQ(alertRemoveCount, 6); +// EXPECT_EQ(alertRemoveDims, wlUid4); + + // Starts the timer for screen on. + eventTimeNs = bucket2StartTimeNs + 46 * NS_PER_SEC; + processor->OnLogEvent(CreateScreenStateChangedEvent( + eventTimeNs, android::view::DisplayStateEnum::DISPLAY_STATE_ON) + .get(), + eventTimeNs); + EXPECT_EQ(alertPreserveCount, 2); + EXPECT_EQ(alertRemoveCount, 6); // Do config update. StatsdConfig newConfig; @@ -2080,7 +2149,7 @@ TEST_F(ConfigUpdateE2eTest, TestAnomalyDurationMetric) { SharedRefBase::make>(); EXPECT_CALL(*newBroadcast, sendSubscriberBroadcast(configUid, configId, newSub.id(), alertNew.id(), _, _)) - .Times(1) + .Times(3) .WillRepeatedly( Invoke([&alertNewCount, &alertNewDims]( int64_t, int64_t, int64_t, int64_t, const vector&, @@ -2091,47 +2160,55 @@ TEST_F(ConfigUpdateE2eTest, TestAnomalyDurationMetric) { })); SubscriberReporter::getInstance().setBroadcastSubscriber(key, newSubId, newBroadcast); - int64_t updateTimeNs = bucket2StartTimeNs + 40 * NS_PER_SEC; + int64_t updateTimeNs = bucket2StartTimeNs + 50 * NS_PER_SEC; processor->OnConfigUpdated(updateTimeNs, key, newConfig); // Alert preserve will set alarm after the refractory period, but alert new will set it for // 8 seconds after this event. - eventTimeNs = bucket2StartTimeNs + 45 * NS_PER_SEC; + // Alert new should fire for uid 4, since it has already accumulated 41s and should fire on the + // first event after the update. + eventTimeNs = bucket2StartTimeNs + 55 * NS_PER_SEC; processor->OnLogEvent( CreateAcquireWakelockEvent(eventTimeNs, attributionUids2, attributionTags2, "wl2") .get(), eventTimeNs); - EXPECT_EQ(alertPreserveCount, 1); - EXPECT_EQ(alertNewCount, 0); + EXPECT_EQ(alertPreserveCount, 2); + EXPECT_EQ(alertNewCount, 1); +// EXPECT_EQ(alertNewDims, wlUid4); - eventTimeNs = bucket2StartTimeNs + 50 * NS_PER_SEC; + eventTimeNs = bucket2StartTimeNs + 60 * NS_PER_SEC; // Alert replace doesn't fire because it has lost history. processor->OnLogEvent(CreateScreenStateChangedEvent( eventTimeNs, android::view::DisplayStateEnum::DISPLAY_STATE_OFF) .get(), eventTimeNs); - EXPECT_EQ(alertPreserveCount, 1); - EXPECT_EQ(alertNewCount, 0); + EXPECT_EQ(alertPreserveCount, 2); + EXPECT_EQ(alertNewCount, 1); - // Alert preserve has 15 seconds from 1st bucket, so alert should fire at bucket2Start + 70. + // Alert preserve has 15 seconds from 1st bucket, so alert should fire at bucket2Start + 80. // Serves as alarm for alert new for uid2. - eventTimeNs = bucket2StartTimeNs + 55 * NS_PER_SEC; + // Also serves as alarm for alert preserve for uid 3, which began at bucket2Start + 30. + eventTimeNs = bucket2StartTimeNs + 65 * NS_PER_SEC; processor->OnLogEvent( CreateAcquireWakelockEvent(eventTimeNs, attributionUids1, attributionTags1, "wl1") .get(), eventTimeNs); - EXPECT_EQ(alertPreserveCount, 1); - EXPECT_EQ(alertNewCount, 1); - EXPECT_EQ(alertNewDims, wlUid2); + EXPECT_EQ(alertPreserveCount, 3); +// EXPECT_EQ(alertPreserveDims, wlUid3); + EXPECT_EQ(alertNewCount, 2); +// EXPECT_EQ(alertNewDims, wlUid2); - eventTimeNs = bucket2StartTimeNs + 71 * NS_PER_SEC; + // Release wakelock for uid1, causing alert preserve to fire for uid1. + // Also serves as alarm for alert new for uid3. + eventTimeNs = bucket2StartTimeNs + 81 * NS_PER_SEC; processor->OnLogEvent( CreateReleaseWakelockEvent(eventTimeNs, attributionUids1, attributionTags1, "wl1") .get(), eventTimeNs); - EXPECT_EQ(alertPreserveCount, 2); - EXPECT_EQ(alertPreserveDims, wlUid1); - EXPECT_EQ(alertNewCount, 1); + EXPECT_EQ(alertPreserveCount, 4); +// EXPECT_EQ(alertPreserveDims, wlUid1); + EXPECT_EQ(alertNewCount, 3); +// EXPECT_EQ(alertNewDims, wlUid3); // Clear data so it doesn't stay on disk. vector buffer; diff --git a/bin/tests/metrics/CountMetricProducer_test.cpp b/bin/tests/metrics/CountMetricProducer_test.cpp index 8e2864c6..aab29f21 100644 --- a/bin/tests/metrics/CountMetricProducer_test.cpp +++ b/bin/tests/metrics/CountMetricProducer_test.cpp @@ -268,7 +268,8 @@ TEST_P(CountMetricProducerTest_PartialBucket, TestSplitInCurrentBucket) { CountMetricProducer countProducer(kConfigKey, metric, -1 /* no condition */, {}, wizard, protoHash, bucketStartTimeNs, bucketStartTimeNs); - sp anomalyTracker = countProducer.addAnomalyTracker(alert, alarmMonitor); + sp anomalyTracker = + countProducer.addAnomalyTracker(alert, alarmMonitor, UPDATE_NEW, bucketStartTimeNs); EXPECT_TRUE(anomalyTracker != nullptr); // Bucket is not flushed yet. @@ -400,7 +401,8 @@ TEST(CountMetricProducerTest, TestAnomalyDetectionUnSliced) { CountMetricProducer countProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, {}, wizard, protoHash, bucketStartTimeNs, bucketStartTimeNs); - sp anomalyTracker = countProducer.addAnomalyTracker(alert, alarmMonitor); + sp anomalyTracker = + countProducer.addAnomalyTracker(alert, alarmMonitor, UPDATE_NEW, bucketStartTimeNs); int tagId = 1; LogEvent event1(/*uid=*/0, /*pid=*/0); diff --git a/bin/tests/metrics/DurationMetricProducer_test.cpp b/bin/tests/metrics/DurationMetricProducer_test.cpp index bb2ede4d..c0805e87 100644 --- a/bin/tests/metrics/DurationMetricProducer_test.cpp +++ b/bin/tests/metrics/DurationMetricProducer_test.cpp @@ -373,7 +373,8 @@ TEST_P(DurationMetricProducerTest_PartialBucket, TestSumDurationAnomaly) { 1 /* start index */, 2 /* stop index */, 3 /* stop_all index */, false /*nesting*/, wizard, protoHash, dimensions, bucketStartTimeNs, bucketStartTimeNs); - sp anomalyTracker = durationProducer.addAnomalyTracker(alert, alarmMonitor); + sp anomalyTracker = + durationProducer.addAnomalyTracker(alert, alarmMonitor, UPDATE_NEW, bucketStartTimeNs); EXPECT_TRUE(anomalyTracker != nullptr); int64_t startTimeNs = bucketStartTimeNs + 1; diff --git a/bin/tests/metrics/GaugeMetricProducer_test.cpp b/bin/tests/metrics/GaugeMetricProducer_test.cpp index 10606810..c7443481 100644 --- a/bin/tests/metrics/GaugeMetricProducer_test.cpp +++ b/bin/tests/metrics/GaugeMetricProducer_test.cpp @@ -221,7 +221,8 @@ TEST_P(GaugeMetricProducerTest_PartialBucket, TestPushedEvents) { bucketStartTimeNs, pullerManager); gaugeProducer.prepareFirstBucket(); - sp anomalyTracker = gaugeProducer.addAnomalyTracker(alert, alarmMonitor); + sp anomalyTracker = + gaugeProducer.addAnomalyTracker(alert, alarmMonitor, UPDATE_NEW, bucketStartTimeNs); EXPECT_TRUE(anomalyTracker != nullptr); LogEvent event1(/*uid=*/0, /*pid=*/0); @@ -571,7 +572,8 @@ TEST(GaugeMetricProducerTest, TestPulledEventsAnomalyDetection) { alert.set_num_buckets(2); const int32_t refPeriodSec = 60; alert.set_refractory_period_secs(refPeriodSec); - sp anomalyTracker = gaugeProducer.addAnomalyTracker(alert, alarmMonitor); + sp anomalyTracker = + gaugeProducer.addAnomalyTracker(alert, alarmMonitor, UPDATE_NEW, bucketStartTimeNs); int tagId = 1; vector> allData; diff --git a/bin/tests/metrics/ValueMetricProducer_test.cpp b/bin/tests/metrics/ValueMetricProducer_test.cpp index 6cf4192b..e74039b2 100644 --- a/bin/tests/metrics/ValueMetricProducer_test.cpp +++ b/bin/tests/metrics/ValueMetricProducer_test.cpp @@ -993,7 +993,8 @@ TEST(ValueMetricProducerTest, TestAnomalyDetection) { pullerManager); valueProducer.prepareFirstBucket(); - sp anomalyTracker = valueProducer.addAnomalyTracker(alert, alarmMonitor); + sp anomalyTracker = + valueProducer.addAnomalyTracker(alert, alarmMonitor, UPDATE_NEW, bucketStartTimeNs); LogEvent event1(/*uid=*/0, /*pid=*/0); CreateRepeatedValueLogEvent(&event1, tagId, bucketStartTimeNs + 1 * NS_PER_SEC, 10); @@ -1076,7 +1077,8 @@ TEST(ValueMetricProducerTest, TestAnomalyDetectionMultipleBucketsSkipped) { sp valueProducer = ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric, ConditionState::kFalse); - sp anomalyTracker = valueProducer->addAnomalyTracker(alert, alarmMonitor); + sp anomalyTracker = + valueProducer->addAnomalyTracker(alert, alarmMonitor, UPDATE_NEW, bucketStartTimeNs); valueProducer->onConditionChanged(true, bucketStartTimeNs + 1); diff --git a/bin/tests/metrics/parsing_utils/config_update_utils_test.cpp b/bin/tests/metrics/parsing_utils/config_update_utils_test.cpp index fac506ed..e43c16a7 100644 --- a/bin/tests/metrics/parsing_utils/config_update_utils_test.cpp +++ b/bin/tests/metrics/parsing_utils/config_update_utils_test.cpp @@ -3389,8 +3389,9 @@ TEST_F(ConfigUpdateTest, TestUpdateAlerts) { unordered_map> deactivationAtomTrackerToMetricMap; vector metricsWithActivation; set replacedMetrics; + int64_t currentTimeNs = 12345; EXPECT_TRUE(updateMetrics( - key, config, /*timeBaseNs=*/123, /*currentTimeNs=*/12345, new StatsPullerManager(), + key, config, /*timeBaseNs=*/123, currentTimeNs, new StatsPullerManager(), oldAtomMatchingTrackerMap, oldAtomMatchingTrackerMap, /*replacedMatchers*/ {}, oldAtomMatchingTrackers, oldConditionTrackerMap, /*replacedConditions=*/{}, oldConditionTrackers, {ConditionState::kUnknown}, /*stateAtomIdMap*/ {}, @@ -3404,9 +3405,9 @@ TEST_F(ConfigUpdateTest, TestUpdateAlerts) { unordered_map newAlertTrackerMap; vector> newAnomalyTrackers; - EXPECT_TRUE(updateAlerts(config, newMetricProducerMap, replacedMetrics, oldAlertTrackerMap, - oldAnomalyTrackers, anomalyAlarmMonitor, newMetricProducers, - newAlertTrackerMap, newAnomalyTrackers)); + EXPECT_TRUE(updateAlerts(config, currentTimeNs, newMetricProducerMap, replacedMetrics, + oldAlertTrackerMap, oldAnomalyTrackers, anomalyAlarmMonitor, + newMetricProducers, newAlertTrackerMap, newAnomalyTrackers)); unordered_map expectedAlertMap = { {alert1Id, alert1Index}, diff --git a/bin/tests/metrics/parsing_utils/metrics_manager_util_test.cpp b/bin/tests/metrics/parsing_utils/metrics_manager_util_test.cpp index 00690b58..e64760ba 100644 --- a/bin/tests/metrics/parsing_utils/metrics_manager_util_test.cpp +++ b/bin/tests/metrics/parsing_utils/metrics_manager_util_test.cpp @@ -804,7 +804,9 @@ TEST(MetricsManagerTest, TestCreateAnomalyTrackerInvalidMetric) { sp anomalyAlarmMonitor; vector> metricProducers; // Pass in empty metric producers, causing an error. - EXPECT_EQ(createAnomalyTracker(alert, anomalyAlarmMonitor, {}, metricProducers), nullopt); + EXPECT_EQ(createAnomalyTracker(alert, anomalyAlarmMonitor, UPDATE_NEW, /*updateTime=*/123, {}, + metricProducers), + nullopt); } TEST(MetricsManagerTest, TestCreateAnomalyTrackerNoThreshold) { @@ -821,7 +823,9 @@ TEST(MetricsManagerTest, TestCreateAnomalyTrackerNoThreshold) { vector> metricProducers({new CountMetricProducer( kConfigKey, metric, 0, {ConditionState::kUnknown}, wizard, 0x0123456789, 0, 0)}); sp anomalyAlarmMonitor; - EXPECT_EQ(createAnomalyTracker(alert, anomalyAlarmMonitor, {{1, 0}}, metricProducers), nullopt); + EXPECT_EQ(createAnomalyTracker(alert, anomalyAlarmMonitor, UPDATE_NEW, /*updateTime=*/123, + {{1, 0}}, metricProducers), + nullopt); } TEST(MetricsManagerTest, TestCreateAnomalyTrackerMissingBuckets) { @@ -838,7 +842,9 @@ TEST(MetricsManagerTest, TestCreateAnomalyTrackerMissingBuckets) { vector> metricProducers({new CountMetricProducer( kConfigKey, metric, 0, {ConditionState::kUnknown}, wizard, 0x0123456789, 0, 0)}); sp anomalyAlarmMonitor; - EXPECT_EQ(createAnomalyTracker(alert, anomalyAlarmMonitor, {{1, 0}}, metricProducers), nullopt); + EXPECT_EQ(createAnomalyTracker(alert, anomalyAlarmMonitor, UPDATE_NEW, /*updateTime=*/123, + {{1, 0}}, metricProducers), + nullopt); } TEST(MetricsManagerTest, TestCreateAnomalyTrackerGood) { @@ -856,7 +862,9 @@ TEST(MetricsManagerTest, TestCreateAnomalyTrackerGood) { vector> metricProducers({new CountMetricProducer( kConfigKey, metric, 0, {ConditionState::kUnknown}, wizard, 0x0123456789, 0, 0)}); sp anomalyAlarmMonitor; - EXPECT_NE(createAnomalyTracker(alert, anomalyAlarmMonitor, {{1, 0}}, metricProducers), nullopt); + EXPECT_NE(createAnomalyTracker(alert, anomalyAlarmMonitor, UPDATE_NEW, /*updateTime=*/123, + {{1, 0}}, metricProducers), + nullopt); } TEST(MetricsManagerTest, TestCreateAnomalyTrackerDurationTooLong) { @@ -879,7 +887,9 @@ TEST(MetricsManagerTest, TestCreateAnomalyTrackerDurationTooLong) { 1 /* start index */, 2 /* stop index */, 3 /* stop_all index */, false /*nesting*/, wizard, 0x0123456789, dimensions, 0, 0)}); sp anomalyAlarmMonitor; - EXPECT_EQ(createAnomalyTracker(alert, anomalyAlarmMonitor, {{1, 0}}, metricProducers), nullopt); + EXPECT_EQ(createAnomalyTracker(alert, anomalyAlarmMonitor, UPDATE_NEW, /*updateTime=*/123, + {{1, 0}}, metricProducers), + nullopt); } TEST(MetricsManagerTest, TestCreateDurationProducerDimensionsInWhatInvalid) { -- cgit v1.2.3 From 9f5abefdf93fa214b45544fb4f5ecb4e0aa47ad5 Mon Sep 17 00:00:00 2001 From: Tej Singh Date: Tue, 9 Mar 2021 17:19:42 -0800 Subject: Increase max allowed log sources The current max is 50, increase it to 150. Test: TH Bug: 182273893 Change-Id: Ic5eb9c5eb65220daa8fede60ff12906da7bac0ef Merged-In: Ibb065d64be3775c699583215f7f376a1a5492ac1 --- bin/src/guardrail/StatsdStats.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/src/guardrail/StatsdStats.h b/bin/src/guardrail/StatsdStats.h index 330bd8d9..3e31bd28 100644 --- a/bin/src/guardrail/StatsdStats.h +++ b/bin/src/guardrail/StatsdStats.h @@ -116,7 +116,7 @@ public: const static int kMaxTimestampCount = 20; - const static int kMaxLogSourceCount = 50; + const static int kMaxLogSourceCount = 150; const static int kMaxPullAtomPackages = 100; -- cgit v1.2.3 From e81c490cf4e5367c8a80f417b9e00971e25a2027 Mon Sep 17 00:00:00 2001 From: Tej Singh Date: Wed, 17 Mar 2021 06:41:42 +0000 Subject: Revert "Revert "Hardcode partial config updates to be on"" This reverts commit 627d756254f6c0e13602c2cce8afd2b61373fb68. Reason for revert: Want to turn on partial config updates in droidfood by default. Change-Id: I81e0969bd5675674e689e92dbd0200d3fed67f84 Merged-In: Ibb065d64be3775c699583215f7f376a1a5492ac1 --- bin/src/StatsLogProcessor.cpp | 3 +-- bin/src/StatsLogProcessor.h | 2 +- bin/src/config/ConfigListener.h | 2 +- bin/tests/ConfigManager_test.cpp | 14 ++++++------ bin/tests/e2e/ConfigUpdate_e2e_ab_test.cpp | 34 +++++++----------------------- bin/tests/e2e/ConfigUpdate_e2e_test.cpp | 17 --------------- 6 files changed, 18 insertions(+), 54 deletions(-) diff --git a/bin/src/StatsLogProcessor.cpp b/bin/src/StatsLogProcessor.cpp index 79842bfc..f4ed1b8d 100644 --- a/bin/src/StatsLogProcessor.cpp +++ b/bin/src/StatsLogProcessor.cpp @@ -521,10 +521,9 @@ void StatsLogProcessor::GetActiveConfigsLocked(const int uid, vector& o } void StatsLogProcessor::OnConfigUpdated(const int64_t timestampNs, const ConfigKey& key, - const StatsdConfig& config) { + const StatsdConfig& config, bool modularUpdate) { std::lock_guard lock(mMetricsMutex); WriteDataToDiskLocked(key, timestampNs, CONFIG_UPDATED, NO_TIME_CONSTRAINTS); - bool modularUpdate = getFlagBool(PARTIAL_CONFIG_UPDATE_FLAG, "false"); OnConfigUpdatedLocked(timestampNs, key, config, modularUpdate); } diff --git a/bin/src/StatsLogProcessor.h b/bin/src/StatsLogProcessor.h index 67d834f6..0554aa1c 100644 --- a/bin/src/StatsLogProcessor.h +++ b/bin/src/StatsLogProcessor.h @@ -48,7 +48,7 @@ public: void OnLogEvent(LogEvent* event); void OnConfigUpdated(const int64_t timestampNs, const ConfigKey& key, - const StatsdConfig& config); + const StatsdConfig& config, bool modularUpdate = true); void OnConfigRemoved(const ConfigKey& key); size_t GetMetricsSize(const ConfigKey& key) const; diff --git a/bin/src/config/ConfigListener.h b/bin/src/config/ConfigListener.h index dcd5e52f..4f71a4e1 100644 --- a/bin/src/config/ConfigListener.h +++ b/bin/src/config/ConfigListener.h @@ -39,7 +39,7 @@ public: * A configuration was added or updated. */ virtual void OnConfigUpdated(const int64_t timestampNs, const ConfigKey& key, - const StatsdConfig& config) = 0; + const StatsdConfig& config, bool modularUpdate = true) = 0; /** * A configuration was removed. diff --git a/bin/tests/ConfigManager_test.cpp b/bin/tests/ConfigManager_test.cpp index 24c8f2b1..7b8f74d0 100644 --- a/bin/tests/ConfigManager_test.cpp +++ b/bin/tests/ConfigManager_test.cpp @@ -44,8 +44,8 @@ static ostream& operator<<(ostream& os, const StatsdConfig& config) { */ class MockListener : public ConfigListener { public: - MOCK_METHOD3(OnConfigUpdated, - void(const int64_t timestampNs, const ConfigKey& key, const StatsdConfig& config)); + MOCK_METHOD4(OnConfigUpdated, void(const int64_t timestampNs, const ConfigKey& key, + const StatsdConfig& config, bool modularUpdate)); MOCK_METHOD1(OnConfigRemoved, void(const ConfigKey& key)); }; @@ -90,25 +90,25 @@ TEST(ConfigManagerTest, TestAddUpdateRemove) { // Add another one EXPECT_CALL(*(listener.get()), - OnConfigUpdated(_, ConfigKeyEq(1, StringToId("zzz")), StatsdConfigEq(91))) + OnConfigUpdated(_, ConfigKeyEq(1, StringToId("zzz")), StatsdConfigEq(91), true)) .RetiresOnSaturation(); manager->UpdateConfig(ConfigKey(1, StringToId("zzz")), config91); // Update It EXPECT_CALL(*(listener.get()), - OnConfigUpdated(_, ConfigKeyEq(1, StringToId("zzz")), StatsdConfigEq(92))) + OnConfigUpdated(_, ConfigKeyEq(1, StringToId("zzz")), StatsdConfigEq(92), true)) .RetiresOnSaturation(); manager->UpdateConfig(ConfigKey(1, StringToId("zzz")), config92); // Add one with the same uid but a different name EXPECT_CALL(*(listener.get()), - OnConfigUpdated(_, ConfigKeyEq(1, StringToId("yyy")), StatsdConfigEq(93))) + OnConfigUpdated(_, ConfigKeyEq(1, StringToId("yyy")), StatsdConfigEq(93), true)) .RetiresOnSaturation(); manager->UpdateConfig(ConfigKey(1, StringToId("yyy")), config93); // Add one with the same name but a different uid EXPECT_CALL(*(listener.get()), - OnConfigUpdated(_, ConfigKeyEq(2, StringToId("zzz")), StatsdConfigEq(94))) + OnConfigUpdated(_, ConfigKeyEq(2, StringToId("zzz")), StatsdConfigEq(94), true)) .RetiresOnSaturation(); manager->UpdateConfig(ConfigKey(2, StringToId("zzz")), config94); @@ -143,7 +143,7 @@ TEST(ConfigManagerTest, TestRemoveUid) { StatsdConfig config; - EXPECT_CALL(*(listener.get()), OnConfigUpdated(_, _, _)).Times(5); + EXPECT_CALL(*(listener.get()), OnConfigUpdated(_, _, _, true)).Times(5); EXPECT_CALL(*(listener.get()), OnConfigRemoved(ConfigKeyEq(2, StringToId("xxx")))); EXPECT_CALL(*(listener.get()), OnConfigRemoved(ConfigKeyEq(2, StringToId("yyy")))); EXPECT_CALL(*(listener.get()), OnConfigRemoved(ConfigKeyEq(2, StringToId("zzz")))); diff --git a/bin/tests/e2e/ConfigUpdate_e2e_ab_test.cpp b/bin/tests/e2e/ConfigUpdate_e2e_ab_test.cpp index 17007f9a..9b1cb12d 100644 --- a/bin/tests/e2e/ConfigUpdate_e2e_ab_test.cpp +++ b/bin/tests/e2e/ConfigUpdate_e2e_ab_test.cpp @@ -53,24 +53,6 @@ StatsdConfig CreateSimpleConfig() { // Setup for parameterized tests. class ConfigUpdateE2eAbTest : public TestWithParam { -private: - string originalFlagValue; - -public: - void SetUp() override { - originalFlagValue = getFlagBool(PARTIAL_CONFIG_UPDATE_FLAG, ""); - string rawFlagName = - StringPrintf("persist.device_config.%s.%s", STATSD_NATIVE_NAMESPACE.c_str(), - PARTIAL_CONFIG_UPDATE_FLAG.c_str()); - SetProperty(rawFlagName, GetParam() ? "true" : "false"); - } - - void TearDown() override { - string rawFlagName = - StringPrintf("persist.device_config.%s.%s", STATSD_NATIVE_NAMESPACE.c_str(), - PARTIAL_CONFIG_UPDATE_FLAG.c_str()); - SetProperty(rawFlagName, originalFlagValue); - } }; INSTANTIATE_TEST_SUITE_P(ConfigUpdateE2eAbTest, ConfigUpdateE2eAbTest, testing::Bool()); @@ -99,7 +81,7 @@ TEST_P(ConfigUpdateE2eAbTest, TestUidMapVersionStringInstaller) { // Now update. config.set_version_strings_in_metric_report(false); config.set_installer_in_metric_report(true); - processor->OnConfigUpdated(baseTimeNs + 1000, cfgKey, config); + processor->OnConfigUpdated(baseTimeNs + 1000, cfgKey, config, GetParam()); EXPECT_EQ(processor->mMetricsManagers.size(), 1u); EXPECT_EQ(metricsManager == processor->mMetricsManagers.begin()->second, GetParam()); EXPECT_TRUE(metricsManager->isConfigValid()); @@ -140,7 +122,7 @@ TEST_P(ConfigUpdateE2eAbTest, TestHashStrings) { // Now update. config.set_hash_strings_in_metric_report(false); - processor->OnConfigUpdated(baseTimeNs + 1000, cfgKey, config); + processor->OnConfigUpdated(baseTimeNs + 1000, cfgKey, config, GetParam()); EXPECT_EQ(processor->mMetricsManagers.size(), 1u); EXPECT_EQ(metricsManager == processor->mMetricsManagers.begin()->second, GetParam()); EXPECT_TRUE(metricsManager->isConfigValid()); @@ -173,7 +155,7 @@ TEST_P(ConfigUpdateE2eAbTest, TestAnnotations) { annotation = config.add_annotation(); annotation->set_field_int64(22); annotation->set_field_int32(2); - processor->OnConfigUpdated(baseTimeNs + 1000, cfgKey, config); + processor->OnConfigUpdated(baseTimeNs + 1000, cfgKey, config, GetParam()); ConfigMetricsReportList reports; vector buffer; @@ -208,7 +190,7 @@ TEST_P(ConfigUpdateE2eAbTest, TestPersistLocally) { // Now update. config.set_persist_locally(true); - processor->OnConfigUpdated(baseTimeNs + 1000, cfgKey, config); + processor->OnConfigUpdated(baseTimeNs + 1000, cfgKey, config, GetParam()); // Should get 2: 1 in memory + 1 on disk. Both should be saved on disk. reports.Clear(); @@ -245,7 +227,7 @@ TEST_P(ConfigUpdateE2eAbTest, TestNoReportMetrics) { // Now update. config.clear_no_report_metric(); config.add_no_report_metric(config.count_metric(1).id()); - processor->OnConfigUpdated(baseTimeNs + 1000, cfgKey, config); + processor->OnConfigUpdated(baseTimeNs + 1000, cfgKey, config, GetParam()); ConfigMetricsReportList reports; vector buffer; @@ -282,7 +264,7 @@ TEST_P(ConfigUpdateE2eAbTest, TestAtomsAllowedFromAnyUid) { // Now update. Allow plugged state to be logged from any uid, so the atom will be counted. config.add_whitelisted_atom_ids(util::PLUGGED_STATE_CHANGED); - processor->OnConfigUpdated(baseTimeNs + 1000, cfgKey, config); + processor->OnConfigUpdated(baseTimeNs + 1000, cfgKey, config, GetParam()); unique_ptr event2 = CreateBatteryStateChangedEvent( baseTimeNs + 2000, BatteryPluggedStateEnum::BATTERY_PLUGGED_USB); processor->OnLogEvent(event.get()); @@ -311,7 +293,7 @@ TEST_P(ConfigUpdateE2eAbTest, TestConfigTtl) { EXPECT_EQ(metricsManager->getTtlEndNs(), baseTimeNs + NS_PER_SEC); config.set_ttl_in_seconds(5); - processor->OnConfigUpdated(baseTimeNs + 2 * NS_PER_SEC, cfgKey, config); + processor->OnConfigUpdated(baseTimeNs + 2 * NS_PER_SEC, cfgKey, config, GetParam()); metricsManager = processor->mMetricsManagers.begin()->second; EXPECT_EQ(metricsManager->getTtlEndNs(), baseTimeNs + 7 * NS_PER_SEC); @@ -344,7 +326,7 @@ TEST_P(ConfigUpdateE2eAbTest, TestExistingGaugePullRandomOneSample) { SharedRefBase::make(), util::SUBSYSTEM_SLEEP_STATE); uint64_t updateTimeNs = bucketStartTimeNs + 60 * NS_PER_SEC; - processor->OnConfigUpdated(updateTimeNs, key, config); + processor->OnConfigUpdated(updateTimeNs, key, config, GetParam()); uint64_t dumpTimeNs = bucketStartTimeNs + 90 * NS_PER_SEC; ConfigMetricsReportList reports; vector buffer; diff --git a/bin/tests/e2e/ConfigUpdate_e2e_test.cpp b/bin/tests/e2e/ConfigUpdate_e2e_test.cpp index 555971a5..1e9b95a6 100644 --- a/bin/tests/e2e/ConfigUpdate_e2e_test.cpp +++ b/bin/tests/e2e/ConfigUpdate_e2e_test.cpp @@ -43,23 +43,6 @@ using namespace std; namespace { // Setup for test fixture. class ConfigUpdateE2eTest : public ::testing::Test { -private: - string originalFlagValue; -public: - void SetUp() override { - originalFlagValue = getFlagBool(PARTIAL_CONFIG_UPDATE_FLAG, ""); - string rawFlagName = - StringPrintf("persist.device_config.%s.%s", STATSD_NATIVE_NAMESPACE.c_str(), - PARTIAL_CONFIG_UPDATE_FLAG.c_str()); - SetProperty(rawFlagName, "true"); - } - - void TearDown() override { - string rawFlagName = - StringPrintf("persist.device_config.%s.%s", STATSD_NATIVE_NAMESPACE.c_str(), - PARTIAL_CONFIG_UPDATE_FLAG.c_str()); - SetProperty(rawFlagName, originalFlagValue); - } }; void ValidateSubsystemSleepDimension(const DimensionsValue& value, string name) { -- cgit v1.2.3 From 65cd0d7608410f913ab18fe3adc22ea02994bd7e Mon Sep 17 00:00:00 2001 From: Tej Singh Date: Thu, 25 Mar 2021 18:48:02 -0700 Subject: Fix closing file descriptors on config updates On config updates, statsd reads the config to verify if it has changed at all from before. There is a bug in this code where we return the success of reading before closing the file descriptor, which means the fd never gets closed. Test: manually pushed config updates and verified lsof -p $(pidof statsd) showed no file descriptors of deleted files being held. Bug: 176967529 Change-Id: Ifa66e2128cfff30298c65b6e0740ceefd26d66be Merged-In: Ibb065d64be3775c699583215f7f376a1a5492ac1 --- bin/src/storage/StorageManager.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/bin/src/storage/StorageManager.cpp b/bin/src/storage/StorageManager.cpp index dcfdfe3a..782b020b 100644 --- a/bin/src/storage/StorageManager.cpp +++ b/bin/src/storage/StorageManager.cpp @@ -629,10 +629,9 @@ bool StorageManager::readConfigFromDisk(const ConfigKey& key, string* content) { int fd = open(StringPrintf("%s/%s", STATS_SERVICE_DIR, name).c_str(), O_RDONLY | O_CLOEXEC); if (fd != -1) { - if (android::base::ReadFdToString(fd, content)) { - return true; - } + bool success = android::base::ReadFdToString(fd, content); close(fd); + return success; } } } -- cgit v1.2.3 From 101626e536672ea8926488850e0f8524ef6b9e31 Mon Sep 17 00:00:00 2001 From: Tej Singh Date: Mon, 29 Mar 2021 19:08:57 -0700 Subject: E2E test for alarms Test that alarm times work across config update. This test is a bit sompler because alarms are essentially replaced on updates, since there is no state to preserve. Test: atest statsd_test Bug: 174976571 Change-Id: Id29ddafba56bf01c7f8d579c89746f9a56ca8061 Merged-In: Ibb065d64be3775c699583215f7f376a1a5492ac1 --- bin/src/StatsLogProcessor.h | 1 + bin/tests/e2e/ConfigUpdate_e2e_test.cpp | 168 +++++++++++++++++++++ .../parsing_utils/config_update_utils_test.cpp | 10 -- bin/tests/statsd_test_util.cpp | 8 + bin/tests/statsd_test_util.h | 2 + 5 files changed, 179 insertions(+), 10 deletions(-) diff --git a/bin/src/StatsLogProcessor.h b/bin/src/StatsLogProcessor.h index 0554aa1c..98bf67a6 100644 --- a/bin/src/StatsLogProcessor.h +++ b/bin/src/StatsLogProcessor.h @@ -339,6 +339,7 @@ private: FRIEND_TEST(MetricActivationE2eTest, TestCountMetricWithSameDeactivation); FRIEND_TEST(MetricActivationE2eTest, TestCountMetricWithTwoMetricsTwoDeactivations); + FRIEND_TEST(ConfigUpdateE2eTest, TestAlarms); FRIEND_TEST(ConfigUpdateE2eTest, TestGaugeMetric); FRIEND_TEST(ConfigUpdateE2eTest, TestValueMetric); FRIEND_TEST(ConfigUpdateE2eTest, TestAnomalyDurationMetric); diff --git a/bin/tests/e2e/ConfigUpdate_e2e_test.cpp b/bin/tests/e2e/ConfigUpdate_e2e_test.cpp index 1e9b95a6..444c2c90 100644 --- a/bin/tests/e2e/ConfigUpdate_e2e_test.cpp +++ b/bin/tests/e2e/ConfigUpdate_e2e_test.cpp @@ -2203,6 +2203,174 @@ TEST_F(ConfigUpdateE2eTest, TestAnomalyDurationMetric) { SubscriberReporter::getInstance().unsetBroadcastSubscriber(key, newSubId); } +TEST_F(ConfigUpdateE2eTest, TestAlarms) { + StatsdConfig config; + config.add_allowed_log_source("AID_ROOT"); + Alarm alarmPreserve = createAlarm("AlarmPreserve", /*offset*/ 5 * MS_PER_SEC, + /*period*/ TimeUnitToBucketSizeInMillis(ONE_MINUTE)); + Alarm alarmReplace = createAlarm("AlarmReplace", /*offset*/ 1, + /*period*/ TimeUnitToBucketSizeInMillis(FIVE_MINUTES)); + Alarm alarmRemove = createAlarm("AlarmRemove", /*offset*/ 1, + /*period*/ TimeUnitToBucketSizeInMillis(ONE_MINUTE)); + *config.add_alarm() = alarmReplace; + *config.add_alarm() = alarmPreserve; + *config.add_alarm() = alarmRemove; + + int preserveSubId = 1, replaceSubId = 2, removeSubId = 3; + Subscription preserveSub = createSubscription("S1", Subscription::ALARM, alarmPreserve.id()); + preserveSub.mutable_broadcast_subscriber_details()->set_subscriber_id(preserveSubId); + Subscription replaceSub = createSubscription("S2", Subscription::ALARM, alarmReplace.id()); + replaceSub.mutable_broadcast_subscriber_details()->set_subscriber_id(replaceSubId); + Subscription removeSub = createSubscription("S3", Subscription::ALARM, alarmRemove.id()); + removeSub.mutable_broadcast_subscriber_details()->set_subscriber_id(removeSubId); + *config.add_subscription() = preserveSub; + *config.add_subscription() = removeSub; + *config.add_subscription() = replaceSub; + + int64_t configUid = 123, configId = 987; + ConfigKey key(configUid, configId); + + int alarmPreserveCount = 0, alarmReplaceCount = 0, alarmRemoveCount = 0; + + // The binder calls here will happen synchronously because they are in-process. + shared_ptr preserveBroadcast = + SharedRefBase::make>(); + EXPECT_CALL(*preserveBroadcast, sendSubscriberBroadcast(configUid, configId, preserveSub.id(), + alarmPreserve.id(), _, _)) + .Times(4) + .WillRepeatedly([&alarmPreserveCount](int64_t, int64_t, int64_t, int64_t, + const vector&, + const StatsDimensionsValueParcel&) { + alarmPreserveCount++; + return Status::ok(); + }); + + shared_ptr replaceBroadcast = + SharedRefBase::make>(); + EXPECT_CALL(*replaceBroadcast, sendSubscriberBroadcast(configUid, configId, replaceSub.id(), + alarmReplace.id(), _, _)) + .Times(2) + .WillRepeatedly([&alarmReplaceCount](int64_t, int64_t, int64_t, int64_t, + const vector&, + const StatsDimensionsValueParcel&) { + alarmReplaceCount++; + return Status::ok(); + }); + + shared_ptr removeBroadcast = + SharedRefBase::make>(); + EXPECT_CALL(*removeBroadcast, sendSubscriberBroadcast(configUid, configId, removeSub.id(), + alarmRemove.id(), _, _)) + .Times(1) + .WillRepeatedly([&alarmRemoveCount](int64_t, int64_t, int64_t, int64_t, + const vector&, + const StatsDimensionsValueParcel&) { + alarmRemoveCount++; + return Status::ok(); + }); + + SubscriberReporter::getInstance().setBroadcastSubscriber(key, preserveSubId, preserveBroadcast); + SubscriberReporter::getInstance().setBroadcastSubscriber(key, replaceSubId, replaceBroadcast); + SubscriberReporter::getInstance().setBroadcastSubscriber(key, removeSubId, removeBroadcast); + + int64_t startTimeSec = 10; + sp processor = CreateStatsLogProcessor( + startTimeSec * NS_PER_SEC, startTimeSec * NS_PER_SEC, config, key); + + sp alarmMonitor = processor->getPeriodicAlarmMonitor(); + // First alarm is for alarm preserve's offset of 5 seconds. + EXPECT_EQ(alarmMonitor->getRegisteredAlarmTimeSec(), startTimeSec + 5); + + // Alarm fired at 5. AlarmPreserve should fire. + int32_t alarmFiredTimestampSec = startTimeSec + 5; + auto alarmSet = alarmMonitor->popSoonerThan(static_cast(alarmFiredTimestampSec)); + processor->onPeriodicAlarmFired(alarmFiredTimestampSec * NS_PER_SEC, alarmSet); + EXPECT_EQ(alarmPreserveCount, 1); + EXPECT_EQ(alarmReplaceCount, 0); + EXPECT_EQ(alarmRemoveCount, 0); + EXPECT_EQ(alarmMonitor->getRegisteredAlarmTimeSec(), startTimeSec + 60); + + // Alarm fired at 75. AlarmPreserve and AlarmRemove should fire. + alarmFiredTimestampSec = startTimeSec + 75; + alarmSet = alarmMonitor->popSoonerThan(static_cast(alarmFiredTimestampSec)); + processor->onPeriodicAlarmFired(alarmFiredTimestampSec * NS_PER_SEC, alarmSet); + EXPECT_EQ(alarmPreserveCount, 2); + EXPECT_EQ(alarmReplaceCount, 0); + EXPECT_EQ(alarmRemoveCount, 1); + EXPECT_EQ(alarmMonitor->getRegisteredAlarmTimeSec(), startTimeSec + 120); + + // Do config update. + StatsdConfig newConfig; + newConfig.add_allowed_log_source("AID_ROOT"); + + // Change alarm replace's definition. + alarmReplace.set_period_millis(TimeUnitToBucketSizeInMillis(ONE_MINUTE)); + Alarm alarmNew = createAlarm("AlarmNew", /*offset*/ 1, + /*period*/ TimeUnitToBucketSizeInMillis(FIVE_MINUTES)); + *newConfig.add_alarm() = alarmNew; + *newConfig.add_alarm() = alarmPreserve; + *newConfig.add_alarm() = alarmReplace; + + int newSubId = 4; + Subscription newSub = createSubscription("S4", Subscription::ALARM, alarmNew.id()); + newSub.mutable_broadcast_subscriber_details()->set_subscriber_id(newSubId); + *newConfig.add_subscription() = newSub; + *newConfig.add_subscription() = replaceSub; + *newConfig.add_subscription() = preserveSub; + + int alarmNewCount = 0; + shared_ptr newBroadcast = + SharedRefBase::make>(); + EXPECT_CALL(*newBroadcast, + sendSubscriberBroadcast(configUid, configId, newSub.id(), alarmNew.id(), _, _)) + .Times(1) + .WillRepeatedly([&alarmNewCount](int64_t, int64_t, int64_t, int64_t, + const vector&, + const StatsDimensionsValueParcel&) { + alarmNewCount++; + return Status::ok(); + }); + SubscriberReporter::getInstance().setBroadcastSubscriber(key, newSubId, newBroadcast); + + processor->OnConfigUpdated((startTimeSec + 90) * NS_PER_SEC, key, newConfig); + // After the update, the alarm time should remain unchanged since alarm replace now fires every + // minute with no offset. + EXPECT_EQ(alarmMonitor->getRegisteredAlarmTimeSec(), startTimeSec + 120); + + // Alarm fired at 120. AlermReplace should fire. + alarmFiredTimestampSec = startTimeSec + 120; + alarmSet = alarmMonitor->popSoonerThan(static_cast(alarmFiredTimestampSec)); + processor->onPeriodicAlarmFired(alarmFiredTimestampSec * NS_PER_SEC, alarmSet); + EXPECT_EQ(alarmPreserveCount, 2); + EXPECT_EQ(alarmReplaceCount, 1); + EXPECT_EQ(alarmNewCount, 0); + EXPECT_EQ(alarmMonitor->getRegisteredAlarmTimeSec(), startTimeSec + 125); + + // Alarm fired at 130. AlarmPreserve should fire. + alarmFiredTimestampSec = startTimeSec + 130; + alarmSet = alarmMonitor->popSoonerThan(static_cast(alarmFiredTimestampSec)); + processor->onPeriodicAlarmFired(alarmFiredTimestampSec * NS_PER_SEC, alarmSet); + EXPECT_EQ(alarmPreserveCount, 3); + EXPECT_EQ(alarmReplaceCount, 1); + EXPECT_EQ(alarmNewCount, 0); + EXPECT_EQ(alarmMonitor->getRegisteredAlarmTimeSec(), startTimeSec + 180); + + // Alarm fired late at 310. All alerms should fire. + alarmFiredTimestampSec = startTimeSec + 310; + alarmSet = alarmMonitor->popSoonerThan(static_cast(alarmFiredTimestampSec)); + processor->onPeriodicAlarmFired(alarmFiredTimestampSec * NS_PER_SEC, alarmSet); + EXPECT_EQ(alarmPreserveCount, 4); + EXPECT_EQ(alarmReplaceCount, 2); + EXPECT_EQ(alarmNewCount, 1); + EXPECT_EQ(alarmMonitor->getRegisteredAlarmTimeSec(), startTimeSec + 360); + + // Clear subscribers + SubscriberReporter::getInstance().unsetBroadcastSubscriber(key, preserveSubId); + SubscriberReporter::getInstance().unsetBroadcastSubscriber(key, replaceSubId); + SubscriberReporter::getInstance().unsetBroadcastSubscriber(key, removeSubId); + SubscriberReporter::getInstance().unsetBroadcastSubscriber(key, newSubId); +} + TEST_F(ConfigUpdateE2eTest, TestNewDurationExistingWhat) { StatsdConfig config; config.add_allowed_log_source("AID_ROOT"); diff --git a/bin/tests/metrics/parsing_utils/config_update_utils_test.cpp b/bin/tests/metrics/parsing_utils/config_update_utils_test.cpp index e43c16a7..31ab73a9 100644 --- a/bin/tests/metrics/parsing_utils/config_update_utils_test.cpp +++ b/bin/tests/metrics/parsing_utils/config_update_utils_test.cpp @@ -128,16 +128,6 @@ EventMetric createEventMetric(string name, int64_t what, optional condi } return metric; } - - - -Alarm createAlarm(string name, int64_t offsetMillis, int64_t periodMillis) { - Alarm alarm; - alarm.set_id(StringToId(name)); - alarm.set_offset_millis(offsetMillis); - alarm.set_period_millis(periodMillis); - return alarm; -} } // anonymous namespace TEST_F(ConfigUpdateTest, TestSimpleMatcherPreserve) { diff --git a/bin/tests/statsd_test_util.cpp b/bin/tests/statsd_test_util.cpp index 3ea520ef..ad9309aa 100644 --- a/bin/tests/statsd_test_util.cpp +++ b/bin/tests/statsd_test_util.cpp @@ -522,6 +522,14 @@ Alert createAlert(const string& name, const int64_t metricId, const int buckets, return alert; } +Alarm createAlarm(const string& name, const int64_t offsetMillis, const int64_t periodMillis) { + Alarm alarm; + alarm.set_id(StringToId(name)); + alarm.set_offset_millis(offsetMillis); + alarm.set_period_millis(periodMillis); + return alarm; +} + Subscription createSubscription(const string& name, const Subscription_RuleType type, const int64_t ruleId) { Subscription subscription; diff --git a/bin/tests/statsd_test_util.h b/bin/tests/statsd_test_util.h index b8406766..fd4b2d3f 100644 --- a/bin/tests/statsd_test_util.h +++ b/bin/tests/statsd_test_util.h @@ -221,6 +221,8 @@ ValueMetric createValueMetric(const string& name, const AtomMatcher& what, const Alert createAlert(const string& name, const int64_t metricId, const int buckets, const int64_t triggerSum); +Alarm createAlarm(const string& name, const int64_t offsetMillis, const int64_t periodMillis); + Subscription createSubscription(const string& name, const Subscription_RuleType type, const int64_t ruleId); -- cgit v1.2.3 From 56dd55e737c10e6ec7ae3d5b8d40d5cc761d7bd0 Mon Sep 17 00:00:00 2001 From: Tej Singh Date: Wed, 31 Mar 2021 21:10:49 -0700 Subject: E2e Tests for event metric Test replaced/preserved/added/removed event metrics. Also tests with combination conditions/matchers since we dont have as much e2e coverage there. Test: atest statsd_test Bug: 174976406 Change-Id: I1e3721f1f3eb02f8931907bbe7624fcba609c963 Merged-In: Ibb065d64be3775c699583215f7f376a1a5492ac1 --- bin/tests/e2e/ConfigUpdate_e2e_test.cpp | 221 +++++++++++++++++++++ .../parsing_utils/config_update_utils_test.cpp | 10 - bin/tests/statsd_test_util.cpp | 15 ++ bin/tests/statsd_test_util.h | 6 + 4 files changed, 242 insertions(+), 10 deletions(-) diff --git a/bin/tests/e2e/ConfigUpdate_e2e_test.cpp b/bin/tests/e2e/ConfigUpdate_e2e_test.cpp index 444c2c90..efb8274d 100644 --- a/bin/tests/e2e/ConfigUpdate_e2e_test.cpp +++ b/bin/tests/e2e/ConfigUpdate_e2e_test.cpp @@ -54,6 +54,227 @@ void ValidateSubsystemSleepDimension(const DimensionsValue& value, string name) } // Anonymous namespace. +TEST_F(ConfigUpdateE2eTest, TestEventMetric) { + StatsdConfig config; + config.add_allowed_log_source("AID_ROOT"); + + AtomMatcher syncStartMatcher = CreateSyncStartAtomMatcher(); + *config.add_atom_matcher() = syncStartMatcher; + AtomMatcher wakelockAcquireMatcher = CreateAcquireWakelockAtomMatcher(); + *config.add_atom_matcher() = wakelockAcquireMatcher; + AtomMatcher screenOnMatcher = CreateScreenTurnedOnAtomMatcher(); + *config.add_atom_matcher() = screenOnMatcher; + AtomMatcher screenOffMatcher = CreateScreenTurnedOffAtomMatcher(); + *config.add_atom_matcher() = screenOffMatcher; + AtomMatcher batteryPluggedUsbMatcher = CreateBatteryStateUsbMatcher(); + *config.add_atom_matcher() = batteryPluggedUsbMatcher; + AtomMatcher unpluggedMatcher = CreateBatteryStateNoneMatcher(); + *config.add_atom_matcher() = unpluggedMatcher; + + AtomMatcher* combinationMatcher = config.add_atom_matcher(); + combinationMatcher->set_id(StringToId("SyncOrWakelockMatcher")); + combinationMatcher->mutable_combination()->set_operation(LogicalOperation::OR); + addMatcherToMatcherCombination(syncStartMatcher, combinationMatcher); + addMatcherToMatcherCombination(wakelockAcquireMatcher, combinationMatcher); + + Predicate screenOnPredicate = CreateScreenIsOnPredicate(); + *config.add_predicate() = screenOnPredicate; + Predicate unpluggedPredicate = CreateDeviceUnpluggedPredicate(); + *config.add_predicate() = unpluggedPredicate; + + Predicate* combinationPredicate = config.add_predicate(); + combinationPredicate->set_id(StringToId("ScreenOnOrUnpluggedPred)")); + combinationPredicate->mutable_combination()->set_operation(LogicalOperation::OR); + addPredicateToPredicateCombination(screenOnPredicate, combinationPredicate); + addPredicateToPredicateCombination(unpluggedPredicate, combinationPredicate); + + EventMetric eventPersist = + createEventMetric("SyncOrWlWhileScreenOnOrUnplugged", combinationMatcher->id(), + combinationPredicate->id()); + EventMetric eventChange = createEventMetric( + "WakelockWhileScreenOn", wakelockAcquireMatcher.id(), screenOnPredicate.id()); + EventMetric eventRemove = createEventMetric("Syncs", syncStartMatcher.id(), nullopt); + + *config.add_event_metric() = eventRemove; + *config.add_event_metric() = eventPersist; + *config.add_event_metric() = eventChange; + + ConfigKey key(123, 987); + uint64_t bucketStartTimeNs = 10000000000; // 0:10 + sp processor = + CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, key); + + int app1Uid = 123; + vector attributionUids1 = {app1Uid}; + vector attributionTags1 = {"App1"}; + + // Initialize log events before update. + std::vector> events; + events.push_back(CreateAcquireWakelockEvent(bucketStartTimeNs + 5 * NS_PER_SEC, + attributionUids1, attributionTags1, + "wl1")); // Not kept. + events.push_back(CreateScreenStateChangedEvent( + bucketStartTimeNs + 10 * NS_PER_SEC, + android::view::DISPLAY_STATE_ON)); // Condition true for change. + events.push_back(CreateSyncStartEvent(bucketStartTimeNs + 15 * NS_PER_SEC, attributionUids1, + attributionTags1, + "sync1")); // Kept for persist & remove. + events.push_back(CreateBatteryStateChangedEvent( + bucketStartTimeNs + 20 * NS_PER_SEC, + BatteryPluggedStateEnum::BATTERY_PLUGGED_NONE)); // Condition true for preserve. + events.push_back(CreateAcquireWakelockEvent(bucketStartTimeNs + 25 * NS_PER_SEC, + attributionUids1, attributionTags1, + "wl2")); // Kept by persist and change. + events.push_back(CreateScreenStateChangedEvent( + bucketStartTimeNs + 30 * NS_PER_SEC, + android::view::DISPLAY_STATE_OFF)); // Condition false for change. + events.push_back(CreateSyncStartEvent(bucketStartTimeNs + 35 * NS_PER_SEC, attributionUids1, + attributionTags1, + "sync2")); // Kept for persist & remove. + events.push_back(CreateAcquireWakelockEvent(bucketStartTimeNs + 40 * NS_PER_SEC, + attributionUids1, attributionTags1, + "wl3")); // Kept by persist. + + // Send log events to StatsLogProcessor. + for (auto& event : events) { + processor->OnLogEvent(event.get()); + } + + // Do update. Add matchers/conditions in different order to force indices to change. + StatsdConfig newConfig; + newConfig.add_allowed_log_source("AID_ROOT"); + + *newConfig.add_atom_matcher() = screenOnMatcher; + *newConfig.add_atom_matcher() = batteryPluggedUsbMatcher; + *newConfig.add_atom_matcher() = syncStartMatcher; + *newConfig.add_atom_matcher() = *combinationMatcher; + *newConfig.add_atom_matcher() = wakelockAcquireMatcher; + *newConfig.add_atom_matcher() = screenOffMatcher; + *newConfig.add_atom_matcher() = unpluggedMatcher; + *newConfig.add_predicate() = *combinationPredicate; + *newConfig.add_predicate() = unpluggedPredicate; + *newConfig.add_predicate() = screenOnPredicate; + + // Add metrics. Note that the condition of eventChange will go from false to true. + eventChange.set_condition(unpluggedPredicate.id()); + *newConfig.add_event_metric() = eventChange; + EventMetric eventNew = createEventMetric("ScreenOn", screenOnMatcher.id(), nullopt); + *newConfig.add_event_metric() = eventNew; + *newConfig.add_event_metric() = eventPersist; + + int64_t updateTimeNs = bucketStartTimeNs + 60 * NS_PER_SEC; + processor->OnConfigUpdated(updateTimeNs, key, newConfig); + + // Send events after the update. + events.clear(); + events.push_back(CreateAcquireWakelockEvent(bucketStartTimeNs + 65 * NS_PER_SEC, + attributionUids1, attributionTags1, + "wl4")); // Kept by preserve & change. + events.push_back(CreateBatteryStateChangedEvent( + bucketStartTimeNs + 70 * NS_PER_SEC, + BatteryPluggedStateEnum::BATTERY_PLUGGED_USB)); // All conditions are false. + events.push_back(CreateAcquireWakelockEvent(bucketStartTimeNs + 75 * NS_PER_SEC, + attributionUids1, attributionTags1, + "wl5")); // Not kept. + events.push_back(CreateScreenStateChangedEvent( + bucketStartTimeNs + 80 * NS_PER_SEC, + android::view::DISPLAY_STATE_ON)); // Condition true for preserve, event kept by new. + events.push_back(CreateAcquireWakelockEvent(bucketStartTimeNs + 85 * NS_PER_SEC, + attributionUids1, attributionTags1, + "wl6")); // Kept by preserve. + events.push_back(CreateSyncStartEvent(bucketStartTimeNs + 90 * NS_PER_SEC, attributionUids1, + attributionTags1, "sync3")); // Kept by preserve. + + // Send log events to StatsLogProcessor. + for (auto& event : events) { + processor->OnLogEvent(event.get()); + } + uint64_t dumpTimeNs = bucketStartTimeNs + 100 * NS_PER_SEC; + ConfigMetricsReportList reports; + vector buffer; + processor->onDumpReport(key, dumpTimeNs, true, true, ADB_DUMP, FAST, &buffer); + EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size())); + backfillDimensionPath(&reports); + backfillStringInReport(&reports); + backfillStartEndTimestamp(&reports); + ASSERT_EQ(reports.reports_size(), 2); + + // Report from before update. + ConfigMetricsReport report = reports.reports(0); + ASSERT_EQ(report.metrics_size(), 3); + // Event remove. Captured sync events. There were 2 syncs before the update. + StatsLogReport eventRemoveBefore = report.metrics(0); + EXPECT_EQ(eventRemoveBefore.metric_id(), eventRemove.id()); + EXPECT_TRUE(eventRemoveBefore.has_event_metrics()); + ASSERT_EQ(eventRemoveBefore.event_metrics().data_size(), 2); + auto data = eventRemoveBefore.event_metrics().data(0); + EXPECT_EQ(data.elapsed_timestamp_nanos(), bucketStartTimeNs + 15 * NS_PER_SEC); + EXPECT_EQ(data.atom().sync_state_changed().sync_name(), "sync1"); + data = eventRemoveBefore.event_metrics().data(1); + EXPECT_EQ(data.elapsed_timestamp_nanos(), bucketStartTimeNs + 35 * NS_PER_SEC); + EXPECT_EQ(data.atom().sync_state_changed().sync_name(), "sync2"); + + // Captured wakelocks & syncs while screen on or unplugged. There were 2 wakelocks and 2 syncs. + StatsLogReport eventPersistBefore = report.metrics(1); + EXPECT_EQ(eventPersistBefore.metric_id(), eventPersist.id()); + EXPECT_TRUE(eventPersistBefore.has_event_metrics()); + ASSERT_EQ(eventPersistBefore.event_metrics().data_size(), 3); + data = eventPersistBefore.event_metrics().data(0); + EXPECT_EQ(data.elapsed_timestamp_nanos(), bucketStartTimeNs + 25 * NS_PER_SEC); + EXPECT_EQ(data.atom().wakelock_state_changed().tag(), "wl2"); + data = eventPersistBefore.event_metrics().data(1); + EXPECT_EQ(data.elapsed_timestamp_nanos(), bucketStartTimeNs + 35 * NS_PER_SEC); + EXPECT_EQ(data.atom().sync_state_changed().sync_name(), "sync2"); + data = eventPersistBefore.event_metrics().data(2); + EXPECT_EQ(data.elapsed_timestamp_nanos(), bucketStartTimeNs + 40 * NS_PER_SEC); + EXPECT_EQ(data.atom().wakelock_state_changed().tag(), "wl3"); + + // Captured wakelock events while screen on. There was 1 before the update. + StatsLogReport eventChangeBefore = report.metrics(2); + EXPECT_EQ(eventChangeBefore.metric_id(), eventChange.id()); + EXPECT_TRUE(eventChangeBefore.has_event_metrics()); + ASSERT_EQ(eventChangeBefore.event_metrics().data_size(), 1); + data = eventChangeBefore.event_metrics().data(0); + EXPECT_EQ(data.elapsed_timestamp_nanos(), bucketStartTimeNs + 25 * NS_PER_SEC); + EXPECT_EQ(data.atom().wakelock_state_changed().tag(), "wl2"); + + // Report from after update. + report = reports.reports(1); + ASSERT_EQ(report.metrics_size(), 3); + // Captured wakelocks while unplugged. There was 1 after the update. + StatsLogReport eventChangeAfter = report.metrics(0); + EXPECT_EQ(eventChangeAfter.metric_id(), eventChange.id()); + EXPECT_TRUE(eventChangeAfter.has_event_metrics()); + ASSERT_EQ(eventChangeAfter.event_metrics().data_size(), 1); + data = eventChangeAfter.event_metrics().data(0); + EXPECT_EQ(data.elapsed_timestamp_nanos(), bucketStartTimeNs + 65 * NS_PER_SEC); + EXPECT_EQ(data.atom().wakelock_state_changed().tag(), "wl4"); + + // Captured screen on events. There was 1 after the update. + StatsLogReport eventNewAfter = report.metrics(1); + EXPECT_EQ(eventNewAfter.metric_id(), eventNew.id()); + EXPECT_TRUE(eventNewAfter.has_event_metrics()); + ASSERT_EQ(eventNewAfter.event_metrics().data_size(), 1); + data = eventNewAfter.event_metrics().data(0); + EXPECT_EQ(data.elapsed_timestamp_nanos(), bucketStartTimeNs + 80 * NS_PER_SEC); + EXPECT_EQ(data.atom().screen_state_changed().state(), android::view::DISPLAY_STATE_ON); + + // There were 2 wakelocks and 1 sync after the update while the condition was true. + StatsLogReport eventPersistAfter = report.metrics(2); + EXPECT_EQ(eventPersistAfter.metric_id(), eventPersist.id()); + EXPECT_TRUE(eventPersistAfter.has_event_metrics()); + ASSERT_EQ(eventPersistAfter.event_metrics().data_size(), 3); + data = eventPersistAfter.event_metrics().data(0); + EXPECT_EQ(data.elapsed_timestamp_nanos(), bucketStartTimeNs + 65 * NS_PER_SEC); + EXPECT_EQ(data.atom().wakelock_state_changed().tag(), "wl4"); + data = eventPersistAfter.event_metrics().data(1); + EXPECT_EQ(data.elapsed_timestamp_nanos(), bucketStartTimeNs + 85 * NS_PER_SEC); + EXPECT_EQ(data.atom().wakelock_state_changed().tag(), "wl6"); + data = eventPersistAfter.event_metrics().data(2); + EXPECT_EQ(data.elapsed_timestamp_nanos(), bucketStartTimeNs + 90 * NS_PER_SEC); + EXPECT_EQ(data.atom().sync_state_changed().sync_name(), "sync3"); +} + TEST_F(ConfigUpdateE2eTest, TestCountMetric) { StatsdConfig config; config.add_allowed_log_source("AID_ROOT"); diff --git a/bin/tests/metrics/parsing_utils/config_update_utils_test.cpp b/bin/tests/metrics/parsing_utils/config_update_utils_test.cpp index 31ab73a9..b29972f7 100644 --- a/bin/tests/metrics/parsing_utils/config_update_utils_test.cpp +++ b/bin/tests/metrics/parsing_utils/config_update_utils_test.cpp @@ -118,16 +118,6 @@ bool initConfig(const StatsdConfig& config) { tmpDeactivationAtomTrackerToMetricMap, oldAlertTrackerMap, metricsWithActivation, oldStateHashes, noReportMetricIds); } - -EventMetric createEventMetric(string name, int64_t what, optional condition) { - EventMetric metric; - metric.set_id(StringToId(name)); - metric.set_what(what); - if (condition) { - metric.set_condition(condition.value()); - } - return metric; -} } // anonymous namespace TEST_F(ConfigUpdateTest, TestSimpleMatcherPreserve) { diff --git a/bin/tests/statsd_test_util.cpp b/bin/tests/statsd_test_util.cpp index ad9309aa..65062724 100644 --- a/bin/tests/statsd_test_util.cpp +++ b/bin/tests/statsd_test_util.cpp @@ -240,6 +240,10 @@ AtomMatcher CreateProcessCrashAtomMatcher() { "Crashed", ProcessLifeCycleStateChanged::CRASHED); } +void addMatcherToMatcherCombination(const AtomMatcher& matcher, AtomMatcher* combinationMatcher) { + combinationMatcher->mutable_combination()->add_matcher(matcher.id()); +} + Predicate CreateScheduledJobPredicate() { Predicate predicate; predicate.set_id(StringToId("ScheduledJobRunningPredicate")); @@ -445,6 +449,17 @@ FieldMatcher CreateAttributionUidAndOtherDimensions(const int atomId, return dimensions; } +EventMetric createEventMetric(const string& name, const int64_t what, + const optional& condition) { + EventMetric metric; + metric.set_id(StringToId(name)); + metric.set_what(what); + if (condition) { + metric.set_condition(condition.value()); + } + return metric; +} + CountMetric createCountMetric(const string& name, const int64_t what, const optional& condition, const vector& states) { CountMetric metric; diff --git a/bin/tests/statsd_test_util.h b/bin/tests/statsd_test_util.h index fd4b2d3f..93d02608 100644 --- a/bin/tests/statsd_test_util.h +++ b/bin/tests/statsd_test_util.h @@ -128,6 +128,9 @@ AtomMatcher CreateMoveToForegroundAtomMatcher(); // Create AtomMatcher proto for process crashes AtomMatcher CreateProcessCrashAtomMatcher() ; +// Add an AtomMatcher to a combination AtomMatcher. +void addMatcherToMatcherCombination(const AtomMatcher& matcher, AtomMatcher* combinationMatcher); + // Create Predicate proto for screen is on. Predicate CreateScreenIsOnPredicate(); @@ -203,6 +206,9 @@ FieldMatcher CreateAttributionUidAndOtherDimensions(const int atomId, const std::vector& positions, const std::vector& fields); +EventMetric createEventMetric(const string& name, const int64_t what, + const optional& condition); + CountMetric createCountMetric(const string& name, const int64_t what, const optional& condition, const vector& states); -- cgit v1.2.3 From ee2835d90642d762a5c3cfdb243c6af813411c1f Mon Sep 17 00:00:00 2001 From: Tej Singh Date: Tue, 13 Apr 2021 15:59:13 -0700 Subject: Update the number of non-platform pushed atom ids We have more of these now, so increase the number of atoms tracked. Bug: 185273649 Test: TH Change-Id: I414d52867b0ebc30a64a4b09e1b43287224a754d Merged-In: Ibb065d64be3775c699583215f7f376a1a5492ac1 --- bin/src/guardrail/StatsdStats.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/src/guardrail/StatsdStats.h b/bin/src/guardrail/StatsdStats.h index 3e31bd28..40cfa3ac 100644 --- a/bin/src/guardrail/StatsdStats.h +++ b/bin/src/guardrail/StatsdStats.h @@ -163,7 +163,7 @@ public: static const int64_t kPullMaxDelayNs = 30 * NS_PER_SEC; // Maximum number of pushed atoms statsd stats will track above kMaxPushedAtomId. - static const int kMaxNonPlatformPushedAtoms = 100; + static const int kMaxNonPlatformPushedAtoms = 400; // Maximum atom id value that we consider a platform pushed atom. // This should be updated once highest pushed atom id in atoms.proto approaches this value. -- cgit v1.2.3 From a8716d3da8b40430920b0b5f430397a9e6553f3a Mon Sep 17 00:00:00 2001 From: Tej Singh Date: Mon, 19 Apr 2021 20:26:29 -0700 Subject: Rename StatsD/bin to Statsd/statsd statsd is a more intuitive name for this, as it is the files for the stats daemon. It also matches the previous structure of frameworks/base/cmds/statsd. There may be some confusion between StatsD (in packages/modules/StatsD) and the inner statsd. The difference is that StatsD is the name of the apex, containing statsd but also the supporting libraries and jars. The lowercase statsd is for statsd the daemon. Test: m Bug: 182597781 Change-Id: Ibb065d64be3775c699583215f7f376a1a5492ac1 Merged-In: Ibb065d64be3775c699583215f7f376a1a5492ac1 --- bin/.clang-format | 17 - bin/Android.bp | 431 -- bin/TEST_MAPPING | 7 - bin/benchmark/duration_metric_benchmark.cpp | 314 - bin/benchmark/filter_value_benchmark.cpp | 72 - .../get_dimensions_for_condition_benchmark.cpp | 77 - bin/benchmark/hello_world_benchmark.cpp | 29 - bin/benchmark/log_event_benchmark.cpp | 50 - bin/benchmark/main.cpp | 19 - bin/benchmark/metric_util.cpp | 379 -- bin/benchmark/metric_util.h | 140 - bin/benchmark/stats_write_benchmark.cpp | 40 - bin/src/FieldValue.cpp | 474 -- bin/src/FieldValue.h | 462 -- bin/src/HashableDimensionKey.cpp | 372 -- bin/src/HashableDimensionKey.h | 247 - bin/src/Log.h | 33 - bin/src/StatsLogProcessor.cpp | 1147 ---- bin/src/StatsLogProcessor.h | 380 -- bin/src/StatsService.cpp | 1330 ---- bin/src/StatsService.h | 420 -- bin/src/active_config_list.proto | 57 - bin/src/annotations.h | 33 - bin/src/anomaly/AlarmMonitor.cpp | 139 - bin/src/anomaly/AlarmMonitor.h | 162 - bin/src/anomaly/AlarmTracker.cpp | 94 - bin/src/anomaly/AlarmTracker.h | 81 - bin/src/anomaly/AnomalyTracker.cpp | 326 - bin/src/anomaly/AnomalyTracker.h | 230 - bin/src/anomaly/DurationAnomalyTracker.cpp | 115 - bin/src/anomaly/DurationAnomalyTracker.h | 79 - bin/src/anomaly/indexed_priority_queue.h | 224 - bin/src/anomaly/subscriber_util.cpp | 70 - bin/src/anomaly/subscriber_util.h | 33 - bin/src/condition/CombinationConditionTracker.cpp | 244 - bin/src/condition/CombinationConditionTracker.h | 115 - bin/src/condition/ConditionTimer.h | 107 - bin/src/condition/ConditionTracker.h | 186 - bin/src/condition/ConditionWizard.cpp | 70 - bin/src/condition/ConditionWizard.h | 68 - bin/src/condition/SimpleConditionTracker.cpp | 409 -- bin/src/condition/SimpleConditionTracker.h | 140 - bin/src/condition/condition_util.cpp | 93 - bin/src/condition/condition_util.h | 43 - bin/src/config/ConfigKey.cpp | 54 - bin/src/config/ConfigKey.h | 89 - bin/src/config/ConfigListener.cpp | 31 - bin/src/config/ConfigListener.h | 52 - bin/src/config/ConfigManager.cpp | 375 -- bin/src/config/ConfigManager.h | 181 - bin/src/experiment_ids.proto | 29 - bin/src/external/Perfetto.cpp | 139 - bin/src/external/Perfetto.h | 37 - bin/src/external/PullDataReceiver.h | 41 - bin/src/external/PullResultReceiver.cpp | 39 - bin/src/external/PullResultReceiver.h | 48 - bin/src/external/PullUidProvider.h | 39 - bin/src/external/StatsCallbackPuller.cpp | 116 - bin/src/external/StatsCallbackPuller.h | 46 - bin/src/external/StatsPuller.cpp | 126 - bin/src/external/StatsPuller.h | 119 - bin/src/external/StatsPullerManager.cpp | 371 -- bin/src/external/StatsPullerManager.h | 192 - bin/src/external/TrainInfoPuller.cpp | 55 - bin/src/external/TrainInfoPuller.h | 38 - bin/src/external/puller_util.cpp | 153 - bin/src/external/puller_util.h | 34 - bin/src/flags/flags.cpp | 37 - bin/src/flags/flags.h | 36 - bin/src/guardrail/StatsdStats.cpp | 1110 ---- bin/src/guardrail/StatsdStats.h | 686 -- bin/src/hash.cpp | 142 - bin/src/hash.h | 47 - bin/src/logd/LogEvent.cpp | 618 -- bin/src/logd/LogEvent.h | 331 - bin/src/logd/LogEventQueue.cpp | 62 - bin/src/logd/LogEventQueue.h | 57 - bin/src/main.cpp | 113 - bin/src/matchers/AtomMatchingTracker.h | 118 - .../matchers/CombinationAtomMatchingTracker.cpp | 140 - bin/src/matchers/CombinationAtomMatchingTracker.h | 58 - bin/src/matchers/EventMatcherWizard.cpp | 35 - bin/src/matchers/EventMatcherWizard.h | 41 - bin/src/matchers/SimpleAtomMatchingTracker.cpp | 81 - bin/src/matchers/SimpleAtomMatchingTracker.h | 58 - bin/src/matchers/matcher_util.cpp | 373 -- bin/src/matchers/matcher_util.h | 44 - bin/src/metadata_util.cpp | 122 - bin/src/metadata_util.h | 32 - bin/src/metrics/CountMetricProducer.cpp | 440 -- bin/src/metrics/CountMetricProducer.h | 143 - bin/src/metrics/DurationMetricProducer.cpp | 822 --- bin/src/metrics/DurationMetricProducer.h | 204 - bin/src/metrics/EventMetricProducer.cpp | 214 - bin/src/metrics/EventMetricProducer.h | 103 - bin/src/metrics/GaugeMetricProducer.cpp | 681 -- bin/src/metrics/GaugeMetricProducer.h | 234 - bin/src/metrics/MetricProducer.cpp | 356 - bin/src/metrics/MetricProducer.h | 593 -- bin/src/metrics/MetricsManager.cpp | 773 --- bin/src/metrics/MetricsManager.h | 385 -- bin/src/metrics/ValueMetricProducer.cpp | 1282 ---- bin/src/metrics/ValueMetricProducer.h | 396 -- bin/src/metrics/duration_helper/DurationTracker.h | 281 - .../metrics/duration_helper/MaxDurationTracker.cpp | 334 - .../metrics/duration_helper/MaxDurationTracker.h | 94 - .../duration_helper/OringDurationTracker.cpp | 473 -- .../metrics/duration_helper/OringDurationTracker.h | 98 - .../metrics/parsing_utils/config_update_utils.cpp | 1121 ---- .../metrics/parsing_utils/config_update_utils.h | 267 - .../metrics/parsing_utils/metrics_manager_util.cpp | 1246 ---- .../metrics/parsing_utils/metrics_manager_util.h | 343 - bin/src/packages/PackageInfoListener.h | 47 - bin/src/packages/UidMap.cpp | 564 -- bin/src/packages/UidMap.h | 226 - bin/src/shell/ShellSubscriber.cpp | 245 - bin/src/shell/ShellSubscriber.h | 146 - bin/src/shell/shell_config.proto | 39 - bin/src/shell/shell_data.proto | 29 - bin/src/socket/StatsSocketListener.cpp | 156 - bin/src/socket/StatsSocketListener.h | 54 - bin/src/state/StateListener.h | 54 - bin/src/state/StateManager.cpp | 112 - bin/src/state/StateManager.h | 103 - bin/src/state/StateTracker.cpp | 190 - bin/src/state/StateTracker.h | 94 - bin/src/stats_log.proto | 556 -- bin/src/stats_log_util.cpp | 625 -- bin/src/stats_log_util.h | 121 - bin/src/stats_util.h | 46 - bin/src/statscompanion_util.cpp | 35 - bin/src/statscompanion_util.h | 33 - bin/src/statsd_config.proto | 524 -- bin/src/statsd_metadata.proto | 67 - bin/src/storage/StorageManager.cpp | 780 --- bin/src/storage/StorageManager.h | 168 - bin/src/subscriber/IncidentdReporter.cpp | 169 - bin/src/subscriber/IncidentdReporter.h | 36 - bin/src/subscriber/SubscriberReporter.cpp | 149 - bin/src/subscriber/SubscriberReporter.h | 112 - bin/src/uid_data.proto | 36 - bin/src/utils/MultiConditionTrigger.cpp | 57 - bin/src/utils/MultiConditionTrigger.h | 55 - bin/statsd_test.xml | 37 - bin/tests/AlarmMonitor_test.cpp | 69 - bin/tests/ConfigManager_test.cpp | 159 - bin/tests/FieldValue_test.cpp | 653 -- bin/tests/HashableDimensionKey_test.cpp | 137 - bin/tests/LogEntryMatcher_test.cpp | 809 --- bin/tests/LogEvent_test.cpp | 481 -- bin/tests/LogReader_test.cpp | 21 - bin/tests/MetricsManager_test.cpp | 325 - bin/tests/StatsLogProcessor_test.cpp | 1886 ------ bin/tests/StatsService_test.cpp | 104 - bin/tests/UidMap_test.cpp | 426 -- bin/tests/anomaly/AlarmTracker_test.cpp | 94 - bin/tests/anomaly/AnomalyTracker_test.cpp | 408 -- .../condition/CombinationConditionTracker_test.cpp | 167 - bin/tests/condition/ConditionTimer_test.cpp | 68 - .../condition/SimpleConditionTracker_test.cpp | 741 --- bin/tests/e2e/Alarm_e2e_test.cpp | 92 - bin/tests/e2e/Anomaly_count_e2e_test.cpp | 390 -- bin/tests/e2e/Anomaly_duration_sum_e2e_test.cpp | 615 -- bin/tests/e2e/Attribution_e2e_test.cpp | 375 -- bin/tests/e2e/ConfigTtl_e2e_test.cpp | 114 - bin/tests/e2e/ConfigUpdate_e2e_ab_test.cpp | 376 -- bin/tests/e2e/ConfigUpdate_e2e_test.cpp | 2895 -------- bin/tests/e2e/CountMetric_e2e_test.cpp | 901 --- bin/tests/e2e/DurationMetric_e2e_test.cpp | 1571 ----- bin/tests/e2e/GaugeMetric_e2e_pull_test.cpp | 624 -- bin/tests/e2e/GaugeMetric_e2e_push_test.cpp | 274 - bin/tests/e2e/MetricActivation_e2e_test.cpp | 1833 ------ bin/tests/e2e/MetricConditionLink_e2e_test.cpp | 348 - bin/tests/e2e/PartialBucket_e2e_test.cpp | 433 -- bin/tests/e2e/ValueMetric_pull_e2e_test.cpp | 679 -- bin/tests/e2e/WakelockDuration_e2e_test.cpp | 355 - bin/tests/external/StatsCallbackPuller_test.cpp | 219 - bin/tests/external/StatsPullerManager_test.cpp | 150 - bin/tests/external/StatsPuller_test.cpp | 316 - bin/tests/external/puller_util_test.cpp | 408 -- bin/tests/guardrail/StatsdStats_test.cpp | 553 -- bin/tests/indexed_priority_queue_test.cpp | 235 - bin/tests/log_event/LogEventQueue_test.cpp | 115 - bin/tests/metadata_util_test.cpp | 69 - bin/tests/metrics/CountMetricProducer_test.cpp | 479 -- bin/tests/metrics/DurationMetricProducer_test.cpp | 519 -- bin/tests/metrics/EventMetricProducer_test.cpp | 186 - bin/tests/metrics/GaugeMetricProducer_test.cpp | 830 --- bin/tests/metrics/MaxDurationTracker_test.cpp | 459 -- bin/tests/metrics/OringDurationTracker_test.cpp | 611 -- bin/tests/metrics/ValueMetricProducer_test.cpp | 6921 -------------------- bin/tests/metrics/metrics_test_helper.cpp | 57 - bin/tests/metrics/metrics_test_helper.h | 63 - .../parsing_utils/config_update_utils_test.cpp | 3533 ---------- .../parsing_utils/metrics_manager_util_test.cpp | 938 --- bin/tests/shell/ShellSubscriber_test.cpp | 207 - bin/tests/state/StateTracker_test.cpp | 571 -- bin/tests/statsd_test_util.cpp | 1624 ----- bin/tests/statsd_test_util.h | 545 -- bin/tests/storage/StorageManager_test.cpp | 204 - bin/tests/subscriber/SubscriberReporter_test.cpp | 107 - bin/tests/utils/MultiConditionTrigger_test.cpp | 174 - bin/tools/localtools/Android.bp | 46 - bin/tools/localtools/TEST_MAPPING | 8 - bin/tools/localtools/localdrive_manifest.txt | 1 - .../src/com/android/statsd/shelltools/Utils.java | 284 - .../statsd/shelltools/localdrive/LocalDrive.java | 379 -- .../statsd/shelltools/testdrive/TestDrive.java | 419 -- .../shelltools/testdrive/ConfigurationTest.java | 326 - .../statsd/shelltools/testdrive/TestDriveTest.java | 195 - bin/tools/localtools/testdrive_manifest.txt | 1 - lib/libstatspull/Android.bp | 1 - lib/libstatssocket/Android.bp | 4 +- statsd/.clang-format | 17 + statsd/Android.bp | 431 ++ statsd/TEST_MAPPING | 7 + statsd/benchmark/duration_metric_benchmark.cpp | 314 + statsd/benchmark/filter_value_benchmark.cpp | 72 + .../get_dimensions_for_condition_benchmark.cpp | 77 + statsd/benchmark/hello_world_benchmark.cpp | 29 + statsd/benchmark/log_event_benchmark.cpp | 50 + statsd/benchmark/main.cpp | 19 + statsd/benchmark/metric_util.cpp | 379 ++ statsd/benchmark/metric_util.h | 140 + statsd/benchmark/stats_write_benchmark.cpp | 40 + statsd/src/FieldValue.cpp | 474 ++ statsd/src/FieldValue.h | 462 ++ statsd/src/HashableDimensionKey.cpp | 372 ++ statsd/src/HashableDimensionKey.h | 247 + statsd/src/Log.h | 33 + statsd/src/StatsLogProcessor.cpp | 1147 ++++ statsd/src/StatsLogProcessor.h | 380 ++ statsd/src/StatsService.cpp | 1330 ++++ statsd/src/StatsService.h | 420 ++ statsd/src/active_config_list.proto | 57 + statsd/src/annotations.h | 33 + statsd/src/anomaly/AlarmMonitor.cpp | 139 + statsd/src/anomaly/AlarmMonitor.h | 162 + statsd/src/anomaly/AlarmTracker.cpp | 94 + statsd/src/anomaly/AlarmTracker.h | 81 + statsd/src/anomaly/AnomalyTracker.cpp | 326 + statsd/src/anomaly/AnomalyTracker.h | 230 + statsd/src/anomaly/DurationAnomalyTracker.cpp | 115 + statsd/src/anomaly/DurationAnomalyTracker.h | 79 + statsd/src/anomaly/indexed_priority_queue.h | 224 + statsd/src/anomaly/subscriber_util.cpp | 70 + statsd/src/anomaly/subscriber_util.h | 33 + .../src/condition/CombinationConditionTracker.cpp | 244 + statsd/src/condition/CombinationConditionTracker.h | 115 + statsd/src/condition/ConditionTimer.h | 107 + statsd/src/condition/ConditionTracker.h | 186 + statsd/src/condition/ConditionWizard.cpp | 70 + statsd/src/condition/ConditionWizard.h | 68 + statsd/src/condition/SimpleConditionTracker.cpp | 409 ++ statsd/src/condition/SimpleConditionTracker.h | 140 + statsd/src/condition/condition_util.cpp | 93 + statsd/src/condition/condition_util.h | 43 + statsd/src/config/ConfigKey.cpp | 54 + statsd/src/config/ConfigKey.h | 89 + statsd/src/config/ConfigListener.cpp | 31 + statsd/src/config/ConfigListener.h | 52 + statsd/src/config/ConfigManager.cpp | 375 ++ statsd/src/config/ConfigManager.h | 181 + statsd/src/experiment_ids.proto | 29 + statsd/src/external/Perfetto.cpp | 139 + statsd/src/external/Perfetto.h | 37 + statsd/src/external/PullDataReceiver.h | 41 + statsd/src/external/PullResultReceiver.cpp | 39 + statsd/src/external/PullResultReceiver.h | 48 + statsd/src/external/PullUidProvider.h | 39 + statsd/src/external/StatsCallbackPuller.cpp | 116 + statsd/src/external/StatsCallbackPuller.h | 46 + statsd/src/external/StatsPuller.cpp | 126 + statsd/src/external/StatsPuller.h | 119 + statsd/src/external/StatsPullerManager.cpp | 371 ++ statsd/src/external/StatsPullerManager.h | 192 + statsd/src/external/TrainInfoPuller.cpp | 55 + statsd/src/external/TrainInfoPuller.h | 38 + statsd/src/external/puller_util.cpp | 153 + statsd/src/external/puller_util.h | 34 + statsd/src/flags/flags.cpp | 37 + statsd/src/flags/flags.h | 36 + statsd/src/guardrail/StatsdStats.cpp | 1110 ++++ statsd/src/guardrail/StatsdStats.h | 686 ++ statsd/src/hash.cpp | 142 + statsd/src/hash.h | 47 + statsd/src/logd/LogEvent.cpp | 618 ++ statsd/src/logd/LogEvent.h | 331 + statsd/src/logd/LogEventQueue.cpp | 62 + statsd/src/logd/LogEventQueue.h | 57 + statsd/src/main.cpp | 113 + statsd/src/matchers/AtomMatchingTracker.h | 118 + .../matchers/CombinationAtomMatchingTracker.cpp | 140 + .../src/matchers/CombinationAtomMatchingTracker.h | 58 + statsd/src/matchers/EventMatcherWizard.cpp | 35 + statsd/src/matchers/EventMatcherWizard.h | 41 + statsd/src/matchers/SimpleAtomMatchingTracker.cpp | 81 + statsd/src/matchers/SimpleAtomMatchingTracker.h | 58 + statsd/src/matchers/matcher_util.cpp | 373 ++ statsd/src/matchers/matcher_util.h | 44 + statsd/src/metadata_util.cpp | 122 + statsd/src/metadata_util.h | 32 + statsd/src/metrics/CountMetricProducer.cpp | 440 ++ statsd/src/metrics/CountMetricProducer.h | 143 + statsd/src/metrics/DurationMetricProducer.cpp | 822 +++ statsd/src/metrics/DurationMetricProducer.h | 204 + statsd/src/metrics/EventMetricProducer.cpp | 214 + statsd/src/metrics/EventMetricProducer.h | 103 + statsd/src/metrics/GaugeMetricProducer.cpp | 681 ++ statsd/src/metrics/GaugeMetricProducer.h | 234 + statsd/src/metrics/MetricProducer.cpp | 356 + statsd/src/metrics/MetricProducer.h | 593 ++ statsd/src/metrics/MetricsManager.cpp | 773 +++ statsd/src/metrics/MetricsManager.h | 385 ++ statsd/src/metrics/ValueMetricProducer.cpp | 1282 ++++ statsd/src/metrics/ValueMetricProducer.h | 396 ++ .../src/metrics/duration_helper/DurationTracker.h | 281 + .../metrics/duration_helper/MaxDurationTracker.cpp | 334 + .../metrics/duration_helper/MaxDurationTracker.h | 94 + .../duration_helper/OringDurationTracker.cpp | 473 ++ .../metrics/duration_helper/OringDurationTracker.h | 98 + .../metrics/parsing_utils/config_update_utils.cpp | 1121 ++++ .../metrics/parsing_utils/config_update_utils.h | 267 + .../metrics/parsing_utils/metrics_manager_util.cpp | 1246 ++++ .../metrics/parsing_utils/metrics_manager_util.h | 343 + statsd/src/packages/PackageInfoListener.h | 47 + statsd/src/packages/UidMap.cpp | 564 ++ statsd/src/packages/UidMap.h | 226 + statsd/src/shell/ShellSubscriber.cpp | 245 + statsd/src/shell/ShellSubscriber.h | 146 + statsd/src/shell/shell_config.proto | 39 + statsd/src/shell/shell_data.proto | 29 + statsd/src/socket/StatsSocketListener.cpp | 156 + statsd/src/socket/StatsSocketListener.h | 54 + statsd/src/state/StateListener.h | 54 + statsd/src/state/StateManager.cpp | 112 + statsd/src/state/StateManager.h | 103 + statsd/src/state/StateTracker.cpp | 190 + statsd/src/state/StateTracker.h | 94 + statsd/src/stats_log.proto | 556 ++ statsd/src/stats_log_util.cpp | 625 ++ statsd/src/stats_log_util.h | 121 + statsd/src/stats_util.h | 46 + statsd/src/statscompanion_util.cpp | 35 + statsd/src/statscompanion_util.h | 33 + statsd/src/statsd_config.proto | 524 ++ statsd/src/statsd_metadata.proto | 67 + statsd/src/storage/StorageManager.cpp | 780 +++ statsd/src/storage/StorageManager.h | 168 + statsd/src/subscriber/IncidentdReporter.cpp | 169 + statsd/src/subscriber/IncidentdReporter.h | 36 + statsd/src/subscriber/SubscriberReporter.cpp | 149 + statsd/src/subscriber/SubscriberReporter.h | 112 + statsd/src/uid_data.proto | 36 + statsd/src/utils/MultiConditionTrigger.cpp | 57 + statsd/src/utils/MultiConditionTrigger.h | 55 + statsd/statsd_test.xml | 37 + statsd/tests/AlarmMonitor_test.cpp | 69 + statsd/tests/ConfigManager_test.cpp | 159 + statsd/tests/FieldValue_test.cpp | 653 ++ statsd/tests/HashableDimensionKey_test.cpp | 137 + statsd/tests/LogEntryMatcher_test.cpp | 809 +++ statsd/tests/LogEvent_test.cpp | 481 ++ statsd/tests/LogReader_test.cpp | 21 + statsd/tests/MetricsManager_test.cpp | 325 + statsd/tests/StatsLogProcessor_test.cpp | 1886 ++++++ statsd/tests/StatsService_test.cpp | 104 + statsd/tests/UidMap_test.cpp | 426 ++ statsd/tests/anomaly/AlarmTracker_test.cpp | 94 + statsd/tests/anomaly/AnomalyTracker_test.cpp | 408 ++ .../condition/CombinationConditionTracker_test.cpp | 167 + statsd/tests/condition/ConditionTimer_test.cpp | 68 + .../condition/SimpleConditionTracker_test.cpp | 741 +++ statsd/tests/e2e/Alarm_e2e_test.cpp | 92 + statsd/tests/e2e/Anomaly_count_e2e_test.cpp | 390 ++ statsd/tests/e2e/Anomaly_duration_sum_e2e_test.cpp | 615 ++ statsd/tests/e2e/Attribution_e2e_test.cpp | 375 ++ statsd/tests/e2e/ConfigTtl_e2e_test.cpp | 114 + statsd/tests/e2e/ConfigUpdate_e2e_ab_test.cpp | 376 ++ statsd/tests/e2e/ConfigUpdate_e2e_test.cpp | 2895 ++++++++ statsd/tests/e2e/CountMetric_e2e_test.cpp | 901 +++ statsd/tests/e2e/DurationMetric_e2e_test.cpp | 1571 +++++ statsd/tests/e2e/GaugeMetric_e2e_pull_test.cpp | 624 ++ statsd/tests/e2e/GaugeMetric_e2e_push_test.cpp | 274 + statsd/tests/e2e/MetricActivation_e2e_test.cpp | 1833 ++++++ statsd/tests/e2e/MetricConditionLink_e2e_test.cpp | 348 + statsd/tests/e2e/PartialBucket_e2e_test.cpp | 433 ++ statsd/tests/e2e/ValueMetric_pull_e2e_test.cpp | 679 ++ statsd/tests/e2e/WakelockDuration_e2e_test.cpp | 355 + statsd/tests/external/StatsCallbackPuller_test.cpp | 219 + statsd/tests/external/StatsPullerManager_test.cpp | 150 + statsd/tests/external/StatsPuller_test.cpp | 316 + statsd/tests/external/puller_util_test.cpp | 408 ++ statsd/tests/guardrail/StatsdStats_test.cpp | 553 ++ statsd/tests/indexed_priority_queue_test.cpp | 235 + statsd/tests/log_event/LogEventQueue_test.cpp | 115 + statsd/tests/metadata_util_test.cpp | 69 + statsd/tests/metrics/CountMetricProducer_test.cpp | 479 ++ .../tests/metrics/DurationMetricProducer_test.cpp | 519 ++ statsd/tests/metrics/EventMetricProducer_test.cpp | 186 + statsd/tests/metrics/GaugeMetricProducer_test.cpp | 830 +++ statsd/tests/metrics/MaxDurationTracker_test.cpp | 459 ++ statsd/tests/metrics/OringDurationTracker_test.cpp | 611 ++ statsd/tests/metrics/ValueMetricProducer_test.cpp | 6921 ++++++++++++++++++++ statsd/tests/metrics/metrics_test_helper.cpp | 57 + statsd/tests/metrics/metrics_test_helper.h | 63 + .../parsing_utils/config_update_utils_test.cpp | 3533 ++++++++++ .../parsing_utils/metrics_manager_util_test.cpp | 938 +++ statsd/tests/shell/ShellSubscriber_test.cpp | 207 + statsd/tests/state/StateTracker_test.cpp | 571 ++ statsd/tests/statsd_test_util.cpp | 1624 +++++ statsd/tests/statsd_test_util.h | 545 ++ statsd/tests/storage/StorageManager_test.cpp | 204 + .../tests/subscriber/SubscriberReporter_test.cpp | 107 + statsd/tests/utils/MultiConditionTrigger_test.cpp | 174 + statsd/tools/localtools/Android.bp | 46 + statsd/tools/localtools/TEST_MAPPING | 8 + statsd/tools/localtools/localdrive_manifest.txt | 1 + .../src/com/android/statsd/shelltools/Utils.java | 284 + .../statsd/shelltools/localdrive/LocalDrive.java | 379 ++ .../statsd/shelltools/testdrive/TestDrive.java | 419 ++ .../shelltools/testdrive/ConfigurationTest.java | 326 + .../statsd/shelltools/testdrive/TestDriveTest.java | 195 + statsd/tools/localtools/testdrive_manifest.txt | 1 + 424 files changed, 73027 insertions(+), 73030 deletions(-) delete mode 100644 bin/.clang-format delete mode 100644 bin/Android.bp delete mode 100644 bin/TEST_MAPPING delete mode 100644 bin/benchmark/duration_metric_benchmark.cpp delete mode 100644 bin/benchmark/filter_value_benchmark.cpp delete mode 100644 bin/benchmark/get_dimensions_for_condition_benchmark.cpp delete mode 100644 bin/benchmark/hello_world_benchmark.cpp delete mode 100644 bin/benchmark/log_event_benchmark.cpp delete mode 100644 bin/benchmark/main.cpp delete mode 100644 bin/benchmark/metric_util.cpp delete mode 100644 bin/benchmark/metric_util.h delete mode 100644 bin/benchmark/stats_write_benchmark.cpp delete mode 100644 bin/src/FieldValue.cpp delete mode 100644 bin/src/FieldValue.h delete mode 100644 bin/src/HashableDimensionKey.cpp delete mode 100644 bin/src/HashableDimensionKey.h delete mode 100644 bin/src/Log.h delete mode 100644 bin/src/StatsLogProcessor.cpp delete mode 100644 bin/src/StatsLogProcessor.h delete mode 100644 bin/src/StatsService.cpp delete mode 100644 bin/src/StatsService.h delete mode 100644 bin/src/active_config_list.proto delete mode 100644 bin/src/annotations.h delete mode 100644 bin/src/anomaly/AlarmMonitor.cpp delete mode 100644 bin/src/anomaly/AlarmMonitor.h delete mode 100644 bin/src/anomaly/AlarmTracker.cpp delete mode 100644 bin/src/anomaly/AlarmTracker.h delete mode 100644 bin/src/anomaly/AnomalyTracker.cpp delete mode 100644 bin/src/anomaly/AnomalyTracker.h delete mode 100644 bin/src/anomaly/DurationAnomalyTracker.cpp delete mode 100644 bin/src/anomaly/DurationAnomalyTracker.h delete mode 100644 bin/src/anomaly/indexed_priority_queue.h delete mode 100644 bin/src/anomaly/subscriber_util.cpp delete mode 100644 bin/src/anomaly/subscriber_util.h delete mode 100644 bin/src/condition/CombinationConditionTracker.cpp delete mode 100644 bin/src/condition/CombinationConditionTracker.h delete mode 100644 bin/src/condition/ConditionTimer.h delete mode 100644 bin/src/condition/ConditionTracker.h delete mode 100644 bin/src/condition/ConditionWizard.cpp delete mode 100644 bin/src/condition/ConditionWizard.h delete mode 100644 bin/src/condition/SimpleConditionTracker.cpp delete mode 100644 bin/src/condition/SimpleConditionTracker.h delete mode 100644 bin/src/condition/condition_util.cpp delete mode 100644 bin/src/condition/condition_util.h delete mode 100644 bin/src/config/ConfigKey.cpp delete mode 100644 bin/src/config/ConfigKey.h delete mode 100644 bin/src/config/ConfigListener.cpp delete mode 100644 bin/src/config/ConfigListener.h delete mode 100644 bin/src/config/ConfigManager.cpp delete mode 100644 bin/src/config/ConfigManager.h delete mode 100644 bin/src/experiment_ids.proto delete mode 100644 bin/src/external/Perfetto.cpp delete mode 100644 bin/src/external/Perfetto.h delete mode 100644 bin/src/external/PullDataReceiver.h delete mode 100644 bin/src/external/PullResultReceiver.cpp delete mode 100644 bin/src/external/PullResultReceiver.h delete mode 100644 bin/src/external/PullUidProvider.h delete mode 100644 bin/src/external/StatsCallbackPuller.cpp delete mode 100644 bin/src/external/StatsCallbackPuller.h delete mode 100644 bin/src/external/StatsPuller.cpp delete mode 100644 bin/src/external/StatsPuller.h delete mode 100644 bin/src/external/StatsPullerManager.cpp delete mode 100644 bin/src/external/StatsPullerManager.h delete mode 100644 bin/src/external/TrainInfoPuller.cpp delete mode 100644 bin/src/external/TrainInfoPuller.h delete mode 100644 bin/src/external/puller_util.cpp delete mode 100644 bin/src/external/puller_util.h delete mode 100644 bin/src/flags/flags.cpp delete mode 100644 bin/src/flags/flags.h delete mode 100644 bin/src/guardrail/StatsdStats.cpp delete mode 100644 bin/src/guardrail/StatsdStats.h delete mode 100644 bin/src/hash.cpp delete mode 100644 bin/src/hash.h delete mode 100644 bin/src/logd/LogEvent.cpp delete mode 100644 bin/src/logd/LogEvent.h delete mode 100644 bin/src/logd/LogEventQueue.cpp delete mode 100644 bin/src/logd/LogEventQueue.h delete mode 100644 bin/src/main.cpp delete mode 100644 bin/src/matchers/AtomMatchingTracker.h delete mode 100644 bin/src/matchers/CombinationAtomMatchingTracker.cpp delete mode 100644 bin/src/matchers/CombinationAtomMatchingTracker.h delete mode 100644 bin/src/matchers/EventMatcherWizard.cpp delete mode 100644 bin/src/matchers/EventMatcherWizard.h delete mode 100644 bin/src/matchers/SimpleAtomMatchingTracker.cpp delete mode 100644 bin/src/matchers/SimpleAtomMatchingTracker.h delete mode 100644 bin/src/matchers/matcher_util.cpp delete mode 100644 bin/src/matchers/matcher_util.h delete mode 100644 bin/src/metadata_util.cpp delete mode 100644 bin/src/metadata_util.h delete mode 100644 bin/src/metrics/CountMetricProducer.cpp delete mode 100644 bin/src/metrics/CountMetricProducer.h delete mode 100644 bin/src/metrics/DurationMetricProducer.cpp delete mode 100644 bin/src/metrics/DurationMetricProducer.h delete mode 100644 bin/src/metrics/EventMetricProducer.cpp delete mode 100644 bin/src/metrics/EventMetricProducer.h delete mode 100644 bin/src/metrics/GaugeMetricProducer.cpp delete mode 100644 bin/src/metrics/GaugeMetricProducer.h delete mode 100644 bin/src/metrics/MetricProducer.cpp delete mode 100644 bin/src/metrics/MetricProducer.h delete mode 100644 bin/src/metrics/MetricsManager.cpp delete mode 100644 bin/src/metrics/MetricsManager.h delete mode 100644 bin/src/metrics/ValueMetricProducer.cpp delete mode 100644 bin/src/metrics/ValueMetricProducer.h delete mode 100644 bin/src/metrics/duration_helper/DurationTracker.h delete mode 100644 bin/src/metrics/duration_helper/MaxDurationTracker.cpp delete mode 100644 bin/src/metrics/duration_helper/MaxDurationTracker.h delete mode 100644 bin/src/metrics/duration_helper/OringDurationTracker.cpp delete mode 100644 bin/src/metrics/duration_helper/OringDurationTracker.h delete mode 100644 bin/src/metrics/parsing_utils/config_update_utils.cpp delete mode 100644 bin/src/metrics/parsing_utils/config_update_utils.h delete mode 100644 bin/src/metrics/parsing_utils/metrics_manager_util.cpp delete mode 100644 bin/src/metrics/parsing_utils/metrics_manager_util.h delete mode 100644 bin/src/packages/PackageInfoListener.h delete mode 100644 bin/src/packages/UidMap.cpp delete mode 100644 bin/src/packages/UidMap.h delete mode 100644 bin/src/shell/ShellSubscriber.cpp delete mode 100644 bin/src/shell/ShellSubscriber.h delete mode 100644 bin/src/shell/shell_config.proto delete mode 100644 bin/src/shell/shell_data.proto delete mode 100755 bin/src/socket/StatsSocketListener.cpp delete mode 100644 bin/src/socket/StatsSocketListener.h delete mode 100644 bin/src/state/StateListener.h delete mode 100644 bin/src/state/StateManager.cpp delete mode 100644 bin/src/state/StateManager.h delete mode 100644 bin/src/state/StateTracker.cpp delete mode 100644 bin/src/state/StateTracker.h delete mode 100644 bin/src/stats_log.proto delete mode 100644 bin/src/stats_log_util.cpp delete mode 100644 bin/src/stats_log_util.h delete mode 100644 bin/src/stats_util.h delete mode 100644 bin/src/statscompanion_util.cpp delete mode 100644 bin/src/statscompanion_util.h delete mode 100644 bin/src/statsd_config.proto delete mode 100644 bin/src/statsd_metadata.proto delete mode 100644 bin/src/storage/StorageManager.cpp delete mode 100644 bin/src/storage/StorageManager.h delete mode 100644 bin/src/subscriber/IncidentdReporter.cpp delete mode 100644 bin/src/subscriber/IncidentdReporter.h delete mode 100644 bin/src/subscriber/SubscriberReporter.cpp delete mode 100644 bin/src/subscriber/SubscriberReporter.h delete mode 100644 bin/src/uid_data.proto delete mode 100644 bin/src/utils/MultiConditionTrigger.cpp delete mode 100644 bin/src/utils/MultiConditionTrigger.h delete mode 100644 bin/statsd_test.xml delete mode 100644 bin/tests/AlarmMonitor_test.cpp delete mode 100644 bin/tests/ConfigManager_test.cpp delete mode 100644 bin/tests/FieldValue_test.cpp delete mode 100644 bin/tests/HashableDimensionKey_test.cpp delete mode 100644 bin/tests/LogEntryMatcher_test.cpp delete mode 100644 bin/tests/LogEvent_test.cpp delete mode 100644 bin/tests/LogReader_test.cpp delete mode 100644 bin/tests/MetricsManager_test.cpp delete mode 100644 bin/tests/StatsLogProcessor_test.cpp delete mode 100644 bin/tests/StatsService_test.cpp delete mode 100644 bin/tests/UidMap_test.cpp delete mode 100644 bin/tests/anomaly/AlarmTracker_test.cpp delete mode 100644 bin/tests/anomaly/AnomalyTracker_test.cpp delete mode 100644 bin/tests/condition/CombinationConditionTracker_test.cpp delete mode 100644 bin/tests/condition/ConditionTimer_test.cpp delete mode 100644 bin/tests/condition/SimpleConditionTracker_test.cpp delete mode 100644 bin/tests/e2e/Alarm_e2e_test.cpp delete mode 100644 bin/tests/e2e/Anomaly_count_e2e_test.cpp delete mode 100644 bin/tests/e2e/Anomaly_duration_sum_e2e_test.cpp delete mode 100644 bin/tests/e2e/Attribution_e2e_test.cpp delete mode 100644 bin/tests/e2e/ConfigTtl_e2e_test.cpp delete mode 100644 bin/tests/e2e/ConfigUpdate_e2e_ab_test.cpp delete mode 100644 bin/tests/e2e/ConfigUpdate_e2e_test.cpp delete mode 100644 bin/tests/e2e/CountMetric_e2e_test.cpp delete mode 100644 bin/tests/e2e/DurationMetric_e2e_test.cpp delete mode 100644 bin/tests/e2e/GaugeMetric_e2e_pull_test.cpp delete mode 100644 bin/tests/e2e/GaugeMetric_e2e_push_test.cpp delete mode 100644 bin/tests/e2e/MetricActivation_e2e_test.cpp delete mode 100644 bin/tests/e2e/MetricConditionLink_e2e_test.cpp delete mode 100644 bin/tests/e2e/PartialBucket_e2e_test.cpp delete mode 100644 bin/tests/e2e/ValueMetric_pull_e2e_test.cpp delete mode 100644 bin/tests/e2e/WakelockDuration_e2e_test.cpp delete mode 100644 bin/tests/external/StatsCallbackPuller_test.cpp delete mode 100644 bin/tests/external/StatsPullerManager_test.cpp delete mode 100644 bin/tests/external/StatsPuller_test.cpp delete mode 100644 bin/tests/external/puller_util_test.cpp delete mode 100644 bin/tests/guardrail/StatsdStats_test.cpp delete mode 100644 bin/tests/indexed_priority_queue_test.cpp delete mode 100644 bin/tests/log_event/LogEventQueue_test.cpp delete mode 100644 bin/tests/metadata_util_test.cpp delete mode 100644 bin/tests/metrics/CountMetricProducer_test.cpp delete mode 100644 bin/tests/metrics/DurationMetricProducer_test.cpp delete mode 100644 bin/tests/metrics/EventMetricProducer_test.cpp delete mode 100644 bin/tests/metrics/GaugeMetricProducer_test.cpp delete mode 100644 bin/tests/metrics/MaxDurationTracker_test.cpp delete mode 100644 bin/tests/metrics/OringDurationTracker_test.cpp delete mode 100644 bin/tests/metrics/ValueMetricProducer_test.cpp delete mode 100644 bin/tests/metrics/metrics_test_helper.cpp delete mode 100644 bin/tests/metrics/metrics_test_helper.h delete mode 100644 bin/tests/metrics/parsing_utils/config_update_utils_test.cpp delete mode 100644 bin/tests/metrics/parsing_utils/metrics_manager_util_test.cpp delete mode 100644 bin/tests/shell/ShellSubscriber_test.cpp delete mode 100644 bin/tests/state/StateTracker_test.cpp delete mode 100644 bin/tests/statsd_test_util.cpp delete mode 100644 bin/tests/statsd_test_util.h delete mode 100644 bin/tests/storage/StorageManager_test.cpp delete mode 100644 bin/tests/subscriber/SubscriberReporter_test.cpp delete mode 100644 bin/tests/utils/MultiConditionTrigger_test.cpp delete mode 100644 bin/tools/localtools/Android.bp delete mode 100644 bin/tools/localtools/TEST_MAPPING delete mode 100644 bin/tools/localtools/localdrive_manifest.txt delete mode 100644 bin/tools/localtools/src/com/android/statsd/shelltools/Utils.java delete mode 100644 bin/tools/localtools/src/com/android/statsd/shelltools/localdrive/LocalDrive.java delete mode 100644 bin/tools/localtools/src/com/android/statsd/shelltools/testdrive/TestDrive.java delete mode 100644 bin/tools/localtools/test/com/android/statsd/shelltools/testdrive/ConfigurationTest.java delete mode 100644 bin/tools/localtools/test/com/android/statsd/shelltools/testdrive/TestDriveTest.java delete mode 100644 bin/tools/localtools/testdrive_manifest.txt create mode 100644 statsd/.clang-format create mode 100644 statsd/Android.bp create mode 100644 statsd/TEST_MAPPING create mode 100644 statsd/benchmark/duration_metric_benchmark.cpp create mode 100644 statsd/benchmark/filter_value_benchmark.cpp create mode 100644 statsd/benchmark/get_dimensions_for_condition_benchmark.cpp create mode 100644 statsd/benchmark/hello_world_benchmark.cpp create mode 100644 statsd/benchmark/log_event_benchmark.cpp create mode 100644 statsd/benchmark/main.cpp create mode 100644 statsd/benchmark/metric_util.cpp create mode 100644 statsd/benchmark/metric_util.h create mode 100644 statsd/benchmark/stats_write_benchmark.cpp create mode 100644 statsd/src/FieldValue.cpp create mode 100644 statsd/src/FieldValue.h create mode 100644 statsd/src/HashableDimensionKey.cpp create mode 100644 statsd/src/HashableDimensionKey.h create mode 100644 statsd/src/Log.h create mode 100644 statsd/src/StatsLogProcessor.cpp create mode 100644 statsd/src/StatsLogProcessor.h create mode 100644 statsd/src/StatsService.cpp create mode 100644 statsd/src/StatsService.h create mode 100644 statsd/src/active_config_list.proto create mode 100644 statsd/src/annotations.h create mode 100644 statsd/src/anomaly/AlarmMonitor.cpp create mode 100644 statsd/src/anomaly/AlarmMonitor.h create mode 100644 statsd/src/anomaly/AlarmTracker.cpp create mode 100644 statsd/src/anomaly/AlarmTracker.h create mode 100644 statsd/src/anomaly/AnomalyTracker.cpp create mode 100644 statsd/src/anomaly/AnomalyTracker.h create mode 100644 statsd/src/anomaly/DurationAnomalyTracker.cpp create mode 100644 statsd/src/anomaly/DurationAnomalyTracker.h create mode 100644 statsd/src/anomaly/indexed_priority_queue.h create mode 100644 statsd/src/anomaly/subscriber_util.cpp create mode 100644 statsd/src/anomaly/subscriber_util.h create mode 100644 statsd/src/condition/CombinationConditionTracker.cpp create mode 100644 statsd/src/condition/CombinationConditionTracker.h create mode 100644 statsd/src/condition/ConditionTimer.h create mode 100644 statsd/src/condition/ConditionTracker.h create mode 100644 statsd/src/condition/ConditionWizard.cpp create mode 100644 statsd/src/condition/ConditionWizard.h create mode 100644 statsd/src/condition/SimpleConditionTracker.cpp create mode 100644 statsd/src/condition/SimpleConditionTracker.h create mode 100644 statsd/src/condition/condition_util.cpp create mode 100644 statsd/src/condition/condition_util.h create mode 100644 statsd/src/config/ConfigKey.cpp create mode 100644 statsd/src/config/ConfigKey.h create mode 100644 statsd/src/config/ConfigListener.cpp create mode 100644 statsd/src/config/ConfigListener.h create mode 100644 statsd/src/config/ConfigManager.cpp create mode 100644 statsd/src/config/ConfigManager.h create mode 100644 statsd/src/experiment_ids.proto create mode 100644 statsd/src/external/Perfetto.cpp create mode 100644 statsd/src/external/Perfetto.h create mode 100644 statsd/src/external/PullDataReceiver.h create mode 100644 statsd/src/external/PullResultReceiver.cpp create mode 100644 statsd/src/external/PullResultReceiver.h create mode 100644 statsd/src/external/PullUidProvider.h create mode 100644 statsd/src/external/StatsCallbackPuller.cpp create mode 100644 statsd/src/external/StatsCallbackPuller.h create mode 100644 statsd/src/external/StatsPuller.cpp create mode 100644 statsd/src/external/StatsPuller.h create mode 100644 statsd/src/external/StatsPullerManager.cpp create mode 100644 statsd/src/external/StatsPullerManager.h create mode 100644 statsd/src/external/TrainInfoPuller.cpp create mode 100644 statsd/src/external/TrainInfoPuller.h create mode 100644 statsd/src/external/puller_util.cpp create mode 100644 statsd/src/external/puller_util.h create mode 100644 statsd/src/flags/flags.cpp create mode 100644 statsd/src/flags/flags.h create mode 100644 statsd/src/guardrail/StatsdStats.cpp create mode 100644 statsd/src/guardrail/StatsdStats.h create mode 100644 statsd/src/hash.cpp create mode 100644 statsd/src/hash.h create mode 100644 statsd/src/logd/LogEvent.cpp create mode 100644 statsd/src/logd/LogEvent.h create mode 100644 statsd/src/logd/LogEventQueue.cpp create mode 100644 statsd/src/logd/LogEventQueue.h create mode 100644 statsd/src/main.cpp create mode 100644 statsd/src/matchers/AtomMatchingTracker.h create mode 100644 statsd/src/matchers/CombinationAtomMatchingTracker.cpp create mode 100644 statsd/src/matchers/CombinationAtomMatchingTracker.h create mode 100644 statsd/src/matchers/EventMatcherWizard.cpp create mode 100644 statsd/src/matchers/EventMatcherWizard.h create mode 100644 statsd/src/matchers/SimpleAtomMatchingTracker.cpp create mode 100644 statsd/src/matchers/SimpleAtomMatchingTracker.h create mode 100644 statsd/src/matchers/matcher_util.cpp create mode 100644 statsd/src/matchers/matcher_util.h create mode 100644 statsd/src/metadata_util.cpp create mode 100644 statsd/src/metadata_util.h create mode 100644 statsd/src/metrics/CountMetricProducer.cpp create mode 100644 statsd/src/metrics/CountMetricProducer.h create mode 100644 statsd/src/metrics/DurationMetricProducer.cpp create mode 100644 statsd/src/metrics/DurationMetricProducer.h create mode 100644 statsd/src/metrics/EventMetricProducer.cpp create mode 100644 statsd/src/metrics/EventMetricProducer.h create mode 100644 statsd/src/metrics/GaugeMetricProducer.cpp create mode 100644 statsd/src/metrics/GaugeMetricProducer.h create mode 100644 statsd/src/metrics/MetricProducer.cpp create mode 100644 statsd/src/metrics/MetricProducer.h create mode 100644 statsd/src/metrics/MetricsManager.cpp create mode 100644 statsd/src/metrics/MetricsManager.h create mode 100644 statsd/src/metrics/ValueMetricProducer.cpp create mode 100644 statsd/src/metrics/ValueMetricProducer.h create mode 100644 statsd/src/metrics/duration_helper/DurationTracker.h create mode 100644 statsd/src/metrics/duration_helper/MaxDurationTracker.cpp create mode 100644 statsd/src/metrics/duration_helper/MaxDurationTracker.h create mode 100644 statsd/src/metrics/duration_helper/OringDurationTracker.cpp create mode 100644 statsd/src/metrics/duration_helper/OringDurationTracker.h create mode 100644 statsd/src/metrics/parsing_utils/config_update_utils.cpp create mode 100644 statsd/src/metrics/parsing_utils/config_update_utils.h create mode 100644 statsd/src/metrics/parsing_utils/metrics_manager_util.cpp create mode 100644 statsd/src/metrics/parsing_utils/metrics_manager_util.h create mode 100644 statsd/src/packages/PackageInfoListener.h create mode 100644 statsd/src/packages/UidMap.cpp create mode 100644 statsd/src/packages/UidMap.h create mode 100644 statsd/src/shell/ShellSubscriber.cpp create mode 100644 statsd/src/shell/ShellSubscriber.h create mode 100644 statsd/src/shell/shell_config.proto create mode 100644 statsd/src/shell/shell_data.proto create mode 100755 statsd/src/socket/StatsSocketListener.cpp create mode 100644 statsd/src/socket/StatsSocketListener.h create mode 100644 statsd/src/state/StateListener.h create mode 100644 statsd/src/state/StateManager.cpp create mode 100644 statsd/src/state/StateManager.h create mode 100644 statsd/src/state/StateTracker.cpp create mode 100644 statsd/src/state/StateTracker.h create mode 100644 statsd/src/stats_log.proto create mode 100644 statsd/src/stats_log_util.cpp create mode 100644 statsd/src/stats_log_util.h create mode 100644 statsd/src/stats_util.h create mode 100644 statsd/src/statscompanion_util.cpp create mode 100644 statsd/src/statscompanion_util.h create mode 100644 statsd/src/statsd_config.proto create mode 100644 statsd/src/statsd_metadata.proto create mode 100644 statsd/src/storage/StorageManager.cpp create mode 100644 statsd/src/storage/StorageManager.h create mode 100644 statsd/src/subscriber/IncidentdReporter.cpp create mode 100644 statsd/src/subscriber/IncidentdReporter.h create mode 100644 statsd/src/subscriber/SubscriberReporter.cpp create mode 100644 statsd/src/subscriber/SubscriberReporter.h create mode 100644 statsd/src/uid_data.proto create mode 100644 statsd/src/utils/MultiConditionTrigger.cpp create mode 100644 statsd/src/utils/MultiConditionTrigger.h create mode 100644 statsd/statsd_test.xml create mode 100644 statsd/tests/AlarmMonitor_test.cpp create mode 100644 statsd/tests/ConfigManager_test.cpp create mode 100644 statsd/tests/FieldValue_test.cpp create mode 100644 statsd/tests/HashableDimensionKey_test.cpp create mode 100644 statsd/tests/LogEntryMatcher_test.cpp create mode 100644 statsd/tests/LogEvent_test.cpp create mode 100644 statsd/tests/LogReader_test.cpp create mode 100644 statsd/tests/MetricsManager_test.cpp create mode 100644 statsd/tests/StatsLogProcessor_test.cpp create mode 100644 statsd/tests/StatsService_test.cpp create mode 100644 statsd/tests/UidMap_test.cpp create mode 100644 statsd/tests/anomaly/AlarmTracker_test.cpp create mode 100644 statsd/tests/anomaly/AnomalyTracker_test.cpp create mode 100644 statsd/tests/condition/CombinationConditionTracker_test.cpp create mode 100644 statsd/tests/condition/ConditionTimer_test.cpp create mode 100644 statsd/tests/condition/SimpleConditionTracker_test.cpp create mode 100644 statsd/tests/e2e/Alarm_e2e_test.cpp create mode 100644 statsd/tests/e2e/Anomaly_count_e2e_test.cpp create mode 100644 statsd/tests/e2e/Anomaly_duration_sum_e2e_test.cpp create mode 100644 statsd/tests/e2e/Attribution_e2e_test.cpp create mode 100644 statsd/tests/e2e/ConfigTtl_e2e_test.cpp create mode 100644 statsd/tests/e2e/ConfigUpdate_e2e_ab_test.cpp create mode 100644 statsd/tests/e2e/ConfigUpdate_e2e_test.cpp create mode 100644 statsd/tests/e2e/CountMetric_e2e_test.cpp create mode 100644 statsd/tests/e2e/DurationMetric_e2e_test.cpp create mode 100644 statsd/tests/e2e/GaugeMetric_e2e_pull_test.cpp create mode 100644 statsd/tests/e2e/GaugeMetric_e2e_push_test.cpp create mode 100644 statsd/tests/e2e/MetricActivation_e2e_test.cpp create mode 100644 statsd/tests/e2e/MetricConditionLink_e2e_test.cpp create mode 100644 statsd/tests/e2e/PartialBucket_e2e_test.cpp create mode 100644 statsd/tests/e2e/ValueMetric_pull_e2e_test.cpp create mode 100644 statsd/tests/e2e/WakelockDuration_e2e_test.cpp create mode 100644 statsd/tests/external/StatsCallbackPuller_test.cpp create mode 100644 statsd/tests/external/StatsPullerManager_test.cpp create mode 100644 statsd/tests/external/StatsPuller_test.cpp create mode 100644 statsd/tests/external/puller_util_test.cpp create mode 100644 statsd/tests/guardrail/StatsdStats_test.cpp create mode 100644 statsd/tests/indexed_priority_queue_test.cpp create mode 100644 statsd/tests/log_event/LogEventQueue_test.cpp create mode 100644 statsd/tests/metadata_util_test.cpp create mode 100644 statsd/tests/metrics/CountMetricProducer_test.cpp create mode 100644 statsd/tests/metrics/DurationMetricProducer_test.cpp create mode 100644 statsd/tests/metrics/EventMetricProducer_test.cpp create mode 100644 statsd/tests/metrics/GaugeMetricProducer_test.cpp create mode 100644 statsd/tests/metrics/MaxDurationTracker_test.cpp create mode 100644 statsd/tests/metrics/OringDurationTracker_test.cpp create mode 100644 statsd/tests/metrics/ValueMetricProducer_test.cpp create mode 100644 statsd/tests/metrics/metrics_test_helper.cpp create mode 100644 statsd/tests/metrics/metrics_test_helper.h create mode 100644 statsd/tests/metrics/parsing_utils/config_update_utils_test.cpp create mode 100644 statsd/tests/metrics/parsing_utils/metrics_manager_util_test.cpp create mode 100644 statsd/tests/shell/ShellSubscriber_test.cpp create mode 100644 statsd/tests/state/StateTracker_test.cpp create mode 100644 statsd/tests/statsd_test_util.cpp create mode 100644 statsd/tests/statsd_test_util.h create mode 100644 statsd/tests/storage/StorageManager_test.cpp create mode 100644 statsd/tests/subscriber/SubscriberReporter_test.cpp create mode 100644 statsd/tests/utils/MultiConditionTrigger_test.cpp create mode 100644 statsd/tools/localtools/Android.bp create mode 100644 statsd/tools/localtools/TEST_MAPPING create mode 100644 statsd/tools/localtools/localdrive_manifest.txt create mode 100644 statsd/tools/localtools/src/com/android/statsd/shelltools/Utils.java create mode 100644 statsd/tools/localtools/src/com/android/statsd/shelltools/localdrive/LocalDrive.java create mode 100644 statsd/tools/localtools/src/com/android/statsd/shelltools/testdrive/TestDrive.java create mode 100644 statsd/tools/localtools/test/com/android/statsd/shelltools/testdrive/ConfigurationTest.java create mode 100644 statsd/tools/localtools/test/com/android/statsd/shelltools/testdrive/TestDriveTest.java create mode 100644 statsd/tools/localtools/testdrive_manifest.txt diff --git a/bin/.clang-format b/bin/.clang-format deleted file mode 100644 index cead3a07..00000000 --- a/bin/.clang-format +++ /dev/null @@ -1,17 +0,0 @@ -BasedOnStyle: Google -AllowShortIfStatementsOnASingleLine: true -AllowShortFunctionsOnASingleLine: false -AllowShortLoopsOnASingleLine: true -BinPackArguments: true -BinPackParameters: true -ColumnLimit: 100 -CommentPragmas: NOLINT:.* -ContinuationIndentWidth: 8 -DerivePointerAlignment: false -IndentWidth: 4 -PointerAlignment: Left -TabWidth: 4 -AccessModifierOffset: -4 -IncludeCategories: - - Regex: '^"Log\.h"' - Priority: -1 diff --git a/bin/Android.bp b/bin/Android.bp deleted file mode 100644 index 2f18d514..00000000 --- a/bin/Android.bp +++ /dev/null @@ -1,431 +0,0 @@ -// -// 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. -// - -cc_defaults { - name: "statsd_defaults", - - srcs: [ - "src/active_config_list.proto", - "src/anomaly/AlarmMonitor.cpp", - "src/anomaly/AlarmTracker.cpp", - "src/anomaly/AnomalyTracker.cpp", - "src/anomaly/DurationAnomalyTracker.cpp", - "src/anomaly/subscriber_util.cpp", - "src/condition/CombinationConditionTracker.cpp", - "src/condition/condition_util.cpp", - "src/condition/ConditionWizard.cpp", - "src/condition/SimpleConditionTracker.cpp", - "src/config/ConfigKey.cpp", - "src/config/ConfigListener.cpp", - "src/config/ConfigManager.cpp", - "src/experiment_ids.proto", - "src/external/Perfetto.cpp", - "src/external/PullResultReceiver.cpp", - "src/external/puller_util.cpp", - "src/external/StatsCallbackPuller.cpp", - "src/external/StatsPuller.cpp", - "src/external/StatsPullerManager.cpp", - "src/external/TrainInfoPuller.cpp", - "src/FieldValue.cpp", - "src/flags/flags.cpp", - "src/guardrail/StatsdStats.cpp", - "src/hash.cpp", - "src/HashableDimensionKey.cpp", - "src/logd/LogEvent.cpp", - "src/logd/LogEventQueue.cpp", - "src/matchers/CombinationAtomMatchingTracker.cpp", - "src/matchers/EventMatcherWizard.cpp", - "src/matchers/matcher_util.cpp", - "src/matchers/SimpleAtomMatchingTracker.cpp", - "src/metadata_util.cpp", - "src/metrics/CountMetricProducer.cpp", - "src/metrics/duration_helper/MaxDurationTracker.cpp", - "src/metrics/duration_helper/OringDurationTracker.cpp", - "src/metrics/DurationMetricProducer.cpp", - "src/metrics/EventMetricProducer.cpp", - "src/metrics/GaugeMetricProducer.cpp", - "src/metrics/MetricProducer.cpp", - "src/metrics/MetricsManager.cpp", - "src/metrics/parsing_utils/config_update_utils.cpp", - "src/metrics/parsing_utils/metrics_manager_util.cpp", - "src/metrics/ValueMetricProducer.cpp", - "src/packages/UidMap.cpp", - "src/shell/shell_config.proto", - "src/shell/ShellSubscriber.cpp", - "src/socket/StatsSocketListener.cpp", - "src/state/StateManager.cpp", - "src/state/StateTracker.cpp", - "src/stats_log_util.cpp", - "src/statscompanion_util.cpp", - "src/statsd_config.proto", - "src/statsd_metadata.proto", - "src/StatsLogProcessor.cpp", - "src/StatsService.cpp", - "src/storage/StorageManager.cpp", - "src/subscriber/IncidentdReporter.cpp", - "src/subscriber/SubscriberReporter.cpp", - "src/uid_data.proto", - "src/utils/MultiConditionTrigger.cpp", - ], - - local_include_dirs: [ - "src", - ], - - static_libs: [ - "libbase", - "libcutils", - "libgtest_prod", - "libprotoutil", - "libstatslog_statsd", - "libsysutils", - "libutils", - "server_configurable_flags", - "statsd-aidl-ndk_platform", - ], - shared_libs: [ - "libbinder_ndk", - "libincident", - "liblog", - ], -} - -genrule { - name: "statslog_statsd.h", - tools: ["stats-log-api-gen"], - cmd: "$(location stats-log-api-gen) --header $(genDir)/statslog_statsd.h --module statsd --namespace android,os,statsd,util", - out: [ - "statslog_statsd.h", - ], -} - -genrule { - name: "statslog_statsd.cpp", - tools: ["stats-log-api-gen"], - cmd: "$(location stats-log-api-gen) --cpp $(genDir)/statslog_statsd.cpp --module statsd --namespace android,os,statsd,util --importHeader statslog_statsd.h", - out: [ - "statslog_statsd.cpp", - ], -} - -genrule { - name: "statslog_statsdtest.h", - tools: ["stats-log-api-gen"], - cmd: "$(location stats-log-api-gen) --header $(genDir)/statslog_statsdtest.h --module statsdtest --namespace android,os,statsd,util", - out: [ - "statslog_statsdtest.h", - ], -} - -genrule { - name: "statslog_statsdtest.cpp", - tools: ["stats-log-api-gen"], - cmd: "$(location stats-log-api-gen) --cpp $(genDir)/statslog_statsdtest.cpp --module statsdtest --namespace android,os,statsd,util --importHeader statslog_statsdtest.h", - out: [ - "statslog_statsdtest.cpp", - ], -} - -cc_library_static { - name: "libstatslog_statsdtest", - generated_sources: ["statslog_statsdtest.cpp"], - generated_headers: ["statslog_statsdtest.h"], - export_generated_headers: ["statslog_statsdtest.h"], - shared_libs: [ - "libstatssocket", - ] -} - -cc_library_static { - name: "libstatslog_statsd", - generated_sources: ["statslog_statsd.cpp"], - generated_headers: ["statslog_statsd.h"], - export_generated_headers: ["statslog_statsd.h"], - apex_available: [ - "com.android.os.statsd", - "test_com.android.os.statsd", - ], - shared_libs: [ - "libstatssocket", - ] -} - -// ========= -// statsd -// ========= - -cc_binary { - name: "statsd", - defaults: ["statsd_defaults"], - - srcs: ["src/main.cpp"], - - cflags: [ - "-Wall", - "-Wextra", - "-Werror", - "-Wno-unused-parameter", - // optimize for size (protobuf glop can get big) - "-Os", - // "-g", - // "-O0", - ], - - product_variables: { - eng: { - // Enable sanitizer ONLY on eng builds - //sanitize: { - // address: true, - //}, - }, - }, - - proto: { - type: "lite", - static: true, - }, - stl: "libc++_static", - - shared_libs: [ - "libstatssocket", - ], - - apex_available: [ - "com.android.os.statsd", - "test_com.android.os.statsd", - ], -} - -// ============== -// statsd_test -// ============== - -cc_test { - name: "statsd_test", - defaults: ["statsd_defaults"], - test_suites: ["device-tests", "mts-statsd"], - test_config: "statsd_test.xml", - - //TODO(b/153588990): Remove when the build system properly separates - //32bit and 64bit architectures. - compile_multilib: "both", - multilib: { - lib64: { - suffix: "64", - }, - lib32: { - suffix: "32", - }, - }, - - cflags: [ - "-Wall", - "-Werror", - "-Wno-missing-field-initializers", - "-Wno-unused-variable", - "-Wno-unused-function", - "-Wno-unused-parameter", - ], - - require_root: true, - - srcs: [ - // atom_field_options.proto needs field_options.proto, but that is - // not included in libprotobuf-cpp-lite, so compile it here. - ":libprotobuf-internal-protos", - ":libstats_internal_protos", - - "src/shell/shell_data.proto", - "src/stats_log.proto", - "tests/AlarmMonitor_test.cpp", - "tests/anomaly/AlarmTracker_test.cpp", - "tests/anomaly/AnomalyTracker_test.cpp", - "tests/condition/CombinationConditionTracker_test.cpp", - "tests/condition/ConditionTimer_test.cpp", - "tests/condition/SimpleConditionTracker_test.cpp", - "tests/ConfigManager_test.cpp", - "tests/e2e/Alarm_e2e_test.cpp", - "tests/e2e/Anomaly_count_e2e_test.cpp", - "tests/e2e/Anomaly_duration_sum_e2e_test.cpp", - "tests/e2e/Attribution_e2e_test.cpp", - "tests/e2e/ConfigTtl_e2e_test.cpp", - "tests/e2e/ConfigUpdate_e2e_ab_test.cpp", - "tests/e2e/ConfigUpdate_e2e_test.cpp", - "tests/e2e/CountMetric_e2e_test.cpp", - "tests/e2e/DurationMetric_e2e_test.cpp", - "tests/e2e/GaugeMetric_e2e_pull_test.cpp", - "tests/e2e/GaugeMetric_e2e_push_test.cpp", - "tests/e2e/MetricActivation_e2e_test.cpp", - "tests/e2e/MetricConditionLink_e2e_test.cpp", - "tests/e2e/PartialBucket_e2e_test.cpp", - "tests/e2e/ValueMetric_pull_e2e_test.cpp", - "tests/e2e/WakelockDuration_e2e_test.cpp", - "tests/external/puller_util_test.cpp", - "tests/external/StatsCallbackPuller_test.cpp", - "tests/external/StatsPuller_test.cpp", - "tests/external/StatsPullerManager_test.cpp", - "tests/FieldValue_test.cpp", - "tests/guardrail/StatsdStats_test.cpp", - "tests/HashableDimensionKey_test.cpp", - "tests/indexed_priority_queue_test.cpp", - "tests/log_event/LogEventQueue_test.cpp", - "tests/LogEntryMatcher_test.cpp", - "tests/LogEvent_test.cpp", - "tests/metadata_util_test.cpp", - "tests/metrics/CountMetricProducer_test.cpp", - "tests/metrics/DurationMetricProducer_test.cpp", - "tests/metrics/EventMetricProducer_test.cpp", - "tests/metrics/GaugeMetricProducer_test.cpp", - "tests/metrics/MaxDurationTracker_test.cpp", - "tests/metrics/metrics_test_helper.cpp", - "tests/metrics/OringDurationTracker_test.cpp", - "tests/metrics/ValueMetricProducer_test.cpp", - "tests/metrics/parsing_utils/config_update_utils_test.cpp", - "tests/metrics/parsing_utils/metrics_manager_util_test.cpp", - "tests/subscriber/SubscriberReporter_test.cpp", - "tests/MetricsManager_test.cpp", - "tests/shell/ShellSubscriber_test.cpp", - "tests/state/StateTracker_test.cpp", - "tests/statsd_test_util.cpp", - "tests/StatsLogProcessor_test.cpp", - "tests/StatsService_test.cpp", - "tests/storage/StorageManager_test.cpp", - "tests/UidMap_test.cpp", - "tests/utils/MultiConditionTrigger_test.cpp", - ], - - static_libs: [ - "libgmock", - "libplatformprotos", - "libstatslog_statsdtest", - "libstatssocket_private", - ], - - proto: { - type: "lite", - include_dirs: [ - "external/protobuf/src", - "frameworks/proto_logging/stats", - ], - }, - -} - -//############################# -// statsd micro benchmark -//############################# - -cc_benchmark { - name: "statsd_benchmark", - defaults: ["statsd_defaults"], - - srcs: [ - // atom_field_options.proto needs field_options.proto, but that is - // not included in libprotobuf-cpp-lite, so compile it here. - ":libprotobuf-internal-protos", - ":libstats_internal_protos", - - "benchmark/duration_metric_benchmark.cpp", - "benchmark/filter_value_benchmark.cpp", - "benchmark/get_dimensions_for_condition_benchmark.cpp", - "benchmark/hello_world_benchmark.cpp", - "benchmark/log_event_benchmark.cpp", - "benchmark/main.cpp", - "benchmark/metric_util.cpp", - "benchmark/stats_write_benchmark.cpp", - "src/stats_log.proto", - ], - - proto: { - type: "lite", - include_dirs: [ - "external/protobuf/src", - "frameworks/proto_logging/stats", - ], - }, - - cflags: [ - "-Wall", - "-Werror", - "-Wno-unused-parameter", - "-Wno-unused-variable", - "-Wno-unused-function", - - // Bug: http://b/29823425 Disable -Wvarargs for Clang update to r271374 - "-Wno-varargs", - ], - - static_libs: [ - "libplatformprotos", - "libstatssocket_private", - ], - - shared_libs: [ - "libgtest_prod", - "libprotobuf-cpp-lite", - "libstatslog", - ], -} - -// ==== java proto device library (for test only) ============================== -java_library { - name: "statsdprotolite", - sdk_version: "core_current", - proto: { - type: "lite", - include_dirs: [ - "external/protobuf/src", - "frameworks/proto_logging/stats", - ], - }, - - srcs: [ - ":libstats_atoms_proto", - "src/shell/shell_config.proto", - "src/shell/shell_data.proto", - "src/stats_log.proto", - "src/statsd_config.proto", - ], - - static_libs: [ - "platformprotoslite", - ], - // Protos have lots of MissingOverride and similar. - errorprone: { - javacflags: ["-XepDisableAllChecks"], - }, -} - -// Filegroup for statsd config proto definition. -filegroup { - name: "statsd-config-proto-def", - srcs: ["src/statsd_config.proto"], -} - -// Filegroup for all statsd protos -filegroup { - name: "statsd_internal_protos", - srcs: [ - "src/active_config_list.proto", - "src/experiment_ids.proto", - "src/shell/shell_config.proto", - "src/shell/shell_data.proto", - "src/statsd_config.proto", - "src/statsd_metadata.proto", - "src/stats_log.proto", - "src/uid_data.proto", - ], -} diff --git a/bin/TEST_MAPPING b/bin/TEST_MAPPING deleted file mode 100644 index 8dee073a..00000000 --- a/bin/TEST_MAPPING +++ /dev/null @@ -1,7 +0,0 @@ -{ - "presubmit" : [ - { - "name" : "statsd_test" - } - ] -} \ No newline at end of file diff --git a/bin/benchmark/duration_metric_benchmark.cpp b/bin/benchmark/duration_metric_benchmark.cpp deleted file mode 100644 index 2d315d93..00000000 --- a/bin/benchmark/duration_metric_benchmark.cpp +++ /dev/null @@ -1,314 +0,0 @@ -/* - * Copyright (C) 2018 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 "benchmark/benchmark.h" -#include "FieldValue.h" -#include "HashableDimensionKey.h" -#include "logd/LogEvent.h" -#include "stats_log_util.h" -#include "metric_util.h" - -namespace android { -namespace os { -namespace statsd { - -using std::vector; - -static StatsdConfig CreateDurationMetricConfig_NoLink_AND_CombinationCondition( - DurationMetric::AggregationType aggregationType, bool addExtraDimensionInCondition) { - StatsdConfig config; - *config.add_atom_matcher() = CreateStartScheduledJobAtomMatcher(); - *config.add_atom_matcher() = CreateFinishScheduledJobAtomMatcher(); - *config.add_atom_matcher() = CreateSyncStartAtomMatcher(); - *config.add_atom_matcher() = CreateSyncEndAtomMatcher(); - *config.add_atom_matcher() = CreateScreenTurnedOnAtomMatcher(); - *config.add_atom_matcher() = CreateScreenTurnedOffAtomMatcher(); - - auto scheduledJobPredicate = CreateScheduledJobPredicate(); - auto dimensions = scheduledJobPredicate.mutable_simple_predicate()->mutable_dimensions(); - dimensions->set_field(android::util::SCHEDULED_JOB_STATE_CHANGED); - dimensions->add_child()->set_field(2); // job name field. - - auto screenIsOffPredicate = CreateScreenIsOffPredicate(); - - auto isSyncingPredicate = CreateIsSyncingPredicate(); - auto syncDimension = isSyncingPredicate.mutable_simple_predicate()->mutable_dimensions(); - *syncDimension = CreateAttributionUidAndTagDimensions(android::util::SYNC_STATE_CHANGED, - {Position::FIRST}); - if (addExtraDimensionInCondition) { - syncDimension->add_child()->set_field(2 /* name field*/); - } - - *config.add_predicate() = scheduledJobPredicate; - *config.add_predicate() = screenIsOffPredicate; - *config.add_predicate() = isSyncingPredicate; - auto combinationPredicate = config.add_predicate(); - combinationPredicate->set_id(StringToId("CombinationPredicate")); - combinationPredicate->mutable_combination()->set_operation(LogicalOperation::AND); - addPredicateToPredicateCombination(screenIsOffPredicate, combinationPredicate); - addPredicateToPredicateCombination(isSyncingPredicate, combinationPredicate); - - auto metric = config.add_duration_metric(); - metric->set_bucket(FIVE_MINUTES); - metric->set_id(StringToId("scheduledJob")); - metric->set_what(scheduledJobPredicate.id()); - metric->set_condition(combinationPredicate->id()); - metric->set_aggregation_type(aggregationType); - auto dimensionWhat = metric->mutable_dimensions_in_what(); - dimensionWhat->set_field(android::util::SCHEDULED_JOB_STATE_CHANGED); - dimensionWhat->add_child()->set_field(2); // job name field. - *metric->mutable_dimensions_in_condition() = CreateAttributionUidAndTagDimensions( - android::util::SYNC_STATE_CHANGED, {Position::FIRST}); - return config; -} - -static StatsdConfig CreateDurationMetricConfig_Link_AND_CombinationCondition( - DurationMetric::AggregationType aggregationType, bool addExtraDimensionInCondition) { - StatsdConfig config; - *config.add_atom_matcher() = CreateStartScheduledJobAtomMatcher(); - *config.add_atom_matcher() = CreateFinishScheduledJobAtomMatcher(); - *config.add_atom_matcher() = CreateSyncStartAtomMatcher(); - *config.add_atom_matcher() = CreateSyncEndAtomMatcher(); - *config.add_atom_matcher() = CreateScreenTurnedOnAtomMatcher(); - *config.add_atom_matcher() = CreateScreenTurnedOffAtomMatcher(); - - auto scheduledJobPredicate = CreateScheduledJobPredicate(); - auto dimensions = scheduledJobPredicate.mutable_simple_predicate()->mutable_dimensions(); - *dimensions = CreateAttributionUidDimensions( - android::util::SCHEDULED_JOB_STATE_CHANGED, {Position::FIRST}); - dimensions->add_child()->set_field(2); // job name field. - - auto isSyncingPredicate = CreateIsSyncingPredicate(); - auto syncDimension = isSyncingPredicate.mutable_simple_predicate()->mutable_dimensions(); - *syncDimension = CreateAttributionUidDimensions( - android::util::SYNC_STATE_CHANGED, {Position::FIRST}); - if (addExtraDimensionInCondition) { - syncDimension->add_child()->set_field(2 /* name field*/); - } - - auto screenIsOffPredicate = CreateScreenIsOffPredicate(); - - *config.add_predicate() = scheduledJobPredicate; - *config.add_predicate() = screenIsOffPredicate; - *config.add_predicate() = isSyncingPredicate; - auto combinationPredicate = config.add_predicate(); - combinationPredicate->set_id(StringToId("CombinationPredicate")); - combinationPredicate->mutable_combination()->set_operation(LogicalOperation::AND); - addPredicateToPredicateCombination(screenIsOffPredicate, combinationPredicate); - addPredicateToPredicateCombination(isSyncingPredicate, combinationPredicate); - - auto metric = config.add_duration_metric(); - metric->set_bucket(FIVE_MINUTES); - metric->set_id(StringToId("scheduledJob")); - metric->set_what(scheduledJobPredicate.id()); - metric->set_condition(combinationPredicate->id()); - metric->set_aggregation_type(aggregationType); - *metric->mutable_dimensions_in_what() = CreateAttributionUidDimensions( - android::util::SCHEDULED_JOB_STATE_CHANGED, {Position::FIRST}); - - auto links = metric->add_links(); - links->set_condition(isSyncingPredicate.id()); - *links->mutable_fields_in_what() = - CreateAttributionUidDimensions( - android::util::SCHEDULED_JOB_STATE_CHANGED, {Position::FIRST}); - *links->mutable_fields_in_condition() = - CreateAttributionUidDimensions(android::util::SYNC_STATE_CHANGED, {Position::FIRST}); - return config; -} - -static void BM_DurationMetricNoLink(benchmark::State& state) { - ConfigKey cfgKey; - auto config = CreateDurationMetricConfig_NoLink_AND_CombinationCondition( - DurationMetric::SUM, false); - int64_t bucketStartTimeNs = 10000000000; - int64_t bucketSizeNs = - TimeUnitToBucketSizeInMillis(config.duration_metric(0).bucket()) * 1000000LL; - - std::vector> events; - - events.push_back(CreateScreenStateChangedEvent(bucketStartTimeNs + 11, - android::view::DISPLAY_STATE_OFF)); - events.push_back( - CreateScreenStateChangedEvent(bucketStartTimeNs + 40, android::view::DISPLAY_STATE_ON)); - - events.push_back(CreateScreenStateChangedEvent(bucketStartTimeNs + 102, - android::view::DISPLAY_STATE_OFF)); - events.push_back(CreateScreenStateChangedEvent(bucketStartTimeNs + 450, - android::view::DISPLAY_STATE_ON)); - - events.push_back(CreateScreenStateChangedEvent(bucketStartTimeNs + 650, - android::view::DISPLAY_STATE_OFF)); - events.push_back(CreateScreenStateChangedEvent(bucketStartTimeNs + bucketSizeNs + 100, - android::view::DISPLAY_STATE_ON)); - - events.push_back(CreateScreenStateChangedEvent(bucketStartTimeNs + bucketSizeNs + 640, - android::view::DISPLAY_STATE_OFF)); - events.push_back(CreateScreenStateChangedEvent(bucketStartTimeNs + bucketSizeNs + 650, - android::view::DISPLAY_STATE_ON)); - - vector attributionUids1 = {9999}; - vector attributionTags1 = {""}; - events.push_back(CreateStartScheduledJobEvent(bucketStartTimeNs + 2, attributionUids1, - attributionTags1, "job0")); - events.push_back(CreateFinishScheduledJobEvent(bucketStartTimeNs + 101, attributionUids1, - attributionTags1, "job0")); - - events.push_back(CreateStartScheduledJobEvent(bucketStartTimeNs + 201, attributionUids1, - attributionTags1, "job2")); - events.push_back(CreateFinishScheduledJobEvent(bucketStartTimeNs + 500, attributionUids1, - attributionTags1, "job2")); - - vector attributionUids2 = {8888}; - events.push_back(CreateStartScheduledJobEvent(bucketStartTimeNs + 600, attributionUids2, - attributionTags1, "job2")); - events.push_back(CreateFinishScheduledJobEvent(bucketStartTimeNs + bucketSizeNs + 850, - attributionUids2, attributionTags1, "job2")); - - events.push_back(CreateStartScheduledJobEvent(bucketStartTimeNs + bucketSizeNs + 600, - attributionUids2, attributionTags1, "job1")); - events.push_back(CreateFinishScheduledJobEvent(bucketStartTimeNs + bucketSizeNs + 900, - attributionUids2, attributionTags1, "job1")); - - vector attributionUids3 = {111, 222, 222}; - vector attributionTags3 = {"App1", "GMSCoreModule1", "GMSCoreModule2"}; - events.push_back(CreateSyncStartEvent(bucketStartTimeNs + 10, attributionUids3, - attributionTags3, "ReadEmail")); - events.push_back(CreateSyncEndEvent(bucketStartTimeNs + 50, attributionUids3, attributionTags3, - "ReadEmail")); - - events.push_back(CreateSyncStartEvent(bucketStartTimeNs + 200, attributionUids3, - attributionTags3, "ReadEmail")); - events.push_back(CreateSyncEndEvent(bucketStartTimeNs + bucketSizeNs + 300, attributionUids3, - attributionTags3, "ReadEmail")); - - events.push_back(CreateSyncStartEvent(bucketStartTimeNs + 400, attributionUids3, - attributionTags3, "ReadDoc")); - events.push_back(CreateSyncEndEvent(bucketStartTimeNs + bucketSizeNs - 1, attributionUids3, - attributionTags3, "ReadDoc")); - - vector attributionUids4 = {333, 222, 555}; - vector attributionTags4 = {"App2", "GMSCoreModule1", "GMSCoreModule2"}; - events.push_back(CreateSyncStartEvent(bucketStartTimeNs + 401, attributionUids4, - attributionTags4, "ReadEmail")); - events.push_back(CreateSyncEndEvent(bucketStartTimeNs + bucketSizeNs + 700, attributionUids4, - attributionTags4, "ReadEmail")); - sortLogEventsByTimestamp(&events); - - while (state.KeepRunning()) { - auto processor = CreateStatsLogProcessor( - bucketStartTimeNs / NS_PER_SEC, config, cfgKey); - for (const auto& event : events) { - processor->OnLogEvent(event.get()); - } - } -} - -BENCHMARK(BM_DurationMetricNoLink); - - -static void BM_DurationMetricLink(benchmark::State& state) { - ConfigKey cfgKey; - auto config = CreateDurationMetricConfig_Link_AND_CombinationCondition( - DurationMetric::SUM, false); - int64_t bucketStartTimeNs = 10000000000; - int64_t bucketSizeNs = - TimeUnitToBucketSizeInMillis(config.duration_metric(0).bucket()) * 1000000LL; - - std::vector> events; - - events.push_back(CreateScreenStateChangedEvent(bucketStartTimeNs + 55, - android::view::DISPLAY_STATE_OFF)); - events.push_back(CreateScreenStateChangedEvent(bucketStartTimeNs + 120, - android::view::DISPLAY_STATE_ON)); - events.push_back(CreateScreenStateChangedEvent(bucketStartTimeNs + 121, - android::view::DISPLAY_STATE_OFF)); - events.push_back(CreateScreenStateChangedEvent(bucketStartTimeNs + 450, - android::view::DISPLAY_STATE_ON)); - - events.push_back(CreateScreenStateChangedEvent(bucketStartTimeNs + 501, - android::view::DISPLAY_STATE_OFF)); - events.push_back(CreateScreenStateChangedEvent(bucketStartTimeNs + bucketSizeNs + 100, - android::view::DISPLAY_STATE_ON)); - - vector attributionUids1 = {111}; - vector attributionTags1 = {"App1"}; - events.push_back(CreateStartScheduledJobEvent(bucketStartTimeNs + 1, attributionUids1, - attributionTags1, "job1")); - events.push_back(CreateFinishScheduledJobEvent(bucketStartTimeNs + 101, attributionUids1, - attributionTags1, "job1")); - - vector attributionUids2 = {333}; - vector attributionTags2 = {"App2"}; - events.push_back(CreateStartScheduledJobEvent(bucketStartTimeNs + 201, attributionUids2, - attributionTags2, "job2")); - events.push_back(CreateFinishScheduledJobEvent(bucketStartTimeNs + 500, attributionUids2, - attributionTags2, "job2")); - events.push_back(CreateStartScheduledJobEvent(bucketStartTimeNs + 600, attributionUids2, - attributionTags2, "job2")); - events.push_back(CreateFinishScheduledJobEvent(bucketStartTimeNs + bucketSizeNs + 850, - attributionUids2, attributionTags2, "job2")); - - vector attributionUids3 = {444}; - vector attributionTags3 = {"App3"}; - events.push_back(CreateStartScheduledJobEvent(bucketStartTimeNs + bucketSizeNs - 2, - attributionUids3, attributionTags3, "job3")); - events.push_back(CreateFinishScheduledJobEvent(bucketStartTimeNs + bucketSizeNs + 900, - attributionUids3, attributionTags3, "job3")); - - vector attributionUids4 = {111, 222, 222}; - vector attributionTags4 = {"App1", "GMSCoreModule1", "GMSCoreModule2"}; - events.push_back(CreateSyncStartEvent(bucketStartTimeNs + 50, attributionUids4, - attributionTags4, "ReadEmail")); - events.push_back(CreateSyncEndEvent(bucketStartTimeNs + 110, attributionUids4, attributionTags4, - "ReadEmail")); - - vector attributionUids5 = {333, 222, 555}; - vector attributionTags5 = {"App2", "GMSCoreModule1", "GMSCoreModule2"}; - events.push_back(CreateSyncStartEvent(bucketStartTimeNs + 300, attributionUids5, - attributionTags5, "ReadEmail")); - events.push_back(CreateSyncEndEvent(bucketStartTimeNs + bucketSizeNs + 700, attributionUids5, - attributionTags5, "ReadEmail")); - events.push_back(CreateSyncStartEvent(bucketStartTimeNs + 400, attributionUids5, - attributionTags5, "ReadDoc")); - events.push_back(CreateSyncEndEvent(bucketStartTimeNs + bucketSizeNs - 1, attributionUids5, - attributionTags5, "ReadDoc")); - - vector attributionUids6 = {444, 222, 555}; - vector attributionTags6 = {"App3", "GMSCoreModule1", "GMSCoreModule2"}; - events.push_back(CreateSyncStartEvent(bucketStartTimeNs + 550, attributionUids6, - attributionTags6, "ReadDoc")); - events.push_back(CreateSyncEndEvent(bucketStartTimeNs + 800, attributionUids6, attributionTags6, - "ReadDoc")); - events.push_back(CreateSyncStartEvent(bucketStartTimeNs + bucketSizeNs - 1, attributionUids6, - attributionTags6, "ReadDoc")); - events.push_back(CreateSyncEndEvent(bucketStartTimeNs + bucketSizeNs + 700, attributionUids6, - attributionTags6, "ReadDoc")); - sortLogEventsByTimestamp(&events); - - while (state.KeepRunning()) { - auto processor = CreateStatsLogProcessor( - bucketStartTimeNs / NS_PER_SEC, config, cfgKey); - for (const auto& event : events) { - processor->OnLogEvent(event.get()); - } - } -} - -BENCHMARK(BM_DurationMetricLink); - -} // namespace statsd -} // namespace os -} // namespace android diff --git a/bin/benchmark/filter_value_benchmark.cpp b/bin/benchmark/filter_value_benchmark.cpp deleted file mode 100644 index 743ccc4e..00000000 --- a/bin/benchmark/filter_value_benchmark.cpp +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright (C) 2018 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 "FieldValue.h" -#include "HashableDimensionKey.h" -#include "benchmark/benchmark.h" -#include "logd/LogEvent.h" -#include "metric_util.h" -#include "stats_event.h" -#include "stats_log_util.h" - -namespace android { -namespace os { -namespace statsd { - -using std::vector; - -static void createLogEventAndMatcher(LogEvent* event, FieldMatcher* field_matcher) { - AStatsEvent* statsEvent = AStatsEvent_obtain(); - AStatsEvent_setAtomId(statsEvent, 1); - AStatsEvent_overwriteTimestamp(statsEvent, 100000); - - std::vector attributionUids = {100, 100}; - std::vector attributionTags = {"LOCATION", "LOCATION"}; - writeAttribution(statsEvent, attributionUids, attributionTags); - - AStatsEvent_writeFloat(statsEvent, 3.2f); - AStatsEvent_writeString(statsEvent, "LOCATION"); - AStatsEvent_writeInt64(statsEvent, 990); - - parseStatsEventToLogEvent(statsEvent, event); - - field_matcher->set_field(1); - auto child = field_matcher->add_child(); - child->set_field(1); - child->set_position(FIRST); - child->add_child()->set_field(1); -} - -static void BM_FilterValue(benchmark::State& state) { - LogEvent event(/*uid=*/0, /*pid=*/0); - FieldMatcher field_matcher; - createLogEventAndMatcher(&event, &field_matcher); - - std::vector matchers; - translateFieldMatcher(field_matcher, &matchers); - - while (state.KeepRunning()) { - HashableDimensionKey output; - filterValues(matchers, event.getValues(), &output); - } -} - -BENCHMARK(BM_FilterValue); - -} // namespace statsd -} // namespace os -} // namespace android diff --git a/bin/benchmark/get_dimensions_for_condition_benchmark.cpp b/bin/benchmark/get_dimensions_for_condition_benchmark.cpp deleted file mode 100644 index 7a455650..00000000 --- a/bin/benchmark/get_dimensions_for_condition_benchmark.cpp +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright (C) 2018 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 "FieldValue.h" -#include "HashableDimensionKey.h" -#include "benchmark/benchmark.h" -#include "logd/LogEvent.h" -#include "metric_util.h" -#include "stats_event.h" -#include "stats_log_util.h" - -namespace android { -namespace os { -namespace statsd { - -using std::vector; - -static void createLogEventAndLink(LogEvent* event, Metric2Condition *link) { - AStatsEvent* statsEvent = AStatsEvent_obtain(); - AStatsEvent_setAtomId(statsEvent, 1); - AStatsEvent_overwriteTimestamp(statsEvent, 100000); - - std::vector attributionUids = {100, 100}; - std::vector attributionTags = {"LOCATION", "LOCATION"}; - writeAttribution(statsEvent, attributionUids, attributionTags); - - AStatsEvent_writeFloat(statsEvent, 3.2f); - AStatsEvent_writeString(statsEvent, "LOCATION"); - AStatsEvent_writeInt64(statsEvent, 990); - - parseStatsEventToLogEvent(statsEvent, event); - - link->conditionId = 1; - - FieldMatcher field_matcher; - field_matcher.set_field(event->GetTagId()); - auto child = field_matcher.add_child(); - child->set_field(1); - child->set_position(FIRST); - child->add_child()->set_field(1); - - translateFieldMatcher(field_matcher, &link->metricFields); - field_matcher.set_field(event->GetTagId() + 1); - translateFieldMatcher(field_matcher, &link->conditionFields); -} - -static void BM_GetDimensionInCondition(benchmark::State& state) { - Metric2Condition link; - LogEvent event(/*uid=*/0, /*pid=*/0); - createLogEventAndLink(&event, &link); - - while (state.KeepRunning()) { - HashableDimensionKey output; - getDimensionForCondition(event.getValues(), link, &output); - } -} - -BENCHMARK(BM_GetDimensionInCondition); - - -} // namespace statsd -} // namespace os -} // namespace android diff --git a/bin/benchmark/hello_world_benchmark.cpp b/bin/benchmark/hello_world_benchmark.cpp deleted file mode 100644 index c732d394..00000000 --- a/bin/benchmark/hello_world_benchmark.cpp +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright (C) 2018 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 "benchmark/benchmark.h" - -static void BM_StringCreation(benchmark::State& state) { - while (state.KeepRunning()) std::string empty_string; -} -// Register the function as a benchmark -BENCHMARK(BM_StringCreation); - -// Define another benchmark -static void BM_StringCopy(benchmark::State& state) { - std::string x = "hello"; - while (state.KeepRunning()) std::string copy(x); -} -BENCHMARK(BM_StringCopy); diff --git a/bin/benchmark/log_event_benchmark.cpp b/bin/benchmark/log_event_benchmark.cpp deleted file mode 100644 index 057e00bd..00000000 --- a/bin/benchmark/log_event_benchmark.cpp +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright (C) 2018 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 "benchmark/benchmark.h" -#include "logd/LogEvent.h" -#include "stats_event.h" - -namespace android { -namespace os { -namespace statsd { - -static size_t createAndParseStatsEvent(uint8_t* msg) { - AStatsEvent* event = AStatsEvent_obtain(); - AStatsEvent_setAtomId(event, 100); - AStatsEvent_writeInt32(event, 2); - AStatsEvent_writeFloat(event, 2.0); - AStatsEvent_build(event); - - size_t size; - uint8_t* buf = AStatsEvent_getBuffer(event, &size); - memcpy(msg, buf, size); - return size; -} - -static void BM_LogEventCreation(benchmark::State& state) { - uint8_t msg[LOGGER_ENTRY_MAX_PAYLOAD]; - size_t size = createAndParseStatsEvent(msg); - while (state.KeepRunning()) { - LogEvent event(/*uid=*/ 1000, /*pid=*/ 1001); - benchmark::DoNotOptimize(event.parseBuffer(msg, size)); - } -} -BENCHMARK(BM_LogEventCreation); - -} // namespace statsd -} // namespace os -} // namespace android diff --git a/bin/benchmark/main.cpp b/bin/benchmark/main.cpp deleted file mode 100644 index 08921f3c..00000000 --- a/bin/benchmark/main.cpp +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Copyright (C) 2018 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 - -BENCHMARK_MAIN(); diff --git a/bin/benchmark/metric_util.cpp b/bin/benchmark/metric_util.cpp deleted file mode 100644 index 89fd3d9b..00000000 --- a/bin/benchmark/metric_util.cpp +++ /dev/null @@ -1,379 +0,0 @@ -// 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 "metric_util.h" - -#include "stats_event.h" - -namespace android { -namespace os { -namespace statsd { - -AtomMatcher CreateSimpleAtomMatcher(const string& name, int atomId) { - 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(atomId); - return atom_matcher; -} - -AtomMatcher CreateScheduledJobStateChangedAtomMatcher(const string& name, - ScheduledJobStateChanged::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::SCHEDULED_JOB_STATE_CHANGED); - auto field_value_matcher = simple_atom_matcher->add_field_value_matcher(); - field_value_matcher->set_field(3); // State field. - field_value_matcher->set_eq_int(state); - return atom_matcher; -} - -AtomMatcher CreateStartScheduledJobAtomMatcher() { - return CreateScheduledJobStateChangedAtomMatcher("ScheduledJobStart", - ScheduledJobStateChanged::STARTED); -} - -AtomMatcher CreateFinishScheduledJobAtomMatcher() { - return CreateScheduledJobStateChangedAtomMatcher("ScheduledJobFinish", - ScheduledJobStateChanged::FINISHED); -} - -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; - atom_matcher.set_id(StringToId(name)); - auto simple_atom_matcher = atom_matcher.mutable_simple_atom_matcher(); - simple_atom_matcher->set_atom_id(android::util::WAKELOCK_STATE_CHANGED); - auto field_value_matcher = simple_atom_matcher->add_field_value_matcher(); - field_value_matcher->set_field(4); // State field. - field_value_matcher->set_eq_int(state); - return atom_matcher; -} - -AtomMatcher CreateAcquireWakelockAtomMatcher() { - return CreateWakelockStateChangedAtomMatcher("AcquireWakelock", WakelockStateChanged::ACQUIRE); -} - -AtomMatcher CreateReleaseWakelockAtomMatcher() { - return CreateWakelockStateChangedAtomMatcher("ReleaseWakelock", WakelockStateChanged::RELEASE); -} - -AtomMatcher CreateScreenStateChangedAtomMatcher( - const string& name, android::view::DisplayStateEnum 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::SCREEN_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 CreateScreenTurnedOnAtomMatcher() { - return CreateScreenStateChangedAtomMatcher("ScreenTurnedOn", - android::view::DisplayStateEnum::DISPLAY_STATE_ON); -} - -AtomMatcher CreateScreenTurnedOffAtomMatcher() { - return CreateScreenStateChangedAtomMatcher("ScreenTurnedOff", - ::android::view::DisplayStateEnum::DISPLAY_STATE_OFF); -} - -AtomMatcher CreateSyncStateChangedAtomMatcher( - const string& name, SyncStateChanged::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::SYNC_STATE_CHANGED); - auto field_value_matcher = simple_atom_matcher->add_field_value_matcher(); - field_value_matcher->set_field(3); // State field. - field_value_matcher->set_eq_int(state); - return atom_matcher; -} - -AtomMatcher CreateSyncStartAtomMatcher() { - return CreateSyncStateChangedAtomMatcher("SyncStart", SyncStateChanged::ON); -} - -AtomMatcher CreateSyncEndAtomMatcher() { - return CreateSyncStateChangedAtomMatcher("SyncEnd", SyncStateChanged::OFF); -} - -AtomMatcher CreateActivityForegroundStateChangedAtomMatcher( - const string& name, ActivityForegroundStateChanged::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::ACTIVITY_FOREGROUND_STATE_CHANGED); - auto field_value_matcher = simple_atom_matcher->add_field_value_matcher(); - field_value_matcher->set_field(4); // Activity field. - field_value_matcher->set_eq_int(state); - return atom_matcher; -} - -AtomMatcher CreateMoveToBackgroundAtomMatcher() { - return CreateActivityForegroundStateChangedAtomMatcher( - "MoveToBackground", ActivityForegroundStateChanged::BACKGROUND); -} - -AtomMatcher CreateMoveToForegroundAtomMatcher() { - return CreateActivityForegroundStateChangedAtomMatcher( - "MoveToForeground", ActivityForegroundStateChanged::FOREGROUND); -} - -Predicate CreateScheduledJobPredicate() { - Predicate predicate; - predicate.set_id(StringToId("ScheduledJobRunningPredicate")); - predicate.mutable_simple_predicate()->set_start(StringToId("ScheduledJobStart")); - predicate.mutable_simple_predicate()->set_stop(StringToId("ScheduledJobFinish")); - return predicate; -} - -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; - predicate.set_id(StringToId("ScreenIsOn")); - predicate.mutable_simple_predicate()->set_start(StringToId("ScreenTurnedOn")); - predicate.mutable_simple_predicate()->set_stop(StringToId("ScreenTurnedOff")); - return predicate; -} - -Predicate CreateScreenIsOffPredicate() { - Predicate predicate; - predicate.set_id(1111123); - predicate.mutable_simple_predicate()->set_start(StringToId("ScreenTurnedOff")); - predicate.mutable_simple_predicate()->set_stop(StringToId("ScreenTurnedOn")); - return predicate; -} - -Predicate CreateHoldingWakelockPredicate() { - Predicate predicate; - predicate.set_id(StringToId("HoldingWakelock")); - predicate.mutable_simple_predicate()->set_start(StringToId("AcquireWakelock")); - predicate.mutable_simple_predicate()->set_stop(StringToId("ReleaseWakelock")); - return predicate; -} - -Predicate CreateIsSyncingPredicate() { - Predicate predicate; - predicate.set_id(33333333333333); - predicate.mutable_simple_predicate()->set_start(StringToId("SyncStart")); - predicate.mutable_simple_predicate()->set_stop(StringToId("SyncEnd")); - return predicate; -} - -Predicate CreateIsInBackgroundPredicate() { - Predicate predicate; - predicate.set_id(StringToId("IsInBackground")); - predicate.mutable_simple_predicate()->set_start(StringToId("MoveToBackground")); - predicate.mutable_simple_predicate()->set_stop(StringToId("MoveToForeground")); - return predicate; -} - -void addPredicateToPredicateCombination(const Predicate& predicate, - Predicate* combinationPredicate) { - combinationPredicate->mutable_combination()->add_predicate(predicate.id()); -} - -FieldMatcher CreateAttributionUidDimensions(const int atomId, - const std::vector& positions) { - FieldMatcher dimensions; - dimensions.set_field(atomId); - for (const auto position : positions) { - auto child = dimensions.add_child(); - child->set_field(1); - child->set_position(position); - child->add_child()->set_field(1); - } - return dimensions; -} - -FieldMatcher CreateAttributionUidAndTagDimensions(const int atomId, - const std::vector& positions) { - FieldMatcher dimensions; - dimensions.set_field(atomId); - for (const auto position : positions) { - auto child = dimensions.add_child(); - child->set_field(1); - child->set_position(position); - child->add_child()->set_field(1); - child->add_child()->set_field(2); - } - return dimensions; -} - -FieldMatcher CreateDimensions(const int atomId, const std::vector& fields) { - FieldMatcher dimensions; - dimensions.set_field(atomId); - for (const int field : fields) { - dimensions.add_child()->set_field(field); - } - return dimensions; -} - -void writeAttribution(AStatsEvent* statsEvent, const vector& attributionUids, - const vector& attributionTags) { - vector cTags(attributionTags.size()); - for (int i = 0; i < cTags.size(); i++) { - cTags[i] = attributionTags[i].c_str(); - } - - AStatsEvent_writeAttributionChain(statsEvent, - reinterpret_cast(attributionUids.data()), - cTags.data(), attributionUids.size()); -} - -void parseStatsEventToLogEvent(AStatsEvent* statsEvent, LogEvent* logEvent) { - AStatsEvent_build(statsEvent); - - size_t size; - uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size); - logEvent->parseBuffer(buf, size); - - AStatsEvent_release(statsEvent); -} - -std::unique_ptr CreateScreenStateChangedEvent( - uint64_t timestampNs, const android::view::DisplayStateEnum state) { - AStatsEvent* statsEvent = AStatsEvent_obtain(); - AStatsEvent_setAtomId(statsEvent, util::SCREEN_STATE_CHANGED); - AStatsEvent_overwriteTimestamp(statsEvent, timestampNs); - AStatsEvent_writeInt32(statsEvent, state); - - std::unique_ptr logEvent = std::make_unique(/*uid=*/0, /*pid=*/0); - parseStatsEventToLogEvent(statsEvent, logEvent.get()); - return logEvent; -} - -std::unique_ptr CreateScheduledJobStateChangedEvent( - const vector& attributionUids, const vector& attributionTags, - const string& jobName, const ScheduledJobStateChanged::State state, uint64_t timestampNs) { - AStatsEvent* statsEvent = AStatsEvent_obtain(); - AStatsEvent_setAtomId(statsEvent, util::SCHEDULED_JOB_STATE_CHANGED); - AStatsEvent_overwriteTimestamp(statsEvent, timestampNs); - - writeAttribution(statsEvent, attributionUids, attributionTags); - AStatsEvent_writeString(statsEvent, jobName.c_str()); - AStatsEvent_writeInt32(statsEvent, state); - - std::unique_ptr logEvent = std::make_unique(/*uid=*/0, /*pid=*/0); - parseStatsEventToLogEvent(statsEvent, logEvent.get()); - return logEvent; -} - -std::unique_ptr CreateStartScheduledJobEvent(uint64_t timestampNs, - const vector& attributionUids, - const vector& attributionTags, - const string& jobName) { - return CreateScheduledJobStateChangedEvent(attributionUids, attributionTags, jobName, - ScheduledJobStateChanged::STARTED, timestampNs); -} - -// Create log event when scheduled job finishes. -std::unique_ptr CreateFinishScheduledJobEvent(uint64_t timestampNs, - const vector& attributionUids, - const vector& attributionTags, - const string& jobName) { - return CreateScheduledJobStateChangedEvent(attributionUids, attributionTags, jobName, - ScheduledJobStateChanged::FINISHED, timestampNs); -} - -std::unique_ptr CreateSyncStateChangedEvent(uint64_t timestampNs, - const vector& attributionUids, - const vector& attributionTags, - const string& name, - const SyncStateChanged::State state) { - AStatsEvent* statsEvent = AStatsEvent_obtain(); - AStatsEvent_setAtomId(statsEvent, util::SYNC_STATE_CHANGED); - AStatsEvent_overwriteTimestamp(statsEvent, timestampNs); - - writeAttribution(statsEvent, attributionUids, attributionTags); - AStatsEvent_writeString(statsEvent, name.c_str()); - AStatsEvent_writeInt32(statsEvent, state); - - std::unique_ptr logEvent = std::make_unique(/*uid=*/0, /*pid=*/0); - parseStatsEventToLogEvent(statsEvent, logEvent.get()); - return logEvent; -} - -std::unique_ptr CreateSyncStartEvent(uint64_t timestampNs, - const vector& attributionUids, - const vector& attributionTags, - const string& name) { - return CreateSyncStateChangedEvent(timestampNs, attributionUids, attributionTags, name, - SyncStateChanged::ON); -} - -std::unique_ptr CreateSyncEndEvent(uint64_t timestampNs, - const vector& attributionUids, - const vector& attributionTags, - const string& name) { - return CreateSyncStateChangedEvent(timestampNs, attributionUids, attributionTags, name, - SyncStateChanged::OFF); -} - -sp CreateStatsLogProcessor(const long timeBaseSec, const StatsdConfig& config, - const ConfigKey& key) { - sp uidMap = new UidMap(); - sp pullerManager = new StatsPullerManager(); - sp anomalyAlarmMonitor; - sp periodicAlarmMonitor; - sp processor = - new StatsLogProcessor(uidMap, pullerManager, anomalyAlarmMonitor, periodicAlarmMonitor, - timeBaseSec * NS_PER_SEC, [](const ConfigKey&) { return true; }, - [](const int&, const vector&) { return true; }); - processor->OnConfigUpdated(timeBaseSec * NS_PER_SEC, key, config); - return processor; -} - -void sortLogEventsByTimestamp(std::vector> *events) { - std::sort(events->begin(), events->end(), - [](const std::unique_ptr& a, const std::unique_ptr& b) { - return a->GetElapsedTimestampNs() < b->GetElapsedTimestampNs(); - }); -} - -int64_t StringToId(const string& str) { - return static_cast(std::hash()(str)); -} - - -} // namespace statsd -} // namespace os -} // namespace android diff --git a/bin/benchmark/metric_util.h b/bin/benchmark/metric_util.h deleted file mode 100644 index 69b8a31c..00000000 --- a/bin/benchmark/metric_util.h +++ /dev/null @@ -1,140 +0,0 @@ -// 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. - -#pragma once - -#include "packages/modules/StatsD/bin/src/stats_log.pb.h" -#include "packages/modules/StatsD/bin/src/statsd_config.pb.h" -#include "src/StatsLogProcessor.h" -#include "src/logd/LogEvent.h" -#include "stats_event.h" -#include "statslog.h" - -namespace android { -namespace os { -namespace statsd { - -// Create AtomMatcher proto to simply match a specific atom type. -AtomMatcher CreateSimpleAtomMatcher(const string& name, int atomId); - -// Create AtomMatcher proto for scheduled job state changed. -AtomMatcher CreateScheduledJobStateChangedAtomMatcher(); - -// Create AtomMatcher proto for starting a scheduled job. -AtomMatcher CreateStartScheduledJobAtomMatcher(); - -// Create AtomMatcher proto for a scheduled job is done. -AtomMatcher CreateFinishScheduledJobAtomMatcher(); - -// Create AtomMatcher proto for screen brightness state changed. -AtomMatcher CreateScreenBrightnessChangedAtomMatcher(); - -// Create AtomMatcher proto for acquiring wakelock. -AtomMatcher CreateAcquireWakelockAtomMatcher(); - -// Create AtomMatcher proto for releasing wakelock. -AtomMatcher CreateReleaseWakelockAtomMatcher() ; - -// Create AtomMatcher proto for screen turned on. -AtomMatcher CreateScreenTurnedOnAtomMatcher(); - -// Create AtomMatcher proto for screen turned off. -AtomMatcher CreateScreenTurnedOffAtomMatcher(); - -// Create AtomMatcher proto for app sync turned on. -AtomMatcher CreateSyncStartAtomMatcher(); - -// Create AtomMatcher proto for app sync turned off. -AtomMatcher CreateSyncEndAtomMatcher(); - -// Create AtomMatcher proto for app sync moves to background. -AtomMatcher CreateMoveToBackgroundAtomMatcher(); - -// Create AtomMatcher proto for app sync moves to foreground. -AtomMatcher CreateMoveToForegroundAtomMatcher(); - -// Create Predicate proto for screen is off. -Predicate CreateScreenIsOffPredicate(); - -// Create Predicate proto for a running scheduled job. -Predicate CreateScheduledJobPredicate(); - -// Create Predicate proto for holding wakelock. -Predicate CreateHoldingWakelockPredicate(); - -// Create a Predicate proto for app syncing. -Predicate CreateIsSyncingPredicate(); - -// Create a Predicate proto for app is in background. -Predicate CreateIsInBackgroundPredicate(); - -// Add a predicate to the predicate combination. -void addPredicateToPredicateCombination(const Predicate& predicate, Predicate* combination); - -// Create dimensions from primitive fields. -FieldMatcher CreateDimensions(const int atomId, const std::vector& fields); - -// Create dimensions by attribution uid and tag. -FieldMatcher CreateAttributionUidAndTagDimensions(const int atomId, - const std::vector& positions); - -// Create dimensions by attribution uid only. -FieldMatcher CreateAttributionUidDimensions(const int atomId, - const std::vector& positions); - -void writeAttribution(AStatsEvent* statsEvent, const vector& attributionUids, - const vector& attributionTags); - -void parseStatsEventToLogEvent(AStatsEvent* statsEvent, LogEvent* logEvent); - -// Create log event for screen state changed. -std::unique_ptr CreateScreenStateChangedEvent( - uint64_t timestampNs, const android::view::DisplayStateEnum state); - -// Create log event when scheduled job starts. -std::unique_ptr CreateStartScheduledJobEvent(uint64_t timestampNs, - const vector& attributionUids, - const vector& attributionTags, - const string& jobName); - -// Create log event when scheduled job finishes. -std::unique_ptr CreateFinishScheduledJobEvent(uint64_t timestampNs, - const vector& attributionUids, - const vector& attributionTags, - const string& jobName); - -// Create log event when the app sync starts. -std::unique_ptr CreateSyncStartEvent(uint64_t timestampNs, - const vector& attributionUids, - const vector& attributionTags, - const string& name); - -// Create log event when the app sync ends. -std::unique_ptr CreateSyncEndEvent(uint64_t timestampNs, - const vector& attributionUids, - const vector& attributionTags, - const string& name); - -// Create a statsd log event processor upon the start time in seconds, config and key. -sp CreateStatsLogProcessor(const long timeBaseSec, const StatsdConfig& config, - const ConfigKey& key); - -// Util function to sort the log events by timestamp. -void sortLogEventsByTimestamp(std::vector> *events); - -int64_t StringToId(const string& str); - -} // namespace statsd -} // namespace os -} // namespace android diff --git a/bin/benchmark/stats_write_benchmark.cpp b/bin/benchmark/stats_write_benchmark.cpp deleted file mode 100644 index f5a0cd5d..00000000 --- a/bin/benchmark/stats_write_benchmark.cpp +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright (C) 2018 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 "benchmark/benchmark.h" -#include - -namespace android { -namespace os { -namespace statsd { - -static void BM_StatsWrite(benchmark::State& state) { - const char* reason = "test"; - int64_t boot_end_time = 1234567; - int64_t total_duration = 100; - int64_t bootloader_duration = 10; - int64_t time_since_last_boot = 99999999; - while (state.KeepRunning()) { - android::util::stats_write( - android::util::BOOT_SEQUENCE_REPORTED, reason, reason, - boot_end_time, total_duration, bootloader_duration, time_since_last_boot); - total_duration++; - } -} -BENCHMARK(BM_StatsWrite); - -} // namespace statsd -} // namespace os -} // namespace android diff --git a/bin/src/FieldValue.cpp b/bin/src/FieldValue.cpp deleted file mode 100644 index c9ccfb93..00000000 --- a/bin/src/FieldValue.cpp +++ /dev/null @@ -1,474 +0,0 @@ -/* - * Copyright (C) 2018 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. - */ - -#define DEBUG false -#include "Log.h" -#include "FieldValue.h" -#include "HashableDimensionKey.h" -#include "math.h" - -namespace android { -namespace os { -namespace statsd { - -int32_t getEncodedField(int32_t pos[], int32_t depth, bool includeDepth) { - int32_t field = 0; - for (int32_t i = 0; i <= depth; i++) { - int32_t shiftBits = 8 * (kMaxLogDepth - i); - field |= (pos[i] << shiftBits); - } - - if (includeDepth) { - field |= (depth << 24); - } - return field; -} - -int32_t encodeMatcherMask(int32_t mask[], int32_t depth) { - return getEncodedField(mask, depth, false) | 0xff000000; -} - -bool Field::matches(const Matcher& matcher) const { - if (mTag != matcher.mMatcher.getTag()) { - return false; - } - if ((mField & matcher.mMask) == matcher.mMatcher.getField()) { - return true; - } - - if (matcher.hasAllPositionMatcher() && - (mField & (matcher.mMask & kClearAllPositionMatcherMask)) == matcher.mMatcher.getField()) { - return true; - } - - return false; -} - -void translateFieldMatcher(int tag, const FieldMatcher& matcher, int depth, int* pos, int* mask, - std::vector* output) { - if (depth > kMaxLogDepth) { - ALOGE("depth > 2"); - return; - } - - pos[depth] = matcher.field(); - mask[depth] = 0x7f; - - if (matcher.has_position()) { - depth++; - if (depth > 2) { - return; - } - switch (matcher.position()) { - case Position::ALL: - pos[depth] = 0x00; - mask[depth] = 0x7f; - break; - case Position::ANY: - pos[depth] = 0; - mask[depth] = 0; - break; - case Position::FIRST: - pos[depth] = 1; - mask[depth] = 0x7f; - break; - case Position::LAST: - pos[depth] = 0x80; - mask[depth] = 0x80; - break; - case Position::POSITION_UNKNOWN: - pos[depth] = 0; - mask[depth] = 0; - break; - } - } - - if (matcher.child_size() == 0) { - output->push_back(Matcher(Field(tag, pos, depth), encodeMatcherMask(mask, depth))); - } else { - for (const auto& child : matcher.child()) { - translateFieldMatcher(tag, child, depth + 1, pos, mask, output); - } - } -} - -void translateFieldMatcher(const FieldMatcher& matcher, std::vector* output) { - int pos[] = {1, 1, 1}; - int mask[] = {0x7f, 0x7f, 0x7f}; - int tag = matcher.field(); - for (const auto& child : matcher.child()) { - translateFieldMatcher(tag, child, 0, pos, mask, output); - } -} - -bool isAttributionUidField(const FieldValue& value) { - return isAttributionUidField(value.mField, value.mValue); -} - -int32_t getUidIfExists(const FieldValue& value) { - // the field is uid field if the field is the uid field in attribution node - // or annotated as such in the atom - bool isUid = isAttributionUidField(value) || isUidField(value); - return isUid ? value.mValue.int_value : -1; -} - -bool isAttributionUidField(const Field& field, const Value& value) { - int f = field.getField() & 0xff007f; - if (f == 0x10001 && value.getType() == INT) { - return true; - } - return false; -} - -bool isUidField(const FieldValue& fieldValue) { - return fieldValue.mAnnotations.isUidField(); -} - -Value::Value(const Value& from) { - type = from.getType(); - switch (type) { - case INT: - int_value = from.int_value; - break; - case LONG: - long_value = from.long_value; - break; - case FLOAT: - float_value = from.float_value; - break; - case DOUBLE: - double_value = from.double_value; - break; - case STRING: - str_value = from.str_value; - break; - case STORAGE: - storage_value = from.storage_value; - break; - default: - break; - } -} - -std::string Value::toString() const { - switch (type) { - case INT: - return std::to_string(int_value) + "[I]"; - case LONG: - return std::to_string(long_value) + "[L]"; - case FLOAT: - return std::to_string(float_value) + "[F]"; - case DOUBLE: - return std::to_string(double_value) + "[D]"; - case STRING: - return str_value + "[S]"; - case STORAGE: - return "bytes of size " + std::to_string(storage_value.size()) + "[ST]"; - default: - return "[UNKNOWN]"; - } -} - -bool Value::isZero() const { - switch (type) { - case INT: - return int_value == 0; - case LONG: - return long_value == 0; - case FLOAT: - return fabs(float_value) <= std::numeric_limits::epsilon(); - case DOUBLE: - return fabs(double_value) <= std::numeric_limits::epsilon(); - case STRING: - return str_value.size() == 0; - case STORAGE: - return storage_value.size() == 0; - default: - return false; - } -} - -bool Value::operator==(const Value& that) const { - if (type != that.getType()) return false; - - switch (type) { - case INT: - return int_value == that.int_value; - case LONG: - return long_value == that.long_value; - case FLOAT: - return float_value == that.float_value; - case DOUBLE: - return double_value == that.double_value; - case STRING: - return str_value == that.str_value; - case STORAGE: - return storage_value == that.storage_value; - default: - return false; - } -} - -bool Value::operator!=(const Value& that) const { - if (type != that.getType()) return true; - switch (type) { - case INT: - return int_value != that.int_value; - case LONG: - return long_value != that.long_value; - case FLOAT: - return float_value != that.float_value; - case DOUBLE: - return double_value != that.double_value; - case STRING: - return str_value != that.str_value; - case STORAGE: - return storage_value != that.storage_value; - default: - return false; - } -} - -bool Value::operator<(const Value& that) const { - if (type != that.getType()) return type < that.getType(); - - switch (type) { - case INT: - return int_value < that.int_value; - case LONG: - return long_value < that.long_value; - case FLOAT: - return float_value < that.float_value; - case DOUBLE: - return double_value < that.double_value; - case STRING: - return str_value < that.str_value; - case STORAGE: - return storage_value < that.storage_value; - default: - return false; - } -} - -bool Value::operator>(const Value& that) const { - if (type != that.getType()) return type > that.getType(); - - switch (type) { - case INT: - return int_value > that.int_value; - case LONG: - return long_value > that.long_value; - case FLOAT: - return float_value > that.float_value; - case DOUBLE: - return double_value > that.double_value; - case STRING: - return str_value > that.str_value; - case STORAGE: - return storage_value > that.storage_value; - default: - return false; - } -} - -bool Value::operator>=(const Value& that) const { - if (type != that.getType()) return type >= that.getType(); - - switch (type) { - case INT: - return int_value >= that.int_value; - case LONG: - return long_value >= that.long_value; - case FLOAT: - return float_value >= that.float_value; - case DOUBLE: - return double_value >= that.double_value; - case STRING: - return str_value >= that.str_value; - case STORAGE: - return storage_value >= that.storage_value; - default: - return false; - } -} - -Value Value::operator-(const Value& that) const { - Value v; - if (type != that.type) { - ALOGE("Can't operate on different value types, %d, %d", type, that.type); - return v; - } - if (type == STRING) { - ALOGE("Can't operate on string value type"); - return v; - } - - if (type == STORAGE) { - ALOGE("Can't operate on storage value type"); - return v; - } - - switch (type) { - case INT: - v.setInt(int_value - that.int_value); - break; - case LONG: - v.setLong(long_value - that.long_value); - break; - case FLOAT: - v.setFloat(float_value - that.float_value); - break; - case DOUBLE: - v.setDouble(double_value - that.double_value); - break; - default: - break; - } - return v; -} - -Value& Value::operator=(const Value& that) { - type = that.type; - switch (type) { - case INT: - int_value = that.int_value; - break; - case LONG: - long_value = that.long_value; - break; - case FLOAT: - float_value = that.float_value; - break; - case DOUBLE: - double_value = that.double_value; - break; - case STRING: - str_value = that.str_value; - break; - case STORAGE: - storage_value = that.storage_value; - break; - default: - break; - } - return *this; -} - -Value& Value::operator+=(const Value& that) { - if (type != that.type) { - ALOGE("Can't operate on different value types, %d, %d", type, that.type); - return *this; - } - if (type == STRING) { - ALOGE("Can't operate on string value type"); - return *this; - } - if (type == STORAGE) { - ALOGE("Can't operate on storage value type"); - return *this; - } - - switch (type) { - case INT: - int_value += that.int_value; - break; - case LONG: - long_value += that.long_value; - break; - case FLOAT: - float_value += that.float_value; - break; - case DOUBLE: - double_value += that.double_value; - break; - default: - break; - } - return *this; -} - -double Value::getDouble() const { - switch (type) { - case INT: - return int_value; - case LONG: - return long_value; - case FLOAT: - return float_value; - case DOUBLE: - return double_value; - default: - return 0; - } -} - -bool equalDimensions(const std::vector& dimension_a, - const std::vector& dimension_b) { - bool eq = dimension_a.size() == dimension_b.size(); - for (size_t i = 0; eq && i < dimension_a.size(); ++i) { - if (dimension_b[i] != dimension_a[i]) { - eq = false; - } - } - return eq; -} - -bool subsetDimensions(const std::vector& dimension_a, - const std::vector& dimension_b) { - if (dimension_a.size() > dimension_b.size()) { - return false; - } - for (size_t i = 0; i < dimension_a.size(); ++i) { - bool found = false; - for (size_t j = 0; j < dimension_b.size(); ++j) { - if (dimension_a[i] == dimension_b[j]) { - found = true; - } - } - if (!found) { - return false; - } - } - return true; -} - -bool HasPositionANY(const FieldMatcher& matcher) { - if (matcher.has_position() && matcher.position() == Position::ANY) { - return true; - } - for (const auto& child : matcher.child()) { - if (HasPositionANY(child)) { - return true; - } - } - return false; -} - -bool HasPositionALL(const FieldMatcher& matcher) { - if (matcher.has_position() && matcher.position() == Position::ALL) { - return true; - } - for (const auto& child : matcher.child()) { - if (HasPositionALL(child)) { - return true; - } - } - return false; -} - -} // namespace statsd -} // namespace os -} // namespace android diff --git a/bin/src/FieldValue.h b/bin/src/FieldValue.h deleted file mode 100644 index 2a293faf..00000000 --- a/bin/src/FieldValue.h +++ /dev/null @@ -1,462 +0,0 @@ -/* - * Copyright (C) 2018 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. - */ -#pragma once - -#include "packages/modules/StatsD/bin/src/statsd_config.pb.h" -#include "annotations.h" - -namespace android { -namespace os { -namespace statsd { - -class HashableDimensionKey; -struct Matcher; -struct Field; -struct FieldValue; - -const int32_t kMaxLogDepth = 2; -const int32_t kLastBitMask = 0x80; -const int32_t kClearLastBitDeco = 0x7f; -const int32_t kClearAllPositionMatcherMask = 0xffff00ff; - -enum Type { UNKNOWN, INT, LONG, FLOAT, DOUBLE, STRING, STORAGE }; - -int32_t getEncodedField(int32_t pos[], int32_t depth, bool includeDepth); - -int32_t encodeMatcherMask(int32_t mask[], int32_t depth); - -// Get the encoded field for a leaf with a [field] number at depth 0; -inline int32_t getSimpleField(size_t field) { - return ((int32_t)field << 8 * 2); -} - -/** - * Field is a wrapper class for 2 integers that represents the field of a log element in its Atom - * proto. - * [mTag]: the atom id. - * [mField]: encoded path from the root (atom) to leaf. - * - * For example: - * WakeLockStateChanged { - * repeated AttributionNode = 1; - * int state = 2; - * string tag = 3; - * } - * Read from logd, the items are structured as below: - * [[[1000, "tag"], [2000, "tag2"],], 2,"hello"] - * - * When we read through the list, we will encode each field in a 32bit integer. - * 8bit segments |--------|--------|--------|--------| - * Depth field0 [L]field1 [L]field1 - * - * The first 8 bits are the depth of the field. for example, the uid 1000 has depth 2. - * The following 3 8-bit are for the item's position at each level. - * The first bit of each 8bits field is reserved to mark if the item is the last item at that level - * this is to make matching easier later. - * - * The above wakelock event is translated into FieldValue pairs. - * 0x02010101->1000 - * 0x02010182->tag - * 0x02018201->2000 - * 0x02018282->tag2 - * 0x00020000->2 - * 0x00030000->"hello" - * - * This encoding is the building block for the later operations. - * Please see the definition for Matcher below to see how the matching is done. - */ -struct Field { -private: - int32_t mTag; - int32_t mField; - -public: - Field() {} - - Field(int32_t tag, int32_t pos[], int32_t depth) : mTag(tag) { - mField = getEncodedField(pos, depth, true); - } - - Field(const Field& from) : mTag(from.getTag()), mField(from.getField()) { - } - - Field(int32_t tag, int32_t field) : mTag(tag), mField(field){}; - - inline void setField(int32_t field) { - mField = field; - } - - inline void setTag(int32_t tag) { - mTag = tag; - } - - inline void decorateLastPos(int32_t depth) { - int32_t mask = kLastBitMask << 8 * (kMaxLogDepth - depth); - mField |= mask; - } - - inline int32_t getTag() const { - return mTag; - } - - inline int32_t getDepth() const { - return (mField >> 24); - } - - inline int32_t getPath(int32_t depth) const { - if (depth > 2 || depth < 0) return 0; - - int32_t field = (mField & 0x00ffffff); - int32_t mask = 0xffffffff; - return (field & (mask << 8 * (kMaxLogDepth - depth))); - } - - inline int32_t getPrefix(int32_t depth) const { - if (depth == 0) return 0; - return getPath(depth - 1); - } - - inline int32_t getField() const { - return mField; - } - - inline int32_t getRawPosAtDepth(int32_t depth) const { - int32_t field = (mField & 0x00ffffff); - int32_t shift = 8 * (kMaxLogDepth - depth); - int32_t mask = 0xff << shift; - - return (field & mask) >> shift; - } - - inline int32_t getPosAtDepth(int32_t depth) const { - return getRawPosAtDepth(depth) & kClearLastBitDeco; - } - - // Check if the first bit of the 8-bit segment for depth is 1 - inline bool isLastPos(int32_t depth) const { - int32_t field = (mField & 0x00ffffff); - int32_t mask = kLastBitMask << 8 * (kMaxLogDepth - depth); - return (field & mask) != 0; - } - - // if the 8-bit segment is all 0's - inline bool isAnyPosMatcher(int32_t depth) const { - return getDepth() >= depth && getRawPosAtDepth(depth) == 0; - } - // if the 8bit is 0x80 (1000 0000) - inline bool isLastPosMatcher(int32_t depth) const { - return getDepth() >= depth && getRawPosAtDepth(depth) == kLastBitMask; - } - - inline bool operator==(const Field& that) const { - return mTag == that.getTag() && mField == that.getField(); - }; - - inline bool operator!=(const Field& that) const { - return mTag != that.getTag() || mField != that.getField(); - }; - - bool operator<(const Field& that) const { - if (mTag != that.getTag()) { - return mTag < that.getTag(); - } - - if (mField != that.getField()) { - return mField < that.getField(); - } - - return false; - } - - bool matches(const Matcher& that) const; -}; - -/** - * Matcher represents a leaf matcher in the FieldMatcher in statsd_config. - * - * It contains all information needed to match one or more leaf node. - * All information is encoded in a Field(2 ints) and a bit mask(1 int). - * - * For example, to match the first/any/last uid field in attribution chain in Atom 10, - * we have the following FieldMatcher in statsd_config - * FieldMatcher { - * field:10 - * FieldMatcher { - * field:1 - * position: any/last/first - * FieldMatcher { - * field:1 - * } - * } - * } - * - * We translate the FieldMatcher into a Field, and mask - * First: [Matcher Field] 0x02010101 [Mask]0xff7f7f7f - * Last: [Matcher Field] 0x02018001 [Mask]0xff7f807f - * Any: [Matcher Field] 0x02010001 [Mask]0xff7f007f - * All: [Matcher Field] 0x02010001 [Mask]0xff7f7f7f - * - * [To match a log Field with a Matcher] we apply the bit mask to the log Field and check if - * the result is equal to the Matcher Field. That's a bit wise AND operation + check if 2 ints are - * equal. Nothing can beat the performance of this matching algorithm. - * - * TODO(b/110561213): ADD EXAMPLE HERE. - */ -struct Matcher { - Matcher(const Field& matcher, int32_t mask) : mMatcher(matcher), mMask(mask){}; - - const Field mMatcher; - const int32_t mMask; - - inline const Field& getMatcher() const { - return mMatcher; - } - - inline int32_t getMask() const { - return mMask; - } - - inline int32_t getRawMaskAtDepth(int32_t depth) const { - int32_t field = (mMask & 0x00ffffff); - int32_t shift = 8 * (kMaxLogDepth - depth); - int32_t mask = 0xff << shift; - - return (field & mask) >> shift; - } - - bool hasAllPositionMatcher() const { - return mMatcher.getDepth() == 2 && getRawMaskAtDepth(1) == 0x7f; - } - - bool hasAnyPositionMatcher(int* prefix) const { - if (mMatcher.getDepth() == 2 && mMatcher.getRawPosAtDepth(1) == 0) { - (*prefix) = mMatcher.getPrefix(1); - return true; - } - return false; - } - - inline bool operator!=(const Matcher& that) const { - return mMatcher != that.getMatcher() || mMask != that.getMask(); - } - - inline bool operator==(const Matcher& that) const { - return mMatcher == that.mMatcher && mMask == that.mMask; - } -}; - -inline Matcher getSimpleMatcher(int32_t tag, size_t field) { - return Matcher(Field(tag, getSimpleField(field)), 0xff7f0000); -} - -inline Matcher getFirstUidMatcher(int32_t atomId) { - int32_t pos[] = {1, 1, 1}; - return Matcher(Field(atomId, pos, 2), 0xff7f7f7f); -} - -/** - * A wrapper for a union type to contain multiple types of values. - * - */ -struct Value { - Value() : type(UNKNOWN) {} - - Value(int32_t v) { - int_value = v; - type = INT; - } - - Value(int64_t v) { - long_value = v; - type = LONG; - } - - Value(float v) { - float_value = v; - type = FLOAT; - } - - Value(double v) { - double_value = v; - type = DOUBLE; - } - - Value(const std::string& v) { - str_value = v; - type = STRING; - } - - Value(const std::vector& v) { - storage_value = v; - type = STORAGE; - } - - void setInt(int32_t v) { - int_value = v; - type = INT; - } - - void setLong(int64_t v) { - long_value = v; - type = LONG; - } - - void setFloat(float v) { - float_value = v; - type = FLOAT; - } - - void setDouble(double v) { - double_value = v; - type = DOUBLE; - } - - union { - int32_t int_value; - int64_t long_value; - float float_value; - double double_value; - }; - std::string str_value; - std::vector storage_value; - - Type type; - - std::string toString() const; - - bool isZero() const; - - Type getType() const { - return type; - } - - double getDouble() const; - - Value(const Value& from); - - bool operator==(const Value& that) const; - bool operator!=(const Value& that) const; - - bool operator<(const Value& that) const; - bool operator>(const Value& that) const; - bool operator>=(const Value& that) const; - Value operator-(const Value& that) const; - Value& operator+=(const Value& that); - Value& operator=(const Value& that); -}; - -class Annotations { -public: - Annotations() { - setNested(true); // Nested = true by default - } - - // This enum stores where particular annotations can be found in the - // bitmask. Note that these pos do not correspond to annotation ids. - enum { - NESTED_POS = 0x0, - PRIMARY_POS = 0x1, - EXCLUSIVE_POS = 0x2, - UID_POS = 0x3 - }; - - inline void setNested(bool nested) { setBitmaskAtPos(NESTED_POS, nested); } - - inline void setPrimaryField(bool primary) { setBitmaskAtPos(PRIMARY_POS, primary); } - - inline void setExclusiveState(bool exclusive) { setBitmaskAtPos(EXCLUSIVE_POS, exclusive); } - - inline void setUidField(bool isUid) { setBitmaskAtPos(UID_POS, isUid); } - - // Default value = false - inline bool isNested() const { return getValueFromBitmask(NESTED_POS); } - - // Default value = false - inline bool isPrimaryField() const { return getValueFromBitmask(PRIMARY_POS); } - - // Default value = false - inline bool isExclusiveState() const { return getValueFromBitmask(EXCLUSIVE_POS); } - - // Default value = false - inline bool isUidField() const { return getValueFromBitmask(UID_POS); } - -private: - inline void setBitmaskAtPos(int pos, bool value) { - mBooleanBitmask &= ~(1 << pos); // clear - mBooleanBitmask |= (value << pos); // set - } - - inline bool getValueFromBitmask(int pos) const { - return (mBooleanBitmask >> pos) & 0x1; - } - - // This is a bitmask over all annotations stored in boolean form. Because - // there are only 4 booleans, just one byte is required. - uint8_t mBooleanBitmask = 0; -}; - -/** - * Represents a log item, or a dimension item (They are essentially the same). - */ -struct FieldValue { - FieldValue() {} - FieldValue(const Field& field, const Value& value) : mField(field), mValue(value) { - } - bool operator==(const FieldValue& that) const { - return mField == that.mField && mValue == that.mValue; - } - bool operator!=(const FieldValue& that) const { - return mField != that.mField || mValue != that.mValue; - } - bool operator<(const FieldValue& that) const { - if (mField != that.mField) { - return mField < that.mField; - } - - if (mValue != that.mValue) { - return mValue < that.mValue; - } - - return false; - } - - Field mField; - Value mValue; - Annotations mAnnotations; -}; - -bool HasPositionANY(const FieldMatcher& matcher); -bool HasPositionALL(const FieldMatcher& matcher); - -bool isAttributionUidField(const FieldValue& value); - -/* returns uid if the field is uid field, or -1 if the field is not a uid field */ -int getUidIfExists(const FieldValue& value); - -void translateFieldMatcher(const FieldMatcher& matcher, std::vector* output); - -bool isAttributionUidField(const Field& field, const Value& value); -bool isUidField(const FieldValue& fieldValue); - -bool equalDimensions(const std::vector& dimension_a, - const std::vector& dimension_b); - -// Returns true if dimension_a is a subset of dimension_b. -bool subsetDimensions(const std::vector& dimension_a, - const std::vector& dimension_b); -} // namespace statsd -} // namespace os -} // namespace android diff --git a/bin/src/HashableDimensionKey.cpp b/bin/src/HashableDimensionKey.cpp deleted file mode 100644 index 037856aa..00000000 --- a/bin/src/HashableDimensionKey.cpp +++ /dev/null @@ -1,372 +0,0 @@ -/* - * 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. - */ -#define DEBUG false // STOPSHIP if true -#include "Log.h" - -#include "HashableDimensionKey.h" -#include "FieldValue.h" - -namespace android { -namespace os { -namespace statsd { - -using std::string; -using std::vector; -using android::base::StringPrintf; - -/** - * Recursive helper function that populates a parent StatsDimensionsValueParcel - * with children StatsDimensionsValueParcels. - * - * \param parent parcel that will be populated with children - * \param childDepth depth of children FieldValues - * \param childPrefix expected FieldValue prefix of children - * \param dims vector of FieldValues stored by HashableDimensionKey - * \param index position in dims to start reading children from - */ -static void populateStatsDimensionsValueParcelChildren(StatsDimensionsValueParcel& parent, - int childDepth, int childPrefix, - const vector& dims, - size_t& index) { - if (childDepth > 2) { - ALOGE("Depth > 2 not supported by StatsDimensionsValueParcel."); - return; - } - - while (index < dims.size()) { - const FieldValue& dim = dims[index]; - int fieldDepth = dim.mField.getDepth(); - int fieldPrefix = dim.mField.getPrefix(childDepth); - - StatsDimensionsValueParcel child; - child.field = dim.mField.getPosAtDepth(childDepth); - - if (fieldDepth == childDepth && fieldPrefix == childPrefix) { - switch (dim.mValue.getType()) { - case INT: - child.valueType = STATS_DIMENSIONS_VALUE_INT_TYPE; - child.intValue = dim.mValue.int_value; - break; - case LONG: - child.valueType = STATS_DIMENSIONS_VALUE_LONG_TYPE; - child.longValue = dim.mValue.long_value; - break; - case FLOAT: - child.valueType = STATS_DIMENSIONS_VALUE_FLOAT_TYPE; - child.floatValue = dim.mValue.float_value; - break; - case STRING: - child.valueType = STATS_DIMENSIONS_VALUE_STRING_TYPE; - child.stringValue = dim.mValue.str_value; - break; - default: - ALOGE("Encountered FieldValue with unsupported value type."); - break; - } - index++; - parent.tupleValue.push_back(child); - } else if (fieldDepth > childDepth && fieldPrefix == childPrefix) { - // This FieldValue is not a child of the current parent, but it is - // an indirect descendant. Thus, create a direct child of TUPLE_TYPE - // and recurse to parcel the indirect descendants. - child.valueType = STATS_DIMENSIONS_VALUE_TUPLE_TYPE; - populateStatsDimensionsValueParcelChildren(child, childDepth + 1, - dim.mField.getPrefix(childDepth + 1), dims, - index); - parent.tupleValue.push_back(child); - } else { - return; - } - } -} - -StatsDimensionsValueParcel HashableDimensionKey::toStatsDimensionsValueParcel() const { - StatsDimensionsValueParcel root; - if (mValues.size() == 0) { - return root; - } - - root.field = mValues[0].mField.getTag(); - root.valueType = STATS_DIMENSIONS_VALUE_TUPLE_TYPE; - - // Children of the root correspond to top-level (depth = 0) FieldValues. - int childDepth = 0; - int childPrefix = 0; - size_t index = 0; - populateStatsDimensionsValueParcelChildren(root, childDepth, childPrefix, mValues, index); - - return root; -} - -android::hash_t hashDimension(const HashableDimensionKey& value) { - android::hash_t hash = 0; - for (const auto& fieldValue : value.getValues()) { - hash = android::JenkinsHashMix(hash, android::hash_type((int)fieldValue.mField.getField())); - hash = android::JenkinsHashMix(hash, android::hash_type((int)fieldValue.mField.getTag())); - hash = android::JenkinsHashMix(hash, android::hash_type((int)fieldValue.mValue.getType())); - switch (fieldValue.mValue.getType()) { - case INT: - hash = android::JenkinsHashMix(hash, - android::hash_type(fieldValue.mValue.int_value)); - break; - case LONG: - hash = android::JenkinsHashMix(hash, - android::hash_type(fieldValue.mValue.long_value)); - break; - case STRING: - hash = android::JenkinsHashMix(hash, static_cast(std::hash()( - fieldValue.mValue.str_value))); - break; - case FLOAT: { - hash = android::JenkinsHashMix(hash, - android::hash_type(fieldValue.mValue.float_value)); - break; - } - default: - break; - } - } - return JenkinsHashWhiten(hash); -} - -bool filterValues(const Matcher& matcherField, const vector& values, - FieldValue* output) { - for (const auto& value : values) { - if (value.mField.matches(matcherField)) { - (*output) = value; - return true; - } - } - return false; -} - -bool filterValues(const vector& matcherFields, const vector& values, - HashableDimensionKey* output) { - size_t num_matches = 0; - for (const auto& value : values) { - for (size_t i = 0; i < matcherFields.size(); ++i) { - const auto& matcher = matcherFields[i]; - if (value.mField.matches(matcher)) { - output->addValue(value); - output->mutableValue(num_matches)->mField.setTag(value.mField.getTag()); - output->mutableValue(num_matches)->mField.setField( - value.mField.getField() & matcher.mMask); - num_matches++; - } - } - } - return num_matches > 0; -} - -bool filterPrimaryKey(const std::vector& values, HashableDimensionKey* output) { - size_t num_matches = 0; - const int32_t simpleFieldMask = 0xff7f0000; - const int32_t attributionUidFieldMask = 0xff7f7f7f; - for (const auto& value : values) { - if (value.mAnnotations.isPrimaryField()) { - output->addValue(value); - output->mutableValue(num_matches)->mField.setTag(value.mField.getTag()); - const int32_t mask = - isAttributionUidField(value) ? attributionUidFieldMask : simpleFieldMask; - output->mutableValue(num_matches)->mField.setField(value.mField.getField() & mask); - num_matches++; - } - } - return num_matches > 0; -} - -void filterGaugeValues(const std::vector& matcherFields, - const std::vector& values, std::vector* output) { - for (const auto& field : matcherFields) { - for (const auto& value : values) { - if (value.mField.matches(field)) { - output->push_back(value); - } - } - } -} - -void getDimensionForCondition(const std::vector& eventValues, - const Metric2Condition& links, - HashableDimensionKey* conditionDimension) { - // Get the dimension first by using dimension from what. - filterValues(links.metricFields, eventValues, conditionDimension); - - size_t count = conditionDimension->getValues().size(); - if (count != links.conditionFields.size()) { - return; - } - - for (size_t i = 0; i < count; i++) { - conditionDimension->mutableValue(i)->mField.setField( - links.conditionFields[i].mMatcher.getField()); - conditionDimension->mutableValue(i)->mField.setTag( - links.conditionFields[i].mMatcher.getTag()); - } -} - -void getDimensionForState(const std::vector& eventValues, const Metric2State& link, - HashableDimensionKey* statePrimaryKey) { - // First, get the dimension from the event using the "what" fields from the - // MetricStateLinks. - filterValues(link.metricFields, eventValues, statePrimaryKey); - - // Then check that the statePrimaryKey size equals the number of state fields - size_t count = statePrimaryKey->getValues().size(); - if (count != link.stateFields.size()) { - return; - } - - // For each dimension Value in the statePrimaryKey, set the field and tag - // using the state atom fields from MetricStateLinks. - for (size_t i = 0; i < count; i++) { - statePrimaryKey->mutableValue(i)->mField.setField(link.stateFields[i].mMatcher.getField()); - statePrimaryKey->mutableValue(i)->mField.setTag(link.stateFields[i].mMatcher.getTag()); - } -} - -bool containsLinkedStateValues(const HashableDimensionKey& whatKey, - const HashableDimensionKey& primaryKey, - const vector& stateLinks, const int32_t stateAtomId) { - if (whatKey.getValues().size() < primaryKey.getValues().size()) { - ALOGE("Contains linked values false: whatKey is too small"); - return false; - } - - for (const auto& primaryValue : primaryKey.getValues()) { - bool found = false; - for (const auto& whatValue : whatKey.getValues()) { - if (linked(stateLinks, stateAtomId, primaryValue.mField, whatValue.mField) && - primaryValue.mValue == whatValue.mValue) { - found = true; - break; - } - } - if (!found) { - return false; - } - } - return true; -} - -bool linked(const vector& stateLinks, const int32_t stateAtomId, - const Field& stateField, const Field& metricField) { - for (auto stateLink : stateLinks) { - if (stateLink.stateAtomId != stateAtomId) { - continue; - } - - for (size_t i = 0; i < stateLink.stateFields.size(); i++) { - if (stateLink.stateFields[i].mMatcher == stateField && - stateLink.metricFields[i].mMatcher == metricField) { - return true; - } - } - } - return false; -} - -bool LessThan(const vector& s1, const vector& s2) { - if (s1.size() != s2.size()) { - return s1.size() < s2.size(); - } - - size_t count = s1.size(); - for (size_t i = 0; i < count; i++) { - if (s1[i] != s2[i]) { - return s1[i] < s2[i]; - } - } - return false; -} - -bool HashableDimensionKey::operator!=(const HashableDimensionKey& that) const { - return !((*this) == that); -} - -bool HashableDimensionKey::operator==(const HashableDimensionKey& that) const { - if (mValues.size() != that.getValues().size()) { - return false; - } - size_t count = mValues.size(); - for (size_t i = 0; i < count; i++) { - if (mValues[i] != (that.getValues())[i]) { - return false; - } - } - return true; -}; - -bool HashableDimensionKey::operator<(const HashableDimensionKey& that) const { - return LessThan(getValues(), that.getValues()); -}; - -bool HashableDimensionKey::contains(const HashableDimensionKey& that) const { - if (mValues.size() < that.getValues().size()) { - return false; - } - - if (mValues.size() == that.getValues().size()) { - return (*this) == that; - } - - for (const auto& value : that.getValues()) { - bool found = false; - for (const auto& myValue : mValues) { - if (value.mField == myValue.mField && value.mValue == myValue.mValue) { - found = true; - break; - } - } - if (!found) { - return false; - } - } - - return true; -} - -string HashableDimensionKey::toString() const { - std::string output; - for (const auto& value : mValues) { - output += StringPrintf("(%d)%#x->%s ", value.mField.getTag(), value.mField.getField(), - value.mValue.toString().c_str()); - } - return output; -} - -bool MetricDimensionKey::operator==(const MetricDimensionKey& that) const { - return mDimensionKeyInWhat == that.getDimensionKeyInWhat() && - mStateValuesKey == that.getStateValuesKey(); -}; - -string MetricDimensionKey::toString() const { - return mDimensionKeyInWhat.toString() + mStateValuesKey.toString(); -} - -bool MetricDimensionKey::operator<(const MetricDimensionKey& that) const { - if (mDimensionKeyInWhat < that.getDimensionKeyInWhat()) { - return true; - } else if (that.getDimensionKeyInWhat() < mDimensionKeyInWhat) { - return false; - } - - return mStateValuesKey < that.getStateValuesKey(); -} - -} // namespace statsd -} // namespace os -} // namespace android diff --git a/bin/src/HashableDimensionKey.h b/bin/src/HashableDimensionKey.h deleted file mode 100644 index 096de5d2..00000000 --- a/bin/src/HashableDimensionKey.h +++ /dev/null @@ -1,247 +0,0 @@ -/* - * 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. - */ - -#pragma once - -#include -#include -#include -#include "android-base/stringprintf.h" -#include "FieldValue.h" -#include "logd/LogEvent.h" - -namespace android { -namespace os { -namespace statsd { - -using ::aidl::android::os::StatsDimensionsValueParcel; - -// These constants must be kept in sync with those in StatsDimensionsValue.java -inline constexpr int STATS_DIMENSIONS_VALUE_STRING_TYPE = 2; -inline constexpr int STATS_DIMENSIONS_VALUE_INT_TYPE = 3; -inline constexpr int STATS_DIMENSIONS_VALUE_LONG_TYPE = 4; -// inline constexpr int STATS_DIMENSIONS_VALUE_BOOL_TYPE = 5; (commented out because -// unused -- statsd does not correctly support bool types) -inline constexpr int STATS_DIMENSIONS_VALUE_FLOAT_TYPE = 6; -inline constexpr int STATS_DIMENSIONS_VALUE_TUPLE_TYPE = 7; - -struct Metric2Condition { - int64_t conditionId; - std::vector metricFields; - std::vector conditionFields; -}; - -struct Metric2State { - int32_t stateAtomId; - std::vector metricFields; - std::vector stateFields; -}; - -class HashableDimensionKey { -public: - explicit HashableDimensionKey(const std::vector& values) { - mValues = values; - } - - HashableDimensionKey() {}; - - HashableDimensionKey(const HashableDimensionKey& that) : mValues(that.getValues()){}; - - inline void addValue(const FieldValue& value) { - mValues.push_back(value); - } - - inline const std::vector& getValues() const { - return mValues; - } - - inline std::vector* mutableValues() { - return &mValues; - } - - inline FieldValue* mutableValue(size_t i) { - if (i >= 0 && i < mValues.size()) { - return &(mValues[i]); - } - return nullptr; - } - - StatsDimensionsValueParcel toStatsDimensionsValueParcel() const; - - std::string toString() const; - - bool operator!=(const HashableDimensionKey& that) const; - - bool operator==(const HashableDimensionKey& that) const; - - bool operator<(const HashableDimensionKey& that) const; - - bool contains(const HashableDimensionKey& that) const; - -private: - std::vector mValues; -}; - -class MetricDimensionKey { -public: - explicit MetricDimensionKey(const HashableDimensionKey& dimensionKeyInWhat, - const HashableDimensionKey& stateValuesKey) - : mDimensionKeyInWhat(dimensionKeyInWhat), mStateValuesKey(stateValuesKey){}; - - MetricDimensionKey(){}; - - MetricDimensionKey(const MetricDimensionKey& that) - : mDimensionKeyInWhat(that.getDimensionKeyInWhat()), - mStateValuesKey(that.getStateValuesKey()){}; - - MetricDimensionKey& operator=(const MetricDimensionKey& from) = default; - - std::string toString() const; - - inline const HashableDimensionKey& getDimensionKeyInWhat() const { - return mDimensionKeyInWhat; - } - - inline const HashableDimensionKey& getStateValuesKey() const { - return mStateValuesKey; - } - - inline HashableDimensionKey* getMutableStateValuesKey() { - return &mStateValuesKey; - } - - inline void setStateValuesKey(const HashableDimensionKey& key) { - mStateValuesKey = key; - } - - bool hasStateValuesKey() const { - return mStateValuesKey.getValues().size() > 0; - } - - bool operator==(const MetricDimensionKey& that) const; - - bool operator<(const MetricDimensionKey& that) const; - -private: - HashableDimensionKey mDimensionKeyInWhat; - HashableDimensionKey mStateValuesKey; -}; - -android::hash_t hashDimension(const HashableDimensionKey& key); - -/** - * Returns true if a FieldValue field matches the matcher field. - * The value of the FieldValue is output. - */ -bool filterValues(const Matcher& matcherField, const std::vector& values, - FieldValue* output); - -/** - * Creating HashableDimensionKeys from FieldValues using matcher. - * - * This function may make modifications to the Field if the matcher has Position=FIRST,LAST or ALL - * in it. This is because: for example, when we create dimension from last uid in attribution chain, - * In one event, uid 1000 is at position 5 and it's the last - * In another event, uid 1000 is at position 6, and it's the last - * these 2 events should be mapped to the same dimension. So we will remove the original position - * from the dimension key for the uid field (by applying 0x80 bit mask). - */ -bool filterValues(const std::vector& matcherFields, const std::vector& values, - HashableDimensionKey* output); - -/** - * Creating HashableDimensionKeys from State Primary Keys in FieldValues. - * - * This function may make modifications to the Field if the matcher has Position=FIRST,LAST or ALL - * in it. This is because: for example, when we create dimension from last uid in attribution chain, - * In one event, uid 1000 is at position 5 and it's the last - * In another event, uid 1000 is at position 6, and it's the last - * these 2 events should be mapped to the same dimension. So we will remove the original position - * from the dimension key for the uid field (by applying 0x80 bit mask). - */ -bool filterPrimaryKey(const std::vector& values, HashableDimensionKey* output); - -/** - * Filter the values from FieldValues using the matchers. - * - * In contrast to the above function, this function will not do any modification to the original - * data. Considering it as taking a snapshot on the atom event. - */ -void filterGaugeValues(const std::vector& matchers, const std::vector& values, - std::vector* output); - -void getDimensionForCondition(const std::vector& eventValues, - const Metric2Condition& links, - HashableDimensionKey* conditionDimension); - -/** - * Get dimension values using metric's "what" fields and fill statePrimaryKey's - * mField information using "state" fields. - */ -void getDimensionForState(const std::vector& eventValues, const Metric2State& link, - HashableDimensionKey* statePrimaryKey); - -/** - * Returns true if the primaryKey values are a subset of the whatKey values. - * The values from the primaryKey come from the state atom, so we need to - * check that a link exists between the state atom field and what atom field. - * - * Example: - * whatKey = [Atom: 10, {uid: 1005, wakelock_name: "compose"}] - * statePrimaryKey = [Atom: 27, {uid: 1005}] - * Returns true IF one of the Metric2State links Atom 10's uid to Atom 27's uid - * - * Example: - * whatKey = [Atom: 10, {uid: 1005, wakelock_name: "compose"}] - * statePrimaryKey = [Atom: 59, {uid: 1005, package_name: "system"}] - * Returns false - */ -bool containsLinkedStateValues(const HashableDimensionKey& whatKey, - const HashableDimensionKey& primaryKey, - const std::vector& stateLinks, - const int32_t stateAtomId); - -/** - * Returns true if there is a Metric2State link that links the stateField and - * the metricField (they are equal fields from different atoms). - */ -bool linked(const std::vector& stateLinks, const int32_t stateAtomId, - const Field& stateField, const Field& metricField); -} // namespace statsd -} // namespace os -} // namespace android - -namespace std { - -using android::os::statsd::HashableDimensionKey; -using android::os::statsd::MetricDimensionKey; - -template <> -struct hash { - std::size_t operator()(const HashableDimensionKey& key) const { - return hashDimension(key); - } -}; - -template <> -struct hash { - std::size_t operator()(const MetricDimensionKey& key) const { - android::hash_t hash = hashDimension(key.getDimensionKeyInWhat()); - hash = android::JenkinsHashMix(hash, hashDimension(key.getStateValuesKey())); - return android::JenkinsHashWhiten(hash); - } -}; -} // namespace std diff --git a/bin/src/Log.h b/bin/src/Log.h deleted file mode 100644 index 87f4cbaf..00000000 --- a/bin/src/Log.h +++ /dev/null @@ -1,33 +0,0 @@ -/* - * 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. - */ - -/* - * This file must be included at the top of the file. Other header files - * occasionally include log.h, and if LOG_TAG isn't set when that happens - * we'll get a preprocesser error when we try to define it here. - */ - -#pragma once - -#define LOG_TAG "statsd" - -#include - -// Use the local value to turn on/off debug logs instead of using log.tag. properties. -// The advantage is that in production compiler can remove the logging code if the local -// DEBUG/VERBOSE is false. -#define VLOG(...) \ - if (DEBUG) ALOGD(__VA_ARGS__); diff --git a/bin/src/StatsLogProcessor.cpp b/bin/src/StatsLogProcessor.cpp deleted file mode 100644 index f4ed1b8d..00000000 --- a/bin/src/StatsLogProcessor.cpp +++ /dev/null @@ -1,1147 +0,0 @@ -/* - * 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. - */ - -#define DEBUG false // STOPSHIP if true -#include "Log.h" - -#include "StatsLogProcessor.h" - -#include -#include -#include -#include - -#include "StatsService.h" -#include "android-base/stringprintf.h" -#include "external/StatsPullerManager.h" -#include "flags/flags.h" -#include "guardrail/StatsdStats.h" -#include "logd/LogEvent.h" -#include "metrics/CountMetricProducer.h" -#include "state/StateManager.h" -#include "stats_log_util.h" -#include "stats_util.h" -#include "statslog_statsd.h" -#include "storage/StorageManager.h" - -using namespace android; -using android::base::StringPrintf; -using android::util::FIELD_COUNT_REPEATED; -using android::util::FIELD_TYPE_BOOL; -using android::util::FIELD_TYPE_FLOAT; -using android::util::FIELD_TYPE_INT32; -using android::util::FIELD_TYPE_INT64; -using android::util::FIELD_TYPE_MESSAGE; -using android::util::FIELD_TYPE_STRING; -using android::util::ProtoOutputStream; -using std::vector; - -namespace android { -namespace os { -namespace statsd { - -// for ConfigMetricsReportList -const int FIELD_ID_CONFIG_KEY = 1; -const int FIELD_ID_REPORTS = 2; -// for ConfigKey -const int FIELD_ID_UID = 1; -const int FIELD_ID_ID = 2; -// for ConfigMetricsReport -// const int FIELD_ID_METRICS = 1; // written in MetricsManager.cpp -const int FIELD_ID_UID_MAP = 2; -const int FIELD_ID_LAST_REPORT_ELAPSED_NANOS = 3; -const int FIELD_ID_CURRENT_REPORT_ELAPSED_NANOS = 4; -const int FIELD_ID_LAST_REPORT_WALL_CLOCK_NANOS = 5; -const int FIELD_ID_CURRENT_REPORT_WALL_CLOCK_NANOS = 6; -const int FIELD_ID_DUMP_REPORT_REASON = 8; -const int FIELD_ID_STRINGS = 9; - -// for ActiveConfigList -const int FIELD_ID_ACTIVE_CONFIG_LIST_CONFIG = 1; - -// for permissions checks -constexpr const char* kPermissionDump = "android.permission.DUMP"; -constexpr const char* kPermissionUsage = "android.permission.PACKAGE_USAGE_STATS"; - -#define NS_PER_HOUR 3600 * NS_PER_SEC - -#define STATS_ACTIVE_METRIC_DIR "/data/misc/stats-active-metric" -#define STATS_METADATA_DIR "/data/misc/stats-metadata" - -// Cool down period for writing data to disk to avoid overwriting files. -#define WRITE_DATA_COOL_DOWN_SEC 5 - -StatsLogProcessor::StatsLogProcessor(const sp& uidMap, - const sp& pullerManager, - const sp& anomalyAlarmMonitor, - const sp& periodicAlarmMonitor, - const int64_t timeBaseNs, - const std::function& sendBroadcast, - const std::function&)>& activateBroadcast) - : mUidMap(uidMap), - mPullerManager(pullerManager), - mAnomalyAlarmMonitor(anomalyAlarmMonitor), - mPeriodicAlarmMonitor(periodicAlarmMonitor), - mSendBroadcast(sendBroadcast), - mSendActivationBroadcast(activateBroadcast), - mTimeBaseNs(timeBaseNs), - mLargestTimestampSeen(0), - mLastTimestampSeen(0) { - mPullerManager->ForceClearPullerCache(); - StateManager::getInstance().updateLogSources(uidMap); -} - -StatsLogProcessor::~StatsLogProcessor() { -} - -static void flushProtoToBuffer(ProtoOutputStream& proto, vector* outData) { - outData->clear(); - outData->resize(proto.size()); - size_t pos = 0; - sp reader = proto.data(); - while (reader->readBuffer() != NULL) { - size_t toRead = reader->currentToRead(); - std::memcpy(&((*outData)[pos]), reader->readBuffer(), toRead); - pos += toRead; - reader->move(toRead); - } -} - -void StatsLogProcessor::processFiredAnomalyAlarmsLocked( - const int64_t& timestampNs, - unordered_set, SpHash> alarmSet) { - for (const auto& itr : mMetricsManagers) { - itr.second->onAnomalyAlarmFired(timestampNs, alarmSet); - } -} -void StatsLogProcessor::onPeriodicAlarmFired( - const int64_t& timestampNs, - unordered_set, SpHash> alarmSet) { - - std::lock_guard lock(mMetricsMutex); - for (const auto& itr : mMetricsManagers) { - itr.second->onPeriodicAlarmFired(timestampNs, alarmSet); - } -} - -void StatsLogProcessor::mapIsolatedUidToHostUidIfNecessaryLocked(LogEvent* event) const { - if (std::pair indexRange; event->hasAttributionChain(&indexRange)) { - vector* const fieldValues = event->getMutableValues(); - for (int i = indexRange.first; i <= indexRange.second; i++) { - FieldValue& fieldValue = fieldValues->at(i); - if (isAttributionUidField(fieldValue)) { - const int hostUid = mUidMap->getHostUidOrSelf(fieldValue.mValue.int_value); - fieldValue.mValue.setInt(hostUid); - } - } - } else { - int uidFieldIndex = event->getUidFieldIndex(); - if (uidFieldIndex != -1) { - Value& value = (*event->getMutableValues())[uidFieldIndex].mValue; - const int hostUid = mUidMap->getHostUidOrSelf(value.int_value); - value.setInt(hostUid); - } - } -} - -void StatsLogProcessor::onIsolatedUidChangedEventLocked(const LogEvent& event) { - status_t err = NO_ERROR, err2 = NO_ERROR, err3 = NO_ERROR; - bool is_create = event.GetBool(3, &err); - auto parent_uid = int(event.GetLong(1, &err2)); - auto isolated_uid = int(event.GetLong(2, &err3)); - if (err == NO_ERROR && err2 == NO_ERROR && err3 == NO_ERROR) { - if (is_create) { - mUidMap->assignIsolatedUid(isolated_uid, parent_uid); - } else { - mUidMap->removeIsolatedUid(isolated_uid); - } - } else { - ALOGE("Failed to parse uid in the isolated uid change event."); - } -} - -void StatsLogProcessor::onBinaryPushStateChangedEventLocked(LogEvent* event) { - pid_t pid = event->GetPid(); - uid_t uid = event->GetUid(); - if (!checkPermissionForIds(kPermissionDump, pid, uid) || - !checkPermissionForIds(kPermissionUsage, pid, uid)) { - return; - } - // The Get* functions don't modify the status on success, they only write in - // failure statuses, so we can use one status variable for all calls then - // check if it is no longer NO_ERROR. - status_t err = NO_ERROR; - InstallTrainInfo trainInfo; - trainInfo.trainName = string(event->GetString(1 /*train name field id*/, &err)); - trainInfo.trainVersionCode = event->GetLong(2 /*train version field id*/, &err); - trainInfo.requiresStaging = event->GetBool(3 /*requires staging field id*/, &err); - trainInfo.rollbackEnabled = event->GetBool(4 /*rollback enabled field id*/, &err); - trainInfo.requiresLowLatencyMonitor = - event->GetBool(5 /*requires low latency monitor field id*/, &err); - trainInfo.status = int32_t(event->GetLong(6 /*state field id*/, &err)); - std::vector trainExperimentIdBytes = - event->GetStorage(7 /*experiment ids field id*/, &err); - bool is_rollback = event->GetBool(10 /*is rollback field id*/, &err); - - if (err != NO_ERROR) { - ALOGE("Failed to parse fields in binary push state changed log event"); - return; - } - ExperimentIds trainExperimentIds; - if (!trainExperimentIds.ParseFromArray(trainExperimentIdBytes.data(), - trainExperimentIdBytes.size())) { - ALOGE("Failed to parse experimentids in binary push state changed."); - return; - } - trainInfo.experimentIds = {trainExperimentIds.experiment_id().begin(), - trainExperimentIds.experiment_id().end()}; - - // Update the train info on disk and get any data the logevent is missing. - getAndUpdateTrainInfoOnDisk(is_rollback, &trainInfo); - - std::vector trainExperimentIdProto; - writeExperimentIdsToProto(trainInfo.experimentIds, &trainExperimentIdProto); - int32_t userId = multiuser_get_user_id(uid); - - event->updateValue(2 /*train version field id*/, trainInfo.trainVersionCode, LONG); - event->updateValue(7 /*experiment ids field id*/, trainExperimentIdProto, STORAGE); - event->updateValue(8 /*user id field id*/, userId, INT); - - // If this event is a rollback event, then the following bits in the event - // are invalid and we will need to update them with the values we pulled - // from disk. - if (is_rollback) { - int bit = trainInfo.requiresStaging ? 1 : 0; - event->updateValue(3 /*requires staging field id*/, bit, INT); - bit = trainInfo.rollbackEnabled ? 1 : 0; - event->updateValue(4 /*rollback enabled field id*/, bit, INT); - bit = trainInfo.requiresLowLatencyMonitor ? 1 : 0; - event->updateValue(5 /*requires low latency monitor field id*/, bit, INT); - } -} - -void StatsLogProcessor::getAndUpdateTrainInfoOnDisk(bool is_rollback, - InstallTrainInfo* trainInfo) { - // If the train name is empty, we don't know which train to attribute the - // event to, so return early. - if (trainInfo->trainName.empty()) { - return; - } - bool readTrainInfoSuccess = false; - InstallTrainInfo trainInfoOnDisk; - readTrainInfoSuccess = StorageManager::readTrainInfo(trainInfo->trainName, trainInfoOnDisk); - - bool resetExperimentIds = false; - if (readTrainInfoSuccess) { - // Keep the old train version if we received an empty version. - if (trainInfo->trainVersionCode == -1) { - trainInfo->trainVersionCode = trainInfoOnDisk.trainVersionCode; - } else if (trainInfo->trainVersionCode != trainInfoOnDisk.trainVersionCode) { - // Reset experiment ids if we receive a new non-empty train version. - resetExperimentIds = true; - } - - // Reset if we received a different experiment id. - if (!trainInfo->experimentIds.empty() && - (trainInfoOnDisk.experimentIds.empty() || - trainInfo->experimentIds.at(0) != trainInfoOnDisk.experimentIds[0])) { - resetExperimentIds = true; - } - } - - // Find the right experiment IDs - if ((!resetExperimentIds || is_rollback) && readTrainInfoSuccess) { - trainInfo->experimentIds = trainInfoOnDisk.experimentIds; - } - - if (!trainInfo->experimentIds.empty()) { - int64_t firstId = trainInfo->experimentIds.at(0); - auto& ids = trainInfo->experimentIds; - switch (trainInfo->status) { - case android::os::statsd::util::BINARY_PUSH_STATE_CHANGED__STATE__INSTALL_SUCCESS: - if (find(ids.begin(), ids.end(), firstId + 1) == ids.end()) { - ids.push_back(firstId + 1); - } - break; - case android::os::statsd::util::BINARY_PUSH_STATE_CHANGED__STATE__INSTALLER_ROLLBACK_INITIATED: - if (find(ids.begin(), ids.end(), firstId + 2) == ids.end()) { - ids.push_back(firstId + 2); - } - break; - case android::os::statsd::util::BINARY_PUSH_STATE_CHANGED__STATE__INSTALLER_ROLLBACK_SUCCESS: - if (find(ids.begin(), ids.end(), firstId + 3) == ids.end()) { - ids.push_back(firstId + 3); - } - break; - } - } - - // If this event is a rollback event, the following fields are invalid and - // need to be replaced by the fields stored to disk. - if (is_rollback) { - trainInfo->requiresStaging = trainInfoOnDisk.requiresStaging; - trainInfo->rollbackEnabled = trainInfoOnDisk.rollbackEnabled; - trainInfo->requiresLowLatencyMonitor = trainInfoOnDisk.requiresLowLatencyMonitor; - } - - StorageManager::writeTrainInfo(*trainInfo); -} - -void StatsLogProcessor::onWatchdogRollbackOccurredLocked(LogEvent* event) { - pid_t pid = event->GetPid(); - uid_t uid = event->GetUid(); - if (!checkPermissionForIds(kPermissionDump, pid, uid) || - !checkPermissionForIds(kPermissionUsage, pid, uid)) { - return; - } - // The Get* functions don't modify the status on success, they only write in - // failure statuses, so we can use one status variable for all calls then - // check if it is no longer NO_ERROR. - status_t err = NO_ERROR; - int32_t rollbackType = int32_t(event->GetInt(1 /*rollback type field id*/, &err)); - string packageName = string(event->GetString(2 /*package name field id*/, &err)); - - if (err != NO_ERROR) { - ALOGE("Failed to parse fields in watchdog rollback occurred log event"); - return; - } - - vector experimentIds = - processWatchdogRollbackOccurred(rollbackType, packageName); - vector experimentIdProto; - writeExperimentIdsToProto(experimentIds, &experimentIdProto); - - event->updateValue(6 /*experiment ids field id*/, experimentIdProto, STORAGE); -} - -vector StatsLogProcessor::processWatchdogRollbackOccurred(const int32_t rollbackTypeIn, - const string& packageNameIn) { - // If the package name is empty, we can't attribute it to any train, so - // return early. - if (packageNameIn.empty()) { - return vector(); - } - bool readTrainInfoSuccess = false; - InstallTrainInfo trainInfoOnDisk; - // We use the package name of the event as the train name. - readTrainInfoSuccess = StorageManager::readTrainInfo(packageNameIn, trainInfoOnDisk); - - if (!readTrainInfoSuccess) { - return vector(); - } - - if (trainInfoOnDisk.experimentIds.empty()) { - return vector(); - } - - int64_t firstId = trainInfoOnDisk.experimentIds[0]; - auto& ids = trainInfoOnDisk.experimentIds; - switch (rollbackTypeIn) { - case android::os::statsd::util::WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_INITIATE: - if (find(ids.begin(), ids.end(), firstId + 4) == ids.end()) { - ids.push_back(firstId + 4); - } - StorageManager::writeTrainInfo(trainInfoOnDisk); - break; - case android::os::statsd::util::WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_SUCCESS: - if (find(ids.begin(), ids.end(), firstId + 5) == ids.end()) { - ids.push_back(firstId + 5); - } - StorageManager::writeTrainInfo(trainInfoOnDisk); - break; - } - - return trainInfoOnDisk.experimentIds; -} - -void StatsLogProcessor::resetConfigs() { - std::lock_guard lock(mMetricsMutex); - resetConfigsLocked(getElapsedRealtimeNs()); -} - -void StatsLogProcessor::resetConfigsLocked(const int64_t timestampNs) { - std::vector configKeys; - for (auto it = mMetricsManagers.begin(); it != mMetricsManagers.end(); it++) { - configKeys.push_back(it->first); - } - resetConfigsLocked(timestampNs, configKeys); -} - -void StatsLogProcessor::OnLogEvent(LogEvent* event) { - OnLogEvent(event, getElapsedRealtimeNs()); -} - -void StatsLogProcessor::OnLogEvent(LogEvent* event, int64_t elapsedRealtimeNs) { - std::lock_guard lock(mMetricsMutex); - - // Tell StatsdStats about new event - const int64_t eventElapsedTimeNs = event->GetElapsedTimestampNs(); - int atomId = event->GetTagId(); - StatsdStats::getInstance().noteAtomLogged(atomId, eventElapsedTimeNs / NS_PER_SEC); - if (!event->isValid()) { - StatsdStats::getInstance().noteAtomError(atomId); - return; - } - - // Hard-coded logic to update train info on disk and fill in any information - // this log event may be missing. - if (atomId == android::os::statsd::util::BINARY_PUSH_STATE_CHANGED) { - onBinaryPushStateChangedEventLocked(event); - } - - // Hard-coded logic to update experiment ids on disk for certain rollback - // types and fill the rollback atom with experiment ids - if (atomId == android::os::statsd::util::WATCHDOG_ROLLBACK_OCCURRED) { - onWatchdogRollbackOccurredLocked(event); - } - - if (mPrintAllLogs) { - ALOGI("%s", event->ToString().c_str()); - } - resetIfConfigTtlExpiredLocked(eventElapsedTimeNs); - - // Hard-coded logic to update the isolated uid's in the uid-map. - // The field numbers need to be currently updated by hand with atoms.proto - if (atomId == android::os::statsd::util::ISOLATED_UID_CHANGED) { - onIsolatedUidChangedEventLocked(*event); - } else { - // Map the isolated uid to host uid if necessary. - mapIsolatedUidToHostUidIfNecessaryLocked(event); - } - - StateManager::getInstance().onLogEvent(*event); - - if (mMetricsManagers.empty()) { - return; - } - - bool fireAlarm = false; - { - std::lock_guard anomalyLock(mAnomalyAlarmMutex); - if (mNextAnomalyAlarmTime != 0 && - MillisToNano(mNextAnomalyAlarmTime) <= elapsedRealtimeNs) { - mNextAnomalyAlarmTime = 0; - VLOG("informing anomaly alarm at time %lld", (long long)elapsedRealtimeNs); - fireAlarm = true; - } - } - if (fireAlarm) { - informAnomalyAlarmFiredLocked(NanoToMillis(elapsedRealtimeNs)); - } - - int64_t curTimeSec = getElapsedRealtimeSec(); - if (curTimeSec - mLastPullerCacheClearTimeSec > StatsdStats::kPullerCacheClearIntervalSec) { - mPullerManager->ClearPullerCacheIfNecessary(curTimeSec * NS_PER_SEC); - mLastPullerCacheClearTimeSec = curTimeSec; - } - - std::unordered_set uidsWithActiveConfigsChanged; - std::unordered_map> activeConfigsPerUid; - // pass the event to metrics managers. - for (auto& pair : mMetricsManagers) { - int uid = pair.first.GetUid(); - int64_t configId = pair.first.GetId(); - bool isPrevActive = pair.second->isActive(); - pair.second->onLogEvent(*event); - bool isCurActive = pair.second->isActive(); - // Map all active configs by uid. - if (isCurActive) { - auto activeConfigs = activeConfigsPerUid.find(uid); - if (activeConfigs != activeConfigsPerUid.end()) { - activeConfigs->second.push_back(configId); - } else { - vector newActiveConfigs; - newActiveConfigs.push_back(configId); - activeConfigsPerUid[uid] = newActiveConfigs; - } - } - // The activation state of this config changed. - if (isPrevActive != isCurActive) { - VLOG("Active status changed for uid %d", uid); - uidsWithActiveConfigsChanged.insert(uid); - StatsdStats::getInstance().noteActiveStatusChanged(pair.first, isCurActive); - } - flushIfNecessaryLocked(pair.first, *(pair.second)); - } - - // Don't use the event timestamp for the guardrail. - for (int uid : uidsWithActiveConfigsChanged) { - // Send broadcast so that receivers can pull data. - auto lastBroadcastTime = mLastActivationBroadcastTimes.find(uid); - if (lastBroadcastTime != mLastActivationBroadcastTimes.end()) { - if (elapsedRealtimeNs - lastBroadcastTime->second < - StatsdStats::kMinActivationBroadcastPeriodNs) { - StatsdStats::getInstance().noteActivationBroadcastGuardrailHit(uid); - VLOG("StatsD would've sent an activation broadcast but the rate limit stopped us."); - return; - } - } - auto activeConfigs = activeConfigsPerUid.find(uid); - if (activeConfigs != activeConfigsPerUid.end()) { - if (mSendActivationBroadcast(uid, activeConfigs->second)) { - VLOG("StatsD sent activation notice for uid %d", uid); - mLastActivationBroadcastTimes[uid] = elapsedRealtimeNs; - } - } else { - std::vector emptyActiveConfigs; - if (mSendActivationBroadcast(uid, emptyActiveConfigs)) { - VLOG("StatsD sent EMPTY activation notice for uid %d", uid); - mLastActivationBroadcastTimes[uid] = elapsedRealtimeNs; - } - } - } -} - -void StatsLogProcessor::GetActiveConfigs(const int uid, vector& outActiveConfigs) { - std::lock_guard lock(mMetricsMutex); - GetActiveConfigsLocked(uid, outActiveConfigs); -} - -void StatsLogProcessor::GetActiveConfigsLocked(const int uid, vector& outActiveConfigs) { - outActiveConfigs.clear(); - for (auto& pair : mMetricsManagers) { - if (pair.first.GetUid() == uid && pair.second->isActive()) { - outActiveConfigs.push_back(pair.first.GetId()); - } - } -} - -void StatsLogProcessor::OnConfigUpdated(const int64_t timestampNs, const ConfigKey& key, - const StatsdConfig& config, bool modularUpdate) { - std::lock_guard lock(mMetricsMutex); - WriteDataToDiskLocked(key, timestampNs, CONFIG_UPDATED, NO_TIME_CONSTRAINTS); - OnConfigUpdatedLocked(timestampNs, key, config, modularUpdate); -} - -void StatsLogProcessor::OnConfigUpdatedLocked(const int64_t timestampNs, const ConfigKey& key, - const StatsdConfig& config, bool modularUpdate) { - VLOG("Updated configuration for key %s", key.ToString().c_str()); - // Create new config if this is not a modular update or if this is a new config. - const auto& it = mMetricsManagers.find(key); - bool configValid = false; - if (!modularUpdate || it == mMetricsManagers.end()) { - sp newMetricsManager = - new MetricsManager(key, config, mTimeBaseNs, timestampNs, mUidMap, mPullerManager, - mAnomalyAlarmMonitor, mPeriodicAlarmMonitor); - configValid = newMetricsManager->isConfigValid(); - if (configValid) { - newMetricsManager->init(); - mUidMap->OnConfigUpdated(key); - newMetricsManager->refreshTtl(timestampNs); - mMetricsManagers[key] = newMetricsManager; - VLOG("StatsdConfig valid"); - } - } else { - // Preserve the existing MetricsManager, update necessary components and metadata in place. - configValid = it->second->updateConfig(config, mTimeBaseNs, timestampNs, - mAnomalyAlarmMonitor, mPeriodicAlarmMonitor); - if (configValid) { - mUidMap->OnConfigUpdated(key); - } - } - if (!configValid) { - // If there is any error in the config, don't use it. - // Remove any existing config with the same key. - ALOGE("StatsdConfig NOT valid"); - mMetricsManagers.erase(key); - } -} - -size_t StatsLogProcessor::GetMetricsSize(const ConfigKey& key) const { - std::lock_guard lock(mMetricsMutex); - auto it = mMetricsManagers.find(key); - if (it == mMetricsManagers.end()) { - ALOGW("Config source %s does not exist", key.ToString().c_str()); - return 0; - } - return it->second->byteSize(); -} - -void StatsLogProcessor::dumpStates(int out, bool verbose) { - std::lock_guard lock(mMetricsMutex); - FILE* fout = fdopen(out, "w"); - if (fout == NULL) { - return; - } - fprintf(fout, "MetricsManager count: %lu\n", (unsigned long)mMetricsManagers.size()); - for (auto metricsManager : mMetricsManagers) { - metricsManager.second->dumpStates(fout, verbose); - } - - fclose(fout); -} - -/* - * onDumpReport dumps serialized ConfigMetricsReportList into proto. - */ -void StatsLogProcessor::onDumpReport(const ConfigKey& key, const int64_t dumpTimeStampNs, - const bool include_current_partial_bucket, - const bool erase_data, - const DumpReportReason dumpReportReason, - const DumpLatency dumpLatency, - ProtoOutputStream* proto) { - std::lock_guard lock(mMetricsMutex); - - // Start of ConfigKey. - uint64_t configKeyToken = proto->start(FIELD_TYPE_MESSAGE | FIELD_ID_CONFIG_KEY); - proto->write(FIELD_TYPE_INT32 | FIELD_ID_UID, key.GetUid()); - proto->write(FIELD_TYPE_INT64 | FIELD_ID_ID, (long long)key.GetId()); - proto->end(configKeyToken); - // End of ConfigKey. - - bool keepFile = false; - auto it = mMetricsManagers.find(key); - if (it != mMetricsManagers.end() && it->second->shouldPersistLocalHistory()) { - keepFile = true; - } - - // Then, check stats-data directory to see there's any file containing - // ConfigMetricsReport from previous shutdowns to concatenate to reports. - StorageManager::appendConfigMetricsReport( - key, proto, erase_data && !keepFile /* should remove file after appending it */, - dumpReportReason == ADB_DUMP /*if caller is adb*/); - - if (it != mMetricsManagers.end()) { - // This allows another broadcast to be sent within the rate-limit period if we get close to - // filling the buffer again soon. - mLastBroadcastTimes.erase(key); - - vector buffer; - onConfigMetricsReportLocked(key, dumpTimeStampNs, include_current_partial_bucket, - erase_data, dumpReportReason, dumpLatency, - false /* is this data going to be saved on disk */, &buffer); - proto->write(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_REPORTS, - reinterpret_cast(buffer.data()), buffer.size()); - } else { - ALOGW("Config source %s does not exist", key.ToString().c_str()); - } -} - -/* - * onDumpReport dumps serialized ConfigMetricsReportList into outData. - */ -void StatsLogProcessor::onDumpReport(const ConfigKey& key, const int64_t dumpTimeStampNs, - const bool include_current_partial_bucket, - const bool erase_data, - const DumpReportReason dumpReportReason, - const DumpLatency dumpLatency, - vector* outData) { - ProtoOutputStream proto; - onDumpReport(key, dumpTimeStampNs, include_current_partial_bucket, erase_data, - dumpReportReason, dumpLatency, &proto); - - if (outData != nullptr) { - flushProtoToBuffer(proto, outData); - VLOG("output data size %zu", outData->size()); - } - - StatsdStats::getInstance().noteMetricsReportSent(key, proto.size()); -} - -/* - * onConfigMetricsReportLocked dumps serialized ConfigMetricsReport into outData. - */ -void StatsLogProcessor::onConfigMetricsReportLocked( - const ConfigKey& key, const int64_t dumpTimeStampNs, - const bool include_current_partial_bucket, const bool erase_data, - const DumpReportReason dumpReportReason, const DumpLatency dumpLatency, - const bool dataSavedOnDisk, vector* buffer) { - // We already checked whether key exists in mMetricsManagers in - // WriteDataToDisk. - auto it = mMetricsManagers.find(key); - if (it == mMetricsManagers.end()) { - return; - } - int64_t lastReportTimeNs = it->second->getLastReportTimeNs(); - int64_t lastReportWallClockNs = it->second->getLastReportWallClockNs(); - - std::set str_set; - - ProtoOutputStream tempProto; - // First, fill in ConfigMetricsReport using current data on memory, which - // starts from filling in StatsLogReport's. - it->second->onDumpReport(dumpTimeStampNs, include_current_partial_bucket, erase_data, - dumpLatency, &str_set, &tempProto); - - // Fill in UidMap if there is at least one metric to report. - // This skips the uid map if it's an empty config. - if (it->second->getNumMetrics() > 0) { - uint64_t uidMapToken = tempProto.start(FIELD_TYPE_MESSAGE | FIELD_ID_UID_MAP); - mUidMap->appendUidMap( - dumpTimeStampNs, key, it->second->hashStringInReport() ? &str_set : nullptr, - it->second->versionStringsInReport(), it->second->installerInReport(), &tempProto); - tempProto.end(uidMapToken); - } - - // Fill in the timestamps. - tempProto.write(FIELD_TYPE_INT64 | FIELD_ID_LAST_REPORT_ELAPSED_NANOS, - (long long)lastReportTimeNs); - tempProto.write(FIELD_TYPE_INT64 | FIELD_ID_CURRENT_REPORT_ELAPSED_NANOS, - (long long)dumpTimeStampNs); - tempProto.write(FIELD_TYPE_INT64 | FIELD_ID_LAST_REPORT_WALL_CLOCK_NANOS, - (long long)lastReportWallClockNs); - tempProto.write(FIELD_TYPE_INT64 | FIELD_ID_CURRENT_REPORT_WALL_CLOCK_NANOS, - (long long)getWallClockNs()); - // Dump report reason - tempProto.write(FIELD_TYPE_INT32 | FIELD_ID_DUMP_REPORT_REASON, dumpReportReason); - - for (const auto& str : str_set) { - tempProto.write(FIELD_TYPE_STRING | FIELD_COUNT_REPEATED | FIELD_ID_STRINGS, str); - } - - flushProtoToBuffer(tempProto, buffer); - - // save buffer to disk if needed - if (erase_data && !dataSavedOnDisk && it->second->shouldPersistLocalHistory()) { - VLOG("save history to disk"); - string file_name = StorageManager::getDataHistoryFileName((long)getWallClockSec(), - key.GetUid(), key.GetId()); - StorageManager::writeFile(file_name.c_str(), buffer->data(), buffer->size()); - } -} - -void StatsLogProcessor::resetConfigsLocked(const int64_t timestampNs, - const std::vector& configs) { - for (const auto& key : configs) { - StatsdConfig config; - if (StorageManager::readConfigFromDisk(key, &config)) { - // Force a full update when resetting a config. - OnConfigUpdatedLocked(timestampNs, key, config, /*modularUpdate=*/false); - StatsdStats::getInstance().noteConfigReset(key); - } else { - ALOGE("Failed to read backup config from disk for : %s", key.ToString().c_str()); - auto it = mMetricsManagers.find(key); - if (it != mMetricsManagers.end()) { - it->second->refreshTtl(timestampNs); - } - } - } -} - -void StatsLogProcessor::resetIfConfigTtlExpiredLocked(const int64_t eventTimeNs) { - std::vector configKeysTtlExpired; - for (auto it = mMetricsManagers.begin(); it != mMetricsManagers.end(); it++) { - if (it->second != nullptr && !it->second->isInTtl(eventTimeNs)) { - configKeysTtlExpired.push_back(it->first); - } - } - if (configKeysTtlExpired.size() > 0) { - WriteDataToDiskLocked(CONFIG_RESET, NO_TIME_CONSTRAINTS, getElapsedRealtimeNs()); - resetConfigsLocked(eventTimeNs, configKeysTtlExpired); - } -} - -void StatsLogProcessor::OnConfigRemoved(const ConfigKey& key) { - std::lock_guard lock(mMetricsMutex); - auto it = mMetricsManagers.find(key); - if (it != mMetricsManagers.end()) { - WriteDataToDiskLocked(key, getElapsedRealtimeNs(), CONFIG_REMOVED, - NO_TIME_CONSTRAINTS); - mMetricsManagers.erase(it); - mUidMap->OnConfigRemoved(key); - } - StatsdStats::getInstance().noteConfigRemoved(key); - - mLastBroadcastTimes.erase(key); - - int uid = key.GetUid(); - bool lastConfigForUid = true; - for (auto it : mMetricsManagers) { - if (it.first.GetUid() == uid) { - lastConfigForUid = false; - break; - } - } - if (lastConfigForUid) { - mLastActivationBroadcastTimes.erase(uid); - } - - if (mMetricsManagers.empty()) { - mPullerManager->ForceClearPullerCache(); - } -} - -void StatsLogProcessor::flushIfNecessaryLocked(const ConfigKey& key, - MetricsManager& metricsManager) { - int64_t elapsedRealtimeNs = getElapsedRealtimeNs(); - auto lastCheckTime = mLastByteSizeTimes.find(key); - if (lastCheckTime != mLastByteSizeTimes.end()) { - if (elapsedRealtimeNs - lastCheckTime->second < StatsdStats::kMinByteSizeCheckPeriodNs) { - return; - } - } - - // We suspect that the byteSize() computation is expensive, so we set a rate limit. - size_t totalBytes = metricsManager.byteSize(); - mLastByteSizeTimes[key] = elapsedRealtimeNs; - bool requestDump = false; - if (totalBytes > StatsdStats::kMaxMetricsBytesPerConfig) { - // Too late. We need to start clearing data. - metricsManager.dropData(elapsedRealtimeNs); - StatsdStats::getInstance().noteDataDropped(key, totalBytes); - VLOG("StatsD had to toss out metrics for %s", key.ToString().c_str()); - } else if ((totalBytes > StatsdStats::kBytesPerConfigTriggerGetData) || - (mOnDiskDataConfigs.find(key) != mOnDiskDataConfigs.end())) { - // Request to send a broadcast if: - // 1. in memory data > threshold OR - // 2. config has old data report on disk. - requestDump = true; - } - - if (requestDump) { - // Send broadcast so that receivers can pull data. - auto lastBroadcastTime = mLastBroadcastTimes.find(key); - if (lastBroadcastTime != mLastBroadcastTimes.end()) { - if (elapsedRealtimeNs - lastBroadcastTime->second < - StatsdStats::kMinBroadcastPeriodNs) { - VLOG("StatsD would've sent a broadcast but the rate limit stopped us."); - return; - } - } - if (mSendBroadcast(key)) { - mOnDiskDataConfigs.erase(key); - VLOG("StatsD triggered data fetch for %s", key.ToString().c_str()); - mLastBroadcastTimes[key] = elapsedRealtimeNs; - StatsdStats::getInstance().noteBroadcastSent(key); - } - } -} - -void StatsLogProcessor::WriteDataToDiskLocked(const ConfigKey& key, - const int64_t timestampNs, - const DumpReportReason dumpReportReason, - const DumpLatency dumpLatency) { - if (mMetricsManagers.find(key) == mMetricsManagers.end() || - !mMetricsManagers.find(key)->second->shouldWriteToDisk()) { - return; - } - vector buffer; - onConfigMetricsReportLocked(key, timestampNs, true /* include_current_partial_bucket*/, - true /* erase_data */, dumpReportReason, dumpLatency, true, - &buffer); - string file_name = - StorageManager::getDataFileName((long)getWallClockSec(), key.GetUid(), key.GetId()); - StorageManager::writeFile(file_name.c_str(), buffer.data(), buffer.size()); - - // We were able to write the ConfigMetricsReport to disk, so we should trigger collection ASAP. - mOnDiskDataConfigs.insert(key); -} - -void StatsLogProcessor::SaveActiveConfigsToDisk(int64_t currentTimeNs) { - std::lock_guard lock(mMetricsMutex); - const int64_t timeNs = getElapsedRealtimeNs(); - // Do not write to disk if we already have in the last few seconds. - if (static_cast (timeNs) < - mLastActiveMetricsWriteNs + WRITE_DATA_COOL_DOWN_SEC * NS_PER_SEC) { - ALOGI("Statsd skipping writing active metrics to disk. Already wrote data in last %d seconds", - WRITE_DATA_COOL_DOWN_SEC); - return; - } - mLastActiveMetricsWriteNs = timeNs; - - ProtoOutputStream proto; - WriteActiveConfigsToProtoOutputStreamLocked(currentTimeNs, DEVICE_SHUTDOWN, &proto); - - string file_name = StringPrintf("%s/active_metrics", STATS_ACTIVE_METRIC_DIR); - StorageManager::deleteFile(file_name.c_str()); - android::base::unique_fd fd( - open(file_name.c_str(), O_WRONLY | O_CREAT | O_CLOEXEC, S_IRUSR | S_IWUSR)); - if (fd == -1) { - ALOGE("Attempt to write %s but failed", file_name.c_str()); - return; - } - proto.flush(fd.get()); -} - -void StatsLogProcessor::SaveMetadataToDisk(int64_t currentWallClockTimeNs, - int64_t systemElapsedTimeNs) { - std::lock_guard lock(mMetricsMutex); - // Do not write to disk if we already have in the last few seconds. - if (static_cast (systemElapsedTimeNs) < - mLastMetadataWriteNs + WRITE_DATA_COOL_DOWN_SEC * NS_PER_SEC) { - ALOGI("Statsd skipping writing metadata to disk. Already wrote data in last %d seconds", - WRITE_DATA_COOL_DOWN_SEC); - return; - } - mLastMetadataWriteNs = systemElapsedTimeNs; - - metadata::StatsMetadataList metadataList; - WriteMetadataToProtoLocked( - currentWallClockTimeNs, systemElapsedTimeNs, &metadataList); - - string file_name = StringPrintf("%s/metadata", STATS_METADATA_DIR); - StorageManager::deleteFile(file_name.c_str()); - - if (metadataList.stats_metadata_size() == 0) { - // Skip the write if we have nothing to write. - return; - } - - std::string data; - metadataList.SerializeToString(&data); - StorageManager::writeFile(file_name.c_str(), data.c_str(), data.size()); -} - -void StatsLogProcessor::WriteMetadataToProto(int64_t currentWallClockTimeNs, - int64_t systemElapsedTimeNs, - metadata::StatsMetadataList* metadataList) { - std::lock_guard lock(mMetricsMutex); - WriteMetadataToProtoLocked(currentWallClockTimeNs, systemElapsedTimeNs, metadataList); -} - -void StatsLogProcessor::WriteMetadataToProtoLocked(int64_t currentWallClockTimeNs, - int64_t systemElapsedTimeNs, - metadata::StatsMetadataList* metadataList) { - for (const auto& pair : mMetricsManagers) { - const sp& metricsManager = pair.second; - metadata::StatsMetadata* statsMetadata = metadataList->add_stats_metadata(); - bool metadataWritten = metricsManager->writeMetadataToProto(currentWallClockTimeNs, - systemElapsedTimeNs, statsMetadata); - if (!metadataWritten) { - metadataList->mutable_stats_metadata()->RemoveLast(); - } - } -} - -void StatsLogProcessor::LoadMetadataFromDisk(int64_t currentWallClockTimeNs, - int64_t systemElapsedTimeNs) { - std::lock_guard lock(mMetricsMutex); - string file_name = StringPrintf("%s/metadata", STATS_METADATA_DIR); - int fd = open(file_name.c_str(), O_RDONLY | O_CLOEXEC); - if (-1 == fd) { - VLOG("Attempt to read %s but failed", file_name.c_str()); - StorageManager::deleteFile(file_name.c_str()); - return; - } - string content; - if (!android::base::ReadFdToString(fd, &content)) { - ALOGE("Attempt to read %s but failed", file_name.c_str()); - close(fd); - StorageManager::deleteFile(file_name.c_str()); - return; - } - - close(fd); - - metadata::StatsMetadataList statsMetadataList; - if (!statsMetadataList.ParseFromString(content)) { - ALOGE("Attempt to read %s but failed; failed to metadata", file_name.c_str()); - StorageManager::deleteFile(file_name.c_str()); - return; - } - SetMetadataStateLocked(statsMetadataList, currentWallClockTimeNs, systemElapsedTimeNs); - StorageManager::deleteFile(file_name.c_str()); -} - -void StatsLogProcessor::SetMetadataState(const metadata::StatsMetadataList& statsMetadataList, - int64_t currentWallClockTimeNs, - int64_t systemElapsedTimeNs) { - std::lock_guard lock(mMetricsMutex); - SetMetadataStateLocked(statsMetadataList, currentWallClockTimeNs, systemElapsedTimeNs); -} - -void StatsLogProcessor::SetMetadataStateLocked( - const metadata::StatsMetadataList& statsMetadataList, - int64_t currentWallClockTimeNs, - int64_t systemElapsedTimeNs) { - for (const metadata::StatsMetadata& metadata : statsMetadataList.stats_metadata()) { - ConfigKey key(metadata.config_key().uid(), metadata.config_key().config_id()); - auto it = mMetricsManagers.find(key); - if (it == mMetricsManagers.end()) { - ALOGE("No config found for configKey %s", key.ToString().c_str()); - continue; - } - VLOG("Setting metadata %s", key.ToString().c_str()); - it->second->loadMetadata(metadata, currentWallClockTimeNs, systemElapsedTimeNs); - } - VLOG("Successfully loaded %d metadata.", statsMetadataList.stats_metadata_size()); -} - -void StatsLogProcessor::WriteActiveConfigsToProtoOutputStream( - int64_t currentTimeNs, const DumpReportReason reason, ProtoOutputStream* proto) { - std::lock_guard lock(mMetricsMutex); - WriteActiveConfigsToProtoOutputStreamLocked(currentTimeNs, reason, proto); -} - -void StatsLogProcessor::WriteActiveConfigsToProtoOutputStreamLocked( - int64_t currentTimeNs, const DumpReportReason reason, ProtoOutputStream* proto) { - for (const auto& pair : mMetricsManagers) { - const sp& metricsManager = pair.second; - uint64_t configToken = proto->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | - FIELD_ID_ACTIVE_CONFIG_LIST_CONFIG); - metricsManager->writeActiveConfigToProtoOutputStream(currentTimeNs, reason, proto); - proto->end(configToken); - } -} -void StatsLogProcessor::LoadActiveConfigsFromDisk() { - std::lock_guard lock(mMetricsMutex); - string file_name = StringPrintf("%s/active_metrics", STATS_ACTIVE_METRIC_DIR); - int fd = open(file_name.c_str(), O_RDONLY | O_CLOEXEC); - if (-1 == fd) { - VLOG("Attempt to read %s but failed", file_name.c_str()); - StorageManager::deleteFile(file_name.c_str()); - return; - } - string content; - if (!android::base::ReadFdToString(fd, &content)) { - ALOGE("Attempt to read %s but failed", file_name.c_str()); - close(fd); - StorageManager::deleteFile(file_name.c_str()); - return; - } - - close(fd); - - ActiveConfigList activeConfigList; - if (!activeConfigList.ParseFromString(content)) { - ALOGE("Attempt to read %s but failed; failed to load active configs", file_name.c_str()); - StorageManager::deleteFile(file_name.c_str()); - return; - } - // Passing in mTimeBaseNs only works as long as we only load from disk is when statsd starts. - SetConfigsActiveStateLocked(activeConfigList, mTimeBaseNs); - StorageManager::deleteFile(file_name.c_str()); -} - -void StatsLogProcessor::SetConfigsActiveState(const ActiveConfigList& activeConfigList, - int64_t currentTimeNs) { - std::lock_guard lock(mMetricsMutex); - SetConfigsActiveStateLocked(activeConfigList, currentTimeNs); -} - -void StatsLogProcessor::SetConfigsActiveStateLocked(const ActiveConfigList& activeConfigList, - int64_t currentTimeNs) { - for (int i = 0; i < activeConfigList.config_size(); i++) { - const auto& config = activeConfigList.config(i); - ConfigKey key(config.uid(), config.id()); - auto it = mMetricsManagers.find(key); - if (it == mMetricsManagers.end()) { - ALOGE("No config found for config %s", key.ToString().c_str()); - continue; - } - VLOG("Setting active config %s", key.ToString().c_str()); - it->second->loadActiveConfig(config, currentTimeNs); - } - VLOG("Successfully loaded %d active configs.", activeConfigList.config_size()); -} - -void StatsLogProcessor::WriteDataToDiskLocked(const DumpReportReason dumpReportReason, - const DumpLatency dumpLatency, - const int64_t elapsedRealtimeNs) { - // Do not write to disk if we already have in the last few seconds. - // This is to avoid overwriting files that would have the same name if we - // write twice in the same second. - if (static_cast(elapsedRealtimeNs) < - mLastWriteTimeNs + WRITE_DATA_COOL_DOWN_SEC * NS_PER_SEC) { - ALOGI("Statsd skipping writing data to disk. Already wrote data in last %d seconds", - WRITE_DATA_COOL_DOWN_SEC); - return; - } - mLastWriteTimeNs = elapsedRealtimeNs; - for (auto& pair : mMetricsManagers) { - WriteDataToDiskLocked(pair.first, elapsedRealtimeNs, dumpReportReason, dumpLatency); - } -} - -void StatsLogProcessor::WriteDataToDisk(const DumpReportReason dumpReportReason, - const DumpLatency dumpLatency, - const int64_t elapsedRealtimeNs) { - std::lock_guard lock(mMetricsMutex); - WriteDataToDiskLocked(dumpReportReason, dumpLatency, elapsedRealtimeNs); -} - -void StatsLogProcessor::informPullAlarmFired(const int64_t timestampNs) { - std::lock_guard lock(mMetricsMutex); - mPullerManager->OnAlarmFired(timestampNs); -} - -int64_t StatsLogProcessor::getLastReportTimeNs(const ConfigKey& key) { - auto it = mMetricsManagers.find(key); - if (it == mMetricsManagers.end()) { - return 0; - } else { - return it->second->getLastReportTimeNs(); - } -} - -void StatsLogProcessor::notifyAppUpgrade(const int64_t& eventTimeNs, const string& apk, - const int uid, const int64_t version) { - std::lock_guard lock(mMetricsMutex); - VLOG("Received app upgrade"); - StateManager::getInstance().notifyAppChanged(apk, mUidMap); - for (const auto& it : mMetricsManagers) { - it.second->notifyAppUpgrade(eventTimeNs, apk, uid, version); - } -} - -void StatsLogProcessor::notifyAppRemoved(const int64_t& eventTimeNs, const string& apk, - const int uid) { - std::lock_guard lock(mMetricsMutex); - VLOG("Received app removed"); - StateManager::getInstance().notifyAppChanged(apk, mUidMap); - for (const auto& it : mMetricsManagers) { - it.second->notifyAppRemoved(eventTimeNs, apk, uid); - } -} - -void StatsLogProcessor::onUidMapReceived(const int64_t& eventTimeNs) { - std::lock_guard lock(mMetricsMutex); - VLOG("Received uid map"); - StateManager::getInstance().updateLogSources(mUidMap); - for (const auto& it : mMetricsManagers) { - it.second->onUidMapReceived(eventTimeNs); - } -} - -void StatsLogProcessor::onStatsdInitCompleted(const int64_t& elapsedTimeNs) { - std::lock_guard lock(mMetricsMutex); - VLOG("Received boot completed signal"); - for (const auto& it : mMetricsManagers) { - it.second->onStatsdInitCompleted(elapsedTimeNs); - } -} - -void StatsLogProcessor::noteOnDiskData(const ConfigKey& key) { - std::lock_guard lock(mMetricsMutex); - mOnDiskDataConfigs.insert(key); -} - -void StatsLogProcessor::setAnomalyAlarm(const int64_t elapsedTimeMillis) { - std::lock_guard lock(mAnomalyAlarmMutex); - mNextAnomalyAlarmTime = elapsedTimeMillis; -} - -void StatsLogProcessor::cancelAnomalyAlarm() { - std::lock_guard lock(mAnomalyAlarmMutex); - mNextAnomalyAlarmTime = 0; -} - -void StatsLogProcessor::informAnomalyAlarmFiredLocked(const int64_t elapsedTimeMillis) { - VLOG("StatsService::informAlarmForSubscriberTriggeringFired was called"); - std::unordered_set, SpHash> alarmSet = - mAnomalyAlarmMonitor->popSoonerThan(static_cast(elapsedTimeMillis / 1000)); - if (alarmSet.size() > 0) { - VLOG("Found periodic alarm fired."); - processFiredAnomalyAlarmsLocked(MillisToNano(elapsedTimeMillis), alarmSet); - } else { - ALOGW("Cannot find an periodic alarm that fired. Perhaps it was recently cancelled."); - } -} - -} // namespace statsd -} // namespace os -} // namespace android diff --git a/bin/src/StatsLogProcessor.h b/bin/src/StatsLogProcessor.h deleted file mode 100644 index 98bf67a6..00000000 --- a/bin/src/StatsLogProcessor.h +++ /dev/null @@ -1,380 +0,0 @@ -/* - * 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. - */ - -#pragma once - -#include -#include "config/ConfigListener.h" -#include "logd/LogEvent.h" -#include "metrics/MetricsManager.h" -#include "packages/UidMap.h" -#include "external/StatsPullerManager.h" - -#include "packages/modules/StatsD/bin/src/statsd_config.pb.h" -#include "packages/modules/StatsD/bin/src/statsd_metadata.pb.h" - -#include -#include - -namespace android { -namespace os { -namespace statsd { - - -class StatsLogProcessor : public ConfigListener, public virtual PackageInfoListener { -public: - StatsLogProcessor(const sp& uidMap, const sp& pullerManager, - const sp& anomalyAlarmMonitor, - const sp& subscriberTriggerAlarmMonitor, - const int64_t timeBaseNs, - const std::function& sendBroadcast, - const std::function&)>& sendActivationBroadcast); - virtual ~StatsLogProcessor(); - - void OnLogEvent(LogEvent* event); - - void OnConfigUpdated(const int64_t timestampNs, const ConfigKey& key, - const StatsdConfig& config, bool modularUpdate = true); - void OnConfigRemoved(const ConfigKey& key); - - size_t GetMetricsSize(const ConfigKey& key) const; - - void GetActiveConfigs(const int uid, vector& outActiveConfigs); - - void onDumpReport(const ConfigKey& key, const int64_t dumpTimeNs, - const bool include_current_partial_bucket, const bool erase_data, - const DumpReportReason dumpReportReason, - const DumpLatency dumpLatency, - vector* outData); - void onDumpReport(const ConfigKey& key, const int64_t dumpTimeNs, - const bool include_current_partial_bucket, const bool erase_data, - const DumpReportReason dumpReportReason, - const DumpLatency dumpLatency, - ProtoOutputStream* proto); - - /* Tells MetricsManager that the alarms in alarmSet have fired. Modifies periodic alarmSet. */ - void onPeriodicAlarmFired( - const int64_t& timestampNs, - unordered_set, SpHash> alarmSet); - - /* Flushes data to disk. Data on memory will be gone after written to disk. */ - void WriteDataToDisk(const DumpReportReason dumpReportReason, const DumpLatency dumpLatency, - const int64_t elapsedRealtimeNs); - - /* Persist configs containing metrics with active activations to disk. */ - void SaveActiveConfigsToDisk(int64_t currentTimeNs); - - /* Writes the current active status/ttl for all configs and metrics to ProtoOutputStream. */ - void WriteActiveConfigsToProtoOutputStream( - int64_t currentTimeNs, const DumpReportReason reason, ProtoOutputStream* proto); - - /* Load configs containing metrics with active activations from disk. */ - void LoadActiveConfigsFromDisk(); - - /* Persist metadata for configs and metrics to disk. */ - void SaveMetadataToDisk(int64_t currentWallClockTimeNs, int64_t systemElapsedTimeNs); - - /* Writes the statsd metadata for all configs and metrics to StatsMetadataList. */ - void WriteMetadataToProto(int64_t currentWallClockTimeNs, - int64_t systemElapsedTimeNs, - metadata::StatsMetadataList* metadataList); - - /* Load stats metadata for configs and metrics from disk. */ - void LoadMetadataFromDisk(int64_t currentWallClockTimeNs, - int64_t systemElapsedTimeNs); - - /* Sets the metadata for all configs and metrics */ - void SetMetadataState(const metadata::StatsMetadataList& statsMetadataList, - int64_t currentWallClockTimeNs, - int64_t systemElapsedTimeNs); - - /* Sets the active status/ttl for all configs and metrics to the status in ActiveConfigList. */ - void SetConfigsActiveState(const ActiveConfigList& activeConfigList, int64_t currentTimeNs); - - /* Notify all MetricsManagers of app upgrades */ - void notifyAppUpgrade(const int64_t& eventTimeNs, const string& apk, const int uid, - const int64_t version) override; - - /* Notify all MetricsManagers of app removals */ - void notifyAppRemoved(const int64_t& eventTimeNs, const string& apk, const int uid) override; - - /* Notify all MetricsManagers of uid map snapshots received */ - void onUidMapReceived(const int64_t& eventTimeNs) override; - - /* Notify all metrics managers of boot completed - * This will force a bucket split when the boot is finished. - */ - void onStatsdInitCompleted(const int64_t& elapsedTimeNs); - - // Reset all configs. - void resetConfigs(); - - inline sp getUidMap() { - return mUidMap; - } - - void dumpStates(int outFd, bool verbose); - - void informPullAlarmFired(const int64_t timestampNs); - - int64_t getLastReportTimeNs(const ConfigKey& key); - - inline void setPrintLogs(bool enabled) { - std::lock_guard lock(mMetricsMutex); - mPrintAllLogs = enabled; - } - - // Add a specific config key to the possible configs to dump ASAP. - void noteOnDiskData(const ConfigKey& key); - - void setAnomalyAlarm(const int64_t timeMillis); - - void cancelAnomalyAlarm(); - -private: - // For testing only. - inline sp getAnomalyAlarmMonitor() const { - return mAnomalyAlarmMonitor; - } - - inline sp getPeriodicAlarmMonitor() const { - return mPeriodicAlarmMonitor; - } - - mutable mutex mMetricsMutex; - - // Guards mNextAnomalyAlarmTime. A separate mutex is needed because alarms are set/cancelled - // in the onLogEvent code path, which is locked by mMetricsMutex. - // DO NOT acquire mMetricsMutex while holding mAnomalyAlarmMutex. This can lead to a deadlock. - mutable mutex mAnomalyAlarmMutex; - - std::unordered_map> mMetricsManagers; - - std::unordered_map mLastBroadcastTimes; - - // Last time we sent a broadcast to this uid that the active configs had changed. - std::unordered_map mLastActivationBroadcastTimes; - - // Tracks when we last checked the bytes consumed for each config key. - std::unordered_map mLastByteSizeTimes; - - // Tracks which config keys has metric reports on disk - std::set mOnDiskDataConfigs; - - sp mUidMap; // Reference to the UidMap to lookup app name and version for each uid. - - sp mPullerManager; // Reference to StatsPullerManager - - sp mAnomalyAlarmMonitor; - - sp mPeriodicAlarmMonitor; - - void OnLogEvent(LogEvent* event, int64_t elapsedRealtimeNs); - - void resetIfConfigTtlExpiredLocked(const int64_t eventTimeNs); - - void OnConfigUpdatedLocked(const int64_t currentTimestampNs, const ConfigKey& key, - const StatsdConfig& config, bool modularUpdate); - - void GetActiveConfigsLocked(const int uid, vector& outActiveConfigs); - - void WriteActiveConfigsToProtoOutputStreamLocked( - int64_t currentTimeNs, const DumpReportReason reason, ProtoOutputStream* proto); - - void SetConfigsActiveStateLocked(const ActiveConfigList& activeConfigList, - int64_t currentTimeNs); - - void SetMetadataStateLocked(const metadata::StatsMetadataList& statsMetadataList, - int64_t currentWallClockTimeNs, - int64_t systemElapsedTimeNs); - - void WriteMetadataToProtoLocked(int64_t currentWallClockTimeNs, - int64_t systemElapsedTimeNs, - metadata::StatsMetadataList* metadataList); - - void WriteDataToDiskLocked(const DumpReportReason dumpReportReason, - const DumpLatency dumpLatency, const int64_t elapsedRealtimeNs); - - void WriteDataToDiskLocked(const ConfigKey& key, const int64_t timestampNs, - const DumpReportReason dumpReportReason, - const DumpLatency dumpLatency); - - void onConfigMetricsReportLocked( - const ConfigKey& key, const int64_t dumpTimeStampNs, - const bool include_current_partial_bucket, const bool erase_data, - const DumpReportReason dumpReportReason, const DumpLatency dumpLatency, - /*if dataSavedToDisk is true, it indicates the caller will write the data to disk - (e.g., before reboot). So no need to further persist local history.*/ - const bool dataSavedToDisk, vector* proto); - - /* Check if we should send a broadcast if approaching memory limits and if we're over, we - * actually delete the data. */ - void flushIfNecessaryLocked(const ConfigKey& key, MetricsManager& metricsManager); - - // Maps the isolated uid in the log event to host uid if the log event contains uid fields. - void mapIsolatedUidToHostUidIfNecessaryLocked(LogEvent* event) const; - - // Handler over the isolated uid change event. - void onIsolatedUidChangedEventLocked(const LogEvent& event); - - // Handler over the binary push state changed event. - void onBinaryPushStateChangedEventLocked(LogEvent* event); - - // Handler over the watchdog rollback occurred event. - void onWatchdogRollbackOccurredLocked(LogEvent* event); - - // Updates train info on disk based on binary push state changed info and - // write disk info into parameters. - void getAndUpdateTrainInfoOnDisk(bool is_rollback, InstallTrainInfo* trainInfoIn); - - // Gets experiment ids on disk for associated train and updates them - // depending on rollback type. Then writes them back to disk and returns - // them. - std::vector processWatchdogRollbackOccurred(const int32_t rollbackTypeIn, - const string& packageName); - - // Reset all configs. - void resetConfigsLocked(const int64_t timestampNs); - // Reset the specified configs. - void resetConfigsLocked(const int64_t timestampNs, const std::vector& configs); - - // An anomaly alarm should have fired. - // Check with anomaly alarm manager to find the alarms and process the result. - void informAnomalyAlarmFiredLocked(const int64_t elapsedTimeMillis); - - /* Tells MetricsManager that the alarms in alarmSet have fired. Modifies anomaly alarmSet. */ - void processFiredAnomalyAlarmsLocked( - const int64_t& timestampNs, - unordered_set, SpHash> alarmSet); - - // Function used to send a broadcast so that receiver for the config key can call getData - // to retrieve the stored data. - std::function mSendBroadcast; - - // Function used to send a broadcast so that receiver can be notified of which configs - // are currently active. - std::function& configIds)> mSendActivationBroadcast; - - const int64_t mTimeBaseNs; - - // Largest timestamp of the events that we have processed. - int64_t mLargestTimestampSeen = 0; - - int64_t mLastTimestampSeen = 0; - - int64_t mLastPullerCacheClearTimeSec = 0; - - // Last time we wrote data to disk. - int64_t mLastWriteTimeNs = 0; - - // Last time we wrote active metrics to disk. - int64_t mLastActiveMetricsWriteNs = 0; - - //Last time we wrote metadata to disk. - int64_t mLastMetadataWriteNs = 0; - - // The time for the next anomaly alarm for alerts. - int64_t mNextAnomalyAlarmTime = 0; - - bool mPrintAllLogs = false; - - FRIEND_TEST(StatsLogProcessorTest, TestOutOfOrderLogs); - FRIEND_TEST(StatsLogProcessorTest, TestRateLimitByteSize); - FRIEND_TEST(StatsLogProcessorTest, TestRateLimitBroadcast); - FRIEND_TEST(StatsLogProcessorTest, TestDropWhenByteSizeTooLarge); - FRIEND_TEST(StatsLogProcessorTest, InvalidConfigRemoved); - FRIEND_TEST(StatsLogProcessorTest, TestActiveConfigMetricDiskWriteRead); - FRIEND_TEST(StatsLogProcessorTest, TestActivationOnBoot); - FRIEND_TEST(StatsLogProcessorTest, TestActivationOnBootMultipleActivations); - FRIEND_TEST(StatsLogProcessorTest, - TestActivationOnBootMultipleActivationsDifferentActivationTypes); - FRIEND_TEST(StatsLogProcessorTest, TestActivationsPersistAcrossSystemServerRestart); - - FRIEND_TEST(WakelockDurationE2eTest, TestAggregatedPredicateDimensionsForSumDuration1); - FRIEND_TEST(WakelockDurationE2eTest, TestAggregatedPredicateDimensionsForSumDuration2); - FRIEND_TEST(WakelockDurationE2eTest, TestAggregatedPredicateDimensionsForSumDuration3); - FRIEND_TEST(WakelockDurationE2eTest, TestAggregatedPredicateDimensionsForMaxDuration1); - FRIEND_TEST(WakelockDurationE2eTest, TestAggregatedPredicateDimensionsForMaxDuration2); - FRIEND_TEST(WakelockDurationE2eTest, TestAggregatedPredicateDimensionsForMaxDuration3); - FRIEND_TEST(MetricConditionLinkE2eTest, TestMultiplePredicatesAndLinks1); - FRIEND_TEST(MetricConditionLinkE2eTest, TestMultiplePredicatesAndLinks2); - FRIEND_TEST(AttributionE2eTest, TestAttributionMatchAndSliceByFirstUid); - FRIEND_TEST(AttributionE2eTest, TestAttributionMatchAndSliceByChain); - FRIEND_TEST(GaugeMetricE2eTest, TestMultipleFieldsForPushedEvent); - FRIEND_TEST(GaugeMetricE2eTest, TestRandomSamplePulledEvents); - FRIEND_TEST(GaugeMetricE2eTest, TestRandomSamplePulledEvent_LateAlarm); - FRIEND_TEST(GaugeMetricE2eTest, TestRandomSamplePulledEventsWithActivation); - FRIEND_TEST(GaugeMetricE2eTest, TestRandomSamplePulledEventsNoCondition); - FRIEND_TEST(GaugeMetricE2eTest, TestConditionChangeToTrueSamplePulledEvents); - - FRIEND_TEST(AnomalyDetectionE2eTest, TestSlicedCountMetric_single_bucket); - FRIEND_TEST(AnomalyDetectionE2eTest, TestSlicedCountMetric_multiple_buckets); - FRIEND_TEST(AnomalyDetectionE2eTest, TestCountMetric_save_refractory_to_disk_no_data_written); - FRIEND_TEST(AnomalyDetectionE2eTest, TestCountMetric_save_refractory_to_disk); - FRIEND_TEST(AnomalyDetectionE2eTest, TestCountMetric_load_refractory_from_disk); - FRIEND_TEST(AnomalyDetectionE2eTest, TestDurationMetric_SUM_single_bucket); - FRIEND_TEST(AnomalyDetectionE2eTest, TestDurationMetric_SUM_partial_bucket); - FRIEND_TEST(AnomalyDetectionE2eTest, TestDurationMetric_SUM_multiple_buckets); - FRIEND_TEST(AnomalyDetectionE2eTest, TestDurationMetric_SUM_long_refractory_period); - - FRIEND_TEST(AlarmE2eTest, TestMultipleAlarms); - FRIEND_TEST(ConfigTtlE2eTest, TestCountMetric); - FRIEND_TEST(MetricActivationE2eTest, TestCountMetric); - FRIEND_TEST(MetricActivationE2eTest, TestCountMetricWithOneDeactivation); - FRIEND_TEST(MetricActivationE2eTest, TestCountMetricWithTwoDeactivations); - FRIEND_TEST(MetricActivationE2eTest, TestCountMetricWithSameDeactivation); - FRIEND_TEST(MetricActivationE2eTest, TestCountMetricWithTwoMetricsTwoDeactivations); - - FRIEND_TEST(ConfigUpdateE2eTest, TestAlarms); - FRIEND_TEST(ConfigUpdateE2eTest, TestGaugeMetric); - FRIEND_TEST(ConfigUpdateE2eTest, TestValueMetric); - FRIEND_TEST(ConfigUpdateE2eTest, TestAnomalyDurationMetric); - FRIEND_TEST(ConfigUpdateE2eAbTest, TestHashStrings); - FRIEND_TEST(ConfigUpdateE2eAbTest, TestUidMapVersionStringInstaller); - FRIEND_TEST(ConfigUpdateE2eAbTest, TestConfigTtl); - - FRIEND_TEST(CountMetricE2eTest, TestInitialConditionChanges); - FRIEND_TEST(CountMetricE2eTest, TestSlicedState); - FRIEND_TEST(CountMetricE2eTest, TestSlicedStateWithMap); - FRIEND_TEST(CountMetricE2eTest, TestMultipleSlicedStates); - FRIEND_TEST(CountMetricE2eTest, TestSlicedStateWithPrimaryFields); - - FRIEND_TEST(DurationMetricE2eTest, TestOneBucket); - FRIEND_TEST(DurationMetricE2eTest, TestTwoBuckets); - FRIEND_TEST(DurationMetricE2eTest, TestWithActivation); - FRIEND_TEST(DurationMetricE2eTest, TestWithCondition); - FRIEND_TEST(DurationMetricE2eTest, TestWithSlicedCondition); - FRIEND_TEST(DurationMetricE2eTest, TestWithActivationAndSlicedCondition); - FRIEND_TEST(DurationMetricE2eTest, TestWithSlicedState); - FRIEND_TEST(DurationMetricE2eTest, TestWithConditionAndSlicedState); - FRIEND_TEST(DurationMetricE2eTest, TestWithSlicedStateMapped); - FRIEND_TEST(DurationMetricE2eTest, TestSlicedStatePrimaryFieldsNotSubsetDimInWhat); - FRIEND_TEST(DurationMetricE2eTest, TestWithSlicedStatePrimaryFieldsSubset); - FRIEND_TEST(DurationMetricE2eTest, TestUploadThreshold); - - FRIEND_TEST(ValueMetricE2eTest, TestInitialConditionChanges); - FRIEND_TEST(ValueMetricE2eTest, TestPulledEvents); - FRIEND_TEST(ValueMetricE2eTest, TestPulledEvents_LateAlarm); - FRIEND_TEST(ValueMetricE2eTest, TestPulledEvents_WithActivation); - FRIEND_TEST(ValueMetricE2eTest, TestInitWithSlicedState); - FRIEND_TEST(ValueMetricE2eTest, TestInitWithSlicedState_WithDimensions); - FRIEND_TEST(ValueMetricE2eTest, TestInitWithSlicedState_WithIncorrectDimensions); -}; - -} // namespace statsd -} // namespace os -} // namespace android diff --git a/bin/src/StatsService.cpp b/bin/src/StatsService.cpp deleted file mode 100644 index 10f2647c..00000000 --- a/bin/src/StatsService.cpp +++ /dev/null @@ -1,1330 +0,0 @@ -/* - * 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. - */ - -#define DEBUG false // STOPSHIP if true -#include "Log.h" - -#include "StatsService.h" -#include "stats_log_util.h" -#include "android-base/stringprintf.h" -#include "config/ConfigKey.h" -#include "config/ConfigManager.h" -#include "guardrail/StatsdStats.h" -#include "storage/StorageManager.h" -#include "subscriber/SubscriberReporter.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -using namespace android; - -using android::base::StringPrintf; -using android::util::FIELD_COUNT_REPEATED; -using android::util::FIELD_TYPE_MESSAGE; - -using Status = ::ndk::ScopedAStatus; - -namespace android { -namespace os { -namespace statsd { - -constexpr const char* kPermissionDump = "android.permission.DUMP"; - -constexpr const char* kPermissionRegisterPullAtom = "android.permission.REGISTER_STATS_PULL_ATOM"; - -#define STATS_SERVICE_DIR "/data/misc/stats-service" - -// for StatsDataDumpProto -const int FIELD_ID_REPORTS_LIST = 1; - -static Status exception(int32_t code, const std::string& msg) { - ALOGE("%s (%d)", msg.c_str(), code); - return Status::fromExceptionCodeWithMessage(code, msg.c_str()); -} - -static bool checkPermission(const char* permission) { - pid_t pid = AIBinder_getCallingPid(); - uid_t uid = AIBinder_getCallingUid(); - return checkPermissionForIds(permission, pid, uid); -} - -Status checkUid(uid_t expectedUid) { - uid_t uid = AIBinder_getCallingUid(); - if (uid == expectedUid || uid == AID_ROOT) { - return Status::ok(); - } else { - return exception(EX_SECURITY, - StringPrintf("UID %d is not expected UID %d", uid, expectedUid)); - } -} - -#define ENFORCE_UID(uid) { \ - Status status = checkUid((uid)); \ - if (!status.isOk()) { \ - return status; \ - } \ -} - -StatsService::StatsService(const sp& handlerLooper, shared_ptr queue) - : mAnomalyAlarmMonitor(new AlarmMonitor( - MIN_DIFF_TO_UPDATE_REGISTERED_ALARM_SECS, - [this](const shared_ptr& /*sc*/, int64_t timeMillis) { - mProcessor->setAnomalyAlarm(timeMillis); - StatsdStats::getInstance().noteRegisteredAnomalyAlarmChanged(); - }, - [this](const shared_ptr& /*sc*/) { - mProcessor->cancelAnomalyAlarm(); - StatsdStats::getInstance().noteRegisteredAnomalyAlarmChanged(); - })), - mPeriodicAlarmMonitor(new AlarmMonitor( - MIN_DIFF_TO_UPDATE_REGISTERED_ALARM_SECS, - [](const shared_ptr& sc, int64_t timeMillis) { - if (sc != nullptr) { - sc->setAlarmForSubscriberTriggering(timeMillis); - StatsdStats::getInstance().noteRegisteredPeriodicAlarmChanged(); - } - }, - [](const shared_ptr& sc) { - if (sc != nullptr) { - sc->cancelAlarmForSubscriberTriggering(); - StatsdStats::getInstance().noteRegisteredPeriodicAlarmChanged(); - } - })), - mEventQueue(queue), - mBootCompleteTrigger({kBootCompleteTag, kUidMapReceivedTag, kAllPullersRegisteredTag}, - [this]() { mProcessor->onStatsdInitCompleted(getElapsedRealtimeNs()); }), - mStatsCompanionServiceDeathRecipient( - AIBinder_DeathRecipient_new(StatsService::statsCompanionServiceDied)) { - mUidMap = UidMap::getInstance(); - mPullerManager = new StatsPullerManager(); - StatsPuller::SetUidMap(mUidMap); - mConfigManager = new ConfigManager(); - mProcessor = new StatsLogProcessor( - mUidMap, mPullerManager, mAnomalyAlarmMonitor, mPeriodicAlarmMonitor, - getElapsedRealtimeNs(), - [this](const ConfigKey& key) { - shared_ptr receiver = mConfigManager->GetConfigReceiver(key); - if (receiver == nullptr) { - VLOG("Could not find a broadcast receiver for %s", key.ToString().c_str()); - return false; - } else if (receiver->sendDataBroadcast( - mProcessor->getLastReportTimeNs(key)).isOk()) { - return true; - } else { - VLOG("Failed to send a broadcast for receiver %s", key.ToString().c_str()); - return false; - } - }, - [this](const int& uid, const vector& activeConfigs) { - shared_ptr receiver = - mConfigManager->GetActiveConfigsChangedReceiver(uid); - if (receiver == nullptr) { - VLOG("Could not find receiver for uid %d", uid); - return false; - } else if (receiver->sendActiveConfigsChangedBroadcast(activeConfigs).isOk()) { - VLOG("StatsService::active configs broadcast succeeded for uid %d" , uid); - return true; - } else { - VLOG("StatsService::active configs broadcast failed for uid %d" , uid); - return false; - } - }); - - mUidMap->setListener(mProcessor); - mConfigManager->AddListener(mProcessor); - - init_system_properties(); - - if (mEventQueue != nullptr) { - std::thread pushedEventThread([this] { readLogs(); }); - pushedEventThread.detach(); - } -} - -StatsService::~StatsService() { -} - -/* Runs on a dedicated thread to process pushed events. */ -void StatsService::readLogs() { - // Read forever..... long live statsd - while (1) { - // Block until an event is available. - auto event = mEventQueue->waitPop(); - // Pass it to StatsLogProcess to all configs/metrics - // At this point, the LogEventQueue is not blocked, so that the socketListener - // can read events from the socket and write to buffer to avoid data drop. - mProcessor->OnLogEvent(event.get()); - // The ShellSubscriber is only used by shell for local debugging. - if (mShellSubscriber != nullptr) { - mShellSubscriber->onLogEvent(*event); - } - } -} - -void StatsService::init_system_properties() { - mEngBuild = false; - const prop_info* buildType = __system_property_find("ro.build.type"); - if (buildType != NULL) { - __system_property_read_callback(buildType, init_build_type_callback, this); - } -} - -void StatsService::init_build_type_callback(void* cookie, const char* /*name*/, const char* value, - uint32_t serial) { - if (0 == strcmp("eng", value) || 0 == strcmp("userdebug", value)) { - reinterpret_cast(cookie)->mEngBuild = true; - } -} - -/** - * Write data from statsd. - * Format for statsdStats: adb shell dumpsys stats --metadata [-v] [--proto] - * Format for data report: adb shell dumpsys stats [anything other than --metadata] [--proto] - * Anything ending in --proto will be in proto format. - * Anything without --metadata as the first argument will be report information. - * (bugreports call "adb shell dumpsys stats --dump-priority NORMAL -a --proto") - * TODO: Come up with a more robust method of enacting . - */ -status_t StatsService::dump(int fd, const char** args, uint32_t numArgs) { - if (!checkPermission(kPermissionDump)) { - return PERMISSION_DENIED; - } - - int lastArg = numArgs - 1; - bool asProto = false; - if (lastArg >= 0 && string(args[lastArg]) == "--proto") { // last argument - asProto = true; - lastArg--; - } - if (numArgs > 0 && string(args[0]) == "--metadata") { // first argument - // Request is to dump statsd stats. - bool verbose = false; - if (lastArg >= 0 && string(args[lastArg]) == "-v") { - verbose = true; - lastArg--; - } - dumpStatsdStats(fd, verbose, asProto); - } else { - // Request is to dump statsd report data. - if (asProto) { - dumpIncidentSection(fd); - } else { - dprintf(fd, "Non-proto format of stats data dump not available; see proto version.\n"); - } - } - - return NO_ERROR; -} - -/** - * Write debugging data about statsd in text or proto format. - */ -void StatsService::dumpStatsdStats(int out, bool verbose, bool proto) { - if (proto) { - vector data; - StatsdStats::getInstance().dumpStats(&data, false); // does not reset statsdStats. - for (size_t i = 0; i < data.size(); i ++) { - dprintf(out, "%c", data[i]); - } - } else { - StatsdStats::getInstance().dumpStats(out); - mProcessor->dumpStates(out, verbose); - } -} - -/** - * Write stats report data in StatsDataDumpProto incident section format. - */ -void StatsService::dumpIncidentSection(int out) { - ProtoOutputStream proto; - for (const ConfigKey& configKey : mConfigManager->GetAllConfigKeys()) { - uint64_t reportsListToken = - proto.start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_REPORTS_LIST); - // Don't include the current bucket to avoid skipping buckets. - // If we need to include the current bucket later, consider changing to NO_TIME_CONSTRAINTS - // or other alternatives to avoid skipping buckets for pulled metrics. - mProcessor->onDumpReport(configKey, getElapsedRealtimeNs(), - false /* includeCurrentBucket */, false /* erase_data */, - ADB_DUMP, - FAST, - &proto); - proto.end(reportsListToken); - proto.flush(out); - proto.clear(); - } -} - -/** - * Implementation of the adb shell cmd stats command. - */ -status_t StatsService::handleShellCommand(int in, int out, int err, const char** argv, - uint32_t argc) { - uid_t uid = AIBinder_getCallingUid(); - if (uid != AID_ROOT && uid != AID_SHELL) { - return PERMISSION_DENIED; - } - - Vector utf8Args; - utf8Args.setCapacity(argc); - for (uint32_t i = 0; i < argc; i++) { - utf8Args.push(String8(argv[i])); - } - - if (argc >= 1) { - // adb shell cmd stats config ... - if (!utf8Args[0].compare(String8("config"))) { - return cmd_config(in, out, err, utf8Args); - } - - if (!utf8Args[0].compare(String8("print-uid-map"))) { - return cmd_print_uid_map(out, utf8Args); - } - - if (!utf8Args[0].compare(String8("dump-report"))) { - return cmd_dump_report(out, utf8Args); - } - - if (!utf8Args[0].compare(String8("pull-source")) && argc > 1) { - return cmd_print_pulled_metrics(out, utf8Args); - } - - if (!utf8Args[0].compare(String8("send-broadcast"))) { - return cmd_trigger_broadcast(out, utf8Args); - } - - if (!utf8Args[0].compare(String8("print-stats"))) { - return cmd_print_stats(out, utf8Args); - } - - if (!utf8Args[0].compare(String8("meminfo"))) { - return cmd_dump_memory_info(out); - } - - if (!utf8Args[0].compare(String8("write-to-disk"))) { - return cmd_write_data_to_disk(out); - } - - if (!utf8Args[0].compare(String8("log-app-breadcrumb"))) { - return cmd_log_app_breadcrumb(out, utf8Args); - } - - if (!utf8Args[0].compare(String8("log-binary-push"))) { - return cmd_log_binary_push(out, utf8Args); - } - - if (!utf8Args[0].compare(String8("clear-puller-cache"))) { - return cmd_clear_puller_cache(out); - } - - if (!utf8Args[0].compare(String8("print-logs"))) { - return cmd_print_logs(out, utf8Args); - } - - if (!utf8Args[0].compare(String8("send-active-configs"))) { - return cmd_trigger_active_config_broadcast(out, utf8Args); - } - - if (!utf8Args[0].compare(String8("data-subscribe"))) { - { - std::lock_guard lock(mShellSubscriberMutex); - if (mShellSubscriber == nullptr) { - mShellSubscriber = new ShellSubscriber(mUidMap, mPullerManager); - } - } - int timeoutSec = -1; - if (argc >= 2) { - timeoutSec = atoi(utf8Args[1].c_str()); - } - mShellSubscriber->startNewSubscription(in, out, timeoutSec); - return NO_ERROR; - } - } - - print_cmd_help(out); - return NO_ERROR; -} - -void StatsService::print_cmd_help(int out) { - dprintf(out, - "usage: adb shell cmd stats print-stats-log [tag_required] " - "[timestamp_nsec_optional]\n"); - dprintf(out, "\n"); - dprintf(out, "\n"); - dprintf(out, "usage: adb shell cmd stats meminfo\n"); - dprintf(out, "\n"); - dprintf(out, " Prints the malloc debug information. You need to run the following first: \n"); - dprintf(out, " # adb shell stop\n"); - dprintf(out, " # adb shell setprop libc.debug.malloc.program statsd \n"); - dprintf(out, " # adb shell setprop libc.debug.malloc.options backtrace \n"); - dprintf(out, " # adb shell start\n"); - dprintf(out, "\n"); - dprintf(out, "\n"); - dprintf(out, "usage: adb shell cmd stats print-uid-map [PKG]\n"); - dprintf(out, "\n"); - dprintf(out, " Prints the UID, app name, version mapping.\n"); - dprintf(out, " PKG Optional package name to print the uids of the package\n"); - dprintf(out, "\n"); - dprintf(out, "\n"); - dprintf(out, "usage: adb shell cmd stats pull-source ATOM_TAG [PACKAGE] \n"); - dprintf(out, "\n"); - dprintf(out, " Prints the output of a pulled atom\n"); - dprintf(out, " UID The atom to pull\n"); - dprintf(out, " PACKAGE The package to pull from. Default is AID_SYSTEM\n"); - dprintf(out, "\n"); - dprintf(out, "\n"); - dprintf(out, "usage: adb shell cmd stats write-to-disk \n"); - dprintf(out, "\n"); - dprintf(out, " Flushes all data on memory to disk.\n"); - dprintf(out, "\n"); - dprintf(out, "\n"); - dprintf(out, "usage: adb shell cmd stats log-app-breadcrumb [UID] LABEL STATE\n"); - dprintf(out, " Writes an AppBreadcrumbReported event to the statslog buffer.\n"); - dprintf(out, " UID The uid to use. It is only possible to pass a UID\n"); - dprintf(out, " parameter on eng builds. If UID is omitted the calling\n"); - dprintf(out, " uid is used.\n"); - dprintf(out, " LABEL Integer in [0, 15], as per atoms.proto.\n"); - dprintf(out, " STATE Integer in [0, 3], as per atoms.proto.\n"); - dprintf(out, "\n"); - dprintf(out, "\n"); - dprintf(out, - "usage: adb shell cmd stats log-binary-push NAME VERSION STAGING ROLLBACK_ENABLED " - "LOW_LATENCY STATE EXPERIMENT_IDS\n"); - dprintf(out, " Log a binary push state changed event.\n"); - dprintf(out, " NAME The train name.\n"); - dprintf(out, " VERSION The train version code.\n"); - dprintf(out, " STAGING If this train requires a restart.\n"); - dprintf(out, " ROLLBACK_ENABLED If rollback should be enabled for this install.\n"); - dprintf(out, " LOW_LATENCY If the train requires low latency monitoring.\n"); - dprintf(out, " STATE The status of the train push.\n"); - dprintf(out, " Integer value of the enum in atoms.proto.\n"); - dprintf(out, " EXPERIMENT_IDS Comma separated list of experiment ids.\n"); - dprintf(out, " Leave blank for none.\n"); - dprintf(out, "\n"); - dprintf(out, "\n"); - dprintf(out, "usage: adb shell cmd stats config remove [UID] [NAME]\n"); - dprintf(out, "usage: adb shell cmd stats config update [UID] NAME\n"); - dprintf(out, "\n"); - dprintf(out, " Adds, updates or removes a configuration. The proto should be in\n"); - dprintf(out, " wire-encoded protobuf format and passed via stdin. If no UID and name is\n"); - dprintf(out, " provided, then all configs will be removed from memory and disk.\n"); - dprintf(out, "\n"); - dprintf(out, " UID The uid to use. It is only possible to pass the UID\n"); - dprintf(out, " parameter on eng builds. If UID is omitted the calling\n"); - dprintf(out, " uid is used.\n"); - dprintf(out, " NAME The per-uid name to use\n"); - dprintf(out, "\n"); - dprintf(out, "\n *Note: If both UID and NAME are omitted then all configs will\n"); - dprintf(out, "\n be removed from memory and disk!\n"); - dprintf(out, "\n"); - dprintf(out, - "usage: adb shell cmd stats dump-report [UID] NAME [--keep_data] " - "[--include_current_bucket] [--proto]\n"); - dprintf(out, " Dump all metric data for a configuration.\n"); - dprintf(out, " UID The uid of the configuration. It is only possible to pass\n"); - dprintf(out, " the UID parameter on eng builds. If UID is omitted the\n"); - dprintf(out, " calling uid is used.\n"); - dprintf(out, " NAME The name of the configuration\n"); - dprintf(out, " --keep_data Do NOT erase the data upon dumping it.\n"); - dprintf(out, " --proto Print proto binary.\n"); - dprintf(out, "\n"); - dprintf(out, "\n"); - dprintf(out, "usage: adb shell cmd stats send-broadcast [UID] NAME\n"); - dprintf(out, " Send a broadcast that triggers the subscriber to fetch metrics.\n"); - dprintf(out, " UID The uid of the configuration. It is only possible to pass\n"); - dprintf(out, " the UID parameter on eng builds. If UID is omitted the\n"); - dprintf(out, " calling uid is used.\n"); - dprintf(out, " NAME The name of the configuration\n"); - dprintf(out, "\n"); - dprintf(out, "\n"); - dprintf(out, - "usage: adb shell cmd stats send-active-configs [--uid=UID] [--configs] " - "[NAME1] [NAME2] [NAME3..]\n"); - dprintf(out, " Send a broadcast that informs the subscriber of the current active configs.\n"); - dprintf(out, " --uid=UID The uid of the configurations. It is only possible to pass\n"); - dprintf(out, " the UID parameter on eng builds. If UID is omitted the\n"); - dprintf(out, " calling uid is used.\n"); - dprintf(out, " --configs Send the list of configs in the name list instead of\n"); - dprintf(out, " the currently active configs\n"); - dprintf(out, " NAME LIST List of configuration names to be included in the broadcast.\n"); - dprintf(out, "\n"); - dprintf(out, "\n"); - dprintf(out, "usage: adb shell cmd stats print-stats\n"); - dprintf(out, " Prints some basic stats.\n"); - dprintf(out, " --proto Print proto binary instead of string format.\n"); - dprintf(out, "\n"); - dprintf(out, "\n"); - dprintf(out, "usage: adb shell cmd stats clear-puller-cache\n"); - dprintf(out, " Clear cached puller data.\n"); - dprintf(out, "\n"); - dprintf(out, "usage: adb shell cmd stats print-logs\n"); - dprintf(out, " Requires root privileges.\n"); - dprintf(out, " Can be disabled by calling adb shell cmd stats print-logs 0\n"); -} - -status_t StatsService::cmd_trigger_broadcast(int out, Vector& args) { - string name; - bool good = false; - int uid; - const int argCount = args.size(); - if (argCount == 2) { - // Automatically pick the UID - uid = AIBinder_getCallingUid(); - name.assign(args[1].c_str(), args[1].size()); - good = true; - } else if (argCount == 3) { - good = getUidFromArgs(args, 1, uid); - if (!good) { - dprintf(out, "Invalid UID. Note that the metrics can only be dumped for " - "other UIDs on eng or userdebug builds.\n"); - } - name.assign(args[2].c_str(), args[2].size()); - } - if (!good) { - print_cmd_help(out); - return UNKNOWN_ERROR; - } - ConfigKey key(uid, StrToInt64(name)); - shared_ptr receiver = mConfigManager->GetConfigReceiver(key); - if (receiver == nullptr) { - VLOG("Could not find receiver for %s, %s", args[1].c_str(), args[2].c_str()); - return UNKNOWN_ERROR; - } else if (receiver->sendDataBroadcast(mProcessor->getLastReportTimeNs(key)).isOk()) { - VLOG("StatsService::trigger broadcast succeeded to %s, %s", args[1].c_str(), - args[2].c_str()); - } else { - VLOG("StatsService::trigger broadcast failed to %s, %s", args[1].c_str(), args[2].c_str()); - return UNKNOWN_ERROR; - } - return NO_ERROR; -} - -status_t StatsService::cmd_trigger_active_config_broadcast(int out, Vector& args) { - const int argCount = args.size(); - int uid; - vector configIds; - if (argCount == 1) { - // Automatically pick the uid and send a broadcast that has no active configs. - uid = AIBinder_getCallingUid(); - mProcessor->GetActiveConfigs(uid, configIds); - } else { - int curArg = 1; - if(args[curArg].find("--uid=") == 0) { - string uidArgStr(args[curArg].c_str()); - string uidStr = uidArgStr.substr(6); - if (!getUidFromString(uidStr.c_str(), uid)) { - dprintf(out, "Invalid UID. Note that the config can only be set for " - "other UIDs on eng or userdebug builds.\n"); - return UNKNOWN_ERROR; - } - curArg++; - } else { - uid = AIBinder_getCallingUid(); - } - if (curArg == argCount || args[curArg] != "--configs") { - VLOG("Reached end of args, or specify configs not set. Sending actual active configs,"); - mProcessor->GetActiveConfigs(uid, configIds); - } else { - // Flag specified, use the given list of configs. - curArg++; - for (int i = curArg; i < argCount; i++) { - char* endp; - int64_t configID = strtoll(args[i].c_str(), &endp, 10); - if (endp == args[i].c_str() || *endp != '\0') { - dprintf(out, "Error parsing config ID.\n"); - return UNKNOWN_ERROR; - } - VLOG("Adding config id %ld", static_cast(configID)); - configIds.push_back(configID); - } - } - } - shared_ptr receiver = mConfigManager->GetActiveConfigsChangedReceiver(uid); - if (receiver == nullptr) { - VLOG("Could not find receiver for uid %d", uid); - return UNKNOWN_ERROR; - } else if (receiver->sendActiveConfigsChangedBroadcast(configIds).isOk()) { - VLOG("StatsService::trigger active configs changed broadcast succeeded for uid %d" , uid); - } else { - VLOG("StatsService::trigger active configs changed broadcast failed for uid %d", uid); - return UNKNOWN_ERROR; - } - return NO_ERROR; -} - -status_t StatsService::cmd_config(int in, int out, int err, Vector& args) { - const int argCount = args.size(); - if (argCount >= 2) { - if (args[1] == "update" || args[1] == "remove") { - bool good = false; - int uid = -1; - string name; - - if (argCount == 3) { - // Automatically pick the UID - uid = AIBinder_getCallingUid(); - name.assign(args[2].c_str(), args[2].size()); - good = true; - } else if (argCount == 4) { - good = getUidFromArgs(args, 2, uid); - if (!good) { - dprintf(err, "Invalid UID. Note that the config can only be set for " - "other UIDs on eng or userdebug builds.\n"); - } - name.assign(args[3].c_str(), args[3].size()); - } else if (argCount == 2 && args[1] == "remove") { - good = true; - } - - if (!good) { - // If arg parsing failed, print the help text and return an error. - print_cmd_help(out); - return UNKNOWN_ERROR; - } - - if (args[1] == "update") { - char* endp; - int64_t configID = strtoll(name.c_str(), &endp, 10); - if (endp == name.c_str() || *endp != '\0') { - dprintf(err, "Error parsing config ID.\n"); - return UNKNOWN_ERROR; - } - - // Read stream into buffer. - string buffer; - if (!android::base::ReadFdToString(in, &buffer)) { - dprintf(err, "Error reading stream for StatsConfig.\n"); - return UNKNOWN_ERROR; - } - - // Parse buffer. - StatsdConfig config; - if (!config.ParseFromString(buffer)) { - dprintf(err, "Error parsing proto stream for StatsConfig.\n"); - return UNKNOWN_ERROR; - } - - // Add / update the config. - mConfigManager->UpdateConfig(ConfigKey(uid, configID), config); - } else { - if (argCount == 2) { - cmd_remove_all_configs(out); - } else { - // Remove the config. - mConfigManager->RemoveConfig(ConfigKey(uid, StrToInt64(name))); - } - } - - return NO_ERROR; - } - } - print_cmd_help(out); - return UNKNOWN_ERROR; -} - -status_t StatsService::cmd_dump_report(int out, const Vector& args) { - if (mProcessor != nullptr) { - int argCount = args.size(); - bool good = false; - bool proto = false; - bool includeCurrentBucket = false; - bool eraseData = true; - int uid; - string name; - if (!std::strcmp("--proto", args[argCount-1].c_str())) { - proto = true; - argCount -= 1; - } - if (!std::strcmp("--include_current_bucket", args[argCount-1].c_str())) { - includeCurrentBucket = true; - argCount -= 1; - } - if (!std::strcmp("--keep_data", args[argCount-1].c_str())) { - eraseData = false; - argCount -= 1; - } - if (argCount == 2) { - // Automatically pick the UID - uid = AIBinder_getCallingUid(); - name.assign(args[1].c_str(), args[1].size()); - good = true; - } else if (argCount == 3) { - good = getUidFromArgs(args, 1, uid); - if (!good) { - dprintf(out, "Invalid UID. Note that the metrics can only be dumped for " - "other UIDs on eng or userdebug builds.\n"); - } - name.assign(args[2].c_str(), args[2].size()); - } - if (good) { - vector data; - mProcessor->onDumpReport(ConfigKey(uid, StrToInt64(name)), getElapsedRealtimeNs(), - includeCurrentBucket, eraseData, ADB_DUMP, - NO_TIME_CONSTRAINTS, - &data); - if (proto) { - for (size_t i = 0; i < data.size(); i ++) { - dprintf(out, "%c", data[i]); - } - } else { - dprintf(out, "Non-proto stats data dump not currently supported.\n"); - } - return android::OK; - } else { - // If arg parsing failed, print the help text and return an error. - print_cmd_help(out); - return UNKNOWN_ERROR; - } - } else { - dprintf(out, "Log processor does not exist...\n"); - return UNKNOWN_ERROR; - } -} - -status_t StatsService::cmd_print_stats(int out, const Vector& args) { - int argCount = args.size(); - bool proto = false; - if (!std::strcmp("--proto", args[argCount-1].c_str())) { - proto = true; - argCount -= 1; - } - StatsdStats& statsdStats = StatsdStats::getInstance(); - if (proto) { - vector data; - statsdStats.dumpStats(&data, false); // does not reset statsdStats. - for (size_t i = 0; i < data.size(); i ++) { - dprintf(out, "%c", data[i]); - } - - } else { - vector configs = mConfigManager->GetAllConfigKeys(); - for (const ConfigKey& key : configs) { - dprintf(out, "Config %s uses %zu bytes\n", key.ToString().c_str(), - mProcessor->GetMetricsSize(key)); - } - statsdStats.dumpStats(out); - } - return NO_ERROR; -} - -status_t StatsService::cmd_print_uid_map(int out, const Vector& args) { - if (args.size() > 1) { - string pkg; - pkg.assign(args[1].c_str(), args[1].size()); - auto uids = mUidMap->getAppUid(pkg); - dprintf(out, "%s -> [ ", pkg.c_str()); - for (const auto& uid : uids) { - dprintf(out, "%d ", uid); - } - dprintf(out, "]\n"); - } else { - mUidMap->printUidMap(out); - } - return NO_ERROR; -} - -status_t StatsService::cmd_write_data_to_disk(int out) { - dprintf(out, "Writing data to disk\n"); - mProcessor->WriteDataToDisk(ADB_DUMP, NO_TIME_CONSTRAINTS, getElapsedRealtimeNs()); - return NO_ERROR; -} - -status_t StatsService::cmd_log_app_breadcrumb(int out, const Vector& args) { - bool good = false; - int32_t uid; - int32_t label; - int32_t state; - const int argCount = args.size(); - if (argCount == 3) { - // Automatically pick the UID - uid = AIBinder_getCallingUid(); - label = atoi(args[1].c_str()); - state = atoi(args[2].c_str()); - good = true; - } else if (argCount == 4) { - good = getUidFromArgs(args, 1, uid); - if (!good) { - dprintf(out, - "Invalid UID. Note that selecting a UID for writing AppBreadcrumb can only be " - "done for other UIDs on eng or userdebug builds.\n"); - } - label = atoi(args[2].c_str()); - state = atoi(args[3].c_str()); - } - if (good) { - dprintf(out, "Logging AppBreadcrumbReported(%d, %d, %d) to statslog.\n", uid, label, state); - android::os::statsd::util::stats_write( - android::os::statsd::util::APP_BREADCRUMB_REPORTED, uid, label, state); - } else { - print_cmd_help(out); - return UNKNOWN_ERROR; - } - return NO_ERROR; -} - -status_t StatsService::cmd_log_binary_push(int out, const Vector& args) { - // Security checks are done in the sendBinaryPushStateChanged atom. - const int argCount = args.size(); - if (argCount != 7 && argCount != 8) { - dprintf(out, "Incorrect number of argument supplied\n"); - return UNKNOWN_ERROR; - } - string trainName = string(args[1].c_str()); - int64_t trainVersion = strtoll(args[2].c_str(), nullptr, 10); - int32_t state = atoi(args[6].c_str()); - vector experimentIds; - if (argCount == 8) { - vector experimentIdsString = android::base::Split(string(args[7].c_str()), ","); - for (string experimentIdString : experimentIdsString) { - int64_t experimentId = strtoll(experimentIdString.c_str(), nullptr, 10); - experimentIds.push_back(experimentId); - } - } - dprintf(out, "Logging BinaryPushStateChanged\n"); - vector experimentIdBytes; - writeExperimentIdsToProto(experimentIds, &experimentIdBytes); - LogEvent event(trainName, trainVersion, args[3], args[4], args[5], state, experimentIdBytes, 0); - mProcessor->OnLogEvent(&event); - return NO_ERROR; -} - -status_t StatsService::cmd_print_pulled_metrics(int out, const Vector& args) { - int s = atoi(args[1].c_str()); - vector uids; - if (args.size() > 2) { - string package = string(args[2].c_str()); - auto it = UidMap::sAidToUidMapping.find(package); - if (it != UidMap::sAidToUidMapping.end()) { - uids.push_back(it->second); - } else { - set uids_set = mUidMap->getAppUid(package); - uids.insert(uids.end(), uids_set.begin(), uids_set.end()); - } - } else { - uids.push_back(AID_SYSTEM); - } - vector> stats; - if (mPullerManager->Pull(s, uids, getElapsedRealtimeNs(), &stats)) { - for (const auto& it : stats) { - dprintf(out, "Pull from %d: %s\n", s, it->ToString().c_str()); - } - dprintf(out, "Pull from %d: Received %zu elements\n", s, stats.size()); - return NO_ERROR; - } - return UNKNOWN_ERROR; -} - -status_t StatsService::cmd_remove_all_configs(int out) { - dprintf(out, "Removing all configs...\n"); - VLOG("StatsService::cmd_remove_all_configs was called"); - mConfigManager->RemoveAllConfigs(); - StorageManager::deleteAllFiles(STATS_SERVICE_DIR); - return NO_ERROR; -} - -status_t StatsService::cmd_dump_memory_info(int out) { - dprintf(out, "meminfo not available.\n"); - return NO_ERROR; -} - -status_t StatsService::cmd_clear_puller_cache(int out) { - VLOG("StatsService::cmd_clear_puller_cache with Pid %i, Uid %i", - AIBinder_getCallingPid(), AIBinder_getCallingUid()); - if (checkPermission(kPermissionDump)) { - int cleared = mPullerManager->ForceClearPullerCache(); - dprintf(out, "Puller removed %d cached data!\n", cleared); - return NO_ERROR; - } else { - return PERMISSION_DENIED; - } -} - -status_t StatsService::cmd_print_logs(int out, const Vector& args) { - Status status = checkUid(AID_ROOT); - if (!status.isOk()) { - return PERMISSION_DENIED; - } - - VLOG("StatsService::cmd_print_logs with pid %i, uid %i", AIBinder_getCallingPid(), - AIBinder_getCallingUid()); - bool enabled = true; - if (args.size() >= 2) { - enabled = atoi(args[1].c_str()) != 0; - } - mProcessor->setPrintLogs(enabled); - return NO_ERROR; -} - -bool StatsService::getUidFromArgs(const Vector& args, size_t uidArgIndex, int32_t& uid) { - return getUidFromString(args[uidArgIndex].c_str(), uid); -} - -bool StatsService::getUidFromString(const char* s, int32_t& uid) { - if (*s == '\0') { - return false; - } - char* endc = NULL; - int64_t longUid = strtol(s, &endc, 0); - if (*endc != '\0') { - return false; - } - int32_t goodUid = static_cast(longUid); - if (longUid < 0 || static_cast(longUid) != static_cast(goodUid)) { - return false; // It was not of uid_t type. - } - uid = goodUid; - - int32_t callingUid = AIBinder_getCallingUid(); - return mEngBuild // UserDebug/EngBuild are allowed to impersonate uids. - || (callingUid == goodUid) // Anyone can 'impersonate' themselves. - || (callingUid == AID_ROOT && goodUid == AID_SHELL); // ROOT can impersonate SHELL. -} - -Status StatsService::informAllUidData(const ScopedFileDescriptor& fd) { - ENFORCE_UID(AID_SYSTEM); - // Read stream into buffer. - string buffer; - if (!android::base::ReadFdToString(fd.get(), &buffer)) { - return exception(EX_ILLEGAL_ARGUMENT, "Failed to read all data from the pipe."); - } - - // Parse buffer. - UidData uidData; - if (!uidData.ParseFromString(buffer)) { - return exception(EX_ILLEGAL_ARGUMENT, "Error parsing proto stream for UidData."); - } - - vector versionStrings; - vector installers; - vector packageNames; - vector uids; - vector versions; - - const auto numEntries = uidData.app_info_size(); - versionStrings.reserve(numEntries); - installers.reserve(numEntries); - packageNames.reserve(numEntries); - uids.reserve(numEntries); - versions.reserve(numEntries); - - for (const auto& appInfo: uidData.app_info()) { - packageNames.emplace_back(String16(appInfo.package_name().c_str())); - uids.push_back(appInfo.uid()); - versions.push_back(appInfo.version()); - versionStrings.emplace_back(String16(appInfo.version_string().c_str())); - installers.emplace_back(String16(appInfo.installer().c_str())); - } - - mUidMap->updateMap(getElapsedRealtimeNs(), - uids, - versions, - versionStrings, - packageNames, - installers); - - mBootCompleteTrigger.markComplete(kUidMapReceivedTag); - VLOG("StatsService::informAllUidData UidData proto parsed successfully."); - return Status::ok(); -} - -Status StatsService::informOnePackage(const string& app, int32_t uid, int64_t version, - const string& versionString, const string& installer) { - ENFORCE_UID(AID_SYSTEM); - - VLOG("StatsService::informOnePackage was called"); - String16 utf16App = String16(app.c_str()); - String16 utf16VersionString = String16(versionString.c_str()); - String16 utf16Installer = String16(installer.c_str()); - - mUidMap->updateApp(getElapsedRealtimeNs(), utf16App, uid, version, utf16VersionString, - utf16Installer); - return Status::ok(); -} - -Status StatsService::informOnePackageRemoved(const string& app, int32_t uid) { - ENFORCE_UID(AID_SYSTEM); - - VLOG("StatsService::informOnePackageRemoved was called"); - String16 utf16App = String16(app.c_str()); - mUidMap->removeApp(getElapsedRealtimeNs(), utf16App, uid); - mConfigManager->RemoveConfigs(uid); - return Status::ok(); -} - -Status StatsService::informAnomalyAlarmFired() { - ENFORCE_UID(AID_SYSTEM); - // Anomaly alarms are handled internally now. This code should be fully deleted. - return Status::ok(); -} - -Status StatsService::informAlarmForSubscriberTriggeringFired() { - ENFORCE_UID(AID_SYSTEM); - - VLOG("StatsService::informAlarmForSubscriberTriggeringFired was called"); - int64_t currentTimeSec = getElapsedRealtimeSec(); - std::unordered_set, SpHash> alarmSet = - mPeriodicAlarmMonitor->popSoonerThan(static_cast(currentTimeSec)); - if (alarmSet.size() > 0) { - VLOG("Found periodic alarm fired."); - mProcessor->onPeriodicAlarmFired(currentTimeSec * NS_PER_SEC, alarmSet); - } else { - ALOGW("Cannot find an periodic alarm that fired. Perhaps it was recently cancelled."); - } - return Status::ok(); -} - -Status StatsService::informPollAlarmFired() { - ENFORCE_UID(AID_SYSTEM); - - VLOG("StatsService::informPollAlarmFired was called"); - mProcessor->informPullAlarmFired(getElapsedRealtimeNs()); - VLOG("StatsService::informPollAlarmFired succeeded"); - return Status::ok(); -} - -Status StatsService::systemRunning() { - ENFORCE_UID(AID_SYSTEM); - - // When system_server is up and running, schedule the dropbox task to run. - VLOG("StatsService::systemRunning"); - sayHiToStatsCompanion(); - return Status::ok(); -} - -Status StatsService::informDeviceShutdown() { - ENFORCE_UID(AID_SYSTEM); - VLOG("StatsService::informDeviceShutdown"); - int64_t elapsedRealtimeNs = getElapsedRealtimeNs(); - mProcessor->WriteDataToDisk(DEVICE_SHUTDOWN, FAST, elapsedRealtimeNs); - mProcessor->SaveActiveConfigsToDisk(elapsedRealtimeNs); - mProcessor->SaveMetadataToDisk(getWallClockNs(), elapsedRealtimeNs); - return Status::ok(); -} - -void StatsService::sayHiToStatsCompanion() { - shared_ptr statsCompanion = getStatsCompanionService(); - if (statsCompanion != nullptr) { - VLOG("Telling statsCompanion that statsd is ready"); - statsCompanion->statsdReady(); - } else { - VLOG("Could not access statsCompanion"); - } -} - -Status StatsService::statsCompanionReady() { - ENFORCE_UID(AID_SYSTEM); - - VLOG("StatsService::statsCompanionReady was called"); - shared_ptr statsCompanion = getStatsCompanionService(); - if (statsCompanion == nullptr) { - return exception(EX_NULL_POINTER, - "StatsCompanion unavailable despite it contacting statsd."); - } - VLOG("StatsService::statsCompanionReady linking to statsCompanion."); - AIBinder_linkToDeath(statsCompanion->asBinder().get(), - mStatsCompanionServiceDeathRecipient.get(), this); - mPullerManager->SetStatsCompanionService(statsCompanion); - mAnomalyAlarmMonitor->setStatsCompanionService(statsCompanion); - mPeriodicAlarmMonitor->setStatsCompanionService(statsCompanion); - return Status::ok(); -} - -Status StatsService::bootCompleted() { - ENFORCE_UID(AID_SYSTEM); - - VLOG("StatsService::bootCompleted was called"); - mBootCompleteTrigger.markComplete(kBootCompleteTag); - return Status::ok(); -} - -void StatsService::Startup() { - mConfigManager->Startup(); - mProcessor->LoadActiveConfigsFromDisk(); - mProcessor->LoadMetadataFromDisk(getWallClockNs(), getElapsedRealtimeNs()); -} - -void StatsService::Terminate() { - ALOGI("StatsService::Terminating"); - if (mProcessor != nullptr) { - int64_t elapsedRealtimeNs = getElapsedRealtimeNs(); - mProcessor->WriteDataToDisk(TERMINATION_SIGNAL_RECEIVED, FAST, elapsedRealtimeNs); - mProcessor->SaveActiveConfigsToDisk(elapsedRealtimeNs); - mProcessor->SaveMetadataToDisk(getWallClockNs(), elapsedRealtimeNs); - } -} - -// Test only interface!!! -void StatsService::OnLogEvent(LogEvent* event) { - mProcessor->OnLogEvent(event); - if (mShellSubscriber != nullptr) { - mShellSubscriber->onLogEvent(*event); - } -} - -Status StatsService::getData(int64_t key, const int32_t callingUid, vector* output) { - ENFORCE_UID(AID_SYSTEM); - - VLOG("StatsService::getData with Uid %i", callingUid); - ConfigKey configKey(callingUid, key); - // TODO(b/149254662): Since libbinder_ndk uses int8_t instead of uint8_t, - // there are inconsistencies with internal statsd logic. Instead of - // modifying lots of files, we create a temporary output array of int8_t and - // copy its data into output. This is a bad hack, but hopefully - // libbinder_ndk will transition to using uint8_t soon: progress is tracked - // in b/144957764. Same applies to StatsService::getMetadata. - vector unsignedOutput; - // The dump latency does not matter here since we do not include the current bucket, we do not - // need to pull any new data anyhow. - mProcessor->onDumpReport(configKey, getElapsedRealtimeNs(), false /* include_current_bucket*/, - true /* erase_data */, GET_DATA_CALLED, FAST, &unsignedOutput); - *output = vector(unsignedOutput.begin(), unsignedOutput.end()); - return Status::ok(); -} - -Status StatsService::getMetadata(vector* output) { - ENFORCE_UID(AID_SYSTEM); - - vector unsignedOutput; - StatsdStats::getInstance().dumpStats(&unsignedOutput, false); // Don't reset the counters. - *output = vector(unsignedOutput.begin(), unsignedOutput.end()); - return Status::ok(); -} - -Status StatsService::addConfiguration(int64_t key, const vector & config, - const int32_t callingUid) { - ENFORCE_UID(AID_SYSTEM); - - if (addConfigurationChecked(callingUid, key, config)) { - return Status::ok(); - } else { - return exception(EX_ILLEGAL_ARGUMENT, "Could not parse malformatted StatsdConfig."); - } -} - -bool StatsService::addConfigurationChecked(int uid, int64_t key, const vector& config) { - ConfigKey configKey(uid, key); - StatsdConfig cfg; - if (config.size() > 0) { // If the config is empty, skip parsing. - if (!cfg.ParseFromArray(&config[0], config.size())) { - return false; - } - } - mConfigManager->UpdateConfig(configKey, cfg); - return true; -} - -Status StatsService::removeDataFetchOperation(int64_t key, - const int32_t callingUid) { - ENFORCE_UID(AID_SYSTEM); - ConfigKey configKey(callingUid, key); - mConfigManager->RemoveConfigReceiver(configKey); - return Status::ok(); -} - -Status StatsService::setDataFetchOperation(int64_t key, - const shared_ptr& pir, - const int32_t callingUid) { - ENFORCE_UID(AID_SYSTEM); - - ConfigKey configKey(callingUid, key); - mConfigManager->SetConfigReceiver(configKey, pir); - if (StorageManager::hasConfigMetricsReport(configKey)) { - VLOG("StatsService::setDataFetchOperation marking configKey %s to dump reports on disk", - configKey.ToString().c_str()); - mProcessor->noteOnDiskData(configKey); - } - return Status::ok(); -} - -Status StatsService::setActiveConfigsChangedOperation(const shared_ptr& pir, - const int32_t callingUid, - vector* output) { - ENFORCE_UID(AID_SYSTEM); - - mConfigManager->SetActiveConfigsChangedReceiver(callingUid, pir); - if (output != nullptr) { - mProcessor->GetActiveConfigs(callingUid, *output); - } else { - ALOGW("StatsService::setActiveConfigsChanged output was nullptr"); - } - return Status::ok(); -} - -Status StatsService::removeActiveConfigsChangedOperation(const int32_t callingUid) { - ENFORCE_UID(AID_SYSTEM); - - mConfigManager->RemoveActiveConfigsChangedReceiver(callingUid); - return Status::ok(); -} - -Status StatsService::removeConfiguration(int64_t key, const int32_t callingUid) { - ENFORCE_UID(AID_SYSTEM); - - ConfigKey configKey(callingUid, key); - mConfigManager->RemoveConfig(configKey); - return Status::ok(); -} - -Status StatsService::setBroadcastSubscriber(int64_t configId, - int64_t subscriberId, - const shared_ptr& pir, - const int32_t callingUid) { - ENFORCE_UID(AID_SYSTEM); - - VLOG("StatsService::setBroadcastSubscriber called."); - ConfigKey configKey(callingUid, configId); - SubscriberReporter::getInstance() - .setBroadcastSubscriber(configKey, subscriberId, pir); - return Status::ok(); -} - -Status StatsService::unsetBroadcastSubscriber(int64_t configId, - int64_t subscriberId, - const int32_t callingUid) { - ENFORCE_UID(AID_SYSTEM); - - VLOG("StatsService::unsetBroadcastSubscriber called."); - ConfigKey configKey(callingUid, configId); - SubscriberReporter::getInstance() - .unsetBroadcastSubscriber(configKey, subscriberId); - return Status::ok(); -} - -Status StatsService::allPullersFromBootRegistered() { - ENFORCE_UID(AID_SYSTEM); - - VLOG("StatsService::allPullersFromBootRegistered was called"); - mBootCompleteTrigger.markComplete(kAllPullersRegisteredTag); - return Status::ok(); -} - -Status StatsService::registerPullAtomCallback(int32_t uid, int32_t atomTag, int64_t coolDownMillis, - int64_t timeoutMillis, - const std::vector& additiveFields, - const shared_ptr& pullerCallback) { - ENFORCE_UID(AID_SYSTEM); - VLOG("StatsService::registerPullAtomCallback called."); - mPullerManager->RegisterPullAtomCallback(uid, atomTag, MillisToNano(coolDownMillis), - MillisToNano(timeoutMillis), additiveFields, - pullerCallback); - return Status::ok(); -} - -Status StatsService::registerNativePullAtomCallback( - int32_t atomTag, int64_t coolDownMillis, int64_t timeoutMillis, - const std::vector& additiveFields, - const shared_ptr& pullerCallback) { - if (!checkPermission(kPermissionRegisterPullAtom)) { - return exception( - EX_SECURITY, - StringPrintf("Uid %d does not have the %s permission when registering atom %d", - AIBinder_getCallingUid(), kPermissionRegisterPullAtom, atomTag)); - } - VLOG("StatsService::registerNativePullAtomCallback called."); - int32_t uid = AIBinder_getCallingUid(); - mPullerManager->RegisterPullAtomCallback(uid, atomTag, MillisToNano(coolDownMillis), - MillisToNano(timeoutMillis), additiveFields, - pullerCallback); - return Status::ok(); -} - -Status StatsService::unregisterPullAtomCallback(int32_t uid, int32_t atomTag) { - ENFORCE_UID(AID_SYSTEM); - VLOG("StatsService::unregisterPullAtomCallback called."); - mPullerManager->UnregisterPullAtomCallback(uid, atomTag); - return Status::ok(); -} - -Status StatsService::unregisterNativePullAtomCallback(int32_t atomTag) { - if (!checkPermission(kPermissionRegisterPullAtom)) { - return exception( - EX_SECURITY, - StringPrintf("Uid %d does not have the %s permission when unregistering atom %d", - AIBinder_getCallingUid(), kPermissionRegisterPullAtom, atomTag)); - } - VLOG("StatsService::unregisterNativePullAtomCallback called."); - int32_t uid = AIBinder_getCallingUid(); - mPullerManager->UnregisterPullAtomCallback(uid, atomTag); - return Status::ok(); -} - -Status StatsService::getRegisteredExperimentIds(std::vector* experimentIdsOut) { - ENFORCE_UID(AID_SYSTEM); - // TODO: add verifier permission - - experimentIdsOut->clear(); - // Read the latest train info - vector trainInfoList = StorageManager::readAllTrainInfo(); - if (trainInfoList.empty()) { - // No train info means no experiment IDs, return an empty list - return Status::ok(); - } - - // Copy the experiment IDs to the out vector - for (InstallTrainInfo& trainInfo : trainInfoList) { - experimentIdsOut->insert(experimentIdsOut->end(), - trainInfo.experimentIds.begin(), - trainInfo.experimentIds.end()); - } - return Status::ok(); -} - -void StatsService::statsCompanionServiceDied(void* cookie) { - auto thiz = static_cast(cookie); - thiz->statsCompanionServiceDiedImpl(); -} - -void StatsService::statsCompanionServiceDiedImpl() { - ALOGW("statscompanion service died"); - StatsdStats::getInstance().noteSystemServerRestart(getWallClockSec()); - if (mProcessor != nullptr) { - ALOGW("Reset statsd upon system server restarts."); - int64_t systemServerRestartNs = getElapsedRealtimeNs(); - ProtoOutputStream activeConfigsProto; - mProcessor->WriteActiveConfigsToProtoOutputStream(systemServerRestartNs, - STATSCOMPANION_DIED, &activeConfigsProto); - metadata::StatsMetadataList metadataList; - mProcessor->WriteMetadataToProto(getWallClockNs(), - systemServerRestartNs, &metadataList); - mProcessor->WriteDataToDisk(STATSCOMPANION_DIED, FAST, systemServerRestartNs); - mProcessor->resetConfigs(); - - std::string serializedActiveConfigs; - if (activeConfigsProto.serializeToString(&serializedActiveConfigs)) { - ActiveConfigList activeConfigs; - if (activeConfigs.ParseFromString(serializedActiveConfigs)) { - mProcessor->SetConfigsActiveState(activeConfigs, systemServerRestartNs); - } - } - mProcessor->SetMetadataState(metadataList, getWallClockNs(), systemServerRestartNs); - } - mAnomalyAlarmMonitor->setStatsCompanionService(nullptr); - mPeriodicAlarmMonitor->setStatsCompanionService(nullptr); - mPullerManager->SetStatsCompanionService(nullptr); -} - -} // namespace statsd -} // namespace os -} // namespace android diff --git a/bin/src/StatsService.h b/bin/src/StatsService.h deleted file mode 100644 index c2b6a99a..00000000 --- a/bin/src/StatsService.h +++ /dev/null @@ -1,420 +0,0 @@ -/* - * 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. - */ - -#ifndef STATS_SERVICE_H -#define STATS_SERVICE_H - -#include -#include -#include -#include -#include - -#include - -#include "StatsLogProcessor.h" -#include "anomaly/AlarmMonitor.h" -#include "config/ConfigManager.h" -#include "external/StatsPullerManager.h" -#include "logd/LogEventQueue.h" -#include "packages/UidMap.h" -#include "shell/ShellSubscriber.h" -#include "statscompanion_util.h" -#include "utils/MultiConditionTrigger.h" - -using namespace android; -using namespace android::os; -using namespace std; - -using Status = ::ndk::ScopedAStatus; -using aidl::android::os::BnStatsd; -using aidl::android::os::IPendingIntentRef; -using aidl::android::os::IPullAtomCallback; -using ::ndk::ScopedAIBinder_DeathRecipient; -using ::ndk::ScopedFileDescriptor; -using std::shared_ptr; - -namespace android { -namespace os { -namespace statsd { - -class StatsService : public BnStatsd { -public: - StatsService(const sp& handlerLooper, std::shared_ptr queue); - virtual ~StatsService(); - - /** The anomaly alarm registered with AlarmManager won't be updated by less than this. */ - const uint32_t MIN_DIFF_TO_UPDATE_REGISTERED_ALARM_SECS = 5; - - virtual status_t dump(int fd, const char** args, uint32_t numArgs) override; - virtual status_t handleShellCommand(int in, int out, int err, const char** argv, - uint32_t argc) override; - - virtual Status systemRunning(); - virtual Status statsCompanionReady(); - virtual Status bootCompleted(); - virtual Status informAnomalyAlarmFired(); - virtual Status informPollAlarmFired(); - virtual Status informAlarmForSubscriberTriggeringFired(); - - virtual Status informAllUidData(const ScopedFileDescriptor& fd); - virtual Status informOnePackage(const string& app, int32_t uid, int64_t version, - const string& versionString, const string& installer); - virtual Status informOnePackageRemoved(const string& app, int32_t uid); - virtual Status informDeviceShutdown(); - - /** - * Called right before we start processing events. - */ - void Startup(); - - /** - * Called when terminiation signal received. - */ - void Terminate(); - - /** - * Test ONLY interface. In real world, StatsService reads from LogEventQueue. - */ - virtual void OnLogEvent(LogEvent* event); - - /** - * Binder call for clients to request data for this configuration key. - */ - virtual Status getData(int64_t key, - const int32_t callingUid, - vector* output) override; - - - /** - * Binder call for clients to get metadata across all configs in statsd. - */ - virtual Status getMetadata(vector* output) override; - - - /** - * Binder call to let clients send a configuration and indicate they're interested when they - * should requestData for this configuration. - */ - virtual Status addConfiguration(int64_t key, - const vector& config, - const int32_t callingUid) override; - - /** - * Binder call to let clients register the data fetch operation for a configuration. - */ - virtual Status setDataFetchOperation(int64_t key, - const shared_ptr& pir, - const int32_t callingUid) override; - - /** - * Binder call to remove the data fetch operation for the specified config key. - */ - virtual Status removeDataFetchOperation(int64_t key, - const int32_t callingUid) override; - - /** - * Binder call to let clients register the active configs changed operation. - */ - virtual Status setActiveConfigsChangedOperation(const shared_ptr& pir, - const int32_t callingUid, - vector* output) override; - - /** - * Binder call to remove the active configs changed operation for the specified package.. - */ - virtual Status removeActiveConfigsChangedOperation(const int32_t callingUid) override; - /** - * Binder call to allow clients to remove the specified configuration. - */ - virtual Status removeConfiguration(int64_t key, - const int32_t callingUid) override; - - /** - * Binder call to associate the given config's subscriberId with the given pendingIntentRef. - */ - virtual Status setBroadcastSubscriber(int64_t configId, - int64_t subscriberId, - const shared_ptr& pir, - const int32_t callingUid) override; - - /** - * Binder call to unassociate the given config's subscriberId with any pendingIntentRef. - */ - virtual Status unsetBroadcastSubscriber(int64_t configId, - int64_t subscriberId, - const int32_t callingUid) override; - - /** Inform statsCompanion that statsd is ready. */ - virtual void sayHiToStatsCompanion(); - - /** - * Binder call to notify statsd that all pullers from boot have been registered. - */ - virtual Status allPullersFromBootRegistered(); - - /** - * Binder call to register a callback function for a pulled atom. - */ - virtual Status registerPullAtomCallback( - int32_t uid, int32_t atomTag, int64_t coolDownMillis, int64_t timeoutMillis, - const std::vector& additiveFields, - const shared_ptr& pullerCallback) override; - - /** - * Binder call to register a callback function for a pulled atom. - */ - virtual Status registerNativePullAtomCallback( - int32_t atomTag, int64_t coolDownMillis, int64_t timeoutMillis, - const std::vector& additiveFields, - const shared_ptr& pullerCallback) override; - - /** - * Binder call to unregister any existing callback for the given uid and atom. - */ - virtual Status unregisterPullAtomCallback(int32_t uid, int32_t atomTag) override; - - /** - * Binder call to unregister any existing callback for the given atom and calling uid. - */ - virtual Status unregisterNativePullAtomCallback(int32_t atomTag) override; - - /** - * Binder call to get registered experiment IDs. - */ - virtual Status getRegisteredExperimentIds(std::vector* expIdsOut); - -private: - /** - * Load system properties at init. - */ - void init_system_properties(); - - /** - * Helper for loading system properties. - */ - static void init_build_type_callback(void* cookie, const char* name, const char* value, - uint32_t serial); - - /** - * Proto output of statsd report data dumpsys, wrapped in a StatsDataDumpProto. - */ - void dumpIncidentSection(int outFd); - - /** - * Text or proto output of statsdStats dumpsys. - */ - void dumpStatsdStats(int outFd, bool verbose, bool proto); - - /** - * Print usage information for the commands - */ - void print_cmd_help(int out); - - /* Runs on its dedicated thread to process pushed stats event from socket. */ - void readLogs(); - - /** - * Trigger a broadcast. - */ - status_t cmd_trigger_broadcast(int outFd, Vector& args); - - - /** - * Trigger an active configs changed broadcast. - */ - status_t cmd_trigger_active_config_broadcast(int outFd, Vector& args); - - /** - * Handle the config sub-command. - */ - status_t cmd_config(int inFd, int outFd, int err, Vector& args); - - /** - * Prints some basic stats to std out. - */ - status_t cmd_print_stats(int outFd, const Vector& args); - - /** - * Print the event log. - */ - status_t cmd_dump_report(int outFd, const Vector& args); - - /** - * Print the mapping of uids to package names. - */ - status_t cmd_print_uid_map(int outFd, const Vector& args); - - /** - * Flush the data to disk. - */ - status_t cmd_write_data_to_disk(int outFd); - - /** - * Write an AppBreadcrumbReported event to the StatsLog buffer, as if calling - * StatsLog.write(APP_BREADCRUMB_REPORTED). - */ - status_t cmd_log_app_breadcrumb(int outFd, const Vector& args); - - /** - * Write an BinaryPushStateChanged event, as if calling StatsLog.logBinaryPushStateChanged(). - */ - status_t cmd_log_binary_push(int outFd, const Vector& args); - - /** - * Print contents of a pulled metrics source. - */ - status_t cmd_print_pulled_metrics(int outFd, const Vector& args); - - /** - * Removes all configs stored on disk and on memory. - */ - status_t cmd_remove_all_configs(int outFd); - - /* - * Dump memory usage by statsd. - */ - status_t cmd_dump_memory_info(int outFd); - - /* - * Clear all puller cached data - */ - status_t cmd_clear_puller_cache(int outFd); - - /** - * Print all stats logs received to logcat. - */ - status_t cmd_print_logs(int outFd, const Vector& args); - - /** - * Writes the value of args[uidArgIndex] into uid. - * Returns whether the uid is reasonable (type uid_t) and whether - * 1. it is equal to the calling uid, or - * 2. the device is mEngBuild, or - * 3. the caller is AID_ROOT and the uid is AID_SHELL (i.e. ROOT can impersonate SHELL). - */ - bool getUidFromArgs(const Vector& args, size_t uidArgIndex, int32_t& uid); - - /** - * Writes the value of uidSting into uid. - * Returns whether the uid is reasonable (type uid_t) and whether - * 1. it is equal to the calling uid, or - * 2. the device is mEngBuild, or - * 3. the caller is AID_ROOT and the uid is AID_SHELL (i.e. ROOT can impersonate SHELL). - */ - bool getUidFromString(const char* uidString, int32_t& uid); - - /** - * Adds a configuration after checking permissions and obtaining UID from binder call. - */ - bool addConfigurationChecked(int uid, int64_t key, const vector& config); - - /** - * Update a configuration. - */ - void set_config(int uid, const string& name, const StatsdConfig& config); - - /** - * Death recipient callback that is called when StatsCompanionService dies. - * The cookie is a pointer to a StatsService object. - */ - static void statsCompanionServiceDied(void* cookie); - - /** - * Implementation of statsCompanionServiceDied. - */ - void statsCompanionServiceDiedImpl(); - - /** - * Tracks the uid <--> package name mapping. - */ - sp mUidMap; - - /** - * Fetches external metrics - */ - sp mPullerManager; - - /** - * Tracks the configurations that have been passed to statsd. - */ - sp mConfigManager; - - /** - * The metrics recorder. - */ - sp mProcessor; - - /** - * The alarm monitor for anomaly detection. - */ - const sp mAnomalyAlarmMonitor; - - /** - * The alarm monitor for alarms to directly trigger subscriber. - */ - const sp mPeriodicAlarmMonitor; - - /** - * Whether this is an eng build. - */ - bool mEngBuild; - - sp mShellSubscriber; - - /** - * Mutex for setting the shell subscriber - */ - mutable mutex mShellSubscriberMutex; - std::shared_ptr mEventQueue; - - MultiConditionTrigger mBootCompleteTrigger; - static const inline string kBootCompleteTag = "BOOT_COMPLETE"; - static const inline string kUidMapReceivedTag = "UID_MAP"; - static const inline string kAllPullersRegisteredTag = "PULLERS_REGISTERED"; - - ScopedAIBinder_DeathRecipient mStatsCompanionServiceDeathRecipient; - - FRIEND_TEST(StatsLogProcessorTest, TestActivationsPersistAcrossSystemServerRestart); - FRIEND_TEST(StatsServiceTest, TestAddConfig_simple); - FRIEND_TEST(StatsServiceTest, TestAddConfig_empty); - FRIEND_TEST(StatsServiceTest, TestAddConfig_invalid); - FRIEND_TEST(StatsServiceTest, TestGetUidFromArgs); - FRIEND_TEST(PartialBucketE2eTest, TestCountMetricNoSplitOnNewApp); - FRIEND_TEST(PartialBucketE2eTest, TestCountMetricSplitOnBoot); - FRIEND_TEST(PartialBucketE2eTest, TestCountMetricSplitOnUpgrade); - FRIEND_TEST(PartialBucketE2eTest, TestCountMetricSplitOnRemoval); - FRIEND_TEST(PartialBucketE2eTest, TestCountMetricWithoutSplit); - FRIEND_TEST(PartialBucketE2eTest, TestValueMetricOnBootWithoutMinPartialBucket); - FRIEND_TEST(PartialBucketE2eTest, TestValueMetricWithoutMinPartialBucket); - FRIEND_TEST(PartialBucketE2eTest, TestValueMetricWithMinPartialBucket); - FRIEND_TEST(PartialBucketE2eTest, TestGaugeMetricOnBootWithoutMinPartialBucket); - FRIEND_TEST(PartialBucketE2eTest, TestGaugeMetricWithoutMinPartialBucket); - FRIEND_TEST(PartialBucketE2eTest, TestGaugeMetricWithMinPartialBucket); - - FRIEND_TEST(ConfigUpdateE2eTest, TestAnomalyDurationMetric); - - FRIEND_TEST(AnomalyDetectionE2eTest, TestDurationMetric_SUM_single_bucket); - FRIEND_TEST(AnomalyDetectionE2eTest, TestDurationMetric_SUM_partial_bucket); - FRIEND_TEST(AnomalyDetectionE2eTest, TestDurationMetric_SUM_multiple_buckets); - FRIEND_TEST(AnomalyDetectionE2eTest, TestDurationMetric_SUM_long_refractory_period); -}; - -} // namespace statsd -} // namespace os -} // namespace android - -#endif // STATS_SERVICE_H diff --git a/bin/src/active_config_list.proto b/bin/src/active_config_list.proto deleted file mode 100644 index 99298335..00000000 --- a/bin/src/active_config_list.proto +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright (C) 2019 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. - */ - -syntax = "proto2"; - -package android.os.statsd; -option java_package = "com.android.os"; -option java_multiple_files = true; -option java_outer_classname = "ActiveConfigProto"; - -message ActiveEventActivation { - optional int32 atom_matcher_index = 1; - - // Time left in activation. When this proto is loaded after device boot, - // the activation should be set to active for this duration. - // This field will only be set when the state is ACTIVE - optional int64 remaining_ttl_nanos = 2; - - enum State { - UNNKNOWN = 0; - // This metric should activate for remaining_ttl_nanos when we load the activations. - ACTIVE = 1; - // When we load the activations, this metric should activate on next boot for the tll - // specified in the config. - ACTIVATE_ON_BOOT = 2; - } - optional State state = 3; -} - -message ActiveMetric { - optional int64 id = 1; - repeated ActiveEventActivation activation = 2; -} - -message ActiveConfig { - optional int64 id = 1; - optional int32 uid = 2; - repeated ActiveMetric metric = 3; -} - -// all configs and their metrics on device. -message ActiveConfigList { - repeated ActiveConfig config = 1; -} diff --git a/bin/src/annotations.h b/bin/src/annotations.h deleted file mode 100644 index cf7f5433..00000000 --- a/bin/src/annotations.h +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright (C) 2020 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. - */ - -#pragma once - -namespace android { -namespace os { -namespace statsd { - -const uint8_t ANNOTATION_ID_IS_UID = 1; -const uint8_t ANNOTATION_ID_TRUNCATE_TIMESTAMP = 2; -const uint8_t ANNOTATION_ID_PRIMARY_FIELD = 3; -const uint8_t ANNOTATION_ID_EXCLUSIVE_STATE = 4; -const uint8_t ANNOTATION_ID_PRIMARY_FIELD_FIRST_UID = 5; -const uint8_t ANNOTATION_ID_TRIGGER_STATE_RESET = 7; -const uint8_t ANNOTATION_ID_STATE_NESTED = 8; - -} // namespace statsd -} // namespace os -} // namespace android diff --git a/bin/src/anomaly/AlarmMonitor.cpp b/bin/src/anomaly/AlarmMonitor.cpp deleted file mode 100644 index b632d040..00000000 --- a/bin/src/anomaly/AlarmMonitor.cpp +++ /dev/null @@ -1,139 +0,0 @@ -/* - * 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. - */ - -#define DEBUG false -#include "Log.h" - -#include "anomaly/AlarmMonitor.h" -#include "guardrail/StatsdStats.h" - -namespace android { -namespace os { -namespace statsd { - -AlarmMonitor::AlarmMonitor( - uint32_t minDiffToUpdateRegisteredAlarmTimeSec, - const std::function&, int64_t)>& updateAlarm, - const std::function&)>& cancelAlarm) - : mRegisteredAlarmTimeSec(0), - mMinUpdateTimeSec(minDiffToUpdateRegisteredAlarmTimeSec), - mUpdateAlarm(updateAlarm), - mCancelAlarm(cancelAlarm) {} - -AlarmMonitor::~AlarmMonitor() {} - -void AlarmMonitor::setStatsCompanionService( - shared_ptr statsCompanionService) { - std::lock_guard lock(mLock); - shared_ptr tmpForLock = mStatsCompanionService; - mStatsCompanionService = statsCompanionService; - if (statsCompanionService == nullptr) { - VLOG("Erasing link to statsCompanionService"); - return; - } - VLOG("Creating link to statsCompanionService"); - const sp top = mPq.top(); - if (top != nullptr) { - updateRegisteredAlarmTime_l(top->timestampSec); - } -} - -void AlarmMonitor::add(sp alarm) { - std::lock_guard lock(mLock); - if (alarm == nullptr) { - ALOGW("Asked to add a null alarm."); - return; - } - if (alarm->timestampSec < 1) { - // forbidden since a timestamp 0 is used to indicate no alarm registered - ALOGW("Asked to add a 0-time alarm."); - return; - } - // TODO(b/110563466): Ensure that refractory period is respected. - VLOG("Adding alarm with time %u", alarm->timestampSec); - mPq.push(alarm); - if (mRegisteredAlarmTimeSec < 1 || - alarm->timestampSec + mMinUpdateTimeSec < mRegisteredAlarmTimeSec) { - updateRegisteredAlarmTime_l(alarm->timestampSec); - } -} - -void AlarmMonitor::remove(sp alarm) { - std::lock_guard lock(mLock); - if (alarm == nullptr) { - ALOGW("Asked to remove a null alarm."); - return; - } - VLOG("Removing alarm with time %u", alarm->timestampSec); - bool wasPresent = mPq.remove(alarm); - if (!wasPresent) return; - if (mPq.empty()) { - VLOG("Queue is empty. Cancel any alarm."); - cancelRegisteredAlarmTime_l(); - return; - } - uint32_t soonestAlarmTimeSec = mPq.top()->timestampSec; - VLOG("Soonest alarm is %u", soonestAlarmTimeSec); - if (soonestAlarmTimeSec > mRegisteredAlarmTimeSec + mMinUpdateTimeSec) { - updateRegisteredAlarmTime_l(soonestAlarmTimeSec); - } -} - -// More efficient than repeatedly calling remove(mPq.top()) since it batches the -// updates to the registered alarm. -unordered_set, SpHash> AlarmMonitor::popSoonerThan( - uint32_t timestampSec) { - VLOG("Removing alarms with time <= %u", timestampSec); - unordered_set, SpHash> oldAlarms; - std::lock_guard lock(mLock); - - for (sp t = mPq.top(); t != nullptr && t->timestampSec <= timestampSec; - t = mPq.top()) { - oldAlarms.insert(t); - mPq.pop(); // remove t - } - // Always update registered alarm time (if anything has changed). - if (!oldAlarms.empty()) { - if (mPq.empty()) { - VLOG("Queue is empty. Cancel any alarm."); - cancelRegisteredAlarmTime_l(); - } else { - // Always update the registered alarm in this case (unlike remove()). - updateRegisteredAlarmTime_l(mPq.top()->timestampSec); - } - } - return oldAlarms; -} - -void AlarmMonitor::updateRegisteredAlarmTime_l(uint32_t timestampSec) { - VLOG("Updating reg alarm time to %u", timestampSec); - mRegisteredAlarmTimeSec = timestampSec; - mUpdateAlarm(mStatsCompanionService, secToMs(mRegisteredAlarmTimeSec)); -} - -void AlarmMonitor::cancelRegisteredAlarmTime_l() { - VLOG("Cancelling reg alarm."); - mRegisteredAlarmTimeSec = 0; - mCancelAlarm(mStatsCompanionService); -} - -int64_t AlarmMonitor::secToMs(uint32_t timeSec) { - return ((int64_t)timeSec) * 1000; -} - -} // namespace statsd -} // namespace os -} // namespace android diff --git a/bin/src/anomaly/AlarmMonitor.h b/bin/src/anomaly/AlarmMonitor.h deleted file mode 100644 index 5c34e381..00000000 --- a/bin/src/anomaly/AlarmMonitor.h +++ /dev/null @@ -1,162 +0,0 @@ -/* - * 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. - */ - -#pragma once - -#include "anomaly/indexed_priority_queue.h" - -#include -#include - -#include -#include - -using namespace android; - -using aidl::android::os::IStatsCompanionService; -using std::function; -using std::shared_ptr; -using std::unordered_set; - -namespace android { -namespace os { -namespace statsd { - -/** - * Represents an alarm, associated with some aggregate metric, holding a - * projected time at which the metric is expected to exceed its anomaly - * threshold. - * Timestamps are in seconds since epoch in a uint32, so will fail in year 2106. - */ -struct InternalAlarm : public RefBase { - explicit InternalAlarm(uint32_t timestampSec) : timestampSec(timestampSec) { - } - - const uint32_t timestampSec; - - /** InternalAlarm a is smaller (higher priority) than b if its timestamp is sooner. */ - struct SmallerTimestamp { - bool operator()(sp a, sp b) const { - return (a->timestampSec < b->timestampSec); - } - }; -}; - -/** - * Manages internal alarms that may get registered with the AlarmManager. - */ -class AlarmMonitor : public RefBase { -public: - /** - * @param minDiffToUpdateRegisteredAlarmTimeSec If the soonest alarm differs - * from the registered alarm by more than this amount, update the registered - * alarm. - */ - AlarmMonitor(uint32_t minDiffToUpdateRegisteredAlarmTimeSec, - const function&, int64_t)>& - updateAlarm, - const function&)>& cancelAlarm); - ~AlarmMonitor(); - - /** - * Tells AnomalyMonitor what IStatsCompanionService to use and, if - * applicable, immediately registers an existing alarm with it. - * If nullptr, AnomalyMonitor will continue to add/remove alarms, but won't - * update IStatsCompanionService (until such time as it is set non-null). - */ - void setStatsCompanionService(shared_ptr statsCompanionService); - - /** - * Adds the given alarm (reference) to the queue. - */ - void add(sp alarm); - - /** - * Removes the given alarm (reference) from the queue. - * Note that alarm comparison is reference-based; if another alarm exists - * with the same timestampSec, that alarm will still remain in the queue. - */ - void remove(sp alarm); - - /** - * Returns and removes all alarms whose timestamp <= the given timestampSec. - * Always updates the registered alarm if return is non-empty. - */ - unordered_set, SpHash> popSoonerThan( - uint32_t timestampSec); - - /** - * Returns the projected alarm timestamp that is registered with - * StatsCompanionService. This may not be equal to the soonest alarm, - * but should be within minDiffToUpdateRegisteredAlarmTimeSec of it. - */ - uint32_t getRegisteredAlarmTimeSec() const { - return mRegisteredAlarmTimeSec; - } - -private: - std::mutex mLock; - - /** - * Timestamp (seconds since epoch) of the alarm registered with - * StatsCompanionService. This, in general, may not be equal to the soonest - * alarm stored in mPq, but should be within minUpdateTimeSec of it. - * A value of 0 indicates that no alarm is currently registered. - */ - uint32_t mRegisteredAlarmTimeSec; - - /** - * Priority queue of alarms, prioritized by soonest alarm.timestampSec. - */ - indexed_priority_queue mPq; - - /** - * Binder interface for communicating with StatsCompanionService. - */ - shared_ptr mStatsCompanionService = nullptr; - - /** - * Amount by which the soonest projected alarm must differ from - * mRegisteredAlarmTimeSec before updateRegisteredAlarmTime_l is called. - */ - uint32_t mMinUpdateTimeSec; - - /** - * Updates the alarm registered with StatsCompanionService to the given time. - * Also correspondingly updates mRegisteredAlarmTimeSec. - */ - void updateRegisteredAlarmTime_l(uint32_t timestampSec); - - /** - * Cancels the alarm registered with StatsCompanionService. - * Also correspondingly sets mRegisteredAlarmTimeSec to 0. - */ - void cancelRegisteredAlarmTime_l(); - - /** Converts uint32 timestamp in seconds to a Java long in msec. */ - int64_t secToMs(uint32_t timeSec); - - // Callback function to update the alarm via StatsCompanionService. - std::function, int64_t)> mUpdateAlarm; - - // Callback function to cancel the alarm via StatsCompanionService. - std::function)> mCancelAlarm; - -}; - -} // namespace statsd -} // namespace os -} // namespace android diff --git a/bin/src/anomaly/AlarmTracker.cpp b/bin/src/anomaly/AlarmTracker.cpp deleted file mode 100644 index 6d9beb8f..00000000 --- a/bin/src/anomaly/AlarmTracker.cpp +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Copyright (C) 2018 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. - */ - -#define DEBUG false // STOPSHIP if true -#include "Log.h" - -#include "anomaly/AlarmTracker.h" -#include "anomaly/subscriber_util.h" -#include "HashableDimensionKey.h" -#include "stats_util.h" -#include "storage/StorageManager.h" - -#include - -namespace android { -namespace os { -namespace statsd { - -AlarmTracker::AlarmTracker(const int64_t startMillis, - const int64_t currentMillis, - const Alarm& alarm, const ConfigKey& configKey, - const sp& alarmMonitor) - : mAlarmConfig(alarm), - mConfigKey(configKey), - mAlarmMonitor(alarmMonitor) { - VLOG("AlarmTracker() called"); - mAlarmSec = (startMillis + mAlarmConfig.offset_millis()) / MS_PER_SEC; - // startMillis is the time statsd is created. We need to find the 1st alarm timestamp after - // the config is added to statsd. - mAlarmSec = findNextAlarmSec(currentMillis / MS_PER_SEC); // round up - mInternalAlarm = new InternalAlarm{static_cast(mAlarmSec)}; - VLOG("AlarmTracker sets the periodic alarm at: %lld", (long long)mAlarmSec); - if (mAlarmMonitor != nullptr) { - mAlarmMonitor->add(mInternalAlarm); - } -} - -AlarmTracker::~AlarmTracker() { - VLOG("~AlarmTracker() called"); - if (mInternalAlarm != nullptr && mAlarmMonitor != nullptr) { - mAlarmMonitor->remove(mInternalAlarm); - } -} - -void AlarmTracker::addSubscription(const Subscription& subscription) { - mSubscriptions.push_back(subscription); -} - -int64_t AlarmTracker::findNextAlarmSec(int64_t currentTimeSec) { - if (currentTimeSec < mAlarmSec) { - return mAlarmSec; - } - int64_t periodsForward = - ((currentTimeSec - mAlarmSec) * MS_PER_SEC) / mAlarmConfig.period_millis() + 1; - return mAlarmSec + periodsForward * mAlarmConfig.period_millis() / MS_PER_SEC; -} - -void AlarmTracker::informAlarmsFired( - const int64_t& timestampNs, - unordered_set, SpHash>& firedAlarms) { - if (firedAlarms.empty() || mInternalAlarm == nullptr || - firedAlarms.find(mInternalAlarm) == firedAlarms.end()) { - return; - } - if (!mSubscriptions.empty()) { - VLOG("AlarmTracker triggers the subscribers."); - triggerSubscribers(mAlarmConfig.id(), 0 /*metricId N/A*/, DEFAULT_METRIC_DIMENSION_KEY, - 0 /* metricValue N/A */, mConfigKey, mSubscriptions); - } - firedAlarms.erase(mInternalAlarm); - mAlarmSec = findNextAlarmSec((timestampNs-1) / NS_PER_SEC + 1); // round up - mInternalAlarm = new InternalAlarm{static_cast(mAlarmSec)}; - VLOG("AlarmTracker sets the periodic alarm at: %lld", (long long)mAlarmSec); - if (mAlarmMonitor != nullptr) { - mAlarmMonitor->add(mInternalAlarm); - } -} - -} // namespace statsd -} // namespace os -} // namespace android diff --git a/bin/src/anomaly/AlarmTracker.h b/bin/src/anomaly/AlarmTracker.h deleted file mode 100644 index 23defc9f..00000000 --- a/bin/src/anomaly/AlarmTracker.h +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Copyright (C) 2018 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. - */ - -#pragma once - -#include - -#include "AlarmMonitor.h" -#include "config/ConfigKey.h" -#include "packages/modules/StatsD/bin/src/statsd_config.pb.h" // Alarm - -#include -#include - -namespace android { -namespace os { -namespace statsd { - -class AlarmTracker : public virtual RefBase { -public: - AlarmTracker(const int64_t startMillis, - const int64_t currentMillis, - const Alarm& alarm, const ConfigKey& configKey, - const sp& subscriberAlarmMonitor); - - virtual ~AlarmTracker(); - - void onAlarmFired(); - - void addSubscription(const Subscription& subscription); - - void informAlarmsFired(const int64_t& timestampNs, - unordered_set, SpHash>& firedAlarms); - -protected: - // For test only. Returns the alarm timestamp in seconds. Otherwise returns 0. - inline int32_t getAlarmTimestampSec() const { - return mInternalAlarm == nullptr ? 0 : mInternalAlarm->timestampSec; - } - - int64_t findNextAlarmSec(int64_t currentTimeMillis); - - // statsd_config.proto Alarm message that defines this tracker. - const Alarm mAlarmConfig; - - // A reference to the Alarm's config key. - const ConfigKey mConfigKey; - - // The subscriptions that depend on this alarm. - std::vector mSubscriptions; - - // Alarm monitor. - sp mAlarmMonitor; - - // The current expected alarm time in seconds. - int64_t mAlarmSec; - - // The current alarm. - sp mInternalAlarm; - - FRIEND_TEST(AlarmTrackerTest, TestTriggerTimestamp); - FRIEND_TEST(AlarmE2eTest, TestMultipleAlarms); - FRIEND_TEST(ConfigUpdateTest, TestUpdateAlarms); -}; - -} // namespace statsd -} // namespace os -} // namespace android diff --git a/bin/src/anomaly/AnomalyTracker.cpp b/bin/src/anomaly/AnomalyTracker.cpp deleted file mode 100644 index 6aa410b1..00000000 --- a/bin/src/anomaly/AnomalyTracker.cpp +++ /dev/null @@ -1,326 +0,0 @@ -/* - * 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. - */ - -#define DEBUG false // STOPSHIP if true -#include "Log.h" - -#include "AnomalyTracker.h" -#include "external/Perfetto.h" -#include "guardrail/StatsdStats.h" -#include "metadata_util.h" -#include "stats_log_util.h" -#include "subscriber_util.h" -#include "subscriber/IncidentdReporter.h" -#include "subscriber/SubscriberReporter.h" - -#include -#include -#include - -namespace android { -namespace os { -namespace statsd { - -AnomalyTracker::AnomalyTracker(const Alert& alert, const ConfigKey& configKey) - : mAlert(alert), mConfigKey(configKey), mNumOfPastBuckets(mAlert.num_buckets() - 1) { - VLOG("AnomalyTracker() called"); - resetStorage(); // initialization -} - -AnomalyTracker::~AnomalyTracker() { - VLOG("~AnomalyTracker() called"); -} - -void AnomalyTracker::onConfigUpdated() { - mSubscriptions.clear(); -} - -void AnomalyTracker::resetStorage() { - VLOG("resetStorage() called."); - mPastBuckets.clear(); - // Excludes the current bucket. - mPastBuckets.resize(mNumOfPastBuckets); - mSumOverPastBuckets.clear(); -} - -size_t AnomalyTracker::index(int64_t bucketNum) const { - if (bucketNum < 0) { - ALOGE("index() was passed a negative bucket number (%lld)!", (long long)bucketNum); - } - return bucketNum % mNumOfPastBuckets; -} - -void AnomalyTracker::advanceMostRecentBucketTo(const int64_t& bucketNum) { - VLOG("advanceMostRecentBucketTo() called."); - if (mNumOfPastBuckets <= 0) { - return; - } - if (bucketNum <= mMostRecentBucketNum) { - ALOGW("Cannot advance buckets backwards (bucketNum=%lld but mMostRecentBucketNum=%lld)", - (long long)bucketNum, (long long)mMostRecentBucketNum); - return; - } - // If in the future (i.e. buckets are ancient), just empty out all past info. - if (bucketNum >= mMostRecentBucketNum + mNumOfPastBuckets) { - resetStorage(); - mMostRecentBucketNum = bucketNum; - return; - } - - // Clear out space by emptying out old mPastBuckets[i] values and update mSumOverPastBuckets. - for (int64_t i = mMostRecentBucketNum + 1; i <= bucketNum; i++) { - const int idx = index(i); - subtractBucketFromSum(mPastBuckets[idx]); - mPastBuckets[idx] = nullptr; // release (but not clear) the old bucket. - } - mMostRecentBucketNum = bucketNum; -} - -void AnomalyTracker::addPastBucket(const MetricDimensionKey& key, - const int64_t& bucketValue, - const int64_t& bucketNum) { - VLOG("addPastBucket(bucketValue) called."); - if (mNumOfPastBuckets == 0 || - bucketNum < 0 || bucketNum <= mMostRecentBucketNum - mNumOfPastBuckets) { - return; - } - - const int bucketIndex = index(bucketNum); - if (bucketNum <= mMostRecentBucketNum && (mPastBuckets[bucketIndex] != nullptr)) { - // We need to insert into an already existing past bucket. - std::shared_ptr& bucket = mPastBuckets[bucketIndex]; - auto itr = bucket->find(key); - if (itr != bucket->end()) { - // Old entry already exists; update it. - subtractValueFromSum(key, itr->second); - itr->second = bucketValue; - } else { - bucket->insert({key, bucketValue}); - } - mSumOverPastBuckets[key] += bucketValue; - } else { - // Bucket does not exist yet (in future or was never made), so we must make it. - std::shared_ptr bucket = std::make_shared(); - bucket->insert({key, bucketValue}); - addPastBucket(bucket, bucketNum); - } -} - -void AnomalyTracker::addPastBucket(std::shared_ptr bucket, - const int64_t& bucketNum) { - VLOG("addPastBucket(bucket) called."); - if (mNumOfPastBuckets == 0 || - bucketNum < 0 || bucketNum <= mMostRecentBucketNum - mNumOfPastBuckets) { - return; - } - - if (bucketNum <= mMostRecentBucketNum) { - // We are updating an old bucket, not adding a new one. - subtractBucketFromSum(mPastBuckets[index(bucketNum)]); - } else { - // Clear space for the new bucket to be at bucketNum. - advanceMostRecentBucketTo(bucketNum); - } - mPastBuckets[index(bucketNum)] = bucket; - addBucketToSum(bucket); -} - -void AnomalyTracker::subtractBucketFromSum(const shared_ptr& bucket) { - if (bucket == nullptr) { - return; - } - for (const auto& keyValuePair : *bucket) { - subtractValueFromSum(keyValuePair.first, keyValuePair.second); - } -} - - -void AnomalyTracker::subtractValueFromSum(const MetricDimensionKey& key, - const int64_t& bucketValue) { - auto itr = mSumOverPastBuckets.find(key); - if (itr == mSumOverPastBuckets.end()) { - return; - } - itr->second -= bucketValue; - if (itr->second == 0) { - mSumOverPastBuckets.erase(itr); - } -} - -void AnomalyTracker::addBucketToSum(const shared_ptr& bucket) { - if (bucket == nullptr) { - return; - } - // For each dimension present in the bucket, add its value to its corresponding sum. - for (const auto& keyValuePair : *bucket) { - mSumOverPastBuckets[keyValuePair.first] += keyValuePair.second; - } -} - -int64_t AnomalyTracker::getPastBucketValue(const MetricDimensionKey& key, - const int64_t& bucketNum) const { - if (bucketNum < 0 || mMostRecentBucketNum < 0 - || bucketNum <= mMostRecentBucketNum - mNumOfPastBuckets - || bucketNum > mMostRecentBucketNum) { - return 0; - } - - const auto& bucket = mPastBuckets[index(bucketNum)]; - if (bucket == nullptr) { - return 0; - } - const auto& itr = bucket->find(key); - return itr == bucket->end() ? 0 : itr->second; -} - -int64_t AnomalyTracker::getSumOverPastBuckets(const MetricDimensionKey& key) const { - const auto& itr = mSumOverPastBuckets.find(key); - if (itr != mSumOverPastBuckets.end()) { - return itr->second; - } - return 0; -} - -bool AnomalyTracker::detectAnomaly(const int64_t& currentBucketNum, - const MetricDimensionKey& key, - const int64_t& currentBucketValue) { - - // currentBucketNum should be the next bucket after pastBuckets. If not, advance so that it is. - if (currentBucketNum > mMostRecentBucketNum + 1) { - advanceMostRecentBucketTo(currentBucketNum - 1); - } - return mAlert.has_trigger_if_sum_gt() && - getSumOverPastBuckets(key) + currentBucketValue > mAlert.trigger_if_sum_gt(); -} - -void AnomalyTracker::declareAnomaly(const int64_t& timestampNs, int64_t metricId, - const MetricDimensionKey& key, int64_t metricValue) { - // TODO(b/110563466): 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"); - return; - } - if (mAlert.has_refractory_period_secs()) { - mRefractoryPeriodEndsSec[key] = ((timestampNs + NS_PER_SEC - 1) / NS_PER_SEC) // round up - + mAlert.refractory_period_secs(); - // TODO(b/110563466): If we had access to the bucket_size_millis, consider - // calling resetStorage() - // if (mAlert.refractory_period_secs() > mNumOfPastBuckets * bucketSizeNs) {resetStorage();} - } - - if (!mSubscriptions.empty()) { - ALOGI("An anomaly (%" PRId64 ") %s has occurred! Informing subscribers.", - mAlert.id(), key.toString().c_str()); - informSubscribers(key, metricId, metricValue); - } else { - ALOGI("An anomaly has occurred! (But no subscriber for that alert.)"); - } - - StatsdStats::getInstance().noteAnomalyDeclared(mConfigKey, mAlert.id()); - - // TODO(b/110564268): This should also take in the const MetricDimensionKey& key? - util::stats_write(util::ANOMALY_DETECTED, mConfigKey.GetUid(), - mConfigKey.GetId(), mAlert.id()); -} - -void AnomalyTracker::detectAndDeclareAnomaly(const int64_t& timestampNs, - const int64_t& currBucketNum, int64_t metricId, - const MetricDimensionKey& key, - const int64_t& currentBucketValue) { - if (detectAnomaly(currBucketNum, key, currentBucketValue)) { - declareAnomaly(timestampNs, metricId, key, currentBucketValue); - } -} - -bool AnomalyTracker::isInRefractoryPeriod(const int64_t& timestampNs, - const MetricDimensionKey& key) const { - const auto& it = mRefractoryPeriodEndsSec.find(key); - if (it != mRefractoryPeriodEndsSec.end()) { - return timestampNs < (it->second * (int64_t)NS_PER_SEC); - } - return false; -} - -std::pair AnomalyTracker::getProtoHash() const { - string serializedAlert; - if (!mAlert.SerializeToString(&serializedAlert)) { - ALOGW("Unable to serialize alert %lld", (long long)mAlert.id()); - return {false, 0}; - } - return {true, Hash64(serializedAlert)}; -} - -void AnomalyTracker::informSubscribers(const MetricDimensionKey& key, int64_t metric_id, - int64_t metricValue) { - triggerSubscribers(mAlert.id(), metric_id, key, metricValue, mConfigKey, mSubscriptions); -} - -bool AnomalyTracker::writeAlertMetadataToProto(int64_t currentWallClockTimeNs, - int64_t systemElapsedTimeNs, - metadata::AlertMetadata* alertMetadata) { - bool metadataWritten = false; - - if (mRefractoryPeriodEndsSec.empty()) { - return false; - } - - for (const auto& it: mRefractoryPeriodEndsSec) { - // Do not write the timestamp to disk if it has already expired - if (it.second < systemElapsedTimeNs / NS_PER_SEC) { - continue; - } - - metadataWritten = true; - if (alertMetadata->alert_dim_keyed_data_size() == 0) { - alertMetadata->set_alert_id(mAlert.id()); - } - - metadata::AlertDimensionKeyedData* keyedData = alertMetadata->add_alert_dim_keyed_data(); - // We convert and write the refractory_end_sec to wall clock time because we do not know - // when statsd will start again. - int32_t refractoryEndWallClockSec = (int32_t) ((currentWallClockTimeNs / NS_PER_SEC) + - (it.second - systemElapsedTimeNs / NS_PER_SEC)); - - keyedData->set_last_refractory_ends_sec(refractoryEndWallClockSec); - writeMetricDimensionKeyToMetadataDimensionKey( - it.first, keyedData->mutable_dimension_key()); - } - - return metadataWritten; -} - -void AnomalyTracker::loadAlertMetadata( - const metadata::AlertMetadata& alertMetadata, - int64_t currentWallClockTimeNs, - int64_t systemElapsedTimeNs) { - for (const metadata::AlertDimensionKeyedData& keyedData : - alertMetadata.alert_dim_keyed_data()) { - if ((uint64_t) keyedData.last_refractory_ends_sec() < currentWallClockTimeNs / NS_PER_SEC) { - // Do not update the timestamp if it has already expired. - continue; - } - MetricDimensionKey metricKey = loadMetricDimensionKeyFromProto( - keyedData.dimension_key()); - int32_t refractoryPeriodEndsSec = (int32_t) keyedData.last_refractory_ends_sec() - - currentWallClockTimeNs / NS_PER_SEC + systemElapsedTimeNs / NS_PER_SEC; - mRefractoryPeriodEndsSec[metricKey] = refractoryPeriodEndsSec; - } -} - -} // namespace statsd -} // namespace os -} // namespace android diff --git a/bin/src/anomaly/AnomalyTracker.h b/bin/src/anomaly/AnomalyTracker.h deleted file mode 100644 index 127a26f5..00000000 --- a/bin/src/anomaly/AnomalyTracker.h +++ /dev/null @@ -1,230 +0,0 @@ -/* - * 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. - */ - -#pragma once - -#include -#include -#include - -#include "AlarmMonitor.h" -#include "config/ConfigKey.h" -#include "packages/modules/StatsD/bin/src/statsd_config.pb.h" // Alert -#include "packages/modules/StatsD/bin/src/statsd_metadata.pb.h" // AlertMetadata -#include "hash.h" -#include "stats_util.h" // HashableDimensionKey and DimToValMap - -namespace android { -namespace os { -namespace statsd { - -using std::shared_ptr; -using std::unordered_map; - -// Does NOT allow negative values. -class AnomalyTracker : public virtual RefBase { -public: - AnomalyTracker(const Alert& alert, const ConfigKey& configKey); - - virtual ~AnomalyTracker(); - - // Reset appropriate state on a config update. Clear subscriptions so they can be reset. - void onConfigUpdated(); - - // Add subscriptions that depend on this alert. - void addSubscription(const Subscription& subscription) { - mSubscriptions.push_back(subscription); - } - - // Adds a bucket for the given bucketNum (index starting at 0). - // If a bucket for bucketNum already exists, it will be replaced. - // Also, advances to bucketNum (if not in the past), effectively filling any intervening - // buckets with 0s. - void addPastBucket(std::shared_ptr bucket, const int64_t& bucketNum); - - // Inserts (or replaces) the bucket entry for the given bucketNum at the given key to be the - // given bucketValue. If the bucket does not exist, it will be created. - // Also, advances to bucketNum (if not in the past), effectively filling any intervening - // buckets with 0s. - void addPastBucket(const MetricDimensionKey& key, const int64_t& bucketValue, - const int64_t& bucketNum); - - // Returns true if, based on past buckets plus the new currentBucketValue (which generally - // represents the partially-filled current bucket), an anomaly has happened. - // Also advances to currBucketNum-1. - bool detectAnomaly(const int64_t& currBucketNum, const MetricDimensionKey& key, - const int64_t& currentBucketValue); - - // Informs incidentd about the detected alert. - void declareAnomaly(const int64_t& timestampNs, int64_t metricId, const MetricDimensionKey& key, - int64_t metricValue); - - // Detects if, based on past buckets plus the new currentBucketValue (which generally - // represents the partially-filled current bucket), an anomaly has happened, and if so, - // declares an anomaly and informs relevant subscribers. - // Also advances to currBucketNum-1. - void detectAndDeclareAnomaly(const int64_t& timestampNs, const int64_t& currBucketNum, - int64_t metricId, const MetricDimensionKey& key, - const int64_t& currentBucketValue); - - // Init the AlarmMonitor which is shared across anomaly trackers. - virtual void setAlarmMonitor(const sp& alarmMonitor) { - return; // Base AnomalyTracker class has no need for the AlarmMonitor. - } - - // Returns the sum of all past bucket values for the given dimension key. - int64_t getSumOverPastBuckets(const MetricDimensionKey& key) const; - - // Returns the value for a past bucket, or 0 if that bucket doesn't exist. - int64_t getPastBucketValue(const MetricDimensionKey& key, const int64_t& bucketNum) const; - - // Returns the anomaly threshold set in the configuration. - inline int64_t getAnomalyThreshold() const { - return mAlert.trigger_if_sum_gt(); - } - - // Returns the refractory period ending timestamp (in seconds) for the given key. - // Before this moment, any detected anomaly will be ignored. - // If there is no stored refractory period ending timestamp, returns 0. - uint32_t getRefractoryPeriodEndsSec(const MetricDimensionKey& key) const { - const auto& it = mRefractoryPeriodEndsSec.find(key); - return it != mRefractoryPeriodEndsSec.end() ? it->second : 0; - } - - // Returns the (constant) number of past buckets this anomaly tracker can store. - inline int getNumOfPastBuckets() const { - return mNumOfPastBuckets; - } - - std::pair getProtoHash() const; - - // Sets an alarm for the given timestamp. - // Replaces previous alarm if one already exists. - virtual void startAlarm(const MetricDimensionKey& dimensionKey, const int64_t& eventTime) { - return; // The base AnomalyTracker class doesn't have alarms. - } - - // Stops the alarm. - // If it should have already fired, but hasn't yet (e.g. because the AlarmManager is delayed), - // declare the anomaly now. - virtual void stopAlarm(const MetricDimensionKey& dimensionKey, const int64_t& timestampNs) { - return; // The base AnomalyTracker class doesn't have alarms. - } - - // Stop all the alarms owned by this tracker. Does not declare any anomalies. - virtual void cancelAllAlarms() { - return; // The base AnomalyTracker class doesn't have alarms. - } - - // Declares an anomaly for each alarm in firedAlarms that belongs to this AnomalyTracker, - // and removes it from firedAlarms. Does NOT remove the alarm from the AlarmMonitor. - virtual void informAlarmsFired(const int64_t& timestampNs, - unordered_set, SpHash>& firedAlarms) { - return; // The base AnomalyTracker class doesn't have alarms. - } - - // Writes metadata of the alert (refractory_period_end_sec) to AlertMetadata. - // Returns true if at least one element is written to alertMetadata. - bool writeAlertMetadataToProto( - int64_t currentWallClockTimeNs, - int64_t systemElapsedTimeNs, metadata::AlertMetadata* alertMetadata); - - void loadAlertMetadata( - const metadata::AlertMetadata& alertMetadata, - int64_t currentWallClockTimeNs, - int64_t systemElapsedTimeNs); - -protected: - // For testing only. - // Returns the alarm timestamp in seconds for the query dimension if it exists. Otherwise - // returns 0. - virtual uint32_t getAlarmTimestampSec(const MetricDimensionKey& dimensionKey) const { - return 0; // The base AnomalyTracker class doesn't have alarms. - } - - // statsd_config.proto Alert message that defines this tracker. - const Alert mAlert; - - // The subscriptions that depend on this alert. - std::vector mSubscriptions; - - // A reference to the Alert's config key. - const ConfigKey mConfigKey; - - // Number of past buckets. One less than the total number of buckets needed - // for the anomaly detection (since the current bucket is not in the past). - const int mNumOfPastBuckets; - - // Values for each of the past mNumOfPastBuckets buckets. Always of size mNumOfPastBuckets. - // mPastBuckets[i] can be null, meaning that no data is present in that bucket. - std::vector> mPastBuckets; - - // Cached sum over all existing buckets in mPastBuckets. - // Its buckets never contain entries of 0. - DimToValMap mSumOverPastBuckets; - - // The bucket number of the last added bucket. - int64_t mMostRecentBucketNum = -1; - - // Map from each dimension to the timestamp that its refractory period (if this anomaly was - // declared for that dimension) ends, in seconds. From this moment and onwards, anomalies - // can be declared again. - // Entries may be, but are not guaranteed to be, removed after the period is finished. - unordered_map mRefractoryPeriodEndsSec; - - // Advances mMostRecentBucketNum to bucketNum, deleting any data that is now too old. - // Specifically, since it is now too old, removes the data for - // [mMostRecentBucketNum - mNumOfPastBuckets + 1, bucketNum - mNumOfPastBuckets]. - void advanceMostRecentBucketTo(const int64_t& bucketNum); - - // Add the information in the given bucket to mSumOverPastBuckets. - void addBucketToSum(const shared_ptr& bucket); - - // Subtract the information in the given bucket from mSumOverPastBuckets - // and remove any items with value 0. - void subtractBucketFromSum(const shared_ptr& bucket); - - // From mSumOverPastBuckets[key], subtracts bucketValue, removing it if it is now 0. - void subtractValueFromSum(const MetricDimensionKey& key, const int64_t& bucketValue); - - // Returns true if in the refractory period, else false. - bool isInRefractoryPeriod(const int64_t& timestampNs, const MetricDimensionKey& key) const; - - // Calculates the corresponding bucket index within the circular array. - // Requires bucketNum >= 0. - size_t index(int64_t bucketNum) const; - - // Resets all bucket data. For use when all the data gets stale. - virtual void resetStorage(); - - // Informs the subscribers (incidentd, perfetto, broadcasts, etc) that an anomaly has occurred. - void informSubscribers(const MetricDimensionKey& key, int64_t metricId, int64_t metricValue); - - FRIEND_TEST(AnomalyTrackerTest, TestConsecutiveBuckets); - FRIEND_TEST(AnomalyTrackerTest, TestSparseBuckets); - FRIEND_TEST(GaugeMetricProducerTest, TestAnomalyDetection); - FRIEND_TEST(CountMetricProducerTest, TestAnomalyDetectionUnSliced); - FRIEND_TEST(AnomalyDetectionE2eTest, TestDurationMetric_SUM_single_bucket); - FRIEND_TEST(AnomalyDetectionE2eTest, TestDurationMetric_SUM_partial_bucket); - FRIEND_TEST(AnomalyDetectionE2eTest, TestDurationMetric_SUM_multiple_buckets); - FRIEND_TEST(AnomalyDetectionE2eTest, TestDurationMetric_SUM_long_refractory_period); - - FRIEND_TEST(ConfigUpdateTest, TestUpdateAlerts); -}; - -} // namespace statsd -} // namespace os -} // namespace android diff --git a/bin/src/anomaly/DurationAnomalyTracker.cpp b/bin/src/anomaly/DurationAnomalyTracker.cpp deleted file mode 100644 index 2b568101..00000000 --- a/bin/src/anomaly/DurationAnomalyTracker.cpp +++ /dev/null @@ -1,115 +0,0 @@ -/* - * 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. - */ - -#define DEBUG false // STOPSHIP if true -#include "Log.h" - -#include "DurationAnomalyTracker.h" -#include "guardrail/StatsdStats.h" - -namespace android { -namespace os { -namespace statsd { - -DurationAnomalyTracker::DurationAnomalyTracker(const Alert& alert, const ConfigKey& configKey, - const sp& alarmMonitor) - : AnomalyTracker(alert, configKey), mAlarmMonitor(alarmMonitor) { - VLOG("DurationAnomalyTracker() called"); -} - -DurationAnomalyTracker::~DurationAnomalyTracker() { - VLOG("~DurationAnomalyTracker() called"); - cancelAllAlarms(); -} - -void DurationAnomalyTracker::startAlarm(const MetricDimensionKey& dimensionKey, - const int64_t& timestampNs) { - // Alarms are stored in secs. Must round up, since if it fires early, it is ignored completely. - uint32_t timestampSec = static_cast((timestampNs -1) / NS_PER_SEC) + 1; // round up - if (isInRefractoryPeriod(timestampNs, dimensionKey)) { - VLOG("Not setting anomaly alarm since it would fall in the refractory period."); - return; - } - - auto itr = mAlarms.find(dimensionKey); - if (itr != mAlarms.end() && mAlarmMonitor != nullptr) { - mAlarmMonitor->remove(itr->second); - } - - sp alarm = new InternalAlarm{timestampSec}; - mAlarms[dimensionKey] = alarm; - if (mAlarmMonitor != nullptr) { - mAlarmMonitor->add(alarm); - } -} - -void DurationAnomalyTracker::stopAlarm(const MetricDimensionKey& dimensionKey, - const int64_t& timestampNs) { - const auto itr = mAlarms.find(dimensionKey); - if (itr == mAlarms.end()) { - return; - } - - // If the alarm is set in the past but hasn't fired yet (due to lag), catch it now. - if (itr->second != nullptr && timestampNs >= (int64_t)NS_PER_SEC * itr->second->timestampSec) { - declareAnomaly(timestampNs, mAlert.metric_id(), dimensionKey, - mAlert.trigger_if_sum_gt() + (timestampNs / NS_PER_SEC) - - itr->second->timestampSec); - } - if (mAlarmMonitor != nullptr) { - mAlarmMonitor->remove(itr->second); - } - mAlarms.erase(dimensionKey); -} - -void DurationAnomalyTracker::cancelAllAlarms() { - if (mAlarmMonitor != nullptr) { - for (const auto& itr : mAlarms) { - mAlarmMonitor->remove(itr.second); - } - } - mAlarms.clear(); -} - -void DurationAnomalyTracker::informAlarmsFired(const int64_t& timestampNs, - unordered_set, SpHash>& firedAlarms) { - - if (firedAlarms.empty() || mAlarms.empty()) return; - // Find the intersection of firedAlarms and mAlarms. - // The for loop is inefficient, since it loops over all keys, but that's okay since it is very - // seldomly called. The alternative would be having InternalAlarms 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; - for (const auto& kv : mAlarms) { - if (firedAlarms.count(kv.second) > 0) { - matchedAlarms.insert({kv.first, kv.second}); - } - } - - // Now declare each of these alarms to have fired. - for (const auto& kv : matchedAlarms) { - declareAnomaly( - timestampNs, mAlert.metric_id(), kv.first, - mAlert.trigger_if_sum_gt() + (timestampNs / NS_PER_SEC) - kv.second->timestampSec); - mAlarms.erase(kv.first); - firedAlarms.erase(kv.second); // No one else can also own it, so we're done with it. - } -} - -} // namespace statsd -} // namespace os -} // namespace android diff --git a/bin/src/anomaly/DurationAnomalyTracker.h b/bin/src/anomaly/DurationAnomalyTracker.h deleted file mode 100644 index 46419149..00000000 --- a/bin/src/anomaly/DurationAnomalyTracker.h +++ /dev/null @@ -1,79 +0,0 @@ -/* - * 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. - */ - -#pragma once - -#include "AlarmMonitor.h" -#include "AnomalyTracker.h" - -namespace android { -namespace os { -namespace statsd { - -using std::unordered_map; - -class DurationAnomalyTracker : public virtual AnomalyTracker { -public: - DurationAnomalyTracker(const Alert& alert, const ConfigKey& configKey, - const sp& alarmMonitor); - - virtual ~DurationAnomalyTracker(); - - // Sets an alarm for the given timestamp. - // Replaces previous alarm if one already exists. - void startAlarm(const MetricDimensionKey& dimensionKey, const int64_t& eventTime) override; - - // Stops the alarm. - // If it should have already fired, but hasn't yet (e.g. because the AlarmManager is delayed), - // declare the anomaly now. - void stopAlarm(const MetricDimensionKey& dimensionKey, const int64_t& timestampNs) override; - - // Stop all the alarms owned by this tracker. Does not declare any anomalies. - void cancelAllAlarms() override; - - // Declares an anomaly for each alarm in firedAlarms that belongs to this DurationAnomalyTracker - // and removes it from firedAlarms. The AlarmMonitor is not informed. - // Note that this will generally be called from a different thread from the other functions; - // the caller is responsible for thread safety. - void informAlarmsFired(const int64_t& timestampNs, - unordered_set, SpHash>& firedAlarms) override; - -protected: - // Returns the alarm timestamp in seconds for the query dimension if it exists. Otherwise - // returns 0. - uint32_t getAlarmTimestampSec(const MetricDimensionKey& dimensionKey) const override { - auto it = mAlarms.find(dimensionKey); - return it == mAlarms.end() ? 0 : it->second->timestampSec; - } - - // The alarms owned by this tracker. The alarm monitor also shares the alarm pointers when they - // are still active. - std::unordered_map> mAlarms; - - // Anomaly alarm monitor. - sp mAlarmMonitor; - - FRIEND_TEST(OringDurationTrackerTest, TestPredictAnomalyTimestamp); - FRIEND_TEST(OringDurationTrackerTest, TestAnomalyDetectionExpiredAlarm); - FRIEND_TEST(OringDurationTrackerTest, TestAnomalyDetectionFiredAlarm); - FRIEND_TEST(MaxDurationTrackerTest, TestAnomalyDetection); - FRIEND_TEST(MaxDurationTrackerTest, TestAnomalyPredictedTimestamp); - FRIEND_TEST(MaxDurationTrackerTest, TestAnomalyPredictedTimestamp_UpdatedOnStop); -}; - -} // namespace statsd -} // namespace os -} // namespace android diff --git a/bin/src/anomaly/indexed_priority_queue.h b/bin/src/anomaly/indexed_priority_queue.h deleted file mode 100644 index 99882d03..00000000 --- a/bin/src/anomaly/indexed_priority_queue.h +++ /dev/null @@ -1,224 +0,0 @@ -/* - * 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. - */ - -#pragma once - -#include -#include -#include - -using namespace android; - -namespace android { -namespace os { -namespace statsd { - -/** Defines a hash function for sp, returning the hash of the underlying pointer. */ -template -struct SpHash { - size_t operator()(const sp& k) const { - return std::hash()(k.get()); - } -}; - -/** - * Min priority queue for generic type AA. - * Unlike a regular priority queue, this class is also capable of removing interior elements. - * @tparam Comparator must implement [bool operator()(sp a, sp b)], returning - * whether a should be closer to the top of the queue than b. - */ -template -class indexed_priority_queue { -public: - indexed_priority_queue(); - /** Adds a into the priority queue. If already present or a==nullptr, does nothing. */ - void push(sp a); - /* - * Removes a from the priority queue. If not present or a==nullptr, does nothing. - * Returns true if a had been present (and is now removed), else false. - */ - bool remove(sp a); - /** Removes the top element, if there is one. */ - void pop(); - /** Removes all elements. */ - void clear(); - /** Returns whether priority queue contains a (not just a copy of a, but a itself). */ - bool contains(sp a) const; - /** Returns min element. Returns nullptr iff empty(). */ - sp top() const; - /** Returns number of elements in priority queue. */ - size_t size() const { - return pq.size() - 1; - } // pq is 1-indexed - /** Returns true iff priority queue is empty. */ - bool empty() const { - return size() < 1; - } - -private: - /** Vector representing a min-heap (1-indexed, with nullptr at 0). */ - std::vector> pq; - /** Mapping of each element in pq to its index in pq (i.e. the inverse of a=pq[i]). */ - std::unordered_map, size_t, SpHash> indices; - - void init(); - void sift_up(size_t idx); - void sift_down(size_t idx); - /** Returns whether pq[idx1] is considered higher than pq[idx2], according to Comparator. */ - bool higher(size_t idx1, size_t idx2) const; - void swap_indices(size_t i, size_t j); -}; - -// Implementation must be done in this file due to use of template. - -template -indexed_priority_queue::indexed_priority_queue() { - init(); -} - -template -void indexed_priority_queue::push(sp a) { - if (a == nullptr) return; - if (contains(a)) return; - pq.push_back(a); - size_t idx = size(); // index of last element since 1-indexed - indices.insert({a, idx}); - sift_up(idx); // get the pq back in order -} - -template -bool indexed_priority_queue::remove(sp a) { - if (a == nullptr) return false; - if (!contains(a)) return false; - size_t idx = indices[a]; - if (idx >= pq.size()) { - return false; - } - if (idx == size()) { // if a is the last element, i.e. at index idx == size() == (pq.size()-1) - pq.pop_back(); - indices.erase(a); - return true; - } - // move last element (guaranteed not to be at idx) to idx, then delete a - sp last_a = pq.back(); - pq[idx] = last_a; - pq.pop_back(); - indices[last_a] = idx; - indices.erase(a); - - // get the heap back in order (since the element at idx is not in order) - sift_up(idx); - sift_down(idx); - - return true; -} - -// The same as, but slightly more efficient than, remove(top()). -template -void indexed_priority_queue::pop() { - sp a = top(); - if (a == nullptr) return; - const size_t idx = 1; - if (idx == size()) { // if a is the last element - pq.pop_back(); - indices.erase(a); - return; - } - // move last element (guaranteed not to be at idx) to idx, then delete a - sp last_a = pq.back(); - pq[idx] = last_a; - pq.pop_back(); - indices[last_a] = idx; - indices.erase(a); - - // get the heap back in order (since the element at idx is not in order) - sift_down(idx); -} - -template -void indexed_priority_queue::clear() { - pq.clear(); - indices.clear(); - init(); -} - -template -sp indexed_priority_queue::top() const { - if (empty()) return nullptr; - return pq[1]; -} - -template -void indexed_priority_queue::init() { - pq.push_back(nullptr); // so that pq is 1-indexed. - indices.insert({nullptr, 0}); // just to be consistent with pq. -} - -template -void indexed_priority_queue::sift_up(size_t idx) { - while (idx > 1) { - size_t parent = idx / 2; - if (higher(idx, parent)) - swap_indices(idx, parent); - else - break; - idx = parent; - } -} - -template -void indexed_priority_queue::sift_down(size_t idx) { - while (2 * idx <= size()) { - size_t child = 2 * idx; - if (child < size() && higher(child + 1, child)) child++; - if (higher(child, idx)) - swap_indices(child, idx); - else - break; - idx = child; - } -} - -template -bool indexed_priority_queue::higher(size_t idx1, size_t idx2) const { - if (!(0u < idx1 && idx1 < pq.size() && 0u < idx2 && idx2 < pq.size())) { - return false; // got to do something. - } - return Comparator()(pq[idx1], pq[idx2]); -} - -template -bool indexed_priority_queue::contains(sp a) const { - if (a == nullptr) return false; // publicly, we pretend that nullptr is not actually in pq. - return indices.count(a) > 0; -} - -template -void indexed_priority_queue::swap_indices(size_t i, size_t j) { - if (!(0u < i && i < pq.size() && 0u < j && j < pq.size())) { - return; - } - sp val_i = pq[i]; - sp val_j = pq[j]; - pq[i] = val_j; - pq[j] = val_i; - indices[val_i] = j; - indices[val_j] = i; -} - -} // namespace statsd -} // namespace os -} // namespace android diff --git a/bin/src/anomaly/subscriber_util.cpp b/bin/src/anomaly/subscriber_util.cpp deleted file mode 100644 index 5a4a41d0..00000000 --- a/bin/src/anomaly/subscriber_util.cpp +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright (C) 2018 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. - */ - -#define DEBUG false // STOPSHIP if true -#include "Log.h" - -#include "external/Perfetto.h" -#include "subscriber/IncidentdReporter.h" -#include "subscriber/SubscriberReporter.h" - -namespace android { -namespace os { -namespace statsd { - -void triggerSubscribers(int64_t ruleId, int64_t metricId, const MetricDimensionKey& dimensionKey, - int64_t metricValue, const ConfigKey& configKey, - const std::vector& subscriptions) { - VLOG("informSubscribers called."); - if (subscriptions.empty()) { - VLOG("No Subscriptions were associated."); - return; - } - - for (const Subscription& subscription : subscriptions) { - if (subscription.probability_of_informing() < 1 - && ((float)rand() / (float)RAND_MAX) >= subscription.probability_of_informing()) { - // Note that due to float imprecision, 0.0 and 1.0 might not truly mean never/always. - // The config writer was advised to use -0.1 and 1.1 for never/always. - ALOGI("Fate decided that a subscriber would not be informed."); - continue; - } - switch (subscription.subscriber_information_case()) { - case Subscription::SubscriberInformationCase::kIncidentdDetails: - if (!GenerateIncidentReport(subscription.incidentd_details(), ruleId, metricId, - dimensionKey, metricValue, configKey)) { - ALOGW("Failed to generate incident report."); - } - break; - case Subscription::SubscriberInformationCase::kPerfettoDetails: - if (!CollectPerfettoTraceAndUploadToDropbox(subscription.perfetto_details(), - subscription.id(), ruleId, configKey)) { - ALOGW("Failed to generate perfetto traces."); - } - break; - case Subscription::SubscriberInformationCase::kBroadcastSubscriberDetails: - SubscriberReporter::getInstance().alertBroadcastSubscriber(configKey, subscription, - dimensionKey); - break; - default: - break; - } - } -} - -} // namespace statsd -} // namespace os -} // namespace android diff --git a/bin/src/anomaly/subscriber_util.h b/bin/src/anomaly/subscriber_util.h deleted file mode 100644 index 7069b723..00000000 --- a/bin/src/anomaly/subscriber_util.h +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright (C) 2018 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. - */ - -#pragma once - -#include "config/ConfigKey.h" -#include "HashableDimensionKey.h" -#include "packages/modules/StatsD/bin/src/statsd_config.pb.h" - -namespace android { -namespace os { -namespace statsd { - -void triggerSubscribers(const int64_t ruleId, const int64_t metricId, - const MetricDimensionKey& dimensionKey, int64_t metricValue, - const ConfigKey& configKey, const std::vector& subscriptions); - -} // namespace statsd -} // namespace os -} // namespace android diff --git a/bin/src/condition/CombinationConditionTracker.cpp b/bin/src/condition/CombinationConditionTracker.cpp deleted file mode 100644 index 4574b2e3..00000000 --- a/bin/src/condition/CombinationConditionTracker.cpp +++ /dev/null @@ -1,244 +0,0 @@ -/* - * 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. - */ - -#define DEBUG false // STOPSHIP if true -#include "Log.h" -#include "CombinationConditionTracker.h" - -namespace android { -namespace os { -namespace statsd { - -using std::unordered_map; -using std::vector; - -CombinationConditionTracker::CombinationConditionTracker(const int64_t& id, const int index, - const uint64_t protoHash) - : ConditionTracker(id, index, protoHash) { - VLOG("creating CombinationConditionTracker %lld", (long long)mConditionId); -} - -CombinationConditionTracker::~CombinationConditionTracker() { - VLOG("~CombinationConditionTracker() %lld", (long long)mConditionId); -} - -bool CombinationConditionTracker::init(const vector& allConditionConfig, - const vector>& allConditionTrackers, - const unordered_map& conditionIdIndexMap, - vector& stack, - vector& conditionCache) { - VLOG("Combination predicate init() %lld", (long long)mConditionId); - if (mInitialized) { - // All the children are guaranteed to be initialized, but the recursion is needed to - // fill the conditionCache properly, since another combination condition or metric - // might rely on this. The recursion is needed to compute the current condition. - - // Init is called instead of isConditionMet so that the ConditionKey can be filled with the - // default key for sliced conditions, since we do not know all indirect descendants here. - for (const int childIndex : mChildren) { - if (conditionCache[childIndex] == ConditionState::kNotEvaluated) { - allConditionTrackers[childIndex]->init(allConditionConfig, allConditionTrackers, - conditionIdIndexMap, stack, conditionCache); - } - } - conditionCache[mIndex] = - evaluateCombinationCondition(mChildren, mLogicalOperation, conditionCache); - return true; - } - - // mark this node as visited in the recursion stack. - stack[mIndex] = true; - - Predicate_Combination combinationCondition = allConditionConfig[mIndex].combination(); - - if (!combinationCondition.has_operation()) { - return false; - } - mLogicalOperation = combinationCondition.operation(); - - if (mLogicalOperation == LogicalOperation::NOT && combinationCondition.predicate_size() != 1) { - return false; - } - - for (auto child : combinationCondition.predicate()) { - auto it = conditionIdIndexMap.find(child); - - if (it == conditionIdIndexMap.end()) { - ALOGW("Predicate %lld not found in the config", (long long)child); - return false; - } - - int childIndex = it->second; - const auto& childTracker = allConditionTrackers[childIndex]; - // if the child is a visited node in the recursion -> circle detected. - if (stack[childIndex]) { - ALOGW("Circle detected!!!"); - return false; - } - - bool initChildSucceeded = childTracker->init(allConditionConfig, allConditionTrackers, - conditionIdIndexMap, stack, conditionCache); - - if (!initChildSucceeded) { - ALOGW("Child initialization failed %lld ", (long long)child); - return false; - } else { - VLOG("Child initialization success %lld ", (long long)child); - } - - if (allConditionTrackers[childIndex]->isSliced()) { - setSliced(true); - mSlicedChildren.push_back(childIndex); - } else { - mUnSlicedChildren.push_back(childIndex); - } - mChildren.push_back(childIndex); - mTrackerIndex.insert(childTracker->getAtomMatchingTrackerIndex().begin(), - childTracker->getAtomMatchingTrackerIndex().end()); - } - - mUnSlicedPartCondition = - evaluateCombinationCondition(mUnSlicedChildren, mLogicalOperation, conditionCache); - conditionCache[mIndex] = - evaluateCombinationCondition(mChildren, mLogicalOperation, conditionCache); - - // unmark this node in the recursion stack. - stack[mIndex] = false; - - mInitialized = true; - - return true; -} - -bool CombinationConditionTracker::onConfigUpdated( - const vector& allConditionProtos, const int index, - const vector>& allConditionTrackers, - const unordered_map& atomMatchingTrackerMap, - const unordered_map& conditionTrackerMap) { - ConditionTracker::onConfigUpdated(allConditionProtos, index, allConditionTrackers, - atomMatchingTrackerMap, conditionTrackerMap); - mTrackerIndex.clear(); - mChildren.clear(); - mUnSlicedChildren.clear(); - mSlicedChildren.clear(); - Predicate_Combination combinationCondition = allConditionProtos[mIndex].combination(); - - for (const int64_t child : combinationCondition.predicate()) { - const auto& it = conditionTrackerMap.find(child); - - if (it == conditionTrackerMap.end()) { - ALOGW("Predicate %lld not found in the config", (long long)child); - return false; - } - - int childIndex = it->second; - const sp& childTracker = allConditionTrackers[childIndex]; - - // Ensures that the child's tracker indices are updated. - if (!childTracker->onConfigUpdated(allConditionProtos, childIndex, allConditionTrackers, - atomMatchingTrackerMap, conditionTrackerMap)) { - ALOGW("Child update failed %lld ", (long long)child); - return false; - } - - if (allConditionTrackers[childIndex]->isSliced()) { - mSlicedChildren.push_back(childIndex); - } else { - mUnSlicedChildren.push_back(childIndex); - } - mChildren.push_back(childIndex); - mTrackerIndex.insert(childTracker->getAtomMatchingTrackerIndex().begin(), - childTracker->getAtomMatchingTrackerIndex().end()); - } - return true; -} - -void CombinationConditionTracker::isConditionMet( - const ConditionKey& conditionParameters, const vector>& allConditions, - const bool isPartialLink, - vector& conditionCache) 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, - isPartialLink, - conditionCache); - } - } - conditionCache[mIndex] = - evaluateCombinationCondition(mChildren, mLogicalOperation, conditionCache); -} - -void CombinationConditionTracker::evaluateCondition( - const LogEvent& event, const std::vector& eventMatcherValues, - const std::vector>& mAllConditions, - std::vector& nonSlicedConditionCache, - std::vector& conditionChangedCache) { - // value is up to date. - if (nonSlicedConditionCache[mIndex] != ConditionState::kNotEvaluated) { - return; - } - - 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, - nonSlicedConditionCache, conditionChangedCache); - } - } - - ConditionState newCondition = - evaluateCombinationCondition(mChildren, mLogicalOperation, nonSlicedConditionCache); - if (!mSliced) { - bool nonSlicedChanged = (mUnSlicedPartCondition != newCondition); - mUnSlicedPartCondition = newCondition; - - nonSlicedConditionCache[mIndex] = mUnSlicedPartCondition; - conditionChangedCache[mIndex] = nonSlicedChanged; - } else { - mUnSlicedPartCondition = evaluateCombinationCondition(mUnSlicedChildren, mLogicalOperation, - nonSlicedConditionCache); - - for (const int childIndex : mChildren) { - // If any of the sliced condition in children condition changes, the combination - // condition may be changed too. - if (conditionChangedCache[childIndex]) { - conditionChangedCache[mIndex] = true; - break; - } - } - nonSlicedConditionCache[mIndex] = newCondition; - VLOG("CombinationPredicate %lld sliced may changed? %d", (long long)mConditionId, - conditionChangedCache[mIndex] == true); - } -} - -bool CombinationConditionTracker::equalOutputDimensions( - const std::vector>& allConditions, - const vector& dimensions) const { - if (mSlicedChildren.size() != 1 || - mSlicedChildren.front() >= (int)allConditions.size() || - mLogicalOperation != LogicalOperation::AND) { - return false; - } - const sp& slicedChild = allConditions.at(mSlicedChildren.front()); - return slicedChild->equalOutputDimensions(allConditions, dimensions); -} - -} // namespace statsd -} // namespace os -} // namespace android diff --git a/bin/src/condition/CombinationConditionTracker.h b/bin/src/condition/CombinationConditionTracker.h deleted file mode 100644 index 329f7077..00000000 --- a/bin/src/condition/CombinationConditionTracker.h +++ /dev/null @@ -1,115 +0,0 @@ -/* - * 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. - */ - -#ifndef COMBINATION_CONDITION_TRACKER_H -#define COMBINATION_CONDITION_TRACKER_H - -#include "ConditionTracker.h" -#include "packages/modules/StatsD/bin/src/statsd_config.pb.h" - -namespace android { -namespace os { -namespace statsd { - -class CombinationConditionTracker : public ConditionTracker { -public: - CombinationConditionTracker(const int64_t& id, const int index, const uint64_t protoHash); - - ~CombinationConditionTracker(); - - bool init(const std::vector& allConditionConfig, - const std::vector>& allConditionTrackers, - const std::unordered_map& conditionIdIndexMap, std::vector& stack, - std::vector& conditionCache) override; - - bool onConfigUpdated(const std::vector& allConditionProtos, const int index, - const std::vector>& allConditionTrackers, - const std::unordered_map& atomMatchingTrackerMap, - const std::unordered_map& conditionTrackerMap) override; - - void evaluateCondition(const LogEvent& event, - const std::vector& eventMatcherValues, - const std::vector>& mAllConditions, - std::vector& conditionCache, - std::vector& changedCache) override; - - void isConditionMet(const ConditionKey& conditionParameters, - const std::vector>& allConditions, - const bool isPartialLink, - std::vector& conditionCache) const override; - - // Only one child predicate can have dimension. - const std::set* getChangedToTrueDimensions( - const std::vector>& allConditions) const override { - for (const auto& child : mChildren) { - auto result = allConditions[child]->getChangedToTrueDimensions(allConditions); - if (result != nullptr) { - return result; - } - } - return nullptr; - } - - // Only one child predicate can have dimension. - const std::set* getChangedToFalseDimensions( - const std::vector>& allConditions) const override { - for (const auto& child : mChildren) { - auto result = allConditions[child]->getChangedToFalseDimensions(allConditions); - if (result != nullptr) { - return result; - } - } - return nullptr; - } - - bool IsSimpleCondition() const override { return false; } - - bool IsChangedDimensionTrackable() const override { - return mLogicalOperation == LogicalOperation::AND && mSlicedChildren.size() == 1; - } - - bool equalOutputDimensions( - const std::vector>& allConditions, - const vector& dimensions) const override; - - const std::map* getSlicedDimensionMap( - const std::vector>& allConditions) const override { - if (mSlicedChildren.size() == 1) { - return allConditions[mSlicedChildren.front()]->getSlicedDimensionMap(allConditions); - } - return nullptr; - } - -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 - // increases the risk of circular dependency and memory leak. - std::vector mChildren; - - std::vector mSlicedChildren; - std::vector mUnSlicedChildren; - - FRIEND_TEST(ConfigUpdateTest, TestUpdateConditions); -}; - -} // namespace statsd -} // namespace os -} // namespace android - -#endif // COMBINATION_CONDITION_TRACKER_H diff --git a/bin/src/condition/ConditionTimer.h b/bin/src/condition/ConditionTimer.h deleted file mode 100644 index 1fbe2527..00000000 --- a/bin/src/condition/ConditionTimer.h +++ /dev/null @@ -1,107 +0,0 @@ -/* - * Copyright (C) 2019 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. - */ -#pragma once - -#include -#include - -namespace android { -namespace os { -namespace statsd { - -/** - * A simple stopwatch to time the duration of condition being true. - * - * The owner of the stopwatch (MetricProducer) is responsible to notify the stopwatch when condition - * changes (start/pause), and when to start a new bucket (a new lap basically). All timestamps - * should be elapsedRealTime in nano seconds. - * - * Keep the timer simple and inline everything. This class is *NOT* thread safe. Caller is - * responsible for thread safety. - */ -class ConditionTimer { -public: - explicit ConditionTimer(bool initCondition, int64_t bucketStartNs) : mCondition(initCondition) { - if (initCondition) { - mLastConditionChangeTimestampNs = bucketStartNs; - } - }; - - // Tracks how long the condition has been stayed true in the *current* bucket. - // When a new bucket is created, this value will be reset to 0. - int64_t mTimerNs = 0; - - // Last elapsed real timestamp when condition changed. - int64_t mLastConditionChangeTimestampNs = 0; - - bool mCondition = false; - - int64_t newBucketStart(int64_t nextBucketStartNs) { - if (mCondition) { - // Normally, the next bucket happens after the last condition - // change. In this case, add the time between the condition becoming - // true to the next bucket start time. - // Otherwise, the next bucket start time is before the last - // condition change time, this means that the condition was false at - // the bucket boundary before the condition became true, so the - // timer should not get updated and the last condition change time - // remains as is. - if (nextBucketStartNs >= mLastConditionChangeTimestampNs) { - mTimerNs += (nextBucketStartNs - mLastConditionChangeTimestampNs); - mLastConditionChangeTimestampNs = nextBucketStartNs; - } - } else if (mLastConditionChangeTimestampNs > nextBucketStartNs) { - // The next bucket start time is before the last condition change - // time, this means that the condition was true at the bucket - // boundary before the condition became false, so adjust the timer - // to match how long the condition was true to the bucket boundary. - // This means remove the amount the condition stayed true in the - // next bucket from the current bucket. - mTimerNs -= (mLastConditionChangeTimestampNs - nextBucketStartNs); - } - - int64_t temp = mTimerNs; - mTimerNs = 0; - - if (!mCondition && (mLastConditionChangeTimestampNs > nextBucketStartNs)) { - // The next bucket start time is before the last condition change - // time, this means that the condition was true at the bucket - // boundary and remained true in the next bucket up to the condition - // change to false, so adjust the timer to match how long the - // condition stayed true in the next bucket (now the current bucket). - mTimerNs = mLastConditionChangeTimestampNs - nextBucketStartNs; - } - return temp; - } - - void onConditionChanged(bool newCondition, int64_t timestampNs) { - if (newCondition == mCondition) { - return; - } - mCondition = newCondition; - if (newCondition == false) { - mTimerNs += (timestampNs - mLastConditionChangeTimestampNs); - } - mLastConditionChangeTimestampNs = timestampNs; - } - - FRIEND_TEST(ConditionTimerTest, TestTimer_Inital_False); - FRIEND_TEST(ConditionTimerTest, TestTimer_Inital_True); -}; - -} // namespace statsd -} // namespace os -} // namespace android diff --git a/bin/src/condition/ConditionTracker.h b/bin/src/condition/ConditionTracker.h deleted file mode 100644 index 84095f04..00000000 --- a/bin/src/condition/ConditionTracker.h +++ /dev/null @@ -1,186 +0,0 @@ -/* - * 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. - */ - -#pragma once - -#include "condition/condition_util.h" -#include "packages/modules/StatsD/bin/src/statsd_config.pb.h" -#include "matchers/AtomMatchingTracker.h" -#include "matchers/matcher_util.h" - -#include - -#include - -namespace android { -namespace os { -namespace statsd { - -class ConditionTracker : public virtual RefBase { -public: - ConditionTracker(const int64_t& id, const int index, const uint64_t protoHash) - : mConditionId(id), - mIndex(index), - mInitialized(false), - mTrackerIndex(), - mUnSlicedPartCondition(ConditionState::kUnknown), - mSliced(false), - mProtoHash(protoHash){}; - - virtual ~ConditionTracker(){}; - - // Initialize this ConditionTracker. This initialization is done recursively (DFS). It can also - // be done in the constructor, but we do it separately because (1) easy to return a bool to - // indicate whether the initialization is successful. (2) makes unit test easier. - // This function can also be called on config updates, in which case it does nothing other than - // fill the condition cache with the current condition. - // allConditionConfig: the list of all Predicate config from statsd_config. - // allConditionTrackers: the list of all ConditionTrackers (this is needed because we may also - // need to call init() on child conditions) - // conditionIdIndexMap: the mapping from condition id to its index. - // stack: a bit map to keep track which nodes have been visited on the stack in the recursion. - // conditionCache: tracks initial conditions of all ConditionTrackers. returns the - // current condition if called on a config update. - virtual bool init(const std::vector& allConditionConfig, - const std::vector>& allConditionTrackers, - const std::unordered_map& conditionIdIndexMap, - std::vector& stack, std::vector& conditionCache) = 0; - - // Update appropriate state on config updates. Primarily, all indices need to be updated. - // This predicate and all of its children are guaranteed to be preserved across the update. - // This function is recursive and will call onConfigUpdated on child conditions. It does not - // manage cycle detection since all preserved conditions should not have any cycles. - // - // allConditionProtos: the new predicates. - // index: the new index of this tracker in allConditionProtos and allConditionTrackers. - // allConditionTrackers: the list of all ConditionTrackers (this is needed because we may also - // need to call onConfigUpdated() on child conditions) - // atomMatchingTrackerMap: map of atom matcher id to index after the config update. - // conditionTrackerMap: map of condition tracker id to index after the config update. - // returns whether or not the update is successful. - virtual bool onConfigUpdated(const std::vector& allConditionProtos, const int index, - const std::vector>& allConditionTrackers, - const std::unordered_map& atomMatchingTrackerMap, - const std::unordered_map& conditionTrackerMap) { - mIndex = index; - return true; - } - - // evaluate current condition given the new event. - // event: the new log event - // eventMatcherValues: the results of the AtomMatchingTrackers. AtomMatchingTrackers always - // process event before ConditionTrackers, because ConditionTracker depends - // on AtomMatchingTrackers. - // mAllConditions: the list of all ConditionTracker - // conditionCache: the cached non-sliced condition of the ConditionTrackers for this new event. - // conditionChanged: the bit map to record whether the condition has changed. - // If the condition has dimension, then any sub condition changes will report - // conditionChanged. - virtual void evaluateCondition(const LogEvent& event, - const std::vector& eventMatcherValues, - const std::vector>& mAllConditions, - std::vector& conditionCache, - std::vector& conditionChanged) = 0; - - // Query the condition with parameters. - // [conditionParameters]: a map from condition name to the HashableDimensionKey to query the - // condition. - // [allConditions]: all condition trackers. This is needed because the condition evaluation is - // done recursively - // [isPartialLink]: true if the link specified by 'conditionParameters' contains all the fields - // in the condition tracker output dimension. - // [conditionCache]: the cache holding the condition evaluation values. - virtual void isConditionMet( - const ConditionKey& conditionParameters, - const std::vector>& allConditions, - const bool isPartialLink, - std::vector& conditionCache) const = 0; - - // return the list of AtomMatchingTracker index that this ConditionTracker uses. - virtual const std::set& getAtomMatchingTrackerIndex() const { - return mTrackerIndex; - } - - virtual void setSliced(bool sliced) { - mSliced = mSliced | sliced; - } - - inline bool isSliced() const { - return mSliced; - } - - virtual const std::set* getChangedToTrueDimensions( - const std::vector>& allConditions) const = 0; - virtual const std::set* getChangedToFalseDimensions( - const std::vector>& allConditions) const = 0; - - inline int64_t getConditionId() const { - return mConditionId; - } - - inline uint64_t getProtoHash() const { - return mProtoHash; - } - - virtual const std::map* getSlicedDimensionMap( - const std::vector>& allConditions) const = 0; - - virtual bool IsChangedDimensionTrackable() const = 0; - - virtual bool IsSimpleCondition() const = 0; - - virtual bool equalOutputDimensions( - const std::vector>& allConditions, - const vector& dimensions) const = 0; - - // Return the current condition state of the unsliced part of the condition. - inline ConditionState getUnSlicedPartConditionState() const { - return mUnSlicedPartCondition; - } - -protected: - const int64_t mConditionId; - - // the index of this condition in the manager's condition list. - int mIndex; - - // if it's properly initialized. - bool mInitialized; - - // the list of AtomMatchingTracker index that this ConditionTracker uses. - std::set mTrackerIndex; - - // This variable is only used for CombinationConditionTrackers. - // SimpleConditionTrackers technically don't have an unsliced part because - // they are either sliced or unsliced. - // - // CombinationConditionTrackers have multiple children ConditionTrackers - // that can be a mixture of sliced or unsliced. This tracks the - // condition of the unsliced part of the combination condition. - ConditionState mUnSlicedPartCondition; - - bool mSliced; - - // Hash of the Predicate's proto bytes from StatsdConfig. - // Used to determine if the definition of this condition has changed across a config update. - const uint64_t mProtoHash; - - FRIEND_TEST(ConfigUpdateTest, TestUpdateConditions); -}; - -} // namespace statsd -} // namespace os -} // namespace android diff --git a/bin/src/condition/ConditionWizard.cpp b/bin/src/condition/ConditionWizard.cpp deleted file mode 100644 index c542032b..00000000 --- a/bin/src/condition/ConditionWizard.cpp +++ /dev/null @@ -1,70 +0,0 @@ -/* - * 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 "ConditionWizard.h" - -namespace android { -namespace os { -namespace statsd { - -using std::vector; - -ConditionState ConditionWizard::query(const int index, const ConditionKey& parameters, - const bool isPartialLink) { - vector cache(mAllConditions.size(), ConditionState::kNotEvaluated); - - mAllConditions[index]->isConditionMet( - parameters, mAllConditions, isPartialLink, - cache); - return cache[index]; -} - -const set* ConditionWizard::getChangedToTrueDimensions( - const int index) const { - return mAllConditions[index]->getChangedToTrueDimensions(mAllConditions); -} - -const set* ConditionWizard::getChangedToFalseDimensions( - const int index) const { - return mAllConditions[index]->getChangedToFalseDimensions(mAllConditions); -} - -bool ConditionWizard::IsChangedDimensionTrackable(const int index) { - if (index >= 0 && index < (int)mAllConditions.size()) { - return mAllConditions[index]->IsChangedDimensionTrackable(); - } else { - return false; - } -} - -bool ConditionWizard::IsSimpleCondition(const int index) { - if (index >= 0 && index < (int)mAllConditions.size()) { - return mAllConditions[index]->IsSimpleCondition(); - } else { - return false; - } -} - -bool ConditionWizard::equalOutputDimensions(const int index, const vector& dimensions) { - if (index >= 0 && index < (int)mAllConditions.size()) { - return mAllConditions[index]->equalOutputDimensions(mAllConditions, dimensions); - } else { - return false; - } -} - -} // namespace statsd -} // namespace os -} // namespace android diff --git a/bin/src/condition/ConditionWizard.h b/bin/src/condition/ConditionWizard.h deleted file mode 100644 index 43db94cf..00000000 --- a/bin/src/condition/ConditionWizard.h +++ /dev/null @@ -1,68 +0,0 @@ -/* - * 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. - */ - -#ifndef CONDITION_WIZARD_H -#define CONDITION_WIZARD_H - -#include "ConditionTracker.h" -#include "condition_util.h" -#include "stats_util.h" - -namespace android { -namespace os { -namespace statsd { - -// Held by MetricProducer, to query a condition state with input defined in MetricConditionLink. -class ConditionWizard : public virtual android::RefBase { -public: - ConditionWizard(){}; // for testing - explicit ConditionWizard(std::vector>& conditionTrackers) - : mAllConditions(conditionTrackers){}; - - virtual ~ConditionWizard(){}; - - // Query condition state, for a ConditionTracker at [conditionIndex], with [conditionParameters] - // [conditionParameters] mapping from condition name to the HashableDimensionKey to query the - // condition. - // The ConditionTracker at [conditionIndex] can be a CombinationConditionTracker. In this case, - // the conditionParameters contains the parameters for it's children SimpleConditionTrackers. - virtual ConditionState query(const int conditionIndex, const ConditionKey& conditionParameters, - const bool isPartialLink); - - virtual const std::set* getChangedToTrueDimensions(const int index) const; - virtual const std::set* getChangedToFalseDimensions( - const int index) const; - bool equalOutputDimensions(const int index, const vector& dimensions); - - bool IsChangedDimensionTrackable(const int index); - bool IsSimpleCondition(const int index); - - ConditionState getUnSlicedPartConditionState(const int index) { - return mAllConditions[index]->getUnSlicedPartConditionState(); - } - - const std::map* getSlicedDimensionMap(const int index) const { - return mAllConditions[index]->getSlicedDimensionMap(mAllConditions); - } - -private: - std::vector> mAllConditions; -}; - -} // namespace statsd -} // namespace os -} // namespace android -#endif // CONDITION_WIZARD_H diff --git a/bin/src/condition/SimpleConditionTracker.cpp b/bin/src/condition/SimpleConditionTracker.cpp deleted file mode 100644 index 1dcc8f96..00000000 --- a/bin/src/condition/SimpleConditionTracker.cpp +++ /dev/null @@ -1,409 +0,0 @@ -/* - * 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. - */ - -#define DEBUG false // STOPSHIP if true -#include "Log.h" - -#include "SimpleConditionTracker.h" -#include "guardrail/StatsdStats.h" - -namespace android { -namespace os { -namespace statsd { - -using std::unordered_map; - -SimpleConditionTracker::SimpleConditionTracker( - const ConfigKey& key, const int64_t& id, const uint64_t protoHash, const int index, - const SimplePredicate& simplePredicate, - const unordered_map& atomMatchingTrackerMap) - : ConditionTracker(id, index, protoHash), - mConfigKey(key), - mContainANYPositionInInternalDimensions(false) { - VLOG("creating SimpleConditionTracker %lld", (long long)mConditionId); - mCountNesting = simplePredicate.count_nesting(); - - setMatcherIndices(simplePredicate, atomMatchingTrackerMap); - - if (simplePredicate.has_dimensions()) { - translateFieldMatcher(simplePredicate.dimensions(), &mOutputDimensions); - if (mOutputDimensions.size() > 0) { - mSliced = true; - } - mContainANYPositionInInternalDimensions = HasPositionANY(simplePredicate.dimensions()); - } - - if (simplePredicate.initial_value() == SimplePredicate_InitialValue_FALSE) { - mInitialValue = ConditionState::kFalse; - } else { - mInitialValue = ConditionState::kUnknown; - } - - mInitialized = true; -} - -SimpleConditionTracker::~SimpleConditionTracker() { - VLOG("~SimpleConditionTracker()"); -} - -bool SimpleConditionTracker::init(const vector& allConditionConfig, - const vector>& allConditionTrackers, - const unordered_map& conditionIdIndexMap, - vector& stack, vector& conditionCache) { - // SimpleConditionTracker does not have dependency on other conditions, thus we just return - // if the initialization was successful. - ConditionKey conditionKey; - if (mSliced) { - conditionKey[mConditionId] = DEFAULT_DIMENSION_KEY; - } - isConditionMet(conditionKey, allConditionTrackers, mSliced, conditionCache); - return mInitialized; -} - -bool SimpleConditionTracker::onConfigUpdated( - const vector& allConditionProtos, const int index, - const vector>& allConditionTrackers, - const unordered_map& atomMatchingTrackerMap, - const unordered_map& conditionTrackerMap) { - ConditionTracker::onConfigUpdated(allConditionProtos, index, allConditionTrackers, - atomMatchingTrackerMap, conditionTrackerMap); - setMatcherIndices(allConditionProtos[index].simple_predicate(), atomMatchingTrackerMap); - return true; -} - -void SimpleConditionTracker::setMatcherIndices( - const SimplePredicate& simplePredicate, - const unordered_map& atomMatchingTrackerMap) { - mTrackerIndex.clear(); - if (simplePredicate.has_start()) { - auto pair = atomMatchingTrackerMap.find(simplePredicate.start()); - if (pair == atomMatchingTrackerMap.end()) { - ALOGW("Start matcher %lld not found in the config", (long long)simplePredicate.start()); - return; - } - mStartLogMatcherIndex = pair->second; - mTrackerIndex.insert(mStartLogMatcherIndex); - } else { - mStartLogMatcherIndex = -1; - } - - if (simplePredicate.has_stop()) { - auto pair = atomMatchingTrackerMap.find(simplePredicate.stop()); - if (pair == atomMatchingTrackerMap.end()) { - ALOGW("Stop matcher %lld not found in the config", (long long)simplePredicate.stop()); - return; - } - mStopLogMatcherIndex = pair->second; - mTrackerIndex.insert(mStopLogMatcherIndex); - } else { - mStopLogMatcherIndex = -1; - } - - if (simplePredicate.has_stop_all()) { - auto pair = atomMatchingTrackerMap.find(simplePredicate.stop_all()); - if (pair == atomMatchingTrackerMap.end()) { - ALOGW("Stop all matcher %lld found in the config", - (long long)simplePredicate.stop_all()); - return; - } - mStopAllLogMatcherIndex = pair->second; - mTrackerIndex.insert(mStopAllLogMatcherIndex); - } else { - mStopAllLogMatcherIndex = -1; - } -} - -void SimpleConditionTracker::dumpState() { - VLOG("%lld DUMP:", (long long)mConditionId); - for (const auto& pair : mSlicedConditionState) { - VLOG("\t%s : %d", pair.first.toString().c_str(), pair.second); - } - - VLOG("Changed to true keys: \n"); - for (const auto& key : mLastChangedToTrueDimensions) { - VLOG("%s", key.toString().c_str()); - } - VLOG("Changed to false keys: \n"); - for (const auto& key : mLastChangedToFalseDimensions) { - VLOG("%s", key.toString().c_str()); - } -} - -void SimpleConditionTracker::handleStopAll(std::vector& conditionCache, - std::vector& conditionChangedCache) { - // Unless the default condition is false, and there was nothing started, otherwise we have - // triggered a condition change. - conditionChangedCache[mIndex] = - (mInitialValue == ConditionState::kFalse && mSlicedConditionState.empty()) ? false - : true; - - for (const auto& cond : mSlicedConditionState) { - if (cond.second > 0) { - mLastChangedToFalseDimensions.insert(cond.first); - } - } - - // After StopAll, we know everything has stopped. From now on, default condition is false. - mInitialValue = ConditionState::kFalse; - mSlicedConditionState.clear(); - conditionCache[mIndex] = ConditionState::kFalse; -} - -bool SimpleConditionTracker::hitGuardRail(const HashableDimensionKey& newKey) { - if (!mSliced || mSlicedConditionState.find(newKey) != mSlicedConditionState.end()) { - // if the condition is not sliced or the key is not new, we are good! - return false; - } - // 1. Report the tuple count if the tuple count > soft limit - if (mSlicedConditionState.size() > StatsdStats::kDimensionKeySizeSoftLimit - 1) { - size_t newTupleCount = mSlicedConditionState.size() + 1; - StatsdStats::getInstance().noteConditionDimensionSize(mConfigKey, mConditionId, newTupleCount); - // 2. Don't add more tuples, we are above the allowed threshold. Drop the data. - if (newTupleCount > StatsdStats::kDimensionKeySizeHardLimit) { - ALOGE("Predicate %lld dropping data for dimension key %s", - (long long)mConditionId, newKey.toString().c_str()); - return true; - } - } - return false; -} - -void SimpleConditionTracker::handleConditionEvent(const HashableDimensionKey& outputKey, - bool matchStart, ConditionState* conditionCache, - bool* conditionChangedCache) { - bool changed = false; - auto outputIt = mSlicedConditionState.find(outputKey); - ConditionState newCondition; - if (hitGuardRail(outputKey)) { - (*conditionChangedCache) = false; - // Tells the caller it's evaluated. - (*conditionCache) = ConditionState::kUnknown; - return; - } - if (outputIt == mSlicedConditionState.end()) { - // We get a new output key. - newCondition = matchStart ? ConditionState::kTrue : ConditionState::kFalse; - if (matchStart && mInitialValue != ConditionState::kTrue) { - mSlicedConditionState[outputKey] = 1; - changed = true; - mLastChangedToTrueDimensions.insert(outputKey); - } else if (mInitialValue != ConditionState::kFalse) { - // it's a stop and we don't have history about it. - // If the default condition is not false, it means this stop is valuable to us. - mSlicedConditionState[outputKey] = 0; - mLastChangedToFalseDimensions.insert(outputKey); - changed = true; - } - } else { - // we have history about this output key. - auto& startedCount = outputIt->second; - // assign the old value first. - newCondition = startedCount > 0 ? ConditionState::kTrue : ConditionState::kFalse; - if (matchStart) { - if (startedCount == 0) { - mLastChangedToTrueDimensions.insert(outputKey); - // This condition for this output key will change from false -> true - changed = true; - } - - // it's ok to do ++ here, even if we don't count nesting. The >1 counts will be treated - // as 1 if not counting nesting. - startedCount++; - newCondition = ConditionState::kTrue; - } else { - // This is a stop event. - if (startedCount > 0) { - if (mCountNesting) { - startedCount--; - if (startedCount == 0) { - newCondition = ConditionState::kFalse; - } - } else { - // not counting nesting, so ignore the number of starts, stop now. - startedCount = 0; - newCondition = ConditionState::kFalse; - } - // if everything has stopped for this output key, condition true -> false; - if (startedCount == 0) { - mLastChangedToFalseDimensions.insert(outputKey); - changed = true; - } - } - - // if default condition is false, it means we don't need to keep the false values. - if (mInitialValue == ConditionState::kFalse && startedCount == 0) { - mSlicedConditionState.erase(outputIt); - VLOG("erase key %s", outputKey.toString().c_str()); - } - } - } - - // dump all dimensions for debugging - if (DEBUG) { - dumpState(); - } - - (*conditionChangedCache) = changed; - (*conditionCache) = newCondition; - - VLOG("SimplePredicate %lld nonSlicedChange? %d", (long long)mConditionId, - conditionChangedCache[mIndex] == true); -} - -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", - (long long)mConditionId, conditionCache[mIndex]); - return; - } - mLastChangedToTrueDimensions.clear(); - mLastChangedToFalseDimensions.clear(); - - if (mStopAllLogMatcherIndex >= 0 && mStopAllLogMatcherIndex < int(eventMatcherValues.size()) && - eventMatcherValues[mStopAllLogMatcherIndex] == MatchingState::kMatched) { - handleStopAll(conditionCache, conditionChangedCache); - return; - } - - int matchedState = -1; - // Note: The order to evaluate the following start, stop, stop_all matters. - // The priority of overwrite is stop_all > stop > start. - if (mStartLogMatcherIndex >= 0 && - eventMatcherValues[mStartLogMatcherIndex] == MatchingState::kMatched) { - matchedState = 1; - } - - if (mStopLogMatcherIndex >= 0 && - eventMatcherValues[mStopLogMatcherIndex] == MatchingState::kMatched) { - matchedState = 0; - } - - if (matchedState < 0) { - // The event doesn't match this condition. So we just report existing condition values. - conditionChangedCache[mIndex] = false; - if (mSliced) { - // if the condition result is sliced. The overall condition is true if any of the sliced - // condition is true - conditionCache[mIndex] = mInitialValue; - for (const auto& slicedCondition : mSlicedConditionState) { - if (slicedCondition.second > 0) { - conditionCache[mIndex] = ConditionState::kTrue; - break; - } - } - } else { - const auto& itr = mSlicedConditionState.find(DEFAULT_DIMENSION_KEY); - if (itr == mSlicedConditionState.end()) { - // condition not sliced, but we haven't seen the matched start or stop yet. so - // return initial value. - conditionCache[mIndex] = mInitialValue; - } else { - // return the cached condition. - conditionCache[mIndex] = - itr->second > 0 ? ConditionState::kTrue : ConditionState::kFalse; - } - } - return; - } - - ConditionState overallState = mInitialValue; - bool overallChanged = false; - - if (mOutputDimensions.size() == 0) { - handleConditionEvent(DEFAULT_DIMENSION_KEY, matchedState == 1, &overallState, - &overallChanged); - } else if (!mContainANYPositionInInternalDimensions) { - HashableDimensionKey outputValue; - filterValues(mOutputDimensions, event.getValues(), &outputValue); - - // If this event has multiple nodes in the attribution chain, this log event probably will - // generate multiple dimensions. If so, we will find if the condition changes for any - // dimension and ask the corresponding metric producer to verify whether the actual sliced - // condition has changed or not. - // A high level assumption is that a predicate is either sliced or unsliced. We will never - // have both sliced and unsliced version of a predicate. - handleConditionEvent(outputValue, matchedState == 1, &overallState, &overallChanged); - } else { - ALOGE("The condition tracker should not be sliced by ANY position matcher."); - } - conditionCache[mIndex] = overallState; - conditionChangedCache[mIndex] = overallChanged; -} - -void SimpleConditionTracker::isConditionMet( - const ConditionKey& conditionParameters, const vector>& allConditions, - const bool isPartialLink, - vector& conditionCache) const { - - if (conditionCache[mIndex] != ConditionState::kNotEvaluated) { - // it has been evaluated. - VLOG("Yes, already evaluated, %lld %d", - (long long)mConditionId, conditionCache[mIndex]); - return; - } - const auto pair = conditionParameters.find(mConditionId); - - if (pair == conditionParameters.end()) { - ConditionState conditionState = ConditionState::kNotEvaluated; - 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; - } - - ConditionState conditionState = ConditionState::kNotEvaluated; - const HashableDimensionKey& key = pair->second; - if (isPartialLink) { - // For unseen key, check whether the require dimensions are subset of sliced condition - // output. - conditionState = conditionState | mInitialValue; - for (const auto& slice : mSlicedConditionState) { - ConditionState sliceState = - slice.second > 0 ? ConditionState::kTrue : ConditionState::kFalse; - if (slice.first.contains(key)) { - conditionState = conditionState | sliceState; - } - } - } else { - auto startedCountIt = mSlicedConditionState.find(key); - conditionState = conditionState | mInitialValue; - if (startedCountIt != mSlicedConditionState.end()) { - ConditionState sliceState = - startedCountIt->second > 0 ? ConditionState::kTrue : ConditionState::kFalse; - conditionState = conditionState | sliceState; - } - - } - conditionCache[mIndex] = conditionState; - VLOG("Predicate %lld return %d", (long long)mConditionId, conditionCache[mIndex]); -} - -} // namespace statsd -} // namespace os -} // namespace android diff --git a/bin/src/condition/SimpleConditionTracker.h b/bin/src/condition/SimpleConditionTracker.h deleted file mode 100644 index 4daec06b..00000000 --- a/bin/src/condition/SimpleConditionTracker.h +++ /dev/null @@ -1,140 +0,0 @@ -/* - * 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. - */ - -#ifndef SIMPLE_CONDITION_TRACKER_H -#define SIMPLE_CONDITION_TRACKER_H - -#include -#include "ConditionTracker.h" -#include "config/ConfigKey.h" -#include "packages/modules/StatsD/bin/src/statsd_config.pb.h" -#include "stats_util.h" - -namespace android { -namespace os { -namespace statsd { - -class SimpleConditionTracker : public ConditionTracker { -public: - SimpleConditionTracker(const ConfigKey& key, const int64_t& id, const uint64_t protoHash, - const int index, const SimplePredicate& simplePredicate, - const std::unordered_map& atomMatchingTrackerMap); - - ~SimpleConditionTracker(); - - bool init(const std::vector& allConditionConfig, - const std::vector>& allConditionTrackers, - const std::unordered_map& conditionIdIndexMap, std::vector& stack, - std::vector& conditionCache) override; - - bool onConfigUpdated(const std::vector& allConditionProtos, const int index, - const std::vector>& allConditionTrackers, - const std::unordered_map& atomMatchingTrackerMap, - const std::unordered_map& conditionTrackerMap) override; - - void evaluateCondition(const LogEvent& event, - const std::vector& eventMatcherValues, - const std::vector>& mAllConditions, - std::vector& conditionCache, - std::vector& changedCache) override; - - void isConditionMet(const ConditionKey& conditionParameters, - const std::vector>& allConditions, - const bool isPartialLink, - std::vector& conditionCache) const override; - - virtual const std::set* getChangedToTrueDimensions( - const std::vector>& allConditions) const { - if (mSliced) { - return &mLastChangedToTrueDimensions; - } else { - return nullptr; - } - } - - virtual const std::set* getChangedToFalseDimensions( - const std::vector>& allConditions) const { - if (mSliced) { - return &mLastChangedToFalseDimensions; - } else { - return nullptr; - } - } - - const std::map* getSlicedDimensionMap( - const std::vector>& allConditions) const override { - return &mSlicedConditionState; - } - - bool IsChangedDimensionTrackable() const override { return true; } - - bool IsSimpleCondition() const override { return true; } - - bool equalOutputDimensions( - const std::vector>& allConditions, - const vector& dimensions) const override { - return equalDimensions(mOutputDimensions, dimensions); - } - -private: - const ConfigKey mConfigKey; - // The index of the LogEventMatcher which defines the start. - int mStartLogMatcherIndex; - - // The index of the LogEventMatcher which defines the end. - int mStopLogMatcherIndex; - - // if the start end needs to be nested. - bool mCountNesting; - - // The index of the LogEventMatcher which defines the stop all. - int mStopAllLogMatcherIndex; - - ConditionState mInitialValue; - - std::vector mOutputDimensions; - - bool mContainANYPositionInInternalDimensions; - - std::set mLastChangedToTrueDimensions; - std::set mLastChangedToFalseDimensions; - - std::map mSlicedConditionState; - - void setMatcherIndices(const SimplePredicate& predicate, - const std::unordered_map& logTrackerMap); - - void handleStopAll(std::vector& conditionCache, - std::vector& changedCache); - - void handleConditionEvent(const HashableDimensionKey& outputKey, bool matchStart, - ConditionState* conditionCache, bool* changedCache); - - bool hitGuardRail(const HashableDimensionKey& newKey); - - void dumpState(); - - FRIEND_TEST(SimpleConditionTrackerTest, TestSlicedCondition); - FRIEND_TEST(SimpleConditionTrackerTest, TestSlicedWithNoOutputDim); - FRIEND_TEST(SimpleConditionTrackerTest, TestStopAll); - FRIEND_TEST(ConfigUpdateTest, TestUpdateConditions); -}; - -} // namespace statsd -} // namespace os -} // namespace android - -#endif // SIMPLE_CONDITION_TRACKER_H diff --git a/bin/src/condition/condition_util.cpp b/bin/src/condition/condition_util.cpp deleted file mode 100644 index 92f07481..00000000 --- a/bin/src/condition/condition_util.cpp +++ /dev/null @@ -1,93 +0,0 @@ -/* - * 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 "Log.h" - -#include "condition_util.h" - -#include "../matchers/matcher_util.h" -#include "ConditionTracker.h" -#include "packages/modules/StatsD/bin/src/statsd_config.pb.h" -#include "stats_util.h" - -namespace android { -namespace os { -namespace statsd { - -using std::vector; - - -ConditionState evaluateCombinationCondition(const std::vector& children, - const LogicalOperation& operation, - const std::vector& conditionCache) { - ConditionState newCondition; - - bool hasUnknown = false; - bool hasFalse = false; - bool hasTrue = false; - - for (auto childIndex : children) { - ConditionState childState = conditionCache[childIndex]; - if (childState == ConditionState::kUnknown) { - hasUnknown = true; - break; - } - if (childState == ConditionState::kFalse) { - hasFalse = true; - } - if (childState == ConditionState::kTrue) { - hasTrue = true; - } - } - - // If any child condition is in unknown state, the condition is unknown too. - if (hasUnknown) { - return ConditionState::kUnknown; - } - - switch (operation) { - case LogicalOperation::AND: { - newCondition = hasFalse ? ConditionState::kFalse : ConditionState::kTrue; - break; - } - case LogicalOperation::OR: { - newCondition = hasTrue ? ConditionState::kTrue : ConditionState::kFalse; - break; - } - case LogicalOperation::NOT: - newCondition = children.empty() ? ConditionState::kUnknown : - ((conditionCache[children[0]] == ConditionState::kFalse) ? - ConditionState::kTrue : ConditionState::kFalse); - break; - case LogicalOperation::NAND: - newCondition = hasFalse ? ConditionState::kTrue : ConditionState::kFalse; - break; - case LogicalOperation::NOR: - newCondition = hasTrue ? ConditionState::kFalse : ConditionState::kTrue; - break; - case LogicalOperation::LOGICAL_OPERATION_UNSPECIFIED: - newCondition = ConditionState::kFalse; - break; - } - return newCondition; -} - -ConditionState operator|(ConditionState l, ConditionState r) { - return l >= r ? l : r; -} -} // namespace statsd -} // namespace os -} // namespace android diff --git a/bin/src/condition/condition_util.h b/bin/src/condition/condition_util.h deleted file mode 100644 index 89694467..00000000 --- a/bin/src/condition/condition_util.h +++ /dev/null @@ -1,43 +0,0 @@ -/* - * 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. - */ - -#ifndef CONDITION_UTIL_H -#define CONDITION_UTIL_H - -#include -#include "../matchers/matcher_util.h" -#include "packages/modules/StatsD/bin/src/statsd_config.pb.h" - -namespace android { -namespace os { -namespace statsd { - -enum ConditionState { - kNotEvaluated = -2, - kUnknown = -1, - kFalse = 0, - kTrue = 1, -}; - -ConditionState operator|(ConditionState l, ConditionState r); - -ConditionState evaluateCombinationCondition(const std::vector& children, - const LogicalOperation& operation, - const std::vector& conditionCache); -} // namespace statsd -} // namespace os -} // namespace android -#endif // CONDITION_UTIL_H diff --git a/bin/src/config/ConfigKey.cpp b/bin/src/config/ConfigKey.cpp deleted file mode 100644 index 4a2bd279..00000000 --- a/bin/src/config/ConfigKey.cpp +++ /dev/null @@ -1,54 +0,0 @@ -/* - * 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 "config/ConfigKey.h" - -namespace android { -namespace os { -namespace statsd { - -ConfigKey::ConfigKey() { -} - -ConfigKey::ConfigKey(const ConfigKey& that) : mId(that.mId), mUid(that.mUid) { -} - -ConfigKey::ConfigKey(int uid, const int64_t& id) : mId(id), mUid(uid) { -} - -ConfigKey::~ConfigKey() { -} - -string ConfigKey::ToString() const { - string s; - s += "(" + std::to_string(mUid) + " " + std::to_string(mId) + ")"; - return s; -} - - -int64_t StrToInt64(const string& str) { - char* endp; - int64_t value; - value = strtoll(str.c_str(), &endp, 0); - if (endp == str.c_str() || *endp != '\0') { - value = 0; - } - return value; -} - -} // namespace statsd -} // namespace os -} // namespace android diff --git a/bin/src/config/ConfigKey.h b/bin/src/config/ConfigKey.h deleted file mode 100644 index 5cd010cd..00000000 --- a/bin/src/config/ConfigKey.h +++ /dev/null @@ -1,89 +0,0 @@ -/* - * 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. - */ - -#pragma once - -#include "packages/modules/StatsD/bin/src/statsd_config.pb.h" - -#include - -namespace android { -namespace os { -namespace statsd { - -using std::hash; -using std::string; - -/** - * Uniquely identifies a configuration. - */ -class ConfigKey { -public: - ConfigKey(); - ConfigKey(const ConfigKey& that); - ConfigKey(int uid, const int64_t& id); - ~ConfigKey(); - - inline int GetUid() const { - return mUid; - } - inline const int64_t& GetId() const { - return mId; - } - - inline bool operator<(const ConfigKey& that) const { - if (mUid < that.mUid) { - return true; - } - if (mUid > that.mUid) { - return false; - } - return mId < that.mId; - }; - - inline bool operator==(const ConfigKey& that) const { - return mUid == that.mUid && mId == that.mId; - }; - - string ToString() const; - -private: - int64_t mId; - int mUid; -}; - -int64_t StrToInt64(const string& str); - -} // namespace statsd -} // namespace os -} // namespace android - -/** - * A hash function for ConfigKey so it can be used for unordered_map/set. - * Unfortunately this has to go in std namespace because C++ is fun! - */ -namespace std { - -using android::os::statsd::ConfigKey; - -template <> -struct hash { - std::size_t operator()(const ConfigKey& key) const { - return (7 * key.GetUid()) ^ ((hash()(key.GetId()))); - } -}; - -} // namespace std diff --git a/bin/src/config/ConfigListener.cpp b/bin/src/config/ConfigListener.cpp deleted file mode 100644 index 21a3f167..00000000 --- a/bin/src/config/ConfigListener.cpp +++ /dev/null @@ -1,31 +0,0 @@ -/* - * 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 "config/ConfigListener.h" - -namespace android { -namespace os { -namespace statsd { - -ConfigListener::ConfigListener() { -} - -ConfigListener::~ConfigListener() { -} - -} // namespace statsd -} // namespace os -} // namespace android diff --git a/bin/src/config/ConfigListener.h b/bin/src/config/ConfigListener.h deleted file mode 100644 index 4f71a4e1..00000000 --- a/bin/src/config/ConfigListener.h +++ /dev/null @@ -1,52 +0,0 @@ -/* - * 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. - */ - -#pragma once - -#include "config/ConfigKey.h" - -#include - -namespace android { -namespace os { -namespace statsd { - -using android::RefBase; - -/** - * Callback for different subsystems inside statsd to implement to find out - * when a configuration has been added, updated or removed. - */ -class ConfigListener : public virtual RefBase { -public: - ConfigListener(); - virtual ~ConfigListener(); - - /** - * A configuration was added or updated. - */ - virtual void OnConfigUpdated(const int64_t timestampNs, const ConfigKey& key, - const StatsdConfig& config, bool modularUpdate = true) = 0; - - /** - * A configuration was removed. - */ - virtual void OnConfigRemoved(const ConfigKey& key) = 0; -}; - -} // namespace statsd -} // namespace os -} // namespace android diff --git a/bin/src/config/ConfigManager.cpp b/bin/src/config/ConfigManager.cpp deleted file mode 100644 index 13020e06..00000000 --- a/bin/src/config/ConfigManager.cpp +++ /dev/null @@ -1,375 +0,0 @@ -/* - * 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. - */ - -#define DEBUG false // STOPSHIP if true -#include "Log.h" - -#include "config/ConfigManager.h" -#include "storage/StorageManager.h" - -#include "guardrail/StatsdStats.h" -#include "stats_log_util.h" -#include "stats_util.h" -#include "stats_log_util.h" - -#include -#include -#include "android-base/stringprintf.h" - -namespace android { -namespace os { -namespace statsd { - -using std::pair; -using std::string; -using std::vector; - -#define STATS_SERVICE_DIR "/data/misc/stats-service" - -using android::base::StringPrintf; -using std::unique_ptr; - -struct ConfigReceiverDeathCookie { - ConfigReceiverDeathCookie(const wp& configManager, const ConfigKey& configKey, - const shared_ptr& pir) : - mConfigManager(configManager), mConfigKey(configKey), mPir(pir) { - } - - wp mConfigManager; - ConfigKey mConfigKey; - shared_ptr mPir; -}; - -void ConfigManager::configReceiverDied(void* cookie) { - auto cookie_ = static_cast(cookie); - sp thiz = cookie_->mConfigManager.promote(); - if (!thiz) { - return; - } - - ConfigKey& configKey = cookie_->mConfigKey; - shared_ptr& pir = cookie_->mPir; - - // Erase the mapping from the config key to the config receiver (pir) if the - // mapping still exists. - lock_guard lock(thiz->mMutex); - auto it = thiz->mConfigReceivers.find(configKey); - if (it != thiz->mConfigReceivers.end() && it->second == pir) { - thiz->mConfigReceivers.erase(configKey); - } - - // The death recipient corresponding to this specific pir can never be - // triggered again, so free up resources. - delete cookie_; -} - -struct ActiveConfigChangedReceiverDeathCookie { - ActiveConfigChangedReceiverDeathCookie(const wp& configManager, const int uid, - const shared_ptr& pir) : - mConfigManager(configManager), mUid(uid), mPir(pir) { - } - - wp mConfigManager; - int mUid; - shared_ptr mPir; -}; - -void ConfigManager::activeConfigChangedReceiverDied(void* cookie) { - auto cookie_ = static_cast(cookie); - sp thiz = cookie_->mConfigManager.promote(); - if (!thiz) { - return; - } - - int uid = cookie_->mUid; - shared_ptr& pir = cookie_->mPir; - - // Erase the mapping from the config key to the active config changed - // receiver (pir) if the mapping still exists. - lock_guard lock(thiz->mMutex); - auto it = thiz->mActiveConfigsChangedReceivers.find(uid); - if (it != thiz->mActiveConfigsChangedReceivers.end() && it->second == pir) { - thiz->mActiveConfigsChangedReceivers.erase(uid); - } - - // The death recipient corresponding to this specific pir can never - // be triggered again, so free up resources. - delete cookie_; -} - -ConfigManager::ConfigManager() : - mConfigReceiverDeathRecipient(AIBinder_DeathRecipient_new(configReceiverDied)), - mActiveConfigChangedReceiverDeathRecipient( - AIBinder_DeathRecipient_new(activeConfigChangedReceiverDied)) { -} - -ConfigManager::~ConfigManager() { -} - -void ConfigManager::Startup() { - map configsFromDisk; - StorageManager::readConfigFromDisk(configsFromDisk); - for (const auto& pair : configsFromDisk) { - UpdateConfig(pair.first, pair.second); - } -} - -void ConfigManager::StartupForTest() { - // No-op function to avoid reading configs from disks for tests. -} - -void ConfigManager::AddListener(const sp& listener) { - lock_guard lock(mMutex); - mListeners.push_back(listener); -} - -void ConfigManager::UpdateConfig(const ConfigKey& key, const StatsdConfig& config) { - vector> broadcastList; - { - lock_guard lock(mMutex); - - const int numBytes = config.ByteSize(); - vector buffer(numBytes); - config.SerializeToArray(&buffer[0], numBytes); - - auto uidIt = mConfigs.find(key.GetUid()); - // GuardRail: Limit the number of configs per uid. - if (uidIt != mConfigs.end()) { - auto it = uidIt->second.find(key); - if (it == uidIt->second.end() && - uidIt->second.size() >= StatsdStats::kMaxConfigCountPerUid) { - ALOGE("ConfigManager: uid %d has exceeded the config count limit", key.GetUid()); - return; - } - } - - // Check if it's a duplicate config. - if (uidIt != mConfigs.end() && uidIt->second.find(key) != uidIt->second.end() && - StorageManager::hasIdenticalConfig(key, buffer)) { - // This is a duplicate config. - ALOGI("ConfigManager This is a duplicate config %s", key.ToString().c_str()); - // Update saved file on disk. We still update timestamp of file when - // there exists a duplicate configuration to avoid garbage collection. - update_saved_configs_locked(key, buffer, numBytes); - return; - } - - // Update saved file on disk. - update_saved_configs_locked(key, buffer, numBytes); - - // Add to set. - mConfigs[key.GetUid()].insert(key); - - for (const sp& listener : mListeners) { - broadcastList.push_back(listener); - } - } - - const int64_t timestampNs = getElapsedRealtimeNs(); - // Tell everyone - for (const sp& listener : broadcastList) { - listener->OnConfigUpdated(timestampNs, key, config); - } -} - -void ConfigManager::SetConfigReceiver(const ConfigKey& key, - const shared_ptr& pir) { - lock_guard lock(mMutex); - mConfigReceivers[key] = pir; - AIBinder_linkToDeath(pir->asBinder().get(), mConfigReceiverDeathRecipient.get(), - new ConfigReceiverDeathCookie(this, key, pir)); -} - -void ConfigManager::RemoveConfigReceiver(const ConfigKey& key) { - lock_guard lock(mMutex); - mConfigReceivers.erase(key); -} - -void ConfigManager::SetActiveConfigsChangedReceiver(const int uid, - const shared_ptr& pir) { - { - lock_guard lock(mMutex); - mActiveConfigsChangedReceivers[uid] = pir; - } - AIBinder_linkToDeath(pir->asBinder().get(), mActiveConfigChangedReceiverDeathRecipient.get(), - new ActiveConfigChangedReceiverDeathCookie(this, uid, pir)); -} - -void ConfigManager::RemoveActiveConfigsChangedReceiver(const int uid) { - lock_guard lock(mMutex); - mActiveConfigsChangedReceivers.erase(uid); -} - -void ConfigManager::RemoveConfig(const ConfigKey& key) { - vector> broadcastList; - { - lock_guard lock(mMutex); - - auto uid = key.GetUid(); - auto uidIt = mConfigs.find(uid); - if (uidIt != mConfigs.end() && uidIt->second.find(key) != uidIt->second.end()) { - // Remove from map - uidIt->second.erase(key); - - for (const sp& listener : mListeners) { - broadcastList.push_back(listener); - } - } - - // Remove from disk. There can still be a lingering file on disk so we check - // whether or not the config was on memory. - remove_saved_configs(key); - } - - for (const sp& listener:broadcastList) { - listener->OnConfigRemoved(key); - } -} - -void ConfigManager::remove_saved_configs(const ConfigKey& key) { - string suffix = StringPrintf("%d_%lld", key.GetUid(), (long long)key.GetId()); - StorageManager::deleteSuffixedFiles(STATS_SERVICE_DIR, suffix.c_str()); -} - -void ConfigManager::RemoveConfigs(int uid) { - vector removed; - vector> broadcastList; - { - lock_guard lock(mMutex); - - auto uidIt = mConfigs.find(uid); - if (uidIt == mConfigs.end()) { - return; - } - - for (auto it = uidIt->second.begin(); it != uidIt->second.end(); ++it) { - // Remove from map - remove_saved_configs(*it); - removed.push_back(*it); - } - - mConfigs.erase(uidIt); - - for (const sp& listener : mListeners) { - broadcastList.push_back(listener); - } - } - - // Remove separately so if they do anything in the callback they can't mess up our iteration. - for (auto& key : removed) { - // Tell everyone - for (const sp& listener:broadcastList) { - listener->OnConfigRemoved(key); - } - } -} - -void ConfigManager::RemoveAllConfigs() { - vector removed; - vector> broadcastList; - { - lock_guard lock(mMutex); - - for (auto uidIt = mConfigs.begin(); uidIt != mConfigs.end();) { - for (auto it = uidIt->second.begin(); it != uidIt->second.end();) { - // Remove from map - removed.push_back(*it); - it = uidIt->second.erase(it); - } - uidIt = mConfigs.erase(uidIt); - } - - for (const sp& listener : mListeners) { - broadcastList.push_back(listener); - } - } - - // Remove separately so if they do anything in the callback they can't mess up our iteration. - for (auto& key : removed) { - // Tell everyone - for (const sp& listener:broadcastList) { - listener->OnConfigRemoved(key); - } - } -} - -vector ConfigManager::GetAllConfigKeys() const { - lock_guard lock(mMutex); - - vector ret; - for (auto uidIt = mConfigs.cbegin(); uidIt != mConfigs.cend(); ++uidIt) { - for (auto it = uidIt->second.cbegin(); it != uidIt->second.cend(); ++it) { - ret.push_back(*it); - } - } - return ret; -} - -const shared_ptr ConfigManager::GetConfigReceiver(const ConfigKey& key) const { - lock_guard lock(mMutex); - - auto it = mConfigReceivers.find(key); - if (it == mConfigReceivers.end()) { - return nullptr; - } else { - return it->second; - } -} - -const shared_ptr ConfigManager::GetActiveConfigsChangedReceiver(const int uid) - const { - lock_guard lock(mMutex); - - auto it = mActiveConfigsChangedReceivers.find(uid); - if (it == mActiveConfigsChangedReceivers.end()) { - return nullptr; - } else { - return it->second; - } -} - -void ConfigManager::Dump(FILE* out) { - lock_guard lock(mMutex); - - fprintf(out, "CONFIGURATIONS\n"); - fprintf(out, " uid name\n"); - for (auto uidIt = mConfigs.cbegin(); uidIt != mConfigs.cend(); ++uidIt) { - for (auto it = uidIt->second.cbegin(); it != uidIt->second.cend(); ++it) { - fprintf(out, " %6d %lld\n", it->GetUid(), (long long)it->GetId()); - auto receiverIt = mConfigReceivers.find(*it); - if (receiverIt != mConfigReceivers.end()) { - fprintf(out, " -> received by PendingIntent as binder\n"); - } - } - } -} - -void ConfigManager::update_saved_configs_locked(const ConfigKey& key, - const vector& buffer, - const int numBytes) { - // If there is a pre-existing config with same key we should first delete it. - remove_saved_configs(key); - - // Then we save the latest config. - string file_name = - StringPrintf("%s/%ld_%d_%lld", STATS_SERVICE_DIR, time(nullptr), - key.GetUid(), (long long)key.GetId()); - StorageManager::writeFile(file_name.c_str(), &buffer[0], numBytes); -} - -} // namespace statsd -} // namespace os -} // namespace android diff --git a/bin/src/config/ConfigManager.h b/bin/src/config/ConfigManager.h deleted file mode 100644 index bef057f9..00000000 --- a/bin/src/config/ConfigManager.h +++ /dev/null @@ -1,181 +0,0 @@ -/* - * 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. - */ - -#pragma once - -#include "config/ConfigKey.h" -#include "config/ConfigListener.h" - -#include -#include -#include - -#include - -using aidl::android::os::IPendingIntentRef; -using std::shared_ptr; - -namespace android { -namespace os { -namespace statsd { - -/** - * Keeps track of which configurations have been set from various sources. - */ -class ConfigManager : public virtual android::RefBase { -public: - ConfigManager(); - virtual ~ConfigManager(); - - /** - * Initialize ConfigListener by reading from disk and get updates. - */ - void Startup(); - - /* - * No-op initializer for tests. - */ - void StartupForTest(); - - /** - * Someone else wants to know about the configs. - */ - void AddListener(const sp& listener); - - /** - * A configuration was added or updated. - * - * Reports this to listeners. - */ - void UpdateConfig(const ConfigKey& key, const StatsdConfig& data); - - /** - * Sets the broadcast receiver for a configuration key. - */ - void SetConfigReceiver(const ConfigKey& key, const shared_ptr& pir); - - /** - * Returns the package name and class name representing the broadcast receiver for this config. - */ - const shared_ptr GetConfigReceiver(const ConfigKey& key) const; - - /** - * Returns all config keys registered. - */ - std::vector GetAllConfigKeys() const; - - /** - * Erase any broadcast receiver associated with this config key. - */ - void RemoveConfigReceiver(const ConfigKey& key); - - /** - * Sets the broadcast receiver that is notified whenever the list of active configs - * changes for this uid. - */ - void SetActiveConfigsChangedReceiver(const int uid, const shared_ptr& pir); - - /** - * Returns the broadcast receiver for active configs changed for this uid. - */ - - const shared_ptr GetActiveConfigsChangedReceiver(const int uid) const; - - /** - * Erase any active configs changed broadcast receiver associated with this uid. - */ - void RemoveActiveConfigsChangedReceiver(const int uid); - - /** - * A configuration was removed. - * - * Reports this to listeners. - */ - void RemoveConfig(const ConfigKey& key); - - /** - * Remove all of the configs for the given uid. - */ - void RemoveConfigs(int uid); - - /** - * Remove all of the configs from memory. - */ - void RemoveAllConfigs(); - - /** - * Text dump of our state for debugging. - */ - void Dump(FILE* out); - -private: - mutable std::mutex mMutex; - - /** - * Save the configs to disk. - */ - void update_saved_configs_locked(const ConfigKey& key, - const std::vector& buffer, - const int numBytes); - - /** - * Remove saved configs from disk. - */ - void remove_saved_configs(const ConfigKey& key); - - /** - * Maps from uid to the config keys that have been set. - */ - std::map> mConfigs; - - /** - * Each config key can be subscribed by up to one receiver, specified as IPendingIntentRef. - */ - std::map> mConfigReceivers; - - /** - * Each uid can be subscribed by up to one receiver to notify that the list of active configs - * for this uid has changed. The receiver is specified as IPendingIntentRef. - */ - std::map> mActiveConfigsChangedReceivers; - - /** - * The ConfigListeners that will be told about changes. - */ - std::vector> mListeners; - - // Death recipients that are triggered when the host process holding an - // IPendingIntentRef dies. - ::ndk::ScopedAIBinder_DeathRecipient mConfigReceiverDeathRecipient; - ::ndk::ScopedAIBinder_DeathRecipient mActiveConfigChangedReceiverDeathRecipient; - - /** - * Death recipient callback that is called when a config receiver dies. - * The cookie is a pointer to a ConfigReceiverDeathCookie. - */ - static void configReceiverDied(void* cookie); - - /** - * Death recipient callback that is called when an active config changed - * receiver dies. The cookie is a pointer to an - * ActiveConfigChangedReceiverDeathCookie. - */ - static void activeConfigChangedReceiverDied(void* cookie); -}; - -} // namespace statsd -} // namespace os -} // namespace android diff --git a/bin/src/experiment_ids.proto b/bin/src/experiment_ids.proto deleted file mode 100644 index c2036314..00000000 --- a/bin/src/experiment_ids.proto +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright (C) 2020 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. - */ - -syntax = "proto2"; - -package android.os.statsd; - -option java_package = "com.android.internal.os"; -option java_outer_classname = "ExperimentIdsProto"; - -// StatsLogProcessor uses the proto to parse experiment ids from -// BinaryPushStateChanged atoms. This needs to be in sync with -// TrainExperimentIds in atoms.proto. -message ExperimentIds { - repeated int64 experiment_id = 1; -} diff --git a/bin/src/external/Perfetto.cpp b/bin/src/external/Perfetto.cpp deleted file mode 100644 index e714c23b..00000000 --- a/bin/src/external/Perfetto.cpp +++ /dev/null @@ -1,139 +0,0 @@ -/* - * Copyright (C) 2018 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. - */ - -#define DEBUG false // STOPSHIP if true -#include "config/ConfigKey.h" -#include "Log.h" - -#include "packages/modules/StatsD/bin/src/statsd_config.pb.h" // Alert - -#include -#include -#include - -#include - -namespace { -const char kDropboxTag[] = "perfetto"; -} - -namespace android { -namespace os { -namespace statsd { - -bool CollectPerfettoTraceAndUploadToDropbox(const PerfettoDetails& config, - int64_t subscription_id, - int64_t alert_id, - const ConfigKey& configKey) { - VLOG("Starting trace collection through perfetto"); - - if (!config.has_trace_config()) { - ALOGE("The perfetto trace config is empty, aborting"); - return false; - } - - char subscriptionId[25]; - char alertId[25]; - char configId[25]; - char configUid[25]; - snprintf(subscriptionId, sizeof(subscriptionId), "%" PRId64, subscription_id); - snprintf(alertId, sizeof(alertId), "%" PRId64, alert_id); - snprintf(configId, sizeof(configId), "%" PRId64, configKey.GetId()); - snprintf(configUid, sizeof(configUid), "%d", configKey.GetUid()); - - android::base::unique_fd readPipe; - android::base::unique_fd writePipe; - if (!android::base::Pipe(&readPipe, &writePipe)) { - ALOGE("pipe() failed while calling the Perfetto client: %s", strerror(errno)); - return false; - } - - pid_t pid = fork(); - if (pid < 0) { - ALOGE("fork() failed while calling the Perfetto client: %s", strerror(errno)); - return false; - } - - if (pid == 0) { - // Child process. - - // No malloc calls or library calls after this point. Remember that even - // ALOGx (aka android_printLog()) can use dynamic memory for vsprintf(). - - writePipe.reset(); // Close the write end (owned by the main process). - - // Replace stdin with |readPipe| so the main process can write into it. - if (dup2(readPipe.get(), STDIN_FILENO) < 0) _exit(1); - readPipe.reset(); - - // Replace stdout/stderr with /dev/null and close any other file - // descriptor. This is to avoid SELinux complaining about perfetto - // trying to access files accidentally left open by statsd (i.e. files - // that have been opened without the O_CLOEXEC flag). - int devNullFd = open("/dev/null", O_RDWR | O_CLOEXEC); - if (dup2(devNullFd, STDOUT_FILENO) < 0) _exit(2); - if (dup2(devNullFd, STDERR_FILENO) < 0) _exit(3); - close(devNullFd); - for (int i = 0; i < 1024; i++) { - if (i != STDIN_FILENO && i != STDOUT_FILENO && i != STDERR_FILENO) close(i); - } - - execl("/system/bin/perfetto", "perfetto", "--background", "--config", "-", "--dropbox", - kDropboxTag, "--alert-id", alertId, "--config-id", configId, "--config-uid", - configUid, "--subscription-id", subscriptionId, nullptr); - - // execl() doesn't return in case of success, if we get here something - // failed. - _exit(4); - } - - // Main process. - - readPipe.reset(); // Close the read end (owned by the child process). - - // Using fdopen() because fwrite() has the right logic to chunking write() - // over a pipe (see __sfvwrite()). - FILE* writePipeStream = android::base::Fdopen(std::move(writePipe), "wb"); - if (!writePipeStream) { - ALOGE("fdopen() failed while calling the Perfetto client: %s", strerror(errno)); - return false; - } - - const std::string& cfgProto = config.trace_config(); - size_t bytesWritten = fwrite(cfgProto.data(), 1, cfgProto.size(), writePipeStream); - fclose(writePipeStream); - if (bytesWritten != cfgProto.size() || cfgProto.size() == 0) { - ALOGE("fwrite() failed (ret: %zd) while calling the Perfetto client: %s", bytesWritten, - strerror(errno)); - return false; - } - - // This does NOT wait for the full duration of the trace. It just waits until - // the process has read the config from stdin and detached. - int childStatus = 0; - waitpid(pid, &childStatus, 0); - if (!WIFEXITED(childStatus) || WEXITSTATUS(childStatus) != 0) { - ALOGE("Child process failed (0x%x) while calling the Perfetto client", childStatus); - return false; - } - - VLOG("CollectPerfettoTraceAndUploadToDropbox() succeeded"); - return true; -} - -} // namespace statsd -} // namespace os -} // namespace android diff --git a/bin/src/external/Perfetto.h b/bin/src/external/Perfetto.h deleted file mode 100644 index 095782a4..00000000 --- a/bin/src/external/Perfetto.h +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright (C) 2018 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. - */ - -#pragma once - -namespace android { -namespace os { -namespace statsd { - -class ConfigKey; -class PerfettoDetails; // Declared in statsd_config.pb.h - -// Starts the collection of a Perfetto trace with the given |config|. -// The trace is uploaded to Dropbox by the perfetto cmdline util once done. -// This method returns immediately after passing the config and does NOT wait -// for the full duration of the trace. -bool CollectPerfettoTraceAndUploadToDropbox(const PerfettoDetails& config, - int64_t subscription_id, - int64_t alert_id, - const ConfigKey& configKey); - -} // namespace statsd -} // namespace os -} // namespace android diff --git a/bin/src/external/PullDataReceiver.h b/bin/src/external/PullDataReceiver.h deleted file mode 100644 index dd5c0cfa..00000000 --- a/bin/src/external/PullDataReceiver.h +++ /dev/null @@ -1,41 +0,0 @@ -/* - * 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. - */ -#pragma once - -#include -#include "StatsPuller.h" -#include "logd/LogEvent.h" - -namespace android { -namespace os { -namespace statsd { - -class PullDataReceiver : virtual public RefBase{ - public: - virtual ~PullDataReceiver() {} - /** - * @param data The pulled data. - * @param pullSuccess Whether the pull succeeded. If the pull does not succeed, the data for the - * bucket should be invalidated. - * @param originalPullTimeNs This is when all the pulls have been initiated (elapsed time). - */ - virtual void onDataPulled(const std::vector>& data, - bool pullSuccess, int64_t originalPullTimeNs) = 0; -}; - -} // namespace statsd -} // namespace os -} // namespace android diff --git a/bin/src/external/PullResultReceiver.cpp b/bin/src/external/PullResultReceiver.cpp deleted file mode 100644 index 8aa4792d..00000000 --- a/bin/src/external/PullResultReceiver.cpp +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright (C) 2019 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 "PullResultReceiver.h" - -namespace android { -namespace os { -namespace statsd { - -PullResultReceiver::PullResultReceiver( - std::function&)> pullFinishCb) - : pullFinishCallback(std::move(pullFinishCb)) { -} - -Status PullResultReceiver::pullFinished(int32_t atomTag, bool success, - const vector& output) { - pullFinishCallback(atomTag, success, output); - return Status::ok(); -} - -PullResultReceiver::~PullResultReceiver() { -} - -} // namespace statsd -} // namespace os -} // namespace android diff --git a/bin/src/external/PullResultReceiver.h b/bin/src/external/PullResultReceiver.h deleted file mode 100644 index ceaae801..00000000 --- a/bin/src/external/PullResultReceiver.h +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright (C) 2019 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 - -using namespace std; - -using Status = ::ndk::ScopedAStatus; -using aidl::android::os::BnPullAtomResultReceiver; -using aidl::android::util::StatsEventParcel; - -namespace android { -namespace os { -namespace statsd { - -class PullResultReceiver : public BnPullAtomResultReceiver { -public: - PullResultReceiver(function&)> - pullFinishCallback); - ~PullResultReceiver(); - - /** - * Binder call for finishing a pull. - */ - Status pullFinished(int32_t atomTag, bool success, - const vector& output) override; - -private: - function&)> pullFinishCallback; -}; - -} // namespace statsd -} // namespace os -} // namespace android diff --git a/bin/src/external/PullUidProvider.h b/bin/src/external/PullUidProvider.h deleted file mode 100644 index 2318c501..00000000 --- a/bin/src/external/PullUidProvider.h +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright (C) 2020 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. - */ -#pragma once - -#include - -#include "StatsPuller.h" -#include "logd/LogEvent.h" - -namespace android { -namespace os { -namespace statsd { - -class PullUidProvider : virtual public RefBase { -public: - virtual ~PullUidProvider() {} - - /** - * @param atomId The atom for which to get the uids. - */ - virtual vector getPullAtomUids(int32_t atomId) = 0; -}; - -} // namespace statsd -} // namespace os -} // namespace android diff --git a/bin/src/external/StatsCallbackPuller.cpp b/bin/src/external/StatsCallbackPuller.cpp deleted file mode 100644 index 78e6f094..00000000 --- a/bin/src/external/StatsCallbackPuller.cpp +++ /dev/null @@ -1,116 +0,0 @@ -/* - * Copyright (C) 2019 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. - */ - -#define DEBUG false // STOPSHIP if true -#include "Log.h" - -#include "StatsCallbackPuller.h" -#include "PullResultReceiver.h" -#include "StatsPullerManager.h" -#include "logd/LogEvent.h" -#include "stats_log_util.h" - -#include - -using namespace std; - -using Status = ::ndk::ScopedAStatus; -using aidl::android::util::StatsEventParcel; -using ::ndk::SharedRefBase; - -namespace android { -namespace os { -namespace statsd { - -StatsCallbackPuller::StatsCallbackPuller(int tagId, const shared_ptr& callback, - const int64_t coolDownNs, int64_t timeoutNs, - const vector additiveFields) - : StatsPuller(tagId, coolDownNs, timeoutNs, additiveFields), mCallback(callback) { - VLOG("StatsCallbackPuller created for tag %d", tagId); -} - -bool StatsCallbackPuller::PullInternal(vector>* data) { - VLOG("StatsCallbackPuller called for tag %d", mTagId); - if(mCallback == nullptr) { - ALOGW("No callback registered"); - return false; - } - - // Shared variables needed in the result receiver. - shared_ptr cv_mutex = make_shared(); - shared_ptr cv = make_shared(); - shared_ptr pullFinish = make_shared(false); - shared_ptr pullSuccess = make_shared(false); - shared_ptr>> sharedData = - make_shared>>(); - - shared_ptr resultReceiver = SharedRefBase::make( - [cv_mutex, cv, pullFinish, pullSuccess, sharedData]( - int32_t atomTag, bool success, const vector& output) { - // This is the result of the pull, executing in a statsd binder thread. - // The pull could have taken a long time, and we should only modify - // data (the output param) if the pointer is in scope and the pull did not time out. - { - lock_guard lk(*cv_mutex); - for (const StatsEventParcel& parcel: output) { - shared_ptr event = make_shared(/*uid=*/-1, /*pid=*/-1); - bool valid = event->parseBuffer((uint8_t*)parcel.buffer.data(), - parcel.buffer.size()); - if (valid) { - sharedData->push_back(event); - } else { - StatsdStats::getInstance().noteAtomError(event->GetTagId(), - /*pull=*/true); - } - } - *pullSuccess = success; - *pullFinish = true; - } - cv->notify_one(); - }); - - // Initiate the pull. This is a oneway call to a different process, except - // in unit tests. In process calls are not oneway. - Status status = mCallback->onPullAtom(mTagId, resultReceiver); - if (!status.isOk()) { - StatsdStats::getInstance().notePullBinderCallFailed(mTagId); - return false; - } - - { - unique_lock unique_lk(*cv_mutex); - // Wait until the pull finishes, or until the pull timeout. - cv->wait_for(unique_lk, chrono::nanoseconds(mPullTimeoutNs), - [pullFinish] { return *pullFinish; }); - if (!*pullFinish) { - // Note: The parent stats puller will also note that there was a timeout and that the - // cache should be cleared. Once we migrate all pullers to this callback, we could - // consolidate the logic. - return true; - } else { - // Only copy the data if we did not timeout and the pull was successful. - if (*pullSuccess) { - *data = std::move(*sharedData); - } - VLOG("StatsCallbackPuller::pull succeeded for %d", mTagId); - return *pullSuccess; - } - } -} - -} // namespace statsd -} // namespace os -} // namespace android diff --git a/bin/src/external/StatsCallbackPuller.h b/bin/src/external/StatsCallbackPuller.h deleted file mode 100644 index e82e8bb5..00000000 --- a/bin/src/external/StatsCallbackPuller.h +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright (C) 2019 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. - */ - -#pragma once - -#include -#include "StatsPuller.h" - -using aidl::android::os::IPullAtomCallback; -using std::shared_ptr; - -namespace android { -namespace os { -namespace statsd { - -class StatsCallbackPuller : public StatsPuller { -public: - explicit StatsCallbackPuller(int tagId, const shared_ptr& callback, - const int64_t coolDownNs, const int64_t timeoutNs, - const std::vector additiveFields); - -private: - bool PullInternal(vector>* data) override; - const shared_ptr mCallback; - - FRIEND_TEST(StatsCallbackPullerTest, PullFail); - FRIEND_TEST(StatsCallbackPullerTest, PullSuccess); - FRIEND_TEST(StatsCallbackPullerTest, PullTimeout); -}; - -} // namespace statsd -} // namespace os -} // namespace android diff --git a/bin/src/external/StatsPuller.cpp b/bin/src/external/StatsPuller.cpp deleted file mode 100644 index bb5d0a6b..00000000 --- a/bin/src/external/StatsPuller.cpp +++ /dev/null @@ -1,126 +0,0 @@ -/* - * 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. - */ - -#define DEBUG false // STOPSHIP if true -#include "Log.h" - -#include "StatsPuller.h" -#include "StatsPullerManager.h" -#include "guardrail/StatsdStats.h" -#include "puller_util.h" -#include "stats_log_util.h" - -namespace android { -namespace os { -namespace statsd { - -using std::lock_guard; - -sp StatsPuller::mUidMap = nullptr; -void StatsPuller::SetUidMap(const sp& uidMap) { mUidMap = uidMap; } - -StatsPuller::StatsPuller(const int tagId, const int64_t coolDownNs, const int64_t pullTimeoutNs, - const std::vector additiveFields) - : mTagId(tagId), - mPullTimeoutNs(pullTimeoutNs), - mCoolDownNs(coolDownNs), - mAdditiveFields(additiveFields), - mLastPullTimeNs(0), - mLastEventTimeNs(0) { -} - -bool StatsPuller::Pull(const int64_t eventTimeNs, std::vector>* data) { - lock_guard lock(mLock); - const int64_t elapsedTimeNs = getElapsedRealtimeNs(); - const int64_t systemUptimeMillis = getSystemUptimeMillis(); - StatsdStats::getInstance().notePull(mTagId); - const bool shouldUseCache = - (mLastEventTimeNs == eventTimeNs) || (elapsedTimeNs - mLastPullTimeNs < mCoolDownNs); - if (shouldUseCache) { - if (mHasGoodData) { - (*data) = mCachedData; - StatsdStats::getInstance().notePullFromCache(mTagId); - - } - return mHasGoodData; - } - if (mLastPullTimeNs > 0) { - StatsdStats::getInstance().updateMinPullIntervalSec( - mTagId, (elapsedTimeNs - mLastPullTimeNs) / NS_PER_SEC); - } - mCachedData.clear(); - mLastPullTimeNs = elapsedTimeNs; - mLastEventTimeNs = eventTimeNs; - mHasGoodData = PullInternal(&mCachedData); - if (!mHasGoodData) { - return mHasGoodData; - } - const int64_t pullElapsedDurationNs = getElapsedRealtimeNs() - elapsedTimeNs; - const int64_t pullSystemUptimeDurationMillis = getSystemUptimeMillis() - systemUptimeMillis; - StatsdStats::getInstance().notePullTime(mTagId, pullElapsedDurationNs); - const bool pullTimeOut = pullElapsedDurationNs > mPullTimeoutNs; - if (pullTimeOut) { - // Something went wrong. Discard the data. - mCachedData.clear(); - mHasGoodData = false; - StatsdStats::getInstance().notePullTimeout( - mTagId, pullSystemUptimeDurationMillis, NanoToMillis(pullElapsedDurationNs)); - ALOGW("Pull for atom %d exceeds timeout %lld nano seconds.", mTagId, - (long long)pullElapsedDurationNs); - return mHasGoodData; - } - - if (mCachedData.size() > 0) { - mapAndMergeIsolatedUidsToHostUid(mCachedData, mUidMap, mTagId, mAdditiveFields); - } - - if (mCachedData.empty()) { - VLOG("Data pulled is empty"); - StatsdStats::getInstance().noteEmptyData(mTagId); - } - - (*data) = mCachedData; - return mHasGoodData; -} - -int StatsPuller::ForceClearCache() { - return clearCache(); -} - -int StatsPuller::clearCache() { - lock_guard lock(mLock); - return clearCacheLocked(); -} - -int StatsPuller::clearCacheLocked() { - int ret = mCachedData.size(); - mCachedData.clear(); - mLastPullTimeNs = 0; - mLastEventTimeNs = 0; - return ret; -} - -int StatsPuller::ClearCacheIfNecessary(int64_t timestampNs) { - if (timestampNs - mLastPullTimeNs > mCoolDownNs) { - return clearCache(); - } else { - return 0; - } -} - -} // namespace statsd -} // namespace os -} // namespace android diff --git a/bin/src/external/StatsPuller.h b/bin/src/external/StatsPuller.h deleted file mode 100644 index 470d15e6..00000000 --- a/bin/src/external/StatsPuller.h +++ /dev/null @@ -1,119 +0,0 @@ -/* - * 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. - */ - -#pragma once - -#include -#include -#include -#include -#include "packages/UidMap.h" - -#include "guardrail/StatsdStats.h" -#include "logd/LogEvent.h" -#include "puller_util.h" - -using aidl::android::os::IStatsCompanionService; -using std::shared_ptr; - -namespace android { -namespace os { -namespace statsd { - -class StatsPuller : public virtual RefBase { -public: - explicit StatsPuller(const int tagId, - const int64_t coolDownNs = NS_PER_SEC, - const int64_t pullTimeoutNs = StatsdStats::kPullMaxDelayNs, - const std::vector additiveFields = std::vector()); - - virtual ~StatsPuller() {} - - // Pulls the most recent data. - // The data may be served from cache if consecutive pulls come within - // predefined cooldown time. - // Returns true if the pull was successful. - // Returns false when - // 1) the pull fails - // 2) pull takes longer than mPullTimeoutNs (intrinsic to puller) - // If a metric wants to make any change to the data, like timestamps, it - // should make a copy as this data may be shared with multiple metrics. - bool Pull(const int64_t eventTimeNs, std::vector>* data); - - // Clear cache immediately - int ForceClearCache(); - - // Clear cache if elapsed time is more than cooldown time - int ClearCacheIfNecessary(int64_t timestampNs); - - static void SetUidMap(const sp& uidMap); - - virtual void SetStatsCompanionService( - shared_ptr statsCompanionService) {}; - -protected: - const int mTagId; - - // Max time allowed to pull this atom. - // We cannot reliably kill a pull thread. So we don't terminate the puller. - // The data is discarded if the pull takes longer than this and mHasGoodData - // marked as false. - const int64_t mPullTimeoutNs = StatsdStats::kPullMaxDelayNs; - -private: - mutable std::mutex mLock; - - // Real puller impl. - virtual bool PullInternal(std::vector>* data) = 0; - - bool mHasGoodData = false; - - // Minimum time before this puller does actual pull again. - // Pullers can cause significant impact to system health and battery. - // So that we don't pull too frequently. - // If a pull request comes before cooldown, a cached version from previous pull - // will be returned. - const int64_t mCoolDownNs = 1 * NS_PER_SEC; - - // The field numbers of the fields that need to be summed when merging - // isolated uid with host uid. - const std::vector mAdditiveFields; - - int64_t mLastPullTimeNs; - - // All pulls happen due to an event (app upgrade, bucket boundary, condition change, etc). - // If multiple pulls need to be done at the same event time, we will always use the cache after - // the first pull. - int64_t mLastEventTimeNs; - - // Cache of data from last pull. If next request comes before cool down finishes, - // cached data will be returned. - // Cached data is cleared when - // 1) A pull fails - // 2) A new pull request comes after cooldown time. - // 3) clearCache is called. - std::vector> mCachedData; - - int clearCache(); - - int clearCacheLocked(); - - static sp mUidMap; -}; - -} // namespace statsd -} // namespace os -} // namespace android diff --git a/bin/src/external/StatsPullerManager.cpp b/bin/src/external/StatsPullerManager.cpp deleted file mode 100644 index 8334b6b4..00000000 --- a/bin/src/external/StatsPullerManager.cpp +++ /dev/null @@ -1,371 +0,0 @@ -/* - * 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. - */ - -#define DEBUG false -#include "Log.h" - -#include "StatsPullerManager.h" - -#include -#include -#include - -#include -#include - -#include "../StatsService.h" -#include "../logd/LogEvent.h" -#include "../stats_log_util.h" -#include "../statscompanion_util.h" -#include "StatsCallbackPuller.h" -#include "TrainInfoPuller.h" -#include "statslog_statsd.h" - -using std::shared_ptr; -using std::vector; - -namespace android { -namespace os { -namespace statsd { - -// Stores the puller as a wp to avoid holding a reference in case it is unregistered and -// pullAtomCallbackDied is never called. -struct PullAtomCallbackDeathCookie { - PullAtomCallbackDeathCookie(const wp& pullerManager, - const PullerKey& pullerKey, const wp& puller) : - mPullerManager(pullerManager), mPullerKey(pullerKey), mPuller(puller) { - } - - wp mPullerManager; - PullerKey mPullerKey; - wp mPuller; -}; - -void StatsPullerManager::pullAtomCallbackDied(void* cookie) { - PullAtomCallbackDeathCookie* cookie_ = static_cast(cookie); - sp thiz = cookie_->mPullerManager.promote(); - if (!thiz) { - return; - } - - const PullerKey& pullerKey = cookie_->mPullerKey; - wp puller = cookie_->mPuller; - - // Erase the mapping from the puller key to the puller if the mapping still exists. - // Note that we are removing the StatsPuller object, which internally holds the binder - // IPullAtomCallback. However, each new registration creates a new StatsPuller, so this works. - lock_guard lock(thiz->mLock); - const auto& it = thiz->kAllPullAtomInfo.find(pullerKey); - if (it != thiz->kAllPullAtomInfo.end() && puller != nullptr && puller == it->second) { - StatsdStats::getInstance().notePullerCallbackRegistrationChanged(pullerKey.atomTag, - /*registered=*/false); - thiz->kAllPullAtomInfo.erase(pullerKey); - } - // The death recipient corresponding to this specific IPullAtomCallback can never - // be triggered again, so free up resources. - delete cookie_; -} - -// Values smaller than this may require to update the alarm. -const int64_t NO_ALARM_UPDATE = INT64_MAX; - -StatsPullerManager::StatsPullerManager() - : kAllPullAtomInfo({ - // TrainInfo. - {{.atomTag = util::TRAIN_INFO, .uid = AID_STATSD}, new TrainInfoPuller()}, - }), - mNextPullTimeNs(NO_ALARM_UPDATE), - mPullAtomCallbackDeathRecipient(AIBinder_DeathRecipient_new(pullAtomCallbackDied)) { -} - -bool StatsPullerManager::Pull(int tagId, const ConfigKey& configKey, const int64_t eventTimeNs, - vector>* data) { - std::lock_guard _l(mLock); - return PullLocked(tagId, configKey, eventTimeNs, data); -} - -bool StatsPullerManager::Pull(int tagId, const vector& uids, const int64_t eventTimeNs, - vector>* data) { - std::lock_guard _l(mLock); - return PullLocked(tagId, uids, eventTimeNs, data); -} - -bool StatsPullerManager::PullLocked(int tagId, const ConfigKey& configKey, - const int64_t eventTimeNs, vector>* data) { - vector uids; - const auto& uidProviderIt = mPullUidProviders.find(configKey); - if (uidProviderIt == mPullUidProviders.end()) { - ALOGE("Error pulling tag %d. No pull uid provider for config key %s", tagId, - configKey.ToString().c_str()); - StatsdStats::getInstance().notePullUidProviderNotFound(tagId); - return false; - } - sp pullUidProvider = uidProviderIt->second.promote(); - if (pullUidProvider == nullptr) { - ALOGE("Error pulling tag %d, pull uid provider for config %s is gone.", tagId, - configKey.ToString().c_str()); - StatsdStats::getInstance().notePullUidProviderNotFound(tagId); - return false; - } - uids = pullUidProvider->getPullAtomUids(tagId); - return PullLocked(tagId, uids, eventTimeNs, data); -} - -bool StatsPullerManager::PullLocked(int tagId, const vector& uids, - const int64_t eventTimeNs, vector>* data) { - VLOG("Initiating pulling %d", tagId); - for (int32_t uid : uids) { - PullerKey key = {.atomTag = tagId, .uid = uid}; - auto pullerIt = kAllPullAtomInfo.find(key); - if (pullerIt != kAllPullAtomInfo.end()) { - bool ret = pullerIt->second->Pull(eventTimeNs, data); - VLOG("pulled %zu items", data->size()); - if (!ret) { - StatsdStats::getInstance().notePullFailed(tagId); - } - return ret; - } - } - StatsdStats::getInstance().notePullerNotFound(tagId); - ALOGW("StatsPullerManager: Unknown tagId %d", tagId); - return false; // Return early since we don't know what to pull. -} - -bool StatsPullerManager::PullerForMatcherExists(int tagId) const { - // Pulled atoms might be registered after we parse the config, so just make sure the id is in - // an appropriate range. - return isVendorPulledAtom(tagId) || isPulledAtom(tagId); -} - -void StatsPullerManager::updateAlarmLocked() { - if (mNextPullTimeNs == NO_ALARM_UPDATE) { - VLOG("No need to set alarms. Skipping"); - return; - } - - // TODO(b/151045771): do not hold a lock while making a binder call - if (mStatsCompanionService != nullptr) { - mStatsCompanionService->setPullingAlarm(mNextPullTimeNs / 1000000); - } else { - VLOG("StatsCompanionService not available. Alarm not set."); - } - return; -} - -void StatsPullerManager::SetStatsCompanionService( - shared_ptr statsCompanionService) { - std::lock_guard _l(mLock); - shared_ptr tmpForLock = mStatsCompanionService; - mStatsCompanionService = statsCompanionService; - for (const auto& pulledAtom : kAllPullAtomInfo) { - pulledAtom.second->SetStatsCompanionService(statsCompanionService); - } - if (mStatsCompanionService != nullptr) { - updateAlarmLocked(); - } -} - -void StatsPullerManager::RegisterReceiver(int tagId, const ConfigKey& configKey, - wp receiver, int64_t nextPullTimeNs, - int64_t intervalNs) { - std::lock_guard _l(mLock); - auto& receivers = mReceivers[{.atomTag = tagId, .configKey = configKey}]; - for (auto it = receivers.begin(); it != receivers.end(); it++) { - if (it->receiver == receiver) { - VLOG("Receiver already registered of %d", (int)receivers.size()); - return; - } - } - ReceiverInfo receiverInfo; - receiverInfo.receiver = receiver; - - // Round it to the nearest minutes. This is the limit of alarm manager. - // In practice, we should always have larger buckets. - int64_t roundedIntervalNs = intervalNs / NS_PER_SEC / 60 * NS_PER_SEC * 60; - // Scheduled pulling should be at least 1 min apart. - // This can be lower in cts tests, in which case we round it to 1 min. - if (roundedIntervalNs < 60 * (int64_t)NS_PER_SEC) { - roundedIntervalNs = 60 * (int64_t)NS_PER_SEC; - } - - receiverInfo.intervalNs = roundedIntervalNs; - receiverInfo.nextPullTimeNs = nextPullTimeNs; - receivers.push_back(receiverInfo); - - // There is only one alarm for all pulled events. So only set it to the smallest denom. - if (nextPullTimeNs < mNextPullTimeNs) { - VLOG("Updating next pull time %lld", (long long)mNextPullTimeNs); - mNextPullTimeNs = nextPullTimeNs; - updateAlarmLocked(); - } - VLOG("Puller for tagId %d registered of %d", tagId, (int)receivers.size()); -} - -void StatsPullerManager::UnRegisterReceiver(int tagId, const ConfigKey& configKey, - wp receiver) { - std::lock_guard _l(mLock); - auto receiversIt = mReceivers.find({.atomTag = tagId, .configKey = configKey}); - if (receiversIt == mReceivers.end()) { - VLOG("Unknown pull code or no receivers: %d", tagId); - return; - } - std::list& receivers = receiversIt->second; - for (auto it = receivers.begin(); it != receivers.end(); it++) { - if (receiver == it->receiver) { - receivers.erase(it); - VLOG("Puller for tagId %d unregistered of %d", tagId, (int)receivers.size()); - return; - } - } -} - -void StatsPullerManager::RegisterPullUidProvider(const ConfigKey& configKey, - wp provider) { - std::lock_guard _l(mLock); - mPullUidProviders[configKey] = provider; -} - -void StatsPullerManager::UnregisterPullUidProvider(const ConfigKey& configKey, - wp provider) { - std::lock_guard _l(mLock); - const auto& it = mPullUidProviders.find(configKey); - if (it != mPullUidProviders.end() && it->second == provider) { - mPullUidProviders.erase(it); - } -} - -void StatsPullerManager::OnAlarmFired(int64_t elapsedTimeNs) { - std::lock_guard _l(mLock); - int64_t wallClockNs = getWallClockNs(); - - int64_t minNextPullTimeNs = NO_ALARM_UPDATE; - - vector>> needToPull; - for (auto& pair : mReceivers) { - vector receivers; - if (pair.second.size() != 0) { - for (ReceiverInfo& receiverInfo : pair.second) { - if (receiverInfo.nextPullTimeNs <= elapsedTimeNs) { - receivers.push_back(&receiverInfo); - } else { - if (receiverInfo.nextPullTimeNs < minNextPullTimeNs) { - minNextPullTimeNs = receiverInfo.nextPullTimeNs; - } - } - } - if (receivers.size() > 0) { - needToPull.push_back(make_pair(&pair.first, receivers)); - } - } - } - for (const auto& pullInfo : needToPull) { - vector> data; - bool pullSuccess = PullLocked(pullInfo.first->atomTag, pullInfo.first->configKey, - elapsedTimeNs, &data); - if (!pullSuccess) { - VLOG("pull failed at %lld, will try again later", (long long)elapsedTimeNs); - } - - // Convention is to mark pull atom timestamp at request time. - // If we pull at t0, puller starts at t1, finishes at t2, and send back - // at t3, we mark t0 as its timestamp, which should correspond to its - // triggering event, such as condition change at t0. - // Here the triggering event is alarm fired from AlarmManager. - // In ValueMetricProducer and GaugeMetricProducer we do same thing - // when pull on condition change, etc. - for (auto& event : data) { - event->setElapsedTimestampNs(elapsedTimeNs); - event->setLogdWallClockTimestampNs(wallClockNs); - } - - for (const auto& receiverInfo : pullInfo.second) { - sp receiverPtr = receiverInfo->receiver.promote(); - if (receiverPtr != nullptr) { - receiverPtr->onDataPulled(data, pullSuccess, elapsedTimeNs); - // We may have just come out of a coma, compute next pull time. - int numBucketsAhead = - (elapsedTimeNs - receiverInfo->nextPullTimeNs) / receiverInfo->intervalNs; - receiverInfo->nextPullTimeNs += (numBucketsAhead + 1) * receiverInfo->intervalNs; - if (receiverInfo->nextPullTimeNs < minNextPullTimeNs) { - minNextPullTimeNs = receiverInfo->nextPullTimeNs; - } - } else { - VLOG("receiver already gone."); - } - } - } - - VLOG("mNextPullTimeNs: %lld updated to %lld", (long long)mNextPullTimeNs, - (long long)minNextPullTimeNs); - mNextPullTimeNs = minNextPullTimeNs; - updateAlarmLocked(); -} - -int StatsPullerManager::ForceClearPullerCache() { - std::lock_guard _l(mLock); - int totalCleared = 0; - for (const auto& pulledAtom : kAllPullAtomInfo) { - totalCleared += pulledAtom.second->ForceClearCache(); - } - return totalCleared; -} - -int StatsPullerManager::ClearPullerCacheIfNecessary(int64_t timestampNs) { - std::lock_guard _l(mLock); - int totalCleared = 0; - for (const auto& pulledAtom : kAllPullAtomInfo) { - totalCleared += pulledAtom.second->ClearCacheIfNecessary(timestampNs); - } - return totalCleared; -} - -void StatsPullerManager::RegisterPullAtomCallback(const int uid, const int32_t atomTag, - const int64_t coolDownNs, const int64_t timeoutNs, - const vector& additiveFields, - const shared_ptr& callback) { - std::lock_guard _l(mLock); - VLOG("RegisterPullerCallback: adding puller for tag %d", atomTag); - - if (callback == nullptr) { - ALOGW("SetPullAtomCallback called with null callback for atom %d.", atomTag); - return; - } - - StatsdStats::getInstance().notePullerCallbackRegistrationChanged(atomTag, /*registered=*/true); - int64_t actualCoolDownNs = coolDownNs < kMinCoolDownNs ? kMinCoolDownNs : coolDownNs; - int64_t actualTimeoutNs = timeoutNs > kMaxTimeoutNs ? kMaxTimeoutNs : timeoutNs; - - sp puller = new StatsCallbackPuller(atomTag, callback, actualCoolDownNs, - actualTimeoutNs, additiveFields); - PullerKey key = {.atomTag = atomTag, .uid = uid}; - AIBinder_linkToDeath(callback->asBinder().get(), mPullAtomCallbackDeathRecipient.get(), - new PullAtomCallbackDeathCookie(this, key, puller)); - kAllPullAtomInfo[key] = puller; -} - -void StatsPullerManager::UnregisterPullAtomCallback(const int uid, const int32_t atomTag) { - std::lock_guard _l(mLock); - PullerKey key = {.atomTag = atomTag, .uid = uid}; - if (kAllPullAtomInfo.find(key) != kAllPullAtomInfo.end()) { - StatsdStats::getInstance().notePullerCallbackRegistrationChanged(atomTag, - /*registered=*/false); - kAllPullAtomInfo.erase(key); - } -} - -} // namespace statsd -} // namespace os -} // namespace android diff --git a/bin/src/external/StatsPullerManager.h b/bin/src/external/StatsPullerManager.h deleted file mode 100644 index 7f880324..00000000 --- a/bin/src/external/StatsPullerManager.h +++ /dev/null @@ -1,192 +0,0 @@ -/* - * 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. - */ - -#pragma once - -#include -#include -#include - -#include -#include - -#include "PullDataReceiver.h" -#include "PullUidProvider.h" -#include "StatsPuller.h" -#include "guardrail/StatsdStats.h" -#include "logd/LogEvent.h" -#include "packages/UidMap.h" - -using aidl::android::os::IPullAtomCallback; -using aidl::android::os::IStatsCompanionService; -using std::shared_ptr; - -namespace android { -namespace os { -namespace statsd { - -typedef struct PullerKey { - // The uid of the process that registers this puller. - const int uid = -1; - // The atom that this puller is for. - const int atomTag; - - bool operator<(const PullerKey& that) const { - if (uid < that.uid) { - return true; - } - if (uid > that.uid) { - return false; - } - return atomTag < that.atomTag; - }; - - bool operator==(const PullerKey& that) const { - return uid == that.uid && atomTag == that.atomTag; - }; -} PullerKey; - -class StatsPullerManager : public virtual RefBase { -public: - StatsPullerManager(); - - virtual ~StatsPullerManager() { - } - - - // Registers a receiver for tagId. It will be pulled on the nextPullTimeNs - // and then every intervalNs thereafter. - virtual void RegisterReceiver(int tagId, const ConfigKey& configKey, - wp receiver, int64_t nextPullTimeNs, - int64_t intervalNs); - - // Stop listening on a tagId. - virtual void UnRegisterReceiver(int tagId, const ConfigKey& configKey, - wp receiver); - - // Registers a pull uid provider for the config key. When pulling atoms, it will be used to - // determine which uids to pull from. - virtual void RegisterPullUidProvider(const ConfigKey& configKey, wp provider); - - // Unregister a pull uid provider. - virtual void UnregisterPullUidProvider(const ConfigKey& configKey, - wp provider); - - // Verify if we know how to pull for this matcher - bool PullerForMatcherExists(int tagId) const; - - void OnAlarmFired(int64_t elapsedTimeNs); - - // Pulls the most recent data. - // The data may be served from cache if consecutive pulls come within - // mCoolDownNs. - // Returns true if the pull was successful. - // Returns false when - // 1) the pull fails - // 2) pull takes longer than mPullTimeoutNs (intrinsic to puller) - // 3) Either a PullUidProvider was not registered for the config, or the there was no puller - // registered for any of the uids for this atom. - // If the metric wants to make any change to the data, like timestamps, they - // should make a copy as this data may be shared with multiple metrics. - virtual bool Pull(int tagId, const ConfigKey& configKey, const int64_t eventTimeNs, - vector>* data); - - // Same as above, but directly specify the allowed uids to pull from. - virtual bool Pull(int tagId, const vector& uids, const int64_t eventTimeNs, - vector>* data); - - // Clear pull data cache immediately. - int ForceClearPullerCache(); - - // Clear pull data cache if it is beyond respective cool down time. - int ClearPullerCacheIfNecessary(int64_t timestampNs); - - void SetStatsCompanionService(shared_ptr statsCompanionService); - - void RegisterPullAtomCallback(const int uid, const int32_t atomTag, const int64_t coolDownNs, - const int64_t timeoutNs, const vector& additiveFields, - const shared_ptr& callback); - - void UnregisterPullAtomCallback(const int uid, const int32_t atomTag); - - std::map> kAllPullAtomInfo; - -private: - const static int64_t kMinCoolDownNs = NS_PER_SEC; - const static int64_t kMaxTimeoutNs = 10 * NS_PER_SEC; - shared_ptr mStatsCompanionService = nullptr; - - // A struct containing an atom id and a Config Key - typedef struct ReceiverKey { - const int atomTag; - const ConfigKey configKey; - - inline bool operator<(const ReceiverKey& that) const { - return atomTag == that.atomTag ? configKey < that.configKey : atomTag < that.atomTag; - } - } ReceiverKey; - - typedef struct { - int64_t nextPullTimeNs; - int64_t intervalNs; - wp receiver; - } ReceiverInfo; - - // mapping from Receiver Key to receivers - std::map> mReceivers; - - // mapping from Config Key to the PullUidProvider for that config - std::map> mPullUidProviders; - - bool PullLocked(int tagId, const ConfigKey& configKey, const int64_t eventTimeNs, - vector>* data); - - bool PullLocked(int tagId, const vector& uids, const int64_t eventTimeNs, - vector>* data); - - // locks for data receiver and StatsCompanionService changes - std::mutex mLock; - - void updateAlarmLocked(); - - int64_t mNextPullTimeNs; - - // Death recipient that is triggered when the process holding the IPullAtomCallback has died. - ::ndk::ScopedAIBinder_DeathRecipient mPullAtomCallbackDeathRecipient; - - /** - * Death recipient callback that is called when a pull atom callback dies. - * The cookie is a pointer to a PullAtomCallbackDeathCookie. - */ - static void pullAtomCallbackDied(void* cookie); - - FRIEND_TEST(GaugeMetricE2eTest, TestRandomSamplePulledEvents); - FRIEND_TEST(GaugeMetricE2eTest, TestRandomSamplePulledEvent_LateAlarm); - FRIEND_TEST(GaugeMetricE2eTest, TestRandomSamplePulledEventsWithActivation); - FRIEND_TEST(GaugeMetricE2eTest, TestRandomSamplePulledEventsNoCondition); - FRIEND_TEST(ValueMetricE2eTest, TestPulledEvents); - FRIEND_TEST(ValueMetricE2eTest, TestPulledEvents_LateAlarm); - FRIEND_TEST(ValueMetricE2eTest, TestPulledEvents_WithActivation); - - FRIEND_TEST(StatsLogProcessorTest, TestPullUidProviderSetOnConfigUpdate); - - FRIEND_TEST(ConfigUpdateE2eTest, TestGaugeMetric); - FRIEND_TEST(ConfigUpdateE2eTest, TestValueMetric); -}; - -} // namespace statsd -} // namespace os -} // namespace android diff --git a/bin/src/external/TrainInfoPuller.cpp b/bin/src/external/TrainInfoPuller.cpp deleted file mode 100644 index 3837f4a1..00000000 --- a/bin/src/external/TrainInfoPuller.cpp +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright (C) 2019 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. - */ - -#define DEBUG false // STOPSHIP if true -#include "Log.h" - -#include "external/StatsPuller.h" - -#include "TrainInfoPuller.h" -#include "logd/LogEvent.h" -#include "stats_log_util.h" -#include "statslog_statsd.h" -#include "storage/StorageManager.h" - -using std::make_shared; -using std::shared_ptr; - -namespace android { -namespace os { -namespace statsd { - -TrainInfoPuller::TrainInfoPuller() : - StatsPuller(util::TRAIN_INFO) { -} - -bool TrainInfoPuller::PullInternal(vector>* data) { - vector trainInfoList = - StorageManager::readAllTrainInfo(); - if (trainInfoList.empty()) { - ALOGW("Train info was empty."); - return true; - } - for (InstallTrainInfo& trainInfo : trainInfoList) { - auto event = make_shared(getWallClockNs(), getElapsedRealtimeNs(), trainInfo); - data->push_back(event); - } - return true; -} - -} // namespace statsd -} // namespace os -} // namespace android diff --git a/bin/src/external/TrainInfoPuller.h b/bin/src/external/TrainInfoPuller.h deleted file mode 100644 index 615d0235..00000000 --- a/bin/src/external/TrainInfoPuller.h +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright (C) 2019 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. - */ - -#pragma once - -#include "StatsPuller.h" - -namespace android { -namespace os { -namespace statsd { - -/** - * Reads train info from disk. - */ -class TrainInfoPuller : public StatsPuller { - public: - TrainInfoPuller(); - - private: - bool PullInternal(vector>* data) override; -}; - -} // namespace statsd -} // namespace os -} // namespace android diff --git a/bin/src/external/puller_util.cpp b/bin/src/external/puller_util.cpp deleted file mode 100644 index aa99d008..00000000 --- a/bin/src/external/puller_util.cpp +++ /dev/null @@ -1,153 +0,0 @@ -/* - * Copyright (C) 2018 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. - */ - -#define DEBUG false // STOPSHIP if true -#include "Log.h" - -#include "puller_util.h" - -namespace android { -namespace os { -namespace statsd { - -using namespace std; - -/** - * Process all data and merge isolated with host if necessary. - * For example: - * NetworkBytesAtom { - * int uid = 1; - * State process_state = 2; - * int byte_send = 3; - * int byte_recv = 4; - * } - * additive fields are {3, 4} - * If we pulled the following events (uid1_child is an isolated uid which maps to uid1): - * [uid1, fg, 100, 200] - * [uid1_child, fg, 100, 200] - * [uid1, bg, 100, 200] - * - * We want to merge them and results should be: - * [uid1, fg, 200, 400] - * [uid1, bg, 100, 200] - * - * All atoms should be of the same tagId. All fields should be present. - */ -void mapAndMergeIsolatedUidsToHostUid(vector>& data, const sp& uidMap, - int tagId, const vector& additiveFieldsVec) { - // Check the first LogEvent for attribution chain or a uid field as either all atoms with this - // tagId have them or none of them do. - std::pair attrIndexRange; - const bool hasAttributionChain = data[0]->hasAttributionChain(&attrIndexRange); - bool hasUidField = (data[0]->getUidFieldIndex() != -1); - - if (!hasAttributionChain && !hasUidField) { - VLOG("No uid or attribution chain to merge, atom %d", tagId); - return; - } - - // 1. Map all isolated uid in-place to host uid - for (shared_ptr& event : data) { - if (event->GetTagId() != tagId) { - ALOGE("Wrong atom. Expecting %d, got %d", tagId, event->GetTagId()); - return; - } - if (hasAttributionChain) { - vector* const fieldValues = event->getMutableValues(); - for (int i = attrIndexRange.first; i <= attrIndexRange.second; i++) { - FieldValue& fieldValue = fieldValues->at(i); - if (isAttributionUidField(fieldValue)) { - const int hostUid = uidMap->getHostUidOrSelf(fieldValue.mValue.int_value); - fieldValue.mValue.setInt(hostUid); - } - } - } else { - int uidFieldIndex = event->getUidFieldIndex(); - if (uidFieldIndex != -1) { - Value& value = (*event->getMutableValues())[uidFieldIndex].mValue; - const int hostUid = uidMap->getHostUidOrSelf(value.int_value); - value.setInt(hostUid); - } else { - ALOGE("Malformed log, uid not found. %s", event->ToString().c_str()); - } - } - } - - // 2. sort the data, bit-wise - sort(data.begin(), data.end(), - [](const shared_ptr& lhs, const shared_ptr& rhs) { - if (lhs->size() != rhs->size()) { - return lhs->size() < rhs->size(); - } - const std::vector& lhsValues = lhs->getValues(); - const std::vector& rhsValues = rhs->getValues(); - for (int i = 0; i < (int)lhs->size(); i++) { - if (lhsValues[i] != rhsValues[i]) { - return lhsValues[i] < rhsValues[i]; - } - } - return false; - }); - - vector> mergedData; - const set additiveFields(additiveFieldsVec.begin(), additiveFieldsVec.end()); - bool needMerge = true; - - // 3. do the merge. - // The loop invariant is this: for every event, check if it differs on - // non-additive fields, or have different attribution chain length. - // If so, no need to merge, add itself to the result. - // Otherwise, merge the value onto the one immediately next to it. - for (int i = 0; i < (int)data.size() - 1; i++) { - // Size different, must be different chains. - if (data[i]->size() != data[i + 1]->size()) { - mergedData.push_back(data[i]); - continue; - } - vector* lhsValues = data[i]->getMutableValues(); - vector* rhsValues = data[i + 1]->getMutableValues(); - needMerge = true; - for (int p = 0; p < (int)lhsValues->size(); p++) { - if ((*lhsValues)[p] != (*rhsValues)[p]) { - int pos = (*lhsValues)[p].mField.getPosAtDepth(0); - // Differ on non-additive field, abort. - if (additiveFields.find(pos) == additiveFields.end()) { - needMerge = false; - break; - } - } - } - if (!needMerge) { - mergedData.push_back(data[i]); - continue; - } - // This should be infrequent operation. - for (int p = 0; p < (int)lhsValues->size(); p++) { - int pos = (*lhsValues)[p].mField.getPosAtDepth(0); - if (additiveFields.find(pos) != additiveFields.end()) { - (*rhsValues)[p].mValue += (*lhsValues)[p].mValue; - } - } - } - mergedData.push_back(data.back()); - - data.clear(); - data = mergedData; -} - -} // namespace statsd -} // namespace os -} // namespace android diff --git a/bin/src/external/puller_util.h b/bin/src/external/puller_util.h deleted file mode 100644 index afcf68ca..00000000 --- a/bin/src/external/puller_util.h +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright (C) 2018 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. - */ - -#pragma once - -#include -#include "StatsPuller.h" -#include "logd/LogEvent.h" -#include "packages/UidMap.h" - -namespace android { -namespace os { -namespace statsd { - -void mapAndMergeIsolatedUidsToHostUid(std::vector>& data, - const sp& uidMap, int tagId, - const vector& additiveFieldsVec); - -} // namespace statsd -} // namespace os -} // namespace android diff --git a/bin/src/flags/flags.cpp b/bin/src/flags/flags.cpp deleted file mode 100644 index e9fceda7..00000000 --- a/bin/src/flags/flags.cpp +++ /dev/null @@ -1,37 +0,0 @@ -/* - * 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 "flags.h" - -#include - -using server_configurable_flags::GetServerConfigurableFlag; -using std::string; - -namespace android { -namespace os { -namespace statsd { - -string getFlagString(const string& flagName, const string& defaultValue) { - return GetServerConfigurableFlag(STATSD_NATIVE_NAMESPACE, flagName, defaultValue); -} - -bool getFlagBool(const string& flagName, const string& defaultValue) { - return getFlagString(flagName, defaultValue) == "true"; -} -} // namespace statsd -} // namespace os -} // namespace android diff --git a/bin/src/flags/flags.h b/bin/src/flags/flags.h deleted file mode 100644 index 213e1a48..00000000 --- a/bin/src/flags/flags.h +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright (C) 2020 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. - */ - -#pragma once - -#include - -namespace android { -namespace os { -namespace statsd { - -const std::string STATSD_NATIVE_NAMESPACE = "statsd_native"; - -const std::string PARTIAL_CONFIG_UPDATE_FLAG = "partial_config_update"; - -std::string getFlagString(const std::string& flagName, const std::string& defaultValue); - -// Returns true IFF flagName has a value of "true". -bool getFlagBool(const std::string& flagName, const std::string& defaultValue); - -} // namespace statsd -} // namespace os -} // namespace android diff --git a/bin/src/guardrail/StatsdStats.cpp b/bin/src/guardrail/StatsdStats.cpp deleted file mode 100644 index f4e01ced..00000000 --- a/bin/src/guardrail/StatsdStats.cpp +++ /dev/null @@ -1,1110 +0,0 @@ -/* - * Copyright 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. - */ -#define DEBUG false // STOPSHIP if true -#include "Log.h" - -#include "StatsdStats.h" - -#include -#include "../stats_log_util.h" -#include "statslog_statsd.h" -#include "storage/StorageManager.h" - -namespace android { -namespace os { -namespace statsd { - -using android::util::FIELD_COUNT_REPEATED; -using android::util::FIELD_TYPE_BOOL; -using android::util::FIELD_TYPE_FLOAT; -using android::util::FIELD_TYPE_INT32; -using android::util::FIELD_TYPE_INT64; -using android::util::FIELD_TYPE_MESSAGE; -using android::util::FIELD_TYPE_STRING; -using android::util::ProtoOutputStream; -using std::lock_guard; -using std::shared_ptr; -using std::string; -using std::to_string; -using std::vector; - -const int FIELD_ID_BEGIN_TIME = 1; -const int FIELD_ID_END_TIME = 2; -const int FIELD_ID_CONFIG_STATS = 3; -const int FIELD_ID_ATOM_STATS = 7; -const int FIELD_ID_UIDMAP_STATS = 8; -const int FIELD_ID_ANOMALY_ALARM_STATS = 9; -const int FIELD_ID_PERIODIC_ALARM_STATS = 12; -const int FIELD_ID_SYSTEM_SERVER_RESTART = 15; -const int FIELD_ID_LOGGER_ERROR_STATS = 16; -const int FIELD_ID_OVERFLOW = 18; -const int FIELD_ID_ACTIVATION_BROADCAST_GUARDRAIL = 19; - -const int FIELD_ID_ATOM_STATS_TAG = 1; -const int FIELD_ID_ATOM_STATS_COUNT = 2; -const int FIELD_ID_ATOM_STATS_ERROR_COUNT = 3; - -const int FIELD_ID_ANOMALY_ALARMS_REGISTERED = 1; -const int FIELD_ID_PERIODIC_ALARMS_REGISTERED = 1; - -const int FIELD_ID_LOG_LOSS_STATS_TIME = 1; -const int FIELD_ID_LOG_LOSS_STATS_COUNT = 2; -const int FIELD_ID_LOG_LOSS_STATS_ERROR = 3; -const int FIELD_ID_LOG_LOSS_STATS_TAG = 4; -const int FIELD_ID_LOG_LOSS_STATS_UID = 5; -const int FIELD_ID_LOG_LOSS_STATS_PID = 6; - -const int FIELD_ID_OVERFLOW_COUNT = 1; -const int FIELD_ID_OVERFLOW_MAX_HISTORY = 2; -const int FIELD_ID_OVERFLOW_MIN_HISTORY = 3; - -const int FIELD_ID_CONFIG_STATS_UID = 1; -const int FIELD_ID_CONFIG_STATS_ID = 2; -const int FIELD_ID_CONFIG_STATS_CREATION = 3; -const int FIELD_ID_CONFIG_STATS_RESET = 19; -const int FIELD_ID_CONFIG_STATS_DELETION = 4; -const int FIELD_ID_CONFIG_STATS_METRIC_COUNT = 5; -const int FIELD_ID_CONFIG_STATS_CONDITION_COUNT = 6; -const int FIELD_ID_CONFIG_STATS_MATCHER_COUNT = 7; -const int FIELD_ID_CONFIG_STATS_ALERT_COUNT = 8; -const int FIELD_ID_CONFIG_STATS_VALID = 9; -const int FIELD_ID_CONFIG_STATS_BROADCAST = 10; -const int FIELD_ID_CONFIG_STATS_DATA_DROP_TIME = 11; -const int FIELD_ID_CONFIG_STATS_DATA_DROP_BYTES = 21; -const int FIELD_ID_CONFIG_STATS_DUMP_REPORT_TIME = 12; -const int FIELD_ID_CONFIG_STATS_DUMP_REPORT_BYTES = 20; -const int FIELD_ID_CONFIG_STATS_MATCHER_STATS = 13; -const int FIELD_ID_CONFIG_STATS_CONDITION_STATS = 14; -const int FIELD_ID_CONFIG_STATS_METRIC_STATS = 15; -const int FIELD_ID_CONFIG_STATS_ALERT_STATS = 16; -const int FIELD_ID_CONFIG_STATS_METRIC_DIMENSION_IN_CONDITION_STATS = 17; -const int FIELD_ID_CONFIG_STATS_ANNOTATION = 18; -const int FIELD_ID_CONFIG_STATS_ACTIVATION = 22; -const int FIELD_ID_CONFIG_STATS_DEACTIVATION = 23; -const int FIELD_ID_CONFIG_STATS_ANNOTATION_INT64 = 1; -const int FIELD_ID_CONFIG_STATS_ANNOTATION_INT32 = 2; - -const int FIELD_ID_MATCHER_STATS_ID = 1; -const int FIELD_ID_MATCHER_STATS_COUNT = 2; -const int FIELD_ID_CONDITION_STATS_ID = 1; -const int FIELD_ID_CONDITION_STATS_COUNT = 2; -const int FIELD_ID_METRIC_STATS_ID = 1; -const int FIELD_ID_METRIC_STATS_COUNT = 2; -const int FIELD_ID_ALERT_STATS_ID = 1; -const int FIELD_ID_ALERT_STATS_COUNT = 2; - -const int FIELD_ID_UID_MAP_CHANGES = 1; -const int FIELD_ID_UID_MAP_BYTES_USED = 2; -const int FIELD_ID_UID_MAP_DROPPED_CHANGES = 3; -const int FIELD_ID_UID_MAP_DELETED_APPS = 4; - -const int FIELD_ID_ACTIVATION_BROADCAST_GUARDRAIL_UID = 1; -const int FIELD_ID_ACTIVATION_BROADCAST_GUARDRAIL_TIME = 2; - -const std::map> StatsdStats::kAtomDimensionKeySizeLimitMap = { - {util::BINDER_CALLS, {6000, 10000}}, - {util::LOOPER_STATS, {1500, 2500}}, - {util::CPU_TIME_PER_UID_FREQ, {6000, 10000}}, -}; - -StatsdStats::StatsdStats() { - mPushedAtomStats.resize(kMaxPushedAtomId + 1); - mStartTimeSec = getWallClockSec(); -} - -StatsdStats& StatsdStats::getInstance() { - static StatsdStats statsInstance; - return statsInstance; -} - -void StatsdStats::addToIceBoxLocked(shared_ptr& stats) { - // The size of mIceBox grows strictly by one at a time. It won't be > kMaxIceBoxSize. - if (mIceBox.size() == kMaxIceBoxSize) { - mIceBox.pop_front(); - } - mIceBox.push_back(stats); -} - -void StatsdStats::noteConfigReceived( - const ConfigKey& key, int metricsCount, int conditionsCount, int matchersCount, - int alertsCount, const std::list>& annotations, - bool isValid) { - lock_guard lock(mLock); - int32_t nowTimeSec = getWallClockSec(); - - // If there is an existing config for the same key, icebox the old config. - noteConfigRemovedInternalLocked(key); - - shared_ptr configStats = std::make_shared(); - configStats->uid = key.GetUid(); - configStats->id = key.GetId(); - configStats->creation_time_sec = nowTimeSec; - configStats->metric_count = metricsCount; - configStats->condition_count = conditionsCount; - configStats->matcher_count = matchersCount; - configStats->alert_count = alertsCount; - configStats->is_valid = isValid; - for (auto& v : annotations) { - configStats->annotations.emplace_back(v); - } - - if (isValid) { - mConfigStats[key] = configStats; - } else { - configStats->deletion_time_sec = nowTimeSec; - addToIceBoxLocked(configStats); - } -} - -void StatsdStats::noteConfigRemovedInternalLocked(const ConfigKey& key) { - auto it = mConfigStats.find(key); - if (it != mConfigStats.end()) { - int32_t nowTimeSec = getWallClockSec(); - it->second->deletion_time_sec = nowTimeSec; - addToIceBoxLocked(it->second); - mConfigStats.erase(it); - } -} - -void StatsdStats::noteConfigRemoved(const ConfigKey& key) { - lock_guard lock(mLock); - noteConfigRemovedInternalLocked(key); -} - -void StatsdStats::noteConfigResetInternalLocked(const ConfigKey& key) { - auto it = mConfigStats.find(key); - if (it != mConfigStats.end()) { - it->second->reset_time_sec = getWallClockSec(); - } -} - -void StatsdStats::noteConfigReset(const ConfigKey& key) { - lock_guard lock(mLock); - noteConfigResetInternalLocked(key); -} - -void StatsdStats::noteLogLost(int32_t wallClockTimeSec, int32_t count, int32_t lastError, - int32_t lastTag, int32_t uid, int32_t pid) { - lock_guard lock(mLock); - if (mLogLossStats.size() == kMaxLoggerErrors) { - mLogLossStats.pop_front(); - } - mLogLossStats.emplace_back(wallClockTimeSec, count, lastError, lastTag, uid, pid); -} - -void StatsdStats::noteBroadcastSent(const ConfigKey& key) { - noteBroadcastSent(key, getWallClockSec()); -} - -void StatsdStats::noteBroadcastSent(const ConfigKey& key, int32_t timeSec) { - lock_guard lock(mLock); - auto it = mConfigStats.find(key); - if (it == mConfigStats.end()) { - ALOGE("Config key %s not found!", key.ToString().c_str()); - return; - } - if (it->second->broadcast_sent_time_sec.size() == kMaxTimestampCount) { - it->second->broadcast_sent_time_sec.pop_front(); - } - it->second->broadcast_sent_time_sec.push_back(timeSec); -} - -void StatsdStats::noteActiveStatusChanged(const ConfigKey& key, bool activated) { - noteActiveStatusChanged(key, activated, getWallClockSec()); -} - -void StatsdStats::noteActiveStatusChanged(const ConfigKey& key, bool activated, int32_t timeSec) { - lock_guard lock(mLock); - auto it = mConfigStats.find(key); - if (it == mConfigStats.end()) { - ALOGE("Config key %s not found!", key.ToString().c_str()); - return; - } - auto& vec = activated ? it->second->activation_time_sec - : it->second->deactivation_time_sec; - if (vec.size() == kMaxTimestampCount) { - vec.pop_front(); - } - vec.push_back(timeSec); -} - -void StatsdStats::noteActivationBroadcastGuardrailHit(const int uid) { - noteActivationBroadcastGuardrailHit(uid, getWallClockSec()); -} - -void StatsdStats::noteActivationBroadcastGuardrailHit(const int uid, const int32_t timeSec) { - lock_guard lock(mLock); - auto& guardrailTimes = mActivationBroadcastGuardrailStats[uid]; - if (guardrailTimes.size() == kMaxTimestampCount) { - guardrailTimes.pop_front(); - } - guardrailTimes.push_back(timeSec); -} - -void StatsdStats::noteDataDropped(const ConfigKey& key, const size_t totalBytes) { - noteDataDropped(key, totalBytes, getWallClockSec()); -} - -void StatsdStats::noteEventQueueOverflow(int64_t oldestEventTimestampNs) { - lock_guard lock(mLock); - - mOverflowCount++; - - int64_t history = getElapsedRealtimeNs() - oldestEventTimestampNs; - - if (history > mMaxQueueHistoryNs) { - mMaxQueueHistoryNs = history; - } - - if (history < mMinQueueHistoryNs) { - mMinQueueHistoryNs = history; - } -} - -void StatsdStats::noteDataDropped(const ConfigKey& key, const size_t totalBytes, int32_t timeSec) { - lock_guard lock(mLock); - auto it = mConfigStats.find(key); - if (it == mConfigStats.end()) { - ALOGE("Config key %s not found!", key.ToString().c_str()); - return; - } - if (it->second->data_drop_time_sec.size() == kMaxTimestampCount) { - it->second->data_drop_time_sec.pop_front(); - it->second->data_drop_bytes.pop_front(); - } - it->second->data_drop_time_sec.push_back(timeSec); - it->second->data_drop_bytes.push_back(totalBytes); -} - -void StatsdStats::noteMetricsReportSent(const ConfigKey& key, const size_t num_bytes) { - noteMetricsReportSent(key, num_bytes, getWallClockSec()); -} - -void StatsdStats::noteMetricsReportSent(const ConfigKey& key, const size_t num_bytes, - int32_t timeSec) { - lock_guard lock(mLock); - auto it = mConfigStats.find(key); - if (it == mConfigStats.end()) { - ALOGE("Config key %s not found!", key.ToString().c_str()); - return; - } - if (it->second->dump_report_stats.size() == kMaxTimestampCount) { - it->second->dump_report_stats.pop_front(); - } - it->second->dump_report_stats.push_back(std::make_pair(timeSec, num_bytes)); -} - -void StatsdStats::noteUidMapDropped(int deltas) { - lock_guard lock(mLock); - mUidMapStats.dropped_changes += mUidMapStats.dropped_changes + deltas; -} - -void StatsdStats::noteUidMapAppDeletionDropped() { - lock_guard lock(mLock); - mUidMapStats.deleted_apps++; -} - -void StatsdStats::setUidMapChanges(int changes) { - lock_guard lock(mLock); - mUidMapStats.changes = changes; -} - -void StatsdStats::setCurrentUidMapMemory(int bytes) { - lock_guard lock(mLock); - mUidMapStats.bytes_used = bytes; -} - -void StatsdStats::noteConditionDimensionSize(const ConfigKey& key, const int64_t& id, int size) { - lock_guard lock(mLock); - // if name doesn't exist before, it will create the key with count 0. - auto statsIt = mConfigStats.find(key); - if (statsIt == mConfigStats.end()) { - return; - } - - auto& conditionSizeMap = statsIt->second->condition_stats; - if (size > conditionSizeMap[id]) { - conditionSizeMap[id] = size; - } -} - -void StatsdStats::noteMetricDimensionSize(const ConfigKey& key, const int64_t& id, int size) { - lock_guard lock(mLock); - // if name doesn't exist before, it will create the key with count 0. - auto statsIt = mConfigStats.find(key); - if (statsIt == mConfigStats.end()) { - return; - } - auto& metricsDimensionMap = statsIt->second->metric_stats; - if (size > metricsDimensionMap[id]) { - metricsDimensionMap[id] = size; - } -} - -void StatsdStats::noteMetricDimensionInConditionSize( - const ConfigKey& key, const int64_t& id, int size) { - lock_guard lock(mLock); - // if name doesn't exist before, it will create the key with count 0. - auto statsIt = mConfigStats.find(key); - if (statsIt == mConfigStats.end()) { - return; - } - auto& metricsDimensionMap = statsIt->second->metric_dimension_in_condition_stats; - if (size > metricsDimensionMap[id]) { - metricsDimensionMap[id] = size; - } -} - -void StatsdStats::noteMatcherMatched(const ConfigKey& key, const int64_t& id) { - lock_guard lock(mLock); - - auto statsIt = mConfigStats.find(key); - if (statsIt == mConfigStats.end()) { - return; - } - statsIt->second->matcher_stats[id]++; -} - -void StatsdStats::noteAnomalyDeclared(const ConfigKey& key, const int64_t& id) { - lock_guard lock(mLock); - auto statsIt = mConfigStats.find(key); - if (statsIt == mConfigStats.end()) { - return; - } - statsIt->second->alert_stats[id]++; -} - -void StatsdStats::noteRegisteredAnomalyAlarmChanged() { - lock_guard lock(mLock); - mAnomalyAlarmRegisteredStats++; -} - -void StatsdStats::noteRegisteredPeriodicAlarmChanged() { - lock_guard lock(mLock); - mPeriodicAlarmRegisteredStats++; -} - -void StatsdStats::updateMinPullIntervalSec(int pullAtomId, long intervalSec) { - lock_guard lock(mLock); - mPulledAtomStats[pullAtomId].minPullIntervalSec = - std::min(mPulledAtomStats[pullAtomId].minPullIntervalSec, intervalSec); -} - -void StatsdStats::notePull(int pullAtomId) { - lock_guard lock(mLock); - mPulledAtomStats[pullAtomId].totalPull++; -} - -void StatsdStats::notePullFromCache(int pullAtomId) { - lock_guard lock(mLock); - mPulledAtomStats[pullAtomId].totalPullFromCache++; -} - -void StatsdStats::notePullTime(int pullAtomId, int64_t pullTimeNs) { - lock_guard lock(mLock); - auto& pullStats = mPulledAtomStats[pullAtomId]; - pullStats.maxPullTimeNs = std::max(pullStats.maxPullTimeNs, pullTimeNs); - pullStats.avgPullTimeNs = (pullStats.avgPullTimeNs * pullStats.numPullTime + pullTimeNs) / - (pullStats.numPullTime + 1); - pullStats.numPullTime += 1; -} - -void StatsdStats::notePullDelay(int pullAtomId, int64_t pullDelayNs) { - lock_guard lock(mLock); - auto& pullStats = mPulledAtomStats[pullAtomId]; - pullStats.maxPullDelayNs = std::max(pullStats.maxPullDelayNs, pullDelayNs); - pullStats.avgPullDelayNs = - (pullStats.avgPullDelayNs * pullStats.numPullDelay + pullDelayNs) / - (pullStats.numPullDelay + 1); - pullStats.numPullDelay += 1; -} - -void StatsdStats::notePullDataError(int pullAtomId) { - lock_guard lock(mLock); - mPulledAtomStats[pullAtomId].dataError++; -} - -void StatsdStats::notePullTimeout(int pullAtomId, - int64_t pullUptimeMillis, - int64_t pullElapsedMillis) { - lock_guard lock(mLock); - PulledAtomStats& pulledAtomStats = mPulledAtomStats[pullAtomId]; - pulledAtomStats.pullTimeout++; - - if (pulledAtomStats.pullTimeoutMetadata.size() == kMaxTimestampCount) { - pulledAtomStats.pullTimeoutMetadata.pop_front(); - } - - pulledAtomStats.pullTimeoutMetadata.emplace_back(pullUptimeMillis, pullElapsedMillis); -} - -void StatsdStats::notePullExceedMaxDelay(int pullAtomId) { - lock_guard lock(mLock); - mPulledAtomStats[pullAtomId].pullExceedMaxDelay++; -} - -void StatsdStats::noteAtomLogged(int atomId, int32_t timeSec) { - lock_guard lock(mLock); - - if (atomId <= kMaxPushedAtomId) { - mPushedAtomStats[atomId]++; - } else { - if (mNonPlatformPushedAtomStats.size() < kMaxNonPlatformPushedAtoms) { - mNonPlatformPushedAtomStats[atomId]++; - } - } -} - -void StatsdStats::noteSystemServerRestart(int32_t timeSec) { - lock_guard lock(mLock); - - if (mSystemServerRestartSec.size() == kMaxSystemServerRestarts) { - mSystemServerRestartSec.pop_front(); - } - mSystemServerRestartSec.push_back(timeSec); -} - -void StatsdStats::notePullFailed(int atomId) { - lock_guard lock(mLock); - mPulledAtomStats[atomId].pullFailed++; -} - -void StatsdStats::notePullUidProviderNotFound(int atomId) { - lock_guard lock(mLock); - mPulledAtomStats[atomId].pullUidProviderNotFound++; -} - -void StatsdStats::notePullerNotFound(int atomId) { - lock_guard lock(mLock); - mPulledAtomStats[atomId].pullerNotFound++; -} - -void StatsdStats::notePullBinderCallFailed(int atomId) { - lock_guard lock(mLock); - mPulledAtomStats[atomId].binderCallFailCount++; -} - -void StatsdStats::noteEmptyData(int atomId) { - lock_guard lock(mLock); - mPulledAtomStats[atomId].emptyData++; -} - -void StatsdStats::notePullerCallbackRegistrationChanged(int atomId, bool registered) { - lock_guard lock(mLock); - if (registered) { - mPulledAtomStats[atomId].registeredCount++; - } else { - mPulledAtomStats[atomId].unregisteredCount++; - } -} - -void StatsdStats::noteHardDimensionLimitReached(int64_t metricId) { - lock_guard lock(mLock); - getAtomMetricStats(metricId).hardDimensionLimitReached++; -} - -void StatsdStats::noteLateLogEventSkipped(int64_t metricId) { - lock_guard lock(mLock); - getAtomMetricStats(metricId).lateLogEventSkipped++; -} - -void StatsdStats::noteLateLogEvent(int64_t metricId, int64_t extraDurationNs) { - lock_guard lock(mLock); - AtomMetricStats& metricStats = getAtomMetricStats(metricId); - metricStats.lateLogEvent++; - metricStats.sumLateLogEventExtraDurationNs += extraDurationNs; - metricStats.maxLateLogEventExtraDurationNs = - std::max(metricStats.maxLateLogEventExtraDurationNs, extraDurationNs); -} - -void StatsdStats::noteSkippedForwardBuckets(int64_t metricId) { - lock_guard lock(mLock); - getAtomMetricStats(metricId).skippedForwardBuckets++; -} - -void StatsdStats::noteBadValueType(int64_t metricId) { - lock_guard lock(mLock); - getAtomMetricStats(metricId).badValueType++; -} - -void StatsdStats::noteBucketDropped(int64_t metricId) { - lock_guard lock(mLock); - getAtomMetricStats(metricId).bucketDropped++; -} - -void StatsdStats::noteBucketUnknownCondition(int64_t metricId) { - lock_guard lock(mLock); - getAtomMetricStats(metricId).bucketUnknownCondition++; -} - -void StatsdStats::noteConditionChangeInNextBucket(int64_t metricId) { - lock_guard lock(mLock); - getAtomMetricStats(metricId).conditionChangeInNextBucket++; -} - -void StatsdStats::noteInvalidatedBucket(int64_t metricId) { - lock_guard lock(mLock); - getAtomMetricStats(metricId).invalidatedBucket++; -} - -void StatsdStats::noteBucketCount(int64_t metricId) { - lock_guard lock(mLock); - getAtomMetricStats(metricId).bucketCount++; -} - -void StatsdStats::noteBucketBoundaryDelayNs(int64_t metricId, int64_t timeDelayNs) { - lock_guard lock(mLock); - AtomMetricStats& metricStats = getAtomMetricStats(metricId); - metricStats.maxBucketBoundaryDelayNs = - std::max(metricStats.maxBucketBoundaryDelayNs, timeDelayNs); - metricStats.minBucketBoundaryDelayNs = - std::min(metricStats.minBucketBoundaryDelayNs, timeDelayNs); -} - -void StatsdStats::noteAtomError(int atomTag, bool pull) { - lock_guard lock(mLock); - if (pull) { - mPulledAtomStats[atomTag].atomErrorCount++; - return; - } - - bool present = (mPushedAtomErrorStats.find(atomTag) != mPushedAtomErrorStats.end()); - bool full = (mPushedAtomErrorStats.size() >= (size_t)kMaxPushedAtomErrorStatsSize); - if (!full || present) { - mPushedAtomErrorStats[atomTag]++; - } -} - -StatsdStats::AtomMetricStats& StatsdStats::getAtomMetricStats(int64_t metricId) { - auto atomMetricStatsIter = mAtomMetricStats.find(metricId); - if (atomMetricStatsIter != mAtomMetricStats.end()) { - return atomMetricStatsIter->second; - } - auto emplaceResult = mAtomMetricStats.emplace(metricId, AtomMetricStats()); - return emplaceResult.first->second; -} - -void StatsdStats::reset() { - lock_guard lock(mLock); - resetInternalLocked(); -} - -void StatsdStats::resetInternalLocked() { - // Reset the historical data, but keep the active ConfigStats - mStartTimeSec = getWallClockSec(); - mIceBox.clear(); - std::fill(mPushedAtomStats.begin(), mPushedAtomStats.end(), 0); - mNonPlatformPushedAtomStats.clear(); - mAnomalyAlarmRegisteredStats = 0; - mPeriodicAlarmRegisteredStats = 0; - mSystemServerRestartSec.clear(); - mLogLossStats.clear(); - mOverflowCount = 0; - mMinQueueHistoryNs = kInt64Max; - mMaxQueueHistoryNs = 0; - for (auto& config : mConfigStats) { - config.second->broadcast_sent_time_sec.clear(); - config.second->activation_time_sec.clear(); - config.second->deactivation_time_sec.clear(); - config.second->data_drop_time_sec.clear(); - config.second->data_drop_bytes.clear(); - config.second->dump_report_stats.clear(); - config.second->annotations.clear(); - config.second->matcher_stats.clear(); - config.second->condition_stats.clear(); - config.second->metric_stats.clear(); - config.second->metric_dimension_in_condition_stats.clear(); - config.second->alert_stats.clear(); - } - for (auto& pullStats : mPulledAtomStats) { - pullStats.second.totalPull = 0; - pullStats.second.totalPullFromCache = 0; - pullStats.second.minPullIntervalSec = LONG_MAX; - pullStats.second.avgPullTimeNs = 0; - pullStats.second.maxPullTimeNs = 0; - pullStats.second.numPullTime = 0; - pullStats.second.avgPullDelayNs = 0; - pullStats.second.maxPullDelayNs = 0; - pullStats.second.numPullDelay = 0; - pullStats.second.dataError = 0; - pullStats.second.pullTimeout = 0; - pullStats.second.pullExceedMaxDelay = 0; - pullStats.second.pullFailed = 0; - pullStats.second.pullUidProviderNotFound = 0; - pullStats.second.pullerNotFound = 0; - pullStats.second.registeredCount = 0; - pullStats.second.unregisteredCount = 0; - pullStats.second.atomErrorCount = 0; - pullStats.second.binderCallFailCount = 0; - pullStats.second.pullTimeoutMetadata.clear(); - } - mAtomMetricStats.clear(); - mActivationBroadcastGuardrailStats.clear(); - mPushedAtomErrorStats.clear(); -} - -string buildTimeString(int64_t timeSec) { - time_t t = timeSec; - struct tm* tm = localtime(&t); - char timeBuffer[80]; - strftime(timeBuffer, sizeof(timeBuffer), "%Y-%m-%d %I:%M%p", tm); - return string(timeBuffer); -} - -int StatsdStats::getPushedAtomErrors(int atomId) const { - const auto& it = mPushedAtomErrorStats.find(atomId); - if (it != mPushedAtomErrorStats.end()) { - return it->second; - } else { - return 0; - } -} - -void StatsdStats::dumpStats(int out) const { - lock_guard lock(mLock); - time_t t = mStartTimeSec; - struct tm* tm = localtime(&t); - char timeBuffer[80]; - strftime(timeBuffer, sizeof(timeBuffer), "%Y-%m-%d %I:%M%p\n", tm); - dprintf(out, "Stats collection start second: %s\n", timeBuffer); - dprintf(out, "%lu Config in icebox: \n", (unsigned long)mIceBox.size()); - for (const auto& configStats : mIceBox) { - dprintf(out, - "Config {%d_%lld}: creation=%d, deletion=%d, reset=%d, #metric=%d, #condition=%d, " - "#matcher=%d, #alert=%d, valid=%d\n", - configStats->uid, (long long)configStats->id, configStats->creation_time_sec, - configStats->deletion_time_sec, configStats->reset_time_sec, - configStats->metric_count, configStats->condition_count, configStats->matcher_count, - configStats->alert_count, configStats->is_valid); - - for (const auto& broadcastTime : configStats->broadcast_sent_time_sec) { - dprintf(out, "\tbroadcast time: %d\n", broadcastTime); - } - - for (const int& activationTime : configStats->activation_time_sec) { - dprintf(out, "\tactivation time: %d\n", activationTime); - } - - for (const int& deactivationTime : configStats->deactivation_time_sec) { - dprintf(out, "\tdeactivation time: %d\n", deactivationTime); - } - - auto dropTimePtr = configStats->data_drop_time_sec.begin(); - auto dropBytesPtr = configStats->data_drop_bytes.begin(); - for (int i = 0; i < (int)configStats->data_drop_time_sec.size(); - i++, dropTimePtr++, dropBytesPtr++) { - dprintf(out, "\tdata drop time: %d with size %lld", *dropTimePtr, - (long long)*dropBytesPtr); - } - } - dprintf(out, "%lu Active Configs\n", (unsigned long)mConfigStats.size()); - for (auto& pair : mConfigStats) { - auto& configStats = pair.second; - dprintf(out, - "Config {%d-%lld}: creation=%d, deletion=%d, #metric=%d, #condition=%d, " - "#matcher=%d, #alert=%d, valid=%d\n", - configStats->uid, (long long)configStats->id, configStats->creation_time_sec, - configStats->deletion_time_sec, configStats->metric_count, - configStats->condition_count, configStats->matcher_count, configStats->alert_count, - configStats->is_valid); - for (const auto& annotation : configStats->annotations) { - dprintf(out, "\tannotation: %lld, %d\n", (long long)annotation.first, - annotation.second); - } - - for (const auto& broadcastTime : configStats->broadcast_sent_time_sec) { - dprintf(out, "\tbroadcast time: %s(%lld)\n", buildTimeString(broadcastTime).c_str(), - (long long)broadcastTime); - } - - for (const int& activationTime : configStats->activation_time_sec) { - dprintf(out, "\tactivation time: %d\n", activationTime); - } - - for (const int& deactivationTime : configStats->deactivation_time_sec) { - dprintf(out, "\tdeactivation time: %d\n", deactivationTime); - } - - auto dropTimePtr = configStats->data_drop_time_sec.begin(); - auto dropBytesPtr = configStats->data_drop_bytes.begin(); - for (int i = 0; i < (int)configStats->data_drop_time_sec.size(); - i++, dropTimePtr++, dropBytesPtr++) { - dprintf(out, "\tdata drop time: %s(%lld) with %lld bytes\n", - buildTimeString(*dropTimePtr).c_str(), (long long)*dropTimePtr, - (long long)*dropBytesPtr); - } - - for (const auto& dump : configStats->dump_report_stats) { - dprintf(out, "\tdump report time: %s(%lld) bytes: %lld\n", - buildTimeString(dump.first).c_str(), (long long)dump.first, - (long long)dump.second); - } - - for (const auto& stats : pair.second->matcher_stats) { - dprintf(out, "matcher %lld matched %d times\n", (long long)stats.first, stats.second); - } - - for (const auto& stats : pair.second->condition_stats) { - dprintf(out, "condition %lld max output tuple size %d\n", (long long)stats.first, - stats.second); - } - - for (const auto& stats : pair.second->condition_stats) { - dprintf(out, "metrics %lld max output tuple size %d\n", (long long)stats.first, - stats.second); - } - - for (const auto& stats : pair.second->alert_stats) { - dprintf(out, "alert %lld declared %d times\n", (long long)stats.first, stats.second); - } - } - dprintf(out, "********Disk Usage stats***********\n"); - StorageManager::printStats(out); - dprintf(out, "********Pushed Atom stats***********\n"); - const size_t atomCounts = mPushedAtomStats.size(); - for (size_t i = 2; i < atomCounts; i++) { - if (mPushedAtomStats[i] > 0) { - dprintf(out, "Atom %zu->(total count)%d, (error count)%d\n", i, mPushedAtomStats[i], - getPushedAtomErrors((int)i)); - } - } - for (const auto& pair : mNonPlatformPushedAtomStats) { - dprintf(out, "Atom %d->(total count)%d, (error count)%d\n", pair.first, pair.second, - getPushedAtomErrors(pair.first)); - } - - dprintf(out, "********Pulled Atom stats***********\n"); - for (const auto& pair : mPulledAtomStats) { - dprintf(out, - "Atom %d->(total pull)%ld, (pull from cache)%ld, " - "(pull failed)%ld, (min pull interval)%ld \n" - " (average pull time nanos)%lld, (max pull time nanos)%lld, (average pull delay " - "nanos)%lld, " - " (max pull delay nanos)%lld, (data error)%ld\n" - " (pull timeout)%ld, (pull exceed max delay)%ld" - " (no uid provider count)%ld, (no puller found count)%ld\n" - " (registered count) %ld, (unregistered count) %ld" - " (atom error count) %d\n", - (int)pair.first, (long)pair.second.totalPull, (long)pair.second.totalPullFromCache, - (long)pair.second.pullFailed, (long)pair.second.minPullIntervalSec, - (long long)pair.second.avgPullTimeNs, (long long)pair.second.maxPullTimeNs, - (long long)pair.second.avgPullDelayNs, (long long)pair.second.maxPullDelayNs, - pair.second.dataError, pair.second.pullTimeout, pair.second.pullExceedMaxDelay, - pair.second.pullUidProviderNotFound, pair.second.pullerNotFound, - pair.second.registeredCount, pair.second.unregisteredCount, - pair.second.atomErrorCount); - if (pair.second.pullTimeoutMetadata.size() > 0) { - string uptimeMillis = "(pull timeout system uptime millis) "; - string pullTimeoutMillis = "(pull timeout elapsed time millis) "; - for (const auto& stats : pair.second.pullTimeoutMetadata) { - uptimeMillis.append(to_string(stats.pullTimeoutUptimeMillis)).append(",");; - pullTimeoutMillis.append(to_string(stats.pullTimeoutElapsedMillis)).append(","); - } - uptimeMillis.pop_back(); - uptimeMillis.push_back('\n'); - pullTimeoutMillis.pop_back(); - pullTimeoutMillis.push_back('\n'); - dprintf(out, "%s", uptimeMillis.c_str()); - dprintf(out, "%s", pullTimeoutMillis.c_str()); - } - } - - if (mAnomalyAlarmRegisteredStats > 0) { - dprintf(out, "********AnomalyAlarmStats stats***********\n"); - dprintf(out, "Anomaly alarm registrations: %d\n", mAnomalyAlarmRegisteredStats); - } - - if (mPeriodicAlarmRegisteredStats > 0) { - dprintf(out, "********SubscriberAlarmStats stats***********\n"); - dprintf(out, "Subscriber alarm registrations: %d\n", mPeriodicAlarmRegisteredStats); - } - - dprintf(out, "UID map stats: bytes=%d, changes=%d, deleted=%d, changes lost=%d\n", - mUidMapStats.bytes_used, mUidMapStats.changes, mUidMapStats.deleted_apps, - mUidMapStats.dropped_changes); - - for (const auto& restart : mSystemServerRestartSec) { - dprintf(out, "System server restarts at %s(%lld)\n", buildTimeString(restart).c_str(), - (long long)restart); - } - - for (const auto& loss : mLogLossStats) { - dprintf(out, - "Log loss: %lld (wall clock sec) - %d (count), %d (last error), %d (last tag), %d " - "(uid), %d (pid)\n", - (long long)loss.mWallClockSec, loss.mCount, loss.mLastError, loss.mLastTag, - loss.mUid, loss.mPid); - } - - dprintf(out, "Event queue overflow: %d; MaxHistoryNs: %lld; MinHistoryNs: %lld\n", - mOverflowCount, (long long)mMaxQueueHistoryNs, (long long)mMinQueueHistoryNs); - - if (mActivationBroadcastGuardrailStats.size() > 0) { - dprintf(out, "********mActivationBroadcastGuardrail stats***********\n"); - for (const auto& pair: mActivationBroadcastGuardrailStats) { - dprintf(out, "Uid %d: Times: ", pair.first); - for (const auto& guardrailHitTime : pair.second) { - dprintf(out, "%d ", guardrailHitTime); - } - } - dprintf(out, "\n"); - } -} - -void addConfigStatsToProto(const ConfigStats& configStats, ProtoOutputStream* proto) { - uint64_t token = - proto->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_CONFIG_STATS); - proto->write(FIELD_TYPE_INT32 | FIELD_ID_CONFIG_STATS_UID, configStats.uid); - proto->write(FIELD_TYPE_INT64 | FIELD_ID_CONFIG_STATS_ID, (long long)configStats.id); - proto->write(FIELD_TYPE_INT32 | FIELD_ID_CONFIG_STATS_CREATION, configStats.creation_time_sec); - if (configStats.reset_time_sec != 0) { - proto->write(FIELD_TYPE_INT32 | FIELD_ID_CONFIG_STATS_RESET, configStats.reset_time_sec); - } - if (configStats.deletion_time_sec != 0) { - proto->write(FIELD_TYPE_INT32 | FIELD_ID_CONFIG_STATS_DELETION, - configStats.deletion_time_sec); - } - proto->write(FIELD_TYPE_INT32 | FIELD_ID_CONFIG_STATS_METRIC_COUNT, configStats.metric_count); - proto->write(FIELD_TYPE_INT32 | FIELD_ID_CONFIG_STATS_CONDITION_COUNT, - configStats.condition_count); - proto->write(FIELD_TYPE_INT32 | FIELD_ID_CONFIG_STATS_MATCHER_COUNT, configStats.matcher_count); - proto->write(FIELD_TYPE_INT32 | FIELD_ID_CONFIG_STATS_ALERT_COUNT, configStats.alert_count); - proto->write(FIELD_TYPE_BOOL | FIELD_ID_CONFIG_STATS_VALID, configStats.is_valid); - - for (const auto& broadcast : configStats.broadcast_sent_time_sec) { - proto->write(FIELD_TYPE_INT32 | FIELD_ID_CONFIG_STATS_BROADCAST | FIELD_COUNT_REPEATED, - broadcast); - } - - for (const auto& activation : configStats.activation_time_sec) { - proto->write(FIELD_TYPE_INT32 | FIELD_ID_CONFIG_STATS_ACTIVATION | FIELD_COUNT_REPEATED, - activation); - } - - for (const auto& deactivation : configStats.deactivation_time_sec) { - proto->write(FIELD_TYPE_INT32 | FIELD_ID_CONFIG_STATS_DEACTIVATION | FIELD_COUNT_REPEATED, - deactivation); - } - - for (const auto& drop_time : configStats.data_drop_time_sec) { - proto->write(FIELD_TYPE_INT32 | FIELD_ID_CONFIG_STATS_DATA_DROP_TIME | FIELD_COUNT_REPEATED, - drop_time); - } - - for (const auto& drop_bytes : configStats.data_drop_bytes) { - proto->write( - FIELD_TYPE_INT64 | FIELD_ID_CONFIG_STATS_DATA_DROP_BYTES | FIELD_COUNT_REPEATED, - (long long)drop_bytes); - } - - for (const auto& dump : configStats.dump_report_stats) { - proto->write(FIELD_TYPE_INT32 | FIELD_ID_CONFIG_STATS_DUMP_REPORT_TIME | - FIELD_COUNT_REPEATED, - dump.first); - } - - for (const auto& dump : configStats.dump_report_stats) { - proto->write(FIELD_TYPE_INT64 | FIELD_ID_CONFIG_STATS_DUMP_REPORT_BYTES | - FIELD_COUNT_REPEATED, - (long long)dump.second); - } - - for (const auto& annotation : configStats.annotations) { - uint64_t token = proto->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | - FIELD_ID_CONFIG_STATS_ANNOTATION); - proto->write(FIELD_TYPE_INT64 | FIELD_ID_CONFIG_STATS_ANNOTATION_INT64, - (long long)annotation.first); - proto->write(FIELD_TYPE_INT32 | FIELD_ID_CONFIG_STATS_ANNOTATION_INT32, annotation.second); - proto->end(token); - } - - for (const auto& pair : configStats.matcher_stats) { - uint64_t tmpToken = proto->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | - FIELD_ID_CONFIG_STATS_MATCHER_STATS); - proto->write(FIELD_TYPE_INT64 | FIELD_ID_MATCHER_STATS_ID, (long long)pair.first); - proto->write(FIELD_TYPE_INT32 | FIELD_ID_MATCHER_STATS_COUNT, pair.second); - proto->end(tmpToken); - } - - for (const auto& pair : configStats.condition_stats) { - uint64_t tmpToken = proto->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | - FIELD_ID_CONFIG_STATS_CONDITION_STATS); - proto->write(FIELD_TYPE_INT64 | FIELD_ID_CONDITION_STATS_ID, (long long)pair.first); - proto->write(FIELD_TYPE_INT32 | FIELD_ID_CONDITION_STATS_COUNT, pair.second); - proto->end(tmpToken); - } - - for (const auto& pair : configStats.metric_stats) { - uint64_t tmpToken = proto->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | - FIELD_ID_CONFIG_STATS_METRIC_STATS); - proto->write(FIELD_TYPE_INT64 | FIELD_ID_METRIC_STATS_ID, (long long)pair.first); - proto->write(FIELD_TYPE_INT32 | FIELD_ID_METRIC_STATS_COUNT, pair.second); - proto->end(tmpToken); - } - for (const auto& pair : configStats.metric_dimension_in_condition_stats) { - uint64_t tmpToken = proto->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | - FIELD_ID_CONFIG_STATS_METRIC_DIMENSION_IN_CONDITION_STATS); - proto->write(FIELD_TYPE_INT64 | FIELD_ID_METRIC_STATS_ID, (long long)pair.first); - proto->write(FIELD_TYPE_INT32 | FIELD_ID_METRIC_STATS_COUNT, pair.second); - proto->end(tmpToken); - } - - for (const auto& pair : configStats.alert_stats) { - uint64_t tmpToken = proto->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | - FIELD_ID_CONFIG_STATS_ALERT_STATS); - proto->write(FIELD_TYPE_INT64 | FIELD_ID_ALERT_STATS_ID, (long long)pair.first); - proto->write(FIELD_TYPE_INT32 | FIELD_ID_ALERT_STATS_COUNT, pair.second); - proto->end(tmpToken); - } - - proto->end(token); -} - -void StatsdStats::dumpStats(std::vector* output, bool reset) { - lock_guard lock(mLock); - - ProtoOutputStream proto; - proto.write(FIELD_TYPE_INT32 | FIELD_ID_BEGIN_TIME, mStartTimeSec); - proto.write(FIELD_TYPE_INT32 | FIELD_ID_END_TIME, (int32_t)getWallClockSec()); - - for (const auto& configStats : mIceBox) { - addConfigStatsToProto(*configStats, &proto); - } - - for (auto& pair : mConfigStats) { - addConfigStatsToProto(*(pair.second), &proto); - } - - const size_t atomCounts = mPushedAtomStats.size(); - for (size_t i = 2; i < atomCounts; i++) { - if (mPushedAtomStats[i] > 0) { - uint64_t token = - proto.start(FIELD_TYPE_MESSAGE | FIELD_ID_ATOM_STATS | FIELD_COUNT_REPEATED); - proto.write(FIELD_TYPE_INT32 | FIELD_ID_ATOM_STATS_TAG, (int32_t)i); - proto.write(FIELD_TYPE_INT32 | FIELD_ID_ATOM_STATS_COUNT, mPushedAtomStats[i]); - int errors = getPushedAtomErrors(i); - if (errors > 0) { - proto.write(FIELD_TYPE_INT32 | FIELD_ID_ATOM_STATS_ERROR_COUNT, errors); - } - proto.end(token); - } - } - - for (const auto& pair : mNonPlatformPushedAtomStats) { - uint64_t token = - proto.start(FIELD_TYPE_MESSAGE | FIELD_ID_ATOM_STATS | FIELD_COUNT_REPEATED); - proto.write(FIELD_TYPE_INT32 | FIELD_ID_ATOM_STATS_TAG, pair.first); - proto.write(FIELD_TYPE_INT32 | FIELD_ID_ATOM_STATS_COUNT, pair.second); - int errors = getPushedAtomErrors(pair.first); - if (errors > 0) { - proto.write(FIELD_TYPE_INT32 | FIELD_ID_ATOM_STATS_ERROR_COUNT, errors); - } - proto.end(token); - } - - for (const auto& pair : mPulledAtomStats) { - android::os::statsd::writePullerStatsToStream(pair, &proto); - } - - for (const auto& pair : mAtomMetricStats) { - android::os::statsd::writeAtomMetricStatsToStream(pair, &proto); - } - - if (mAnomalyAlarmRegisteredStats > 0) { - uint64_t token = proto.start(FIELD_TYPE_MESSAGE | FIELD_ID_ANOMALY_ALARM_STATS); - proto.write(FIELD_TYPE_INT32 | FIELD_ID_ANOMALY_ALARMS_REGISTERED, - mAnomalyAlarmRegisteredStats); - proto.end(token); - } - - if (mPeriodicAlarmRegisteredStats > 0) { - uint64_t token = proto.start(FIELD_TYPE_MESSAGE | FIELD_ID_PERIODIC_ALARM_STATS); - proto.write(FIELD_TYPE_INT32 | FIELD_ID_PERIODIC_ALARMS_REGISTERED, - mPeriodicAlarmRegisteredStats); - proto.end(token); - } - - uint64_t uidMapToken = proto.start(FIELD_TYPE_MESSAGE | FIELD_ID_UIDMAP_STATS); - proto.write(FIELD_TYPE_INT32 | FIELD_ID_UID_MAP_CHANGES, mUidMapStats.changes); - proto.write(FIELD_TYPE_INT32 | FIELD_ID_UID_MAP_BYTES_USED, mUidMapStats.bytes_used); - proto.write(FIELD_TYPE_INT32 | FIELD_ID_UID_MAP_DROPPED_CHANGES, mUidMapStats.dropped_changes); - proto.write(FIELD_TYPE_INT32 | FIELD_ID_UID_MAP_DELETED_APPS, mUidMapStats.deleted_apps); - proto.end(uidMapToken); - - for (const auto& error : mLogLossStats) { - uint64_t token = proto.start(FIELD_TYPE_MESSAGE | FIELD_ID_LOGGER_ERROR_STATS | - FIELD_COUNT_REPEATED); - proto.write(FIELD_TYPE_INT32 | FIELD_ID_LOG_LOSS_STATS_TIME, error.mWallClockSec); - proto.write(FIELD_TYPE_INT32 | FIELD_ID_LOG_LOSS_STATS_COUNT, error.mCount); - proto.write(FIELD_TYPE_INT32 | FIELD_ID_LOG_LOSS_STATS_ERROR, error.mLastError); - proto.write(FIELD_TYPE_INT32 | FIELD_ID_LOG_LOSS_STATS_TAG, error.mLastTag); - proto.write(FIELD_TYPE_INT32 | FIELD_ID_LOG_LOSS_STATS_UID, error.mUid); - proto.write(FIELD_TYPE_INT32 | FIELD_ID_LOG_LOSS_STATS_PID, error.mPid); - proto.end(token); - } - - if (mOverflowCount > 0) { - uint64_t token = proto.start(FIELD_TYPE_MESSAGE | FIELD_ID_OVERFLOW); - proto.write(FIELD_TYPE_INT32 | FIELD_ID_OVERFLOW_COUNT, (int32_t)mOverflowCount); - proto.write(FIELD_TYPE_INT64 | FIELD_ID_OVERFLOW_MAX_HISTORY, - (long long)mMaxQueueHistoryNs); - proto.write(FIELD_TYPE_INT64 | FIELD_ID_OVERFLOW_MIN_HISTORY, - (long long)mMinQueueHistoryNs); - proto.end(token); - } - - for (const auto& restart : mSystemServerRestartSec) { - proto.write(FIELD_TYPE_INT32 | FIELD_ID_SYSTEM_SERVER_RESTART | FIELD_COUNT_REPEATED, - restart); - } - - for (const auto& pair: mActivationBroadcastGuardrailStats) { - uint64_t token = proto.start(FIELD_TYPE_MESSAGE | - FIELD_ID_ACTIVATION_BROADCAST_GUARDRAIL | - FIELD_COUNT_REPEATED); - proto.write(FIELD_TYPE_INT32 | FIELD_ID_ACTIVATION_BROADCAST_GUARDRAIL_UID, - (int32_t) pair.first); - for (const auto& guardrailHitTime : pair.second) { - proto.write(FIELD_TYPE_INT32 | FIELD_ID_ACTIVATION_BROADCAST_GUARDRAIL_TIME | - FIELD_COUNT_REPEATED, - guardrailHitTime); - } - proto.end(token); - } - - output->clear(); - size_t bufferSize = proto.size(); - output->resize(bufferSize); - - size_t pos = 0; - sp reader = proto.data(); - while (reader->readBuffer() != NULL) { - size_t toRead = reader->currentToRead(); - std::memcpy(&((*output)[pos]), reader->readBuffer(), toRead); - pos += toRead; - reader->move(toRead); - } - - if (reset) { - resetInternalLocked(); - } - - VLOG("reset=%d, returned proto size %lu", reset, (unsigned long)bufferSize); -} - -} // namespace statsd -} // namespace os -} // namespace android diff --git a/bin/src/guardrail/StatsdStats.h b/bin/src/guardrail/StatsdStats.h deleted file mode 100644 index 40cfa3ac..00000000 --- a/bin/src/guardrail/StatsdStats.h +++ /dev/null @@ -1,686 +0,0 @@ -/* - * Copyright 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. - */ -#pragma once - -#include "config/ConfigKey.h" - -#include -#include -#include -#include -#include -#include -#include - -namespace android { -namespace os { -namespace statsd { - -struct ConfigStats { - int32_t uid; - int64_t id; - int32_t creation_time_sec; - int32_t deletion_time_sec = 0; - int32_t reset_time_sec = 0; - int32_t metric_count; - int32_t condition_count; - int32_t matcher_count; - int32_t alert_count; - bool is_valid; - - std::list broadcast_sent_time_sec; - - // Times at which this config is activated. - std::list activation_time_sec; - - // Times at which this config is deactivated. - std::list deactivation_time_sec; - - std::list data_drop_time_sec; - // Number of bytes dropped at corresponding time. - std::list data_drop_bytes; - std::list> dump_report_stats; - - // Stores how many times a matcher have been matched. The map size is capped by kMaxConfigCount. - std::map matcher_stats; - - // Stores the number of output tuple of condition trackers when it's bigger than - // kDimensionKeySizeSoftLimit. When you see the number is kDimensionKeySizeHardLimit +1, - // it means some data has been dropped. The map size is capped by kMaxConfigCount. - std::map condition_stats; - - // Stores the number of output tuple of metric producers when it's bigger than - // kDimensionKeySizeSoftLimit. When you see the number is kDimensionKeySizeHardLimit +1, - // it means some data has been dropped. The map size is capped by kMaxConfigCount. - std::map metric_stats; - - // Stores the max number of output tuple of dimensions in condition across dimensions in what - // when it's bigger than kDimensionKeySizeSoftLimit. When you see the number is - // kDimensionKeySizeHardLimit +1, it means some data has been dropped. The map size is capped by - // kMaxConfigCount. - std::map metric_dimension_in_condition_stats; - - // Stores the number of times an anomaly detection alert has been declared. - // The map size is capped by kMaxConfigCount. - std::map alert_stats; - - // Stores the config ID for each sub-config used. - std::list> annotations; -}; - -struct UidMapStats { - int32_t changes; - int32_t bytes_used; - int32_t dropped_changes; - int32_t deleted_apps = 0; -}; - -// Keeps track of stats of statsd. -// Single instance shared across the process. All public methods are thread safe. -class StatsdStats { -public: - static StatsdStats& getInstance(); - ~StatsdStats(){}; - - const static int kDimensionKeySizeSoftLimit = 500; - const static int kDimensionKeySizeHardLimit = 800; - - // Per atom dimension key size limit - static const std::map> kAtomDimensionKeySizeLimitMap; - - const static int kMaxConfigCountPerUid = 20; - const static int kMaxAlertCountPerConfig = 100; - const static int kMaxConditionCountPerConfig = 300; - const static int kMaxMetricCountPerConfig = 1000; - const static int kMaxMatcherCountPerConfig = 1200; - - // The max number of old config stats we keep. - const static int kMaxIceBoxSize = 20; - - const static int kMaxLoggerErrors = 20; - - const static int kMaxSystemServerRestarts = 20; - - const static int kMaxTimestampCount = 20; - - const static int kMaxLogSourceCount = 150; - - const static int kMaxPullAtomPackages = 100; - - // Max memory allowed for storing metrics per configuration. If this limit is exceeded, statsd - // drops the metrics data in memory. - static const size_t kMaxMetricsBytesPerConfig = 2 * 1024 * 1024; - - // Soft memory limit per configuration. Once this limit is exceeded, we begin notifying the - // data subscriber that it's time to call getData. - static const size_t kBytesPerConfigTriggerGetData = 192 * 1024; - - // Cap the UID map's memory usage to this. This should be fairly high since the UID information - // is critical for understanding the metrics. - const static size_t kMaxBytesUsedUidMap = 50 * 1024; - - // The number of deleted apps that are stored in the uid map. - const static int kMaxDeletedAppsInUidMap = 100; - - /* Minimum period between two broadcasts in nanoseconds. */ - static const int64_t kMinBroadcastPeriodNs = 60 * NS_PER_SEC; - - /* Min period between two checks of byte size per config key in nanoseconds. */ - static const int64_t kMinByteSizeCheckPeriodNs = 60 * NS_PER_SEC; - - /* Minimum period between two activation broadcasts in nanoseconds. */ - static const int64_t kMinActivationBroadcastPeriodNs = 10 * NS_PER_SEC; - - // Maximum age (30 days) that files on disk can exist in seconds. - static const int kMaxAgeSecond = 60 * 60 * 24 * 30; - - // Maximum age (2 days) that local history files on disk can exist in seconds. - static const int kMaxLocalHistoryAgeSecond = 60 * 60 * 24 * 2; - - // Maximum number of files (1000) that can be in stats directory on disk. - static const int kMaxFileNumber = 1000; - - // Maximum size of all files that can be written to stats directory on disk. - static const int kMaxFileSize = 50 * 1024 * 1024; - - // How long to try to clear puller cache from last time - static const long kPullerCacheClearIntervalSec = 1; - - // Max time to do a pull. - static const int64_t kPullMaxDelayNs = 30 * NS_PER_SEC; - - // Maximum number of pushed atoms statsd stats will track above kMaxPushedAtomId. - static const int kMaxNonPlatformPushedAtoms = 400; - - // Maximum atom id value that we consider a platform pushed atom. - // This should be updated once highest pushed atom id in atoms.proto approaches this value. - static const int kMaxPushedAtomId = 500; - - // Atom id that is the start of the pulled atoms. - static const int kPullAtomStartTag = 10000; - - // Atom id that is the start of vendor atoms. - static const int kVendorAtomStartTag = 100000; - - // Vendor pulled atom start id. - static const int32_t kVendorPulledAtomStartTag = 150000; - - // Beginning of range for timestamp truncation. - static const int32_t kTimestampTruncationStartTag = 300000; - - // End of range for timestamp truncation. - static const int32_t kTimestampTruncationEndTag = 304999; - - // Max accepted atom id. - static const int32_t kMaxAtomTag = 200000; - - static const int64_t kInt64Max = 0x7fffffffffffffffLL; - - static const int32_t kMaxLoggedBucketDropEvents = 10; - - /** - * Report a new config has been received and report the static stats about the config. - * - * The static stats include: the count of metrics, conditions, matchers, and alerts. - * If the config is not valid, this config stats will be put into icebox immediately. - */ - void noteConfigReceived(const ConfigKey& key, int metricsCount, int conditionsCount, - int matchersCount, int alertCount, - const std::list>& annotations, - bool isValid); - /** - * Report a config has been removed. - */ - void noteConfigRemoved(const ConfigKey& key); - /** - * Report a config has been reset when ttl expires. - */ - void noteConfigReset(const ConfigKey& key); - - /** - * Report a broadcast has been sent to a config owner to collect the data. - */ - void noteBroadcastSent(const ConfigKey& key); - - /** - * Report that a config has become activated or deactivated. - * This can be different from whether or not a broadcast is sent if the - * guardrail prevented the broadcast from being sent. - */ - void noteActiveStatusChanged(const ConfigKey& key, bool activate); - - /** - * Report a config's metrics data has been dropped. - */ - void noteDataDropped(const ConfigKey& key, const size_t totalBytes); - - /** - * Report metrics data report has been sent. - * - * The report may be requested via StatsManager API, or through adb cmd. - */ - void noteMetricsReportSent(const ConfigKey& key, const size_t num_bytes); - - /** - * Report the size of output tuple of a condition. - * - * Note: only report when the condition has an output dimension, and the tuple - * count > kDimensionKeySizeSoftLimit. - * - * [key]: The config key that this condition belongs to. - * [id]: The id of the condition. - * [size]: The output tuple size. - */ - void noteConditionDimensionSize(const ConfigKey& key, const int64_t& id, int size); - - /** - * Report the size of output tuple of a metric. - * - * Note: only report when the metric has an output dimension, and the tuple - * count > kDimensionKeySizeSoftLimit. - * - * [key]: The config key that this metric belongs to. - * [id]: The id of the metric. - * [size]: The output tuple size. - */ - void noteMetricDimensionSize(const ConfigKey& key, const int64_t& id, int size); - - /** - * Report the max size of output tuple of dimension in condition across dimensions in what. - * - * Note: only report when the metric has an output dimension in condition, and the max tuple - * count > kDimensionKeySizeSoftLimit. - * - * [key]: The config key that this metric belongs to. - * [id]: The id of the metric. - * [size]: The output tuple size. - */ - void noteMetricDimensionInConditionSize(const ConfigKey& key, const int64_t& id, int size); - - /** - * Report a matcher has been matched. - * - * [key]: The config key that this matcher belongs to. - * [id]: The id of the matcher. - */ - void noteMatcherMatched(const ConfigKey& key, const int64_t& id); - - /** - * Report that an anomaly detection alert has been declared. - * - * [key]: The config key that this alert belongs to. - * [id]: The id of the alert. - */ - void noteAnomalyDeclared(const ConfigKey& key, const int64_t& id); - - /** - * Report an atom event has been logged. - */ - void noteAtomLogged(int atomId, int32_t timeSec); - - /** - * Report that statsd modified the anomaly alarm registered with StatsCompanionService. - */ - void noteRegisteredAnomalyAlarmChanged(); - - /** - * Report that statsd modified the periodic alarm registered with StatsCompanionService. - */ - void noteRegisteredPeriodicAlarmChanged(); - - /** - * Records the number of delta entries that are being dropped from the uid map. - */ - void noteUidMapDropped(int deltas); - - /** - * Records that an app was deleted (from statsd's map). - */ - void noteUidMapAppDeletionDropped(); - - /** - * Updates the number of changes currently stored in the uid map. - */ - void setUidMapChanges(int changes); - void setCurrentUidMapMemory(int bytes); - - /* - * Updates minimum interval between pulls for an pulled atom. - */ - void updateMinPullIntervalSec(int pullAtomId, long intervalSec); - - /* - * Notes an atom is pulled. - */ - void notePull(int pullAtomId); - - /* - * Notes an atom is served from puller cache. - */ - void notePullFromCache(int pullAtomId); - - /* - * Notify data error for pulled atom. - */ - void notePullDataError(int pullAtomId); - - /* - * Records time for actual pulling, not including those served from cache and not including - * statsd processing delays. - */ - void notePullTime(int pullAtomId, int64_t pullTimeNs); - - /* - * Records pull delay for a pulled atom, including those served from cache and including statsd - * processing delays. - */ - void notePullDelay(int pullAtomId, int64_t pullDelayNs); - - /* - * Records pull exceeds timeout for the puller. - */ - void notePullTimeout(int pullAtomId, int64_t pullUptimeMillis, int64_t pullElapsedMillis); - - /* - * Records pull exceeds max delay for a metric. - */ - void notePullExceedMaxDelay(int pullAtomId); - - /* - * Records when system server restarts. - */ - void noteSystemServerRestart(int32_t timeSec); - - /** - * Records statsd skipped an event. - */ - void noteLogLost(int32_t wallClockTimeSec, int32_t count, int32_t lastError, - int32_t lastAtomTag, int32_t uid, int32_t pid); - - /** - * Records that the pull of an atom has failed. Eg, if the client indicated the pull failed, if - * the pull timed out, or if the outgoing binder call failed. - * This count will only increment if the puller was actually invoked. - * - * It does not include a pull not occurring due to not finding the appropriate - * puller. These cases are covered in other counts. - */ - void notePullFailed(int atomId); - - /** - * Records that the pull of an atom has failed due to not having a uid provider. - */ - void notePullUidProviderNotFound(int atomId); - - /** - * Records that the pull of an atom has failed due not finding a puller registered by a - * trusted uid. - */ - void notePullerNotFound(int atomId); - - /** - * Records that the pull has failed due to the outgoing binder call failing. - */ - void notePullBinderCallFailed(int atomId); - - /** - * A pull with no data occurred - */ - void noteEmptyData(int atomId); - - /** - * Records that a puller callback for the given atomId was registered or unregistered. - * - * @param registered True if the callback was registered, false if was unregistered. - */ - void notePullerCallbackRegistrationChanged(int atomId, bool registered); - - /** - * Hard limit was reached in the cardinality of an atom - */ - void noteHardDimensionLimitReached(int64_t metricId); - - /** - * A log event was too late, arrived in the wrong bucket and was skipped - */ - void noteLateLogEventSkipped(int64_t metricId); - - /** - * A log event was too late, arrived in the wrong bucket. - */ - void noteLateLogEvent(int64_t metricId, int64_t extraDurationNs); - - /** - * Buckets were skipped as time elapsed without any data for them - */ - void noteSkippedForwardBuckets(int64_t metricId); - - /** - * An unsupported value type was received - */ - void noteBadValueType(int64_t metricId); - - /** - * Buckets were dropped due to reclaim memory. - */ - void noteBucketDropped(int64_t metricId); - - /** - * A condition change was too late, arrived in the wrong bucket and was skipped - */ - void noteConditionChangeInNextBucket(int64_t metricId); - - /** - * A bucket has been tagged as invalid. - */ - void noteInvalidatedBucket(int64_t metricId); - - /** - * Tracks the total number of buckets (include skipped/invalid buckets). - */ - void noteBucketCount(int64_t metricId); - - /** - * For pulls at bucket boundaries, it represents the misalignment between the real timestamp and - * the end of the bucket. - */ - void noteBucketBoundaryDelayNs(int64_t metricId, int64_t timeDelayNs); - - /** - * Number of buckets with unknown condition. - */ - void noteBucketUnknownCondition(int64_t metricId); - - /* Reports one event has been dropped due to queue overflow, and the oldest event timestamp in - * the queue */ - void noteEventQueueOverflow(int64_t oldestEventTimestampNs); - - /** - * Reports that the activation broadcast guardrail was hit for this uid. Namely, the broadcast - * should have been sent, but instead was skipped due to hitting the guardrail. - */ - void noteActivationBroadcastGuardrailHit(const int uid); - - /** - * Reports that an atom is erroneous or cannot be parsed successfully by - * statsd. An atom tag of 0 indicates that the client did not supply the - * atom id within the encoding. - * - * For pushed atoms only, this call should be preceded by a call to - * noteAtomLogged. - */ - void noteAtomError(int atomTag, bool pull=false); - - /** - * Reset the historical stats. Including all stats in icebox, and the tracked stats about - * metrics, matchers, and atoms. The active configs will be kept and StatsdStats will continue - * to collect stats after reset() has been called. - */ - void reset(); - - /** - * Output the stats in protobuf binary format to [buffer]. - * - * [reset]: whether to clear the historical stats after the call. - */ - void dumpStats(std::vector* buffer, bool reset); - - /** - * Output statsd stats in human readable format to [out] file descriptor. - */ - void dumpStats(int outFd) const; - - typedef struct PullTimeoutMetadata { - int64_t pullTimeoutUptimeMillis; - int64_t pullTimeoutElapsedMillis; - PullTimeoutMetadata(int64_t uptimeMillis, int64_t elapsedMillis) : - pullTimeoutUptimeMillis(uptimeMillis), - pullTimeoutElapsedMillis(elapsedMillis) {/* do nothing */} - } PullTimeoutMetadata; - - typedef struct { - long totalPull = 0; - long totalPullFromCache = 0; - long minPullIntervalSec = LONG_MAX; - int64_t avgPullTimeNs = 0; - int64_t maxPullTimeNs = 0; - long numPullTime = 0; - int64_t avgPullDelayNs = 0; - int64_t maxPullDelayNs = 0; - long numPullDelay = 0; - long dataError = 0; - long pullTimeout = 0; - long pullExceedMaxDelay = 0; - long pullFailed = 0; - long pullUidProviderNotFound = 0; - long pullerNotFound = 0; - long emptyData = 0; - long registeredCount = 0; - long unregisteredCount = 0; - int32_t atomErrorCount = 0; - long binderCallFailCount = 0; - std::list pullTimeoutMetadata; - } PulledAtomStats; - - typedef struct { - long hardDimensionLimitReached = 0; - long lateLogEventSkipped = 0; - long skippedForwardBuckets = 0; - long badValueType = 0; - long conditionChangeInNextBucket = 0; - long invalidatedBucket = 0; - long bucketDropped = 0; - int64_t minBucketBoundaryDelayNs = 0; - int64_t maxBucketBoundaryDelayNs = 0; - long bucketUnknownCondition = 0; - long bucketCount = 0; - long lateLogEvent = 0; - int64_t sumLateLogEventExtraDurationNs = 0; - int64_t maxLateLogEventExtraDurationNs = 0; - } AtomMetricStats; - -private: - StatsdStats(); - - mutable std::mutex mLock; - - int32_t mStartTimeSec; - - // Track the number of dropped entries used by the uid map. - UidMapStats mUidMapStats; - - // The stats about the configs that are still in use. - // The map size is capped by kMaxConfigCount. - std::map> mConfigStats; - - // Stores the stats for the configs that are no longer in use. - // The size of the vector is capped by kMaxIceBoxSize. - std::list> mIceBox; - - // Stores the number of times a pushed atom is logged. - // The size of the vector is the largest pushed atom id in atoms.proto + 1. Atoms - // out of that range will be put in mNonPlatformPushedAtomStats. - // This is a vector, not a map because it will be accessed A LOT -- for each stats log. - std::vector mPushedAtomStats; - - // Stores the number of times a pushed atom is logged for atom ids above kMaxPushedAtomId. - // The max size of the map is kMaxNonPlatformPushedAtoms. - std::unordered_map mNonPlatformPushedAtomStats; - - // Maps PullAtomId to its stats. The size is capped by the puller atom counts. - std::map mPulledAtomStats; - - // Stores the number of times a pushed atom was logged erroneously. The - // corresponding counts for pulled atoms are stored in PulledAtomStats. - // The max size of this map is kMaxAtomErrorsStatsSize. - std::map mPushedAtomErrorStats; - int kMaxPushedAtomErrorStatsSize = 100; - - // Maps metric ID to its stats. The size is capped by the number of metrics. - std::map mAtomMetricStats; - - // Maps uids to times when the activation changed broadcast not sent due to hitting the - // guardrail. The size is capped by the number of configs, and up to 20 times per uid. - std::map> mActivationBroadcastGuardrailStats; - - struct LogLossStats { - LogLossStats(int32_t sec, int32_t count, int32_t error, int32_t tag, int32_t uid, - int32_t pid) - : mWallClockSec(sec), - mCount(count), - mLastError(error), - mLastTag(tag), - mUid(uid), - mPid(pid) { - } - int32_t mWallClockSec; - int32_t mCount; - // error code defined in linux/errno.h - int32_t mLastError; - int32_t mLastTag; - int32_t mUid; - int32_t mPid; - }; - - // Max of {(now - oldestEventTimestamp) when overflow happens}. - // This number is helpful to understand how SLOW statsd can be. - int64_t mMaxQueueHistoryNs = 0; - - // Min of {(now - oldestEventTimestamp) when overflow happens}. - // This number is helpful to understand how FAST the events floods to statsd. - int64_t mMinQueueHistoryNs = kInt64Max; - - // Total number of events that are lost due to queue overflow. - int32_t mOverflowCount = 0; - - // Timestamps when we detect log loss, and the number of logs lost. - std::list mLogLossStats; - - std::list mSystemServerRestartSec; - - // Stores the number of times statsd modified the anomaly alarm registered with - // StatsCompanionService. - int mAnomalyAlarmRegisteredStats = 0; - - // Stores the number of times statsd registers the periodic alarm changes - int mPeriodicAlarmRegisteredStats = 0; - - void noteConfigResetInternalLocked(const ConfigKey& key); - - void noteConfigRemovedInternalLocked(const ConfigKey& key); - - void resetInternalLocked(); - - void noteDataDropped(const ConfigKey& key, const size_t totalBytes, int32_t timeSec); - - void noteMetricsReportSent(const ConfigKey& key, const size_t num_bytes, int32_t timeSec); - - void noteBroadcastSent(const ConfigKey& key, int32_t timeSec); - - void noteActiveStatusChanged(const ConfigKey& key, bool activate, int32_t timeSec); - - void noteActivationBroadcastGuardrailHit(const int uid, int32_t timeSec); - - void addToIceBoxLocked(std::shared_ptr& stats); - - int getPushedAtomErrors(int atomId) const; - - /** - * Get a reference to AtomMetricStats for a metric. If none exists, create it. The reference - * will live as long as `this`. - */ - StatsdStats::AtomMetricStats& getAtomMetricStats(int64_t metricId); - - FRIEND_TEST(StatsdStatsTest, TestValidConfigAdd); - FRIEND_TEST(StatsdStatsTest, TestInvalidConfigAdd); - FRIEND_TEST(StatsdStatsTest, TestConfigRemove); - FRIEND_TEST(StatsdStatsTest, TestSubStats); - FRIEND_TEST(StatsdStatsTest, TestAtomLog); - FRIEND_TEST(StatsdStatsTest, TestNonPlatformAtomLog); - FRIEND_TEST(StatsdStatsTest, TestTimestampThreshold); - FRIEND_TEST(StatsdStatsTest, TestAnomalyMonitor); - FRIEND_TEST(StatsdStatsTest, TestSystemServerCrash); - FRIEND_TEST(StatsdStatsTest, TestPullAtomStats); - FRIEND_TEST(StatsdStatsTest, TestAtomMetricsStats); - FRIEND_TEST(StatsdStatsTest, TestActivationBroadcastGuardrailHit); - FRIEND_TEST(StatsdStatsTest, TestAtomErrorStats); - - FRIEND_TEST(StatsLogProcessorTest, InvalidConfigRemoved); -}; - -} // namespace statsd -} // namespace os -} // namespace android diff --git a/bin/src/hash.cpp b/bin/src/hash.cpp deleted file mode 100644 index 543a748a..00000000 --- a/bin/src/hash.cpp +++ /dev/null @@ -1,142 +0,0 @@ -/* - * 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 "hash.h" - -#ifndef FALLTHROUGH_INTENDED -#define FALLTHROUGH_INTENDED [[fallthrough]] -#endif - -namespace android { -namespace os { -namespace statsd { - -namespace { -// Lower-level versions of Get... that read directly from a character buffer -// without any bounds checking. -inline uint32_t DecodeFixed32(const char *ptr) { - return ((static_cast(static_cast(ptr[0]))) | - (static_cast(static_cast(ptr[1])) << 8) | - (static_cast(static_cast(ptr[2])) << 16) | - (static_cast(static_cast(ptr[3])) << 24)); -} - -inline uint64_t DecodeFixed64(const char* ptr) { - uint64_t lo = DecodeFixed32(ptr); - uint64_t hi = DecodeFixed32(ptr + 4); - return (hi << 32) | lo; -} - -// 0xff is in case char is signed. -static inline uint32_t ByteAs32(char c) { return static_cast(c) & 0xff; } -static inline uint64_t ByteAs64(char c) { return static_cast(c) & 0xff; } - -} // namespace - -uint32_t Hash32(const char *data, size_t n, uint32_t seed) { - // 'm' and 'r' are mixing constants generated offline. - // They're not really 'magic', they just happen to work well. - const uint32_t m = 0x5bd1e995; - const int r = 24; - - // Initialize the hash to a 'random' value - uint32_t h = static_cast(seed ^ n); - - // Mix 4 bytes at a time into the hash - while (n >= 4) { - uint32_t k = DecodeFixed32(data); - k *= m; - k ^= k >> r; - k *= m; - h *= m; - h ^= k; - data += 4; - n -= 4; - } - - // Handle the last few bytes of the input array - switch (n) { - case 3: - h ^= ByteAs32(data[2]) << 16; - FALLTHROUGH_INTENDED; - case 2: - h ^= ByteAs32(data[1]) << 8; - FALLTHROUGH_INTENDED; - case 1: - h ^= ByteAs32(data[0]); - h *= m; - } - - // Do a few final mixes of the hash to ensure the last few - // bytes are well-incorporated. - h ^= h >> 13; - h *= m; - h ^= h >> 15; - return h; -} - -uint64_t Hash64(const char* data, size_t n, uint64_t seed) { - const uint64_t m = 0xc6a4a7935bd1e995; - const int r = 47; - - uint64_t h = seed ^ (n * m); - - while (n >= 8) { - uint64_t k = DecodeFixed64(data); - data += 8; - n -= 8; - - k *= m; - k ^= k >> r; - k *= m; - - h ^= k; - h *= m; - } - - switch (n) { - case 7: - h ^= ByteAs64(data[6]) << 48; - FALLTHROUGH_INTENDED; - case 6: - h ^= ByteAs64(data[5]) << 40; - FALLTHROUGH_INTENDED; - case 5: - h ^= ByteAs64(data[4]) << 32; - FALLTHROUGH_INTENDED; - case 4: - h ^= ByteAs64(data[3]) << 24; - FALLTHROUGH_INTENDED; - case 3: - h ^= ByteAs64(data[2]) << 16; - FALLTHROUGH_INTENDED; - case 2: - h ^= ByteAs64(data[1]) << 8; - FALLTHROUGH_INTENDED; - case 1: - h ^= ByteAs64(data[0]); - h *= m; - } - - h ^= h >> r; - h *= m; - h ^= h >> r; - - return h; -} -} // namespace statsd -} // namespace os -} // namespace android diff --git a/bin/src/hash.h b/bin/src/hash.h deleted file mode 100644 index bd6b0cd4..00000000 --- a/bin/src/hash.h +++ /dev/null @@ -1,47 +0,0 @@ -/* - * 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. - */ - -#pragma once - -#include - -namespace android { -namespace os { -namespace statsd { - -// Uses murmur2 hashing algorithm. -extern uint32_t Hash32(const char *data, size_t n, uint32_t seed); -extern uint64_t Hash64(const char* data, size_t n, uint64_t seed); - -inline uint32_t Hash32(const char *data, size_t n) { - return Hash32(data, n, 0xBEEF); -} - -inline uint32_t Hash32(const std::string &input) { - return Hash32(input.data(), input.size()); -} - -inline uint64_t Hash64(const char* data, size_t n) { - return Hash64(data, n, 0xDECAFCAFFE); -} - -inline uint64_t Hash64(const std::string& str) { - return Hash64(str.data(), str.size()); -} - -} // namespace statsd -} // namespace os -} // namespace android diff --git a/bin/src/logd/LogEvent.cpp b/bin/src/logd/LogEvent.cpp deleted file mode 100644 index 4f031724..00000000 --- a/bin/src/logd/LogEvent.cpp +++ /dev/null @@ -1,618 +0,0 @@ -/* - * 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. - */ - -#define DEBUG false // STOPSHIP if true -#include "logd/LogEvent.h" - -#include -#include -#include -#include - -#include "annotations.h" -#include "stats_log_util.h" -#include "statslog_statsd.h" - -namespace android { -namespace os { -namespace statsd { - -// for TrainInfo experiment id serialization -const int FIELD_ID_EXPERIMENT_ID = 1; - -using namespace android::util; -using android::base::StringPrintf; -using android::util::ProtoOutputStream; -using std::string; -using std::vector; - -// stats_event.h socket types. Keep in sync. -/* ERRORS */ -#define ERROR_NO_TIMESTAMP 0x1 -#define ERROR_NO_ATOM_ID 0x2 -#define ERROR_OVERFLOW 0x4 -#define ERROR_ATTRIBUTION_CHAIN_TOO_LONG 0x8 -#define ERROR_TOO_MANY_KEY_VALUE_PAIRS 0x10 -#define ERROR_ANNOTATION_DOES_NOT_FOLLOW_FIELD 0x20 -#define ERROR_INVALID_ANNOTATION_ID 0x40 -#define ERROR_ANNOTATION_ID_TOO_LARGE 0x80 -#define ERROR_TOO_MANY_ANNOTATIONS 0x100 -#define ERROR_TOO_MANY_FIELDS 0x200 -#define ERROR_INVALID_VALUE_TYPE 0x400 -#define ERROR_STRING_NOT_NULL_TERMINATED 0x800 - -/* TYPE IDS */ -#define INT32_TYPE 0x00 -#define INT64_TYPE 0x01 -#define STRING_TYPE 0x02 -#define LIST_TYPE 0x03 -#define FLOAT_TYPE 0x04 -#define BOOL_TYPE 0x05 -#define BYTE_ARRAY_TYPE 0x06 -#define OBJECT_TYPE 0x07 -#define KEY_VALUE_PAIRS_TYPE 0x08 -#define ATTRIBUTION_CHAIN_TYPE 0x09 -#define ERROR_TYPE 0x0F - -LogEvent::LogEvent(int32_t uid, int32_t pid) - : mLogdTimestampNs(time(nullptr)), mLogUid(uid), mLogPid(pid) { -} - -LogEvent::LogEvent(const string& trainName, int64_t trainVersionCode, bool requiresStaging, - bool rollbackEnabled, bool requiresLowLatencyMonitor, int32_t state, - const std::vector& experimentIds, int32_t userId) { - mLogdTimestampNs = getWallClockNs(); - mElapsedTimestampNs = getElapsedRealtimeNs(); - mTagId = util::BINARY_PUSH_STATE_CHANGED; - mLogUid = AIBinder_getCallingUid(); - mLogPid = AIBinder_getCallingPid(); - - mValues.push_back(FieldValue(Field(mTagId, getSimpleField(1)), Value(trainName))); - mValues.push_back(FieldValue(Field(mTagId, getSimpleField(2)), Value(trainVersionCode))); - mValues.push_back(FieldValue(Field(mTagId, getSimpleField(3)), Value((int)requiresStaging))); - mValues.push_back(FieldValue(Field(mTagId, getSimpleField(4)), Value((int)rollbackEnabled))); - mValues.push_back( - FieldValue(Field(mTagId, getSimpleField(5)), Value((int)requiresLowLatencyMonitor))); - mValues.push_back(FieldValue(Field(mTagId, getSimpleField(6)), Value(state))); - mValues.push_back(FieldValue(Field(mTagId, getSimpleField(7)), Value(experimentIds))); - mValues.push_back(FieldValue(Field(mTagId, getSimpleField(8)), Value(userId))); -} - -LogEvent::LogEvent(int64_t wallClockTimestampNs, int64_t elapsedTimestampNs, - const InstallTrainInfo& trainInfo) { - mLogdTimestampNs = wallClockTimestampNs; - mElapsedTimestampNs = elapsedTimestampNs; - mTagId = util::TRAIN_INFO; - - mValues.push_back( - FieldValue(Field(mTagId, getSimpleField(1)), Value(trainInfo.trainVersionCode))); - std::vector experimentIdsProto; - writeExperimentIdsToProto(trainInfo.experimentIds, &experimentIdsProto); - mValues.push_back(FieldValue(Field(mTagId, getSimpleField(2)), Value(experimentIdsProto))); - mValues.push_back(FieldValue(Field(mTagId, getSimpleField(3)), Value(trainInfo.trainName))); - mValues.push_back(FieldValue(Field(mTagId, getSimpleField(4)), Value(trainInfo.status))); -} - -void LogEvent::parseInt32(int32_t* pos, int32_t depth, bool* last, uint8_t numAnnotations) { - int32_t value = readNextValue(); - addToValues(pos, depth, value, last); - parseAnnotations(numAnnotations); -} - -void LogEvent::parseInt64(int32_t* pos, int32_t depth, bool* last, uint8_t numAnnotations) { - int64_t value = readNextValue(); - addToValues(pos, depth, value, last); - parseAnnotations(numAnnotations); -} - -void LogEvent::parseString(int32_t* pos, int32_t depth, bool* last, uint8_t numAnnotations) { - int32_t numBytes = readNextValue(); - if ((uint32_t)numBytes > mRemainingLen) { - mValid = false; - return; - } - - string value = string((char*)mBuf, numBytes); - mBuf += numBytes; - mRemainingLen -= numBytes; - addToValues(pos, depth, value, last); - parseAnnotations(numAnnotations); -} - -void LogEvent::parseFloat(int32_t* pos, int32_t depth, bool* last, uint8_t numAnnotations) { - float value = readNextValue(); - addToValues(pos, depth, value, last); - parseAnnotations(numAnnotations); -} - -void LogEvent::parseBool(int32_t* pos, int32_t depth, bool* last, uint8_t numAnnotations) { - // cast to int32_t because FieldValue does not support bools - int32_t value = (int32_t)readNextValue(); - addToValues(pos, depth, value, last); - parseAnnotations(numAnnotations); -} - -void LogEvent::parseByteArray(int32_t* pos, int32_t depth, bool* last, uint8_t numAnnotations) { - int32_t numBytes = readNextValue(); - if ((uint32_t)numBytes > mRemainingLen) { - mValid = false; - return; - } - - vector value(mBuf, mBuf + numBytes); - mBuf += numBytes; - mRemainingLen -= numBytes; - addToValues(pos, depth, value, last); - parseAnnotations(numAnnotations); -} - -void LogEvent::parseKeyValuePairs(int32_t* pos, int32_t depth, bool* last, uint8_t numAnnotations) { - int32_t numPairs = readNextValue(); - - for (pos[1] = 1; pos[1] <= numPairs; pos[1]++) { - last[1] = (pos[1] == numPairs); - - // parse key - pos[2] = 1; - parseInt32(pos, /*depth=*/2, last, /*numAnnotations=*/0); - - // parse value - last[2] = true; - - uint8_t typeInfo = readNextValue(); - switch (getTypeId(typeInfo)) { - case INT32_TYPE: - pos[2] = 2; // pos[2] determined by index of type in KeyValuePair in atoms.proto - parseInt32(pos, /*depth=*/2, last, /*numAnnotations=*/0); - break; - case INT64_TYPE: - pos[2] = 3; - parseInt64(pos, /*depth=*/2, last, /*numAnnotations=*/0); - break; - case STRING_TYPE: - pos[2] = 4; - parseString(pos, /*depth=*/2, last, /*numAnnotations=*/0); - break; - case FLOAT_TYPE: - pos[2] = 5; - parseFloat(pos, /*depth=*/2, last, /*numAnnotations=*/0); - break; - default: - mValid = false; - } - } - - parseAnnotations(numAnnotations); - - pos[1] = pos[2] = 1; - last[1] = last[2] = false; -} - -void LogEvent::parseAttributionChain(int32_t* pos, int32_t depth, bool* last, - uint8_t numAnnotations) { - const unsigned int firstUidInChainIndex = mValues.size(); - const int32_t numNodes = readNextValue(); - for (pos[1] = 1; pos[1] <= numNodes; pos[1]++) { - last[1] = (pos[1] == numNodes); - - // parse uid - pos[2] = 1; - parseInt32(pos, /*depth=*/2, last, /*numAnnotations=*/0); - - // parse tag - pos[2] = 2; - last[2] = true; - parseString(pos, /*depth=*/2, last, /*numAnnotations=*/0); - } - - if (mValues.size() - 1 > INT8_MAX) { - mValid = false; - } else if (mValues.size() - 1 > firstUidInChainIndex) { - // At least one node was successfully parsed. - mAttributionChainStartIndex = static_cast(firstUidInChainIndex); - mAttributionChainEndIndex = static_cast(mValues.size() - 1); - } - - if (mValid) { - parseAnnotations(numAnnotations, firstUidInChainIndex); - } - - pos[1] = pos[2] = 1; - last[1] = last[2] = false; -} - -// Assumes that mValues is not empty -bool LogEvent::checkPreviousValueType(Type expected) { - return mValues[mValues.size() - 1].mValue.getType() == expected; -} - -void LogEvent::parseIsUidAnnotation(uint8_t annotationType) { - if (mValues.empty() || mValues.size() - 1 > INT8_MAX || !checkPreviousValueType(INT) - || annotationType != BOOL_TYPE) { - mValid = false; - return; - } - - bool isUid = readNextValue(); - if (isUid) mUidFieldIndex = static_cast(mValues.size() - 1); - mValues[mValues.size() - 1].mAnnotations.setUidField(isUid); -} - -void LogEvent::parseTruncateTimestampAnnotation(uint8_t annotationType) { - if (!mValues.empty() || annotationType != BOOL_TYPE) { - mValid = false; - return; - } - - mTruncateTimestamp = readNextValue(); -} - -void LogEvent::parsePrimaryFieldAnnotation(uint8_t annotationType) { - if (mValues.empty() || annotationType != BOOL_TYPE) { - mValid = false; - return; - } - - const bool primaryField = readNextValue(); - mValues[mValues.size() - 1].mAnnotations.setPrimaryField(primaryField); -} - -void LogEvent::parsePrimaryFieldFirstUidAnnotation(uint8_t annotationType, - int firstUidInChainIndex) { - if (mValues.empty() || annotationType != BOOL_TYPE || -1 == firstUidInChainIndex) { - mValid = false; - return; - } - - if (static_cast(mValues.size() - 1) < firstUidInChainIndex) { // AttributionChain is empty. - mValid = false; - android_errorWriteLog(0x534e4554, "174485572"); - return; - } - - const bool primaryField = readNextValue(); - mValues[firstUidInChainIndex].mAnnotations.setPrimaryField(primaryField); -} - -void LogEvent::parseExclusiveStateAnnotation(uint8_t annotationType) { - if (mValues.empty() || annotationType != BOOL_TYPE) { - mValid = false; - return; - } - - if (mValues.size() - 1 > INT8_MAX) { - android_errorWriteLog(0x534e4554, "174488848"); - mValid = false; - return; - } - - const bool exclusiveState = readNextValue(); - mExclusiveStateFieldIndex = static_cast(mValues.size() - 1); - mValues[getExclusiveStateFieldIndex()].mAnnotations.setExclusiveState(exclusiveState); -} - -void LogEvent::parseTriggerStateResetAnnotation(uint8_t annotationType) { - if (mValues.empty() || annotationType != INT32_TYPE) { - mValid = false; - return; - } - - mResetState = readNextValue(); -} - -void LogEvent::parseStateNestedAnnotation(uint8_t annotationType) { - if (mValues.empty() || annotationType != BOOL_TYPE) { - mValid = false; - return; - } - - bool nested = readNextValue(); - mValues[mValues.size() - 1].mAnnotations.setNested(nested); -} - -// firstUidInChainIndex is a default parameter that is only needed when parsing -// annotations for attribution chains. -void LogEvent::parseAnnotations(uint8_t numAnnotations, int firstUidInChainIndex) { - for (uint8_t i = 0; i < numAnnotations; i++) { - uint8_t annotationId = readNextValue(); - uint8_t annotationType = readNextValue(); - - switch (annotationId) { - case ANNOTATION_ID_IS_UID: - parseIsUidAnnotation(annotationType); - break; - case ANNOTATION_ID_TRUNCATE_TIMESTAMP: - parseTruncateTimestampAnnotation(annotationType); - break; - case ANNOTATION_ID_PRIMARY_FIELD: - parsePrimaryFieldAnnotation(annotationType); - break; - case ANNOTATION_ID_PRIMARY_FIELD_FIRST_UID: - parsePrimaryFieldFirstUidAnnotation(annotationType, firstUidInChainIndex); - break; - case ANNOTATION_ID_EXCLUSIVE_STATE: - parseExclusiveStateAnnotation(annotationType); - break; - case ANNOTATION_ID_TRIGGER_STATE_RESET: - parseTriggerStateResetAnnotation(annotationType); - break; - case ANNOTATION_ID_STATE_NESTED: - parseStateNestedAnnotation(annotationType); - break; - default: - mValid = false; - return; - } - } -} - -// This parsing logic is tied to the encoding scheme used in StatsEvent.java and -// stats_event.c -bool LogEvent::parseBuffer(uint8_t* buf, size_t len) { - mBuf = buf; - mRemainingLen = (uint32_t)len; - - int32_t pos[] = {1, 1, 1}; - bool last[] = {false, false, false}; - - // Beginning of buffer is OBJECT_TYPE | NUM_FIELDS | TIMESTAMP | ATOM_ID - uint8_t typeInfo = readNextValue(); - if (getTypeId(typeInfo) != OBJECT_TYPE) mValid = false; - - uint8_t numElements = readNextValue(); - if (numElements < 2 || numElements > 127) mValid = false; - - typeInfo = readNextValue(); - if (getTypeId(typeInfo) != INT64_TYPE) mValid = false; - mElapsedTimestampNs = readNextValue(); - numElements--; - - typeInfo = readNextValue(); - if (getTypeId(typeInfo) != INT32_TYPE) mValid = false; - mTagId = readNextValue(); - numElements--; - parseAnnotations(getNumAnnotations(typeInfo)); // atom-level annotations - - for (pos[0] = 1; pos[0] <= numElements && mValid; pos[0]++) { - last[0] = (pos[0] == numElements); - - typeInfo = readNextValue(); - uint8_t typeId = getTypeId(typeInfo); - - switch (typeId) { - case BOOL_TYPE: - parseBool(pos, /*depth=*/0, last, getNumAnnotations(typeInfo)); - break; - case INT32_TYPE: - parseInt32(pos, /*depth=*/0, last, getNumAnnotations(typeInfo)); - break; - case INT64_TYPE: - parseInt64(pos, /*depth=*/0, last, getNumAnnotations(typeInfo)); - break; - case FLOAT_TYPE: - parseFloat(pos, /*depth=*/0, last, getNumAnnotations(typeInfo)); - break; - case BYTE_ARRAY_TYPE: - parseByteArray(pos, /*depth=*/0, last, getNumAnnotations(typeInfo)); - break; - case STRING_TYPE: - parseString(pos, /*depth=*/0, last, getNumAnnotations(typeInfo)); - break; - case KEY_VALUE_PAIRS_TYPE: - parseKeyValuePairs(pos, /*depth=*/0, last, getNumAnnotations(typeInfo)); - break; - case ATTRIBUTION_CHAIN_TYPE: - parseAttributionChain(pos, /*depth=*/0, last, getNumAnnotations(typeInfo)); - break; - case ERROR_TYPE: - /* mErrorBitmask =*/ readNextValue(); - mValid = false; - break; - default: - mValid = false; - break; - } - } - - if (mRemainingLen != 0) mValid = false; - mBuf = nullptr; - return mValid; -} - -uint8_t LogEvent::getTypeId(uint8_t typeInfo) { - return typeInfo & 0x0F; // type id in lower 4 bytes -} - -uint8_t LogEvent::getNumAnnotations(uint8_t typeInfo) { - return (typeInfo >> 4) & 0x0F; // num annotations in upper 4 bytes -} - -int64_t LogEvent::GetLong(size_t key, status_t* err) const { - // TODO(b/110561208): encapsulate the magical operations in Field struct as static functions - int field = getSimpleField(key); - for (const auto& value : mValues) { - if (value.mField.getField() == field) { - if (value.mValue.getType() == LONG) { - return value.mValue.long_value; - } else if (value.mValue.getType() == INT) { - return value.mValue.int_value; - } else { - *err = BAD_TYPE; - return 0; - } - } - if ((size_t)value.mField.getPosAtDepth(0) > key) { - break; - } - } - - *err = BAD_INDEX; - return 0; -} - -int LogEvent::GetInt(size_t key, status_t* err) const { - int field = getSimpleField(key); - for (const auto& value : mValues) { - if (value.mField.getField() == field) { - if (value.mValue.getType() == INT) { - return value.mValue.int_value; - } else { - *err = BAD_TYPE; - return 0; - } - } - if ((size_t)value.mField.getPosAtDepth(0) > key) { - break; - } - } - - *err = BAD_INDEX; - return 0; -} - -const char* LogEvent::GetString(size_t key, status_t* err) const { - int field = getSimpleField(key); - for (const auto& value : mValues) { - if (value.mField.getField() == field) { - if (value.mValue.getType() == STRING) { - return value.mValue.str_value.c_str(); - } else { - *err = BAD_TYPE; - return 0; - } - } - if ((size_t)value.mField.getPosAtDepth(0) > key) { - break; - } - } - - *err = BAD_INDEX; - return NULL; -} - -bool LogEvent::GetBool(size_t key, status_t* err) const { - int field = getSimpleField(key); - for (const auto& value : mValues) { - if (value.mField.getField() == field) { - if (value.mValue.getType() == INT) { - return value.mValue.int_value != 0; - } else if (value.mValue.getType() == LONG) { - return value.mValue.long_value != 0; - } else { - *err = BAD_TYPE; - return false; - } - } - if ((size_t)value.mField.getPosAtDepth(0) > key) { - break; - } - } - - *err = BAD_INDEX; - return false; -} - -float LogEvent::GetFloat(size_t key, status_t* err) const { - int field = getSimpleField(key); - for (const auto& value : mValues) { - if (value.mField.getField() == field) { - if (value.mValue.getType() == FLOAT) { - return value.mValue.float_value; - } else { - *err = BAD_TYPE; - return 0.0; - } - } - if ((size_t)value.mField.getPosAtDepth(0) > key) { - break; - } - } - - *err = BAD_INDEX; - return 0.0; -} - -std::vector LogEvent::GetStorage(size_t key, status_t* err) const { - int field = getSimpleField(key); - for (const auto& value : mValues) { - if (value.mField.getField() == field) { - if (value.mValue.getType() == STORAGE) { - return value.mValue.storage_value; - } else { - *err = BAD_TYPE; - return vector(); - } - } - if ((size_t)value.mField.getPosAtDepth(0) > key) { - break; - } - } - - *err = BAD_INDEX; - return vector(); -} - -string LogEvent::ToString() const { - string result; - result += StringPrintf("{ uid(%d) %lld %lld (%d)", mLogUid, (long long)mLogdTimestampNs, - (long long)mElapsedTimestampNs, mTagId); - for (const auto& value : mValues) { - result += - StringPrintf("%#x", value.mField.getField()) + "->" + value.mValue.toString() + " "; - } - result += " }"; - return result; -} - -void LogEvent::ToProto(ProtoOutputStream& protoOutput) const { - writeFieldValueTreeToStream(mTagId, getValues(), &protoOutput); -} - -bool LogEvent::hasAttributionChain(std::pair* indexRange) const { - if (mAttributionChainStartIndex == -1 || mAttributionChainEndIndex == -1) { - return false; - } - - if (nullptr != indexRange) { - indexRange->first = static_cast(mAttributionChainStartIndex); - indexRange->second = static_cast(mAttributionChainEndIndex); - } - - return true; -} - -void writeExperimentIdsToProto(const std::vector& experimentIds, - std::vector* protoOut) { - ProtoOutputStream proto; - for (const auto& expId : experimentIds) { - proto.write(FIELD_TYPE_INT64 | FIELD_COUNT_REPEATED | FIELD_ID_EXPERIMENT_ID, - (long long)expId); - } - - protoOut->resize(proto.size()); - size_t pos = 0; - sp reader = proto.data(); - while (reader->readBuffer() != NULL) { - size_t toRead = reader->currentToRead(); - std::memcpy(protoOut->data() + pos, reader->readBuffer(), toRead); - pos += toRead; - reader->move(toRead); - } -} - -} // namespace statsd -} // namespace os -} // namespace android diff --git a/bin/src/logd/LogEvent.h b/bin/src/logd/LogEvent.h deleted file mode 100644 index a5f24603..00000000 --- a/bin/src/logd/LogEvent.h +++ /dev/null @@ -1,331 +0,0 @@ -/* - * 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. - */ - -#pragma once - -#include "FieldValue.h" - -#include -#include - -#include -#include - -namespace android { -namespace os { -namespace statsd { - -struct InstallTrainInfo { - int64_t trainVersionCode; - std::string trainName; - int32_t status; - std::vector experimentIds; - bool requiresStaging; - bool rollbackEnabled; - bool requiresLowLatencyMonitor; -}; - -/** - * This class decodes the structured, serialized encoding of an atom into a - * vector of FieldValues. - */ -class LogEvent { -public: - /** - * \param uid user id of the logging caller - * \param pid process id of the logging caller - */ - explicit LogEvent(int32_t uid, int32_t pid); - - /** - * Parses the atomId, timestamp, and vector of values from a buffer - * containing the StatsEvent/AStatsEvent encoding of an atom. - * - * \param buf a buffer that begins at the start of the serialized atom (it - * should not include the android_log_header_t or the StatsEventTag) - * \param len size of the buffer - * - * \return success of the initialization - */ - bool parseBuffer(uint8_t* buf, size_t len); - - // Constructs a BinaryPushStateChanged LogEvent from API call. - explicit LogEvent(const std::string& trainName, int64_t trainVersionCode, bool requiresStaging, - bool rollbackEnabled, bool requiresLowLatencyMonitor, int32_t state, - const std::vector& experimentIds, int32_t userId); - - explicit LogEvent(int64_t wallClockTimestampNs, int64_t elapsedTimestampNs, - const InstallTrainInfo& installTrainInfo); - - ~LogEvent() {} - - /** - * Get the timestamp associated with this event. - */ - inline int64_t GetLogdTimestampNs() const { return mLogdTimestampNs; } - inline int64_t GetElapsedTimestampNs() const { return mElapsedTimestampNs; } - - /** - * Get the tag for this event. - */ - inline int GetTagId() const { return mTagId; } - - /** - * Get the uid of the logging client. - * Returns -1 if the uid is unknown/has not been set. - */ - inline int32_t GetUid() const { return mLogUid; } - - /** - * Get the pid of the logging client. - * Returns -1 if the pid is unknown/has not been set. - */ - inline int32_t GetPid() const { return mLogPid; } - - /** - * Get the nth value, starting at 1. - * - * Returns BAD_INDEX if the index is larger than the number of elements. - * Returns BAD_TYPE if the index is available but the data is the wrong type. - */ - int64_t GetLong(size_t key, status_t* err) const; - int GetInt(size_t key, status_t* err) const; - const char* GetString(size_t key, status_t* err) const; - bool GetBool(size_t key, status_t* err) const; - float GetFloat(size_t key, status_t* err) const; - std::vector GetStorage(size_t key, status_t* err) const; - - /** - * Return a string representation of this event. - */ - std::string ToString() const; - - /** - * Write this object to a ProtoOutputStream. - */ - void ToProto(android::util::ProtoOutputStream& out) const; - - /** - * Set elapsed timestamp if the original timestamp is missing. - */ - void setElapsedTimestampNs(int64_t timestampNs) { - mElapsedTimestampNs = timestampNs; - } - - /** - * Set the timestamp if the original logd timestamp is missing. - */ - void setLogdWallClockTimestampNs(int64_t timestampNs) { - mLogdTimestampNs = timestampNs; - } - - inline int size() const { - return mValues.size(); - } - - const std::vector& getValues() const { - return mValues; - } - - std::vector* getMutableValues() { - return &mValues; - } - - // Default value = false - inline bool shouldTruncateTimestamp() const { - return mTruncateTimestamp; - } - - // Returns the index of the uid field within the FieldValues vector if the - // uid exists. If there is no uid field, returns -1. - // - // If the index within the atom definition is desired, do the following: - // int vectorIndex = LogEvent.getUidFieldIndex(); - // if (vectorIndex != -1) { - // FieldValue& v = LogEvent.getValues()[vectorIndex]; - // int atomIndex = v.mField.getPosAtDepth(0); - // } - // Note that atomIndex is 1-indexed. - inline int getUidFieldIndex() { - return static_cast(mUidFieldIndex); - } - - // Returns whether this LogEvent has an AttributionChain. - // If it does and indexRange is not a nullptr, populate indexRange with the start and end index - // of the AttributionChain within mValues. - bool hasAttributionChain(std::pair* indexRange = nullptr) const; - - // Returns the index of the exclusive state field within the FieldValues vector if - // an exclusive state exists. If there is no exclusive state field, returns -1. - // - // If the index within the atom definition is desired, do the following: - // int vectorIndex = LogEvent.getExclusiveStateFieldIndex(); - // if (vectorIndex != -1) { - // FieldValue& v = LogEvent.getValues()[vectorIndex]; - // int atomIndex = v.mField.getPosAtDepth(0); - // } - // Note that atomIndex is 1-indexed. - inline int getExclusiveStateFieldIndex() const { - return static_cast(mExclusiveStateFieldIndex); - } - - // If a reset state is not sent in the StatsEvent, returns -1. Note that a - // reset state is sent if and only if a reset should be triggered. - inline int getResetState() const { - return mResetState; - } - - inline LogEvent makeCopy() { - return LogEvent(*this); - } - - template - status_t updateValue(size_t key, T& value, Type type) { - int field = getSimpleField(key); - for (auto& fieldValue : mValues) { - if (fieldValue.mField.getField() == field) { - if (fieldValue.mValue.getType() == type) { - fieldValue.mValue = Value(value); - return OK; - } else { - return BAD_TYPE; - } - } - } - return BAD_INDEX; - } - - bool isValid() const { - return mValid; - } - -private: - /** - * Only use this if copy is absolutely needed. - */ - LogEvent(const LogEvent&) = default; - - void parseInt32(int32_t* pos, int32_t depth, bool* last, uint8_t numAnnotations); - void parseInt64(int32_t* pos, int32_t depth, bool* last, uint8_t numAnnotations); - void parseString(int32_t* pos, int32_t depth, bool* last, uint8_t numAnnotations); - void parseFloat(int32_t* pos, int32_t depth, bool* last, uint8_t numAnnotations); - void parseBool(int32_t* pos, int32_t depth, bool* last, uint8_t numAnnotations); - void parseByteArray(int32_t* pos, int32_t depth, bool* last, uint8_t numAnnotations); - void parseKeyValuePairs(int32_t* pos, int32_t depth, bool* last, uint8_t numAnnotations); - void parseAttributionChain(int32_t* pos, int32_t depth, bool* last, uint8_t numAnnotations); - - void parseAnnotations(uint8_t numAnnotations, int firstUidInChainIndex = -1); - void parseIsUidAnnotation(uint8_t annotationType); - void parseTruncateTimestampAnnotation(uint8_t annotationType); - void parsePrimaryFieldAnnotation(uint8_t annotationType); - void parsePrimaryFieldFirstUidAnnotation(uint8_t annotationType, int firstUidInChainIndex); - void parseExclusiveStateAnnotation(uint8_t annotationType); - void parseTriggerStateResetAnnotation(uint8_t annotationType); - void parseStateNestedAnnotation(uint8_t annotationType); - bool checkPreviousValueType(Type expected); - - /** - * The below two variables are only valid during the execution of - * parseBuffer. There are no guarantees about the state of these variables - * before/after. - */ - uint8_t* mBuf; - uint32_t mRemainingLen; // number of valid bytes left in the buffer being parsed - - bool mValid = true; // stores whether the event we received from the socket is valid - - /** - * Side-effects: - * If there is enough space in buffer to read value of type T - * - move mBuf past the value that was just read - * - decrement mRemainingLen by size of T - * Else - * - set mValid to false - */ - template - T readNextValue() { - T value; - if (mRemainingLen < sizeof(T)) { - mValid = false; - value = 0; // all primitive types can successfully cast 0 - } else { - // When alignof(T) == 1, hopefully the compiler can optimize away - // this conditional as always true. - if ((reinterpret_cast(mBuf) % alignof(T)) == 0) { - // We're properly aligned, and can safely make this assignment. - value = *((T*)mBuf); - } else { - // We need to use memcpy. It's slower, but safe. - memcpy(&value, mBuf, sizeof(T)); - } - mBuf += sizeof(T); - mRemainingLen -= sizeof(T); - } - return value; - } - - template - void addToValues(int32_t* pos, int32_t depth, T& value, bool* last) { - Field f = Field(mTagId, pos, depth); - // do not decorate last position at depth 0 - for (int i = 1; i < depth; i++) { - if (last[i]) f.decorateLastPos(i); - } - - Value v = Value(value); - mValues.push_back(FieldValue(f, v)); - } - - uint8_t getTypeId(uint8_t typeInfo); - uint8_t getNumAnnotations(uint8_t typeInfo); - - // The items are naturally sorted in DFS order as we read them. this allows us to do fast - // matching. - std::vector mValues; - - // The timestamp set by the logd. - int64_t mLogdTimestampNs; - - // The elapsed timestamp set by statsd log writer. - int64_t mElapsedTimestampNs; - - // The atom tag of the event (defaults to 0 if client does not - // appropriately set the atom id). - int mTagId = 0; - - // The uid of the logging client (defaults to -1). - int32_t mLogUid = -1; - - // The pid of the logging client (defaults to -1). - int32_t mLogPid = -1; - - // Annotations - bool mTruncateTimestamp = false; - int mResetState = -1; - - // Indexes within the FieldValue vector can be stored in 7 bits because - // that's the assumption enforced by the encoding used in FieldValue. - int8_t mUidFieldIndex = -1; - int8_t mAttributionChainStartIndex = -1; - int8_t mAttributionChainEndIndex = -1; - int8_t mExclusiveStateFieldIndex = -1; -}; - -void writeExperimentIdsToProto(const std::vector& experimentIds, std::vector* protoOut); - -} // namespace statsd -} // namespace os -} // namespace android diff --git a/bin/src/logd/LogEventQueue.cpp b/bin/src/logd/LogEventQueue.cpp deleted file mode 100644 index 146464bb..00000000 --- a/bin/src/logd/LogEventQueue.cpp +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright (C) 2019 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. - */ - -#define DEBUG false // STOPSHIP if true -#include "Log.h" - -#include "LogEventQueue.h" - -namespace android { -namespace os { -namespace statsd { - -using std::unique_lock; -using std::unique_ptr; - -unique_ptr LogEventQueue::waitPop() { - std::unique_lock lock(mMutex); - - if (mQueue.empty()) { - mCondition.wait(lock, [this] { return !this->mQueue.empty(); }); - } - - unique_ptr item = std::move(mQueue.front()); - mQueue.pop(); - - return item; -} - -bool LogEventQueue::push(unique_ptr item, int64_t* oldestTimestampNs) { - bool success; - { - std::unique_lock lock(mMutex); - if (mQueue.size() < mQueueLimit) { - mQueue.push(std::move(item)); - success = true; - } else { - // safe operation as queue must not be empty. - *oldestTimestampNs = mQueue.front()->GetElapsedTimestampNs(); - success = false; - } - } - - mCondition.notify_one(); - return success; -} - -} // namespace statsd -} // namespace os -} // namespace android diff --git a/bin/src/logd/LogEventQueue.h b/bin/src/logd/LogEventQueue.h deleted file mode 100644 index 9dda3d24..00000000 --- a/bin/src/logd/LogEventQueue.h +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright (C) 2019 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. - */ - -#pragma once - -#include "LogEvent.h" - -#include -#include -#include - -namespace android { -namespace os { -namespace statsd { - -/** - * A zero copy thread safe queue buffer for producing and consuming LogEvent. - */ -class LogEventQueue { -public: - explicit LogEventQueue(size_t maxSize) : mQueueLimit(maxSize){}; - - /** - * Blocking read one event from the queue. - */ - std::unique_ptr waitPop(); - - /** - * Puts a LogEvent ptr to the end of the queue. - * Returns false on failure when the queue is full, and output the oldest event timestamp - * in the queue. - */ - bool push(std::unique_ptr event, int64_t* oldestTimestampNs); - -private: - const size_t mQueueLimit; - std::condition_variable mCondition; - std::mutex mMutex; - std::queue> mQueue; -}; - -} // namespace statsd -} // namespace os -} // namespace android diff --git a/bin/src/main.cpp b/bin/src/main.cpp deleted file mode 100644 index 03b178a9..00000000 --- a/bin/src/main.cpp +++ /dev/null @@ -1,113 +0,0 @@ -/* - * 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. - */ - -#define DEBUG false // STOPSHIP if true -#include "Log.h" - -#include "StatsService.h" -#include "socket/StatsSocketListener.h" - -#include -#include -#include -#include - -#include -#include -#include -#include - -using namespace android; -using namespace android::os::statsd; -using ::ndk::SharedRefBase; -using std::shared_ptr; -using std::make_shared; - -shared_ptr gStatsService = nullptr; -sp gSocketListener = nullptr; - -void signalHandler(int sig) { - if (sig == SIGPIPE) { - // ShellSubscriber uses SIGPIPE as a signal to detect the end of the - // client process. Don't prematurely exit(1) here. Instead, ignore the - // signal and allow the write call to return EPIPE. - ALOGI("statsd received SIGPIPE. Ignoring signal."); - return; - } - - if (gSocketListener != nullptr) gSocketListener->stopListener(); - if (gStatsService != nullptr) gStatsService->Terminate(); - ALOGW("statsd terminated on receiving signal %d.", sig); - exit(1); -} - -void registerSignalHandlers() -{ - struct sigaction sa; - sigemptyset(&sa.sa_mask); - sa.sa_flags = 0; - sa.sa_handler = signalHandler; - sigaction(SIGPIPE, &sa, nullptr); - sigaction(SIGHUP, &sa, nullptr); - sigaction(SIGINT, &sa, nullptr); - sigaction(SIGQUIT, &sa, nullptr); - sigaction(SIGTERM, &sa, nullptr); -} - -int main(int /*argc*/, char** /*argv*/) { - // Set up the looper - sp looper(Looper::prepare(0 /* opts */)); - - // Set up the binder - ABinderProcess_setThreadPoolMaxThreadCount(9); - ABinderProcess_startThreadPool(); - - std::shared_ptr eventQueue = - std::make_shared(4000 /*buffer limit. Buffer is NOT pre-allocated*/); - - // Create the service - gStatsService = SharedRefBase::make(looper, eventQueue); - // TODO(b/149582373): Set DUMP_FLAG_PROTO once libbinder_ndk supports - // setting dumpsys priorities. - binder_status_t status = AServiceManager_addService(gStatsService->asBinder().get(), "stats"); - if (status != STATUS_OK) { - ALOGE("Failed to add service as AIDL service"); - return -1; - } - - registerSignalHandlers(); - - gStatsService->sayHiToStatsCompanion(); - - gStatsService->Startup(); - - gSocketListener = new StatsSocketListener(eventQueue); - - ALOGI("Statsd starts to listen to socket."); - // Backlog and /proc/sys/net/unix/max_dgram_qlen set to large value - if (gSocketListener->startListener(600)) { - exit(1); - } - - // Loop forever -- the reports run on this thread in a handler, and the - // binder calls remain responsive in their pool of one thread. - while (true) { - looper->pollAll(-1 /* timeoutMillis */); - } - ALOGW("statsd escaped from its loop."); - - return 1; -} diff --git a/bin/src/matchers/AtomMatchingTracker.h b/bin/src/matchers/AtomMatchingTracker.h deleted file mode 100644 index 0155195a..00000000 --- a/bin/src/matchers/AtomMatchingTracker.h +++ /dev/null @@ -1,118 +0,0 @@ -/* - * 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. - */ - -#ifndef ATOM_MATCHING_TRACKER_H -#define ATOM_MATCHING_TRACKER_H - -#include "packages/modules/StatsD/bin/src/statsd_config.pb.h" -#include "logd/LogEvent.h" -#include "matchers/matcher_util.h" - -#include - -#include -#include -#include - -namespace android { -namespace os { -namespace statsd { - -class AtomMatchingTracker : public virtual RefBase { -public: - AtomMatchingTracker(const int64_t& id, const int index, const uint64_t protoHash) - : mId(id), mIndex(index), mInitialized(false), mProtoHash(protoHash){}; - - virtual ~AtomMatchingTracker(){}; - - // Initialize this AtomMatchingTracker. - // allAtomMatchers: the list of the AtomMatcher proto config. This is needed because we don't - // store the proto object in memory. We only need it during initilization. - // allAtomMatchingTrackers: the list of the AtomMatchingTracker objects. It's a one-to-one - // mapping with allAtomMatchers. This is needed because the - // initialization is done recursively for - // CombinationAtomMatchingTrackers using DFS. - // stack: a bit map to record which matcher has been visited on the stack. This is for detecting - // circle dependency. - virtual bool init(const std::vector& allAtomMatchers, - const std::vector>& allAtomMatchingTrackers, - const std::unordered_map& matcherMap, - std::vector& stack) = 0; - - // Update appropriate state on config updates. Primarily, all indices need to be updated. - // This matcher and all of its children are guaranteed to be preserved across the update. - // matcher: the AtomMatcher proto from the config. - // index: the index of this matcher in mAllAtomMatchingTrackers. - // atomMatchingTrackerMap: map from matcher id to index in mAllAtomMatchingTrackers - virtual bool onConfigUpdated( - const AtomMatcher& matcher, const int index, - const std::unordered_map& atomMatchingTrackerMap) = 0; - - // Called when a log event comes. - // event: the log event. - // allAtomMatchingTrackers: the list of all AtomMatchingTrackers. This is needed because the log - // processing is done recursively. - // matcherResults: The cached results for all matchers for this event. Parent matchers can - // directly access the children's matching results if they have been evaluated. - // Otherwise, call children matchers' onLogEvent. - virtual void onLogEvent(const LogEvent& event, - const std::vector>& allAtomMatchingTrackers, - std::vector& matcherResults) = 0; - - // Get the tagIds that this matcher cares about. The combined collection is stored - // in MetricMananger, so that we can pass any LogEvents that are not interest of us. It uses - // some memory but hopefully it can save us much CPU time when there is flood of events. - virtual const std::set& getAtomIds() const { - return mAtomIds; - } - - int64_t getId() const { - return mId; - } - - uint64_t getProtoHash() const { - return mProtoHash; - } - -protected: - // Name of this matching. We don't really need the name, but it makes log message easy to debug. - const int64_t mId; - - // Index of this AtomMatchingTracker in MetricsManager's container. - int mIndex; - - // Whether this AtomMatchingTracker has been properly initialized. - bool mInitialized; - - // The collection of the event tag ids that this AtomMatchingTracker cares. So we can quickly - // return kNotMatched when we receive an event with an id not in the list. This is especially - // useful when we have a complex CombinationAtomMatchingTracker. - std::set mAtomIds; - - // Hash of the AtomMatcher's proto bytes from StatsdConfig. - // Used to determine if the definition of this matcher has changed across a config update. - const uint64_t mProtoHash; - - FRIEND_TEST(MetricsManagerTest, TestCreateAtomMatchingTrackerSimple); - FRIEND_TEST(MetricsManagerTest, TestCreateAtomMatchingTrackerCombination); - FRIEND_TEST(ConfigUpdateTest, TestUpdateMatchers); -}; - -} // namespace statsd -} // namespace os -} // namespace android - -#endif // ATOM_MATCHING_TRACKER_H diff --git a/bin/src/matchers/CombinationAtomMatchingTracker.cpp b/bin/src/matchers/CombinationAtomMatchingTracker.cpp deleted file mode 100644 index 45685ce5..00000000 --- a/bin/src/matchers/CombinationAtomMatchingTracker.cpp +++ /dev/null @@ -1,140 +0,0 @@ -/* - * 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 "Log.h" - -#include "CombinationAtomMatchingTracker.h" - -#include "matchers/matcher_util.h" - -namespace android { -namespace os { -namespace statsd { - -using std::set; -using std::unordered_map; -using std::vector; - -CombinationAtomMatchingTracker::CombinationAtomMatchingTracker(const int64_t& id, const int index, - const uint64_t protoHash) - : AtomMatchingTracker(id, index, protoHash) { -} - -CombinationAtomMatchingTracker::~CombinationAtomMatchingTracker() { -} - -bool CombinationAtomMatchingTracker::init( - const vector& allAtomMatchers, - const vector>& allAtomMatchingTrackers, - const unordered_map& matcherMap, vector& stack) { - if (mInitialized) { - return true; - } - - // mark this node as visited in the recursion stack. - stack[mIndex] = true; - - AtomMatcher_Combination matcher = allAtomMatchers[mIndex].combination(); - - // LogicalOperation is missing in the config - if (!matcher.has_operation()) { - return false; - } - - mLogicalOperation = matcher.operation(); - - if (mLogicalOperation == LogicalOperation::NOT && matcher.matcher_size() != 1) { - return false; - } - - for (const auto& child : matcher.matcher()) { - auto pair = matcherMap.find(child); - if (pair == matcherMap.end()) { - ALOGW("Matcher %lld not found in the config", (long long)child); - return false; - } - - int childIndex = pair->second; - - // if the child is a visited node in the recursion -> circle detected. - if (stack[childIndex]) { - ALOGE("Circle detected in matcher config"); - return false; - } - - if (!allAtomMatchingTrackers[childIndex]->init(allAtomMatchers, allAtomMatchingTrackers, - matcherMap, stack)) { - ALOGW("child matcher init failed %lld", (long long)child); - return false; - } - - mChildren.push_back(childIndex); - - const set& childTagIds = allAtomMatchingTrackers[childIndex]->getAtomIds(); - mAtomIds.insert(childTagIds.begin(), childTagIds.end()); - } - - mInitialized = true; - // unmark this node in the recursion stack. - stack[mIndex] = false; - return true; -} - -bool CombinationAtomMatchingTracker::onConfigUpdated( - const AtomMatcher& matcher, const int index, - const unordered_map& atomMatchingTrackerMap) { - mIndex = index; - mChildren.clear(); - AtomMatcher_Combination combinationMatcher = matcher.combination(); - for (const int64_t child : combinationMatcher.matcher()) { - const auto& pair = atomMatchingTrackerMap.find(child); - if (pair == atomMatchingTrackerMap.end()) { - ALOGW("Matcher %lld not found in the config", (long long)child); - return false; - } - mChildren.push_back(pair->second); - } - return true; -} - -void CombinationAtomMatchingTracker::onLogEvent( - const LogEvent& event, const vector>& allAtomMatchingTrackers, - vector& matcherResults) { - // this event has been processed. - if (matcherResults[mIndex] != MatchingState::kNotComputed) { - return; - } - - if (mAtomIds.find(event.GetTagId()) == mAtomIds.end()) { - matcherResults[mIndex] = MatchingState::kNotMatched; - return; - } - - // evaluate children matchers if they haven't been evaluated. - for (const int childIndex : mChildren) { - if (matcherResults[childIndex] == MatchingState::kNotComputed) { - const sp& child = allAtomMatchingTrackers[childIndex]; - child->onLogEvent(event, allAtomMatchingTrackers, matcherResults); - } - } - - bool matched = combinationMatch(mChildren, mLogicalOperation, matcherResults); - matcherResults[mIndex] = matched ? MatchingState::kMatched : MatchingState::kNotMatched; -} - -} // namespace statsd -} // namespace os -} // namespace android diff --git a/bin/src/matchers/CombinationAtomMatchingTracker.h b/bin/src/matchers/CombinationAtomMatchingTracker.h deleted file mode 100644 index d6e8f2c7..00000000 --- a/bin/src/matchers/CombinationAtomMatchingTracker.h +++ /dev/null @@ -1,58 +0,0 @@ -/* - * 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. - */ -#ifndef COMBINATION_ATOM_MATCHING_TRACKER_H -#define COMBINATION_ATOM_MATCHING_TRACKER_H - -#include -#include - -#include "AtomMatchingTracker.h" -#include "packages/modules/StatsD/bin/src/statsd_config.pb.h" - -namespace android { -namespace os { -namespace statsd { - -// Represents a AtomMatcher_Combination in the StatsdConfig. -class CombinationAtomMatchingTracker : public AtomMatchingTracker { -public: - CombinationAtomMatchingTracker(const int64_t& id, const int index, const uint64_t protoHash); - - bool init(const std::vector& allAtomMatchers, - const std::vector>& allAtomMatchingTrackers, - const std::unordered_map& matcherMap, std::vector& stack); - - bool onConfigUpdated(const AtomMatcher& matcher, const int index, - const std::unordered_map& atomMatchingTrackerMap) override; - - ~CombinationAtomMatchingTracker(); - - void onLogEvent(const LogEvent& event, - const std::vector>& allAtomMatchingTrackers, - std::vector& matcherResults) override; - -private: - LogicalOperation mLogicalOperation; - - std::vector mChildren; - - FRIEND_TEST(ConfigUpdateTest, TestUpdateMatchers); -}; - -} // namespace statsd -} // namespace os -} // namespace android -#endif // COMBINATION_ATOM_MATCHING_TRACKER_H diff --git a/bin/src/matchers/EventMatcherWizard.cpp b/bin/src/matchers/EventMatcherWizard.cpp deleted file mode 100644 index 025c9a87..00000000 --- a/bin/src/matchers/EventMatcherWizard.cpp +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright (C) 2018 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 "EventMatcherWizard.h" - -namespace android { -namespace os { -namespace statsd { - -using std::vector; - -MatchingState EventMatcherWizard::matchLogEvent(const LogEvent& event, int matcher_index) { - if (matcher_index < 0 || matcher_index >= (int)mAllEventMatchers.size()) { - return MatchingState::kNotComputed; - } - vector matcherCache(mAllEventMatchers.size(), MatchingState::kNotComputed); - mAllEventMatchers[matcher_index]->onLogEvent(event, mAllEventMatchers, matcherCache); - return matcherCache[matcher_index]; -} - -} // namespace statsd -} // namespace os -} // namespace android \ No newline at end of file diff --git a/bin/src/matchers/EventMatcherWizard.h b/bin/src/matchers/EventMatcherWizard.h deleted file mode 100644 index 5d780f24..00000000 --- a/bin/src/matchers/EventMatcherWizard.h +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright (C) 2018 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. - */ - -#pragma once - -#include "AtomMatchingTracker.h" - -namespace android { -namespace os { -namespace statsd { - -class EventMatcherWizard : public virtual android::RefBase { -public: - EventMatcherWizard(){}; // for testing - EventMatcherWizard(const std::vector>& eventTrackers) - : mAllEventMatchers(eventTrackers){}; - - virtual ~EventMatcherWizard(){}; - - MatchingState matchLogEvent(const LogEvent& event, int matcher_index); - -private: - std::vector> mAllEventMatchers; -}; - -} // namespace statsd -} // namespace os -} // namespace android diff --git a/bin/src/matchers/SimpleAtomMatchingTracker.cpp b/bin/src/matchers/SimpleAtomMatchingTracker.cpp deleted file mode 100644 index 423da5bd..00000000 --- a/bin/src/matchers/SimpleAtomMatchingTracker.cpp +++ /dev/null @@ -1,81 +0,0 @@ -/* - * 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. - */ - -#define DEBUG false // STOPSHIP if true -#include "Log.h" - -#include "SimpleAtomMatchingTracker.h" - -namespace android { -namespace os { -namespace statsd { - -using std::unordered_map; -using std::vector; - -SimpleAtomMatchingTracker::SimpleAtomMatchingTracker(const int64_t& id, const int index, - const uint64_t protoHash, - const SimpleAtomMatcher& matcher, - const sp& uidMap) - : AtomMatchingTracker(id, index, protoHash), mMatcher(matcher), mUidMap(uidMap) { - if (!matcher.has_atom_id()) { - mInitialized = false; - } else { - mAtomIds.insert(matcher.atom_id()); - mInitialized = true; - } -} - -SimpleAtomMatchingTracker::~SimpleAtomMatchingTracker() { -} - -bool SimpleAtomMatchingTracker::init(const vector& allAtomMatchers, - const vector>& allAtomMatchingTrackers, - const unordered_map& matcherMap, - vector& stack) { - // no need to do anything. - return mInitialized; -} - -bool SimpleAtomMatchingTracker::onConfigUpdated( - const AtomMatcher& matcher, const int index, - const unordered_map& atomMatchingTrackerMap) { - mIndex = index; - // Do not need to update mMatcher since the matcher must be identical across the update. - return mInitialized; -} - -void SimpleAtomMatchingTracker::onLogEvent( - const LogEvent& event, const vector>& allAtomMatchingTrackers, - vector& matcherResults) { - if (matcherResults[mIndex] != MatchingState::kNotComputed) { - VLOG("Matcher %lld already evaluated ", (long long)mId); - return; - } - - if (mAtomIds.find(event.GetTagId()) == mAtomIds.end()) { - matcherResults[mIndex] = MatchingState::kNotMatched; - return; - } - - bool matched = matchesSimple(mUidMap, mMatcher, event); - matcherResults[mIndex] = matched ? MatchingState::kMatched : MatchingState::kNotMatched; - VLOG("Stats SimpleAtomMatcher %lld matched? %d", (long long)mId, matched); -} - -} // namespace statsd -} // namespace os -} // namespace android diff --git a/bin/src/matchers/SimpleAtomMatchingTracker.h b/bin/src/matchers/SimpleAtomMatchingTracker.h deleted file mode 100644 index a76a4677..00000000 --- a/bin/src/matchers/SimpleAtomMatchingTracker.h +++ /dev/null @@ -1,58 +0,0 @@ -/* - * 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. - */ - -#ifndef SIMPLE_ATOM_MATCHING_TRACKER_H -#define SIMPLE_ATOM_MATCHING_TRACKER_H - -#include -#include - -#include "AtomMatchingTracker.h" -#include "packages/modules/StatsD/bin/src/statsd_config.pb.h" -#include "packages/UidMap.h" - -namespace android { -namespace os { -namespace statsd { - -class SimpleAtomMatchingTracker : public AtomMatchingTracker { -public: - SimpleAtomMatchingTracker(const int64_t& id, const int index, const uint64_t protoHash, - const SimpleAtomMatcher& matcher, const sp& uidMap); - - ~SimpleAtomMatchingTracker(); - - bool init(const std::vector& allAtomMatchers, - const std::vector>& allAtomMatchingTrackers, - const std::unordered_map& matcherMap, - std::vector& stack) override; - - bool onConfigUpdated(const AtomMatcher& matcher, const int index, - const std::unordered_map& atomMatchingTrackerMap) override; - - void onLogEvent(const LogEvent& event, - const std::vector>& allAtomMatchingTrackers, - std::vector& matcherResults) override; - -private: - const SimpleAtomMatcher mMatcher; - const sp mUidMap; -}; - -} // namespace statsd -} // namespace os -} // namespace android -#endif // SIMPLE_ATOM_MATCHING_TRACKER_H diff --git a/bin/src/matchers/matcher_util.cpp b/bin/src/matchers/matcher_util.cpp deleted file mode 100644 index 625eef98..00000000 --- a/bin/src/matchers/matcher_util.cpp +++ /dev/null @@ -1,373 +0,0 @@ -/* - * 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. - */ -#define DEBUG false // STOPSHIP if true -#include "Log.h" - -#include "packages/modules/StatsD/bin/src/statsd_config.pb.h" -#include "matchers/AtomMatchingTracker.h" -#include "matchers/matcher_util.h" -#include "stats_util.h" - -using std::set; -using std::string; -using std::vector; - -namespace android { -namespace os { -namespace statsd { - -bool combinationMatch(const vector& children, const LogicalOperation& operation, - const vector& matcherResults) { - bool matched; - switch (operation) { - case LogicalOperation::AND: { - matched = true; - for (const int childIndex : children) { - if (matcherResults[childIndex] != MatchingState::kMatched) { - matched = false; - break; - } - } - break; - } - case LogicalOperation::OR: { - matched = false; - for (const int childIndex : children) { - if (matcherResults[childIndex] == MatchingState::kMatched) { - matched = true; - break; - } - } - break; - } - case LogicalOperation::NOT: - matched = matcherResults[children[0]] == MatchingState::kNotMatched; - break; - case LogicalOperation::NAND: - matched = false; - for (const int childIndex : children) { - if (matcherResults[childIndex] != MatchingState::kMatched) { - matched = true; - break; - } - } - break; - case LogicalOperation::NOR: - matched = true; - for (const int childIndex : children) { - if (matcherResults[childIndex] == MatchingState::kMatched) { - matched = false; - break; - } - } - break; - case LogicalOperation::LOGICAL_OPERATION_UNSPECIFIED: - matched = false; - break; - } - return matched; -} - -bool tryMatchString(const sp& uidMap, const FieldValue& fieldValue, - const string& str_match) { - if (isAttributionUidField(fieldValue) || isUidField(fieldValue)) { - int uid = fieldValue.mValue.int_value; - auto aidIt = UidMap::sAidToUidMapping.find(str_match); - if (aidIt != UidMap::sAidToUidMapping.end()) { - return ((int)aidIt->second) == uid; - } - std::set packageNames = uidMap->getAppNamesFromUid(uid, true /* normalize*/); - return packageNames.find(str_match) != packageNames.end(); - } else if (fieldValue.mValue.getType() == STRING) { - return fieldValue.mValue.str_value == str_match; - } - return false; -} - -bool matchesSimple(const sp& uidMap, const FieldValueMatcher& matcher, - const vector& values, int start, int end, int depth) { - if (depth > 2) { - ALOGE("Depth > 3 not supported"); - return false; - } - - if (start >= end) { - return false; - } - - // Filter by entry field first - int newStart = -1; - int newEnd = end; - // because the fields are naturally sorted in the DFS order. we can safely - // break when pos is larger than the one we are searching for. - for (int i = start; i < end; i++) { - int pos = values[i].mField.getPosAtDepth(depth); - if (pos == matcher.field()) { - if (newStart == -1) { - newStart = i; - } - newEnd = i + 1; - } else if (pos > matcher.field()) { - break; - } - } - - // Now we have zoomed in to a new range - start = newStart; - end = newEnd; - - if (start == -1) { - // No such field found. - return false; - } - - vector> ranges; // the ranges are for matching ANY position - if (matcher.has_position()) { - // Repeated fields position is stored as a node in the path. - depth++; - if (depth > 2) { - return false; - } - switch (matcher.position()) { - case Position::FIRST: { - for (int i = start; i < end; i++) { - int pos = values[i].mField.getPosAtDepth(depth); - if (pos != 1) { - // Again, the log elements are stored in sorted order. so - // once the position is > 1, we break; - end = i; - break; - } - } - ranges.push_back(std::make_pair(start, end)); - break; - } - case Position::LAST: { - // move the starting index to the first LAST field at the depth. - for (int i = start; i < end; i++) { - if (values[i].mField.isLastPos(depth)) { - start = i; - break; - } - } - ranges.push_back(std::make_pair(start, end)); - break; - } - case Position::ANY: { - // ANY means all the children matchers match in any of the sub trees, it's a match - newStart = start; - newEnd = end; - // Here start is guaranteed to be a valid index. - int currentPos = values[start].mField.getPosAtDepth(depth); - // Now find all sub trees ranges. - for (int i = start; i < end; i++) { - int newPos = values[i].mField.getPosAtDepth(depth); - if (newPos != currentPos) { - ranges.push_back(std::make_pair(newStart, i)); - newStart = i; - currentPos = newPos; - } - } - ranges.push_back(std::make_pair(newStart, end)); - break; - } - case Position::ALL: - ALOGE("Not supported: field matcher with ALL position."); - break; - case Position::POSITION_UNKNOWN: - break; - } - } else { - // No position - ranges.push_back(std::make_pair(start, end)); - } - // start and end are still pointing to the matched range. - switch (matcher.value_matcher_case()) { - case FieldValueMatcher::kMatchesTuple: { - ++depth; - // If any range matches all matchers, good. - for (const auto& range : ranges) { - bool matched = true; - for (const auto& subMatcher : matcher.matches_tuple().field_value_matcher()) { - if (!matchesSimple(uidMap, subMatcher, values, range.first, range.second, - depth)) { - matched = false; - break; - } - } - if (matched) return true; - } - return false; - } - // Finally, we get to the point of real value matching. - // If the field matcher ends with ANY, then we have [start, end) range > 1. - // In the following, we should return true, when ANY of the values matches. - case FieldValueMatcher::ValueMatcherCase::kEqBool: { - for (int i = start; i < end; i++) { - if ((values[i].mValue.getType() == INT && - (values[i].mValue.int_value != 0) == matcher.eq_bool()) || - (values[i].mValue.getType() == LONG && - (values[i].mValue.long_value != 0) == matcher.eq_bool())) { - return true; - } - } - return false; - } - case FieldValueMatcher::ValueMatcherCase::kEqString: { - for (int i = start; i < end; i++) { - if (tryMatchString(uidMap, values[i], matcher.eq_string())) { - return true; - } - } - return false; - } - case FieldValueMatcher::ValueMatcherCase::kNeqAnyString: { - const auto& str_list = matcher.neq_any_string(); - for (int i = start; i < end; i++) { - bool notEqAll = true; - for (const auto& str : str_list.str_value()) { - if (tryMatchString(uidMap, values[i], str)) { - notEqAll = false; - break; - } - } - if (notEqAll) { - return true; - } - } - return false; - } - case FieldValueMatcher::ValueMatcherCase::kEqAnyString: { - const auto& str_list = matcher.eq_any_string(); - for (int i = start; i < end; i++) { - for (const auto& str : str_list.str_value()) { - if (tryMatchString(uidMap, values[i], str)) { - return true; - } - } - } - return false; - } - case FieldValueMatcher::ValueMatcherCase::kEqInt: { - for (int i = start; i < end; i++) { - if (values[i].mValue.getType() == INT && - (matcher.eq_int() == values[i].mValue.int_value)) { - return true; - } - // eq_int covers both int and long. - if (values[i].mValue.getType() == LONG && - (matcher.eq_int() == values[i].mValue.long_value)) { - return true; - } - } - return false; - } - case FieldValueMatcher::ValueMatcherCase::kLtInt: { - for (int i = start; i < end; i++) { - if (values[i].mValue.getType() == INT && - (values[i].mValue.int_value < matcher.lt_int())) { - return true; - } - // lt_int covers both int and long. - if (values[i].mValue.getType() == LONG && - (values[i].mValue.long_value < matcher.lt_int())) { - return true; - } - } - return false; - } - case FieldValueMatcher::ValueMatcherCase::kGtInt: { - for (int i = start; i < end; i++) { - if (values[i].mValue.getType() == INT && - (values[i].mValue.int_value > matcher.gt_int())) { - return true; - } - // gt_int covers both int and long. - if (values[i].mValue.getType() == LONG && - (values[i].mValue.long_value > matcher.gt_int())) { - return true; - } - } - return false; - } - case FieldValueMatcher::ValueMatcherCase::kLtFloat: { - for (int i = start; i < end; i++) { - if (values[i].mValue.getType() == FLOAT && - (values[i].mValue.float_value < matcher.lt_float())) { - return true; - } - } - return false; - } - case FieldValueMatcher::ValueMatcherCase::kGtFloat: { - for (int i = start; i < end; i++) { - if (values[i].mValue.getType() == FLOAT && - (values[i].mValue.float_value > matcher.gt_float())) { - return true; - } - } - return false; - } - case FieldValueMatcher::ValueMatcherCase::kLteInt: { - for (int i = start; i < end; i++) { - if (values[i].mValue.getType() == INT && - (values[i].mValue.int_value <= matcher.lte_int())) { - return true; - } - // lte_int covers both int and long. - if (values[i].mValue.getType() == LONG && - (values[i].mValue.long_value <= matcher.lte_int())) { - return true; - } - } - return false; - } - case FieldValueMatcher::ValueMatcherCase::kGteInt: { - for (int i = start; i < end; i++) { - if (values[i].mValue.getType() == INT && - (values[i].mValue.int_value >= matcher.gte_int())) { - return true; - } - // gte_int covers both int and long. - if (values[i].mValue.getType() == LONG && - (values[i].mValue.long_value >= matcher.gte_int())) { - return true; - } - } - return false; - } - default: - return false; - } -} - -bool matchesSimple(const sp& uidMap, const SimpleAtomMatcher& simpleMatcher, - const LogEvent& event) { - if (event.GetTagId() != simpleMatcher.atom_id()) { - return false; - } - - for (const auto& matcher : simpleMatcher.field_value_matcher()) { - if (!matchesSimple(uidMap, matcher, event.getValues(), 0, event.getValues().size(), 0)) { - return false; - } - } - return true; -} - -} // namespace statsd -} // namespace os -} // namespace android diff --git a/bin/src/matchers/matcher_util.h b/bin/src/matchers/matcher_util.h deleted file mode 100644 index fc526da4..00000000 --- a/bin/src/matchers/matcher_util.h +++ /dev/null @@ -1,44 +0,0 @@ -/* - * 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. - */ - -#pragma once - -#include "logd/LogEvent.h" - -#include -#include "packages/modules/StatsD/bin/src/statsd_config.pb.h" -#include "packages/UidMap.h" -#include "stats_util.h" - -namespace android { -namespace os { -namespace statsd { - -enum MatchingState { - kNotComputed = -1, - kNotMatched = 0, - kMatched = 1, -}; - -bool combinationMatch(const std::vector& children, const LogicalOperation& operation, - const std::vector& matcherResults); - -bool matchesSimple(const sp& uidMap, const SimpleAtomMatcher& simpleMatcher, - const LogEvent& wrapper); - -} // namespace statsd -} // namespace os -} // namespace android diff --git a/bin/src/metadata_util.cpp b/bin/src/metadata_util.cpp deleted file mode 100644 index 27ee59b3..00000000 --- a/bin/src/metadata_util.cpp +++ /dev/null @@ -1,122 +0,0 @@ -/* - * Copyright (C) 2020 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 "FieldValue.h" -#include "metadata_util.h" - -namespace android { -namespace os { -namespace statsd { - -using google::protobuf::RepeatedPtrField; - -void writeValueToProto(metadata::FieldValue* metadataFieldValue, const Value& value) { - std::string storage_value; - switch (value.getType()) { - case INT: - metadataFieldValue->set_value_int(value.int_value); - break; - case LONG: - metadataFieldValue->set_value_long(value.long_value); - break; - case FLOAT: - metadataFieldValue->set_value_float(value.float_value); - break; - case DOUBLE: - metadataFieldValue->set_value_double(value.double_value); - break; - case STRING: - metadataFieldValue->set_value_str(value.str_value.c_str()); - break; - case STORAGE: // byte array - storage_value = ((char*) value.storage_value.data()); - metadataFieldValue->set_value_storage(storage_value); - break; - default: - break; - } -} - -void writeMetricDimensionKeyToMetadataDimensionKey( - const MetricDimensionKey& metricKey, - metadata::MetricDimensionKey* metadataMetricKey) { - for (const FieldValue& fieldValue : metricKey.getDimensionKeyInWhat().getValues()) { - metadata::FieldValue* metadataFieldValue = metadataMetricKey->add_dimension_key_in_what(); - metadata::Field* metadataField = metadataFieldValue->mutable_field(); - metadataField->set_tag(fieldValue.mField.getTag()); - metadataField->set_field(fieldValue.mField.getField()); - writeValueToProto(metadataFieldValue, fieldValue.mValue); - } - - for (const FieldValue& fieldValue : metricKey.getStateValuesKey().getValues()) { - metadata::FieldValue* metadataFieldValue = metadataMetricKey->add_state_values_key(); - metadata::Field* metadataField = metadataFieldValue->mutable_field(); - metadataField->set_tag(fieldValue.mField.getTag()); - metadataField->set_field(fieldValue.mField.getField()); - writeValueToProto(metadataFieldValue, fieldValue.mValue); - } -} - -void writeFieldValuesFromMetadata( - const RepeatedPtrField& repeatedFieldValueList, - std::vector* fieldValues) { - for (const metadata::FieldValue& metadataFieldValue : repeatedFieldValueList) { - Field field(metadataFieldValue.field().tag(), metadataFieldValue.field().field()); - Value value; - switch (metadataFieldValue.value_case()) { - case metadata::FieldValue::ValueCase::kValueInt: - value = Value(metadataFieldValue.value_int()); - break; - case metadata::FieldValue::ValueCase::kValueLong: - value = Value(metadataFieldValue.value_long()); - break; - case metadata::FieldValue::ValueCase::kValueFloat: - value = Value(metadataFieldValue.value_float()); - break; - case metadata::FieldValue::ValueCase::kValueDouble: - value = Value(metadataFieldValue.value_double()); - break; - case metadata::FieldValue::ValueCase::kValueStr: - value = Value(metadataFieldValue.value_str()); - break; - case metadata::FieldValue::ValueCase::kValueStorage: - value = Value(metadataFieldValue.value_storage()); - break; - default: - break; - } - FieldValue fieldValue(field, value); - fieldValues->emplace_back(field, value); - } -} - -MetricDimensionKey loadMetricDimensionKeyFromProto( - const metadata::MetricDimensionKey& metricDimensionKey) { - std::vector dimKeyInWhatFieldValues; - writeFieldValuesFromMetadata(metricDimensionKey.dimension_key_in_what(), - &dimKeyInWhatFieldValues); - std::vector stateValuesFieldValues; - writeFieldValuesFromMetadata(metricDimensionKey.state_values_key(), &stateValuesFieldValues); - - HashableDimensionKey dimKeyInWhat(dimKeyInWhatFieldValues); - HashableDimensionKey stateValues(stateValuesFieldValues); - MetricDimensionKey metricKey(dimKeyInWhat, stateValues); - return metricKey; -} - -} // namespace statsd -} // namespace os -} // namespace android \ No newline at end of file diff --git a/bin/src/metadata_util.h b/bin/src/metadata_util.h deleted file mode 100644 index e1381fa3..00000000 --- a/bin/src/metadata_util.h +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright (C) 2020 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 "HashableDimensionKey.h" - -#include "packages/modules/StatsD/bin/src/statsd_metadata.pb.h" // AlertMetadata - -namespace android { -namespace os { -namespace statsd { - -void writeMetricDimensionKeyToMetadataDimensionKey(const MetricDimensionKey& metricKey, - metadata::MetricDimensionKey* metadataMetricKey); - -MetricDimensionKey loadMetricDimensionKeyFromProto( - const metadata::MetricDimensionKey& metricDimensionKey); - -} // namespace statsd -} // namespace os -} // namespace android \ No newline at end of file diff --git a/bin/src/metrics/CountMetricProducer.cpp b/bin/src/metrics/CountMetricProducer.cpp deleted file mode 100644 index a8ef54a3..00000000 --- a/bin/src/metrics/CountMetricProducer.cpp +++ /dev/null @@ -1,440 +0,0 @@ -/* - * 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. - */ - -#define DEBUG false // STOPSHIP if true -#include "Log.h" - -#include "CountMetricProducer.h" - -#include -#include -#include - -#include "guardrail/StatsdStats.h" -#include "metrics/parsing_utils/metrics_manager_util.h" -#include "stats_log_util.h" -#include "stats_util.h" - -using android::util::FIELD_COUNT_REPEATED; -using android::util::FIELD_TYPE_BOOL; -using android::util::FIELD_TYPE_FLOAT; -using android::util::FIELD_TYPE_INT32; -using android::util::FIELD_TYPE_INT64; -using android::util::FIELD_TYPE_MESSAGE; -using android::util::FIELD_TYPE_STRING; -using android::util::ProtoOutputStream; -using std::map; -using std::string; -using std::unordered_map; -using std::vector; -using std::shared_ptr; - -namespace android { -namespace os { -namespace statsd { - -// for StatsLogReport -const int FIELD_ID_ID = 1; -const int FIELD_ID_COUNT_METRICS = 5; -const int FIELD_ID_TIME_BASE = 9; -const int FIELD_ID_BUCKET_SIZE = 10; -const int FIELD_ID_DIMENSION_PATH_IN_WHAT = 11; -const int FIELD_ID_IS_ACTIVE = 14; - -// for CountMetricDataWrapper -const int FIELD_ID_DATA = 1; -// for CountMetricData -const int FIELD_ID_DIMENSION_IN_WHAT = 1; -const int FIELD_ID_SLICE_BY_STATE = 6; -const int FIELD_ID_BUCKET_INFO = 3; -const int FIELD_ID_DIMENSION_LEAF_IN_WHAT = 4; -// for CountBucketInfo -const int FIELD_ID_COUNT = 3; -const int FIELD_ID_BUCKET_NUM = 4; -const int FIELD_ID_START_BUCKET_ELAPSED_MILLIS = 5; -const int FIELD_ID_END_BUCKET_ELAPSED_MILLIS = 6; - -CountMetricProducer::CountMetricProducer( - const ConfigKey& key, const CountMetric& metric, const int conditionIndex, - const vector& initialConditionCache, const sp& wizard, - const uint64_t protoHash, const int64_t timeBaseNs, const int64_t startTimeNs, - const unordered_map>& eventActivationMap, - const unordered_map>>& eventDeactivationMap, - const vector& slicedStateAtoms, - const unordered_map>& stateGroupMap) - : MetricProducer(metric.id(), key, timeBaseNs, conditionIndex, initialConditionCache, wizard, - protoHash, eventActivationMap, eventDeactivationMap, slicedStateAtoms, - stateGroupMap) { - if (metric.has_bucket()) { - mBucketSizeNs = - TimeUnitToBucketSizeInMillisGuardrailed(key.GetUid(), metric.bucket()) * 1000000; - } else { - mBucketSizeNs = LLONG_MAX; - } - - if (metric.has_dimensions_in_what()) { - translateFieldMatcher(metric.dimensions_in_what(), &mDimensionsInWhat); - mContainANYPositionInDimensionsInWhat = HasPositionANY(metric.dimensions_in_what()); - } - - mSliceByPositionALL = HasPositionALL(metric.dimensions_in_what()); - - if (metric.links().size() > 0) { - for (const auto& link : metric.links()) { - Metric2Condition mc; - mc.conditionId = link.condition(); - translateFieldMatcher(link.fields_in_what(), &mc.metricFields); - translateFieldMatcher(link.fields_in_condition(), &mc.conditionFields); - mMetric2ConditionLinks.push_back(mc); - } - mConditionSliced = true; - } - - for (const auto& stateLink : metric.state_link()) { - Metric2State ms; - ms.stateAtomId = stateLink.state_atom_id(); - translateFieldMatcher(stateLink.fields_in_what(), &ms.metricFields); - translateFieldMatcher(stateLink.fields_in_state(), &ms.stateFields); - mMetric2StateLinks.push_back(ms); - } - - flushIfNeededLocked(startTimeNs); - // Adjust start for partial bucket - mCurrentBucketStartTimeNs = startTimeNs; - - VLOG("metric %lld created. bucket size %lld start_time: %lld", (long long)metric.id(), - (long long)mBucketSizeNs, (long long)mTimeBaseNs); -} - -CountMetricProducer::~CountMetricProducer() { - VLOG("~CountMetricProducer() called"); -} - -bool CountMetricProducer::onConfigUpdatedLocked( - const StatsdConfig& config, const int configIndex, const int metricIndex, - const vector>& allAtomMatchingTrackers, - const unordered_map& oldAtomMatchingTrackerMap, - const unordered_map& newAtomMatchingTrackerMap, - const sp& matcherWizard, - const vector>& allConditionTrackers, - const unordered_map& conditionTrackerMap, const sp& wizard, - const unordered_map& metricToActivationMap, - unordered_map>& trackerToMetricMap, - unordered_map>& conditionToMetricMap, - unordered_map>& activationAtomTrackerToMetricMap, - unordered_map>& deactivationAtomTrackerToMetricMap, - vector& metricsWithActivation) { - if (!MetricProducer::onConfigUpdatedLocked( - config, configIndex, metricIndex, allAtomMatchingTrackers, - oldAtomMatchingTrackerMap, newAtomMatchingTrackerMap, matcherWizard, - allConditionTrackers, conditionTrackerMap, wizard, metricToActivationMap, - trackerToMetricMap, conditionToMetricMap, activationAtomTrackerToMetricMap, - deactivationAtomTrackerToMetricMap, metricsWithActivation)) { - return false; - } - - const CountMetric& metric = config.count_metric(configIndex); - int trackerIndex; - // Update appropriate indices, specifically mConditionIndex and MetricsManager maps. - if (!handleMetricWithAtomMatchingTrackers(metric.what(), metricIndex, false, - allAtomMatchingTrackers, newAtomMatchingTrackerMap, - trackerToMetricMap, trackerIndex)) { - return false; - } - - if (metric.has_condition() && - !handleMetricWithConditions(metric.condition(), metricIndex, conditionTrackerMap, - metric.links(), allConditionTrackers, mConditionTrackerIndex, - conditionToMetricMap)) { - return false; - } - return true; -} - -void CountMetricProducer::onStateChanged(const int64_t eventTimeNs, const int32_t atomId, - const HashableDimensionKey& primaryKey, - const FieldValue& oldState, const FieldValue& newState) { - VLOG("CountMetric %lld onStateChanged time %lld, State%d, key %s, %d -> %d", - (long long)mMetricId, (long long)eventTimeNs, atomId, primaryKey.toString().c_str(), - oldState.mValue.int_value, newState.mValue.int_value); -} - -void CountMetricProducer::dumpStatesLocked(FILE* out, bool verbose) const { - if (mCurrentSlicedCounter == nullptr || - mCurrentSlicedCounter->size() == 0) { - return; - } - - fprintf(out, "CountMetric %lld dimension size %lu\n", (long long)mMetricId, - (unsigned long)mCurrentSlicedCounter->size()); - if (verbose) { - for (const auto& it : *mCurrentSlicedCounter) { - fprintf(out, "\t(what)%s\t(state)%s %lld\n", - it.first.getDimensionKeyInWhat().toString().c_str(), - it.first.getStateValuesKey().toString().c_str(), (unsigned long long)it.second); - } - } -} - -void CountMetricProducer::onSlicedConditionMayChangeLocked(bool overallCondition, - const int64_t eventTime) { - VLOG("Metric %lld onSlicedConditionMayChange", (long long)mMetricId); -} - - -void CountMetricProducer::clearPastBucketsLocked(const int64_t dumpTimeNs) { - mPastBuckets.clear(); -} - -void CountMetricProducer::onDumpReportLocked(const int64_t dumpTimeNs, - const bool include_current_partial_bucket, - const bool erase_data, - const DumpLatency dumpLatency, - std::set *str_set, - ProtoOutputStream* protoOutput) { - if (include_current_partial_bucket) { - flushLocked(dumpTimeNs); - } else { - flushIfNeededLocked(dumpTimeNs); - } - protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_ID, (long long)mMetricId); - protoOutput->write(FIELD_TYPE_BOOL | FIELD_ID_IS_ACTIVE, isActiveLocked()); - - - if (mPastBuckets.empty()) { - return; - } - protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_TIME_BASE, (long long)mTimeBaseNs); - protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_BUCKET_SIZE, (long long)mBucketSizeNs); - - // Fills the dimension path if not slicing by ALL. - if (!mSliceByPositionALL) { - if (!mDimensionsInWhat.empty()) { - uint64_t dimenPathToken = protoOutput->start( - FIELD_TYPE_MESSAGE | FIELD_ID_DIMENSION_PATH_IN_WHAT); - writeDimensionPathToProto(mDimensionsInWhat, protoOutput); - protoOutput->end(dimenPathToken); - } - } - - uint64_t protoToken = protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_ID_COUNT_METRICS); - - for (const auto& counter : mPastBuckets) { - const MetricDimensionKey& dimensionKey = counter.first; - VLOG(" dimension key %s", dimensionKey.toString().c_str()); - - uint64_t wrapperToken = - protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_DATA); - - // First fill dimension. - if (mSliceByPositionALL) { - uint64_t dimensionToken = protoOutput->start( - FIELD_TYPE_MESSAGE | FIELD_ID_DIMENSION_IN_WHAT); - writeDimensionToProto(dimensionKey.getDimensionKeyInWhat(), str_set, protoOutput); - protoOutput->end(dimensionToken); - } else { - writeDimensionLeafNodesToProto(dimensionKey.getDimensionKeyInWhat(), - FIELD_ID_DIMENSION_LEAF_IN_WHAT, str_set, protoOutput); - } - // Then fill slice_by_state. - for (auto state : dimensionKey.getStateValuesKey().getValues()) { - uint64_t stateToken = protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | - FIELD_ID_SLICE_BY_STATE); - writeStateToProto(state, protoOutput); - protoOutput->end(stateToken); - } - // Then fill bucket_info (CountBucketInfo). - for (const auto& bucket : counter.second) { - uint64_t bucketInfoToken = protoOutput->start( - FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_BUCKET_INFO); - // Partial bucket. - if (bucket.mBucketEndNs - bucket.mBucketStartNs != mBucketSizeNs) { - protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_START_BUCKET_ELAPSED_MILLIS, - (long long)NanoToMillis(bucket.mBucketStartNs)); - protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_END_BUCKET_ELAPSED_MILLIS, - (long long)NanoToMillis(bucket.mBucketEndNs)); - } else { - protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_BUCKET_NUM, - (long long)(getBucketNumFromEndTimeNs(bucket.mBucketEndNs))); - } - protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_COUNT, (long long)bucket.mCount); - protoOutput->end(bucketInfoToken); - VLOG("\t bucket [%lld - %lld] count: %lld", (long long)bucket.mBucketStartNs, - (long long)bucket.mBucketEndNs, (long long)bucket.mCount); - } - protoOutput->end(wrapperToken); - } - - protoOutput->end(protoToken); - - if (erase_data) { - mPastBuckets.clear(); - } -} - -void CountMetricProducer::dropDataLocked(const int64_t dropTimeNs) { - flushIfNeededLocked(dropTimeNs); - StatsdStats::getInstance().noteBucketDropped(mMetricId); - mPastBuckets.clear(); -} - -void CountMetricProducer::onConditionChangedLocked(const bool conditionMet, - const int64_t eventTime) { - VLOG("Metric %lld onConditionChanged", (long long)mMetricId); - mCondition = conditionMet ? ConditionState::kTrue : ConditionState::kFalse; -} - -bool CountMetricProducer::hitGuardRailLocked(const MetricDimensionKey& newKey) { - if (mCurrentSlicedCounter->find(newKey) != mCurrentSlicedCounter->end()) { - return false; - } - // ===========GuardRail============== - // 1. Report the tuple count if the tuple count > soft limit - if (mCurrentSlicedCounter->size() > StatsdStats::kDimensionKeySizeSoftLimit - 1) { - size_t newTupleCount = mCurrentSlicedCounter->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) { - ALOGE("CountMetric %lld dropping data for dimension key %s", - (long long)mMetricId, newKey.toString().c_str()); - StatsdStats::getInstance().noteHardDimensionLimitReached(mMetricId); - return true; - } - } - - return false; -} - -void CountMetricProducer::onMatchedLogEventInternalLocked( - const size_t matcherIndex, const MetricDimensionKey& eventKey, - const ConditionKey& conditionKey, bool condition, const LogEvent& event, - const map& statePrimaryKeys) { - int64_t eventTimeNs = event.GetElapsedTimestampNs(); - flushIfNeededLocked(eventTimeNs); - - if (!condition) { - return; - } - - auto it = mCurrentSlicedCounter->find(eventKey); - if (it == mCurrentSlicedCounter->end()) { - // ===========GuardRail============== - if (hitGuardRailLocked(eventKey)) { - return; - } - // create a counter for the new key - (*mCurrentSlicedCounter)[eventKey] = 1; - } else { - // increment the existing value - auto& count = it->second; - count++; - } - for (auto& tracker : mAnomalyTrackers) { - int64_t countWholeBucket = mCurrentSlicedCounter->find(eventKey)->second; - auto prev = mCurrentFullCounters->find(eventKey); - if (prev != mCurrentFullCounters->end()) { - countWholeBucket += prev->second; - } - tracker->detectAndDeclareAnomaly(eventTimeNs, mCurrentBucketNum, mMetricId, eventKey, - countWholeBucket); - } - - VLOG("metric %lld %s->%lld", (long long)mMetricId, eventKey.toString().c_str(), - (long long)(*mCurrentSlicedCounter)[eventKey]); -} - -// When a new matched event comes in, we check if event falls into the current -// bucket. If not, flush the old counter to past buckets and initialize the new bucket. -void CountMetricProducer::flushIfNeededLocked(const int64_t& eventTimeNs) { - int64_t currentBucketEndTimeNs = getCurrentBucketEndTimeNs(); - if (eventTimeNs < currentBucketEndTimeNs) { - return; - } - - // Setup the bucket start time and number. - int64_t numBucketsForward = 1 + (eventTimeNs - currentBucketEndTimeNs) / mBucketSizeNs; - int64_t nextBucketNs = currentBucketEndTimeNs + (numBucketsForward - 1) * mBucketSizeNs; - flushCurrentBucketLocked(eventTimeNs, nextBucketNs); - - mCurrentBucketNum += numBucketsForward; - VLOG("metric %lld: new bucket start time: %lld", (long long)mMetricId, - (long long)mCurrentBucketStartTimeNs); -} - -void CountMetricProducer::flushCurrentBucketLocked(const int64_t& eventTimeNs, - const int64_t& nextBucketStartTimeNs) { - int64_t fullBucketEndTimeNs = getCurrentBucketEndTimeNs(); - CountBucket info; - info.mBucketStartNs = mCurrentBucketStartTimeNs; - if (eventTimeNs < fullBucketEndTimeNs) { - info.mBucketEndNs = eventTimeNs; - } else { - info.mBucketEndNs = fullBucketEndTimeNs; - } - for (const auto& counter : *mCurrentSlicedCounter) { - info.mCount = counter.second; - auto& bucketList = mPastBuckets[counter.first]; - bucketList.push_back(info); - VLOG("metric %lld, dump key value: %s -> %lld", (long long)mMetricId, - counter.first.toString().c_str(), - (long long)counter.second); - } - - // If we have finished a full bucket, then send this to anomaly tracker. - if (eventTimeNs > fullBucketEndTimeNs) { - // Accumulate partial buckets with current value and then send to anomaly tracker. - if (mCurrentFullCounters->size() > 0) { - for (const auto& keyValuePair : *mCurrentSlicedCounter) { - (*mCurrentFullCounters)[keyValuePair.first] += keyValuePair.second; - } - for (auto& tracker : mAnomalyTrackers) { - tracker->addPastBucket(mCurrentFullCounters, mCurrentBucketNum); - } - mCurrentFullCounters = std::make_shared(); - } else { - // Skip aggregating the partial buckets since there's no previous partial bucket. - for (auto& tracker : mAnomalyTrackers) { - tracker->addPastBucket(mCurrentSlicedCounter, mCurrentBucketNum); - } - } - } else { - // Accumulate partial bucket. - for (const auto& keyValuePair : *mCurrentSlicedCounter) { - (*mCurrentFullCounters)[keyValuePair.first] += keyValuePair.second; - } - } - - StatsdStats::getInstance().noteBucketCount(mMetricId); - // Only resets the counters, but doesn't setup the times nor numbers. - // (Do not clear since the old one is still referenced in mAnomalyTrackers). - mCurrentSlicedCounter = std::make_shared(); - mCurrentBucketStartTimeNs = nextBucketStartTimeNs; -} - -// Rough estimate of CountMetricProducer buffer stored. This number will be -// greater than actual data size as it contains each dimension of -// CountMetricData is duplicated. -size_t CountMetricProducer::byteSizeLocked() const { - size_t totalSize = 0; - for (const auto& pair : mPastBuckets) { - totalSize += pair.second.size() * kBucketSize; - } - return totalSize; -} - -} // namespace statsd -} // namespace os -} // namespace android diff --git a/bin/src/metrics/CountMetricProducer.h b/bin/src/metrics/CountMetricProducer.h deleted file mode 100644 index 4b1f1c85..00000000 --- a/bin/src/metrics/CountMetricProducer.h +++ /dev/null @@ -1,143 +0,0 @@ -/* - * 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. - */ - -#ifndef COUNT_METRIC_PRODUCER_H -#define COUNT_METRIC_PRODUCER_H - -#include -#include - -#include - -#include "MetricProducer.h" -#include "anomaly/AnomalyTracker.h" -#include "condition/ConditionTracker.h" -#include "packages/modules/StatsD/bin/src/statsd_config.pb.h" -#include "matchers/matcher_util.h" -#include "stats_util.h" - -namespace android { -namespace os { -namespace statsd { - -struct CountBucket { - int64_t mBucketStartNs; - int64_t mBucketEndNs; - int64_t mCount; -}; - -class CountMetricProducer : public MetricProducer { -public: - CountMetricProducer( - const ConfigKey& key, const CountMetric& countMetric, const int conditionIndex, - const vector& initialConditionCache, const sp& wizard, - const uint64_t protoHash, const int64_t timeBaseNs, const int64_t startTimeNs, - const std::unordered_map>& eventActivationMap = {}, - const std::unordered_map>>& - eventDeactivationMap = {}, - const vector& slicedStateAtoms = {}, - const unordered_map>& stateGroupMap = {}); - - virtual ~CountMetricProducer(); - - void onStateChanged(const int64_t eventTimeNs, const int32_t atomId, - const HashableDimensionKey& primaryKey, const FieldValue& oldState, - const FieldValue& newState) override; - - MetricType getMetricType() const override { - return METRIC_TYPE_COUNT; - } - -protected: - void onMatchedLogEventInternalLocked( - const size_t matcherIndex, const MetricDimensionKey& eventKey, - const ConditionKey& conditionKey, bool condition, const LogEvent& event, - const std::map& statePrimaryKeys) override; - -private: - - void onDumpReportLocked(const int64_t dumpTimeNs, - const bool include_current_partial_bucket, - const bool erase_data, - const DumpLatency dumpLatency, - std::set *str_set, - android::util::ProtoOutputStream* protoOutput) override; - - void clearPastBucketsLocked(const int64_t dumpTimeNs) override; - - // Internal interface to handle condition change. - void onConditionChangedLocked(const bool conditionMet, const int64_t eventTime) override; - - // Internal interface to handle sliced condition change. - void onSlicedConditionMayChangeLocked(bool overallCondition, const int64_t eventTime) override; - - // Internal function to calculate the current used bytes. - size_t byteSizeLocked() const override; - - void dumpStatesLocked(FILE* out, bool verbose) const override; - - void dropDataLocked(const int64_t dropTimeNs) override; - - // Util function to flush the old packet. - void flushIfNeededLocked(const int64_t& newEventTime) override; - - void flushCurrentBucketLocked(const int64_t& eventTimeNs, - const int64_t& nextBucketStartTimeNs) override; - - bool onConfigUpdatedLocked( - const StatsdConfig& config, const int configIndex, const int metricIndex, - const std::vector>& allAtomMatchingTrackers, - const std::unordered_map& oldAtomMatchingTrackerMap, - const std::unordered_map& newAtomMatchingTrackerMap, - const sp& matcherWizard, - const std::vector>& allConditionTrackers, - const std::unordered_map& conditionTrackerMap, - const sp& wizard, - const std::unordered_map& metricToActivationMap, - std::unordered_map>& trackerToMetricMap, - std::unordered_map>& conditionToMetricMap, - std::unordered_map>& activationAtomTrackerToMetricMap, - std::unordered_map>& deactivationAtomTrackerToMetricMap, - std::vector& metricsWithActivation) override; - - std::unordered_map> mPastBuckets; - - // The current bucket (may be a partial bucket). - std::shared_ptr mCurrentSlicedCounter = std::make_shared(); - - // The sum of previous partial buckets in the current full bucket (excluding the current - // partial bucket). This is only updated while flushing the current bucket. - std::shared_ptr mCurrentFullCounters = std::make_shared(); - - static const size_t kBucketSize = sizeof(CountBucket{}); - - bool hitGuardRailLocked(const MetricDimensionKey& newKey); - - FRIEND_TEST(CountMetricProducerTest, TestNonDimensionalEvents); - FRIEND_TEST(CountMetricProducerTest, TestEventsWithNonSlicedCondition); - FRIEND_TEST(CountMetricProducerTest, TestEventsWithSlicedCondition); - FRIEND_TEST(CountMetricProducerTest, TestAnomalyDetectionUnSliced); - FRIEND_TEST(CountMetricProducerTest, TestFirstBucket); - FRIEND_TEST(CountMetricProducerTest, TestOneWeekTimeUnit); - - FRIEND_TEST(CountMetricProducerTest_PartialBucket, TestSplitInCurrentBucket); - FRIEND_TEST(CountMetricProducerTest_PartialBucket, TestSplitInNextBucket); -}; - -} // namespace statsd -} // namespace os -} // namespace android -#endif // COUNT_METRIC_PRODUCER_H diff --git a/bin/src/metrics/DurationMetricProducer.cpp b/bin/src/metrics/DurationMetricProducer.cpp deleted file mode 100644 index 733fb388..00000000 --- a/bin/src/metrics/DurationMetricProducer.cpp +++ /dev/null @@ -1,822 +0,0 @@ -/* - * 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. - */ - -#define DEBUG false - -#include "Log.h" - -#include "DurationMetricProducer.h" - -#include -#include - -#include "guardrail/StatsdStats.h" -#include "metrics/parsing_utils/metrics_manager_util.h" -#include "stats_log_util.h" -#include "stats_util.h" - -using android::util::FIELD_COUNT_REPEATED; -using android::util::FIELD_TYPE_BOOL; -using android::util::FIELD_TYPE_FLOAT; -using android::util::FIELD_TYPE_INT32; -using android::util::FIELD_TYPE_INT64; -using android::util::FIELD_TYPE_MESSAGE; -using android::util::FIELD_TYPE_STRING; -using android::util::ProtoOutputStream; -using std::string; -using std::unordered_map; -using std::vector; -using std::shared_ptr; - -namespace android { -namespace os { -namespace statsd { - -// for StatsLogReport -const int FIELD_ID_ID = 1; -const int FIELD_ID_DURATION_METRICS = 6; -const int FIELD_ID_TIME_BASE = 9; -const int FIELD_ID_BUCKET_SIZE = 10; -const int FIELD_ID_DIMENSION_PATH_IN_WHAT = 11; -const int FIELD_ID_IS_ACTIVE = 14; -// for DurationMetricDataWrapper -const int FIELD_ID_DATA = 1; -// for DurationMetricData -const int FIELD_ID_DIMENSION_IN_WHAT = 1; -const int FIELD_ID_BUCKET_INFO = 3; -const int FIELD_ID_DIMENSION_LEAF_IN_WHAT = 4; -const int FIELD_ID_SLICE_BY_STATE = 6; -// for DurationBucketInfo -const int FIELD_ID_DURATION = 3; -const int FIELD_ID_BUCKET_NUM = 4; -const int FIELD_ID_START_BUCKET_ELAPSED_MILLIS = 5; -const int FIELD_ID_END_BUCKET_ELAPSED_MILLIS = 6; - -DurationMetricProducer::DurationMetricProducer( - const ConfigKey& key, const DurationMetric& metric, const int conditionIndex, - const vector& initialConditionCache, const int whatIndex, - const int startIndex, const int stopIndex, const int stopAllIndex, const bool nesting, - const sp& wizard, const uint64_t protoHash, - const FieldMatcher& internalDimensions, const int64_t timeBaseNs, const int64_t startTimeNs, - const unordered_map>& eventActivationMap, - const unordered_map>>& eventDeactivationMap, - const vector& slicedStateAtoms, - const unordered_map>& stateGroupMap) - : MetricProducer(metric.id(), key, timeBaseNs, conditionIndex, initialConditionCache, wizard, - protoHash, eventActivationMap, eventDeactivationMap, slicedStateAtoms, - stateGroupMap), - mAggregationType(metric.aggregation_type()), - mStartIndex(startIndex), - mStopIndex(stopIndex), - mStopAllIndex(stopAllIndex), - mNested(nesting), - mContainANYPositionInInternalDimensions(false) { - if (metric.has_bucket()) { - mBucketSizeNs = - TimeUnitToBucketSizeInMillisGuardrailed(key.GetUid(), metric.bucket()) * 1000000; - } else { - mBucketSizeNs = LLONG_MAX; - } - - if (metric.has_threshold()) { - mUploadThreshold = metric.threshold(); - } - - if (metric.has_dimensions_in_what()) { - translateFieldMatcher(metric.dimensions_in_what(), &mDimensionsInWhat); - mContainANYPositionInDimensionsInWhat = HasPositionANY(metric.dimensions_in_what()); - } - - if (internalDimensions.has_field()) { - translateFieldMatcher(internalDimensions, &mInternalDimensions); - mContainANYPositionInInternalDimensions = HasPositionANY(internalDimensions); - } - if (mContainANYPositionInInternalDimensions) { - ALOGE("Position ANY in internal dimension not supported."); - } - if (mContainANYPositionInDimensionsInWhat) { - ALOGE("Position ANY in dimension_in_what not supported."); - } - - // Dimensions in what must be subset of internal dimensions - if (!subsetDimensions(mDimensionsInWhat, mInternalDimensions)) { - ALOGE("Dimensions in what must be a subset of the internal dimensions"); - mValid = false; - } - - mSliceByPositionALL = HasPositionALL(metric.dimensions_in_what()); - - if (metric.links().size() > 0) { - for (const auto& link : metric.links()) { - Metric2Condition mc; - mc.conditionId = link.condition(); - translateFieldMatcher(link.fields_in_what(), &mc.metricFields); - translateFieldMatcher(link.fields_in_condition(), &mc.conditionFields); - if (!subsetDimensions(mc.metricFields, mInternalDimensions)) { - ALOGE(("Condition links must be a subset of the internal dimensions")); - mValid = false; - } - mMetric2ConditionLinks.push_back(mc); - } - mConditionSliced = true; - } - mUnSlicedPartCondition = ConditionState::kUnknown; - - for (const auto& stateLink : metric.state_link()) { - Metric2State ms; - ms.stateAtomId = stateLink.state_atom_id(); - translateFieldMatcher(stateLink.fields_in_what(), &ms.metricFields); - translateFieldMatcher(stateLink.fields_in_state(), &ms.stateFields); - if (!subsetDimensions(ms.metricFields, mInternalDimensions)) { - ALOGE(("State links must be a subset of the dimensions in what internal dimensions")); - mValid = false; - } - mMetric2StateLinks.push_back(ms); - } - - mUseWhatDimensionAsInternalDimension = equalDimensions(mDimensionsInWhat, mInternalDimensions); - if (mWizard != nullptr && mConditionTrackerIndex >= 0 && - mMetric2ConditionLinks.size() == 1) { - mHasLinksToAllConditionDimensionsInTracker = mWizard->equalOutputDimensions( - mConditionTrackerIndex, mMetric2ConditionLinks.begin()->conditionFields); - } - flushIfNeededLocked(startTimeNs); - // Adjust start for partial bucket - mCurrentBucketStartTimeNs = startTimeNs; - VLOG("metric %lld created. bucket size %lld start_time: %lld", (long long)metric.id(), - (long long)mBucketSizeNs, (long long)mTimeBaseNs); - - initTrueDimensions(whatIndex, startTimeNs); -} - -DurationMetricProducer::~DurationMetricProducer() { - VLOG("~DurationMetric() called"); -} - -bool DurationMetricProducer::onConfigUpdatedLocked( - const StatsdConfig& config, const int configIndex, const int metricIndex, - const vector>& allAtomMatchingTrackers, - const unordered_map& oldAtomMatchingTrackerMap, - const unordered_map& newAtomMatchingTrackerMap, - const sp& matcherWizard, - const vector>& allConditionTrackers, - const unordered_map& conditionTrackerMap, const sp& wizard, - const unordered_map& metricToActivationMap, - unordered_map>& trackerToMetricMap, - unordered_map>& conditionToMetricMap, - unordered_map>& activationAtomTrackerToMetricMap, - unordered_map>& deactivationAtomTrackerToMetricMap, - vector& metricsWithActivation) { - if (!MetricProducer::onConfigUpdatedLocked( - config, configIndex, metricIndex, allAtomMatchingTrackers, - oldAtomMatchingTrackerMap, newAtomMatchingTrackerMap, matcherWizard, - allConditionTrackers, conditionTrackerMap, wizard, metricToActivationMap, - trackerToMetricMap, conditionToMetricMap, activationAtomTrackerToMetricMap, - deactivationAtomTrackerToMetricMap, metricsWithActivation)) { - return false; - } - - const DurationMetric& metric = config.duration_metric(configIndex); - const auto& what_it = conditionTrackerMap.find(metric.what()); - if (what_it == conditionTrackerMap.end()) { - ALOGE("DurationMetric's \"what\" is not present in the config"); - return false; - } - - const Predicate& durationWhat = config.predicate(what_it->second); - if (durationWhat.contents_case() != Predicate::ContentsCase::kSimplePredicate) { - ALOGE("DurationMetric's \"what\" must be a simple condition"); - return false; - } - - const SimplePredicate& simplePredicate = durationWhat.simple_predicate(); - - // Update indices: mStartIndex, mStopIndex, mStopAllIndex, mConditionIndex and MetricsManager - // maps. - if (!handleMetricWithAtomMatchingTrackers(simplePredicate.start(), metricIndex, - metric.has_dimensions_in_what(), - allAtomMatchingTrackers, newAtomMatchingTrackerMap, - trackerToMetricMap, mStartIndex)) { - ALOGE("Duration metrics must specify a valid start event matcher"); - return false; - } - - if (simplePredicate.has_stop() && - !handleMetricWithAtomMatchingTrackers(simplePredicate.stop(), metricIndex, - metric.has_dimensions_in_what(), - allAtomMatchingTrackers, newAtomMatchingTrackerMap, - trackerToMetricMap, mStopIndex)) { - return false; - } - - if (simplePredicate.has_stop_all() && - !handleMetricWithAtomMatchingTrackers(simplePredicate.stop_all(), metricIndex, - metric.has_dimensions_in_what(), - allAtomMatchingTrackers, newAtomMatchingTrackerMap, - trackerToMetricMap, mStopAllIndex)) { - return false; - } - - if (metric.has_condition() && - !handleMetricWithConditions(metric.condition(), metricIndex, conditionTrackerMap, - metric.links(), allConditionTrackers, mConditionTrackerIndex, - conditionToMetricMap)) { - return false; - } - - for (const auto& it : mCurrentSlicedDurationTrackerMap) { - it.second->onConfigUpdated(wizard, mConditionTrackerIndex); - } - - return true; -} - -void DurationMetricProducer::initTrueDimensions(const int whatIndex, const int64_t startTimeNs) { - std::lock_guard lock(mMutex); - // Currently whatIndex will only be -1 in tests. In the future, we might want to avoid creating - // a ConditionTracker if the condition is only used in the "what" of a duration metric. In that - // scenario, -1 can also be passed. - if (whatIndex == -1) { - return; - } - const map* slicedWhatMap = mWizard->getSlicedDimensionMap(whatIndex); - for (const auto& [internalDimKey, count] : *slicedWhatMap) { - for (int i = 0; i < count; i++) { - // Fake start events. - handleMatchedLogEventValuesLocked(mStartIndex, internalDimKey.getValues(), startTimeNs); - } - } -} - -sp DurationMetricProducer::addAnomalyTracker( - const Alert& alert, const sp& anomalyAlarmMonitor, - const UpdateStatus& updateStatus, const int64_t updateTimeNs) { - std::lock_guard lock(mMutex); - if (mAggregationType == DurationMetric_AggregationType_SUM) { - if (alert.trigger_if_sum_gt() > alert.num_buckets() * mBucketSizeNs) { - ALOGW("invalid alert for SUM: threshold (%f) > possible recordable value (%d x %lld)", - alert.trigger_if_sum_gt(), alert.num_buckets(), (long long)mBucketSizeNs); - return nullptr; - } - } - sp anomalyTracker = - new DurationAnomalyTracker(alert, mConfigKey, anomalyAlarmMonitor); - // The update status is either new or replaced. - addAnomalyTrackerLocked(anomalyTracker, updateStatus, updateTimeNs); - return anomalyTracker; -} - -// Adds an AnomalyTracker that has already been created. -// Note: this gets called on config updates, and will only get called if the metric and the -// associated alert are preserved, which means the AnomalyTracker must be a DurationAnomalyTracker. -void DurationMetricProducer::addAnomalyTracker(sp& anomalyTracker, - const int64_t updateTimeNs) { - std::lock_guard lock(mMutex); - addAnomalyTrackerLocked(anomalyTracker, UpdateStatus::UPDATE_PRESERVE, updateTimeNs); -} - -void DurationMetricProducer::addAnomalyTrackerLocked(sp& anomalyTracker, - const UpdateStatus& updateStatus, - const int64_t updateTimeNs) { - mAnomalyTrackers.push_back(anomalyTracker); - for (const auto& [_, durationTracker] : mCurrentSlicedDurationTrackerMap) { - durationTracker->addAnomalyTracker(anomalyTracker, updateStatus, updateTimeNs); - } -} -void DurationMetricProducer::onStateChanged(const int64_t eventTimeNs, const int32_t atomId, - const HashableDimensionKey& primaryKey, - const FieldValue& oldState, - const FieldValue& newState) { - // Check if this metric has a StateMap. If so, map the new state value to - // the correct state group id. - FieldValue newStateCopy = newState; - mapStateValue(atomId, &newStateCopy); - - flushIfNeededLocked(eventTimeNs); - - // Log late event and extra duration. - if (eventTimeNs < mCurrentBucketStartTimeNs) { - StatsdStats::getInstance().noteLateLogEvent(mMetricId, - mCurrentBucketStartTimeNs - eventTimeNs); - } - - // Each duration tracker is mapped to a different whatKey (a set of values from the - // dimensionsInWhat fields). We notify all trackers iff the primaryKey field values from the - // state change event are a subset of the tracker's whatKey field values. - // - // Ex. For a duration metric dimensioned on uid and tag: - // DurationTracker1 whatKey = uid: 1001, tag: 1 - // DurationTracker2 whatKey = uid: 1002, tag 1 - // - // If the state change primaryKey = uid: 1001, we only notify DurationTracker1 of a state - // change. - for (auto& whatIt : mCurrentSlicedDurationTrackerMap) { - if (!containsLinkedStateValues(whatIt.first, primaryKey, mMetric2StateLinks, atomId)) { - continue; - } - whatIt.second->onStateChanged(eventTimeNs, atomId, newStateCopy); - } -} - -unique_ptr DurationMetricProducer::createDurationTracker( - const MetricDimensionKey& eventKey) const { - switch (mAggregationType) { - case DurationMetric_AggregationType_SUM: - return make_unique( - mConfigKey, mMetricId, eventKey, mWizard, mConditionTrackerIndex, mNested, - mCurrentBucketStartTimeNs, mCurrentBucketNum, mTimeBaseNs, mBucketSizeNs, - mConditionSliced, mHasLinksToAllConditionDimensionsInTracker, mAnomalyTrackers); - case DurationMetric_AggregationType_MAX_SPARSE: - return make_unique( - mConfigKey, mMetricId, eventKey, mWizard, mConditionTrackerIndex, mNested, - mCurrentBucketStartTimeNs, mCurrentBucketNum, mTimeBaseNs, mBucketSizeNs, - mConditionSliced, mHasLinksToAllConditionDimensionsInTracker, mAnomalyTrackers); - } -} - -// SlicedConditionChange optimization case 1: -// 1. If combination condition, logical operation is AND, only one sliced child predicate. -// 2. The links covers all dimension fields in the sliced child condition predicate. -void DurationMetricProducer::onSlicedConditionMayChangeLocked_opt1(bool condition, - const int64_t eventTime) { - if (mMetric2ConditionLinks.size() != 1 || - !mHasLinksToAllConditionDimensionsInTracker) { - return; - } - - bool currentUnSlicedPartCondition = true; - if (!mWizard->IsSimpleCondition(mConditionTrackerIndex)) { - ConditionState unslicedPartState = - mWizard->getUnSlicedPartConditionState(mConditionTrackerIndex); - // When the unsliced part is still false, return directly. - if (mUnSlicedPartCondition == ConditionState::kFalse && - unslicedPartState == ConditionState::kFalse) { - return; - } - mUnSlicedPartCondition = unslicedPartState; - currentUnSlicedPartCondition = mUnSlicedPartCondition > 0; - } - - auto dimensionsChangedToTrue = mWizard->getChangedToTrueDimensions(mConditionTrackerIndex); - auto dimensionsChangedToFalse = mWizard->getChangedToFalseDimensions(mConditionTrackerIndex); - - // The condition change is from the unsliced predicates. - // We need to find out the true dimensions from the sliced predicate and flip their condition - // state based on the new unsliced condition state. - if (dimensionsChangedToTrue == nullptr || dimensionsChangedToFalse == nullptr || - (dimensionsChangedToTrue->empty() && dimensionsChangedToFalse->empty())) { - const map* slicedConditionMap = - mWizard->getSlicedDimensionMap(mConditionTrackerIndex); - for (auto& whatIt : mCurrentSlicedDurationTrackerMap) { - HashableDimensionKey linkedConditionDimensionKey; - getDimensionForCondition(whatIt.first.getValues(), mMetric2ConditionLinks[0], - &linkedConditionDimensionKey); - const auto& slicedConditionIt = slicedConditionMap->find(linkedConditionDimensionKey); - if (slicedConditionIt != slicedConditionMap->end() && slicedConditionIt->second > 0) { - whatIt.second->onConditionChanged(currentUnSlicedPartCondition, eventTime); - } - } - } else { - // Handle the condition change from the sliced predicate. - if (currentUnSlicedPartCondition) { - for (auto& whatIt : mCurrentSlicedDurationTrackerMap) { - HashableDimensionKey linkedConditionDimensionKey; - getDimensionForCondition(whatIt.first.getValues(), mMetric2ConditionLinks[0], - &linkedConditionDimensionKey); - if (dimensionsChangedToTrue->find(linkedConditionDimensionKey) != - dimensionsChangedToTrue->end()) { - whatIt.second->onConditionChanged(true, eventTime); - } - if (dimensionsChangedToFalse->find(linkedConditionDimensionKey) != - dimensionsChangedToFalse->end()) { - whatIt.second->onConditionChanged(false, eventTime); - } - } - } - } -} - -void DurationMetricProducer::onSlicedConditionMayChangeInternalLocked(bool overallCondition, - const int64_t eventTimeNs) { - bool changeDimTrackable = mWizard->IsChangedDimensionTrackable(mConditionTrackerIndex); - if (changeDimTrackable && mHasLinksToAllConditionDimensionsInTracker) { - onSlicedConditionMayChangeLocked_opt1(overallCondition, eventTimeNs); - return; - } - - // Now for each of the on-going event, check if the condition has changed for them. - for (auto& whatIt : mCurrentSlicedDurationTrackerMap) { - whatIt.second->onSlicedConditionMayChange(overallCondition, eventTimeNs); - } -} - -void DurationMetricProducer::onSlicedConditionMayChangeLocked(bool overallCondition, - const int64_t eventTime) { - VLOG("Metric %lld onSlicedConditionMayChange", (long long)mMetricId); - - if (!mIsActive) { - return; - } - - // Log late event and extra duration. - if (eventTime < mCurrentBucketStartTimeNs) { - StatsdStats::getInstance().noteLateLogEvent(mMetricId, - mCurrentBucketStartTimeNs - eventTime); - } - - flushIfNeededLocked(eventTime); - - if (!mConditionSliced) { - return; - } - - onSlicedConditionMayChangeInternalLocked(overallCondition, eventTime); -} - -void DurationMetricProducer::onActiveStateChangedLocked(const int64_t& eventTimeNs) { - MetricProducer::onActiveStateChangedLocked(eventTimeNs); - - if (!mConditionSliced) { - if (ConditionState::kTrue != mCondition) { - return; - } - - // Log late event and extra duration. - if (eventTimeNs < mCurrentBucketStartTimeNs) { - StatsdStats::getInstance().noteLateLogEvent(mMetricId, - mCurrentBucketStartTimeNs - eventTimeNs); - } - - if (mIsActive) { - flushIfNeededLocked(eventTimeNs); - } - - for (auto& whatIt : mCurrentSlicedDurationTrackerMap) { - whatIt.second->onConditionChanged(mIsActive, eventTimeNs); - } - } else if (mIsActive) { - flushIfNeededLocked(eventTimeNs); - onSlicedConditionMayChangeInternalLocked(mIsActive, eventTimeNs); - } else { // mConditionSliced == true && !mIsActive - for (auto& whatIt : mCurrentSlicedDurationTrackerMap) { - whatIt.second->onConditionChanged(mIsActive, eventTimeNs); - } - } -} - -void DurationMetricProducer::onConditionChangedLocked(const bool conditionMet, - const int64_t eventTime) { - VLOG("Metric %lld onConditionChanged", (long long)mMetricId); - mCondition = conditionMet ? ConditionState::kTrue : ConditionState::kFalse; - - if (!mIsActive) { - return; - } - - // Log late event and extra duration. - if (eventTime < mCurrentBucketStartTimeNs) { - StatsdStats::getInstance().noteLateLogEvent(mMetricId, - mCurrentBucketStartTimeNs - eventTime); - } - - flushIfNeededLocked(eventTime); - for (auto& whatIt : mCurrentSlicedDurationTrackerMap) { - whatIt.second->onConditionChanged(conditionMet, eventTime); - } -} - -void DurationMetricProducer::dropDataLocked(const int64_t dropTimeNs) { - flushIfNeededLocked(dropTimeNs); - StatsdStats::getInstance().noteBucketDropped(mMetricId); - mPastBuckets.clear(); -} - -void DurationMetricProducer::clearPastBucketsLocked(const int64_t dumpTimeNs) { - flushIfNeededLocked(dumpTimeNs); - mPastBuckets.clear(); -} - -void DurationMetricProducer::onDumpReportLocked(const int64_t dumpTimeNs, - const bool include_current_partial_bucket, - const bool erase_data, - const DumpLatency dumpLatency, - std::set *str_set, - ProtoOutputStream* protoOutput) { - if (include_current_partial_bucket) { - flushLocked(dumpTimeNs); - } else { - flushIfNeededLocked(dumpTimeNs); - } - protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_ID, (long long)mMetricId); - protoOutput->write(FIELD_TYPE_BOOL | FIELD_ID_IS_ACTIVE, isActiveLocked()); - - if (mPastBuckets.empty()) { - VLOG(" Duration metric, empty return"); - return; - } - - protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_TIME_BASE, (long long)mTimeBaseNs); - protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_BUCKET_SIZE, (long long)mBucketSizeNs); - - if (!mSliceByPositionALL) { - if (!mDimensionsInWhat.empty()) { - uint64_t dimenPathToken = protoOutput->start( - FIELD_TYPE_MESSAGE | FIELD_ID_DIMENSION_PATH_IN_WHAT); - writeDimensionPathToProto(mDimensionsInWhat, protoOutput); - protoOutput->end(dimenPathToken); - } - } - - uint64_t protoToken = protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_ID_DURATION_METRICS); - - VLOG("Duration metric %lld dump report now...", (long long)mMetricId); - - for (const auto& pair : mPastBuckets) { - const MetricDimensionKey& dimensionKey = pair.first; - VLOG(" dimension key %s", dimensionKey.toString().c_str()); - - uint64_t wrapperToken = - protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_DATA); - - // First fill dimension. - if (mSliceByPositionALL) { - uint64_t dimensionToken = protoOutput->start( - FIELD_TYPE_MESSAGE | FIELD_ID_DIMENSION_IN_WHAT); - writeDimensionToProto(dimensionKey.getDimensionKeyInWhat(), str_set, protoOutput); - protoOutput->end(dimensionToken); - } else { - writeDimensionLeafNodesToProto(dimensionKey.getDimensionKeyInWhat(), - FIELD_ID_DIMENSION_LEAF_IN_WHAT, str_set, protoOutput); - } - // Then fill slice_by_state. - for (auto state : dimensionKey.getStateValuesKey().getValues()) { - uint64_t stateToken = protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | - FIELD_ID_SLICE_BY_STATE); - writeStateToProto(state, protoOutput); - protoOutput->end(stateToken); - } - // Then fill bucket_info (DurationBucketInfo). - for (const auto& bucket : pair.second) { - uint64_t bucketInfoToken = protoOutput->start( - FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_BUCKET_INFO); - if (bucket.mBucketEndNs - bucket.mBucketStartNs != mBucketSizeNs) { - protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_START_BUCKET_ELAPSED_MILLIS, - (long long)NanoToMillis(bucket.mBucketStartNs)); - protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_END_BUCKET_ELAPSED_MILLIS, - (long long)NanoToMillis(bucket.mBucketEndNs)); - } else { - protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_BUCKET_NUM, - (long long)(getBucketNumFromEndTimeNs(bucket.mBucketEndNs))); - } - protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_DURATION, (long long)bucket.mDuration); - protoOutput->end(bucketInfoToken); - VLOG("\t bucket [%lld - %lld] duration: %lld", (long long)bucket.mBucketStartNs, - (long long)bucket.mBucketEndNs, (long long)bucket.mDuration); - } - - protoOutput->end(wrapperToken); - } - - protoOutput->end(protoToken); - if (erase_data) { - mPastBuckets.clear(); - } -} - -void DurationMetricProducer::flushIfNeededLocked(const int64_t& eventTimeNs) { - int64_t currentBucketEndTimeNs = getCurrentBucketEndTimeNs(); - - if (currentBucketEndTimeNs > eventTimeNs) { - return; - } - VLOG("flushing..........."); - int numBucketsForward = 1 + (eventTimeNs - currentBucketEndTimeNs) / mBucketSizeNs; - int64_t nextBucketNs = currentBucketEndTimeNs + (numBucketsForward - 1) * mBucketSizeNs; - flushCurrentBucketLocked(eventTimeNs, nextBucketNs); - - mCurrentBucketNum += numBucketsForward; -} - -void DurationMetricProducer::flushCurrentBucketLocked(const int64_t& eventTimeNs, - const int64_t& nextBucketStartTimeNs) { - for (auto whatIt = mCurrentSlicedDurationTrackerMap.begin(); - whatIt != mCurrentSlicedDurationTrackerMap.end();) { - if (whatIt->second->flushCurrentBucket(eventTimeNs, mUploadThreshold, &mPastBuckets)) { - VLOG("erase bucket for key %s", whatIt->first.toString().c_str()); - whatIt = mCurrentSlicedDurationTrackerMap.erase(whatIt); - } else { - ++whatIt; - } - } - StatsdStats::getInstance().noteBucketCount(mMetricId); - mCurrentBucketStartTimeNs = nextBucketStartTimeNs; -} - -void DurationMetricProducer::dumpStatesLocked(FILE* out, bool verbose) const { - if (mCurrentSlicedDurationTrackerMap.size() == 0) { - return; - } - - fprintf(out, "DurationMetric %lld dimension size %lu\n", (long long)mMetricId, - (unsigned long)mCurrentSlicedDurationTrackerMap.size()); - if (verbose) { - for (const auto& whatIt : mCurrentSlicedDurationTrackerMap) { - fprintf(out, "\t(what)%s\n", whatIt.first.toString().c_str()); - whatIt.second->dumpStates(out, verbose); - } - } -} - -bool DurationMetricProducer::hitGuardRailLocked(const MetricDimensionKey& newKey) { - auto whatIt = mCurrentSlicedDurationTrackerMap.find(newKey.getDimensionKeyInWhat()); - if (whatIt == mCurrentSlicedDurationTrackerMap.end()) { - // 1. Report the tuple count if the tuple count > soft limit - 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) { - ALOGE("DurationMetric %lld dropping data for what dimension key %s", - (long long)mMetricId, newKey.getDimensionKeyInWhat().toString().c_str()); - StatsdStats::getInstance().noteHardDimensionLimitReached(mMetricId); - return true; - } - } - } - return false; -} - -void DurationMetricProducer::handleStartEvent(const MetricDimensionKey& eventKey, - const ConditionKey& conditionKeys, bool condition, - const int64_t eventTimeNs, - const vector& eventValues) { - const auto& whatKey = eventKey.getDimensionKeyInWhat(); - auto whatIt = mCurrentSlicedDurationTrackerMap.find(whatKey); - if (whatIt == mCurrentSlicedDurationTrackerMap.end()) { - if (hitGuardRailLocked(eventKey)) { - return; - } - mCurrentSlicedDurationTrackerMap[whatKey] = createDurationTracker(eventKey); - } - - auto it = mCurrentSlicedDurationTrackerMap.find(whatKey); - if (mUseWhatDimensionAsInternalDimension) { - it->second->noteStart(whatKey, condition, eventTimeNs, conditionKeys); - return; - } - - if (mInternalDimensions.empty()) { - it->second->noteStart(DEFAULT_DIMENSION_KEY, condition, eventTimeNs, conditionKeys); - } else { - HashableDimensionKey dimensionKey = DEFAULT_DIMENSION_KEY; - filterValues(mInternalDimensions, eventValues, &dimensionKey); - it->second->noteStart(dimensionKey, condition, eventTimeNs, conditionKeys); - } -} - -void DurationMetricProducer::onMatchedLogEventInternalLocked( - const size_t matcherIndex, const MetricDimensionKey& eventKey, - const ConditionKey& conditionKeys, bool condition, const LogEvent& event, - const map& statePrimaryKeys) { - ALOGW("Not used in duration tracker."); -} - -void DurationMetricProducer::onMatchedLogEventLocked(const size_t matcherIndex, - const LogEvent& event) { - handleMatchedLogEventValuesLocked(matcherIndex, event.getValues(), - event.GetElapsedTimestampNs()); -} - -void DurationMetricProducer::handleMatchedLogEventValuesLocked(const size_t matcherIndex, - const vector& values, - const int64_t eventTimeNs) { - if (eventTimeNs < mTimeBaseNs) { - return; - } - - // Log late event and extra duration. - if (eventTimeNs < mCurrentBucketStartTimeNs) { - StatsdStats::getInstance().noteLateLogEvent(mMetricId, - mCurrentBucketStartTimeNs - eventTimeNs); - } - - if (mIsActive) { - flushIfNeededLocked(eventTimeNs); - } - - // Handles Stopall events. - if ((int)matcherIndex == mStopAllIndex) { - for (auto& whatIt : mCurrentSlicedDurationTrackerMap) { - whatIt.second->noteStopAll(eventTimeNs); - } - return; - } - - HashableDimensionKey dimensionInWhat = DEFAULT_DIMENSION_KEY; - if (!mDimensionsInWhat.empty()) { - filterValues(mDimensionsInWhat, values, &dimensionInWhat); - } - - // Stores atom id to primary key pairs for each state atom that the metric is - // sliced by. - std::map statePrimaryKeys; - - // For states with primary fields, use MetricStateLinks to get the primary - // field values from the log event. These values will form a primary key - // that will be used to query StateTracker for the correct state value. - for (const auto& stateLink : mMetric2StateLinks) { - getDimensionForState(values, stateLink, &statePrimaryKeys[stateLink.stateAtomId]); - } - - // For each sliced state, query StateTracker for the state value using - // either the primary key from the previous step or the DEFAULT_DIMENSION_KEY. - // - // Expected functionality: for any case where the MetricStateLinks are - // initialized incorrectly (ex. # of state links != # of primary fields, no - // links are provided for a state with primary fields, links are provided - // in the wrong order, etc.), StateTracker will simply return kStateUnknown - // when queried using an incorrect key. - HashableDimensionKey stateValuesKey = DEFAULT_DIMENSION_KEY; - for (auto atomId : mSlicedStateAtoms) { - FieldValue value; - if (statePrimaryKeys.find(atomId) != statePrimaryKeys.end()) { - // found a primary key for this state, query using the key - queryStateValue(atomId, statePrimaryKeys[atomId], &value); - } else { - // if no MetricStateLinks exist for this state atom, - // query using the default dimension key (empty HashableDimensionKey) - queryStateValue(atomId, DEFAULT_DIMENSION_KEY, &value); - } - mapStateValue(atomId, &value); - stateValuesKey.addValue(value); - } - - // Handles Stop events. - if ((int)matcherIndex == mStopIndex) { - if (mUseWhatDimensionAsInternalDimension) { - auto whatIt = mCurrentSlicedDurationTrackerMap.find(dimensionInWhat); - if (whatIt != mCurrentSlicedDurationTrackerMap.end()) { - whatIt->second->noteStop(dimensionInWhat, eventTimeNs, false); - } - return; - } - - HashableDimensionKey internalDimensionKey = DEFAULT_DIMENSION_KEY; - if (!mInternalDimensions.empty()) { - filterValues(mInternalDimensions, values, &internalDimensionKey); - } - - auto whatIt = mCurrentSlicedDurationTrackerMap.find(dimensionInWhat); - if (whatIt != mCurrentSlicedDurationTrackerMap.end()) { - whatIt->second->noteStop(internalDimensionKey, eventTimeNs, false); - } - return; - } - - bool condition; - ConditionKey conditionKey; - if (mConditionSliced) { - for (const auto& link : mMetric2ConditionLinks) { - getDimensionForCondition(values, link, &conditionKey[link.conditionId]); - } - - auto conditionState = - mWizard->query(mConditionTrackerIndex, conditionKey, - !mHasLinksToAllConditionDimensionsInTracker); - condition = conditionState == ConditionState::kTrue; - } else { - // TODO: The unknown condition state is not handled here, we should fix it. - condition = mCondition == ConditionState::kTrue; - } - - condition = condition && mIsActive; - - handleStartEvent(MetricDimensionKey(dimensionInWhat, stateValuesKey), conditionKey, condition, - eventTimeNs, values); -} - -size_t DurationMetricProducer::byteSizeLocked() const { - size_t totalSize = 0; - for (const auto& pair : mPastBuckets) { - totalSize += pair.second.size() * kBucketSize; - } - return totalSize; -} - -} // namespace statsd -} // namespace os -} // namespace android diff --git a/bin/src/metrics/DurationMetricProducer.h b/bin/src/metrics/DurationMetricProducer.h deleted file mode 100644 index 100ea459..00000000 --- a/bin/src/metrics/DurationMetricProducer.h +++ /dev/null @@ -1,204 +0,0 @@ -/* - * 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. - */ - -#pragma once - -#include - -#include - -#include "../anomaly/DurationAnomalyTracker.h" -#include "../condition/ConditionTracker.h" -#include "../matchers/matcher_util.h" -#include "MetricProducer.h" -#include "duration_helper/DurationTracker.h" -#include "duration_helper/MaxDurationTracker.h" -#include "duration_helper/OringDurationTracker.h" -#include "packages/modules/StatsD/bin/src/statsd_config.pb.h" -#include "stats_util.h" - -using namespace std; - -namespace android { -namespace os { -namespace statsd { - -class DurationMetricProducer : public MetricProducer { -public: - DurationMetricProducer( - const ConfigKey& key, const DurationMetric& durationMetric, const int conditionIndex, - const vector& initialConditionCache, const int whatIndex, - const int startIndex, const int stopIndex, const int stopAllIndex, const bool nesting, - const sp& wizard, const uint64_t protoHash, - const FieldMatcher& internalDimensions, const int64_t timeBaseNs, - const int64_t startTimeNs, - const unordered_map>& eventActivationMap = {}, - const unordered_map>>& eventDeactivationMap = {}, - const vector& slicedStateAtoms = {}, - const unordered_map>& stateGroupMap = {}); - - virtual ~DurationMetricProducer(); - - sp addAnomalyTracker(const Alert& alert, - const sp& anomalyAlarmMonitor, - const UpdateStatus& updateStatus, - const int64_t updateTimeNs) override; - - void addAnomalyTracker(sp& anomalyTracker, const int64_t updateTimeNs) override; - - void onStateChanged(const int64_t eventTimeNs, const int32_t atomId, - const HashableDimensionKey& primaryKey, const FieldValue& oldState, - const FieldValue& newState) override; - - MetricType getMetricType() const override { - return METRIC_TYPE_DURATION; - } - -protected: - void onMatchedLogEventLocked(const size_t matcherIndex, const LogEvent& event) override; - - void onMatchedLogEventInternalLocked( - const size_t matcherIndex, const MetricDimensionKey& eventKey, - const ConditionKey& conditionKeys, bool condition, const LogEvent& event, - const std::map& statePrimaryKeys) override; - -private: - // Initializes true dimensions of the 'what' predicate. Only to be called during initialization. - void initTrueDimensions(const int whatIndex, const int64_t startTimeNs); - - void handleMatchedLogEventValuesLocked(const size_t matcherIndex, - const std::vector& values, - const int64_t eventTimeNs); - void handleStartEvent(const MetricDimensionKey& eventKey, const ConditionKey& conditionKeys, - bool condition, const int64_t eventTimeNs, - const vector& eventValues); - - void onDumpReportLocked(const int64_t dumpTimeNs, - const bool include_current_partial_bucket, - const bool erase_data, - const DumpLatency dumpLatency, - std::set *str_set, - android::util::ProtoOutputStream* protoOutput) override; - - void clearPastBucketsLocked(const int64_t dumpTimeNs) override; - - // Internal interface to handle condition change. - void onConditionChangedLocked(const bool conditionMet, const int64_t eventTime) override; - - // Internal interface to handle active state change. - void onActiveStateChangedLocked(const int64_t& eventTimeNs) override; - - // Internal interface to handle sliced condition change. - void onSlicedConditionMayChangeLocked(bool overallCondition, const int64_t eventTime) override; - - void onSlicedConditionMayChangeInternalLocked(bool overallCondition, - const int64_t eventTimeNs); - - void onSlicedConditionMayChangeLocked_opt1(bool overallCondition, const int64_t eventTime); - void onSlicedConditionMayChangeLocked_opt2(bool overallCondition, const int64_t eventTime); - - // Internal function to calculate the current used bytes. - size_t byteSizeLocked() const override; - - void dumpStatesLocked(FILE* out, bool verbose) const override; - - void dropDataLocked(const int64_t dropTimeNs) override; - - // Util function to flush the old packet. - void flushIfNeededLocked(const int64_t& eventTime); - - void flushCurrentBucketLocked(const int64_t& eventTimeNs, - const int64_t& nextBucketStartTimeNs) override; - - bool onConfigUpdatedLocked( - const StatsdConfig& config, const int configIndex, const int metricIndex, - const std::vector>& allAtomMatchingTrackers, - const std::unordered_map& oldAtomMatchingTrackerMap, - const std::unordered_map& newAtomMatchingTrackerMap, - const sp& matcherWizard, - const std::vector>& allConditionTrackers, - const std::unordered_map& conditionTrackerMap, - const sp& wizard, - const std::unordered_map& metricToActivationMap, - std::unordered_map>& trackerToMetricMap, - std::unordered_map>& conditionToMetricMap, - std::unordered_map>& activationAtomTrackerToMetricMap, - std::unordered_map>& deactivationAtomTrackerToMetricMap, - std::vector& metricsWithActivation) override; - - void addAnomalyTrackerLocked(sp& anomalyTracker, - const UpdateStatus& updateStatus, const int64_t updateTimeNs); - - const DurationMetric_AggregationType mAggregationType; - - // Index of the SimpleAtomMatcher which defines the start. - int mStartIndex; - - // Index of the SimpleAtomMatcher which defines the stop. - int mStopIndex; - - // Index of the SimpleAtomMatcher which defines the stop all for all dimensions. - int mStopAllIndex; - - // nest counting -- for the same key, stops must match the number of starts to make real stop - const bool mNested; - - // The dimension from the atom predicate. e.g., uid, wakelock name. - vector mInternalDimensions; - - bool mContainANYPositionInInternalDimensions; - - // This boolean is true iff When mInternalDimensions == mDimensionsInWhat - bool mUseWhatDimensionAsInternalDimension; - - // Caches the current unsliced part condition. - ConditionState mUnSlicedPartCondition; - - // Save the past buckets and we can clear when the StatsLogReport is dumped. - std::unordered_map> mPastBuckets; - - // The duration trackers in the current bucket. - std::unordered_map> - mCurrentSlicedDurationTrackerMap; - - // Helper function to create a duration tracker given the metric aggregation type. - std::unique_ptr createDurationTracker( - const MetricDimensionKey& eventKey) const; - - // Util function to check whether the specified dimension hits the guardrail. - bool hitGuardRailLocked(const MetricDimensionKey& newKey); - - static const size_t kBucketSize = sizeof(DurationBucket{}); - - FRIEND_TEST(DurationMetricTrackerTest, TestNoCondition); - FRIEND_TEST(DurationMetricTrackerTest, TestNonSlicedCondition); - FRIEND_TEST(DurationMetricTrackerTest, TestNonSlicedConditionUnknownState); - FRIEND_TEST(WakelockDurationE2eTest, TestAggregatedPredicates); - FRIEND_TEST(DurationMetricTrackerTest, TestFirstBucket); - - FRIEND_TEST(DurationMetricProducerTest_PartialBucket, TestSumDuration); - FRIEND_TEST(DurationMetricProducerTest_PartialBucket, - TestSumDurationWithSplitInFollowingBucket); - FRIEND_TEST(DurationMetricProducerTest_PartialBucket, TestMaxDuration); - FRIEND_TEST(DurationMetricProducerTest_PartialBucket, TestMaxDurationWithSplitInNextBucket); - - FRIEND_TEST(ConfigUpdateTest, TestUpdateDurationMetrics); - FRIEND_TEST(ConfigUpdateTest, TestUpdateAlerts); -}; - -} // namespace statsd -} // namespace os -} // namespace android diff --git a/bin/src/metrics/EventMetricProducer.cpp b/bin/src/metrics/EventMetricProducer.cpp deleted file mode 100644 index ca302c0e..00000000 --- a/bin/src/metrics/EventMetricProducer.cpp +++ /dev/null @@ -1,214 +0,0 @@ -/* - * 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. - */ - -#define DEBUG false // STOPSHIP if true -#include "Log.h" - -#include "EventMetricProducer.h" - -#include -#include - -#include "metrics/parsing_utils/metrics_manager_util.h" -#include "stats_log_util.h" -#include "stats_util.h" - -using android::util::FIELD_COUNT_REPEATED; -using android::util::FIELD_TYPE_BOOL; -using android::util::FIELD_TYPE_FLOAT; -using android::util::FIELD_TYPE_INT32; -using android::util::FIELD_TYPE_INT64; -using android::util::FIELD_TYPE_STRING; -using android::util::FIELD_TYPE_MESSAGE; -using android::util::ProtoOutputStream; -using std::map; -using std::string; -using std::unordered_map; -using std::vector; -using std::shared_ptr; - -namespace android { -namespace os { -namespace statsd { - -// for StatsLogReport -const int FIELD_ID_ID = 1; -const int FIELD_ID_EVENT_METRICS = 4; -const int FIELD_ID_IS_ACTIVE = 14; -// for EventMetricDataWrapper -const int FIELD_ID_DATA = 1; -// for EventMetricData -const int FIELD_ID_ELAPSED_TIMESTAMP_NANOS = 1; -const int FIELD_ID_ATOMS = 2; - -EventMetricProducer::EventMetricProducer( - const ConfigKey& key, const EventMetric& metric, const int conditionIndex, - const vector& initialConditionCache, const sp& wizard, - const uint64_t protoHash, const int64_t startTimeNs, - const unordered_map>& eventActivationMap, - const unordered_map>>& eventDeactivationMap, - const vector& slicedStateAtoms, - const unordered_map>& stateGroupMap) - : MetricProducer(metric.id(), key, startTimeNs, conditionIndex, initialConditionCache, wizard, - protoHash, eventActivationMap, eventDeactivationMap, slicedStateAtoms, - stateGroupMap) { - if (metric.links().size() > 0) { - for (const auto& link : metric.links()) { - Metric2Condition mc; - mc.conditionId = link.condition(); - translateFieldMatcher(link.fields_in_what(), &mc.metricFields); - translateFieldMatcher(link.fields_in_condition(), &mc.conditionFields); - mMetric2ConditionLinks.push_back(mc); - } - mConditionSliced = true; - } - mProto = std::make_unique(); - VLOG("metric %lld created. bucket size %lld start_time: %lld", (long long)metric.id(), - (long long)mBucketSizeNs, (long long)mTimeBaseNs); -} - -EventMetricProducer::~EventMetricProducer() { - VLOG("~EventMetricProducer() called"); -} - -bool EventMetricProducer::onConfigUpdatedLocked( - const StatsdConfig& config, const int configIndex, const int metricIndex, - const vector>& allAtomMatchingTrackers, - const unordered_map& oldAtomMatchingTrackerMap, - const unordered_map& newAtomMatchingTrackerMap, - const sp& matcherWizard, - const vector>& allConditionTrackers, - const unordered_map& conditionTrackerMap, const sp& wizard, - const unordered_map& metricToActivationMap, - unordered_map>& trackerToMetricMap, - unordered_map>& conditionToMetricMap, - unordered_map>& activationAtomTrackerToMetricMap, - unordered_map>& deactivationAtomTrackerToMetricMap, - vector& metricsWithActivation) { - if (!MetricProducer::onConfigUpdatedLocked( - config, configIndex, metricIndex, allAtomMatchingTrackers, - oldAtomMatchingTrackerMap, newAtomMatchingTrackerMap, matcherWizard, - allConditionTrackers, conditionTrackerMap, wizard, metricToActivationMap, - trackerToMetricMap, conditionToMetricMap, activationAtomTrackerToMetricMap, - deactivationAtomTrackerToMetricMap, metricsWithActivation)) { - return false; - } - - const EventMetric& metric = config.event_metric(configIndex); - int trackerIndex; - // Update appropriate indices, specifically mConditionIndex and MetricsManager maps. - if (!handleMetricWithAtomMatchingTrackers(metric.what(), metricIndex, false, - allAtomMatchingTrackers, newAtomMatchingTrackerMap, - trackerToMetricMap, trackerIndex)) { - return false; - } - - if (metric.has_condition() && - !handleMetricWithConditions(metric.condition(), metricIndex, conditionTrackerMap, - metric.links(), allConditionTrackers, mConditionTrackerIndex, - conditionToMetricMap)) { - return false; - } - return true; -} - -void EventMetricProducer::dropDataLocked(const int64_t dropTimeNs) { - mProto->clear(); - StatsdStats::getInstance().noteBucketDropped(mMetricId); -} - -void EventMetricProducer::onSlicedConditionMayChangeLocked(bool overallCondition, - const int64_t eventTime) { -} - -std::unique_ptr> serializeProtoLocked(ProtoOutputStream& protoOutput) { - size_t bufferSize = protoOutput.size(); - - std::unique_ptr> buffer(new std::vector(bufferSize)); - - size_t pos = 0; - sp reader = protoOutput.data(); - while (reader->readBuffer() != NULL) { - size_t toRead = reader->currentToRead(); - std::memcpy(&((*buffer)[pos]), reader->readBuffer(), toRead); - pos += toRead; - reader->move(toRead); - } - - return buffer; -} - -void EventMetricProducer::clearPastBucketsLocked(const int64_t dumpTimeNs) { - mProto->clear(); -} - -void EventMetricProducer::onDumpReportLocked(const int64_t dumpTimeNs, - const bool include_current_partial_bucket, - const bool erase_data, - const DumpLatency dumpLatency, - std::set *str_set, - ProtoOutputStream* protoOutput) { - protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_ID, (long long)mMetricId); - protoOutput->write(FIELD_TYPE_BOOL | FIELD_ID_IS_ACTIVE, isActiveLocked()); - if (mProto->size() <= 0) { - return; - } - - size_t bufferSize = mProto->size(); - VLOG("metric %lld dump report now... proto size: %zu ", - (long long)mMetricId, bufferSize); - std::unique_ptr> buffer = serializeProtoLocked(*mProto); - - protoOutput->write(FIELD_TYPE_MESSAGE | FIELD_ID_EVENT_METRICS, - reinterpret_cast(buffer.get()->data()), buffer.get()->size()); - - if (erase_data) { - mProto->clear(); - } -} - -void EventMetricProducer::onConditionChangedLocked(const bool conditionMet, - const int64_t eventTime) { - VLOG("Metric %lld onConditionChanged", (long long)mMetricId); - mCondition = conditionMet ? ConditionState::kTrue : ConditionState::kFalse; -} - -void EventMetricProducer::onMatchedLogEventInternalLocked( - const size_t matcherIndex, const MetricDimensionKey& eventKey, - const ConditionKey& conditionKey, bool condition, const LogEvent& event, - const map& statePrimaryKeys) { - if (!condition) { - return; - } - - uint64_t wrapperToken = - mProto->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_DATA); - const int64_t elapsedTimeNs = truncateTimestampIfNecessary(event); - mProto->write(FIELD_TYPE_INT64 | FIELD_ID_ELAPSED_TIMESTAMP_NANOS, (long long) elapsedTimeNs); - - uint64_t eventToken = mProto->start(FIELD_TYPE_MESSAGE | FIELD_ID_ATOMS); - event.ToProto(*mProto); - mProto->end(eventToken); - mProto->end(wrapperToken); -} - -size_t EventMetricProducer::byteSizeLocked() const { - return mProto->bytesWritten(); -} - -} // namespace statsd -} // namespace os -} // namespace android diff --git a/bin/src/metrics/EventMetricProducer.h b/bin/src/metrics/EventMetricProducer.h deleted file mode 100644 index 5236ebe7..00000000 --- a/bin/src/metrics/EventMetricProducer.h +++ /dev/null @@ -1,103 +0,0 @@ -/* - * 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. - */ - -#ifndef EVENT_METRIC_PRODUCER_H -#define EVENT_METRIC_PRODUCER_H - -#include - -#include - -#include "../condition/ConditionTracker.h" -#include "../matchers/matcher_util.h" -#include "MetricProducer.h" -#include "packages/modules/StatsD/bin/src/statsd_config.pb.h" -#include "stats_util.h" - -namespace android { -namespace os { -namespace statsd { - -class EventMetricProducer : public MetricProducer { -public: - EventMetricProducer( - const ConfigKey& key, const EventMetric& eventMetric, const int conditionIndex, - const vector& initialConditionCache, const sp& wizard, - const uint64_t protoHash, const int64_t startTimeNs, - const std::unordered_map>& eventActivationMap = {}, - const std::unordered_map>>& - eventDeactivationMap = {}, - const vector& slicedStateAtoms = {}, - const unordered_map>& stateGroupMap = {}); - - virtual ~EventMetricProducer(); - - MetricType getMetricType() const override { - return METRIC_TYPE_EVENT; - } - -private: - void onMatchedLogEventInternalLocked( - const size_t matcherIndex, const MetricDimensionKey& eventKey, - const ConditionKey& conditionKey, bool condition, const LogEvent& event, - const std::map& statePrimaryKeys) override; - - void onDumpReportLocked(const int64_t dumpTimeNs, - const bool include_current_partial_bucket, - const bool erase_data, - const DumpLatency dumpLatency, - std::set *str_set, - android::util::ProtoOutputStream* protoOutput) override; - void clearPastBucketsLocked(const int64_t dumpTimeNs) override; - - // Internal interface to handle condition change. - void onConditionChangedLocked(const bool conditionMet, const int64_t eventTime) override; - - // Internal interface to handle sliced condition change. - void onSlicedConditionMayChangeLocked(bool overallCondition, const int64_t eventTime) override; - - bool onConfigUpdatedLocked( - const StatsdConfig& config, const int configIndex, const int metricIndex, - const std::vector>& allAtomMatchingTrackers, - const std::unordered_map& oldAtomMatchingTrackerMap, - const std::unordered_map& newAtomMatchingTrackerMap, - const sp& matcherWizard, - const std::vector>& allConditionTrackers, - const std::unordered_map& conditionTrackerMap, - const sp& wizard, - const std::unordered_map& metricToActivationMap, - std::unordered_map>& trackerToMetricMap, - std::unordered_map>& conditionToMetricMap, - std::unordered_map>& activationAtomTrackerToMetricMap, - std::unordered_map>& deactivationAtomTrackerToMetricMap, - std::vector& metricsWithActivation) override; - - void dropDataLocked(const int64_t dropTimeNs) override; - - // Internal function to calculate the current used bytes. - size_t byteSizeLocked() const override; - - void dumpStatesLocked(FILE* out, bool verbose) const override{}; - - // Maps to a EventMetricDataWrapper. Storing atom events in ProtoOutputStream - // is more space efficient than storing LogEvent. - std::unique_ptr mProto; -}; - -} // namespace statsd -} // namespace os -} // namespace android -#endif // EVENT_METRIC_PRODUCER_H diff --git a/bin/src/metrics/GaugeMetricProducer.cpp b/bin/src/metrics/GaugeMetricProducer.cpp deleted file mode 100644 index bca5c17b..00000000 --- a/bin/src/metrics/GaugeMetricProducer.cpp +++ /dev/null @@ -1,681 +0,0 @@ -/* -* 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. -*/ - -#define DEBUG false // STOPSHIP if true -#include "Log.h" - -#include "GaugeMetricProducer.h" - -#include "guardrail/StatsdStats.h" -#include "metrics/parsing_utils/metrics_manager_util.h" -#include "stats_log_util.h" - -using android::util::FIELD_COUNT_REPEATED; -using android::util::FIELD_TYPE_BOOL; -using android::util::FIELD_TYPE_FLOAT; -using android::util::FIELD_TYPE_INT32; -using android::util::FIELD_TYPE_INT64; -using android::util::FIELD_TYPE_MESSAGE; -using android::util::FIELD_TYPE_STRING; -using android::util::ProtoOutputStream; -using std::map; -using std::string; -using std::unordered_map; -using std::vector; -using std::make_shared; -using std::shared_ptr; - -namespace android { -namespace os { -namespace statsd { - -// for StatsLogReport -const int FIELD_ID_ID = 1; -const int FIELD_ID_GAUGE_METRICS = 8; -const int FIELD_ID_TIME_BASE = 9; -const int FIELD_ID_BUCKET_SIZE = 10; -const int FIELD_ID_DIMENSION_PATH_IN_WHAT = 11; -const int FIELD_ID_IS_ACTIVE = 14; -// for GaugeMetricDataWrapper -const int FIELD_ID_DATA = 1; -const int FIELD_ID_SKIPPED = 2; -// for SkippedBuckets -const int FIELD_ID_SKIPPED_START_MILLIS = 3; -const int FIELD_ID_SKIPPED_END_MILLIS = 4; -const int FIELD_ID_SKIPPED_DROP_EVENT = 5; -// for DumpEvent Proto -const int FIELD_ID_BUCKET_DROP_REASON = 1; -const int FIELD_ID_DROP_TIME = 2; -// for GaugeMetricData -const int FIELD_ID_DIMENSION_IN_WHAT = 1; -const int FIELD_ID_BUCKET_INFO = 3; -const int FIELD_ID_DIMENSION_LEAF_IN_WHAT = 4; -// for GaugeBucketInfo -const int FIELD_ID_ATOM = 3; -const int FIELD_ID_ELAPSED_ATOM_TIMESTAMP = 4; -const int FIELD_ID_BUCKET_NUM = 6; -const int FIELD_ID_START_BUCKET_ELAPSED_MILLIS = 7; -const int FIELD_ID_END_BUCKET_ELAPSED_MILLIS = 8; - -GaugeMetricProducer::GaugeMetricProducer( - const ConfigKey& key, const GaugeMetric& metric, const int conditionIndex, - const vector& initialConditionCache, const sp& wizard, - const uint64_t protoHash, const int whatMatcherIndex, - const sp& matcherWizard, const int pullTagId, const int triggerAtomId, - const int atomId, const int64_t timeBaseNs, const int64_t startTimeNs, - const sp& pullerManager, - const unordered_map>& eventActivationMap, - const unordered_map>>& eventDeactivationMap) - : MetricProducer(metric.id(), key, timeBaseNs, conditionIndex, initialConditionCache, wizard, - protoHash, eventActivationMap, eventDeactivationMap, /*slicedStateAtoms=*/{}, - /*stateGroupMap=*/{}), - mWhatMatcherIndex(whatMatcherIndex), - mEventMatcherWizard(matcherWizard), - mPullerManager(pullerManager), - mPullTagId(pullTagId), - mTriggerAtomId(triggerAtomId), - mAtomId(atomId), - mIsPulled(pullTagId != -1), - mMinBucketSizeNs(metric.min_bucket_size_nanos()), - mMaxPullDelayNs(metric.max_pull_delay_sec() > 0 ? metric.max_pull_delay_sec() * NS_PER_SEC - : StatsdStats::kPullMaxDelayNs), - mDimensionSoftLimit(StatsdStats::kAtomDimensionKeySizeLimitMap.find(pullTagId) != - StatsdStats::kAtomDimensionKeySizeLimitMap.end() - ? StatsdStats::kAtomDimensionKeySizeLimitMap.at(pullTagId).first - : StatsdStats::kDimensionKeySizeSoftLimit), - mDimensionHardLimit(StatsdStats::kAtomDimensionKeySizeLimitMap.find(pullTagId) != - StatsdStats::kAtomDimensionKeySizeLimitMap.end() - ? StatsdStats::kAtomDimensionKeySizeLimitMap.at(pullTagId).second - : StatsdStats::kDimensionKeySizeHardLimit), - mGaugeAtomsPerDimensionLimit(metric.max_num_gauge_atoms_per_bucket()), - mSplitBucketForAppUpgrade(metric.split_bucket_for_app_upgrade()) { - mCurrentSlicedBucket = std::make_shared(); - mCurrentSlicedBucketForAnomaly = std::make_shared(); - int64_t bucketSizeMills = 0; - if (metric.has_bucket()) { - bucketSizeMills = TimeUnitToBucketSizeInMillisGuardrailed(key.GetUid(), metric.bucket()); - } else { - bucketSizeMills = TimeUnitToBucketSizeInMillis(ONE_HOUR); - } - mBucketSizeNs = bucketSizeMills * 1000000; - - mSamplingType = metric.sampling_type(); - if (!metric.gauge_fields_filter().include_all()) { - translateFieldMatcher(metric.gauge_fields_filter().fields(), &mFieldMatchers); - } - - if (metric.has_dimensions_in_what()) { - translateFieldMatcher(metric.dimensions_in_what(), &mDimensionsInWhat); - mContainANYPositionInDimensionsInWhat = HasPositionANY(metric.dimensions_in_what()); - } - - if (metric.links().size() > 0) { - for (const auto& link : metric.links()) { - Metric2Condition mc; - mc.conditionId = link.condition(); - translateFieldMatcher(link.fields_in_what(), &mc.metricFields); - translateFieldMatcher(link.fields_in_condition(), &mc.conditionFields); - mMetric2ConditionLinks.push_back(mc); - } - mConditionSliced = true; - } - mSliceByPositionALL = HasPositionALL(metric.dimensions_in_what()); - - flushIfNeededLocked(startTimeNs); - // Kicks off the puller immediately. - if (mIsPulled && mSamplingType == GaugeMetric::RANDOM_ONE_SAMPLE) { - mPullerManager->RegisterReceiver(mPullTagId, mConfigKey, this, getCurrentBucketEndTimeNs(), - mBucketSizeNs); - } - - // Adjust start for partial first bucket and then pull if needed - mCurrentBucketStartTimeNs = startTimeNs; - - VLOG("Gauge metric %lld created. bucket size %lld start_time: %lld sliced %d", - (long long)metric.id(), (long long)mBucketSizeNs, (long long)mTimeBaseNs, - mConditionSliced); -} - -GaugeMetricProducer::~GaugeMetricProducer() { - VLOG("~GaugeMetricProducer() called"); - if (mIsPulled && mSamplingType == GaugeMetric::RANDOM_ONE_SAMPLE) { - mPullerManager->UnRegisterReceiver(mPullTagId, mConfigKey, this); - } -} - -bool GaugeMetricProducer::onConfigUpdatedLocked( - const StatsdConfig& config, const int configIndex, const int metricIndex, - const vector>& allAtomMatchingTrackers, - const unordered_map& oldAtomMatchingTrackerMap, - const unordered_map& newAtomMatchingTrackerMap, - const sp& matcherWizard, - const vector>& allConditionTrackers, - const unordered_map& conditionTrackerMap, const sp& wizard, - const unordered_map& metricToActivationMap, - unordered_map>& trackerToMetricMap, - unordered_map>& conditionToMetricMap, - unordered_map>& activationAtomTrackerToMetricMap, - unordered_map>& deactivationAtomTrackerToMetricMap, - vector& metricsWithActivation) { - if (!MetricProducer::onConfigUpdatedLocked( - config, configIndex, metricIndex, allAtomMatchingTrackers, - oldAtomMatchingTrackerMap, newAtomMatchingTrackerMap, matcherWizard, - allConditionTrackers, conditionTrackerMap, wizard, metricToActivationMap, - trackerToMetricMap, conditionToMetricMap, activationAtomTrackerToMetricMap, - deactivationAtomTrackerToMetricMap, metricsWithActivation)) { - return false; - } - - const GaugeMetric& metric = config.gauge_metric(configIndex); - // Update appropriate indices: mWhatMatcherIndex, mConditionIndex and MetricsManager maps. - if (!handleMetricWithAtomMatchingTrackers(metric.what(), metricIndex, /*enforceOneAtom=*/false, - allAtomMatchingTrackers, newAtomMatchingTrackerMap, - trackerToMetricMap, mWhatMatcherIndex)) { - return false; - } - - // Need to update maps since the index changed, but mTriggerAtomId will not change. - int triggerTrackerIndex; - if (metric.has_trigger_event() && - !handleMetricWithAtomMatchingTrackers(metric.trigger_event(), metricIndex, - /*enforceOneAtom=*/true, allAtomMatchingTrackers, - newAtomMatchingTrackerMap, trackerToMetricMap, - triggerTrackerIndex)) { - return false; - } - - if (metric.has_condition() && - !handleMetricWithConditions(metric.condition(), metricIndex, conditionTrackerMap, - metric.links(), allConditionTrackers, mConditionTrackerIndex, - conditionToMetricMap)) { - return false; - } - sp tmpEventWizard = mEventMatcherWizard; - mEventMatcherWizard = matcherWizard; - - // If this is a config update, we must have just forced a partial bucket. Pull if needed to get - // data for the new bucket. - if (mIsActive && mIsPulled && mSamplingType == GaugeMetric::RANDOM_ONE_SAMPLE) { - pullAndMatchEventsLocked(mCurrentBucketStartTimeNs); - } - return true; -} - -void GaugeMetricProducer::dumpStatesLocked(FILE* out, bool verbose) const { - if (mCurrentSlicedBucket == nullptr || - mCurrentSlicedBucket->size() == 0) { - return; - } - - fprintf(out, "GaugeMetric %lld dimension size %lu\n", (long long)mMetricId, - (unsigned long)mCurrentSlicedBucket->size()); - if (verbose) { - for (const auto& it : *mCurrentSlicedBucket) { - fprintf(out, "\t(what)%s\t(states)%s %d atoms\n", - it.first.getDimensionKeyInWhat().toString().c_str(), - it.first.getStateValuesKey().toString().c_str(), (int)it.second.size()); - } - } -} - -void GaugeMetricProducer::clearPastBucketsLocked(const int64_t dumpTimeNs) { - flushIfNeededLocked(dumpTimeNs); - mPastBuckets.clear(); - mSkippedBuckets.clear(); -} - -void GaugeMetricProducer::onDumpReportLocked(const int64_t dumpTimeNs, - const bool include_current_partial_bucket, - const bool erase_data, - const DumpLatency dumpLatency, - std::set *str_set, - ProtoOutputStream* protoOutput) { - VLOG("Gauge metric %lld report now...", (long long)mMetricId); - if (include_current_partial_bucket) { - flushLocked(dumpTimeNs); - } else { - flushIfNeededLocked(dumpTimeNs); - } - - protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_ID, (long long)mMetricId); - protoOutput->write(FIELD_TYPE_BOOL | FIELD_ID_IS_ACTIVE, isActiveLocked()); - - if (mPastBuckets.empty() && mSkippedBuckets.empty()) { - return; - } - - protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_TIME_BASE, (long long)mTimeBaseNs); - protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_BUCKET_SIZE, (long long)mBucketSizeNs); - - // Fills the dimension path if not slicing by ALL. - if (!mSliceByPositionALL) { - if (!mDimensionsInWhat.empty()) { - uint64_t dimenPathToken = protoOutput->start( - FIELD_TYPE_MESSAGE | FIELD_ID_DIMENSION_PATH_IN_WHAT); - writeDimensionPathToProto(mDimensionsInWhat, protoOutput); - protoOutput->end(dimenPathToken); - } - } - - uint64_t protoToken = protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_ID_GAUGE_METRICS); - - for (const auto& skippedBucket : mSkippedBuckets) { - uint64_t wrapperToken = - protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_SKIPPED); - protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_SKIPPED_START_MILLIS, - (long long)(NanoToMillis(skippedBucket.bucketStartTimeNs))); - protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_SKIPPED_END_MILLIS, - (long long)(NanoToMillis(skippedBucket.bucketEndTimeNs))); - - for (const auto& dropEvent : skippedBucket.dropEvents) { - uint64_t dropEventToken = protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | - FIELD_ID_SKIPPED_DROP_EVENT); - protoOutput->write(FIELD_TYPE_INT32 | FIELD_ID_BUCKET_DROP_REASON, dropEvent.reason); - protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_DROP_TIME, (long long) (NanoToMillis(dropEvent.dropTimeNs))); - protoOutput->end(dropEventToken); - } - protoOutput->end(wrapperToken); - } - - for (const auto& pair : mPastBuckets) { - const MetricDimensionKey& dimensionKey = pair.first; - - VLOG("Gauge dimension key %s", dimensionKey.toString().c_str()); - uint64_t wrapperToken = - protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_DATA); - - // First fill dimension. - if (mSliceByPositionALL) { - uint64_t dimensionToken = protoOutput->start( - FIELD_TYPE_MESSAGE | FIELD_ID_DIMENSION_IN_WHAT); - writeDimensionToProto(dimensionKey.getDimensionKeyInWhat(), str_set, protoOutput); - protoOutput->end(dimensionToken); - } else { - writeDimensionLeafNodesToProto(dimensionKey.getDimensionKeyInWhat(), - FIELD_ID_DIMENSION_LEAF_IN_WHAT, str_set, protoOutput); - } - - // Then fill bucket_info (GaugeBucketInfo). - for (const auto& bucket : pair.second) { - uint64_t bucketInfoToken = protoOutput->start( - FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_BUCKET_INFO); - - if (bucket.mBucketEndNs - bucket.mBucketStartNs != mBucketSizeNs) { - protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_START_BUCKET_ELAPSED_MILLIS, - (long long)NanoToMillis(bucket.mBucketStartNs)); - protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_END_BUCKET_ELAPSED_MILLIS, - (long long)NanoToMillis(bucket.mBucketEndNs)); - } else { - protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_BUCKET_NUM, - (long long)(getBucketNumFromEndTimeNs(bucket.mBucketEndNs))); - } - - if (!bucket.mGaugeAtoms.empty()) { - for (const auto& atom : bucket.mGaugeAtoms) { - uint64_t atomsToken = - protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | - FIELD_ID_ATOM); - writeFieldValueTreeToStream(mAtomId, *(atom.mFields), protoOutput); - protoOutput->end(atomsToken); - } - for (const auto& atom : bucket.mGaugeAtoms) { - protoOutput->write(FIELD_TYPE_INT64 | FIELD_COUNT_REPEATED | - FIELD_ID_ELAPSED_ATOM_TIMESTAMP, - (long long)atom.mElapsedTimestampNs); - } - } - protoOutput->end(bucketInfoToken); - VLOG("Gauge \t bucket [%lld - %lld] includes %d atoms.", - (long long)bucket.mBucketStartNs, (long long)bucket.mBucketEndNs, - (int)bucket.mGaugeAtoms.size()); - } - protoOutput->end(wrapperToken); - } - protoOutput->end(protoToken); - - - if (erase_data) { - mPastBuckets.clear(); - mSkippedBuckets.clear(); - } -} - -void GaugeMetricProducer::prepareFirstBucketLocked() { - if (mIsActive && mIsPulled && mSamplingType == GaugeMetric::RANDOM_ONE_SAMPLE) { - pullAndMatchEventsLocked(mCurrentBucketStartTimeNs); - } -} - -void GaugeMetricProducer::pullAndMatchEventsLocked(const int64_t timestampNs) { - bool triggerPuller = false; - switch(mSamplingType) { - // When the metric wants to do random sampling and there is already one gauge atom for the - // current bucket, do not do it again. - case GaugeMetric::RANDOM_ONE_SAMPLE: { - triggerPuller = mCondition == ConditionState::kTrue && mCurrentSlicedBucket->empty(); - break; - } - case GaugeMetric::CONDITION_CHANGE_TO_TRUE: { - triggerPuller = mCondition == ConditionState::kTrue; - break; - } - case GaugeMetric::FIRST_N_SAMPLES: { - triggerPuller = mCondition == ConditionState::kTrue; - break; - } - default: - break; - } - if (!triggerPuller) { - return; - } - vector> allData; - if (!mPullerManager->Pull(mPullTagId, mConfigKey, timestampNs, &allData)) { - ALOGE("Gauge Stats puller failed for tag: %d at %lld", mPullTagId, (long long)timestampNs); - return; - } - const int64_t pullDelayNs = getElapsedRealtimeNs() - timestampNs; - StatsdStats::getInstance().notePullDelay(mPullTagId, pullDelayNs); - if (pullDelayNs > mMaxPullDelayNs) { - ALOGE("Pull finish too late for atom %d", mPullTagId); - StatsdStats::getInstance().notePullExceedMaxDelay(mPullTagId); - return; - } - for (const auto& data : allData) { - LogEvent localCopy = data->makeCopy(); - localCopy.setElapsedTimestampNs(timestampNs); - if (mEventMatcherWizard->matchLogEvent(localCopy, mWhatMatcherIndex) == - MatchingState::kMatched) { - onMatchedLogEventLocked(mWhatMatcherIndex, localCopy); - } - } -} - -void GaugeMetricProducer::onActiveStateChangedLocked(const int64_t& eventTimeNs) { - MetricProducer::onActiveStateChangedLocked(eventTimeNs); - if (ConditionState::kTrue != mCondition || !mIsPulled) { - return; - } - if (mTriggerAtomId == -1 || (mIsActive && mSamplingType == GaugeMetric::RANDOM_ONE_SAMPLE)) { - pullAndMatchEventsLocked(eventTimeNs); - } - -} - -void GaugeMetricProducer::onConditionChangedLocked(const bool conditionMet, - const int64_t eventTimeNs) { - VLOG("GaugeMetric %lld onConditionChanged", (long long)mMetricId); - - mCondition = conditionMet ? ConditionState::kTrue : ConditionState::kFalse; - if (!mIsActive) { - return; - } - - flushIfNeededLocked(eventTimeNs); - if (mIsPulled && mTriggerAtomId == -1) { - pullAndMatchEventsLocked(eventTimeNs); - } // else: Push mode. No need to proactively pull the gauge data. -} - -void GaugeMetricProducer::onSlicedConditionMayChangeLocked(bool overallCondition, - const int64_t eventTimeNs) { - VLOG("GaugeMetric %lld onSlicedConditionMayChange overall condition %d", (long long)mMetricId, - overallCondition); - mCondition = overallCondition ? ConditionState::kTrue : ConditionState::kFalse; - if (!mIsActive) { - return; - } - - flushIfNeededLocked(eventTimeNs); - // If the condition is sliced, mCondition is true if any of the dimensions is true. And we will - // pull for every dimension. - if (mIsPulled && mTriggerAtomId == -1) { - pullAndMatchEventsLocked(eventTimeNs); - } // else: Push mode. No need to proactively pull the gauge data. -} - -std::shared_ptr> GaugeMetricProducer::getGaugeFields(const LogEvent& event) { - std::shared_ptr> gaugeFields; - if (mFieldMatchers.size() > 0) { - gaugeFields = std::make_shared>(); - filterGaugeValues(mFieldMatchers, event.getValues(), gaugeFields.get()); - } else { - gaugeFields = std::make_shared>(event.getValues()); - } - // Trim all dimension fields from output. Dimensions will appear in output report and will - // benefit from dictionary encoding. For large pulled atoms, this can give the benefit of - // optional repeated field. - for (const auto& field : mDimensionsInWhat) { - for (auto it = gaugeFields->begin(); it != gaugeFields->end();) { - if (it->mField.matches(field)) { - it = gaugeFields->erase(it); - } else { - it++; - } - } - } - return gaugeFields; -} - -void GaugeMetricProducer::onDataPulled(const std::vector>& allData, - bool pullSuccess, int64_t originalPullTimeNs) { - std::lock_guard lock(mMutex); - if (!pullSuccess || allData.size() == 0) { - return; - } - const int64_t pullDelayNs = getElapsedRealtimeNs() - originalPullTimeNs; - StatsdStats::getInstance().notePullDelay(mPullTagId, pullDelayNs); - if (pullDelayNs > mMaxPullDelayNs) { - ALOGE("Pull finish too late for atom %d", mPullTagId); - StatsdStats::getInstance().notePullExceedMaxDelay(mPullTagId); - return; - } - for (const auto& data : allData) { - if (mEventMatcherWizard->matchLogEvent( - *data, mWhatMatcherIndex) == MatchingState::kMatched) { - onMatchedLogEventLocked(mWhatMatcherIndex, *data); - } - } -} - -bool GaugeMetricProducer::hitGuardRailLocked(const MetricDimensionKey& newKey) { - if (mCurrentSlicedBucket->find(newKey) != mCurrentSlicedBucket->end()) { - return false; - } - // 1. Report the tuple count if the tuple count > soft limit - if (mCurrentSlicedBucket->size() > mDimensionSoftLimit - 1) { - size_t newTupleCount = mCurrentSlicedBucket->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 > mDimensionHardLimit) { - ALOGE("GaugeMetric %lld dropping data for dimension key %s", - (long long)mMetricId, newKey.toString().c_str()); - StatsdStats::getInstance().noteHardDimensionLimitReached(mMetricId); - return true; - } - } - - return false; -} - -void GaugeMetricProducer::onMatchedLogEventInternalLocked( - const size_t matcherIndex, const MetricDimensionKey& eventKey, - const ConditionKey& conditionKey, bool condition, const LogEvent& event, - const map& statePrimaryKeys) { - if (condition == false) { - return; - } - int64_t eventTimeNs = event.GetElapsedTimestampNs(); - if (eventTimeNs < mCurrentBucketStartTimeNs) { - VLOG("Gauge Skip event due to late arrival: %lld vs %lld", (long long)eventTimeNs, - (long long)mCurrentBucketStartTimeNs); - return; - } - flushIfNeededLocked(eventTimeNs); - - if (mTriggerAtomId == event.GetTagId()) { - pullAndMatchEventsLocked(eventTimeNs); - return; - } - - // When gauge metric wants to randomly sample the output atom, we just simply use the first - // gauge in the given bucket. - if (mCurrentSlicedBucket->find(eventKey) != mCurrentSlicedBucket->end() && - mSamplingType == GaugeMetric::RANDOM_ONE_SAMPLE) { - return; - } - if (hitGuardRailLocked(eventKey)) { - return; - } - if ((*mCurrentSlicedBucket)[eventKey].size() >= mGaugeAtomsPerDimensionLimit) { - return; - } - - const int64_t truncatedElapsedTimestampNs = truncateTimestampIfNecessary(event); - GaugeAtom gaugeAtom(getGaugeFields(event), truncatedElapsedTimestampNs); - (*mCurrentSlicedBucket)[eventKey].push_back(gaugeAtom); - // Anomaly detection on gauge metric only works when there is one numeric - // field specified. - if (mAnomalyTrackers.size() > 0) { - if (gaugeAtom.mFields->size() == 1) { - const Value& value = gaugeAtom.mFields->begin()->mValue; - long gaugeVal = 0; - if (value.getType() == INT) { - gaugeVal = (long)value.int_value; - } else if (value.getType() == LONG) { - gaugeVal = value.long_value; - } - for (auto& tracker : mAnomalyTrackers) { - tracker->detectAndDeclareAnomaly(eventTimeNs, mCurrentBucketNum, mMetricId, - eventKey, gaugeVal); - } - } - } -} - -void GaugeMetricProducer::updateCurrentSlicedBucketForAnomaly() { - for (const auto& slice : *mCurrentSlicedBucket) { - if (slice.second.empty()) { - continue; - } - const Value& value = slice.second.front().mFields->front().mValue; - long gaugeVal = 0; - if (value.getType() == INT) { - gaugeVal = (long)value.int_value; - } else if (value.getType() == LONG) { - gaugeVal = value.long_value; - } - (*mCurrentSlicedBucketForAnomaly)[slice.first] = gaugeVal; - } -} - -void GaugeMetricProducer::dropDataLocked(const int64_t dropTimeNs) { - flushIfNeededLocked(dropTimeNs); - StatsdStats::getInstance().noteBucketDropped(mMetricId); - mPastBuckets.clear(); -} - -// When a new matched event comes in, we check if event falls into the current -// bucket. If not, flush the old counter to past buckets and initialize the new -// bucket. -// if data is pushed, onMatchedLogEvent will only be called through onConditionChanged() inside -// the GaugeMetricProducer while holding the lock. -void GaugeMetricProducer::flushIfNeededLocked(const int64_t& eventTimeNs) { - int64_t currentBucketEndTimeNs = getCurrentBucketEndTimeNs(); - - if (eventTimeNs < currentBucketEndTimeNs) { - VLOG("Gauge eventTime is %lld, less than next bucket start time %lld", - (long long)eventTimeNs, (long long)(mCurrentBucketStartTimeNs + mBucketSizeNs)); - return; - } - - // Adjusts the bucket start and end times. - int64_t numBucketsForward = 1 + (eventTimeNs - currentBucketEndTimeNs) / mBucketSizeNs; - int64_t nextBucketNs = currentBucketEndTimeNs + (numBucketsForward - 1) * mBucketSizeNs; - flushCurrentBucketLocked(eventTimeNs, nextBucketNs); - - mCurrentBucketNum += numBucketsForward; - VLOG("Gauge metric %lld: new bucket start time: %lld", (long long)mMetricId, - (long long)mCurrentBucketStartTimeNs); -} - -void GaugeMetricProducer::flushCurrentBucketLocked(const int64_t& eventTimeNs, - const int64_t& nextBucketStartTimeNs) { - int64_t fullBucketEndTimeNs = getCurrentBucketEndTimeNs(); - int64_t bucketEndTime = eventTimeNs < fullBucketEndTimeNs ? eventTimeNs : fullBucketEndTimeNs; - - GaugeBucket info; - info.mBucketStartNs = mCurrentBucketStartTimeNs; - info.mBucketEndNs = bucketEndTime; - - // Add bucket to mPastBuckets if bucket is large enough. - // Otherwise, drop the bucket data and add bucket metadata to mSkippedBuckets. - bool isBucketLargeEnough = info.mBucketEndNs - mCurrentBucketStartTimeNs >= mMinBucketSizeNs; - if (isBucketLargeEnough) { - for (const auto& slice : *mCurrentSlicedBucket) { - info.mGaugeAtoms = slice.second; - auto& bucketList = mPastBuckets[slice.first]; - bucketList.push_back(info); - VLOG("Gauge gauge metric %lld, dump key value: %s", (long long)mMetricId, - slice.first.toString().c_str()); - } - } else { - mCurrentSkippedBucket.bucketStartTimeNs = mCurrentBucketStartTimeNs; - mCurrentSkippedBucket.bucketEndTimeNs = bucketEndTime; - if (!maxDropEventsReached()) { - mCurrentSkippedBucket.dropEvents.emplace_back( - buildDropEvent(eventTimeNs, BucketDropReason::BUCKET_TOO_SMALL)); - } - mSkippedBuckets.emplace_back(mCurrentSkippedBucket); - } - - // If we have anomaly trackers, we need to update the partial bucket values. - if (mAnomalyTrackers.size() > 0) { - updateCurrentSlicedBucketForAnomaly(); - - if (eventTimeNs > fullBucketEndTimeNs) { - // This is known to be a full bucket, so send this data to the anomaly tracker. - for (auto& tracker : mAnomalyTrackers) { - tracker->addPastBucket(mCurrentSlicedBucketForAnomaly, mCurrentBucketNum); - } - mCurrentSlicedBucketForAnomaly = std::make_shared(); - } - } - - StatsdStats::getInstance().noteBucketCount(mMetricId); - mCurrentSlicedBucket = std::make_shared(); - mCurrentBucketStartTimeNs = nextBucketStartTimeNs; - mCurrentSkippedBucket.reset(); -} - -size_t GaugeMetricProducer::byteSizeLocked() const { - size_t totalSize = 0; - for (const auto& pair : mPastBuckets) { - for (const auto& bucket : pair.second) { - totalSize += bucket.mGaugeAtoms.size() * sizeof(GaugeAtom); - for (const auto& atom : bucket.mGaugeAtoms) { - if (atom.mFields != nullptr) { - totalSize += atom.mFields->size() * sizeof(FieldValue); - } - } - } - } - return totalSize; -} - -} // namespace statsd -} // namespace os -} // namespace android diff --git a/bin/src/metrics/GaugeMetricProducer.h b/bin/src/metrics/GaugeMetricProducer.h deleted file mode 100644 index 751b4872..00000000 --- a/bin/src/metrics/GaugeMetricProducer.h +++ /dev/null @@ -1,234 +0,0 @@ -/* - * 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. - */ - -#pragma once - -#include - -#include -#include -#include "../condition/ConditionTracker.h" -#include "../external/PullDataReceiver.h" -#include "../external/StatsPullerManager.h" -#include "../matchers/matcher_util.h" -#include "../matchers/EventMatcherWizard.h" -#include "MetricProducer.h" -#include "packages/modules/StatsD/bin/src/statsd_config.pb.h" -#include "../stats_util.h" - -namespace android { -namespace os { -namespace statsd { - -struct GaugeAtom { - GaugeAtom(std::shared_ptr> fields, int64_t elapsedTimeNs) - : mFields(fields), mElapsedTimestampNs(elapsedTimeNs) { - } - std::shared_ptr> mFields; - int64_t mElapsedTimestampNs; -}; - -struct GaugeBucket { - int64_t mBucketStartNs; - int64_t mBucketEndNs; - std::vector mGaugeAtoms; -}; - -typedef std::unordered_map> - DimToGaugeAtomsMap; - -// This gauge metric producer first register the puller to automatically pull the gauge at the -// beginning of each bucket. If the condition is met, insert it to the bucket info. Otherwise -// proactively pull the gauge when the condition is changed to be true. Therefore, the gauge metric -// producer always reports the gauge at the earliest time of the bucket when the condition is met. -class GaugeMetricProducer : public MetricProducer, public virtual PullDataReceiver { -public: - GaugeMetricProducer( - const ConfigKey& key, const GaugeMetric& gaugeMetric, const int conditionIndex, - const vector& initialConditionCache, - const sp& conditionWizard, const uint64_t protoHash, - const int whatMatcherIndex, const sp& matcherWizard, - const int pullTagId, const int triggerAtomId, const int atomId, - const int64_t timeBaseNs, const int64_t startTimeNs, - const sp& pullerManager, - const std::unordered_map>& eventActivationMap = {}, - const std::unordered_map>>& - eventDeactivationMap = {}); - - virtual ~GaugeMetricProducer(); - - // Handles when the pulled data arrives. - void onDataPulled(const std::vector>& data, - bool pullSuccess, int64_t originalPullTimeNs) override; - - // GaugeMetric needs to immediately trigger another pull when we create the partial bucket. - void notifyAppUpgrade(const int64_t& eventTimeNs) override { - std::lock_guard lock(mMutex); - - if (!mSplitBucketForAppUpgrade) { - return; - } - flushLocked(eventTimeNs); - if (mIsPulled && mSamplingType == GaugeMetric::RANDOM_ONE_SAMPLE) { - pullAndMatchEventsLocked(eventTimeNs); - } - }; - - // GaugeMetric needs to immediately trigger another pull when we create the partial bucket. - void onStatsdInitCompleted(const int64_t& eventTimeNs) override { - std::lock_guard lock(mMutex); - - flushLocked(eventTimeNs); - if (mIsPulled && mSamplingType == GaugeMetric::RANDOM_ONE_SAMPLE) { - pullAndMatchEventsLocked(eventTimeNs); - } - }; - - MetricType getMetricType() const override { - return METRIC_TYPE_GAUGE; - } - -protected: - void onMatchedLogEventInternalLocked( - const size_t matcherIndex, const MetricDimensionKey& eventKey, - const ConditionKey& conditionKey, bool condition, const LogEvent& event, - const std::map& statePrimaryKeys) override; - -private: - void onDumpReportLocked(const int64_t dumpTimeNs, - const bool include_current_partial_bucket, - const bool erase_data, - const DumpLatency dumpLatency, - std::set *str_set, - android::util::ProtoOutputStream* protoOutput) override; - void clearPastBucketsLocked(const int64_t dumpTimeNs) override; - - // Internal interface to handle condition change. - void onConditionChangedLocked(const bool conditionMet, const int64_t eventTime) override; - - // Internal interface to handle active state change. - void onActiveStateChangedLocked(const int64_t& eventTimeNs) override; - - // Internal interface to handle sliced condition change. - void onSlicedConditionMayChangeLocked(bool overallCondition, const int64_t eventTime) override; - - // Internal function to calculate the current used bytes. - size_t byteSizeLocked() const override; - - void dumpStatesLocked(FILE* out, bool verbose) const override; - - void dropDataLocked(const int64_t dropTimeNs) override; - - // Util function to flush the old packet. - void flushIfNeededLocked(const int64_t& eventTime) override; - - void flushCurrentBucketLocked(const int64_t& eventTimeNs, - const int64_t& nextBucketStartTimeNs) override; - - void prepareFirstBucketLocked() override; - - void pullAndMatchEventsLocked(const int64_t timestampNs); - - bool onConfigUpdatedLocked( - const StatsdConfig& config, const int configIndex, const int metricIndex, - const std::vector>& allAtomMatchingTrackers, - const std::unordered_map& oldAtomMatchingTrackerMap, - const std::unordered_map& newAtomMatchingTrackerMap, - const sp& matcherWizard, - const std::vector>& allConditionTrackers, - const std::unordered_map& conditionTrackerMap, - const sp& wizard, - const std::unordered_map& metricToActivationMap, - std::unordered_map>& trackerToMetricMap, - std::unordered_map>& conditionToMetricMap, - std::unordered_map>& activationAtomTrackerToMetricMap, - std::unordered_map>& deactivationAtomTrackerToMetricMap, - std::vector& metricsWithActivation) override; - - int mWhatMatcherIndex; - - sp mEventMatcherWizard; - - sp mPullerManager; - // tagId for pulled data. -1 if this is not pulled - const int mPullTagId; - - // tagId for atoms that trigger the pulling, if any - const int mTriggerAtomId; - - // tagId for output atom - const int mAtomId; - - // if this is pulled metric - const bool mIsPulled; - - // Save the past buckets and we can clear when the StatsLogReport is dumped. - std::unordered_map> mPastBuckets; - - // The current partial bucket. - std::shared_ptr mCurrentSlicedBucket; - - // The current full bucket for anomaly detection. This is updated to the latest value seen for - // this slice (ie, for partial buckets, we use the last partial bucket in this full bucket). - std::shared_ptr mCurrentSlicedBucketForAnomaly; - - const int64_t mMinBucketSizeNs; - - // Translate Atom based bucket to single numeric value bucket for anomaly and updates the map - // for each slice with the latest value. - void updateCurrentSlicedBucketForAnomaly(); - - // Allowlist of fields to report. Empty means all are reported. - std::vector mFieldMatchers; - - GaugeMetric::SamplingType mSamplingType; - - const int64_t mMaxPullDelayNs; - - // apply an allowlist on the original input - std::shared_ptr> getGaugeFields(const LogEvent& event); - - // Util function to check whether the specified dimension hits the guardrail. - bool hitGuardRailLocked(const MetricDimensionKey& newKey); - - static const size_t kBucketSize = sizeof(GaugeBucket{}); - - const size_t mDimensionSoftLimit; - - const size_t mDimensionHardLimit; - - const size_t mGaugeAtomsPerDimensionLimit; - - const bool mSplitBucketForAppUpgrade; - - FRIEND_TEST(GaugeMetricProducerTest, TestPulledEventsWithCondition); - FRIEND_TEST(GaugeMetricProducerTest, TestPulledEventsWithSlicedCondition); - FRIEND_TEST(GaugeMetricProducerTest, TestPulledEventsNoCondition); - FRIEND_TEST(GaugeMetricProducerTest, TestPulledWithAppUpgradeDisabled); - FRIEND_TEST(GaugeMetricProducerTest, TestPulledEventsAnomalyDetection); - FRIEND_TEST(GaugeMetricProducerTest, TestFirstBucket); - FRIEND_TEST(GaugeMetricProducerTest, TestPullOnTrigger); - FRIEND_TEST(GaugeMetricProducerTest, TestRemoveDimensionInOutput); - - FRIEND_TEST(GaugeMetricProducerTest_PartialBucket, TestPushedEvents); - FRIEND_TEST(GaugeMetricProducerTest_PartialBucket, TestPulled); - - FRIEND_TEST(ConfigUpdateTest, TestUpdateGaugeMetrics); -}; - -} // namespace statsd -} // namespace os -} // namespace android diff --git a/bin/src/metrics/MetricProducer.cpp b/bin/src/metrics/MetricProducer.cpp deleted file mode 100644 index c68e61e7..00000000 --- a/bin/src/metrics/MetricProducer.cpp +++ /dev/null @@ -1,356 +0,0 @@ -/* - * 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. - */ - -#define DEBUG false // STOPSHIP if true -#include "Log.h" - -#include "MetricProducer.h" - -#include "../guardrail/StatsdStats.h" -#include "metrics/parsing_utils/metrics_manager_util.h" -#include "state/StateTracker.h" - -using android::util::FIELD_COUNT_REPEATED; -using android::util::FIELD_TYPE_ENUM; -using android::util::FIELD_TYPE_INT32; -using android::util::FIELD_TYPE_INT64; -using android::util::FIELD_TYPE_MESSAGE; -using android::util::ProtoOutputStream; - -namespace android { -namespace os { -namespace statsd { - - -// for ActiveMetric -const int FIELD_ID_ACTIVE_METRIC_ID = 1; -const int FIELD_ID_ACTIVE_METRIC_ACTIVATION = 2; - -// for ActiveEventActivation -const int FIELD_ID_ACTIVE_EVENT_ACTIVATION_ATOM_MATCHER_INDEX = 1; -const int FIELD_ID_ACTIVE_EVENT_ACTIVATION_REMAINING_TTL_NANOS = 2; -const int FIELD_ID_ACTIVE_EVENT_ACTIVATION_STATE = 3; - -MetricProducer::MetricProducer( - const int64_t& metricId, const ConfigKey& key, const int64_t timeBaseNs, - const int conditionIndex, const vector& initialConditionCache, - const sp& wizard, const uint64_t protoHash, - const std::unordered_map>& eventActivationMap, - const std::unordered_map>>& - eventDeactivationMap, - const vector& slicedStateAtoms, - const unordered_map>& stateGroupMap) - : mMetricId(metricId), - mProtoHash(protoHash), - mConfigKey(key), - mValid(true), - mTimeBaseNs(timeBaseNs), - mCurrentBucketStartTimeNs(timeBaseNs), - mCurrentBucketNum(0), - mCondition(initialCondition(conditionIndex, initialConditionCache)), - mConditionTrackerIndex(conditionIndex), - mConditionSliced(false), - mWizard(wizard), - mContainANYPositionInDimensionsInWhat(false), - mSliceByPositionALL(false), - mHasLinksToAllConditionDimensionsInTracker(false), - mEventActivationMap(eventActivationMap), - mEventDeactivationMap(eventDeactivationMap), - mIsActive(mEventActivationMap.empty()), - mSlicedStateAtoms(slicedStateAtoms), - mStateGroupMap(stateGroupMap) { -} - -bool MetricProducer::onConfigUpdatedLocked( - const StatsdConfig& config, const int configIndex, const int metricIndex, - const vector>& allAtomMatchingTrackers, - const unordered_map& oldAtomMatchingTrackerMap, - const unordered_map& newAtomMatchingTrackerMap, - const sp& matcherWizard, - const vector>& allConditionTrackers, - const unordered_map& conditionTrackerMap, const sp& wizard, - const unordered_map& metricToActivationMap, - unordered_map>& trackerToMetricMap, - unordered_map>& conditionToMetricMap, - unordered_map>& activationAtomTrackerToMetricMap, - unordered_map>& deactivationAtomTrackerToMetricMap, - vector& metricsWithActivation) { - sp tmpWizard = mWizard; - mWizard = wizard; - - unordered_map> newEventActivationMap; - unordered_map>> newEventDeactivationMap; - if (!handleMetricActivationOnConfigUpdate( - config, mMetricId, metricIndex, metricToActivationMap, oldAtomMatchingTrackerMap, - newAtomMatchingTrackerMap, mEventActivationMap, activationAtomTrackerToMetricMap, - deactivationAtomTrackerToMetricMap, metricsWithActivation, newEventActivationMap, - newEventDeactivationMap)) { - return false; - } - mEventActivationMap = newEventActivationMap; - mEventDeactivationMap = newEventDeactivationMap; - mAnomalyTrackers.clear(); - return true; -} - -void MetricProducer::onMatchedLogEventLocked(const size_t matcherIndex, const LogEvent& event) { - if (!mIsActive) { - return; - } - int64_t eventTimeNs = event.GetElapsedTimestampNs(); - // this is old event, maybe statsd restarted? - if (eventTimeNs < mTimeBaseNs) { - return; - } - - bool condition; - ConditionKey conditionKey; - if (mConditionSliced) { - for (const auto& link : mMetric2ConditionLinks) { - getDimensionForCondition(event.getValues(), link, &conditionKey[link.conditionId]); - } - auto conditionState = - mWizard->query(mConditionTrackerIndex, conditionKey, - !mHasLinksToAllConditionDimensionsInTracker); - condition = (conditionState == ConditionState::kTrue); - } else { - // TODO: The unknown condition state is not handled here, we should fix it. - condition = mCondition == ConditionState::kTrue; - } - - // Stores atom id to primary key pairs for each state atom that the metric is - // sliced by. - std::map statePrimaryKeys; - - // For states with primary fields, use MetricStateLinks to get the primary - // field values from the log event. These values will form a primary key - // that will be used to query StateTracker for the correct state value. - for (const auto& stateLink : mMetric2StateLinks) { - getDimensionForState(event.getValues(), stateLink, - &statePrimaryKeys[stateLink.stateAtomId]); - } - - // For each sliced state, query StateTracker for the state value using - // either the primary key from the previous step or the DEFAULT_DIMENSION_KEY. - // - // Expected functionality: for any case where the MetricStateLinks are - // initialized incorrectly (ex. # of state links != # of primary fields, no - // links are provided for a state with primary fields, links are provided - // in the wrong order, etc.), StateTracker will simply return kStateUnknown - // when queried using an incorrect key. - HashableDimensionKey stateValuesKey; - for (auto atomId : mSlicedStateAtoms) { - FieldValue value; - if (statePrimaryKeys.find(atomId) != statePrimaryKeys.end()) { - // found a primary key for this state, query using the key - queryStateValue(atomId, statePrimaryKeys[atomId], &value); - } else { - // if no MetricStateLinks exist for this state atom, - // query using the default dimension key (empty HashableDimensionKey) - queryStateValue(atomId, DEFAULT_DIMENSION_KEY, &value); - } - mapStateValue(atomId, &value); - stateValuesKey.addValue(value); - } - - HashableDimensionKey dimensionInWhat; - filterValues(mDimensionsInWhat, event.getValues(), &dimensionInWhat); - MetricDimensionKey metricKey(dimensionInWhat, stateValuesKey); - onMatchedLogEventInternalLocked(matcherIndex, metricKey, conditionKey, condition, event, - statePrimaryKeys); -} - -bool MetricProducer::evaluateActiveStateLocked(int64_t elapsedTimestampNs) { - bool isActive = mEventActivationMap.empty(); - for (auto& it : mEventActivationMap) { - if (it.second->state == ActivationState::kActive && - elapsedTimestampNs > it.second->ttl_ns + it.second->start_ns) { - it.second->state = ActivationState::kNotActive; - } - if (it.second->state == ActivationState::kActive) { - isActive = true; - } - } - return isActive; -} - -void MetricProducer::flushIfExpire(int64_t elapsedTimestampNs) { - std::lock_guard lock(mMutex); - if (!mIsActive) { - return; - } - mIsActive = evaluateActiveStateLocked(elapsedTimestampNs); - if (!mIsActive) { - onActiveStateChangedLocked(elapsedTimestampNs); - } -} - -void MetricProducer::activateLocked(int activationTrackerIndex, int64_t elapsedTimestampNs) { - auto it = mEventActivationMap.find(activationTrackerIndex); - if (it == mEventActivationMap.end()) { - return; - } - auto& activation = it->second; - if (ACTIVATE_ON_BOOT == activation->activationType) { - if (ActivationState::kNotActive == activation->state) { - activation->state = ActivationState::kActiveOnBoot; - } - // If the Activation is already active or set to kActiveOnBoot, do nothing. - return; - } - activation->start_ns = elapsedTimestampNs; - activation->state = ActivationState::kActive; - bool oldActiveState = mIsActive; - mIsActive = true; - if (!oldActiveState) { // Metric went from not active to active. - onActiveStateChangedLocked(elapsedTimestampNs); - } -} - -void MetricProducer::cancelEventActivationLocked(int deactivationTrackerIndex) { - auto it = mEventDeactivationMap.find(deactivationTrackerIndex); - if (it == mEventDeactivationMap.end()) { - return; - } - for (auto activationToCancelIt : it->second) { - activationToCancelIt->state = ActivationState::kNotActive; - } -} - -void MetricProducer::loadActiveMetricLocked(const ActiveMetric& activeMetric, - int64_t currentTimeNs) { - if (mEventActivationMap.size() == 0) { - return; - } - for (int i = 0; i < activeMetric.activation_size(); i++) { - const auto& activeEventActivation = activeMetric.activation(i); - auto it = mEventActivationMap.find(activeEventActivation.atom_matcher_index()); - if (it == mEventActivationMap.end()) { - ALOGE("Saved event activation not found"); - continue; - } - auto& activation = it->second; - // If the event activation does not have a state, assume it is active. - if (!activeEventActivation.has_state() || - activeEventActivation.state() == ActiveEventActivation::ACTIVE) { - // We don't want to change the ttl for future activations, so we set the start_ns - // such that start_ns + ttl_ns == currentTimeNs + remaining_ttl_nanos - activation->start_ns = - currentTimeNs + activeEventActivation.remaining_ttl_nanos() - activation->ttl_ns; - activation->state = ActivationState::kActive; - mIsActive = true; - } else if (activeEventActivation.state() == ActiveEventActivation::ACTIVATE_ON_BOOT) { - activation->state = ActivationState::kActiveOnBoot; - } - } -} - -void MetricProducer::writeActiveMetricToProtoOutputStream( - int64_t currentTimeNs, const DumpReportReason reason, ProtoOutputStream* proto) { - proto->write(FIELD_TYPE_INT64 | FIELD_ID_ACTIVE_METRIC_ID, (long long)mMetricId); - for (auto& it : mEventActivationMap) { - const int atom_matcher_index = it.first; - const std::shared_ptr& activation = it.second; - - if (ActivationState::kNotActive == activation->state || - (ActivationState::kActive == activation->state && - activation->start_ns + activation->ttl_ns < currentTimeNs)) { - continue; - } - - const uint64_t activationToken = proto->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | - FIELD_ID_ACTIVE_METRIC_ACTIVATION); - proto->write(FIELD_TYPE_INT32 | FIELD_ID_ACTIVE_EVENT_ACTIVATION_ATOM_MATCHER_INDEX, - atom_matcher_index); - if (ActivationState::kActive == activation->state) { - const int64_t remainingTtlNs = - activation->start_ns + activation->ttl_ns - currentTimeNs; - proto->write(FIELD_TYPE_INT64 | FIELD_ID_ACTIVE_EVENT_ACTIVATION_REMAINING_TTL_NANOS, - (long long)remainingTtlNs); - proto->write(FIELD_TYPE_ENUM | FIELD_ID_ACTIVE_EVENT_ACTIVATION_STATE, - ActiveEventActivation::ACTIVE); - - } else if (ActivationState::kActiveOnBoot == activation->state) { - if (reason == DEVICE_SHUTDOWN || reason == TERMINATION_SIGNAL_RECEIVED) { - proto->write( - FIELD_TYPE_INT64 | FIELD_ID_ACTIVE_EVENT_ACTIVATION_REMAINING_TTL_NANOS, - (long long)activation->ttl_ns); - proto->write(FIELD_TYPE_ENUM | FIELD_ID_ACTIVE_EVENT_ACTIVATION_STATE, - ActiveEventActivation::ACTIVE); - } else if (reason == STATSCOMPANION_DIED) { - // We are saving because of system server death, not due to a device shutdown. - // Next time we load, we do not want to activate metrics that activate on boot. - proto->write(FIELD_TYPE_ENUM | FIELD_ID_ACTIVE_EVENT_ACTIVATION_STATE, - ActiveEventActivation::ACTIVATE_ON_BOOT); - } - } - proto->end(activationToken); - } -} - -void MetricProducer::queryStateValue(const int32_t atomId, const HashableDimensionKey& queryKey, - FieldValue* value) { - if (!StateManager::getInstance().getStateValue(atomId, queryKey, value)) { - value->mValue = Value(StateTracker::kStateUnknown); - value->mField.setTag(atomId); - ALOGW("StateTracker not found for state atom %d", atomId); - return; - } -} - -void MetricProducer::mapStateValue(const int32_t atomId, FieldValue* value) { - // check if there is a state map for this atom - auto atomIt = mStateGroupMap.find(atomId); - if (atomIt == mStateGroupMap.end()) { - return; - } - auto valueIt = atomIt->second.find(value->mValue.int_value); - if (valueIt == atomIt->second.end()) { - // state map exists, but value was not put in a state group - // so set mValue to kStateUnknown - // TODO(tsaichristine): handle incomplete state maps - value->mValue.setInt(StateTracker::kStateUnknown); - } else { - // set mValue to group_id - value->mValue.setLong(valueIt->second); - } -} - -HashableDimensionKey MetricProducer::getUnknownStateKey() { - HashableDimensionKey stateKey; - for (auto atom : mSlicedStateAtoms) { - FieldValue fieldValue; - fieldValue.mField.setTag(atom); - fieldValue.mValue.setInt(StateTracker::kStateUnknown); - stateKey.addValue(fieldValue); - } - return stateKey; -} - -DropEvent MetricProducer::buildDropEvent(const int64_t dropTimeNs, const BucketDropReason reason) { - DropEvent event; - event.reason = reason; - event.dropTimeNs = dropTimeNs; - return event; -} - -bool MetricProducer::maxDropEventsReached() { - return mCurrentSkippedBucket.dropEvents.size() >= StatsdStats::kMaxLoggedBucketDropEvents; -} - -} // namespace statsd -} // namespace os -} // namespace android diff --git a/bin/src/metrics/MetricProducer.h b/bin/src/metrics/MetricProducer.h deleted file mode 100644 index 44b364ac..00000000 --- a/bin/src/metrics/MetricProducer.h +++ /dev/null @@ -1,593 +0,0 @@ -/* - * 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. - */ - -#ifndef METRIC_PRODUCER_H -#define METRIC_PRODUCER_H - -#include -#include - -#include - -#include "HashableDimensionKey.h" -#include "anomaly/AnomalyTracker.h" -#include "condition/ConditionWizard.h" -#include "config/ConfigKey.h" -#include "matchers/EventMatcherWizard.h" -#include "matchers/matcher_util.h" -#include "packages/PackageInfoListener.h" -#include "state/StateListener.h" -#include "state/StateManager.h" - -namespace android { -namespace os { -namespace statsd { - -// Keep this in sync with DumpReportReason enum in stats_log.proto -enum DumpReportReason { - DEVICE_SHUTDOWN = 1, - CONFIG_UPDATED = 2, - CONFIG_REMOVED = 3, - GET_DATA_CALLED = 4, - ADB_DUMP = 5, - CONFIG_RESET = 6, - STATSCOMPANION_DIED = 7, - TERMINATION_SIGNAL_RECEIVED = 8 -}; - -// If the metric has no activation requirement, it will be active once the metric producer is -// created. -// If the metric needs to be activated by atoms, the metric producer will start -// with kNotActive state, turn to kActive or kActiveOnBoot when the activation event arrives, become -// kNotActive when it reaches the duration limit (timebomb). If the activation event arrives again -// before or after it expires, the event producer will be re-activated and ttl will be reset. -enum ActivationState { - kNotActive = 0, - kActive = 1, - kActiveOnBoot = 2, -}; - -enum DumpLatency { - // In some cases, we only have a short time range to do the dump, e.g. statsd is being killed. - // We might be able to return all the data in this mode. For instance, pull metrics might need - // to be pulled when the current bucket is requested. - FAST = 1, - // In other cases, it is fine for a dump to take more than a few milliseconds, e.g. config - // updates. - NO_TIME_CONSTRAINTS = 2 -}; - -// Keep this in sync with BucketDropReason enum in stats_log.proto -enum BucketDropReason { - // For ValueMetric, a bucket is dropped during a dump report request iff - // current bucket should be included, a pull is needed (pulled metric and - // condition is true), and we are under fast time constraints. - DUMP_REPORT_REQUESTED = 1, - EVENT_IN_WRONG_BUCKET = 2, - CONDITION_UNKNOWN = 3, - PULL_FAILED = 4, - PULL_DELAYED = 5, - DIMENSION_GUARDRAIL_REACHED = 6, - MULTIPLE_BUCKETS_SKIPPED = 7, - // Not an invalid bucket case, but the bucket is dropped. - BUCKET_TOO_SMALL = 8, - // Not an invalid bucket case, but the bucket is skipped. - NO_DATA = 9 -}; - -enum MetricType { - METRIC_TYPE_EVENT = 1, - METRIC_TYPE_COUNT = 2, - METRIC_TYPE_DURATION = 3, - METRIC_TYPE_GAUGE = 4, - METRIC_TYPE_VALUE = 5, -}; -struct Activation { - Activation(const ActivationType& activationType, const int64_t ttlNs) - : ttl_ns(ttlNs), - start_ns(0), - state(ActivationState::kNotActive), - activationType(activationType) {} - - const int64_t ttl_ns; - int64_t start_ns; - ActivationState state; - const ActivationType activationType; -}; - -struct DropEvent { - // Reason for dropping the bucket and/or marking the bucket invalid. - BucketDropReason reason; - // The timestamp of the drop event. - int64_t dropTimeNs; -}; - -struct SkippedBucket { - // Start time of the dropped bucket. - int64_t bucketStartTimeNs; - // End time of the dropped bucket. - int64_t bucketEndTimeNs; - // List of events that invalidated this bucket. - std::vector dropEvents; - - void reset() { - bucketStartTimeNs = 0; - bucketEndTimeNs = 0; - dropEvents.clear(); - } -}; - -// A MetricProducer is responsible for compute one single metrics, creating stats log report, and -// writing the report to dropbox. MetricProducers should respond to package changes as required in -// PackageInfoListener, but if none of the metrics are slicing by package name, then the update can -// be a no-op. -class MetricProducer : public virtual android::RefBase, public virtual StateListener { -public: - MetricProducer(const int64_t& metricId, const ConfigKey& key, const int64_t timeBaseNs, - const int conditionIndex, const vector& initialConditionCache, - const sp& wizard, const uint64_t protoHash, - const std::unordered_map>& eventActivationMap, - const std::unordered_map>>& - eventDeactivationMap, - const vector& slicedStateAtoms, - const unordered_map>& stateGroupMap); - - virtual ~MetricProducer(){}; - - ConditionState initialCondition(const int conditionIndex, - const vector& initialConditionCache) const { - return conditionIndex >= 0 ? initialConditionCache[conditionIndex] : ConditionState::kTrue; - } - - // Update appropriate state on config updates. Primarily, all indices need to be updated. - // This metric and all of its dependencies are guaranteed to be preserved across the update. - // This function also updates several maps used by metricsManager. - // This function clears all anomaly trackers. All anomaly trackers need to be added again. - bool onConfigUpdated( - const StatsdConfig& config, const int configIndex, const int metricIndex, - const std::vector>& allAtomMatchingTrackers, - const std::unordered_map& oldAtomMatchingTrackerMap, - const std::unordered_map& newAtomMatchingTrackerMap, - const sp& matcherWizard, - const std::vector>& allConditionTrackers, - const std::unordered_map& conditionTrackerMap, - const sp& wizard, - const std::unordered_map& metricToActivationMap, - std::unordered_map>& trackerToMetricMap, - std::unordered_map>& conditionToMetricMap, - std::unordered_map>& activationAtomTrackerToMetricMap, - std::unordered_map>& deactivationAtomTrackerToMetricMap, - std::vector& metricsWithActivation) { - std::lock_guard lock(mMutex); - return onConfigUpdatedLocked(config, configIndex, metricIndex, allAtomMatchingTrackers, - oldAtomMatchingTrackerMap, newAtomMatchingTrackerMap, - matcherWizard, allConditionTrackers, conditionTrackerMap, - wizard, metricToActivationMap, trackerToMetricMap, - conditionToMetricMap, activationAtomTrackerToMetricMap, - deactivationAtomTrackerToMetricMap, metricsWithActivation); - }; - - /** - * Force a partial bucket split on app upgrade - */ - virtual void notifyAppUpgrade(const int64_t& eventTimeNs) { - std::lock_guard lock(mMutex); - flushLocked(eventTimeNs); - }; - - void notifyAppRemoved(const int64_t& eventTimeNs) { - // Force buckets to split on removal also. - notifyAppUpgrade(eventTimeNs); - }; - - /** - * Force a partial bucket split on boot complete. - */ - virtual void onStatsdInitCompleted(const int64_t& eventTimeNs) { - std::lock_guard lock(mMutex); - flushLocked(eventTimeNs); - } - // Consume the parsed stats log entry that already matched the "what" of the metric. - void onMatchedLogEvent(const size_t matcherIndex, const LogEvent& event) { - std::lock_guard lock(mMutex); - onMatchedLogEventLocked(matcherIndex, event); - } - - void onConditionChanged(const bool condition, const int64_t eventTime) { - std::lock_guard lock(mMutex); - onConditionChangedLocked(condition, eventTime); - } - - void onSlicedConditionMayChange(bool overallCondition, const int64_t eventTime) { - std::lock_guard lock(mMutex); - onSlicedConditionMayChangeLocked(overallCondition, eventTime); - } - - bool isConditionSliced() const { - std::lock_guard lock(mMutex); - return mConditionSliced; - }; - - void onStateChanged(const int64_t eventTimeNs, const int32_t atomId, - const HashableDimensionKey& primaryKey, const FieldValue& oldState, - const FieldValue& newState){}; - - // Output the metrics data to [protoOutput]. All metrics reports end with the same timestamp. - // This method clears all the past buckets. - void onDumpReport(const int64_t dumpTimeNs, - const bool include_current_partial_bucket, - const bool erase_data, - const DumpLatency dumpLatency, - std::set *str_set, - android::util::ProtoOutputStream* protoOutput) { - std::lock_guard lock(mMutex); - return onDumpReportLocked(dumpTimeNs, include_current_partial_bucket, erase_data, - dumpLatency, str_set, protoOutput); - } - - virtual bool onConfigUpdatedLocked( - const StatsdConfig& config, const int configIndex, const int metricIndex, - const std::vector>& allAtomMatchingTrackers, - const std::unordered_map& oldAtomMatchingTrackerMap, - const std::unordered_map& newAtomMatchingTrackerMap, - const sp& matcherWizard, - const std::vector>& allConditionTrackers, - const std::unordered_map& conditionTrackerMap, - const sp& wizard, - const std::unordered_map& metricToActivationMap, - std::unordered_map>& trackerToMetricMap, - std::unordered_map>& conditionToMetricMap, - std::unordered_map>& activationAtomTrackerToMetricMap, - std::unordered_map>& deactivationAtomTrackerToMetricMap, - std::vector& metricsWithActivation); - - void clearPastBuckets(const int64_t dumpTimeNs) { - std::lock_guard lock(mMutex); - return clearPastBucketsLocked(dumpTimeNs); - } - - void prepareFirstBucket() { - std::lock_guard lock(mMutex); - prepareFirstBucketLocked(); - } - - // Returns the memory in bytes currently used to store this metric's data. Does not change - // state. - size_t byteSize() const { - std::lock_guard lock(mMutex); - return byteSizeLocked(); - } - - void dumpStates(FILE* out, bool verbose) const { - std::lock_guard lock(mMutex); - dumpStatesLocked(out, verbose); - } - - // Let MetricProducer drop in-memory data to save memory. - // We still need to keep future data valid and anomaly tracking work, which means we will - // have to flush old data, informing anomaly trackers then safely drop old data. - // We still keep current bucket data for future metrics' validity. - void dropData(const int64_t dropTimeNs) { - std::lock_guard lock(mMutex); - dropDataLocked(dropTimeNs); - } - - void loadActiveMetric(const ActiveMetric& activeMetric, int64_t currentTimeNs) { - std::lock_guard lock(mMutex); - loadActiveMetricLocked(activeMetric, currentTimeNs); - } - - void activate(int activationTrackerIndex, int64_t elapsedTimestampNs) { - std::lock_guard lock(mMutex); - activateLocked(activationTrackerIndex, elapsedTimestampNs); - } - - void cancelEventActivation(int deactivationTrackerIndex) { - std::lock_guard lock(mMutex); - cancelEventActivationLocked(deactivationTrackerIndex); - } - - bool isActive() const { - std::lock_guard lock(mMutex); - return isActiveLocked(); - } - - void flushIfExpire(int64_t elapsedTimestampNs); - - void writeActiveMetricToProtoOutputStream( - int64_t currentTimeNs, const DumpReportReason reason, ProtoOutputStream* proto); - - // Start: getters/setters - inline int64_t getMetricId() const { - return mMetricId; - } - - inline uint64_t getProtoHash() const { - return mProtoHash; - } - - virtual MetricType getMetricType() const = 0; - - // For test only. - inline int64_t getCurrentBucketNum() const { - return mCurrentBucketNum; - } - - int64_t getBucketSizeInNs() const { - std::lock_guard lock(mMutex); - return mBucketSizeNs; - } - - inline const std::vector getSlicedStateAtoms() { - std::lock_guard lock(mMutex); - return mSlicedStateAtoms; - } - - inline bool isValid() const { - return mValid; - } - - /* Adds an AnomalyTracker and returns it. */ - virtual sp addAnomalyTracker(const Alert& alert, - const sp& anomalyAlarmMonitor, - const UpdateStatus& updateStatus, - const int64_t updateTimeNs) { - std::lock_guard lock(mMutex); - sp anomalyTracker = new AnomalyTracker(alert, mConfigKey); - mAnomalyTrackers.push_back(anomalyTracker); - return anomalyTracker; - } - - /* Adds an AnomalyTracker that has already been created */ - virtual void addAnomalyTracker(sp& anomalyTracker, const int64_t updateTimeNs) { - std::lock_guard lock(mMutex); - mAnomalyTrackers.push_back(anomalyTracker); - } - // End: getters/setters -protected: - /** - * Flushes the current bucket if the eventTime is after the current bucket's end time. - */ - virtual void flushIfNeededLocked(const int64_t& eventTime){}; - - /** - * For metrics that aggregate (ie, every metric producer except for EventMetricProducer), - * we need to be able to flush the current buckets on demand (ie, end the current bucket and - * start new bucket). If this function is called when eventTimeNs is greater than the current - * bucket's end timestamp, than we flush up to the end of the latest full bucket; otherwise, - * we assume that we want to flush a partial bucket. The bucket start timestamp and bucket - * number are not changed by this function. This method should only be called by - * flushIfNeededLocked or flushLocked or the app upgrade handler; the caller MUST update the - * bucket timestamp and bucket number as needed. - */ - virtual void flushCurrentBucketLocked(const int64_t& eventTimeNs, - const int64_t& nextBucketStartTimeNs) {}; - - /** - * Flushes all the data including the current partial bucket. - */ - virtual void flushLocked(const int64_t& eventTimeNs) { - flushIfNeededLocked(eventTimeNs); - flushCurrentBucketLocked(eventTimeNs, eventTimeNs); - }; - - /* - * Individual metrics can implement their own business logic here. All pre-processing is done. - * - * [matcherIndex]: the index of the matcher which matched this event. This is interesting to - * DurationMetric, because it has start/stop/stop_all 3 matchers. - * [eventKey]: the extracted dimension key for the final output. if the metric doesn't have - * dimensions, it will be DEFAULT_DIMENSION_KEY - * [conditionKey]: the keys of conditions which should be used to query the condition for this - * target event (from MetricConditionLink). This is passed to individual metrics - * because DurationMetric needs it to be cached. - * [condition]: whether condition is met. If condition is sliced, this is the result coming from - * query with ConditionWizard; If condition is not sliced, this is the - * nonSlicedCondition. - * [event]: the log event, just in case the metric needs its data, e.g., EventMetric. - */ - virtual void onMatchedLogEventInternalLocked( - const size_t matcherIndex, const MetricDimensionKey& eventKey, - const ConditionKey& conditionKey, bool condition, const LogEvent& event, - const map& statePrimaryKeys) = 0; - - // Consume the parsed stats log entry that already matched the "what" of the metric. - virtual void onMatchedLogEventLocked(const size_t matcherIndex, const LogEvent& event); - virtual void onConditionChangedLocked(const bool condition, const int64_t eventTime) = 0; - virtual void onSlicedConditionMayChangeLocked(bool overallCondition, - const int64_t eventTime) = 0; - virtual void onDumpReportLocked(const int64_t dumpTimeNs, - const bool include_current_partial_bucket, - const bool erase_data, - const DumpLatency dumpLatency, - std::set *str_set, - android::util::ProtoOutputStream* protoOutput) = 0; - virtual void clearPastBucketsLocked(const int64_t dumpTimeNs) = 0; - virtual void prepareFirstBucketLocked(){}; - virtual size_t byteSizeLocked() const = 0; - virtual void dumpStatesLocked(FILE* out, bool verbose) const = 0; - virtual void dropDataLocked(const int64_t dropTimeNs) = 0; - void loadActiveMetricLocked(const ActiveMetric& activeMetric, int64_t currentTimeNs); - void activateLocked(int activationTrackerIndex, int64_t elapsedTimestampNs); - void cancelEventActivationLocked(int deactivationTrackerIndex); - - bool evaluateActiveStateLocked(int64_t elapsedTimestampNs); - - virtual void onActiveStateChangedLocked(const int64_t& eventTimeNs) { - if (!mIsActive) { - flushLocked(eventTimeNs); - } - } - - inline bool isActiveLocked() const { - return mIsActive; - } - - // Convenience to compute the current bucket's end time, which is always aligned with the - // start time of the metric. - int64_t getCurrentBucketEndTimeNs() const { - return mTimeBaseNs + (mCurrentBucketNum + 1) * mBucketSizeNs; - } - - int64_t getBucketNumFromEndTimeNs(const int64_t endNs) { - return (endNs - mTimeBaseNs) / mBucketSizeNs - 1; - } - - // Query StateManager for original state value using the queryKey. - // The field and value are output. - void queryStateValue(const int32_t atomId, const HashableDimensionKey& queryKey, - FieldValue* value); - - // If a state map exists for the given atom, replace the original state - // value with the group id mapped to the value. - // If no state map exists, keep the original state value. - void mapStateValue(const int32_t atomId, FieldValue* value); - - // Returns a HashableDimensionKey with unknown state value for each state - // atom. - HashableDimensionKey getUnknownStateKey(); - - DropEvent buildDropEvent(const int64_t dropTimeNs, const BucketDropReason reason); - - // Returns true if the number of drop events in the current bucket has - // exceeded the maximum number allowed, which is currently capped at 10. - bool maxDropEventsReached(); - - const int64_t mMetricId; - - // Hash of the Metric's proto bytes from StatsdConfig, including any activations. - // Used to determine if the definition of this metric has changed across a config update. - const uint64_t mProtoHash; - - const ConfigKey mConfigKey; - - bool mValid; - - // The time when this metric producer was first created. The end time for the current bucket - // can be computed from this based on mCurrentBucketNum. - int64_t mTimeBaseNs; - - // Start time may not be aligned with the start of statsd if there is an app upgrade in the - // middle of a bucket. - int64_t mCurrentBucketStartTimeNs; - - // Used by anomaly detector to track which bucket we are in. This is not sent with the produced - // report. - int64_t mCurrentBucketNum; - - int64_t mBucketSizeNs; - - ConditionState mCondition; - - int mConditionTrackerIndex; - - bool mConditionSliced; - - sp mWizard; - - bool mContainANYPositionInDimensionsInWhat; - - bool mSliceByPositionALL; - - vector mDimensionsInWhat; // The dimensions_in_what defined in statsd_config - - // True iff the metric to condition links cover all dimension fields in the condition tracker. - // This field is always false for combinational condition trackers. - bool mHasLinksToAllConditionDimensionsInTracker; - - std::vector mMetric2ConditionLinks; - - std::vector> mAnomalyTrackers; - - mutable std::mutex mMutex; - - // When the metric producer has multiple activations, these activations are ORed to determine - // whether the metric producer is ready to generate metrics. - std::unordered_map> mEventActivationMap; - - // Maps index of atom matcher for deactivation to a list of Activation structs. - std::unordered_map>> mEventDeactivationMap; - - bool mIsActive; - - // The slice_by_state atom ids defined in statsd_config. - const std::vector mSlicedStateAtoms; - - // Maps atom ids and state values to group_ids (>). - const std::unordered_map> mStateGroupMap; - - // MetricStateLinks defined in statsd_config that link fields in the state - // atom to fields in the "what" atom. - std::vector mMetric2StateLinks; - - optional mUploadThreshold; - - SkippedBucket mCurrentSkippedBucket; - // Buckets that were invalidated and had their data dropped. - std::vector mSkippedBuckets; - - FRIEND_TEST(CountMetricE2eTest, TestSlicedState); - FRIEND_TEST(CountMetricE2eTest, TestSlicedStateWithMap); - FRIEND_TEST(CountMetricE2eTest, TestMultipleSlicedStates); - FRIEND_TEST(CountMetricE2eTest, TestSlicedStateWithPrimaryFields); - FRIEND_TEST(CountMetricE2eTest, TestInitialConditionChanges); - - FRIEND_TEST(DurationMetricE2eTest, TestOneBucket); - FRIEND_TEST(DurationMetricE2eTest, TestTwoBuckets); - FRIEND_TEST(DurationMetricE2eTest, TestWithActivation); - FRIEND_TEST(DurationMetricE2eTest, TestWithCondition); - FRIEND_TEST(DurationMetricE2eTest, TestWithSlicedCondition); - FRIEND_TEST(DurationMetricE2eTest, TestWithActivationAndSlicedCondition); - FRIEND_TEST(DurationMetricE2eTest, TestWithSlicedState); - FRIEND_TEST(DurationMetricE2eTest, TestWithConditionAndSlicedState); - FRIEND_TEST(DurationMetricE2eTest, TestWithSlicedStateMapped); - FRIEND_TEST(DurationMetricE2eTest, TestSlicedStatePrimaryFieldsNotSubsetDimInWhat); - FRIEND_TEST(DurationMetricE2eTest, TestWithSlicedStatePrimaryFieldsSubset); - FRIEND_TEST(DurationMetricE2eTest, TestUploadThreshold); - - FRIEND_TEST(MetricActivationE2eTest, TestCountMetric); - FRIEND_TEST(MetricActivationE2eTest, TestCountMetricWithOneDeactivation); - FRIEND_TEST(MetricActivationE2eTest, TestCountMetricWithTwoDeactivations); - FRIEND_TEST(MetricActivationE2eTest, TestCountMetricWithSameDeactivation); - FRIEND_TEST(MetricActivationE2eTest, TestCountMetricWithTwoMetricsTwoDeactivations); - - FRIEND_TEST(StatsLogProcessorTest, TestActiveConfigMetricDiskWriteRead); - FRIEND_TEST(StatsLogProcessorTest, TestActivationOnBoot); - FRIEND_TEST(StatsLogProcessorTest, TestActivationOnBootMultipleActivations); - FRIEND_TEST(StatsLogProcessorTest, - TestActivationOnBootMultipleActivationsDifferentActivationTypes); - FRIEND_TEST(StatsLogProcessorTest, TestActivationsPersistAcrossSystemServerRestart); - - FRIEND_TEST(ValueMetricE2eTest, TestInitWithSlicedState); - FRIEND_TEST(ValueMetricE2eTest, TestInitWithSlicedState_WithDimensions); - FRIEND_TEST(ValueMetricE2eTest, TestInitWithSlicedState_WithIncorrectDimensions); - FRIEND_TEST(ValueMetricE2eTest, TestInitialConditionChanges); - - FRIEND_TEST(MetricsManagerTest, TestInitialConditions); - - FRIEND_TEST(ConfigUpdateTest, TestUpdateMetricActivations); - FRIEND_TEST(ConfigUpdateTest, TestUpdateCountMetrics); - FRIEND_TEST(ConfigUpdateTest, TestUpdateEventMetrics); - FRIEND_TEST(ConfigUpdateTest, TestUpdateGaugeMetrics); - FRIEND_TEST(ConfigUpdateTest, TestUpdateDurationMetrics); - FRIEND_TEST(ConfigUpdateTest, TestUpdateMetricsMultipleTypes); - FRIEND_TEST(ConfigUpdateTest, TestUpdateAlerts); -}; - -} // namespace statsd -} // namespace os -} // namespace android -#endif // METRIC_PRODUCER_H diff --git a/bin/src/metrics/MetricsManager.cpp b/bin/src/metrics/MetricsManager.cpp deleted file mode 100644 index f9b0a103..00000000 --- a/bin/src/metrics/MetricsManager.cpp +++ /dev/null @@ -1,773 +0,0 @@ -/* - * 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. - */ -#define DEBUG false // STOPSHIP if true -#include "Log.h" - -#include "MetricsManager.h" - -#include - -#include "CountMetricProducer.h" -#include "condition/CombinationConditionTracker.h" -#include "condition/SimpleConditionTracker.h" -#include "guardrail/StatsdStats.h" -#include "matchers/CombinationAtomMatchingTracker.h" -#include "matchers/SimpleAtomMatchingTracker.h" -#include "parsing_utils/config_update_utils.h" -#include "parsing_utils/metrics_manager_util.h" -#include "state/StateManager.h" -#include "stats_log_util.h" -#include "stats_util.h" -#include "statslog_statsd.h" - -using android::util::FIELD_COUNT_REPEATED; -using android::util::FIELD_TYPE_INT32; -using android::util::FIELD_TYPE_INT64; -using android::util::FIELD_TYPE_MESSAGE; -using android::util::FIELD_TYPE_STRING; -using android::util::ProtoOutputStream; - -using std::set; -using std::string; -using std::vector; - -namespace android { -namespace os { -namespace statsd { - -const int FIELD_ID_METRICS = 1; -const int FIELD_ID_ANNOTATIONS = 7; -const int FIELD_ID_ANNOTATIONS_INT64 = 1; -const int FIELD_ID_ANNOTATIONS_INT32 = 2; - -// for ActiveConfig -const int FIELD_ID_ACTIVE_CONFIG_ID = 1; -const int FIELD_ID_ACTIVE_CONFIG_UID = 2; -const int FIELD_ID_ACTIVE_CONFIG_METRIC = 3; - -MetricsManager::MetricsManager(const ConfigKey& key, const StatsdConfig& config, - const int64_t timeBaseNs, const int64_t currentTimeNs, - const sp& uidMap, - const sp& pullerManager, - const sp& anomalyAlarmMonitor, - const sp& periodicAlarmMonitor) - : mConfigKey(key), - mUidMap(uidMap), - mTtlNs(config.has_ttl_in_seconds() ? config.ttl_in_seconds() * NS_PER_SEC : -1), - mTtlEndNs(-1), - mLastReportTimeNs(currentTimeNs), - mLastReportWallClockNs(getWallClockNs()), - mPullerManager(pullerManager), - mWhitelistedAtomIds(config.whitelisted_atom_ids().begin(), - config.whitelisted_atom_ids().end()), - mShouldPersistHistory(config.persist_locally()) { - // Init the ttl end timestamp. - refreshTtl(timeBaseNs); - - mConfigValid = initStatsdConfig( - key, config, uidMap, pullerManager, anomalyAlarmMonitor, periodicAlarmMonitor, - timeBaseNs, currentTimeNs, mTagIds, mAllAtomMatchingTrackers, mAtomMatchingTrackerMap, - mAllConditionTrackers, mConditionTrackerMap, mAllMetricProducers, mMetricProducerMap, - mAllAnomalyTrackers, mAllPeriodicAlarmTrackers, mConditionToMetricMap, - mTrackerToMetricMap, mTrackerToConditionMap, mActivationAtomTrackerToMetricMap, - mDeactivationAtomTrackerToMetricMap, mAlertTrackerMap, mMetricIndexesWithActivation, - mStateProtoHashes, mNoReportMetricIds); - - mHashStringsInReport = config.hash_strings_in_metric_report(); - mVersionStringsInReport = config.version_strings_in_metric_report(); - mInstallerInReport = config.installer_in_metric_report(); - - createAllLogSourcesFromConfig(config); - mPullerManager->RegisterPullUidProvider(mConfigKey, this); - - // Store the sub-configs used. - for (const auto& annotation : config.annotation()) { - mAnnotations.emplace_back(annotation.field_int64(), annotation.field_int32()); - } - verifyGuardrailsAndUpdateStatsdStats(); - initializeConfigActiveStatus(); -} - -MetricsManager::~MetricsManager() { - for (auto it : mAllMetricProducers) { - for (int atomId : it->getSlicedStateAtoms()) { - StateManager::getInstance().unregisterListener(atomId, it); - } - } - mPullerManager->UnregisterPullUidProvider(mConfigKey, this); - - VLOG("~MetricsManager()"); -} - -bool MetricsManager::updateConfig(const StatsdConfig& config, const int64_t timeBaseNs, - const int64_t currentTimeNs, - const sp& anomalyAlarmMonitor, - const sp& periodicAlarmMonitor) { - vector> newAtomMatchingTrackers; - unordered_map newAtomMatchingTrackerMap; - vector> newConditionTrackers; - unordered_map newConditionTrackerMap; - map newStateProtoHashes; - vector> newMetricProducers; - unordered_map newMetricProducerMap; - vector> newAnomalyTrackers; - unordered_map newAlertTrackerMap; - vector> newPeriodicAlarmTrackers; - mTagIds.clear(); - mConditionToMetricMap.clear(); - mTrackerToMetricMap.clear(); - mTrackerToConditionMap.clear(); - mActivationAtomTrackerToMetricMap.clear(); - mDeactivationAtomTrackerToMetricMap.clear(); - mMetricIndexesWithActivation.clear(); - mNoReportMetricIds.clear(); - mConfigValid = updateStatsdConfig( - mConfigKey, config, mUidMap, mPullerManager, anomalyAlarmMonitor, periodicAlarmMonitor, - timeBaseNs, currentTimeNs, mAllAtomMatchingTrackers, mAtomMatchingTrackerMap, - mAllConditionTrackers, mConditionTrackerMap, mAllMetricProducers, mMetricProducerMap, - mAllAnomalyTrackers, mAlertTrackerMap, mStateProtoHashes, mTagIds, - newAtomMatchingTrackers, newAtomMatchingTrackerMap, newConditionTrackers, - newConditionTrackerMap, newMetricProducers, newMetricProducerMap, newAnomalyTrackers, - newAlertTrackerMap, newPeriodicAlarmTrackers, mConditionToMetricMap, - mTrackerToMetricMap, mTrackerToConditionMap, mActivationAtomTrackerToMetricMap, - mDeactivationAtomTrackerToMetricMap, mMetricIndexesWithActivation, newStateProtoHashes, - mNoReportMetricIds); - mAllAtomMatchingTrackers = newAtomMatchingTrackers; - mAtomMatchingTrackerMap = newAtomMatchingTrackerMap; - mAllConditionTrackers = newConditionTrackers; - mConditionTrackerMap = newConditionTrackerMap; - mAllMetricProducers = newMetricProducers; - mMetricProducerMap = newMetricProducerMap; - mStateProtoHashes = newStateProtoHashes; - mAllAnomalyTrackers = newAnomalyTrackers; - mAlertTrackerMap = newAlertTrackerMap; - mAllPeriodicAlarmTrackers = newPeriodicAlarmTrackers; - - mTtlNs = config.has_ttl_in_seconds() ? config.ttl_in_seconds() * NS_PER_SEC : -1; - refreshTtl(currentTimeNs); - - mHashStringsInReport = config.hash_strings_in_metric_report(); - mVersionStringsInReport = config.version_strings_in_metric_report(); - mInstallerInReport = config.installer_in_metric_report(); - mWhitelistedAtomIds.clear(); - mWhitelistedAtomIds.insert(config.whitelisted_atom_ids().begin(), - config.whitelisted_atom_ids().end()); - mShouldPersistHistory = config.persist_locally(); - - // Store the sub-configs used. - mAnnotations.clear(); - for (const auto& annotation : config.annotation()) { - mAnnotations.emplace_back(annotation.field_int64(), annotation.field_int32()); - } - - mAllowedUid.clear(); - mAllowedPkg.clear(); - mDefaultPullUids.clear(); - mPullAtomUids.clear(); - mPullAtomPackages.clear(); - createAllLogSourcesFromConfig(config); - - verifyGuardrailsAndUpdateStatsdStats(); - initializeConfigActiveStatus(); - return mConfigValid; -} - -void MetricsManager::createAllLogSourcesFromConfig(const StatsdConfig& config) { - // Init allowed pushed atom uids. - if (config.allowed_log_source_size() == 0) { - mConfigValid = false; - ALOGE("Log source allowlist is empty! This config won't get any data. Suggest adding at " - "least AID_SYSTEM and AID_STATSD to the allowed_log_source field."); - } else { - for (const auto& source : config.allowed_log_source()) { - auto it = UidMap::sAidToUidMapping.find(source); - if (it != UidMap::sAidToUidMapping.end()) { - mAllowedUid.push_back(it->second); - } else { - mAllowedPkg.push_back(source); - } - } - - if (mAllowedUid.size() + mAllowedPkg.size() > StatsdStats::kMaxLogSourceCount) { - ALOGE("Too many log sources. This is likely to be an error in the config."); - mConfigValid = false; - } else { - initAllowedLogSources(); - } - } - - // Init default allowed pull atom uids. - int numPullPackages = 0; - for (const string& pullSource : config.default_pull_packages()) { - auto it = UidMap::sAidToUidMapping.find(pullSource); - if (it != UidMap::sAidToUidMapping.end()) { - numPullPackages++; - mDefaultPullUids.insert(it->second); - } else { - ALOGE("Default pull atom packages must be in sAidToUidMapping"); - mConfigValid = false; - } - } - // Init per-atom pull atom packages. - for (const PullAtomPackages& pullAtomPackages : config.pull_atom_packages()) { - int32_t atomId = pullAtomPackages.atom_id(); - for (const string& pullPackage : pullAtomPackages.packages()) { - numPullPackages++; - auto it = UidMap::sAidToUidMapping.find(pullPackage); - if (it != UidMap::sAidToUidMapping.end()) { - mPullAtomUids[atomId].insert(it->second); - } else { - mPullAtomPackages[atomId].insert(pullPackage); - } - } - } - if (numPullPackages > StatsdStats::kMaxPullAtomPackages) { - ALOGE("Too many sources in default_pull_packages and pull_atom_packages. This is likely to " - "be an error in the config"); - mConfigValid = false; - } else { - initPullAtomSources(); - } -} - -void MetricsManager::verifyGuardrailsAndUpdateStatsdStats() { - // Guardrail. Reject the config if it's too big. - if (mAllMetricProducers.size() > StatsdStats::kMaxMetricCountPerConfig || - mAllConditionTrackers.size() > StatsdStats::kMaxConditionCountPerConfig || - mAllAtomMatchingTrackers.size() > StatsdStats::kMaxMatcherCountPerConfig) { - ALOGE("This config is too big! Reject!"); - mConfigValid = false; - } - if (mAllAnomalyTrackers.size() > StatsdStats::kMaxAlertCountPerConfig) { - ALOGE("This config has too many alerts! Reject!"); - mConfigValid = false; - } - // no matter whether this config is valid, log it in the stats. - StatsdStats::getInstance().noteConfigReceived( - mConfigKey, mAllMetricProducers.size(), mAllConditionTrackers.size(), - mAllAtomMatchingTrackers.size(), mAllAnomalyTrackers.size(), mAnnotations, - mConfigValid); -} - -void MetricsManager::initializeConfigActiveStatus() { - mIsAlwaysActive = (mMetricIndexesWithActivation.size() != mAllMetricProducers.size()) || - (mAllMetricProducers.size() == 0); - mIsActive = mIsAlwaysActive; - for (int metric : mMetricIndexesWithActivation) { - mIsActive |= mAllMetricProducers[metric]->isActive(); - } - VLOG("mIsActive is initialized to %d", mIsActive); -} - -void MetricsManager::initAllowedLogSources() { - std::lock_guard lock(mAllowedLogSourcesMutex); - mAllowedLogSources.clear(); - mAllowedLogSources.insert(mAllowedUid.begin(), mAllowedUid.end()); - - for (const auto& pkg : mAllowedPkg) { - auto uids = mUidMap->getAppUid(pkg); - mAllowedLogSources.insert(uids.begin(), uids.end()); - } - if (DEBUG) { - for (const auto& uid : mAllowedLogSources) { - VLOG("Allowed uid %d", uid); - } - } -} - -void MetricsManager::initPullAtomSources() { - std::lock_guard lock(mAllowedLogSourcesMutex); - mCombinedPullAtomUids.clear(); - for (const auto& [atomId, uids] : mPullAtomUids) { - mCombinedPullAtomUids[atomId].insert(uids.begin(), uids.end()); - } - for (const auto& [atomId, packages] : mPullAtomPackages) { - for (const string& pkg : packages) { - set uids = mUidMap->getAppUid(pkg); - mCombinedPullAtomUids[atomId].insert(uids.begin(), uids.end()); - } - } -} - -bool MetricsManager::isConfigValid() const { - return mConfigValid; -} - -void MetricsManager::notifyAppUpgrade(const int64_t& eventTimeNs, const string& apk, const int uid, - const int64_t version) { - // Inform all metric producers. - for (const auto& it : mAllMetricProducers) { - it->notifyAppUpgrade(eventTimeNs); - } - // check if we care this package - if (std::find(mAllowedPkg.begin(), mAllowedPkg.end(), apk) != mAllowedPkg.end()) { - // We will re-initialize the whole list because we don't want to keep the multi mapping of - // UID<->pkg inside MetricsManager to reduce the memory usage. - initAllowedLogSources(); - } - - for (const auto& it : mPullAtomPackages) { - if (it.second.find(apk) != it.second.end()) { - initPullAtomSources(); - return; - } - } -} - -void MetricsManager::notifyAppRemoved(const int64_t& eventTimeNs, const string& apk, - const int uid) { - // Inform all metric producers. - for (const auto& it : mAllMetricProducers) { - it->notifyAppRemoved(eventTimeNs); - } - // check if we care this package - if (std::find(mAllowedPkg.begin(), mAllowedPkg.end(), apk) != mAllowedPkg.end()) { - // We will re-initialize the whole list because we don't want to keep the multi mapping of - // UID<->pkg inside MetricsManager to reduce the memory usage. - initAllowedLogSources(); - } - - for (const auto& it : mPullAtomPackages) { - if (it.second.find(apk) != it.second.end()) { - initPullAtomSources(); - return; - } - } -} - -void MetricsManager::onUidMapReceived(const int64_t& eventTimeNs) { - // Purposefully don't inform metric producers on a new snapshot - // because we don't need to flush partial buckets. - // This occurs if a new user is added/removed or statsd crashes. - initPullAtomSources(); - - if (mAllowedPkg.size() == 0) { - return; - } - initAllowedLogSources(); -} - -void MetricsManager::onStatsdInitCompleted(const int64_t& eventTimeNs) { - // Inform all metric producers. - for (const auto& it : mAllMetricProducers) { - it->onStatsdInitCompleted(eventTimeNs); - } -} - -void MetricsManager::init() { - for (const auto& producer : mAllMetricProducers) { - producer->prepareFirstBucket(); - } -} - -vector MetricsManager::getPullAtomUids(int32_t atomId) { - std::lock_guard lock(mAllowedLogSourcesMutex); - vector uids; - const auto& it = mCombinedPullAtomUids.find(atomId); - if (it != mCombinedPullAtomUids.end()) { - uids.insert(uids.end(), it->second.begin(), it->second.end()); - } - uids.insert(uids.end(), mDefaultPullUids.begin(), mDefaultPullUids.end()); - return uids; -} - -void MetricsManager::dumpStates(FILE* out, bool verbose) { - fprintf(out, "ConfigKey %s, allowed source:", mConfigKey.ToString().c_str()); - { - std::lock_guard lock(mAllowedLogSourcesMutex); - for (const auto& source : mAllowedLogSources) { - fprintf(out, "%d ", source); - } - } - fprintf(out, "\n"); - for (const auto& producer : mAllMetricProducers) { - producer->dumpStates(out, verbose); - } -} - -void MetricsManager::dropData(const int64_t dropTimeNs) { - for (const auto& producer : mAllMetricProducers) { - producer->dropData(dropTimeNs); - } -} - -void MetricsManager::onDumpReport(const int64_t dumpTimeStampNs, - const bool include_current_partial_bucket, - const bool erase_data, - const DumpLatency dumpLatency, - std::set *str_set, - ProtoOutputStream* protoOutput) { - VLOG("=========================Metric Reports Start=========================="); - // one StatsLogReport per MetricProduer - for (const auto& producer : mAllMetricProducers) { - if (mNoReportMetricIds.find(producer->getMetricId()) == mNoReportMetricIds.end()) { - uint64_t token = protoOutput->start( - FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_METRICS); - if (mHashStringsInReport) { - producer->onDumpReport(dumpTimeStampNs, include_current_partial_bucket, erase_data, - dumpLatency, str_set, protoOutput); - } else { - producer->onDumpReport(dumpTimeStampNs, include_current_partial_bucket, erase_data, - dumpLatency, nullptr, protoOutput); - } - protoOutput->end(token); - } else { - producer->clearPastBuckets(dumpTimeStampNs); - } - } - for (const auto& annotation : mAnnotations) { - uint64_t token = protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | - FIELD_ID_ANNOTATIONS); - protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_ANNOTATIONS_INT64, - (long long)annotation.first); - protoOutput->write(FIELD_TYPE_INT32 | FIELD_ID_ANNOTATIONS_INT32, annotation.second); - protoOutput->end(token); - } - - // Do not update the timestamps when data is not cleared to avoid timestamps from being - // misaligned. - if (erase_data) { - mLastReportTimeNs = dumpTimeStampNs; - mLastReportWallClockNs = getWallClockNs(); - } - VLOG("=========================Metric Reports End=========================="); -} - - -bool MetricsManager::checkLogCredentials(const LogEvent& event) { - if (mWhitelistedAtomIds.find(event.GetTagId()) != mWhitelistedAtomIds.end()) { - return true; - } - std::lock_guard lock(mAllowedLogSourcesMutex); - if (mAllowedLogSources.find(event.GetUid()) == mAllowedLogSources.end()) { - VLOG("log source %d not on the whitelist", event.GetUid()); - return false; - } - return true; -} - -bool MetricsManager::eventSanityCheck(const LogEvent& event) { - if (event.GetTagId() == util::APP_BREADCRUMB_REPORTED) { - // Check that app breadcrumb reported fields are valid. - status_t err = NO_ERROR; - - // Uid is 3rd from last field and must match the caller's uid, - // unless that caller is statsd itself (statsd is allowed to spoof uids). - long appHookUid = event.GetLong(event.size()-2, &err); - if (err != NO_ERROR) { - VLOG("APP_BREADCRUMB_REPORTED had error when parsing the uid"); - return false; - } - - // Because the uid within the LogEvent may have been mapped from - // isolated to host, map the loggerUid similarly before comparing. - int32_t loggerUid = mUidMap->getHostUidOrSelf(event.GetUid()); - if (loggerUid != appHookUid && loggerUid != AID_STATSD) { - VLOG("APP_BREADCRUMB_REPORTED has invalid uid: claimed %ld but caller is %d", - appHookUid, loggerUid); - return false; - } - - // The state must be from 0,3. This part of code must be manually updated. - long appHookState = event.GetLong(event.size(), &err); - if (err != NO_ERROR) { - VLOG("APP_BREADCRUMB_REPORTED had error when parsing the state field"); - return false; - } else if (appHookState < 0 || appHookState > 3) { - VLOG("APP_BREADCRUMB_REPORTED does not have valid state %ld", appHookState); - return false; - } - } else if (event.GetTagId() == util::DAVEY_OCCURRED) { - // Daveys can be logged from any app since they are logged in libs/hwui/JankTracker.cpp. - // Check that the davey duration is reasonable. Max length check is for privacy. - status_t err = NO_ERROR; - - // Uid is the first field provided. - long jankUid = event.GetLong(1, &err); - if (err != NO_ERROR) { - VLOG("Davey occurred had error when parsing the uid"); - return false; - } - int32_t loggerUid = event.GetUid(); - if (loggerUid != jankUid && loggerUid != AID_STATSD) { - VLOG("DAVEY_OCCURRED has invalid uid: claimed %ld but caller is %d", jankUid, - loggerUid); - return false; - } - - long duration = event.GetLong(event.size(), &err); - if (err != NO_ERROR) { - VLOG("Davey occurred had error when parsing the duration"); - return false; - } else if (duration > 100000) { - VLOG("Davey duration is unreasonably long: %ld", duration); - return false; - } - } - - return true; -} - -// Consume the stats log if it's interesting to this metric. -void MetricsManager::onLogEvent(const LogEvent& event) { - if (!mConfigValid) { - return; - } - - if (!checkLogCredentials(event)) { - return; - } - - if (!eventSanityCheck(event)) { - return; - } - - int tagId = event.GetTagId(); - int64_t eventTimeNs = event.GetElapsedTimestampNs(); - - bool isActive = mIsAlwaysActive; - - // Set of metrics that are still active after flushing. - unordered_set activeMetricsIndices; - - // Update state of all metrics w/ activation conditions as of eventTimeNs. - for (int metricIndex : mMetricIndexesWithActivation) { - const sp& metric = mAllMetricProducers[metricIndex]; - metric->flushIfExpire(eventTimeNs); - if (metric->isActive()) { - // If this metric w/ activation condition is still active after - // flushing, remember it. - activeMetricsIndices.insert(metricIndex); - } - } - - mIsActive = isActive || !activeMetricsIndices.empty(); - - if (mTagIds.find(tagId) == mTagIds.end()) { - // Not interesting... - return; - } - - vector matcherCache(mAllAtomMatchingTrackers.size(), - MatchingState::kNotComputed); - - // Evaluate all atom matchers. - for (auto& matcher : mAllAtomMatchingTrackers) { - matcher->onLogEvent(event, mAllAtomMatchingTrackers, matcherCache); - } - - // Set of metrics that received an activation cancellation. - unordered_set metricIndicesWithCanceledActivations; - - // Determine which metric activations received a cancellation and cancel them. - for (const auto& it : mDeactivationAtomTrackerToMetricMap) { - if (matcherCache[it.first] == MatchingState::kMatched) { - for (int metricIndex : it.second) { - mAllMetricProducers[metricIndex]->cancelEventActivation(it.first); - metricIndicesWithCanceledActivations.insert(metricIndex); - } - } - } - - // Determine whether any metrics are no longer active after cancelling metric activations. - for (const int metricIndex : metricIndicesWithCanceledActivations) { - const sp& metric = mAllMetricProducers[metricIndex]; - metric->flushIfExpire(eventTimeNs); - if (!metric->isActive()) { - activeMetricsIndices.erase(metricIndex); - } - } - - isActive |= !activeMetricsIndices.empty(); - - - // Determine which metric activations should be turned on and turn them on - for (const auto& it : mActivationAtomTrackerToMetricMap) { - if (matcherCache[it.first] == MatchingState::kMatched) { - for (int metricIndex : it.second) { - mAllMetricProducers[metricIndex]->activate(it.first, eventTimeNs); - isActive |= mAllMetricProducers[metricIndex]->isActive(); - } - } - } - - mIsActive = isActive; - - // A bitmap to see which ConditionTracker needs to be re-evaluated. - vector conditionToBeEvaluated(mAllConditionTrackers.size(), false); - - for (const auto& pair : mTrackerToConditionMap) { - if (matcherCache[pair.first] == MatchingState::kMatched) { - const auto& conditionList = pair.second; - for (const int conditionIndex : conditionList) { - conditionToBeEvaluated[conditionIndex] = true; - } - } - } - - vector conditionCache(mAllConditionTrackers.size(), - ConditionState::kNotEvaluated); - // A bitmap to track if a condition has changed value. - vector changedCache(mAllConditionTrackers.size(), false); - for (size_t i = 0; i < mAllConditionTrackers.size(); i++) { - if (conditionToBeEvaluated[i] == false) { - continue; - } - sp& condition = mAllConditionTrackers[i]; - condition->evaluateCondition(event, matcherCache, mAllConditionTrackers, conditionCache, - changedCache); - } - - for (size_t i = 0; i < mAllConditionTrackers.size(); i++) { - if (changedCache[i] == false) { - continue; - } - auto pair = mConditionToMetricMap.find(i); - if (pair != mConditionToMetricMap.end()) { - auto& metricList = pair->second; - for (auto metricIndex : metricList) { - // Metric cares about non sliced condition, and it's changed. - // Push the new condition to it directly. - if (!mAllMetricProducers[metricIndex]->isConditionSliced()) { - mAllMetricProducers[metricIndex]->onConditionChanged(conditionCache[i], - eventTimeNs); - // Metric cares about sliced conditions, and it may have changed. Send - // notification, and the metric can query the sliced conditions that are - // interesting to it. - } else { - mAllMetricProducers[metricIndex]->onSlicedConditionMayChange(conditionCache[i], - eventTimeNs); - } - } - } - } - - // For matched AtomMatchers, tell relevant metrics that a matched event has come. - for (size_t i = 0; i < mAllAtomMatchingTrackers.size(); i++) { - if (matcherCache[i] == MatchingState::kMatched) { - StatsdStats::getInstance().noteMatcherMatched(mConfigKey, - mAllAtomMatchingTrackers[i]->getId()); - auto pair = mTrackerToMetricMap.find(i); - if (pair != mTrackerToMetricMap.end()) { - auto& metricList = pair->second; - for (const int metricIndex : metricList) { - // pushed metrics are never scheduled pulls - mAllMetricProducers[metricIndex]->onMatchedLogEvent(i, event); - } - } - } - } -} - -void MetricsManager::onAnomalyAlarmFired( - const int64_t& timestampNs, - unordered_set, SpHash>& alarmSet) { - for (const auto& itr : mAllAnomalyTrackers) { - itr->informAlarmsFired(timestampNs, alarmSet); - } -} - -void MetricsManager::onPeriodicAlarmFired( - const int64_t& timestampNs, - unordered_set, SpHash>& alarmSet) { - for (const auto& itr : mAllPeriodicAlarmTrackers) { - itr->informAlarmsFired(timestampNs, alarmSet); - } -} - -// Returns the total byte size of all metrics managed by a single config source. -size_t MetricsManager::byteSize() { - size_t totalSize = 0; - for (const auto& metricProducer : mAllMetricProducers) { - totalSize += metricProducer->byteSize(); - } - return totalSize; -} - -void MetricsManager::loadActiveConfig(const ActiveConfig& config, int64_t currentTimeNs) { - if (config.metric_size() == 0) { - ALOGW("No active metric for config %s", mConfigKey.ToString().c_str()); - return; - } - - for (int i = 0; i < config.metric_size(); i++) { - const auto& activeMetric = config.metric(i); - for (int metricIndex : mMetricIndexesWithActivation) { - const auto& metric = mAllMetricProducers[metricIndex]; - if (metric->getMetricId() == activeMetric.id()) { - VLOG("Setting active metric: %lld", (long long)metric->getMetricId()); - metric->loadActiveMetric(activeMetric, currentTimeNs); - if (!mIsActive && metric->isActive()) { - StatsdStats::getInstance().noteActiveStatusChanged(mConfigKey, - /*activate=*/ true); - } - mIsActive |= metric->isActive(); - } - } - } -} - -void MetricsManager::writeActiveConfigToProtoOutputStream( - int64_t currentTimeNs, const DumpReportReason reason, ProtoOutputStream* proto) { - proto->write(FIELD_TYPE_INT64 | FIELD_ID_ACTIVE_CONFIG_ID, (long long)mConfigKey.GetId()); - proto->write(FIELD_TYPE_INT32 | FIELD_ID_ACTIVE_CONFIG_UID, mConfigKey.GetUid()); - for (int metricIndex : mMetricIndexesWithActivation) { - const auto& metric = mAllMetricProducers[metricIndex]; - const uint64_t metricToken = proto->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | - FIELD_ID_ACTIVE_CONFIG_METRIC); - metric->writeActiveMetricToProtoOutputStream(currentTimeNs, reason, proto); - proto->end(metricToken); - } -} - -bool MetricsManager::writeMetadataToProto(int64_t currentWallClockTimeNs, - int64_t systemElapsedTimeNs, - metadata::StatsMetadata* statsMetadata) { - bool metadataWritten = false; - metadata::ConfigKey* configKey = statsMetadata->mutable_config_key(); - configKey->set_config_id(mConfigKey.GetId()); - configKey->set_uid(mConfigKey.GetUid()); - for (const auto& anomalyTracker : mAllAnomalyTrackers) { - metadata::AlertMetadata* alertMetadata = statsMetadata->add_alert_metadata(); - bool alertWritten = anomalyTracker->writeAlertMetadataToProto(currentWallClockTimeNs, - systemElapsedTimeNs, alertMetadata); - if (!alertWritten) { - statsMetadata->mutable_alert_metadata()->RemoveLast(); - } - metadataWritten |= alertWritten; - } - return metadataWritten; -} - -void MetricsManager::loadMetadata(const metadata::StatsMetadata& metadata, - int64_t currentWallClockTimeNs, - int64_t systemElapsedTimeNs) { - for (const metadata::AlertMetadata& alertMetadata : metadata.alert_metadata()) { - int64_t alertId = alertMetadata.alert_id(); - auto it = mAlertTrackerMap.find(alertId); - if (it == mAlertTrackerMap.end()) { - ALOGE("No anomalyTracker found for alertId %lld", (long long) alertId); - continue; - } - mAllAnomalyTrackers[it->second]->loadAlertMetadata(alertMetadata, - currentWallClockTimeNs, - systemElapsedTimeNs); - } -} - -} // namespace statsd -} // namespace os -} // namespace android diff --git a/bin/src/metrics/MetricsManager.h b/bin/src/metrics/MetricsManager.h deleted file mode 100644 index e6f3f606..00000000 --- a/bin/src/metrics/MetricsManager.h +++ /dev/null @@ -1,385 +0,0 @@ -/* - * 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. - */ - -#pragma once - -#include "anomaly/AlarmMonitor.h" -#include "anomaly/AlarmTracker.h" -#include "anomaly/AnomalyTracker.h" -#include "condition/ConditionTracker.h" -#include "config/ConfigKey.h" -#include "external/StatsPullerManager.h" -#include "packages/modules/StatsD/bin/src/statsd_config.pb.h" -#include "packages/modules/StatsD/bin/src/statsd_metadata.pb.h" -#include "logd/LogEvent.h" -#include "matchers/AtomMatchingTracker.h" -#include "metrics/MetricProducer.h" -#include "packages/UidMap.h" - -#include - -namespace android { -namespace os { -namespace statsd { - -// A MetricsManager is responsible for managing metrics from one single config source. -class MetricsManager : public virtual android::RefBase, public virtual PullUidProvider { -public: - MetricsManager(const ConfigKey& configKey, const StatsdConfig& config, const int64_t timeBaseNs, - const int64_t currentTimeNs, const sp& uidMap, - const sp& pullerManager, - const sp& anomalyAlarmMonitor, - const sp& periodicAlarmMonitor); - - virtual ~MetricsManager(); - - bool updateConfig(const StatsdConfig& config, const int64_t timeBaseNs, - const int64_t currentTimeNs, const sp& anomalyAlarmMonitor, - const sp& periodicAlarmMonitor); - - // Return whether the configuration is valid. - bool isConfigValid() const; - - bool checkLogCredentials(const LogEvent& event); - - bool eventSanityCheck(const LogEvent& event); - - void onLogEvent(const LogEvent& event); - - void onAnomalyAlarmFired( - const int64_t& timestampNs, - unordered_set, SpHash>& alarmSet); - - void onPeriodicAlarmFired( - const int64_t& timestampNs, - unordered_set, SpHash>& alarmSet); - - void notifyAppUpgrade(const int64_t& eventTimeNs, const string& apk, const int uid, - const int64_t version); - - void notifyAppRemoved(const int64_t& eventTimeNs, const string& apk, const int uid); - - void onUidMapReceived(const int64_t& eventTimeNs); - - void onStatsdInitCompleted(const int64_t& elapsedTimeNs); - - void init(); - - vector getPullAtomUids(int32_t atomId) override; - - bool shouldWriteToDisk() const { - return mNoReportMetricIds.size() != mAllMetricProducers.size(); - } - - bool shouldPersistLocalHistory() const { - return mShouldPersistHistory; - } - - void dumpStates(FILE* out, bool verbose); - - inline bool isInTtl(const int64_t timestampNs) const { - return mTtlNs <= 0 || timestampNs < mTtlEndNs; - }; - - inline bool hashStringInReport() const { - return mHashStringsInReport; - }; - - inline bool versionStringsInReport() const { - return mVersionStringsInReport; - }; - - inline bool installerInReport() const { - return mInstallerInReport; - }; - - void refreshTtl(const int64_t currentTimestampNs) { - if (mTtlNs > 0) { - mTtlEndNs = currentTimestampNs + mTtlNs; - } - }; - - // Returns the elapsed realtime when this metric manager last reported metrics. If this config - // has not yet dumped any reports, this is the time the metricsmanager was initialized. - inline int64_t getLastReportTimeNs() const { - return mLastReportTimeNs; - }; - - inline int64_t getLastReportWallClockNs() const { - return mLastReportWallClockNs; - }; - - inline size_t getNumMetrics() const { - return mAllMetricProducers.size(); - } - - virtual void dropData(const int64_t dropTimeNs); - - virtual void onDumpReport(const int64_t dumpTimeNs, - const bool include_current_partial_bucket, - const bool erase_data, - const DumpLatency dumpLatency, - std::set *str_set, - android::util::ProtoOutputStream* protoOutput); - - // Computes the total byte size of all metrics managed by a single config source. - // Does not change the state. - virtual size_t byteSize(); - - // Returns whether or not this config is active. - // The config is active if any metric in the config is active. - inline bool isActive() const { - return mIsActive; - } - - void loadActiveConfig(const ActiveConfig& config, int64_t currentTimeNs); - - void writeActiveConfigToProtoOutputStream( - int64_t currentTimeNs, const DumpReportReason reason, ProtoOutputStream* proto); - - // Returns true if at least one piece of metadata is written. - bool writeMetadataToProto(int64_t currentWallClockTimeNs, - int64_t systemElapsedTimeNs, - metadata::StatsMetadata* statsMetadata); - - void loadMetadata(const metadata::StatsMetadata& metadata, - int64_t currentWallClockTimeNs, - int64_t systemElapsedTimeNs); -private: - // For test only. - inline int64_t getTtlEndNs() const { return mTtlEndNs; } - - const ConfigKey mConfigKey; - - sp mUidMap; - - bool mConfigValid = false; - - bool mHashStringsInReport = false; - bool mVersionStringsInReport = false; - bool mInstallerInReport = false; - - int64_t mTtlNs; - int64_t mTtlEndNs; - - int64_t mLastReportTimeNs; - int64_t mLastReportWallClockNs; - - sp mPullerManager; - - // The uid log sources from StatsdConfig. - std::vector mAllowedUid; - - // The pkg log sources from StatsdConfig. - std::vector mAllowedPkg; - - // The combined uid sources (after translating pkg name to uid). - // Logs from uids that are not in the list will be ignored to avoid spamming. - std::set mAllowedLogSources; - - // To guard access to mAllowedLogSources - mutable std::mutex mAllowedLogSourcesMutex; - - std::set mWhitelistedAtomIds; - - // We can pull any atom from these uids. - std::set mDefaultPullUids; - - // Uids that specific atoms can pull from. - // This is a map> - std::map> mPullAtomUids; - - // Packages that specific atoms can be pulled from. - std::map> mPullAtomPackages; - - // All uids to pull for this atom. NOTE: Does not include the default uids for memory. - std::map> mCombinedPullAtomUids; - - // Contains the annotations passed in with StatsdConfig. - std::list> mAnnotations; - - bool mShouldPersistHistory; - - // All event tags that are interesting to my metrics. - std::set mTagIds; - - // We only store the sp of AtomMatchingTracker, MetricProducer, and ConditionTracker in - // MetricsManager. There are relationships between them, and the relationships are denoted by - // index instead of pointers. The reasons for this are: (1) the relationship between them are - // complicated, so storing index instead of pointers reduces the risk that A holds B's sp, and B - // holds A's sp. (2) When we evaluate matcher results, or condition results, we can quickly get - // the related results from a cache using the index. - - // Hold all the atom matchers from the config. - std::vector> mAllAtomMatchingTrackers; - - // Hold all the conditions from the config. - std::vector> mAllConditionTrackers; - - // Hold all metrics from the config. - std::vector> mAllMetricProducers; - - // Hold all alert trackers. - std::vector> mAllAnomalyTrackers; - - // Hold all periodic alarm trackers. - std::vector> mAllPeriodicAlarmTrackers; - - // To make updating configs faster, we map the id of a AtomMatchingTracker, MetricProducer, and - // ConditionTracker to its index in the corresponding vector. - - // Maps the id of an atom matching tracker to its index in mAllAtomMatchingTrackers. - std::unordered_map mAtomMatchingTrackerMap; - - // Maps the id of a condition tracker to its index in mAllConditionTrackers. - std::unordered_map mConditionTrackerMap; - - // Maps the id of a metric producer to its index in mAllMetricProducers. - std::unordered_map mMetricProducerMap; - - // To make the log processing more efficient, we want to do as much filtering as possible - // before we go into individual trackers and conditions to match. - - // 1st filter: check if the event tag id is in mTagIds. - // 2nd filter: if it is, we parse the event because there is at least one member is interested. - // then pass to all AtomMatchingTrackers (itself also filter events by ids). - // 3nd filter: for AtomMatchingTrackers that matched this event, we pass this event to the - // ConditionTrackers and MetricProducers that use this matcher. - // 4th filter: for ConditionTrackers that changed value due to this event, we pass - // new conditions to metrics that use this condition. - - // The following map is initialized from the statsd_config. - - // Maps from the index of the AtomMatchingTracker to index of MetricProducer. - std::unordered_map> mTrackerToMetricMap; - - // Maps from AtomMatchingTracker to ConditionTracker - std::unordered_map> mTrackerToConditionMap; - - // Maps from ConditionTracker to MetricProducer - std::unordered_map> mConditionToMetricMap; - - // Maps from life span triggering event to MetricProducers. - std::unordered_map> mActivationAtomTrackerToMetricMap; - - // Maps deactivation triggering event to MetricProducers. - std::unordered_map> mDeactivationAtomTrackerToMetricMap; - - // Maps AlertIds to the index of the corresponding AnomalyTracker stored in mAllAnomalyTrackers. - // The map is used in LoadMetadata to more efficiently lookup AnomalyTrackers from an AlertId. - std::unordered_map mAlertTrackerMap; - - std::vector mMetricIndexesWithActivation; - - void initAllowedLogSources(); - - void initPullAtomSources(); - - // Only called on config creation/update to initialize log sources from the config. - // Calls initAllowedLogSources and initPullAtomSources. Sets mConfigValid to false on error. - void createAllLogSourcesFromConfig(const StatsdConfig& config); - - // Verifies the config meets guardrails and updates statsdStats. - // Sets mConfigValid to false on error. Should be called on config creation/update - void verifyGuardrailsAndUpdateStatsdStats(); - - // Initializes mIsAlwaysActive and mIsActive. - // Should be called on config creation/update. - void initializeConfigActiveStatus(); - - // The metrics that don't need to be uploaded or even reported. - std::set mNoReportMetricIds; - - // The config is active if any metric in the config is active. - bool mIsActive; - - // The config is always active if any metric in the config does not have an activation signal. - bool mIsAlwaysActive; - - // Hashes of the States used in this config, keyed by the state id, used in config updates. - std::map mStateProtoHashes; - - FRIEND_TEST(WakelockDurationE2eTest, TestAggregatedPredicateDimensions); - FRIEND_TEST(MetricConditionLinkE2eTest, TestMultiplePredicatesAndLinks); - FRIEND_TEST(AttributionE2eTest, TestAttributionMatchAndSliceByFirstUid); - FRIEND_TEST(AttributionE2eTest, TestAttributionMatchAndSliceByChain); - FRIEND_TEST(GaugeMetricE2eTest, TestMultipleFieldsForPushedEvent); - FRIEND_TEST(GaugeMetricE2eTest, TestRandomSamplePulledEvents); - FRIEND_TEST(GaugeMetricE2eTest, TestRandomSamplePulledEvent_LateAlarm); - FRIEND_TEST(GaugeMetricE2eTest, TestRandomSamplePulledEventsWithActivation); - FRIEND_TEST(GaugeMetricE2eTest, TestRandomSamplePulledEventsNoCondition); - FRIEND_TEST(GaugeMetricE2eTest, TestConditionChangeToTrueSamplePulledEvents); - - FRIEND_TEST(AnomalyDetectionE2eTest, TestSlicedCountMetric_single_bucket); - FRIEND_TEST(AnomalyDetectionE2eTest, TestSlicedCountMetric_multiple_buckets); - FRIEND_TEST(AnomalyDetectionE2eTest, TestCountMetric_save_refractory_to_disk_no_data_written); - FRIEND_TEST(AnomalyDetectionE2eTest, TestCountMetric_save_refractory_to_disk); - FRIEND_TEST(AnomalyDetectionE2eTest, TestCountMetric_load_refractory_from_disk); - FRIEND_TEST(AnomalyDetectionE2eTest, TestDurationMetric_SUM_single_bucket); - FRIEND_TEST(AnomalyDetectionE2eTest, TestDurationMetric_SUM_partial_bucket); - FRIEND_TEST(AnomalyDetectionE2eTest, TestDurationMetric_SUM_multiple_buckets); - FRIEND_TEST(AnomalyDetectionE2eTest, TestDurationMetric_SUM_long_refractory_period); - - FRIEND_TEST(AlarmE2eTest, TestMultipleAlarms); - FRIEND_TEST(ConfigTtlE2eTest, TestCountMetric); - FRIEND_TEST(ConfigUpdateE2eAbTest, TestConfigTtl); - FRIEND_TEST(MetricActivationE2eTest, TestCountMetric); - FRIEND_TEST(MetricActivationE2eTest, TestCountMetricWithOneDeactivation); - FRIEND_TEST(MetricActivationE2eTest, TestCountMetricWithTwoDeactivations); - FRIEND_TEST(MetricActivationE2eTest, TestCountMetricWithSameDeactivation); - FRIEND_TEST(MetricActivationE2eTest, TestCountMetricWithTwoMetricsTwoDeactivations); - - FRIEND_TEST(MetricsManagerTest, TestLogSources); - FRIEND_TEST(MetricsManagerTest, TestLogSourcesOnConfigUpdate); - - FRIEND_TEST(StatsLogProcessorTest, TestActiveConfigMetricDiskWriteRead); - FRIEND_TEST(StatsLogProcessorTest, TestActivationOnBoot); - FRIEND_TEST(StatsLogProcessorTest, TestActivationOnBootMultipleActivations); - FRIEND_TEST(StatsLogProcessorTest, - TestActivationOnBootMultipleActivationsDifferentActivationTypes); - FRIEND_TEST(StatsLogProcessorTest, TestActivationsPersistAcrossSystemServerRestart); - - FRIEND_TEST(CountMetricE2eTest, TestInitialConditionChanges); - FRIEND_TEST(CountMetricE2eTest, TestSlicedState); - FRIEND_TEST(CountMetricE2eTest, TestSlicedStateWithMap); - FRIEND_TEST(CountMetricE2eTest, TestMultipleSlicedStates); - FRIEND_TEST(CountMetricE2eTest, TestSlicedStateWithPrimaryFields); - - FRIEND_TEST(DurationMetricE2eTest, TestOneBucket); - FRIEND_TEST(DurationMetricE2eTest, TestTwoBuckets); - FRIEND_TEST(DurationMetricE2eTest, TestWithActivation); - FRIEND_TEST(DurationMetricE2eTest, TestWithCondition); - FRIEND_TEST(DurationMetricE2eTest, TestWithSlicedCondition); - FRIEND_TEST(DurationMetricE2eTest, TestWithActivationAndSlicedCondition); - FRIEND_TEST(DurationMetricE2eTest, TestWithSlicedState); - FRIEND_TEST(DurationMetricE2eTest, TestWithConditionAndSlicedState); - FRIEND_TEST(DurationMetricE2eTest, TestWithSlicedStateMapped); - FRIEND_TEST(DurationMetricE2eTest, TestWithSlicedStatePrimaryFieldsSuperset); - FRIEND_TEST(DurationMetricE2eTest, TestWithSlicedStatePrimaryFieldsSubset); - FRIEND_TEST(DurationMetricE2eTest, TestUploadThreshold); - - FRIEND_TEST(ValueMetricE2eTest, TestInitialConditionChanges); - FRIEND_TEST(ValueMetricE2eTest, TestPulledEvents); - FRIEND_TEST(ValueMetricE2eTest, TestPulledEvents_LateAlarm); - FRIEND_TEST(ValueMetricE2eTest, TestPulledEvents_WithActivation); - FRIEND_TEST(ValueMetricE2eTest, TestInitWithSlicedState); - FRIEND_TEST(ValueMetricE2eTest, TestInitWithSlicedState_WithDimensions); - FRIEND_TEST(ValueMetricE2eTest, TestInitWithSlicedState_WithIncorrectDimensions); -}; - -} // namespace statsd -} // namespace os -} // namespace android diff --git a/bin/src/metrics/ValueMetricProducer.cpp b/bin/src/metrics/ValueMetricProducer.cpp deleted file mode 100644 index e766289f..00000000 --- a/bin/src/metrics/ValueMetricProducer.cpp +++ /dev/null @@ -1,1282 +0,0 @@ -/* - * 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. - */ - -#define DEBUG false // STOPSHIP if true -#include "Log.h" - -#include "ValueMetricProducer.h" - -#include -#include - -#include "../guardrail/StatsdStats.h" -#include "../stats_log_util.h" -#include "metrics/parsing_utils/metrics_manager_util.h" - -using android::util::FIELD_COUNT_REPEATED; -using android::util::FIELD_TYPE_BOOL; -using android::util::FIELD_TYPE_DOUBLE; -using android::util::FIELD_TYPE_INT32; -using android::util::FIELD_TYPE_INT64; -using android::util::FIELD_TYPE_MESSAGE; -using android::util::FIELD_TYPE_STRING; -using android::util::ProtoOutputStream; -using std::map; -using std::shared_ptr; -using std::unordered_map; - -namespace android { -namespace os { -namespace statsd { - -// for StatsLogReport -const int FIELD_ID_ID = 1; -const int FIELD_ID_VALUE_METRICS = 7; -const int FIELD_ID_TIME_BASE = 9; -const int FIELD_ID_BUCKET_SIZE = 10; -const int FIELD_ID_DIMENSION_PATH_IN_WHAT = 11; -const int FIELD_ID_IS_ACTIVE = 14; -// for ValueMetricDataWrapper -const int FIELD_ID_DATA = 1; -const int FIELD_ID_SKIPPED = 2; -// for SkippedBuckets -const int FIELD_ID_SKIPPED_START_MILLIS = 3; -const int FIELD_ID_SKIPPED_END_MILLIS = 4; -const int FIELD_ID_SKIPPED_DROP_EVENT = 5; -// for DumpEvent Proto -const int FIELD_ID_BUCKET_DROP_REASON = 1; -const int FIELD_ID_DROP_TIME = 2; -// for ValueMetricData -const int FIELD_ID_DIMENSION_IN_WHAT = 1; -const int FIELD_ID_BUCKET_INFO = 3; -const int FIELD_ID_DIMENSION_LEAF_IN_WHAT = 4; -const int FIELD_ID_SLICE_BY_STATE = 6; -// for ValueBucketInfo -const int FIELD_ID_VALUE_INDEX = 1; -const int FIELD_ID_VALUE_LONG = 2; -const int FIELD_ID_VALUE_DOUBLE = 3; -const int FIELD_ID_VALUES = 9; -const int FIELD_ID_BUCKET_NUM = 4; -const int FIELD_ID_START_BUCKET_ELAPSED_MILLIS = 5; -const int FIELD_ID_END_BUCKET_ELAPSED_MILLIS = 6; -const int FIELD_ID_CONDITION_TRUE_NS = 10; - -const Value ZERO_LONG((int64_t)0); -const Value ZERO_DOUBLE((int64_t)0); - -// ValueMetric has a minimum bucket size of 10min so that we don't pull too frequently -ValueMetricProducer::ValueMetricProducer( - const ConfigKey& key, const ValueMetric& metric, const int conditionIndex, - const vector& initialConditionCache, - const sp& conditionWizard, const uint64_t protoHash, - const int whatMatcherIndex, const sp& matcherWizard, - const int pullTagId, const int64_t timeBaseNs, const int64_t startTimeNs, - const sp& pullerManager, - const unordered_map>& eventActivationMap, - const unordered_map>>& eventDeactivationMap, - const vector& slicedStateAtoms, - const unordered_map>& stateGroupMap) - : MetricProducer(metric.id(), key, timeBaseNs, conditionIndex, initialConditionCache, - conditionWizard, protoHash, eventActivationMap, eventDeactivationMap, - slicedStateAtoms, stateGroupMap), - mWhatMatcherIndex(whatMatcherIndex), - mEventMatcherWizard(matcherWizard), - mPullerManager(pullerManager), - mPullTagId(pullTagId), - mIsPulled(pullTagId != -1), - mMinBucketSizeNs(metric.min_bucket_size_nanos()), - mDimensionSoftLimit(StatsdStats::kAtomDimensionKeySizeLimitMap.find(pullTagId) != - StatsdStats::kAtomDimensionKeySizeLimitMap.end() - ? StatsdStats::kAtomDimensionKeySizeLimitMap.at(pullTagId).first - : StatsdStats::kDimensionKeySizeSoftLimit), - mDimensionHardLimit(StatsdStats::kAtomDimensionKeySizeLimitMap.find(pullTagId) != - StatsdStats::kAtomDimensionKeySizeLimitMap.end() - ? StatsdStats::kAtomDimensionKeySizeLimitMap.at(pullTagId).second - : StatsdStats::kDimensionKeySizeHardLimit), - mUseAbsoluteValueOnReset(metric.use_absolute_value_on_reset()), - mAggregationType(metric.aggregation_type()), - mUseDiff(metric.has_use_diff() ? metric.use_diff() : (mIsPulled ? true : false)), - mValueDirection(metric.value_direction()), - mSkipZeroDiffOutput(metric.skip_zero_diff_output()), - mUseZeroDefaultBase(metric.use_zero_default_base()), - mHasGlobalBase(false), - mCurrentBucketIsSkipped(false), - mMaxPullDelayNs(metric.max_pull_delay_sec() > 0 ? metric.max_pull_delay_sec() * NS_PER_SEC - : StatsdStats::kPullMaxDelayNs), - mSplitBucketForAppUpgrade(metric.split_bucket_for_app_upgrade()), - // Condition timer will be set later within the constructor after pulling events - mConditionTimer(false, timeBaseNs) { - int64_t bucketSizeMills = 0; - if (metric.has_bucket()) { - bucketSizeMills = TimeUnitToBucketSizeInMillisGuardrailed(key.GetUid(), metric.bucket()); - } else { - bucketSizeMills = TimeUnitToBucketSizeInMillis(ONE_HOUR); - } - - mBucketSizeNs = bucketSizeMills * 1000000; - - translateFieldMatcher(metric.value_field(), &mFieldMatchers); - - if (metric.has_dimensions_in_what()) { - translateFieldMatcher(metric.dimensions_in_what(), &mDimensionsInWhat); - mContainANYPositionInDimensionsInWhat = HasPositionANY(metric.dimensions_in_what()); - mSliceByPositionALL = HasPositionALL(metric.dimensions_in_what()); - } - - if (metric.links().size() > 0) { - for (const auto& link : metric.links()) { - Metric2Condition mc; - mc.conditionId = link.condition(); - translateFieldMatcher(link.fields_in_what(), &mc.metricFields); - translateFieldMatcher(link.fields_in_condition(), &mc.conditionFields); - mMetric2ConditionLinks.push_back(mc); - } - mConditionSliced = true; - } - - for (const auto& stateLink : metric.state_link()) { - Metric2State ms; - ms.stateAtomId = stateLink.state_atom_id(); - translateFieldMatcher(stateLink.fields_in_what(), &ms.metricFields); - translateFieldMatcher(stateLink.fields_in_state(), &ms.stateFields); - mMetric2StateLinks.push_back(ms); - } - - int64_t numBucketsForward = calcBucketsForwardCount(startTimeNs); - mCurrentBucketNum += numBucketsForward; - - flushIfNeededLocked(startTimeNs); - - if (mIsPulled) { - mPullerManager->RegisterReceiver(mPullTagId, mConfigKey, this, getCurrentBucketEndTimeNs(), - mBucketSizeNs); - } - - // Only do this for partial buckets like first bucket. All other buckets should use - // flushIfNeeded to adjust start and end to bucket boundaries. - // Adjust start for partial bucket - mCurrentBucketStartTimeNs = startTimeNs; - mConditionTimer.newBucketStart(mCurrentBucketStartTimeNs); - - // Now that activations are processed, start the condition timer if needed. - mConditionTimer.onConditionChanged(mIsActive && mCondition == ConditionState::kTrue, - mCurrentBucketStartTimeNs); - - VLOG("value metric %lld created. bucket size %lld start_time: %lld", (long long)metric.id(), - (long long)mBucketSizeNs, (long long)mTimeBaseNs); -} - -ValueMetricProducer::~ValueMetricProducer() { - VLOG("~ValueMetricProducer() called"); - if (mIsPulled) { - mPullerManager->UnRegisterReceiver(mPullTagId, mConfigKey, this); - } -} - -bool ValueMetricProducer::onConfigUpdatedLocked( - const StatsdConfig& config, const int configIndex, const int metricIndex, - const vector>& allAtomMatchingTrackers, - const unordered_map& oldAtomMatchingTrackerMap, - const unordered_map& newAtomMatchingTrackerMap, - const sp& matcherWizard, - const vector>& allConditionTrackers, - const unordered_map& conditionTrackerMap, const sp& wizard, - const unordered_map& metricToActivationMap, - unordered_map>& trackerToMetricMap, - unordered_map>& conditionToMetricMap, - unordered_map>& activationAtomTrackerToMetricMap, - unordered_map>& deactivationAtomTrackerToMetricMap, - vector& metricsWithActivation) { - if (!MetricProducer::onConfigUpdatedLocked( - config, configIndex, metricIndex, allAtomMatchingTrackers, - oldAtomMatchingTrackerMap, newAtomMatchingTrackerMap, matcherWizard, - allConditionTrackers, conditionTrackerMap, wizard, metricToActivationMap, - trackerToMetricMap, conditionToMetricMap, activationAtomTrackerToMetricMap, - deactivationAtomTrackerToMetricMap, metricsWithActivation)) { - return false; - } - - const ValueMetric& metric = config.value_metric(configIndex); - // Update appropriate indices: mWhatMatcherIndex, mConditionIndex and MetricsManager maps. - if (!handleMetricWithAtomMatchingTrackers(metric.what(), metricIndex, /*enforceOneAtom=*/false, - allAtomMatchingTrackers, newAtomMatchingTrackerMap, - trackerToMetricMap, mWhatMatcherIndex)) { - return false; - } - - if (metric.has_condition() && - !handleMetricWithConditions(metric.condition(), metricIndex, conditionTrackerMap, - metric.links(), allConditionTrackers, mConditionTrackerIndex, - conditionToMetricMap)) { - return false; - } - sp tmpEventWizard = mEventMatcherWizard; - mEventMatcherWizard = matcherWizard; - return true; -} - -void ValueMetricProducer::onStateChanged(int64_t eventTimeNs, int32_t atomId, - const HashableDimensionKey& primaryKey, - const FieldValue& oldState, const FieldValue& newState) { - VLOG("ValueMetric %lld onStateChanged time %lld, State %d, key %s, %d -> %d", - (long long)mMetricId, (long long)eventTimeNs, atomId, primaryKey.toString().c_str(), - oldState.mValue.int_value, newState.mValue.int_value); - - // If old and new states are in the same StateGroup, then we do not need to - // pull for this state change. - FieldValue oldStateCopy = oldState; - FieldValue newStateCopy = newState; - mapStateValue(atomId, &oldStateCopy); - mapStateValue(atomId, &newStateCopy); - if (oldStateCopy == newStateCopy) { - return; - } - - // If condition is not true or metric is not active, we do not need to pull - // for this state change. - if (mCondition != ConditionState::kTrue || !mIsActive) { - return; - } - - bool isEventLate = eventTimeNs < mCurrentBucketStartTimeNs; - if (isEventLate) { - VLOG("Skip event due to late arrival: %lld vs %lld", (long long)eventTimeNs, - (long long)mCurrentBucketStartTimeNs); - invalidateCurrentBucket(eventTimeNs, BucketDropReason::EVENT_IN_WRONG_BUCKET); - return; - } - mStateChangePrimaryKey.first = atomId; - mStateChangePrimaryKey.second = primaryKey; - if (mIsPulled) { - pullAndMatchEventsLocked(eventTimeNs); - } - mStateChangePrimaryKey.first = 0; - mStateChangePrimaryKey.second = DEFAULT_DIMENSION_KEY; - flushIfNeededLocked(eventTimeNs); -} - -void ValueMetricProducer::onSlicedConditionMayChangeLocked(bool overallCondition, - const int64_t eventTime) { - VLOG("Metric %lld onSlicedConditionMayChange", (long long)mMetricId); -} - -void ValueMetricProducer::dropDataLocked(const int64_t dropTimeNs) { - StatsdStats::getInstance().noteBucketDropped(mMetricId); - - // The current partial bucket is not flushed and does not require a pull, - // so the data is still valid. - flushIfNeededLocked(dropTimeNs); - clearPastBucketsLocked(dropTimeNs); -} - -void ValueMetricProducer::clearPastBucketsLocked(const int64_t dumpTimeNs) { - mPastBuckets.clear(); - mSkippedBuckets.clear(); -} - -void ValueMetricProducer::onDumpReportLocked(const int64_t dumpTimeNs, - const bool include_current_partial_bucket, - const bool erase_data, - const DumpLatency dumpLatency, - std::set *str_set, - ProtoOutputStream* protoOutput) { - VLOG("metric %lld dump report now...", (long long)mMetricId); - if (include_current_partial_bucket) { - // For pull metrics, we need to do a pull at bucket boundaries. If we do not do that the - // current bucket will have incomplete data and the next will have the wrong snapshot to do - // a diff against. If the condition is false, we are fine since the base data is reset and - // we are not tracking anything. - bool pullNeeded = mIsPulled && mCondition == ConditionState::kTrue; - if (pullNeeded) { - switch (dumpLatency) { - case FAST: - invalidateCurrentBucket(dumpTimeNs, BucketDropReason::DUMP_REPORT_REQUESTED); - break; - case NO_TIME_CONSTRAINTS: - pullAndMatchEventsLocked(dumpTimeNs); - break; - } - } - flushCurrentBucketLocked(dumpTimeNs, dumpTimeNs); - } - protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_ID, (long long)mMetricId); - protoOutput->write(FIELD_TYPE_BOOL | FIELD_ID_IS_ACTIVE, isActiveLocked()); - - if (mPastBuckets.empty() && mSkippedBuckets.empty()) { - return; - } - protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_TIME_BASE, (long long)mTimeBaseNs); - protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_BUCKET_SIZE, (long long)mBucketSizeNs); - // Fills the dimension path if not slicing by ALL. - if (!mSliceByPositionALL) { - if (!mDimensionsInWhat.empty()) { - uint64_t dimenPathToken = - protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_ID_DIMENSION_PATH_IN_WHAT); - writeDimensionPathToProto(mDimensionsInWhat, protoOutput); - protoOutput->end(dimenPathToken); - } - } - - uint64_t protoToken = protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_ID_VALUE_METRICS); - - for (const auto& skippedBucket : mSkippedBuckets) { - uint64_t wrapperToken = - protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_SKIPPED); - protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_SKIPPED_START_MILLIS, - (long long)(NanoToMillis(skippedBucket.bucketStartTimeNs))); - protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_SKIPPED_END_MILLIS, - (long long)(NanoToMillis(skippedBucket.bucketEndTimeNs))); - for (const auto& dropEvent : skippedBucket.dropEvents) { - uint64_t dropEventToken = protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | - FIELD_ID_SKIPPED_DROP_EVENT); - protoOutput->write(FIELD_TYPE_INT32 | FIELD_ID_BUCKET_DROP_REASON, dropEvent.reason); - protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_DROP_TIME, - (long long)(NanoToMillis(dropEvent.dropTimeNs))); - protoOutput->end(dropEventToken); - } - protoOutput->end(wrapperToken); - } - - for (const auto& pair : mPastBuckets) { - const MetricDimensionKey& dimensionKey = pair.first; - VLOG(" dimension key %s", dimensionKey.toString().c_str()); - uint64_t wrapperToken = - protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_DATA); - - // First fill dimension. - if (mSliceByPositionALL) { - uint64_t dimensionToken = - protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_ID_DIMENSION_IN_WHAT); - writeDimensionToProto(dimensionKey.getDimensionKeyInWhat(), str_set, protoOutput); - protoOutput->end(dimensionToken); - } else { - writeDimensionLeafNodesToProto(dimensionKey.getDimensionKeyInWhat(), - FIELD_ID_DIMENSION_LEAF_IN_WHAT, str_set, protoOutput); - } - - // Then fill slice_by_state. - for (auto state : dimensionKey.getStateValuesKey().getValues()) { - uint64_t stateToken = protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | - FIELD_ID_SLICE_BY_STATE); - writeStateToProto(state, protoOutput); - protoOutput->end(stateToken); - } - - // Then fill bucket_info (ValueBucketInfo). - for (const auto& bucket : pair.second) { - uint64_t bucketInfoToken = protoOutput->start( - FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_BUCKET_INFO); - - if (bucket.mBucketEndNs - bucket.mBucketStartNs != mBucketSizeNs) { - protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_START_BUCKET_ELAPSED_MILLIS, - (long long)NanoToMillis(bucket.mBucketStartNs)); - protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_END_BUCKET_ELAPSED_MILLIS, - (long long)NanoToMillis(bucket.mBucketEndNs)); - } else { - protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_BUCKET_NUM, - (long long)(getBucketNumFromEndTimeNs(bucket.mBucketEndNs))); - } - // We only write the condition timer value if the metric has a - // condition and/or is sliced by state. - // If the metric is sliced by state, the condition timer value is - // also sliced by state to reflect time spent in that state. - if (mConditionTrackerIndex >= 0 || !mSlicedStateAtoms.empty()) { - protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_CONDITION_TRUE_NS, - (long long)bucket.mConditionTrueNs); - } - for (int i = 0; i < (int)bucket.valueIndex.size(); i++) { - int index = bucket.valueIndex[i]; - const Value& value = bucket.values[i]; - uint64_t valueToken = protoOutput->start( - FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_VALUES); - protoOutput->write(FIELD_TYPE_INT32 | FIELD_ID_VALUE_INDEX, - index); - if (value.getType() == LONG) { - protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_VALUE_LONG, - (long long)value.long_value); - VLOG("\t bucket [%lld - %lld] value %d: %lld", (long long)bucket.mBucketStartNs, - (long long)bucket.mBucketEndNs, index, (long long)value.long_value); - } else if (value.getType() == DOUBLE) { - protoOutput->write(FIELD_TYPE_DOUBLE | FIELD_ID_VALUE_DOUBLE, - value.double_value); - VLOG("\t bucket [%lld - %lld] value %d: %.2f", (long long)bucket.mBucketStartNs, - (long long)bucket.mBucketEndNs, index, value.double_value); - } else { - VLOG("Wrong value type for ValueMetric output: %d", value.getType()); - } - protoOutput->end(valueToken); - } - protoOutput->end(bucketInfoToken); - } - protoOutput->end(wrapperToken); - } - protoOutput->end(protoToken); - - VLOG("metric %lld done with dump report...", (long long)mMetricId); - if (erase_data) { - mPastBuckets.clear(); - mSkippedBuckets.clear(); - } -} - -void ValueMetricProducer::invalidateCurrentBucketWithoutResetBase(const int64_t dropTimeNs, - const BucketDropReason reason) { - if (!mCurrentBucketIsSkipped) { - // Only report to StatsdStats once per invalid bucket. - StatsdStats::getInstance().noteInvalidatedBucket(mMetricId); - } - - skipCurrentBucket(dropTimeNs, reason); -} - -void ValueMetricProducer::invalidateCurrentBucket(const int64_t dropTimeNs, - const BucketDropReason reason) { - invalidateCurrentBucketWithoutResetBase(dropTimeNs, reason); - resetBase(); -} - -void ValueMetricProducer::skipCurrentBucket(const int64_t dropTimeNs, - const BucketDropReason reason) { - if (!maxDropEventsReached()) { - mCurrentSkippedBucket.dropEvents.emplace_back(buildDropEvent(dropTimeNs, reason)); - } - mCurrentBucketIsSkipped = true; -} - -void ValueMetricProducer::resetBase() { - for (auto& slice : mCurrentBaseInfo) { - for (auto& baseInfo : slice.second.baseInfos) { - baseInfo.hasBase = false; - } - } - mHasGlobalBase = false; -} - -// Handle active state change. Active state change is treated like a condition change: -// - drop bucket if active state change event arrives too late -// - if condition is true, pull data on active state changes -// - ConditionTimer tracks changes based on AND of condition and active state. -void ValueMetricProducer::onActiveStateChangedLocked(const int64_t& eventTimeNs) { - bool isEventTooLate = eventTimeNs < mCurrentBucketStartTimeNs; - if (isEventTooLate) { - // Drop bucket because event arrived too late, ie. we are missing data for this bucket. - StatsdStats::getInstance().noteLateLogEventSkipped(mMetricId); - invalidateCurrentBucket(eventTimeNs, BucketDropReason::EVENT_IN_WRONG_BUCKET); - } - - // Call parent method once we've verified the validity of current bucket. - MetricProducer::onActiveStateChangedLocked(eventTimeNs); - - if (ConditionState::kTrue != mCondition) { - return; - } - - // Pull on active state changes. - if (!isEventTooLate) { - if (mIsPulled) { - pullAndMatchEventsLocked(eventTimeNs); - } - // When active state changes from true to false, clear diff base but don't - // reset other counters as we may accumulate more value in the bucket. - if (mUseDiff && !mIsActive) { - resetBase(); - } - } - - flushIfNeededLocked(eventTimeNs); - - // Let condition timer know of new active state. - mConditionTimer.onConditionChanged(mIsActive, eventTimeNs); - - updateCurrentSlicedBucketConditionTimers(mIsActive, eventTimeNs); -} - -void ValueMetricProducer::onConditionChangedLocked(const bool condition, - const int64_t eventTimeNs) { - ConditionState newCondition = condition ? ConditionState::kTrue : ConditionState::kFalse; - bool isEventTooLate = eventTimeNs < mCurrentBucketStartTimeNs; - - // If the config is not active, skip the event. - if (!mIsActive) { - mCondition = isEventTooLate ? ConditionState::kUnknown : newCondition; - return; - } - - // If the event arrived late, mark the bucket as invalid and skip the event. - if (isEventTooLate) { - VLOG("Skip event due to late arrival: %lld vs %lld", (long long)eventTimeNs, - (long long)mCurrentBucketStartTimeNs); - StatsdStats::getInstance().noteLateLogEventSkipped(mMetricId); - StatsdStats::getInstance().noteConditionChangeInNextBucket(mMetricId); - invalidateCurrentBucket(eventTimeNs, BucketDropReason::EVENT_IN_WRONG_BUCKET); - mCondition = ConditionState::kUnknown; - mConditionTimer.onConditionChanged(mCondition, eventTimeNs); - - updateCurrentSlicedBucketConditionTimers(mCondition, eventTimeNs); - return; - } - - // If the previous condition was unknown, mark the bucket as invalid - // because the bucket will contain partial data. For example, the condition - // change might happen close to the end of the bucket and we might miss a - // lot of data. - // - // We still want to pull to set the base. - if (mCondition == ConditionState::kUnknown) { - invalidateCurrentBucket(eventTimeNs, BucketDropReason::CONDITION_UNKNOWN); - } - - // Pull and match for the following condition change cases: - // unknown/false -> true - condition changed - // true -> false - condition changed - // true -> true - old condition was true so we can flush the bucket at the - // end if needed. - // - // We don’t need to pull for unknown -> false or false -> false. - // - // onConditionChangedLocked might happen on bucket boundaries if this is - // called before #onDataPulled. - if (mIsPulled && - (newCondition == ConditionState::kTrue || mCondition == ConditionState::kTrue)) { - pullAndMatchEventsLocked(eventTimeNs); - } - - // For metrics that use diff, when condition changes from true to false, - // clear diff base but don't reset other counts because we may accumulate - // more value in the bucket. - if (mUseDiff && - (mCondition == ConditionState::kTrue && newCondition == ConditionState::kFalse)) { - resetBase(); - } - - // Update condition state after pulling. - mCondition = newCondition; - - flushIfNeededLocked(eventTimeNs); - mConditionTimer.onConditionChanged(mCondition, eventTimeNs); - - updateCurrentSlicedBucketConditionTimers(mCondition, eventTimeNs); -} - -void ValueMetricProducer::updateCurrentSlicedBucketConditionTimers(bool newCondition, - int64_t eventTimeNs) { - if (mSlicedStateAtoms.empty()) { - return; - } - - // Utilize the current state key of each DimensionsInWhat key to determine - // which condition timers to update. - // - // Assumes that the MetricDimensionKey exists in `mCurrentSlicedBucket`. - bool inPulledData; - for (const auto& [dimensionInWhatKey, dimensionInWhatInfo] : mCurrentBaseInfo) { - // If the new condition is true, turn ON the condition timer only if - // the DimensionInWhat key was present in the pulled data. - inPulledData = dimensionInWhatInfo.hasCurrentState; - mCurrentSlicedBucket[MetricDimensionKey(dimensionInWhatKey, - dimensionInWhatInfo.currentState)] - .conditionTimer.onConditionChanged(newCondition && inPulledData, eventTimeNs); - } -} - -void ValueMetricProducer::prepareFirstBucketLocked() { - // Kicks off the puller immediately if condition is true and diff based. - if (mIsActive && mIsPulled && mCondition == ConditionState::kTrue && mUseDiff) { - pullAndMatchEventsLocked(mCurrentBucketStartTimeNs); - } -} - -void ValueMetricProducer::pullAndMatchEventsLocked(const int64_t timestampNs) { - vector> allData; - if (!mPullerManager->Pull(mPullTagId, mConfigKey, timestampNs, &allData)) { - ALOGE("Stats puller failed for tag: %d at %lld", mPullTagId, (long long)timestampNs); - invalidateCurrentBucket(timestampNs, BucketDropReason::PULL_FAILED); - return; - } - - accumulateEvents(allData, timestampNs, timestampNs); -} - -int64_t ValueMetricProducer::calcPreviousBucketEndTime(const int64_t currentTimeNs) { - return mTimeBaseNs + ((currentTimeNs - mTimeBaseNs) / mBucketSizeNs) * mBucketSizeNs; -} - -// By design, statsd pulls data at bucket boundaries using AlarmManager. These pulls are likely -// to be delayed. Other events like condition changes or app upgrade which are not based on -// AlarmManager might have arrived earlier and close the bucket. -void ValueMetricProducer::onDataPulled(const std::vector>& allData, - bool pullSuccess, int64_t originalPullTimeNs) { - std::lock_guard lock(mMutex); - if (mCondition == ConditionState::kTrue) { - // If the pull failed, we won't be able to compute a diff. - if (!pullSuccess) { - invalidateCurrentBucket(originalPullTimeNs, BucketDropReason::PULL_FAILED); - } else { - bool isEventLate = originalPullTimeNs < getCurrentBucketEndTimeNs(); - if (isEventLate) { - // If the event is late, we are in the middle of a bucket. Just - // process the data without trying to snap the data to the nearest bucket. - accumulateEvents(allData, originalPullTimeNs, originalPullTimeNs); - } else { - // For scheduled pulled data, the effective event time is snap to the nearest - // bucket end. In the case of waking up from a deep sleep state, we will - // attribute to the previous bucket end. If the sleep was long but not very - // long, we will be in the immediate next bucket. Previous bucket may get a - // larger number as we pull at a later time than real bucket end. - // - // If the sleep was very long, we skip more than one bucket before sleep. In - // this case, if the diff base will be cleared and this new data will serve as - // new diff base. - int64_t bucketEndTime = calcPreviousBucketEndTime(originalPullTimeNs) - 1; - StatsdStats::getInstance().noteBucketBoundaryDelayNs( - mMetricId, originalPullTimeNs - bucketEndTime); - accumulateEvents(allData, originalPullTimeNs, bucketEndTime); - } - } - } - - // We can probably flush the bucket. Since we used bucketEndTime when calling - // #onMatchedLogEventInternalLocked, the current bucket will not have been flushed. - flushIfNeededLocked(originalPullTimeNs); -} - -void ValueMetricProducer::accumulateEvents(const std::vector>& allData, - int64_t originalPullTimeNs, int64_t eventElapsedTimeNs) { - bool isEventLate = eventElapsedTimeNs < mCurrentBucketStartTimeNs; - if (isEventLate) { - VLOG("Skip bucket end pull due to late arrival: %lld vs %lld", - (long long)eventElapsedTimeNs, (long long)mCurrentBucketStartTimeNs); - StatsdStats::getInstance().noteLateLogEventSkipped(mMetricId); - invalidateCurrentBucket(eventElapsedTimeNs, BucketDropReason::EVENT_IN_WRONG_BUCKET); - return; - } - - const int64_t elapsedRealtimeNs = getElapsedRealtimeNs(); - const int64_t pullDelayNs = elapsedRealtimeNs - originalPullTimeNs; - StatsdStats::getInstance().notePullDelay(mPullTagId, pullDelayNs); - if (pullDelayNs > mMaxPullDelayNs) { - ALOGE("Pull finish too late for atom %d, longer than %lld", mPullTagId, - (long long)mMaxPullDelayNs); - StatsdStats::getInstance().notePullExceedMaxDelay(mPullTagId); - // We are missing one pull from the bucket which means we will not have a complete view of - // what's going on. - invalidateCurrentBucket(eventElapsedTimeNs, BucketDropReason::PULL_DELAYED); - return; - } - - mMatchedMetricDimensionKeys.clear(); - for (const auto& data : allData) { - LogEvent localCopy = data->makeCopy(); - if (mEventMatcherWizard->matchLogEvent(localCopy, mWhatMatcherIndex) == - MatchingState::kMatched) { - localCopy.setElapsedTimestampNs(eventElapsedTimeNs); - onMatchedLogEventLocked(mWhatMatcherIndex, localCopy); - } - } - // If a key that is: - // 1. Tracked in mCurrentSlicedBucket and - // 2. A superset of the current mStateChangePrimaryKey - // was not found in the new pulled data (i.e. not in mMatchedDimensionInWhatKeys) - // then we need to reset the base. - for (auto& [metricDimensionKey, currentValueBucket] : mCurrentSlicedBucket) { - const auto& whatKey = metricDimensionKey.getDimensionKeyInWhat(); - bool presentInPulledData = - mMatchedMetricDimensionKeys.find(whatKey) != mMatchedMetricDimensionKeys.end(); - if (!presentInPulledData && whatKey.contains(mStateChangePrimaryKey.second)) { - auto it = mCurrentBaseInfo.find(whatKey); - for (auto& baseInfo : it->second.baseInfos) { - baseInfo.hasBase = false; - } - // Set to false when DimensionInWhat key is not present in a pull. - // Used in onMatchedLogEventInternalLocked() to ensure the condition - // timer is turned on the next pull when data is present. - it->second.hasCurrentState = false; - // Turn OFF condition timer for keys not present in pulled data. - currentValueBucket.conditionTimer.onConditionChanged(false, eventElapsedTimeNs); - } - } - mMatchedMetricDimensionKeys.clear(); - mHasGlobalBase = true; - - // If we reach the guardrail, we might have dropped some data which means the bucket is - // incomplete. - // - // The base also needs to be reset. If we do not have the full data, we might - // incorrectly compute the diff when mUseZeroDefaultBase is true since an existing key - // might be missing from mCurrentSlicedBucket. - if (hasReachedGuardRailLimit()) { - invalidateCurrentBucket(eventElapsedTimeNs, BucketDropReason::DIMENSION_GUARDRAIL_REACHED); - mCurrentSlicedBucket.clear(); - } -} - -void ValueMetricProducer::dumpStatesLocked(FILE* out, bool verbose) const { - if (mCurrentSlicedBucket.size() == 0) { - return; - } - - fprintf(out, "ValueMetric %lld dimension size %lu\n", (long long)mMetricId, - (unsigned long)mCurrentSlicedBucket.size()); - if (verbose) { - for (const auto& it : mCurrentSlicedBucket) { - for (const auto& interval : it.second.intervals) { - fprintf(out, "\t(what)%s\t(states)%s (value)%s\n", - it.first.getDimensionKeyInWhat().toString().c_str(), - it.first.getStateValuesKey().toString().c_str(), - interval.value.toString().c_str()); - } - } - } -} - -bool ValueMetricProducer::hasReachedGuardRailLimit() const { - return mCurrentSlicedBucket.size() >= mDimensionHardLimit; -} - -bool ValueMetricProducer::hitGuardRailLocked(const MetricDimensionKey& newKey) { - // ===========GuardRail============== - // 1. Report the tuple count if the tuple count > soft limit - if (mCurrentSlicedBucket.find(newKey) != mCurrentSlicedBucket.end()) { - return false; - } - if (mCurrentSlicedBucket.size() > mDimensionSoftLimit - 1) { - size_t newTupleCount = mCurrentSlicedBucket.size() + 1; - StatsdStats::getInstance().noteMetricDimensionSize(mConfigKey, mMetricId, newTupleCount); - // 2. Don't add more tuples, we are above the allowed threshold. Drop the data. - if (hasReachedGuardRailLimit()) { - ALOGE("ValueMetric %lld dropping data for dimension key %s", (long long)mMetricId, - newKey.toString().c_str()); - StatsdStats::getInstance().noteHardDimensionLimitReached(mMetricId); - return true; - } - } - - return false; -} - -bool ValueMetricProducer::hitFullBucketGuardRailLocked(const MetricDimensionKey& newKey) { - // ===========GuardRail============== - // 1. Report the tuple count if the tuple count > soft limit - if (mCurrentFullBucket.find(newKey) != mCurrentFullBucket.end()) { - return false; - } - if (mCurrentFullBucket.size() > mDimensionSoftLimit - 1) { - size_t newTupleCount = mCurrentFullBucket.size() + 1; - // 2. Don't add more tuples, we are above the allowed threshold. Drop the data. - if (newTupleCount > mDimensionHardLimit) { - ALOGE("ValueMetric %lld dropping data for full bucket dimension key %s", - (long long)mMetricId, - newKey.toString().c_str()); - return true; - } - } - - return false; -} - -bool getDoubleOrLong(const LogEvent& event, const Matcher& matcher, Value& ret) { - for (const FieldValue& value : event.getValues()) { - if (value.mField.matches(matcher)) { - switch (value.mValue.type) { - case INT: - ret.setLong(value.mValue.int_value); - break; - case LONG: - ret.setLong(value.mValue.long_value); - break; - case FLOAT: - ret.setDouble(value.mValue.float_value); - break; - case DOUBLE: - ret.setDouble(value.mValue.double_value); - break; - default: - return false; - break; - } - return true; - } - } - return false; -} - -bool ValueMetricProducer::multipleBucketsSkipped(const int64_t numBucketsForward) { - // Skip buckets if this is a pulled metric or a pushed metric that is diffed. - return numBucketsForward > 1 && (mIsPulled || mUseDiff); -} - -void ValueMetricProducer::onMatchedLogEventInternalLocked( - const size_t matcherIndex, const MetricDimensionKey& eventKey, - const ConditionKey& conditionKey, bool condition, const LogEvent& event, - const map& statePrimaryKeys) { - auto whatKey = eventKey.getDimensionKeyInWhat(); - auto stateKey = eventKey.getStateValuesKey(); - - // Skip this event if a state changed occurred for a different primary key. - auto it = statePrimaryKeys.find(mStateChangePrimaryKey.first); - // Check that both the atom id and the primary key are equal. - if (it != statePrimaryKeys.end() && it->second != mStateChangePrimaryKey.second) { - VLOG("ValueMetric skip event with primary key %s because state change primary key " - "is %s", - it->second.toString().c_str(), mStateChangePrimaryKey.second.toString().c_str()); - return; - } - - int64_t eventTimeNs = event.GetElapsedTimestampNs(); - if (eventTimeNs < mCurrentBucketStartTimeNs) { - VLOG("Skip event due to late arrival: %lld vs %lld", (long long)eventTimeNs, - (long long)mCurrentBucketStartTimeNs); - return; - } - mMatchedMetricDimensionKeys.insert(whatKey); - - if (!mIsPulled) { - // We cannot flush without doing a pull first. - flushIfNeededLocked(eventTimeNs); - } - - // We should not accumulate the data for pushed metrics when the condition is false. - bool shouldSkipForPushMetric = !mIsPulled && !condition; - // For pulled metrics, there are two cases: - // - to compute diffs, we need to process all the state changes - // - for non-diffs metrics, we should ignore the data if the condition wasn't true. If we have a - // state change from - // + True -> True: we should process the data, it might be a bucket boundary - // + True -> False: we als need to process the data. - bool shouldSkipForPulledMetric = mIsPulled && !mUseDiff - && mCondition != ConditionState::kTrue; - if (shouldSkipForPushMetric || shouldSkipForPulledMetric) { - VLOG("ValueMetric skip event because condition is false and we are not using diff (for " - "pulled metric)"); - return; - } - - if (hitGuardRailLocked(eventKey)) { - return; - } - - const auto& returnVal = - mCurrentBaseInfo.emplace(whatKey, DimensionsInWhatInfo(getUnknownStateKey())); - DimensionsInWhatInfo& dimensionsInWhatInfo = returnVal.first->second; - const HashableDimensionKey oldStateKey = dimensionsInWhatInfo.currentState; - vector& baseInfos = dimensionsInWhatInfo.baseInfos; - if (baseInfos.size() < mFieldMatchers.size()) { - VLOG("Resizing number of intervals to %d", (int)mFieldMatchers.size()); - baseInfos.resize(mFieldMatchers.size()); - } - - // Ensure we turn on the condition timer in the case where dimensions - // were missing on a previous pull due to a state change. - bool stateChange = oldStateKey != stateKey; - if (!dimensionsInWhatInfo.hasCurrentState) { - stateChange = true; - dimensionsInWhatInfo.hasCurrentState = true; - } - - // We need to get the intervals stored with the previous state key so we can - // close these value intervals. - vector& intervals = - mCurrentSlicedBucket[MetricDimensionKey(whatKey, oldStateKey)].intervals; - if (intervals.size() < mFieldMatchers.size()) { - VLOG("Resizing number of intervals to %d", (int)mFieldMatchers.size()); - intervals.resize(mFieldMatchers.size()); - } - - // We only use anomaly detection under certain cases. - // N.B.: The anomaly detection cases were modified in order to fix an issue with value metrics - // containing multiple values. We tried to retain all previous behaviour, but we are unsure the - // previous behaviour was correct. At the time of the fix, anomaly detection had no owner. - // Whoever next works on it should look into the cases where it is triggered in this function. - // Discussion here: http://ag/6124370. - bool useAnomalyDetection = true; - - dimensionsInWhatInfo.hasCurrentState = true; - dimensionsInWhatInfo.currentState = stateKey; - for (int i = 0; i < (int)mFieldMatchers.size(); i++) { - const Matcher& matcher = mFieldMatchers[i]; - BaseInfo& baseInfo = baseInfos[i]; - Interval& interval = intervals[i]; - interval.valueIndex = i; - Value value; - if (!getDoubleOrLong(event, matcher, value)) { - VLOG("Failed to get value %d from event %s", i, event.ToString().c_str()); - StatsdStats::getInstance().noteBadValueType(mMetricId); - return; - } - interval.seenNewData = true; - - if (mUseDiff) { - if (!baseInfo.hasBase) { - if (mHasGlobalBase && mUseZeroDefaultBase) { - // The bucket has global base. This key does not. - // Optionally use zero as base. - baseInfo.base = (value.type == LONG ? ZERO_LONG : ZERO_DOUBLE); - baseInfo.hasBase = true; - } else { - // no base. just update base and return. - baseInfo.base = value; - baseInfo.hasBase = true; - // If we're missing a base, do not use anomaly detection on incomplete data - useAnomalyDetection = false; - // Continue (instead of return) here in order to set baseInfo.base and - // baseInfo.hasBase for other baseInfos - continue; - } - } - - Value diff; - switch (mValueDirection) { - case ValueMetric::INCREASING: - if (value >= baseInfo.base) { - diff = value - baseInfo.base; - } else if (mUseAbsoluteValueOnReset) { - diff = value; - } else { - VLOG("Unexpected decreasing value"); - StatsdStats::getInstance().notePullDataError(mPullTagId); - baseInfo.base = value; - // If we've got bad data, do not use anomaly detection - useAnomalyDetection = false; - continue; - } - break; - case ValueMetric::DECREASING: - if (baseInfo.base >= value) { - diff = baseInfo.base - value; - } else if (mUseAbsoluteValueOnReset) { - diff = value; - } else { - VLOG("Unexpected increasing value"); - StatsdStats::getInstance().notePullDataError(mPullTagId); - baseInfo.base = value; - // If we've got bad data, do not use anomaly detection - useAnomalyDetection = false; - continue; - } - break; - case ValueMetric::ANY: - diff = value - baseInfo.base; - break; - default: - break; - } - baseInfo.base = value; - value = diff; - } - - if (interval.hasValue) { - switch (mAggregationType) { - case ValueMetric::SUM: - // for AVG, we add up and take average when flushing the bucket - case ValueMetric::AVG: - interval.value += value; - break; - case ValueMetric::MIN: - interval.value = std::min(value, interval.value); - break; - case ValueMetric::MAX: - interval.value = std::max(value, interval.value); - break; - default: - break; - } - } else { - interval.value = value; - interval.hasValue = true; - } - interval.sampleSize += 1; - } - - // State change. - if (!mSlicedStateAtoms.empty() && stateChange) { - // Turn OFF the condition timer for the previous state key. - mCurrentSlicedBucket[MetricDimensionKey(whatKey, oldStateKey)] - .conditionTimer.onConditionChanged(false, eventTimeNs); - - // Turn ON the condition timer for the new state key. - mCurrentSlicedBucket[MetricDimensionKey(whatKey, stateKey)] - .conditionTimer.onConditionChanged(true, eventTimeNs); - } - - // Only trigger the tracker if all intervals are correct and we have not skipped the bucket due - // to MULTIPLE_BUCKETS_SKIPPED. - if (useAnomalyDetection && !multipleBucketsSkipped(calcBucketsForwardCount(eventTimeNs))) { - // TODO: propgate proper values down stream when anomaly support doubles - long wholeBucketVal = intervals[0].value.long_value; - auto prev = mCurrentFullBucket.find(eventKey); - if (prev != mCurrentFullBucket.end()) { - wholeBucketVal += prev->second; - } - for (auto& tracker : mAnomalyTrackers) { - tracker->detectAndDeclareAnomaly(eventTimeNs, mCurrentBucketNum, mMetricId, eventKey, - wholeBucketVal); - } - } -} - -// For pulled metrics, we always need to make sure we do a pull before flushing the bucket -// if mCondition is true! -void ValueMetricProducer::flushIfNeededLocked(const int64_t& eventTimeNs) { - int64_t currentBucketEndTimeNs = getCurrentBucketEndTimeNs(); - if (eventTimeNs < currentBucketEndTimeNs) { - VLOG("eventTime is %lld, less than current bucket end time %lld", (long long)eventTimeNs, - (long long)(currentBucketEndTimeNs)); - return; - } - int64_t numBucketsForward = calcBucketsForwardCount(eventTimeNs); - int64_t nextBucketStartTimeNs = currentBucketEndTimeNs + (numBucketsForward - 1) * mBucketSizeNs; - flushCurrentBucketLocked(eventTimeNs, nextBucketStartTimeNs); -} - -int64_t ValueMetricProducer::calcBucketsForwardCount(const int64_t& eventTimeNs) const { - int64_t currentBucketEndTimeNs = getCurrentBucketEndTimeNs(); - if (eventTimeNs < currentBucketEndTimeNs) { - return 0; - } - return 1 + (eventTimeNs - currentBucketEndTimeNs) / mBucketSizeNs; -} - -void ValueMetricProducer::flushCurrentBucketLocked(const int64_t& eventTimeNs, - const int64_t& nextBucketStartTimeNs) { - if (mCondition == ConditionState::kUnknown) { - StatsdStats::getInstance().noteBucketUnknownCondition(mMetricId); - invalidateCurrentBucketWithoutResetBase(eventTimeNs, BucketDropReason::CONDITION_UNKNOWN); - } - - VLOG("finalizing bucket for %ld, dumping %d slices", (long)mCurrentBucketStartTimeNs, - (int)mCurrentSlicedBucket.size()); - - int64_t fullBucketEndTimeNs = getCurrentBucketEndTimeNs(); - int64_t bucketEndTime = fullBucketEndTimeNs; - int64_t numBucketsForward = calcBucketsForwardCount(eventTimeNs); - - if (multipleBucketsSkipped(numBucketsForward)) { - VLOG("Skipping forward %lld buckets", (long long)numBucketsForward); - StatsdStats::getInstance().noteSkippedForwardBuckets(mMetricId); - // Something went wrong. Maybe the device was sleeping for a long time. It is better - // to mark the current bucket as invalid. The last pull might have been successful through. - invalidateCurrentBucketWithoutResetBase(eventTimeNs, - BucketDropReason::MULTIPLE_BUCKETS_SKIPPED); - // End the bucket at the next bucket start time so the entire interval is skipped. - bucketEndTime = nextBucketStartTimeNs; - } else if (eventTimeNs < fullBucketEndTimeNs) { - bucketEndTime = eventTimeNs; - } - - // Close the current bucket. - int64_t conditionTrueDuration = mConditionTimer.newBucketStart(bucketEndTime); - bool isBucketLargeEnough = bucketEndTime - mCurrentBucketStartTimeNs >= mMinBucketSizeNs; - if (!isBucketLargeEnough) { - skipCurrentBucket(eventTimeNs, BucketDropReason::BUCKET_TOO_SMALL); - } - if (!mCurrentBucketIsSkipped) { - bool bucketHasData = false; - // The current bucket is large enough to keep. - for (auto& [metricDimensionKey, currentValueBucket] : mCurrentSlicedBucket) { - PastValueBucket bucket = - buildPartialBucket(bucketEndTime, currentValueBucket.intervals); - if (!mSlicedStateAtoms.empty()) { - bucket.mConditionTrueNs = - currentValueBucket.conditionTimer.newBucketStart(bucketEndTime); - } else { - bucket.mConditionTrueNs = conditionTrueDuration; - } - // it will auto create new vector of ValuebucketInfo if the key is not found. - if (bucket.valueIndex.size() > 0) { - auto& bucketList = mPastBuckets[metricDimensionKey]; - bucketList.push_back(bucket); - bucketHasData = true; - } - } - if (!bucketHasData) { - skipCurrentBucket(eventTimeNs, BucketDropReason::NO_DATA); - } - } - - if (mCurrentBucketIsSkipped) { - mCurrentSkippedBucket.bucketStartTimeNs = mCurrentBucketStartTimeNs; - mCurrentSkippedBucket.bucketEndTimeNs = bucketEndTime; - mSkippedBuckets.emplace_back(mCurrentSkippedBucket); - } - - // This means that the current bucket was not flushed before a forced bucket split. - // This can happen if an app update or a dump report with include_current_partial_bucket is - // requested before we get a chance to flush the bucket due to receiving new data, either from - // the statsd socket or the StatsPullerManager. - if (bucketEndTime < nextBucketStartTimeNs) { - SkippedBucket bucketInGap; - bucketInGap.bucketStartTimeNs = bucketEndTime; - bucketInGap.bucketEndTimeNs = nextBucketStartTimeNs; - bucketInGap.dropEvents.emplace_back( - buildDropEvent(eventTimeNs, BucketDropReason::NO_DATA)); - mSkippedBuckets.emplace_back(bucketInGap); - } - appendToFullBucket(eventTimeNs > fullBucketEndTimeNs); - initCurrentSlicedBucket(nextBucketStartTimeNs); - // Update the condition timer again, in case we skipped buckets. - mConditionTimer.newBucketStart(nextBucketStartTimeNs); - - // NOTE: Update the condition timers in `mCurrentSlicedBucket` only when slicing - // by state. Otherwise, the "global" condition timer will be used. - if (!mSlicedStateAtoms.empty()) { - for (auto& [metricDimensionKey, currentValueBucket] : mCurrentSlicedBucket) { - currentValueBucket.conditionTimer.newBucketStart(nextBucketStartTimeNs); - } - } - mCurrentBucketNum += numBucketsForward; -} - -PastValueBucket ValueMetricProducer::buildPartialBucket(int64_t bucketEndTime, - const std::vector& intervals) { - PastValueBucket bucket; - bucket.mBucketStartNs = mCurrentBucketStartTimeNs; - bucket.mBucketEndNs = bucketEndTime; - for (const auto& interval : intervals) { - if (interval.hasValue) { - // skip the output if the diff is zero - if (mSkipZeroDiffOutput && mUseDiff && interval.value.isZero()) { - continue; - } - bucket.valueIndex.push_back(interval.valueIndex); - if (mAggregationType != ValueMetric::AVG) { - bucket.values.push_back(interval.value); - } else { - double sum = interval.value.type == LONG ? (double)interval.value.long_value - : interval.value.double_value; - bucket.values.push_back(Value((double)sum / interval.sampleSize)); - } - } - } - return bucket; -} - -void ValueMetricProducer::initCurrentSlicedBucket(int64_t nextBucketStartTimeNs) { - StatsdStats::getInstance().noteBucketCount(mMetricId); - // Cleanup data structure to aggregate values. - for (auto it = mCurrentSlicedBucket.begin(); it != mCurrentSlicedBucket.end();) { - bool obsolete = true; - for (auto& interval : it->second.intervals) { - interval.hasValue = false; - interval.sampleSize = 0; - if (interval.seenNewData) { - obsolete = false; - } - interval.seenNewData = false; - } - - if (obsolete && !mSlicedStateAtoms.empty()) { - // When slicing by state, only delete the MetricDimensionKey when the - // state key in the MetricDimensionKey is not the current state key. - const HashableDimensionKey& dimensionInWhatKey = it->first.getDimensionKeyInWhat(); - const auto& currentBaseInfoItr = mCurrentBaseInfo.find(dimensionInWhatKey); - - if ((currentBaseInfoItr != mCurrentBaseInfo.end()) && - (it->first.getStateValuesKey() == currentBaseInfoItr->second.currentState)) { - obsolete = false; - } - } - if (obsolete) { - it = mCurrentSlicedBucket.erase(it); - } else { - it++; - } - // TODO(b/157655103): remove mCurrentBaseInfo entries when obsolete - } - - mCurrentBucketIsSkipped = false; - mCurrentSkippedBucket.reset(); - - // If we do not have a global base when the condition is true, - // we will have incomplete bucket for the next bucket. - if (mUseDiff && !mHasGlobalBase && mCondition) { - mCurrentBucketIsSkipped = false; - } - mCurrentBucketStartTimeNs = nextBucketStartTimeNs; - VLOG("metric %lld: new bucket start time: %lld", (long long)mMetricId, - (long long)mCurrentBucketStartTimeNs); -} - -void ValueMetricProducer::appendToFullBucket(const bool isFullBucketReached) { - if (mCurrentBucketIsSkipped) { - if (isFullBucketReached) { - // If the bucket is invalid, we ignore the full bucket since it contains invalid data. - mCurrentFullBucket.clear(); - } - // Current bucket is invalid, we do not add it to the full bucket. - return; - } - - if (isFullBucketReached) { // If full bucket, send to anomaly tracker. - // Accumulate partial buckets with current value and then send to anomaly tracker. - if (mCurrentFullBucket.size() > 0) { - for (const auto& slice : mCurrentSlicedBucket) { - if (hitFullBucketGuardRailLocked(slice.first) || slice.second.intervals.empty()) { - continue; - } - // TODO: fix this when anomaly can accept double values - auto& interval = slice.second.intervals[0]; - if (interval.hasValue) { - mCurrentFullBucket[slice.first] += interval.value.long_value; - } - } - for (const auto& slice : mCurrentFullBucket) { - for (auto& tracker : mAnomalyTrackers) { - if (tracker != nullptr) { - tracker->addPastBucket(slice.first, slice.second, mCurrentBucketNum); - } - } - } - mCurrentFullBucket.clear(); - } else { - // Skip aggregating the partial buckets since there's no previous partial bucket. - for (const auto& slice : mCurrentSlicedBucket) { - for (auto& tracker : mAnomalyTrackers) { - if (tracker != nullptr && !slice.second.intervals.empty()) { - // TODO: fix this when anomaly can accept double values - auto& interval = slice.second.intervals[0]; - if (interval.hasValue) { - tracker->addPastBucket(slice.first, interval.value.long_value, - mCurrentBucketNum); - } - } - } - } - } - } else { - // Accumulate partial bucket. - for (const auto& slice : mCurrentSlicedBucket) { - if (!slice.second.intervals.empty()) { - // TODO: fix this when anomaly can accept double values - auto& interval = slice.second.intervals[0]; - if (interval.hasValue) { - mCurrentFullBucket[slice.first] += interval.value.long_value; - } - } - } - } -} - -size_t ValueMetricProducer::byteSizeLocked() const { - size_t totalSize = 0; - for (const auto& pair : mPastBuckets) { - totalSize += pair.second.size() * kBucketSize; - } - return totalSize; -} - -} // namespace statsd -} // namespace os -} // namespace android diff --git a/bin/src/metrics/ValueMetricProducer.h b/bin/src/metrics/ValueMetricProducer.h deleted file mode 100644 index 1de05241..00000000 --- a/bin/src/metrics/ValueMetricProducer.h +++ /dev/null @@ -1,396 +0,0 @@ -/* - * 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. - */ - -#pragma once - -#include -#include "anomaly/AnomalyTracker.h" -#include "condition/ConditionTimer.h" -#include "condition/ConditionTracker.h" -#include "external/PullDataReceiver.h" -#include "external/StatsPullerManager.h" -#include "matchers/EventMatcherWizard.h" -#include "stats_log_util.h" -#include "MetricProducer.h" -#include "packages/modules/StatsD/bin/src/statsd_config.pb.h" - -namespace android { -namespace os { -namespace statsd { - -struct PastValueBucket { - int64_t mBucketStartNs; - int64_t mBucketEndNs; - std::vector valueIndex; - std::vector values; - // If the metric has no condition, then this field is just wasted. - // When we tune statsd memory usage in the future, this is a candidate to optimize. - int64_t mConditionTrueNs; -}; - -// Aggregates values within buckets. -// -// There are different events that might complete a bucket -// - a condition change -// - an app upgrade -// - an alarm set to the end of the bucket -class ValueMetricProducer : public MetricProducer, public virtual PullDataReceiver { -public: - ValueMetricProducer( - const ConfigKey& key, const ValueMetric& valueMetric, const int conditionIndex, - const vector& initialConditionCache, - const sp& conditionWizard, const uint64_t protoHash, - const int whatMatcherIndex, const sp& matcherWizard, - const int pullTagId, const int64_t timeBaseNs, const int64_t startTimeNs, - const sp& pullerManager, - const std::unordered_map>& eventActivationMap = {}, - const std::unordered_map>>& - eventDeactivationMap = {}, - const vector& slicedStateAtoms = {}, - const unordered_map>& stateGroupMap = {}); - - virtual ~ValueMetricProducer(); - - // Process data pulled on bucket boundary. - void onDataPulled(const std::vector>& data, - bool pullSuccess, int64_t originalPullTimeNs) override; - - // ValueMetric needs special logic if it's a pulled atom. - void notifyAppUpgrade(const int64_t& eventTimeNs) override { - std::lock_guard lock(mMutex); - if (!mSplitBucketForAppUpgrade) { - return; - } - if (mIsPulled && mCondition == ConditionState::kTrue) { - pullAndMatchEventsLocked(eventTimeNs); - } - flushCurrentBucketLocked(eventTimeNs, eventTimeNs); - }; - - // ValueMetric needs special logic if it's a pulled atom. - void onStatsdInitCompleted(const int64_t& eventTimeNs) override { - std::lock_guard lock(mMutex); - if (mIsPulled && mCondition == ConditionState::kTrue) { - pullAndMatchEventsLocked(eventTimeNs); - } - flushCurrentBucketLocked(eventTimeNs, eventTimeNs); - }; - - void onStateChanged(int64_t eventTimeNs, int32_t atomId, const HashableDimensionKey& primaryKey, - const FieldValue& oldState, const FieldValue& newState) override; - - MetricType getMetricType() const override { - return METRIC_TYPE_VALUE; - } - -protected: - void onMatchedLogEventInternalLocked( - const size_t matcherIndex, const MetricDimensionKey& eventKey, - const ConditionKey& conditionKey, bool condition, const LogEvent& event, - const std::map& statePrimaryKeys) override; - -private: - void onDumpReportLocked(const int64_t dumpTimeNs, - const bool include_current_partial_bucket, - const bool erase_data, - const DumpLatency dumpLatency, - std::set *str_set, - android::util::ProtoOutputStream* protoOutput) override; - void clearPastBucketsLocked(const int64_t dumpTimeNs) override; - - // Internal interface to handle active state change. - void onActiveStateChangedLocked(const int64_t& eventTimeNs) override; - - // Internal interface to handle condition change. - void onConditionChangedLocked(const bool conditionMet, const int64_t eventTime) override; - - // Internal interface to handle sliced condition change. - void onSlicedConditionMayChangeLocked(bool overallCondition, const int64_t eventTime) override; - - // Internal function to calculate the current used bytes. - size_t byteSizeLocked() const override; - - void dumpStatesLocked(FILE* out, bool verbose) const override; - - // For pulled metrics, this method should only be called if a pull has be done. Else we will - // not have complete data for the bucket. - void flushIfNeededLocked(const int64_t& eventTime) override; - - // For pulled metrics, this method should only be called if a pulled have be done. Else we will - // not have complete data for the bucket. - void flushCurrentBucketLocked(const int64_t& eventTimeNs, - const int64_t& nextBucketStartTimeNs) override; - - void prepareFirstBucketLocked() override; - - void dropDataLocked(const int64_t dropTimeNs) override; - - // Calculate previous bucket end time based on current time. - int64_t calcPreviousBucketEndTime(const int64_t currentTimeNs); - - // Calculate how many buckets are present between the current bucket and eventTimeNs. - int64_t calcBucketsForwardCount(const int64_t& eventTimeNs) const; - - // Mark the data as invalid. - void invalidateCurrentBucket(const int64_t dropTimeNs, const BucketDropReason reason); - - void invalidateCurrentBucketWithoutResetBase(const int64_t dropTimeNs, - const BucketDropReason reason); - - // Skips the current bucket without notifying StatsdStats of the skipped bucket. - // This should only be called from #flushCurrentBucketLocked. Otherwise, a future event that - // causes the bucket to be invalidated will not notify StatsdStats. - void skipCurrentBucket(const int64_t dropTimeNs, const BucketDropReason reason); - - bool onConfigUpdatedLocked( - const StatsdConfig& config, const int configIndex, const int metricIndex, - const std::vector>& allAtomMatchingTrackers, - const std::unordered_map& oldAtomMatchingTrackerMap, - const std::unordered_map& newAtomMatchingTrackerMap, - const sp& matcherWizard, - const std::vector>& allConditionTrackers, - const std::unordered_map& conditionTrackerMap, - const sp& wizard, - const std::unordered_map& metricToActivationMap, - std::unordered_map>& trackerToMetricMap, - std::unordered_map>& conditionToMetricMap, - std::unordered_map>& activationAtomTrackerToMetricMap, - std::unordered_map>& deactivationAtomTrackerToMetricMap, - std::vector& metricsWithActivation) override; - - int mWhatMatcherIndex; - - sp mEventMatcherWizard; - - sp mPullerManager; - - // Value fields for matching. - std::vector mFieldMatchers; - - // Value fields for matching. - std::set mMatchedMetricDimensionKeys; - - // Holds the atom id, primary key pair from a state change. - pair mStateChangePrimaryKey; - - // tagId for pulled data. -1 if this is not pulled - const int mPullTagId; - - // if this is pulled metric - const bool mIsPulled; - - // Tracks the value information of one value field. - typedef struct { - // Index in multi value aggregation. - int valueIndex; - // Current value, depending on the aggregation type. - Value value; - // Number of samples collected. - int sampleSize; - // If this dimension has any non-tainted value. If not, don't report the - // dimension. - bool hasValue = false; - // Whether new data is seen in the bucket. - bool seenNewData = false; - } Interval; - - // Internal state of an ongoing aggregation bucket. - typedef struct CurrentValueBucket { - // If the `MetricDimensionKey` state key is the current state key, then - // the condition timer will be updated later (e.g. condition/state/active - // state change) with the correct condition and time. - CurrentValueBucket() : intervals(), conditionTimer(ConditionTimer(false, 0)) {} - // Value information for each value field of the metric. - std::vector intervals; - // Tracks how long the condition is true. - ConditionTimer conditionTimer; - } CurrentValueBucket; - - // Holds base information for diffing values from one value field. - typedef struct { - // Holds current base value of the dimension. Take diff and update if necessary. - Value base; - // Whether there is a base to diff to. - bool hasBase; - } BaseInfo; - - // State key and base information for a specific DimensionsInWhat key. - typedef struct DimensionsInWhatInfo { - DimensionsInWhatInfo(const HashableDimensionKey& stateKey) - : baseInfos(), currentState(stateKey), hasCurrentState(false) { - } - std::vector baseInfos; - // Last seen state value(s). - HashableDimensionKey currentState; - // Whether this dimensions in what key has a current state key. - bool hasCurrentState; - } DimensionsInWhatInfo; - - // Tracks the internal state in the ongoing aggregation bucket for each DimensionsInWhat - // key and StateValuesKey pair. - std::unordered_map mCurrentSlicedBucket; - - // Tracks current state key and base information for each DimensionsInWhat key. - std::unordered_map mCurrentBaseInfo; - - std::unordered_map mCurrentFullBucket; - - // Save the past buckets and we can clear when the StatsLogReport is dumped. - std::unordered_map> mPastBuckets; - - const int64_t mMinBucketSizeNs; - - // Util function to check whether the specified dimension hits the guardrail. - bool hitGuardRailLocked(const MetricDimensionKey& newKey); - - bool hasReachedGuardRailLimit() const; - - bool hitFullBucketGuardRailLocked(const MetricDimensionKey& newKey); - - void pullAndMatchEventsLocked(const int64_t timestampNs); - - bool multipleBucketsSkipped(const int64_t numBucketsForward); - - void accumulateEvents(const std::vector>& allData, - int64_t originalPullTimeNs, int64_t eventElapsedTimeNs); - - PastValueBucket buildPartialBucket(int64_t bucketEndTime, - const std::vector& intervals); - - void initCurrentSlicedBucket(int64_t nextBucketStartTimeNs); - - void appendToFullBucket(const bool isFullBucketReached); - - // Reset diff base and mHasGlobalBase - void resetBase(); - - // Updates the condition timers in the current sliced bucket when there is a - // condition change or an active state change. - void updateCurrentSlicedBucketConditionTimers(bool newCondition, int64_t eventTimeNs); - - static const size_t kBucketSize = sizeof(PastValueBucket{}); - - const size_t mDimensionSoftLimit; - - const size_t mDimensionHardLimit; - - const bool mUseAbsoluteValueOnReset; - - const ValueMetric::AggregationType mAggregationType; - - const bool mUseDiff; - - const ValueMetric::ValueDirection mValueDirection; - - const bool mSkipZeroDiffOutput; - - // If true, use a zero value as base to compute the diff. - // This is used for new keys which are present in the new data but was not - // present in the base data. - // The default base will only be used if we have a global base. - const bool mUseZeroDefaultBase; - - // For pulled metrics, this is always set to true whenever a pull succeeds. - // It is set to false when a pull fails, or upon condition change to false. - // This is used to decide if we have the right base data to compute the - // diff against. - bool mHasGlobalBase; - - // This is to track whether or not the bucket is skipped for any of the reasons listed in - // BucketDropReason, many of which make the bucket potentially invalid. - bool mCurrentBucketIsSkipped; - - const int64_t mMaxPullDelayNs; - - const bool mSplitBucketForAppUpgrade; - - ConditionTimer mConditionTimer; - - FRIEND_TEST(ValueMetricProducerTest, TestAnomalyDetection); - FRIEND_TEST(ValueMetricProducerTest, TestBaseSetOnConditionChange); - FRIEND_TEST(ValueMetricProducerTest, TestBucketBoundariesOnConditionChange); - FRIEND_TEST(ValueMetricProducerTest, TestBucketBoundaryNoCondition); - FRIEND_TEST(ValueMetricProducerTest, TestBucketBoundaryWithCondition); - FRIEND_TEST(ValueMetricProducerTest, TestBucketBoundaryWithCondition2); - FRIEND_TEST(ValueMetricProducerTest, TestBucketInvalidIfGlobalBaseIsNotSet); - FRIEND_TEST(ValueMetricProducerTest, TestCalcPreviousBucketEndTime); - FRIEND_TEST(ValueMetricProducerTest, TestDataIsNotUpdatedWhenNoConditionChanged); - FRIEND_TEST(ValueMetricProducerTest, TestEmptyDataResetsBase_onBucketBoundary); - FRIEND_TEST(ValueMetricProducerTest, TestEmptyDataResetsBase_onConditionChanged); - FRIEND_TEST(ValueMetricProducerTest, TestEmptyDataResetsBase_onDataPulled); - FRIEND_TEST(ValueMetricProducerTest, TestEventsWithNonSlicedCondition); - FRIEND_TEST(ValueMetricProducerTest, TestFirstBucket); - FRIEND_TEST(ValueMetricProducerTest, TestLateOnDataPulledWithDiff); - FRIEND_TEST(ValueMetricProducerTest, TestLateOnDataPulledWithoutDiff); - FRIEND_TEST(ValueMetricProducerTest, TestPartialResetOnBucketBoundaries); - FRIEND_TEST(ValueMetricProducerTest, TestPulledData_noDiff_bucketBoundaryFalse); - FRIEND_TEST(ValueMetricProducerTest, TestPulledData_noDiff_bucketBoundaryTrue); - FRIEND_TEST(ValueMetricProducerTest, TestPulledData_noDiff_withFailure); - FRIEND_TEST(ValueMetricProducerTest, TestPulledData_noDiff_withMultipleConditionChanges); - FRIEND_TEST(ValueMetricProducerTest, TestPulledData_noDiff_withoutCondition); - FRIEND_TEST(ValueMetricProducerTest, TestPulledEventsNoCondition); - FRIEND_TEST(ValueMetricProducerTest, TestPulledEventsTakeAbsoluteValueOnReset); - FRIEND_TEST(ValueMetricProducerTest, TestPulledEventsTakeZeroOnReset); - FRIEND_TEST(ValueMetricProducerTest, TestPulledEventsWithFiltering); - FRIEND_TEST(ValueMetricProducerTest, TestPulledWithAppUpgradeDisabled); - FRIEND_TEST(ValueMetricProducerTest, TestPushedAggregateAvg); - FRIEND_TEST(ValueMetricProducerTest, TestPushedAggregateMax); - FRIEND_TEST(ValueMetricProducerTest, TestPushedAggregateMin); - FRIEND_TEST(ValueMetricProducerTest, TestPushedAggregateSum); - FRIEND_TEST(ValueMetricProducerTest, TestPushedEventsWithCondition); - FRIEND_TEST(ValueMetricProducerTest, TestPushedEventsWithoutCondition); - FRIEND_TEST(ValueMetricProducerTest, TestResetBaseOnPullDelayExceeded); - FRIEND_TEST(ValueMetricProducerTest, TestResetBaseOnPullFailAfterConditionChange); - FRIEND_TEST(ValueMetricProducerTest, TestResetBaseOnPullFailAfterConditionChange_EndOfBucket); - FRIEND_TEST(ValueMetricProducerTest, TestResetBaseOnPullFailBeforeConditionChange); - FRIEND_TEST(ValueMetricProducerTest, TestResetBaseOnPullTooLate); - FRIEND_TEST(ValueMetricProducerTest, TestSkipZeroDiffOutput); - FRIEND_TEST(ValueMetricProducerTest, TestSkipZeroDiffOutputMultiValue); - FRIEND_TEST(ValueMetricProducerTest, TestSlicedState); - FRIEND_TEST(ValueMetricProducerTest, TestSlicedStateWithMap); - FRIEND_TEST(ValueMetricProducerTest, TestSlicedStateWithPrimaryField_WithDimensions); - FRIEND_TEST(ValueMetricProducerTest, TestSlicedStateWithCondition); - FRIEND_TEST(ValueMetricProducerTest, TestTrimUnusedDimensionKey); - FRIEND_TEST(ValueMetricProducerTest, TestUseZeroDefaultBase); - FRIEND_TEST(ValueMetricProducerTest, TestUseZeroDefaultBaseWithPullFailures); - FRIEND_TEST(ValueMetricProducerTest, TestSlicedStateWithMultipleDimensions); - FRIEND_TEST(ValueMetricProducerTest, TestSlicedStateWithMissingDataInStateChange); - FRIEND_TEST(ValueMetricProducerTest, TestSlicedStateWithDataMissingInConditionChange); - FRIEND_TEST(ValueMetricProducerTest, TestSlicedStateWithMissingDataThenFlushBucket); - FRIEND_TEST(ValueMetricProducerTest, TestSlicedStateWithNoPullOnBucketBoundary); - - FRIEND_TEST(ValueMetricProducerTest_BucketDrop, TestInvalidBucketWhenOneConditionFailed); - FRIEND_TEST(ValueMetricProducerTest_BucketDrop, TestInvalidBucketWhenInitialPullFailed); - FRIEND_TEST(ValueMetricProducerTest_BucketDrop, TestInvalidBucketWhenLastPullFailed); - FRIEND_TEST(ValueMetricProducerTest_BucketDrop, TestInvalidBucketWhenGuardRailHit); - FRIEND_TEST(ValueMetricProducerTest_BucketDrop, - TestInvalidBucketWhenAccumulateEventWrongBucket); - - FRIEND_TEST(ValueMetricProducerTest_PartialBucket, TestBucketBoundariesOnPartialBucket); - FRIEND_TEST(ValueMetricProducerTest_PartialBucket, TestFullBucketResetWhenLastBucketInvalid); - FRIEND_TEST(ValueMetricProducerTest_PartialBucket, TestPartialBucketCreated); - FRIEND_TEST(ValueMetricProducerTest_PartialBucket, TestPushedEvents); - FRIEND_TEST(ValueMetricProducerTest_PartialBucket, TestPulledValue); - FRIEND_TEST(ValueMetricProducerTest_PartialBucket, TestPulledValueWhileConditionFalse); - - FRIEND_TEST(ConfigUpdateTest, TestUpdateValueMetrics); - - friend class ValueMetricProducerTestHelper; -}; - -} // namespace statsd -} // namespace os -} // namespace android diff --git a/bin/src/metrics/duration_helper/DurationTracker.h b/bin/src/metrics/duration_helper/DurationTracker.h deleted file mode 100644 index 849effa1..00000000 --- a/bin/src/metrics/duration_helper/DurationTracker.h +++ /dev/null @@ -1,281 +0,0 @@ -/* - * 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. - */ - -#ifndef DURATION_TRACKER_H -#define DURATION_TRACKER_H - -#include "anomaly/DurationAnomalyTracker.h" -#include "condition/ConditionWizard.h" -#include "config/ConfigKey.h" -#include "metrics/parsing_utils/config_update_utils.h" -#include "stats_util.h" - -namespace android { -namespace os { -namespace statsd { - -enum DurationState { - kStopped = 0, // The event is stopped. - kStarted = 1, // The event is on going. - kPaused = 2, // The event is started, but condition is false, clock is paused. When condition - // turns to true, kPaused will become kStarted. -}; - -// Hold duration information for one atom level duration in current on-going bucket. -struct DurationInfo { - DurationState state; - - // the number of starts seen. - int32_t startCount; - - // most recent start time. - int64_t lastStartTime; - // existing duration in current bucket. - int64_t lastDuration; - // cache the HashableDimensionKeys we need to query the condition for this duration event. - ConditionKey conditionKeys; - - DurationInfo() : state(kStopped), startCount(0), lastStartTime(0), lastDuration(0){}; -}; - -struct DurationBucket { - int64_t mBucketStartNs; - int64_t mBucketEndNs; - int64_t mDuration; -}; - -struct DurationValues { - // Recorded duration for current partial bucket. - int64_t mDuration; - - // Sum of past partial bucket durations in current full bucket. - // Used for anomaly detection. - int64_t mDurationFullBucket; -}; - -class DurationTracker { -public: - DurationTracker(const ConfigKey& key, const int64_t& id, const MetricDimensionKey& eventKey, - sp wizard, int conditionIndex, bool nesting, - int64_t currentBucketStartNs, int64_t currentBucketNum, int64_t startTimeNs, - int64_t bucketSizeNs, bool conditionSliced, bool fullLink, - const std::vector>& anomalyTrackers) - : mConfigKey(key), - mTrackerId(id), - mEventKey(eventKey), - mWizard(wizard), - mConditionTrackerIndex(conditionIndex), - mBucketSizeNs(bucketSizeNs), - mNested(nesting), - mCurrentBucketStartTimeNs(currentBucketStartNs), - mDuration(0), - mCurrentBucketNum(currentBucketNum), - mStartTimeNs(startTimeNs), - mConditionSliced(conditionSliced), - mHasLinksToAllConditionDimensionsInTracker(fullLink), - mAnomalyTrackers(anomalyTrackers){}; - - virtual ~DurationTracker(){}; - - void onConfigUpdated(const sp& wizard, const int conditionTrackerIndex) { - sp tmpWizard = mWizard; - mWizard = wizard; - mConditionTrackerIndex = conditionTrackerIndex; - mAnomalyTrackers.clear(); - }; - - virtual void noteStart(const HashableDimensionKey& key, bool condition, const int64_t eventTime, - const ConditionKey& conditionKey) = 0; - virtual void noteStop(const HashableDimensionKey& key, const int64_t eventTime, - const bool stopAll) = 0; - virtual void noteStopAll(const int64_t eventTime) = 0; - - virtual void onSlicedConditionMayChange(bool overallCondition, const int64_t timestamp) = 0; - virtual void onConditionChanged(bool condition, const int64_t timestamp) = 0; - - virtual void onStateChanged(const int64_t timestamp, const int32_t atomId, - const FieldValue& newState) = 0; - - // Flush stale buckets if needed, and return true if the tracker has no on-going duration - // events, so that the owner can safely remove the tracker. - virtual bool flushIfNeeded( - int64_t timestampNs, const optional& uploadThreshold, - std::unordered_map>* output) = 0; - - // Should only be called during an app upgrade or from this tracker's flushIfNeeded. If from - // an app upgrade, we assume that we're trying to form a partial bucket. - virtual bool flushCurrentBucket( - const int64_t& eventTimeNs, const optional& uploadThreshold, - std::unordered_map>* output) = 0; - - // Predict the anomaly timestamp given the current status. - virtual int64_t predictAnomalyTimestampNs(const AnomalyTracker& anomalyTracker, - const int64_t currentTimestamp) const = 0; - // Dump internal states for debugging - virtual void dumpStates(FILE* out, bool verbose) const = 0; - - virtual int64_t getCurrentStateKeyDuration() const = 0; - - virtual int64_t getCurrentStateKeyFullBucketDuration() const = 0; - - // Replace old value with new value for the given state atom. - virtual void updateCurrentStateKey(const int32_t atomId, const FieldValue& newState) = 0; - - void addAnomalyTracker(sp& anomalyTracker, const UpdateStatus& updateStatus, - const int64_t updateTimeNs) { - mAnomalyTrackers.push_back(anomalyTracker); - // Preserved anomaly trackers will have the correct alarm times. - // New/replaced alerts will need to set alarms for pending durations, or may have already - // fired if the full bucket duration is high enough. - // NB: this depends on a config updating that splits a partial bucket having just happened. - // If this constraint changes, predict will return the wrong timestamp. - if (updateStatus == UpdateStatus::UPDATE_NEW || - updateStatus == UpdateStatus::UPDATE_PRESERVE) { - const int64_t alarmTimeNs = predictAnomalyTimestampNs(*anomalyTracker, updateTimeNs); - if (alarmTimeNs <= updateTimeNs || hasAccumulatingDuration()) { - anomalyTracker->startAlarm(mEventKey, std::max(alarmTimeNs, updateTimeNs)); - } - } - } - -protected: - virtual bool hasAccumulatingDuration() = 0; - - int64_t getCurrentBucketEndTimeNs() const { - return mStartTimeNs + (mCurrentBucketNum + 1) * mBucketSizeNs; - } - - // Starts the anomaly alarm. - void startAnomalyAlarm(const int64_t eventTime) { - for (auto& anomalyTracker : mAnomalyTrackers) { - if (anomalyTracker != nullptr) { - const int64_t alarmTimestampNs = - predictAnomalyTimestampNs(*anomalyTracker, eventTime); - if (alarmTimestampNs > 0) { - anomalyTracker->startAlarm(mEventKey, alarmTimestampNs); - } - } - } - } - - // Stops the anomaly alarm. If it should have already fired, declare the anomaly now. - void stopAnomalyAlarm(const int64_t timestamp) { - for (auto& anomalyTracker : mAnomalyTrackers) { - if (anomalyTracker != nullptr) { - anomalyTracker->stopAlarm(mEventKey, timestamp); - } - } - } - - void addPastBucketToAnomalyTrackers(const MetricDimensionKey eventKey, - const int64_t& bucketValue, const int64_t& bucketNum) { - for (auto& anomalyTracker : mAnomalyTrackers) { - if (anomalyTracker != nullptr) { - anomalyTracker->addPastBucket(eventKey, bucketValue, bucketNum); - } - } - } - - void detectAndDeclareAnomaly(const int64_t& timestamp, const int64_t& currBucketNum, - const int64_t& currentBucketValue) { - for (auto& anomalyTracker : mAnomalyTrackers) { - if (anomalyTracker != nullptr) { - anomalyTracker->detectAndDeclareAnomaly(timestamp, currBucketNum, mTrackerId, - mEventKey, currentBucketValue); - } - } - } - - // Convenience to compute the current bucket's end time, which is always aligned with the - // start time of the metric. - int64_t getCurrentBucketEndTimeNs() { - return mStartTimeNs + (mCurrentBucketNum + 1) * mBucketSizeNs; - } - - void setEventKey(const MetricDimensionKey& eventKey) { - mEventKey = eventKey; - } - - bool durationPassesThreshold(const optional& uploadThreshold, - int64_t duration) { - if (duration <= 0) { - return false; - } - - if (uploadThreshold == nullopt) { - return true; - } - - switch (uploadThreshold->value_comparison_case()) { - case UploadThreshold::kLtInt: - return duration < uploadThreshold->lt_int(); - case UploadThreshold::kGtInt: - return duration > uploadThreshold->gt_int(); - case UploadThreshold::kLteInt: - return duration <= uploadThreshold->lte_int(); - case UploadThreshold::kGteInt: - return duration >= uploadThreshold->gte_int(); - default: - ALOGE("Duration metric incorrect upload threshold type used"); - return false; - } - } - - // A reference to the DurationMetricProducer's config key. - const ConfigKey& mConfigKey; - - const int64_t mTrackerId; - - MetricDimensionKey mEventKey; - - sp mWizard; - - int mConditionTrackerIndex; - - const int64_t mBucketSizeNs; - - const bool mNested; - - int64_t mCurrentBucketStartTimeNs; - - int64_t mDuration; // current recorded duration result (for partial bucket) - - // Recorded duration results for each state key in the current partial bucket. - std::unordered_map mStateKeyDurationMap; - - int64_t mCurrentBucketNum; - - const int64_t mStartTimeNs; - - const bool mConditionSliced; - - bool mHasLinksToAllConditionDimensionsInTracker; - - std::vector> mAnomalyTrackers; - - FRIEND_TEST(OringDurationTrackerTest, TestPredictAnomalyTimestamp); - FRIEND_TEST(OringDurationTrackerTest, TestAnomalyDetectionExpiredAlarm); - FRIEND_TEST(OringDurationTrackerTest, TestAnomalyDetectionFiredAlarm); - - FRIEND_TEST(ConfigUpdateTest, TestUpdateDurationMetrics); - FRIEND_TEST(ConfigUpdateTest, TestUpdateAlerts); -}; - -} // namespace statsd -} // namespace os -} // namespace android - -#endif // DURATION_TRACKER_H diff --git a/bin/src/metrics/duration_helper/MaxDurationTracker.cpp b/bin/src/metrics/duration_helper/MaxDurationTracker.cpp deleted file mode 100644 index 0ee5e46d..00000000 --- a/bin/src/metrics/duration_helper/MaxDurationTracker.cpp +++ /dev/null @@ -1,334 +0,0 @@ -/* - * 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. - */ - -#define DEBUG false - -#include "Log.h" -#include "MaxDurationTracker.h" -#include "guardrail/StatsdStats.h" - -namespace android { -namespace os { -namespace statsd { - -MaxDurationTracker::MaxDurationTracker(const ConfigKey& key, const int64_t& id, - const MetricDimensionKey& eventKey, - sp wizard, int conditionIndex, bool nesting, - int64_t currentBucketStartNs, int64_t currentBucketNum, - int64_t startTimeNs, int64_t bucketSizeNs, - bool conditionSliced, bool fullLink, - const vector>& anomalyTrackers) - : DurationTracker(key, id, eventKey, wizard, conditionIndex, nesting, currentBucketStartNs, - currentBucketNum, startTimeNs, bucketSizeNs, conditionSliced, fullLink, - anomalyTrackers) { -} - -bool MaxDurationTracker::hitGuardRail(const HashableDimensionKey& newKey) { - // ===========GuardRail============== - if (mInfos.find(newKey) != mInfos.end()) { - // if the key existed, we are good! - return false; - } - // 1. Report the tuple count if the tuple count > soft limit - if (mInfos.size() > StatsdStats::kDimensionKeySizeSoftLimit - 1) { - size_t newTupleCount = mInfos.size() + 1; - StatsdStats::getInstance().noteMetricDimensionSize(mConfigKey, mTrackerId, newTupleCount); - // 2. Don't add more tuples, we are above the allowed threshold. Drop the data. - if (newTupleCount > StatsdStats::kDimensionKeySizeHardLimit) { - ALOGE("MaxDurTracker %lld dropping data for dimension key %s", - (long long)mTrackerId, newKey.toString().c_str()); - return true; - } - } - return false; -} - -void MaxDurationTracker::noteStart(const HashableDimensionKey& key, bool condition, - const int64_t eventTime, const ConditionKey& conditionKey) { - // this will construct a new DurationInfo if this key didn't exist. - if (hitGuardRail(key)) { - return; - } - - DurationInfo& duration = mInfos[key]; - if (mConditionSliced) { - duration.conditionKeys = conditionKey; - } - VLOG("MaxDuration: key %s start condition %d", key.toString().c_str(), condition); - - switch (duration.state) { - case kStarted: - duration.startCount++; - break; - case kPaused: - duration.startCount++; - break; - case kStopped: - if (!condition) { - // event started, but we need to wait for the condition to become true. - duration.state = DurationState::kPaused; - } else { - duration.state = DurationState::kStarted; - duration.lastStartTime = eventTime; - startAnomalyAlarm(eventTime); - } - duration.startCount = 1; - break; - } -} - -void MaxDurationTracker::noteStop(const HashableDimensionKey& key, const int64_t eventTime, - bool forceStop) { - VLOG("MaxDuration: key %s stop", key.toString().c_str()); - if (mInfos.find(key) == mInfos.end()) { - // we didn't see a start event before. do nothing. - return; - } - DurationInfo& duration = mInfos[key]; - - switch (duration.state) { - case DurationState::kStopped: - // already stopped, do nothing. - break; - case DurationState::kStarted: { - duration.startCount--; - if (forceStop || !mNested || duration.startCount <= 0) { - stopAnomalyAlarm(eventTime); - duration.state = DurationState::kStopped; - int64_t durationTime = eventTime - duration.lastStartTime; - VLOG("Max, key %s, Stop %lld %lld %lld", key.toString().c_str(), - (long long)duration.lastStartTime, (long long)eventTime, - (long long)durationTime); - duration.lastDuration += durationTime; - if (hasAccumulatingDuration()) { - // In case any other dimensions are still started, we need to keep the alarm - // set. - startAnomalyAlarm(eventTime); - } - VLOG(" record duration: %lld ", (long long)duration.lastDuration); - } - break; - } - case DurationState::kPaused: { - duration.startCount--; - if (forceStop || !mNested || duration.startCount <= 0) { - duration.state = DurationState::kStopped; - } - break; - } - } - - if (duration.lastDuration > mDuration) { - mDuration = duration.lastDuration; - VLOG("Max: new max duration: %lld", (long long)mDuration); - } - // Once an atom duration ends, we erase it. Next time, if we see another atom event with the - // same name, they are still considered as different atom durations. - if (duration.state == DurationState::kStopped) { - mInfos.erase(key); - } -} - -bool MaxDurationTracker::hasAccumulatingDuration() { - for (auto& pair : mInfos) { - if (pair.second.state == kStarted) { - return true; - } - } - return false; -} - -void MaxDurationTracker::noteStopAll(const int64_t eventTime) { - std::set keys; - for (const auto& pair : mInfos) { - keys.insert(pair.first); - } - for (auto& key : keys) { - noteStop(key, eventTime, true); - } -} - -bool MaxDurationTracker::flushCurrentBucket( - const int64_t& eventTimeNs, const optional& uploadThreshold, - std::unordered_map>* output) { - VLOG("MaxDurationTracker flushing....."); - - // adjust the bucket start time - int numBucketsForward = 0; - int64_t fullBucketEnd = getCurrentBucketEndTimeNs(); - int64_t currentBucketEndTimeNs; - if (eventTimeNs >= fullBucketEnd) { - numBucketsForward = 1 + (eventTimeNs - fullBucketEnd) / mBucketSizeNs; - currentBucketEndTimeNs = fullBucketEnd; - } else { - // This must be a partial bucket. - currentBucketEndTimeNs = eventTimeNs; - } - - bool hasPendingEvent = - false; // has either a kStarted or kPaused event across bucket boundaries - // meaning we need to carry them over to the new bucket. - for (auto it = mInfos.begin(); it != mInfos.end();) { - if (it->second.state == DurationState::kStopped) { - // No need to keep buckets for events that were stopped before. - it = mInfos.erase(it); - } else { - ++it; - hasPendingEvent = true; - } - } - - // mDuration is updated in noteStop to the maximum duration that ended in the current bucket. - if (durationPassesThreshold(uploadThreshold, mDuration)) { - DurationBucket info; - info.mBucketStartNs = mCurrentBucketStartTimeNs; - info.mBucketEndNs = currentBucketEndTimeNs; - info.mDuration = mDuration; - (*output)[mEventKey].push_back(info); - VLOG(" final duration for last bucket: %lld", (long long)mDuration); - } else { - VLOG(" duration: %lld does not pass set threshold", (long long)mDuration); - } - - if (numBucketsForward > 0) { - mCurrentBucketStartTimeNs = fullBucketEnd + (numBucketsForward - 1) * mBucketSizeNs; - mCurrentBucketNum += numBucketsForward; - } else { // We must be forming a partial bucket. - mCurrentBucketStartTimeNs = eventTimeNs; - } - - mDuration = 0; - // If this tracker has no pending events, tell owner to remove. - return !hasPendingEvent; -} - -bool MaxDurationTracker::flushIfNeeded( - int64_t eventTimeNs, const optional& uploadThreshold, - unordered_map>* output) { - if (eventTimeNs < getCurrentBucketEndTimeNs()) { - return false; - } - return flushCurrentBucket(eventTimeNs, uploadThreshold, output); -} - -void MaxDurationTracker::onSlicedConditionMayChange(bool overallCondition, - const int64_t timestamp) { - // Now for each of the on-going event, check if the condition has changed for them. - for (auto& pair : mInfos) { - if (pair.second.state == kStopped) { - continue; - } - ConditionState conditionState = mWizard->query( - mConditionTrackerIndex, pair.second.conditionKeys, - !mHasLinksToAllConditionDimensionsInTracker); - bool conditionMet = (conditionState == ConditionState::kTrue); - - VLOG("key: %s, condition: %d", pair.first.toString().c_str(), conditionMet); - noteConditionChanged(pair.first, conditionMet, timestamp); - } -} - -void MaxDurationTracker::onStateChanged(const int64_t timestamp, const int32_t atomId, - const FieldValue& newState) { - ALOGE("MaxDurationTracker does not handle sliced state changes."); -} - -void MaxDurationTracker::onConditionChanged(bool condition, const int64_t timestamp) { - for (auto& pair : mInfos) { - noteConditionChanged(pair.first, condition, timestamp); - } -} - -void MaxDurationTracker::noteConditionChanged(const HashableDimensionKey& key, bool conditionMet, - const int64_t timestamp) { - auto it = mInfos.find(key); - if (it == mInfos.end()) { - return; - } - - switch (it->second.state) { - case kStarted: - // If condition becomes false, kStarted -> kPaused. Record the current duration and - // stop anomaly alarm. - if (!conditionMet) { - stopAnomalyAlarm(timestamp); - it->second.state = DurationState::kPaused; - it->second.lastDuration += (timestamp - it->second.lastStartTime); - if (hasAccumulatingDuration()) { - // In case any other dimensions are still started, we need to set the alarm. - startAnomalyAlarm(timestamp); - } - VLOG("MaxDurationTracker Key: %s Started->Paused ", key.toString().c_str()); - } - break; - case kStopped: - // Nothing to do if it's stopped. - break; - case kPaused: - // If condition becomes true, kPaused -> kStarted. and the start time is the condition - // change time. - if (conditionMet) { - it->second.state = DurationState::kStarted; - it->second.lastStartTime = timestamp; - startAnomalyAlarm(timestamp); - VLOG("MaxDurationTracker Key: %s Paused->Started", key.toString().c_str()); - } - break; - } - // Note that we don't update mDuration here since it's only updated during noteStop. -} - -int64_t MaxDurationTracker::predictAnomalyTimestampNs(const AnomalyTracker& anomalyTracker, - const int64_t currentTimestamp) const { - // The allowed time we can continue in the current state is the - // (anomaly threshold) - max(elapsed time of the started mInfos). - int64_t maxElapsed = 0; - for (auto it = mInfos.begin(); it != mInfos.end(); ++it) { - if (it->second.state == DurationState::kStarted) { - int64_t duration = - it->second.lastDuration + (currentTimestamp - it->second.lastStartTime); - if (duration > maxElapsed) { - maxElapsed = duration; - } - } - } - int64_t anomalyTimeNs = currentTimestamp + anomalyTracker.getAnomalyThreshold() - maxElapsed; - int64_t refractoryEndNs = anomalyTracker.getRefractoryPeriodEndsSec(mEventKey) * NS_PER_SEC; - return std::max(anomalyTimeNs, refractoryEndNs); -} - -void MaxDurationTracker::dumpStates(FILE* out, bool verbose) const { - fprintf(out, "\t\t sub-durations %lu\n", (unsigned long)mInfos.size()); - fprintf(out, "\t\t current duration %lld\n", (long long)mDuration); -} - -int64_t MaxDurationTracker::getCurrentStateKeyDuration() const { - ALOGE("MaxDurationTracker does not handle sliced state changes."); - return -1; -} - -int64_t MaxDurationTracker::getCurrentStateKeyFullBucketDuration() const { - ALOGE("MaxDurationTracker does not handle sliced state changes."); - return -1; -} - -void MaxDurationTracker::updateCurrentStateKey(const int32_t atomId, const FieldValue& newState) { - ALOGE("MaxDurationTracker does not handle sliced state changes."); -} - -} // namespace statsd -} // namespace os -} // namespace android diff --git a/bin/src/metrics/duration_helper/MaxDurationTracker.h b/bin/src/metrics/duration_helper/MaxDurationTracker.h deleted file mode 100644 index b1fdd6e6..00000000 --- a/bin/src/metrics/duration_helper/MaxDurationTracker.h +++ /dev/null @@ -1,94 +0,0 @@ -/* - * 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. - */ - -#ifndef MAX_DURATION_TRACKER_H -#define MAX_DURATION_TRACKER_H - -#include "DurationTracker.h" - -namespace android { -namespace os { -namespace statsd { - -// Tracks a pool of atom durations, and output the max duration for each bucket. -// To get max duration, we need to keep track of each individual durations, and compare them when -// they stop or bucket expires. -class MaxDurationTracker : public DurationTracker { -public: - MaxDurationTracker(const ConfigKey& key, const int64_t& id, const MetricDimensionKey& eventKey, - sp wizard, int conditionIndex, bool nesting, - int64_t currentBucketStartNs, int64_t currentBucketNum, int64_t startTimeNs, - int64_t bucketSizeNs, bool conditionSliced, bool fullLink, - const std::vector>& anomalyTrackers); - - MaxDurationTracker(const MaxDurationTracker& tracker) = default; - - void noteStart(const HashableDimensionKey& key, bool condition, const int64_t eventTime, - const ConditionKey& conditionKey) override; - void noteStop(const HashableDimensionKey& key, const int64_t eventTime, - const bool stopAll) override; - void noteStopAll(const int64_t eventTime) override; - - bool flushIfNeeded( - int64_t timestampNs, const optional& uploadThreshold, - std::unordered_map>* output) override; - bool flushCurrentBucket( - const int64_t& eventTimeNs, const optional& uploadThreshold, - std::unordered_map>*) override; - - void onSlicedConditionMayChange(bool overallCondition, const int64_t timestamp) override; - void onConditionChanged(bool condition, const int64_t timestamp) override; - - void onStateChanged(const int64_t timestamp, const int32_t atomId, - const FieldValue& newState) override; - - int64_t predictAnomalyTimestampNs(const AnomalyTracker& anomalyTracker, - const int64_t currentTimestamp) const override; - void dumpStates(FILE* out, bool verbose) const override; - - int64_t getCurrentStateKeyDuration() const override; - - int64_t getCurrentStateKeyFullBucketDuration() const override; - - void updateCurrentStateKey(const int32_t atomId, const FieldValue& newState); - -protected: - // Returns true if at least one of the mInfos is started. - bool hasAccumulatingDuration() override; - -private: - std::unordered_map mInfos; - - void noteConditionChanged(const HashableDimensionKey& key, bool conditionMet, - const int64_t timestamp); - - // return true if we should not allow newKey to be tracked because we are above the threshold - bool hitGuardRail(const HashableDimensionKey& newKey); - - FRIEND_TEST(MaxDurationTrackerTest, TestSimpleMaxDuration); - FRIEND_TEST(MaxDurationTrackerTest, TestCrossBucketBoundary); - FRIEND_TEST(MaxDurationTrackerTest, TestMaxDurationWithCondition); - FRIEND_TEST(MaxDurationTrackerTest, TestStopAll); - FRIEND_TEST(MaxDurationTrackerTest, TestAnomalyDetection); - FRIEND_TEST(MaxDurationTrackerTest, TestAnomalyPredictedTimestamp); - FRIEND_TEST(MaxDurationTrackerTest, TestUploadThreshold); -}; - -} // namespace statsd -} // namespace os -} // namespace android - -#endif // MAX_DURATION_TRACKER_H diff --git a/bin/src/metrics/duration_helper/OringDurationTracker.cpp b/bin/src/metrics/duration_helper/OringDurationTracker.cpp deleted file mode 100644 index b67e25c4..00000000 --- a/bin/src/metrics/duration_helper/OringDurationTracker.cpp +++ /dev/null @@ -1,473 +0,0 @@ -/* - * 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. - */ -#define DEBUG false -#include "Log.h" -#include "OringDurationTracker.h" -#include "guardrail/StatsdStats.h" - -namespace android { -namespace os { -namespace statsd { - -using std::pair; - -OringDurationTracker::OringDurationTracker( - const ConfigKey& key, const int64_t& id, const MetricDimensionKey& eventKey, - sp wizard, int conditionIndex, bool nesting, int64_t currentBucketStartNs, - int64_t currentBucketNum, int64_t startTimeNs, int64_t bucketSizeNs, bool conditionSliced, - bool fullLink, const vector>& anomalyTrackers) - : DurationTracker(key, id, eventKey, wizard, conditionIndex, nesting, currentBucketStartNs, - currentBucketNum, startTimeNs, bucketSizeNs, conditionSliced, fullLink, - anomalyTrackers), - mStarted(), - mPaused() { - mLastStartTime = 0; -} - -bool OringDurationTracker::hitGuardRail(const HashableDimensionKey& newKey) { - // ===========GuardRail============== - // 1. Report the tuple count if the tuple count > soft limit - if (mConditionKeyMap.find(newKey) != mConditionKeyMap.end()) { - return false; - } - if (mConditionKeyMap.size() > StatsdStats::kDimensionKeySizeSoftLimit - 1) { - size_t newTupleCount = mConditionKeyMap.size() + 1; - StatsdStats::getInstance().noteMetricDimensionSize(mConfigKey, mTrackerId, newTupleCount); - // 2. Don't add more tuples, we are above the allowed threshold. Drop the data. - if (newTupleCount > StatsdStats::kDimensionKeySizeHardLimit) { - ALOGE("OringDurTracker %lld dropping data for dimension key %s", - (long long)mTrackerId, newKey.toString().c_str()); - return true; - } - } - return false; -} - -void OringDurationTracker::noteStart(const HashableDimensionKey& key, bool condition, - const int64_t eventTime, const ConditionKey& conditionKey) { - if (hitGuardRail(key)) { - return; - } - if (condition) { - if (mStarted.size() == 0) { - mLastStartTime = eventTime; - VLOG("record first start...."); - startAnomalyAlarm(eventTime); - } - mStarted[key]++; - } else { - mPaused[key]++; - } - - if (mConditionSliced && mConditionKeyMap.find(key) == mConditionKeyMap.end()) { - mConditionKeyMap[key] = conditionKey; - } - VLOG("Oring: %s start, condition %d", key.toString().c_str(), condition); -} - -void OringDurationTracker::noteStop(const HashableDimensionKey& key, const int64_t timestamp, - const bool stopAll) { - VLOG("Oring: %s stop", key.toString().c_str()); - auto it = mStarted.find(key); - if (it != mStarted.end()) { - (it->second)--; - if (stopAll || !mNested || it->second <= 0) { - mStarted.erase(it); - mConditionKeyMap.erase(key); - } - if (mStarted.empty()) { - mStateKeyDurationMap[mEventKey.getStateValuesKey()].mDuration += - (timestamp - mLastStartTime); - detectAndDeclareAnomaly( - timestamp, mCurrentBucketNum, - getCurrentStateKeyDuration() + getCurrentStateKeyFullBucketDuration()); - VLOG("record duration %lld, total duration %lld for state key %s", - (long long)timestamp - mLastStartTime, (long long)getCurrentStateKeyDuration(), - mEventKey.getStateValuesKey().toString().c_str()); - } - } - - auto pausedIt = mPaused.find(key); - if (pausedIt != mPaused.end()) { - (pausedIt->second)--; - if (stopAll || !mNested || pausedIt->second <= 0) { - mPaused.erase(pausedIt); - mConditionKeyMap.erase(key); - } - } - if (mStarted.empty()) { - stopAnomalyAlarm(timestamp); - } -} - -void OringDurationTracker::noteStopAll(const int64_t timestamp) { - if (!mStarted.empty()) { - mStateKeyDurationMap[mEventKey.getStateValuesKey()].mDuration += - (timestamp - mLastStartTime); - VLOG("Oring Stop all: record duration %lld, total duration %lld for state key %s", - (long long)timestamp - mLastStartTime, (long long)getCurrentStateKeyDuration(), - mEventKey.getStateValuesKey().toString().c_str()); - detectAndDeclareAnomaly( - timestamp, mCurrentBucketNum, - getCurrentStateKeyDuration() + getCurrentStateKeyFullBucketDuration()); - } - - stopAnomalyAlarm(timestamp); - mStarted.clear(); - mPaused.clear(); - mConditionKeyMap.clear(); -} - -bool OringDurationTracker::flushCurrentBucket( - const int64_t& eventTimeNs, const optional& uploadThreshold, - std::unordered_map>* output) { - VLOG("OringDurationTracker Flushing............."); - - // Note that we have to mimic the bucket time changes we do in the - // MetricProducer#notifyAppUpgrade. - - int numBucketsForward = 0; - int64_t fullBucketEnd = getCurrentBucketEndTimeNs(); - int64_t currentBucketEndTimeNs; - - bool isFullBucket = eventTimeNs >= fullBucketEnd; - if (isFullBucket) { - numBucketsForward = 1 + (eventTimeNs - fullBucketEnd) / mBucketSizeNs; - currentBucketEndTimeNs = fullBucketEnd; - } else { - // This must be a partial bucket. - currentBucketEndTimeNs = eventTimeNs; - } - - // Process the current bucket. - if (mStarted.size() > 0) { - // Calculate the duration for the current state key. - mStateKeyDurationMap[mEventKey.getStateValuesKey()].mDuration += - (currentBucketEndTimeNs - mLastStartTime); - } - // Store DurationBucket info for each whatKey, stateKey pair. - // Note: The whatKey stored in mEventKey is constant for each DurationTracker, while the - // stateKey stored in mEventKey is only the current stateKey. mStateKeyDurationMap is used to - // store durations for each stateKey, so we need to flush the bucket by creating a - // DurationBucket for each stateKey. - for (auto& durationIt : mStateKeyDurationMap) { - if (durationPassesThreshold(uploadThreshold, durationIt.second.mDuration)) { - DurationBucket current_info; - current_info.mBucketStartNs = mCurrentBucketStartTimeNs; - current_info.mBucketEndNs = currentBucketEndTimeNs; - current_info.mDuration = durationIt.second.mDuration; - (*output)[MetricDimensionKey(mEventKey.getDimensionKeyInWhat(), durationIt.first)] - .push_back(current_info); - - durationIt.second.mDurationFullBucket += durationIt.second.mDuration; - VLOG(" duration: %lld", (long long)current_info.mDuration); - } else { - VLOG(" duration: %lld does not pass set threshold", (long long)mDuration); - } - - if (isFullBucket) { - // End of full bucket, can send to anomaly tracker now. - addPastBucketToAnomalyTrackers( - MetricDimensionKey(mEventKey.getDimensionKeyInWhat(), durationIt.first), - getCurrentStateKeyFullBucketDuration(), mCurrentBucketNum); - durationIt.second.mDurationFullBucket = 0; - } - durationIt.second.mDuration = 0; - } - - if (mStarted.size() > 0) { - for (int i = 1; i < numBucketsForward; i++) { - DurationBucket info; - info.mBucketStartNs = fullBucketEnd + mBucketSizeNs * (i - 1); - info.mBucketEndNs = info.mBucketStartNs + mBucketSizeNs; - info.mDuration = mBucketSizeNs; - // Full duration buckets are attributed to the current stateKey. - (*output)[mEventKey].push_back(info); - // Safe to send these buckets to anomaly tracker since they must be full buckets. - // If it's a partial bucket, numBucketsForward would be 0. - addPastBucketToAnomalyTrackers(mEventKey, info.mDuration, mCurrentBucketNum + i); - VLOG(" add filling bucket with duration %lld", (long long)info.mDuration); - } - } else { - if (numBucketsForward >= 2) { - addPastBucketToAnomalyTrackers(mEventKey, 0, mCurrentBucketNum + numBucketsForward - 1); - } - } - - if (numBucketsForward > 0) { - mCurrentBucketStartTimeNs = fullBucketEnd + (numBucketsForward - 1) * mBucketSizeNs; - mCurrentBucketNum += numBucketsForward; - } else { // We must be forming a partial bucket. - mCurrentBucketStartTimeNs = eventTimeNs; - } - mLastStartTime = mCurrentBucketStartTimeNs; - - // If all stopped, then tell owner it's safe to remove this tracker on a full bucket. - // On a partial bucket, only clear if no anomaly trackers, as full bucket duration is used - // for anomaly detection. - // Note: Anomaly trackers can be added on config updates, in which case mAnomalyTrackers > 0 and - // the full bucket duration could be used, but this is very rare so it is okay to clear. - return mStarted.empty() && mPaused.empty() && (isFullBucket || mAnomalyTrackers.size() == 0); -} - -bool OringDurationTracker::flushIfNeeded( - int64_t eventTimeNs, const optional& uploadThreshold, - unordered_map>* output) { - if (eventTimeNs < getCurrentBucketEndTimeNs()) { - return false; - } - return flushCurrentBucket(eventTimeNs, uploadThreshold, output); -} - -void OringDurationTracker::onSlicedConditionMayChange(bool overallCondition, - const int64_t timestamp) { - vector> startedToPaused; - vector> pausedToStarted; - if (!mStarted.empty()) { - for (auto it = mStarted.begin(); it != mStarted.end();) { - const auto& key = it->first; - const auto& condIt = mConditionKeyMap.find(key); - if (condIt == mConditionKeyMap.end()) { - VLOG("Key %s dont have condition key", key.toString().c_str()); - ++it; - continue; - } - ConditionState conditionState = - mWizard->query(mConditionTrackerIndex, condIt->second, - !mHasLinksToAllConditionDimensionsInTracker); - if (conditionState != ConditionState::kTrue) { - startedToPaused.push_back(*it); - it = mStarted.erase(it); - VLOG("Key %s started -> paused", key.toString().c_str()); - } else { - ++it; - } - } - - if (mStarted.empty()) { - mStateKeyDurationMap[mEventKey.getStateValuesKey()].mDuration += - (timestamp - mLastStartTime); - VLOG("record duration %lld, total duration %lld for state key %s", - (long long)(timestamp - mLastStartTime), (long long)getCurrentStateKeyDuration(), - mEventKey.getStateValuesKey().toString().c_str()); - detectAndDeclareAnomaly( - timestamp, mCurrentBucketNum, - getCurrentStateKeyDuration() + getCurrentStateKeyFullBucketDuration()); - } - } - - if (!mPaused.empty()) { - for (auto it = mPaused.begin(); it != mPaused.end();) { - const auto& key = it->first; - if (mConditionKeyMap.find(key) == mConditionKeyMap.end()) { - VLOG("Key %s dont have condition key", key.toString().c_str()); - ++it; - continue; - } - ConditionState conditionState = - mWizard->query(mConditionTrackerIndex, mConditionKeyMap[key], - !mHasLinksToAllConditionDimensionsInTracker); - if (conditionState == ConditionState::kTrue) { - pausedToStarted.push_back(*it); - it = mPaused.erase(it); - VLOG("Key %s paused -> started", key.toString().c_str()); - } else { - ++it; - } - } - - if (mStarted.empty() && pausedToStarted.size() > 0) { - mLastStartTime = timestamp; - } - } - - if (mStarted.empty() && !pausedToStarted.empty()) { - startAnomalyAlarm(timestamp); - } - mStarted.insert(pausedToStarted.begin(), pausedToStarted.end()); - mPaused.insert(startedToPaused.begin(), startedToPaused.end()); - - if (mStarted.empty()) { - stopAnomalyAlarm(timestamp); - } -} - -void OringDurationTracker::onConditionChanged(bool condition, const int64_t timestamp) { - if (condition) { - if (!mPaused.empty()) { - VLOG("Condition true, all started"); - if (mStarted.empty()) { - mLastStartTime = timestamp; - } - if (mStarted.empty() && !mPaused.empty()) { - startAnomalyAlarm(timestamp); - } - mStarted.insert(mPaused.begin(), mPaused.end()); - mPaused.clear(); - } - } else { - if (!mStarted.empty()) { - VLOG("Condition false, all paused"); - mStateKeyDurationMap[mEventKey.getStateValuesKey()].mDuration += - (timestamp - mLastStartTime); - mPaused.insert(mStarted.begin(), mStarted.end()); - mStarted.clear(); - detectAndDeclareAnomaly( - timestamp, mCurrentBucketNum, - getCurrentStateKeyDuration() + getCurrentStateKeyFullBucketDuration()); - } - } - if (mStarted.empty()) { - stopAnomalyAlarm(timestamp); - } -} - -void OringDurationTracker::onStateChanged(const int64_t timestamp, const int32_t atomId, - const FieldValue& newState) { - // Nothing needs to be done on a state change if we have not seen a start - // event, the metric is currently not active, or condition is false. - // For these cases, no keys are being tracked in mStarted, so update - // the current state key and return. - if (mStarted.empty()) { - updateCurrentStateKey(atomId, newState); - return; - } - // Add the current duration length to the previous state key and then update - // the last start time and current state key. - mStateKeyDurationMap[mEventKey.getStateValuesKey()].mDuration += (timestamp - mLastStartTime); - mLastStartTime = timestamp; - updateCurrentStateKey(atomId, newState); -} - -bool OringDurationTracker::hasAccumulatingDuration() { - return !mStarted.empty(); -} -int64_t OringDurationTracker::predictAnomalyTimestampNs(const AnomalyTracker& anomalyTracker, - const int64_t eventTimestampNs) const { - // The anomaly threshold. - const int64_t thresholdNs = anomalyTracker.getAnomalyThreshold(); - - // The timestamp of the current bucket end. - const int64_t currentBucketEndNs = getCurrentBucketEndTimeNs(); - - // The past duration ns for the current bucket of the current stateKey. - int64_t currentStateBucketPastNs = - getCurrentStateKeyDuration() + getCurrentStateKeyFullBucketDuration(); - - // As we move into the future, old buckets get overwritten (so their old data is erased). - // Sum of past durations. Will change as we overwrite old buckets. - int64_t pastNs = currentStateBucketPastNs + anomalyTracker.getSumOverPastBuckets(mEventKey); - - // The refractory period end timestamp for dimension mEventKey. - const int64_t refractoryPeriodEndNs = - anomalyTracker.getRefractoryPeriodEndsSec(mEventKey) * NS_PER_SEC; - - // The anomaly should happen when accumulated wakelock duration is above the threshold and - // not within the refractory period. - int64_t anomalyTimestampNs = - std::max(eventTimestampNs + thresholdNs - pastNs, refractoryPeriodEndNs); - // If the predicted the anomaly timestamp is within the current bucket, return it directly. - if (anomalyTimestampNs <= currentBucketEndNs) { - return std::max(eventTimestampNs, anomalyTimestampNs); - } - - // Remove the old bucket. - if (anomalyTracker.getNumOfPastBuckets() > 0) { - pastNs -= anomalyTracker.getPastBucketValue( - mEventKey, - mCurrentBucketNum - anomalyTracker.getNumOfPastBuckets()); - // Add the remaining of the current bucket to the accumulated wakelock duration. - pastNs += (currentBucketEndNs - eventTimestampNs); - } else { - // The anomaly depends on only one bucket. - pastNs = 0; - } - - // The anomaly will not happen in the current bucket. We need to iterate over the future buckets - // to predict the accumulated wakelock duration and determine the anomaly timestamp accordingly. - for (int futureBucketIdx = 1; futureBucketIdx <= anomalyTracker.getNumOfPastBuckets() + 1; - futureBucketIdx++) { - // The alarm candidate timestamp should meet two requirements: - // 1. the accumulated wakelock duration is above the threshold. - // 2. it is not within the refractory period. - // 3. the alarm timestamp falls in this bucket. Otherwise we need to flush the past buckets, - // find the new alarm candidate timestamp and check these requirements again. - const int64_t bucketEndNs = currentBucketEndNs + futureBucketIdx * mBucketSizeNs; - int64_t anomalyTimestampNs = - std::max(bucketEndNs - mBucketSizeNs + thresholdNs - pastNs, refractoryPeriodEndNs); - if (anomalyTimestampNs <= bucketEndNs) { - return anomalyTimestampNs; - } - if (anomalyTracker.getNumOfPastBuckets() <= 0) { - continue; - } - - // No valid alarm timestamp is found in this bucket. The clock moves to the end of the - // bucket. Update the pastNs. - pastNs += mBucketSizeNs; - // 1. If the oldest past bucket is still in the past bucket window, we could fetch the past - // bucket and erase it from pastNs. - // 2. If the oldest past bucket is the current bucket, we should compute the - // wakelock duration in the current bucket and erase it from pastNs. - // 3. Otherwise all othe past buckets are ancient. - if (futureBucketIdx < anomalyTracker.getNumOfPastBuckets()) { - pastNs -= anomalyTracker.getPastBucketValue( - mEventKey, - mCurrentBucketNum - anomalyTracker.getNumOfPastBuckets() + futureBucketIdx); - } else if (futureBucketIdx == anomalyTracker.getNumOfPastBuckets()) { - pastNs -= (currentStateBucketPastNs + (currentBucketEndNs - eventTimestampNs)); - } - } - - return std::max(eventTimestampNs + thresholdNs, refractoryPeriodEndNs); -} - -void OringDurationTracker::dumpStates(FILE* out, bool verbose) const { - fprintf(out, "\t\t started count %lu\n", (unsigned long)mStarted.size()); - fprintf(out, "\t\t paused count %lu\n", (unsigned long)mPaused.size()); - fprintf(out, "\t\t current duration %lld\n", (long long)getCurrentStateKeyDuration()); -} - -int64_t OringDurationTracker::getCurrentStateKeyDuration() const { - auto it = mStateKeyDurationMap.find(mEventKey.getStateValuesKey()); - if (it == mStateKeyDurationMap.end()) { - return 0; - } else { - return it->second.mDuration; - } -} - -int64_t OringDurationTracker::getCurrentStateKeyFullBucketDuration() const { - auto it = mStateKeyDurationMap.find(mEventKey.getStateValuesKey()); - if (it == mStateKeyDurationMap.end()) { - return 0; - } else { - return it->second.mDurationFullBucket; - } -} - -void OringDurationTracker::updateCurrentStateKey(const int32_t atomId, const FieldValue& newState) { - HashableDimensionKey* stateValuesKey = mEventKey.getMutableStateValuesKey(); - for (size_t i = 0; i < stateValuesKey->getValues().size(); i++) { - if (stateValuesKey->getValues()[i].mField.getTag() == atomId) { - stateValuesKey->mutableValue(i)->mValue = newState.mValue; - } - } -} - -} // namespace statsd -} // namespace os -} // namespace android diff --git a/bin/src/metrics/duration_helper/OringDurationTracker.h b/bin/src/metrics/duration_helper/OringDurationTracker.h deleted file mode 100644 index 8f8d5355..00000000 --- a/bin/src/metrics/duration_helper/OringDurationTracker.h +++ /dev/null @@ -1,98 +0,0 @@ -/* - * 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. - */ - -#ifndef ORING_DURATION_TRACKER_H -#define ORING_DURATION_TRACKER_H - -#include "DurationTracker.h" - -namespace android { -namespace os { -namespace statsd { - -// Tracks the "Or'd" duration -- if 2 durations are overlapping, they won't be double counted. -class OringDurationTracker : public DurationTracker { -public: - OringDurationTracker(const ConfigKey& key, const int64_t& id, - const MetricDimensionKey& eventKey, sp wizard, - int conditionIndex, bool nesting, int64_t currentBucketStartNs, - int64_t currentBucketNum, int64_t startTimeNs, int64_t bucketSizeNs, - bool conditionSliced, bool fullLink, - const std::vector>& anomalyTrackers); - - OringDurationTracker(const OringDurationTracker& tracker) = default; - - void noteStart(const HashableDimensionKey& key, bool condition, const int64_t eventTime, - const ConditionKey& conditionKey) override; - void noteStop(const HashableDimensionKey& key, const int64_t eventTime, - const bool stopAll) override; - void noteStopAll(const int64_t eventTime) override; - - void onSlicedConditionMayChange(bool overallCondition, const int64_t timestamp) override; - void onConditionChanged(bool condition, const int64_t timestamp) override; - - void onStateChanged(const int64_t timestamp, const int32_t atomId, - const FieldValue& newState) override; - - bool flushCurrentBucket( - const int64_t& eventTimeNs, const optional& uploadThreshold, - std::unordered_map>* output) override; - bool flushIfNeeded( - int64_t timestampNs, const optional& uploadThreshold, - std::unordered_map>* output) override; - - int64_t predictAnomalyTimestampNs(const AnomalyTracker& anomalyTracker, - const int64_t currentTimestamp) const override; - void dumpStates(FILE* out, bool verbose) const override; - - int64_t getCurrentStateKeyDuration() const override; - - int64_t getCurrentStateKeyFullBucketDuration() const override; - - void updateCurrentStateKey(const int32_t atomId, const FieldValue& newState); - -protected: - // Returns true if at least one of the mInfos is started. - bool hasAccumulatingDuration() override; - -private: - // We don't need to keep track of individual durations. The information that's needed is: - // 1) which keys are started. We record the first start time. - // 2) which keys are paused (started but condition was false) - // 3) whenever a key stops, we remove it from the started set. And if the set becomes empty, - // it means everything has stopped, we then record the end time. - std::unordered_map mStarted; - std::unordered_map mPaused; - int64_t mLastStartTime; - std::unordered_map mConditionKeyMap; - - // return true if we should not allow newKey to be tracked because we are above the threshold - bool hitGuardRail(const HashableDimensionKey& newKey); - - FRIEND_TEST(OringDurationTrackerTest, TestDurationOverlap); - FRIEND_TEST(OringDurationTrackerTest, TestCrossBucketBoundary); - FRIEND_TEST(OringDurationTrackerTest, TestDurationConditionChange); - FRIEND_TEST(OringDurationTrackerTest, TestPredictAnomalyTimestamp); - FRIEND_TEST(OringDurationTrackerTest, TestAnomalyDetectionExpiredAlarm); - FRIEND_TEST(OringDurationTrackerTest, TestAnomalyDetectionFiredAlarm); - FRIEND_TEST(OringDurationTrackerTest, TestUploadThreshold); -}; - -} // namespace statsd -} // namespace os -} // namespace android - -#endif // ORING_DURATION_TRACKER_H diff --git a/bin/src/metrics/parsing_utils/config_update_utils.cpp b/bin/src/metrics/parsing_utils/config_update_utils.cpp deleted file mode 100644 index b8c505ee..00000000 --- a/bin/src/metrics/parsing_utils/config_update_utils.cpp +++ /dev/null @@ -1,1121 +0,0 @@ -/* - * Copyright (C) 2020 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. - */ - -#define DEBUG false // STOPSHIP if true -#include "Log.h" - -#include "config_update_utils.h" - -#include "external/StatsPullerManager.h" -#include "hash.h" -#include "matchers/EventMatcherWizard.h" -#include "metrics_manager_util.h" - -using google::protobuf::MessageLite; - -namespace android { -namespace os { -namespace statsd { - -// Recursive function to determine if a matcher needs to be updated. Populates matcherToUpdate. -// Returns whether the function was successful or not. -bool determineMatcherUpdateStatus(const StatsdConfig& config, const int matcherIdx, - const unordered_map& oldAtomMatchingTrackerMap, - const vector>& oldAtomMatchingTrackers, - const unordered_map& newAtomMatchingTrackerMap, - vector& matchersToUpdate, - vector& cycleTracker) { - // Have already examined this matcher. - if (matchersToUpdate[matcherIdx] != UPDATE_UNKNOWN) { - return true; - } - - const AtomMatcher& matcher = config.atom_matcher(matcherIdx); - int64_t id = matcher.id(); - // Check if new matcher. - const auto& oldAtomMatchingTrackerIt = oldAtomMatchingTrackerMap.find(id); - if (oldAtomMatchingTrackerIt == oldAtomMatchingTrackerMap.end()) { - matchersToUpdate[matcherIdx] = UPDATE_NEW; - return true; - } - - // This is an existing matcher. Check if it has changed. - string serializedMatcher; - if (!matcher.SerializeToString(&serializedMatcher)) { - ALOGE("Unable to serialize matcher %lld", (long long)id); - return false; - } - uint64_t newProtoHash = Hash64(serializedMatcher); - if (newProtoHash != oldAtomMatchingTrackers[oldAtomMatchingTrackerIt->second]->getProtoHash()) { - matchersToUpdate[matcherIdx] = UPDATE_REPLACE; - return true; - } - - switch (matcher.contents_case()) { - case AtomMatcher::ContentsCase::kSimpleAtomMatcher: { - matchersToUpdate[matcherIdx] = UPDATE_PRESERVE; - return true; - } - case AtomMatcher::ContentsCase::kCombination: { - // Recurse to check if children have changed. - cycleTracker[matcherIdx] = true; - UpdateStatus status = UPDATE_PRESERVE; - for (const int64_t childMatcherId : matcher.combination().matcher()) { - const auto& childIt = newAtomMatchingTrackerMap.find(childMatcherId); - if (childIt == newAtomMatchingTrackerMap.end()) { - ALOGW("Matcher %lld not found in the config", (long long)childMatcherId); - return false; - } - const int childIdx = childIt->second; - if (cycleTracker[childIdx]) { - ALOGE("Cycle detected in matcher config"); - return false; - } - if (!determineMatcherUpdateStatus( - config, childIdx, oldAtomMatchingTrackerMap, oldAtomMatchingTrackers, - newAtomMatchingTrackerMap, matchersToUpdate, cycleTracker)) { - return false; - } - - if (matchersToUpdate[childIdx] == UPDATE_REPLACE) { - status = UPDATE_REPLACE; - break; - } - } - matchersToUpdate[matcherIdx] = status; - cycleTracker[matcherIdx] = false; - return true; - } - default: { - ALOGE("Matcher \"%lld\" malformed", (long long)id); - return false; - } - } - return true; -} - -bool updateAtomMatchingTrackers(const StatsdConfig& config, const sp& uidMap, - const unordered_map& oldAtomMatchingTrackerMap, - const vector>& oldAtomMatchingTrackers, - set& allTagIds, - unordered_map& newAtomMatchingTrackerMap, - vector>& newAtomMatchingTrackers, - set& replacedMatchers) { - const int atomMatcherCount = config.atom_matcher_size(); - vector matcherProtos; - matcherProtos.reserve(atomMatcherCount); - newAtomMatchingTrackers.reserve(atomMatcherCount); - - // Maps matcher id to their position in the config. For fast lookup of dependencies. - for (int i = 0; i < atomMatcherCount; i++) { - const AtomMatcher& matcher = config.atom_matcher(i); - if (newAtomMatchingTrackerMap.find(matcher.id()) != newAtomMatchingTrackerMap.end()) { - ALOGE("Duplicate atom matcher found for id %lld", (long long)matcher.id()); - return false; - } - newAtomMatchingTrackerMap[matcher.id()] = i; - matcherProtos.push_back(matcher); - } - - // For combination matchers, we need to determine if any children need to be updated. - vector matchersToUpdate(atomMatcherCount, UPDATE_UNKNOWN); - vector cycleTracker(atomMatcherCount, false); - for (int i = 0; i < atomMatcherCount; i++) { - if (!determineMatcherUpdateStatus(config, i, oldAtomMatchingTrackerMap, - oldAtomMatchingTrackers, newAtomMatchingTrackerMap, - matchersToUpdate, cycleTracker)) { - return false; - } - } - - for (int i = 0; i < atomMatcherCount; i++) { - const AtomMatcher& matcher = config.atom_matcher(i); - const int64_t id = matcher.id(); - switch (matchersToUpdate[i]) { - case UPDATE_PRESERVE: { - const auto& oldAtomMatchingTrackerIt = oldAtomMatchingTrackerMap.find(id); - if (oldAtomMatchingTrackerIt == oldAtomMatchingTrackerMap.end()) { - ALOGE("Could not find AtomMatcher %lld in the previous config, but expected it " - "to be there", - (long long)id); - return false; - } - const sp& tracker = - oldAtomMatchingTrackers[oldAtomMatchingTrackerIt->second]; - if (!tracker->onConfigUpdated(matcherProtos[i], i, newAtomMatchingTrackerMap)) { - ALOGW("Config update failed for matcher %lld", (long long)id); - return false; - } - newAtomMatchingTrackers.push_back(tracker); - break; - } - case UPDATE_REPLACE: - replacedMatchers.insert(id); - [[fallthrough]]; // Intentionally fallthrough to create the new matcher. - case UPDATE_NEW: { - sp tracker = createAtomMatchingTracker(matcher, i, uidMap); - if (tracker == nullptr) { - return false; - } - newAtomMatchingTrackers.push_back(tracker); - break; - } - default: { - ALOGE("Matcher \"%lld\" update state is unknown. This should never happen", - (long long)id); - return false; - } - } - } - - std::fill(cycleTracker.begin(), cycleTracker.end(), false); - for (auto& matcher : newAtomMatchingTrackers) { - if (!matcher->init(matcherProtos, newAtomMatchingTrackers, newAtomMatchingTrackerMap, - cycleTracker)) { - return false; - } - // Collect all the tag ids that are interesting. TagIds exist in leaf nodes only. - const set& tagIds = matcher->getAtomIds(); - allTagIds.insert(tagIds.begin(), tagIds.end()); - } - - return true; -} - -// Recursive function to determine if a condition needs to be updated. Populates conditionsToUpdate. -// Returns whether the function was successful or not. -bool determineConditionUpdateStatus(const StatsdConfig& config, const int conditionIdx, - const unordered_map& oldConditionTrackerMap, - const vector>& oldConditionTrackers, - const unordered_map& newConditionTrackerMap, - const set& replacedMatchers, - vector& conditionsToUpdate, - vector& cycleTracker) { - // Have already examined this condition. - if (conditionsToUpdate[conditionIdx] != UPDATE_UNKNOWN) { - return true; - } - - const Predicate& predicate = config.predicate(conditionIdx); - int64_t id = predicate.id(); - // Check if new condition. - const auto& oldConditionTrackerIt = oldConditionTrackerMap.find(id); - if (oldConditionTrackerIt == oldConditionTrackerMap.end()) { - conditionsToUpdate[conditionIdx] = UPDATE_NEW; - return true; - } - - // This is an existing condition. Check if it has changed. - string serializedCondition; - if (!predicate.SerializeToString(&serializedCondition)) { - ALOGE("Unable to serialize matcher %lld", (long long)id); - return false; - } - uint64_t newProtoHash = Hash64(serializedCondition); - if (newProtoHash != oldConditionTrackers[oldConditionTrackerIt->second]->getProtoHash()) { - conditionsToUpdate[conditionIdx] = UPDATE_REPLACE; - return true; - } - - switch (predicate.contents_case()) { - case Predicate::ContentsCase::kSimplePredicate: { - // Need to check if any of the underlying matchers changed. - const SimplePredicate& simplePredicate = predicate.simple_predicate(); - if (simplePredicate.has_start()) { - if (replacedMatchers.find(simplePredicate.start()) != replacedMatchers.end()) { - conditionsToUpdate[conditionIdx] = UPDATE_REPLACE; - return true; - } - } - if (simplePredicate.has_stop()) { - if (replacedMatchers.find(simplePredicate.stop()) != replacedMatchers.end()) { - conditionsToUpdate[conditionIdx] = UPDATE_REPLACE; - return true; - } - } - if (simplePredicate.has_stop_all()) { - if (replacedMatchers.find(simplePredicate.stop_all()) != replacedMatchers.end()) { - conditionsToUpdate[conditionIdx] = UPDATE_REPLACE; - return true; - } - } - conditionsToUpdate[conditionIdx] = UPDATE_PRESERVE; - return true; - } - case Predicate::ContentsCase::kCombination: { - // Need to recurse on the children to see if any of the child predicates changed. - cycleTracker[conditionIdx] = true; - UpdateStatus status = UPDATE_PRESERVE; - for (const int64_t childPredicateId : predicate.combination().predicate()) { - const auto& childIt = newConditionTrackerMap.find(childPredicateId); - if (childIt == newConditionTrackerMap.end()) { - ALOGW("Predicate %lld not found in the config", (long long)childPredicateId); - return false; - } - const int childIdx = childIt->second; - if (cycleTracker[childIdx]) { - ALOGE("Cycle detected in predicate config"); - return false; - } - if (!determineConditionUpdateStatus(config, childIdx, oldConditionTrackerMap, - oldConditionTrackers, newConditionTrackerMap, - replacedMatchers, conditionsToUpdate, - cycleTracker)) { - return false; - } - - if (conditionsToUpdate[childIdx] == UPDATE_REPLACE) { - status = UPDATE_REPLACE; - break; - } - } - conditionsToUpdate[conditionIdx] = status; - cycleTracker[conditionIdx] = false; - return true; - } - default: { - ALOGE("Predicate \"%lld\" malformed", (long long)id); - return false; - } - } - - return true; -} - -bool updateConditions(const ConfigKey& key, const StatsdConfig& config, - const unordered_map& atomMatchingTrackerMap, - const set& replacedMatchers, - const unordered_map& oldConditionTrackerMap, - const vector>& oldConditionTrackers, - unordered_map& newConditionTrackerMap, - vector>& newConditionTrackers, - unordered_map>& trackerToConditionMap, - vector& conditionCache, set& replacedConditions) { - vector conditionProtos; - const int conditionTrackerCount = config.predicate_size(); - conditionProtos.reserve(conditionTrackerCount); - newConditionTrackers.reserve(conditionTrackerCount); - conditionCache.assign(conditionTrackerCount, ConditionState::kNotEvaluated); - - for (int i = 0; i < conditionTrackerCount; i++) { - const Predicate& condition = config.predicate(i); - if (newConditionTrackerMap.find(condition.id()) != newConditionTrackerMap.end()) { - ALOGE("Duplicate Predicate found!"); - return false; - } - newConditionTrackerMap[condition.id()] = i; - conditionProtos.push_back(condition); - } - - vector conditionsToUpdate(conditionTrackerCount, UPDATE_UNKNOWN); - vector cycleTracker(conditionTrackerCount, false); - for (int i = 0; i < conditionTrackerCount; i++) { - if (!determineConditionUpdateStatus(config, i, oldConditionTrackerMap, oldConditionTrackers, - newConditionTrackerMap, replacedMatchers, - conditionsToUpdate, cycleTracker)) { - return false; - } - } - - // Update status has been determined for all conditions. Now perform the update. - set preservedConditions; - for (int i = 0; i < conditionTrackerCount; i++) { - const Predicate& predicate = config.predicate(i); - const int64_t id = predicate.id(); - switch (conditionsToUpdate[i]) { - case UPDATE_PRESERVE: { - preservedConditions.insert(i); - const auto& oldConditionTrackerIt = oldConditionTrackerMap.find(id); - if (oldConditionTrackerIt == oldConditionTrackerMap.end()) { - ALOGE("Could not find Predicate %lld in the previous config, but expected it " - "to be there", - (long long)id); - return false; - } - const int oldIndex = oldConditionTrackerIt->second; - newConditionTrackers.push_back(oldConditionTrackers[oldIndex]); - break; - } - case UPDATE_REPLACE: - replacedConditions.insert(id); - [[fallthrough]]; // Intentionally fallthrough to create the new condition tracker. - case UPDATE_NEW: { - sp tracker = - createConditionTracker(key, predicate, i, atomMatchingTrackerMap); - if (tracker == nullptr) { - return false; - } - newConditionTrackers.push_back(tracker); - break; - } - default: { - ALOGE("Condition \"%lld\" update state is unknown. This should never happen", - (long long)id); - return false; - } - } - } - - // Update indices of preserved predicates. - for (const int conditionIndex : preservedConditions) { - if (!newConditionTrackers[conditionIndex]->onConfigUpdated( - conditionProtos, conditionIndex, newConditionTrackers, atomMatchingTrackerMap, - newConditionTrackerMap)) { - ALOGE("Failed to update condition %lld", - (long long)newConditionTrackers[conditionIndex]->getConditionId()); - return false; - } - } - - std::fill(cycleTracker.begin(), cycleTracker.end(), false); - for (int conditionIndex = 0; conditionIndex < conditionTrackerCount; conditionIndex++) { - const sp& conditionTracker = newConditionTrackers[conditionIndex]; - // Calling init on preserved conditions is OK. It is needed to fill the condition cache. - if (!conditionTracker->init(conditionProtos, newConditionTrackers, newConditionTrackerMap, - cycleTracker, conditionCache)) { - return false; - } - for (const int trackerIndex : conditionTracker->getAtomMatchingTrackerIndex()) { - vector& conditionList = trackerToConditionMap[trackerIndex]; - conditionList.push_back(conditionIndex); - } - } - return true; -} - -bool updateStates(const StatsdConfig& config, const map& oldStateProtoHashes, - unordered_map& stateAtomIdMap, - unordered_map>& allStateGroupMaps, - map& newStateProtoHashes, set& replacedStates) { - // Share with metrics_manager_util. - if (!initStates(config, stateAtomIdMap, allStateGroupMaps, newStateProtoHashes)) { - return false; - } - - for (const auto& [stateId, stateHash] : oldStateProtoHashes) { - const auto& it = newStateProtoHashes.find(stateId); - if (it != newStateProtoHashes.end() && it->second != stateHash) { - replacedStates.insert(stateId); - } - } - return true; -} -// Returns true if any matchers in the metric activation were replaced. -bool metricActivationDepsChange(const StatsdConfig& config, - const unordered_map& metricToActivationMap, - const int64_t metricId, const set& replacedMatchers) { - const auto& metricActivationIt = metricToActivationMap.find(metricId); - if (metricActivationIt == metricToActivationMap.end()) { - return false; - } - const MetricActivation& metricActivation = config.metric_activation(metricActivationIt->second); - for (int i = 0; i < metricActivation.event_activation_size(); i++) { - const EventActivation& activation = metricActivation.event_activation(i); - if (replacedMatchers.find(activation.atom_matcher_id()) != replacedMatchers.end()) { - return true; - } - if (activation.has_deactivation_atom_matcher_id()) { - if (replacedMatchers.find(activation.deactivation_atom_matcher_id()) != - replacedMatchers.end()) { - return true; - } - } - } - return false; -} - -bool determineMetricUpdateStatus( - const StatsdConfig& config, const MessageLite& metric, const int64_t metricId, - const MetricType metricType, const set& matcherDependencies, - const set& conditionDependencies, - const ::google::protobuf::RepeatedField& stateDependencies, - const ::google::protobuf::RepeatedPtrField& conditionLinks, - const unordered_map& oldMetricProducerMap, - const vector>& oldMetricProducers, - const unordered_map& metricToActivationMap, - const set& replacedMatchers, const set& replacedConditions, - const set& replacedStates, UpdateStatus& updateStatus) { - // Check if new metric - const auto& oldMetricProducerIt = oldMetricProducerMap.find(metricId); - if (oldMetricProducerIt == oldMetricProducerMap.end()) { - updateStatus = UPDATE_NEW; - return true; - } - - // This is an existing metric, check if it has changed. - uint64_t metricHash; - if (!getMetricProtoHash(config, metric, metricId, metricToActivationMap, metricHash)) { - return false; - } - const sp oldMetricProducer = oldMetricProducers[oldMetricProducerIt->second]; - if (oldMetricProducer->getMetricType() != metricType || - oldMetricProducer->getProtoHash() != metricHash) { - updateStatus = UPDATE_REPLACE; - return true; - } - - // Take intersections of the matchers/predicates/states that the metric - // depends on with those that have been replaced. If a metric depends on any - // replaced component, it too must be replaced. - set intersection; - set_intersection(matcherDependencies.begin(), matcherDependencies.end(), - replacedMatchers.begin(), replacedMatchers.end(), - inserter(intersection, intersection.begin())); - if (intersection.size() > 0) { - updateStatus = UPDATE_REPLACE; - return true; - } - set_intersection(conditionDependencies.begin(), conditionDependencies.end(), - replacedConditions.begin(), replacedConditions.end(), - inserter(intersection, intersection.begin())); - if (intersection.size() > 0) { - updateStatus = UPDATE_REPLACE; - return true; - } - set_intersection(stateDependencies.begin(), stateDependencies.end(), replacedStates.begin(), - replacedStates.end(), inserter(intersection, intersection.begin())); - if (intersection.size() > 0) { - updateStatus = UPDATE_REPLACE; - return true; - } - - for (const auto& metricConditionLink : conditionLinks) { - if (replacedConditions.find(metricConditionLink.condition()) != replacedConditions.end()) { - updateStatus = UPDATE_REPLACE; - return true; - } - } - - if (metricActivationDepsChange(config, metricToActivationMap, metricId, replacedMatchers)) { - updateStatus = UPDATE_REPLACE; - return true; - } - - updateStatus = UPDATE_PRESERVE; - return true; -} - -bool determineAllMetricUpdateStatuses(const StatsdConfig& config, - const unordered_map& oldMetricProducerMap, - const vector>& oldMetricProducers, - const unordered_map& metricToActivationMap, - const set& replacedMatchers, - const set& replacedConditions, - const set& replacedStates, - vector& metricsToUpdate) { - int metricIndex = 0; - for (int i = 0; i < config.count_metric_size(); i++, metricIndex++) { - const CountMetric& metric = config.count_metric(i); - set conditionDependencies; - if (metric.has_condition()) { - conditionDependencies.insert(metric.condition()); - } - if (!determineMetricUpdateStatus( - config, metric, metric.id(), METRIC_TYPE_COUNT, {metric.what()}, - conditionDependencies, metric.slice_by_state(), metric.links(), - oldMetricProducerMap, oldMetricProducers, metricToActivationMap, - replacedMatchers, replacedConditions, replacedStates, - metricsToUpdate[metricIndex])) { - return false; - } - } - for (int i = 0; i < config.duration_metric_size(); i++, metricIndex++) { - const DurationMetric& metric = config.duration_metric(i); - set conditionDependencies({metric.what()}); - if (metric.has_condition()) { - conditionDependencies.insert(metric.condition()); - } - if (!determineMetricUpdateStatus( - config, metric, metric.id(), METRIC_TYPE_DURATION, /*matcherDependencies=*/{}, - conditionDependencies, metric.slice_by_state(), metric.links(), - oldMetricProducerMap, oldMetricProducers, metricToActivationMap, - replacedMatchers, replacedConditions, replacedStates, - metricsToUpdate[metricIndex])) { - return false; - } - } - for (int i = 0; i < config.event_metric_size(); i++, metricIndex++) { - const EventMetric& metric = config.event_metric(i); - set conditionDependencies; - if (metric.has_condition()) { - conditionDependencies.insert(metric.condition()); - } - if (!determineMetricUpdateStatus( - config, metric, metric.id(), METRIC_TYPE_EVENT, {metric.what()}, - conditionDependencies, ::google::protobuf::RepeatedField(), - metric.links(), oldMetricProducerMap, oldMetricProducers, metricToActivationMap, - replacedMatchers, replacedConditions, replacedStates, - metricsToUpdate[metricIndex])) { - return false; - } - } - for (int i = 0; i < config.value_metric_size(); i++, metricIndex++) { - const ValueMetric& metric = config.value_metric(i); - set conditionDependencies; - if (metric.has_condition()) { - conditionDependencies.insert(metric.condition()); - } - if (!determineMetricUpdateStatus( - config, metric, metric.id(), METRIC_TYPE_VALUE, {metric.what()}, - conditionDependencies, metric.slice_by_state(), metric.links(), - oldMetricProducerMap, oldMetricProducers, metricToActivationMap, - replacedMatchers, replacedConditions, replacedStates, - metricsToUpdate[metricIndex])) { - return false; - } - } - for (int i = 0; i < config.gauge_metric_size(); i++, metricIndex++) { - const GaugeMetric& metric = config.gauge_metric(i); - set conditionDependencies; - if (metric.has_condition()) { - conditionDependencies.insert(metric.condition()); - } - set matcherDependencies({metric.what()}); - if (metric.has_trigger_event()) { - matcherDependencies.insert(metric.trigger_event()); - } - if (!determineMetricUpdateStatus( - config, metric, metric.id(), METRIC_TYPE_GAUGE, matcherDependencies, - conditionDependencies, ::google::protobuf::RepeatedField(), - metric.links(), oldMetricProducerMap, oldMetricProducers, metricToActivationMap, - replacedMatchers, replacedConditions, replacedStates, - metricsToUpdate[metricIndex])) { - return false; - } - } - return true; -} - -// Called when a metric is preserved during a config update. Finds the metric in oldMetricProducers -// and calls onConfigUpdated to update all indices. -optional> updateMetric( - const StatsdConfig& config, const int configIndex, const int metricIndex, - const int64_t metricId, const vector>& allAtomMatchingTrackers, - const unordered_map& oldAtomMatchingTrackerMap, - const unordered_map& newAtomMatchingTrackerMap, - const sp& matcherWizard, - const vector>& allConditionTrackers, - const unordered_map& conditionTrackerMap, const sp& wizard, - const unordered_map& oldMetricProducerMap, - const vector>& oldMetricProducers, - const unordered_map& metricToActivationMap, - unordered_map>& trackerToMetricMap, - unordered_map>& conditionToMetricMap, - unordered_map>& activationAtomTrackerToMetricMap, - unordered_map>& deactivationAtomTrackerToMetricMap, - vector& metricsWithActivation) { - const auto& oldMetricProducerIt = oldMetricProducerMap.find(metricId); - if (oldMetricProducerIt == oldMetricProducerMap.end()) { - ALOGE("Could not find Metric %lld in the previous config, but expected it " - "to be there", - (long long)metricId); - return nullopt; - } - const int oldIndex = oldMetricProducerIt->second; - sp producer = oldMetricProducers[oldIndex]; - if (!producer->onConfigUpdated(config, configIndex, metricIndex, allAtomMatchingTrackers, - oldAtomMatchingTrackerMap, newAtomMatchingTrackerMap, - matcherWizard, allConditionTrackers, conditionTrackerMap, wizard, - metricToActivationMap, trackerToMetricMap, conditionToMetricMap, - activationAtomTrackerToMetricMap, - deactivationAtomTrackerToMetricMap, metricsWithActivation)) { - return nullopt; - } - return {producer}; -} - -bool updateMetrics(const ConfigKey& key, const StatsdConfig& config, const int64_t timeBaseNs, - const int64_t currentTimeNs, const sp& pullerManager, - const unordered_map& oldAtomMatchingTrackerMap, - const unordered_map& newAtomMatchingTrackerMap, - const set& replacedMatchers, - const vector>& allAtomMatchingTrackers, - const unordered_map& conditionTrackerMap, - const set& replacedConditions, - vector>& allConditionTrackers, - const vector& initialConditionCache, - const unordered_map& stateAtomIdMap, - const unordered_map>& allStateGroupMaps, - const set& replacedStates, - const unordered_map& oldMetricProducerMap, - const vector>& oldMetricProducers, - unordered_map& newMetricProducerMap, - vector>& newMetricProducers, - unordered_map>& conditionToMetricMap, - unordered_map>& trackerToMetricMap, - set& noReportMetricIds, - unordered_map>& activationAtomTrackerToMetricMap, - unordered_map>& deactivationAtomTrackerToMetricMap, - vector& metricsWithActivation, set& replacedMetrics) { - sp wizard = new ConditionWizard(allConditionTrackers); - sp matcherWizard = new EventMatcherWizard(allAtomMatchingTrackers); - const int allMetricsCount = config.count_metric_size() + config.duration_metric_size() + - config.event_metric_size() + config.gauge_metric_size() + - config.value_metric_size(); - newMetricProducers.reserve(allMetricsCount); - - // Construct map from metric id to metric activation index. The map will be used to determine - // the metric activation corresponding to a metric. - unordered_map metricToActivationMap; - for (int i = 0; i < config.metric_activation_size(); i++) { - const MetricActivation& metricActivation = config.metric_activation(i); - int64_t metricId = metricActivation.metric_id(); - if (metricToActivationMap.find(metricId) != metricToActivationMap.end()) { - ALOGE("Metric %lld has multiple MetricActivations", (long long)metricId); - return false; - } - metricToActivationMap.insert({metricId, i}); - } - - vector metricsToUpdate(allMetricsCount, UPDATE_UNKNOWN); - if (!determineAllMetricUpdateStatuses(config, oldMetricProducerMap, oldMetricProducers, - metricToActivationMap, replacedMatchers, - replacedConditions, replacedStates, metricsToUpdate)) { - return false; - } - - // Now, perform the update. Must iterate the metric types in the same order - int metricIndex = 0; - for (int i = 0; i < config.count_metric_size(); i++, metricIndex++) { - const CountMetric& metric = config.count_metric(i); - newMetricProducerMap[metric.id()] = metricIndex; - optional> producer; - switch (metricsToUpdate[metricIndex]) { - case UPDATE_PRESERVE: { - producer = updateMetric( - config, i, metricIndex, metric.id(), allAtomMatchingTrackers, - oldAtomMatchingTrackerMap, newAtomMatchingTrackerMap, matcherWizard, - allConditionTrackers, conditionTrackerMap, wizard, oldMetricProducerMap, - oldMetricProducers, metricToActivationMap, trackerToMetricMap, - conditionToMetricMap, activationAtomTrackerToMetricMap, - deactivationAtomTrackerToMetricMap, metricsWithActivation); - break; - } - case UPDATE_REPLACE: - replacedMetrics.insert(metric.id()); - [[fallthrough]]; // Intentionally fallthrough to create the new metric producer. - case UPDATE_NEW: { - producer = createCountMetricProducerAndUpdateMetadata( - key, config, timeBaseNs, currentTimeNs, metric, metricIndex, - allAtomMatchingTrackers, newAtomMatchingTrackerMap, allConditionTrackers, - conditionTrackerMap, initialConditionCache, wizard, stateAtomIdMap, - allStateGroupMaps, metricToActivationMap, trackerToMetricMap, - conditionToMetricMap, activationAtomTrackerToMetricMap, - deactivationAtomTrackerToMetricMap, metricsWithActivation); - break; - } - default: { - ALOGE("Metric \"%lld\" update state is unknown. This should never happen", - (long long)metric.id()); - return false; - } - } - if (!producer) { - return false; - } - newMetricProducers.push_back(producer.value()); - } - for (int i = 0; i < config.duration_metric_size(); i++, metricIndex++) { - const DurationMetric& metric = config.duration_metric(i); - newMetricProducerMap[metric.id()] = metricIndex; - optional> producer; - switch (metricsToUpdate[metricIndex]) { - case UPDATE_PRESERVE: { - producer = updateMetric( - config, i, metricIndex, metric.id(), allAtomMatchingTrackers, - oldAtomMatchingTrackerMap, newAtomMatchingTrackerMap, matcherWizard, - allConditionTrackers, conditionTrackerMap, wizard, oldMetricProducerMap, - oldMetricProducers, metricToActivationMap, trackerToMetricMap, - conditionToMetricMap, activationAtomTrackerToMetricMap, - deactivationAtomTrackerToMetricMap, metricsWithActivation); - break; - } - case UPDATE_REPLACE: - replacedMetrics.insert(metric.id()); - [[fallthrough]]; // Intentionally fallthrough to create the new metric producer. - case UPDATE_NEW: { - producer = createDurationMetricProducerAndUpdateMetadata( - key, config, timeBaseNs, currentTimeNs, metric, metricIndex, - allAtomMatchingTrackers, newAtomMatchingTrackerMap, allConditionTrackers, - conditionTrackerMap, initialConditionCache, wizard, stateAtomIdMap, - allStateGroupMaps, metricToActivationMap, trackerToMetricMap, - conditionToMetricMap, activationAtomTrackerToMetricMap, - deactivationAtomTrackerToMetricMap, metricsWithActivation); - break; - } - default: { - ALOGE("Metric \"%lld\" update state is unknown. This should never happen", - (long long)metric.id()); - return false; - } - } - if (!producer) { - return false; - } - newMetricProducers.push_back(producer.value()); - } - for (int i = 0; i < config.event_metric_size(); i++, metricIndex++) { - const EventMetric& metric = config.event_metric(i); - newMetricProducerMap[metric.id()] = metricIndex; - optional> producer; - switch (metricsToUpdate[metricIndex]) { - case UPDATE_PRESERVE: { - producer = updateMetric( - config, i, metricIndex, metric.id(), allAtomMatchingTrackers, - oldAtomMatchingTrackerMap, newAtomMatchingTrackerMap, matcherWizard, - allConditionTrackers, conditionTrackerMap, wizard, oldMetricProducerMap, - oldMetricProducers, metricToActivationMap, trackerToMetricMap, - conditionToMetricMap, activationAtomTrackerToMetricMap, - deactivationAtomTrackerToMetricMap, metricsWithActivation); - break; - } - case UPDATE_REPLACE: - replacedMetrics.insert(metric.id()); - [[fallthrough]]; // Intentionally fallthrough to create the new metric producer. - case UPDATE_NEW: { - producer = createEventMetricProducerAndUpdateMetadata( - key, config, timeBaseNs, metric, metricIndex, allAtomMatchingTrackers, - newAtomMatchingTrackerMap, allConditionTrackers, conditionTrackerMap, - initialConditionCache, wizard, metricToActivationMap, trackerToMetricMap, - conditionToMetricMap, activationAtomTrackerToMetricMap, - deactivationAtomTrackerToMetricMap, metricsWithActivation); - break; - } - default: { - ALOGE("Metric \"%lld\" update state is unknown. This should never happen", - (long long)metric.id()); - return false; - } - } - if (!producer) { - return false; - } - newMetricProducers.push_back(producer.value()); - } - - for (int i = 0; i < config.value_metric_size(); i++, metricIndex++) { - const ValueMetric& metric = config.value_metric(i); - newMetricProducerMap[metric.id()] = metricIndex; - optional> producer; - switch (metricsToUpdate[metricIndex]) { - case UPDATE_PRESERVE: { - producer = updateMetric( - config, i, metricIndex, metric.id(), allAtomMatchingTrackers, - oldAtomMatchingTrackerMap, newAtomMatchingTrackerMap, matcherWizard, - allConditionTrackers, conditionTrackerMap, wizard, oldMetricProducerMap, - oldMetricProducers, metricToActivationMap, trackerToMetricMap, - conditionToMetricMap, activationAtomTrackerToMetricMap, - deactivationAtomTrackerToMetricMap, metricsWithActivation); - break; - } - case UPDATE_REPLACE: - replacedMetrics.insert(metric.id()); - [[fallthrough]]; // Intentionally fallthrough to create the new metric producer. - case UPDATE_NEW: { - producer = createValueMetricProducerAndUpdateMetadata( - key, config, timeBaseNs, currentTimeNs, pullerManager, metric, metricIndex, - allAtomMatchingTrackers, newAtomMatchingTrackerMap, allConditionTrackers, - conditionTrackerMap, initialConditionCache, wizard, matcherWizard, - stateAtomIdMap, allStateGroupMaps, metricToActivationMap, - trackerToMetricMap, conditionToMetricMap, activationAtomTrackerToMetricMap, - deactivationAtomTrackerToMetricMap, metricsWithActivation); - break; - } - default: { - ALOGE("Metric \"%lld\" update state is unknown. This should never happen", - (long long)metric.id()); - return false; - } - } - if (!producer) { - return false; - } - newMetricProducers.push_back(producer.value()); - } - - for (int i = 0; i < config.gauge_metric_size(); i++, metricIndex++) { - const GaugeMetric& metric = config.gauge_metric(i); - newMetricProducerMap[metric.id()] = metricIndex; - optional> producer; - switch (metricsToUpdate[metricIndex]) { - case UPDATE_PRESERVE: { - producer = updateMetric( - config, i, metricIndex, metric.id(), allAtomMatchingTrackers, - oldAtomMatchingTrackerMap, newAtomMatchingTrackerMap, matcherWizard, - allConditionTrackers, conditionTrackerMap, wizard, oldMetricProducerMap, - oldMetricProducers, metricToActivationMap, trackerToMetricMap, - conditionToMetricMap, activationAtomTrackerToMetricMap, - deactivationAtomTrackerToMetricMap, metricsWithActivation); - break; - } - case UPDATE_REPLACE: - replacedMetrics.insert(metric.id()); - [[fallthrough]]; // Intentionally fallthrough to create the new metric producer. - case UPDATE_NEW: { - producer = createGaugeMetricProducerAndUpdateMetadata( - key, config, timeBaseNs, currentTimeNs, pullerManager, metric, metricIndex, - allAtomMatchingTrackers, newAtomMatchingTrackerMap, allConditionTrackers, - conditionTrackerMap, initialConditionCache, wizard, matcherWizard, - metricToActivationMap, trackerToMetricMap, conditionToMetricMap, - activationAtomTrackerToMetricMap, deactivationAtomTrackerToMetricMap, - metricsWithActivation); - break; - } - default: { - ALOGE("Metric \"%lld\" update state is unknown. This should never happen", - (long long)metric.id()); - return false; - } - } - if (!producer) { - return false; - } - newMetricProducers.push_back(producer.value()); - } - - for (int i = 0; i < config.no_report_metric_size(); ++i) { - const int64_t noReportMetric = config.no_report_metric(i); - if (newMetricProducerMap.find(noReportMetric) == newMetricProducerMap.end()) { - ALOGW("no_report_metric %" PRId64 " not exist", noReportMetric); - return false; - } - noReportMetricIds.insert(noReportMetric); - } - const set atomsAllowedFromAnyUid(config.whitelisted_atom_ids().begin(), - config.whitelisted_atom_ids().end()); - for (int i = 0; i < allMetricsCount; i++) { - sp producer = newMetricProducers[i]; - // Register metrics to StateTrackers - for (int atomId : producer->getSlicedStateAtoms()) { - // Register listener for atoms that use allowed_log_sources. - // Using atoms allowed from any uid as a sliced state atom is not allowed. - // Redo this check for all metrics in case the atoms allowed from any uid changed. - if (atomsAllowedFromAnyUid.find(atomId) != atomsAllowedFromAnyUid.end()) { - return false; - // Preserved metrics should've already registered.` - } else if (metricsToUpdate[i] != UPDATE_PRESERVE) { - StateManager::getInstance().registerListener(atomId, producer); - } - } - } - - // Init new/replaced metrics. - for (size_t i = 0; i < newMetricProducers.size(); i++) { - if (metricsToUpdate[i] == UPDATE_REPLACE || metricsToUpdate[i] == UPDATE_NEW) { - newMetricProducers[i]->prepareFirstBucket(); - } - } - return true; -} - -bool determineAlertUpdateStatus(const Alert& alert, - const unordered_map& oldAlertTrackerMap, - const vector>& oldAnomalyTrackers, - const set& replacedMetrics, UpdateStatus& updateStatus) { - // Check if new alert. - const auto& oldAnomalyTrackerIt = oldAlertTrackerMap.find(alert.id()); - if (oldAnomalyTrackerIt == oldAlertTrackerMap.end()) { - updateStatus = UPDATE_NEW; - return true; - } - - // This is an existing alert, check if it has changed. - string serializedAlert; - if (!alert.SerializeToString(&serializedAlert)) { - ALOGW("Unable to serialize alert %lld", (long long)alert.id()); - return false; - } - uint64_t newProtoHash = Hash64(serializedAlert); - const auto [success, oldProtoHash] = - oldAnomalyTrackers[oldAnomalyTrackerIt->second]->getProtoHash(); - if (!success) { - return false; - } - if (newProtoHash != oldProtoHash) { - updateStatus = UPDATE_REPLACE; - return true; - } - - // Check if the metric this alert relies on has changed. - if (replacedMetrics.find(alert.metric_id()) != replacedMetrics.end()) { - updateStatus = UPDATE_REPLACE; - return true; - } - - updateStatus = UPDATE_PRESERVE; - return true; -} - -bool updateAlerts(const StatsdConfig& config, const int64_t currentTimeNs, - const unordered_map& metricProducerMap, - const set& replacedMetrics, - const unordered_map& oldAlertTrackerMap, - const vector>& oldAnomalyTrackers, - const sp& anomalyAlarmMonitor, - vector>& allMetricProducers, - unordered_map& newAlertTrackerMap, - vector>& newAnomalyTrackers) { - int alertCount = config.alert_size(); - vector alertUpdateStatuses(alertCount); - for (int i = 0; i < alertCount; i++) { - if (!determineAlertUpdateStatus(config.alert(i), oldAlertTrackerMap, oldAnomalyTrackers, - replacedMetrics, alertUpdateStatuses[i])) { - return false; - } - } - - for (int i = 0; i < alertCount; i++) { - const Alert& alert = config.alert(i); - newAlertTrackerMap[alert.id()] = newAnomalyTrackers.size(); - switch (alertUpdateStatuses[i]) { - case UPDATE_PRESERVE: { - // Find the alert and update it. - const auto& oldAnomalyTrackerIt = oldAlertTrackerMap.find(alert.id()); - if (oldAnomalyTrackerIt == oldAlertTrackerMap.end()) { - ALOGW("Could not find AnomalyTracker %lld in the previous config, but " - "expected it to be there", - (long long)alert.id()); - return false; - } - sp anomalyTracker = oldAnomalyTrackers[oldAnomalyTrackerIt->second]; - anomalyTracker->onConfigUpdated(); - // Add the alert to the relevant metric. - const auto& metricProducerIt = metricProducerMap.find(alert.metric_id()); - if (metricProducerIt == metricProducerMap.end()) { - ALOGW("alert \"%lld\" has unknown metric id: \"%lld\"", (long long)alert.id(), - (long long)alert.metric_id()); - return false; - } - allMetricProducers[metricProducerIt->second]->addAnomalyTracker(anomalyTracker, - currentTimeNs); - newAnomalyTrackers.push_back(anomalyTracker); - break; - } - case UPDATE_REPLACE: - case UPDATE_NEW: { - optional> anomalyTracker = - createAnomalyTracker(alert, anomalyAlarmMonitor, alertUpdateStatuses[i], - currentTimeNs, metricProducerMap, allMetricProducers); - if (!anomalyTracker) { - return false; - } - newAnomalyTrackers.push_back(anomalyTracker.value()); - break; - } - default: { - ALOGE("Alert \"%lld\" update state is unknown. This should never happen", - (long long)alert.id()); - return false; - } - } - } - if (!initSubscribersForSubscriptionType(config, Subscription::ALERT, newAlertTrackerMap, - newAnomalyTrackers)) { - return false; - } - return true; -} - -bool updateStatsdConfig(const ConfigKey& key, const StatsdConfig& config, const sp& uidMap, - const sp& pullerManager, - const sp& anomalyAlarmMonitor, - const sp& periodicAlarmMonitor, const int64_t timeBaseNs, - const int64_t currentTimeNs, - const vector>& oldAtomMatchingTrackers, - const unordered_map& oldAtomMatchingTrackerMap, - const vector>& oldConditionTrackers, - const unordered_map& oldConditionTrackerMap, - const vector>& oldMetricProducers, - const unordered_map& oldMetricProducerMap, - const vector>& oldAnomalyTrackers, - const unordered_map& oldAlertTrackerMap, - const map& oldStateProtoHashes, set& allTagIds, - vector>& newAtomMatchingTrackers, - unordered_map& newAtomMatchingTrackerMap, - vector>& newConditionTrackers, - unordered_map& newConditionTrackerMap, - vector>& newMetricProducers, - unordered_map& newMetricProducerMap, - vector>& newAnomalyTrackers, - unordered_map& newAlertTrackerMap, - vector>& newPeriodicAlarmTrackers, - unordered_map>& conditionToMetricMap, - unordered_map>& trackerToMetricMap, - unordered_map>& trackerToConditionMap, - unordered_map>& activationTrackerToMetricMap, - unordered_map>& deactivationTrackerToMetricMap, - vector& metricsWithActivation, - map& newStateProtoHashes, - set& noReportMetricIds) { - set replacedMatchers; - set replacedConditions; - set replacedStates; - set replacedMetrics; - vector conditionCache; - unordered_map stateAtomIdMap; - unordered_map> allStateGroupMaps; - - if (!updateAtomMatchingTrackers(config, uidMap, oldAtomMatchingTrackerMap, - oldAtomMatchingTrackers, allTagIds, newAtomMatchingTrackerMap, - newAtomMatchingTrackers, replacedMatchers)) { - ALOGE("updateAtomMatchingTrackers failed"); - return false; - } - - if (!updateConditions(key, config, newAtomMatchingTrackerMap, replacedMatchers, - oldConditionTrackerMap, oldConditionTrackers, newConditionTrackerMap, - newConditionTrackers, trackerToConditionMap, conditionCache, - replacedConditions)) { - ALOGE("updateConditions failed"); - return false; - } - - if (!updateStates(config, oldStateProtoHashes, stateAtomIdMap, allStateGroupMaps, - newStateProtoHashes, replacedStates)) { - ALOGE("updateStates failed"); - return false; - } - if (!updateMetrics(key, config, timeBaseNs, currentTimeNs, pullerManager, - oldAtomMatchingTrackerMap, newAtomMatchingTrackerMap, replacedMatchers, - newAtomMatchingTrackers, newConditionTrackerMap, replacedConditions, - newConditionTrackers, conditionCache, stateAtomIdMap, allStateGroupMaps, - replacedStates, oldMetricProducerMap, oldMetricProducers, - newMetricProducerMap, newMetricProducers, conditionToMetricMap, - trackerToMetricMap, noReportMetricIds, activationTrackerToMetricMap, - deactivationTrackerToMetricMap, metricsWithActivation, replacedMetrics)) { - ALOGE("updateMetrics failed"); - return false; - } - - if (!updateAlerts(config, currentTimeNs, newMetricProducerMap, replacedMetrics, - oldAlertTrackerMap, oldAnomalyTrackers, anomalyAlarmMonitor, - newMetricProducers, newAlertTrackerMap, newAnomalyTrackers)) { - ALOGE("updateAlerts failed"); - return false; - } - - // Alarms do not have any state, so we can reuse the initialization logic. - if (!initAlarms(config, key, periodicAlarmMonitor, timeBaseNs, currentTimeNs, - newPeriodicAlarmTrackers)) { - ALOGE("initAlarms failed"); - return false; - } - return true; -} - -} // namespace statsd -} // namespace os -} // namespace android diff --git a/bin/src/metrics/parsing_utils/config_update_utils.h b/bin/src/metrics/parsing_utils/config_update_utils.h deleted file mode 100644 index 24f8c850..00000000 --- a/bin/src/metrics/parsing_utils/config_update_utils.h +++ /dev/null @@ -1,267 +0,0 @@ -/* - * Copyright (C) 2020 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. - */ - -#pragma once - -#include - -#include "anomaly/AlarmMonitor.h" -#include "anomaly/AlarmTracker.h" -#include "condition/ConditionTracker.h" -#include "external/StatsPullerManager.h" -#include "matchers/AtomMatchingTracker.h" -#include "metrics/MetricProducer.h" - -namespace android { -namespace os { -namespace statsd { - -// Helper functions for MetricsManager to update itself from a new StatsdConfig. -// *Note*: only updateStatsdConfig() should be called from outside this file. -// All other functions are intermediate steps, created to make unit testing easier. - -// Recursive function to determine if a matcher needs to be updated. -// input: -// [config]: the input StatsdConfig -// [matcherIdx]: the index of the current matcher to be updated -// [oldAtomMatchingTrackerMap]: matcher id to index mapping in the existing MetricsManager -// [oldAtomMatchingTrackers]: stores the existing AtomMatchingTrackers -// [newAtomMatchingTrackerMap]: matcher id to index mapping in the input StatsdConfig -// output: -// [matchersToUpdate]: vector of the update status of each matcher. The matcherIdx index will -// be updated from UPDATE_UNKNOWN after this call. -// [cycleTracker]: intermediate param used during recursion. -// Returns whether the function was successful or not. -bool determineMatcherUpdateStatus( - const StatsdConfig& config, const int matcherIdx, - const std::unordered_map& oldAtomMatchingTrackerMap, - const std::vector>& oldAtomMatchingTrackers, - const std::unordered_map& newAtomMatchingTrackerMap, - std::vector& matchersToUpdate, std::vector& cycleTracker); - -// Updates the AtomMatchingTrackers. -// input: -// [config]: the input StatsdConfig -// [oldAtomMatchingTrackerMap]: existing matcher id to index mapping -// [oldAtomMatchingTrackers]: stores the existing AtomMatchingTrackers -// output: -// [allTagIds]: contains the set of all interesting tag ids to this config. -// [newAtomMatchingTrackerMap]: new matcher id to index mapping -// [newAtomMatchingTrackers]: stores the new AtomMatchingTrackers -// [replacedMatchers]: set of matcher ids that changed and have been replaced -bool updateAtomMatchingTrackers(const StatsdConfig& config, const sp& uidMap, - const std::unordered_map& oldAtomMatchingTrackerMap, - const std::vector>& oldAtomMatchingTrackers, - std::set& allTagIds, - std::unordered_map& newAtomMatchingTrackerMap, - std::vector>& newAtomMatchingTrackers, - std::set& replacedMatchers); - -// Recursive function to determine if a condition needs to be updated. -// input: -// [config]: the input StatsdConfig -// [conditionIdx]: the index of the current condition to be updated -// [oldConditionTrackerMap]: condition id to index mapping in the existing MetricsManager -// [oldConditionTrackers]: stores the existing ConditionTrackers -// [newConditionTrackerMap]: condition id to index mapping in the input StatsdConfig -// [replacedMatchers]: set of replaced matcher ids. conditions using these matchers must be replaced -// output: -// [conditionsToUpdate]: vector of the update status of each condition. The conditionIdx index will -// be updated from UPDATE_UNKNOWN after this call. -// [cycleTracker]: intermediate param used during recursion. -// Returns whether the function was successful or not. -bool determineConditionUpdateStatus(const StatsdConfig& config, const int conditionIdx, - const std::unordered_map& oldConditionTrackerMap, - const std::vector>& oldConditionTrackers, - const std::unordered_map& newConditionTrackerMap, - const std::set& replacedMatchers, - std::vector& conditionsToUpdate, - std::vector& cycleTracker); - -// Updates ConditionTrackers -// input: -// [config]: the input config -// [atomMatchingTrackerMap]: AtomMatchingTracker name to index mapping from previous step. -// [replacedMatchers]: ids of replaced matchers. conditions depending on these must also be replaced -// [oldConditionTrackerMap]: existing matcher id to index mapping -// [oldConditionTrackers]: stores the existing ConditionTrackers -// output: -// [newConditionTrackerMap]: new condition id to index mapping -// [newConditionTrackers]: stores the sp to all the ConditionTrackers -// [trackerToConditionMap]: contains the mapping from the index of an atom matcher -// to indices of condition trackers that use the matcher -// [conditionCache]: stores the current conditions for each ConditionTracker -// [replacedConditions]: set of condition ids that have changed and have been replaced -bool updateConditions(const ConfigKey& key, const StatsdConfig& config, - const std::unordered_map& atomMatchingTrackerMap, - const std::set& replacedMatchers, - const std::unordered_map& oldConditionTrackerMap, - const std::vector>& oldConditionTrackers, - std::unordered_map& newConditionTrackerMap, - std::vector>& newConditionTrackers, - std::unordered_map>& trackerToConditionMap, - std::vector& conditionCache, - std::set& replacedConditions); - -bool updateStates(const StatsdConfig& config, - const std::map& oldStateProtoHashes, - std::unordered_map& stateAtomIdMap, - std::unordered_map>& allStateGroupMaps, - std::map& newStateProtoHashes, - std::set& replacedStates); - -// Function to determine the update status (preserve/replace/new) of all metrics in the config. -// [config]: the input StatsdConfig -// [oldMetricProducerMap]: metric id to index mapping in the existing MetricsManager -// [oldMetricProducers]: stores the existing MetricProducers -// [metricToActivationMap]: map from metric id to metric activation index -// [replacedMatchers]: set of replaced matcher ids. metrics using these matchers must be replaced -// [replacedConditions]: set of replaced conditions. metrics using these conditions must be replaced -// [replacedStates]: set of replaced state ids. metrics using these states must be replaced -// output: -// [metricsToUpdate]: update status of each metric. Will be changed from UPDATE_UNKNOWN -// Returns whether the function was successful or not. -bool determineAllMetricUpdateStatuses(const StatsdConfig& config, - const unordered_map& oldMetricProducerMap, - const vector>& oldMetricProducers, - const unordered_map& metricToActivationMap, - const set& replacedMatchers, - const set& replacedConditions, - const set& replacedStates, - vector& metricsToUpdate); - -// Update MetricProducers. -// input: -// [key]: the config key that this config belongs to -// [config]: the input config -// [timeBaseNs]: start time base for all metrics -// [currentTimeNs]: time of the config update -// [atomMatchingTrackerMap]: AtomMatchingTracker name to index mapping from previous step. -// [replacedMatchers]: ids of replaced matchers. Metrics depending on these must also be replaced -// [allAtomMatchingTrackers]: stores the sp of the atom matchers. -// [conditionTrackerMap]: condition name to index mapping -// [replacedConditions]: set of condition ids that have changed and have been replaced -// [stateAtomIdMap]: contains the mapping from state ids to atom ids -// [allStateGroupMaps]: contains the mapping from atom ids and state values to -// state group ids for all states -// output: -// [allMetricProducers]: contains the list of sp to the MetricProducers created. -// [conditionToMetricMap]: contains the mapping from condition tracker index to -// the list of MetricProducer index -// [trackerToMetricMap]: contains the mapping from log tracker to MetricProducer index. -bool updateMetrics( - const ConfigKey& key, const StatsdConfig& config, const int64_t timeBaseNs, - const int64_t currentTimeNs, const sp& pullerManager, - const std::unordered_map& oldAtomMatchingTrackerMap, - const std::unordered_map& newAtomMatchingTrackerMap, - const std::set& replacedMatchers, - const std::vector>& allAtomMatchingTrackers, - const std::unordered_map& conditionTrackerMap, - const std::set& replacedConditions, - std::vector>& allConditionTrackers, - const std::vector& initialConditionCache, - const std::unordered_map& stateAtomIdMap, - const std::unordered_map>& allStateGroupMaps, - const std::set& replacedStates, - const std::unordered_map& oldMetricProducerMap, - const std::vector>& oldMetricProducers, - std::unordered_map& newMetricProducerMap, - std::vector>& newMetricProducers, - std::unordered_map>& conditionToMetricMap, - std::unordered_map>& trackerToMetricMap, - std::set& noReportMetricIds, - std::unordered_map>& activationAtomTrackerToMetricMap, - std::unordered_map>& deactivationAtomTrackerToMetricMap, - std::vector& metricsWithActivation, std::set& replacedMetrics); - -// Function to determine the update status (preserve/replace/new) of an alert. -// [alert]: the input Alert -// [oldAlertTrackerMap]: alert id to index mapping in the existing MetricsManager -// [oldAnomalyTrackers]: stores the existing AnomalyTrackers -// [replacedMetrics]: set of replaced metric ids. alerts using these metrics must be replaced -// output: -// [updateStatus]: update status of the alert. Will be changed from UPDATE_UNKNOWN -// Returns whether the function was successful or not. -bool determineAlertUpdateStatus(const Alert& alert, - const std::unordered_map& oldAlertTrackerMap, - const std::vector>& oldAnomalyTrackers, - const std::set& replacedMetrics, - UpdateStatus& updateStatus); - -// Update MetricProducers. -// input: -// [config]: the input config -// [currentTimeNs]: time of the config update -// [metricProducerMap]: metric id to index mapping in the new config -// [replacedMetrics]: set of metric ids that have changed and were replaced -// [oldAlertTrackerMap]: alert id to index mapping in the existing MetricsManager. -// [oldAnomalyTrackers]: stores the existing AnomalyTrackers -// [anomalyAlarmMonitor]: AlarmMonitor used for duration metric anomaly detection -// [allMetricProducers]: stores the sp of the metric producers, AnomalyTrackers need to be added. -// [stateAtomIdMap]: contains the mapping from state ids to atom ids -// [allStateGroupMaps]: contains the mapping from atom ids and state values to -// state group ids for all states -// output: -// [newAlertTrackerMap]: mapping of alert id to index in the new config -// [newAnomalyTrackers]: contains the list of sp to the AnomalyTrackers created. -bool updateAlerts(const StatsdConfig& config, const int64_t currentTimeNs, - const std::unordered_map& metricProducerMap, - const std::set& replacedMetrics, - const std::unordered_map& oldAlertTrackerMap, - const std::vector>& oldAnomalyTrackers, - const sp& anomalyAlarmMonitor, - std::vector>& allMetricProducers, - std::unordered_map& newAlertTrackerMap, - std::vector>& newAnomalyTrackers); - -// Updates the existing MetricsManager from a new StatsdConfig. -// Parameters are the members of MetricsManager. See MetricsManager for declaration. -bool updateStatsdConfig(const ConfigKey& key, const StatsdConfig& config, const sp& uidMap, - const sp& pullerManager, - const sp& anomalyAlarmMonitor, - const sp& periodicAlarmMonitor, const int64_t timeBaseNs, - const int64_t currentTimeNs, - const std::vector>& oldAtomMatchingTrackers, - const std::unordered_map& oldAtomMatchingTrackerMap, - const std::vector>& oldConditionTrackers, - const std::unordered_map& oldConditionTrackerMap, - const std::vector>& oldMetricProducers, - const std::unordered_map& oldMetricProducerMap, - const std::vector>& oldAnomalyTrackers, - const std::unordered_map& oldAlertTrackerMap, - const std::map& oldStateProtoHashes, - std::set& allTagIds, - std::vector>& newAtomMatchingTrackers, - std::unordered_map& newAtomMatchingTrackerMap, - std::vector>& newConditionTrackers, - std::unordered_map& newConditionTrackerMap, - std::vector>& newMetricProducers, - std::unordered_map& newMetricProducerMap, - std::vector>& newAlertTrackers, - std::unordered_map& newAlertTrackerMap, - std::vector>& newPeriodicAlarmTrackers, - std::unordered_map>& conditionToMetricMap, - std::unordered_map>& trackerToMetricMap, - std::unordered_map>& trackerToConditionMap, - std::unordered_map>& activationTrackerToMetricMap, - std::unordered_map>& deactivationTrackerToMetricMap, - std::vector& metricsWithActivation, - std::map& newStateProtoHashes, - std::set& noReportMetricIds); - -} // namespace statsd -} // namespace os -} // namespace android diff --git a/bin/src/metrics/parsing_utils/metrics_manager_util.cpp b/bin/src/metrics/parsing_utils/metrics_manager_util.cpp deleted file mode 100644 index 1e230008..00000000 --- a/bin/src/metrics/parsing_utils/metrics_manager_util.cpp +++ /dev/null @@ -1,1246 +0,0 @@ -/* - * 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. - */ - -#define DEBUG false // STOPSHIP if true -#include "Log.h" - -#include "metrics_manager_util.h" - -#include - -#include "FieldValue.h" -#include "condition/CombinationConditionTracker.h" -#include "condition/SimpleConditionTracker.h" -#include "external/StatsPullerManager.h" -#include "hash.h" -#include "matchers/CombinationAtomMatchingTracker.h" -#include "matchers/EventMatcherWizard.h" -#include "matchers/SimpleAtomMatchingTracker.h" -#include "metrics/CountMetricProducer.h" -#include "metrics/DurationMetricProducer.h" -#include "metrics/EventMetricProducer.h" -#include "metrics/GaugeMetricProducer.h" -#include "metrics/MetricProducer.h" -#include "metrics/ValueMetricProducer.h" -#include "state/StateManager.h" -#include "stats_util.h" - -using google::protobuf::MessageLite; -using std::set; -using std::unordered_map; -using std::vector; - -namespace android { -namespace os { -namespace statsd { - -namespace { - -bool hasLeafNode(const FieldMatcher& matcher) { - if (!matcher.has_field()) { - return false; - } - for (int i = 0; i < matcher.child_size(); ++i) { - if (hasLeafNode(matcher.child(i))) { - return true; - } - } - return true; -} - -} // namespace - -sp createAtomMatchingTracker(const AtomMatcher& logMatcher, const int index, - const sp& uidMap) { - string serializedMatcher; - if (!logMatcher.SerializeToString(&serializedMatcher)) { - ALOGE("Unable to serialize matcher %lld", (long long)logMatcher.id()); - return nullptr; - } - uint64_t protoHash = Hash64(serializedMatcher); - switch (logMatcher.contents_case()) { - case AtomMatcher::ContentsCase::kSimpleAtomMatcher: - return new SimpleAtomMatchingTracker(logMatcher.id(), index, protoHash, - logMatcher.simple_atom_matcher(), uidMap); - case AtomMatcher::ContentsCase::kCombination: - return new CombinationAtomMatchingTracker(logMatcher.id(), index, protoHash); - default: - ALOGE("Matcher \"%lld\" malformed", (long long)logMatcher.id()); - return nullptr; - } -} - -sp createConditionTracker( - const ConfigKey& key, const Predicate& predicate, const int index, - const unordered_map& atomMatchingTrackerMap) { - string serializedPredicate; - if (!predicate.SerializeToString(&serializedPredicate)) { - ALOGE("Unable to serialize predicate %lld", (long long)predicate.id()); - return nullptr; - } - uint64_t protoHash = Hash64(serializedPredicate); - switch (predicate.contents_case()) { - case Predicate::ContentsCase::kSimplePredicate: { - return new SimpleConditionTracker(key, predicate.id(), protoHash, index, - predicate.simple_predicate(), atomMatchingTrackerMap); - } - case Predicate::ContentsCase::kCombination: { - return new CombinationConditionTracker(predicate.id(), index, protoHash); - } - default: - ALOGE("Predicate \"%lld\" malformed", (long long)predicate.id()); - return nullptr; - } -} - -bool getMetricProtoHash(const StatsdConfig& config, const MessageLite& metric, const int64_t id, - const unordered_map& metricToActivationMap, - uint64_t& metricHash) { - string serializedMetric; - if (!metric.SerializeToString(&serializedMetric)) { - ALOGE("Unable to serialize metric %lld", (long long)id); - return false; - } - metricHash = Hash64(serializedMetric); - - // Combine with activation hash, if applicable - const auto& metricActivationIt = metricToActivationMap.find(id); - if (metricActivationIt != metricToActivationMap.end()) { - string serializedActivation; - const MetricActivation& activation = config.metric_activation(metricActivationIt->second); - if (!activation.SerializeToString(&serializedActivation)) { - ALOGE("Unable to serialize metric activation for metric %lld", (long long)id); - return false; - } - metricHash = Hash64(to_string(metricHash).append(to_string(Hash64(serializedActivation)))); - } - return true; -} - -bool handleMetricWithAtomMatchingTrackers( - const int64_t matcherId, const int metricIndex, const bool enforceOneAtom, - const vector>& allAtomMatchingTrackers, - const unordered_map& atomMatchingTrackerMap, - unordered_map>& trackerToMetricMap, int& logTrackerIndex) { - auto logTrackerIt = atomMatchingTrackerMap.find(matcherId); - if (logTrackerIt == atomMatchingTrackerMap.end()) { - ALOGW("cannot find the AtomMatcher \"%lld\" in config", (long long)matcherId); - return false; - } - if (enforceOneAtom && allAtomMatchingTrackers[logTrackerIt->second]->getAtomIds().size() > 1) { - ALOGE("AtomMatcher \"%lld\" has more than one tag ids. When a metric has dimension, " - "the \"what\" can only be about one atom type. trigger_event matchers can also only " - "be about one atom type.", - (long long)matcherId); - return false; - } - logTrackerIndex = logTrackerIt->second; - auto& metric_list = trackerToMetricMap[logTrackerIndex]; - metric_list.push_back(metricIndex); - return true; -} - -bool handleMetricWithConditions( - const int64_t condition, const int metricIndex, - const unordered_map& conditionTrackerMap, - const ::google::protobuf::RepeatedPtrField<::android::os::statsd::MetricConditionLink>& - links, - const vector>& allConditionTrackers, int& conditionIndex, - unordered_map>& conditionToMetricMap) { - auto condition_it = conditionTrackerMap.find(condition); - if (condition_it == conditionTrackerMap.end()) { - ALOGW("cannot find Predicate \"%lld\" in the config", (long long)condition); - return false; - } - - for (const auto& link : links) { - auto it = conditionTrackerMap.find(link.condition()); - if (it == conditionTrackerMap.end()) { - ALOGW("cannot find Predicate \"%lld\" in the config", (long long)link.condition()); - return false; - } - } - conditionIndex = condition_it->second; - - // will create new vector if not exist before. - auto& metricList = conditionToMetricMap[condition_it->second]; - metricList.push_back(metricIndex); - return true; -} - -// Initializes state data structures for a metric. -// input: -// [config]: the input config -// [stateIds]: the slice_by_state ids for this metric -// [stateAtomIdMap]: this map contains the mapping from all state ids to atom ids -// [allStateGroupMaps]: this map contains the mapping from state ids and state -// values to state group ids for all states -// output: -// [slicedStateAtoms]: a vector of atom ids of all the slice_by_states -// [stateGroupMap]: this map should contain the mapping from states ids and state -// values to state group ids for all states that this metric -// is interested in -bool handleMetricWithStates( - const StatsdConfig& config, const ::google::protobuf::RepeatedField& stateIds, - const unordered_map& stateAtomIdMap, - const unordered_map>& allStateGroupMaps, - vector& slicedStateAtoms, - unordered_map>& stateGroupMap) { - for (const auto& stateId : stateIds) { - auto it = stateAtomIdMap.find(stateId); - if (it == stateAtomIdMap.end()) { - ALOGW("cannot find State %" PRId64 " in the config", stateId); - return false; - } - int atomId = it->second; - slicedStateAtoms.push_back(atomId); - - auto stateIt = allStateGroupMaps.find(stateId); - if (stateIt != allStateGroupMaps.end()) { - stateGroupMap[atomId] = stateIt->second; - } - } - return true; -} - -bool handleMetricWithStateLink(const FieldMatcher& stateMatcher, - const vector& dimensionsInWhat) { - vector stateMatchers; - translateFieldMatcher(stateMatcher, &stateMatchers); - - return subsetDimensions(stateMatchers, dimensionsInWhat); -} - -// Validates a metricActivation and populates state. -// EventActivationMap and EventDeactivationMap are supplied to a MetricProducer -// to provide the producer with state about its activators and deactivators. -// Returns false if there are errors. -bool handleMetricActivation( - const StatsdConfig& config, const int64_t metricId, const int metricIndex, - const unordered_map& metricToActivationMap, - const unordered_map& atomMatchingTrackerMap, - unordered_map>& activationAtomTrackerToMetricMap, - unordered_map>& deactivationAtomTrackerToMetricMap, - vector& metricsWithActivation, - unordered_map>& eventActivationMap, - unordered_map>>& eventDeactivationMap) { - // Check if metric has an associated activation - auto itr = metricToActivationMap.find(metricId); - if (itr == metricToActivationMap.end()) { - return true; - } - - int activationIndex = itr->second; - const MetricActivation& metricActivation = config.metric_activation(activationIndex); - - for (int i = 0; i < metricActivation.event_activation_size(); i++) { - const EventActivation& activation = metricActivation.event_activation(i); - - auto itr = atomMatchingTrackerMap.find(activation.atom_matcher_id()); - if (itr == atomMatchingTrackerMap.end()) { - ALOGE("Atom matcher not found for event activation."); - return false; - } - - ActivationType activationType = (activation.has_activation_type()) - ? activation.activation_type() - : metricActivation.activation_type(); - std::shared_ptr activationWrapper = - std::make_shared(activationType, activation.ttl_seconds() * NS_PER_SEC); - - int atomMatcherIndex = itr->second; - activationAtomTrackerToMetricMap[atomMatcherIndex].push_back(metricIndex); - eventActivationMap.emplace(atomMatcherIndex, activationWrapper); - - if (activation.has_deactivation_atom_matcher_id()) { - itr = atomMatchingTrackerMap.find(activation.deactivation_atom_matcher_id()); - if (itr == atomMatchingTrackerMap.end()) { - ALOGE("Atom matcher not found for event deactivation."); - return false; - } - int deactivationAtomMatcherIndex = itr->second; - deactivationAtomTrackerToMetricMap[deactivationAtomMatcherIndex].push_back(metricIndex); - eventDeactivationMap[deactivationAtomMatcherIndex].push_back(activationWrapper); - } - } - - metricsWithActivation.push_back(metricIndex); - return true; -} - -// Validates a metricActivation and populates state. -// Fills the new event activation/deactivation maps, preserving the existing activations -// Returns false if there are errors. -bool handleMetricActivationOnConfigUpdate( - const StatsdConfig& config, const int64_t metricId, const int metricIndex, - const unordered_map& metricToActivationMap, - const unordered_map& oldAtomMatchingTrackerMap, - const unordered_map& newAtomMatchingTrackerMap, - const unordered_map>& oldEventActivationMap, - unordered_map>& activationAtomTrackerToMetricMap, - unordered_map>& deactivationAtomTrackerToMetricMap, - vector& metricsWithActivation, - unordered_map>& newEventActivationMap, - unordered_map>>& newEventDeactivationMap) { - // Check if metric has an associated activation. - const auto& itr = metricToActivationMap.find(metricId); - if (itr == metricToActivationMap.end()) { - return true; - } - - int activationIndex = itr->second; - const MetricActivation& metricActivation = config.metric_activation(activationIndex); - - for (int i = 0; i < metricActivation.event_activation_size(); i++) { - const int64_t activationMatcherId = metricActivation.event_activation(i).atom_matcher_id(); - - const auto& newActivationIt = newAtomMatchingTrackerMap.find(activationMatcherId); - if (newActivationIt == newAtomMatchingTrackerMap.end()) { - ALOGE("Atom matcher not found in new config for event activation."); - return false; - } - int newActivationMatcherIndex = newActivationIt->second; - - // Find the old activation struct and copy it over. - const auto& oldActivationIt = oldAtomMatchingTrackerMap.find(activationMatcherId); - if (oldActivationIt == oldAtomMatchingTrackerMap.end()) { - ALOGE("Atom matcher not found in existing config for event activation."); - return false; - } - int oldActivationMatcherIndex = oldActivationIt->second; - const auto& oldEventActivationIt = oldEventActivationMap.find(oldActivationMatcherIndex); - if (oldEventActivationIt == oldEventActivationMap.end()) { - ALOGE("Could not find existing event activation to update"); - return false; - } - newEventActivationMap.emplace(newActivationMatcherIndex, oldEventActivationIt->second); - activationAtomTrackerToMetricMap[newActivationMatcherIndex].push_back(metricIndex); - - if (metricActivation.event_activation(i).has_deactivation_atom_matcher_id()) { - const int64_t deactivationMatcherId = - metricActivation.event_activation(i).deactivation_atom_matcher_id(); - const auto& newDeactivationIt = newAtomMatchingTrackerMap.find(deactivationMatcherId); - if (newDeactivationIt == newAtomMatchingTrackerMap.end()) { - ALOGE("Deactivation atom matcher not found in new config for event activation."); - return false; - } - int newDeactivationMatcherIndex = newDeactivationIt->second; - newEventDeactivationMap[newDeactivationMatcherIndex].push_back( - oldEventActivationIt->second); - deactivationAtomTrackerToMetricMap[newDeactivationMatcherIndex].push_back(metricIndex); - } - } - - metricsWithActivation.push_back(metricIndex); - return true; -} - -optional> createCountMetricProducerAndUpdateMetadata( - const ConfigKey& key, const StatsdConfig& config, const int64_t timeBaseNs, - const int64_t currentTimeNs, const CountMetric& metric, const int metricIndex, - const vector>& allAtomMatchingTrackers, - const unordered_map& atomMatchingTrackerMap, - vector>& allConditionTrackers, - const unordered_map& conditionTrackerMap, - const vector& initialConditionCache, const sp& wizard, - const unordered_map& stateAtomIdMap, - const unordered_map>& allStateGroupMaps, - const unordered_map& metricToActivationMap, - unordered_map>& trackerToMetricMap, - unordered_map>& conditionToMetricMap, - unordered_map>& activationAtomTrackerToMetricMap, - unordered_map>& deactivationAtomTrackerToMetricMap, - vector& metricsWithActivation) { - if (!metric.has_id() || !metric.has_what()) { - ALOGE("cannot find metric id or \"what\" in CountMetric \"%lld\"", (long long)metric.id()); - return nullopt; - } - int trackerIndex; - if (!handleMetricWithAtomMatchingTrackers(metric.what(), metricIndex, - metric.has_dimensions_in_what(), - allAtomMatchingTrackers, atomMatchingTrackerMap, - trackerToMetricMap, trackerIndex)) { - return nullopt; - } - - int conditionIndex = -1; - if (metric.has_condition()) { - if (!handleMetricWithConditions(metric.condition(), metricIndex, conditionTrackerMap, - metric.links(), allConditionTrackers, conditionIndex, - conditionToMetricMap)) { - return nullopt; - } - } else { - if (metric.links_size() > 0) { - ALOGW("metrics has a MetricConditionLink but doesn't have a condition"); - return nullopt; - } - } - - std::vector slicedStateAtoms; - unordered_map> stateGroupMap; - if (metric.slice_by_state_size() > 0) { - if (!handleMetricWithStates(config, metric.slice_by_state(), stateAtomIdMap, - allStateGroupMaps, slicedStateAtoms, stateGroupMap)) { - return nullopt; - } - } else { - if (metric.state_link_size() > 0) { - ALOGW("CountMetric has a MetricStateLink but doesn't have a slice_by_state"); - return nullopt; - } - } - - unordered_map> eventActivationMap; - unordered_map>> eventDeactivationMap; - if (!handleMetricActivation(config, metric.id(), metricIndex, metricToActivationMap, - atomMatchingTrackerMap, activationAtomTrackerToMetricMap, - deactivationAtomTrackerToMetricMap, metricsWithActivation, - eventActivationMap, eventDeactivationMap)) { - return nullopt; - } - - uint64_t metricHash; - if (!getMetricProtoHash(config, metric, metric.id(), metricToActivationMap, metricHash)) { - return nullopt; - } - - return {new CountMetricProducer(key, metric, conditionIndex, initialConditionCache, wizard, - metricHash, timeBaseNs, currentTimeNs, eventActivationMap, - eventDeactivationMap, slicedStateAtoms, stateGroupMap)}; -} - -optional> createDurationMetricProducerAndUpdateMetadata( - const ConfigKey& key, const StatsdConfig& config, const int64_t timeBaseNs, - const int64_t currentTimeNs, const DurationMetric& metric, const int metricIndex, - const vector>& allAtomMatchingTrackers, - const unordered_map& atomMatchingTrackerMap, - vector>& allConditionTrackers, - const unordered_map& conditionTrackerMap, - const vector& initialConditionCache, const sp& wizard, - const unordered_map& stateAtomIdMap, - const unordered_map>& allStateGroupMaps, - const unordered_map& metricToActivationMap, - unordered_map>& trackerToMetricMap, - unordered_map>& conditionToMetricMap, - unordered_map>& activationAtomTrackerToMetricMap, - unordered_map>& deactivationAtomTrackerToMetricMap, - vector& metricsWithActivation) { - if (!metric.has_id() || !metric.has_what()) { - ALOGE("cannot find metric id or \"what\" in DurationMetric \"%lld\"", - (long long)metric.id()); - return nullopt; - } - const auto& what_it = conditionTrackerMap.find(metric.what()); - if (what_it == conditionTrackerMap.end()) { - ALOGE("DurationMetric's \"what\" is not present in the condition trackers"); - return nullopt; - } - - const int whatIndex = what_it->second; - const Predicate& durationWhat = config.predicate(whatIndex); - if (durationWhat.contents_case() != Predicate::ContentsCase::kSimplePredicate) { - ALOGE("DurationMetric's \"what\" must be a simple condition"); - return nullopt; - } - - const SimplePredicate& simplePredicate = durationWhat.simple_predicate(); - bool nesting = simplePredicate.count_nesting(); - - int startIndex = -1, stopIndex = -1, stopAllIndex = -1; - if (!simplePredicate.has_start() || - !handleMetricWithAtomMatchingTrackers( - simplePredicate.start(), metricIndex, metric.has_dimensions_in_what(), - allAtomMatchingTrackers, atomMatchingTrackerMap, trackerToMetricMap, startIndex)) { - ALOGE("Duration metrics must specify a valid start event matcher"); - return nullopt; - } - - if (simplePredicate.has_stop() && - !handleMetricWithAtomMatchingTrackers( - simplePredicate.stop(), metricIndex, metric.has_dimensions_in_what(), - allAtomMatchingTrackers, atomMatchingTrackerMap, trackerToMetricMap, stopIndex)) { - return nullopt; - } - - if (simplePredicate.has_stop_all() && - !handleMetricWithAtomMatchingTrackers(simplePredicate.stop_all(), metricIndex, - metric.has_dimensions_in_what(), - allAtomMatchingTrackers, atomMatchingTrackerMap, - trackerToMetricMap, stopAllIndex)) { - return nullopt; - } - - FieldMatcher internalDimensions = simplePredicate.dimensions(); - - int conditionIndex = -1; - if (metric.has_condition()) { - if (!handleMetricWithConditions(metric.condition(), metricIndex, conditionTrackerMap, - metric.links(), allConditionTrackers, conditionIndex, - conditionToMetricMap)) { - return nullopt; - } - } else if (metric.links_size() > 0) { - ALOGW("metrics has a MetricConditionLink but doesn't have a condition"); - return nullopt; - } - - std::vector slicedStateAtoms; - unordered_map> stateGroupMap; - if (metric.slice_by_state_size() > 0) { - if (metric.aggregation_type() == DurationMetric::MAX_SPARSE) { - ALOGE("DurationMetric with aggregation type MAX_SPARSE cannot be sliced by state"); - return nullopt; - } - if (!handleMetricWithStates(config, metric.slice_by_state(), stateAtomIdMap, - allStateGroupMaps, slicedStateAtoms, stateGroupMap)) { - return nullopt; - } - } else if (metric.state_link_size() > 0) { - ALOGW("DurationMetric has a MetricStateLink but doesn't have a sliced state"); - return nullopt; - } - - // Check that all metric state links are a subset of dimensions_in_what fields. - std::vector dimensionsInWhat; - translateFieldMatcher(metric.dimensions_in_what(), &dimensionsInWhat); - for (const auto& stateLink : metric.state_link()) { - if (!handleMetricWithStateLink(stateLink.fields_in_what(), dimensionsInWhat)) { - ALOGW("DurationMetric's MetricStateLinks must be a subset of dimensions in what"); - return nullopt; - } - } - - unordered_map> eventActivationMap; - unordered_map>> eventDeactivationMap; - if (!handleMetricActivation(config, metric.id(), metricIndex, metricToActivationMap, - atomMatchingTrackerMap, activationAtomTrackerToMetricMap, - deactivationAtomTrackerToMetricMap, metricsWithActivation, - eventActivationMap, eventDeactivationMap)) { - return nullopt; - } - - uint64_t metricHash; - if (!getMetricProtoHash(config, metric, metric.id(), metricToActivationMap, metricHash)) { - return nullopt; - } - - if (metric.has_threshold()) { - switch (metric.threshold().value_comparison_case()) { - case UploadThreshold::kLtInt: - case UploadThreshold::kGtInt: - case UploadThreshold::kLteInt: - case UploadThreshold::kGteInt: - break; - default: - ALOGE("Duration metric incorrect upload threshold type or no type used"); - return nullopt; - break; - } - } - - sp producer = new DurationMetricProducer( - key, metric, conditionIndex, initialConditionCache, whatIndex, startIndex, stopIndex, - stopAllIndex, nesting, wizard, metricHash, internalDimensions, timeBaseNs, - currentTimeNs, eventActivationMap, eventDeactivationMap, slicedStateAtoms, - stateGroupMap); - if (!producer->isValid()) { - return nullopt; - } - return {producer}; -} - -optional> createEventMetricProducerAndUpdateMetadata( - const ConfigKey& key, const StatsdConfig& config, const int64_t timeBaseNs, - const EventMetric& metric, const int metricIndex, - const vector>& allAtomMatchingTrackers, - const unordered_map& atomMatchingTrackerMap, - vector>& allConditionTrackers, - const unordered_map& conditionTrackerMap, - const vector& initialConditionCache, const sp& wizard, - const unordered_map& metricToActivationMap, - unordered_map>& trackerToMetricMap, - unordered_map>& conditionToMetricMap, - unordered_map>& activationAtomTrackerToMetricMap, - unordered_map>& deactivationAtomTrackerToMetricMap, - vector& metricsWithActivation) { - if (!metric.has_id() || !metric.has_what()) { - ALOGE("cannot find the metric name or what in config"); - return nullopt; - } - int trackerIndex; - if (!handleMetricWithAtomMatchingTrackers(metric.what(), metricIndex, false, - allAtomMatchingTrackers, atomMatchingTrackerMap, - trackerToMetricMap, trackerIndex)) { - return nullopt; - } - - int conditionIndex = -1; - if (metric.has_condition()) { - if (!handleMetricWithConditions(metric.condition(), metricIndex, conditionTrackerMap, - metric.links(), allConditionTrackers, conditionIndex, - conditionToMetricMap)) { - return nullopt; - } - } else { - if (metric.links_size() > 0) { - ALOGW("metrics has a MetricConditionLink but doesn't have a condition"); - return nullopt; - } - } - - unordered_map> eventActivationMap; - unordered_map>> eventDeactivationMap; - bool success = handleMetricActivation(config, metric.id(), metricIndex, metricToActivationMap, - atomMatchingTrackerMap, activationAtomTrackerToMetricMap, - deactivationAtomTrackerToMetricMap, metricsWithActivation, - eventActivationMap, eventDeactivationMap); - if (!success) return nullptr; - - uint64_t metricHash; - if (!getMetricProtoHash(config, metric, metric.id(), metricToActivationMap, metricHash)) { - return nullopt; - } - - return {new EventMetricProducer(key, metric, conditionIndex, initialConditionCache, wizard, - metricHash, timeBaseNs, eventActivationMap, - eventDeactivationMap)}; -} - -optional> createValueMetricProducerAndUpdateMetadata( - const ConfigKey& key, const StatsdConfig& config, const int64_t timeBaseNs, - const int64_t currentTimeNs, const sp& pullerManager, - const ValueMetric& metric, const int metricIndex, - const vector>& allAtomMatchingTrackers, - const unordered_map& atomMatchingTrackerMap, - vector>& allConditionTrackers, - const unordered_map& conditionTrackerMap, - const vector& initialConditionCache, const sp& wizard, - const sp& matcherWizard, - const unordered_map& stateAtomIdMap, - const unordered_map>& allStateGroupMaps, - const unordered_map& metricToActivationMap, - unordered_map>& trackerToMetricMap, - unordered_map>& conditionToMetricMap, - unordered_map>& activationAtomTrackerToMetricMap, - unordered_map>& deactivationAtomTrackerToMetricMap, - vector& metricsWithActivation) { - if (!metric.has_id() || !metric.has_what()) { - ALOGE("cannot find metric id or \"what\" in ValueMetric \"%lld\"", (long long)metric.id()); - return nullopt; - } - if (!metric.has_value_field()) { - ALOGE("cannot find \"value_field\" in ValueMetric \"%lld\"", (long long)metric.id()); - return nullopt; - } - std::vector fieldMatchers; - translateFieldMatcher(metric.value_field(), &fieldMatchers); - if (fieldMatchers.size() < 1) { - ALOGE("incorrect \"value_field\" in ValueMetric \"%lld\"", (long long)metric.id()); - return nullopt; - } - - int trackerIndex; - if (!handleMetricWithAtomMatchingTrackers(metric.what(), metricIndex, - metric.has_dimensions_in_what(), - allAtomMatchingTrackers, atomMatchingTrackerMap, - trackerToMetricMap, trackerIndex)) { - return nullopt; - } - - sp atomMatcher = allAtomMatchingTrackers.at(trackerIndex); - // If it is pulled atom, it should be simple matcher with one tagId. - if (atomMatcher->getAtomIds().size() != 1) { - return nullopt; - } - int atomTagId = *(atomMatcher->getAtomIds().begin()); - int pullTagId = pullerManager->PullerForMatcherExists(atomTagId) ? atomTagId : -1; - - int conditionIndex = -1; - if (metric.has_condition()) { - if (!handleMetricWithConditions(metric.condition(), metricIndex, conditionTrackerMap, - metric.links(), allConditionTrackers, conditionIndex, - conditionToMetricMap)) { - return nullopt; - } - } else if (metric.links_size() > 0) { - ALOGE("metrics has a MetricConditionLink but doesn't have a condition"); - return nullopt; - } - - std::vector slicedStateAtoms; - unordered_map> stateGroupMap; - if (metric.slice_by_state_size() > 0) { - if (!handleMetricWithStates(config, metric.slice_by_state(), stateAtomIdMap, - allStateGroupMaps, slicedStateAtoms, stateGroupMap)) { - return nullopt; - } - } else if (metric.state_link_size() > 0) { - ALOGE("ValueMetric has a MetricStateLink but doesn't have a sliced state"); - return nullopt; - } - - // Check that all metric state links are a subset of dimensions_in_what fields. - std::vector dimensionsInWhat; - translateFieldMatcher(metric.dimensions_in_what(), &dimensionsInWhat); - for (const auto& stateLink : metric.state_link()) { - if (!handleMetricWithStateLink(stateLink.fields_in_what(), dimensionsInWhat)) { - ALOGW("ValueMetric's MetricStateLinks must be a subset of the dimensions in what"); - return nullopt; - } - } - - unordered_map> eventActivationMap; - unordered_map>> eventDeactivationMap; - if (!handleMetricActivation(config, metric.id(), metricIndex, metricToActivationMap, - atomMatchingTrackerMap, activationAtomTrackerToMetricMap, - deactivationAtomTrackerToMetricMap, metricsWithActivation, - eventActivationMap, eventDeactivationMap)) { - return nullopt; - } - - uint64_t metricHash; - if (!getMetricProtoHash(config, metric, metric.id(), metricToActivationMap, metricHash)) { - return nullopt; - } - - return {new ValueMetricProducer(key, metric, conditionIndex, initialConditionCache, wizard, - metricHash, trackerIndex, matcherWizard, pullTagId, timeBaseNs, - currentTimeNs, pullerManager, eventActivationMap, - eventDeactivationMap, slicedStateAtoms, stateGroupMap)}; -} - -optional> createGaugeMetricProducerAndUpdateMetadata( - const ConfigKey& key, const StatsdConfig& config, const int64_t timeBaseNs, - const int64_t currentTimeNs, const sp& pullerManager, - const GaugeMetric& metric, const int metricIndex, - const vector>& allAtomMatchingTrackers, - const unordered_map& atomMatchingTrackerMap, - vector>& allConditionTrackers, - const unordered_map& conditionTrackerMap, - const vector& initialConditionCache, const sp& wizard, - const sp& matcherWizard, - const unordered_map& metricToActivationMap, - unordered_map>& trackerToMetricMap, - unordered_map>& conditionToMetricMap, - unordered_map>& activationAtomTrackerToMetricMap, - unordered_map>& deactivationAtomTrackerToMetricMap, - vector& metricsWithActivation) { - if (!metric.has_id() || !metric.has_what()) { - ALOGE("cannot find metric id or \"what\" in GaugeMetric \"%lld\"", (long long)metric.id()); - return nullopt; - } - - if ((!metric.gauge_fields_filter().has_include_all() || - (metric.gauge_fields_filter().include_all() == false)) && - !hasLeafNode(metric.gauge_fields_filter().fields())) { - ALOGW("Incorrect field filter setting in GaugeMetric %lld", (long long)metric.id()); - return nullopt; - } - if ((metric.gauge_fields_filter().has_include_all() && - metric.gauge_fields_filter().include_all() == true) && - hasLeafNode(metric.gauge_fields_filter().fields())) { - ALOGW("Incorrect field filter setting in GaugeMetric %lld", (long long)metric.id()); - return nullopt; - } - - int trackerIndex; - if (!handleMetricWithAtomMatchingTrackers(metric.what(), metricIndex, - metric.has_dimensions_in_what(), - allAtomMatchingTrackers, atomMatchingTrackerMap, - trackerToMetricMap, trackerIndex)) { - return nullopt; - } - - sp atomMatcher = allAtomMatchingTrackers.at(trackerIndex); - // For GaugeMetric atom, it should be simple matcher with one tagId. - if (atomMatcher->getAtomIds().size() != 1) { - return nullopt; - } - int atomTagId = *(atomMatcher->getAtomIds().begin()); - int pullTagId = pullerManager->PullerForMatcherExists(atomTagId) ? atomTagId : -1; - - int triggerTrackerIndex; - int triggerAtomId = -1; - if (metric.has_trigger_event()) { - if (pullTagId == -1) { - ALOGW("Pull atom not specified for trigger"); - return nullopt; - } - // trigger_event should be used with FIRST_N_SAMPLES - if (metric.sampling_type() != GaugeMetric::FIRST_N_SAMPLES) { - ALOGW("Gauge Metric with trigger event must have sampling type FIRST_N_SAMPLES"); - return nullopt; - } - if (!handleMetricWithAtomMatchingTrackers(metric.trigger_event(), metricIndex, - /*enforceOneAtom=*/true, allAtomMatchingTrackers, - atomMatchingTrackerMap, trackerToMetricMap, - triggerTrackerIndex)) { - return nullopt; - } - sp triggerAtomMatcher = - allAtomMatchingTrackers.at(triggerTrackerIndex); - triggerAtomId = *(triggerAtomMatcher->getAtomIds().begin()); - } - - if (!metric.has_trigger_event() && pullTagId != -1 && - metric.sampling_type() == GaugeMetric::FIRST_N_SAMPLES) { - ALOGW("FIRST_N_SAMPLES is only for pushed event or pull_on_trigger"); - return nullopt; - } - - int conditionIndex = -1; - if (metric.has_condition()) { - if (!handleMetricWithConditions(metric.condition(), metricIndex, conditionTrackerMap, - metric.links(), allConditionTrackers, conditionIndex, - conditionToMetricMap)) { - return nullopt; - } - } else { - if (metric.links_size() > 0) { - ALOGW("metrics has a MetricConditionLink but doesn't have a condition"); - return nullopt; - } - } - - unordered_map> eventActivationMap; - unordered_map>> eventDeactivationMap; - if (!handleMetricActivation(config, metric.id(), metricIndex, metricToActivationMap, - atomMatchingTrackerMap, activationAtomTrackerToMetricMap, - deactivationAtomTrackerToMetricMap, metricsWithActivation, - eventActivationMap, eventDeactivationMap)) { - return nullopt; - } - - uint64_t metricHash; - if (!getMetricProtoHash(config, metric, metric.id(), metricToActivationMap, metricHash)) { - return nullopt; - } - - return {new GaugeMetricProducer(key, metric, conditionIndex, initialConditionCache, wizard, - metricHash, trackerIndex, matcherWizard, pullTagId, - triggerAtomId, atomTagId, timeBaseNs, currentTimeNs, - pullerManager, eventActivationMap, eventDeactivationMap)}; -} - -optional> createAnomalyTracker( - const Alert& alert, const sp& anomalyAlarmMonitor, - const UpdateStatus& updateStatus, const int64_t currentTimeNs, - const unordered_map& metricProducerMap, - vector>& allMetricProducers) { - const auto& itr = metricProducerMap.find(alert.metric_id()); - if (itr == metricProducerMap.end()) { - ALOGW("alert \"%lld\" has unknown metric id: \"%lld\"", (long long)alert.id(), - (long long)alert.metric_id()); - return nullopt; - } - if (!alert.has_trigger_if_sum_gt()) { - ALOGW("invalid alert: missing threshold"); - return nullopt; - } - if (alert.trigger_if_sum_gt() < 0 || alert.num_buckets() <= 0) { - ALOGW("invalid alert: threshold=%f num_buckets= %d", alert.trigger_if_sum_gt(), - alert.num_buckets()); - return nullopt; - } - const int metricIndex = itr->second; - sp metric = allMetricProducers[metricIndex]; - sp anomalyTracker = - metric->addAnomalyTracker(alert, anomalyAlarmMonitor, updateStatus, currentTimeNs); - if (anomalyTracker == nullptr) { - // The ALOGW for this invalid alert was already displayed in addAnomalyTracker(). - return nullopt; - } - return {anomalyTracker}; -} - -bool initAtomMatchingTrackers(const StatsdConfig& config, const sp& uidMap, - unordered_map& atomMatchingTrackerMap, - vector>& allAtomMatchingTrackers, - set& allTagIds) { - vector matcherConfigs; - const int atomMatcherCount = config.atom_matcher_size(); - matcherConfigs.reserve(atomMatcherCount); - allAtomMatchingTrackers.reserve(atomMatcherCount); - - for (int i = 0; i < atomMatcherCount; i++) { - const AtomMatcher& logMatcher = config.atom_matcher(i); - sp tracker = createAtomMatchingTracker(logMatcher, i, uidMap); - if (tracker == nullptr) { - return false; - } - allAtomMatchingTrackers.push_back(tracker); - if (atomMatchingTrackerMap.find(logMatcher.id()) != atomMatchingTrackerMap.end()) { - ALOGE("Duplicate AtomMatcher found!"); - return false; - } - atomMatchingTrackerMap[logMatcher.id()] = i; - matcherConfigs.push_back(logMatcher); - } - - vector stackTracker2(allAtomMatchingTrackers.size(), false); - for (auto& matcher : allAtomMatchingTrackers) { - if (!matcher->init(matcherConfigs, allAtomMatchingTrackers, atomMatchingTrackerMap, - stackTracker2)) { - return false; - } - // Collect all the tag ids that are interesting. TagIds exist in leaf nodes only. - const set& tagIds = matcher->getAtomIds(); - allTagIds.insert(tagIds.begin(), tagIds.end()); - } - return true; -} - -bool initConditions(const ConfigKey& key, const StatsdConfig& config, - const unordered_map& atomMatchingTrackerMap, - unordered_map& conditionTrackerMap, - vector>& allConditionTrackers, - unordered_map>& trackerToConditionMap, - vector& initialConditionCache) { - vector conditionConfigs; - const int conditionTrackerCount = config.predicate_size(); - conditionConfigs.reserve(conditionTrackerCount); - allConditionTrackers.reserve(conditionTrackerCount); - initialConditionCache.assign(conditionTrackerCount, ConditionState::kNotEvaluated); - - for (int i = 0; i < conditionTrackerCount; i++) { - const Predicate& condition = config.predicate(i); - sp tracker = - createConditionTracker(key, condition, i, atomMatchingTrackerMap); - if (tracker == nullptr) { - return false; - } - allConditionTrackers.push_back(tracker); - if (conditionTrackerMap.find(condition.id()) != conditionTrackerMap.end()) { - ALOGE("Duplicate Predicate found!"); - return false; - } - conditionTrackerMap[condition.id()] = i; - conditionConfigs.push_back(condition); - } - - vector stackTracker(allConditionTrackers.size(), false); - for (size_t i = 0; i < allConditionTrackers.size(); i++) { - auto& conditionTracker = allConditionTrackers[i]; - if (!conditionTracker->init(conditionConfigs, allConditionTrackers, conditionTrackerMap, - stackTracker, initialConditionCache)) { - return false; - } - for (const int trackerIndex : conditionTracker->getAtomMatchingTrackerIndex()) { - auto& conditionList = trackerToConditionMap[trackerIndex]; - conditionList.push_back(i); - } - } - return true; -} - -bool initStates(const StatsdConfig& config, unordered_map& stateAtomIdMap, - unordered_map>& allStateGroupMaps, - map& stateProtoHashes) { - for (int i = 0; i < config.state_size(); i++) { - const State& state = config.state(i); - const int64_t stateId = state.id(); - stateAtomIdMap[stateId] = state.atom_id(); - - string serializedState; - if (!state.SerializeToString(&serializedState)) { - ALOGE("Unable to serialize state %lld", (long long)stateId); - return false; - } - stateProtoHashes[stateId] = Hash64(serializedState); - - const StateMap& stateMap = state.map(); - for (auto group : stateMap.group()) { - for (auto value : group.value()) { - allStateGroupMaps[stateId][value] = group.group_id(); - } - } - } - - return true; -} - -bool initMetrics(const ConfigKey& key, const StatsdConfig& config, const int64_t timeBaseTimeNs, - const int64_t currentTimeNs, const sp& pullerManager, - const unordered_map& atomMatchingTrackerMap, - const unordered_map& conditionTrackerMap, - const vector>& allAtomMatchingTrackers, - const unordered_map& stateAtomIdMap, - const unordered_map>& allStateGroupMaps, - vector>& allConditionTrackers, - const vector& initialConditionCache, - vector>& allMetricProducers, - unordered_map>& conditionToMetricMap, - unordered_map>& trackerToMetricMap, - unordered_map& metricMap, std::set& noReportMetricIds, - unordered_map>& activationAtomTrackerToMetricMap, - unordered_map>& deactivationAtomTrackerToMetricMap, - vector& metricsWithActivation) { - sp wizard = new ConditionWizard(allConditionTrackers); - sp matcherWizard = new EventMatcherWizard(allAtomMatchingTrackers); - const int allMetricsCount = config.count_metric_size() + config.duration_metric_size() + - config.event_metric_size() + config.gauge_metric_size() + - config.value_metric_size(); - allMetricProducers.reserve(allMetricsCount); - - // Construct map from metric id to metric activation index. The map will be used to determine - // the metric activation corresponding to a metric. - unordered_map metricToActivationMap; - for (int i = 0; i < config.metric_activation_size(); i++) { - const MetricActivation& metricActivation = config.metric_activation(i); - int64_t metricId = metricActivation.metric_id(); - if (metricToActivationMap.find(metricId) != metricToActivationMap.end()) { - ALOGE("Metric %lld has multiple MetricActivations", (long long)metricId); - return false; - } - metricToActivationMap.insert({metricId, i}); - } - - // Build MetricProducers for each metric defined in config. - // build CountMetricProducer - for (int i = 0; i < config.count_metric_size(); i++) { - int metricIndex = allMetricProducers.size(); - const CountMetric& metric = config.count_metric(i); - metricMap.insert({metric.id(), metricIndex}); - optional> producer = createCountMetricProducerAndUpdateMetadata( - key, config, timeBaseTimeNs, currentTimeNs, metric, metricIndex, - allAtomMatchingTrackers, atomMatchingTrackerMap, allConditionTrackers, - conditionTrackerMap, initialConditionCache, wizard, stateAtomIdMap, - allStateGroupMaps, metricToActivationMap, trackerToMetricMap, conditionToMetricMap, - activationAtomTrackerToMetricMap, deactivationAtomTrackerToMetricMap, - metricsWithActivation); - if (!producer) { - return false; - } - allMetricProducers.push_back(producer.value()); - } - - // build DurationMetricProducer - for (int i = 0; i < config.duration_metric_size(); i++) { - int metricIndex = allMetricProducers.size(); - const DurationMetric& metric = config.duration_metric(i); - metricMap.insert({metric.id(), metricIndex}); - - optional> producer = createDurationMetricProducerAndUpdateMetadata( - key, config, timeBaseTimeNs, currentTimeNs, metric, metricIndex, - allAtomMatchingTrackers, atomMatchingTrackerMap, allConditionTrackers, - conditionTrackerMap, initialConditionCache, wizard, stateAtomIdMap, - allStateGroupMaps, metricToActivationMap, trackerToMetricMap, conditionToMetricMap, - activationAtomTrackerToMetricMap, deactivationAtomTrackerToMetricMap, - metricsWithActivation); - if (!producer) { - return false; - } - allMetricProducers.push_back(producer.value()); - } - - // build EventMetricProducer - for (int i = 0; i < config.event_metric_size(); i++) { - int metricIndex = allMetricProducers.size(); - const EventMetric& metric = config.event_metric(i); - metricMap.insert({metric.id(), metricIndex}); - optional> producer = createEventMetricProducerAndUpdateMetadata( - key, config, timeBaseTimeNs, metric, metricIndex, allAtomMatchingTrackers, - atomMatchingTrackerMap, allConditionTrackers, conditionTrackerMap, - initialConditionCache, wizard, metricToActivationMap, trackerToMetricMap, - conditionToMetricMap, activationAtomTrackerToMetricMap, - deactivationAtomTrackerToMetricMap, metricsWithActivation); - if (!producer) { - return false; - } - allMetricProducers.push_back(producer.value()); - } - - // build ValueMetricProducer - for (int i = 0; i < config.value_metric_size(); i++) { - int metricIndex = allMetricProducers.size(); - const ValueMetric& metric = config.value_metric(i); - metricMap.insert({metric.id(), metricIndex}); - optional> producer = createValueMetricProducerAndUpdateMetadata( - key, config, timeBaseTimeNs, currentTimeNs, pullerManager, metric, metricIndex, - allAtomMatchingTrackers, atomMatchingTrackerMap, allConditionTrackers, - conditionTrackerMap, initialConditionCache, wizard, matcherWizard, stateAtomIdMap, - allStateGroupMaps, metricToActivationMap, trackerToMetricMap, conditionToMetricMap, - activationAtomTrackerToMetricMap, deactivationAtomTrackerToMetricMap, - metricsWithActivation); - if (!producer) { - return false; - } - allMetricProducers.push_back(producer.value()); - } - - // Gauge metrics. - for (int i = 0; i < config.gauge_metric_size(); i++) { - int metricIndex = allMetricProducers.size(); - const GaugeMetric& metric = config.gauge_metric(i); - metricMap.insert({metric.id(), metricIndex}); - optional> producer = createGaugeMetricProducerAndUpdateMetadata( - key, config, timeBaseTimeNs, currentTimeNs, pullerManager, metric, metricIndex, - allAtomMatchingTrackers, atomMatchingTrackerMap, allConditionTrackers, - conditionTrackerMap, initialConditionCache, wizard, matcherWizard, - metricToActivationMap, trackerToMetricMap, conditionToMetricMap, - activationAtomTrackerToMetricMap, deactivationAtomTrackerToMetricMap, - metricsWithActivation); - if (!producer) { - return false; - } - allMetricProducers.push_back(producer.value()); - } - for (int i = 0; i < config.no_report_metric_size(); ++i) { - const auto no_report_metric = config.no_report_metric(i); - if (metricMap.find(no_report_metric) == metricMap.end()) { - ALOGW("no_report_metric %" PRId64 " not exist", no_report_metric); - return false; - } - noReportMetricIds.insert(no_report_metric); - } - - const set whitelistedAtomIds(config.whitelisted_atom_ids().begin(), - config.whitelisted_atom_ids().end()); - for (const auto& it : allMetricProducers) { - // Register metrics to StateTrackers - for (int atomId : it->getSlicedStateAtoms()) { - // Register listener for non-whitelisted atoms only. Using whitelisted atom as a sliced - // state atom is not allowed. - if (whitelistedAtomIds.find(atomId) == whitelistedAtomIds.end()) { - StateManager::getInstance().registerListener(atomId, it); - } else { - return false; - } - } - } - return true; -} - -bool initAlerts(const StatsdConfig& config, const int64_t currentTimeNs, - const unordered_map& metricProducerMap, - unordered_map& alertTrackerMap, - const sp& anomalyAlarmMonitor, - vector>& allMetricProducers, - vector>& allAnomalyTrackers) { - for (int i = 0; i < config.alert_size(); i++) { - const Alert& alert = config.alert(i); - alertTrackerMap.insert(std::make_pair(alert.id(), allAnomalyTrackers.size())); - optional> anomalyTracker = - createAnomalyTracker(alert, anomalyAlarmMonitor, UpdateStatus::UPDATE_NEW, - currentTimeNs, metricProducerMap, allMetricProducers); - if (!anomalyTracker) { - return false; - } - allAnomalyTrackers.push_back(anomalyTracker.value()); - } - if (!initSubscribersForSubscriptionType(config, Subscription::ALERT, alertTrackerMap, - allAnomalyTrackers)) { - return false; - } - return true; -} - -bool initAlarms(const StatsdConfig& config, const ConfigKey& key, - const sp& periodicAlarmMonitor, const int64_t timeBaseNs, - const int64_t currentTimeNs, vector>& allAlarmTrackers) { - unordered_map alarmTrackerMap; - int64_t startMillis = timeBaseNs / 1000 / 1000; - int64_t currentTimeMillis = currentTimeNs / 1000 / 1000; - for (int i = 0; i < config.alarm_size(); i++) { - const Alarm& alarm = config.alarm(i); - if (alarm.offset_millis() <= 0) { - ALOGW("Alarm offset_millis should be larger than 0."); - return false; - } - if (alarm.period_millis() <= 0) { - ALOGW("Alarm period_millis should be larger than 0."); - return false; - } - alarmTrackerMap.insert(std::make_pair(alarm.id(), allAlarmTrackers.size())); - allAlarmTrackers.push_back( - new AlarmTracker(startMillis, currentTimeMillis, alarm, key, periodicAlarmMonitor)); - } - if (!initSubscribersForSubscriptionType(config, Subscription::ALARM, alarmTrackerMap, - allAlarmTrackers)) { - return false; - } - return true; -} - -bool initStatsdConfig(const ConfigKey& key, const StatsdConfig& config, const sp& uidMap, - const sp& pullerManager, - const sp& anomalyAlarmMonitor, - const sp& periodicAlarmMonitor, const int64_t timeBaseNs, - const int64_t currentTimeNs, set& allTagIds, - vector>& allAtomMatchingTrackers, - unordered_map& atomMatchingTrackerMap, - vector>& allConditionTrackers, - unordered_map& conditionTrackerMap, - vector>& allMetricProducers, - unordered_map& metricProducerMap, - vector>& allAnomalyTrackers, - vector>& allPeriodicAlarmTrackers, - unordered_map>& conditionToMetricMap, - unordered_map>& trackerToMetricMap, - unordered_map>& trackerToConditionMap, - unordered_map>& activationAtomTrackerToMetricMap, - unordered_map>& deactivationAtomTrackerToMetricMap, - unordered_map& alertTrackerMap, - vector& metricsWithActivation, map& stateProtoHashes, - set& noReportMetricIds) { - vector initialConditionCache; - unordered_map stateAtomIdMap; - unordered_map> allStateGroupMaps; - - if (!initAtomMatchingTrackers(config, uidMap, atomMatchingTrackerMap, allAtomMatchingTrackers, - allTagIds)) { - ALOGE("initAtomMatchingTrackers failed"); - return false; - } - VLOG("initAtomMatchingTrackers succeed..."); - - if (!initConditions(key, config, atomMatchingTrackerMap, conditionTrackerMap, - allConditionTrackers, trackerToConditionMap, initialConditionCache)) { - ALOGE("initConditionTrackers failed"); - return false; - } - - if (!initStates(config, stateAtomIdMap, allStateGroupMaps, stateProtoHashes)) { - ALOGE("initStates failed"); - return false; - } - if (!initMetrics(key, config, timeBaseNs, currentTimeNs, pullerManager, atomMatchingTrackerMap, - conditionTrackerMap, allAtomMatchingTrackers, stateAtomIdMap, - allStateGroupMaps, allConditionTrackers, initialConditionCache, - allMetricProducers, conditionToMetricMap, trackerToMetricMap, - metricProducerMap, noReportMetricIds, activationAtomTrackerToMetricMap, - deactivationAtomTrackerToMetricMap, metricsWithActivation)) { - ALOGE("initMetricProducers failed"); - return false; - } - if (!initAlerts(config, currentTimeNs, metricProducerMap, alertTrackerMap, anomalyAlarmMonitor, - allMetricProducers, allAnomalyTrackers)) { - ALOGE("initAlerts failed"); - return false; - } - if (!initAlarms(config, key, periodicAlarmMonitor, timeBaseNs, currentTimeNs, - allPeriodicAlarmTrackers)) { - ALOGE("initAlarms failed"); - return false; - } - - return true; -} - -} // namespace statsd -} // namespace os -} // namespace android diff --git a/bin/src/metrics/parsing_utils/metrics_manager_util.h b/bin/src/metrics/parsing_utils/metrics_manager_util.h deleted file mode 100644 index 5d66a0f1..00000000 --- a/bin/src/metrics/parsing_utils/metrics_manager_util.h +++ /dev/null @@ -1,343 +0,0 @@ -/* - * 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. - */ - -#pragma once - -#include -#include -#include - -#include "anomaly/AlarmTracker.h" -#include "condition/ConditionTracker.h" -#include "external/StatsPullerManager.h" -#include "matchers/AtomMatchingTracker.h" -#include "metrics/MetricProducer.h" - -namespace android { -namespace os { -namespace statsd { - -// Helper functions for creating, validating, and updating config components from StatsdConfig. -// Should only be called from metrics_manager_util and config_update_utils. - -// Create a AtomMatchingTracker. -// input: -// [logMatcher]: the input AtomMatcher from the StatsdConfig -// [index]: the index of the matcher -// output: -// new AtomMatchingTracker, or null if the tracker is unable to be created -sp createAtomMatchingTracker(const AtomMatcher& logMatcher, const int index, - const sp& uidMap); - -// Create a ConditionTracker. -// input: -// [predicate]: the input Predicate from the StatsdConfig -// [index]: the index of the condition tracker -// [atomMatchingTrackerMap]: map of atom matcher id to its index in allAtomMatchingTrackers -// output: -// new ConditionTracker, or null if the tracker is unable to be created -sp createConditionTracker( - const ConfigKey& key, const Predicate& predicate, const int index, - const unordered_map& atomMatchingTrackerMap); - -// Get the hash of a metric, combining the activation if the metric has one. -bool getMetricProtoHash(const StatsdConfig& config, const google::protobuf::MessageLite& metric, - const int64_t id, - const std::unordered_map& metricToActivationMap, - uint64_t& metricHash); - -// 1. Validates matcher existence -// 2. Enforces matchers with dimensions and those used for trigger_event are about one atom -// 3. Gets matcher index and updates tracker to metric map -bool handleMetricWithAtomMatchingTrackers( - const int64_t matcherId, const int metricIndex, const bool enforceOneAtom, - const std::vector>& allAtomMatchingTrackers, - const std::unordered_map& atomMatchingTrackerMap, - std::unordered_map>& trackerToMetricMap, int& logTrackerIndex); - -// 1. Validates condition existence, including those in links -// 2. Gets condition index and updates condition to metric map -bool handleMetricWithConditions( - const int64_t condition, const int metricIndex, - const std::unordered_map& conditionTrackerMap, - const ::google::protobuf::RepeatedPtrField<::android::os::statsd::MetricConditionLink>& - links, - const std::vector>& allConditionTrackers, int& conditionIndex, - std::unordered_map>& conditionToMetricMap); - -// Validates a metricActivation and populates state. -// Fills the new event activation/deactivation maps, preserving the existing activations. -// Returns false if there are errors. -bool handleMetricActivationOnConfigUpdate( - const StatsdConfig& config, const int64_t metricId, const int metricIndex, - const std::unordered_map& metricToActivationMap, - const std::unordered_map& oldAtomMatchingTrackerMap, - const std::unordered_map& newAtomMatchingTrackerMap, - const std::unordered_map>& oldEventActivationMap, - std::unordered_map>& activationAtomTrackerToMetricMap, - std::unordered_map>& deactivationAtomTrackerToMetricMap, - std::vector& metricsWithActivation, - std::unordered_map>& newEventActivationMap, - std::unordered_map>>& newEventDeactivationMap); - -// Creates a CountMetricProducer and updates the vectors/maps used by MetricsManager with -// the appropriate indices. Returns an sp to the producer, or nullopt if there was an error. -optional> createCountMetricProducerAndUpdateMetadata( - const ConfigKey& key, const StatsdConfig& config, const int64_t timeBaseNs, - const int64_t currentTimeNs, const CountMetric& metric, const int metricIndex, - const std::vector>& allAtomMatchingTrackers, - const std::unordered_map& atomMatchingTrackerMap, - std::vector>& allConditionTrackers, - const std::unordered_map& conditionTrackerMap, - const std::vector& initialConditionCache, const sp& wizard, - const std::unordered_map& stateAtomIdMap, - const std::unordered_map>& allStateGroupMaps, - const std::unordered_map& metricToActivationMap, - std::unordered_map>& trackerToMetricMap, - std::unordered_map>& conditionToMetricMap, - std::unordered_map>& activationAtomTrackerToMetricMap, - std::unordered_map>& deactivationAtomTrackerToMetricMap, - std::vector& metricsWithActivation); - -// Creates a DurationMetricProducer and updates the vectors/maps used by MetricsManager with -// the appropriate indices. Returns an sp to the producer, or nullopt if there was an error. -optional> createDurationMetricProducerAndUpdateMetadata( - const ConfigKey& key, const StatsdConfig& config, const int64_t timeBaseNs, - const int64_t currentTimeNs, const DurationMetric& metric, const int metricIndex, - const std::vector>& allAtomMatchingTrackers, - const std::unordered_map& atomMatchingTrackerMap, - std::vector>& allConditionTrackers, - const std::unordered_map& conditionTrackerMap, - const std::vector& initialConditionCache, const sp& wizard, - const std::unordered_map& stateAtomIdMap, - const std::unordered_map>& allStateGroupMaps, - const std::unordered_map& metricToActivationMap, - std::unordered_map>& trackerToMetricMap, - std::unordered_map>& conditionToMetricMap, - std::unordered_map>& activationAtomTrackerToMetricMap, - std::unordered_map>& deactivationAtomTrackerToMetricMap, - std::vector& metricsWithActivation); - -// Creates an EventMetricProducer and updates the vectors/maps used by MetricsManager with -// the appropriate indices. Returns an sp to the producer, or nullopt if there was an error. -optional> createEventMetricProducerAndUpdateMetadata( - const ConfigKey& key, const StatsdConfig& config, const int64_t timeBaseNs, - const EventMetric& metric, const int metricIndex, - const std::vector>& allAtomMatchingTrackers, - const std::unordered_map& atomMatchingTrackerMap, - std::vector>& allConditionTrackers, - const std::unordered_map& conditionTrackerMap, - const std::vector& initialConditionCache, const sp& wizard, - const std::unordered_map& metricToActivationMap, - std::unordered_map>& trackerToMetricMap, - std::unordered_map>& conditionToMetricMap, - std::unordered_map>& activationAtomTrackerToMetricMap, - std::unordered_map>& deactivationAtomTrackerToMetricMap, - std::vector& metricsWithActivation); - -// Creates a CountMetricProducer and updates the vectors/maps used by MetricsManager with -// the appropriate indices. Returns an sp to the producer, or nullopt if there was an error. -optional> createValueMetricProducerAndUpdateMetadata( - const ConfigKey& key, const StatsdConfig& config, const int64_t timeBaseNs, - const int64_t currentTimeNs, const sp& pullerManager, - const ValueMetric& metric, const int metricIndex, - const std::vector>& allAtomMatchingTrackers, - const std::unordered_map& atomMatchingTrackerMap, - std::vector>& allConditionTrackers, - const std::unordered_map& conditionTrackerMap, - const std::vector& initialConditionCache, const sp& wizard, - const sp& matcherWizard, - const std::unordered_map& stateAtomIdMap, - const std::unordered_map>& allStateGroupMaps, - const std::unordered_map& metricToActivationMap, - std::unordered_map>& trackerToMetricMap, - std::unordered_map>& conditionToMetricMap, - std::unordered_map>& activationAtomTrackerToMetricMap, - std::unordered_map>& deactivationAtomTrackerToMetricMap, - std::vector& metricsWithActivation); - -// Creates a GaugeMetricProducer and updates the vectors/maps used by MetricsManager with -// the appropriate indices. Returns an sp to the producer, or nullopt if there was an error. -optional> createGaugeMetricProducerAndUpdateMetadata( - const ConfigKey& key, const StatsdConfig& config, const int64_t timeBaseNs, - const int64_t currentTimeNs, const sp& pullerManager, - const GaugeMetric& metric, const int metricIndex, - const std::vector>& allAtomMatchingTrackers, - const std::unordered_map& atomMatchingTrackerMap, - std::vector>& allConditionTrackers, - const std::unordered_map& conditionTrackerMap, - const std::vector& initialConditionCache, const sp& wizard, - const sp& matcherWizard, - const std::unordered_map& metricToActivationMap, - std::unordered_map>& trackerToMetricMap, - std::unordered_map>& conditionToMetricMap, - std::unordered_map>& activationAtomTrackerToMetricMap, - std::unordered_map>& deactivationAtomTrackerToMetricMap, - std::vector& metricsWithActivation); - -// Creates an AnomalyTracker and adds it to the appropriate metric. -// Returns an sp to the AnomalyTracker, or nullopt if there was an error. -optional> createAnomalyTracker( - const Alert& alert, const sp& anomalyAlarmMonitor, - const UpdateStatus& updateStatus, const int64_t currentTimeNs, - const std::unordered_map& metricProducerMap, - std::vector>& allMetricProducers); - -// Templated function for adding subscriptions to alarms or alerts. Returns true if successful. -template -bool initSubscribersForSubscriptionType(const StatsdConfig& config, - const Subscription_RuleType ruleType, - const std::unordered_map& ruleMap, - std::vector& allRules) { - for (int i = 0; i < config.subscription_size(); ++i) { - const Subscription& subscription = config.subscription(i); - if (subscription.rule_type() != ruleType) { - continue; - } - if (subscription.subscriber_information_case() == - Subscription::SubscriberInformationCase::SUBSCRIBER_INFORMATION_NOT_SET) { - ALOGW("subscription \"%lld\" has no subscriber info.\"", (long long)subscription.id()); - return false; - } - const auto& itr = ruleMap.find(subscription.rule_id()); - if (itr == ruleMap.end()) { - ALOGW("subscription \"%lld\" has unknown rule id: \"%lld\"", - (long long)subscription.id(), (long long)subscription.rule_id()); - return false; - } - const int ruleIndex = itr->second; - allRules[ruleIndex]->addSubscription(subscription); - } - return true; -} - -// Helper functions for MetricsManager to initialize from StatsdConfig. -// *Note*: only initStatsdConfig() should be called from outside. -// All other functions are intermediate -// steps, created to make unit tests easier. And most of the parameters in these -// functions are temporary objects in the initialization phase. - -// Initialize the AtomMatchingTrackers. -// input: -// [key]: the config key that this config belongs to -// [config]: the input StatsdConfig -// output: -// [atomMatchingTrackerMap]: this map should contain matcher name to index mapping -// [allAtomMatchingTrackers]: should store the sp to all the AtomMatchingTracker -// [allTagIds]: contains the set of all interesting tag ids to this config. -bool initAtomMatchingTrackers(const StatsdConfig& config, const sp& uidMap, - std::unordered_map& atomMatchingTrackerMap, - std::vector>& allAtomMatchingTrackers, - std::set& allTagIds); - -// Initialize ConditionTrackers -// input: -// [key]: the config key that this config belongs to -// [config]: the input config -// [atomMatchingTrackerMap]: AtomMatchingTracker name to index mapping from previous step. -// output: -// [conditionTrackerMap]: this map should contain condition name to index mapping -// [allConditionTrackers]: stores the sp to all the ConditionTrackers -// [trackerToConditionMap]: contain the mapping from index of -// log tracker to condition trackers that use the log tracker -// [initialConditionCache]: stores the initial conditions for each ConditionTracker -bool initConditions(const ConfigKey& key, const StatsdConfig& config, - const std::unordered_map& atomMatchingTrackerMap, - std::unordered_map& conditionTrackerMap, - std::vector>& allConditionTrackers, - std::unordered_map>& trackerToConditionMap, - std::vector& initialConditionCache); - -// Initialize State maps using State protos in the config. These maps will -// eventually be passed to MetricProducers to initialize their state info. -// input: -// [config]: the input config -// output: -// [stateAtomIdMap]: this map should contain the mapping from state ids to atom ids -// [allStateGroupMaps]: this map should contain the mapping from states ids and state -// values to state group ids for all states -// [stateProtoHashes]: contains a map of state id to the hash of the State proto from the config -bool initStates(const StatsdConfig& config, unordered_map& stateAtomIdMap, - unordered_map>& allStateGroupMaps, - std::map& stateProtoHashes); - -// Initialize MetricProducers. -// input: -// [key]: the config key that this config belongs to -// [config]: the input config -// [timeBaseSec]: start time base for all metrics -// [atomMatchingTrackerMap]: AtomMatchingTracker name to index mapping from previous step. -// [conditionTrackerMap]: condition name to index mapping -// [stateAtomIdMap]: contains the mapping from state ids to atom ids -// [allStateGroupMaps]: contains the mapping from atom ids and state values to -// state group ids for all states -// output: -// [allMetricProducers]: contains the list of sp to the MetricProducers created. -// [conditionToMetricMap]: contains the mapping from condition tracker index to -// the list of MetricProducer index -// [trackerToMetricMap]: contains the mapping from log tracker to MetricProducer index. -bool initMetrics( - const ConfigKey& key, const StatsdConfig& config, const int64_t timeBaseTimeNs, - const int64_t currentTimeNs, const sp& pullerManager, - const std::unordered_map& atomMatchingTrackerMap, - const std::unordered_map& conditionTrackerMap, - const vector>& allAtomMatchingTrackers, - const unordered_map& stateAtomIdMap, - const unordered_map>& allStateGroupMaps, - vector>& allConditionTrackers, - const std::vector& initialConditionCache, - std::vector>& allMetricProducers, - std::unordered_map>& conditionToMetricMap, - std::unordered_map>& trackerToMetricMap, - std::set& noReportMetricIds, - std::unordered_map>& activationAtomTrackerToMetricMap, - std::unordered_map>& deactivationAtomTrackerToMetricMap, - std::vector& metricsWithActivation); - -// Initialize alarms -// Is called both on initialize new configs and config updates since alarms do not have any state. -bool initAlarms(const StatsdConfig& config, const ConfigKey& key, - const sp& periodicAlarmMonitor, const int64_t timeBaseNs, - const int64_t currentTimeNs, std::vector>& allAlarmTrackers); - -// Initialize MetricsManager from StatsdConfig. -// Parameters are the members of MetricsManager. See MetricsManager for declaration. -bool initStatsdConfig(const ConfigKey& key, const StatsdConfig& config, const sp& uidMap, - const sp& pullerManager, - const sp& anomalyAlarmMonitor, - const sp& periodicAlarmMonitor, const int64_t timeBaseNs, - const int64_t currentTimeNs, std::set& allTagIds, - std::vector>& allAtomMatchingTrackers, - std::unordered_map& atomMatchingTrackerMap, - std::vector>& allConditionTrackers, - std::unordered_map& conditionTrackerMap, - std::vector>& allMetricProducers, - std::unordered_map& metricProducerMap, - vector>& allAnomalyTrackers, - vector>& allPeriodicAlarmTrackers, - std::unordered_map>& conditionToMetricMap, - std::unordered_map>& trackerToMetricMap, - std::unordered_map>& trackerToConditionMap, - std::unordered_map>& activationAtomTrackerToMetricMap, - std::unordered_map>& deactivationAtomTrackerToMetricMap, - std::unordered_map& alertTrackerMap, - std::vector& metricsWithActivation, - std::map& stateProtoHashes, - std::set& noReportMetricIds); - -} // namespace statsd -} // namespace os -} // namespace android diff --git a/bin/src/packages/PackageInfoListener.h b/bin/src/packages/PackageInfoListener.h deleted file mode 100644 index 1bc84c54..00000000 --- a/bin/src/packages/PackageInfoListener.h +++ /dev/null @@ -1,47 +0,0 @@ -/* - * 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. - */ - -#ifndef STATSD_PACKAGE_INFO_LISTENER_H -#define STATSD_PACKAGE_INFO_LISTENER_H - -#include - -#include - -namespace android { -namespace os { -namespace statsd { - -class PackageInfoListener : public virtual android::RefBase { -public: - // Uid map will notify this listener that the app with apk name and uid has been upgraded to - // the specified version. - virtual void notifyAppUpgrade(const int64_t& eventTimeNs, const std::string& apk, - const int uid, const int64_t version) = 0; - - // Notify interested listeners that the given apk and uid combination no longer exits. - virtual void notifyAppRemoved(const int64_t& eventTimeNs, const std::string& apk, - const int uid) = 0; - - // Notify the listener that the UidMap snapshot is available. - virtual void onUidMapReceived(const int64_t& eventTimeNs) = 0; -}; - -} // namespace statsd -} // namespace os -} // namespace android - -#endif // STATSD_PACKAGE_INFO_LISTENER_H diff --git a/bin/src/packages/UidMap.cpp b/bin/src/packages/UidMap.cpp deleted file mode 100644 index 32be1c6b..00000000 --- a/bin/src/packages/UidMap.cpp +++ /dev/null @@ -1,564 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, versionCode 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. - */ -#define DEBUG false // STOPSHIP if true -#include "Log.h" - -#include "hash.h" -#include "stats_log_util.h" -#include "guardrail/StatsdStats.h" -#include "packages/UidMap.h" - -#include - -using namespace android; - -using android::base::StringPrintf; -using android::util::FIELD_COUNT_REPEATED; -using android::util::FIELD_TYPE_BOOL; -using android::util::FIELD_TYPE_FLOAT; -using android::util::FIELD_TYPE_INT32; -using android::util::FIELD_TYPE_INT64; -using android::util::FIELD_TYPE_UINT64; -using android::util::FIELD_TYPE_MESSAGE; -using android::util::FIELD_TYPE_STRING; -using android::util::ProtoOutputStream; - -namespace android { -namespace os { -namespace statsd { - -const int FIELD_ID_SNAPSHOT_PACKAGE_NAME = 1; -const int FIELD_ID_SNAPSHOT_PACKAGE_VERSION = 2; -const int FIELD_ID_SNAPSHOT_PACKAGE_UID = 3; -const int FIELD_ID_SNAPSHOT_PACKAGE_DELETED = 4; -const int FIELD_ID_SNAPSHOT_PACKAGE_NAME_HASH = 5; -const int FIELD_ID_SNAPSHOT_PACKAGE_VERSION_STRING = 6; -const int FIELD_ID_SNAPSHOT_PACKAGE_VERSION_STRING_HASH = 7; -const int FIELD_ID_SNAPSHOT_PACKAGE_INSTALLER = 8; -const int FIELD_ID_SNAPSHOT_PACKAGE_INSTALLER_HASH = 9; -const int FIELD_ID_SNAPSHOT_TIMESTAMP = 1; -const int FIELD_ID_SNAPSHOT_PACKAGE_INFO = 2; -const int FIELD_ID_SNAPSHOTS = 1; -const int FIELD_ID_CHANGES = 2; -const int FIELD_ID_CHANGE_DELETION = 1; -const int FIELD_ID_CHANGE_TIMESTAMP = 2; -const int FIELD_ID_CHANGE_PACKAGE = 3; -const int FIELD_ID_CHANGE_UID = 4; -const int FIELD_ID_CHANGE_NEW_VERSION = 5; -const int FIELD_ID_CHANGE_PREV_VERSION = 6; -const int FIELD_ID_CHANGE_PACKAGE_HASH = 7; -const int FIELD_ID_CHANGE_NEW_VERSION_STRING = 8; -const int FIELD_ID_CHANGE_PREV_VERSION_STRING = 9; -const int FIELD_ID_CHANGE_NEW_VERSION_STRING_HASH = 10; -const int FIELD_ID_CHANGE_PREV_VERSION_STRING_HASH = 11; - -UidMap::UidMap() : mBytesUsed(0) {} - -UidMap::~UidMap() {} - -sp UidMap::getInstance() { - static sp sInstance = new UidMap(); - return sInstance; -} - -bool UidMap::hasApp(int uid, const string& packageName) const { - lock_guard lock(mMutex); - - auto it = mMap.find(std::make_pair(uid, packageName)); - return it != mMap.end() && !it->second.deleted; -} - -string UidMap::normalizeAppName(const string& appName) const { - string normalizedName = appName; - std::transform(normalizedName.begin(), normalizedName.end(), normalizedName.begin(), ::tolower); - return normalizedName; -} - -std::set UidMap::getAppNamesFromUid(const int32_t& uid, bool returnNormalized) const { - lock_guard lock(mMutex); - return getAppNamesFromUidLocked(uid,returnNormalized); -} - -std::set UidMap::getAppNamesFromUidLocked(const int32_t& uid, bool returnNormalized) const { - std::set names; - for (const auto& kv : mMap) { - if (kv.first.first == uid && !kv.second.deleted) { - names.insert(returnNormalized ? normalizeAppName(kv.first.second) : kv.first.second); - } - } - return names; -} - -int64_t UidMap::getAppVersion(int uid, const string& packageName) const { - lock_guard lock(mMutex); - - auto it = mMap.find(std::make_pair(uid, packageName)); - if (it == mMap.end() || it->second.deleted) { - return 0; - } - return it->second.versionCode; -} - -void UidMap::updateMap(const int64_t& timestamp, const vector& uid, - const vector& versionCode, const vector& versionString, - const vector& packageName, const vector& installer) { - wp broadcast = NULL; - { - lock_guard lock(mMutex); // Exclusively lock for updates. - - std::unordered_map, AppData, PairHash> deletedApps; - - // Copy all the deleted apps. - for (const auto& kv : mMap) { - if (kv.second.deleted) { - deletedApps[kv.first] = kv.second; - } - } - - mMap.clear(); - for (size_t j = 0; j < uid.size(); j++) { - string package = string(String8(packageName[j]).string()); - mMap[std::make_pair(uid[j], package)] = - AppData(versionCode[j], string(String8(versionString[j]).string()), - string(String8(installer[j]).string())); - } - - for (const auto& kv : deletedApps) { - auto mMapIt = mMap.find(kv.first); - if (mMapIt != mMap.end()) { - // Insert this deleted app back into the current map. - mMap[kv.first] = kv.second; - } - } - - ensureBytesUsedBelowLimit(); - StatsdStats::getInstance().setCurrentUidMapMemory(mBytesUsed); - broadcast = mSubscriber; - } - // To avoid invoking callback while holding the internal lock. we get a copy of the listener - // and invoke the callback. It's still possible that after we copy the listener, it removes - // itself before we call it. It's then the listener's job to handle it (expect the callback to - // be called after listener is removed, and the listener should properly ignore it). - auto strongPtr = broadcast.promote(); - if (strongPtr != NULL) { - strongPtr->onUidMapReceived(timestamp); - } -} - -void UidMap::updateApp(const int64_t& timestamp, const String16& app_16, const int32_t& uid, - const int64_t& versionCode, const String16& versionString, - const String16& installer) { - wp broadcast = NULL; - string appName = string(String8(app_16).string()); - { - lock_guard lock(mMutex); - int32_t prevVersion = 0; - string prevVersionString = ""; - string newVersionString = string(String8(versionString).string()); - bool found = false; - auto it = mMap.find(std::make_pair(uid, appName)); - if (it != mMap.end()) { - found = true; - prevVersion = it->second.versionCode; - prevVersionString = it->second.versionString; - it->second.versionCode = versionCode; - it->second.versionString = newVersionString; - it->second.installer = string(String8(installer).string()); - it->second.deleted = false; - } - if (!found) { - // Otherwise, we need to add an app at this uid. - mMap[std::make_pair(uid, appName)] = - AppData(versionCode, newVersionString, string(String8(installer).string())); - } else { - // Only notify the listeners if this is an app upgrade. If this app is being installed - // for the first time, then we don't notify the listeners. - // It's also OK to split again if we're forming a partial bucket after re-installing an - // app after deletion. - broadcast = mSubscriber; - } - mChanges.emplace_back(false, timestamp, appName, uid, versionCode, newVersionString, - prevVersion, prevVersionString); - mBytesUsed += kBytesChangeRecord; - ensureBytesUsedBelowLimit(); - StatsdStats::getInstance().setCurrentUidMapMemory(mBytesUsed); - StatsdStats::getInstance().setUidMapChanges(mChanges.size()); - } - - auto strongPtr = broadcast.promote(); - if (strongPtr != NULL) { - strongPtr->notifyAppUpgrade(timestamp, appName, uid, versionCode); - } -} - -void UidMap::ensureBytesUsedBelowLimit() { - size_t limit; - if (maxBytesOverride <= 0) { - limit = StatsdStats::kMaxBytesUsedUidMap; - } else { - limit = maxBytesOverride; - } - while (mBytesUsed > limit) { - ALOGI("Bytes used %zu is above limit %zu, need to delete something", mBytesUsed, limit); - if (mChanges.size() > 0) { - mBytesUsed -= kBytesChangeRecord; - mChanges.pop_front(); - StatsdStats::getInstance().noteUidMapDropped(1); - } - } -} - -void UidMap::removeApp(const int64_t& timestamp, const String16& app_16, const int32_t& uid) { - wp broadcast = NULL; - string app = string(String8(app_16).string()); - { - lock_guard lock(mMutex); - - int64_t prevVersion = 0; - string prevVersionString = ""; - auto key = std::make_pair(uid, app); - auto it = mMap.find(key); - if (it != mMap.end() && !it->second.deleted) { - prevVersion = it->second.versionCode; - prevVersionString = it->second.versionString; - it->second.deleted = true; - mDeletedApps.push_back(key); - } - if (mDeletedApps.size() > StatsdStats::kMaxDeletedAppsInUidMap) { - // Delete the oldest one. - auto oldest = mDeletedApps.front(); - mDeletedApps.pop_front(); - mMap.erase(oldest); - StatsdStats::getInstance().noteUidMapAppDeletionDropped(); - } - mChanges.emplace_back(true, timestamp, app, uid, 0, "", prevVersion, prevVersionString); - mBytesUsed += kBytesChangeRecord; - ensureBytesUsedBelowLimit(); - StatsdStats::getInstance().setCurrentUidMapMemory(mBytesUsed); - StatsdStats::getInstance().setUidMapChanges(mChanges.size()); - broadcast = mSubscriber; - } - - auto strongPtr = broadcast.promote(); - if (strongPtr != NULL) { - strongPtr->notifyAppRemoved(timestamp, app, uid); - } -} - -void UidMap::setListener(wp listener) { - lock_guard lock(mMutex); // Lock for updates - mSubscriber = listener; -} - -void UidMap::assignIsolatedUid(int isolatedUid, int parentUid) { - lock_guard lock(mIsolatedMutex); - - mIsolatedUidMap[isolatedUid] = parentUid; -} - -void UidMap::removeIsolatedUid(int isolatedUid) { - lock_guard lock(mIsolatedMutex); - - auto it = mIsolatedUidMap.find(isolatedUid); - if (it != mIsolatedUidMap.end()) { - mIsolatedUidMap.erase(it); - } -} - -int UidMap::getHostUidOrSelf(int uid) const { - lock_guard lock(mIsolatedMutex); - - auto it = mIsolatedUidMap.find(uid); - if (it != mIsolatedUidMap.end()) { - return it->second; - } - return uid; -} - -void UidMap::clearOutput() { - mChanges.clear(); - // Also update the guardrail trackers. - StatsdStats::getInstance().setUidMapChanges(0); - mBytesUsed = 0; - StatsdStats::getInstance().setCurrentUidMapMemory(mBytesUsed); -} - -int64_t UidMap::getMinimumTimestampNs() { - int64_t m = 0; - for (const auto& kv : mLastUpdatePerConfigKey) { - if (m == 0) { - m = kv.second; - } else if (kv.second < m) { - m = kv.second; - } - } - return m; -} - -size_t UidMap::getBytesUsed() const { - return mBytesUsed; -} - -void UidMap::writeUidMapSnapshot(int64_t timestamp, bool includeVersionStrings, - bool includeInstaller, const std::set& interestingUids, - std::set* str_set, ProtoOutputStream* proto) { - lock_guard lock(mMutex); - - writeUidMapSnapshotLocked(timestamp, includeVersionStrings, includeInstaller, interestingUids, - str_set, proto); -} - -void UidMap::writeUidMapSnapshotLocked(int64_t timestamp, bool includeVersionStrings, - bool includeInstaller, - const std::set& interestingUids, - std::set* str_set, ProtoOutputStream* proto) { - proto->write(FIELD_TYPE_INT64 | FIELD_ID_SNAPSHOT_TIMESTAMP, (long long)timestamp); - for (const auto& kv : mMap) { - if (!interestingUids.empty() && - interestingUids.find(kv.first.first) == interestingUids.end()) { - continue; - } - uint64_t token = proto->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | - FIELD_ID_SNAPSHOT_PACKAGE_INFO); - if (str_set != nullptr) { - str_set->insert(kv.first.second); - proto->write(FIELD_TYPE_UINT64 | FIELD_ID_SNAPSHOT_PACKAGE_NAME_HASH, - (long long)Hash64(kv.first.second)); - if (includeVersionStrings) { - str_set->insert(kv.second.versionString); - proto->write(FIELD_TYPE_UINT64 | FIELD_ID_SNAPSHOT_PACKAGE_VERSION_STRING_HASH, - (long long)Hash64(kv.second.versionString)); - } - if (includeInstaller) { - str_set->insert(kv.second.installer); - proto->write(FIELD_TYPE_UINT64 | FIELD_ID_SNAPSHOT_PACKAGE_INSTALLER_HASH, - (long long)Hash64(kv.second.installer)); - } - } else { - proto->write(FIELD_TYPE_STRING | FIELD_ID_SNAPSHOT_PACKAGE_NAME, kv.first.second); - if (includeVersionStrings) { - proto->write(FIELD_TYPE_STRING | FIELD_ID_SNAPSHOT_PACKAGE_VERSION_STRING, - kv.second.versionString); - } - if (includeInstaller) { - proto->write(FIELD_TYPE_STRING | FIELD_ID_SNAPSHOT_PACKAGE_INSTALLER, - kv.second.installer); - } - } - - proto->write(FIELD_TYPE_INT64 | FIELD_ID_SNAPSHOT_PACKAGE_VERSION, - (long long)kv.second.versionCode); - proto->write(FIELD_TYPE_INT32 | FIELD_ID_SNAPSHOT_PACKAGE_UID, kv.first.first); - proto->write(FIELD_TYPE_BOOL | FIELD_ID_SNAPSHOT_PACKAGE_DELETED, kv.second.deleted); - proto->end(token); - } -} - -void UidMap::appendUidMap(const int64_t& timestamp, const ConfigKey& key, std::set* str_set, - bool includeVersionStrings, bool includeInstaller, - ProtoOutputStream* proto) { - lock_guard lock(mMutex); // Lock for updates - - for (const ChangeRecord& record : mChanges) { - if (record.timestampNs > mLastUpdatePerConfigKey[key]) { - uint64_t changesToken = - proto->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_CHANGES); - proto->write(FIELD_TYPE_BOOL | FIELD_ID_CHANGE_DELETION, (bool)record.deletion); - proto->write(FIELD_TYPE_INT64 | FIELD_ID_CHANGE_TIMESTAMP, - (long long)record.timestampNs); - if (str_set != nullptr) { - str_set->insert(record.package); - proto->write(FIELD_TYPE_UINT64 | FIELD_ID_CHANGE_PACKAGE_HASH, - (long long)Hash64(record.package)); - if (includeVersionStrings) { - str_set->insert(record.versionString); - proto->write(FIELD_TYPE_UINT64 | FIELD_ID_CHANGE_NEW_VERSION_STRING_HASH, - (long long)Hash64(record.versionString)); - str_set->insert(record.prevVersionString); - proto->write(FIELD_TYPE_UINT64 | FIELD_ID_CHANGE_PREV_VERSION_STRING_HASH, - (long long)Hash64(record.prevVersionString)); - } - } else { - proto->write(FIELD_TYPE_STRING | FIELD_ID_CHANGE_PACKAGE, record.package); - if (includeVersionStrings) { - proto->write(FIELD_TYPE_STRING | FIELD_ID_CHANGE_NEW_VERSION_STRING, - record.versionString); - proto->write(FIELD_TYPE_STRING | FIELD_ID_CHANGE_PREV_VERSION_STRING, - record.prevVersionString); - } - } - - proto->write(FIELD_TYPE_INT32 | FIELD_ID_CHANGE_UID, (int)record.uid); - proto->write(FIELD_TYPE_INT64 | FIELD_ID_CHANGE_NEW_VERSION, (long long)record.version); - proto->write(FIELD_TYPE_INT64 | FIELD_ID_CHANGE_PREV_VERSION, - (long long)record.prevVersion); - proto->end(changesToken); - } - } - - // Write snapshot from current uid map state. - uint64_t snapshotsToken = - proto->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_SNAPSHOTS); - writeUidMapSnapshotLocked(timestamp, includeVersionStrings, includeInstaller, - std::set() /*empty uid set means including every uid*/, - str_set, proto); - proto->end(snapshotsToken); - - int64_t prevMin = getMinimumTimestampNs(); - mLastUpdatePerConfigKey[key] = timestamp; - int64_t newMin = getMinimumTimestampNs(); - - if (newMin > prevMin) { // Delete anything possible now that the minimum has - // moved forward. - int64_t cutoff_nanos = newMin; - for (auto it_changes = mChanges.begin(); it_changes != mChanges.end();) { - if (it_changes->timestampNs < cutoff_nanos) { - mBytesUsed -= kBytesChangeRecord; - it_changes = mChanges.erase(it_changes); - } else { - ++it_changes; - } - } - } - StatsdStats::getInstance().setCurrentUidMapMemory(mBytesUsed); - StatsdStats::getInstance().setUidMapChanges(mChanges.size()); -} - -void UidMap::printUidMap(int out) const { - lock_guard lock(mMutex); - - for (const auto& kv : mMap) { - if (!kv.second.deleted) { - dprintf(out, "%s, v%" PRId64 ", %s, %s (%i)\n", kv.first.second.c_str(), - kv.second.versionCode, kv.second.versionString.c_str(), - kv.second.installer.c_str(), kv.first.first); - } - } -} - -void UidMap::OnConfigUpdated(const ConfigKey& key) { - mLastUpdatePerConfigKey[key] = -1; -} - -void UidMap::OnConfigRemoved(const ConfigKey& key) { - mLastUpdatePerConfigKey.erase(key); -} - -set UidMap::getAppUid(const string& package) const { - lock_guard lock(mMutex); - - set results; - for (const auto& kv : mMap) { - if (kv.first.second == package && !kv.second.deleted) { - results.insert(kv.first.first); - } - } - return results; -} - -// Note not all the following AIDs are used as uids. Some are used only for gids. -// It's ok to leave them in the map, but we won't ever see them in the log's uid field. -// App's uid starts from 10000, and will not overlap with the following AIDs. -const std::map UidMap::sAidToUidMapping = {{"AID_ROOT", 0}, - {"AID_SYSTEM", 1000}, - {"AID_RADIO", 1001}, - {"AID_BLUETOOTH", 1002}, - {"AID_GRAPHICS", 1003}, - {"AID_INPUT", 1004}, - {"AID_AUDIO", 1005}, - {"AID_CAMERA", 1006}, - {"AID_LOG", 1007}, - {"AID_COMPASS", 1008}, - {"AID_MOUNT", 1009}, - {"AID_WIFI", 1010}, - {"AID_ADB", 1011}, - {"AID_INSTALL", 1012}, - {"AID_MEDIA", 1013}, - {"AID_DHCP", 1014}, - {"AID_SDCARD_RW", 1015}, - {"AID_VPN", 1016}, - {"AID_KEYSTORE", 1017}, - {"AID_USB", 1018}, - {"AID_DRM", 1019}, - {"AID_MDNSR", 1020}, - {"AID_GPS", 1021}, - // {"AID_UNUSED1", 1022}, - {"AID_MEDIA_RW", 1023}, - {"AID_MTP", 1024}, - // {"AID_UNUSED2", 1025}, - {"AID_DRMRPC", 1026}, - {"AID_NFC", 1027}, - {"AID_SDCARD_R", 1028}, - {"AID_CLAT", 1029}, - {"AID_LOOP_RADIO", 1030}, - {"AID_MEDIA_DRM", 1031}, - {"AID_PACKAGE_INFO", 1032}, - {"AID_SDCARD_PICS", 1033}, - {"AID_SDCARD_AV", 1034}, - {"AID_SDCARD_ALL", 1035}, - {"AID_LOGD", 1036}, - {"AID_SHARED_RELRO", 1037}, - {"AID_DBUS", 1038}, - {"AID_TLSDATE", 1039}, - {"AID_MEDIA_EX", 1040}, - {"AID_AUDIOSERVER", 1041}, - {"AID_METRICS_COLL", 1042}, - {"AID_METRICSD", 1043}, - {"AID_WEBSERV", 1044}, - {"AID_DEBUGGERD", 1045}, - {"AID_MEDIA_CODEC", 1046}, - {"AID_CAMERASERVER", 1047}, - {"AID_FIREWALL", 1048}, - {"AID_TRUNKS", 1049}, - {"AID_NVRAM", 1050}, - {"AID_DNS", 1051}, - {"AID_DNS_TETHER", 1052}, - {"AID_WEBVIEW_ZYGOTE", 1053}, - {"AID_VEHICLE_NETWORK", 1054}, - {"AID_MEDIA_AUDIO", 1055}, - {"AID_MEDIA_VIDEO", 1056}, - {"AID_MEDIA_IMAGE", 1057}, - {"AID_TOMBSTONED", 1058}, - {"AID_MEDIA_OBB", 1059}, - {"AID_ESE", 1060}, - {"AID_OTA_UPDATE", 1061}, - {"AID_AUTOMOTIVE_EVS", 1062}, - {"AID_LOWPAN", 1063}, - {"AID_HSM", 1064}, - {"AID_RESERVED_DISK", 1065}, - {"AID_STATSD", 1066}, - {"AID_INCIDENTD", 1067}, - {"AID_SECURE_ELEMENT", 1068}, - {"AID_LMKD", 1069}, - {"AID_LLKD", 1070}, - {"AID_IORAPD", 1071}, - {"AID_GPU_SERVICE", 1072}, - {"AID_NETWORK_STACK", 1073}, - {"AID_GSID", 1074}, - {"AID_FSVERITY_CERT", 1075}, - {"AID_CREDSTORE", 1076}, - {"AID_EXTERNAL_STORAGE", 1077}, - {"AID_EXT_DATA_RW", 1078}, - {"AID_EXT_OBB_RW", 1079}, - {"AID_CONTEXT_HUB", 1080}, - {"AID_SHELL", 2000}, - {"AID_CACHE", 2001}, - {"AID_DIAG", 2002}, - {"AID_NOBODY", 9999}}; - -} // namespace statsd -} // namespace os -} // namespace android diff --git a/bin/src/packages/UidMap.h b/bin/src/packages/UidMap.h deleted file mode 100644 index 622321b8..00000000 --- a/bin/src/packages/UidMap.h +++ /dev/null @@ -1,226 +0,0 @@ -/* - * 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. - */ - -#pragma once - -#include "config/ConfigKey.h" -#include "packages/PackageInfoListener.h" -#include "stats_util.h" - -#include -#include -#include -#include - -#include -#include -#include -#include -#include - -using namespace android; -using namespace std; - -using android::util::ProtoOutputStream; - -namespace android { -namespace os { -namespace statsd { - -struct AppData { - int64_t versionCode; - string versionString; - string installer; - bool deleted; - - // Empty constructor needed for unordered map. - AppData() { - } - - AppData(const int64_t v, const string& versionString, const string& installer) - : versionCode(v), versionString(versionString), installer(installer), deleted(false){}; -}; - -// When calling appendUidMap, we retrieve all the ChangeRecords since the last -// timestamp we called appendUidMap for this configuration key. -struct ChangeRecord { - const bool deletion; - const int64_t timestampNs; - const string package; - const int32_t uid; - const int64_t version; - const int64_t prevVersion; - const string versionString; - const string prevVersionString; - - ChangeRecord(const bool isDeletion, const int64_t timestampNs, const string& package, - const int32_t uid, const int64_t version, const string versionString, - const int64_t prevVersion, const string prevVersionString) - : deletion(isDeletion), - timestampNs(timestampNs), - package(package), - uid(uid), - version(version), - prevVersion(prevVersion), - versionString(versionString), - prevVersionString(prevVersionString) { - } -}; - -const unsigned int kBytesChangeRecord = sizeof(struct ChangeRecord); - -// UidMap keeps track of what the corresponding app name (APK name) and version code for every uid -// at any given moment. This map must be updated by StatsCompanionService. -class UidMap : public virtual android::RefBase { -public: - UidMap(); - ~UidMap(); - static const std::map sAidToUidMapping; - - static sp getInstance(); - /* - * All three inputs must be the same size, and the jth element in each array refers to the same - * tuple, ie. uid[j] corresponds to packageName[j] with versionCode[j]. - */ - void updateMap(const int64_t& timestamp, const vector& uid, - const vector& versionCode, const vector& versionString, - const vector& packageName, const vector& installer); - - void updateApp(const int64_t& timestamp, const String16& packageName, const int32_t& uid, - const int64_t& versionCode, const String16& versionString, - const String16& installer); - void removeApp(const int64_t& timestamp, const String16& packageName, const int32_t& uid); - - // Returns true if the given uid contains the specified app (eg. com.google.android.gms). - bool hasApp(int uid, const string& packageName) const; - - // Returns the app names from uid. - std::set getAppNamesFromUid(const int32_t& uid, bool returnNormalized) const; - - int64_t getAppVersion(int uid, const string& packageName) const; - - // Helper for debugging contents of this uid map. Can be triggered with: - // adb shell cmd stats print-uid-map - void printUidMap(int outFd) const; - - // Command for indicating to the map that StatsLogProcessor should be notified if an app is - // updated. This allows metric producers and managers to distinguish when the same uid or app - // represents a different version of an app. - void setListener(wp listener); - - // Informs uid map that a config is added/updated. Used for keeping mConfigKeys up to date. - void OnConfigUpdated(const ConfigKey& key); - - // Informs uid map that a config is removed. Used for keeping mConfigKeys up to date. - void OnConfigRemoved(const ConfigKey& key); - - void assignIsolatedUid(int isolatedUid, int parentUid); - void removeIsolatedUid(int isolatedUid); - - // Returns the host uid if it exists. Otherwise, returns the same uid that was passed-in. - virtual int getHostUidOrSelf(int uid) const; - - // Gets all snapshots and changes that have occurred since the last output. - // If every config key has received a change or snapshot record, then this - // record is deleted. - void appendUidMap(const int64_t& timestamp, const ConfigKey& key, std::set* str_set, - bool includeVersionStrings, bool includeInstaller, - ProtoOutputStream* proto); - - // Forces the output to be cleared. We still generate a snapshot based on the current state. - // This results in extra data uploaded but helps us reconstruct the uid mapping on the server - // in case we lose a previous upload. - void clearOutput(); - - // Get currently cached value of memory used by UID map. - size_t getBytesUsed() const; - - virtual std::set getAppUid(const string& package) const; - - // Write current PackageInfoSnapshot to ProtoOutputStream. - // interestingUids: If not empty, only write the package info for these uids. If empty, write - // package info for all uids. - // str_set: if not null, add new string to the set and write str_hash to proto - // if null, write string to proto. - void writeUidMapSnapshot(int64_t timestamp, bool includeVersionStrings, bool includeInstaller, - const std::set& interestingUids, std::set* str_set, - ProtoOutputStream* proto); - -private: - std::set getAppNamesFromUidLocked(const int32_t& uid, bool returnNormalized) const; - string normalizeAppName(const string& appName) const; - - void writeUidMapSnapshotLocked(int64_t timestamp, bool includeVersionStrings, - bool includeInstaller, const std::set& interestingUids, - std::set* str_set, ProtoOutputStream* proto); - - mutable mutex mMutex; - mutable mutex mIsolatedMutex; - - struct PairHash { - size_t operator()(std::pair p) const noexcept { - std::hash hash_fn; - return hash_fn(std::to_string(p.first) + p.second); - } - }; - // Maps uid and package name to application data. - std::unordered_map, AppData, PairHash> mMap; - - // Maps isolated uid to the parent uid. Any metrics for an isolated uid will instead contribute - // to the parent uid. - std::unordered_map mIsolatedUidMap; - - // Record the changes that can be provided with the uploads. - std::list mChanges; - - // Store which uid and apps represent deleted ones. - std::list> mDeletedApps; - - // Notify StatsLogProcessor if there's an upgrade/removal in any app. - wp mSubscriber; - - // Mapping of config keys we're aware of to the epoch time they last received an update. This - // lets us know it's safe to delete events older than the oldest update. The value is nanosec. - // Value of -1 denotes this config key has never received an upload. - std::unordered_map mLastUpdatePerConfigKey; - - // Returns the minimum value from mConfigKeys. - int64_t getMinimumTimestampNs(); - - // If our current used bytes is above the limit, then we clear out the earliest snapshot. If - // there are no more snapshots, then we clear out the earliest delta. We repeat the deletions - // until the memory consumed by mOutput is below the specified limit. - void ensureBytesUsedBelowLimit(); - - // Override used for testing the max memory allowed by uid map. 0 means we use the value - // specified in StatsdStats.h with the rest of the guardrails. - size_t maxBytesOverride = 0; - - // Cache the size of mOutput; - size_t mBytesUsed; - - // Allows unit-test to access private methods. - FRIEND_TEST(UidMapTest, TestClearingOutput); - FRIEND_TEST(UidMapTest, TestRemovedAppRetained); - FRIEND_TEST(UidMapTest, TestRemovedAppOverGuardrail); - FRIEND_TEST(UidMapTest, TestOutputIncludesAtLeastOneSnapshot); - FRIEND_TEST(UidMapTest, TestMemoryComputed); - FRIEND_TEST(UidMapTest, TestMemoryGuardrail); -}; - -} // namespace statsd -} // namespace os -} // namespace android diff --git a/bin/src/shell/ShellSubscriber.cpp b/bin/src/shell/ShellSubscriber.cpp deleted file mode 100644 index 9d8f0c24..00000000 --- a/bin/src/shell/ShellSubscriber.cpp +++ /dev/null @@ -1,245 +0,0 @@ -/* - * Copyright (C) 2018 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. - */ -#define DEBUG false // STOPSHIP if true -#include "Log.h" - -#include "ShellSubscriber.h" - -#include - -#include "matchers/matcher_util.h" -#include "stats_log_util.h" - -using android::util::ProtoOutputStream; - -namespace android { -namespace os { -namespace statsd { - -const static int FIELD_ID_ATOM = 1; - -void ShellSubscriber::startNewSubscription(int in, int out, int timeoutSec) { - int myToken = claimToken(); - VLOG("ShellSubscriber: new subscription %d has come in", myToken); - mSubscriptionShouldEnd.notify_one(); - - shared_ptr mySubscriptionInfo = make_shared(in, out); - if (!readConfig(mySubscriptionInfo)) return; - - { - std::unique_lock lock(mMutex); - mSubscriptionInfo = mySubscriptionInfo; - spawnHelperThread(myToken); - waitForSubscriptionToEndLocked(mySubscriptionInfo, myToken, lock, timeoutSec); - - if (mSubscriptionInfo == mySubscriptionInfo) { - mSubscriptionInfo = nullptr; - } - - } -} - -void ShellSubscriber::spawnHelperThread(int myToken) { - std::thread t([this, myToken] { pullAndSendHeartbeats(myToken); }); - t.detach(); -} - -void ShellSubscriber::waitForSubscriptionToEndLocked(shared_ptr myInfo, - int myToken, - std::unique_lock& lock, - int timeoutSec) { - if (timeoutSec > 0) { - mSubscriptionShouldEnd.wait_for(lock, timeoutSec * 1s, [this, myToken, &myInfo] { - return mToken != myToken || !myInfo->mClientAlive; - }); - } else { - mSubscriptionShouldEnd.wait(lock, [this, myToken, &myInfo] { - return mToken != myToken || !myInfo->mClientAlive; - }); - } -} - -// Atomically claim the next token. Token numbers denote subscriber ordering. -int ShellSubscriber::claimToken() { - std::unique_lock lock(mMutex); - int myToken = ++mToken; - return myToken; -} - -// Read and parse single config. There should only one config per input. -bool ShellSubscriber::readConfig(shared_ptr subscriptionInfo) { - // Read the size of the config. - size_t bufferSize; - if (!android::base::ReadFully(subscriptionInfo->mInputFd, &bufferSize, sizeof(bufferSize))) { - return false; - } - - // Read the config. - vector buffer(bufferSize); - if (!android::base::ReadFully(subscriptionInfo->mInputFd, buffer.data(), bufferSize)) { - return false; - } - - // Parse the config. - ShellSubscription config; - if (!config.ParseFromArray(buffer.data(), bufferSize)) { - return false; - } - - // Update SubscriptionInfo with state from config - for (const auto& pushed : config.pushed()) { - subscriptionInfo->mPushedMatchers.push_back(pushed); - } - - for (const auto& pulled : config.pulled()) { - vector packages; - vector uids; - for (const string& pkg : pulled.packages()) { - auto it = UidMap::sAidToUidMapping.find(pkg); - if (it != UidMap::sAidToUidMapping.end()) { - uids.push_back(it->second); - } else { - packages.push_back(pkg); - } - } - - subscriptionInfo->mPulledInfo.emplace_back(pulled.matcher(), pulled.freq_millis(), packages, - uids); - VLOG("adding matcher for pulled atom %d", pulled.matcher().atom_id()); - } - - return true; -} - -void ShellSubscriber::pullAndSendHeartbeats(int myToken) { - VLOG("ShellSubscriber: helper thread %d starting", myToken); - while (true) { - int64_t sleepTimeMs = INT_MAX; - { - std::lock_guard lock(mMutex); - if (!mSubscriptionInfo || mToken != myToken) { - VLOG("ShellSubscriber: helper thread %d done!", myToken); - return; - } - - int64_t nowMillis = getElapsedRealtimeMillis(); - int64_t nowNanos = getElapsedRealtimeNs(); - for (PullInfo& pullInfo : mSubscriptionInfo->mPulledInfo) { - if (pullInfo.mPrevPullElapsedRealtimeMs + pullInfo.mInterval >= nowMillis) { - continue; - } - - vector uids; - getUidsForPullAtom(&uids, pullInfo); - - vector> data; - mPullerMgr->Pull(pullInfo.mPullerMatcher.atom_id(), uids, nowNanos, &data); - VLOG("Pulled %zu atoms with id %d", data.size(), pullInfo.mPullerMatcher.atom_id()); - writePulledAtomsLocked(data, pullInfo.mPullerMatcher); - - pullInfo.mPrevPullElapsedRealtimeMs = nowMillis; - } - - // Send a heartbeat, consisting of a data size of 0, if perfd hasn't recently received - // data from statsd. When it receives the data size of 0, perfd will not expect any - // atoms and recheck whether the subscription should end. - if (nowMillis - mLastWriteMs > kMsBetweenHeartbeats) { - attemptWriteToPipeLocked(/*dataSize=*/0); - } - - // Determine how long to sleep before doing more work. - for (PullInfo& pullInfo : mSubscriptionInfo->mPulledInfo) { - int64_t nextPullTime = pullInfo.mPrevPullElapsedRealtimeMs + pullInfo.mInterval; - int64_t timeBeforePull = nextPullTime - nowMillis; // guaranteed to be non-negative - if (timeBeforePull < sleepTimeMs) sleepTimeMs = timeBeforePull; - } - int64_t timeBeforeHeartbeat = (mLastWriteMs + kMsBetweenHeartbeats) - nowMillis; - if (timeBeforeHeartbeat < sleepTimeMs) sleepTimeMs = timeBeforeHeartbeat; - } - - VLOG("ShellSubscriber: helper thread %d sleeping for %lld ms", myToken, - (long long)sleepTimeMs); - std::this_thread::sleep_for(std::chrono::milliseconds(sleepTimeMs)); - } -} - -void ShellSubscriber::getUidsForPullAtom(vector* uids, const PullInfo& pullInfo) { - uids->insert(uids->end(), pullInfo.mPullUids.begin(), pullInfo.mPullUids.end()); - // This is slow. Consider storing the uids per app and listening to uidmap updates. - for (const string& pkg : pullInfo.mPullPackages) { - set uidsForPkg = mUidMap->getAppUid(pkg); - uids->insert(uids->end(), uidsForPkg.begin(), uidsForPkg.end()); - } - uids->push_back(DEFAULT_PULL_UID); -} - -void ShellSubscriber::writePulledAtomsLocked(const vector>& data, - const SimpleAtomMatcher& matcher) { - mProto.clear(); - int count = 0; - for (const auto& event : data) { - if (matchesSimple(mUidMap, matcher, *event)) { - count++; - uint64_t atomToken = mProto.start(util::FIELD_TYPE_MESSAGE | - util::FIELD_COUNT_REPEATED | FIELD_ID_ATOM); - event->ToProto(mProto); - mProto.end(atomToken); - } - } - - if (count > 0) attemptWriteToPipeLocked(mProto.size()); -} - -void ShellSubscriber::onLogEvent(const LogEvent& event) { - std::lock_guard lock(mMutex); - if (!mSubscriptionInfo) return; - - mProto.clear(); - for (const auto& matcher : mSubscriptionInfo->mPushedMatchers) { - if (matchesSimple(mUidMap, matcher, event)) { - uint64_t atomToken = mProto.start(util::FIELD_TYPE_MESSAGE | - util::FIELD_COUNT_REPEATED | FIELD_ID_ATOM); - event.ToProto(mProto); - mProto.end(atomToken); - attemptWriteToPipeLocked(mProto.size()); - } - } -} - -// Tries to write the atom encoded in mProto to the pipe. If the write fails -// because the read end of the pipe has closed, signals to other threads that -// the subscription should end. -void ShellSubscriber::attemptWriteToPipeLocked(size_t dataSize) { - // First, write the payload size. - if (!android::base::WriteFully(mSubscriptionInfo->mOutputFd, &dataSize, sizeof(dataSize))) { - mSubscriptionInfo->mClientAlive = false; - mSubscriptionShouldEnd.notify_one(); - return; - } - - // Then, write the payload if this is not just a heartbeat. - if (dataSize > 0 && !mProto.flush(mSubscriptionInfo->mOutputFd)) { - mSubscriptionInfo->mClientAlive = false; - mSubscriptionShouldEnd.notify_one(); - return; - } - - mLastWriteMs = getElapsedRealtimeMillis(); -} - -} // namespace statsd -} // namespace os -} // namespace android diff --git a/bin/src/shell/ShellSubscriber.h b/bin/src/shell/ShellSubscriber.h deleted file mode 100644 index 49bae113..00000000 --- a/bin/src/shell/ShellSubscriber.h +++ /dev/null @@ -1,146 +0,0 @@ -/* - * Copyright (C) 2018 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. - */ - -#pragma once - -#include -#include - -#include -#include -#include - -#include "external/StatsPullerManager.h" -#include "packages/modules/StatsD/bin/src/shell/shell_config.pb.h" -#include "packages/modules/StatsD/bin/src/statsd_config.pb.h" -#include "logd/LogEvent.h" -#include "packages/UidMap.h" - -namespace android { -namespace os { -namespace statsd { - -/** - * Handles atoms subscription via shell cmd. - * - * A shell subscription lasts *until shell exits*. Unlike config based clients, a shell client - * communicates with statsd via file descriptors. They can subscribe pushed and pulled atoms. - * The atoms are sent back to the client in real time, as opposed to keeping the data in memory. - * Shell clients do not subscribe aggregated metrics, as they are responsible for doing the - * aggregation after receiving the atom events. - * - * Shell clients pass ShellSubscription in the proto binary format. Clients can update the - * subscription by sending a new subscription. The new subscription would replace the old one. - * Input data stream format is: - * - * |size_t|subscription proto|size_t|subscription proto|.... - * - * statsd sends the events back in Atom proto binary format. Each Atom message is preceded - * with sizeof(size_t) bytes indicating the size of the proto message payload. - * - * The stream would be in the following format: - * |size_t|shellData proto|size_t|shellData proto|.... - * - * Only one shell subscriber is allowed at a time because each shell subscriber blocks one thread - * until it exits. - */ -class ShellSubscriber : public virtual RefBase { -public: - ShellSubscriber(sp uidMap, sp pullerMgr) - : mUidMap(uidMap), mPullerMgr(pullerMgr){}; - - void startNewSubscription(int inFd, int outFd, int timeoutSec); - - void onLogEvent(const LogEvent& event); - -private: - struct PullInfo { - PullInfo(const SimpleAtomMatcher& matcher, int64_t interval, - const std::vector& packages, const std::vector& uids) - : mPullerMatcher(matcher), - mInterval(interval), - mPrevPullElapsedRealtimeMs(0), - mPullPackages(packages), - mPullUids(uids) { - } - SimpleAtomMatcher mPullerMatcher; - int64_t mInterval; - int64_t mPrevPullElapsedRealtimeMs; - std::vector mPullPackages; - std::vector mPullUids; - }; - - struct SubscriptionInfo { - SubscriptionInfo(const int& inputFd, const int& outputFd) - : mInputFd(inputFd), mOutputFd(outputFd), mClientAlive(true) { - } - - int mInputFd; - int mOutputFd; - std::vector mPushedMatchers; - std::vector mPulledInfo; - bool mClientAlive; - }; - - int claimToken(); - - bool readConfig(std::shared_ptr subscriptionInfo); - - void spawnHelperThread(int myToken); - - void waitForSubscriptionToEndLocked(std::shared_ptr myInfo, - int myToken, - std::unique_lock& lock, - int timeoutSec); - - // Helper thread that pulls atoms at a regular frequency and sends - // heartbeats to perfd if statsd hasn't recently sent any data. Statsd must - // send heartbeats for perfd to escape a blocking read call and recheck if - // the user has terminated the subscription. - void pullAndSendHeartbeats(int myToken); - - void writePulledAtomsLocked(const vector>& data, - const SimpleAtomMatcher& matcher); - - void getUidsForPullAtom(vector* uids, const PullInfo& pullInfo); - - void attemptWriteToPipeLocked(size_t dataSize); - - sp mUidMap; - - sp mPullerMgr; - - android::util::ProtoOutputStream mProto; - - mutable std::mutex mMutex; - - std::condition_variable mSubscriptionShouldEnd; - - std::shared_ptr mSubscriptionInfo = nullptr; - - int mToken = 0; - - const int32_t DEFAULT_PULL_UID = AID_SYSTEM; - - // Tracks when we last send data to perfd. We need that time to determine - // when next to send a heartbeat. - int64_t mLastWriteMs = 0; - const int64_t kMsBetweenHeartbeats = 1000; -}; - -} // namespace statsd -} // namespace os -} // namespace android diff --git a/bin/src/shell/shell_config.proto b/bin/src/shell/shell_config.proto deleted file mode 100644 index 58db05bc..00000000 --- a/bin/src/shell/shell_config.proto +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright (C) 2018 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. - */ - -syntax = "proto2"; - -package android.os.statsd; - -option java_package = "com.android.os"; -option java_outer_classname = "ShellConfig"; - -import "packages/modules/StatsD/bin/src/statsd_config.proto"; - -message PulledAtomSubscription { - optional SimpleAtomMatcher matcher = 1; - - /* gap between two pulls in milliseconds */ - optional int32 freq_millis = 2; - - /* Packages that the pull is requested from */ - repeated string packages = 3; -} - -message ShellSubscription { - repeated SimpleAtomMatcher pushed = 1; - repeated PulledAtomSubscription pulled = 2; -} \ No newline at end of file diff --git a/bin/src/shell/shell_data.proto b/bin/src/shell/shell_data.proto deleted file mode 100644 index ec41cbc5..00000000 --- a/bin/src/shell/shell_data.proto +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright (C) 2018 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. - */ - -syntax = "proto2"; - -package android.os.statsd; - -option java_package = "com.android.os.statsd"; -option java_outer_classname = "ShellDataProto"; - -import "frameworks/proto_logging/stats/atoms.proto"; - -// The output of shell subscription, including both pulled and pushed subscriptions. -message ShellData { - repeated Atom atom = 1; -} diff --git a/bin/src/socket/StatsSocketListener.cpp b/bin/src/socket/StatsSocketListener.cpp deleted file mode 100755 index b877cc9c..00000000 --- a/bin/src/socket/StatsSocketListener.cpp +++ /dev/null @@ -1,156 +0,0 @@ -/* - * Copyright (C) 2018 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. - */ -#define DEBUG false // STOPSHIP if true -#include "Log.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#include "StatsSocketListener.h" -#include "guardrail/StatsdStats.h" -#include "stats_log_util.h" - -namespace android { -namespace os { -namespace statsd { - -StatsSocketListener::StatsSocketListener(std::shared_ptr queue) - : SocketListener(getLogSocket(), false /*start listen*/), mQueue(queue) { -} - -StatsSocketListener::~StatsSocketListener() { -} - -bool StatsSocketListener::onDataAvailable(SocketClient* cli) { - static bool name_set; - if (!name_set) { - prctl(PR_SET_NAME, "statsd.writer"); - name_set = true; - } - - // + 1 to ensure null terminator if MAX_PAYLOAD buffer is received - char buffer[sizeof(android_log_header_t) + LOGGER_ENTRY_MAX_PAYLOAD + 1]; - struct iovec iov = {buffer, sizeof(buffer) - 1}; - - alignas(4) char control[CMSG_SPACE(sizeof(struct ucred))]; - struct msghdr hdr = { - NULL, 0, &iov, 1, control, sizeof(control), 0, - }; - - int socket = cli->getSocket(); - - // To clear the entire buffer is secure/safe, but this contributes to 1.68% - // overhead under logging load. We are safe because we check counts, but - // still need to clear null terminator - // memset(buffer, 0, sizeof(buffer)); - ssize_t n = recvmsg(socket, &hdr, 0); - if (n <= (ssize_t)(sizeof(android_log_header_t))) { - return false; - } - - buffer[n] = 0; - - struct ucred* cred = NULL; - - struct cmsghdr* cmsg = CMSG_FIRSTHDR(&hdr); - while (cmsg != NULL) { - if (cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_CREDENTIALS) { - cred = (struct ucred*)CMSG_DATA(cmsg); - break; - } - cmsg = CMSG_NXTHDR(&hdr, cmsg); - } - - struct ucred fake_cred; - if (cred == NULL) { - cred = &fake_cred; - cred->pid = 0; - cred->uid = DEFAULT_OVERFLOWUID; - } - - uint8_t* ptr = ((uint8_t*)buffer) + sizeof(android_log_header_t); - n -= sizeof(android_log_header_t); - - // When a log failed to write to statsd socket (e.g., due ot EBUSY), a special message would - // be sent to statsd when the socket communication becomes available again. - // The format is android_log_event_int_t with a single integer in the payload indicating the - // number of logs that failed. (*FORMAT MUST BE IN SYNC WITH system/core/libstats*) - // Note that all normal stats logs are in the format of event_list, so there won't be confusion. - // - // TODO(b/80538532): In addition to log it in StatsdStats, we should properly reset the config. - if (n == sizeof(android_log_event_long_t)) { - android_log_event_long_t* long_event = reinterpret_cast(ptr); - if (long_event->payload.type == EVENT_TYPE_LONG) { - int64_t composed_long = long_event->payload.data; - - // format: - // |last_tag|dropped_count| - int32_t dropped_count = (int32_t)(0xffffffff & composed_long); - int32_t last_atom_tag = (int32_t)((0xffffffff00000000 & (uint64_t)composed_long) >> 32); - - ALOGE("Found dropped events: %d error %d last atom tag %d from uid %d", dropped_count, - long_event->header.tag, last_atom_tag, cred->uid); - StatsdStats::getInstance().noteLogLost((int32_t)getWallClockSec(), dropped_count, - long_event->header.tag, last_atom_tag, cred->uid, - cred->pid); - return true; - } - } - - // move past the 4-byte StatsEventTag - uint8_t* msg = ptr + sizeof(uint32_t); - uint32_t len = n - sizeof(uint32_t); - uint32_t uid = cred->uid; - uint32_t pid = cred->pid; - - int64_t oldestTimestamp; - std::unique_ptr logEvent = std::make_unique(uid, pid); - logEvent->parseBuffer(msg, len); - - if (!mQueue->push(std::move(logEvent), &oldestTimestamp)) { - StatsdStats::getInstance().noteEventQueueOverflow(oldestTimestamp); - } - - return true; -} - -int StatsSocketListener::getLogSocket() { - static const char socketName[] = "statsdw"; - int sock = android_get_control_socket(socketName); - - if (sock < 0) { // statsd started up in init.sh - sock = socket_local_server(socketName, ANDROID_SOCKET_NAMESPACE_RESERVED, SOCK_DGRAM); - - int on = 1; - if (setsockopt(sock, SOL_SOCKET, SO_PASSCRED, &on, sizeof(on))) { - return -1; - } - } - return sock; -} - -} // namespace statsd -} // namespace os -} // namespace android diff --git a/bin/src/socket/StatsSocketListener.h b/bin/src/socket/StatsSocketListener.h deleted file mode 100644 index 2167a564..00000000 --- a/bin/src/socket/StatsSocketListener.h +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright (C) 2018 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. - */ -#pragma once - -#include -#include -#include "logd/LogEventQueue.h" - -// DEFAULT_OVERFLOWUID is defined in linux/highuid.h, which is not part of -// the uapi headers for userspace to use. This value is filled in on the -// out-of-band socket credentials if the OS fails to find one available. -// One of the causes of this is if SO_PASSCRED is set, all the packets before -// that point will have this value. We also use it in a fake credential if -// no socket credentials are supplied. -#ifndef DEFAULT_OVERFLOWUID -#define DEFAULT_OVERFLOWUID 65534 -#endif - -namespace android { -namespace os { -namespace statsd { - -class StatsSocketListener : public SocketListener, public virtual android::RefBase { -public: - explicit StatsSocketListener(std::shared_ptr queue); - - virtual ~StatsSocketListener(); - -protected: - virtual bool onDataAvailable(SocketClient* cli); - -private: - static int getLogSocket(); - /** - * Who is going to get the events when they're read. - */ - std::shared_ptr mQueue; -}; -} // namespace statsd -} // namespace os -} // namespace android diff --git a/bin/src/state/StateListener.h b/bin/src/state/StateListener.h deleted file mode 100644 index 63880017..00000000 --- a/bin/src/state/StateListener.h +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright (C) 2019, 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. - */ -#pragma once - -#include - -#include "HashableDimensionKey.h" - -namespace android { -namespace os { -namespace statsd { - -class StateListener : public virtual RefBase { -public: - StateListener(){}; - - virtual ~StateListener(){}; - - /** - * Interface for handling a state change. - * - * The old and new state values map to the original state values. - * StateTrackers only track the original state values and are unaware - * of higher-level state groups. MetricProducers hold information on - * state groups and are responsible for mapping original state values to - * the correct state group. - * - * [eventTimeNs]: Time of the state change log event. - * [atomId]: The id of the state atom - * [primaryKey]: The primary field values of the state atom - * [oldState]: Previous state value before state change - * [newState]: Current state value after state change - */ - virtual void onStateChanged(const int64_t eventTimeNs, const int32_t atomId, - const HashableDimensionKey& primaryKey, const FieldValue& oldState, - const FieldValue& newState) = 0; -}; - -} // namespace statsd -} // namespace os -} // namespace android diff --git a/bin/src/state/StateManager.cpp b/bin/src/state/StateManager.cpp deleted file mode 100644 index c29afeb7..00000000 --- a/bin/src/state/StateManager.cpp +++ /dev/null @@ -1,112 +0,0 @@ -/* - * Copyright (C) 2019, 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. - */ - -#define DEBUG false // STOPSHIP if true -#include "Log.h" - -#include "StateManager.h" - -#include - -namespace android { -namespace os { -namespace statsd { - -StateManager::StateManager() - : mAllowedPkg({ - "com.android.systemui", - }) { -} - -StateManager& StateManager::getInstance() { - static StateManager sStateManager; - return sStateManager; -} - -void StateManager::clear() { - mStateTrackers.clear(); -} - -void StateManager::onLogEvent(const LogEvent& event) { - // Only process state events from uids in AID_* and packages that are whitelisted in - // mAllowedPkg. - // Whitelisted AIDs are AID_ROOT and all AIDs in [1000, 2000) - if (event.GetUid() == AID_ROOT || (event.GetUid() >= 1000 && event.GetUid() < 2000) || - mAllowedLogSources.find(event.GetUid()) != mAllowedLogSources.end()) { - if (mStateTrackers.find(event.GetTagId()) != mStateTrackers.end()) { - mStateTrackers[event.GetTagId()]->onLogEvent(event); - } - } -} - -void StateManager::registerListener(const int32_t atomId, wp listener) { - // Check if state tracker already exists. - if (mStateTrackers.find(atomId) == mStateTrackers.end()) { - mStateTrackers[atomId] = new StateTracker(atomId); - } - mStateTrackers[atomId]->registerListener(listener); -} - -void StateManager::unregisterListener(const int32_t atomId, wp listener) { - std::unique_lock lock(mMutex); - - // Hold the sp<> until the lock is released so that ~StateTracker() is - // not called while the lock is held. - sp toRemove; - - // Unregister listener from correct StateTracker - auto it = mStateTrackers.find(atomId); - if (it != mStateTrackers.end()) { - it->second->unregisterListener(listener); - - // Remove the StateTracker if it has no listeners - if (it->second->getListenersCount() == 0) { - toRemove = it->second; - mStateTrackers.erase(it); - } - } else { - ALOGE("StateManager cannot unregister listener, StateTracker for atom %d does not exist", - atomId); - } - lock.unlock(); -} - -bool StateManager::getStateValue(const int32_t atomId, const HashableDimensionKey& key, - FieldValue* output) const { - auto it = mStateTrackers.find(atomId); - if (it != mStateTrackers.end()) { - return it->second->getStateValue(key, output); - } - return false; -} - -void StateManager::updateLogSources(const sp& uidMap) { - mAllowedLogSources.clear(); - for (const auto& pkg : mAllowedPkg) { - auto uids = uidMap->getAppUid(pkg); - mAllowedLogSources.insert(uids.begin(), uids.end()); - } -} - -void StateManager::notifyAppChanged(const string& apk, const sp& uidMap) { - if (mAllowedPkg.find(apk) != mAllowedPkg.end()) { - updateLogSources(uidMap); - } -} - -} // namespace statsd -} // namespace os -} // namespace android diff --git a/bin/src/state/StateManager.h b/bin/src/state/StateManager.h deleted file mode 100644 index 18c404c2..00000000 --- a/bin/src/state/StateManager.h +++ /dev/null @@ -1,103 +0,0 @@ -/* - * Copyright (C) 2019, 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. - */ -#pragma once - -#include -#include - -#include -#include -#include - -#include "HashableDimensionKey.h" -#include "packages/UidMap.h" -#include "state/StateListener.h" -#include "state/StateTracker.h" - -namespace android { -namespace os { -namespace statsd { - -/** - * This class is NOT thread safe. - * It should only be used while StatsLogProcessor's lock is held. - */ -class StateManager : public virtual RefBase { -public: - StateManager(); - - ~StateManager(){}; - - // Returns a pointer to the single, shared StateManager object. - static StateManager& getInstance(); - - // Unregisters all listeners and removes all trackers from StateManager. - void clear(); - - // Notifies the correct StateTracker of an event. - void onLogEvent(const LogEvent& event); - - // Notifies the StateTracker for the given atomId to register listener. - // If the correct StateTracker does not exist, a new StateTracker is created. - // Note: StateTrackers can be created for non-state atoms. They are essentially empty and - // do not perform any actions. - void registerListener(const int32_t atomId, wp listener); - - // Notifies the correct StateTracker to unregister a listener - // and removes the tracker if it no longer has any listeners. - void unregisterListener(const int32_t atomId, wp listener); - - // Returns true if the StateTracker exists and queries for the - // original state value mapped to the given query key. The state value is - // stored and output in a FieldValue class. - // Returns false if the StateTracker doesn't exist. - bool getStateValue(const int32_t atomId, const HashableDimensionKey& queryKey, - FieldValue* output) const; - - // Updates mAllowedLogSources with the latest uids for the packages that are allowed to log. - void updateLogSources(const sp& uidMap); - - void notifyAppChanged(const string& apk, const sp& uidMap); - - inline int getStateTrackersCount() const { - return mStateTrackers.size(); - } - - inline int getListenersCount(const int32_t atomId) const { - auto it = mStateTrackers.find(atomId); - if (it != mStateTrackers.end()) { - return it->second->getListenersCount(); - } - return -1; - } - -private: - mutable std::mutex mMutex; - - // Maps state atom ids to StateTrackers - std::unordered_map> mStateTrackers; - - // The package names that can log state events. - const std::set mAllowedPkg; - - // The combined uid sources (after translating pkg name to uid). - // State events from uids that are not in the list will be ignored to avoid state pollution. - std::set mAllowedLogSources; -}; - -} // namespace statsd -} // namespace os -} // namespace android diff --git a/bin/src/state/StateTracker.cpp b/bin/src/state/StateTracker.cpp deleted file mode 100644 index 41e525c3..00000000 --- a/bin/src/state/StateTracker.cpp +++ /dev/null @@ -1,190 +0,0 @@ -/* - * Copyright (C) 2019, 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. - */ - -#define DEBUG true // STOPSHIP if true -#include "Log.h" - -#include "stats_util.h" - -#include "StateTracker.h" - -namespace android { -namespace os { -namespace statsd { - -StateTracker::StateTracker(const int32_t atomId) : mField(atomId, 0) { -} - -void StateTracker::onLogEvent(const LogEvent& event) { - const int64_t eventTimeNs = event.GetElapsedTimestampNs(); - - // Parse event for primary field values i.e. primary key. - HashableDimensionKey primaryKey; - filterPrimaryKey(event.getValues(), &primaryKey); - - FieldValue newState; - if (!getStateFieldValueFromLogEvent(event, &newState)) { - ALOGE("StateTracker error extracting state from log event. Missing exclusive state field."); - clearStateForPrimaryKey(eventTimeNs, primaryKey); - return; - } - - mField.setField(newState.mField.getField()); - - if (newState.mValue.getType() != INT) { - ALOGE("StateTracker error extracting state from log event. Type: %d", - newState.mValue.getType()); - clearStateForPrimaryKey(eventTimeNs, primaryKey); - return; - } - - if (int resetState = event.getResetState(); resetState != -1) { - VLOG("StateTracker new reset state: %d", resetState); - const FieldValue resetStateFieldValue(mField, Value(resetState)); - handleReset(eventTimeNs, resetStateFieldValue); - return; - } - - const bool nested = newState.mAnnotations.isNested(); - StateValueInfo* stateValueInfo = &mStateMap[primaryKey]; - updateStateForPrimaryKey(eventTimeNs, primaryKey, newState, nested, stateValueInfo); -} - -void StateTracker::registerListener(wp listener) { - mListeners.insert(listener); -} - -void StateTracker::unregisterListener(wp listener) { - mListeners.erase(listener); -} - -bool StateTracker::getStateValue(const HashableDimensionKey& queryKey, FieldValue* output) const { - output->mField = mField; - - if (const auto it = mStateMap.find(queryKey); it != mStateMap.end()) { - output->mValue = it->second.state; - return true; - } - - // Set the state value to kStateUnknown if query key is not found in state map. - output->mValue = kStateUnknown; - return false; -} - -void StateTracker::handleReset(const int64_t eventTimeNs, const FieldValue& newState) { - VLOG("StateTracker handle reset"); - for (auto& [primaryKey, stateValueInfo] : mStateMap) { - updateStateForPrimaryKey(eventTimeNs, primaryKey, newState, - false /* nested; treat this state change as not nested */, - &stateValueInfo); - } -} - -void StateTracker::clearStateForPrimaryKey(const int64_t eventTimeNs, - const HashableDimensionKey& primaryKey) { - VLOG("StateTracker clear state for primary key"); - const std::unordered_map::iterator it = - mStateMap.find(primaryKey); - - // If there is no entry for the primaryKey in mStateMap, then the state is already - // kStateUnknown. - const FieldValue state(mField, Value(kStateUnknown)); - if (it != mStateMap.end()) { - updateStateForPrimaryKey(eventTimeNs, primaryKey, state, - false /* nested; treat this state change as not nested */, - &it->second); - } -} - -void StateTracker::updateStateForPrimaryKey(const int64_t eventTimeNs, - const HashableDimensionKey& primaryKey, - const FieldValue& newState, const bool nested, - StateValueInfo* stateValueInfo) { - FieldValue oldState; - oldState.mField = mField; - oldState.mValue.setInt(stateValueInfo->state); - const int32_t oldStateValue = stateValueInfo->state; - const int32_t newStateValue = newState.mValue.int_value; - - if (kStateUnknown == newStateValue) { - mStateMap.erase(primaryKey); - } - - // Update state map for non-nested counting case. - // Every state event triggers a state overwrite. - if (!nested) { - stateValueInfo->state = newStateValue; - stateValueInfo->count = 1; - - // Notify listeners if state has changed. - if (oldStateValue != newStateValue) { - notifyListeners(eventTimeNs, primaryKey, oldState, newState); - } - return; - } - - // Update state map for nested counting case. - // - // Nested counting is only allowed for binary state events such as ON/OFF or - // ACQUIRE/RELEASE. For example, WakelockStateChanged might have the state - // events: ON, ON, OFF. The state will still be ON until we see the same - // number of OFF events as ON events. - // - // In atoms.proto, a state atom with nested counting enabled - // must only have 2 states. There is no enforcemnt here of this requirement. - // The atom must be logged correctly. - if (kStateUnknown == newStateValue) { - if (kStateUnknown != oldStateValue) { - notifyListeners(eventTimeNs, primaryKey, oldState, newState); - } - } else if (oldStateValue == kStateUnknown) { - stateValueInfo->state = newStateValue; - stateValueInfo->count = 1; - notifyListeners(eventTimeNs, primaryKey, oldState, newState); - } else if (oldStateValue == newStateValue) { - stateValueInfo->count++; - } else if (--stateValueInfo->count == 0) { - stateValueInfo->state = newStateValue; - stateValueInfo->count = 1; - notifyListeners(eventTimeNs, primaryKey, oldState, newState); - } -} - -void StateTracker::notifyListeners(const int64_t eventTimeNs, - const HashableDimensionKey& primaryKey, - const FieldValue& oldState, const FieldValue& newState) { - for (auto l : mListeners) { - auto sl = l.promote(); - if (sl != nullptr) { - sl->onStateChanged(eventTimeNs, mField.getTag(), primaryKey, oldState, newState); - } - } -} - -bool getStateFieldValueFromLogEvent(const LogEvent& event, FieldValue* output) { - const int exclusiveStateFieldIndex = event.getExclusiveStateFieldIndex(); - if (-1 == exclusiveStateFieldIndex) { - ALOGE("error extracting state from log event. Missing exclusive state field."); - return false; - } - - *output = event.getValues()[exclusiveStateFieldIndex]; - return true; -} - -} // namespace statsd -} // namespace os -} // namespace android diff --git a/bin/src/state/StateTracker.h b/bin/src/state/StateTracker.h deleted file mode 100644 index abd579e7..00000000 --- a/bin/src/state/StateTracker.h +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Copyright (C) 2019, 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. - */ -#pragma once - -#include -#include "HashableDimensionKey.h" -#include "logd/LogEvent.h" - -#include "state/StateListener.h" - -#include - -namespace android { -namespace os { -namespace statsd { - -class StateTracker : public virtual RefBase { -public: - StateTracker(const int32_t atomId); - - virtual ~StateTracker(){}; - - // Updates state map and notifies all listeners if a state change occurs. - // Checks if a state change has occurred by getting the state value from - // the log event and comparing the old and new states. - void onLogEvent(const LogEvent& event); - - // Adds new listeners to set of StateListeners. If a listener is already - // registered, it is ignored. - void registerListener(wp listener); - - void unregisterListener(wp listener); - - // The output is a FieldValue object that has mStateField as the field and - // the original state value (found using the given query key) as the value. - // - // If the key isn't mapped to a state or the key size doesn't match the - // number of primary fields, the output value is set to kStateUnknown. - bool getStateValue(const HashableDimensionKey& queryKey, FieldValue* output) const; - - inline int getListenersCount() const { - return mListeners.size(); - } - - const static int kStateUnknown = -1; - -private: - struct StateValueInfo { - int32_t state = kStateUnknown; // state value - int count = 0; // nested count (only used for binary states) - }; - - Field mField; - - // Maps primary key to state value info - std::unordered_map mStateMap; - - // Set of all StateListeners (objects listening for state changes) - std::set> mListeners; - - // Reset all state values in map to the given state. - void handleReset(const int64_t eventTimeNs, const FieldValue& newState); - - // Clears the state value mapped to the given primary key by setting it to kStateUnknown. - void clearStateForPrimaryKey(const int64_t eventTimeNs, const HashableDimensionKey& primaryKey); - - // Update the StateMap based on the received state value. - void updateStateForPrimaryKey(const int64_t eventTimeNs, const HashableDimensionKey& primaryKey, - const FieldValue& newState, const bool nested, - StateValueInfo* stateValueInfo); - - // Notify registered state listeners of state change. - void notifyListeners(const int64_t eventTimeNs, const HashableDimensionKey& primaryKey, - const FieldValue& oldState, const FieldValue& newState); -}; - -bool getStateFieldValueFromLogEvent(const LogEvent& event, FieldValue* output); - -} // namespace statsd -} // namespace os -} // namespace android diff --git a/bin/src/stats_log.proto b/bin/src/stats_log.proto deleted file mode 100644 index f91c4c08..00000000 --- a/bin/src/stats_log.proto +++ /dev/null @@ -1,556 +0,0 @@ -/* - * 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. - */ - -syntax = "proto2"; - -package android.os.statsd; - -option java_package = "com.android.os"; -option java_outer_classname = "StatsLog"; - -import "frameworks/proto_logging/stats/atoms.proto"; - -message DimensionsValue { - optional int32 field = 1; - - oneof value { - string value_str = 2; - int32 value_int = 3; - int64 value_long = 4; - bool value_bool = 5; - float value_float = 6; - DimensionsValueTuple value_tuple = 7; - uint64 value_str_hash = 8; - } -} - -message DimensionsValueTuple { - repeated DimensionsValue dimensions_value = 1; -} - -message StateValue { - optional int32 atom_id = 1; - - oneof contents { - int64 group_id = 2; - int32 value = 3; - } -} - -message EventMetricData { - optional int64 elapsed_timestamp_nanos = 1; - - optional Atom atom = 2; - - optional int64 wall_clock_timestamp_nanos = 3 [deprecated = true]; -} - -message CountBucketInfo { - optional int64 start_bucket_elapsed_nanos = 1; - - optional int64 end_bucket_elapsed_nanos = 2; - - optional int64 count = 3; - - optional int64 bucket_num = 4; - - optional int64 start_bucket_elapsed_millis = 5; - - optional int64 end_bucket_elapsed_millis = 6; -} - -message CountMetricData { - optional DimensionsValue dimensions_in_what = 1; - - repeated StateValue slice_by_state = 6; - - repeated CountBucketInfo bucket_info = 3; - - repeated DimensionsValue dimension_leaf_values_in_what = 4; - - optional DimensionsValue dimensions_in_condition = 2 [deprecated = true]; - - repeated DimensionsValue dimension_leaf_values_in_condition = 5 [deprecated = true]; -} - -message DurationBucketInfo { - optional int64 start_bucket_elapsed_nanos = 1; - - optional int64 end_bucket_elapsed_nanos = 2; - - optional int64 duration_nanos = 3; - - optional int64 bucket_num = 4; - - optional int64 start_bucket_elapsed_millis = 5; - - optional int64 end_bucket_elapsed_millis = 6; -} - -message DurationMetricData { - optional DimensionsValue dimensions_in_what = 1; - - repeated StateValue slice_by_state = 6; - - repeated DurationBucketInfo bucket_info = 3; - - repeated DimensionsValue dimension_leaf_values_in_what = 4; - - optional DimensionsValue dimensions_in_condition = 2 [deprecated = true]; - - repeated DimensionsValue dimension_leaf_values_in_condition = 5 [deprecated = true]; -} - -message ValueBucketInfo { - optional int64 start_bucket_elapsed_nanos = 1; - - optional int64 end_bucket_elapsed_nanos = 2; - - optional int64 value = 3 [deprecated = true]; - - oneof single_value { - int64 value_long = 7 [deprecated = true]; - - double value_double = 8 [deprecated = true]; - } - - message Value { - optional int32 index = 1; - oneof value { - int64 value_long = 2; - double value_double = 3; - } - } - - repeated Value values = 9; - - optional int64 bucket_num = 4; - - optional int64 start_bucket_elapsed_millis = 5; - - optional int64 end_bucket_elapsed_millis = 6; - - optional int64 condition_true_nanos = 10; -} - -message ValueMetricData { - optional DimensionsValue dimensions_in_what = 1; - - repeated StateValue slice_by_state = 6; - - repeated ValueBucketInfo bucket_info = 3; - - repeated DimensionsValue dimension_leaf_values_in_what = 4; - - optional DimensionsValue dimensions_in_condition = 2 [deprecated = true]; - - repeated DimensionsValue dimension_leaf_values_in_condition = 5 [deprecated = true]; -} - -message GaugeBucketInfo { - optional int64 start_bucket_elapsed_nanos = 1; - - optional int64 end_bucket_elapsed_nanos = 2; - - repeated Atom atom = 3; - - repeated int64 elapsed_timestamp_nanos = 4; - - repeated int64 wall_clock_timestamp_nanos = 5 [deprecated = true]; - - optional int64 bucket_num = 6; - - optional int64 start_bucket_elapsed_millis = 7; - - optional int64 end_bucket_elapsed_millis = 8; -} - -message GaugeMetricData { - optional DimensionsValue dimensions_in_what = 1; - - // Currently unsupported - repeated StateValue slice_by_state = 6; - - repeated GaugeBucketInfo bucket_info = 3; - - repeated DimensionsValue dimension_leaf_values_in_what = 4; - - optional DimensionsValue dimensions_in_condition = 2 [deprecated = true]; - - repeated DimensionsValue dimension_leaf_values_in_condition = 5 [deprecated = true]; -} - -message StatsLogReport { - optional int64 metric_id = 1; - - // Fields 2 and 3 are reserved. - - // Keep this in sync with BucketDropReason enum in MetricProducer.h. - enum BucketDropReason { - // For ValueMetric, a bucket is dropped during a dump report request iff - // current bucket should be included, a pull is needed (pulled metric and - // condition is true), and we are under fast time constraints. - DUMP_REPORT_REQUESTED = 1; - EVENT_IN_WRONG_BUCKET = 2; - CONDITION_UNKNOWN = 3; - PULL_FAILED = 4; - PULL_DELAYED = 5; - DIMENSION_GUARDRAIL_REACHED = 6; - MULTIPLE_BUCKETS_SKIPPED = 7; - // Not an invalid bucket case, but the bucket is dropped. - BUCKET_TOO_SMALL = 8; - // Not an invalid bucket case, but the bucket is skipped. - NO_DATA = 9; - }; - - message DropEvent { - optional BucketDropReason drop_reason = 1; - - optional int64 drop_time_millis = 2; - } - - message SkippedBuckets { - optional int64 start_bucket_elapsed_nanos = 1; - - optional int64 end_bucket_elapsed_nanos = 2; - - optional int64 start_bucket_elapsed_millis = 3; - - optional int64 end_bucket_elapsed_millis = 4; - - // The number of drop events is capped by StatsdStats::kMaxLoggedBucketDropEvents. - // The current maximum is 10 drop events. - repeated DropEvent drop_event = 5; - } - - message EventMetricDataWrapper { - repeated EventMetricData data = 1; - } - message CountMetricDataWrapper { - repeated CountMetricData data = 1; - } - message DurationMetricDataWrapper { - repeated DurationMetricData data = 1; - } - message ValueMetricDataWrapper { - repeated ValueMetricData data = 1; - repeated SkippedBuckets skipped = 2; - } - - message GaugeMetricDataWrapper { - repeated GaugeMetricData data = 1; - repeated SkippedBuckets skipped = 2; - } - - oneof data { - EventMetricDataWrapper event_metrics = 4; - CountMetricDataWrapper count_metrics = 5; - DurationMetricDataWrapper duration_metrics = 6; - ValueMetricDataWrapper value_metrics = 7; - GaugeMetricDataWrapper gauge_metrics = 8; - } - - optional int64 time_base_elapsed_nano_seconds = 9; - - optional int64 bucket_size_nano_seconds = 10; - - optional DimensionsValue dimensions_path_in_what = 11; - - optional DimensionsValue dimensions_path_in_condition = 12 [deprecated = true]; - - // DO NOT USE field 13. - - optional bool is_active = 14; -} - -message UidMapping { - message PackageInfoSnapshot { - message PackageInfo { - optional string name = 1; - - optional int64 version = 2; - - optional int32 uid = 3; - - optional bool deleted = 4; - - optional uint64 name_hash = 5; - - optional string version_string = 6; - - optional uint64 version_string_hash = 7; - - optional string installer = 8; - - optional uint64 installer_hash = 9; - } - optional int64 elapsed_timestamp_nanos = 1; - - repeated PackageInfo package_info = 2; - } - repeated PackageInfoSnapshot snapshots = 1; - - message Change { - optional bool deletion = 1; - - optional int64 elapsed_timestamp_nanos = 2; - optional string app = 3; - optional int32 uid = 4; - - optional int64 new_version = 5; - optional int64 prev_version = 6; - optional uint64 app_hash = 7; - optional string new_version_string = 8; - optional string prev_version_string = 9; - optional uint64 new_version_string_hash = 10; - optional uint64 prev_version_string_hash = 11; - } - repeated Change changes = 2; -} - -message ConfigMetricsReport { - repeated StatsLogReport metrics = 1; - - optional UidMapping uid_map = 2; - - optional int64 last_report_elapsed_nanos = 3; - - optional int64 current_report_elapsed_nanos = 4; - - optional int64 last_report_wall_clock_nanos = 5; - - optional int64 current_report_wall_clock_nanos = 6; - - message Annotation { - optional int64 field_int64 = 1; - optional int32 field_int32 = 2; - } - repeated Annotation annotation = 7; - - enum DumpReportReason { - DEVICE_SHUTDOWN = 1; - CONFIG_UPDATED = 2; - CONFIG_REMOVED = 3; - GET_DATA_CALLED = 4; - ADB_DUMP = 5; - CONFIG_RESET = 6; - STATSCOMPANION_DIED = 7; - TERMINATION_SIGNAL_RECEIVED = 8; - } - optional DumpReportReason dump_report_reason = 8; - - repeated string strings = 9; -} - -message ConfigMetricsReportList { - message ConfigKey { - optional int32 uid = 1; - optional int64 id = 2; - } - optional ConfigKey config_key = 1; - - repeated ConfigMetricsReport reports = 2; - - reserved 10; -} - -message StatsdStatsReport { - optional int32 stats_begin_time_sec = 1; - - optional int32 stats_end_time_sec = 2; - - message MatcherStats { - optional int64 id = 1; - optional int32 matched_times = 2; - } - - message ConditionStats { - optional int64 id = 1; - optional int32 max_tuple_counts = 2; - } - - message MetricStats { - optional int64 id = 1; - optional int32 max_tuple_counts = 2; - } - - message AlertStats { - optional int64 id = 1; - optional int32 alerted_times = 2; - } - - message ConfigStats { - optional int32 uid = 1; - optional int64 id = 2; - optional int32 creation_time_sec = 3; - optional int32 deletion_time_sec = 4; - optional int32 reset_time_sec = 19; - optional int32 metric_count = 5; - optional int32 condition_count = 6; - optional int32 matcher_count = 7; - optional int32 alert_count = 8; - optional bool is_valid = 9; - repeated int32 broadcast_sent_time_sec = 10; - repeated int32 data_drop_time_sec = 11; - repeated int64 data_drop_bytes = 21; - repeated int32 dump_report_time_sec = 12; - repeated int32 dump_report_data_size = 20; - repeated MatcherStats matcher_stats = 13; - repeated ConditionStats condition_stats = 14; - repeated MetricStats metric_stats = 15; - repeated AlertStats alert_stats = 16; - repeated MetricStats metric_dimension_in_condition_stats = 17 [deprecated = true]; - message Annotation { - optional int64 field_int64 = 1; - optional int32 field_int32 = 2; - } - repeated Annotation annotation = 18; - repeated int32 activation_time_sec = 22; - repeated int32 deactivation_time_sec = 23; - } - - repeated ConfigStats config_stats = 3; - - message AtomStats { - optional int32 tag = 1; - optional int32 count = 2; - optional int32 error_count = 3; - } - - repeated AtomStats atom_stats = 7; - - message UidMapStats { - optional int32 changes = 1; - optional int32 bytes_used = 2; - optional int32 dropped_changes = 3; - optional int32 deleted_apps = 4; - } - optional UidMapStats uidmap_stats = 8; - - message AnomalyAlarmStats { - optional int32 alarms_registered = 1; - } - optional AnomalyAlarmStats anomaly_alarm_stats = 9; - - message PulledAtomStats { - optional int32 atom_id = 1; - optional int64 total_pull = 2; - optional int64 total_pull_from_cache = 3; - optional int64 min_pull_interval_sec = 4; - optional int64 average_pull_time_nanos = 5; - optional int64 max_pull_time_nanos = 6; - optional int64 average_pull_delay_nanos = 7; - optional int64 max_pull_delay_nanos = 8; - optional int64 data_error = 9; - optional int64 pull_timeout = 10; - optional int64 pull_exceed_max_delay = 11; - optional int64 pull_failed = 12; - optional int64 stats_companion_pull_failed = 13 [deprecated = true]; - optional int64 stats_companion_pull_binder_transaction_failed = 14 [deprecated = true]; - optional int64 empty_data = 15; - optional int64 registered_count = 16; - optional int64 unregistered_count = 17; - optional int32 atom_error_count = 18; - optional int64 binder_call_failed = 19; - optional int64 failed_uid_provider_not_found = 20; - optional int64 puller_not_found = 21; - message PullTimeoutMetadata { - optional int64 pull_timeout_uptime_millis = 1; - optional int64 pull_timeout_elapsed_millis = 2; - } - repeated PullTimeoutMetadata pull_atom_metadata = 22; - } - repeated PulledAtomStats pulled_atom_stats = 10; - - message AtomMetricStats { - optional int64 metric_id = 1; - optional int64 hard_dimension_limit_reached = 2; - optional int64 late_log_event_skipped = 3; - optional int64 skipped_forward_buckets = 4; - optional int64 bad_value_type = 5; - optional int64 condition_change_in_next_bucket = 6; - optional int64 invalidated_bucket = 7; - optional int64 bucket_dropped = 8; - optional int64 min_bucket_boundary_delay_ns = 9; - optional int64 max_bucket_boundary_delay_ns = 10; - optional int64 bucket_unknown_condition = 11; - optional int64 bucket_count = 12; - optional int64 late_log_event = 13; - optional int64 sum_late_log_event_extra_duration_ns = 14; - optional int64 max_late_log_event_extra_duration_ns = 15; - } - repeated AtomMetricStats atom_metric_stats = 17; - - message LoggerErrorStats { - optional int32 logger_disconnection_sec = 1; - optional int32 error_code = 2; - } - repeated LoggerErrorStats logger_error_stats = 11; - - message PeriodicAlarmStats { - optional int32 alarms_registered = 1; - } - optional PeriodicAlarmStats periodic_alarm_stats = 12; - - message SkippedLogEventStats { - optional int32 tag = 1; - optional int64 elapsed_timestamp_nanos = 2; - } - repeated SkippedLogEventStats skipped_log_event_stats = 13; - - repeated int64 log_loss_stats = 14; - - repeated int32 system_restart_sec = 15; - - message LogLossStats { - optional int32 detected_time_sec = 1; - optional int32 count = 2; - optional int32 last_error = 3; - optional int32 last_tag = 4; - optional int32 uid = 5; - optional int32 pid = 6; - } - repeated LogLossStats detected_log_loss = 16; - - message EventQueueOverflow { - optional int32 count = 1; - optional int64 max_queue_history_ns = 2; - optional int64 min_queue_history_ns = 3; - } - - optional EventQueueOverflow queue_overflow = 18; - - message ActivationBroadcastGuardrail { - optional int32 uid = 1; - repeated int32 guardrail_met_sec = 2; - } - - repeated ActivationBroadcastGuardrail activation_guardrail_stats = 19; -} - -message AlertTriggerDetails { - message MetricValue { - optional int64 metric_id = 1; - optional DimensionsValue dimension_in_what = 2; - optional DimensionsValue dimension_in_condition = 3 [deprecated = true]; - optional int64 value = 4; - } - oneof value { - MetricValue trigger_metric = 1; - EventMetricData trigger_event = 2; - } - optional UidMapping.PackageInfoSnapshot package_info = 3; -} diff --git a/bin/src/stats_log_util.cpp b/bin/src/stats_log_util.cpp deleted file mode 100644 index a7e4d703..00000000 --- a/bin/src/stats_log_util.cpp +++ /dev/null @@ -1,625 +0,0 @@ -/* - * 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 "hash.h" -#include "stats_log_util.h" - -#include -#include -#include -#include - -#include "statscompanion_util.h" - -using android::util::FIELD_COUNT_REPEATED; -using android::util::FIELD_TYPE_BOOL; -using android::util::FIELD_TYPE_FIXED64; -using android::util::FIELD_TYPE_FLOAT; -using android::util::FIELD_TYPE_INT32; -using android::util::FIELD_TYPE_INT64; -using android::util::FIELD_TYPE_MESSAGE; -using android::util::FIELD_TYPE_STRING; -using android::util::FIELD_TYPE_UINT64; -using android::util::ProtoOutputStream; - -using aidl::android::os::IStatsCompanionService; -using std::shared_ptr; -using std::string; - -namespace android { -namespace os { -namespace statsd { - -// for DimensionsValue Proto -const int DIMENSIONS_VALUE_FIELD = 1; -const int DIMENSIONS_VALUE_VALUE_STR = 2; -const int DIMENSIONS_VALUE_VALUE_INT = 3; -const int DIMENSIONS_VALUE_VALUE_LONG = 4; -// const int DIMENSIONS_VALUE_VALUE_BOOL = 5; // logd doesn't have bool data type. -const int DIMENSIONS_VALUE_VALUE_FLOAT = 6; -const int DIMENSIONS_VALUE_VALUE_TUPLE = 7; -const int DIMENSIONS_VALUE_VALUE_STR_HASH = 8; - -const int DIMENSIONS_VALUE_TUPLE_VALUE = 1; - -// for StateValue Proto -const int STATE_VALUE_ATOM_ID = 1; -const int STATE_VALUE_CONTENTS_GROUP_ID = 2; -const int STATE_VALUE_CONTENTS_VALUE = 3; - -// for PulledAtomStats proto -const int FIELD_ID_PULLED_ATOM_STATS = 10; -const int FIELD_ID_PULL_ATOM_ID = 1; -const int FIELD_ID_TOTAL_PULL = 2; -const int FIELD_ID_TOTAL_PULL_FROM_CACHE = 3; -const int FIELD_ID_MIN_PULL_INTERVAL_SEC = 4; -const int FIELD_ID_AVERAGE_PULL_TIME_NANOS = 5; -const int FIELD_ID_MAX_PULL_TIME_NANOS = 6; -const int FIELD_ID_AVERAGE_PULL_DELAY_NANOS = 7; -const int FIELD_ID_MAX_PULL_DELAY_NANOS = 8; -const int FIELD_ID_DATA_ERROR = 9; -const int FIELD_ID_PULL_TIMEOUT = 10; -const int FIELD_ID_PULL_EXCEED_MAX_DELAY = 11; -const int FIELD_ID_PULL_FAILED = 12; -const int FIELD_ID_EMPTY_DATA = 15; -const int FIELD_ID_PULL_REGISTERED_COUNT = 16; -const int FIELD_ID_PULL_UNREGISTERED_COUNT = 17; -const int FIELD_ID_ATOM_ERROR_COUNT = 18; -const int FIELD_ID_BINDER_CALL_FAIL_COUNT = 19; -const int FIELD_ID_PULL_UID_PROVIDER_NOT_FOUND = 20; -const int FIELD_ID_PULLER_NOT_FOUND = 21; -const int FIELD_ID_PULL_TIMEOUT_METADATA = 22; -const int FIELD_ID_PULL_TIMEOUT_METADATA_UPTIME_MILLIS = 1; -const int FIELD_ID_PULL_TIMEOUT_METADATA_ELAPSED_MILLIS = 2; - -// for AtomMetricStats proto -const int FIELD_ID_ATOM_METRIC_STATS = 17; -const int FIELD_ID_METRIC_ID = 1; -const int FIELD_ID_HARD_DIMENSION_LIMIT_REACHED = 2; -const int FIELD_ID_LATE_LOG_EVENT_SKIPPED = 3; -const int FIELD_ID_SKIPPED_FORWARD_BUCKETS = 4; -const int FIELD_ID_BAD_VALUE_TYPE = 5; -const int FIELD_ID_CONDITION_CHANGE_IN_NEXT_BUCKET = 6; -const int FIELD_ID_INVALIDATED_BUCKET = 7; -const int FIELD_ID_BUCKET_DROPPED = 8; -const int FIELD_ID_MIN_BUCKET_BOUNDARY_DELAY_NS = 9; -const int FIELD_ID_MAX_BUCKET_BOUNDARY_DELAY_NS = 10; -const int FIELD_ID_BUCKET_UNKNOWN_CONDITION = 11; -const int FIELD_ID_BUCKET_COUNT = 12; -const int FIELD_ID_LATE_LOG_EVENT = 13; -const int FIELD_ID_SUM_LATE_LOG_EVENT_EXTRA_DURATION_NS = 14; -const int FIELD_ID_MAX_LATE_LOG_EVENT_EXTRA_DURATION_NS = 15; - -namespace { - -void writeDimensionToProtoHelper(const std::vector& dims, size_t* index, int depth, - int prefix, std::set *str_set, - ProtoOutputStream* protoOutput) { - size_t count = dims.size(); - while (*index < count) { - const auto& dim = dims[*index]; - const int valueDepth = dim.mField.getDepth(); - const int valuePrefix = dim.mField.getPrefix(depth); - const int fieldNum = dim.mField.getPosAtDepth(depth); - if (valueDepth > 2) { - ALOGE("Depth > 2 not supported"); - return; - } - - if (depth == valueDepth && valuePrefix == prefix) { - uint64_t token = protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | - DIMENSIONS_VALUE_TUPLE_VALUE); - protoOutput->write(FIELD_TYPE_INT32 | DIMENSIONS_VALUE_FIELD, fieldNum); - switch (dim.mValue.getType()) { - case INT: - protoOutput->write(FIELD_TYPE_INT32 | DIMENSIONS_VALUE_VALUE_INT, - dim.mValue.int_value); - break; - case LONG: - protoOutput->write(FIELD_TYPE_INT64 | DIMENSIONS_VALUE_VALUE_LONG, - (long long)dim.mValue.long_value); - break; - case FLOAT: - protoOutput->write(FIELD_TYPE_FLOAT | DIMENSIONS_VALUE_VALUE_FLOAT, - dim.mValue.float_value); - break; - case STRING: - if (str_set == nullptr) { - protoOutput->write(FIELD_TYPE_STRING | DIMENSIONS_VALUE_VALUE_STR, - dim.mValue.str_value); - } else { - str_set->insert(dim.mValue.str_value); - protoOutput->write( - FIELD_TYPE_UINT64 | DIMENSIONS_VALUE_VALUE_STR_HASH, - (long long)Hash64(dim.mValue.str_value)); - } - break; - default: - break; - } - if (token != 0) { - protoOutput->end(token); - } - (*index)++; - } else if (valueDepth > depth && valuePrefix == prefix) { - // Writing the sub tree - uint64_t dimensionToken = protoOutput->start( - FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | DIMENSIONS_VALUE_TUPLE_VALUE); - protoOutput->write(FIELD_TYPE_INT32 | DIMENSIONS_VALUE_FIELD, fieldNum); - uint64_t tupleToken = - protoOutput->start(FIELD_TYPE_MESSAGE | DIMENSIONS_VALUE_VALUE_TUPLE); - writeDimensionToProtoHelper(dims, index, valueDepth, dim.mField.getPrefix(valueDepth), - str_set, protoOutput); - protoOutput->end(tupleToken); - protoOutput->end(dimensionToken); - } else { - // Done with the prev sub tree - return; - } - } -} - -void writeDimensionLeafToProtoHelper(const std::vector& dims, - const int dimensionLeafField, - size_t* index, int depth, - int prefix, std::set *str_set, - ProtoOutputStream* protoOutput) { - size_t count = dims.size(); - while (*index < count) { - const auto& dim = dims[*index]; - const int valueDepth = dim.mField.getDepth(); - const int valuePrefix = dim.mField.getPrefix(depth); - if (valueDepth > 2) { - ALOGE("Depth > 2 not supported"); - return; - } - - if (depth == valueDepth && valuePrefix == prefix) { - uint64_t token = protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | - dimensionLeafField); - switch (dim.mValue.getType()) { - case INT: - protoOutput->write(FIELD_TYPE_INT32 | DIMENSIONS_VALUE_VALUE_INT, - dim.mValue.int_value); - break; - case LONG: - protoOutput->write(FIELD_TYPE_INT64 | DIMENSIONS_VALUE_VALUE_LONG, - (long long)dim.mValue.long_value); - break; - case FLOAT: - protoOutput->write(FIELD_TYPE_FLOAT | DIMENSIONS_VALUE_VALUE_FLOAT, - dim.mValue.float_value); - break; - case STRING: - if (str_set == nullptr) { - protoOutput->write(FIELD_TYPE_STRING | DIMENSIONS_VALUE_VALUE_STR, - dim.mValue.str_value); - } else { - str_set->insert(dim.mValue.str_value); - protoOutput->write( - FIELD_TYPE_UINT64 | DIMENSIONS_VALUE_VALUE_STR_HASH, - (long long)Hash64(dim.mValue.str_value)); - } - break; - default: - break; - } - if (token != 0) { - protoOutput->end(token); - } - (*index)++; - } else if (valueDepth > depth && valuePrefix == prefix) { - writeDimensionLeafToProtoHelper(dims, dimensionLeafField, - index, valueDepth, dim.mField.getPrefix(valueDepth), - str_set, protoOutput); - } else { - // Done with the prev sub tree - return; - } - } -} - -void writeDimensionPathToProtoHelper(const std::vector& fieldMatchers, - size_t* index, int depth, int prefix, - ProtoOutputStream* protoOutput) { - size_t count = fieldMatchers.size(); - while (*index < count) { - const Field& field = fieldMatchers[*index].mMatcher; - const int valueDepth = field.getDepth(); - const int valuePrefix = field.getPrefix(depth); - const int fieldNum = field.getPosAtDepth(depth); - if (valueDepth > 2) { - ALOGE("Depth > 2 not supported"); - return; - } - - if (depth == valueDepth && valuePrefix == prefix) { - uint64_t token = protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | - DIMENSIONS_VALUE_TUPLE_VALUE); - protoOutput->write(FIELD_TYPE_INT32 | DIMENSIONS_VALUE_FIELD, fieldNum); - if (token != 0) { - protoOutput->end(token); - } - (*index)++; - } else if (valueDepth > depth && valuePrefix == prefix) { - // Writing the sub tree - uint64_t dimensionToken = protoOutput->start( - FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | DIMENSIONS_VALUE_TUPLE_VALUE); - protoOutput->write(FIELD_TYPE_INT32 | DIMENSIONS_VALUE_FIELD, fieldNum); - uint64_t tupleToken = - protoOutput->start(FIELD_TYPE_MESSAGE | DIMENSIONS_VALUE_VALUE_TUPLE); - writeDimensionPathToProtoHelper(fieldMatchers, index, valueDepth, - field.getPrefix(valueDepth), protoOutput); - protoOutput->end(tupleToken); - protoOutput->end(dimensionToken); - } else { - // Done with the prev sub tree - return; - } - } -} - -} // namespace - -void writeDimensionToProto(const HashableDimensionKey& dimension, std::set *str_set, - ProtoOutputStream* protoOutput) { - if (dimension.getValues().size() == 0) { - return; - } - protoOutput->write(FIELD_TYPE_INT32 | DIMENSIONS_VALUE_FIELD, - dimension.getValues()[0].mField.getTag()); - uint64_t topToken = protoOutput->start(FIELD_TYPE_MESSAGE | DIMENSIONS_VALUE_VALUE_TUPLE); - size_t index = 0; - writeDimensionToProtoHelper(dimension.getValues(), &index, 0, 0, str_set, protoOutput); - protoOutput->end(topToken); -} - -void writeDimensionLeafNodesToProto(const HashableDimensionKey& dimension, - const int dimensionLeafFieldId, - std::set *str_set, - ProtoOutputStream* protoOutput) { - if (dimension.getValues().size() == 0) { - return; - } - size_t index = 0; - writeDimensionLeafToProtoHelper(dimension.getValues(), dimensionLeafFieldId, - &index, 0, 0, str_set, protoOutput); -} - -void writeDimensionPathToProto(const std::vector& fieldMatchers, - ProtoOutputStream* protoOutput) { - if (fieldMatchers.size() == 0) { - return; - } - protoOutput->write(FIELD_TYPE_INT32 | DIMENSIONS_VALUE_FIELD, - fieldMatchers[0].mMatcher.getTag()); - uint64_t topToken = protoOutput->start(FIELD_TYPE_MESSAGE | DIMENSIONS_VALUE_VALUE_TUPLE); - size_t index = 0; - writeDimensionPathToProtoHelper(fieldMatchers, &index, 0, 0, protoOutput); - protoOutput->end(topToken); -} - -// Supported Atoms format -// XYZ_Atom { -// repeated SubMsg field_1 = 1; -// SubMsg2 field_2 = 2; -// int32/float/string/int63 field_3 = 3; -// } -// logd's msg format, doesn't allow us to distinguish between the 2 cases below -// Case (1): -// Atom { -// SubMsg { -// int i = 1; -// int j = 2; -// } -// repeated SubMsg -// } -// -// and case (2): -// Atom { -// SubMsg { -// repeated int i = 1; -// repeated int j = 2; -// } -// optional SubMsg = 1; -// } -// -// -void writeFieldValueTreeToStreamHelper(int tagId, const std::vector& dims, - size_t* index, int depth, int prefix, - ProtoOutputStream* protoOutput) { - size_t count = dims.size(); - while (*index < count) { - const auto& dim = dims[*index]; - const int valueDepth = dim.mField.getDepth(); - const int valuePrefix = dim.mField.getPrefix(depth); - const int fieldNum = dim.mField.getPosAtDepth(depth); - if (valueDepth > 2) { - ALOGE("Depth > 2 not supported"); - return; - } - - if (depth == valueDepth && valuePrefix == prefix) { - switch (dim.mValue.getType()) { - case INT: - protoOutput->write(FIELD_TYPE_INT32 | fieldNum, dim.mValue.int_value); - break; - case LONG: - protoOutput->write(FIELD_TYPE_INT64 | fieldNum, - (long long)dim.mValue.long_value); - break; - case FLOAT: - protoOutput->write(FIELD_TYPE_FLOAT | fieldNum, dim.mValue.float_value); - break; - case STRING: { - protoOutput->write(FIELD_TYPE_STRING | fieldNum, dim.mValue.str_value); - break; - } - case STORAGE: - protoOutput->write(FIELD_TYPE_MESSAGE | fieldNum, - (const char*)dim.mValue.storage_value.data(), - dim.mValue.storage_value.size()); - break; - default: - break; - } - (*index)++; - } else if (valueDepth > depth && valuePrefix == prefix) { - // Writing the sub tree - uint64_t msg_token = 0ULL; - if (valueDepth == depth + 2) { - msg_token = - protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | fieldNum); - } else if (valueDepth == depth + 1) { - msg_token = protoOutput->start(FIELD_TYPE_MESSAGE | fieldNum); - } - // Directly jump to the leaf value because the repeated position field is implied - // by the position of the sub msg in the parent field. - writeFieldValueTreeToStreamHelper(tagId, dims, index, valueDepth, - dim.mField.getPrefix(valueDepth), protoOutput); - if (msg_token != 0) { - protoOutput->end(msg_token); - } - } else { - // Done with the prev sub tree - return; - } - } -} - -void writeFieldValueTreeToStream(int tagId, const std::vector& values, - util::ProtoOutputStream* protoOutput) { - uint64_t atomToken = protoOutput->start(FIELD_TYPE_MESSAGE | tagId); - - size_t index = 0; - writeFieldValueTreeToStreamHelper(tagId, values, &index, 0, 0, protoOutput); - protoOutput->end(atomToken); -} - -void writeStateToProto(const FieldValue& state, util::ProtoOutputStream* protoOutput) { - protoOutput->write(FIELD_TYPE_INT32 | STATE_VALUE_ATOM_ID, state.mField.getTag()); - - switch (state.mValue.getType()) { - case INT: - protoOutput->write(FIELD_TYPE_INT32 | STATE_VALUE_CONTENTS_VALUE, - state.mValue.int_value); - break; - case LONG: - protoOutput->write(FIELD_TYPE_INT64 | STATE_VALUE_CONTENTS_GROUP_ID, - state.mValue.long_value); - break; - default: - break; - } -} - -int64_t TimeUnitToBucketSizeInMillisGuardrailed(int uid, TimeUnit unit) { - int64_t bucketSizeMillis = TimeUnitToBucketSizeInMillis(unit); - if (bucketSizeMillis > 1000 && bucketSizeMillis < 5 * 60 * 1000LL && uid != AID_SHELL && - uid != AID_ROOT) { - bucketSizeMillis = 5 * 60 * 1000LL; - } - return bucketSizeMillis; -} - -int64_t TimeUnitToBucketSizeInMillis(TimeUnit unit) { - switch (unit) { - case ONE_MINUTE: - return 60 * 1000LL; - case FIVE_MINUTES: - return 5 * 60 * 1000LL; - case TEN_MINUTES: - return 10 * 60 * 1000LL; - case THIRTY_MINUTES: - return 30 * 60 * 1000LL; - case ONE_HOUR: - return 60 * 60 * 1000LL; - case THREE_HOURS: - return 3 * 60 * 60 * 1000LL; - case SIX_HOURS: - return 6 * 60 * 60 * 1000LL; - case TWELVE_HOURS: - return 12 * 60 * 60 * 1000LL; - case ONE_DAY: - return 24 * 60 * 60 * 1000LL; - case ONE_WEEK: - return 7 * 24 * 60 * 60 * 1000LL; - case CTS: - return 1000; - case TIME_UNIT_UNSPECIFIED: - default: - return -1; - } -} - -void writeNonZeroStatToStream(const uint64_t fieldId, const int64_t value, - util::ProtoOutputStream* protoOutput) { - if (value != 0) { - protoOutput->write(fieldId, value); - } -} - -void writePullerStatsToStream(const std::pair& pair, - util::ProtoOutputStream* protoOutput) { - uint64_t token = protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_ID_PULLED_ATOM_STATS | - FIELD_COUNT_REPEATED); - protoOutput->write(FIELD_TYPE_INT32 | FIELD_ID_PULL_ATOM_ID, (int32_t)pair.first); - protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_TOTAL_PULL, (long long)pair.second.totalPull); - protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_TOTAL_PULL_FROM_CACHE, - (long long)pair.second.totalPullFromCache); - protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_MIN_PULL_INTERVAL_SEC, - (long long)pair.second.minPullIntervalSec); - protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_AVERAGE_PULL_TIME_NANOS, - (long long)pair.second.avgPullTimeNs); - protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_MAX_PULL_TIME_NANOS, - (long long)pair.second.maxPullTimeNs); - protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_AVERAGE_PULL_DELAY_NANOS, - (long long)pair.second.avgPullDelayNs); - protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_MAX_PULL_DELAY_NANOS, - (long long)pair.second.maxPullDelayNs); - protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_DATA_ERROR, (long long)pair.second.dataError); - protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_PULL_TIMEOUT, - (long long)pair.second.pullTimeout); - protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_PULL_EXCEED_MAX_DELAY, - (long long)pair.second.pullExceedMaxDelay); - protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_PULL_FAILED, (long long)pair.second.pullFailed); - protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_EMPTY_DATA, (long long)pair.second.emptyData); - protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_PULL_REGISTERED_COUNT, - (long long)pair.second.registeredCount); - protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_PULL_UNREGISTERED_COUNT, - (long long)pair.second.unregisteredCount); - protoOutput->write(FIELD_TYPE_INT32 | FIELD_ID_ATOM_ERROR_COUNT, pair.second.atomErrorCount); - protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_BINDER_CALL_FAIL_COUNT, - (long long)pair.second.binderCallFailCount); - protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_PULL_UID_PROVIDER_NOT_FOUND, - (long long)pair.second.pullUidProviderNotFound); - protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_PULLER_NOT_FOUND, - (long long)pair.second.pullerNotFound); - for (const auto& pullTimeoutMetadata : pair.second.pullTimeoutMetadata) { - uint64_t timeoutMetadataToken = protoOutput->start(FIELD_TYPE_MESSAGE | - FIELD_ID_PULL_TIMEOUT_METADATA | - FIELD_COUNT_REPEATED); - protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_PULL_TIMEOUT_METADATA_UPTIME_MILLIS, - pullTimeoutMetadata.pullTimeoutUptimeMillis); - protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_PULL_TIMEOUT_METADATA_ELAPSED_MILLIS, - pullTimeoutMetadata.pullTimeoutElapsedMillis); - protoOutput->end(timeoutMetadataToken); - } - protoOutput->end(token); -} - -void writeAtomMetricStatsToStream(const std::pair &pair, - util::ProtoOutputStream *protoOutput) { - uint64_t token = protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_ID_ATOM_METRIC_STATS | - FIELD_COUNT_REPEATED); - - writeNonZeroStatToStream(FIELD_TYPE_INT64 | FIELD_ID_METRIC_ID, (long long)pair.first, - protoOutput); - writeNonZeroStatToStream(FIELD_TYPE_INT64 | FIELD_ID_HARD_DIMENSION_LIMIT_REACHED, - (long long)pair.second.hardDimensionLimitReached, protoOutput); - writeNonZeroStatToStream(FIELD_TYPE_INT64 | FIELD_ID_LATE_LOG_EVENT_SKIPPED, - (long long)pair.second.lateLogEventSkipped, protoOutput); - writeNonZeroStatToStream(FIELD_TYPE_INT64 | FIELD_ID_SKIPPED_FORWARD_BUCKETS, - (long long)pair.second.skippedForwardBuckets, protoOutput); - writeNonZeroStatToStream(FIELD_TYPE_INT64 | FIELD_ID_BAD_VALUE_TYPE, - (long long)pair.second.badValueType, protoOutput); - writeNonZeroStatToStream(FIELD_TYPE_INT64 | FIELD_ID_CONDITION_CHANGE_IN_NEXT_BUCKET, - (long long)pair.second.conditionChangeInNextBucket, protoOutput); - writeNonZeroStatToStream(FIELD_TYPE_INT64 | FIELD_ID_INVALIDATED_BUCKET, - (long long)pair.second.invalidatedBucket, protoOutput); - writeNonZeroStatToStream(FIELD_TYPE_INT64 | FIELD_ID_BUCKET_DROPPED, - (long long)pair.second.bucketDropped, protoOutput); - writeNonZeroStatToStream(FIELD_TYPE_INT64 | FIELD_ID_MIN_BUCKET_BOUNDARY_DELAY_NS, - (long long)pair.second.minBucketBoundaryDelayNs, protoOutput); - writeNonZeroStatToStream(FIELD_TYPE_INT64 | FIELD_ID_MAX_BUCKET_BOUNDARY_DELAY_NS, - (long long)pair.second.maxBucketBoundaryDelayNs, protoOutput); - writeNonZeroStatToStream(FIELD_TYPE_INT64 | FIELD_ID_BUCKET_UNKNOWN_CONDITION, - (long long)pair.second.bucketUnknownCondition, protoOutput); - writeNonZeroStatToStream(FIELD_TYPE_INT64 | FIELD_ID_BUCKET_COUNT, - (long long)pair.second.bucketCount, protoOutput); - writeNonZeroStatToStream(FIELD_TYPE_INT64 | FIELD_ID_LATE_LOG_EVENT, - (long long)pair.second.lateLogEvent, protoOutput); - writeNonZeroStatToStream(FIELD_TYPE_INT64 | FIELD_ID_SUM_LATE_LOG_EVENT_EXTRA_DURATION_NS, - (long long)pair.second.sumLateLogEventExtraDurationNs, protoOutput); - writeNonZeroStatToStream(FIELD_TYPE_INT64 | FIELD_ID_MAX_LATE_LOG_EVENT_EXTRA_DURATION_NS, - (long long)pair.second.maxLateLogEventExtraDurationNs, protoOutput); - protoOutput->end(token); -} - -int64_t getElapsedRealtimeNs() { - return ::android::elapsedRealtimeNano(); -} - -int64_t getElapsedRealtimeSec() { - return ::android::elapsedRealtimeNano() / NS_PER_SEC; -} - -int64_t getElapsedRealtimeMillis() { - return ::android::elapsedRealtime(); -} - -int64_t getSystemUptimeMillis() { - return ::android::uptimeMillis(); -} - -int64_t getWallClockNs() { - return time(nullptr) * NS_PER_SEC; -} - -int64_t getWallClockSec() { - return time(nullptr); -} - -int64_t getWallClockMillis() { - return time(nullptr) * MS_PER_SEC; -} - -int64_t truncateTimestampIfNecessary(const LogEvent& event) { - if (event.shouldTruncateTimestamp() || - (event.GetTagId() >= StatsdStats::kTimestampTruncationStartTag && - event.GetTagId() <= StatsdStats::kTimestampTruncationEndTag)) { - return event.GetElapsedTimestampNs() / NS_PER_SEC / (5 * 60) * NS_PER_SEC * (5 * 60); - } else { - return event.GetElapsedTimestampNs(); - } -} - -int64_t NanoToMillis(const int64_t nano) { - return nano / 1000000; -} - -int64_t MillisToNano(const int64_t millis) { - return millis * 1000000; -} - -bool checkPermissionForIds(const char* permission, pid_t pid, uid_t uid) { - shared_ptr scs = getStatsCompanionService(); - if (scs == nullptr) { - return false; - } - - bool success; - ::ndk::ScopedAStatus status = scs->checkPermission(string(permission), pid, uid, &success); - if (!status.isOk()) { - return false; - } - - return success; -} - -} // namespace statsd -} // namespace os -} // namespace android diff --git a/bin/src/stats_log_util.h b/bin/src/stats_log_util.h deleted file mode 100644 index 1ba4c4ae..00000000 --- a/bin/src/stats_log_util.h +++ /dev/null @@ -1,121 +0,0 @@ -/* - * 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. - */ - -#pragma once - -#include - -#include "FieldValue.h" -#include "HashableDimensionKey.h" -#include "packages/modules/StatsD/bin/src/statsd_config.pb.h" -#include "guardrail/StatsdStats.h" -#include "logd/LogEvent.h" - -using android::util::ProtoOutputStream; - -namespace android { -namespace os { -namespace statsd { - -void writeFieldValueTreeToStream(int tagId, const std::vector& values, - ProtoOutputStream* protoOutput); -void writeDimensionToProto(const HashableDimensionKey& dimension, std::set *str_set, - ProtoOutputStream* protoOutput); - -void writeDimensionLeafNodesToProto(const HashableDimensionKey& dimension, - const int dimensionLeafFieldId, - std::set *str_set, - ProtoOutputStream* protoOutput); - -void writeDimensionPathToProto(const std::vector& fieldMatchers, - ProtoOutputStream* protoOutput); - -void writeStateToProto(const FieldValue& state, ProtoOutputStream* protoOutput); - -// Convert the TimeUnit enum to the bucket size in millis with a guardrail on -// bucket size. -int64_t TimeUnitToBucketSizeInMillisGuardrailed(int uid, TimeUnit unit); - -// Convert the TimeUnit enum to the bucket size in millis. -int64_t TimeUnitToBucketSizeInMillis(TimeUnit unit); - -// Gets the elapsed timestamp in ns. -int64_t getElapsedRealtimeNs(); - -// Gets the elapsed timestamp in millis. -int64_t getElapsedRealtimeMillis(); - -// Gets the elapsed timestamp in seconds. -int64_t getElapsedRealtimeSec(); - -// Gets the system uptime in millis. -int64_t getSystemUptimeMillis(); - -// Gets the wall clock timestamp in ns. -int64_t getWallClockNs(); - -// Gets the wall clock timestamp in millis. -int64_t getWallClockMillis(); - -// Gets the wall clock timestamp in seconds. -int64_t getWallClockSec(); - -int64_t NanoToMillis(const int64_t nano); - -int64_t MillisToNano(const int64_t millis); - -// Helper function to write a stats field to ProtoOutputStream if it's a non-zero value. -void writeNonZeroStatToStream(const uint64_t fieldId, const int64_t value, - ProtoOutputStream* protoOutput); - -// Helper function to write PulledAtomStats to ProtoOutputStream -void writePullerStatsToStream(const std::pair& pair, - ProtoOutputStream* protoOutput); - -// Helper function to write AtomMetricStats to ProtoOutputStream -void writeAtomMetricStatsToStream(const std::pair &pair, - ProtoOutputStream *protoOutput); - -template -bool parseProtoOutputStream(ProtoOutputStream& protoOutput, T* message) { - std::string pbBytes; - sp reader = protoOutput.data(); - while (reader->readBuffer() != NULL) { - size_t toRead = reader->currentToRead(); - pbBytes.append(reinterpret_cast(reader->readBuffer()), toRead); - reader->move(toRead); - } - return message->ParseFromArray(pbBytes.c_str(), pbBytes.size()); -} - -// Checks the truncate timestamp annotation as well as the restricted range of 300,000 - 304,999. -// Returns the truncated timestamp to the nearest 5 minutes if needed. -int64_t truncateTimestampIfNecessary(const LogEvent& event); - -// Checks permission for given pid and uid. -bool checkPermissionForIds(const char* permission, pid_t pid, uid_t uid); - -inline bool isVendorPulledAtom(int atomId) { - return atomId >= StatsdStats::kVendorPulledAtomStartTag && atomId < StatsdStats::kMaxAtomTag; -} - -inline bool isPulledAtom(int atomId) { - return atomId >= StatsdStats::kPullAtomStartTag && atomId < StatsdStats::kVendorAtomStartTag; -} - -} // namespace statsd -} // namespace os -} // namespace android diff --git a/bin/src/stats_util.h b/bin/src/stats_util.h deleted file mode 100644 index c60babcc..00000000 --- a/bin/src/stats_util.h +++ /dev/null @@ -1,46 +0,0 @@ -/* - * 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. - */ - -#pragma once - -#include "HashableDimensionKey.h" - -#include - -namespace android { -namespace os { -namespace statsd { - -// Possible update states for a component. PRESERVE means we should keep the existing one. -// REPLACE means we should create a new one because the existing one changed -// NEW means we should create a new one because one does not currently exist. -enum UpdateStatus { - UPDATE_UNKNOWN = 0, - UPDATE_PRESERVE = 1, - UPDATE_REPLACE = 2, - UPDATE_NEW = 3, -}; - -const HashableDimensionKey DEFAULT_DIMENSION_KEY = HashableDimensionKey(); -const MetricDimensionKey DEFAULT_METRIC_DIMENSION_KEY = MetricDimensionKey(); - -typedef std::map ConditionKey; - -typedef std::unordered_map DimToValMap; - -} // namespace statsd -} // namespace os -} // namespace android diff --git a/bin/src/statscompanion_util.cpp b/bin/src/statscompanion_util.cpp deleted file mode 100644 index ce07ec0e..00000000 --- a/bin/src/statscompanion_util.cpp +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright (C) 2018 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. - */ - -#define DEBUG false // STOPSHIP if true -#include "Log.h" - -#include "statscompanion_util.h" -#include -#include - -namespace android { -namespace os { -namespace statsd { - -shared_ptr getStatsCompanionService() { - ::ndk::SpAIBinder binder(AServiceManager_getService("statscompanion")); - return IStatsCompanionService::fromBinder(binder); -} - -} // namespace statsd -} // namespace os -} // namespace android diff --git a/bin/src/statscompanion_util.h b/bin/src/statscompanion_util.h deleted file mode 100644 index e20c40bb..00000000 --- a/bin/src/statscompanion_util.h +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright (C) 2018 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. - */ - -#pragma once - -#include - -using aidl::android::os::IStatsCompanionService; -using std::shared_ptr; - -namespace android { -namespace os { -namespace statsd { - -/** Fetches and returns the StatsCompanionService. */ -shared_ptr getStatsCompanionService(); - -} // namespace statsd -} // namespace os -} // namespace android diff --git a/bin/src/statsd_config.proto b/bin/src/statsd_config.proto deleted file mode 100644 index 53bcba29..00000000 --- a/bin/src/statsd_config.proto +++ /dev/null @@ -1,524 +0,0 @@ -/* - * 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. - */ - -syntax = "proto2"; - -package android.os.statsd; - -option java_package = "com.android.internal.os"; -option java_outer_classname = "StatsdConfigProto"; - -enum Position { - POSITION_UNKNOWN = 0; - - FIRST = 1; - - LAST = 2; - - ANY = 3; - - ALL = 4; -} - -enum TimeUnit { - TIME_UNIT_UNSPECIFIED = 0; - ONE_MINUTE = 1; // WILL BE GUARDRAILED TO 5 MINS UNLESS UID = SHELL OR ROOT - FIVE_MINUTES = 2; - TEN_MINUTES = 3; - THIRTY_MINUTES = 4; - ONE_HOUR = 5; - THREE_HOURS = 6; - SIX_HOURS = 7; - TWELVE_HOURS = 8; - ONE_DAY = 9; - ONE_WEEK = 10; - CTS = 1000; -} - -message FieldMatcher { - optional int32 field = 1; - - optional Position position = 2; - - repeated FieldMatcher child = 3; -} - -message FieldValueMatcher { - optional int32 field = 1; - - optional Position position = 2; - - oneof value_matcher { - bool eq_bool = 3; - string eq_string = 4; - int64 eq_int = 5; - - int64 lt_int = 6; - int64 gt_int = 7; - float lt_float = 8; - float gt_float = 9; - - int64 lte_int = 10; - int64 gte_int = 11; - - MessageMatcher matches_tuple = 12; - - StringListMatcher eq_any_string = 13; - StringListMatcher neq_any_string = 14; - } -} - -message MessageMatcher { - repeated FieldValueMatcher field_value_matcher = 1; -} - -message StringListMatcher { - repeated string str_value = 1; -} - -enum LogicalOperation { - LOGICAL_OPERATION_UNSPECIFIED = 0; - AND = 1; - OR = 2; - NOT = 3; - NAND = 4; - NOR = 5; -} - -message SimpleAtomMatcher { - optional int32 atom_id = 1; - - repeated FieldValueMatcher field_value_matcher = 2; -} - -message AtomMatcher { - optional int64 id = 1; - - message Combination { - optional LogicalOperation operation = 1; - - repeated int64 matcher = 2; - } - oneof contents { - SimpleAtomMatcher simple_atom_matcher = 2; - Combination combination = 3; - } -} - -message SimplePredicate { - optional int64 start = 1; - - optional int64 stop = 2; - - optional bool count_nesting = 3 [default = true]; - - optional int64 stop_all = 4; - - enum InitialValue { - UNKNOWN = 0; - FALSE = 1; - } - optional InitialValue initial_value = 5 [default = UNKNOWN]; - - optional FieldMatcher dimensions = 6; -} - -message Predicate { - optional int64 id = 1; - - message Combination { - optional LogicalOperation operation = 1; - - repeated int64 predicate = 2; - } - - oneof contents { - SimplePredicate simple_predicate = 2; - Combination combination = 3; - } -} - -message StateMap { - message StateGroup { - optional int64 group_id = 1; - - repeated int32 value = 2; - } - - repeated StateGroup group = 1; -} - -message State { - optional int64 id = 1; - - optional int32 atom_id = 2; - - optional StateMap map = 3; -} - -message MetricConditionLink { - optional int64 condition = 1; - - optional FieldMatcher fields_in_what = 2; - - optional FieldMatcher fields_in_condition = 3; -} - -message MetricStateLink { - optional int32 state_atom_id = 1; - - optional FieldMatcher fields_in_what = 2; - - optional FieldMatcher fields_in_state = 3; -} - -message FieldFilter { - optional bool include_all = 1 [default = false]; - optional FieldMatcher fields = 2; -} - -message UploadThreshold { - oneof value_comparison { - int64 lt_int = 1; - int64 gt_int = 2; - float lt_float = 3; - float gt_float = 4; - int64 lte_int = 5; - int64 gte_int = 6; - } -} - -message EventMetric { - optional int64 id = 1; - - optional int64 what = 2; - - optional int64 condition = 3; - - repeated MetricConditionLink links = 4; - - reserved 100; - reserved 101; -} - -message CountMetric { - optional int64 id = 1; - - optional int64 what = 2; - - optional int64 condition = 3; - - optional FieldMatcher dimensions_in_what = 4; - - repeated int64 slice_by_state = 8; - - optional TimeUnit bucket = 5; - - repeated MetricConditionLink links = 6; - - repeated MetricStateLink state_link = 9; - - optional FieldMatcher dimensions_in_condition = 7 [deprecated = true]; - - reserved 100; - reserved 101; -} - -message DurationMetric { - optional int64 id = 1; - - optional int64 what = 2; - - optional int64 condition = 3; - - repeated int64 slice_by_state = 9; - - repeated MetricConditionLink links = 4; - - repeated MetricStateLink state_link = 10; - - enum AggregationType { - SUM = 1; - - MAX_SPARSE = 2; - } - optional AggregationType aggregation_type = 5 [default = SUM]; - - optional FieldMatcher dimensions_in_what = 6; - - optional TimeUnit bucket = 7; - - optional UploadThreshold threshold = 11; - - optional FieldMatcher dimensions_in_condition = 8 [deprecated = true]; - - reserved 100; - reserved 101; -} - -message GaugeMetric { - optional int64 id = 1; - - optional int64 what = 2; - - optional int64 trigger_event = 12; - - optional FieldFilter gauge_fields_filter = 3; - - optional int64 condition = 4; - - optional FieldMatcher dimensions_in_what = 5; - - optional FieldMatcher dimensions_in_condition = 8 [deprecated = true]; - - optional TimeUnit bucket = 6; - - repeated MetricConditionLink links = 7; - - enum SamplingType { - RANDOM_ONE_SAMPLE = 1; - ALL_CONDITION_CHANGES = 2 [deprecated = true]; - CONDITION_CHANGE_TO_TRUE = 3; - FIRST_N_SAMPLES = 4; - } - optional SamplingType sampling_type = 9 [default = RANDOM_ONE_SAMPLE] ; - - optional int64 min_bucket_size_nanos = 10; - - optional int64 max_num_gauge_atoms_per_bucket = 11 [default = 10]; - - optional int32 max_pull_delay_sec = 13 [default = 30]; - - optional bool split_bucket_for_app_upgrade = 14 [default = true]; - - reserved 100; - reserved 101; -} - -message ValueMetric { - optional int64 id = 1; - - optional int64 what = 2; - - optional FieldMatcher value_field = 3; - - optional int64 condition = 4; - - optional FieldMatcher dimensions_in_what = 5; - - repeated int64 slice_by_state = 18; - - optional TimeUnit bucket = 6; - - repeated MetricConditionLink links = 7; - - repeated MetricStateLink state_link = 19; - - enum AggregationType { - SUM = 1; - MIN = 2; - MAX = 3; - AVG = 4; - } - optional AggregationType aggregation_type = 8 [default = SUM]; - - optional int64 min_bucket_size_nanos = 10; - - optional bool use_absolute_value_on_reset = 11 [default = false]; - - optional bool use_diff = 12; - - optional bool use_zero_default_base = 15 [default = false]; - - enum ValueDirection { - UNKNOWN = 0; - INCREASING = 1; - DECREASING = 2; - ANY = 3; - } - optional ValueDirection value_direction = 13 [default = INCREASING]; - - optional bool skip_zero_diff_output = 14 [default = true]; - - optional int32 max_pull_delay_sec = 16 [default = 30]; - - optional bool split_bucket_for_app_upgrade = 17 [default = true]; - - optional FieldMatcher dimensions_in_condition = 9 [deprecated = true]; - - reserved 100; - reserved 101; -} - -message Alert { - optional int64 id = 1; - - optional int64 metric_id = 2; - - optional int32 num_buckets = 3; - - optional int32 refractory_period_secs = 4; - - optional double trigger_if_sum_gt = 5; -} - -message Alarm { - optional int64 id = 1; - - optional int64 offset_millis = 2; - - optional int64 period_millis = 3; -} - -message IncidentdDetails { - repeated int32 section = 1; - - enum Destination { - AUTOMATIC = 0; - EXPLICIT = 1; - } - optional Destination dest = 2; - - // Package name of the incident report receiver. - optional string receiver_pkg = 3; - - // Class name of the incident report receiver. - optional string receiver_cls = 4; - - optional string alert_description = 5; -} - -message PerfettoDetails { - // The |trace_config| field is a proto-encoded message of type - // perfetto.protos.TraceConfig defined in - // //external/perfetto/protos/perfetto/config/. On device, - // statsd doesn't need to deserialize the message as it's just - // passed binary-encoded to the perfetto cmdline client. - optional bytes trace_config = 1; -} - -message BroadcastSubscriberDetails { - optional int64 subscriber_id = 1; - repeated string cookie = 2; -} - -message Subscription { - optional int64 id = 1; - - enum RuleType { - RULE_TYPE_UNSPECIFIED = 0; - ALARM = 1; - ALERT = 2; - } - optional RuleType rule_type = 2; - - optional int64 rule_id = 3; - - oneof subscriber_information { - IncidentdDetails incidentd_details = 4; - PerfettoDetails perfetto_details = 5; - BroadcastSubscriberDetails broadcast_subscriber_details = 6; - } - - optional float probability_of_informing = 7 [default = 1.1]; - - // This was used for perfprofd historically. - reserved 8; -} - -enum ActivationType { - ACTIVATION_TYPE_UNKNOWN = 0; - ACTIVATE_IMMEDIATELY = 1; - ACTIVATE_ON_BOOT = 2; -} - -message EventActivation { - optional int64 atom_matcher_id = 1; - optional int64 ttl_seconds = 2; - optional int64 deactivation_atom_matcher_id = 3; - optional ActivationType activation_type = 4; -} - -message MetricActivation { - optional int64 metric_id = 1; - - optional ActivationType activation_type = 3 [deprecated = true]; - - repeated EventActivation event_activation = 2; -} - -message PullAtomPackages { - optional int32 atom_id = 1; - - repeated string packages = 2; -} - -message StatsdConfig { - optional int64 id = 1; - - repeated EventMetric event_metric = 2; - - repeated CountMetric count_metric = 3; - - repeated ValueMetric value_metric = 4; - - repeated GaugeMetric gauge_metric = 5; - - repeated DurationMetric duration_metric = 6; - - repeated AtomMatcher atom_matcher = 7; - - repeated Predicate predicate = 8; - - repeated Alert alert = 9; - - repeated Alarm alarm = 10; - - repeated Subscription subscription = 11; - - repeated string allowed_log_source = 12; - - repeated int64 no_report_metric = 13; - - message Annotation { - optional int64 field_int64 = 1; - optional int32 field_int32 = 2; - } - repeated Annotation annotation = 14; - - optional int64 ttl_in_seconds = 15; - - optional bool hash_strings_in_metric_report = 16 [default = true]; - - repeated MetricActivation metric_activation = 17; - - optional bool version_strings_in_metric_report = 18; - - optional bool installer_in_metric_report = 19; - - optional bool persist_locally = 20 [default = false]; - - repeated State state = 21; - - repeated string default_pull_packages = 22; - - repeated PullAtomPackages pull_atom_packages = 23; - - repeated int32 whitelisted_atom_ids = 24; - - // Field number 1000 is reserved for later use. - reserved 1000; -} diff --git a/bin/src/statsd_metadata.proto b/bin/src/statsd_metadata.proto deleted file mode 100644 index 200b392f..00000000 --- a/bin/src/statsd_metadata.proto +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright (C) 2020 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. - */ - -syntax = "proto2"; - -package android.os.statsd.metadata; - -message ConfigKey { - optional int64 config_id = 1; - optional int32 uid = 2; -} - -message Field { - optional int32 tag = 1; - optional int32 field = 2; -} - -message FieldValue { - optional Field field = 1; - oneof value { - int32 value_int = 2; - int64 value_long = 3; - float value_float = 4; - double value_double = 5; - string value_str = 6; - bytes value_storage = 7; - } -} - -message MetricDimensionKey { - repeated FieldValue dimension_key_in_what = 1; - repeated FieldValue state_values_key = 2; -} - -message AlertDimensionKeyedData { - // The earliest time the alert can be fired again in wall clock time. - optional int32 last_refractory_ends_sec = 1; - optional MetricDimensionKey dimension_key = 2; -} - -message AlertMetadata { - optional int64 alert_id = 1; - repeated AlertDimensionKeyedData alert_dim_keyed_data = 2; -} - -// All metadata for a config in statsd -message StatsMetadata { - optional ConfigKey config_key = 1; - repeated AlertMetadata alert_metadata = 2; -} - -message StatsMetadataList { - repeated StatsMetadata stats_metadata = 1; -} \ No newline at end of file diff --git a/bin/src/storage/StorageManager.cpp b/bin/src/storage/StorageManager.cpp deleted file mode 100644 index 782b020b..00000000 --- a/bin/src/storage/StorageManager.cpp +++ /dev/null @@ -1,780 +0,0 @@ -/* - * 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. - */ - -#define DEBUG false // STOPSHIP if true -#include "Log.h" - -#include "android-base/stringprintf.h" -#include "guardrail/StatsdStats.h" -#include "storage/StorageManager.h" -#include "stats_log_util.h" - -#include -#include -#include - -namespace android { -namespace os { -namespace statsd { - -using android::util::FIELD_COUNT_REPEATED; -using android::util::FIELD_TYPE_MESSAGE; -using std::map; - -/** - * NOTE: these directories are protected by SELinux, any changes here must also update - * the SELinux policies. - */ -#define STATS_DATA_DIR "/data/misc/stats-data" -#define STATS_SERVICE_DIR "/data/misc/stats-service" -#define TRAIN_INFO_DIR "/data/misc/train-info" -#define TRAIN_INFO_PATH "/data/misc/train-info/train-info.bin" - -// Magic word at the start of the train info file, change this if changing the file format -const uint32_t TRAIN_INFO_FILE_MAGIC = 0xfb7447bf; - -// for ConfigMetricsReportList -const int FIELD_ID_REPORTS = 2; - -std::mutex StorageManager::sTrainInfoMutex; - -using android::base::StringPrintf; -using std::unique_ptr; - -struct FileName { - int64_t mTimestampSec; - int mUid; - int64_t mConfigId; - bool mIsHistory; - string getFullFileName(const char* path) { - return StringPrintf("%s/%lld_%d_%lld%s", path, (long long)mTimestampSec, (int)mUid, - (long long)mConfigId, (mIsHistory ? "_history" : "")); - }; -}; - -string StorageManager::getDataFileName(long wallClockSec, int uid, int64_t id) { - return StringPrintf("%s/%ld_%d_%lld", STATS_DATA_DIR, wallClockSec, uid, - (long long)id); -} - -string StorageManager::getDataHistoryFileName(long wallClockSec, int uid, int64_t id) { - return StringPrintf("%s/%ld_%d_%lld_history", STATS_DATA_DIR, wallClockSec, uid, - (long long)id); -} - -static string findTrainInfoFileNameLocked(const string& trainName) { - unique_ptr dir(opendir(TRAIN_INFO_DIR), closedir); - if (dir == NULL) { - VLOG("Path %s does not exist", TRAIN_INFO_DIR); - return ""; - } - dirent* de; - while ((de = readdir(dir.get()))) { - char* fileName = de->d_name; - if (fileName[0] == '.') continue; - - size_t fileNameLength = strlen(fileName); - if (fileNameLength >= trainName.length()) { - if (0 == strncmp(fileName + fileNameLength - trainName.length(), trainName.c_str(), - trainName.length())) { - return string(fileName); - } - } - } - - return ""; -} - -// Returns array of int64_t which contains timestamp in seconds, uid, -// configID and whether the file is a local history file. -static void parseFileName(char* name, FileName* output) { - int64_t result[3]; - int index = 0; - char* substr = strtok(name, "_"); - while (substr != nullptr && index < 3) { - result[index] = StrToInt64(substr); - index++; - substr = strtok(nullptr, "_"); - } - // When index ends before hitting 3, file name is corrupted. We - // intentionally put -1 at index 0 to indicate the error to caller. - // TODO(b/110563137): consider removing files with unexpected name format. - if (index < 3) { - result[0] = -1; - } - - output->mTimestampSec = result[0]; - output->mUid = result[1]; - output->mConfigId = result[2]; - // check if the file is a local history. - output->mIsHistory = (substr != nullptr && strcmp("history", substr) == 0); -} - -void StorageManager::writeFile(const char* file, const void* buffer, int numBytes) { - int fd = open(file, O_WRONLY | O_CREAT | O_CLOEXEC, S_IRUSR | S_IWUSR); - if (fd == -1) { - VLOG("Attempt to access %s but failed", file); - return; - } - trimToFit(STATS_SERVICE_DIR); - trimToFit(STATS_DATA_DIR); - - if (android::base::WriteFully(fd, buffer, numBytes)) { - VLOG("Successfully wrote %s", file); - } else { - ALOGE("Failed to write %s", file); - } - - int result = fchown(fd, AID_STATSD, AID_STATSD); - if (result) { - VLOG("Failed to chown %s to statsd", file); - } - - close(fd); -} - -bool StorageManager::writeTrainInfo(const InstallTrainInfo& trainInfo) { - std::lock_guard lock(sTrainInfoMutex); - - if (trainInfo.trainName.empty()) { - return false; - } - deleteSuffixedFiles(TRAIN_INFO_DIR, trainInfo.trainName.c_str()); - - std::string fileName = - StringPrintf("%s/%ld_%s", TRAIN_INFO_DIR, (long) getWallClockSec(), - trainInfo.trainName.c_str()); - - int fd = open(fileName.c_str(), O_WRONLY | O_CREAT | O_CLOEXEC, S_IRUSR | S_IWUSR); - if (fd == -1) { - VLOG("Attempt to access %s but failed", fileName.c_str()); - return false; - } - - size_t result; - // Write the magic word - result = write(fd, &TRAIN_INFO_FILE_MAGIC, sizeof(TRAIN_INFO_FILE_MAGIC)); - if (result != sizeof(TRAIN_INFO_FILE_MAGIC)) { - VLOG("Failed to wrtie train info magic"); - close(fd); - return false; - } - - // Write the train version - const size_t trainVersionCodeByteCount = sizeof(trainInfo.trainVersionCode); - result = write(fd, &trainInfo.trainVersionCode, trainVersionCodeByteCount); - if (result != trainVersionCodeByteCount) { - VLOG("Failed to wrtie train version code"); - close(fd); - return false; - } - - // Write # of bytes in trainName to file - const size_t trainNameSize = trainInfo.trainName.size(); - const size_t trainNameSizeByteCount = sizeof(trainNameSize); - result = write(fd, (uint8_t*)&trainNameSize, trainNameSizeByteCount); - if (result != trainNameSizeByteCount) { - VLOG("Failed to write train name size"); - close(fd); - return false; - } - - // Write trainName to file - result = write(fd, trainInfo.trainName.c_str(), trainNameSize); - if (result != trainNameSize) { - VLOG("Failed to write train name"); - close(fd); - return false; - } - - // Write status to file - const size_t statusByteCount = sizeof(trainInfo.status); - result = write(fd, (uint8_t*)&trainInfo.status, statusByteCount); - if (result != statusByteCount) { - VLOG("Failed to write status"); - close(fd); - return false; - } - - // Write experiment id count to file. - const size_t experimentIdsCount = trainInfo.experimentIds.size(); - const size_t experimentIdsCountByteCount = sizeof(experimentIdsCount); - result = write(fd, (uint8_t*) &experimentIdsCount, experimentIdsCountByteCount); - if (result != experimentIdsCountByteCount) { - VLOG("Failed to write experiment id count"); - close(fd); - return false; - } - - // Write experimentIds to file - for (size_t i = 0; i < experimentIdsCount; i++) { - const int64_t experimentId = trainInfo.experimentIds[i]; - const size_t experimentIdByteCount = sizeof(experimentId); - result = write(fd, &experimentId, experimentIdByteCount); - if (result == experimentIdByteCount) { - VLOG("Successfully wrote experiment IDs"); - } else { - VLOG("Failed to write experiment ids"); - close(fd); - return false; - } - } - - // Write bools to file - const size_t boolByteCount = sizeof(trainInfo.requiresStaging); - result = write(fd, (uint8_t*)&trainInfo.requiresStaging, boolByteCount); - if (result != boolByteCount) { - VLOG("Failed to write requires staging"); - close(fd); - return false; - } - - result = write(fd, (uint8_t*)&trainInfo.rollbackEnabled, boolByteCount); - if (result != boolByteCount) { - VLOG("Failed to write rollback enabled"); - close(fd); - return false; - } - - result = write(fd, (uint8_t*)&trainInfo.requiresLowLatencyMonitor, boolByteCount); - if (result != boolByteCount) { - VLOG("Failed to write requires log latency monitor"); - close(fd); - return false; - } - - close(fd); - return true; -} - -bool StorageManager::readTrainInfo(const std::string& trainName, InstallTrainInfo& trainInfo) { - std::lock_guard lock(sTrainInfoMutex); - return readTrainInfoLocked(trainName, trainInfo); -} - -bool StorageManager::readTrainInfoLocked(const std::string& trainName, InstallTrainInfo& trainInfo) { - trimToFit(TRAIN_INFO_DIR, /*parseTimestampOnly=*/ true); - string fileName = findTrainInfoFileNameLocked(trainName); - if (fileName.empty()) { - return false; - } - int fd = open(StringPrintf("%s/%s", TRAIN_INFO_DIR, fileName.c_str()).c_str(), O_RDONLY | O_CLOEXEC); - if (fd == -1) { - VLOG("Failed to open %s", fileName.c_str()); - return false; - } - - // Read the magic word - uint32_t magic; - size_t result = read(fd, &magic, sizeof(magic)); - if (result != sizeof(magic)) { - VLOG("Failed to read train info magic"); - close(fd); - return false; - } - - if (magic != TRAIN_INFO_FILE_MAGIC) { - VLOG("Train info magic was 0x%08x, expected 0x%08x", magic, TRAIN_INFO_FILE_MAGIC); - close(fd); - return false; - } - - // Read the train version code - const size_t trainVersionCodeByteCount(sizeof(trainInfo.trainVersionCode)); - result = read(fd, &trainInfo.trainVersionCode, trainVersionCodeByteCount); - if (result != trainVersionCodeByteCount) { - VLOG("Failed to read train version code from train info file"); - close(fd); - return false; - } - - // Read # of bytes taken by trainName in the file. - size_t trainNameSize; - result = read(fd, &trainNameSize, sizeof(size_t)); - if (result != sizeof(size_t)) { - VLOG("Failed to read train name size from train info file"); - close(fd); - return false; - } - - // Read trainName - trainInfo.trainName.resize(trainNameSize); - result = read(fd, trainInfo.trainName.data(), trainNameSize); - if (result != trainNameSize) { - VLOG("Failed to read train name from train info file"); - close(fd); - return false; - } - - // Read status - const size_t statusByteCount = sizeof(trainInfo.status); - result = read(fd, &trainInfo.status, statusByteCount); - if (result != statusByteCount) { - VLOG("Failed to read train status from train info file"); - close(fd); - return false; - } - - // Read experiment ids count. - size_t experimentIdsCount; - result = read(fd, &experimentIdsCount, sizeof(size_t)); - if (result != sizeof(size_t)) { - VLOG("Failed to read train experiment id count from train info file"); - close(fd); - return false; - } - - // Read experimentIds - for (size_t i = 0; i < experimentIdsCount; i++) { - int64_t experimentId; - result = read(fd, &experimentId, sizeof(experimentId)); - if (result != sizeof(experimentId)) { - VLOG("Failed to read train experiment id from train info file"); - close(fd); - return false; - } - trainInfo.experimentIds.push_back(experimentId); - } - - // Read bools - const size_t boolByteCount = sizeof(trainInfo.requiresStaging); - result = read(fd, &trainInfo.requiresStaging, boolByteCount); - if (result != boolByteCount) { - VLOG("Failed to read requires requires staging from train info file"); - close(fd); - return false; - } - - result = read(fd, &trainInfo.rollbackEnabled, boolByteCount); - if (result != boolByteCount) { - VLOG("Failed to read requires rollback enabled from train info file"); - close(fd); - return false; - } - - result = read(fd, &trainInfo.requiresLowLatencyMonitor, boolByteCount); - if (result != boolByteCount) { - VLOG("Failed to read requires requires low latency monitor from train info file"); - close(fd); - return false; - } - - // Expect to be at EOF. - char c; - result = read(fd, &c, 1); - if (result != 0) { - VLOG("Failed to read train info from file. Did not get expected EOF."); - close(fd); - return false; - } - - VLOG("Read train info file successful"); - close(fd); - return true; -} - -vector StorageManager::readAllTrainInfo() { - std::lock_guard lock(sTrainInfoMutex); - vector trainInfoList; - unique_ptr dir(opendir(TRAIN_INFO_DIR), closedir); - if (dir == NULL) { - VLOG("Directory does not exist: %s", TRAIN_INFO_DIR); - return trainInfoList; - } - - dirent* de; - while ((de = readdir(dir.get()))) { - char* name = de->d_name; - if (name[0] == '.') { - continue; - } - - InstallTrainInfo trainInfo; - bool readSuccess = StorageManager::readTrainInfoLocked(name, trainInfo); - if (!readSuccess) { - continue; - } - trainInfoList.push_back(trainInfo); - } - return trainInfoList; -} - -void StorageManager::deleteFile(const char* file) { - if (remove(file) != 0) { - VLOG("Attempt to delete %s but is not found", file); - } else { - VLOG("Successfully deleted %s", file); - } -} - -void StorageManager::deleteAllFiles(const char* path) { - unique_ptr dir(opendir(path), closedir); - if (dir == NULL) { - VLOG("Directory does not exist: %s", path); - return; - } - - dirent* de; - while ((de = readdir(dir.get()))) { - char* name = de->d_name; - if (name[0] == '.') continue; - deleteFile(StringPrintf("%s/%s", path, name).c_str()); - } -} - -void StorageManager::deleteSuffixedFiles(const char* path, const char* suffix) { - unique_ptr dir(opendir(path), closedir); - if (dir == NULL) { - VLOG("Directory does not exist: %s", path); - return; - } - - dirent* de; - while ((de = readdir(dir.get()))) { - char* name = de->d_name; - if (name[0] == '.') { - continue; - } - size_t nameLen = strlen(name); - size_t suffixLen = strlen(suffix); - if (suffixLen <= nameLen && strncmp(name + nameLen - suffixLen, suffix, suffixLen) == 0) { - deleteFile(StringPrintf("%s/%s", path, name).c_str()); - } - } -} - -void StorageManager::sendBroadcast(const char* path, - const std::function& sendBroadcast) { - unique_ptr dir(opendir(path), closedir); - if (dir == NULL) { - VLOG("no stats-data directory on disk"); - return; - } - - dirent* de; - while ((de = readdir(dir.get()))) { - char* name = de->d_name; - if (name[0] == '.') continue; - VLOG("file %s", name); - - FileName output; - parseFileName(name, &output); - if (output.mTimestampSec == -1 || output.mIsHistory) continue; - sendBroadcast(ConfigKey((int)output.mUid, output.mConfigId)); - } -} - -bool StorageManager::hasConfigMetricsReport(const ConfigKey& key) { - unique_ptr dir(opendir(STATS_DATA_DIR), closedir); - if (dir == NULL) { - VLOG("Path %s does not exist", STATS_DATA_DIR); - return false; - } - - string suffix = StringPrintf("%d_%lld", key.GetUid(), (long long)key.GetId()); - - dirent* de; - while ((de = readdir(dir.get()))) { - char* name = de->d_name; - if (name[0] == '.') continue; - - size_t nameLen = strlen(name); - size_t suffixLen = suffix.length(); - if (suffixLen <= nameLen && - strncmp(name + nameLen - suffixLen, suffix.c_str(), suffixLen) == 0) { - // Check again that the file name is parseable. - FileName output; - parseFileName(name, &output); - if (output.mTimestampSec == -1 || output.mIsHistory) continue; - return true; - } - } - return false; -} - -void StorageManager::appendConfigMetricsReport(const ConfigKey& key, ProtoOutputStream* proto, - bool erase_data, bool isAdb) { - unique_ptr dir(opendir(STATS_DATA_DIR), closedir); - if (dir == NULL) { - VLOG("Path %s does not exist", STATS_DATA_DIR); - return; - } - - dirent* de; - while ((de = readdir(dir.get()))) { - char* name = de->d_name; - string fileName(name); - if (name[0] == '.') continue; - FileName output; - parseFileName(name, &output); - - if (output.mTimestampSec == -1 || (output.mIsHistory && !isAdb) || - output.mUid != key.GetUid() || output.mConfigId != key.GetId()) { - continue; - } - - auto fullPathName = StringPrintf("%s/%s", STATS_DATA_DIR, fileName.c_str()); - int fd = open(fullPathName.c_str(), O_RDONLY | O_CLOEXEC); - if (fd != -1) { - string content; - if (android::base::ReadFdToString(fd, &content)) { - proto->write(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_REPORTS, - content.c_str(), content.size()); - } - close(fd); - } else { - ALOGE("file cannot be opened"); - } - - if (erase_data) { - remove(fullPathName.c_str()); - } else if (!output.mIsHistory && !isAdb) { - // This means a real data owner has called to get this data. But the config says it - // wants to keep a local history. So now this file must be renamed as a history file. - // So that next time, when owner calls getData() again, this data won't be uploaded - // again. rename returns 0 on success - if (rename(fullPathName.c_str(), (fullPathName + "_history").c_str())) { - ALOGE("Failed to rename file %s", fullPathName.c_str()); - } - } - } -} - -bool StorageManager::readFileToString(const char* file, string* content) { - int fd = open(file, O_RDONLY | O_CLOEXEC); - bool res = false; - if (fd != -1) { - if (android::base::ReadFdToString(fd, content)) { - res = true; - } else { - VLOG("Failed to read file %s\n", file); - } - close(fd); - } - return res; -} - -void StorageManager::readConfigFromDisk(map& configsMap) { - unique_ptr dir(opendir(STATS_SERVICE_DIR), closedir); - if (dir == NULL) { - VLOG("no default config on disk"); - return; - } - trimToFit(STATS_SERVICE_DIR); - - dirent* de; - while ((de = readdir(dir.get()))) { - char* name = de->d_name; - if (name[0] == '.') continue; - - FileName output; - parseFileName(name, &output); - if (output.mTimestampSec == -1) continue; - string file_name = output.getFullFileName(STATS_SERVICE_DIR); - int fd = open(file_name.c_str(), O_RDONLY | O_CLOEXEC); - if (fd != -1) { - string content; - if (android::base::ReadFdToString(fd, &content)) { - StatsdConfig config; - if (config.ParseFromString(content)) { - configsMap[ConfigKey(output.mUid, output.mConfigId)] = config; - VLOG("map key uid=%lld|configID=%lld", (long long)output.mUid, - (long long)output.mConfigId); - } - } - close(fd); - } - } -} - -bool StorageManager::readConfigFromDisk(const ConfigKey& key, StatsdConfig* config) { - string content; - return config != nullptr && - StorageManager::readConfigFromDisk(key, &content) && config->ParseFromString(content); -} - -bool StorageManager::readConfigFromDisk(const ConfigKey& key, string* content) { - unique_ptr dir(opendir(STATS_SERVICE_DIR), - closedir); - if (dir == NULL) { - VLOG("Directory does not exist: %s", STATS_SERVICE_DIR); - return false; - } - - string suffix = StringPrintf("%d_%lld", key.GetUid(), (long long)key.GetId()); - dirent* de; - while ((de = readdir(dir.get()))) { - char* name = de->d_name; - if (name[0] == '.') { - continue; - } - size_t nameLen = strlen(name); - size_t suffixLen = suffix.length(); - // There can be at most one file that matches this suffix (config key). - if (suffixLen <= nameLen && - strncmp(name + nameLen - suffixLen, suffix.c_str(), suffixLen) == 0) { - int fd = open(StringPrintf("%s/%s", STATS_SERVICE_DIR, name).c_str(), - O_RDONLY | O_CLOEXEC); - if (fd != -1) { - bool success = android::base::ReadFdToString(fd, content); - close(fd); - return success; - } - } - } - return false; -} - -bool StorageManager::hasIdenticalConfig(const ConfigKey& key, - const vector& config) { - string content; - if (StorageManager::readConfigFromDisk(key, &content)) { - vector vec(content.begin(), content.end()); - if (vec == config) { - return true; - } - } - return false; -} - -void StorageManager::sortFiles(vector* fileNames) { - // Reverse sort to effectively remove from the back (oldest entries). - // This will sort files in reverse-chronological order. Local history files have lower - // priority than regular data files. - sort(fileNames->begin(), fileNames->end(), [](FileInfo& lhs, FileInfo& rhs) { - // first consider if the file is a local history - if (lhs.mIsHistory && !rhs.mIsHistory) { - return false; - } else if (rhs.mIsHistory && !lhs.mIsHistory) { - return true; - } - - // then consider the age. - if (lhs.mFileAgeSec < rhs.mFileAgeSec) { - return true; - } else if (lhs.mFileAgeSec > rhs.mFileAgeSec) { - return false; - } - - // then good luck.... use string::compare - return lhs.mFileName.compare(rhs.mFileName) > 0; - }); -} - -void StorageManager::trimToFit(const char* path, bool parseTimestampOnly) { - unique_ptr dir(opendir(path), closedir); - if (dir == NULL) { - VLOG("Path %s does not exist", path); - return; - } - dirent* de; - int totalFileSize = 0; - vector fileNames; - auto nowSec = getWallClockSec(); - while ((de = readdir(dir.get()))) { - char* name = de->d_name; - if (name[0] == '.') continue; - - FileName output; - string file_name; - if (parseTimestampOnly) { - file_name = StringPrintf("%s/%s", path, name); - output.mTimestampSec = StrToInt64(strtok(name, "_")); - output.mIsHistory = false; - } else { - parseFileName(name, &output); - file_name = output.getFullFileName(path); - } - if (output.mTimestampSec == -1) continue; - - // Check for timestamp and delete if it's too old. - long fileAge = nowSec - output.mTimestampSec; - if (fileAge > StatsdStats::kMaxAgeSecond || - (output.mIsHistory && fileAge > StatsdStats::kMaxLocalHistoryAgeSecond)) { - deleteFile(file_name.c_str()); - continue; - } - - ifstream file(file_name.c_str(), ifstream::in | ifstream::binary); - int fileSize = 0; - if (file.is_open()) { - file.seekg(0, ios::end); - fileSize = file.tellg(); - file.close(); - totalFileSize += fileSize; - } - fileNames.emplace_back(file_name, output.mIsHistory, fileSize, fileAge); - } - - if (fileNames.size() > StatsdStats::kMaxFileNumber || - totalFileSize > StatsdStats::kMaxFileSize) { - sortFiles(&fileNames); - } - - // Start removing files from oldest to be under the limit. - while (fileNames.size() > 0 && (fileNames.size() > StatsdStats::kMaxFileNumber || - totalFileSize > StatsdStats::kMaxFileSize)) { - totalFileSize -= fileNames.at(fileNames.size() - 1).mFileSizeBytes; - deleteFile(fileNames.at(fileNames.size() - 1).mFileName.c_str()); - fileNames.pop_back(); - } -} - -void StorageManager::printStats(int outFd) { - printDirStats(outFd, STATS_SERVICE_DIR); - printDirStats(outFd, STATS_DATA_DIR); -} - -void StorageManager::printDirStats(int outFd, const char* path) { - dprintf(outFd, "Printing stats of %s\n", path); - unique_ptr dir(opendir(path), closedir); - if (dir == NULL) { - VLOG("Path %s does not exist", path); - return; - } - dirent* de; - int fileCount = 0; - int totalFileSize = 0; - while ((de = readdir(dir.get()))) { - char* name = de->d_name; - if (name[0] == '.') { - continue; - } - FileName output; - parseFileName(name, &output); - if (output.mTimestampSec == -1) continue; - dprintf(outFd, "\t #%d, Last updated: %lld, UID: %d, Config ID: %lld, %s", fileCount + 1, - (long long)output.mTimestampSec, output.mUid, (long long)output.mConfigId, - (output.mIsHistory ? "local history" : "")); - string file_name = output.getFullFileName(path); - ifstream file(file_name.c_str(), ifstream::in | ifstream::binary); - if (file.is_open()) { - file.seekg(0, ios::end); - int fileSize = file.tellg(); - file.close(); - dprintf(outFd, ", File Size: %d bytes", fileSize); - totalFileSize += fileSize; - } - dprintf(outFd, "\n"); - fileCount++; - } - dprintf(outFd, "\tTotal number of files: %d, Total size of files: %d bytes.\n", fileCount, - totalFileSize); -} - -} // namespace statsd -} // namespace os -} // namespace android diff --git a/bin/src/storage/StorageManager.h b/bin/src/storage/StorageManager.h deleted file mode 100644 index d59046df..00000000 --- a/bin/src/storage/StorageManager.h +++ /dev/null @@ -1,168 +0,0 @@ -/* - * 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. - */ - -#ifndef STORAGE_MANAGER_H -#define STORAGE_MANAGER_H - -#include -#include -#include - -#include "packages/UidMap.h" - -namespace android { -namespace os { -namespace statsd { - -using android::util::ProtoOutputStream; - -class StorageManager : public virtual RefBase { -public: - struct FileInfo { - FileInfo(std::string name, bool isHistory, int fileSize, long fileAge) - : mFileName(name), - mIsHistory(isHistory), - mFileSizeBytes(fileSize), - mFileAgeSec(fileAge) { - } - std::string mFileName; - bool mIsHistory; - int mFileSizeBytes; - long mFileAgeSec; - }; - - /** - * Writes a given byte array as a file to the specified file path. - */ - static void writeFile(const char* file, const void* buffer, int numBytes); - - /** - * Writes train info. - */ - static bool writeTrainInfo(const InstallTrainInfo& trainInfo); - - /** - * Reads train info. - */ - static bool readTrainInfo(const std::string& trainName, InstallTrainInfo& trainInfo); - - /** - * Reads train info assuming lock is obtained. - */ - static bool readTrainInfoLocked(const std::string& trainName, InstallTrainInfo& trainInfo); - - /** - * Reads all train info and returns a vector of train info. - */ - static vector readAllTrainInfo(); - - /** - * Reads the file content to the buffer. - */ - static bool readFileToString(const char* file, string* content); - - /** - * Deletes a single file given a file name. - */ - static void deleteFile(const char* file); - - /** - * Deletes all files in a given directory. - */ - static void deleteAllFiles(const char* path); - - /** - * Deletes all files whose name matches with a provided suffix. - */ - static void deleteSuffixedFiles(const char* path, const char* suffix); - - /** - * Send broadcasts to relevant receiver for each data stored on disk. - */ - static void sendBroadcast(const char* path, - const std::function& sendBroadcast); - - /** - * Returns true if there's at least one report on disk. - */ - static bool hasConfigMetricsReport(const ConfigKey& key); - - /** - * Appends the ConfigMetricsReport found on disk to the specifid proto - * and, if erase_data, deletes it from disk. - * - * [isAdb]: if the caller is adb dump. This includes local adb dump or dumpsys by - * bugreport or incidentd. When true, we will append any local history data too. - * - * When - * erase_data=true, isAdb=true: append history data to output, remove all data after read - * erase_data=false, isAdb=true: append history data to output, keep data after read - * erase_data=true, isAdb=false: do not append history data, and remove data after read - * erase_data=false, isAdb=false: do not append history data and *rename* all data files to - * history files. - */ - static void appendConfigMetricsReport(const ConfigKey& key, ProtoOutputStream* proto, - bool erase_data, bool isAdb); - - /** - * Call to load the saved configs from disk. - */ - static void readConfigFromDisk(std::map& configsMap); - - /** - * Call to load the specified config from disk. Returns false if the config file does not - * exist or error occurs when reading the file. - */ - static bool readConfigFromDisk(const ConfigKey& key, StatsdConfig* config); - static bool readConfigFromDisk(const ConfigKey& key, string* config); - - /** - * Trims files in the provided directory to limit the total size, number of - * files, accumulation of outdated files. - */ - static void trimToFit(const char* dir, bool parseTimestampOnly = false); - - /** - * Returns true if there already exists identical configuration on device. - */ - static bool hasIdenticalConfig(const ConfigKey& key, - const vector& config); - - /** - * Prints disk usage statistics related to statsd. - */ - static void printStats(int out); - - static string getDataFileName(long wallClockSec, int uid, int64_t id); - - static string getDataHistoryFileName(long wallClockSec, int uid, int64_t id); - - static void sortFiles(vector* fileNames); - -private: - /** - * Prints disk usage statistics about a directory related to statsd. - */ - static void printDirStats(int out, const char* path); - - static std::mutex sTrainInfoMutex; -}; - -} // namespace statsd -} // namespace os -} // namespace android - -#endif // STORAGE_MANAGER_H diff --git a/bin/src/subscriber/IncidentdReporter.cpp b/bin/src/subscriber/IncidentdReporter.cpp deleted file mode 100644 index 1d77513d..00000000 --- a/bin/src/subscriber/IncidentdReporter.cpp +++ /dev/null @@ -1,169 +0,0 @@ -/* - * Copyright (C) 2018 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. - */ -#define DEBUG false -#include "Log.h" - -#include "FieldValue.h" -#include "IncidentdReporter.h" -#include "packages/UidMap.h" -#include "stats_log_util.h" - -#include -#include - -#include - -namespace android { -namespace os { -namespace statsd { - -using android::util::ProtoOutputStream; -using std::vector; - -using util::FIELD_TYPE_INT32; -using util::FIELD_TYPE_INT64; -using util::FIELD_TYPE_MESSAGE; -using util::FIELD_TYPE_STRING; - -// field ids in IncidentHeaderProto -const int FIELD_ID_ALERT_ID = 1; -const int FIELD_ID_REASON = 2; -const int FIELD_ID_CONFIG_KEY = 3; -const int FIELD_ID_CONFIG_KEY_UID = 1; -const int FIELD_ID_CONFIG_KEY_ID = 2; - -const int FIELD_ID_TRIGGER_DETAILS = 4; -const int FIELD_ID_TRIGGER_DETAILS_TRIGGER_METRIC = 1; -const int FIELD_ID_METRIC_VALUE_METRIC_ID = 1; -const int FIELD_ID_METRIC_VALUE_DIMENSION_IN_WHAT = 2; -const int FIELD_ID_METRIC_VALUE_VALUE = 4; - -const int FIELD_ID_PACKAGE_INFO = 3; - -namespace { -void getProtoData(const int64_t& rule_id, int64_t metricId, const MetricDimensionKey& dimensionKey, - int64_t metricValue, const ConfigKey& configKey, const string& reason, - vector* protoData) { - ProtoOutputStream headerProto; - headerProto.write(FIELD_TYPE_INT64 | FIELD_ID_ALERT_ID, (long long)rule_id); - headerProto.write(FIELD_TYPE_STRING | FIELD_ID_REASON, reason); - uint64_t token = - headerProto.start(FIELD_TYPE_MESSAGE | FIELD_ID_CONFIG_KEY); - headerProto.write(FIELD_TYPE_INT32 | FIELD_ID_CONFIG_KEY_UID, configKey.GetUid()); - headerProto.write(FIELD_TYPE_INT64 | FIELD_ID_CONFIG_KEY_ID, (long long)configKey.GetId()); - headerProto.end(token); - - token = headerProto.start(FIELD_TYPE_MESSAGE | FIELD_ID_TRIGGER_DETAILS); - - // MetricValue trigger_metric = 1; - uint64_t metricToken = - headerProto.start(FIELD_TYPE_MESSAGE | FIELD_ID_TRIGGER_DETAILS_TRIGGER_METRIC); - // message MetricValue { - // optional int64 metric_id = 1; - headerProto.write(FIELD_TYPE_INT64 | FIELD_ID_METRIC_VALUE_METRIC_ID, (long long)metricId); - // optional DimensionsValue dimension_in_what = 2; - uint64_t dimToken = - headerProto.start(FIELD_TYPE_MESSAGE | FIELD_ID_METRIC_VALUE_DIMENSION_IN_WHAT); - writeDimensionToProto(dimensionKey.getDimensionKeyInWhat(), nullptr, &headerProto); - headerProto.end(dimToken); - - // deprecated field - // optional DimensionsValue dimension_in_condition = 3; - - // optional int64 value = 4; - headerProto.write(FIELD_TYPE_INT64 | FIELD_ID_METRIC_VALUE_VALUE, (long long)metricValue); - - // } - headerProto.end(metricToken); - - // write relevant uid package info - std::set uids; - - for (const auto& dim : dimensionKey.getDimensionKeyInWhat().getValues()) { - int uid = getUidIfExists(dim); - // any uid <= 2000 are predefined AID_* - if (uid > 2000) { - uids.insert(uid); - } - } - - if (!uids.empty()) { - uint64_t token = headerProto.start(FIELD_TYPE_MESSAGE | FIELD_ID_PACKAGE_INFO); - UidMap::getInstance()->writeUidMapSnapshot(getElapsedRealtimeNs(), true, true, uids, - nullptr /*string set*/, &headerProto); - headerProto.end(token); - } - - headerProto.end(token); - - protoData->resize(headerProto.size()); - size_t pos = 0; - sp reader = headerProto.data(); - while (reader->readBuffer() != NULL) { - size_t toRead = reader->currentToRead(); - std::memcpy(&((*protoData)[pos]), reader->readBuffer(), toRead); - pos += toRead; - reader->move(toRead); - } -} -} // namespace - -bool GenerateIncidentReport(const IncidentdDetails& config, int64_t rule_id, int64_t metricId, - const MetricDimensionKey& dimensionKey, int64_t metricValue, - const ConfigKey& configKey) { - if (config.section_size() == 0) { - VLOG("The alert %lld contains zero section in config(%d,%lld)", (unsigned long long)rule_id, - configKey.GetUid(), (long long)configKey.GetId()); - return false; - } - - AIncidentReportArgs* args = AIncidentReportArgs_init(); - - vector protoData; - getProtoData(rule_id, metricId, dimensionKey, metricValue, configKey, - config.alert_description(), &protoData); - AIncidentReportArgs_addHeader(args, protoData.data(), protoData.size()); - - for (int i = 0; i < config.section_size(); i++) { - AIncidentReportArgs_addSection(args, config.section(i)); - } - - uint8_t dest; - switch (config.dest()) { - case IncidentdDetails_Destination_AUTOMATIC: - dest = INCIDENT_REPORT_PRIVACY_POLICY_AUTOMATIC; - break; - case IncidentdDetails_Destination_EXPLICIT: - dest = INCIDENT_REPORT_PRIVACY_POLICY_EXPLICIT; - break; - default: - dest = INCIDENT_REPORT_PRIVACY_POLICY_AUTOMATIC; - } - AIncidentReportArgs_setPrivacyPolicy(args, dest); - - AIncidentReportArgs_setReceiverPackage(args, config.receiver_pkg().c_str()); - - AIncidentReportArgs_setReceiverClass(args, config.receiver_cls().c_str()); - - int err = AIncidentReportArgs_takeReport(args); - AIncidentReportArgs_delete(args); - - return err == NO_ERROR; -} - -} // namespace statsd -} // namespace os -} // namespace android diff --git a/bin/src/subscriber/IncidentdReporter.h b/bin/src/subscriber/IncidentdReporter.h deleted file mode 100644 index d0b72f21..00000000 --- a/bin/src/subscriber/IncidentdReporter.h +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright (C) 2018 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. - */ - -#pragma once - -#include "HashableDimensionKey.h" -#include "config/ConfigKey.h" -#include "packages/modules/StatsD/bin/src/statsd_config.pb.h" // Alert, IncidentdDetails - -namespace android { -namespace os { -namespace statsd { - -/** - * Calls incidentd to trigger an incident report and put in dropbox for uploading. - */ -bool GenerateIncidentReport(const IncidentdDetails& config, int64_t rule_id, int64_t metricId, - const MetricDimensionKey& dimensionKey, int64_t metricValue, - const ConfigKey& configKey); - -} // namespace statsd -} // namespace os -} // namespace android diff --git a/bin/src/subscriber/SubscriberReporter.cpp b/bin/src/subscriber/SubscriberReporter.cpp deleted file mode 100644 index c6bdc3fe..00000000 --- a/bin/src/subscriber/SubscriberReporter.cpp +++ /dev/null @@ -1,149 +0,0 @@ -/* - * Copyright (C) 2018 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. - */ - -#define DEBUG false // STOPSHIP if true -#include "Log.h" - -#include "SubscriberReporter.h" - -using std::lock_guard; - -namespace android { -namespace os { -namespace statsd { - -using std::vector; - -void SubscriberReporter::broadcastSubscriberDied(void* rawPir) { - SubscriberReporter& thiz = getInstance(); - - // Erase the mapping from a (config_key, subscriberId) to a pir if the - // mapping exists. This requires iterating over the map, but this operation - // should be rare and the map is expected to be small. - lock_guard lock(thiz.mLock); - for (auto subscriberMapIt = thiz.mIntentMap.begin(); subscriberMapIt != thiz.mIntentMap.end(); - subscriberMapIt++) { - unordered_map>& subscriberMap = - subscriberMapIt->second; - for (auto pirIt = subscriberMap.begin(); pirIt != subscriberMap.end(); pirIt++) { - if (pirIt->second.get() == rawPir) { - subscriberMap.erase(pirIt); - if (subscriberMap.empty()) { - thiz.mIntentMap.erase(subscriberMapIt); - } - // pirIt and subscriberMapIt are now invalid. - return; - } - } - } -} - -SubscriberReporter::SubscriberReporter() : - mBroadcastSubscriberDeathRecipient(AIBinder_DeathRecipient_new(broadcastSubscriberDied)) { -} - -void SubscriberReporter::setBroadcastSubscriber(const ConfigKey& configKey, - int64_t subscriberId, - const shared_ptr& pir) { - VLOG("SubscriberReporter::setBroadcastSubscriber called with configKey %s and subscriberId " - "%lld.", - configKey.ToString().c_str(), (long long)subscriberId); - { - lock_guard lock(mLock); - mIntentMap[configKey][subscriberId] = pir; - } - // Pass the raw binder pointer address to be the cookie of the death recipient. While the death - // notification is fired, the cookie is used for identifying which binder was died. Because - // the NDK binder doesn't pass dead binder pointer to binder death handler, the binder death - // handler can't know who died. - // If a dedicated cookie is used to store metadata (config key, subscriber id) for direct - // lookup, a data structure is needed manage the cookies. - AIBinder_linkToDeath(pir->asBinder().get(), mBroadcastSubscriberDeathRecipient.get(), - pir.get()); -} - -void SubscriberReporter::unsetBroadcastSubscriber(const ConfigKey& configKey, - int64_t subscriberId) { - VLOG("SubscriberReporter::unsetBroadcastSubscriber called."); - lock_guard lock(mLock); - auto subscriberMapIt = mIntentMap.find(configKey); - if (subscriberMapIt != mIntentMap.end()) { - subscriberMapIt->second.erase(subscriberId); - if (subscriberMapIt->second.empty()) { - mIntentMap.erase(configKey); - } - } -} - -void SubscriberReporter::alertBroadcastSubscriber(const ConfigKey& configKey, - const Subscription& subscription, - 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) - // subscriber_id - name of the PendingIntent to use to send the broadcast - // config uid - the uid that uploaded the config (and therefore gave the PendingIntent, - // although the intent may be to broadcast to a different uid) - // config id - the name of this config (for this particular uid) - - VLOG("SubscriberReporter::alertBroadcastSubscriber called."); - lock_guard lock(mLock); - - if (!subscription.has_broadcast_subscriber_details() - || !subscription.broadcast_subscriber_details().has_subscriber_id()) { - ALOGE("Broadcast subscriber does not have an id."); - return; - } - int64_t subscriberId = subscription.broadcast_subscriber_details().subscriber_id(); - - vector cookies; - cookies.reserve(subscription.broadcast_subscriber_details().cookie_size()); - for (auto& cookie : subscription.broadcast_subscriber_details().cookie()) { - cookies.push_back(cookie); - } - - auto it1 = mIntentMap.find(configKey); - if (it1 == mIntentMap.end()) { - ALOGW("Cannot inform subscriber for missing config key %s ", configKey.ToString().c_str()); - return; - } - auto it2 = it1->second.find(subscriberId); - if (it2 == it1->second.end()) { - ALOGW("Cannot inform subscriber of config %s for missing subscriberId %lld ", - configKey.ToString().c_str(), (long long)subscriberId); - return; - } - sendBroadcastLocked(it2->second, configKey, subscription, cookies, dimKey); -} - -void SubscriberReporter::sendBroadcastLocked(const shared_ptr& pir, - const ConfigKey& configKey, - const Subscription& subscription, - const vector& cookies, - const MetricDimensionKey& dimKey) const { - VLOG("SubscriberReporter::sendBroadcastLocked called."); - pir->sendSubscriberBroadcast( - configKey.GetUid(), - configKey.GetId(), - subscription.id(), - subscription.rule_id(), - cookies, - dimKey.getDimensionKeyInWhat().toStatsDimensionsValueParcel()); -} - -} // namespace statsd -} // namespace os -} // namespace android diff --git a/bin/src/subscriber/SubscriberReporter.h b/bin/src/subscriber/SubscriberReporter.h deleted file mode 100644 index 6d7d2cb3..00000000 --- a/bin/src/subscriber/SubscriberReporter.h +++ /dev/null @@ -1,112 +0,0 @@ -/* - * Copyright (C) 2018 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. - */ - -#pragma once -#include -#include -#include -#include - -#include -#include -#include - -#include "HashableDimensionKey.h" -#include "config/ConfigKey.h" -#include "packages/modules/StatsD/bin/src/statsd_config.pb.h" // subscription - -using aidl::android::os::IPendingIntentRef; -using std::mutex; -using std::shared_ptr; -using std::string; -using std::unordered_map; -using std::vector; - -namespace android { -namespace os { -namespace statsd { - -// Reports information to subscribers. -// Single instance shared across the process. All methods are thread safe. -class SubscriberReporter { -public: - /** Get (singleton) instance of SubscriberReporter. */ - static SubscriberReporter& getInstance() { - static SubscriberReporter subscriberReporter; - return subscriberReporter; - } - - ~SubscriberReporter(){}; - SubscriberReporter(SubscriberReporter const&) = delete; - void operator=(SubscriberReporter const&) = delete; - - /** - * Stores the given intentSender, associating it with the given (configKey, subscriberId) pair. - */ - void setBroadcastSubscriber(const ConfigKey& configKey, - int64_t subscriberId, - const shared_ptr& pir); - - /** - * Erases any intentSender information from the given (configKey, subscriberId) pair. - */ - void unsetBroadcastSubscriber(const ConfigKey& configKey, int64_t subscriberId); - - /** - * Sends a broadcast via the intentSender previously stored for the - * given (configKey, subscriberId) pair by setBroadcastSubscriber. - * Information about the subscriber, as well as information extracted from the dimKey, is sent. - */ - void alertBroadcastSubscriber(const ConfigKey& configKey, - const Subscription& subscription, - const MetricDimensionKey& dimKey) const; - -private: - SubscriberReporter(); - - mutable mutex mLock; - - /** Maps -> IPendingIntentRef (which represents a PendingIntent). */ - unordered_map>> mIntentMap; - - /** - * Sends a broadcast via the given intentSender (using mStatsCompanionService), along - * with the information in the other parameters. - */ - void sendBroadcastLocked(const shared_ptr& pir, - const ConfigKey& configKey, - const Subscription& subscription, - const vector& cookies, - const MetricDimensionKey& dimKey) const; - - ::ndk::ScopedAIBinder_DeathRecipient mBroadcastSubscriberDeathRecipient; - - /** - * Death recipient callback that is called when a broadcast subscriber dies. - * The cookie is a raw pointer to a PendingIntentReference. It is only used for identifying - * which binder has died and must not be dereferenced. - */ - static void broadcastSubscriberDied(void* cookie); - - friend class SubscriberReporterTest; - FRIEND_TEST(SubscriberReporterTest, TestBroadcastSubscriberDeathRemovesPir); - FRIEND_TEST(SubscriberReporterTest, TestBroadcastSubscriberDeathRemovesPirAndConfigKey); - FRIEND_TEST(SubscriberReporterTest, TestBroadcastSubscriberDeathKeepsReplacedPir); -}; - -} // namespace statsd -} // namespace os -} // namespace android diff --git a/bin/src/uid_data.proto b/bin/src/uid_data.proto deleted file mode 100644 index a6fa26cd..00000000 --- a/bin/src/uid_data.proto +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright (C) 2019 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. - */ - -syntax = "proto2"; - -package android.os.statsd; - -option java_package = "com.android.internal.os"; -option java_outer_classname = "UidDataProto"; - -message ApplicationInfo { - optional int32 uid = 1; - optional int64 version = 2; - optional string version_string = 3; - optional string package_name = 4; - optional string installer = 5; -} - -// StatsServiceCompanion uses the proto to supply statsd with uid-package -// mapping updates. -message UidData { - repeated ApplicationInfo app_info = 1; -} diff --git a/bin/src/utils/MultiConditionTrigger.cpp b/bin/src/utils/MultiConditionTrigger.cpp deleted file mode 100644 index 43a69337..00000000 --- a/bin/src/utils/MultiConditionTrigger.cpp +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright (C) 2020 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. - */ -#define DEBUG false // STOPSHIP if true - -#include "MultiConditionTrigger.h" - -#include - -using namespace std; - -namespace android { -namespace os { -namespace statsd { - -MultiConditionTrigger::MultiConditionTrigger(const set& conditionNames, - function trigger) - : mRemainingConditionNames(conditionNames), - mTrigger(trigger), - mCompleted(mRemainingConditionNames.empty()) { - if (mCompleted) { - thread executorThread([this] { mTrigger(); }); - executorThread.detach(); - } -} - -void MultiConditionTrigger::markComplete(const string& conditionName) { - bool doTrigger = false; - { - lock_guard lg(mMutex); - if (mCompleted) { - return; - } - mRemainingConditionNames.erase(conditionName); - mCompleted = mRemainingConditionNames.empty(); - doTrigger = mCompleted; - } - if (doTrigger) { - std::thread executorThread([this] { mTrigger(); }); - executorThread.detach(); - } -} -} // namespace statsd -} // namespace os -} // namespace android diff --git a/bin/src/utils/MultiConditionTrigger.h b/bin/src/utils/MultiConditionTrigger.h deleted file mode 100644 index 51f60299..00000000 --- a/bin/src/utils/MultiConditionTrigger.h +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright (C) 2020 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. - */ -#pragma once - -#include - -#include -#include - -namespace android { -namespace os { -namespace statsd { - -/** - * This class provides a utility to wait for a set of named conditions to occur. - * - * It will execute the trigger runnable in a detached thread once all conditions have been marked - * true. - */ -class MultiConditionTrigger { -public: - explicit MultiConditionTrigger(const std::set& conditionNames, - std::function trigger); - - MultiConditionTrigger(const MultiConditionTrigger&) = delete; - MultiConditionTrigger& operator=(const MultiConditionTrigger&) = delete; - - // Mark a specific condition as true. If this condition has called markComplete already or if - // the event was not specified in the constructor, the function is a no-op. - void markComplete(const std::string& eventName); - -private: - mutable std::mutex mMutex; - std::set mRemainingConditionNames; - std::function mTrigger; - bool mCompleted; - - FRIEND_TEST(MultiConditionTriggerTest, TestCountDownCalledBySameEventName); -}; -} // namespace statsd -} // namespace os -} // namespace android diff --git a/bin/statsd_test.xml b/bin/statsd_test.xml deleted file mode 100644 index 8f9bb1cb..00000000 --- a/bin/statsd_test.xml +++ /dev/null @@ -1,37 +0,0 @@ - - - - diff --git a/bin/tests/AlarmMonitor_test.cpp b/bin/tests/AlarmMonitor_test.cpp deleted file mode 100644 index 1dc9795d..00000000 --- a/bin/tests/AlarmMonitor_test.cpp +++ /dev/null @@ -1,69 +0,0 @@ -// 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 "anomaly/AlarmMonitor.h" - -#include - -using namespace android::os::statsd; -using std::shared_ptr; - -#ifdef __ANDROID__ -TEST(AlarmMonitor, popSoonerThan) { - std::string emptyMetricId; - std::string emptyDimensionId; - unordered_set, SpHash> set; - AlarmMonitor am(2, - [](const shared_ptr&, int64_t){}, - [](const shared_ptr&){}); - - set = am.popSoonerThan(5); - EXPECT_TRUE(set.empty()); - - sp a = new InternalAlarm{10}; - sp b = new InternalAlarm{20}; - sp c = new InternalAlarm{20}; - sp d = new InternalAlarm{30}; - sp e = new InternalAlarm{40}; - sp f = new InternalAlarm{50}; - - am.add(a); - am.add(b); - am.add(c); - am.add(d); - am.add(e); - am.add(f); - - set = am.popSoonerThan(5); - EXPECT_TRUE(set.empty()); - - set = am.popSoonerThan(30); - ASSERT_EQ(4u, set.size()); - EXPECT_EQ(1u, set.count(a)); - EXPECT_EQ(1u, set.count(b)); - EXPECT_EQ(1u, set.count(c)); - EXPECT_EQ(1u, set.count(d)); - - set = am.popSoonerThan(60); - ASSERT_EQ(2u, set.size()); - EXPECT_EQ(1u, set.count(e)); - EXPECT_EQ(1u, set.count(f)); - - set = am.popSoonerThan(80); - ASSERT_EQ(0u, set.size()); -} - -#else -GTEST_LOG_(INFO) << "This test does nothing.\n"; -#endif diff --git a/bin/tests/ConfigManager_test.cpp b/bin/tests/ConfigManager_test.cpp deleted file mode 100644 index 7b8f74d0..00000000 --- a/bin/tests/ConfigManager_test.cpp +++ /dev/null @@ -1,159 +0,0 @@ -// 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 "src/config/ConfigManager.h" -#include "src/metrics/MetricsManager.h" -#include "statsd_test_util.h" - -#include -#include - -#include -#include - -using namespace android; -using namespace android::os::statsd; -using namespace testing; -using namespace std; - -namespace android { -namespace os { -namespace statsd { - -static ostream& operator<<(ostream& os, const StatsdConfig& config) { - return os << "StatsdConfig{id=" << config.id() << "}"; -} - -} // namespace statsd -} // namespace os -} // namespace android - -/** - * Mock ConfigListener - */ -class MockListener : public ConfigListener { -public: - MOCK_METHOD4(OnConfigUpdated, void(const int64_t timestampNs, const ConfigKey& key, - const StatsdConfig& config, bool modularUpdate)); - MOCK_METHOD1(OnConfigRemoved, void(const ConfigKey& key)); -}; - -/** - * Validate that the ConfigKey is the one we wanted. - */ -MATCHER_P2(ConfigKeyEq, uid, id, "") { - return arg.GetUid() == uid && (long long)arg.GetId() == (long long)id; -} - -/** - * Validate that the StatsdConfig is the one we wanted. - */ -MATCHER_P(StatsdConfigEq, id, 0) { - return (long long)arg.id() == (long long)id; -} - -const int64_t testConfigId = 12345; - -/** - * Test the addOrUpdate and remove methods - */ -TEST(ConfigManagerTest, TestAddUpdateRemove) { - sp listener = new StrictMock(); - - sp manager = new ConfigManager(); - manager->AddListener(listener); - - StatsdConfig config91; - config91.set_id(91); - StatsdConfig config92; - config92.set_id(92); - StatsdConfig config93; - config93.set_id(93); - StatsdConfig config94; - config94.set_id(94); - - { - InSequence s; - - manager->StartupForTest(); - - // Add another one - EXPECT_CALL(*(listener.get()), - OnConfigUpdated(_, ConfigKeyEq(1, StringToId("zzz")), StatsdConfigEq(91), true)) - .RetiresOnSaturation(); - manager->UpdateConfig(ConfigKey(1, StringToId("zzz")), config91); - - // Update It - EXPECT_CALL(*(listener.get()), - OnConfigUpdated(_, ConfigKeyEq(1, StringToId("zzz")), StatsdConfigEq(92), true)) - .RetiresOnSaturation(); - manager->UpdateConfig(ConfigKey(1, StringToId("zzz")), config92); - - // Add one with the same uid but a different name - EXPECT_CALL(*(listener.get()), - OnConfigUpdated(_, ConfigKeyEq(1, StringToId("yyy")), StatsdConfigEq(93), true)) - .RetiresOnSaturation(); - manager->UpdateConfig(ConfigKey(1, StringToId("yyy")), config93); - - // Add one with the same name but a different uid - EXPECT_CALL(*(listener.get()), - OnConfigUpdated(_, ConfigKeyEq(2, StringToId("zzz")), StatsdConfigEq(94), true)) - .RetiresOnSaturation(); - manager->UpdateConfig(ConfigKey(2, StringToId("zzz")), config94); - - // Remove (1,yyy) - EXPECT_CALL(*(listener.get()), OnConfigRemoved(ConfigKeyEq(1, StringToId("yyy")))) - .RetiresOnSaturation(); - manager->RemoveConfig(ConfigKey(1, StringToId("yyy"))); - - // Remove (2,zzz) - EXPECT_CALL(*(listener.get()), OnConfigRemoved(ConfigKeyEq(2, StringToId("zzz")))) - .RetiresOnSaturation(); - manager->RemoveConfig(ConfigKey(2, StringToId("zzz"))); - - // Remove (1,zzz) - EXPECT_CALL(*(listener.get()), OnConfigRemoved(ConfigKeyEq(1, StringToId("zzz")))) - .RetiresOnSaturation(); - manager->RemoveConfig(ConfigKey(1, StringToId("zzz"))); - - // Remove (2,zzz) again and we shouldn't get the callback - manager->RemoveConfig(ConfigKey(2, StringToId("zzz"))); - } -} - -/** - * Test removing all of the configs for a uid. - */ -TEST(ConfigManagerTest, TestRemoveUid) { - sp listener = new StrictMock(); - - sp manager = new ConfigManager(); - manager->AddListener(listener); - - StatsdConfig config; - - EXPECT_CALL(*(listener.get()), OnConfigUpdated(_, _, _, true)).Times(5); - EXPECT_CALL(*(listener.get()), OnConfigRemoved(ConfigKeyEq(2, StringToId("xxx")))); - EXPECT_CALL(*(listener.get()), OnConfigRemoved(ConfigKeyEq(2, StringToId("yyy")))); - EXPECT_CALL(*(listener.get()), OnConfigRemoved(ConfigKeyEq(2, StringToId("zzz")))); - - manager->StartupForTest(); - manager->UpdateConfig(ConfigKey(1, StringToId("aaa")), config); - manager->UpdateConfig(ConfigKey(2, StringToId("xxx")), config); - manager->UpdateConfig(ConfigKey(2, StringToId("yyy")), config); - manager->UpdateConfig(ConfigKey(2, StringToId("zzz")), config); - manager->UpdateConfig(ConfigKey(3, StringToId("bbb")), config); - - manager->RemoveConfigs(2); -} diff --git a/bin/tests/FieldValue_test.cpp b/bin/tests/FieldValue_test.cpp deleted file mode 100644 index 5af13492..00000000 --- a/bin/tests/FieldValue_test.cpp +++ /dev/null @@ -1,653 +0,0 @@ -/* - * 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 "packages/modules/StatsD/bin/src/stats_log.pb.h" -#include "packages/modules/StatsD/bin/src/statsd_config.pb.h" -#include "matchers/matcher_util.h" -#include "src/logd/LogEvent.h" -#include "stats_event.h" -#include "stats_log_util.h" -#include "stats_util.h" -#include "subscriber/SubscriberReporter.h" -#include "tests/statsd_test_util.h" - -#ifdef __ANDROID__ - -using android::util::ProtoReader; - -namespace android { -namespace os { -namespace statsd { - -namespace { -void makeLogEvent(LogEvent* logEvent, const int32_t atomId, const int64_t timestamp, - const vector& attributionUids, const vector& attributionTags, - const string& name) { - AStatsEvent* statsEvent = AStatsEvent_obtain(); - AStatsEvent_setAtomId(statsEvent, atomId); - AStatsEvent_overwriteTimestamp(statsEvent, timestamp); - - writeAttribution(statsEvent, attributionUids, attributionTags); - AStatsEvent_writeString(statsEvent, name.c_str()); - - parseStatsEventToLogEvent(statsEvent, logEvent); -} - -void makeLogEvent(LogEvent* logEvent, const int32_t atomId, const int64_t timestamp, - const vector& attributionUids, const vector& attributionTags, - const int32_t value) { - AStatsEvent* statsEvent = AStatsEvent_obtain(); - AStatsEvent_setAtomId(statsEvent, atomId); - AStatsEvent_overwriteTimestamp(statsEvent, timestamp); - - writeAttribution(statsEvent, attributionUids, attributionTags); - AStatsEvent_writeInt32(statsEvent, value); - - parseStatsEventToLogEvent(statsEvent, logEvent); -} -} // anonymous namespace - -TEST(AtomMatcherTest, TestFieldTranslation) { - FieldMatcher matcher1; - matcher1.set_field(10); - FieldMatcher* child = matcher1.add_child(); - child->set_field(1); - child->set_position(Position::ANY); - - child = child->add_child(); - child->set_field(1); - - vector output; - translateFieldMatcher(matcher1, &output); - - ASSERT_EQ((size_t)1, output.size()); - - const auto& matcher12 = output[0]; - EXPECT_EQ((int32_t)10, matcher12.mMatcher.getTag()); - EXPECT_EQ((int32_t)0x02010001, matcher12.mMatcher.getField()); - EXPECT_EQ((int32_t)0xff7f007f, matcher12.mMask); -} - -TEST(AtomMatcherTest, TestFieldTranslation_ALL) { - FieldMatcher matcher1; - matcher1.set_field(10); - FieldMatcher* child = matcher1.add_child(); - child->set_field(1); - child->set_position(Position::ALL); - - child = child->add_child(); - child->set_field(1); - - vector output; - translateFieldMatcher(matcher1, &output); - - ASSERT_EQ((size_t)1, output.size()); - - const auto& matcher12 = output[0]; - EXPECT_EQ((int32_t)10, matcher12.mMatcher.getTag()); - EXPECT_EQ((int32_t)0x02010001, matcher12.mMatcher.getField()); - EXPECT_EQ((int32_t)0xff7f7f7f, matcher12.mMask); -} - -TEST(AtomMatcherTest, TestFilter_ALL) { - FieldMatcher matcher1; - matcher1.set_field(10); - FieldMatcher* child = matcher1.add_child(); - child->set_field(1); - child->set_position(Position::ALL); - - child->add_child()->set_field(1); - child->add_child()->set_field(2); - - child = matcher1.add_child(); - child->set_field(2); - - vector matchers; - translateFieldMatcher(matcher1, &matchers); - - std::vector attributionUids = {1111, 2222, 3333}; - std::vector attributionTags = {"location1", "location2", "location3"}; - - LogEvent event(/*uid=*/0, /*pid=*/0); - makeLogEvent(&event, 10 /*atomId*/, 1012345, attributionUids, attributionTags, "some value"); - HashableDimensionKey output; - - filterValues(matchers, event.getValues(), &output); - - ASSERT_EQ((size_t)7, output.getValues().size()); - EXPECT_EQ((int32_t)0x02010101, output.getValues()[0].mField.getField()); - EXPECT_EQ((int32_t)1111, output.getValues()[0].mValue.int_value); - EXPECT_EQ((int32_t)0x02010102, output.getValues()[1].mField.getField()); - EXPECT_EQ("location1", output.getValues()[1].mValue.str_value); - - EXPECT_EQ((int32_t)0x02010201, output.getValues()[2].mField.getField()); - EXPECT_EQ((int32_t)2222, output.getValues()[2].mValue.int_value); - EXPECT_EQ((int32_t)0x02010202, output.getValues()[3].mField.getField()); - EXPECT_EQ("location2", output.getValues()[3].mValue.str_value); - - EXPECT_EQ((int32_t)0x02010301, output.getValues()[4].mField.getField()); - EXPECT_EQ((int32_t)3333, output.getValues()[4].mValue.int_value); - EXPECT_EQ((int32_t)0x02010302, output.getValues()[5].mField.getField()); - EXPECT_EQ("location3", output.getValues()[5].mValue.str_value); - - EXPECT_EQ((int32_t)0x00020000, output.getValues()[6].mField.getField()); - EXPECT_EQ("some value", output.getValues()[6].mValue.str_value); -} - -TEST(AtomMatcherTest, TestSubDimension) { - HashableDimensionKey dim; - - int pos1[] = {1, 1, 1}; - int pos2[] = {1, 1, 2}; - int pos3[] = {1, 1, 3}; - int pos4[] = {2, 0, 0}; - Field field1(10, pos1, 2); - Field field2(10, pos2, 2); - - Field field3(10, pos3, 2); - Field field4(10, pos4, 0); - - Value value1((int32_t)10025); - Value value2("tag"); - - Value value11((int32_t)10026); - Value value22("tag2"); - - dim.addValue(FieldValue(field1, value1)); - dim.addValue(FieldValue(field2, value2)); - - HashableDimensionKey subDim1; - subDim1.addValue(FieldValue(field1, value1)); - - HashableDimensionKey subDim2; - subDim1.addValue(FieldValue(field2, value2)); - - EXPECT_TRUE(dim.contains(dim)); - EXPECT_TRUE(dim.contains(subDim1)); - EXPECT_TRUE(dim.contains(subDim2)); - - HashableDimensionKey subDim3; - subDim3.addValue(FieldValue(field1, value11)); - EXPECT_FALSE(dim.contains(subDim3)); - - HashableDimensionKey subDim4; - // Empty dimension is always a sub dimension of other dimensions - EXPECT_TRUE(dim.contains(subDim4)); -} - -TEST(AtomMatcherTest, TestMetric2ConditionLink) { - std::vector attributionUids = {1111, 2222, 3333}; - std::vector attributionTags = {"location1", "location2", "location3"}; - - LogEvent event(/*uid=*/0, /*pid=*/0); - makeLogEvent(&event, 10 /*atomId*/, 12345, attributionUids, attributionTags, "some value"); - - FieldMatcher whatMatcher; - whatMatcher.set_field(10); - FieldMatcher* child11 = whatMatcher.add_child(); - child11->set_field(1); - child11->set_position(Position::ANY); - child11 = child11->add_child(); - child11->set_field(1); - - FieldMatcher conditionMatcher; - conditionMatcher.set_field(27); - FieldMatcher* child2 = conditionMatcher.add_child(); - child2->set_field(2); - child2->set_position(Position::LAST); - - child2 = child2->add_child(); - child2->set_field(2); - - Metric2Condition link; - - translateFieldMatcher(whatMatcher, &link.metricFields); - translateFieldMatcher(conditionMatcher, &link.conditionFields); - - ASSERT_EQ((size_t)1, link.metricFields.size()); - EXPECT_EQ((int32_t)0x02010001, link.metricFields[0].mMatcher.getField()); - EXPECT_EQ((int32_t)0xff7f007f, link.metricFields[0].mMask); - EXPECT_EQ((int32_t)10, link.metricFields[0].mMatcher.getTag()); - - ASSERT_EQ((size_t)1, link.conditionFields.size()); - EXPECT_EQ((int32_t)0x02028002, link.conditionFields[0].mMatcher.getField()); - EXPECT_EQ((int32_t)0xff7f807f, link.conditionFields[0].mMask); - EXPECT_EQ((int32_t)27, link.conditionFields[0].mMatcher.getTag()); -} - -TEST(AtomMatcherTest, TestWriteDimensionPath) { - for (auto position : {Position::ANY, Position::ALL, Position::FIRST, Position::LAST}) { - FieldMatcher matcher1; - matcher1.set_field(10); - FieldMatcher* child = matcher1.add_child(); - child->set_field(2); - child->set_position(position); - child->add_child()->set_field(1); - child->add_child()->set_field(3); - - child = matcher1.add_child(); - child->set_field(4); - - child = matcher1.add_child(); - child->set_field(6); - child->add_child()->set_field(2); - - vector matchers; - translateFieldMatcher(matcher1, &matchers); - - android::util::ProtoOutputStream protoOut; - writeDimensionPathToProto(matchers, &protoOut); - - vector outData; - outData.resize(protoOut.size()); - size_t pos = 0; - sp reader = protoOut.data(); - while (reader->readBuffer() != NULL) { - size_t toRead = reader->currentToRead(); - std::memcpy(&(outData[pos]), reader->readBuffer(), toRead); - pos += toRead; - reader->move(toRead); - } - - DimensionsValue result; - ASSERT_EQ(true, result.ParseFromArray(&outData[0], outData.size())); - - EXPECT_EQ(10, result.field()); - EXPECT_EQ(DimensionsValue::ValueCase::kValueTuple, result.value_case()); - ASSERT_EQ(3, result.value_tuple().dimensions_value_size()); - - const auto& dim1 = result.value_tuple().dimensions_value(0); - EXPECT_EQ(2, dim1.field()); - ASSERT_EQ(2, dim1.value_tuple().dimensions_value_size()); - - const auto& dim11 = dim1.value_tuple().dimensions_value(0); - EXPECT_EQ(1, dim11.field()); - - const auto& dim12 = dim1.value_tuple().dimensions_value(1); - EXPECT_EQ(3, dim12.field()); - - const auto& dim2 = result.value_tuple().dimensions_value(1); - EXPECT_EQ(4, dim2.field()); - - const auto& dim3 = result.value_tuple().dimensions_value(2); - EXPECT_EQ(6, dim3.field()); - ASSERT_EQ(1, dim3.value_tuple().dimensions_value_size()); - const auto& dim31 = dim3.value_tuple().dimensions_value(0); - EXPECT_EQ(2, dim31.field()); - } -} - -void checkAttributionNodeInDimensionsValueParcel(StatsDimensionsValueParcel& attributionNodeParcel, - int32_t nodeDepthInAttributionChain, - int32_t uid, string tag) { - EXPECT_EQ(attributionNodeParcel.field, nodeDepthInAttributionChain /*position at depth 1*/); - ASSERT_EQ(attributionNodeParcel.valueType, STATS_DIMENSIONS_VALUE_TUPLE_TYPE); - ASSERT_EQ(attributionNodeParcel.tupleValue.size(), 2); - - StatsDimensionsValueParcel uidParcel = attributionNodeParcel.tupleValue[0]; - EXPECT_EQ(uidParcel.field, 1 /*position at depth 2*/); - EXPECT_EQ(uidParcel.valueType, STATS_DIMENSIONS_VALUE_INT_TYPE); - EXPECT_EQ(uidParcel.intValue, uid); - - StatsDimensionsValueParcel tagParcel = attributionNodeParcel.tupleValue[1]; - EXPECT_EQ(tagParcel.field, 2 /*position at depth 2*/); - EXPECT_EQ(tagParcel.valueType, STATS_DIMENSIONS_VALUE_STRING_TYPE); - EXPECT_EQ(tagParcel.stringValue, tag); -} - -// Test conversion of a HashableDimensionKey into a StatsDimensionValueParcel -TEST(AtomMatcherTest, TestSubscriberDimensionWrite) { - int atomId = 10; - // First four fields form an attribution chain - int pos1[] = {1, 1, 1}; - int pos2[] = {1, 1, 2}; - int pos3[] = {1, 2, 1}; - int pos4[] = {1, 2, 2}; - int pos5[] = {2, 1, 1}; - - Field field1(atomId, pos1, /*depth=*/2); - Field field2(atomId, pos2, /*depth=*/2); - Field field3(atomId, pos3, /*depth=*/2); - Field field4(atomId, pos4, /*depth=*/2); - Field field5(atomId, pos5, /*depth=*/0); - - Value value1((int32_t)1); - Value value2("string2"); - Value value3((int32_t)3); - Value value4("string4"); - Value value5((float)5.0); - - HashableDimensionKey dimensionKey; - dimensionKey.addValue(FieldValue(field1, value1)); - dimensionKey.addValue(FieldValue(field2, value2)); - dimensionKey.addValue(FieldValue(field3, value3)); - dimensionKey.addValue(FieldValue(field4, value4)); - dimensionKey.addValue(FieldValue(field5, value5)); - - StatsDimensionsValueParcel rootParcel = dimensionKey.toStatsDimensionsValueParcel(); - EXPECT_EQ(rootParcel.field, atomId); - ASSERT_EQ(rootParcel.valueType, STATS_DIMENSIONS_VALUE_TUPLE_TYPE); - ASSERT_EQ(rootParcel.tupleValue.size(), 2); - - // Check that attribution chain is populated correctly - StatsDimensionsValueParcel attributionChainParcel = rootParcel.tupleValue[0]; - EXPECT_EQ(attributionChainParcel.field, 1 /*position at depth 0*/); - ASSERT_EQ(attributionChainParcel.valueType, STATS_DIMENSIONS_VALUE_TUPLE_TYPE); - ASSERT_EQ(attributionChainParcel.tupleValue.size(), 2); - checkAttributionNodeInDimensionsValueParcel(attributionChainParcel.tupleValue[0], - /*nodeDepthInAttributionChain=*/1, - value1.int_value, value2.str_value); - checkAttributionNodeInDimensionsValueParcel(attributionChainParcel.tupleValue[1], - /*nodeDepthInAttributionChain=*/2, - value3.int_value, value4.str_value); - - // Check that the float is populated correctly - StatsDimensionsValueParcel floatParcel = rootParcel.tupleValue[1]; - EXPECT_EQ(floatParcel.field, 2 /*position at depth 0*/); - EXPECT_EQ(floatParcel.valueType, STATS_DIMENSIONS_VALUE_FLOAT_TYPE); - EXPECT_EQ(floatParcel.floatValue, value5.float_value); -} - -TEST(AtomMatcherTest, TestWriteDimensionToProto) { - HashableDimensionKey dim; - int pos1[] = {1, 1, 1}; - int pos2[] = {1, 1, 2}; - int pos3[] = {1, 1, 3}; - int pos4[] = {2, 0, 0}; - Field field1(10, pos1, 2); - Field field2(10, pos2, 2); - Field field3(10, pos3, 2); - Field field4(10, pos4, 0); - - Value value1((int32_t)10025); - Value value2("tag"); - Value value3((int32_t)987654); - Value value4((int32_t)99999); - - dim.addValue(FieldValue(field1, value1)); - dim.addValue(FieldValue(field2, value2)); - dim.addValue(FieldValue(field3, value3)); - dim.addValue(FieldValue(field4, value4)); - - android::util::ProtoOutputStream protoOut; - writeDimensionToProto(dim, nullptr /* include strings */, &protoOut); - - vector outData; - outData.resize(protoOut.size()); - size_t pos = 0; - sp reader = protoOut.data(); - while (reader->readBuffer() != NULL) { - size_t toRead = reader->currentToRead(); - std::memcpy(&(outData[pos]), reader->readBuffer(), toRead); - pos += toRead; - reader->move(toRead); - } - - DimensionsValue result; - ASSERT_EQ(true, result.ParseFromArray(&outData[0], outData.size())); - EXPECT_EQ(10, result.field()); - EXPECT_EQ(DimensionsValue::ValueCase::kValueTuple, result.value_case()); - ASSERT_EQ(2, result.value_tuple().dimensions_value_size()); - - const auto& dim1 = result.value_tuple().dimensions_value(0); - EXPECT_EQ(DimensionsValue::ValueCase::kValueTuple, dim1.value_case()); - ASSERT_EQ(3, dim1.value_tuple().dimensions_value_size()); - - const auto& dim11 = dim1.value_tuple().dimensions_value(0); - EXPECT_EQ(DimensionsValue::ValueCase::kValueInt, dim11.value_case()); - EXPECT_EQ(10025, dim11.value_int()); - - const auto& dim12 = dim1.value_tuple().dimensions_value(1); - EXPECT_EQ(DimensionsValue::ValueCase::kValueStr, dim12.value_case()); - EXPECT_EQ("tag", dim12.value_str()); - - const auto& dim13 = dim1.value_tuple().dimensions_value(2); - EXPECT_EQ(DimensionsValue::ValueCase::kValueInt, dim13.value_case()); - EXPECT_EQ(987654, dim13.value_int()); - - const auto& dim2 = result.value_tuple().dimensions_value(1); - EXPECT_EQ(DimensionsValue::ValueCase::kValueInt, dim2.value_case()); - EXPECT_EQ(99999, dim2.value_int()); -} - -TEST(AtomMatcherTest, TestWriteDimensionLeafNodesToProto) { - HashableDimensionKey dim; - int pos1[] = {1, 1, 1}; - int pos2[] = {1, 1, 2}; - int pos3[] = {1, 1, 3}; - int pos4[] = {2, 0, 0}; - Field field1(10, pos1, 2); - Field field2(10, pos2, 2); - Field field3(10, pos3, 2); - Field field4(10, pos4, 0); - - Value value1((int32_t)10025); - Value value2("tag"); - Value value3((int32_t)987654); - Value value4((int64_t)99999); - - dim.addValue(FieldValue(field1, value1)); - dim.addValue(FieldValue(field2, value2)); - dim.addValue(FieldValue(field3, value3)); - dim.addValue(FieldValue(field4, value4)); - - android::util::ProtoOutputStream protoOut; - writeDimensionLeafNodesToProto(dim, 1, nullptr /* include strings */, &protoOut); - - vector outData; - outData.resize(protoOut.size()); - size_t pos = 0; - sp reader = protoOut.data(); - while (reader->readBuffer() != NULL) { - size_t toRead = reader->currentToRead(); - std::memcpy(&(outData[pos]), reader->readBuffer(), toRead); - pos += toRead; - reader->move(toRead); - } - - DimensionsValueTuple result; - ASSERT_EQ(true, result.ParseFromArray(&outData[0], outData.size())); - ASSERT_EQ(4, result.dimensions_value_size()); - - const auto& dim1 = result.dimensions_value(0); - EXPECT_EQ(DimensionsValue::ValueCase::kValueInt, dim1.value_case()); - EXPECT_EQ(10025, dim1.value_int()); - - const auto& dim2 = result.dimensions_value(1); - EXPECT_EQ(DimensionsValue::ValueCase::kValueStr, dim2.value_case()); - EXPECT_EQ("tag", dim2.value_str()); - - const auto& dim3 = result.dimensions_value(2); - EXPECT_EQ(DimensionsValue::ValueCase::kValueInt, dim3.value_case()); - EXPECT_EQ(987654, dim3.value_int()); - - const auto& dim4 = result.dimensions_value(3); - EXPECT_EQ(DimensionsValue::ValueCase::kValueLong, dim4.value_case()); - EXPECT_EQ(99999, dim4.value_long()); -} - -TEST(AtomMatcherTest, TestWriteAtomToProto) { - std::vector attributionUids = {1111, 2222}; - std::vector attributionTags = {"location1", "location2"}; - - LogEvent event(/*uid=*/0, /*pid=*/0); - makeLogEvent(&event, 4 /*atomId*/, 12345, attributionUids, attributionTags, 999); - - android::util::ProtoOutputStream protoOutput; - writeFieldValueTreeToStream(event.GetTagId(), event.getValues(), &protoOutput); - - vector outData; - outData.resize(protoOutput.size()); - size_t pos = 0; - sp reader = protoOutput.data(); - while (reader->readBuffer() != NULL) { - size_t toRead = reader->currentToRead(); - std::memcpy(&(outData[pos]), reader->readBuffer(), toRead); - pos += toRead; - reader->move(toRead); - } - - Atom result; - ASSERT_EQ(true, result.ParseFromArray(&outData[0], outData.size())); - EXPECT_EQ(Atom::PushedCase::kBleScanResultReceived, result.pushed_case()); - const auto& atom = result.ble_scan_result_received(); - ASSERT_EQ(2, atom.attribution_node_size()); - EXPECT_EQ(1111, atom.attribution_node(0).uid()); - EXPECT_EQ("location1", atom.attribution_node(0).tag()); - EXPECT_EQ(2222, atom.attribution_node(1).uid()); - EXPECT_EQ("location2", atom.attribution_node(1).tag()); - EXPECT_EQ(999, atom.num_results()); -} - -/* - * Test two Matchers is not a subset of one Matcher. - * Test one Matcher is subset of two Matchers. - */ -TEST(AtomMatcherTest, TestSubsetDimensions1) { - // Initialize first set of matchers - FieldMatcher matcher1; - matcher1.set_field(10); - - FieldMatcher* child = matcher1.add_child(); - child->set_field(1); - child->set_position(Position::ALL); - child->add_child()->set_field(1); - child->add_child()->set_field(2); - - vector matchers1; - translateFieldMatcher(matcher1, &matchers1); - ASSERT_EQ(2, matchers1.size()); - - // Initialize second set of matchers - FieldMatcher matcher2; - matcher2.set_field(10); - - child = matcher2.add_child(); - child->set_field(1); - child->set_position(Position::ALL); - child->add_child()->set_field(1); - - vector matchers2; - translateFieldMatcher(matcher2, &matchers2); - ASSERT_EQ(1, matchers2.size()); - - EXPECT_FALSE(subsetDimensions(matchers1, matchers2)); - EXPECT_TRUE(subsetDimensions(matchers2, matchers1)); -} -/* - * Test not a subset with one matching Matcher, one non-matching Matcher. - */ -TEST(AtomMatcherTest, TestSubsetDimensions2) { - // Initialize first set of matchers - FieldMatcher matcher1; - matcher1.set_field(10); - - FieldMatcher* child = matcher1.add_child(); - child->set_field(1); - - child = matcher1.add_child(); - child->set_field(2); - - vector matchers1; - translateFieldMatcher(matcher1, &matchers1); - - // Initialize second set of matchers - FieldMatcher matcher2; - matcher2.set_field(10); - - child = matcher2.add_child(); - child->set_field(1); - - child = matcher2.add_child(); - child->set_field(3); - - vector matchers2; - translateFieldMatcher(matcher2, &matchers2); - - EXPECT_FALSE(subsetDimensions(matchers1, matchers2)); -} - -/* - * Test not a subset if parent field is not equal. - */ -TEST(AtomMatcherTest, TestSubsetDimensions3) { - // Initialize first set of matchers - FieldMatcher matcher1; - matcher1.set_field(10); - - FieldMatcher* child = matcher1.add_child(); - child->set_field(1); - - vector matchers1; - translateFieldMatcher(matcher1, &matchers1); - - // Initialize second set of matchers - FieldMatcher matcher2; - matcher2.set_field(5); - - child = matcher2.add_child(); - child->set_field(1); - - vector matchers2; - translateFieldMatcher(matcher2, &matchers2); - - EXPECT_FALSE(subsetDimensions(matchers1, matchers2)); -} - -/* - * Test is subset with two matching Matchers. - */ -TEST(AtomMatcherTest, TestSubsetDimensions4) { - // Initialize first set of matchers - FieldMatcher matcher1; - matcher1.set_field(10); - - FieldMatcher* child = matcher1.add_child(); - child->set_field(1); - - child = matcher1.add_child(); - child->set_field(2); - - vector matchers1; - translateFieldMatcher(matcher1, &matchers1); - - // Initialize second set of matchers - FieldMatcher matcher2; - matcher2.set_field(10); - - child = matcher2.add_child(); - child->set_field(1); - - child = matcher2.add_child(); - child->set_field(2); - - child = matcher2.add_child(); - child->set_field(3); - - vector matchers2; - translateFieldMatcher(matcher2, &matchers2); - - EXPECT_TRUE(subsetDimensions(matchers1, matchers2)); - EXPECT_FALSE(subsetDimensions(matchers2, matchers1)); -} - -} // namespace statsd -} // namespace os -} // namespace android -#else -GTEST_LOG_(INFO) << "This test does nothing.\n"; -#endif diff --git a/bin/tests/HashableDimensionKey_test.cpp b/bin/tests/HashableDimensionKey_test.cpp deleted file mode 100644 index eaa22bf2..00000000 --- a/bin/tests/HashableDimensionKey_test.cpp +++ /dev/null @@ -1,137 +0,0 @@ -/* - * Copyright (C) 2020 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 "src/HashableDimensionKey.h" - -#include - -#include "packages/modules/StatsD/bin/src/statsd_config.pb.h" -#include "statsd_test_util.h" - -#ifdef __ANDROID__ - -using android::util::ProtoReader; - -namespace android { -namespace os { -namespace statsd { - -/** - * Test that #containsLinkedStateValues returns false when the whatKey is - * smaller than the primaryKey. - */ -TEST(HashableDimensionKeyTest, TestContainsLinkedStateValues_WhatKeyTooSmall) { - std::vector mMetric2StateLinks; - - int32_t uid1 = 1000; - HashableDimensionKey whatKey = DEFAULT_DIMENSION_KEY; - HashableDimensionKey primaryKey; - getUidProcessKey(uid1, &primaryKey); - - EXPECT_FALSE(containsLinkedStateValues(whatKey, primaryKey, mMetric2StateLinks, - UID_PROCESS_STATE_ATOM_ID)); -} - -/** - * Test that #containsLinkedStateValues returns false when the linked values - * are not equal. - */ -TEST(HashableDimensionKeyTest, TestContainsLinkedStateValues_UnequalLinkedValues) { - int stateAtomId = UID_PROCESS_STATE_ATOM_ID; - - FieldMatcher whatMatcher; - whatMatcher.set_field(util::OVERLAY_STATE_CHANGED); - FieldMatcher* child11 = whatMatcher.add_child(); - child11->set_field(1); - - FieldMatcher stateMatcher; - stateMatcher.set_field(stateAtomId); - FieldMatcher* child21 = stateMatcher.add_child(); - child21->set_field(1); - - std::vector mMetric2StateLinks; - Metric2State ms; - ms.stateAtomId = stateAtomId; - translateFieldMatcher(whatMatcher, &ms.metricFields); - translateFieldMatcher(stateMatcher, &ms.stateFields); - mMetric2StateLinks.push_back(ms); - - int32_t uid1 = 1000; - int32_t uid2 = 1001; - HashableDimensionKey whatKey; - getOverlayKey(uid2, "package", &whatKey); - HashableDimensionKey primaryKey; - getUidProcessKey(uid1, &primaryKey); - - EXPECT_FALSE(containsLinkedStateValues(whatKey, primaryKey, mMetric2StateLinks, stateAtomId)); -} - -/** - * Test that #containsLinkedStateValues returns false when there is no link - * between the key values. - */ -TEST(HashableDimensionKeyTest, TestContainsLinkedStateValues_MissingMetric2StateLinks) { - int stateAtomId = UID_PROCESS_STATE_ATOM_ID; - - std::vector mMetric2StateLinks; - - int32_t uid1 = 1000; - HashableDimensionKey whatKey; - getOverlayKey(uid1, "package", &whatKey); - HashableDimensionKey primaryKey; - getUidProcessKey(uid1, &primaryKey); - - EXPECT_FALSE(containsLinkedStateValues(whatKey, primaryKey, mMetric2StateLinks, stateAtomId)); -} - -/** - * Test that #containsLinkedStateValues returns true when the key values are - * linked and equal. - */ -TEST(HashableDimensionKeyTest, TestContainsLinkedStateValues_AllConditionsMet) { - int stateAtomId = UID_PROCESS_STATE_ATOM_ID; - - FieldMatcher whatMatcher; - whatMatcher.set_field(util::OVERLAY_STATE_CHANGED); - FieldMatcher* child11 = whatMatcher.add_child(); - child11->set_field(1); - - FieldMatcher stateMatcher; - stateMatcher.set_field(stateAtomId); - FieldMatcher* child21 = stateMatcher.add_child(); - child21->set_field(1); - - std::vector mMetric2StateLinks; - Metric2State ms; - ms.stateAtomId = stateAtomId; - translateFieldMatcher(whatMatcher, &ms.metricFields); - translateFieldMatcher(stateMatcher, &ms.stateFields); - mMetric2StateLinks.push_back(ms); - - int32_t uid1 = 1000; - HashableDimensionKey whatKey; - getOverlayKey(uid1, "package", &whatKey); - HashableDimensionKey primaryKey; - getUidProcessKey(uid1, &primaryKey); - - EXPECT_TRUE(containsLinkedStateValues(whatKey, primaryKey, mMetric2StateLinks, stateAtomId)); -} - -} // namespace statsd -} // namespace os -} // namespace android -#else -GTEST_LOG_(INFO) << "This test does nothing.\n"; -#endif diff --git a/bin/tests/LogEntryMatcher_test.cpp b/bin/tests/LogEntryMatcher_test.cpp deleted file mode 100644 index 6a433d4b..00000000 --- a/bin/tests/LogEntryMatcher_test.cpp +++ /dev/null @@ -1,809 +0,0 @@ -// 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 - -#include "annotations.h" -#include "packages/modules/StatsD/bin/src/statsd_config.pb.h" -#include "matchers/matcher_util.h" -#include "stats_event.h" -#include "stats_log_util.h" -#include "stats_util.h" -#include "statsd_test_util.h" - -using namespace android::os::statsd; -using std::unordered_map; -using std::vector; - -const int32_t TAG_ID = 123; -const int32_t TAG_ID_2 = 28; // hardcoded tag of atom with uid field -const int FIELD_ID_1 = 1; -const int FIELD_ID_2 = 2; -const int FIELD_ID_3 = 2; - -const int ATTRIBUTION_UID_FIELD_ID = 1; -const int ATTRIBUTION_TAG_FIELD_ID = 2; - - -#ifdef __ANDROID__ - -namespace { - -void makeIntLogEvent(LogEvent* logEvent, const int32_t atomId, const int64_t timestamp, - const int32_t value) { - AStatsEvent* statsEvent = AStatsEvent_obtain(); - AStatsEvent_setAtomId(statsEvent, atomId); - AStatsEvent_overwriteTimestamp(statsEvent, timestamp); - AStatsEvent_writeInt32(statsEvent, value); - - parseStatsEventToLogEvent(statsEvent, logEvent); -} - -void makeFloatLogEvent(LogEvent* logEvent, const int32_t atomId, const int64_t timestamp, - const float floatValue) { - AStatsEvent* statsEvent = AStatsEvent_obtain(); - AStatsEvent_setAtomId(statsEvent, atomId); - AStatsEvent_overwriteTimestamp(statsEvent, timestamp); - AStatsEvent_writeFloat(statsEvent, floatValue); - - parseStatsEventToLogEvent(statsEvent, logEvent); -} - -void makeStringLogEvent(LogEvent* logEvent, const int32_t atomId, const int64_t timestamp, - const string& name) { - AStatsEvent* statsEvent = AStatsEvent_obtain(); - AStatsEvent_setAtomId(statsEvent, atomId); - AStatsEvent_overwriteTimestamp(statsEvent, timestamp); - AStatsEvent_writeString(statsEvent, name.c_str()); - - parseStatsEventToLogEvent(statsEvent, logEvent); -} - -void makeIntWithBoolAnnotationLogEvent(LogEvent* logEvent, const int32_t atomId, - const int32_t field, const uint8_t annotationId, - const bool annotationValue) { - AStatsEvent* statsEvent = AStatsEvent_obtain(); - AStatsEvent_setAtomId(statsEvent, atomId); - AStatsEvent_writeInt32(statsEvent, field); - AStatsEvent_addBoolAnnotation(statsEvent, annotationId, annotationValue); - - parseStatsEventToLogEvent(statsEvent, logEvent); -} - -void makeAttributionLogEvent(LogEvent* logEvent, const int32_t atomId, const int64_t timestamp, - const vector& attributionUids, - const vector& attributionTags, const string& name) { - AStatsEvent* statsEvent = AStatsEvent_obtain(); - AStatsEvent_setAtomId(statsEvent, atomId); - AStatsEvent_overwriteTimestamp(statsEvent, timestamp); - - writeAttribution(statsEvent, attributionUids, attributionTags); - AStatsEvent_writeString(statsEvent, name.c_str()); - - parseStatsEventToLogEvent(statsEvent, logEvent); -} - -void makeBoolLogEvent(LogEvent* logEvent, const int32_t atomId, const int64_t timestamp, - const bool bool1, const bool bool2) { - AStatsEvent* statsEvent = AStatsEvent_obtain(); - AStatsEvent_setAtomId(statsEvent, atomId); - AStatsEvent_overwriteTimestamp(statsEvent, timestamp); - - AStatsEvent_writeBool(statsEvent, bool1); - AStatsEvent_writeBool(statsEvent, bool2); - - parseStatsEventToLogEvent(statsEvent, logEvent); -} - -} // anonymous namespace - -TEST(AtomMatcherTest, TestSimpleMatcher) { - sp uidMap = new UidMap(); - - // Set up the matcher - AtomMatcher matcher; - auto simpleMatcher = matcher.mutable_simple_atom_matcher(); - simpleMatcher->set_atom_id(TAG_ID); - - LogEvent event(/*uid=*/0, /*pid=*/0); - makeIntLogEvent(&event, TAG_ID, 0, 11); - - // Test - EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); - - // Wrong tag id. - simpleMatcher->set_atom_id(TAG_ID + 1); - EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event)); -} - -TEST(AtomMatcherTest, TestAttributionMatcher) { - sp uidMap = new UidMap(); - std::vector attributionUids = {1111, 2222, 3333}; - std::vector attributionTags = {"location1", "location2", "location3"}; - - // Set up the log event. - LogEvent event(/*uid=*/0, /*pid=*/0); - makeAttributionLogEvent(&event, TAG_ID, 0, attributionUids, attributionTags, "some value"); - - // Set up the matcher - AtomMatcher matcher; - auto simpleMatcher = matcher.mutable_simple_atom_matcher(); - simpleMatcher->set_atom_id(TAG_ID); - - // Match first node. - auto attributionMatcher = simpleMatcher->add_field_value_matcher(); - attributionMatcher->set_field(FIELD_ID_1); - attributionMatcher->set_position(Position::FIRST); - attributionMatcher->mutable_matches_tuple()->add_field_value_matcher()->set_field( - ATTRIBUTION_TAG_FIELD_ID); - attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_string( - "tag"); - - auto fieldMatcher = simpleMatcher->add_field_value_matcher(); - fieldMatcher->set_field(FIELD_ID_2); - fieldMatcher->set_eq_string("some value"); - - // Tag not matched. - EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event)); - attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_string( - "location3"); - EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event)); - attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_string( - "location1"); - EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); - - // Match last node. - attributionMatcher->set_position(Position::LAST); - EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event)); - attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_string( - "location3"); - EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); - - // Match any node. - attributionMatcher->set_position(Position::ANY); - EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); - attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_string( - "location1"); - EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); - attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_string( - "location2"); - EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); - attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_string( - "location3"); - EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); - attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_string( - "location4"); - EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event)); - - // Attribution match but primitive field not match. - attributionMatcher->set_position(Position::ANY); - attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_string( - "location2"); - fieldMatcher->set_eq_string("wrong value"); - EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event)); - - fieldMatcher->set_eq_string("some value"); - - // Uid match. - attributionMatcher->set_position(Position::ANY); - attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_field( - ATTRIBUTION_UID_FIELD_ID); - attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_string( - "pkg0"); - EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event)); - - uidMap->updateMap( - 1, {1111, 1111, 2222, 3333, 3333} /* uid list */, {1, 1, 2, 1, 2} /* version list */, - {android::String16("v1"), android::String16("v1"), android::String16("v2"), - android::String16("v1"), android::String16("v2")}, - {android::String16("pkg0"), android::String16("pkg1"), android::String16("pkg1"), - android::String16("Pkg2"), android::String16("PkG3")} /* package name list */, - {android::String16(""), android::String16(""), android::String16(""), - android::String16(""), android::String16("")}); - - EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); - attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_string( - "pkg3"); - EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); - attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_string( - "pkg2"); - EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); - attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_string( - "pkg1"); - EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); - attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_string( - "pkg0"); - EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); - - attributionMatcher->set_position(Position::FIRST); - attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_string( - "pkg0"); - EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); - attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_string( - "pkg3"); - EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event)); - attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_string( - "pkg2"); - EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event)); - attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_string( - "pkg1"); - EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); - - attributionMatcher->set_position(Position::LAST); - attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_string( - "pkg0"); - EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event)); - attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_string( - "pkg3"); - EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); - attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_string( - "pkg2"); - EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); - attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_string( - "pkg1"); - EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event)); - - // Uid + tag. - attributionMatcher->set_position(Position::ANY); - attributionMatcher->mutable_matches_tuple()->add_field_value_matcher()->set_field( - ATTRIBUTION_TAG_FIELD_ID); - attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_string( - "pkg0"); - attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(1)->set_eq_string( - "location1"); - EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); - attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_string( - "pkg1"); - attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(1)->set_eq_string( - "location1"); - EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); - attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_string( - "pkg1"); - attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(1)->set_eq_string( - "location2"); - EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); - attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_string( - "pkg2"); - attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(1)->set_eq_string( - "location3"); - EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); - attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_string( - "pkg3"); - attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(1)->set_eq_string( - "location3"); - EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); - attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_string( - "pkg3"); - attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(1)->set_eq_string( - "location1"); - EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event)); - - attributionMatcher->set_position(Position::FIRST); - attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_string( - "pkg0"); - attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(1)->set_eq_string( - "location1"); - EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); - attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_string( - "pkg1"); - attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(1)->set_eq_string( - "location1"); - EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); - attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_string( - "pkg1"); - attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(1)->set_eq_string( - "location2"); - EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event)); - attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_string( - "pkg2"); - attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(1)->set_eq_string( - "location3"); - EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event)); - attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_string( - "pkg3"); - attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(1)->set_eq_string( - "location3"); - EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event)); - attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_string( - "pkg3"); - attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(1)->set_eq_string( - "location1"); - EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event)); - - attributionMatcher->set_position(Position::LAST); - attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_string( - "pkg0"); - attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(1)->set_eq_string( - "location1"); - EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event)); - attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_string( - "pkg1"); - attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(1)->set_eq_string( - "location1"); - EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event)); - attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_string( - "pkg1"); - attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(1)->set_eq_string( - "location2"); - EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event)); - attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_string( - "pkg2"); - attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(1)->set_eq_string( - "location3"); - EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); - attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_string( - "pkg3"); - attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(1)->set_eq_string( - "location3"); - EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); - attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_string( - "pkg3"); - attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(1)->set_eq_string( - "location1"); - EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event)); -} - -TEST(AtomMatcherTest, TestUidFieldMatcher) { - sp uidMap = new UidMap(); - uidMap->updateMap( - 1, {1111, 1111, 2222, 3333, 3333} /* uid list */, {1, 1, 2, 1, 2} /* version list */, - {android::String16("v1"), android::String16("v1"), android::String16("v2"), - android::String16("v1"), android::String16("v2")}, - {android::String16("pkg0"), android::String16("pkg1"), android::String16("pkg1"), - android::String16("Pkg2"), android::String16("PkG3")} /* package name list */, - {android::String16(""), android::String16(""), android::String16(""), - android::String16(""), android::String16("")}); - - // Set up matcher - AtomMatcher matcher; - auto simpleMatcher = matcher.mutable_simple_atom_matcher(); - simpleMatcher->set_atom_id(TAG_ID); - simpleMatcher->add_field_value_matcher()->set_field(1); - simpleMatcher->mutable_field_value_matcher(0)->set_eq_string("pkg0"); - - // Make event without is_uid annotation. - LogEvent event1(/*uid=*/0, /*pid=*/0); - makeIntLogEvent(&event1, TAG_ID, 0, 1111); - EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event1)); - - // Make event with is_uid annotation. - LogEvent event2(/*uid=*/0, /*pid=*/0); - makeIntWithBoolAnnotationLogEvent(&event2, TAG_ID_2, 1111, ANNOTATION_ID_IS_UID, true); - - // Event has is_uid annotation, so mapping from uid to package name occurs. - simpleMatcher->set_atom_id(TAG_ID_2); - EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event2)); - - // Event has is_uid annotation, but uid maps to different package name. - simpleMatcher->mutable_field_value_matcher(0)->set_eq_string("Pkg2"); - EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event2)); -} - -TEST(AtomMatcherTest, TestNeqAnyStringMatcher) { - sp uidMap = new UidMap(); - uidMap->updateMap( - 1, {1111, 1111, 2222, 3333, 3333} /* uid list */, {1, 1, 2, 1, 2} /* version list */, - {android::String16("v1"), android::String16("v1"), android::String16("v2"), - android::String16("v1"), android::String16("v2")}, - {android::String16("pkg0"), android::String16("pkg1"), android::String16("pkg1"), - android::String16("Pkg2"), android::String16("PkG3")} /* package name list */, - {android::String16(""), android::String16(""), android::String16(""), - android::String16(""), android::String16("")}); - - std::vector attributionUids = {1111, 2222, 3333, 1066}; - std::vector attributionTags = {"location1", "location2", "location3", "location3"}; - - // Set up the event - LogEvent event(/*uid=*/0, /*pid=*/0); - makeAttributionLogEvent(&event, TAG_ID, 0, attributionUids, attributionTags, "some value"); - - // Set up the matcher - AtomMatcher matcher; - auto simpleMatcher = matcher.mutable_simple_atom_matcher(); - simpleMatcher->set_atom_id(TAG_ID); - - // Match first node. - auto attributionMatcher = simpleMatcher->add_field_value_matcher(); - attributionMatcher->set_field(FIELD_ID_1); - attributionMatcher->set_position(Position::FIRST); - attributionMatcher->mutable_matches_tuple()->add_field_value_matcher()->set_field( - ATTRIBUTION_UID_FIELD_ID); - auto neqStringList = attributionMatcher->mutable_matches_tuple() - ->mutable_field_value_matcher(0) - ->mutable_neq_any_string(); - neqStringList->add_str_value("pkg2"); - neqStringList->add_str_value("pkg3"); - - auto fieldMatcher = simpleMatcher->add_field_value_matcher(); - fieldMatcher->set_field(FIELD_ID_2); - fieldMatcher->set_eq_string("some value"); - - EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); - - neqStringList->Clear(); - neqStringList->add_str_value("pkg1"); - neqStringList->add_str_value("pkg3"); - EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event)); - - attributionMatcher->set_position(Position::ANY); - neqStringList->Clear(); - neqStringList->add_str_value("maps.com"); - EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); - - neqStringList->Clear(); - neqStringList->add_str_value("PkG3"); - EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); - - attributionMatcher->set_position(Position::LAST); - neqStringList->Clear(); - neqStringList->add_str_value("AID_STATSD"); - EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event)); -} - -TEST(AtomMatcherTest, TestEqAnyStringMatcher) { - sp uidMap = new UidMap(); - uidMap->updateMap( - 1, {1111, 1111, 2222, 3333, 3333} /* uid list */, {1, 1, 2, 1, 2} /* version list */, - {android::String16("v1"), android::String16("v1"), android::String16("v2"), - android::String16("v1"), android::String16("v2")}, - {android::String16("pkg0"), android::String16("pkg1"), android::String16("pkg1"), - android::String16("Pkg2"), android::String16("PkG3")} /* package name list */, - {android::String16(""), android::String16(""), android::String16(""), - android::String16(""), android::String16("")}); - - std::vector attributionUids = {1067, 2222, 3333, 1066}; - std::vector attributionTags = {"location1", "location2", "location3", "location3"}; - - // Set up the event - LogEvent event(/*uid=*/0, /*pid=*/0); - makeAttributionLogEvent(&event, TAG_ID, 0, attributionUids, attributionTags, "some value"); - - // Set up the matcher - AtomMatcher matcher; - auto simpleMatcher = matcher.mutable_simple_atom_matcher(); - simpleMatcher->set_atom_id(TAG_ID); - - // Match first node. - auto attributionMatcher = simpleMatcher->add_field_value_matcher(); - attributionMatcher->set_field(FIELD_ID_1); - attributionMatcher->set_position(Position::FIRST); - attributionMatcher->mutable_matches_tuple()->add_field_value_matcher()->set_field( - ATTRIBUTION_UID_FIELD_ID); - auto eqStringList = attributionMatcher->mutable_matches_tuple() - ->mutable_field_value_matcher(0) - ->mutable_eq_any_string(); - eqStringList->add_str_value("AID_ROOT"); - eqStringList->add_str_value("AID_INCIDENTD"); - - auto fieldMatcher = simpleMatcher->add_field_value_matcher(); - fieldMatcher->set_field(FIELD_ID_2); - fieldMatcher->set_eq_string("some value"); - - EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); - - attributionMatcher->set_position(Position::ANY); - eqStringList->Clear(); - eqStringList->add_str_value("AID_STATSD"); - EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); - - eqStringList->Clear(); - eqStringList->add_str_value("pkg1"); - EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); - - auto normalStringField = fieldMatcher->mutable_eq_any_string(); - normalStringField->add_str_value("some value123"); - normalStringField->add_str_value("some value"); - EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); - - normalStringField->Clear(); - normalStringField->add_str_value("AID_STATSD"); - EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event)); - - eqStringList->Clear(); - eqStringList->add_str_value("maps.com"); - EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event)); -} - -TEST(AtomMatcherTest, TestBoolMatcher) { - sp uidMap = new UidMap(); - // Set up the matcher - AtomMatcher matcher; - auto simpleMatcher = matcher.mutable_simple_atom_matcher(); - simpleMatcher->set_atom_id(TAG_ID); - auto keyValue1 = simpleMatcher->add_field_value_matcher(); - keyValue1->set_field(FIELD_ID_1); - auto keyValue2 = simpleMatcher->add_field_value_matcher(); - keyValue2->set_field(FIELD_ID_2); - - // Set up the event - LogEvent event(/*uid=*/0, /*pid=*/0); - makeBoolLogEvent(&event, TAG_ID, 0, true, false); - - // Test - keyValue1->set_eq_bool(true); - keyValue2->set_eq_bool(false); - EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); - - keyValue1->set_eq_bool(false); - keyValue2->set_eq_bool(false); - EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event)); - - keyValue1->set_eq_bool(false); - keyValue2->set_eq_bool(true); - EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event)); - - keyValue1->set_eq_bool(true); - keyValue2->set_eq_bool(true); - EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event)); -} - -TEST(AtomMatcherTest, TestStringMatcher) { - sp uidMap = new UidMap(); - // Set up the matcher - AtomMatcher matcher; - auto simpleMatcher = matcher.mutable_simple_atom_matcher(); - simpleMatcher->set_atom_id(TAG_ID); - auto keyValue = simpleMatcher->add_field_value_matcher(); - keyValue->set_field(FIELD_ID_1); - keyValue->set_eq_string("some value"); - - // Set up the event - LogEvent event(/*uid=*/0, /*pid=*/0); - makeStringLogEvent(&event, TAG_ID, 0, "some value"); - - // Test - EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); -} - -TEST(AtomMatcherTest, TestMultiFieldsMatcher) { - sp uidMap = new UidMap(); - // Set up the matcher - AtomMatcher matcher; - auto simpleMatcher = matcher.mutable_simple_atom_matcher(); - simpleMatcher->set_atom_id(TAG_ID); - auto keyValue1 = simpleMatcher->add_field_value_matcher(); - keyValue1->set_field(FIELD_ID_1); - auto keyValue2 = simpleMatcher->add_field_value_matcher(); - keyValue2->set_field(FIELD_ID_2); - - // Set up the event - LogEvent event(/*uid=*/0, /*pid=*/0); - CreateTwoValueLogEvent(&event, TAG_ID, 0, 2, 3); - - // Test - keyValue1->set_eq_int(2); - keyValue2->set_eq_int(3); - EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); - - keyValue1->set_eq_int(2); - keyValue2->set_eq_int(4); - EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event)); - - keyValue1->set_eq_int(4); - keyValue2->set_eq_int(3); - EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event)); -} - -TEST(AtomMatcherTest, TestIntComparisonMatcher) { - sp uidMap = new UidMap(); - // Set up the matcher - AtomMatcher matcher; - auto simpleMatcher = matcher.mutable_simple_atom_matcher(); - - simpleMatcher->set_atom_id(TAG_ID); - auto keyValue = simpleMatcher->add_field_value_matcher(); - keyValue->set_field(FIELD_ID_1); - - // Set up the event - LogEvent event(/*uid=*/0, /*pid=*/0); - makeIntLogEvent(&event, TAG_ID, 0, 11); - - // Test - - // eq_int - keyValue->set_eq_int(10); - EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event)); - keyValue->set_eq_int(11); - EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); - keyValue->set_eq_int(12); - EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event)); - - // lt_int - keyValue->set_lt_int(10); - EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event)); - keyValue->set_lt_int(11); - EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event)); - keyValue->set_lt_int(12); - EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); - - // lte_int - keyValue->set_lte_int(10); - EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event)); - keyValue->set_lte_int(11); - EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); - keyValue->set_lte_int(12); - EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); - - // gt_int - keyValue->set_gt_int(10); - EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); - keyValue->set_gt_int(11); - EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event)); - keyValue->set_gt_int(12); - EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event)); - - // gte_int - keyValue->set_gte_int(10); - EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); - keyValue->set_gte_int(11); - EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); - keyValue->set_gte_int(12); - EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event)); -} - -TEST(AtomMatcherTest, TestFloatComparisonMatcher) { - sp uidMap = new UidMap(); - // Set up the matcher - AtomMatcher matcher; - auto simpleMatcher = matcher.mutable_simple_atom_matcher(); - simpleMatcher->set_atom_id(TAG_ID); - - auto keyValue = simpleMatcher->add_field_value_matcher(); - keyValue->set_field(FIELD_ID_1); - - LogEvent event1(/*uid=*/0, /*pid=*/0); - makeFloatLogEvent(&event1, TAG_ID, 0, 10.1f); - keyValue->set_lt_float(10.0); - EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event1)); - - LogEvent event2(/*uid=*/0, /*pid=*/0); - makeFloatLogEvent(&event2, TAG_ID, 0, 9.9f); - EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event2)); - - LogEvent event3(/*uid=*/0, /*pid=*/0); - makeFloatLogEvent(&event3, TAG_ID, 0, 10.1f); - keyValue->set_gt_float(10.0); - EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event3)); - - LogEvent event4(/*uid=*/0, /*pid=*/0); - makeFloatLogEvent(&event4, TAG_ID, 0, 9.9f); - EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event4)); -} - -// Helper for the composite matchers. -void addSimpleMatcher(SimpleAtomMatcher* simpleMatcher, int tag, int key, int val) { - simpleMatcher->set_atom_id(tag); - auto keyValue = simpleMatcher->add_field_value_matcher(); - keyValue->set_field(key); - keyValue->set_eq_int(val); -} - -TEST(AtomMatcherTest, TestAndMatcher) { - // Set up the matcher - LogicalOperation operation = LogicalOperation::AND; - - vector children; - children.push_back(0); - children.push_back(1); - children.push_back(2); - - vector matcherResults; - matcherResults.push_back(MatchingState::kMatched); - matcherResults.push_back(MatchingState::kNotMatched); - matcherResults.push_back(MatchingState::kMatched); - - EXPECT_FALSE(combinationMatch(children, operation, matcherResults)); - - matcherResults.clear(); - matcherResults.push_back(MatchingState::kMatched); - matcherResults.push_back(MatchingState::kMatched); - matcherResults.push_back(MatchingState::kMatched); - - EXPECT_TRUE(combinationMatch(children, operation, matcherResults)); -} - -TEST(AtomMatcherTest, TestOrMatcher) { - // Set up the matcher - LogicalOperation operation = LogicalOperation::OR; - - vector children; - children.push_back(0); - children.push_back(1); - children.push_back(2); - - vector matcherResults; - matcherResults.push_back(MatchingState::kMatched); - matcherResults.push_back(MatchingState::kNotMatched); - matcherResults.push_back(MatchingState::kMatched); - - EXPECT_TRUE(combinationMatch(children, operation, matcherResults)); - - matcherResults.clear(); - matcherResults.push_back(MatchingState::kNotMatched); - matcherResults.push_back(MatchingState::kNotMatched); - matcherResults.push_back(MatchingState::kNotMatched); - - EXPECT_FALSE(combinationMatch(children, operation, matcherResults)); -} - -TEST(AtomMatcherTest, TestNotMatcher) { - // Set up the matcher - LogicalOperation operation = LogicalOperation::NOT; - - vector children; - children.push_back(0); - - vector matcherResults; - matcherResults.push_back(MatchingState::kMatched); - - EXPECT_FALSE(combinationMatch(children, operation, matcherResults)); - - matcherResults.clear(); - matcherResults.push_back(MatchingState::kNotMatched); - EXPECT_TRUE(combinationMatch(children, operation, matcherResults)); -} - -TEST(AtomMatcherTest, TestNandMatcher) { - // Set up the matcher - LogicalOperation operation = LogicalOperation::NAND; - - vector children; - children.push_back(0); - children.push_back(1); - - vector matcherResults; - matcherResults.push_back(MatchingState::kMatched); - matcherResults.push_back(MatchingState::kNotMatched); - - EXPECT_TRUE(combinationMatch(children, operation, matcherResults)); - - matcherResults.clear(); - matcherResults.push_back(MatchingState::kNotMatched); - matcherResults.push_back(MatchingState::kNotMatched); - EXPECT_TRUE(combinationMatch(children, operation, matcherResults)); - - matcherResults.clear(); - matcherResults.push_back(MatchingState::kMatched); - matcherResults.push_back(MatchingState::kMatched); - EXPECT_FALSE(combinationMatch(children, operation, matcherResults)); -} - -TEST(AtomMatcherTest, TestNorMatcher) { - // Set up the matcher - LogicalOperation operation = LogicalOperation::NOR; - - vector children; - children.push_back(0); - children.push_back(1); - - vector matcherResults; - matcherResults.push_back(MatchingState::kMatched); - matcherResults.push_back(MatchingState::kNotMatched); - - EXPECT_FALSE(combinationMatch(children, operation, matcherResults)); - - matcherResults.clear(); - matcherResults.push_back(MatchingState::kNotMatched); - matcherResults.push_back(MatchingState::kNotMatched); - EXPECT_TRUE(combinationMatch(children, operation, matcherResults)); - - matcherResults.clear(); - matcherResults.push_back(MatchingState::kMatched); - matcherResults.push_back(MatchingState::kMatched); - EXPECT_FALSE(combinationMatch(children, operation, matcherResults)); -} -#else -GTEST_LOG_(INFO) << "This test does nothing.\n"; -#endif diff --git a/bin/tests/LogEvent_test.cpp b/bin/tests/LogEvent_test.cpp deleted file mode 100644 index 13c23d22..00000000 --- a/bin/tests/LogEvent_test.cpp +++ /dev/null @@ -1,481 +0,0 @@ -// 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 "src/logd/LogEvent.h" - -#include - -#include "frameworks/proto_logging/stats/atoms.pb.h" -#include "frameworks/proto_logging/stats/enums/stats/launcher/launcher.pb.h" -#include "log/log_event_list.h" -#include "stats_event.h" - -#ifdef __ANDROID__ - -namespace android { -namespace os { -namespace statsd { - -using std::string; -using std::vector; -using util::ProtoOutputStream; -using util::ProtoReader; - -namespace { - -Field getField(int32_t tag, const vector& pos, int32_t depth, const vector& last) { - Field f(tag, (int32_t*)pos.data(), depth); - - // For loop starts at 1 because the last field at depth 0 is not decorated. - for (int i = 1; i < depth; i++) { - if (last[i]) f.decorateLastPos(i); - } - - return f; -} - -void createIntWithBoolAnnotationLogEvent(LogEvent* logEvent, uint8_t annotationId, - bool annotationValue) { - AStatsEvent* statsEvent = AStatsEvent_obtain(); - AStatsEvent_setAtomId(statsEvent, /*atomId=*/100); - AStatsEvent_writeInt32(statsEvent, 10); - AStatsEvent_addBoolAnnotation(statsEvent, annotationId, annotationValue); - AStatsEvent_build(statsEvent); - - size_t size; - uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size); - EXPECT_TRUE(logEvent->parseBuffer(buf, size)); - - AStatsEvent_release(statsEvent); -} - -void createIntWithIntAnnotationLogEvent(LogEvent* logEvent, uint8_t annotationId, - int annotationValue) { - AStatsEvent* statsEvent = AStatsEvent_obtain(); - AStatsEvent_setAtomId(statsEvent, /*atomId=*/100); - AStatsEvent_writeInt32(statsEvent, 10); - AStatsEvent_addInt32Annotation(statsEvent, annotationId, annotationValue); - AStatsEvent_build(statsEvent); - - size_t size; - uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size); - EXPECT_TRUE(logEvent->parseBuffer(buf, size)); - - AStatsEvent_release(statsEvent); -} - -} // anonymous namespace - -TEST(LogEventTest, TestPrimitiveParsing) { - AStatsEvent* event = AStatsEvent_obtain(); - AStatsEvent_setAtomId(event, 100); - AStatsEvent_writeInt32(event, 10); - AStatsEvent_writeInt64(event, 0x123456789); - AStatsEvent_writeFloat(event, 2.0); - AStatsEvent_writeBool(event, true); - AStatsEvent_build(event); - - size_t size; - uint8_t* buf = AStatsEvent_getBuffer(event, &size); - - LogEvent logEvent(/*uid=*/1000, /*pid=*/1001); - EXPECT_TRUE(logEvent.parseBuffer(buf, size)); - - EXPECT_EQ(100, logEvent.GetTagId()); - EXPECT_EQ(1000, logEvent.GetUid()); - EXPECT_EQ(1001, logEvent.GetPid()); - EXPECT_FALSE(logEvent.hasAttributionChain()); - - const vector& values = logEvent.getValues(); - ASSERT_EQ(4, values.size()); - - const FieldValue& int32Item = values[0]; - Field expectedField = getField(100, {1, 1, 1}, 0, {false, false, false}); - EXPECT_EQ(expectedField, int32Item.mField); - EXPECT_EQ(Type::INT, int32Item.mValue.getType()); - EXPECT_EQ(10, int32Item.mValue.int_value); - - const FieldValue& int64Item = values[1]; - expectedField = getField(100, {2, 1, 1}, 0, {false, false, false}); - EXPECT_EQ(expectedField, int64Item.mField); - EXPECT_EQ(Type::LONG, int64Item.mValue.getType()); - EXPECT_EQ(0x123456789, int64Item.mValue.long_value); - - const FieldValue& floatItem = values[2]; - expectedField = getField(100, {3, 1, 1}, 0, {false, false, false}); - EXPECT_EQ(expectedField, floatItem.mField); - EXPECT_EQ(Type::FLOAT, floatItem.mValue.getType()); - EXPECT_EQ(2.0, floatItem.mValue.float_value); - - const FieldValue& boolItem = values[3]; - expectedField = getField(100, {4, 1, 1}, 0, {true, false, false}); - EXPECT_EQ(expectedField, boolItem.mField); - EXPECT_EQ(Type::INT, boolItem.mValue.getType()); // FieldValue does not support boolean type - EXPECT_EQ(1, boolItem.mValue.int_value); - - AStatsEvent_release(event); -} - -TEST(LogEventTest, TestStringAndByteArrayParsing) { - AStatsEvent* event = AStatsEvent_obtain(); - AStatsEvent_setAtomId(event, 100); - string str = "test"; - AStatsEvent_writeString(event, str.c_str()); - AStatsEvent_writeByteArray(event, (uint8_t*)str.c_str(), str.length()); - AStatsEvent_build(event); - - size_t size; - uint8_t* buf = AStatsEvent_getBuffer(event, &size); - - LogEvent logEvent(/*uid=*/1000, /*pid=*/1001); - EXPECT_TRUE(logEvent.parseBuffer(buf, size)); - - EXPECT_EQ(100, logEvent.GetTagId()); - EXPECT_EQ(1000, logEvent.GetUid()); - EXPECT_EQ(1001, logEvent.GetPid()); - EXPECT_FALSE(logEvent.hasAttributionChain()); - - const vector& values = logEvent.getValues(); - ASSERT_EQ(2, values.size()); - - const FieldValue& stringItem = values[0]; - Field expectedField = getField(100, {1, 1, 1}, 0, {false, false, false}); - EXPECT_EQ(expectedField, stringItem.mField); - EXPECT_EQ(Type::STRING, stringItem.mValue.getType()); - EXPECT_EQ(str, stringItem.mValue.str_value); - - const FieldValue& storageItem = values[1]; - expectedField = getField(100, {2, 1, 1}, 0, {true, false, false}); - EXPECT_EQ(expectedField, storageItem.mField); - EXPECT_EQ(Type::STORAGE, storageItem.mValue.getType()); - vector expectedValue = {'t', 'e', 's', 't'}; - EXPECT_EQ(expectedValue, storageItem.mValue.storage_value); - - AStatsEvent_release(event); -} - -TEST(LogEventTest, TestEmptyString) { - AStatsEvent* event = AStatsEvent_obtain(); - AStatsEvent_setAtomId(event, 100); - string empty = ""; - AStatsEvent_writeString(event, empty.c_str()); - AStatsEvent_build(event); - - size_t size; - uint8_t* buf = AStatsEvent_getBuffer(event, &size); - - LogEvent logEvent(/*uid=*/1000, /*pid=*/1001); - EXPECT_TRUE(logEvent.parseBuffer(buf, size)); - - EXPECT_EQ(100, logEvent.GetTagId()); - EXPECT_EQ(1000, logEvent.GetUid()); - EXPECT_EQ(1001, logEvent.GetPid()); - EXPECT_FALSE(logEvent.hasAttributionChain()); - - const vector& values = logEvent.getValues(); - ASSERT_EQ(1, values.size()); - - const FieldValue& item = values[0]; - Field expectedField = getField(100, {1, 1, 1}, 0, {true, false, false}); - EXPECT_EQ(expectedField, item.mField); - EXPECT_EQ(Type::STRING, item.mValue.getType()); - EXPECT_EQ(empty, item.mValue.str_value); - - AStatsEvent_release(event); -} - -TEST(LogEventTest, TestByteArrayWithNullCharacter) { - AStatsEvent* event = AStatsEvent_obtain(); - AStatsEvent_setAtomId(event, 100); - uint8_t message[] = {'\t', 'e', '\0', 's', 't'}; - AStatsEvent_writeByteArray(event, message, 5); - AStatsEvent_build(event); - - size_t size; - uint8_t* buf = AStatsEvent_getBuffer(event, &size); - - LogEvent logEvent(/*uid=*/1000, /*pid=*/1001); - EXPECT_TRUE(logEvent.parseBuffer(buf, size)); - - EXPECT_EQ(100, logEvent.GetTagId()); - EXPECT_EQ(1000, logEvent.GetUid()); - EXPECT_EQ(1001, logEvent.GetPid()); - - const vector& values = logEvent.getValues(); - ASSERT_EQ(1, values.size()); - - const FieldValue& item = values[0]; - Field expectedField = getField(100, {1, 1, 1}, 0, {true, false, false}); - EXPECT_EQ(expectedField, item.mField); - EXPECT_EQ(Type::STORAGE, item.mValue.getType()); - vector expectedValue(message, message + 5); - EXPECT_EQ(expectedValue, item.mValue.storage_value); - - AStatsEvent_release(event); -} - -TEST(LogEventTest, TestAttributionChain) { - AStatsEvent* event = AStatsEvent_obtain(); - AStatsEvent_setAtomId(event, 100); - - string tag1 = "tag1"; - string tag2 = "tag2"; - - uint32_t uids[] = {1001, 1002}; - const char* tags[] = {tag1.c_str(), tag2.c_str()}; - - AStatsEvent_writeAttributionChain(event, uids, tags, 2); - AStatsEvent_build(event); - - size_t size; - uint8_t* buf = AStatsEvent_getBuffer(event, &size); - - LogEvent logEvent(/*uid=*/1000, /*pid=*/1001); - EXPECT_TRUE(logEvent.parseBuffer(buf, size)); - - EXPECT_EQ(100, logEvent.GetTagId()); - EXPECT_EQ(1000, logEvent.GetUid()); - EXPECT_EQ(1001, logEvent.GetPid()); - - const vector& values = logEvent.getValues(); - ASSERT_EQ(4, values.size()); // 2 per attribution node - - std::pair attrIndexRange; - EXPECT_TRUE(logEvent.hasAttributionChain(&attrIndexRange)); - EXPECT_EQ(0, attrIndexRange.first); - EXPECT_EQ(3, attrIndexRange.second); - - // Check first attribution node - const FieldValue& uid1Item = values[0]; - Field expectedField = getField(100, {1, 1, 1}, 2, {true, false, false}); - EXPECT_EQ(expectedField, uid1Item.mField); - EXPECT_EQ(Type::INT, uid1Item.mValue.getType()); - EXPECT_EQ(1001, uid1Item.mValue.int_value); - - const FieldValue& tag1Item = values[1]; - expectedField = getField(100, {1, 1, 2}, 2, {true, false, true}); - EXPECT_EQ(expectedField, tag1Item.mField); - EXPECT_EQ(Type::STRING, tag1Item.mValue.getType()); - EXPECT_EQ(tag1, tag1Item.mValue.str_value); - - // Check second attribution nodes - const FieldValue& uid2Item = values[2]; - expectedField = getField(100, {1, 2, 1}, 2, {true, true, false}); - EXPECT_EQ(expectedField, uid2Item.mField); - EXPECT_EQ(Type::INT, uid2Item.mValue.getType()); - EXPECT_EQ(1002, uid2Item.mValue.int_value); - - const FieldValue& tag2Item = values[3]; - expectedField = getField(100, {1, 2, 2}, 2, {true, true, true}); - EXPECT_EQ(expectedField, tag2Item.mField); - EXPECT_EQ(Type::STRING, tag2Item.mValue.getType()); - EXPECT_EQ(tag2, tag2Item.mValue.str_value); - - AStatsEvent_release(event); -} - -TEST(LogEventTest, TestAnnotationIdIsUid) { - LogEvent event(/*uid=*/0, /*pid=*/0); - createIntWithBoolAnnotationLogEvent(&event, ANNOTATION_ID_IS_UID, true); - - const vector& values = event.getValues(); - ASSERT_EQ(values.size(), 1); - EXPECT_EQ(event.getUidFieldIndex(), 0); -} - -TEST(LogEventTest, TestAnnotationIdStateNested) { - LogEvent event(/*uid=*/0, /*pid=*/0); - createIntWithBoolAnnotationLogEvent(&event, ANNOTATION_ID_STATE_NESTED, true); - - const vector& values = event.getValues(); - ASSERT_EQ(values.size(), 1); - EXPECT_TRUE(values[0].mAnnotations.isNested()); -} - -TEST(LogEventTest, TestPrimaryFieldAnnotation) { - LogEvent event(/*uid=*/0, /*pid=*/0); - createIntWithBoolAnnotationLogEvent(&event, ANNOTATION_ID_PRIMARY_FIELD, true); - - const vector& values = event.getValues(); - ASSERT_EQ(values.size(), 1); - EXPECT_TRUE(values[0].mAnnotations.isPrimaryField()); -} - -TEST(LogEventTest, TestExclusiveStateAnnotation) { - LogEvent event(/*uid=*/0, /*pid=*/0); - createIntWithBoolAnnotationLogEvent(&event, ANNOTATION_ID_EXCLUSIVE_STATE, true); - - const vector& values = event.getValues(); - ASSERT_EQ(values.size(), 1); - EXPECT_TRUE(values[0].mAnnotations.isExclusiveState()); -} - -TEST(LogEventTest, TestPrimaryFieldFirstUidAnnotation) { - // Event has 10 ints and then an attribution chain - int numInts = 10; - int firstUidInChainIndex = numInts; - string tag1 = "tag1"; - string tag2 = "tag2"; - uint32_t uids[] = {1001, 1002}; - const char* tags[] = {tag1.c_str(), tag2.c_str()}; - - // Construct AStatsEvent - AStatsEvent* statsEvent = AStatsEvent_obtain(); - AStatsEvent_setAtomId(statsEvent, 100); - for (int i = 0; i < numInts; i++) { - AStatsEvent_writeInt32(statsEvent, 10); - } - AStatsEvent_writeAttributionChain(statsEvent, uids, tags, 2); - AStatsEvent_addBoolAnnotation(statsEvent, ANNOTATION_ID_PRIMARY_FIELD_FIRST_UID, true); - AStatsEvent_build(statsEvent); - - // Construct LogEvent - size_t size; - uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size); - LogEvent logEvent(/*uid=*/0, /*pid=*/0); - EXPECT_TRUE(logEvent.parseBuffer(buf, size)); - AStatsEvent_release(statsEvent); - - // Check annotation - const vector& values = logEvent.getValues(); - ASSERT_EQ(values.size(), numInts + 4); - EXPECT_TRUE(values[firstUidInChainIndex].mAnnotations.isPrimaryField()); -} - -TEST(LogEventTest, TestResetStateAnnotation) { - int32_t resetState = 10; - LogEvent event(/*uid=*/0, /*pid=*/0); - createIntWithIntAnnotationLogEvent(&event, ANNOTATION_ID_TRIGGER_STATE_RESET, resetState); - - const vector& values = event.getValues(); - ASSERT_EQ(values.size(), 1); - EXPECT_EQ(event.getResetState(), resetState); -} - -TEST(LogEventTest, TestExclusiveStateAnnotationAfterTooManyFields) { - AStatsEvent* event = AStatsEvent_obtain(); - AStatsEvent_setAtomId(event, 100); - - const unsigned int numAttributionNodes = 64; - - uint32_t uids[numAttributionNodes]; - const char* tags[numAttributionNodes]; - - for (unsigned int i = 1; i <= numAttributionNodes; i++) { - uids[i-1] = i; - tags[i-1] = std::to_string(i).c_str(); - } - - AStatsEvent_writeAttributionChain(event, uids, tags, numAttributionNodes); - AStatsEvent_writeInt32(event, 1); - AStatsEvent_addBoolAnnotation(event, ANNOTATION_ID_EXCLUSIVE_STATE, true); - - AStatsEvent_build(event); - - size_t size; - uint8_t* buf = AStatsEvent_getBuffer(event, &size); - - LogEvent logEvent(/*uid=*/1000, /*pid=*/1001); - EXPECT_FALSE(logEvent.parseBuffer(buf, size)); - EXPECT_EQ(-1, logEvent.getExclusiveStateFieldIndex()); - - AStatsEvent_release(event); -} - -TEST(LogEventTest, TestUidAnnotationAfterTooManyFields) { - AStatsEvent* event = AStatsEvent_obtain(); - AStatsEvent_setAtomId(event, 100); - - const unsigned int numAttributionNodes = 64; - - uint32_t uids[numAttributionNodes]; - const char* tags[numAttributionNodes]; - - for (unsigned int i = 1; i <= numAttributionNodes; i++) { - uids[i-1] = i; - tags[i-1] = std::to_string(i).c_str(); - } - - AStatsEvent_writeAttributionChain(event, uids, tags, numAttributionNodes); - AStatsEvent_writeInt32(event, 1); - AStatsEvent_addBoolAnnotation(event, ANNOTATION_ID_IS_UID, true); - - AStatsEvent_build(event); - - size_t size; - uint8_t* buf = AStatsEvent_getBuffer(event, &size); - - LogEvent logEvent(/*uid=*/1000, /*pid=*/1001); - EXPECT_FALSE(logEvent.parseBuffer(buf, size)); - EXPECT_EQ(-1, logEvent.getUidFieldIndex()); - - AStatsEvent_release(event); -} - -TEST(LogEventTest, TestAttributionChainEndIndexAfterTooManyFields) { - AStatsEvent* event = AStatsEvent_obtain(); - AStatsEvent_setAtomId(event, 100); - - const unsigned int numAttributionNodes = 65; - - uint32_t uids[numAttributionNodes]; - const char* tags[numAttributionNodes]; - - for (unsigned int i = 1; i <= numAttributionNodes; i++) { - uids[i-1] = i; - tags[i-1] = std::to_string(i).c_str(); - } - - AStatsEvent_writeAttributionChain(event, uids, tags, numAttributionNodes); - - AStatsEvent_build(event); - - size_t size; - uint8_t* buf = AStatsEvent_getBuffer(event, &size); - - LogEvent logEvent(/*uid=*/1000, /*pid=*/1001); - EXPECT_FALSE(logEvent.parseBuffer(buf, size)); - EXPECT_FALSE(logEvent.hasAttributionChain()); - - AStatsEvent_release(event); -} - -TEST(LogEventTest, TestEmptyAttributionChainWithPrimaryFieldFirstUidAnnotation) { - AStatsEvent* event = AStatsEvent_obtain(); - AStatsEvent_setAtomId(event, 100); - - uint32_t uids[] = {}; - const char* tags[] = {}; - - AStatsEvent_writeInt32(event, 10); - AStatsEvent_writeAttributionChain(event, uids, tags, 0); - AStatsEvent_addBoolAnnotation(event, ANNOTATION_ID_PRIMARY_FIELD_FIRST_UID, true); - - AStatsEvent_build(event); - - size_t size; - uint8_t* buf = AStatsEvent_getBuffer(event, &size); - - LogEvent logEvent(/*uid=*/1000, /*pid=*/1001); - EXPECT_FALSE(logEvent.parseBuffer(buf, size)); - - AStatsEvent_release(event); -} - -} // namespace statsd -} // namespace os -} // namespace android -#else -GTEST_LOG_(INFO) << "This test does nothing.\n"; -#endif diff --git a/bin/tests/LogReader_test.cpp b/bin/tests/LogReader_test.cpp deleted file mode 100644 index 7ce1d6a7..00000000 --- a/bin/tests/LogReader_test.cpp +++ /dev/null @@ -1,21 +0,0 @@ -// 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 - -TEST(LogReaderTest, TestNothingAtAll) { - printf("yay!"); -} diff --git a/bin/tests/MetricsManager_test.cpp b/bin/tests/MetricsManager_test.cpp deleted file mode 100644 index 62c9e2e0..00000000 --- a/bin/tests/MetricsManager_test.cpp +++ /dev/null @@ -1,325 +0,0 @@ -// 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 -#include -#include - -#include -#include -#include - -#include "packages/modules/StatsD/bin/src/statsd_config.pb.h" -#include "metrics/metrics_test_helper.h" -#include "src/condition/ConditionTracker.h" -#include "src/matchers/AtomMatchingTracker.h" -#include "src/metrics/CountMetricProducer.h" -#include "src/metrics/GaugeMetricProducer.h" -#include "src/metrics/MetricProducer.h" -#include "src/metrics/ValueMetricProducer.h" -#include "src/metrics/parsing_utils/metrics_manager_util.h" -#include "src/state/StateManager.h" -#include "statsd_test_util.h" - -using namespace testing; -using android::sp; -using android::os::statsd::Predicate; -using std::map; -using std::set; -using std::unordered_map; -using std::vector; - -#ifdef __ANDROID__ - -namespace android { -namespace os { -namespace statsd { - -namespace { -const ConfigKey kConfigKey(0, 12345); - -const long timeBaseSec = 1000; - -StatsdConfig buildGoodConfig() { - StatsdConfig config; - config.set_id(12345); - - AtomMatcher* eventMatcher = config.add_atom_matcher(); - eventMatcher->set_id(StringToId("SCREEN_IS_ON")); - - SimpleAtomMatcher* simpleAtomMatcher = eventMatcher->mutable_simple_atom_matcher(); - simpleAtomMatcher->set_atom_id(2 /*SCREEN_STATE_CHANGE*/); - simpleAtomMatcher->add_field_value_matcher()->set_field( - 1 /*SCREEN_STATE_CHANGE__DISPLAY_STATE*/); - simpleAtomMatcher->mutable_field_value_matcher(0)->set_eq_int( - 2 /*SCREEN_STATE_CHANGE__DISPLAY_STATE__STATE_ON*/); - - eventMatcher = config.add_atom_matcher(); - eventMatcher->set_id(StringToId("SCREEN_IS_OFF")); - - simpleAtomMatcher = eventMatcher->mutable_simple_atom_matcher(); - simpleAtomMatcher->set_atom_id(2 /*SCREEN_STATE_CHANGE*/); - simpleAtomMatcher->add_field_value_matcher()->set_field( - 1 /*SCREEN_STATE_CHANGE__DISPLAY_STATE*/); - simpleAtomMatcher->mutable_field_value_matcher(0)->set_eq_int( - 1 /*SCREEN_STATE_CHANGE__DISPLAY_STATE__STATE_OFF*/); - - eventMatcher = config.add_atom_matcher(); - eventMatcher->set_id(StringToId("SCREEN_ON_OR_OFF")); - - AtomMatcher_Combination* combination = eventMatcher->mutable_combination(); - combination->set_operation(LogicalOperation::OR); - combination->add_matcher(StringToId("SCREEN_IS_ON")); - combination->add_matcher(StringToId("SCREEN_IS_OFF")); - - CountMetric* metric = config.add_count_metric(); - metric->set_id(3); - metric->set_what(StringToId("SCREEN_IS_ON")); - metric->set_bucket(ONE_MINUTE); - metric->mutable_dimensions_in_what()->set_field(2 /*SCREEN_STATE_CHANGE*/); - metric->mutable_dimensions_in_what()->add_child()->set_field(1); - return config; -} - -set unionSet(const vector> sets) { - set toRet; - for (const set& s : sets) { - toRet.insert(s.begin(), s.end()); - } - return toRet; -} -} // anonymous namespace - -TEST(MetricsManagerTest, TestLogSources) { - string app1 = "app1"; - set app1Uids = {1111, 11111}; - string app2 = "app2"; - set app2Uids = {2222}; - string app3 = "app3"; - set app3Uids = {3333, 1111}; - - map> pkgToUids; - pkgToUids[app1] = app1Uids; - pkgToUids[app2] = app2Uids; - pkgToUids[app3] = app3Uids; - - int32_t atom1 = 10, atom2 = 20, atom3 = 30; - sp uidMap = new StrictMock(); - EXPECT_CALL(*uidMap, getAppUid(_)) - .Times(4) - .WillRepeatedly(Invoke([&pkgToUids](const string& pkg) { - const auto& it = pkgToUids.find(pkg); - if (it != pkgToUids.end()) { - return it->second; - } - return set(); - })); - sp pullerManager = new StrictMock(); - EXPECT_CALL(*pullerManager, RegisterPullUidProvider(kConfigKey, _)).Times(1); - EXPECT_CALL(*pullerManager, UnregisterPullUidProvider(kConfigKey, _)).Times(1); - - sp anomalyAlarmMonitor; - sp periodicAlarmMonitor; - - StatsdConfig config; - config.add_allowed_log_source("AID_SYSTEM"); - config.add_allowed_log_source(app1); - config.add_default_pull_packages("AID_SYSTEM"); - config.add_default_pull_packages("AID_ROOT"); - - const set defaultPullUids = {AID_SYSTEM, AID_ROOT}; - - PullAtomPackages* pullAtomPackages = config.add_pull_atom_packages(); - pullAtomPackages->set_atom_id(atom1); - pullAtomPackages->add_packages(app1); - pullAtomPackages->add_packages(app3); - - pullAtomPackages = config.add_pull_atom_packages(); - pullAtomPackages->set_atom_id(atom2); - pullAtomPackages->add_packages(app2); - pullAtomPackages->add_packages("AID_STATSD"); - - MetricsManager metricsManager(kConfigKey, config, timeBaseSec, timeBaseSec, uidMap, - pullerManager, anomalyAlarmMonitor, periodicAlarmMonitor); - EXPECT_TRUE(metricsManager.isConfigValid()); - - EXPECT_THAT(metricsManager.mAllowedUid, ElementsAre(AID_SYSTEM)); - EXPECT_THAT(metricsManager.mAllowedPkg, ElementsAre(app1)); - EXPECT_THAT(metricsManager.mAllowedLogSources, - ContainerEq(unionSet(vector>({app1Uids, {AID_SYSTEM}})))); - EXPECT_THAT(metricsManager.mDefaultPullUids, ContainerEq(defaultPullUids)); - - vector atom1Uids = metricsManager.getPullAtomUids(atom1); - EXPECT_THAT(atom1Uids, - UnorderedElementsAreArray(unionSet({defaultPullUids, app1Uids, app3Uids}))); - - vector atom2Uids = metricsManager.getPullAtomUids(atom2); - EXPECT_THAT(atom2Uids, - UnorderedElementsAreArray(unionSet({defaultPullUids, app2Uids, {AID_STATSD}}))); - - vector atom3Uids = metricsManager.getPullAtomUids(atom3); - EXPECT_THAT(atom3Uids, UnorderedElementsAreArray(defaultPullUids)); -} - -TEST(MetricsManagerTest, TestLogSourcesOnConfigUpdate) { - string app1 = "app1"; - set app1Uids = {1111, 11111}; - string app2 = "app2"; - set app2Uids = {2222}; - string app3 = "app3"; - set app3Uids = {3333, 1111}; - - map> pkgToUids; - pkgToUids[app1] = app1Uids; - pkgToUids[app2] = app2Uids; - pkgToUids[app3] = app3Uids; - - int32_t atom1 = 10, atom2 = 20, atom3 = 30; - sp uidMap = new StrictMock(); - EXPECT_CALL(*uidMap, getAppUid(_)) - .Times(8) - .WillRepeatedly(Invoke([&pkgToUids](const string& pkg) { - const auto& it = pkgToUids.find(pkg); - if (it != pkgToUids.end()) { - return it->second; - } - return set(); - })); - sp pullerManager = new StrictMock(); - EXPECT_CALL(*pullerManager, RegisterPullUidProvider(kConfigKey, _)).Times(1); - EXPECT_CALL(*pullerManager, UnregisterPullUidProvider(kConfigKey, _)).Times(1); - - sp anomalyAlarmMonitor; - sp periodicAlarmMonitor; - - StatsdConfig config; - config.add_allowed_log_source("AID_SYSTEM"); - config.add_allowed_log_source(app1); - config.add_default_pull_packages("AID_SYSTEM"); - config.add_default_pull_packages("AID_ROOT"); - - PullAtomPackages* pullAtomPackages = config.add_pull_atom_packages(); - pullAtomPackages->set_atom_id(atom1); - pullAtomPackages->add_packages(app1); - pullAtomPackages->add_packages(app3); - - pullAtomPackages = config.add_pull_atom_packages(); - pullAtomPackages->set_atom_id(atom2); - pullAtomPackages->add_packages(app2); - pullAtomPackages->add_packages("AID_STATSD"); - - MetricsManager metricsManager(kConfigKey, config, timeBaseSec, timeBaseSec, uidMap, - pullerManager, anomalyAlarmMonitor, periodicAlarmMonitor); - EXPECT_TRUE(metricsManager.isConfigValid()); - - // Update with new allowed log sources. - StatsdConfig newConfig; - newConfig.add_allowed_log_source("AID_ROOT"); - newConfig.add_allowed_log_source(app2); - newConfig.add_default_pull_packages("AID_SYSTEM"); - newConfig.add_default_pull_packages("AID_STATSD"); - - pullAtomPackages = newConfig.add_pull_atom_packages(); - pullAtomPackages->set_atom_id(atom2); - pullAtomPackages->add_packages(app1); - pullAtomPackages->add_packages(app3); - - pullAtomPackages = newConfig.add_pull_atom_packages(); - pullAtomPackages->set_atom_id(atom3); - pullAtomPackages->add_packages(app2); - pullAtomPackages->add_packages("AID_ADB"); - - metricsManager.updateConfig(newConfig, timeBaseSec, timeBaseSec, anomalyAlarmMonitor, - periodicAlarmMonitor); - EXPECT_TRUE(metricsManager.isConfigValid()); - - EXPECT_THAT(metricsManager.mAllowedUid, ElementsAre(AID_ROOT)); - EXPECT_THAT(metricsManager.mAllowedPkg, ElementsAre(app2)); - EXPECT_THAT(metricsManager.mAllowedLogSources, - ContainerEq(unionSet(vector>({app2Uids, {AID_ROOT}})))); - const set defaultPullUids = {AID_SYSTEM, AID_STATSD}; - EXPECT_THAT(metricsManager.mDefaultPullUids, ContainerEq(defaultPullUids)); - - vector atom1Uids = metricsManager.getPullAtomUids(atom1); - EXPECT_THAT(atom1Uids, UnorderedElementsAreArray(defaultPullUids)); - - vector atom2Uids = metricsManager.getPullAtomUids(atom2); - EXPECT_THAT(atom2Uids, - UnorderedElementsAreArray(unionSet({defaultPullUids, app1Uids, app3Uids}))); - - vector atom3Uids = metricsManager.getPullAtomUids(atom3); - EXPECT_THAT(atom3Uids, - UnorderedElementsAreArray(unionSet({defaultPullUids, app2Uids, {AID_ADB}}))); -} - -TEST(MetricsManagerTest, TestCheckLogCredentialsWhitelistedAtom) { - sp uidMap; - sp pullerManager = new StatsPullerManager(); - sp anomalyAlarmMonitor; - sp periodicAlarmMonitor; - - StatsdConfig config; - config.add_whitelisted_atom_ids(3); - config.add_whitelisted_atom_ids(4); - - MetricsManager metricsManager(kConfigKey, config, timeBaseSec, timeBaseSec, uidMap, - pullerManager, anomalyAlarmMonitor, periodicAlarmMonitor); - - LogEvent event(0 /* uid */, 0 /* pid */); - CreateNoValuesLogEvent(&event, 10 /* atom id */, 0 /* timestamp */); - EXPECT_FALSE(metricsManager.checkLogCredentials(event)); - - CreateNoValuesLogEvent(&event, 3 /* atom id */, 0 /* timestamp */); - EXPECT_TRUE(metricsManager.checkLogCredentials(event)); - - CreateNoValuesLogEvent(&event, 4 /* atom id */, 0 /* timestamp */); - EXPECT_TRUE(metricsManager.checkLogCredentials(event)); -} - -TEST(MetricsManagerTest, TestWhitelistedAtomStateTracker) { - sp uidMap; - sp pullerManager = new StatsPullerManager(); - sp anomalyAlarmMonitor; - sp periodicAlarmMonitor; - - StatsdConfig config = buildGoodConfig(); - config.add_allowed_log_source("AID_SYSTEM"); - config.add_whitelisted_atom_ids(3); - config.add_whitelisted_atom_ids(4); - - State state; - state.set_id(1); - state.set_atom_id(3); - - *config.add_state() = state; - - config.mutable_count_metric(0)->add_slice_by_state(state.id()); - - StateManager::getInstance().clear(); - - MetricsManager metricsManager(kConfigKey, config, timeBaseSec, timeBaseSec, uidMap, - pullerManager, anomalyAlarmMonitor, periodicAlarmMonitor); - - EXPECT_EQ(0, StateManager::getInstance().getStateTrackersCount()); - EXPECT_FALSE(metricsManager.isConfigValid()); -} - -} // namespace statsd -} // namespace os -} // namespace android - -#else -GTEST_LOG_(INFO) << "This test does nothing.\n"; -#endif diff --git a/bin/tests/StatsLogProcessor_test.cpp b/bin/tests/StatsLogProcessor_test.cpp deleted file mode 100644 index 26b64f82..00000000 --- a/bin/tests/StatsLogProcessor_test.cpp +++ /dev/null @@ -1,1886 +0,0 @@ -// 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 "StatsLogProcessor.h" - -#include -#include -#include - -#include "StatsService.h" -#include "config/ConfigKey.h" -#include "packages/modules/StatsD/bin/src/stats_log.pb.h" -#include "packages/modules/StatsD/bin/src/statsd_config.pb.h" -#include "guardrail/StatsdStats.h" -#include "logd/LogEvent.h" -#include "packages/UidMap.h" -#include "statslog_statsdtest.h" -#include "storage/StorageManager.h" -#include "tests/statsd_test_util.h" - -using namespace android; -using namespace testing; -using ::ndk::SharedRefBase; -using std::shared_ptr; - -namespace android { -namespace os { -namespace statsd { - -using android::util::ProtoOutputStream; - -#ifdef __ANDROID__ - -/** - * Mock MetricsManager (ByteSize() is called). - */ -class MockMetricsManager : public MetricsManager { -public: - MockMetricsManager() - : MetricsManager(ConfigKey(1, 12345), StatsdConfig(), 1000, 1000, new UidMap(), - new StatsPullerManager(), - new AlarmMonitor(10, - [](const shared_ptr&, int64_t) {}, - [](const shared_ptr&) {}), - new AlarmMonitor(10, - [](const shared_ptr&, int64_t) {}, - [](const shared_ptr&) {})) { - } - - MOCK_METHOD0(byteSize, size_t()); - - MOCK_METHOD1(dropData, void(const int64_t dropTimeNs)); -}; - -TEST(StatsLogProcessorTest, TestRateLimitByteSize) { - sp m = new UidMap(); - sp pullerManager = new StatsPullerManager(); - sp anomalyAlarmMonitor; - sp periodicAlarmMonitor; - // Construct the processor with a no-op sendBroadcast function that does nothing. - StatsLogProcessor p(m, pullerManager, anomalyAlarmMonitor, periodicAlarmMonitor, 0, - [](const ConfigKey& key) { return true; }, - [](const int&, const vector&) {return true;}); - - MockMetricsManager mockMetricsManager; - - ConfigKey key(100, 12345); - // Expect only the first flush to trigger a check for byte size since the last two are - // rate-limited. - EXPECT_CALL(mockMetricsManager, byteSize()).Times(1); - p.flushIfNecessaryLocked(key, mockMetricsManager); - p.flushIfNecessaryLocked(key, mockMetricsManager); - p.flushIfNecessaryLocked(key, mockMetricsManager); -} - -TEST(StatsLogProcessorTest, TestRateLimitBroadcast) { - sp m = new UidMap(); - sp pullerManager = new StatsPullerManager(); - sp anomalyAlarmMonitor; - sp subscriberAlarmMonitor; - int broadcastCount = 0; - StatsLogProcessor p(m, pullerManager, anomalyAlarmMonitor, subscriberAlarmMonitor, 0, - [&broadcastCount](const ConfigKey& key) { - broadcastCount++; - return true; - }, - [](const int&, const vector&) {return true;}); - - MockMetricsManager mockMetricsManager; - - ConfigKey key(100, 12345); - EXPECT_CALL(mockMetricsManager, byteSize()) - .Times(1) - .WillRepeatedly(::testing::Return(int( - StatsdStats::kMaxMetricsBytesPerConfig * .95))); - - // Expect only one broadcast despite always returning a size that should trigger broadcast. - p.flushIfNecessaryLocked(key, mockMetricsManager); - EXPECT_EQ(1, broadcastCount); - - // b/73089712 - // This next call to flush should not trigger a broadcast. - // p.mLastByteSizeTimes.clear(); // Force another check for byte size. - // p.flushIfNecessaryLocked(2, key, mockMetricsManager); - // EXPECT_EQ(1, broadcastCount); -} - -TEST(StatsLogProcessorTest, TestDropWhenByteSizeTooLarge) { - sp m = new UidMap(); - sp pullerManager = new StatsPullerManager(); - sp anomalyAlarmMonitor; - sp subscriberAlarmMonitor; - int broadcastCount = 0; - StatsLogProcessor p(m, pullerManager, anomalyAlarmMonitor, subscriberAlarmMonitor, 0, - [&broadcastCount](const ConfigKey& key) { - broadcastCount++; - return true; - }, - [](const int&, const vector&) {return true;}); - - MockMetricsManager mockMetricsManager; - - ConfigKey key(100, 12345); - EXPECT_CALL(mockMetricsManager, byteSize()) - .Times(1) - .WillRepeatedly(::testing::Return(int(StatsdStats::kMaxMetricsBytesPerConfig * 1.2))); - - EXPECT_CALL(mockMetricsManager, dropData(_)).Times(1); - - // Expect to call the onDumpReport and skip the broadcast. - p.flushIfNecessaryLocked(key, mockMetricsManager); - EXPECT_EQ(0, broadcastCount); -} - -StatsdConfig MakeConfig(bool includeMetric) { - StatsdConfig config; - config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root. - - if (includeMetric) { - auto appCrashMatcher = CreateProcessCrashAtomMatcher(); - *config.add_atom_matcher() = appCrashMatcher; - auto countMetric = config.add_count_metric(); - countMetric->set_id(StringToId("AppCrashes")); - countMetric->set_what(appCrashMatcher.id()); - countMetric->set_bucket(FIVE_MINUTES); - } - return config; -} - -TEST(StatsLogProcessorTest, TestUidMapHasSnapshot) { - // Setup simple config key corresponding to empty config. - sp m = new UidMap(); - sp pullerManager = new StatsPullerManager(); - m->updateMap(1, {1, 2}, {1, 2}, {String16("v1"), String16("v2")}, - {String16("p1"), String16("p2")}, {String16(""), String16("")}); - sp anomalyAlarmMonitor; - sp subscriberAlarmMonitor; - int broadcastCount = 0; - StatsLogProcessor p(m, pullerManager, anomalyAlarmMonitor, subscriberAlarmMonitor, 0, - [&broadcastCount](const ConfigKey& key) { - broadcastCount++; - return true; - }, - [](const int&, const vector&) {return true;}); - ConfigKey key(3, 4); - StatsdConfig config = MakeConfig(true); - p.OnConfigUpdated(0, key, config); - - // Expect to get no metrics, but snapshot specified above in uidmap. - vector bytes; - p.onDumpReport(key, 1, false, true, ADB_DUMP, FAST, &bytes); - - ConfigMetricsReportList output; - output.ParseFromArray(bytes.data(), bytes.size()); - EXPECT_TRUE(output.reports_size() > 0); - auto uidmap = output.reports(0).uid_map(); - EXPECT_TRUE(uidmap.snapshots_size() > 0); - ASSERT_EQ(2, uidmap.snapshots(0).package_info_size()); -} - -TEST(StatsLogProcessorTest, TestEmptyConfigHasNoUidMap) { - // Setup simple config key corresponding to empty config. - sp m = new UidMap(); - sp pullerManager = new StatsPullerManager(); - m->updateMap(1, {1, 2}, {1, 2}, {String16("v1"), String16("v2")}, - {String16("p1"), String16("p2")}, {String16(""), String16("")}); - sp anomalyAlarmMonitor; - sp subscriberAlarmMonitor; - int broadcastCount = 0; - StatsLogProcessor p(m, pullerManager, anomalyAlarmMonitor, subscriberAlarmMonitor, 0, - [&broadcastCount](const ConfigKey& key) { - broadcastCount++; - return true; - }, - [](const int&, const vector&) {return true;}); - ConfigKey key(3, 4); - StatsdConfig config = MakeConfig(false); - p.OnConfigUpdated(0, key, config); - - // Expect to get no metrics, but snapshot specified above in uidmap. - vector bytes; - p.onDumpReport(key, 1, false, true, ADB_DUMP, FAST, &bytes); - - ConfigMetricsReportList output; - output.ParseFromArray(bytes.data(), bytes.size()); - EXPECT_TRUE(output.reports_size() > 0); - EXPECT_FALSE(output.reports(0).has_uid_map()); -} - -TEST(StatsLogProcessorTest, TestReportIncludesSubConfig) { - // Setup simple config key corresponding to empty config. - sp m = new UidMap(); - sp pullerManager = new StatsPullerManager(); - sp anomalyAlarmMonitor; - sp subscriberAlarmMonitor; - int broadcastCount = 0; - StatsLogProcessor p(m, pullerManager, anomalyAlarmMonitor, subscriberAlarmMonitor, 0, - [&broadcastCount](const ConfigKey& key) { - broadcastCount++; - return true; - }, - [](const int&, const vector&) {return true;}); - ConfigKey key(3, 4); - StatsdConfig config; - auto annotation = config.add_annotation(); - annotation->set_field_int64(1); - annotation->set_field_int32(2); - config.add_allowed_log_source("AID_ROOT"); - p.OnConfigUpdated(1, key, config); - - // Expect to get no metrics, but snapshot specified above in uidmap. - vector bytes; - p.onDumpReport(key, 1, false, true, ADB_DUMP, FAST, &bytes); - - ConfigMetricsReportList output; - output.ParseFromArray(bytes.data(), bytes.size()); - EXPECT_TRUE(output.reports_size() > 0); - auto report = output.reports(0); - ASSERT_EQ(1, report.annotation_size()); - EXPECT_EQ(1, report.annotation(0).field_int64()); - EXPECT_EQ(2, report.annotation(0).field_int32()); -} - -TEST(StatsLogProcessorTest, TestOnDumpReportEraseData) { - // Setup a simple config. - StatsdConfig config; - config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root. - auto wakelockAcquireMatcher = CreateAcquireWakelockAtomMatcher(); - *config.add_atom_matcher() = wakelockAcquireMatcher; - - auto countMetric = config.add_count_metric(); - countMetric->set_id(123456); - countMetric->set_what(wakelockAcquireMatcher.id()); - countMetric->set_bucket(FIVE_MINUTES); - - ConfigKey cfgKey; - sp processor = CreateStatsLogProcessor(1, 1, config, cfgKey); - - std::vector attributionUids = {111}; - std::vector attributionTags = {"App1"}; - std::unique_ptr event = - CreateAcquireWakelockEvent(2 /*timestamp*/, attributionUids, attributionTags, "wl1"); - processor->OnLogEvent(event.get()); - - vector bytes; - ConfigMetricsReportList output; - - // Dump report WITHOUT erasing data. - processor->onDumpReport(cfgKey, 3, true, false /* Do NOT erase data. */, ADB_DUMP, FAST, - &bytes); - output.ParseFromArray(bytes.data(), bytes.size()); - ASSERT_EQ(output.reports_size(), 1); - ASSERT_EQ(output.reports(0).metrics_size(), 1); - ASSERT_EQ(output.reports(0).metrics(0).count_metrics().data_size(), 1); - - // Dump report WITH erasing data. There should be data since we didn't previously erase it. - processor->onDumpReport(cfgKey, 4, true, true /* DO erase data. */, ADB_DUMP, FAST, &bytes); - output.ParseFromArray(bytes.data(), bytes.size()); - ASSERT_EQ(output.reports_size(), 1); - ASSERT_EQ(output.reports(0).metrics_size(), 1); - ASSERT_EQ(output.reports(0).metrics(0).count_metrics().data_size(), 1); - - // Dump report again. There should be no data since we erased it. - processor->onDumpReport(cfgKey, 5, true, true /* DO erase data. */, ADB_DUMP, FAST, &bytes); - output.ParseFromArray(bytes.data(), bytes.size()); - // We don't care whether statsd has a report, as long as it has no count metrics in it. - bool noData = output.reports_size() == 0 || output.reports(0).metrics_size() == 0 || - output.reports(0).metrics(0).count_metrics().data_size() == 0; - EXPECT_TRUE(noData); -} - -TEST(StatsLogProcessorTest, TestPullUidProviderSetOnConfigUpdate) { - // Setup simple config key corresponding to empty config. - sp m = new UidMap(); - sp pullerManager = new StatsPullerManager(); - sp anomalyAlarmMonitor; - sp subscriberAlarmMonitor; - StatsLogProcessor p( - m, pullerManager, anomalyAlarmMonitor, subscriberAlarmMonitor, 0, - [](const ConfigKey& key) { return true; }, - [](const int&, const vector&) { return true; }); - ConfigKey key(3, 4); - StatsdConfig config = MakeConfig(false); - p.OnConfigUpdated(0, key, config); - EXPECT_NE(pullerManager->mPullUidProviders.find(key), pullerManager->mPullUidProviders.end()); - - config.add_default_pull_packages("AID_STATSD"); - p.OnConfigUpdated(5, key, config); - EXPECT_NE(pullerManager->mPullUidProviders.find(key), pullerManager->mPullUidProviders.end()); - - p.OnConfigRemoved(key); - EXPECT_EQ(pullerManager->mPullUidProviders.find(key), pullerManager->mPullUidProviders.end()); -} - -TEST(StatsLogProcessorTest, InvalidConfigRemoved) { - // Setup simple config key corresponding to empty config. - StatsdStats::getInstance().reset(); - sp m = new UidMap(); - sp pullerManager = new StatsPullerManager(); - m->updateMap(1, {1, 2}, {1, 2}, {String16("v1"), String16("v2")}, - {String16("p1"), String16("p2")}, {String16(""), String16("")}); - sp anomalyAlarmMonitor; - sp subscriberAlarmMonitor; - StatsLogProcessor p(m, pullerManager, anomalyAlarmMonitor, subscriberAlarmMonitor, 0, - [](const ConfigKey& key) { return true; }, - [](const int&, const vector&) {return true;}); - ConfigKey key(3, 4); - StatsdConfig config = MakeConfig(true); - p.OnConfigUpdated(0, key, config); - EXPECT_EQ(1, p.mMetricsManagers.size()); - EXPECT_NE(p.mMetricsManagers.find(key), p.mMetricsManagers.end()); - // Cannot assert the size of mConfigStats since it is static and does not get cleared on reset. - EXPECT_NE(StatsdStats::getInstance().mConfigStats.end(), - StatsdStats::getInstance().mConfigStats.find(key)); - EXPECT_EQ(0, StatsdStats::getInstance().mIceBox.size()); - - StatsdConfig invalidConfig = MakeConfig(true); - invalidConfig.clear_allowed_log_source(); - p.OnConfigUpdated(0, key, invalidConfig); - EXPECT_EQ(0, p.mMetricsManagers.size()); - // The current configs should not contain the invalid config. - EXPECT_EQ(StatsdStats::getInstance().mConfigStats.end(), - StatsdStats::getInstance().mConfigStats.find(key)); - // Both "config" and "invalidConfig" should be in the icebox. - EXPECT_EQ(2, StatsdStats::getInstance().mIceBox.size()); - -} - - -TEST(StatsLogProcessorTest, TestActiveConfigMetricDiskWriteRead) { - int uid = 1111; - - // Setup a simple config, no activation - StatsdConfig config1; - int64_t cfgId1 = 12341; - config1.set_id(cfgId1); - config1.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root. - auto wakelockAcquireMatcher = CreateAcquireWakelockAtomMatcher(); - *config1.add_atom_matcher() = wakelockAcquireMatcher; - - long metricId1 = 1234561; - long metricId2 = 1234562; - auto countMetric1 = config1.add_count_metric(); - countMetric1->set_id(metricId1); - countMetric1->set_what(wakelockAcquireMatcher.id()); - countMetric1->set_bucket(FIVE_MINUTES); - - auto countMetric2 = config1.add_count_metric(); - countMetric2->set_id(metricId2); - countMetric2->set_what(wakelockAcquireMatcher.id()); - countMetric2->set_bucket(FIVE_MINUTES); - - ConfigKey cfgKey1(uid, cfgId1); - - // Add another config, with two metrics, one with activation - StatsdConfig config2; - int64_t cfgId2 = 12342; - config2.set_id(cfgId2); - config2.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root. - *config2.add_atom_matcher() = wakelockAcquireMatcher; - - long metricId3 = 1234561; - long metricId4 = 1234562; - - auto countMetric3 = config2.add_count_metric(); - countMetric3->set_id(metricId3); - countMetric3->set_what(wakelockAcquireMatcher.id()); - countMetric3->set_bucket(FIVE_MINUTES); - - auto countMetric4 = config2.add_count_metric(); - countMetric4->set_id(metricId4); - countMetric4->set_what(wakelockAcquireMatcher.id()); - countMetric4->set_bucket(FIVE_MINUTES); - - auto metric3Activation = config2.add_metric_activation(); - metric3Activation->set_metric_id(metricId3); - metric3Activation->set_activation_type(ACTIVATE_IMMEDIATELY); - auto metric3ActivationTrigger = metric3Activation->add_event_activation(); - metric3ActivationTrigger->set_atom_matcher_id(wakelockAcquireMatcher.id()); - metric3ActivationTrigger->set_ttl_seconds(100); - - ConfigKey cfgKey2(uid, cfgId2); - - // Add another config, with two metrics, both with activations - StatsdConfig config3; - int64_t cfgId3 = 12343; - config3.set_id(cfgId3); - config3.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root. - *config3.add_atom_matcher() = wakelockAcquireMatcher; - - long metricId5 = 1234565; - long metricId6 = 1234566; - auto countMetric5 = config3.add_count_metric(); - countMetric5->set_id(metricId5); - countMetric5->set_what(wakelockAcquireMatcher.id()); - countMetric5->set_bucket(FIVE_MINUTES); - - auto countMetric6 = config3.add_count_metric(); - countMetric6->set_id(metricId6); - countMetric6->set_what(wakelockAcquireMatcher.id()); - countMetric6->set_bucket(FIVE_MINUTES); - - auto metric5Activation = config3.add_metric_activation(); - metric5Activation->set_metric_id(metricId5); - metric5Activation->set_activation_type(ACTIVATE_IMMEDIATELY); - auto metric5ActivationTrigger = metric5Activation->add_event_activation(); - metric5ActivationTrigger->set_atom_matcher_id(wakelockAcquireMatcher.id()); - metric5ActivationTrigger->set_ttl_seconds(100); - - auto metric6Activation = config3.add_metric_activation(); - metric6Activation->set_metric_id(metricId6); - metric6Activation->set_activation_type(ACTIVATE_IMMEDIATELY); - auto metric6ActivationTrigger = metric6Activation->add_event_activation(); - metric6ActivationTrigger->set_atom_matcher_id(wakelockAcquireMatcher.id()); - metric6ActivationTrigger->set_ttl_seconds(200); - - ConfigKey cfgKey3(uid, cfgId3); - - sp m = new UidMap(); - sp pullerManager = new StatsPullerManager(); - sp anomalyAlarmMonitor; - sp subscriberAlarmMonitor; - vector activeConfigsBroadcast; - - long timeBase1 = 1; - int broadcastCount = 0; - StatsLogProcessor processor( - m, pullerManager, anomalyAlarmMonitor, subscriberAlarmMonitor, timeBase1, - [](const ConfigKey& key) { return true; }, - [&uid, &broadcastCount, &activeConfigsBroadcast](const int& broadcastUid, - const vector& activeConfigs) { - broadcastCount++; - EXPECT_EQ(broadcastUid, uid); - activeConfigsBroadcast.clear(); - activeConfigsBroadcast.insert(activeConfigsBroadcast.end(), activeConfigs.begin(), - activeConfigs.end()); - return true; - }); - - processor.OnConfigUpdated(1, cfgKey1, config1); - processor.OnConfigUpdated(2, cfgKey2, config2); - processor.OnConfigUpdated(3, cfgKey3, config3); - - ASSERT_EQ(3, processor.mMetricsManagers.size()); - - // Expect the first config and both metrics in it to be active. - auto it = processor.mMetricsManagers.find(cfgKey1); - EXPECT_TRUE(it != processor.mMetricsManagers.end()); - auto& metricsManager1 = it->second; - EXPECT_TRUE(metricsManager1->isActive()); - - auto metricIt = metricsManager1->mAllMetricProducers.begin(); - for (; metricIt != metricsManager1->mAllMetricProducers.end(); metricIt++) { - if ((*metricIt)->getMetricId() == metricId1) { - break; - } - } - EXPECT_TRUE(metricIt != metricsManager1->mAllMetricProducers.end()); - auto& metricProducer1 = *metricIt; - EXPECT_TRUE(metricProducer1->isActive()); - - metricIt = metricsManager1->mAllMetricProducers.begin(); - for (; metricIt != metricsManager1->mAllMetricProducers.end(); metricIt++) { - if ((*metricIt)->getMetricId() == metricId2) { - break; - } - } - EXPECT_TRUE(metricIt != metricsManager1->mAllMetricProducers.end()); - auto& metricProducer2 = *metricIt; - EXPECT_TRUE(metricProducer2->isActive()); - - // Expect config 2 to be active. Metric 3 shouldn't be active, metric 4 should be active. - it = processor.mMetricsManagers.find(cfgKey2); - EXPECT_TRUE(it != processor.mMetricsManagers.end()); - auto& metricsManager2 = it->second; - EXPECT_TRUE(metricsManager2->isActive()); - - metricIt = metricsManager2->mAllMetricProducers.begin(); - for (; metricIt != metricsManager2->mAllMetricProducers.end(); metricIt++) { - if ((*metricIt)->getMetricId() == metricId3) { - break; - } - } - EXPECT_TRUE(metricIt != metricsManager2->mAllMetricProducers.end()); - auto& metricProducer3 = *metricIt; - EXPECT_FALSE(metricProducer3->isActive()); - - metricIt = metricsManager2->mAllMetricProducers.begin(); - for (; metricIt != metricsManager2->mAllMetricProducers.end(); metricIt++) { - if ((*metricIt)->getMetricId() == metricId4) { - break; - } - } - EXPECT_TRUE(metricIt != metricsManager2->mAllMetricProducers.end()); - auto& metricProducer4 = *metricIt; - EXPECT_TRUE(metricProducer4->isActive()); - - // Expect the third config and both metrics in it to be inactive. - it = processor.mMetricsManagers.find(cfgKey3); - EXPECT_TRUE(it != processor.mMetricsManagers.end()); - auto& metricsManager3 = it->second; - EXPECT_FALSE(metricsManager3->isActive()); - - metricIt = metricsManager3->mAllMetricProducers.begin(); - for (; metricIt != metricsManager2->mAllMetricProducers.end(); metricIt++) { - if ((*metricIt)->getMetricId() == metricId5) { - break; - } - } - EXPECT_TRUE(metricIt != metricsManager3->mAllMetricProducers.end()); - auto& metricProducer5 = *metricIt; - EXPECT_FALSE(metricProducer5->isActive()); - - metricIt = metricsManager3->mAllMetricProducers.begin(); - for (; metricIt != metricsManager3->mAllMetricProducers.end(); metricIt++) { - if ((*metricIt)->getMetricId() == metricId6) { - break; - } - } - EXPECT_TRUE(metricIt != metricsManager3->mAllMetricProducers.end()); - auto& metricProducer6 = *metricIt; - EXPECT_FALSE(metricProducer6->isActive()); - - // No broadcast for active configs should have happened yet. - EXPECT_EQ(broadcastCount, 0); - - // Activate all 3 metrics that were not active. - std::vector attributionUids = {111}; - std::vector attributionTags = {"App1"}; - std::unique_ptr event = - CreateAcquireWakelockEvent(timeBase1 + 100, attributionUids, attributionTags, "wl1"); - processor.OnLogEvent(event.get()); - - // Assert that all 3 configs are active. - EXPECT_TRUE(metricsManager1->isActive()); - EXPECT_TRUE(metricsManager2->isActive()); - EXPECT_TRUE(metricsManager3->isActive()); - - // A broadcast should have happened, and all 3 configs should be active in the broadcast. - EXPECT_EQ(broadcastCount, 1); - ASSERT_EQ(activeConfigsBroadcast.size(), 3); - EXPECT_TRUE(std::find(activeConfigsBroadcast.begin(), activeConfigsBroadcast.end(), cfgId1) != - activeConfigsBroadcast.end()); - EXPECT_TRUE(std::find(activeConfigsBroadcast.begin(), activeConfigsBroadcast.end(), cfgId2) != - activeConfigsBroadcast.end()); - EXPECT_TRUE(std::find(activeConfigsBroadcast.begin(), activeConfigsBroadcast.end(), cfgId3) != - activeConfigsBroadcast.end()); - - // When we shut down, metrics 3 & 5 have 100ns remaining, metric 6 has 100s + 100ns. - int64_t shutDownTime = timeBase1 + 100 * NS_PER_SEC; - processor.SaveActiveConfigsToDisk(shutDownTime); - const int64_t ttl3 = event->GetElapsedTimestampNs() + - metric3ActivationTrigger->ttl_seconds() * NS_PER_SEC - shutDownTime; - const int64_t ttl5 = event->GetElapsedTimestampNs() + - metric5ActivationTrigger->ttl_seconds() * NS_PER_SEC - shutDownTime; - const int64_t ttl6 = event->GetElapsedTimestampNs() + - metric6ActivationTrigger->ttl_seconds() * NS_PER_SEC - shutDownTime; - - // Create a second StatsLogProcessor and push the same 3 configs. - long timeBase2 = 1000; - sp processor2 = - CreateStatsLogProcessor(timeBase2, timeBase2, config1, cfgKey1); - processor2->OnConfigUpdated(timeBase2, cfgKey2, config2); - processor2->OnConfigUpdated(timeBase2, cfgKey3, config3); - - ASSERT_EQ(3, processor2->mMetricsManagers.size()); - - // First config and both metrics are active. - it = processor2->mMetricsManagers.find(cfgKey1); - EXPECT_TRUE(it != processor2->mMetricsManagers.end()); - auto& metricsManager1001 = it->second; - EXPECT_TRUE(metricsManager1001->isActive()); - - metricIt = metricsManager1001->mAllMetricProducers.begin(); - for (; metricIt != metricsManager1001->mAllMetricProducers.end(); metricIt++) { - if ((*metricIt)->getMetricId() == metricId1) { - break; - } - } - EXPECT_TRUE(metricIt != metricsManager1001->mAllMetricProducers.end()); - auto& metricProducer1001 = *metricIt; - EXPECT_TRUE(metricProducer1001->isActive()); - - metricIt = metricsManager1001->mAllMetricProducers.begin(); - for (; metricIt != metricsManager1001->mAllMetricProducers.end(); metricIt++) { - if ((*metricIt)->getMetricId() == metricId2) { - break; - } - } - EXPECT_TRUE(metricIt != metricsManager1001->mAllMetricProducers.end()); - auto& metricProducer1002 = *metricIt; - EXPECT_TRUE(metricProducer1002->isActive()); - - // Second config is active. Metric 3 is inactive, metric 4 is active. - it = processor2->mMetricsManagers.find(cfgKey2); - EXPECT_TRUE(it != processor2->mMetricsManagers.end()); - auto& metricsManager1002 = it->second; - EXPECT_TRUE(metricsManager1002->isActive()); - - metricIt = metricsManager1002->mAllMetricProducers.begin(); - for (; metricIt != metricsManager1002->mAllMetricProducers.end(); metricIt++) { - if ((*metricIt)->getMetricId() == metricId3) { - break; - } - } - EXPECT_TRUE(metricIt != metricsManager1002->mAllMetricProducers.end()); - auto& metricProducer1003 = *metricIt; - EXPECT_FALSE(metricProducer1003->isActive()); - - metricIt = metricsManager1002->mAllMetricProducers.begin(); - for (; metricIt != metricsManager1002->mAllMetricProducers.end(); metricIt++) { - if ((*metricIt)->getMetricId() == metricId4) { - break; - } - } - EXPECT_TRUE(metricIt != metricsManager1002->mAllMetricProducers.end()); - auto& metricProducer1004 = *metricIt; - EXPECT_TRUE(metricProducer1004->isActive()); - - // Config 3 is inactive. both metrics are inactive. - it = processor2->mMetricsManagers.find(cfgKey3); - EXPECT_TRUE(it != processor2->mMetricsManagers.end()); - auto& metricsManager1003 = it->second; - EXPECT_FALSE(metricsManager1003->isActive()); - ASSERT_EQ(2, metricsManager1003->mAllMetricProducers.size()); - - metricIt = metricsManager1003->mAllMetricProducers.begin(); - for (; metricIt != metricsManager1002->mAllMetricProducers.end(); metricIt++) { - if ((*metricIt)->getMetricId() == metricId5) { - break; - } - } - EXPECT_TRUE(metricIt != metricsManager1003->mAllMetricProducers.end()); - auto& metricProducer1005 = *metricIt; - EXPECT_FALSE(metricProducer1005->isActive()); - - metricIt = metricsManager1003->mAllMetricProducers.begin(); - for (; metricIt != metricsManager1003->mAllMetricProducers.end(); metricIt++) { - if ((*metricIt)->getMetricId() == metricId6) { - break; - } - } - EXPECT_TRUE(metricIt != metricsManager1003->mAllMetricProducers.end()); - auto& metricProducer1006 = *metricIt; - EXPECT_FALSE(metricProducer1006->isActive()); - - // Assert that all 3 metrics with activation are inactive and that the ttls were properly set. - EXPECT_FALSE(metricProducer1003->isActive()); - const auto& activation1003 = metricProducer1003->mEventActivationMap.begin()->second; - EXPECT_EQ(100 * NS_PER_SEC, activation1003->ttl_ns); - EXPECT_EQ(0, activation1003->start_ns); - EXPECT_FALSE(metricProducer1005->isActive()); - const auto& activation1005 = metricProducer1005->mEventActivationMap.begin()->second; - EXPECT_EQ(100 * NS_PER_SEC, activation1005->ttl_ns); - EXPECT_EQ(0, activation1005->start_ns); - EXPECT_FALSE(metricProducer1006->isActive()); - const auto& activation1006 = metricProducer1006->mEventActivationMap.begin()->second; - EXPECT_EQ(200 * NS_PER_SEC, activation1006->ttl_ns); - EXPECT_EQ(0, activation1006->start_ns); - - processor2->LoadActiveConfigsFromDisk(); - - // After loading activations from disk, assert that all 3 metrics are active. - EXPECT_TRUE(metricProducer1003->isActive()); - EXPECT_EQ(timeBase2 + ttl3 - activation1003->ttl_ns, activation1003->start_ns); - EXPECT_TRUE(metricProducer1005->isActive()); - EXPECT_EQ(timeBase2 + ttl5 - activation1005->ttl_ns, activation1005->start_ns); - EXPECT_TRUE(metricProducer1006->isActive()); - EXPECT_EQ(timeBase2 + ttl6 - activation1006->ttl_ns, activation1003->start_ns); - - // Make sure no more broadcasts have happened. - EXPECT_EQ(broadcastCount, 1); -} - -TEST(StatsLogProcessorTest, TestActivationOnBoot) { - int uid = 1111; - - StatsdConfig config1; - config1.set_id(12341); - config1.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root. - auto wakelockAcquireMatcher = CreateAcquireWakelockAtomMatcher(); - *config1.add_atom_matcher() = wakelockAcquireMatcher; - - long metricId1 = 1234561; - long metricId2 = 1234562; - auto countMetric1 = config1.add_count_metric(); - countMetric1->set_id(metricId1); - countMetric1->set_what(wakelockAcquireMatcher.id()); - countMetric1->set_bucket(FIVE_MINUTES); - - auto countMetric2 = config1.add_count_metric(); - countMetric2->set_id(metricId2); - countMetric2->set_what(wakelockAcquireMatcher.id()); - countMetric2->set_bucket(FIVE_MINUTES); - - auto metric1Activation = config1.add_metric_activation(); - metric1Activation->set_metric_id(metricId1); - metric1Activation->set_activation_type(ACTIVATE_ON_BOOT); - auto metric1ActivationTrigger = metric1Activation->add_event_activation(); - metric1ActivationTrigger->set_atom_matcher_id(wakelockAcquireMatcher.id()); - metric1ActivationTrigger->set_ttl_seconds(100); - - ConfigKey cfgKey1(uid, 12341); - long timeBase1 = 1; - sp processor = - CreateStatsLogProcessor(timeBase1, timeBase1, config1, cfgKey1); - - ASSERT_EQ(1, processor->mMetricsManagers.size()); - auto it = processor->mMetricsManagers.find(cfgKey1); - EXPECT_TRUE(it != processor->mMetricsManagers.end()); - auto& metricsManager1 = it->second; - EXPECT_TRUE(metricsManager1->isActive()); - - auto metricIt = metricsManager1->mAllMetricProducers.begin(); - for (; metricIt != metricsManager1->mAllMetricProducers.end(); metricIt++) { - if ((*metricIt)->getMetricId() == metricId1) { - break; - } - } - EXPECT_TRUE(metricIt != metricsManager1->mAllMetricProducers.end()); - auto& metricProducer1 = *metricIt; - EXPECT_FALSE(metricProducer1->isActive()); - - metricIt = metricsManager1->mAllMetricProducers.begin(); - for (; metricIt != metricsManager1->mAllMetricProducers.end(); metricIt++) { - if ((*metricIt)->getMetricId() == metricId2) { - break; - } - } - EXPECT_TRUE(metricIt != metricsManager1->mAllMetricProducers.end()); - auto& metricProducer2 = *metricIt; - EXPECT_TRUE(metricProducer2->isActive()); - - const auto& activation1 = metricProducer1->mEventActivationMap.begin()->second; - EXPECT_EQ(100 * NS_PER_SEC, activation1->ttl_ns); - EXPECT_EQ(0, activation1->start_ns); - EXPECT_EQ(kNotActive, activation1->state); - - std::vector attributionUids = {111}; - std::vector attributionTags = {"App1"}; - std::unique_ptr event = - CreateAcquireWakelockEvent(timeBase1 + 100, attributionUids, attributionTags, "wl1"); - processor->OnLogEvent(event.get()); - - EXPECT_FALSE(metricProducer1->isActive()); - EXPECT_EQ(0, activation1->start_ns); - EXPECT_EQ(kActiveOnBoot, activation1->state); - - int64_t shutDownTime = timeBase1 + 100 * NS_PER_SEC; - processor->SaveActiveConfigsToDisk(shutDownTime); - EXPECT_FALSE(metricProducer1->isActive()); - const int64_t ttl1 = metric1ActivationTrigger->ttl_seconds() * NS_PER_SEC; - - long timeBase2 = 1000; - sp processor2 = - CreateStatsLogProcessor(timeBase2, timeBase2, config1, cfgKey1); - - ASSERT_EQ(1, processor2->mMetricsManagers.size()); - it = processor2->mMetricsManagers.find(cfgKey1); - EXPECT_TRUE(it != processor2->mMetricsManagers.end()); - auto& metricsManager1001 = it->second; - EXPECT_TRUE(metricsManager1001->isActive()); - - metricIt = metricsManager1001->mAllMetricProducers.begin(); - for (; metricIt != metricsManager1001->mAllMetricProducers.end(); metricIt++) { - if ((*metricIt)->getMetricId() == metricId1) { - break; - } - } - EXPECT_TRUE(metricIt != metricsManager1001->mAllMetricProducers.end()); - auto& metricProducer1001 = *metricIt; - EXPECT_FALSE(metricProducer1001->isActive()); - - metricIt = metricsManager1001->mAllMetricProducers.begin(); - for (; metricIt != metricsManager1001->mAllMetricProducers.end(); metricIt++) { - if ((*metricIt)->getMetricId() == metricId2) { - break; - } - } - EXPECT_TRUE(metricIt != metricsManager1001->mAllMetricProducers.end()); - auto& metricProducer1002 = *metricIt; - EXPECT_TRUE(metricProducer1002->isActive()); - - const auto& activation1001 = metricProducer1001->mEventActivationMap.begin()->second; - EXPECT_EQ(100 * NS_PER_SEC, activation1001->ttl_ns); - EXPECT_EQ(0, activation1001->start_ns); - EXPECT_EQ(kNotActive, activation1001->state); - - processor2->LoadActiveConfigsFromDisk(); - - EXPECT_TRUE(metricProducer1001->isActive()); - EXPECT_EQ(timeBase2 + ttl1 - activation1001->ttl_ns, activation1001->start_ns); - EXPECT_EQ(kActive, activation1001->state); -} - -TEST(StatsLogProcessorTest, TestActivationOnBootMultipleActivations) { - int uid = 1111; - - // Create config with 2 metrics: - // Metric 1: Activate on boot with 2 activations - // Metric 2: Always active - StatsdConfig config1; - config1.set_id(12341); - config1.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root. - auto wakelockAcquireMatcher = CreateAcquireWakelockAtomMatcher(); - auto screenOnMatcher = CreateScreenTurnedOnAtomMatcher(); - *config1.add_atom_matcher() = wakelockAcquireMatcher; - *config1.add_atom_matcher() = screenOnMatcher; - - long metricId1 = 1234561; - long metricId2 = 1234562; - - auto countMetric1 = config1.add_count_metric(); - countMetric1->set_id(metricId1); - countMetric1->set_what(wakelockAcquireMatcher.id()); - countMetric1->set_bucket(FIVE_MINUTES); - - auto countMetric2 = config1.add_count_metric(); - countMetric2->set_id(metricId2); - countMetric2->set_what(wakelockAcquireMatcher.id()); - countMetric2->set_bucket(FIVE_MINUTES); - - auto metric1Activation = config1.add_metric_activation(); - metric1Activation->set_metric_id(metricId1); - metric1Activation->set_activation_type(ACTIVATE_ON_BOOT); - auto metric1ActivationTrigger1 = metric1Activation->add_event_activation(); - metric1ActivationTrigger1->set_atom_matcher_id(wakelockAcquireMatcher.id()); - metric1ActivationTrigger1->set_ttl_seconds(100); - auto metric1ActivationTrigger2 = metric1Activation->add_event_activation(); - metric1ActivationTrigger2->set_atom_matcher_id(screenOnMatcher.id()); - metric1ActivationTrigger2->set_ttl_seconds(200); - - ConfigKey cfgKey1(uid, 12341); - long timeBase1 = 1; - sp processor = - CreateStatsLogProcessor(timeBase1, timeBase1, config1, cfgKey1); - - // Metric 1 is not active. - // Metric 2 is active. - // {{{--------------------------------------------------------------------------- - ASSERT_EQ(1, processor->mMetricsManagers.size()); - auto it = processor->mMetricsManagers.find(cfgKey1); - EXPECT_TRUE(it != processor->mMetricsManagers.end()); - auto& metricsManager1 = it->second; - EXPECT_TRUE(metricsManager1->isActive()); - - auto metricIt = metricsManager1->mAllMetricProducers.begin(); - for (; metricIt != metricsManager1->mAllMetricProducers.end(); metricIt++) { - if ((*metricIt)->getMetricId() == metricId1) { - break; - } - } - EXPECT_TRUE(metricIt != metricsManager1->mAllMetricProducers.end()); - auto& metricProducer1 = *metricIt; - EXPECT_FALSE(metricProducer1->isActive()); - - metricIt = metricsManager1->mAllMetricProducers.begin(); - for (; metricIt != metricsManager1->mAllMetricProducers.end(); metricIt++) { - if ((*metricIt)->getMetricId() == metricId2) { - break; - } - } - EXPECT_TRUE(metricIt != metricsManager1->mAllMetricProducers.end()); - auto& metricProducer2 = *metricIt; - EXPECT_TRUE(metricProducer2->isActive()); - - int i = 0; - for (; i < metricsManager1->mAllAtomMatchingTrackers.size(); i++) { - if (metricsManager1->mAllAtomMatchingTrackers[i]->getId() == - metric1ActivationTrigger1->atom_matcher_id()) { - break; - } - } - const auto& activation1 = metricProducer1->mEventActivationMap.at(i); - EXPECT_EQ(100 * NS_PER_SEC, activation1->ttl_ns); - EXPECT_EQ(0, activation1->start_ns); - EXPECT_EQ(kNotActive, activation1->state); - - i = 0; - for (; i < metricsManager1->mAllAtomMatchingTrackers.size(); i++) { - if (metricsManager1->mAllAtomMatchingTrackers[i]->getId() == - metric1ActivationTrigger2->atom_matcher_id()) { - break; - } - } - const auto& activation2 = metricProducer1->mEventActivationMap.at(i); - EXPECT_EQ(200 * NS_PER_SEC, activation2->ttl_ns); - EXPECT_EQ(0, activation2->start_ns); - EXPECT_EQ(kNotActive, activation2->state); - // }}}------------------------------------------------------------------------------ - - // Trigger Activation 1 for Metric 1 - std::vector attributionUids = {111}; - std::vector attributionTags = {"App1"}; - std::unique_ptr event = - CreateAcquireWakelockEvent(timeBase1 + 100, attributionUids, attributionTags, "wl1"); - processor->OnLogEvent(event.get()); - - // Metric 1 is not active; Activation 1 set to kActiveOnBoot - // Metric 2 is active. - // {{{--------------------------------------------------------------------------- - EXPECT_FALSE(metricProducer1->isActive()); - EXPECT_EQ(0, activation1->start_ns); - EXPECT_EQ(kActiveOnBoot, activation1->state); - EXPECT_EQ(0, activation2->start_ns); - EXPECT_EQ(kNotActive, activation2->state); - - EXPECT_TRUE(metricProducer2->isActive()); - // }}}----------------------------------------------------------------------------- - - // Simulate shutdown by saving state to disk - int64_t shutDownTime = timeBase1 + 100 * NS_PER_SEC; - processor->SaveActiveConfigsToDisk(shutDownTime); - EXPECT_FALSE(metricProducer1->isActive()); - int64_t ttl1 = metric1ActivationTrigger1->ttl_seconds() * NS_PER_SEC; - - // Simulate device restarted state by creating new instance of StatsLogProcessor with the - // same config. - long timeBase2 = 1000; - sp processor2 = - CreateStatsLogProcessor(timeBase2, timeBase2, config1, cfgKey1); - - // Metric 1 is not active. - // Metric 2 is active. - // {{{--------------------------------------------------------------------------- - ASSERT_EQ(1, processor2->mMetricsManagers.size()); - it = processor2->mMetricsManagers.find(cfgKey1); - EXPECT_TRUE(it != processor2->mMetricsManagers.end()); - auto& metricsManager1001 = it->second; - EXPECT_TRUE(metricsManager1001->isActive()); - - metricIt = metricsManager1001->mAllMetricProducers.begin(); - for (; metricIt != metricsManager1001->mAllMetricProducers.end(); metricIt++) { - if ((*metricIt)->getMetricId() == metricId1) { - break; - } - } - EXPECT_TRUE(metricIt != metricsManager1001->mAllMetricProducers.end()); - auto& metricProducer1001 = *metricIt; - EXPECT_FALSE(metricProducer1001->isActive()); - - metricIt = metricsManager1001->mAllMetricProducers.begin(); - for (; metricIt != metricsManager1001->mAllMetricProducers.end(); metricIt++) { - if ((*metricIt)->getMetricId() == metricId2) { - break; - } - } - EXPECT_TRUE(metricIt != metricsManager1001->mAllMetricProducers.end()); - auto& metricProducer1002 = *metricIt; - EXPECT_TRUE(metricProducer1002->isActive()); - - i = 0; - for (; i < metricsManager1001->mAllAtomMatchingTrackers.size(); i++) { - if (metricsManager1001->mAllAtomMatchingTrackers[i]->getId() == - metric1ActivationTrigger1->atom_matcher_id()) { - break; - } - } - const auto& activation1001_1 = metricProducer1001->mEventActivationMap.at(i); - EXPECT_EQ(100 * NS_PER_SEC, activation1001_1->ttl_ns); - EXPECT_EQ(0, activation1001_1->start_ns); - EXPECT_EQ(kNotActive, activation1001_1->state); - - i = 0; - for (; i < metricsManager1001->mAllAtomMatchingTrackers.size(); i++) { - if (metricsManager1001->mAllAtomMatchingTrackers[i]->getId() == - metric1ActivationTrigger2->atom_matcher_id()) { - break; - } - } - - const auto& activation1001_2 = metricProducer1001->mEventActivationMap.at(i); - EXPECT_EQ(200 * NS_PER_SEC, activation1001_2->ttl_ns); - EXPECT_EQ(0, activation1001_2->start_ns); - EXPECT_EQ(kNotActive, activation1001_2->state); - // }}}----------------------------------------------------------------------------------- - - // Load saved state from disk. - processor2->LoadActiveConfigsFromDisk(); - - // Metric 1 active; Activation 1 is active, Activation 2 is not active - // Metric 2 is active. - // {{{--------------------------------------------------------------------------- - EXPECT_TRUE(metricProducer1001->isActive()); - EXPECT_EQ(timeBase2 + ttl1 - activation1001_1->ttl_ns, activation1001_1->start_ns); - EXPECT_EQ(kActive, activation1001_1->state); - EXPECT_EQ(0, activation1001_2->start_ns); - EXPECT_EQ(kNotActive, activation1001_2->state); - - EXPECT_TRUE(metricProducer1002->isActive()); - // }}}-------------------------------------------------------------------------------- - - // Trigger Activation 2 for Metric 1. - auto screenOnEvent = - CreateScreenStateChangedEvent(timeBase2 + 200, android::view::DISPLAY_STATE_ON); - processor2->OnLogEvent(screenOnEvent.get()); - - // Metric 1 active; Activation 1 is active, Activation 2 is set to kActiveOnBoot - // Metric 2 is active. - // {{{--------------------------------------------------------------------------- - EXPECT_TRUE(metricProducer1001->isActive()); - EXPECT_EQ(timeBase2 + ttl1 - activation1001_1->ttl_ns, activation1001_1->start_ns); - EXPECT_EQ(kActive, activation1001_1->state); - EXPECT_EQ(0, activation1001_2->start_ns); - EXPECT_EQ(kActiveOnBoot, activation1001_2->state); - - EXPECT_TRUE(metricProducer1002->isActive()); - // }}}--------------------------------------------------------------------------- - - // Simulate shutdown by saving state to disk - shutDownTime = timeBase2 + 50 * NS_PER_SEC; - processor2->SaveActiveConfigsToDisk(shutDownTime); - EXPECT_TRUE(metricProducer1001->isActive()); - EXPECT_TRUE(metricProducer1002->isActive()); - ttl1 = timeBase2 + metric1ActivationTrigger1->ttl_seconds() * NS_PER_SEC - shutDownTime; - int64_t ttl2 = metric1ActivationTrigger2->ttl_seconds() * NS_PER_SEC; - - // Simulate device restarted state by creating new instance of StatsLogProcessor with the - // same config. - long timeBase3 = timeBase2 + 120 * NS_PER_SEC; - sp processor3 = - CreateStatsLogProcessor(timeBase3, timeBase3, config1, cfgKey1); - - // Metric 1 is not active. - // Metric 2 is active. - // {{{--------------------------------------------------------------------------- - ASSERT_EQ(1, processor3->mMetricsManagers.size()); - it = processor3->mMetricsManagers.find(cfgKey1); - EXPECT_TRUE(it != processor3->mMetricsManagers.end()); - auto& metricsManagerTimeBase3 = it->second; - EXPECT_TRUE(metricsManagerTimeBase3->isActive()); - - metricIt = metricsManagerTimeBase3->mAllMetricProducers.begin(); - for (; metricIt != metricsManagerTimeBase3->mAllMetricProducers.end(); metricIt++) { - if ((*metricIt)->getMetricId() == metricId1) { - break; - } - } - EXPECT_TRUE(metricIt != metricsManagerTimeBase3->mAllMetricProducers.end()); - auto& metricProducerTimeBase3_1 = *metricIt; - EXPECT_FALSE(metricProducerTimeBase3_1->isActive()); - - metricIt = metricsManagerTimeBase3->mAllMetricProducers.begin(); - for (; metricIt != metricsManagerTimeBase3->mAllMetricProducers.end(); metricIt++) { - if ((*metricIt)->getMetricId() == metricId2) { - break; - } - } - EXPECT_TRUE(metricIt != metricsManagerTimeBase3->mAllMetricProducers.end()); - auto& metricProducerTimeBase3_2 = *metricIt; - EXPECT_TRUE(metricProducerTimeBase3_2->isActive()); - - i = 0; - for (; i < metricsManagerTimeBase3->mAllAtomMatchingTrackers.size(); i++) { - if (metricsManagerTimeBase3->mAllAtomMatchingTrackers[i]->getId() == - metric1ActivationTrigger1->atom_matcher_id()) { - break; - } - } - const auto& activationTimeBase3_1 = metricProducerTimeBase3_1->mEventActivationMap.at(i); - EXPECT_EQ(100 * NS_PER_SEC, activationTimeBase3_1->ttl_ns); - EXPECT_EQ(0, activationTimeBase3_1->start_ns); - EXPECT_EQ(kNotActive, activationTimeBase3_1->state); - - i = 0; - for (; i < metricsManagerTimeBase3->mAllAtomMatchingTrackers.size(); i++) { - if (metricsManagerTimeBase3->mAllAtomMatchingTrackers[i]->getId() == - metric1ActivationTrigger2->atom_matcher_id()) { - break; - } - } - - const auto& activationTimeBase3_2 = metricProducerTimeBase3_1->mEventActivationMap.at(i); - EXPECT_EQ(200 * NS_PER_SEC, activationTimeBase3_2->ttl_ns); - EXPECT_EQ(0, activationTimeBase3_2->start_ns); - EXPECT_EQ(kNotActive, activationTimeBase3_2->state); - - EXPECT_TRUE(metricProducerTimeBase3_2->isActive()); - // }}}---------------------------------------------------------------------------------- - - // Load saved state from disk. - processor3->LoadActiveConfigsFromDisk(); - - // Metric 1 active: Activation 1 is active, Activation 2 is active - // Metric 2 is active. - // {{{--------------------------------------------------------------------------- - EXPECT_TRUE(metricProducerTimeBase3_1->isActive()); - EXPECT_EQ(timeBase3 + ttl1 - activationTimeBase3_1->ttl_ns, activationTimeBase3_1->start_ns); - EXPECT_EQ(kActive, activationTimeBase3_1->state); - EXPECT_EQ(timeBase3 + ttl2 - activationTimeBase3_2->ttl_ns, activationTimeBase3_2->start_ns); - EXPECT_EQ(kActive, activationTimeBase3_2->state); - - EXPECT_TRUE(metricProducerTimeBase3_2->isActive()); - // }}}------------------------------------------------------------------------------- - - // Trigger Activation 2 for Metric 1 again. - screenOnEvent = CreateScreenStateChangedEvent(timeBase3 + 100 * NS_PER_SEC, - android::view::DISPLAY_STATE_ON); - processor3->OnLogEvent(screenOnEvent.get()); - - // Metric 1 active; Activation 1 is not active, Activation 2 is set to active - // Metric 2 is active. - // {{{--------------------------------------------------------------------------- - EXPECT_TRUE(metricProducerTimeBase3_1->isActive()); - EXPECT_EQ(kNotActive, activationTimeBase3_1->state); - EXPECT_EQ(timeBase3 + ttl2 - activationTimeBase3_2->ttl_ns, activationTimeBase3_2->start_ns); - EXPECT_EQ(kActive, activationTimeBase3_2->state); - - EXPECT_TRUE(metricProducerTimeBase3_2->isActive()); - // }}}--------------------------------------------------------------------------- - - // Simulate shutdown by saving state to disk. - shutDownTime = timeBase3 + 500 * NS_PER_SEC; - processor3->SaveActiveConfigsToDisk(shutDownTime); - EXPECT_TRUE(metricProducer1001->isActive()); - EXPECT_TRUE(metricProducer1002->isActive()); - ttl1 = timeBase3 + ttl1 - shutDownTime; - ttl2 = timeBase3 + metric1ActivationTrigger2->ttl_seconds() * NS_PER_SEC - shutDownTime; - - // Simulate device restarted state by creating new instance of StatsLogProcessor with the - // same config. - long timeBase4 = timeBase3 + 600 * NS_PER_SEC; - sp processor4 = - CreateStatsLogProcessor(timeBase4, timeBase4, config1, cfgKey1); - - // Metric 1 is not active. - // Metric 2 is active. - // {{{--------------------------------------------------------------------------- - ASSERT_EQ(1, processor4->mMetricsManagers.size()); - it = processor4->mMetricsManagers.find(cfgKey1); - EXPECT_TRUE(it != processor4->mMetricsManagers.end()); - auto& metricsManagerTimeBase4 = it->second; - EXPECT_TRUE(metricsManagerTimeBase4->isActive()); - - metricIt = metricsManagerTimeBase4->mAllMetricProducers.begin(); - for (; metricIt != metricsManagerTimeBase4->mAllMetricProducers.end(); metricIt++) { - if ((*metricIt)->getMetricId() == metricId1) { - break; - } - } - EXPECT_TRUE(metricIt != metricsManagerTimeBase4->mAllMetricProducers.end()); - auto& metricProducerTimeBase4_1 = *metricIt; - EXPECT_FALSE(metricProducerTimeBase4_1->isActive()); - - metricIt = metricsManagerTimeBase4->mAllMetricProducers.begin(); - for (; metricIt != metricsManagerTimeBase4->mAllMetricProducers.end(); metricIt++) { - if ((*metricIt)->getMetricId() == metricId2) { - break; - } - } - EXPECT_TRUE(metricIt != metricsManagerTimeBase4->mAllMetricProducers.end()); - auto& metricProducerTimeBase4_2 = *metricIt; - EXPECT_TRUE(metricProducerTimeBase4_2->isActive()); - - i = 0; - for (; i < metricsManagerTimeBase4->mAllAtomMatchingTrackers.size(); i++) { - if (metricsManagerTimeBase4->mAllAtomMatchingTrackers[i]->getId() == - metric1ActivationTrigger1->atom_matcher_id()) { - break; - } - } - const auto& activationTimeBase4_1 = metricProducerTimeBase4_1->mEventActivationMap.at(i); - EXPECT_EQ(100 * NS_PER_SEC, activationTimeBase4_1->ttl_ns); - EXPECT_EQ(0, activationTimeBase4_1->start_ns); - EXPECT_EQ(kNotActive, activationTimeBase4_1->state); - - i = 0; - for (; i < metricsManagerTimeBase4->mAllAtomMatchingTrackers.size(); i++) { - if (metricsManagerTimeBase4->mAllAtomMatchingTrackers[i]->getId() == - metric1ActivationTrigger2->atom_matcher_id()) { - break; - } - } - - const auto& activationTimeBase4_2 = metricProducerTimeBase4_1->mEventActivationMap.at(i); - EXPECT_EQ(200 * NS_PER_SEC, activationTimeBase4_2->ttl_ns); - EXPECT_EQ(0, activationTimeBase4_2->start_ns); - EXPECT_EQ(kNotActive, activationTimeBase4_2->state); - - EXPECT_TRUE(metricProducerTimeBase4_2->isActive()); - // }}}---------------------------------------------------------------------------------- - - // Load saved state from disk. - processor4->LoadActiveConfigsFromDisk(); - - // Metric 1 active: Activation 1 is not active, Activation 2 is not active - // Metric 2 is active. - // {{{--------------------------------------------------------------------------- - EXPECT_FALSE(metricProducerTimeBase4_1->isActive()); - EXPECT_EQ(kNotActive, activationTimeBase4_1->state); - EXPECT_EQ(kNotActive, activationTimeBase4_2->state); - - EXPECT_TRUE(metricProducerTimeBase4_2->isActive()); - // }}}------------------------------------------------------------------------------- -} - -TEST(StatsLogProcessorTest, TestActivationOnBootMultipleActivationsDifferentActivationTypes) { - int uid = 1111; - - // Create config with 2 metrics: - // Metric 1: Activate on boot with 2 activations - // Metric 2: Always active - StatsdConfig config1; - config1.set_id(12341); - config1.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root. - auto wakelockAcquireMatcher = CreateAcquireWakelockAtomMatcher(); - auto screenOnMatcher = CreateScreenTurnedOnAtomMatcher(); - *config1.add_atom_matcher() = wakelockAcquireMatcher; - *config1.add_atom_matcher() = screenOnMatcher; - - long metricId1 = 1234561; - long metricId2 = 1234562; - - auto countMetric1 = config1.add_count_metric(); - countMetric1->set_id(metricId1); - countMetric1->set_what(wakelockAcquireMatcher.id()); - countMetric1->set_bucket(FIVE_MINUTES); - - auto countMetric2 = config1.add_count_metric(); - countMetric2->set_id(metricId2); - countMetric2->set_what(wakelockAcquireMatcher.id()); - countMetric2->set_bucket(FIVE_MINUTES); - - auto metric1Activation = config1.add_metric_activation(); - metric1Activation->set_metric_id(metricId1); - metric1Activation->set_activation_type(ACTIVATE_ON_BOOT); - auto metric1ActivationTrigger1 = metric1Activation->add_event_activation(); - metric1ActivationTrigger1->set_atom_matcher_id(wakelockAcquireMatcher.id()); - metric1ActivationTrigger1->set_ttl_seconds(100); - auto metric1ActivationTrigger2 = metric1Activation->add_event_activation(); - metric1ActivationTrigger2->set_atom_matcher_id(screenOnMatcher.id()); - metric1ActivationTrigger2->set_ttl_seconds(200); - metric1ActivationTrigger2->set_activation_type(ACTIVATE_IMMEDIATELY); - - ConfigKey cfgKey1(uid, 12341); - long timeBase1 = 1; - sp processor1 = - CreateStatsLogProcessor(timeBase1, timeBase1, config1, cfgKey1); - - // Metric 1 is not active. - // Metric 2 is active. - // {{{--------------------------------------------------------------------------- - ASSERT_EQ(1, processor1->mMetricsManagers.size()); - auto it = processor1->mMetricsManagers.find(cfgKey1); - EXPECT_TRUE(it != processor1->mMetricsManagers.end()); - auto& metricsManager1 = it->second; - EXPECT_TRUE(metricsManager1->isActive()); - - ASSERT_EQ(metricsManager1->mAllMetricProducers.size(), 2); - // We assume that the index of a MetricProducer within the mAllMetricProducers - // array follows the order in which metrics are added to the config. - auto& metricProducer1_1 = metricsManager1->mAllMetricProducers[0]; - EXPECT_EQ(metricProducer1_1->getMetricId(), metricId1); - EXPECT_FALSE(metricProducer1_1->isActive()); // inactive due to associated MetricActivation - - auto& metricProducer1_2 = metricsManager1->mAllMetricProducers[1]; - EXPECT_EQ(metricProducer1_2->getMetricId(), metricId2); - EXPECT_TRUE(metricProducer1_2->isActive()); - - ASSERT_EQ(metricProducer1_1->mEventActivationMap.size(), 2); - // The key in mEventActivationMap is the index of the associated atom matcher. We assume - // that matchers are indexed in the order that they are added to the config. - const auto& activation1_1_1 = metricProducer1_1->mEventActivationMap.at(0); - EXPECT_EQ(100 * NS_PER_SEC, activation1_1_1->ttl_ns); - EXPECT_EQ(0, activation1_1_1->start_ns); - EXPECT_EQ(kNotActive, activation1_1_1->state); - EXPECT_EQ(ACTIVATE_ON_BOOT, activation1_1_1->activationType); - - const auto& activation1_1_2 = metricProducer1_1->mEventActivationMap.at(1); - EXPECT_EQ(200 * NS_PER_SEC, activation1_1_2->ttl_ns); - EXPECT_EQ(0, activation1_1_2->start_ns); - EXPECT_EQ(kNotActive, activation1_1_2->state); - EXPECT_EQ(ACTIVATE_IMMEDIATELY, activation1_1_2->activationType); - // }}}------------------------------------------------------------------------------ - - // Trigger Activation 1 for Metric 1 - std::vector attributionUids = {111}; - std::vector attributionTags = {"App1"}; - std::unique_ptr event = - CreateAcquireWakelockEvent(timeBase1 + 100, attributionUids, attributionTags, "wl1"); - processor1->OnLogEvent(event.get()); - - // Metric 1 is not active; Activation 1 set to kActiveOnBoot - // Metric 2 is active. - // {{{--------------------------------------------------------------------------- - EXPECT_FALSE(metricProducer1_1->isActive()); - EXPECT_EQ(0, activation1_1_1->start_ns); - EXPECT_EQ(kActiveOnBoot, activation1_1_1->state); - EXPECT_EQ(0, activation1_1_2->start_ns); - EXPECT_EQ(kNotActive, activation1_1_2->state); - - EXPECT_TRUE(metricProducer1_2->isActive()); - // }}}----------------------------------------------------------------------------- - - // Simulate shutdown by saving state to disk - int64_t shutDownTime = timeBase1 + 100 * NS_PER_SEC; - processor1->SaveActiveConfigsToDisk(shutDownTime); - EXPECT_FALSE(metricProducer1_1->isActive()); - - // Simulate device restarted state by creating new instance of StatsLogProcessor with the - // same config. - long timeBase2 = 1000; - sp processor2 = - CreateStatsLogProcessor(timeBase2, timeBase2, config1, cfgKey1); - - // Metric 1 is not active. - // Metric 2 is active. - // {{{--------------------------------------------------------------------------- - ASSERT_EQ(1, processor2->mMetricsManagers.size()); - it = processor2->mMetricsManagers.find(cfgKey1); - EXPECT_TRUE(it != processor2->mMetricsManagers.end()); - auto& metricsManager2 = it->second; - EXPECT_TRUE(metricsManager2->isActive()); - - ASSERT_EQ(metricsManager2->mAllMetricProducers.size(), 2); - // We assume that the index of a MetricProducer within the mAllMetricProducers - // array follows the order in which metrics are added to the config. - auto& metricProducer2_1 = metricsManager2->mAllMetricProducers[0]; - EXPECT_EQ(metricProducer2_1->getMetricId(), metricId1); - EXPECT_FALSE(metricProducer2_1->isActive()); - - auto& metricProducer2_2 = metricsManager2->mAllMetricProducers[1]; - EXPECT_EQ(metricProducer2_2->getMetricId(), metricId2); - EXPECT_TRUE(metricProducer2_2->isActive()); - - ASSERT_EQ(metricProducer2_1->mEventActivationMap.size(), 2); - // The key in mEventActivationMap is the index of the associated atom matcher. We assume - // that matchers are indexed in the order that they are added to the config. - const auto& activation2_1_1 = metricProducer2_1->mEventActivationMap.at(0); - EXPECT_EQ(100 * NS_PER_SEC, activation2_1_1->ttl_ns); - EXPECT_EQ(0, activation2_1_1->start_ns); - EXPECT_EQ(kNotActive, activation2_1_1->state); - EXPECT_EQ(ACTIVATE_ON_BOOT, activation2_1_1->activationType); - - const auto& activation2_1_2 = metricProducer2_1->mEventActivationMap.at(1); - EXPECT_EQ(200 * NS_PER_SEC, activation2_1_2->ttl_ns); - EXPECT_EQ(0, activation2_1_2->start_ns); - EXPECT_EQ(kNotActive, activation2_1_2->state); - EXPECT_EQ(ACTIVATE_IMMEDIATELY, activation2_1_2->activationType); - // }}}----------------------------------------------------------------------------------- - - // Load saved state from disk. - processor2->LoadActiveConfigsFromDisk(); - - // Metric 1 active; Activation 1 is active, Activation 2 is not active - // Metric 2 is active. - // {{{--------------------------------------------------------------------------- - EXPECT_TRUE(metricProducer2_1->isActive()); - int64_t ttl1 = metric1ActivationTrigger1->ttl_seconds() * NS_PER_SEC; - EXPECT_EQ(timeBase2 + ttl1 - activation2_1_1->ttl_ns, activation2_1_1->start_ns); - EXPECT_EQ(kActive, activation2_1_1->state); - EXPECT_EQ(0, activation2_1_2->start_ns); - EXPECT_EQ(kNotActive, activation2_1_2->state); - - EXPECT_TRUE(metricProducer2_2->isActive()); - // }}}-------------------------------------------------------------------------------- - - // Trigger Activation 2 for Metric 1. - auto screenOnEvent = - CreateScreenStateChangedEvent(timeBase2 + 200, android::view::DISPLAY_STATE_ON); - processor2->OnLogEvent(screenOnEvent.get()); - - // Metric 1 active; Activation 1 is active, Activation 2 is active - // Metric 2 is active. - // {{{--------------------------------------------------------------------------- - EXPECT_TRUE(metricProducer2_1->isActive()); - EXPECT_EQ(timeBase2 + ttl1 - activation2_1_1->ttl_ns, activation2_1_1->start_ns); - EXPECT_EQ(kActive, activation2_1_1->state); - EXPECT_EQ(screenOnEvent->GetElapsedTimestampNs(), activation2_1_2->start_ns); - EXPECT_EQ(kActive, activation2_1_2->state); - - EXPECT_TRUE(metricProducer2_2->isActive()); - // }}}--------------------------------------------------------------------------- - - // Simulate shutdown by saving state to disk - shutDownTime = timeBase2 + 50 * NS_PER_SEC; - processor2->SaveActiveConfigsToDisk(shutDownTime); - EXPECT_TRUE(metricProducer2_1->isActive()); - EXPECT_TRUE(metricProducer2_2->isActive()); - ttl1 -= shutDownTime - timeBase2; - int64_t ttl2 = metric1ActivationTrigger2->ttl_seconds() * NS_PER_SEC - - (shutDownTime - screenOnEvent->GetElapsedTimestampNs()); - - // Simulate device restarted state by creating new instance of StatsLogProcessor with the - // same config. - long timeBase3 = timeBase2 + 120 * NS_PER_SEC; - sp processor3 = - CreateStatsLogProcessor(timeBase3, timeBase3, config1, cfgKey1); - - // Metric 1 is not active. - // Metric 2 is active. - // {{{--------------------------------------------------------------------------- - ASSERT_EQ(1, processor3->mMetricsManagers.size()); - it = processor3->mMetricsManagers.find(cfgKey1); - EXPECT_TRUE(it != processor3->mMetricsManagers.end()); - auto& metricsManager3 = it->second; - EXPECT_TRUE(metricsManager3->isActive()); - - ASSERT_EQ(metricsManager3->mAllMetricProducers.size(), 2); - // We assume that the index of a MetricProducer within the mAllMetricProducers - // array follows the order in which metrics are added to the config. - auto& metricProducer3_1 = metricsManager3->mAllMetricProducers[0]; - EXPECT_EQ(metricProducer3_1->getMetricId(), metricId1); - EXPECT_FALSE(metricProducer3_1->isActive()); - - auto& metricProducer3_2 = metricsManager3->mAllMetricProducers[1]; - EXPECT_EQ(metricProducer3_2->getMetricId(), metricId2); - EXPECT_TRUE(metricProducer3_2->isActive()); - - ASSERT_EQ(metricProducer3_1->mEventActivationMap.size(), 2); - // The key in mEventActivationMap is the index of the associated atom matcher. We assume - // that matchers are indexed in the order that they are added to the config. - const auto& activation3_1_1 = metricProducer3_1->mEventActivationMap.at(0); - EXPECT_EQ(100 * NS_PER_SEC, activation3_1_1->ttl_ns); - EXPECT_EQ(0, activation3_1_1->start_ns); - EXPECT_EQ(kNotActive, activation3_1_1->state); - EXPECT_EQ(ACTIVATE_ON_BOOT, activation3_1_1->activationType); - - const auto& activation3_1_2 = metricProducer3_1->mEventActivationMap.at(1); - EXPECT_EQ(200 * NS_PER_SEC, activation3_1_2->ttl_ns); - EXPECT_EQ(0, activation3_1_2->start_ns); - EXPECT_EQ(kNotActive, activation3_1_2->state); - EXPECT_EQ(ACTIVATE_IMMEDIATELY, activation3_1_2->activationType); - // }}}---------------------------------------------------------------------------------- - - // Load saved state from disk. - processor3->LoadActiveConfigsFromDisk(); - - // Metric 1 active: Activation 1 is active, Activation 2 is active - // Metric 2 is active. - // {{{--------------------------------------------------------------------------- - EXPECT_TRUE(metricProducer3_1->isActive()); - EXPECT_EQ(timeBase3 + ttl1 - activation3_1_1->ttl_ns, activation3_1_1->start_ns); - EXPECT_EQ(kActive, activation3_1_1->state); - EXPECT_EQ(timeBase3 + ttl2 - activation3_1_2->ttl_ns, activation3_1_2->start_ns); - EXPECT_EQ(kActive, activation3_1_2->state); - - EXPECT_TRUE(metricProducer3_2->isActive()); - // }}}------------------------------------------------------------------------------- - - // Trigger Activation 2 for Metric 1 again. - screenOnEvent = CreateScreenStateChangedEvent(timeBase3 + 100 * NS_PER_SEC, - android::view::DISPLAY_STATE_ON); - processor3->OnLogEvent(screenOnEvent.get()); - - // Metric 1 active; Activation 1 is inactive (above screenOnEvent causes ttl1 to expire), - // Activation 2 is set to active - // Metric 2 is active. - // {{{--------------------------------------------------------------------------- - EXPECT_TRUE(metricProducer3_1->isActive()); - EXPECT_EQ(kNotActive, activation3_1_1->state); - EXPECT_EQ(screenOnEvent->GetElapsedTimestampNs(), activation3_1_2->start_ns); - EXPECT_EQ(kActive, activation3_1_2->state); - - EXPECT_TRUE(metricProducer3_2->isActive()); - // }}}--------------------------------------------------------------------------- -} - -TEST(StatsLogProcessorTest, TestActivationsPersistAcrossSystemServerRestart) { - int uid = 9876; - long configId = 12341; - - // Create config with 3 metrics: - // Metric 1: Activate on 2 activations, 1 on boot, 1 immediate. - // Metric 2: Activate on 2 activations, 1 on boot, 1 immediate. - // Metric 3: Always active - StatsdConfig config1; - config1.set_id(configId); - config1.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root. - auto wakelockAcquireMatcher = CreateAcquireWakelockAtomMatcher(); - auto screenOnMatcher = CreateScreenTurnedOnAtomMatcher(); - auto jobStartMatcher = CreateStartScheduledJobAtomMatcher(); - auto jobFinishMatcher = CreateFinishScheduledJobAtomMatcher(); - *config1.add_atom_matcher() = wakelockAcquireMatcher; - *config1.add_atom_matcher() = screenOnMatcher; - *config1.add_atom_matcher() = jobStartMatcher; - *config1.add_atom_matcher() = jobFinishMatcher; - - long metricId1 = 1234561; - long metricId2 = 1234562; - long metricId3 = 1234563; - - auto countMetric1 = config1.add_count_metric(); - countMetric1->set_id(metricId1); - countMetric1->set_what(wakelockAcquireMatcher.id()); - countMetric1->set_bucket(FIVE_MINUTES); - - auto countMetric2 = config1.add_count_metric(); - countMetric2->set_id(metricId2); - countMetric2->set_what(wakelockAcquireMatcher.id()); - countMetric2->set_bucket(FIVE_MINUTES); - - auto countMetric3 = config1.add_count_metric(); - countMetric3->set_id(metricId3); - countMetric3->set_what(wakelockAcquireMatcher.id()); - countMetric3->set_bucket(FIVE_MINUTES); - - // Metric 1 activates on boot for wakelock acquire, immediately for screen on. - auto metric1Activation = config1.add_metric_activation(); - metric1Activation->set_metric_id(metricId1); - auto metric1ActivationTrigger1 = metric1Activation->add_event_activation(); - metric1ActivationTrigger1->set_atom_matcher_id(wakelockAcquireMatcher.id()); - metric1ActivationTrigger1->set_ttl_seconds(100); - metric1ActivationTrigger1->set_activation_type(ACTIVATE_ON_BOOT); - auto metric1ActivationTrigger2 = metric1Activation->add_event_activation(); - metric1ActivationTrigger2->set_atom_matcher_id(screenOnMatcher.id()); - metric1ActivationTrigger2->set_ttl_seconds(200); - metric1ActivationTrigger2->set_activation_type(ACTIVATE_IMMEDIATELY); - - // Metric 2 activates on boot for scheduled job start, immediately for scheduled job finish. - auto metric2Activation = config1.add_metric_activation(); - metric2Activation->set_metric_id(metricId2); - auto metric2ActivationTrigger1 = metric2Activation->add_event_activation(); - metric2ActivationTrigger1->set_atom_matcher_id(jobStartMatcher.id()); - metric2ActivationTrigger1->set_ttl_seconds(100); - metric2ActivationTrigger1->set_activation_type(ACTIVATE_ON_BOOT); - auto metric2ActivationTrigger2 = metric2Activation->add_event_activation(); - metric2ActivationTrigger2->set_atom_matcher_id(jobFinishMatcher.id()); - metric2ActivationTrigger2->set_ttl_seconds(200); - metric2ActivationTrigger2->set_activation_type(ACTIVATE_IMMEDIATELY); - - // Send the config. - shared_ptr service = SharedRefBase::make(nullptr, nullptr); - string serialized = config1.SerializeAsString(); - service->addConfigurationChecked(uid, configId, {serialized.begin(), serialized.end()}); - - // Make sure the config is stored on disk. Otherwise, we will not reset on system server death. - StatsdConfig tmpConfig; - ConfigKey cfgKey1(uid, configId); - EXPECT_TRUE(StorageManager::readConfigFromDisk(cfgKey1, &tmpConfig)); - - // Metric 1 is not active. - // Metric 2 is not active. - // Metric 3 is active. - // {{{--------------------------------------------------------------------------- - sp processor = service->mProcessor; - ASSERT_EQ(1, processor->mMetricsManagers.size()); - auto it = processor->mMetricsManagers.find(cfgKey1); - EXPECT_TRUE(it != processor->mMetricsManagers.end()); - auto& metricsManager1 = it->second; - EXPECT_TRUE(metricsManager1->isActive()); - ASSERT_EQ(3, metricsManager1->mAllMetricProducers.size()); - - auto& metricProducer1 = metricsManager1->mAllMetricProducers[0]; - EXPECT_EQ(metricId1, metricProducer1->getMetricId()); - EXPECT_FALSE(metricProducer1->isActive()); - - auto& metricProducer2 = metricsManager1->mAllMetricProducers[1]; - EXPECT_EQ(metricId2, metricProducer2->getMetricId()); - EXPECT_FALSE(metricProducer2->isActive()); - - auto& metricProducer3 = metricsManager1->mAllMetricProducers[2]; - EXPECT_EQ(metricId3, metricProducer3->getMetricId()); - EXPECT_TRUE(metricProducer3->isActive()); - - // Check event activations. - ASSERT_EQ(metricsManager1->mAllAtomMatchingTrackers.size(), 4); - EXPECT_EQ(metricsManager1->mAllAtomMatchingTrackers[0]->getId(), - metric1ActivationTrigger1->atom_matcher_id()); - const auto& activation1 = metricProducer1->mEventActivationMap.at(0); - EXPECT_EQ(100 * NS_PER_SEC, activation1->ttl_ns); - EXPECT_EQ(0, activation1->start_ns); - EXPECT_EQ(kNotActive, activation1->state); - EXPECT_EQ(ACTIVATE_ON_BOOT, activation1->activationType); - - EXPECT_EQ(metricsManager1->mAllAtomMatchingTrackers[1]->getId(), - metric1ActivationTrigger2->atom_matcher_id()); - const auto& activation2 = metricProducer1->mEventActivationMap.at(1); - EXPECT_EQ(200 * NS_PER_SEC, activation2->ttl_ns); - EXPECT_EQ(0, activation2->start_ns); - EXPECT_EQ(kNotActive, activation2->state); - EXPECT_EQ(ACTIVATE_IMMEDIATELY, activation2->activationType); - - EXPECT_EQ(metricsManager1->mAllAtomMatchingTrackers[2]->getId(), - metric2ActivationTrigger1->atom_matcher_id()); - const auto& activation3 = metricProducer2->mEventActivationMap.at(2); - EXPECT_EQ(100 * NS_PER_SEC, activation3->ttl_ns); - EXPECT_EQ(0, activation3->start_ns); - EXPECT_EQ(kNotActive, activation3->state); - EXPECT_EQ(ACTIVATE_ON_BOOT, activation3->activationType); - - EXPECT_EQ(metricsManager1->mAllAtomMatchingTrackers[3]->getId(), - metric2ActivationTrigger2->atom_matcher_id()); - const auto& activation4 = metricProducer2->mEventActivationMap.at(3); - EXPECT_EQ(200 * NS_PER_SEC, activation4->ttl_ns); - EXPECT_EQ(0, activation4->start_ns); - EXPECT_EQ(kNotActive, activation4->state); - EXPECT_EQ(ACTIVATE_IMMEDIATELY, activation4->activationType); - // }}}------------------------------------------------------------------------------ - - // Trigger Activation 1 for Metric 1. Should activate on boot. - // Trigger Activation 4 for Metric 2. Should activate immediately. - int64_t configAddedTimeNs = metricsManager1->mLastReportTimeNs; - std::vector attributionUids = {111}; - std::vector attributionTags = {"App1"}; - std::unique_ptr event1 = CreateAcquireWakelockEvent( - 1 + configAddedTimeNs, attributionUids, attributionTags, "wl1"); - processor->OnLogEvent(event1.get()); - - std::unique_ptr event2 = CreateFinishScheduledJobEvent( - 2 + configAddedTimeNs, attributionUids, attributionTags, "finish1"); - processor->OnLogEvent(event2.get()); - - // Metric 1 is not active; Activation 1 set to kActiveOnBoot - // Metric 2 is active. Activation 4 set to kActive - // Metric 3 is active. - // {{{--------------------------------------------------------------------------- - EXPECT_FALSE(metricProducer1->isActive()); - EXPECT_EQ(0, activation1->start_ns); - EXPECT_EQ(kActiveOnBoot, activation1->state); - EXPECT_EQ(0, activation2->start_ns); - EXPECT_EQ(kNotActive, activation2->state); - - EXPECT_TRUE(metricProducer2->isActive()); - EXPECT_EQ(0, activation3->start_ns); - EXPECT_EQ(kNotActive, activation3->state); - EXPECT_EQ(2 + configAddedTimeNs, activation4->start_ns); - EXPECT_EQ(kActive, activation4->state); - - EXPECT_TRUE(metricProducer3->isActive()); - // }}}----------------------------------------------------------------------------- - - // Can't fake time with StatsService. - // Lets get a time close to the system server death time and make sure it's sane. - int64_t approximateSystemServerDeath = getElapsedRealtimeNs(); - EXPECT_TRUE(approximateSystemServerDeath > 2 + configAddedTimeNs); - EXPECT_TRUE(approximateSystemServerDeath < NS_PER_SEC + configAddedTimeNs); - - // System server dies. - service->statsCompanionServiceDiedImpl(); - - // We should have a new metrics manager. Lets get it and ensure activation status is restored. - // {{{--------------------------------------------------------------------------- - ASSERT_EQ(1, processor->mMetricsManagers.size()); - it = processor->mMetricsManagers.find(cfgKey1); - EXPECT_TRUE(it != processor->mMetricsManagers.end()); - auto& metricsManager2 = it->second; - EXPECT_TRUE(metricsManager2->isActive()); - ASSERT_EQ(3, metricsManager2->mAllMetricProducers.size()); - - auto& metricProducer1001 = metricsManager2->mAllMetricProducers[0]; - EXPECT_EQ(metricId1, metricProducer1001->getMetricId()); - EXPECT_FALSE(metricProducer1001->isActive()); - - auto& metricProducer1002 = metricsManager2->mAllMetricProducers[1]; - EXPECT_EQ(metricId2, metricProducer1002->getMetricId()); - EXPECT_TRUE(metricProducer1002->isActive()); - - auto& metricProducer1003 = metricsManager2->mAllMetricProducers[2]; - EXPECT_EQ(metricId3, metricProducer1003->getMetricId()); - EXPECT_TRUE(metricProducer1003->isActive()); - - // Check event activations. - // Activation 1 is kActiveOnBoot. - // Activation 2 and 3 are not active. - // Activation 4 is active. - ASSERT_EQ(metricsManager2->mAllAtomMatchingTrackers.size(), 4); - EXPECT_EQ(metricsManager2->mAllAtomMatchingTrackers[0]->getId(), - metric1ActivationTrigger1->atom_matcher_id()); - const auto& activation1001 = metricProducer1001->mEventActivationMap.at(0); - EXPECT_EQ(100 * NS_PER_SEC, activation1001->ttl_ns); - EXPECT_EQ(0, activation1001->start_ns); - EXPECT_EQ(kActiveOnBoot, activation1001->state); - EXPECT_EQ(ACTIVATE_ON_BOOT, activation1001->activationType); - - EXPECT_EQ(metricsManager2->mAllAtomMatchingTrackers[1]->getId(), - metric1ActivationTrigger2->atom_matcher_id()); - const auto& activation1002 = metricProducer1001->mEventActivationMap.at(1); - EXPECT_EQ(200 * NS_PER_SEC, activation1002->ttl_ns); - EXPECT_EQ(0, activation1002->start_ns); - EXPECT_EQ(kNotActive, activation1002->state); - EXPECT_EQ(ACTIVATE_IMMEDIATELY, activation1002->activationType); - - EXPECT_EQ(metricsManager2->mAllAtomMatchingTrackers[2]->getId(), - metric2ActivationTrigger1->atom_matcher_id()); - const auto& activation1003 = metricProducer1002->mEventActivationMap.at(2); - EXPECT_EQ(100 * NS_PER_SEC, activation1003->ttl_ns); - EXPECT_EQ(0, activation1003->start_ns); - EXPECT_EQ(kNotActive, activation1003->state); - EXPECT_EQ(ACTIVATE_ON_BOOT, activation1003->activationType); - - EXPECT_EQ(metricsManager2->mAllAtomMatchingTrackers[3]->getId(), - metric2ActivationTrigger2->atom_matcher_id()); - const auto& activation1004 = metricProducer1002->mEventActivationMap.at(3); - EXPECT_EQ(200 * NS_PER_SEC, activation1004->ttl_ns); - EXPECT_EQ(2 + configAddedTimeNs, activation1004->start_ns); - EXPECT_EQ(kActive, activation1004->state); - EXPECT_EQ(ACTIVATE_IMMEDIATELY, activation1004->activationType); - // }}}------------------------------------------------------------------------------ - - // Clear the data stored on disk as a result of the system server death. - vector buffer; - processor->onDumpReport(cfgKey1, configAddedTimeNs + NS_PER_SEC, false, true, ADB_DUMP, FAST, - &buffer); -} - -TEST(StatsLogProcessorTest_mapIsolatedUidToHostUid, LogHostUid) { - int hostUid = 20; - int isolatedUid = 30; - uint64_t eventTimeNs = 12355; - int atomId = 89; - int field1 = 90; - int field2 = 28; - sp mockUidMap = makeMockUidMapForOneHost(hostUid, {isolatedUid}); - ConfigKey cfgKey; - StatsdConfig config = MakeConfig(false); - sp processor = - CreateStatsLogProcessor(1, 1, config, cfgKey, nullptr, 0, mockUidMap); - - shared_ptr logEvent = makeUidLogEvent(atomId, eventTimeNs, hostUid, field1, field2); - - processor->OnLogEvent(logEvent.get()); - - const vector* actualFieldValues = &logEvent->getValues(); - ASSERT_EQ(3, actualFieldValues->size()); - EXPECT_EQ(hostUid, actualFieldValues->at(0).mValue.int_value); - EXPECT_EQ(field1, actualFieldValues->at(1).mValue.int_value); - EXPECT_EQ(field2, actualFieldValues->at(2).mValue.int_value); -} - -TEST(StatsLogProcessorTest_mapIsolatedUidToHostUid, LogIsolatedUid) { - int hostUid = 20; - int isolatedUid = 30; - uint64_t eventTimeNs = 12355; - int atomId = 89; - int field1 = 90; - int field2 = 28; - sp mockUidMap = makeMockUidMapForOneHost(hostUid, {isolatedUid}); - ConfigKey cfgKey; - StatsdConfig config = MakeConfig(false); - sp processor = - CreateStatsLogProcessor(1, 1, config, cfgKey, nullptr, 0, mockUidMap); - - shared_ptr logEvent = - makeUidLogEvent(atomId, eventTimeNs, isolatedUid, field1, field2); - - processor->OnLogEvent(logEvent.get()); - - const vector* actualFieldValues = &logEvent->getValues(); - ASSERT_EQ(3, actualFieldValues->size()); - EXPECT_EQ(hostUid, actualFieldValues->at(0).mValue.int_value); - EXPECT_EQ(field1, actualFieldValues->at(1).mValue.int_value); - EXPECT_EQ(field2, actualFieldValues->at(2).mValue.int_value); -} - -TEST(StatsLogProcessorTest_mapIsolatedUidToHostUid, LogHostUidAttributionChain) { - int hostUid = 20; - int isolatedUid = 30; - uint64_t eventTimeNs = 12355; - int atomId = 89; - int field1 = 90; - int field2 = 28; - sp mockUidMap = makeMockUidMapForOneHost(hostUid, {isolatedUid}); - ConfigKey cfgKey; - StatsdConfig config = MakeConfig(false); - sp processor = - CreateStatsLogProcessor(1, 1, config, cfgKey, nullptr, 0, mockUidMap); - - shared_ptr logEvent = makeAttributionLogEvent(atomId, eventTimeNs, {hostUid, 200}, - {"tag1", "tag2"}, field1, field2); - - processor->OnLogEvent(logEvent.get()); - - const vector* actualFieldValues = &logEvent->getValues(); - ASSERT_EQ(6, actualFieldValues->size()); - EXPECT_EQ(hostUid, actualFieldValues->at(0).mValue.int_value); - EXPECT_EQ("tag1", actualFieldValues->at(1).mValue.str_value); - EXPECT_EQ(200, actualFieldValues->at(2).mValue.int_value); - EXPECT_EQ("tag2", actualFieldValues->at(3).mValue.str_value); - EXPECT_EQ(field1, actualFieldValues->at(4).mValue.int_value); - EXPECT_EQ(field2, actualFieldValues->at(5).mValue.int_value); -} - -TEST(StatsLogProcessorTest_mapIsolatedUidToHostUid, LogIsolatedUidAttributionChain) { - int hostUid = 20; - int isolatedUid = 30; - uint64_t eventTimeNs = 12355; - int atomId = 89; - int field1 = 90; - int field2 = 28; - sp mockUidMap = makeMockUidMapForOneHost(hostUid, {isolatedUid}); - ConfigKey cfgKey; - StatsdConfig config = MakeConfig(false); - sp processor = - CreateStatsLogProcessor(1, 1, config, cfgKey, nullptr, 0, mockUidMap); - - shared_ptr logEvent = makeAttributionLogEvent(atomId, eventTimeNs, {isolatedUid, 200}, - {"tag1", "tag2"}, field1, field2); - - processor->OnLogEvent(logEvent.get()); - - const vector* actualFieldValues = &logEvent->getValues(); - ASSERT_EQ(6, actualFieldValues->size()); - EXPECT_EQ(hostUid, actualFieldValues->at(0).mValue.int_value); - EXPECT_EQ("tag1", actualFieldValues->at(1).mValue.str_value); - EXPECT_EQ(200, actualFieldValues->at(2).mValue.int_value); - EXPECT_EQ("tag2", actualFieldValues->at(3).mValue.str_value); - EXPECT_EQ(field1, actualFieldValues->at(4).mValue.int_value); - EXPECT_EQ(field2, actualFieldValues->at(5).mValue.int_value); -} - -TEST(StatsLogProcessorTest, TestDumpReportWithoutErasingDataDoesNotUpdateTimestamp) { - int hostUid = 20; - int isolatedUid = 30; - sp mockUidMap = makeMockUidMapForOneHost(hostUid, {isolatedUid}); - ConfigKey key(3, 4); - - // TODO: All tests should not persist state on disk. This removes any reports that were present. - ProtoOutputStream proto; - StorageManager::appendConfigMetricsReport(key, &proto, /*erase data=*/true, /*isAdb=*/false); - - StatsdConfig config = MakeConfig(false); - sp processor = - CreateStatsLogProcessor(1, 1, config, key, nullptr, 0, mockUidMap); - vector bytes; - - int64_t dumpTime1Ns = 1 * NS_PER_SEC; - processor->onDumpReport(key, dumpTime1Ns, false /* include_current_bucket */, - true /* erase_data */, ADB_DUMP, FAST, &bytes); - - ConfigMetricsReportList output; - output.ParseFromArray(bytes.data(), bytes.size()); - EXPECT_EQ(output.reports_size(), 1); - EXPECT_EQ(output.reports(0).current_report_elapsed_nanos(), dumpTime1Ns); - - int64_t dumpTime2Ns = 5 * NS_PER_SEC; - processor->onDumpReport(key, dumpTime2Ns, false /* include_current_bucket */, - false /* erase_data */, ADB_DUMP, FAST, &bytes); - - // Check that the dump report without clearing data is successful. - output.ParseFromArray(bytes.data(), bytes.size()); - EXPECT_EQ(output.reports_size(), 1); - EXPECT_EQ(output.reports(0).current_report_elapsed_nanos(), dumpTime2Ns); - EXPECT_EQ(output.reports(0).last_report_elapsed_nanos(), dumpTime1Ns); - - int64_t dumpTime3Ns = 10 * NS_PER_SEC; - processor->onDumpReport(key, dumpTime3Ns, false /* include_current_bucket */, - true /* erase_data */, ADB_DUMP, FAST, &bytes); - - // Check that the previous dump report that didn't clear data did not overwrite the first dump's - // timestamps. - output.ParseFromArray(bytes.data(), bytes.size()); - EXPECT_EQ(output.reports_size(), 1); - EXPECT_EQ(output.reports(0).current_report_elapsed_nanos(), dumpTime3Ns); - EXPECT_EQ(output.reports(0).last_report_elapsed_nanos(), dumpTime1Ns); - -} - -#else -GTEST_LOG_(INFO) << "This test does nothing.\n"; -#endif - -} // namespace statsd -} // namespace os -} // namespace android diff --git a/bin/tests/StatsService_test.cpp b/bin/tests/StatsService_test.cpp deleted file mode 100644 index a574c58e..00000000 --- a/bin/tests/StatsService_test.cpp +++ /dev/null @@ -1,104 +0,0 @@ -// 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 "StatsService.h" -#include "config/ConfigKey.h" -#include "packages/modules/StatsD/bin/src/statsd_config.pb.h" - -#include -#include -#include - -#include - -using namespace android; -using namespace testing; - -namespace android { -namespace os { -namespace statsd { - -using android::util::ProtoOutputStream; -using ::ndk::SharedRefBase; - -#ifdef __ANDROID__ - -TEST(StatsServiceTest, TestAddConfig_simple) { - shared_ptr service = SharedRefBase::make(nullptr, nullptr); - StatsdConfig config; - config.set_id(12345); - string serialized = config.SerializeAsString(); - - EXPECT_TRUE( - service->addConfigurationChecked(123, 12345, {serialized.begin(), serialized.end()})); -} - -TEST(StatsServiceTest, TestAddConfig_empty) { - shared_ptr service = SharedRefBase::make(nullptr, nullptr); - string serialized = ""; - - EXPECT_TRUE( - service->addConfigurationChecked(123, 12345, {serialized.begin(), serialized.end()})); -} - -TEST(StatsServiceTest, TestAddConfig_invalid) { - shared_ptr service = SharedRefBase::make(nullptr, nullptr); - string serialized = "Invalid config!"; - - EXPECT_FALSE( - service->addConfigurationChecked(123, 12345, {serialized.begin(), serialized.end()})); -} - -TEST(StatsServiceTest, TestGetUidFromArgs) { - Vector args; - args.push(String8("-1")); - args.push(String8("0")); - args.push(String8("1")); - args.push(String8("a1")); - args.push(String8("")); - - int32_t uid; - - shared_ptr service = SharedRefBase::make(nullptr, nullptr); - service->mEngBuild = true; - - // "-1" - EXPECT_FALSE(service->getUidFromArgs(args, 0, uid)); - - // "0" - EXPECT_TRUE(service->getUidFromArgs(args, 1, uid)); - EXPECT_EQ(0, uid); - - // "1" - EXPECT_TRUE(service->getUidFromArgs(args, 2, uid)); - EXPECT_EQ(1, uid); - - // "a1" - EXPECT_FALSE(service->getUidFromArgs(args, 3, uid)); - - // "" - EXPECT_FALSE(service->getUidFromArgs(args, 4, uid)); - - // For a non-userdebug, uid "1" cannot be impersonated. - service->mEngBuild = false; - EXPECT_FALSE(service->getUidFromArgs(args, 2, uid)); -} - -#else -GTEST_LOG_(INFO) << "This test does nothing.\n"; -#endif - -} // namespace statsd -} // namespace os -} // namespace android diff --git a/bin/tests/UidMap_test.cpp b/bin/tests/UidMap_test.cpp deleted file mode 100644 index 33bdc643..00000000 --- a/bin/tests/UidMap_test.cpp +++ /dev/null @@ -1,426 +0,0 @@ -// 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 "packages/UidMap.h" -#include "StatsLogProcessor.h" -#include "config/ConfigKey.h" -#include "guardrail/StatsdStats.h" -#include "logd/LogEvent.h" -#include "hash.h" -#include "statslog_statsdtest.h" -#include "statsd_test_util.h" - -#include -#include - -#include - -using namespace android; - -namespace android { -namespace os { -namespace statsd { - -using android::util::ProtoOutputStream; -using android::util::ProtoReader; - -#ifdef __ANDROID__ -const string kApp1 = "app1.sharing.1"; -const string kApp2 = "app2.sharing.1"; - -TEST(UidMapTest, TestIsolatedUID) { - sp m = new UidMap(); - sp pullerManager = new StatsPullerManager(); - sp anomalyAlarmMonitor; - sp subscriberAlarmMonitor; - // Construct the processor with a no-op sendBroadcast function that does nothing. - StatsLogProcessor p( - m, pullerManager, anomalyAlarmMonitor, subscriberAlarmMonitor, 0, - [](const ConfigKey& key) { return true; }, - [](const int&, const vector&) { return true; }); - - std::unique_ptr addEvent = CreateIsolatedUidChangedEvent( - 1 /*timestamp*/, 100 /*hostUid*/, 101 /*isolatedUid*/, 1 /*is_create*/); - EXPECT_EQ(101, m->getHostUidOrSelf(101)); - p.OnLogEvent(addEvent.get()); - EXPECT_EQ(100, m->getHostUidOrSelf(101)); - - std::unique_ptr removeEvent = CreateIsolatedUidChangedEvent( - 1 /*timestamp*/, 100 /*hostUid*/, 101 /*isolatedUid*/, 0 /*is_create*/); - p.OnLogEvent(removeEvent.get()); - EXPECT_EQ(101, m->getHostUidOrSelf(101)); -} - -TEST(UidMapTest, TestMatching) { - UidMap m; - vector uids; - vector versions; - vector apps; - vector versionStrings; - vector installers; - - uids.push_back(1000); - uids.push_back(1000); - versionStrings.push_back(String16("v1")); - versionStrings.push_back(String16("v1")); - installers.push_back(String16("")); - installers.push_back(String16("")); - apps.push_back(String16(kApp1.c_str())); - apps.push_back(String16(kApp2.c_str())); - versions.push_back(4); - versions.push_back(5); - m.updateMap(1, uids, versions, versionStrings, apps, installers); - EXPECT_TRUE(m.hasApp(1000, kApp1)); - EXPECT_TRUE(m.hasApp(1000, kApp2)); - EXPECT_FALSE(m.hasApp(1000, "not.app")); - - std::set name_set = m.getAppNamesFromUid(1000u, true /* returnNormalized */); - ASSERT_EQ(name_set.size(), 2u); - EXPECT_TRUE(name_set.find(kApp1) != name_set.end()); - EXPECT_TRUE(name_set.find(kApp2) != name_set.end()); - - name_set = m.getAppNamesFromUid(12345, true /* returnNormalized */); - EXPECT_TRUE(name_set.empty()); -} - -TEST(UidMapTest, TestAddAndRemove) { - UidMap m; - vector uids; - vector versions; - vector apps; - vector versionStrings; - vector installers; - - uids.push_back(1000); - uids.push_back(1000); - versionStrings.push_back(String16("v1")); - versionStrings.push_back(String16("v1")); - installers.push_back(String16("")); - installers.push_back(String16("")); - apps.push_back(String16(kApp1.c_str())); - apps.push_back(String16(kApp2.c_str())); - versions.push_back(4); - versions.push_back(5); - m.updateMap(1, uids, versions, versionStrings, apps, installers); - - std::set name_set = m.getAppNamesFromUid(1000, true /* returnNormalized */); - ASSERT_EQ(name_set.size(), 2u); - EXPECT_TRUE(name_set.find(kApp1) != name_set.end()); - EXPECT_TRUE(name_set.find(kApp2) != name_set.end()); - - // Update the app1 version. - m.updateApp(2, String16(kApp1.c_str()), 1000, 40, String16("v40"), String16("")); - EXPECT_EQ(40, m.getAppVersion(1000, kApp1)); - - name_set = m.getAppNamesFromUid(1000, true /* returnNormalized */); - ASSERT_EQ(name_set.size(), 2u); - EXPECT_TRUE(name_set.find(kApp1) != name_set.end()); - EXPECT_TRUE(name_set.find(kApp2) != name_set.end()); - - m.removeApp(3, String16(kApp1.c_str()), 1000); - EXPECT_FALSE(m.hasApp(1000, kApp1)); - EXPECT_TRUE(m.hasApp(1000, kApp2)); - name_set = m.getAppNamesFromUid(1000, true /* returnNormalized */); - ASSERT_EQ(name_set.size(), 1u); - EXPECT_TRUE(name_set.find(kApp1) == name_set.end()); - EXPECT_TRUE(name_set.find(kApp2) != name_set.end()); - - // Remove app2. - m.removeApp(4, String16(kApp2.c_str()), 1000); - EXPECT_FALSE(m.hasApp(1000, kApp1)); - EXPECT_FALSE(m.hasApp(1000, kApp2)); - name_set = m.getAppNamesFromUid(1000, true /* returnNormalized */); - EXPECT_TRUE(name_set.empty()); -} - -TEST(UidMapTest, TestUpdateApp) { - UidMap m; - m.updateMap(1, {1000, 1000}, {4, 5}, {String16("v4"), String16("v5")}, - {String16(kApp1.c_str()), String16(kApp2.c_str())}, {String16(""), String16("")}); - std::set name_set = m.getAppNamesFromUid(1000, true /* returnNormalized */); - ASSERT_EQ(name_set.size(), 2u); - EXPECT_TRUE(name_set.find(kApp1) != name_set.end()); - EXPECT_TRUE(name_set.find(kApp2) != name_set.end()); - - // Adds a new name for uid 1000. - m.updateApp(2, String16("NeW_aPP1_NAmE"), 1000, 40, String16("v40"), String16("")); - name_set = m.getAppNamesFromUid(1000, true /* returnNormalized */); - ASSERT_EQ(name_set.size(), 3u); - EXPECT_TRUE(name_set.find(kApp1) != name_set.end()); - EXPECT_TRUE(name_set.find(kApp2) != name_set.end()); - EXPECT_TRUE(name_set.find("NeW_aPP1_NAmE") == name_set.end()); - EXPECT_TRUE(name_set.find("new_app1_name") != name_set.end()); - - // This name is also reused by another uid 2000. - m.updateApp(3, String16("NeW_aPP1_NAmE"), 2000, 1, String16("v1"), String16("")); - name_set = m.getAppNamesFromUid(2000, true /* returnNormalized */); - ASSERT_EQ(name_set.size(), 1u); - EXPECT_TRUE(name_set.find("NeW_aPP1_NAmE") == name_set.end()); - EXPECT_TRUE(name_set.find("new_app1_name") != name_set.end()); -} - -static void protoOutputStreamToUidMapping(ProtoOutputStream* proto, UidMapping* results) { - vector bytes; - bytes.resize(proto->size()); - size_t pos = 0; - sp reader = proto->data(); - while (reader->readBuffer() != NULL) { - size_t toRead = reader->currentToRead(); - std::memcpy(&((bytes)[pos]), reader->readBuffer(), toRead); - pos += toRead; - reader->move(toRead); - } - results->ParseFromArray(bytes.data(), bytes.size()); -} - -// Test that uid map returns at least one snapshot even if we already obtained -// this snapshot from a previous call to getData. -TEST(UidMapTest, TestOutputIncludesAtLeastOneSnapshot) { - UidMap m; - // Initialize single config key. - ConfigKey config1(1, StringToId("config1")); - m.OnConfigUpdated(config1); - vector uids; - vector versions; - vector apps; - vector versionStrings; - vector installers; - uids.push_back(1000); - apps.push_back(String16(kApp2.c_str())); - versionStrings.push_back(String16("v1")); - installers.push_back(String16("")); - versions.push_back(5); - m.updateMap(1, uids, versions, versionStrings, apps, installers); - - // Set the last timestamp for this config key to be newer. - m.mLastUpdatePerConfigKey[config1] = 2; - - ProtoOutputStream proto; - m.appendUidMap(3, config1, nullptr, true, true, &proto); - - // Check there's still a uidmap attached this one. - UidMapping results; - protoOutputStreamToUidMapping(&proto, &results); - ASSERT_EQ(1, results.snapshots_size()); - EXPECT_EQ("v1", results.snapshots(0).package_info(0).version_string()); -} - -TEST(UidMapTest, TestRemovedAppRetained) { - UidMap m; - // Initialize single config key. - ConfigKey config1(1, StringToId("config1")); - m.OnConfigUpdated(config1); - vector uids; - vector versions; - vector versionStrings; - vector installers; - vector apps; - uids.push_back(1000); - apps.push_back(String16(kApp2.c_str())); - versions.push_back(5); - versionStrings.push_back(String16("v5")); - installers.push_back(String16("")); - m.updateMap(1, uids, versions, versionStrings, apps, installers); - m.removeApp(2, String16(kApp2.c_str()), 1000); - - ProtoOutputStream proto; - m.appendUidMap(3, config1, nullptr, true, true, &proto); - - // Snapshot should still contain this item as deleted. - UidMapping results; - protoOutputStreamToUidMapping(&proto, &results); - ASSERT_EQ(1, results.snapshots(0).package_info_size()); - EXPECT_EQ(true, results.snapshots(0).package_info(0).deleted()); -} - -TEST(UidMapTest, TestRemovedAppOverGuardrail) { - UidMap m; - // Initialize single config key. - ConfigKey config1(1, StringToId("config1")); - m.OnConfigUpdated(config1); - vector uids; - vector versions; - vector versionStrings; - vector installers; - vector apps; - const int maxDeletedApps = StatsdStats::kMaxDeletedAppsInUidMap; - for (int j = 0; j < maxDeletedApps + 10; j++) { - uids.push_back(j); - apps.push_back(String16(kApp1.c_str())); - versions.push_back(j); - versionStrings.push_back(String16("v")); - installers.push_back(String16("")); - } - m.updateMap(1, uids, versions, versionStrings, apps, installers); - - // First, verify that we have the expected number of items. - UidMapping results; - ProtoOutputStream proto; - m.appendUidMap(3, config1, nullptr, true, true, &proto); - protoOutputStreamToUidMapping(&proto, &results); - ASSERT_EQ(maxDeletedApps + 10, results.snapshots(0).package_info_size()); - - // Now remove all the apps. - m.updateMap(1, uids, versions, versionStrings, apps, installers); - for (int j = 0; j < maxDeletedApps + 10; j++) { - m.removeApp(4, String16(kApp1.c_str()), j); - } - - proto.clear(); - m.appendUidMap(5, config1, nullptr, true, true, &proto); - // Snapshot drops the first nine items. - protoOutputStreamToUidMapping(&proto, &results); - ASSERT_EQ(maxDeletedApps, results.snapshots(0).package_info_size()); -} - -TEST(UidMapTest, TestClearingOutput) { - UidMap m; - - ConfigKey config1(1, StringToId("config1")); - ConfigKey config2(1, StringToId("config2")); - - m.OnConfigUpdated(config1); - - vector uids; - vector versions; - vector versionStrings; - vector installers; - vector apps; - uids.push_back(1000); - uids.push_back(1000); - apps.push_back(String16(kApp1.c_str())); - apps.push_back(String16(kApp2.c_str())); - versions.push_back(4); - versions.push_back(5); - versionStrings.push_back(String16("v4")); - versionStrings.push_back(String16("v5")); - installers.push_back(String16("")); - installers.push_back(String16("")); - m.updateMap(1, uids, versions, versionStrings, apps, installers); - - ProtoOutputStream proto; - m.appendUidMap(2, config1, nullptr, true, true, &proto); - UidMapping results; - protoOutputStreamToUidMapping(&proto, &results); - ASSERT_EQ(1, results.snapshots_size()); - - // We have to keep at least one snapshot in memory at all times. - proto.clear(); - m.appendUidMap(2, config1, nullptr, true, true, &proto); - protoOutputStreamToUidMapping(&proto, &results); - ASSERT_EQ(1, results.snapshots_size()); - - // Now add another configuration. - m.OnConfigUpdated(config2); - m.updateApp(5, String16(kApp1.c_str()), 1000, 40, String16("v40"), String16("")); - ASSERT_EQ(1U, m.mChanges.size()); - proto.clear(); - m.appendUidMap(6, config1, nullptr, true, true, &proto); - protoOutputStreamToUidMapping(&proto, &results); - ASSERT_EQ(1, results.snapshots_size()); - ASSERT_EQ(1, results.changes_size()); - ASSERT_EQ(1U, m.mChanges.size()); - - // Add another delta update. - m.updateApp(7, String16(kApp2.c_str()), 1001, 41, String16("v41"), String16("")); - ASSERT_EQ(2U, m.mChanges.size()); - - // We still can't remove anything. - proto.clear(); - m.appendUidMap(8, config1, nullptr, true, true, &proto); - protoOutputStreamToUidMapping(&proto, &results); - ASSERT_EQ(1, results.snapshots_size()); - ASSERT_EQ(1, results.changes_size()); - ASSERT_EQ(2U, m.mChanges.size()); - - proto.clear(); - m.appendUidMap(9, config2, nullptr, true, true, &proto); - protoOutputStreamToUidMapping(&proto, &results); - ASSERT_EQ(1, results.snapshots_size()); - ASSERT_EQ(2, results.changes_size()); - // At this point both should be cleared. - ASSERT_EQ(0U, m.mChanges.size()); -} - -TEST(UidMapTest, TestMemoryComputed) { - UidMap m; - - ConfigKey config1(1, StringToId("config1")); - m.OnConfigUpdated(config1); - - size_t startBytes = m.mBytesUsed; - vector uids; - vector versions; - vector apps; - vector versionStrings; - vector installers; - uids.push_back(1000); - apps.push_back(String16(kApp1.c_str())); - versions.push_back(1); - versionStrings.push_back(String16("v1")); - installers.push_back(String16("")); - m.updateMap(1, uids, versions, versionStrings, apps, installers); - - m.updateApp(3, String16(kApp1.c_str()), 1000, 40, String16("v40"), String16("")); - - ProtoOutputStream proto; - vector bytes; - m.appendUidMap(2, config1, nullptr, true, true, &proto); - size_t prevBytes = m.mBytesUsed; - - m.appendUidMap(4, config1, nullptr, true, true, &proto); - EXPECT_TRUE(m.mBytesUsed < prevBytes); -} - -TEST(UidMapTest, TestMemoryGuardrail) { - UidMap m; - string buf; - - ConfigKey config1(1, StringToId("config1")); - m.OnConfigUpdated(config1); - - size_t startBytes = m.mBytesUsed; - vector uids; - vector versions; - vector versionStrings; - vector installers; - vector apps; - for (int i = 0; i < 100; i++) { - uids.push_back(1); - buf = "EXTREMELY_LONG_STRING_FOR_APP_TO_WASTE_MEMORY." + to_string(i); - apps.push_back(String16(buf.c_str())); - versions.push_back(1); - versionStrings.push_back(String16("v1")); - installers.push_back(String16("")); - } - m.updateMap(1, uids, versions, versionStrings, apps, installers); - - m.updateApp(3, String16("EXTREMELY_LONG_STRING_FOR_APP_TO_WASTE_MEMORY.0"), 1000, 2, - String16("v2"), String16("")); - ASSERT_EQ(1U, m.mChanges.size()); - - // Now force deletion by limiting the memory to hold one delta change. - m.maxBytesOverride = 120; // Since the app string alone requires >45 characters. - m.updateApp(5, String16("EXTREMELY_LONG_STRING_FOR_APP_TO_WASTE_MEMORY.0"), 1000, 4, - String16("v4"), String16("")); - ASSERT_EQ(1U, m.mChanges.size()); -} - -#else -GTEST_LOG_(INFO) << "This test does nothing.\n"; -#endif - -} // namespace statsd -} // namespace os -} // namespace android diff --git a/bin/tests/anomaly/AlarmTracker_test.cpp b/bin/tests/anomaly/AlarmTracker_test.cpp deleted file mode 100644 index 64ea219c..00000000 --- a/bin/tests/anomaly/AlarmTracker_test.cpp +++ /dev/null @@ -1,94 +0,0 @@ -// Copyright (C) 2018 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 "src/anomaly/AlarmTracker.h" - -#include -#include -#include -#include - -using namespace testing; -using android::sp; -using std::set; -using std::shared_ptr; -using std::unordered_map; -using std::vector; - -#ifdef __ANDROID__ - -namespace android { -namespace os { -namespace statsd { - -const ConfigKey kConfigKey(0, 12345); - -TEST(AlarmTrackerTest, TestTriggerTimestamp) { - sp subscriberAlarmMonitor = - new AlarmMonitor(100, - [](const shared_ptr&, int64_t){}, - [](const shared_ptr&){}); - Alarm alarm; - alarm.set_offset_millis(15 * MS_PER_SEC); - alarm.set_period_millis(60 * 60 * MS_PER_SEC); // 1hr - int64_t startMillis = 100000000 * MS_PER_SEC; - int64_t nextAlarmTime = startMillis / MS_PER_SEC + 15; - AlarmTracker tracker(startMillis, startMillis, alarm, kConfigKey, subscriberAlarmMonitor); - - EXPECT_EQ(tracker.mAlarmSec, nextAlarmTime); - - uint64_t currentTimeSec = startMillis / MS_PER_SEC + 10; - std::unordered_set, SpHash> firedAlarmSet = - subscriberAlarmMonitor->popSoonerThan(static_cast(currentTimeSec)); - EXPECT_TRUE(firedAlarmSet.empty()); - tracker.informAlarmsFired(currentTimeSec * NS_PER_SEC, firedAlarmSet); - EXPECT_EQ(tracker.mAlarmSec, nextAlarmTime); - EXPECT_EQ(tracker.getAlarmTimestampSec(), nextAlarmTime); - - currentTimeSec = startMillis / MS_PER_SEC + 7000; - nextAlarmTime = startMillis / MS_PER_SEC + 15 + 2 * 60 * 60; - firedAlarmSet = subscriberAlarmMonitor->popSoonerThan(static_cast(currentTimeSec)); - ASSERT_EQ(firedAlarmSet.size(), 1u); - tracker.informAlarmsFired(currentTimeSec * NS_PER_SEC, firedAlarmSet); - EXPECT_TRUE(firedAlarmSet.empty()); - EXPECT_EQ(tracker.mAlarmSec, nextAlarmTime); - EXPECT_EQ(tracker.getAlarmTimestampSec(), nextAlarmTime); - - // Alarm fires exactly on time. - currentTimeSec = startMillis / MS_PER_SEC + 15 + 2 * 60 * 60; - nextAlarmTime = startMillis / MS_PER_SEC + 15 + 3 * 60 * 60; - firedAlarmSet = subscriberAlarmMonitor->popSoonerThan(static_cast(currentTimeSec)); - ASSERT_EQ(firedAlarmSet.size(), 1u); - tracker.informAlarmsFired(currentTimeSec * NS_PER_SEC, firedAlarmSet); - EXPECT_TRUE(firedAlarmSet.empty()); - EXPECT_EQ(tracker.mAlarmSec, nextAlarmTime); - EXPECT_EQ(tracker.getAlarmTimestampSec(), nextAlarmTime); - - // Alarm fires exactly 1 period late. - currentTimeSec = startMillis / MS_PER_SEC + 15 + 4 * 60 * 60; - nextAlarmTime = startMillis / MS_PER_SEC + 15 + 5 * 60 * 60; - firedAlarmSet = subscriberAlarmMonitor->popSoonerThan(static_cast(currentTimeSec)); - ASSERT_EQ(firedAlarmSet.size(), 1u); - tracker.informAlarmsFired(currentTimeSec * NS_PER_SEC, firedAlarmSet); - EXPECT_TRUE(firedAlarmSet.empty()); - EXPECT_EQ(tracker.mAlarmSec, nextAlarmTime); - EXPECT_EQ(tracker.getAlarmTimestampSec(), nextAlarmTime); -} - -} // namespace statsd -} // namespace os -} // namespace android -#else -GTEST_LOG_(INFO) << "This test does nothing.\n"; -#endif diff --git a/bin/tests/anomaly/AnomalyTracker_test.cpp b/bin/tests/anomaly/AnomalyTracker_test.cpp deleted file mode 100644 index 0cc8af16..00000000 --- a/bin/tests/anomaly/AnomalyTracker_test.cpp +++ /dev/null @@ -1,408 +0,0 @@ -// 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 "src/anomaly/AnomalyTracker.h" - -#include -#include -#include - -#include - -#include "tests/statsd_test_util.h" - -using namespace testing; -using android::sp; -using std::set; -using std::unordered_map; -using std::vector; - -#ifdef __ANDROID__ - -namespace android { -namespace os { -namespace statsd { - -const ConfigKey kConfigKey(0, 12345); - -MetricDimensionKey getMockMetricDimensionKey(int key, string value) { - int pos[] = {key, 0, 0}; - HashableDimensionKey dim; - dim.addValue(FieldValue(Field(1, pos, 0), Value(value))); - return MetricDimensionKey(dim, DEFAULT_DIMENSION_KEY); -} - -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; - } -} - -std::shared_ptr MockBucket( - const std::vector>& key_value_pair_list) { - std::shared_ptr bucket = std::make_shared(); - AddValueToBucket(key_value_pair_list, bucket); - return bucket; -} - -// Returns the value, for the given key, in that bucket, or 0 if not present. -int64_t getBucketValue(const std::shared_ptr& bucket, - const MetricDimensionKey& key) { - const auto& itr = bucket->find(key); - if (itr != bucket->end()) { - return itr->second; - } - return 0; -} - -// Returns true if keys in trueList are detected as anomalies and keys in falseList are not. -bool detectAnomaliesPass(AnomalyTracker& tracker, - const int64_t& bucketNum, - const std::shared_ptr& currentBucket, - const std::set& trueList, - const std::set& falseList) { - for (const MetricDimensionKey& key : trueList) { - if (!tracker.detectAnomaly(bucketNum, key, getBucketValue(currentBucket, key))) { - return false; - } - } - for (const MetricDimensionKey& key : falseList) { - if (tracker.detectAnomaly(bucketNum, key, getBucketValue(currentBucket, key))) { - return false; - } - } - return true; -} - -// Calls tracker.detectAndDeclareAnomaly on each key in the bucket. -void detectAndDeclareAnomalies(AnomalyTracker& tracker, - const int64_t& bucketNum, - const std::shared_ptr& bucket, - const int64_t& eventTimestamp) { - for (const auto& kv : *bucket) { - tracker.detectAndDeclareAnomaly(eventTimestamp, bucketNum, 0 /*metric_id*/, kv.first, - kv.second); - } -} - -// Asserts that the refractory time for each key in timestamps is the corresponding -// timestamp (in ns) + refractoryPeriodSec. -// If a timestamp value is negative, instead asserts that the refractory period is inapplicable -// (either non-existant or already past). -void checkRefractoryTimes(AnomalyTracker& tracker, - const int64_t& currTimestampNs, - const int32_t& refractoryPeriodSec, - 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. - EXPECT_LT(tracker.getRefractoryPeriodEndsSec(kv.first) * NS_PER_SEC, - (uint64_t)currTimestampNs) - << "Failure was at currTimestampNs " << currTimestampNs; - } else { - EXPECT_EQ(tracker.getRefractoryPeriodEndsSec(kv.first), - std::ceil(1.0 * kv.second / NS_PER_SEC) + refractoryPeriodSec) - << "Failure was at currTimestampNs " << currTimestampNs; - } - } -} - -TEST(AnomalyTrackerTest, TestConsecutiveBuckets) { - const int64_t bucketSizeNs = 30 * NS_PER_SEC; - const int32_t refractoryPeriodSec = 2 * bucketSizeNs / NS_PER_SEC; - Alert alert; - alert.set_num_buckets(3); - alert.set_refractory_period_secs(refractoryPeriodSec); - alert.set_trigger_if_sum_gt(2); - - AnomalyTracker anomalyTracker(alert, kConfigKey); - 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; - int64_t eventTimestamp2 = 2 * bucketSizeNs + 12 * NS_PER_SEC; - int64_t eventTimestamp3 = 3 * bucketSizeNs + 13 * NS_PER_SEC; - int64_t eventTimestamp4 = 4 * bucketSizeNs + 14 * NS_PER_SEC; - int64_t eventTimestamp5 = 5 * bucketSizeNs + 5 * NS_PER_SEC; - int64_t eventTimestamp6 = 6 * bucketSizeNs + 16 * NS_PER_SEC; - - std::shared_ptr bucket0 = MockBucket({{keyA, 1}, {keyB, 2}, {keyC, 1}}); - std::shared_ptr bucket1 = MockBucket({{keyA, 1}}); - std::shared_ptr bucket2 = MockBucket({{keyB, 1}}); - std::shared_ptr bucket3 = MockBucket({{keyA, 2}}); - std::shared_ptr bucket4 = MockBucket({{keyB, 5}}); - std::shared_ptr bucket5 = MockBucket({{keyA, 2}}); - std::shared_ptr bucket6 = MockBucket({{keyA, 2}}); - - // Start time with no events. - ASSERT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 0u); - EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, -1LL); - - // Event from bucket #0 occurs. - EXPECT_TRUE(detectAnomaliesPass(anomalyTracker, 0, bucket0, {}, {keyA, keyB, keyC})); - detectAndDeclareAnomalies(anomalyTracker, 0, bucket0, eventTimestamp1); - checkRefractoryTimes(anomalyTracker, eventTimestamp0, refractoryPeriodSec, - {{keyA, -1}, {keyB, -1}, {keyC, -1}}); - - // Adds past bucket #0 - anomalyTracker.addPastBucket(bucket0, 0); - ASSERT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 3u); - EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyA), 1LL); - EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyB), 2LL); - EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyC), 1LL); - EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 0LL); - - // Event from bucket #1 occurs. - EXPECT_TRUE(detectAnomaliesPass(anomalyTracker, 1, bucket1, {}, {keyA, keyB, keyC})); - detectAndDeclareAnomalies(anomalyTracker, 1, bucket1, eventTimestamp1); - checkRefractoryTimes(anomalyTracker, eventTimestamp1, refractoryPeriodSec, - {{keyA, -1}, {keyB, -1}, {keyC, -1}}); - - // Adds past bucket #0 again. The sum does not change. - anomalyTracker.addPastBucket(bucket0, 0); - ASSERT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 3u); - EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyA), 1LL); - EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyB), 2LL); - EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyC), 1LL); - EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 0LL); - EXPECT_TRUE(detectAnomaliesPass(anomalyTracker, 1, bucket1, {}, {keyA, keyB, keyC})); - detectAndDeclareAnomalies(anomalyTracker, 1, bucket1, eventTimestamp1 + 1); - checkRefractoryTimes(anomalyTracker, eventTimestamp1, refractoryPeriodSec, - {{keyA, -1}, {keyB, -1}, {keyC, -1}}); - - // Adds past bucket #1. - anomalyTracker.addPastBucket(bucket1, 1); - EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 1L); - ASSERT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 3UL); - EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyA), 2LL); - EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyB), 2LL); - EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyC), 1LL); - - // Event from bucket #2 occurs. New anomaly on keyB. - EXPECT_TRUE(detectAnomaliesPass(anomalyTracker, 2, bucket2, {keyB}, {keyA, keyC})); - detectAndDeclareAnomalies(anomalyTracker, 2, bucket2, eventTimestamp2); - checkRefractoryTimes(anomalyTracker, eventTimestamp2, refractoryPeriodSec, - {{keyA, -1}, {keyB, eventTimestamp2}, {keyC, -1}}); - - // Adds past bucket #1 again. Nothing changes. - anomalyTracker.addPastBucket(bucket1, 1); - EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 1L); - ASSERT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 3UL); - EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyA), 2LL); - EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyB), 2LL); - EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyC), 1LL); - // Event from bucket #2 occurs (again). - EXPECT_TRUE(detectAnomaliesPass(anomalyTracker, 2, bucket2, {keyB}, {keyA, keyC})); - detectAndDeclareAnomalies(anomalyTracker, 2, bucket2, eventTimestamp2 + 1); - checkRefractoryTimes(anomalyTracker, eventTimestamp2, refractoryPeriodSec, - {{keyA, -1}, {keyB, eventTimestamp2}, {keyC, -1}}); - - // Adds past bucket #2. - anomalyTracker.addPastBucket(bucket2, 2); - EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 2L); - ASSERT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 2UL); - EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyA), 1LL); - EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyB), 1LL); - - // Event from bucket #3 occurs. New anomaly on keyA. - EXPECT_TRUE(detectAnomaliesPass(anomalyTracker, 3, bucket3, {keyA}, {keyB, keyC})); - detectAndDeclareAnomalies(anomalyTracker, 3, bucket3, eventTimestamp3); - checkRefractoryTimes(anomalyTracker, eventTimestamp3, refractoryPeriodSec, - {{keyA, eventTimestamp3}, {keyB, eventTimestamp2}, {keyC, -1}}); - - // Adds bucket #3. - anomalyTracker.addPastBucket(bucket3, 3L); - EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 3L); - ASSERT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 2UL); - EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyA), 2LL); - EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyB), 1LL); - - // Event from bucket #4 occurs. New anomaly on keyB. - EXPECT_TRUE(detectAnomaliesPass(anomalyTracker, 4, bucket4, {keyB}, {keyA, keyC})); - detectAndDeclareAnomalies(anomalyTracker, 4, bucket4, eventTimestamp4); - checkRefractoryTimes(anomalyTracker, eventTimestamp4, refractoryPeriodSec, - {{keyA, eventTimestamp3}, {keyB, eventTimestamp4}, {keyC, -1}}); - - // Adds bucket #4. - anomalyTracker.addPastBucket(bucket4, 4); - EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 4L); - ASSERT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 2UL); - EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyA), 2LL); - EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyB), 5LL); - - // Event from bucket #5 occurs. New anomaly on keyA, which is still in refractory. - EXPECT_TRUE(detectAnomaliesPass(anomalyTracker, 5, bucket5, {keyA, keyB}, {keyC})); - detectAndDeclareAnomalies(anomalyTracker, 5, bucket5, eventTimestamp5); - checkRefractoryTimes(anomalyTracker, eventTimestamp5, refractoryPeriodSec, - {{keyA, eventTimestamp3}, {keyB, eventTimestamp4}, {keyC, -1}}); - - // Adds bucket #5. - anomalyTracker.addPastBucket(bucket5, 5); - EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 5L); - ASSERT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 2UL); - EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyA), 2LL); - EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyB), 5LL); - - // Event from bucket #6 occurs. New anomaly on keyA, which is now out of refractory. - EXPECT_TRUE(detectAnomaliesPass(anomalyTracker, 6, bucket6, {keyA, keyB}, {keyC})); - detectAndDeclareAnomalies(anomalyTracker, 6, bucket6, eventTimestamp6); - checkRefractoryTimes(anomalyTracker, eventTimestamp6, refractoryPeriodSec, - {{keyA, eventTimestamp6}, {keyB, eventTimestamp4}, {keyC, -1}}); -} - -TEST(AnomalyTrackerTest, TestSparseBuckets) { - const int64_t bucketSizeNs = 30 * NS_PER_SEC; - const int32_t refractoryPeriodSec = 2 * bucketSizeNs / NS_PER_SEC; - Alert alert; - alert.set_num_buckets(3); - alert.set_refractory_period_secs(refractoryPeriodSec); - alert.set_trigger_if_sum_gt(2); - - AnomalyTracker anomalyTracker(alert, kConfigKey); - 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}}); - std::shared_ptr bucket18 = MockBucket({{keyB, 1}, {keyC, 1}}); - std::shared_ptr bucket20 = MockBucket({{keyB, 3}, {keyC, 1}}); - std::shared_ptr bucket25 = MockBucket({{keyD, 1}}); - std::shared_ptr bucket28 = MockBucket({{keyE, 2}}); - - int64_t eventTimestamp1 = bucketSizeNs * 8 + 1; - int64_t eventTimestamp2 = bucketSizeNs * 15 + 11; - int64_t eventTimestamp3 = bucketSizeNs * 17 + 1; - int64_t eventTimestamp4 = bucketSizeNs * 19 + 2; - int64_t eventTimestamp5 = bucketSizeNs * 24 + 3; - int64_t eventTimestamp6 = bucketSizeNs * 27 + 3; - - EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, -1LL); - ASSERT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 0UL); - EXPECT_TRUE(detectAnomaliesPass(anomalyTracker, 9, bucket9, {}, {keyA, keyB, keyC, keyD})); - detectAndDeclareAnomalies(anomalyTracker, 9, bucket9, eventTimestamp1); - checkRefractoryTimes(anomalyTracker, eventTimestamp1, refractoryPeriodSec, - {{keyA, -1}, {keyB, -1}, {keyC, -1}, {keyD, -1}, {keyE, -1}}); - - // Add past bucket #9 - anomalyTracker.addPastBucket(bucket9, 9); - EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 9L); - ASSERT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 3UL); - EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyA), 1LL); - EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyB), 2LL); - EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyC), 1LL); - EXPECT_TRUE(detectAnomaliesPass(anomalyTracker, 16, bucket16, {keyB}, {keyA, keyC, keyD})); - ASSERT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 0UL); - EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 15L); - detectAndDeclareAnomalies(anomalyTracker, 16, bucket16, eventTimestamp2); - ASSERT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 0UL); - EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 15L); - checkRefractoryTimes(anomalyTracker, eventTimestamp2, refractoryPeriodSec, - {{keyA, -1}, {keyB, eventTimestamp2}, {keyC, -1}, {keyD, -1}, {keyE, -1}}); - - // Add past bucket #16 - anomalyTracker.addPastBucket(bucket16, 16); - EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 16L); - ASSERT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 1UL); - EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyB), 4LL); - EXPECT_TRUE(detectAnomaliesPass(anomalyTracker, 18, bucket18, {keyB}, {keyA, keyC, keyD})); - ASSERT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 1UL); - EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyB), 4LL); - // Within refractory period. - detectAndDeclareAnomalies(anomalyTracker, 18, bucket18, eventTimestamp3); - checkRefractoryTimes(anomalyTracker, eventTimestamp3, refractoryPeriodSec, - {{keyA, -1}, {keyB, eventTimestamp2}, {keyC, -1}, {keyD, -1}, {keyE, -1}}); - ASSERT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 1UL); - EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyB), 4LL); - - // Add past bucket #18 - anomalyTracker.addPastBucket(bucket18, 18); - EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 18L); - ASSERT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 2UL); - EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyB), 1LL); - EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyC), 1LL); - EXPECT_TRUE(detectAnomaliesPass(anomalyTracker, 20, bucket20, {keyB}, {keyA, keyC, keyD})); - EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 19L); - ASSERT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 2UL); - EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyB), 1LL); - EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyC), 1LL); - detectAndDeclareAnomalies(anomalyTracker, 20, bucket20, eventTimestamp4); - checkRefractoryTimes(anomalyTracker, eventTimestamp4, refractoryPeriodSec, - {{keyA, -1}, {keyB, eventTimestamp4}, {keyC, -1}, {keyD, -1}, {keyE, -1}}); - - // Add bucket #18 again. Nothing changes. - anomalyTracker.addPastBucket(bucket18, 18); - EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 19L); - ASSERT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 2UL); - EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyB), 1LL); - EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyC), 1LL); - EXPECT_TRUE(detectAnomaliesPass(anomalyTracker, 20, bucket20, {keyB}, {keyA, keyC, keyD})); - ASSERT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 2UL); - EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyB), 1LL); - EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyC), 1LL); - detectAndDeclareAnomalies(anomalyTracker, 20, bucket20, eventTimestamp4 + 1); - // Within refractory period. - checkRefractoryTimes(anomalyTracker, eventTimestamp4 + 1, refractoryPeriodSec, - {{keyA, -1}, {keyB, eventTimestamp4}, {keyC, -1}, {keyD, -1}, {keyE, -1}}); - - // Add past bucket #20 - anomalyTracker.addPastBucket(bucket20, 20); - EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 20L); - ASSERT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 2UL); - EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyB), 3LL); - EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyC), 1LL); - EXPECT_TRUE(detectAnomaliesPass(anomalyTracker, 25, bucket25, {}, {keyA, keyB, keyC, keyD})); - EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 24L); - ASSERT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 0UL); - detectAndDeclareAnomalies(anomalyTracker, 25, bucket25, eventTimestamp5); - checkRefractoryTimes(anomalyTracker, eventTimestamp5, refractoryPeriodSec, - {{keyA, -1}, {keyB, eventTimestamp4}, {keyC, -1}, {keyD, -1}, {keyE, -1}}); - - // Add past bucket #25 - anomalyTracker.addPastBucket(bucket25, 25); - EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 25L); - ASSERT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 1UL); - EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyD), 1LL); - EXPECT_TRUE(detectAnomaliesPass(anomalyTracker, 28, bucket28, {}, - {keyA, keyB, keyC, keyD, keyE})); - EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 27L); - ASSERT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 0UL); - detectAndDeclareAnomalies(anomalyTracker, 28, bucket28, eventTimestamp6); - ASSERT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 0UL); - checkRefractoryTimes(anomalyTracker, eventTimestamp6, refractoryPeriodSec, - {{keyA, -1}, {keyB, -1}, {keyC, -1}, {keyD, -1}, {keyE, -1}}); - - // Updates current bucket #28. - (*bucket28)[keyE] = 5; - EXPECT_TRUE(detectAnomaliesPass(anomalyTracker, 28, bucket28, {keyE}, - {keyA, keyB, keyC, keyD})); - EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 27L); - ASSERT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 0UL); - detectAndDeclareAnomalies(anomalyTracker, 28, bucket28, eventTimestamp6 + 7); - ASSERT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 0UL); - checkRefractoryTimes(anomalyTracker, eventTimestamp6, refractoryPeriodSec, - {{keyA, -1}, {keyB, -1}, {keyC, -1}, {keyD, -1}, {keyE, eventTimestamp6 + 7}}); -} - -} // namespace statsd -} // namespace os -} // namespace android -#else -GTEST_LOG_(INFO) << "This test does nothing.\n"; -#endif diff --git a/bin/tests/condition/CombinationConditionTracker_test.cpp b/bin/tests/condition/CombinationConditionTracker_test.cpp deleted file mode 100644 index 3f3f9a81..00000000 --- a/bin/tests/condition/CombinationConditionTracker_test.cpp +++ /dev/null @@ -1,167 +0,0 @@ -// 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 "condition/condition_util.h" -#include "packages/modules/StatsD/bin/src/statsd_config.pb.h" - -#include - -#include -#include - -using namespace android::os::statsd; -using std::vector; - -#ifdef __ANDROID__ - -TEST(ConditionTrackerTest, TestUnknownCondition) { - LogicalOperation operation = LogicalOperation::AND; - - vector children; - children.push_back(0); - children.push_back(1); - children.push_back(2); - - vector conditionResults; - conditionResults.push_back(ConditionState::kUnknown); - conditionResults.push_back(ConditionState::kFalse); - conditionResults.push_back(ConditionState::kTrue); - - EXPECT_EQ(evaluateCombinationCondition(children, operation, conditionResults), - ConditionState::kUnknown); -} - -TEST(ConditionTrackerTest, TestAndCondition) { - // Set up the matcher - LogicalOperation operation = LogicalOperation::AND; - - vector children; - children.push_back(0); - children.push_back(1); - children.push_back(2); - - vector conditionResults; - conditionResults.push_back(ConditionState::kTrue); - conditionResults.push_back(ConditionState::kFalse); - conditionResults.push_back(ConditionState::kTrue); - - EXPECT_FALSE(evaluateCombinationCondition(children, operation, conditionResults)); - - conditionResults.clear(); - conditionResults.push_back(ConditionState::kTrue); - conditionResults.push_back(ConditionState::kTrue); - conditionResults.push_back(ConditionState::kTrue); - - EXPECT_TRUE(evaluateCombinationCondition(children, operation, conditionResults)); -} - -TEST(ConditionTrackerTest, TestOrCondition) { - // Set up the matcher - LogicalOperation operation = LogicalOperation::OR; - - vector children; - children.push_back(0); - children.push_back(1); - children.push_back(2); - - vector conditionResults; - conditionResults.push_back(ConditionState::kTrue); - conditionResults.push_back(ConditionState::kFalse); - conditionResults.push_back(ConditionState::kTrue); - - EXPECT_TRUE(evaluateCombinationCondition(children, operation, conditionResults)); - - conditionResults.clear(); - conditionResults.push_back(ConditionState::kFalse); - conditionResults.push_back(ConditionState::kFalse); - conditionResults.push_back(ConditionState::kFalse); - - EXPECT_FALSE(evaluateCombinationCondition(children, operation, conditionResults)); -} - -TEST(ConditionTrackerTest, TestNotCondition) { - // Set up the matcher - LogicalOperation operation = LogicalOperation::NOT; - - vector children; - children.push_back(0); - - vector conditionResults; - conditionResults.push_back(ConditionState::kTrue); - - EXPECT_FALSE(evaluateCombinationCondition(children, operation, conditionResults)); - - conditionResults.clear(); - conditionResults.push_back(ConditionState::kFalse); - EXPECT_TRUE(evaluateCombinationCondition(children, operation, conditionResults)); - - children.clear(); - conditionResults.clear(); - EXPECT_EQ(evaluateCombinationCondition(children, operation, conditionResults), - ConditionState::kUnknown); -} - -TEST(ConditionTrackerTest, TestNandCondition) { - // Set up the matcher - LogicalOperation operation = LogicalOperation::NAND; - - vector children; - children.push_back(0); - children.push_back(1); - - vector conditionResults; - conditionResults.push_back(ConditionState::kTrue); - conditionResults.push_back(ConditionState::kFalse); - - EXPECT_TRUE(evaluateCombinationCondition(children, operation, conditionResults)); - - conditionResults.clear(); - conditionResults.push_back(ConditionState::kFalse); - conditionResults.push_back(ConditionState::kFalse); - EXPECT_TRUE(evaluateCombinationCondition(children, operation, conditionResults)); - - conditionResults.clear(); - conditionResults.push_back(ConditionState::kTrue); - conditionResults.push_back(ConditionState::kTrue); - EXPECT_FALSE(evaluateCombinationCondition(children, operation, conditionResults)); -} - -TEST(ConditionTrackerTest, TestNorCondition) { - // Set up the matcher - LogicalOperation operation = LogicalOperation::NOR; - - vector children; - children.push_back(0); - children.push_back(1); - - vector conditionResults; - conditionResults.push_back(ConditionState::kTrue); - conditionResults.push_back(ConditionState::kFalse); - - EXPECT_FALSE(evaluateCombinationCondition(children, operation, conditionResults)); - - conditionResults.clear(); - conditionResults.push_back(ConditionState::kFalse); - conditionResults.push_back(ConditionState::kFalse); - EXPECT_TRUE(evaluateCombinationCondition(children, operation, conditionResults)); - - conditionResults.clear(); - conditionResults.push_back(ConditionState::kTrue); - conditionResults.push_back(ConditionState::kTrue); - EXPECT_FALSE(evaluateCombinationCondition(children, operation, conditionResults)); -} - -#else -GTEST_LOG_(INFO) << "This test does nothing.\n"; -#endif diff --git a/bin/tests/condition/ConditionTimer_test.cpp b/bin/tests/condition/ConditionTimer_test.cpp deleted file mode 100644 index 46dc9a9d..00000000 --- a/bin/tests/condition/ConditionTimer_test.cpp +++ /dev/null @@ -1,68 +0,0 @@ -// Copyright (C) 2019 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 "src/condition/ConditionTimer.h" - -#include -#include - -#ifdef __ANDROID__ - -namespace android { -namespace os { -namespace statsd { - -static int64_t time_base = 10; -static int64_t ct_start_time = 200; - -TEST(ConditionTimerTest, TestTimer_Inital_False) { - ConditionTimer timer(false, time_base); - EXPECT_EQ(false, timer.mCondition); - EXPECT_EQ(0, timer.mTimerNs); - - EXPECT_EQ(0, timer.newBucketStart(ct_start_time)); - EXPECT_EQ(0, timer.mTimerNs); - - timer.onConditionChanged(true, ct_start_time + 5); - EXPECT_EQ(ct_start_time + 5, timer.mLastConditionChangeTimestampNs); - EXPECT_EQ(true, timer.mCondition); - - EXPECT_EQ(95, timer.newBucketStart(ct_start_time + 100)); - EXPECT_EQ(ct_start_time + 100, timer.mLastConditionChangeTimestampNs); - EXPECT_EQ(true, timer.mCondition); -} - -TEST(ConditionTimerTest, TestTimer_Inital_True) { - ConditionTimer timer(true, time_base); - EXPECT_EQ(true, timer.mCondition); - EXPECT_EQ(0, timer.mTimerNs); - - EXPECT_EQ(ct_start_time - time_base, timer.newBucketStart(ct_start_time)); - EXPECT_EQ(true, timer.mCondition); - EXPECT_EQ(0, timer.mTimerNs); - EXPECT_EQ(ct_start_time, timer.mLastConditionChangeTimestampNs); - - timer.onConditionChanged(false, ct_start_time + 5); - EXPECT_EQ(5, timer.mTimerNs); - - EXPECT_EQ(5, timer.newBucketStart(ct_start_time + 100)); - EXPECT_EQ(0, timer.mTimerNs); -} - -} // namespace statsd -} // namespace os -} // namespace android -#else -GTEST_LOG_(INFO) << "This test does nothing.\n"; -#endif diff --git a/bin/tests/condition/SimpleConditionTracker_test.cpp b/bin/tests/condition/SimpleConditionTracker_test.cpp deleted file mode 100644 index 8998b5f9..00000000 --- a/bin/tests/condition/SimpleConditionTracker_test.cpp +++ /dev/null @@ -1,741 +0,0 @@ -// 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 "src/condition/SimpleConditionTracker.h" -#include "stats_event.h" -#include "tests/statsd_test_util.h" - -#include -#include -#include -#include -#include - -using std::map; -using std::unordered_map; -using std::vector; - -#ifdef __ANDROID__ - -namespace android { -namespace os { -namespace statsd { - -namespace { - -const ConfigKey kConfigKey(0, 12345); - -const int ATTRIBUTION_NODE_FIELD_ID = 1; -const int ATTRIBUTION_UID_FIELD_ID = 1; -const int TAG_ID = 1; -const uint64_t protoHash = 0x123456789; - -SimplePredicate getWakeLockHeldCondition(bool countNesting, bool defaultFalse, - bool outputSlicedUid, Position position) { - SimplePredicate simplePredicate; - simplePredicate.set_start(StringToId("WAKE_LOCK_ACQUIRE")); - simplePredicate.set_stop(StringToId("WAKE_LOCK_RELEASE")); - simplePredicate.set_stop_all(StringToId("RELEASE_ALL")); - if (outputSlicedUid) { - simplePredicate.mutable_dimensions()->set_field(TAG_ID); - simplePredicate.mutable_dimensions()->add_child()->set_field(ATTRIBUTION_NODE_FIELD_ID); - simplePredicate.mutable_dimensions()->mutable_child(0)->set_position(position); - simplePredicate.mutable_dimensions()->mutable_child(0)->add_child()->set_field( - ATTRIBUTION_UID_FIELD_ID); - } - - simplePredicate.set_count_nesting(countNesting); - simplePredicate.set_initial_value(defaultFalse ? SimplePredicate_InitialValue_FALSE - : SimplePredicate_InitialValue_UNKNOWN); - return simplePredicate; -} - -void makeWakeLockEvent(LogEvent* logEvent, uint32_t atomId, uint64_t timestamp, - const vector& uids, const string& wl, int acquire) { - AStatsEvent* statsEvent = AStatsEvent_obtain(); - AStatsEvent_setAtomId(statsEvent, atomId); - AStatsEvent_overwriteTimestamp(statsEvent, timestamp); - - vector tags(uids.size()); // vector of empty strings - writeAttribution(statsEvent, uids, tags); - - AStatsEvent_writeString(statsEvent, wl.c_str()); - AStatsEvent_writeInt32(statsEvent, acquire); - - parseStatsEventToLogEvent(statsEvent, logEvent); -} - -} // anonymous namespace - - -std::map getWakeLockQueryKey( - const Position position, - const std::vector &uids, const string& conditionName) { - std::map outputKeyMap; - std::vector uid_indexes; - int pos[] = {1, 1, 1}; - int depth = 2; - Field field(1, pos, depth); - switch(position) { - case Position::FIRST: - uid_indexes.push_back(0); - break; - case Position::LAST: - uid_indexes.push_back(uids.size() - 1); - field.setField(0x02018001); - break; - case Position::ANY: - uid_indexes.resize(uids.size()); - std::iota(uid_indexes.begin(), uid_indexes.end(), 0); - field.setField(0x02010001); - break; - default: - break; - } - - for (const int idx : uid_indexes) { - Value value((int32_t)uids[idx]); - HashableDimensionKey dim; - dim.addValue(FieldValue(field, value)); - outputKeyMap[StringToId(conditionName)] = dim; - } - return outputKeyMap; -} - -TEST(SimpleConditionTrackerTest, TestNonSlicedInitialValueFalse) { - SimplePredicate simplePredicate; - simplePredicate.set_start(StringToId("SCREEN_TURNED_ON")); - simplePredicate.set_stop(StringToId("SCREEN_TURNED_OFF")); - simplePredicate.set_count_nesting(false); - simplePredicate.set_initial_value(SimplePredicate_InitialValue_FALSE); - - unordered_map trackerNameIndexMap; - trackerNameIndexMap[StringToId("SCREEN_TURNED_ON")] = 0; - trackerNameIndexMap[StringToId("SCREEN_TURNED_OFF")] = 1; - - SimpleConditionTracker conditionTracker(kConfigKey, StringToId("SCREEN_IS_ON"), protoHash, - 0 /*tracker index*/, simplePredicate, - trackerNameIndexMap); - - ConditionKey queryKey; - vector> allPredicates; - vector conditionCache(1, ConditionState::kNotEvaluated); - - // Check that initial condition is false. - conditionTracker.isConditionMet(queryKey, allPredicates, false, conditionCache); - EXPECT_EQ(ConditionState::kFalse, conditionCache[0]); - - vector matcherState; - vector changedCache(1, false); - - // Matched stop event. - // Check that condition is still false. - unique_ptr screenOffEvent = - CreateScreenStateChangedEvent(/*timestamp=*/50, android::view::DISPLAY_STATE_OFF); - matcherState.clear(); - matcherState.push_back(MatchingState::kNotMatched); // On matcher not matched - matcherState.push_back(MatchingState::kMatched); // Off matcher matched - conditionCache[0] = ConditionState::kNotEvaluated; - conditionTracker.evaluateCondition(*screenOffEvent, matcherState, allPredicates, conditionCache, - changedCache); - EXPECT_EQ(ConditionState::kFalse, conditionCache[0]); - EXPECT_FALSE(changedCache[0]); - - // Matched start event. - // Check that condition has changed to true. - unique_ptr screenOnEvent = - CreateScreenStateChangedEvent(/*timestamp=*/100, android::view::DISPLAY_STATE_ON); - matcherState.clear(); - matcherState.push_back(MatchingState::kMatched); // On matcher matched - matcherState.push_back(MatchingState::kNotMatched); // Off matcher not matched - conditionCache[0] = ConditionState::kNotEvaluated; - changedCache[0] = false; - conditionTracker.evaluateCondition(*screenOnEvent, matcherState, allPredicates, conditionCache, - changedCache); - EXPECT_EQ(ConditionState::kTrue, conditionCache[0]); - EXPECT_TRUE(changedCache[0]); -} - -TEST(SimpleConditionTrackerTest, TestNonSlicedInitialValueUnknown) { - SimplePredicate simplePredicate; - simplePredicate.set_start(StringToId("SCREEN_TURNED_ON")); - simplePredicate.set_stop(StringToId("SCREEN_TURNED_OFF")); - simplePredicate.set_count_nesting(false); - simplePredicate.set_initial_value(SimplePredicate_InitialValue_UNKNOWN); - - unordered_map trackerNameIndexMap; - trackerNameIndexMap[StringToId("SCREEN_TURNED_ON")] = 0; - trackerNameIndexMap[StringToId("SCREEN_TURNED_OFF")] = 1; - - SimpleConditionTracker conditionTracker(kConfigKey, StringToId("SCREEN_IS_ON"), protoHash, - 0 /*tracker index*/, simplePredicate, - trackerNameIndexMap); - - ConditionKey queryKey; - vector> allPredicates; - vector conditionCache(1, ConditionState::kNotEvaluated); - - // Check that initial condition is unknown. - conditionTracker.isConditionMet(queryKey, allPredicates, false, conditionCache); - EXPECT_EQ(ConditionState::kUnknown, conditionCache[0]); - - vector matcherState; - vector changedCache(1, false); - - // Matched stop event. - // Check that condition is changed to false. - unique_ptr screenOffEvent = - CreateScreenStateChangedEvent(/*timestamp=*/50, android::view::DISPLAY_STATE_OFF); - matcherState.clear(); - matcherState.push_back(MatchingState::kNotMatched); // On matcher not matched - matcherState.push_back(MatchingState::kMatched); // Off matcher matched - conditionCache[0] = ConditionState::kNotEvaluated; - conditionTracker.evaluateCondition(*screenOffEvent, matcherState, allPredicates, conditionCache, - changedCache); - EXPECT_EQ(ConditionState::kFalse, conditionCache[0]); - EXPECT_TRUE(changedCache[0]); - - // Matched start event. - // Check that condition has changed to true. - unique_ptr screenOnEvent = - CreateScreenStateChangedEvent(/*timestamp=*/100, android::view::DISPLAY_STATE_ON); - matcherState.clear(); - matcherState.push_back(MatchingState::kMatched); // On matcher matched - matcherState.push_back(MatchingState::kNotMatched); // Off matcher not matched - conditionCache[0] = ConditionState::kNotEvaluated; - changedCache[0] = false; - conditionTracker.evaluateCondition(*screenOnEvent, matcherState, allPredicates, conditionCache, - changedCache); - EXPECT_EQ(ConditionState::kTrue, conditionCache[0]); - EXPECT_TRUE(changedCache[0]); -} - -TEST(SimpleConditionTrackerTest, TestNonSlicedCondition) { - SimplePredicate simplePredicate; - simplePredicate.set_start(StringToId("SCREEN_TURNED_ON")); - simplePredicate.set_stop(StringToId("SCREEN_TURNED_OFF")); - simplePredicate.set_count_nesting(false); - simplePredicate.set_initial_value(SimplePredicate_InitialValue_UNKNOWN); - - unordered_map trackerNameIndexMap; - trackerNameIndexMap[StringToId("SCREEN_TURNED_ON")] = 0; - trackerNameIndexMap[StringToId("SCREEN_TURNED_OFF")] = 1; - - SimpleConditionTracker conditionTracker(kConfigKey, StringToId("SCREEN_IS_ON"), protoHash, - 0 /*tracker index*/, simplePredicate, - trackerNameIndexMap); - EXPECT_FALSE(conditionTracker.isSliced()); - - // This event is not accessed in this test besides dimensions which is why this is okay. - // This is technically an invalid LogEvent because we do not call parseBuffer. - LogEvent event(/*uid=*/0, /*pid=*/0); - - vector matcherState; - matcherState.push_back(MatchingState::kNotMatched); - matcherState.push_back(MatchingState::kNotMatched); - - vector> allPredicates; - vector conditionCache(1, ConditionState::kNotEvaluated); - vector changedCache(1, false); - - conditionTracker.evaluateCondition(event, matcherState, allPredicates, conditionCache, - changedCache); - // not matched start or stop. condition doesn't change - EXPECT_EQ(ConditionState::kUnknown, conditionCache[0]); - EXPECT_FALSE(changedCache[0]); - - // prepare a case for match start. - matcherState.clear(); - matcherState.push_back(MatchingState::kMatched); - matcherState.push_back(MatchingState::kNotMatched); - conditionCache[0] = ConditionState::kNotEvaluated; - changedCache[0] = false; - - conditionTracker.evaluateCondition(event, matcherState, allPredicates, conditionCache, - changedCache); - // now condition should change to true. - EXPECT_EQ(ConditionState::kTrue, conditionCache[0]); - EXPECT_TRUE(changedCache[0]); - - // match nothing. - matcherState.clear(); - matcherState.push_back(MatchingState::kNotMatched); - matcherState.push_back(MatchingState::kNotMatched); - conditionCache[0] = ConditionState::kNotEvaluated; - changedCache[0] = false; - - conditionTracker.evaluateCondition(event, matcherState, allPredicates, conditionCache, - changedCache); - EXPECT_EQ(ConditionState::kTrue, conditionCache[0]); - EXPECT_FALSE(changedCache[0]); - - // the case for match stop. - matcherState.clear(); - matcherState.push_back(MatchingState::kNotMatched); - matcherState.push_back(MatchingState::kMatched); - conditionCache[0] = ConditionState::kNotEvaluated; - changedCache[0] = false; - - conditionTracker.evaluateCondition(event, matcherState, allPredicates, conditionCache, - changedCache); - - // condition changes to false. - EXPECT_EQ(ConditionState::kFalse, conditionCache[0]); - EXPECT_TRUE(changedCache[0]); - - // match stop again. - matcherState.clear(); - matcherState.push_back(MatchingState::kNotMatched); - matcherState.push_back(MatchingState::kMatched); - conditionCache[0] = ConditionState::kNotEvaluated; - changedCache[0] = false; - - conditionTracker.evaluateCondition(event, matcherState, allPredicates, conditionCache, - changedCache); - // condition should still be false. not changed. - EXPECT_EQ(ConditionState::kFalse, conditionCache[0]); - EXPECT_FALSE(changedCache[0]); -} - -TEST(SimpleConditionTrackerTest, TestNonSlicedConditionNestCounting) { - std::vector> allConditions; - SimplePredicate simplePredicate; - simplePredicate.set_start(StringToId("SCREEN_TURNED_ON")); - simplePredicate.set_stop(StringToId("SCREEN_TURNED_OFF")); - simplePredicate.set_count_nesting(true); - - unordered_map trackerNameIndexMap; - trackerNameIndexMap[StringToId("SCREEN_TURNED_ON")] = 0; - trackerNameIndexMap[StringToId("SCREEN_TURNED_OFF")] = 1; - - SimpleConditionTracker conditionTracker(kConfigKey, StringToId("SCREEN_IS_ON"), protoHash, - 0 /*condition tracker index*/, simplePredicate, - trackerNameIndexMap); - EXPECT_FALSE(conditionTracker.isSliced()); - - // This event is not accessed in this test besides dimensions which is why this is okay. - // This is technically an invalid LogEvent because we do not call parseBuffer. - LogEvent event(/*uid=*/0, /*pid=*/0); - - // one matched start - vector matcherState; - matcherState.push_back(MatchingState::kMatched); - matcherState.push_back(MatchingState::kNotMatched); - vector> allPredicates; - vector conditionCache(1, ConditionState::kNotEvaluated); - vector changedCache(1, false); - - conditionTracker.evaluateCondition(event, matcherState, allPredicates, conditionCache, - changedCache); - - EXPECT_EQ(ConditionState::kTrue, conditionCache[0]); - EXPECT_TRUE(changedCache[0]); - - // prepare for another matched start. - matcherState.clear(); - matcherState.push_back(MatchingState::kMatched); - matcherState.push_back(MatchingState::kNotMatched); - conditionCache[0] = ConditionState::kNotEvaluated; - changedCache[0] = false; - - conditionTracker.evaluateCondition(event, matcherState, allPredicates, conditionCache, - changedCache); - - EXPECT_EQ(ConditionState::kTrue, conditionCache[0]); - EXPECT_FALSE(changedCache[0]); - - // ONE MATCHED STOP - matcherState.clear(); - matcherState.push_back(MatchingState::kNotMatched); - matcherState.push_back(MatchingState::kMatched); - conditionCache[0] = ConditionState::kNotEvaluated; - changedCache[0] = false; - - conditionTracker.evaluateCondition(event, matcherState, allPredicates, conditionCache, - changedCache); - // result should still be true - EXPECT_EQ(ConditionState::kTrue, conditionCache[0]); - EXPECT_FALSE(changedCache[0]); - - // ANOTHER MATCHED STOP - matcherState.clear(); - matcherState.push_back(MatchingState::kNotMatched); - matcherState.push_back(MatchingState::kMatched); - conditionCache[0] = ConditionState::kNotEvaluated; - changedCache[0] = false; - - conditionTracker.evaluateCondition(event, matcherState, allPredicates, conditionCache, - changedCache); - EXPECT_EQ(ConditionState::kFalse, conditionCache[0]); - EXPECT_TRUE(changedCache[0]); -} - -TEST(SimpleConditionTrackerTest, TestSlicedCondition) { - std::vector> allConditions; - for (Position position : {Position::FIRST, Position::LAST}) { - SimplePredicate simplePredicate = getWakeLockHeldCondition( - true /*nesting*/, true /*default to false*/, true /*output slice by uid*/, - position); - string conditionName = "WL_HELD_BY_UID2"; - - unordered_map trackerNameIndexMap; - trackerNameIndexMap[StringToId("WAKE_LOCK_ACQUIRE")] = 0; - trackerNameIndexMap[StringToId("WAKE_LOCK_RELEASE")] = 1; - trackerNameIndexMap[StringToId("RELEASE_ALL")] = 2; - - SimpleConditionTracker conditionTracker(kConfigKey, StringToId(conditionName), protoHash, - 0 /*condition tracker index*/, simplePredicate, - trackerNameIndexMap); - - std::vector uids = {111, 222, 333}; - - LogEvent event1(/*uid=*/0, /*pid=*/0); - makeWakeLockEvent(&event1, /*atomId=*/1, /*timestamp=*/0, uids, "wl1", /*acquire=*/1); - - // one matched start - vector matcherState; - matcherState.push_back(MatchingState::kMatched); - matcherState.push_back(MatchingState::kNotMatched); - matcherState.push_back(MatchingState::kNotMatched); - vector> allPredicates; - vector conditionCache(1, ConditionState::kNotEvaluated); - vector changedCache(1, false); - - conditionTracker.evaluateCondition(event1, matcherState, allPredicates, conditionCache, - changedCache); - - if (position == Position::FIRST || position == Position::LAST) { - ASSERT_EQ(1UL, conditionTracker.mSlicedConditionState.size()); - } else { - ASSERT_EQ(uids.size(), conditionTracker.mSlicedConditionState.size()); - } - EXPECT_TRUE(changedCache[0]); - if (position == Position::FIRST || position == Position::LAST) { - ASSERT_EQ(conditionTracker.getChangedToTrueDimensions(allConditions)->size(), 1u); - EXPECT_TRUE(conditionTracker.getChangedToFalseDimensions(allConditions)->empty()); - } else { - EXPECT_EQ(conditionTracker.getChangedToTrueDimensions(allConditions)->size(), - uids.size()); - } - - // Now test query - const auto queryKey = getWakeLockQueryKey(position, uids, conditionName); - conditionCache[0] = ConditionState::kNotEvaluated; - - conditionTracker.isConditionMet(queryKey, allPredicates, false, conditionCache); - EXPECT_EQ(ConditionState::kTrue, conditionCache[0]); - - // another wake lock acquired by this uid - LogEvent event2(/*uid=*/0, /*pid=*/0); - makeWakeLockEvent(&event2, /*atomId=*/1, /*timestamp=*/0, uids, "wl2", /*acquire=*/1); - matcherState.clear(); - matcherState.push_back(MatchingState::kMatched); - matcherState.push_back(MatchingState::kNotMatched); - conditionCache[0] = ConditionState::kNotEvaluated; - changedCache[0] = false; - conditionTracker.evaluateCondition(event2, matcherState, allPredicates, conditionCache, - changedCache); - EXPECT_FALSE(changedCache[0]); - if (position == Position::FIRST || position == Position::LAST) { - ASSERT_EQ(1UL, conditionTracker.mSlicedConditionState.size()); - } else { - ASSERT_EQ(uids.size(), conditionTracker.mSlicedConditionState.size()); - } - EXPECT_TRUE(conditionTracker.getChangedToTrueDimensions(allConditions)->empty()); - EXPECT_TRUE(conditionTracker.getChangedToFalseDimensions(allConditions)->empty()); - - - // wake lock 1 release - LogEvent event3(/*uid=*/0, /*pid=*/0); - makeWakeLockEvent(&event3, /*atomId=*/1, /*timestamp=*/0, uids, "wl1", /*acquire=*/0); - matcherState.clear(); - matcherState.push_back(MatchingState::kNotMatched); - matcherState.push_back(MatchingState::kMatched); - conditionCache[0] = ConditionState::kNotEvaluated; - changedCache[0] = false; - conditionTracker.evaluateCondition(event3, matcherState, allPredicates, conditionCache, - changedCache); - // nothing changes, because wake lock 2 is still held for this uid - EXPECT_FALSE(changedCache[0]); - if (position == Position::FIRST || position == Position::LAST) { - ASSERT_EQ(1UL, conditionTracker.mSlicedConditionState.size()); - } else { - ASSERT_EQ(uids.size(), conditionTracker.mSlicedConditionState.size()); - } - EXPECT_TRUE(conditionTracker.getChangedToTrueDimensions(allConditions)->empty()); - EXPECT_TRUE(conditionTracker.getChangedToFalseDimensions(allConditions)->empty()); - - LogEvent event4(/*uid=*/0, /*pid=*/0); - makeWakeLockEvent(&event4, /*atomId=*/1, /*timestamp=*/0, uids, "wl2", /*acquire=*/0); - matcherState.clear(); - matcherState.push_back(MatchingState::kNotMatched); - matcherState.push_back(MatchingState::kMatched); - conditionCache[0] = ConditionState::kNotEvaluated; - changedCache[0] = false; - conditionTracker.evaluateCondition(event4, matcherState, allPredicates, conditionCache, - changedCache); - ASSERT_EQ(0UL, conditionTracker.mSlicedConditionState.size()); - EXPECT_TRUE(changedCache[0]); - if (position == Position::FIRST || position == Position::LAST) { - ASSERT_EQ(conditionTracker.getChangedToFalseDimensions(allConditions)->size(), 1u); - EXPECT_TRUE(conditionTracker.getChangedToTrueDimensions(allConditions)->empty()); - } else { - EXPECT_EQ(conditionTracker.getChangedToFalseDimensions(allConditions)->size(), - uids.size()); - } - - // query again - conditionCache[0] = ConditionState::kNotEvaluated; - conditionTracker.isConditionMet(queryKey, allPredicates, false, conditionCache); - EXPECT_EQ(ConditionState::kFalse, conditionCache[0]); - } - -} - -TEST(SimpleConditionTrackerTest, TestSlicedWithNoOutputDim) { - std::vector> allConditions; - - SimplePredicate simplePredicate = - getWakeLockHeldCondition(true /*nesting*/, true /*default to false*/, - false /*slice output by uid*/, Position::ANY /* position */); - string conditionName = "WL_HELD"; - - unordered_map trackerNameIndexMap; - trackerNameIndexMap[StringToId("WAKE_LOCK_ACQUIRE")] = 0; - trackerNameIndexMap[StringToId("WAKE_LOCK_RELEASE")] = 1; - trackerNameIndexMap[StringToId("RELEASE_ALL")] = 2; - - SimpleConditionTracker conditionTracker(kConfigKey, StringToId(conditionName), protoHash, - 0 /*condition tracker index*/, simplePredicate, - trackerNameIndexMap); - - EXPECT_FALSE(conditionTracker.isSliced()); - - std::vector uids1 = {111, 1111, 11111}; - string uid1_wl1 = "wl1_1"; - std::vector uids2 = {222, 2222, 22222}; - string uid2_wl1 = "wl2_1"; - - LogEvent event1(/*uid=*/0, /*pid=*/0); - makeWakeLockEvent(&event1, /*atomId=*/1, /*timestamp=*/0, uids1, uid1_wl1, /*acquire=*/1); - - // one matched start for uid1 - vector matcherState; - matcherState.push_back(MatchingState::kMatched); - matcherState.push_back(MatchingState::kNotMatched); - matcherState.push_back(MatchingState::kNotMatched); - vector> allPredicates; - vector conditionCache(1, ConditionState::kNotEvaluated); - vector changedCache(1, false); - - conditionTracker.evaluateCondition(event1, matcherState, allPredicates, conditionCache, - changedCache); - - ASSERT_EQ(1UL, conditionTracker.mSlicedConditionState.size()); - EXPECT_TRUE(changedCache[0]); - - // Now test query - ConditionKey queryKey; - conditionCache[0] = ConditionState::kNotEvaluated; - - conditionTracker.isConditionMet(queryKey, allPredicates, true, conditionCache); - EXPECT_EQ(ConditionState::kTrue, conditionCache[0]); - - // another wake lock acquired by this uid - LogEvent event2(/*uid=*/0, /*pid=*/0); - makeWakeLockEvent(&event2, /*atomId=*/1, /*timestamp=*/0, uids2, uid2_wl1, /*acquire=*/1); - - matcherState.clear(); - matcherState.push_back(MatchingState::kMatched); - matcherState.push_back(MatchingState::kNotMatched); - conditionCache[0] = ConditionState::kNotEvaluated; - changedCache[0] = false; - conditionTracker.evaluateCondition(event2, matcherState, allPredicates, conditionCache, - changedCache); - EXPECT_FALSE(changedCache[0]); - - // uid1 wake lock 1 release - LogEvent event3(/*uid=*/0, /*pid=*/0); - makeWakeLockEvent(&event3, /*atomId=*/1, /*timestamp=*/0, uids1, uid1_wl1, - /*release=*/0); // now release it. - - matcherState.clear(); - matcherState.push_back(MatchingState::kNotMatched); - matcherState.push_back(MatchingState::kMatched); - conditionCache[0] = ConditionState::kNotEvaluated; - changedCache[0] = false; - conditionTracker.evaluateCondition(event3, matcherState, allPredicates, conditionCache, - changedCache); - // nothing changes, because uid2 is still holding wl. - EXPECT_FALSE(changedCache[0]); - - LogEvent event4(/*uid=*/0, /*pid=*/0); - makeWakeLockEvent(&event4, /*atomId=*/1, /*timestamp=*/0, uids2, uid2_wl1, - /*acquire=*/0); // now release it. - matcherState.clear(); - matcherState.push_back(MatchingState::kNotMatched); - matcherState.push_back(MatchingState::kMatched); - conditionCache[0] = ConditionState::kNotEvaluated; - changedCache[0] = false; - conditionTracker.evaluateCondition(event4, matcherState, allPredicates, conditionCache, - changedCache); - ASSERT_EQ(0UL, conditionTracker.mSlicedConditionState.size()); - EXPECT_TRUE(changedCache[0]); - - // query again - conditionCache[0] = ConditionState::kNotEvaluated; - conditionTracker.isConditionMet(queryKey, allPredicates, true, conditionCache); - EXPECT_EQ(ConditionState::kFalse, conditionCache[0]); -} - -TEST(SimpleConditionTrackerTest, TestStopAll) { - std::vector> allConditions; - for (Position position : {Position::FIRST, Position::LAST}) { - SimplePredicate simplePredicate = - getWakeLockHeldCondition(true /*nesting*/, true /*default to false*/, - true /*output slice by uid*/, position); - string conditionName = "WL_HELD_BY_UID3"; - - unordered_map trackerNameIndexMap; - trackerNameIndexMap[StringToId("WAKE_LOCK_ACQUIRE")] = 0; - trackerNameIndexMap[StringToId("WAKE_LOCK_RELEASE")] = 1; - trackerNameIndexMap[StringToId("RELEASE_ALL")] = 2; - - SimpleConditionTracker conditionTracker(kConfigKey, StringToId(conditionName), protoHash, - 0 /*condition tracker index*/, simplePredicate, - trackerNameIndexMap); - - std::vector uids1 = {111, 1111, 11111}; - std::vector uids2 = {222, 2222, 22222}; - - LogEvent event1(/*uid=*/0, /*pid=*/0); - makeWakeLockEvent(&event1, /*atomId=*/1, /*timestamp=*/0, uids1, "wl1", /*acquire=*/1); - - // one matched start - vector matcherState; - matcherState.push_back(MatchingState::kMatched); - matcherState.push_back(MatchingState::kNotMatched); - matcherState.push_back(MatchingState::kNotMatched); - vector> allPredicates; - vector conditionCache(1, ConditionState::kNotEvaluated); - vector changedCache(1, false); - - conditionTracker.evaluateCondition(event1, matcherState, allPredicates, conditionCache, - changedCache); - if (position == Position::FIRST || position == Position::LAST) { - ASSERT_EQ(1UL, conditionTracker.mSlicedConditionState.size()); - } else { - ASSERT_EQ(uids1.size(), conditionTracker.mSlicedConditionState.size()); - } - EXPECT_TRUE(changedCache[0]); - { - if (position == Position::FIRST || position == Position::LAST) { - ASSERT_EQ(1UL, conditionTracker.getChangedToTrueDimensions(allConditions)->size()); - EXPECT_TRUE(conditionTracker.getChangedToFalseDimensions(allConditions)->empty()); - } else { - EXPECT_EQ(uids1.size(), - conditionTracker.getChangedToTrueDimensions(allConditions)->size()); - EXPECT_TRUE(conditionTracker.getChangedToFalseDimensions(allConditions)->empty()); - } - } - - // Now test query - const auto queryKey = getWakeLockQueryKey(position, uids1, conditionName); - conditionCache[0] = ConditionState::kNotEvaluated; - - conditionTracker.isConditionMet(queryKey, allPredicates, false, conditionCache); - EXPECT_EQ(ConditionState::kTrue, conditionCache[0]); - - // another wake lock acquired by uid2 - LogEvent event2(/*uid=*/0, /*pid=*/0); - makeWakeLockEvent(&event2, /*atomId=*/1, /*timestamp=*/0, uids2, "wl2", /*acquire=*/1); - - matcherState.clear(); - matcherState.push_back(MatchingState::kMatched); - matcherState.push_back(MatchingState::kNotMatched); - matcherState.push_back(MatchingState::kNotMatched); - conditionCache[0] = ConditionState::kNotEvaluated; - changedCache[0] = false; - conditionTracker.evaluateCondition(event2, matcherState, allPredicates, conditionCache, - changedCache); - if (position == Position::FIRST || position == Position::LAST) { - ASSERT_EQ(2UL, conditionTracker.mSlicedConditionState.size()); - } else { - ASSERT_EQ(uids1.size() + uids2.size(), conditionTracker.mSlicedConditionState.size()); - } - EXPECT_TRUE(changedCache[0]); - { - if (position == Position::FIRST || position == Position::LAST) { - ASSERT_EQ(1UL, conditionTracker.getChangedToTrueDimensions(allConditions)->size()); - EXPECT_TRUE(conditionTracker.getChangedToFalseDimensions(allConditions)->empty()); - } else { - EXPECT_EQ(uids2.size(), - conditionTracker.getChangedToTrueDimensions(allConditions)->size()); - EXPECT_TRUE(conditionTracker.getChangedToFalseDimensions(allConditions)->empty()); - } - } - - // TEST QUERY - const auto queryKey2 = getWakeLockQueryKey(position, uids2, conditionName); - conditionCache[0] = ConditionState::kNotEvaluated; - conditionTracker.isConditionMet(queryKey, allPredicates, false, conditionCache); - - EXPECT_EQ(ConditionState::kTrue, conditionCache[0]); - - // stop all event - LogEvent event3(/*uid=*/0, /*pid=*/0); - makeWakeLockEvent(&event3, /*atomId=*/1, /*timestamp=*/0, uids2, "wl2", /*acquire=*/1); - - matcherState.clear(); - matcherState.push_back(MatchingState::kNotMatched); - matcherState.push_back(MatchingState::kNotMatched); - matcherState.push_back(MatchingState::kMatched); - - conditionCache[0] = ConditionState::kNotEvaluated; - changedCache[0] = false; - conditionTracker.evaluateCondition(event3, matcherState, allPredicates, conditionCache, - changedCache); - EXPECT_TRUE(changedCache[0]); - ASSERT_EQ(0UL, conditionTracker.mSlicedConditionState.size()); - { - if (position == Position::FIRST || position == Position::LAST) { - ASSERT_EQ(2UL, conditionTracker.getChangedToFalseDimensions(allConditions)->size()); - EXPECT_TRUE(conditionTracker.getChangedToTrueDimensions(allConditions)->empty()); - } else { - EXPECT_EQ(uids1.size() + uids2.size(), - conditionTracker.getChangedToFalseDimensions(allConditions)->size()); - EXPECT_TRUE(conditionTracker.getChangedToTrueDimensions(allConditions)->empty()); - } - } - - // TEST QUERY - const auto queryKey3 = getWakeLockQueryKey(position, uids1, conditionName); - conditionCache[0] = ConditionState::kNotEvaluated; - conditionTracker.isConditionMet(queryKey, allPredicates, false, conditionCache); - EXPECT_EQ(ConditionState::kFalse, conditionCache[0]); - - // TEST QUERY - const auto queryKey4 = getWakeLockQueryKey(position, uids2, conditionName); - conditionCache[0] = ConditionState::kNotEvaluated; - conditionTracker.isConditionMet(queryKey, allPredicates, false, conditionCache); - EXPECT_EQ(ConditionState::kFalse, conditionCache[0]); - } -} - -} // namespace statsd -} // namespace os -} // namespace android -#else -GTEST_LOG_(INFO) << "This test does nothing.\n"; -#endif diff --git a/bin/tests/e2e/Alarm_e2e_test.cpp b/bin/tests/e2e/Alarm_e2e_test.cpp deleted file mode 100644 index 93b27838..00000000 --- a/bin/tests/e2e/Alarm_e2e_test.cpp +++ /dev/null @@ -1,92 +0,0 @@ -// 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 CreateStatsdConfig() { - StatsdConfig config; - config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root. - - auto alarm = config.add_alarm(); - alarm->set_id(123456); - alarm->set_offset_millis(TimeUnitToBucketSizeInMillis(TEN_MINUTES)); - alarm->set_period_millis(TimeUnitToBucketSizeInMillis(ONE_HOUR)); - - alarm = config.add_alarm(); - alarm->set_id(654321); - alarm->set_offset_millis(TimeUnitToBucketSizeInMillis(FIVE_MINUTES)); - alarm->set_period_millis(TimeUnitToBucketSizeInMillis(THIRTY_MINUTES)); - return config; -} - -} // namespace - -TEST(AlarmE2eTest, TestMultipleAlarms) { - auto config = CreateStatsdConfig(); - int64_t bucketStartTimeNs = 10000000000; - - ConfigKey cfgKey; - auto processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey); - ASSERT_EQ(processor->mMetricsManagers.size(), 1u); - EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid()); - ASSERT_EQ(2u, processor->mMetricsManagers.begin()->second->mAllPeriodicAlarmTrackers.size()); - - auto alarmTracker1 = processor->mMetricsManagers.begin()->second->mAllPeriodicAlarmTrackers[0]; - auto alarmTracker2 = processor->mMetricsManagers.begin()->second->mAllPeriodicAlarmTrackers[1]; - - int64_t alarmTimestampSec0 = bucketStartTimeNs / NS_PER_SEC + 10 * 60; - int64_t alarmTimestampSec1 = bucketStartTimeNs / NS_PER_SEC + 5 * 60; - EXPECT_EQ(alarmTimestampSec0, alarmTracker1->getAlarmTimestampSec()); - EXPECT_EQ(alarmTimestampSec1, alarmTracker2->getAlarmTimestampSec()); - - // Alarm fired. - const int64_t alarmFiredTimestampSec0 = alarmTimestampSec1 + 5; - auto alarmSet = processor->getPeriodicAlarmMonitor()->popSoonerThan( - static_cast(alarmFiredTimestampSec0)); - ASSERT_EQ(1u, alarmSet.size()); - processor->onPeriodicAlarmFired(alarmFiredTimestampSec0 * NS_PER_SEC, alarmSet); - EXPECT_EQ(alarmTimestampSec0, alarmTracker1->getAlarmTimestampSec()); - EXPECT_EQ(alarmTimestampSec1 + 30 * 60, alarmTracker2->getAlarmTimestampSec()); - - // Alarms fired very late. - const int64_t alarmFiredTimestampSec1 = alarmTimestampSec0 + 2 * 60 * 60 + 125; - alarmSet = processor->getPeriodicAlarmMonitor()->popSoonerThan( - static_cast(alarmFiredTimestampSec1)); - ASSERT_EQ(2u, alarmSet.size()); - processor->onPeriodicAlarmFired(alarmFiredTimestampSec1 * NS_PER_SEC, alarmSet); - EXPECT_EQ(alarmTimestampSec0 + 60 * 60 * 3, alarmTracker1->getAlarmTimestampSec()); - EXPECT_EQ(alarmTimestampSec1 + 30 * 60 * 5, alarmTracker2->getAlarmTimestampSec()); -} - -#else -GTEST_LOG_(INFO) << "This test does nothing.\n"; -#endif - -} // namespace statsd -} // namespace os -} // namespace android diff --git a/bin/tests/e2e/Anomaly_count_e2e_test.cpp b/bin/tests/e2e/Anomaly_count_e2e_test.cpp deleted file mode 100644 index e6d81288..00000000 --- a/bin/tests/e2e/Anomaly_count_e2e_test.cpp +++ /dev/null @@ -1,390 +0,0 @@ -// Copyright (C) 2018 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 "packages/modules/StatsD/bin/src/statsd_metadata.pb.h" -#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 CreateStatsdConfig(int num_buckets, int threshold, int refractory_period_sec) { - StatsdConfig config; - config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root. - auto wakelockAcquireMatcher = CreateAcquireWakelockAtomMatcher(); - - *config.add_atom_matcher() = wakelockAcquireMatcher; - - auto countMetric = config.add_count_metric(); - countMetric->set_id(123456); - countMetric->set_what(wakelockAcquireMatcher.id()); - *countMetric->mutable_dimensions_in_what() = CreateAttributionUidDimensions( - util::WAKELOCK_STATE_CHANGED, {Position::FIRST}); - countMetric->set_bucket(FIVE_MINUTES); - - auto alert = config.add_alert(); - alert->set_id(StringToId("alert")); - alert->set_metric_id(123456); - alert->set_num_buckets(num_buckets); - alert->set_refractory_period_secs(refractory_period_sec); - alert->set_trigger_if_sum_gt(threshold); - return config; -} - -} // namespace - -TEST(AnomalyDetectionE2eTest, TestSlicedCountMetric_single_bucket) { - const int num_buckets = 1; - const int threshold = 3; - const int refractory_period_sec = 10; - auto config = CreateStatsdConfig(num_buckets, threshold, refractory_period_sec); - const uint64_t alert_id = config.alert(0).id(); - - int64_t bucketStartTimeNs = 10000000000; - int64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(config.count_metric(0).bucket()) * 1000000; - - ConfigKey cfgKey; - auto processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey); - ASSERT_EQ(processor->mMetricsManagers.size(), 1u); - EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid()); - ASSERT_EQ(1u, processor->mMetricsManagers.begin()->second->mAllAnomalyTrackers.size()); - - sp anomalyTracker = - processor->mMetricsManagers.begin()->second->mAllAnomalyTrackers[0]; - - std::vector attributionUids1 = {111}; - std::vector attributionTags1 = {"App1"}; - std::vector attributionUids2 = {111, 222}; - std::vector attributionTags2 = {"App1", "GMSCoreModule1"}; - std::vector attributionUids3 = {111, 333}; - std::vector attributionTags3 = {"App1", "App3"}; - std::vector attributionUids4 = {222, 333}; - std::vector attributionTags4 = {"GMSCoreModule1", "App3"}; - std::vector attributionUids5 = {222}; - std::vector attributionTags5 = {"GMSCoreModule1"}; - - FieldValue fieldValue1(Field(util::WAKELOCK_STATE_CHANGED, (int32_t)0x02010101), - Value((int32_t)111)); - HashableDimensionKey whatKey1({fieldValue1}); - MetricDimensionKey dimensionKey1(whatKey1, DEFAULT_DIMENSION_KEY); - - FieldValue fieldValue2(Field(util::WAKELOCK_STATE_CHANGED, (int32_t)0x02010101), - Value((int32_t)222)); - HashableDimensionKey whatKey2({fieldValue2}); - MetricDimensionKey dimensionKey2(whatKey2, DEFAULT_DIMENSION_KEY); - - auto event = CreateAcquireWakelockEvent(bucketStartTimeNs + 2, attributionUids1, - attributionTags1, "wl1"); - processor->OnLogEvent(event.get()); - EXPECT_EQ(0u, anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1)); - - event = CreateAcquireWakelockEvent(bucketStartTimeNs + 2, attributionUids4, attributionTags4, - "wl2"); - processor->OnLogEvent(event.get()); - EXPECT_EQ(0u, anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey2)); - - event = CreateAcquireWakelockEvent(bucketStartTimeNs + 3, attributionUids2, attributionTags2, - "wl1"); - processor->OnLogEvent(event.get()); - EXPECT_EQ(0u, anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1)); - - event = CreateAcquireWakelockEvent(bucketStartTimeNs + 3, attributionUids5, attributionTags5, - "wl2"); - processor->OnLogEvent(event.get()); - EXPECT_EQ(0u, anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey2)); - - event = CreateAcquireWakelockEvent(bucketStartTimeNs + 4, attributionUids3, attributionTags3, - "wl1"); - processor->OnLogEvent(event.get()); - EXPECT_EQ(0u, anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1)); - - event = CreateAcquireWakelockEvent(bucketStartTimeNs + 4, attributionUids5, attributionTags5, - "wl2"); - processor->OnLogEvent(event.get()); - EXPECT_EQ(0u, anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey2)); - - // Fired alarm and refractory period end timestamp updated. - event = CreateAcquireWakelockEvent(bucketStartTimeNs + 5, attributionUids1, attributionTags1, - "wl1"); - processor->OnLogEvent(event.get()); - EXPECT_EQ(refractory_period_sec + bucketStartTimeNs / NS_PER_SEC + 1, - anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1)); - - event = CreateAcquireWakelockEvent(bucketStartTimeNs + 100, attributionUids1, attributionTags1, - "wl1"); - processor->OnLogEvent(event.get()); - EXPECT_EQ(refractory_period_sec + bucketStartTimeNs / NS_PER_SEC + 1, - anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1)); - - event = CreateAcquireWakelockEvent(bucketStartTimeNs + bucketSizeNs - 1, attributionUids1, - attributionTags1, "wl1"); - processor->OnLogEvent(event.get()); - EXPECT_EQ(refractory_period_sec + (bucketStartTimeNs + bucketSizeNs - 1) / NS_PER_SEC + 1, - anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1)); - - event = CreateAcquireWakelockEvent(bucketStartTimeNs + bucketSizeNs + 1, attributionUids1, - attributionTags1, "wl1"); - processor->OnLogEvent(event.get()); - EXPECT_EQ(refractory_period_sec + (bucketStartTimeNs + bucketSizeNs - 1) / NS_PER_SEC + 1, - anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1)); - - event = CreateAcquireWakelockEvent(bucketStartTimeNs + bucketSizeNs + 1, attributionUids4, - attributionTags4, "wl2"); - processor->OnLogEvent(event.get()); - EXPECT_EQ(0u, anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey2)); - - event = CreateAcquireWakelockEvent(bucketStartTimeNs + bucketSizeNs + 2, attributionUids5, - attributionTags5, "wl2"); - processor->OnLogEvent(event.get()); - EXPECT_EQ(0u, anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey2)); - - event = CreateAcquireWakelockEvent(bucketStartTimeNs + bucketSizeNs + 3, attributionUids5, - attributionTags5, "wl2"); - processor->OnLogEvent(event.get()); - EXPECT_EQ(0u, anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey2)); - - event = CreateAcquireWakelockEvent(bucketStartTimeNs + bucketSizeNs + 4, attributionUids5, - attributionTags5, "wl2"); - processor->OnLogEvent(event.get()); - EXPECT_EQ(refractory_period_sec + (bucketStartTimeNs + bucketSizeNs + 4) / NS_PER_SEC + 1, - anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey2)); -} - -TEST(AnomalyDetectionE2eTest, TestSlicedCountMetric_multiple_buckets) { - const int num_buckets = 3; - const int threshold = 3; - const int refractory_period_sec = 10; - auto config = CreateStatsdConfig(num_buckets, threshold, refractory_period_sec); - const uint64_t alert_id = config.alert(0).id(); - - int64_t bucketStartTimeNs = 10000000000; - int64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(config.count_metric(0).bucket()) * 1000000; - - ConfigKey cfgKey; - auto processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey); - ASSERT_EQ(processor->mMetricsManagers.size(), 1u); - EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid()); - ASSERT_EQ(1u, processor->mMetricsManagers.begin()->second->mAllAnomalyTrackers.size()); - - sp anomalyTracker = - processor->mMetricsManagers.begin()->second->mAllAnomalyTrackers[0]; - - std::vector attributionUids1 = {111}; - std::vector attributionTags1 = {"App1"}; - std::vector attributionUids2 = {111, 222}; - std::vector attributionTags2 = {"App1", "GMSCoreModule1"}; - - FieldValue fieldValue1(Field(util::WAKELOCK_STATE_CHANGED, (int32_t)0x02010101), - Value((int32_t)111)); - HashableDimensionKey whatKey1({fieldValue1}); - MetricDimensionKey dimensionKey1(whatKey1, DEFAULT_DIMENSION_KEY); - - auto event = CreateAcquireWakelockEvent(bucketStartTimeNs + 2, attributionUids1, - attributionTags1, "wl1"); - processor->OnLogEvent(event.get()); - EXPECT_EQ(0u, anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1)); - - event = CreateAcquireWakelockEvent(bucketStartTimeNs + 3, attributionUids2, attributionTags2, - "wl1"); - processor->OnLogEvent(event.get()); - EXPECT_EQ(0u, anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1)); - - // Fired alarm and refractory period end timestamp updated. - event = CreateAcquireWakelockEvent(bucketStartTimeNs + 4, attributionUids1, attributionTags1, - "wl1"); - processor->OnLogEvent(event.get()); - EXPECT_EQ(0u, anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1)); - - event = CreateAcquireWakelockEvent(bucketStartTimeNs + bucketSizeNs + 1, attributionUids1, - attributionTags1, "wl1"); - processor->OnLogEvent(event.get()); - EXPECT_EQ(refractory_period_sec + (bucketStartTimeNs + bucketSizeNs + 1) / NS_PER_SEC + 1, - anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1)); - - event = CreateAcquireWakelockEvent(bucketStartTimeNs + bucketSizeNs + 2, attributionUids2, - attributionTags2, "wl1"); - processor->OnLogEvent(event.get()); - EXPECT_EQ(refractory_period_sec + (bucketStartTimeNs + bucketSizeNs + 1) / NS_PER_SEC + 1, - anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1)); - - event = CreateAcquireWakelockEvent(bucketStartTimeNs + 3 * bucketSizeNs + 1, attributionUids2, - attributionTags2, "wl1"); - processor->OnLogEvent(event.get()); - EXPECT_EQ(refractory_period_sec + (bucketStartTimeNs + bucketSizeNs + 1) / NS_PER_SEC + 1, - anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1)); - - event = CreateAcquireWakelockEvent(bucketStartTimeNs + 3 * bucketSizeNs + 2, attributionUids2, - attributionTags2, "wl1"); - processor->OnLogEvent(event.get()); - EXPECT_EQ(refractory_period_sec + (bucketStartTimeNs + 3 * bucketSizeNs + 2) / NS_PER_SEC + 1, - anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1)); -} - -TEST(AnomalyDetectionE2eTest, TestCountMetric_save_refractory_to_disk_no_data_written) { - const int num_buckets = 1; - const int threshold = 0; - const int refractory_period_sec = 86400 * 365; // 1 year - auto config = CreateStatsdConfig(num_buckets, threshold, refractory_period_sec); - const int64_t alert_id = config.alert(0).id(); - - int64_t bucketStartTimeNs = 10000000000; - - int configUid = 2000; - int64_t configId = 1000; - ConfigKey cfgKey(configUid, configId); - auto processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey); - ASSERT_EQ(processor->mMetricsManagers.size(), 1u); - EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid()); - ASSERT_EQ(1u, processor->mMetricsManagers.begin()->second->mAllAnomalyTrackers.size()); - - metadata::StatsMetadataList result; - int64_t mockWallClockNs = 1584991200 * NS_PER_SEC; - int64_t mockElapsedTimeNs = bucketStartTimeNs + 5000 * NS_PER_SEC; - processor->WriteMetadataToProto(mockWallClockNs, mockElapsedTimeNs, &result); - - ASSERT_EQ(result.stats_metadata_size(), 0); -} - -TEST(AnomalyDetectionE2eTest, TestCountMetric_save_refractory_to_disk) { - const int num_buckets = 1; - const int threshold = 0; - const int refractory_period_sec = 86400 * 365; // 1 year - auto config = CreateStatsdConfig(num_buckets, threshold, refractory_period_sec); - const int64_t alert_id = config.alert(0).id(); - - int64_t bucketStartTimeNs = 10000000000; - - int configUid = 2000; - int64_t configId = 1000; - ConfigKey cfgKey(configUid, configId); - auto processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey); - ASSERT_EQ(processor->mMetricsManagers.size(), 1u); - EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid()); - ASSERT_EQ(1u, processor->mMetricsManagers.begin()->second->mAllAnomalyTrackers.size()); - - sp anomalyTracker = - processor->mMetricsManagers.begin()->second->mAllAnomalyTrackers[0]; - - std::vector attributionUids1 = {111}; - std::vector attributionTags1 = {"App1"}; - std::vector attributionUids2 = {111, 222}; - std::vector attributionTags2 = {"App1", "GMSCoreModule1"}; - - FieldValue fieldValue1(Field(util::WAKELOCK_STATE_CHANGED, (int32_t)0x02010101), - Value((int32_t)111)); - HashableDimensionKey whatKey1({fieldValue1}); - MetricDimensionKey dimensionKey1(whatKey1, DEFAULT_DIMENSION_KEY); - - auto event = CreateAcquireWakelockEvent(bucketStartTimeNs + 2, attributionUids1, - attributionTags1, "wl1"); - processor->OnLogEvent(event.get()); - EXPECT_EQ(refractory_period_sec + (bucketStartTimeNs + 2) / NS_PER_SEC + 1, - anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1)); - - metadata::StatsMetadataList result; - int64_t mockWallClockNs = 1584991200 * NS_PER_SEC; - int64_t mockElapsedTimeNs = bucketStartTimeNs + 5000 * NS_PER_SEC; - processor->WriteMetadataToProto(mockWallClockNs, mockElapsedTimeNs, &result); - - metadata::StatsMetadata statsMetadata = result.stats_metadata(0); - ASSERT_EQ(result.stats_metadata_size(), 1); - EXPECT_EQ(statsMetadata.config_key().config_id(), configId); - EXPECT_EQ(statsMetadata.config_key().uid(), configUid); - - metadata::AlertMetadata alertMetadata = statsMetadata.alert_metadata(0); - ASSERT_EQ(statsMetadata.alert_metadata_size(), 1); - EXPECT_EQ(alertMetadata.alert_id(), alert_id); - metadata::AlertDimensionKeyedData keyedData = alertMetadata.alert_dim_keyed_data(0); - ASSERT_EQ(alertMetadata.alert_dim_keyed_data_size(), 1); - EXPECT_EQ(keyedData.last_refractory_ends_sec(), - anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1) - - mockElapsedTimeNs / NS_PER_SEC + - mockWallClockNs / NS_PER_SEC); - - metadata::MetricDimensionKey metadataDimKey = keyedData.dimension_key(); - metadata::FieldValue dimKeyInWhat = metadataDimKey.dimension_key_in_what(0); - EXPECT_EQ(dimKeyInWhat.field().tag(), fieldValue1.mField.getTag()); - EXPECT_EQ(dimKeyInWhat.field().field(), fieldValue1.mField.getField()); - EXPECT_EQ(dimKeyInWhat.value_int(), fieldValue1.mValue.int_value); -} - -TEST(AnomalyDetectionE2eTest, TestCountMetric_load_refractory_from_disk) { - const int num_buckets = 1; - const int threshold = 0; - const int refractory_period_sec = 86400 * 365; // 1 year - auto config = CreateStatsdConfig(num_buckets, threshold, refractory_period_sec); - const int64_t alert_id = config.alert(0).id(); - - int64_t bucketStartTimeNs = 10000000000; - - int configUid = 2000; - int64_t configId = 1000; - ConfigKey cfgKey(configUid, configId); - auto processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey); - ASSERT_EQ(processor->mMetricsManagers.size(), 1u); - EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid()); - ASSERT_EQ(1u, processor->mMetricsManagers.begin()->second->mAllAnomalyTrackers.size()); - - sp anomalyTracker = - processor->mMetricsManagers.begin()->second->mAllAnomalyTrackers[0]; - - std::vector attributionUids1 = {111}; - std::vector attributionTags1 = {"App1"}; - std::vector attributionUids2 = {111, 222}; - std::vector attributionTags2 = {"App1", "GMSCoreModule1"}; - - FieldValue fieldValue1(Field(util::WAKELOCK_STATE_CHANGED, (int32_t)0x02010101), - Value((int32_t)111)); - HashableDimensionKey whatKey1({fieldValue1}); - MetricDimensionKey dimensionKey1(whatKey1, DEFAULT_DIMENSION_KEY); - - auto event = CreateAcquireWakelockEvent(bucketStartTimeNs + 2, attributionUids1, - attributionTags1, "wl1"); - processor->OnLogEvent(event.get()); - EXPECT_EQ(refractory_period_sec + (bucketStartTimeNs + 2) / NS_PER_SEC + 1, - anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1)); - - int64_t mockWallClockNs = 1584991200 * NS_PER_SEC; - int64_t mockElapsedTimeNs = bucketStartTimeNs + 5000 * NS_PER_SEC; - processor->SaveMetadataToDisk(mockWallClockNs, mockElapsedTimeNs); - - auto processor2 = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey); - int64_t mockElapsedTimeSinceBoot = 10 * NS_PER_SEC; - processor2->LoadMetadataFromDisk(mockWallClockNs, mockElapsedTimeSinceBoot); - - sp anomalyTracker2 = - processor2->mMetricsManagers.begin()->second->mAllAnomalyTrackers[0]; - EXPECT_EQ(anomalyTracker2->getRefractoryPeriodEndsSec(dimensionKey1) - - mockElapsedTimeSinceBoot / NS_PER_SEC, - anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1) - - mockElapsedTimeNs / NS_PER_SEC); -} - -#else -GTEST_LOG_(INFO) << "This test does nothing.\n"; -#endif - -} // namespace statsd -} // namespace os -} // namespace android diff --git a/bin/tests/e2e/Anomaly_duration_sum_e2e_test.cpp b/bin/tests/e2e/Anomaly_duration_sum_e2e_test.cpp deleted file mode 100644 index e8014805..00000000 --- a/bin/tests/e2e/Anomaly_duration_sum_e2e_test.cpp +++ /dev/null @@ -1,615 +0,0 @@ -// Copyright (C) 2018 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 -#include - -#include - -#include "src/StatsLogProcessor.h" -#include "src/StatsService.h" -#include "src/anomaly/DurationAnomalyTracker.h" -#include "src/stats_log_util.h" -#include "tests/statsd_test_util.h" - -using ::ndk::SharedRefBase; - -namespace android { -namespace os { -namespace statsd { - -#ifdef __ANDROID__ - -namespace { - -const int kConfigKey = 789130124; -const int kCallingUid = 0; - -StatsdConfig CreateStatsdConfig(int num_buckets, - uint64_t threshold_ns, - DurationMetric::AggregationType aggregationType, - bool nesting) { - StatsdConfig config; - config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root. - *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(); - FieldMatcher dimensions = CreateAttributionUidDimensions( - util::WAKELOCK_STATE_CHANGED, {Position::FIRST}); - dimensions.add_child()->set_field(3); // The wakelock tag is set in field 3 of the wakelock. - *holdingWakelockPredicate.mutable_simple_predicate()->mutable_dimensions() = dimensions; - holdingWakelockPredicate.mutable_simple_predicate()->set_count_nesting(nesting); - *config.add_predicate() = holdingWakelockPredicate; - - auto durationMetric = config.add_duration_metric(); - durationMetric->set_id(StringToId("WakelockDuration")); - durationMetric->set_what(holdingWakelockPredicate.id()); - durationMetric->set_condition(screenIsOffPredicate.id()); - durationMetric->set_aggregation_type(aggregationType); - *durationMetric->mutable_dimensions_in_what() = - CreateAttributionUidDimensions(util::WAKELOCK_STATE_CHANGED, {Position::FIRST}); - durationMetric->set_bucket(FIVE_MINUTES); - - auto alert = config.add_alert(); - alert->set_id(StringToId("alert")); - alert->set_metric_id(StringToId("WakelockDuration")); - alert->set_num_buckets(num_buckets); - alert->set_refractory_period_secs(2); - alert->set_trigger_if_sum_gt(threshold_ns); - return config; -} - -std::vector attributionUids1 = {111, 222}; -std::vector attributionTags1 = {"App1", "GMSCoreModule1"}; - -std::vector attributionUids2 = {111, 222}; -std::vector attributionTags2 = {"App2", "GMSCoreModule1"}; - -std::vector attributionUids3 = {222}; -std::vector attributionTags3 = {"GMSCoreModule1"}; - -MetricDimensionKey dimensionKey1( - HashableDimensionKey({FieldValue(Field(util::WAKELOCK_STATE_CHANGED, - (int32_t)0x02010101), - Value((int32_t)111))}), - DEFAULT_DIMENSION_KEY); - -MetricDimensionKey dimensionKey2( - HashableDimensionKey({FieldValue(Field(util::WAKELOCK_STATE_CHANGED, - (int32_t)0x02010101), Value((int32_t)222))}), - DEFAULT_DIMENSION_KEY); - -void sendConfig(shared_ptr& service, const StatsdConfig& config) { - string str; - config.SerializeToString(&str); - std::vector configAsVec(str.begin(), str.end()); - service->addConfiguration(kConfigKey, configAsVec, kCallingUid); -} - -} // namespace - -TEST(AnomalyDetectionE2eTest, TestDurationMetric_SUM_single_bucket) { - const int num_buckets = 1; - const uint64_t threshold_ns = NS_PER_SEC; - auto config = CreateStatsdConfig(num_buckets, threshold_ns, DurationMetric::SUM, true); - const uint64_t alert_id = config.alert(0).id(); - const uint32_t refractory_period_sec = config.alert(0).refractory_period_secs(); - - shared_ptr service = SharedRefBase::make(nullptr, nullptr); - sendConfig(service, config); - - auto processor = service->mProcessor; - ASSERT_EQ(processor->mMetricsManagers.size(), 1u); - EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid()); - ASSERT_EQ(1u, processor->mMetricsManagers.begin()->second->mAllAnomalyTrackers.size()); - - int64_t bucketStartTimeNs = processor->mTimeBaseNs; - int64_t roundedBucketStartTimeNs = bucketStartTimeNs / NS_PER_SEC * NS_PER_SEC; - int64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(config.duration_metric(0).bucket()) * 1e6; - - sp anomalyTracker = - processor->mMetricsManagers.begin()->second->mAllAnomalyTrackers[0]; - - auto screen_on_event = CreateScreenStateChangedEvent( - bucketStartTimeNs + 1, android::view::DisplayStateEnum::DISPLAY_STATE_ON); - auto screen_off_event = CreateScreenStateChangedEvent( - bucketStartTimeNs + 10, android::view::DisplayStateEnum::DISPLAY_STATE_OFF); - processor->OnLogEvent(screen_on_event.get()); - processor->OnLogEvent(screen_off_event.get()); - - // Acquire wakelock wl1. - auto acquire_event = CreateAcquireWakelockEvent(bucketStartTimeNs + 11, attributionUids1, - attributionTags1, "wl1"); - processor->OnLogEvent(acquire_event.get()); - EXPECT_EQ((bucketStartTimeNs + 11 + threshold_ns) / NS_PER_SEC + 1, - anomalyTracker->getAlarmTimestampSec(dimensionKey1)); - EXPECT_EQ(0u, anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1)); - - // Release wakelock wl1. No anomaly detected. Alarm cancelled at the "release" event. - auto release_event = CreateReleaseWakelockEvent(bucketStartTimeNs + 101, attributionUids1, - attributionTags1, "wl1"); - processor->OnLogEvent(release_event.get()); - EXPECT_EQ(0u, anomalyTracker->getAlarmTimestampSec(dimensionKey1)); - EXPECT_EQ(0u, anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1)); - - // Acquire wakelock wl1 within bucket #0. - acquire_event = CreateAcquireWakelockEvent(bucketStartTimeNs + 110, attributionUids2, - attributionTags2, "wl1"); - processor->OnLogEvent(acquire_event.get()); - EXPECT_EQ((bucketStartTimeNs + 110 + threshold_ns - 90) / NS_PER_SEC + 1, - anomalyTracker->getAlarmTimestampSec(dimensionKey1)); - EXPECT_EQ(0u, anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1)); - - // Release wakelock wl1. One anomaly detected. - release_event = CreateReleaseWakelockEvent(bucketStartTimeNs + NS_PER_SEC + 109, - attributionUids2, attributionTags2, "wl1"); - processor->OnLogEvent(release_event.get()); - EXPECT_EQ(0u, anomalyTracker->getAlarmTimestampSec(dimensionKey1)); - EXPECT_EQ(refractory_period_sec + (bucketStartTimeNs + NS_PER_SEC + 109) / NS_PER_SEC + 1, - anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1)); - - // Acquire wakelock wl1. - acquire_event = CreateAcquireWakelockEvent(bucketStartTimeNs + NS_PER_SEC + 112, - attributionUids1, attributionTags1, "wl1"); - processor->OnLogEvent(acquire_event.get()); - // Wakelock has been hold longer than the threshold in bucket #0. The alarm is set at the - // end of the refractory period. - const int64_t alarmFiredTimestampSec0 = anomalyTracker->getAlarmTimestampSec(dimensionKey1); - EXPECT_EQ(refractory_period_sec + (bucketStartTimeNs + NS_PER_SEC + 109) / NS_PER_SEC + 1, - (uint32_t)alarmFiredTimestampSec0); - EXPECT_EQ(alarmFiredTimestampSec0, - processor->getAnomalyAlarmMonitor()->getRegisteredAlarmTimeSec()); - - // Anomaly alarm fired. - auto alarmTriggerEvent = CreateBatterySaverOnEvent(alarmFiredTimestampSec0 * NS_PER_SEC); - processor->OnLogEvent(alarmTriggerEvent.get(), alarmFiredTimestampSec0 * NS_PER_SEC); - - EXPECT_EQ(0u, anomalyTracker->getAlarmTimestampSec(dimensionKey1)); - EXPECT_EQ(refractory_period_sec + alarmFiredTimestampSec0, - anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1)); - - // Release wakelock wl1. - release_event = - CreateReleaseWakelockEvent(alarmFiredTimestampSec0 * NS_PER_SEC + NS_PER_SEC + 1, - attributionUids1, attributionTags1, "wl1"); - processor->OnLogEvent(release_event.get()); - EXPECT_EQ(0u, anomalyTracker->getAlarmTimestampSec(dimensionKey1)); - // Within refractory period. No more anomaly detected. - EXPECT_EQ(refractory_period_sec + alarmFiredTimestampSec0, - anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1)); - - // Acquire wakelock wl1. - acquire_event = CreateAcquireWakelockEvent( - roundedBucketStartTimeNs + bucketSizeNs - 5 * NS_PER_SEC - 11, attributionUids2, - attributionTags2, "wl1"); - processor->OnLogEvent(acquire_event.get()); - const int64_t alarmFiredTimestampSec1 = anomalyTracker->getAlarmTimestampSec(dimensionKey1); - EXPECT_EQ((bucketStartTimeNs + bucketSizeNs - 5 * NS_PER_SEC) / NS_PER_SEC, - (uint64_t)alarmFiredTimestampSec1); - - // Release wakelock wl1. - int64_t release_event_time = roundedBucketStartTimeNs + bucketSizeNs - 4 * NS_PER_SEC - 10; - release_event = CreateReleaseWakelockEvent(release_event_time, attributionUids2, - attributionTags2, "wl1"); - processor->OnLogEvent(release_event.get(), release_event_time); - EXPECT_EQ(0u, anomalyTracker->getAlarmTimestampSec(dimensionKey1)); - EXPECT_EQ(refractory_period_sec + (release_event_time) / NS_PER_SEC + 1, - anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1)); - - auto alarmSet = processor->getAnomalyAlarmMonitor()->popSoonerThan( - static_cast(alarmFiredTimestampSec1)); - ASSERT_EQ(0u, alarmSet.size()); - - // Acquire wakelock wl1 near the end of bucket #0. - acquire_event = CreateAcquireWakelockEvent(roundedBucketStartTimeNs + bucketSizeNs - 2, - attributionUids1, attributionTags1, "wl1"); - processor->OnLogEvent(acquire_event.get()); - EXPECT_EQ((bucketStartTimeNs + bucketSizeNs) / NS_PER_SEC, - anomalyTracker->getAlarmTimestampSec(dimensionKey1)); - - // Release the event at early bucket #1. - release_event_time = roundedBucketStartTimeNs + bucketSizeNs + NS_PER_SEC - 1; - release_event = CreateReleaseWakelockEvent(release_event_time, attributionUids1, - attributionTags1, "wl1"); - processor->OnLogEvent(release_event.get(), release_event_time); - EXPECT_EQ(0u, anomalyTracker->getAlarmTimestampSec(dimensionKey1)); - // Anomaly detected when stopping the alarm. The refractory period does not change. - EXPECT_EQ(refractory_period_sec + (bucketStartTimeNs + bucketSizeNs + NS_PER_SEC) / NS_PER_SEC, - anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1)); - - // Condition changes to false. - screen_on_event = - CreateScreenStateChangedEvent(bucketStartTimeNs + 2 * bucketSizeNs + 20, - android::view::DisplayStateEnum::DISPLAY_STATE_ON); - processor->OnLogEvent(screen_on_event.get()); - EXPECT_EQ(refractory_period_sec + (bucketStartTimeNs + bucketSizeNs + NS_PER_SEC) / NS_PER_SEC, - anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1)); - EXPECT_EQ(0u, anomalyTracker->getAlarmTimestampSec(dimensionKey1)); - - acquire_event = CreateAcquireWakelockEvent(bucketStartTimeNs + 2 * bucketSizeNs + 30, - attributionUids2, attributionTags2, "wl1"); - processor->OnLogEvent(acquire_event.get()); - // The condition is false. Do not start the alarm. - EXPECT_EQ(0u, anomalyTracker->getAlarmTimestampSec(dimensionKey1)); - EXPECT_EQ(refractory_period_sec + (bucketStartTimeNs + bucketSizeNs + NS_PER_SEC) / NS_PER_SEC, - anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1)); - - // Condition turns true. - screen_off_event = - CreateScreenStateChangedEvent(roundedBucketStartTimeNs + 2 * bucketSizeNs + NS_PER_SEC, - android::view::DisplayStateEnum::DISPLAY_STATE_OFF); - processor->OnLogEvent(screen_off_event.get()); - EXPECT_EQ((bucketStartTimeNs + 2 * bucketSizeNs + NS_PER_SEC + threshold_ns) / NS_PER_SEC, - anomalyTracker->getAlarmTimestampSec(dimensionKey1)); - - // Condition turns to false. - int64_t condition_false_time = bucketStartTimeNs + 2 * bucketSizeNs + 2 * NS_PER_SEC + 1; - screen_on_event = CreateScreenStateChangedEvent( - condition_false_time, android::view::DisplayStateEnum::DISPLAY_STATE_ON); - processor->OnLogEvent(screen_on_event.get(), condition_false_time); - // Condition turns to false. Cancelled the alarm. - EXPECT_EQ(0u, anomalyTracker->getAlarmTimestampSec(dimensionKey1)); - // Detected one anomaly. - EXPECT_EQ(refractory_period_sec + - (bucketStartTimeNs + 2 * bucketSizeNs + 2 * NS_PER_SEC + 1) / NS_PER_SEC + 1, - anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1)); - - // Condition turns to true again. - screen_off_event = - CreateScreenStateChangedEvent(bucketStartTimeNs + 2 * bucketSizeNs + 2 * NS_PER_SEC + 2, - android::view::DisplayStateEnum::DISPLAY_STATE_OFF); - processor->OnLogEvent(screen_off_event.get()); - EXPECT_EQ((bucketStartTimeNs + 2 * bucketSizeNs) / NS_PER_SEC + 2 + 2 + 1, - anomalyTracker->getAlarmTimestampSec(dimensionKey1)); - - release_event_time = roundedBucketStartTimeNs + 2 * bucketSizeNs + 5 * NS_PER_SEC; - release_event = CreateReleaseWakelockEvent(release_event_time, attributionUids2, - attributionTags2, "wl1"); - processor->OnLogEvent(release_event.get()); - EXPECT_EQ(refractory_period_sec + (release_event_time) / NS_PER_SEC, - anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1)); - EXPECT_EQ(0u, anomalyTracker->getAlarmTimestampSec(dimensionKey1)); -} - -TEST(AnomalyDetectionE2eTest, TestDurationMetric_SUM_multiple_buckets) { - const int num_buckets = 3; - const uint64_t threshold_ns = NS_PER_SEC; - auto config = CreateStatsdConfig(num_buckets, threshold_ns, DurationMetric::SUM, true); - const uint64_t alert_id = config.alert(0).id(); - const uint32_t refractory_period_sec = config.alert(0).refractory_period_secs(); - - shared_ptr service = SharedRefBase::make(nullptr, nullptr); - sendConfig(service, config); - - auto processor = service->mProcessor; - ASSERT_EQ(processor->mMetricsManagers.size(), 1u); - EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid()); - ASSERT_EQ(1u, processor->mMetricsManagers.begin()->second->mAllAnomalyTrackers.size()); - - int64_t bucketStartTimeNs = processor->mTimeBaseNs; - int64_t roundedBucketStartTimeNs = bucketStartTimeNs / NS_PER_SEC * NS_PER_SEC; - int64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(config.duration_metric(0).bucket()) * 1e6; - - sp anomalyTracker = - processor->mMetricsManagers.begin()->second->mAllAnomalyTrackers[0]; - - auto screen_off_event = CreateScreenStateChangedEvent( - bucketStartTimeNs + 1, android::view::DisplayStateEnum::DISPLAY_STATE_OFF); - processor->OnLogEvent(screen_off_event.get()); - - // Acquire wakelock "wc1" in bucket #0. - auto acquire_event = - CreateAcquireWakelockEvent(roundedBucketStartTimeNs + bucketSizeNs - NS_PER_SEC / 2 - 1, - attributionUids1, attributionTags1, "wl1"); - processor->OnLogEvent(acquire_event.get()); - EXPECT_EQ((roundedBucketStartTimeNs + bucketSizeNs) / NS_PER_SEC + 1, - anomalyTracker->getAlarmTimestampSec(dimensionKey1)); - EXPECT_EQ(0u, anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1)); - - // Release wakelock "wc1" in bucket #0. - int64_t release_event_time = roundedBucketStartTimeNs + bucketSizeNs - 1; - auto release_event = CreateReleaseWakelockEvent(release_event_time, attributionUids1, - attributionTags1, "wl1"); - processor->OnLogEvent(release_event.get(), release_event_time); - EXPECT_EQ(0u, anomalyTracker->getAlarmTimestampSec(dimensionKey1)); - EXPECT_EQ(0u, anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1)); - - // Acquire wakelock "wc1" in bucket #1. - acquire_event = - CreateAcquireWakelockEvent(roundedBucketStartTimeNs + bucketSizeNs + NS_PER_SEC + 1, - attributionUids2, attributionTags2, "wl1"); - processor->OnLogEvent(acquire_event.get()); - EXPECT_EQ((bucketStartTimeNs + bucketSizeNs + NS_PER_SEC) / NS_PER_SEC + 1, - anomalyTracker->getAlarmTimestampSec(dimensionKey1)); - EXPECT_EQ(0u, anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1)); - - release_event_time = roundedBucketStartTimeNs + bucketSizeNs + NS_PER_SEC + 100; - release_event = CreateReleaseWakelockEvent(release_event_time, attributionUids2, - attributionTags2, "wl1"); - processor->OnLogEvent(release_event.get(), release_event_time); - EXPECT_EQ(0u, anomalyTracker->getAlarmTimestampSec(dimensionKey1)); - EXPECT_EQ(0u, anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1)); - - // Acquire wakelock "wc2" in bucket #2. - acquire_event = - CreateAcquireWakelockEvent(roundedBucketStartTimeNs + 2 * bucketSizeNs + NS_PER_SEC + 1, - attributionUids3, attributionTags3, "wl2"); - processor->OnLogEvent(acquire_event.get()); - EXPECT_EQ((bucketStartTimeNs + 2 * bucketSizeNs) / NS_PER_SEC + 3, - anomalyTracker->getAlarmTimestampSec(dimensionKey2)); - EXPECT_EQ(0u, anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey2)); - - // Release wakelock "wc2" in bucket #2. - release_event_time = roundedBucketStartTimeNs + 2 * bucketSizeNs + 3 * NS_PER_SEC; - release_event = CreateReleaseWakelockEvent(release_event_time, attributionUids3, - attributionTags3, "wl2"); - processor->OnLogEvent(release_event.get(), release_event_time); - EXPECT_EQ(0u, anomalyTracker->getAlarmTimestampSec(dimensionKey2)); - EXPECT_EQ(refractory_period_sec + (release_event_time) / NS_PER_SEC, - anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey2)); - - // Acquire wakelock "wc1" in bucket #2. - acquire_event = - CreateAcquireWakelockEvent(roundedBucketStartTimeNs + 2 * bucketSizeNs + 3 * NS_PER_SEC, - attributionUids2, attributionTags2, "wl1"); - processor->OnLogEvent(acquire_event.get()); - EXPECT_EQ((roundedBucketStartTimeNs + 2 * bucketSizeNs) / NS_PER_SEC + 3 + 1, - anomalyTracker->getAlarmTimestampSec(dimensionKey1)); - EXPECT_EQ(0u, anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1)); - - // Release wakelock "wc1" in bucket #2. - release_event_time = roundedBucketStartTimeNs + 2 * bucketSizeNs + 3.5 * NS_PER_SEC; - release_event = CreateReleaseWakelockEvent(release_event_time, attributionUids2, - attributionTags2, "wl1"); - processor->OnLogEvent(release_event.get(), release_event_time); - EXPECT_EQ(0u, anomalyTracker->getAlarmTimestampSec(dimensionKey1)); - EXPECT_EQ(refractory_period_sec + (release_event_time) / NS_PER_SEC + 1, - anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1)); - - acquire_event = CreateAcquireWakelockEvent(roundedBucketStartTimeNs + 6 * bucketSizeNs + 4, - attributionUids3, attributionTags3, "wl2"); - processor->OnLogEvent(acquire_event.get()); - acquire_event = CreateAcquireWakelockEvent(roundedBucketStartTimeNs + 6 * bucketSizeNs + 5, - attributionUids1, attributionTags1, "wl1"); - processor->OnLogEvent(acquire_event.get()); - EXPECT_EQ((roundedBucketStartTimeNs + 6 * bucketSizeNs) / NS_PER_SEC + 2, - anomalyTracker->getAlarmTimestampSec(dimensionKey1)); - EXPECT_EQ((roundedBucketStartTimeNs + 6 * bucketSizeNs) / NS_PER_SEC + 2, - anomalyTracker->getAlarmTimestampSec(dimensionKey2)); - - release_event_time = roundedBucketStartTimeNs + 6 * bucketSizeNs + NS_PER_SEC + 2; - release_event = CreateReleaseWakelockEvent(release_event_time, attributionUids3, - attributionTags3, "wl2"); - processor->OnLogEvent(release_event.get(), release_event_time); - release_event = CreateReleaseWakelockEvent(release_event_time + 4, attributionUids1, - attributionTags1, "wl1"); - processor->OnLogEvent(release_event.get(), release_event_time + 4); - EXPECT_EQ(0u, anomalyTracker->getAlarmTimestampSec(dimensionKey1)); - EXPECT_EQ(0u, anomalyTracker->getAlarmTimestampSec(dimensionKey2)); - // The buckets are not messed up across dimensions. Only one dimension has anomaly triggered. - EXPECT_EQ(refractory_period_sec + - (int64_t)(roundedBucketStartTimeNs + 6 * bucketSizeNs + NS_PER_SEC) / - NS_PER_SEC + - 1, - anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1)); -} - -TEST(AnomalyDetectionE2eTest, TestDurationMetric_SUM_partial_bucket) { - const int num_buckets = 1; - const uint64_t threshold_ns = NS_PER_SEC; - auto config = CreateStatsdConfig(num_buckets, threshold_ns, DurationMetric::SUM, true); - const uint64_t alert_id = config.alert(0).id(); - const uint32_t refractory_period_sec = config.alert(0).refractory_period_secs(); - - shared_ptr service = SharedRefBase::make(nullptr, nullptr); - sendConfig(service, config); - - auto processor = service->mProcessor; - ASSERT_EQ(processor->mMetricsManagers.size(), 1u); - EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid()); - ASSERT_EQ(1u, processor->mMetricsManagers.begin()->second->mAllAnomalyTrackers.size()); - - int64_t bucketStartTimeNs = processor->mTimeBaseNs; - int64_t roundedBucketStartTimeNs = bucketStartTimeNs / NS_PER_SEC * NS_PER_SEC; - int64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(config.duration_metric(0).bucket()) * 1e6; - - service->mUidMap->updateMap(bucketStartTimeNs, {1}, {1}, {String16("v1")}, - {String16("randomApp")}, {String16("")}); - - sp anomalyTracker = - processor->mMetricsManagers.begin()->second->mAllAnomalyTrackers[0]; - - auto screen_off_event = CreateScreenStateChangedEvent( - bucketStartTimeNs + 10, android::view::DisplayStateEnum::DISPLAY_STATE_OFF); - processor->OnLogEvent(screen_off_event.get()); - - // Acquire wakelock wl1. - auto acquire_event = CreateAcquireWakelockEvent(bucketStartTimeNs + 11, attributionUids1, - attributionTags1, "wl1"); - processor->OnLogEvent(acquire_event.get()); - EXPECT_EQ((bucketStartTimeNs + 11 + threshold_ns) / NS_PER_SEC + 1, - anomalyTracker->getAlarmTimestampSec(dimensionKey1)); - EXPECT_EQ(0u, anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1)); - - // Release wakelock wl1. No anomaly detected. Alarm cancelled at the "release" event. - // 90 ns accumulated. - auto release_event = CreateReleaseWakelockEvent(bucketStartTimeNs + 101, attributionUids1, - attributionTags1, "wl1"); - processor->OnLogEvent(release_event.get()); - EXPECT_EQ(0u, anomalyTracker->getAlarmTimestampSec(dimensionKey1)); - EXPECT_EQ(0u, anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1)); - - // Acquire wakelock wl2. - acquire_event = CreateAcquireWakelockEvent(bucketStartTimeNs + 110, attributionUids3, - attributionTags3, "wl2"); - processor->OnLogEvent(acquire_event.get()); - int64_t wl2AlarmTimeNs = bucketStartTimeNs + 110 + threshold_ns; - EXPECT_EQ(wl2AlarmTimeNs / NS_PER_SEC + 1, anomalyTracker->getAlarmTimestampSec(dimensionKey2)); - EXPECT_EQ(0u, anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey2)); - - // Partial bucket split. - int64_t appUpgradeTimeNs = bucketStartTimeNs + 500; - service->mUidMap->updateApp(appUpgradeTimeNs, String16("randomApp"), 1, 2, String16("v2"), - String16("")); - EXPECT_EQ(0u, anomalyTracker->getAlarmTimestampSec(dimensionKey1)); - EXPECT_EQ(0u, anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1)); - EXPECT_EQ((bucketStartTimeNs + 110 + threshold_ns) / NS_PER_SEC + 1, - anomalyTracker->getAlarmTimestampSec(dimensionKey2)); - EXPECT_EQ(0u, anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey2)); - - // Acquire wakelock wl1. Subtract 100 ns since that accumulated before the bucket split. - acquire_event = CreateAcquireWakelockEvent(bucketStartTimeNs + 510, attributionUids1, - attributionTags1, "wl1"); - processor->OnLogEvent(acquire_event.get()); - int64_t wl1AlarmTimeNs = bucketStartTimeNs + 510 + threshold_ns - 90; - EXPECT_EQ(wl1AlarmTimeNs / NS_PER_SEC + 1, anomalyTracker->getAlarmTimestampSec(dimensionKey1)); - EXPECT_EQ(0u, anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1)); - - // Release wakelock wl1. One anomaly detected. - release_event = CreateReleaseWakelockEvent(wl1AlarmTimeNs + 1, attributionUids2, - attributionTags2, "wl1"); - processor->OnLogEvent(release_event.get()); - EXPECT_EQ(0u, anomalyTracker->getAlarmTimestampSec(dimensionKey1)); - EXPECT_EQ(refractory_period_sec + (wl1AlarmTimeNs + 1) / NS_PER_SEC + 1, - anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1)); - - // Anomaly alarm fired. - auto alarmTriggerEvent = CreateBatterySaverOnEvent(wl2AlarmTimeNs); - processor->OnLogEvent(alarmTriggerEvent.get(), wl2AlarmTimeNs); - - EXPECT_EQ(0u, anomalyTracker->getAlarmTimestampSec(dimensionKey1)); - EXPECT_EQ(refractory_period_sec + wl2AlarmTimeNs / NS_PER_SEC + 1, - anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1)); -} - -TEST(AnomalyDetectionE2eTest, TestDurationMetric_SUM_long_refractory_period) { - const int num_buckets = 2; - const uint64_t threshold_ns = 3 * NS_PER_SEC; - auto config = CreateStatsdConfig(num_buckets, threshold_ns, DurationMetric::SUM, false); - const uint64_t alert_id = config.alert(0).id(); - int64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(config.duration_metric(0).bucket()) * 1e6; - const uint32_t refractory_period_sec = 3 * bucketSizeNs / NS_PER_SEC; - config.mutable_alert(0)->set_refractory_period_secs(refractory_period_sec); - - shared_ptr service = SharedRefBase::make(nullptr, nullptr); - sendConfig(service, config); - - auto processor = service->mProcessor; - ASSERT_EQ(processor->mMetricsManagers.size(), 1u); - EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid()); - ASSERT_EQ(1u, processor->mMetricsManagers.begin()->second->mAllAnomalyTrackers.size()); - - int64_t bucketStartTimeNs = processor->mTimeBaseNs; - int64_t roundedBucketStartTimeNs = bucketStartTimeNs / NS_PER_SEC * NS_PER_SEC; - - sp anomalyTracker = - processor->mMetricsManagers.begin()->second->mAllAnomalyTrackers[0]; - - auto screen_off_event = CreateScreenStateChangedEvent( - bucketStartTimeNs + 1, android::view::DisplayStateEnum::DISPLAY_STATE_OFF); - processor->OnLogEvent(screen_off_event.get()); - - // Acquire wakelock "wc1" in bucket #0. - auto acquire_event = CreateAcquireWakelockEvent(roundedBucketStartTimeNs + bucketSizeNs - 100, - attributionUids1, attributionTags1, "wl1"); - processor->OnLogEvent(acquire_event.get()); - EXPECT_EQ((roundedBucketStartTimeNs + bucketSizeNs) / NS_PER_SEC + 3, - anomalyTracker->getAlarmTimestampSec(dimensionKey1)); - EXPECT_EQ(0u, anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1)); - - // Acquire the wakelock "wc1" again. - acquire_event = - CreateAcquireWakelockEvent(roundedBucketStartTimeNs + bucketSizeNs + 2 * NS_PER_SEC + 1, - attributionUids1, attributionTags1, "wl1"); - processor->OnLogEvent(acquire_event.get()); - // The alarm does not change. - EXPECT_EQ((roundedBucketStartTimeNs + bucketSizeNs) / NS_PER_SEC + 3, - anomalyTracker->getAlarmTimestampSec(dimensionKey1)); - EXPECT_EQ(0u, anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1)); - - // Anomaly alarm fired late. - const int64_t firedAlarmTimestampNs = roundedBucketStartTimeNs + 2 * bucketSizeNs - NS_PER_SEC; - auto alarmTriggerEvent = CreateBatterySaverOnEvent(firedAlarmTimestampNs); - processor->OnLogEvent(alarmTriggerEvent.get(), firedAlarmTimestampNs); - EXPECT_EQ(0u, anomalyTracker->getAlarmTimestampSec(dimensionKey1)); - EXPECT_EQ(refractory_period_sec + firedAlarmTimestampNs / NS_PER_SEC, - anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1)); - - acquire_event = CreateAcquireWakelockEvent(roundedBucketStartTimeNs + 2 * bucketSizeNs - 100, - attributionUids1, attributionTags1, "wl1"); - processor->OnLogEvent(acquire_event.get()); - EXPECT_EQ(0u, anomalyTracker->getAlarmTimestampSec(dimensionKey1)); - EXPECT_EQ(refractory_period_sec + firedAlarmTimestampNs / NS_PER_SEC, - anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1)); - - int64_t release_event_time = bucketStartTimeNs + 2 * bucketSizeNs + 1; - auto release_event = CreateReleaseWakelockEvent(release_event_time, attributionUids1, - attributionTags1, "wl1"); - processor->OnLogEvent(release_event.get(), release_event_time); - EXPECT_EQ(0u, anomalyTracker->getAlarmTimestampSec(dimensionKey1)); - // Within the refractory period. No anomaly. - EXPECT_EQ(refractory_period_sec + firedAlarmTimestampNs / NS_PER_SEC, - anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1)); - - // A new wakelock, but still within refractory period. - acquire_event = CreateAcquireWakelockEvent( - roundedBucketStartTimeNs + 2 * bucketSizeNs + 10 * NS_PER_SEC, attributionUids1, - attributionTags1, "wl1"); - processor->OnLogEvent(acquire_event.get()); - EXPECT_EQ(refractory_period_sec + firedAlarmTimestampNs / NS_PER_SEC, - anomalyTracker->getAlarmTimestampSec(dimensionKey1)); - - release_event = - CreateReleaseWakelockEvent(roundedBucketStartTimeNs + 3 * bucketSizeNs - NS_PER_SEC, - attributionUids1, attributionTags1, "wl1"); - // Still in the refractory period. No anomaly. - processor->OnLogEvent(release_event.get()); - EXPECT_EQ(refractory_period_sec + firedAlarmTimestampNs / NS_PER_SEC, - anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1)); - - acquire_event = CreateAcquireWakelockEvent( - roundedBucketStartTimeNs + 5 * bucketSizeNs - 2 * NS_PER_SEC - 5, attributionUids1, - attributionTags1, "wl1"); - processor->OnLogEvent(acquire_event.get()); - EXPECT_EQ((roundedBucketStartTimeNs + 5 * bucketSizeNs) / NS_PER_SEC + 1, - anomalyTracker->getAlarmTimestampSec(dimensionKey1)); - - release_event_time = roundedBucketStartTimeNs + 5 * bucketSizeNs - 2 * NS_PER_SEC - 4; - release_event = CreateReleaseWakelockEvent(release_event_time, attributionUids1, - attributionTags1, "wl1"); - processor->OnLogEvent(release_event.get(), release_event_time); - EXPECT_EQ(0u, anomalyTracker->getAlarmTimestampSec(dimensionKey1)); - - acquire_event = CreateAcquireWakelockEvent( - roundedBucketStartTimeNs + 5 * bucketSizeNs - 2 * NS_PER_SEC - 3, attributionUids1, - attributionTags1, "wl1"); - processor->OnLogEvent(acquire_event.get()); - EXPECT_EQ((roundedBucketStartTimeNs + 5 * bucketSizeNs) / NS_PER_SEC + 1, - anomalyTracker->getAlarmTimestampSec(dimensionKey1)); -} - -#else -GTEST_LOG_(INFO) << "This test does nothing.\n"; -#endif - -} // namespace statsd -} // namespace os -} // namespace android diff --git a/bin/tests/e2e/Attribution_e2e_test.cpp b/bin/tests/e2e/Attribution_e2e_test.cpp deleted file mode 100644 index 4c2caa90..00000000 --- a/bin/tests/e2e/Attribution_e2e_test.cpp +++ /dev/null @@ -1,375 +0,0 @@ -// 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 -#include - -namespace android { -namespace os { -namespace statsd { - -#ifdef __ANDROID__ - -namespace { - -StatsdConfig CreateStatsdConfig(const Position position) { - StatsdConfig config; - config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root. - auto wakelockAcquireMatcher = CreateAcquireWakelockAtomMatcher(); - auto attributionNodeMatcher = - wakelockAcquireMatcher.mutable_simple_atom_matcher()->add_field_value_matcher(); - attributionNodeMatcher->set_field(1); - attributionNodeMatcher->set_position(Position::ANY); - auto uidMatcher = attributionNodeMatcher->mutable_matches_tuple()->add_field_value_matcher(); - uidMatcher->set_field(1); // uid field. - uidMatcher->set_eq_string("com.android.gmscore"); - - *config.add_atom_matcher() = wakelockAcquireMatcher; - - auto countMetric = config.add_count_metric(); - countMetric->set_id(123456); - countMetric->set_what(wakelockAcquireMatcher.id()); - *countMetric->mutable_dimensions_in_what() = - CreateAttributionUidAndTagDimensions( - util::WAKELOCK_STATE_CHANGED, {position}); - countMetric->set_bucket(FIVE_MINUTES); - return config; -} - -// GMS core node is in the middle. -std::vector attributionUids1 = {111, 222, 333}; -std::vector attributionTags1 = {"App1", "GMSCoreModule1", "App3"}; - -// GMS core node is the last one. -std::vector attributionUids2 = {111, 333, 222}; -std::vector attributionTags2 = {"App1", "App3", "GMSCoreModule1"}; - -// GMS core node is the first one. -std::vector attributionUids3 = {222, 333}; -std::vector attributionTags3 = {"GMSCoreModule1", "App3"}; - -// Single GMS core node. -std::vector attributionUids4 = {222}; -std::vector attributionTags4 = {"GMSCoreModule1"}; - -// GMS core has another uid. -std::vector attributionUids5 = {111, 444, 333}; -std::vector attributionTags5 = {"App1", "GMSCoreModule2", "App3"}; - -// Multiple GMS core nodes. -std::vector attributionUids6 = {444, 222}; -std::vector attributionTags6 = {"GMSCoreModule2", "GMSCoreModule1"}; - -// No GMS core nodes -std::vector attributionUids7 = {111, 333}; -std::vector attributionTags7 = {"App1", "App3"}; - -std::vector attributionUids8 = {111}; -std::vector attributionTags8 = {"App1"}; - -// GMS core node with isolated uid. -const int isolatedUid = 666; -std::vector attributionUids9 = {isolatedUid}; -std::vector attributionTags9 = {"GMSCoreModule3"}; - -std::vector attributionUids10 = {isolatedUid}; -std::vector attributionTags10 = {"GMSCoreModule1"}; - -} // namespace - -TEST(AttributionE2eTest, TestAttributionMatchAndSliceByFirstUid) { - auto config = CreateStatsdConfig(Position::FIRST); - int64_t bucketStartTimeNs = 10000000000; - int64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(config.count_metric(0).bucket()) * 1000000; - - ConfigKey cfgKey; - auto processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey); - ASSERT_EQ(processor->mMetricsManagers.size(), 1u); - EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid()); - - // Here it assumes that GMS core has two uids. - processor->getUidMap()->updateMap( - 1, {222, 444, 111, 333}, {1, 1, 2, 2}, - {String16("v1"), String16("v1"), String16("v2"), String16("v2")}, - {String16("com.android.gmscore"), String16("com.android.gmscore"), String16("app1"), - String16("APP3")}, - {String16(""), String16(""), String16(""), String16("")}); - - std::vector> events; - // Events 1~4 are in the 1st bucket. - events.push_back(CreateAcquireWakelockEvent(bucketStartTimeNs + 2, attributionUids1, - attributionTags1, "wl1")); - events.push_back(CreateAcquireWakelockEvent(bucketStartTimeNs + 200, attributionUids2, - attributionTags2, "wl1")); - events.push_back(CreateAcquireWakelockEvent(bucketStartTimeNs + bucketSizeNs - 1, - attributionUids3, attributionTags3, "wl1")); - events.push_back(CreateAcquireWakelockEvent(bucketStartTimeNs + bucketSizeNs, attributionUids4, - attributionTags4, "wl1")); - - // Events 5~8 are in the 3rd bucket. - events.push_back(CreateAcquireWakelockEvent(bucketStartTimeNs + 2 * bucketSizeNs + 1, - attributionUids5, attributionTags5, "wl2")); - events.push_back(CreateAcquireWakelockEvent(bucketStartTimeNs + 2 * bucketSizeNs + 100, - attributionUids6, attributionTags6, "wl2")); - events.push_back(CreateAcquireWakelockEvent(bucketStartTimeNs + 3 * bucketSizeNs - 2, - attributionUids7, attributionTags7, "wl2")); - events.push_back(CreateAcquireWakelockEvent(bucketStartTimeNs + 3 * bucketSizeNs, - attributionUids8, attributionTags8, "wl2")); - events.push_back(CreateAcquireWakelockEvent(bucketStartTimeNs + 3 * bucketSizeNs + 1, - attributionUids9, attributionTags9, "wl2")); - events.push_back(CreateAcquireWakelockEvent(bucketStartTimeNs + 3 * bucketSizeNs + 100, - attributionUids9, attributionTags9, "wl2")); - events.push_back(CreateIsolatedUidChangedEvent(bucketStartTimeNs + 3 * bucketSizeNs - 1, 222, - isolatedUid, true /*is_create*/)); - events.push_back(CreateIsolatedUidChangedEvent(bucketStartTimeNs + 3 * bucketSizeNs + 10, 222, - isolatedUid, false /*is_create*/)); - - sortLogEventsByTimestamp(&events); - - for (const auto& event : events) { - processor->OnLogEvent(event.get()); - } - ConfigMetricsReportList reports; - vector buffer; - processor->onDumpReport(cfgKey, bucketStartTimeNs + 4 * bucketSizeNs + 1, false, true, ADB_DUMP, - FAST, &buffer); - EXPECT_TRUE(buffer.size() > 0); - EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size())); - backfillDimensionPath(&reports); - backfillStringInReport(&reports); - backfillStartEndTimestamp(&reports); - ASSERT_EQ(reports.reports_size(), 1); - ASSERT_EQ(reports.reports(0).metrics_size(), 1); - - StatsLogReport::CountMetricDataWrapper countMetrics; - sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).count_metrics(), &countMetrics); - ASSERT_EQ(countMetrics.data_size(), 4); - - auto data = countMetrics.data(0); - ValidateAttributionUidAndTagDimension(data.dimensions_in_what(), - util::WAKELOCK_STATE_CHANGED, 111, "App1"); - ASSERT_EQ(data.bucket_info_size(), 2); - EXPECT_EQ(data.bucket_info(0).count(), 2); - EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), bucketStartTimeNs); - EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(), bucketStartTimeNs + bucketSizeNs); - EXPECT_EQ(data.bucket_info(1).count(), 1); - EXPECT_EQ(data.bucket_info(1).start_bucket_elapsed_nanos(), - bucketStartTimeNs + 2 * bucketSizeNs); - EXPECT_EQ(data.bucket_info(1).end_bucket_elapsed_nanos(), bucketStartTimeNs + 3 * bucketSizeNs); - - data = countMetrics.data(1); - ValidateAttributionUidAndTagDimension(data.dimensions_in_what(), - util::WAKELOCK_STATE_CHANGED, 222, - "GMSCoreModule1"); - ASSERT_EQ(data.bucket_info_size(), 2); - EXPECT_EQ(data.bucket_info(0).count(), 1); - EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), bucketStartTimeNs); - EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(), bucketStartTimeNs + bucketSizeNs); - EXPECT_EQ(data.bucket_info(1).count(), 1); - EXPECT_EQ(data.bucket_info(1).start_bucket_elapsed_nanos(), bucketStartTimeNs + bucketSizeNs); - EXPECT_EQ(data.bucket_info(1).end_bucket_elapsed_nanos(), bucketStartTimeNs + 2 * bucketSizeNs); - - data = countMetrics.data(2); - ValidateAttributionUidAndTagDimension(data.dimensions_in_what(), - util::WAKELOCK_STATE_CHANGED, 222, - "GMSCoreModule3"); - ASSERT_EQ(data.bucket_info_size(), 1); - EXPECT_EQ(data.bucket_info(0).count(), 1); - EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), - bucketStartTimeNs + 3 * bucketSizeNs); - EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(), bucketStartTimeNs + 4 * bucketSizeNs); - - data = countMetrics.data(3); - ValidateAttributionUidAndTagDimension(data.dimensions_in_what(), - util::WAKELOCK_STATE_CHANGED, 444, - "GMSCoreModule2"); - ASSERT_EQ(data.bucket_info_size(), 1); - EXPECT_EQ(data.bucket_info(0).count(), 1); - EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), - bucketStartTimeNs + 2 * bucketSizeNs); - EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(), bucketStartTimeNs + 3 * bucketSizeNs); -} - -TEST(AttributionE2eTest, TestAttributionMatchAndSliceByChain) { - auto config = CreateStatsdConfig(Position::ALL); - int64_t bucketStartTimeNs = 10000000000; - int64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(config.count_metric(0).bucket()) * 1000000; - - ConfigKey cfgKey; - auto processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey); - ASSERT_EQ(processor->mMetricsManagers.size(), 1u); - EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid()); - - // Here it assumes that GMS core has two uids. - processor->getUidMap()->updateMap( - 1, {222, 444, 111, 333}, {1, 1, 2, 2}, - {String16("v1"), String16("v1"), String16("v2"), String16("v2")}, - {String16("com.android.gmscore"), String16("com.android.gmscore"), String16("app1"), - String16("APP3")}, - {String16(""), String16(""), String16(""), String16("")}); - - std::vector> events; - // Events 1~4 are in the 1st bucket. - events.push_back(CreateAcquireWakelockEvent(bucketStartTimeNs + 2, attributionUids1, - attributionTags1, "wl1")); - events.push_back(CreateAcquireWakelockEvent(bucketStartTimeNs + 200, attributionUids2, - attributionTags2, "wl1")); - events.push_back(CreateAcquireWakelockEvent(bucketStartTimeNs + bucketSizeNs - 1, - attributionUids3, attributionTags3, "wl1")); - events.push_back(CreateAcquireWakelockEvent(bucketStartTimeNs + bucketSizeNs, attributionUids4, - attributionTags4, "wl1")); - - // Events 5~8 are in the 3rd bucket. - events.push_back(CreateAcquireWakelockEvent(bucketStartTimeNs + 2 * bucketSizeNs + 1, - attributionUids5, attributionTags5, "wl2")); - events.push_back(CreateAcquireWakelockEvent(bucketStartTimeNs + 2 * bucketSizeNs + 100, - attributionUids6, attributionTags6, "wl2")); - events.push_back(CreateAcquireWakelockEvent(bucketStartTimeNs + 3 * bucketSizeNs - 2, - attributionUids7, attributionTags7, "wl2")); - events.push_back(CreateAcquireWakelockEvent(bucketStartTimeNs + 3 * bucketSizeNs, - attributionUids8, attributionTags8, "wl2")); - events.push_back(CreateAcquireWakelockEvent(bucketStartTimeNs + 3 * bucketSizeNs + 1, - attributionUids10, attributionTags10, "wl2")); - events.push_back(CreateAcquireWakelockEvent(bucketStartTimeNs + 3 * bucketSizeNs + 100, - attributionUids10, attributionTags10, "wl2")); - events.push_back(CreateIsolatedUidChangedEvent(bucketStartTimeNs + 3 * bucketSizeNs - 1, 222, - isolatedUid, true /*is_create*/)); - events.push_back(CreateIsolatedUidChangedEvent(bucketStartTimeNs + 3 * bucketSizeNs + 10, 222, - isolatedUid, false /*is_create*/)); - - sortLogEventsByTimestamp(&events); - - for (const auto& event : events) { - processor->OnLogEvent(event.get()); - } - ConfigMetricsReportList reports; - vector buffer; - processor->onDumpReport(cfgKey, bucketStartTimeNs + 4 * bucketSizeNs + 1, false, true, ADB_DUMP, - FAST, &buffer); - EXPECT_TRUE(buffer.size() > 0); - EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size())); - backfillDimensionPath(&reports); - backfillStringInReport(&reports); - backfillStartEndTimestamp(&reports); - ASSERT_EQ(reports.reports_size(), 1); - ASSERT_EQ(reports.reports(0).metrics_size(), 1); - - StatsLogReport::CountMetricDataWrapper countMetrics; - sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).count_metrics(), &countMetrics); - ASSERT_EQ(countMetrics.data_size(), 6); - - auto data = countMetrics.data(0); - ValidateAttributionUidAndTagDimension(data.dimensions_in_what(), - util::WAKELOCK_STATE_CHANGED, 222, - "GMSCoreModule1"); - ASSERT_EQ(2, data.bucket_info_size()); - EXPECT_EQ(1, data.bucket_info(0).count()); - EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, data.bucket_info(0).start_bucket_elapsed_nanos()); - EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs, data.bucket_info(0).end_bucket_elapsed_nanos()); - EXPECT_EQ(1, data.bucket_info(1).count()); - EXPECT_EQ(bucketStartTimeNs + 3 * bucketSizeNs, - data.bucket_info(1).start_bucket_elapsed_nanos()); - EXPECT_EQ(bucketStartTimeNs + 4 * bucketSizeNs, data.bucket_info(1).end_bucket_elapsed_nanos()); - - data = countMetrics.data(1); - ValidateUidDimension(data.dimensions_in_what(), 0, util::WAKELOCK_STATE_CHANGED, 222); - ValidateAttributionUidAndTagDimension(data.dimensions_in_what(), 0, - util::WAKELOCK_STATE_CHANGED, 222, - "GMSCoreModule1"); - ValidateUidDimension(data.dimensions_in_what(), 1, util::WAKELOCK_STATE_CHANGED, 333); - ValidateAttributionUidAndTagDimension(data.dimensions_in_what(), 1, - util::WAKELOCK_STATE_CHANGED, 333, "App3"); - ASSERT_EQ(data.bucket_info_size(), 1); - EXPECT_EQ(data.bucket_info(0).count(), 1); - EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), bucketStartTimeNs); - EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(), bucketStartTimeNs + bucketSizeNs); - - data = countMetrics.data(2); - ValidateUidDimension(data.dimensions_in_what(), 0, util::WAKELOCK_STATE_CHANGED, 444); - ValidateAttributionUidAndTagDimension(data.dimensions_in_what(), 0, - util::WAKELOCK_STATE_CHANGED, 444, - "GMSCoreModule2"); - ValidateUidDimension(data.dimensions_in_what(), 1, util::WAKELOCK_STATE_CHANGED, 222); - ValidateAttributionUidAndTagDimension(data.dimensions_in_what(), 1, - util::WAKELOCK_STATE_CHANGED, 222, - "GMSCoreModule1"); - ASSERT_EQ(data.bucket_info_size(), 1); - EXPECT_EQ(data.bucket_info(0).count(), 1); - EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs, - data.bucket_info(0).start_bucket_elapsed_nanos()); - EXPECT_EQ(bucketStartTimeNs + 3 * bucketSizeNs, data.bucket_info(0).end_bucket_elapsed_nanos()); - - data = countMetrics.data(3); - ValidateUidDimension(data.dimensions_in_what(), 0, util::WAKELOCK_STATE_CHANGED, 111); - ValidateAttributionUidAndTagDimension(data.dimensions_in_what(), 0, - util::WAKELOCK_STATE_CHANGED, 111, "App1"); - ValidateUidDimension(data.dimensions_in_what(), 1, util::WAKELOCK_STATE_CHANGED, 222); - ValidateAttributionUidAndTagDimension(data.dimensions_in_what(), 1, - util::WAKELOCK_STATE_CHANGED, 222, - "GMSCoreModule1"); - ValidateUidDimension(data.dimensions_in_what(), 2, util::WAKELOCK_STATE_CHANGED, 333); - ValidateAttributionUidAndTagDimension(data.dimensions_in_what(), 2, - util::WAKELOCK_STATE_CHANGED, 333, "App3"); - ASSERT_EQ(data.bucket_info_size(), 1); - EXPECT_EQ(data.bucket_info(0).count(), 1); - EXPECT_EQ(bucketStartTimeNs, data.bucket_info(0).start_bucket_elapsed_nanos()); - EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, data.bucket_info(0).end_bucket_elapsed_nanos()); - - data = countMetrics.data(4); - ValidateUidDimension(data.dimensions_in_what(), 0, util::WAKELOCK_STATE_CHANGED, 111); - ValidateAttributionUidAndTagDimension(data.dimensions_in_what(), 0, - util::WAKELOCK_STATE_CHANGED, 111, "App1"); - ValidateUidDimension(data.dimensions_in_what(), 1, util::WAKELOCK_STATE_CHANGED, 333); - ValidateAttributionUidAndTagDimension(data.dimensions_in_what(), 1, - util::WAKELOCK_STATE_CHANGED, 333, "App3"); - ValidateUidDimension(data.dimensions_in_what(), 2, util::WAKELOCK_STATE_CHANGED, 222); - ValidateAttributionUidAndTagDimension(data.dimensions_in_what(), 2, - util::WAKELOCK_STATE_CHANGED, 222, - "GMSCoreModule1"); - ASSERT_EQ(data.bucket_info_size(), 1); - EXPECT_EQ(data.bucket_info(0).count(), 1); - EXPECT_EQ(bucketStartTimeNs, data.bucket_info(0).start_bucket_elapsed_nanos()); - EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, data.bucket_info(0).end_bucket_elapsed_nanos()); - - data = countMetrics.data(5); - ValidateUidDimension(data.dimensions_in_what(), 0, util::WAKELOCK_STATE_CHANGED, 111); - ValidateAttributionUidAndTagDimension(data.dimensions_in_what(), 0, - util::WAKELOCK_STATE_CHANGED, 111, "App1"); - ValidateUidDimension(data.dimensions_in_what(), 1, util::WAKELOCK_STATE_CHANGED, 444); - ValidateAttributionUidAndTagDimension(data.dimensions_in_what(), 1, - util::WAKELOCK_STATE_CHANGED, 444, - "GMSCoreModule2"); - ValidateUidDimension(data.dimensions_in_what(), 2, util::WAKELOCK_STATE_CHANGED, 333); - ValidateAttributionUidAndTagDimension(data.dimensions_in_what(), 2, - util::WAKELOCK_STATE_CHANGED, 333, "App3"); - ASSERT_EQ(data.bucket_info_size(), 1); - EXPECT_EQ(data.bucket_info(0).count(), 1); - EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs, - data.bucket_info(0).start_bucket_elapsed_nanos()); - EXPECT_EQ(bucketStartTimeNs + 3 * bucketSizeNs, data.bucket_info(0).end_bucket_elapsed_nanos()); -} - -#else -GTEST_LOG_(INFO) << "This test does nothing.\n"; -#endif - -} // namespace statsd -} // namespace os -} // namespace android diff --git a/bin/tests/e2e/ConfigTtl_e2e_test.cpp b/bin/tests/e2e/ConfigTtl_e2e_test.cpp deleted file mode 100644 index 0bce0baa..00000000 --- a/bin/tests/e2e/ConfigTtl_e2e_test.cpp +++ /dev/null @@ -1,114 +0,0 @@ -// Copyright (C) 2018 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 CreateStatsdConfig(int num_buckets, int threshold) { - StatsdConfig config; - config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root. - auto wakelockAcquireMatcher = CreateAcquireWakelockAtomMatcher(); - - *config.add_atom_matcher() = wakelockAcquireMatcher; - - auto countMetric = config.add_count_metric(); - countMetric->set_id(123456); - countMetric->set_what(wakelockAcquireMatcher.id()); - *countMetric->mutable_dimensions_in_what() = CreateAttributionUidDimensions( - util::WAKELOCK_STATE_CHANGED, {Position::FIRST}); - countMetric->set_bucket(FIVE_MINUTES); - - auto alert = config.add_alert(); - alert->set_id(StringToId("alert")); - alert->set_metric_id(123456); - alert->set_num_buckets(num_buckets); - alert->set_refractory_period_secs(10); - alert->set_trigger_if_sum_gt(threshold); - - // Two hours - config.set_ttl_in_seconds(2 * 3600); - return config; -} - -} // namespace - -TEST(ConfigTtlE2eTest, TestCountMetric) { - const int num_buckets = 1; - const int threshold = 3; - auto config = CreateStatsdConfig(num_buckets, threshold); - const uint64_t alert_id = config.alert(0).id(); - const uint32_t refractory_period_sec = config.alert(0).refractory_period_secs(); - - int64_t bucketStartTimeNs = 10000000000; - int64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(config.count_metric(0).bucket()) * 1000000; - - ConfigKey cfgKey; - auto processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey); - ASSERT_EQ(processor->mMetricsManagers.size(), 1u); - EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid()); - - std::vector attributionUids1 = {111}; - std::vector attributionTags1 = {"App1"}; - - FieldValue fieldValue1(Field(util::WAKELOCK_STATE_CHANGED, (int32_t)0x02010101), - Value((int32_t)111)); - HashableDimensionKey whatKey1({fieldValue1}); - MetricDimensionKey dimensionKey1(whatKey1, DEFAULT_DIMENSION_KEY); - - FieldValue fieldValue2(Field(util::WAKELOCK_STATE_CHANGED, (int32_t)0x02010101), - Value((int32_t)222)); - HashableDimensionKey whatKey2({fieldValue2}); - MetricDimensionKey dimensionKey2(whatKey2, DEFAULT_DIMENSION_KEY); - - auto event = CreateAcquireWakelockEvent(bucketStartTimeNs + 2, attributionUids1, - attributionTags1, "wl1"); - processor->OnLogEvent(event.get()); - - event = CreateAcquireWakelockEvent(bucketStartTimeNs + bucketSizeNs + 2, attributionUids1, - attributionTags1, "wl2"); - processor->OnLogEvent(event.get()); - - event = CreateAcquireWakelockEvent(bucketStartTimeNs + 25 * bucketSizeNs + 2, attributionUids1, - attributionTags1, "wl1"); - processor->OnLogEvent(event.get()); - - EXPECT_EQ((int64_t)(bucketStartTimeNs + 25 * bucketSizeNs + 2 + 2 * 3600 * NS_PER_SEC), - processor->mMetricsManagers.begin()->second->getTtlEndNs()); - - // Clear the data stored on disk as a result of the ttl. - vector buffer; - processor->onDumpReport(cfgKey, bucketStartTimeNs + 25 * bucketSizeNs + 3, false, true, - ADB_DUMP, FAST, &buffer); -} - -#else -GTEST_LOG_(INFO) << "This test does nothing.\n"; -#endif - -} // namespace statsd -} // namespace os -} // namespace android diff --git a/bin/tests/e2e/ConfigUpdate_e2e_ab_test.cpp b/bin/tests/e2e/ConfigUpdate_e2e_ab_test.cpp deleted file mode 100644 index 9b1cb12d..00000000 --- a/bin/tests/e2e/ConfigUpdate_e2e_ab_test.cpp +++ /dev/null @@ -1,376 +0,0 @@ -// Copyright (C) 2020 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 -#include -#include - -#include "flags/flags.h" -#include "src/StatsLogProcessor.h" -#include "src/storage/StorageManager.h" -#include "tests/statsd_test_util.h" - -namespace android { -namespace os { -namespace statsd { - -#ifdef __ANDROID__ -#define STATS_DATA_DIR "/data/misc/stats-data" - -using android::base::SetProperty; -using android::base::StringPrintf; -using ::ndk::SharedRefBase; -using namespace std; - -namespace { - -StatsdConfig CreateSimpleConfig() { - StatsdConfig config; - config.add_allowed_log_source("AID_STATSD"); - config.set_hash_strings_in_metric_report(false); - - *config.add_atom_matcher() = CreateBatteryStateUsbMatcher(); - // Simple count metric so the config isn't empty. - CountMetric* countMetric1 = config.add_count_metric(); - countMetric1->set_id(StringToId("Count1")); - countMetric1->set_what(config.atom_matcher(0).id()); - countMetric1->set_bucket(FIVE_MINUTES); - return config; -} -} // namespace - -// Setup for parameterized tests. -class ConfigUpdateE2eAbTest : public TestWithParam { -}; - -INSTANTIATE_TEST_SUITE_P(ConfigUpdateE2eAbTest, ConfigUpdateE2eAbTest, testing::Bool()); - -TEST_P(ConfigUpdateE2eAbTest, TestUidMapVersionStringInstaller) { - sp uidMap = new UidMap(); - vector uids({1000}); - vector versions({1}); - vector apps({String16("app1")}); - vector versionStrings({String16("v1")}); - vector installers({String16("installer1")}); - uidMap->updateMap(1, uids, versions, versionStrings, apps, installers); - - StatsdConfig config = CreateSimpleConfig(); - config.set_version_strings_in_metric_report(true); - config.set_installer_in_metric_report(false); - int64_t baseTimeNs = getElapsedRealtimeNs(); - - ConfigKey cfgKey(0, 12345); - sp processor = - CreateStatsLogProcessor(baseTimeNs, baseTimeNs, config, cfgKey, nullptr, 0, uidMap); - EXPECT_EQ(processor->mMetricsManagers.size(), 1u); - sp metricsManager = processor->mMetricsManagers.begin()->second; - EXPECT_TRUE(metricsManager->isConfigValid()); - - // Now update. - config.set_version_strings_in_metric_report(false); - config.set_installer_in_metric_report(true); - processor->OnConfigUpdated(baseTimeNs + 1000, cfgKey, config, GetParam()); - EXPECT_EQ(processor->mMetricsManagers.size(), 1u); - EXPECT_EQ(metricsManager == processor->mMetricsManagers.begin()->second, GetParam()); - EXPECT_TRUE(metricsManager->isConfigValid()); - - ConfigMetricsReportList reports; - vector buffer; - processor->onDumpReport(cfgKey, baseTimeNs + 1001, false, true, ADB_DUMP, FAST, &buffer); - EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size())); - // First report is written to disk when the update happens. - ASSERT_EQ(reports.reports_size(), 2); - UidMapping uidMapping = reports.reports(1).uid_map(); - ASSERT_EQ(uidMapping.snapshots_size(), 1); - ASSERT_EQ(uidMapping.snapshots(0).package_info_size(), 1); - EXPECT_FALSE(uidMapping.snapshots(0).package_info(0).has_version_string()); - EXPECT_EQ(uidMapping.snapshots(0).package_info(0).installer(), "installer1"); -} - -TEST_P(ConfigUpdateE2eAbTest, TestHashStrings) { - sp uidMap = new UidMap(); - vector uids({1000}); - vector versions({1}); - vector apps({String16("app1")}); - vector versionStrings({String16("v1")}); - vector installers({String16("installer1")}); - uidMap->updateMap(1, uids, versions, versionStrings, apps, installers); - - StatsdConfig config = CreateSimpleConfig(); - config.set_version_strings_in_metric_report(true); - config.set_hash_strings_in_metric_report(true); - int64_t baseTimeNs = getElapsedRealtimeNs(); - - ConfigKey cfgKey(0, 12345); - sp processor = - CreateStatsLogProcessor(baseTimeNs, baseTimeNs, config, cfgKey, nullptr, 0, uidMap); - EXPECT_EQ(processor->mMetricsManagers.size(), 1u); - sp metricsManager = processor->mMetricsManagers.begin()->second; - EXPECT_TRUE(metricsManager->isConfigValid()); - - // Now update. - config.set_hash_strings_in_metric_report(false); - processor->OnConfigUpdated(baseTimeNs + 1000, cfgKey, config, GetParam()); - EXPECT_EQ(processor->mMetricsManagers.size(), 1u); - EXPECT_EQ(metricsManager == processor->mMetricsManagers.begin()->second, GetParam()); - EXPECT_TRUE(metricsManager->isConfigValid()); - - ConfigMetricsReportList reports; - vector buffer; - processor->onDumpReport(cfgKey, baseTimeNs + 1001, false, true, ADB_DUMP, FAST, &buffer); - EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size())); - // First report is written to disk when the update happens. - ASSERT_EQ(reports.reports_size(), 2); - UidMapping uidMapping = reports.reports(1).uid_map(); - ASSERT_EQ(uidMapping.snapshots_size(), 1); - ASSERT_EQ(uidMapping.snapshots(0).package_info_size(), 1); - EXPECT_TRUE(uidMapping.snapshots(0).package_info(0).has_version_string()); - EXPECT_FALSE(uidMapping.snapshots(0).package_info(0).has_version_string_hash()); -} - -TEST_P(ConfigUpdateE2eAbTest, TestAnnotations) { - StatsdConfig config = CreateSimpleConfig(); - StatsdConfig_Annotation* annotation = config.add_annotation(); - annotation->set_field_int64(11); - annotation->set_field_int32(1); - int64_t baseTimeNs = getElapsedRealtimeNs(); - ConfigKey cfgKey(0, 12345); - sp processor = - CreateStatsLogProcessor(baseTimeNs, baseTimeNs, config, cfgKey); - - // Now update - config.clear_annotation(); - annotation = config.add_annotation(); - annotation->set_field_int64(22); - annotation->set_field_int32(2); - processor->OnConfigUpdated(baseTimeNs + 1000, cfgKey, config, GetParam()); - - ConfigMetricsReportList reports; - vector buffer; - processor->onDumpReport(cfgKey, baseTimeNs + 1001, false, true, ADB_DUMP, FAST, &buffer); - EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size())); - // First report is written to disk when the update happens. - ASSERT_EQ(reports.reports_size(), 2); - ConfigMetricsReport report = reports.reports(1); - EXPECT_EQ(report.annotation_size(), 1); - EXPECT_EQ(report.annotation(0).field_int64(), 22); - EXPECT_EQ(report.annotation(0).field_int32(), 2); -} - -TEST_P(ConfigUpdateE2eAbTest, TestPersistLocally) { - StatsdConfig config = CreateSimpleConfig(); - config.set_persist_locally(false); - int64_t baseTimeNs = getElapsedRealtimeNs(); - ConfigKey cfgKey(0, 12345); - sp processor = - CreateStatsLogProcessor(baseTimeNs, baseTimeNs, config, cfgKey); - ConfigMetricsReportList reports; - vector buffer; - processor->onDumpReport(cfgKey, baseTimeNs + 1001, false, true, ADB_DUMP, FAST, &buffer); - EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size())); - ASSERT_EQ(reports.reports_size(), 1); - // Number of reports should still be 1 since persist_locally is false. - reports.Clear(); - buffer.clear(); - processor->onDumpReport(cfgKey, baseTimeNs + 1001, false, true, ADB_DUMP, FAST, &buffer); - EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size())); - ASSERT_EQ(reports.reports_size(), 1); - - // Now update. - config.set_persist_locally(true); - processor->OnConfigUpdated(baseTimeNs + 1000, cfgKey, config, GetParam()); - - // Should get 2: 1 in memory + 1 on disk. Both should be saved on disk. - reports.Clear(); - buffer.clear(); - processor->onDumpReport(cfgKey, baseTimeNs + 1001, false, true, ADB_DUMP, FAST, &buffer); - EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size())); - ASSERT_EQ(reports.reports_size(), 2); - // Should get 3, 2 on disk + 1 in memory. - reports.Clear(); - buffer.clear(); - processor->onDumpReport(cfgKey, baseTimeNs + 1001, false, true, ADB_DUMP, FAST, &buffer); - EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size())); - ASSERT_EQ(reports.reports_size(), 3); - string suffix = StringPrintf("%d_%lld", cfgKey.GetUid(), (long long)cfgKey.GetId()); - StorageManager::deleteSuffixedFiles(STATS_DATA_DIR, suffix.c_str()); - string historySuffix = - StringPrintf("%d_%lld_history", cfgKey.GetUid(), (long long)cfgKey.GetId()); - StorageManager::deleteSuffixedFiles(STATS_DATA_DIR, historySuffix.c_str()); -} - -TEST_P(ConfigUpdateE2eAbTest, TestNoReportMetrics) { - StatsdConfig config = CreateSimpleConfig(); - // Second simple count metric. - CountMetric* countMetric = config.add_count_metric(); - countMetric->set_id(StringToId("Count2")); - countMetric->set_what(config.atom_matcher(0).id()); - countMetric->set_bucket(FIVE_MINUTES); - config.add_no_report_metric(config.count_metric(0).id()); - int64_t baseTimeNs = getElapsedRealtimeNs(); - ConfigKey cfgKey(0, 12345); - sp processor = - CreateStatsLogProcessor(baseTimeNs, baseTimeNs, config, cfgKey); - - // Now update. - config.clear_no_report_metric(); - config.add_no_report_metric(config.count_metric(1).id()); - processor->OnConfigUpdated(baseTimeNs + 1000, cfgKey, config, GetParam()); - - ConfigMetricsReportList reports; - vector buffer; - processor->onDumpReport(cfgKey, baseTimeNs + 1001, false, true, ADB_DUMP, FAST, &buffer); - EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size())); - // First report is written to disk when the update happens. - ASSERT_EQ(reports.reports_size(), 2); - // First report (before update) has the first count metric. - ASSERT_EQ(reports.reports(0).metrics_size(), 1); - EXPECT_EQ(reports.reports(0).metrics(0).metric_id(), config.count_metric(1).id()); - // Second report (after update) has the first count metric. - ASSERT_EQ(reports.reports(1).metrics_size(), 1); - EXPECT_EQ(reports.reports(1).metrics(0).metric_id(), config.count_metric(0).id()); -} - -TEST_P(ConfigUpdateE2eAbTest, TestAtomsAllowedFromAnyUid) { - StatsdConfig config = CreateSimpleConfig(); - int64_t baseTimeNs = getElapsedRealtimeNs(); - ConfigKey cfgKey(0, 12345); - sp processor = - CreateStatsLogProcessor(baseTimeNs, baseTimeNs, config, cfgKey); - // Uses AID_ROOT, which isn't in allowed log sources. - unique_ptr event = CreateBatteryStateChangedEvent( - baseTimeNs + 2, BatteryPluggedStateEnum::BATTERY_PLUGGED_USB); - processor->OnLogEvent(event.get()); - ConfigMetricsReportList reports; - vector buffer; - processor->onDumpReport(cfgKey, baseTimeNs + 1001, true, true, ADB_DUMP, FAST, &buffer); - EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size())); - ASSERT_EQ(reports.reports_size(), 1); - // Check the metric and make sure it has 0 count. - ASSERT_EQ(reports.reports(0).metrics_size(), 1); - EXPECT_FALSE(reports.reports(0).metrics(0).has_count_metrics()); - - // Now update. Allow plugged state to be logged from any uid, so the atom will be counted. - config.add_whitelisted_atom_ids(util::PLUGGED_STATE_CHANGED); - processor->OnConfigUpdated(baseTimeNs + 1000, cfgKey, config, GetParam()); - unique_ptr event2 = CreateBatteryStateChangedEvent( - baseTimeNs + 2000, BatteryPluggedStateEnum::BATTERY_PLUGGED_USB); - processor->OnLogEvent(event.get()); - reports.Clear(); - buffer.clear(); - processor->onDumpReport(cfgKey, baseTimeNs + 3000, true, true, ADB_DUMP, FAST, &buffer); - EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size())); - ASSERT_EQ(reports.reports_size(), 2); - // Check the metric and make sure it has 0 count. - ASSERT_EQ(reports.reports(1).metrics_size(), 1); - EXPECT_TRUE(reports.reports(1).metrics(0).has_count_metrics()); - ASSERT_EQ(reports.reports(1).metrics(0).count_metrics().data_size(), 1); - ASSERT_EQ(reports.reports(1).metrics(0).count_metrics().data(0).bucket_info_size(), 1); - EXPECT_EQ(reports.reports(1).metrics(0).count_metrics().data(0).bucket_info(0).count(), 1); -} - -TEST_P(ConfigUpdateE2eAbTest, TestConfigTtl) { - StatsdConfig config = CreateSimpleConfig(); - config.set_ttl_in_seconds(1); - int64_t baseTimeNs = getElapsedRealtimeNs(); - ConfigKey cfgKey(0, 12345); - sp processor = - CreateStatsLogProcessor(baseTimeNs, baseTimeNs, config, cfgKey); - EXPECT_EQ(processor->mMetricsManagers.size(), 1u); - sp metricsManager = processor->mMetricsManagers.begin()->second; - EXPECT_EQ(metricsManager->getTtlEndNs(), baseTimeNs + NS_PER_SEC); - - config.set_ttl_in_seconds(5); - processor->OnConfigUpdated(baseTimeNs + 2 * NS_PER_SEC, cfgKey, config, GetParam()); - metricsManager = processor->mMetricsManagers.begin()->second; - EXPECT_EQ(metricsManager->getTtlEndNs(), baseTimeNs + 7 * NS_PER_SEC); - - // Clear the data stored on disk as a result of the update. - vector buffer; - processor->onDumpReport(cfgKey, baseTimeNs + 3 * NS_PER_SEC, false, true, ADB_DUMP, FAST, - &buffer); -} - -TEST_P(ConfigUpdateE2eAbTest, TestExistingGaugePullRandomOneSample) { - StatsdConfig config; - config.add_allowed_log_source("AID_ROOT"); - config.add_default_pull_packages("AID_ROOT"); // Fake puller is registered with root. - - AtomMatcher subsystemSleepMatcher = - CreateSimpleAtomMatcher("SubsystemSleep", util::SUBSYSTEM_SLEEP_STATE); - *config.add_atom_matcher() = subsystemSleepMatcher; - - GaugeMetric metric = createGaugeMetric("GaugeSubsystemSleep", subsystemSleepMatcher.id(), - GaugeMetric::RANDOM_ONE_SAMPLE, nullopt, nullopt); - *metric.mutable_dimensions_in_what() = - CreateDimensions(util::SUBSYSTEM_SLEEP_STATE, {1 /* subsystem name */}); - *config.add_gauge_metric() = metric; - - ConfigKey key(123, 987); - uint64_t bucketStartTimeNs = getElapsedRealtimeNs(); - uint64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(TEN_MINUTES) * 1000000LL; - sp processor = CreateStatsLogProcessor( - bucketStartTimeNs, bucketStartTimeNs, config, key, - SharedRefBase::make(), util::SUBSYSTEM_SLEEP_STATE); - - uint64_t updateTimeNs = bucketStartTimeNs + 60 * NS_PER_SEC; - processor->OnConfigUpdated(updateTimeNs, key, config, GetParam()); - uint64_t dumpTimeNs = bucketStartTimeNs + 90 * NS_PER_SEC; - ConfigMetricsReportList reports; - vector buffer; - processor->onDumpReport(key, dumpTimeNs, true, true, ADB_DUMP, NO_TIME_CONSTRAINTS, &buffer); - EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size())); - backfillDimensionPath(&reports); - backfillStringInReport(&reports); - backfillStartEndTimestamp(&reports); - ASSERT_EQ(reports.reports_size(), 2); - - // From after the update - ConfigMetricsReport report = reports.reports(1); - ASSERT_EQ(report.metrics_size(), 1); - // Count screen on while screen is on. There was 1 after the update. - StatsLogReport metricData = report.metrics(0); - EXPECT_EQ(metricData.metric_id(), metric.id()); - EXPECT_TRUE(metricData.has_gauge_metrics()); - StatsLogReport::GaugeMetricDataWrapper gaugeMetrics; - sortMetricDataByDimensionsValue(metricData.gauge_metrics(), &gaugeMetrics); - ASSERT_EQ(gaugeMetrics.data_size(), 2); - - GaugeMetricData data = metricData.gauge_metrics().data(0); - EXPECT_EQ(util::SUBSYSTEM_SLEEP_STATE, data.dimensions_in_what().field()); - ASSERT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size()); - EXPECT_EQ(1 /* subsystem name field */, - data.dimensions_in_what().value_tuple().dimensions_value(0).field()); - EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).value_str(), - "subsystem_name_1"); - ASSERT_EQ(data.bucket_info_size(), 1); - ASSERT_EQ(1, data.bucket_info(0).atom_size()); - ASSERT_EQ(1, data.bucket_info(0).elapsed_timestamp_nanos_size()); - EXPECT_EQ(updateTimeNs, data.bucket_info(0).elapsed_timestamp_nanos(0)); - EXPECT_EQ(MillisToNano(NanoToMillis(updateTimeNs)), - data.bucket_info(0).start_bucket_elapsed_nanos()); - EXPECT_EQ(MillisToNano(NanoToMillis(dumpTimeNs)), - data.bucket_info(0).end_bucket_elapsed_nanos()); - EXPECT_TRUE(data.bucket_info(0).atom(0).subsystem_sleep_state().subsystem_name().empty()); - EXPECT_GT(data.bucket_info(0).atom(0).subsystem_sleep_state().time_millis(), 0); -} - -#else -GTEST_LOG_(INFO) << "This test does nothing.\n"; -#endif - -} // namespace statsd -} // namespace os -} // namespace android diff --git a/bin/tests/e2e/ConfigUpdate_e2e_test.cpp b/bin/tests/e2e/ConfigUpdate_e2e_test.cpp deleted file mode 100644 index efb8274d..00000000 --- a/bin/tests/e2e/ConfigUpdate_e2e_test.cpp +++ /dev/null @@ -1,2895 +0,0 @@ -// Copyright (C) 2020 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 -#include -#include -#include - -#include - -#include "flags/flags.h" -#include "src/StatsLogProcessor.h" -#include "src/StatsService.h" -#include "src/storage/StorageManager.h" -#include "src/subscriber/SubscriberReporter.h" -#include "tests/statsd_test_util.h" - -namespace android { -namespace os { -namespace statsd { - -#ifdef __ANDROID__ - -using aidl::android::os::StatsDimensionsValueParcel; -using android::base::SetProperty; -using android::base::StringPrintf; -using ::ndk::SharedRefBase; -using namespace std; - -// Tests that only run with the partial config update feature turned on. -namespace { -// Setup for test fixture. -class ConfigUpdateE2eTest : public ::testing::Test { -}; - -void ValidateSubsystemSleepDimension(const DimensionsValue& value, string name) { - EXPECT_EQ(value.field(), util::SUBSYSTEM_SLEEP_STATE); - ASSERT_EQ(value.value_tuple().dimensions_value_size(), 1); - EXPECT_EQ(value.value_tuple().dimensions_value(0).field(), 1 /* subsystem name field */); - EXPECT_EQ(value.value_tuple().dimensions_value(0).value_str(), name); -} - -} // Anonymous namespace. - -TEST_F(ConfigUpdateE2eTest, TestEventMetric) { - StatsdConfig config; - config.add_allowed_log_source("AID_ROOT"); - - AtomMatcher syncStartMatcher = CreateSyncStartAtomMatcher(); - *config.add_atom_matcher() = syncStartMatcher; - AtomMatcher wakelockAcquireMatcher = CreateAcquireWakelockAtomMatcher(); - *config.add_atom_matcher() = wakelockAcquireMatcher; - AtomMatcher screenOnMatcher = CreateScreenTurnedOnAtomMatcher(); - *config.add_atom_matcher() = screenOnMatcher; - AtomMatcher screenOffMatcher = CreateScreenTurnedOffAtomMatcher(); - *config.add_atom_matcher() = screenOffMatcher; - AtomMatcher batteryPluggedUsbMatcher = CreateBatteryStateUsbMatcher(); - *config.add_atom_matcher() = batteryPluggedUsbMatcher; - AtomMatcher unpluggedMatcher = CreateBatteryStateNoneMatcher(); - *config.add_atom_matcher() = unpluggedMatcher; - - AtomMatcher* combinationMatcher = config.add_atom_matcher(); - combinationMatcher->set_id(StringToId("SyncOrWakelockMatcher")); - combinationMatcher->mutable_combination()->set_operation(LogicalOperation::OR); - addMatcherToMatcherCombination(syncStartMatcher, combinationMatcher); - addMatcherToMatcherCombination(wakelockAcquireMatcher, combinationMatcher); - - Predicate screenOnPredicate = CreateScreenIsOnPredicate(); - *config.add_predicate() = screenOnPredicate; - Predicate unpluggedPredicate = CreateDeviceUnpluggedPredicate(); - *config.add_predicate() = unpluggedPredicate; - - Predicate* combinationPredicate = config.add_predicate(); - combinationPredicate->set_id(StringToId("ScreenOnOrUnpluggedPred)")); - combinationPredicate->mutable_combination()->set_operation(LogicalOperation::OR); - addPredicateToPredicateCombination(screenOnPredicate, combinationPredicate); - addPredicateToPredicateCombination(unpluggedPredicate, combinationPredicate); - - EventMetric eventPersist = - createEventMetric("SyncOrWlWhileScreenOnOrUnplugged", combinationMatcher->id(), - combinationPredicate->id()); - EventMetric eventChange = createEventMetric( - "WakelockWhileScreenOn", wakelockAcquireMatcher.id(), screenOnPredicate.id()); - EventMetric eventRemove = createEventMetric("Syncs", syncStartMatcher.id(), nullopt); - - *config.add_event_metric() = eventRemove; - *config.add_event_metric() = eventPersist; - *config.add_event_metric() = eventChange; - - ConfigKey key(123, 987); - uint64_t bucketStartTimeNs = 10000000000; // 0:10 - sp processor = - CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, key); - - int app1Uid = 123; - vector attributionUids1 = {app1Uid}; - vector attributionTags1 = {"App1"}; - - // Initialize log events before update. - std::vector> events; - events.push_back(CreateAcquireWakelockEvent(bucketStartTimeNs + 5 * NS_PER_SEC, - attributionUids1, attributionTags1, - "wl1")); // Not kept. - events.push_back(CreateScreenStateChangedEvent( - bucketStartTimeNs + 10 * NS_PER_SEC, - android::view::DISPLAY_STATE_ON)); // Condition true for change. - events.push_back(CreateSyncStartEvent(bucketStartTimeNs + 15 * NS_PER_SEC, attributionUids1, - attributionTags1, - "sync1")); // Kept for persist & remove. - events.push_back(CreateBatteryStateChangedEvent( - bucketStartTimeNs + 20 * NS_PER_SEC, - BatteryPluggedStateEnum::BATTERY_PLUGGED_NONE)); // Condition true for preserve. - events.push_back(CreateAcquireWakelockEvent(bucketStartTimeNs + 25 * NS_PER_SEC, - attributionUids1, attributionTags1, - "wl2")); // Kept by persist and change. - events.push_back(CreateScreenStateChangedEvent( - bucketStartTimeNs + 30 * NS_PER_SEC, - android::view::DISPLAY_STATE_OFF)); // Condition false for change. - events.push_back(CreateSyncStartEvent(bucketStartTimeNs + 35 * NS_PER_SEC, attributionUids1, - attributionTags1, - "sync2")); // Kept for persist & remove. - events.push_back(CreateAcquireWakelockEvent(bucketStartTimeNs + 40 * NS_PER_SEC, - attributionUids1, attributionTags1, - "wl3")); // Kept by persist. - - // Send log events to StatsLogProcessor. - for (auto& event : events) { - processor->OnLogEvent(event.get()); - } - - // Do update. Add matchers/conditions in different order to force indices to change. - StatsdConfig newConfig; - newConfig.add_allowed_log_source("AID_ROOT"); - - *newConfig.add_atom_matcher() = screenOnMatcher; - *newConfig.add_atom_matcher() = batteryPluggedUsbMatcher; - *newConfig.add_atom_matcher() = syncStartMatcher; - *newConfig.add_atom_matcher() = *combinationMatcher; - *newConfig.add_atom_matcher() = wakelockAcquireMatcher; - *newConfig.add_atom_matcher() = screenOffMatcher; - *newConfig.add_atom_matcher() = unpluggedMatcher; - *newConfig.add_predicate() = *combinationPredicate; - *newConfig.add_predicate() = unpluggedPredicate; - *newConfig.add_predicate() = screenOnPredicate; - - // Add metrics. Note that the condition of eventChange will go from false to true. - eventChange.set_condition(unpluggedPredicate.id()); - *newConfig.add_event_metric() = eventChange; - EventMetric eventNew = createEventMetric("ScreenOn", screenOnMatcher.id(), nullopt); - *newConfig.add_event_metric() = eventNew; - *newConfig.add_event_metric() = eventPersist; - - int64_t updateTimeNs = bucketStartTimeNs + 60 * NS_PER_SEC; - processor->OnConfigUpdated(updateTimeNs, key, newConfig); - - // Send events after the update. - events.clear(); - events.push_back(CreateAcquireWakelockEvent(bucketStartTimeNs + 65 * NS_PER_SEC, - attributionUids1, attributionTags1, - "wl4")); // Kept by preserve & change. - events.push_back(CreateBatteryStateChangedEvent( - bucketStartTimeNs + 70 * NS_PER_SEC, - BatteryPluggedStateEnum::BATTERY_PLUGGED_USB)); // All conditions are false. - events.push_back(CreateAcquireWakelockEvent(bucketStartTimeNs + 75 * NS_PER_SEC, - attributionUids1, attributionTags1, - "wl5")); // Not kept. - events.push_back(CreateScreenStateChangedEvent( - bucketStartTimeNs + 80 * NS_PER_SEC, - android::view::DISPLAY_STATE_ON)); // Condition true for preserve, event kept by new. - events.push_back(CreateAcquireWakelockEvent(bucketStartTimeNs + 85 * NS_PER_SEC, - attributionUids1, attributionTags1, - "wl6")); // Kept by preserve. - events.push_back(CreateSyncStartEvent(bucketStartTimeNs + 90 * NS_PER_SEC, attributionUids1, - attributionTags1, "sync3")); // Kept by preserve. - - // Send log events to StatsLogProcessor. - for (auto& event : events) { - processor->OnLogEvent(event.get()); - } - uint64_t dumpTimeNs = bucketStartTimeNs + 100 * NS_PER_SEC; - ConfigMetricsReportList reports; - vector buffer; - processor->onDumpReport(key, dumpTimeNs, true, true, ADB_DUMP, FAST, &buffer); - EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size())); - backfillDimensionPath(&reports); - backfillStringInReport(&reports); - backfillStartEndTimestamp(&reports); - ASSERT_EQ(reports.reports_size(), 2); - - // Report from before update. - ConfigMetricsReport report = reports.reports(0); - ASSERT_EQ(report.metrics_size(), 3); - // Event remove. Captured sync events. There were 2 syncs before the update. - StatsLogReport eventRemoveBefore = report.metrics(0); - EXPECT_EQ(eventRemoveBefore.metric_id(), eventRemove.id()); - EXPECT_TRUE(eventRemoveBefore.has_event_metrics()); - ASSERT_EQ(eventRemoveBefore.event_metrics().data_size(), 2); - auto data = eventRemoveBefore.event_metrics().data(0); - EXPECT_EQ(data.elapsed_timestamp_nanos(), bucketStartTimeNs + 15 * NS_PER_SEC); - EXPECT_EQ(data.atom().sync_state_changed().sync_name(), "sync1"); - data = eventRemoveBefore.event_metrics().data(1); - EXPECT_EQ(data.elapsed_timestamp_nanos(), bucketStartTimeNs + 35 * NS_PER_SEC); - EXPECT_EQ(data.atom().sync_state_changed().sync_name(), "sync2"); - - // Captured wakelocks & syncs while screen on or unplugged. There were 2 wakelocks and 2 syncs. - StatsLogReport eventPersistBefore = report.metrics(1); - EXPECT_EQ(eventPersistBefore.metric_id(), eventPersist.id()); - EXPECT_TRUE(eventPersistBefore.has_event_metrics()); - ASSERT_EQ(eventPersistBefore.event_metrics().data_size(), 3); - data = eventPersistBefore.event_metrics().data(0); - EXPECT_EQ(data.elapsed_timestamp_nanos(), bucketStartTimeNs + 25 * NS_PER_SEC); - EXPECT_EQ(data.atom().wakelock_state_changed().tag(), "wl2"); - data = eventPersistBefore.event_metrics().data(1); - EXPECT_EQ(data.elapsed_timestamp_nanos(), bucketStartTimeNs + 35 * NS_PER_SEC); - EXPECT_EQ(data.atom().sync_state_changed().sync_name(), "sync2"); - data = eventPersistBefore.event_metrics().data(2); - EXPECT_EQ(data.elapsed_timestamp_nanos(), bucketStartTimeNs + 40 * NS_PER_SEC); - EXPECT_EQ(data.atom().wakelock_state_changed().tag(), "wl3"); - - // Captured wakelock events while screen on. There was 1 before the update. - StatsLogReport eventChangeBefore = report.metrics(2); - EXPECT_EQ(eventChangeBefore.metric_id(), eventChange.id()); - EXPECT_TRUE(eventChangeBefore.has_event_metrics()); - ASSERT_EQ(eventChangeBefore.event_metrics().data_size(), 1); - data = eventChangeBefore.event_metrics().data(0); - EXPECT_EQ(data.elapsed_timestamp_nanos(), bucketStartTimeNs + 25 * NS_PER_SEC); - EXPECT_EQ(data.atom().wakelock_state_changed().tag(), "wl2"); - - // Report from after update. - report = reports.reports(1); - ASSERT_EQ(report.metrics_size(), 3); - // Captured wakelocks while unplugged. There was 1 after the update. - StatsLogReport eventChangeAfter = report.metrics(0); - EXPECT_EQ(eventChangeAfter.metric_id(), eventChange.id()); - EXPECT_TRUE(eventChangeAfter.has_event_metrics()); - ASSERT_EQ(eventChangeAfter.event_metrics().data_size(), 1); - data = eventChangeAfter.event_metrics().data(0); - EXPECT_EQ(data.elapsed_timestamp_nanos(), bucketStartTimeNs + 65 * NS_PER_SEC); - EXPECT_EQ(data.atom().wakelock_state_changed().tag(), "wl4"); - - // Captured screen on events. There was 1 after the update. - StatsLogReport eventNewAfter = report.metrics(1); - EXPECT_EQ(eventNewAfter.metric_id(), eventNew.id()); - EXPECT_TRUE(eventNewAfter.has_event_metrics()); - ASSERT_EQ(eventNewAfter.event_metrics().data_size(), 1); - data = eventNewAfter.event_metrics().data(0); - EXPECT_EQ(data.elapsed_timestamp_nanos(), bucketStartTimeNs + 80 * NS_PER_SEC); - EXPECT_EQ(data.atom().screen_state_changed().state(), android::view::DISPLAY_STATE_ON); - - // There were 2 wakelocks and 1 sync after the update while the condition was true. - StatsLogReport eventPersistAfter = report.metrics(2); - EXPECT_EQ(eventPersistAfter.metric_id(), eventPersist.id()); - EXPECT_TRUE(eventPersistAfter.has_event_metrics()); - ASSERT_EQ(eventPersistAfter.event_metrics().data_size(), 3); - data = eventPersistAfter.event_metrics().data(0); - EXPECT_EQ(data.elapsed_timestamp_nanos(), bucketStartTimeNs + 65 * NS_PER_SEC); - EXPECT_EQ(data.atom().wakelock_state_changed().tag(), "wl4"); - data = eventPersistAfter.event_metrics().data(1); - EXPECT_EQ(data.elapsed_timestamp_nanos(), bucketStartTimeNs + 85 * NS_PER_SEC); - EXPECT_EQ(data.atom().wakelock_state_changed().tag(), "wl6"); - data = eventPersistAfter.event_metrics().data(2); - EXPECT_EQ(data.elapsed_timestamp_nanos(), bucketStartTimeNs + 90 * NS_PER_SEC); - EXPECT_EQ(data.atom().sync_state_changed().sync_name(), "sync3"); -} - -TEST_F(ConfigUpdateE2eTest, TestCountMetric) { - StatsdConfig config; - config.add_allowed_log_source("AID_ROOT"); - - AtomMatcher syncStartMatcher = CreateSyncStartAtomMatcher(); - *config.add_atom_matcher() = syncStartMatcher; - AtomMatcher wakelockAcquireMatcher = CreateAcquireWakelockAtomMatcher(); - *config.add_atom_matcher() = wakelockAcquireMatcher; - AtomMatcher wakelockReleaseMatcher = CreateReleaseWakelockAtomMatcher(); - *config.add_atom_matcher() = wakelockReleaseMatcher; - AtomMatcher screenOnMatcher = CreateScreenTurnedOnAtomMatcher(); - *config.add_atom_matcher() = screenOnMatcher; - AtomMatcher screenOffMatcher = CreateScreenTurnedOffAtomMatcher(); - *config.add_atom_matcher() = screenOffMatcher; - - Predicate holdingWakelockPredicate = CreateHoldingWakelockPredicate(); - // The predicate is dimensioning by first attribution node by uid. - *holdingWakelockPredicate.mutable_simple_predicate()->mutable_dimensions() = - CreateAttributionUidDimensions(util::WAKELOCK_STATE_CHANGED, {Position::FIRST}); - *config.add_predicate() = holdingWakelockPredicate; - - Predicate screenOnPredicate = CreateScreenIsOnPredicate(); - *config.add_predicate() = screenOnPredicate; - - Predicate* combination = config.add_predicate(); - combination->set_id(StringToId("ScreenOnAndHoldingWL)")); - combination->mutable_combination()->set_operation(LogicalOperation::AND); - addPredicateToPredicateCombination(screenOnPredicate, combination); - addPredicateToPredicateCombination(holdingWakelockPredicate, combination); - - State uidProcessState = CreateUidProcessState(); - *config.add_state() = uidProcessState; - - CountMetric countPersist = - createCountMetric("CountSyncPerUidWhileScreenOnHoldingWLSliceProcessState", - syncStartMatcher.id(), combination->id(), {uidProcessState.id()}); - *countPersist.mutable_dimensions_in_what() = - CreateAttributionUidDimensions(util::SYNC_STATE_CHANGED, {Position::FIRST}); - // Links between sync state atom and condition of uid is holding wakelock. - MetricConditionLink* links = countPersist.add_links(); - links->set_condition(holdingWakelockPredicate.id()); - *links->mutable_fields_in_what() = - CreateAttributionUidDimensions(util::SYNC_STATE_CHANGED, {Position::FIRST}); - *links->mutable_fields_in_condition() = - CreateAttributionUidDimensions(util::WAKELOCK_STATE_CHANGED, {Position::FIRST}); - MetricStateLink* stateLink = countPersist.add_state_link(); - stateLink->set_state_atom_id(util::UID_PROCESS_STATE_CHANGED); - *stateLink->mutable_fields_in_what() = - CreateAttributionUidDimensions(util::SYNC_STATE_CHANGED, {Position::FIRST}); - *stateLink->mutable_fields_in_state() = - CreateDimensions(util::UID_PROCESS_STATE_CHANGED, {1 /*uid*/}); - - CountMetric countChange = createCountMetric("Count*WhileScreenOn", syncStartMatcher.id(), - screenOnPredicate.id(), {}); - CountMetric countRemove = createCountMetric("CountSync", syncStartMatcher.id(), nullopt, {}); - *config.add_count_metric() = countRemove; - *config.add_count_metric() = countPersist; - *config.add_count_metric() = countChange; - - ConfigKey key(123, 987); - uint64_t bucketStartTimeNs = 10000000000; // 0:10 - uint64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(TEN_MINUTES) * 1000000LL; - sp processor = - CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, key); - - int app1Uid = 123, app2Uid = 456; - vector attributionUids1 = {app1Uid}; - vector attributionTags1 = {"App1"}; - vector attributionUids2 = {app2Uid}; - vector attributionTags2 = {"App2"}; - - // Initialize log events before update. Counts are for countPersist since others are simpler. - std::vector> events; - events.push_back(CreateUidProcessStateChangedEvent( - bucketStartTimeNs + 2 * NS_PER_SEC, app1Uid, - android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_BACKGROUND)); - events.push_back(CreateUidProcessStateChangedEvent( - bucketStartTimeNs + 3 * NS_PER_SEC, app2Uid, - android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_FOREGROUND)); - events.push_back(CreateSyncStartEvent(bucketStartTimeNs + 5 * NS_PER_SEC, attributionUids1, - attributionTags1, "sync_name")); // Not counted. - events.push_back( - CreateScreenStateChangedEvent(bucketStartTimeNs + 10 * NS_PER_SEC, - android::view::DisplayStateEnum::DISPLAY_STATE_ON)); - events.push_back(CreateAcquireWakelockEvent(bucketStartTimeNs + 15 * NS_PER_SEC, - attributionUids1, attributionTags1, "wl1")); - events.push_back(CreateSyncStartEvent(bucketStartTimeNs + 20 * NS_PER_SEC, attributionUids1, - attributionTags1, "sync_name")); // Counted. uid1 = 1. - events.push_back(CreateSyncStartEvent(bucketStartTimeNs + 21 * NS_PER_SEC, attributionUids2, - attributionTags2, "sync_name")); // Not counted. - events.push_back(CreateAcquireWakelockEvent(bucketStartTimeNs + 25 * NS_PER_SEC, - attributionUids2, attributionTags2, "wl2")); - events.push_back(CreateSyncStartEvent(bucketStartTimeNs + 30 * NS_PER_SEC, attributionUids1, - attributionTags1, "sync_name")); // Counted. uid1 = 2. - events.push_back(CreateSyncStartEvent(bucketStartTimeNs + 31 * NS_PER_SEC, attributionUids2, - attributionTags2, "sync_name")); // Counted. uid2 = 1 - events.push_back(CreateReleaseWakelockEvent(bucketStartTimeNs + 35 * NS_PER_SEC, - attributionUids1, attributionTags1, "wl1")); - // Send log events to StatsLogProcessor. - for (auto& event : events) { - processor->OnLogEvent(event.get()); - } - - // Do update. Add matchers/conditions in different order to force indices to change. - StatsdConfig newConfig; - newConfig.add_allowed_log_source("AID_ROOT"); - - *newConfig.add_atom_matcher() = screenOnMatcher; - *newConfig.add_atom_matcher() = screenOffMatcher; - *newConfig.add_atom_matcher() = syncStartMatcher; - *newConfig.add_atom_matcher() = wakelockAcquireMatcher; - *newConfig.add_atom_matcher() = wakelockReleaseMatcher; - *newConfig.add_predicate() = *combination; - *newConfig.add_predicate() = holdingWakelockPredicate; - *newConfig.add_predicate() = screenOnPredicate; - *newConfig.add_state() = uidProcessState; - - countChange.set_what(screenOnMatcher.id()); - *newConfig.add_count_metric() = countChange; - CountMetric countNew = createCountMetric("CountWlWhileScreenOn", wakelockAcquireMatcher.id(), - screenOnPredicate.id(), {}); - *newConfig.add_count_metric() = countNew; - *newConfig.add_count_metric() = countPersist; - - int64_t updateTimeNs = bucketStartTimeNs + 60 * NS_PER_SEC; - processor->OnConfigUpdated(updateTimeNs, key, newConfig); - - // Send events after the update. Counts reset to 0 since this is a new bucket. - events.clear(); - events.push_back(CreateSyncStartEvent(bucketStartTimeNs + 65 * NS_PER_SEC, attributionUids1, - attributionTags1, "sync_name")); // Not counted. - events.push_back(CreateSyncStartEvent(bucketStartTimeNs + 66 * NS_PER_SEC, attributionUids2, - attributionTags2, "sync_name")); // Counted. uid2 = 1. - events.push_back(CreateAcquireWakelockEvent(bucketStartTimeNs + 70 * NS_PER_SEC, - attributionUids1, attributionTags1, "wl1")); - events.push_back( - CreateScreenStateChangedEvent(bucketStartTimeNs + 75 * NS_PER_SEC, - android::view::DisplayStateEnum::DISPLAY_STATE_OFF)); - events.push_back(CreateSyncStartEvent(bucketStartTimeNs + 80 * NS_PER_SEC, attributionUids2, - attributionTags2, "sync_name")); // Not counted. - events.push_back( - CreateScreenStateChangedEvent(bucketStartTimeNs + 85 * NS_PER_SEC, - android::view::DisplayStateEnum::DISPLAY_STATE_ON)); - events.push_back(CreateSyncStartEvent(bucketStartTimeNs + 90 * NS_PER_SEC, attributionUids1, - attributionTags1, "sync_name")); // Counted. uid1 = 1. - events.push_back(CreateSyncStartEvent(bucketStartTimeNs + 11 * NS_PER_SEC, attributionUids2, - attributionTags2, "sync_name")); // Counted. uid2 = 2. - // Flushes bucket. - events.push_back(CreateAcquireWakelockEvent(bucketStartTimeNs + bucketSizeNs + NS_PER_SEC, - attributionUids1, attributionTags1, "wl2")); - // Send log events to StatsLogProcessor. - for (auto& event : events) { - processor->OnLogEvent(event.get()); - } - uint64_t dumpTimeNs = bucketStartTimeNs + bucketSizeNs + 10 * NS_PER_SEC; - ConfigMetricsReportList reports; - vector buffer; - processor->onDumpReport(key, dumpTimeNs, true, true, ADB_DUMP, FAST, &buffer); - EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size())); - backfillDimensionPath(&reports); - backfillStringInReport(&reports); - backfillStartEndTimestamp(&reports); - ASSERT_EQ(reports.reports_size(), 2); - - // Report from before update. - ConfigMetricsReport report = reports.reports(0); - ASSERT_EQ(report.metrics_size(), 3); - // Count syncs. There were 5 syncs before the update. - StatsLogReport countRemoveBefore = report.metrics(0); - EXPECT_EQ(countRemoveBefore.metric_id(), countRemove.id()); - EXPECT_TRUE(countRemoveBefore.has_count_metrics()); - ASSERT_EQ(countRemoveBefore.count_metrics().data_size(), 1); - auto data = countRemoveBefore.count_metrics().data(0); - ASSERT_EQ(data.bucket_info_size(), 1); - ValidateCountBucket(data.bucket_info(0), bucketStartTimeNs, updateTimeNs, 5); - - // Uid 1 had 2 syncs, uid 2 had 1 sync. - StatsLogReport countPersistBefore = report.metrics(1); - EXPECT_EQ(countPersistBefore.metric_id(), countPersist.id()); - EXPECT_TRUE(countPersistBefore.has_count_metrics()); - StatsLogReport::CountMetricDataWrapper countMetrics; - sortMetricDataByDimensionsValue(countPersistBefore.count_metrics(), &countMetrics); - ASSERT_EQ(countMetrics.data_size(), 2); - data = countMetrics.data(0); - ValidateAttributionUidDimension(data.dimensions_in_what(), util::SYNC_STATE_CHANGED, app1Uid); - ValidateStateValue(data.slice_by_state(), util::UID_PROCESS_STATE_CHANGED, - android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_BACKGROUND); - ASSERT_EQ(data.bucket_info_size(), 1); - ValidateCountBucket(data.bucket_info(0), bucketStartTimeNs, updateTimeNs, 2); - - data = countMetrics.data(1); - ValidateAttributionUidDimension(data.dimensions_in_what(), util::SYNC_STATE_CHANGED, app2Uid); - ValidateStateValue(data.slice_by_state(), util::UID_PROCESS_STATE_CHANGED, - android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_FOREGROUND); - ASSERT_EQ(data.bucket_info_size(), 1); - ValidateCountBucket(data.bucket_info(0), bucketStartTimeNs, updateTimeNs, 1); - - // Counts syncs while screen on. There were 4 before the update. - StatsLogReport countChangeBefore = report.metrics(2); - EXPECT_EQ(countChangeBefore.metric_id(), countChange.id()); - EXPECT_TRUE(countChangeBefore.has_count_metrics()); - ASSERT_EQ(countChangeBefore.count_metrics().data_size(), 1); - data = countChangeBefore.count_metrics().data(0); - ASSERT_EQ(data.bucket_info_size(), 1); - ValidateCountBucket(data.bucket_info(0), bucketStartTimeNs, updateTimeNs, 4); - - // Report from after update. - report = reports.reports(1); - ASSERT_EQ(report.metrics_size(), 3); - // Count screen on while screen is on. There was 1 after the update. - StatsLogReport countChangeAfter = report.metrics(0); - EXPECT_EQ(countChangeAfter.metric_id(), countChange.id()); - EXPECT_TRUE(countChangeAfter.has_count_metrics()); - ASSERT_EQ(countChangeAfter.count_metrics().data_size(), 1); - data = countChangeAfter.count_metrics().data(0); - ASSERT_EQ(data.bucket_info_size(), 1); - ValidateCountBucket(data.bucket_info(0), updateTimeNs, bucketStartTimeNs + bucketSizeNs, 1); - - // Count wl acquires while screen on. There were 2, one in each bucket. - StatsLogReport countNewAfter = report.metrics(1); - EXPECT_EQ(countNewAfter.metric_id(), countNew.id()); - EXPECT_TRUE(countNewAfter.has_count_metrics()); - ASSERT_EQ(countNewAfter.count_metrics().data_size(), 1); - data = countNewAfter.count_metrics().data(0); - ASSERT_EQ(data.bucket_info_size(), 2); - ValidateCountBucket(data.bucket_info(0), updateTimeNs, bucketStartTimeNs + bucketSizeNs, 1); - ValidateCountBucket(data.bucket_info(1), bucketStartTimeNs + bucketSizeNs, dumpTimeNs, 1); - - // Uid 1 had 1 sync, uid 2 had 2 syncs. - StatsLogReport countPersistAfter = report.metrics(2); - EXPECT_EQ(countPersistAfter.metric_id(), countPersist.id()); - EXPECT_TRUE(countPersistAfter.has_count_metrics()); - countMetrics.Clear(); - sortMetricDataByDimensionsValue(countPersistAfter.count_metrics(), &countMetrics); - ASSERT_EQ(countMetrics.data_size(), 2); - data = countMetrics.data(0); - ValidateAttributionUidDimension(data.dimensions_in_what(), util::SYNC_STATE_CHANGED, app1Uid); - ValidateStateValue(data.slice_by_state(), util::UID_PROCESS_STATE_CHANGED, - android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_BACKGROUND); - ASSERT_EQ(data.bucket_info_size(), 1); - ValidateCountBucket(data.bucket_info(0), updateTimeNs, bucketStartTimeNs + bucketSizeNs, 1); - - data = countMetrics.data(1); - ValidateAttributionUidDimension(data.dimensions_in_what(), util::SYNC_STATE_CHANGED, app2Uid); - ValidateStateValue(data.slice_by_state(), util::UID_PROCESS_STATE_CHANGED, - android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_FOREGROUND); - ASSERT_EQ(data.bucket_info_size(), 1); - ValidateCountBucket(data.bucket_info(0), updateTimeNs, bucketStartTimeNs + bucketSizeNs, 2); -} - -TEST_F(ConfigUpdateE2eTest, TestDurationMetric) { - StatsdConfig config; - config.add_allowed_log_source("AID_ROOT"); - - AtomMatcher syncStartMatcher = CreateSyncStartAtomMatcher(); - *config.add_atom_matcher() = syncStartMatcher; - AtomMatcher syncStopMatcher = CreateSyncEndAtomMatcher(); - *config.add_atom_matcher() = syncStopMatcher; - AtomMatcher wakelockAcquireMatcher = CreateAcquireWakelockAtomMatcher(); - *config.add_atom_matcher() = wakelockAcquireMatcher; - AtomMatcher wakelockReleaseMatcher = CreateReleaseWakelockAtomMatcher(); - *config.add_atom_matcher() = wakelockReleaseMatcher; - AtomMatcher screenOnMatcher = CreateScreenTurnedOnAtomMatcher(); - *config.add_atom_matcher() = screenOnMatcher; - AtomMatcher screenOffMatcher = CreateScreenTurnedOffAtomMatcher(); - *config.add_atom_matcher() = screenOffMatcher; - - Predicate holdingWakelockPredicate = CreateHoldingWakelockPredicate(); - // The predicate is dimensioning by first attribution node by uid. - *holdingWakelockPredicate.mutable_simple_predicate()->mutable_dimensions() = - CreateAttributionUidDimensions(util::WAKELOCK_STATE_CHANGED, {Position::FIRST}); - *config.add_predicate() = holdingWakelockPredicate; - - Predicate screenOnPredicate = CreateScreenIsOnPredicate(); - *config.add_predicate() = screenOnPredicate; - - Predicate syncPredicate = CreateIsSyncingPredicate(); - // The predicate is dimensioning by first attribution node by uid. - *syncPredicate.mutable_simple_predicate()->mutable_dimensions() = - CreateAttributionUidDimensions(util::SYNC_STATE_CHANGED, {Position::FIRST}); - *config.add_predicate() = syncPredicate; - - State uidProcessState = CreateUidProcessState(); - *config.add_state() = uidProcessState; - - DurationMetric durationSumPersist = - createDurationMetric("DurSyncPerUidWhileHoldingWLSliceProcessState", syncPredicate.id(), - holdingWakelockPredicate.id(), {uidProcessState.id()}); - *durationSumPersist.mutable_dimensions_in_what() = - CreateAttributionUidDimensions(util::SYNC_STATE_CHANGED, {Position::FIRST}); - // Links between sync state atom and condition of uid is holding wakelock. - MetricConditionLink* links = durationSumPersist.add_links(); - links->set_condition(holdingWakelockPredicate.id()); - *links->mutable_fields_in_what() = - CreateAttributionUidDimensions(util::SYNC_STATE_CHANGED, {Position::FIRST}); - *links->mutable_fields_in_condition() = - CreateAttributionUidDimensions(util::WAKELOCK_STATE_CHANGED, {Position::FIRST}); - MetricStateLink* stateLink = durationSumPersist.add_state_link(); - stateLink->set_state_atom_id(util::UID_PROCESS_STATE_CHANGED); - *stateLink->mutable_fields_in_what() = - CreateAttributionUidDimensions(util::SYNC_STATE_CHANGED, {Position::FIRST}); - *stateLink->mutable_fields_in_state() = - CreateDimensions(util::UID_PROCESS_STATE_CHANGED, {1 /*uid*/}); - - DurationMetric durationMaxPersist = - createDurationMetric("DurMaxSyncPerUidWhileHoldingWL", syncPredicate.id(), - holdingWakelockPredicate.id(), {}); - durationMaxPersist.set_aggregation_type(DurationMetric::MAX_SPARSE); - *durationMaxPersist.mutable_dimensions_in_what() = - CreateAttributionUidDimensions(util::SYNC_STATE_CHANGED, {Position::FIRST}); - // Links between sync state atom and condition of uid is holding wakelock. - links = durationMaxPersist.add_links(); - links->set_condition(holdingWakelockPredicate.id()); - *links->mutable_fields_in_what() = - CreateAttributionUidDimensions(util::SYNC_STATE_CHANGED, {Position::FIRST}); - *links->mutable_fields_in_condition() = - CreateAttributionUidDimensions(util::WAKELOCK_STATE_CHANGED, {Position::FIRST}); - - DurationMetric durationChange = createDurationMetric("Dur*WhileScreenOn", syncPredicate.id(), - screenOnPredicate.id(), {}); - DurationMetric durationRemove = - createDurationMetric("DurScreenOn", screenOnPredicate.id(), nullopt, {}); - *config.add_duration_metric() = durationMaxPersist; - *config.add_duration_metric() = durationRemove; - *config.add_duration_metric() = durationSumPersist; - *config.add_duration_metric() = durationChange; - - ConfigKey key(123, 987); - uint64_t bucketStartTimeNs = 10000000000; // 0:10 - uint64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(TEN_MINUTES) * 1000000LL; - sp processor = - CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, key); - - int app1Uid = 123, app2Uid = 456, app3Uid = 789; - vector attributionUids1 = {app1Uid}; - vector attributionTags1 = {"App1"}; - vector attributionUids2 = {app2Uid}; - vector attributionTags2 = {"App2"}; - vector attributionUids3 = {app3Uid}; - vector attributionTags3 = {"App3"}; - - // Initialize log events before update. Comments provided for durations of persisted metrics. - std::vector> events; - events.push_back(CreateUidProcessStateChangedEvent( - bucketStartTimeNs + 2 * NS_PER_SEC, app1Uid, - android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_BACKGROUND)); - events.push_back(CreateUidProcessStateChangedEvent( - bucketStartTimeNs + 3 * NS_PER_SEC, app2Uid, - android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_FOREGROUND)); - events.push_back(CreateScreenStateChangedEvent( - bucketStartTimeNs + 5 * NS_PER_SEC, android::view::DisplayStateEnum::DISPLAY_STATE_ON)); - events.push_back(CreateUidProcessStateChangedEvent( - bucketStartTimeNs + 6 * NS_PER_SEC, app3Uid, - android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_BACKGROUND)); - events.push_back(CreateSyncStartEvent(bucketStartTimeNs + 10 * NS_PER_SEC, attributionUids1, - attributionTags1, "sync_name")); // uid1 paused. - events.push_back(CreateAcquireWakelockEvent(bucketStartTimeNs + 15 * NS_PER_SEC, - attributionUids2, attributionTags2, - "wl2")); // uid2 cond true. - events.push_back( - CreateScreenStateChangedEvent(bucketStartTimeNs + 20 * NS_PER_SEC, - android::view::DisplayStateEnum::DISPLAY_STATE_OFF)); - events.push_back(CreateSyncStartEvent(bucketStartTimeNs + 25 * NS_PER_SEC, attributionUids2, - attributionTags2, "sync_name")); // Uid 2 start t=25. - events.push_back(CreateAcquireWakelockEvent(bucketStartTimeNs + 27 * NS_PER_SEC, - attributionUids1, attributionTags1, - "wl1")); // Uid 1 start t=27. - events.push_back(CreateSyncStartEvent(bucketStartTimeNs + 35 * NS_PER_SEC, attributionUids3, - attributionTags3, "sync_name")); // Uid 3 paused. - events.push_back( - CreateScreenStateChangedEvent(bucketStartTimeNs + 40 * NS_PER_SEC, - android::view::DisplayStateEnum::DISPLAY_STATE_ON)); - events.push_back(CreateSyncEndEvent(bucketStartTimeNs + 45 * NS_PER_SEC, attributionUids2, - attributionTags2, - "sync_name")); // Uid 2 stop. sum/max = 20. - // Send log events to StatsLogProcessor. - for (auto& event : events) { - processor->OnLogEvent(event.get()); - } - - // Do update. Add matchers/conditions in different order to force indices to change. - StatsdConfig newConfig; - newConfig.add_allowed_log_source("AID_ROOT"); - - *newConfig.add_atom_matcher() = wakelockReleaseMatcher; - *newConfig.add_atom_matcher() = syncStopMatcher; - *newConfig.add_atom_matcher() = screenOnMatcher; - *newConfig.add_atom_matcher() = screenOffMatcher; - *newConfig.add_atom_matcher() = syncStartMatcher; - *newConfig.add_atom_matcher() = wakelockAcquireMatcher; - *newConfig.add_predicate() = syncPredicate; - *newConfig.add_predicate() = screenOnPredicate; - *newConfig.add_predicate() = holdingWakelockPredicate; - *newConfig.add_state() = uidProcessState; - - durationChange.set_what(holdingWakelockPredicate.id()); - *newConfig.add_duration_metric() = durationChange; - DurationMetric durationNew = - createDurationMetric("DurationSync", syncPredicate.id(), nullopt, {}); - *newConfig.add_duration_metric() = durationNew; - *newConfig.add_duration_metric() = durationMaxPersist; - *newConfig.add_duration_metric() = durationSumPersist; - - // At update, only uid 1 is syncing & holding a wakelock, duration=33. Max is paused for uid3. - // Before the update, only uid2 will report a duration for max, since others are started/paused. - int64_t updateTimeNs = bucketStartTimeNs + 60 * NS_PER_SEC; - processor->OnConfigUpdated(updateTimeNs, key, newConfig); - - // Send events after the update. - events.clear(); - events.push_back(CreateAcquireWakelockEvent(bucketStartTimeNs + 65 * NS_PER_SEC, - attributionUids3, attributionTags3, - "wl3")); // Uid3 start t=65. - events.push_back(CreateSyncStartEvent(bucketStartTimeNs + 70 * NS_PER_SEC, attributionUids2, - attributionTags2, "sync_name")); // Uid2 start t=70. - events.push_back(CreateReleaseWakelockEvent(bucketStartTimeNs + 75 * NS_PER_SEC, - attributionUids1, attributionTags1, - "wl1")); // Uid1 Pause. Dur = 15. - events.push_back( - CreateScreenStateChangedEvent(bucketStartTimeNs + 81 * NS_PER_SEC, - android::view::DisplayStateEnum::DISPLAY_STATE_OFF)); - events.push_back(CreateUidProcessStateChangedEvent( - bucketStartTimeNs + 85 * NS_PER_SEC, app3Uid, - android::app::ProcessStateEnum:: - PROCESS_STATE_IMPORTANT_FOREGROUND)); // Uid3 Sum in BG: 20. FG starts. Max is - // unchanged. - events.push_back(CreateSyncEndEvent(bucketStartTimeNs + 90 * NS_PER_SEC, attributionUids3, - attributionTags3, - "sync_name")); // Uid3 stop. Sum in FG: 5. MAX: 25. - - // Flushes bucket. Sum: uid1=15, uid2=bucketSize - 70, uid3 = 20 in FG, 5 in BG. Max: uid3=25, - // others don't report. - events.push_back(CreateSyncEndEvent(bucketStartTimeNs + bucketSizeNs + NS_PER_SEC, - attributionUids1, attributionTags1, - "sync_name")); // Uid1 stop. Max=15+33=48, Sum is 0. - - // Send log events to StatsLogProcessor. - for (auto& event : events) { - processor->OnLogEvent(event.get()); - } - uint64_t bucketEndTimeNs = bucketStartTimeNs + bucketSizeNs; - uint64_t dumpTimeNs = bucketStartTimeNs + bucketSizeNs + 10 * NS_PER_SEC; - ConfigMetricsReportList reports; - vector buffer; - // In the partial bucket, Sum for uid2 = 10, Max for Uid1 = 48. Rest are unreported. - processor->onDumpReport(key, dumpTimeNs, true, true, ADB_DUMP, FAST, &buffer); - EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size())); - backfillDimensionPath(&reports); - backfillStringInReport(&reports); - backfillStartEndTimestamp(&reports); - ASSERT_EQ(reports.reports_size(), 2); - - // Report from before update. - ConfigMetricsReport report = reports.reports(0); - ASSERT_EQ(report.metrics_size(), 4); - // Max duration of syncs per uid while uid holding WL. Only uid2 should have data. - StatsLogReport durationMaxPersistBefore = report.metrics(0); - EXPECT_EQ(durationMaxPersistBefore.metric_id(), durationMaxPersist.id()); - EXPECT_TRUE(durationMaxPersistBefore.has_duration_metrics()); - StatsLogReport::DurationMetricDataWrapper durationMetrics; - sortMetricDataByDimensionsValue(durationMaxPersistBefore.duration_metrics(), &durationMetrics); - ASSERT_EQ(durationMetrics.data_size(), 1); - auto data = durationMetrics.data(0); - ValidateAttributionUidDimension(data.dimensions_in_what(), util::SYNC_STATE_CHANGED, app2Uid); - ASSERT_EQ(data.bucket_info_size(), 1); - ValidateDurationBucket(data.bucket_info(0), bucketStartTimeNs, updateTimeNs, 20 * NS_PER_SEC); - - // Screen on time. ON: 5, OFF: 20, ON: 40. Update: 60. Result: 35 - StatsLogReport durationRemoveBefore = report.metrics(1); - EXPECT_EQ(durationRemoveBefore.metric_id(), durationRemove.id()); - EXPECT_TRUE(durationRemoveBefore.has_duration_metrics()); - durationMetrics.Clear(); - sortMetricDataByDimensionsValue(durationRemoveBefore.duration_metrics(), &durationMetrics); - ASSERT_EQ(durationMetrics.data_size(), 1); - data = durationMetrics.data(0); - ASSERT_EQ(data.bucket_info_size(), 1); - ValidateDurationBucket(data.bucket_info(0), bucketStartTimeNs, updateTimeNs, 35 * NS_PER_SEC); - - // Duration of syncs per uid while uid holding WL, slice screen. Uid1=33, uid2=20. - StatsLogReport durationSumPersistBefore = report.metrics(2); - EXPECT_EQ(durationSumPersistBefore.metric_id(), durationSumPersist.id()); - EXPECT_TRUE(durationSumPersistBefore.has_duration_metrics()); - durationMetrics.Clear(); - sortMetricDataByDimensionsValue(durationSumPersistBefore.duration_metrics(), &durationMetrics); - ASSERT_EQ(durationMetrics.data_size(), 2); - data = durationMetrics.data(0); - ValidateAttributionUidDimension(data.dimensions_in_what(), util::SYNC_STATE_CHANGED, app1Uid); - ValidateStateValue(data.slice_by_state(), util::UID_PROCESS_STATE_CHANGED, - android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_BACKGROUND); - ASSERT_EQ(data.bucket_info_size(), 1); - ValidateDurationBucket(data.bucket_info(0), bucketStartTimeNs, updateTimeNs, 33 * NS_PER_SEC); - - data = durationMetrics.data(1); - ValidateAttributionUidDimension(data.dimensions_in_what(), util::SYNC_STATE_CHANGED, app2Uid); - ValidateStateValue(data.slice_by_state(), util::UID_PROCESS_STATE_CHANGED, - android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_FOREGROUND); - ASSERT_EQ(data.bucket_info_size(), 1); - ValidateDurationBucket(data.bucket_info(0), bucketStartTimeNs, updateTimeNs, 20 * NS_PER_SEC); - - // Duration of syncs while screen on. Start: 10, pause: 20, start: 40 Update: 60. Total: 30. - StatsLogReport durationChangeBefore = report.metrics(3); - EXPECT_EQ(durationChangeBefore.metric_id(), durationChange.id()); - EXPECT_TRUE(durationChangeBefore.has_duration_metrics()); - durationMetrics.Clear(); - sortMetricDataByDimensionsValue(durationChangeBefore.duration_metrics(), &durationMetrics); - ASSERT_EQ(durationMetrics.data_size(), 1); - data = durationMetrics.data(0); - ASSERT_EQ(data.bucket_info_size(), 1); - ValidateDurationBucket(data.bucket_info(0), bucketStartTimeNs, updateTimeNs, 30 * NS_PER_SEC); - - // Report from after update. - report = reports.reports(1); - ASSERT_EQ(report.metrics_size(), 4); - // Duration of WL while screen on. Update: 60, pause: 81. Total: 21. - StatsLogReport durationChangeAfter = report.metrics(0); - EXPECT_EQ(durationChangeAfter.metric_id(), durationChange.id()); - EXPECT_TRUE(durationChangeAfter.has_duration_metrics()); - durationMetrics.Clear(); - sortMetricDataByDimensionsValue(durationChangeAfter.duration_metrics(), &durationMetrics); - ASSERT_EQ(durationMetrics.data_size(), 1); - data = durationMetrics.data(0); - ASSERT_EQ(data.bucket_info_size(), 1); - ValidateDurationBucket(data.bucket_info(0), updateTimeNs, bucketEndTimeNs, 21 * NS_PER_SEC); - - // Duration of syncs. Always true since at least 1 uid is always syncing. - StatsLogReport durationNewAfter = report.metrics(1); - EXPECT_EQ(durationNewAfter.metric_id(), durationNew.id()); - EXPECT_TRUE(durationNewAfter.has_duration_metrics()); - durationMetrics.Clear(); - sortMetricDataByDimensionsValue(durationNewAfter.duration_metrics(), &durationMetrics); - ASSERT_EQ(durationMetrics.data_size(), 1); - data = durationMetrics.data(0); - ASSERT_EQ(data.bucket_info_size(), 2); - ValidateDurationBucket(data.bucket_info(0), updateTimeNs, bucketEndTimeNs, - bucketEndTimeNs - updateTimeNs); - ValidateDurationBucket(data.bucket_info(1), bucketEndTimeNs, dumpTimeNs, - dumpTimeNs - bucketEndTimeNs); - - // Max duration of syncs per uid while uid holding WL. - StatsLogReport durationMaxPersistAfter = report.metrics(2); - EXPECT_EQ(durationMaxPersistAfter.metric_id(), durationMaxPersist.id()); - EXPECT_TRUE(durationMaxPersistAfter.has_duration_metrics()); - durationMetrics.Clear(); - sortMetricDataByDimensionsValue(durationMaxPersistAfter.duration_metrics(), &durationMetrics); - ASSERT_EQ(durationMetrics.data_size(), 2); - - // Uid 1. Duration = 48 in the later bucket. - data = durationMetrics.data(0); - ValidateAttributionUidDimension(data.dimensions_in_what(), util::SYNC_STATE_CHANGED, app1Uid); - ASSERT_EQ(data.bucket_info_size(), 1); - ValidateDurationBucket(data.bucket_info(0), bucketEndTimeNs, dumpTimeNs, 48 * NS_PER_SEC); - - // Uid 3. Duration = 25. - data = durationMetrics.data(1); - ValidateAttributionUidDimension(data.dimensions_in_what(), util::SYNC_STATE_CHANGED, app3Uid); - ASSERT_EQ(data.bucket_info_size(), 1); - ValidateDurationBucket(data.bucket_info(0), updateTimeNs, bucketEndTimeNs, 25 * NS_PER_SEC); - - // Duration of syncs per uid while uid holding WL, slice screen. - StatsLogReport durationSumPersistAfter = report.metrics(3); - EXPECT_EQ(durationSumPersistAfter.metric_id(), durationSumPersist.id()); - EXPECT_TRUE(durationSumPersistAfter.has_duration_metrics()); - durationMetrics.Clear(); - sortMetricDataByDimensionsValue(durationSumPersistAfter.duration_metrics(), &durationMetrics); - ASSERT_EQ(durationMetrics.data_size(), 4); - - // Uid 1 in BG. Duration = 15. - data = durationMetrics.data(0); - ValidateAttributionUidDimension(data.dimensions_in_what(), util::SYNC_STATE_CHANGED, app1Uid); - ValidateStateValue(data.slice_by_state(), util::UID_PROCESS_STATE_CHANGED, - android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_BACKGROUND); - ASSERT_EQ(data.bucket_info_size(), 1); - ValidateDurationBucket(data.bucket_info(0), updateTimeNs, bucketEndTimeNs, 15 * NS_PER_SEC); - - // Uid 2 in FG. Duration = bucketSize - 70 in first bucket, 10 in second bucket. - data = durationMetrics.data(1); - ValidateAttributionUidDimension(data.dimensions_in_what(), util::SYNC_STATE_CHANGED, app2Uid); - ValidateStateValue(data.slice_by_state(), util::UID_PROCESS_STATE_CHANGED, - android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_FOREGROUND); - ASSERT_EQ(data.bucket_info_size(), 2); - ValidateDurationBucket(data.bucket_info(0), updateTimeNs, bucketEndTimeNs, - bucketSizeNs - 70 * NS_PER_SEC); - ValidateDurationBucket(data.bucket_info(1), bucketEndTimeNs, dumpTimeNs, 10 * NS_PER_SEC); - - // Uid 3 in FG. Duration = 5. - data = durationMetrics.data(2); - ValidateAttributionUidDimension(data.dimensions_in_what(), util::SYNC_STATE_CHANGED, app3Uid); - ValidateStateValue(data.slice_by_state(), util::UID_PROCESS_STATE_CHANGED, - android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_FOREGROUND); - ASSERT_EQ(data.bucket_info_size(), 1); - ValidateDurationBucket(data.bucket_info(0), updateTimeNs, bucketEndTimeNs, 5 * NS_PER_SEC); - - // Uid 3 in BG. Duration = 20. - data = durationMetrics.data(3); - ValidateAttributionUidDimension(data.dimensions_in_what(), util::SYNC_STATE_CHANGED, app3Uid); - ValidateStateValue(data.slice_by_state(), util::UID_PROCESS_STATE_CHANGED, - android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_BACKGROUND); - ASSERT_EQ(data.bucket_info_size(), 1); - ValidateDurationBucket(data.bucket_info(0), updateTimeNs, bucketEndTimeNs, 20 * NS_PER_SEC); -} - -TEST_F(ConfigUpdateE2eTest, TestGaugeMetric) { - StatsdConfig config; - config.add_allowed_log_source("AID_ROOT"); - config.add_default_pull_packages("AID_ROOT"); // Fake puller is registered with root. - - AtomMatcher appStartMatcher = CreateSimpleAtomMatcher("AppStart", util::APP_START_OCCURRED); - *config.add_atom_matcher() = appStartMatcher; - AtomMatcher backgroundMatcher = CreateMoveToBackgroundAtomMatcher(); - *config.add_atom_matcher() = backgroundMatcher; - AtomMatcher foregroundMatcher = CreateMoveToForegroundAtomMatcher(); - *config.add_atom_matcher() = foregroundMatcher; - AtomMatcher screenOnMatcher = CreateScreenTurnedOnAtomMatcher(); - *config.add_atom_matcher() = screenOnMatcher; - AtomMatcher screenOffMatcher = CreateScreenTurnedOffAtomMatcher(); - *config.add_atom_matcher() = screenOffMatcher; - AtomMatcher subsystemSleepMatcher = - CreateSimpleAtomMatcher("SubsystemSleep", util::SUBSYSTEM_SLEEP_STATE); - *config.add_atom_matcher() = subsystemSleepMatcher; - - Predicate isInBackgroundPredicate = CreateIsInBackgroundPredicate(); - *isInBackgroundPredicate.mutable_simple_predicate()->mutable_dimensions() = - CreateDimensions(util::ACTIVITY_FOREGROUND_STATE_CHANGED, {1 /*uid field*/}); - *config.add_predicate() = isInBackgroundPredicate; - - Predicate screenOnPredicate = CreateScreenIsOnPredicate(); - *config.add_predicate() = screenOnPredicate; - - GaugeMetric gaugePullPersist = - createGaugeMetric("SubsystemSleepWhileScreenOn", subsystemSleepMatcher.id(), - GaugeMetric::RANDOM_ONE_SAMPLE, screenOnPredicate.id(), {}); - *gaugePullPersist.mutable_dimensions_in_what() = - CreateDimensions(util::SUBSYSTEM_SLEEP_STATE, {1 /* subsystem name */}); - - GaugeMetric gaugePushPersist = - createGaugeMetric("AppStartWhileInBg", appStartMatcher.id(), - GaugeMetric::FIRST_N_SAMPLES, isInBackgroundPredicate.id(), nullopt); - *gaugePushPersist.mutable_dimensions_in_what() = - CreateDimensions(util::APP_START_OCCURRED, {1 /*uid field*/}); - // Links between sync state atom and condition of uid is holding wakelock. - MetricConditionLink* links = gaugePushPersist.add_links(); - links->set_condition(isInBackgroundPredicate.id()); - *links->mutable_fields_in_what() = - CreateDimensions(util::APP_START_OCCURRED, {1 /*uid field*/}); - *links->mutable_fields_in_condition() = - CreateDimensions(util::ACTIVITY_FOREGROUND_STATE_CHANGED, {1 /*uid field*/}); - - GaugeMetric gaugeChange = createGaugeMetric("GaugeScrOn", screenOnMatcher.id(), - GaugeMetric::RANDOM_ONE_SAMPLE, nullopt, nullopt); - GaugeMetric gaugeRemove = - createGaugeMetric("GaugeSubsysTriggerScr", subsystemSleepMatcher.id(), - GaugeMetric::FIRST_N_SAMPLES, nullopt, screenOnMatcher.id()); - *gaugeRemove.mutable_dimensions_in_what() = - CreateDimensions(util::SUBSYSTEM_SLEEP_STATE, {1 /* subsystem name */}); - *config.add_gauge_metric() = gaugeRemove; - *config.add_gauge_metric() = gaugePullPersist; - *config.add_gauge_metric() = gaugeChange; - *config.add_gauge_metric() = gaugePushPersist; - - ConfigKey key(123, 987); - uint64_t bucketStartTimeNs = getElapsedRealtimeNs(); // 0:10 - uint64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(TEN_MINUTES) * 1000000LL; - sp processor = CreateStatsLogProcessor( - bucketStartTimeNs, bucketStartTimeNs, config, key, - SharedRefBase::make(), util::SUBSYSTEM_SLEEP_STATE); - - int app1Uid = 123, app2Uid = 456; - - // Initialize log events before update. - std::vector> events; - events.push_back(CreateMoveToBackgroundEvent(bucketStartTimeNs + 5 * NS_PER_SEC, app1Uid)); - events.push_back(CreateAppStartOccurredEvent(bucketStartTimeNs + 10 * NS_PER_SEC, app1Uid, - "app1", AppStartOccurred::WARM, "", "", true, - /*start_msec*/ 101)); // Kept by gaugePushPersist. - events.push_back( - CreateAppStartOccurredEvent(bucketStartTimeNs + 15 * NS_PER_SEC, app2Uid, "app2", - AppStartOccurred::WARM, "", "", true, - /*start_msec*/ 201)); // Not kept by gaugePushPersist. - events.push_back(CreateScreenStateChangedEvent( - bucketStartTimeNs + 20 * NS_PER_SEC, - android::view::DISPLAY_STATE_ON)); // Pulls gaugePullPersist and gaugeRemove. - events.push_back(CreateMoveToBackgroundEvent(bucketStartTimeNs + 25 * NS_PER_SEC, app2Uid)); - events.push_back( - CreateScreenStateChangedEvent(bucketStartTimeNs + 30 * NS_PER_SEC, - android::view::DisplayStateEnum::DISPLAY_STATE_OFF)); - events.push_back(CreateAppStartOccurredEvent(bucketStartTimeNs + 35 * NS_PER_SEC, app1Uid, - "app1", AppStartOccurred::WARM, "", "", true, - /*start_msec*/ 102)); // Kept by gaugePushPersist. - events.push_back(CreateAppStartOccurredEvent(bucketStartTimeNs + 40 * NS_PER_SEC, app2Uid, - "app2", AppStartOccurred::WARM, "", "", true, - /*start_msec*/ 202)); // Kept by gaugePushPersist. - events.push_back(CreateScreenStateChangedEvent( - bucketStartTimeNs + 45 * NS_PER_SEC, - android::view::DisplayStateEnum::DISPLAY_STATE_ON)); // Pulls gaugeRemove only. - events.push_back(CreateMoveToForegroundEvent(bucketStartTimeNs + 50 * NS_PER_SEC, app1Uid)); - - // Send log events to StatsLogProcessor. - for (auto& event : events) { - processor->mPullerManager->ForceClearPullerCache(); - processor->OnLogEvent(event.get()); - } - processor->mPullerManager->ForceClearPullerCache(); - - // Do the update. Add matchers/conditions in different order to force indices to change. - StatsdConfig newConfig; - newConfig.add_allowed_log_source("AID_ROOT"); - newConfig.add_default_pull_packages("AID_ROOT"); // Fake puller is registered with root. - - *newConfig.add_atom_matcher() = screenOffMatcher; - *newConfig.add_atom_matcher() = foregroundMatcher; - *newConfig.add_atom_matcher() = appStartMatcher; - *newConfig.add_atom_matcher() = subsystemSleepMatcher; - *newConfig.add_atom_matcher() = backgroundMatcher; - *newConfig.add_atom_matcher() = screenOnMatcher; - - *newConfig.add_predicate() = isInBackgroundPredicate; - *newConfig.add_predicate() = screenOnPredicate; - - gaugeChange.set_sampling_type(GaugeMetric::FIRST_N_SAMPLES); - *newConfig.add_gauge_metric() = gaugeChange; - GaugeMetric gaugeNew = createGaugeMetric("GaugeSubsys", subsystemSleepMatcher.id(), - GaugeMetric::RANDOM_ONE_SAMPLE, {}, {}); - *gaugeNew.mutable_dimensions_in_what() = - CreateDimensions(util::SUBSYSTEM_SLEEP_STATE, {1 /* subsystem name */}); - *newConfig.add_gauge_metric() = gaugeNew; - *newConfig.add_gauge_metric() = gaugePushPersist; - *newConfig.add_gauge_metric() = gaugePullPersist; - - int64_t updateTimeNs = bucketStartTimeNs + 60 * NS_PER_SEC; - // Update pulls gaugePullPersist and gaugeNew. - processor->OnConfigUpdated(updateTimeNs, key, newConfig); - - // Verify puller manager is properly set. - sp pullerManager = processor->mPullerManager; - EXPECT_EQ(pullerManager->mNextPullTimeNs, bucketStartTimeNs + bucketSizeNs); - ASSERT_EQ(pullerManager->mReceivers.size(), 1); - ASSERT_EQ(pullerManager->mReceivers.begin()->second.size(), 2); - - // Send events after the update. Counts reset to 0 since this is a new bucket. - events.clear(); - events.push_back( - CreateAppStartOccurredEvent(bucketStartTimeNs + 65 * NS_PER_SEC, app1Uid, "app1", - AppStartOccurred::WARM, "", "", true, - /*start_msec*/ 103)); // Not kept by gaugePushPersist. - events.push_back(CreateAppStartOccurredEvent(bucketStartTimeNs + 70 * NS_PER_SEC, app2Uid, - "app2", AppStartOccurred::WARM, "", "", true, - /*start_msec*/ 203)); // Kept by gaugePushPersist. - events.push_back( - CreateScreenStateChangedEvent(bucketStartTimeNs + 75 * NS_PER_SEC, - android::view::DisplayStateEnum::DISPLAY_STATE_OFF)); - events.push_back( - CreateScreenStateChangedEvent(bucketStartTimeNs + 80 * NS_PER_SEC, - android::view::DisplayStateEnum::DISPLAY_STATE_ON)); - events.push_back(CreateMoveToBackgroundEvent(bucketStartTimeNs + 85 * NS_PER_SEC, app1Uid)); - events.push_back( - CreateScreenStateChangedEvent(bucketStartTimeNs + 90 * NS_PER_SEC, - android::view::DisplayStateEnum::DISPLAY_STATE_OFF)); - events.push_back(CreateAppStartOccurredEvent(bucketStartTimeNs + 95 * NS_PER_SEC, app1Uid, - "app1", AppStartOccurred::WARM, "", "", true, - /*start_msec*/ 104)); // Kept by gaugePushPersist. - events.push_back(CreateAppStartOccurredEvent(bucketStartTimeNs + 100 * NS_PER_SEC, app2Uid, - "app2", AppStartOccurred::WARM, "", "", true, - /*start_msec*/ 204)); // Kept by gaugePushPersist. - events.push_back( - CreateScreenStateChangedEvent(bucketStartTimeNs + 105 * NS_PER_SEC, - android::view::DisplayStateEnum::DISPLAY_STATE_ON)); - events.push_back( - CreateScreenStateChangedEvent(bucketStartTimeNs + 110 * NS_PER_SEC, - android::view::DisplayStateEnum::DISPLAY_STATE_OFF)); - // Send log events to StatsLogProcessor. - for (auto& event : events) { - processor->mPullerManager->ForceClearPullerCache(); - processor->OnLogEvent(event.get()); - } - processor->mPullerManager->ForceClearPullerCache(); - // Pulling alarm arrive, triggering a bucket split. Only gaugeNew keeps the data since the - // condition is false for gaugeNew. - processor->informPullAlarmFired(bucketStartTimeNs + bucketSizeNs); - - uint64_t dumpTimeNs = bucketStartTimeNs + bucketSizeNs + 10 * NS_PER_SEC; - ConfigMetricsReportList reports; - vector buffer; - processor->onDumpReport(key, dumpTimeNs, true, true, ADB_DUMP, FAST, &buffer); - EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size())); - backfillDimensionPath(&reports); - backfillStringInReport(&reports); - backfillStartEndTimestamp(&reports); - ASSERT_EQ(reports.reports_size(), 2); - - int64_t roundedBucketStartNs = MillisToNano(NanoToMillis(bucketStartTimeNs)); - int64_t roundedUpdateTimeNs = MillisToNano(NanoToMillis(updateTimeNs)); - int64_t roundedBucketEndNs = MillisToNano(NanoToMillis(bucketStartTimeNs + bucketSizeNs)); - int64_t roundedDumpTimeNs = MillisToNano(NanoToMillis(dumpTimeNs)); - - // Report from before update. - ConfigMetricsReport report = reports.reports(0); - ASSERT_EQ(report.metrics_size(), 4); - // Gauge subsystem sleep state trigger screen on. 2 pulls occurred. - StatsLogReport gaugeRemoveBefore = report.metrics(0); - EXPECT_EQ(gaugeRemoveBefore.metric_id(), gaugeRemove.id()); - EXPECT_TRUE(gaugeRemoveBefore.has_gauge_metrics()); - StatsLogReport::GaugeMetricDataWrapper gaugeMetrics; - sortMetricDataByDimensionsValue(gaugeRemoveBefore.gauge_metrics(), &gaugeMetrics); - ASSERT_EQ(gaugeMetrics.data_size(), 2); - auto data = gaugeMetrics.data(0); - ValidateSubsystemSleepDimension(data.dimensions_in_what(), "subsystem_name_1"); - ASSERT_EQ(data.bucket_info_size(), 1); - ValidateGaugeBucketTimes(data.bucket_info(0), roundedBucketStartNs, roundedUpdateTimeNs, - {(int64_t)(bucketStartTimeNs + 20 * NS_PER_SEC), - (int64_t)(bucketStartTimeNs + 45 * NS_PER_SEC)}); - ASSERT_EQ(data.bucket_info(0).atom_size(), 2); - EXPECT_EQ(data.bucket_info(0).atom(0).subsystem_sleep_state().time_millis(), 101); - EXPECT_EQ(data.bucket_info(0).atom(1).subsystem_sleep_state().time_millis(), 401); - - data = gaugeMetrics.data(1); - ValidateSubsystemSleepDimension(data.dimensions_in_what(), "subsystem_name_2"); - ASSERT_EQ(data.bucket_info_size(), 1); - ValidateGaugeBucketTimes(data.bucket_info(0), roundedBucketStartNs, roundedUpdateTimeNs, - {(int64_t)(bucketStartTimeNs + 20 * NS_PER_SEC), - (int64_t)(bucketStartTimeNs + 45 * NS_PER_SEC)}); - ASSERT_EQ(data.bucket_info(0).atom_size(), 2); - EXPECT_EQ(data.bucket_info(0).atom(0).subsystem_sleep_state().time_millis(), 102); - EXPECT_EQ(data.bucket_info(0).atom(1).subsystem_sleep_state().time_millis(), 402); - - // Gauge subsystem sleep state when screen is on. One pull when the screen turned on - StatsLogReport gaugePullPersistBefore = report.metrics(1); - EXPECT_EQ(gaugePullPersistBefore.metric_id(), gaugePullPersist.id()); - EXPECT_TRUE(gaugePullPersistBefore.has_gauge_metrics()); - gaugeMetrics.Clear(); - sortMetricDataByDimensionsValue(gaugePullPersistBefore.gauge_metrics(), &gaugeMetrics); - ASSERT_EQ(gaugeMetrics.data_size(), 2); - data = gaugeMetrics.data(0); - ValidateSubsystemSleepDimension(data.dimensions_in_what(), "subsystem_name_1"); - ASSERT_EQ(data.bucket_info_size(), 1); - ValidateGaugeBucketTimes(data.bucket_info(0), roundedBucketStartNs, roundedUpdateTimeNs, - {(int64_t)(bucketStartTimeNs + 20 * NS_PER_SEC)}); - ASSERT_EQ(data.bucket_info(0).atom_size(), 1); - EXPECT_EQ(data.bucket_info(0).atom(0).subsystem_sleep_state().time_millis(), 101); - - data = gaugeMetrics.data(1); - ValidateSubsystemSleepDimension(data.dimensions_in_what(), "subsystem_name_2"); - ASSERT_EQ(data.bucket_info_size(), 1); - ValidateGaugeBucketTimes(data.bucket_info(0), roundedBucketStartNs, roundedUpdateTimeNs, - {(int64_t)(bucketStartTimeNs + 20 * NS_PER_SEC)}); - ASSERT_EQ(data.bucket_info(0).atom_size(), 1); - EXPECT_EQ(data.bucket_info(0).atom(0).subsystem_sleep_state().time_millis(), 102); - - // Gauge screen on events, one per bucket. - StatsLogReport gaugeChangeBefore = report.metrics(2); - EXPECT_EQ(gaugeChangeBefore.metric_id(), gaugeChange.id()); - EXPECT_TRUE(gaugeChangeBefore.has_gauge_metrics()); - gaugeMetrics.Clear(); - sortMetricDataByDimensionsValue(gaugeChangeBefore.gauge_metrics(), &gaugeMetrics); - ASSERT_EQ(gaugeMetrics.data_size(), 1); - data = gaugeMetrics.data(0); - ASSERT_EQ(data.bucket_info_size(), 1); - ValidateGaugeBucketTimes(data.bucket_info(0), roundedBucketStartNs, roundedUpdateTimeNs, - {(int64_t)(bucketStartTimeNs + 20 * NS_PER_SEC)}); - ASSERT_EQ(data.bucket_info(0).atom_size(), 1); - EXPECT_EQ(data.bucket_info(0).atom(0).screen_state_changed().state(), - android::view::DISPLAY_STATE_ON); - - // Gauge app start while app is in the background. App 1 started twice, app 2 started once. - StatsLogReport gaugePushPersistBefore = report.metrics(3); - EXPECT_EQ(gaugePushPersistBefore.metric_id(), gaugePushPersist.id()); - EXPECT_TRUE(gaugePushPersistBefore.has_gauge_metrics()); - gaugeMetrics.Clear(); - sortMetricDataByDimensionsValue(gaugePushPersistBefore.gauge_metrics(), &gaugeMetrics); - ASSERT_EQ(gaugeMetrics.data_size(), 2); - data = gaugeMetrics.data(0); - ValidateUidDimension(data.dimensions_in_what(), util::APP_START_OCCURRED, app1Uid); - ASSERT_EQ(data.bucket_info_size(), 1); - ValidateGaugeBucketTimes(data.bucket_info(0), roundedBucketStartNs, roundedUpdateTimeNs, - {(int64_t)(bucketStartTimeNs + 10 * NS_PER_SEC), - (int64_t)(bucketStartTimeNs + 35 * NS_PER_SEC)}); - ASSERT_EQ(data.bucket_info(0).atom_size(), 2); - EXPECT_EQ(data.bucket_info(0).atom(0).app_start_occurred().pkg_name(), "app1"); - EXPECT_EQ(data.bucket_info(0).atom(0).app_start_occurred().activity_start_millis(), 101); - EXPECT_EQ(data.bucket_info(0).atom(1).app_start_occurred().pkg_name(), "app1"); - EXPECT_EQ(data.bucket_info(0).atom(1).app_start_occurred().activity_start_millis(), 102); - - data = gaugeMetrics.data(1); - ValidateUidDimension(data.dimensions_in_what(), util::APP_START_OCCURRED, app2Uid); - ASSERT_EQ(data.bucket_info_size(), 1); - ValidateGaugeBucketTimes(data.bucket_info(0), roundedBucketStartNs, roundedUpdateTimeNs, - {(int64_t)(bucketStartTimeNs + 40 * NS_PER_SEC)}); - ASSERT_EQ(data.bucket_info(0).atom_size(), 1); - EXPECT_EQ(data.bucket_info(0).atom(0).app_start_occurred().pkg_name(), "app2"); - EXPECT_EQ(data.bucket_info(0).atom(0).app_start_occurred().activity_start_millis(), 202); - - // Report from after update. - report = reports.reports(1); - ASSERT_EQ(report.metrics_size(), 4); - // Gauge screen on events FIRST_N_SAMPLES. There were 2. - StatsLogReport gaugeChangeAfter = report.metrics(0); - EXPECT_EQ(gaugeChangeAfter.metric_id(), gaugeChange.id()); - EXPECT_TRUE(gaugeChangeAfter.has_gauge_metrics()); - gaugeMetrics.Clear(); - sortMetricDataByDimensionsValue(gaugeChangeAfter.gauge_metrics(), &gaugeMetrics); - ASSERT_EQ(gaugeMetrics.data_size(), 1); - data = gaugeMetrics.data(0); - ASSERT_EQ(data.bucket_info_size(), 1); - ValidateGaugeBucketTimes(data.bucket_info(0), roundedUpdateTimeNs, roundedBucketEndNs, - {(int64_t)(bucketStartTimeNs + 80 * NS_PER_SEC), - (int64_t)(bucketStartTimeNs + 105 * NS_PER_SEC)}); - ASSERT_EQ(data.bucket_info(0).atom_size(), 2); - EXPECT_EQ(data.bucket_info(0).atom(0).screen_state_changed().state(), - android::view::DISPLAY_STATE_ON); - EXPECT_EQ(data.bucket_info(0).atom(1).screen_state_changed().state(), - android::view::DISPLAY_STATE_ON); - - // Gauge subsystem sleep state, random one sample, no condition. - // Pulled at update time and after the normal bucket end. - StatsLogReport gaugeNewAfter = report.metrics(1); - EXPECT_EQ(gaugeNewAfter.metric_id(), gaugeNew.id()); - EXPECT_TRUE(gaugeNewAfter.has_gauge_metrics()); - gaugeMetrics.Clear(); - sortMetricDataByDimensionsValue(gaugeNewAfter.gauge_metrics(), &gaugeMetrics); - ASSERT_EQ(gaugeMetrics.data_size(), 2); - data = gaugeMetrics.data(0); - ValidateSubsystemSleepDimension(data.dimensions_in_what(), "subsystem_name_1"); - ASSERT_EQ(data.bucket_info_size(), 2); - ValidateGaugeBucketTimes(data.bucket_info(0), roundedUpdateTimeNs, roundedBucketEndNs, - {updateTimeNs}); - ASSERT_EQ(data.bucket_info(0).atom_size(), 1); - EXPECT_EQ(data.bucket_info(0).atom(0).subsystem_sleep_state().time_millis(), 901); - ValidateGaugeBucketTimes(data.bucket_info(1), roundedBucketEndNs, roundedDumpTimeNs, - {(int64_t)(bucketStartTimeNs + bucketSizeNs)}); - ASSERT_EQ(data.bucket_info(1).atom_size(), 1); - EXPECT_EQ(data.bucket_info(1).atom(0).subsystem_sleep_state().time_millis(), 1601); - - data = gaugeMetrics.data(1); - ValidateSubsystemSleepDimension(data.dimensions_in_what(), "subsystem_name_2"); - ASSERT_EQ(data.bucket_info_size(), 2); - ValidateGaugeBucketTimes(data.bucket_info(0), roundedUpdateTimeNs, roundedBucketEndNs, - {updateTimeNs}); - ASSERT_EQ(data.bucket_info(0).atom_size(), 1); - EXPECT_EQ(data.bucket_info(0).atom(0).subsystem_sleep_state().time_millis(), 902); - ValidateGaugeBucketTimes(data.bucket_info(1), roundedBucketEndNs, roundedDumpTimeNs, - {(int64_t)(bucketStartTimeNs + bucketSizeNs)}); - ASSERT_EQ(data.bucket_info(1).atom_size(), 1); - EXPECT_EQ(data.bucket_info(1).atom(0).subsystem_sleep_state().time_millis(), 1602); - - // Gauge app start while app is in the background. App 1 started once, app 2 started twice. - StatsLogReport gaugePushPersistAfter = report.metrics(2); - EXPECT_EQ(gaugePushPersistAfter.metric_id(), gaugePushPersist.id()); - EXPECT_TRUE(gaugePushPersistAfter.has_gauge_metrics()); - gaugeMetrics.Clear(); - sortMetricDataByDimensionsValue(gaugePushPersistAfter.gauge_metrics(), &gaugeMetrics); - ASSERT_EQ(gaugeMetrics.data_size(), 2); - data = gaugeMetrics.data(0); - ValidateUidDimension(data.dimensions_in_what(), util::APP_START_OCCURRED, app1Uid); - ASSERT_EQ(data.bucket_info_size(), 1); - ValidateGaugeBucketTimes(data.bucket_info(0), roundedUpdateTimeNs, roundedBucketEndNs, - {(int64_t)(bucketStartTimeNs + 95 * NS_PER_SEC)}); - ASSERT_EQ(data.bucket_info(0).atom_size(), 1); - EXPECT_EQ(data.bucket_info(0).atom(0).app_start_occurred().pkg_name(), "app1"); - EXPECT_EQ(data.bucket_info(0).atom(0).app_start_occurred().activity_start_millis(), 104); - - data = gaugeMetrics.data(1); - ValidateUidDimension(data.dimensions_in_what(), util::APP_START_OCCURRED, app2Uid); - ASSERT_EQ(data.bucket_info_size(), 1); - ValidateGaugeBucketTimes(data.bucket_info(0), roundedUpdateTimeNs, roundedBucketEndNs, - {(int64_t)(bucketStartTimeNs + 70 * NS_PER_SEC), - (int64_t)(bucketStartTimeNs + 100 * NS_PER_SEC)}); - ASSERT_EQ(data.bucket_info(0).atom_size(), 2); - EXPECT_EQ(data.bucket_info(0).atom(0).app_start_occurred().pkg_name(), "app2"); - EXPECT_EQ(data.bucket_info(0).atom(0).app_start_occurred().activity_start_millis(), 203); - EXPECT_EQ(data.bucket_info(0).atom(1).app_start_occurred().pkg_name(), "app2"); - EXPECT_EQ(data.bucket_info(0).atom(1).app_start_occurred().activity_start_millis(), 204); - - // Gauge subsystem sleep state when screen is on. One pull at update since screen is on then. - StatsLogReport gaugePullPersistAfter = report.metrics(3); - EXPECT_EQ(gaugePullPersistAfter.metric_id(), gaugePullPersist.id()); - EXPECT_TRUE(gaugePullPersistAfter.has_gauge_metrics()); - gaugeMetrics.Clear(); - sortMetricDataByDimensionsValue(gaugePullPersistAfter.gauge_metrics(), &gaugeMetrics); - ASSERT_EQ(gaugeMetrics.data_size(), 2); - data = gaugeMetrics.data(0); - ValidateSubsystemSleepDimension(data.dimensions_in_what(), "subsystem_name_1"); - ASSERT_EQ(data.bucket_info_size(), 1); - ValidateGaugeBucketTimes(data.bucket_info(0), roundedUpdateTimeNs, roundedBucketEndNs, - {updateTimeNs}); - ASSERT_EQ(data.bucket_info(0).atom_size(), 1); - EXPECT_EQ(data.bucket_info(0).atom(0).subsystem_sleep_state().time_millis(), 901); - - data = gaugeMetrics.data(1); - ValidateSubsystemSleepDimension(data.dimensions_in_what(), "subsystem_name_2"); - ASSERT_EQ(data.bucket_info_size(), 1); - ValidateGaugeBucketTimes(data.bucket_info(0), roundedUpdateTimeNs, roundedBucketEndNs, - {updateTimeNs}); - ASSERT_EQ(data.bucket_info(0).atom_size(), 1); - EXPECT_EQ(data.bucket_info(0).atom(0).subsystem_sleep_state().time_millis(), 902); -} - -TEST_F(ConfigUpdateE2eTest, TestValueMetric) { - StatsdConfig config; - config.add_allowed_log_source("AID_ROOT"); - config.add_default_pull_packages("AID_ROOT"); // Fake puller is registered with root. - - AtomMatcher brightnessMatcher = CreateScreenBrightnessChangedAtomMatcher(); - *config.add_atom_matcher() = brightnessMatcher; - AtomMatcher screenOnMatcher = CreateScreenTurnedOnAtomMatcher(); - *config.add_atom_matcher() = screenOnMatcher; - AtomMatcher screenOffMatcher = CreateScreenTurnedOffAtomMatcher(); - *config.add_atom_matcher() = screenOffMatcher; - AtomMatcher batteryPluggedUsbMatcher = CreateBatteryStateUsbMatcher(); - *config.add_atom_matcher() = batteryPluggedUsbMatcher; - AtomMatcher unpluggedMatcher = CreateBatteryStateNoneMatcher(); - *config.add_atom_matcher() = unpluggedMatcher; - AtomMatcher subsystemSleepMatcher = - CreateSimpleAtomMatcher("SubsystemSleep", util::SUBSYSTEM_SLEEP_STATE); - *config.add_atom_matcher() = subsystemSleepMatcher; - - Predicate screenOnPredicate = CreateScreenIsOnPredicate(); - *config.add_predicate() = screenOnPredicate; - Predicate unpluggedPredicate = CreateDeviceUnpluggedPredicate(); - *config.add_predicate() = unpluggedPredicate; - - State screenState = CreateScreenState(); - *config.add_state() = screenState; - - ValueMetric valuePullPersist = - createValueMetric("SubsystemSleepWhileUnpluggedSliceScreen", subsystemSleepMatcher, 4, - unpluggedPredicate.id(), {screenState.id()}); - *valuePullPersist.mutable_dimensions_in_what() = - CreateDimensions(util::SUBSYSTEM_SLEEP_STATE, {1 /* subsystem name */}); - - ValueMetric valuePushPersist = createValueMetric( - "MinScreenBrightnessWhileScreenOn", brightnessMatcher, 1, screenOnPredicate.id(), {}); - valuePushPersist.set_aggregation_type(ValueMetric::MIN); - - ValueMetric valueChange = - createValueMetric("SubsystemSleep", subsystemSleepMatcher, 4, nullopt, {}); - *valueChange.mutable_dimensions_in_what() = - CreateDimensions(util::SUBSYSTEM_SLEEP_STATE, {1 /* subsystem name */}); - - ValueMetric valueRemove = - createValueMetric("AvgScreenBrightness", brightnessMatcher, 1, nullopt, {}); - valueRemove.set_aggregation_type(ValueMetric::AVG); - - *config.add_value_metric() = valuePullPersist; - *config.add_value_metric() = valueRemove; - *config.add_value_metric() = valuePushPersist; - *config.add_value_metric() = valueChange; - - ConfigKey key(123, 987); - uint64_t bucketStartTimeNs = getElapsedRealtimeNs(); - uint64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(TEN_MINUTES) * 1000000LL; - // Config creation triggers pull #1. - sp processor = CreateStatsLogProcessor( - bucketStartTimeNs, bucketStartTimeNs, config, key, - SharedRefBase::make(), util::SUBSYSTEM_SLEEP_STATE); - - // Initialize log events before update. - // ValuePushPersist and ValuePullPersist will skip the bucket due to condition unknown. - std::vector> events; - events.push_back(CreateScreenBrightnessChangedEvent(bucketStartTimeNs + 5 * NS_PER_SEC, 5)); - events.push_back(CreateScreenStateChangedEvent(bucketStartTimeNs + 10 * NS_PER_SEC, - android::view::DISPLAY_STATE_ON)); - events.push_back(CreateScreenBrightnessChangedEvent(bucketStartTimeNs + 15 * NS_PER_SEC, 15)); - events.push_back(CreateBatteryStateChangedEvent( - bucketStartTimeNs + 20 * NS_PER_SEC, - BatteryPluggedStateEnum::BATTERY_PLUGGED_NONE)); // Pull #2. - events.push_back(CreateScreenBrightnessChangedEvent(bucketStartTimeNs + 25 * NS_PER_SEC, 40)); - - // Send log events to StatsLogProcessor. - for (auto& event : events) { - processor->mPullerManager->ForceClearPullerCache(); - processor->OnLogEvent(event.get()); - } - processor->mPullerManager->ForceClearPullerCache(); - - // Do the update. Add matchers/conditions in different order to force indices to change. - StatsdConfig newConfig; - newConfig.add_allowed_log_source("AID_ROOT"); - newConfig.add_default_pull_packages("AID_ROOT"); // Fake puller is registered with root. - - *newConfig.add_atom_matcher() = screenOffMatcher; - *newConfig.add_atom_matcher() = unpluggedMatcher; - *newConfig.add_atom_matcher() = batteryPluggedUsbMatcher; - *newConfig.add_atom_matcher() = subsystemSleepMatcher; - *newConfig.add_atom_matcher() = brightnessMatcher; - *newConfig.add_atom_matcher() = screenOnMatcher; - - *newConfig.add_predicate() = unpluggedPredicate; - *newConfig.add_predicate() = screenOnPredicate; - - *config.add_state() = screenState; - - valueChange.set_condition(screenOnPredicate.id()); - *newConfig.add_value_metric() = valueChange; - ValueMetric valueNew = createValueMetric("MaxScrBrightness", brightnessMatcher, 1, nullopt, {}); - valueNew.set_aggregation_type(ValueMetric::MAX); - *newConfig.add_value_metric() = valueNew; - *newConfig.add_value_metric() = valuePushPersist; - *newConfig.add_value_metric() = valuePullPersist; - - int64_t updateTimeNs = bucketStartTimeNs + 30 * NS_PER_SEC; - // Update pulls valuePullPersist and valueNew. Pull #3. - processor->OnConfigUpdated(updateTimeNs, key, newConfig); - - // Verify puller manager is properly set. - sp pullerManager = processor->mPullerManager; - EXPECT_EQ(pullerManager->mNextPullTimeNs, bucketStartTimeNs + bucketSizeNs); - ASSERT_EQ(pullerManager->mReceivers.size(), 1); - ASSERT_EQ(pullerManager->mReceivers.begin()->second.size(), 2); - - // Send events after the update. Values reset since this is a new bucket. - events.clear(); - events.push_back(CreateScreenBrightnessChangedEvent(bucketStartTimeNs + 35 * NS_PER_SEC, 30)); - events.push_back(CreateScreenStateChangedEvent(bucketStartTimeNs + 40 * NS_PER_SEC, - android::view::DISPLAY_STATE_OFF)); // Pull #4. - events.push_back(CreateScreenBrightnessChangedEvent(bucketStartTimeNs + 45 * NS_PER_SEC, 20)); - events.push_back(CreateBatteryStateChangedEvent( - bucketStartTimeNs + 50 * NS_PER_SEC, - BatteryPluggedStateEnum::BATTERY_PLUGGED_USB)); // Pull #5. - events.push_back(CreateScreenBrightnessChangedEvent(bucketStartTimeNs + 55 * NS_PER_SEC, 25)); - events.push_back(CreateScreenStateChangedEvent(bucketStartTimeNs + 60 * NS_PER_SEC, - android::view::DISPLAY_STATE_ON)); // Pull #6. - events.push_back(CreateBatteryStateChangedEvent( - bucketStartTimeNs + 65 * NS_PER_SEC, - BatteryPluggedStateEnum::BATTERY_PLUGGED_NONE)); // Pull #7. - events.push_back(CreateScreenBrightnessChangedEvent(bucketStartTimeNs + 70 * NS_PER_SEC, 40)); - - // Send log events to StatsLogProcessor. - for (auto& event : events) { - processor->mPullerManager->ForceClearPullerCache(); - processor->OnLogEvent(event.get()); - } - processor->mPullerManager->ForceClearPullerCache(); - - // Pulling alarm arrive, triggering a bucket split. - // Both valuePullPersist and valueChange use the value since both conditions are true. Pull #8. - processor->informPullAlarmFired(bucketStartTimeNs + bucketSizeNs); - processor->OnLogEvent(CreateScreenBrightnessChangedEvent( - bucketStartTimeNs + bucketSizeNs + 5 * NS_PER_SEC, 50) - .get()); - - uint64_t dumpTimeNs = bucketStartTimeNs + bucketSizeNs + 10 * NS_PER_SEC; - ConfigMetricsReportList reports; - vector buffer; - processor->onDumpReport(key, dumpTimeNs, true, true, ADB_DUMP, FAST, &buffer); - EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size())); - backfillDimensionPath(&reports); - backfillStringInReport(&reports); - backfillStartEndTimestamp(&reports); - ASSERT_EQ(reports.reports_size(), 2); - - int64_t roundedBucketStartNs = MillisToNano(NanoToMillis(bucketStartTimeNs)); - int64_t roundedUpdateTimeNs = MillisToNano(NanoToMillis(updateTimeNs)); - int64_t roundedBucketEndNs = MillisToNano(NanoToMillis(bucketStartTimeNs + bucketSizeNs)); - int64_t roundedDumpTimeNs = MillisToNano(NanoToMillis(dumpTimeNs)); - - // Report from before update. - ConfigMetricsReport report = reports.reports(0); - ASSERT_EQ(report.metrics_size(), 4); - // Pull subsystem sleep while unplugged slice screen. Bucket skipped due to condition unknown. - StatsLogReport valuePullPersistBefore = report.metrics(0); - EXPECT_EQ(valuePullPersistBefore.metric_id(), valuePullPersist.id()); - EXPECT_TRUE(valuePullPersistBefore.has_value_metrics()); - ASSERT_EQ(valuePullPersistBefore.value_metrics().data_size(), 0); - ASSERT_EQ(valuePullPersistBefore.value_metrics().skipped_size(), 1); - StatsLogReport::SkippedBuckets skipBucket = valuePullPersistBefore.value_metrics().skipped(0); - EXPECT_EQ(skipBucket.start_bucket_elapsed_nanos(), roundedBucketStartNs); - EXPECT_EQ(skipBucket.end_bucket_elapsed_nanos(), roundedUpdateTimeNs); - ASSERT_EQ(skipBucket.drop_event_size(), 1); - EXPECT_EQ(skipBucket.drop_event(0).drop_reason(), BucketDropReason::CONDITION_UNKNOWN); - - // Average screen brightness. Values were 5, 15, 40. Avg: 20. - StatsLogReport valueRemoveBefore = report.metrics(1); - EXPECT_EQ(valueRemoveBefore.metric_id(), valueRemove.id()); - EXPECT_TRUE(valueRemoveBefore.has_value_metrics()); - StatsLogReport::ValueMetricDataWrapper valueMetrics; - sortMetricDataByDimensionsValue(valueRemoveBefore.value_metrics(), &valueMetrics); - ASSERT_EQ(valueMetrics.data_size(), 1); - ValueMetricData data = valueMetrics.data(0); - EXPECT_FALSE(data.has_dimensions_in_what()); - EXPECT_EQ(data.slice_by_state_size(), 0); - ASSERT_EQ(data.bucket_info_size(), 1); - ValidateValueBucket(data.bucket_info(0), roundedBucketStartNs, roundedUpdateTimeNs, 20, 0); - - // Min screen brightness while screen on. Bucket skipped due to condition unknown. - StatsLogReport valuePushPersistBefore = report.metrics(2); - EXPECT_EQ(valuePushPersistBefore.metric_id(), valuePushPersist.id()); - EXPECT_TRUE(valuePushPersistBefore.has_value_metrics()); - ASSERT_EQ(valuePushPersistBefore.value_metrics().data_size(), 0); - ASSERT_EQ(valuePushPersistBefore.value_metrics().skipped_size(), 1); - skipBucket = valuePushPersistBefore.value_metrics().skipped(0); - EXPECT_EQ(skipBucket.start_bucket_elapsed_nanos(), roundedBucketStartNs); - EXPECT_EQ(skipBucket.end_bucket_elapsed_nanos(), roundedUpdateTimeNs); - ASSERT_EQ(skipBucket.drop_event_size(), 1); - EXPECT_EQ(skipBucket.drop_event(0).drop_reason(), BucketDropReason::CONDITION_UNKNOWN); - - // Pull Subsystem sleep state. Value is Pull #3 (900) - Pull#1 (100). - StatsLogReport valueChangeBefore = report.metrics(3); - EXPECT_EQ(valueChangeBefore.metric_id(), valueChange.id()); - EXPECT_TRUE(valueChangeBefore.has_value_metrics()); - valueMetrics.Clear(); - sortMetricDataByDimensionsValue(valueChangeBefore.value_metrics(), &valueMetrics); - ASSERT_EQ(valueMetrics.data_size(), 2); - data = valueMetrics.data(0); - ValidateSubsystemSleepDimension(data.dimensions_in_what(), "subsystem_name_1"); - ASSERT_EQ(data.bucket_info_size(), 1); - ValidateValueBucket(data.bucket_info(0), roundedBucketStartNs, roundedUpdateTimeNs, 800, 0); - data = valueMetrics.data(1); - ValidateSubsystemSleepDimension(data.dimensions_in_what(), "subsystem_name_2"); - ASSERT_EQ(data.bucket_info_size(), 1); - ValidateValueBucket(data.bucket_info(0), roundedBucketStartNs, roundedUpdateTimeNs, 800, 0); - - // Report from after update. - report = reports.reports(1); - ASSERT_EQ(report.metrics_size(), 4); - // Pull subsystem sleep while screen on. - // Pull#4 (1600) - pull#3 (900) + pull#8 (6400) - pull#6 (3600) - StatsLogReport valueChangeAfter = report.metrics(0); - EXPECT_EQ(valueChangeAfter.metric_id(), valueChange.id()); - EXPECT_TRUE(valueChangeAfter.has_value_metrics()); - valueMetrics.Clear(); - sortMetricDataByDimensionsValue(valueChangeAfter.value_metrics(), &valueMetrics); - int64_t conditionTrueNs = bucketSizeNs - 60 * NS_PER_SEC + 10 * NS_PER_SEC; - ASSERT_EQ(valueMetrics.data_size(), 2); - data = valueMetrics.data(0); - ValidateSubsystemSleepDimension(data.dimensions_in_what(), "subsystem_name_1"); - ASSERT_EQ(data.bucket_info_size(), 1); - ValidateValueBucket(data.bucket_info(0), roundedUpdateTimeNs, roundedBucketEndNs, 3500, - conditionTrueNs); - ASSERT_EQ(valueMetrics.data_size(), 2); - data = valueMetrics.data(1); - ValidateSubsystemSleepDimension(data.dimensions_in_what(), "subsystem_name_2"); - ASSERT_EQ(data.bucket_info_size(), 1); - ValidateValueBucket(data.bucket_info(0), roundedUpdateTimeNs, roundedBucketEndNs, 3500, - conditionTrueNs); - - ASSERT_EQ(valueChangeAfter.value_metrics().skipped_size(), 1); - skipBucket = valueChangeAfter.value_metrics().skipped(0); - EXPECT_EQ(skipBucket.start_bucket_elapsed_nanos(), roundedBucketEndNs); - EXPECT_EQ(skipBucket.end_bucket_elapsed_nanos(), roundedDumpTimeNs); - ASSERT_EQ(skipBucket.drop_event_size(), 1); - EXPECT_EQ(skipBucket.drop_event(0).drop_reason(), BucketDropReason::DUMP_REPORT_REQUESTED); - - // Max screen brightness, no condition. Val is 40 in first bucket, 50 in second. - StatsLogReport valueNewAfter = report.metrics(1); - EXPECT_EQ(valueNewAfter.metric_id(), valueNew.id()); - EXPECT_TRUE(valueNewAfter.has_value_metrics()); - valueMetrics.Clear(); - sortMetricDataByDimensionsValue(valueNewAfter.value_metrics(), &valueMetrics); - ASSERT_EQ(valueMetrics.data_size(), 1); - data = valueMetrics.data(0); - ASSERT_EQ(data.bucket_info_size(), 2); - ValidateValueBucket(data.bucket_info(0), roundedUpdateTimeNs, roundedBucketEndNs, 40, 0); - ValidateValueBucket(data.bucket_info(1), roundedBucketEndNs, roundedDumpTimeNs, 50, 0); - - // Min screen brightness when screen on. Val is 30 in first bucket, 50 in second. - StatsLogReport valuePushPersistAfter = report.metrics(2); - EXPECT_EQ(valuePushPersistAfter.metric_id(), valuePushPersist.id()); - EXPECT_TRUE(valuePushPersistAfter.has_value_metrics()); - valueMetrics.Clear(); - sortMetricDataByDimensionsValue(valuePushPersistAfter.value_metrics(), &valueMetrics); - ASSERT_EQ(valueMetrics.data_size(), 1); - data = valueMetrics.data(0); - ASSERT_EQ(data.bucket_info_size(), 2); - conditionTrueNs = bucketSizeNs - 60 * NS_PER_SEC + 10 * NS_PER_SEC; - ValidateValueBucket(data.bucket_info(0), roundedUpdateTimeNs, roundedBucketEndNs, 30, - conditionTrueNs); - ValidateValueBucket(data.bucket_info(1), roundedBucketEndNs, roundedDumpTimeNs, 50, - 10 * NS_PER_SEC); - - // TODO(b/179725160): fix assertions. - // Subsystem sleep state while unplugged slice screen. - StatsLogReport valuePullPersistAfter = report.metrics(3); - EXPECT_EQ(valuePullPersistAfter.metric_id(), valuePullPersist.id()); - EXPECT_TRUE(valuePullPersistAfter.has_value_metrics()); - valueMetrics.Clear(); - sortMetricDataByDimensionsValue(valuePullPersistAfter.value_metrics(), &valueMetrics); - ASSERT_EQ(valueMetrics.data_size(), 4); - // Name 1, screen OFF. Pull#5 (2500) - pull#4 (1600). - data = valueMetrics.data(0); - conditionTrueNs = 10 * NS_PER_SEC; - ValidateSubsystemSleepDimension(data.dimensions_in_what(), "subsystem_name_1"); - ValidateStateValue(data.slice_by_state(), util::SCREEN_STATE_CHANGED, - android::view::DisplayStateEnum::DISPLAY_STATE_OFF); - ASSERT_EQ(data.bucket_info_size(), 1); - ValidateValueBucket(data.bucket_info(0), roundedUpdateTimeNs, roundedBucketEndNs, 900, -1); - // Name 1, screen ON. Pull#4 (1600) - pull#3 (900) + pull#8 (6400) - pull#7 (4900). - data = valueMetrics.data(1); - conditionTrueNs = 10 * NS_PER_SEC + bucketSizeNs - 65 * NS_PER_SEC; - ValidateSubsystemSleepDimension(data.dimensions_in_what(), "subsystem_name_1"); - ValidateStateValue(data.slice_by_state(), util::SCREEN_STATE_CHANGED, - android::view::DisplayStateEnum::DISPLAY_STATE_ON); - ASSERT_EQ(data.bucket_info_size(), 1); - ValidateValueBucket(data.bucket_info(0), roundedUpdateTimeNs, roundedBucketEndNs, 2200, -1); - // Name 2, screen OFF. Pull#5 (2500) - pull#4 (1600). - data = valueMetrics.data(2); - conditionTrueNs = 10 * NS_PER_SEC; - ValidateSubsystemSleepDimension(data.dimensions_in_what(), "subsystem_name_2"); - ValidateStateValue(data.slice_by_state(), util::SCREEN_STATE_CHANGED, - android::view::DisplayStateEnum::DISPLAY_STATE_OFF); - ASSERT_EQ(data.bucket_info_size(), 1); - ValidateValueBucket(data.bucket_info(0), roundedUpdateTimeNs, roundedBucketEndNs, 900, -1); - // Name 2, screen ON. Pull#4 (1600) - pull#3 (900) + pull#8 (6400) - pull#7 (4900). - data = valueMetrics.data(3); - conditionTrueNs = 10 * NS_PER_SEC + bucketSizeNs - 65 * NS_PER_SEC; - ValidateSubsystemSleepDimension(data.dimensions_in_what(), "subsystem_name_2"); - ValidateStateValue(data.slice_by_state(), util::SCREEN_STATE_CHANGED, - android::view::DisplayStateEnum::DISPLAY_STATE_ON); - ASSERT_EQ(data.bucket_info_size(), 1); - ValidateValueBucket(data.bucket_info(0), roundedUpdateTimeNs, roundedBucketEndNs, 2200, -1); - - ASSERT_EQ(valuePullPersistAfter.value_metrics().skipped_size(), 1); - skipBucket = valuePullPersistAfter.value_metrics().skipped(0); - EXPECT_EQ(skipBucket.start_bucket_elapsed_nanos(), roundedBucketEndNs); - EXPECT_EQ(skipBucket.end_bucket_elapsed_nanos(), roundedDumpTimeNs); - ASSERT_EQ(skipBucket.drop_event_size(), 1); - EXPECT_EQ(skipBucket.drop_event(0).drop_reason(), BucketDropReason::DUMP_REPORT_REQUESTED); -} - -TEST_F(ConfigUpdateE2eTest, TestMetricActivation) { - StatsdConfig config; - config.add_allowed_log_source("AID_ROOT"); - - string immediateTag = "immediate", bootTag = "boot", childTag = "child"; - - AtomMatcher syncStartMatcher = CreateSyncStartAtomMatcher(); - *config.add_atom_matcher() = syncStartMatcher; - - AtomMatcher immediateMatcher = - CreateSimpleAtomMatcher("immediateMatcher", util::WAKELOCK_STATE_CHANGED); - FieldValueMatcher* fvm = - immediateMatcher.mutable_simple_atom_matcher()->add_field_value_matcher(); - fvm->set_field(3); // Tag. - fvm->set_eq_string(immediateTag); - *config.add_atom_matcher() = immediateMatcher; - - AtomMatcher bootMatcher = CreateSimpleAtomMatcher("bootMatcher", util::WAKELOCK_STATE_CHANGED); - fvm = bootMatcher.mutable_simple_atom_matcher()->add_field_value_matcher(); - fvm->set_field(3); // Tag. - fvm->set_eq_string(bootTag); - *config.add_atom_matcher() = bootMatcher; - - AtomMatcher childMatcher = - CreateSimpleAtomMatcher("childMatcher", util::WAKELOCK_STATE_CHANGED); - fvm = childMatcher.mutable_simple_atom_matcher()->add_field_value_matcher(); - fvm->set_field(3); // Tag. - fvm->set_eq_string(childTag); - *config.add_atom_matcher() = childMatcher; - - AtomMatcher acquireMatcher = CreateAcquireWakelockAtomMatcher(); - *config.add_atom_matcher() = acquireMatcher; - - AtomMatcher combinationMatcher; - combinationMatcher.set_id(StringToId("combination")); - AtomMatcher_Combination* combination = combinationMatcher.mutable_combination(); - combination->set_operation(LogicalOperation::OR); - combination->add_matcher(acquireMatcher.id()); - combination->add_matcher(childMatcher.id()); - *config.add_atom_matcher() = combinationMatcher; - - CountMetric immediateMetric = - createCountMetric("ImmediateMetric", syncStartMatcher.id(), nullopt, {}); - CountMetric bootMetric = createCountMetric("BootMetric", syncStartMatcher.id(), nullopt, {}); - CountMetric combinationMetric = - createCountMetric("CombinationMetric", syncStartMatcher.id(), nullopt, {}); - *config.add_count_metric() = immediateMetric; - *config.add_count_metric() = bootMetric; - *config.add_count_metric() = combinationMetric; - - MetricActivation immediateMetricActivation; - immediateMetricActivation.set_metric_id(immediateMetric.id()); - auto eventActivation = immediateMetricActivation.add_event_activation(); - eventActivation->set_activation_type(ActivationType::ACTIVATE_IMMEDIATELY); - eventActivation->set_atom_matcher_id(immediateMatcher.id()); - eventActivation->set_ttl_seconds(60); // One minute. - *config.add_metric_activation() = immediateMetricActivation; - - MetricActivation bootMetricActivation; - bootMetricActivation.set_metric_id(bootMetric.id()); - eventActivation = bootMetricActivation.add_event_activation(); - eventActivation->set_activation_type(ActivationType::ACTIVATE_ON_BOOT); - eventActivation->set_atom_matcher_id(bootMatcher.id()); - eventActivation->set_ttl_seconds(60); // One minute. - *config.add_metric_activation() = bootMetricActivation; - - MetricActivation combinationMetricActivation; - combinationMetricActivation.set_metric_id(combinationMetric.id()); - eventActivation = combinationMetricActivation.add_event_activation(); - eventActivation->set_activation_type(ActivationType::ACTIVATE_IMMEDIATELY); - eventActivation->set_atom_matcher_id(combinationMatcher.id()); - eventActivation->set_ttl_seconds(60); // One minute. - *config.add_metric_activation() = combinationMetricActivation; - - ConfigKey key(123, 987); - uint64_t bucketStartTimeNs = 10000000000; // 0:10 - uint64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(TEN_MINUTES) * 1000000LL; - sp processor = - CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, key); - int uid1 = 55555; - - // Initialize log events before update. - // Counts provided in order of immediate, boot, and combination metric. - std::vector> events; - - events.push_back(CreateSyncStartEvent(bucketStartTimeNs + 5 * NS_PER_SEC, {uid1}, {""}, - "")); // Count: 0, 0, 0. - events.push_back(CreateReleaseWakelockEvent(bucketStartTimeNs + 10 * NS_PER_SEC, {uid1}, {""}, - immediateTag)); // Activate immediate metric. - events.push_back(CreateSyncStartEvent(bucketStartTimeNs + 15 * NS_PER_SEC, {uid1}, {""}, - "")); // Count: 1, 0, 0. - events.push_back(CreateAcquireWakelockEvent(bucketStartTimeNs + 20 * NS_PER_SEC, {uid1}, {""}, - "foo")); // Activate combination metric. - events.push_back(CreateSyncStartEvent(bucketStartTimeNs + 25 * NS_PER_SEC, {uid1}, {""}, - "")); // Count: 2, 0, 1. - events.push_back(CreateReleaseWakelockEvent(bucketStartTimeNs + 30 * NS_PER_SEC, {uid1}, {""}, - bootTag)); // Boot metric pending activation. - events.push_back(CreateSyncStartEvent(bucketStartTimeNs + 35 * NS_PER_SEC, {uid1}, {""}, - "")); // Count: 3, 0, 2. - // Send log events to StatsLogProcessor. - for (auto& event : events) { - processor->OnLogEvent(event.get()); - } - - // Do update. Add matchers/conditions in different order to force indices to change. - StatsdConfig newConfig; - newConfig.add_allowed_log_source("AID_ROOT"); - newConfig.set_hash_strings_in_metric_report(false); // Modify metadata for fun. - - // Change combination matcher, will mean combination metric isn't active after update. - combinationMatcher.mutable_combination()->set_operation(LogicalOperation::AND); - *newConfig.add_atom_matcher() = acquireMatcher; - *newConfig.add_atom_matcher() = bootMatcher; - *newConfig.add_atom_matcher() = combinationMatcher; - *newConfig.add_atom_matcher() = childMatcher; - *newConfig.add_atom_matcher() = syncStartMatcher; - *newConfig.add_atom_matcher() = immediateMatcher; - - *newConfig.add_count_metric() = bootMetric; - *newConfig.add_count_metric() = combinationMetric; - *newConfig.add_count_metric() = immediateMetric; - - *newConfig.add_metric_activation() = bootMetricActivation; - *newConfig.add_metric_activation() = combinationMetricActivation; - *newConfig.add_metric_activation() = immediateMetricActivation; - - int64_t updateTimeNs = bucketStartTimeNs + 40 * NS_PER_SEC; - processor->OnConfigUpdated(updateTimeNs, key, newConfig); - - // The reboot will write to disk again, so sleep for 1 second to avoid this. - // TODO(b/178887128): clean this up. - std::this_thread::sleep_for(1000ms); - // Send event after the update. Counts reset to 0 since this is a new bucket. - processor->OnLogEvent( - CreateSyncStartEvent(bucketStartTimeNs + 45 * NS_PER_SEC, {uid1}, {""}, "") - .get()); // Count: 1, 0, 0. - - // Fake a reboot. Code is from StatsService::informDeviceShutdown. - int64_t shutDownTimeNs = bucketStartTimeNs + 50 * NS_PER_SEC; - processor->WriteDataToDisk(DEVICE_SHUTDOWN, FAST, shutDownTimeNs); - processor->SaveActiveConfigsToDisk(shutDownTimeNs); - processor->SaveMetadataToDisk(getWallClockNs(), shutDownTimeNs); - - // On boot, use StartUp. However, skip config manager for simplicity. - int64_t bootTimeNs = bucketStartTimeNs + 55 * NS_PER_SEC; - processor = CreateStatsLogProcessor(bootTimeNs, bootTimeNs, newConfig, key); - processor->LoadActiveConfigsFromDisk(); - processor->LoadMetadataFromDisk(getWallClockNs(), bootTimeNs); - - // Send events after boot. Counts reset to 0 since this is a new bucket. Boot metric now active. - events.clear(); - events.push_back(CreateSyncStartEvent(bucketStartTimeNs + 60 * NS_PER_SEC, {uid1}, {""}, - "")); // Count: 1, 1, 0. - int64_t deactivationTimeNs = bucketStartTimeNs + 76 * NS_PER_SEC; - events.push_back(CreateScreenStateChangedEvent( - deactivationTimeNs, - android::view::DisplayStateEnum::DISPLAY_STATE_OFF)); // TTLs immediate metric. - events.push_back(CreateSyncStartEvent(bucketStartTimeNs + 80 * NS_PER_SEC, {uid1}, {""}, - "")); // Count: 1, 2, 0. - events.push_back(CreateAcquireWakelockEvent(bucketStartTimeNs + 85 * NS_PER_SEC, {uid1}, {""}, - childTag)); // Activate combination metric. - events.push_back(CreateSyncStartEvent(bucketStartTimeNs + 90 * NS_PER_SEC, {uid1}, {""}, - "")); // Count: 1, 3, 1. - - // Send log events to StatsLogProcessor. - for (auto& event : events) { - processor->OnLogEvent(event.get()); - } - uint64_t dumpTimeNs = bucketStartTimeNs + 90 * NS_PER_SEC; - ConfigMetricsReportList reports; - vector buffer; - processor->onDumpReport(key, dumpTimeNs, true, true, ADB_DUMP, FAST, &buffer); - EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size())); - backfillDimensionPath(&reports); - backfillStringInReport(&reports); - backfillStartEndTimestamp(&reports); - ASSERT_EQ(reports.reports_size(), 3); - - // Report from before update. - ConfigMetricsReport report = reports.reports(0); - EXPECT_EQ(report.last_report_elapsed_nanos(), bucketStartTimeNs); - EXPECT_EQ(report.current_report_elapsed_nanos(), updateTimeNs); - ASSERT_EQ(report.metrics_size(), 3); - // Immediate metric. Count = 3. - StatsLogReport metricReport = report.metrics(0); - EXPECT_EQ(metricReport.metric_id(), immediateMetric.id()); - EXPECT_TRUE(metricReport.is_active()); - EXPECT_TRUE(metricReport.has_count_metrics()); - ASSERT_EQ(metricReport.count_metrics().data_size(), 1); - auto data = metricReport.count_metrics().data(0); - ASSERT_EQ(data.bucket_info_size(), 1); - ValidateCountBucket(data.bucket_info(0), bucketStartTimeNs, updateTimeNs, 3); - - // Boot metric. Count = 0. - metricReport = report.metrics(1); - EXPECT_EQ(metricReport.metric_id(), bootMetric.id()); - EXPECT_FALSE(metricReport.is_active()); - EXPECT_FALSE(metricReport.has_count_metrics()); - - // Combination metric. Count = 2. - metricReport = report.metrics(2); - EXPECT_EQ(metricReport.metric_id(), combinationMetric.id()); - EXPECT_TRUE(metricReport.is_active()); - EXPECT_TRUE(metricReport.has_count_metrics()); - ASSERT_EQ(metricReport.count_metrics().data_size(), 1); - data = metricReport.count_metrics().data(0); - ASSERT_EQ(data.bucket_info_size(), 1); - ValidateCountBucket(data.bucket_info(0), bucketStartTimeNs, updateTimeNs, 2); - - // Report from after update, before boot. - report = reports.reports(1); - EXPECT_EQ(report.last_report_elapsed_nanos(), updateTimeNs); - EXPECT_EQ(report.current_report_elapsed_nanos(), shutDownTimeNs); - ASSERT_EQ(report.metrics_size(), 3); - // Boot metric. Count = 0. - metricReport = report.metrics(0); - EXPECT_EQ(metricReport.metric_id(), bootMetric.id()); - EXPECT_FALSE(metricReport.is_active()); - EXPECT_FALSE(metricReport.has_count_metrics()); - - // Combination metric. Count = 0. - metricReport = report.metrics(1); - EXPECT_EQ(metricReport.metric_id(), combinationMetric.id()); - EXPECT_FALSE(metricReport.is_active()); - EXPECT_FALSE(metricReport.has_count_metrics()); - - // Immediate metric. Count = 1. - metricReport = report.metrics(2); - EXPECT_EQ(metricReport.metric_id(), immediateMetric.id()); - EXPECT_TRUE(metricReport.is_active()); - EXPECT_TRUE(metricReport.has_count_metrics()); - ASSERT_EQ(metricReport.count_metrics().data_size(), 1); - data = metricReport.count_metrics().data(0); - ASSERT_EQ(data.bucket_info_size(), 1); - ValidateCountBucket(data.bucket_info(0), updateTimeNs, shutDownTimeNs, 1); - - // Report from after reboot. - report = reports.reports(2); - EXPECT_EQ(report.last_report_elapsed_nanos(), bootTimeNs); - EXPECT_EQ(report.current_report_elapsed_nanos(), dumpTimeNs); - ASSERT_EQ(report.metrics_size(), 3); - // Boot metric. Count = 3. - metricReport = report.metrics(0); - EXPECT_EQ(metricReport.metric_id(), bootMetric.id()); - EXPECT_TRUE(metricReport.is_active()); - EXPECT_TRUE(metricReport.has_count_metrics()); - ASSERT_EQ(metricReport.count_metrics().data_size(), 1); - data = metricReport.count_metrics().data(0); - ASSERT_EQ(data.bucket_info_size(), 1); - ValidateCountBucket(data.bucket_info(0), bootTimeNs, dumpTimeNs, 3); - - // Combination metric. Count = 1. - metricReport = report.metrics(1); - EXPECT_EQ(metricReport.metric_id(), combinationMetric.id()); - EXPECT_TRUE(metricReport.is_active()); - EXPECT_TRUE(metricReport.has_count_metrics()); - ASSERT_EQ(metricReport.count_metrics().data_size(), 1); - data = metricReport.count_metrics().data(0); - ASSERT_EQ(data.bucket_info_size(), 1); - ValidateCountBucket(data.bucket_info(0), bootTimeNs, dumpTimeNs, 1); - - // Immediate metric. Count = 1. - metricReport = report.metrics(2); - EXPECT_EQ(metricReport.metric_id(), immediateMetric.id()); - EXPECT_FALSE(metricReport.is_active()); - EXPECT_TRUE(metricReport.has_count_metrics()); - ASSERT_EQ(metricReport.count_metrics().data_size(), 1); - data = metricReport.count_metrics().data(0); - ASSERT_EQ(data.bucket_info_size(), 1); - ValidateCountBucket(data.bucket_info(0), bootTimeNs, deactivationTimeNs, 1); -} - -TEST_F(ConfigUpdateE2eTest, TestAnomalyCountMetric) { - StatsdConfig config; - config.add_allowed_log_source("AID_ROOT"); - - AtomMatcher syncStartMatcher = CreateSyncStartAtomMatcher(); - *config.add_atom_matcher() = syncStartMatcher; - AtomMatcher wakelockAcquireMatcher = CreateAcquireWakelockAtomMatcher(); - *config.add_atom_matcher() = wakelockAcquireMatcher; - - CountMetric countWakelock = - createCountMetric("CountWakelock", wakelockAcquireMatcher.id(), nullopt, {}); - *countWakelock.mutable_dimensions_in_what() = - CreateAttributionUidDimensions(util::WAKELOCK_STATE_CHANGED, {Position::FIRST}); - - CountMetric countSync = createCountMetric("CountSync", syncStartMatcher.id(), nullopt, {}); - *countSync.mutable_dimensions_in_what() = - CreateAttributionUidDimensions(util::SYNC_STATE_CHANGED, {Position::FIRST}); - - *config.add_count_metric() = countWakelock; - *config.add_count_metric() = countSync; - - Alert alertPreserve = - createAlert("AlertPreserve", countWakelock.id(), /*buckets=*/2, /*triggerSumGt=*/1); - alertPreserve.set_refractory_period_secs(20); - Alert alertReplace = createAlert("AlertReplace", countSync.id(), 1, 1); - alertReplace.set_refractory_period_secs(1); - Alert alertRemove = createAlert("AlertRemove", countWakelock.id(), 1, 0); - alertRemove.set_refractory_period_secs(1); - *config.add_alert() = alertReplace; - *config.add_alert() = alertPreserve; - *config.add_alert() = alertRemove; - - int preserveSubId = 1, replaceSubId = 2, removeSubId = 3; - Subscription preserveSub = createSubscription("S1", Subscription::ALERT, alertPreserve.id()); - preserveSub.mutable_broadcast_subscriber_details()->set_subscriber_id(preserveSubId); - Subscription replaceSub = createSubscription("S2", Subscription::ALERT, alertReplace.id()); - replaceSub.mutable_broadcast_subscriber_details()->set_subscriber_id(replaceSubId); - Subscription removeSub = createSubscription("S3", Subscription::ALERT, alertRemove.id()); - removeSub.mutable_broadcast_subscriber_details()->set_subscriber_id(removeSubId); - *config.add_subscription() = preserveSub; - *config.add_subscription() = removeSub; - *config.add_subscription() = replaceSub; - - int app1Uid = 123, app2Uid = 456; - vector attributionUids1 = {app1Uid}; - vector attributionTags1 = {"App1"}; - vector attributionUids2 = {app2Uid}; - vector attributionTags2 = {"App2"}; - int64_t configUid = 123, configId = 987; - ConfigKey key(configUid, configId); - - int alertPreserveCount = 0, alertRemoveCount = 0; - StatsDimensionsValueParcel alertPreserveDims; - StatsDimensionsValueParcel alertRemoveDims; - - // The binder calls here will happen synchronously because they are in-process. - shared_ptr preserveBroadcast = - SharedRefBase::make>(); - EXPECT_CALL(*preserveBroadcast, sendSubscriberBroadcast(configUid, configId, preserveSub.id(), - alertPreserve.id(), _, _)) - .Times(2) - .WillRepeatedly( - Invoke([&alertPreserveCount, &alertPreserveDims]( - int64_t, int64_t, int64_t, int64_t, const vector&, - const StatsDimensionsValueParcel& dimensionsValueParcel) { - alertPreserveCount++; - alertPreserveDims = dimensionsValueParcel; - return Status::ok(); - })); - - shared_ptr replaceBroadcast = - SharedRefBase::make>(); - EXPECT_CALL(*replaceBroadcast, sendSubscriberBroadcast(configUid, configId, replaceSub.id(), - alertReplace.id(), _, _)) - .Times(0); - - shared_ptr removeBroadcast = - SharedRefBase::make>(); - EXPECT_CALL(*removeBroadcast, sendSubscriberBroadcast(configUid, configId, removeSub.id(), - alertRemove.id(), _, _)) - .Times(3) - .WillRepeatedly( - Invoke([&alertRemoveCount, &alertRemoveDims]( - int64_t, int64_t, int64_t, int64_t, const vector&, - const StatsDimensionsValueParcel& dimensionsValueParcel) { - alertRemoveCount++; - alertRemoveDims = dimensionsValueParcel; - return Status::ok(); - })); - - SubscriberReporter::getInstance().setBroadcastSubscriber(key, preserveSubId, preserveBroadcast); - SubscriberReporter::getInstance().setBroadcastSubscriber(key, replaceSubId, replaceBroadcast); - SubscriberReporter::getInstance().setBroadcastSubscriber(key, removeSubId, removeBroadcast); - - uint64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(TEN_MINUTES) * 1000000LL; - uint64_t bucketStartTimeNs = 10000000000; // 0:10 - uint64_t bucket2StartTimeNs = bucketStartTimeNs + bucketSizeNs; - sp processor = - CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, key); - - StatsDimensionsValueParcel wlUid1 = - CreateAttributionUidDimensionsValueParcel(util::WAKELOCK_STATE_CHANGED, app1Uid); - StatsDimensionsValueParcel wlUid2 = - CreateAttributionUidDimensionsValueParcel(util::WAKELOCK_STATE_CHANGED, app2Uid); - - processor->OnLogEvent(CreateAcquireWakelockEvent(bucketStartTimeNs + 15 * NS_PER_SEC, - attributionUids1, attributionTags1, "wl1") - .get()); - EXPECT_EQ(alertPreserveCount, 0); - EXPECT_EQ(alertRemoveCount, 1); -// EXPECT_EQ(alertRemoveDims, wlUid1); - - processor->OnLogEvent(CreateAcquireWakelockEvent(bucketStartTimeNs + 20 * NS_PER_SEC, - attributionUids2, attributionTags2, "wl2") - .get()); - EXPECT_EQ(alertPreserveCount, 0); - EXPECT_EQ(alertRemoveCount, 2); -// EXPECT_EQ(alertRemoveDims, wlUid2); - - processor->OnLogEvent(CreateSyncStartEvent(bucket2StartTimeNs + 5 * NS_PER_SEC, - attributionUids1, attributionTags1, "sync1") - .get()); - EXPECT_EQ(alertPreserveCount, 0); - EXPECT_EQ(alertRemoveCount, 2); - - // AlertPreserve enters 30 sec refractory period for uid2. - processor->OnLogEvent(CreateAcquireWakelockEvent(bucket2StartTimeNs + 10 * NS_PER_SEC, - attributionUids2, attributionTags2, "wl2") - .get()); - EXPECT_EQ(alertPreserveCount, 1); -// EXPECT_EQ(alertPreserveDims, wlUid2); - EXPECT_EQ(alertRemoveCount, 3); -// EXPECT_EQ(alertRemoveDims, wlUid2); - - // Do config update. - StatsdConfig newConfig; - newConfig.add_allowed_log_source("AID_ROOT"); - *newConfig.add_atom_matcher() = wakelockAcquireMatcher; - *newConfig.add_atom_matcher() = syncStartMatcher; - - // Clear dims of sync metric, will result in alertReplace getting replaced. - countSync.clear_dimensions_in_what(); - *newConfig.add_count_metric() = countSync; - *newConfig.add_count_metric() = countWakelock; - - // New alert on existing metric. Should get current full bucket, but not history of 1st bucket. - Alert alertNew = createAlert("AlertNew", countWakelock.id(), /*buckets=*/1, /*triggerSumGt=*/1); - *newConfig.add_alert() = alertPreserve; - *newConfig.add_alert() = alertNew; - *newConfig.add_alert() = alertReplace; - - int newSubId = 4; - Subscription newSub = createSubscription("S4", Subscription::ALERT, alertNew.id()); - newSub.mutable_broadcast_subscriber_details()->set_subscriber_id(newSubId); - *newConfig.add_subscription() = newSub; - *newConfig.add_subscription() = replaceSub; - *newConfig.add_subscription() = preserveSub; - - int alertNewCount = 0; - StatsDimensionsValueParcel alertNewDims; - shared_ptr newBroadcast = - SharedRefBase::make>(); - EXPECT_CALL(*newBroadcast, - sendSubscriberBroadcast(configUid, configId, newSub.id(), alertNew.id(), _, _)) - .Times(1) - .WillRepeatedly( - Invoke([&alertNewCount, &alertNewDims]( - int64_t, int64_t, int64_t, int64_t, const vector&, - const StatsDimensionsValueParcel& dimensionsValueParcel) { - alertNewCount++; - alertNewDims = dimensionsValueParcel; - return Status::ok(); - })); - SubscriberReporter::getInstance().setBroadcastSubscriber(key, newSubId, newBroadcast); - - int64_t updateTimeNs = bucket2StartTimeNs + 15 * NS_PER_SEC; - processor->OnConfigUpdated(updateTimeNs, key, newConfig); - - // Within refractory of AlertPreserve, but AlertNew should fire since the full bucket has 2. - processor->OnLogEvent(CreateAcquireWakelockEvent(bucket2StartTimeNs + 20 * NS_PER_SEC, - attributionUids2, attributionTags2, "wl2") - .get()); - EXPECT_EQ(alertPreserveCount, 1); - EXPECT_EQ(alertNewCount, 1); -// EXPECT_EQ(alertNewDims, wlUid2); - - // Wakelock for uid1 fired in first bucket, alert preserve should keep the history and fire. - processor->OnLogEvent(CreateAcquireWakelockEvent(bucket2StartTimeNs + 25 * NS_PER_SEC, - attributionUids1, attributionTags1, "wl1") - .get()); - EXPECT_EQ(alertPreserveCount, 2); -// EXPECT_EQ(alertPreserveDims, wlUid1); - EXPECT_EQ(alertNewCount, 1); - - processor->OnLogEvent(CreateSyncStartEvent(bucket2StartTimeNs + 30 * NS_PER_SEC, - attributionUids1, attributionTags1, "sync1") - .get()); - EXPECT_EQ(alertPreserveCount, 2); - EXPECT_EQ(alertNewCount, 1); - EXPECT_EQ(alertRemoveCount, 3); - - // Clear data so it doesn't stay on disk. - vector buffer; - processor->onDumpReport(key, bucket2StartTimeNs + 100 * NS_PER_SEC, true, true, ADB_DUMP, FAST, - &buffer); - SubscriberReporter::getInstance().unsetBroadcastSubscriber(key, preserveSubId); - SubscriberReporter::getInstance().unsetBroadcastSubscriber(key, replaceSubId); - SubscriberReporter::getInstance().unsetBroadcastSubscriber(key, removeSubId); - SubscriberReporter::getInstance().unsetBroadcastSubscriber(key, newSubId); -} - -TEST_F(ConfigUpdateE2eTest, TestAnomalyDurationMetric) { - StatsdConfig config; - config.add_allowed_log_source("AID_ROOT"); - - AtomMatcher wakelockAcquireMatcher = CreateAcquireWakelockAtomMatcher(); - *config.add_atom_matcher() = wakelockAcquireMatcher; - AtomMatcher wakelockReleaseMatcher = CreateReleaseWakelockAtomMatcher(); - *config.add_atom_matcher() = wakelockReleaseMatcher; - AtomMatcher screenOnMatcher = CreateScreenTurnedOnAtomMatcher(); - *config.add_atom_matcher() = screenOnMatcher; - AtomMatcher screenOffMatcher = CreateScreenTurnedOffAtomMatcher(); - *config.add_atom_matcher() = screenOffMatcher; - - Predicate holdingWakelockPredicate = CreateHoldingWakelockPredicate(); - *holdingWakelockPredicate.mutable_simple_predicate()->mutable_dimensions() = - CreateAttributionUidDimensions(util::WAKELOCK_STATE_CHANGED, {Position::FIRST}); - *config.add_predicate() = holdingWakelockPredicate; - Predicate screenOnPredicate = CreateScreenIsOnPredicate(); - *config.add_predicate() = screenOnPredicate; - - DurationMetric durationWakelock = - createDurationMetric("DurWakelock", holdingWakelockPredicate.id(), nullopt, {}); - *durationWakelock.mutable_dimensions_in_what() = - CreateAttributionUidDimensions(util::WAKELOCK_STATE_CHANGED, {Position::FIRST}); - DurationMetric durationScreen = - createDurationMetric("DurScreen", screenOnPredicate.id(), nullopt, {}); - *config.add_duration_metric() = durationScreen; - *config.add_duration_metric() = durationWakelock; - - Alert alertPreserve = createAlert("AlertPreserve", durationWakelock.id(), /*buckets=*/2, - /*triggerSumGt=*/30 * NS_PER_SEC); - alertPreserve.set_refractory_period_secs(300); - Alert alertReplace = createAlert("AlertReplace", durationScreen.id(), 2, 30 * NS_PER_SEC); - alertReplace.set_refractory_period_secs(1); - Alert alertRemove = createAlert("AlertRemove", durationWakelock.id(), 5, 10 * NS_PER_SEC); - alertRemove.set_refractory_period_secs(1); - *config.add_alert() = alertReplace; - *config.add_alert() = alertPreserve; - *config.add_alert() = alertRemove; - - int preserveSubId = 1, replaceSubId = 2, removeSubId = 3; - Subscription preserveSub = createSubscription("S1", Subscription::ALERT, alertPreserve.id()); - preserveSub.mutable_broadcast_subscriber_details()->set_subscriber_id(preserveSubId); - Subscription replaceSub = createSubscription("S2", Subscription::ALERT, alertReplace.id()); - replaceSub.mutable_broadcast_subscriber_details()->set_subscriber_id(replaceSubId); - Subscription removeSub = createSubscription("S3", Subscription::ALERT, alertRemove.id()); - removeSub.mutable_broadcast_subscriber_details()->set_subscriber_id(removeSubId); - *config.add_subscription() = preserveSub; - *config.add_subscription() = removeSub; - *config.add_subscription() = replaceSub; - - int app1Uid = 123, app2Uid = 456, app3Uid = 789, app4Uid = 111; - vector attributionUids1 = {app1Uid}, attributionUids2 = {app2Uid}, - attributionUids3 = {app3Uid}, attributionUids4 = {app4Uid}; - vector attributionTags1 = {"App1"}, attributionTags2 = {"App2"}, - attributionTags3 = {"App3"}, attributionTags4 = {"App4"}; - - int64_t configUid = 123, configId = 987; - ConfigKey key(configUid, configId); - - int alertPreserveCount = 0, alertRemoveCount = 0; - StatsDimensionsValueParcel alertPreserveDims; - StatsDimensionsValueParcel alertRemoveDims; - - // The binder calls here will happen synchronously because they are in-process. - shared_ptr preserveBroadcast = - SharedRefBase::make>(); - EXPECT_CALL(*preserveBroadcast, sendSubscriberBroadcast(configUid, configId, preserveSub.id(), - alertPreserve.id(), _, _)) - .Times(4) - .WillRepeatedly( - Invoke([&alertPreserveCount, &alertPreserveDims]( - int64_t, int64_t, int64_t, int64_t, const vector&, - const StatsDimensionsValueParcel& dimensionsValueParcel) { - alertPreserveCount++; - alertPreserveDims = dimensionsValueParcel; - return Status::ok(); - })); - - shared_ptr replaceBroadcast = - SharedRefBase::make>(); - EXPECT_CALL(*replaceBroadcast, sendSubscriberBroadcast(configUid, configId, replaceSub.id(), - alertReplace.id(), _, _)) - .Times(0); - - shared_ptr removeBroadcast = - SharedRefBase::make>(); - EXPECT_CALL(*removeBroadcast, sendSubscriberBroadcast(configUid, configId, removeSub.id(), - alertRemove.id(), _, _)) - .Times(6) - .WillRepeatedly( - Invoke([&alertRemoveCount, &alertRemoveDims]( - int64_t, int64_t, int64_t, int64_t, const vector&, - const StatsDimensionsValueParcel& dimensionsValueParcel) { - alertRemoveCount++; - alertRemoveDims = dimensionsValueParcel; - return Status::ok(); - })); - - SubscriberReporter::getInstance().setBroadcastSubscriber(key, preserveSubId, preserveBroadcast); - SubscriberReporter::getInstance().setBroadcastSubscriber(key, replaceSubId, replaceBroadcast); - SubscriberReporter::getInstance().setBroadcastSubscriber(key, removeSubId, removeBroadcast); - - shared_ptr service = SharedRefBase::make(nullptr, nullptr); - sp processor = service->mProcessor; - uint64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(TEN_MINUTES) * 1000000LL; - int64_t bucketStartTimeNs = processor->mTimeBaseNs; - int64_t roundedBucketStartTimeNs = bucketStartTimeNs / NS_PER_SEC * NS_PER_SEC; - uint64_t bucket2StartTimeNs = bucketStartTimeNs + bucketSizeNs; - processor->OnConfigUpdated(bucketStartTimeNs, key, config); - - StatsDimensionsValueParcel wlUid1 = - CreateAttributionUidDimensionsValueParcel(util::WAKELOCK_STATE_CHANGED, app1Uid); - StatsDimensionsValueParcel wlUid2 = - CreateAttributionUidDimensionsValueParcel(util::WAKELOCK_STATE_CHANGED, app2Uid); - StatsDimensionsValueParcel wlUid3 = - CreateAttributionUidDimensionsValueParcel(util::WAKELOCK_STATE_CHANGED, app3Uid); - StatsDimensionsValueParcel wlUid4 = - CreateAttributionUidDimensionsValueParcel(util::WAKELOCK_STATE_CHANGED, app4Uid); - - int64_t eventTimeNs = bucketStartTimeNs + 15 * NS_PER_SEC; - processor->OnLogEvent( - CreateAcquireWakelockEvent(eventTimeNs, attributionUids1, attributionTags1, "wl1") - .get(), - eventTimeNs); - EXPECT_EQ(alertPreserveCount, 0); - EXPECT_EQ(alertRemoveCount, 0); - - eventTimeNs = bucketStartTimeNs + 20 * NS_PER_SEC; - processor->OnLogEvent(CreateScreenStateChangedEvent( - eventTimeNs, android::view::DisplayStateEnum::DISPLAY_STATE_ON) - .get(), - eventTimeNs); - EXPECT_EQ(alertPreserveCount, 0); - EXPECT_EQ(alertRemoveCount, 0); - - // Uid 1 accumulates 15 seconds in bucket #1. - eventTimeNs = bucketStartTimeNs + 30 * NS_PER_SEC; - processor->OnLogEvent( - CreateReleaseWakelockEvent(eventTimeNs, attributionUids1, attributionTags1, "wl1") - .get(), - eventTimeNs); - EXPECT_EQ(alertPreserveCount, 0); - EXPECT_EQ(alertRemoveCount, 1); -// EXPECT_EQ(alertRemoveDims, wlUid1); - - // 20 seconds accumulated in bucket #1. - eventTimeNs = bucketStartTimeNs + 40 * NS_PER_SEC; - processor->OnLogEvent(CreateScreenStateChangedEvent( - eventTimeNs, android::view::DisplayStateEnum::DISPLAY_STATE_OFF) - .get(), - eventTimeNs); - EXPECT_EQ(alertPreserveCount, 0); - EXPECT_EQ(alertRemoveCount, 1); - - eventTimeNs = bucket2StartTimeNs + 2 * NS_PER_SEC; - processor->OnLogEvent( - CreateAcquireWakelockEvent(eventTimeNs, attributionUids4, attributionTags4, "wl4") - .get(), - eventTimeNs); - EXPECT_EQ(alertPreserveCount, 0); - EXPECT_EQ(alertRemoveCount, 1); - - eventTimeNs = bucket2StartTimeNs + 5 * NS_PER_SEC; - processor->OnLogEvent( - CreateAcquireWakelockEvent(eventTimeNs, attributionUids2, attributionTags2, "wl2") - .get(), - eventTimeNs); - EXPECT_EQ(alertPreserveCount, 0); - EXPECT_EQ(alertRemoveCount, 1); - - // Alarm for alert remove for uid 4. - eventTimeNs = bucket2StartTimeNs + 13 * NS_PER_SEC; - processor->OnLogEvent(CreateBatteryStateChangedEvent( - eventTimeNs, BatteryPluggedStateEnum::BATTERY_PLUGGED_USB) - .get(), - eventTimeNs); - EXPECT_EQ(alertPreserveCount, 0); - EXPECT_EQ(alertRemoveCount, 2); -// EXPECT_EQ(alertRemoveDims, wlUid4); - - // Uid3 will be pending at the update. - // Also acts as the alarm for alert remove for uid 2. - eventTimeNs = bucket2StartTimeNs + 30 * NS_PER_SEC; - processor->OnLogEvent( - CreateAcquireWakelockEvent(eventTimeNs, attributionUids3, attributionTags3, "wl3") - .get(), - eventTimeNs); - EXPECT_EQ(alertPreserveCount, 0); - EXPECT_EQ(alertRemoveCount, 3); -// EXPECT_EQ(alertRemoveDims, wlUid2); - - // Alarm for alert preserve for uid 4, enters 5 min refractory period. - eventTimeNs = bucket2StartTimeNs + 33 * NS_PER_SEC; - processor->OnLogEvent(CreateBatteryStateChangedEvent( - eventTimeNs, BatteryPluggedStateEnum::BATTERY_PLUGGED_USB) - .get(), - eventTimeNs); - EXPECT_EQ(alertPreserveCount, 1); -// EXPECT_EQ(alertPreserveDims, wlUid4); - EXPECT_EQ(alertRemoveCount, 3); - - // Uid 2 accumulates 32 seconds in partial bucket before the update. Alert preserve fires. - // Preserve enters 5 min refractory for uid 2. - // Alert remove fires again for uid 2 since the refractory has expired. - eventTimeNs = bucket2StartTimeNs + 37 * NS_PER_SEC; - processor->OnLogEvent( - CreateReleaseWakelockEvent(eventTimeNs, attributionUids2, attributionTags2, "wl2") - .get(), - eventTimeNs); - EXPECT_EQ(alertPreserveCount, 2); -// EXPECT_EQ(alertPreserveDims, wlUid2); - EXPECT_EQ(alertRemoveCount, 4); -// EXPECT_EQ(alertRemoveDims, wlUid2); - - // Alarm for alert remove for uid 3. - eventTimeNs = bucket2StartTimeNs + 41 * NS_PER_SEC; - processor->OnLogEvent(CreateBatteryStateChangedEvent( - eventTimeNs, BatteryPluggedStateEnum::BATTERY_PLUGGED_USB) - .get(), - eventTimeNs); - EXPECT_EQ(alertPreserveCount, 2); - EXPECT_EQ(alertRemoveCount, 5); -// EXPECT_EQ(alertRemoveDims, wlUid3); - - // Release wl for uid 4, has accumulated 41 seconds in partial bucket before update. - // Acts as alarm for uid3 of alert remove. - eventTimeNs = bucket2StartTimeNs + 43 * NS_PER_SEC; - processor->OnLogEvent( - CreateReleaseWakelockEvent(eventTimeNs, attributionUids4, attributionTags4, "wl4") - .get(), - eventTimeNs); - EXPECT_EQ(alertPreserveCount, 2); - EXPECT_EQ(alertRemoveCount, 6); -// EXPECT_EQ(alertRemoveDims, wlUid4); - - // Starts the timer for screen on. - eventTimeNs = bucket2StartTimeNs + 46 * NS_PER_SEC; - processor->OnLogEvent(CreateScreenStateChangedEvent( - eventTimeNs, android::view::DisplayStateEnum::DISPLAY_STATE_ON) - .get(), - eventTimeNs); - EXPECT_EQ(alertPreserveCount, 2); - EXPECT_EQ(alertRemoveCount, 6); - - // Do config update. - StatsdConfig newConfig; - newConfig.add_allowed_log_source("AID_ROOT"); - *newConfig.add_atom_matcher() = wakelockAcquireMatcher; - *newConfig.add_atom_matcher() = screenOffMatcher; - *newConfig.add_atom_matcher() = wakelockReleaseMatcher; - *newConfig.add_atom_matcher() = screenOnMatcher; - *newConfig.add_predicate() = screenOnPredicate; - *newConfig.add_predicate() = holdingWakelockPredicate; - *newConfig.add_duration_metric() = durationWakelock; - *newConfig.add_duration_metric() = durationScreen; - - alertReplace.set_refractory_period_secs(2); // Causes replacement. - // New alert on existing metric. Should get current full bucket, but not history of 1st bucket. - Alert alertNew = createAlert("AlertNew", durationWakelock.id(), /*buckets=*/2, - /*triggerSumGt=*/40 * NS_PER_SEC); - *newConfig.add_alert() = alertPreserve; - *newConfig.add_alert() = alertNew; - *newConfig.add_alert() = alertReplace; - - int newSubId = 4; - Subscription newSub = createSubscription("S4", Subscription::ALERT, alertNew.id()); - newSub.mutable_broadcast_subscriber_details()->set_subscriber_id(newSubId); - *newConfig.add_subscription() = newSub; - *newConfig.add_subscription() = replaceSub; - *newConfig.add_subscription() = preserveSub; - - int alertNewCount = 0; - StatsDimensionsValueParcel alertNewDims; - shared_ptr newBroadcast = - SharedRefBase::make>(); - EXPECT_CALL(*newBroadcast, - sendSubscriberBroadcast(configUid, configId, newSub.id(), alertNew.id(), _, _)) - .Times(3) - .WillRepeatedly( - Invoke([&alertNewCount, &alertNewDims]( - int64_t, int64_t, int64_t, int64_t, const vector&, - const StatsDimensionsValueParcel& dimensionsValueParcel) { - alertNewCount++; - alertNewDims = dimensionsValueParcel; - return Status::ok(); - })); - SubscriberReporter::getInstance().setBroadcastSubscriber(key, newSubId, newBroadcast); - - int64_t updateTimeNs = bucket2StartTimeNs + 50 * NS_PER_SEC; - processor->OnConfigUpdated(updateTimeNs, key, newConfig); - - // Alert preserve will set alarm after the refractory period, but alert new will set it for - // 8 seconds after this event. - // Alert new should fire for uid 4, since it has already accumulated 41s and should fire on the - // first event after the update. - eventTimeNs = bucket2StartTimeNs + 55 * NS_PER_SEC; - processor->OnLogEvent( - CreateAcquireWakelockEvent(eventTimeNs, attributionUids2, attributionTags2, "wl2") - .get(), - eventTimeNs); - EXPECT_EQ(alertPreserveCount, 2); - EXPECT_EQ(alertNewCount, 1); -// EXPECT_EQ(alertNewDims, wlUid4); - - eventTimeNs = bucket2StartTimeNs + 60 * NS_PER_SEC; - // Alert replace doesn't fire because it has lost history. - processor->OnLogEvent(CreateScreenStateChangedEvent( - eventTimeNs, android::view::DisplayStateEnum::DISPLAY_STATE_OFF) - .get(), - eventTimeNs); - EXPECT_EQ(alertPreserveCount, 2); - EXPECT_EQ(alertNewCount, 1); - - // Alert preserve has 15 seconds from 1st bucket, so alert should fire at bucket2Start + 80. - // Serves as alarm for alert new for uid2. - // Also serves as alarm for alert preserve for uid 3, which began at bucket2Start + 30. - eventTimeNs = bucket2StartTimeNs + 65 * NS_PER_SEC; - processor->OnLogEvent( - CreateAcquireWakelockEvent(eventTimeNs, attributionUids1, attributionTags1, "wl1") - .get(), - eventTimeNs); - EXPECT_EQ(alertPreserveCount, 3); -// EXPECT_EQ(alertPreserveDims, wlUid3); - EXPECT_EQ(alertNewCount, 2); -// EXPECT_EQ(alertNewDims, wlUid2); - - // Release wakelock for uid1, causing alert preserve to fire for uid1. - // Also serves as alarm for alert new for uid3. - eventTimeNs = bucket2StartTimeNs + 81 * NS_PER_SEC; - processor->OnLogEvent( - CreateReleaseWakelockEvent(eventTimeNs, attributionUids1, attributionTags1, "wl1") - .get(), - eventTimeNs); - EXPECT_EQ(alertPreserveCount, 4); -// EXPECT_EQ(alertPreserveDims, wlUid1); - EXPECT_EQ(alertNewCount, 3); -// EXPECT_EQ(alertNewDims, wlUid3); - - // Clear data so it doesn't stay on disk. - vector buffer; - processor->onDumpReport(key, bucket2StartTimeNs + 100 * NS_PER_SEC, true, true, ADB_DUMP, FAST, - &buffer); - SubscriberReporter::getInstance().unsetBroadcastSubscriber(key, preserveSubId); - SubscriberReporter::getInstance().unsetBroadcastSubscriber(key, replaceSubId); - SubscriberReporter::getInstance().unsetBroadcastSubscriber(key, removeSubId); - SubscriberReporter::getInstance().unsetBroadcastSubscriber(key, newSubId); -} - -TEST_F(ConfigUpdateE2eTest, TestAlarms) { - StatsdConfig config; - config.add_allowed_log_source("AID_ROOT"); - Alarm alarmPreserve = createAlarm("AlarmPreserve", /*offset*/ 5 * MS_PER_SEC, - /*period*/ TimeUnitToBucketSizeInMillis(ONE_MINUTE)); - Alarm alarmReplace = createAlarm("AlarmReplace", /*offset*/ 1, - /*period*/ TimeUnitToBucketSizeInMillis(FIVE_MINUTES)); - Alarm alarmRemove = createAlarm("AlarmRemove", /*offset*/ 1, - /*period*/ TimeUnitToBucketSizeInMillis(ONE_MINUTE)); - *config.add_alarm() = alarmReplace; - *config.add_alarm() = alarmPreserve; - *config.add_alarm() = alarmRemove; - - int preserveSubId = 1, replaceSubId = 2, removeSubId = 3; - Subscription preserveSub = createSubscription("S1", Subscription::ALARM, alarmPreserve.id()); - preserveSub.mutable_broadcast_subscriber_details()->set_subscriber_id(preserveSubId); - Subscription replaceSub = createSubscription("S2", Subscription::ALARM, alarmReplace.id()); - replaceSub.mutable_broadcast_subscriber_details()->set_subscriber_id(replaceSubId); - Subscription removeSub = createSubscription("S3", Subscription::ALARM, alarmRemove.id()); - removeSub.mutable_broadcast_subscriber_details()->set_subscriber_id(removeSubId); - *config.add_subscription() = preserveSub; - *config.add_subscription() = removeSub; - *config.add_subscription() = replaceSub; - - int64_t configUid = 123, configId = 987; - ConfigKey key(configUid, configId); - - int alarmPreserveCount = 0, alarmReplaceCount = 0, alarmRemoveCount = 0; - - // The binder calls here will happen synchronously because they are in-process. - shared_ptr preserveBroadcast = - SharedRefBase::make>(); - EXPECT_CALL(*preserveBroadcast, sendSubscriberBroadcast(configUid, configId, preserveSub.id(), - alarmPreserve.id(), _, _)) - .Times(4) - .WillRepeatedly([&alarmPreserveCount](int64_t, int64_t, int64_t, int64_t, - const vector&, - const StatsDimensionsValueParcel&) { - alarmPreserveCount++; - return Status::ok(); - }); - - shared_ptr replaceBroadcast = - SharedRefBase::make>(); - EXPECT_CALL(*replaceBroadcast, sendSubscriberBroadcast(configUid, configId, replaceSub.id(), - alarmReplace.id(), _, _)) - .Times(2) - .WillRepeatedly([&alarmReplaceCount](int64_t, int64_t, int64_t, int64_t, - const vector&, - const StatsDimensionsValueParcel&) { - alarmReplaceCount++; - return Status::ok(); - }); - - shared_ptr removeBroadcast = - SharedRefBase::make>(); - EXPECT_CALL(*removeBroadcast, sendSubscriberBroadcast(configUid, configId, removeSub.id(), - alarmRemove.id(), _, _)) - .Times(1) - .WillRepeatedly([&alarmRemoveCount](int64_t, int64_t, int64_t, int64_t, - const vector&, - const StatsDimensionsValueParcel&) { - alarmRemoveCount++; - return Status::ok(); - }); - - SubscriberReporter::getInstance().setBroadcastSubscriber(key, preserveSubId, preserveBroadcast); - SubscriberReporter::getInstance().setBroadcastSubscriber(key, replaceSubId, replaceBroadcast); - SubscriberReporter::getInstance().setBroadcastSubscriber(key, removeSubId, removeBroadcast); - - int64_t startTimeSec = 10; - sp processor = CreateStatsLogProcessor( - startTimeSec * NS_PER_SEC, startTimeSec * NS_PER_SEC, config, key); - - sp alarmMonitor = processor->getPeriodicAlarmMonitor(); - // First alarm is for alarm preserve's offset of 5 seconds. - EXPECT_EQ(alarmMonitor->getRegisteredAlarmTimeSec(), startTimeSec + 5); - - // Alarm fired at 5. AlarmPreserve should fire. - int32_t alarmFiredTimestampSec = startTimeSec + 5; - auto alarmSet = alarmMonitor->popSoonerThan(static_cast(alarmFiredTimestampSec)); - processor->onPeriodicAlarmFired(alarmFiredTimestampSec * NS_PER_SEC, alarmSet); - EXPECT_EQ(alarmPreserveCount, 1); - EXPECT_EQ(alarmReplaceCount, 0); - EXPECT_EQ(alarmRemoveCount, 0); - EXPECT_EQ(alarmMonitor->getRegisteredAlarmTimeSec(), startTimeSec + 60); - - // Alarm fired at 75. AlarmPreserve and AlarmRemove should fire. - alarmFiredTimestampSec = startTimeSec + 75; - alarmSet = alarmMonitor->popSoonerThan(static_cast(alarmFiredTimestampSec)); - processor->onPeriodicAlarmFired(alarmFiredTimestampSec * NS_PER_SEC, alarmSet); - EXPECT_EQ(alarmPreserveCount, 2); - EXPECT_EQ(alarmReplaceCount, 0); - EXPECT_EQ(alarmRemoveCount, 1); - EXPECT_EQ(alarmMonitor->getRegisteredAlarmTimeSec(), startTimeSec + 120); - - // Do config update. - StatsdConfig newConfig; - newConfig.add_allowed_log_source("AID_ROOT"); - - // Change alarm replace's definition. - alarmReplace.set_period_millis(TimeUnitToBucketSizeInMillis(ONE_MINUTE)); - Alarm alarmNew = createAlarm("AlarmNew", /*offset*/ 1, - /*period*/ TimeUnitToBucketSizeInMillis(FIVE_MINUTES)); - *newConfig.add_alarm() = alarmNew; - *newConfig.add_alarm() = alarmPreserve; - *newConfig.add_alarm() = alarmReplace; - - int newSubId = 4; - Subscription newSub = createSubscription("S4", Subscription::ALARM, alarmNew.id()); - newSub.mutable_broadcast_subscriber_details()->set_subscriber_id(newSubId); - *newConfig.add_subscription() = newSub; - *newConfig.add_subscription() = replaceSub; - *newConfig.add_subscription() = preserveSub; - - int alarmNewCount = 0; - shared_ptr newBroadcast = - SharedRefBase::make>(); - EXPECT_CALL(*newBroadcast, - sendSubscriberBroadcast(configUid, configId, newSub.id(), alarmNew.id(), _, _)) - .Times(1) - .WillRepeatedly([&alarmNewCount](int64_t, int64_t, int64_t, int64_t, - const vector&, - const StatsDimensionsValueParcel&) { - alarmNewCount++; - return Status::ok(); - }); - SubscriberReporter::getInstance().setBroadcastSubscriber(key, newSubId, newBroadcast); - - processor->OnConfigUpdated((startTimeSec + 90) * NS_PER_SEC, key, newConfig); - // After the update, the alarm time should remain unchanged since alarm replace now fires every - // minute with no offset. - EXPECT_EQ(alarmMonitor->getRegisteredAlarmTimeSec(), startTimeSec + 120); - - // Alarm fired at 120. AlermReplace should fire. - alarmFiredTimestampSec = startTimeSec + 120; - alarmSet = alarmMonitor->popSoonerThan(static_cast(alarmFiredTimestampSec)); - processor->onPeriodicAlarmFired(alarmFiredTimestampSec * NS_PER_SEC, alarmSet); - EXPECT_EQ(alarmPreserveCount, 2); - EXPECT_EQ(alarmReplaceCount, 1); - EXPECT_EQ(alarmNewCount, 0); - EXPECT_EQ(alarmMonitor->getRegisteredAlarmTimeSec(), startTimeSec + 125); - - // Alarm fired at 130. AlarmPreserve should fire. - alarmFiredTimestampSec = startTimeSec + 130; - alarmSet = alarmMonitor->popSoonerThan(static_cast(alarmFiredTimestampSec)); - processor->onPeriodicAlarmFired(alarmFiredTimestampSec * NS_PER_SEC, alarmSet); - EXPECT_EQ(alarmPreserveCount, 3); - EXPECT_EQ(alarmReplaceCount, 1); - EXPECT_EQ(alarmNewCount, 0); - EXPECT_EQ(alarmMonitor->getRegisteredAlarmTimeSec(), startTimeSec + 180); - - // Alarm fired late at 310. All alerms should fire. - alarmFiredTimestampSec = startTimeSec + 310; - alarmSet = alarmMonitor->popSoonerThan(static_cast(alarmFiredTimestampSec)); - processor->onPeriodicAlarmFired(alarmFiredTimestampSec * NS_PER_SEC, alarmSet); - EXPECT_EQ(alarmPreserveCount, 4); - EXPECT_EQ(alarmReplaceCount, 2); - EXPECT_EQ(alarmNewCount, 1); - EXPECT_EQ(alarmMonitor->getRegisteredAlarmTimeSec(), startTimeSec + 360); - - // Clear subscribers - SubscriberReporter::getInstance().unsetBroadcastSubscriber(key, preserveSubId); - SubscriberReporter::getInstance().unsetBroadcastSubscriber(key, replaceSubId); - SubscriberReporter::getInstance().unsetBroadcastSubscriber(key, removeSubId); - SubscriberReporter::getInstance().unsetBroadcastSubscriber(key, newSubId); -} - -TEST_F(ConfigUpdateE2eTest, TestNewDurationExistingWhat) { - StatsdConfig config; - config.add_allowed_log_source("AID_ROOT"); - *config.add_atom_matcher() = CreateAcquireWakelockAtomMatcher(); - *config.add_atom_matcher() = CreateReleaseWakelockAtomMatcher(); - - Predicate holdingWakelockPredicate = CreateHoldingWakelockPredicate(); - *config.add_predicate() = holdingWakelockPredicate; - - ConfigKey key(123, 987); - uint64_t bucketStartTimeNs = 10000000000; // 0:10 - uint64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(FIVE_MINUTES) * 1000000LL; - sp processor = - CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, key); - - int app1Uid = 123; - vector attributionUids1 = {app1Uid}; - vector attributionTags1 = {"App1"}; - // Create a wakelock acquire, causing the condition to be true. - unique_ptr event = CreateAcquireWakelockEvent(bucketStartTimeNs + 10 * NS_PER_SEC, - attributionUids1, attributionTags1, - "wl1"); // 0:10 - processor->OnLogEvent(event.get()); - - // Add metric. - DurationMetric* durationMetric = config.add_duration_metric(); - durationMetric->set_id(StringToId("WakelockDuration")); - durationMetric->set_what(holdingWakelockPredicate.id()); - durationMetric->set_aggregation_type(DurationMetric::SUM); - durationMetric->set_bucket(FIVE_MINUTES); - - uint64_t updateTimeNs = bucketStartTimeNs + 60 * NS_PER_SEC; // 1:00 - processor->OnConfigUpdated(updateTimeNs, key, config); - - event = CreateReleaseWakelockEvent(bucketStartTimeNs + 80 * NS_PER_SEC, attributionUids1, - attributionTags1, - "wl1"); // 1:20 - processor->OnLogEvent(event.get()); - uint64_t dumpTimeNs = bucketStartTimeNs + 90 * NS_PER_SEC; // 1:30 - ConfigMetricsReportList reports; - vector buffer; - processor->onDumpReport(key, dumpTimeNs, true, true, ADB_DUMP, FAST, &buffer); - EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size())); - backfillDimensionPath(&reports); - backfillStringInReport(&reports); - backfillStartEndTimestamp(&reports); - ASSERT_EQ(reports.reports_size(), 1); - ASSERT_EQ(reports.reports(0).metrics_size(), 1); - EXPECT_TRUE(reports.reports(0).metrics(0).has_duration_metrics()); - - StatsLogReport::DurationMetricDataWrapper metricData; - sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).duration_metrics(), &metricData); - ASSERT_EQ(metricData.data_size(), 1); - DurationMetricData data = metricData.data(0); - ASSERT_EQ(data.bucket_info_size(), 1); - - DurationBucketInfo bucketInfo = data.bucket_info(0); - EXPECT_EQ(bucketInfo.start_bucket_elapsed_nanos(), updateTimeNs); - EXPECT_EQ(bucketInfo.end_bucket_elapsed_nanos(), dumpTimeNs); - EXPECT_EQ(bucketInfo.duration_nanos(), 20 * NS_PER_SEC); -} - -TEST_F(ConfigUpdateE2eTest, TestNewDurationExistingWhatSlicedCondition) { - StatsdConfig config; - config.add_allowed_log_source("AID_ROOT"); - *config.add_atom_matcher() = CreateAcquireWakelockAtomMatcher(); - *config.add_atom_matcher() = CreateReleaseWakelockAtomMatcher(); - *config.add_atom_matcher() = CreateMoveToBackgroundAtomMatcher(); - *config.add_atom_matcher() = CreateMoveToForegroundAtomMatcher(); - - Predicate holdingWakelockPredicate = CreateHoldingWakelockPredicate(); - // The predicate is dimensioning by first attribution node by uid. - *holdingWakelockPredicate.mutable_simple_predicate()->mutable_dimensions() = - CreateAttributionUidDimensions(util::WAKELOCK_STATE_CHANGED, {Position::FIRST}); - *config.add_predicate() = holdingWakelockPredicate; - - Predicate isInBackgroundPredicate = CreateIsInBackgroundPredicate(); - *isInBackgroundPredicate.mutable_simple_predicate()->mutable_dimensions() = - CreateDimensions(util::ACTIVITY_FOREGROUND_STATE_CHANGED, {1 /*uid*/}); - *config.add_predicate() = isInBackgroundPredicate; - - ConfigKey key(123, 987); - uint64_t bucketStartTimeNs = 10000000000; // 0:10 - uint64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(FIVE_MINUTES) * 1000000LL; - sp processor = - CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, key); - - int app1Uid = 123, app2Uid = 456; - vector attributionUids1 = {app1Uid}; - vector attributionTags1 = {"App1"}; - vector attributionUids2 = {app2Uid}; - vector attributionTags2 = {"App2"}; - unique_ptr event = CreateAcquireWakelockEvent(bucketStartTimeNs + 10 * NS_PER_SEC, - attributionUids1, attributionTags1, - "wl1"); // 0:10 - processor->OnLogEvent(event.get()); - event = CreateMoveToBackgroundEvent(bucketStartTimeNs + 22 * NS_PER_SEC, app1Uid); // 0:22 - processor->OnLogEvent(event.get()); - event = CreateAcquireWakelockEvent(bucketStartTimeNs + 35 * NS_PER_SEC, attributionUids2, - attributionTags2, - "wl1"); // 0:35 - processor->OnLogEvent(event.get()); - - // Add metric. - DurationMetric* durationMetric = config.add_duration_metric(); - durationMetric->set_id(StringToId("WakelockDuration")); - durationMetric->set_what(holdingWakelockPredicate.id()); - durationMetric->set_condition(isInBackgroundPredicate.id()); - durationMetric->set_aggregation_type(DurationMetric::SUM); - // The metric is dimensioning by first attribution node and only by uid. - *durationMetric->mutable_dimensions_in_what() = - CreateAttributionUidDimensions(util::WAKELOCK_STATE_CHANGED, {Position::FIRST}); - durationMetric->set_bucket(FIVE_MINUTES); - // Links between wakelock state atom and condition of app is in background. - auto links = durationMetric->add_links(); - links->set_condition(isInBackgroundPredicate.id()); - *links->mutable_fields_in_what() = - CreateAttributionUidDimensions(util::WAKELOCK_STATE_CHANGED, {Position::FIRST}); - *links->mutable_fields_in_condition() = - CreateDimensions(util::ACTIVITY_FOREGROUND_STATE_CHANGED, {1 /*uid*/}); - - uint64_t updateTimeNs = bucketStartTimeNs + 60 * NS_PER_SEC; // 1:00 - processor->OnConfigUpdated(updateTimeNs, key, config); - - event = CreateMoveToBackgroundEvent(bucketStartTimeNs + 73 * NS_PER_SEC, app2Uid); // 1:13 - processor->OnLogEvent(event.get()); - event = CreateReleaseWakelockEvent(bucketStartTimeNs + 84 * NS_PER_SEC, attributionUids1, - attributionTags1, "wl1"); // 1:24 - processor->OnLogEvent(event.get()); - - uint64_t dumpTimeNs = bucketStartTimeNs + 90 * NS_PER_SEC; // 1:30 - ConfigMetricsReportList reports; - vector buffer; - processor->onDumpReport(key, dumpTimeNs, true, true, ADB_DUMP, FAST, &buffer); - EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size())); - backfillDimensionPath(&reports); - backfillStringInReport(&reports); - backfillStartEndTimestamp(&reports); - ASSERT_EQ(reports.reports_size(), 1); - ASSERT_EQ(reports.reports(0).metrics_size(), 1); - EXPECT_TRUE(reports.reports(0).metrics(0).has_duration_metrics()); - - StatsLogReport::DurationMetricDataWrapper metricData; - sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).duration_metrics(), &metricData); - ASSERT_EQ(metricData.data_size(), 2); - - DurationMetricData data = metricData.data(0); - ValidateAttributionUidDimension(data.dimensions_in_what(), util::WAKELOCK_STATE_CHANGED, - app1Uid); - ASSERT_EQ(data.bucket_info_size(), 1); - DurationBucketInfo bucketInfo = data.bucket_info(0); - EXPECT_EQ(bucketInfo.duration_nanos(), 24 * NS_PER_SEC); - - data = metricData.data(1); - ValidateAttributionUidDimension(data.dimensions_in_what(), util::WAKELOCK_STATE_CHANGED, - app2Uid); - ASSERT_EQ(data.bucket_info_size(), 1); - bucketInfo = data.bucket_info(0); - EXPECT_EQ(bucketInfo.duration_nanos(), 17 * NS_PER_SEC); -} - -TEST_F(ConfigUpdateE2eTest, TestNewDurationExistingWhatSlicedState) { - StatsdConfig config; - config.add_allowed_log_source("AID_ROOT"); - *config.add_atom_matcher() = CreateAcquireWakelockAtomMatcher(); - *config.add_atom_matcher() = CreateReleaseWakelockAtomMatcher(); - - Predicate holdingWakelockPredicate = CreateHoldingWakelockPredicate(); - // The predicate is dimensioning by first attribution node by uid. - *holdingWakelockPredicate.mutable_simple_predicate()->mutable_dimensions() = - CreateAttributionUidDimensions(util::WAKELOCK_STATE_CHANGED, {Position::FIRST}); - *config.add_predicate() = holdingWakelockPredicate; - - auto uidProcessState = CreateUidProcessState(); - *config.add_state() = uidProcessState; - - // Count metric. We don't care about this one. Only use it so the StateTracker gets persisted. - CountMetric* countMetric = config.add_count_metric(); - countMetric->set_id(StringToId("Tmp")); - countMetric->set_what(config.atom_matcher(0).id()); - countMetric->add_slice_by_state(uidProcessState.id()); - // The metric is dimensioning by first attribution node and only by uid. - *countMetric->mutable_dimensions_in_what() = - CreateAttributionUidDimensions(util::WAKELOCK_STATE_CHANGED, {Position::FIRST}); - countMetric->set_bucket(FIVE_MINUTES); - auto stateLink = countMetric->add_state_link(); - stateLink->set_state_atom_id(util::UID_PROCESS_STATE_CHANGED); - *stateLink->mutable_fields_in_what() = - CreateAttributionUidDimensions(util::WAKELOCK_STATE_CHANGED, {Position::FIRST}); - *stateLink->mutable_fields_in_state() = - CreateDimensions(util::UID_PROCESS_STATE_CHANGED, {1 /*uid*/}); - config.add_no_report_metric(countMetric->id()); - - ConfigKey key(123, 987); - uint64_t bucketStartTimeNs = 10000000000; // 0:10 - uint64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(FIVE_MINUTES) * 1000000LL; - sp processor = - CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, key); - - int app1Uid = 123, app2Uid = 456; - vector attributionUids1 = {app1Uid}; - vector attributionTags1 = {"App1"}; - vector attributionUids2 = {app2Uid}; - vector attributionTags2 = {"App2"}; - unique_ptr event = CreateUidProcessStateChangedEvent( - bucketStartTimeNs + 10 * NS_PER_SEC, app1Uid, - android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_FOREGROUND); // 0:10 - processor->OnLogEvent(event.get()); - event = CreateAcquireWakelockEvent(bucketStartTimeNs + 22 * NS_PER_SEC, attributionUids1, - attributionTags1, - "wl1"); // 0:22 - processor->OnLogEvent(event.get()); - event = CreateUidProcessStateChangedEvent( - bucketStartTimeNs + 30 * NS_PER_SEC, app2Uid, - android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_FOREGROUND); // 0:30 - processor->OnLogEvent(event.get()); - - // Add metric. - DurationMetric* durationMetric = config.add_duration_metric(); - durationMetric->set_id(StringToId("WakelockDuration")); - durationMetric->set_what(holdingWakelockPredicate.id()); - durationMetric->add_slice_by_state(uidProcessState.id()); - durationMetric->set_aggregation_type(DurationMetric::SUM); - // The metric is dimensioning by first attribution node and only by uid. - *durationMetric->mutable_dimensions_in_what() = - CreateAttributionUidDimensions(util::WAKELOCK_STATE_CHANGED, {Position::FIRST}); - durationMetric->set_bucket(FIVE_MINUTES); - // Links between wakelock state atom and condition of app is in background. - stateLink = durationMetric->add_state_link(); - stateLink->set_state_atom_id(util::UID_PROCESS_STATE_CHANGED); - *stateLink->mutable_fields_in_what() = - CreateAttributionUidDimensions(util::WAKELOCK_STATE_CHANGED, {Position::FIRST}); - *stateLink->mutable_fields_in_state() = - CreateDimensions(util::UID_PROCESS_STATE_CHANGED, {1 /*uid*/}); - - uint64_t updateTimeNs = bucketStartTimeNs + 60 * NS_PER_SEC; // 1:00 - processor->OnConfigUpdated(updateTimeNs, key, config); - - event = CreateAcquireWakelockEvent(bucketStartTimeNs + 72 * NS_PER_SEC, attributionUids2, - attributionTags2, - "wl1"); // 1:13 - processor->OnLogEvent(event.get()); - event = CreateUidProcessStateChangedEvent( - bucketStartTimeNs + 75 * NS_PER_SEC, app1Uid, - android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_BACKGROUND); // 1:15 - processor->OnLogEvent(event.get()); - event = CreateReleaseWakelockEvent(bucketStartTimeNs + 84 * NS_PER_SEC, attributionUids1, - attributionTags1, "wl1"); // 1:24 - processor->OnLogEvent(event.get()); - - uint64_t dumpTimeNs = bucketStartTimeNs + 90 * NS_PER_SEC; // 1:30 - ConfigMetricsReportList reports; - vector buffer; - processor->onDumpReport(key, dumpTimeNs, true, true, ADB_DUMP, FAST, &buffer); - EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size())); - backfillDimensionPath(&reports); - backfillStringInReport(&reports); - backfillStartEndTimestamp(&reports); - ASSERT_EQ(reports.reports_size(), 1); - ASSERT_EQ(reports.reports(0).metrics_size(), 1); - EXPECT_TRUE(reports.reports(0).metrics(0).has_duration_metrics()); - - StatsLogReport::DurationMetricDataWrapper metricData; - sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).duration_metrics(), &metricData); - ASSERT_EQ(metricData.data_size(), 3); - - DurationMetricData data = metricData.data(0); - ValidateAttributionUidDimension(data.dimensions_in_what(), util::WAKELOCK_STATE_CHANGED, - app1Uid); - ValidateStateValue(data.slice_by_state(), util::UID_PROCESS_STATE_CHANGED, - android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_FOREGROUND); - ASSERT_EQ(data.bucket_info_size(), 1); - DurationBucketInfo bucketInfo = data.bucket_info(0); - EXPECT_EQ(bucketInfo.duration_nanos(), 15 * NS_PER_SEC); - - data = metricData.data(1); - ValidateAttributionUidDimension(data.dimensions_in_what(), util::WAKELOCK_STATE_CHANGED, - app1Uid); - ValidateStateValue(data.slice_by_state(), util::UID_PROCESS_STATE_CHANGED, - android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_BACKGROUND); - ASSERT_EQ(data.bucket_info_size(), 1); - bucketInfo = data.bucket_info(0); - EXPECT_EQ(bucketInfo.duration_nanos(), 9 * NS_PER_SEC); - - data = metricData.data(2); - ValidateAttributionUidDimension(data.dimensions_in_what(), util::WAKELOCK_STATE_CHANGED, - app2Uid); - ValidateStateValue(data.slice_by_state(), util::UID_PROCESS_STATE_CHANGED, - android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_FOREGROUND); - ASSERT_EQ(data.bucket_info_size(), 1); - bucketInfo = data.bucket_info(0); - EXPECT_EQ(bucketInfo.duration_nanos(), 18 * NS_PER_SEC); -} - -#else -GTEST_LOG_(INFO) << "This test does nothing.\n"; -#endif - -} // namespace statsd -} // namespace os -} // namespace android diff --git a/bin/tests/e2e/CountMetric_e2e_test.cpp b/bin/tests/e2e/CountMetric_e2e_test.cpp deleted file mode 100644 index 04eb4008..00000000 --- a/bin/tests/e2e/CountMetric_e2e_test.cpp +++ /dev/null @@ -1,901 +0,0 @@ -/* - * Copyright (C) 2019, 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/state/StateManager.h" -#include "src/state/StateTracker.h" -#include "tests/statsd_test_util.h" - -namespace android { -namespace os { -namespace statsd { - -#ifdef __ANDROID__ - -/** - * Tests the initial condition and condition after the first log events for - * count metrics with either a combination condition or simple condition. - * - * Metrics should be initialized with condition kUnknown (given that the - * predicate is using the default InitialValue of UNKNOWN). The condition should - * be updated to either kFalse or kTrue if a condition event is logged for all - * children conditions. - */ -TEST(CountMetricE2eTest, TestInitialConditionChanges) { - // Initialize config. - StatsdConfig config; - config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root. - config.add_default_pull_packages("AID_ROOT"); // Fake puller is registered with root. - - auto syncStartMatcher = CreateSyncStartAtomMatcher(); - *config.add_atom_matcher() = syncStartMatcher; - *config.add_atom_matcher() = CreateScreenTurnedOnAtomMatcher(); - *config.add_atom_matcher() = CreateScreenTurnedOffAtomMatcher(); - *config.add_atom_matcher() = CreateBatteryStateNoneMatcher(); - *config.add_atom_matcher() = CreateBatteryStateUsbMatcher(); - - auto screenOnPredicate = CreateScreenIsOnPredicate(); - *config.add_predicate() = screenOnPredicate; - - auto deviceUnpluggedPredicate = CreateDeviceUnpluggedPredicate(); - *config.add_predicate() = deviceUnpluggedPredicate; - - auto screenOnOnBatteryPredicate = config.add_predicate(); - screenOnOnBatteryPredicate->set_id(StringToId("screenOnOnBatteryPredicate")); - screenOnOnBatteryPredicate->mutable_combination()->set_operation(LogicalOperation::AND); - addPredicateToPredicateCombination(screenOnPredicate, screenOnOnBatteryPredicate); - addPredicateToPredicateCombination(deviceUnpluggedPredicate, screenOnOnBatteryPredicate); - - // CountSyncStartWhileScreenOnOnBattery (CombinationCondition) - CountMetric* countMetric1 = config.add_count_metric(); - countMetric1->set_id(StringToId("CountSyncStartWhileScreenOnOnBattery")); - countMetric1->set_what(syncStartMatcher.id()); - countMetric1->set_condition(screenOnOnBatteryPredicate->id()); - countMetric1->set_bucket(FIVE_MINUTES); - - // CountSyncStartWhileOnBattery (SimpleCondition) - CountMetric* countMetric2 = config.add_count_metric(); - countMetric2->set_id(StringToId("CountSyncStartWhileOnBatterySliceScreen")); - countMetric2->set_what(syncStartMatcher.id()); - countMetric2->set_condition(deviceUnpluggedPredicate.id()); - countMetric2->set_bucket(FIVE_MINUTES); - - const uint64_t bucketStartTimeNs = 10000000000; // 0:10 - const uint64_t bucketSizeNs = - TimeUnitToBucketSizeInMillis(config.count_metric(0).bucket()) * 1000000LL; - int uid = 12345; - int64_t cfgId = 98765; - ConfigKey cfgKey(uid, cfgId); - auto processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey); - - EXPECT_EQ(processor->mMetricsManagers.size(), 1u); - sp metricsManager = processor->mMetricsManagers.begin()->second; - EXPECT_TRUE(metricsManager->isConfigValid()); - EXPECT_EQ(2, metricsManager->mAllMetricProducers.size()); - - sp metricProducer1 = metricsManager->mAllMetricProducers[0]; - sp metricProducer2 = metricsManager->mAllMetricProducers[1]; - - EXPECT_EQ(ConditionState::kUnknown, metricProducer1->mCondition); - EXPECT_EQ(ConditionState::kUnknown, metricProducer2->mCondition); - - auto screenOnEvent = - CreateScreenStateChangedEvent(bucketStartTimeNs + 30, android::view::DISPLAY_STATE_ON); - processor->OnLogEvent(screenOnEvent.get()); - EXPECT_EQ(ConditionState::kUnknown, metricProducer1->mCondition); - EXPECT_EQ(ConditionState::kUnknown, metricProducer2->mCondition); - - auto pluggedUsbEvent = CreateBatteryStateChangedEvent( - bucketStartTimeNs + 50, BatteryPluggedStateEnum::BATTERY_PLUGGED_USB); - processor->OnLogEvent(pluggedUsbEvent.get()); - EXPECT_EQ(ConditionState::kFalse, metricProducer1->mCondition); - EXPECT_EQ(ConditionState::kFalse, metricProducer2->mCondition); - - auto pluggedNoneEvent = CreateBatteryStateChangedEvent( - bucketStartTimeNs + 70, BatteryPluggedStateEnum::BATTERY_PLUGGED_NONE); - processor->OnLogEvent(pluggedNoneEvent.get()); - EXPECT_EQ(ConditionState::kTrue, metricProducer1->mCondition); - EXPECT_EQ(ConditionState::kTrue, metricProducer2->mCondition); -} - -/** -* Test a count metric that has one slice_by_state with no primary fields. -* -* Once the CountMetricProducer is initialized, it has one atom id in -* mSlicedStateAtoms and no entries in mStateGroupMap. - -* One StateTracker tracks the state atom, and it has one listener which is the -* CountMetricProducer that was initialized. -*/ -TEST(CountMetricE2eTest, TestSlicedState) { - // Initialize config. - StatsdConfig config; - config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root. - - auto syncStartMatcher = CreateSyncStartAtomMatcher(); - *config.add_atom_matcher() = syncStartMatcher; - - auto state = CreateScreenState(); - *config.add_state() = state; - - // Create count metric that slices by screen state. - int64_t metricId = 123456; - auto countMetric = config.add_count_metric(); - countMetric->set_id(metricId); - countMetric->set_what(syncStartMatcher.id()); - countMetric->set_bucket(TimeUnit::FIVE_MINUTES); - countMetric->add_slice_by_state(state.id()); - - // Initialize StatsLogProcessor. - const uint64_t bucketStartTimeNs = 10000000000; // 0:10 - const uint64_t bucketSizeNs = - TimeUnitToBucketSizeInMillis(config.count_metric(0).bucket()) * 1000000LL; - int uid = 12345; - int64_t cfgId = 98765; - ConfigKey cfgKey(uid, cfgId); - auto processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey); - - // Check that CountMetricProducer was initialized correctly. - ASSERT_EQ(processor->mMetricsManagers.size(), 1u); - sp metricsManager = processor->mMetricsManagers.begin()->second; - EXPECT_TRUE(metricsManager->isConfigValid()); - ASSERT_EQ(metricsManager->mAllMetricProducers.size(), 1); - sp metricProducer = metricsManager->mAllMetricProducers[0]; - ASSERT_EQ(metricProducer->mSlicedStateAtoms.size(), 1); - EXPECT_EQ(metricProducer->mSlicedStateAtoms.at(0), SCREEN_STATE_ATOM_ID); - ASSERT_EQ(metricProducer->mStateGroupMap.size(), 0); - - // Check that StateTrackers were initialized correctly. - EXPECT_EQ(1, StateManager::getInstance().getStateTrackersCount()); - EXPECT_EQ(1, StateManager::getInstance().getListenersCount(SCREEN_STATE_ATOM_ID)); - - /* - bucket #1 bucket #2 - | 1 2 3 4 5 6 7 8 9 10 (minutes) - |-----------------------------|-----------------------------|-- - x x x x x x (syncStartEvents) - | | (ScreenIsOnEvent) - | | (ScreenIsOffEvent) - | (ScreenDozeEvent) - */ - // Initialize log events - first bucket. - std::vector attributionUids1 = {123}; - std::vector attributionTags1 = {"App1"}; - - std::vector> events; - events.push_back(CreateScreenStateChangedEvent( - bucketStartTimeNs + 50 * NS_PER_SEC, - android::view::DisplayStateEnum::DISPLAY_STATE_ON)); // 1:00 - events.push_back(CreateSyncStartEvent(bucketStartTimeNs + 75 * NS_PER_SEC, attributionUids1, - attributionTags1, "sync_name")); // 1:25 - events.push_back(CreateScreenStateChangedEvent( - bucketStartTimeNs + 150 * NS_PER_SEC, - android::view::DisplayStateEnum::DISPLAY_STATE_OFF)); // 2:40 - events.push_back(CreateScreenStateChangedEvent( - bucketStartTimeNs + 200 * NS_PER_SEC, - android::view::DisplayStateEnum::DISPLAY_STATE_OFF)); // 3:30 - events.push_back(CreateSyncStartEvent(bucketStartTimeNs + 250 * NS_PER_SEC, attributionUids1, - attributionTags1, "sync_name")); // 4:20 - - // Initialize log events - second bucket. - events.push_back(CreateSyncStartEvent(bucketStartTimeNs + 350 * NS_PER_SEC, attributionUids1, - attributionTags1, "sync_name")); // 6:00 - events.push_back(CreateSyncStartEvent(bucketStartTimeNs + 400 * NS_PER_SEC, attributionUids1, - attributionTags1, "sync_name")); // 6:50 - events.push_back(CreateScreenStateChangedEvent( - bucketStartTimeNs + 450 * NS_PER_SEC, - android::view::DisplayStateEnum::DISPLAY_STATE_ON)); // 7:40 - events.push_back(CreateSyncStartEvent(bucketStartTimeNs + 475 * NS_PER_SEC, attributionUids1, - attributionTags1, "sync_name")); // 8:05 - events.push_back(CreateScreenStateChangedEvent( - bucketStartTimeNs + 500 * NS_PER_SEC, - android::view::DisplayStateEnum::DISPLAY_STATE_UNKNOWN)); // 8:30 - events.push_back(CreateSyncStartEvent(bucketStartTimeNs + 520 * NS_PER_SEC, attributionUids1, - attributionTags1, "sync_name")); // 8:50 - - // Send log events to StatsLogProcessor. - for (auto& event : events) { - processor->OnLogEvent(event.get()); - } - - // Check dump report. - vector buffer; - ConfigMetricsReportList reports; - processor->onDumpReport(cfgKey, bucketStartTimeNs + bucketSizeNs * 2 + 1, false, true, ADB_DUMP, - FAST, &buffer); - ASSERT_GT(buffer.size(), 0); - EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size())); - backfillDimensionPath(&reports); - backfillStringInReport(&reports); - backfillStartEndTimestamp(&reports); - - ASSERT_EQ(1, reports.reports_size()); - ASSERT_EQ(1, reports.reports(0).metrics_size()); - EXPECT_TRUE(reports.reports(0).metrics(0).has_count_metrics()); - StatsLogReport::CountMetricDataWrapper countMetrics; - sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).count_metrics(), &countMetrics); - ASSERT_EQ(3, countMetrics.data_size()); - - // For each CountMetricData, check StateValue info is correct and buckets - // have correct counts. - auto data = countMetrics.data(0); - ASSERT_EQ(1, data.slice_by_state_size()); - EXPECT_EQ(SCREEN_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); - EXPECT_TRUE(data.slice_by_state(0).has_value()); - EXPECT_EQ(android::view::DisplayStateEnum::DISPLAY_STATE_UNKNOWN, - data.slice_by_state(0).value()); - ASSERT_EQ(1, data.bucket_info_size()); - EXPECT_EQ(1, data.bucket_info(0).count()); - - data = countMetrics.data(1); - ASSERT_EQ(1, data.slice_by_state_size()); - EXPECT_EQ(SCREEN_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); - EXPECT_TRUE(data.slice_by_state(0).has_value()); - EXPECT_EQ(android::view::DisplayStateEnum::DISPLAY_STATE_OFF, data.slice_by_state(0).value()); - ASSERT_EQ(2, data.bucket_info_size()); - EXPECT_EQ(1, data.bucket_info(0).count()); - EXPECT_EQ(2, data.bucket_info(1).count()); - - data = countMetrics.data(2); - ASSERT_EQ(1, data.slice_by_state_size()); - EXPECT_EQ(SCREEN_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); - EXPECT_TRUE(data.slice_by_state(0).has_value()); - EXPECT_EQ(android::view::DisplayStateEnum::DISPLAY_STATE_ON, data.slice_by_state(0).value()); - ASSERT_EQ(2, data.bucket_info_size()); - EXPECT_EQ(1, data.bucket_info(0).count()); - EXPECT_EQ(1, data.bucket_info(1).count()); -} - -/** - * Test a count metric that has one slice_by_state with a mapping and no - * primary fields. - * - * Once the CountMetricProducer is initialized, it has one atom id in - * mSlicedStateAtoms and has one entry per state value in mStateGroupMap. - * - * One StateTracker tracks the state atom, and it has one listener which is the - * CountMetricProducer that was initialized. - */ -TEST(CountMetricE2eTest, TestSlicedStateWithMap) { - // Initialize config. - StatsdConfig config; - config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root. - - auto syncStartMatcher = CreateSyncStartAtomMatcher(); - *config.add_atom_matcher() = syncStartMatcher; - - int64_t screenOnId = 4444; - int64_t screenOffId = 9876; - auto state = CreateScreenStateWithOnOffMap(screenOnId, screenOffId); - *config.add_state() = state; - - // Create count metric that slices by screen state with on/off map. - int64_t metricId = 123456; - auto countMetric = config.add_count_metric(); - countMetric->set_id(metricId); - countMetric->set_what(syncStartMatcher.id()); - countMetric->set_bucket(TimeUnit::FIVE_MINUTES); - countMetric->add_slice_by_state(state.id()); - - // Initialize StatsLogProcessor. - const uint64_t bucketStartTimeNs = 10000000000; // 0:10 - const uint64_t bucketSizeNs = - TimeUnitToBucketSizeInMillis(config.count_metric(0).bucket()) * 1000000LL; - int uid = 12345; - int64_t cfgId = 98765; - ConfigKey cfgKey(uid, cfgId); - auto processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey); - - // Check that StateTrackers were initialized correctly. - EXPECT_EQ(1, StateManager::getInstance().getStateTrackersCount()); - EXPECT_EQ(1, StateManager::getInstance().getListenersCount(SCREEN_STATE_ATOM_ID)); - - // Check that CountMetricProducer was initialized correctly. - ASSERT_EQ(processor->mMetricsManagers.size(), 1u); - sp metricsManager = processor->mMetricsManagers.begin()->second; - EXPECT_TRUE(metricsManager->isConfigValid()); - ASSERT_EQ(metricsManager->mAllMetricProducers.size(), 1); - sp metricProducer = metricsManager->mAllMetricProducers[0]; - ASSERT_EQ(metricProducer->mSlicedStateAtoms.size(), 1); - EXPECT_EQ(metricProducer->mSlicedStateAtoms.at(0), SCREEN_STATE_ATOM_ID); - ASSERT_EQ(metricProducer->mStateGroupMap.size(), 1); - - StateMap map = state.map(); - for (auto group : map.group()) { - for (auto value : group.value()) { - EXPECT_EQ(metricProducer->mStateGroupMap.at(SCREEN_STATE_ATOM_ID).at(value), - group.group_id()); - } - } - - /* - bucket #1 bucket #2 - | 1 2 3 4 5 6 7 8 9 10 (minutes) - |-----------------------------|-----------------------------|-- - x x x x x x x x x (syncStartEvents) - -----------------------------------------------------------SCREEN_OFF events - | | (ScreenStateOffEvent = 1) - | | (ScreenStateDozeEvent = 3) - | (ScreenStateDozeSuspendEvent = - 4) - -----------------------------------------------------------SCREEN_ON events - | | (ScreenStateOnEvent = 2) - | (ScreenStateVrEvent = 5) - | (ScreenStateOnSuspendEvent = 6) - */ - // Initialize log events - first bucket. - std::vector attributionUids1 = {123}; - std::vector attributionTags1 = {"App1"}; - - std::vector> events; - events.push_back(CreateSyncStartEvent(bucketStartTimeNs + 20 * NS_PER_SEC, attributionUids1, - attributionTags1, "sync_name")); // 0:30 - events.push_back(CreateScreenStateChangedEvent( - bucketStartTimeNs + 30 * NS_PER_SEC, - android::view::DisplayStateEnum::DISPLAY_STATE_DOZE)); // 0:40 - events.push_back(CreateSyncStartEvent(bucketStartTimeNs + 60 * NS_PER_SEC, attributionUids1, - attributionTags1, "sync_name")); // 1:10 - events.push_back(CreateScreenStateChangedEvent( - bucketStartTimeNs + 90 * NS_PER_SEC, - android::view::DisplayStateEnum::DISPLAY_STATE_OFF)); // 1:40 - events.push_back(CreateSyncStartEvent(bucketStartTimeNs + 120 * NS_PER_SEC, attributionUids1, - attributionTags1, "sync_name")); // 2:10 - events.push_back(CreateScreenStateChangedEvent( - bucketStartTimeNs + 150 * NS_PER_SEC, - android::view::DisplayStateEnum::DISPLAY_STATE_ON)); // 2:40 - events.push_back(CreateScreenStateChangedEvent( - bucketStartTimeNs + 180 * NS_PER_SEC, - android::view::DisplayStateEnum::DISPLAY_STATE_VR)); // 3:10 - events.push_back(CreateSyncStartEvent(bucketStartTimeNs + 200 * NS_PER_SEC, attributionUids1, - attributionTags1, "sync_name")); // 3:30 - events.push_back(CreateScreenStateChangedEvent( - bucketStartTimeNs + 210 * NS_PER_SEC, - android::view::DisplayStateEnum::DISPLAY_STATE_DOZE)); // 3:40 - events.push_back(CreateSyncStartEvent(bucketStartTimeNs + 250 * NS_PER_SEC, attributionUids1, - attributionTags1, "sync_name")); // 4:20 - events.push_back(CreateScreenStateChangedEvent( - bucketStartTimeNs + 280 * NS_PER_SEC, - android::view::DisplayStateEnum::DISPLAY_STATE_OFF)); // 4:50 - events.push_back(CreateSyncStartEvent(bucketStartTimeNs + 285 * NS_PER_SEC, attributionUids1, - attributionTags1, "sync_name")); // 4:55 - - // Initialize log events - second bucket. - events.push_back(CreateSyncStartEvent(bucketStartTimeNs + 360 * NS_PER_SEC, attributionUids1, - attributionTags1, "sync_name")); // 6:10 - events.push_back(CreateScreenStateChangedEvent( - bucketStartTimeNs + 390 * NS_PER_SEC, - android::view::DisplayStateEnum::DISPLAY_STATE_ON_SUSPEND)); // 6:40 - events.push_back(CreateScreenStateChangedEvent( - bucketStartTimeNs + 430 * NS_PER_SEC, - android::view::DisplayStateEnum::DISPLAY_STATE_DOZE_SUSPEND)); // 7:20 - events.push_back(CreateSyncStartEvent(bucketStartTimeNs + 440 * NS_PER_SEC, attributionUids1, - attributionTags1, "sync_name")); // 7:30 - events.push_back(CreateScreenStateChangedEvent( - bucketStartTimeNs + 540 * NS_PER_SEC, - android::view::DisplayStateEnum::DISPLAY_STATE_ON)); // 9:10 - events.push_back(CreateSyncStartEvent(bucketStartTimeNs + 570 * NS_PER_SEC, attributionUids1, - attributionTags1, "sync_name")); // 9:40 - - // Send log events to StatsLogProcessor. - for (auto& event : events) { - processor->OnLogEvent(event.get()); - } - - // Check dump report. - vector buffer; - ConfigMetricsReportList reports; - processor->onDumpReport(cfgKey, bucketStartTimeNs + bucketSizeNs * 2 + 1, false, true, ADB_DUMP, - FAST, &buffer); - ASSERT_GT(buffer.size(), 0); - EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size())); - backfillDimensionPath(&reports); - backfillStringInReport(&reports); - backfillStartEndTimestamp(&reports); - - ASSERT_EQ(1, reports.reports_size()); - ASSERT_EQ(1, reports.reports(0).metrics_size()); - EXPECT_TRUE(reports.reports(0).metrics(0).has_count_metrics()); - StatsLogReport::CountMetricDataWrapper countMetrics; - sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).count_metrics(), &countMetrics); - ASSERT_EQ(3, countMetrics.data_size()); - - // For each CountMetricData, check StateValue info is correct and buckets - // have correct counts. - auto data = countMetrics.data(0); - ASSERT_EQ(1, data.slice_by_state_size()); - EXPECT_EQ(SCREEN_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); - EXPECT_TRUE(data.slice_by_state(0).has_value()); - EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, data.slice_by_state(0).value()); - ASSERT_EQ(1, data.bucket_info_size()); - EXPECT_EQ(1, data.bucket_info(0).count()); - - data = countMetrics.data(1); - ASSERT_EQ(1, data.slice_by_state_size()); - EXPECT_EQ(SCREEN_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); - EXPECT_TRUE(data.slice_by_state(0).has_group_id()); - EXPECT_EQ(screenOnId, data.slice_by_state(0).group_id()); - ASSERT_EQ(2, data.bucket_info_size()); - EXPECT_EQ(1, data.bucket_info(0).count()); - EXPECT_EQ(1, data.bucket_info(1).count()); - - data = countMetrics.data(2); - ASSERT_EQ(1, data.slice_by_state_size()); - EXPECT_EQ(SCREEN_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); - EXPECT_TRUE(data.slice_by_state(0).has_group_id()); - EXPECT_EQ(screenOffId, data.slice_by_state(0).group_id()); - ASSERT_EQ(2, data.bucket_info_size()); - EXPECT_EQ(4, data.bucket_info(0).count()); - EXPECT_EQ(2, data.bucket_info(1).count()); -} - -/** -* Test a count metric that has one slice_by_state with a primary field. - -* Once the CountMetricProducer is initialized, it should have one -* MetricStateLink stored. State querying using a non-empty primary key -* should also work as intended. -*/ -TEST(CountMetricE2eTest, TestSlicedStateWithPrimaryFields) { - // Initialize config. - StatsdConfig config; - config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root. - - auto appCrashMatcher = - CreateSimpleAtomMatcher("APP_CRASH_OCCURRED", util::APP_CRASH_OCCURRED); - *config.add_atom_matcher() = appCrashMatcher; - - auto state = CreateUidProcessState(); - *config.add_state() = state; - - // Create count metric that slices by uid process state. - int64_t metricId = 123456; - auto countMetric = config.add_count_metric(); - countMetric->set_id(metricId); - countMetric->set_what(appCrashMatcher.id()); - countMetric->set_bucket(TimeUnit::FIVE_MINUTES); - countMetric->add_slice_by_state(state.id()); - MetricStateLink* stateLink = countMetric->add_state_link(); - stateLink->set_state_atom_id(UID_PROCESS_STATE_ATOM_ID); - auto fieldsInWhat = stateLink->mutable_fields_in_what(); - *fieldsInWhat = CreateDimensions(util::APP_CRASH_OCCURRED, {1 /*uid*/}); - auto fieldsInState = stateLink->mutable_fields_in_state(); - *fieldsInState = CreateDimensions(UID_PROCESS_STATE_ATOM_ID, {1 /*uid*/}); - - // Initialize StatsLogProcessor. - const uint64_t bucketStartTimeNs = 10000000000; // 0:10 - const uint64_t bucketSizeNs = - TimeUnitToBucketSizeInMillis(config.count_metric(0).bucket()) * 1000000LL; - int uid = 12345; - int64_t cfgId = 98765; - ConfigKey cfgKey(uid, cfgId); - auto processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey); - - // Check that StateTrackers were initialized correctly. - EXPECT_EQ(1, StateManager::getInstance().getStateTrackersCount()); - EXPECT_EQ(1, StateManager::getInstance().getListenersCount(UID_PROCESS_STATE_ATOM_ID)); - - // Check that CountMetricProducer was initialized correctly. - ASSERT_EQ(processor->mMetricsManagers.size(), 1u); - sp metricsManager = processor->mMetricsManagers.begin()->second; - EXPECT_TRUE(metricsManager->isConfigValid()); - ASSERT_EQ(metricsManager->mAllMetricProducers.size(), 1); - sp metricProducer = metricsManager->mAllMetricProducers[0]; - ASSERT_EQ(metricProducer->mSlicedStateAtoms.size(), 1); - EXPECT_EQ(metricProducer->mSlicedStateAtoms.at(0), UID_PROCESS_STATE_ATOM_ID); - ASSERT_EQ(metricProducer->mStateGroupMap.size(), 0); - ASSERT_EQ(metricProducer->mMetric2StateLinks.size(), 1); - - /* - NOTE: "1" or "2" represents the uid associated with the state/app crash event - bucket #1 bucket #2 - | 1 2 3 4 5 6 7 8 9 10 - |------------------------|-------------------------|-- - 1 1 1 1 1 2 1 1 2 (AppCrashEvents) - -----------------------------------------------------PROCESS STATE events - 1 2 (TopEvent = 1002) - 1 1 (ForegroundServiceEvent = 1003) - 2 (ImportantBackgroundEvent = 1006) - 1 1 1 (ImportantForegroundEvent = 1005) - - Based on the diagram above, an AppCrashEvent querying for process state value would return: - - StateTracker::kStateUnknown - - Important foreground - - Top - - Important foreground - - Foreground service - - Top (both the app crash and state still have matching uid = 2) - - - Foreground service - - Foreground service - - Important background - */ - // Initialize log events - first bucket. - std::vector> events; - events.push_back( - CreateAppCrashOccurredEvent(bucketStartTimeNs + 20 * NS_PER_SEC, 1 /*uid*/)); // 0:30 - events.push_back(CreateUidProcessStateChangedEvent( - bucketStartTimeNs + 30 * NS_PER_SEC, 1 /*uid*/, - android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_FOREGROUND)); // 0:40 - events.push_back( - CreateAppCrashOccurredEvent(bucketStartTimeNs + 60 * NS_PER_SEC, 1 /*uid*/)); // 1:10 - events.push_back(CreateUidProcessStateChangedEvent( - bucketStartTimeNs + 90 * NS_PER_SEC, 1 /*uid*/, - android::app::ProcessStateEnum::PROCESS_STATE_TOP)); // 1:40 - events.push_back( - CreateAppCrashOccurredEvent(bucketStartTimeNs + 120 * NS_PER_SEC, 1 /*uid*/)); // 2:10 - events.push_back(CreateUidProcessStateChangedEvent( - bucketStartTimeNs + 150 * NS_PER_SEC, 1 /*uid*/, - android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_FOREGROUND)); // 2:40 - events.push_back( - CreateAppCrashOccurredEvent(bucketStartTimeNs + 200 * NS_PER_SEC, 1 /*uid*/)); // 3:30 - events.push_back(CreateUidProcessStateChangedEvent( - bucketStartTimeNs + 210 * NS_PER_SEC, 1 /*uid*/, - android::app::ProcessStateEnum::PROCESS_STATE_FOREGROUND_SERVICE)); // 3:40 - events.push_back( - CreateAppCrashOccurredEvent(bucketStartTimeNs + 250 * NS_PER_SEC, 1 /*uid*/)); // 4:20 - events.push_back(CreateUidProcessStateChangedEvent( - bucketStartTimeNs + 280 * NS_PER_SEC, 2 /*uid*/, - android::app::ProcessStateEnum::PROCESS_STATE_TOP)); // 4:50 - events.push_back( - CreateAppCrashOccurredEvent(bucketStartTimeNs + 285 * NS_PER_SEC, 2 /*uid*/)); // 4:55 - - // Initialize log events - second bucket. - events.push_back( - CreateAppCrashOccurredEvent(bucketStartTimeNs + 360 * NS_PER_SEC, 1 /*uid*/)); // 6:10 - events.push_back(CreateUidProcessStateChangedEvent( - bucketStartTimeNs + 390 * NS_PER_SEC, 1 /*uid*/, - android::app::ProcessStateEnum::PROCESS_STATE_FOREGROUND_SERVICE)); // 6:40 - events.push_back(CreateUidProcessStateChangedEvent( - bucketStartTimeNs + 430 * NS_PER_SEC, 2 /*uid*/, - android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_BACKGROUND)); // 7:20 - events.push_back( - CreateAppCrashOccurredEvent(bucketStartTimeNs + 440 * NS_PER_SEC, 1 /*uid*/)); // 7:30 - events.push_back(CreateUidProcessStateChangedEvent( - bucketStartTimeNs + 540 * NS_PER_SEC, 1 /*uid*/, - android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_FOREGROUND)); // 9:10 - events.push_back( - CreateAppCrashOccurredEvent(bucketStartTimeNs + 570 * NS_PER_SEC, 2 /*uid*/)); // 9:40 - - // Send log events to StatsLogProcessor. - for (auto& event : events) { - processor->OnLogEvent(event.get()); - } - - // Check dump report. - vector buffer; - ConfigMetricsReportList reports; - processor->onDumpReport(cfgKey, bucketStartTimeNs + bucketSizeNs * 2 + 1, false, true, ADB_DUMP, - FAST, &buffer); - ASSERT_GT(buffer.size(), 0); - EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size())); - backfillDimensionPath(&reports); - backfillStringInReport(&reports); - backfillStartEndTimestamp(&reports); - - ASSERT_EQ(1, reports.reports_size()); - ASSERT_EQ(1, reports.reports(0).metrics_size()); - EXPECT_TRUE(reports.reports(0).metrics(0).has_count_metrics()); - StatsLogReport::CountMetricDataWrapper countMetrics; - sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).count_metrics(), &countMetrics); - ASSERT_EQ(5, countMetrics.data_size()); - - // For each CountMetricData, check StateValue info is correct and buckets - // have correct counts. - auto data = countMetrics.data(0); - ASSERT_EQ(1, data.slice_by_state_size()); - EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); - EXPECT_TRUE(data.slice_by_state(0).has_value()); - EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, data.slice_by_state(0).value()); - ASSERT_EQ(1, data.bucket_info_size()); - EXPECT_EQ(1, data.bucket_info(0).count()); - - data = countMetrics.data(1); - ASSERT_EQ(1, data.slice_by_state_size()); - EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); - EXPECT_TRUE(data.slice_by_state(0).has_value()); - EXPECT_EQ(android::app::PROCESS_STATE_TOP, data.slice_by_state(0).value()); - ASSERT_EQ(1, data.bucket_info_size()); - EXPECT_EQ(2, data.bucket_info(0).count()); - - data = countMetrics.data(2); - ASSERT_EQ(1, data.slice_by_state_size()); - EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); - EXPECT_TRUE(data.slice_by_state(0).has_value()); - EXPECT_EQ(android::app::PROCESS_STATE_FOREGROUND_SERVICE, data.slice_by_state(0).value()); - ASSERT_EQ(2, data.bucket_info_size()); - EXPECT_EQ(1, data.bucket_info(0).count()); - EXPECT_EQ(2, data.bucket_info(1).count()); - - data = countMetrics.data(3); - ASSERT_EQ(1, data.slice_by_state_size()); - EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); - EXPECT_TRUE(data.slice_by_state(0).has_value()); - EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_FOREGROUND, data.slice_by_state(0).value()); - ASSERT_EQ(1, data.bucket_info_size()); - EXPECT_EQ(2, data.bucket_info(0).count()); - - data = countMetrics.data(4); - ASSERT_EQ(1, data.slice_by_state_size()); - EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); - EXPECT_TRUE(data.slice_by_state(0).has_value()); - EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND, data.slice_by_state(0).value()); - ASSERT_EQ(1, data.bucket_info_size()); - EXPECT_EQ(1, data.bucket_info(0).count()); -} - -TEST(CountMetricE2eTest, TestMultipleSlicedStates) { - // Initialize config. - StatsdConfig config; - config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root. - - auto appCrashMatcher = - CreateSimpleAtomMatcher("APP_CRASH_OCCURRED", util::APP_CRASH_OCCURRED); - *config.add_atom_matcher() = appCrashMatcher; - - int64_t screenOnId = 4444; - int64_t screenOffId = 9876; - auto state1 = CreateScreenStateWithOnOffMap(screenOnId, screenOffId); - *config.add_state() = state1; - auto state2 = CreateUidProcessState(); - *config.add_state() = state2; - - // Create count metric that slices by screen state with on/off map and - // slices by uid process state. - int64_t metricId = 123456; - auto countMetric = config.add_count_metric(); - countMetric->set_id(metricId); - countMetric->set_what(appCrashMatcher.id()); - countMetric->set_bucket(TimeUnit::FIVE_MINUTES); - countMetric->add_slice_by_state(state1.id()); - countMetric->add_slice_by_state(state2.id()); - MetricStateLink* stateLink = countMetric->add_state_link(); - stateLink->set_state_atom_id(UID_PROCESS_STATE_ATOM_ID); - auto fieldsInWhat = stateLink->mutable_fields_in_what(); - *fieldsInWhat = CreateDimensions(util::APP_CRASH_OCCURRED, {1 /*uid*/}); - auto fieldsInState = stateLink->mutable_fields_in_state(); - *fieldsInState = CreateDimensions(UID_PROCESS_STATE_ATOM_ID, {1 /*uid*/}); - - // Initialize StatsLogProcessor. - const uint64_t bucketStartTimeNs = 10000000000; // 0:10 - const uint64_t bucketSizeNs = - TimeUnitToBucketSizeInMillis(config.count_metric(0).bucket()) * 1000000LL; - int uid = 12345; - int64_t cfgId = 98765; - ConfigKey cfgKey(uid, cfgId); - auto processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey); - - // Check that StateTrackers were properly initialized. - EXPECT_EQ(2, StateManager::getInstance().getStateTrackersCount()); - EXPECT_EQ(1, StateManager::getInstance().getListenersCount(SCREEN_STATE_ATOM_ID)); - EXPECT_EQ(1, StateManager::getInstance().getListenersCount(UID_PROCESS_STATE_ATOM_ID)); - - // Check that CountMetricProducer was initialized correctly. - ASSERT_EQ(processor->mMetricsManagers.size(), 1u); - sp metricsManager = processor->mMetricsManagers.begin()->second; - EXPECT_TRUE(metricsManager->isConfigValid()); - ASSERT_EQ(metricsManager->mAllMetricProducers.size(), 1); - sp metricProducer = metricsManager->mAllMetricProducers[0]; - ASSERT_EQ(metricProducer->mSlicedStateAtoms.size(), 2); - EXPECT_EQ(metricProducer->mSlicedStateAtoms.at(0), SCREEN_STATE_ATOM_ID); - EXPECT_EQ(metricProducer->mSlicedStateAtoms.at(1), UID_PROCESS_STATE_ATOM_ID); - ASSERT_EQ(metricProducer->mStateGroupMap.size(), 1); - ASSERT_EQ(metricProducer->mMetric2StateLinks.size(), 1); - - StateMap map = state1.map(); - for (auto group : map.group()) { - for (auto value : group.value()) { - EXPECT_EQ(metricProducer->mStateGroupMap.at(SCREEN_STATE_ATOM_ID).at(value), - group.group_id()); - } - } - - /* - bucket #1 bucket #2 - | 1 2 3 4 5 6 7 8 9 10 (minutes) - |------------------------|------------------------|-- - 1 1 1 1 1 2 1 1 2 (AppCrashEvents) - ---------------------------------------------------SCREEN_OFF events - | | (ScreenOffEvent = 1) - | | (ScreenDozeEvent = 3) - ---------------------------------------------------SCREEN_ON events - | | (ScreenOnEvent = 2) - | (ScreenOnSuspendEvent = 6) - ---------------------------------------------------PROCESS STATE events - 1 2 (TopEvent = 1002) - 1 (ForegroundServiceEvent = 1003) - 2 (ImportantBackgroundEvent = 1006) - 1 1 1 (ImportantForegroundEvent = 1005) - - Based on the diagram above, Screen State / Process State pairs for each - AppCrashEvent are: - - StateTracker::kStateUnknown / important foreground - - off / important foreground - - off / Top - - on / important foreground - - off / important foreground - - off / top - - - off / important foreground - - off / foreground service - - on / important background - - */ - // Initialize log events - first bucket. - std::vector> events; - events.push_back(CreateUidProcessStateChangedEvent( - bucketStartTimeNs + 5 * NS_PER_SEC, 1 /*uid*/, - android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_FOREGROUND)); // 0:15 - events.push_back( - CreateAppCrashOccurredEvent(bucketStartTimeNs + 20 * NS_PER_SEC, 1 /*uid*/)); // 0:30 - events.push_back(CreateScreenStateChangedEvent( - bucketStartTimeNs + 30 * NS_PER_SEC, - android::view::DisplayStateEnum::DISPLAY_STATE_DOZE)); // 0:40 - events.push_back( - CreateAppCrashOccurredEvent(bucketStartTimeNs + 60 * NS_PER_SEC, 1 /*uid*/)); // 1:10 - events.push_back(CreateUidProcessStateChangedEvent( - bucketStartTimeNs + 90 * NS_PER_SEC, 1 /*uid*/, - android::app::ProcessStateEnum::PROCESS_STATE_TOP)); // 1:40 - events.push_back(CreateScreenStateChangedEvent( - bucketStartTimeNs + 90 * NS_PER_SEC, - android::view::DisplayStateEnum::DISPLAY_STATE_OFF)); // 1:40 - events.push_back( - CreateAppCrashOccurredEvent(bucketStartTimeNs + 120 * NS_PER_SEC, 1 /*uid*/)); // 2:10 - events.push_back(CreateUidProcessStateChangedEvent( - bucketStartTimeNs + 150 * NS_PER_SEC, 1 /*uid*/, - android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_FOREGROUND)); // 2:40 - events.push_back(CreateScreenStateChangedEvent( - bucketStartTimeNs + 160 * NS_PER_SEC, - android::view::DisplayStateEnum::DISPLAY_STATE_ON)); // 2:50 - events.push_back( - CreateAppCrashOccurredEvent(bucketStartTimeNs + 200 * NS_PER_SEC, 1 /*uid*/)); // 3:30 - events.push_back(CreateScreenStateChangedEvent( - bucketStartTimeNs + 210 * NS_PER_SEC, - android::view::DisplayStateEnum::DISPLAY_STATE_DOZE)); // 3:40 - events.push_back( - CreateAppCrashOccurredEvent(bucketStartTimeNs + 250 * NS_PER_SEC, 1 /*uid*/)); // 4:20 - events.push_back(CreateUidProcessStateChangedEvent( - bucketStartTimeNs + 280 * NS_PER_SEC, 2 /*uid*/, - android::app::ProcessStateEnum::PROCESS_STATE_TOP)); // 4:50 - events.push_back( - CreateAppCrashOccurredEvent(bucketStartTimeNs + 285 * NS_PER_SEC, 2 /*uid*/)); // 4:55 - - // Initialize log events - second bucket. - events.push_back( - CreateAppCrashOccurredEvent(bucketStartTimeNs + 360 * NS_PER_SEC, 1 /*uid*/)); // 6:10 - events.push_back(CreateUidProcessStateChangedEvent( - bucketStartTimeNs + 380 * NS_PER_SEC, 1 /*uid*/, - android::app::ProcessStateEnum::PROCESS_STATE_FOREGROUND_SERVICE)); // 6:30 - events.push_back(CreateScreenStateChangedEvent( - bucketStartTimeNs + 390 * NS_PER_SEC, - android::view::DisplayStateEnum::DISPLAY_STATE_ON_SUSPEND)); // 6:40 - events.push_back(CreateUidProcessStateChangedEvent( - bucketStartTimeNs + 420 * NS_PER_SEC, 2 /*uid*/, - android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_BACKGROUND)); // 7:10 - events.push_back(CreateScreenStateChangedEvent( - bucketStartTimeNs + 440 * NS_PER_SEC, - android::view::DisplayStateEnum::DISPLAY_STATE_OFF)); // 7:30 - events.push_back( - CreateAppCrashOccurredEvent(bucketStartTimeNs + 450 * NS_PER_SEC, 1 /*uid*/)); // 7:40 - events.push_back(CreateScreenStateChangedEvent( - bucketStartTimeNs + 520 * NS_PER_SEC, - android::view::DisplayStateEnum::DISPLAY_STATE_ON)); // 8:50 - events.push_back(CreateUidProcessStateChangedEvent( - bucketStartTimeNs + 540 * NS_PER_SEC, 1 /*uid*/, - android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_FOREGROUND)); // 9:10 - events.push_back( - CreateAppCrashOccurredEvent(bucketStartTimeNs + 570 * NS_PER_SEC, 2 /*uid*/)); // 9:40 - - // Send log events to StatsLogProcessor. - for (auto& event : events) { - processor->OnLogEvent(event.get()); - } - - // Check dump report. - vector buffer; - ConfigMetricsReportList reports; - processor->onDumpReport(cfgKey, bucketStartTimeNs + bucketSizeNs * 2 + 1, false, true, ADB_DUMP, - FAST, &buffer); - ASSERT_GT(buffer.size(), 0); - EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size())); - backfillDimensionPath(&reports); - backfillStringInReport(&reports); - backfillStartEndTimestamp(&reports); - - ASSERT_EQ(1, reports.reports_size()); - ASSERT_EQ(1, reports.reports(0).metrics_size()); - EXPECT_TRUE(reports.reports(0).metrics(0).has_count_metrics()); - StatsLogReport::CountMetricDataWrapper countMetrics; - sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).count_metrics(), &countMetrics); - ASSERT_EQ(6, countMetrics.data_size()); - - // For each CountMetricData, check StateValue info is correct and buckets - // have correct counts. - auto data = countMetrics.data(0); - ASSERT_EQ(2, data.slice_by_state_size()); - EXPECT_EQ(SCREEN_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); - EXPECT_TRUE(data.slice_by_state(0).has_value()); - EXPECT_EQ(-1, data.slice_by_state(0).value()); - EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(1).atom_id()); - EXPECT_TRUE(data.slice_by_state(1).has_value()); - EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_FOREGROUND, data.slice_by_state(1).value()); - ASSERT_EQ(1, data.bucket_info_size()); - EXPECT_EQ(1, data.bucket_info(0).count()); - - data = countMetrics.data(1); - ASSERT_EQ(2, data.slice_by_state_size()); - EXPECT_EQ(SCREEN_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); - EXPECT_TRUE(data.slice_by_state(0).has_group_id()); - EXPECT_EQ(screenOnId, data.slice_by_state(0).group_id()); - EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(1).atom_id()); - EXPECT_TRUE(data.slice_by_state(1).has_value()); - EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_FOREGROUND, data.slice_by_state(1).value()); - ASSERT_EQ(1, data.bucket_info_size()); - EXPECT_EQ(1, data.bucket_info(0).count()); - - data = countMetrics.data(2); - ASSERT_EQ(2, data.slice_by_state_size()); - EXPECT_EQ(SCREEN_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); - EXPECT_TRUE(data.slice_by_state(0).has_group_id()); - EXPECT_EQ(screenOnId, data.slice_by_state(0).group_id()); - EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(1).atom_id()); - EXPECT_TRUE(data.slice_by_state(1).has_value()); - EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND, data.slice_by_state(1).value()); - ASSERT_EQ(1, data.bucket_info_size()); - EXPECT_EQ(1, data.bucket_info(0).count()); - - data = countMetrics.data(3); - ASSERT_EQ(2, data.slice_by_state_size()); - EXPECT_EQ(SCREEN_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); - EXPECT_TRUE(data.slice_by_state(0).has_group_id()); - EXPECT_EQ(screenOffId, data.slice_by_state(0).group_id()); - EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(1).atom_id()); - EXPECT_TRUE(data.slice_by_state(1).has_value()); - EXPECT_EQ(android::app::PROCESS_STATE_TOP, data.slice_by_state(1).value()); - ASSERT_EQ(1, data.bucket_info_size()); - EXPECT_EQ(2, data.bucket_info(0).count()); - - data = countMetrics.data(4); - ASSERT_EQ(2, data.slice_by_state_size()); - EXPECT_EQ(SCREEN_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); - EXPECT_TRUE(data.slice_by_state(0).has_group_id()); - EXPECT_EQ(screenOffId, data.slice_by_state(0).group_id()); - EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(1).atom_id()); - EXPECT_TRUE(data.slice_by_state(1).has_value()); - EXPECT_EQ(android::app::PROCESS_STATE_FOREGROUND_SERVICE, data.slice_by_state(1).value()); - ASSERT_EQ(1, data.bucket_info_size()); - EXPECT_EQ(1, data.bucket_info(0).count()); - - data = countMetrics.data(5); - ASSERT_EQ(2, data.slice_by_state_size()); - EXPECT_EQ(SCREEN_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); - EXPECT_TRUE(data.slice_by_state(0).has_group_id()); - EXPECT_EQ(screenOffId, data.slice_by_state(0).group_id()); - EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(1).atom_id()); - EXPECT_TRUE(data.slice_by_state(1).has_value()); - EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_FOREGROUND, data.slice_by_state(1).value()); - ASSERT_EQ(2, data.bucket_info_size()); - EXPECT_EQ(2, data.bucket_info(0).count()); - EXPECT_EQ(1, data.bucket_info(1).count()); -} - -} // namespace statsd -} // namespace os -} // namespace android -#else -GTEST_LOG_(INFO) << "This test does nothing.\n"; -#endif diff --git a/bin/tests/e2e/DurationMetric_e2e_test.cpp b/bin/tests/e2e/DurationMetric_e2e_test.cpp deleted file mode 100644 index 8a4c9627..00000000 --- a/bin/tests/e2e/DurationMetric_e2e_test.cpp +++ /dev/null @@ -1,1571 +0,0 @@ -// Copyright (C) 2019 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 - -#include "src/StatsLogProcessor.h" -#include "src/state/StateTracker.h" -#include "src/stats_log_util.h" -#include "tests/statsd_test_util.h" - -namespace android { -namespace os { -namespace statsd { - -#ifdef __ANDROID__ - -TEST(DurationMetricE2eTest, TestOneBucket) { - StatsdConfig config; - config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root. - - auto screenOnMatcher = CreateScreenTurnedOnAtomMatcher(); - auto screenOffMatcher = CreateScreenTurnedOffAtomMatcher(); - *config.add_atom_matcher() = screenOnMatcher; - *config.add_atom_matcher() = screenOffMatcher; - - auto durationPredicate = CreateScreenIsOnPredicate(); - *config.add_predicate() = durationPredicate; - - int64_t metricId = 123456; - auto durationMetric = config.add_duration_metric(); - durationMetric->set_id(metricId); - durationMetric->set_what(durationPredicate.id()); - durationMetric->set_bucket(FIVE_MINUTES); - durationMetric->set_aggregation_type(DurationMetric_AggregationType_SUM); - - const int64_t baseTimeNs = 0; // 0:00 - const int64_t configAddedTimeNs = baseTimeNs + 1 * NS_PER_SEC; // 0:01 - const int64_t bucketSizeNs = - TimeUnitToBucketSizeInMillis(config.duration_metric(0).bucket()) * 1000LL * 1000LL; - - int uid = 12345; - int64_t cfgId = 98765; - ConfigKey cfgKey(uid, cfgId); - - auto processor = CreateStatsLogProcessor(baseTimeNs, configAddedTimeNs, config, cfgKey); - - ASSERT_EQ(processor->mMetricsManagers.size(), 1u); - sp metricsManager = processor->mMetricsManagers.begin()->second; - EXPECT_TRUE(metricsManager->isConfigValid()); - ASSERT_EQ(metricsManager->mAllMetricProducers.size(), 1); - sp metricProducer = metricsManager->mAllMetricProducers[0]; - EXPECT_TRUE(metricsManager->isActive()); - EXPECT_TRUE(metricProducer->mIsActive); - - std::unique_ptr event; - - // Screen is off at start of bucket. - event = CreateScreenStateChangedEvent(configAddedTimeNs, - android::view::DISPLAY_STATE_OFF); // 0:01 - processor->OnLogEvent(event.get()); - - // Turn screen on. - const int64_t durationStartNs = configAddedTimeNs + 10 * NS_PER_SEC; // 0:11 - event = CreateScreenStateChangedEvent(durationStartNs, android::view::DISPLAY_STATE_ON); - processor->OnLogEvent(event.get()); - - // Turn off screen 30 seconds after turning on. - const int64_t durationEndNs = durationStartNs + 30 * NS_PER_SEC; // 0:41 - event = CreateScreenStateChangedEvent(durationEndNs, android::view::DISPLAY_STATE_OFF); - processor->OnLogEvent(event.get()); - - event = CreateScreenBrightnessChangedEvent(durationEndNs + 1 * NS_PER_SEC, 64); // 0:42 - processor->OnLogEvent(event.get()); - - ConfigMetricsReportList reports; - vector buffer; - processor->onDumpReport(cfgKey, configAddedTimeNs + bucketSizeNs + 1 * NS_PER_SEC, false, true, - ADB_DUMP, FAST, &buffer); // 5:01 - EXPECT_TRUE(buffer.size() > 0); - EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size())); - backfillDimensionPath(&reports); - backfillStartEndTimestamp(&reports); - ASSERT_EQ(1, reports.reports_size()); - ASSERT_EQ(1, reports.reports(0).metrics_size()); - EXPECT_EQ(metricId, reports.reports(0).metrics(0).metric_id()); - EXPECT_TRUE(reports.reports(0).metrics(0).has_duration_metrics()); - - StatsLogReport::DurationMetricDataWrapper durationMetrics; - sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).duration_metrics(), - &durationMetrics); - ASSERT_EQ(1, durationMetrics.data_size()); - - DurationMetricData data = durationMetrics.data(0); - ASSERT_EQ(1, data.bucket_info_size()); - EXPECT_EQ(durationEndNs - durationStartNs, data.bucket_info(0).duration_nanos()); - EXPECT_EQ(configAddedTimeNs, data.bucket_info(0).start_bucket_elapsed_nanos()); - EXPECT_EQ(baseTimeNs + bucketSizeNs, data.bucket_info(0).end_bucket_elapsed_nanos()); -} - -TEST(DurationMetricE2eTest, TestTwoBuckets) { - StatsdConfig config; - config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root. - - auto screenOnMatcher = CreateScreenTurnedOnAtomMatcher(); - auto screenOffMatcher = CreateScreenTurnedOffAtomMatcher(); - *config.add_atom_matcher() = screenOnMatcher; - *config.add_atom_matcher() = screenOffMatcher; - - auto durationPredicate = CreateScreenIsOnPredicate(); - *config.add_predicate() = durationPredicate; - - int64_t metricId = 123456; - auto durationMetric = config.add_duration_metric(); - durationMetric->set_id(metricId); - durationMetric->set_what(durationPredicate.id()); - durationMetric->set_bucket(FIVE_MINUTES); - durationMetric->set_aggregation_type(DurationMetric_AggregationType_SUM); - - const int64_t baseTimeNs = 0; // 0:00 - const int64_t configAddedTimeNs = baseTimeNs + 1 * NS_PER_SEC; // 0:01 - const int64_t bucketSizeNs = - TimeUnitToBucketSizeInMillis(config.duration_metric(0).bucket()) * 1000LL * 1000LL; - - int uid = 12345; - int64_t cfgId = 98765; - ConfigKey cfgKey(uid, cfgId); - - auto processor = CreateStatsLogProcessor(baseTimeNs, configAddedTimeNs, config, cfgKey); - - ASSERT_EQ(processor->mMetricsManagers.size(), 1u); - sp metricsManager = processor->mMetricsManagers.begin()->second; - EXPECT_TRUE(metricsManager->isConfigValid()); - ASSERT_EQ(metricsManager->mAllMetricProducers.size(), 1); - sp metricProducer = metricsManager->mAllMetricProducers[0]; - EXPECT_TRUE(metricsManager->isActive()); - EXPECT_TRUE(metricProducer->mIsActive); - - std::unique_ptr event; - - // Screen is off at start of bucket. - event = CreateScreenStateChangedEvent(configAddedTimeNs, - android::view::DISPLAY_STATE_OFF); // 0:01 - processor->OnLogEvent(event.get()); - - // Turn screen on. - const int64_t durationStartNs = configAddedTimeNs + 10 * NS_PER_SEC; // 0:11 - event = CreateScreenStateChangedEvent(durationStartNs, android::view::DISPLAY_STATE_ON); - processor->OnLogEvent(event.get()); - - // Turn off screen 30 seconds after turning on. - const int64_t durationEndNs = durationStartNs + 30 * NS_PER_SEC; // 0:41 - event = CreateScreenStateChangedEvent(durationEndNs, android::view::DISPLAY_STATE_OFF); - processor->OnLogEvent(event.get()); - - event = CreateScreenBrightnessChangedEvent(durationEndNs + 1 * NS_PER_SEC, 64); // 0:42 - processor->OnLogEvent(event.get()); - - ConfigMetricsReportList reports; - vector buffer; - processor->onDumpReport(cfgKey, configAddedTimeNs + 2 * bucketSizeNs + 1 * NS_PER_SEC, false, - true, ADB_DUMP, FAST, &buffer); // 10:01 - EXPECT_TRUE(buffer.size() > 0); - EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size())); - backfillDimensionPath(&reports); - backfillStartEndTimestamp(&reports); - ASSERT_EQ(1, reports.reports_size()); - ASSERT_EQ(1, reports.reports(0).metrics_size()); - EXPECT_EQ(metricId, reports.reports(0).metrics(0).metric_id()); - EXPECT_TRUE(reports.reports(0).metrics(0).has_duration_metrics()); - - StatsLogReport::DurationMetricDataWrapper durationMetrics; - sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).duration_metrics(), - &durationMetrics); - ASSERT_EQ(1, durationMetrics.data_size()); - - DurationMetricData data = durationMetrics.data(0); - ASSERT_EQ(1, data.bucket_info_size()); - - auto bucketInfo = data.bucket_info(0); - EXPECT_EQ(0, bucketInfo.bucket_num()); - EXPECT_EQ(durationEndNs - durationStartNs, bucketInfo.duration_nanos()); - EXPECT_EQ(configAddedTimeNs, bucketInfo.start_bucket_elapsed_nanos()); - EXPECT_EQ(baseTimeNs + bucketSizeNs, bucketInfo.end_bucket_elapsed_nanos()); -} - -TEST(DurationMetricE2eTest, TestWithActivation) { - StatsdConfig config; - config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root. - - auto screenOnMatcher = CreateScreenTurnedOnAtomMatcher(); - auto screenOffMatcher = CreateScreenTurnedOffAtomMatcher(); - auto crashMatcher = CreateProcessCrashAtomMatcher(); - *config.add_atom_matcher() = screenOnMatcher; - *config.add_atom_matcher() = screenOffMatcher; - *config.add_atom_matcher() = crashMatcher; - - auto durationPredicate = CreateScreenIsOnPredicate(); - *config.add_predicate() = durationPredicate; - - int64_t metricId = 123456; - auto durationMetric = config.add_duration_metric(); - durationMetric->set_id(metricId); - durationMetric->set_what(durationPredicate.id()); - durationMetric->set_bucket(FIVE_MINUTES); - durationMetric->set_aggregation_type(DurationMetric_AggregationType_SUM); - - auto metric_activation1 = config.add_metric_activation(); - metric_activation1->set_metric_id(metricId); - auto event_activation1 = metric_activation1->add_event_activation(); - event_activation1->set_atom_matcher_id(crashMatcher.id()); - event_activation1->set_ttl_seconds(30); // 30 secs. - - const int64_t bucketStartTimeNs = 10000000000; - const int64_t bucketSizeNs = - TimeUnitToBucketSizeInMillis(config.duration_metric(0).bucket()) * 1000LL * 1000LL; - - int uid = 12345; - int64_t cfgId = 98765; - ConfigKey cfgKey(uid, cfgId); - - sp m = new UidMap(); - sp pullerManager = new StatsPullerManager(); - sp anomalyAlarmMonitor; - sp subscriberAlarmMonitor; - vector activeConfigsBroadcast; - - int broadcastCount = 0; - StatsLogProcessor processor( - m, pullerManager, anomalyAlarmMonitor, subscriberAlarmMonitor, bucketStartTimeNs, - [](const ConfigKey& key) { return true; }, - [&uid, &broadcastCount, &activeConfigsBroadcast](const int& broadcastUid, - const vector& activeConfigs) { - broadcastCount++; - EXPECT_EQ(broadcastUid, uid); - activeConfigsBroadcast.clear(); - activeConfigsBroadcast.insert(activeConfigsBroadcast.end(), activeConfigs.begin(), - activeConfigs.end()); - return true; - }); - - processor.OnConfigUpdated(bucketStartTimeNs, cfgKey, config); // 0:00 - - ASSERT_EQ(processor.mMetricsManagers.size(), 1u); - sp metricsManager = processor.mMetricsManagers.begin()->second; - EXPECT_TRUE(metricsManager->isConfigValid()); - ASSERT_EQ(metricsManager->mAllMetricProducers.size(), 1); - sp metricProducer = metricsManager->mAllMetricProducers[0]; - auto& eventActivationMap = metricProducer->mEventActivationMap; - - EXPECT_FALSE(metricsManager->isActive()); - EXPECT_FALSE(metricProducer->mIsActive); - ASSERT_EQ(eventActivationMap.size(), 1u); - EXPECT_TRUE(eventActivationMap.find(2) != eventActivationMap.end()); - EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kNotActive); - EXPECT_EQ(eventActivationMap[2]->start_ns, 0); - EXPECT_EQ(eventActivationMap[2]->ttl_ns, event_activation1->ttl_seconds() * NS_PER_SEC); - - std::unique_ptr event; - - // Turn screen off. - event = CreateScreenStateChangedEvent(bucketStartTimeNs + 2 * NS_PER_SEC, - android::view::DISPLAY_STATE_OFF); // 0:02 - processor.OnLogEvent(event.get(), bucketStartTimeNs + 2 * NS_PER_SEC); - - // Turn screen on. - const int64_t durationStartNs = bucketStartTimeNs + 5 * NS_PER_SEC; // 0:05 - event = CreateScreenStateChangedEvent(durationStartNs, android::view::DISPLAY_STATE_ON); - processor.OnLogEvent(event.get(), durationStartNs); - - // Activate metric. - const int64_t activationStartNs = bucketStartTimeNs + 5 * NS_PER_SEC; // 0:10 - const int64_t activationEndNs = - activationStartNs + event_activation1->ttl_seconds() * NS_PER_SEC; // 0:40 - event = CreateAppCrashEvent(activationStartNs, 111); - processor.OnLogEvent(event.get(), activationStartNs); - EXPECT_TRUE(metricsManager->isActive()); - EXPECT_TRUE(metricProducer->mIsActive); - EXPECT_EQ(broadcastCount, 1); - ASSERT_EQ(activeConfigsBroadcast.size(), 1); - EXPECT_EQ(activeConfigsBroadcast[0], cfgId); - EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kActive); - EXPECT_EQ(eventActivationMap[2]->start_ns, activationStartNs); - EXPECT_EQ(eventActivationMap[2]->ttl_ns, event_activation1->ttl_seconds() * NS_PER_SEC); - - // Expire activation. - const int64_t expirationNs = activationEndNs + 7 * NS_PER_SEC; - event = CreateScreenBrightnessChangedEvent(expirationNs, 64); // 0:47 - processor.OnLogEvent(event.get(), expirationNs); - EXPECT_FALSE(metricsManager->isActive()); - EXPECT_FALSE(metricProducer->mIsActive); - EXPECT_EQ(broadcastCount, 2); - ASSERT_EQ(activeConfigsBroadcast.size(), 0); - ASSERT_EQ(eventActivationMap.size(), 1u); - EXPECT_TRUE(eventActivationMap.find(2) != eventActivationMap.end()); - EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kNotActive); - EXPECT_EQ(eventActivationMap[2]->start_ns, activationStartNs); - EXPECT_EQ(eventActivationMap[2]->ttl_ns, event_activation1->ttl_seconds() * NS_PER_SEC); - - // Turn off screen 10 seconds after activation expiration. - const int64_t durationEndNs = activationEndNs + 10 * NS_PER_SEC; // 0:50 - event = CreateScreenStateChangedEvent(durationEndNs, android::view::DISPLAY_STATE_OFF); - processor.OnLogEvent(event.get(), durationEndNs); - - // Turn screen on. - const int64_t duration2StartNs = durationEndNs + 5 * NS_PER_SEC; // 0:55 - event = CreateScreenStateChangedEvent(duration2StartNs, android::view::DISPLAY_STATE_ON); - processor.OnLogEvent(event.get(), duration2StartNs); - - // Turn off screen. - const int64_t duration2EndNs = duration2StartNs + 10 * NS_PER_SEC; // 1:05 - event = CreateScreenStateChangedEvent(duration2EndNs, android::view::DISPLAY_STATE_OFF); - processor.OnLogEvent(event.get(), duration2EndNs); - - // Activate metric. - const int64_t activation2StartNs = duration2EndNs + 5 * NS_PER_SEC; // 1:10 - const int64_t activation2EndNs = - activation2StartNs + event_activation1->ttl_seconds() * NS_PER_SEC; // 1:40 - event = CreateAppCrashEvent(activation2StartNs, 211); - processor.OnLogEvent(event.get(), activation2StartNs); - EXPECT_TRUE(metricsManager->isActive()); - EXPECT_TRUE(metricProducer->mIsActive); - EXPECT_EQ(broadcastCount, 3); - ASSERT_EQ(activeConfigsBroadcast.size(), 1); - EXPECT_EQ(activeConfigsBroadcast[0], cfgId); - EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kActive); - EXPECT_EQ(eventActivationMap[2]->start_ns, activation2StartNs); - EXPECT_EQ(eventActivationMap[2]->ttl_ns, event_activation1->ttl_seconds() * NS_PER_SEC); - - ConfigMetricsReportList reports; - vector buffer; - processor.onDumpReport(cfgKey, bucketStartTimeNs + bucketSizeNs + 1 * NS_PER_SEC, false, true, - ADB_DUMP, FAST, &buffer); // 5:01 - EXPECT_TRUE(buffer.size() > 0); - EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size())); - backfillDimensionPath(&reports); - backfillStartEndTimestamp(&reports); - ASSERT_EQ(1, reports.reports_size()); - ASSERT_EQ(1, reports.reports(0).metrics_size()); - EXPECT_EQ(metricId, reports.reports(0).metrics(0).metric_id()); - EXPECT_TRUE(reports.reports(0).metrics(0).has_duration_metrics()); - - StatsLogReport::DurationMetricDataWrapper durationMetrics; - sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).duration_metrics(), - &durationMetrics); - ASSERT_EQ(1, durationMetrics.data_size()); - - DurationMetricData data = durationMetrics.data(0); - ASSERT_EQ(1, data.bucket_info_size()); - - auto bucketInfo = data.bucket_info(0); - EXPECT_EQ(0, bucketInfo.bucket_num()); - EXPECT_EQ(bucketStartTimeNs, bucketInfo.start_bucket_elapsed_nanos()); - EXPECT_EQ(expirationNs, bucketInfo.end_bucket_elapsed_nanos()); - EXPECT_EQ(expirationNs - durationStartNs, bucketInfo.duration_nanos()); -} - -TEST(DurationMetricE2eTest, TestWithCondition) { - StatsdConfig config; - config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root. - *config.add_atom_matcher() = CreateAcquireWakelockAtomMatcher(); - *config.add_atom_matcher() = CreateReleaseWakelockAtomMatcher(); - *config.add_atom_matcher() = CreateMoveToBackgroundAtomMatcher(); - *config.add_atom_matcher() = CreateMoveToForegroundAtomMatcher(); - - auto holdingWakelockPredicate = CreateHoldingWakelockPredicate(); - *config.add_predicate() = holdingWakelockPredicate; - - auto isInBackgroundPredicate = CreateIsInBackgroundPredicate(); - *config.add_predicate() = isInBackgroundPredicate; - - auto durationMetric = config.add_duration_metric(); - durationMetric->set_id(StringToId("WakelockDuration")); - durationMetric->set_what(holdingWakelockPredicate.id()); - durationMetric->set_condition(isInBackgroundPredicate.id()); - durationMetric->set_aggregation_type(DurationMetric::SUM); - durationMetric->set_bucket(FIVE_MINUTES); - - ConfigKey cfgKey; - uint64_t bucketStartTimeNs = 10000000000; - uint64_t bucketSizeNs = - TimeUnitToBucketSizeInMillis(config.duration_metric(0).bucket()) * 1000000LL; - auto processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey); - ASSERT_EQ(processor->mMetricsManagers.size(), 1u); - sp metricsManager = processor->mMetricsManagers.begin()->second; - EXPECT_TRUE(metricsManager->isConfigValid()); - ASSERT_EQ(metricsManager->mAllMetricProducers.size(), 1); - sp metricProducer = metricsManager->mAllMetricProducers[0]; - auto& eventActivationMap = metricProducer->mEventActivationMap; - EXPECT_TRUE(metricsManager->isActive()); - EXPECT_TRUE(metricProducer->mIsActive); - EXPECT_TRUE(eventActivationMap.empty()); - - int appUid = 123; - vector attributionUids1 = {appUid}; - vector attributionTags1 = {"App1"}; - - auto event = CreateAcquireWakelockEvent(bucketStartTimeNs + 10 * NS_PER_SEC, attributionUids1, - attributionTags1, - "wl1"); // 0:10 - processor->OnLogEvent(event.get()); - - event = CreateMoveToBackgroundEvent(bucketStartTimeNs + 22 * NS_PER_SEC, appUid); // 0:22 - processor->OnLogEvent(event.get()); - - event = CreateMoveToForegroundEvent(bucketStartTimeNs + (3 * 60 + 15) * NS_PER_SEC, - appUid); // 3:15 - processor->OnLogEvent(event.get()); - - event = CreateReleaseWakelockEvent(bucketStartTimeNs + 4 * 60 * NS_PER_SEC, attributionUids1, - attributionTags1, - "wl1"); // 4:00 - processor->OnLogEvent(event.get()); - - vector buffer; - ConfigMetricsReportList reports; - processor->onDumpReport(cfgKey, bucketStartTimeNs + bucketSizeNs + 1, false, true, ADB_DUMP, - FAST, &buffer); - ASSERT_GT(buffer.size(), 0); - EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size())); - backfillDimensionPath(&reports); - backfillStringInReport(&reports); - backfillStartEndTimestamp(&reports); - - ASSERT_EQ(1, reports.reports_size()); - ASSERT_EQ(1, reports.reports(0).metrics_size()); - StatsLogReport::DurationMetricDataWrapper durationMetrics; - sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).duration_metrics(), - &durationMetrics); - ASSERT_EQ(1, durationMetrics.data_size()); - - DurationMetricData data = durationMetrics.data(0); - - // Validate bucket info. - ASSERT_EQ(1, data.bucket_info_size()); - - auto bucketInfo = data.bucket_info(0); - EXPECT_EQ(bucketStartTimeNs, bucketInfo.start_bucket_elapsed_nanos()); - EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, bucketInfo.end_bucket_elapsed_nanos()); - EXPECT_EQ((2 * 60 + 53) * NS_PER_SEC, bucketInfo.duration_nanos()); -} - -TEST(DurationMetricE2eTest, TestWithSlicedCondition) { - StatsdConfig config; - config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root. - auto screenOnMatcher = CreateScreenTurnedOnAtomMatcher(); - *config.add_atom_matcher() = CreateAcquireWakelockAtomMatcher(); - *config.add_atom_matcher() = CreateReleaseWakelockAtomMatcher(); - *config.add_atom_matcher() = CreateMoveToBackgroundAtomMatcher(); - *config.add_atom_matcher() = CreateMoveToForegroundAtomMatcher(); - - auto holdingWakelockPredicate = CreateHoldingWakelockPredicate(); - // The predicate is dimensioning by first attribution node by uid. - FieldMatcher dimensions = CreateAttributionUidDimensions(util::WAKELOCK_STATE_CHANGED, - {Position::FIRST}); - *holdingWakelockPredicate.mutable_simple_predicate()->mutable_dimensions() = dimensions; - *config.add_predicate() = holdingWakelockPredicate; - - auto isInBackgroundPredicate = CreateIsInBackgroundPredicate(); - *isInBackgroundPredicate.mutable_simple_predicate()->mutable_dimensions() = - CreateDimensions(util::ACTIVITY_FOREGROUND_STATE_CHANGED, {Position::FIRST}); - *config.add_predicate() = isInBackgroundPredicate; - - auto durationMetric = config.add_duration_metric(); - durationMetric->set_id(StringToId("WakelockDuration")); - durationMetric->set_what(holdingWakelockPredicate.id()); - durationMetric->set_condition(isInBackgroundPredicate.id()); - durationMetric->set_aggregation_type(DurationMetric::SUM); - // The metric is dimensioning by first attribution node and only by uid. - *durationMetric->mutable_dimensions_in_what() = CreateAttributionUidDimensions( - util::WAKELOCK_STATE_CHANGED, {Position::FIRST}); - durationMetric->set_bucket(FIVE_MINUTES); - - // Links between wakelock state atom and condition of app is in background. - auto links = durationMetric->add_links(); - links->set_condition(isInBackgroundPredicate.id()); - *links->mutable_fields_in_what() = - CreateAttributionUidDimensions(util::WAKELOCK_STATE_CHANGED, {Position::FIRST}); - auto dimensionCondition = links->mutable_fields_in_condition(); - dimensionCondition->set_field(util::ACTIVITY_FOREGROUND_STATE_CHANGED); - dimensionCondition->add_child()->set_field(1); // uid field. - - ConfigKey cfgKey; - uint64_t bucketStartTimeNs = 10000000000; - uint64_t bucketSizeNs = - TimeUnitToBucketSizeInMillis(config.duration_metric(0).bucket()) * 1000000LL; - auto processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey); - ASSERT_EQ(processor->mMetricsManagers.size(), 1u); - sp metricsManager = processor->mMetricsManagers.begin()->second; - EXPECT_TRUE(metricsManager->isConfigValid()); - ASSERT_EQ(metricsManager->mAllMetricProducers.size(), 1); - sp metricProducer = metricsManager->mAllMetricProducers[0]; - auto& eventActivationMap = metricProducer->mEventActivationMap; - EXPECT_TRUE(metricsManager->isActive()); - EXPECT_TRUE(metricProducer->mIsActive); - EXPECT_TRUE(eventActivationMap.empty()); - - int appUid = 123; - std::vector attributionUids1 = {appUid}; - std::vector attributionTags1 = {"App1"}; - - auto event = CreateAcquireWakelockEvent(bucketStartTimeNs + 10 * NS_PER_SEC, attributionUids1, - attributionTags1, "wl1"); // 0:10 - processor->OnLogEvent(event.get()); - - event = CreateMoveToBackgroundEvent(bucketStartTimeNs + 22 * NS_PER_SEC, appUid); // 0:22 - processor->OnLogEvent(event.get()); - - event = CreateReleaseWakelockEvent(bucketStartTimeNs + 60 * NS_PER_SEC, attributionUids1, - attributionTags1, "wl1"); // 1:00 - processor->OnLogEvent(event.get()); - - event = CreateMoveToForegroundEvent(bucketStartTimeNs + (3 * 60 + 15) * NS_PER_SEC, - appUid); // 3:15 - processor->OnLogEvent(event.get()); - - vector buffer; - ConfigMetricsReportList reports; - processor->onDumpReport(cfgKey, bucketStartTimeNs + bucketSizeNs + 1, false, true, ADB_DUMP, - FAST, &buffer); - ASSERT_GT(buffer.size(), 0); - EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size())); - backfillDimensionPath(&reports); - backfillStringInReport(&reports); - backfillStartEndTimestamp(&reports); - - ASSERT_EQ(1, reports.reports_size()); - ASSERT_EQ(1, reports.reports(0).metrics_size()); - StatsLogReport::DurationMetricDataWrapper durationMetrics; - sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).duration_metrics(), - &durationMetrics); - ASSERT_EQ(1, durationMetrics.data_size()); - - DurationMetricData data = durationMetrics.data(0); - // Validate dimension value. - ValidateAttributionUidDimension(data.dimensions_in_what(), - util::WAKELOCK_STATE_CHANGED, appUid); - // Validate bucket info. - ASSERT_EQ(1, data.bucket_info_size()); - - auto bucketInfo = data.bucket_info(0); - EXPECT_EQ(bucketStartTimeNs, bucketInfo.start_bucket_elapsed_nanos()); - EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, bucketInfo.end_bucket_elapsed_nanos()); - EXPECT_EQ(38 * NS_PER_SEC, bucketInfo.duration_nanos()); -} - -TEST(DurationMetricE2eTest, TestWithActivationAndSlicedCondition) { - StatsdConfig config; - config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root. - auto screenOnMatcher = CreateScreenTurnedOnAtomMatcher(); - *config.add_atom_matcher() = CreateAcquireWakelockAtomMatcher(); - *config.add_atom_matcher() = CreateReleaseWakelockAtomMatcher(); - *config.add_atom_matcher() = CreateMoveToBackgroundAtomMatcher(); - *config.add_atom_matcher() = CreateMoveToForegroundAtomMatcher(); - *config.add_atom_matcher() = screenOnMatcher; - - auto holdingWakelockPredicate = CreateHoldingWakelockPredicate(); - // The predicate is dimensioning by first attribution node by uid. - FieldMatcher dimensions = CreateAttributionUidDimensions(util::WAKELOCK_STATE_CHANGED, - {Position::FIRST}); - *holdingWakelockPredicate.mutable_simple_predicate()->mutable_dimensions() = dimensions; - *config.add_predicate() = holdingWakelockPredicate; - - auto isInBackgroundPredicate = CreateIsInBackgroundPredicate(); - *isInBackgroundPredicate.mutable_simple_predicate()->mutable_dimensions() = - CreateDimensions(util::ACTIVITY_FOREGROUND_STATE_CHANGED, {Position::FIRST}); - *config.add_predicate() = isInBackgroundPredicate; - - auto durationMetric = config.add_duration_metric(); - durationMetric->set_id(StringToId("WakelockDuration")); - durationMetric->set_what(holdingWakelockPredicate.id()); - durationMetric->set_condition(isInBackgroundPredicate.id()); - durationMetric->set_aggregation_type(DurationMetric::SUM); - // The metric is dimensioning by first attribution node and only by uid. - *durationMetric->mutable_dimensions_in_what() = CreateAttributionUidDimensions( - util::WAKELOCK_STATE_CHANGED, {Position::FIRST}); - durationMetric->set_bucket(FIVE_MINUTES); - - // Links between wakelock state atom and condition of app is in background. - auto links = durationMetric->add_links(); - links->set_condition(isInBackgroundPredicate.id()); - *links->mutable_fields_in_what() = - CreateAttributionUidDimensions(util::WAKELOCK_STATE_CHANGED, {Position::FIRST}); - auto dimensionCondition = links->mutable_fields_in_condition(); - dimensionCondition->set_field(util::ACTIVITY_FOREGROUND_STATE_CHANGED); - dimensionCondition->add_child()->set_field(1); // uid field. - - auto metric_activation1 = config.add_metric_activation(); - metric_activation1->set_metric_id(durationMetric->id()); - auto event_activation1 = metric_activation1->add_event_activation(); - event_activation1->set_atom_matcher_id(screenOnMatcher.id()); - event_activation1->set_ttl_seconds(60 * 2); // 2 minutes. - - ConfigKey cfgKey; - uint64_t bucketStartTimeNs = 10000000000; - uint64_t bucketSizeNs = - TimeUnitToBucketSizeInMillis(config.duration_metric(0).bucket()) * 1000000LL; - auto processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey); - ASSERT_EQ(processor->mMetricsManagers.size(), 1u); - sp metricsManager = processor->mMetricsManagers.begin()->second; - EXPECT_TRUE(metricsManager->isConfigValid()); - ASSERT_EQ(metricsManager->mAllMetricProducers.size(), 1); - sp metricProducer = metricsManager->mAllMetricProducers[0]; - auto& eventActivationMap = metricProducer->mEventActivationMap; - EXPECT_FALSE(metricsManager->isActive()); - EXPECT_FALSE(metricProducer->mIsActive); - ASSERT_EQ(eventActivationMap.size(), 1u); - EXPECT_TRUE(eventActivationMap.find(4) != eventActivationMap.end()); - EXPECT_EQ(eventActivationMap[4]->state, ActivationState::kNotActive); - EXPECT_EQ(eventActivationMap[4]->start_ns, 0); - EXPECT_EQ(eventActivationMap[4]->ttl_ns, event_activation1->ttl_seconds() * NS_PER_SEC); - - int appUid = 123; - std::vector attributionUids1 = {appUid}; - std::vector attributionTags1 = {"App1"}; - - auto event = CreateAcquireWakelockEvent(bucketStartTimeNs + 10 * NS_PER_SEC, attributionUids1, - attributionTags1, "wl1"); // 0:10 - processor->OnLogEvent(event.get()); - EXPECT_FALSE(metricsManager->isActive()); - EXPECT_FALSE(metricProducer->mIsActive); - EXPECT_EQ(eventActivationMap[4]->state, ActivationState::kNotActive); - EXPECT_EQ(eventActivationMap[4]->start_ns, 0); - EXPECT_EQ(eventActivationMap[4]->ttl_ns, event_activation1->ttl_seconds() * NS_PER_SEC); - - event = CreateMoveToBackgroundEvent(bucketStartTimeNs + 22 * NS_PER_SEC, appUid); // 0:22 - processor->OnLogEvent(event.get()); - EXPECT_FALSE(metricsManager->isActive()); - EXPECT_FALSE(metricProducer->mIsActive); - EXPECT_EQ(eventActivationMap[4]->state, ActivationState::kNotActive); - EXPECT_EQ(eventActivationMap[4]->start_ns, 0); - EXPECT_EQ(eventActivationMap[4]->ttl_ns, event_activation1->ttl_seconds() * NS_PER_SEC); - - const int64_t durationStartNs = bucketStartTimeNs + 30 * NS_PER_SEC; // 0:30 - event = CreateScreenStateChangedEvent(durationStartNs, android::view::DISPLAY_STATE_ON); - processor->OnLogEvent(event.get()); - EXPECT_TRUE(metricsManager->isActive()); - EXPECT_TRUE(metricProducer->mIsActive); - EXPECT_EQ(eventActivationMap[4]->state, ActivationState::kActive); - EXPECT_EQ(eventActivationMap[4]->start_ns, durationStartNs); - EXPECT_EQ(eventActivationMap[4]->ttl_ns, event_activation1->ttl_seconds() * NS_PER_SEC); - - const int64_t durationEndNs = - durationStartNs + (event_activation1->ttl_seconds() + 30) * NS_PER_SEC; // 3:00 - event = CreateAppCrashEvent(durationEndNs, 333); - processor->OnLogEvent(event.get()); - EXPECT_FALSE(metricsManager->isActive()); - EXPECT_FALSE(metricProducer->mIsActive); - EXPECT_EQ(eventActivationMap[4]->state, ActivationState::kNotActive); - EXPECT_EQ(eventActivationMap[4]->start_ns, durationStartNs); - EXPECT_EQ(eventActivationMap[4]->ttl_ns, event_activation1->ttl_seconds() * NS_PER_SEC); - - event = CreateMoveToForegroundEvent(bucketStartTimeNs + (3 * 60 + 15) * NS_PER_SEC, - appUid); // 3:15 - processor->OnLogEvent(event.get()); - - event = CreateReleaseWakelockEvent(bucketStartTimeNs + (4 * 60 + 17) * NS_PER_SEC, - attributionUids1, attributionTags1, "wl1"); // 4:17 - processor->OnLogEvent(event.get()); - - event = CreateMoveToBackgroundEvent(bucketStartTimeNs + (4 * 60 + 20) * NS_PER_SEC, - appUid); // 4:20 - processor->OnLogEvent(event.get()); - - event = CreateAcquireWakelockEvent(bucketStartTimeNs + (4 * 60 + 25) * NS_PER_SEC, - attributionUids1, attributionTags1, "wl1"); // 4:25 - processor->OnLogEvent(event.get()); - - const int64_t duration2StartNs = bucketStartTimeNs + (4 * 60 + 30) * NS_PER_SEC; // 4:30 - event = CreateScreenStateChangedEvent(duration2StartNs, android::view::DISPLAY_STATE_ON); - processor->OnLogEvent(event.get()); - EXPECT_TRUE(metricsManager->isActive()); - EXPECT_TRUE(metricProducer->mIsActive); - EXPECT_EQ(eventActivationMap[4]->state, ActivationState::kActive); - EXPECT_EQ(eventActivationMap[4]->start_ns, duration2StartNs); - EXPECT_EQ(eventActivationMap[4]->ttl_ns, event_activation1->ttl_seconds() * NS_PER_SEC); - - vector buffer; - ConfigMetricsReportList reports; - processor->onDumpReport(cfgKey, bucketStartTimeNs + bucketSizeNs + 1, false, true, ADB_DUMP, - FAST, &buffer); - ASSERT_GT(buffer.size(), 0); - EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size())); - backfillDimensionPath(&reports); - backfillStringInReport(&reports); - backfillStartEndTimestamp(&reports); - - ASSERT_EQ(1, reports.reports_size()); - ASSERT_EQ(1, reports.reports(0).metrics_size()); - StatsLogReport::DurationMetricDataWrapper durationMetrics; - sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).duration_metrics(), - &durationMetrics); - ASSERT_EQ(1, durationMetrics.data_size()); - - DurationMetricData data = durationMetrics.data(0); - // Validate dimension value. - ValidateAttributionUidDimension(data.dimensions_in_what(), - util::WAKELOCK_STATE_CHANGED, appUid); - // Validate bucket info. - ASSERT_EQ(2, data.bucket_info_size()); - - auto bucketInfo = data.bucket_info(0); - EXPECT_EQ(bucketStartTimeNs, bucketInfo.start_bucket_elapsed_nanos()); - EXPECT_EQ(durationEndNs, bucketInfo.end_bucket_elapsed_nanos()); - EXPECT_EQ(durationEndNs - durationStartNs, bucketInfo.duration_nanos()); - - bucketInfo = data.bucket_info(1); - EXPECT_EQ(durationEndNs, bucketInfo.start_bucket_elapsed_nanos()); - EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, bucketInfo.end_bucket_elapsed_nanos()); - EXPECT_EQ(bucketStartTimeNs + bucketSizeNs - duration2StartNs, bucketInfo.duration_nanos()); -} - -TEST(DurationMetricE2eTest, TestWithSlicedState) { - // Initialize config. - StatsdConfig config; - config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root. - - *config.add_atom_matcher() = CreateBatterySaverModeStartAtomMatcher(); - *config.add_atom_matcher() = CreateBatterySaverModeStopAtomMatcher(); - - auto batterySaverModePredicate = CreateBatterySaverModePredicate(); - *config.add_predicate() = batterySaverModePredicate; - - auto screenState = CreateScreenState(); - *config.add_state() = screenState; - - // Create duration metric that slices by screen state. - auto durationMetric = config.add_duration_metric(); - durationMetric->set_id(StringToId("DurationBatterySaverModeSliceScreen")); - durationMetric->set_what(batterySaverModePredicate.id()); - durationMetric->add_slice_by_state(screenState.id()); - durationMetric->set_aggregation_type(DurationMetric::SUM); - durationMetric->set_bucket(FIVE_MINUTES); - - // Initialize StatsLogProcessor. - int uid = 12345; - int64_t cfgId = 98765; - ConfigKey cfgKey(uid, cfgId); - uint64_t bucketStartTimeNs = 10000000000; // 0:10 - uint64_t bucketSizeNs = - TimeUnitToBucketSizeInMillis(config.duration_metric(0).bucket()) * 1000000LL; - auto processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey); - - ASSERT_EQ(processor->mMetricsManagers.size(), 1u); - sp metricsManager = processor->mMetricsManagers.begin()->second; - EXPECT_TRUE(metricsManager->isConfigValid()); - ASSERT_EQ(metricsManager->mAllMetricProducers.size(), 1); - EXPECT_TRUE(metricsManager->isActive()); - sp metricProducer = metricsManager->mAllMetricProducers[0]; - EXPECT_TRUE(metricProducer->mIsActive); - ASSERT_EQ(metricProducer->mSlicedStateAtoms.size(), 1); - EXPECT_EQ(metricProducer->mSlicedStateAtoms.at(0), SCREEN_STATE_ATOM_ID); - ASSERT_EQ(metricProducer->mStateGroupMap.size(), 0); - - // Check that StateTrackers were initialized correctly. - EXPECT_EQ(1, StateManager::getInstance().getStateTrackersCount()); - EXPECT_EQ(1, StateManager::getInstance().getListenersCount(SCREEN_STATE_ATOM_ID)); - - /* - bucket #1 bucket #2 - | 1 2 3 4 5 6 7 8 9 10 (minutes) - |-----------------------------|-----------------------------|-- - ON OFF ON (BatterySaverMode) - | | | (ScreenIsOnEvent) - | | (ScreenIsOffEvent) - | (ScreenDozeEvent) - */ - // Initialize log events. - std::vector> events; - events.push_back(CreateScreenStateChangedEvent( - bucketStartTimeNs + 10 * NS_PER_SEC, - android::view::DisplayStateEnum::DISPLAY_STATE_ON)); // 0:20 - events.push_back(CreateBatterySaverOnEvent(bucketStartTimeNs + 20 * NS_PER_SEC)); // 0:30 - events.push_back(CreateScreenStateChangedEvent( - bucketStartTimeNs + 50 * NS_PER_SEC, - android::view::DisplayStateEnum::DISPLAY_STATE_OFF)); // 1:00 - events.push_back(CreateScreenStateChangedEvent( - bucketStartTimeNs + 80 * NS_PER_SEC, - android::view::DisplayStateEnum::DISPLAY_STATE_DOZE)); // 1:30 - events.push_back(CreateScreenStateChangedEvent( - bucketStartTimeNs + 120 * NS_PER_SEC, - android::view::DisplayStateEnum::DISPLAY_STATE_ON)); // 2:10 - events.push_back(CreateBatterySaverOffEvent(bucketStartTimeNs + 200 * NS_PER_SEC)); // 3:30 - events.push_back(CreateScreenStateChangedEvent( - bucketStartTimeNs + 250 * NS_PER_SEC, - android::view::DisplayStateEnum::DISPLAY_STATE_OFF)); // 4:20 - events.push_back(CreateBatterySaverOnEvent(bucketStartTimeNs + 280 * NS_PER_SEC)); // 4:50 - - // Bucket boundary. - events.push_back(CreateScreenStateChangedEvent( - bucketStartTimeNs + 310 * NS_PER_SEC, - android::view::DisplayStateEnum::DISPLAY_STATE_ON)); // 5:20 - - // Send log events to StatsLogProcessor. - for (auto& event : events) { - processor->OnLogEvent(event.get()); - } - - // Check dump report. - vector buffer; - ConfigMetricsReportList reports; - processor->onDumpReport(cfgKey, bucketStartTimeNs + 360 * NS_PER_SEC, - true /* include current partial bucket */, true, ADB_DUMP, FAST, - &buffer); // 6:10 - ASSERT_GT(buffer.size(), 0); - EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size())); - backfillDimensionPath(&reports); - backfillStringInReport(&reports); - backfillStartEndTimestamp(&reports); - - ASSERT_EQ(1, reports.reports_size()); - ASSERT_EQ(1, reports.reports(0).metrics_size()); - EXPECT_TRUE(reports.reports(0).metrics(0).has_duration_metrics()); - StatsLogReport::DurationMetricDataWrapper durationMetrics; - sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).duration_metrics(), - &durationMetrics); - ASSERT_EQ(3, durationMetrics.data_size()); - - DurationMetricData data = durationMetrics.data(0); - ASSERT_EQ(1, data.slice_by_state_size()); - EXPECT_EQ(SCREEN_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); - EXPECT_TRUE(data.slice_by_state(0).has_value()); - EXPECT_EQ(android::view::DisplayStateEnum::DISPLAY_STATE_OFF, data.slice_by_state(0).value()); - ASSERT_EQ(2, data.bucket_info_size()); - EXPECT_EQ(50 * NS_PER_SEC, data.bucket_info(0).duration_nanos()); - EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).start_bucket_elapsed_nanos()); - EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(0).end_bucket_elapsed_nanos()); - EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(1).duration_nanos()); - EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(1).start_bucket_elapsed_nanos()); - EXPECT_EQ(370 * NS_PER_SEC, data.bucket_info(1).end_bucket_elapsed_nanos()); - - data = durationMetrics.data(1); - ASSERT_EQ(1, data.slice_by_state_size()); - EXPECT_EQ(SCREEN_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); - EXPECT_TRUE(data.slice_by_state(0).has_value()); - EXPECT_EQ(android::view::DisplayStateEnum::DISPLAY_STATE_ON, data.slice_by_state(0).value()); - ASSERT_EQ(2, data.bucket_info_size()); - EXPECT_EQ(110 * NS_PER_SEC, data.bucket_info(0).duration_nanos()); - EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).start_bucket_elapsed_nanos()); - EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(0).end_bucket_elapsed_nanos()); - EXPECT_EQ(50 * NS_PER_SEC, data.bucket_info(1).duration_nanos()); - EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(1).start_bucket_elapsed_nanos()); - EXPECT_EQ(370 * NS_PER_SEC, data.bucket_info(1).end_bucket_elapsed_nanos()); - - data = durationMetrics.data(2); - ASSERT_EQ(1, data.slice_by_state_size()); - EXPECT_EQ(SCREEN_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); - EXPECT_TRUE(data.slice_by_state(0).has_value()); - EXPECT_EQ(android::view::DisplayStateEnum::DISPLAY_STATE_DOZE, data.slice_by_state(0).value()); - ASSERT_EQ(1, data.bucket_info_size()); - EXPECT_EQ(40 * NS_PER_SEC, data.bucket_info(0).duration_nanos()); - EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).start_bucket_elapsed_nanos()); - EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(0).end_bucket_elapsed_nanos()); -} - -TEST(DurationMetricE2eTest, TestWithConditionAndSlicedState) { - // Initialize config. - StatsdConfig config; - config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root. - - *config.add_atom_matcher() = CreateBatterySaverModeStartAtomMatcher(); - *config.add_atom_matcher() = CreateBatterySaverModeStopAtomMatcher(); - *config.add_atom_matcher() = CreateBatteryStateNoneMatcher(); - *config.add_atom_matcher() = CreateBatteryStateUsbMatcher(); - - auto batterySaverModePredicate = CreateBatterySaverModePredicate(); - *config.add_predicate() = batterySaverModePredicate; - - auto deviceUnpluggedPredicate = CreateDeviceUnpluggedPredicate(); - *config.add_predicate() = deviceUnpluggedPredicate; - - auto screenState = CreateScreenState(); - *config.add_state() = screenState; - - // Create duration metric that has a condition and slices by screen state. - auto durationMetric = config.add_duration_metric(); - durationMetric->set_id(StringToId("DurationBatterySaverModeOnBatterySliceScreen")); - durationMetric->set_what(batterySaverModePredicate.id()); - durationMetric->set_condition(deviceUnpluggedPredicate.id()); - durationMetric->add_slice_by_state(screenState.id()); - durationMetric->set_aggregation_type(DurationMetric::SUM); - durationMetric->set_bucket(FIVE_MINUTES); - - // Initialize StatsLogProcessor. - int uid = 12345; - int64_t cfgId = 98765; - ConfigKey cfgKey(uid, cfgId); - uint64_t bucketStartTimeNs = 10000000000; // 0:10 - uint64_t bucketSizeNs = - TimeUnitToBucketSizeInMillis(config.duration_metric(0).bucket()) * 1000000LL; - auto processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey); - - ASSERT_EQ(processor->mMetricsManagers.size(), 1u); - sp metricsManager = processor->mMetricsManagers.begin()->second; - EXPECT_TRUE(metricsManager->isConfigValid()); - ASSERT_EQ(metricsManager->mAllMetricProducers.size(), 1); - EXPECT_TRUE(metricsManager->isActive()); - sp metricProducer = metricsManager->mAllMetricProducers[0]; - EXPECT_TRUE(metricProducer->mIsActive); - ASSERT_EQ(metricProducer->mSlicedStateAtoms.size(), 1); - EXPECT_EQ(metricProducer->mSlicedStateAtoms.at(0), SCREEN_STATE_ATOM_ID); - ASSERT_EQ(metricProducer->mStateGroupMap.size(), 0); - - // Check that StateTrackers were initialized correctly. - EXPECT_EQ(1, StateManager::getInstance().getStateTrackersCount()); - EXPECT_EQ(1, StateManager::getInstance().getListenersCount(SCREEN_STATE_ATOM_ID)); - - /* - bucket #1 bucket #2 - | 1 2 3 4 5 6 7 8 (minutes) - |---------------------------------------|------------------ - ON OFF ON (BatterySaverMode) - T F T (DeviceUnpluggedPredicate) - | | | (ScreenIsOnEvent) - | | | (ScreenIsOffEvent) - | (ScreenDozeEvent) - */ - // Initialize log events. - std::vector> events; - events.push_back(CreateScreenStateChangedEvent( - bucketStartTimeNs + 20 * NS_PER_SEC, - android::view::DisplayStateEnum::DISPLAY_STATE_ON)); // 0:30 - events.push_back(CreateBatterySaverOnEvent(bucketStartTimeNs + 60 * NS_PER_SEC)); // 1:10 - events.push_back(CreateScreenStateChangedEvent( - bucketStartTimeNs + 80 * NS_PER_SEC, - android::view::DisplayStateEnum::DISPLAY_STATE_OFF)); // 1:30 - events.push_back( - CreateBatteryStateChangedEvent(bucketStartTimeNs + 110 * NS_PER_SEC, - BatteryPluggedStateEnum::BATTERY_PLUGGED_NONE)); // 2:00 - events.push_back(CreateScreenStateChangedEvent( - bucketStartTimeNs + 145 * NS_PER_SEC, - android::view::DisplayStateEnum::DISPLAY_STATE_ON)); // 2:35 - events.push_back(CreateScreenStateChangedEvent( - bucketStartTimeNs + 170 * NS_PER_SEC, - android::view::DisplayStateEnum::DISPLAY_STATE_OFF)); // 3:00 - events.push_back( - CreateBatteryStateChangedEvent(bucketStartTimeNs + 180 * NS_PER_SEC, - BatteryPluggedStateEnum::BATTERY_PLUGGED_USB)); // 3:10 - events.push_back(CreateScreenStateChangedEvent( - bucketStartTimeNs + 200 * NS_PER_SEC, - android::view::DisplayStateEnum::DISPLAY_STATE_DOZE)); // 3:30 - events.push_back( - CreateBatteryStateChangedEvent(bucketStartTimeNs + 230 * NS_PER_SEC, - BatteryPluggedStateEnum::BATTERY_PLUGGED_NONE)); // 4:00 - events.push_back(CreateScreenStateChangedEvent( - bucketStartTimeNs + 260 * NS_PER_SEC, - android::view::DisplayStateEnum::DISPLAY_STATE_ON)); // 4:30 - events.push_back(CreateBatterySaverOffEvent(bucketStartTimeNs + 280 * NS_PER_SEC)); // 4:50 - - // Bucket boundary. - events.push_back(CreateBatterySaverOnEvent(bucketStartTimeNs + 320 * NS_PER_SEC)); // 5:30 - events.push_back(CreateScreenStateChangedEvent( - bucketStartTimeNs + 380 * NS_PER_SEC, - android::view::DisplayStateEnum::DISPLAY_STATE_OFF)); // 6:30 - - // Send log events to StatsLogProcessor. - for (auto& event : events) { - processor->OnLogEvent(event.get()); - } - - // Check dump report. - vector buffer; - ConfigMetricsReportList reports; - processor->onDumpReport(cfgKey, bucketStartTimeNs + 410 * NS_PER_SEC, - true /* include current partial bucket */, true, ADB_DUMP, FAST, - &buffer); - ASSERT_GT(buffer.size(), 0); - EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size())); - backfillDimensionPath(&reports); - backfillStringInReport(&reports); - backfillStartEndTimestamp(&reports); - - ASSERT_EQ(1, reports.reports_size()); - ASSERT_EQ(1, reports.reports(0).metrics_size()); - EXPECT_TRUE(reports.reports(0).metrics(0).has_duration_metrics()); - StatsLogReport::DurationMetricDataWrapper durationMetrics; - sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).duration_metrics(), - &durationMetrics); - ASSERT_EQ(3, durationMetrics.data_size()); - - DurationMetricData data = durationMetrics.data(0); - ASSERT_EQ(1, data.slice_by_state_size()); - EXPECT_EQ(SCREEN_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); - EXPECT_TRUE(data.slice_by_state(0).has_value()); - EXPECT_EQ(android::view::DisplayStateEnum::DISPLAY_STATE_OFF, data.slice_by_state(0).value()); - ASSERT_EQ(2, data.bucket_info_size()); - EXPECT_EQ(45 * NS_PER_SEC, data.bucket_info(0).duration_nanos()); - EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).start_bucket_elapsed_nanos()); - EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(0).end_bucket_elapsed_nanos()); - EXPECT_EQ(30 * NS_PER_SEC, data.bucket_info(1).duration_nanos()); - EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(1).start_bucket_elapsed_nanos()); - EXPECT_EQ(420 * NS_PER_SEC, data.bucket_info(1).end_bucket_elapsed_nanos()); - - data = durationMetrics.data(1); - ASSERT_EQ(1, data.slice_by_state_size()); - EXPECT_EQ(SCREEN_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); - EXPECT_TRUE(data.slice_by_state(0).has_value()); - EXPECT_EQ(android::view::DisplayStateEnum::DISPLAY_STATE_ON, data.slice_by_state(0).value()); - ASSERT_EQ(2, data.bucket_info_size()); - EXPECT_EQ(45 * NS_PER_SEC, data.bucket_info(0).duration_nanos()); - EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).start_bucket_elapsed_nanos()); - EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(0).end_bucket_elapsed_nanos()); - EXPECT_EQ(60 * NS_PER_SEC, data.bucket_info(1).duration_nanos()); - EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(1).start_bucket_elapsed_nanos()); - EXPECT_EQ(420 * NS_PER_SEC, data.bucket_info(1).end_bucket_elapsed_nanos()); - - data = durationMetrics.data(2); - ASSERT_EQ(1, data.slice_by_state_size()); - EXPECT_EQ(SCREEN_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); - EXPECT_TRUE(data.slice_by_state(0).has_value()); - EXPECT_EQ(android::view::DisplayStateEnum::DISPLAY_STATE_DOZE, data.slice_by_state(0).value()); - ASSERT_EQ(1, data.bucket_info_size()); - EXPECT_EQ(30 * NS_PER_SEC, data.bucket_info(0).duration_nanos()); - EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).start_bucket_elapsed_nanos()); - EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(0).end_bucket_elapsed_nanos()); -} - -TEST(DurationMetricE2eTest, TestWithSlicedStateMapped) { - // Initialize config. - StatsdConfig config; - config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root. - - *config.add_atom_matcher() = CreateBatterySaverModeStartAtomMatcher(); - *config.add_atom_matcher() = CreateBatterySaverModeStopAtomMatcher(); - - auto batterySaverModePredicate = CreateBatterySaverModePredicate(); - *config.add_predicate() = batterySaverModePredicate; - - int64_t screenOnId = 4444; - int64_t screenOffId = 9876; - auto screenStateWithMap = CreateScreenStateWithOnOffMap(screenOnId, screenOffId); - *config.add_state() = screenStateWithMap; - - // Create duration metric that slices by mapped screen state. - auto durationMetric = config.add_duration_metric(); - durationMetric->set_id(StringToId("DurationBatterySaverModeSliceScreenMapped")); - durationMetric->set_what(batterySaverModePredicate.id()); - durationMetric->add_slice_by_state(screenStateWithMap.id()); - durationMetric->set_aggregation_type(DurationMetric::SUM); - durationMetric->set_bucket(FIVE_MINUTES); - - // Initialize StatsLogProcessor. - int uid = 12345; - int64_t cfgId = 98765; - ConfigKey cfgKey(uid, cfgId); - uint64_t bucketStartTimeNs = 10000000000; // 0:10 - uint64_t bucketSizeNs = - TimeUnitToBucketSizeInMillis(config.duration_metric(0).bucket()) * 1000000LL; - auto processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey); - - ASSERT_EQ(processor->mMetricsManagers.size(), 1u); - sp metricsManager = processor->mMetricsManagers.begin()->second; - EXPECT_TRUE(metricsManager->isConfigValid()); - ASSERT_EQ(metricsManager->mAllMetricProducers.size(), 1); - EXPECT_TRUE(metricsManager->isActive()); - sp metricProducer = metricsManager->mAllMetricProducers[0]; - EXPECT_TRUE(metricProducer->mIsActive); - ASSERT_EQ(metricProducer->mSlicedStateAtoms.size(), 1); - EXPECT_EQ(metricProducer->mSlicedStateAtoms.at(0), SCREEN_STATE_ATOM_ID); - ASSERT_EQ(metricProducer->mStateGroupMap.size(), 1); - - // Check that StateTrackers were initialized correctly. - EXPECT_EQ(1, StateManager::getInstance().getStateTrackersCount()); - EXPECT_EQ(1, StateManager::getInstance().getListenersCount(SCREEN_STATE_ATOM_ID)); - - /* - bucket #1 bucket #2 - | 1 2 3 4 5 6 7 8 9 10 (minutes) - |-----------------------------|-----------------------------|-- - ON OFF ON (BatterySaverMode) - ---------------------------------------------------------SCREEN_OFF events - | | (ScreenStateOffEvent = 1) - | (ScreenStateDozeEvent = 3) - | (ScreenStateDozeSuspendEvent = 4) - ---------------------------------------------------------SCREEN_ON events - | | | (ScreenStateOnEvent = 2) - | (ScreenStateVrEvent = 5) - | (ScreenStateOnSuspendEvent = 6) - */ - // Initialize log events. - std::vector> events; - events.push_back(CreateScreenStateChangedEvent( - bucketStartTimeNs + 10 * NS_PER_SEC, - android::view::DisplayStateEnum::DISPLAY_STATE_ON)); // 0:20 - events.push_back(CreateBatterySaverOnEvent(bucketStartTimeNs + 20 * NS_PER_SEC)); // 0:30 - events.push_back(CreateScreenStateChangedEvent( - bucketStartTimeNs + 70 * NS_PER_SEC, - android::view::DisplayStateEnum::DISPLAY_STATE_OFF)); // 1:20 - events.push_back(CreateScreenStateChangedEvent( - bucketStartTimeNs + 100 * NS_PER_SEC, - android::view::DisplayStateEnum::DISPLAY_STATE_DOZE)); // 1:50 - events.push_back(CreateScreenStateChangedEvent( - bucketStartTimeNs + 120 * NS_PER_SEC, - android::view::DisplayStateEnum::DISPLAY_STATE_ON)); // 2:10 - events.push_back(CreateScreenStateChangedEvent( - bucketStartTimeNs + 170 * NS_PER_SEC, - android::view::DisplayStateEnum::DISPLAY_STATE_VR)); // 3:00 - events.push_back(CreateBatterySaverOffEvent(bucketStartTimeNs + 200 * NS_PER_SEC)); // 3:30 - events.push_back(CreateScreenStateChangedEvent( - bucketStartTimeNs + 250 * NS_PER_SEC, - android::view::DisplayStateEnum::DISPLAY_STATE_OFF)); // 4:20 - events.push_back(CreateBatterySaverOnEvent(bucketStartTimeNs + 280 * NS_PER_SEC)); // 4:50 - - // Bucket boundary 5:10. - events.push_back(CreateScreenStateChangedEvent( - bucketStartTimeNs + 320 * NS_PER_SEC, - android::view::DisplayStateEnum::DISPLAY_STATE_ON)); // 5:30 - events.push_back(CreateScreenStateChangedEvent( - bucketStartTimeNs + 390 * NS_PER_SEC, - android::view::DisplayStateEnum::DISPLAY_STATE_ON_SUSPEND)); // 6:40 - events.push_back(CreateScreenStateChangedEvent( - bucketStartTimeNs + 430 * NS_PER_SEC, - android::view::DisplayStateEnum::DISPLAY_STATE_DOZE_SUSPEND)); // 7:20 - // Send log events to StatsLogProcessor. - for (auto& event : events) { - processor->OnLogEvent(event.get()); - } - - // Check dump report. - vector buffer; - ConfigMetricsReportList reports; - processor->onDumpReport(cfgKey, bucketStartTimeNs + 490 * NS_PER_SEC, - true /* include current partial bucket */, true, ADB_DUMP, FAST, - &buffer); - ASSERT_GT(buffer.size(), 0); - EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size())); - backfillDimensionPath(&reports); - backfillStringInReport(&reports); - backfillStartEndTimestamp(&reports); - - ASSERT_EQ(1, reports.reports_size()); - ASSERT_EQ(1, reports.reports(0).metrics_size()); - EXPECT_TRUE(reports.reports(0).metrics(0).has_duration_metrics()); - StatsLogReport::DurationMetricDataWrapper durationMetrics; - sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).duration_metrics(), - &durationMetrics); - ASSERT_EQ(2, durationMetrics.data_size()); - - DurationMetricData data = durationMetrics.data(0); - ASSERT_EQ(1, data.slice_by_state_size()); - EXPECT_EQ(SCREEN_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); - EXPECT_TRUE(data.slice_by_state(0).has_group_id()); - EXPECT_EQ(screenOnId, data.slice_by_state(0).group_id()); - ASSERT_EQ(2, data.bucket_info_size()); - EXPECT_EQ(130 * NS_PER_SEC, data.bucket_info(0).duration_nanos()); - EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).start_bucket_elapsed_nanos()); - EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(0).end_bucket_elapsed_nanos()); - EXPECT_EQ(110 * NS_PER_SEC, data.bucket_info(1).duration_nanos()); - EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(1).start_bucket_elapsed_nanos()); - EXPECT_EQ(500 * NS_PER_SEC, data.bucket_info(1).end_bucket_elapsed_nanos()); - - data = durationMetrics.data(1); - ASSERT_EQ(1, data.slice_by_state_size()); - EXPECT_EQ(SCREEN_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); - EXPECT_TRUE(data.slice_by_state(0).has_group_id()); - EXPECT_EQ(screenOffId, data.slice_by_state(0).group_id()); - ASSERT_EQ(2, data.bucket_info_size()); - EXPECT_EQ(70 * NS_PER_SEC, data.bucket_info(0).duration_nanos()); - EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).start_bucket_elapsed_nanos()); - EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(0).end_bucket_elapsed_nanos()); - EXPECT_EQ(80 * NS_PER_SEC, data.bucket_info(1).duration_nanos()); - EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(1).start_bucket_elapsed_nanos()); - EXPECT_EQ(500 * NS_PER_SEC, data.bucket_info(1).end_bucket_elapsed_nanos()); -} - -TEST(DurationMetricE2eTest, TestSlicedStatePrimaryFieldsNotSubsetDimInWhat) { - // Initialize config. - StatsdConfig config; - config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root. - - *config.add_atom_matcher() = CreateAcquireWakelockAtomMatcher(); - *config.add_atom_matcher() = CreateReleaseWakelockAtomMatcher(); - - auto holdingWakelockPredicate = CreateHoldingWakelockPredicate(); - *config.add_predicate() = holdingWakelockPredicate; - - auto uidProcessState = CreateUidProcessState(); - *config.add_state() = uidProcessState; - - // Create duration metric that slices by uid process state. - auto durationMetric = config.add_duration_metric(); - durationMetric->set_id(StringToId("DurationHoldingWakelockSliceUidProcessState")); - durationMetric->set_what(holdingWakelockPredicate.id()); - durationMetric->add_slice_by_state(uidProcessState.id()); - durationMetric->set_aggregation_type(DurationMetric::SUM); - durationMetric->set_bucket(FIVE_MINUTES); - - // The state has only one primary field (uid). - auto stateLink = durationMetric->add_state_link(); - stateLink->set_state_atom_id(UID_PROCESS_STATE_ATOM_ID); - auto fieldsInWhat = stateLink->mutable_fields_in_what(); - *fieldsInWhat = CreateAttributionUidDimensions(util::WAKELOCK_STATE_CHANGED, {Position::FIRST}); - auto fieldsInState = stateLink->mutable_fields_in_state(); - *fieldsInState = CreateDimensions(UID_PROCESS_STATE_ATOM_ID, {1 /* uid */}); - - // Initialize StatsLogProcessor. - int uid = 12345; - int64_t cfgId = 98765; - ConfigKey cfgKey(uid, cfgId); - uint64_t bucketStartTimeNs = 10000000000; // 0:10 - uint64_t bucketSizeNs = - TimeUnitToBucketSizeInMillis(config.duration_metric(0).bucket()) * 1000000LL; - auto processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey); - - // This config is rejected because the dimension in what fields are not a superset of the sliced - // state primary fields. - ASSERT_EQ(processor->mMetricsManagers.size(), 0); -} - -TEST(DurationMetricE2eTest, TestWithSlicedStatePrimaryFieldsSubset) { - // Initialize config. - StatsdConfig config; - config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root. - - *config.add_atom_matcher() = CreateAcquireWakelockAtomMatcher(); - *config.add_atom_matcher() = CreateReleaseWakelockAtomMatcher(); - - auto holdingWakelockPredicate = CreateHoldingWakelockPredicate(); - *(holdingWakelockPredicate.mutable_simple_predicate()->mutable_dimensions()) = - CreateAttributionUidAndOtherDimensions(util::WAKELOCK_STATE_CHANGED, {Position::FIRST}, - {3 /* tag */}); - *config.add_predicate() = holdingWakelockPredicate; - - auto uidProcessState = CreateUidProcessState(); - *config.add_state() = uidProcessState; - - // Create duration metric that slices by uid process state. - auto durationMetric = config.add_duration_metric(); - durationMetric->set_id(StringToId("DurationPartialWakelockPerTagUidSliceProcessState")); - durationMetric->set_what(holdingWakelockPredicate.id()); - durationMetric->add_slice_by_state(uidProcessState.id()); - durationMetric->set_aggregation_type(DurationMetric::SUM); - durationMetric->set_bucket(FIVE_MINUTES); - - // The metric is dimensioning by first uid of attribution node and tag. - *durationMetric->mutable_dimensions_in_what() = CreateAttributionUidAndOtherDimensions( - util::WAKELOCK_STATE_CHANGED, {Position::FIRST}, {3 /* tag */}); - // The state has only one primary field (uid). - auto stateLink = durationMetric->add_state_link(); - stateLink->set_state_atom_id(UID_PROCESS_STATE_ATOM_ID); - auto fieldsInWhat = stateLink->mutable_fields_in_what(); - *fieldsInWhat = CreateAttributionUidDimensions(util::WAKELOCK_STATE_CHANGED, {Position::FIRST}); - auto fieldsInState = stateLink->mutable_fields_in_state(); - *fieldsInState = CreateDimensions(UID_PROCESS_STATE_ATOM_ID, {1 /* uid */}); - - // Initialize StatsLogProcessor. - int uid = 12345; - int64_t cfgId = 98765; - ConfigKey cfgKey(uid, cfgId); - uint64_t bucketStartTimeNs = 10000000000; // 0:10 - uint64_t bucketSizeNs = - TimeUnitToBucketSizeInMillis(config.duration_metric(0).bucket()) * 1000000LL; - auto processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey); - - ASSERT_EQ(processor->mMetricsManagers.size(), 1u); - sp metricsManager = processor->mMetricsManagers.begin()->second; - EXPECT_TRUE(metricsManager->isConfigValid()); - ASSERT_EQ(metricsManager->mAllMetricProducers.size(), 1); - EXPECT_TRUE(metricsManager->isActive()); - sp metricProducer = metricsManager->mAllMetricProducers[0]; - EXPECT_TRUE(metricProducer->mIsActive); - ASSERT_EQ(metricProducer->mSlicedStateAtoms.size(), 1); - EXPECT_EQ(metricProducer->mSlicedStateAtoms.at(0), UID_PROCESS_STATE_ATOM_ID); - ASSERT_EQ(metricProducer->mStateGroupMap.size(), 0); - - // Check that StateTrackers were initialized correctly. - EXPECT_EQ(1, StateManager::getInstance().getStateTrackersCount()); - EXPECT_EQ(1, StateManager::getInstance().getListenersCount(UID_PROCESS_STATE_ATOM_ID)); - - // Initialize log events. - int appUid1 = 1001; - int appUid2 = 1002; - std::vector attributionUids1 = {appUid1}; - std::vector attributionTags1 = {"App1"}; - - std::vector attributionUids2 = {appUid2}; - std::vector attributionTags2 = {"App2"}; - - std::vector> events; - events.push_back(CreateUidProcessStateChangedEvent( - bucketStartTimeNs + 10 * NS_PER_SEC, appUid1, - android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_FOREGROUND)); // 0:20 - events.push_back(CreateAcquireWakelockEvent(bucketStartTimeNs + 20 * NS_PER_SEC, - attributionUids1, attributionTags1, - "wakelock1")); // 0:30 - events.push_back(CreateAcquireWakelockEvent(bucketStartTimeNs + 25 * NS_PER_SEC, - attributionUids1, attributionTags1, - "wakelock2")); // 0:35 - events.push_back(CreateAcquireWakelockEvent(bucketStartTimeNs + 30 * NS_PER_SEC, - attributionUids2, attributionTags2, - "wakelock1")); // 0:40 - events.push_back(CreateAcquireWakelockEvent(bucketStartTimeNs + 35 * NS_PER_SEC, - attributionUids2, attributionTags2, - "wakelock2")); // 0:45 - events.push_back(CreateUidProcessStateChangedEvent( - bucketStartTimeNs + 50 * NS_PER_SEC, appUid2, - android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_BACKGROUND)); // 1:00 - events.push_back(CreateUidProcessStateChangedEvent( - bucketStartTimeNs + 60 * NS_PER_SEC, appUid1, - android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_BACKGROUND)); // 1:10 - events.push_back(CreateReleaseWakelockEvent(bucketStartTimeNs + 100 * NS_PER_SEC, - attributionUids2, attributionTags2, - "wakelock1")); // 1:50 - events.push_back(CreateUidProcessStateChangedEvent( - bucketStartTimeNs + 120 * NS_PER_SEC, appUid2, - android::app::ProcessStateEnum::PROCESS_STATE_FOREGROUND_SERVICE)); // 2:10 - events.push_back(CreateReleaseWakelockEvent(bucketStartTimeNs + 200 * NS_PER_SEC, - attributionUids1, attributionTags1, - "wakelock2")); // 3:30 - - // Send log events to StatsLogProcessor. - for (auto& event : events) { - processor->OnLogEvent(event.get()); - } - - // Check dump report. - vector buffer; - ConfigMetricsReportList reports; - processor->onDumpReport(cfgKey, bucketStartTimeNs + 320 * NS_PER_SEC, - true /* include current partial bucket */, true, ADB_DUMP, FAST, - &buffer); - ASSERT_GT(buffer.size(), 0); - EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size())); - backfillDimensionPath(&reports); - backfillStringInReport(&reports); - backfillStartEndTimestamp(&reports); - - ASSERT_EQ(1, reports.reports_size()); - ASSERT_EQ(1, reports.reports(0).metrics_size()); - EXPECT_TRUE(reports.reports(0).metrics(0).has_duration_metrics()); - StatsLogReport::DurationMetricDataWrapper durationMetrics; - sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).duration_metrics(), - &durationMetrics); - ASSERT_EQ(9, durationMetrics.data_size()); - - DurationMetricData data = durationMetrics.data(0); - ValidateWakelockAttributionUidAndTagDimension(data.dimensions_in_what(), 10, appUid1, - "wakelock1"); - ASSERT_EQ(1, data.slice_by_state_size()); - EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); - EXPECT_TRUE(data.slice_by_state(0).has_value()); - EXPECT_EQ(android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_FOREGROUND, - data.slice_by_state(0).value()); - ASSERT_EQ(1, data.bucket_info_size()); - EXPECT_EQ(40 * NS_PER_SEC, data.bucket_info(0).duration_nanos()); - EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).start_bucket_elapsed_nanos()); - EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(0).end_bucket_elapsed_nanos()); - - data = durationMetrics.data(1); - ValidateWakelockAttributionUidAndTagDimension(data.dimensions_in_what(), 10, appUid1, - "wakelock1"); - ASSERT_EQ(1, data.slice_by_state_size()); - EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); - EXPECT_TRUE(data.slice_by_state(0).has_value()); - EXPECT_EQ(android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_BACKGROUND, - data.slice_by_state(0).value()); - ASSERT_EQ(2, data.bucket_info_size()); - EXPECT_EQ(240 * NS_PER_SEC, data.bucket_info(0).duration_nanos()); - EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).start_bucket_elapsed_nanos()); - EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(0).end_bucket_elapsed_nanos()); - EXPECT_EQ(20 * NS_PER_SEC, data.bucket_info(1).duration_nanos()); - EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(1).start_bucket_elapsed_nanos()); - EXPECT_EQ(330 * NS_PER_SEC, data.bucket_info(1).end_bucket_elapsed_nanos()); - - data = durationMetrics.data(2); - ValidateWakelockAttributionUidAndTagDimension(data.dimensions_in_what(), 10, appUid1, - "wakelock2"); - ASSERT_EQ(1, data.slice_by_state_size()); - EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); - EXPECT_TRUE(data.slice_by_state(0).has_value()); - EXPECT_EQ(android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_FOREGROUND, - data.slice_by_state(0).value()); - ASSERT_EQ(1, data.bucket_info_size()); - EXPECT_EQ(35 * NS_PER_SEC, data.bucket_info(0).duration_nanos()); - EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).start_bucket_elapsed_nanos()); - EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(0).end_bucket_elapsed_nanos()); - - data = durationMetrics.data(3); - ValidateWakelockAttributionUidAndTagDimension(data.dimensions_in_what(), 10, appUid1, - "wakelock2"); - ASSERT_EQ(1, data.slice_by_state_size()); - EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); - EXPECT_TRUE(data.slice_by_state(0).has_value()); - EXPECT_EQ(android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_BACKGROUND, - data.slice_by_state(0).value()); - ASSERT_EQ(1, data.bucket_info_size()); - EXPECT_EQ(140 * NS_PER_SEC, data.bucket_info(0).duration_nanos()); - EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).start_bucket_elapsed_nanos()); - EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(0).end_bucket_elapsed_nanos()); - - data = durationMetrics.data(4); - ValidateWakelockAttributionUidAndTagDimension(data.dimensions_in_what(), 10, appUid2, - "wakelock1"); - ASSERT_EQ(1, data.slice_by_state_size()); - EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); - EXPECT_TRUE(data.slice_by_state(0).has_value()); - EXPECT_EQ(-1 /* StateTracker:: kStateUnknown */, data.slice_by_state(0).value()); - ASSERT_EQ(1, data.bucket_info_size()); - EXPECT_EQ(20 * NS_PER_SEC, data.bucket_info(0).duration_nanos()); - EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).start_bucket_elapsed_nanos()); - EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(0).end_bucket_elapsed_nanos()); - - data = durationMetrics.data(5); - ValidateWakelockAttributionUidAndTagDimension(data.dimensions_in_what(), 10, appUid2, - "wakelock1"); - ASSERT_EQ(1, data.slice_by_state_size()); - EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); - EXPECT_TRUE(data.slice_by_state(0).has_value()); - EXPECT_EQ(android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_BACKGROUND, - data.slice_by_state(0).value()); - ASSERT_EQ(1, data.bucket_info_size()); - EXPECT_EQ(50 * NS_PER_SEC, data.bucket_info(0).duration_nanos()); - EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).start_bucket_elapsed_nanos()); - EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(0).end_bucket_elapsed_nanos()); - - data = durationMetrics.data(6); - ValidateWakelockAttributionUidAndTagDimension(data.dimensions_in_what(), 10, appUid2, - "wakelock2"); - ASSERT_EQ(1, data.slice_by_state_size()); - EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); - EXPECT_TRUE(data.slice_by_state(0).has_value()); - EXPECT_EQ(-1 /* StateTracker:: kStateUnknown */, data.slice_by_state(0).value()); - ASSERT_EQ(1, data.bucket_info_size()); - EXPECT_EQ(15 * NS_PER_SEC, data.bucket_info(0).duration_nanos()); - EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).start_bucket_elapsed_nanos()); - EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(0).end_bucket_elapsed_nanos()); - - data = durationMetrics.data(7); - ValidateWakelockAttributionUidAndTagDimension(data.dimensions_in_what(), 10, appUid2, - "wakelock2"); - ASSERT_EQ(1, data.slice_by_state_size()); - EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); - EXPECT_TRUE(data.slice_by_state(0).has_value()); - EXPECT_EQ(android::app::ProcessStateEnum::PROCESS_STATE_FOREGROUND_SERVICE, - data.slice_by_state(0).value()); - ASSERT_EQ(2, data.bucket_info_size()); - EXPECT_EQ(180 * NS_PER_SEC, data.bucket_info(0).duration_nanos()); - EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).start_bucket_elapsed_nanos()); - EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(0).end_bucket_elapsed_nanos()); - EXPECT_EQ(20 * NS_PER_SEC, data.bucket_info(1).duration_nanos()); - EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(1).start_bucket_elapsed_nanos()); - EXPECT_EQ(330 * NS_PER_SEC, data.bucket_info(1).end_bucket_elapsed_nanos()); - - data = durationMetrics.data(8); - ValidateWakelockAttributionUidAndTagDimension(data.dimensions_in_what(), 10, appUid2, - "wakelock2"); - ASSERT_EQ(1, data.slice_by_state_size()); - EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); - EXPECT_TRUE(data.slice_by_state(0).has_value()); - EXPECT_EQ(android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_BACKGROUND, - data.slice_by_state(0).value()); - ASSERT_EQ(1, data.bucket_info_size()); - EXPECT_EQ(70 * NS_PER_SEC, data.bucket_info(0).duration_nanos()); - EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).start_bucket_elapsed_nanos()); - EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(0).end_bucket_elapsed_nanos()); -} - -TEST(DurationMetricE2eTest, TestUploadThreshold) { - StatsdConfig config; - config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root. - - auto screenOnMatcher = CreateScreenTurnedOnAtomMatcher(); - auto screenOffMatcher = CreateScreenTurnedOffAtomMatcher(); - *config.add_atom_matcher() = screenOnMatcher; - *config.add_atom_matcher() = screenOffMatcher; - - auto durationPredicate = CreateScreenIsOnPredicate(); - *config.add_predicate() = durationPredicate; - - int64_t thresholdDurationNs = 30 * 1000 * 1000 * 1000LL; // 30 seconds - UploadThreshold threshold; - threshold.set_gt_int(thresholdDurationNs); - - int64_t metricId = 123456; - auto durationMetric = config.add_duration_metric(); - durationMetric->set_id(metricId); - durationMetric->set_what(durationPredicate.id()); - durationMetric->set_bucket(FIVE_MINUTES); - durationMetric->set_aggregation_type(DurationMetric_AggregationType_SUM); - *durationMetric->mutable_threshold() = threshold; - - const int64_t baseTimeNs = 0; // 0:00 - const int64_t configAddedTimeNs = baseTimeNs + 1 * NS_PER_SEC; // 0:01 - const int64_t bucketSizeNs = - TimeUnitToBucketSizeInMillis(config.duration_metric(0).bucket()) * 1000LL * 1000LL; - - int uid = 12345; - int64_t cfgId = 98765; - ConfigKey cfgKey(uid, cfgId); - - auto processor = CreateStatsLogProcessor(baseTimeNs, configAddedTimeNs, config, cfgKey); - - ASSERT_EQ(processor->mMetricsManagers.size(), 1u); - sp metricsManager = processor->mMetricsManagers.begin()->second; - EXPECT_TRUE(metricsManager->isConfigValid()); - ASSERT_EQ(metricsManager->mAllMetricProducers.size(), 1); - sp metricProducer = metricsManager->mAllMetricProducers[0]; - EXPECT_TRUE(metricsManager->isActive()); - EXPECT_TRUE(metricProducer->mIsActive); - - std::unique_ptr event; - - // Screen is off at start of first bucket. - event = CreateScreenStateChangedEvent(configAddedTimeNs, - android::view::DISPLAY_STATE_OFF); // 0:01 - processor->OnLogEvent(event.get()); - - // Turn screen on. - const int64_t durationStartNs = configAddedTimeNs + 10 * NS_PER_SEC; // 0:11 - event = CreateScreenStateChangedEvent(durationStartNs, android::view::DISPLAY_STATE_ON); - processor->OnLogEvent(event.get()); - - // Turn off screen 30 seconds after turning on. - const int64_t durationEndNs = durationStartNs + 30 * NS_PER_SEC; // 0:41 - event = CreateScreenStateChangedEvent(durationEndNs, android::view::DISPLAY_STATE_OFF); - processor->OnLogEvent(event.get()); - - // Turn screen on in second bucket. - const int64_t duration2StartNs = configAddedTimeNs + bucketSizeNs + 10 * NS_PER_SEC; // 5:11 - event = CreateScreenStateChangedEvent(duration2StartNs, android::view::DISPLAY_STATE_ON); - processor->OnLogEvent(event.get()); - - // Turn off screen 31 seconds after turning on. - const int64_t duration2EndNs = duration2StartNs + 31 * NS_PER_SEC; // 5:42 - event = CreateScreenStateChangedEvent(duration2EndNs, android::view::DISPLAY_STATE_OFF); - processor->OnLogEvent(event.get()); - - ConfigMetricsReportList reports; - vector buffer; - processor->onDumpReport(cfgKey, configAddedTimeNs + bucketSizeNs * 2 + 1 * NS_PER_SEC, false, - true, ADB_DUMP, FAST, &buffer); // 10:01 - EXPECT_TRUE(buffer.size() > 0); - EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size())); - backfillDimensionPath(&reports); - backfillStartEndTimestamp(&reports); - ASSERT_EQ(1, reports.reports_size()); - ASSERT_EQ(1, reports.reports(0).metrics_size()); - EXPECT_EQ(metricId, reports.reports(0).metrics(0).metric_id()); - EXPECT_TRUE(reports.reports(0).metrics(0).has_duration_metrics()); - - StatsLogReport::DurationMetricDataWrapper durationMetrics; - sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).duration_metrics(), - &durationMetrics); - ASSERT_EQ(1, durationMetrics.data_size()); - - DurationMetricData data = durationMetrics.data(0); - ASSERT_EQ(1, data.bucket_info_size()); - EXPECT_EQ(duration2EndNs - duration2StartNs, data.bucket_info(0).duration_nanos()); - EXPECT_EQ(baseTimeNs + bucketSizeNs, data.bucket_info(0).start_bucket_elapsed_nanos()); - EXPECT_EQ(baseTimeNs + bucketSizeNs * 2, data.bucket_info(0).end_bucket_elapsed_nanos()); -} - -#else -GTEST_LOG_(INFO) << "This test does nothing.\n"; -#endif - -} // namespace statsd -} // namespace os -} // namespace android diff --git a/bin/tests/e2e/GaugeMetric_e2e_pull_test.cpp b/bin/tests/e2e/GaugeMetric_e2e_pull_test.cpp deleted file mode 100644 index 1be26129..00000000 --- a/bin/tests/e2e/GaugeMetric_e2e_pull_test.cpp +++ /dev/null @@ -1,624 +0,0 @@ -// 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 - -#include - -#include "src/StatsLogProcessor.h" -#include "src/stats_log_util.h" -#include "tests/statsd_test_util.h" - -using ::ndk::SharedRefBase; - -namespace android { -namespace os { -namespace statsd { - -#ifdef __ANDROID__ - -namespace { - -const int64_t metricId = 123456; -const int32_t ATOM_TAG = util::SUBSYSTEM_SLEEP_STATE; - -StatsdConfig CreateStatsdConfig(const GaugeMetric::SamplingType sampling_type, - bool useCondition = true) { - StatsdConfig config; - config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root. - config.add_default_pull_packages("AID_ROOT"); // Fake puller is registered with root. - auto atomMatcher = CreateSimpleAtomMatcher("TestMatcher", ATOM_TAG); - *config.add_atom_matcher() = atomMatcher; - *config.add_atom_matcher() = CreateScreenTurnedOnAtomMatcher(); - *config.add_atom_matcher() = CreateScreenTurnedOffAtomMatcher(); - - auto screenIsOffPredicate = CreateScreenIsOffPredicate(); - *config.add_predicate() = screenIsOffPredicate; - - auto gaugeMetric = config.add_gauge_metric(); - gaugeMetric->set_id(metricId); - gaugeMetric->set_what(atomMatcher.id()); - if (useCondition) { - gaugeMetric->set_condition(screenIsOffPredicate.id()); - } - gaugeMetric->set_sampling_type(sampling_type); - gaugeMetric->mutable_gauge_fields_filter()->set_include_all(true); - *gaugeMetric->mutable_dimensions_in_what() = - CreateDimensions(ATOM_TAG, {1 /* subsystem name */}); - gaugeMetric->set_bucket(FIVE_MINUTES); - gaugeMetric->set_max_pull_delay_sec(INT_MAX); - config.set_hash_strings_in_metric_report(false); - - return config; -} - -} // namespaces - -TEST(GaugeMetricE2eTest, TestRandomSamplePulledEvents) { - auto config = CreateStatsdConfig(GaugeMetric::RANDOM_ONE_SAMPLE); - int64_t baseTimeNs = getElapsedRealtimeNs(); - int64_t configAddedTimeNs = 10 * 60 * NS_PER_SEC + baseTimeNs; - int64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(config.gauge_metric(0).bucket()) * 1000000; - - ConfigKey cfgKey; - auto processor = - CreateStatsLogProcessor(baseTimeNs, configAddedTimeNs, config, cfgKey, - SharedRefBase::make(), ATOM_TAG); - ASSERT_EQ(processor->mMetricsManagers.size(), 1u); - EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid()); - processor->mPullerManager->ForceClearPullerCache(); - - int startBucketNum = processor->mMetricsManagers.begin() - ->second->mAllMetricProducers[0] - ->getCurrentBucketNum(); - EXPECT_GT(startBucketNum, (int64_t)0); - - // When creating the config, the gauge metric producer should register the alarm at the - // end of the current bucket. - ASSERT_EQ((size_t)1, processor->mPullerManager->mReceivers.size()); - EXPECT_EQ(bucketSizeNs, - processor->mPullerManager->mReceivers.begin()->second.front().intervalNs); - int64_t& nextPullTimeNs = - processor->mPullerManager->mReceivers.begin()->second.front().nextPullTimeNs; - EXPECT_EQ(baseTimeNs + startBucketNum * bucketSizeNs + bucketSizeNs, nextPullTimeNs); - - auto screenOffEvent = - CreateScreenStateChangedEvent(configAddedTimeNs + 55, android::view::DISPLAY_STATE_OFF); - processor->OnLogEvent(screenOffEvent.get()); - - // Pulling alarm arrives on time and reset the sequential pulling alarm. - processor->informPullAlarmFired(nextPullTimeNs + 1); - EXPECT_EQ(baseTimeNs + startBucketNum * bucketSizeNs + 2 * bucketSizeNs, nextPullTimeNs); - - auto screenOnEvent = CreateScreenStateChangedEvent(configAddedTimeNs + bucketSizeNs + 10, - android::view::DISPLAY_STATE_ON); - processor->OnLogEvent(screenOnEvent.get()); - - screenOffEvent = CreateScreenStateChangedEvent(configAddedTimeNs + bucketSizeNs + 100, - android::view::DISPLAY_STATE_OFF); - processor->OnLogEvent(screenOffEvent.get()); - - processor->informPullAlarmFired(nextPullTimeNs + 1); - EXPECT_EQ(baseTimeNs + startBucketNum * bucketSizeNs + 3 * bucketSizeNs, nextPullTimeNs); - - processor->informPullAlarmFired(nextPullTimeNs + 1); - EXPECT_EQ(baseTimeNs + startBucketNum * bucketSizeNs + 4 * bucketSizeNs, nextPullTimeNs); - - screenOnEvent = CreateScreenStateChangedEvent(configAddedTimeNs + 3 * bucketSizeNs + 2, - android::view::DISPLAY_STATE_ON); - processor->OnLogEvent(screenOnEvent.get()); - - processor->informPullAlarmFired(nextPullTimeNs + 3); - EXPECT_EQ(baseTimeNs + startBucketNum * bucketSizeNs + 5 * bucketSizeNs, nextPullTimeNs); - - screenOffEvent = CreateScreenStateChangedEvent(configAddedTimeNs + 5 * bucketSizeNs + 1, - android::view::DISPLAY_STATE_OFF); - processor->OnLogEvent(screenOffEvent.get()); - - processor->informPullAlarmFired(nextPullTimeNs + 2); - EXPECT_EQ(baseTimeNs + startBucketNum * bucketSizeNs + 6 * bucketSizeNs, nextPullTimeNs); - - processor->informPullAlarmFired(nextPullTimeNs + 2); - - ConfigMetricsReportList reports; - vector buffer; - processor->onDumpReport(cfgKey, configAddedTimeNs + 7 * bucketSizeNs + 10, false, true, - ADB_DUMP, FAST, &buffer); - EXPECT_TRUE(buffer.size() > 0); - EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size())); - backfillDimensionPath(&reports); - backfillStringInReport(&reports); - backfillStartEndTimestamp(&reports); - ASSERT_EQ(1, reports.reports_size()); - ASSERT_EQ(1, reports.reports(0).metrics_size()); - StatsLogReport::GaugeMetricDataWrapper gaugeMetrics; - sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).gauge_metrics(), &gaugeMetrics); - ASSERT_GT((int)gaugeMetrics.data_size(), 1); - - auto data = gaugeMetrics.data(0); - EXPECT_EQ(ATOM_TAG, data.dimensions_in_what().field()); - ASSERT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size()); - EXPECT_EQ(1 /* subsystem name field */, - data.dimensions_in_what().value_tuple().dimensions_value(0).field()); - EXPECT_FALSE(data.dimensions_in_what().value_tuple().dimensions_value(0).value_str().empty()); - ASSERT_EQ(6, data.bucket_info_size()); - - ASSERT_EQ(1, data.bucket_info(0).atom_size()); - ASSERT_EQ(1, data.bucket_info(0).elapsed_timestamp_nanos_size()); - EXPECT_EQ(configAddedTimeNs + 55, data.bucket_info(0).elapsed_timestamp_nanos(0)); - ASSERT_EQ(0, data.bucket_info(0).wall_clock_timestamp_nanos_size()); - EXPECT_EQ(baseTimeNs + 2 * bucketSizeNs, data.bucket_info(0).start_bucket_elapsed_nanos()); - EXPECT_EQ(baseTimeNs + 3 * bucketSizeNs, data.bucket_info(0).end_bucket_elapsed_nanos()); - EXPECT_TRUE(data.bucket_info(0).atom(0).subsystem_sleep_state().subsystem_name().empty()); - EXPECT_GT(data.bucket_info(0).atom(0).subsystem_sleep_state().time_millis(), 0); - - ASSERT_EQ(1, data.bucket_info(1).atom_size()); - EXPECT_EQ(baseTimeNs + 3 * bucketSizeNs + 1, data.bucket_info(1).elapsed_timestamp_nanos(0)); - EXPECT_EQ(baseTimeNs + 3 * bucketSizeNs + 1, data.bucket_info(1).elapsed_timestamp_nanos(0)); - EXPECT_EQ(baseTimeNs + 3 * bucketSizeNs, data.bucket_info(1).start_bucket_elapsed_nanos()); - EXPECT_EQ(baseTimeNs + 4 * bucketSizeNs, data.bucket_info(1).end_bucket_elapsed_nanos()); - EXPECT_TRUE(data.bucket_info(1).atom(0).subsystem_sleep_state().subsystem_name().empty()); - EXPECT_GT(data.bucket_info(1).atom(0).subsystem_sleep_state().time_millis(), 0); - - ASSERT_EQ(1, data.bucket_info(2).atom_size()); - ASSERT_EQ(1, data.bucket_info(2).elapsed_timestamp_nanos_size()); - EXPECT_EQ(baseTimeNs + 4 * bucketSizeNs + 1, data.bucket_info(2).elapsed_timestamp_nanos(0)); - EXPECT_EQ(baseTimeNs + 4 * bucketSizeNs, data.bucket_info(2).start_bucket_elapsed_nanos()); - EXPECT_EQ(baseTimeNs + 5 * bucketSizeNs, data.bucket_info(2).end_bucket_elapsed_nanos()); - EXPECT_TRUE(data.bucket_info(2).atom(0).subsystem_sleep_state().subsystem_name().empty()); - EXPECT_GT(data.bucket_info(2).atom(0).subsystem_sleep_state().time_millis(), 0); - - ASSERT_EQ(1, data.bucket_info(3).atom_size()); - ASSERT_EQ(1, data.bucket_info(3).elapsed_timestamp_nanos_size()); - EXPECT_EQ(baseTimeNs + 5 * bucketSizeNs + 1, data.bucket_info(3).elapsed_timestamp_nanos(0)); - EXPECT_EQ(baseTimeNs + 5 * bucketSizeNs, data.bucket_info(3).start_bucket_elapsed_nanos()); - EXPECT_EQ(baseTimeNs + 6 * bucketSizeNs, data.bucket_info(3).end_bucket_elapsed_nanos()); - EXPECT_TRUE(data.bucket_info(3).atom(0).subsystem_sleep_state().subsystem_name().empty()); - EXPECT_GT(data.bucket_info(3).atom(0).subsystem_sleep_state().time_millis(), 0); - - ASSERT_EQ(1, data.bucket_info(4).atom_size()); - ASSERT_EQ(1, data.bucket_info(4).elapsed_timestamp_nanos_size()); - EXPECT_EQ(baseTimeNs + 7 * bucketSizeNs + 1, data.bucket_info(4).elapsed_timestamp_nanos(0)); - EXPECT_EQ(baseTimeNs + 7 * bucketSizeNs, data.bucket_info(4).start_bucket_elapsed_nanos()); - EXPECT_EQ(baseTimeNs + 8 * bucketSizeNs, data.bucket_info(4).end_bucket_elapsed_nanos()); - EXPECT_TRUE(data.bucket_info(4).atom(0).subsystem_sleep_state().subsystem_name().empty()); - EXPECT_GT(data.bucket_info(4).atom(0).subsystem_sleep_state().time_millis(), 0); - - ASSERT_EQ(1, data.bucket_info(5).atom_size()); - ASSERT_EQ(1, data.bucket_info(5).elapsed_timestamp_nanos_size()); - EXPECT_EQ(baseTimeNs + 8 * bucketSizeNs + 2, data.bucket_info(5).elapsed_timestamp_nanos(0)); - EXPECT_EQ(baseTimeNs + 8 * bucketSizeNs, data.bucket_info(5).start_bucket_elapsed_nanos()); - EXPECT_EQ(baseTimeNs + 9 * bucketSizeNs, data.bucket_info(5).end_bucket_elapsed_nanos()); - EXPECT_TRUE(data.bucket_info(5).atom(0).subsystem_sleep_state().subsystem_name().empty()); - EXPECT_GT(data.bucket_info(5).atom(0).subsystem_sleep_state().time_millis(), 0); -} - -TEST(GaugeMetricE2eTest, TestConditionChangeToTrueSamplePulledEvents) { - auto config = CreateStatsdConfig(GaugeMetric::CONDITION_CHANGE_TO_TRUE); - int64_t baseTimeNs = getElapsedRealtimeNs(); - int64_t configAddedTimeNs = 10 * 60 * NS_PER_SEC + baseTimeNs; - int64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(config.gauge_metric(0).bucket()) * 1000000; - - ConfigKey cfgKey; - auto processor = - CreateStatsLogProcessor(baseTimeNs, configAddedTimeNs, config, cfgKey, - SharedRefBase::make(), ATOM_TAG); - ASSERT_EQ(processor->mMetricsManagers.size(), 1u); - EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid()); - processor->mPullerManager->ForceClearPullerCache(); - - int startBucketNum = processor->mMetricsManagers.begin() - ->second->mAllMetricProducers[0] - ->getCurrentBucketNum(); - EXPECT_GT(startBucketNum, (int64_t)0); - - auto screenOffEvent = - CreateScreenStateChangedEvent(configAddedTimeNs + 55, android::view::DISPLAY_STATE_OFF); - processor->OnLogEvent(screenOffEvent.get()); - - auto screenOnEvent = CreateScreenStateChangedEvent(configAddedTimeNs + bucketSizeNs + 10, - android::view::DISPLAY_STATE_ON); - processor->OnLogEvent(screenOnEvent.get()); - - screenOffEvent = CreateScreenStateChangedEvent(configAddedTimeNs + bucketSizeNs + 100, - android::view::DISPLAY_STATE_OFF); - processor->OnLogEvent(screenOffEvent.get()); - - screenOnEvent = CreateScreenStateChangedEvent(configAddedTimeNs + 3 * bucketSizeNs + 2, - android::view::DISPLAY_STATE_ON); - processor->OnLogEvent(screenOnEvent.get()); - - screenOffEvent = CreateScreenStateChangedEvent(configAddedTimeNs + 5 * bucketSizeNs + 1, - android::view::DISPLAY_STATE_OFF); - processor->OnLogEvent(screenOffEvent.get()); - screenOnEvent = CreateScreenStateChangedEvent(configAddedTimeNs + 5 * bucketSizeNs + 3, - android::view::DISPLAY_STATE_ON); - processor->OnLogEvent(screenOnEvent.get()); - screenOffEvent = CreateScreenStateChangedEvent(configAddedTimeNs + 5 * bucketSizeNs + 10, - android::view::DISPLAY_STATE_OFF); - processor->OnLogEvent(screenOffEvent.get()); - - ConfigMetricsReportList reports; - vector buffer; - processor->onDumpReport(cfgKey, configAddedTimeNs + 8 * bucketSizeNs + 10, false, true, - ADB_DUMP, FAST, &buffer); - EXPECT_TRUE(buffer.size() > 0); - EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size())); - backfillDimensionPath(&reports); - backfillStringInReport(&reports); - backfillStartEndTimestamp(&reports); - ASSERT_EQ(1, reports.reports_size()); - ASSERT_EQ(1, reports.reports(0).metrics_size()); - StatsLogReport::GaugeMetricDataWrapper gaugeMetrics; - sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).gauge_metrics(), &gaugeMetrics); - ASSERT_GT((int)gaugeMetrics.data_size(), 1); - - auto data = gaugeMetrics.data(0); - EXPECT_EQ(ATOM_TAG, data.dimensions_in_what().field()); - ASSERT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size()); - EXPECT_EQ(1 /* subsystem name field */, - data.dimensions_in_what().value_tuple().dimensions_value(0).field()); - EXPECT_FALSE(data.dimensions_in_what().value_tuple().dimensions_value(0).value_str().empty()); - ASSERT_EQ(3, data.bucket_info_size()); - - ASSERT_EQ(1, data.bucket_info(0).atom_size()); - ASSERT_EQ(1, data.bucket_info(0).elapsed_timestamp_nanos_size()); - EXPECT_EQ(configAddedTimeNs + 55, data.bucket_info(0).elapsed_timestamp_nanos(0)); - ASSERT_EQ(0, data.bucket_info(0).wall_clock_timestamp_nanos_size()); - EXPECT_EQ(baseTimeNs + 2 * bucketSizeNs, data.bucket_info(0).start_bucket_elapsed_nanos()); - EXPECT_EQ(baseTimeNs + 3 * bucketSizeNs, data.bucket_info(0).end_bucket_elapsed_nanos()); - EXPECT_TRUE(data.bucket_info(0).atom(0).subsystem_sleep_state().subsystem_name().empty()); - EXPECT_GT(data.bucket_info(0).atom(0).subsystem_sleep_state().time_millis(), 0); - - ASSERT_EQ(1, data.bucket_info(1).atom_size()); - EXPECT_EQ(baseTimeNs + 3 * bucketSizeNs + 100, data.bucket_info(1).elapsed_timestamp_nanos(0)); - EXPECT_EQ(configAddedTimeNs + 55, data.bucket_info(0).elapsed_timestamp_nanos(0)); - EXPECT_EQ(baseTimeNs + 3 * bucketSizeNs, data.bucket_info(1).start_bucket_elapsed_nanos()); - EXPECT_EQ(baseTimeNs + 4 * bucketSizeNs, data.bucket_info(1).end_bucket_elapsed_nanos()); - EXPECT_TRUE(data.bucket_info(1).atom(0).subsystem_sleep_state().subsystem_name().empty()); - EXPECT_GT(data.bucket_info(1).atom(0).subsystem_sleep_state().time_millis(), 0); - - ASSERT_EQ(2, data.bucket_info(2).atom_size()); - ASSERT_EQ(2, data.bucket_info(2).elapsed_timestamp_nanos_size()); - EXPECT_EQ(baseTimeNs + 7 * bucketSizeNs + 1, data.bucket_info(2).elapsed_timestamp_nanos(0)); - EXPECT_EQ(baseTimeNs + 7 * bucketSizeNs + 10, data.bucket_info(2).elapsed_timestamp_nanos(1)); - EXPECT_EQ(baseTimeNs + 7 * bucketSizeNs, data.bucket_info(2).start_bucket_elapsed_nanos()); - EXPECT_EQ(baseTimeNs + 8 * bucketSizeNs, data.bucket_info(2).end_bucket_elapsed_nanos()); - EXPECT_TRUE(data.bucket_info(2).atom(0).subsystem_sleep_state().subsystem_name().empty()); - EXPECT_GT(data.bucket_info(2).atom(0).subsystem_sleep_state().time_millis(), 0); - EXPECT_TRUE(data.bucket_info(2).atom(1).subsystem_sleep_state().subsystem_name().empty()); - EXPECT_GT(data.bucket_info(2).atom(1).subsystem_sleep_state().time_millis(), 0); -} - -TEST(GaugeMetricE2eTest, TestRandomSamplePulledEvent_LateAlarm) { - auto config = CreateStatsdConfig(GaugeMetric::RANDOM_ONE_SAMPLE); - int64_t baseTimeNs = getElapsedRealtimeNs(); - int64_t configAddedTimeNs = 10 * 60 * NS_PER_SEC + baseTimeNs; - int64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(config.gauge_metric(0).bucket()) * 1000000; - - ConfigKey cfgKey; - auto processor = - CreateStatsLogProcessor(baseTimeNs, configAddedTimeNs, config, cfgKey, - SharedRefBase::make(), ATOM_TAG); - ASSERT_EQ(processor->mMetricsManagers.size(), 1u); - EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid()); - processor->mPullerManager->ForceClearPullerCache(); - - int startBucketNum = processor->mMetricsManagers.begin() - ->second->mAllMetricProducers[0] - ->getCurrentBucketNum(); - EXPECT_GT(startBucketNum, (int64_t)0); - - // When creating the config, the gauge metric producer should register the alarm at the - // end of the current bucket. - ASSERT_EQ((size_t)1, processor->mPullerManager->mReceivers.size()); - EXPECT_EQ(bucketSizeNs, - processor->mPullerManager->mReceivers.begin()->second.front().intervalNs); - int64_t& nextPullTimeNs = - processor->mPullerManager->mReceivers.begin()->second.front().nextPullTimeNs; - EXPECT_EQ(baseTimeNs + startBucketNum * bucketSizeNs + bucketSizeNs, nextPullTimeNs); - - auto screenOffEvent = - CreateScreenStateChangedEvent(configAddedTimeNs + 55, android::view::DISPLAY_STATE_OFF); - processor->OnLogEvent(screenOffEvent.get()); - - auto screenOnEvent = CreateScreenStateChangedEvent(configAddedTimeNs + bucketSizeNs + 10, - android::view::DISPLAY_STATE_ON); - processor->OnLogEvent(screenOnEvent.get()); - - // Pulling alarm arrives one bucket size late. - processor->informPullAlarmFired(nextPullTimeNs + bucketSizeNs); - EXPECT_EQ(baseTimeNs + startBucketNum * bucketSizeNs + 3 * bucketSizeNs, nextPullTimeNs); - - screenOffEvent = CreateScreenStateChangedEvent(configAddedTimeNs + 3 * bucketSizeNs + 11, - android::view::DISPLAY_STATE_OFF); - processor->OnLogEvent(screenOffEvent.get()); - - // Pulling alarm arrives more than one bucket size late. - processor->informPullAlarmFired(nextPullTimeNs + bucketSizeNs + 12); - EXPECT_EQ(baseTimeNs + startBucketNum * bucketSizeNs + 5 * bucketSizeNs, nextPullTimeNs); - - ConfigMetricsReportList reports; - vector buffer; - processor->onDumpReport(cfgKey, configAddedTimeNs + 7 * bucketSizeNs + 10, false, true, - ADB_DUMP, FAST, &buffer); - EXPECT_TRUE(buffer.size() > 0); - EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size())); - backfillDimensionPath(&reports); - backfillStringInReport(&reports); - backfillStartEndTimestamp(&reports); - ASSERT_EQ(1, reports.reports_size()); - ASSERT_EQ(1, reports.reports(0).metrics_size()); - StatsLogReport::GaugeMetricDataWrapper gaugeMetrics; - sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).gauge_metrics(), &gaugeMetrics); - ASSERT_GT((int)gaugeMetrics.data_size(), 1); - - auto data = gaugeMetrics.data(0); - EXPECT_EQ(ATOM_TAG, data.dimensions_in_what().field()); - ASSERT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size()); - EXPECT_EQ(1 /* subsystem name field */, - data.dimensions_in_what().value_tuple().dimensions_value(0).field()); - EXPECT_FALSE(data.dimensions_in_what().value_tuple().dimensions_value(0).value_str().empty()); - ASSERT_EQ(3, data.bucket_info_size()); - - ASSERT_EQ(1, data.bucket_info(0).atom_size()); - ASSERT_EQ(1, data.bucket_info(0).elapsed_timestamp_nanos_size()); - EXPECT_EQ(configAddedTimeNs + 55, data.bucket_info(0).elapsed_timestamp_nanos(0)); - EXPECT_EQ(baseTimeNs + 2 * bucketSizeNs, data.bucket_info(0).start_bucket_elapsed_nanos()); - EXPECT_EQ(baseTimeNs + 3 * bucketSizeNs, data.bucket_info(0).end_bucket_elapsed_nanos()); - EXPECT_TRUE(data.bucket_info(0).atom(0).subsystem_sleep_state().subsystem_name().empty()); - EXPECT_GT(data.bucket_info(0).atom(0).subsystem_sleep_state().time_millis(), 0); - - ASSERT_EQ(1, data.bucket_info(1).atom_size()); - EXPECT_EQ(configAddedTimeNs + 3 * bucketSizeNs + 11, - data.bucket_info(1).elapsed_timestamp_nanos(0)); - EXPECT_EQ(configAddedTimeNs + 55, data.bucket_info(0).elapsed_timestamp_nanos(0)); - EXPECT_EQ(baseTimeNs + 5 * bucketSizeNs, data.bucket_info(1).start_bucket_elapsed_nanos()); - EXPECT_EQ(baseTimeNs + 6 * bucketSizeNs, data.bucket_info(1).end_bucket_elapsed_nanos()); - EXPECT_TRUE(data.bucket_info(1).atom(0).subsystem_sleep_state().subsystem_name().empty()); - EXPECT_GT(data.bucket_info(1).atom(0).subsystem_sleep_state().time_millis(), 0); - - ASSERT_EQ(1, data.bucket_info(2).atom_size()); - ASSERT_EQ(1, data.bucket_info(2).elapsed_timestamp_nanos_size()); - EXPECT_EQ(baseTimeNs + 6 * bucketSizeNs + 12, data.bucket_info(2).elapsed_timestamp_nanos(0)); - EXPECT_EQ(baseTimeNs + 6 * bucketSizeNs, data.bucket_info(2).start_bucket_elapsed_nanos()); - EXPECT_EQ(baseTimeNs + 7 * bucketSizeNs, data.bucket_info(2).end_bucket_elapsed_nanos()); - EXPECT_TRUE(data.bucket_info(2).atom(0).subsystem_sleep_state().subsystem_name().empty()); - EXPECT_GT(data.bucket_info(2).atom(0).subsystem_sleep_state().time_millis(), 0); -} - -TEST(GaugeMetricE2eTest, TestRandomSamplePulledEventsWithActivation) { - auto config = CreateStatsdConfig(GaugeMetric::RANDOM_ONE_SAMPLE, /*useCondition=*/false); - - int64_t baseTimeNs = getElapsedRealtimeNs(); - int64_t configAddedTimeNs = 10 * 60 * NS_PER_SEC + baseTimeNs; - int64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(config.gauge_metric(0).bucket()) * 1000000; - - auto batterySaverStartMatcher = CreateBatterySaverModeStartAtomMatcher(); - *config.add_atom_matcher() = batterySaverStartMatcher; - const int64_t ttlNs = 2 * bucketSizeNs; // Two buckets. - auto metric_activation = config.add_metric_activation(); - metric_activation->set_metric_id(metricId); - metric_activation->set_activation_type(ACTIVATE_IMMEDIATELY); - auto event_activation = metric_activation->add_event_activation(); - event_activation->set_atom_matcher_id(batterySaverStartMatcher.id()); - event_activation->set_ttl_seconds(ttlNs / 1000000000); - - ConfigKey cfgKey; - auto processor = - CreateStatsLogProcessor(baseTimeNs, configAddedTimeNs, config, cfgKey, - SharedRefBase::make(), ATOM_TAG); - ASSERT_EQ(processor->mMetricsManagers.size(), 1u); - EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid()); - processor->mPullerManager->ForceClearPullerCache(); - - int startBucketNum = processor->mMetricsManagers.begin() - ->second->mAllMetricProducers[0] - ->getCurrentBucketNum(); - EXPECT_GT(startBucketNum, (int64_t)0); - EXPECT_FALSE(processor->mMetricsManagers.begin()->second->mAllMetricProducers[0]->isActive()); - - // When creating the config, the gauge metric producer should register the alarm at the - // end of the current bucket. - ASSERT_EQ((size_t)1, processor->mPullerManager->mReceivers.size()); - EXPECT_EQ(bucketSizeNs, - processor->mPullerManager->mReceivers.begin()->second.front().intervalNs); - int64_t& nextPullTimeNs = - processor->mPullerManager->mReceivers.begin()->second.front().nextPullTimeNs; - EXPECT_EQ(baseTimeNs + startBucketNum * bucketSizeNs + bucketSizeNs, nextPullTimeNs); - - // Pulling alarm arrives on time and reset the sequential pulling alarm. - // Event should not be kept. - processor->informPullAlarmFired(nextPullTimeNs + 1); // 15 mins + 1 ns. - EXPECT_EQ(baseTimeNs + startBucketNum * bucketSizeNs + 2 * bucketSizeNs, nextPullTimeNs); - EXPECT_FALSE(processor->mMetricsManagers.begin()->second->mAllMetricProducers[0]->isActive()); - - // Activate the metric. A pull occurs upon activation. - const int64_t activationNs = configAddedTimeNs + bucketSizeNs + (2 * 1000 * 1000); // 2 millis. - auto batterySaverOnEvent = CreateBatterySaverOnEvent(activationNs); - processor->OnLogEvent(batterySaverOnEvent.get()); // 15 mins + 2 ms. - EXPECT_TRUE(processor->mMetricsManagers.begin()->second->mAllMetricProducers[0]->isActive()); - - // This event should be kept. 2 total. - processor->informPullAlarmFired(nextPullTimeNs + 1); // 20 mins + 1 ns. - EXPECT_EQ(baseTimeNs + startBucketNum * bucketSizeNs + 3 * bucketSizeNs, nextPullTimeNs); - - // This event should be kept. 3 total. - processor->informPullAlarmFired(nextPullTimeNs + 2); // 25 mins + 2 ns. - EXPECT_EQ(baseTimeNs + startBucketNum * bucketSizeNs + 4 * bucketSizeNs, nextPullTimeNs); - - // Create random event to deactivate metric. - auto deactivationEvent = CreateScreenBrightnessChangedEvent(activationNs + ttlNs + 1, 50); - processor->OnLogEvent(deactivationEvent.get()); - EXPECT_FALSE(processor->mMetricsManagers.begin()->second->mAllMetricProducers[0]->isActive()); - - // Event should not be kept. 3 total. - processor->informPullAlarmFired(nextPullTimeNs + 3); - EXPECT_EQ(baseTimeNs + startBucketNum * bucketSizeNs + 5 * bucketSizeNs, nextPullTimeNs); - - processor->informPullAlarmFired(nextPullTimeNs + 2); - - ConfigMetricsReportList reports; - vector buffer; - processor->onDumpReport(cfgKey, configAddedTimeNs + 7 * bucketSizeNs + 10, false, true, - ADB_DUMP, FAST, &buffer); - EXPECT_TRUE(buffer.size() > 0); - EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size())); - backfillDimensionPath(&reports); - backfillStringInReport(&reports); - backfillStartEndTimestamp(&reports); - ASSERT_EQ(1, reports.reports_size()); - ASSERT_EQ(1, reports.reports(0).metrics_size()); - StatsLogReport::GaugeMetricDataWrapper gaugeMetrics; - sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).gauge_metrics(), &gaugeMetrics); - ASSERT_GT((int)gaugeMetrics.data_size(), 0); - - auto data = gaugeMetrics.data(0); - EXPECT_EQ(ATOM_TAG, data.dimensions_in_what().field()); - ASSERT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size()); - EXPECT_EQ(1 /* subsystem name field */, - data.dimensions_in_what().value_tuple().dimensions_value(0).field()); - EXPECT_FALSE(data.dimensions_in_what().value_tuple().dimensions_value(0).value_str().empty()); - ASSERT_EQ(3, data.bucket_info_size()); - - auto bucketInfo = data.bucket_info(0); - ASSERT_EQ(1, bucketInfo.atom_size()); - ASSERT_EQ(1, bucketInfo.elapsed_timestamp_nanos_size()); - EXPECT_EQ(activationNs, bucketInfo.elapsed_timestamp_nanos(0)); - ASSERT_EQ(0, bucketInfo.wall_clock_timestamp_nanos_size()); - EXPECT_EQ(baseTimeNs + 3 * bucketSizeNs, bucketInfo.start_bucket_elapsed_nanos()); - EXPECT_EQ(baseTimeNs + 4 * bucketSizeNs, bucketInfo.end_bucket_elapsed_nanos()); - EXPECT_TRUE(bucketInfo.atom(0).subsystem_sleep_state().subsystem_name().empty()); - EXPECT_GT(bucketInfo.atom(0).subsystem_sleep_state().time_millis(), 0); - - bucketInfo = data.bucket_info(1); - ASSERT_EQ(1, bucketInfo.atom_size()); - ASSERT_EQ(1, bucketInfo.elapsed_timestamp_nanos_size()); - EXPECT_EQ(baseTimeNs + 4 * bucketSizeNs + 1, bucketInfo.elapsed_timestamp_nanos(0)); - ASSERT_EQ(0, bucketInfo.wall_clock_timestamp_nanos_size()); - EXPECT_EQ(baseTimeNs + 4 * bucketSizeNs, bucketInfo.start_bucket_elapsed_nanos()); - EXPECT_EQ(baseTimeNs + 5 * bucketSizeNs, bucketInfo.end_bucket_elapsed_nanos()); - EXPECT_TRUE(bucketInfo.atom(0).subsystem_sleep_state().subsystem_name().empty()); - EXPECT_GT(bucketInfo.atom(0).subsystem_sleep_state().time_millis(), 0); - - bucketInfo = data.bucket_info(2); - ASSERT_EQ(1, bucketInfo.atom_size()); - ASSERT_EQ(1, bucketInfo.elapsed_timestamp_nanos_size()); - EXPECT_EQ(baseTimeNs + 5 * bucketSizeNs + 2, bucketInfo.elapsed_timestamp_nanos(0)); - ASSERT_EQ(0, bucketInfo.wall_clock_timestamp_nanos_size()); - EXPECT_EQ(MillisToNano(NanoToMillis(baseTimeNs + 5 * bucketSizeNs)), - bucketInfo.start_bucket_elapsed_nanos()); - EXPECT_EQ(MillisToNano(NanoToMillis(activationNs + ttlNs + 1)), - bucketInfo.end_bucket_elapsed_nanos()); - EXPECT_TRUE(bucketInfo.atom(0).subsystem_sleep_state().subsystem_name().empty()); - EXPECT_GT(bucketInfo.atom(0).subsystem_sleep_state().time_millis(), 0); -} - -TEST(GaugeMetricE2eTest, TestRandomSamplePulledEventsNoCondition) { - auto config = CreateStatsdConfig(GaugeMetric::RANDOM_ONE_SAMPLE, /*useCondition=*/false); - - int64_t baseTimeNs = getElapsedRealtimeNs(); - int64_t configAddedTimeNs = 10 * 60 * NS_PER_SEC + baseTimeNs; - int64_t bucketSizeNs = - TimeUnitToBucketSizeInMillis(config.gauge_metric(0).bucket()) * 1000000; - - ConfigKey cfgKey; - auto processor = CreateStatsLogProcessor(baseTimeNs, configAddedTimeNs, config, cfgKey, - SharedRefBase::make(), - ATOM_TAG); - ASSERT_EQ(processor->mMetricsManagers.size(), 1u); - EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid()); - processor->mPullerManager->ForceClearPullerCache(); - - int startBucketNum = processor->mMetricsManagers.begin()->second-> - mAllMetricProducers[0]->getCurrentBucketNum(); - EXPECT_GT(startBucketNum, (int64_t)0); - - // When creating the config, the gauge metric producer should register the alarm at the - // end of the current bucket. - ASSERT_EQ((size_t)1, processor->mPullerManager->mReceivers.size()); - EXPECT_EQ(bucketSizeNs, - processor->mPullerManager->mReceivers.begin()->second.front().intervalNs); - int64_t& nextPullTimeNs = - processor->mPullerManager->mReceivers.begin()->second.front().nextPullTimeNs; - EXPECT_EQ(baseTimeNs + startBucketNum * bucketSizeNs + bucketSizeNs, nextPullTimeNs); - - // Pulling alarm arrives on time and reset the sequential pulling alarm. - processor->informPullAlarmFired(nextPullTimeNs + 1); - EXPECT_EQ(baseTimeNs + startBucketNum * bucketSizeNs + 2 * bucketSizeNs, nextPullTimeNs); - - processor->informPullAlarmFired(nextPullTimeNs + 4); - EXPECT_EQ(baseTimeNs + startBucketNum * bucketSizeNs + 3 * bucketSizeNs, - nextPullTimeNs); - - ConfigMetricsReportList reports; - vector buffer; - processor->onDumpReport(cfgKey, configAddedTimeNs + 7 * bucketSizeNs + 10, false, true, - ADB_DUMP, FAST, &buffer); - EXPECT_TRUE(buffer.size() > 0); - EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size())); - backfillDimensionPath(&reports); - backfillStringInReport(&reports); - backfillStartEndTimestamp(&reports); - ASSERT_EQ(1, reports.reports_size()); - ASSERT_EQ(1, reports.reports(0).metrics_size()); - StatsLogReport::GaugeMetricDataWrapper gaugeMetrics; - sortMetricDataByDimensionsValue( - reports.reports(0).metrics(0).gauge_metrics(), &gaugeMetrics); - ASSERT_GT((int)gaugeMetrics.data_size(), 0); - - auto data = gaugeMetrics.data(0); - EXPECT_EQ(ATOM_TAG, data.dimensions_in_what().field()); - ASSERT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size()); - EXPECT_EQ(1 /* subsystem name field */, - data.dimensions_in_what().value_tuple().dimensions_value(0).field()); - EXPECT_FALSE(data.dimensions_in_what().value_tuple().dimensions_value(0).value_str().empty()); - ASSERT_EQ(3, data.bucket_info_size()); - - ASSERT_EQ(1, data.bucket_info(0).atom_size()); - ASSERT_EQ(1, data.bucket_info(0).elapsed_timestamp_nanos_size()); - EXPECT_EQ(configAddedTimeNs, data.bucket_info(0).elapsed_timestamp_nanos(0)); - ASSERT_EQ(0, data.bucket_info(0).wall_clock_timestamp_nanos_size()); - EXPECT_EQ(baseTimeNs + 2 * bucketSizeNs, data.bucket_info(0).start_bucket_elapsed_nanos()); - EXPECT_EQ(baseTimeNs + 3 * bucketSizeNs, data.bucket_info(0).end_bucket_elapsed_nanos()); - EXPECT_TRUE(data.bucket_info(0).atom(0).subsystem_sleep_state().subsystem_name().empty()); - EXPECT_GT(data.bucket_info(0).atom(0).subsystem_sleep_state().time_millis(), 0); - - ASSERT_EQ(1, data.bucket_info(1).atom_size()); - ASSERT_EQ(1, data.bucket_info(1).elapsed_timestamp_nanos_size()); - EXPECT_EQ(baseTimeNs + 3 * bucketSizeNs + 1, data.bucket_info(1).elapsed_timestamp_nanos(0)); - ASSERT_EQ(0, data.bucket_info(1).wall_clock_timestamp_nanos_size()); - EXPECT_EQ(baseTimeNs + 3 * bucketSizeNs, data.bucket_info(1).start_bucket_elapsed_nanos()); - EXPECT_EQ(baseTimeNs + 4 * bucketSizeNs, data.bucket_info(1).end_bucket_elapsed_nanos()); - EXPECT_TRUE(data.bucket_info(1).atom(0).subsystem_sleep_state().subsystem_name().empty()); - EXPECT_GT(data.bucket_info(1).atom(0).subsystem_sleep_state().time_millis(), 0); - - ASSERT_EQ(1, data.bucket_info(2).atom_size()); - ASSERT_EQ(1, data.bucket_info(2).elapsed_timestamp_nanos_size()); - EXPECT_EQ(baseTimeNs + 4 * bucketSizeNs + 4, data.bucket_info(2).elapsed_timestamp_nanos(0)); - ASSERT_EQ(0, data.bucket_info(2).wall_clock_timestamp_nanos_size()); - EXPECT_EQ(baseTimeNs + 4 * bucketSizeNs, data.bucket_info(2).start_bucket_elapsed_nanos()); - EXPECT_EQ(baseTimeNs + 5 * bucketSizeNs, data.bucket_info(2).end_bucket_elapsed_nanos()); - EXPECT_TRUE(data.bucket_info(2).atom(0).subsystem_sleep_state().subsystem_name().empty()); - EXPECT_GT(data.bucket_info(2).atom(0).subsystem_sleep_state().time_millis(), 0); -} - -#else -GTEST_LOG_(INFO) << "This test does nothing.\n"; -#endif - -} // namespace statsd -} // namespace os -} // namespace android diff --git a/bin/tests/e2e/GaugeMetric_e2e_push_test.cpp b/bin/tests/e2e/GaugeMetric_e2e_push_test.cpp deleted file mode 100644 index 04874aa9..00000000 --- a/bin/tests/e2e/GaugeMetric_e2e_push_test.cpp +++ /dev/null @@ -1,274 +0,0 @@ -// 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 - -#include "src/StatsLogProcessor.h" -#include "src/stats_log_util.h" -#include "stats_event.h" -#include "tests/statsd_test_util.h" - -namespace android { -namespace os { -namespace statsd { - -#ifdef __ANDROID__ - -namespace { - -StatsdConfig CreateStatsdConfigForPushedEvent(const GaugeMetric::SamplingType sampling_type) { - StatsdConfig config; - config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root. - *config.add_atom_matcher() = CreateMoveToBackgroundAtomMatcher(); - *config.add_atom_matcher() = CreateMoveToForegroundAtomMatcher(); - - auto atomMatcher = CreateSimpleAtomMatcher("", util::APP_START_OCCURRED); - *config.add_atom_matcher() = atomMatcher; - - auto isInBackgroundPredicate = CreateIsInBackgroundPredicate(); - *isInBackgroundPredicate.mutable_simple_predicate()->mutable_dimensions() = - CreateDimensions(util::ACTIVITY_FOREGROUND_STATE_CHANGED, {1 /* uid field */ }); - *config.add_predicate() = isInBackgroundPredicate; - - auto gaugeMetric = config.add_gauge_metric(); - gaugeMetric->set_id(123456); - gaugeMetric->set_what(atomMatcher.id()); - gaugeMetric->set_condition(isInBackgroundPredicate.id()); - gaugeMetric->mutable_gauge_fields_filter()->set_include_all(false); - gaugeMetric->set_sampling_type(sampling_type); - auto fieldMatcher = gaugeMetric->mutable_gauge_fields_filter()->mutable_fields(); - fieldMatcher->set_field(util::APP_START_OCCURRED); - fieldMatcher->add_child()->set_field(3); // type (enum) - fieldMatcher->add_child()->set_field(4); // activity_name(str) - fieldMatcher->add_child()->set_field(7); // activity_start_msec(int64) - *gaugeMetric->mutable_dimensions_in_what() = - CreateDimensions(util::APP_START_OCCURRED, {1 /* uid field */ }); - gaugeMetric->set_bucket(FIVE_MINUTES); - - auto links = gaugeMetric->add_links(); - links->set_condition(isInBackgroundPredicate.id()); - auto dimensionWhat = links->mutable_fields_in_what(); - dimensionWhat->set_field(util::APP_START_OCCURRED); - dimensionWhat->add_child()->set_field(1); // uid field. - auto dimensionCondition = links->mutable_fields_in_condition(); - dimensionCondition->set_field(util::ACTIVITY_FOREGROUND_STATE_CHANGED); - dimensionCondition->add_child()->set_field(1); // uid field. - return config; -} - -} // namespace - -TEST(GaugeMetricE2eTest, TestMultipleFieldsForPushedEvent) { - for (const auto& sampling_type : - {GaugeMetric::FIRST_N_SAMPLES, GaugeMetric::RANDOM_ONE_SAMPLE}) { - auto config = CreateStatsdConfigForPushedEvent(sampling_type); - int64_t bucketStartTimeNs = 10000000000; - int64_t bucketSizeNs = - TimeUnitToBucketSizeInMillis(config.gauge_metric(0).bucket()) * 1000000; - - ConfigKey cfgKey; - auto processor = - CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey); - ASSERT_EQ(processor->mMetricsManagers.size(), 1u); - EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid()); - - int appUid1 = 123; - int appUid2 = 456; - std::vector> events; - events.push_back(CreateMoveToBackgroundEvent(bucketStartTimeNs + 15, appUid1)); - events.push_back( - CreateMoveToForegroundEvent(bucketStartTimeNs + bucketSizeNs + 250, appUid1)); - events.push_back( - CreateMoveToBackgroundEvent(bucketStartTimeNs + bucketSizeNs + 350, appUid1)); - events.push_back( - CreateMoveToForegroundEvent(bucketStartTimeNs + 2 * bucketSizeNs + 100, appUid1)); - - events.push_back(CreateAppStartOccurredEvent( - bucketStartTimeNs + 10, appUid1, "app1", AppStartOccurred::WARM, "activity_name1", - "calling_pkg_name1", true /*is_instant_app*/, 101 /*activity_start_msec*/)); - events.push_back(CreateAppStartOccurredEvent( - bucketStartTimeNs + 20, appUid1, "app1", AppStartOccurred::HOT, "activity_name2", - "calling_pkg_name2", true /*is_instant_app*/, 102 /*activity_start_msec*/)); - events.push_back(CreateAppStartOccurredEvent( - bucketStartTimeNs + 30, appUid1, "app1", AppStartOccurred::COLD, "activity_name3", - "calling_pkg_name3", true /*is_instant_app*/, 103 /*activity_start_msec*/)); - events.push_back(CreateAppStartOccurredEvent( - bucketStartTimeNs + bucketSizeNs + 30, appUid1, "app1", AppStartOccurred::WARM, - "activity_name4", "calling_pkg_name4", true /*is_instant_app*/, - 104 /*activity_start_msec*/)); - events.push_back(CreateAppStartOccurredEvent( - bucketStartTimeNs + 2 * bucketSizeNs, appUid1, "app1", AppStartOccurred::COLD, - "activity_name5", "calling_pkg_name5", true /*is_instant_app*/, - 105 /*activity_start_msec*/)); - events.push_back(CreateAppStartOccurredEvent( - bucketStartTimeNs + 2 * bucketSizeNs + 10, appUid1, "app1", AppStartOccurred::HOT, - "activity_name6", "calling_pkg_name6", false /*is_instant_app*/, - 106 /*activity_start_msec*/)); - - events.push_back( - CreateMoveToBackgroundEvent(bucketStartTimeNs + bucketSizeNs + 10, appUid2)); - events.push_back(CreateAppStartOccurredEvent( - bucketStartTimeNs + 2 * bucketSizeNs + 10, appUid2, "app2", AppStartOccurred::COLD, - "activity_name7", "calling_pkg_name7", true /*is_instant_app*/, - 201 /*activity_start_msec*/)); - - sortLogEventsByTimestamp(&events); - - for (const auto& event : events) { - processor->OnLogEvent(event.get()); - } - ConfigMetricsReportList reports; - vector buffer; - processor->onDumpReport(cfgKey, bucketStartTimeNs + 3 * bucketSizeNs, false, true, ADB_DUMP, - FAST, &buffer); - EXPECT_TRUE(buffer.size() > 0); - EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size())); - backfillDimensionPath(&reports); - backfillStringInReport(&reports); - backfillStartEndTimestamp(&reports); - ASSERT_EQ(1, reports.reports_size()); - ASSERT_EQ(1, reports.reports(0).metrics_size()); - StatsLogReport::GaugeMetricDataWrapper gaugeMetrics; - sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).gauge_metrics(), - &gaugeMetrics); - ASSERT_EQ(2, gaugeMetrics.data_size()); - - auto data = gaugeMetrics.data(0); - EXPECT_EQ(util::APP_START_OCCURRED, data.dimensions_in_what().field()); - ASSERT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size()); - EXPECT_EQ(1 /* uid field */, - data.dimensions_in_what().value_tuple().dimensions_value(0).field()); - EXPECT_EQ(appUid1, data.dimensions_in_what().value_tuple().dimensions_value(0).value_int()); - ASSERT_EQ(3, data.bucket_info_size()); - if (sampling_type == GaugeMetric::FIRST_N_SAMPLES) { - ASSERT_EQ(2, data.bucket_info(0).atom_size()); - ASSERT_EQ(2, data.bucket_info(0).elapsed_timestamp_nanos_size()); - ASSERT_EQ(0, data.bucket_info(0).wall_clock_timestamp_nanos_size()); - EXPECT_EQ(bucketStartTimeNs, data.bucket_info(0).start_bucket_elapsed_nanos()); - EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, - data.bucket_info(0).end_bucket_elapsed_nanos()); - EXPECT_EQ(AppStartOccurred::HOT, - data.bucket_info(0).atom(0).app_start_occurred().type()); - EXPECT_EQ("activity_name2", - data.bucket_info(0).atom(0).app_start_occurred().activity_name()); - EXPECT_EQ(102L, - data.bucket_info(0).atom(0).app_start_occurred().activity_start_millis()); - EXPECT_EQ(AppStartOccurred::COLD, - data.bucket_info(0).atom(1).app_start_occurred().type()); - EXPECT_EQ("activity_name3", - data.bucket_info(0).atom(1).app_start_occurred().activity_name()); - EXPECT_EQ(103L, - data.bucket_info(0).atom(1).app_start_occurred().activity_start_millis()); - - ASSERT_EQ(1, data.bucket_info(1).atom_size()); - ASSERT_EQ(1, data.bucket_info(1).elapsed_timestamp_nanos_size()); - EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, - data.bucket_info(1).start_bucket_elapsed_nanos()); - EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs, - data.bucket_info(1).end_bucket_elapsed_nanos()); - EXPECT_EQ(AppStartOccurred::WARM, - data.bucket_info(1).atom(0).app_start_occurred().type()); - EXPECT_EQ("activity_name4", - data.bucket_info(1).atom(0).app_start_occurred().activity_name()); - EXPECT_EQ(104L, - data.bucket_info(1).atom(0).app_start_occurred().activity_start_millis()); - - ASSERT_EQ(2, data.bucket_info(2).atom_size()); - ASSERT_EQ(2, data.bucket_info(2).elapsed_timestamp_nanos_size()); - EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs, - data.bucket_info(2).start_bucket_elapsed_nanos()); - EXPECT_EQ(bucketStartTimeNs + 3 * bucketSizeNs, - data.bucket_info(2).end_bucket_elapsed_nanos()); - EXPECT_EQ(AppStartOccurred::COLD, - data.bucket_info(2).atom(0).app_start_occurred().type()); - EXPECT_EQ("activity_name5", - data.bucket_info(2).atom(0).app_start_occurred().activity_name()); - EXPECT_EQ(105L, - data.bucket_info(2).atom(0).app_start_occurred().activity_start_millis()); - EXPECT_EQ(AppStartOccurred::HOT, - data.bucket_info(2).atom(1).app_start_occurred().type()); - EXPECT_EQ("activity_name6", - data.bucket_info(2).atom(1).app_start_occurred().activity_name()); - EXPECT_EQ(106L, - data.bucket_info(2).atom(1).app_start_occurred().activity_start_millis()); - } else { - ASSERT_EQ(1, data.bucket_info(0).atom_size()); - ASSERT_EQ(1, data.bucket_info(0).elapsed_timestamp_nanos_size()); - EXPECT_EQ(bucketStartTimeNs, data.bucket_info(0).start_bucket_elapsed_nanos()); - EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, - data.bucket_info(0).end_bucket_elapsed_nanos()); - EXPECT_EQ(AppStartOccurred::HOT, - data.bucket_info(0).atom(0).app_start_occurred().type()); - EXPECT_EQ("activity_name2", - data.bucket_info(0).atom(0).app_start_occurred().activity_name()); - EXPECT_EQ(102L, - data.bucket_info(0).atom(0).app_start_occurred().activity_start_millis()); - - ASSERT_EQ(1, data.bucket_info(1).atom_size()); - ASSERT_EQ(1, data.bucket_info(1).elapsed_timestamp_nanos_size()); - EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, - data.bucket_info(1).start_bucket_elapsed_nanos()); - EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs, - data.bucket_info(1).end_bucket_elapsed_nanos()); - EXPECT_EQ(AppStartOccurred::WARM, - data.bucket_info(1).atom(0).app_start_occurred().type()); - EXPECT_EQ("activity_name4", - data.bucket_info(1).atom(0).app_start_occurred().activity_name()); - EXPECT_EQ(104L, - data.bucket_info(1).atom(0).app_start_occurred().activity_start_millis()); - - ASSERT_EQ(1, data.bucket_info(2).atom_size()); - ASSERT_EQ(1, data.bucket_info(2).elapsed_timestamp_nanos_size()); - EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs, - data.bucket_info(2).start_bucket_elapsed_nanos()); - EXPECT_EQ(bucketStartTimeNs + 3 * bucketSizeNs, - data.bucket_info(2).end_bucket_elapsed_nanos()); - EXPECT_EQ(AppStartOccurred::COLD, - data.bucket_info(2).atom(0).app_start_occurred().type()); - EXPECT_EQ("activity_name5", - data.bucket_info(2).atom(0).app_start_occurred().activity_name()); - EXPECT_EQ(105L, - data.bucket_info(2).atom(0).app_start_occurred().activity_start_millis()); - } - - data = gaugeMetrics.data(1); - - EXPECT_EQ(data.dimensions_in_what().field(), util::APP_START_OCCURRED); - ASSERT_EQ(data.dimensions_in_what().value_tuple().dimensions_value_size(), 1); - EXPECT_EQ(1 /* uid field */, - data.dimensions_in_what().value_tuple().dimensions_value(0).field()); - EXPECT_EQ(appUid2, data.dimensions_in_what().value_tuple().dimensions_value(0).value_int()); - ASSERT_EQ(1, data.bucket_info_size()); - ASSERT_EQ(1, data.bucket_info(0).atom_size()); - ASSERT_EQ(1, data.bucket_info(0).elapsed_timestamp_nanos_size()); - EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs, - data.bucket_info(0).start_bucket_elapsed_nanos()); - EXPECT_EQ(bucketStartTimeNs + 3 * bucketSizeNs, - data.bucket_info(0).end_bucket_elapsed_nanos()); - EXPECT_EQ(AppStartOccurred::COLD, data.bucket_info(0).atom(0).app_start_occurred().type()); - EXPECT_EQ("activity_name7", - data.bucket_info(0).atom(0).app_start_occurred().activity_name()); - EXPECT_EQ(201L, data.bucket_info(0).atom(0).app_start_occurred().activity_start_millis()); - } -} - -#else -GTEST_LOG_(INFO) << "This test does nothing.\n"; -#endif - -} // namespace statsd -} // namespace os -} // namespace android diff --git a/bin/tests/e2e/MetricActivation_e2e_test.cpp b/bin/tests/e2e/MetricActivation_e2e_test.cpp deleted file mode 100644 index e320419a..00000000 --- a/bin/tests/e2e/MetricActivation_e2e_test.cpp +++ /dev/null @@ -1,1833 +0,0 @@ -// Copyright (C) 2018 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 CreateStatsdConfig() { - StatsdConfig config; - config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root. - auto saverModeMatcher = CreateBatterySaverModeStartAtomMatcher(); - auto crashMatcher = CreateProcessCrashAtomMatcher(); - auto screenOnMatcher = CreateScreenTurnedOnAtomMatcher(); - - *config.add_atom_matcher() = saverModeMatcher; - *config.add_atom_matcher() = crashMatcher; - *config.add_atom_matcher() = screenOnMatcher; - - int64_t metricId = 123456; - auto countMetric = config.add_count_metric(); - countMetric->set_id(metricId); - countMetric->set_what(crashMatcher.id()); - countMetric->set_bucket(FIVE_MINUTES); - countMetric->mutable_dimensions_in_what()->set_field( - util::PROCESS_LIFE_CYCLE_STATE_CHANGED); - countMetric->mutable_dimensions_in_what()->add_child()->set_field(1); // uid field - - auto metric_activation1 = config.add_metric_activation(); - metric_activation1->set_metric_id(metricId); - auto event_activation1 = metric_activation1->add_event_activation(); - event_activation1->set_atom_matcher_id(saverModeMatcher.id()); - event_activation1->set_ttl_seconds(60 * 6); // 6 minutes - auto event_activation2 = metric_activation1->add_event_activation(); - event_activation2->set_atom_matcher_id(screenOnMatcher.id()); - event_activation2->set_ttl_seconds(60 * 2); // 2 minutes - - return config; -} - -StatsdConfig CreateStatsdConfigWithOneDeactivation() { - StatsdConfig config; - config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root. - auto saverModeMatcher = CreateBatterySaverModeStartAtomMatcher(); - auto crashMatcher = CreateProcessCrashAtomMatcher(); - auto screenOnMatcher = CreateScreenTurnedOnAtomMatcher(); - auto brightnessChangedMatcher = CreateScreenBrightnessChangedAtomMatcher(); - - *config.add_atom_matcher() = saverModeMatcher; - *config.add_atom_matcher() = crashMatcher; - *config.add_atom_matcher() = screenOnMatcher; - *config.add_atom_matcher() = brightnessChangedMatcher; - - int64_t metricId = 123456; - auto countMetric = config.add_count_metric(); - countMetric->set_id(metricId); - countMetric->set_what(crashMatcher.id()); - countMetric->set_bucket(FIVE_MINUTES); - countMetric->mutable_dimensions_in_what()->set_field( - util::PROCESS_LIFE_CYCLE_STATE_CHANGED); - countMetric->mutable_dimensions_in_what()->add_child()->set_field(1); // uid field - - auto metric_activation1 = config.add_metric_activation(); - metric_activation1->set_metric_id(metricId); - auto event_activation1 = metric_activation1->add_event_activation(); - event_activation1->set_atom_matcher_id(saverModeMatcher.id()); - event_activation1->set_ttl_seconds(60 * 6); // 6 minutes - event_activation1->set_deactivation_atom_matcher_id(brightnessChangedMatcher.id()); - auto event_activation2 = metric_activation1->add_event_activation(); - event_activation2->set_atom_matcher_id(screenOnMatcher.id()); - event_activation2->set_ttl_seconds(60 * 2); // 2 minutes - - return config; -} - -StatsdConfig CreateStatsdConfigWithTwoDeactivations() { - StatsdConfig config; - config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root. - auto saverModeMatcher = CreateBatterySaverModeStartAtomMatcher(); - auto crashMatcher = CreateProcessCrashAtomMatcher(); - auto screenOnMatcher = CreateScreenTurnedOnAtomMatcher(); - auto brightnessChangedMatcher = CreateScreenBrightnessChangedAtomMatcher(); - auto brightnessChangedMatcher2 = CreateScreenBrightnessChangedAtomMatcher(); - brightnessChangedMatcher2.set_id(StringToId("ScreenBrightnessChanged2")); - - *config.add_atom_matcher() = saverModeMatcher; - *config.add_atom_matcher() = crashMatcher; - *config.add_atom_matcher() = screenOnMatcher; - *config.add_atom_matcher() = brightnessChangedMatcher; - *config.add_atom_matcher() = brightnessChangedMatcher2; - - int64_t metricId = 123456; - auto countMetric = config.add_count_metric(); - countMetric->set_id(metricId); - countMetric->set_what(crashMatcher.id()); - countMetric->set_bucket(FIVE_MINUTES); - countMetric->mutable_dimensions_in_what()->set_field( - util::PROCESS_LIFE_CYCLE_STATE_CHANGED); - countMetric->mutable_dimensions_in_what()->add_child()->set_field(1); // uid field - - auto metric_activation1 = config.add_metric_activation(); - metric_activation1->set_metric_id(metricId); - auto event_activation1 = metric_activation1->add_event_activation(); - event_activation1->set_atom_matcher_id(saverModeMatcher.id()); - event_activation1->set_ttl_seconds(60 * 6); // 6 minutes - event_activation1->set_deactivation_atom_matcher_id(brightnessChangedMatcher.id()); - auto event_activation2 = metric_activation1->add_event_activation(); - event_activation2->set_atom_matcher_id(screenOnMatcher.id()); - event_activation2->set_ttl_seconds(60 * 2); // 2 minutes - event_activation2->set_deactivation_atom_matcher_id(brightnessChangedMatcher2.id()); - - return config; -} - -StatsdConfig CreateStatsdConfigWithSameDeactivations() { - StatsdConfig config; - config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root. - auto saverModeMatcher = CreateBatterySaverModeStartAtomMatcher(); - auto crashMatcher = CreateProcessCrashAtomMatcher(); - auto screenOnMatcher = CreateScreenTurnedOnAtomMatcher(); - auto brightnessChangedMatcher = CreateScreenBrightnessChangedAtomMatcher(); - - *config.add_atom_matcher() = saverModeMatcher; - *config.add_atom_matcher() = crashMatcher; - *config.add_atom_matcher() = screenOnMatcher; - *config.add_atom_matcher() = brightnessChangedMatcher; - - int64_t metricId = 123456; - auto countMetric = config.add_count_metric(); - countMetric->set_id(metricId); - countMetric->set_what(crashMatcher.id()); - countMetric->set_bucket(FIVE_MINUTES); - countMetric->mutable_dimensions_in_what()->set_field( - util::PROCESS_LIFE_CYCLE_STATE_CHANGED); - countMetric->mutable_dimensions_in_what()->add_child()->set_field(1); // uid field - - auto metric_activation1 = config.add_metric_activation(); - metric_activation1->set_metric_id(metricId); - auto event_activation1 = metric_activation1->add_event_activation(); - event_activation1->set_atom_matcher_id(saverModeMatcher.id()); - event_activation1->set_ttl_seconds(60 * 6); // 6 minutes - event_activation1->set_deactivation_atom_matcher_id(brightnessChangedMatcher.id()); - auto event_activation2 = metric_activation1->add_event_activation(); - event_activation2->set_atom_matcher_id(screenOnMatcher.id()); - event_activation2->set_ttl_seconds(60 * 2); // 2 minutes - event_activation2->set_deactivation_atom_matcher_id(brightnessChangedMatcher.id()); - - return config; -} - -StatsdConfig CreateStatsdConfigWithTwoMetricsTwoDeactivations() { - StatsdConfig config; - config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root. - auto saverModeMatcher = CreateBatterySaverModeStartAtomMatcher(); - auto crashMatcher = CreateProcessCrashAtomMatcher(); - auto foregroundMatcher = CreateMoveToForegroundAtomMatcher(); - auto screenOnMatcher = CreateScreenTurnedOnAtomMatcher(); - auto brightnessChangedMatcher = CreateScreenBrightnessChangedAtomMatcher(); - auto brightnessChangedMatcher2 = CreateScreenBrightnessChangedAtomMatcher(); - brightnessChangedMatcher2.set_id(StringToId("ScreenBrightnessChanged2")); - - *config.add_atom_matcher() = saverModeMatcher; - *config.add_atom_matcher() = crashMatcher; - *config.add_atom_matcher() = screenOnMatcher; - *config.add_atom_matcher() = brightnessChangedMatcher; - *config.add_atom_matcher() = brightnessChangedMatcher2; - *config.add_atom_matcher() = foregroundMatcher; - - int64_t metricId = 123456; - auto countMetric = config.add_count_metric(); - countMetric->set_id(metricId); - countMetric->set_what(crashMatcher.id()); - countMetric->set_bucket(FIVE_MINUTES); - countMetric->mutable_dimensions_in_what()->set_field( - util::PROCESS_LIFE_CYCLE_STATE_CHANGED); - countMetric->mutable_dimensions_in_what()->add_child()->set_field(1); // uid field - - int64_t metricId2 = 234567; - countMetric = config.add_count_metric(); - countMetric->set_id(metricId2); - countMetric->set_what(foregroundMatcher.id()); - countMetric->set_bucket(FIVE_MINUTES); - countMetric->mutable_dimensions_in_what()->set_field( - util::ACTIVITY_FOREGROUND_STATE_CHANGED); - countMetric->mutable_dimensions_in_what()->add_child()->set_field(1); // uid field - - auto metric_activation1 = config.add_metric_activation(); - metric_activation1->set_metric_id(metricId); - auto event_activation1 = metric_activation1->add_event_activation(); - event_activation1->set_atom_matcher_id(saverModeMatcher.id()); - event_activation1->set_ttl_seconds(60 * 6); // 6 minutes - event_activation1->set_deactivation_atom_matcher_id(brightnessChangedMatcher.id()); - auto event_activation2 = metric_activation1->add_event_activation(); - event_activation2->set_atom_matcher_id(screenOnMatcher.id()); - event_activation2->set_ttl_seconds(60 * 2); // 2 minutes - event_activation2->set_deactivation_atom_matcher_id(brightnessChangedMatcher2.id()); - - metric_activation1 = config.add_metric_activation(); - metric_activation1->set_metric_id(metricId2); - event_activation1 = metric_activation1->add_event_activation(); - event_activation1->set_atom_matcher_id(saverModeMatcher.id()); - event_activation1->set_ttl_seconds(60 * 6); // 6 minutes - event_activation1->set_deactivation_atom_matcher_id(brightnessChangedMatcher.id()); - event_activation2 = metric_activation1->add_event_activation(); - event_activation2->set_atom_matcher_id(screenOnMatcher.id()); - event_activation2->set_ttl_seconds(60 * 2); // 2 minutes - event_activation2->set_deactivation_atom_matcher_id(brightnessChangedMatcher2.id()); - - return config; -} - -} // namespace - -TEST(MetricActivationE2eTest, TestCountMetric) { - auto config = CreateStatsdConfig(); - - int64_t bucketStartTimeNs = NS_PER_SEC * 10; // 10 secs - int64_t bucketSizeNs = - TimeUnitToBucketSizeInMillis(config.count_metric(0).bucket()) * 1000LL * 1000LL; - - int uid = 12345; - int64_t cfgId = 98765; - ConfigKey cfgKey(uid, cfgId); - - sp m = new UidMap(); - sp pullerManager = new StatsPullerManager(); - sp anomalyAlarmMonitor; - sp subscriberAlarmMonitor; - vector activeConfigsBroadcast; - - long timeBase1 = 1; - int broadcastCount = 0; - StatsLogProcessor processor( - m, pullerManager, anomalyAlarmMonitor, subscriberAlarmMonitor, bucketStartTimeNs, - [](const ConfigKey& key) { return true; }, - [&uid, &broadcastCount, &activeConfigsBroadcast](const int& broadcastUid, - const vector& activeConfigs) { - broadcastCount++; - EXPECT_EQ(broadcastUid, uid); - activeConfigsBroadcast.clear(); - activeConfigsBroadcast.insert(activeConfigsBroadcast.end(), activeConfigs.begin(), - activeConfigs.end()); - return true; - }); - - processor.OnConfigUpdated(bucketStartTimeNs, cfgKey, config); - - ASSERT_EQ(processor.mMetricsManagers.size(), 1u); - sp metricsManager = processor.mMetricsManagers.begin()->second; - EXPECT_TRUE(metricsManager->isConfigValid()); - ASSERT_EQ(metricsManager->mAllMetricProducers.size(), 1); - sp metricProducer = metricsManager->mAllMetricProducers[0]; - auto& eventActivationMap = metricProducer->mEventActivationMap; - - EXPECT_FALSE(metricsManager->isActive()); - EXPECT_FALSE(metricProducer->mIsActive); - // Two activations: one is triggered by battery saver mode (tracker index 0), the other is - // triggered by screen on event (tracker index 2). - ASSERT_EQ(eventActivationMap.size(), 2u); - EXPECT_TRUE(eventActivationMap.find(0) != eventActivationMap.end()); - EXPECT_TRUE(eventActivationMap.find(2) != eventActivationMap.end()); - EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kNotActive); - EXPECT_EQ(eventActivationMap[0]->start_ns, 0); - EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC); - EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kNotActive); - EXPECT_EQ(eventActivationMap[2]->start_ns, 0); - EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC); - - std::unique_ptr event; - - event = CreateAppCrashEvent(bucketStartTimeNs + 5, 111); - processor.OnLogEvent(event.get(), bucketStartTimeNs + 5); - EXPECT_FALSE(metricsManager->isActive()); - EXPECT_FALSE(metricProducer->mIsActive); - EXPECT_EQ(broadcastCount, 0); - - // Activated by battery save mode. - event = CreateBatterySaverOnEvent(bucketStartTimeNs + 10); - processor.OnLogEvent(event.get(), bucketStartTimeNs + 10); - EXPECT_TRUE(metricsManager->isActive()); - EXPECT_TRUE(metricProducer->mIsActive); - EXPECT_EQ(broadcastCount, 1); - ASSERT_EQ(activeConfigsBroadcast.size(), 1); - EXPECT_EQ(activeConfigsBroadcast[0], cfgId); - EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kActive); - EXPECT_EQ(eventActivationMap[0]->start_ns, bucketStartTimeNs + 10); - EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC); - EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kNotActive); - EXPECT_EQ(eventActivationMap[2]->start_ns, 0); - EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC); - - // First processed event. - event = CreateAppCrashEvent(bucketStartTimeNs + 15, 222); - processor.OnLogEvent(event.get(), bucketStartTimeNs + 15); - - // Activated by screen on event. - event = CreateScreenStateChangedEvent(bucketStartTimeNs + 20, android::view::DISPLAY_STATE_ON); - processor.OnLogEvent(event.get(), bucketStartTimeNs + 20); - EXPECT_TRUE(metricsManager->isActive()); - EXPECT_TRUE(metricProducer->mIsActive); - EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kActive); - EXPECT_EQ(eventActivationMap[0]->start_ns, bucketStartTimeNs + 10); - EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC); - EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kActive); - EXPECT_EQ(eventActivationMap[2]->start_ns, bucketStartTimeNs + 20); - EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC); - - // 2nd processed event. - // The activation by screen_on event expires, but the one by battery save mode is still active. - event = CreateAppCrashEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 2 + 25, 333); - processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 2 + 25); - EXPECT_TRUE(metricsManager->isActive()); - EXPECT_TRUE(metricProducer->mIsActive); - EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kActive); - EXPECT_EQ(eventActivationMap[0]->start_ns, bucketStartTimeNs + 10); - EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC); - EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kNotActive); - EXPECT_EQ(eventActivationMap[2]->start_ns, bucketStartTimeNs + 20); - EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC); - // No new broadcast since the config should still be active. - EXPECT_EQ(broadcastCount, 1); - - // 3rd processed event. - event = CreateAppCrashEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 5 + 25, 444); - processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 5 + 25); - - // All activations expired. - event = CreateAppCrashEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 8, 555); - processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 8); - EXPECT_FALSE(metricsManager->isActive()); - EXPECT_FALSE(metricProducer->mIsActive); - // New broadcast since the config is no longer active. - EXPECT_EQ(broadcastCount, 2); - ASSERT_EQ(activeConfigsBroadcast.size(), 0); - EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kNotActive); - EXPECT_EQ(eventActivationMap[0]->start_ns, bucketStartTimeNs + 10); - EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC); - EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kNotActive); - EXPECT_EQ(eventActivationMap[2]->start_ns, bucketStartTimeNs + 20); - EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC); - - // Re-activate metric via screen on. - event = CreateScreenStateChangedEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10, - android::view::DISPLAY_STATE_ON); - processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10); - EXPECT_TRUE(metricsManager->isActive()); - EXPECT_TRUE(metricProducer->mIsActive); - EXPECT_EQ(broadcastCount, 3); - ASSERT_EQ(activeConfigsBroadcast.size(), 1); - EXPECT_EQ(activeConfigsBroadcast[0], cfgId); - EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kNotActive); - EXPECT_EQ(eventActivationMap[0]->start_ns, bucketStartTimeNs + 10); - EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC); - EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kActive); - EXPECT_EQ(eventActivationMap[2]->start_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10); - EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC); - - // 4th processed event. - event = CreateAppCrashEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 1, 666); - processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 1); - - ConfigMetricsReportList reports; - vector buffer; - processor.onDumpReport(cfgKey, bucketStartTimeNs + NS_PER_SEC * 60 * 15 + 1, false, true, - ADB_DUMP, FAST, &buffer); - EXPECT_TRUE(buffer.size() > 0); - EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size())); - backfillDimensionPath(&reports); - backfillStartEndTimestamp(&reports); - ASSERT_EQ(1, reports.reports_size()); - ASSERT_EQ(1, reports.reports(0).metrics_size()); - - StatsLogReport::CountMetricDataWrapper countMetrics; - sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).count_metrics(), &countMetrics); - ASSERT_EQ(4, countMetrics.data_size()); - - auto data = countMetrics.data(0); - EXPECT_EQ(util::PROCESS_LIFE_CYCLE_STATE_CHANGED, data.dimensions_in_what().field()); - ASSERT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size()); - EXPECT_EQ(1 /* uid field */, - data.dimensions_in_what().value_tuple().dimensions_value(0).field()); - EXPECT_EQ(222, data.dimensions_in_what().value_tuple().dimensions_value(0).value_int()); - ASSERT_EQ(1, data.bucket_info_size()); - EXPECT_EQ(1, data.bucket_info(0).count()); - EXPECT_EQ(bucketStartTimeNs, data.bucket_info(0).start_bucket_elapsed_nanos()); - EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, data.bucket_info(0).end_bucket_elapsed_nanos()); - - data = countMetrics.data(1); - EXPECT_EQ(util::PROCESS_LIFE_CYCLE_STATE_CHANGED, data.dimensions_in_what().field()); - ASSERT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size()); - EXPECT_EQ(1 /* uid field */, - data.dimensions_in_what().value_tuple().dimensions_value(0).field()); - EXPECT_EQ(333, data.dimensions_in_what().value_tuple().dimensions_value(0).value_int()); - ASSERT_EQ(1, data.bucket_info_size()); - EXPECT_EQ(1, data.bucket_info(0).count()); - EXPECT_EQ(bucketStartTimeNs, data.bucket_info(0).start_bucket_elapsed_nanos()); - EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, data.bucket_info(0).end_bucket_elapsed_nanos()); - - data = countMetrics.data(2); - EXPECT_EQ(util::PROCESS_LIFE_CYCLE_STATE_CHANGED, data.dimensions_in_what().field()); - ASSERT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size()); - EXPECT_EQ(1 /* uid field */, - data.dimensions_in_what().value_tuple().dimensions_value(0).field()); - EXPECT_EQ(444, data.dimensions_in_what().value_tuple().dimensions_value(0).value_int()); - ASSERT_EQ(1, data.bucket_info_size()); - EXPECT_EQ(1, data.bucket_info(0).count()); - // Partial bucket as metric is deactivated. - EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, data.bucket_info(0).start_bucket_elapsed_nanos()); - EXPECT_EQ(bucketStartTimeNs + NS_PER_SEC * 60 * 8, - data.bucket_info(0).end_bucket_elapsed_nanos()); - - data = countMetrics.data(3); - EXPECT_EQ(util::PROCESS_LIFE_CYCLE_STATE_CHANGED, data.dimensions_in_what().field()); - ASSERT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size()); - EXPECT_EQ(1 /* uid field */, - data.dimensions_in_what().value_tuple().dimensions_value(0).field()); - EXPECT_EQ(666, data.dimensions_in_what().value_tuple().dimensions_value(0).value_int()); - ASSERT_EQ(1, data.bucket_info_size()); - EXPECT_EQ(1, data.bucket_info(0).count()); - EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs, - data.bucket_info(0).start_bucket_elapsed_nanos()); - EXPECT_EQ(bucketStartTimeNs + 3 * bucketSizeNs, data.bucket_info(0).end_bucket_elapsed_nanos()); -} - -TEST(MetricActivationE2eTest, TestCountMetricWithOneDeactivation) { - auto config = CreateStatsdConfigWithOneDeactivation(); - - int64_t bucketStartTimeNs = NS_PER_SEC * 10; // 10 secs - int64_t bucketSizeNs = - TimeUnitToBucketSizeInMillis(config.count_metric(0).bucket()) * 1000LL * 1000LL; - - int uid = 12345; - int64_t cfgId = 98765; - ConfigKey cfgKey(uid, cfgId); - - sp m = new UidMap(); - sp pullerManager = new StatsPullerManager(); - sp anomalyAlarmMonitor; - sp subscriberAlarmMonitor; - vector activeConfigsBroadcast; - - long timeBase1 = 1; - int broadcastCount = 0; - StatsLogProcessor processor( - m, pullerManager, anomalyAlarmMonitor, subscriberAlarmMonitor, bucketStartTimeNs, - [](const ConfigKey& key) { return true; }, - [&uid, &broadcastCount, &activeConfigsBroadcast](const int& broadcastUid, - const vector& activeConfigs) { - broadcastCount++; - EXPECT_EQ(broadcastUid, uid); - activeConfigsBroadcast.clear(); - activeConfigsBroadcast.insert(activeConfigsBroadcast.end(), activeConfigs.begin(), - activeConfigs.end()); - return true; - }); - - processor.OnConfigUpdated(bucketStartTimeNs, cfgKey, config); - - ASSERT_EQ(processor.mMetricsManagers.size(), 1u); - sp metricsManager = processor.mMetricsManagers.begin()->second; - EXPECT_TRUE(metricsManager->isConfigValid()); - ASSERT_EQ(metricsManager->mAllMetricProducers.size(), 1); - sp metricProducer = metricsManager->mAllMetricProducers[0]; - auto& eventActivationMap = metricProducer->mEventActivationMap; - auto& eventDeactivationMap = metricProducer->mEventDeactivationMap; - - EXPECT_FALSE(metricsManager->isActive()); - EXPECT_FALSE(metricProducer->mIsActive); - // Two activations: one is triggered by battery saver mode (tracker index 0), the other is - // triggered by screen on event (tracker index 2). - ASSERT_EQ(eventActivationMap.size(), 2u); - EXPECT_TRUE(eventActivationMap.find(0) != eventActivationMap.end()); - EXPECT_TRUE(eventActivationMap.find(2) != eventActivationMap.end()); - EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kNotActive); - EXPECT_EQ(eventActivationMap[0]->start_ns, 0); - EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC); - EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kNotActive); - EXPECT_EQ(eventActivationMap[2]->start_ns, 0); - EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC); - ASSERT_EQ(eventDeactivationMap.size(), 1u); - EXPECT_TRUE(eventDeactivationMap.find(3) != eventDeactivationMap.end()); - ASSERT_EQ(eventDeactivationMap[3].size(), 1u); - EXPECT_EQ(eventDeactivationMap[3][0], eventActivationMap[0]); - - std::unique_ptr event; - - event = CreateAppCrashEvent(bucketStartTimeNs + 5, 111); - processor.OnLogEvent(event.get(), bucketStartTimeNs + 5); - EXPECT_FALSE(metricsManager->isActive()); - EXPECT_FALSE(metricProducer->mIsActive); - EXPECT_EQ(broadcastCount, 0); - - // Activated by battery save mode. - event = CreateBatterySaverOnEvent(bucketStartTimeNs + 10); - processor.OnLogEvent(event.get(), bucketStartTimeNs + 10); - EXPECT_TRUE(metricsManager->isActive()); - EXPECT_TRUE(metricProducer->mIsActive); - EXPECT_EQ(broadcastCount, 1); - ASSERT_EQ(activeConfigsBroadcast.size(), 1); - EXPECT_EQ(activeConfigsBroadcast[0], cfgId); - EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kActive); - EXPECT_EQ(eventActivationMap[0]->start_ns, bucketStartTimeNs + 10); - EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC); - EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kNotActive); - EXPECT_EQ(eventActivationMap[2]->start_ns, 0); - EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC); - EXPECT_EQ(eventDeactivationMap[3][0], eventActivationMap[0]); - - // First processed event. - event = CreateAppCrashEvent(bucketStartTimeNs + 15, 222); - processor.OnLogEvent(event.get(), bucketStartTimeNs + 15); - - // Activated by screen on event. - event = CreateScreenStateChangedEvent(bucketStartTimeNs + 20, android::view::DISPLAY_STATE_ON); - processor.OnLogEvent(event.get(), bucketStartTimeNs + 20); - EXPECT_TRUE(metricsManager->isActive()); - EXPECT_TRUE(metricProducer->mIsActive); - EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kActive); - EXPECT_EQ(eventActivationMap[0]->start_ns, bucketStartTimeNs + 10); - EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC); - EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kActive); - EXPECT_EQ(eventActivationMap[2]->start_ns, bucketStartTimeNs + 20); - EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC); - EXPECT_EQ(eventDeactivationMap[3][0], eventActivationMap[0]); - - // 2nd processed event. - // The activation by screen_on event expires, but the one by battery save mode is still active. - event = CreateAppCrashEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 2 + 25, 333); - processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 2 + 25); - EXPECT_TRUE(metricsManager->isActive()); - EXPECT_TRUE(metricProducer->mIsActive); - EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kActive); - EXPECT_EQ(eventActivationMap[0]->start_ns, bucketStartTimeNs + 10); - EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC); - EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kNotActive); - EXPECT_EQ(eventActivationMap[2]->start_ns, bucketStartTimeNs + 20); - EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC); - EXPECT_EQ(eventDeactivationMap[3][0], eventActivationMap[0]); - // No new broadcast since the config should still be active. - EXPECT_EQ(broadcastCount, 1); - - // 3rd processed event. - event = CreateAppCrashEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 5 + 25, 444); - processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 5 + 25); - - // All activations expired. - event = CreateAppCrashEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 8, 555); - processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 8); - EXPECT_FALSE(metricsManager->isActive()); - EXPECT_FALSE(metricProducer->mIsActive); - // New broadcast since the config is no longer active. - EXPECT_EQ(broadcastCount, 2); - ASSERT_EQ(activeConfigsBroadcast.size(), 0); - EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kNotActive); - EXPECT_EQ(eventActivationMap[0]->start_ns, bucketStartTimeNs + 10); - EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC); - EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kNotActive); - EXPECT_EQ(eventActivationMap[2]->start_ns, bucketStartTimeNs + 20); - EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC); - EXPECT_EQ(eventDeactivationMap[3][0], eventActivationMap[0]); - - // Re-activate metric via screen on. - event = CreateScreenStateChangedEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10, - android::view::DISPLAY_STATE_ON); - processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10); - EXPECT_TRUE(metricsManager->isActive()); - EXPECT_TRUE(metricProducer->mIsActive); - EXPECT_EQ(broadcastCount, 3); - ASSERT_EQ(activeConfigsBroadcast.size(), 1); - EXPECT_EQ(activeConfigsBroadcast[0], cfgId); - EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kNotActive); - EXPECT_EQ(eventActivationMap[0]->start_ns, bucketStartTimeNs + 10); - EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC); - EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kActive); - EXPECT_EQ(eventActivationMap[2]->start_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10); - EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC); - EXPECT_EQ(eventDeactivationMap[3][0], eventActivationMap[0]); - - // 4th processed event. - event = CreateAppCrashEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 1, 666); - processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 1); - - // Re-enable battery saver mode activation. - event = CreateBatterySaverOnEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 15); - processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 15); - EXPECT_TRUE(metricsManager->isActive()); - EXPECT_TRUE(metricProducer->mIsActive); - EXPECT_EQ(broadcastCount, 3); - ASSERT_EQ(activeConfigsBroadcast.size(), 1); - EXPECT_EQ(activeConfigsBroadcast[0], cfgId); - EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kActive); - EXPECT_EQ(eventActivationMap[0]->start_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 15); - EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC); - EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kActive); - EXPECT_EQ(eventActivationMap[2]->start_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10); - EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC); - EXPECT_EQ(eventDeactivationMap[3][0], eventActivationMap[0]); - - // 5th processed event. - event = CreateAppCrashEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 40, 777); - processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 40); - - // Cancel battery saver mode activation. - event = CreateScreenBrightnessChangedEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 60, 64); - processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 60); - EXPECT_TRUE(metricsManager->isActive()); - EXPECT_TRUE(metricProducer->mIsActive); - EXPECT_EQ(broadcastCount, 3); - ASSERT_EQ(activeConfigsBroadcast.size(), 1); - EXPECT_EQ(activeConfigsBroadcast[0], cfgId); - EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kNotActive); - EXPECT_EQ(eventActivationMap[0]->start_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 15); - EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC); - EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kActive); - EXPECT_EQ(eventActivationMap[2]->start_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10); - EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC); - EXPECT_EQ(eventDeactivationMap[3][0], eventActivationMap[0]); - - // Screen-on activation expired. - event = CreateAppCrashEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 13, 888); - processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 13); - EXPECT_FALSE(metricsManager->isActive()); - EXPECT_FALSE(metricProducer->mIsActive); - // New broadcast since the config is no longer active. - EXPECT_EQ(broadcastCount, 4); - ASSERT_EQ(activeConfigsBroadcast.size(), 0); - EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kNotActive); - EXPECT_EQ(eventActivationMap[0]->start_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 15); - EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC); - EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kNotActive); - EXPECT_EQ(eventActivationMap[2]->start_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10); - EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC); - EXPECT_EQ(eventDeactivationMap[3][0], eventActivationMap[0]); - - event = CreateAppCrashEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 14 + 1, 999); - processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 14 + 1); - - // Re-enable battery saver mode activation. - event = CreateBatterySaverOnEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 15 + 15); - processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 15 + 15); - EXPECT_TRUE(metricsManager->isActive()); - EXPECT_TRUE(metricProducer->mIsActive); - EXPECT_EQ(broadcastCount, 5); - ASSERT_EQ(activeConfigsBroadcast.size(), 1); - EXPECT_EQ(activeConfigsBroadcast[0], cfgId); - EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kActive); - EXPECT_EQ(eventActivationMap[0]->start_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 15 + 15); - EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC); - EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kNotActive); - EXPECT_EQ(eventActivationMap[2]->start_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10); - EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC); - EXPECT_EQ(eventDeactivationMap[3][0], eventActivationMap[0]); - - // Cancel battery saver mode activation. - event = CreateScreenBrightnessChangedEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 16, 140); - processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 16); - EXPECT_FALSE(metricsManager->isActive()); - EXPECT_FALSE(metricProducer->mIsActive); - EXPECT_EQ(broadcastCount, 6); - ASSERT_EQ(activeConfigsBroadcast.size(), 0); - EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kNotActive); - EXPECT_EQ(eventActivationMap[0]->start_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 15 + 15); - EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC); - EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kNotActive); - EXPECT_EQ(eventActivationMap[2]->start_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10); - EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC); - EXPECT_EQ(eventDeactivationMap[3][0], eventActivationMap[0]); - - ConfigMetricsReportList reports; - vector buffer; - processor.onDumpReport(cfgKey, bucketStartTimeNs + NS_PER_SEC * 60 * 15 + 1, false, true, - ADB_DUMP, FAST, &buffer); - EXPECT_TRUE(buffer.size() > 0); - EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size())); - backfillDimensionPath(&reports); - backfillStartEndTimestamp(&reports); - ASSERT_EQ(1, reports.reports_size()); - ASSERT_EQ(1, reports.reports(0).metrics_size()); - - StatsLogReport::CountMetricDataWrapper countMetrics; - sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).count_metrics(), &countMetrics); - ASSERT_EQ(5, countMetrics.data_size()); - - auto data = countMetrics.data(0); - EXPECT_EQ(util::PROCESS_LIFE_CYCLE_STATE_CHANGED, data.dimensions_in_what().field()); - ASSERT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size()); - EXPECT_EQ(1 /* uid field */, - data.dimensions_in_what().value_tuple().dimensions_value(0).field()); - EXPECT_EQ(222, data.dimensions_in_what().value_tuple().dimensions_value(0).value_int()); - ASSERT_EQ(1, data.bucket_info_size()); - EXPECT_EQ(1, data.bucket_info(0).count()); - EXPECT_EQ(bucketStartTimeNs, data.bucket_info(0).start_bucket_elapsed_nanos()); - EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, data.bucket_info(0).end_bucket_elapsed_nanos()); - - data = countMetrics.data(1); - EXPECT_EQ(util::PROCESS_LIFE_CYCLE_STATE_CHANGED, data.dimensions_in_what().field()); - ASSERT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size()); - EXPECT_EQ(1 /* uid field */, - data.dimensions_in_what().value_tuple().dimensions_value(0).field()); - EXPECT_EQ(333, data.dimensions_in_what().value_tuple().dimensions_value(0).value_int()); - ASSERT_EQ(1, data.bucket_info_size()); - EXPECT_EQ(1, data.bucket_info(0).count()); - EXPECT_EQ(bucketStartTimeNs, data.bucket_info(0).start_bucket_elapsed_nanos()); - EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, data.bucket_info(0).end_bucket_elapsed_nanos()); - - data = countMetrics.data(2); - EXPECT_EQ(util::PROCESS_LIFE_CYCLE_STATE_CHANGED, data.dimensions_in_what().field()); - ASSERT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size()); - EXPECT_EQ(1 /* uid field */, - data.dimensions_in_what().value_tuple().dimensions_value(0).field()); - EXPECT_EQ(444, data.dimensions_in_what().value_tuple().dimensions_value(0).value_int()); - ASSERT_EQ(1, data.bucket_info_size()); - EXPECT_EQ(1, data.bucket_info(0).count()); - // Partial bucket as metric is deactivated. - EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, data.bucket_info(0).start_bucket_elapsed_nanos()); - EXPECT_EQ(bucketStartTimeNs + NS_PER_SEC * 60 * 8, - data.bucket_info(0).end_bucket_elapsed_nanos()); - - data = countMetrics.data(3); - EXPECT_EQ(util::PROCESS_LIFE_CYCLE_STATE_CHANGED, data.dimensions_in_what().field()); - ASSERT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size()); - EXPECT_EQ(1 /* uid field */, - data.dimensions_in_what().value_tuple().dimensions_value(0).field()); - EXPECT_EQ(666, data.dimensions_in_what().value_tuple().dimensions_value(0).value_int()); - ASSERT_EQ(1, data.bucket_info_size()); - EXPECT_EQ(1, data.bucket_info(0).count()); - EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs, - data.bucket_info(0).start_bucket_elapsed_nanos()); - EXPECT_EQ(bucketStartTimeNs + NS_PER_SEC * 60 * 13, - data.bucket_info(0).end_bucket_elapsed_nanos()); - - data = countMetrics.data(4); - EXPECT_EQ(util::PROCESS_LIFE_CYCLE_STATE_CHANGED, data.dimensions_in_what().field()); - ASSERT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size()); - EXPECT_EQ(1 /* uid field */, - data.dimensions_in_what().value_tuple().dimensions_value(0).field()); - EXPECT_EQ(777, data.dimensions_in_what().value_tuple().dimensions_value(0).value_int()); - ASSERT_EQ(1, data.bucket_info_size()); - EXPECT_EQ(1, data.bucket_info(0).count()); - EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs, - data.bucket_info(0).start_bucket_elapsed_nanos()); - EXPECT_EQ(bucketStartTimeNs + NS_PER_SEC * 60 * 13, - data.bucket_info(0).end_bucket_elapsed_nanos()); -} - -TEST(MetricActivationE2eTest, TestCountMetricWithTwoDeactivations) { - auto config = CreateStatsdConfigWithTwoDeactivations(); - - int64_t bucketStartTimeNs = NS_PER_SEC * 10; // 10 secs - int64_t bucketSizeNs = - TimeUnitToBucketSizeInMillis(config.count_metric(0).bucket()) * 1000LL * 1000LL; - - int uid = 12345; - int64_t cfgId = 98765; - ConfigKey cfgKey(uid, cfgId); - - sp m = new UidMap(); - sp pullerManager = new StatsPullerManager(); - sp anomalyAlarmMonitor; - sp subscriberAlarmMonitor; - vector activeConfigsBroadcast; - - long timeBase1 = 1; - int broadcastCount = 0; - StatsLogProcessor processor( - m, pullerManager, anomalyAlarmMonitor, subscriberAlarmMonitor, bucketStartTimeNs, - [](const ConfigKey& key) { return true; }, - [&uid, &broadcastCount, &activeConfigsBroadcast](const int& broadcastUid, - const vector& activeConfigs) { - broadcastCount++; - EXPECT_EQ(broadcastUid, uid); - activeConfigsBroadcast.clear(); - activeConfigsBroadcast.insert(activeConfigsBroadcast.end(), activeConfigs.begin(), - activeConfigs.end()); - return true; - }); - - processor.OnConfigUpdated(bucketStartTimeNs, cfgKey, config); - - ASSERT_EQ(processor.mMetricsManagers.size(), 1u); - sp metricsManager = processor.mMetricsManagers.begin()->second; - EXPECT_TRUE(metricsManager->isConfigValid()); - ASSERT_EQ(metricsManager->mAllMetricProducers.size(), 1); - sp metricProducer = metricsManager->mAllMetricProducers[0]; - auto& eventActivationMap = metricProducer->mEventActivationMap; - auto& eventDeactivationMap = metricProducer->mEventDeactivationMap; - - EXPECT_FALSE(metricsManager->isActive()); - EXPECT_FALSE(metricProducer->mIsActive); - // Two activations: one is triggered by battery saver mode (tracker index 0), the other is - // triggered by screen on event (tracker index 2). - ASSERT_EQ(eventActivationMap.size(), 2u); - EXPECT_TRUE(eventActivationMap.find(0) != eventActivationMap.end()); - EXPECT_TRUE(eventActivationMap.find(2) != eventActivationMap.end()); - EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kNotActive); - EXPECT_EQ(eventActivationMap[0]->start_ns, 0); - EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC); - EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kNotActive); - EXPECT_EQ(eventActivationMap[2]->start_ns, 0); - EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC); - ASSERT_EQ(eventDeactivationMap.size(), 2u); - EXPECT_TRUE(eventDeactivationMap.find(3) != eventDeactivationMap.end()); - EXPECT_TRUE(eventDeactivationMap.find(4) != eventDeactivationMap.end()); - ASSERT_EQ(eventDeactivationMap[3].size(), 1u); - ASSERT_EQ(eventDeactivationMap[4].size(), 1u); - EXPECT_EQ(eventDeactivationMap[3][0], eventActivationMap[0]); - EXPECT_EQ(eventDeactivationMap[4][0], eventActivationMap[2]); - - std::unique_ptr event; - - event = CreateAppCrashEvent(bucketStartTimeNs + 5, 111); - processor.OnLogEvent(event.get(), bucketStartTimeNs + 5); - EXPECT_FALSE(metricsManager->isActive()); - EXPECT_FALSE(metricProducer->mIsActive); - EXPECT_EQ(broadcastCount, 0); - - // Activated by battery save mode. - event = CreateBatterySaverOnEvent(bucketStartTimeNs + 10); - processor.OnLogEvent(event.get(), bucketStartTimeNs + 10); - EXPECT_TRUE(metricsManager->isActive()); - EXPECT_TRUE(metricProducer->mIsActive); - EXPECT_EQ(broadcastCount, 1); - ASSERT_EQ(activeConfigsBroadcast.size(), 1); - EXPECT_EQ(activeConfigsBroadcast[0], cfgId); - EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kActive); - EXPECT_EQ(eventActivationMap[0]->start_ns, bucketStartTimeNs + 10); - EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC); - EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kNotActive); - EXPECT_EQ(eventActivationMap[2]->start_ns, 0); - EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC); - EXPECT_EQ(eventDeactivationMap[3][0], eventActivationMap[0]); - EXPECT_EQ(eventDeactivationMap[4][0], eventActivationMap[2]); - - // First processed event. - event = CreateAppCrashEvent(bucketStartTimeNs + 15, 222); - processor.OnLogEvent(event.get(), bucketStartTimeNs + 15); - - // Activated by screen on event. - event = CreateScreenStateChangedEvent(bucketStartTimeNs + 20, android::view::DISPLAY_STATE_ON); - processor.OnLogEvent(event.get(), bucketStartTimeNs + 20); - EXPECT_TRUE(metricsManager->isActive()); - EXPECT_TRUE(metricProducer->mIsActive); - EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kActive); - EXPECT_EQ(eventActivationMap[0]->start_ns, bucketStartTimeNs + 10); - EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC); - EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kActive); - EXPECT_EQ(eventActivationMap[2]->start_ns, bucketStartTimeNs + 20); - EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC); - EXPECT_EQ(eventDeactivationMap[3][0], eventActivationMap[0]); - EXPECT_EQ(eventDeactivationMap[4][0], eventActivationMap[2]); - - // 2nd processed event. - // The activation by screen_on event expires, but the one by battery save mode is still active. - event = CreateAppCrashEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 2 + 25, 333); - processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 2 + 25); - EXPECT_TRUE(metricsManager->isActive()); - EXPECT_TRUE(metricProducer->mIsActive); - EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kActive); - EXPECT_EQ(eventActivationMap[0]->start_ns, bucketStartTimeNs + 10); - EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC); - EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kNotActive); - EXPECT_EQ(eventActivationMap[2]->start_ns, bucketStartTimeNs + 20); - EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC); - EXPECT_EQ(eventDeactivationMap[3][0], eventActivationMap[0]); - EXPECT_EQ(eventDeactivationMap[4][0], eventActivationMap[2]); - // No new broadcast since the config should still be active. - EXPECT_EQ(broadcastCount, 1); - - // 3rd processed event. - event = CreateAppCrashEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 5 + 25, 444); - processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 5 + 25); - - // All activations expired. - event = CreateAppCrashEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 8, 555); - processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 8); - EXPECT_FALSE(metricsManager->isActive()); - EXPECT_FALSE(metricProducer->mIsActive); - // New broadcast since the config is no longer active. - EXPECT_EQ(broadcastCount, 2); - ASSERT_EQ(activeConfigsBroadcast.size(), 0); - EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kNotActive); - EXPECT_EQ(eventActivationMap[0]->start_ns, bucketStartTimeNs + 10); - EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC); - EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kNotActive); - EXPECT_EQ(eventActivationMap[2]->start_ns, bucketStartTimeNs + 20); - EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC); - EXPECT_EQ(eventDeactivationMap[3][0], eventActivationMap[0]); - EXPECT_EQ(eventDeactivationMap[4][0], eventActivationMap[2]); - - // Re-activate metric via screen on. - event = CreateScreenStateChangedEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10, - android::view::DISPLAY_STATE_ON); - processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10); - EXPECT_TRUE(metricsManager->isActive()); - EXPECT_TRUE(metricProducer->mIsActive); - EXPECT_EQ(broadcastCount, 3); - ASSERT_EQ(activeConfigsBroadcast.size(), 1); - EXPECT_EQ(activeConfigsBroadcast[0], cfgId); - EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kNotActive); - EXPECT_EQ(eventActivationMap[0]->start_ns, bucketStartTimeNs + 10); - EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC); - EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kActive); - EXPECT_EQ(eventActivationMap[2]->start_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10); - EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC); - EXPECT_EQ(eventDeactivationMap[3][0], eventActivationMap[0]); - EXPECT_EQ(eventDeactivationMap[4][0], eventActivationMap[2]); - - // 4th processed event. - event = CreateAppCrashEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 1, 666); - processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 1); - - // Re-enable battery saver mode activation. - event = CreateBatterySaverOnEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 15); - processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 15); - EXPECT_TRUE(metricsManager->isActive()); - EXPECT_TRUE(metricProducer->mIsActive); - EXPECT_EQ(broadcastCount, 3); - ASSERT_EQ(activeConfigsBroadcast.size(), 1); - EXPECT_EQ(activeConfigsBroadcast[0], cfgId); - EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kActive); - EXPECT_EQ(eventActivationMap[0]->start_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 15); - EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC); - EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kActive); - EXPECT_EQ(eventActivationMap[2]->start_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10); - EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC); - EXPECT_EQ(eventDeactivationMap[3][0], eventActivationMap[0]); - EXPECT_EQ(eventDeactivationMap[4][0], eventActivationMap[2]); - - // 5th processed event. - event = CreateAppCrashEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 40, 777); - processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 40); - - // Cancel battery saver mode and screen on activation. - event = CreateScreenBrightnessChangedEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 60, 64); - processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 60); - EXPECT_FALSE(metricsManager->isActive()); - EXPECT_FALSE(metricProducer->mIsActive); - // New broadcast since the config is no longer active. - EXPECT_EQ(broadcastCount, 4); - ASSERT_EQ(activeConfigsBroadcast.size(), 0); - EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kNotActive); - EXPECT_EQ(eventActivationMap[0]->start_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 15); - EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC); - EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kNotActive); - EXPECT_EQ(eventActivationMap[2]->start_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10); - EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC); - EXPECT_EQ(eventDeactivationMap[3][0], eventActivationMap[0]); - EXPECT_EQ(eventDeactivationMap[4][0], eventActivationMap[2]); - - // Screen-on activation expired. - event = CreateAppCrashEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 13, 888); - processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 13); - EXPECT_FALSE(metricsManager->isActive()); - EXPECT_FALSE(metricProducer->mIsActive); - EXPECT_EQ(broadcastCount, 4); - ASSERT_EQ(activeConfigsBroadcast.size(), 0); - EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kNotActive); - EXPECT_EQ(eventActivationMap[0]->start_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 15); - EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC); - EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kNotActive); - EXPECT_EQ(eventActivationMap[2]->start_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10); - EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC); - EXPECT_EQ(eventDeactivationMap[3][0], eventActivationMap[0]); - EXPECT_EQ(eventDeactivationMap[4][0], eventActivationMap[2]); - - event = CreateAppCrashEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 14 + 1, 999); - processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 14 + 1); - - // Re-enable battery saver mode activation. - event = CreateBatterySaverOnEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 15 + 15); - processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 15 + 15); - EXPECT_TRUE(metricsManager->isActive()); - EXPECT_TRUE(metricProducer->mIsActive); - EXPECT_EQ(broadcastCount, 5); - ASSERT_EQ(activeConfigsBroadcast.size(), 1); - EXPECT_EQ(activeConfigsBroadcast[0], cfgId); - EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kActive); - EXPECT_EQ(eventActivationMap[0]->start_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 15 + 15); - EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC); - EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kNotActive); - EXPECT_EQ(eventActivationMap[2]->start_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10); - EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC); - EXPECT_EQ(eventDeactivationMap[3][0], eventActivationMap[0]); - EXPECT_EQ(eventDeactivationMap[4][0], eventActivationMap[2]); - - // Cancel battery saver mode and screen on activation. - event = CreateScreenBrightnessChangedEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 16, 140); - processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 16); - EXPECT_FALSE(metricsManager->isActive()); - EXPECT_FALSE(metricProducer->mIsActive); - EXPECT_EQ(broadcastCount, 6); - ASSERT_EQ(activeConfigsBroadcast.size(), 0); - EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kNotActive); - EXPECT_EQ(eventActivationMap[0]->start_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 15 + 15); - EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC); - EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kNotActive); - EXPECT_EQ(eventActivationMap[2]->start_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10); - EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC); - EXPECT_EQ(eventDeactivationMap[3][0], eventActivationMap[0]); - EXPECT_EQ(eventDeactivationMap[4][0], eventActivationMap[2]); - - ConfigMetricsReportList reports; - vector buffer; - processor.onDumpReport(cfgKey, bucketStartTimeNs + NS_PER_SEC * 60 * 15 + 1, false, true, - ADB_DUMP, FAST, &buffer); - EXPECT_TRUE(buffer.size() > 0); - EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size())); - backfillDimensionPath(&reports); - backfillStartEndTimestamp(&reports); - ASSERT_EQ(1, reports.reports_size()); - ASSERT_EQ(1, reports.reports(0).metrics_size()); - - StatsLogReport::CountMetricDataWrapper countMetrics; - sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).count_metrics(), &countMetrics); - ASSERT_EQ(5, countMetrics.data_size()); - - auto data = countMetrics.data(0); - EXPECT_EQ(util::PROCESS_LIFE_CYCLE_STATE_CHANGED, data.dimensions_in_what().field()); - ASSERT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size()); - EXPECT_EQ(1 /* uid field */, - data.dimensions_in_what().value_tuple().dimensions_value(0).field()); - EXPECT_EQ(222, data.dimensions_in_what().value_tuple().dimensions_value(0).value_int()); - ASSERT_EQ(1, data.bucket_info_size()); - EXPECT_EQ(1, data.bucket_info(0).count()); - EXPECT_EQ(bucketStartTimeNs, data.bucket_info(0).start_bucket_elapsed_nanos()); - EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, data.bucket_info(0).end_bucket_elapsed_nanos()); - - data = countMetrics.data(1); - EXPECT_EQ(util::PROCESS_LIFE_CYCLE_STATE_CHANGED, data.dimensions_in_what().field()); - ASSERT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size()); - EXPECT_EQ(1 /* uid field */, - data.dimensions_in_what().value_tuple().dimensions_value(0).field()); - EXPECT_EQ(333, data.dimensions_in_what().value_tuple().dimensions_value(0).value_int()); - ASSERT_EQ(1, data.bucket_info_size()); - EXPECT_EQ(1, data.bucket_info(0).count()); - EXPECT_EQ(bucketStartTimeNs, data.bucket_info(0).start_bucket_elapsed_nanos()); - EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, data.bucket_info(0).end_bucket_elapsed_nanos()); - - data = countMetrics.data(2); - EXPECT_EQ(util::PROCESS_LIFE_CYCLE_STATE_CHANGED, data.dimensions_in_what().field()); - ASSERT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size()); - EXPECT_EQ(1 /* uid field */, - data.dimensions_in_what().value_tuple().dimensions_value(0).field()); - EXPECT_EQ(444, data.dimensions_in_what().value_tuple().dimensions_value(0).value_int()); - ASSERT_EQ(1, data.bucket_info_size()); - EXPECT_EQ(1, data.bucket_info(0).count()); - // Partial bucket as metric is deactivated. - EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, data.bucket_info(0).start_bucket_elapsed_nanos()); - EXPECT_EQ(bucketStartTimeNs + NS_PER_SEC * 60 * 8, - data.bucket_info(0).end_bucket_elapsed_nanos()); - - data = countMetrics.data(3); - EXPECT_EQ(util::PROCESS_LIFE_CYCLE_STATE_CHANGED, data.dimensions_in_what().field()); - ASSERT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size()); - EXPECT_EQ(1 /* uid field */, - data.dimensions_in_what().value_tuple().dimensions_value(0).field()); - EXPECT_EQ(666, data.dimensions_in_what().value_tuple().dimensions_value(0).value_int()); - ASSERT_EQ(1, data.bucket_info_size()); - EXPECT_EQ(1, data.bucket_info(0).count()); - EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs, - data.bucket_info(0).start_bucket_elapsed_nanos()); - EXPECT_EQ(bucketStartTimeNs + NS_PER_SEC * 60 * 11, - data.bucket_info(0).end_bucket_elapsed_nanos()); - - data = countMetrics.data(4); - EXPECT_EQ(util::PROCESS_LIFE_CYCLE_STATE_CHANGED, data.dimensions_in_what().field()); - ASSERT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size()); - EXPECT_EQ(1 /* uid field */, - data.dimensions_in_what().value_tuple().dimensions_value(0).field()); - EXPECT_EQ(777, data.dimensions_in_what().value_tuple().dimensions_value(0).value_int()); - ASSERT_EQ(1, data.bucket_info_size()); - EXPECT_EQ(1, data.bucket_info(0).count()); - EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs, - data.bucket_info(0).start_bucket_elapsed_nanos()); - EXPECT_EQ(bucketStartTimeNs + NS_PER_SEC * 60 * 11, - data.bucket_info(0).end_bucket_elapsed_nanos()); -} - -TEST(MetricActivationE2eTest, TestCountMetricWithSameDeactivation) { - auto config = CreateStatsdConfigWithSameDeactivations(); - - int64_t bucketStartTimeNs = NS_PER_SEC * 10; // 10 secs - int64_t bucketSizeNs = - TimeUnitToBucketSizeInMillis(config.count_metric(0).bucket()) * 1000LL * 1000LL; - - int uid = 12345; - int64_t cfgId = 98765; - ConfigKey cfgKey(uid, cfgId); - - sp m = new UidMap(); - sp pullerManager = new StatsPullerManager(); - sp anomalyAlarmMonitor; - sp subscriberAlarmMonitor; - vector activeConfigsBroadcast; - - long timeBase1 = 1; - int broadcastCount = 0; - StatsLogProcessor processor( - m, pullerManager, anomalyAlarmMonitor, subscriberAlarmMonitor, bucketStartTimeNs, - [](const ConfigKey& key) { return true; }, - [&uid, &broadcastCount, &activeConfigsBroadcast](const int& broadcastUid, - const vector& activeConfigs) { - broadcastCount++; - EXPECT_EQ(broadcastUid, uid); - activeConfigsBroadcast.clear(); - activeConfigsBroadcast.insert(activeConfigsBroadcast.end(), activeConfigs.begin(), - activeConfigs.end()); - return true; - }); - - processor.OnConfigUpdated(bucketStartTimeNs, cfgKey, config); - - ASSERT_EQ(processor.mMetricsManagers.size(), 1u); - sp metricsManager = processor.mMetricsManagers.begin()->second; - EXPECT_TRUE(metricsManager->isConfigValid()); - ASSERT_EQ(metricsManager->mAllMetricProducers.size(), 1); - sp metricProducer = metricsManager->mAllMetricProducers[0]; - auto& eventActivationMap = metricProducer->mEventActivationMap; - auto& eventDeactivationMap = metricProducer->mEventDeactivationMap; - - EXPECT_FALSE(metricsManager->isActive()); - EXPECT_FALSE(metricProducer->mIsActive); - // Two activations: one is triggered by battery saver mode (tracker index 0), the other is - // triggered by screen on event (tracker index 2). - ASSERT_EQ(eventActivationMap.size(), 2u); - EXPECT_TRUE(eventActivationMap.find(0) != eventActivationMap.end()); - EXPECT_TRUE(eventActivationMap.find(2) != eventActivationMap.end()); - EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kNotActive); - EXPECT_EQ(eventActivationMap[0]->start_ns, 0); - EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC); - EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kNotActive); - EXPECT_EQ(eventActivationMap[2]->start_ns, 0); - EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC); - ASSERT_EQ(eventDeactivationMap.size(), 1u); - EXPECT_TRUE(eventDeactivationMap.find(3) != eventDeactivationMap.end()); - ASSERT_EQ(eventDeactivationMap[3].size(), 2u); - EXPECT_EQ(eventDeactivationMap[3][0], eventActivationMap[0]); - EXPECT_EQ(eventDeactivationMap[3][1], eventActivationMap[2]); - EXPECT_EQ(broadcastCount, 0); - - std::unique_ptr event; - - // Event that should be ignored. - event = CreateAppCrashEvent(bucketStartTimeNs + 1, 111); - processor.OnLogEvent(event.get(), bucketStartTimeNs + 1); - - // Activate metric via screen on for 2 minutes. - event = CreateScreenStateChangedEvent(bucketStartTimeNs + 10, android::view::DISPLAY_STATE_ON); - processor.OnLogEvent(event.get(), bucketStartTimeNs + 10); - EXPECT_TRUE(metricsManager->isActive()); - EXPECT_TRUE(metricProducer->mIsActive); - EXPECT_EQ(broadcastCount, 1); - ASSERT_EQ(activeConfigsBroadcast.size(), 1); - EXPECT_EQ(activeConfigsBroadcast[0], cfgId); - EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kNotActive); - EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kActive); - EXPECT_EQ(eventActivationMap[2]->start_ns, bucketStartTimeNs + 10); - - // 1st processed event. - event = CreateAppCrashEvent(bucketStartTimeNs + 15, 222); - processor.OnLogEvent(event.get(), bucketStartTimeNs + 15); - - // Enable battery saver mode activation for 5 minutes. - event = CreateBatterySaverOnEvent(bucketStartTimeNs + NS_PER_SEC * 60 + 10); - processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 + 10); - EXPECT_TRUE(metricsManager->isActive()); - EXPECT_TRUE(metricProducer->mIsActive); - EXPECT_EQ(broadcastCount, 1); - EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kActive); - EXPECT_EQ(eventActivationMap[0]->start_ns, bucketStartTimeNs + NS_PER_SEC * 60 + 10); - EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kActive); - EXPECT_EQ(eventActivationMap[2]->start_ns, bucketStartTimeNs + 10); - - // 2nd processed event. - event = CreateAppCrashEvent(bucketStartTimeNs + NS_PER_SEC * 60 + 40, 333); - processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 + 40); - - // Cancel battery saver mode and screen on activation. - int64_t firstDeactivation = bucketStartTimeNs + NS_PER_SEC * 61; - event = CreateScreenBrightnessChangedEvent(firstDeactivation, 64); - processor.OnLogEvent(event.get(), firstDeactivation); - EXPECT_FALSE(metricsManager->isActive()); - EXPECT_FALSE(metricProducer->mIsActive); - // New broadcast since the config is no longer active. - EXPECT_EQ(broadcastCount, 2); - ASSERT_EQ(activeConfigsBroadcast.size(), 0); - EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kNotActive); - EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kNotActive); - - // Should be ignored - event = CreateAppCrashEvent(bucketStartTimeNs + NS_PER_SEC * 61 + 80, 444); - processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 61 + 80); - - // Re-enable battery saver mode activation. - event = CreateBatterySaverOnEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 15); - processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 15); - EXPECT_TRUE(metricsManager->isActive()); - EXPECT_TRUE(metricProducer->mIsActive); - EXPECT_EQ(broadcastCount, 3); - ASSERT_EQ(activeConfigsBroadcast.size(), 1); - EXPECT_EQ(activeConfigsBroadcast[0], cfgId); - EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kActive); - EXPECT_EQ(eventActivationMap[0]->start_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 15); - EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kNotActive); - - // 3rd processed event. - event = CreateAppCrashEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 80, 555); - processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 80); - - // Cancel battery saver mode activation. - int64_t secondDeactivation = bucketStartTimeNs + NS_PER_SEC * 60 * 13; - event = CreateScreenBrightnessChangedEvent(secondDeactivation, 140); - processor.OnLogEvent(event.get(), secondDeactivation); - EXPECT_FALSE(metricsManager->isActive()); - EXPECT_FALSE(metricProducer->mIsActive); - EXPECT_EQ(broadcastCount, 4); - ASSERT_EQ(activeConfigsBroadcast.size(), 0); - EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kNotActive); - EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kNotActive); - - // Should be ignored. - event = CreateAppCrashEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 13 + 80, 666); - processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 13 + 80); - - ConfigMetricsReportList reports; - vector buffer; - processor.onDumpReport(cfgKey, bucketStartTimeNs + NS_PER_SEC * 60 * 15 + 1, false, true, - ADB_DUMP, FAST, &buffer); - EXPECT_TRUE(buffer.size() > 0); - EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size())); - backfillDimensionPath(&reports); - backfillStartEndTimestamp(&reports); - ASSERT_EQ(1, reports.reports_size()); - ASSERT_EQ(1, reports.reports(0).metrics_size()); - - StatsLogReport::CountMetricDataWrapper countMetrics; - sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).count_metrics(), &countMetrics); - ASSERT_EQ(3, countMetrics.data_size()); - - auto data = countMetrics.data(0); - EXPECT_EQ(util::PROCESS_LIFE_CYCLE_STATE_CHANGED, data.dimensions_in_what().field()); - ASSERT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size()); - EXPECT_EQ(1 /* uid field */, - data.dimensions_in_what().value_tuple().dimensions_value(0).field()); - EXPECT_EQ(222, data.dimensions_in_what().value_tuple().dimensions_value(0).value_int()); - ASSERT_EQ(1, data.bucket_info_size()); - EXPECT_EQ(1, data.bucket_info(0).count()); - EXPECT_EQ(bucketStartTimeNs, data.bucket_info(0).start_bucket_elapsed_nanos()); - EXPECT_EQ(firstDeactivation, data.bucket_info(0).end_bucket_elapsed_nanos()); - - data = countMetrics.data(1); - EXPECT_EQ(util::PROCESS_LIFE_CYCLE_STATE_CHANGED, data.dimensions_in_what().field()); - ASSERT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size()); - EXPECT_EQ(1 /* uid field */, - data.dimensions_in_what().value_tuple().dimensions_value(0).field()); - EXPECT_EQ(333, data.dimensions_in_what().value_tuple().dimensions_value(0).value_int()); - ASSERT_EQ(1, data.bucket_info_size()); - EXPECT_EQ(1, data.bucket_info(0).count()); - EXPECT_EQ(bucketStartTimeNs, data.bucket_info(0).start_bucket_elapsed_nanos()); - EXPECT_EQ(firstDeactivation, data.bucket_info(0).end_bucket_elapsed_nanos()); - - data = countMetrics.data(2); - EXPECT_EQ(util::PROCESS_LIFE_CYCLE_STATE_CHANGED, data.dimensions_in_what().field()); - ASSERT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size()); - EXPECT_EQ(1 /* uid field */, - data.dimensions_in_what().value_tuple().dimensions_value(0).field()); - EXPECT_EQ(555, data.dimensions_in_what().value_tuple().dimensions_value(0).value_int()); - ASSERT_EQ(1, data.bucket_info_size()); - EXPECT_EQ(1, data.bucket_info(0).count()); - // Partial bucket as metric is deactivated. - EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs, - data.bucket_info(0).start_bucket_elapsed_nanos()); - EXPECT_EQ(secondDeactivation, data.bucket_info(0).end_bucket_elapsed_nanos()); -} - -TEST(MetricActivationE2eTest, TestCountMetricWithTwoMetricsTwoDeactivations) { - auto config = CreateStatsdConfigWithTwoMetricsTwoDeactivations(); - - int64_t bucketStartTimeNs = NS_PER_SEC * 10; // 10 secs - int64_t bucketSizeNs = - TimeUnitToBucketSizeInMillis(config.count_metric(0).bucket()) * 1000LL * 1000LL; - - int uid = 12345; - int64_t cfgId = 98765; - ConfigKey cfgKey(uid, cfgId); - - sp m = new UidMap(); - sp pullerManager = new StatsPullerManager(); - sp anomalyAlarmMonitor; - sp subscriberAlarmMonitor; - vector activeConfigsBroadcast; - - long timeBase1 = 1; - int broadcastCount = 0; - StatsLogProcessor processor( - m, pullerManager, anomalyAlarmMonitor, subscriberAlarmMonitor, bucketStartTimeNs, - [](const ConfigKey& key) { return true; }, - [&uid, &broadcastCount, &activeConfigsBroadcast](const int& broadcastUid, - const vector& activeConfigs) { - broadcastCount++; - EXPECT_EQ(broadcastUid, uid); - activeConfigsBroadcast.clear(); - activeConfigsBroadcast.insert(activeConfigsBroadcast.end(), activeConfigs.begin(), - activeConfigs.end()); - return true; - }); - - processor.OnConfigUpdated(bucketStartTimeNs, cfgKey, config); - - ASSERT_EQ(processor.mMetricsManagers.size(), 1u); - sp metricsManager = processor.mMetricsManagers.begin()->second; - EXPECT_TRUE(metricsManager->isConfigValid()); - ASSERT_EQ(metricsManager->mAllMetricProducers.size(), 2); - sp metricProducer = metricsManager->mAllMetricProducers[0]; - auto& eventActivationMap = metricProducer->mEventActivationMap; - auto& eventDeactivationMap = metricProducer->mEventDeactivationMap; - sp metricProducer2 = metricsManager->mAllMetricProducers[1]; - auto& eventActivationMap2 = metricProducer2->mEventActivationMap; - auto& eventDeactivationMap2 = metricProducer2->mEventDeactivationMap; - - EXPECT_FALSE(metricsManager->isActive()); - EXPECT_FALSE(metricProducer->mIsActive); - EXPECT_FALSE(metricProducer2->mIsActive); - // Two activations: one is triggered by battery saver mode (tracker index 0), the other is - // triggered by screen on event (tracker index 2). - ASSERT_EQ(eventActivationMap.size(), 2u); - EXPECT_TRUE(eventActivationMap.find(0) != eventActivationMap.end()); - EXPECT_TRUE(eventActivationMap.find(2) != eventActivationMap.end()); - EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kNotActive); - EXPECT_EQ(eventActivationMap[0]->start_ns, 0); - EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC); - EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kNotActive); - EXPECT_EQ(eventActivationMap[2]->start_ns, 0); - EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC); - ASSERT_EQ(eventDeactivationMap.size(), 2u); - EXPECT_TRUE(eventDeactivationMap.find(3) != eventDeactivationMap.end()); - EXPECT_TRUE(eventDeactivationMap.find(4) != eventDeactivationMap.end()); - ASSERT_EQ(eventDeactivationMap[3].size(), 1u); - ASSERT_EQ(eventDeactivationMap[4].size(), 1u); - EXPECT_EQ(eventDeactivationMap[3][0], eventActivationMap[0]); - EXPECT_EQ(eventDeactivationMap[4][0], eventActivationMap[2]); - - ASSERT_EQ(eventActivationMap2.size(), 2u); - EXPECT_TRUE(eventActivationMap2.find(0) != eventActivationMap2.end()); - EXPECT_TRUE(eventActivationMap2.find(2) != eventActivationMap2.end()); - EXPECT_EQ(eventActivationMap2[0]->state, ActivationState::kNotActive); - EXPECT_EQ(eventActivationMap2[0]->start_ns, 0); - EXPECT_EQ(eventActivationMap2[0]->ttl_ns, 60 * 6 * NS_PER_SEC); - EXPECT_EQ(eventActivationMap2[2]->state, ActivationState::kNotActive); - EXPECT_EQ(eventActivationMap2[2]->start_ns, 0); - EXPECT_EQ(eventActivationMap2[2]->ttl_ns, 60 * 2 * NS_PER_SEC); - ASSERT_EQ(eventDeactivationMap2.size(), 2u); - EXPECT_TRUE(eventDeactivationMap2.find(3) != eventDeactivationMap2.end()); - EXPECT_TRUE(eventDeactivationMap2.find(4) != eventDeactivationMap2.end()); - ASSERT_EQ(eventDeactivationMap[3].size(), 1u); - ASSERT_EQ(eventDeactivationMap[4].size(), 1u); - EXPECT_EQ(eventDeactivationMap2[3][0], eventActivationMap2[0]); - EXPECT_EQ(eventDeactivationMap2[4][0], eventActivationMap2[2]); - - std::unique_ptr event; - - event = CreateAppCrashEvent(bucketStartTimeNs + 5, 111); - processor.OnLogEvent(event.get(), bucketStartTimeNs + 5); - event = CreateMoveToForegroundEvent(bucketStartTimeNs + 5, 1111); - processor.OnLogEvent(event.get(), bucketStartTimeNs + 5); - EXPECT_FALSE(metricsManager->isActive()); - EXPECT_FALSE(metricProducer->mIsActive); - EXPECT_FALSE(metricProducer2->mIsActive); - EXPECT_EQ(broadcastCount, 0); - - // Activated by battery save mode. - event = CreateBatterySaverOnEvent(bucketStartTimeNs + 10); - processor.OnLogEvent(event.get(), bucketStartTimeNs + 10); - EXPECT_TRUE(metricsManager->isActive()); - EXPECT_EQ(broadcastCount, 1); - ASSERT_EQ(activeConfigsBroadcast.size(), 1); - EXPECT_EQ(activeConfigsBroadcast[0], cfgId); - EXPECT_TRUE(metricProducer->mIsActive); - EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kActive); - EXPECT_EQ(eventActivationMap[0]->start_ns, bucketStartTimeNs + 10); - EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC); - EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kNotActive); - EXPECT_EQ(eventActivationMap[2]->start_ns, 0); - EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC); - EXPECT_EQ(eventDeactivationMap[3][0], eventActivationMap[0]); - EXPECT_EQ(eventDeactivationMap[4][0], eventActivationMap[2]); - EXPECT_TRUE(metricProducer2->mIsActive); - EXPECT_EQ(eventActivationMap2[0]->state, ActivationState::kActive); - EXPECT_EQ(eventActivationMap2[0]->start_ns, bucketStartTimeNs + 10); - EXPECT_EQ(eventActivationMap2[0]->ttl_ns, 60 * 6 * NS_PER_SEC); - EXPECT_EQ(eventActivationMap2[2]->state, ActivationState::kNotActive); - EXPECT_EQ(eventActivationMap2[2]->start_ns, 0); - EXPECT_EQ(eventActivationMap2[2]->ttl_ns, 60 * 2 * NS_PER_SEC); - EXPECT_EQ(eventDeactivationMap2[3][0], eventActivationMap2[0]); - EXPECT_EQ(eventDeactivationMap2[4][0], eventActivationMap2[2]); - - // First processed event. - event = CreateAppCrashEvent(bucketStartTimeNs + 15, 222); - processor.OnLogEvent(event.get(), bucketStartTimeNs + 15); - event = CreateMoveToForegroundEvent(bucketStartTimeNs + 15, 2222); - processor.OnLogEvent(event.get(), bucketStartTimeNs + 15); - - // Activated by screen on event. - event = CreateScreenStateChangedEvent(bucketStartTimeNs + 20, android::view::DISPLAY_STATE_ON); - processor.OnLogEvent(event.get(), bucketStartTimeNs + 20); - EXPECT_TRUE(metricsManager->isActive()); - EXPECT_TRUE(metricProducer->mIsActive); - EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kActive); - EXPECT_EQ(eventActivationMap[0]->start_ns, bucketStartTimeNs + 10); - EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC); - EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kActive); - EXPECT_EQ(eventActivationMap[2]->start_ns, bucketStartTimeNs + 20); - EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC); - EXPECT_EQ(eventDeactivationMap[3][0], eventActivationMap[0]); - EXPECT_EQ(eventDeactivationMap[4][0], eventActivationMap[2]); - EXPECT_TRUE(metricProducer2->mIsActive); - EXPECT_EQ(eventActivationMap2[0]->state, ActivationState::kActive); - EXPECT_EQ(eventActivationMap2[0]->start_ns, bucketStartTimeNs + 10); - EXPECT_EQ(eventActivationMap2[0]->ttl_ns, 60 * 6 * NS_PER_SEC); - EXPECT_EQ(eventActivationMap2[2]->state, ActivationState::kActive); - EXPECT_EQ(eventActivationMap2[2]->start_ns, bucketStartTimeNs + 20); - EXPECT_EQ(eventActivationMap2[2]->ttl_ns, 60 * 2 * NS_PER_SEC); - EXPECT_EQ(eventDeactivationMap2[3][0], eventActivationMap2[0]); - EXPECT_EQ(eventDeactivationMap2[4][0], eventActivationMap2[2]); - - // 2nd processed event. - // The activation by screen_on event expires, but the one by battery save mode is still active. - event = CreateAppCrashEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 2 + 25, 333); - processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 2 + 25); - event = CreateMoveToForegroundEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 2 + 25, 3333); - processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 2 + 25); - EXPECT_TRUE(metricsManager->isActive()); - EXPECT_TRUE(metricProducer->mIsActive); - EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kActive); - EXPECT_EQ(eventActivationMap[0]->start_ns, bucketStartTimeNs + 10); - EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC); - EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kNotActive); - EXPECT_EQ(eventActivationMap[2]->start_ns, bucketStartTimeNs + 20); - EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC); - EXPECT_EQ(eventDeactivationMap[3][0], eventActivationMap[0]); - EXPECT_EQ(eventDeactivationMap[4][0], eventActivationMap[2]); - EXPECT_TRUE(metricProducer2->mIsActive); - EXPECT_EQ(eventActivationMap2[0]->state, ActivationState::kActive); - EXPECT_EQ(eventActivationMap2[0]->start_ns, bucketStartTimeNs + 10); - EXPECT_EQ(eventActivationMap2[0]->ttl_ns, 60 * 6 * NS_PER_SEC); - EXPECT_EQ(eventActivationMap2[2]->state, ActivationState::kNotActive); - EXPECT_EQ(eventActivationMap2[2]->start_ns, bucketStartTimeNs + 20); - EXPECT_EQ(eventActivationMap2[2]->ttl_ns, 60 * 2 * NS_PER_SEC); - EXPECT_EQ(eventDeactivationMap2[3][0], eventActivationMap2[0]); - EXPECT_EQ(eventDeactivationMap2[4][0], eventActivationMap2[2]); - // No new broadcast since the config should still be active. - EXPECT_EQ(broadcastCount, 1); - - // 3rd processed event. - event = CreateAppCrashEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 5 + 25, 444); - processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 5 + 25); - event = CreateMoveToForegroundEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 5 + 25, 4444); - processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 5 + 25); - - // All activations expired. - event = CreateAppCrashEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 8, 555); - processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 8); - event = CreateMoveToForegroundEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 8, 5555); - processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 8); - EXPECT_FALSE(metricsManager->isActive()); - // New broadcast since the config is no longer active. - EXPECT_EQ(broadcastCount, 2); - ASSERT_EQ(activeConfigsBroadcast.size(), 0); - EXPECT_FALSE(metricProducer->mIsActive); - EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kNotActive); - EXPECT_EQ(eventActivationMap[0]->start_ns, bucketStartTimeNs + 10); - EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC); - EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kNotActive); - EXPECT_EQ(eventActivationMap[2]->start_ns, bucketStartTimeNs + 20); - EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC); - EXPECT_EQ(eventDeactivationMap[3][0], eventActivationMap[0]); - EXPECT_EQ(eventDeactivationMap[4][0], eventActivationMap[2]); - EXPECT_FALSE(metricProducer2->mIsActive); - EXPECT_EQ(eventActivationMap2[0]->state, ActivationState::kNotActive); - EXPECT_EQ(eventActivationMap2[0]->start_ns, bucketStartTimeNs + 10); - EXPECT_EQ(eventActivationMap2[0]->ttl_ns, 60 * 6 * NS_PER_SEC); - EXPECT_EQ(eventActivationMap2[2]->state, ActivationState::kNotActive); - EXPECT_EQ(eventActivationMap2[2]->start_ns, bucketStartTimeNs + 20); - EXPECT_EQ(eventActivationMap2[2]->ttl_ns, 60 * 2 * NS_PER_SEC); - EXPECT_EQ(eventDeactivationMap2[3][0], eventActivationMap2[0]); - EXPECT_EQ(eventDeactivationMap2[4][0], eventActivationMap2[2]); - - // Re-activate metric via screen on. - event = CreateScreenStateChangedEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10, - android::view::DISPLAY_STATE_ON); - processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10); - EXPECT_TRUE(metricsManager->isActive()); - EXPECT_EQ(broadcastCount, 3); - ASSERT_EQ(activeConfigsBroadcast.size(), 1); - EXPECT_EQ(activeConfigsBroadcast[0], cfgId); - EXPECT_TRUE(metricProducer->mIsActive); - EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kNotActive); - EXPECT_EQ(eventActivationMap[0]->start_ns, bucketStartTimeNs + 10); - EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC); - EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kActive); - EXPECT_EQ(eventActivationMap[2]->start_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10); - EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC); - EXPECT_EQ(eventDeactivationMap[3][0], eventActivationMap[0]); - EXPECT_EQ(eventDeactivationMap[4][0], eventActivationMap[2]); - EXPECT_TRUE(metricProducer2->mIsActive); - EXPECT_EQ(eventActivationMap2[0]->state, ActivationState::kNotActive); - EXPECT_EQ(eventActivationMap2[0]->start_ns, bucketStartTimeNs + 10); - EXPECT_EQ(eventActivationMap2[0]->ttl_ns, 60 * 6 * NS_PER_SEC); - EXPECT_EQ(eventActivationMap2[2]->state, ActivationState::kActive); - EXPECT_EQ(eventActivationMap2[2]->start_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10); - EXPECT_EQ(eventActivationMap2[2]->ttl_ns, 60 * 2 * NS_PER_SEC); - EXPECT_EQ(eventDeactivationMap2[3][0], eventActivationMap2[0]); - EXPECT_EQ(eventDeactivationMap2[4][0], eventActivationMap2[2]); - - // 4th processed event. - event = CreateAppCrashEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 1, 666); - processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 1); - event = CreateMoveToForegroundEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 1, 6666); - processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 1); - - // Re-enable battery saver mode activation. - event = CreateBatterySaverOnEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 15); - processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 15); - EXPECT_TRUE(metricsManager->isActive()); - EXPECT_EQ(broadcastCount, 3); - ASSERT_EQ(activeConfigsBroadcast.size(), 1); - EXPECT_EQ(activeConfigsBroadcast[0], cfgId); - EXPECT_TRUE(metricProducer->mIsActive); - EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kActive); - EXPECT_EQ(eventActivationMap[0]->start_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 15); - EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC); - EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kActive); - EXPECT_EQ(eventActivationMap[2]->start_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10); - EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC); - EXPECT_EQ(eventDeactivationMap[3][0], eventActivationMap[0]); - EXPECT_EQ(eventDeactivationMap[4][0], eventActivationMap[2]); - EXPECT_TRUE(metricProducer2->mIsActive); - EXPECT_EQ(eventActivationMap2[0]->state, ActivationState::kActive); - EXPECT_EQ(eventActivationMap2[0]->start_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 15); - EXPECT_EQ(eventActivationMap2[0]->ttl_ns, 60 * 6 * NS_PER_SEC); - EXPECT_EQ(eventActivationMap2[2]->state, ActivationState::kActive); - EXPECT_EQ(eventActivationMap2[2]->start_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10); - EXPECT_EQ(eventActivationMap2[2]->ttl_ns, 60 * 2 * NS_PER_SEC); - EXPECT_EQ(eventDeactivationMap2[3][0], eventActivationMap2[0]); - EXPECT_EQ(eventDeactivationMap2[4][0], eventActivationMap2[2]); - - // 5th processed event. - event = CreateAppCrashEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 40, 777); - processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 40); - event = CreateMoveToForegroundEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 40, 7777); - processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 40); - - // Cancel battery saver mode and screen on activation. - event = CreateScreenBrightnessChangedEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 60, 64); - processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 60); - EXPECT_FALSE(metricsManager->isActive()); - // New broadcast since the config is no longer active. - EXPECT_EQ(broadcastCount, 4); - ASSERT_EQ(activeConfigsBroadcast.size(), 0); - EXPECT_FALSE(metricProducer->mIsActive); - EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kNotActive); - EXPECT_EQ(eventActivationMap[0]->start_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 15); - EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC); - EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kNotActive); - EXPECT_EQ(eventActivationMap[2]->start_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10); - EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC); - EXPECT_EQ(eventDeactivationMap[3][0], eventActivationMap[0]); - EXPECT_EQ(eventDeactivationMap[4][0], eventActivationMap[2]); - EXPECT_FALSE(metricProducer2->mIsActive); - EXPECT_EQ(eventActivationMap2[0]->state, ActivationState::kNotActive); - EXPECT_EQ(eventActivationMap2[0]->start_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 15); - EXPECT_EQ(eventActivationMap2[0]->ttl_ns, 60 * 6 * NS_PER_SEC); - EXPECT_EQ(eventActivationMap2[2]->state, ActivationState::kNotActive); - EXPECT_EQ(eventActivationMap2[2]->start_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10); - EXPECT_EQ(eventActivationMap2[2]->ttl_ns, 60 * 2 * NS_PER_SEC); - EXPECT_EQ(eventDeactivationMap2[3][0], eventActivationMap2[0]); - EXPECT_EQ(eventDeactivationMap2[4][0], eventActivationMap2[2]); - - // Screen-on activation expired. - event = CreateAppCrashEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 13, 888); - processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 13); - event = CreateMoveToForegroundEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 13, 8888); - processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 13); - EXPECT_FALSE(metricsManager->isActive()); - EXPECT_EQ(broadcastCount, 4); - ASSERT_EQ(activeConfigsBroadcast.size(), 0); - EXPECT_FALSE(metricProducer->mIsActive); - EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kNotActive); - EXPECT_EQ(eventActivationMap[0]->start_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 15); - EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC); - EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kNotActive); - EXPECT_EQ(eventActivationMap[2]->start_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10); - EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC); - EXPECT_EQ(eventDeactivationMap[3][0], eventActivationMap[0]); - EXPECT_EQ(eventDeactivationMap[4][0], eventActivationMap[2]); - EXPECT_FALSE(metricProducer2->mIsActive); - EXPECT_EQ(eventActivationMap2[0]->state, ActivationState::kNotActive); - EXPECT_EQ(eventActivationMap2[0]->start_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 15); - EXPECT_EQ(eventActivationMap2[0]->ttl_ns, 60 * 6 * NS_PER_SEC); - EXPECT_EQ(eventActivationMap2[2]->state, ActivationState::kNotActive); - EXPECT_EQ(eventActivationMap2[2]->start_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10); - EXPECT_EQ(eventActivationMap2[2]->ttl_ns, 60 * 2 * NS_PER_SEC); - EXPECT_EQ(eventDeactivationMap2[3][0], eventActivationMap2[0]); - EXPECT_EQ(eventDeactivationMap2[4][0], eventActivationMap2[2]); - - event = CreateAppCrashEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 14 + 1, 999); - processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 14 + 1); - event = CreateMoveToForegroundEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 14 + 1, 9999); - processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 14 + 1); - - // Re-enable battery saver mode activation. - event = CreateBatterySaverOnEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 15 + 15); - processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 15 + 15); - EXPECT_TRUE(metricsManager->isActive()); - EXPECT_EQ(broadcastCount, 5); - ASSERT_EQ(activeConfigsBroadcast.size(), 1); - EXPECT_EQ(activeConfigsBroadcast[0], cfgId); - EXPECT_TRUE(metricProducer->mIsActive); - EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kActive); - EXPECT_EQ(eventActivationMap[0]->start_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 15 + 15); - EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC); - EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kNotActive); - EXPECT_EQ(eventActivationMap[2]->start_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10); - EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC); - EXPECT_EQ(eventDeactivationMap[3][0], eventActivationMap[0]); - EXPECT_EQ(eventDeactivationMap[4][0], eventActivationMap[2]); - EXPECT_TRUE(metricProducer2->mIsActive); - EXPECT_EQ(eventActivationMap2[0]->state, ActivationState::kActive); - EXPECT_EQ(eventActivationMap2[0]->start_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 15 + 15); - EXPECT_EQ(eventActivationMap2[0]->ttl_ns, 60 * 6 * NS_PER_SEC); - EXPECT_EQ(eventActivationMap2[2]->state, ActivationState::kNotActive); - EXPECT_EQ(eventActivationMap2[2]->start_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10); - EXPECT_EQ(eventActivationMap2[2]->ttl_ns, 60 * 2 * NS_PER_SEC); - EXPECT_EQ(eventDeactivationMap2[3][0], eventActivationMap2[0]); - EXPECT_EQ(eventDeactivationMap2[4][0], eventActivationMap2[2]); - - // Cancel battery saver mode and screen on activation. - event = CreateScreenBrightnessChangedEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 16, 140); - processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 16); - EXPECT_FALSE(metricsManager->isActive()); - EXPECT_EQ(broadcastCount, 6); - ASSERT_EQ(activeConfigsBroadcast.size(), 0); - EXPECT_FALSE(metricProducer->mIsActive); - EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kNotActive); - EXPECT_EQ(eventActivationMap[0]->start_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 15 + 15); - EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC); - EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kNotActive); - EXPECT_EQ(eventActivationMap[2]->start_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10); - EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC); - EXPECT_EQ(eventDeactivationMap[3][0], eventActivationMap[0]); - EXPECT_EQ(eventDeactivationMap[4][0], eventActivationMap[2]); - EXPECT_FALSE(metricProducer2->mIsActive); - EXPECT_EQ(eventActivationMap2[0]->state, ActivationState::kNotActive); - EXPECT_EQ(eventActivationMap2[0]->start_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 15 + 15); - EXPECT_EQ(eventActivationMap2[0]->ttl_ns, 60 * 6 * NS_PER_SEC); - EXPECT_EQ(eventActivationMap2[2]->state, ActivationState::kNotActive); - EXPECT_EQ(eventActivationMap2[2]->start_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10); - EXPECT_EQ(eventActivationMap2[2]->ttl_ns, 60 * 2 * NS_PER_SEC); - EXPECT_EQ(eventDeactivationMap2[3][0], eventActivationMap2[0]); - EXPECT_EQ(eventDeactivationMap2[4][0], eventActivationMap2[2]); - - ConfigMetricsReportList reports; - vector buffer; - processor.onDumpReport(cfgKey, bucketStartTimeNs + NS_PER_SEC * 60 * 15 + 1, false, true, - ADB_DUMP, FAST, &buffer); - EXPECT_TRUE(buffer.size() > 0); - EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size())); - backfillDimensionPath(&reports); - backfillStartEndTimestamp(&reports); - ASSERT_EQ(1, reports.reports_size()); - ASSERT_EQ(2, reports.reports(0).metrics_size()); - - StatsLogReport::CountMetricDataWrapper countMetrics; - - sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).count_metrics(), &countMetrics); - ASSERT_EQ(5, countMetrics.data_size()); - - auto data = countMetrics.data(0); - EXPECT_EQ(util::PROCESS_LIFE_CYCLE_STATE_CHANGED, data.dimensions_in_what().field()); - ASSERT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size()); - EXPECT_EQ(1 /* uid field */, - data.dimensions_in_what().value_tuple().dimensions_value(0).field()); - EXPECT_EQ(222, data.dimensions_in_what().value_tuple().dimensions_value(0).value_int()); - ASSERT_EQ(1, data.bucket_info_size()); - EXPECT_EQ(1, data.bucket_info(0).count()); - EXPECT_EQ(bucketStartTimeNs, data.bucket_info(0).start_bucket_elapsed_nanos()); - EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, data.bucket_info(0).end_bucket_elapsed_nanos()); - - data = countMetrics.data(1); - EXPECT_EQ(util::PROCESS_LIFE_CYCLE_STATE_CHANGED, data.dimensions_in_what().field()); - ASSERT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size()); - EXPECT_EQ(1 /* uid field */, - data.dimensions_in_what().value_tuple().dimensions_value(0).field()); - EXPECT_EQ(333, data.dimensions_in_what().value_tuple().dimensions_value(0).value_int()); - ASSERT_EQ(1, data.bucket_info_size()); - EXPECT_EQ(1, data.bucket_info(0).count()); - EXPECT_EQ(bucketStartTimeNs, data.bucket_info(0).start_bucket_elapsed_nanos()); - EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, data.bucket_info(0).end_bucket_elapsed_nanos()); - - data = countMetrics.data(2); - EXPECT_EQ(util::PROCESS_LIFE_CYCLE_STATE_CHANGED, data.dimensions_in_what().field()); - ASSERT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size()); - EXPECT_EQ(1 /* uid field */, - data.dimensions_in_what().value_tuple().dimensions_value(0).field()); - EXPECT_EQ(444, data.dimensions_in_what().value_tuple().dimensions_value(0).value_int()); - ASSERT_EQ(1, data.bucket_info_size()); - EXPECT_EQ(1, data.bucket_info(0).count()); - // Partial bucket as metric is deactivated. - EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, data.bucket_info(0).start_bucket_elapsed_nanos()); - EXPECT_EQ(bucketStartTimeNs + NS_PER_SEC * 60 * 8, - data.bucket_info(0).end_bucket_elapsed_nanos()); - - data = countMetrics.data(3); - EXPECT_EQ(util::PROCESS_LIFE_CYCLE_STATE_CHANGED, data.dimensions_in_what().field()); - ASSERT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size()); - EXPECT_EQ(1 /* uid field */, - data.dimensions_in_what().value_tuple().dimensions_value(0).field()); - EXPECT_EQ(666, data.dimensions_in_what().value_tuple().dimensions_value(0).value_int()); - ASSERT_EQ(1, data.bucket_info_size()); - EXPECT_EQ(1, data.bucket_info(0).count()); - EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs, - data.bucket_info(0).start_bucket_elapsed_nanos()); - EXPECT_EQ(bucketStartTimeNs + NS_PER_SEC * 60 * 11, - data.bucket_info(0).end_bucket_elapsed_nanos()); - - data = countMetrics.data(4); - EXPECT_EQ(util::PROCESS_LIFE_CYCLE_STATE_CHANGED, data.dimensions_in_what().field()); - ASSERT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size()); - EXPECT_EQ(1 /* uid field */, - data.dimensions_in_what().value_tuple().dimensions_value(0).field()); - EXPECT_EQ(777, data.dimensions_in_what().value_tuple().dimensions_value(0).value_int()); - ASSERT_EQ(1, data.bucket_info_size()); - EXPECT_EQ(1, data.bucket_info(0).count()); - EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs, - data.bucket_info(0).start_bucket_elapsed_nanos()); - EXPECT_EQ(bucketStartTimeNs + NS_PER_SEC * 60 * 11, - data.bucket_info(0).end_bucket_elapsed_nanos()); - - countMetrics.clear_data(); - sortMetricDataByDimensionsValue(reports.reports(0).metrics(1).count_metrics(), &countMetrics); - ASSERT_EQ(5, countMetrics.data_size()); - - data = countMetrics.data(0); - EXPECT_EQ(util::ACTIVITY_FOREGROUND_STATE_CHANGED, data.dimensions_in_what().field()); - ASSERT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size()); - EXPECT_EQ(1 /* uid field */, - data.dimensions_in_what().value_tuple().dimensions_value(0).field()); - EXPECT_EQ(2222, data.dimensions_in_what().value_tuple().dimensions_value(0).value_int()); - ASSERT_EQ(1, data.bucket_info_size()); - EXPECT_EQ(1, data.bucket_info(0).count()); - EXPECT_EQ(bucketStartTimeNs, data.bucket_info(0).start_bucket_elapsed_nanos()); - EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, data.bucket_info(0).end_bucket_elapsed_nanos()); - - data = countMetrics.data(1); - EXPECT_EQ(util::ACTIVITY_FOREGROUND_STATE_CHANGED, data.dimensions_in_what().field()); - ASSERT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size()); - EXPECT_EQ(1 /* uid field */, - data.dimensions_in_what().value_tuple().dimensions_value(0).field()); - EXPECT_EQ(3333, data.dimensions_in_what().value_tuple().dimensions_value(0).value_int()); - ASSERT_EQ(1, data.bucket_info_size()); - EXPECT_EQ(1, data.bucket_info(0).count()); - EXPECT_EQ(bucketStartTimeNs, data.bucket_info(0).start_bucket_elapsed_nanos()); - EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, data.bucket_info(0).end_bucket_elapsed_nanos()); - - data = countMetrics.data(2); - EXPECT_EQ(util::ACTIVITY_FOREGROUND_STATE_CHANGED, data.dimensions_in_what().field()); - ASSERT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size()); - EXPECT_EQ(1 /* uid field */, - data.dimensions_in_what().value_tuple().dimensions_value(0).field()); - EXPECT_EQ(4444, data.dimensions_in_what().value_tuple().dimensions_value(0).value_int()); - ASSERT_EQ(1, data.bucket_info_size()); - EXPECT_EQ(1, data.bucket_info(0).count()); - // Partial bucket as metric is deactivated. - EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, data.bucket_info(0).start_bucket_elapsed_nanos()); - EXPECT_EQ(bucketStartTimeNs + NS_PER_SEC * 60 * 8, - data.bucket_info(0).end_bucket_elapsed_nanos()); - - data = countMetrics.data(3); - EXPECT_EQ(util::ACTIVITY_FOREGROUND_STATE_CHANGED, data.dimensions_in_what().field()); - ASSERT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size()); - EXPECT_EQ(1 /* uid field */, - data.dimensions_in_what().value_tuple().dimensions_value(0).field()); - EXPECT_EQ(6666, data.dimensions_in_what().value_tuple().dimensions_value(0).value_int()); - ASSERT_EQ(1, data.bucket_info_size()); - EXPECT_EQ(1, data.bucket_info(0).count()); - EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs, - data.bucket_info(0).start_bucket_elapsed_nanos()); - EXPECT_EQ(bucketStartTimeNs + NS_PER_SEC * 60 * 11, - data.bucket_info(0).end_bucket_elapsed_nanos()); - - data = countMetrics.data(4); - EXPECT_EQ(util::ACTIVITY_FOREGROUND_STATE_CHANGED, data.dimensions_in_what().field()); - ASSERT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size()); - EXPECT_EQ(1 /* uid field */, - data.dimensions_in_what().value_tuple().dimensions_value(0).field()); - EXPECT_EQ(7777, data.dimensions_in_what().value_tuple().dimensions_value(0).value_int()); - ASSERT_EQ(1, data.bucket_info_size()); - EXPECT_EQ(1, data.bucket_info(0).count()); - EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs, - data.bucket_info(0).start_bucket_elapsed_nanos()); - EXPECT_EQ(bucketStartTimeNs + NS_PER_SEC * 60 * 11, - data.bucket_info(0).end_bucket_elapsed_nanos()); -} - -#else -GTEST_LOG_(INFO) << "This test does nothing.\n"; -#endif - -} // namespace statsd -} // namespace os -} // namespace android diff --git a/bin/tests/e2e/MetricConditionLink_e2e_test.cpp b/bin/tests/e2e/MetricConditionLink_e2e_test.cpp deleted file mode 100644 index 5e77ee0f..00000000 --- a/bin/tests/e2e/MetricConditionLink_e2e_test.cpp +++ /dev/null @@ -1,348 +0,0 @@ -// 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 CreateStatsdConfig() { - StatsdConfig config; - config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root. - - *config.add_atom_matcher() = CreateScreenTurnedOnAtomMatcher(); - *config.add_atom_matcher() = CreateScreenTurnedOffAtomMatcher(); - - *config.add_atom_matcher() = CreateSyncStartAtomMatcher(); - *config.add_atom_matcher() = CreateSyncEndAtomMatcher(); - - *config.add_atom_matcher() = CreateMoveToBackgroundAtomMatcher(); - *config.add_atom_matcher() = CreateMoveToForegroundAtomMatcher(); - - auto appCrashMatcher = CreateProcessCrashAtomMatcher(); - *config.add_atom_matcher() = appCrashMatcher; - - auto screenIsOffPredicate = CreateScreenIsOffPredicate(); - - auto isSyncingPredicate = CreateIsSyncingPredicate(); - auto syncDimension = isSyncingPredicate.mutable_simple_predicate()->mutable_dimensions(); - *syncDimension = CreateAttributionUidDimensions( - util::SYNC_STATE_CHANGED, {Position::FIRST}); - syncDimension->add_child()->set_field(2 /* name field*/); - - auto isInBackgroundPredicate = CreateIsInBackgroundPredicate(); - *isInBackgroundPredicate.mutable_simple_predicate()->mutable_dimensions() = - CreateDimensions(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(StringToId("combinationPredicate")); - combinationPredicate->mutable_combination()->set_operation(LogicalOperation::AND); - addPredicateToPredicateCombination(screenIsOffPredicate, combinationPredicate); - addPredicateToPredicateCombination(isSyncingPredicate, combinationPredicate); - addPredicateToPredicateCombination(isInBackgroundPredicate, combinationPredicate); - - auto countMetric = config.add_count_metric(); - countMetric->set_id(StringToId("AppCrashes")); - countMetric->set_what(appCrashMatcher.id()); - countMetric->set_condition(combinationPredicate->id()); - // The metric is dimensioning by uid only. - *countMetric->mutable_dimensions_in_what() = - CreateDimensions(util::PROCESS_LIFE_CYCLE_STATE_CHANGED, {1}); - countMetric->set_bucket(FIVE_MINUTES); - - // Links between crash atom and condition of app is in syncing. - auto links = countMetric->add_links(); - links->set_condition(isSyncingPredicate.id()); - auto dimensionWhat = links->mutable_fields_in_what(); - dimensionWhat->set_field(util::PROCESS_LIFE_CYCLE_STATE_CHANGED); - dimensionWhat->add_child()->set_field(1); // uid field. - *links->mutable_fields_in_condition() = CreateAttributionUidDimensions( - util::SYNC_STATE_CHANGED, {Position::FIRST}); - - // Links between crash atom and condition of app is in background. - links = countMetric->add_links(); - links->set_condition(isInBackgroundPredicate.id()); - dimensionWhat = links->mutable_fields_in_what(); - dimensionWhat->set_field(util::PROCESS_LIFE_CYCLE_STATE_CHANGED); - dimensionWhat->add_child()->set_field(1); // uid field. - auto dimensionCondition = links->mutable_fields_in_condition(); - dimensionCondition->set_field(util::ACTIVITY_FOREGROUND_STATE_CHANGED); - dimensionCondition->add_child()->set_field(1); // uid field. - return config; -} -} // namespace - -// If we want to test multiple dump data, we must do it in separate tests, because in the e2e tests, -// we should use the real API which will clear the data after dump data is called. -TEST(MetricConditionLinkE2eTest, TestMultiplePredicatesAndLinks1) { - auto config = CreateStatsdConfig(); - uint64_t bucketStartTimeNs = 10000000000; - uint64_t bucketSizeNs = - TimeUnitToBucketSizeInMillis(config.count_metric(0).bucket()) * 1000000LL; - - ConfigKey cfgKey; - auto processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey); - ASSERT_EQ(processor->mMetricsManagers.size(), 1u); - EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid()); - - int appUid = 123; - auto crashEvent1 = CreateAppCrashEvent(bucketStartTimeNs + 1, appUid); - auto crashEvent2 = CreateAppCrashEvent(bucketStartTimeNs + 201, appUid); - auto crashEvent3 = CreateAppCrashEvent(bucketStartTimeNs + 2 * bucketSizeNs - 101, appUid); - - auto crashEvent4 = CreateAppCrashEvent(bucketStartTimeNs + 51, appUid); - auto crashEvent5 = CreateAppCrashEvent(bucketStartTimeNs + bucketSizeNs + 299, appUid); - auto crashEvent6 = CreateAppCrashEvent(bucketStartTimeNs + bucketSizeNs + 2001, appUid); - - auto crashEvent7 = CreateAppCrashEvent(bucketStartTimeNs + 16, appUid); - auto crashEvent8 = CreateAppCrashEvent(bucketStartTimeNs + bucketSizeNs + 249, appUid); - - auto crashEvent9 = CreateAppCrashEvent(bucketStartTimeNs + bucketSizeNs + 351, appUid); - auto crashEvent10 = CreateAppCrashEvent(bucketStartTimeNs + 2 * bucketSizeNs - 2, appUid); - - auto screenTurnedOnEvent = CreateScreenStateChangedEvent( - bucketStartTimeNs + 2, android::view::DisplayStateEnum::DISPLAY_STATE_ON); - auto screenTurnedOffEvent = CreateScreenStateChangedEvent( - bucketStartTimeNs + 200, android::view::DisplayStateEnum::DISPLAY_STATE_OFF); - auto screenTurnedOnEvent2 = - CreateScreenStateChangedEvent(bucketStartTimeNs + 2 * bucketSizeNs - 100, - android::view::DisplayStateEnum::DISPLAY_STATE_ON); - - std::vector attributionUids = {appUid, appUid + 1}; - std::vector attributionTags = {"App1", "GMSCoreModule1"}; - - auto syncOnEvent1 = CreateSyncStartEvent(bucketStartTimeNs + 50, attributionUids, - attributionTags, "ReadEmail"); - auto syncOffEvent1 = CreateSyncEndEvent(bucketStartTimeNs + bucketSizeNs + 300, attributionUids, - attributionTags, "ReadEmail"); - auto syncOnEvent2 = CreateSyncStartEvent(bucketStartTimeNs + bucketSizeNs + 2000, - attributionUids, attributionTags, "ReadDoc"); - - auto moveToBackgroundEvent1 = CreateMoveToBackgroundEvent(bucketStartTimeNs + 15, appUid); - auto moveToForegroundEvent1 = - CreateMoveToForegroundEvent(bucketStartTimeNs + bucketSizeNs + 250, appUid); - - auto moveToBackgroundEvent2 = - CreateMoveToBackgroundEvent(bucketStartTimeNs + bucketSizeNs + 350, appUid); - auto moveToForegroundEvent2 = - CreateMoveToForegroundEvent(bucketStartTimeNs + 2 * bucketSizeNs - 1, appUid); - - /* - bucket #1 bucket #2 - - - | | | | | | | | | | (crashEvents) - |-------------------------------------|-----------------------------------|--------- - - | | (MoveToBkground) - - | | (MoveToForeground) - - | | (SyncIsOn) - | (SyncIsOff) - | | (ScreenIsOn) - | (ScreenIsOff) - */ - std::vector> events; - events.push_back(std::move(crashEvent1)); - events.push_back(std::move(crashEvent2)); - events.push_back(std::move(crashEvent3)); - events.push_back(std::move(crashEvent4)); - events.push_back(std::move(crashEvent5)); - events.push_back(std::move(crashEvent6)); - events.push_back(std::move(crashEvent7)); - events.push_back(std::move(crashEvent8)); - events.push_back(std::move(crashEvent9)); - events.push_back(std::move(crashEvent10)); - events.push_back(std::move(screenTurnedOnEvent)); - events.push_back(std::move(screenTurnedOffEvent)); - events.push_back(std::move(screenTurnedOnEvent2)); - events.push_back(std::move(syncOnEvent1)); - events.push_back(std::move(syncOffEvent1)); - events.push_back(std::move(syncOnEvent2)); - events.push_back(std::move(moveToBackgroundEvent1)); - events.push_back(std::move(moveToForegroundEvent1)); - events.push_back(std::move(moveToBackgroundEvent2)); - events.push_back(std::move(moveToForegroundEvent2)); - - sortLogEventsByTimestamp(&events); - - for (const auto& event : events) { - processor->OnLogEvent(event.get()); - } - ConfigMetricsReportList reports; - vector buffer; - processor->onDumpReport(cfgKey, bucketStartTimeNs + 2 * bucketSizeNs - 1, false, true, ADB_DUMP, - FAST, &buffer); - EXPECT_TRUE(buffer.size() > 0); - EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size())); - backfillDimensionPath(&reports); - backfillStringInReport(&reports); - backfillStartEndTimestamp(&reports); - ASSERT_EQ(reports.reports_size(), 1); - ASSERT_EQ(reports.reports(0).metrics_size(), 1); - ASSERT_EQ(reports.reports(0).metrics(0).count_metrics().data_size(), 1); - ASSERT_EQ(reports.reports(0).metrics(0).count_metrics().data(0).bucket_info_size(), 1); - EXPECT_EQ(reports.reports(0).metrics(0).count_metrics().data(0).bucket_info(0).count(), 1); - auto data = reports.reports(0).metrics(0).count_metrics().data(0); - // Validate dimension value. - EXPECT_EQ(data.dimensions_in_what().field(), util::PROCESS_LIFE_CYCLE_STATE_CHANGED); - ASSERT_EQ(data.dimensions_in_what().value_tuple().dimensions_value_size(), 1); - // Uid field. - 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(), appUid); -} - -TEST(MetricConditionLinkE2eTest, TestMultiplePredicatesAndLinks2) { - auto config = CreateStatsdConfig(); - uint64_t bucketStartTimeNs = 10000000000; - uint64_t bucketSizeNs = - TimeUnitToBucketSizeInMillis(config.count_metric(0).bucket()) * 1000000LL; - - ConfigKey cfgKey; - auto processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey); - ASSERT_EQ(processor->mMetricsManagers.size(), 1u); - EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid()); - - int appUid = 123; - auto crashEvent1 = CreateAppCrashEvent(bucketStartTimeNs + 1, appUid); - auto crashEvent2 = CreateAppCrashEvent(bucketStartTimeNs + 201, appUid); - auto crashEvent3 = CreateAppCrashEvent(bucketStartTimeNs + 2 * bucketSizeNs - 101, appUid); - - auto crashEvent4 = CreateAppCrashEvent(bucketStartTimeNs + 51, appUid); - auto crashEvent5 = CreateAppCrashEvent(bucketStartTimeNs + bucketSizeNs + 299, appUid); - auto crashEvent6 = CreateAppCrashEvent(bucketStartTimeNs + bucketSizeNs + 2001, appUid); - - auto crashEvent7 = CreateAppCrashEvent(bucketStartTimeNs + 16, appUid); - auto crashEvent8 = CreateAppCrashEvent(bucketStartTimeNs + bucketSizeNs + 249, appUid); - - auto crashEvent9 = CreateAppCrashEvent(bucketStartTimeNs + bucketSizeNs + 351, appUid); - auto crashEvent10 = CreateAppCrashEvent(bucketStartTimeNs + 2 * bucketSizeNs - 2, appUid); - - auto screenTurnedOnEvent = CreateScreenStateChangedEvent( - bucketStartTimeNs + 2, android::view::DisplayStateEnum::DISPLAY_STATE_ON); - auto screenTurnedOffEvent = CreateScreenStateChangedEvent( - bucketStartTimeNs + 200, android::view::DisplayStateEnum::DISPLAY_STATE_OFF); - auto screenTurnedOnEvent2 = - CreateScreenStateChangedEvent(bucketStartTimeNs + 2 * bucketSizeNs - 100, - android::view::DisplayStateEnum::DISPLAY_STATE_ON); - - std::vector attributionUids = {appUid, appUid + 1}; - std::vector attributionTags = {"App1", "GMSCoreModule1"}; - - auto syncOnEvent1 = CreateSyncStartEvent(bucketStartTimeNs + 50, attributionUids, - attributionTags, "ReadEmail"); - auto syncOffEvent1 = CreateSyncEndEvent(bucketStartTimeNs + bucketSizeNs + 300, attributionUids, - attributionTags, "ReadEmail"); - auto syncOnEvent2 = CreateSyncStartEvent(bucketStartTimeNs + bucketSizeNs + 2000, - attributionUids, attributionTags, "ReadDoc"); - - auto moveToBackgroundEvent1 = CreateMoveToBackgroundEvent(bucketStartTimeNs + 15, appUid); - auto moveToForegroundEvent1 = - CreateMoveToForegroundEvent(bucketStartTimeNs + bucketSizeNs + 250, appUid); - - auto moveToBackgroundEvent2 = - CreateMoveToBackgroundEvent(bucketStartTimeNs + bucketSizeNs + 350, appUid); - auto moveToForegroundEvent2 = - CreateMoveToForegroundEvent(bucketStartTimeNs + 2 * bucketSizeNs - 1, appUid); - - /* - bucket #1 bucket #2 - - - | | | | | | | | | | (crashEvents) - |-------------------------------------|-----------------------------------|--------- - - | | (MoveToBkground) - - | | (MoveToForeground) - - | | (SyncIsOn) - | (SyncIsOff) - | | (ScreenIsOn) - | (ScreenIsOff) - */ - std::vector> events; - events.push_back(std::move(crashEvent1)); - events.push_back(std::move(crashEvent2)); - events.push_back(std::move(crashEvent3)); - events.push_back(std::move(crashEvent4)); - events.push_back(std::move(crashEvent5)); - events.push_back(std::move(crashEvent6)); - events.push_back(std::move(crashEvent7)); - events.push_back(std::move(crashEvent8)); - events.push_back(std::move(crashEvent9)); - events.push_back(std::move(crashEvent10)); - events.push_back(std::move(screenTurnedOnEvent)); - events.push_back(std::move(screenTurnedOffEvent)); - events.push_back(std::move(screenTurnedOnEvent2)); - events.push_back(std::move(syncOnEvent1)); - events.push_back(std::move(syncOffEvent1)); - events.push_back(std::move(syncOnEvent2)); - events.push_back(std::move(moveToBackgroundEvent1)); - events.push_back(std::move(moveToForegroundEvent1)); - events.push_back(std::move(moveToBackgroundEvent2)); - events.push_back(std::move(moveToForegroundEvent2)); - - sortLogEventsByTimestamp(&events); - - for (const auto& event : events) { - processor->OnLogEvent(event.get()); - } - ConfigMetricsReportList reports; - vector buffer; - - processor->onDumpReport(cfgKey, bucketStartTimeNs + 2 * bucketSizeNs + 1, false, true, ADB_DUMP, - FAST, &buffer); - EXPECT_TRUE(buffer.size() > 0); - EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size())); - backfillDimensionPath(&reports); - backfillStringInReport(&reports); - backfillStartEndTimestamp(&reports); - ASSERT_EQ(reports.reports_size(), 1); - ASSERT_EQ(reports.reports(0).metrics_size(), 1); - ASSERT_EQ(reports.reports(0).metrics(0).count_metrics().data_size(), 1); - ASSERT_EQ(reports.reports(0).metrics(0).count_metrics().data(0).bucket_info_size(), 2); - EXPECT_EQ(reports.reports(0).metrics(0).count_metrics().data(0).bucket_info(0).count(), 1); - EXPECT_EQ(reports.reports(0).metrics(0).count_metrics().data(0).bucket_info(1).count(), 3); - auto data = reports.reports(0).metrics(0).count_metrics().data(0); - // Validate dimension value. - EXPECT_EQ(data.dimensions_in_what().field(), util::PROCESS_LIFE_CYCLE_STATE_CHANGED); - ASSERT_EQ(data.dimensions_in_what().value_tuple().dimensions_value_size(), 1); - // Uid field. - 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(), appUid); -} - -#else -GTEST_LOG_(INFO) << "This test does nothing.\n"; -#endif - -} // namespace statsd -} // namespace os -} // namespace android diff --git a/bin/tests/e2e/PartialBucket_e2e_test.cpp b/bin/tests/e2e/PartialBucket_e2e_test.cpp deleted file mode 100644 index 783f31c7..00000000 --- a/bin/tests/e2e/PartialBucket_e2e_test.cpp +++ /dev/null @@ -1,433 +0,0 @@ -// 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 -#include - -#include - -#include "src/StatsLogProcessor.h" -#include "src/StatsService.h" -#include "src/stats_log_util.h" -#include "tests/statsd_test_util.h" - -using::ndk::SharedRefBase; -using std::shared_ptr; - -namespace android { -namespace os { -namespace statsd { - -#ifdef __ANDROID__ -namespace { -const string kApp1 = "app1.sharing.1"; -const int kConfigKey = 789130123; // Randomly chosen to avoid collisions with existing configs. -const int kCallingUid = 0; // Randomly chosen - -void SendConfig(shared_ptr& service, const StatsdConfig& config) { - string str; - config.SerializeToString(&str); - std::vector configAsVec(str.begin(), str.end()); - service->addConfiguration(kConfigKey, configAsVec, kCallingUid); -} - -ConfigMetricsReport GetReports(sp processor, int64_t timestamp, - bool include_current = false) { - vector output; - ConfigKey configKey(AIBinder_getCallingUid(), kConfigKey); - processor->onDumpReport(configKey, timestamp, include_current /* include_current_bucket*/, - true /* erase_data */, ADB_DUMP, NO_TIME_CONSTRAINTS, &output); - ConfigMetricsReportList reports; - reports.ParseFromArray(output.data(), output.size()); - EXPECT_EQ(1, reports.reports_size()); - return reports.reports(kCallingUid); -} - -StatsdConfig MakeConfig() { - StatsdConfig config; - config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root. - - auto appCrashMatcher = CreateProcessCrashAtomMatcher(); - *config.add_atom_matcher() = appCrashMatcher; - auto countMetric = config.add_count_metric(); - countMetric->set_id(StringToId("AppCrashes")); - countMetric->set_what(appCrashMatcher.id()); - countMetric->set_bucket(FIVE_MINUTES); - return config; -} - -StatsdConfig MakeValueMetricConfig(int64_t minTime) { - StatsdConfig config; - config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root. - config.add_default_pull_packages("AID_ROOT"); // Fake puller is registered with root. - - auto pulledAtomMatcher = - CreateSimpleAtomMatcher("TestMatcher", util::SUBSYSTEM_SLEEP_STATE); - *config.add_atom_matcher() = pulledAtomMatcher; - *config.add_atom_matcher() = CreateScreenTurnedOnAtomMatcher(); - *config.add_atom_matcher() = CreateScreenTurnedOffAtomMatcher(); - - auto valueMetric = config.add_value_metric(); - valueMetric->set_id(123456); - valueMetric->set_what(pulledAtomMatcher.id()); - *valueMetric->mutable_value_field() = - CreateDimensions(util::SUBSYSTEM_SLEEP_STATE, {4 /* time sleeping field */}); - *valueMetric->mutable_dimensions_in_what() = - CreateDimensions(util::SUBSYSTEM_SLEEP_STATE, {1 /* subsystem name */}); - valueMetric->set_bucket(FIVE_MINUTES); - valueMetric->set_min_bucket_size_nanos(minTime); - valueMetric->set_use_absolute_value_on_reset(true); - valueMetric->set_skip_zero_diff_output(false); - return config; -} - -StatsdConfig MakeGaugeMetricConfig(int64_t minTime) { - StatsdConfig config; - config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root. - config.add_default_pull_packages("AID_ROOT"); // Fake puller is registered with root. - - auto pulledAtomMatcher = - CreateSimpleAtomMatcher("TestMatcher", util::SUBSYSTEM_SLEEP_STATE); - *config.add_atom_matcher() = pulledAtomMatcher; - *config.add_atom_matcher() = CreateScreenTurnedOnAtomMatcher(); - *config.add_atom_matcher() = CreateScreenTurnedOffAtomMatcher(); - - auto gaugeMetric = config.add_gauge_metric(); - gaugeMetric->set_id(123456); - gaugeMetric->set_what(pulledAtomMatcher.id()); - gaugeMetric->mutable_gauge_fields_filter()->set_include_all(true); - *gaugeMetric->mutable_dimensions_in_what() = - CreateDimensions(util::SUBSYSTEM_SLEEP_STATE, {1 /* subsystem name */}); - gaugeMetric->set_bucket(FIVE_MINUTES); - gaugeMetric->set_min_bucket_size_nanos(minTime); - return config; -} -} // anonymous namespace - -TEST(PartialBucketE2eTest, TestCountMetricWithoutSplit) { - shared_ptr service = SharedRefBase::make(nullptr, nullptr); - SendConfig(service, MakeConfig()); - int64_t start = getElapsedRealtimeNs(); // This is the start-time the metrics producers are - // initialized with. - - service->mProcessor->OnLogEvent(CreateAppCrashEvent(start + 1, 100).get()); - service->mProcessor->OnLogEvent(CreateAppCrashEvent(start + 2, 100).get()); - - ConfigMetricsReport report = GetReports(service->mProcessor, start + 3); - // Expect no metrics since the bucket has not finished yet. - ASSERT_EQ(1, report.metrics_size()); - ASSERT_EQ(0, report.metrics(0).count_metrics().data_size()); -} - -TEST(PartialBucketE2eTest, TestCountMetricNoSplitOnNewApp) { - shared_ptr service = SharedRefBase::make(nullptr, nullptr); - SendConfig(service, MakeConfig()); - int64_t start = getElapsedRealtimeNs(); // This is the start-time the metrics producers are - // initialized with. - - // Force the uidmap to update at timestamp 2. - service->mProcessor->OnLogEvent(CreateAppCrashEvent(start + 1, 100).get()); - // This is a new installation, so there shouldn't be a split (should be same as the without - // split case). - service->mUidMap->updateApp(start + 2, String16(kApp1.c_str()), 1, 2, String16("v2"), - String16("")); - // Goes into the second bucket. - service->mProcessor->OnLogEvent(CreateAppCrashEvent(start + 3, 100).get()); - - ConfigMetricsReport report = GetReports(service->mProcessor, start + 4); - ASSERT_EQ(1, report.metrics_size()); - ASSERT_EQ(0, report.metrics(0).count_metrics().data_size()); -} - -TEST(PartialBucketE2eTest, TestCountMetricSplitOnUpgrade) { - shared_ptr service = SharedRefBase::make(nullptr, nullptr); - SendConfig(service, MakeConfig()); - int64_t start = getElapsedRealtimeNs(); // This is the start-time the metrics producers are - // initialized with. - service->mUidMap->updateMap(start, {1}, {1}, {String16("v1")}, {String16(kApp1.c_str())}, - {String16("")}); - - // Force the uidmap to update at timestamp 2. - service->mProcessor->OnLogEvent(CreateAppCrashEvent(start + 1, 100).get()); - service->mUidMap->updateApp(start + 2, String16(kApp1.c_str()), 1, 2, String16("v2"), - String16("")); - // Goes into the second bucket. - service->mProcessor->OnLogEvent(CreateAppCrashEvent(start + 3, 100).get()); - - ConfigMetricsReport report = GetReports(service->mProcessor, start + 4); - backfillStartEndTimestamp(&report); - - ASSERT_EQ(1, report.metrics_size()); - ASSERT_EQ(1, report.metrics(0).count_metrics().data_size()); - ASSERT_EQ(1, report.metrics(0).count_metrics().data(0).bucket_info_size()); - EXPECT_TRUE(report.metrics(0) - .count_metrics() - .data(0) - .bucket_info(0) - .has_start_bucket_elapsed_nanos()); - EXPECT_TRUE(report.metrics(0) - .count_metrics() - .data(0) - .bucket_info(0) - .has_end_bucket_elapsed_nanos()); - EXPECT_EQ(1, report.metrics(0).count_metrics().data(0).bucket_info(0).count()); -} - -TEST(PartialBucketE2eTest, TestCountMetricSplitOnRemoval) { - shared_ptr service = SharedRefBase::make(nullptr, nullptr); - SendConfig(service, MakeConfig()); - int64_t start = getElapsedRealtimeNs(); // This is the start-time the metrics producers are - // initialized with. - service->mUidMap->updateMap(start, {1}, {1}, {String16("v1")}, {String16(kApp1.c_str())}, - {String16("")}); - - // Force the uidmap to update at timestamp 2. - service->mProcessor->OnLogEvent(CreateAppCrashEvent(start + 1, 100).get()); - service->mUidMap->removeApp(start + 2, String16(kApp1.c_str()), 1); - // Goes into the second bucket. - service->mProcessor->OnLogEvent(CreateAppCrashEvent(start + 3, 100).get()); - - ConfigMetricsReport report = GetReports(service->mProcessor, start + 4); - backfillStartEndTimestamp(&report); - - ASSERT_EQ(1, report.metrics_size()); - ASSERT_EQ(1, report.metrics(0).count_metrics().data_size()); - ASSERT_EQ(1, report.metrics(0).count_metrics().data(0).bucket_info_size()); - EXPECT_TRUE(report.metrics(0) - .count_metrics() - .data(0) - .bucket_info(0) - .has_start_bucket_elapsed_nanos()); - EXPECT_TRUE(report.metrics(0) - .count_metrics() - .data(0) - .bucket_info(0) - .has_end_bucket_elapsed_nanos()); - EXPECT_EQ(1, report.metrics(0).count_metrics().data(0).bucket_info(0).count()); -} - -TEST(PartialBucketE2eTest, TestCountMetricSplitOnBoot) { - shared_ptr service = SharedRefBase::make(nullptr, nullptr); - SendConfig(service, MakeConfig()); - int64_t start = getElapsedRealtimeNs(); // This is the start-time the metrics producers are - // initialized with. - - // Goes into the first bucket - service->mProcessor->OnLogEvent(CreateAppCrashEvent(start + NS_PER_SEC, 100).get()); - int64_t bootCompleteTimeNs = start + 2 * NS_PER_SEC; - service->mProcessor->onStatsdInitCompleted(bootCompleteTimeNs); - // Goes into the second bucket. - service->mProcessor->OnLogEvent(CreateAppCrashEvent(start + 3 * NS_PER_SEC, 100).get()); - - ConfigMetricsReport report = GetReports(service->mProcessor, start + 4 * NS_PER_SEC); - backfillStartEndTimestamp(&report); - - ASSERT_EQ(1, report.metrics_size()); - ASSERT_EQ(1, report.metrics(0).count_metrics().data_size()); - ASSERT_EQ(1, report.metrics(0).count_metrics().data(0).bucket_info_size()); - EXPECT_TRUE(report.metrics(0) - .count_metrics() - .data(0) - .bucket_info(0) - .has_start_bucket_elapsed_nanos()); - EXPECT_EQ(MillisToNano(NanoToMillis(bootCompleteTimeNs)), - report.metrics(0).count_metrics().data(0).bucket_info(0).end_bucket_elapsed_nanos()); - EXPECT_EQ(1, report.metrics(0).count_metrics().data(0).bucket_info(0).count()); -} - -TEST(PartialBucketE2eTest, TestValueMetricWithoutMinPartialBucket) { - shared_ptr service = SharedRefBase::make(nullptr, nullptr); - service->mPullerManager->RegisterPullAtomCallback( - /*uid=*/0, util::SUBSYSTEM_SLEEP_STATE, NS_PER_SEC, NS_PER_SEC * 10, {}, - SharedRefBase::make()); - // Partial buckets don't occur when app is first installed. - service->mUidMap->updateApp(1, String16(kApp1.c_str()), 1, 1, String16("v1"), String16("")); - SendConfig(service, MakeValueMetricConfig(0)); - int64_t start = getElapsedRealtimeNs(); // This is the start-time the metrics producers are - // initialized with. - - service->mProcessor->informPullAlarmFired(5 * 60 * NS_PER_SEC + start); - int64_t appUpgradeTimeNs = 5 * 60 * NS_PER_SEC + start + 2 * NS_PER_SEC; - service->mUidMap->updateApp(appUpgradeTimeNs, String16(kApp1.c_str()), 1, 2, String16("v2"), - String16("")); - - ConfigMetricsReport report = - GetReports(service->mProcessor, 5 * 60 * NS_PER_SEC + start + 100 * NS_PER_SEC); - backfillStartEndTimestamp(&report); - - ASSERT_EQ(1, report.metrics_size()); - ASSERT_EQ(0, report.metrics(0).value_metrics().skipped_size()); - - // The fake subsystem state sleep puller returns two atoms. - ASSERT_EQ(2, report.metrics(0).value_metrics().data_size()); - ASSERT_EQ(2, report.metrics(0).value_metrics().data(0).bucket_info_size()); - EXPECT_EQ(MillisToNano(NanoToMillis(appUpgradeTimeNs)), - report.metrics(0).value_metrics().data(0).bucket_info(1).end_bucket_elapsed_nanos()); -} - -TEST(PartialBucketE2eTest, TestValueMetricWithMinPartialBucket) { - shared_ptr service = SharedRefBase::make(nullptr, nullptr); - service->mPullerManager->RegisterPullAtomCallback( - /*uid=*/0, util::SUBSYSTEM_SLEEP_STATE, NS_PER_SEC, NS_PER_SEC * 10, {}, - SharedRefBase::make()); - // Partial buckets don't occur when app is first installed. - service->mUidMap->updateApp(1, String16(kApp1.c_str()), 1, 1, String16("v1"), String16("")); - SendConfig(service, MakeValueMetricConfig(60 * NS_PER_SEC /* One minute */)); - int64_t start = getElapsedRealtimeNs(); // This is the start-time the metrics producers are - // initialized with. - - const int64_t endSkipped = 5 * 60 * NS_PER_SEC + start + 2 * NS_PER_SEC; - service->mProcessor->informPullAlarmFired(5 * 60 * NS_PER_SEC + start); - service->mUidMap->updateApp(endSkipped, String16(kApp1.c_str()), 1, 2, String16("v2"), - String16("")); - - ConfigMetricsReport report = - GetReports(service->mProcessor, 5 * 60 * NS_PER_SEC + start + 100 * NS_PER_SEC); - backfillStartEndTimestamp(&report); - - ASSERT_EQ(1, report.metrics_size()); - ASSERT_EQ(1, report.metrics(0).value_metrics().skipped_size()); - EXPECT_TRUE(report.metrics(0).value_metrics().skipped(0).has_start_bucket_elapsed_nanos()); - // Can't test the start time since it will be based on the actual time when the pulling occurs. - EXPECT_EQ(MillisToNano(NanoToMillis(endSkipped)), - report.metrics(0).value_metrics().skipped(0).end_bucket_elapsed_nanos()); - - ASSERT_EQ(2, report.metrics(0).value_metrics().data_size()); - ASSERT_EQ(1, report.metrics(0).value_metrics().data(0).bucket_info_size()); -} - -TEST(PartialBucketE2eTest, TestValueMetricOnBootWithoutMinPartialBucket) { - shared_ptr service = SharedRefBase::make(nullptr, nullptr); - // Initial pull will fail since puller is not registered. - SendConfig(service, MakeValueMetricConfig(0)); - int64_t start = getElapsedRealtimeNs(); // This is the start-time the metrics producers are - // initialized with. - - service->mPullerManager->RegisterPullAtomCallback( - /*uid=*/0, util::SUBSYSTEM_SLEEP_STATE, NS_PER_SEC, NS_PER_SEC * 10, {}, - SharedRefBase::make()); - - int64_t bootCompleteTimeNs = start + NS_PER_SEC; - service->mProcessor->onStatsdInitCompleted(bootCompleteTimeNs); - - service->mProcessor->informPullAlarmFired(5 * 60 * NS_PER_SEC + start); - - ConfigMetricsReport report = GetReports(service->mProcessor, 5 * 60 * NS_PER_SEC + start + 100); - backfillStartEndTimestamp(&report); - - // First bucket is dropped due to the initial pull failing - ASSERT_EQ(1, report.metrics_size()); - ASSERT_EQ(1, report.metrics(0).value_metrics().skipped_size()); - EXPECT_EQ(MillisToNano(NanoToMillis(bootCompleteTimeNs)), - report.metrics(0).value_metrics().skipped(0).end_bucket_elapsed_nanos()); - - // The fake subsystem state sleep puller returns two atoms. - ASSERT_EQ(2, report.metrics(0).value_metrics().data_size()); - ASSERT_EQ(1, report.metrics(0).value_metrics().data(0).bucket_info_size()); - EXPECT_EQ( - MillisToNano(NanoToMillis(bootCompleteTimeNs)), - report.metrics(0).value_metrics().data(0).bucket_info(0).start_bucket_elapsed_nanos()); -} - -TEST(PartialBucketE2eTest, TestGaugeMetricWithoutMinPartialBucket) { - shared_ptr service = SharedRefBase::make(nullptr, nullptr); - service->mPullerManager->RegisterPullAtomCallback( - /*uid=*/0, util::SUBSYSTEM_SLEEP_STATE, NS_PER_SEC, NS_PER_SEC * 10, {}, - SharedRefBase::make()); - // Partial buckets don't occur when app is first installed. - service->mUidMap->updateApp(1, String16(kApp1.c_str()), 1, 1, String16("v1"), String16("")); - SendConfig(service, MakeGaugeMetricConfig(0)); - int64_t start = getElapsedRealtimeNs(); // This is the start-time the metrics producers are - // initialized with. - - service->mProcessor->informPullAlarmFired(5 * 60 * NS_PER_SEC + start); - service->mUidMap->updateApp(5 * 60 * NS_PER_SEC + start + 2, String16(kApp1.c_str()), 1, 2, - String16("v2"), String16("")); - - ConfigMetricsReport report = GetReports(service->mProcessor, 5 * 60 * NS_PER_SEC + start + 100); - backfillStartEndTimestamp(&report); - ASSERT_EQ(1, report.metrics_size()); - ASSERT_EQ(0, report.metrics(0).gauge_metrics().skipped_size()); - // The fake subsystem state sleep puller returns two atoms. - ASSERT_EQ(2, report.metrics(0).gauge_metrics().data_size()); - ASSERT_EQ(2, report.metrics(0).gauge_metrics().data(0).bucket_info_size()); -} - -TEST(PartialBucketE2eTest, TestGaugeMetricWithMinPartialBucket) { - shared_ptr service = SharedRefBase::make(nullptr, nullptr); - // Partial buckets don't occur when app is first installed. - service->mUidMap->updateApp(1, String16(kApp1.c_str()), 1, 1, String16("v1"), String16("")); - service->mPullerManager->RegisterPullAtomCallback( - /*uid=*/0, util::SUBSYSTEM_SLEEP_STATE, NS_PER_SEC, NS_PER_SEC * 10, {}, - SharedRefBase::make()); - SendConfig(service, MakeGaugeMetricConfig(60 * NS_PER_SEC /* One minute */)); - int64_t start = getElapsedRealtimeNs(); // This is the start-time the metrics producers are - // initialized with. - - const int64_t endSkipped = 5 * 60 * NS_PER_SEC + start + 2; - service->mProcessor->informPullAlarmFired(5 * 60 * NS_PER_SEC + start); - service->mUidMap->updateApp(endSkipped, String16(kApp1.c_str()), 1, 2, String16("v2"), - String16("")); - - ConfigMetricsReport report = - GetReports(service->mProcessor, 5 * 60 * NS_PER_SEC + start + 100 * NS_PER_SEC); - backfillStartEndTimestamp(&report); - ASSERT_EQ(1, report.metrics_size()); - ASSERT_EQ(1, report.metrics(0).gauge_metrics().skipped_size()); - // Can't test the start time since it will be based on the actual time when the pulling occurs. - EXPECT_TRUE(report.metrics(0).gauge_metrics().skipped(0).has_start_bucket_elapsed_nanos()); - EXPECT_EQ(MillisToNano(NanoToMillis(endSkipped)), - report.metrics(0).gauge_metrics().skipped(0).end_bucket_elapsed_nanos()); - ASSERT_EQ(2, report.metrics(0).gauge_metrics().data_size()); - ASSERT_EQ(1, report.metrics(0).gauge_metrics().data(0).bucket_info_size()); -} - -TEST(PartialBucketE2eTest, TestGaugeMetricOnBootWithoutMinPartialBucket) { - shared_ptr service = SharedRefBase::make(nullptr, nullptr); - // Initial pull will fail since puller hasn't been registered. - SendConfig(service, MakeGaugeMetricConfig(0)); - int64_t start = getElapsedRealtimeNs(); // This is the start-time the metrics producers are - // initialized with. - - service->mPullerManager->RegisterPullAtomCallback( - /*uid=*/0, util::SUBSYSTEM_SLEEP_STATE, NS_PER_SEC, NS_PER_SEC * 10, {}, - SharedRefBase::make()); - - int64_t bootCompleteTimeNs = start + NS_PER_SEC; - service->mProcessor->onStatsdInitCompleted(bootCompleteTimeNs); - - service->mProcessor->informPullAlarmFired(5 * 60 * NS_PER_SEC + start); - - ConfigMetricsReport report = GetReports(service->mProcessor, 5 * 60 * NS_PER_SEC + start + 100); - backfillStartEndTimestamp(&report); - - ASSERT_EQ(1, report.metrics_size()); - ASSERT_EQ(0, report.metrics(0).gauge_metrics().skipped_size()); - // The fake subsystem state sleep puller returns two atoms. - ASSERT_EQ(2, report.metrics(0).gauge_metrics().data_size()); - // No data in the first bucket, so nothing is reported - ASSERT_EQ(1, report.metrics(0).gauge_metrics().data(0).bucket_info_size()); - EXPECT_EQ( - MillisToNano(NanoToMillis(bootCompleteTimeNs)), - report.metrics(0).gauge_metrics().data(0).bucket_info(0).start_bucket_elapsed_nanos()); -} - -#else -GTEST_LOG_(INFO) << "This test does nothing.\n"; -#endif - -} // namespace statsd -} // namespace os -} // namespace android diff --git a/bin/tests/e2e/ValueMetric_pull_e2e_test.cpp b/bin/tests/e2e/ValueMetric_pull_e2e_test.cpp deleted file mode 100644 index 4d392827..00000000 --- a/bin/tests/e2e/ValueMetric_pull_e2e_test.cpp +++ /dev/null @@ -1,679 +0,0 @@ -// 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 - -#include "src/StatsLogProcessor.h" -#include "src/stats_log_util.h" -#include "tests/statsd_test_util.h" - -#include - -using ::ndk::SharedRefBase; - -namespace android { -namespace os { -namespace statsd { - -#ifdef __ANDROID__ - -namespace { - -const int64_t metricId = 123456; - -StatsdConfig CreateStatsdConfig(bool useCondition = true) { - StatsdConfig config; - config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root. - config.add_default_pull_packages("AID_ROOT"); // Fake puller is registered with root. - auto pulledAtomMatcher = - CreateSimpleAtomMatcher("TestMatcher", util::SUBSYSTEM_SLEEP_STATE); - *config.add_atom_matcher() = pulledAtomMatcher; - *config.add_atom_matcher() = CreateScreenTurnedOnAtomMatcher(); - *config.add_atom_matcher() = CreateScreenTurnedOffAtomMatcher(); - - auto screenIsOffPredicate = CreateScreenIsOffPredicate(); - *config.add_predicate() = screenIsOffPredicate; - - auto valueMetric = config.add_value_metric(); - valueMetric->set_id(metricId); - valueMetric->set_what(pulledAtomMatcher.id()); - if (useCondition) { - valueMetric->set_condition(screenIsOffPredicate.id()); - } - *valueMetric->mutable_value_field() = - CreateDimensions(util::SUBSYSTEM_SLEEP_STATE, {4 /* time sleeping field */}); - *valueMetric->mutable_dimensions_in_what() = - CreateDimensions(util::SUBSYSTEM_SLEEP_STATE, {1 /* subsystem name */}); - valueMetric->set_bucket(FIVE_MINUTES); - valueMetric->set_use_absolute_value_on_reset(true); - valueMetric->set_skip_zero_diff_output(false); - valueMetric->set_max_pull_delay_sec(INT_MAX); - return config; -} - -StatsdConfig CreateStatsdConfigWithStates() { - StatsdConfig config; - config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root. - config.add_default_pull_packages("AID_ROOT"); // Fake puller is registered with root. - - auto pulledAtomMatcher = CreateSimpleAtomMatcher("TestMatcher", util::SUBSYSTEM_SLEEP_STATE); - *config.add_atom_matcher() = pulledAtomMatcher; - *config.add_atom_matcher() = CreateScreenTurnedOnAtomMatcher(); - *config.add_atom_matcher() = CreateScreenTurnedOffAtomMatcher(); - *config.add_atom_matcher() = CreateBatteryStateNoneMatcher(); - *config.add_atom_matcher() = CreateBatteryStateUsbMatcher(); - - auto screenOnPredicate = CreateScreenIsOnPredicate(); - *config.add_predicate() = screenOnPredicate; - - auto screenOffPredicate = CreateScreenIsOffPredicate(); - *config.add_predicate() = screenOffPredicate; - - auto deviceUnpluggedPredicate = CreateDeviceUnpluggedPredicate(); - *config.add_predicate() = deviceUnpluggedPredicate; - - auto screenOnOnBatteryPredicate = config.add_predicate(); - screenOnOnBatteryPredicate->set_id(StringToId("screenOnOnBatteryPredicate")); - screenOnOnBatteryPredicate->mutable_combination()->set_operation(LogicalOperation::AND); - addPredicateToPredicateCombination(screenOnPredicate, screenOnOnBatteryPredicate); - addPredicateToPredicateCombination(deviceUnpluggedPredicate, screenOnOnBatteryPredicate); - - auto screenOffOnBatteryPredicate = config.add_predicate(); - screenOffOnBatteryPredicate->set_id(StringToId("ScreenOffOnBattery")); - screenOffOnBatteryPredicate->mutable_combination()->set_operation(LogicalOperation::AND); - addPredicateToPredicateCombination(screenOffPredicate, screenOffOnBatteryPredicate); - addPredicateToPredicateCombination(deviceUnpluggedPredicate, screenOffOnBatteryPredicate); - - const State screenState = - CreateScreenStateWithSimpleOnOffMap(/*screen on id=*/321, /*screen off id=*/123); - *config.add_state() = screenState; - - // ValueMetricSubsystemSleepWhileScreenOnOnBattery - auto valueMetric1 = config.add_value_metric(); - valueMetric1->set_id(metricId); - valueMetric1->set_what(pulledAtomMatcher.id()); - valueMetric1->set_condition(screenOnOnBatteryPredicate->id()); - *valueMetric1->mutable_value_field() = - CreateDimensions(util::SUBSYSTEM_SLEEP_STATE, {4 /* time sleeping field */}); - valueMetric1->set_bucket(FIVE_MINUTES); - valueMetric1->set_use_absolute_value_on_reset(true); - valueMetric1->set_skip_zero_diff_output(false); - valueMetric1->set_max_pull_delay_sec(INT_MAX); - - // ValueMetricSubsystemSleepWhileScreenOffOnBattery - ValueMetric* valueMetric2 = config.add_value_metric(); - valueMetric2->set_id(StringToId("ValueMetricSubsystemSleepWhileScreenOffOnBattery")); - valueMetric2->set_what(pulledAtomMatcher.id()); - valueMetric2->set_condition(screenOffOnBatteryPredicate->id()); - *valueMetric2->mutable_value_field() = - CreateDimensions(util::SUBSYSTEM_SLEEP_STATE, {4 /* time sleeping field */}); - valueMetric2->set_bucket(FIVE_MINUTES); - valueMetric2->set_use_absolute_value_on_reset(true); - valueMetric2->set_skip_zero_diff_output(false); - valueMetric2->set_max_pull_delay_sec(INT_MAX); - - // ValueMetricSubsystemSleepWhileOnBatterySliceScreen - ValueMetric* valueMetric3 = config.add_value_metric(); - valueMetric3->set_id(StringToId("ValueMetricSubsystemSleepWhileOnBatterySliceScreen")); - valueMetric3->set_what(pulledAtomMatcher.id()); - valueMetric3->set_condition(deviceUnpluggedPredicate.id()); - *valueMetric3->mutable_value_field() = - CreateDimensions(util::SUBSYSTEM_SLEEP_STATE, {4 /* time sleeping field */}); - valueMetric3->add_slice_by_state(screenState.id()); - valueMetric3->set_bucket(FIVE_MINUTES); - valueMetric3->set_use_absolute_value_on_reset(true); - valueMetric3->set_skip_zero_diff_output(false); - valueMetric3->set_max_pull_delay_sec(INT_MAX); - return config; -} - -} // namespace - -/** - * Tests the initial condition and condition after the first log events for - * value metrics with either a combination condition or simple condition. - * - * Metrics should be initialized with condition kUnknown (given that the - * predicate is using the default InitialValue of UNKNOWN). The condition should - * be updated to either kFalse or kTrue if a condition event is logged for all - * children conditions. - */ -TEST(ValueMetricE2eTest, TestInitialConditionChanges) { - StatsdConfig config = CreateStatsdConfigWithStates(); - int64_t baseTimeNs = getElapsedRealtimeNs(); - int64_t configAddedTimeNs = 10 * 60 * NS_PER_SEC + baseTimeNs; - int64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(config.value_metric(0).bucket()) * 1000000; - - ConfigKey cfgKey; - int32_t tagId = util::SUBSYSTEM_SLEEP_STATE; - auto processor = - CreateStatsLogProcessor(baseTimeNs, configAddedTimeNs, config, cfgKey, - SharedRefBase::make(), tagId); - - EXPECT_EQ(processor->mMetricsManagers.size(), 1u); - sp metricsManager = processor->mMetricsManagers.begin()->second; - EXPECT_TRUE(metricsManager->isConfigValid()); - EXPECT_EQ(3, metricsManager->mAllMetricProducers.size()); - - // Combination condition metric - screen on and device unplugged - sp metricProducer1 = metricsManager->mAllMetricProducers[0]; - // Simple condition metric - device unplugged - sp metricProducer2 = metricsManager->mAllMetricProducers[2]; - - EXPECT_EQ(ConditionState::kUnknown, metricProducer1->mCondition); - EXPECT_EQ(ConditionState::kUnknown, metricProducer2->mCondition); - - auto screenOnEvent = - CreateScreenStateChangedEvent(configAddedTimeNs + 30, android::view::DISPLAY_STATE_ON); - processor->OnLogEvent(screenOnEvent.get()); - EXPECT_EQ(ConditionState::kUnknown, metricProducer1->mCondition); - EXPECT_EQ(ConditionState::kUnknown, metricProducer2->mCondition); - - auto screenOffEvent = - CreateScreenStateChangedEvent(configAddedTimeNs + 40, android::view::DISPLAY_STATE_OFF); - processor->OnLogEvent(screenOffEvent.get()); - EXPECT_EQ(ConditionState::kUnknown, metricProducer1->mCondition); - EXPECT_EQ(ConditionState::kUnknown, metricProducer2->mCondition); - - auto pluggedUsbEvent = CreateBatteryStateChangedEvent( - configAddedTimeNs + 50, BatteryPluggedStateEnum::BATTERY_PLUGGED_USB); - processor->OnLogEvent(pluggedUsbEvent.get()); - EXPECT_EQ(ConditionState::kFalse, metricProducer1->mCondition); - EXPECT_EQ(ConditionState::kFalse, metricProducer2->mCondition); - - auto pluggedNoneEvent = CreateBatteryStateChangedEvent( - configAddedTimeNs + 70, BatteryPluggedStateEnum::BATTERY_PLUGGED_NONE); - processor->OnLogEvent(pluggedNoneEvent.get()); - EXPECT_EQ(ConditionState::kFalse, metricProducer1->mCondition); - EXPECT_EQ(ConditionState::kTrue, metricProducer2->mCondition); -} - -TEST(ValueMetricE2eTest, TestPulledEvents) { - auto config = CreateStatsdConfig(); - int64_t baseTimeNs = getElapsedRealtimeNs(); - int64_t configAddedTimeNs = 10 * 60 * NS_PER_SEC + baseTimeNs; - int64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(config.value_metric(0).bucket()) * 1000000; - - ConfigKey cfgKey; - auto processor = CreateStatsLogProcessor(baseTimeNs, configAddedTimeNs, config, cfgKey, - SharedRefBase::make(), - util::SUBSYSTEM_SLEEP_STATE); - ASSERT_EQ(processor->mMetricsManagers.size(), 1u); - EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid()); - processor->mPullerManager->ForceClearPullerCache(); - - int startBucketNum = processor->mMetricsManagers.begin() - ->second->mAllMetricProducers[0] - ->getCurrentBucketNum(); - EXPECT_GT(startBucketNum, (int64_t)0); - - // When creating the config, the value metric producer should register the alarm at the - // end of the current bucket. - ASSERT_EQ((size_t)1, processor->mPullerManager->mReceivers.size()); - EXPECT_EQ(bucketSizeNs, - processor->mPullerManager->mReceivers.begin()->second.front().intervalNs); - int64_t& expectedPullTimeNs = - processor->mPullerManager->mReceivers.begin()->second.front().nextPullTimeNs; - EXPECT_EQ(baseTimeNs + startBucketNum * bucketSizeNs + bucketSizeNs, expectedPullTimeNs); - - auto screenOffEvent = - CreateScreenStateChangedEvent(configAddedTimeNs + 55, android::view::DISPLAY_STATE_OFF); - processor->OnLogEvent(screenOffEvent.get()); - - auto screenOnEvent = - CreateScreenStateChangedEvent(configAddedTimeNs + 65, android::view::DISPLAY_STATE_ON); - processor->OnLogEvent(screenOnEvent.get()); - - screenOffEvent = - CreateScreenStateChangedEvent(configAddedTimeNs + 75, android::view::DISPLAY_STATE_OFF); - processor->OnLogEvent(screenOffEvent.get()); - - // Pulling alarm arrives on time and reset the sequential pulling alarm. - processor->informPullAlarmFired(expectedPullTimeNs + 1); - EXPECT_EQ(baseTimeNs + startBucketNum * bucketSizeNs + 2 * bucketSizeNs, expectedPullTimeNs); - - processor->informPullAlarmFired(expectedPullTimeNs + 1); - - screenOnEvent = CreateScreenStateChangedEvent(configAddedTimeNs + 2 * bucketSizeNs + 15, - android::view::DISPLAY_STATE_ON); - processor->OnLogEvent(screenOnEvent.get()); - - processor->informPullAlarmFired(expectedPullTimeNs + 1); - - processor->informPullAlarmFired(expectedPullTimeNs + 1); - - screenOffEvent = CreateScreenStateChangedEvent(configAddedTimeNs + 4 * bucketSizeNs + 11, - android::view::DISPLAY_STATE_OFF); - processor->OnLogEvent(screenOffEvent.get()); - - processor->informPullAlarmFired(expectedPullTimeNs + 1); - - processor->informPullAlarmFired(expectedPullTimeNs + 1); - - ConfigMetricsReportList reports; - vector buffer; - processor->onDumpReport(cfgKey, configAddedTimeNs + 7 * bucketSizeNs + 10, false, true, - ADB_DUMP, FAST, &buffer); - EXPECT_TRUE(buffer.size() > 0); - EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size())); - backfillDimensionPath(&reports); - backfillStringInReport(&reports); - backfillStartEndTimestamp(&reports); - ASSERT_EQ(1, reports.reports_size()); - ASSERT_EQ(1, reports.reports(0).metrics_size()); - StatsLogReport::ValueMetricDataWrapper valueMetrics; - sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).value_metrics(), &valueMetrics); - ASSERT_GT((int)valueMetrics.data_size(), 1); - - auto data = valueMetrics.data(0); - EXPECT_EQ(util::SUBSYSTEM_SLEEP_STATE, data.dimensions_in_what().field()); - ASSERT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size()); - EXPECT_EQ(1 /* subsystem name field */, - data.dimensions_in_what().value_tuple().dimensions_value(0).field()); - EXPECT_FALSE(data.dimensions_in_what().value_tuple().dimensions_value(0).value_str().empty()); - // We have 4 buckets, the first one was incomplete since the condition was unknown. - ASSERT_EQ(4, data.bucket_info_size()); - - EXPECT_EQ(baseTimeNs + 3 * bucketSizeNs, data.bucket_info(0).start_bucket_elapsed_nanos()); - EXPECT_EQ(baseTimeNs + 4 * bucketSizeNs, data.bucket_info(0).end_bucket_elapsed_nanos()); - ASSERT_EQ(1, data.bucket_info(0).values_size()); - - EXPECT_EQ(baseTimeNs + 4 * bucketSizeNs, data.bucket_info(1).start_bucket_elapsed_nanos()); - EXPECT_EQ(baseTimeNs + 5 * bucketSizeNs, data.bucket_info(1).end_bucket_elapsed_nanos()); - ASSERT_EQ(1, data.bucket_info(1).values_size()); - - EXPECT_EQ(baseTimeNs + 6 * bucketSizeNs, data.bucket_info(2).start_bucket_elapsed_nanos()); - EXPECT_EQ(baseTimeNs + 7 * bucketSizeNs, data.bucket_info(2).end_bucket_elapsed_nanos()); - ASSERT_EQ(1, data.bucket_info(2).values_size()); - - EXPECT_EQ(baseTimeNs + 7 * bucketSizeNs, data.bucket_info(3).start_bucket_elapsed_nanos()); - EXPECT_EQ(baseTimeNs + 8 * bucketSizeNs, data.bucket_info(3).end_bucket_elapsed_nanos()); - ASSERT_EQ(1, data.bucket_info(3).values_size()); -} - -TEST(ValueMetricE2eTest, TestPulledEvents_LateAlarm) { - auto config = CreateStatsdConfig(); - int64_t baseTimeNs = getElapsedRealtimeNs(); - // 10 mins == 2 bucket durations. - int64_t configAddedTimeNs = 10 * 60 * NS_PER_SEC + baseTimeNs; - int64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(config.value_metric(0).bucket()) * 1000000; - - ConfigKey cfgKey; - auto processor = CreateStatsLogProcessor(baseTimeNs, configAddedTimeNs, config, cfgKey, - SharedRefBase::make(), - util::SUBSYSTEM_SLEEP_STATE); - ASSERT_EQ(processor->mMetricsManagers.size(), 1u); - EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid()); - processor->mPullerManager->ForceClearPullerCache(); - - int startBucketNum = processor->mMetricsManagers.begin() - ->second->mAllMetricProducers[0] - ->getCurrentBucketNum(); - EXPECT_GT(startBucketNum, (int64_t)0); - - // When creating the config, the value metric producer should register the alarm at the - // end of the current bucket. - ASSERT_EQ((size_t)1, processor->mPullerManager->mReceivers.size()); - EXPECT_EQ(bucketSizeNs, - processor->mPullerManager->mReceivers.begin()->second.front().intervalNs); - int64_t& expectedPullTimeNs = - processor->mPullerManager->mReceivers.begin()->second.front().nextPullTimeNs; - EXPECT_EQ(baseTimeNs + startBucketNum * bucketSizeNs + bucketSizeNs, expectedPullTimeNs); - - // Screen off/on/off events. - auto screenOffEvent = - CreateScreenStateChangedEvent(configAddedTimeNs + 55, android::view::DISPLAY_STATE_OFF); - processor->OnLogEvent(screenOffEvent.get()); - - auto screenOnEvent = - CreateScreenStateChangedEvent(configAddedTimeNs + 65, android::view::DISPLAY_STATE_ON); - processor->OnLogEvent(screenOnEvent.get()); - - screenOffEvent = - CreateScreenStateChangedEvent(configAddedTimeNs + 75, android::view::DISPLAY_STATE_OFF); - processor->OnLogEvent(screenOffEvent.get()); - - // Pulling alarm arrives late by 2 buckets and 1 ns. 2 buckets late is too far away in the - // future, data will be skipped. - processor->informPullAlarmFired(expectedPullTimeNs + 2 * bucketSizeNs + 1); - EXPECT_EQ(baseTimeNs + startBucketNum * bucketSizeNs + 4 * bucketSizeNs, expectedPullTimeNs); - - // This screen state change will start a new bucket. - screenOnEvent = CreateScreenStateChangedEvent(configAddedTimeNs + 4 * bucketSizeNs + 65, - android::view::DISPLAY_STATE_ON); - processor->OnLogEvent(screenOnEvent.get()); - - // The alarm is delayed but we already created a bucket thanks to the screen state condition. - // This bucket does not have to be skipped since the alarm arrives in time for the next bucket. - processor->informPullAlarmFired(expectedPullTimeNs + bucketSizeNs + 21); - EXPECT_EQ(baseTimeNs + startBucketNum * bucketSizeNs + 6 * bucketSizeNs, expectedPullTimeNs); - - screenOffEvent = CreateScreenStateChangedEvent(configAddedTimeNs + 6 * bucketSizeNs + 31, - android::view::DISPLAY_STATE_OFF); - processor->OnLogEvent(screenOffEvent.get()); - - processor->informPullAlarmFired(expectedPullTimeNs + bucketSizeNs + 21); - EXPECT_EQ(baseTimeNs + startBucketNum * bucketSizeNs + 8 * bucketSizeNs, expectedPullTimeNs); - - processor->informPullAlarmFired(expectedPullTimeNs + 1); - EXPECT_EQ(baseTimeNs + startBucketNum * bucketSizeNs + 9 * bucketSizeNs, expectedPullTimeNs); - - ConfigMetricsReportList reports; - vector buffer; - processor->onDumpReport(cfgKey, configAddedTimeNs + 9 * bucketSizeNs + 10, false, true, - ADB_DUMP, FAST, &buffer); - EXPECT_TRUE(buffer.size() > 0); - EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size())); - backfillDimensionPath(&reports); - backfillStringInReport(&reports); - backfillStartEndTimestamp(&reports); - ASSERT_EQ(1, reports.reports_size()); - ASSERT_EQ(1, reports.reports(0).metrics_size()); - StatsLogReport::ValueMetricDataWrapper valueMetrics; - sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).value_metrics(), &valueMetrics); - ASSERT_GT((int)valueMetrics.data_size(), 1); - - auto data = valueMetrics.data(0); - EXPECT_EQ(util::SUBSYSTEM_SLEEP_STATE, data.dimensions_in_what().field()); - ASSERT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size()); - EXPECT_EQ(1 /* subsystem name field */, - data.dimensions_in_what().value_tuple().dimensions_value(0).field()); - EXPECT_FALSE(data.dimensions_in_what().value_tuple().dimensions_value(0).value_str().empty()); - ASSERT_EQ(3, data.bucket_info_size()); - - EXPECT_EQ(baseTimeNs + 5 * bucketSizeNs, data.bucket_info(0).start_bucket_elapsed_nanos()); - EXPECT_EQ(baseTimeNs + 6 * bucketSizeNs, data.bucket_info(0).end_bucket_elapsed_nanos()); - ASSERT_EQ(1, data.bucket_info(0).values_size()); - - EXPECT_EQ(baseTimeNs + 8 * bucketSizeNs, data.bucket_info(1).start_bucket_elapsed_nanos()); - EXPECT_EQ(baseTimeNs + 9 * bucketSizeNs, data.bucket_info(1).end_bucket_elapsed_nanos()); - ASSERT_EQ(1, data.bucket_info(1).values_size()); - - EXPECT_EQ(baseTimeNs + 9 * bucketSizeNs, data.bucket_info(2).start_bucket_elapsed_nanos()); - EXPECT_EQ(baseTimeNs + 10 * bucketSizeNs, data.bucket_info(2).end_bucket_elapsed_nanos()); - ASSERT_EQ(1, data.bucket_info(2).values_size()); -} - -TEST(ValueMetricE2eTest, TestPulledEvents_WithActivation) { - auto config = CreateStatsdConfig(false); - int64_t baseTimeNs = getElapsedRealtimeNs(); - int64_t configAddedTimeNs = 10 * 60 * NS_PER_SEC + baseTimeNs; - int64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(config.value_metric(0).bucket()) * 1000000; - - auto batterySaverStartMatcher = CreateBatterySaverModeStartAtomMatcher(); - *config.add_atom_matcher() = batterySaverStartMatcher; - const int64_t ttlNs = 2 * bucketSizeNs; // Two buckets. - auto metric_activation = config.add_metric_activation(); - metric_activation->set_metric_id(metricId); - metric_activation->set_activation_type(ACTIVATE_IMMEDIATELY); - auto event_activation = metric_activation->add_event_activation(); - event_activation->set_atom_matcher_id(batterySaverStartMatcher.id()); - event_activation->set_ttl_seconds(ttlNs / 1000000000); - - ConfigKey cfgKey; - auto processor = CreateStatsLogProcessor(baseTimeNs, configAddedTimeNs, config, cfgKey, - SharedRefBase::make(), - util::SUBSYSTEM_SLEEP_STATE); - ASSERT_EQ(processor->mMetricsManagers.size(), 1u); - EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid()); - processor->mPullerManager->ForceClearPullerCache(); - - int startBucketNum = processor->mMetricsManagers.begin() - ->second->mAllMetricProducers[0] - ->getCurrentBucketNum(); - EXPECT_GT(startBucketNum, (int64_t)0); - EXPECT_FALSE(processor->mMetricsManagers.begin()->second->mAllMetricProducers[0]->isActive()); - - // When creating the config, the value metric producer should register the alarm at the - // end of the current bucket. - ASSERT_EQ((size_t)1, processor->mPullerManager->mReceivers.size()); - EXPECT_EQ(bucketSizeNs, - processor->mPullerManager->mReceivers.begin()->second.front().intervalNs); - int64_t& expectedPullTimeNs = - processor->mPullerManager->mReceivers.begin()->second.front().nextPullTimeNs; - EXPECT_EQ(baseTimeNs + startBucketNum * bucketSizeNs + bucketSizeNs, expectedPullTimeNs); - - // Pulling alarm arrives on time and reset the sequential pulling alarm. - processor->informPullAlarmFired(expectedPullTimeNs + 1); // 15 mins + 1 ns. - EXPECT_EQ(baseTimeNs + startBucketNum * bucketSizeNs + 2 * bucketSizeNs, expectedPullTimeNs); - EXPECT_FALSE(processor->mMetricsManagers.begin()->second->mAllMetricProducers[0]->isActive()); - - // Activate the metric. A pull occurs here - const int64_t activationNs = configAddedTimeNs + bucketSizeNs + (2 * 1000 * 1000); // 2 millis. - auto batterySaverOnEvent = CreateBatterySaverOnEvent(activationNs); - processor->OnLogEvent(batterySaverOnEvent.get()); // 15 mins + 2 ms. - EXPECT_TRUE(processor->mMetricsManagers.begin()->second->mAllMetricProducers[0]->isActive()); - - processor->informPullAlarmFired(expectedPullTimeNs + 1); // 20 mins + 1 ns. - EXPECT_EQ(baseTimeNs + startBucketNum * bucketSizeNs + 3 * bucketSizeNs, expectedPullTimeNs); - - processor->informPullAlarmFired(expectedPullTimeNs + 2); // 25 mins + 2 ns. - EXPECT_EQ(baseTimeNs + startBucketNum * bucketSizeNs + 4 * bucketSizeNs, expectedPullTimeNs); - - // Create random event to deactivate metric. - auto deactivationEvent = CreateScreenBrightnessChangedEvent(activationNs + ttlNs + 1, 50); - processor->OnLogEvent(deactivationEvent.get()); - EXPECT_FALSE(processor->mMetricsManagers.begin()->second->mAllMetricProducers[0]->isActive()); - - processor->informPullAlarmFired(expectedPullTimeNs + 3); - EXPECT_EQ(baseTimeNs + startBucketNum * bucketSizeNs + 5 * bucketSizeNs, expectedPullTimeNs); - - processor->informPullAlarmFired(expectedPullTimeNs + 4); - EXPECT_EQ(baseTimeNs + startBucketNum * bucketSizeNs + 6 * bucketSizeNs, expectedPullTimeNs); - - ConfigMetricsReportList reports; - vector buffer; - processor->onDumpReport(cfgKey, configAddedTimeNs + 7 * bucketSizeNs + 10, false, true, - ADB_DUMP, FAST, &buffer); - EXPECT_TRUE(buffer.size() > 0); - EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size())); - backfillDimensionPath(&reports); - backfillStringInReport(&reports); - backfillStartEndTimestamp(&reports); - ASSERT_EQ(1, reports.reports_size()); - ASSERT_EQ(1, reports.reports(0).metrics_size()); - StatsLogReport::ValueMetricDataWrapper valueMetrics; - sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).value_metrics(), &valueMetrics); - ASSERT_GT((int)valueMetrics.data_size(), 0); - - auto data = valueMetrics.data(0); - EXPECT_EQ(util::SUBSYSTEM_SLEEP_STATE, data.dimensions_in_what().field()); - ASSERT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size()); - EXPECT_EQ(1 /* subsystem name field */, - data.dimensions_in_what().value_tuple().dimensions_value(0).field()); - EXPECT_FALSE(data.dimensions_in_what().value_tuple().dimensions_value(0).value_str().empty()); - // We have 2 full buckets, the two surrounding the activation are dropped. - ASSERT_EQ(2, data.bucket_info_size()); - - auto bucketInfo = data.bucket_info(0); - EXPECT_EQ(baseTimeNs + 3 * bucketSizeNs, bucketInfo.start_bucket_elapsed_nanos()); - EXPECT_EQ(baseTimeNs + 4 * bucketSizeNs, bucketInfo.end_bucket_elapsed_nanos()); - ASSERT_EQ(1, bucketInfo.values_size()); - - bucketInfo = data.bucket_info(1); - EXPECT_EQ(baseTimeNs + 4 * bucketSizeNs, bucketInfo.start_bucket_elapsed_nanos()); - EXPECT_EQ(baseTimeNs + 5 * bucketSizeNs, bucketInfo.end_bucket_elapsed_nanos()); - ASSERT_EQ(1, bucketInfo.values_size()); -} - -/** - * Test initialization of a simple value metric that is sliced by a state. - * - * ValueCpuUserTimePerScreenState - */ -TEST(ValueMetricE2eTest, TestInitWithSlicedState) { - // Create config. - StatsdConfig config; - config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root. - - auto pulledAtomMatcher = - CreateSimpleAtomMatcher("TestMatcher", util::SUBSYSTEM_SLEEP_STATE); - *config.add_atom_matcher() = pulledAtomMatcher; - - auto screenState = CreateScreenState(); - *config.add_state() = screenState; - - // Create value metric that slices by screen state without a map. - int64_t metricId = 123456; - auto valueMetric = config.add_value_metric(); - valueMetric->set_id(metricId); - valueMetric->set_bucket(TimeUnit::FIVE_MINUTES); - valueMetric->set_what(pulledAtomMatcher.id()); - *valueMetric->mutable_value_field() = - CreateDimensions(util::CPU_TIME_PER_UID, {2 /* user_time_micros */}); - valueMetric->add_slice_by_state(screenState.id()); - valueMetric->set_max_pull_delay_sec(INT_MAX); - - // Initialize StatsLogProcessor. - const uint64_t bucketStartTimeNs = 10000000000; // 0:10 - const uint64_t bucketSizeNs = - TimeUnitToBucketSizeInMillis(config.value_metric(0).bucket()) * 1000000LL; - int uid = 12345; - int64_t cfgId = 98765; - ConfigKey cfgKey(uid, cfgId); - - auto processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey); - - // Check that StateTrackers were initialized correctly. - EXPECT_EQ(1, StateManager::getInstance().getStateTrackersCount()); - EXPECT_EQ(1, StateManager::getInstance().getListenersCount(SCREEN_STATE_ATOM_ID)); - - // Check that ValueMetricProducer was initialized correctly. - ASSERT_EQ(1U, processor->mMetricsManagers.size()); - sp metricsManager = processor->mMetricsManagers.begin()->second; - EXPECT_TRUE(metricsManager->isConfigValid()); - ASSERT_EQ(1, metricsManager->mAllMetricProducers.size()); - sp metricProducer = metricsManager->mAllMetricProducers[0]; - ASSERT_EQ(1, metricProducer->mSlicedStateAtoms.size()); - EXPECT_EQ(SCREEN_STATE_ATOM_ID, metricProducer->mSlicedStateAtoms.at(0)); - ASSERT_EQ(0, metricProducer->mStateGroupMap.size()); -} - -/** - * Test initialization of a value metric that is sliced by state and has - * dimensions_in_what. - * - * ValueCpuUserTimePerUidPerUidProcessState - */ -TEST(ValueMetricE2eTest, TestInitWithSlicedState_WithDimensions) { - // Create config. - StatsdConfig config; - config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root. - - auto cpuTimePerUidMatcher = - CreateSimpleAtomMatcher("CpuTimePerUidMatcher", util::CPU_TIME_PER_UID); - *config.add_atom_matcher() = cpuTimePerUidMatcher; - - auto uidProcessState = CreateUidProcessState(); - *config.add_state() = uidProcessState; - - // Create value metric that slices by screen state with a complete map. - int64_t metricId = 123456; - auto valueMetric = config.add_value_metric(); - valueMetric->set_id(metricId); - valueMetric->set_bucket(TimeUnit::FIVE_MINUTES); - valueMetric->set_what(cpuTimePerUidMatcher.id()); - *valueMetric->mutable_value_field() = - CreateDimensions(util::CPU_TIME_PER_UID, {2 /* user_time_micros */}); - *valueMetric->mutable_dimensions_in_what() = - CreateDimensions(util::CPU_TIME_PER_UID, {1 /* uid */}); - valueMetric->add_slice_by_state(uidProcessState.id()); - MetricStateLink* stateLink = valueMetric->add_state_link(); - stateLink->set_state_atom_id(UID_PROCESS_STATE_ATOM_ID); - auto fieldsInWhat = stateLink->mutable_fields_in_what(); - *fieldsInWhat = CreateDimensions(util::CPU_TIME_PER_UID, {1 /* uid */}); - auto fieldsInState = stateLink->mutable_fields_in_state(); - *fieldsInState = CreateDimensions(UID_PROCESS_STATE_ATOM_ID, {1 /* uid */}); - valueMetric->set_max_pull_delay_sec(INT_MAX); - - // Initialize StatsLogProcessor. - const uint64_t bucketStartTimeNs = 10000000000; // 0:10 - int uid = 12345; - int64_t cfgId = 98765; - ConfigKey cfgKey(uid, cfgId); - - auto processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey); - - // Check that StateTrackers were initialized correctly. - EXPECT_EQ(1, StateManager::getInstance().getStateTrackersCount()); - EXPECT_EQ(1, StateManager::getInstance().getListenersCount(UID_PROCESS_STATE_ATOM_ID)); - - // Check that ValueMetricProducer was initialized correctly. - ASSERT_EQ(1U, processor->mMetricsManagers.size()); - sp metricsManager = processor->mMetricsManagers.begin()->second; - EXPECT_TRUE(metricsManager->isConfigValid()); - ASSERT_EQ(1, metricsManager->mAllMetricProducers.size()); - sp metricProducer = metricsManager->mAllMetricProducers[0]; - ASSERT_EQ(1, metricProducer->mSlicedStateAtoms.size()); - EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, metricProducer->mSlicedStateAtoms.at(0)); - ASSERT_EQ(0, metricProducer->mStateGroupMap.size()); -} - -/** - * Test initialization of a value metric that is sliced by state and has - * dimensions_in_what. - * - * ValueCpuUserTimePerUidPerUidProcessState - */ -TEST(ValueMetricE2eTest, TestInitWithSlicedState_WithIncorrectDimensions) { - // Create config. - StatsdConfig config; - config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root. - - auto cpuTimePerUidMatcher = - CreateSimpleAtomMatcher("CpuTimePerUidMatcher", util::CPU_TIME_PER_UID); - *config.add_atom_matcher() = cpuTimePerUidMatcher; - - auto uidProcessState = CreateUidProcessState(); - *config.add_state() = uidProcessState; - - // Create value metric that slices by screen state with a complete map. - int64_t metricId = 123456; - auto valueMetric = config.add_value_metric(); - valueMetric->set_id(metricId); - valueMetric->set_bucket(TimeUnit::FIVE_MINUTES); - valueMetric->set_what(cpuTimePerUidMatcher.id()); - *valueMetric->mutable_value_field() = - CreateDimensions(util::CPU_TIME_PER_UID, {2 /* user_time_micros */}); - valueMetric->add_slice_by_state(uidProcessState.id()); - MetricStateLink* stateLink = valueMetric->add_state_link(); - stateLink->set_state_atom_id(UID_PROCESS_STATE_ATOM_ID); - auto fieldsInWhat = stateLink->mutable_fields_in_what(); - *fieldsInWhat = CreateDimensions(util::CPU_TIME_PER_UID, {1 /* uid */}); - auto fieldsInState = stateLink->mutable_fields_in_state(); - *fieldsInState = CreateDimensions(UID_PROCESS_STATE_ATOM_ID, {1 /* uid */}); - valueMetric->set_max_pull_delay_sec(INT_MAX); - - // Initialize StatsLogProcessor. - const uint64_t bucketStartTimeNs = 10000000000; // 0:10 - int uid = 12345; - int64_t cfgId = 98765; - ConfigKey cfgKey(uid, cfgId); - auto processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey); - - // No StateTrackers are initialized. - EXPECT_EQ(0, StateManager::getInstance().getStateTrackersCount()); - - // Config initialization fails. - ASSERT_EQ(0, processor->mMetricsManagers.size()); -} - -#else -GTEST_LOG_(INFO) << "This test does nothing.\n"; -#endif - -} // namespace statsd -} // namespace os -} // namespace android diff --git a/bin/tests/e2e/WakelockDuration_e2e_test.cpp b/bin/tests/e2e/WakelockDuration_e2e_test.cpp deleted file mode 100644 index 52bc222e..00000000 --- a/bin/tests/e2e/WakelockDuration_e2e_test.cpp +++ /dev/null @@ -1,355 +0,0 @@ -// 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 CreateStatsdConfig(DurationMetric::AggregationType aggregationType) { - StatsdConfig config; - config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root. - *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. - FieldMatcher dimensions = CreateAttributionUidAndTagDimensions( - util::WAKELOCK_STATE_CHANGED, {Position::FIRST, Position::LAST}); - // Also slice by the wakelock tag - dimensions.add_child()->set_field(3); // The wakelock tag is set in field 3 of the wakelock. - *holdingWakelockPredicate.mutable_simple_predicate()->mutable_dimensions() = dimensions; - *config.add_predicate() = holdingWakelockPredicate; - - auto durationMetric = config.add_duration_metric(); - durationMetric->set_id(StringToId("WakelockDuration")); - durationMetric->set_what(holdingWakelockPredicate.id()); - durationMetric->set_condition(screenIsOffPredicate.id()); - durationMetric->set_aggregation_type(aggregationType); - // The metric is dimensioning by first attribution node and only by uid. - *durationMetric->mutable_dimensions_in_what() = - CreateAttributionUidDimensions( - util::WAKELOCK_STATE_CHANGED, {Position::FIRST}); - durationMetric->set_bucket(FIVE_MINUTES); - return config; -} - -std::vector attributionUids1 = {111, 222, 222}; -std::vector attributionTags1 = {"App1", "GMSCoreModule1", "GMSCoreModule2"}; - -std::vector attributionUids2 = {111, 222, 222}; -std::vector attributionTags2 = {"App2", "GMSCoreModule1", "GMSCoreModule2"}; - -/* -Events: -Screen off is met from (200ns,1 min+500ns]. -Acquire event for wl1 from 2ns to 1min+2ns -Acquire event for wl2 from 1min-10ns to 2min-15ns -*/ -void FeedEvents(StatsdConfig config, sp processor) { - uint64_t bucketStartTimeNs = 10000000000; - uint64_t bucketSizeNs = - TimeUnitToBucketSizeInMillis(config.duration_metric(0).bucket()) * 1000000LL; - - auto screenTurnedOnEvent = CreateScreenStateChangedEvent( - bucketStartTimeNs + 1, android::view::DisplayStateEnum::DISPLAY_STATE_ON); - auto screenTurnedOffEvent = CreateScreenStateChangedEvent( - bucketStartTimeNs + 200, android::view::DisplayStateEnum::DISPLAY_STATE_OFF); - auto screenTurnedOnEvent2 = - CreateScreenStateChangedEvent(bucketStartTimeNs + bucketSizeNs + 500, - android::view::DisplayStateEnum::DISPLAY_STATE_ON); - - auto acquireEvent1 = CreateAcquireWakelockEvent(bucketStartTimeNs + 2, attributionUids1, - attributionTags1, "wl1"); - auto releaseEvent1 = CreateReleaseWakelockEvent(bucketStartTimeNs + bucketSizeNs + 2, - attributionUids1, attributionTags1, "wl1"); - auto acquireEvent2 = CreateAcquireWakelockEvent(bucketStartTimeNs + bucketSizeNs - 10, - attributionUids2, attributionTags2, "wl2"); - auto releaseEvent2 = CreateReleaseWakelockEvent(bucketStartTimeNs + 2 * bucketSizeNs - 15, - attributionUids2, attributionTags2, "wl2"); - - std::vector> events; - - events.push_back(std::move(screenTurnedOnEvent)); - events.push_back(std::move(screenTurnedOffEvent)); - events.push_back(std::move(screenTurnedOnEvent2)); - events.push_back(std::move(acquireEvent1)); - events.push_back(std::move(acquireEvent2)); - events.push_back(std::move(releaseEvent1)); - events.push_back(std::move(releaseEvent2)); - - sortLogEventsByTimestamp(&events); - - for (const auto& event : events) { - processor->OnLogEvent(event.get()); - } -} - -} // namespace - -TEST(WakelockDurationE2eTest, TestAggregatedPredicateDimensionsForSumDuration1) { - ConfigKey cfgKey; - auto config = CreateStatsdConfig(DurationMetric::SUM); - uint64_t bucketStartTimeNs = 10000000000; - uint64_t bucketSizeNs = - TimeUnitToBucketSizeInMillis(config.duration_metric(0).bucket()) * 1000000LL; - auto processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey); - ASSERT_EQ(processor->mMetricsManagers.size(), 1u); - EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid()); - FeedEvents(config, processor); - vector buffer; - ConfigMetricsReportList reports; - processor->onDumpReport(cfgKey, bucketStartTimeNs + 2 * bucketSizeNs - 1, false, true, ADB_DUMP, - FAST, &buffer); - EXPECT_TRUE(buffer.size() > 0); - EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size())); - backfillDimensionPath(&reports); - backfillStringInReport(&reports); - backfillStartEndTimestamp(&reports); - - ASSERT_EQ(reports.reports_size(), 1); - ASSERT_EQ(reports.reports(0).metrics_size(), 1); - // Only 1 dimension output. The tag dimension in the predicate has been aggregated. - ASSERT_EQ(reports.reports(0).metrics(0).duration_metrics().data_size(), 1); - - auto data = reports.reports(0).metrics(0).duration_metrics().data(0); - // Validate dimension value. - ValidateAttributionUidDimension(data.dimensions_in_what(), - util::WAKELOCK_STATE_CHANGED, 111); - // Validate bucket info. - ASSERT_EQ(reports.reports(0).metrics(0).duration_metrics().data(0).bucket_info_size(), 1); - data = reports.reports(0).metrics(0).duration_metrics().data(0); - // The wakelock holding interval starts from the screen off event and to the end of the 1st - // bucket. - EXPECT_EQ((unsigned long long)data.bucket_info(0).duration_nanos(), bucketSizeNs - 200); -} - -TEST(WakelockDurationE2eTest, TestAggregatedPredicateDimensionsForSumDuration2) { - ConfigKey cfgKey; - auto config = CreateStatsdConfig(DurationMetric::SUM); - uint64_t bucketStartTimeNs = 10000000000; - uint64_t bucketSizeNs = - TimeUnitToBucketSizeInMillis(config.duration_metric(0).bucket()) * 1000000LL; - auto processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey); - ASSERT_EQ(processor->mMetricsManagers.size(), 1u); - EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid()); - FeedEvents(config, processor); - vector buffer; - ConfigMetricsReportList reports; - processor->onDumpReport(cfgKey, bucketStartTimeNs + 2 * bucketSizeNs + 1, false, true, ADB_DUMP, - FAST, &buffer); - EXPECT_TRUE(buffer.size() > 0); - EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size())); - backfillDimensionPath(&reports); - backfillStringInReport(&reports); - backfillStartEndTimestamp(&reports); - ASSERT_EQ(reports.reports_size(), 1); - ASSERT_EQ(reports.reports(0).metrics_size(), 1); - ASSERT_EQ(reports.reports(0).metrics(0).duration_metrics().data_size(), 1); - // Dump the report after the end of 2nd bucket. - ASSERT_EQ(reports.reports(0).metrics(0).duration_metrics().data(0).bucket_info_size(), 2); - auto data = reports.reports(0).metrics(0).duration_metrics().data(0); - // Validate dimension value. - ValidateAttributionUidDimension(data.dimensions_in_what(), - util::WAKELOCK_STATE_CHANGED, 111); - // Two output buckets. - // The wakelock holding interval in the 1st bucket starts from the screen off event and to - // the end of the 1st bucket. - EXPECT_EQ((unsigned long long)data.bucket_info(0).duration_nanos(), - bucketStartTimeNs + bucketSizeNs - (bucketStartTimeNs + 200)); - // The wakelock holding interval in the 2nd bucket starts at the beginning of the bucket and - // ends at the second screen on event. - EXPECT_EQ((unsigned long long)data.bucket_info(1).duration_nanos(), 500UL); -} - -TEST(WakelockDurationE2eTest, TestAggregatedPredicateDimensionsForSumDuration3) { - ConfigKey cfgKey; - auto config = CreateStatsdConfig(DurationMetric::SUM); - uint64_t bucketStartTimeNs = 10000000000; - uint64_t bucketSizeNs = - TimeUnitToBucketSizeInMillis(config.duration_metric(0).bucket()) * 1000000LL; - auto processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey); - ASSERT_EQ(processor->mMetricsManagers.size(), 1u); - EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid()); - FeedEvents(config, processor); - vector buffer; - ConfigMetricsReportList reports; - - std::vector> events; - events.push_back( - CreateScreenStateChangedEvent(bucketStartTimeNs + 2 * bucketSizeNs + 90, - android::view::DisplayStateEnum::DISPLAY_STATE_OFF)); - events.push_back(CreateAcquireWakelockEvent(bucketStartTimeNs + 2 * bucketSizeNs + 100, - attributionUids1, attributionTags1, "wl3")); - events.push_back(CreateReleaseWakelockEvent(bucketStartTimeNs + 5 * bucketSizeNs + 100, - attributionUids1, attributionTags1, "wl3")); - sortLogEventsByTimestamp(&events); - for (const auto& event : events) { - processor->OnLogEvent(event.get()); - } - - processor->onDumpReport(cfgKey, bucketStartTimeNs + 6 * bucketSizeNs + 1, false, true, ADB_DUMP, - FAST, &buffer); - EXPECT_TRUE(buffer.size() > 0); - EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size())); - backfillDimensionPath(&reports); - backfillStringInReport(&reports); - backfillStartEndTimestamp(&reports); - ASSERT_EQ(reports.reports_size(), 1); - ASSERT_EQ(reports.reports(0).metrics_size(), 1); - ASSERT_EQ(reports.reports(0).metrics(0).duration_metrics().data_size(), 1); - ASSERT_EQ(reports.reports(0).metrics(0).duration_metrics().data(0).bucket_info_size(), 6); - auto data = reports.reports(0).metrics(0).duration_metrics().data(0); - ValidateAttributionUidDimension(data.dimensions_in_what(), - util::WAKELOCK_STATE_CHANGED, 111); - // The last wakelock holding spans 4 buckets. - EXPECT_EQ((unsigned long long)data.bucket_info(2).duration_nanos(), bucketSizeNs - 100); - EXPECT_EQ((unsigned long long)data.bucket_info(3).duration_nanos(), bucketSizeNs); - EXPECT_EQ((unsigned long long)data.bucket_info(4).duration_nanos(), bucketSizeNs); - EXPECT_EQ((unsigned long long)data.bucket_info(5).duration_nanos(), 100UL); -} - -TEST(WakelockDurationE2eTest, TestAggregatedPredicateDimensionsForMaxDuration1) { - ConfigKey cfgKey; - auto config = CreateStatsdConfig(DurationMetric::MAX_SPARSE); - uint64_t bucketStartTimeNs = 10000000000; - uint64_t bucketSizeNs = - TimeUnitToBucketSizeInMillis(config.duration_metric(0).bucket()) * 1000000LL; - auto processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey); - ASSERT_EQ(processor->mMetricsManagers.size(), 1u); - EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid()); - FeedEvents(config, processor); - ConfigMetricsReportList reports; - vector buffer; - processor->onDumpReport(cfgKey, bucketStartTimeNs + 2 * bucketSizeNs - 1, false, true, ADB_DUMP, - FAST, &buffer); - EXPECT_TRUE(buffer.size() > 0); - - EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size())); - backfillDimensionPath(&reports); - backfillStringInReport(&reports); - backfillStartEndTimestamp(&reports); - - ASSERT_EQ(reports.reports_size(), 1); - - // When using ProtoOutputStream, if nothing written to a sub msg, it won't be treated as - // one. It was previsouly 1 because we had a fake onDumpReport which calls add_metric() by - // itself. - ASSERT_EQ(1, reports.reports(0).metrics_size()); - ASSERT_EQ(0, reports.reports(0).metrics(0).duration_metrics().data_size()); -} - -TEST(WakelockDurationE2eTest, TestAggregatedPredicateDimensionsForMaxDuration2) { - ConfigKey cfgKey; - auto config = CreateStatsdConfig(DurationMetric::MAX_SPARSE); - uint64_t bucketStartTimeNs = 10000000000; - uint64_t bucketSizeNs = - TimeUnitToBucketSizeInMillis(config.duration_metric(0).bucket()) * 1000000LL; - auto processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey); - ASSERT_EQ(processor->mMetricsManagers.size(), 1u); - EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid()); - FeedEvents(config, processor); - ConfigMetricsReportList reports; - vector buffer; - processor->onDumpReport(cfgKey, bucketStartTimeNs + 2 * bucketSizeNs + 1, false, true, ADB_DUMP, - FAST, &buffer); - EXPECT_TRUE(buffer.size() > 0); - EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size())); - backfillDimensionPath(&reports); - backfillStringInReport(&reports); - backfillStartEndTimestamp(&reports); - ASSERT_EQ(reports.reports_size(), 1); - ASSERT_EQ(reports.reports(0).metrics_size(), 1); - ASSERT_EQ(reports.reports(0).metrics(0).duration_metrics().data_size(), 1); - // Dump the report after the end of 2nd bucket. One dimension with one bucket. - ASSERT_EQ(reports.reports(0).metrics(0).duration_metrics().data(0).bucket_info_size(), 1); - auto data = reports.reports(0).metrics(0).duration_metrics().data(0); - // Validate dimension value. - ValidateAttributionUidDimension(data.dimensions_in_what(), - util::WAKELOCK_STATE_CHANGED, 111); - // The max is acquire event for wl1 to screen off start. - EXPECT_EQ((unsigned long long)data.bucket_info(0).duration_nanos(), bucketSizeNs + 2 - 200); -} - -TEST(WakelockDurationE2eTest, TestAggregatedPredicateDimensionsForMaxDuration3) { - ConfigKey cfgKey; - auto config = CreateStatsdConfig(DurationMetric::MAX_SPARSE); - uint64_t bucketStartTimeNs = 10000000000; - uint64_t bucketSizeNs = - TimeUnitToBucketSizeInMillis(config.duration_metric(0).bucket()) * 1000000LL; - auto processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey); - ASSERT_EQ(processor->mMetricsManagers.size(), 1u); - EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid()); - FeedEvents(config, processor); - ConfigMetricsReportList reports; - vector buffer; - - std::vector> events; - events.push_back( - CreateScreenStateChangedEvent(bucketStartTimeNs + 2 * bucketSizeNs + 90, - android::view::DisplayStateEnum::DISPLAY_STATE_OFF)); - events.push_back(CreateAcquireWakelockEvent(bucketStartTimeNs + 2 * bucketSizeNs + 100, - attributionUids1, attributionTags1, "wl3")); - events.push_back(CreateReleaseWakelockEvent(bucketStartTimeNs + 5 * bucketSizeNs + 100, - attributionUids1, attributionTags1, "wl3")); - sortLogEventsByTimestamp(&events); - for (const auto& event : events) { - processor->OnLogEvent(event.get()); - } - - processor->onDumpReport(cfgKey, bucketStartTimeNs + 6 * bucketSizeNs + 1, false, true, ADB_DUMP, - FAST, &buffer); - EXPECT_TRUE(buffer.size() > 0); - EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size())); - backfillDimensionPath(&reports); - backfillStringInReport(&reports); - backfillStartEndTimestamp(&reports); - ASSERT_EQ(reports.reports_size(), 1); - ASSERT_EQ(reports.reports(0).metrics_size(), 1); - ASSERT_EQ(reports.reports(0).metrics(0).duration_metrics().data_size(), 1); - ASSERT_EQ(reports.reports(0).metrics(0).duration_metrics().data(0).bucket_info_size(), 2); - auto data = reports.reports(0).metrics(0).duration_metrics().data(0); - ValidateAttributionUidDimension(data.dimensions_in_what(), - util::WAKELOCK_STATE_CHANGED, 111); - // The last wakelock holding spans 4 buckets. - EXPECT_EQ((unsigned long long)data.bucket_info(1).duration_nanos(), 3 * bucketSizeNs); - EXPECT_EQ((unsigned long long)data.bucket_info(1).start_bucket_elapsed_nanos(), - bucketStartTimeNs + 5 * bucketSizeNs); - EXPECT_EQ((unsigned long long)data.bucket_info(1).end_bucket_elapsed_nanos(), - bucketStartTimeNs + 6 * bucketSizeNs); -} - -#else -GTEST_LOG_(INFO) << "This test does nothing.\n"; -#endif - -} // namespace statsd -} // namespace os -} // namespace android diff --git a/bin/tests/external/StatsCallbackPuller_test.cpp b/bin/tests/external/StatsCallbackPuller_test.cpp deleted file mode 100644 index 85a60886..00000000 --- a/bin/tests/external/StatsCallbackPuller_test.cpp +++ /dev/null @@ -1,219 +0,0 @@ -// Copyright (C) 2019 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 "src/external/StatsCallbackPuller.h" - -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include - -#include "../metrics/metrics_test_helper.h" -#include "src/stats_log_util.h" -#include "stats_event.h" -#include "tests/statsd_test_util.h" - -#ifdef __ANDROID__ - -namespace android { -namespace os { -namespace statsd { - -using namespace testing; -using Status = ::ndk::ScopedAStatus; -using aidl::android::os::BnPullAtomCallback; -using aidl::android::os::IPullAtomResultReceiver; -using aidl::android::util::StatsEventParcel; -using ::ndk::SharedRefBase; -using std::make_shared; -using std::shared_ptr; -using std::vector; -using std::this_thread::sleep_for; -using testing::Contains; - -namespace { -int pullTagId = -12; -bool pullSuccess; -vector values; -int64_t pullDelayNs; -int64_t pullTimeoutNs; -int64_t pullCoolDownNs; -std::thread pullThread; - -AStatsEvent* createSimpleEvent(int64_t value) { - AStatsEvent* event = AStatsEvent_obtain(); - AStatsEvent_setAtomId(event, pullTagId); - AStatsEvent_writeInt64(event, value); - AStatsEvent_build(event); - return event; -} - -void executePull(const shared_ptr& resultReceiver) { - // Convert stats_events into StatsEventParcels. - vector parcels; - for (int i = 0; i < values.size(); i++) { - AStatsEvent* event = createSimpleEvent(values[i]); - size_t size; - uint8_t* buffer = AStatsEvent_getBuffer(event, &size); - - StatsEventParcel p; - // vector.assign() creates a copy, but this is inevitable unless - // stats_event.h/c uses a vector as opposed to a buffer. - p.buffer.assign(buffer, buffer + size); - parcels.push_back(std::move(p)); - AStatsEvent_release(event); - } - - sleep_for(std::chrono::nanoseconds(pullDelayNs)); - resultReceiver->pullFinished(pullTagId, pullSuccess, parcels); -} - -class FakePullAtomCallback : public BnPullAtomCallback { -public: - Status onPullAtom(int atomTag, - const shared_ptr& resultReceiver) override { - // Force pull to happen in separate thread to simulate binder. - pullThread = std::thread(executePull, resultReceiver); - return Status::ok(); - } -}; - -class StatsCallbackPullerTest : public ::testing::Test { -public: - StatsCallbackPullerTest() { - } - - void SetUp() override { - pullSuccess = false; - pullDelayNs = 0; - values.clear(); - pullTimeoutNs = 10000000000LL; // 10 seconds. - pullCoolDownNs = 1000000000; // 1 second. - } - - void TearDown() override { - if (pullThread.joinable()) { - pullThread.join(); - } - values.clear(); - } -}; -} // Anonymous namespace. - -TEST_F(StatsCallbackPullerTest, PullSuccess) { - shared_ptr cb = SharedRefBase::make(); - int64_t value = 43; - pullSuccess = true; - values.push_back(value); - - StatsCallbackPuller puller(pullTagId, cb, pullCoolDownNs, pullTimeoutNs, {}); - - vector> dataHolder; - int64_t startTimeNs = getElapsedRealtimeNs(); - EXPECT_TRUE(puller.PullInternal(&dataHolder)); - int64_t endTimeNs = getElapsedRealtimeNs(); - - ASSERT_EQ(1, dataHolder.size()); - EXPECT_EQ(pullTagId, dataHolder[0]->GetTagId()); - EXPECT_LT(startTimeNs, dataHolder[0]->GetElapsedTimestampNs()); - EXPECT_GT(endTimeNs, dataHolder[0]->GetElapsedTimestampNs()); - ASSERT_EQ(1, dataHolder[0]->size()); - EXPECT_EQ(value, dataHolder[0]->getValues()[0].mValue.int_value); -} - -TEST_F(StatsCallbackPullerTest, PullFail) { - shared_ptr cb = SharedRefBase::make(); - pullSuccess = false; - int64_t value = 1234; - values.push_back(value); - - StatsCallbackPuller puller(pullTagId, cb, pullCoolDownNs, pullTimeoutNs, {}); - - vector> dataHolder; - EXPECT_FALSE(puller.PullInternal(&dataHolder)); - ASSERT_EQ(0, dataHolder.size()); -} - -TEST_F(StatsCallbackPullerTest, PullTimeout) { - shared_ptr cb = SharedRefBase::make(); - pullSuccess = true; - pullDelayNs = MillisToNano(5); // 5ms. - pullTimeoutNs = 10000; // 10 microseconds. - int64_t value = 4321; - values.push_back(value); - - StatsCallbackPuller puller(pullTagId, cb, pullCoolDownNs, pullTimeoutNs, {}); - - vector> dataHolder; - int64_t startTimeNs = getElapsedRealtimeNs(); - // Returns true to let StatsPuller code evaluate the timeout. - EXPECT_TRUE(puller.PullInternal(&dataHolder)); - int64_t endTimeNs = getElapsedRealtimeNs(); - int64_t actualPullDurationNs = endTimeNs - startTimeNs; - - // Pull should take at least the timeout amount of time, but should stop early because the delay - // is bigger. - EXPECT_LT(pullTimeoutNs, actualPullDurationNs); - EXPECT_GT(pullDelayNs, actualPullDurationNs); - ASSERT_EQ(0, dataHolder.size()); - - // Let the pull return and make sure that the dataHolder is not modified. - pullThread.join(); - ASSERT_EQ(0, dataHolder.size()); -} - -// Register a puller and ensure that the timeout logic works. -TEST_F(StatsCallbackPullerTest, RegisterAndTimeout) { - shared_ptr cb = SharedRefBase::make(); - pullSuccess = true; - pullDelayNs = MillisToNano(5); // 5 ms. - pullTimeoutNs = 10000; // 10 microsseconds. - int64_t value = 4321; - int32_t uid = 123; - values.push_back(value); - - sp pullerManager = new StatsPullerManager(); - pullerManager->RegisterPullAtomCallback(uid, pullTagId, pullCoolDownNs, pullTimeoutNs, - vector(), cb); - vector> dataHolder; - int64_t startTimeNs = getElapsedRealtimeNs(); - // Returns false, since StatsPuller code will evaluate the timeout. - EXPECT_FALSE(pullerManager->Pull(pullTagId, {uid}, startTimeNs, &dataHolder)); - int64_t endTimeNs = getElapsedRealtimeNs(); - int64_t actualPullDurationNs = endTimeNs - startTimeNs; - - // Pull should take at least the timeout amount of time, but should stop early because the delay - // is bigger. Make sure that the time is closer to the timeout, than to the intended delay. - EXPECT_LT(pullTimeoutNs, actualPullDurationNs); - EXPECT_GT(pullDelayNs / 5, actualPullDurationNs); - ASSERT_EQ(0, dataHolder.size()); - - // Let the pull return and make sure that the dataHolder is not modified. - pullThread.join(); - ASSERT_EQ(0, dataHolder.size()); -} - -} // namespace statsd -} // namespace os -} // namespace android -#else -GTEST_LOG_(INFO) << "This test does nothing.\n"; -#endif diff --git a/bin/tests/external/StatsPullerManager_test.cpp b/bin/tests/external/StatsPullerManager_test.cpp deleted file mode 100644 index 0d539f47..00000000 --- a/bin/tests/external/StatsPullerManager_test.cpp +++ /dev/null @@ -1,150 +0,0 @@ -// Copyright (C) 2020 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 "src/external/StatsPullerManager.h" - -#include -#include -#include -#include - -#include "stats_event.h" -#include "tests/statsd_test_util.h" - -using aidl::android::util::StatsEventParcel; -using ::ndk::SharedRefBase; -using std::make_shared; -using std::shared_ptr; -using std::vector; - -namespace android { -namespace os { -namespace statsd { - -namespace { - -int pullTagId1 = 10101; -int pullTagId2 = 10102; -int uid1 = 9999; -int uid2 = 8888; -ConfigKey configKey(50, 12345); -ConfigKey badConfigKey(60, 54321); -int unregisteredUid = 98765; -int64_t coolDownNs = NS_PER_SEC; -int64_t timeoutNs = NS_PER_SEC / 2; - -AStatsEvent* createSimpleEvent(int32_t atomId, int32_t value) { - AStatsEvent* event = AStatsEvent_obtain(); - AStatsEvent_setAtomId(event, atomId); - AStatsEvent_writeInt32(event, value); - AStatsEvent_build(event); - return event; -} - -class FakePullAtomCallback : public BnPullAtomCallback { -public: - FakePullAtomCallback(int32_t uid) : mUid(uid){}; - Status onPullAtom(int atomTag, - const shared_ptr& resultReceiver) override { - vector parcels; - AStatsEvent* event = createSimpleEvent(atomTag, mUid); - size_t size; - uint8_t* buffer = AStatsEvent_getBuffer(event, &size); - - StatsEventParcel p; - // vector.assign() creates a copy, but this is inevitable unless - // stats_event.h/c uses a vector as opposed to a buffer. - p.buffer.assign(buffer, buffer + size); - parcels.push_back(std::move(p)); - AStatsEvent_release(event); - resultReceiver->pullFinished(atomTag, /*success*/ true, parcels); - return Status::ok(); - } - int32_t mUid; -}; - -class FakePullUidProvider : public PullUidProvider { -public: - vector getPullAtomUids(int atomId) override { - if (atomId == pullTagId1) { - return {uid2, uid1}; - } else if (atomId == pullTagId2) { - return {uid2}; - } - return {}; - } -}; - -sp createPullerManagerAndRegister() { - sp pullerManager = new StatsPullerManager(); - shared_ptr cb1 = SharedRefBase::make(uid1); - pullerManager->RegisterPullAtomCallback(uid1, pullTagId1, coolDownNs, timeoutNs, {}, cb1); - shared_ptr cb2 = SharedRefBase::make(uid2); - pullerManager->RegisterPullAtomCallback(uid2, pullTagId1, coolDownNs, timeoutNs, {}, cb2); - pullerManager->RegisterPullAtomCallback(uid1, pullTagId2, coolDownNs, timeoutNs, {}, cb1); - return pullerManager; -} -} // anonymous namespace - -TEST(StatsPullerManagerTest, TestPullInvalidUid) { - sp pullerManager = createPullerManagerAndRegister(); - - vector> data; - EXPECT_FALSE(pullerManager->Pull(pullTagId1, {unregisteredUid}, /*timestamp =*/1, &data)); -} - -TEST(StatsPullerManagerTest, TestPullChoosesCorrectUid) { - sp pullerManager = createPullerManagerAndRegister(); - - vector> data; - EXPECT_TRUE(pullerManager->Pull(pullTagId1, {uid1}, /*timestamp =*/1, &data)); - ASSERT_EQ(data.size(), 1); - EXPECT_EQ(data[0]->GetTagId(), pullTagId1); - ASSERT_EQ(data[0]->getValues().size(), 1); - EXPECT_EQ(data[0]->getValues()[0].mValue.int_value, uid1); -} - -TEST(StatsPullerManagerTest, TestPullInvalidConfigKey) { - sp pullerManager = createPullerManagerAndRegister(); - sp uidProvider = new FakePullUidProvider(); - pullerManager->RegisterPullUidProvider(configKey, uidProvider); - - vector> data; - EXPECT_FALSE(pullerManager->Pull(pullTagId1, badConfigKey, /*timestamp =*/1, &data)); -} - -TEST(StatsPullerManagerTest, TestPullConfigKeyGood) { - sp pullerManager = createPullerManagerAndRegister(); - sp uidProvider = new FakePullUidProvider(); - pullerManager->RegisterPullUidProvider(configKey, uidProvider); - - vector> data; - EXPECT_TRUE(pullerManager->Pull(pullTagId1, configKey, /*timestamp =*/1, &data)); - EXPECT_EQ(data[0]->GetTagId(), pullTagId1); - ASSERT_EQ(data[0]->getValues().size(), 1); - EXPECT_EQ(data[0]->getValues()[0].mValue.int_value, uid2); -} - -TEST(StatsPullerManagerTest, TestPullConfigKeyNoPullerWithUid) { - sp pullerManager = createPullerManagerAndRegister(); - sp uidProvider = new FakePullUidProvider(); - pullerManager->RegisterPullUidProvider(configKey, uidProvider); - - vector> data; - EXPECT_FALSE(pullerManager->Pull(pullTagId2, configKey, /*timestamp =*/1, &data)); -} - -} // namespace statsd -} // namespace os -} // namespace android \ No newline at end of file diff --git a/bin/tests/external/StatsPuller_test.cpp b/bin/tests/external/StatsPuller_test.cpp deleted file mode 100644 index 55a90365..00000000 --- a/bin/tests/external/StatsPuller_test.cpp +++ /dev/null @@ -1,316 +0,0 @@ -// Copyright (C) 2018 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 -#include - -#include -#include -#include - -#include "../metrics/metrics_test_helper.h" -#include "src/stats_log_util.h" -#include "stats_event.h" -#include "tests/statsd_test_util.h" - -#ifdef __ANDROID__ - -namespace android { -namespace os { -namespace statsd { - -using namespace testing; -using std::make_shared; -using std::shared_ptr; -using std::vector; -using std::this_thread::sleep_for; -using testing::Contains; - -namespace { -int pullTagId = 10014; - -bool pullSuccess; -vector> pullData; -long pullDelayNs; - -class FakePuller : public StatsPuller { -public: - FakePuller() - : StatsPuller(pullTagId, /*coolDownNs=*/MillisToNano(10), /*timeoutNs=*/MillisToNano(5)){}; - -private: - bool PullInternal(vector>* data) override { - (*data) = pullData; - sleep_for(std::chrono::nanoseconds(pullDelayNs)); - return pullSuccess; - } -}; - -FakePuller puller; - -std::unique_ptr createSimpleEvent(int64_t eventTimeNs, int64_t value) { - AStatsEvent* statsEvent = AStatsEvent_obtain(); - AStatsEvent_setAtomId(statsEvent, pullTagId); - AStatsEvent_overwriteTimestamp(statsEvent, eventTimeNs); - AStatsEvent_writeInt64(statsEvent, value); - - std::unique_ptr logEvent = std::make_unique(/*uid=*/0, /*pid=*/0); - parseStatsEventToLogEvent(statsEvent, logEvent.get()); - return logEvent; -} - -class StatsPullerTest : public ::testing::Test { -public: - StatsPullerTest() { - } - - void SetUp() override { - puller.ForceClearCache(); - pullSuccess = false; - pullDelayNs = 0; - pullData.clear(); - } -}; - -} // Anonymous namespace. - -TEST_F(StatsPullerTest, PullSuccess) { - pullData.push_back(createSimpleEvent(1111L, 33)); - - pullSuccess = true; - - vector> dataHolder; - EXPECT_TRUE(puller.Pull(getElapsedRealtimeNs(), &dataHolder)); - ASSERT_EQ(1, dataHolder.size()); - EXPECT_EQ(pullTagId, dataHolder[0]->GetTagId()); - EXPECT_EQ(1111L, dataHolder[0]->GetElapsedTimestampNs()); - ASSERT_EQ(1, dataHolder[0]->size()); - EXPECT_EQ(33, dataHolder[0]->getValues()[0].mValue.int_value); - - sleep_for(std::chrono::milliseconds(11)); - - pullData.clear(); - pullData.push_back(createSimpleEvent(2222L, 44)); - - pullSuccess = true; - - EXPECT_TRUE(puller.Pull(getElapsedRealtimeNs(), &dataHolder)); - ASSERT_EQ(1, dataHolder.size()); - EXPECT_EQ(pullTagId, dataHolder[0]->GetTagId()); - EXPECT_EQ(2222L, dataHolder[0]->GetElapsedTimestampNs()); - ASSERT_EQ(1, dataHolder[0]->size()); - EXPECT_EQ(44, dataHolder[0]->getValues()[0].mValue.int_value); -} - -TEST_F(StatsPullerTest, PullFailAfterSuccess) { - pullData.push_back(createSimpleEvent(1111L, 33)); - - pullSuccess = true; - - vector> dataHolder; - EXPECT_TRUE(puller.Pull(getElapsedRealtimeNs(), &dataHolder)); - ASSERT_EQ(1, dataHolder.size()); - EXPECT_EQ(pullTagId, dataHolder[0]->GetTagId()); - EXPECT_EQ(1111L, dataHolder[0]->GetElapsedTimestampNs()); - ASSERT_EQ(1, dataHolder[0]->size()); - EXPECT_EQ(33, dataHolder[0]->getValues()[0].mValue.int_value); - - sleep_for(std::chrono::milliseconds(11)); - - pullData.clear(); - pullData.push_back(createSimpleEvent(2222L, 44)); - - pullSuccess = false; - dataHolder.clear(); - EXPECT_FALSE(puller.Pull(getElapsedRealtimeNs(), &dataHolder)); - ASSERT_EQ(0, dataHolder.size()); - - // Fails due to hitting the cool down. - pullSuccess = true; - dataHolder.clear(); - EXPECT_FALSE(puller.Pull(getElapsedRealtimeNs(), &dataHolder)); - ASSERT_EQ(0, dataHolder.size()); -} - -// Test pull takes longer than timeout, 2nd pull happens shorter than cooldown -TEST_F(StatsPullerTest, PullTakeTooLongAndPullFast) { - pullData.push_back(createSimpleEvent(1111L, 33)); - pullSuccess = true; - // timeout is 5ms - pullDelayNs = MillisToNano(6); - - vector> dataHolder; - EXPECT_FALSE(puller.Pull(getElapsedRealtimeNs(), &dataHolder)); - ASSERT_EQ(0, dataHolder.size()); - - pullData.clear(); - pullData.push_back(createSimpleEvent(2222L, 44)); - pullDelayNs = 0; - - pullSuccess = true; - dataHolder.clear(); - EXPECT_FALSE(puller.Pull(getElapsedRealtimeNs(), &dataHolder)); - ASSERT_EQ(0, dataHolder.size()); -} - -TEST_F(StatsPullerTest, PullFail) { - pullData.push_back(createSimpleEvent(1111L, 33)); - - pullSuccess = false; - - vector> dataHolder; - EXPECT_FALSE(puller.Pull(getElapsedRealtimeNs(), &dataHolder)); - ASSERT_EQ(0, dataHolder.size()); -} - -TEST_F(StatsPullerTest, PullTakeTooLong) { - pullData.push_back(createSimpleEvent(1111L, 33)); - - pullSuccess = true; - pullDelayNs = MillisToNano(6); - - vector> dataHolder; - EXPECT_FALSE(puller.Pull(getElapsedRealtimeNs(), &dataHolder)); - ASSERT_EQ(0, dataHolder.size()); -} - -TEST_F(StatsPullerTest, PullTooFast) { - pullData.push_back(createSimpleEvent(1111L, 33)); - - pullSuccess = true; - - vector> dataHolder; - EXPECT_TRUE(puller.Pull(getElapsedRealtimeNs(), &dataHolder)); - ASSERT_EQ(1, dataHolder.size()); - EXPECT_EQ(pullTagId, dataHolder[0]->GetTagId()); - EXPECT_EQ(1111L, dataHolder[0]->GetElapsedTimestampNs()); - ASSERT_EQ(1, dataHolder[0]->size()); - EXPECT_EQ(33, dataHolder[0]->getValues()[0].mValue.int_value); - - pullData.clear(); - pullData.push_back(createSimpleEvent(2222L, 44)); - - pullSuccess = true; - - dataHolder.clear(); - EXPECT_TRUE(puller.Pull(getElapsedRealtimeNs(), &dataHolder)); - ASSERT_EQ(1, dataHolder.size()); - EXPECT_EQ(pullTagId, dataHolder[0]->GetTagId()); - EXPECT_EQ(1111L, dataHolder[0]->GetElapsedTimestampNs()); - ASSERT_EQ(1, dataHolder[0]->size()); - EXPECT_EQ(33, dataHolder[0]->getValues()[0].mValue.int_value); -} - -TEST_F(StatsPullerTest, PullFailsAndTooFast) { - pullData.push_back(createSimpleEvent(1111L, 33)); - - pullSuccess = false; - - vector> dataHolder; - EXPECT_FALSE(puller.Pull(getElapsedRealtimeNs(), &dataHolder)); - ASSERT_EQ(0, dataHolder.size()); - - pullData.clear(); - pullData.push_back(createSimpleEvent(2222L, 44)); - - pullSuccess = true; - - EXPECT_FALSE(puller.Pull(getElapsedRealtimeNs(), &dataHolder)); - ASSERT_EQ(0, dataHolder.size()); -} - -TEST_F(StatsPullerTest, PullSameEventTime) { - pullData.push_back(createSimpleEvent(1111L, 33)); - - pullSuccess = true; - int64_t eventTimeNs = getElapsedRealtimeNs(); - - vector> dataHolder; - EXPECT_TRUE(puller.Pull(eventTimeNs, &dataHolder)); - ASSERT_EQ(1, dataHolder.size()); - EXPECT_EQ(pullTagId, dataHolder[0]->GetTagId()); - EXPECT_EQ(1111L, dataHolder[0]->GetElapsedTimestampNs()); - ASSERT_EQ(1, dataHolder[0]->size()); - EXPECT_EQ(33, dataHolder[0]->getValues()[0].mValue.int_value); - - pullData.clear(); - pullData.push_back(createSimpleEvent(2222L, 44)); - - // Sleep to ensure the cool down expires. - sleep_for(std::chrono::milliseconds(11)); - pullSuccess = true; - - dataHolder.clear(); - EXPECT_TRUE(puller.Pull(eventTimeNs, &dataHolder)); - ASSERT_EQ(1, dataHolder.size()); - EXPECT_EQ(pullTagId, dataHolder[0]->GetTagId()); - EXPECT_EQ(1111L, dataHolder[0]->GetElapsedTimestampNs()); - ASSERT_EQ(1, dataHolder[0]->size()); - EXPECT_EQ(33, dataHolder[0]->getValues()[0].mValue.int_value); -} - -// Test pull takes longer than timeout, 2nd pull happens at same event time -TEST_F(StatsPullerTest, PullTakeTooLongAndPullSameEventTime) { - pullData.push_back(createSimpleEvent(1111L, 33)); - pullSuccess = true; - int64_t eventTimeNs = getElapsedRealtimeNs(); - // timeout is 5ms - pullDelayNs = MillisToNano(6); - - vector> dataHolder; - EXPECT_FALSE(puller.Pull(eventTimeNs, &dataHolder)); - ASSERT_EQ(0, dataHolder.size()); - - // Sleep to ensure the cool down expires. 6ms is taken by the delay, so only 5 is needed here. - sleep_for(std::chrono::milliseconds(5)); - - pullData.clear(); - pullData.push_back(createSimpleEvent(2222L, 44)); - pullDelayNs = 0; - - pullSuccess = true; - dataHolder.clear(); - EXPECT_FALSE(puller.Pull(eventTimeNs, &dataHolder)); - ASSERT_EQ(0, dataHolder.size()); -} - -TEST_F(StatsPullerTest, PullFailsAndPullSameEventTime) { - pullData.push_back(createSimpleEvent(1111L, 33)); - - pullSuccess = false; - int64_t eventTimeNs = getElapsedRealtimeNs(); - - vector> dataHolder; - EXPECT_FALSE(puller.Pull(eventTimeNs, &dataHolder)); - ASSERT_EQ(0, dataHolder.size()); - - // Sleep to ensure the cool down expires. - sleep_for(std::chrono::milliseconds(11)); - - pullData.clear(); - pullData.push_back(createSimpleEvent(2222L, 44)); - - pullSuccess = true; - - EXPECT_FALSE(puller.Pull(eventTimeNs, &dataHolder)); - ASSERT_EQ(0, dataHolder.size()); -} - -} // namespace statsd -} // namespace os -} // namespace android -#else -GTEST_LOG_(INFO) << "This test does nothing.\n"; -#endif diff --git a/bin/tests/external/puller_util_test.cpp b/bin/tests/external/puller_util_test.cpp deleted file mode 100644 index a21dc871..00000000 --- a/bin/tests/external/puller_util_test.cpp +++ /dev/null @@ -1,408 +0,0 @@ -// Copyright (C) 2018 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 "external/puller_util.h" - -#include -#include -#include - -#include - -#include "../metrics/metrics_test_helper.h" -#include "FieldValue.h" -#include "annotations.h" -#include "stats_event.h" -#include "tests/statsd_test_util.h" - -#ifdef __ANDROID__ - -namespace android { -namespace os { -namespace statsd { - -using namespace testing; -using std::shared_ptr; -using std::vector; -/* - * Test merge isolated and host uid - */ -namespace { -const int uidAtomTagId = 100; -const vector additiveFields = {3}; -const int nonUidAtomTagId = 200; -const int timestamp = 1234; -const int isolatedUid1 = 30; -const int isolatedUid2 = 40; -const int isolatedNonAdditiveData = 32; -const int isolatedAdditiveData = 31; -const int hostUid = 20; -const int hostNonAdditiveData = 22; -const int hostAdditiveData = 21; -const int attributionAtomTagId = 300; - -sp makeMockUidMap() { - return makeMockUidMapForOneHost(hostUid, {isolatedUid1, isolatedUid2}); -} - -} // anonymous namespace - -TEST(PullerUtilTest, MergeNoDimension) { - vector> data = { - // 30->22->31 - makeUidLogEvent(uidAtomTagId, timestamp, isolatedUid1, hostNonAdditiveData, - isolatedAdditiveData), - - // 20->22->21 - makeUidLogEvent(uidAtomTagId, timestamp, hostUid, hostNonAdditiveData, - hostAdditiveData), - }; - - sp uidMap = makeMockUidMap(); - mapAndMergeIsolatedUidsToHostUid(data, uidMap, uidAtomTagId, additiveFields); - - ASSERT_EQ(1, (int)data.size()); - const vector* actualFieldValues = &data[0]->getValues(); - ASSERT_EQ(3, actualFieldValues->size()); - EXPECT_EQ(hostUid, actualFieldValues->at(0).mValue.int_value); - EXPECT_EQ(hostNonAdditiveData, actualFieldValues->at(1).mValue.int_value); - EXPECT_EQ(isolatedAdditiveData + hostAdditiveData, actualFieldValues->at(2).mValue.int_value); -} - -TEST(PullerUtilTest, MergeWithDimension) { - vector> data = { - // 30->32->31 - makeUidLogEvent(uidAtomTagId, timestamp, isolatedUid1, isolatedNonAdditiveData, - isolatedAdditiveData), - - // 20->32->21 - makeUidLogEvent(uidAtomTagId, timestamp, hostUid, isolatedNonAdditiveData, - hostAdditiveData), - - // 20->22->21 - makeUidLogEvent(uidAtomTagId, timestamp, hostUid, hostNonAdditiveData, - hostAdditiveData), - }; - - sp uidMap = makeMockUidMap(); - mapAndMergeIsolatedUidsToHostUid(data, uidMap, uidAtomTagId, additiveFields); - - ASSERT_EQ(2, (int)data.size()); - - const vector* actualFieldValues = &data[0]->getValues(); - ASSERT_EQ(3, actualFieldValues->size()); - EXPECT_EQ(hostUid, actualFieldValues->at(0).mValue.int_value); - EXPECT_EQ(hostNonAdditiveData, actualFieldValues->at(1).mValue.int_value); - EXPECT_EQ(hostAdditiveData, actualFieldValues->at(2).mValue.int_value); - - actualFieldValues = &data[1]->getValues(); - ASSERT_EQ(3, actualFieldValues->size()); - EXPECT_EQ(hostUid, actualFieldValues->at(0).mValue.int_value); - EXPECT_EQ(isolatedNonAdditiveData, actualFieldValues->at(1).mValue.int_value); - EXPECT_EQ(hostAdditiveData + isolatedAdditiveData, actualFieldValues->at(2).mValue.int_value); -} - -TEST(PullerUtilTest, NoMergeHostUidOnly) { - vector> data = { - // 20->32->31 - makeUidLogEvent(uidAtomTagId, timestamp, hostUid, isolatedNonAdditiveData, - isolatedAdditiveData), - - // 20->22->21 - makeUidLogEvent(uidAtomTagId, timestamp, hostUid, hostNonAdditiveData, - hostAdditiveData), - }; - - sp uidMap = makeMockUidMap(); - mapAndMergeIsolatedUidsToHostUid(data, uidMap, uidAtomTagId, additiveFields); - - ASSERT_EQ(2, (int)data.size()); - - const vector* actualFieldValues = &data[0]->getValues(); - ASSERT_EQ(3, actualFieldValues->size()); - EXPECT_EQ(hostUid, actualFieldValues->at(0).mValue.int_value); - EXPECT_EQ(hostNonAdditiveData, actualFieldValues->at(1).mValue.int_value); - EXPECT_EQ(hostAdditiveData, actualFieldValues->at(2).mValue.int_value); - - actualFieldValues = &data[1]->getValues(); - ASSERT_EQ(3, actualFieldValues->size()); - EXPECT_EQ(hostUid, actualFieldValues->at(0).mValue.int_value); - EXPECT_EQ(isolatedNonAdditiveData, actualFieldValues->at(1).mValue.int_value); - EXPECT_EQ(isolatedAdditiveData, actualFieldValues->at(2).mValue.int_value); -} - -TEST(PullerUtilTest, IsolatedUidOnly) { - vector> data = { - // 30->32->31 - makeUidLogEvent(uidAtomTagId, timestamp, isolatedUid1, isolatedNonAdditiveData, - isolatedAdditiveData), - - // 30->22->21 - makeUidLogEvent(uidAtomTagId, timestamp, isolatedUid1, hostNonAdditiveData, - hostAdditiveData), - }; - - sp uidMap = makeMockUidMap(); - mapAndMergeIsolatedUidsToHostUid(data, uidMap, uidAtomTagId, additiveFields); - - ASSERT_EQ(2, (int)data.size()); - - // 20->32->31 - const vector* actualFieldValues = &data[0]->getValues(); - ASSERT_EQ(3, actualFieldValues->size()); - EXPECT_EQ(hostUid, actualFieldValues->at(0).mValue.int_value); - EXPECT_EQ(hostNonAdditiveData, actualFieldValues->at(1).mValue.int_value); - EXPECT_EQ(hostAdditiveData, actualFieldValues->at(2).mValue.int_value); - - // 20->22->21 - actualFieldValues = &data[1]->getValues(); - ASSERT_EQ(3, actualFieldValues->size()); - EXPECT_EQ(hostUid, actualFieldValues->at(0).mValue.int_value); - EXPECT_EQ(isolatedNonAdditiveData, actualFieldValues->at(1).mValue.int_value); - EXPECT_EQ(isolatedAdditiveData, actualFieldValues->at(2).mValue.int_value); -} - -TEST(PullerUtilTest, MultipleIsolatedUidToOneHostUid) { - vector> data = { - // 30->32->31 - makeUidLogEvent(uidAtomTagId, timestamp, isolatedUid1, isolatedNonAdditiveData, - isolatedAdditiveData), - - // 31->32->21 - makeUidLogEvent(uidAtomTagId, timestamp, isolatedUid2, isolatedNonAdditiveData, - hostAdditiveData), - - // 20->32->21 - makeUidLogEvent(uidAtomTagId, timestamp, hostUid, isolatedNonAdditiveData, - hostAdditiveData), - }; - - sp uidMap = makeMockUidMap(); - mapAndMergeIsolatedUidsToHostUid(data, uidMap, uidAtomTagId, additiveFields); - - ASSERT_EQ(1, (int)data.size()); - - const vector* actualFieldValues = &data[0]->getValues(); - ASSERT_EQ(3, actualFieldValues->size()); - EXPECT_EQ(hostUid, actualFieldValues->at(0).mValue.int_value); - EXPECT_EQ(isolatedNonAdditiveData, actualFieldValues->at(1).mValue.int_value); - EXPECT_EQ(isolatedAdditiveData + hostAdditiveData + hostAdditiveData, - actualFieldValues->at(2).mValue.int_value); -} - -TEST(PullerUtilTest, NoNeedToMerge) { - vector> data = { - // 32->31 - CreateTwoValueLogEvent(nonUidAtomTagId, timestamp, isolatedNonAdditiveData, - isolatedAdditiveData), - - // 22->21 - CreateTwoValueLogEvent(nonUidAtomTagId, timestamp, hostNonAdditiveData, - hostAdditiveData), - - }; - - sp uidMap = makeMockUidMap(); - mapAndMergeIsolatedUidsToHostUid(data, uidMap, nonUidAtomTagId, {} /*no additive fields*/); - - ASSERT_EQ(2, (int)data.size()); - - const vector* actualFieldValues = &data[0]->getValues(); - ASSERT_EQ(2, actualFieldValues->size()); - EXPECT_EQ(isolatedNonAdditiveData, actualFieldValues->at(0).mValue.int_value); - EXPECT_EQ(isolatedAdditiveData, actualFieldValues->at(1).mValue.int_value); - - actualFieldValues = &data[1]->getValues(); - ASSERT_EQ(2, actualFieldValues->size()); - EXPECT_EQ(hostNonAdditiveData, actualFieldValues->at(0).mValue.int_value); - EXPECT_EQ(hostAdditiveData, actualFieldValues->at(1).mValue.int_value); -} - -TEST(PullerUtilTest, MergeNoDimensionAttributionChain) { - vector> data = { - // 30->tag1->400->tag2->22->31 - makeAttributionLogEvent(attributionAtomTagId, timestamp, {isolatedUid1, 400}, - {"tag1", "tag2"}, hostNonAdditiveData, isolatedAdditiveData), - - // 20->tag1->400->tag2->22->21 - makeAttributionLogEvent(attributionAtomTagId, timestamp, {hostUid, 400}, - {"tag1", "tag2"}, hostNonAdditiveData, hostAdditiveData), - }; - - sp uidMap = makeMockUidMap(); - mapAndMergeIsolatedUidsToHostUid(data, uidMap, attributionAtomTagId, additiveFields); - - ASSERT_EQ(1, (int)data.size()); - const vector* actualFieldValues = &data[0]->getValues(); - ASSERT_EQ(6, actualFieldValues->size()); - EXPECT_EQ(hostUid, actualFieldValues->at(0).mValue.int_value); - EXPECT_EQ("tag1", actualFieldValues->at(1).mValue.str_value); - EXPECT_EQ(400, actualFieldValues->at(2).mValue.int_value); - EXPECT_EQ("tag2", actualFieldValues->at(3).mValue.str_value); - EXPECT_EQ(hostNonAdditiveData, actualFieldValues->at(4).mValue.int_value); - EXPECT_EQ(isolatedAdditiveData + hostAdditiveData, actualFieldValues->at(5).mValue.int_value); -} - -TEST(PullerUtilTest, MergeWithDimensionAttributionChain) { - vector> data = { - // 200->tag1->30->tag2->32->31 - makeAttributionLogEvent(attributionAtomTagId, timestamp, {200, isolatedUid1}, - {"tag1", "tag2"}, isolatedNonAdditiveData, - isolatedAdditiveData), - - // 200->tag1->20->tag2->32->21 - makeAttributionLogEvent(attributionAtomTagId, timestamp, {200, hostUid}, - {"tag1", "tag2"}, isolatedNonAdditiveData, hostAdditiveData), - - // 200->tag1->20->tag2->22->21 - makeAttributionLogEvent(attributionAtomTagId, timestamp, {200, hostUid}, - {"tag1", "tag2"}, hostNonAdditiveData, hostAdditiveData), - }; - - sp uidMap = makeMockUidMap(); - mapAndMergeIsolatedUidsToHostUid(data, uidMap, attributionAtomTagId, additiveFields); - - ASSERT_EQ(2, (int)data.size()); - - const vector* actualFieldValues = &data[0]->getValues(); - ASSERT_EQ(6, actualFieldValues->size()); - EXPECT_EQ(200, actualFieldValues->at(0).mValue.int_value); - EXPECT_EQ("tag1", actualFieldValues->at(1).mValue.str_value); - EXPECT_EQ(hostUid, actualFieldValues->at(2).mValue.int_value); - EXPECT_EQ("tag2", actualFieldValues->at(3).mValue.str_value); - EXPECT_EQ(hostNonAdditiveData, actualFieldValues->at(4).mValue.int_value); - EXPECT_EQ(hostAdditiveData, actualFieldValues->at(5).mValue.int_value); - - actualFieldValues = &data[1]->getValues(); - ASSERT_EQ(6, actualFieldValues->size()); - EXPECT_EQ(200, actualFieldValues->at(0).mValue.int_value); - EXPECT_EQ("tag1", actualFieldValues->at(1).mValue.str_value); - EXPECT_EQ(hostUid, actualFieldValues->at(2).mValue.int_value); - EXPECT_EQ("tag2", actualFieldValues->at(3).mValue.str_value); - EXPECT_EQ(isolatedNonAdditiveData, actualFieldValues->at(4).mValue.int_value); - EXPECT_EQ(hostAdditiveData + isolatedAdditiveData, actualFieldValues->at(5).mValue.int_value); -} - -TEST(PullerUtilTest, NoMergeHostUidOnlyAttributionChain) { - vector> data = { - // 20->tag1->400->tag2->32->31 - makeAttributionLogEvent(attributionAtomTagId, timestamp, {hostUid, 400}, - {"tag1", "tag2"}, isolatedNonAdditiveData, - isolatedAdditiveData), - - // 20->tag1->400->tag2->22->21 - makeAttributionLogEvent(attributionAtomTagId, timestamp, {hostUid, 400}, - {"tag1", "tag2"}, hostNonAdditiveData, hostAdditiveData), - }; - - sp uidMap = makeMockUidMap(); - mapAndMergeIsolatedUidsToHostUid(data, uidMap, attributionAtomTagId, additiveFields); - - ASSERT_EQ(2, (int)data.size()); - - const vector* actualFieldValues = &data[0]->getValues(); - ASSERT_EQ(6, actualFieldValues->size()); - EXPECT_EQ(hostUid, actualFieldValues->at(0).mValue.int_value); - EXPECT_EQ("tag1", actualFieldValues->at(1).mValue.str_value); - EXPECT_EQ(400, actualFieldValues->at(2).mValue.int_value); - EXPECT_EQ("tag2", actualFieldValues->at(3).mValue.str_value); - EXPECT_EQ(hostNonAdditiveData, actualFieldValues->at(4).mValue.int_value); - EXPECT_EQ(hostAdditiveData, actualFieldValues->at(5).mValue.int_value); - - actualFieldValues = &data[1]->getValues(); - ASSERT_EQ(6, actualFieldValues->size()); - EXPECT_EQ(hostUid, actualFieldValues->at(0).mValue.int_value); - EXPECT_EQ("tag1", actualFieldValues->at(1).mValue.str_value); - EXPECT_EQ(400, actualFieldValues->at(2).mValue.int_value); - EXPECT_EQ("tag2", actualFieldValues->at(3).mValue.str_value); - EXPECT_EQ(isolatedNonAdditiveData, actualFieldValues->at(4).mValue.int_value); - EXPECT_EQ(isolatedAdditiveData, actualFieldValues->at(5).mValue.int_value); -} - -TEST(PullerUtilTest, IsolatedUidOnlyAttributionChain) { - vector> data = { - // 30->tag1->400->tag2->32->31 - makeAttributionLogEvent(attributionAtomTagId, timestamp, {isolatedUid1, 400}, - {"tag1", "tag2"}, isolatedNonAdditiveData, - isolatedAdditiveData), - - // 30->tag1->400->tag2->22->21 - makeAttributionLogEvent(attributionAtomTagId, timestamp, {isolatedUid1, 400}, - {"tag1", "tag2"}, hostNonAdditiveData, hostAdditiveData), - }; - - sp uidMap = makeMockUidMap(); - mapAndMergeIsolatedUidsToHostUid(data, uidMap, attributionAtomTagId, additiveFields); - - ASSERT_EQ(2, (int)data.size()); - - // 20->tag1->400->tag2->32->31 - const vector* actualFieldValues = &data[0]->getValues(); - ASSERT_EQ(6, actualFieldValues->size()); - EXPECT_EQ(hostUid, actualFieldValues->at(0).mValue.int_value); - EXPECT_EQ("tag1", actualFieldValues->at(1).mValue.str_value); - EXPECT_EQ(400, actualFieldValues->at(2).mValue.int_value); - EXPECT_EQ("tag2", actualFieldValues->at(3).mValue.str_value); - EXPECT_EQ(hostNonAdditiveData, actualFieldValues->at(4).mValue.int_value); - EXPECT_EQ(hostAdditiveData, actualFieldValues->at(5).mValue.int_value); - - // 20->tag1->400->tag2->22->21 - actualFieldValues = &data[1]->getValues(); - ASSERT_EQ(6, actualFieldValues->size()); - EXPECT_EQ(hostUid, actualFieldValues->at(0).mValue.int_value); - EXPECT_EQ("tag1", actualFieldValues->at(1).mValue.str_value); - EXPECT_EQ(400, actualFieldValues->at(2).mValue.int_value); - EXPECT_EQ("tag2", actualFieldValues->at(3).mValue.str_value); - EXPECT_EQ(isolatedNonAdditiveData, actualFieldValues->at(4).mValue.int_value); - EXPECT_EQ(isolatedAdditiveData, actualFieldValues->at(5).mValue.int_value); -} - -TEST(PullerUtilTest, MultipleIsolatedUidToOneHostUidAttributionChain) { - vector> data = { - // 30->tag1->400->tag2->32->31 - makeAttributionLogEvent(attributionAtomTagId, timestamp, {isolatedUid1, 400}, - {"tag1", "tag2"}, isolatedNonAdditiveData, - isolatedAdditiveData), - - // 31->tag1->400->tag2->32->21 - makeAttributionLogEvent(attributionAtomTagId, timestamp, {isolatedUid2, 400}, - {"tag1", "tag2"}, isolatedNonAdditiveData, hostAdditiveData), - - // 20->tag1->400->tag2->32->21 - makeAttributionLogEvent(attributionAtomTagId, timestamp, {hostUid, 400}, - {"tag1", "tag2"}, isolatedNonAdditiveData, hostAdditiveData), - }; - - sp uidMap = makeMockUidMap(); - mapAndMergeIsolatedUidsToHostUid(data, uidMap, attributionAtomTagId, additiveFields); - - ASSERT_EQ(1, (int)data.size()); - - const vector* actualFieldValues = &data[0]->getValues(); - ASSERT_EQ(6, actualFieldValues->size()); - EXPECT_EQ(hostUid, actualFieldValues->at(0).mValue.int_value); - EXPECT_EQ("tag1", actualFieldValues->at(1).mValue.str_value); - EXPECT_EQ(400, actualFieldValues->at(2).mValue.int_value); - EXPECT_EQ("tag2", actualFieldValues->at(3).mValue.str_value); - EXPECT_EQ(isolatedNonAdditiveData, actualFieldValues->at(4).mValue.int_value); - EXPECT_EQ(isolatedAdditiveData + hostAdditiveData + hostAdditiveData, - actualFieldValues->at(5).mValue.int_value); -} - -} // namespace statsd -} // namespace os -} // namespace android -#else -GTEST_LOG_(INFO) << "This test does nothing.\n"; -#endif diff --git a/bin/tests/guardrail/StatsdStats_test.cpp b/bin/tests/guardrail/StatsdStats_test.cpp deleted file mode 100644 index 5a824c53..00000000 --- a/bin/tests/guardrail/StatsdStats_test.cpp +++ /dev/null @@ -1,553 +0,0 @@ -// 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 "src/guardrail/StatsdStats.h" -#include "statslog_statsdtest.h" -#include "tests/statsd_test_util.h" - -#include -#include - -#ifdef __ANDROID__ - -namespace android { -namespace os { -namespace statsd { - -using std::vector; - -TEST(StatsdStatsTest, TestValidConfigAdd) { - StatsdStats stats; - ConfigKey key(0, 12345); - const int metricsCount = 10; - const int conditionsCount = 20; - const int matchersCount = 30; - const int alertsCount = 10; - stats.noteConfigReceived(key, metricsCount, conditionsCount, matchersCount, alertsCount, {}, - true /*valid config*/); - vector output; - stats.dumpStats(&output, false /*reset stats*/); - - StatsdStatsReport report; - bool good = report.ParseFromArray(&output[0], output.size()); - EXPECT_TRUE(good); - ASSERT_EQ(1, report.config_stats_size()); - const auto& configReport = report.config_stats(0); - EXPECT_EQ(0, configReport.uid()); - EXPECT_EQ(12345, configReport.id()); - EXPECT_EQ(metricsCount, configReport.metric_count()); - EXPECT_EQ(conditionsCount, configReport.condition_count()); - EXPECT_EQ(matchersCount, configReport.matcher_count()); - EXPECT_EQ(alertsCount, configReport.alert_count()); - EXPECT_EQ(true, configReport.is_valid()); - EXPECT_FALSE(configReport.has_deletion_time_sec()); -} - -TEST(StatsdStatsTest, TestInvalidConfigAdd) { - StatsdStats stats; - ConfigKey key(0, 12345); - const int metricsCount = 10; - const int conditionsCount = 20; - const int matchersCount = 30; - const int alertsCount = 10; - stats.noteConfigReceived(key, metricsCount, conditionsCount, matchersCount, alertsCount, {}, - false /*bad config*/); - vector output; - stats.dumpStats(&output, false); - - StatsdStatsReport report; - bool good = report.ParseFromArray(&output[0], output.size()); - EXPECT_TRUE(good); - ASSERT_EQ(1, report.config_stats_size()); - const auto& configReport = report.config_stats(0); - // The invalid config should be put into icebox with a deletion time. - EXPECT_TRUE(configReport.has_deletion_time_sec()); -} - -TEST(StatsdStatsTest, TestConfigRemove) { - StatsdStats stats; - ConfigKey key(0, 12345); - const int metricsCount = 10; - const int conditionsCount = 20; - const int matchersCount = 30; - const int alertsCount = 10; - stats.noteConfigReceived(key, metricsCount, conditionsCount, matchersCount, alertsCount, {}, - true); - vector output; - stats.dumpStats(&output, false); - StatsdStatsReport report; - bool good = report.ParseFromArray(&output[0], output.size()); - EXPECT_TRUE(good); - ASSERT_EQ(1, report.config_stats_size()); - const auto& configReport = report.config_stats(0); - EXPECT_FALSE(configReport.has_deletion_time_sec()); - - stats.noteConfigRemoved(key); - stats.dumpStats(&output, false); - good = report.ParseFromArray(&output[0], output.size()); - EXPECT_TRUE(good); - ASSERT_EQ(1, report.config_stats_size()); - const auto& configReport2 = report.config_stats(0); - EXPECT_TRUE(configReport2.has_deletion_time_sec()); -} - -TEST(StatsdStatsTest, TestSubStats) { - StatsdStats stats; - ConfigKey key(0, 12345); - stats.noteConfigReceived(key, 2, 3, 4, 5, {std::make_pair(123, 456)}, true); - - stats.noteMatcherMatched(key, StringToId("matcher1")); - stats.noteMatcherMatched(key, StringToId("matcher1")); - stats.noteMatcherMatched(key, StringToId("matcher2")); - - stats.noteConditionDimensionSize(key, StringToId("condition1"), 250); - stats.noteConditionDimensionSize(key, StringToId("condition1"), 240); - - stats.noteMetricDimensionSize(key, StringToId("metric1"), 201); - stats.noteMetricDimensionSize(key, StringToId("metric1"), 202); - - stats.noteAnomalyDeclared(key, StringToId("alert1")); - stats.noteAnomalyDeclared(key, StringToId("alert1")); - stats.noteAnomalyDeclared(key, StringToId("alert2")); - - // broadcast-> 2 - stats.noteBroadcastSent(key); - stats.noteBroadcastSent(key); - - // data drop -> 1 - stats.noteDataDropped(key, 123); - - // dump report -> 3 - stats.noteMetricsReportSent(key, 0); - stats.noteMetricsReportSent(key, 0); - stats.noteMetricsReportSent(key, 0); - - // activation_time_sec -> 2 - stats.noteActiveStatusChanged(key, true); - stats.noteActiveStatusChanged(key, true); - - // deactivation_time_sec -> 1 - stats.noteActiveStatusChanged(key, false); - - vector output; - stats.dumpStats(&output, true); // Dump and reset stats - StatsdStatsReport report; - bool good = report.ParseFromArray(&output[0], output.size()); - EXPECT_TRUE(good); - ASSERT_EQ(1, report.config_stats_size()); - const auto& configReport = report.config_stats(0); - ASSERT_EQ(2, configReport.broadcast_sent_time_sec_size()); - ASSERT_EQ(1, configReport.data_drop_time_sec_size()); - ASSERT_EQ(1, configReport.data_drop_bytes_size()); - EXPECT_EQ(123, configReport.data_drop_bytes(0)); - ASSERT_EQ(3, configReport.dump_report_time_sec_size()); - ASSERT_EQ(3, configReport.dump_report_data_size_size()); - ASSERT_EQ(2, configReport.activation_time_sec_size()); - ASSERT_EQ(1, configReport.deactivation_time_sec_size()); - ASSERT_EQ(1, configReport.annotation_size()); - EXPECT_EQ(123, configReport.annotation(0).field_int64()); - EXPECT_EQ(456, configReport.annotation(0).field_int32()); - - ASSERT_EQ(2, configReport.matcher_stats_size()); - // matcher1 is the first in the list - if (configReport.matcher_stats(0).id() == StringToId("matcher1")) { - EXPECT_EQ(2, configReport.matcher_stats(0).matched_times()); - EXPECT_EQ(1, configReport.matcher_stats(1).matched_times()); - EXPECT_EQ(StringToId("matcher2"), configReport.matcher_stats(1).id()); - } else { - // matcher1 is the second in the list. - EXPECT_EQ(1, configReport.matcher_stats(0).matched_times()); - EXPECT_EQ(StringToId("matcher2"), configReport.matcher_stats(0).id()); - - EXPECT_EQ(2, configReport.matcher_stats(1).matched_times()); - EXPECT_EQ(StringToId("matcher1"), configReport.matcher_stats(1).id()); - } - - ASSERT_EQ(2, configReport.alert_stats_size()); - bool alert1first = configReport.alert_stats(0).id() == StringToId("alert1"); - EXPECT_EQ(StringToId("alert1"), configReport.alert_stats(alert1first ? 0 : 1).id()); - EXPECT_EQ(2, configReport.alert_stats(alert1first ? 0 : 1).alerted_times()); - EXPECT_EQ(StringToId("alert2"), configReport.alert_stats(alert1first ? 1 : 0).id()); - EXPECT_EQ(1, configReport.alert_stats(alert1first ? 1 : 0).alerted_times()); - - ASSERT_EQ(1, configReport.condition_stats_size()); - EXPECT_EQ(StringToId("condition1"), configReport.condition_stats(0).id()); - EXPECT_EQ(250, configReport.condition_stats(0).max_tuple_counts()); - - ASSERT_EQ(1, configReport.metric_stats_size()); - EXPECT_EQ(StringToId("metric1"), configReport.metric_stats(0).id()); - EXPECT_EQ(202, configReport.metric_stats(0).max_tuple_counts()); - - // after resetting the stats, some new events come - stats.noteMatcherMatched(key, StringToId("matcher99")); - stats.noteConditionDimensionSize(key, StringToId("condition99"), 300); - stats.noteMetricDimensionSize(key, StringToId("metric99tion99"), 270); - stats.noteAnomalyDeclared(key, StringToId("alert99")); - - // now the config stats should only contain the stats about the new event. - stats.dumpStats(&output, false); - good = report.ParseFromArray(&output[0], output.size()); - EXPECT_TRUE(good); - ASSERT_EQ(1, report.config_stats_size()); - const auto& configReport2 = report.config_stats(0); - ASSERT_EQ(1, configReport2.matcher_stats_size()); - EXPECT_EQ(StringToId("matcher99"), configReport2.matcher_stats(0).id()); - EXPECT_EQ(1, configReport2.matcher_stats(0).matched_times()); - - ASSERT_EQ(1, configReport2.condition_stats_size()); - EXPECT_EQ(StringToId("condition99"), configReport2.condition_stats(0).id()); - EXPECT_EQ(300, configReport2.condition_stats(0).max_tuple_counts()); - - ASSERT_EQ(1, configReport2.metric_stats_size()); - EXPECT_EQ(StringToId("metric99tion99"), configReport2.metric_stats(0).id()); - EXPECT_EQ(270, configReport2.metric_stats(0).max_tuple_counts()); - - ASSERT_EQ(1, configReport2.alert_stats_size()); - EXPECT_EQ(StringToId("alert99"), configReport2.alert_stats(0).id()); - EXPECT_EQ(1, configReport2.alert_stats(0).alerted_times()); -} - -TEST(StatsdStatsTest, TestAtomLog) { - StatsdStats stats; - time_t now = time(nullptr); - // old event, we get it from the stats buffer. should be ignored. - stats.noteAtomLogged(util::SENSOR_STATE_CHANGED, 1000); - - stats.noteAtomLogged(util::SENSOR_STATE_CHANGED, now + 1); - stats.noteAtomLogged(util::SENSOR_STATE_CHANGED, now + 2); - stats.noteAtomLogged(util::APP_CRASH_OCCURRED, now + 3); - - vector output; - stats.dumpStats(&output, false); - StatsdStatsReport report; - bool good = report.ParseFromArray(&output[0], output.size()); - EXPECT_TRUE(good); - - ASSERT_EQ(2, report.atom_stats_size()); - bool sensorAtomGood = false; - bool dropboxAtomGood = false; - - for (const auto& atomStats : report.atom_stats()) { - if (atomStats.tag() == util::SENSOR_STATE_CHANGED && atomStats.count() == 3) { - sensorAtomGood = true; - } - if (atomStats.tag() == util::APP_CRASH_OCCURRED && atomStats.count() == 1) { - dropboxAtomGood = true; - } - } - - EXPECT_TRUE(dropboxAtomGood); - EXPECT_TRUE(sensorAtomGood); -} - -TEST(StatsdStatsTest, TestNonPlatformAtomLog) { - StatsdStats stats; - time_t now = time(nullptr); - int newAtom1 = StatsdStats::kMaxPushedAtomId + 1; - int newAtom2 = StatsdStats::kMaxPushedAtomId + 2; - - stats.noteAtomLogged(newAtom1, now + 1); - stats.noteAtomLogged(newAtom1, now + 2); - stats.noteAtomLogged(newAtom2, now + 3); - - vector output; - stats.dumpStats(&output, false); - StatsdStatsReport report; - bool good = report.ParseFromArray(&output[0], output.size()); - EXPECT_TRUE(good); - - ASSERT_EQ(2, report.atom_stats_size()); - bool newAtom1Good = false; - bool newAtom2Good = false; - - for (const auto& atomStats : report.atom_stats()) { - if (atomStats.tag() == newAtom1 && atomStats.count() == 2) { - newAtom1Good = true; - } - if (atomStats.tag() == newAtom2 && atomStats.count() == 1) { - newAtom2Good = true; - } - } - - EXPECT_TRUE(newAtom1Good); - EXPECT_TRUE(newAtom2Good); -} - -TEST(StatsdStatsTest, TestPullAtomStats) { - StatsdStats stats; - - stats.updateMinPullIntervalSec(util::DISK_SPACE, 3333L); - stats.updateMinPullIntervalSec(util::DISK_SPACE, 2222L); - stats.updateMinPullIntervalSec(util::DISK_SPACE, 4444L); - - stats.notePull(util::DISK_SPACE); - stats.notePullTime(util::DISK_SPACE, 1111L); - stats.notePullDelay(util::DISK_SPACE, 1111L); - stats.notePull(util::DISK_SPACE); - stats.notePullTime(util::DISK_SPACE, 3333L); - stats.notePullDelay(util::DISK_SPACE, 3335L); - stats.notePull(util::DISK_SPACE); - stats.notePullFromCache(util::DISK_SPACE); - stats.notePullerCallbackRegistrationChanged(util::DISK_SPACE, true); - stats.notePullerCallbackRegistrationChanged(util::DISK_SPACE, false); - stats.notePullerCallbackRegistrationChanged(util::DISK_SPACE, true); - stats.notePullBinderCallFailed(util::DISK_SPACE); - stats.notePullUidProviderNotFound(util::DISK_SPACE); - stats.notePullerNotFound(util::DISK_SPACE); - stats.notePullerNotFound(util::DISK_SPACE); - stats.notePullTimeout(util::DISK_SPACE, 3000L, 6000L); - stats.notePullTimeout(util::DISK_SPACE, 4000L, 7000L); - - vector output; - stats.dumpStats(&output, false); - StatsdStatsReport report; - bool good = report.ParseFromArray(&output[0], output.size()); - EXPECT_TRUE(good); - - ASSERT_EQ(1, report.pulled_atom_stats_size()); - - EXPECT_EQ(util::DISK_SPACE, report.pulled_atom_stats(0).atom_id()); - EXPECT_EQ(3, report.pulled_atom_stats(0).total_pull()); - EXPECT_EQ(1, report.pulled_atom_stats(0).total_pull_from_cache()); - EXPECT_EQ(2222L, report.pulled_atom_stats(0).min_pull_interval_sec()); - EXPECT_EQ(2222L, report.pulled_atom_stats(0).average_pull_time_nanos()); - EXPECT_EQ(3333L, report.pulled_atom_stats(0).max_pull_time_nanos()); - EXPECT_EQ(2223L, report.pulled_atom_stats(0).average_pull_delay_nanos()); - EXPECT_EQ(3335L, report.pulled_atom_stats(0).max_pull_delay_nanos()); - EXPECT_EQ(2L, report.pulled_atom_stats(0).registered_count()); - EXPECT_EQ(1L, report.pulled_atom_stats(0).unregistered_count()); - EXPECT_EQ(1L, report.pulled_atom_stats(0).binder_call_failed()); - EXPECT_EQ(1L, report.pulled_atom_stats(0).failed_uid_provider_not_found()); - EXPECT_EQ(2L, report.pulled_atom_stats(0).puller_not_found()); - ASSERT_EQ(2, report.pulled_atom_stats(0).pull_atom_metadata_size()); - EXPECT_EQ(3000L, report.pulled_atom_stats(0).pull_atom_metadata(0).pull_timeout_uptime_millis()); - EXPECT_EQ(4000L, report.pulled_atom_stats(0).pull_atom_metadata(1).pull_timeout_uptime_millis()); - EXPECT_EQ(6000L, report.pulled_atom_stats(0).pull_atom_metadata(0) - .pull_timeout_elapsed_millis()); - EXPECT_EQ(7000L, report.pulled_atom_stats(0).pull_atom_metadata(1) - .pull_timeout_elapsed_millis()); -} - -TEST(StatsdStatsTest, TestAtomMetricsStats) { - StatsdStats stats; - time_t now = time(nullptr); - // old event, we get it from the stats buffer. should be ignored. - stats.noteBucketDropped(10000000000LL); - - stats.noteLateLogEvent(10000000000LL, 10L); - stats.noteLateLogEvent(10000000000LL, 50L); - - stats.noteBucketBoundaryDelayNs(10000000000LL, -1L); - stats.noteBucketBoundaryDelayNs(10000000000LL, -10L); - stats.noteBucketBoundaryDelayNs(10000000000LL, 2L); - - stats.noteBucketBoundaryDelayNs(10000000001LL, 1L); - - vector output; - stats.dumpStats(&output, false); - StatsdStatsReport report; - bool good = report.ParseFromArray(&output[0], output.size()); - EXPECT_TRUE(good); - - ASSERT_EQ(2, report.atom_metric_stats().size()); - - auto atomStats = report.atom_metric_stats(0); - EXPECT_EQ(10000000000LL, atomStats.metric_id()); - EXPECT_EQ(1L, atomStats.bucket_dropped()); - EXPECT_EQ(-10L, atomStats.min_bucket_boundary_delay_ns()); - EXPECT_EQ(2L, atomStats.max_bucket_boundary_delay_ns()); - EXPECT_EQ(2L, atomStats.late_log_event()); - EXPECT_EQ(60L, atomStats.sum_late_log_event_extra_duration_ns()); - EXPECT_EQ(50L, atomStats.max_late_log_event_extra_duration_ns()); - - auto atomStats2 = report.atom_metric_stats(1); - EXPECT_EQ(10000000001LL, atomStats2.metric_id()); - EXPECT_EQ(0L, atomStats2.bucket_dropped()); - EXPECT_EQ(0L, atomStats2.min_bucket_boundary_delay_ns()); - EXPECT_EQ(1L, atomStats2.max_bucket_boundary_delay_ns()); - EXPECT_EQ(0L, atomStats2.late_log_event()); - EXPECT_EQ(0L, atomStats2.sum_late_log_event_extra_duration_ns()); - EXPECT_EQ(0L, atomStats2.max_late_log_event_extra_duration_ns()); -} - -TEST(StatsdStatsTest, TestAnomalyMonitor) { - StatsdStats stats; - stats.noteRegisteredAnomalyAlarmChanged(); - stats.noteRegisteredAnomalyAlarmChanged(); - - vector output; - stats.dumpStats(&output, false); - StatsdStatsReport report; - bool good = report.ParseFromArray(&output[0], output.size()); - EXPECT_TRUE(good); - - EXPECT_EQ(2, report.anomaly_alarm_stats().alarms_registered()); -} - -TEST(StatsdStatsTest, TestTimestampThreshold) { - StatsdStats stats; - vector timestamps; - for (int i = 0; i < StatsdStats::kMaxTimestampCount; i++) { - timestamps.push_back(i); - } - ConfigKey key(0, 12345); - stats.noteConfigReceived(key, 2, 3, 4, 5, {}, true); - - for (int i = 0; i < StatsdStats::kMaxTimestampCount; i++) { - stats.noteDataDropped(key, timestamps[i]); - stats.noteBroadcastSent(key, timestamps[i]); - stats.noteMetricsReportSent(key, 0, timestamps[i]); - stats.noteActiveStatusChanged(key, true, timestamps[i]); - stats.noteActiveStatusChanged(key, false, timestamps[i]); - } - - int32_t newTimestamp = 10000; - - // now it should trigger removing oldest timestamp - stats.noteDataDropped(key, 123, 10000); - stats.noteBroadcastSent(key, 10000); - stats.noteMetricsReportSent(key, 0, 10000); - stats.noteActiveStatusChanged(key, true, 10000); - stats.noteActiveStatusChanged(key, false, 10000); - - EXPECT_TRUE(stats.mConfigStats.find(key) != stats.mConfigStats.end()); - const auto& configStats = stats.mConfigStats[key]; - - size_t maxCount = StatsdStats::kMaxTimestampCount; - ASSERT_EQ(maxCount, configStats->broadcast_sent_time_sec.size()); - ASSERT_EQ(maxCount, configStats->data_drop_time_sec.size()); - ASSERT_EQ(maxCount, configStats->dump_report_stats.size()); - ASSERT_EQ(maxCount, configStats->activation_time_sec.size()); - ASSERT_EQ(maxCount, configStats->deactivation_time_sec.size()); - - // the oldest timestamp is the second timestamp in history - EXPECT_EQ(1, configStats->broadcast_sent_time_sec.front()); - EXPECT_EQ(1, configStats->data_drop_bytes.front()); - EXPECT_EQ(1, configStats->dump_report_stats.front().first); - EXPECT_EQ(1, configStats->activation_time_sec.front()); - EXPECT_EQ(1, configStats->deactivation_time_sec.front()); - - // the last timestamp is the newest timestamp. - EXPECT_EQ(newTimestamp, configStats->broadcast_sent_time_sec.back()); - EXPECT_EQ(newTimestamp, configStats->data_drop_time_sec.back()); - EXPECT_EQ(123, configStats->data_drop_bytes.back()); - EXPECT_EQ(newTimestamp, configStats->dump_report_stats.back().first); - EXPECT_EQ(newTimestamp, configStats->activation_time_sec.back()); - EXPECT_EQ(newTimestamp, configStats->deactivation_time_sec.back()); -} - -TEST(StatsdStatsTest, TestSystemServerCrash) { - StatsdStats stats; - vector timestamps; - for (int i = 0; i < StatsdStats::kMaxSystemServerRestarts; i++) { - timestamps.push_back(i); - stats.noteSystemServerRestart(timestamps[i]); - } - vector output; - stats.dumpStats(&output, false); - StatsdStatsReport report; - EXPECT_TRUE(report.ParseFromArray(&output[0], output.size())); - const int maxCount = StatsdStats::kMaxSystemServerRestarts; - ASSERT_EQ(maxCount, (int)report.system_restart_sec_size()); - - stats.noteSystemServerRestart(StatsdStats::kMaxSystemServerRestarts + 1); - output.clear(); - stats.dumpStats(&output, false); - EXPECT_TRUE(report.ParseFromArray(&output[0], output.size())); - ASSERT_EQ(maxCount, (int)report.system_restart_sec_size()); - EXPECT_EQ(StatsdStats::kMaxSystemServerRestarts + 1, report.system_restart_sec(maxCount - 1)); -} - -TEST(StatsdStatsTest, TestActivationBroadcastGuardrailHit) { - StatsdStats stats; - int uid1 = 1; - int uid2 = 2; - stats.noteActivationBroadcastGuardrailHit(uid1, 10); - stats.noteActivationBroadcastGuardrailHit(uid1, 20); - - // Test that we only keep 20 timestamps. - for (int i = 0; i < 100; i++) { - stats.noteActivationBroadcastGuardrailHit(uid2, i); - } - - vector output; - stats.dumpStats(&output, false); - StatsdStatsReport report; - EXPECT_TRUE(report.ParseFromArray(&output[0], output.size())); - - ASSERT_EQ(2, report.activation_guardrail_stats_size()); - bool uid1Good = false; - bool uid2Good = false; - for (const auto& guardrailTimes : report.activation_guardrail_stats()) { - if (uid1 == guardrailTimes.uid()) { - uid1Good = true; - ASSERT_EQ(2, guardrailTimes.guardrail_met_sec_size()); - EXPECT_EQ(10, guardrailTimes.guardrail_met_sec(0)); - EXPECT_EQ(20, guardrailTimes.guardrail_met_sec(1)); - } else if (uid2 == guardrailTimes.uid()) { - int maxCount = StatsdStats::kMaxTimestampCount; - uid2Good = true; - ASSERT_EQ(maxCount, guardrailTimes.guardrail_met_sec_size()); - for (int i = 0; i < maxCount; i++) { - EXPECT_EQ(100 - maxCount + i, guardrailTimes.guardrail_met_sec(i)); - } - } else { - FAIL() << "Unexpected uid."; - } - } - EXPECT_TRUE(uid1Good); - EXPECT_TRUE(uid2Good); -} - -TEST(StatsdStatsTest, TestAtomErrorStats) { - StatsdStats stats; - - int pushAtomTag = 100; - int pullAtomTag = 1000; - int numErrors = 10; - - for (int i = 0; i < numErrors; i++) { - // We must call noteAtomLogged as well because only those pushed atoms - // that have been logged will have stats printed about them in the - // proto. - stats.noteAtomLogged(pushAtomTag, /*timeSec=*/0); - stats.noteAtomError(pushAtomTag, /*pull=*/false); - - stats.noteAtomError(pullAtomTag, /*pull=*/true); - } - - vector output; - stats.dumpStats(&output, false); - StatsdStatsReport report; - EXPECT_TRUE(report.ParseFromArray(&output[0], output.size())); - - // Check error count = numErrors for push atom - ASSERT_EQ(1, report.atom_stats_size()); - const auto& pushedAtomStats = report.atom_stats(0); - EXPECT_EQ(pushAtomTag, pushedAtomStats.tag()); - EXPECT_EQ(numErrors, pushedAtomStats.error_count()); - - // Check error count = numErrors for pull atom - ASSERT_EQ(1, report.pulled_atom_stats_size()); - const auto& pulledAtomStats = report.pulled_atom_stats(0); - EXPECT_EQ(pullAtomTag, pulledAtomStats.atom_id()); - EXPECT_EQ(numErrors, pulledAtomStats.atom_error_count()); -} - -} // namespace statsd -} // namespace os -} // namespace android -#else -GTEST_LOG_(INFO) << "This test does nothing.\n"; -#endif diff --git a/bin/tests/indexed_priority_queue_test.cpp b/bin/tests/indexed_priority_queue_test.cpp deleted file mode 100644 index 3a654565..00000000 --- a/bin/tests/indexed_priority_queue_test.cpp +++ /dev/null @@ -1,235 +0,0 @@ -/* - * 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 "src/anomaly/indexed_priority_queue.h" - -#include - -using namespace android::os::statsd; - -/** struct for template in indexed_priority_queue */ -struct AATest : public RefBase { - AATest(uint32_t val, std::string a, std::string b) : val(val), a(a), b(b) { - } - - const int val; - const std::string a; - const std::string b; - - struct Smaller { - bool operator()(const sp a, const sp b) const { - return (a->val < b->val); - } - }; -}; - -#ifdef __ANDROID__ -TEST(indexed_priority_queue, empty_and_size) { - std::string emptyMetricId; - std::string emptyDimensionId; - indexed_priority_queue ipq; - sp aa4 = new AATest{4, emptyMetricId, emptyDimensionId}; - sp aa8 = new AATest{8, emptyMetricId, emptyDimensionId}; - - ASSERT_EQ(0u, ipq.size()); - EXPECT_TRUE(ipq.empty()); - - ipq.push(aa4); - ASSERT_EQ(1u, ipq.size()); - EXPECT_FALSE(ipq.empty()); - - ipq.push(aa8); - ASSERT_EQ(2u, ipq.size()); - EXPECT_FALSE(ipq.empty()); - - ipq.remove(aa4); - ASSERT_EQ(1u, ipq.size()); - EXPECT_FALSE(ipq.empty()); - - ipq.remove(aa8); - ASSERT_EQ(0u, ipq.size()); - EXPECT_TRUE(ipq.empty()); -} - -TEST(indexed_priority_queue, top) { - std::string emptyMetricId; - std::string emptyDimensionId; - indexed_priority_queue ipq; - sp aa2 = new AATest{2, emptyMetricId, emptyDimensionId}; - sp aa4 = new AATest{4, emptyMetricId, emptyDimensionId}; - sp aa8 = new AATest{8, emptyMetricId, emptyDimensionId}; - sp aa12 = new AATest{12, emptyMetricId, emptyDimensionId}; - sp aa16 = new AATest{16, emptyMetricId, emptyDimensionId}; - sp aa20 = new AATest{20, emptyMetricId, emptyDimensionId}; - - EXPECT_EQ(ipq.top(), nullptr); - - // add 8, 4, 12 - ipq.push(aa8); - EXPECT_EQ(ipq.top(), aa8); - - ipq.push(aa12); - EXPECT_EQ(ipq.top(), aa8); - - ipq.push(aa4); - EXPECT_EQ(ipq.top(), aa4); - - // remove 12, 4 - ipq.remove(aa12); - EXPECT_EQ(ipq.top(), aa4); - - ipq.remove(aa4); - EXPECT_EQ(ipq.top(), aa8); - - // add 16, 2, 20 - ipq.push(aa16); - EXPECT_EQ(ipq.top(), aa8); - - ipq.push(aa2); - EXPECT_EQ(ipq.top(), aa2); - - ipq.push(aa20); - EXPECT_EQ(ipq.top(), aa2); - - // remove 2, 20, 16, 8 - ipq.remove(aa2); - EXPECT_EQ(ipq.top(), aa8); - - ipq.remove(aa20); - EXPECT_EQ(ipq.top(), aa8); - - ipq.remove(aa16); - EXPECT_EQ(ipq.top(), aa8); - - ipq.remove(aa8); - EXPECT_EQ(ipq.top(), nullptr); -} - -TEST(indexed_priority_queue, push_same_aa) { - std::string emptyMetricId; - std::string emptyDimensionId; - indexed_priority_queue ipq; - sp aa4_a = new AATest{4, emptyMetricId, emptyDimensionId}; - sp aa4_b = new AATest{4, emptyMetricId, emptyDimensionId}; - - ipq.push(aa4_a); - ASSERT_EQ(1u, ipq.size()); - EXPECT_TRUE(ipq.contains(aa4_a)); - EXPECT_FALSE(ipq.contains(aa4_b)); - - ipq.push(aa4_a); - ASSERT_EQ(1u, ipq.size()); - EXPECT_TRUE(ipq.contains(aa4_a)); - EXPECT_FALSE(ipq.contains(aa4_b)); - - ipq.push(aa4_b); - ASSERT_EQ(2u, ipq.size()); - EXPECT_TRUE(ipq.contains(aa4_a)); - EXPECT_TRUE(ipq.contains(aa4_b)); -} - -TEST(indexed_priority_queue, remove_nonexistant) { - std::string emptyMetricId; - std::string emptyDimensionId; - indexed_priority_queue ipq; - sp aa4 = new AATest{4, emptyMetricId, emptyDimensionId}; - sp aa5 = new AATest{5, emptyMetricId, emptyDimensionId}; - - ipq.push(aa4); - ipq.remove(aa5); - ASSERT_EQ(1u, ipq.size()); - EXPECT_TRUE(ipq.contains(aa4)); - EXPECT_FALSE(ipq.contains(aa5)); -} - -TEST(indexed_priority_queue, remove_same_aa) { - indexed_priority_queue ipq; - std::string emptyMetricId; - std::string emptyDimensionId; - sp aa4_a = new AATest{4, emptyMetricId, emptyDimensionId}; - sp aa4_b = new AATest{4, emptyMetricId, emptyDimensionId}; - - ipq.push(aa4_a); - ipq.push(aa4_b); - ASSERT_EQ(2u, ipq.size()); - EXPECT_TRUE(ipq.contains(aa4_a)); - EXPECT_TRUE(ipq.contains(aa4_b)); - - ipq.remove(aa4_b); - ASSERT_EQ(1u, ipq.size()); - EXPECT_TRUE(ipq.contains(aa4_a)); - EXPECT_FALSE(ipq.contains(aa4_b)); - - ipq.remove(aa4_a); - ASSERT_EQ(0u, ipq.size()); - EXPECT_FALSE(ipq.contains(aa4_a)); - EXPECT_FALSE(ipq.contains(aa4_b)); -} - -TEST(indexed_priority_queue, nulls) { - indexed_priority_queue ipq; - - EXPECT_TRUE(ipq.empty()); - EXPECT_FALSE(ipq.contains(nullptr)); - - ipq.push(nullptr); - EXPECT_TRUE(ipq.empty()); - EXPECT_FALSE(ipq.contains(nullptr)); - - ipq.remove(nullptr); - EXPECT_TRUE(ipq.empty()); - EXPECT_FALSE(ipq.contains(nullptr)); -} - -TEST(indexed_priority_queue, pop) { - indexed_priority_queue ipq; - std::string emptyMetricId; - std::string emptyDimensionId; - sp a = new AATest{1, emptyMetricId, emptyDimensionId}; - sp b = new AATest{2, emptyMetricId, emptyDimensionId}; - sp c = new AATest{3, emptyMetricId, emptyDimensionId}; - - ipq.push(c); - ipq.push(b); - ipq.push(a); - ASSERT_EQ(3u, ipq.size()); - - ipq.pop(); - ASSERT_EQ(2u, ipq.size()); - EXPECT_FALSE(ipq.contains(a)); - EXPECT_TRUE(ipq.contains(b)); - EXPECT_TRUE(ipq.contains(c)); - - ipq.pop(); - ASSERT_EQ(1u, ipq.size()); - EXPECT_FALSE(ipq.contains(a)); - EXPECT_FALSE(ipq.contains(b)); - EXPECT_TRUE(ipq.contains(c)); - - ipq.pop(); - ASSERT_EQ(0u, ipq.size()); - EXPECT_FALSE(ipq.contains(a)); - EXPECT_FALSE(ipq.contains(b)); - EXPECT_FALSE(ipq.contains(c)); - EXPECT_TRUE(ipq.empty()); - - ipq.pop(); // pop an empty queue - EXPECT_TRUE(ipq.empty()); -} - -#else -GTEST_LOG_(INFO) << "This test does nothing.\n"; -#endif diff --git a/bin/tests/log_event/LogEventQueue_test.cpp b/bin/tests/log_event/LogEventQueue_test.cpp deleted file mode 100644 index a15f95be..00000000 --- a/bin/tests/log_event/LogEventQueue_test.cpp +++ /dev/null @@ -1,115 +0,0 @@ -// Copyright (C) 2019 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 "logd/LogEventQueue.h" - -#include -#include -#include - -#include - -#include "stats_event.h" -#include "tests/statsd_test_util.h" - -namespace android { -namespace os { -namespace statsd { - -using namespace android; -using namespace testing; - -using std::unique_ptr; - -namespace { - -std::unique_ptr makeLogEvent(uint64_t timestampNs) { - AStatsEvent* statsEvent = AStatsEvent_obtain(); - AStatsEvent_setAtomId(statsEvent, 10); - AStatsEvent_overwriteTimestamp(statsEvent, timestampNs); - - std::unique_ptr logEvent = std::make_unique(/*uid=*/0, /*pid=*/0); - parseStatsEventToLogEvent(statsEvent, logEvent.get()); - return logEvent; -} - -} // anonymous namespace - -#ifdef __ANDROID__ -TEST(LogEventQueue_test, TestGoodConsumer) { - LogEventQueue queue(50); - int64_t timeBaseNs = 100; - std::thread writer([&queue, timeBaseNs] { - for (int i = 0; i < 100; i++) { - int64_t oldestEventNs; - bool success = queue.push(makeLogEvent(timeBaseNs + i * 1000), &oldestEventNs); - EXPECT_TRUE(success); - std::this_thread::sleep_for(std::chrono::milliseconds(1)); - } - }); - - std::thread reader([&queue, timeBaseNs] { - for (int i = 0; i < 100; i++) { - auto event = queue.waitPop(); - EXPECT_TRUE(event != nullptr); - // All events are in right order. - EXPECT_EQ(timeBaseNs + i * 1000, event->GetElapsedTimestampNs()); - } - }); - - reader.join(); - writer.join(); -} - -TEST(LogEventQueue_test, TestSlowConsumer) { - LogEventQueue queue(50); - int64_t timeBaseNs = 100; - std::thread writer([&queue, timeBaseNs] { - int failure_count = 0; - int64_t oldestEventNs; - for (int i = 0; i < 100; i++) { - bool success = queue.push(makeLogEvent(timeBaseNs + i * 1000), &oldestEventNs); - if (!success) failure_count++; - std::this_thread::sleep_for(std::chrono::milliseconds(1)); - } - - // There is some remote chance that reader thread not get chance to run before writer thread - // ends. That's why the following comparison is not "==". - // There will be at least 45 events lost due to overflow. - EXPECT_TRUE(failure_count >= 45); - // The oldest event must be at least the 6th event. - EXPECT_TRUE(oldestEventNs <= (100 + 5 * 1000)); - }); - - std::thread reader([&queue, timeBaseNs] { - // The consumer quickly processed 5 events, then it got stuck (not reading anymore). - for (int i = 0; i < 5; i++) { - auto event = queue.waitPop(); - EXPECT_TRUE(event != nullptr); - // All events are in right order. - EXPECT_EQ(timeBaseNs + i * 1000, event->GetElapsedTimestampNs()); - } - }); - - reader.join(); - writer.join(); -} - -#else -GTEST_LOG_(INFO) << "This test does nothing.\n"; -#endif - -} // namespace statsd -} // namespace os -} // namespace android diff --git a/bin/tests/metadata_util_test.cpp b/bin/tests/metadata_util_test.cpp deleted file mode 100644 index 7707890c..00000000 --- a/bin/tests/metadata_util_test.cpp +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright (C) 2020 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 "metadata_util.h" -#include "tests/statsd_test_util.h" - -#ifdef __ANDROID__ - -namespace android { -namespace os { -namespace statsd { - -TEST(MetadataUtilTest, TestWriteAndReadMetricDimensionKey) { - HashableDimensionKey dim; - HashableDimensionKey dim2; - int pos1[] = {1, 1, 1}; - int pos2[] = {1, 1, 2}; - int pos3[] = {1, 1, 3}; - int pos4[] = {2, 0, 0}; - Field field1(10, pos1, 2); - Field field2(10, pos2, 2); - Field field3(10, pos3, 2); - Field field4(10, pos4, 0); - - Value value1((int32_t)10025); - Value value2("tag"); - Value value3((int32_t)987654); - Value value4((int32_t)99999); - - dim.addValue(FieldValue(field1, value1)); - dim.addValue(FieldValue(field2, value2)); - dim.addValue(FieldValue(field3, value3)); - dim.addValue(FieldValue(field4, value4)); - - dim2.addValue(FieldValue(field1, value1)); - dim2.addValue(FieldValue(field2, value2)); - - MetricDimensionKey dimKey(dim, dim2); - - metadata::MetricDimensionKey metadataDimKey; - writeMetricDimensionKeyToMetadataDimensionKey(dimKey, &metadataDimKey); - - MetricDimensionKey loadedDimKey = loadMetricDimensionKeyFromProto(metadataDimKey); - - ASSERT_EQ(loadedDimKey, dimKey); - ASSERT_EQ(std::hash{}(loadedDimKey), - std::hash{}(dimKey)); -} - -} // namespace statsd -} // namespace os -} // namespace android -#else -GTEST_LOG_(INFO) << "This test does nothing.\n"; -#endif diff --git a/bin/tests/metrics/CountMetricProducer_test.cpp b/bin/tests/metrics/CountMetricProducer_test.cpp deleted file mode 100644 index aab29f21..00000000 --- a/bin/tests/metrics/CountMetricProducer_test.cpp +++ /dev/null @@ -1,479 +0,0 @@ -// 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 "src/metrics/CountMetricProducer.h" - -#include -#include -#include -#include - -#include - -#include "metrics_test_helper.h" -#include "src/stats_log_util.h" -#include "stats_event.h" -#include "tests/statsd_test_util.h" - -using namespace testing; -using android::sp; -using std::set; -using std::unordered_map; -using std::vector; - -#ifdef __ANDROID__ - -namespace android { -namespace os { -namespace statsd { - - -namespace { -const ConfigKey kConfigKey(0, 12345); -const uint64_t protoHash = 0x1234567890; - -void makeLogEvent(LogEvent* logEvent, int64_t timestampNs, int atomId) { - AStatsEvent* statsEvent = AStatsEvent_obtain(); - AStatsEvent_setAtomId(statsEvent, atomId); - AStatsEvent_overwriteTimestamp(statsEvent, timestampNs); - - parseStatsEventToLogEvent(statsEvent, logEvent); -} - -void makeLogEvent(LogEvent* logEvent, int64_t timestampNs, int atomId, string uid) { - AStatsEvent* statsEvent = AStatsEvent_obtain(); - AStatsEvent_setAtomId(statsEvent, atomId); - AStatsEvent_overwriteTimestamp(statsEvent, timestampNs); - AStatsEvent_writeString(statsEvent, uid.c_str()); - - parseStatsEventToLogEvent(statsEvent, logEvent); -} - -} // namespace - -// Setup for parameterized tests. -class CountMetricProducerTest_PartialBucket : public TestWithParam {}; - -INSTANTIATE_TEST_SUITE_P(CountMetricProducerTest_PartialBucket, - CountMetricProducerTest_PartialBucket, - testing::Values(APP_UPGRADE, BOOT_COMPLETE)); - -TEST(CountMetricProducerTest, TestFirstBucket) { - CountMetric metric; - metric.set_id(1); - metric.set_bucket(ONE_MINUTE); - sp wizard = new NaggyMock(); - - CountMetricProducer countProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, {}, - wizard, protoHash, 5, 600 * NS_PER_SEC + NS_PER_SEC / 2); - EXPECT_EQ(600500000000, countProducer.mCurrentBucketStartTimeNs); - EXPECT_EQ(10, countProducer.mCurrentBucketNum); - EXPECT_EQ(660000000005, countProducer.getCurrentBucketEndTimeNs()); -} - -TEST(CountMetricProducerTest, TestNonDimensionalEvents) { - int64_t bucketStartTimeNs = 10000000000; - int64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(ONE_MINUTE) * 1000000LL; - int64_t bucket2StartTimeNs = bucketStartTimeNs + bucketSizeNs; - int64_t bucket3StartTimeNs = bucketStartTimeNs + 2 * bucketSizeNs; - int tagId = 1; - - CountMetric metric; - metric.set_id(1); - metric.set_bucket(ONE_MINUTE); - - sp wizard = new NaggyMock(); - - CountMetricProducer countProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, {}, - wizard, protoHash, bucketStartTimeNs, bucketStartTimeNs); - - // 2 events in bucket 1. - LogEvent event1(/*uid=*/0, /*pid=*/0); - makeLogEvent(&event1, bucketStartTimeNs + 1, tagId); - LogEvent event2(/*uid=*/0, /*pid=*/0); - makeLogEvent(&event2, bucketStartTimeNs + 2, tagId); - - countProducer.onMatchedLogEvent(1 /*log matcher index*/, event1); - countProducer.onMatchedLogEvent(1 /*log matcher index*/, event2); - - // Flushes at event #2. - countProducer.flushIfNeededLocked(bucketStartTimeNs + 2); - ASSERT_EQ(0UL, countProducer.mPastBuckets.size()); - - // Flushes. - countProducer.flushIfNeededLocked(bucketStartTimeNs + bucketSizeNs + 1); - ASSERT_EQ(1UL, countProducer.mPastBuckets.size()); - EXPECT_TRUE(countProducer.mPastBuckets.find(DEFAULT_METRIC_DIMENSION_KEY) != - countProducer.mPastBuckets.end()); - const auto& buckets = countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY]; - ASSERT_EQ(1UL, buckets.size()); - EXPECT_EQ(bucketStartTimeNs, buckets[0].mBucketStartNs); - EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, buckets[0].mBucketEndNs); - EXPECT_EQ(2LL, buckets[0].mCount); - - // 1 matched event happens in bucket 2. - LogEvent event3(/*uid=*/0, /*pid=*/0); - makeLogEvent(&event3, bucketStartTimeNs + bucketSizeNs + 2, tagId); - - countProducer.onMatchedLogEvent(1 /*log matcher index*/, event3); - - countProducer.flushIfNeededLocked(bucketStartTimeNs + 2 * bucketSizeNs + 1); - ASSERT_EQ(1UL, countProducer.mPastBuckets.size()); - EXPECT_TRUE(countProducer.mPastBuckets.find(DEFAULT_METRIC_DIMENSION_KEY) != - countProducer.mPastBuckets.end()); - ASSERT_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); - - // nothing happens in bucket 3. we should not record anything for bucket 3. - countProducer.flushIfNeededLocked(bucketStartTimeNs + 3 * bucketSizeNs + 1); - ASSERT_EQ(1UL, countProducer.mPastBuckets.size()); - EXPECT_TRUE(countProducer.mPastBuckets.find(DEFAULT_METRIC_DIMENSION_KEY) != - countProducer.mPastBuckets.end()); - const auto& buckets3 = countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY]; - ASSERT_EQ(2UL, buckets3.size()); -} - -TEST(CountMetricProducerTest, TestEventsWithNonSlicedCondition) { - int64_t bucketStartTimeNs = 10000000000; - int64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(ONE_MINUTE) * 1000000LL; - - CountMetric metric; - metric.set_id(1); - metric.set_bucket(ONE_MINUTE); - metric.set_condition(StringToId("SCREEN_ON")); - - sp wizard = new NaggyMock(); - - CountMetricProducer countProducer(kConfigKey, metric, 0, {ConditionState::kUnknown}, wizard, - protoHash, bucketStartTimeNs, bucketStartTimeNs); - - countProducer.onConditionChanged(true, bucketStartTimeNs); - - LogEvent event1(/*uid=*/0, /*pid=*/0); - makeLogEvent(&event1, bucketStartTimeNs + 1, /*atomId=*/1); - countProducer.onMatchedLogEvent(1 /*matcher index*/, event1); - - ASSERT_EQ(0UL, countProducer.mPastBuckets.size()); - - countProducer.onConditionChanged(false /*new condition*/, bucketStartTimeNs + 2); - - // Upon this match event, the matched event1 is flushed. - LogEvent event2(/*uid=*/0, /*pid=*/0); - makeLogEvent(&event2, bucketStartTimeNs + 10, /*atomId=*/1); - countProducer.onMatchedLogEvent(1 /*matcher index*/, event2); - ASSERT_EQ(0UL, countProducer.mPastBuckets.size()); - - countProducer.flushIfNeededLocked(bucketStartTimeNs + bucketSizeNs + 1); - ASSERT_EQ(1UL, countProducer.mPastBuckets.size()); - EXPECT_TRUE(countProducer.mPastBuckets.find(DEFAULT_METRIC_DIMENSION_KEY) != - countProducer.mPastBuckets.end()); - - const auto& buckets = countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY]; - ASSERT_EQ(1UL, buckets.size()); - const auto& bucketInfo = buckets[0]; - EXPECT_EQ(bucketStartTimeNs, bucketInfo.mBucketStartNs); - EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, bucketInfo.mBucketEndNs); - EXPECT_EQ(1LL, bucketInfo.mCount); -} - -TEST(CountMetricProducerTest, TestEventsWithSlicedCondition) { - int64_t bucketStartTimeNs = 10000000000; - int64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(ONE_MINUTE) * 1000000LL; - - int tagId = 1; - int conditionTagId = 2; - - CountMetric metric; - metric.set_id(1); - metric.set_bucket(ONE_MINUTE); - metric.set_condition(StringToId("APP_IN_BACKGROUND_PER_UID_AND_SCREEN_ON")); - MetricConditionLink* link = metric.add_links(); - link->set_condition(StringToId("APP_IN_BACKGROUND_PER_UID")); - buildSimpleAtomFieldMatcher(tagId, 1, link->mutable_fields_in_what()); - buildSimpleAtomFieldMatcher(conditionTagId, 2, link->mutable_fields_in_condition()); - - LogEvent event1(/*uid=*/0, /*pid=*/0); - makeLogEvent(&event1, bucketStartTimeNs + 1, tagId, /*uid=*/"111"); - - LogEvent event2(/*uid=*/0, /*pid=*/0); - makeLogEvent(&event2, bucketStartTimeNs + 10, tagId, /*uid=*/"222"); - - ConditionKey key1; - key1[StringToId("APP_IN_BACKGROUND_PER_UID")] = { - getMockedDimensionKey(conditionTagId, 2, "111")}; - - ConditionKey key2; - 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(_, key2, _)).WillOnce(Return(ConditionState::kTrue)); - - CountMetricProducer countProducer(kConfigKey, metric, 0 /*condition tracker index*/, - {ConditionState::kUnknown}, wizard, protoHash, - bucketStartTimeNs, bucketStartTimeNs); - - countProducer.onMatchedLogEvent(1 /*log matcher index*/, event1); - countProducer.flushIfNeededLocked(bucketStartTimeNs + 1); - ASSERT_EQ(0UL, countProducer.mPastBuckets.size()); - - countProducer.onMatchedLogEvent(1 /*log matcher index*/, event2); - countProducer.flushIfNeededLocked(bucketStartTimeNs + bucketSizeNs + 1); - ASSERT_EQ(1UL, countProducer.mPastBuckets.size()); - EXPECT_TRUE(countProducer.mPastBuckets.find(DEFAULT_METRIC_DIMENSION_KEY) != - countProducer.mPastBuckets.end()); - const auto& buckets = countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY]; - ASSERT_EQ(1UL, buckets.size()); - const auto& bucketInfo = buckets[0]; - EXPECT_EQ(bucketStartTimeNs, bucketInfo.mBucketStartNs); - EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, bucketInfo.mBucketEndNs); - EXPECT_EQ(1LL, bucketInfo.mCount); -} - -TEST_P(CountMetricProducerTest_PartialBucket, TestSplitInCurrentBucket) { - sp alarmMonitor; - int64_t bucketStartTimeNs = 10000000000; - int64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(ONE_MINUTE) * 1000000LL; - int64_t eventTimeNs = bucketStartTimeNs + 15 * NS_PER_SEC; - - int tagId = 1; - int conditionTagId = 2; - - CountMetric metric; - metric.set_id(1); - metric.set_bucket(ONE_MINUTE); - Alert alert; - alert.set_num_buckets(3); - alert.set_trigger_if_sum_gt(2); - - sp wizard = new NaggyMock(); - - CountMetricProducer countProducer(kConfigKey, metric, -1 /* no condition */, {}, wizard, - protoHash, bucketStartTimeNs, bucketStartTimeNs); - - sp anomalyTracker = - countProducer.addAnomalyTracker(alert, alarmMonitor, UPDATE_NEW, bucketStartTimeNs); - EXPECT_TRUE(anomalyTracker != nullptr); - - // Bucket is not flushed yet. - LogEvent event1(/*uid=*/0, /*pid=*/0); - makeLogEvent(&event1, bucketStartTimeNs + 1, tagId, /*uid=*/"111"); - countProducer.onMatchedLogEvent(1 /*log matcher index*/, event1); - ASSERT_EQ(0UL, countProducer.mPastBuckets.size()); - EXPECT_EQ(0, anomalyTracker->getSumOverPastBuckets(DEFAULT_METRIC_DIMENSION_KEY)); - - // App upgrade or boot complete forces bucket flush. - // Check that there's a past bucket and the bucket end is not adjusted. - switch (GetParam()) { - case APP_UPGRADE: - countProducer.notifyAppUpgrade(eventTimeNs); - break; - case BOOT_COMPLETE: - countProducer.onStatsdInitCompleted(eventTimeNs); - break; - } - ASSERT_EQ(1UL, countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size()); - EXPECT_EQ(bucketStartTimeNs, - countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY][0].mBucketStartNs); - EXPECT_EQ(eventTimeNs, - countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY][0].mBucketEndNs); - EXPECT_EQ(0, countProducer.getCurrentBucketNum()); - EXPECT_EQ(eventTimeNs, countProducer.mCurrentBucketStartTimeNs); - // Anomaly tracker only contains full buckets. - EXPECT_EQ(0, anomalyTracker->getSumOverPastBuckets(DEFAULT_METRIC_DIMENSION_KEY)); - - int64_t lastEndTimeNs = countProducer.getCurrentBucketEndTimeNs(); - // Next event occurs in same bucket as partial bucket created. - LogEvent event2(/*uid=*/0, /*pid=*/0); - makeLogEvent(&event2, bucketStartTimeNs + 59 * NS_PER_SEC + 10, tagId, /*uid=*/"222"); - countProducer.onMatchedLogEvent(1 /*log matcher index*/, event2); - ASSERT_EQ(1UL, countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size()); - EXPECT_EQ(eventTimeNs, countProducer.mCurrentBucketStartTimeNs); - EXPECT_EQ(0, countProducer.getCurrentBucketNum()); - EXPECT_EQ(0, anomalyTracker->getSumOverPastBuckets(DEFAULT_METRIC_DIMENSION_KEY)); - - // Third event in following bucket. - LogEvent event3(/*uid=*/0, /*pid=*/0); - makeLogEvent(&event3, bucketStartTimeNs + 62 * NS_PER_SEC + 10, tagId, /*uid=*/"333"); - countProducer.onMatchedLogEvent(1 /*log matcher index*/, event3); - ASSERT_EQ(2UL, countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size()); - EXPECT_EQ(lastEndTimeNs, countProducer.mCurrentBucketStartTimeNs); - EXPECT_EQ(1, countProducer.getCurrentBucketNum()); - EXPECT_EQ(2, anomalyTracker->getSumOverPastBuckets(DEFAULT_METRIC_DIMENSION_KEY)); -} - -TEST_P(CountMetricProducerTest_PartialBucket, TestSplitInNextBucket) { - int64_t bucketStartTimeNs = 10000000000; - int64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(ONE_MINUTE) * 1000000LL; - int64_t eventTimeNs = bucketStartTimeNs + 65 * NS_PER_SEC; - - int tagId = 1; - int conditionTagId = 2; - - CountMetric metric; - metric.set_id(1); - metric.set_bucket(ONE_MINUTE); - - sp wizard = new NaggyMock(); - - CountMetricProducer countProducer(kConfigKey, metric, -1 /* no condition */, {}, wizard, - protoHash, bucketStartTimeNs, bucketStartTimeNs); - - // Bucket is flushed yet. - LogEvent event1(/*uid=*/0, /*pid=*/0); - makeLogEvent(&event1, bucketStartTimeNs + 1, tagId, /*uid=*/"111"); - countProducer.onMatchedLogEvent(1 /*log matcher index*/, event1); - ASSERT_EQ(0UL, countProducer.mPastBuckets.size()); - - // App upgrade or boot complete forces bucket flush. - // Check that there's a past bucket and the bucket end is not adjusted since the upgrade - // occurred after the bucket end time. - switch (GetParam()) { - case APP_UPGRADE: - countProducer.notifyAppUpgrade(eventTimeNs); - break; - case BOOT_COMPLETE: - countProducer.onStatsdInitCompleted(eventTimeNs); - break; - } - ASSERT_EQ(1UL, countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size()); - EXPECT_EQ(bucketStartTimeNs, - countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY][0].mBucketStartNs); - EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, - countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY][0].mBucketEndNs); - EXPECT_EQ(eventTimeNs, countProducer.mCurrentBucketStartTimeNs); - - // Next event occurs in same bucket as partial bucket created. - LogEvent event2(/*uid=*/0, /*pid=*/0); - makeLogEvent(&event2, bucketStartTimeNs + 70 * NS_PER_SEC + 10, tagId, /*uid=*/"222"); - countProducer.onMatchedLogEvent(1 /*log matcher index*/, event2); - ASSERT_EQ(1UL, countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size()); - - // Third event in following bucket. - LogEvent event3(/*uid=*/0, /*pid=*/0); - makeLogEvent(&event3, bucketStartTimeNs + 121 * NS_PER_SEC + 10, tagId, /*uid=*/"333"); - countProducer.onMatchedLogEvent(1 /*log matcher index*/, event3); - ASSERT_EQ(2UL, countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size()); - EXPECT_EQ((int64_t)eventTimeNs, - countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY][1].mBucketStartNs); - EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs, - countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY][1].mBucketEndNs); -} - -TEST(CountMetricProducerTest, TestAnomalyDetectionUnSliced) { - sp alarmMonitor; - Alert alert; - alert.set_id(11); - alert.set_metric_id(1); - alert.set_trigger_if_sum_gt(2); - alert.set_num_buckets(2); - const int32_t refPeriodSec = 1; - alert.set_refractory_period_secs(refPeriodSec); - - int64_t bucketStartTimeNs = 10000000000; - int64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(ONE_MINUTE) * 1000000LL; - int64_t bucket2StartTimeNs = bucketStartTimeNs + bucketSizeNs; - int64_t bucket3StartTimeNs = bucketStartTimeNs + 2 * bucketSizeNs; - - CountMetric metric; - metric.set_id(1); - metric.set_bucket(ONE_MINUTE); - - sp wizard = new NaggyMock(); - - CountMetricProducer countProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, {}, - wizard, protoHash, bucketStartTimeNs, bucketStartTimeNs); - - sp anomalyTracker = - countProducer.addAnomalyTracker(alert, alarmMonitor, UPDATE_NEW, bucketStartTimeNs); - - int tagId = 1; - LogEvent event1(/*uid=*/0, /*pid=*/0); - makeLogEvent(&event1, bucketStartTimeNs + 1, tagId); - LogEvent event2(/*uid=*/0, /*pid=*/0); - makeLogEvent(&event2, bucketStartTimeNs + 2, tagId); - LogEvent event3(/*uid=*/0, /*pid=*/0); - makeLogEvent(&event3, bucketStartTimeNs + 2 * bucketSizeNs + 1, tagId); - LogEvent event4(/*uid=*/0, /*pid=*/0); - makeLogEvent(&event4, bucketStartTimeNs + 3 * bucketSizeNs + 1, tagId); - LogEvent event5(/*uid=*/0, /*pid=*/0); - makeLogEvent(&event5, bucketStartTimeNs + 3 * bucketSizeNs + 2, tagId); - LogEvent event6(/*uid=*/0, /*pid=*/0); - makeLogEvent(&event6, bucketStartTimeNs + 3 * bucketSizeNs + 3, tagId); - LogEvent event7(/*uid=*/0, /*pid=*/0); - makeLogEvent(&event7, bucketStartTimeNs + 3 * bucketSizeNs + 2 * NS_PER_SEC, tagId); - - // Two events in bucket #0. - countProducer.onMatchedLogEvent(1 /*log matcher index*/, event1); - countProducer.onMatchedLogEvent(1 /*log matcher index*/, event2); - - ASSERT_EQ(1UL, countProducer.mCurrentSlicedCounter->size()); - EXPECT_EQ(2L, countProducer.mCurrentSlicedCounter->begin()->second); - 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); - ASSERT_EQ(1UL, countProducer.mCurrentSlicedCounter->size()); - EXPECT_EQ(1L, countProducer.mCurrentSlicedCounter->begin()->second); - EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(DEFAULT_METRIC_DIMENSION_KEY), 0U); - - // Two events in bucket #3. - countProducer.onMatchedLogEvent(1 /*log matcher index*/, event4); - countProducer.onMatchedLogEvent(1 /*log matcher index*/, event5); - countProducer.onMatchedLogEvent(1 /*log matcher index*/, event6); - ASSERT_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_METRIC_DIMENSION_KEY), - std::ceil(1.0 * event5.GetElapsedTimestampNs() / NS_PER_SEC + refPeriodSec)); - - countProducer.onMatchedLogEvent(1 /*log matcher index*/, event7); - ASSERT_EQ(1UL, countProducer.mCurrentSlicedCounter->size()); - EXPECT_EQ(4L, countProducer.mCurrentSlicedCounter->begin()->second); - EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(DEFAULT_METRIC_DIMENSION_KEY), - std::ceil(1.0 * event7.GetElapsedTimestampNs() / NS_PER_SEC + refPeriodSec)); -} - -TEST(CountMetricProducerTest, TestOneWeekTimeUnit) { - CountMetric metric; - metric.set_id(1); - metric.set_bucket(ONE_WEEK); - - sp wizard = new NaggyMock(); - - int64_t oneDayNs = 24 * 60 * 60 * 1e9; - int64_t fiveWeeksNs = 5 * 7 * oneDayNs; - - CountMetricProducer countProducer(kConfigKey, metric, -1 /* meaning no condition */, {}, wizard, - protoHash, oneDayNs, fiveWeeksNs); - - int64_t fiveWeeksOneDayNs = fiveWeeksNs + oneDayNs; - - EXPECT_EQ(fiveWeeksNs, countProducer.mCurrentBucketStartTimeNs); - EXPECT_EQ(4, countProducer.mCurrentBucketNum); - EXPECT_EQ(fiveWeeksOneDayNs, countProducer.getCurrentBucketEndTimeNs()); -} - -} // namespace statsd -} // namespace os -} // namespace android -#else -GTEST_LOG_(INFO) << "This test does nothing.\n"; -#endif diff --git a/bin/tests/metrics/DurationMetricProducer_test.cpp b/bin/tests/metrics/DurationMetricProducer_test.cpp deleted file mode 100644 index c0805e87..00000000 --- a/bin/tests/metrics/DurationMetricProducer_test.cpp +++ /dev/null @@ -1,519 +0,0 @@ -// 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 "src/metrics/DurationMetricProducer.h" - -#include -#include -#include - -#include -#include -#include - -#include "metrics_test_helper.h" -#include "src/condition/ConditionWizard.h" -#include "src/stats_log_util.h" -#include "stats_event.h" -#include "tests/statsd_test_util.h" - -using namespace android::os::statsd; -using namespace testing; -using android::sp; -using std::set; -using std::unordered_map; -using std::vector; - -#ifdef __ANDROID__ - -namespace android { -namespace os { -namespace statsd { - - -namespace { - -const ConfigKey kConfigKey(0, 12345); -const uint64_t protoHash = 0x1234567890; -void makeLogEvent(LogEvent* logEvent, int64_t timestampNs, int atomId) { - AStatsEvent* statsEvent = AStatsEvent_obtain(); - AStatsEvent_setAtomId(statsEvent, atomId); - AStatsEvent_overwriteTimestamp(statsEvent, timestampNs); - - parseStatsEventToLogEvent(statsEvent, logEvent); -} - -} // namespace - -// Setup for parameterized tests. -class DurationMetricProducerTest_PartialBucket : public TestWithParam {}; - -INSTANTIATE_TEST_SUITE_P(DurationMetricProducerTest_PartialBucket, - DurationMetricProducerTest_PartialBucket, - testing::Values(APP_UPGRADE, BOOT_COMPLETE)); - -TEST(DurationMetricTrackerTest, TestFirstBucket) { - sp wizard = new NaggyMock(); - DurationMetric metric; - metric.set_id(1); - metric.set_bucket(ONE_MINUTE); - metric.set_aggregation_type(DurationMetric_AggregationType_SUM); - - FieldMatcher dimensions; - - DurationMetricProducer durationProducer( - kConfigKey, metric, -1 /*no condition*/, {}, -1 /*what index not needed*/, - 1 /* start index */, 2 /* stop index */, 3 /* stop_all index */, false /*nesting*/, - wizard, protoHash, dimensions, 5, 600 * NS_PER_SEC + NS_PER_SEC / 2); - - EXPECT_EQ(600500000000, durationProducer.mCurrentBucketStartTimeNs); - EXPECT_EQ(10, durationProducer.mCurrentBucketNum); - EXPECT_EQ(660000000005, durationProducer.getCurrentBucketEndTimeNs()); -} - -TEST(DurationMetricTrackerTest, TestNoCondition) { - sp wizard = new NaggyMock(); - int64_t bucketStartTimeNs = 10000000000; - int64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(ONE_MINUTE) * 1000000LL; - - DurationMetric metric; - metric.set_id(1); - metric.set_bucket(ONE_MINUTE); - metric.set_aggregation_type(DurationMetric_AggregationType_SUM); - - int tagId = 1; - LogEvent event1(/*uid=*/0, /*pid=*/0); - makeLogEvent(&event1, bucketStartTimeNs + 1, tagId); - LogEvent event2(/*uid=*/0, /*pid=*/0); - makeLogEvent(&event2, bucketStartTimeNs + bucketSizeNs + 2, tagId); - - FieldMatcher dimensions; - - DurationMetricProducer durationProducer( - kConfigKey, metric, -1 /*no condition*/, {}, -1 /*what index not needed*/, - 1 /* start index */, 2 /* stop index */, 3 /* stop_all index */, false /*nesting*/, - wizard, protoHash, dimensions, bucketStartTimeNs, bucketStartTimeNs); - - durationProducer.onMatchedLogEvent(1 /* start index*/, event1); - durationProducer.onMatchedLogEvent(2 /* stop index*/, event2); - durationProducer.flushIfNeededLocked(bucketStartTimeNs + 2 * bucketSizeNs + 1); - ASSERT_EQ(1UL, durationProducer.mPastBuckets.size()); - EXPECT_TRUE(durationProducer.mPastBuckets.find(DEFAULT_METRIC_DIMENSION_KEY) != - durationProducer.mPastBuckets.end()); - const auto& buckets = durationProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY]; - ASSERT_EQ(2UL, buckets.size()); - EXPECT_EQ(bucketStartTimeNs, buckets[0].mBucketStartNs); - EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, buckets[0].mBucketEndNs); - EXPECT_EQ(bucketSizeNs - 1LL, buckets[0].mDuration); - EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, buckets[1].mBucketStartNs); - EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs, buckets[1].mBucketEndNs); - EXPECT_EQ(2LL, buckets[1].mDuration); -} - -TEST(DurationMetricTrackerTest, TestNonSlicedCondition) { - sp wizard = new NaggyMock(); - int64_t bucketStartTimeNs = 10000000000; - int64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(ONE_MINUTE) * 1000000LL; - - DurationMetric metric; - metric.set_id(1); - metric.set_bucket(ONE_MINUTE); - metric.set_aggregation_type(DurationMetric_AggregationType_SUM); - - int tagId = 1; - LogEvent event1(/*uid=*/0, /*pid=*/0); - makeLogEvent(&event1, bucketStartTimeNs + 1, tagId); - LogEvent event2(/*uid=*/0, /*pid=*/0); - makeLogEvent(&event2, bucketStartTimeNs + 2, tagId); - LogEvent event3(/*uid=*/0, /*pid=*/0); - makeLogEvent(&event3, bucketStartTimeNs + bucketSizeNs + 1, tagId); - LogEvent event4(/*uid=*/0, /*pid=*/0); - makeLogEvent(&event4, bucketStartTimeNs + bucketSizeNs + 3, tagId); - - FieldMatcher dimensions; - - DurationMetricProducer durationProducer( - kConfigKey, metric, 0 /* condition index */, {ConditionState::kUnknown}, - -1 /*what index not needed*/, 1 /* start index */, 2 /* stop index */, - 3 /* stop_all index */, false /*nesting*/, wizard, protoHash, dimensions, - bucketStartTimeNs, bucketStartTimeNs); - durationProducer.mCondition = ConditionState::kFalse; - - EXPECT_FALSE(durationProducer.mCondition); - EXPECT_FALSE(durationProducer.isConditionSliced()); - - durationProducer.onMatchedLogEvent(1 /* start index*/, event1); - durationProducer.onMatchedLogEvent(2 /* stop index*/, event2); - durationProducer.flushIfNeededLocked(bucketStartTimeNs + bucketSizeNs + 1); - ASSERT_EQ(0UL, durationProducer.mPastBuckets.size()); - - durationProducer.onMatchedLogEvent(1 /* start index*/, event3); - durationProducer.onConditionChanged(true /* condition */, bucketStartTimeNs + bucketSizeNs + 2); - durationProducer.onMatchedLogEvent(2 /* stop index*/, event4); - durationProducer.flushIfNeededLocked(bucketStartTimeNs + 2 * bucketSizeNs + 1); - ASSERT_EQ(1UL, durationProducer.mPastBuckets.size()); - EXPECT_TRUE(durationProducer.mPastBuckets.find(DEFAULT_METRIC_DIMENSION_KEY) != - durationProducer.mPastBuckets.end()); - const auto& buckets2 = durationProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY]; - ASSERT_EQ(1UL, buckets2.size()); - EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, buckets2[0].mBucketStartNs); - EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs, buckets2[0].mBucketEndNs); - EXPECT_EQ(1LL, buckets2[0].mDuration); -} - -TEST(DurationMetricTrackerTest, TestNonSlicedConditionUnknownState) { - sp wizard = new NaggyMock(); - int64_t bucketStartTimeNs = 10000000000; - int64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(ONE_MINUTE) * 1000000LL; - - DurationMetric metric; - metric.set_id(1); - metric.set_bucket(ONE_MINUTE); - metric.set_aggregation_type(DurationMetric_AggregationType_SUM); - - int tagId = 1; - LogEvent event1(/*uid=*/0, /*pid=*/0); - makeLogEvent(&event1, bucketStartTimeNs + 1, tagId); - LogEvent event2(/*uid=*/0, /*pid=*/0); - makeLogEvent(&event2, bucketStartTimeNs + 2, tagId); - LogEvent event3(/*uid=*/0, /*pid=*/0); - makeLogEvent(&event3, bucketStartTimeNs + bucketSizeNs + 1, tagId); - LogEvent event4(/*uid=*/0, /*pid=*/0); - makeLogEvent(&event4, bucketStartTimeNs + bucketSizeNs + 3, tagId); - - FieldMatcher dimensions; - - DurationMetricProducer durationProducer( - kConfigKey, metric, 0 /* condition index */, {ConditionState::kUnknown}, - -1 /*what index not needed*/, 1 /* start index */, 2 /* stop index */, - 3 /* stop_all index */, false /*nesting*/, wizard, protoHash, dimensions, - bucketStartTimeNs, bucketStartTimeNs); - - EXPECT_EQ(ConditionState::kUnknown, durationProducer.mCondition); - EXPECT_FALSE(durationProducer.isConditionSliced()); - - durationProducer.onMatchedLogEvent(1 /* start index*/, event1); - durationProducer.onMatchedLogEvent(2 /* stop index*/, event2); - durationProducer.flushIfNeededLocked(bucketStartTimeNs + bucketSizeNs + 1); - ASSERT_EQ(0UL, durationProducer.mPastBuckets.size()); - - durationProducer.onMatchedLogEvent(1 /* start index*/, event3); - durationProducer.onConditionChanged(true /* condition */, bucketStartTimeNs + bucketSizeNs + 2); - durationProducer.onMatchedLogEvent(2 /* stop index*/, event4); - durationProducer.flushIfNeededLocked(bucketStartTimeNs + 2 * bucketSizeNs + 1); - ASSERT_EQ(1UL, durationProducer.mPastBuckets.size()); - const auto& buckets2 = durationProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY]; - ASSERT_EQ(1UL, buckets2.size()); - EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, buckets2[0].mBucketStartNs); - EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs, buckets2[0].mBucketEndNs); - EXPECT_EQ(1LL, buckets2[0].mDuration); -} - -TEST_P(DurationMetricProducerTest_PartialBucket, TestSumDuration) { - /** - * The duration starts from the first bucket, through the two partial buckets (10-70sec), - * another bucket, and ends at the beginning of the next full bucket. - * Expected buckets: - * - [10,25]: 14 secs - * - [25,70]: All 45 secs - * - [70,130]: All 60 secs - * - [130, 210]: Only 5 secs (event ended at 135sec) - */ - int64_t bucketStartTimeNs = 10000000000; - int64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(ONE_MINUTE) * 1000000LL; - int tagId = 1; - - DurationMetric metric; - metric.set_id(1); - metric.set_bucket(ONE_MINUTE); - metric.set_aggregation_type(DurationMetric_AggregationType_SUM); - sp wizard = new NaggyMock(); - FieldMatcher dimensions; - - DurationMetricProducer durationProducer( - kConfigKey, metric, -1 /* no condition */, {}, -1 /*what index not needed*/, - 1 /* start index */, 2 /* stop index */, 3 /* stop_all index */, false /*nesting*/, - wizard, protoHash, dimensions, bucketStartTimeNs, bucketStartTimeNs); - - int64_t startTimeNs = bucketStartTimeNs + 1 * NS_PER_SEC; - LogEvent event1(/*uid=*/0, /*pid=*/0); - makeLogEvent(&event1, startTimeNs, tagId); - durationProducer.onMatchedLogEvent(1 /* start index*/, event1); - ASSERT_EQ(0UL, durationProducer.mPastBuckets.size()); - EXPECT_EQ(bucketStartTimeNs, durationProducer.mCurrentBucketStartTimeNs); - - int64_t partialBucketSplitTimeNs = bucketStartTimeNs + 15 * NS_PER_SEC; - switch (GetParam()) { - case APP_UPGRADE: - durationProducer.notifyAppUpgrade(partialBucketSplitTimeNs); - break; - case BOOT_COMPLETE: - durationProducer.onStatsdInitCompleted(partialBucketSplitTimeNs); - break; - } - ASSERT_EQ(1UL, durationProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size()); - std::vector buckets = - durationProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY]; - EXPECT_EQ(bucketStartTimeNs, buckets[0].mBucketStartNs); - EXPECT_EQ(partialBucketSplitTimeNs, buckets[0].mBucketEndNs); - EXPECT_EQ(partialBucketSplitTimeNs - startTimeNs, buckets[0].mDuration); - EXPECT_EQ(partialBucketSplitTimeNs, durationProducer.mCurrentBucketStartTimeNs); - EXPECT_EQ(0, durationProducer.getCurrentBucketNum()); - - // We skip ahead one bucket, so we fill in the first two partial buckets and one full bucket. - int64_t endTimeNs = startTimeNs + 125 * NS_PER_SEC; - LogEvent event2(/*uid=*/0, /*pid=*/0); - makeLogEvent(&event2, endTimeNs, tagId); - durationProducer.onMatchedLogEvent(2 /* stop index*/, event2); - buckets = durationProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY]; - ASSERT_EQ(3UL, buckets.size()); - EXPECT_EQ(partialBucketSplitTimeNs, buckets[1].mBucketStartNs); - EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, buckets[1].mBucketEndNs); - EXPECT_EQ(bucketStartTimeNs + bucketSizeNs - partialBucketSplitTimeNs, buckets[1].mDuration); - EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, buckets[2].mBucketStartNs); - EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs, buckets[2].mBucketEndNs); - EXPECT_EQ(bucketSizeNs, buckets[2].mDuration); -} - -TEST_P(DurationMetricProducerTest_PartialBucket, TestSumDurationWithSplitInFollowingBucket) { - /** - * Expected buckets (start at 11s, upgrade at 75s, end at 135s): - * - [10,70]: 59 secs - * - [70,75]: 5 sec - * - [75,130]: 55 secs - */ - int64_t bucketStartTimeNs = 10000000000; - int64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(ONE_MINUTE) * 1000000LL; - int tagId = 1; - - DurationMetric metric; - metric.set_id(1); - metric.set_bucket(ONE_MINUTE); - metric.set_aggregation_type(DurationMetric_AggregationType_SUM); - sp wizard = new NaggyMock(); - FieldMatcher dimensions; - - DurationMetricProducer durationProducer( - kConfigKey, metric, -1 /* no condition */, {}, -1 /*what index not needed*/, - 1 /* start index */, 2 /* stop index */, 3 /* stop_all index */, false /*nesting*/, - wizard, protoHash, dimensions, bucketStartTimeNs, bucketStartTimeNs); - - int64_t startTimeNs = bucketStartTimeNs + 1 * NS_PER_SEC; - LogEvent event1(/*uid=*/0, /*pid=*/0); - makeLogEvent(&event1, startTimeNs, tagId); - durationProducer.onMatchedLogEvent(1 /* start index*/, event1); - ASSERT_EQ(0UL, durationProducer.mPastBuckets.size()); - EXPECT_EQ(bucketStartTimeNs, durationProducer.mCurrentBucketStartTimeNs); - - int64_t partialBucketSplitTimeNs = bucketStartTimeNs + 65 * NS_PER_SEC; - switch (GetParam()) { - case APP_UPGRADE: - durationProducer.notifyAppUpgrade(partialBucketSplitTimeNs); - break; - case BOOT_COMPLETE: - durationProducer.onStatsdInitCompleted(partialBucketSplitTimeNs); - break; - } - ASSERT_EQ(2UL, durationProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size()); - std::vector buckets = - durationProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY]; - EXPECT_EQ(bucketStartTimeNs, buckets[0].mBucketStartNs); - EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, buckets[0].mBucketEndNs); - EXPECT_EQ(bucketStartTimeNs + bucketSizeNs - startTimeNs, buckets[0].mDuration); - EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, buckets[1].mBucketStartNs); - EXPECT_EQ(partialBucketSplitTimeNs, buckets[1].mBucketEndNs); - EXPECT_EQ(partialBucketSplitTimeNs - (bucketStartTimeNs + bucketSizeNs), buckets[1].mDuration); - EXPECT_EQ(partialBucketSplitTimeNs, durationProducer.mCurrentBucketStartTimeNs); - EXPECT_EQ(1, durationProducer.getCurrentBucketNum()); - - // We skip ahead one bucket, so we fill in the first two partial buckets and one full bucket. - int64_t endTimeNs = startTimeNs + 125 * NS_PER_SEC; - LogEvent event2(/*uid=*/0, /*pid=*/0); - makeLogEvent(&event2, endTimeNs, tagId); - durationProducer.onMatchedLogEvent(2 /* stop index*/, event2); - buckets = durationProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY]; - ASSERT_EQ(3UL, buckets.size()); - EXPECT_EQ(partialBucketSplitTimeNs, buckets[2].mBucketStartNs); - EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs, buckets[2].mBucketEndNs); - EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs - partialBucketSplitTimeNs, - buckets[2].mDuration); -} - -TEST_P(DurationMetricProducerTest_PartialBucket, TestSumDurationAnomaly) { - sp alarmMonitor; - int64_t bucketStartTimeNs = 10000000000; - int64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(ONE_MINUTE) * 1000000LL; - int tagId = 1; - - // Setup metric with alert. - DurationMetric metric; - metric.set_id(1); - metric.set_bucket(ONE_MINUTE); - metric.set_aggregation_type(DurationMetric_AggregationType_SUM); - Alert alert; - alert.set_num_buckets(3); - alert.set_trigger_if_sum_gt(2); - - sp wizard = new NaggyMock(); - FieldMatcher dimensions; - - DurationMetricProducer durationProducer( - kConfigKey, metric, -1 /* no condition */, {}, -1 /*what index not needed*/, - 1 /* start index */, 2 /* stop index */, 3 /* stop_all index */, false /*nesting*/, - wizard, protoHash, dimensions, bucketStartTimeNs, bucketStartTimeNs); - - sp anomalyTracker = - durationProducer.addAnomalyTracker(alert, alarmMonitor, UPDATE_NEW, bucketStartTimeNs); - EXPECT_TRUE(anomalyTracker != nullptr); - - int64_t startTimeNs = bucketStartTimeNs + 1; - LogEvent event1(/*uid=*/0, /*pid=*/0); - makeLogEvent(&event1, startTimeNs, tagId); - durationProducer.onMatchedLogEvent(1 /* start index*/, event1); - - int64_t partialBucketSplitTimeNs = bucketStartTimeNs + 15 * NS_PER_SEC; - switch (GetParam()) { - case APP_UPGRADE: - durationProducer.notifyAppUpgrade(partialBucketSplitTimeNs); - break; - case BOOT_COMPLETE: - durationProducer.onStatsdInitCompleted(partialBucketSplitTimeNs); - break; - } - - // We skip ahead one bucket, so we fill in the first two partial buckets and one full bucket. - int64_t endTimeNs = startTimeNs + 65 * NS_PER_SEC; - LogEvent event2(/*uid=*/0, /*pid=*/0); - makeLogEvent(&event2, endTimeNs, tagId); - durationProducer.onMatchedLogEvent(2 /* stop index*/, event2); - - EXPECT_EQ(bucketStartTimeNs + bucketSizeNs - startTimeNs, - anomalyTracker->getSumOverPastBuckets(DEFAULT_METRIC_DIMENSION_KEY)); -} - -TEST_P(DurationMetricProducerTest_PartialBucket, TestMaxDuration) { - int64_t bucketStartTimeNs = 10000000000; - int64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(ONE_MINUTE) * 1000000LL; - int tagId = 1; - - DurationMetric metric; - metric.set_id(1); - metric.set_bucket(ONE_MINUTE); - metric.set_aggregation_type(DurationMetric_AggregationType_MAX_SPARSE); - - sp wizard = new NaggyMock(); - FieldMatcher dimensions; - - DurationMetricProducer durationProducer( - kConfigKey, metric, -1 /* no condition */, {}, -1 /*what index not needed*/, - 1 /* start index */, 2 /* stop index */, 3 /* stop_all index */, false /*nesting*/, - wizard, protoHash, dimensions, bucketStartTimeNs, bucketStartTimeNs); - - int64_t startTimeNs = bucketStartTimeNs + 1; - LogEvent event1(/*uid=*/0, /*pid=*/0); - makeLogEvent(&event1, startTimeNs, tagId); - durationProducer.onMatchedLogEvent(1 /* start index*/, event1); - ASSERT_EQ(0UL, durationProducer.mPastBuckets.size()); - EXPECT_EQ(bucketStartTimeNs, durationProducer.mCurrentBucketStartTimeNs); - - int64_t partialBucketSplitTimeNs = bucketStartTimeNs + 15 * NS_PER_SEC; - switch (GetParam()) { - case APP_UPGRADE: - durationProducer.notifyAppUpgrade(partialBucketSplitTimeNs); - break; - case BOOT_COMPLETE: - durationProducer.onStatsdInitCompleted(partialBucketSplitTimeNs); - break; - } - ASSERT_EQ(0UL, durationProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size()); - EXPECT_EQ(partialBucketSplitTimeNs, durationProducer.mCurrentBucketStartTimeNs); - EXPECT_EQ(0, durationProducer.getCurrentBucketNum()); - - // We skip ahead one bucket, so we fill in the first two partial buckets and one full bucket. - int64_t endTimeNs = startTimeNs + 125 * NS_PER_SEC; - LogEvent event2(/*uid=*/0, /*pid=*/0); - makeLogEvent(&event2, endTimeNs, tagId); - durationProducer.onMatchedLogEvent(2 /* stop index*/, event2); - ASSERT_EQ(0UL, durationProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size()); - - durationProducer.flushIfNeededLocked(bucketStartTimeNs + 3 * bucketSizeNs + 1); - std::vector buckets = - durationProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY]; - ASSERT_EQ(1UL, buckets.size()); - EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs, buckets[0].mBucketStartNs); - EXPECT_EQ(bucketStartTimeNs + 3 * bucketSizeNs, buckets[0].mBucketEndNs); - EXPECT_EQ(endTimeNs - startTimeNs, buckets[0].mDuration); -} - -TEST_P(DurationMetricProducerTest_PartialBucket, TestMaxDurationWithSplitInNextBucket) { - int64_t bucketStartTimeNs = 10000000000; - int64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(ONE_MINUTE) * 1000000LL; - int tagId = 1; - - DurationMetric metric; - metric.set_id(1); - metric.set_bucket(ONE_MINUTE); - metric.set_aggregation_type(DurationMetric_AggregationType_MAX_SPARSE); - - sp wizard = new NaggyMock(); - FieldMatcher dimensions; - - DurationMetricProducer durationProducer( - kConfigKey, metric, -1 /* no condition */, {}, -1 /*what index not needed*/, - 1 /* start index */, 2 /* stop index */, 3 /* stop_all index */, false /*nesting*/, - wizard, protoHash, dimensions, bucketStartTimeNs, bucketStartTimeNs); - - int64_t startTimeNs = bucketStartTimeNs + 1; - LogEvent event1(/*uid=*/0, /*pid=*/0); - makeLogEvent(&event1, startTimeNs, tagId); - durationProducer.onMatchedLogEvent(1 /* start index*/, event1); - ASSERT_EQ(0UL, durationProducer.mPastBuckets.size()); - EXPECT_EQ(bucketStartTimeNs, durationProducer.mCurrentBucketStartTimeNs); - - int64_t partialBucketSplitTimeNs = bucketStartTimeNs + 65 * NS_PER_SEC; - switch (GetParam()) { - case APP_UPGRADE: - durationProducer.notifyAppUpgrade(partialBucketSplitTimeNs); - break; - case BOOT_COMPLETE: - durationProducer.onStatsdInitCompleted(partialBucketSplitTimeNs); - break; - } - ASSERT_EQ(0UL, durationProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size()); - EXPECT_EQ(partialBucketSplitTimeNs, durationProducer.mCurrentBucketStartTimeNs); - EXPECT_EQ(1, durationProducer.getCurrentBucketNum()); - - // Stop occurs in the same partial bucket as created for the app upgrade. - int64_t endTimeNs = startTimeNs + 115 * NS_PER_SEC; - LogEvent event2(/*uid=*/0, /*pid=*/0); - makeLogEvent(&event2, endTimeNs, tagId); - durationProducer.onMatchedLogEvent(2 /* stop index*/, event2); - ASSERT_EQ(0UL, durationProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size()); - EXPECT_EQ(partialBucketSplitTimeNs, durationProducer.mCurrentBucketStartTimeNs); - - durationProducer.flushIfNeededLocked(bucketStartTimeNs + 2 * bucketSizeNs + 1); - std::vector buckets = - durationProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY]; - ASSERT_EQ(1UL, buckets.size()); - EXPECT_EQ(partialBucketSplitTimeNs, buckets[0].mBucketStartNs); - EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs, buckets[0].mBucketEndNs); - EXPECT_EQ(endTimeNs - startTimeNs, buckets[0].mDuration); -} - -} // namespace statsd -} // namespace os -} // namespace android -#else -GTEST_LOG_(INFO) << "This test does nothing.\n"; -#endif diff --git a/bin/tests/metrics/EventMetricProducer_test.cpp b/bin/tests/metrics/EventMetricProducer_test.cpp deleted file mode 100644 index 4bbbd2cb..00000000 --- a/bin/tests/metrics/EventMetricProducer_test.cpp +++ /dev/null @@ -1,186 +0,0 @@ -// 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 "src/metrics/EventMetricProducer.h" - -#include -#include -#include - -#include - -#include "metrics_test_helper.h" -#include "stats_event.h" -#include "tests/statsd_test_util.h" - -using namespace testing; -using android::sp; -using std::set; -using std::unordered_map; -using std::vector; - -#ifdef __ANDROID__ - -namespace android { -namespace os { -namespace statsd { - - -namespace { -const ConfigKey kConfigKey(0, 12345); -const uint64_t protoHash = 0x1234567890; - -void makeLogEvent(LogEvent* logEvent, int32_t atomId, int64_t timestampNs, string str) { - AStatsEvent* statsEvent = AStatsEvent_obtain(); - AStatsEvent_setAtomId(statsEvent, atomId); - AStatsEvent_overwriteTimestamp(statsEvent, timestampNs); - AStatsEvent_writeString(statsEvent, str.c_str()); - - parseStatsEventToLogEvent(statsEvent, logEvent); -} -} // anonymous namespace - -TEST(EventMetricProducerTest, TestNoCondition) { - int64_t bucketStartTimeNs = 10000000000; - int64_t eventStartTimeNs = bucketStartTimeNs + 1; - int64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL; - - EventMetric metric; - metric.set_id(1); - - LogEvent event1(/*uid=*/0, /*pid=*/0); - CreateNoValuesLogEvent(&event1, 1 /*tagId*/, bucketStartTimeNs + 1); - - LogEvent event2(/*uid=*/0, /*pid=*/0); - CreateNoValuesLogEvent(&event2, 1 /*tagId*/, bucketStartTimeNs + 2); - - sp wizard = new NaggyMock(); - - EventMetricProducer eventProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, {}, - wizard, protoHash, bucketStartTimeNs); - - eventProducer.onMatchedLogEvent(1 /*matcher index*/, event1); - eventProducer.onMatchedLogEvent(1 /*matcher index*/, event2); - - // Check dump report content. - ProtoOutputStream output; - std::set strSet; - eventProducer.onDumpReport(bucketStartTimeNs + 20, true /*include current partial bucket*/, - true /*erase data*/, FAST, &strSet, &output); - - StatsLogReport report = outputStreamToProto(&output); - EXPECT_TRUE(report.has_event_metrics()); - ASSERT_EQ(2, report.event_metrics().data_size()); - EXPECT_EQ(bucketStartTimeNs + 1, report.event_metrics().data(0).elapsed_timestamp_nanos()); - EXPECT_EQ(bucketStartTimeNs + 2, report.event_metrics().data(1).elapsed_timestamp_nanos()); -} - -TEST(EventMetricProducerTest, TestEventsWithNonSlicedCondition) { - int64_t bucketStartTimeNs = 10000000000; - int64_t eventStartTimeNs = bucketStartTimeNs + 1; - int64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL; - - EventMetric metric; - metric.set_id(1); - metric.set_condition(StringToId("SCREEN_ON")); - - LogEvent event1(/*uid=*/0, /*pid=*/0); - CreateNoValuesLogEvent(&event1, 1 /*tagId*/, bucketStartTimeNs + 1); - - LogEvent event2(/*uid=*/0, /*pid=*/0); - CreateNoValuesLogEvent(&event2, 1 /*tagId*/, bucketStartTimeNs + 10); - - sp wizard = new NaggyMock(); - - EventMetricProducer eventProducer(kConfigKey, metric, 0 /*condition index*/, - {ConditionState::kUnknown}, wizard, protoHash, - bucketStartTimeNs); - - eventProducer.onConditionChanged(true /*condition*/, bucketStartTimeNs); - eventProducer.onMatchedLogEvent(1 /*matcher index*/, event1); - - eventProducer.onConditionChanged(false /*condition*/, bucketStartTimeNs + 2); - - eventProducer.onMatchedLogEvent(1 /*matcher index*/, event2); - - // Check dump report content. - ProtoOutputStream output; - std::set strSet; - eventProducer.onDumpReport(bucketStartTimeNs + 20, true /*include current partial bucket*/, - true /*erase data*/, FAST, &strSet, &output); - - StatsLogReport report = outputStreamToProto(&output); - EXPECT_TRUE(report.has_event_metrics()); - ASSERT_EQ(1, report.event_metrics().data_size()); - EXPECT_EQ(bucketStartTimeNs + 1, report.event_metrics().data(0).elapsed_timestamp_nanos()); -} - -TEST(EventMetricProducerTest, TestEventsWithSlicedCondition) { - int64_t bucketStartTimeNs = 10000000000; - int64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL; - - int tagId = 1; - int conditionTagId = 2; - - EventMetric metric; - metric.set_id(1); - metric.set_condition(StringToId("APP_IN_BACKGROUND_PER_UID_AND_SCREEN_ON")); - MetricConditionLink* link = metric.add_links(); - link->set_condition(StringToId("APP_IN_BACKGROUND_PER_UID")); - buildSimpleAtomFieldMatcher(tagId, 1, link->mutable_fields_in_what()); - buildSimpleAtomFieldMatcher(conditionTagId, 2, link->mutable_fields_in_condition()); - - LogEvent event1(/*uid=*/0, /*pid=*/0); - makeLogEvent(&event1, 1 /*tagId*/, bucketStartTimeNs + 1, "111"); - ConditionKey key1; - key1[StringToId("APP_IN_BACKGROUND_PER_UID")] = { - getMockedDimensionKey(conditionTagId, 2, "111")}; - - LogEvent event2(/*uid=*/0, /*pid=*/0); - makeLogEvent(&event2, 1 /*tagId*/, bucketStartTimeNs + 10, "222"); - ConditionKey key2; - key2[StringToId("APP_IN_BACKGROUND_PER_UID")] = { - getMockedDimensionKey(conditionTagId, 2, "222")}; - - sp wizard = new NaggyMock(); - // Condition is false for first event. - EXPECT_CALL(*wizard, query(_, key1, _)).WillOnce(Return(ConditionState::kFalse)); - // Condition is true for second event. - EXPECT_CALL(*wizard, query(_, key2, _)).WillOnce(Return(ConditionState::kTrue)); - - EventMetricProducer eventProducer(kConfigKey, metric, 0 /*condition index*/, - {ConditionState::kUnknown}, wizard, protoHash, - bucketStartTimeNs); - - eventProducer.onMatchedLogEvent(1 /*matcher index*/, event1); - eventProducer.onMatchedLogEvent(1 /*matcher index*/, event2); - - // Check dump report content. - ProtoOutputStream output; - std::set strSet; - eventProducer.onDumpReport(bucketStartTimeNs + 20, true /*include current partial bucket*/, - true /*erase data*/, FAST, &strSet, &output); - - StatsLogReport report = outputStreamToProto(&output); - EXPECT_TRUE(report.has_event_metrics()); - ASSERT_EQ(1, report.event_metrics().data_size()); - EXPECT_EQ(bucketStartTimeNs + 10, report.event_metrics().data(0).elapsed_timestamp_nanos()); -} - -} // namespace statsd -} // namespace os -} // namespace android -#else -GTEST_LOG_(INFO) << "This test does nothing.\n"; -#endif diff --git a/bin/tests/metrics/GaugeMetricProducer_test.cpp b/bin/tests/metrics/GaugeMetricProducer_test.cpp deleted file mode 100644 index c7443481..00000000 --- a/bin/tests/metrics/GaugeMetricProducer_test.cpp +++ /dev/null @@ -1,830 +0,0 @@ -// 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 "src/metrics/GaugeMetricProducer.h" - -#include -#include -#include -#include - -#include - -#include "logd/LogEvent.h" -#include "metrics_test_helper.h" -#include "src/matchers/SimpleAtomMatchingTracker.h" -#include "src/metrics/MetricProducer.h" -#include "src/stats_log_util.h" -#include "stats_event.h" -#include "tests/statsd_test_util.h" - -using namespace testing; -using android::sp; -using std::set; -using std::unordered_map; -using std::vector; -using std::make_shared; - -#ifdef __ANDROID__ - -namespace android { -namespace os { -namespace statsd { - -namespace { - -const ConfigKey kConfigKey(0, 12345); -const int tagId = 1; -const int64_t metricId = 123; -const uint64_t protoHash = 0x123456789; -const int logEventMatcherIndex = 0; -const int64_t bucketStartTimeNs = 10 * NS_PER_SEC; -const int64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(ONE_MINUTE) * 1000000LL; -const int64_t bucket2StartTimeNs = bucketStartTimeNs + bucketSizeNs; -const int64_t bucket3StartTimeNs = bucketStartTimeNs + 2 * bucketSizeNs; -const int64_t bucket4StartTimeNs = bucketStartTimeNs + 3 * bucketSizeNs; -const int64_t partialBucketSplitTimeNs = bucketStartTimeNs + 15 * NS_PER_SEC; - -shared_ptr makeLogEvent(int32_t atomId, int64_t timestampNs, int32_t value1, string str1, - int32_t value2) { - AStatsEvent* statsEvent = AStatsEvent_obtain(); - AStatsEvent_setAtomId(statsEvent, atomId); - AStatsEvent_overwriteTimestamp(statsEvent, timestampNs); - - AStatsEvent_writeInt32(statsEvent, value1); - AStatsEvent_writeString(statsEvent, str1.c_str()); - AStatsEvent_writeInt32(statsEvent, value2); - - shared_ptr logEvent = std::make_shared(/*uid=*/0, /*pid=*/0); - parseStatsEventToLogEvent(statsEvent, logEvent.get()); - return logEvent; -} -} // anonymous namespace - -// Setup for parameterized tests. -class GaugeMetricProducerTest_PartialBucket : public TestWithParam {}; - -INSTANTIATE_TEST_SUITE_P(GaugeMetricProducerTest_PartialBucket, - GaugeMetricProducerTest_PartialBucket, - testing::Values(APP_UPGRADE, BOOT_COMPLETE)); - -/* - * Tests that the first bucket works correctly - */ -TEST(GaugeMetricProducerTest, TestFirstBucket) { - GaugeMetric metric; - metric.set_id(metricId); - metric.set_bucket(ONE_MINUTE); - metric.mutable_gauge_fields_filter()->set_include_all(false); - auto gaugeFieldMatcher = metric.mutable_gauge_fields_filter()->mutable_fields(); - gaugeFieldMatcher->set_field(tagId); - gaugeFieldMatcher->add_child()->set_field(1); - gaugeFieldMatcher->add_child()->set_field(3); - - sp wizard = new NaggyMock(); - - sp eventMatcherWizard = - createEventMatcherWizard(tagId, logEventMatcherIndex); - - sp pullerManager = new StrictMock(); - - // statsd started long ago. - // The metric starts in the middle of the bucket - GaugeMetricProducer gaugeProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, {}, - wizard, protoHash, logEventMatcherIndex, eventMatcherWizard, - -1, -1, tagId, 5, 600 * NS_PER_SEC + NS_PER_SEC / 2, - pullerManager); - gaugeProducer.prepareFirstBucket(); - - EXPECT_EQ(600500000000, gaugeProducer.mCurrentBucketStartTimeNs); - EXPECT_EQ(10, gaugeProducer.mCurrentBucketNum); - EXPECT_EQ(660000000005, gaugeProducer.getCurrentBucketEndTimeNs()); -} - -TEST(GaugeMetricProducerTest, TestPulledEventsNoCondition) { - GaugeMetric metric; - metric.set_id(metricId); - metric.set_bucket(ONE_MINUTE); - metric.mutable_gauge_fields_filter()->set_include_all(false); - metric.set_max_pull_delay_sec(INT_MAX); - auto gaugeFieldMatcher = metric.mutable_gauge_fields_filter()->mutable_fields(); - gaugeFieldMatcher->set_field(tagId); - gaugeFieldMatcher->add_child()->set_field(1); - gaugeFieldMatcher->add_child()->set_field(3); - - sp wizard = new NaggyMock(); - - sp eventMatcherWizard = - createEventMatcherWizard(tagId, logEventMatcherIndex); - - sp pullerManager = new StrictMock(); - EXPECT_CALL(*pullerManager, RegisterReceiver(tagId, kConfigKey, _, _, _)).WillOnce(Return()); - EXPECT_CALL(*pullerManager, UnRegisterReceiver(tagId, kConfigKey, _)).WillOnce(Return()); - EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, bucketStartTimeNs, _)) - .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, - vector>* data) { - EXPECT_EQ(eventTimeNs, bucketStartTimeNs); - data->clear(); - data->push_back(makeLogEvent(tagId, eventTimeNs + 10, 3, "some value", 11)); - return true; - })); - - GaugeMetricProducer gaugeProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, {}, - wizard, protoHash, logEventMatcherIndex, eventMatcherWizard, - tagId, -1, tagId, bucketStartTimeNs, bucketStartTimeNs, - pullerManager); - gaugeProducer.prepareFirstBucket(); - - vector> allData; - allData.clear(); - allData.push_back(makeLogEvent(tagId, bucket2StartTimeNs + 1, 10, "some value", 11)); - - gaugeProducer.onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs); - ASSERT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->size()); - auto it = gaugeProducer.mCurrentSlicedBucket->begin()->second.front().mFields->begin(); - EXPECT_EQ(INT, it->mValue.getType()); - EXPECT_EQ(10, it->mValue.int_value); - it++; - EXPECT_EQ(11, it->mValue.int_value); - ASSERT_EQ(1UL, gaugeProducer.mPastBuckets.size()); - EXPECT_EQ(3, gaugeProducer.mPastBuckets.begin() - ->second.back() - .mGaugeAtoms.front() - .mFields->begin() - ->mValue.int_value); - - allData.clear(); - allData.push_back(makeLogEvent(tagId, bucket3StartTimeNs + 10, 24, "some value", 25)); - gaugeProducer.onDataPulled(allData, /** succeed */ true, bucket3StartTimeNs); - ASSERT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->size()); - it = gaugeProducer.mCurrentSlicedBucket->begin()->second.front().mFields->begin(); - EXPECT_EQ(INT, it->mValue.getType()); - EXPECT_EQ(24, it->mValue.int_value); - it++; - EXPECT_EQ(INT, it->mValue.getType()); - EXPECT_EQ(25, it->mValue.int_value); - // One dimension. - ASSERT_EQ(1UL, gaugeProducer.mPastBuckets.size()); - ASSERT_EQ(2UL, gaugeProducer.mPastBuckets.begin()->second.size()); - it = gaugeProducer.mPastBuckets.begin()->second.back().mGaugeAtoms.front().mFields->begin(); - EXPECT_EQ(INT, it->mValue.getType()); - EXPECT_EQ(10L, it->mValue.int_value); - it++; - EXPECT_EQ(INT, it->mValue.getType()); - EXPECT_EQ(11L, it->mValue.int_value); - - gaugeProducer.flushIfNeededLocked(bucket4StartTimeNs); - ASSERT_EQ(0UL, gaugeProducer.mCurrentSlicedBucket->size()); - // One dimension. - ASSERT_EQ(1UL, gaugeProducer.mPastBuckets.size()); - ASSERT_EQ(3UL, gaugeProducer.mPastBuckets.begin()->second.size()); - it = gaugeProducer.mPastBuckets.begin()->second.back().mGaugeAtoms.front().mFields->begin(); - EXPECT_EQ(INT, it->mValue.getType()); - EXPECT_EQ(24L, it->mValue.int_value); - it++; - EXPECT_EQ(INT, it->mValue.getType()); - EXPECT_EQ(25L, it->mValue.int_value); -} - -TEST_P(GaugeMetricProducerTest_PartialBucket, TestPushedEvents) { - sp alarmMonitor; - GaugeMetric metric; - metric.set_id(metricId); - metric.set_bucket(ONE_MINUTE); - metric.mutable_gauge_fields_filter()->set_include_all(true); - - Alert alert; - alert.set_id(101); - alert.set_metric_id(metricId); - alert.set_trigger_if_sum_gt(25); - alert.set_num_buckets(100); - sp wizard = new NaggyMock(); - sp pullerManager = new StrictMock(); - - sp eventMatcherWizard = - createEventMatcherWizard(tagId, logEventMatcherIndex); - - GaugeMetricProducer gaugeProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, {}, - wizard, protoHash, logEventMatcherIndex, eventMatcherWizard, - -1 /* -1 means no pulling */, -1, tagId, bucketStartTimeNs, - bucketStartTimeNs, pullerManager); - gaugeProducer.prepareFirstBucket(); - - sp anomalyTracker = - gaugeProducer.addAnomalyTracker(alert, alarmMonitor, UPDATE_NEW, bucketStartTimeNs); - EXPECT_TRUE(anomalyTracker != nullptr); - - LogEvent event1(/*uid=*/0, /*pid=*/0); - CreateTwoValueLogEvent(&event1, tagId, bucketStartTimeNs + 10, 1, 10); - gaugeProducer.onMatchedLogEvent(1 /*log matcher index*/, event1); - EXPECT_EQ(1UL, (*gaugeProducer.mCurrentSlicedBucket).count(DEFAULT_METRIC_DIMENSION_KEY)); - - switch (GetParam()) { - case APP_UPGRADE: - gaugeProducer.notifyAppUpgrade(partialBucketSplitTimeNs); - break; - case BOOT_COMPLETE: - gaugeProducer.onStatsdInitCompleted(partialBucketSplitTimeNs); - break; - } - EXPECT_EQ(0UL, (*gaugeProducer.mCurrentSlicedBucket).count(DEFAULT_METRIC_DIMENSION_KEY)); - ASSERT_EQ(1UL, gaugeProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size()); - EXPECT_EQ(bucketStartTimeNs, - gaugeProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY][0].mBucketStartNs); - EXPECT_EQ(partialBucketSplitTimeNs, - gaugeProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY][0].mBucketEndNs); - EXPECT_EQ(0L, gaugeProducer.mCurrentBucketNum); - EXPECT_EQ(partialBucketSplitTimeNs, gaugeProducer.mCurrentBucketStartTimeNs); - // Partial buckets are not sent to anomaly tracker. - EXPECT_EQ(0, anomalyTracker->getSumOverPastBuckets(DEFAULT_METRIC_DIMENSION_KEY)); - - // Create an event in the same partial bucket. - LogEvent event2(/*uid=*/0, /*pid=*/0); - CreateTwoValueLogEvent(&event2, tagId, bucketStartTimeNs + 59 * NS_PER_SEC, 1, 10); - gaugeProducer.onMatchedLogEvent(1 /*log matcher index*/, event2); - EXPECT_EQ(0L, gaugeProducer.mCurrentBucketNum); - ASSERT_EQ(1UL, gaugeProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size()); - EXPECT_EQ(bucketStartTimeNs, - gaugeProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY][0].mBucketStartNs); - EXPECT_EQ(partialBucketSplitTimeNs, - gaugeProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY][0].mBucketEndNs); - EXPECT_EQ((int64_t)partialBucketSplitTimeNs, gaugeProducer.mCurrentBucketStartTimeNs); - // Partial buckets are not sent to anomaly tracker. - EXPECT_EQ(0, anomalyTracker->getSumOverPastBuckets(DEFAULT_METRIC_DIMENSION_KEY)); - - // Next event should trigger creation of new bucket and send previous full bucket to anomaly - // tracker. - LogEvent event3(/*uid=*/0, /*pid=*/0); - CreateTwoValueLogEvent(&event3, tagId, bucketStartTimeNs + 65 * NS_PER_SEC, 1, 10); - gaugeProducer.onMatchedLogEvent(1 /*log matcher index*/, event3); - EXPECT_EQ(1L, gaugeProducer.mCurrentBucketNum); - ASSERT_EQ(2UL, gaugeProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size()); - EXPECT_EQ((int64_t)bucketStartTimeNs + bucketSizeNs, gaugeProducer.mCurrentBucketStartTimeNs); - EXPECT_EQ(1, anomalyTracker->getSumOverPastBuckets(DEFAULT_METRIC_DIMENSION_KEY)); - - // Next event should trigger creation of new bucket. - LogEvent event4(/*uid=*/0, /*pid=*/0); - CreateTwoValueLogEvent(&event4, tagId, bucketStartTimeNs + 125 * NS_PER_SEC, 1, 10); - gaugeProducer.onMatchedLogEvent(1 /*log matcher index*/, event4); - EXPECT_EQ(2L, gaugeProducer.mCurrentBucketNum); - ASSERT_EQ(3UL, gaugeProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size()); - EXPECT_EQ(2, anomalyTracker->getSumOverPastBuckets(DEFAULT_METRIC_DIMENSION_KEY)); -} - -TEST_P(GaugeMetricProducerTest_PartialBucket, TestPulled) { - GaugeMetric metric; - metric.set_id(metricId); - metric.set_bucket(ONE_MINUTE); - metric.set_max_pull_delay_sec(INT_MAX); - auto gaugeFieldMatcher = metric.mutable_gauge_fields_filter()->mutable_fields(); - gaugeFieldMatcher->set_field(tagId); - gaugeFieldMatcher->add_child()->set_field(2); - - sp wizard = new NaggyMock(); - - sp eventMatcherWizard = - createEventMatcherWizard(tagId, logEventMatcherIndex); - - sp pullerManager = new StrictMock(); - EXPECT_CALL(*pullerManager, RegisterReceiver(tagId, kConfigKey, _, _, _)).WillOnce(Return()); - EXPECT_CALL(*pullerManager, UnRegisterReceiver(tagId, kConfigKey, _)).WillOnce(Return()); - EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _)) - .WillOnce(Return(false)) - .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, - vector>* data) { - EXPECT_EQ(eventTimeNs, partialBucketSplitTimeNs); - data->clear(); - data->push_back(CreateRepeatedValueLogEvent(tagId, eventTimeNs, 2)); - return true; - })); - - GaugeMetricProducer gaugeProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, {}, - wizard, protoHash, logEventMatcherIndex, eventMatcherWizard, - tagId, -1, tagId, bucketStartTimeNs, bucketStartTimeNs, - pullerManager); - gaugeProducer.prepareFirstBucket(); - - vector> allData; - allData.push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 1, 1)); - gaugeProducer.onDataPulled(allData, /** succeed */ true, bucketStartTimeNs); - ASSERT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->size()); - EXPECT_EQ(1, gaugeProducer.mCurrentSlicedBucket->begin() - ->second.front() - .mFields->begin() - ->mValue.int_value); - - switch (GetParam()) { - case APP_UPGRADE: - gaugeProducer.notifyAppUpgrade(partialBucketSplitTimeNs); - break; - case BOOT_COMPLETE: - gaugeProducer.onStatsdInitCompleted(partialBucketSplitTimeNs); - break; - } - ASSERT_EQ(1UL, gaugeProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size()); - EXPECT_EQ(bucketStartTimeNs, - gaugeProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY][0].mBucketStartNs); - EXPECT_EQ(partialBucketSplitTimeNs, - gaugeProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY][0].mBucketEndNs); - EXPECT_EQ(0L, gaugeProducer.mCurrentBucketNum); - EXPECT_EQ(partialBucketSplitTimeNs, gaugeProducer.mCurrentBucketStartTimeNs); - ASSERT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->size()); - EXPECT_EQ(2, gaugeProducer.mCurrentSlicedBucket->begin() - ->second.front() - .mFields->begin() - ->mValue.int_value); - - allData.clear(); - allData.push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + bucketSizeNs + 1, 3)); - gaugeProducer.onDataPulled(allData, /** succeed */ true, bucketStartTimeNs + bucketSizeNs); - ASSERT_EQ(2UL, gaugeProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size()); - ASSERT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->size()); - EXPECT_EQ(3, gaugeProducer.mCurrentSlicedBucket->begin() - ->second.front() - .mFields->begin() - ->mValue.int_value); -} - -TEST(GaugeMetricProducerTest, TestPulledWithAppUpgradeDisabled) { - GaugeMetric metric; - metric.set_id(metricId); - metric.set_bucket(ONE_MINUTE); - metric.set_max_pull_delay_sec(INT_MAX); - metric.set_split_bucket_for_app_upgrade(false); - auto gaugeFieldMatcher = metric.mutable_gauge_fields_filter()->mutable_fields(); - gaugeFieldMatcher->set_field(tagId); - gaugeFieldMatcher->add_child()->set_field(2); - - sp wizard = new NaggyMock(); - - sp eventMatcherWizard = - createEventMatcherWizard(tagId, logEventMatcherIndex); - - sp pullerManager = new StrictMock(); - EXPECT_CALL(*pullerManager, RegisterReceiver(tagId, kConfigKey, _, _, _)).WillOnce(Return()); - EXPECT_CALL(*pullerManager, UnRegisterReceiver(tagId, kConfigKey, _)).WillOnce(Return()); - EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, bucketStartTimeNs, _)) - .WillOnce(Return(false)); - - GaugeMetricProducer gaugeProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, {}, - wizard, protoHash, logEventMatcherIndex, eventMatcherWizard, - tagId, -1, tagId, bucketStartTimeNs, bucketStartTimeNs, - pullerManager); - gaugeProducer.prepareFirstBucket(); - - vector> allData; - allData.push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 1, 1)); - gaugeProducer.onDataPulled(allData, /** succeed */ true, bucketStartTimeNs); - ASSERT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->size()); - EXPECT_EQ(1, gaugeProducer.mCurrentSlicedBucket->begin() - ->second.front() - .mFields->begin() - ->mValue.int_value); - - gaugeProducer.notifyAppUpgrade(partialBucketSplitTimeNs); - ASSERT_EQ(0UL, gaugeProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size()); - EXPECT_EQ(0L, gaugeProducer.mCurrentBucketNum); - EXPECT_EQ(bucketStartTimeNs, gaugeProducer.mCurrentBucketStartTimeNs); - ASSERT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->size()); - EXPECT_EQ(1, gaugeProducer.mCurrentSlicedBucket->begin() - ->second.front() - .mFields->begin() - ->mValue.int_value); -} - -TEST(GaugeMetricProducerTest, TestPulledEventsWithCondition) { - GaugeMetric metric; - metric.set_id(metricId); - metric.set_bucket(ONE_MINUTE); - metric.set_max_pull_delay_sec(INT_MAX); - auto gaugeFieldMatcher = metric.mutable_gauge_fields_filter()->mutable_fields(); - gaugeFieldMatcher->set_field(tagId); - gaugeFieldMatcher->add_child()->set_field(2); - metric.set_condition(StringToId("SCREEN_ON")); - - sp wizard = new NaggyMock(); - - sp eventMatcherWizard = - createEventMatcherWizard(tagId, logEventMatcherIndex); - - int64_t conditionChangeNs = bucketStartTimeNs + 8; - - sp pullerManager = new StrictMock(); - EXPECT_CALL(*pullerManager, RegisterReceiver(tagId, kConfigKey, _, _, _)).WillOnce(Return()); - EXPECT_CALL(*pullerManager, UnRegisterReceiver(tagId, kConfigKey, _)).WillOnce(Return()); - EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, conditionChangeNs, _)) - .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, - vector>* data) { - data->clear(); - data->push_back(CreateRepeatedValueLogEvent(tagId, eventTimeNs + 10, 100)); - return true; - })); - - GaugeMetricProducer gaugeProducer(kConfigKey, metric, 0 /*condition index*/, - {ConditionState::kUnknown}, wizard, protoHash, - logEventMatcherIndex, eventMatcherWizard, tagId, -1, tagId, - bucketStartTimeNs, bucketStartTimeNs, pullerManager); - gaugeProducer.prepareFirstBucket(); - - gaugeProducer.onConditionChanged(true, conditionChangeNs); - ASSERT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->size()); - EXPECT_EQ(100, gaugeProducer.mCurrentSlicedBucket->begin() - ->second.front() - .mFields->begin() - ->mValue.int_value); - ASSERT_EQ(0UL, gaugeProducer.mPastBuckets.size()); - - vector> allData; - allData.clear(); - allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs + 1, 110)); - gaugeProducer.onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs); - - ASSERT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->size()); - EXPECT_EQ(110, gaugeProducer.mCurrentSlicedBucket->begin() - ->second.front() - .mFields->begin() - ->mValue.int_value); - ASSERT_EQ(1UL, gaugeProducer.mPastBuckets.size()); - EXPECT_EQ(100, gaugeProducer.mPastBuckets.begin() - ->second.back() - .mGaugeAtoms.front() - .mFields->begin() - ->mValue.int_value); - - gaugeProducer.onConditionChanged(false, bucket2StartTimeNs + 10); - gaugeProducer.flushIfNeededLocked(bucket3StartTimeNs + 10); - ASSERT_EQ(1UL, gaugeProducer.mPastBuckets.size()); - ASSERT_EQ(2UL, gaugeProducer.mPastBuckets.begin()->second.size()); - EXPECT_EQ(110L, gaugeProducer.mPastBuckets.begin() - ->second.back() - .mGaugeAtoms.front() - .mFields->begin() - ->mValue.int_value); -} - -TEST(GaugeMetricProducerTest, TestPulledEventsWithSlicedCondition) { - const int conditionTag = 65; - GaugeMetric metric; - metric.set_id(1111111); - metric.set_bucket(ONE_MINUTE); - metric.mutable_gauge_fields_filter()->set_include_all(true); - metric.set_condition(StringToId("APP_DIED")); - metric.set_max_pull_delay_sec(INT_MAX); - auto dim = metric.mutable_dimensions_in_what(); - dim->set_field(tagId); - dim->add_child()->set_field(1); - - sp eventMatcherWizard = - createEventMatcherWizard(tagId, logEventMatcherIndex); - - sp wizard = new NaggyMock(); - EXPECT_CALL(*wizard, query(_, _, _)) - .WillRepeatedly( - Invoke([](const int conditionIndex, const ConditionKey& conditionParameters, - const bool isPartialLink) { - int pos[] = {1, 0, 0}; - Field f(conditionTag, pos, 0); - HashableDimensionKey key; - key.mutableValues()->emplace_back(f, Value((int32_t)1000000)); - - return ConditionState::kTrue; - })); - - int64_t sliceConditionChangeNs = bucketStartTimeNs + 8; - - sp pullerManager = new StrictMock(); - EXPECT_CALL(*pullerManager, RegisterReceiver(tagId, kConfigKey, _, _, _)).WillOnce(Return()); - EXPECT_CALL(*pullerManager, UnRegisterReceiver(tagId, kConfigKey, _)).WillOnce(Return()); - EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, sliceConditionChangeNs, _)) - .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, - vector>* data) { - data->clear(); - data->push_back(CreateTwoValueLogEvent(tagId, eventTimeNs + 10, 1000, 100)); - return true; - })); - - GaugeMetricProducer gaugeProducer(kConfigKey, metric, 0 /*condition index*/, - {ConditionState::kUnknown}, wizard, protoHash, - logEventMatcherIndex, eventMatcherWizard, tagId, -1, tagId, - bucketStartTimeNs, bucketStartTimeNs, pullerManager); - gaugeProducer.prepareFirstBucket(); - - gaugeProducer.onSlicedConditionMayChange(true, sliceConditionChangeNs); - - ASSERT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->size()); - const auto& key = gaugeProducer.mCurrentSlicedBucket->begin()->first; - ASSERT_EQ(1UL, key.getDimensionKeyInWhat().getValues().size()); - EXPECT_EQ(1000, key.getDimensionKeyInWhat().getValues()[0].mValue.int_value); - - ASSERT_EQ(0UL, gaugeProducer.mPastBuckets.size()); - - vector> allData; - allData.clear(); - allData.push_back(CreateTwoValueLogEvent(tagId, bucket2StartTimeNs + 1, 1000, 110)); - gaugeProducer.onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs); - - ASSERT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->size()); - ASSERT_EQ(1UL, gaugeProducer.mPastBuckets.size()); -} - -TEST(GaugeMetricProducerTest, TestPulledEventsAnomalyDetection) { - sp alarmMonitor; - sp wizard = new NaggyMock(); - - sp pullerManager = new StrictMock(); - EXPECT_CALL(*pullerManager, RegisterReceiver(tagId, kConfigKey, _, _, _)).WillOnce(Return()); - EXPECT_CALL(*pullerManager, UnRegisterReceiver(tagId, kConfigKey, _)).WillOnce(Return()); - EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, bucketStartTimeNs, _)) - .WillOnce(Return(false)); - - GaugeMetric metric; - metric.set_id(metricId); - metric.set_bucket(ONE_MINUTE); - metric.set_max_pull_delay_sec(INT_MAX); - auto gaugeFieldMatcher = metric.mutable_gauge_fields_filter()->mutable_fields(); - gaugeFieldMatcher->set_field(tagId); - gaugeFieldMatcher->add_child()->set_field(2); - - sp eventMatcherWizard = - createEventMatcherWizard(tagId, logEventMatcherIndex); - - GaugeMetricProducer gaugeProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, {}, - wizard, protoHash, logEventMatcherIndex, eventMatcherWizard, - tagId, -1, tagId, bucketStartTimeNs, bucketStartTimeNs, - pullerManager); - gaugeProducer.prepareFirstBucket(); - - Alert alert; - alert.set_id(101); - alert.set_metric_id(metricId); - alert.set_trigger_if_sum_gt(25); - alert.set_num_buckets(2); - const int32_t refPeriodSec = 60; - alert.set_refractory_period_secs(refPeriodSec); - sp anomalyTracker = - gaugeProducer.addAnomalyTracker(alert, alarmMonitor, UPDATE_NEW, bucketStartTimeNs); - - int tagId = 1; - vector> allData; - allData.clear(); - allData.push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 1, 13)); - gaugeProducer.onDataPulled(allData, /** succeed */ true, bucketStartTimeNs); - ASSERT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->size()); - EXPECT_EQ(13L, gaugeProducer.mCurrentSlicedBucket->begin() - ->second.front() - .mFields->begin() - ->mValue.int_value); - EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(DEFAULT_METRIC_DIMENSION_KEY), 0U); - - std::shared_ptr event2 = - CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + bucketSizeNs + 20, 15); - - allData.clear(); - allData.push_back(event2); - gaugeProducer.onDataPulled(allData, /** succeed */ true, bucketStartTimeNs + bucketSizeNs); - ASSERT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->size()); - EXPECT_EQ(15L, gaugeProducer.mCurrentSlicedBucket->begin() - ->second.front() - .mFields->begin() - ->mValue.int_value); - EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(DEFAULT_METRIC_DIMENSION_KEY), - std::ceil(1.0 * event2->GetElapsedTimestampNs() / NS_PER_SEC) + refPeriodSec); - - allData.clear(); - allData.push_back( - CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 2 * bucketSizeNs + 10, 26)); - gaugeProducer.onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs + 2 * bucketSizeNs); - ASSERT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->size()); - EXPECT_EQ(26L, gaugeProducer.mCurrentSlicedBucket->begin() - ->second.front() - .mFields->begin() - ->mValue.int_value); - EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(DEFAULT_METRIC_DIMENSION_KEY), - std::ceil(1.0 * event2->GetElapsedTimestampNs() / NS_PER_SEC + refPeriodSec)); - - // This event does not have the gauge field. Thus the current bucket value is 0. - allData.clear(); - allData.push_back(CreateNoValuesLogEvent(tagId, bucketStartTimeNs + 3 * bucketSizeNs + 10)); - gaugeProducer.onDataPulled(allData, /** succeed */ true, bucketStartTimeNs + 3 * bucketSizeNs); - ASSERT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->size()); - EXPECT_TRUE(gaugeProducer.mCurrentSlicedBucket->begin()->second.front().mFields->empty()); -} - -TEST(GaugeMetricProducerTest, TestPullOnTrigger) { - GaugeMetric metric; - metric.set_id(metricId); - metric.set_bucket(ONE_MINUTE); - metric.set_sampling_type(GaugeMetric::FIRST_N_SAMPLES); - metric.mutable_gauge_fields_filter()->set_include_all(false); - metric.set_max_pull_delay_sec(INT_MAX); - auto gaugeFieldMatcher = metric.mutable_gauge_fields_filter()->mutable_fields(); - gaugeFieldMatcher->set_field(tagId); - gaugeFieldMatcher->add_child()->set_field(1); - - sp wizard = new NaggyMock(); - - sp eventMatcherWizard = - createEventMatcherWizard(tagId, logEventMatcherIndex); - - sp pullerManager = new StrictMock(); - EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _)) - .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, - vector>* data) { - EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 10); - data->clear(); - data->push_back(CreateRepeatedValueLogEvent(tagId, eventTimeNs, 4)); - return true; - })) - .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, - vector>* data) { - EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 20); - data->clear(); - data->push_back(CreateRepeatedValueLogEvent(tagId, eventTimeNs, 5)); - return true; - })) - .WillOnce(Return(true)); - - int triggerId = 5; - GaugeMetricProducer gaugeProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, {}, - wizard, protoHash, logEventMatcherIndex, eventMatcherWizard, - tagId, triggerId, tagId, bucketStartTimeNs, bucketStartTimeNs, - pullerManager); - gaugeProducer.prepareFirstBucket(); - - ASSERT_EQ(0UL, gaugeProducer.mCurrentSlicedBucket->size()); - - LogEvent triggerEvent(/*uid=*/0, /*pid=*/0); - CreateNoValuesLogEvent(&triggerEvent, triggerId, bucketStartTimeNs + 10); - gaugeProducer.onMatchedLogEvent(1 /*log matcher index*/, triggerEvent); - ASSERT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->begin()->second.size()); - triggerEvent.setElapsedTimestampNs(bucketStartTimeNs + 20); - gaugeProducer.onMatchedLogEvent(1 /*log matcher index*/, triggerEvent); - ASSERT_EQ(2UL, gaugeProducer.mCurrentSlicedBucket->begin()->second.size()); - triggerEvent.setElapsedTimestampNs(bucket2StartTimeNs + 1); - gaugeProducer.onMatchedLogEvent(1 /*log matcher index*/, triggerEvent); - - ASSERT_EQ(1UL, gaugeProducer.mPastBuckets.size()); - ASSERT_EQ(2UL, gaugeProducer.mPastBuckets.begin()->second.back().mGaugeAtoms.size()); - EXPECT_EQ(4, gaugeProducer.mPastBuckets.begin() - ->second.back() - .mGaugeAtoms[0] - .mFields->begin() - ->mValue.int_value); - EXPECT_EQ(5, gaugeProducer.mPastBuckets.begin() - ->second.back() - .mGaugeAtoms[1] - .mFields->begin() - ->mValue.int_value); -} - -TEST(GaugeMetricProducerTest, TestRemoveDimensionInOutput) { - GaugeMetric metric; - metric.set_id(metricId); - metric.set_bucket(ONE_MINUTE); - metric.set_sampling_type(GaugeMetric::FIRST_N_SAMPLES); - metric.mutable_gauge_fields_filter()->set_include_all(true); - metric.set_max_pull_delay_sec(INT_MAX); - auto dimensionMatcher = metric.mutable_dimensions_in_what(); - // use field 1 as dimension. - dimensionMatcher->set_field(tagId); - dimensionMatcher->add_child()->set_field(1); - - sp wizard = new NaggyMock(); - - sp eventMatcherWizard = - createEventMatcherWizard(tagId, logEventMatcherIndex); - - sp pullerManager = new StrictMock(); - EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _)) - .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, - vector>* data) { - EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 3); - data->clear(); - data->push_back(CreateTwoValueLogEvent(tagId, eventTimeNs, 3, 4)); - return true; - })) - .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, - vector>* data) { - EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 10); - data->clear(); - data->push_back(CreateTwoValueLogEvent(tagId, eventTimeNs, 4, 5)); - return true; - })) - .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, - vector>* data) { - EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 20); - data->clear(); - data->push_back(CreateTwoValueLogEvent(tagId, eventTimeNs, 4, 6)); - return true; - })) - .WillOnce(Return(true)); - - int triggerId = 5; - GaugeMetricProducer gaugeProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, {}, - wizard, protoHash, logEventMatcherIndex, eventMatcherWizard, - tagId, triggerId, tagId, bucketStartTimeNs, bucketStartTimeNs, - pullerManager); - gaugeProducer.prepareFirstBucket(); - - LogEvent triggerEvent(/*uid=*/0, /*pid=*/0); - CreateNoValuesLogEvent(&triggerEvent, triggerId, bucketStartTimeNs + 3); - gaugeProducer.onMatchedLogEvent(1 /*log matcher index*/, triggerEvent); - ASSERT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->size()); - triggerEvent.setElapsedTimestampNs(bucketStartTimeNs + 10); - gaugeProducer.onMatchedLogEvent(1 /*log matcher index*/, triggerEvent); - ASSERT_EQ(2UL, gaugeProducer.mCurrentSlicedBucket->size()); - ASSERT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->begin()->second.size()); - triggerEvent.setElapsedTimestampNs(bucketStartTimeNs + 20); - gaugeProducer.onMatchedLogEvent(1 /*log matcher index*/, triggerEvent); - ASSERT_EQ(2UL, gaugeProducer.mCurrentSlicedBucket->begin()->second.size()); - triggerEvent.setElapsedTimestampNs(bucket2StartTimeNs + 1); - gaugeProducer.onMatchedLogEvent(1 /*log matcher index*/, triggerEvent); - - ASSERT_EQ(2UL, gaugeProducer.mPastBuckets.size()); - auto bucketIt = gaugeProducer.mPastBuckets.begin(); - ASSERT_EQ(1UL, bucketIt->second.back().mGaugeAtoms.size()); - EXPECT_EQ(3, bucketIt->first.getDimensionKeyInWhat().getValues().begin()->mValue.int_value); - EXPECT_EQ(4, bucketIt->second.back().mGaugeAtoms[0].mFields->begin()->mValue.int_value); - bucketIt++; - ASSERT_EQ(2UL, bucketIt->second.back().mGaugeAtoms.size()); - EXPECT_EQ(4, bucketIt->first.getDimensionKeyInWhat().getValues().begin()->mValue.int_value); - EXPECT_EQ(5, bucketIt->second.back().mGaugeAtoms[0].mFields->begin()->mValue.int_value); - EXPECT_EQ(6, bucketIt->second.back().mGaugeAtoms[1].mFields->begin()->mValue.int_value); -} - -/* - * Test that BUCKET_TOO_SMALL dump reason is logged when a flushed bucket size - * is smaller than the "min_bucket_size_nanos" specified in the metric config. - */ -TEST(GaugeMetricProducerTest_BucketDrop, TestBucketDropWhenBucketTooSmall) { - GaugeMetric metric; - metric.set_id(metricId); - metric.set_bucket(FIVE_MINUTES); - metric.set_sampling_type(GaugeMetric::FIRST_N_SAMPLES); - metric.set_min_bucket_size_nanos(10000000000); // 10 seconds - - sp wizard = new NaggyMock(); - - sp eventMatcherWizard = - createEventMatcherWizard(tagId, logEventMatcherIndex); - - sp pullerManager = new StrictMock(); - EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, bucketStartTimeNs + 3, _)) - // Bucket start. - .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, - vector>* data) { - data->clear(); - data->push_back(CreateRepeatedValueLogEvent(tagId, eventTimeNs, 10)); - return true; - })); - - int triggerId = 5; - GaugeMetricProducer gaugeProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, {}, - wizard, protoHash, logEventMatcherIndex, eventMatcherWizard, - tagId, triggerId, tagId, bucketStartTimeNs, bucketStartTimeNs, - pullerManager); - gaugeProducer.prepareFirstBucket(); - - LogEvent triggerEvent(/*uid=*/0, /*pid=*/0); - CreateNoValuesLogEvent(&triggerEvent, triggerId, bucketStartTimeNs + 3); - gaugeProducer.onMatchedLogEvent(1 /*log matcher index*/, triggerEvent); - - // Check dump report. - ProtoOutputStream output; - std::set strSet; - gaugeProducer.onDumpReport(bucketStartTimeNs + 9000000, true /* include recent buckets */, true, - FAST /* dump_latency */, &strSet, &output); - - StatsLogReport report = outputStreamToProto(&output); - EXPECT_TRUE(report.has_gauge_metrics()); - ASSERT_EQ(0, report.gauge_metrics().data_size()); - ASSERT_EQ(1, report.gauge_metrics().skipped_size()); - - EXPECT_EQ(NanoToMillis(bucketStartTimeNs), - report.gauge_metrics().skipped(0).start_bucket_elapsed_millis()); - EXPECT_EQ(NanoToMillis(bucketStartTimeNs + 9000000), - report.gauge_metrics().skipped(0).end_bucket_elapsed_millis()); - ASSERT_EQ(1, report.gauge_metrics().skipped(0).drop_event_size()); - - auto dropEvent = report.gauge_metrics().skipped(0).drop_event(0); - EXPECT_EQ(BucketDropReason::BUCKET_TOO_SMALL, dropEvent.drop_reason()); - EXPECT_EQ(NanoToMillis(bucketStartTimeNs + 9000000), dropEvent.drop_time_millis()); -} - -} // namespace statsd -} // namespace os -} // namespace android -#else -GTEST_LOG_(INFO) << "This test does nothing.\n"; -#endif diff --git a/bin/tests/metrics/MaxDurationTracker_test.cpp b/bin/tests/metrics/MaxDurationTracker_test.cpp deleted file mode 100644 index 25eaf887..00000000 --- a/bin/tests/metrics/MaxDurationTracker_test.cpp +++ /dev/null @@ -1,459 +0,0 @@ -// 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 "src/metrics/duration_helper/MaxDurationTracker.h" -#include "src/condition/ConditionWizard.h" -#include "metrics_test_helper.h" -#include "tests/statsd_test_util.h" - -#include -#include -#include -#include -#include -#include - -using namespace android::os::statsd; -using namespace testing; -using android::sp; -using std::set; -using std::unordered_map; -using std::vector; - -#ifdef __ANDROID__ - -namespace android { -namespace os { -namespace statsd { - -const ConfigKey kConfigKey(0, 12345); - -const int TagId = 1; -const int64_t metricId = 123; -const optional emptyThreshold; - -const MetricDimensionKey eventKey = getMockedMetricDimensionKey(TagId, 0, "1"); -const HashableDimensionKey conditionKey = getMockedDimensionKey(TagId, 4, "1"); -const HashableDimensionKey key1 = getMockedDimensionKey(TagId, 1, "1"); -const HashableDimensionKey key2 = getMockedDimensionKey(TagId, 1, "2"); -const int64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL; - -TEST(MaxDurationTrackerTest, TestSimpleMaxDuration) { - const MetricDimensionKey eventKey = getMockedMetricDimensionKey(TagId, 0, "1"); - const HashableDimensionKey key1 = getMockedDimensionKey(TagId, 1, "1"); - const HashableDimensionKey key2 = getMockedDimensionKey(TagId, 1, "2"); - - - sp wizard = new NaggyMock(); - - unordered_map> buckets; - - int64_t bucketStartTimeNs = 10000000000; - int64_t bucketEndTimeNs = bucketStartTimeNs + bucketSizeNs; - int64_t bucketNum = 0; - - int64_t metricId = 1; - MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, -1, false, bucketStartTimeNs, - bucketNum, bucketStartTimeNs, bucketSizeNs, false, false, {}); - - tracker.noteStart(key1, true, bucketStartTimeNs, ConditionKey()); - // Event starts again. This would not change anything as it already starts. - tracker.noteStart(key1, true, bucketStartTimeNs + 3, ConditionKey()); - // Stopped. - tracker.noteStop(key1, bucketStartTimeNs + 10, false); - - // Another event starts in this bucket. - tracker.noteStart(key2, true, bucketStartTimeNs + 20, ConditionKey()); - tracker.noteStop(key2, bucketStartTimeNs + 40, false /*stop all*/); - - tracker.flushIfNeeded(bucketStartTimeNs + bucketSizeNs + 1, emptyThreshold, &buckets); - EXPECT_TRUE(buckets.find(eventKey) != buckets.end()); - ASSERT_EQ(1u, buckets[eventKey].size()); - EXPECT_EQ(20LL, buckets[eventKey][0].mDuration); -} - -TEST(MaxDurationTrackerTest, TestStopAll) { - const MetricDimensionKey eventKey = getMockedMetricDimensionKey(TagId, 0, "1"); - const HashableDimensionKey key1 = getMockedDimensionKey(TagId, 1, "1"); - const HashableDimensionKey key2 = getMockedDimensionKey(TagId, 1, "2"); - - sp wizard = new NaggyMock(); - - unordered_map> buckets; - - int64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL; - int64_t bucketStartTimeNs = 10000000000; - int64_t bucketEndTimeNs = bucketStartTimeNs + bucketSizeNs; - int64_t bucketNum = 0; - - int64_t metricId = 1; - MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, -1, false, bucketStartTimeNs, - bucketNum, bucketStartTimeNs, bucketSizeNs, false, false, {}); - - tracker.noteStart(key1, true, bucketStartTimeNs + 1, ConditionKey()); - - // Another event starts in this bucket. - tracker.noteStart(key2, true, bucketStartTimeNs + 20, ConditionKey()); - tracker.flushIfNeeded(bucketStartTimeNs + bucketSizeNs + 40, emptyThreshold, &buckets); - tracker.noteStopAll(bucketStartTimeNs + bucketSizeNs + 40); - EXPECT_TRUE(tracker.mInfos.empty()); - EXPECT_TRUE(buckets.find(eventKey) == buckets.end()); - - tracker.flushIfNeeded(bucketStartTimeNs + 3 * bucketSizeNs + 40, emptyThreshold, &buckets); - EXPECT_TRUE(buckets.find(eventKey) != buckets.end()); - ASSERT_EQ(1u, buckets[eventKey].size()); - EXPECT_EQ(bucketSizeNs + 40 - 1, buckets[eventKey][0].mDuration); - EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, buckets[eventKey][0].mBucketStartNs); - EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs, buckets[eventKey][0].mBucketEndNs); -} - -TEST(MaxDurationTrackerTest, TestCrossBucketBoundary) { - const MetricDimensionKey eventKey = getMockedMetricDimensionKey(TagId, 0, "1"); - const HashableDimensionKey key1 = getMockedDimensionKey(TagId, 1, "1"); - const HashableDimensionKey key2 = getMockedDimensionKey(TagId, 1, "2"); - sp wizard = new NaggyMock(); - - unordered_map> buckets; - - int64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL; - int64_t bucketStartTimeNs = 10000000000; - int64_t bucketEndTimeNs = bucketStartTimeNs + bucketSizeNs; - int64_t bucketNum = 0; - - int64_t metricId = 1; - MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, -1, false, bucketStartTimeNs, - bucketNum, bucketStartTimeNs, bucketSizeNs, false, false, {}); - - // The event starts. - tracker.noteStart(DEFAULT_DIMENSION_KEY, true, bucketStartTimeNs + 1, ConditionKey()); - - // Starts again. Does not DEFAULT_DIMENSION_KEY anything. - tracker.noteStart(DEFAULT_DIMENSION_KEY, true, bucketStartTimeNs + bucketSizeNs + 1, - ConditionKey()); - - // The event stops at early 4th bucket. - // Notestop is called from DurationMetricProducer's onMatchedLogEvent, which calls - // flushIfneeded. - tracker.flushIfNeeded(bucketStartTimeNs + (3 * bucketSizeNs) + 20, emptyThreshold, &buckets); - tracker.noteStop(DEFAULT_DIMENSION_KEY, bucketStartTimeNs + (3 * bucketSizeNs) + 20, - false /*stop all*/); - EXPECT_TRUE(buckets.find(eventKey) == buckets.end()); - - tracker.flushIfNeeded(bucketStartTimeNs + 4 * bucketSizeNs, emptyThreshold, &buckets); - ASSERT_EQ(1u, buckets[eventKey].size()); - EXPECT_EQ((3 * bucketSizeNs) + 20 - 1, buckets[eventKey][0].mDuration); - EXPECT_EQ(bucketStartTimeNs + 3 * bucketSizeNs, buckets[eventKey][0].mBucketStartNs); - EXPECT_EQ(bucketStartTimeNs + 4 * bucketSizeNs, buckets[eventKey][0].mBucketEndNs); -} - -TEST(MaxDurationTrackerTest, TestCrossBucketBoundary_nested) { - const MetricDimensionKey eventKey = getMockedMetricDimensionKey(TagId, 0, "1"); - const HashableDimensionKey key1 = getMockedDimensionKey(TagId, 1, "1"); - const HashableDimensionKey key2 = getMockedDimensionKey(TagId, 1, "2"); - sp wizard = new NaggyMock(); - - unordered_map> buckets; - - int64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL; - int64_t bucketStartTimeNs = 10000000000; - int64_t bucketEndTimeNs = bucketStartTimeNs + bucketSizeNs; - int64_t bucketNum = 0; - - int64_t metricId = 1; - MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, -1, true, bucketStartTimeNs, - bucketNum, bucketStartTimeNs, bucketSizeNs, false, false, {}); - - // 2 starts - tracker.noteStart(DEFAULT_DIMENSION_KEY, true, bucketStartTimeNs + 1, ConditionKey()); - tracker.noteStart(DEFAULT_DIMENSION_KEY, true, bucketStartTimeNs + 10, ConditionKey()); - // one stop - tracker.noteStop(DEFAULT_DIMENSION_KEY, bucketStartTimeNs + 20, false /*stop all*/); - - tracker.flushIfNeeded(bucketStartTimeNs + (2 * bucketSizeNs) + 1, emptyThreshold, &buckets); - // Because of nesting, still not stopped. - EXPECT_TRUE(buckets.find(eventKey) == buckets.end()); - - // real stop now. - tracker.noteStop(DEFAULT_DIMENSION_KEY, - bucketStartTimeNs + (2 * bucketSizeNs) + 5, false); - tracker.flushIfNeeded(bucketStartTimeNs + (3 * bucketSizeNs) + 1, emptyThreshold, &buckets); - - ASSERT_EQ(1u, buckets[eventKey].size()); - EXPECT_EQ(2 * bucketSizeNs + 5 - 1, buckets[eventKey][0].mDuration); -} - -TEST(MaxDurationTrackerTest, TestMaxDurationWithCondition) { - const HashableDimensionKey conditionDimKey = key1; - - sp wizard = new NaggyMock(); - - ConditionKey conditionKey1; - MetricDimensionKey eventKey = getMockedMetricDimensionKey(TagId, 1, "1"); - conditionKey1[StringToId("APP_BACKGROUND")] = conditionDimKey; - - /** - Start in first bucket, stop in second bucket. Condition turns on and off in the first bucket - and again turns on and off in the second bucket. - */ - int64_t bucketStartTimeNs = 10000000000; - int64_t bucketEndTimeNs = bucketStartTimeNs + bucketSizeNs; - int64_t eventStartTimeNs = bucketStartTimeNs + 1 * NS_PER_SEC; - int64_t conditionStarts1 = bucketStartTimeNs + 11 * NS_PER_SEC; - int64_t conditionStops1 = bucketStartTimeNs + 14 * NS_PER_SEC; - int64_t conditionStarts2 = bucketStartTimeNs + bucketSizeNs + 5 * NS_PER_SEC; - int64_t conditionStops2 = conditionStarts2 + 10 * NS_PER_SEC; - int64_t eventStopTimeNs = conditionStops2 + 8 * NS_PER_SEC; - - int64_t metricId = 1; - MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, false, bucketStartTimeNs, - 0, bucketStartTimeNs, bucketSizeNs, true, false, {}); - EXPECT_TRUE(tracker.mAnomalyTrackers.empty()); - - tracker.noteStart(key1, false, eventStartTimeNs, conditionKey1); - tracker.noteConditionChanged(key1, true, conditionStarts1); - tracker.noteConditionChanged(key1, false, conditionStops1); - unordered_map> buckets; - tracker.flushIfNeeded(bucketStartTimeNs + bucketSizeNs + 1, emptyThreshold, &buckets); - ASSERT_EQ(0U, buckets.size()); - - tracker.noteConditionChanged(key1, true, conditionStarts2); - tracker.noteConditionChanged(key1, false, conditionStops2); - tracker.noteStop(key1, eventStopTimeNs, false); - tracker.flushIfNeeded(bucketStartTimeNs + 2 * bucketSizeNs + 1, emptyThreshold, &buckets); - ASSERT_EQ(1U, buckets.size()); - vector item = buckets.begin()->second; - ASSERT_EQ(1UL, item.size()); - EXPECT_EQ((int64_t)(13LL * NS_PER_SEC), item[0].mDuration); -} - -TEST(MaxDurationTrackerTest, TestAnomalyDetection) { - sp wizard = new NaggyMock(); - - ConditionKey conditionKey1; - MetricDimensionKey eventKey = getMockedMetricDimensionKey(TagId, 2, "maps"); - conditionKey1[StringToId("APP_BACKGROUND")] = conditionKey; - - unordered_map> buckets; - - int64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL; - int64_t bucketStartTimeNs = 10000000000; - int64_t bucketEndTimeNs = bucketStartTimeNs + bucketSizeNs; - int64_t bucketNum = 0; - int64_t eventStartTimeNs = 13000000000; - int64_t durationTimeNs = 2 * 1000; - - int64_t metricId = 1; - Alert alert; - alert.set_id(101); - alert.set_metric_id(1); - alert.set_trigger_if_sum_gt(40 * NS_PER_SEC); - alert.set_num_buckets(2); - const int32_t refPeriodSec = 45; - alert.set_refractory_period_secs(refPeriodSec); - sp alarmMonitor; - sp anomalyTracker = - new DurationAnomalyTracker(alert, kConfigKey, alarmMonitor); - MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, false, bucketStartTimeNs, - bucketNum, bucketStartTimeNs, bucketSizeNs, true, false, - {anomalyTracker}); - - tracker.noteStart(key1, true, eventStartTimeNs, conditionKey1); - sp alarm = anomalyTracker->mAlarms.begin()->second; - EXPECT_EQ((long long)(53ULL * NS_PER_SEC), (long long)(alarm->timestampSec * NS_PER_SEC)); - - // Remove the anomaly alarm when the duration is no longer fully met. - tracker.noteConditionChanged(key1, false, eventStartTimeNs + 15 * NS_PER_SEC); - ASSERT_EQ(0U, anomalyTracker->mAlarms.size()); - - // Since the condition was off for 10 seconds, the anomaly should trigger 10 sec later. - tracker.noteConditionChanged(key1, true, eventStartTimeNs + 25 * NS_PER_SEC); - ASSERT_EQ(1U, anomalyTracker->mAlarms.size()); - alarm = anomalyTracker->mAlarms.begin()->second; - EXPECT_EQ((long long)(63ULL * NS_PER_SEC), (long long)(alarm->timestampSec * NS_PER_SEC)); -} - -// This tests that we correctly compute the predicted time of an anomaly assuming that the current -// state continues forward as-is. -TEST(MaxDurationTrackerTest, TestAnomalyPredictedTimestamp) { - sp wizard = new NaggyMock(); - - ConditionKey conditionKey1; - MetricDimensionKey eventKey = getMockedMetricDimensionKey(TagId, 2, "maps"); - conditionKey1[StringToId("APP_BACKGROUND")] = conditionKey; - ConditionKey conditionKey2; - conditionKey2[StringToId("APP_BACKGROUND")] = getMockedDimensionKey(TagId, 4, "2"); - - unordered_map> buckets; - - /** - * Suppose we have two sub-dimensions that we're taking the MAX over. In the first of these - * nested dimensions, we enter the pause state after 3 seconds. When we resume, the second - * dimension has already been running for 4 seconds. Thus, we have 40-4=36 seconds remaining - * before we trigger the anomaly. - */ - int64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL; - int64_t bucketStartTimeNs = 10000000000; - int64_t bucketEndTimeNs = bucketStartTimeNs + bucketSizeNs; - int64_t bucketNum = 0; - int64_t eventStartTimeNs = bucketStartTimeNs + 5 * NS_PER_SEC; // Condition is off at start. - int64_t conditionStarts1 = bucketStartTimeNs + 11 * NS_PER_SEC; - int64_t conditionStops1 = bucketStartTimeNs + 14 * NS_PER_SEC; - int64_t conditionStarts2 = bucketStartTimeNs + 20 * NS_PER_SEC; - int64_t eventStartTimeNs2 = conditionStarts2 - 4 * NS_PER_SEC; - - int64_t metricId = 1; - Alert alert; - alert.set_id(101); - alert.set_metric_id(1); - alert.set_trigger_if_sum_gt(40 * NS_PER_SEC); - alert.set_num_buckets(2); - const int32_t refPeriodSec = 45; - alert.set_refractory_period_secs(refPeriodSec); - sp alarmMonitor; - sp anomalyTracker = - new DurationAnomalyTracker(alert, kConfigKey, alarmMonitor); - MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, false, bucketStartTimeNs, - bucketNum, bucketStartTimeNs, bucketSizeNs, true, false, - {anomalyTracker}); - - tracker.noteStart(key1, false, eventStartTimeNs, conditionKey1); - tracker.noteConditionChanged(key1, true, conditionStarts1); - tracker.noteConditionChanged(key1, false, conditionStops1); - tracker.noteStart(key2, true, eventStartTimeNs2, conditionKey2); // Condition is on already. - tracker.noteConditionChanged(key1, true, conditionStarts2); - ASSERT_EQ(1U, anomalyTracker->mAlarms.size()); - auto alarm = anomalyTracker->mAlarms.begin()->second; - int64_t anomalyFireTimeSec = alarm->timestampSec; - EXPECT_EQ(conditionStarts2 + 36 * NS_PER_SEC, - (long long)anomalyFireTimeSec * NS_PER_SEC); - - // Now we test the calculation now that there's a refractory period. - // At the correct time, declare the anomaly. This will set a refractory period. Make sure it - // gets correctly taken into account in future predictAnomalyTimestampNs calculations. - std::unordered_set, SpHash> firedAlarms({alarm}); - anomalyTracker->informAlarmsFired(anomalyFireTimeSec * NS_PER_SEC, firedAlarms); - ASSERT_EQ(0u, anomalyTracker->mAlarms.size()); - int64_t refractoryPeriodEndsSec = anomalyFireTimeSec + refPeriodSec; - EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(eventKey), refractoryPeriodEndsSec); - - // Now stop and start again. Make sure the new predictAnomalyTimestampNs takes into account - // the refractory period correctly. - int64_t eventStopTimeNs = anomalyFireTimeSec * NS_PER_SEC + 10; - tracker.noteStop(key1, eventStopTimeNs, false); - tracker.noteStop(key2, eventStopTimeNs, false); - tracker.noteStart(key1, true, eventStopTimeNs + 1000000, conditionKey1); - // Anomaly is ongoing, but we're still in the refractory period. - ASSERT_EQ(1U, anomalyTracker->mAlarms.size()); - alarm = anomalyTracker->mAlarms.begin()->second; - EXPECT_EQ(refractoryPeriodEndsSec, (long long)(alarm->timestampSec)); - - // Makes sure it is correct after the refractory period is over. - tracker.noteStop(key1, eventStopTimeNs + 2000000, false); - int64_t justBeforeRefPeriodNs = (refractoryPeriodEndsSec - 2) * NS_PER_SEC; - tracker.noteStart(key1, true, justBeforeRefPeriodNs, conditionKey1); - alarm = anomalyTracker->mAlarms.begin()->second; - EXPECT_EQ(justBeforeRefPeriodNs + 40 * NS_PER_SEC, - (unsigned long long)(alarm->timestampSec * NS_PER_SEC)); -} - -// Suppose that within one tracker there are two dimensions A and B. -// Suppose A starts, then B starts, and then A stops. We still need to set an anomaly based on the -// elapsed duration of B. -TEST(MaxDurationTrackerTest, TestAnomalyPredictedTimestamp_UpdatedOnStop) { - sp wizard = new NaggyMock(); - - ConditionKey conditionKey1; - MetricDimensionKey eventKey = getMockedMetricDimensionKey(TagId, 2, "maps"); - conditionKey1[StringToId("APP_BACKGROUND")] = conditionKey; - ConditionKey conditionKey2; - conditionKey2[StringToId("APP_BACKGROUND")] = getMockedDimensionKey(TagId, 4, "2"); - - unordered_map> buckets; - - /** - * Suppose we have two sub-dimensions that we're taking the MAX over. In the first of these - * nested dimensions, are started for 8 seconds. When we stop, the other nested dimension has - * been started for 5 seconds. So we can only allow 35 more seconds from now. - */ - int64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL; - int64_t bucketStartTimeNs = 10000000000; - int64_t bucketEndTimeNs = bucketStartTimeNs + bucketSizeNs; - int64_t bucketNum = 0; - int64_t eventStartTimeNs1 = bucketStartTimeNs + 5 * NS_PER_SEC; // Condition is off at start. - int64_t eventStopTimeNs1 = bucketStartTimeNs + 13 * NS_PER_SEC; - int64_t eventStartTimeNs2 = bucketStartTimeNs + 8 * NS_PER_SEC; - - int64_t metricId = 1; - Alert alert; - alert.set_id(101); - alert.set_metric_id(1); - alert.set_trigger_if_sum_gt(40 * NS_PER_SEC); - alert.set_num_buckets(2); - const int32_t refPeriodSec = 45; - alert.set_refractory_period_secs(refPeriodSec); - sp alarmMonitor; - sp anomalyTracker = - new DurationAnomalyTracker(alert, kConfigKey, alarmMonitor); - MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, false, bucketStartTimeNs, - bucketNum, bucketStartTimeNs, bucketSizeNs, true, false, - {anomalyTracker}); - - tracker.noteStart(key1, true, eventStartTimeNs1, conditionKey1); - tracker.noteStart(key2, true, eventStartTimeNs2, conditionKey2); - tracker.noteStop(key1, eventStopTimeNs1, false); - ASSERT_EQ(1U, anomalyTracker->mAlarms.size()); - auto alarm = anomalyTracker->mAlarms.begin()->second; - EXPECT_EQ(eventStopTimeNs1 + 35 * NS_PER_SEC, - (unsigned long long)(alarm->timestampSec * NS_PER_SEC)); -} - -TEST(MaxDurationTrackerTest, TestUploadThreshold) { - sp wizard = new NaggyMock(); - - unordered_map> buckets; - - int64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL; - int64_t bucketStartTimeNs = 10000000000; - int64_t bucketNum = 0; - int64_t eventStartTimeNs = bucketStartTimeNs + 1; - int64_t event2StartTimeNs = bucketStartTimeNs + bucketSizeNs + 1; - int64_t thresholdDurationNs = 2000; - - UploadThreshold threshold; - threshold.set_gt_int(thresholdDurationNs); - - MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, false, bucketStartTimeNs, - bucketNum, bucketStartTimeNs, bucketSizeNs, false, false, {}); - - // Duration below the gt_int threshold should not be added to past buckets. - tracker.noteStart(key1, true, eventStartTimeNs, ConditionKey()); - tracker.noteStop(key1, eventStartTimeNs + thresholdDurationNs, false); - tracker.flushIfNeeded(eventStartTimeNs + bucketSizeNs + 1, threshold, &buckets); - EXPECT_TRUE(buckets.find(eventKey) == buckets.end()); - - // Duration above the gt_int threshold should be added to past buckets. - tracker.noteStart(key1, true, event2StartTimeNs, ConditionKey()); - tracker.noteStop(key1, event2StartTimeNs + thresholdDurationNs + 1, false); - tracker.flushIfNeeded(event2StartTimeNs + bucketSizeNs + 1, threshold, &buckets); - EXPECT_TRUE(buckets.find(eventKey) != buckets.end()); - ASSERT_EQ(1u, buckets[eventKey].size()); - EXPECT_EQ(thresholdDurationNs + 1, buckets[eventKey][0].mDuration); -} - -} // namespace statsd -} // namespace os -} // namespace android -#else -GTEST_LOG_(INFO) << "This test does nothing.\n"; -#endif diff --git a/bin/tests/metrics/OringDurationTracker_test.cpp b/bin/tests/metrics/OringDurationTracker_test.cpp deleted file mode 100644 index 369f4738..00000000 --- a/bin/tests/metrics/OringDurationTracker_test.cpp +++ /dev/null @@ -1,611 +0,0 @@ -// 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 "src/metrics/duration_helper/OringDurationTracker.h" -#include "src/condition/ConditionWizard.h" -#include "metrics_test_helper.h" -#include "tests/statsd_test_util.h" - -#include -#include -#include -#include -#include -#include -#include - -using namespace testing; -using android::sp; -using std::set; -using std::unordered_map; -using std::vector; - -#ifdef __ANDROID__ -namespace android { -namespace os { -namespace statsd { - -const ConfigKey kConfigKey(0, 12345); -const int TagId = 1; -const int64_t metricId = 123; -const optional emptyThreshold; -const MetricDimensionKey eventKey = getMockedMetricDimensionKey(TagId, 0, "event"); - -const HashableDimensionKey kConditionKey1 = getMockedDimensionKey(TagId, 1, "maps"); -const HashableDimensionKey kEventKey1 = getMockedDimensionKey(TagId, 2, "maps"); -const HashableDimensionKey kEventKey2 = getMockedDimensionKey(TagId, 3, "maps"); -const int64_t bucketSizeNs = 30 * NS_PER_SEC; - -TEST(OringDurationTrackerTest, TestDurationOverlap) { - const MetricDimensionKey eventKey = getMockedMetricDimensionKey(TagId, 0, "event"); - - const HashableDimensionKey kEventKey1 = getMockedDimensionKey(TagId, 2, "maps"); - const HashableDimensionKey kEventKey2 = getMockedDimensionKey(TagId, 3, "maps"); - sp wizard = new NaggyMock(); - - unordered_map> buckets; - - int64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL; - int64_t bucketStartTimeNs = 10000000000; - int64_t bucketNum = 0; - int64_t eventStartTimeNs = bucketStartTimeNs + 1; - int64_t durationTimeNs = 2 * 1000; - - OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, false, - bucketStartTimeNs, bucketNum, bucketStartTimeNs, bucketSizeNs, - false, false, {}); - - tracker.noteStart(kEventKey1, true, eventStartTimeNs, ConditionKey()); - EXPECT_EQ((long long)eventStartTimeNs, tracker.mLastStartTime); - tracker.noteStart(kEventKey1, true, eventStartTimeNs + 10, ConditionKey()); // overlapping wl - EXPECT_EQ((long long)eventStartTimeNs, tracker.mLastStartTime); - - tracker.noteStop(kEventKey1, eventStartTimeNs + durationTimeNs, false); - tracker.flushIfNeeded(eventStartTimeNs + bucketSizeNs + 1, emptyThreshold, &buckets); - EXPECT_TRUE(buckets.find(eventKey) != buckets.end()); - - ASSERT_EQ(1u, buckets[eventKey].size()); - EXPECT_EQ(durationTimeNs, buckets[eventKey][0].mDuration); -} - -TEST(OringDurationTrackerTest, TestDurationNested) { - const MetricDimensionKey eventKey = getMockedMetricDimensionKey(TagId, 0, "event"); - - const HashableDimensionKey kEventKey1 = getMockedDimensionKey(TagId, 2, "maps"); - const HashableDimensionKey kEventKey2 = getMockedDimensionKey(TagId, 3, "maps"); - sp wizard = new NaggyMock(); - - unordered_map> buckets; - - int64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL; - int64_t bucketStartTimeNs = 10000000000; - int64_t bucketNum = 0; - int64_t eventStartTimeNs = bucketStartTimeNs + 1; - - OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, true, bucketStartTimeNs, - bucketNum, bucketStartTimeNs, bucketSizeNs, false, false, {}); - - tracker.noteStart(kEventKey1, true, eventStartTimeNs, ConditionKey()); - tracker.noteStart(kEventKey1, true, eventStartTimeNs + 10, ConditionKey()); // overlapping wl - - tracker.noteStop(kEventKey1, eventStartTimeNs + 2000, false); - tracker.noteStop(kEventKey1, eventStartTimeNs + 2003, false); - - tracker.flushIfNeeded(bucketStartTimeNs + bucketSizeNs + 1, emptyThreshold, &buckets); - EXPECT_TRUE(buckets.find(eventKey) != buckets.end()); - ASSERT_EQ(1u, buckets[eventKey].size()); - EXPECT_EQ(2003LL, buckets[eventKey][0].mDuration); -} - -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"); - sp wizard = new NaggyMock(); - - unordered_map> buckets; - - int64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL; - int64_t bucketStartTimeNs = 10000000000; - int64_t bucketNum = 0; - int64_t eventStartTimeNs = bucketStartTimeNs + 1; - - OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, true, bucketStartTimeNs, - bucketNum, bucketStartTimeNs, bucketSizeNs, false, false, {}); - - tracker.noteStart(kEventKey1, true, eventStartTimeNs, ConditionKey()); - tracker.noteStart(kEventKey2, true, eventStartTimeNs + 10, ConditionKey()); // overlapping wl - - tracker.noteStopAll(eventStartTimeNs + 2003); - - tracker.flushIfNeeded(bucketStartTimeNs + bucketSizeNs + 1, emptyThreshold, &buckets); - EXPECT_TRUE(buckets.find(eventKey) != buckets.end()); - ASSERT_EQ(1u, buckets[eventKey].size()); - EXPECT_EQ(2003LL, buckets[eventKey][0].mDuration); -} - -TEST(OringDurationTrackerTest, TestCrossBucketBoundary) { - const MetricDimensionKey eventKey = getMockedMetricDimensionKey(TagId, 0, "event"); - - const HashableDimensionKey kEventKey1 = getMockedDimensionKey(TagId, 2, "maps"); - const HashableDimensionKey kEventKey2 = getMockedDimensionKey(TagId, 3, "maps"); - sp wizard = new NaggyMock(); - - unordered_map> buckets; - - int64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL; - int64_t bucketStartTimeNs = 10000000000; - int64_t bucketNum = 0; - int64_t eventStartTimeNs = bucketStartTimeNs + 1; - int64_t durationTimeNs = 2 * 1000; - - OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, true, bucketStartTimeNs, - bucketNum, bucketStartTimeNs, bucketSizeNs, false, false, {}); - - tracker.noteStart(kEventKey1, true, eventStartTimeNs, ConditionKey()); - EXPECT_EQ((long long)eventStartTimeNs, tracker.mLastStartTime); - tracker.flushIfNeeded(eventStartTimeNs + 2 * bucketSizeNs, emptyThreshold, &buckets); - tracker.noteStart(kEventKey1, true, eventStartTimeNs + 2 * bucketSizeNs, ConditionKey()); - EXPECT_EQ((long long)(bucketStartTimeNs + 2 * bucketSizeNs), tracker.mLastStartTime); - - ASSERT_EQ(2u, buckets[eventKey].size()); - EXPECT_EQ(bucketSizeNs - 1, buckets[eventKey][0].mDuration); - EXPECT_EQ(bucketSizeNs, buckets[eventKey][1].mDuration); - - tracker.noteStop(kEventKey1, eventStartTimeNs + 2 * bucketSizeNs + 10, false); - tracker.noteStop(kEventKey1, eventStartTimeNs + 2 * bucketSizeNs + 12, false); - tracker.flushIfNeeded(eventStartTimeNs + 2 * bucketSizeNs + 12, emptyThreshold, &buckets); - EXPECT_TRUE(buckets.find(eventKey) != buckets.end()); - ASSERT_EQ(2u, buckets[eventKey].size()); - EXPECT_EQ(bucketSizeNs - 1, buckets[eventKey][0].mDuration); - EXPECT_EQ(bucketSizeNs, buckets[eventKey][1].mDuration); -} - -TEST(OringDurationTrackerTest, TestDurationConditionChange) { - const MetricDimensionKey eventKey = getMockedMetricDimensionKey(TagId, 0, "event"); - - const HashableDimensionKey kEventKey1 = getMockedDimensionKey(TagId, 2, "maps"); - const HashableDimensionKey kEventKey2 = getMockedDimensionKey(TagId, 3, "maps"); - sp wizard = new NaggyMock(); - - ConditionKey key1; - key1[StringToId("APP_BACKGROUND")] = kConditionKey1; - - EXPECT_CALL(*wizard, query(_, key1, _)) // #4 - .WillOnce(Return(ConditionState::kFalse)); - - unordered_map> buckets; - - int64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL; - int64_t bucketStartTimeNs = 10000000000; - int64_t bucketNum = 0; - int64_t eventStartTimeNs = bucketStartTimeNs + 1; - int64_t durationTimeNs = 2 * 1000; - - OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, false, - bucketStartTimeNs, bucketNum, bucketStartTimeNs, bucketSizeNs, - true, false, {}); - - tracker.noteStart(kEventKey1, true, eventStartTimeNs, key1); - - tracker.onSlicedConditionMayChange(true, eventStartTimeNs + 5); - - tracker.noteStop(kEventKey1, eventStartTimeNs + durationTimeNs, false); - - tracker.flushIfNeeded(bucketStartTimeNs + bucketSizeNs + 1, emptyThreshold, &buckets); - EXPECT_TRUE(buckets.find(eventKey) != buckets.end()); - ASSERT_EQ(1u, buckets[eventKey].size()); - EXPECT_EQ(5LL, buckets[eventKey][0].mDuration); -} - -TEST(OringDurationTrackerTest, TestDurationConditionChange2) { - const MetricDimensionKey eventKey = getMockedMetricDimensionKey(TagId, 0, "event"); - - const HashableDimensionKey kEventKey1 = getMockedDimensionKey(TagId, 2, "maps"); - const HashableDimensionKey kEventKey2 = getMockedDimensionKey(TagId, 3, "maps"); - sp wizard = new NaggyMock(); - - ConditionKey key1; - key1[StringToId("APP_BACKGROUND")] = kConditionKey1; - - EXPECT_CALL(*wizard, query(_, key1, _)) - .Times(2) - .WillOnce(Return(ConditionState::kFalse)) - .WillOnce(Return(ConditionState::kTrue)); - - unordered_map> buckets; - - int64_t bucketStartTimeNs = 10000000000; - int64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL; - int64_t bucketNum = 0; - int64_t eventStartTimeNs = bucketStartTimeNs + 1; - int64_t durationTimeNs = 2 * 1000; - - OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, false, - bucketStartTimeNs, bucketNum, bucketStartTimeNs, bucketSizeNs, - true, false, {}); - - tracker.noteStart(kEventKey1, true, eventStartTimeNs, key1); - // condition to false; record duration 5n - tracker.onSlicedConditionMayChange(true, eventStartTimeNs + 5); - // condition to true. - tracker.onSlicedConditionMayChange(true, eventStartTimeNs + 1000); - // 2nd duration: 1000ns - tracker.noteStop(kEventKey1, eventStartTimeNs + durationTimeNs, false); - - tracker.flushIfNeeded(bucketStartTimeNs + bucketSizeNs + 1, emptyThreshold, &buckets); - EXPECT_TRUE(buckets.find(eventKey) != buckets.end()); - ASSERT_EQ(1u, buckets[eventKey].size()); - EXPECT_EQ(1005LL, buckets[eventKey][0].mDuration); -} - -TEST(OringDurationTrackerTest, TestDurationConditionChangeNested) { - const MetricDimensionKey eventKey = getMockedMetricDimensionKey(TagId, 0, "event"); - - const HashableDimensionKey kEventKey1 = getMockedDimensionKey(TagId, 2, "maps"); - const HashableDimensionKey kEventKey2 = getMockedDimensionKey(TagId, 3, "maps"); - sp wizard = new NaggyMock(); - - ConditionKey key1; - key1[StringToId("APP_BACKGROUND")] = kConditionKey1; - - EXPECT_CALL(*wizard, query(_, key1, _)) // #4 - .WillOnce(Return(ConditionState::kFalse)); - - unordered_map> buckets; - - int64_t bucketStartTimeNs = 10000000000; - int64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL; - int64_t bucketNum = 0; - int64_t eventStartTimeNs = bucketStartTimeNs + 1; - - OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, true, bucketStartTimeNs, - bucketNum, bucketStartTimeNs, bucketSizeNs, true, false, {}); - - tracker.noteStart(kEventKey1, true, eventStartTimeNs, key1); - tracker.noteStart(kEventKey1, true, eventStartTimeNs + 2, key1); - - tracker.noteStop(kEventKey1, eventStartTimeNs + 3, false); - - tracker.onSlicedConditionMayChange(true, eventStartTimeNs + 15); - - tracker.noteStop(kEventKey1, eventStartTimeNs + 2003, false); - - tracker.flushIfNeeded(bucketStartTimeNs + bucketSizeNs + 1, emptyThreshold, &buckets); - EXPECT_TRUE(buckets.find(eventKey) != buckets.end()); - ASSERT_EQ(1u, buckets[eventKey].size()); - EXPECT_EQ(15LL, buckets[eventKey][0].mDuration); -} - -TEST(OringDurationTrackerTest, TestPredictAnomalyTimestamp) { - const MetricDimensionKey eventKey = getMockedMetricDimensionKey(TagId, 0, "event"); - - const HashableDimensionKey kEventKey1 = getMockedDimensionKey(TagId, 2, "maps"); - const HashableDimensionKey kEventKey2 = getMockedDimensionKey(TagId, 3, "maps"); - Alert alert; - alert.set_id(101); - alert.set_metric_id(1); - alert.set_trigger_if_sum_gt(40 * NS_PER_SEC); - alert.set_num_buckets(2); - alert.set_refractory_period_secs(1); - - unordered_map> buckets; - sp wizard = new NaggyMock(); - - int64_t bucketStartTimeNs = 10 * NS_PER_SEC; - int64_t bucketNum = 0; - int64_t eventStartTimeNs = bucketStartTimeNs + NS_PER_SEC + 1; - - sp alarmMonitor; - sp anomalyTracker = - new DurationAnomalyTracker(alert, kConfigKey, alarmMonitor); - OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, true, bucketStartTimeNs, - bucketNum, bucketStartTimeNs, bucketSizeNs, true, false, - {anomalyTracker}); - - // Nothing in the past bucket. - tracker.noteStart(DEFAULT_DIMENSION_KEY, true, eventStartTimeNs, ConditionKey()); - EXPECT_EQ((long long)(alert.trigger_if_sum_gt() + eventStartTimeNs), - tracker.predictAnomalyTimestampNs(*anomalyTracker, eventStartTimeNs)); - - tracker.noteStop(DEFAULT_DIMENSION_KEY, eventStartTimeNs + 3, false); - ASSERT_EQ(0u, buckets[eventKey].size()); - - int64_t event1StartTimeNs = eventStartTimeNs + 10; - tracker.noteStart(kEventKey1, true, event1StartTimeNs, ConditionKey()); - // No past buckets. The anomaly will happen in bucket #0. - EXPECT_EQ((long long)(event1StartTimeNs + alert.trigger_if_sum_gt() - 3), - tracker.predictAnomalyTimestampNs(*anomalyTracker, event1StartTimeNs)); - - int64_t event1StopTimeNs = eventStartTimeNs + bucketSizeNs + 10; - tracker.flushIfNeeded(event1StopTimeNs, emptyThreshold, &buckets); - tracker.noteStop(kEventKey1, event1StopTimeNs, false); - - EXPECT_TRUE(buckets.find(eventKey) != buckets.end()); - ASSERT_EQ(1u, buckets[eventKey].size()); - EXPECT_EQ(3LL + bucketStartTimeNs + bucketSizeNs - eventStartTimeNs - 10, - buckets[eventKey][0].mDuration); - - const int64_t bucket0Duration = 3ULL + bucketStartTimeNs + bucketSizeNs - eventStartTimeNs - 10; - const int64_t bucket1Duration = eventStartTimeNs + 10 - bucketStartTimeNs; - - // One past buckets. The anomaly will happen in bucket #1. - int64_t event2StartTimeNs = eventStartTimeNs + bucketSizeNs + 15; - tracker.noteStart(kEventKey1, true, event2StartTimeNs, ConditionKey()); - EXPECT_EQ((long long)(event2StartTimeNs + alert.trigger_if_sum_gt() - bucket0Duration - - bucket1Duration), - tracker.predictAnomalyTimestampNs(*anomalyTracker, event2StartTimeNs)); - tracker.noteStop(kEventKey1, event2StartTimeNs + 1, false); - - // Only one past buckets is applicable. Bucket +0 should be trashed. The anomaly will happen in - // bucket #2. - int64_t event3StartTimeNs = bucketStartTimeNs + 2 * bucketSizeNs - 9 * NS_PER_SEC; - tracker.noteStart(kEventKey1, true, event3StartTimeNs, ConditionKey()); - EXPECT_EQ((long long)(event3StartTimeNs + alert.trigger_if_sum_gt() - bucket1Duration - 1LL), - tracker.predictAnomalyTimestampNs(*anomalyTracker, event3StartTimeNs)); -} - -TEST(OringDurationTrackerTest, TestPredictAnomalyTimestamp2) { - Alert alert; - alert.set_id(101); - alert.set_metric_id(1); - alert.set_trigger_if_sum_gt(5 * NS_PER_SEC); - alert.set_num_buckets(1); - alert.set_refractory_period_secs(20); - - int64_t bucketStartTimeNs = 10 * NS_PER_SEC; - int64_t bucketNum = 0; - - sp wizard = new NaggyMock(); - sp alarmMonitor; - sp anomalyTracker = - new DurationAnomalyTracker(alert, kConfigKey, alarmMonitor); - OringDurationTracker tracker(kConfigKey, metricId, DEFAULT_METRIC_DIMENSION_KEY, wizard, 1, - - true, bucketStartTimeNs, bucketNum, bucketStartTimeNs, - bucketSizeNs, true, false, {anomalyTracker}); - - int64_t eventStartTimeNs = bucketStartTimeNs + 9 * NS_PER_SEC; - tracker.noteStart(DEFAULT_DIMENSION_KEY, true, eventStartTimeNs, ConditionKey()); - // Anomaly happens in the bucket #1. - EXPECT_EQ((long long)(bucketStartTimeNs + 14 * NS_PER_SEC), - tracker.predictAnomalyTimestampNs(*anomalyTracker, eventStartTimeNs)); - - tracker.noteStop(DEFAULT_DIMENSION_KEY, bucketStartTimeNs + 14 * NS_PER_SEC, false); - - EXPECT_EQ((long long)(bucketStartTimeNs + 34 * NS_PER_SEC) / NS_PER_SEC, - anomalyTracker->getRefractoryPeriodEndsSec(DEFAULT_METRIC_DIMENSION_KEY)); - - int64_t event2StartTimeNs = bucketStartTimeNs + 22 * NS_PER_SEC; - EXPECT_EQ((long long)(bucketStartTimeNs + 34 * NS_PER_SEC) / NS_PER_SEC, - anomalyTracker->getRefractoryPeriodEndsSec(DEFAULT_METRIC_DIMENSION_KEY)); - EXPECT_EQ((long long)(bucketStartTimeNs + 35 * NS_PER_SEC), - tracker.predictAnomalyTimestampNs(*anomalyTracker, event2StartTimeNs)); -} - -TEST(OringDurationTrackerTest, TestPredictAnomalyTimestamp3) { - // Test the cases where the refractory period is smaller than the bucket size, longer than - // the bucket size, and longer than 2x of the anomaly detection window. - for (int j = 0; j < 3; j++) { - int64_t thresholdNs = j * bucketSizeNs + 5 * NS_PER_SEC; - for (int i = 0; i <= 7; ++i) { - - Alert alert; - alert.set_id(101); - alert.set_metric_id(1); - alert.set_trigger_if_sum_gt(thresholdNs); - alert.set_num_buckets(3); - alert.set_refractory_period_secs( - bucketSizeNs / NS_PER_SEC / 2 + i * bucketSizeNs / NS_PER_SEC); - - int64_t bucketStartTimeNs = 10 * NS_PER_SEC; - int64_t bucketNum = 101; - - sp wizard = new NaggyMock(); - sp alarmMonitor; - sp anomalyTracker = - new DurationAnomalyTracker(alert, kConfigKey, alarmMonitor); - OringDurationTracker tracker(kConfigKey, metricId, DEFAULT_METRIC_DIMENSION_KEY, wizard, - 1, true, bucketStartTimeNs, bucketNum, bucketStartTimeNs, - bucketSizeNs, true, false, {anomalyTracker}); - - int64_t eventStartTimeNs = bucketStartTimeNs + 9 * NS_PER_SEC; - tracker.noteStart(DEFAULT_DIMENSION_KEY, true, eventStartTimeNs, ConditionKey()); - EXPECT_EQ((long long)(eventStartTimeNs + thresholdNs), - tracker.predictAnomalyTimestampNs(*anomalyTracker, eventStartTimeNs)); - int64_t eventStopTimeNs = eventStartTimeNs + thresholdNs + NS_PER_SEC; - tracker.noteStop(DEFAULT_DIMENSION_KEY, eventStopTimeNs, false); - - int64_t refractoryPeriodEndSec = - anomalyTracker->getRefractoryPeriodEndsSec(DEFAULT_METRIC_DIMENSION_KEY); - EXPECT_EQ(eventStopTimeNs / (int64_t)NS_PER_SEC + alert.refractory_period_secs(), - refractoryPeriodEndSec); - - // Acquire and release a wakelock in the next bucket. - int64_t event2StartTimeNs = eventStopTimeNs + bucketSizeNs; - tracker.noteStart(DEFAULT_DIMENSION_KEY, true, event2StartTimeNs, ConditionKey()); - int64_t event2StopTimeNs = event2StartTimeNs + 4 * NS_PER_SEC; - tracker.noteStop(DEFAULT_DIMENSION_KEY, event2StopTimeNs, false); - - // Test the alarm prediction works well when seeing another wakelock start event. - for (int k = 0; k <= 2; ++k) { - int64_t event3StartTimeNs = event2StopTimeNs + NS_PER_SEC + k * bucketSizeNs; - int64_t alarmTimestampNs = - tracker.predictAnomalyTimestampNs(*anomalyTracker, event3StartTimeNs); - EXPECT_GT(alarmTimestampNs, 0u); - EXPECT_GE(alarmTimestampNs, event3StartTimeNs); - EXPECT_GE(alarmTimestampNs, refractoryPeriodEndSec *(int64_t) NS_PER_SEC); - } - } - } -} - -TEST(OringDurationTrackerTest, TestAnomalyDetectionExpiredAlarm) { - const MetricDimensionKey eventKey = getMockedMetricDimensionKey(TagId, 0, "event"); - - const HashableDimensionKey kEventKey1 = getMockedDimensionKey(TagId, 2, "maps"); - const HashableDimensionKey kEventKey2 = getMockedDimensionKey(TagId, 3, "maps"); - Alert alert; - alert.set_id(101); - alert.set_metric_id(1); - alert.set_trigger_if_sum_gt(40 * NS_PER_SEC); - alert.set_num_buckets(2); - const int32_t refPeriodSec = 45; - alert.set_refractory_period_secs(refPeriodSec); - - unordered_map> buckets; - sp wizard = new NaggyMock(); - - int64_t bucketStartTimeNs = 10 * NS_PER_SEC; - int64_t bucketNum = 0; - int64_t eventStartTimeNs = bucketStartTimeNs + NS_PER_SEC + 1; - - sp alarmMonitor; - sp anomalyTracker = - new DurationAnomalyTracker(alert, kConfigKey, alarmMonitor); - OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, true /*nesting*/, - bucketStartTimeNs, bucketNum, bucketStartTimeNs, bucketSizeNs, - false, false, {anomalyTracker}); - - tracker.noteStart(kEventKey1, true, eventStartTimeNs, ConditionKey()); - tracker.noteStop(kEventKey1, eventStartTimeNs + 10, false); - EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(eventKey), 0U); - EXPECT_TRUE(tracker.mStarted.empty()); - EXPECT_EQ(10LL, tracker.mStateKeyDurationMap[DEFAULT_DIMENSION_KEY].mDuration); // 10ns - - ASSERT_EQ(0u, tracker.mStarted.size()); - - tracker.noteStart(kEventKey1, true, eventStartTimeNs + 20, ConditionKey()); - ASSERT_EQ(1u, anomalyTracker->mAlarms.size()); - EXPECT_EQ((long long)(52ULL * NS_PER_SEC), // (10s + 1s + 1ns + 20ns) - 10ns + 40s, rounded up - (long long)(anomalyTracker->mAlarms.begin()->second->timestampSec * NS_PER_SEC)); - // The alarm is set to fire at 52s, and when it does, an anomaly would be declared. However, - // because this is a unit test, the alarm won't actually fire at all. Since the alarm fails - // to fire in time, the anomaly is instead caught when noteStop is called, at around 71s. - tracker.flushIfNeeded(eventStartTimeNs + 2 * bucketSizeNs + 25, emptyThreshold, &buckets); - tracker.noteStop(kEventKey1, eventStartTimeNs + 2 * bucketSizeNs + 25, false); - EXPECT_EQ(anomalyTracker->getSumOverPastBuckets(eventKey), (long long)(bucketSizeNs)); - EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(eventKey), - std::ceil((eventStartTimeNs + 2 * bucketSizeNs + 25.0) / NS_PER_SEC + refPeriodSec)); -} - -TEST(OringDurationTrackerTest, TestAnomalyDetectionFiredAlarm) { - const MetricDimensionKey eventKey = getMockedMetricDimensionKey(TagId, 0, "event"); - - const HashableDimensionKey kEventKey1 = getMockedDimensionKey(TagId, 2, "maps"); - const HashableDimensionKey kEventKey2 = getMockedDimensionKey(TagId, 3, "maps"); - Alert alert; - alert.set_id(101); - alert.set_metric_id(1); - alert.set_trigger_if_sum_gt(40 * NS_PER_SEC); - alert.set_num_buckets(2); - const int32_t refPeriodSec = 45; - alert.set_refractory_period_secs(refPeriodSec); - - unordered_map> buckets; - sp wizard = new NaggyMock(); - ConditionKey conkey; - conkey[StringToId("APP_BACKGROUND")] = kConditionKey1; - int64_t bucketStartTimeNs = 10 * NS_PER_SEC; - int64_t bucketSizeNs = 30 * NS_PER_SEC; - - sp alarmMonitor; - sp anomalyTracker = - new DurationAnomalyTracker(alert, kConfigKey, alarmMonitor); - OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, true /*nesting*/, - bucketStartTimeNs, 0, bucketStartTimeNs, bucketSizeNs, false, - false, {anomalyTracker}); - - tracker.noteStart(kEventKey1, true, 15 * NS_PER_SEC, conkey); // start key1 - ASSERT_EQ(1u, anomalyTracker->mAlarms.size()); - sp alarm = anomalyTracker->mAlarms.begin()->second; - EXPECT_EQ((long long)(55ULL * NS_PER_SEC), (long long)(alarm->timestampSec * NS_PER_SEC)); - EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(eventKey), 0U); - - tracker.noteStop(kEventKey1, 17 * NS_PER_SEC, false); // stop key1 (2 seconds later) - ASSERT_EQ(0u, anomalyTracker->mAlarms.size()); - EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(eventKey), 0U); - - tracker.noteStart(kEventKey1, true, 22 * NS_PER_SEC, conkey); // start key1 again - ASSERT_EQ(1u, anomalyTracker->mAlarms.size()); - alarm = anomalyTracker->mAlarms.begin()->second; - EXPECT_EQ((long long)(60ULL * NS_PER_SEC), (long long)(alarm->timestampSec * NS_PER_SEC)); - EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(eventKey), 0U); - - tracker.noteStart(kEventKey2, true, 32 * NS_PER_SEC, conkey); // start key2 - ASSERT_EQ(1u, anomalyTracker->mAlarms.size()); - alarm = anomalyTracker->mAlarms.begin()->second; - EXPECT_EQ((long long)(60ULL * NS_PER_SEC), (long long)(alarm->timestampSec * NS_PER_SEC)); - EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(eventKey), 0U); - - tracker.noteStop(kEventKey1, 47 * NS_PER_SEC, false); // stop key1 - ASSERT_EQ(1u, anomalyTracker->mAlarms.size()); - alarm = anomalyTracker->mAlarms.begin()->second; - EXPECT_EQ((long long)(60ULL * NS_PER_SEC), (long long)(alarm->timestampSec * NS_PER_SEC)); - EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(eventKey), 0U); - - // Now, at 60s, which is 38s after key1 started again, we have reached 40s of 'on' time. - std::unordered_set, SpHash> firedAlarms({alarm}); - anomalyTracker->informAlarmsFired(62 * NS_PER_SEC, firedAlarms); - ASSERT_EQ(0u, anomalyTracker->mAlarms.size()); - EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(eventKey), 62U + refPeriodSec); - - tracker.noteStop(kEventKey2, 69 * NS_PER_SEC, false); // stop key2 - ASSERT_EQ(0u, anomalyTracker->mAlarms.size()); - EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(eventKey), 62U + refPeriodSec); -} - -TEST(OringDurationTrackerTest, TestUploadThreshold) { - sp wizard = new NaggyMock(); - - unordered_map> buckets; - - int64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL; - int64_t bucketStartTimeNs = 10000000000; - int64_t bucketNum = 0; - int64_t eventStartTimeNs = bucketStartTimeNs + 1; - int64_t event2StartTimeNs = bucketStartTimeNs + bucketSizeNs + 1; - int64_t thresholdDurationNs = 2000; - - UploadThreshold threshold; - threshold.set_gt_int(thresholdDurationNs); - - OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, false, - bucketStartTimeNs, bucketNum, bucketStartTimeNs, bucketSizeNs, - false, false, {}); - - // Duration below the gt_int threshold should not be added to past buckets. - tracker.noteStart(kEventKey1, true, eventStartTimeNs, ConditionKey()); - tracker.noteStop(kEventKey1, eventStartTimeNs + thresholdDurationNs, false); - tracker.flushIfNeeded(eventStartTimeNs + bucketSizeNs + 1, threshold, &buckets); - EXPECT_TRUE(buckets.find(eventKey) == buckets.end()); - - // Duration above the gt_int threshold should be added to past buckets. - tracker.noteStart(kEventKey1, true, event2StartTimeNs, ConditionKey()); - tracker.noteStop(kEventKey1, event2StartTimeNs + thresholdDurationNs + 1, false); - tracker.flushIfNeeded(event2StartTimeNs + bucketSizeNs + 1, threshold, &buckets); - EXPECT_TRUE(buckets.find(eventKey) != buckets.end()); - ASSERT_EQ(1u, buckets[eventKey].size()); - EXPECT_EQ(thresholdDurationNs + 1, buckets[eventKey][0].mDuration); -} - -} // namespace statsd -} // namespace os -} // namespace android -#else -GTEST_LOG_(INFO) << "This test does nothing.\n"; -#endif diff --git a/bin/tests/metrics/ValueMetricProducer_test.cpp b/bin/tests/metrics/ValueMetricProducer_test.cpp deleted file mode 100644 index e74039b2..00000000 --- a/bin/tests/metrics/ValueMetricProducer_test.cpp +++ /dev/null @@ -1,6921 +0,0 @@ -// 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 "src/metrics/ValueMetricProducer.h" - -#include -#include -#include -#include - -#include - -#include "metrics_test_helper.h" -#include "src/matchers/SimpleAtomMatchingTracker.h" -#include "src/metrics/MetricProducer.h" -#include "src/stats_log_util.h" -#include "tests/statsd_test_util.h" - -using namespace testing; -using android::sp; -using std::make_shared; -using std::set; -using std::shared_ptr; -using std::unordered_map; -using std::vector; - -#ifdef __ANDROID__ - -namespace android { -namespace os { -namespace statsd { - -namespace { - -const ConfigKey kConfigKey(0, 12345); -const int tagId = 1; -const int64_t metricId = 123; -const uint64_t protoHash = 0x1234567890; -const int logEventMatcherIndex = 0; -const int64_t bucketStartTimeNs = 10000000000; -const int64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(ONE_MINUTE) * 1000000LL; -const int64_t bucket2StartTimeNs = bucketStartTimeNs + bucketSizeNs; -const int64_t bucket3StartTimeNs = bucketStartTimeNs + 2 * bucketSizeNs; -const int64_t bucket4StartTimeNs = bucketStartTimeNs + 3 * bucketSizeNs; -const int64_t bucket5StartTimeNs = bucketStartTimeNs + 4 * bucketSizeNs; -const int64_t bucket6StartTimeNs = bucketStartTimeNs + 5 * bucketSizeNs; -double epsilon = 0.001; - -static void assertPastBucketValuesSingleKey( - const std::unordered_map>& mPastBuckets, - const std::initializer_list& expectedValuesList, - const std::initializer_list& expectedDurationNsList, - const std::initializer_list& expectedStartTimeNsList, - const std::initializer_list& expectedEndTimeNsList) { - vector expectedValues(expectedValuesList); - vector expectedDurationNs(expectedDurationNsList); - vector expectedStartTimeNs(expectedStartTimeNsList); - vector expectedEndTimeNs(expectedEndTimeNsList); - - ASSERT_EQ(expectedValues.size(), expectedDurationNs.size()); - ASSERT_EQ(expectedValues.size(), expectedStartTimeNs.size()); - ASSERT_EQ(expectedValues.size(), expectedEndTimeNs.size()); - - if (expectedValues.size() == 0) { - ASSERT_EQ(0, mPastBuckets.size()); - return; - } - - ASSERT_EQ(1, mPastBuckets.size()); - ASSERT_EQ(expectedValues.size(), mPastBuckets.begin()->second.size()); - - const vector& buckets = mPastBuckets.begin()->second; - for (int i = 0; i < expectedValues.size(); i++) { - EXPECT_EQ(expectedValues[i], buckets[i].values[0].long_value) - << "Values differ at index " << i; - EXPECT_EQ(expectedDurationNs[i], buckets[i].mConditionTrueNs) - << "Condition duration value differ at index " << i; - EXPECT_EQ(expectedStartTimeNs[i], buckets[i].mBucketStartNs) - << "Start time differs at index " << i; - EXPECT_EQ(expectedEndTimeNs[i], buckets[i].mBucketEndNs) - << "End time differs at index " << i; - } -} - -static void assertConditionTimer(const ConditionTimer& conditionTimer, bool condition, - int64_t timerNs, int64_t lastConditionTrueTimestampNs) { - EXPECT_EQ(condition, conditionTimer.mCondition); - EXPECT_EQ(timerNs, conditionTimer.mTimerNs); - EXPECT_EQ(lastConditionTrueTimestampNs, conditionTimer.mLastConditionChangeTimestampNs); -} - -} // anonymous namespace - -class ValueMetricProducerTestHelper { -public: - static sp createValueProducerNoConditions( - sp& pullerManager, ValueMetric& metric) { - sp eventMatcherWizard = - createEventMatcherWizard(tagId, logEventMatcherIndex); - sp wizard = new NaggyMock(); - EXPECT_CALL(*pullerManager, RegisterReceiver(tagId, kConfigKey, _, _, _)) - .WillOnce(Return()); - EXPECT_CALL(*pullerManager, UnRegisterReceiver(tagId, kConfigKey, _)) - .WillRepeatedly(Return()); - - sp valueProducer = - new ValueMetricProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, {}, - wizard, protoHash, logEventMatcherIndex, eventMatcherWizard, - tagId, bucketStartTimeNs, bucketStartTimeNs, pullerManager); - valueProducer->prepareFirstBucket(); - return valueProducer; - } - - static sp createValueProducerWithCondition( - sp& pullerManager, ValueMetric& metric, - ConditionState conditionAfterFirstBucketPrepared) { - sp eventMatcherWizard = - createEventMatcherWizard(tagId, logEventMatcherIndex); - sp wizard = new NaggyMock(); - EXPECT_CALL(*pullerManager, RegisterReceiver(tagId, kConfigKey, _, _, _)) - .WillOnce(Return()); - EXPECT_CALL(*pullerManager, UnRegisterReceiver(tagId, kConfigKey, _)) - .WillRepeatedly(Return()); - - sp valueProducer = new ValueMetricProducer( - kConfigKey, metric, 0 /*condition index*/, {ConditionState::kUnknown}, wizard, - protoHash, logEventMatcherIndex, eventMatcherWizard, tagId, bucketStartTimeNs, - bucketStartTimeNs, pullerManager); - valueProducer->prepareFirstBucket(); - valueProducer->mCondition = conditionAfterFirstBucketPrepared; - return valueProducer; - } - - static sp createValueProducerWithState( - sp& pullerManager, ValueMetric& metric, - vector slicedStateAtoms, - unordered_map> stateGroupMap) { - sp eventMatcherWizard = - createEventMatcherWizard(tagId, logEventMatcherIndex); - sp wizard = new NaggyMock(); - EXPECT_CALL(*pullerManager, RegisterReceiver(tagId, kConfigKey, _, _, _)) - .WillOnce(Return()); - EXPECT_CALL(*pullerManager, UnRegisterReceiver(tagId, kConfigKey, _)) - .WillRepeatedly(Return()); - - sp valueProducer = new ValueMetricProducer( - kConfigKey, metric, -1 /* no condition */, {}, wizard, protoHash, - logEventMatcherIndex, eventMatcherWizard, tagId, bucketStartTimeNs, - bucketStartTimeNs, pullerManager, {}, {}, slicedStateAtoms, stateGroupMap); - valueProducer->prepareFirstBucket(); - return valueProducer; - } - - static sp createValueProducerWithConditionAndState( - sp& pullerManager, ValueMetric& metric, - vector slicedStateAtoms, - unordered_map> stateGroupMap, - ConditionState conditionAfterFirstBucketPrepared) { - sp eventMatcherWizard = - createEventMatcherWizard(tagId, logEventMatcherIndex); - sp wizard = new NaggyMock(); - EXPECT_CALL(*pullerManager, RegisterReceiver(tagId, kConfigKey, _, _, _)) - .WillOnce(Return()); - EXPECT_CALL(*pullerManager, UnRegisterReceiver(tagId, kConfigKey, _)) - .WillRepeatedly(Return()); - - sp valueProducer = new ValueMetricProducer( - kConfigKey, metric, 0 /* condition tracker index */, {ConditionState::kUnknown}, - wizard, protoHash, logEventMatcherIndex, eventMatcherWizard, tagId, - bucketStartTimeNs, bucketStartTimeNs, pullerManager, {}, {}, slicedStateAtoms, - stateGroupMap); - valueProducer->prepareFirstBucket(); - valueProducer->mCondition = conditionAfterFirstBucketPrepared; - return valueProducer; - } - - static ValueMetric createMetric() { - ValueMetric metric; - metric.set_id(metricId); - metric.set_bucket(ONE_MINUTE); - metric.mutable_value_field()->set_field(tagId); - metric.mutable_value_field()->add_child()->set_field(2); - metric.set_max_pull_delay_sec(INT_MAX); - return metric; - } - - static ValueMetric createMetricWithCondition() { - ValueMetric metric = ValueMetricProducerTestHelper::createMetric(); - metric.set_condition(StringToId("SCREEN_ON")); - return metric; - } - - static ValueMetric createMetricWithState(string state) { - ValueMetric metric = ValueMetricProducerTestHelper::createMetric(); - metric.add_slice_by_state(StringToId(state)); - return metric; - } - - static ValueMetric createMetricWithConditionAndState(string state) { - ValueMetric metric = ValueMetricProducerTestHelper::createMetric(); - metric.set_condition(StringToId("SCREEN_ON")); - metric.add_slice_by_state(StringToId(state)); - return metric; - } -}; - -// Setup for parameterized tests. -class ValueMetricProducerTest_PartialBucket : public TestWithParam {}; - -INSTANTIATE_TEST_SUITE_P(ValueMetricProducerTest_PartialBucket, - ValueMetricProducerTest_PartialBucket, - testing::Values(APP_UPGRADE, BOOT_COMPLETE)); - -/* - * Tests that the first bucket works correctly - */ -TEST(ValueMetricProducerTest, TestCalcPreviousBucketEndTime) { - ValueMetric metric = ValueMetricProducerTestHelper::createMetric(); - - int64_t startTimeBase = 11; - sp eventMatcherWizard = - createEventMatcherWizard(tagId, logEventMatcherIndex); - sp wizard = new NaggyMock(); - sp pullerManager = new StrictMock(); - - // statsd started long ago. - // The metric starts in the middle of the bucket - ValueMetricProducer valueProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, {}, - wizard, protoHash, logEventMatcherIndex, eventMatcherWizard, - -1, startTimeBase, 22, pullerManager); - valueProducer.prepareFirstBucket(); - - EXPECT_EQ(startTimeBase, valueProducer.calcPreviousBucketEndTime(60 * NS_PER_SEC + 10)); - EXPECT_EQ(startTimeBase, valueProducer.calcPreviousBucketEndTime(60 * NS_PER_SEC + 10)); - EXPECT_EQ(60 * NS_PER_SEC + startTimeBase, - valueProducer.calcPreviousBucketEndTime(2 * 60 * NS_PER_SEC)); - EXPECT_EQ(2 * 60 * NS_PER_SEC + startTimeBase, - valueProducer.calcPreviousBucketEndTime(3 * 60 * NS_PER_SEC)); -} - -/* - * Tests that the first bucket works correctly - */ -TEST(ValueMetricProducerTest, TestFirstBucket) { - ValueMetric metric = ValueMetricProducerTestHelper::createMetric(); - - sp eventMatcherWizard = - createEventMatcherWizard(tagId, logEventMatcherIndex); - sp wizard = new NaggyMock(); - sp pullerManager = new StrictMock(); - - // statsd started long ago. - // The metric starts in the middle of the bucket - ValueMetricProducer valueProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, {}, - wizard, protoHash, logEventMatcherIndex, eventMatcherWizard, - -1, 5, 600 * NS_PER_SEC + NS_PER_SEC / 2, pullerManager); - valueProducer.prepareFirstBucket(); - - EXPECT_EQ(600500000000, valueProducer.mCurrentBucketStartTimeNs); - EXPECT_EQ(10, valueProducer.mCurrentBucketNum); - EXPECT_EQ(660000000005, valueProducer.getCurrentBucketEndTimeNs()); -} - -/* - * Tests pulled atoms with no conditions - */ -TEST(ValueMetricProducerTest, TestPulledEventsNoCondition) { - ValueMetric metric = ValueMetricProducerTestHelper::createMetric(); - sp pullerManager = new StrictMock(); - EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, bucketStartTimeNs, _)) - .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t, - vector>* data) { - data->clear(); - data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs, 3)); - return true; - })); - - sp valueProducer = - ValueMetricProducerTestHelper::createValueProducerNoConditions(pullerManager, metric); - - vector> allData; - allData.clear(); - allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs + 1, 11)); - - valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs); - // has one slice - ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); - ValueMetricProducer::Interval curInterval = - valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0]; - ValueMetricProducer::BaseInfo curBaseInfo = - valueProducer->mCurrentBaseInfo.begin()->second.baseInfos[0]; - - EXPECT_EQ(true, curBaseInfo.hasBase); - EXPECT_EQ(11, curBaseInfo.base.long_value); - EXPECT_EQ(false, curInterval.hasValue); - EXPECT_EQ(8, curInterval.value.long_value); - ASSERT_EQ(1UL, valueProducer->mPastBuckets.size()); - EXPECT_EQ(8, valueProducer->mPastBuckets.begin()->second[0].values[0].long_value); - EXPECT_EQ(bucketSizeNs, valueProducer->mPastBuckets.begin()->second[0].mConditionTrueNs); - - allData.clear(); - allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket3StartTimeNs + 1, 23)); - valueProducer->onDataPulled(allData, /** succeed */ true, bucket3StartTimeNs); - // has one slice - ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); - curInterval = valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0]; - curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second.baseInfos[0]; - - EXPECT_EQ(true, curBaseInfo.hasBase); - EXPECT_EQ(23, curBaseInfo.base.long_value); - EXPECT_EQ(false, curInterval.hasValue); - EXPECT_EQ(12, curInterval.value.long_value); - ASSERT_EQ(1UL, valueProducer->mPastBuckets.size()); - ASSERT_EQ(2UL, valueProducer->mPastBuckets.begin()->second.size()); - EXPECT_EQ(8, valueProducer->mPastBuckets.begin()->second[0].values[0].long_value); - EXPECT_EQ(bucketSizeNs, valueProducer->mPastBuckets.begin()->second[0].mConditionTrueNs); - EXPECT_EQ(12, valueProducer->mPastBuckets.begin()->second.back().values[0].long_value); - EXPECT_EQ(bucketSizeNs, valueProducer->mPastBuckets.begin()->second.back().mConditionTrueNs); - - allData.clear(); - allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket4StartTimeNs + 1, 36)); - valueProducer->onDataPulled(allData, /** succeed */ true, bucket4StartTimeNs); - ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); - curInterval = valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0]; - curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second.baseInfos[0]; - - EXPECT_EQ(true, curBaseInfo.hasBase); - EXPECT_EQ(36, curBaseInfo.base.long_value); - EXPECT_EQ(false, curInterval.hasValue); - EXPECT_EQ(13, curInterval.value.long_value); - ASSERT_EQ(1UL, valueProducer->mPastBuckets.size()); - ASSERT_EQ(3UL, valueProducer->mPastBuckets.begin()->second.size()); - EXPECT_EQ(8, valueProducer->mPastBuckets.begin()->second[0].values[0].long_value); - EXPECT_EQ(bucketSizeNs, valueProducer->mPastBuckets.begin()->second[0].mConditionTrueNs); - EXPECT_EQ(12, valueProducer->mPastBuckets.begin()->second[1].values[0].long_value); - EXPECT_EQ(bucketSizeNs, valueProducer->mPastBuckets.begin()->second[1].mConditionTrueNs); - EXPECT_EQ(13, valueProducer->mPastBuckets.begin()->second[2].values[0].long_value); - EXPECT_EQ(bucketSizeNs, valueProducer->mPastBuckets.begin()->second[2].mConditionTrueNs); -} - -TEST_P(ValueMetricProducerTest_PartialBucket, TestPartialBucketCreated) { - ValueMetric metric = ValueMetricProducerTestHelper::createMetric(); - sp pullerManager = new StrictMock(); - int64_t partialBucketSplitTimeNs = bucket2StartTimeNs + 2; - EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _)) - // Initialize bucket. - .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, - vector>* data) { - EXPECT_EQ(eventTimeNs, bucketStartTimeNs); - data->clear(); - data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 1, 1)); - return true; - })) - // Partial bucket. - .WillOnce(Invoke([partialBucketSplitTimeNs](int tagId, const ConfigKey&, - const int64_t eventTimeNs, - vector>* data) { - EXPECT_EQ(eventTimeNs, partialBucketSplitTimeNs); - data->clear(); - data->push_back( - CreateRepeatedValueLogEvent(tagId, partialBucketSplitTimeNs + 8, 5)); - return true; - })); - - sp valueProducer = - ValueMetricProducerTestHelper::createValueProducerNoConditions(pullerManager, metric); - - // First bucket ends. - vector> allData; - allData.clear(); - allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs + 10, 2)); - valueProducer->onDataPulled(allData, /** success */ true, bucket2StartTimeNs); - - // Partial buckets created in 2nd bucket. - switch (GetParam()) { - case APP_UPGRADE: - valueProducer->notifyAppUpgrade(partialBucketSplitTimeNs); - break; - case BOOT_COMPLETE: - valueProducer->onStatsdInitCompleted(partialBucketSplitTimeNs); - break; - } - EXPECT_EQ(partialBucketSplitTimeNs, valueProducer->mCurrentBucketStartTimeNs); - EXPECT_EQ(1, valueProducer->getCurrentBucketNum()); - - assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {1, 3}, - {bucketSizeNs, partialBucketSplitTimeNs - bucket2StartTimeNs}, - {bucketStartTimeNs, bucket2StartTimeNs}, - {bucket2StartTimeNs, partialBucketSplitTimeNs}); -} - -/* - * Tests pulled atoms with filtering - */ -TEST(ValueMetricProducerTest, TestPulledEventsWithFiltering) { - ValueMetric metric = ValueMetricProducerTestHelper::createMetric(); - - FieldValueMatcher fvm; - fvm.set_field(1); - fvm.set_eq_int(3); - sp eventMatcherWizard = - createEventMatcherWizard(tagId, logEventMatcherIndex, {fvm}); - sp wizard = new NaggyMock(); - sp pullerManager = new StrictMock(); - EXPECT_CALL(*pullerManager, RegisterReceiver(tagId, kConfigKey, _, _, _)).WillOnce(Return()); - EXPECT_CALL(*pullerManager, UnRegisterReceiver(tagId, kConfigKey, _)).WillOnce(Return()); - EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, bucketStartTimeNs, _)) - .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t, - vector>* data) { - data->clear(); - data->push_back(CreateTwoValueLogEvent(tagId, bucketStartTimeNs, 3, 3)); - return true; - })); - - sp valueProducer = - new ValueMetricProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, {}, wizard, - protoHash, logEventMatcherIndex, eventMatcherWizard, tagId, - bucketStartTimeNs, bucketStartTimeNs, pullerManager); - valueProducer->prepareFirstBucket(); - - vector> allData; - allData.clear(); - allData.push_back(CreateTwoValueLogEvent(tagId, bucket2StartTimeNs + 1, 3, 11)); - - valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs); - // has one slice - ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); - ValueMetricProducer::Interval curInterval = - valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0]; - ValueMetricProducer::BaseInfo curBaseInfo = - valueProducer->mCurrentBaseInfo.begin()->second.baseInfos[0]; - - EXPECT_EQ(true, curBaseInfo.hasBase); - EXPECT_EQ(11, curBaseInfo.base.long_value); - EXPECT_EQ(false, curInterval.hasValue); - EXPECT_EQ(8, curInterval.value.long_value); - ASSERT_EQ(1UL, valueProducer->mPastBuckets.size()); - EXPECT_EQ(8, valueProducer->mPastBuckets.begin()->second[0].values[0].long_value); - EXPECT_EQ(bucketSizeNs, valueProducer->mPastBuckets.begin()->second[0].mConditionTrueNs); - - allData.clear(); - allData.push_back(CreateTwoValueLogEvent(tagId, bucket3StartTimeNs + 1, 4, 23)); - valueProducer->onDataPulled(allData, /** succeed */ true, bucket3StartTimeNs); - // No new data seen, so data has been cleared. - ASSERT_EQ(0UL, valueProducer->mCurrentSlicedBucket.size()); - - EXPECT_EQ(true, curBaseInfo.hasBase); - EXPECT_EQ(11, curBaseInfo.base.long_value); - EXPECT_EQ(false, curInterval.hasValue); - EXPECT_EQ(8, curInterval.value.long_value); - ASSERT_EQ(1UL, valueProducer->mPastBuckets.size()); - EXPECT_EQ(8, valueProducer->mPastBuckets.begin()->second[0].values[0].long_value); - EXPECT_EQ(bucketSizeNs, valueProducer->mPastBuckets.begin()->second[0].mConditionTrueNs); - - allData.clear(); - allData.push_back(CreateTwoValueLogEvent(tagId, bucket4StartTimeNs + 1, 3, 36)); - valueProducer->onDataPulled(allData, /** succeed */ true, bucket4StartTimeNs); - ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); - curInterval = valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0]; - curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second.baseInfos[0]; - - // the base was reset - EXPECT_EQ(true, curBaseInfo.hasBase); - EXPECT_EQ(36, curBaseInfo.base.long_value); - EXPECT_EQ(false, curInterval.hasValue); - ASSERT_EQ(1UL, valueProducer->mPastBuckets.size()); - ASSERT_EQ(1UL, valueProducer->mPastBuckets.begin()->second.size()); - EXPECT_EQ(8, valueProducer->mPastBuckets.begin()->second.back().values[0].long_value); - EXPECT_EQ(bucketSizeNs, valueProducer->mPastBuckets.begin()->second.back().mConditionTrueNs); -} - -/* - * Tests pulled atoms with no conditions and take absolute value after reset - */ -TEST(ValueMetricProducerTest, TestPulledEventsTakeAbsoluteValueOnReset) { - ValueMetric metric = ValueMetricProducerTestHelper::createMetric(); - metric.set_use_absolute_value_on_reset(true); - - sp pullerManager = new StrictMock(); - EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, bucketStartTimeNs, _)) - .WillOnce(Return(true)); - sp valueProducer = - ValueMetricProducerTestHelper::createValueProducerNoConditions(pullerManager, metric); - - vector> allData; - allData.clear(); - allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs + 1, 11)); - - valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs); - // has one slice - ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); - ValueMetricProducer::Interval curInterval = - valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0]; - ValueMetricProducer::BaseInfo curBaseInfo = - valueProducer->mCurrentBaseInfo.begin()->second.baseInfos[0]; - - EXPECT_EQ(true, curBaseInfo.hasBase); - EXPECT_EQ(11, curBaseInfo.base.long_value); - EXPECT_EQ(false, curInterval.hasValue); - ASSERT_EQ(0UL, valueProducer->mPastBuckets.size()); - - allData.clear(); - allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket3StartTimeNs + 1, 10)); - valueProducer->onDataPulled(allData, /** succeed */ true, bucket3StartTimeNs); - // has one slice - ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); - curInterval = valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0]; - curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second.baseInfos[0]; - EXPECT_EQ(true, curBaseInfo.hasBase); - EXPECT_EQ(10, curBaseInfo.base.long_value); - EXPECT_EQ(false, curInterval.hasValue); - EXPECT_EQ(10, curInterval.value.long_value); - ASSERT_EQ(1UL, valueProducer->mPastBuckets.size()); - EXPECT_EQ(10, valueProducer->mPastBuckets.begin()->second.back().values[0].long_value); - EXPECT_EQ(bucketSizeNs, valueProducer->mPastBuckets.begin()->second.back().mConditionTrueNs); - - allData.clear(); - allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket4StartTimeNs + 1, 36)); - valueProducer->onDataPulled(allData, /** succeed */ true, bucket4StartTimeNs); - ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); - curInterval = valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0]; - curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second.baseInfos[0]; - EXPECT_EQ(true, curBaseInfo.hasBase); - EXPECT_EQ(36, curBaseInfo.base.long_value); - EXPECT_EQ(false, curInterval.hasValue); - EXPECT_EQ(26, curInterval.value.long_value); - ASSERT_EQ(1UL, valueProducer->mPastBuckets.size()); - ASSERT_EQ(2UL, valueProducer->mPastBuckets.begin()->second.size()); - EXPECT_EQ(10, valueProducer->mPastBuckets.begin()->second[0].values[0].long_value); - EXPECT_EQ(bucketSizeNs, valueProducer->mPastBuckets.begin()->second[0].mConditionTrueNs); - EXPECT_EQ(26, valueProducer->mPastBuckets.begin()->second[1].values[0].long_value); - EXPECT_EQ(bucketSizeNs, valueProducer->mPastBuckets.begin()->second[1].mConditionTrueNs); -} - -/* - * Tests pulled atoms with no conditions and take zero value after reset - */ -TEST(ValueMetricProducerTest, TestPulledEventsTakeZeroOnReset) { - ValueMetric metric = ValueMetricProducerTestHelper::createMetric(); - sp pullerManager = new StrictMock(); - EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, bucketStartTimeNs, _)) - .WillOnce(Return(false)); - sp valueProducer = - ValueMetricProducerTestHelper::createValueProducerNoConditions(pullerManager, metric); - - vector> allData; - allData.clear(); - allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs + 1, 11)); - - valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs); - // has one slice - ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); - ValueMetricProducer::Interval curInterval = - valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0]; - ValueMetricProducer::BaseInfo curBaseInfo = - valueProducer->mCurrentBaseInfo.begin()->second.baseInfos[0]; - - EXPECT_EQ(true, curBaseInfo.hasBase); - EXPECT_EQ(11, curBaseInfo.base.long_value); - EXPECT_EQ(false, curInterval.hasValue); - ASSERT_EQ(0UL, valueProducer->mPastBuckets.size()); - - allData.clear(); - allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket3StartTimeNs + 1, 10)); - valueProducer->onDataPulled(allData, /** succeed */ true, bucket3StartTimeNs); - // has one slice - ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); - curInterval = valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0]; - curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second.baseInfos[0]; - EXPECT_EQ(true, curBaseInfo.hasBase); - EXPECT_EQ(10, curBaseInfo.base.long_value); - EXPECT_EQ(false, curInterval.hasValue); - ASSERT_EQ(0UL, valueProducer->mPastBuckets.size()); - - allData.clear(); - allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket4StartTimeNs + 1, 36)); - valueProducer->onDataPulled(allData, /** succeed */ true, bucket4StartTimeNs); - ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); - curInterval = valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0]; - curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second.baseInfos[0]; - EXPECT_EQ(true, curBaseInfo.hasBase); - EXPECT_EQ(36, curBaseInfo.base.long_value); - EXPECT_EQ(false, curInterval.hasValue); - EXPECT_EQ(26, curInterval.value.long_value); - ASSERT_EQ(1UL, valueProducer->mPastBuckets.size()); - EXPECT_EQ(26, valueProducer->mPastBuckets.begin()->second[0].values[0].long_value); - EXPECT_EQ(bucketSizeNs, valueProducer->mPastBuckets.begin()->second[0].mConditionTrueNs); -} - -/* - * Test pulled event with non sliced condition. - */ -TEST(ValueMetricProducerTest, TestEventsWithNonSlicedCondition) { - ValueMetric metric = ValueMetricProducerTestHelper::createMetricWithCondition(); - - sp pullerManager = new StrictMock(); - - EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _)) - .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, - vector>* data) { - EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 8); // First condition change. - data->clear(); - data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 8, 100)); - return true; - })) - .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, - vector>* data) { - EXPECT_EQ(eventTimeNs, bucket2StartTimeNs + 1); // Second condition change. - data->clear(); - data->push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs + 1, 130)); - return true; - })) - .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, - vector>* data) { - EXPECT_EQ(eventTimeNs, bucket3StartTimeNs + 1); // Third condition change. - data->clear(); - data->push_back(CreateRepeatedValueLogEvent(tagId, bucket3StartTimeNs + 1, 180)); - return true; - })); - - sp valueProducer = - ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric, - ConditionState::kFalse); - - valueProducer->onConditionChanged(true, bucketStartTimeNs + 8); - - // has one slice - ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); - ValueMetricProducer::Interval curInterval = - valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0]; - ValueMetricProducer::BaseInfo curBaseInfo = - valueProducer->mCurrentBaseInfo.begin()->second.baseInfos[0]; - // startUpdated:false sum:0 start:100 - EXPECT_EQ(true, curBaseInfo.hasBase); - EXPECT_EQ(100, curBaseInfo.base.long_value); - EXPECT_EQ(false, curInterval.hasValue); - ASSERT_EQ(0UL, valueProducer->mPastBuckets.size()); - - vector> allData; - allData.clear(); - allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs + 1, 110)); - valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs); - assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {10}, {bucketSizeNs - 8}, - {bucketStartTimeNs}, {bucket2StartTimeNs}); - - // has one slice - ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); - curInterval = valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0]; - curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second.baseInfos[0]; - EXPECT_EQ(true, curBaseInfo.hasBase); - EXPECT_EQ(110, curBaseInfo.base.long_value); - EXPECT_EQ(false, curInterval.hasValue); - EXPECT_EQ(10, curInterval.value.long_value); - - valueProducer->onConditionChanged(false, bucket2StartTimeNs + 1); - assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {10}, {bucketSizeNs - 8}, - {bucketStartTimeNs}, {bucket2StartTimeNs}); - - // has one slice - ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); - curInterval = valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0]; - curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second.baseInfos[0]; - EXPECT_EQ(true, curInterval.hasValue); - EXPECT_EQ(20, curInterval.value.long_value); - EXPECT_EQ(false, curBaseInfo.hasBase); - - valueProducer->onConditionChanged(true, bucket3StartTimeNs + 1); - assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {10, 20}, {bucketSizeNs - 8, 1}, - {bucketStartTimeNs, bucket2StartTimeNs}, - {bucket2StartTimeNs, bucket3StartTimeNs}); -} - -TEST_P(ValueMetricProducerTest_PartialBucket, TestPushedEvents) { - ValueMetric metric = ValueMetricProducerTestHelper::createMetric(); - - sp eventMatcherWizard = - createEventMatcherWizard(tagId, logEventMatcherIndex); - sp wizard = new NaggyMock(); - sp pullerManager = new StrictMock(); - - ValueMetricProducer valueProducer(kConfigKey, metric, -1, {}, wizard, protoHash, - logEventMatcherIndex, eventMatcherWizard, -1, - bucketStartTimeNs, bucketStartTimeNs, pullerManager); - valueProducer.prepareFirstBucket(); - - LogEvent event1(/*uid=*/0, /*pid=*/0); - CreateRepeatedValueLogEvent(&event1, tagId, bucketStartTimeNs + 10, 10); - valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event1); - ASSERT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); - - int64_t partialBucketSplitTimeNs = bucketStartTimeNs + 150; - switch (GetParam()) { - case APP_UPGRADE: - valueProducer.notifyAppUpgrade(partialBucketSplitTimeNs); - break; - case BOOT_COMPLETE: - valueProducer.onStatsdInitCompleted(partialBucketSplitTimeNs); - break; - } - assertPastBucketValuesSingleKey(valueProducer.mPastBuckets, {10}, - {partialBucketSplitTimeNs - bucketStartTimeNs}, - {bucketStartTimeNs}, {partialBucketSplitTimeNs}); - EXPECT_EQ(partialBucketSplitTimeNs, valueProducer.mCurrentBucketStartTimeNs); - EXPECT_EQ(0, valueProducer.getCurrentBucketNum()); - - // Event arrives after the bucket split. - LogEvent event2(/*uid=*/0, /*pid=*/0); - CreateRepeatedValueLogEvent(&event2, tagId, bucketStartTimeNs + 59 * NS_PER_SEC, 20); - valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event2); - - assertPastBucketValuesSingleKey(valueProducer.mPastBuckets, {10}, - {partialBucketSplitTimeNs - bucketStartTimeNs}, - {bucketStartTimeNs}, {partialBucketSplitTimeNs}); - EXPECT_EQ(partialBucketSplitTimeNs, valueProducer.mCurrentBucketStartTimeNs); - EXPECT_EQ(0, valueProducer.getCurrentBucketNum()); - - // Next value should create a new bucket. - LogEvent event3(/*uid=*/0, /*pid=*/0); - CreateRepeatedValueLogEvent(&event3, tagId, bucket2StartTimeNs + 5 * NS_PER_SEC, 10); - valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event3); - assertPastBucketValuesSingleKey(valueProducer.mPastBuckets, {10, 20}, - {partialBucketSplitTimeNs - bucketStartTimeNs, - bucket2StartTimeNs - partialBucketSplitTimeNs}, - {bucketStartTimeNs, partialBucketSplitTimeNs}, - {partialBucketSplitTimeNs, bucket2StartTimeNs}); - EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, valueProducer.mCurrentBucketStartTimeNs); - EXPECT_EQ(1, valueProducer.getCurrentBucketNum()); -} - -TEST_P(ValueMetricProducerTest_PartialBucket, TestPulledValue) { - ValueMetric metric = ValueMetricProducerTestHelper::createMetric(); - - sp eventMatcherWizard = - createEventMatcherWizard(tagId, logEventMatcherIndex); - sp wizard = new NaggyMock(); - sp pullerManager = new StrictMock(); - int64_t partialBucketSplitTimeNs = bucket2StartTimeNs + 150; - EXPECT_CALL(*pullerManager, RegisterReceiver(tagId, kConfigKey, _, _, _)).WillOnce(Return()); - EXPECT_CALL(*pullerManager, UnRegisterReceiver(tagId, kConfigKey, _)).WillOnce(Return()); - EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _)) - .WillOnce(Return(true)) - .WillOnce(Invoke([partialBucketSplitTimeNs](int tagId, const ConfigKey&, - const int64_t eventTimeNs, - vector>* data) { - EXPECT_EQ(eventTimeNs, partialBucketSplitTimeNs); - data->clear(); - data->push_back(CreateRepeatedValueLogEvent(tagId, partialBucketSplitTimeNs, 120)); - return true; - })); - - ValueMetricProducer valueProducer(kConfigKey, metric, -1, {}, wizard, protoHash, - logEventMatcherIndex, eventMatcherWizard, tagId, - bucketStartTimeNs, bucketStartTimeNs, pullerManager); - valueProducer.prepareFirstBucket(); - - vector> allData; - allData.clear(); - allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs + 1, 100)); - - valueProducer.onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs); - ASSERT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); - - switch (GetParam()) { - case APP_UPGRADE: - valueProducer.notifyAppUpgrade(partialBucketSplitTimeNs); - break; - case BOOT_COMPLETE: - valueProducer.onStatsdInitCompleted(partialBucketSplitTimeNs); - break; - } - EXPECT_EQ(partialBucketSplitTimeNs, valueProducer.mCurrentBucketStartTimeNs); - EXPECT_EQ(1, valueProducer.getCurrentBucketNum()); - assertPastBucketValuesSingleKey(valueProducer.mPastBuckets, {20}, {150}, {bucket2StartTimeNs}, - {partialBucketSplitTimeNs}); - - allData.clear(); - allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket3StartTimeNs + 1, 150)); - valueProducer.onDataPulled(allData, /** succeed */ true, bucket3StartTimeNs); - EXPECT_EQ(bucket3StartTimeNs, valueProducer.mCurrentBucketStartTimeNs); - EXPECT_EQ(2, valueProducer.getCurrentBucketNum()); - assertPastBucketValuesSingleKey(valueProducer.mPastBuckets, {20, 30}, {150, bucketSizeNs - 150}, - {bucket2StartTimeNs, partialBucketSplitTimeNs}, - {partialBucketSplitTimeNs, bucket3StartTimeNs}); -} - -TEST(ValueMetricProducerTest, TestPulledWithAppUpgradeDisabled) { - ValueMetric metric = ValueMetricProducerTestHelper::createMetric(); - metric.set_split_bucket_for_app_upgrade(false); - - sp eventMatcherWizard = - createEventMatcherWizard(tagId, logEventMatcherIndex); - sp wizard = new NaggyMock(); - sp pullerManager = new StrictMock(); - EXPECT_CALL(*pullerManager, RegisterReceiver(tagId, kConfigKey, _, _, _)).WillOnce(Return()); - EXPECT_CALL(*pullerManager, UnRegisterReceiver(tagId, kConfigKey, _)).WillOnce(Return()); - EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, bucketStartTimeNs, _)) - .WillOnce(Return(true)); - - ValueMetricProducer valueProducer(kConfigKey, metric, -1, {}, wizard, protoHash, - logEventMatcherIndex, eventMatcherWizard, tagId, - bucketStartTimeNs, bucketStartTimeNs, pullerManager); - valueProducer.prepareFirstBucket(); - - vector> allData; - allData.clear(); - allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs + 1, 100)); - - valueProducer.onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs); - ASSERT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); - - valueProducer.notifyAppUpgrade(bucket2StartTimeNs + 150); - ASSERT_EQ(0UL, valueProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size()); - EXPECT_EQ(bucket2StartTimeNs, valueProducer.mCurrentBucketStartTimeNs); -} - -TEST_P(ValueMetricProducerTest_PartialBucket, TestPulledValueWhileConditionFalse) { - ValueMetric metric = ValueMetricProducerTestHelper::createMetricWithCondition(); - - sp pullerManager = new StrictMock(); - EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _)) - .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, - vector>* data) { - EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 1); // Condition change to true time. - data->clear(); - data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 1, 100)); - return true; - })) - .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, - vector>* data) { - EXPECT_EQ(eventTimeNs, - bucket2StartTimeNs - 100); // Condition change to false time. - data->clear(); - data->push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs - 100, 120)); - return true; - })); - sp valueProducer = - ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric, - ConditionState::kFalse); - - valueProducer->onConditionChanged(true, bucketStartTimeNs + 1); - - valueProducer->onConditionChanged(false, bucket2StartTimeNs - 100); - EXPECT_FALSE(valueProducer->mCondition); - - int64_t partialBucketSplitTimeNs = bucket2StartTimeNs - 50; - switch (GetParam()) { - case APP_UPGRADE: - valueProducer->notifyAppUpgrade(partialBucketSplitTimeNs); - break; - case BOOT_COMPLETE: - valueProducer->onStatsdInitCompleted(partialBucketSplitTimeNs); - break; - } - // Expect one full buckets already done and starting a partial bucket. - EXPECT_EQ(partialBucketSplitTimeNs, valueProducer->mCurrentBucketStartTimeNs); - EXPECT_EQ(0, valueProducer->getCurrentBucketNum()); - assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {20}, - {(bucket2StartTimeNs - 100) - (bucketStartTimeNs + 1)}, - {bucketStartTimeNs}, {partialBucketSplitTimeNs}); - EXPECT_FALSE(valueProducer->mCondition); -} - -TEST(ValueMetricProducerTest, TestPushedEventsWithoutCondition) { - ValueMetric metric = ValueMetricProducerTestHelper::createMetric(); - - sp eventMatcherWizard = - createEventMatcherWizard(tagId, logEventMatcherIndex); - sp wizard = new NaggyMock(); - sp pullerManager = new StrictMock(); - - ValueMetricProducer valueProducer(kConfigKey, metric, -1, {}, wizard, protoHash, - logEventMatcherIndex, eventMatcherWizard, -1, - bucketStartTimeNs, bucketStartTimeNs, pullerManager); - valueProducer.prepareFirstBucket(); - - LogEvent event1(/*uid=*/0, /*pid=*/0); - CreateRepeatedValueLogEvent(&event1, tagId, bucketStartTimeNs + 10, 10); - - LogEvent event2(/*uid=*/0, /*pid=*/0); - CreateRepeatedValueLogEvent(&event2, tagId, bucketStartTimeNs + 20, 20); - - valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event1); - // has one slice - ASSERT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); - ValueMetricProducer::Interval curInterval = - valueProducer.mCurrentSlicedBucket.begin()->second.intervals[0]; - ValueMetricProducer::BaseInfo curBaseInfo = - valueProducer.mCurrentBaseInfo.begin()->second.baseInfos[0]; - EXPECT_EQ(10, curInterval.value.long_value); - EXPECT_EQ(true, curInterval.hasValue); - - valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event2); - - // has one slice - ASSERT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); - curInterval = valueProducer.mCurrentSlicedBucket.begin()->second.intervals[0]; - EXPECT_EQ(30, curInterval.value.long_value); - - valueProducer.flushIfNeededLocked(bucket2StartTimeNs); - assertPastBucketValuesSingleKey(valueProducer.mPastBuckets, {30}, {bucketSizeNs}, - {bucketStartTimeNs}, {bucket2StartTimeNs}); -} - -TEST(ValueMetricProducerTest, TestPushedEventsWithCondition) { - ValueMetric metric = ValueMetricProducerTestHelper::createMetric(); - - sp eventMatcherWizard = - createEventMatcherWizard(tagId, logEventMatcherIndex); - sp wizard = new NaggyMock(); - sp pullerManager = new StrictMock(); - - ValueMetricProducer valueProducer(kConfigKey, metric, 0, {ConditionState::kUnknown}, wizard, - protoHash, logEventMatcherIndex, eventMatcherWizard, -1, - bucketStartTimeNs, bucketStartTimeNs, pullerManager); - valueProducer.prepareFirstBucket(); - valueProducer.mCondition = ConditionState::kFalse; - - LogEvent event1(/*uid=*/0, /*pid=*/0); - CreateRepeatedValueLogEvent(&event1, tagId, bucketStartTimeNs + 10, 10); - valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event1); - // has 1 slice - ASSERT_EQ(0UL, valueProducer.mCurrentSlicedBucket.size()); - - valueProducer.onConditionChangedLocked(true, bucketStartTimeNs + 15); - - LogEvent event2(/*uid=*/0, /*pid=*/0); - CreateRepeatedValueLogEvent(&event2, tagId, bucketStartTimeNs + 20, 20); - valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event2); - - // has one slice - ASSERT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); - ValueMetricProducer::Interval curInterval = - valueProducer.mCurrentSlicedBucket.begin()->second.intervals[0]; - curInterval = valueProducer.mCurrentSlicedBucket.begin()->second.intervals[0]; - EXPECT_EQ(20, curInterval.value.long_value); - - LogEvent event3(/*uid=*/0, /*pid=*/0); - CreateRepeatedValueLogEvent(&event3, tagId, bucketStartTimeNs + 30, 30); - valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event3); - - // has one slice - ASSERT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); - curInterval = valueProducer.mCurrentSlicedBucket.begin()->second.intervals[0]; - EXPECT_EQ(50, curInterval.value.long_value); - - valueProducer.onConditionChangedLocked(false, bucketStartTimeNs + 35); - - LogEvent event4(/*uid=*/0, /*pid=*/0); - CreateRepeatedValueLogEvent(&event4, tagId, bucketStartTimeNs + 40, 40); - valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event4); - - // has one slice - ASSERT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); - curInterval = valueProducer.mCurrentSlicedBucket.begin()->second.intervals[0]; - EXPECT_EQ(50, curInterval.value.long_value); - - valueProducer.flushIfNeededLocked(bucket2StartTimeNs); - assertPastBucketValuesSingleKey(valueProducer.mPastBuckets, {50}, {20}, {bucketStartTimeNs}, - {bucket2StartTimeNs}); -} - -TEST(ValueMetricProducerTest, TestAnomalyDetection) { - sp alarmMonitor; - Alert alert; - alert.set_id(101); - alert.set_metric_id(metricId); - alert.set_trigger_if_sum_gt(130); - alert.set_num_buckets(2); - const int32_t refPeriodSec = 3; - alert.set_refractory_period_secs(refPeriodSec); - - ValueMetric metric = ValueMetricProducerTestHelper::createMetric(); - - sp eventMatcherWizard = - createEventMatcherWizard(tagId, logEventMatcherIndex); - sp wizard = new NaggyMock(); - sp pullerManager = new StrictMock(); - - ValueMetricProducer valueProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, {}, - wizard, protoHash, logEventMatcherIndex, eventMatcherWizard, - -1 /*not pulled*/, bucketStartTimeNs, bucketStartTimeNs, - pullerManager); - valueProducer.prepareFirstBucket(); - - sp anomalyTracker = - valueProducer.addAnomalyTracker(alert, alarmMonitor, UPDATE_NEW, bucketStartTimeNs); - - LogEvent event1(/*uid=*/0, /*pid=*/0); - CreateRepeatedValueLogEvent(&event1, tagId, bucketStartTimeNs + 1 * NS_PER_SEC, 10); - - LogEvent event2(/*uid=*/0, /*pid=*/0); - CreateRepeatedValueLogEvent(&event2, tagId, bucketStartTimeNs + 2 + NS_PER_SEC, 20); - - LogEvent event3(/*uid=*/0, /*pid=*/0); - CreateRepeatedValueLogEvent(&event3, tagId, - bucketStartTimeNs + 2 * bucketSizeNs + 1 * NS_PER_SEC, 130); - - LogEvent event4(/*uid=*/0, /*pid=*/0); - CreateRepeatedValueLogEvent(&event4, tagId, - bucketStartTimeNs + 3 * bucketSizeNs + 1 * NS_PER_SEC, 1); - - LogEvent event5(/*uid=*/0, /*pid=*/0); - CreateRepeatedValueLogEvent(&event5, tagId, - bucketStartTimeNs + 3 * bucketSizeNs + 2 * NS_PER_SEC, 150); - - LogEvent event6(/*uid=*/0, /*pid=*/0); - CreateRepeatedValueLogEvent(&event6, tagId, - bucketStartTimeNs + 3 * bucketSizeNs + 10 * NS_PER_SEC, 160); - - // Two events in bucket #0. - valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event1); - valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event2); - // Value sum == 30 <= 130. - 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_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_METRIC_DIMENSION_KEY), - std::ceil(1.0 * event4.GetElapsedTimestampNs() / 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_METRIC_DIMENSION_KEY), - std::ceil(1.0 * event4.GetElapsedTimestampNs() / 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_METRIC_DIMENSION_KEY), - std::ceil(1.0 * event6.GetElapsedTimestampNs() / NS_PER_SEC + refPeriodSec)); -} - -TEST(ValueMetricProducerTest, TestAnomalyDetectionMultipleBucketsSkipped) { - sp alarmMonitor; - Alert alert; - alert.set_id(101); - alert.set_metric_id(metricId); - alert.set_trigger_if_sum_gt(100); - alert.set_num_buckets(1); - const int32_t refPeriodSec = 3; - alert.set_refractory_period_secs(refPeriodSec); - - ValueMetric metric = ValueMetricProducerTestHelper::createMetricWithCondition(); - - sp pullerManager = new StrictMock(); - EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _)) - .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, - vector>* data) { - EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 1); // Condition change to true time. - data->clear(); - data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 1, 0)); - return true; - })) - .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, - vector>* data) { - EXPECT_EQ(eventTimeNs, - bucket3StartTimeNs + 100); // Condition changed to false time. - data->clear(); - data->push_back(CreateRepeatedValueLogEvent(tagId, bucket3StartTimeNs + 100, 120)); - return true; - })); - sp valueProducer = - ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric, - ConditionState::kFalse); - sp anomalyTracker = - valueProducer->addAnomalyTracker(alert, alarmMonitor, UPDATE_NEW, bucketStartTimeNs); - - valueProducer->onConditionChanged(true, bucketStartTimeNs + 1); - - // multiple buckets should be skipped here. - valueProducer->onConditionChanged(false, bucket3StartTimeNs + 100); - - // No alert is fired when multiple buckets are skipped. - EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(DEFAULT_METRIC_DIMENSION_KEY), 0U); -} - -// Test value metric no condition, the pull on bucket boundary come in time and too late -TEST(ValueMetricProducerTest, TestBucketBoundaryNoCondition) { - ValueMetric metric = ValueMetricProducerTestHelper::createMetric(); - sp pullerManager = new StrictMock(); - EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, bucketStartTimeNs, _)) - .WillOnce(Return(true)); - sp valueProducer = - ValueMetricProducerTestHelper::createValueProducerNoConditions(pullerManager, metric); - - vector> allData; - // pull 1 - allData.clear(); - allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs + 1, 11)); - - valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs); - // has one slice - ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); - ValueMetricProducer::Interval curInterval = - valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0]; - ValueMetricProducer::BaseInfo curBaseInfo = - valueProducer->mCurrentBaseInfo.begin()->second.baseInfos[0]; - - // startUpdated:true sum:0 start:11 - EXPECT_EQ(true, curBaseInfo.hasBase); - EXPECT_EQ(11, curBaseInfo.base.long_value); - EXPECT_EQ(false, curInterval.hasValue); - ASSERT_EQ(0UL, valueProducer->mPastBuckets.size()); - - // pull 2 at correct time - allData.clear(); - allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket3StartTimeNs + 1, 23)); - valueProducer->onDataPulled(allData, /** succeed */ true, bucket3StartTimeNs); - // has one slice - ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); - curInterval = valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0]; - curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second.baseInfos[0]; - // tartUpdated:false sum:12 - EXPECT_EQ(true, curBaseInfo.hasBase); - EXPECT_EQ(23, curBaseInfo.base.long_value); - EXPECT_EQ(false, curInterval.hasValue); - assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {12}, {bucketSizeNs}, - {bucket2StartTimeNs}, {bucket3StartTimeNs}); - - // pull 3 come late. - // The previous bucket gets closed with error. (Has start value 23, no ending) - // Another bucket gets closed with error. (No start, but ending with 36) - // The new bucket is back to normal. - allData.clear(); - allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket6StartTimeNs + 1, 36)); - valueProducer->onDataPulled(allData, /** succeed */ true, bucket6StartTimeNs); - ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); - curInterval = valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0]; - curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second.baseInfos[0]; - // startUpdated:false sum:12 - EXPECT_EQ(true, curBaseInfo.hasBase); - EXPECT_EQ(36, curBaseInfo.base.long_value); - EXPECT_EQ(false, curInterval.hasValue); - assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {12}, {bucketSizeNs}, - {bucket2StartTimeNs}, {bucket3StartTimeNs}); - // The 1st bucket is dropped because of no data - // The 3rd bucket is dropped due to multiple buckets being skipped. - ASSERT_EQ(2, valueProducer->mSkippedBuckets.size()); - - EXPECT_EQ(bucketStartTimeNs, valueProducer->mSkippedBuckets[0].bucketStartTimeNs); - EXPECT_EQ(bucket2StartTimeNs, valueProducer->mSkippedBuckets[0].bucketEndTimeNs); - ASSERT_EQ(1, valueProducer->mSkippedBuckets[0].dropEvents.size()); - EXPECT_EQ(NO_DATA, valueProducer->mSkippedBuckets[0].dropEvents[0].reason); - EXPECT_EQ(bucket2StartTimeNs, valueProducer->mSkippedBuckets[0].dropEvents[0].dropTimeNs); - - EXPECT_EQ(bucket3StartTimeNs, valueProducer->mSkippedBuckets[1].bucketStartTimeNs); - EXPECT_EQ(bucket6StartTimeNs, valueProducer->mSkippedBuckets[1].bucketEndTimeNs); - ASSERT_EQ(1, valueProducer->mSkippedBuckets[1].dropEvents.size()); - EXPECT_EQ(MULTIPLE_BUCKETS_SKIPPED, valueProducer->mSkippedBuckets[1].dropEvents[0].reason); - EXPECT_EQ(bucket6StartTimeNs, valueProducer->mSkippedBuckets[1].dropEvents[0].dropTimeNs); -} - -/* - * Test pulled event with non sliced condition. The pull on boundary come late because the alarm - * was delivered late. - */ -TEST(ValueMetricProducerTest, TestBucketBoundaryWithCondition) { - ValueMetric metric = ValueMetricProducerTestHelper::createMetricWithCondition(); - - sp pullerManager = new StrictMock(); - EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _)) - // condition becomes true - .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, - vector>* data) { - EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 8); // First condition change. - data->clear(); - data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 8, 100)); - return true; - })) - // condition becomes false - .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, - vector>* data) { - EXPECT_EQ(eventTimeNs, bucket2StartTimeNs + 1); // Second condition change. - data->clear(); - data->push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs + 1, 120)); - return true; - })); - sp valueProducer = - ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric, - ConditionState::kFalse); - - valueProducer->onConditionChanged(true, bucketStartTimeNs + 8); - - // has one slice - ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); - ValueMetricProducer::Interval curInterval = - valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0]; - ValueMetricProducer::BaseInfo curBaseInfo = - valueProducer->mCurrentBaseInfo.begin()->second.baseInfos[0]; - EXPECT_EQ(true, curBaseInfo.hasBase); - EXPECT_EQ(100, curBaseInfo.base.long_value); - EXPECT_EQ(false, curInterval.hasValue); - ASSERT_EQ(0UL, valueProducer->mPastBuckets.size()); - - // pull on bucket boundary come late, condition change happens before it - valueProducer->onConditionChanged(false, bucket2StartTimeNs + 1); - curInterval = valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0]; - curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second.baseInfos[0]; - assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {20}, {bucketSizeNs - 8}, - {bucketStartTimeNs}, {bucket2StartTimeNs}); - EXPECT_EQ(false, curBaseInfo.hasBase); - - // Now the alarm is delivered. - // since the condition turned to off before this pull finish, it has no effect - vector> allData; - allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs + 30, 110)); - valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs); - - assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {20}, {bucketSizeNs - 8}, - {bucketStartTimeNs}, {bucket2StartTimeNs}); - curInterval = valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0]; - curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second.baseInfos[0]; - EXPECT_EQ(false, curBaseInfo.hasBase); - EXPECT_EQ(false, curInterval.hasValue); -} - -/* - * Test pulled event with non sliced condition. The pull on boundary come late, after the condition - * change to false, and then true again. This is due to alarm delivered late. - */ -TEST(ValueMetricProducerTest, TestBucketBoundaryWithCondition2) { - ValueMetric metric = ValueMetricProducerTestHelper::createMetricWithCondition(); - - sp pullerManager = new StrictMock(); - EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _)) - // condition becomes true - .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, - vector>* data) { - EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 8); - data->clear(); - data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 8, 100)); - return true; - })) - // condition becomes false - .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, - vector>* data) { - EXPECT_EQ(eventTimeNs, bucket2StartTimeNs + 1); - data->clear(); - data->push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs + 1, 120)); - return true; - })) - // condition becomes true again - .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, - vector>* data) { - EXPECT_EQ(eventTimeNs, bucket2StartTimeNs + 25); - data->clear(); - data->push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs + 25, 130)); - return true; - })); - - sp valueProducer = - ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric, - ConditionState::kFalse); - - valueProducer->onConditionChanged(true, bucketStartTimeNs + 8); - - // has one slice - ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); - ValueMetricProducer::Interval curInterval = - valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0]; - ValueMetricProducer::BaseInfo curBaseInfo = - valueProducer->mCurrentBaseInfo.begin()->second.baseInfos[0]; - // startUpdated:false sum:0 start:100 - EXPECT_EQ(true, curBaseInfo.hasBase); - EXPECT_EQ(100, curBaseInfo.base.long_value); - EXPECT_EQ(false, curInterval.hasValue); - ASSERT_EQ(0UL, valueProducer->mPastBuckets.size()); - - // pull on bucket boundary come late, condition change happens before it - valueProducer->onConditionChanged(false, bucket2StartTimeNs + 1); - assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {20}, {bucketSizeNs - 8}, - {bucketStartTimeNs}, {bucket2StartTimeNs}); - ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); - curInterval = valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0]; - curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second.baseInfos[0]; - EXPECT_EQ(false, curBaseInfo.hasBase); - EXPECT_EQ(false, curInterval.hasValue); - - // condition changed to true again, before the pull alarm is delivered - valueProducer->onConditionChanged(true, bucket2StartTimeNs + 25); - assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {20}, {bucketSizeNs - 8}, - {bucketStartTimeNs}, {bucket2StartTimeNs}); - curInterval = valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0]; - curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second.baseInfos[0]; - EXPECT_EQ(true, curBaseInfo.hasBase); - EXPECT_EQ(130, curBaseInfo.base.long_value); - EXPECT_EQ(false, curInterval.hasValue); - - // Now the alarm is delivered, but it is considered late, the data will be used - // for the new bucket since it was just pulled. - vector> allData; - allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs + 50, 140)); - valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs + 50); - - curInterval = valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0]; - curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second.baseInfos[0]; - EXPECT_EQ(true, curBaseInfo.hasBase); - EXPECT_EQ(140, curBaseInfo.base.long_value); - EXPECT_EQ(true, curInterval.hasValue); - EXPECT_EQ(10, curInterval.value.long_value); - assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {20}, {bucketSizeNs - 8}, - {bucketStartTimeNs}, {bucket2StartTimeNs}); - - allData.clear(); - allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket3StartTimeNs, 160)); - valueProducer->onDataPulled(allData, /** succeed */ true, bucket3StartTimeNs); - assertPastBucketValuesSingleKey( - valueProducer->mPastBuckets, {20, 30}, {bucketSizeNs - 8, bucketSizeNs - 24}, - {bucketStartTimeNs, bucket2StartTimeNs}, {bucket2StartTimeNs, bucket3StartTimeNs}); -} - -TEST(ValueMetricProducerTest, TestPushedAggregateMin) { - ValueMetric metric = ValueMetricProducerTestHelper::createMetric(); - metric.set_aggregation_type(ValueMetric::MIN); - - sp eventMatcherWizard = - createEventMatcherWizard(tagId, logEventMatcherIndex); - sp wizard = new NaggyMock(); - sp pullerManager = new StrictMock(); - - ValueMetricProducer valueProducer(kConfigKey, metric, -1, {}, wizard, protoHash, - logEventMatcherIndex, eventMatcherWizard, -1, - bucketStartTimeNs, bucketStartTimeNs, pullerManager); - valueProducer.prepareFirstBucket(); - - LogEvent event1(/*uid=*/0, /*pid=*/0); - CreateRepeatedValueLogEvent(&event1, tagId, bucketStartTimeNs + 10, 10); - - LogEvent event2(/*uid=*/0, /*pid=*/0); - CreateRepeatedValueLogEvent(&event2, tagId, bucketStartTimeNs + 20, 20); - - valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event1); - // has one slice - ASSERT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); - ValueMetricProducer::Interval curInterval = - valueProducer.mCurrentSlicedBucket.begin()->second.intervals[0]; - EXPECT_EQ(10, curInterval.value.long_value); - EXPECT_EQ(true, curInterval.hasValue); - - valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event2); - - // has one slice - ASSERT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); - curInterval = valueProducer.mCurrentSlicedBucket.begin()->second.intervals[0]; - EXPECT_EQ(10, curInterval.value.long_value); - - valueProducer.flushIfNeededLocked(bucket2StartTimeNs); - assertPastBucketValuesSingleKey(valueProducer.mPastBuckets, {10}, {bucketSizeNs}, - {bucketStartTimeNs}, {bucket2StartTimeNs}); -} - -TEST(ValueMetricProducerTest, TestPushedAggregateMax) { - ValueMetric metric = ValueMetricProducerTestHelper::createMetric(); - metric.set_aggregation_type(ValueMetric::MAX); - - sp eventMatcherWizard = - createEventMatcherWizard(tagId, logEventMatcherIndex); - sp wizard = new NaggyMock(); - sp pullerManager = new StrictMock(); - - ValueMetricProducer valueProducer(kConfigKey, metric, -1, {}, wizard, protoHash, - logEventMatcherIndex, eventMatcherWizard, -1, - bucketStartTimeNs, bucketStartTimeNs, pullerManager); - valueProducer.prepareFirstBucket(); - - LogEvent event1(/*uid=*/0, /*pid=*/0); - CreateRepeatedValueLogEvent(&event1, tagId, bucketStartTimeNs + 10, 10); - valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event1); - - // has one slice - ASSERT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); - ValueMetricProducer::Interval curInterval = - valueProducer.mCurrentSlicedBucket.begin()->second.intervals[0]; - EXPECT_EQ(10, curInterval.value.long_value); - EXPECT_EQ(true, curInterval.hasValue); - - LogEvent event2(/*uid=*/0, /*pid=*/0); - CreateRepeatedValueLogEvent(&event2, tagId, bucketStartTimeNs + 20, 20); - valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event2); - - // has one slice - ASSERT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); - curInterval = valueProducer.mCurrentSlicedBucket.begin()->second.intervals[0]; - EXPECT_EQ(20, curInterval.value.long_value); - - valueProducer.flushIfNeededLocked(bucket2StartTimeNs); - assertPastBucketValuesSingleKey(valueProducer.mPastBuckets, {20}, {bucketSizeNs}, - {bucketStartTimeNs}, {bucket2StartTimeNs}); -} - -TEST(ValueMetricProducerTest, TestPushedAggregateAvg) { - ValueMetric metric = ValueMetricProducerTestHelper::createMetric(); - metric.set_aggregation_type(ValueMetric::AVG); - - sp eventMatcherWizard = - createEventMatcherWizard(tagId, logEventMatcherIndex); - sp wizard = new NaggyMock(); - sp pullerManager = new StrictMock(); - - ValueMetricProducer valueProducer(kConfigKey, metric, -1, {}, wizard, protoHash, - logEventMatcherIndex, eventMatcherWizard, -1, - bucketStartTimeNs, bucketStartTimeNs, pullerManager); - valueProducer.prepareFirstBucket(); - - LogEvent event1(/*uid=*/0, /*pid=*/0); - CreateRepeatedValueLogEvent(&event1, tagId, bucketStartTimeNs + 10, 10); - - LogEvent event2(/*uid=*/0, /*pid=*/0); - CreateRepeatedValueLogEvent(&event2, tagId, bucketStartTimeNs + 20, 15); - valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event1); - // has one slice - ASSERT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); - ValueMetricProducer::Interval curInterval; - curInterval = valueProducer.mCurrentSlicedBucket.begin()->second.intervals[0]; - EXPECT_EQ(10, curInterval.value.long_value); - EXPECT_EQ(true, curInterval.hasValue); - EXPECT_EQ(1, curInterval.sampleSize); - - valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event2); - - // has one slice - ASSERT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); - curInterval = valueProducer.mCurrentSlicedBucket.begin()->second.intervals[0]; - EXPECT_EQ(25, curInterval.value.long_value); - EXPECT_EQ(2, curInterval.sampleSize); - - valueProducer.flushIfNeededLocked(bucket2StartTimeNs); - ASSERT_EQ(1UL, valueProducer.mPastBuckets.size()); - ASSERT_EQ(1UL, valueProducer.mPastBuckets.begin()->second.size()); - - EXPECT_TRUE(std::abs(valueProducer.mPastBuckets.begin()->second.back().values[0].double_value - - 12.5) < epsilon); -} - -TEST(ValueMetricProducerTest, TestPushedAggregateSum) { - ValueMetric metric = ValueMetricProducerTestHelper::createMetric(); - metric.set_aggregation_type(ValueMetric::SUM); - - sp eventMatcherWizard = - createEventMatcherWizard(tagId, logEventMatcherIndex); - sp wizard = new NaggyMock(); - sp pullerManager = new StrictMock(); - - ValueMetricProducer valueProducer(kConfigKey, metric, -1, {}, wizard, protoHash, - logEventMatcherIndex, eventMatcherWizard, -1, - bucketStartTimeNs, bucketStartTimeNs, pullerManager); - valueProducer.prepareFirstBucket(); - - LogEvent event1(/*uid=*/0, /*pid=*/0); - CreateRepeatedValueLogEvent(&event1, tagId, bucketStartTimeNs + 10, 10); - - LogEvent event2(/*uid=*/0, /*pid=*/0); - CreateRepeatedValueLogEvent(&event2, tagId, bucketStartTimeNs + 20, 15); - valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event1); - // has one slice - ASSERT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); - ValueMetricProducer::Interval curInterval = - valueProducer.mCurrentSlicedBucket.begin()->second.intervals[0]; - EXPECT_EQ(10, curInterval.value.long_value); - EXPECT_EQ(true, curInterval.hasValue); - - valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event2); - - // has one slice - ASSERT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); - curInterval = valueProducer.mCurrentSlicedBucket.begin()->second.intervals[0]; - EXPECT_EQ(25, curInterval.value.long_value); - - valueProducer.flushIfNeededLocked(bucket2StartTimeNs); - assertPastBucketValuesSingleKey(valueProducer.mPastBuckets, {25}, {bucketSizeNs}, - {bucketStartTimeNs}, {bucket2StartTimeNs}); -} - -TEST(ValueMetricProducerTest, TestSkipZeroDiffOutput) { - ValueMetric metric = ValueMetricProducerTestHelper::createMetric(); - metric.set_aggregation_type(ValueMetric::MIN); - metric.set_use_diff(true); - - sp eventMatcherWizard = - createEventMatcherWizard(tagId, logEventMatcherIndex); - sp wizard = new NaggyMock(); - sp pullerManager = new StrictMock(); - - ValueMetricProducer valueProducer(kConfigKey, metric, -1, {}, wizard, protoHash, - logEventMatcherIndex, eventMatcherWizard, -1, - bucketStartTimeNs, bucketStartTimeNs, pullerManager); - valueProducer.prepareFirstBucket(); - - LogEvent event1(/*uid=*/0, /*pid=*/0); - CreateRepeatedValueLogEvent(&event1, tagId, bucketStartTimeNs + 10, 10); - valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event1); - - // has one slice - ASSERT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); - ValueMetricProducer::Interval curInterval = - valueProducer.mCurrentSlicedBucket.begin()->second.intervals[0]; - ValueMetricProducer::BaseInfo curBaseInfo = - valueProducer.mCurrentBaseInfo.begin()->second.baseInfos[0]; - EXPECT_EQ(true, curBaseInfo.hasBase); - EXPECT_EQ(10, curBaseInfo.base.long_value); - EXPECT_EQ(false, curInterval.hasValue); - - LogEvent event2(/*uid=*/0, /*pid=*/0); - CreateRepeatedValueLogEvent(&event2, tagId, bucketStartTimeNs + 15, 15); - valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event2); - - // has one slice - ASSERT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); - curInterval = valueProducer.mCurrentSlicedBucket.begin()->second.intervals[0]; - EXPECT_EQ(true, curInterval.hasValue); - EXPECT_EQ(5, curInterval.value.long_value); - - // no change in data. - LogEvent event3(/*uid=*/0, /*pid=*/0); - CreateRepeatedValueLogEvent(&event3, tagId, bucket2StartTimeNs + 10, 15); - valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event3); - - ASSERT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); - curInterval = valueProducer.mCurrentSlicedBucket.begin()->second.intervals[0]; - curBaseInfo = valueProducer.mCurrentBaseInfo.begin()->second.baseInfos[0]; - EXPECT_EQ(true, curBaseInfo.hasBase); - EXPECT_EQ(15, curBaseInfo.base.long_value); - EXPECT_EQ(true, curInterval.hasValue); - EXPECT_EQ(0, curInterval.value.long_value); - - LogEvent event4(/*uid=*/0, /*pid=*/0); - CreateRepeatedValueLogEvent(&event4, tagId, bucket2StartTimeNs + 15, 15); - valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event4); - ASSERT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); - curInterval = valueProducer.mCurrentSlicedBucket.begin()->second.intervals[0]; - curBaseInfo = valueProducer.mCurrentBaseInfo.begin()->second.baseInfos[0]; - EXPECT_EQ(true, curBaseInfo.hasBase); - EXPECT_EQ(15, curBaseInfo.base.long_value); - EXPECT_EQ(true, curInterval.hasValue); - EXPECT_EQ(0, curInterval.value.long_value); - - valueProducer.flushIfNeededLocked(bucket3StartTimeNs); - assertPastBucketValuesSingleKey(valueProducer.mPastBuckets, {5}, {bucketSizeNs}, - {bucketStartTimeNs}, {bucket2StartTimeNs}); -} - -TEST(ValueMetricProducerTest, TestSkipZeroDiffOutputMultiValue) { - ValueMetric metric = ValueMetricProducerTestHelper::createMetric(); - metric.mutable_value_field()->add_child()->set_field(3); - metric.set_aggregation_type(ValueMetric::MIN); - metric.set_use_diff(true); - - sp eventMatcherWizard = - createEventMatcherWizard(tagId, logEventMatcherIndex); - sp wizard = new NaggyMock(); - sp pullerManager = new StrictMock(); - - ValueMetricProducer valueProducer(kConfigKey, metric, -1, {}, wizard, protoHash, - logEventMatcherIndex, eventMatcherWizard, -1, - bucketStartTimeNs, bucketStartTimeNs, pullerManager); - valueProducer.prepareFirstBucket(); - - LogEvent event1(/*uid=*/0, /*pid=*/0); - CreateThreeValueLogEvent(&event1, tagId, bucketStartTimeNs + 10, 1, 10, 20); - - LogEvent event2(/*uid=*/0, /*pid=*/0); - CreateThreeValueLogEvent(&event2, tagId, bucketStartTimeNs + 15, 1, 15, 22); - - valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event1); - // has one slice - ASSERT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); - ValueMetricProducer::Interval curInterval = - valueProducer.mCurrentSlicedBucket.begin()->second.intervals[0]; - ValueMetricProducer::BaseInfo curBaseInfo = - valueProducer.mCurrentBaseInfo.begin()->second.baseInfos[0]; - EXPECT_EQ(true, curBaseInfo.hasBase); - EXPECT_EQ(10, curBaseInfo.base.long_value); - EXPECT_EQ(false, curInterval.hasValue); - curBaseInfo = valueProducer.mCurrentBaseInfo.begin()->second.baseInfos[1]; - EXPECT_EQ(true, curBaseInfo.hasBase); - EXPECT_EQ(20, curBaseInfo.base.long_value); - EXPECT_EQ(false, curInterval.hasValue); - - valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event2); - - // has one slice - ASSERT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); - curInterval = valueProducer.mCurrentSlicedBucket.begin()->second.intervals[0]; - curBaseInfo = valueProducer.mCurrentBaseInfo.begin()->second.baseInfos[0]; - EXPECT_EQ(true, curInterval.hasValue); - EXPECT_EQ(5, curInterval.value.long_value); - curInterval = valueProducer.mCurrentSlicedBucket.begin()->second.intervals[1]; - curBaseInfo = valueProducer.mCurrentBaseInfo.begin()->second.baseInfos[1]; - EXPECT_EQ(true, curInterval.hasValue); - EXPECT_EQ(2, curInterval.value.long_value); - - // no change in first value field - LogEvent event3(/*uid=*/0, /*pid=*/0); - CreateThreeValueLogEvent(&event3, tagId, bucket2StartTimeNs + 10, 1, 15, 25); - - valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event3); - ASSERT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); - curInterval = valueProducer.mCurrentSlicedBucket.begin()->second.intervals[0]; - curBaseInfo = valueProducer.mCurrentBaseInfo.begin()->second.baseInfos[0]; - - EXPECT_EQ(true, curBaseInfo.hasBase); - EXPECT_EQ(15, curBaseInfo.base.long_value); - EXPECT_EQ(true, curInterval.hasValue); - curInterval = valueProducer.mCurrentSlicedBucket.begin()->second.intervals[1]; - curBaseInfo = valueProducer.mCurrentBaseInfo.begin()->second.baseInfos[1]; - EXPECT_EQ(true, curBaseInfo.hasBase); - EXPECT_EQ(25, curBaseInfo.base.long_value); - EXPECT_EQ(true, curInterval.hasValue); - - LogEvent event4(/*uid=*/0, /*pid=*/0); - CreateThreeValueLogEvent(&event4, tagId, bucket2StartTimeNs + 15, 1, 15, 29); - - valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event4); - ASSERT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); - curInterval = valueProducer.mCurrentSlicedBucket.begin()->second.intervals[0]; - curBaseInfo = valueProducer.mCurrentBaseInfo.begin()->second.baseInfos[0]; - EXPECT_EQ(true, curBaseInfo.hasBase); - EXPECT_EQ(15, curBaseInfo.base.long_value); - EXPECT_EQ(true, curInterval.hasValue); - curInterval = valueProducer.mCurrentSlicedBucket.begin()->second.intervals[1]; - curBaseInfo = valueProducer.mCurrentBaseInfo.begin()->second.baseInfos[1]; - EXPECT_EQ(true, curBaseInfo.hasBase); - EXPECT_EQ(29, curBaseInfo.base.long_value); - EXPECT_EQ(true, curInterval.hasValue); - - valueProducer.flushIfNeededLocked(bucket3StartTimeNs); - - ASSERT_EQ(1UL, valueProducer.mPastBuckets.size()); - ASSERT_EQ(2UL, valueProducer.mPastBuckets.begin()->second.size()); - ASSERT_EQ(2UL, valueProducer.mPastBuckets.begin()->second[0].values.size()); - ASSERT_EQ(1UL, valueProducer.mPastBuckets.begin()->second[1].values.size()); - - EXPECT_EQ(bucketSizeNs, valueProducer.mPastBuckets.begin()->second[0].mConditionTrueNs); - EXPECT_EQ(5, valueProducer.mPastBuckets.begin()->second[0].values[0].long_value); - EXPECT_EQ(0, valueProducer.mPastBuckets.begin()->second[0].valueIndex[0]); - EXPECT_EQ(2, valueProducer.mPastBuckets.begin()->second[0].values[1].long_value); - EXPECT_EQ(1, valueProducer.mPastBuckets.begin()->second[0].valueIndex[1]); - - EXPECT_EQ(bucketSizeNs, valueProducer.mPastBuckets.begin()->second[1].mConditionTrueNs); - EXPECT_EQ(3, valueProducer.mPastBuckets.begin()->second[1].values[0].long_value); - EXPECT_EQ(1, valueProducer.mPastBuckets.begin()->second[1].valueIndex[0]); -} - -/* - * Tests zero default base. - */ -TEST(ValueMetricProducerTest, TestUseZeroDefaultBase) { - ValueMetric metric = ValueMetricProducerTestHelper::createMetric(); - metric.mutable_dimensions_in_what()->set_field(tagId); - metric.mutable_dimensions_in_what()->add_child()->set_field(1); - metric.set_use_zero_default_base(true); - - sp pullerManager = new StrictMock(); - EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, bucketStartTimeNs, _)) - .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t, - vector>* data) { - data->clear(); - data->push_back(CreateTwoValueLogEvent(tagId, bucketStartTimeNs, 1, 3)); - return true; - })); - - sp valueProducer = - ValueMetricProducerTestHelper::createValueProducerNoConditions(pullerManager, metric); - - ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); - auto iter = valueProducer->mCurrentSlicedBucket.begin(); - auto& interval1 = iter->second.intervals[0]; - auto iterBase = valueProducer->mCurrentBaseInfo.begin(); - auto& baseInfo1 = iterBase->second.baseInfos[0]; - EXPECT_EQ(1, iter->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); - EXPECT_EQ(true, baseInfo1.hasBase); - EXPECT_EQ(3, baseInfo1.base.long_value); - EXPECT_EQ(false, interval1.hasValue); - EXPECT_EQ(true, valueProducer->mHasGlobalBase); - ASSERT_EQ(0UL, valueProducer->mPastBuckets.size()); - vector> allData; - - allData.clear(); - allData.push_back(CreateTwoValueLogEvent(tagId, bucket2StartTimeNs + 1, 2, 4)); - allData.push_back(CreateTwoValueLogEvent(tagId, bucket2StartTimeNs + 1, 1, 11)); - - valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs); - ASSERT_EQ(2UL, valueProducer->mCurrentSlicedBucket.size()); - EXPECT_EQ(true, baseInfo1.hasBase); - EXPECT_EQ(11, baseInfo1.base.long_value); - EXPECT_EQ(false, interval1.hasValue); - EXPECT_EQ(8, interval1.value.long_value); - - auto it = valueProducer->mCurrentSlicedBucket.begin(); - for (; it != valueProducer->mCurrentSlicedBucket.end(); it++) { - if (it != iter) { - break; - } - } - auto itBase = valueProducer->mCurrentBaseInfo.begin(); - for (; itBase != valueProducer->mCurrentBaseInfo.end(); it++) { - if (itBase != iterBase) { - break; - } - } - EXPECT_TRUE(it != iter); - EXPECT_TRUE(itBase != iterBase); - auto& interval2 = it->second.intervals[0]; - auto& baseInfo2 = itBase->second.baseInfos[0]; - EXPECT_EQ(2, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); - EXPECT_EQ(true, baseInfo2.hasBase); - EXPECT_EQ(4, baseInfo2.base.long_value); - EXPECT_EQ(false, interval2.hasValue); - EXPECT_EQ(4, interval2.value.long_value); - - ASSERT_EQ(2UL, valueProducer->mPastBuckets.size()); - auto iterator = valueProducer->mPastBuckets.begin(); - EXPECT_EQ(bucketSizeNs, iterator->second[0].mConditionTrueNs); - EXPECT_EQ(8, iterator->second[0].values[0].long_value); - iterator++; - EXPECT_EQ(bucketSizeNs, iterator->second[0].mConditionTrueNs); - EXPECT_EQ(4, iterator->second[0].values[0].long_value); -} - -/* - * Tests using zero default base with failed pull. - */ -TEST(ValueMetricProducerTest, TestUseZeroDefaultBaseWithPullFailures) { - ValueMetric metric = ValueMetricProducerTestHelper::createMetric(); - metric.mutable_dimensions_in_what()->set_field(tagId); - metric.mutable_dimensions_in_what()->add_child()->set_field(1); - metric.set_use_zero_default_base(true); - - sp pullerManager = new StrictMock(); - EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, bucketStartTimeNs, _)) - .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t, - vector>* data) { - data->clear(); - data->push_back(CreateTwoValueLogEvent(tagId, bucketStartTimeNs, 1, 3)); - return true; - })); - - sp valueProducer = - ValueMetricProducerTestHelper::createValueProducerNoConditions(pullerManager, metric); - - ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); - const auto& it = valueProducer->mCurrentSlicedBucket.begin(); - ValueMetricProducer::Interval& interval1 = it->second.intervals[0]; - ValueMetricProducer::BaseInfo& baseInfo1 = - valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()) - ->second.baseInfos[0]; - EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); - EXPECT_EQ(true, baseInfo1.hasBase); - EXPECT_EQ(3, baseInfo1.base.long_value); - EXPECT_EQ(false, interval1.hasValue); - EXPECT_EQ(true, valueProducer->mHasGlobalBase); - ASSERT_EQ(0UL, valueProducer->mPastBuckets.size()); - vector> allData; - - allData.clear(); - allData.push_back(CreateTwoValueLogEvent(tagId, bucket2StartTimeNs + 1, 2, 4)); - allData.push_back(CreateTwoValueLogEvent(tagId, bucket2StartTimeNs + 1, 1, 11)); - - valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs); - ASSERT_EQ(2UL, valueProducer->mCurrentSlicedBucket.size()); - EXPECT_EQ(true, baseInfo1.hasBase); - EXPECT_EQ(11, baseInfo1.base.long_value); - EXPECT_EQ(false, interval1.hasValue); - EXPECT_EQ(8, interval1.value.long_value); - - auto it2 = valueProducer->mCurrentSlicedBucket.begin(); - for (; it2 != valueProducer->mCurrentSlicedBucket.end(); it2++) { - if (it2 != it) { - break; - } - } - EXPECT_TRUE(it2 != it); - ValueMetricProducer::Interval& interval2 = it2->second.intervals[0]; - ValueMetricProducer::BaseInfo& baseInfo2 = - valueProducer->mCurrentBaseInfo.find(it2->first.getDimensionKeyInWhat()) - ->second.baseInfos[0]; - EXPECT_EQ(2, it2->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); - EXPECT_EQ(true, baseInfo2.hasBase); - EXPECT_EQ(4, baseInfo2.base.long_value); - EXPECT_EQ(false, interval2.hasValue); - EXPECT_EQ(4, interval2.value.long_value); - ASSERT_EQ(2UL, valueProducer->mPastBuckets.size()); - - // next pull somehow did not happen, skip to end of bucket 3 - allData.clear(); - allData.push_back(CreateTwoValueLogEvent(tagId, bucket4StartTimeNs + 1, 2, 5)); - valueProducer->onDataPulled(allData, /** succeed */ true, bucket4StartTimeNs); - - ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); - EXPECT_EQ(true, baseInfo2.hasBase); - EXPECT_EQ(5, baseInfo2.base.long_value); - EXPECT_EQ(false, interval2.hasValue); - EXPECT_EQ(true, valueProducer->mHasGlobalBase); - ASSERT_EQ(2UL, valueProducer->mPastBuckets.size()); - - allData.clear(); - allData.push_back(CreateTwoValueLogEvent(tagId, bucket5StartTimeNs + 1, 2, 13)); - allData.push_back(CreateTwoValueLogEvent(tagId, bucket5StartTimeNs + 1, 1, 5)); - valueProducer->onDataPulled(allData, /** succeed */ true, bucket5StartTimeNs); - - ASSERT_EQ(2UL, valueProducer->mCurrentSlicedBucket.size()); - // Get new references now that entries have been deleted from the map - const auto& it3 = valueProducer->mCurrentSlicedBucket.begin(); - const auto& it4 = std::next(valueProducer->mCurrentSlicedBucket.begin()); - ASSERT_EQ(it3->second.intervals.size(), 1); - ASSERT_EQ(it4->second.intervals.size(), 1); - ValueMetricProducer::Interval& interval3 = it3->second.intervals[0]; - ValueMetricProducer::Interval& interval4 = it4->second.intervals[0]; - ValueMetricProducer::BaseInfo& baseInfo3 = - valueProducer->mCurrentBaseInfo.find(it3->first.getDimensionKeyInWhat()) - ->second.baseInfos[0]; - ValueMetricProducer::BaseInfo& baseInfo4 = - valueProducer->mCurrentBaseInfo.find(it4->first.getDimensionKeyInWhat()) - ->second.baseInfos[0]; - - EXPECT_EQ(true, baseInfo3.hasBase); - EXPECT_EQ(5, baseInfo3.base.long_value); - EXPECT_EQ(false, interval3.hasValue); - EXPECT_EQ(5, interval3.value.long_value); - EXPECT_EQ(true, valueProducer->mHasGlobalBase); - - EXPECT_EQ(true, baseInfo4.hasBase); - EXPECT_EQ(13, baseInfo4.base.long_value); - EXPECT_EQ(false, interval4.hasValue); - EXPECT_EQ(8, interval4.value.long_value); - - ASSERT_EQ(2UL, valueProducer->mPastBuckets.size()); -} - -/* - * Tests trim unused dimension key if no new data is seen in an entire bucket. - */ -TEST(ValueMetricProducerTest, TestTrimUnusedDimensionKey) { - ValueMetric metric = ValueMetricProducerTestHelper::createMetric(); - metric.mutable_dimensions_in_what()->set_field(tagId); - metric.mutable_dimensions_in_what()->add_child()->set_field(1); - - sp pullerManager = new StrictMock(); - EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, bucketStartTimeNs, _)) - .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t, - vector>* data) { - data->clear(); - data->push_back(CreateTwoValueLogEvent(tagId, bucketStartTimeNs, 1, 3)); - return true; - })); - - sp valueProducer = - ValueMetricProducerTestHelper::createValueProducerNoConditions(pullerManager, metric); - - ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); - auto iter = valueProducer->mCurrentSlicedBucket.begin(); - auto& interval1 = iter->second.intervals[0]; - auto iterBase = valueProducer->mCurrentBaseInfo.begin(); - auto& baseInfo1 = iterBase->second.baseInfos[0]; - EXPECT_EQ(1, iter->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); - EXPECT_EQ(true, baseInfo1.hasBase); - EXPECT_EQ(3, baseInfo1.base.long_value); - EXPECT_EQ(false, interval1.hasValue); - ASSERT_EQ(0UL, valueProducer->mPastBuckets.size()); - - vector> allData; - allData.clear(); - allData.push_back(CreateTwoValueLogEvent(tagId, bucket2StartTimeNs + 1, 2, 4)); - allData.push_back(CreateTwoValueLogEvent(tagId, bucket2StartTimeNs + 1, 1, 11)); - valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs); - - ASSERT_EQ(2UL, valueProducer->mCurrentSlicedBucket.size()); - EXPECT_EQ(true, baseInfo1.hasBase); - EXPECT_EQ(11, baseInfo1.base.long_value); - EXPECT_EQ(false, interval1.hasValue); - EXPECT_EQ(8, interval1.value.long_value); - EXPECT_FALSE(interval1.seenNewData); - assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {8}, {bucketSizeNs}, - {bucketStartTimeNs}, {bucket2StartTimeNs}); - - auto it = valueProducer->mCurrentSlicedBucket.begin(); - for (; it != valueProducer->mCurrentSlicedBucket.end(); it++) { - if (it != iter) { - break; - } - } - auto itBase = valueProducer->mCurrentBaseInfo.begin(); - for (; itBase != valueProducer->mCurrentBaseInfo.end(); it++) { - if (itBase != iterBase) { - break; - } - } - EXPECT_TRUE(it != iter); - EXPECT_TRUE(itBase != iterBase); - auto interval2 = it->second.intervals[0]; - auto baseInfo2 = itBase->second.baseInfos[0]; - EXPECT_EQ(2, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); - EXPECT_EQ(true, baseInfo2.hasBase); - EXPECT_EQ(4, baseInfo2.base.long_value); - EXPECT_EQ(false, interval2.hasValue); - EXPECT_FALSE(interval2.seenNewData); - - // next pull somehow did not happen, skip to end of bucket 3 - allData.clear(); - allData.push_back(CreateTwoValueLogEvent(tagId, bucket4StartTimeNs + 1, 2, 5)); - valueProducer->onDataPulled(allData, /** succeed */ true, bucket4StartTimeNs); - // Only one interval left. One was trimmed. - ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); - interval2 = valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0]; - baseInfo2 = valueProducer->mCurrentBaseInfo.begin()->second.baseInfos[0]; - EXPECT_EQ(2, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); - EXPECT_EQ(true, baseInfo2.hasBase); - EXPECT_EQ(5, baseInfo2.base.long_value); - EXPECT_EQ(false, interval2.hasValue); - EXPECT_FALSE(interval2.seenNewData); - assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {8}, {bucketSizeNs}, - {bucketStartTimeNs}, {bucket2StartTimeNs}); - - allData.clear(); - allData.push_back(CreateTwoValueLogEvent(tagId, bucket5StartTimeNs + 1, 2, 14)); - valueProducer->onDataPulled(allData, /** succeed */ true, bucket5StartTimeNs); - - interval2 = valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0]; - baseInfo2 = valueProducer->mCurrentBaseInfo.begin()->second.baseInfos[0]; - EXPECT_EQ(true, baseInfo2.hasBase); - EXPECT_EQ(14, baseInfo2.base.long_value); - EXPECT_EQ(false, interval2.hasValue); - EXPECT_FALSE(interval2.seenNewData); - ASSERT_EQ(2UL, valueProducer->mPastBuckets.size()); - auto iterator = valueProducer->mPastBuckets.begin(); - EXPECT_EQ(bucket4StartTimeNs, iterator->second[0].mBucketStartNs); - EXPECT_EQ(bucket5StartTimeNs, iterator->second[0].mBucketEndNs); - EXPECT_EQ(9, iterator->second[0].values[0].long_value); - EXPECT_EQ(bucketSizeNs, iterator->second[0].mConditionTrueNs); - iterator++; - EXPECT_EQ(bucketStartTimeNs, iterator->second[0].mBucketStartNs); - EXPECT_EQ(bucket2StartTimeNs, iterator->second[0].mBucketEndNs); - EXPECT_EQ(8, iterator->second[0].values[0].long_value); - EXPECT_EQ(bucketSizeNs, iterator->second[0].mConditionTrueNs); -} - -TEST(ValueMetricProducerTest, TestResetBaseOnPullFailAfterConditionChange_EndOfBucket) { - ValueMetric metric = ValueMetricProducerTestHelper::createMetricWithCondition(); - - sp pullerManager = new StrictMock(); - // Used by onConditionChanged. - EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, bucketStartTimeNs + 8, _)) - .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t, - vector>* data) { - data->clear(); - data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 8, 100)); - return true; - })); - - sp valueProducer = - ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric, - ConditionState::kFalse); - - valueProducer->onConditionChanged(true, bucketStartTimeNs + 8); - // has one slice - ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); - ValueMetricProducer::Interval& curInterval = - valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0]; - ValueMetricProducer::BaseInfo& curBaseInfo = - valueProducer->mCurrentBaseInfo.begin()->second.baseInfos[0]; - EXPECT_EQ(true, curBaseInfo.hasBase); - EXPECT_EQ(100, curBaseInfo.base.long_value); - EXPECT_EQ(false, curInterval.hasValue); - - vector> allData; - valueProducer->onDataPulled(allData, /** succeed */ false, bucket2StartTimeNs); - ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); - EXPECT_EQ(false, curBaseInfo.hasBase); - EXPECT_EQ(false, curInterval.hasValue); - EXPECT_EQ(false, valueProducer->mHasGlobalBase); -} - -TEST(ValueMetricProducerTest, TestResetBaseOnPullFailAfterConditionChange) { - ValueMetric metric = ValueMetricProducerTestHelper::createMetricWithCondition(); - - sp pullerManager = new StrictMock(); - EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _)) - .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, - vector>* data) { - EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 8); // Condition change to true. - data->clear(); - data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 8, 100)); - return true; - })) - .WillOnce(Return(false)); - - sp valueProducer = - ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric, - ConditionState::kFalse); - - valueProducer->onConditionChanged(true, bucketStartTimeNs + 8); - - // has one slice - ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); - ValueMetricProducer::Interval& curInterval = - valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0]; - ValueMetricProducer::BaseInfo& curBaseInfo = - valueProducer->mCurrentBaseInfo.begin()->second.baseInfos[0]; - EXPECT_EQ(true, curBaseInfo.hasBase); - EXPECT_EQ(100, curBaseInfo.base.long_value); - EXPECT_EQ(false, curInterval.hasValue); - ASSERT_EQ(0UL, valueProducer->mPastBuckets.size()); - - valueProducer->onConditionChanged(false, bucketStartTimeNs + 20); - - // has one slice - ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); - EXPECT_EQ(false, curInterval.hasValue); - EXPECT_EQ(false, curBaseInfo.hasBase); - EXPECT_EQ(false, valueProducer->mHasGlobalBase); -} - -TEST(ValueMetricProducerTest, TestResetBaseOnPullFailBeforeConditionChange) { - ValueMetric metric = ValueMetricProducerTestHelper::createMetricWithCondition(); - - sp pullerManager = new StrictMock(); - EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _)) - .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, - vector>* data) { - EXPECT_EQ(eventTimeNs, bucketStartTimeNs); - data->clear(); - data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs, 50)); - return false; - })) - .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, - vector>* data) { - EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 1); // Condition change to false. - data->clear(); - data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 8, 100)); - return true; - })); - - sp valueProducer = - ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric, - ConditionState::kFalse); - - // Don't directly set mCondition; the real code never does that. Go through regular code path - // to avoid unexpected behaviors. - // valueProducer->mCondition = ConditionState::kTrue; - valueProducer->onConditionChanged(true, bucketStartTimeNs); - - ASSERT_EQ(0UL, valueProducer->mCurrentSlicedBucket.size()); - - valueProducer->onConditionChanged(false, bucketStartTimeNs + 1); - ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); - ValueMetricProducer::Interval& curInterval = - valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0]; - ValueMetricProducer::BaseInfo curBaseInfo = - valueProducer->mCurrentBaseInfo.begin()->second.baseInfos[0]; - EXPECT_EQ(false, curBaseInfo.hasBase); - EXPECT_EQ(false, curInterval.hasValue); - EXPECT_EQ(false, valueProducer->mHasGlobalBase); -} - -TEST(ValueMetricProducerTest, TestResetBaseOnPullDelayExceeded) { - ValueMetric metric = ValueMetricProducerTestHelper::createMetric(); - metric.set_condition(StringToId("SCREEN_ON")); - metric.set_max_pull_delay_sec(0); - - sp pullerManager = new StrictMock(); - EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, bucketStartTimeNs + 1, _)) - .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t, - vector>* data) { - data->clear(); - data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 1, 120)); - return true; - })); - - sp valueProducer = - ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric, - ConditionState::kFalse); - - // Max delay is set to 0 so pull will exceed max delay. - valueProducer->onConditionChanged(true, bucketStartTimeNs + 1); - ASSERT_EQ(0UL, valueProducer->mCurrentSlicedBucket.size()); -} - -TEST(ValueMetricProducerTest, TestResetBaseOnPullTooLate) { - ValueMetric metric = ValueMetricProducerTestHelper::createMetricWithCondition(); - - sp eventMatcherWizard = - createEventMatcherWizard(tagId, logEventMatcherIndex); - sp wizard = new NaggyMock(); - sp pullerManager = new StrictMock(); - EXPECT_CALL(*pullerManager, RegisterReceiver(tagId, kConfigKey, _, _, _)).WillOnce(Return()); - EXPECT_CALL(*pullerManager, UnRegisterReceiver(tagId, kConfigKey, _)).WillRepeatedly(Return()); - - ValueMetricProducer valueProducer(kConfigKey, metric, 0, {ConditionState::kUnknown}, wizard, - protoHash, logEventMatcherIndex, eventMatcherWizard, tagId, - bucket2StartTimeNs, bucket2StartTimeNs, pullerManager); - valueProducer.prepareFirstBucket(); - valueProducer.mCondition = ConditionState::kFalse; - - // Event should be skipped since it is from previous bucket. - // Pull should not be called. - valueProducer.onConditionChanged(true, bucketStartTimeNs); - ASSERT_EQ(0UL, valueProducer.mCurrentSlicedBucket.size()); -} - -TEST(ValueMetricProducerTest, TestBaseSetOnConditionChange) { - ValueMetric metric = ValueMetricProducerTestHelper::createMetricWithCondition(); - - sp pullerManager = new StrictMock(); - EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, bucketStartTimeNs + 1, _)) - .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t, - vector>* data) { - data->clear(); - data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 1, 100)); - return true; - })); - - sp valueProducer = - ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric, - ConditionState::kFalse); - valueProducer->mHasGlobalBase = false; - - valueProducer->onConditionChanged(true, bucketStartTimeNs + 1); - valueProducer->mHasGlobalBase = true; - ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); - ValueMetricProducer::Interval& curInterval = - valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0]; - ValueMetricProducer::BaseInfo curBaseInfo = - valueProducer->mCurrentBaseInfo.begin()->second.baseInfos[0]; - EXPECT_EQ(true, curBaseInfo.hasBase); - EXPECT_EQ(100, curBaseInfo.base.long_value); - EXPECT_EQ(false, curInterval.hasValue); - EXPECT_EQ(true, valueProducer->mHasGlobalBase); -} - -/* - * Tests that a bucket is marked invalid when a condition change pull fails. - */ -TEST(ValueMetricProducerTest_BucketDrop, TestInvalidBucketWhenOneConditionFailed) { - ValueMetric metric = ValueMetricProducerTestHelper::createMetricWithCondition(); - - sp pullerManager = new StrictMock(); - EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _)) - // First onConditionChanged - .WillOnce(Return(false)) - // Second onConditionChanged - .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, - vector>* data) { - EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 3); - data->clear(); - data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 8, 130)); - return true; - })); - - sp valueProducer = - ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric, - ConditionState::kTrue); - - // Bucket start. - vector> allData; - allData.clear(); - allData.push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 1, 110)); - valueProducer->onDataPulled(allData, /** succeed */ true, bucketStartTimeNs); - - // This will fail and should invalidate the whole bucket since we do not have all the data - // needed to compute the metric value when the screen was on. - valueProducer->onConditionChanged(false, bucketStartTimeNs + 2); - valueProducer->onConditionChanged(true, bucketStartTimeNs + 3); - - // Bucket end. - allData.clear(); - allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs + 1, 140)); - valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs); - - valueProducer->flushIfNeededLocked(bucket2StartTimeNs + 1); - - ASSERT_EQ(0UL, valueProducer->mPastBuckets.size()); - // Contains base from last pull which was successful. - ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); - ValueMetricProducer::Interval& curInterval = - valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0]; - ValueMetricProducer::BaseInfo curBaseInfo = - valueProducer->mCurrentBaseInfo.begin()->second.baseInfos[0]; - EXPECT_EQ(true, curBaseInfo.hasBase); - EXPECT_EQ(140, curBaseInfo.base.long_value); - EXPECT_EQ(false, curInterval.hasValue); - EXPECT_EQ(true, valueProducer->mHasGlobalBase); - - // Check dump report. - ProtoOutputStream output; - std::set strSet; - valueProducer->onDumpReport(bucket2StartTimeNs + 10, false /* include partial bucket */, true, - FAST /* dumpLatency */, &strSet, &output); - - StatsLogReport report = outputStreamToProto(&output); - EXPECT_TRUE(report.has_value_metrics()); - ASSERT_EQ(0, report.value_metrics().data_size()); - ASSERT_EQ(1, report.value_metrics().skipped_size()); - - EXPECT_EQ(NanoToMillis(bucketStartTimeNs), - report.value_metrics().skipped(0).start_bucket_elapsed_millis()); - EXPECT_EQ(NanoToMillis(bucket2StartTimeNs), - report.value_metrics().skipped(0).end_bucket_elapsed_millis()); - ASSERT_EQ(1, report.value_metrics().skipped(0).drop_event_size()); - - auto dropEvent = report.value_metrics().skipped(0).drop_event(0); - EXPECT_EQ(BucketDropReason::PULL_FAILED, dropEvent.drop_reason()); - EXPECT_EQ(NanoToMillis(bucketStartTimeNs + 2), dropEvent.drop_time_millis()); -} - -/* - * Tests that a bucket is marked invalid when the guardrail is hit. - */ -TEST(ValueMetricProducerTest_BucketDrop, TestInvalidBucketWhenGuardRailHit) { - ValueMetric metric = ValueMetricProducerTestHelper::createMetric(); - metric.mutable_dimensions_in_what()->set_field(tagId); - metric.mutable_dimensions_in_what()->add_child()->set_field(1); - metric.set_condition(StringToId("SCREEN_ON")); - - sp pullerManager = new StrictMock(); - EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, bucketStartTimeNs + 2, _)) - // First onConditionChanged - .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t, - vector>* data) { - for (int i = 0; i < 2000; i++) { - data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 1, i)); - } - return true; - })); - - sp valueProducer = - ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric, - ConditionState::kFalse); - - valueProducer->onConditionChanged(true, bucketStartTimeNs + 2); - EXPECT_EQ(true, valueProducer->mCurrentBucketIsSkipped); - ASSERT_EQ(0UL, valueProducer->mCurrentSlicedBucket.size()); - ASSERT_EQ(0UL, valueProducer->mSkippedBuckets.size()); - - // Bucket 2 start. - vector> allData; - allData.clear(); - allData.push_back(CreateTwoValueLogEvent(tagId, bucket2StartTimeNs + 1, 1, 10)); - valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs); - - // First bucket added to mSkippedBuckets after flush. - ASSERT_EQ(1UL, valueProducer->mSkippedBuckets.size()); - - // Check dump report. - ProtoOutputStream output; - std::set strSet; - valueProducer->onDumpReport(bucket2StartTimeNs + 10000, false /* include recent buckets */, - true, FAST /* dumpLatency */, &strSet, &output); - - StatsLogReport report = outputStreamToProto(&output); - EXPECT_TRUE(report.has_value_metrics()); - ASSERT_EQ(0, report.value_metrics().data_size()); - ASSERT_EQ(1, report.value_metrics().skipped_size()); - - EXPECT_EQ(NanoToMillis(bucketStartTimeNs), - report.value_metrics().skipped(0).start_bucket_elapsed_millis()); - EXPECT_EQ(NanoToMillis(bucket2StartTimeNs), - report.value_metrics().skipped(0).end_bucket_elapsed_millis()); - ASSERT_EQ(1, report.value_metrics().skipped(0).drop_event_size()); - - auto dropEvent = report.value_metrics().skipped(0).drop_event(0); - EXPECT_EQ(BucketDropReason::DIMENSION_GUARDRAIL_REACHED, dropEvent.drop_reason()); - EXPECT_EQ(NanoToMillis(bucketStartTimeNs + 2), dropEvent.drop_time_millis()); -} - -/* - * Tests that a bucket is marked invalid when the bucket's initial pull fails. - */ -TEST(ValueMetricProducerTest_BucketDrop, TestInvalidBucketWhenInitialPullFailed) { - ValueMetric metric = ValueMetricProducerTestHelper::createMetricWithCondition(); - - sp pullerManager = new StrictMock(); - EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _)) - // First onConditionChanged - .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, - vector>* data) { - EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 2); - data->clear(); - data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 8, 120)); - return true; - })) - // Second onConditionChanged - .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, - vector>* data) { - EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 3); - data->clear(); - data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 8, 130)); - return true; - })); - - sp valueProducer = - ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric, - ConditionState::kTrue); - - // Bucket start. - vector> allData; - allData.clear(); - allData.push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 1, 110)); - valueProducer->onDataPulled(allData, /** succeed */ false, bucketStartTimeNs); - - valueProducer->onConditionChanged(false, bucketStartTimeNs + 2); - valueProducer->onConditionChanged(true, bucketStartTimeNs + 3); - - // Bucket end. - allData.clear(); - allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs + 1, 140)); - valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs); - - valueProducer->flushIfNeededLocked(bucket2StartTimeNs + 1); - - ASSERT_EQ(0UL, valueProducer->mPastBuckets.size()); - // Contains base from last pull which was successful. - ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); - ValueMetricProducer::Interval& curInterval = - valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0]; - ValueMetricProducer::BaseInfo curBaseInfo = - valueProducer->mCurrentBaseInfo.begin()->second.baseInfos[0]; - EXPECT_EQ(true, curBaseInfo.hasBase); - EXPECT_EQ(140, curBaseInfo.base.long_value); - EXPECT_EQ(false, curInterval.hasValue); - EXPECT_EQ(true, valueProducer->mHasGlobalBase); - - // Check dump report. - ProtoOutputStream output; - std::set strSet; - valueProducer->onDumpReport(bucket2StartTimeNs + 10000, false /* include recent buckets */, - true, FAST /* dumpLatency */, &strSet, &output); - - StatsLogReport report = outputStreamToProto(&output); - EXPECT_TRUE(report.has_value_metrics()); - ASSERT_EQ(0, report.value_metrics().data_size()); - ASSERT_EQ(1, report.value_metrics().skipped_size()); - - EXPECT_EQ(NanoToMillis(bucketStartTimeNs), - report.value_metrics().skipped(0).start_bucket_elapsed_millis()); - EXPECT_EQ(NanoToMillis(bucket2StartTimeNs), - report.value_metrics().skipped(0).end_bucket_elapsed_millis()); - ASSERT_EQ(1, report.value_metrics().skipped(0).drop_event_size()); - - auto dropEvent = report.value_metrics().skipped(0).drop_event(0); - EXPECT_EQ(BucketDropReason::PULL_FAILED, dropEvent.drop_reason()); - EXPECT_EQ(NanoToMillis(bucketStartTimeNs + 2), dropEvent.drop_time_millis()); -} - -/* - * Tests that a bucket is marked invalid when the bucket's final pull fails - * (i.e. failed pull on bucket boundary). - */ -TEST(ValueMetricProducerTest_BucketDrop, TestInvalidBucketWhenLastPullFailed) { - ValueMetric metric = ValueMetricProducerTestHelper::createMetricWithCondition(); - - sp pullerManager = new StrictMock(); - EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _)) - // First onConditionChanged - .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, - vector>* data) { - EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 2); - data->clear(); - data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 8, 120)); - return true; - })) - // Second onConditionChanged - .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, - vector>* data) { - EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 3); - data->clear(); - data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 8, 130)); - return true; - })); - - sp valueProducer = - ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric, - ConditionState::kTrue); - - // Bucket start. - vector> allData; - allData.clear(); - allData.push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 1, 110)); - valueProducer->onDataPulled(allData, /** succeed */ true, bucketStartTimeNs); - - valueProducer->onConditionChanged(false, bucketStartTimeNs + 2); - valueProducer->onConditionChanged(true, bucketStartTimeNs + 3); - - // Bucket end. - allData.clear(); - allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs + 1, 140)); - valueProducer->onDataPulled(allData, /** succeed */ false, bucket2StartTimeNs); - - valueProducer->flushIfNeededLocked(bucket2StartTimeNs + 1); - - ASSERT_EQ(0UL, valueProducer->mPastBuckets.size()); - // Last pull failed so base has been reset. - ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); - ValueMetricProducer::Interval& curInterval = - valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0]; - ValueMetricProducer::BaseInfo curBaseInfo = - valueProducer->mCurrentBaseInfo.begin()->second.baseInfos[0]; - EXPECT_EQ(false, curBaseInfo.hasBase); - EXPECT_EQ(false, curInterval.hasValue); - EXPECT_EQ(false, valueProducer->mHasGlobalBase); - - // Check dump report. - ProtoOutputStream output; - std::set strSet; - valueProducer->onDumpReport(bucket2StartTimeNs + 10000, false /* include recent buckets */, - true, FAST /* dumpLatency */, &strSet, &output); - - StatsLogReport report = outputStreamToProto(&output); - EXPECT_TRUE(report.has_value_metrics()); - ASSERT_EQ(0, report.value_metrics().data_size()); - ASSERT_EQ(1, report.value_metrics().skipped_size()); - - EXPECT_EQ(NanoToMillis(bucketStartTimeNs), - report.value_metrics().skipped(0).start_bucket_elapsed_millis()); - EXPECT_EQ(NanoToMillis(bucket2StartTimeNs), - report.value_metrics().skipped(0).end_bucket_elapsed_millis()); - ASSERT_EQ(1, report.value_metrics().skipped(0).drop_event_size()); - - auto dropEvent = report.value_metrics().skipped(0).drop_event(0); - EXPECT_EQ(BucketDropReason::PULL_FAILED, dropEvent.drop_reason()); - EXPECT_EQ(NanoToMillis(bucket2StartTimeNs), dropEvent.drop_time_millis()); -} - -TEST(ValueMetricProducerTest, TestEmptyDataResetsBase_onDataPulled) { - ValueMetric metric = ValueMetricProducerTestHelper::createMetric(); - sp pullerManager = new StrictMock(); - EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, bucketStartTimeNs, _)) - // Start bucket. - .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t, - vector>* data) { - data->clear(); - data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs, 3)); - return true; - })); - - sp valueProducer = - ValueMetricProducerTestHelper::createValueProducerNoConditions(pullerManager, metric); - - // Bucket 2 start. - vector> allData; - allData.clear(); - allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs + 1, 110)); - valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs); - ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); - ASSERT_EQ(1UL, valueProducer->mPastBuckets.size()); - - // Bucket 3 empty. - allData.clear(); - allData.push_back(CreateNoValuesLogEvent(tagId, bucket3StartTimeNs + 1)); - valueProducer->onDataPulled(allData, /** succeed */ true, bucket3StartTimeNs); - // Data has been trimmed. - ASSERT_EQ(0UL, valueProducer->mCurrentSlicedBucket.size()); - ASSERT_EQ(1UL, valueProducer->mPastBuckets.size()); -} - -TEST(ValueMetricProducerTest, TestEmptyDataResetsBase_onConditionChanged) { - ValueMetric metric = ValueMetricProducerTestHelper::createMetricWithCondition(); - - sp pullerManager = new StrictMock(); - EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _)) - // First onConditionChanged - .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, - vector>* data) { - EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 10); - data->clear(); - data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs, 3)); - return true; - })) - .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, - vector>* data) { - EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 10); - data->clear(); - return true; - })); - - sp valueProducer = - ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric, - ConditionState::kFalse); - - valueProducer->onConditionChanged(true, bucketStartTimeNs + 10); - ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); - ValueMetricProducer::Interval& curInterval = - valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0]; - ValueMetricProducer::BaseInfo curBaseInfo = - valueProducer->mCurrentBaseInfo.begin()->second.baseInfos[0]; - EXPECT_EQ(true, curBaseInfo.hasBase); - EXPECT_EQ(false, curInterval.hasValue); - EXPECT_EQ(true, valueProducer->mHasGlobalBase); - - // Empty pull. - valueProducer->onConditionChanged(false, bucketStartTimeNs + 10); - ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); - curInterval = valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0]; - curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second.baseInfos[0]; - EXPECT_EQ(false, curBaseInfo.hasBase); - EXPECT_EQ(false, curInterval.hasValue); - EXPECT_EQ(false, valueProducer->mHasGlobalBase); -} - -TEST(ValueMetricProducerTest, TestEmptyDataResetsBase_onBucketBoundary) { - ValueMetric metric = ValueMetricProducerTestHelper::createMetricWithCondition(); - - sp pullerManager = new StrictMock(); - EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _)) - // First onConditionChanged - .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, - vector>* data) { - EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 10); - data->clear(); - data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs, 1)); - return true; - })) - .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, - vector>* data) { - EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 11); - data->clear(); - data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs, 2)); - return true; - })) - .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, - vector>* data) { - EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 12); - data->clear(); - data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs, 5)); - return true; - })); - - sp valueProducer = - ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric, - ConditionState::kFalse); - - valueProducer->onConditionChanged(true, bucketStartTimeNs + 10); - valueProducer->onConditionChanged(false, bucketStartTimeNs + 11); - valueProducer->onConditionChanged(true, bucketStartTimeNs + 12); - ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); - ValueMetricProducer::Interval& curInterval = - valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0]; - ValueMetricProducer::BaseInfo curBaseInfo = - valueProducer->mCurrentBaseInfo.begin()->second.baseInfos[0]; - EXPECT_EQ(true, curBaseInfo.hasBase); - EXPECT_EQ(true, curInterval.hasValue); - EXPECT_EQ(true, valueProducer->mHasGlobalBase); - - // End of bucket - vector> allData; - allData.clear(); - valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs); - ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); - curInterval = valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0]; - curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second.baseInfos[0]; - // Data is empty, base should be reset. - EXPECT_EQ(false, curBaseInfo.hasBase); - EXPECT_EQ(5, curBaseInfo.base.long_value); - EXPECT_EQ(false, curInterval.hasValue); - EXPECT_EQ(true, valueProducer->mHasGlobalBase); - - ASSERT_EQ(1UL, valueProducer->mPastBuckets.size()); - assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {1}, {bucketSizeNs - 12 + 1}, - {bucketStartTimeNs}, {bucket2StartTimeNs}); -} - -TEST(ValueMetricProducerTest, TestPartialResetOnBucketBoundaries) { - ValueMetric metric = ValueMetricProducerTestHelper::createMetric(); - metric.mutable_dimensions_in_what()->set_field(tagId); - metric.mutable_dimensions_in_what()->add_child()->set_field(1); - metric.set_condition(StringToId("SCREEN_ON")); - - sp pullerManager = new StrictMock(); - EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, bucketStartTimeNs + 10, _)) - // First onConditionChanged - .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t, - vector>* data) { - data->clear(); - data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs, 1)); - return true; - })); - - sp valueProducer = - ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric, - ConditionState::kFalse); - - valueProducer->onConditionChanged(true, bucketStartTimeNs + 10); - ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); - - // End of bucket - vector> allData; - allData.clear(); - allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs + 1, 2)); - valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs); - - // Key 1 should be reset since in not present in the most pull. - ASSERT_EQ(2UL, valueProducer->mCurrentSlicedBucket.size()); - auto iterator = valueProducer->mCurrentSlicedBucket.begin(); - auto baseInfoIter = valueProducer->mCurrentBaseInfo.begin(); - EXPECT_EQ(true, baseInfoIter->second.baseInfos[0].hasBase); - EXPECT_EQ(2, baseInfoIter->second.baseInfos[0].base.long_value); - EXPECT_EQ(false, iterator->second.intervals[0].hasValue); - iterator++; - baseInfoIter++; - EXPECT_EQ(false, baseInfoIter->second.baseInfos[0].hasBase); - EXPECT_EQ(1, baseInfoIter->second.baseInfos[0].base.long_value); - EXPECT_EQ(false, iterator->second.intervals[0].hasValue); - - EXPECT_EQ(true, valueProducer->mHasGlobalBase); -} - -TEST_P(ValueMetricProducerTest_PartialBucket, TestFullBucketResetWhenLastBucketInvalid) { - ValueMetric metric = ValueMetricProducerTestHelper::createMetric(); - - sp pullerManager = new StrictMock(); - int64_t partialBucketSplitTimeNs = bucketStartTimeNs + bucketSizeNs / 2; - EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _)) - // Initialization. - .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, - vector>* data) { - EXPECT_EQ(eventTimeNs, bucketStartTimeNs); - data->clear(); - data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs, 1)); - return true; - })) - // notifyAppUpgrade. - .WillOnce(Invoke([partialBucketSplitTimeNs](int tagId, const ConfigKey&, - const int64_t eventTimeNs, - vector>* data) { - EXPECT_EQ(eventTimeNs, partialBucketSplitTimeNs); - data->clear(); - data->push_back(CreateRepeatedValueLogEvent(tagId, partialBucketSplitTimeNs, 10)); - return true; - })); - sp valueProducer = - ValueMetricProducerTestHelper::createValueProducerNoConditions(pullerManager, metric); - ASSERT_EQ(0UL, valueProducer->mCurrentFullBucket.size()); - - switch (GetParam()) { - case APP_UPGRADE: - valueProducer->notifyAppUpgrade(partialBucketSplitTimeNs); - break; - case BOOT_COMPLETE: - valueProducer->onStatsdInitCompleted(partialBucketSplitTimeNs); - break; - } - EXPECT_EQ(partialBucketSplitTimeNs, valueProducer->mCurrentBucketStartTimeNs); - EXPECT_EQ(0, valueProducer->getCurrentBucketNum()); - assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {9}, - {partialBucketSplitTimeNs - bucketStartTimeNs}, - {bucketStartTimeNs}, {partialBucketSplitTimeNs}); - ASSERT_EQ(1UL, valueProducer->mCurrentFullBucket.size()); - - vector> allData; - allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket3StartTimeNs + 1, 4)); - // Pull fails and arrives late. - valueProducer->onDataPulled(allData, /** fails */ false, bucket3StartTimeNs + 1); - assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {9}, - {partialBucketSplitTimeNs - bucketStartTimeNs}, - {bucketStartTimeNs}, {partialBucketSplitTimeNs}); - ASSERT_EQ(1, valueProducer->mSkippedBuckets.size()); - ASSERT_EQ(2, valueProducer->mSkippedBuckets[0].dropEvents.size()); - EXPECT_EQ(PULL_FAILED, valueProducer->mSkippedBuckets[0].dropEvents[0].reason); - EXPECT_EQ(MULTIPLE_BUCKETS_SKIPPED, valueProducer->mSkippedBuckets[0].dropEvents[1].reason); - EXPECT_EQ(partialBucketSplitTimeNs, valueProducer->mSkippedBuckets[0].bucketStartTimeNs); - EXPECT_EQ(bucket3StartTimeNs, valueProducer->mSkippedBuckets[0].bucketEndTimeNs); - ASSERT_EQ(0UL, valueProducer->mCurrentFullBucket.size()); -} - -TEST(ValueMetricProducerTest, TestBucketBoundariesOnConditionChange) { - ValueMetric metric = ValueMetricProducerTestHelper::createMetricWithCondition(); - sp pullerManager = new StrictMock(); - EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _)) - // Second onConditionChanged. - .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, - vector>* data) { - EXPECT_EQ(eventTimeNs, bucket2StartTimeNs + 10); - data->clear(); - data->push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs + 10, 5)); - return true; - })) - // Third onConditionChanged. - .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, - vector>* data) { - EXPECT_EQ(eventTimeNs, bucket3StartTimeNs + 10); - data->clear(); - data->push_back(CreateRepeatedValueLogEvent(tagId, bucket3StartTimeNs + 10, 7)); - return true; - })); - - sp valueProducer = - ValueMetricProducerTestHelper::createValueProducerWithCondition( - pullerManager, metric, ConditionState::kUnknown); - - valueProducer->onConditionChanged(false, bucketStartTimeNs); - ASSERT_EQ(0UL, valueProducer->mCurrentSlicedBucket.size()); - - // End of first bucket - vector> allData; - allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs + 1, 4)); - valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs + 1); - ASSERT_EQ(0UL, valueProducer->mCurrentSlicedBucket.size()); - - valueProducer->onConditionChanged(true, bucket2StartTimeNs + 10); - ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); - auto curInterval = valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0]; - auto curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second.baseInfos[0]; - EXPECT_EQ(true, curBaseInfo.hasBase); - EXPECT_EQ(5, curBaseInfo.base.long_value); - EXPECT_EQ(false, curInterval.hasValue); - - valueProducer->onConditionChanged(false, bucket3StartTimeNs + 10); - - // Bucket should have been completed. - assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {2}, {bucketSizeNs - 10}, - {bucket2StartTimeNs}, {bucket3StartTimeNs}); -} - -TEST(ValueMetricProducerTest, TestLateOnDataPulledWithoutDiff) { - ValueMetric metric = ValueMetricProducerTestHelper::createMetric(); - metric.set_use_diff(false); - - sp pullerManager = new StrictMock(); - sp valueProducer = - ValueMetricProducerTestHelper::createValueProducerNoConditions(pullerManager, metric); - - vector> allData; - allData.push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 30, 10)); - valueProducer->onDataPulled(allData, /** succeed */ true, bucketStartTimeNs + 30); - - allData.clear(); - allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs, 20)); - valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs); - - // Bucket should have been completed. - assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {30}, {bucketSizeNs}, - {bucketStartTimeNs}, {bucket2StartTimeNs}); -} - -TEST(ValueMetricProducerTest, TestLateOnDataPulledWithDiff) { - ValueMetric metric = ValueMetricProducerTestHelper::createMetric(); - - sp pullerManager = new StrictMock(); - EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, bucketStartTimeNs, _)) - // Initialization. - .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t, - vector>* data) { - data->clear(); - data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs, 1)); - return true; - })); - - sp valueProducer = - ValueMetricProducerTestHelper::createValueProducerNoConditions(pullerManager, metric); - - vector> allData; - allData.push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 30, 10)); - valueProducer->onDataPulled(allData, /** succeed */ true, bucketStartTimeNs + 30); - - allData.clear(); - allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs, 20)); - valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs); - - // Bucket should have been completed. - assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {19}, {bucketSizeNs}, - {bucketStartTimeNs}, {bucket2StartTimeNs}); -} - -TEST_P(ValueMetricProducerTest_PartialBucket, TestBucketBoundariesOnPartialBucket) { - ValueMetric metric = ValueMetricProducerTestHelper::createMetric(); - - int64_t partialBucketSplitTimeNs = bucket2StartTimeNs + 2; - sp pullerManager = new StrictMock(); - EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _)) - // Initialization. - .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, - vector>* data) { - EXPECT_EQ(eventTimeNs, bucketStartTimeNs); - data->clear(); - data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs, 1)); - return true; - })) - // notifyAppUpgrade. - .WillOnce(Invoke([partialBucketSplitTimeNs](int tagId, const ConfigKey&, - const int64_t eventTimeNs, - vector>* data) { - EXPECT_EQ(eventTimeNs, partialBucketSplitTimeNs); - data->clear(); - data->push_back(CreateRepeatedValueLogEvent(tagId, partialBucketSplitTimeNs, 10)); - return true; - })); - - sp valueProducer = - ValueMetricProducerTestHelper::createValueProducerNoConditions(pullerManager, metric); - - switch (GetParam()) { - case APP_UPGRADE: - valueProducer->notifyAppUpgrade(partialBucketSplitTimeNs); - break; - case BOOT_COMPLETE: - valueProducer->onStatsdInitCompleted(partialBucketSplitTimeNs); - break; - } - - // Bucket should have been completed. - assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {9}, {bucketSizeNs}, - {bucketStartTimeNs}, {bucket2StartTimeNs}); -} - -TEST(ValueMetricProducerTest, TestDataIsNotUpdatedWhenNoConditionChanged) { - ValueMetric metric = ValueMetricProducerTestHelper::createMetricWithCondition(); - - sp pullerManager = new StrictMock(); - EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _)) - // First on condition changed. - .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, - vector>* data) { - EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 8); - data->clear(); - data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs, 1)); - return true; - })) - // Second on condition changed. - .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, - vector>* data) { - EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 10); - data->clear(); - data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs, 3)); - return true; - })); - - sp valueProducer = - ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric, - ConditionState::kFalse); - - valueProducer->onConditionChanged(true, bucketStartTimeNs + 8); - valueProducer->onConditionChanged(false, bucketStartTimeNs + 10); - valueProducer->onConditionChanged(false, bucketStartTimeNs + 12); - - ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); - auto curInterval = valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0]; - auto curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second.baseInfos[0]; - EXPECT_EQ(true, curInterval.hasValue); - EXPECT_EQ(2, curInterval.value.long_value); - - vector> allData; - allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs + 1, 10)); - valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs + 1); - - assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {2}, {2}, {bucketStartTimeNs}, - {bucket2StartTimeNs}); -} - -// TODO: b/145705635 fix or delete this test -TEST(ValueMetricProducerTest, TestBucketInvalidIfGlobalBaseIsNotSet) { - ValueMetric metric = ValueMetricProducerTestHelper::createMetricWithCondition(); - - sp pullerManager = new StrictMock(); - EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _)) - // First condition change. - .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, - vector>* data) { - EXPECT_EQ(eventTimeNs, bucket2StartTimeNs + 10); - data->clear(); - data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs, 1)); - return true; - })) - // 2nd condition change. - .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, - vector>* data) { - EXPECT_EQ(eventTimeNs, bucket2StartTimeNs + 8); - data->clear(); - data->push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs, 1)); - return true; - })) - // 3rd condition change. - .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, - vector>* data) { - EXPECT_EQ(eventTimeNs, bucket2StartTimeNs + 10); - data->clear(); - data->push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs, 1)); - return true; - })); - - sp valueProducer = - ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric, - ConditionState::kFalse); - valueProducer->onConditionChanged(true, bucket2StartTimeNs + 10); - - vector> allData; - allData.push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 3, 10)); - valueProducer->onDataPulled(allData, /** succeed */ false, bucketStartTimeNs + 3); - - allData.clear(); - allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs, 20)); - valueProducer->onDataPulled(allData, /** succeed */ false, bucket2StartTimeNs); - - valueProducer->onConditionChanged(false, bucket2StartTimeNs + 8); - valueProducer->onConditionChanged(true, bucket2StartTimeNs + 10); - - allData.clear(); - allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket3StartTimeNs, 30)); - valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs); - - // There was not global base available so all buckets are invalid. - assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {}, {}, {}, {}); -} - -TEST(ValueMetricProducerTest, TestPullNeededFastDump) { - ValueMetric metric = ValueMetricProducerTestHelper::createMetric(); - - sp eventMatcherWizard = - createEventMatcherWizard(tagId, logEventMatcherIndex); - sp wizard = new NaggyMock(); - sp pullerManager = new StrictMock(); - EXPECT_CALL(*pullerManager, RegisterReceiver(tagId, kConfigKey, _, _, _)).WillOnce(Return()); - EXPECT_CALL(*pullerManager, UnRegisterReceiver(tagId, kConfigKey, _)).WillRepeatedly(Return()); - - EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, bucketStartTimeNs, _)) - // Initial pull. - .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t, - vector>* data) { - data->clear(); - data->push_back(CreateThreeValueLogEvent(tagId, bucketStartTimeNs, tagId, 1, 1)); - return true; - })); - - ValueMetricProducer valueProducer(kConfigKey, metric, -1, {}, wizard, protoHash, - logEventMatcherIndex, eventMatcherWizard, tagId, - bucketStartTimeNs, bucketStartTimeNs, pullerManager); - valueProducer.prepareFirstBucket(); - - ProtoOutputStream output; - std::set strSet; - valueProducer.onDumpReport(bucketStartTimeNs + 10, true /* include recent buckets */, true, - FAST, &strSet, &output); - - StatsLogReport report = outputStreamToProto(&output); - // Bucket is invalid since we did not pull when dump report was called. - ASSERT_EQ(0, report.value_metrics().data_size()); -} - -TEST(ValueMetricProducerTest, TestFastDumpWithoutCurrentBucket) { - ValueMetric metric = ValueMetricProducerTestHelper::createMetric(); - - sp eventMatcherWizard = - createEventMatcherWizard(tagId, logEventMatcherIndex); - sp wizard = new NaggyMock(); - sp pullerManager = new StrictMock(); - EXPECT_CALL(*pullerManager, RegisterReceiver(tagId, kConfigKey, _, _, _)).WillOnce(Return()); - EXPECT_CALL(*pullerManager, UnRegisterReceiver(tagId, kConfigKey, _)).WillRepeatedly(Return()); - - EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, bucketStartTimeNs, _)) - // Initial pull. - .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t, - vector>* data) { - data->clear(); - data->push_back(CreateThreeValueLogEvent(tagId, bucketStartTimeNs, tagId, 1, 1)); - return true; - })); - - ValueMetricProducer valueProducer(kConfigKey, metric, -1, {}, wizard, protoHash, - logEventMatcherIndex, eventMatcherWizard, tagId, - bucketStartTimeNs, bucketStartTimeNs, pullerManager); - valueProducer.prepareFirstBucket(); - - vector> allData; - allData.clear(); - allData.push_back(CreateThreeValueLogEvent(tagId, bucket2StartTimeNs + 1, tagId, 2, 2)); - valueProducer.onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs); - - ProtoOutputStream output; - std::set strSet; - valueProducer.onDumpReport(bucket4StartTimeNs, false /* include recent buckets */, true, FAST, - &strSet, &output); - - StatsLogReport report = outputStreamToProto(&output); - // Previous bucket is part of the report. - ASSERT_EQ(1, report.value_metrics().data_size()); - EXPECT_EQ(0, report.value_metrics().data(0).bucket_info(0).bucket_num()); -} - -TEST(ValueMetricProducerTest, TestPullNeededNoTimeConstraints) { - ValueMetric metric = ValueMetricProducerTestHelper::createMetric(); - - sp eventMatcherWizard = - createEventMatcherWizard(tagId, logEventMatcherIndex); - sp wizard = new NaggyMock(); - sp pullerManager = new StrictMock(); - EXPECT_CALL(*pullerManager, RegisterReceiver(tagId, kConfigKey, _, _, _)).WillOnce(Return()); - EXPECT_CALL(*pullerManager, UnRegisterReceiver(tagId, kConfigKey, _)).WillRepeatedly(Return()); - - EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _)) - // Initial pull. - .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, - vector>* data) { - EXPECT_EQ(eventTimeNs, bucketStartTimeNs); - data->clear(); - data->push_back(CreateThreeValueLogEvent(tagId, bucketStartTimeNs, tagId, 1, 1)); - return true; - })) - .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, - vector>* data) { - EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 10); - data->clear(); - data->push_back( - CreateThreeValueLogEvent(tagId, bucketStartTimeNs + 10, tagId, 3, 3)); - return true; - })); - - ValueMetricProducer valueProducer(kConfigKey, metric, -1, {}, wizard, protoHash, - logEventMatcherIndex, eventMatcherWizard, tagId, - bucketStartTimeNs, bucketStartTimeNs, pullerManager); - valueProducer.prepareFirstBucket(); - - ProtoOutputStream output; - std::set strSet; - valueProducer.onDumpReport(bucketStartTimeNs + 10, true /* include recent buckets */, true, - NO_TIME_CONSTRAINTS, &strSet, &output); - - StatsLogReport report = outputStreamToProto(&output); - ASSERT_EQ(1, report.value_metrics().data_size()); - ASSERT_EQ(1, report.value_metrics().data(0).bucket_info_size()); - EXPECT_EQ(2, report.value_metrics().data(0).bucket_info(0).values(0).value_long()); -} - -TEST(ValueMetricProducerTest, TestPulledData_noDiff_withoutCondition) { - ValueMetric metric = ValueMetricProducerTestHelper::createMetric(); - metric.set_use_diff(false); - - sp pullerManager = new StrictMock(); - sp valueProducer = - ValueMetricProducerTestHelper::createValueProducerNoConditions(pullerManager, metric); - - vector> allData; - allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs + 30, 10)); - valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs + 30); - - // Bucket should have been completed. - assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {10}, {bucketSizeNs}, - {bucketStartTimeNs}, {bucket2StartTimeNs}); -} - -TEST(ValueMetricProducerTest, TestPulledData_noDiff_withMultipleConditionChanges) { - ValueMetric metric = ValueMetricProducerTestHelper::createMetricWithCondition(); - metric.set_use_diff(false); - - sp pullerManager = new StrictMock(); - EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _)) - // condition becomes true - .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, - vector>* data) { - EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 8); - data->clear(); - data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 30, 10)); - return true; - })) - // condition becomes false - .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, - vector>* data) { - EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 50); - data->clear(); - data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 50, 20)); - return true; - })); - sp valueProducer = - ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric, - ConditionState::kFalse); - - valueProducer->onConditionChanged(true, bucketStartTimeNs + 8); - valueProducer->onConditionChanged(false, bucketStartTimeNs + 50); - // has one slice - ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); - ValueMetricProducer::Interval curInterval = - valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0]; - ValueMetricProducer::BaseInfo curBaseInfo = - valueProducer->mCurrentBaseInfo.begin()->second.baseInfos[0]; - EXPECT_EQ(false, curBaseInfo.hasBase); - EXPECT_EQ(true, curInterval.hasValue); - EXPECT_EQ(20, curInterval.value.long_value); - - // Now the alarm is delivered. Condition is off though. - vector> allData; - allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs + 30, 110)); - valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs); - - assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {20}, {50 - 8}, - {bucketStartTimeNs}, {bucket2StartTimeNs}); - curInterval = valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0]; - curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second.baseInfos[0]; - EXPECT_EQ(false, curBaseInfo.hasBase); - EXPECT_EQ(false, curInterval.hasValue); -} - -TEST(ValueMetricProducerTest, TestPulledData_noDiff_bucketBoundaryTrue) { - ValueMetric metric = ValueMetricProducerTestHelper::createMetricWithCondition(); - metric.set_use_diff(false); - - sp pullerManager = new StrictMock(); - EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, bucketStartTimeNs + 8, _)) - // condition becomes true - .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t, - vector>* data) { - data->clear(); - data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 30, 10)); - return true; - })); - sp valueProducer = - ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric, - ConditionState::kFalse); - - valueProducer->onConditionChanged(true, bucketStartTimeNs + 8); - - // Now the alarm is delivered. Condition is off though. - vector> allData; - allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs + 30, 30)); - valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs); - - assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {30}, {bucketSizeNs - 8}, - {bucketStartTimeNs}, {bucket2StartTimeNs}); - ValueMetricProducer::Interval curInterval = - valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0]; - ValueMetricProducer::BaseInfo curBaseInfo = - valueProducer->mCurrentBaseInfo.begin()->second.baseInfos[0]; - EXPECT_EQ(false, curBaseInfo.hasBase); - EXPECT_EQ(false, curInterval.hasValue); -} - -TEST(ValueMetricProducerTest, TestPulledData_noDiff_bucketBoundaryFalse) { - ValueMetric metric = ValueMetricProducerTestHelper::createMetricWithCondition(); - metric.set_use_diff(false); - - sp pullerManager = new StrictMock(); - sp valueProducer = - ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric, - ConditionState::kFalse); - - // Now the alarm is delivered. Condition is off though. - vector> allData; - allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs + 30, 30)); - valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs); - - // Condition was always false. - assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {}, {}, {}, {}); -} - -TEST(ValueMetricProducerTest, TestPulledData_noDiff_withFailure) { - ValueMetric metric = ValueMetricProducerTestHelper::createMetricWithCondition(); - metric.set_use_diff(false); - - sp pullerManager = new StrictMock(); - EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _)) - // condition becomes true - .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, - vector>* data) { - EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 8); - data->clear(); - data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 30, 10)); - return true; - })) - .WillOnce(Return(false)); - sp valueProducer = - ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric, - ConditionState::kFalse); - - valueProducer->onConditionChanged(true, bucketStartTimeNs + 8); - valueProducer->onConditionChanged(false, bucketStartTimeNs + 50); - - // Now the alarm is delivered. Condition is off though. - vector> allData; - allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs + 30, 30)); - valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs); - - // No buckets, we had a failure. - assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {}, {}, {}, {}); -} - -/* - * Test that DUMP_REPORT_REQUESTED dump reason is logged. - * - * For the bucket to be marked invalid during a dump report requested, - * three things must be true: - * - we want to include the current partial bucket - * - we need a pull (metric is pulled and condition is true) - * - the dump latency must be FAST - */ - -TEST(ValueMetricProducerTest_BucketDrop, TestInvalidBucketWhenDumpReportRequested) { - ValueMetric metric = ValueMetricProducerTestHelper::createMetricWithCondition(); - - sp pullerManager = new StrictMock(); - EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, bucketStartTimeNs + 20, _)) - // Condition change to true. - .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t, - vector>* data) { - data->clear(); - data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 20, 10)); - return true; - })); - - sp valueProducer = - ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric, - ConditionState::kFalse); - - // Condition change event. - valueProducer->onConditionChanged(true, bucketStartTimeNs + 20); - - // Check dump report. - ProtoOutputStream output; - std::set strSet; - valueProducer->onDumpReport(bucketStartTimeNs + 40, true /* include recent buckets */, true, - FAST /* dumpLatency */, &strSet, &output); - - StatsLogReport report = outputStreamToProto(&output); - EXPECT_TRUE(report.has_value_metrics()); - ASSERT_EQ(0, report.value_metrics().data_size()); - ASSERT_EQ(1, report.value_metrics().skipped_size()); - - EXPECT_EQ(NanoToMillis(bucketStartTimeNs), - report.value_metrics().skipped(0).start_bucket_elapsed_millis()); - EXPECT_EQ(NanoToMillis(bucketStartTimeNs + 40), - report.value_metrics().skipped(0).end_bucket_elapsed_millis()); - ASSERT_EQ(1, report.value_metrics().skipped(0).drop_event_size()); - - auto dropEvent = report.value_metrics().skipped(0).drop_event(0); - EXPECT_EQ(BucketDropReason::DUMP_REPORT_REQUESTED, dropEvent.drop_reason()); - EXPECT_EQ(NanoToMillis(bucketStartTimeNs + 40), dropEvent.drop_time_millis()); -} - -/* - * Test that EVENT_IN_WRONG_BUCKET dump reason is logged for a late condition - * change event (i.e. the condition change occurs in the wrong bucket). - */ -TEST(ValueMetricProducerTest_BucketDrop, TestInvalidBucketWhenConditionEventWrongBucket) { - ValueMetric metric = ValueMetricProducerTestHelper::createMetricWithCondition(); - - sp pullerManager = new StrictMock(); - EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, bucketStartTimeNs + 50, _)) - // Condition change to true. - .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t, - vector>* data) { - data->clear(); - data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 50, 10)); - return true; - })); - - sp valueProducer = - ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric, - ConditionState::kFalse); - - // Condition change event. - valueProducer->onConditionChanged(true, bucketStartTimeNs + 50); - - // Bucket boundary pull. - vector> allData; - allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs, 15)); - valueProducer->onDataPulled(allData, /** succeeds */ true, bucket2StartTimeNs + 1); - - // Late condition change event. - valueProducer->onConditionChanged(false, bucket2StartTimeNs - 100); - - // Check dump report. - ProtoOutputStream output; - std::set strSet; - valueProducer->onDumpReport(bucket2StartTimeNs + 100, true /* include recent buckets */, true, - NO_TIME_CONSTRAINTS /* dumpLatency */, &strSet, &output); - - StatsLogReport report = outputStreamToProto(&output); - EXPECT_TRUE(report.has_value_metrics()); - ASSERT_EQ(1, report.value_metrics().data_size()); - ASSERT_EQ(1, report.value_metrics().skipped_size()); - - EXPECT_EQ(NanoToMillis(bucket2StartTimeNs), - report.value_metrics().skipped(0).start_bucket_elapsed_millis()); - EXPECT_EQ(NanoToMillis(bucket2StartTimeNs + 100), - report.value_metrics().skipped(0).end_bucket_elapsed_millis()); - ASSERT_EQ(2, report.value_metrics().skipped(0).drop_event_size()); - - auto dropEvent = report.value_metrics().skipped(0).drop_event(0); - EXPECT_EQ(BucketDropReason::EVENT_IN_WRONG_BUCKET, dropEvent.drop_reason()); - EXPECT_EQ(NanoToMillis(bucket2StartTimeNs - 100), dropEvent.drop_time_millis()); - - dropEvent = report.value_metrics().skipped(0).drop_event(1); - EXPECT_EQ(BucketDropReason::CONDITION_UNKNOWN, dropEvent.drop_reason()); - EXPECT_EQ(NanoToMillis(bucket2StartTimeNs + 100), dropEvent.drop_time_millis()); -} - -/* - * Test that EVENT_IN_WRONG_BUCKET dump reason is logged for a late accumulate - * event (i.e. the accumulate events call occurs in the wrong bucket). - */ -TEST(ValueMetricProducerTest_BucketDrop, TestInvalidBucketWhenAccumulateEventWrongBucket) { - ValueMetric metric = ValueMetricProducerTestHelper::createMetricWithCondition(); - - sp pullerManager = new StrictMock(); - EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _)) - // Condition change to true. - .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, - vector>* data) { - EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 50); - data->clear(); - data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 50, 10)); - return true; - })) - // Dump report requested. - .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, - vector>* data) { - EXPECT_EQ(eventTimeNs, bucket2StartTimeNs + 100); - data->clear(); - data->push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs + 100, 15)); - return true; - })); - - sp valueProducer = - ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric, - ConditionState::kFalse); - - // Condition change event. - valueProducer->onConditionChanged(true, bucketStartTimeNs + 50); - - // Bucket boundary pull. - vector> allData; - allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs, 15)); - valueProducer->onDataPulled(allData, /** succeeds */ true, bucket2StartTimeNs + 1); - - allData.clear(); - allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs - 100, 20)); - - // Late accumulateEvents event. - valueProducer->accumulateEvents(allData, bucket2StartTimeNs - 100, bucket2StartTimeNs - 100); - - // Check dump report. - ProtoOutputStream output; - std::set strSet; - valueProducer->onDumpReport(bucket2StartTimeNs + 100, true /* include recent buckets */, true, - NO_TIME_CONSTRAINTS /* dumpLatency */, &strSet, &output); - - StatsLogReport report = outputStreamToProto(&output); - EXPECT_TRUE(report.has_value_metrics()); - ASSERT_EQ(1, report.value_metrics().data_size()); - ASSERT_EQ(1, report.value_metrics().skipped_size()); - - EXPECT_EQ(NanoToMillis(bucket2StartTimeNs), - report.value_metrics().skipped(0).start_bucket_elapsed_millis()); - EXPECT_EQ(NanoToMillis(bucket2StartTimeNs + 100), - report.value_metrics().skipped(0).end_bucket_elapsed_millis()); - ASSERT_EQ(1, report.value_metrics().skipped(0).drop_event_size()); - - auto dropEvent = report.value_metrics().skipped(0).drop_event(0); - EXPECT_EQ(BucketDropReason::EVENT_IN_WRONG_BUCKET, dropEvent.drop_reason()); - EXPECT_EQ(NanoToMillis(bucket2StartTimeNs - 100), dropEvent.drop_time_millis()); -} - -/* - * Test that CONDITION_UNKNOWN dump reason is logged due to an unknown condition - * when a metric is initialized. - */ -TEST(ValueMetricProducerTest_BucketDrop, TestInvalidBucketWhenConditionUnknown) { - ValueMetric metric = ValueMetricProducerTestHelper::createMetricWithCondition(); - - sp pullerManager = new StrictMock(); - EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _)) - // Condition change to true. - .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, - vector>* data) { - EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 50); - data->clear(); - data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 50, 10)); - return true; - })) - // Dump report requested. - .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, - vector>* data) { - EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 10000); - data->clear(); - data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 100, 15)); - return true; - })); - - sp valueProducer = - ValueMetricProducerTestHelper::createValueProducerWithCondition( - pullerManager, metric, ConditionState::kUnknown); - - // Condition change event. - valueProducer->onConditionChanged(true, bucketStartTimeNs + 50); - - // Check dump report. - ProtoOutputStream output; - std::set strSet; - int64_t dumpReportTimeNs = bucketStartTimeNs + 10000; - valueProducer->onDumpReport(dumpReportTimeNs, true /* include recent buckets */, true, - NO_TIME_CONSTRAINTS /* dumpLatency */, &strSet, &output); - - StatsLogReport report = outputStreamToProto(&output); - EXPECT_TRUE(report.has_value_metrics()); - ASSERT_EQ(0, report.value_metrics().data_size()); - ASSERT_EQ(1, report.value_metrics().skipped_size()); - - EXPECT_EQ(NanoToMillis(bucketStartTimeNs), - report.value_metrics().skipped(0).start_bucket_elapsed_millis()); - EXPECT_EQ(NanoToMillis(dumpReportTimeNs), - report.value_metrics().skipped(0).end_bucket_elapsed_millis()); - ASSERT_EQ(1, report.value_metrics().skipped(0).drop_event_size()); - - auto dropEvent = report.value_metrics().skipped(0).drop_event(0); - EXPECT_EQ(BucketDropReason::CONDITION_UNKNOWN, dropEvent.drop_reason()); - EXPECT_EQ(NanoToMillis(dumpReportTimeNs), dropEvent.drop_time_millis()); -} - -/* - * Test that PULL_FAILED dump reason is logged due to a pull failure in - * #pullAndMatchEventsLocked. - */ -TEST(ValueMetricProducerTest_BucketDrop, TestInvalidBucketWhenPullFailed) { - ValueMetric metric = ValueMetricProducerTestHelper::createMetricWithCondition(); - - sp pullerManager = new StrictMock(); - EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _)) - // Condition change to true. - .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, - vector>* data) { - EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 50); - data->clear(); - data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 50, 10)); - return true; - })) - // Dump report requested, pull fails. - .WillOnce(Return(false)); - - sp valueProducer = - ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric, - ConditionState::kFalse); - - // Condition change event. - valueProducer->onConditionChanged(true, bucketStartTimeNs + 50); - - // Check dump report. - ProtoOutputStream output; - std::set strSet; - int64_t dumpReportTimeNs = bucketStartTimeNs + 10000; - valueProducer->onDumpReport(dumpReportTimeNs, true /* include recent buckets */, true, - NO_TIME_CONSTRAINTS /* dumpLatency */, &strSet, &output); - - StatsLogReport report = outputStreamToProto(&output); - EXPECT_TRUE(report.has_value_metrics()); - ASSERT_EQ(0, report.value_metrics().data_size()); - ASSERT_EQ(1, report.value_metrics().skipped_size()); - - EXPECT_EQ(NanoToMillis(bucketStartTimeNs), - report.value_metrics().skipped(0).start_bucket_elapsed_millis()); - EXPECT_EQ(NanoToMillis(dumpReportTimeNs), - report.value_metrics().skipped(0).end_bucket_elapsed_millis()); - ASSERT_EQ(1, report.value_metrics().skipped(0).drop_event_size()); - - auto dropEvent = report.value_metrics().skipped(0).drop_event(0); - EXPECT_EQ(BucketDropReason::PULL_FAILED, dropEvent.drop_reason()); - EXPECT_EQ(NanoToMillis(dumpReportTimeNs), dropEvent.drop_time_millis()); -} - -/* - * Test that MULTIPLE_BUCKETS_SKIPPED dump reason is logged when a log event - * skips over more than one bucket. - */ -TEST(ValueMetricProducerTest_BucketDrop, TestInvalidBucketWhenMultipleBucketsSkipped) { - ValueMetric metric = ValueMetricProducerTestHelper::createMetricWithCondition(); - - sp pullerManager = new StrictMock(); - EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _)) - // Condition change to true. - .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, - vector>* data) { - EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 10); - data->clear(); - data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 10, 10)); - return true; - })) - // Dump report requested. - .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, - vector>* data) { - EXPECT_EQ(eventTimeNs, bucket4StartTimeNs + 10); - data->clear(); - data->push_back(CreateRepeatedValueLogEvent(tagId, bucket4StartTimeNs + 1000, 15)); - return true; - })); - - sp valueProducer = - ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric, - ConditionState::kFalse); - - // Condition change event. - valueProducer->onConditionChanged(true, bucketStartTimeNs + 10); - - // Condition change event that skips forward by three buckets. - valueProducer->onConditionChanged(false, bucket4StartTimeNs + 10); - - int64_t dumpTimeNs = bucket4StartTimeNs + 1000; - - // Check dump report. - ProtoOutputStream output; - std::set strSet; - valueProducer->onDumpReport(dumpTimeNs, true /* include current buckets */, true, - NO_TIME_CONSTRAINTS /* dumpLatency */, &strSet, &output); - - StatsLogReport report = outputStreamToProto(&output); - EXPECT_TRUE(report.has_value_metrics()); - ASSERT_EQ(0, report.value_metrics().data_size()); - ASSERT_EQ(2, report.value_metrics().skipped_size()); - - EXPECT_EQ(NanoToMillis(bucketStartTimeNs), - report.value_metrics().skipped(0).start_bucket_elapsed_millis()); - EXPECT_EQ(NanoToMillis(bucket4StartTimeNs), - report.value_metrics().skipped(0).end_bucket_elapsed_millis()); - ASSERT_EQ(1, report.value_metrics().skipped(0).drop_event_size()); - - auto dropEvent = report.value_metrics().skipped(0).drop_event(0); - EXPECT_EQ(BucketDropReason::MULTIPLE_BUCKETS_SKIPPED, dropEvent.drop_reason()); - EXPECT_EQ(NanoToMillis(bucket4StartTimeNs + 10), dropEvent.drop_time_millis()); - - // This bucket is skipped because a dumpReport with include current buckets is called. - // This creates a new bucket from bucket4StartTimeNs to dumpTimeNs in which we have no data - // since the condition is false for the entire bucket interval. - EXPECT_EQ(NanoToMillis(bucket4StartTimeNs), - report.value_metrics().skipped(1).start_bucket_elapsed_millis()); - EXPECT_EQ(NanoToMillis(dumpTimeNs), - report.value_metrics().skipped(1).end_bucket_elapsed_millis()); - ASSERT_EQ(1, report.value_metrics().skipped(1).drop_event_size()); - - dropEvent = report.value_metrics().skipped(1).drop_event(0); - EXPECT_EQ(BucketDropReason::NO_DATA, dropEvent.drop_reason()); - EXPECT_EQ(NanoToMillis(dumpTimeNs), dropEvent.drop_time_millis()); -} - -/* - * Test that BUCKET_TOO_SMALL dump reason is logged when a flushed bucket size - * is smaller than the "min_bucket_size_nanos" specified in the metric config. - */ -TEST(ValueMetricProducerTest_BucketDrop, TestBucketDropWhenBucketTooSmall) { - ValueMetric metric = ValueMetricProducerTestHelper::createMetricWithCondition(); - metric.set_min_bucket_size_nanos(10000000000); // 10 seconds - - sp pullerManager = new StrictMock(); - EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _)) - // Condition change to true. - .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, - vector>* data) { - EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 10); - data->clear(); - data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 10, 10)); - return true; - })) - // Dump report requested. - .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, - vector>* data) { - EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 9000000); - data->clear(); - data->push_back( - CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 9000000, 15)); - return true; - })); - - sp valueProducer = - ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric, - ConditionState::kFalse); - - // Condition change event. - valueProducer->onConditionChanged(true, bucketStartTimeNs + 10); - - // Check dump report. - ProtoOutputStream output; - std::set strSet; - int64_t dumpReportTimeNs = bucketStartTimeNs + 9000000; - valueProducer->onDumpReport(dumpReportTimeNs, true /* include recent buckets */, true, - NO_TIME_CONSTRAINTS /* dumpLatency */, &strSet, &output); - - StatsLogReport report = outputStreamToProto(&output); - EXPECT_TRUE(report.has_value_metrics()); - ASSERT_EQ(0, report.value_metrics().data_size()); - ASSERT_EQ(1, report.value_metrics().skipped_size()); - - EXPECT_EQ(NanoToMillis(bucketStartTimeNs), - report.value_metrics().skipped(0).start_bucket_elapsed_millis()); - EXPECT_EQ(NanoToMillis(dumpReportTimeNs), - report.value_metrics().skipped(0).end_bucket_elapsed_millis()); - ASSERT_EQ(1, report.value_metrics().skipped(0).drop_event_size()); - - auto dropEvent = report.value_metrics().skipped(0).drop_event(0); - EXPECT_EQ(BucketDropReason::BUCKET_TOO_SMALL, dropEvent.drop_reason()); - EXPECT_EQ(NanoToMillis(dumpReportTimeNs), dropEvent.drop_time_millis()); -} - -/* - * Test that NO_DATA dump reason is logged when a flushed bucket contains no data. - */ -TEST(ValueMetricProducerTest_BucketDrop, TestBucketDropWhenDataUnavailable) { - ValueMetric metric = ValueMetricProducerTestHelper::createMetricWithCondition(); - - sp pullerManager = new StrictMock(); - - sp valueProducer = - ValueMetricProducerTestHelper::createValueProducerWithCondition( - pullerManager, metric, ConditionState::kFalse); - - // Check dump report. - ProtoOutputStream output; - std::set strSet; - int64_t dumpReportTimeNs = bucketStartTimeNs + 10000000000; // 10 seconds - valueProducer->onDumpReport(dumpReportTimeNs, true /* include current bucket */, true, - NO_TIME_CONSTRAINTS /* dumpLatency */, &strSet, &output); - - StatsLogReport report = outputStreamToProto(&output); - EXPECT_TRUE(report.has_value_metrics()); - ASSERT_EQ(0, report.value_metrics().data_size()); - ASSERT_EQ(1, report.value_metrics().skipped_size()); - - EXPECT_EQ(NanoToMillis(bucketStartTimeNs), - report.value_metrics().skipped(0).start_bucket_elapsed_millis()); - EXPECT_EQ(NanoToMillis(dumpReportTimeNs), - report.value_metrics().skipped(0).end_bucket_elapsed_millis()); - ASSERT_EQ(1, report.value_metrics().skipped(0).drop_event_size()); - - auto dropEvent = report.value_metrics().skipped(0).drop_event(0); - EXPECT_EQ(BucketDropReason::NO_DATA, dropEvent.drop_reason()); - EXPECT_EQ(NanoToMillis(dumpReportTimeNs), dropEvent.drop_time_millis()); -} - -/* - * Test that all buckets are dropped due to condition unknown until the first onConditionChanged. - */ -TEST(ValueMetricProducerTest_BucketDrop, TestConditionUnknownMultipleBuckets) { - ValueMetric metric = ValueMetricProducerTestHelper::createMetricWithCondition(); - - sp pullerManager = new StrictMock(); - EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _)) - // Condition change to true. - .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, - vector>* data) { - EXPECT_EQ(eventTimeNs, bucket2StartTimeNs + 10 * NS_PER_SEC); - data->clear(); - data->push_back(CreateRepeatedValueLogEvent( - tagId, bucket2StartTimeNs + 10 * NS_PER_SEC, 10)); - return true; - })) - // Dump report requested. - .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, - vector>* data) { - EXPECT_EQ(eventTimeNs, bucket2StartTimeNs + 15 * NS_PER_SEC); - data->clear(); - data->push_back(CreateRepeatedValueLogEvent( - tagId, bucket2StartTimeNs + 15 * NS_PER_SEC, 15)); - return true; - })); - - sp valueProducer = - ValueMetricProducerTestHelper::createValueProducerWithCondition( - pullerManager, metric, ConditionState::kUnknown); - - // Bucket should be dropped because of condition unknown. - int64_t appUpgradeTimeNs = bucketStartTimeNs + 5 * NS_PER_SEC; - valueProducer->notifyAppUpgrade(appUpgradeTimeNs); - - // Bucket also dropped due to condition unknown - vector> allData; - allData.clear(); - allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs + 1, 3)); - valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs); - - // This bucket is also dropped due to condition unknown. - int64_t conditionChangeTimeNs = bucket2StartTimeNs + 10 * NS_PER_SEC; - valueProducer->onConditionChanged(true, conditionChangeTimeNs); - - // Check dump report. - ProtoOutputStream output; - std::set strSet; - int64_t dumpReportTimeNs = bucket2StartTimeNs + 15 * NS_PER_SEC; // 15 seconds - valueProducer->onDumpReport(dumpReportTimeNs, true /* include current bucket */, true, - NO_TIME_CONSTRAINTS /* dumpLatency */, &strSet, &output); - - StatsLogReport report = outputStreamToProto(&output); - EXPECT_TRUE(report.has_value_metrics()); - ASSERT_EQ(0, report.value_metrics().data_size()); - ASSERT_EQ(3, report.value_metrics().skipped_size()); - - EXPECT_EQ(NanoToMillis(bucketStartTimeNs), - report.value_metrics().skipped(0).start_bucket_elapsed_millis()); - EXPECT_EQ(NanoToMillis(appUpgradeTimeNs), - report.value_metrics().skipped(0).end_bucket_elapsed_millis()); - ASSERT_EQ(1, report.value_metrics().skipped(0).drop_event_size()); - - auto dropEvent = report.value_metrics().skipped(0).drop_event(0); - EXPECT_EQ(BucketDropReason::CONDITION_UNKNOWN, dropEvent.drop_reason()); - EXPECT_EQ(NanoToMillis(appUpgradeTimeNs), dropEvent.drop_time_millis()); - - EXPECT_EQ(NanoToMillis(appUpgradeTimeNs), - report.value_metrics().skipped(1).start_bucket_elapsed_millis()); - EXPECT_EQ(NanoToMillis(bucket2StartTimeNs), - report.value_metrics().skipped(1).end_bucket_elapsed_millis()); - ASSERT_EQ(1, report.value_metrics().skipped(1).drop_event_size()); - - dropEvent = report.value_metrics().skipped(1).drop_event(0); - EXPECT_EQ(BucketDropReason::CONDITION_UNKNOWN, dropEvent.drop_reason()); - EXPECT_EQ(NanoToMillis(bucket2StartTimeNs), dropEvent.drop_time_millis()); - - EXPECT_EQ(NanoToMillis(bucket2StartTimeNs), - report.value_metrics().skipped(2).start_bucket_elapsed_millis()); - EXPECT_EQ(NanoToMillis(dumpReportTimeNs), - report.value_metrics().skipped(2).end_bucket_elapsed_millis()); - ASSERT_EQ(1, report.value_metrics().skipped(2).drop_event_size()); - - dropEvent = report.value_metrics().skipped(2).drop_event(0); - EXPECT_EQ(BucketDropReason::CONDITION_UNKNOWN, dropEvent.drop_reason()); - EXPECT_EQ(NanoToMillis(conditionChangeTimeNs), dropEvent.drop_time_millis()); -} - -/* - * Test that a skipped bucket is logged when a forced bucket split occurs when the previous bucket - * was not flushed in time. - */ -TEST(ValueMetricProducerTest_BucketDrop, TestBucketDropWhenForceBucketSplitBeforeBucketFlush) { - ValueMetric metric = ValueMetricProducerTestHelper::createMetricWithCondition(); - - sp pullerManager = new StrictMock(); - EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _)) - // Condition change to true. - .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, - vector>* data) { - EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 10); - data->clear(); - data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 10, 10)); - return true; - })) - // App Update. - .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, - vector>* data) { - EXPECT_EQ(eventTimeNs, bucket2StartTimeNs + 1000); - data->clear(); - data->push_back( - CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs + 1000, 15)); - return true; - })); - - sp valueProducer = - ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric, - ConditionState::kFalse); - - // Condition changed event - int64_t conditionChangeTimeNs = bucketStartTimeNs + 10; - valueProducer->onConditionChanged(true, conditionChangeTimeNs); - - // App update event. - int64_t appUpdateTimeNs = bucket2StartTimeNs + 1000; - valueProducer->notifyAppUpgrade(appUpdateTimeNs); - - // Check dump report. - ProtoOutputStream output; - std::set strSet; - int64_t dumpReportTimeNs = bucket2StartTimeNs + 10000000000; // 10 seconds - valueProducer->onDumpReport(dumpReportTimeNs, false /* include current buckets */, true, - NO_TIME_CONSTRAINTS /* dumpLatency */, &strSet, &output); - - StatsLogReport report = outputStreamToProto(&output); - EXPECT_TRUE(report.has_value_metrics()); - ASSERT_EQ(1, report.value_metrics().data_size()); - ASSERT_EQ(1, report.value_metrics().skipped_size()); - - ASSERT_EQ(1, report.value_metrics().data(0).bucket_info_size()); - auto data = report.value_metrics().data(0); - ASSERT_EQ(0, data.bucket_info(0).bucket_num()); - EXPECT_EQ(5, data.bucket_info(0).values(0).value_long()); - - EXPECT_EQ(NanoToMillis(bucket2StartTimeNs), - report.value_metrics().skipped(0).start_bucket_elapsed_millis()); - EXPECT_EQ(NanoToMillis(appUpdateTimeNs), - report.value_metrics().skipped(0).end_bucket_elapsed_millis()); - ASSERT_EQ(1, report.value_metrics().skipped(0).drop_event_size()); - - auto dropEvent = report.value_metrics().skipped(0).drop_event(0); - EXPECT_EQ(BucketDropReason::NO_DATA, dropEvent.drop_reason()); - EXPECT_EQ(NanoToMillis(appUpdateTimeNs), dropEvent.drop_time_millis()); -} - -/* - * Test multiple bucket drop events in the same bucket. - */ -TEST(ValueMetricProducerTest_BucketDrop, TestMultipleBucketDropEvents) { - ValueMetric metric = ValueMetricProducerTestHelper::createMetricWithCondition(); - - sp pullerManager = new StrictMock(); - EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, bucketStartTimeNs + 10, _)) - // Condition change to true. - .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t, - vector>* data) { - data->clear(); - data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 10, 10)); - return true; - })); - - sp valueProducer = - ValueMetricProducerTestHelper::createValueProducerWithCondition( - pullerManager, metric, ConditionState::kUnknown); - - // Condition change event. - valueProducer->onConditionChanged(true, bucketStartTimeNs + 10); - - // Check dump report. - ProtoOutputStream output; - std::set strSet; - int64_t dumpReportTimeNs = bucketStartTimeNs + 1000; - valueProducer->onDumpReport(dumpReportTimeNs, true /* include recent buckets */, true, - FAST /* dumpLatency */, &strSet, &output); - - StatsLogReport report = outputStreamToProto(&output); - EXPECT_TRUE(report.has_value_metrics()); - ASSERT_EQ(0, report.value_metrics().data_size()); - ASSERT_EQ(1, report.value_metrics().skipped_size()); - - EXPECT_EQ(NanoToMillis(bucketStartTimeNs), - report.value_metrics().skipped(0).start_bucket_elapsed_millis()); - EXPECT_EQ(NanoToMillis(dumpReportTimeNs), - report.value_metrics().skipped(0).end_bucket_elapsed_millis()); - ASSERT_EQ(2, report.value_metrics().skipped(0).drop_event_size()); - - auto dropEvent = report.value_metrics().skipped(0).drop_event(0); - EXPECT_EQ(BucketDropReason::CONDITION_UNKNOWN, dropEvent.drop_reason()); - EXPECT_EQ(NanoToMillis(bucketStartTimeNs + 10), dropEvent.drop_time_millis()); - - dropEvent = report.value_metrics().skipped(0).drop_event(1); - EXPECT_EQ(BucketDropReason::DUMP_REPORT_REQUESTED, dropEvent.drop_reason()); - EXPECT_EQ(NanoToMillis(dumpReportTimeNs), dropEvent.drop_time_millis()); -} - -/* - * Test that the number of logged bucket drop events is capped at the maximum. - * The maximum is currently 10 and is set in MetricProducer::maxDropEventsReached(). - */ -TEST(ValueMetricProducerTest_BucketDrop, TestMaxBucketDropEvents) { - ValueMetric metric = ValueMetricProducerTestHelper::createMetricWithCondition(); - - sp pullerManager = new StrictMock(); - EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _)) - // First condition change event. - .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, - vector>* data) { - EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 10); - for (int i = 0; i < 2000; i++) { - data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 1, i)); - } - return true; - })) - .WillOnce(Return(false)) - .WillOnce(Return(false)) - .WillOnce(Return(false)) - .WillOnce(Return(false)) - .WillOnce(Return(false)) - .WillOnce(Return(false)) - .WillOnce(Return(false)) - .WillOnce(Return(false)) - .WillOnce(Return(false)) - .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, - vector>* data) { - EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 220); - data->clear(); - data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 220, 10)); - return true; - })); - - sp valueProducer = - ValueMetricProducerTestHelper::createValueProducerWithCondition( - pullerManager, metric, ConditionState::kUnknown); - - // First condition change event causes guardrail to be reached. - valueProducer->onConditionChanged(true, bucketStartTimeNs + 10); - - // 2-10 condition change events result in failed pulls. - valueProducer->onConditionChanged(false, bucketStartTimeNs + 30); - valueProducer->onConditionChanged(true, bucketStartTimeNs + 50); - valueProducer->onConditionChanged(false, bucketStartTimeNs + 70); - valueProducer->onConditionChanged(true, bucketStartTimeNs + 90); - valueProducer->onConditionChanged(false, bucketStartTimeNs + 100); - valueProducer->onConditionChanged(true, bucketStartTimeNs + 150); - valueProducer->onConditionChanged(false, bucketStartTimeNs + 170); - valueProducer->onConditionChanged(true, bucketStartTimeNs + 190); - valueProducer->onConditionChanged(false, bucketStartTimeNs + 200); - - // Condition change event 11 - valueProducer->onConditionChanged(true, bucketStartTimeNs + 220); - - // Check dump report. - ProtoOutputStream output; - std::set strSet; - int64_t dumpReportTimeNs = bucketStartTimeNs + 1000; - // Because we already have 10 dump events in the current bucket, - // this case should not be added to the list of dump events. - valueProducer->onDumpReport(bucketStartTimeNs + 1000, true /* include recent buckets */, true, - FAST /* dumpLatency */, &strSet, &output); - - StatsLogReport report = outputStreamToProto(&output); - EXPECT_TRUE(report.has_value_metrics()); - ASSERT_EQ(0, report.value_metrics().data_size()); - ASSERT_EQ(1, report.value_metrics().skipped_size()); - - EXPECT_EQ(NanoToMillis(bucketStartTimeNs), - report.value_metrics().skipped(0).start_bucket_elapsed_millis()); - EXPECT_EQ(NanoToMillis(dumpReportTimeNs), - report.value_metrics().skipped(0).end_bucket_elapsed_millis()); - ASSERT_EQ(10, report.value_metrics().skipped(0).drop_event_size()); - - auto dropEvent = report.value_metrics().skipped(0).drop_event(0); - EXPECT_EQ(BucketDropReason::CONDITION_UNKNOWN, dropEvent.drop_reason()); - EXPECT_EQ(NanoToMillis(bucketStartTimeNs + 10), dropEvent.drop_time_millis()); - - dropEvent = report.value_metrics().skipped(0).drop_event(1); - EXPECT_EQ(BucketDropReason::PULL_FAILED, dropEvent.drop_reason()); - EXPECT_EQ(NanoToMillis(bucketStartTimeNs + 30), dropEvent.drop_time_millis()); - - dropEvent = report.value_metrics().skipped(0).drop_event(2); - EXPECT_EQ(BucketDropReason::PULL_FAILED, dropEvent.drop_reason()); - EXPECT_EQ(NanoToMillis(bucketStartTimeNs + 50), dropEvent.drop_time_millis()); - - dropEvent = report.value_metrics().skipped(0).drop_event(3); - EXPECT_EQ(BucketDropReason::PULL_FAILED, dropEvent.drop_reason()); - EXPECT_EQ(NanoToMillis(bucketStartTimeNs + 70), dropEvent.drop_time_millis()); - - dropEvent = report.value_metrics().skipped(0).drop_event(4); - EXPECT_EQ(BucketDropReason::PULL_FAILED, dropEvent.drop_reason()); - EXPECT_EQ(NanoToMillis(bucketStartTimeNs + 90), dropEvent.drop_time_millis()); - - dropEvent = report.value_metrics().skipped(0).drop_event(5); - EXPECT_EQ(BucketDropReason::PULL_FAILED, dropEvent.drop_reason()); - EXPECT_EQ(NanoToMillis(bucketStartTimeNs + 100), dropEvent.drop_time_millis()); - - dropEvent = report.value_metrics().skipped(0).drop_event(6); - EXPECT_EQ(BucketDropReason::PULL_FAILED, dropEvent.drop_reason()); - EXPECT_EQ(NanoToMillis(bucketStartTimeNs + 150), dropEvent.drop_time_millis()); - - dropEvent = report.value_metrics().skipped(0).drop_event(7); - EXPECT_EQ(BucketDropReason::PULL_FAILED, dropEvent.drop_reason()); - EXPECT_EQ(NanoToMillis(bucketStartTimeNs + 170), dropEvent.drop_time_millis()); - - dropEvent = report.value_metrics().skipped(0).drop_event(8); - EXPECT_EQ(BucketDropReason::PULL_FAILED, dropEvent.drop_reason()); - EXPECT_EQ(NanoToMillis(bucketStartTimeNs + 190), dropEvent.drop_time_millis()); - - dropEvent = report.value_metrics().skipped(0).drop_event(9); - EXPECT_EQ(BucketDropReason::PULL_FAILED, dropEvent.drop_reason()); - EXPECT_EQ(NanoToMillis(bucketStartTimeNs + 200), dropEvent.drop_time_millis()); -} - -/* - * Test metric with a simple sliced state - * - Increasing values - * - Using diff - * - Second field is value field - */ -TEST(ValueMetricProducerTest, TestSlicedState) { - // Set up ValueMetricProducer. - ValueMetric metric = ValueMetricProducerTestHelper::createMetricWithState("SCREEN_STATE"); - sp pullerManager = new StrictMock(); - EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _)) - // ValueMetricProducer initialized. - .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, - vector>* data) { - EXPECT_EQ(eventTimeNs, bucketStartTimeNs); - data->clear(); - data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs, 3)); - return true; - })) - // Screen state change to ON. - .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, - vector>* data) { - EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 5 * NS_PER_SEC); - data->clear(); - data->push_back( - CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 5 * NS_PER_SEC, 5)); - return true; - })) - // Screen state change to OFF. - .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, - vector>* data) { - EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 10 * NS_PER_SEC); - data->clear(); - data->push_back( - CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 10 * NS_PER_SEC, 9)); - return true; - })) - // Screen state change to ON. - .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, - vector>* data) { - EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 15 * NS_PER_SEC); - data->clear(); - data->push_back(CreateRepeatedValueLogEvent( - tagId, bucketStartTimeNs + 15 * NS_PER_SEC, 21)); - return true; - })) - // Dump report requested. - .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, - vector>* data) { - EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 50 * NS_PER_SEC); - data->clear(); - data->push_back(CreateRepeatedValueLogEvent( - tagId, bucketStartTimeNs + 50 * NS_PER_SEC, 30)); - return true; - })); - - StateManager::getInstance().clear(); - sp valueProducer = - ValueMetricProducerTestHelper::createValueProducerWithState( - pullerManager, metric, {util::SCREEN_STATE_CHANGED}, {}); - EXPECT_EQ(1, valueProducer->mSlicedStateAtoms.size()); - - // Set up StateManager and check that StateTrackers are initialized. - StateManager::getInstance().registerListener(SCREEN_STATE_ATOM_ID, valueProducer); - EXPECT_EQ(1, StateManager::getInstance().getStateTrackersCount()); - EXPECT_EQ(1, StateManager::getInstance().getListenersCount(SCREEN_STATE_ATOM_ID)); - - // Bucket status after metric initialized. - ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); - // Base for dimension key {} - auto it = valueProducer->mCurrentSlicedBucket.begin(); - auto itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); - EXPECT_TRUE(itBase->second.baseInfos[0].hasBase); - EXPECT_EQ(3, itBase->second.baseInfos[0].base.long_value); - EXPECT_TRUE(itBase->second.hasCurrentState); - ASSERT_EQ(1, itBase->second.currentState.getValues().size()); - EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, - itBase->second.currentState.getValues()[0].mValue.int_value); - // Value for dimension, state key {{}, kStateUnknown} - EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); - ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); - EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, - it->first.getStateValuesKey().getValues()[0].mValue.int_value); - EXPECT_FALSE(it->second.intervals[0].hasValue); - assertConditionTimer(it->second.conditionTimer, true, 0, bucketStartTimeNs); - - // Bucket status after screen state change kStateUnknown->ON. - auto screenEvent = CreateScreenStateChangedEvent( - bucketStartTimeNs + 5 * NS_PER_SEC, android::view::DisplayStateEnum::DISPLAY_STATE_ON); - StateManager::getInstance().onLogEvent(*screenEvent); - ASSERT_EQ(2UL, valueProducer->mCurrentSlicedBucket.size()); - // Base for dimension key {} - it = valueProducer->mCurrentSlicedBucket.begin(); - itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); - EXPECT_TRUE(itBase->second.baseInfos[0].hasBase); - EXPECT_EQ(5, itBase->second.baseInfos[0].base.long_value); - EXPECT_TRUE(itBase->second.hasCurrentState); - ASSERT_EQ(1, itBase->second.currentState.getValues().size()); - EXPECT_EQ(android::view::DisplayStateEnum::DISPLAY_STATE_ON, - itBase->second.currentState.getValues()[0].mValue.int_value); - // Value for dimension, state key {{}, ON} - EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); - ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); - EXPECT_EQ(android::view::DisplayStateEnum::DISPLAY_STATE_ON, - it->first.getStateValuesKey().getValues()[0].mValue.int_value); - EXPECT_EQ(0, it->second.intervals.size()); - assertConditionTimer(it->second.conditionTimer, true, 0, bucketStartTimeNs + 5 * NS_PER_SEC); - // Value for dimension, state key {{}, kStateUnknown} - it++; - EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); - ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); - EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, - it->first.getStateValuesKey().getValues()[0].mValue.int_value); - EXPECT_TRUE(it->second.intervals[0].hasValue); - EXPECT_EQ(2, it->second.intervals[0].value.long_value); - assertConditionTimer(it->second.conditionTimer, false, 5 * NS_PER_SEC, - bucketStartTimeNs + 5 * NS_PER_SEC); - - // Bucket status after screen state change ON->OFF. - screenEvent = CreateScreenStateChangedEvent(bucketStartTimeNs + 10 * NS_PER_SEC, - android::view::DisplayStateEnum::DISPLAY_STATE_OFF); - StateManager::getInstance().onLogEvent(*screenEvent); - ASSERT_EQ(3UL, valueProducer->mCurrentSlicedBucket.size()); - // Base for dimension key {} - it = valueProducer->mCurrentSlicedBucket.begin(); - itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); - EXPECT_TRUE(itBase->second.baseInfos[0].hasBase); - EXPECT_EQ(9, itBase->second.baseInfos[0].base.long_value); - EXPECT_TRUE(itBase->second.hasCurrentState); - EXPECT_EQ(android::view::DisplayStateEnum::DISPLAY_STATE_OFF, - itBase->second.currentState.getValues()[0].mValue.int_value); - // Value for dimension, state key {{}, OFF} - EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); - ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); - EXPECT_EQ(android::view::DisplayStateEnum::DISPLAY_STATE_OFF, - it->first.getStateValuesKey().getValues()[0].mValue.int_value); - EXPECT_EQ(0, it->second.intervals.size()); - assertConditionTimer(it->second.conditionTimer, true, 0, bucketStartTimeNs + 10 * NS_PER_SEC); - // Value for dimension, state key {{}, ON} - it++; - EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); - ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); - EXPECT_EQ(android::view::DisplayStateEnum::DISPLAY_STATE_ON, - it->first.getStateValuesKey().getValues()[0].mValue.int_value); - EXPECT_TRUE(it->second.intervals[0].hasValue); - EXPECT_EQ(4, it->second.intervals[0].value.long_value); - assertConditionTimer(it->second.conditionTimer, false, 5 * NS_PER_SEC, - bucketStartTimeNs + 10 * NS_PER_SEC); - // Value for dimension, state key {{}, kStateUnknown} - it++; - EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); - ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); - EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, - it->first.getStateValuesKey().getValues()[0].mValue.int_value); - EXPECT_TRUE(it->second.intervals[0].hasValue); - EXPECT_EQ(2, it->second.intervals[0].value.long_value); - assertConditionTimer(it->second.conditionTimer, false, 5 * NS_PER_SEC, - bucketStartTimeNs + 5 * NS_PER_SEC); - - // Bucket status after screen state change OFF->ON. - screenEvent = CreateScreenStateChangedEvent(bucketStartTimeNs + 15 * NS_PER_SEC, - android::view::DisplayStateEnum::DISPLAY_STATE_ON); - StateManager::getInstance().onLogEvent(*screenEvent); - ASSERT_EQ(3UL, valueProducer->mCurrentSlicedBucket.size()); - // Base for dimension key {} - it = valueProducer->mCurrentSlicedBucket.begin(); - itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); - EXPECT_TRUE(itBase->second.baseInfos[0].hasBase); - EXPECT_EQ(21, itBase->second.baseInfos[0].base.long_value); - EXPECT_TRUE(itBase->second.hasCurrentState); - ASSERT_EQ(1, itBase->second.currentState.getValues().size()); - EXPECT_EQ(android::view::DisplayStateEnum::DISPLAY_STATE_ON, - itBase->second.currentState.getValues()[0].mValue.int_value); - // Value for dimension, state key {{}, OFF} - EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); - ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); - EXPECT_EQ(android::view::DisplayStateEnum::DISPLAY_STATE_OFF, - it->first.getStateValuesKey().getValues()[0].mValue.int_value); - EXPECT_TRUE(it->second.intervals[0].hasValue); - EXPECT_EQ(12, it->second.intervals[0].value.long_value); - assertConditionTimer(it->second.conditionTimer, false, 5 * NS_PER_SEC, - bucketStartTimeNs + 15 * NS_PER_SEC); - // Value for dimension, state key {{}, ON} - it++; - EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); - ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); - EXPECT_EQ(android::view::DisplayStateEnum::DISPLAY_STATE_ON, - it->first.getStateValuesKey().getValues()[0].mValue.int_value); - EXPECT_TRUE(it->second.intervals[0].hasValue); - EXPECT_EQ(4, it->second.intervals[0].value.long_value); - assertConditionTimer(it->second.conditionTimer, true, 5 * NS_PER_SEC, - bucketStartTimeNs + 15 * NS_PER_SEC); - // Value for dimension, state key {{}, kStateUnknown} - it++; - EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); - ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); - EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, - it->first.getStateValuesKey().getValues()[0].mValue.int_value); - EXPECT_TRUE(it->second.intervals[0].hasValue); - EXPECT_EQ(2, it->second.intervals[0].value.long_value); - assertConditionTimer(it->second.conditionTimer, false, 5 * NS_PER_SEC, - bucketStartTimeNs + 5 * NS_PER_SEC); - - // Start dump report and check output. - ProtoOutputStream output; - std::set strSet; - valueProducer->onDumpReport(bucketStartTimeNs + 50 * NS_PER_SEC, - true /* include recent buckets */, true, NO_TIME_CONSTRAINTS, - &strSet, &output); - - StatsLogReport report = outputStreamToProto(&output); - EXPECT_TRUE(report.has_value_metrics()); - ASSERT_EQ(3, report.value_metrics().data_size()); - - // {{}, kStateUnknown} - auto data = report.value_metrics().data(0); - ASSERT_EQ(1, data.bucket_info_size()); - EXPECT_EQ(2, report.value_metrics().data(0).bucket_info(0).values(0).value_long()); - EXPECT_EQ(SCREEN_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); - EXPECT_TRUE(data.slice_by_state(0).has_value()); - EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, data.slice_by_state(0).value()); - EXPECT_EQ(5 * NS_PER_SEC, data.bucket_info(0).condition_true_nanos()); - - // {{}, ON} - data = report.value_metrics().data(1); - ASSERT_EQ(1, report.value_metrics().data(1).bucket_info_size()); - EXPECT_EQ(13, report.value_metrics().data(1).bucket_info(0).values(0).value_long()); - EXPECT_EQ(SCREEN_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); - EXPECT_TRUE(data.slice_by_state(0).has_value()); - EXPECT_EQ(android::view::DisplayStateEnum::DISPLAY_STATE_ON, data.slice_by_state(0).value()); - EXPECT_EQ(40 * NS_PER_SEC, data.bucket_info(0).condition_true_nanos()); - - // {{}, OFF} - data = report.value_metrics().data(2); - ASSERT_EQ(1, report.value_metrics().data(2).bucket_info_size()); - EXPECT_EQ(12, report.value_metrics().data(2).bucket_info(0).values(0).value_long()); - EXPECT_EQ(SCREEN_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); - EXPECT_TRUE(data.slice_by_state(0).has_value()); - EXPECT_EQ(android::view::DisplayStateEnum::DISPLAY_STATE_OFF, data.slice_by_state(0).value()); - EXPECT_EQ(5 * NS_PER_SEC, data.bucket_info(0).condition_true_nanos()); -} - -/* - * Test metric with sliced state with map - * - Increasing values - * - Using diff - * - Second field is value field - */ -TEST(ValueMetricProducerTest, TestSlicedStateWithMap) { - // Set up ValueMetricProducer. - ValueMetric metric = ValueMetricProducerTestHelper::createMetricWithState("SCREEN_STATE_ONOFF"); - sp pullerManager = new StrictMock(); - EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _)) - // ValueMetricProducer initialized. - .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, - vector>* data) { - EXPECT_EQ(eventTimeNs, bucketStartTimeNs); - data->clear(); - data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs, 3)); - return true; - })) - // Screen state change to ON. - .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, - vector>* data) { - EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 5 * NS_PER_SEC); - data->clear(); - data->push_back( - CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 5 * NS_PER_SEC, 5)); - return true; - })) - // Screen state change to VR has no pull because it is in the same - // state group as ON. - - // Screen state change to ON has no pull because it is in the same - // state group as VR. - - // Screen state change to OFF. - .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, - vector>* data) { - EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 15 * NS_PER_SEC); - data->clear(); - data->push_back(CreateRepeatedValueLogEvent( - tagId, bucketStartTimeNs + 15 * NS_PER_SEC, 21)); - return true; - })) - // Dump report requested. - .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, - vector>* data) { - EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 50 * NS_PER_SEC); - data->clear(); - data->push_back(CreateRepeatedValueLogEvent( - tagId, bucketStartTimeNs + 50 * NS_PER_SEC, 30)); - return true; - })); - - const StateMap& stateMap = - CreateScreenStateOnOffMap(/*screen on id=*/321, /*screen off id=*/123); - const StateMap_StateGroup screenOnGroup = stateMap.group(0); - const StateMap_StateGroup screenOffGroup = stateMap.group(1); - - unordered_map> stateGroupMap; - for (auto group : stateMap.group()) { - for (auto value : group.value()) { - stateGroupMap[SCREEN_STATE_ATOM_ID][value] = group.group_id(); - } - } - - StateManager::getInstance().clear(); - sp valueProducer = - ValueMetricProducerTestHelper::createValueProducerWithState( - pullerManager, metric, {util::SCREEN_STATE_CHANGED}, stateGroupMap); - - // Set up StateManager and check that StateTrackers are initialized. - StateManager::getInstance().registerListener(SCREEN_STATE_ATOM_ID, valueProducer); - EXPECT_EQ(1, StateManager::getInstance().getStateTrackersCount()); - EXPECT_EQ(1, StateManager::getInstance().getListenersCount(SCREEN_STATE_ATOM_ID)); - - // Bucket status after metric initialized. - ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); - // Base for dimension key {} - auto it = valueProducer->mCurrentSlicedBucket.begin(); - auto itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); - EXPECT_TRUE(itBase->second.baseInfos[0].hasBase); - EXPECT_EQ(3, itBase->second.baseInfos[0].base.long_value); - EXPECT_TRUE(itBase->second.hasCurrentState); - ASSERT_EQ(1, itBase->second.currentState.getValues().size()); - EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, - itBase->second.currentState.getValues()[0].mValue.int_value); - // Value for dimension, state key {{}, {kStateUnknown}} - EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); - ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); - EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, - it->first.getStateValuesKey().getValues()[0].mValue.int_value); - EXPECT_FALSE(it->second.intervals[0].hasValue); - assertConditionTimer(it->second.conditionTimer, true, 0, bucketStartTimeNs); - - // Bucket status after screen state change kStateUnknown->ON. - auto screenEvent = CreateScreenStateChangedEvent( - bucketStartTimeNs + 5 * NS_PER_SEC, android::view::DisplayStateEnum::DISPLAY_STATE_ON); - StateManager::getInstance().onLogEvent(*screenEvent); - ASSERT_EQ(2UL, valueProducer->mCurrentSlicedBucket.size()); - // Base for dimension key {} - it = valueProducer->mCurrentSlicedBucket.begin(); - itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); - EXPECT_TRUE(itBase->second.baseInfos[0].hasBase); - EXPECT_EQ(5, itBase->second.baseInfos[0].base.long_value); - EXPECT_TRUE(itBase->second.hasCurrentState); - ASSERT_EQ(1, itBase->second.currentState.getValues().size()); - EXPECT_EQ(screenOnGroup.group_id(), - itBase->second.currentState.getValues()[0].mValue.long_value); - // Value for dimension, state key {{}, ON GROUP} - EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); - ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); - EXPECT_EQ(screenOnGroup.group_id(), - it->first.getStateValuesKey().getValues()[0].mValue.int_value); - assertConditionTimer(it->second.conditionTimer, true, 0, bucketStartTimeNs + 5 * NS_PER_SEC); - // Value for dimension, state key {{}, kStateUnknown} - it++; - EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); - ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); - EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, - it->first.getStateValuesKey().getValues()[0].mValue.int_value); - EXPECT_TRUE(it->second.intervals[0].hasValue); - EXPECT_EQ(2, it->second.intervals[0].value.long_value); - assertConditionTimer(it->second.conditionTimer, false, 5 * NS_PER_SEC, - bucketStartTimeNs + 5 * NS_PER_SEC); - - // Bucket status after screen state change ON->VR. - // Both ON and VR are in the same state group, so the base should not change. - screenEvent = CreateScreenStateChangedEvent(bucketStartTimeNs + 10 * NS_PER_SEC, - android::view::DisplayStateEnum::DISPLAY_STATE_VR); - StateManager::getInstance().onLogEvent(*screenEvent); - ASSERT_EQ(2UL, valueProducer->mCurrentSlicedBucket.size()); - // Base for dimension key {} - it = valueProducer->mCurrentSlicedBucket.begin(); - itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); - EXPECT_TRUE(itBase->second.baseInfos[0].hasBase); - EXPECT_EQ(5, itBase->second.baseInfos[0].base.long_value); - EXPECT_TRUE(itBase->second.hasCurrentState); - ASSERT_EQ(1, itBase->second.currentState.getValues().size()); - EXPECT_EQ(screenOnGroup.group_id(), - itBase->second.currentState.getValues()[0].mValue.int_value); - // Value for dimension, state key {{}, ON GROUP} - EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); - ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); - EXPECT_EQ(screenOnGroup.group_id(), - it->first.getStateValuesKey().getValues()[0].mValue.int_value); - assertConditionTimer(it->second.conditionTimer, true, 0, bucketStartTimeNs + 5 * NS_PER_SEC); - // Value for dimension, state key {{}, kStateUnknown} - it++; - EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); - ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); - EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, - it->first.getStateValuesKey().getValues()[0].mValue.int_value); - EXPECT_TRUE(it->second.intervals[0].hasValue); - EXPECT_EQ(2, it->second.intervals[0].value.long_value); - assertConditionTimer(it->second.conditionTimer, false, 5 * NS_PER_SEC, - bucketStartTimeNs + 5 * NS_PER_SEC); - - // Bucket status after screen state change VR->ON. - // Both ON and VR are in the same state group, so the base should not change. - screenEvent = CreateScreenStateChangedEvent(bucketStartTimeNs + 12 * NS_PER_SEC, - android::view::DisplayStateEnum::DISPLAY_STATE_ON); - StateManager::getInstance().onLogEvent(*screenEvent); - ASSERT_EQ(2UL, valueProducer->mCurrentSlicedBucket.size()); - // Base for dimension key {} - it = valueProducer->mCurrentSlicedBucket.begin(); - itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); - EXPECT_TRUE(itBase->second.baseInfos[0].hasBase); - EXPECT_EQ(5, itBase->second.baseInfos[0].base.long_value); - EXPECT_TRUE(itBase->second.hasCurrentState); - ASSERT_EQ(1, itBase->second.currentState.getValues().size()); - EXPECT_EQ(screenOnGroup.group_id(), - itBase->second.currentState.getValues()[0].mValue.int_value); - // Value for dimension, state key {{}, ON GROUP} - EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); - ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); - EXPECT_EQ(screenOnGroup.group_id(), - it->first.getStateValuesKey().getValues()[0].mValue.int_value); - assertConditionTimer(it->second.conditionTimer, true, 0, bucketStartTimeNs + 5 * NS_PER_SEC); - // Value for dimension, state key {{}, kStateUnknown} - it++; - EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); - ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); - EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, - it->first.getStateValuesKey().getValues()[0].mValue.int_value); - EXPECT_TRUE(it->second.intervals[0].hasValue); - EXPECT_EQ(2, it->second.intervals[0].value.long_value); - assertConditionTimer(it->second.conditionTimer, false, 5 * NS_PER_SEC, - bucketStartTimeNs + 5 * NS_PER_SEC); - - // Bucket status after screen state change VR->OFF. - screenEvent = CreateScreenStateChangedEvent(bucketStartTimeNs + 15 * NS_PER_SEC, - android::view::DisplayStateEnum::DISPLAY_STATE_OFF); - StateManager::getInstance().onLogEvent(*screenEvent); - ASSERT_EQ(3UL, valueProducer->mCurrentSlicedBucket.size()); - // Base for dimension key {} - it = valueProducer->mCurrentSlicedBucket.begin(); - itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); - EXPECT_TRUE(itBase->second.baseInfos[0].hasBase); - EXPECT_EQ(21, itBase->second.baseInfos[0].base.long_value); - EXPECT_TRUE(itBase->second.hasCurrentState); - ASSERT_EQ(1, itBase->second.currentState.getValues().size()); - EXPECT_EQ(screenOffGroup.group_id(), - itBase->second.currentState.getValues()[0].mValue.int_value); - // Value for dimension, state key {{}, OFF GROUP} - EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); - ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); - EXPECT_EQ(screenOffGroup.group_id(), - it->first.getStateValuesKey().getValues()[0].mValue.long_value); - assertConditionTimer(it->second.conditionTimer, true, 0, bucketStartTimeNs + 15 * NS_PER_SEC); - // Value for dimension, state key {{}, ON GROUP} - it++; - EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); - ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); - EXPECT_EQ(screenOnGroup.group_id(), - it->first.getStateValuesKey().getValues()[0].mValue.long_value); - EXPECT_TRUE(it->second.intervals[0].hasValue); - EXPECT_EQ(16, it->second.intervals[0].value.long_value); - assertConditionTimer(it->second.conditionTimer, false, 10 * NS_PER_SEC, - bucketStartTimeNs + 15 * NS_PER_SEC); - // Value for dimension, state key {{}, kStateUnknown} - it++; - EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); - ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); - EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, - it->first.getStateValuesKey().getValues()[0].mValue.int_value); - EXPECT_TRUE(it->second.intervals[0].hasValue); - EXPECT_EQ(2, it->second.intervals[0].value.long_value); - assertConditionTimer(it->second.conditionTimer, false, 5 * NS_PER_SEC, - bucketStartTimeNs + 5 * NS_PER_SEC); - - // Start dump report and check output. - ProtoOutputStream output; - std::set strSet; - valueProducer->onDumpReport(bucketStartTimeNs + 50 * NS_PER_SEC, - true /* include recent buckets */, true, NO_TIME_CONSTRAINTS, - &strSet, &output); - - StatsLogReport report = outputStreamToProto(&output); - EXPECT_TRUE(report.has_value_metrics()); - ASSERT_EQ(3, report.value_metrics().data_size()); - - // {{}, kStateUnknown} - auto data = report.value_metrics().data(0); - ASSERT_EQ(1, data.bucket_info_size()); - EXPECT_EQ(2, report.value_metrics().data(0).bucket_info(0).values(0).value_long()); - EXPECT_EQ(SCREEN_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); - EXPECT_TRUE(data.slice_by_state(0).has_value()); - EXPECT_EQ(-1 /*StateTracker::kStateUnknown*/, data.slice_by_state(0).value()); - EXPECT_EQ(5 * NS_PER_SEC, data.bucket_info(0).condition_true_nanos()); - - // {{}, ON GROUP} - data = report.value_metrics().data(1); - ASSERT_EQ(1, report.value_metrics().data(1).bucket_info_size()); - EXPECT_EQ(16, report.value_metrics().data(1).bucket_info(0).values(0).value_long()); - EXPECT_EQ(SCREEN_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); - EXPECT_TRUE(data.slice_by_state(0).has_group_id()); - EXPECT_EQ(screenOnGroup.group_id(), data.slice_by_state(0).group_id()); - EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).condition_true_nanos()); - - // {{}, OFF GROUP} - data = report.value_metrics().data(2); - ASSERT_EQ(1, report.value_metrics().data(2).bucket_info_size()); - EXPECT_EQ(9, report.value_metrics().data(2).bucket_info(0).values(0).value_long()); - EXPECT_EQ(SCREEN_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); - EXPECT_TRUE(data.slice_by_state(0).has_group_id()); - EXPECT_EQ(screenOffGroup.group_id(), data.slice_by_state(0).group_id()); - EXPECT_EQ(35 * NS_PER_SEC, data.bucket_info(0).condition_true_nanos()); -} - -/* - * Test metric that slices by state with a primary field and has dimensions - * - Increasing values - * - Using diff - * - Second field is value field - */ -TEST(ValueMetricProducerTest, TestSlicedStateWithPrimaryField_WithDimensions) { - // Set up ValueMetricProducer. - ValueMetric metric = ValueMetricProducerTestHelper::createMetricWithState("UID_PROCESS_STATE"); - metric.mutable_dimensions_in_what()->set_field(tagId); - metric.mutable_dimensions_in_what()->add_child()->set_field(1); - - MetricStateLink* stateLink = metric.add_state_link(); - stateLink->set_state_atom_id(UID_PROCESS_STATE_ATOM_ID); - auto fieldsInWhat = stateLink->mutable_fields_in_what(); - *fieldsInWhat = CreateDimensions(tagId, {1 /* uid */}); - auto fieldsInState = stateLink->mutable_fields_in_state(); - *fieldsInState = CreateDimensions(UID_PROCESS_STATE_ATOM_ID, {1 /* uid */}); - - /* - NOTE: "1" denotes uid 1 and "2" denotes uid 2. - bucket # 1 bucket # 2 - 10 20 30 40 50 60 70 80 90 100 110 120 (seconds) - |------------------------------------------|---------------------------------|-- - - (kStateUnknown) - 1 - |-------------| - 20 - - 2 - |----------------------------| - 40 - - (FOREGROUND) - 1 1 - |----------------------------|-------------| |------| - 40 20 10 - - - (BACKGROUND) - 1 - |------------| - 20 - 2 - |-------------|---------------------------------| - 20 50 - */ - sp pullerManager = new StrictMock(); - EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _)) - // ValueMetricProducer initialized. - .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, - vector>* data) { - EXPECT_EQ(eventTimeNs, bucketStartTimeNs); - data->clear(); - data->push_back(CreateTwoValueLogEvent(tagId, bucketStartTimeNs, 2 /*uid*/, 7)); - data->push_back(CreateTwoValueLogEvent(tagId, bucketStartTimeNs, 1 /*uid*/, 3)); - return true; - })) - // Uid 1 process state change from kStateUnknown -> Foreground - .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, - vector>* data) { - EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 20 * NS_PER_SEC); - data->clear(); - data->push_back(CreateTwoValueLogEvent(tagId, bucketStartTimeNs + 20 * NS_PER_SEC, - 1 /*uid*/, 6)); - - // This event should be skipped. - data->push_back(CreateTwoValueLogEvent(tagId, bucketStartTimeNs + 20 * NS_PER_SEC, - 2 /*uid*/, 8)); - return true; - })) - // Uid 2 process state change from kStateUnknown -> Background - .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, - vector>* data) { - EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 40 * NS_PER_SEC); - data->clear(); - data->push_back(CreateTwoValueLogEvent(tagId, bucketStartTimeNs + 40 * NS_PER_SEC, - 2 /*uid*/, 9)); - - // This event should be skipped. - data->push_back(CreateTwoValueLogEvent(tagId, bucketStartTimeNs + 40 * NS_PER_SEC, - 1 /*uid*/, 12)); - return true; - })) - // Uid 1 process state change from Foreground -> Background - .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, - vector>* data) { - EXPECT_EQ(eventTimeNs, bucket2StartTimeNs + 20 * NS_PER_SEC); - data->clear(); - data->push_back(CreateTwoValueLogEvent(tagId, bucket2StartTimeNs + 20 * NS_PER_SEC, - 1 /*uid*/, 13)); - - // This event should be skipped. - data->push_back(CreateTwoValueLogEvent(tagId, bucket2StartTimeNs + 20 * NS_PER_SEC, - 2 /*uid*/, 11)); - return true; - })) - // Uid 1 process state change from Background -> Foreground - .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, - vector>* data) { - EXPECT_EQ(eventTimeNs, bucket2StartTimeNs + 40 * NS_PER_SEC); - data->clear(); - data->push_back(CreateTwoValueLogEvent(tagId, bucket2StartTimeNs + 40 * NS_PER_SEC, - 1 /*uid*/, 17)); - - // This event should be skipped. - data->push_back(CreateTwoValueLogEvent(tagId, bucket2StartTimeNs + 40 * NS_PER_SEC, - 2 /*uid */, 15)); - return true; - })) - // Dump report pull. - .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, - vector>* data) { - EXPECT_EQ(eventTimeNs, bucket2StartTimeNs + 50 * NS_PER_SEC); - data->clear(); - data->push_back(CreateTwoValueLogEvent(tagId, bucket2StartTimeNs + 50 * NS_PER_SEC, - 2 /*uid*/, 20)); - data->push_back(CreateTwoValueLogEvent(tagId, bucket2StartTimeNs + 50 * NS_PER_SEC, - 1 /*uid*/, 21)); - return true; - })); - - StateManager::getInstance().clear(); - sp valueProducer = - ValueMetricProducerTestHelper::createValueProducerWithState( - pullerManager, metric, {UID_PROCESS_STATE_ATOM_ID}, {}); - - // Set up StateManager and check that StateTrackers are initialized. - StateManager::getInstance().registerListener(UID_PROCESS_STATE_ATOM_ID, valueProducer); - EXPECT_EQ(1, StateManager::getInstance().getStateTrackersCount()); - EXPECT_EQ(1, StateManager::getInstance().getListenersCount(UID_PROCESS_STATE_ATOM_ID)); - - // Bucket status after metric initialized. - ASSERT_EQ(2UL, valueProducer->mCurrentSlicedBucket.size()); - // Base for dimension key {uid 1}. - auto it = valueProducer->mCurrentSlicedBucket.begin(); - auto itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); - EXPECT_TRUE(itBase->second.baseInfos[0].hasBase); - EXPECT_EQ(3, itBase->second.baseInfos[0].base.long_value); - EXPECT_TRUE(itBase->second.hasCurrentState); - ASSERT_EQ(1, itBase->second.currentState.getValues().size()); - EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, - itBase->second.currentState.getValues()[0].mValue.int_value); - // Value for dimension, state key {{uid 1}, kStateUnknown} - ASSERT_EQ(1, it->first.getDimensionKeyInWhat().getValues().size()); - EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); - ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); - EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, - it->first.getStateValuesKey().getValues()[0].mValue.int_value); - EXPECT_FALSE(it->second.intervals[0].hasValue); - assertConditionTimer(it->second.conditionTimer, true, 0, bucketStartTimeNs); - // Base for dimension key {uid 2} - it++; - itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); - EXPECT_TRUE(itBase->second.baseInfos[0].hasBase); - EXPECT_EQ(7, itBase->second.baseInfos[0].base.long_value); - EXPECT_TRUE(itBase->second.hasCurrentState); - ASSERT_EQ(1, itBase->second.currentState.getValues().size()); - EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, - itBase->second.currentState.getValues()[0].mValue.int_value); - // Value for dimension, state key {{uid 2}, kStateUnknown} - ASSERT_EQ(1, it->first.getDimensionKeyInWhat().getValues().size()); - EXPECT_EQ(2, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); - ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); - EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, - it->first.getStateValuesKey().getValues()[0].mValue.int_value); - EXPECT_FALSE(it->second.intervals[0].hasValue); - assertConditionTimer(it->second.conditionTimer, true, 0, bucketStartTimeNs); - - // Bucket status after uid 1 process state change kStateUnknown -> Foreground. - auto uidProcessEvent = - CreateUidProcessStateChangedEvent(bucketStartTimeNs + 20 * NS_PER_SEC, 1 /* uid */, - android::app::PROCESS_STATE_IMPORTANT_FOREGROUND); - StateManager::getInstance().onLogEvent(*uidProcessEvent); - ASSERT_EQ(3UL, valueProducer->mCurrentSlicedBucket.size()); - // Base for dimension key {uid 1}. - it = valueProducer->mCurrentSlicedBucket.begin(); - itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); - EXPECT_TRUE(itBase->second.baseInfos[0].hasBase); - EXPECT_EQ(6, itBase->second.baseInfos[0].base.long_value); - EXPECT_TRUE(itBase->second.hasCurrentState); - ASSERT_EQ(1, itBase->second.currentState.getValues().size()); - EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_FOREGROUND, - itBase->second.currentState.getValues()[0].mValue.int_value); - // Value for key {uid 1, kStateUnknown}. - ASSERT_EQ(1, it->first.getDimensionKeyInWhat().getValues().size()); - EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); - ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); - EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, - it->first.getStateValuesKey().getValues()[0].mValue.int_value); - EXPECT_TRUE(it->second.intervals[0].hasValue); - EXPECT_EQ(3, it->second.intervals[0].value.long_value); - assertConditionTimer(it->second.conditionTimer, false, 20 * NS_PER_SEC, - bucketStartTimeNs + 20 * NS_PER_SEC); - // Value for key {uid 1, FOREGROUND}. - it++; - ASSERT_EQ(1, it->first.getDimensionKeyInWhat().getValues().size()); - EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); - ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); - EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_FOREGROUND, - it->first.getStateValuesKey().getValues()[0].mValue.int_value); - assertConditionTimer(it->second.conditionTimer, true, 0, bucketStartTimeNs + 20 * NS_PER_SEC); - - // Base for dimension key {uid 2}. - it++; - itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); - EXPECT_TRUE(itBase->second.baseInfos[0].hasBase); - EXPECT_EQ(7, itBase->second.baseInfos[0].base.long_value); - EXPECT_TRUE(itBase->second.hasCurrentState); - ASSERT_EQ(1, itBase->second.currentState.getValues().size()); - EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, - itBase->second.currentState.getValues()[0].mValue.int_value); - // Value for key {uid 2, kStateUnknown}. - ASSERT_EQ(1, it->first.getDimensionKeyInWhat().getValues().size()); - EXPECT_EQ(2, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); - ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); - EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, - it->first.getStateValuesKey().getValues()[0].mValue.int_value); - EXPECT_FALSE(it->second.intervals[0].hasValue); - assertConditionTimer(it->second.conditionTimer, true, 0, bucketStartTimeNs); - - // Bucket status after uid 2 process state change kStateUnknown -> Background. - uidProcessEvent = - CreateUidProcessStateChangedEvent(bucketStartTimeNs + 40 * NS_PER_SEC, 2 /* uid */, - android::app::PROCESS_STATE_IMPORTANT_BACKGROUND); - StateManager::getInstance().onLogEvent(*uidProcessEvent); - ASSERT_EQ(4UL, valueProducer->mCurrentSlicedBucket.size()); - // Base for dimension key {uid 2}. - it = valueProducer->mCurrentSlicedBucket.begin(); - itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); - EXPECT_TRUE(itBase->second.baseInfos[0].hasBase); - EXPECT_EQ(9, itBase->second.baseInfos[0].base.long_value); - EXPECT_TRUE(itBase->second.hasCurrentState); - ASSERT_EQ(1, itBase->second.currentState.getValues().size()); - EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND, - itBase->second.currentState.getValues()[0].mValue.int_value); - // Value for key {uid 2, BACKGROUND}. - ASSERT_EQ(1, it->first.getDimensionKeyInWhat().getValues().size()); - EXPECT_EQ(2, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); - ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); - EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND, - it->first.getStateValuesKey().getValues()[0].mValue.int_value); - assertConditionTimer(it->second.conditionTimer, true, 0, bucketStartTimeNs + 40 * NS_PER_SEC); - - // Base for dimension key {uid 1}. - it++; - itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); - EXPECT_TRUE(itBase->second.baseInfos[0].hasBase); - EXPECT_EQ(6, itBase->second.baseInfos[0].base.long_value); - EXPECT_TRUE(itBase->second.hasCurrentState); - ASSERT_EQ(1, itBase->second.currentState.getValues().size()); - EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_FOREGROUND, - itBase->second.currentState.getValues()[0].mValue.int_value); - // Value for key {uid 1, kStateUnknown}. - ASSERT_EQ(1, it->first.getDimensionKeyInWhat().getValues().size()); - EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); - ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); - EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, - it->first.getStateValuesKey().getValues()[0].mValue.int_value); - EXPECT_TRUE(it->second.intervals[0].hasValue); - EXPECT_EQ(3, it->second.intervals[0].value.long_value); - assertConditionTimer(it->second.conditionTimer, false, 20 * NS_PER_SEC, - bucketStartTimeNs + 20 * NS_PER_SEC); - - // Value for key {uid 1, FOREGROUND}. - it++; - ASSERT_EQ(1, it->first.getDimensionKeyInWhat().getValues().size()); - EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); - ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); - EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_FOREGROUND, - it->first.getStateValuesKey().getValues()[0].mValue.int_value); - assertConditionTimer(it->second.conditionTimer, true, 0, bucketStartTimeNs + 20 * NS_PER_SEC); - - // Value for key {uid 2, kStateUnknown} - it++; - ASSERT_EQ(1, it->first.getDimensionKeyInWhat().getValues().size()); - EXPECT_EQ(2, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); - ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); - EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, - it->first.getStateValuesKey().getValues()[0].mValue.int_value); - EXPECT_TRUE(it->second.intervals[0].hasValue); - EXPECT_EQ(2, it->second.intervals[0].value.long_value); - assertConditionTimer(it->second.conditionTimer, false, 40 * NS_PER_SEC, - bucketStartTimeNs + 40 * NS_PER_SEC); - - // Pull at end of first bucket. - vector> allData; - allData.push_back(CreateTwoValueLogEvent(tagId, bucket2StartTimeNs, 1 /*uid*/, 10)); - allData.push_back(CreateTwoValueLogEvent(tagId, bucket2StartTimeNs, 2 /*uid*/, 15)); - valueProducer->onDataPulled(allData, /** succeeds */ true, bucket2StartTimeNs + 1); - - // Buckets flushed after end of first bucket. - // None of the buckets should have a value. - ASSERT_EQ(4UL, valueProducer->mCurrentSlicedBucket.size()); - ASSERT_EQ(4UL, valueProducer->mPastBuckets.size()); - ASSERT_EQ(2UL, valueProducer->mCurrentBaseInfo.size()); - // Base for dimension key {uid 2}. - it = valueProducer->mCurrentSlicedBucket.begin(); - EXPECT_EQ(2, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); - itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); - EXPECT_TRUE(itBase->second.baseInfos[0].hasBase); - EXPECT_EQ(15, itBase->second.baseInfos[0].base.long_value); - EXPECT_TRUE(itBase->second.hasCurrentState); - ASSERT_EQ(1, itBase->second.currentState.getValues().size()); - EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND, - itBase->second.currentState.getValues()[0].mValue.int_value); - // Value for key {uid 2, BACKGROUND}. - ASSERT_EQ(1, it->first.getDimensionKeyInWhat().getValues().size()); - EXPECT_EQ(2, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); - ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); - EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND, - it->first.getStateValuesKey().getValues()[0].mValue.int_value); - EXPECT_FALSE(it->second.intervals[0].hasValue); - assertConditionTimer(it->second.conditionTimer, true, 0, bucket2StartTimeNs); - EXPECT_EQ(20 * NS_PER_SEC, valueProducer->mPastBuckets[it->first][0].mConditionTrueNs); - - // Base for dimension key {uid 1} - it++; - itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); - EXPECT_TRUE(itBase->second.baseInfos[0].hasBase); - EXPECT_EQ(10, itBase->second.baseInfos[0].base.long_value); - EXPECT_TRUE(itBase->second.hasCurrentState); - ASSERT_EQ(1, itBase->second.currentState.getValues().size()); - EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_FOREGROUND, - itBase->second.currentState.getValues()[0].mValue.int_value); - // Value for key {uid 1, kStateUnknown} - ASSERT_EQ(1, it->first.getDimensionKeyInWhat().getValues().size()); - EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); - ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); - EXPECT_EQ(-1 /* kStateTracker::kUnknown */, - it->first.getStateValuesKey().getValues()[0].mValue.int_value); - EXPECT_FALSE(it->second.intervals[0].hasValue); - assertConditionTimer(it->second.conditionTimer, false, 0, bucketStartTimeNs + 20 * NS_PER_SEC); - EXPECT_EQ(20 * NS_PER_SEC, valueProducer->mPastBuckets[it->first][0].mConditionTrueNs); - - // Value for key {uid 1, FOREGROUND} - it++; - ASSERT_EQ(1, it->first.getDimensionKeyInWhat().getValues().size()); - EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); - ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); - EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_FOREGROUND, - it->first.getStateValuesKey().getValues()[0].mValue.int_value); - EXPECT_FALSE(it->second.intervals[0].hasValue); - assertConditionTimer(it->second.conditionTimer, true, 0, bucket2StartTimeNs); - EXPECT_EQ(40 * NS_PER_SEC, valueProducer->mPastBuckets[it->first][0].mConditionTrueNs); - - // Value for key {uid 2, kStateUnknown} - it++; - ASSERT_EQ(1, it->first.getDimensionKeyInWhat().getValues().size()); - EXPECT_EQ(2, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); - ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); - EXPECT_EQ(-1 /* kStateTracker::kUnknown */, - it->first.getStateValuesKey().getValues()[0].mValue.int_value); - EXPECT_FALSE(it->second.intervals[0].hasValue); - assertConditionTimer(it->second.conditionTimer, false, 0, bucketStartTimeNs + 40 * NS_PER_SEC); - EXPECT_EQ(40 * NS_PER_SEC, valueProducer->mPastBuckets[it->first][0].mConditionTrueNs); - - // Bucket status after uid 1 process state change from Foreground -> Background. - uidProcessEvent = - CreateUidProcessStateChangedEvent(bucket2StartTimeNs + 20 * NS_PER_SEC, 1 /* uid */, - android::app::PROCESS_STATE_IMPORTANT_BACKGROUND); - StateManager::getInstance().onLogEvent(*uidProcessEvent); - - ASSERT_EQ(5UL, valueProducer->mCurrentSlicedBucket.size()); - ASSERT_EQ(4UL, valueProducer->mPastBuckets.size()); - ASSERT_EQ(2UL, valueProducer->mCurrentBaseInfo.size()); - // Base for dimension key {uid 2}. - it = valueProducer->mCurrentSlicedBucket.begin(); - itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); - EXPECT_TRUE(itBase->second.baseInfos[0].hasBase); - EXPECT_EQ(15, itBase->second.baseInfos[0].base.long_value); - EXPECT_TRUE(itBase->second.hasCurrentState); - ASSERT_EQ(1, itBase->second.currentState.getValues().size()); - EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND, - itBase->second.currentState.getValues()[0].mValue.int_value); - // Value for key {uid 2, BACKGROUND}. - ASSERT_EQ(1, it->first.getDimensionKeyInWhat().getValues().size()); - EXPECT_EQ(2, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); - ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); - EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND, - it->first.getStateValuesKey().getValues()[0].mValue.int_value); - EXPECT_FALSE(it->second.intervals[0].hasValue); - assertConditionTimer(it->second.conditionTimer, true, 0, bucket2StartTimeNs); - - // Base for dimension key {uid 1} - it++; - itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); - EXPECT_TRUE(itBase->second.baseInfos[0].hasBase); - EXPECT_EQ(13, itBase->second.baseInfos[0].base.long_value); - EXPECT_TRUE(itBase->second.hasCurrentState); - ASSERT_EQ(1, itBase->second.currentState.getValues().size()); - EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND, - itBase->second.currentState.getValues()[0].mValue.int_value); - // Value for key {uid 1, kStateUnknown} - ASSERT_EQ(1, it->first.getDimensionKeyInWhat().getValues().size()); - EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); - ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); - EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, - it->first.getStateValuesKey().getValues()[0].mValue.int_value); - EXPECT_FALSE(it->second.intervals[0].hasValue); - assertConditionTimer(it->second.conditionTimer, false, 0, bucketStartTimeNs + 20 * NS_PER_SEC); - - // Value for key {uid 1, BACKGROUND} - it++; - ASSERT_EQ(1, it->first.getDimensionKeyInWhat().getValues().size()); - EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); - ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); - EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND, - it->first.getStateValuesKey().getValues()[0].mValue.int_value); - assertConditionTimer(it->second.conditionTimer, true, 0, bucket2StartTimeNs + 20 * NS_PER_SEC); - - // Value for key {uid 1, FOREGROUND} - it++; - ASSERT_EQ(1, it->first.getDimensionKeyInWhat().getValues().size()); - EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); - ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); - EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_FOREGROUND, - it->first.getStateValuesKey().getValues()[0].mValue.int_value); - EXPECT_TRUE(it->second.intervals[0].hasValue); - EXPECT_EQ(3, it->second.intervals[0].value.long_value); - assertConditionTimer(it->second.conditionTimer, false, 20 * NS_PER_SEC, - bucket2StartTimeNs + 20 * NS_PER_SEC); - - // Value for key {uid 2, kStateUnknown} - it++; - ASSERT_EQ(1, it->first.getDimensionKeyInWhat().getValues().size()); - EXPECT_EQ(2, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); - ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); - EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, - it->first.getStateValuesKey().getValues()[0].mValue.int_value); - EXPECT_FALSE(it->second.intervals[0].hasValue); - assertConditionTimer(it->second.conditionTimer, false, 0, bucketStartTimeNs + 40 * NS_PER_SEC); - - // Bucket status after uid 1 process state change Background->Foreground. - uidProcessEvent = - CreateUidProcessStateChangedEvent(bucket2StartTimeNs + 40 * NS_PER_SEC, 1 /* uid */, - android::app::PROCESS_STATE_IMPORTANT_FOREGROUND); - StateManager::getInstance().onLogEvent(*uidProcessEvent); - - ASSERT_EQ(5UL, valueProducer->mCurrentSlicedBucket.size()); - ASSERT_EQ(2UL, valueProducer->mCurrentBaseInfo.size()); - // Base for dimension key {uid 2} - it = valueProducer->mCurrentSlicedBucket.begin(); - itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); - EXPECT_TRUE(itBase->second.baseInfos[0].hasBase); - EXPECT_EQ(15, itBase->second.baseInfos[0].base.long_value); - EXPECT_TRUE(itBase->second.hasCurrentState); - ASSERT_EQ(1, itBase->second.currentState.getValues().size()); - EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND, - itBase->second.currentState.getValues()[0].mValue.int_value); - // Value for key {uid 2, BACKGROUND} - ASSERT_EQ(1, it->first.getDimensionKeyInWhat().getValues().size()); - EXPECT_EQ(2, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); - ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); - EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND, - it->first.getStateValuesKey().getValues()[0].mValue.int_value); - EXPECT_FALSE(it->second.intervals[0].hasValue); - assertConditionTimer(it->second.conditionTimer, true, 0, bucket2StartTimeNs); - - // Base for dimension key {uid 1} - it++; - itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); - EXPECT_TRUE(itBase->second.baseInfos[0].hasBase); - EXPECT_EQ(17, itBase->second.baseInfos[0].base.long_value); - EXPECT_TRUE(itBase->second.hasCurrentState); - ASSERT_EQ(1, itBase->second.currentState.getValues().size()); - EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_FOREGROUND, - itBase->second.currentState.getValues()[0].mValue.int_value); - // Value for key {uid 1, kStateUnknown} - ASSERT_EQ(1, it->first.getDimensionKeyInWhat().getValues().size()); - EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); - ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); - EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, - it->first.getStateValuesKey().getValues()[0].mValue.int_value); - EXPECT_FALSE(it->second.intervals[0].hasValue); - assertConditionTimer(it->second.conditionTimer, false, 0, bucketStartTimeNs + 20 * NS_PER_SEC); - - // Value for key {uid 1, BACKGROUND} - it++; - ASSERT_EQ(1, it->first.getDimensionKeyInWhat().getValues().size()); - EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); - ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); - EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND, - it->first.getStateValuesKey().getValues()[0].mValue.int_value); - EXPECT_TRUE(it->second.intervals[0].hasValue); - EXPECT_EQ(4, it->second.intervals[0].value.long_value); - assertConditionTimer(it->second.conditionTimer, false, 20 * NS_PER_SEC, - bucket2StartTimeNs + 40 * NS_PER_SEC); - - // Value for key {uid 1, FOREGROUND} - it++; - ASSERT_EQ(1, it->first.getDimensionKeyInWhat().getValues().size()); - EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); - ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); - EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_FOREGROUND, - it->first.getStateValuesKey().getValues()[0].mValue.int_value); - EXPECT_TRUE(it->second.intervals[0].hasValue); - EXPECT_EQ(3, it->second.intervals[0].value.long_value); - assertConditionTimer(it->second.conditionTimer, true, 20 * NS_PER_SEC, - bucket2StartTimeNs + 40 * NS_PER_SEC); - - // Value for key {uid 2, kStateUnknown} - it++; - ASSERT_EQ(1, it->first.getDimensionKeyInWhat().getValues().size()); - EXPECT_EQ(2, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); - ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); - EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, - it->first.getStateValuesKey().getValues()[0].mValue.int_value); - assertConditionTimer(it->second.conditionTimer, false, 0, bucketStartTimeNs + 40 * NS_PER_SEC); - - // Start dump report and check output. - ProtoOutputStream output; - std::set strSet; - valueProducer->onDumpReport(bucket2StartTimeNs + 50 * NS_PER_SEC, - true /* include recent buckets */, true, NO_TIME_CONSTRAINTS, - &strSet, &output); - - StatsLogReport report = outputStreamToProto(&output); - EXPECT_TRUE(report.has_value_metrics()); - ASSERT_EQ(5, report.value_metrics().data_size()); - - // {uid 1, BACKGROUND} - auto data = report.value_metrics().data(0); - ASSERT_EQ(1, data.bucket_info_size()); - EXPECT_EQ(4, report.value_metrics().data(0).bucket_info(0).values(0).value_long()); - EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); - EXPECT_TRUE(data.slice_by_state(0).has_value()); - EXPECT_EQ(android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_BACKGROUND, - data.slice_by_state(0).value()); - EXPECT_EQ(20 * NS_PER_SEC, data.bucket_info(0).condition_true_nanos()); - - // {uid 2, kStateUnknown} - data = report.value_metrics().data(1); - ASSERT_EQ(1, report.value_metrics().data(1).bucket_info_size()); - EXPECT_EQ(2, report.value_metrics().data(1).bucket_info(0).values(0).value_long()); - EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); - EXPECT_TRUE(data.slice_by_state(0).has_value()); - EXPECT_EQ(-1 /*StateTracker::kStateUnknown*/, data.slice_by_state(0).value()); - EXPECT_EQ(40 * NS_PER_SEC, data.bucket_info(0).condition_true_nanos()); - - // {uid 1, FOREGROUND} - data = report.value_metrics().data(2); - EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); - EXPECT_TRUE(data.slice_by_state(0).has_value()); - EXPECT_EQ(android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_FOREGROUND, - data.slice_by_state(0).value()); - ASSERT_EQ(2, report.value_metrics().data(2).bucket_info_size()); - EXPECT_EQ(4, report.value_metrics().data(2).bucket_info(0).values(0).value_long()); - EXPECT_EQ(7, report.value_metrics().data(2).bucket_info(1).values(0).value_long()); - EXPECT_EQ(40 * NS_PER_SEC, data.bucket_info(0).condition_true_nanos()); - EXPECT_EQ(30 * NS_PER_SEC, data.bucket_info(1).condition_true_nanos()); - - // {uid 1, kStateUnknown} - data = report.value_metrics().data(3); - ASSERT_EQ(1, report.value_metrics().data(3).bucket_info_size()); - EXPECT_EQ(3, report.value_metrics().data(3).bucket_info(0).values(0).value_long()); - EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); - EXPECT_TRUE(data.slice_by_state(0).has_value()); - EXPECT_EQ(-1 /*StateTracker::kStateUnknown*/, data.slice_by_state(0).value()); - EXPECT_EQ(20 * NS_PER_SEC, data.bucket_info(0).condition_true_nanos()); - - // {uid 2, BACKGROUND} - data = report.value_metrics().data(4); - EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); - EXPECT_TRUE(data.slice_by_state(0).has_value()); - EXPECT_EQ(android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_BACKGROUND, - data.slice_by_state(0).value()); - ASSERT_EQ(2, report.value_metrics().data(4).bucket_info_size()); - EXPECT_EQ(6, report.value_metrics().data(4).bucket_info(0).values(0).value_long()); - EXPECT_EQ(5, report.value_metrics().data(4).bucket_info(1).values(0).value_long()); - EXPECT_EQ(20 * NS_PER_SEC, data.bucket_info(0).condition_true_nanos()); - EXPECT_EQ(50 * NS_PER_SEC, data.bucket_info(1).condition_true_nanos()); -} - -/* - * Test slicing condition_true_nanos by state for metric that slices by state when data is not - * present in pulled data during a state change. - */ -TEST(ValueMetricProducerTest, TestSlicedStateWithMissingDataInStateChange) { - // Set up ValueMetricProducer. - ValueMetric metric = - ValueMetricProducerTestHelper::createMetricWithState("BATTERY_SAVER_MODE_STATE"); - sp pullerManager = new StrictMock(); - /* - NOTE: "-" means that the data was not present in the pulled data. - - bucket # 1 - 10 20 30 40 50 60 (seconds) - |-------------------------------------------------------|-- - x (kStateUnknown) - |-----------| - 10 - - x x (ON) - |---------------------| |-----------| - 20 10 - - - (OFF) - */ - EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _)) - // ValueMetricProducer initialized. - .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, - vector>* data) { - EXPECT_EQ(eventTimeNs, bucketStartTimeNs); - data->clear(); - data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs, 3)); - return true; - })) - // Battery saver mode state changed to ON. - .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, - vector>* data) { - EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 10 * NS_PER_SEC); - data->clear(); - data->push_back( - CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 10 * NS_PER_SEC, 5)); - return true; - })) - // Battery saver mode state changed to OFF but data for dimension key {} is not present - // in the pulled data. - .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, - vector>* data) { - EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 30 * NS_PER_SEC); - data->clear(); - return true; - })) - // Battery saver mode state changed to ON. - .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, - vector>* data) { - EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 40 * NS_PER_SEC); - data->clear(); - data->push_back( - CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 40 * NS_PER_SEC, 7)); - return true; - })) - // Dump report pull. - .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, - vector>* data) { - EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 50 * NS_PER_SEC); - data->clear(); - data->push_back(CreateRepeatedValueLogEvent( - tagId, bucketStartTimeNs + 50 * NS_PER_SEC, 15)); - return true; - })); - - StateManager::getInstance().clear(); - sp valueProducer = - ValueMetricProducerTestHelper::createValueProducerWithState( - pullerManager, metric, {util::BATTERY_SAVER_MODE_STATE_CHANGED}, {}); - EXPECT_EQ(1, valueProducer->mSlicedStateAtoms.size()); - - // Set up StateManager and check that StateTrackers are initialized. - StateManager::getInstance().registerListener(util::BATTERY_SAVER_MODE_STATE_CHANGED, - valueProducer); - EXPECT_EQ(1, StateManager::getInstance().getStateTrackersCount()); - EXPECT_EQ(1, StateManager::getInstance().getListenersCount( - util::BATTERY_SAVER_MODE_STATE_CHANGED)); - - // Bucket status after metric initialized. - ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); - // Base for dimension key {} - auto it = valueProducer->mCurrentSlicedBucket.begin(); - auto itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); - EXPECT_TRUE(itBase->second.hasCurrentState); - ASSERT_EQ(1, itBase->second.currentState.getValues().size()); - EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, - itBase->second.currentState.getValues()[0].mValue.int_value); - // Value for dimension, state key {{}, kStateUnknown} - EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); - ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); - EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, - it->first.getStateValuesKey().getValues()[0].mValue.int_value); - assertConditionTimer(it->second.conditionTimer, true, 0, bucketStartTimeNs); - - // Bucket status after battery saver mode ON event. - unique_ptr batterySaverOnEvent = - CreateBatterySaverOnEvent(/*timestamp=*/bucketStartTimeNs + 10 * NS_PER_SEC); - StateManager::getInstance().onLogEvent(*batterySaverOnEvent); - - // Base for dimension key {} - - ASSERT_EQ(1UL, valueProducer->mCurrentBaseInfo.size()); - ASSERT_EQ(2UL, valueProducer->mCurrentSlicedBucket.size()); - it = valueProducer->mCurrentSlicedBucket.begin(); - itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); - EXPECT_TRUE(itBase->second.hasCurrentState); - ASSERT_EQ(1, itBase->second.currentState.getValues().size()); - EXPECT_EQ(BatterySaverModeStateChanged::ON, - itBase->second.currentState.getValues()[0].mValue.int_value); - // Value for key {{}, ON} - EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); - ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); - EXPECT_EQ(BatterySaverModeStateChanged::ON, - it->first.getStateValuesKey().getValues()[0].mValue.int_value); - assertConditionTimer(it->second.conditionTimer, true, 0, bucketStartTimeNs + 10 * NS_PER_SEC); - - // Value for key {{}, -1} - it++; - EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); - ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); - EXPECT_EQ(-1 /*StateTracker::kUnknown*/, - it->first.getStateValuesKey().getValues()[0].mValue.int_value); - assertConditionTimer(it->second.conditionTimer, false, 10 * NS_PER_SEC, - bucketStartTimeNs + 10 * NS_PER_SEC); - - // Bucket status after battery saver mode OFF event which is not present - // in the pulled data. - unique_ptr batterySaverOffEvent = - CreateBatterySaverOffEvent(/*timestamp=*/bucketStartTimeNs + 30 * NS_PER_SEC); - StateManager::getInstance().onLogEvent(*batterySaverOffEvent); - - // Base for dimension key {} - ASSERT_EQ(1UL, valueProducer->mCurrentBaseInfo.size()); - ASSERT_EQ(2UL, valueProducer->mCurrentSlicedBucket.size()); - it = valueProducer->mCurrentSlicedBucket.begin(); - itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); - EXPECT_FALSE(itBase->second.hasCurrentState); - ASSERT_EQ(1, itBase->second.currentState.getValues().size()); - EXPECT_EQ(BatterySaverModeStateChanged::ON, - itBase->second.currentState.getValues()[0].mValue.int_value); - // Value for key {{}, ON} - EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); - ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); - EXPECT_EQ(BatterySaverModeStateChanged::ON, - it->first.getStateValuesKey().getValues()[0].mValue.int_value); - assertConditionTimer(it->second.conditionTimer, false, 20 * NS_PER_SEC, - bucketStartTimeNs + 30 * NS_PER_SEC); - - // Value for key {{}, -1} - it++; - EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); - ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); - EXPECT_EQ(-1 /*StateTracker::kUnknown*/, - it->first.getStateValuesKey().getValues()[0].mValue.int_value); - assertConditionTimer(it->second.conditionTimer, false, 10 * NS_PER_SEC, - bucketStartTimeNs + 10 * NS_PER_SEC); - - // Bucket status after battery saver mode ON event. - batterySaverOnEvent = - CreateBatterySaverOnEvent(/*timestamp=*/bucketStartTimeNs + 40 * NS_PER_SEC); - StateManager::getInstance().onLogEvent(*batterySaverOnEvent); - - // Base for dimension key {} - ASSERT_EQ(1UL, valueProducer->mCurrentBaseInfo.size()); - ASSERT_EQ(2UL, valueProducer->mCurrentSlicedBucket.size()); - it = valueProducer->mCurrentSlicedBucket.begin(); - itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); - ASSERT_EQ(1, itBase->second.currentState.getValues().size()); - EXPECT_EQ(BatterySaverModeStateChanged::ON, - itBase->second.currentState.getValues()[0].mValue.int_value); - // Value for key {{}, ON} - EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); - ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); - EXPECT_EQ(BatterySaverModeStateChanged::ON, - it->first.getStateValuesKey().getValues()[0].mValue.int_value); - assertConditionTimer(it->second.conditionTimer, true, 20 * NS_PER_SEC, - bucketStartTimeNs + 40 * NS_PER_SEC); - - // Value for key {{}, -1} - it++; - EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); - ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); - EXPECT_EQ(-1 /*StateTracker::kUnknown*/, - it->first.getStateValuesKey().getValues()[0].mValue.int_value); - assertConditionTimer(it->second.conditionTimer, false, 10 * NS_PER_SEC, - bucketStartTimeNs + 10 * NS_PER_SEC); - - // Start dump report and check output. - ProtoOutputStream output; - std::set strSet; - valueProducer->onDumpReport(bucketStartTimeNs + 50 * NS_PER_SEC, - true /* include recent buckets */, true, NO_TIME_CONSTRAINTS, - &strSet, &output); - - StatsLogReport report = outputStreamToProto(&output); - EXPECT_TRUE(report.has_value_metrics()); - ASSERT_EQ(2, report.value_metrics().data_size()); - - // {{}, kStateUnknown} - ValueMetricData data = report.value_metrics().data(0); - EXPECT_EQ(util::BATTERY_SAVER_MODE_STATE_CHANGED, data.slice_by_state(0).atom_id()); - EXPECT_EQ(-1 /*StateTracker::kUnknown*/, data.slice_by_state(0).value()); - ASSERT_EQ(1, data.bucket_info_size()); - EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).condition_true_nanos()); - - // {{}, ON} - data = report.value_metrics().data(1); - EXPECT_EQ(util::BATTERY_SAVER_MODE_STATE_CHANGED, data.slice_by_state(0).atom_id()); - EXPECT_EQ(BatterySaverModeStateChanged::ON, data.slice_by_state(0).value()); - ASSERT_EQ(1, data.bucket_info_size()); - EXPECT_EQ(30 * NS_PER_SEC, data.bucket_info(0).condition_true_nanos()); -} - -/* - * Test for metric that slices by state when data is not present in pulled data - * during an event and then a flush occurs for the current bucket. With the new - * condition timer behavior, a "new" MetricDimensionKey is inserted into - * `mCurrentSlicedBucket` before intervals are closed/added to that new - * MetricDimensionKey. - */ -TEST(ValueMetricProducerTest, TestSlicedStateWithMissingDataThenFlushBucket) { - // Set up ValueMetricProducer. - ValueMetric metric = - ValueMetricProducerTestHelper::createMetricWithState("BATTERY_SAVER_MODE_STATE"); - sp pullerManager = new StrictMock(); - /* - NOTE: "-" means that the data was not present in the pulled data. - - bucket # 1 - 10 20 30 40 50 60 (seconds) - |-------------------------------------------------------|-- - - (kStateUnknown) - - - (ON) - */ - EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _)) - // ValueMetricProducer initialized but data for dimension key {} is not present - // in the pulled data.. - .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, - vector>* data) { - EXPECT_EQ(eventTimeNs, bucketStartTimeNs); - data->clear(); - return true; - })) - // Battery saver mode state changed to ON but data for dimension key {} is not present - // in the pulled data. - .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, - vector>* data) { - EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 10 * NS_PER_SEC); - data->clear(); - return true; - })) - // Dump report pull. - .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, - vector>* data) { - EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 50 * NS_PER_SEC); - data->clear(); - data->push_back(CreateRepeatedValueLogEvent( - tagId, bucketStartTimeNs + 50 * NS_PER_SEC, 15)); - return true; - })); - - StateManager::getInstance().clear(); - sp valueProducer = - ValueMetricProducerTestHelper::createValueProducerWithState( - pullerManager, metric, {util::BATTERY_SAVER_MODE_STATE_CHANGED}, {}); - EXPECT_EQ(1, valueProducer->mSlicedStateAtoms.size()); - - // Set up StateManager and check that StateTrackers are initialized. - StateManager::getInstance().registerListener(util::BATTERY_SAVER_MODE_STATE_CHANGED, - valueProducer); - EXPECT_EQ(1, StateManager::getInstance().getStateTrackersCount()); - EXPECT_EQ(1, StateManager::getInstance().getListenersCount( - util::BATTERY_SAVER_MODE_STATE_CHANGED)); - - // Bucket status after metric initialized. - ASSERT_EQ(0UL, valueProducer->mCurrentSlicedBucket.size()); - ASSERT_EQ(0UL, valueProducer->mCurrentBaseInfo.size()); - - // Bucket status after battery saver mode ON event which is not present - // in the pulled data. - unique_ptr batterySaverOnEvent = - CreateBatterySaverOnEvent(/*timestamp=*/bucketStartTimeNs + 10 * NS_PER_SEC); - StateManager::getInstance().onLogEvent(*batterySaverOnEvent); - - ASSERT_EQ(0UL, valueProducer->mCurrentBaseInfo.size()); - ASSERT_EQ(0UL, valueProducer->mCurrentSlicedBucket.size()); - - // Start dump report and check output. - ProtoOutputStream output; - std::set strSet; - valueProducer->onDumpReport(bucketStartTimeNs + 50 * NS_PER_SEC, - true /* include recent buckets */, true, NO_TIME_CONSTRAINTS, - &strSet, &output); - - StatsLogReport report = outputStreamToProto(&output); - EXPECT_TRUE(report.has_value_metrics()); - ASSERT_EQ(0, report.value_metrics().data_size()); - ASSERT_EQ(2UL, valueProducer->mCurrentSlicedBucket.size()); - ASSERT_EQ(1UL, valueProducer->mCurrentBaseInfo.size()); -} - -TEST(ValueMetricProducerTest, TestSlicedStateWithNoPullOnBucketBoundary) { - // Set up ValueMetricProducer. - ValueMetric metric = - ValueMetricProducerTestHelper::createMetricWithState("BATTERY_SAVER_MODE_STATE"); - sp pullerManager = new StrictMock(); - /* - bucket # 1 bucket # 2 - 10 20 30 40 50 60 70 80 90 100 110 120 (seconds) - |------------------------------------|---------------------------|-- - x (kStateUnknown) - |-----| - 10 - x x (ON) - |-----| |-----------| - 10 20 - x (OFF) - |------------------------| - 40 - */ - EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _)) - // ValueMetricProducer initialized. - .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, - vector>* data) { - EXPECT_EQ(eventTimeNs, bucketStartTimeNs); - data->clear(); - data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs, 3)); - return true; - })) - // Battery saver mode state changed to ON. - .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, - vector>* data) { - EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 10 * NS_PER_SEC); - data->clear(); - data->push_back( - CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 10 * NS_PER_SEC, 5)); - return true; - })) - // Battery saver mode state changed to OFF. - .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, - vector>* data) { - EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 20 * NS_PER_SEC); - data->clear(); - data->push_back( - CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 20 * NS_PER_SEC, 7)); - return true; - })) - // Battery saver mode state changed to ON. - .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, - vector>* data) { - EXPECT_EQ(eventTimeNs, bucket2StartTimeNs + 30 * NS_PER_SEC); - data->clear(); - data->push_back(CreateRepeatedValueLogEvent( - tagId, bucket2StartTimeNs + 30 * NS_PER_SEC, 10)); - return true; - })) - // Dump report pull. - .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, - vector>* data) { - EXPECT_EQ(eventTimeNs, bucket2StartTimeNs + 50 * NS_PER_SEC); - data->clear(); - data->push_back(CreateRepeatedValueLogEvent( - tagId, bucket2StartTimeNs + 50 * NS_PER_SEC, 15)); - return true; - })); - - StateManager::getInstance().clear(); - sp valueProducer = - ValueMetricProducerTestHelper::createValueProducerWithState( - pullerManager, metric, {util::BATTERY_SAVER_MODE_STATE_CHANGED}, {}); - EXPECT_EQ(1, valueProducer->mSlicedStateAtoms.size()); - - // Set up StateManager and check that StateTrackers are initialized. - StateManager::getInstance().registerListener(util::BATTERY_SAVER_MODE_STATE_CHANGED, - valueProducer); - EXPECT_EQ(1, StateManager::getInstance().getStateTrackersCount()); - EXPECT_EQ(1, StateManager::getInstance().getListenersCount( - util::BATTERY_SAVER_MODE_STATE_CHANGED)); - - // Bucket status after metric initialized. - ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); - ASSERT_EQ(1UL, valueProducer->mCurrentBaseInfo.size()); - auto it = valueProducer->mCurrentSlicedBucket.begin(); - auto itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); - EXPECT_TRUE(itBase->second.hasCurrentState); - ASSERT_EQ(1, itBase->second.currentState.getValues().size()); - EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, - itBase->second.currentState.getValues()[0].mValue.int_value); - // Value for dimension, state key {{}, kStateUnknown} - EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); - ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); - EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, - it->first.getStateValuesKey().getValues()[0].mValue.int_value); - assertConditionTimer(it->second.conditionTimer, true, 0, bucketStartTimeNs); - - // Bucket status after battery saver mode ON event. - unique_ptr batterySaverOnEvent = - CreateBatterySaverOnEvent(/*timestamp=*/bucketStartTimeNs + 10 * NS_PER_SEC); - StateManager::getInstance().onLogEvent(*batterySaverOnEvent); - - ASSERT_EQ(1UL, valueProducer->mCurrentBaseInfo.size()); - ASSERT_EQ(2UL, valueProducer->mCurrentSlicedBucket.size()); - it = valueProducer->mCurrentSlicedBucket.begin(); - itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); - ASSERT_EQ(1, itBase->second.currentState.getValues().size()); - EXPECT_EQ(BatterySaverModeStateChanged::ON, - itBase->second.currentState.getValues()[0].mValue.int_value); - // Value for key {{}, ON} - EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); - ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); - EXPECT_EQ(BatterySaverModeStateChanged::ON, - it->first.getStateValuesKey().getValues()[0].mValue.int_value); - assertConditionTimer(it->second.conditionTimer, true, 0, bucketStartTimeNs + 10 * NS_PER_SEC); - - // Value for key {{}, -1} - it++; - EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); - ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); - EXPECT_EQ(-1 /*StateTracker::kUnknown*/, - it->first.getStateValuesKey().getValues()[0].mValue.int_value); - assertConditionTimer(it->second.conditionTimer, false, 10 * NS_PER_SEC, - bucketStartTimeNs + 10 * NS_PER_SEC); - - // Bucket status after battery saver mode OFF event. - unique_ptr batterySaverOffEvent = - CreateBatterySaverOffEvent(/*timestamp=*/bucketStartTimeNs + 20 * NS_PER_SEC); - StateManager::getInstance().onLogEvent(*batterySaverOffEvent); - - ASSERT_EQ(1UL, valueProducer->mCurrentBaseInfo.size()); - ASSERT_EQ(3UL, valueProducer->mCurrentSlicedBucket.size()); - it = valueProducer->mCurrentSlicedBucket.begin(); - itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); - ASSERT_EQ(1, itBase->second.currentState.getValues().size()); - EXPECT_EQ(BatterySaverModeStateChanged::OFF, - itBase->second.currentState.getValues()[0].mValue.int_value); - // Value for key {{}, OFF} - EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); - ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); - EXPECT_EQ(BatterySaverModeStateChanged::OFF, - it->first.getStateValuesKey().getValues()[0].mValue.int_value); - assertConditionTimer(it->second.conditionTimer, true, 0, bucketStartTimeNs + 20 * NS_PER_SEC); - - // Value for key {{}, ON} - it++; - EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); - ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); - EXPECT_EQ(BatterySaverModeStateChanged::ON, - it->first.getStateValuesKey().getValues()[0].mValue.int_value); - assertConditionTimer(it->second.conditionTimer, false, 10 * NS_PER_SEC, - bucketStartTimeNs + 20 * NS_PER_SEC); - - // Value for key {{}, -1} - it++; - EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); - ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); - EXPECT_EQ(-1 /*StateTracker::kUnknown*/, - it->first.getStateValuesKey().getValues()[0].mValue.int_value); - assertConditionTimer(it->second.conditionTimer, false, 10 * NS_PER_SEC, - bucketStartTimeNs + 10 * NS_PER_SEC); - - // Bucket status after battery saver mode ON event. - batterySaverOnEvent = - CreateBatterySaverOnEvent(/*timestamp=*/bucket2StartTimeNs + 30 * NS_PER_SEC); - StateManager::getInstance().onLogEvent(*batterySaverOnEvent); - - ASSERT_EQ(1UL, valueProducer->mCurrentBaseInfo.size()); - ASSERT_EQ(3UL, valueProducer->mCurrentSlicedBucket.size()); - it = valueProducer->mCurrentSlicedBucket.begin(); - itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); - ASSERT_EQ(1, itBase->second.currentState.getValues().size()); - EXPECT_EQ(BatterySaverModeStateChanged::ON, - itBase->second.currentState.getValues()[0].mValue.int_value); - // Value for key {{}, OFF} - EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); - ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); - EXPECT_EQ(BatterySaverModeStateChanged::OFF, - it->first.getStateValuesKey().getValues()[0].mValue.int_value); - assertConditionTimer(it->second.conditionTimer, false, 30 * NS_PER_SEC, - bucket2StartTimeNs + 30 * NS_PER_SEC); - - // Value for key {{}, ON} - it++; - EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); - ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); - EXPECT_EQ(BatterySaverModeStateChanged::ON, - it->first.getStateValuesKey().getValues()[0].mValue.int_value); - assertConditionTimer(it->second.conditionTimer, true, 0, bucket2StartTimeNs + 30 * NS_PER_SEC); - - // Value for key {{}, -1} - it++; - EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); - ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); - EXPECT_EQ(-1 /*StateTracker::kUnknown*/, - it->first.getStateValuesKey().getValues()[0].mValue.int_value); - assertConditionTimer(it->second.conditionTimer, false, 0, bucketStartTimeNs + 10 * NS_PER_SEC); - - // Start dump report and check output. - ProtoOutputStream output; - std::set strSet; - valueProducer->onDumpReport(bucket2StartTimeNs + 50 * NS_PER_SEC, - true /* include recent buckets */, true, NO_TIME_CONSTRAINTS, - &strSet, &output); - - StatsLogReport report = outputStreamToProto(&output); - EXPECT_TRUE(report.has_value_metrics()); - ASSERT_EQ(3, report.value_metrics().data_size()); - - // {{}, kStateUnknown} - ValueMetricData data = report.value_metrics().data(0); - EXPECT_EQ(util::BATTERY_SAVER_MODE_STATE_CHANGED, data.slice_by_state(0).atom_id()); - EXPECT_EQ(-1 /*StateTracker::kUnknown*/, data.slice_by_state(0).value()); - ASSERT_EQ(1, data.bucket_info_size()); - EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).condition_true_nanos()); - - // {{}, ON} - data = report.value_metrics().data(1); - EXPECT_EQ(util::BATTERY_SAVER_MODE_STATE_CHANGED, data.slice_by_state(0).atom_id()); - EXPECT_EQ(BatterySaverModeStateChanged::ON, data.slice_by_state(0).value()); - ASSERT_EQ(2, data.bucket_info_size()); - EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).condition_true_nanos()); - EXPECT_EQ(20 * NS_PER_SEC, data.bucket_info(1).condition_true_nanos()); - - // {{}, OFF} - data = report.value_metrics().data(2); - EXPECT_EQ(util::BATTERY_SAVER_MODE_STATE_CHANGED, data.slice_by_state(0).atom_id()); - EXPECT_EQ(BatterySaverModeStateChanged::OFF, data.slice_by_state(0).value()); - ASSERT_EQ(1, data.bucket_info_size()); - EXPECT_EQ(40 * NS_PER_SEC, data.bucket_info(0).condition_true_nanos()); -} - -/* - * Test slicing condition_true_nanos by state for metric that slices by state when data is not - * present in pulled data during a condition change. - */ -TEST(ValueMetricProducerTest, TestSlicedStateWithDataMissingInConditionChange) { - // Set up ValueMetricProducer. - ValueMetric metric = ValueMetricProducerTestHelper::createMetricWithConditionAndState( - "BATTERY_SAVER_MODE_STATE"); - sp pullerManager = new StrictMock(); - /* - NOTE: "-" means that the data was not present in the pulled data. - - bucket # 1 - 10 20 30 40 50 60 (seconds) - |-------------------------------------------------------|-- - - T F T (Condition) - x (ON) - |----------------------| - - 20 - */ - EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _)) - // Battery saver mode state changed to ON. - .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, - vector>* data) { - EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 10 * NS_PER_SEC); - data->clear(); - data->push_back( - CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 10 * NS_PER_SEC, 3)); - return true; - })) - // Condition changed to false. - .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, - vector>* data) { - EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 30 * NS_PER_SEC); - data->clear(); - data->push_back( - CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 30 * NS_PER_SEC, 5)); - return true; - })) - // Condition changed to true but data for dimension key {} is not present in the - // pulled data. - .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, - vector>* data) { - EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 40 * NS_PER_SEC); - data->clear(); - return true; - })) - // Dump report pull. - .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, - vector>* data) { - EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 50 * NS_PER_SEC); - data->clear(); - data->push_back(CreateRepeatedValueLogEvent( - tagId, bucketStartTimeNs + 50 * NS_PER_SEC, 20)); - return true; - })); - - StateManager::getInstance().clear(); - sp valueProducer = - ValueMetricProducerTestHelper::createValueProducerWithConditionAndState( - pullerManager, metric, {util::BATTERY_SAVER_MODE_STATE_CHANGED}, {}, - ConditionState::kTrue); - EXPECT_EQ(1, valueProducer->mSlicedStateAtoms.size()); - - // Set up StateManager and check that StateTrackers are initialized. - StateManager::getInstance().registerListener(util::BATTERY_SAVER_MODE_STATE_CHANGED, - valueProducer); - EXPECT_EQ(1, StateManager::getInstance().getStateTrackersCount()); - EXPECT_EQ(1, StateManager::getInstance().getListenersCount( - util::BATTERY_SAVER_MODE_STATE_CHANGED)); - - // Bucket status after battery saver mode ON event. - unique_ptr batterySaverOnEvent = - CreateBatterySaverOnEvent(/*timestamp=*/bucketStartTimeNs + 10 * NS_PER_SEC); - StateManager::getInstance().onLogEvent(*batterySaverOnEvent); - // Base for dimension key {} - ASSERT_EQ(1UL, valueProducer->mCurrentBaseInfo.size()); - ASSERT_EQ(2UL, valueProducer->mCurrentSlicedBucket.size()); - auto it = valueProducer->mCurrentSlicedBucket.begin(); - auto itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); - EXPECT_TRUE(itBase->second.hasCurrentState); - ASSERT_EQ(1, itBase->second.currentState.getValues().size()); - EXPECT_EQ(BatterySaverModeStateChanged::ON, - itBase->second.currentState.getValues()[0].mValue.int_value); - // Value for key {{}, ON} - EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); - ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); - EXPECT_EQ(BatterySaverModeStateChanged::ON, - it->first.getStateValuesKey().getValues()[0].mValue.int_value); - assertConditionTimer(it->second.conditionTimer, true, 0, bucketStartTimeNs + 10 * NS_PER_SEC); - - // Value for key {{}, -1} - it++; - EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); - ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); - EXPECT_EQ(-1 /*StateTracker::kUnknown*/, - it->first.getStateValuesKey().getValues()[0].mValue.int_value); - assertConditionTimer(it->second.conditionTimer, false, 0, 0); - - // Bucket status after condition change to false. - valueProducer->onConditionChanged(false, bucketStartTimeNs + 30 * NS_PER_SEC); - // Base for dimension key {} - ASSERT_EQ(1UL, valueProducer->mCurrentBaseInfo.size()); - ASSERT_EQ(2UL, valueProducer->mCurrentSlicedBucket.size()); - it = valueProducer->mCurrentSlicedBucket.begin(); - itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); - EXPECT_TRUE(itBase->second.hasCurrentState); - ASSERT_EQ(1, itBase->second.currentState.getValues().size()); - EXPECT_EQ(BatterySaverModeStateChanged::ON, - itBase->second.currentState.getValues()[0].mValue.int_value); - // Value for key {{}, ON} - EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); - ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); - EXPECT_EQ(BatterySaverModeStateChanged::ON, - it->first.getStateValuesKey().getValues()[0].mValue.int_value); - assertConditionTimer(it->second.conditionTimer, false, 20 * NS_PER_SEC, - bucketStartTimeNs + 30 * NS_PER_SEC); - - // Value for key {{}, -1} - it++; - EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); - ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); - EXPECT_EQ(-1 /*StateTracker::kUnknown*/, - it->first.getStateValuesKey().getValues()[0].mValue.int_value); - assertConditionTimer(it->second.conditionTimer, false, 0, 0); - - // Bucket status after condition change to true. - valueProducer->onConditionChanged(true, bucketStartTimeNs + 40 * NS_PER_SEC); - // Base for dimension key {} - ASSERT_EQ(1UL, valueProducer->mCurrentBaseInfo.size()); - ASSERT_EQ(2UL, valueProducer->mCurrentSlicedBucket.size()); - it = valueProducer->mCurrentSlicedBucket.begin(); - itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); - EXPECT_FALSE(itBase->second.hasCurrentState); - ASSERT_EQ(1, itBase->second.currentState.getValues().size()); - EXPECT_EQ(BatterySaverModeStateChanged::ON, - itBase->second.currentState.getValues()[0].mValue.int_value); - // Value for key {{}, ON} - EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); - ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); - EXPECT_EQ(BatterySaverModeStateChanged::ON, - it->first.getStateValuesKey().getValues()[0].mValue.int_value); - assertConditionTimer(it->second.conditionTimer, false, 20 * NS_PER_SEC, - bucketStartTimeNs + 30 * NS_PER_SEC); - - // Value for key {{}, -1} - it++; - EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); - ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); - EXPECT_EQ(-1 /*StateTracker::kUnknown*/, - it->first.getStateValuesKey().getValues()[0].mValue.int_value); - assertConditionTimer(it->second.conditionTimer, false, 0, 0); - - // Start dump report and check output. - ProtoOutputStream output; - std::set strSet; - valueProducer->onDumpReport(bucketStartTimeNs + 50 * NS_PER_SEC, - true /* include recent buckets */, true, NO_TIME_CONSTRAINTS, - &strSet, &output); - - StatsLogReport report = outputStreamToProto(&output); - EXPECT_TRUE(report.has_value_metrics()); - ASSERT_EQ(1, report.value_metrics().data_size()); - - // {{}, ON} - ValueMetricData data = report.value_metrics().data(0); - EXPECT_EQ(util::BATTERY_SAVER_MODE_STATE_CHANGED, data.slice_by_state(0).atom_id()); - EXPECT_EQ(BatterySaverModeStateChanged::ON, data.slice_by_state(0).value()); - ASSERT_EQ(1, data.bucket_info_size()); - EXPECT_EQ(20 * NS_PER_SEC, data.bucket_info(0).condition_true_nanos()); -} - -/* - * Test slicing condition_true_nanos by state for metric that slices by state with a primary field, - * condition, and has multiple dimensions. - */ -TEST(ValueMetricProducerTest, TestSlicedStateWithMultipleDimensions) { - // Set up ValueMetricProducer. - ValueMetric metric = - ValueMetricProducerTestHelper::createMetricWithConditionAndState("UID_PROCESS_STATE"); - metric.mutable_dimensions_in_what()->set_field(tagId); - metric.mutable_dimensions_in_what()->add_child()->set_field(1); - metric.mutable_dimensions_in_what()->add_child()->set_field(3); - - MetricStateLink* stateLink = metric.add_state_link(); - stateLink->set_state_atom_id(UID_PROCESS_STATE_ATOM_ID); - auto fieldsInWhat = stateLink->mutable_fields_in_what(); - *fieldsInWhat = CreateDimensions(tagId, {1 /* uid */}); - auto fieldsInState = stateLink->mutable_fields_in_state(); - *fieldsInState = CreateDimensions(UID_PROCESS_STATE_ATOM_ID, {1 /* uid */}); - - /* - bucket # 1 bucket # 2 - 10 20 30 40 50 60 70 80 90 100 110 120 (seconds) - |------------------------------------------|---------------------------------|-- - - T F T (Condition) - (FOREGROUND) - x {1, 14} - |------| - 10 - - x {1, 16} - |------| - 10 - x {2, 8} - |-------------| - 20 - - (BACKGROUND) - x {1, 14} - |-------------| |----------|---------------------------------| - 20 15 50 - - x {1, 16} - |-------------| |----------|---------------------------------| - 20 15 50 - - x {2, 8} - |----------| |----------|-------------------| - 15 15 30 - */ - sp pullerManager = new StrictMock(); - EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _)) - // Uid 1 process state change from kStateUnknown -> Foreground - .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, - vector>* data) { - EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 10 * NS_PER_SEC); - data->clear(); - data->push_back(CreateThreeValueLogEvent(tagId, bucketStartTimeNs + 10 * NS_PER_SEC, - 1 /*uid*/, 3, 14 /*tag*/)); - data->push_back(CreateThreeValueLogEvent(tagId, bucketStartTimeNs + 10 * NS_PER_SEC, - 1 /*uid*/, 3, 16 /*tag*/)); - - // This event should be skipped. - data->push_back(CreateThreeValueLogEvent(tagId, bucketStartTimeNs + 10 * NS_PER_SEC, - 2 /*uid*/, 5, 8 /*tag*/)); - return true; - })) - // Uid 1 process state change from Foreground -> Background - .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, - vector>* data) { - EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 20 * NS_PER_SEC); - data->clear(); - data->push_back(CreateThreeValueLogEvent(tagId, bucketStartTimeNs + 20 * NS_PER_SEC, - 1 /*uid*/, 5, 14 /*tag*/)); - data->push_back(CreateThreeValueLogEvent(tagId, bucketStartTimeNs + 20 * NS_PER_SEC, - 1 /*uid*/, 5, 16 /*tag*/)); - - // This event should be skipped. - data->push_back(CreateThreeValueLogEvent(tagId, bucketStartTimeNs + 20 * NS_PER_SEC, - 2 /*uid*/, 7, 8 /*tag*/)); - - return true; - })) - // Uid 2 process state change from kStateUnknown -> Background - .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, - vector>* data) { - EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 25 * NS_PER_SEC); - data->clear(); - data->push_back(CreateThreeValueLogEvent(tagId, bucketStartTimeNs + 25 * NS_PER_SEC, - 2 /*uid*/, 9, 8 /*tag*/)); - - // This event should be skipped. - data->push_back(CreateThreeValueLogEvent(tagId, bucketStartTimeNs + 25 * NS_PER_SEC, - 1 /*uid*/, 9, 14 /* tag */)); - - // This event should be skipped. - data->push_back(CreateThreeValueLogEvent(tagId, bucketStartTimeNs + 25 * NS_PER_SEC, - 1 /*uid*/, 9, 16 /* tag */)); - - return true; - })) - // Condition changed to false. - .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, - vector>* data) { - EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 40 * NS_PER_SEC); - data->clear(); - data->push_back(CreateThreeValueLogEvent(tagId, bucketStartTimeNs + 40 * NS_PER_SEC, - 1 /*uid*/, 11, 14 /* tag */)); - data->push_back(CreateThreeValueLogEvent(tagId, bucketStartTimeNs + 40 * NS_PER_SEC, - 1 /*uid*/, 11, 16 /* tag */)); - data->push_back(CreateThreeValueLogEvent(tagId, bucketStartTimeNs + 40 * NS_PER_SEC, - 2 /*uid*/, 11, 8 /*tag*/)); - - return true; - })) - // Condition changed to true. - .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, - vector>* data) { - EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 45 * NS_PER_SEC); - data->clear(); - data->push_back(CreateThreeValueLogEvent(tagId, bucketStartTimeNs + 45 * NS_PER_SEC, - 1 /*uid*/, 13, 14 /* tag */)); - data->push_back(CreateThreeValueLogEvent(tagId, bucketStartTimeNs + 45 * NS_PER_SEC, - 1 /*uid*/, 13, 16 /* tag */)); - data->push_back(CreateThreeValueLogEvent(tagId, bucketStartTimeNs + 45 * NS_PER_SEC, - 2 /*uid*/, 13, 8 /*tag*/)); - return true; - })) - // Uid 2 process state change from Background -> Foreground - .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, - vector>* data) { - EXPECT_EQ(eventTimeNs, bucket2StartTimeNs + 30 * NS_PER_SEC); - data->clear(); - data->push_back(CreateThreeValueLogEvent( - tagId, bucket2StartTimeNs + 30 * NS_PER_SEC, 2 /*uid*/, 18, 8 /*tag*/)); - - // This event should be skipped. - data->push_back(CreateThreeValueLogEvent( - tagId, bucket2StartTimeNs + 30 * NS_PER_SEC, 1 /*uid*/, 18, 14 /* tag */)); - // This event should be skipped. - data->push_back(CreateThreeValueLogEvent( - tagId, bucket2StartTimeNs + 30 * NS_PER_SEC, 1 /*uid*/, 18, 16 /* tag */)); - - return true; - })) - // Dump report pull. - .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, - vector>* data) { - EXPECT_EQ(eventTimeNs, bucket2StartTimeNs + 50 * NS_PER_SEC); - data->clear(); - data->push_back(CreateThreeValueLogEvent( - tagId, bucket2StartTimeNs + 50 * NS_PER_SEC, 1 /*uid*/, 21, 14 /* tag */)); - data->push_back(CreateThreeValueLogEvent( - tagId, bucket2StartTimeNs + 50 * NS_PER_SEC, 1 /*uid*/, 21, 16 /* tag */)); - data->push_back(CreateThreeValueLogEvent( - tagId, bucket2StartTimeNs + 50 * NS_PER_SEC, 2 /*uid*/, 21, 8 /*tag*/)); - return true; - })); - - StateManager::getInstance().clear(); - sp valueProducer = - ValueMetricProducerTestHelper::createValueProducerWithConditionAndState( - pullerManager, metric, {UID_PROCESS_STATE_ATOM_ID}, {}, ConditionState::kTrue); - EXPECT_EQ(1, valueProducer->mSlicedStateAtoms.size()); - - // Set up StateManager and check that StateTrackers are initialized. - StateManager::getInstance().registerListener(UID_PROCESS_STATE_ATOM_ID, valueProducer); - EXPECT_EQ(1, StateManager::getInstance().getStateTrackersCount()); - EXPECT_EQ(1, StateManager::getInstance().getListenersCount(UID_PROCESS_STATE_ATOM_ID)); - - // Condition is true. - // Bucket status after uid 1 process state change kStateUnknown -> Foreground. - auto uidProcessEvent = - CreateUidProcessStateChangedEvent(bucketStartTimeNs + 10 * NS_PER_SEC, 1 /* uid */, - android::app::PROCESS_STATE_IMPORTANT_FOREGROUND); - StateManager::getInstance().onLogEvent(*uidProcessEvent); - ASSERT_EQ(2UL, valueProducer->mCurrentBaseInfo.size()); - ASSERT_EQ(4UL, valueProducer->mCurrentSlicedBucket.size()); - // Base for dimension {uid 1, tag 16}. - auto it = valueProducer->mCurrentSlicedBucket.begin(); - auto itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); - ASSERT_EQ(1, itBase->second.currentState.getValues().size()); - EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_FOREGROUND, - itBase->second.currentState.getValues()[0].mValue.int_value); - // Value for key {{uid 1, uid 16}, FOREGROUND}. - ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size()); - EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); - EXPECT_EQ(16, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value); - ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); - EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_FOREGROUND, - it->first.getStateValuesKey().getValues()[0].mValue.int_value); - assertConditionTimer(it->second.conditionTimer, true, 0, bucketStartTimeNs + 10 * NS_PER_SEC); - // Value for key {{uid 1, tag 16}, kStateUnknown}. - it++; - ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size()); - EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); - EXPECT_EQ(16, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value); - ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); - EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, - it->first.getStateValuesKey().getValues()[0].mValue.int_value); - assertConditionTimer(it->second.conditionTimer, false, 0, 0); - - // Base for dimension key {uid 1, tag 14}. - it++; - itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); - ASSERT_EQ(1, itBase->second.currentState.getValues().size()); - EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_FOREGROUND, - itBase->second.currentState.getValues()[0].mValue.int_value); - // Value for key {{uid 1, tag 14}, FOREGROUND}. - ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size()); - EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); - EXPECT_EQ(14, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value); - ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); - EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_FOREGROUND, - it->first.getStateValuesKey().getValues()[0].mValue.int_value); - assertConditionTimer(it->second.conditionTimer, true, 0, bucketStartTimeNs + 10 * NS_PER_SEC); - // Value for key {{uid 1, tag 14}, kStateUnknown}. - it++; - ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size()); - EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); - EXPECT_EQ(14, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value); - ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); - EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, - it->first.getStateValuesKey().getValues()[0].mValue.int_value); - assertConditionTimer(it->second.conditionTimer, false, 0, 0); - - // Bucket status after uid 1 process state change Foreground -> Background. - uidProcessEvent = - CreateUidProcessStateChangedEvent(bucketStartTimeNs + 20 * NS_PER_SEC, 1 /* uid */, - android::app::PROCESS_STATE_IMPORTANT_BACKGROUND); - StateManager::getInstance().onLogEvent(*uidProcessEvent); - ASSERT_EQ(2UL, valueProducer->mCurrentBaseInfo.size()); - ASSERT_EQ(6UL, valueProducer->mCurrentSlicedBucket.size()); - // Base for dimension {uid 1, tag 16}. - it = valueProducer->mCurrentSlicedBucket.begin(); - itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); - ASSERT_EQ(1, itBase->second.currentState.getValues().size()); - EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND, - itBase->second.currentState.getValues()[0].mValue.int_value); - // Value for key {{uid 1, uid 16}, BACKGROUND}. - ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size()); - EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); - EXPECT_EQ(16, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value); - ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); - EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND, - it->first.getStateValuesKey().getValues()[0].mValue.int_value); - assertConditionTimer(it->second.conditionTimer, true, 0, bucketStartTimeNs + 20 * NS_PER_SEC); - - // Base for dimension key {uid 1, tag 14}. - it++; - itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); - ASSERT_EQ(1, itBase->second.currentState.getValues().size()); - EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND, - itBase->second.currentState.getValues()[0].mValue.int_value); - // Value for key {{uid 1, tag 14}, BACKGROUND}. - ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size()); - EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); - EXPECT_EQ(14, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value); - ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); - EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND, - it->first.getStateValuesKey().getValues()[0].mValue.int_value); - assertConditionTimer(it->second.conditionTimer, true, 0, bucketStartTimeNs + 20 * NS_PER_SEC); - - // Value for key {{uid 1, uid 16}, FOREGROUND}. - it++; - ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size()); - EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); - EXPECT_EQ(16, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value); - ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); - EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_FOREGROUND, - it->first.getStateValuesKey().getValues()[0].mValue.int_value); - assertConditionTimer(it->second.conditionTimer, false, 10 * NS_PER_SEC, - bucketStartTimeNs + 20 * NS_PER_SEC); - - // Value for key {{uid 1, tag 16}, kStateUnknown}. - it++; - ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size()); - EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); - EXPECT_EQ(16, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value); - ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); - EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, - it->first.getStateValuesKey().getValues()[0].mValue.int_value); - assertConditionTimer(it->second.conditionTimer, false, 0, 0); - - // Value for key {{uid 1, tag 14}, FOREGROUND}. - it++; - ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size()); - EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); - EXPECT_EQ(14, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value); - ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); - EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_FOREGROUND, - it->first.getStateValuesKey().getValues()[0].mValue.int_value); - assertConditionTimer(it->second.conditionTimer, false, 10 * NS_PER_SEC, - bucketStartTimeNs + 20 * NS_PER_SEC); - - // Value for key {{uid 1, tag 14}, kStateUnknown}. - it++; - ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size()); - EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); - EXPECT_EQ(14, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value); - ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); - EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, - it->first.getStateValuesKey().getValues()[0].mValue.int_value); - assertConditionTimer(it->second.conditionTimer, false, 0, 0); - - // Bucket status after uid 2 process state change kStateUnknown -> Background. - uidProcessEvent = - CreateUidProcessStateChangedEvent(bucketStartTimeNs + 25 * NS_PER_SEC, 2 /* uid */, - android::app::PROCESS_STATE_IMPORTANT_BACKGROUND); - StateManager::getInstance().onLogEvent(*uidProcessEvent); - ASSERT_EQ(3UL, valueProducer->mCurrentBaseInfo.size()); - ASSERT_EQ(8UL, valueProducer->mCurrentSlicedBucket.size()); - // Base for dimension {uid 2, tag 8}. - it = valueProducer->mCurrentSlicedBucket.begin(); - itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); - ASSERT_EQ(1, itBase->second.currentState.getValues().size()); - EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND, - itBase->second.currentState.getValues()[0].mValue.int_value); - // Value for key {{uid 2, uid 8}, BACKGROUND}. - ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size()); - EXPECT_EQ(2, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); - EXPECT_EQ(8, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value); - ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); - EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND, - it->first.getStateValuesKey().getValues()[0].mValue.int_value); - assertConditionTimer(it->second.conditionTimer, true, 0, bucketStartTimeNs + 25 * NS_PER_SEC); - - // Value for key {{uid 2, uid 8}, kStateUnknown}. - it++; - ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size()); - EXPECT_EQ(2, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); - EXPECT_EQ(8, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value); - ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); - EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, - it->first.getStateValuesKey().getValues()[0].mValue.int_value); - assertConditionTimer(it->second.conditionTimer, false, 0, 0); - - // Base for dimension {uid 1, tag 16}. - it++; - itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); - ASSERT_EQ(1, itBase->second.currentState.getValues().size()); - EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND, - itBase->second.currentState.getValues()[0].mValue.int_value); - // Value for key {{uid 1, uid 16}, BACKGROUND}. - ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size()); - EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); - EXPECT_EQ(16, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value); - ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); - EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND, - it->first.getStateValuesKey().getValues()[0].mValue.int_value); - assertConditionTimer(it->second.conditionTimer, true, 0, bucketStartTimeNs + 20 * NS_PER_SEC); - - // Base for dimension key {uid 1, tag 14}. - it++; - itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); - ASSERT_EQ(1, itBase->second.currentState.getValues().size()); - EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND, - itBase->second.currentState.getValues()[0].mValue.int_value); - // Value for key {{uid 1, tag 14}, BACKGROUND}. - ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size()); - EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); - EXPECT_EQ(14, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value); - ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); - EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND, - it->first.getStateValuesKey().getValues()[0].mValue.int_value); - assertConditionTimer(it->second.conditionTimer, true, 0, bucketStartTimeNs + 20 * NS_PER_SEC); - - // Value for key {{uid 1, uid 16}, FOREGROUND}. - it++; - ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size()); - EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); - EXPECT_EQ(16, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value); - ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); - EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_FOREGROUND, - it->first.getStateValuesKey().getValues()[0].mValue.int_value); - assertConditionTimer(it->second.conditionTimer, false, 10 * NS_PER_SEC, - bucketStartTimeNs + 20 * NS_PER_SEC); - - // Value for key {{uid 1, tag 16}, kStateUnknown}. - it++; - ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size()); - EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); - EXPECT_EQ(16, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value); - ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); - EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, - it->first.getStateValuesKey().getValues()[0].mValue.int_value); - assertConditionTimer(it->second.conditionTimer, false, 0, 0); - - // Value for key {{uid 1, tag 14}, FOREGROUND}. - it++; - ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size()); - EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); - EXPECT_EQ(14, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value); - ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); - EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_FOREGROUND, - it->first.getStateValuesKey().getValues()[0].mValue.int_value); - assertConditionTimer(it->second.conditionTimer, false, 10 * NS_PER_SEC, - bucketStartTimeNs + 20 * NS_PER_SEC); - - // Value for key {{uid 1, tag 14}, kStateUnknown}. - it++; - ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size()); - EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); - EXPECT_EQ(14, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value); - ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); - EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, - it->first.getStateValuesKey().getValues()[0].mValue.int_value); - assertConditionTimer(it->second.conditionTimer, false, 0, 0); - - // Bucket 1 status after condition change to false. - // All condition timers should be turned off. - valueProducer->onConditionChanged(false, bucketStartTimeNs + 40 * NS_PER_SEC); - ASSERT_EQ(3UL, valueProducer->mCurrentBaseInfo.size()); - ASSERT_EQ(8UL, valueProducer->mCurrentSlicedBucket.size()); - // Base for dimension {uid 2, tag 8}. - it = valueProducer->mCurrentSlicedBucket.begin(); - itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); - ASSERT_EQ(1, itBase->second.currentState.getValues().size()); - EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND, - itBase->second.currentState.getValues()[0].mValue.int_value); - // Value for key {{uid 2, uid 8}, BACKGROUND}. - ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size()); - EXPECT_EQ(2, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); - EXPECT_EQ(8, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value); - ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); - EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND, - it->first.getStateValuesKey().getValues()[0].mValue.int_value); - assertConditionTimer(it->second.conditionTimer, false, 15 * NS_PER_SEC, - bucketStartTimeNs + 40 * NS_PER_SEC); - - // Value for key {{uid 2, uid 8}, kStateUnknown}. - it++; - ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size()); - EXPECT_EQ(2, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); - EXPECT_EQ(8, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value); - ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); - EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, - it->first.getStateValuesKey().getValues()[0].mValue.int_value); - assertConditionTimer(it->second.conditionTimer, false, 0, 0); - - // Base for dimension {uid 1, tag 16}. - it++; - itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); - ASSERT_EQ(1, itBase->second.currentState.getValues().size()); - EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND, - itBase->second.currentState.getValues()[0].mValue.int_value); - // Value for key {{uid 1, uid 16}, BACKGROUND}. - ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size()); - EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); - EXPECT_EQ(16, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value); - ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); - EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND, - it->first.getStateValuesKey().getValues()[0].mValue.int_value); - assertConditionTimer(it->second.conditionTimer, false, 20 * NS_PER_SEC, - bucketStartTimeNs + 40 * NS_PER_SEC); - - // Base for dimension key {uid 1, tag 14}. - it++; - itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); - ASSERT_EQ(1, itBase->second.currentState.getValues().size()); - EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND, - itBase->second.currentState.getValues()[0].mValue.int_value); - // Value for key {{uid 1, tag 14}, BACKGROUND}. - ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size()); - EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); - EXPECT_EQ(14, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value); - ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); - EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND, - it->first.getStateValuesKey().getValues()[0].mValue.int_value); - assertConditionTimer(it->second.conditionTimer, false, 20 * NS_PER_SEC, - bucketStartTimeNs + 40 * NS_PER_SEC); - - // Value for key {{uid 1, uid 16}, FOREGROUND}. - it++; - ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size()); - EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); - EXPECT_EQ(16, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value); - ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); - EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_FOREGROUND, - it->first.getStateValuesKey().getValues()[0].mValue.int_value); - assertConditionTimer(it->second.conditionTimer, false, 10 * NS_PER_SEC, - bucketStartTimeNs + 20 * NS_PER_SEC); - - // Value for key {{uid 1, tag 16}, kStateUnknown}. - it++; - ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size()); - EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); - EXPECT_EQ(16, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value); - ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); - EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, - it->first.getStateValuesKey().getValues()[0].mValue.int_value); - assertConditionTimer(it->second.conditionTimer, false, 0, 0); - - // Value for key {{uid 1, tag 14}, FOREGROUND}. - it++; - ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size()); - EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); - EXPECT_EQ(14, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value); - ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); - EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_FOREGROUND, - it->first.getStateValuesKey().getValues()[0].mValue.int_value); - assertConditionTimer(it->second.conditionTimer, false, 10 * NS_PER_SEC, - bucketStartTimeNs + 20 * NS_PER_SEC); - - // Value for key {{uid 1, tag 14}, kStateUnknown}. - it++; - ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size()); - EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); - EXPECT_EQ(14, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value); - ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); - EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, - it->first.getStateValuesKey().getValues()[0].mValue.int_value); - assertConditionTimer(it->second.conditionTimer, false, 0, 0); - - // Bucket 1 status after condition change to true. - valueProducer->onConditionChanged(true, bucketStartTimeNs + 45 * NS_PER_SEC); - ASSERT_EQ(3UL, valueProducer->mCurrentBaseInfo.size()); - ASSERT_EQ(8UL, valueProducer->mCurrentSlicedBucket.size()); - // Base for dimension {uid 2, tag 8}. - it = valueProducer->mCurrentSlicedBucket.begin(); - itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); - ASSERT_EQ(1, itBase->second.currentState.getValues().size()); - EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND, - itBase->second.currentState.getValues()[0].mValue.int_value); - // Value for key {{uid 2, uid 8}, BACKGROUND}. - ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size()); - EXPECT_EQ(2, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); - EXPECT_EQ(8, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value); - ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); - EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND, - it->first.getStateValuesKey().getValues()[0].mValue.int_value); - assertConditionTimer(it->second.conditionTimer, true, 15 * NS_PER_SEC, - bucketStartTimeNs + 45 * NS_PER_SEC); - - // Value for key {{uid 2, uid 8}, kStateUnknown}. - it++; - ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size()); - EXPECT_EQ(2, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); - EXPECT_EQ(8, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value); - ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); - EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, - it->first.getStateValuesKey().getValues()[0].mValue.int_value); - assertConditionTimer(it->second.conditionTimer, false, 0, 0); - - // Base for dimension {uid 1, tag 16}. - it++; - itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); - ASSERT_EQ(1, itBase->second.currentState.getValues().size()); - EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND, - itBase->second.currentState.getValues()[0].mValue.int_value); - // Value for key {{uid 1, uid 16}, BACKGROUND}. - ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size()); - EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); - EXPECT_EQ(16, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value); - ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); - EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND, - it->first.getStateValuesKey().getValues()[0].mValue.int_value); - assertConditionTimer(it->second.conditionTimer, true, 20 * NS_PER_SEC, - bucketStartTimeNs + 45 * NS_PER_SEC); - - // Base for dimension key {uid 1, tag 14}. - it++; - itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); - EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND, - itBase->second.currentState.getValues()[0].mValue.int_value); - // Value for key {{uid 1, tag 14}, BACKGROUND}. - ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size()); - EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); - EXPECT_EQ(14, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value); - ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); - EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND, - it->first.getStateValuesKey().getValues()[0].mValue.int_value); - assertConditionTimer(it->second.conditionTimer, true, 20 * NS_PER_SEC, - bucketStartTimeNs + 45 * NS_PER_SEC); - - // Value for key {{uid 1, uid 16}, FOREGROUND}. - it++; - ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size()); - EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); - EXPECT_EQ(16, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value); - ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); - EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_FOREGROUND, - it->first.getStateValuesKey().getValues()[0].mValue.int_value); - assertConditionTimer(it->second.conditionTimer, false, 10 * NS_PER_SEC, - bucketStartTimeNs + 20 * NS_PER_SEC); - - // Value for key {{uid 1, tag 16}, kStateUnknown}. - it++; - ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size()); - EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); - EXPECT_EQ(16, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value); - ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); - EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, - it->first.getStateValuesKey().getValues()[0].mValue.int_value); - assertConditionTimer(it->second.conditionTimer, false, 0, 0); - - // Value for key {{uid 1, tag 14}, FOREGROUND}. - it++; - ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size()); - EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); - EXPECT_EQ(14, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value); - ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); - EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_FOREGROUND, - it->first.getStateValuesKey().getValues()[0].mValue.int_value); - assertConditionTimer(it->second.conditionTimer, false, 10 * NS_PER_SEC, - bucketStartTimeNs + 20 * NS_PER_SEC); - - // Value for key {{uid 1, tag 14}, kStateUnknown}. - it++; - ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size()); - EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); - EXPECT_EQ(14, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value); - ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); - EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, - it->first.getStateValuesKey().getValues()[0].mValue.int_value); - assertConditionTimer(it->second.conditionTimer, false, 0, 0); - - // Pull at end of first bucket. - vector> allData; - allData.push_back( - CreateThreeValueLogEvent(tagId, bucket2StartTimeNs, 1 /*uid*/, 13, 14 /* tag */)); - allData.push_back( - CreateThreeValueLogEvent(tagId, bucket2StartTimeNs, 1 /*uid*/, 13, 16 /* tag */)); - allData.push_back( - CreateThreeValueLogEvent(tagId, bucket2StartTimeNs, 2 /*uid*/, 13, 8 /*tag*/)); - valueProducer->onDataPulled(allData, /** succeeds */ true, bucket2StartTimeNs + 1); - - // Buckets flushed after end of first bucket. - // All condition timers' behavior should rollover to bucket 2. - ASSERT_EQ(8UL, valueProducer->mCurrentSlicedBucket.size()); - ASSERT_EQ(5UL, valueProducer->mPastBuckets.size()); - ASSERT_EQ(3UL, valueProducer->mCurrentBaseInfo.size()); - // Base for dimension {uid 2, tag 8}. - it = valueProducer->mCurrentSlicedBucket.begin(); - itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); - ASSERT_EQ(1, itBase->second.currentState.getValues().size()); - EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND, - itBase->second.currentState.getValues()[0].mValue.int_value); - // Value for key {{uid 2, uid 8}, BACKGROUND}. - ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size()); - EXPECT_EQ(2, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); - EXPECT_EQ(8, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value); - ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); - EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND, - it->first.getStateValuesKey().getValues()[0].mValue.int_value); - assertConditionTimer(it->second.conditionTimer, true, 0, bucket2StartTimeNs); - ASSERT_EQ(1, valueProducer->mPastBuckets[it->first].size()); - EXPECT_EQ(30 * NS_PER_SEC, valueProducer->mPastBuckets[it->first][0].mConditionTrueNs); - - // Value for key {{uid 2, uid 8}, kStateUnknown}. - it++; - ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size()); - EXPECT_EQ(2, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); - EXPECT_EQ(8, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value); - ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); - EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, - it->first.getStateValuesKey().getValues()[0].mValue.int_value); - assertConditionTimer(it->second.conditionTimer, false, 0, 0); - - // Base for dimension {uid 1, tag 16}. - it++; - itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); - ASSERT_EQ(1, itBase->second.currentState.getValues().size()); - EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND, - itBase->second.currentState.getValues()[0].mValue.int_value); - // Value for key {{uid 1, uid 16}, BACKGROUND}. - ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size()); - EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); - EXPECT_EQ(16, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value); - ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); - EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND, - it->first.getStateValuesKey().getValues()[0].mValue.int_value); - assertConditionTimer(it->second.conditionTimer, true, 0, bucket2StartTimeNs); - ASSERT_EQ(1, valueProducer->mPastBuckets[it->first].size()); - EXPECT_EQ(35 * NS_PER_SEC, valueProducer->mPastBuckets[it->first][0].mConditionTrueNs); - - // Base for dimension key {uid 1, tag 14}. - it++; - itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); - EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND, - itBase->second.currentState.getValues()[0].mValue.int_value); - // Value for key {{uid 1, tag 14}, BACKGROUND}. - ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size()); - EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); - EXPECT_EQ(14, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value); - ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); - EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND, - it->first.getStateValuesKey().getValues()[0].mValue.int_value); - assertConditionTimer(it->second.conditionTimer, true, 0, bucket2StartTimeNs); - ASSERT_EQ(1, valueProducer->mPastBuckets[it->first].size()); - EXPECT_EQ(35 * NS_PER_SEC, valueProducer->mPastBuckets[it->first][0].mConditionTrueNs); - - // Value for key {{uid 1, uid 16}, FOREGROUND}. - it++; - ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size()); - EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); - EXPECT_EQ(16, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value); - ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); - EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_FOREGROUND, - it->first.getStateValuesKey().getValues()[0].mValue.int_value); - assertConditionTimer(it->second.conditionTimer, false, 0, bucketStartTimeNs + 20 * NS_PER_SEC); - ASSERT_EQ(1, valueProducer->mPastBuckets[it->first].size()); - EXPECT_EQ(10 * NS_PER_SEC, valueProducer->mPastBuckets[it->first][0].mConditionTrueNs); - - // Value for key {{uid 1, tag 16}, kStateUnknown}. - it++; - ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size()); - EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); - EXPECT_EQ(16, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value); - ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); - EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, - it->first.getStateValuesKey().getValues()[0].mValue.int_value); - assertConditionTimer(it->second.conditionTimer, false, 0, 0); - - // Value for key {{uid 1, tag 14}, FOREGROUND}. - it++; - ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size()); - EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); - EXPECT_EQ(14, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value); - ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); - EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_FOREGROUND, - it->first.getStateValuesKey().getValues()[0].mValue.int_value); - assertConditionTimer(it->second.conditionTimer, false, 0, bucketStartTimeNs + 20 * NS_PER_SEC); - ASSERT_EQ(1, valueProducer->mPastBuckets[it->first].size()); - EXPECT_EQ(10 * NS_PER_SEC, valueProducer->mPastBuckets[it->first][0].mConditionTrueNs); - - // Value for key {{uid 1, tag 14}, kStateUnknown}. - it++; - ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size()); - EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); - EXPECT_EQ(14, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value); - ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); - EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, - it->first.getStateValuesKey().getValues()[0].mValue.int_value); - assertConditionTimer(it->second.conditionTimer, false, 0, 0); - - // Bucket 2 status after uid 2 process state change Background->Foreground. - uidProcessEvent = - CreateUidProcessStateChangedEvent(bucket2StartTimeNs + 30 * NS_PER_SEC, 2 /* uid */, - android::app::PROCESS_STATE_IMPORTANT_FOREGROUND); - StateManager::getInstance().onLogEvent(*uidProcessEvent); - - ASSERT_EQ(9UL, valueProducer->mCurrentSlicedBucket.size()); - ASSERT_EQ(3UL, valueProducer->mCurrentBaseInfo.size()); - // Base for dimension {uid 2, tag 8}. - it = valueProducer->mCurrentSlicedBucket.begin(); - itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); - ASSERT_EQ(1, itBase->second.currentState.getValues().size()); - EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_FOREGROUND, - itBase->second.currentState.getValues()[0].mValue.int_value); - // Value for key {{uid 2, uid 8}, FOREGROUND}. - ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size()); - EXPECT_EQ(2, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); - EXPECT_EQ(8, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value); - ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); - EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_FOREGROUND, - it->first.getStateValuesKey().getValues()[0].mValue.int_value); - assertConditionTimer(it->second.conditionTimer, true, 0, bucket2StartTimeNs + 30 * NS_PER_SEC); - - // Value for key {{uid 2, uid 8}, BACKGROUND}. - it++; - ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size()); - EXPECT_EQ(2, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); - EXPECT_EQ(8, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value); - ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); - EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND, - it->first.getStateValuesKey().getValues()[0].mValue.int_value); - assertConditionTimer(it->second.conditionTimer, false, 30 * NS_PER_SEC, - bucket2StartTimeNs + 30 * NS_PER_SEC); - - // Value for key {{uid 2, uid 8}, kStateUnknown}. - it++; - ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size()); - EXPECT_EQ(2, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); - EXPECT_EQ(8, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value); - ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); - EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, - it->first.getStateValuesKey().getValues()[0].mValue.int_value); - assertConditionTimer(it->second.conditionTimer, false, 0, 0); - - // Base for dimension {uid 1, tag 16}. - it++; - itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); - ASSERT_EQ(1, itBase->second.currentState.getValues().size()); - EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND, - itBase->second.currentState.getValues()[0].mValue.int_value); - // Value for key {{uid 1, uid 16}, BACKGROUND}. - ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size()); - EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); - EXPECT_EQ(16, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value); - ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); - EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND, - it->first.getStateValuesKey().getValues()[0].mValue.int_value); - assertConditionTimer(it->second.conditionTimer, true, 0, bucket2StartTimeNs); - - // Base for dimension key {uid 1, tag 14}. - it++; - itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); - ASSERT_EQ(1, itBase->second.currentState.getValues().size()); - EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND, - itBase->second.currentState.getValues()[0].mValue.int_value); - // Value for key {{uid 1, tag 14}, BACKGROUND}. - ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size()); - EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); - EXPECT_EQ(14, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value); - ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); - EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND, - it->first.getStateValuesKey().getValues()[0].mValue.int_value); - assertConditionTimer(it->second.conditionTimer, true, 0, bucket2StartTimeNs); - - // Value for key {{uid 1, uid 16}, FOREGROUND}. - it++; - ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size()); - EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); - EXPECT_EQ(16, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value); - ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); - EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_FOREGROUND, - it->first.getStateValuesKey().getValues()[0].mValue.int_value); - assertConditionTimer(it->second.conditionTimer, false, 0, bucketStartTimeNs + 20 * NS_PER_SEC); - - // Value for key {{uid 1, tag 16}, kStateUnknown}. - it++; - ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size()); - EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); - EXPECT_EQ(16, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value); - ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); - EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, - it->first.getStateValuesKey().getValues()[0].mValue.int_value); - assertConditionTimer(it->second.conditionTimer, false, 0, 0); - - // Value for key {{uid 1, tag 14}, FOREGROUND}. - it++; - ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size()); - EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); - EXPECT_EQ(14, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value); - ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); - EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_FOREGROUND, - it->first.getStateValuesKey().getValues()[0].mValue.int_value); - assertConditionTimer(it->second.conditionTimer, false, 0, bucketStartTimeNs + 20 * NS_PER_SEC); - - // Value for key {{uid 1, tag 14}, kStateUnknown}. - it++; - ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size()); - EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); - EXPECT_EQ(14, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value); - ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); - EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, - it->first.getStateValuesKey().getValues()[0].mValue.int_value); - assertConditionTimer(it->second.conditionTimer, false, 0, 0); - - // Start dump report and check output. - ProtoOutputStream output; - std::set strSet; - valueProducer->onDumpReport(bucket2StartTimeNs + 50 * NS_PER_SEC, - true /* include recent buckets */, true, NO_TIME_CONSTRAINTS, - &strSet, &output); - - StatsLogReport report = outputStreamToProto(&output); - EXPECT_TRUE(report.has_value_metrics()); - ASSERT_EQ(6, report.value_metrics().data_size()); - - // {{uid 1, tag 14}, FOREGROUND}. - auto data = report.value_metrics().data(0); - EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); - EXPECT_EQ(android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_FOREGROUND, - data.slice_by_state(0).value()); - ASSERT_EQ(1, data.bucket_info_size()); - EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).condition_true_nanos()); - - // {{uid 1, tag 16}, BACKGROUND}. - data = report.value_metrics().data(1); - EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); - EXPECT_EQ(android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_BACKGROUND, - data.slice_by_state(0).value()); - ASSERT_EQ(2, data.bucket_info_size()); - EXPECT_EQ(35 * NS_PER_SEC, data.bucket_info(0).condition_true_nanos()); - EXPECT_EQ(50 * NS_PER_SEC, data.bucket_info(1).condition_true_nanos()); - - // {{uid 1, tag 14}, BACKGROUND}. - data = report.value_metrics().data(2); - EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); - EXPECT_EQ(android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_BACKGROUND, - data.slice_by_state(0).value()); - ASSERT_EQ(2, data.bucket_info_size()); - EXPECT_EQ(35 * NS_PER_SEC, data.bucket_info(0).condition_true_nanos()); - EXPECT_EQ(50 * NS_PER_SEC, data.bucket_info(1).condition_true_nanos()); - - // {{uid 1, tag 16}, FOREGROUND}. - data = report.value_metrics().data(3); - EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); - EXPECT_EQ(android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_FOREGROUND, - data.slice_by_state(0).value()); - ASSERT_EQ(1, data.bucket_info_size()); - EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).condition_true_nanos()); - - // {{uid 2, tag 8}, FOREGROUND}. - data = report.value_metrics().data(4); - EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); - EXPECT_EQ(android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_FOREGROUND, - data.slice_by_state(0).value()); - ASSERT_EQ(1, data.bucket_info_size()); - EXPECT_EQ(20 * NS_PER_SEC, data.bucket_info(0).condition_true_nanos()); - - // {{uid 2, tag 8}, BACKGROUND}. - data = report.value_metrics().data(5); - EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); - EXPECT_EQ(android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_BACKGROUND, - data.slice_by_state(0).value()); - ASSERT_EQ(2, data.bucket_info_size()); - EXPECT_EQ(30 * NS_PER_SEC, data.bucket_info(0).condition_true_nanos()); - EXPECT_EQ(30 * NS_PER_SEC, data.bucket_info(1).condition_true_nanos()); -} - -TEST(ValueMetricProducerTest, TestSlicedStateWithCondition) { - // Set up ValueMetricProducer. - ValueMetric metric = ValueMetricProducerTestHelper::createMetricWithConditionAndState( - "BATTERY_SAVER_MODE_STATE"); - sp pullerManager = new StrictMock(); - EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _)) - // Condition changed to true. - .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, - vector>* data) { - EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 20 * NS_PER_SEC); - data->clear(); - data->push_back( - CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 20 * NS_PER_SEC, 3)); - return true; - })) - // Battery saver mode state changed to OFF. - .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, - vector>* data) { - EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 30 * NS_PER_SEC); - data->clear(); - data->push_back( - CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 30 * NS_PER_SEC, 5)); - return true; - })) - // Condition changed to false. - .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, - vector>* data) { - EXPECT_EQ(eventTimeNs, bucket2StartTimeNs + 10 * NS_PER_SEC); - data->clear(); - data->push_back(CreateRepeatedValueLogEvent( - tagId, bucket2StartTimeNs + 10 * NS_PER_SEC, 15)); - return true; - })); - - StateManager::getInstance().clear(); - sp valueProducer = - ValueMetricProducerTestHelper::createValueProducerWithConditionAndState( - pullerManager, metric, {util::BATTERY_SAVER_MODE_STATE_CHANGED}, {}, - ConditionState::kFalse); - EXPECT_EQ(1, valueProducer->mSlicedStateAtoms.size()); - - // Set up StateManager and check that StateTrackers are initialized. - StateManager::getInstance().registerListener(util::BATTERY_SAVER_MODE_STATE_CHANGED, - valueProducer); - EXPECT_EQ(1, StateManager::getInstance().getStateTrackersCount()); - EXPECT_EQ(1, StateManager::getInstance().getListenersCount( - util::BATTERY_SAVER_MODE_STATE_CHANGED)); - - // Bucket status after battery saver mode ON event. - // Condition is false so we do nothing. - unique_ptr batterySaverOnEvent = - CreateBatterySaverOnEvent(/*timestamp=*/bucketStartTimeNs + 10 * NS_PER_SEC); - StateManager::getInstance().onLogEvent(*batterySaverOnEvent); - EXPECT_EQ(0UL, valueProducer->mCurrentSlicedBucket.size()); - EXPECT_EQ(0UL, valueProducer->mCurrentBaseInfo.size()); - - // Bucket status after condition change to true. - valueProducer->onConditionChanged(true, bucketStartTimeNs + 20 * NS_PER_SEC); - // Base for dimension key {} - ASSERT_EQ(1UL, valueProducer->mCurrentBaseInfo.size()); - std::unordered_map::iterator - itBase = valueProducer->mCurrentBaseInfo.find(DEFAULT_DIMENSION_KEY); - EXPECT_TRUE(itBase->second.baseInfos[0].hasBase); - EXPECT_EQ(3, itBase->second.baseInfos[0].base.long_value); - EXPECT_TRUE(itBase->second.hasCurrentState); - ASSERT_EQ(1, itBase->second.currentState.getValues().size()); - EXPECT_EQ(BatterySaverModeStateChanged::ON, - itBase->second.currentState.getValues()[0].mValue.int_value); - // Value for key {{}, ON} - ASSERT_EQ(2UL, valueProducer->mCurrentSlicedBucket.size()); - std::unordered_map::iterator it = - valueProducer->mCurrentSlicedBucket.begin(); - EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); - ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); - EXPECT_EQ(BatterySaverModeStateChanged::ON, - it->first.getStateValuesKey().getValues()[0].mValue.int_value); - assertConditionTimer(it->second.conditionTimer, true, 0, bucketStartTimeNs + 20 * NS_PER_SEC); - // Value for key {{}, -1} - it++; - EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); - ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); - EXPECT_EQ(-1 /*StateTracker::kUnknown*/, - it->first.getStateValuesKey().getValues()[0].mValue.int_value); - EXPECT_FALSE(it->second.intervals[0].hasValue); - assertConditionTimer(it->second.conditionTimer, false, 0, 0); - - // Bucket status after battery saver mode OFF event. - unique_ptr batterySaverOffEvent = - CreateBatterySaverOffEvent(/*timestamp=*/bucketStartTimeNs + 30 * NS_PER_SEC); - StateManager::getInstance().onLogEvent(*batterySaverOffEvent); - // Base for dimension key {} - ASSERT_EQ(1UL, valueProducer->mCurrentBaseInfo.size()); - itBase = valueProducer->mCurrentBaseInfo.find(DEFAULT_DIMENSION_KEY); - EXPECT_TRUE(itBase->second.baseInfos[0].hasBase); - EXPECT_EQ(5, itBase->second.baseInfos[0].base.long_value); - EXPECT_TRUE(itBase->second.hasCurrentState); - ASSERT_EQ(1, itBase->second.currentState.getValues().size()); - EXPECT_EQ(BatterySaverModeStateChanged::OFF, - itBase->second.currentState.getValues()[0].mValue.int_value); - // Value for key {{}, OFF} - ASSERT_EQ(3UL, valueProducer->mCurrentSlicedBucket.size()); - it = valueProducer->mCurrentSlicedBucket.begin(); - EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); - ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); - EXPECT_EQ(BatterySaverModeStateChanged::OFF, - it->first.getStateValuesKey().getValues()[0].mValue.int_value); - assertConditionTimer(it->second.conditionTimer, true, 0, bucketStartTimeNs + 30 * NS_PER_SEC); - // Value for key {{}, ON} - it++; - EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); - ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); - EXPECT_EQ(BatterySaverModeStateChanged::ON, - it->first.getStateValuesKey().getValues()[0].mValue.int_value); - EXPECT_TRUE(it->second.intervals[0].hasValue); - EXPECT_EQ(2, it->second.intervals[0].value.long_value); - assertConditionTimer(it->second.conditionTimer, false, 10 * NS_PER_SEC, - bucketStartTimeNs + 30 * NS_PER_SEC); - // Value for key {{}, -1} - it++; - assertConditionTimer(it->second.conditionTimer, false, 0, 0); - - // Pull at end of first bucket. - vector> allData; - allData.clear(); - allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs, 11)); - valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs); - - EXPECT_EQ(2UL, valueProducer->mPastBuckets.size()); - EXPECT_EQ(3UL, valueProducer->mCurrentSlicedBucket.size()); - // Base for dimension key {} - ASSERT_EQ(1UL, valueProducer->mCurrentBaseInfo.size()); - itBase = valueProducer->mCurrentBaseInfo.find(DEFAULT_DIMENSION_KEY); - EXPECT_TRUE(itBase->second.baseInfos[0].hasBase); - EXPECT_EQ(11, itBase->second.baseInfos[0].base.long_value); - EXPECT_TRUE(itBase->second.hasCurrentState); - ASSERT_EQ(1, itBase->second.currentState.getValues().size()); - EXPECT_EQ(BatterySaverModeStateChanged::OFF, - itBase->second.currentState.getValues()[0].mValue.int_value); - // Value for key {{}, OFF} - it = valueProducer->mCurrentSlicedBucket.begin(); - assertConditionTimer(it->second.conditionTimer, true, 0, bucket2StartTimeNs); - // Value for key {{}, ON} - it++; - assertConditionTimer(it->second.conditionTimer, false, 0, bucketStartTimeNs + 30 * NS_PER_SEC); - // Value for key {{}, -1} - it++; - assertConditionTimer(it->second.conditionTimer, false, 0, 0); - - // Bucket 2 status after condition change to false. - valueProducer->onConditionChanged(false, bucket2StartTimeNs + 10 * NS_PER_SEC); - // Base for dimension key {} - ASSERT_EQ(1UL, valueProducer->mCurrentBaseInfo.size()); - itBase = valueProducer->mCurrentBaseInfo.find(DEFAULT_DIMENSION_KEY); - EXPECT_FALSE(itBase->second.baseInfos[0].hasBase); - EXPECT_TRUE(itBase->second.hasCurrentState); - ASSERT_EQ(1, itBase->second.currentState.getValues().size()); - EXPECT_EQ(BatterySaverModeStateChanged::OFF, - itBase->second.currentState.getValues()[0].mValue.int_value); - // Value for key {{}, OFF} - ASSERT_EQ(3UL, valueProducer->mCurrentSlicedBucket.size()); - it = valueProducer->mCurrentSlicedBucket.begin(); - EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); - ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); - EXPECT_EQ(BatterySaverModeStateChanged::OFF, - it->first.getStateValuesKey().getValues()[0].mValue.int_value); - EXPECT_TRUE(it->second.intervals[0].hasValue); - EXPECT_EQ(4, it->second.intervals[0].value.long_value); - assertConditionTimer(it->second.conditionTimer, false, 10 * NS_PER_SEC, - bucket2StartTimeNs + 10 * NS_PER_SEC); - // Value for key {{}, ON} - it++; - EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); - ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); - EXPECT_EQ(BatterySaverModeStateChanged::ON, - it->first.getStateValuesKey().getValues()[0].mValue.int_value); - EXPECT_FALSE(it->second.intervals[0].hasValue); - assertConditionTimer(it->second.conditionTimer, false, 0, bucketStartTimeNs + 30 * NS_PER_SEC); - // Value for key {{}, -1} - it++; - assertConditionTimer(it->second.conditionTimer, false, 0, 0); - - // Start dump report and check output. - ProtoOutputStream output; - std::set strSet; - valueProducer->onDumpReport(bucket2StartTimeNs + 50 * NS_PER_SEC, - true /* include recent buckets */, true, NO_TIME_CONSTRAINTS, - &strSet, &output); - - StatsLogReport report = outputStreamToProto(&output); - EXPECT_TRUE(report.has_value_metrics()); - ASSERT_EQ(2, report.value_metrics().data_size()); - - ValueMetricData data = report.value_metrics().data(0); - EXPECT_EQ(util::BATTERY_SAVER_MODE_STATE_CHANGED, data.slice_by_state(0).atom_id()); - EXPECT_TRUE(data.slice_by_state(0).has_value()); - EXPECT_EQ(BatterySaverModeStateChanged::ON, data.slice_by_state(0).value()); - ASSERT_EQ(1, data.bucket_info_size()); - EXPECT_EQ(2, data.bucket_info(0).values(0).value_long()); - EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).condition_true_nanos()); - - data = report.value_metrics().data(1); - EXPECT_EQ(util::BATTERY_SAVER_MODE_STATE_CHANGED, data.slice_by_state(0).atom_id()); - EXPECT_TRUE(data.slice_by_state(0).has_value()); - EXPECT_EQ(BatterySaverModeStateChanged::OFF, data.slice_by_state(0).value()); - ASSERT_EQ(2, data.bucket_info_size()); - EXPECT_EQ(6, data.bucket_info(0).values(0).value_long()); - EXPECT_EQ(4, data.bucket_info(1).values(0).value_long()); - EXPECT_EQ(30 * NS_PER_SEC, data.bucket_info(0).condition_true_nanos()); - EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(1).condition_true_nanos()); -} - -/* - * Test bucket splits when condition is unknown. - */ -TEST(ValueMetricProducerTest, TestForcedBucketSplitWhenConditionUnknownSkipsBucket) { - ValueMetric metric = ValueMetricProducerTestHelper::createMetricWithCondition(); - - sp pullerManager = new StrictMock(); - - sp valueProducer = - ValueMetricProducerTestHelper::createValueProducerWithCondition( - pullerManager, metric, - ConditionState::kUnknown); - - // App update event. - int64_t appUpdateTimeNs = bucketStartTimeNs + 1000; - valueProducer->notifyAppUpgrade(appUpdateTimeNs); - - // Check dump report. - ProtoOutputStream output; - std::set strSet; - int64_t dumpReportTimeNs = bucketStartTimeNs + 10000000000; // 10 seconds - valueProducer->onDumpReport(dumpReportTimeNs, false /* include current buckets */, true, - NO_TIME_CONSTRAINTS /* dumpLatency */, &strSet, &output); - - StatsLogReport report = outputStreamToProto(&output); - EXPECT_TRUE(report.has_value_metrics()); - ASSERT_EQ(0, report.value_metrics().data_size()); - ASSERT_EQ(1, report.value_metrics().skipped_size()); - - EXPECT_EQ(NanoToMillis(bucketStartTimeNs), - report.value_metrics().skipped(0).start_bucket_elapsed_millis()); - EXPECT_EQ(NanoToMillis(appUpdateTimeNs), - report.value_metrics().skipped(0).end_bucket_elapsed_millis()); - ASSERT_EQ(1, report.value_metrics().skipped(0).drop_event_size()); - - auto dropEvent = report.value_metrics().skipped(0).drop_event(0); - EXPECT_EQ(BucketDropReason::CONDITION_UNKNOWN, dropEvent.drop_reason()); - EXPECT_EQ(NanoToMillis(appUpdateTimeNs), dropEvent.drop_time_millis()); -} - -} // namespace statsd -} // namespace os -} // namespace android -#else -GTEST_LOG_(INFO) << "This test does nothing.\n"; -#endif diff --git a/bin/tests/metrics/metrics_test_helper.cpp b/bin/tests/metrics/metrics_test_helper.cpp deleted file mode 100644 index 108df04b..00000000 --- a/bin/tests/metrics/metrics_test_helper.cpp +++ /dev/null @@ -1,57 +0,0 @@ -// 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 "metrics_test_helper.h" - -namespace android { -namespace os { -namespace statsd { - -HashableDimensionKey getMockedDimensionKey(int tagId, int key, string value) { - HashableDimensionKey dimension; - int pos[] = {key, 0, 0}; - dimension.addValue(FieldValue(Field(tagId, pos, 0), Value(value))); - - return dimension; -} - -HashableDimensionKey getMockedDimensionKeyLongValue(int tagId, int key, int64_t value) { - HashableDimensionKey dimension; - int pos[] = {key, 0, 0}; - dimension.addValue(FieldValue(Field(tagId, pos, 0), Value(value))); - - return dimension; -} - -MetricDimensionKey getMockedMetricDimensionKey(int tagId, int key, string value) { - return MetricDimensionKey(getMockedDimensionKey(tagId, key, value), DEFAULT_DIMENSION_KEY); -} - -MetricDimensionKey getMockedStateDimensionKey(int tagId, int key, int64_t value) { - return MetricDimensionKey(DEFAULT_DIMENSION_KEY, - getMockedDimensionKeyLongValue(tagId, key, value)); -} - -void buildSimpleAtomFieldMatcher(const int tagId, FieldMatcher* matcher) { - matcher->set_field(tagId); -} - -void buildSimpleAtomFieldMatcher(const int tagId, const int fieldNum, FieldMatcher* matcher) { - matcher->set_field(tagId); - matcher->add_child()->set_field(fieldNum); -} - -} // namespace statsd -} // namespace os -} // namespace android diff --git a/bin/tests/metrics/metrics_test_helper.h b/bin/tests/metrics/metrics_test_helper.h deleted file mode 100644 index 39232c19..00000000 --- a/bin/tests/metrics/metrics_test_helper.h +++ /dev/null @@ -1,63 +0,0 @@ -// 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. -#pragma once - -#include "src/condition/ConditionWizard.h" -#include "src/external/StatsPullerManager.h" -#include "src/packages/UidMap.h" - -#include -#include - -namespace android { -namespace os { -namespace statsd { - -class MockConditionWizard : public ConditionWizard { -public: - MOCK_METHOD3(query, - ConditionState(const int conditionIndex, const ConditionKey& conditionParameters, - const bool isPartialLink)); -}; - -class MockStatsPullerManager : public StatsPullerManager { -public: - MOCK_METHOD5(RegisterReceiver, - void(int tagId, const ConfigKey& key, wp receiver, - int64_t nextPulltimeNs, int64_t intervalNs)); - MOCK_METHOD3(UnRegisterReceiver, - void(int tagId, const ConfigKey& key, wp receiver)); - MOCK_METHOD4(Pull, bool(const int pullCode, const ConfigKey& key, const int64_t eventTimeNs, - vector>* data)); - MOCK_METHOD4(Pull, bool(const int pullCode, const vector& uids, - const int64_t eventTimeNs, vector>* data)); - MOCK_METHOD2(RegisterPullUidProvider, - void(const ConfigKey& configKey, wp provider)); - MOCK_METHOD2(UnregisterPullUidProvider, - void(const ConfigKey& configKey, wp provider)); -}; - -HashableDimensionKey getMockedDimensionKey(int tagId, int key, std::string value); -MetricDimensionKey getMockedMetricDimensionKey(int tagId, int key, std::string value); - -HashableDimensionKey getMockedDimensionKeyLongValue(int tagId, int key, int64_t value); -MetricDimensionKey getMockedStateDimensionKey(int tagId, int key, int64_t value); - -// Utils to build FieldMatcher proto for simple one-depth atoms. -void buildSimpleAtomFieldMatcher(const int tagId, const int atomFieldNum, FieldMatcher* matcher); -void buildSimpleAtomFieldMatcher(const int tagId, FieldMatcher* matcher); - -} // namespace statsd -} // namespace os -} // namespace android diff --git a/bin/tests/metrics/parsing_utils/config_update_utils_test.cpp b/bin/tests/metrics/parsing_utils/config_update_utils_test.cpp deleted file mode 100644 index b29972f7..00000000 --- a/bin/tests/metrics/parsing_utils/config_update_utils_test.cpp +++ /dev/null @@ -1,3533 +0,0 @@ -// Copyright (C) 2020 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 "src/metrics/parsing_utils/config_update_utils.h" - -#include -#include -#include -#include - -#include -#include -#include - -#include "packages/modules/StatsD/bin/src/statsd_config.pb.h" -#include "src/condition/CombinationConditionTracker.h" -#include "src/condition/SimpleConditionTracker.h" -#include "src/matchers/CombinationAtomMatchingTracker.h" -#include "src/metrics/DurationMetricProducer.h" -#include "src/metrics/GaugeMetricProducer.h" -#include "src/metrics/ValueMetricProducer.h" -#include "src/metrics/parsing_utils/metrics_manager_util.h" -#include "tests/statsd_test_util.h" - -using namespace testing; -using android::sp; -using android::os::statsd::Predicate; -using std::map; -using std::nullopt; -using std::optional; -using std::set; -using std::unordered_map; -using std::vector; - -#ifdef __ANDROID__ - -namespace android { -namespace os { -namespace statsd { - -namespace { - -ConfigKey key(123, 456); -const int64_t timeBaseNs = 1000 * NS_PER_SEC; - -sp uidMap = new UidMap(); -sp pullerManager = new StatsPullerManager(); -sp anomalyAlarmMonitor; -sp periodicAlarmMonitor = new AlarmMonitor( - /*minDiffToUpdateRegisteredAlarmTimeSec=*/0, - [](const shared_ptr&, int64_t) {}, - [](const shared_ptr&) {}); -set allTagIds; -vector> oldAtomMatchingTrackers; -unordered_map oldAtomMatchingTrackerMap; -vector> oldConditionTrackers; -unordered_map oldConditionTrackerMap; -vector> oldMetricProducers; -unordered_map oldMetricProducerMap; -std::vector> oldAnomalyTrackers; -unordered_map oldAlertTrackerMap; -std::vector> oldAlarmTrackers; -unordered_map> tmpConditionToMetricMap; -unordered_map> tmpTrackerToMetricMap; -unordered_map> tmpTrackerToConditionMap; -unordered_map> tmpActivationAtomTrackerToMetricMap; -unordered_map> tmpDeactivationAtomTrackerToMetricMap; -vector metricsWithActivation; -map oldStateHashes; -std::set noReportMetricIds; - -class ConfigUpdateTest : public ::testing::Test { -public: - ConfigUpdateTest() { - } - - void SetUp() override { - allTagIds.clear(); - oldAtomMatchingTrackers.clear(); - oldAtomMatchingTrackerMap.clear(); - oldConditionTrackers.clear(); - oldConditionTrackerMap.clear(); - oldMetricProducers.clear(); - oldMetricProducerMap.clear(); - oldAnomalyTrackers.clear(); - oldAlarmTrackers.clear(); - tmpConditionToMetricMap.clear(); - tmpTrackerToMetricMap.clear(); - tmpTrackerToConditionMap.clear(); - tmpActivationAtomTrackerToMetricMap.clear(); - tmpDeactivationAtomTrackerToMetricMap.clear(); - oldAlertTrackerMap.clear(); - metricsWithActivation.clear(); - oldStateHashes.clear(); - noReportMetricIds.clear(); - StateManager::getInstance().clear(); - } -}; - -bool initConfig(const StatsdConfig& config) { - return initStatsdConfig( - key, config, uidMap, pullerManager, anomalyAlarmMonitor, periodicAlarmMonitor, - timeBaseNs, timeBaseNs, allTagIds, oldAtomMatchingTrackers, oldAtomMatchingTrackerMap, - oldConditionTrackers, oldConditionTrackerMap, oldMetricProducers, oldMetricProducerMap, - oldAnomalyTrackers, oldAlarmTrackers, tmpConditionToMetricMap, tmpTrackerToMetricMap, - tmpTrackerToConditionMap, tmpActivationAtomTrackerToMetricMap, - tmpDeactivationAtomTrackerToMetricMap, oldAlertTrackerMap, metricsWithActivation, - oldStateHashes, noReportMetricIds); -} -} // anonymous namespace - -TEST_F(ConfigUpdateTest, TestSimpleMatcherPreserve) { - StatsdConfig config; - AtomMatcher matcher = CreateSimpleAtomMatcher("TEST", /*atom=*/10); - int64_t matcherId = matcher.id(); - *config.add_atom_matcher() = matcher; - - // Create an initial config. - EXPECT_TRUE(initConfig(config)); - - vector matchersToUpdate(1, UPDATE_UNKNOWN); - vector cycleTracker(1, false); - unordered_map newAtomMatchingTrackerMap; - newAtomMatchingTrackerMap[matcherId] = 0; - EXPECT_TRUE(determineMatcherUpdateStatus(config, 0, oldAtomMatchingTrackerMap, - oldAtomMatchingTrackers, newAtomMatchingTrackerMap, - matchersToUpdate, cycleTracker)); - EXPECT_EQ(matchersToUpdate[0], UPDATE_PRESERVE); -} - -TEST_F(ConfigUpdateTest, TestSimpleMatcherReplace) { - StatsdConfig config; - AtomMatcher matcher = CreateSimpleAtomMatcher("TEST", /*atom=*/10); - *config.add_atom_matcher() = matcher; - - EXPECT_TRUE(initConfig(config)); - - StatsdConfig newConfig; - // Same id, different atom, so should be replaced. - AtomMatcher newMatcher = CreateSimpleAtomMatcher("TEST", /*atom=*/11); - int64_t matcherId = newMatcher.id(); - EXPECT_EQ(matcherId, matcher.id()); - *newConfig.add_atom_matcher() = newMatcher; - - vector matchersToUpdate(1, UPDATE_UNKNOWN); - vector cycleTracker(1, false); - unordered_map newAtomMatchingTrackerMap; - newAtomMatchingTrackerMap[matcherId] = 0; - EXPECT_TRUE(determineMatcherUpdateStatus(newConfig, 0, oldAtomMatchingTrackerMap, - oldAtomMatchingTrackers, newAtomMatchingTrackerMap, - matchersToUpdate, cycleTracker)); - EXPECT_EQ(matchersToUpdate[0], UPDATE_REPLACE); -} - -TEST_F(ConfigUpdateTest, TestSimpleMatcherNew) { - StatsdConfig config; - AtomMatcher matcher = CreateSimpleAtomMatcher("TEST", /*atom=*/10); - *config.add_atom_matcher() = matcher; - - EXPECT_TRUE(initConfig(config)); - - StatsdConfig newConfig; - // Different id, so should be a new matcher. - AtomMatcher newMatcher = CreateSimpleAtomMatcher("DIFFERENT_NAME", /*atom=*/10); - int64_t matcherId = newMatcher.id(); - EXPECT_NE(matcherId, matcher.id()); - *newConfig.add_atom_matcher() = newMatcher; - - vector matchersToUpdate(1, UPDATE_UNKNOWN); - vector cycleTracker(1, false); - unordered_map newAtomMatchingTrackerMap; - newAtomMatchingTrackerMap[matcherId] = 0; - EXPECT_TRUE(determineMatcherUpdateStatus(newConfig, 0, oldAtomMatchingTrackerMap, - oldAtomMatchingTrackers, newAtomMatchingTrackerMap, - matchersToUpdate, cycleTracker)); - EXPECT_EQ(matchersToUpdate[0], UPDATE_NEW); -} - -TEST_F(ConfigUpdateTest, TestCombinationMatcherPreserve) { - StatsdConfig config; - AtomMatcher matcher1 = CreateSimpleAtomMatcher("TEST1", /*atom=*/10); - int64_t matcher1Id = matcher1.id(); - *config.add_atom_matcher() = matcher1; - - AtomMatcher matcher2 = CreateSimpleAtomMatcher("TEST2", /*atom=*/11); - *config.add_atom_matcher() = matcher2; - int64_t matcher2Id = matcher2.id(); - - AtomMatcher matcher3; - matcher3.set_id(StringToId("TEST3")); - AtomMatcher_Combination* combination = matcher3.mutable_combination(); - combination->set_operation(LogicalOperation::OR); - combination->add_matcher(matcher1Id); - combination->add_matcher(matcher2Id); - int64_t matcher3Id = matcher3.id(); - *config.add_atom_matcher() = matcher3; - - EXPECT_TRUE(initConfig(config)); - - StatsdConfig newConfig; - unordered_map newAtomMatchingTrackerMap; - // Same matchers, different order, all should be preserved. - *newConfig.add_atom_matcher() = matcher2; - newAtomMatchingTrackerMap[matcher2Id] = 0; - *newConfig.add_atom_matcher() = matcher3; - newAtomMatchingTrackerMap[matcher3Id] = 1; - *newConfig.add_atom_matcher() = matcher1; - newAtomMatchingTrackerMap[matcher1Id] = 2; - - vector matchersToUpdate(3, UPDATE_UNKNOWN); - vector cycleTracker(3, false); - // Only update the combination. It should recurse the two child matchers and preserve all 3. - EXPECT_TRUE(determineMatcherUpdateStatus(newConfig, 1, oldAtomMatchingTrackerMap, - oldAtomMatchingTrackers, newAtomMatchingTrackerMap, - matchersToUpdate, cycleTracker)); - EXPECT_EQ(matchersToUpdate[0], UPDATE_PRESERVE); - EXPECT_EQ(matchersToUpdate[1], UPDATE_PRESERVE); - EXPECT_EQ(matchersToUpdate[2], UPDATE_PRESERVE); -} - -TEST_F(ConfigUpdateTest, TestCombinationMatcherReplace) { - StatsdConfig config; - AtomMatcher matcher1 = CreateSimpleAtomMatcher("TEST1", /*atom=*/10); - int64_t matcher1Id = matcher1.id(); - *config.add_atom_matcher() = matcher1; - - AtomMatcher matcher2 = CreateSimpleAtomMatcher("TEST2", /*atom=*/11); - *config.add_atom_matcher() = matcher2; - int64_t matcher2Id = matcher2.id(); - - AtomMatcher matcher3; - matcher3.set_id(StringToId("TEST3")); - AtomMatcher_Combination* combination = matcher3.mutable_combination(); - combination->set_operation(LogicalOperation::OR); - combination->add_matcher(matcher1Id); - combination->add_matcher(matcher2Id); - int64_t matcher3Id = matcher3.id(); - *config.add_atom_matcher() = matcher3; - - EXPECT_TRUE(initConfig(config)); - - // Change the logical operation of the combination matcher, causing a replacement. - matcher3.mutable_combination()->set_operation(LogicalOperation::AND); - - StatsdConfig newConfig; - unordered_map newAtomMatchingTrackerMap; - *newConfig.add_atom_matcher() = matcher2; - newAtomMatchingTrackerMap[matcher2Id] = 0; - *newConfig.add_atom_matcher() = matcher3; - newAtomMatchingTrackerMap[matcher3Id] = 1; - *newConfig.add_atom_matcher() = matcher1; - newAtomMatchingTrackerMap[matcher1Id] = 2; - - vector matchersToUpdate(3, UPDATE_UNKNOWN); - vector cycleTracker(3, false); - // Only update the combination. The simple matchers should not be evaluated. - EXPECT_TRUE(determineMatcherUpdateStatus(newConfig, 1, oldAtomMatchingTrackerMap, - oldAtomMatchingTrackers, newAtomMatchingTrackerMap, - matchersToUpdate, cycleTracker)); - EXPECT_EQ(matchersToUpdate[0], UPDATE_UNKNOWN); - EXPECT_EQ(matchersToUpdate[1], UPDATE_REPLACE); - EXPECT_EQ(matchersToUpdate[2], UPDATE_UNKNOWN); -} - -TEST_F(ConfigUpdateTest, TestCombinationMatcherDepsChange) { - StatsdConfig config; - AtomMatcher matcher1 = CreateSimpleAtomMatcher("TEST1", /*atom=*/10); - int64_t matcher1Id = matcher1.id(); - *config.add_atom_matcher() = matcher1; - - AtomMatcher matcher2 = CreateSimpleAtomMatcher("TEST2", /*atom=*/11); - *config.add_atom_matcher() = matcher2; - int64_t matcher2Id = matcher2.id(); - - AtomMatcher matcher3; - matcher3.set_id(StringToId("TEST3")); - AtomMatcher_Combination* combination = matcher3.mutable_combination(); - combination->set_operation(LogicalOperation::OR); - combination->add_matcher(matcher1Id); - combination->add_matcher(matcher2Id); - int64_t matcher3Id = matcher3.id(); - *config.add_atom_matcher() = matcher3; - - EXPECT_TRUE(initConfig(config)); - - // Change a dependency of matcher 3. - matcher2.mutable_simple_atom_matcher()->set_atom_id(12); - - StatsdConfig newConfig; - unordered_map newAtomMatchingTrackerMap; - *newConfig.add_atom_matcher() = matcher2; - newAtomMatchingTrackerMap[matcher2Id] = 0; - *newConfig.add_atom_matcher() = matcher3; - newAtomMatchingTrackerMap[matcher3Id] = 1; - *newConfig.add_atom_matcher() = matcher1; - newAtomMatchingTrackerMap[matcher1Id] = 2; - - vector matchersToUpdate(3, UPDATE_UNKNOWN); - vector cycleTracker(3, false); - // Only update the combination. - EXPECT_TRUE(determineMatcherUpdateStatus(newConfig, 1, oldAtomMatchingTrackerMap, - oldAtomMatchingTrackers, newAtomMatchingTrackerMap, - matchersToUpdate, cycleTracker)); - // Matcher 2 and matcher3 must be reevaluated. Matcher 1 might, but does not need to be. - EXPECT_EQ(matchersToUpdate[0], UPDATE_REPLACE); - EXPECT_EQ(matchersToUpdate[1], UPDATE_REPLACE); -} - -TEST_F(ConfigUpdateTest, TestUpdateMatchers) { - StatsdConfig config; - // Will be preserved. - AtomMatcher simple1 = CreateSimpleAtomMatcher("SIMPLE1", /*atom=*/10); - int64_t simple1Id = simple1.id(); - *config.add_atom_matcher() = simple1; - - // Will be replaced. - AtomMatcher simple2 = CreateSimpleAtomMatcher("SIMPLE2", /*atom=*/11); - *config.add_atom_matcher() = simple2; - int64_t simple2Id = simple2.id(); - - // Will be removed. - AtomMatcher simple3 = CreateSimpleAtomMatcher("SIMPLE3", /*atom=*/12); - *config.add_atom_matcher() = simple3; - int64_t simple3Id = simple3.id(); - - // Will be preserved. - AtomMatcher combination1; - combination1.set_id(StringToId("combination1")); - AtomMatcher_Combination* combination = combination1.mutable_combination(); - combination->set_operation(LogicalOperation::NOT); - combination->add_matcher(simple1Id); - int64_t combination1Id = combination1.id(); - *config.add_atom_matcher() = combination1; - - // Will be replaced since it depends on simple2. - AtomMatcher combination2; - combination2.set_id(StringToId("combination2")); - combination = combination2.mutable_combination(); - combination->set_operation(LogicalOperation::AND); - combination->add_matcher(simple1Id); - combination->add_matcher(simple2Id); - int64_t combination2Id = combination2.id(); - *config.add_atom_matcher() = combination2; - - EXPECT_TRUE(initConfig(config)); - - // Change simple2, causing simple2 and combination2 to be replaced. - simple2.mutable_simple_atom_matcher()->set_atom_id(111); - - // 2 new matchers: simple4 and combination3: - AtomMatcher simple4 = CreateSimpleAtomMatcher("SIMPLE4", /*atom=*/13); - int64_t simple4Id = simple4.id(); - - AtomMatcher combination3; - combination3.set_id(StringToId("combination3")); - combination = combination3.mutable_combination(); - combination->set_operation(LogicalOperation::AND); - combination->add_matcher(simple4Id); - combination->add_matcher(simple2Id); - int64_t combination3Id = combination3.id(); - - StatsdConfig newConfig; - *newConfig.add_atom_matcher() = combination3; - *newConfig.add_atom_matcher() = simple2; - *newConfig.add_atom_matcher() = combination2; - *newConfig.add_atom_matcher() = simple1; - *newConfig.add_atom_matcher() = simple4; - *newConfig.add_atom_matcher() = combination1; - - set newTagIds; - unordered_map newAtomMatchingTrackerMap; - vector> newAtomMatchingTrackers; - set replacedMatchers; - EXPECT_TRUE(updateAtomMatchingTrackers( - newConfig, uidMap, oldAtomMatchingTrackerMap, oldAtomMatchingTrackers, newTagIds, - newAtomMatchingTrackerMap, newAtomMatchingTrackers, replacedMatchers)); - - ASSERT_EQ(newTagIds.size(), 3); - EXPECT_EQ(newTagIds.count(10), 1); - EXPECT_EQ(newTagIds.count(111), 1); - EXPECT_EQ(newTagIds.count(13), 1); - - ASSERT_EQ(newAtomMatchingTrackerMap.size(), 6); - EXPECT_EQ(newAtomMatchingTrackerMap.at(combination3Id), 0); - EXPECT_EQ(newAtomMatchingTrackerMap.at(simple2Id), 1); - EXPECT_EQ(newAtomMatchingTrackerMap.at(combination2Id), 2); - EXPECT_EQ(newAtomMatchingTrackerMap.at(simple1Id), 3); - EXPECT_EQ(newAtomMatchingTrackerMap.at(simple4Id), 4); - EXPECT_EQ(newAtomMatchingTrackerMap.at(combination1Id), 5); - - ASSERT_EQ(newAtomMatchingTrackers.size(), 6); - // Make sure all atom matchers are initialized: - for (const sp& tracker : newAtomMatchingTrackers) { - EXPECT_TRUE(tracker->mInitialized); - } - // Make sure preserved atom matchers are the same. - EXPECT_EQ(oldAtomMatchingTrackers[oldAtomMatchingTrackerMap.at(simple1Id)], - newAtomMatchingTrackers[newAtomMatchingTrackerMap.at(simple1Id)]); - EXPECT_EQ(oldAtomMatchingTrackers[oldAtomMatchingTrackerMap.at(combination1Id)], - newAtomMatchingTrackers[newAtomMatchingTrackerMap.at(combination1Id)]); - // Make sure replaced matchers are different. - EXPECT_NE(oldAtomMatchingTrackers[oldAtomMatchingTrackerMap.at(simple2Id)], - newAtomMatchingTrackers[newAtomMatchingTrackerMap.at(simple2Id)]); - EXPECT_NE(oldAtomMatchingTrackers[oldAtomMatchingTrackerMap.at(combination2Id)], - newAtomMatchingTrackers[newAtomMatchingTrackerMap.at(combination2Id)]); - - // Validation, make sure the matchers have the proper ids/indices. Could do more checks here. - EXPECT_EQ(newAtomMatchingTrackers[0]->getId(), combination3Id); - EXPECT_EQ(newAtomMatchingTrackers[0]->mIndex, 0); - EXPECT_EQ(newAtomMatchingTrackers[1]->getId(), simple2Id); - EXPECT_EQ(newAtomMatchingTrackers[1]->mIndex, 1); - EXPECT_EQ(newAtomMatchingTrackers[2]->getId(), combination2Id); - EXPECT_EQ(newAtomMatchingTrackers[2]->mIndex, 2); - EXPECT_EQ(newAtomMatchingTrackers[3]->getId(), simple1Id); - EXPECT_EQ(newAtomMatchingTrackers[3]->mIndex, 3); - EXPECT_EQ(newAtomMatchingTrackers[4]->getId(), simple4Id); - EXPECT_EQ(newAtomMatchingTrackers[4]->mIndex, 4); - EXPECT_EQ(newAtomMatchingTrackers[5]->getId(), combination1Id); - EXPECT_EQ(newAtomMatchingTrackers[5]->mIndex, 5); - - // Verify child indices of Combination Matchers are correct. - CombinationAtomMatchingTracker* combinationTracker1 = - static_cast(newAtomMatchingTrackers[5].get()); - vector* childMatchers = &combinationTracker1->mChildren; - EXPECT_EQ(childMatchers->size(), 1); - EXPECT_NE(std::find(childMatchers->begin(), childMatchers->end(), 3), childMatchers->end()); - - CombinationAtomMatchingTracker* combinationTracker2 = - static_cast(newAtomMatchingTrackers[2].get()); - childMatchers = &combinationTracker2->mChildren; - EXPECT_EQ(childMatchers->size(), 2); - EXPECT_NE(std::find(childMatchers->begin(), childMatchers->end(), 1), childMatchers->end()); - EXPECT_NE(std::find(childMatchers->begin(), childMatchers->end(), 3), childMatchers->end()); - - CombinationAtomMatchingTracker* combinationTracker3 = - static_cast(newAtomMatchingTrackers[0].get()); - childMatchers = &combinationTracker3->mChildren; - EXPECT_EQ(childMatchers->size(), 2); - EXPECT_NE(std::find(childMatchers->begin(), childMatchers->end(), 1), childMatchers->end()); - EXPECT_NE(std::find(childMatchers->begin(), childMatchers->end(), 4), childMatchers->end()); - - // Expect replacedMatchers to have simple2 and combination2 - ASSERT_EQ(replacedMatchers.size(), 2); - EXPECT_NE(replacedMatchers.find(simple2Id), replacedMatchers.end()); - EXPECT_NE(replacedMatchers.find(combination2Id), replacedMatchers.end()); -} - -TEST_F(ConfigUpdateTest, TestSimpleConditionPreserve) { - StatsdConfig config; - AtomMatcher startMatcher = CreateScreenTurnedOnAtomMatcher(); - *config.add_atom_matcher() = startMatcher; - AtomMatcher stopMatcher = CreateScreenTurnedOffAtomMatcher(); - *config.add_atom_matcher() = stopMatcher; - - Predicate predicate = CreateScreenIsOnPredicate(); - *config.add_predicate() = predicate; - - // Create an initial config. - EXPECT_TRUE(initConfig(config)); - - set replacedMatchers; - vector conditionsToUpdate(1, UPDATE_UNKNOWN); - vector cycleTracker(1, false); - unordered_map newConditionTrackerMap; - newConditionTrackerMap[predicate.id()] = 0; - EXPECT_TRUE(determineConditionUpdateStatus(config, 0, oldConditionTrackerMap, - oldConditionTrackers, newConditionTrackerMap, - replacedMatchers, conditionsToUpdate, cycleTracker)); - EXPECT_EQ(conditionsToUpdate[0], UPDATE_PRESERVE); -} - -TEST_F(ConfigUpdateTest, TestSimpleConditionReplace) { - StatsdConfig config; - AtomMatcher startMatcher = CreateScreenTurnedOnAtomMatcher(); - *config.add_atom_matcher() = startMatcher; - AtomMatcher stopMatcher = CreateScreenTurnedOffAtomMatcher(); - *config.add_atom_matcher() = stopMatcher; - - Predicate predicate = CreateScreenIsOnPredicate(); - *config.add_predicate() = predicate; - - EXPECT_TRUE(initConfig(config)); - - // Modify the predicate. - config.mutable_predicate(0)->mutable_simple_predicate()->set_count_nesting(true); - - set replacedMatchers; - vector conditionsToUpdate(1, UPDATE_UNKNOWN); - vector cycleTracker(1, false); - unordered_map newConditionTrackerMap; - newConditionTrackerMap[predicate.id()] = 0; - EXPECT_TRUE(determineConditionUpdateStatus(config, 0, oldConditionTrackerMap, - oldConditionTrackers, newConditionTrackerMap, - replacedMatchers, conditionsToUpdate, cycleTracker)); - EXPECT_EQ(conditionsToUpdate[0], UPDATE_REPLACE); -} - -TEST_F(ConfigUpdateTest, TestSimpleConditionDepsChange) { - StatsdConfig config; - AtomMatcher startMatcher = CreateScreenTurnedOnAtomMatcher(); - int64_t startMatcherId = startMatcher.id(); - *config.add_atom_matcher() = startMatcher; - AtomMatcher stopMatcher = CreateScreenTurnedOffAtomMatcher(); - *config.add_atom_matcher() = stopMatcher; - - Predicate predicate = CreateScreenIsOnPredicate(); - *config.add_predicate() = predicate; - - EXPECT_TRUE(initConfig(config)); - - // Start matcher was replaced. - set replacedMatchers; - replacedMatchers.insert(startMatcherId); - - vector conditionsToUpdate(1, UPDATE_UNKNOWN); - vector cycleTracker(1, false); - unordered_map newConditionTrackerMap; - newConditionTrackerMap[predicate.id()] = 0; - EXPECT_TRUE(determineConditionUpdateStatus(config, 0, oldConditionTrackerMap, - oldConditionTrackers, newConditionTrackerMap, - replacedMatchers, conditionsToUpdate, cycleTracker)); - EXPECT_EQ(conditionsToUpdate[0], UPDATE_REPLACE); -} - -TEST_F(ConfigUpdateTest, TestCombinationConditionPreserve) { - StatsdConfig config; - AtomMatcher screenOnMatcher = CreateScreenTurnedOnAtomMatcher(); - *config.add_atom_matcher() = screenOnMatcher; - AtomMatcher screenOffMatcher = CreateScreenTurnedOffAtomMatcher(); - *config.add_atom_matcher() = screenOffMatcher; - - Predicate simple1 = CreateScreenIsOnPredicate(); - *config.add_predicate() = simple1; - Predicate simple2 = CreateScreenIsOffPredicate(); - *config.add_predicate() = simple2; - - Predicate combination1; - combination1.set_id(StringToId("COMBINATION1")); - Predicate_Combination* combinationInternal = combination1.mutable_combination(); - combinationInternal->set_operation(LogicalOperation::NAND); - combinationInternal->add_predicate(simple1.id()); - combinationInternal->add_predicate(simple2.id()); - *config.add_predicate() = combination1; - - EXPECT_TRUE(initConfig(config)); - - // Same predicates, different order - StatsdConfig newConfig; - unordered_map newConditionTrackerMap; - *newConfig.add_predicate() = combination1; - newConditionTrackerMap[combination1.id()] = 0; - *newConfig.add_predicate() = simple2; - newConditionTrackerMap[simple2.id()] = 1; - *newConfig.add_predicate() = simple1; - newConditionTrackerMap[simple1.id()] = 2; - - set replacedMatchers; - vector conditionsToUpdate(3, UPDATE_UNKNOWN); - vector cycleTracker(3, false); - // Only update the combination. It should recurse the two child predicates and preserve all 3. - EXPECT_TRUE(determineConditionUpdateStatus(newConfig, 0, oldConditionTrackerMap, - oldConditionTrackers, newConditionTrackerMap, - replacedMatchers, conditionsToUpdate, cycleTracker)); - EXPECT_EQ(conditionsToUpdate[0], UPDATE_PRESERVE); - EXPECT_EQ(conditionsToUpdate[1], UPDATE_PRESERVE); - EXPECT_EQ(conditionsToUpdate[2], UPDATE_PRESERVE); -} - -TEST_F(ConfigUpdateTest, TestCombinationConditionReplace) { - StatsdConfig config; - AtomMatcher screenOnMatcher = CreateScreenTurnedOnAtomMatcher(); - *config.add_atom_matcher() = screenOnMatcher; - AtomMatcher screenOffMatcher = CreateScreenTurnedOffAtomMatcher(); - *config.add_atom_matcher() = screenOffMatcher; - - Predicate simple1 = CreateScreenIsOnPredicate(); - *config.add_predicate() = simple1; - Predicate simple2 = CreateScreenIsOffPredicate(); - *config.add_predicate() = simple2; - - Predicate combination1; - combination1.set_id(StringToId("COMBINATION1")); - Predicate_Combination* combinationInternal = combination1.mutable_combination(); - combinationInternal->set_operation(LogicalOperation::NAND); - combinationInternal->add_predicate(simple1.id()); - combinationInternal->add_predicate(simple2.id()); - *config.add_predicate() = combination1; - - EXPECT_TRUE(initConfig(config)); - - // Changing the logical operation changes the predicate definition, so it should be replaced. - combination1.mutable_combination()->set_operation(LogicalOperation::OR); - - StatsdConfig newConfig; - unordered_map newConditionTrackerMap; - *newConfig.add_predicate() = combination1; - newConditionTrackerMap[combination1.id()] = 0; - *newConfig.add_predicate() = simple2; - newConditionTrackerMap[simple2.id()] = 1; - *newConfig.add_predicate() = simple1; - newConditionTrackerMap[simple1.id()] = 2; - - set replacedMatchers; - vector conditionsToUpdate(3, UPDATE_UNKNOWN); - vector cycleTracker(3, false); - // Only update the combination. The simple conditions should not be evaluated. - EXPECT_TRUE(determineConditionUpdateStatus(newConfig, 0, oldConditionTrackerMap, - oldConditionTrackers, newConditionTrackerMap, - replacedMatchers, conditionsToUpdate, cycleTracker)); - EXPECT_EQ(conditionsToUpdate[0], UPDATE_REPLACE); - EXPECT_EQ(conditionsToUpdate[1], UPDATE_UNKNOWN); - EXPECT_EQ(conditionsToUpdate[2], UPDATE_UNKNOWN); -} - -TEST_F(ConfigUpdateTest, TestCombinationConditionDepsChange) { - StatsdConfig config; - AtomMatcher screenOnMatcher = CreateScreenTurnedOnAtomMatcher(); - *config.add_atom_matcher() = screenOnMatcher; - AtomMatcher screenOffMatcher = CreateScreenTurnedOffAtomMatcher(); - *config.add_atom_matcher() = screenOffMatcher; - - Predicate simple1 = CreateScreenIsOnPredicate(); - *config.add_predicate() = simple1; - Predicate simple2 = CreateScreenIsOffPredicate(); - *config.add_predicate() = simple2; - - Predicate combination1; - combination1.set_id(StringToId("COMBINATION1")); - Predicate_Combination* combinationInternal = combination1.mutable_combination(); - combinationInternal->set_operation(LogicalOperation::NAND); - combinationInternal->add_predicate(simple1.id()); - combinationInternal->add_predicate(simple2.id()); - *config.add_predicate() = combination1; - - EXPECT_TRUE(initConfig(config)); - - simple2.mutable_simple_predicate()->set_count_nesting(false); - - StatsdConfig newConfig; - unordered_map newConditionTrackerMap; - *newConfig.add_predicate() = combination1; - newConditionTrackerMap[combination1.id()] = 0; - *newConfig.add_predicate() = simple2; - newConditionTrackerMap[simple2.id()] = 1; - *newConfig.add_predicate() = simple1; - newConditionTrackerMap[simple1.id()] = 2; - - set replacedMatchers; - vector conditionsToUpdate(3, UPDATE_UNKNOWN); - vector cycleTracker(3, false); - // Only update the combination. Simple2 and combination1 must be evaluated. - EXPECT_TRUE(determineConditionUpdateStatus(newConfig, 0, oldConditionTrackerMap, - oldConditionTrackers, newConditionTrackerMap, - replacedMatchers, conditionsToUpdate, cycleTracker)); - EXPECT_EQ(conditionsToUpdate[0], UPDATE_REPLACE); - EXPECT_EQ(conditionsToUpdate[1], UPDATE_REPLACE); -} - -TEST_F(ConfigUpdateTest, TestUpdateConditions) { - StatsdConfig config; - // Add atom matchers. These are mostly needed for initStatsdConfig - AtomMatcher matcher1 = CreateScreenTurnedOnAtomMatcher(); - int64_t matcher1Id = matcher1.id(); - *config.add_atom_matcher() = matcher1; - - AtomMatcher matcher2 = CreateScreenTurnedOffAtomMatcher(); - int64_t matcher2Id = matcher2.id(); - *config.add_atom_matcher() = matcher2; - - AtomMatcher matcher3 = CreateStartScheduledJobAtomMatcher(); - int64_t matcher3Id = matcher3.id(); - *config.add_atom_matcher() = matcher3; - - AtomMatcher matcher4 = CreateFinishScheduledJobAtomMatcher(); - int64_t matcher4Id = matcher4.id(); - *config.add_atom_matcher() = matcher4; - - AtomMatcher matcher5 = CreateBatterySaverModeStartAtomMatcher(); - int64_t matcher5Id = matcher5.id(); - *config.add_atom_matcher() = matcher5; - - AtomMatcher matcher6 = CreateBatterySaverModeStopAtomMatcher(); - int64_t matcher6Id = matcher6.id(); - *config.add_atom_matcher() = matcher6; - - // Add the predicates. - // Will be preserved. - Predicate simple1 = CreateScreenIsOnPredicate(); - int64_t simple1Id = simple1.id(); - *config.add_predicate() = simple1; - - // Will be preserved. - Predicate simple2 = CreateScheduledJobPredicate(); - int64_t simple2Id = simple2.id(); - *config.add_predicate() = simple2; - - // Will be replaced. - Predicate simple3 = CreateBatterySaverModePredicate(); - int64_t simple3Id = simple3.id(); - *config.add_predicate() = simple3; - - // Will be preserved - Predicate combination1; - combination1.set_id(StringToId("COMBINATION1")); - combination1.mutable_combination()->set_operation(LogicalOperation::AND); - combination1.mutable_combination()->add_predicate(simple1Id); - combination1.mutable_combination()->add_predicate(simple2Id); - int64_t combination1Id = combination1.id(); - *config.add_predicate() = combination1; - - // Will be replaced since simple3 will be replaced. - Predicate combination2; - combination2.set_id(StringToId("COMBINATION2")); - combination2.mutable_combination()->set_operation(LogicalOperation::OR); - combination2.mutable_combination()->add_predicate(simple1Id); - combination2.mutable_combination()->add_predicate(simple3Id); - int64_t combination2Id = combination2.id(); - *config.add_predicate() = combination2; - - // Will be removed. - Predicate combination3; - combination3.set_id(StringToId("COMBINATION3")); - combination3.mutable_combination()->set_operation(LogicalOperation::NOT); - combination3.mutable_combination()->add_predicate(simple2Id); - int64_t combination3Id = combination3.id(); - *config.add_predicate() = combination3; - - EXPECT_TRUE(initConfig(config)); - - // Mark marcher 5 as replaced. Causes simple3, and therefore combination2 to be replaced. - set replacedMatchers; - replacedMatchers.insert(matcher6Id); - - // Change the condition of simple1 to false. - ASSERT_EQ(oldConditionTrackers[0]->getConditionId(), simple1Id); - LogEvent event(/*uid=*/0, /*pid=*/0); // Empty event is fine since there are no dimensions. - // Mark the stop matcher as matched, condition should be false. - vector eventMatcherValues(6, MatchingState::kNotMatched); - eventMatcherValues[1] = MatchingState::kMatched; - vector tmpConditionCache(6, ConditionState::kNotEvaluated); - vector conditionChangeCache(6, false); - oldConditionTrackers[0]->evaluateCondition(event, eventMatcherValues, oldConditionTrackers, - tmpConditionCache, conditionChangeCache); - EXPECT_EQ(tmpConditionCache[0], ConditionState::kFalse); - EXPECT_EQ(conditionChangeCache[0], true); - - // New combination predicate. Should have an initial condition of true since it is NOT(simple1). - Predicate combination4; - combination4.set_id(StringToId("COMBINATION4")); - combination4.mutable_combination()->set_operation(LogicalOperation::NOT); - combination4.mutable_combination()->add_predicate(simple1Id); - int64_t combination4Id = combination4.id(); - *config.add_predicate() = combination4; - - // Map the matchers in reverse order to force the indices to change. - std::unordered_map newAtomMatchingTrackerMap; - const int matcher6Index = 0; - newAtomMatchingTrackerMap[matcher6Id] = 0; - const int matcher5Index = 1; - newAtomMatchingTrackerMap[matcher5Id] = 1; - const int matcher4Index = 2; - newAtomMatchingTrackerMap[matcher4Id] = 2; - const int matcher3Index = 3; - newAtomMatchingTrackerMap[matcher3Id] = 3; - const int matcher2Index = 4; - newAtomMatchingTrackerMap[matcher2Id] = 4; - const int matcher1Index = 5; - newAtomMatchingTrackerMap[matcher1Id] = 5; - - StatsdConfig newConfig; - *newConfig.add_predicate() = simple3; - const int simple3Index = 0; - *newConfig.add_predicate() = combination2; - const int combination2Index = 1; - *newConfig.add_predicate() = combination4; - const int combination4Index = 2; - *newConfig.add_predicate() = simple2; - const int simple2Index = 3; - *newConfig.add_predicate() = combination1; - const int combination1Index = 4; - *newConfig.add_predicate() = simple1; - const int simple1Index = 5; - - unordered_map newConditionTrackerMap; - vector> newConditionTrackers; - unordered_map> trackerToConditionMap; - std::vector conditionCache; - std::set replacedConditions; - EXPECT_TRUE(updateConditions(key, newConfig, newAtomMatchingTrackerMap, replacedMatchers, - oldConditionTrackerMap, oldConditionTrackers, - newConditionTrackerMap, newConditionTrackers, - trackerToConditionMap, conditionCache, replacedConditions)); - - unordered_map expectedConditionTrackerMap = { - {simple1Id, simple1Index}, {simple2Id, simple2Index}, - {simple3Id, simple3Index}, {combination1Id, combination1Index}, - {combination2Id, combination2Index}, {combination4Id, combination4Index}, - }; - EXPECT_THAT(newConditionTrackerMap, ContainerEq(expectedConditionTrackerMap)); - - ASSERT_EQ(newConditionTrackers.size(), 6); - // Make sure all conditions are initialized: - for (const sp& tracker : newConditionTrackers) { - EXPECT_TRUE(tracker->mInitialized); - } - - // Make sure preserved conditions are the same. - EXPECT_EQ(oldConditionTrackers[oldConditionTrackerMap.at(simple1Id)], - newConditionTrackers[newConditionTrackerMap.at(simple1Id)]); - EXPECT_EQ(oldConditionTrackers[oldConditionTrackerMap.at(simple2Id)], - newConditionTrackers[newConditionTrackerMap.at(simple2Id)]); - EXPECT_EQ(oldConditionTrackers[oldConditionTrackerMap.at(combination1Id)], - newConditionTrackers[newConditionTrackerMap.at(combination1Id)]); - - // Make sure replaced conditions are different and included in replacedConditions. - EXPECT_NE(oldConditionTrackers[oldConditionTrackerMap.at(simple3Id)], - newConditionTrackers[newConditionTrackerMap.at(simple3Id)]); - EXPECT_NE(oldConditionTrackers[oldConditionTrackerMap.at(combination2Id)], - newConditionTrackers[newConditionTrackerMap.at(combination2Id)]); - EXPECT_THAT(replacedConditions, ContainerEq(set({simple3Id, combination2Id}))); - - // Verify the trackerToConditionMap - ASSERT_EQ(trackerToConditionMap.size(), 6); - const vector& matcher1Conditions = trackerToConditionMap[matcher1Index]; - EXPECT_THAT(matcher1Conditions, UnorderedElementsAre(simple1Index, combination1Index, - combination2Index, combination4Index)); - const vector& matcher2Conditions = trackerToConditionMap[matcher2Index]; - EXPECT_THAT(matcher2Conditions, UnorderedElementsAre(simple1Index, combination1Index, - combination2Index, combination4Index)); - const vector& matcher3Conditions = trackerToConditionMap[matcher3Index]; - EXPECT_THAT(matcher3Conditions, UnorderedElementsAre(simple2Index, combination1Index)); - const vector& matcher4Conditions = trackerToConditionMap[matcher4Index]; - EXPECT_THAT(matcher4Conditions, UnorderedElementsAre(simple2Index, combination1Index)); - const vector& matcher5Conditions = trackerToConditionMap[matcher5Index]; - EXPECT_THAT(matcher5Conditions, UnorderedElementsAre(simple3Index, combination2Index)); - const vector& matcher6Conditions = trackerToConditionMap[matcher6Index]; - EXPECT_THAT(matcher6Conditions, UnorderedElementsAre(simple3Index, combination2Index)); - - // Verify the conditionCache. Specifically, simple1 is false and combination4 is true. - ASSERT_EQ(conditionCache.size(), 6); - EXPECT_EQ(conditionCache[simple1Index], ConditionState::kFalse); - EXPECT_EQ(conditionCache[simple2Index], ConditionState::kUnknown); - EXPECT_EQ(conditionCache[simple3Index], ConditionState::kUnknown); - EXPECT_EQ(conditionCache[combination1Index], ConditionState::kUnknown); - EXPECT_EQ(conditionCache[combination2Index], ConditionState::kUnknown); - EXPECT_EQ(conditionCache[combination4Index], ConditionState::kTrue); - - // Verify tracker indices/ids are correct. - EXPECT_EQ(newConditionTrackers[simple1Index]->getConditionId(), simple1Id); - EXPECT_EQ(newConditionTrackers[simple1Index]->mIndex, simple1Index); - EXPECT_TRUE(newConditionTrackers[simple1Index]->IsSimpleCondition()); - EXPECT_EQ(newConditionTrackers[simple2Index]->getConditionId(), simple2Id); - EXPECT_EQ(newConditionTrackers[simple2Index]->mIndex, simple2Index); - EXPECT_TRUE(newConditionTrackers[simple2Index]->IsSimpleCondition()); - EXPECT_EQ(newConditionTrackers[simple3Index]->getConditionId(), simple3Id); - EXPECT_EQ(newConditionTrackers[simple3Index]->mIndex, simple3Index); - EXPECT_TRUE(newConditionTrackers[simple3Index]->IsSimpleCondition()); - EXPECT_EQ(newConditionTrackers[combination1Index]->getConditionId(), combination1Id); - EXPECT_EQ(newConditionTrackers[combination1Index]->mIndex, combination1Index); - EXPECT_FALSE(newConditionTrackers[combination1Index]->IsSimpleCondition()); - EXPECT_EQ(newConditionTrackers[combination2Index]->getConditionId(), combination2Id); - EXPECT_EQ(newConditionTrackers[combination2Index]->mIndex, combination2Index); - EXPECT_FALSE(newConditionTrackers[combination2Index]->IsSimpleCondition()); - EXPECT_EQ(newConditionTrackers[combination4Index]->getConditionId(), combination4Id); - EXPECT_EQ(newConditionTrackers[combination4Index]->mIndex, combination4Index); - EXPECT_FALSE(newConditionTrackers[combination4Index]->IsSimpleCondition()); - - // Verify preserved trackers have indices updated. - SimpleConditionTracker* simpleTracker1 = - static_cast(newConditionTrackers[simple1Index].get()); - EXPECT_EQ(simpleTracker1->mStartLogMatcherIndex, matcher1Index); - EXPECT_EQ(simpleTracker1->mStopLogMatcherIndex, matcher2Index); - EXPECT_EQ(simpleTracker1->mStopAllLogMatcherIndex, -1); - - SimpleConditionTracker* simpleTracker2 = - static_cast(newConditionTrackers[simple2Index].get()); - EXPECT_EQ(simpleTracker2->mStartLogMatcherIndex, matcher3Index); - EXPECT_EQ(simpleTracker2->mStopLogMatcherIndex, matcher4Index); - EXPECT_EQ(simpleTracker2->mStopAllLogMatcherIndex, -1); - - CombinationConditionTracker* combinationTracker1 = static_cast( - newConditionTrackers[combination1Index].get()); - EXPECT_THAT(combinationTracker1->mChildren, UnorderedElementsAre(simple1Index, simple2Index)); - EXPECT_THAT(combinationTracker1->mUnSlicedChildren, - UnorderedElementsAre(simple1Index, simple2Index)); - EXPECT_THAT(combinationTracker1->mSlicedChildren, IsEmpty()); -} - -TEST_F(ConfigUpdateTest, TestUpdateStates) { - StatsdConfig config; - // Add states. - // Will be replaced because we add a state map. - State state1 = CreateScreenState(); - int64_t state1Id = state1.id(); - *config.add_state() = state1; - - // Will be preserved. - State state2 = CreateUidProcessState(); - int64_t state2Id = state2.id(); - *config.add_state() = state2; - - // Will be replaced since the atom changes from overlay to screen. - State state3 = CreateOverlayState(); - int64_t state3Id = state3.id(); - *config.add_state() = state3; - - EXPECT_TRUE(initConfig(config)); - - // Change definitions of state1 and state3. - int64_t screenOnId = 0x4321, screenOffId = 0x1234; - *state1.mutable_map() = CreateScreenStateSimpleOnOffMap(screenOnId, screenOffId); - state3.set_atom_id(util::SCREEN_STATE_CHANGED); - - StatsdConfig newConfig; - *newConfig.add_state() = state3; - *newConfig.add_state() = state1; - *newConfig.add_state() = state2; - - unordered_map stateAtomIdMap; - unordered_map> allStateGroupMaps; - map newStateProtoHashes; - set replacedStates; - EXPECT_TRUE(updateStates(newConfig, oldStateHashes, stateAtomIdMap, allStateGroupMaps, - newStateProtoHashes, replacedStates)); - EXPECT_THAT(replacedStates, ContainerEq(set({state1Id, state3Id}))); - - unordered_map expectedStateAtomIdMap = { - {state1Id, util::SCREEN_STATE_CHANGED}, - {state2Id, util::UID_PROCESS_STATE_CHANGED}, - {state3Id, util::SCREEN_STATE_CHANGED}}; - EXPECT_THAT(stateAtomIdMap, ContainerEq(expectedStateAtomIdMap)); - - unordered_map> expectedStateGroupMaps = { - {state1Id, - {{android::view::DisplayStateEnum::DISPLAY_STATE_OFF, screenOffId}, - {android::view::DisplayStateEnum::DISPLAY_STATE_ON, screenOnId}}}}; - EXPECT_THAT(allStateGroupMaps, ContainerEq(expectedStateGroupMaps)); -} - -TEST_F(ConfigUpdateTest, TestEventMetricPreserve) { - StatsdConfig config; - AtomMatcher startMatcher = CreateScreenTurnedOnAtomMatcher(); - *config.add_atom_matcher() = startMatcher; - AtomMatcher stopMatcher = CreateScreenTurnedOffAtomMatcher(); - *config.add_atom_matcher() = stopMatcher; - AtomMatcher whatMatcher = CreateScreenBrightnessChangedAtomMatcher(); - *config.add_atom_matcher() = whatMatcher; - - Predicate predicate = CreateScreenIsOnPredicate(); - *config.add_predicate() = predicate; - - EventMetric* metric = config.add_event_metric(); - metric->set_id(12345); - metric->set_what(whatMatcher.id()); - metric->set_condition(predicate.id()); - - // Create an initial config. - EXPECT_TRUE(initConfig(config)); - - unordered_map metricToActivationMap; - vector metricsToUpdate(1, UPDATE_UNKNOWN); - EXPECT_TRUE(determineAllMetricUpdateStatuses(config, oldMetricProducerMap, oldMetricProducers, - metricToActivationMap, - /*replacedMatchers*/ {}, /*replacedConditions=*/{}, - /*replacedStates=*/{}, metricsToUpdate)); - EXPECT_EQ(metricsToUpdate[0], UPDATE_PRESERVE); -} - -TEST_F(ConfigUpdateTest, TestEventMetricActivationAdded) { - StatsdConfig config; - AtomMatcher startMatcher = CreateScreenTurnedOnAtomMatcher(); - *config.add_atom_matcher() = startMatcher; - AtomMatcher stopMatcher = CreateScreenTurnedOffAtomMatcher(); - *config.add_atom_matcher() = stopMatcher; - AtomMatcher whatMatcher = CreateScreenBrightnessChangedAtomMatcher(); - *config.add_atom_matcher() = whatMatcher; - - Predicate predicate = CreateScreenIsOnPredicate(); - *config.add_predicate() = predicate; - - EventMetric* metric = config.add_event_metric(); - metric->set_id(12345); - metric->set_what(whatMatcher.id()); - metric->set_condition(predicate.id()); - - // Create an initial config. - EXPECT_TRUE(initConfig(config)); - - // Add a metric activation, which should change the proto, causing replacement. - MetricActivation* activation = config.add_metric_activation(); - activation->set_metric_id(12345); - EventActivation* eventActivation = activation->add_event_activation(); - eventActivation->set_atom_matcher_id(startMatcher.id()); - eventActivation->set_ttl_seconds(5); - - unordered_map metricToActivationMap = {{12345, 0}}; - vector metricsToUpdate(1, UPDATE_UNKNOWN); - EXPECT_TRUE(determineAllMetricUpdateStatuses(config, oldMetricProducerMap, oldMetricProducers, - metricToActivationMap, - /*replacedMatchers*/ {}, /*replacedConditions=*/{}, - /*replacedStates=*/{}, metricsToUpdate)); - EXPECT_EQ(metricsToUpdate[0], UPDATE_REPLACE); -} - -TEST_F(ConfigUpdateTest, TestEventMetricWhatChanged) { - StatsdConfig config; - AtomMatcher startMatcher = CreateScreenTurnedOnAtomMatcher(); - *config.add_atom_matcher() = startMatcher; - AtomMatcher stopMatcher = CreateScreenTurnedOffAtomMatcher(); - *config.add_atom_matcher() = stopMatcher; - AtomMatcher whatMatcher = CreateScreenBrightnessChangedAtomMatcher(); - *config.add_atom_matcher() = whatMatcher; - - Predicate predicate = CreateScreenIsOnPredicate(); - *config.add_predicate() = predicate; - - EventMetric* metric = config.add_event_metric(); - metric->set_id(12345); - metric->set_what(whatMatcher.id()); - metric->set_condition(predicate.id()); - - // Create an initial config. - EXPECT_TRUE(initConfig(config)); - - unordered_map metricToActivationMap; - vector metricsToUpdate(1, UPDATE_UNKNOWN); - EXPECT_TRUE(determineAllMetricUpdateStatuses( - config, oldMetricProducerMap, oldMetricProducers, metricToActivationMap, - /*replacedMatchers*/ {whatMatcher.id()}, /*replacedConditions=*/{}, - /*replacedStates=*/{}, metricsToUpdate)); - EXPECT_EQ(metricsToUpdate[0], UPDATE_REPLACE); -} - -TEST_F(ConfigUpdateTest, TestEventMetricConditionChanged) { - StatsdConfig config; - AtomMatcher startMatcher = CreateScreenTurnedOnAtomMatcher(); - *config.add_atom_matcher() = startMatcher; - AtomMatcher stopMatcher = CreateScreenTurnedOffAtomMatcher(); - *config.add_atom_matcher() = stopMatcher; - AtomMatcher whatMatcher = CreateScreenBrightnessChangedAtomMatcher(); - *config.add_atom_matcher() = whatMatcher; - - Predicate predicate = CreateScreenIsOnPredicate(); - *config.add_predicate() = predicate; - - EventMetric* metric = config.add_event_metric(); - metric->set_id(12345); - metric->set_what(whatMatcher.id()); - metric->set_condition(predicate.id()); - - // Create an initial config. - EXPECT_TRUE(initConfig(config)); - - unordered_map metricToActivationMap; - vector metricsToUpdate(1, UPDATE_UNKNOWN); - EXPECT_TRUE(determineAllMetricUpdateStatuses( - config, oldMetricProducerMap, oldMetricProducers, metricToActivationMap, - /*replacedMatchers*/ {}, /*replacedConditions=*/{predicate.id()}, - /*replacedStates=*/{}, metricsToUpdate)); - EXPECT_EQ(metricsToUpdate[0], UPDATE_REPLACE); -} - -TEST_F(ConfigUpdateTest, TestMetricConditionLinkDepsChanged) { - StatsdConfig config; - AtomMatcher startMatcher = CreateScreenTurnedOnAtomMatcher(); - *config.add_atom_matcher() = startMatcher; - AtomMatcher stopMatcher = CreateScreenTurnedOffAtomMatcher(); - *config.add_atom_matcher() = stopMatcher; - AtomMatcher whatMatcher = CreateScreenBrightnessChangedAtomMatcher(); - *config.add_atom_matcher() = whatMatcher; - - Predicate predicate = CreateScreenIsOnPredicate(); - *config.add_predicate() = predicate; - - Predicate linkPredicate = CreateScreenIsOffPredicate(); - *config.add_predicate() = linkPredicate; - - EventMetric* metric = config.add_event_metric(); - metric->set_id(12345); - metric->set_what(whatMatcher.id()); - metric->set_condition(predicate.id()); - // Doesn't make sense as a real metric definition, but suffices as a separate predicate - // From the one in the condition. - MetricConditionLink* link = metric->add_links(); - link->set_condition(linkPredicate.id()); - - // Create an initial config. - EXPECT_TRUE(initConfig(config)); - - unordered_map metricToActivationMap; - vector metricsToUpdate(1, UPDATE_UNKNOWN); - EXPECT_TRUE(determineAllMetricUpdateStatuses( - config, oldMetricProducerMap, oldMetricProducers, metricToActivationMap, - /*replacedMatchers*/ {}, /*replacedConditions=*/{linkPredicate.id()}, - /*replacedStates=*/{}, metricsToUpdate)); - EXPECT_EQ(metricsToUpdate[0], UPDATE_REPLACE); -} - -TEST_F(ConfigUpdateTest, TestEventMetricActivationDepsChange) { - StatsdConfig config; - AtomMatcher startMatcher = CreateScreenTurnedOnAtomMatcher(); - *config.add_atom_matcher() = startMatcher; - AtomMatcher stopMatcher = CreateScreenTurnedOffAtomMatcher(); - *config.add_atom_matcher() = stopMatcher; - AtomMatcher whatMatcher = CreateScreenBrightnessChangedAtomMatcher(); - *config.add_atom_matcher() = whatMatcher; - - Predicate predicate = CreateScreenIsOnPredicate(); - *config.add_predicate() = predicate; - - EventMetric* metric = config.add_event_metric(); - metric->set_id(12345); - metric->set_what(whatMatcher.id()); - metric->set_condition(predicate.id()); - - MetricActivation* activation = config.add_metric_activation(); - activation->set_metric_id(12345); - EventActivation* eventActivation = activation->add_event_activation(); - eventActivation->set_atom_matcher_id(startMatcher.id()); - eventActivation->set_ttl_seconds(5); - - // Create an initial config. - EXPECT_TRUE(initConfig(config)); - - unordered_map metricToActivationMap = {{12345, 0}}; - vector metricsToUpdate(1, UPDATE_UNKNOWN); - EXPECT_TRUE(determineAllMetricUpdateStatuses( - config, oldMetricProducerMap, oldMetricProducers, metricToActivationMap, - /*replacedMatchers*/ {startMatcher.id()}, /*replacedConditions=*/{}, - /*replacedStates=*/{}, metricsToUpdate)); - EXPECT_EQ(metricsToUpdate[0], UPDATE_REPLACE); -} - -TEST_F(ConfigUpdateTest, TestCountMetricPreserve) { - StatsdConfig config; - AtomMatcher startMatcher = CreateScreenTurnedOnAtomMatcher(); - *config.add_atom_matcher() = startMatcher; - AtomMatcher stopMatcher = CreateScreenTurnedOffAtomMatcher(); - *config.add_atom_matcher() = stopMatcher; - AtomMatcher whatMatcher = CreateScreenBrightnessChangedAtomMatcher(); - *config.add_atom_matcher() = whatMatcher; - - Predicate predicate = CreateScreenIsOnPredicate(); - *config.add_predicate() = predicate; - State sliceState = CreateScreenState(); - *config.add_state() = sliceState; - - CountMetric* metric = config.add_count_metric(); - metric->set_id(12345); - metric->set_what(whatMatcher.id()); - metric->set_condition(predicate.id()); - metric->add_slice_by_state(sliceState.id()); - metric->set_bucket(ONE_HOUR); - - // Create an initial config. - EXPECT_TRUE(initConfig(config)); - - unordered_map metricToActivationMap; - vector metricsToUpdate(1, UPDATE_UNKNOWN); - EXPECT_TRUE(determineAllMetricUpdateStatuses(config, oldMetricProducerMap, oldMetricProducers, - metricToActivationMap, - /*replacedMatchers*/ {}, /*replacedConditions=*/{}, - /*replacedStates=*/{}, metricsToUpdate)); - EXPECT_EQ(metricsToUpdate[0], UPDATE_PRESERVE); -} - -TEST_F(ConfigUpdateTest, TestCountMetricDefinitionChange) { - StatsdConfig config; - AtomMatcher startMatcher = CreateScreenTurnedOnAtomMatcher(); - *config.add_atom_matcher() = startMatcher; - AtomMatcher stopMatcher = CreateScreenTurnedOffAtomMatcher(); - *config.add_atom_matcher() = stopMatcher; - AtomMatcher whatMatcher = CreateScreenBrightnessChangedAtomMatcher(); - *config.add_atom_matcher() = whatMatcher; - - Predicate predicate = CreateScreenIsOnPredicate(); - *config.add_predicate() = predicate; - - CountMetric* metric = config.add_count_metric(); - metric->set_id(12345); - metric->set_what(whatMatcher.id()); - metric->set_condition(predicate.id()); - metric->set_bucket(ONE_HOUR); - - // Create an initial config. - EXPECT_TRUE(initConfig(config)); - - // Change bucket size, which should change the proto, causing replacement. - metric->set_bucket(TEN_MINUTES); - - unordered_map metricToActivationMap; - vector metricsToUpdate(1, UPDATE_UNKNOWN); - EXPECT_TRUE(determineAllMetricUpdateStatuses(config, oldMetricProducerMap, oldMetricProducers, - metricToActivationMap, - /*replacedMatchers*/ {}, /*replacedConditions=*/{}, - /*replacedStates=*/{}, metricsToUpdate)); - EXPECT_EQ(metricsToUpdate[0], UPDATE_REPLACE); -} - -TEST_F(ConfigUpdateTest, TestCountMetricWhatChanged) { - StatsdConfig config; - AtomMatcher startMatcher = CreateScreenTurnedOnAtomMatcher(); - *config.add_atom_matcher() = startMatcher; - AtomMatcher stopMatcher = CreateScreenTurnedOffAtomMatcher(); - *config.add_atom_matcher() = stopMatcher; - AtomMatcher whatMatcher = CreateScreenBrightnessChangedAtomMatcher(); - *config.add_atom_matcher() = whatMatcher; - - Predicate predicate = CreateScreenIsOnPredicate(); - *config.add_predicate() = predicate; - - CountMetric* metric = config.add_count_metric(); - metric->set_id(12345); - metric->set_what(whatMatcher.id()); - metric->set_condition(predicate.id()); - metric->set_bucket(ONE_HOUR); - - // Create an initial config. - EXPECT_TRUE(initConfig(config)); - - unordered_map metricToActivationMap; - vector metricsToUpdate(1, UPDATE_UNKNOWN); - EXPECT_TRUE(determineAllMetricUpdateStatuses( - config, oldMetricProducerMap, oldMetricProducers, metricToActivationMap, - /*replacedMatchers*/ {whatMatcher.id()}, /*replacedConditions=*/{}, - /*replacedStates=*/{}, metricsToUpdate)); - EXPECT_EQ(metricsToUpdate[0], UPDATE_REPLACE); -} - -TEST_F(ConfigUpdateTest, TestCountMetricConditionChanged) { - StatsdConfig config; - AtomMatcher startMatcher = CreateScreenTurnedOnAtomMatcher(); - *config.add_atom_matcher() = startMatcher; - AtomMatcher stopMatcher = CreateScreenTurnedOffAtomMatcher(); - *config.add_atom_matcher() = stopMatcher; - AtomMatcher whatMatcher = CreateScreenBrightnessChangedAtomMatcher(); - *config.add_atom_matcher() = whatMatcher; - - Predicate predicate = CreateScreenIsOnPredicate(); - *config.add_predicate() = predicate; - - CountMetric* metric = config.add_count_metric(); - metric->set_id(12345); - metric->set_what(whatMatcher.id()); - metric->set_condition(predicate.id()); - metric->set_bucket(ONE_HOUR); - - // Create an initial config. - EXPECT_TRUE(initConfig(config)); - - unordered_map metricToActivationMap; - vector metricsToUpdate(1, UPDATE_UNKNOWN); - EXPECT_TRUE(determineAllMetricUpdateStatuses( - config, oldMetricProducerMap, oldMetricProducers, metricToActivationMap, - /*replacedMatchers*/ {}, /*replacedConditions=*/{predicate.id()}, - /*replacedStates=*/{}, metricsToUpdate)); - EXPECT_EQ(metricsToUpdate[0], UPDATE_REPLACE); -} - -TEST_F(ConfigUpdateTest, TestCountMetricStateChanged) { - StatsdConfig config; - AtomMatcher startMatcher = CreateScreenTurnedOnAtomMatcher(); - *config.add_atom_matcher() = startMatcher; - AtomMatcher stopMatcher = CreateScreenTurnedOffAtomMatcher(); - *config.add_atom_matcher() = stopMatcher; - AtomMatcher whatMatcher = CreateScreenBrightnessChangedAtomMatcher(); - *config.add_atom_matcher() = whatMatcher; - - State sliceState = CreateScreenState(); - *config.add_state() = sliceState; - - CountMetric* metric = config.add_count_metric(); - metric->set_id(12345); - metric->set_what(whatMatcher.id()); - metric->add_slice_by_state(sliceState.id()); - metric->set_bucket(ONE_HOUR); - - // Create an initial config. - EXPECT_TRUE(initConfig(config)); - - unordered_map metricToActivationMap; - vector metricsToUpdate(1, UPDATE_UNKNOWN); - EXPECT_TRUE(determineAllMetricUpdateStatuses( - config, oldMetricProducerMap, oldMetricProducers, metricToActivationMap, - /*replacedMatchers*/ {}, /*replacedConditions=*/{}, - /*replacedStates=*/{sliceState.id()}, metricsToUpdate)); - EXPECT_EQ(metricsToUpdate[0], UPDATE_REPLACE); -} - -TEST_F(ConfigUpdateTest, TestGaugeMetricPreserve) { - StatsdConfig config; - AtomMatcher startMatcher = CreateScreenTurnedOnAtomMatcher(); - *config.add_atom_matcher() = startMatcher; - AtomMatcher stopMatcher = CreateScreenTurnedOffAtomMatcher(); - *config.add_atom_matcher() = stopMatcher; - AtomMatcher whatMatcher = CreateScreenBrightnessChangedAtomMatcher(); - *config.add_atom_matcher() = whatMatcher; - - Predicate predicate = CreateScreenIsOnPredicate(); - *config.add_predicate() = predicate; - - *config.add_gauge_metric() = createGaugeMetric( - "GAUGE1", whatMatcher.id(), GaugeMetric::RANDOM_ONE_SAMPLE, predicate.id(), nullopt); - - EXPECT_TRUE(initConfig(config)); - - unordered_map metricToActivationMap; - vector metricsToUpdate(1, UPDATE_UNKNOWN); - EXPECT_TRUE(determineAllMetricUpdateStatuses(config, oldMetricProducerMap, oldMetricProducers, - metricToActivationMap, - /*replacedMatchers*/ {}, /*replacedConditions=*/{}, - /*replacedStates=*/{}, metricsToUpdate)); - EXPECT_EQ(metricsToUpdate[0], UPDATE_PRESERVE); -} - -TEST_F(ConfigUpdateTest, TestGaugeMetricDefinitionChange) { - StatsdConfig config; - AtomMatcher whatMatcher = CreateScreenBrightnessChangedAtomMatcher(); - *config.add_atom_matcher() = whatMatcher; - - *config.add_gauge_metric() = createGaugeMetric( - "GAUGE1", whatMatcher.id(), GaugeMetric::RANDOM_ONE_SAMPLE, nullopt, nullopt); - - EXPECT_TRUE(initConfig(config)); - - // Change split bucket on app upgrade, which should change the proto, causing replacement. - config.mutable_gauge_metric(0)->set_split_bucket_for_app_upgrade(false); - - unordered_map metricToActivationMap; - vector metricsToUpdate(1, UPDATE_UNKNOWN); - EXPECT_TRUE(determineAllMetricUpdateStatuses(config, oldMetricProducerMap, oldMetricProducers, - metricToActivationMap, - /*replacedMatchers*/ {}, /*replacedConditions=*/{}, - /*replacedStates=*/{}, metricsToUpdate)); - EXPECT_EQ(metricsToUpdate[0], UPDATE_REPLACE); -} - -TEST_F(ConfigUpdateTest, TestGaugeMetricWhatChanged) { - StatsdConfig config; - AtomMatcher whatMatcher = CreateScreenBrightnessChangedAtomMatcher(); - *config.add_atom_matcher() = whatMatcher; - - *config.add_gauge_metric() = createGaugeMetric( - "GAUGE1", whatMatcher.id(), GaugeMetric::RANDOM_ONE_SAMPLE, nullopt, nullopt); - - EXPECT_TRUE(initConfig(config)); - - unordered_map metricToActivationMap; - vector metricsToUpdate(1, UPDATE_UNKNOWN); - EXPECT_TRUE(determineAllMetricUpdateStatuses( - config, oldMetricProducerMap, oldMetricProducers, metricToActivationMap, - /*replacedMatchers*/ {whatMatcher.id()}, /*replacedConditions=*/{}, - /*replacedStates=*/{}, metricsToUpdate)); - EXPECT_EQ(metricsToUpdate[0], UPDATE_REPLACE); -} - -TEST_F(ConfigUpdateTest, TestGaugeMetricConditionChanged) { - StatsdConfig config; - AtomMatcher startMatcher = CreateScreenTurnedOnAtomMatcher(); - *config.add_atom_matcher() = startMatcher; - AtomMatcher stopMatcher = CreateScreenTurnedOffAtomMatcher(); - *config.add_atom_matcher() = stopMatcher; - AtomMatcher whatMatcher = CreateScreenBrightnessChangedAtomMatcher(); - *config.add_atom_matcher() = whatMatcher; - - Predicate predicate = CreateScreenIsOnPredicate(); - *config.add_predicate() = predicate; - - *config.add_gauge_metric() = createGaugeMetric( - "GAUGE1", whatMatcher.id(), GaugeMetric::RANDOM_ONE_SAMPLE, predicate.id(), nullopt); - - EXPECT_TRUE(initConfig(config)); - - unordered_map metricToActivationMap; - vector metricsToUpdate(1, UPDATE_UNKNOWN); - EXPECT_TRUE(determineAllMetricUpdateStatuses( - config, oldMetricProducerMap, oldMetricProducers, metricToActivationMap, - /*replacedMatchers*/ {}, /*replacedConditions=*/{predicate.id()}, - /*replacedStates=*/{}, metricsToUpdate)); - EXPECT_EQ(metricsToUpdate[0], UPDATE_REPLACE); -} - -TEST_F(ConfigUpdateTest, TestGaugeMetricTriggerEventChanged) { - StatsdConfig config; - AtomMatcher triggerEvent = CreateScreenTurnedOnAtomMatcher(); - *config.add_atom_matcher() = triggerEvent; - AtomMatcher whatMatcher = CreateTemperatureAtomMatcher(); - *config.add_atom_matcher() = whatMatcher; - - *config.add_gauge_metric() = createGaugeMetric( - "GAUGE1", whatMatcher.id(), GaugeMetric::FIRST_N_SAMPLES, nullopt, triggerEvent.id()); - - EXPECT_TRUE(initConfig(config)); - - unordered_map metricToActivationMap; - vector metricsToUpdate(1, UPDATE_UNKNOWN); - EXPECT_TRUE(determineAllMetricUpdateStatuses( - config, oldMetricProducerMap, oldMetricProducers, metricToActivationMap, - /*replacedMatchers*/ {triggerEvent.id()}, /*replacedConditions=*/{}, - /*replacedStates=*/{}, metricsToUpdate)); - EXPECT_EQ(metricsToUpdate[0], UPDATE_REPLACE); -} - -TEST_F(ConfigUpdateTest, TestDurationMetricPreserve) { - StatsdConfig config; - AtomMatcher startMatcher = CreateScreenTurnedOnAtomMatcher(); - *config.add_atom_matcher() = startMatcher; - AtomMatcher stopMatcher = CreateScreenTurnedOffAtomMatcher(); - *config.add_atom_matcher() = stopMatcher; - - Predicate what = CreateScreenIsOnPredicate(); - *config.add_predicate() = what; - Predicate condition = CreateScreenIsOffPredicate(); - *config.add_predicate() = condition; - - State sliceState = CreateScreenState(); - *config.add_state() = sliceState; - - *config.add_duration_metric() = - createDurationMetric("DURATION1", what.id(), condition.id(), {sliceState.id()}); - EXPECT_TRUE(initConfig(config)); - - unordered_map metricToActivationMap; - vector metricsToUpdate(1, UPDATE_UNKNOWN); - EXPECT_TRUE(determineAllMetricUpdateStatuses(config, oldMetricProducerMap, oldMetricProducers, - metricToActivationMap, - /*replacedMatchers*/ {}, /*replacedConditions=*/{}, - /*replacedStates=*/{}, metricsToUpdate)); - EXPECT_EQ(metricsToUpdate[0], UPDATE_PRESERVE); -} - -TEST_F(ConfigUpdateTest, TestDurationMetricDefinitionChange) { - StatsdConfig config; - AtomMatcher startMatcher = CreateScreenTurnedOnAtomMatcher(); - *config.add_atom_matcher() = startMatcher; - AtomMatcher stopMatcher = CreateScreenTurnedOffAtomMatcher(); - *config.add_atom_matcher() = stopMatcher; - - Predicate what = CreateScreenIsOnPredicate(); - *config.add_predicate() = what; - - *config.add_duration_metric() = createDurationMetric("DURATION1", what.id(), nullopt, {}); - EXPECT_TRUE(initConfig(config)); - - config.mutable_duration_metric(0)->set_aggregation_type(DurationMetric::MAX_SPARSE); - - unordered_map metricToActivationMap; - vector metricsToUpdate(1, UPDATE_UNKNOWN); - EXPECT_TRUE(determineAllMetricUpdateStatuses(config, oldMetricProducerMap, oldMetricProducers, - metricToActivationMap, /*replacedMatchers*/ {}, - /*replacedConditions=*/{}, /*replacedStates=*/{}, - metricsToUpdate)); - EXPECT_EQ(metricsToUpdate[0], UPDATE_REPLACE); -} - -TEST_F(ConfigUpdateTest, TestDurationMetricWhatChanged) { - StatsdConfig config; - AtomMatcher startMatcher = CreateScreenTurnedOnAtomMatcher(); - *config.add_atom_matcher() = startMatcher; - AtomMatcher stopMatcher = CreateScreenTurnedOffAtomMatcher(); - *config.add_atom_matcher() = stopMatcher; - - Predicate what = CreateScreenIsOnPredicate(); - *config.add_predicate() = what; - - *config.add_duration_metric() = createDurationMetric("DURATION1", what.id(), nullopt, {}); - EXPECT_TRUE(initConfig(config)); - - unordered_map metricToActivationMap; - vector metricsToUpdate(1, UPDATE_UNKNOWN); - EXPECT_TRUE(determineAllMetricUpdateStatuses( - config, oldMetricProducerMap, oldMetricProducers, metricToActivationMap, - /*replacedMatchers*/ {}, /*replacedConditions=*/{what.id()}, - /*replacedStates=*/{}, metricsToUpdate)); - EXPECT_EQ(metricsToUpdate[0], UPDATE_REPLACE); -} - -TEST_F(ConfigUpdateTest, TestDurationMetricConditionChanged) { - StatsdConfig config; - AtomMatcher startMatcher = CreateScreenTurnedOnAtomMatcher(); - *config.add_atom_matcher() = startMatcher; - AtomMatcher stopMatcher = CreateScreenTurnedOffAtomMatcher(); - *config.add_atom_matcher() = stopMatcher; - - Predicate what = CreateScreenIsOnPredicate(); - *config.add_predicate() = what; - Predicate condition = CreateScreenIsOffPredicate(); - *config.add_predicate() = condition; - - *config.add_duration_metric() = createDurationMetric("DURATION", what.id(), condition.id(), {}); - EXPECT_TRUE(initConfig(config)); - - unordered_map metricToActivationMap; - vector metricsToUpdate(1, UPDATE_UNKNOWN); - EXPECT_TRUE(determineAllMetricUpdateStatuses( - config, oldMetricProducerMap, oldMetricProducers, metricToActivationMap, - /*replacedMatchers*/ {}, /*replacedConditions=*/{condition.id()}, - /*replacedStates=*/{}, metricsToUpdate)); - EXPECT_EQ(metricsToUpdate[0], UPDATE_REPLACE); -} - -TEST_F(ConfigUpdateTest, TestDurationMetricStateChange) { - StatsdConfig config; - AtomMatcher startMatcher = CreateScreenTurnedOnAtomMatcher(); - *config.add_atom_matcher() = startMatcher; - AtomMatcher stopMatcher = CreateScreenTurnedOffAtomMatcher(); - *config.add_atom_matcher() = stopMatcher; - - Predicate what = CreateScreenIsOnPredicate(); - *config.add_predicate() = what; - - State sliceState = CreateScreenState(); - *config.add_state() = sliceState; - - *config.add_duration_metric() = - createDurationMetric("DURATION1", what.id(), nullopt, {sliceState.id()}); - EXPECT_TRUE(initConfig(config)); - - unordered_map metricToActivationMap; - vector metricsToUpdate(1, UPDATE_UNKNOWN); - EXPECT_TRUE(determineAllMetricUpdateStatuses( - config, oldMetricProducerMap, oldMetricProducers, metricToActivationMap, - /*replacedMatchers*/ {}, /*replacedConditions=*/{}, - /*replacedStates=*/{sliceState.id()}, metricsToUpdate)); - EXPECT_EQ(metricsToUpdate[0], UPDATE_REPLACE); -} - -TEST_F(ConfigUpdateTest, TestValueMetricPreserve) { - StatsdConfig config; - AtomMatcher startMatcher = CreateScreenTurnedOnAtomMatcher(); - *config.add_atom_matcher() = startMatcher; - AtomMatcher stopMatcher = CreateScreenTurnedOffAtomMatcher(); - *config.add_atom_matcher() = stopMatcher; - AtomMatcher whatMatcher = CreateTemperatureAtomMatcher(); - *config.add_atom_matcher() = whatMatcher; - - Predicate predicate = CreateScreenIsOnPredicate(); - *config.add_predicate() = predicate; - State sliceState = CreateScreenState(); - *config.add_state() = sliceState; - - *config.add_value_metric() = - createValueMetric("VALUE1", whatMatcher, 2, predicate.id(), {sliceState.id()}); - EXPECT_TRUE(initConfig(config)); - - unordered_map metricToActivationMap; - vector metricsToUpdate(1, UPDATE_UNKNOWN); - EXPECT_TRUE(determineAllMetricUpdateStatuses(config, oldMetricProducerMap, oldMetricProducers, - metricToActivationMap, - /*replacedMatchers*/ {}, /*replacedConditions=*/{}, - /*replacedStates=*/{}, metricsToUpdate)); - EXPECT_EQ(metricsToUpdate[0], UPDATE_PRESERVE); -} - -TEST_F(ConfigUpdateTest, TestValueMetricDefinitionChange) { - StatsdConfig config; - AtomMatcher whatMatcher = CreateScreenBrightnessChangedAtomMatcher(); - *config.add_atom_matcher() = whatMatcher; - - *config.add_value_metric() = createValueMetric("VALUE1", whatMatcher, 2, nullopt, {}); - EXPECT_TRUE(initConfig(config)); - - // Change skip zero diff output, which should change the proto, causing replacement. - config.mutable_value_metric(0)->set_skip_zero_diff_output(true); - - unordered_map metricToActivationMap; - vector metricsToUpdate(1, UPDATE_UNKNOWN); - EXPECT_TRUE(determineAllMetricUpdateStatuses(config, oldMetricProducerMap, oldMetricProducers, - metricToActivationMap, - /*replacedMatchers*/ {}, /*replacedConditions=*/{}, - /*replacedStates=*/{}, metricsToUpdate)); - EXPECT_EQ(metricsToUpdate[0], UPDATE_REPLACE); -} - -TEST_F(ConfigUpdateTest, TestValueMetricWhatChanged) { - StatsdConfig config; - AtomMatcher whatMatcher = CreateTemperatureAtomMatcher(); - *config.add_atom_matcher() = whatMatcher; - - *config.add_value_metric() = createValueMetric("VALUE1", whatMatcher, 2, nullopt, {}); - EXPECT_TRUE(initConfig(config)); - - unordered_map metricToActivationMap; - vector metricsToUpdate(1, UPDATE_UNKNOWN); - EXPECT_TRUE(determineAllMetricUpdateStatuses( - config, oldMetricProducerMap, oldMetricProducers, metricToActivationMap, - /*replacedMatchers*/ {whatMatcher.id()}, /*replacedConditions=*/{}, - /*replacedStates=*/{}, metricsToUpdate)); - EXPECT_EQ(metricsToUpdate[0], UPDATE_REPLACE); -} - -TEST_F(ConfigUpdateTest, TestValueMetricConditionChanged) { - StatsdConfig config; - AtomMatcher startMatcher = CreateScreenTurnedOnAtomMatcher(); - *config.add_atom_matcher() = startMatcher; - AtomMatcher stopMatcher = CreateScreenTurnedOffAtomMatcher(); - *config.add_atom_matcher() = stopMatcher; - AtomMatcher whatMatcher = CreateTemperatureAtomMatcher(); - *config.add_atom_matcher() = whatMatcher; - - Predicate predicate = CreateScreenIsOnPredicate(); - *config.add_predicate() = predicate; - - *config.add_value_metric() = createValueMetric("VALUE1", whatMatcher, 2, predicate.id(), {}); - EXPECT_TRUE(initConfig(config)); - - unordered_map metricToActivationMap; - vector metricsToUpdate(1, UPDATE_UNKNOWN); - EXPECT_TRUE(determineAllMetricUpdateStatuses( - config, oldMetricProducerMap, oldMetricProducers, metricToActivationMap, - /*replacedMatchers*/ {}, /*replacedConditions=*/{predicate.id()}, - /*replacedStates=*/{}, metricsToUpdate)); - EXPECT_EQ(metricsToUpdate[0], UPDATE_REPLACE); -} - -TEST_F(ConfigUpdateTest, TestValueMetricStateChanged) { - StatsdConfig config; - AtomMatcher whatMatcher = CreateScreenBrightnessChangedAtomMatcher(); - *config.add_atom_matcher() = whatMatcher; - - State sliceState = CreateScreenState(); - *config.add_state() = sliceState; - - *config.add_value_metric() = - createValueMetric("VALUE1", whatMatcher, 2, nullopt, {sliceState.id()}); - EXPECT_TRUE(initConfig(config)); - - unordered_map metricToActivationMap; - vector metricsToUpdate(1, UPDATE_UNKNOWN); - EXPECT_TRUE(determineAllMetricUpdateStatuses( - config, oldMetricProducerMap, oldMetricProducers, metricToActivationMap, - /*replacedMatchers*/ {}, /*replacedConditions=*/{}, - /*replacedStates=*/{sliceState.id()}, metricsToUpdate)); - EXPECT_EQ(metricsToUpdate[0], UPDATE_REPLACE); -} - -TEST_F(ConfigUpdateTest, TestUpdateEventMetrics) { - StatsdConfig config; - - // Add atom matchers/predicates. These are mostly needed for initStatsdConfig - AtomMatcher matcher1 = CreateScreenTurnedOnAtomMatcher(); - int64_t matcher1Id = matcher1.id(); - *config.add_atom_matcher() = matcher1; - - AtomMatcher matcher2 = CreateScreenTurnedOffAtomMatcher(); - int64_t matcher2Id = matcher2.id(); - *config.add_atom_matcher() = matcher2; - - AtomMatcher matcher3 = CreateStartScheduledJobAtomMatcher(); - int64_t matcher3Id = matcher3.id(); - *config.add_atom_matcher() = matcher3; - - AtomMatcher matcher4 = CreateFinishScheduledJobAtomMatcher(); - int64_t matcher4Id = matcher4.id(); - *config.add_atom_matcher() = matcher4; - - AtomMatcher matcher5 = CreateBatterySaverModeStartAtomMatcher(); - int64_t matcher5Id = matcher5.id(); - *config.add_atom_matcher() = matcher5; - - Predicate predicate1 = CreateScreenIsOnPredicate(); - int64_t predicate1Id = predicate1.id(); - *config.add_predicate() = predicate1; - - Predicate predicate2 = CreateScheduledJobPredicate(); - int64_t predicate2Id = predicate2.id(); - *config.add_predicate() = predicate2; - - // Add a few event metrics. - // Will be preserved. - EventMetric event1 = createEventMetric("EVENT1", matcher1Id, predicate2Id); - int64_t event1Id = event1.id(); - *config.add_event_metric() = event1; - - // Will be replaced. - EventMetric event2 = createEventMetric("EVENT2", matcher2Id, nullopt); - int64_t event2Id = event2.id(); - *config.add_event_metric() = event2; - - // Will be replaced. - EventMetric event3 = createEventMetric("EVENT3", matcher3Id, nullopt); - int64_t event3Id = event3.id(); - *config.add_event_metric() = event3; - - MetricActivation event3Activation; - event3Activation.set_metric_id(event3Id); - EventActivation* eventActivation = event3Activation.add_event_activation(); - eventActivation->set_atom_matcher_id(matcher5Id); - eventActivation->set_ttl_seconds(5); - *config.add_metric_activation() = event3Activation; - - // Will be replaced. - EventMetric event4 = createEventMetric("EVENT4", matcher4Id, predicate1Id); - int64_t event4Id = event4.id(); - *config.add_event_metric() = event4; - - // Will be deleted. - EventMetric event5 = createEventMetric("EVENT5", matcher5Id, nullopt); - int64_t event5Id = event5.id(); - *config.add_event_metric() = event5; - - EXPECT_TRUE(initConfig(config)); - - // Used later to ensure the condition wizard is replaced. Get it before doing the update. - sp oldConditionWizard = oldMetricProducers[0]->mWizard; - EXPECT_EQ(oldConditionWizard->getStrongCount(), oldMetricProducers.size() + 1); - - // Add a condition to event2, causing it to be replaced. - event2.set_condition(predicate1Id); - - // Mark matcher 5 as replaced. Causes event3 to be replaced. - set replacedMatchers; - replacedMatchers.insert(matcher5Id); - - // Mark predicate 1 as replaced. Causes event4 to be replaced. - set replacedConditions; - replacedConditions.insert(predicate1Id); - - // Fake that predicate 2 is true. - ASSERT_EQ(oldMetricProducers[0]->getMetricId(), event1Id); - oldMetricProducers[0]->onConditionChanged(true, /*timestamp=*/0); - EXPECT_EQ(oldMetricProducers[0]->mCondition, ConditionState::kTrue); - - // New event metric. Should have an initial condition of true since it depends on predicate2. - EventMetric event6 = createEventMetric("EVENT6", matcher3Id, predicate2Id); - int64_t event6Id = event6.id(); - MetricActivation event6Activation; - event6Activation.set_metric_id(event6Id); - eventActivation = event6Activation.add_event_activation(); - eventActivation->set_atom_matcher_id(matcher5Id); - eventActivation->set_ttl_seconds(20); - - // Map the matchers and predicates in reverse order to force the indices to change. - std::unordered_map newAtomMatchingTrackerMap; - const int matcher5Index = 0; - newAtomMatchingTrackerMap[matcher5Id] = 0; - const int matcher4Index = 1; - newAtomMatchingTrackerMap[matcher4Id] = 1; - const int matcher3Index = 2; - newAtomMatchingTrackerMap[matcher3Id] = 2; - const int matcher2Index = 3; - newAtomMatchingTrackerMap[matcher2Id] = 3; - const int matcher1Index = 4; - newAtomMatchingTrackerMap[matcher1Id] = 4; - // Use the existing matchers. A bit hacky, but saves code and we don't rely on them. - vector> newAtomMatchingTrackers(5); - std::reverse_copy(oldAtomMatchingTrackers.begin(), oldAtomMatchingTrackers.end(), - newAtomMatchingTrackers.begin()); - - std::unordered_map newConditionTrackerMap; - const int predicate2Index = 0; - newConditionTrackerMap[predicate2Id] = 0; - const int predicate1Index = 1; - newConditionTrackerMap[predicate1Id] = 1; - // Use the existing conditionTrackers. A bit hacky, but saves code and we don't rely on them. - vector> newConditionTrackers(2); - std::reverse_copy(oldConditionTrackers.begin(), oldConditionTrackers.end(), - newConditionTrackers.begin()); - // Fake that predicate2 is true. - vector conditionCache = {ConditionState::kTrue, ConditionState::kUnknown}; - - StatsdConfig newConfig; - *newConfig.add_event_metric() = event6; - const int event6Index = 0; - *newConfig.add_event_metric() = event3; - const int event3Index = 1; - *newConfig.add_event_metric() = event1; - const int event1Index = 2; - *newConfig.add_event_metric() = event4; - const int event4Index = 3; - *newConfig.add_event_metric() = event2; - const int event2Index = 4; - *newConfig.add_metric_activation() = event3Activation; - *newConfig.add_metric_activation() = event6Activation; - - // Output data structures to validate. - unordered_map newMetricProducerMap; - vector> newMetricProducers; - unordered_map> conditionToMetricMap; - unordered_map> trackerToMetricMap; - set noReportMetricIds; - unordered_map> activationAtomTrackerToMetricMap; - unordered_map> deactivationAtomTrackerToMetricMap; - vector metricsWithActivation; - set replacedMetrics; - EXPECT_TRUE(updateMetrics( - key, newConfig, /*timeBaseNs=*/123, /*currentTimeNs=*/12345, new StatsPullerManager(), - oldAtomMatchingTrackerMap, newAtomMatchingTrackerMap, replacedMatchers, - newAtomMatchingTrackers, newConditionTrackerMap, replacedConditions, - newConditionTrackers, conditionCache, /*stateAtomIdMap=*/{}, /*allStateGroupMaps=*/{}, - /*replacedStates=*/{}, oldMetricProducerMap, oldMetricProducers, newMetricProducerMap, - newMetricProducers, conditionToMetricMap, trackerToMetricMap, noReportMetricIds, - activationAtomTrackerToMetricMap, deactivationAtomTrackerToMetricMap, - metricsWithActivation, replacedMetrics)); - - unordered_map expectedMetricProducerMap = { - {event1Id, event1Index}, {event2Id, event2Index}, {event3Id, event3Index}, - {event4Id, event4Index}, {event6Id, event6Index}, - }; - EXPECT_THAT(newMetricProducerMap, ContainerEq(expectedMetricProducerMap)); - EXPECT_EQ(replacedMetrics, set({event2Id, event3Id, event4Id})); - - // Make sure preserved metrics are the same. - ASSERT_EQ(newMetricProducers.size(), 5); - EXPECT_EQ(oldMetricProducers[oldMetricProducerMap.at(event1Id)], - newMetricProducers[newMetricProducerMap.at(event1Id)]); - - // Make sure replaced metrics are different. - EXPECT_NE(oldMetricProducers[oldMetricProducerMap.at(event2Id)], - newMetricProducers[newMetricProducerMap.at(event2Id)]); - EXPECT_NE(oldMetricProducers[oldMetricProducerMap.at(event3Id)], - newMetricProducers[newMetricProducerMap.at(event3Id)]); - EXPECT_NE(oldMetricProducers[oldMetricProducerMap.at(event4Id)], - newMetricProducers[newMetricProducerMap.at(event4Id)]); - - // Verify the conditionToMetricMap. - ASSERT_EQ(conditionToMetricMap.size(), 2); - const vector& condition1Metrics = conditionToMetricMap[predicate1Index]; - EXPECT_THAT(condition1Metrics, UnorderedElementsAre(event2Index, event4Index)); - const vector& condition2Metrics = conditionToMetricMap[predicate2Index]; - EXPECT_THAT(condition2Metrics, UnorderedElementsAre(event1Index, event6Index)); - - // Verify the trackerToMetricMap. - ASSERT_EQ(trackerToMetricMap.size(), 4); - const vector& matcher1Metrics = trackerToMetricMap[matcher1Index]; - EXPECT_THAT(matcher1Metrics, UnorderedElementsAre(event1Index)); - const vector& matcher2Metrics = trackerToMetricMap[matcher2Index]; - EXPECT_THAT(matcher2Metrics, UnorderedElementsAre(event2Index)); - const vector& matcher3Metrics = trackerToMetricMap[matcher3Index]; - EXPECT_THAT(matcher3Metrics, UnorderedElementsAre(event3Index, event6Index)); - const vector& matcher4Metrics = trackerToMetricMap[matcher4Index]; - EXPECT_THAT(matcher4Metrics, UnorderedElementsAre(event4Index)); - - // Verify event activation/deactivation maps. - ASSERT_EQ(activationAtomTrackerToMetricMap.size(), 1); - EXPECT_THAT(activationAtomTrackerToMetricMap[matcher5Index], - UnorderedElementsAre(event3Index, event6Index)); - ASSERT_EQ(deactivationAtomTrackerToMetricMap.size(), 0); - ASSERT_EQ(metricsWithActivation.size(), 2); - EXPECT_THAT(metricsWithActivation, UnorderedElementsAre(event3Index, event6Index)); - - // Verify tracker indices/ids/conditions are correct. - EXPECT_EQ(newMetricProducers[event1Index]->getMetricId(), event1Id); - EXPECT_EQ(newMetricProducers[event1Index]->mConditionTrackerIndex, predicate2Index); - EXPECT_EQ(newMetricProducers[event1Index]->mCondition, ConditionState::kTrue); - EXPECT_EQ(newMetricProducers[event2Index]->getMetricId(), event2Id); - EXPECT_EQ(newMetricProducers[event2Index]->mConditionTrackerIndex, predicate1Index); - EXPECT_EQ(newMetricProducers[event2Index]->mCondition, ConditionState::kUnknown); - EXPECT_EQ(newMetricProducers[event3Index]->getMetricId(), event3Id); - EXPECT_EQ(newMetricProducers[event3Index]->mConditionTrackerIndex, -1); - EXPECT_EQ(newMetricProducers[event3Index]->mCondition, ConditionState::kTrue); - EXPECT_EQ(newMetricProducers[event4Index]->getMetricId(), event4Id); - EXPECT_EQ(newMetricProducers[event4Index]->mConditionTrackerIndex, predicate1Index); - EXPECT_EQ(newMetricProducers[event4Index]->mCondition, ConditionState::kUnknown); - EXPECT_EQ(newMetricProducers[event6Index]->getMetricId(), event6Id); - EXPECT_EQ(newMetricProducers[event6Index]->mConditionTrackerIndex, predicate2Index); - EXPECT_EQ(newMetricProducers[event6Index]->mCondition, ConditionState::kTrue); - - sp newConditionWizard = newMetricProducers[0]->mWizard; - EXPECT_NE(newConditionWizard, oldConditionWizard); - EXPECT_EQ(newConditionWizard->getStrongCount(), newMetricProducers.size() + 1); - oldMetricProducers.clear(); - // Only reference to the old wizard should be the one in the test. - EXPECT_EQ(oldConditionWizard->getStrongCount(), 1); -} - -TEST_F(ConfigUpdateTest, TestUpdateCountMetrics) { - StatsdConfig config; - - // Add atom matchers/predicates/states. These are mostly needed for initStatsdConfig. - AtomMatcher matcher1 = CreateScreenTurnedOnAtomMatcher(); - int64_t matcher1Id = matcher1.id(); - *config.add_atom_matcher() = matcher1; - - AtomMatcher matcher2 = CreateScreenTurnedOffAtomMatcher(); - int64_t matcher2Id = matcher2.id(); - *config.add_atom_matcher() = matcher2; - - AtomMatcher matcher3 = CreateStartScheduledJobAtomMatcher(); - int64_t matcher3Id = matcher3.id(); - *config.add_atom_matcher() = matcher3; - - AtomMatcher matcher4 = CreateFinishScheduledJobAtomMatcher(); - int64_t matcher4Id = matcher4.id(); - *config.add_atom_matcher() = matcher4; - - AtomMatcher matcher5 = CreateBatterySaverModeStartAtomMatcher(); - int64_t matcher5Id = matcher5.id(); - *config.add_atom_matcher() = matcher5; - - Predicate predicate1 = CreateScreenIsOnPredicate(); - int64_t predicate1Id = predicate1.id(); - *config.add_predicate() = predicate1; - - State state1 = CreateScreenStateWithOnOffMap(0x123, 0x321); - int64_t state1Id = state1.id(); - *config.add_state() = state1; - - State state2 = CreateScreenState(); - int64_t state2Id = state2.id(); - *config.add_state() = state2; - - // Add a few count metrics. - // Will be preserved. - CountMetric count1 = createCountMetric("COUNT1", matcher1Id, predicate1Id, {state1Id}); - int64_t count1Id = count1.id(); - *config.add_count_metric() = count1; - - // Will be replaced. - CountMetric count2 = createCountMetric("COUNT2", matcher2Id, nullopt, {}); - int64_t count2Id = count2.id(); - *config.add_count_metric() = count2; - - // Will be replaced. - CountMetric count3 = createCountMetric("COUNT3", matcher3Id, nullopt, {}); - int64_t count3Id = count3.id(); - *config.add_count_metric() = count3; - - // Will be replaced. - CountMetric count4 = createCountMetric("COUNT4", matcher4Id, nullopt, {state2Id}); - int64_t count4Id = count4.id(); - *config.add_count_metric() = count4; - - // Will be deleted. - CountMetric count5 = createCountMetric("COUNT5", matcher5Id, nullopt, {}); - int64_t count5Id = count5.id(); - *config.add_count_metric() = count5; - - EXPECT_TRUE(initConfig(config)); - - // Change bucket size of count2, causing it to be replaced. - count2.set_bucket(ONE_HOUR); - - // Mark matcher 3 as replaced. Causes count3 to be replaced. - set replacedMatchers; - replacedMatchers.insert(matcher3Id); - - // Mark state 2 as replaced and change the state to be about a different atom. - // Causes count4 to be replaced. - set replacedStates; - replacedStates.insert(state2Id); - state2.set_atom_id(util::BATTERY_SAVER_MODE_STATE_CHANGED); - - // Fake that predicate 1 is true for count metric 1. - ASSERT_EQ(oldMetricProducers[0]->getMetricId(), count1Id); - oldMetricProducers[0]->onConditionChanged(true, /*timestamp=*/0); - EXPECT_EQ(oldMetricProducers[0]->mCondition, ConditionState::kTrue); - - EXPECT_EQ(StateManager::getInstance().getStateTrackersCount(), 1); - // Tell the StateManager that the screen is on. - unique_ptr event = - CreateScreenStateChangedEvent(0, android::view::DisplayStateEnum::DISPLAY_STATE_ON); - StateManager::getInstance().onLogEvent(*event); - - // New count metric. Should have an initial condition of true since it depends on predicate1. - CountMetric count6 = createCountMetric("EVENT6", matcher2Id, predicate1Id, {state1Id}); - int64_t count6Id = count6.id(); - - // Map the matchers and predicates in reverse order to force the indices to change. - std::unordered_map newAtomMatchingTrackerMap; - const int matcher5Index = 0; - newAtomMatchingTrackerMap[matcher5Id] = 0; - const int matcher4Index = 1; - newAtomMatchingTrackerMap[matcher4Id] = 1; - const int matcher3Index = 2; - newAtomMatchingTrackerMap[matcher3Id] = 2; - const int matcher2Index = 3; - newAtomMatchingTrackerMap[matcher2Id] = 3; - const int matcher1Index = 4; - newAtomMatchingTrackerMap[matcher1Id] = 4; - // Use the existing matchers. A bit hacky, but saves code and we don't rely on them. - vector> newAtomMatchingTrackers(5); - std::reverse_copy(oldAtomMatchingTrackers.begin(), oldAtomMatchingTrackers.end(), - newAtomMatchingTrackers.begin()); - - std::unordered_map newConditionTrackerMap; - const int predicate1Index = 0; - newConditionTrackerMap[predicate1Id] = 0; - // Use the existing conditionTrackers. A bit hacky, but saves code and we don't rely on them. - vector> newConditionTrackers(1); - std::reverse_copy(oldConditionTrackers.begin(), oldConditionTrackers.end(), - newConditionTrackers.begin()); - // Fake that predicate1 is true for all new metrics. - vector conditionCache = {ConditionState::kTrue}; - - StatsdConfig newConfig; - *newConfig.add_count_metric() = count6; - const int count6Index = 0; - *newConfig.add_count_metric() = count3; - const int count3Index = 1; - *newConfig.add_count_metric() = count1; - const int count1Index = 2; - *newConfig.add_count_metric() = count4; - const int count4Index = 3; - *newConfig.add_count_metric() = count2; - const int count2Index = 4; - - *newConfig.add_state() = state1; - *newConfig.add_state() = state2; - - unordered_map stateAtomIdMap; - unordered_map> allStateGroupMaps; - map stateProtoHashes; - EXPECT_TRUE(initStates(newConfig, stateAtomIdMap, allStateGroupMaps, stateProtoHashes)); - EXPECT_EQ(stateAtomIdMap[state2Id], util::BATTERY_SAVER_MODE_STATE_CHANGED); - - // Output data structures to validate. - unordered_map newMetricProducerMap; - vector> newMetricProducers; - unordered_map> conditionToMetricMap; - unordered_map> trackerToMetricMap; - set noReportMetricIds; - unordered_map> activationAtomTrackerToMetricMap; - unordered_map> deactivationAtomTrackerToMetricMap; - vector metricsWithActivation; - set replacedMetrics; - EXPECT_TRUE(updateMetrics( - key, newConfig, /*timeBaseNs=*/123, /*currentTimeNs=*/12345, new StatsPullerManager(), - oldAtomMatchingTrackerMap, newAtomMatchingTrackerMap, replacedMatchers, - newAtomMatchingTrackers, newConditionTrackerMap, /*replacedConditions=*/{}, - newConditionTrackers, conditionCache, stateAtomIdMap, allStateGroupMaps, replacedStates, - oldMetricProducerMap, oldMetricProducers, newMetricProducerMap, newMetricProducers, - conditionToMetricMap, trackerToMetricMap, noReportMetricIds, - activationAtomTrackerToMetricMap, deactivationAtomTrackerToMetricMap, - metricsWithActivation, replacedMetrics)); - - unordered_map expectedMetricProducerMap = { - {count1Id, count1Index}, {count2Id, count2Index}, {count3Id, count3Index}, - {count4Id, count4Index}, {count6Id, count6Index}, - }; - EXPECT_THAT(newMetricProducerMap, ContainerEq(expectedMetricProducerMap)); - EXPECT_EQ(replacedMetrics, set({count2Id, count3Id, count4Id})); - - // Make sure preserved metrics are the same. - ASSERT_EQ(newMetricProducers.size(), 5); - EXPECT_EQ(oldMetricProducers[oldMetricProducerMap.at(count1Id)], - newMetricProducers[newMetricProducerMap.at(count1Id)]); - - // Make sure replaced metrics are different. - EXPECT_NE(oldMetricProducers[oldMetricProducerMap.at(count2Id)], - newMetricProducers[newMetricProducerMap.at(count2Id)]); - EXPECT_NE(oldMetricProducers[oldMetricProducerMap.at(count3Id)], - newMetricProducers[newMetricProducerMap.at(count3Id)]); - EXPECT_NE(oldMetricProducers[oldMetricProducerMap.at(count4Id)], - newMetricProducers[newMetricProducerMap.at(count4Id)]); - - // Verify the conditionToMetricMap. - ASSERT_EQ(conditionToMetricMap.size(), 1); - const vector& condition1Metrics = conditionToMetricMap[predicate1Index]; - EXPECT_THAT(condition1Metrics, UnorderedElementsAre(count1Index, count6Index)); - - // Verify the trackerToMetricMap. - ASSERT_EQ(trackerToMetricMap.size(), 4); - const vector& matcher1Metrics = trackerToMetricMap[matcher1Index]; - EXPECT_THAT(matcher1Metrics, UnorderedElementsAre(count1Index)); - const vector& matcher2Metrics = trackerToMetricMap[matcher2Index]; - EXPECT_THAT(matcher2Metrics, UnorderedElementsAre(count2Index, count6Index)); - const vector& matcher3Metrics = trackerToMetricMap[matcher3Index]; - EXPECT_THAT(matcher3Metrics, UnorderedElementsAre(count3Index)); - const vector& matcher4Metrics = trackerToMetricMap[matcher4Index]; - EXPECT_THAT(matcher4Metrics, UnorderedElementsAre(count4Index)); - - // Verify event activation/deactivation maps. - ASSERT_EQ(activationAtomTrackerToMetricMap.size(), 0); - ASSERT_EQ(deactivationAtomTrackerToMetricMap.size(), 0); - ASSERT_EQ(metricsWithActivation.size(), 0); - - // Verify tracker indices/ids/conditions/states are correct. - EXPECT_EQ(newMetricProducers[count1Index]->getMetricId(), count1Id); - EXPECT_EQ(newMetricProducers[count1Index]->mConditionTrackerIndex, predicate1Index); - EXPECT_EQ(newMetricProducers[count1Index]->mCondition, ConditionState::kTrue); - EXPECT_THAT(newMetricProducers[count1Index]->getSlicedStateAtoms(), - UnorderedElementsAre(util::SCREEN_STATE_CHANGED)); - EXPECT_EQ(newMetricProducers[count2Index]->getMetricId(), count2Id); - EXPECT_EQ(newMetricProducers[count2Index]->mConditionTrackerIndex, -1); - EXPECT_EQ(newMetricProducers[count2Index]->mCondition, ConditionState::kTrue); - EXPECT_TRUE(newMetricProducers[count2Index]->getSlicedStateAtoms().empty()); - EXPECT_EQ(newMetricProducers[count3Index]->getMetricId(), count3Id); - EXPECT_EQ(newMetricProducers[count3Index]->mConditionTrackerIndex, -1); - EXPECT_EQ(newMetricProducers[count3Index]->mCondition, ConditionState::kTrue); - EXPECT_TRUE(newMetricProducers[count3Index]->getSlicedStateAtoms().empty()); - EXPECT_EQ(newMetricProducers[count4Index]->getMetricId(), count4Id); - EXPECT_EQ(newMetricProducers[count4Index]->mConditionTrackerIndex, -1); - EXPECT_EQ(newMetricProducers[count4Index]->mCondition, ConditionState::kTrue); - EXPECT_THAT(newMetricProducers[count4Index]->getSlicedStateAtoms(), - UnorderedElementsAre(util::BATTERY_SAVER_MODE_STATE_CHANGED)); - EXPECT_EQ(newMetricProducers[count6Index]->getMetricId(), count6Id); - EXPECT_EQ(newMetricProducers[count6Index]->mConditionTrackerIndex, predicate1Index); - EXPECT_EQ(newMetricProducers[count6Index]->mCondition, ConditionState::kTrue); - EXPECT_THAT(newMetricProducers[count6Index]->getSlicedStateAtoms(), - UnorderedElementsAre(util::SCREEN_STATE_CHANGED)); - - oldMetricProducers.clear(); - // Ensure that the screen state StateTracker did not get deleted and replaced. - EXPECT_EQ(StateManager::getInstance().getStateTrackersCount(), 2); - FieldValue screenState; - StateManager::getInstance().getStateValue(util::SCREEN_STATE_CHANGED, DEFAULT_DIMENSION_KEY, - &screenState); - EXPECT_EQ(screenState.mValue.int_value, android::view::DisplayStateEnum::DISPLAY_STATE_ON); -} - -TEST_F(ConfigUpdateTest, TestUpdateGaugeMetrics) { - StatsdConfig config; - - // Add atom matchers/predicates/states. These are mostly needed for initStatsdConfig. - AtomMatcher matcher1 = CreateScreenTurnedOnAtomMatcher(); - int64_t matcher1Id = matcher1.id(); - *config.add_atom_matcher() = matcher1; - - AtomMatcher matcher2 = CreateScreenTurnedOffAtomMatcher(); - int64_t matcher2Id = matcher2.id(); - *config.add_atom_matcher() = matcher2; - - AtomMatcher matcher3 = CreateStartScheduledJobAtomMatcher(); - int64_t matcher3Id = matcher3.id(); - *config.add_atom_matcher() = matcher3; - - AtomMatcher matcher4 = CreateTemperatureAtomMatcher(); - int64_t matcher4Id = matcher4.id(); - *config.add_atom_matcher() = matcher4; - - AtomMatcher matcher5 = CreateSimpleAtomMatcher("SubsystemSleep", util::SUBSYSTEM_SLEEP_STATE); - int64_t matcher5Id = matcher5.id(); - *config.add_atom_matcher() = matcher5; - - Predicate predicate1 = CreateScreenIsOnPredicate(); - int64_t predicate1Id = predicate1.id(); - *config.add_predicate() = predicate1; - - // Add a few gauge metrics. - // Will be preserved. - GaugeMetric gauge1 = createGaugeMetric("GAUGE1", matcher4Id, GaugeMetric::FIRST_N_SAMPLES, - predicate1Id, matcher1Id); - int64_t gauge1Id = gauge1.id(); - *config.add_gauge_metric() = gauge1; - - // Will be replaced. - GaugeMetric gauge2 = - createGaugeMetric("GAUGE2", matcher1Id, GaugeMetric::FIRST_N_SAMPLES, nullopt, nullopt); - int64_t gauge2Id = gauge2.id(); - *config.add_gauge_metric() = gauge2; - - // Will be replaced. - GaugeMetric gauge3 = createGaugeMetric("GAUGE3", matcher5Id, GaugeMetric::FIRST_N_SAMPLES, - nullopt, matcher3Id); - int64_t gauge3Id = gauge3.id(); - *config.add_gauge_metric() = gauge3; - - // Will be replaced. - GaugeMetric gauge4 = createGaugeMetric("GAUGE4", matcher3Id, GaugeMetric::RANDOM_ONE_SAMPLE, - predicate1Id, nullopt); - int64_t gauge4Id = gauge4.id(); - *config.add_gauge_metric() = gauge4; - - // Will be deleted. - GaugeMetric gauge5 = - createGaugeMetric("GAUGE5", matcher2Id, GaugeMetric::RANDOM_ONE_SAMPLE, nullopt, {}); - int64_t gauge5Id = gauge5.id(); - *config.add_gauge_metric() = gauge5; - - EXPECT_TRUE(initConfig(config)); - - // Used later to ensure the condition wizard is replaced. Get it before doing the update. - sp oldMatcherWizard = - static_cast(oldMetricProducers[0].get())->mEventMatcherWizard; - EXPECT_EQ(oldMatcherWizard->getStrongCount(), 6); - - // Change gauge2, causing it to be replaced. - gauge2.set_max_num_gauge_atoms_per_bucket(50); - - // Mark matcher 3 as replaced. Causes gauge3 and gauge4 to be replaced. - set replacedMatchers = {matcher3Id}; - - // New gauge metric. - GaugeMetric gauge6 = createGaugeMetric("GAUGE6", matcher5Id, GaugeMetric::FIRST_N_SAMPLES, - predicate1Id, matcher3Id); - int64_t gauge6Id = gauge6.id(); - - // Map the matchers and predicates in reverse order to force the indices to change. - std::unordered_map newAtomMatchingTrackerMap; - const int matcher5Index = 0; - newAtomMatchingTrackerMap[matcher5Id] = 0; - const int matcher4Index = 1; - newAtomMatchingTrackerMap[matcher4Id] = 1; - const int matcher3Index = 2; - newAtomMatchingTrackerMap[matcher3Id] = 2; - const int matcher2Index = 3; - newAtomMatchingTrackerMap[matcher2Id] = 3; - const int matcher1Index = 4; - newAtomMatchingTrackerMap[matcher1Id] = 4; - // Use the existing matchers. A bit hacky, but saves code and we don't rely on them. - vector> newAtomMatchingTrackers(5); - std::reverse_copy(oldAtomMatchingTrackers.begin(), oldAtomMatchingTrackers.end(), - newAtomMatchingTrackers.begin()); - - std::unordered_map newConditionTrackerMap; - const int predicate1Index = 0; - newConditionTrackerMap[predicate1Id] = 0; - // Use the existing conditionTrackers. A bit hacky, but saves code and we don't rely on them. - vector> newConditionTrackers(1); - std::reverse_copy(oldConditionTrackers.begin(), oldConditionTrackers.end(), - newConditionTrackers.begin()); - // Say that predicate1 is unknown since the initial condition never changed. - vector conditionCache = {ConditionState::kUnknown}; - - StatsdConfig newConfig; - *newConfig.add_gauge_metric() = gauge6; - const int gauge6Index = 0; - *newConfig.add_gauge_metric() = gauge3; - const int gauge3Index = 1; - *newConfig.add_gauge_metric() = gauge1; - const int gauge1Index = 2; - *newConfig.add_gauge_metric() = gauge4; - const int gauge4Index = 3; - *newConfig.add_gauge_metric() = gauge2; - const int gauge2Index = 4; - - // Output data structures to validate. - unordered_map newMetricProducerMap; - vector> newMetricProducers; - unordered_map> conditionToMetricMap; - unordered_map> trackerToMetricMap; - set noReportMetricIds; - unordered_map> activationAtomTrackerToMetricMap; - unordered_map> deactivationAtomTrackerToMetricMap; - vector metricsWithActivation; - set replacedMetrics; - EXPECT_TRUE(updateMetrics( - key, newConfig, /*timeBaseNs=*/123, /*currentTimeNs=*/12345, new StatsPullerManager(), - oldAtomMatchingTrackerMap, newAtomMatchingTrackerMap, replacedMatchers, - newAtomMatchingTrackers, newConditionTrackerMap, /*replacedConditions=*/{}, - newConditionTrackers, conditionCache, /*stateAtomIdMap=*/{}, /*allStateGroupMaps=*/{}, - /*replacedStates=*/{}, oldMetricProducerMap, oldMetricProducers, newMetricProducerMap, - newMetricProducers, conditionToMetricMap, trackerToMetricMap, noReportMetricIds, - activationAtomTrackerToMetricMap, deactivationAtomTrackerToMetricMap, - metricsWithActivation, replacedMetrics)); - - unordered_map expectedMetricProducerMap = { - {gauge1Id, gauge1Index}, {gauge2Id, gauge2Index}, {gauge3Id, gauge3Index}, - {gauge4Id, gauge4Index}, {gauge6Id, gauge6Index}, - }; - EXPECT_THAT(newMetricProducerMap, ContainerEq(expectedMetricProducerMap)); - EXPECT_EQ(replacedMetrics, set({gauge2Id, gauge3Id, gauge4Id})); - - // Make sure preserved metrics are the same. - ASSERT_EQ(newMetricProducers.size(), 5); - EXPECT_EQ(oldMetricProducers[oldMetricProducerMap.at(gauge1Id)], - newMetricProducers[newMetricProducerMap.at(gauge1Id)]); - - // Make sure replaced metrics are different. - EXPECT_NE(oldMetricProducers[oldMetricProducerMap.at(gauge2Id)], - newMetricProducers[newMetricProducerMap.at(gauge2Id)]); - EXPECT_NE(oldMetricProducers[oldMetricProducerMap.at(gauge3Id)], - newMetricProducers[newMetricProducerMap.at(gauge3Id)]); - EXPECT_NE(oldMetricProducers[oldMetricProducerMap.at(gauge4Id)], - newMetricProducers[newMetricProducerMap.at(gauge4Id)]); - - // Verify the conditionToMetricMap. - ASSERT_EQ(conditionToMetricMap.size(), 1); - const vector& condition1Metrics = conditionToMetricMap[predicate1Index]; - EXPECT_THAT(condition1Metrics, UnorderedElementsAre(gauge1Index, gauge4Index, gauge6Index)); - - // Verify the trackerToMetricMap. - ASSERT_EQ(trackerToMetricMap.size(), 4); - const vector& matcher1Metrics = trackerToMetricMap[matcher1Index]; - EXPECT_THAT(matcher1Metrics, UnorderedElementsAre(gauge1Index, gauge2Index)); - const vector& matcher3Metrics = trackerToMetricMap[matcher3Index]; - EXPECT_THAT(matcher3Metrics, UnorderedElementsAre(gauge3Index, gauge4Index, gauge6Index)); - const vector& matcher4Metrics = trackerToMetricMap[matcher4Index]; - EXPECT_THAT(matcher4Metrics, UnorderedElementsAre(gauge1Index)); - const vector& matcher5Metrics = trackerToMetricMap[matcher5Index]; - EXPECT_THAT(matcher5Metrics, UnorderedElementsAre(gauge3Index, gauge6Index)); - - // Verify event activation/deactivation maps. - ASSERT_EQ(activationAtomTrackerToMetricMap.size(), 0); - ASSERT_EQ(deactivationAtomTrackerToMetricMap.size(), 0); - ASSERT_EQ(metricsWithActivation.size(), 0); - - // Verify tracker indices/ids/conditions/states are correct. - GaugeMetricProducer* gaugeProducer1 = - static_cast(newMetricProducers[gauge1Index].get()); - EXPECT_EQ(gaugeProducer1->getMetricId(), gauge1Id); - EXPECT_EQ(gaugeProducer1->mConditionTrackerIndex, predicate1Index); - EXPECT_EQ(gaugeProducer1->mCondition, ConditionState::kUnknown); - EXPECT_EQ(gaugeProducer1->mWhatMatcherIndex, matcher4Index); - GaugeMetricProducer* gaugeProducer2 = - static_cast(newMetricProducers[gauge2Index].get()); - EXPECT_EQ(gaugeProducer2->getMetricId(), gauge2Id); - EXPECT_EQ(gaugeProducer2->mConditionTrackerIndex, -1); - EXPECT_EQ(gaugeProducer2->mCondition, ConditionState::kTrue); - EXPECT_EQ(gaugeProducer2->mWhatMatcherIndex, matcher1Index); - GaugeMetricProducer* gaugeProducer3 = - static_cast(newMetricProducers[gauge3Index].get()); - EXPECT_EQ(gaugeProducer3->getMetricId(), gauge3Id); - EXPECT_EQ(gaugeProducer3->mConditionTrackerIndex, -1); - EXPECT_EQ(gaugeProducer3->mCondition, ConditionState::kTrue); - EXPECT_EQ(gaugeProducer3->mWhatMatcherIndex, matcher5Index); - GaugeMetricProducer* gaugeProducer4 = - static_cast(newMetricProducers[gauge4Index].get()); - EXPECT_EQ(gaugeProducer4->getMetricId(), gauge4Id); - EXPECT_EQ(gaugeProducer4->mConditionTrackerIndex, predicate1Index); - EXPECT_EQ(gaugeProducer4->mCondition, ConditionState::kUnknown); - EXPECT_EQ(gaugeProducer4->mWhatMatcherIndex, matcher3Index); - GaugeMetricProducer* gaugeProducer6 = - static_cast(newMetricProducers[gauge6Index].get()); - EXPECT_EQ(gaugeProducer6->getMetricId(), gauge6Id); - EXPECT_EQ(gaugeProducer6->mConditionTrackerIndex, predicate1Index); - EXPECT_EQ(gaugeProducer6->mCondition, ConditionState::kUnknown); - EXPECT_EQ(gaugeProducer6->mWhatMatcherIndex, matcher5Index); - - sp newMatcherWizard = gaugeProducer1->mEventMatcherWizard; - EXPECT_NE(newMatcherWizard, oldMatcherWizard); - EXPECT_EQ(newMatcherWizard->getStrongCount(), 6); - oldMetricProducers.clear(); - // Only reference to the old wizard should be the one in the test. - EXPECT_EQ(oldMatcherWizard->getStrongCount(), 1); -} - -TEST_F(ConfigUpdateTest, TestUpdateDurationMetrics) { - StatsdConfig config; - // Add atom matchers/predicates/states. These are mostly needed for initStatsdConfig. - AtomMatcher matcher1 = CreateScreenTurnedOnAtomMatcher(); - int64_t matcher1Id = matcher1.id(); - *config.add_atom_matcher() = matcher1; - - AtomMatcher matcher2 = CreateScreenTurnedOffAtomMatcher(); - int64_t matcher2Id = matcher2.id(); - *config.add_atom_matcher() = matcher2; - - AtomMatcher matcher3 = CreateAcquireWakelockAtomMatcher(); - int64_t matcher3Id = matcher3.id(); - *config.add_atom_matcher() = matcher3; - - AtomMatcher matcher4 = CreateReleaseWakelockAtomMatcher(); - int64_t matcher4Id = matcher4.id(); - *config.add_atom_matcher() = matcher4; - - AtomMatcher matcher5 = CreateMoveToForegroundAtomMatcher(); - int64_t matcher5Id = matcher5.id(); - *config.add_atom_matcher() = matcher5; - - AtomMatcher matcher6 = CreateMoveToBackgroundAtomMatcher(); - int64_t matcher6Id = matcher6.id(); - *config.add_atom_matcher() = matcher6; - - AtomMatcher matcher7 = CreateBatteryStateNoneMatcher(); - int64_t matcher7Id = matcher7.id(); - *config.add_atom_matcher() = matcher7; - - AtomMatcher matcher8 = CreateBatteryStateUsbMatcher(); - int64_t matcher8Id = matcher8.id(); - *config.add_atom_matcher() = matcher8; - - Predicate predicate1 = CreateScreenIsOnPredicate(); - int64_t predicate1Id = predicate1.id(); - *config.add_predicate() = predicate1; - - Predicate predicate2 = CreateScreenIsOffPredicate(); - int64_t predicate2Id = predicate2.id(); - *config.add_predicate() = predicate2; - - Predicate predicate3 = CreateDeviceUnpluggedPredicate(); - int64_t predicate3Id = predicate3.id(); - *config.add_predicate() = predicate3; - - Predicate predicate4 = CreateIsInBackgroundPredicate(); - *predicate4.mutable_simple_predicate()->mutable_dimensions() = - CreateDimensions(util::ACTIVITY_FOREGROUND_STATE_CHANGED, {1}); - int64_t predicate4Id = predicate4.id(); - *config.add_predicate() = predicate4; - - Predicate predicate5 = CreateHoldingWakelockPredicate(); - *predicate5.mutable_simple_predicate()->mutable_dimensions() = - CreateAttributionUidDimensions(util::WAKELOCK_STATE_CHANGED, {Position::FIRST}); - predicate5.mutable_simple_predicate()->set_stop_all(matcher7Id); - int64_t predicate5Id = predicate5.id(); - *config.add_predicate() = predicate5; - - State state1 = CreateScreenStateWithOnOffMap(0x123, 0x321); - int64_t state1Id = state1.id(); - *config.add_state() = state1; - - State state2 = CreateScreenState(); - int64_t state2Id = state2.id(); - *config.add_state() = state2; - - // Add a few duration metrics. - // Will be preserved. - DurationMetric duration1 = - createDurationMetric("DURATION1", predicate5Id, predicate4Id, {state2Id}); - *duration1.mutable_dimensions_in_what() = - CreateAttributionUidDimensions(util::WAKELOCK_STATE_CHANGED, {Position::FIRST}); - MetricConditionLink* link = duration1.add_links(); - link->set_condition(predicate4Id); - *link->mutable_fields_in_what() = - CreateAttributionUidDimensions(util::WAKELOCK_STATE_CHANGED, {Position::FIRST}); - *link->mutable_fields_in_condition() = - CreateDimensions(util::ACTIVITY_FOREGROUND_STATE_CHANGED, {1} /*uid field*/); - int64_t duration1Id = duration1.id(); - *config.add_duration_metric() = duration1; - - // Will be replaced. - DurationMetric duration2 = createDurationMetric("DURATION2", predicate1Id, nullopt, {}); - int64_t duration2Id = duration2.id(); - *config.add_duration_metric() = duration2; - - // Will be replaced. - DurationMetric duration3 = createDurationMetric("DURATION3", predicate3Id, nullopt, {state1Id}); - int64_t duration3Id = duration3.id(); - *config.add_duration_metric() = duration3; - - // Will be replaced. - DurationMetric duration4 = createDurationMetric("DURATION4", predicate3Id, predicate2Id, {}); - int64_t duration4Id = duration4.id(); - *config.add_duration_metric() = duration4; - - // Will be deleted. - DurationMetric duration5 = createDurationMetric("DURATION5", predicate2Id, nullopt, {}); - int64_t duration5Id = duration5.id(); - *config.add_duration_metric() = duration5; - - EXPECT_TRUE(initConfig(config)); - - // Make some sliced conditions true. - int uid1 = 10; - int uid2 = 11; - vector matchingStates(8, MatchingState::kNotMatched); - matchingStates[2] = kMatched; - vector conditionCache(5, ConditionState::kNotEvaluated); - vector changedCache(5, false); - unique_ptr event = CreateAcquireWakelockEvent(timeBaseNs + 3, {uid1}, {"tag"}, "wl1"); - oldConditionTrackers[4]->evaluateCondition(*event.get(), matchingStates, oldConditionTrackers, - conditionCache, changedCache); - EXPECT_TRUE(oldConditionTrackers[4]->isSliced()); - EXPECT_TRUE(changedCache[4]); - EXPECT_EQ(conditionCache[4], ConditionState::kTrue); - oldMetricProducers[0]->onMatchedLogEvent(2, *event.get()); - - fill(conditionCache.begin(), conditionCache.end(), ConditionState::kNotEvaluated); - fill(changedCache.begin(), changedCache.end(), false); - event = CreateAcquireWakelockEvent(timeBaseNs + 3, {uid2}, {"tag"}, "wl2"); - oldConditionTrackers[4]->evaluateCondition(*event.get(), matchingStates, oldConditionTrackers, - conditionCache, changedCache); - EXPECT_TRUE(changedCache[4]); - EXPECT_EQ(conditionCache[4], ConditionState::kTrue); - oldMetricProducers[0]->onMatchedLogEvent(2, *event.get()); - - // Used later to ensure the condition wizard is replaced. Get it before doing the update. - // The duration trackers have a pointer to the wizard, and 2 trackers were created above. - sp oldConditionWizard = oldMetricProducers[0]->mWizard; - EXPECT_EQ(oldConditionWizard->getStrongCount(), 8); - - // Replace predicate1, predicate3, and state1. Causes duration2/3/4 to be replaced. - set replacedConditions({predicate1Id, predicate2Id}); - set replacedStates({state1Id}); - - // New duration metric. - DurationMetric duration6 = createDurationMetric("DURATION6", predicate4Id, predicate5Id, {}); - *duration6.mutable_dimensions_in_what() = - CreateDimensions(util::ACTIVITY_FOREGROUND_STATE_CHANGED, {1} /*uid field*/); - link = duration6.add_links(); - link->set_condition(predicate5Id); - *link->mutable_fields_in_what() = - CreateDimensions(util::ACTIVITY_FOREGROUND_STATE_CHANGED, {1} /*uid field*/); - *link->mutable_fields_in_condition() = - CreateAttributionUidDimensions(util::WAKELOCK_STATE_CHANGED, {Position::FIRST}); - int64_t duration6Id = duration6.id(); - - // Map the matchers and predicates in reverse order to force the indices to change. - const int matcher8Index = 0, matcher7Index = 1, matcher6Index = 2, matcher5Index = 3, - matcher4Index = 4, matcher3Index = 5, matcher2Index = 6, matcher1Index = 7; - std::unordered_map newAtomMatchingTrackerMap({{matcher8Id, matcher8Index}, - {matcher7Id, matcher7Index}, - {matcher6Id, matcher6Index}, - {matcher5Id, matcher5Index}, - {matcher4Id, matcher4Index}, - {matcher3Id, matcher3Index}, - {matcher2Id, matcher2Index}, - {matcher1Id, matcher1Index}}); - // Use the existing matchers. A bit hacky, but saves code and we don't rely on them. - vector> newAtomMatchingTrackers(8); - reverse_copy(oldAtomMatchingTrackers.begin(), oldAtomMatchingTrackers.end(), - newAtomMatchingTrackers.begin()); - - const int predicate5Index = 0, predicate4Index = 1, predicate3Index = 2, predicate2Index = 3, - predicate1Index = 4; - std::unordered_map newConditionTrackerMap({ - {predicate5Id, predicate5Index}, - {predicate4Id, predicate4Index}, - {predicate3Id, predicate3Index}, - {predicate2Id, predicate2Index}, - {predicate1Id, predicate1Index}, - }); - // Use the existing conditionTrackers and reinitialize them to get the initial condition cache. - vector> newConditionTrackers(5); - reverse_copy(oldConditionTrackers.begin(), oldConditionTrackers.end(), - newConditionTrackers.begin()); - vector conditionProtos(5); - reverse_copy(config.predicate().begin(), config.predicate().end(), conditionProtos.begin()); - for (int i = 0; i < newConditionTrackers.size(); i++) { - EXPECT_TRUE(newConditionTrackers[i]->onConfigUpdated( - conditionProtos, i, newConditionTrackers, newAtomMatchingTrackerMap, - newConditionTrackerMap)); - } - vector cycleTracker(5, false); - fill(conditionCache.begin(), conditionCache.end(), ConditionState::kNotEvaluated); - for (int i = 0; i < newConditionTrackers.size(); i++) { - EXPECT_TRUE(newConditionTrackers[i]->init(conditionProtos, newConditionTrackers, - newConditionTrackerMap, cycleTracker, - conditionCache)); - } - // Predicate5 should be true since 2 uids have wakelocks - EXPECT_EQ(conditionCache, vector({kTrue, kUnknown, kUnknown, kUnknown, kUnknown})); - - StatsdConfig newConfig; - *newConfig.add_duration_metric() = duration6; - const int duration6Index = 0; - *newConfig.add_duration_metric() = duration3; - const int duration3Index = 1; - *newConfig.add_duration_metric() = duration1; - const int duration1Index = 2; - *newConfig.add_duration_metric() = duration4; - const int duration4Index = 3; - *newConfig.add_duration_metric() = duration2; - const int duration2Index = 4; - - for (const Predicate& predicate : conditionProtos) { - *newConfig.add_predicate() = predicate; - } - *newConfig.add_state() = state1; - *newConfig.add_state() = state2; - unordered_map stateAtomIdMap; - unordered_map> allStateGroupMaps; - map stateProtoHashes; - EXPECT_TRUE(initStates(newConfig, stateAtomIdMap, allStateGroupMaps, stateProtoHashes)); - - // Output data structures to validate. - unordered_map newMetricProducerMap; - vector> newMetricProducers; - unordered_map> conditionToMetricMap; - unordered_map> trackerToMetricMap; - set noReportMetricIds; - unordered_map> activationAtomTrackerToMetricMap; - unordered_map> deactivationAtomTrackerToMetricMap; - vector metricsWithActivation; - set replacedMetrics; - EXPECT_TRUE(updateMetrics( - key, newConfig, /*timeBaseNs=*/123, /*currentTimeNs=*/12345, new StatsPullerManager(), - oldAtomMatchingTrackerMap, newAtomMatchingTrackerMap, /*replacedMatchers=*/{}, - newAtomMatchingTrackers, newConditionTrackerMap, replacedConditions, - newConditionTrackers, conditionCache, stateAtomIdMap, allStateGroupMaps, replacedStates, - oldMetricProducerMap, oldMetricProducers, newMetricProducerMap, newMetricProducers, - conditionToMetricMap, trackerToMetricMap, noReportMetricIds, - activationAtomTrackerToMetricMap, deactivationAtomTrackerToMetricMap, - metricsWithActivation, replacedMetrics)); - - unordered_map expectedMetricProducerMap = { - {duration1Id, duration1Index}, {duration2Id, duration2Index}, - {duration3Id, duration3Index}, {duration4Id, duration4Index}, - {duration6Id, duration6Index}, - }; - EXPECT_THAT(newMetricProducerMap, ContainerEq(expectedMetricProducerMap)); - EXPECT_EQ(replacedMetrics, set({duration2Id, duration3Id, duration4Id})); - // Make sure preserved metrics are the same. - ASSERT_EQ(newMetricProducers.size(), 5); - EXPECT_EQ(oldMetricProducers[oldMetricProducerMap.at(duration1Id)], - newMetricProducers[newMetricProducerMap.at(duration1Id)]); - - // Make sure replaced metrics are different. - EXPECT_NE(oldMetricProducers[oldMetricProducerMap.at(duration2Id)], - newMetricProducers[newMetricProducerMap.at(duration2Id)]); - EXPECT_NE(oldMetricProducers[oldMetricProducerMap.at(duration3Id)], - newMetricProducers[newMetricProducerMap.at(duration3Id)]); - EXPECT_NE(oldMetricProducers[oldMetricProducerMap.at(duration4Id)], - newMetricProducers[newMetricProducerMap.at(duration4Id)]); - - // Verify the conditionToMetricMap. Note that the "what" is not in this map. - ASSERT_EQ(conditionToMetricMap.size(), 3); - const vector& condition2Metrics = conditionToMetricMap[predicate2Index]; - EXPECT_THAT(condition2Metrics, UnorderedElementsAre(duration4Index)); - const vector& condition4Metrics = conditionToMetricMap[predicate4Index]; - EXPECT_THAT(condition4Metrics, UnorderedElementsAre(duration1Index)); - const vector& condition5Metrics = conditionToMetricMap[predicate5Index]; - EXPECT_THAT(condition5Metrics, UnorderedElementsAre(duration6Index)); - - // Verify the trackerToMetricMap. The start/stop/stopall indices from the "what" should be here. - ASSERT_EQ(trackerToMetricMap.size(), 8); - const vector& matcher1Metrics = trackerToMetricMap[matcher1Index]; - EXPECT_THAT(matcher1Metrics, UnorderedElementsAre(duration2Index)); - const vector& matcher2Metrics = trackerToMetricMap[matcher2Index]; - EXPECT_THAT(matcher2Metrics, UnorderedElementsAre(duration2Index)); - const vector& matcher3Metrics = trackerToMetricMap[matcher3Index]; - EXPECT_THAT(matcher3Metrics, UnorderedElementsAre(duration1Index)); - const vector& matcher4Metrics = trackerToMetricMap[matcher4Index]; - EXPECT_THAT(matcher4Metrics, UnorderedElementsAre(duration1Index)); - const vector& matcher5Metrics = trackerToMetricMap[matcher5Index]; - EXPECT_THAT(matcher5Metrics, UnorderedElementsAre(duration6Index)); - const vector& matcher6Metrics = trackerToMetricMap[matcher6Index]; - EXPECT_THAT(matcher6Metrics, UnorderedElementsAre(duration6Index)); - const vector& matcher7Metrics = trackerToMetricMap[matcher7Index]; - EXPECT_THAT(matcher7Metrics, - UnorderedElementsAre(duration1Index, duration3Index, duration4Index)); - const vector& matcher8Metrics = trackerToMetricMap[matcher8Index]; - EXPECT_THAT(matcher8Metrics, UnorderedElementsAre(duration3Index, duration4Index)); - - // Verify event activation/deactivation maps. - ASSERT_EQ(activationAtomTrackerToMetricMap.size(), 0); - ASSERT_EQ(deactivationAtomTrackerToMetricMap.size(), 0); - ASSERT_EQ(metricsWithActivation.size(), 0); - - // Verify tracker indices/ids/conditions are correct. - DurationMetricProducer* durationProducer1 = - static_cast(newMetricProducers[duration1Index].get()); - EXPECT_EQ(durationProducer1->getMetricId(), duration1Id); - EXPECT_EQ(durationProducer1->mConditionTrackerIndex, predicate4Index); - EXPECT_EQ(durationProducer1->mCondition, ConditionState::kUnknown); - EXPECT_EQ(durationProducer1->mStartIndex, matcher3Index); - EXPECT_EQ(durationProducer1->mStopIndex, matcher4Index); - EXPECT_EQ(durationProducer1->mStopAllIndex, matcher7Index); - EXPECT_EQ(durationProducer1->mCurrentSlicedDurationTrackerMap.size(), 2); - for (const auto& durationTrackerIt : durationProducer1->mCurrentSlicedDurationTrackerMap) { - EXPECT_EQ(durationTrackerIt.second->mConditionTrackerIndex, predicate4Index); - } - DurationMetricProducer* durationProducer2 = - static_cast(newMetricProducers[duration2Index].get()); - EXPECT_EQ(durationProducer2->getMetricId(), duration2Id); - EXPECT_EQ(durationProducer2->mConditionTrackerIndex, -1); - EXPECT_EQ(durationProducer2->mCondition, ConditionState::kTrue); - EXPECT_EQ(durationProducer2->mStartIndex, matcher1Index); - EXPECT_EQ(durationProducer2->mStopIndex, matcher2Index); - EXPECT_EQ(durationProducer2->mStopAllIndex, -1); - DurationMetricProducer* durationProducer3 = - static_cast(newMetricProducers[duration3Index].get()); - EXPECT_EQ(durationProducer3->getMetricId(), duration3Id); - EXPECT_EQ(durationProducer3->mConditionTrackerIndex, -1); - EXPECT_EQ(durationProducer3->mCondition, ConditionState::kTrue); - EXPECT_EQ(durationProducer3->mStartIndex, matcher7Index); - EXPECT_EQ(durationProducer3->mStopIndex, matcher8Index); - EXPECT_EQ(durationProducer3->mStopAllIndex, -1); - DurationMetricProducer* durationProducer4 = - static_cast(newMetricProducers[duration4Index].get()); - EXPECT_EQ(durationProducer4->getMetricId(), duration4Id); - EXPECT_EQ(durationProducer4->mConditionTrackerIndex, predicate2Index); - EXPECT_EQ(durationProducer4->mCondition, ConditionState::kUnknown); - EXPECT_EQ(durationProducer4->mStartIndex, matcher7Index); - EXPECT_EQ(durationProducer4->mStopIndex, matcher8Index); - EXPECT_EQ(durationProducer4->mStopAllIndex, -1); - DurationMetricProducer* durationProducer6 = - static_cast(newMetricProducers[duration6Index].get()); - EXPECT_EQ(durationProducer6->getMetricId(), duration6Id); - EXPECT_EQ(durationProducer6->mConditionTrackerIndex, predicate5Index); - // TODO(b/167491517): should this be unknown since the condition is sliced? - EXPECT_EQ(durationProducer6->mCondition, ConditionState::kTrue); - EXPECT_EQ(durationProducer6->mStartIndex, matcher6Index); - EXPECT_EQ(durationProducer6->mStopIndex, matcher5Index); - EXPECT_EQ(durationProducer6->mStopAllIndex, -1); - - sp newConditionWizard = newMetricProducers[0]->mWizard; - EXPECT_NE(newConditionWizard, oldConditionWizard); - EXPECT_EQ(newConditionWizard->getStrongCount(), 8); - oldMetricProducers.clear(); - // Only reference to the old wizard should be the one in the test. - EXPECT_EQ(oldConditionWizard->getStrongCount(), 1); -} - -TEST_F(ConfigUpdateTest, TestUpdateValueMetrics) { - StatsdConfig config; - - // Add atom matchers/predicates/states. These are mostly needed for initStatsdConfig. - AtomMatcher matcher1 = CreateScreenTurnedOnAtomMatcher(); - int64_t matcher1Id = matcher1.id(); - *config.add_atom_matcher() = matcher1; - - AtomMatcher matcher2 = CreateScreenTurnedOffAtomMatcher(); - int64_t matcher2Id = matcher2.id(); - *config.add_atom_matcher() = matcher2; - - AtomMatcher matcher3 = CreateStartScheduledJobAtomMatcher(); - int64_t matcher3Id = matcher3.id(); - *config.add_atom_matcher() = matcher3; - - AtomMatcher matcher4 = CreateTemperatureAtomMatcher(); - int64_t matcher4Id = matcher4.id(); - *config.add_atom_matcher() = matcher4; - - AtomMatcher matcher5 = CreateSimpleAtomMatcher("SubsystemSleep", util::SUBSYSTEM_SLEEP_STATE); - int64_t matcher5Id = matcher5.id(); - *config.add_atom_matcher() = matcher5; - - Predicate predicate1 = CreateScreenIsOnPredicate(); - int64_t predicate1Id = predicate1.id(); - *config.add_predicate() = predicate1; - - Predicate predicate2 = CreateScreenIsOffPredicate(); - int64_t predicate2Id = predicate2.id(); - *config.add_predicate() = predicate2; - - State state1 = CreateScreenStateWithOnOffMap(0x123, 0x321); - int64_t state1Id = state1.id(); - *config.add_state() = state1; - - State state2 = CreateScreenState(); - int64_t state2Id = state2.id(); - *config.add_state() = state2; - - // Add a few value metrics. - // Note that these will not work as "real" metrics since the value field is always 2. - // Will be preserved. - ValueMetric value1 = createValueMetric("VALUE1", matcher4, 2, predicate1Id, {state1Id}); - int64_t value1Id = value1.id(); - *config.add_value_metric() = value1; - - // Will be replaced - definition change. - ValueMetric value2 = createValueMetric("VALUE2", matcher1, 2, nullopt, {}); - int64_t value2Id = value2.id(); - *config.add_value_metric() = value2; - - // Will be replaced - condition change. - ValueMetric value3 = createValueMetric("VALUE3", matcher5, 2, predicate2Id, {}); - int64_t value3Id = value3.id(); - *config.add_value_metric() = value3; - - // Will be replaced - state change. - ValueMetric value4 = createValueMetric("VALUE4", matcher3, 2, nullopt, {state2Id}); - int64_t value4Id = value4.id(); - *config.add_value_metric() = value4; - - // Will be deleted. - ValueMetric value5 = createValueMetric("VALUE5", matcher2, 2, nullopt, {}); - int64_t value5Id = value5.id(); - *config.add_value_metric() = value5; - - EXPECT_TRUE(initConfig(config)); - - // Used later to ensure the condition wizard is replaced. Get it before doing the update. - sp oldMatcherWizard = - static_cast(oldMetricProducers[0].get())->mEventMatcherWizard; - EXPECT_EQ(oldMatcherWizard->getStrongCount(), 6); - - // Change value2, causing it to be replaced. - value2.set_aggregation_type(ValueMetric::AVG); - - // Mark predicate 2 as replaced. Causes value3 to be replaced. - set replacedConditions = {predicate2Id}; - - // Mark state 2 as replaced. Causes value4 to be replaced. - set replacedStates = {state2Id}; - - // New value metric. - ValueMetric value6 = createValueMetric("VALUE6", matcher5, 2, predicate1Id, {state1Id}); - int64_t value6Id = value6.id(); - - // Map the matchers and predicates in reverse order to force the indices to change. - std::unordered_map newAtomMatchingTrackerMap; - const int matcher5Index = 0; - newAtomMatchingTrackerMap[matcher5Id] = 0; - const int matcher4Index = 1; - newAtomMatchingTrackerMap[matcher4Id] = 1; - const int matcher3Index = 2; - newAtomMatchingTrackerMap[matcher3Id] = 2; - const int matcher2Index = 3; - newAtomMatchingTrackerMap[matcher2Id] = 3; - const int matcher1Index = 4; - newAtomMatchingTrackerMap[matcher1Id] = 4; - // Use the existing matchers. A bit hacky, but saves code and we don't rely on them. - vector> newAtomMatchingTrackers(5); - std::reverse_copy(oldAtomMatchingTrackers.begin(), oldAtomMatchingTrackers.end(), - newAtomMatchingTrackers.begin()); - - std::unordered_map newConditionTrackerMap; - const int predicate2Index = 0; - newConditionTrackerMap[predicate2Id] = 0; - const int predicate1Index = 1; - newConditionTrackerMap[predicate1Id] = 1; - // Use the existing conditionTrackers. A bit hacky, but saves code and we don't rely on them. - vector> newConditionTrackers(2); - std::reverse_copy(oldConditionTrackers.begin(), oldConditionTrackers.end(), - newConditionTrackers.begin()); - // Say that predicate1 & predicate2 is unknown since the initial condition never changed. - vector conditionCache = {ConditionState::kUnknown, ConditionState::kUnknown}; - - StatsdConfig newConfig; - *newConfig.add_value_metric() = value6; - const int value6Index = 0; - *newConfig.add_value_metric() = value3; - const int value3Index = 1; - *newConfig.add_value_metric() = value1; - const int value1Index = 2; - *newConfig.add_value_metric() = value4; - const int value4Index = 3; - *newConfig.add_value_metric() = value2; - const int value2Index = 4; - - *newConfig.add_state() = state1; - *newConfig.add_state() = state2; - - unordered_map stateAtomIdMap; - unordered_map> allStateGroupMaps; - map stateProtoHashes; - EXPECT_TRUE(initStates(newConfig, stateAtomIdMap, allStateGroupMaps, stateProtoHashes)); - - // Output data structures to validate. - unordered_map newMetricProducerMap; - vector> newMetricProducers; - unordered_map> conditionToMetricMap; - unordered_map> trackerToMetricMap; - set noReportMetricIds; - unordered_map> activationAtomTrackerToMetricMap; - unordered_map> deactivationAtomTrackerToMetricMap; - vector metricsWithActivation; - set replacedMetrics; - EXPECT_TRUE(updateMetrics( - key, newConfig, /*timeBaseNs=*/123, /*currentTimeNs=*/12345, new StatsPullerManager(), - oldAtomMatchingTrackerMap, newAtomMatchingTrackerMap, /*replacedMatchers=*/{}, - newAtomMatchingTrackers, newConditionTrackerMap, replacedConditions, - newConditionTrackers, conditionCache, stateAtomIdMap, allStateGroupMaps, replacedStates, - oldMetricProducerMap, oldMetricProducers, newMetricProducerMap, newMetricProducers, - conditionToMetricMap, trackerToMetricMap, noReportMetricIds, - activationAtomTrackerToMetricMap, deactivationAtomTrackerToMetricMap, - metricsWithActivation, replacedMetrics)); - - unordered_map expectedMetricProducerMap = { - {value1Id, value1Index}, {value2Id, value2Index}, {value3Id, value3Index}, - {value4Id, value4Index}, {value6Id, value6Index}, - }; - EXPECT_THAT(newMetricProducerMap, ContainerEq(expectedMetricProducerMap)); - EXPECT_EQ(replacedMetrics, set({value2Id, value3Id, value4Id})); - - // Make sure preserved metrics are the same. - ASSERT_EQ(newMetricProducers.size(), 5); - EXPECT_EQ(oldMetricProducers[oldMetricProducerMap.at(value1Id)], - newMetricProducers[newMetricProducerMap.at(value1Id)]); - - // Make sure replaced metrics are different. - EXPECT_NE(oldMetricProducers[oldMetricProducerMap.at(value2Id)], - newMetricProducers[newMetricProducerMap.at(value2Id)]); - EXPECT_NE(oldMetricProducers[oldMetricProducerMap.at(value3Id)], - newMetricProducers[newMetricProducerMap.at(value3Id)]); - EXPECT_NE(oldMetricProducers[oldMetricProducerMap.at(value4Id)], - newMetricProducers[newMetricProducerMap.at(value4Id)]); - - // Verify the conditionToMetricMap. - ASSERT_EQ(conditionToMetricMap.size(), 2); - const vector& condition1Metrics = conditionToMetricMap[predicate1Index]; - EXPECT_THAT(condition1Metrics, UnorderedElementsAre(value1Index, value6Index)); - const vector& condition2Metrics = conditionToMetricMap[predicate2Index]; - EXPECT_THAT(condition2Metrics, UnorderedElementsAre(value3Index)); - - // Verify the trackerToMetricMap. - ASSERT_EQ(trackerToMetricMap.size(), 4); - const vector& matcher1Metrics = trackerToMetricMap[matcher1Index]; - EXPECT_THAT(matcher1Metrics, UnorderedElementsAre(value2Index)); - const vector& matcher3Metrics = trackerToMetricMap[matcher3Index]; - EXPECT_THAT(matcher3Metrics, UnorderedElementsAre(value4Index)); - const vector& matcher4Metrics = trackerToMetricMap[matcher4Index]; - EXPECT_THAT(matcher4Metrics, UnorderedElementsAre(value1Index)); - const vector& matcher5Metrics = trackerToMetricMap[matcher5Index]; - EXPECT_THAT(matcher5Metrics, UnorderedElementsAre(value3Index, value6Index)); - - // Verify event activation/deactivation maps. - ASSERT_EQ(activationAtomTrackerToMetricMap.size(), 0); - ASSERT_EQ(deactivationAtomTrackerToMetricMap.size(), 0); - ASSERT_EQ(metricsWithActivation.size(), 0); - - // Verify tracker indices/ids/conditions/states are correct. - ValueMetricProducer* valueProducer1 = - static_cast(newMetricProducers[value1Index].get()); - EXPECT_EQ(valueProducer1->getMetricId(), value1Id); - EXPECT_EQ(valueProducer1->mConditionTrackerIndex, predicate1Index); - EXPECT_EQ(valueProducer1->mCondition, ConditionState::kUnknown); - EXPECT_EQ(valueProducer1->mWhatMatcherIndex, matcher4Index); - ValueMetricProducer* valueProducer2 = - static_cast(newMetricProducers[value2Index].get()); - EXPECT_EQ(valueProducer2->getMetricId(), value2Id); - EXPECT_EQ(valueProducer2->mConditionTrackerIndex, -1); - EXPECT_EQ(valueProducer2->mCondition, ConditionState::kTrue); - EXPECT_EQ(valueProducer2->mWhatMatcherIndex, matcher1Index); - ValueMetricProducer* valueProducer3 = - static_cast(newMetricProducers[value3Index].get()); - EXPECT_EQ(valueProducer3->getMetricId(), value3Id); - EXPECT_EQ(valueProducer3->mConditionTrackerIndex, predicate2Index); - EXPECT_EQ(valueProducer3->mCondition, ConditionState::kUnknown); - EXPECT_EQ(valueProducer3->mWhatMatcherIndex, matcher5Index); - ValueMetricProducer* valueProducer4 = - static_cast(newMetricProducers[value4Index].get()); - EXPECT_EQ(valueProducer4->getMetricId(), value4Id); - EXPECT_EQ(valueProducer4->mConditionTrackerIndex, -1); - EXPECT_EQ(valueProducer4->mCondition, ConditionState::kTrue); - EXPECT_EQ(valueProducer4->mWhatMatcherIndex, matcher3Index); - ValueMetricProducer* valueProducer6 = - static_cast(newMetricProducers[value6Index].get()); - EXPECT_EQ(valueProducer6->getMetricId(), value6Id); - EXPECT_EQ(valueProducer6->mConditionTrackerIndex, predicate1Index); - EXPECT_EQ(valueProducer6->mCondition, ConditionState::kUnknown); - EXPECT_EQ(valueProducer6->mWhatMatcherIndex, matcher5Index); - - sp newMatcherWizard = valueProducer1->mEventMatcherWizard; - EXPECT_NE(newMatcherWizard, oldMatcherWizard); - EXPECT_EQ(newMatcherWizard->getStrongCount(), 6); - oldMetricProducers.clear(); - // Only reference to the old wizard should be the one in the test. - EXPECT_EQ(oldMatcherWizard->getStrongCount(), 1); -} - -TEST_F(ConfigUpdateTest, TestUpdateMetricActivations) { - StatsdConfig config; - // Add atom matchers - AtomMatcher matcher1 = CreateScreenTurnedOnAtomMatcher(); - int64_t matcher1Id = matcher1.id(); - *config.add_atom_matcher() = matcher1; - - AtomMatcher matcher2 = CreateScreenTurnedOffAtomMatcher(); - int64_t matcher2Id = matcher2.id(); - *config.add_atom_matcher() = matcher2; - - AtomMatcher matcher3 = CreateStartScheduledJobAtomMatcher(); - int64_t matcher3Id = matcher3.id(); - *config.add_atom_matcher() = matcher3; - - AtomMatcher matcher4 = CreateFinishScheduledJobAtomMatcher(); - int64_t matcher4Id = matcher4.id(); - *config.add_atom_matcher() = matcher4; - - // Add an event metric with multiple activations. - EventMetric event1 = createEventMetric("EVENT1", matcher1Id, nullopt); - int64_t event1Id = event1.id(); - *config.add_event_metric() = event1; - - int64_t matcher2TtlSec = 2, matcher3TtlSec = 3, matcher4TtlSec = 4; - MetricActivation metricActivation; - metricActivation.set_metric_id(event1Id); - EventActivation* activation = metricActivation.add_event_activation(); - activation->set_atom_matcher_id(matcher2Id); - activation->set_ttl_seconds(matcher2TtlSec); - activation->set_activation_type(ACTIVATE_IMMEDIATELY); - activation->set_deactivation_atom_matcher_id(matcher1Id); - activation = metricActivation.add_event_activation(); - activation->set_atom_matcher_id(matcher3Id); - activation->set_ttl_seconds(matcher3TtlSec); - activation->set_activation_type(ACTIVATE_ON_BOOT); - activation->set_deactivation_atom_matcher_id(matcher1Id); - activation = metricActivation.add_event_activation(); - activation->set_atom_matcher_id(matcher4Id); - activation->set_ttl_seconds(matcher4TtlSec); - activation->set_activation_type(ACTIVATE_IMMEDIATELY); - activation->set_deactivation_atom_matcher_id(matcher2Id); - *config.add_metric_activation() = metricActivation; - - EXPECT_TRUE(initConfig(config)); - - // Activate some of the event activations. - ASSERT_EQ(oldMetricProducers[0]->getMetricId(), event1Id); - int64_t matcher2StartNs = 12345; - oldMetricProducers[0]->activate(oldAtomMatchingTrackerMap[matcher2Id], matcher2StartNs); - int64_t matcher3StartNs = 23456; - oldMetricProducers[0]->activate(oldAtomMatchingTrackerMap[matcher3Id], matcher3StartNs); - EXPECT_TRUE(oldMetricProducers[0]->isActive()); - - // Map the matchers and predicates in reverse order to force the indices to change. - std::unordered_map newAtomMatchingTrackerMap; - const int matcher4Index = 0; - newAtomMatchingTrackerMap[matcher4Id] = 0; - const int matcher3Index = 1; - newAtomMatchingTrackerMap[matcher3Id] = 1; - const int matcher2Index = 2; - newAtomMatchingTrackerMap[matcher2Id] = 2; - const int matcher1Index = 3; - newAtomMatchingTrackerMap[matcher1Id] = 3; - // Use the existing matchers. A bit hacky, but saves code and we don't rely on them. - vector> newAtomMatchingTrackers(4); - std::reverse_copy(oldAtomMatchingTrackers.begin(), oldAtomMatchingTrackers.end(), - newAtomMatchingTrackers.begin()); - set replacedMatchers; - - unordered_map newConditionTrackerMap; - vector> newConditionTrackers; - set replacedConditions; - vector conditionCache; - unordered_map newMetricProducerMap; - vector> newMetricProducers; - unordered_map> conditionToMetricMap; - unordered_map> trackerToMetricMap; - set noReportMetricIds; - unordered_map> activationAtomTrackerToMetricMap; - unordered_map> deactivationAtomTrackerToMetricMap; - vector metricsWithActivation; - set replacedMetrics; - EXPECT_TRUE(updateMetrics( - key, config, /*timeBaseNs=*/123, /*currentTimeNs=*/12345, new StatsPullerManager(), - oldAtomMatchingTrackerMap, newAtomMatchingTrackerMap, replacedMatchers, - newAtomMatchingTrackers, newConditionTrackerMap, replacedConditions, - newConditionTrackers, conditionCache, /*stateAtomIdMap=*/{}, /*allStateGroupMaps=*/{}, - /*replacedStates=*/{}, oldMetricProducerMap, oldMetricProducers, newMetricProducerMap, - newMetricProducers, conditionToMetricMap, trackerToMetricMap, noReportMetricIds, - activationAtomTrackerToMetricMap, deactivationAtomTrackerToMetricMap, - metricsWithActivation, replacedMetrics)); - - // Verify event activation/deactivation maps. - ASSERT_EQ(activationAtomTrackerToMetricMap.size(), 3); - EXPECT_THAT(activationAtomTrackerToMetricMap[matcher2Index], UnorderedElementsAre(0)); - EXPECT_THAT(activationAtomTrackerToMetricMap[matcher3Index], UnorderedElementsAre(0)); - EXPECT_THAT(activationAtomTrackerToMetricMap[matcher4Index], UnorderedElementsAre(0)); - ASSERT_EQ(deactivationAtomTrackerToMetricMap.size(), 2); - EXPECT_THAT(deactivationAtomTrackerToMetricMap[matcher1Index], UnorderedElementsAre(0, 0)); - EXPECT_THAT(deactivationAtomTrackerToMetricMap[matcher2Index], UnorderedElementsAre(0)); - ASSERT_EQ(metricsWithActivation.size(), 1); - EXPECT_THAT(metricsWithActivation, UnorderedElementsAre(0)); - - // Verify mEventActivation and mEventDeactivation map of the producer. - sp producer = newMetricProducers[0]; - EXPECT_TRUE(producer->isActive()); - ASSERT_EQ(producer->mEventActivationMap.size(), 3); - shared_ptr matcher2Activation = producer->mEventActivationMap[matcher2Index]; - EXPECT_EQ(matcher2Activation->ttl_ns, matcher2TtlSec * NS_PER_SEC); - EXPECT_EQ(matcher2Activation->activationType, ACTIVATE_IMMEDIATELY); - EXPECT_EQ(matcher2Activation->state, kActive); - EXPECT_EQ(matcher2Activation->start_ns, matcher2StartNs); - shared_ptr matcher3Activation = producer->mEventActivationMap[matcher3Index]; - EXPECT_EQ(matcher3Activation->ttl_ns, matcher3TtlSec * NS_PER_SEC); - EXPECT_EQ(matcher3Activation->activationType, ACTIVATE_ON_BOOT); - EXPECT_EQ(matcher3Activation->state, kActiveOnBoot); - shared_ptr matcher4Activation = producer->mEventActivationMap[matcher4Index]; - EXPECT_EQ(matcher4Activation->ttl_ns, matcher4TtlSec * NS_PER_SEC); - EXPECT_EQ(matcher4Activation->activationType, ACTIVATE_IMMEDIATELY); - EXPECT_EQ(matcher4Activation->state, kNotActive); - - ASSERT_EQ(producer->mEventDeactivationMap.size(), 2); - EXPECT_THAT(producer->mEventDeactivationMap[matcher1Index], - UnorderedElementsAre(matcher2Activation, matcher3Activation)); - EXPECT_THAT(producer->mEventDeactivationMap[matcher2Index], - UnorderedElementsAre(matcher4Activation)); -} - -TEST_F(ConfigUpdateTest, TestUpdateMetricsMultipleTypes) { - StatsdConfig config; - // Add atom matchers/predicates/states. These are mostly needed for initStatsdConfig - AtomMatcher matcher1 = CreateScreenTurnedOnAtomMatcher(); - int64_t matcher1Id = matcher1.id(); - *config.add_atom_matcher() = matcher1; - - AtomMatcher matcher2 = CreateScreenTurnedOffAtomMatcher(); - int64_t matcher2Id = matcher2.id(); - *config.add_atom_matcher() = matcher2; - - AtomMatcher matcher3 = CreateTemperatureAtomMatcher(); - int64_t matcher3Id = matcher3.id(); - *config.add_atom_matcher() = matcher3; - - Predicate predicate1 = CreateScreenIsOnPredicate(); - int64_t predicate1Id = predicate1.id(); - *config.add_predicate() = predicate1; - - // Add a few count metrics. - // Will be preserved. - CountMetric countMetric = createCountMetric("COUNT1", matcher1Id, predicate1Id, {}); - int64_t countMetricId = countMetric.id(); - *config.add_count_metric() = countMetric; - - // Will be replaced since matcher2 is replaced. - EventMetric eventMetric = createEventMetric("EVENT1", matcher2Id, nullopt); - int64_t eventMetricId = eventMetric.id(); - *config.add_event_metric() = eventMetric; - - // Will be replaced because the definition changes - a predicate is added. - GaugeMetric gaugeMetric = createGaugeMetric("GAUGE1", matcher3Id, - GaugeMetric::RANDOM_ONE_SAMPLE, nullopt, nullopt); - int64_t gaugeMetricId = gaugeMetric.id(); - *config.add_gauge_metric() = gaugeMetric; - - // Preserved. - ValueMetric valueMetric = createValueMetric("VALUE1", matcher3, 2, predicate1Id, {}); - int64_t valueMetricId = valueMetric.id(); - *config.add_value_metric() = valueMetric; - - // Preserved. - DurationMetric durationMetric = createDurationMetric("DURATION1", predicate1Id, nullopt, {}); - int64_t durationMetricId = durationMetric.id(); - *config.add_duration_metric() = durationMetric; - - EXPECT_TRUE(initConfig(config)); - - // Used later to ensure the condition wizard is replaced. Get it before doing the update. - sp oldConditionWizard = oldMetricProducers[0]->mWizard; - EXPECT_EQ(oldConditionWizard->getStrongCount(), 6); - - // Mark matcher 2 as replaced. Causes eventMetric to be replaced. - set replacedMatchers; - replacedMatchers.insert(matcher2Id); - - // Add predicate1 as a predicate on gaugeMetric, causing it to be replaced. - gaugeMetric.set_condition(predicate1Id); - - // Map the matchers and predicates in reverse order to force the indices to change. - std::unordered_map newAtomMatchingTrackerMap; - const int matcher3Index = 0; - newAtomMatchingTrackerMap[matcher3Id] = 0; - const int matcher2Index = 1; - newAtomMatchingTrackerMap[matcher2Id] = 1; - const int matcher1Index = 2; - newAtomMatchingTrackerMap[matcher1Id] = 2; - // Use the existing matchers. A bit hacky, but saves code and we don't rely on them. - vector> newAtomMatchingTrackers(3); - std::reverse_copy(oldAtomMatchingTrackers.begin(), oldAtomMatchingTrackers.end(), - newAtomMatchingTrackers.begin()); - - std::unordered_map newConditionTrackerMap; - const int predicate1Index = 0; - newConditionTrackerMap[predicate1Id] = 0; - // Use the existing conditionTrackers. A bit hacky, but saves code and we don't rely on them. - vector> newConditionTrackers(1); - std::reverse_copy(oldConditionTrackers.begin(), oldConditionTrackers.end(), - newConditionTrackers.begin()); - vector conditionCache = {ConditionState::kUnknown}; - - // The order matters. we parse in the order of: count, duration, event, value, gauge. - StatsdConfig newConfig; - *newConfig.add_count_metric() = countMetric; - const int countMetricIndex = 0; - *newConfig.add_duration_metric() = durationMetric; - const int durationMetricIndex = 1; - *newConfig.add_event_metric() = eventMetric; - const int eventMetricIndex = 2; - *newConfig.add_value_metric() = valueMetric; - const int valueMetricIndex = 3; - *newConfig.add_gauge_metric() = gaugeMetric; - const int gaugeMetricIndex = 4; - - // Add the predicate since duration metric needs it. - *newConfig.add_predicate() = predicate1; - - // Output data structures to validate. - unordered_map newMetricProducerMap; - vector> newMetricProducers; - unordered_map> conditionToMetricMap; - unordered_map> trackerToMetricMap; - set noReportMetricIds; - unordered_map> activationAtomTrackerToMetricMap; - unordered_map> deactivationAtomTrackerToMetricMap; - vector metricsWithActivation; - set replacedMetrics; - EXPECT_TRUE(updateMetrics( - key, newConfig, /*timeBaseNs=*/123, /*currentTimeNs=*/12345, new StatsPullerManager(), - oldAtomMatchingTrackerMap, newAtomMatchingTrackerMap, replacedMatchers, - newAtomMatchingTrackers, newConditionTrackerMap, /*replacedConditions=*/{}, - newConditionTrackers, conditionCache, /*stateAtomIdMap*/ {}, /*allStateGroupMaps=*/{}, - /*replacedStates=*/{}, oldMetricProducerMap, oldMetricProducers, newMetricProducerMap, - newMetricProducers, conditionToMetricMap, trackerToMetricMap, noReportMetricIds, - activationAtomTrackerToMetricMap, deactivationAtomTrackerToMetricMap, - metricsWithActivation, replacedMetrics)); - - unordered_map expectedMetricProducerMap = { - {countMetricId, countMetricIndex}, {durationMetricId, durationMetricIndex}, - {eventMetricId, eventMetricIndex}, {valueMetricId, valueMetricIndex}, - {gaugeMetricId, gaugeMetricIndex}, - }; - EXPECT_THAT(newMetricProducerMap, ContainerEq(expectedMetricProducerMap)); - - EXPECT_EQ(replacedMetrics, set({eventMetricId, gaugeMetricId})); - - // Make sure preserved metrics are the same. - ASSERT_EQ(newMetricProducers.size(), 5); - EXPECT_EQ(oldMetricProducers[oldMetricProducerMap.at(countMetricId)], - newMetricProducers[newMetricProducerMap.at(countMetricId)]); - EXPECT_EQ(oldMetricProducers[oldMetricProducerMap.at(durationMetricId)], - newMetricProducers[newMetricProducerMap.at(durationMetricId)]); - EXPECT_EQ(oldMetricProducers[oldMetricProducerMap.at(valueMetricId)], - newMetricProducers[newMetricProducerMap.at(valueMetricId)]); - - // Make sure replaced metrics are different. - EXPECT_NE(oldMetricProducers[oldMetricProducerMap.at(eventMetricId)], - newMetricProducers[newMetricProducerMap.at(eventMetricId)]); - EXPECT_NE(oldMetricProducers[oldMetricProducerMap.at(gaugeMetricId)], - newMetricProducers[newMetricProducerMap.at(gaugeMetricId)]); - - // Verify the conditionToMetricMap. - ASSERT_EQ(conditionToMetricMap.size(), 1); - const vector& condition1Metrics = conditionToMetricMap[predicate1Index]; - EXPECT_THAT(condition1Metrics, - UnorderedElementsAre(countMetricIndex, gaugeMetricIndex, valueMetricIndex)); - - // Verify the trackerToMetricMap. - ASSERT_EQ(trackerToMetricMap.size(), 3); - const vector& matcher1Metrics = trackerToMetricMap[matcher1Index]; - EXPECT_THAT(matcher1Metrics, UnorderedElementsAre(countMetricIndex, durationMetricIndex)); - const vector& matcher2Metrics = trackerToMetricMap[matcher2Index]; - EXPECT_THAT(matcher2Metrics, UnorderedElementsAre(eventMetricIndex, durationMetricIndex)); - const vector& matcher3Metrics = trackerToMetricMap[matcher3Index]; - EXPECT_THAT(matcher3Metrics, UnorderedElementsAre(gaugeMetricIndex, valueMetricIndex)); - - // Verify event activation/deactivation maps. - ASSERT_EQ(activationAtomTrackerToMetricMap.size(), 0); - ASSERT_EQ(deactivationAtomTrackerToMetricMap.size(), 0); - ASSERT_EQ(metricsWithActivation.size(), 0); - - // Verify tracker indices/ids/conditions are correct. - EXPECT_EQ(newMetricProducers[countMetricIndex]->getMetricId(), countMetricId); - EXPECT_EQ(newMetricProducers[countMetricIndex]->mConditionTrackerIndex, predicate1Index); - EXPECT_EQ(newMetricProducers[countMetricIndex]->mCondition, ConditionState::kUnknown); - EXPECT_EQ(newMetricProducers[durationMetricIndex]->getMetricId(), durationMetricId); - EXPECT_EQ(newMetricProducers[durationMetricIndex]->mConditionTrackerIndex, -1); - EXPECT_EQ(newMetricProducers[durationMetricIndex]->mCondition, ConditionState::kTrue); - EXPECT_EQ(newMetricProducers[eventMetricIndex]->getMetricId(), eventMetricId); - EXPECT_EQ(newMetricProducers[eventMetricIndex]->mConditionTrackerIndex, -1); - EXPECT_EQ(newMetricProducers[eventMetricIndex]->mCondition, ConditionState::kTrue); - EXPECT_EQ(newMetricProducers[gaugeMetricIndex]->getMetricId(), gaugeMetricId); - EXPECT_EQ(newMetricProducers[gaugeMetricIndex]->mConditionTrackerIndex, predicate1Index); - EXPECT_EQ(newMetricProducers[gaugeMetricIndex]->mCondition, ConditionState::kUnknown); - - sp newConditionWizard = newMetricProducers[0]->mWizard; - EXPECT_NE(newConditionWizard, oldConditionWizard); - EXPECT_EQ(newConditionWizard->getStrongCount(), 6); - oldMetricProducers.clear(); - // Only reference to the old wizard should be the one in the test. - EXPECT_EQ(oldConditionWizard->getStrongCount(), 1); -} - -TEST_F(ConfigUpdateTest, TestAlertPreserve) { - StatsdConfig config; - AtomMatcher whatMatcher = CreateScreenBrightnessChangedAtomMatcher(); - *config.add_atom_matcher() = whatMatcher; - - *config.add_count_metric() = createCountMetric("VALUE1", whatMatcher.id(), nullopt, {}); - - Alert alert = createAlert("Alert1", config.count_metric(0).id(), 1, 1); - *config.add_alert() = alert; - EXPECT_TRUE(initConfig(config)); - - UpdateStatus updateStatus = UPDATE_UNKNOWN; - EXPECT_TRUE(determineAlertUpdateStatus(alert, oldAlertTrackerMap, oldAnomalyTrackers, - /*replacedMetrics*/ {}, updateStatus)); - EXPECT_EQ(updateStatus, UPDATE_PRESERVE); -} - -TEST_F(ConfigUpdateTest, TestAlertMetricChanged) { - StatsdConfig config; - AtomMatcher whatMatcher = CreateScreenBrightnessChangedAtomMatcher(); - *config.add_atom_matcher() = whatMatcher; - - CountMetric metric = createCountMetric("VALUE1", whatMatcher.id(), nullopt, {}); - *config.add_count_metric() = metric; - - Alert alert = createAlert("Alert1", config.count_metric(0).id(), 1, 1); - *config.add_alert() = alert; - EXPECT_TRUE(initConfig(config)); - - UpdateStatus updateStatus = UPDATE_UNKNOWN; - EXPECT_TRUE(determineAlertUpdateStatus(alert, oldAlertTrackerMap, oldAnomalyTrackers, - /*replacedMetrics*/ {metric.id()}, updateStatus)); - EXPECT_EQ(updateStatus, UPDATE_REPLACE); -} - -TEST_F(ConfigUpdateTest, TestAlertDefinitionChanged) { - StatsdConfig config; - AtomMatcher whatMatcher = CreateScreenBrightnessChangedAtomMatcher(); - *config.add_atom_matcher() = whatMatcher; - - *config.add_count_metric() = createCountMetric("VALUE1", whatMatcher.id(), nullopt, {}); - - Alert alert = createAlert("Alert1", config.count_metric(0).id(), 1, 1); - *config.add_alert() = alert; - EXPECT_TRUE(initConfig(config)); - - alert.set_num_buckets(2); - - UpdateStatus updateStatus = UPDATE_UNKNOWN; - EXPECT_TRUE(determineAlertUpdateStatus(alert, oldAlertTrackerMap, oldAnomalyTrackers, - /*replacedMetrics*/ {}, updateStatus)); - EXPECT_EQ(updateStatus, UPDATE_REPLACE); -} - -TEST_F(ConfigUpdateTest, TestUpdateAlerts) { - StatsdConfig config; - // Add atom matchers/predicates/metrics. These are mostly needed for initStatsdConfig - *config.add_atom_matcher() = CreateScreenTurnedOnAtomMatcher(); - *config.add_atom_matcher() = CreateScreenTurnedOffAtomMatcher(); - *config.add_predicate() = CreateScreenIsOnPredicate(); - - CountMetric countMetric = createCountMetric("COUNT1", config.atom_matcher(0).id(), nullopt, {}); - int64_t countMetricId = countMetric.id(); - *config.add_count_metric() = countMetric; - - DurationMetric durationMetric = - createDurationMetric("DURATION1", config.predicate(0).id(), nullopt, {}); - int64_t durationMetricId = durationMetric.id(); - *config.add_duration_metric() = durationMetric; - - // Add alerts. - // Preserved. - Alert alert1 = createAlert("Alert1", durationMetricId, /*buckets*/ 1, /*triggerSum*/ 5000); - int64_t alert1Id = alert1.id(); - *config.add_alert() = alert1; - - // Replaced. - Alert alert2 = createAlert("Alert2", countMetricId, /*buckets*/ 1, /*triggerSum*/ 2); - int64_t alert2Id = alert2.id(); - *config.add_alert() = alert2; - - // Replaced. - Alert alert3 = createAlert("Alert3", durationMetricId, /*buckets*/ 3, /*triggerSum*/ 5000); - int64_t alert3Id = alert3.id(); - *config.add_alert() = alert3; - - // Add Subscriptions. - Subscription subscription1 = createSubscription("S1", Subscription::ALERT, alert1Id); - *config.add_subscription() = subscription1; - Subscription subscription2 = createSubscription("S2", Subscription::ALERT, alert1Id); - *config.add_subscription() = subscription2; - Subscription subscription3 = createSubscription("S3", Subscription::ALERT, alert2Id); - *config.add_subscription() = subscription3; - - EXPECT_TRUE(initConfig(config)); - - // Add a duration tracker to the duration metric to ensure durationTrackers are updated - // with the proper anomalyTrackers. - unique_ptr event = CreateScreenStateChangedEvent( - timeBaseNs + 1, android::view::DisplayStateEnum::DISPLAY_STATE_ON); - oldMetricProducers[1]->onMatchedLogEvent(0, *event.get()); - - // Change the count metric. Causes alert2 to be replaced. - config.mutable_count_metric(0)->set_bucket(ONE_DAY); - // Change num buckets on alert3, causing replacement. - alert3.set_num_buckets(5); - - // New alert. - Alert alert4 = createAlert("Alert4", durationMetricId, /*buckets*/ 3, /*triggerSum*/ 10000); - int64_t alert4Id = alert4.id(); - - // Move subscription2 to be on alert2 and make a new subscription. - subscription2.set_rule_id(alert2Id); - Subscription subscription4 = createSubscription("S4", Subscription::ALERT, alert2Id); - - // Create the new config. Modify the old one to avoid adding the matchers/predicates. - // Add alerts in different order so the map is changed. - config.clear_alert(); - *config.add_alert() = alert4; - const int alert4Index = 0; - *config.add_alert() = alert3; - const int alert3Index = 1; - *config.add_alert() = alert1; - const int alert1Index = 2; - *config.add_alert() = alert2; - const int alert2Index = 3; - - // Subscription3 is removed. - config.clear_subscription(); - *config.add_subscription() = subscription4; - *config.add_subscription() = subscription2; - *config.add_subscription() = subscription1; - - // Output data structures from update metrics. Don't care about the outputs besides - // replacedMetrics, but need to do this so that the metrics clear their anomaly trackers. - unordered_map newMetricProducerMap; - vector> newMetricProducers; - unordered_map> conditionToMetricMap; - unordered_map> trackerToMetricMap; - set noReportMetricIds; - unordered_map> activationAtomTrackerToMetricMap; - unordered_map> deactivationAtomTrackerToMetricMap; - vector metricsWithActivation; - set replacedMetrics; - int64_t currentTimeNs = 12345; - EXPECT_TRUE(updateMetrics( - key, config, /*timeBaseNs=*/123, currentTimeNs, new StatsPullerManager(), - oldAtomMatchingTrackerMap, oldAtomMatchingTrackerMap, /*replacedMatchers*/ {}, - oldAtomMatchingTrackers, oldConditionTrackerMap, /*replacedConditions=*/{}, - oldConditionTrackers, {ConditionState::kUnknown}, /*stateAtomIdMap*/ {}, - /*allStateGroupMaps=*/{}, - /*replacedStates=*/{}, oldMetricProducerMap, oldMetricProducers, newMetricProducerMap, - newMetricProducers, conditionToMetricMap, trackerToMetricMap, noReportMetricIds, - activationAtomTrackerToMetricMap, deactivationAtomTrackerToMetricMap, - metricsWithActivation, replacedMetrics)); - - EXPECT_EQ(replacedMetrics, set({countMetricId})); - - unordered_map newAlertTrackerMap; - vector> newAnomalyTrackers; - EXPECT_TRUE(updateAlerts(config, currentTimeNs, newMetricProducerMap, replacedMetrics, - oldAlertTrackerMap, oldAnomalyTrackers, anomalyAlarmMonitor, - newMetricProducers, newAlertTrackerMap, newAnomalyTrackers)); - - unordered_map expectedAlertMap = { - {alert1Id, alert1Index}, - {alert2Id, alert2Index}, - {alert3Id, alert3Index}, - {alert4Id, alert4Index}, - }; - EXPECT_THAT(newAlertTrackerMap, ContainerEq(expectedAlertMap)); - - // Make sure preserved alerts are the same. - ASSERT_EQ(newAnomalyTrackers.size(), 4); - EXPECT_EQ(oldAnomalyTrackers[oldAlertTrackerMap.at(alert1Id)], - newAnomalyTrackers[newAlertTrackerMap.at(alert1Id)]); - - // Make sure replaced alerts are different. - EXPECT_NE(oldAnomalyTrackers[oldAlertTrackerMap.at(alert2Id)], - newAnomalyTrackers[newAlertTrackerMap.at(alert2Id)]); - EXPECT_NE(oldAnomalyTrackers[oldAlertTrackerMap.at(alert3Id)], - newAnomalyTrackers[newAlertTrackerMap.at(alert3Id)]); - - // Verify the alerts have the correct anomaly trackers. - ASSERT_EQ(newMetricProducers.size(), 2); - EXPECT_THAT(newMetricProducers[0]->mAnomalyTrackers, - UnorderedElementsAre(newAnomalyTrackers[alert2Index])); - // For durationMetric, make sure the duration trackers get the updated anomalyTrackers. - DurationMetricProducer* durationProducer = - static_cast(newMetricProducers[1].get()); - EXPECT_THAT( - durationProducer->mAnomalyTrackers, - UnorderedElementsAre(newAnomalyTrackers[alert1Index], newAnomalyTrackers[alert3Index], - newAnomalyTrackers[alert4Index])); - ASSERT_EQ(durationProducer->mCurrentSlicedDurationTrackerMap.size(), 1); - for (const auto& durationTrackerIt : durationProducer->mCurrentSlicedDurationTrackerMap) { - EXPECT_EQ(durationTrackerIt.second->mAnomalyTrackers, durationProducer->mAnomalyTrackers); - } - - // Verify alerts have the correct subscriptions. Use subscription id as proxy for equivalency. - vector alert1Subscriptions; - for (const Subscription& subscription : newAnomalyTrackers[alert1Index]->mSubscriptions) { - alert1Subscriptions.push_back(subscription.id()); - } - EXPECT_THAT(alert1Subscriptions, UnorderedElementsAre(subscription1.id())); - vector alert2Subscriptions; - for (const Subscription& subscription : newAnomalyTrackers[alert2Index]->mSubscriptions) { - alert2Subscriptions.push_back(subscription.id()); - } - EXPECT_THAT(alert2Subscriptions, UnorderedElementsAre(subscription2.id(), subscription4.id())); - EXPECT_THAT(newAnomalyTrackers[alert3Index]->mSubscriptions, IsEmpty()); - EXPECT_THAT(newAnomalyTrackers[alert4Index]->mSubscriptions, IsEmpty()); -} - -TEST_F(ConfigUpdateTest, TestUpdateAlarms) { - StatsdConfig config; - // Add alarms. - Alarm alarm1 = createAlarm("Alarm1", /*offset*/ 1 * MS_PER_SEC, /*period*/ 50 * MS_PER_SEC); - int64_t alarm1Id = alarm1.id(); - *config.add_alarm() = alarm1; - - Alarm alarm2 = createAlarm("Alarm2", /*offset*/ 1 * MS_PER_SEC, /*period*/ 2000 * MS_PER_SEC); - int64_t alarm2Id = alarm2.id(); - *config.add_alarm() = alarm2; - - Alarm alarm3 = createAlarm("Alarm3", /*offset*/ 10 * MS_PER_SEC, /*period*/ 5000 * MS_PER_SEC); - int64_t alarm3Id = alarm3.id(); - *config.add_alarm() = alarm3; - - // Add Subscriptions. - Subscription subscription1 = createSubscription("S1", Subscription::ALARM, alarm1Id); - *config.add_subscription() = subscription1; - Subscription subscription2 = createSubscription("S2", Subscription::ALARM, alarm1Id); - *config.add_subscription() = subscription2; - Subscription subscription3 = createSubscription("S3", Subscription::ALARM, alarm2Id); - *config.add_subscription() = subscription3; - - EXPECT_TRUE(initConfig(config)); - - ASSERT_EQ(oldAlarmTrackers.size(), 3); - // Config is created at statsd start time, so just add the offsets. - EXPECT_EQ(oldAlarmTrackers[0]->getAlarmTimestampSec(), timeBaseNs / NS_PER_SEC + 1); - EXPECT_EQ(oldAlarmTrackers[1]->getAlarmTimestampSec(), timeBaseNs / NS_PER_SEC + 1); - EXPECT_EQ(oldAlarmTrackers[2]->getAlarmTimestampSec(), timeBaseNs / NS_PER_SEC + 10); - - // Change alarm2/alarm3. - config.mutable_alarm(1)->set_offset_millis(5 * MS_PER_SEC); - config.mutable_alarm(2)->set_period_millis(10000 * MS_PER_SEC); - - // Move subscription2 to be on alarm2 and make a new subscription. - config.mutable_subscription(1)->set_rule_id(alarm2Id); - Subscription subscription4 = createSubscription("S4", Subscription::ALARM, alarm1Id); - *config.add_subscription() = subscription4; - - // Update time is 2 seconds after the base time. - int64_t currentTimeNs = timeBaseNs + 2 * NS_PER_SEC; - vector> newAlarmTrackers; - EXPECT_TRUE(initAlarms(config, key, periodicAlarmMonitor, timeBaseNs, currentTimeNs, - newAlarmTrackers)); - - ASSERT_EQ(newAlarmTrackers.size(), 3); - // Config is updated 2 seconds after statsd start - // The offset has passed for alarm1, but not for alarms 2/3. - EXPECT_EQ(newAlarmTrackers[0]->getAlarmTimestampSec(), timeBaseNs / NS_PER_SEC + 1 + 50); - EXPECT_EQ(newAlarmTrackers[1]->getAlarmTimestampSec(), timeBaseNs / NS_PER_SEC + 5); - EXPECT_EQ(newAlarmTrackers[2]->getAlarmTimestampSec(), timeBaseNs / NS_PER_SEC + 10); - - // Verify alarms have the correct subscriptions. Use subscription id as proxy for equivalency. - vector alarm1Subscriptions; - for (const Subscription& subscription : newAlarmTrackers[0]->mSubscriptions) { - alarm1Subscriptions.push_back(subscription.id()); - } - EXPECT_THAT(alarm1Subscriptions, UnorderedElementsAre(subscription1.id(), subscription4.id())); - vector alarm2Subscriptions; - for (const Subscription& subscription : newAlarmTrackers[1]->mSubscriptions) { - alarm2Subscriptions.push_back(subscription.id()); - } - EXPECT_THAT(alarm2Subscriptions, UnorderedElementsAre(subscription2.id(), subscription3.id())); - EXPECT_THAT(newAlarmTrackers[2]->mSubscriptions, IsEmpty()); - - // Verify the alarm monitor is updated accordingly once the old alarms are removed. - // Alarm2 fires the earliest. - oldAlarmTrackers.clear(); - EXPECT_EQ(periodicAlarmMonitor->getRegisteredAlarmTimeSec(), timeBaseNs / NS_PER_SEC + 5); - - // Do another update 60 seconds after config creation time, after the offsets of each alarm. - currentTimeNs = timeBaseNs + 60 * NS_PER_SEC; - newAlarmTrackers.clear(); - EXPECT_TRUE(initAlarms(config, key, periodicAlarmMonitor, timeBaseNs, currentTimeNs, - newAlarmTrackers)); - - ASSERT_EQ(newAlarmTrackers.size(), 3); - // Config is updated one minute after statsd start. - // Two periods have passed for alarm 1, one has passed for alarms2/3. - EXPECT_EQ(newAlarmTrackers[0]->getAlarmTimestampSec(), timeBaseNs / NS_PER_SEC + 1 + 2 * 50); - EXPECT_EQ(newAlarmTrackers[1]->getAlarmTimestampSec(), timeBaseNs / NS_PER_SEC + 5 + 2000); - EXPECT_EQ(newAlarmTrackers[2]->getAlarmTimestampSec(), timeBaseNs / NS_PER_SEC + 10 + 10000); -} - -} // namespace statsd -} // namespace os -} // namespace android - -#else -GTEST_LOG_(INFO) << "This test does nothing.\n"; -#endif diff --git a/bin/tests/metrics/parsing_utils/metrics_manager_util_test.cpp b/bin/tests/metrics/parsing_utils/metrics_manager_util_test.cpp deleted file mode 100644 index e64760ba..00000000 --- a/bin/tests/metrics/parsing_utils/metrics_manager_util_test.cpp +++ /dev/null @@ -1,938 +0,0 @@ -// Copyright (C) 2020 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 "src/metrics/parsing_utils/metrics_manager_util.h" - -#include -#include -#include -#include - -#include -#include -#include - -#include "packages/modules/StatsD/bin/src/statsd_config.pb.h" -#include "src/condition/ConditionTracker.h" -#include "src/matchers/AtomMatchingTracker.h" -#include "src/metrics/CountMetricProducer.h" -#include "src/metrics/DurationMetricProducer.h" -#include "src/metrics/GaugeMetricProducer.h" -#include "src/metrics/MetricProducer.h" -#include "src/metrics/ValueMetricProducer.h" -#include "src/state/StateManager.h" -#include "tests/metrics/metrics_test_helper.h" -#include "tests/statsd_test_util.h" - -using namespace testing; -using android::sp; -using android::os::statsd::Predicate; -using std::map; -using std::set; -using std::unordered_map; -using std::vector; - -#ifdef __ANDROID__ - -namespace android { -namespace os { -namespace statsd { - -namespace { -const ConfigKey kConfigKey(0, 12345); -const long kAlertId = 3; - -const long timeBaseSec = 1000; - -StatsdConfig buildGoodConfig() { - StatsdConfig config; - config.set_id(12345); - - AtomMatcher* eventMatcher = config.add_atom_matcher(); - eventMatcher->set_id(StringToId("SCREEN_IS_ON")); - - SimpleAtomMatcher* simpleAtomMatcher = eventMatcher->mutable_simple_atom_matcher(); - simpleAtomMatcher->set_atom_id(2 /*SCREEN_STATE_CHANGE*/); - simpleAtomMatcher->add_field_value_matcher()->set_field( - 1 /*SCREEN_STATE_CHANGE__DISPLAY_STATE*/); - simpleAtomMatcher->mutable_field_value_matcher(0)->set_eq_int( - 2 /*SCREEN_STATE_CHANGE__DISPLAY_STATE__STATE_ON*/); - - eventMatcher = config.add_atom_matcher(); - eventMatcher->set_id(StringToId("SCREEN_IS_OFF")); - - simpleAtomMatcher = eventMatcher->mutable_simple_atom_matcher(); - simpleAtomMatcher->set_atom_id(2 /*SCREEN_STATE_CHANGE*/); - simpleAtomMatcher->add_field_value_matcher()->set_field( - 1 /*SCREEN_STATE_CHANGE__DISPLAY_STATE*/); - simpleAtomMatcher->mutable_field_value_matcher(0)->set_eq_int( - 1 /*SCREEN_STATE_CHANGE__DISPLAY_STATE__STATE_OFF*/); - - eventMatcher = config.add_atom_matcher(); - eventMatcher->set_id(StringToId("SCREEN_ON_OR_OFF")); - - AtomMatcher_Combination* combination = eventMatcher->mutable_combination(); - combination->set_operation(LogicalOperation::OR); - combination->add_matcher(StringToId("SCREEN_IS_ON")); - combination->add_matcher(StringToId("SCREEN_IS_OFF")); - - CountMetric* metric = config.add_count_metric(); - metric->set_id(3); - metric->set_what(StringToId("SCREEN_IS_ON")); - metric->set_bucket(ONE_MINUTE); - metric->mutable_dimensions_in_what()->set_field(2 /*SCREEN_STATE_CHANGE*/); - metric->mutable_dimensions_in_what()->add_child()->set_field(1); - - config.add_no_report_metric(3); - - auto alert = config.add_alert(); - alert->set_id(kAlertId); - alert->set_metric_id(3); - alert->set_num_buckets(10); - alert->set_refractory_period_secs(100); - alert->set_trigger_if_sum_gt(100); - return config; -} - -StatsdConfig buildCircleMatchers() { - StatsdConfig config; - config.set_id(12345); - - AtomMatcher* eventMatcher = config.add_atom_matcher(); - eventMatcher->set_id(StringToId("SCREEN_IS_ON")); - - SimpleAtomMatcher* simpleAtomMatcher = eventMatcher->mutable_simple_atom_matcher(); - simpleAtomMatcher->set_atom_id(2 /*SCREEN_STATE_CHANGE*/); - simpleAtomMatcher->add_field_value_matcher()->set_field( - 1 /*SCREEN_STATE_CHANGE__DISPLAY_STATE*/); - simpleAtomMatcher->mutable_field_value_matcher(0)->set_eq_int( - 2 /*SCREEN_STATE_CHANGE__DISPLAY_STATE__STATE_ON*/); - - eventMatcher = config.add_atom_matcher(); - eventMatcher->set_id(StringToId("SCREEN_ON_OR_OFF")); - - AtomMatcher_Combination* combination = eventMatcher->mutable_combination(); - combination->set_operation(LogicalOperation::OR); - combination->add_matcher(StringToId("SCREEN_IS_ON")); - // Circle dependency - combination->add_matcher(StringToId("SCREEN_ON_OR_OFF")); - - return config; -} - -StatsdConfig buildAlertWithUnknownMetric() { - StatsdConfig config; - config.set_id(12345); - - AtomMatcher* eventMatcher = config.add_atom_matcher(); - eventMatcher->set_id(StringToId("SCREEN_IS_ON")); - - CountMetric* metric = config.add_count_metric(); - metric->set_id(3); - metric->set_what(StringToId("SCREEN_IS_ON")); - metric->set_bucket(ONE_MINUTE); - metric->mutable_dimensions_in_what()->set_field(2 /*SCREEN_STATE_CHANGE*/); - metric->mutable_dimensions_in_what()->add_child()->set_field(1); - - auto alert = config.add_alert(); - alert->set_id(3); - alert->set_metric_id(2); - alert->set_num_buckets(10); - alert->set_refractory_period_secs(100); - alert->set_trigger_if_sum_gt(100); - return config; -} - -StatsdConfig buildMissingMatchers() { - StatsdConfig config; - config.set_id(12345); - - AtomMatcher* eventMatcher = config.add_atom_matcher(); - eventMatcher->set_id(StringToId("SCREEN_IS_ON")); - - SimpleAtomMatcher* simpleAtomMatcher = eventMatcher->mutable_simple_atom_matcher(); - simpleAtomMatcher->set_atom_id(2 /*SCREEN_STATE_CHANGE*/); - simpleAtomMatcher->add_field_value_matcher()->set_field( - 1 /*SCREEN_STATE_CHANGE__DISPLAY_STATE*/); - simpleAtomMatcher->mutable_field_value_matcher(0)->set_eq_int( - 2 /*SCREEN_STATE_CHANGE__DISPLAY_STATE__STATE_ON*/); - - eventMatcher = config.add_atom_matcher(); - eventMatcher->set_id(StringToId("SCREEN_ON_OR_OFF")); - - AtomMatcher_Combination* combination = eventMatcher->mutable_combination(); - combination->set_operation(LogicalOperation::OR); - combination->add_matcher(StringToId("SCREEN_IS_ON")); - // undefined matcher - combination->add_matcher(StringToId("ABC")); - - return config; -} - -StatsdConfig buildMissingPredicate() { - StatsdConfig config; - config.set_id(12345); - - CountMetric* metric = config.add_count_metric(); - metric->set_id(3); - metric->set_what(StringToId("SCREEN_EVENT")); - metric->set_bucket(ONE_MINUTE); - metric->set_condition(StringToId("SOME_CONDITION")); - - AtomMatcher* eventMatcher = config.add_atom_matcher(); - eventMatcher->set_id(StringToId("SCREEN_EVENT")); - - SimpleAtomMatcher* simpleAtomMatcher = eventMatcher->mutable_simple_atom_matcher(); - simpleAtomMatcher->set_atom_id(2); - - return config; -} - -StatsdConfig buildDimensionMetricsWithMultiTags() { - StatsdConfig config; - config.set_id(12345); - - AtomMatcher* eventMatcher = config.add_atom_matcher(); - eventMatcher->set_id(StringToId("BATTERY_VERY_LOW")); - SimpleAtomMatcher* simpleAtomMatcher = eventMatcher->mutable_simple_atom_matcher(); - simpleAtomMatcher->set_atom_id(2); - - eventMatcher = config.add_atom_matcher(); - eventMatcher->set_id(StringToId("BATTERY_VERY_VERY_LOW")); - simpleAtomMatcher = eventMatcher->mutable_simple_atom_matcher(); - simpleAtomMatcher->set_atom_id(3); - - eventMatcher = config.add_atom_matcher(); - eventMatcher->set_id(StringToId("BATTERY_LOW")); - - AtomMatcher_Combination* combination = eventMatcher->mutable_combination(); - combination->set_operation(LogicalOperation::OR); - combination->add_matcher(StringToId("BATTERY_VERY_LOW")); - combination->add_matcher(StringToId("BATTERY_VERY_VERY_LOW")); - - // Count process state changes, slice by uid, while SCREEN_IS_OFF - CountMetric* metric = config.add_count_metric(); - metric->set_id(3); - metric->set_what(StringToId("BATTERY_LOW")); - metric->set_bucket(ONE_MINUTE); - // This case is interesting. We want to dimension across two atoms. - metric->mutable_dimensions_in_what()->add_child()->set_field(1); - - auto alert = config.add_alert(); - alert->set_id(kAlertId); - alert->set_metric_id(3); - alert->set_num_buckets(10); - alert->set_refractory_period_secs(100); - alert->set_trigger_if_sum_gt(100); - return config; -} - -StatsdConfig buildCirclePredicates() { - StatsdConfig config; - config.set_id(12345); - - AtomMatcher* eventMatcher = config.add_atom_matcher(); - eventMatcher->set_id(StringToId("SCREEN_IS_ON")); - - SimpleAtomMatcher* simpleAtomMatcher = eventMatcher->mutable_simple_atom_matcher(); - simpleAtomMatcher->set_atom_id(2 /*SCREEN_STATE_CHANGE*/); - simpleAtomMatcher->add_field_value_matcher()->set_field( - 1 /*SCREEN_STATE_CHANGE__DISPLAY_STATE*/); - simpleAtomMatcher->mutable_field_value_matcher(0)->set_eq_int( - 2 /*SCREEN_STATE_CHANGE__DISPLAY_STATE__STATE_ON*/); - - eventMatcher = config.add_atom_matcher(); - eventMatcher->set_id(StringToId("SCREEN_IS_OFF")); - - simpleAtomMatcher = eventMatcher->mutable_simple_atom_matcher(); - simpleAtomMatcher->set_atom_id(2 /*SCREEN_STATE_CHANGE*/); - simpleAtomMatcher->add_field_value_matcher()->set_field( - 1 /*SCREEN_STATE_CHANGE__DISPLAY_STATE*/); - simpleAtomMatcher->mutable_field_value_matcher(0)->set_eq_int( - 1 /*SCREEN_STATE_CHANGE__DISPLAY_STATE__STATE_OFF*/); - - auto condition = config.add_predicate(); - condition->set_id(StringToId("SCREEN_IS_ON")); - SimplePredicate* simplePredicate = condition->mutable_simple_predicate(); - simplePredicate->set_start(StringToId("SCREEN_IS_ON")); - simplePredicate->set_stop(StringToId("SCREEN_IS_OFF")); - - condition = config.add_predicate(); - condition->set_id(StringToId("SCREEN_IS_EITHER_ON_OFF")); - - Predicate_Combination* combination = condition->mutable_combination(); - combination->set_operation(LogicalOperation::OR); - combination->add_predicate(StringToId("SCREEN_IS_ON")); - combination->add_predicate(StringToId("SCREEN_IS_EITHER_ON_OFF")); - - return config; -} - -StatsdConfig buildConfigWithDifferentPredicates() { - StatsdConfig config; - config.set_id(12345); - - auto pulledAtomMatcher = - CreateSimpleAtomMatcher("SUBSYSTEM_SLEEP", util::SUBSYSTEM_SLEEP_STATE); - *config.add_atom_matcher() = pulledAtomMatcher; - auto screenOnAtomMatcher = CreateScreenTurnedOnAtomMatcher(); - *config.add_atom_matcher() = screenOnAtomMatcher; - auto screenOffAtomMatcher = CreateScreenTurnedOffAtomMatcher(); - *config.add_atom_matcher() = screenOffAtomMatcher; - auto batteryNoneAtomMatcher = CreateBatteryStateNoneMatcher(); - *config.add_atom_matcher() = batteryNoneAtomMatcher; - auto batteryUsbAtomMatcher = CreateBatteryStateUsbMatcher(); - *config.add_atom_matcher() = batteryUsbAtomMatcher; - - // Simple condition with InitialValue set to default (unknown). - auto screenOnUnknownPredicate = CreateScreenIsOnPredicate(); - *config.add_predicate() = screenOnUnknownPredicate; - - // Simple condition with InitialValue set to false. - auto screenOnFalsePredicate = config.add_predicate(); - screenOnFalsePredicate->set_id(StringToId("ScreenIsOnInitialFalse")); - SimplePredicate* simpleScreenOnFalsePredicate = - screenOnFalsePredicate->mutable_simple_predicate(); - simpleScreenOnFalsePredicate->set_start(screenOnAtomMatcher.id()); - simpleScreenOnFalsePredicate->set_stop(screenOffAtomMatcher.id()); - simpleScreenOnFalsePredicate->set_initial_value(SimplePredicate_InitialValue_FALSE); - - // Simple condition with InitialValue set to false. - auto onBatteryFalsePredicate = config.add_predicate(); - onBatteryFalsePredicate->set_id(StringToId("OnBatteryInitialFalse")); - SimplePredicate* simpleOnBatteryFalsePredicate = - onBatteryFalsePredicate->mutable_simple_predicate(); - simpleOnBatteryFalsePredicate->set_start(batteryNoneAtomMatcher.id()); - simpleOnBatteryFalsePredicate->set_stop(batteryUsbAtomMatcher.id()); - simpleOnBatteryFalsePredicate->set_initial_value(SimplePredicate_InitialValue_FALSE); - - // Combination condition with both simple condition InitialValues set to false. - auto screenOnFalseOnBatteryFalsePredicate = config.add_predicate(); - screenOnFalseOnBatteryFalsePredicate->set_id(StringToId("ScreenOnFalseOnBatteryFalse")); - screenOnFalseOnBatteryFalsePredicate->mutable_combination()->set_operation( - LogicalOperation::AND); - addPredicateToPredicateCombination(*screenOnFalsePredicate, - screenOnFalseOnBatteryFalsePredicate); - addPredicateToPredicateCombination(*onBatteryFalsePredicate, - screenOnFalseOnBatteryFalsePredicate); - - // Combination condition with one simple condition InitialValue set to unknown and one set to - // false. - auto screenOnUnknownOnBatteryFalsePredicate = config.add_predicate(); - screenOnUnknownOnBatteryFalsePredicate->set_id(StringToId("ScreenOnUnknowneOnBatteryFalse")); - screenOnUnknownOnBatteryFalsePredicate->mutable_combination()->set_operation( - LogicalOperation::AND); - addPredicateToPredicateCombination(screenOnUnknownPredicate, - screenOnUnknownOnBatteryFalsePredicate); - addPredicateToPredicateCombination(*onBatteryFalsePredicate, - screenOnUnknownOnBatteryFalsePredicate); - - // Simple condition metric with initial value false. - ValueMetric* metric1 = config.add_value_metric(); - metric1->set_id(StringToId("ValueSubsystemSleepWhileScreenOnInitialFalse")); - metric1->set_what(pulledAtomMatcher.id()); - *metric1->mutable_value_field() = - CreateDimensions(util::SUBSYSTEM_SLEEP_STATE, {4 /* time sleeping field */}); - metric1->set_bucket(FIVE_MINUTES); - metric1->set_condition(screenOnFalsePredicate->id()); - - // Simple condition metric with initial value unknown. - ValueMetric* metric2 = config.add_value_metric(); - metric2->set_id(StringToId("ValueSubsystemSleepWhileScreenOnInitialUnknown")); - metric2->set_what(pulledAtomMatcher.id()); - *metric2->mutable_value_field() = - CreateDimensions(util::SUBSYSTEM_SLEEP_STATE, {4 /* time sleeping field */}); - metric2->set_bucket(FIVE_MINUTES); - metric2->set_condition(screenOnUnknownPredicate.id()); - - // Combination condition metric with initial values false and false. - ValueMetric* metric3 = config.add_value_metric(); - metric3->set_id(StringToId("ValueSubsystemSleepWhileScreenOnFalseDeviceUnpluggedFalse")); - metric3->set_what(pulledAtomMatcher.id()); - *metric3->mutable_value_field() = - CreateDimensions(util::SUBSYSTEM_SLEEP_STATE, {4 /* time sleeping field */}); - metric3->set_bucket(FIVE_MINUTES); - metric3->set_condition(screenOnFalseOnBatteryFalsePredicate->id()); - - // Combination condition metric with initial values unknown and false. - ValueMetric* metric4 = config.add_value_metric(); - metric4->set_id(StringToId("ValueSubsystemSleepWhileScreenOnUnknownDeviceUnpluggedFalse")); - metric4->set_what(pulledAtomMatcher.id()); - *metric4->mutable_value_field() = - CreateDimensions(util::SUBSYSTEM_SLEEP_STATE, {4 /* time sleeping field */}); - metric4->set_bucket(FIVE_MINUTES); - metric4->set_condition(screenOnUnknownOnBatteryFalsePredicate->id()); - - return config; -} -} // anonymous namespace - -TEST(MetricsManagerTest, TestInitialConditions) { - sp uidMap = new UidMap(); - sp pullerManager = new StatsPullerManager(); - sp anomalyAlarmMonitor; - sp periodicAlarmMonitor; - StatsdConfig config = buildConfigWithDifferentPredicates(); - set allTagIds; - vector> allAtomMatchingTrackers; - unordered_map atomMatchingTrackerMap; - vector> allConditionTrackers; - unordered_map conditionTrackerMap; - vector> allMetricProducers; - unordered_map metricProducerMap; - std::vector> allAnomalyTrackers; - std::vector> allAlarmTrackers; - unordered_map> conditionToMetricMap; - unordered_map> trackerToMetricMap; - unordered_map> trackerToConditionMap; - unordered_map> activationAtomTrackerToMetricMap; - unordered_map> deactivationAtomTrackerToMetricMap; - unordered_map alertTrackerMap; - vector metricsWithActivation; - map stateProtoHashes; - std::set noReportMetricIds; - - EXPECT_TRUE(initStatsdConfig( - kConfigKey, config, uidMap, pullerManager, anomalyAlarmMonitor, periodicAlarmMonitor, - timeBaseSec, timeBaseSec, allTagIds, allAtomMatchingTrackers, atomMatchingTrackerMap, - allConditionTrackers, conditionTrackerMap, allMetricProducers, metricProducerMap, - allAnomalyTrackers, allAlarmTrackers, conditionToMetricMap, trackerToMetricMap, - trackerToConditionMap, activationAtomTrackerToMetricMap, - deactivationAtomTrackerToMetricMap, alertTrackerMap, metricsWithActivation, - stateProtoHashes, noReportMetricIds)); - ASSERT_EQ(4u, allMetricProducers.size()); - ASSERT_EQ(5u, allConditionTrackers.size()); - - ConditionKey queryKey; - vector conditionCache(5, ConditionState::kNotEvaluated); - - allConditionTrackers[3]->isConditionMet(queryKey, allConditionTrackers, false, conditionCache); - allConditionTrackers[4]->isConditionMet(queryKey, allConditionTrackers, false, conditionCache); - EXPECT_EQ(ConditionState::kUnknown, conditionCache[0]); - EXPECT_EQ(ConditionState::kFalse, conditionCache[1]); - EXPECT_EQ(ConditionState::kFalse, conditionCache[2]); - EXPECT_EQ(ConditionState::kFalse, conditionCache[3]); - EXPECT_EQ(ConditionState::kUnknown, conditionCache[4]); - - EXPECT_EQ(ConditionState::kFalse, allMetricProducers[0]->mCondition); - EXPECT_EQ(ConditionState::kUnknown, allMetricProducers[1]->mCondition); - EXPECT_EQ(ConditionState::kFalse, allMetricProducers[2]->mCondition); - EXPECT_EQ(ConditionState::kUnknown, allMetricProducers[3]->mCondition); -} - -TEST(MetricsManagerTest, TestGoodConfig) { - sp uidMap = new UidMap(); - sp pullerManager = new StatsPullerManager(); - sp anomalyAlarmMonitor; - sp periodicAlarmMonitor; - StatsdConfig config = buildGoodConfig(); - set allTagIds; - vector> allAtomMatchingTrackers; - unordered_map atomMatchingTrackerMap; - vector> allConditionTrackers; - unordered_map conditionTrackerMap; - vector> allMetricProducers; - unordered_map metricProducerMap; - std::vector> allAnomalyTrackers; - std::vector> allAlarmTrackers; - unordered_map> conditionToMetricMap; - unordered_map> trackerToMetricMap; - unordered_map> trackerToConditionMap; - unordered_map> activationAtomTrackerToMetricMap; - unordered_map> deactivationAtomTrackerToMetricMap; - unordered_map alertTrackerMap; - vector metricsWithActivation; - map stateProtoHashes; - std::set noReportMetricIds; - - EXPECT_TRUE(initStatsdConfig( - kConfigKey, config, uidMap, pullerManager, anomalyAlarmMonitor, periodicAlarmMonitor, - timeBaseSec, timeBaseSec, allTagIds, allAtomMatchingTrackers, atomMatchingTrackerMap, - allConditionTrackers, conditionTrackerMap, allMetricProducers, metricProducerMap, - allAnomalyTrackers, allAlarmTrackers, conditionToMetricMap, trackerToMetricMap, - trackerToConditionMap, activationAtomTrackerToMetricMap, - deactivationAtomTrackerToMetricMap, alertTrackerMap, metricsWithActivation, - stateProtoHashes, noReportMetricIds)); - ASSERT_EQ(1u, allMetricProducers.size()); - EXPECT_THAT(metricProducerMap, UnorderedElementsAre(Pair(config.count_metric(0).id(), 0))); - ASSERT_EQ(1u, allAnomalyTrackers.size()); - ASSERT_EQ(1u, noReportMetricIds.size()); - ASSERT_EQ(1u, alertTrackerMap.size()); - EXPECT_NE(alertTrackerMap.find(kAlertId), alertTrackerMap.end()); - EXPECT_EQ(alertTrackerMap.find(kAlertId)->second, 0); -} - -TEST(MetricsManagerTest, TestDimensionMetricsWithMultiTags) { - sp uidMap = new UidMap(); - sp pullerManager = new StatsPullerManager(); - sp anomalyAlarmMonitor; - sp periodicAlarmMonitor; - StatsdConfig config = buildDimensionMetricsWithMultiTags(); - set allTagIds; - vector> allAtomMatchingTrackers; - unordered_map atomMatchingTrackerMap; - vector> allConditionTrackers; - unordered_map conditionTrackerMap; - vector> allMetricProducers; - unordered_map metricProducerMap; - std::vector> allAnomalyTrackers; - std::vector> allAlarmTrackers; - unordered_map> conditionToMetricMap; - unordered_map> trackerToMetricMap; - unordered_map> trackerToConditionMap; - unordered_map> activationAtomTrackerToMetricMap; - unordered_map> deactivationAtomTrackerToMetricMap; - unordered_map alertTrackerMap; - vector metricsWithActivation; - map stateProtoHashes; - std::set noReportMetricIds; - - EXPECT_FALSE(initStatsdConfig( - kConfigKey, config, uidMap, pullerManager, anomalyAlarmMonitor, periodicAlarmMonitor, - timeBaseSec, timeBaseSec, allTagIds, allAtomMatchingTrackers, atomMatchingTrackerMap, - allConditionTrackers, conditionTrackerMap, allMetricProducers, metricProducerMap, - allAnomalyTrackers, allAlarmTrackers, conditionToMetricMap, trackerToMetricMap, - trackerToConditionMap, activationAtomTrackerToMetricMap, - deactivationAtomTrackerToMetricMap, alertTrackerMap, metricsWithActivation, - stateProtoHashes, noReportMetricIds)); -} - -TEST(MetricsManagerTest, TestCircleLogMatcherDependency) { - sp uidMap = new UidMap(); - sp pullerManager = new StatsPullerManager(); - sp anomalyAlarmMonitor; - sp periodicAlarmMonitor; - StatsdConfig config = buildCircleMatchers(); - set allTagIds; - vector> allAtomMatchingTrackers; - unordered_map atomMatchingTrackerMap; - vector> allConditionTrackers; - unordered_map conditionTrackerMap; - vector> allMetricProducers; - unordered_map metricProducerMap; - std::vector> allAnomalyTrackers; - std::vector> allAlarmTrackers; - unordered_map> conditionToMetricMap; - unordered_map> trackerToMetricMap; - unordered_map> trackerToConditionMap; - unordered_map> activationAtomTrackerToMetricMap; - unordered_map> deactivationAtomTrackerToMetricMap; - unordered_map alertTrackerMap; - vector metricsWithActivation; - map stateProtoHashes; - std::set noReportMetricIds; - - EXPECT_FALSE(initStatsdConfig( - kConfigKey, config, uidMap, pullerManager, anomalyAlarmMonitor, periodicAlarmMonitor, - timeBaseSec, timeBaseSec, allTagIds, allAtomMatchingTrackers, atomMatchingTrackerMap, - allConditionTrackers, conditionTrackerMap, allMetricProducers, metricProducerMap, - allAnomalyTrackers, allAlarmTrackers, conditionToMetricMap, trackerToMetricMap, - trackerToConditionMap, activationAtomTrackerToMetricMap, - deactivationAtomTrackerToMetricMap, alertTrackerMap, metricsWithActivation, - stateProtoHashes, noReportMetricIds)); -} - -TEST(MetricsManagerTest, TestMissingMatchers) { - sp uidMap = new UidMap(); - sp pullerManager = new StatsPullerManager(); - sp anomalyAlarmMonitor; - sp periodicAlarmMonitor; - StatsdConfig config = buildMissingMatchers(); - set allTagIds; - vector> allAtomMatchingTrackers; - unordered_map atomMatchingTrackerMap; - vector> allConditionTrackers; - unordered_map conditionTrackerMap; - vector> allMetricProducers; - unordered_map metricProducerMap; - std::vector> allAnomalyTrackers; - std::vector> allAlarmTrackers; - unordered_map> conditionToMetricMap; - unordered_map> trackerToMetricMap; - unordered_map> trackerToConditionMap; - unordered_map> activationAtomTrackerToMetricMap; - unordered_map> deactivationAtomTrackerToMetricMap; - unordered_map alertTrackerMap; - vector metricsWithActivation; - map stateProtoHashes; - std::set noReportMetricIds; - EXPECT_FALSE(initStatsdConfig( - kConfigKey, config, uidMap, pullerManager, anomalyAlarmMonitor, periodicAlarmMonitor, - timeBaseSec, timeBaseSec, allTagIds, allAtomMatchingTrackers, atomMatchingTrackerMap, - allConditionTrackers, conditionTrackerMap, allMetricProducers, metricProducerMap, - allAnomalyTrackers, allAlarmTrackers, conditionToMetricMap, trackerToMetricMap, - trackerToConditionMap, activationAtomTrackerToMetricMap, - deactivationAtomTrackerToMetricMap, alertTrackerMap, metricsWithActivation, - stateProtoHashes, noReportMetricIds)); -} - -TEST(MetricsManagerTest, TestMissingPredicate) { - sp uidMap = new UidMap(); - sp pullerManager = new StatsPullerManager(); - sp anomalyAlarmMonitor; - sp periodicAlarmMonitor; - StatsdConfig config = buildMissingPredicate(); - set allTagIds; - vector> allAtomMatchingTrackers; - unordered_map atomMatchingTrackerMap; - vector> allConditionTrackers; - unordered_map conditionTrackerMap; - vector> allMetricProducers; - unordered_map metricProducerMap; - std::vector> allAnomalyTrackers; - std::vector> allAlarmTrackers; - unordered_map> conditionToMetricMap; - unordered_map> trackerToMetricMap; - unordered_map> trackerToConditionMap; - unordered_map> activationAtomTrackerToMetricMap; - unordered_map> deactivationAtomTrackerToMetricMap; - unordered_map alertTrackerMap; - vector metricsWithActivation; - map stateProtoHashes; - std::set noReportMetricIds; - EXPECT_FALSE(initStatsdConfig( - kConfigKey, config, uidMap, pullerManager, anomalyAlarmMonitor, periodicAlarmMonitor, - timeBaseSec, timeBaseSec, allTagIds, allAtomMatchingTrackers, atomMatchingTrackerMap, - allConditionTrackers, conditionTrackerMap, allMetricProducers, metricProducerMap, - allAnomalyTrackers, allAlarmTrackers, conditionToMetricMap, trackerToMetricMap, - trackerToConditionMap, activationAtomTrackerToMetricMap, - deactivationAtomTrackerToMetricMap, alertTrackerMap, metricsWithActivation, - stateProtoHashes, noReportMetricIds)); -} - -TEST(MetricsManagerTest, TestCirclePredicateDependency) { - sp uidMap = new UidMap(); - sp pullerManager = new StatsPullerManager(); - sp anomalyAlarmMonitor; - sp periodicAlarmMonitor; - StatsdConfig config = buildCirclePredicates(); - set allTagIds; - vector> allAtomMatchingTrackers; - unordered_map atomMatchingTrackerMap; - vector> allConditionTrackers; - unordered_map conditionTrackerMap; - vector> allMetricProducers; - unordered_map metricProducerMap; - std::vector> allAnomalyTrackers; - std::vector> allAlarmTrackers; - unordered_map> conditionToMetricMap; - unordered_map> trackerToMetricMap; - unordered_map> trackerToConditionMap; - unordered_map> activationAtomTrackerToMetricMap; - unordered_map> deactivationAtomTrackerToMetricMap; - unordered_map alertTrackerMap; - vector metricsWithActivation; - map stateProtoHashes; - std::set noReportMetricIds; - - EXPECT_FALSE(initStatsdConfig( - kConfigKey, config, uidMap, pullerManager, anomalyAlarmMonitor, periodicAlarmMonitor, - timeBaseSec, timeBaseSec, allTagIds, allAtomMatchingTrackers, atomMatchingTrackerMap, - allConditionTrackers, conditionTrackerMap, allMetricProducers, metricProducerMap, - allAnomalyTrackers, allAlarmTrackers, conditionToMetricMap, trackerToMetricMap, - trackerToConditionMap, activationAtomTrackerToMetricMap, - deactivationAtomTrackerToMetricMap, alertTrackerMap, metricsWithActivation, - stateProtoHashes, noReportMetricIds)); -} - -TEST(MetricsManagerTest, testAlertWithUnknownMetric) { - sp uidMap = new UidMap(); - sp pullerManager = new StatsPullerManager(); - sp anomalyAlarmMonitor; - sp periodicAlarmMonitor; - StatsdConfig config = buildAlertWithUnknownMetric(); - set allTagIds; - vector> allAtomMatchingTrackers; - unordered_map atomMatchingTrackerMap; - vector> allConditionTrackers; - unordered_map conditionTrackerMap; - vector> allMetricProducers; - unordered_map metricProducerMap; - std::vector> allAnomalyTrackers; - std::vector> allAlarmTrackers; - unordered_map> conditionToMetricMap; - unordered_map> trackerToMetricMap; - unordered_map> trackerToConditionMap; - unordered_map> activationAtomTrackerToMetricMap; - unordered_map> deactivationAtomTrackerToMetricMap; - unordered_map alertTrackerMap; - vector metricsWithActivation; - map stateProtoHashes; - std::set noReportMetricIds; - - EXPECT_FALSE(initStatsdConfig( - kConfigKey, config, uidMap, pullerManager, anomalyAlarmMonitor, periodicAlarmMonitor, - timeBaseSec, timeBaseSec, allTagIds, allAtomMatchingTrackers, atomMatchingTrackerMap, - allConditionTrackers, conditionTrackerMap, allMetricProducers, metricProducerMap, - allAnomalyTrackers, allAlarmTrackers, conditionToMetricMap, trackerToMetricMap, - trackerToConditionMap, activationAtomTrackerToMetricMap, - deactivationAtomTrackerToMetricMap, alertTrackerMap, metricsWithActivation, - stateProtoHashes, noReportMetricIds)); -} - -TEST(MetricsManagerTest, TestCreateAtomMatchingTrackerInvalidMatcher) { - sp uidMap = new UidMap(); - AtomMatcher matcher; - // Matcher has no contents_case (simple/combination), so it is invalid. - matcher.set_id(21); - EXPECT_EQ(createAtomMatchingTracker(matcher, 0, uidMap), nullptr); -} - -TEST(MetricsManagerTest, TestCreateAtomMatchingTrackerSimple) { - int index = 1; - int64_t id = 123; - sp uidMap = new UidMap(); - AtomMatcher matcher; - matcher.set_id(id); - SimpleAtomMatcher* simpleAtomMatcher = matcher.mutable_simple_atom_matcher(); - simpleAtomMatcher->set_atom_id(util::SCREEN_STATE_CHANGED); - simpleAtomMatcher->add_field_value_matcher()->set_field( - 1 /*SCREEN_STATE_CHANGE__DISPLAY_STATE*/); - simpleAtomMatcher->mutable_field_value_matcher(0)->set_eq_int( - android::view::DisplayStateEnum::DISPLAY_STATE_ON); - - sp tracker = createAtomMatchingTracker(matcher, index, uidMap); - EXPECT_NE(tracker, nullptr); - - EXPECT_TRUE(tracker->mInitialized); - EXPECT_EQ(tracker->getId(), id); - EXPECT_EQ(tracker->mIndex, index); - const set& atomIds = tracker->getAtomIds(); - ASSERT_EQ(atomIds.size(), 1); - EXPECT_EQ(atomIds.count(util::SCREEN_STATE_CHANGED), 1); -} - -TEST(MetricsManagerTest, TestCreateAtomMatchingTrackerCombination) { - int index = 1; - int64_t id = 123; - sp uidMap = new UidMap(); - AtomMatcher matcher; - matcher.set_id(id); - AtomMatcher_Combination* combination = matcher.mutable_combination(); - combination->set_operation(LogicalOperation::OR); - combination->add_matcher(123); - combination->add_matcher(223); - - sp tracker = createAtomMatchingTracker(matcher, index, uidMap); - EXPECT_NE(tracker, nullptr); - - // Combination matchers need to be initialized first. - EXPECT_FALSE(tracker->mInitialized); - EXPECT_EQ(tracker->getId(), id); - EXPECT_EQ(tracker->mIndex, index); - const set& atomIds = tracker->getAtomIds(); - ASSERT_EQ(atomIds.size(), 0); -} - -TEST(MetricsManagerTest, TestCreateConditionTrackerInvalid) { - const ConfigKey key(123, 456); - // Predicate has no contents_case (simple/combination), so it is invalid. - Predicate predicate; - predicate.set_id(21); - unordered_map atomTrackerMap; - EXPECT_EQ(createConditionTracker(key, predicate, 0, atomTrackerMap), nullptr); -} - -TEST(MetricsManagerTest, TestCreateConditionTrackerSimple) { - int index = 1; - int64_t id = 987; - const ConfigKey key(123, 456); - - int startMatcherIndex = 2, stopMatcherIndex = 0, stopAllMatcherIndex = 1; - int64_t startMatcherId = 246, stopMatcherId = 153, stopAllMatcherId = 975; - - Predicate predicate; - predicate.set_id(id); - SimplePredicate* simplePredicate = predicate.mutable_simple_predicate(); - simplePredicate->set_start(startMatcherId); - simplePredicate->set_stop(stopMatcherId); - simplePredicate->set_stop_all(stopAllMatcherId); - - unordered_map atomTrackerMap; - atomTrackerMap[startMatcherId] = startMatcherIndex; - atomTrackerMap[stopMatcherId] = stopMatcherIndex; - atomTrackerMap[stopAllMatcherId] = stopAllMatcherIndex; - - sp tracker = createConditionTracker(key, predicate, index, atomTrackerMap); - EXPECT_EQ(tracker->getConditionId(), id); - EXPECT_EQ(tracker->isSliced(), false); - EXPECT_TRUE(tracker->IsSimpleCondition()); - const set& interestedMatchers = tracker->getAtomMatchingTrackerIndex(); - ASSERT_EQ(interestedMatchers.size(), 3); - ASSERT_EQ(interestedMatchers.count(startMatcherIndex), 1); - ASSERT_EQ(interestedMatchers.count(stopMatcherIndex), 1); - ASSERT_EQ(interestedMatchers.count(stopAllMatcherIndex), 1); -} - -TEST(MetricsManagerTest, TestCreateConditionTrackerCombination) { - int index = 1; - int64_t id = 987; - const ConfigKey key(123, 456); - - Predicate predicate; - predicate.set_id(id); - Predicate_Combination* combinationPredicate = predicate.mutable_combination(); - combinationPredicate->set_operation(LogicalOperation::AND); - combinationPredicate->add_predicate(888); - combinationPredicate->add_predicate(777); - - // Combination conditions must be initialized to set most state. - unordered_map atomTrackerMap; - sp tracker = createConditionTracker(key, predicate, index, atomTrackerMap); - EXPECT_EQ(tracker->getConditionId(), id); - EXPECT_FALSE(tracker->IsSimpleCondition()); -} - -TEST(MetricsManagerTest, TestCreateAnomalyTrackerInvalidMetric) { - Alert alert; - alert.set_id(123); - alert.set_metric_id(1); - alert.set_trigger_if_sum_gt(1); - alert.set_num_buckets(1); - - sp anomalyAlarmMonitor; - vector> metricProducers; - // Pass in empty metric producers, causing an error. - EXPECT_EQ(createAnomalyTracker(alert, anomalyAlarmMonitor, UPDATE_NEW, /*updateTime=*/123, {}, - metricProducers), - nullopt); -} - -TEST(MetricsManagerTest, TestCreateAnomalyTrackerNoThreshold) { - int64_t metricId = 1; - Alert alert; - alert.set_id(123); - alert.set_metric_id(metricId); - alert.set_num_buckets(1); - - CountMetric metric; - metric.set_id(metricId); - metric.set_bucket(ONE_MINUTE); - sp wizard = new NaggyMock(); - vector> metricProducers({new CountMetricProducer( - kConfigKey, metric, 0, {ConditionState::kUnknown}, wizard, 0x0123456789, 0, 0)}); - sp anomalyAlarmMonitor; - EXPECT_EQ(createAnomalyTracker(alert, anomalyAlarmMonitor, UPDATE_NEW, /*updateTime=*/123, - {{1, 0}}, metricProducers), - nullopt); -} - -TEST(MetricsManagerTest, TestCreateAnomalyTrackerMissingBuckets) { - int64_t metricId = 1; - Alert alert; - alert.set_id(123); - alert.set_metric_id(metricId); - alert.set_trigger_if_sum_gt(1); - - CountMetric metric; - metric.set_id(metricId); - metric.set_bucket(ONE_MINUTE); - sp wizard = new NaggyMock(); - vector> metricProducers({new CountMetricProducer( - kConfigKey, metric, 0, {ConditionState::kUnknown}, wizard, 0x0123456789, 0, 0)}); - sp anomalyAlarmMonitor; - EXPECT_EQ(createAnomalyTracker(alert, anomalyAlarmMonitor, UPDATE_NEW, /*updateTime=*/123, - {{1, 0}}, metricProducers), - nullopt); -} - -TEST(MetricsManagerTest, TestCreateAnomalyTrackerGood) { - int64_t metricId = 1; - Alert alert; - alert.set_id(123); - alert.set_metric_id(metricId); - alert.set_trigger_if_sum_gt(1); - alert.set_num_buckets(1); - - CountMetric metric; - metric.set_id(metricId); - metric.set_bucket(ONE_MINUTE); - sp wizard = new NaggyMock(); - vector> metricProducers({new CountMetricProducer( - kConfigKey, metric, 0, {ConditionState::kUnknown}, wizard, 0x0123456789, 0, 0)}); - sp anomalyAlarmMonitor; - EXPECT_NE(createAnomalyTracker(alert, anomalyAlarmMonitor, UPDATE_NEW, /*updateTime=*/123, - {{1, 0}}, metricProducers), - nullopt); -} - -TEST(MetricsManagerTest, TestCreateAnomalyTrackerDurationTooLong) { - int64_t metricId = 1; - Alert alert; - alert.set_id(123); - alert.set_metric_id(metricId); - // Impossible for alert to fire since the time is bigger than bucketSize * numBuckets - alert.set_trigger_if_sum_gt(MillisToNano(TimeUnitToBucketSizeInMillis(ONE_MINUTE)) + 1); - alert.set_num_buckets(1); - - DurationMetric metric; - metric.set_id(metricId); - metric.set_bucket(ONE_MINUTE); - metric.set_aggregation_type(DurationMetric_AggregationType_SUM); - FieldMatcher dimensions; - sp wizard = new NaggyMock(); - vector> metricProducers({new DurationMetricProducer( - kConfigKey, metric, -1 /*no condition*/, {}, -1 /* what index not needed*/, - 1 /* start index */, 2 /* stop index */, 3 /* stop_all index */, false /*nesting*/, - wizard, 0x0123456789, dimensions, 0, 0)}); - sp anomalyAlarmMonitor; - EXPECT_EQ(createAnomalyTracker(alert, anomalyAlarmMonitor, UPDATE_NEW, /*updateTime=*/123, - {{1, 0}}, metricProducers), - nullopt); -} - -TEST(MetricsManagerTest, TestCreateDurationProducerDimensionsInWhatInvalid) { - StatsdConfig config; - config.add_allowed_log_source("AID_ROOT"); - *config.add_atom_matcher() = CreateAcquireWakelockAtomMatcher(); - *config.add_atom_matcher() = CreateReleaseWakelockAtomMatcher(); - *config.add_atom_matcher() = CreateMoveToBackgroundAtomMatcher(); - *config.add_atom_matcher() = CreateMoveToForegroundAtomMatcher(); - - Predicate holdingWakelockPredicate = CreateHoldingWakelockPredicate(); - // The predicate is dimensioning by first attribution node by uid. - FieldMatcher dimensions = - CreateAttributionUidDimensions(util::WAKELOCK_STATE_CHANGED, {Position::FIRST}); - *holdingWakelockPredicate.mutable_simple_predicate()->mutable_dimensions() = dimensions; - *config.add_predicate() = holdingWakelockPredicate; - - DurationMetric* durationMetric = config.add_duration_metric(); - durationMetric->set_id(StringToId("WakelockDuration")); - durationMetric->set_what(holdingWakelockPredicate.id()); - durationMetric->set_aggregation_type(DurationMetric::SUM); - // The metric is dimensioning by first attribution node by uid AND tag. - // Invalid since the predicate only dimensions by uid. - *durationMetric->mutable_dimensions_in_what() = CreateAttributionUidAndOtherDimensions( - util::WAKELOCK_STATE_CHANGED, {Position::FIRST}, {3 /* tag */}); - durationMetric->set_bucket(FIVE_MINUTES); - - ConfigKey key(123, 987); - uint64_t timeNs = 456; - sp pullerManager = new StatsPullerManager(); - sp anomalyAlarmMonitor; - sp periodicAlarmMonitor; - sp uidMap; - sp metricsManager = - new MetricsManager(key, config, timeNs, timeNs, uidMap, pullerManager, - anomalyAlarmMonitor, periodicAlarmMonitor); - EXPECT_FALSE(metricsManager->isConfigValid()); -} - -} // namespace statsd -} // namespace os -} // namespace android - -#else -GTEST_LOG_(INFO) << "This test does nothing.\n"; -#endif diff --git a/bin/tests/shell/ShellSubscriber_test.cpp b/bin/tests/shell/ShellSubscriber_test.cpp deleted file mode 100644 index 7b131fce..00000000 --- a/bin/tests/shell/ShellSubscriber_test.cpp +++ /dev/null @@ -1,207 +0,0 @@ -// Copyright (C) 2018 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 "src/shell/ShellSubscriber.h" - -#include -#include -#include - -#include - -#include "packages/modules/StatsD/bin/src/shell/shell_config.pb.h" -#include "packages/modules/StatsD/bin/src/shell/shell_data.pb.h" -#include "frameworks/proto_logging/stats/atoms.pb.h" -#include "stats_event.h" -#include "tests/metrics/metrics_test_helper.h" -#include "tests/statsd_test_util.h" - -using namespace android::os::statsd; -using android::sp; -using std::vector; -using testing::_; -using testing::Invoke; -using testing::NaggyMock; -using testing::StrictMock; - -#ifdef __ANDROID__ - -void runShellTest(ShellSubscription config, sp uidMap, - sp pullerManager, - const vector>& pushedEvents, - const ShellData& expectedData) { - // set up 2 pipes for read/write config and data - int fds_config[2]; - ASSERT_EQ(0, pipe(fds_config)); - - int fds_data[2]; - ASSERT_EQ(0, pipe(fds_data)); - - size_t bufferSize = config.ByteSize(); - // write the config to pipe, first write size of the config - write(fds_config[1], &bufferSize, sizeof(bufferSize)); - // then write config itself - vector buffer(bufferSize); - config.SerializeToArray(&buffer[0], bufferSize); - write(fds_config[1], buffer.data(), bufferSize); - close(fds_config[1]); - - sp shellClient = new ShellSubscriber(uidMap, pullerManager); - - // mimic a binder thread that a shell subscriber runs on. it would block. - std::thread reader([&shellClient, &fds_config, &fds_data] { - shellClient->startNewSubscription(fds_config[0], fds_data[1], /*timeoutSec=*/-1); - }); - reader.detach(); - - // let the shell subscriber to receive the config from pipe. - std::this_thread::sleep_for(100ms); - - if (pushedEvents.size() > 0) { - // send a log event that matches the config. - std::thread log_reader([&shellClient, &pushedEvents] { - for (const auto& event : pushedEvents) { - shellClient->onLogEvent(*event); - } - }); - - log_reader.detach(); - - if (log_reader.joinable()) { - log_reader.join(); - } - } - - // wait for the data to be written. - std::this_thread::sleep_for(100ms); - - // Because we might receive heartbeats from statsd, consisting of data sizes - // of 0, encapsulate reads within a while loop. - bool readAtom = false; - while (!readAtom) { - // Read the atom size. - size_t dataSize = 0; - read(fds_data[0], &dataSize, sizeof(dataSize)); - if (dataSize == 0) continue; - EXPECT_EQ(expectedData.ByteSize(), int(dataSize)); - - // Read that much data in proto binary format. - vector dataBuffer(dataSize); - EXPECT_EQ((int)dataSize, read(fds_data[0], dataBuffer.data(), dataSize)); - - // Make sure the received bytes can be parsed to an atom. - ShellData receivedAtom; - EXPECT_TRUE(receivedAtom.ParseFromArray(dataBuffer.data(), dataSize) != 0); - - // Serialize the expected atom to byte array and compare to make sure - // they are the same. - vector expectedAtomBuffer(expectedData.ByteSize()); - expectedData.SerializeToArray(expectedAtomBuffer.data(), expectedData.ByteSize()); - EXPECT_EQ(expectedAtomBuffer, dataBuffer); - - readAtom = true; - } - - close(fds_data[0]); - if (reader.joinable()) { - reader.join(); - } -} - -TEST(ShellSubscriberTest, testPushedSubscription) { - sp uidMap = new NaggyMock(); - - sp pullerManager = new StrictMock(); - vector> pushedList; - - // Create the LogEvent from an AStatsEvent - std::unique_ptr logEvent = CreateScreenStateChangedEvent( - 1000 /*timestamp*/, ::android::view::DisplayStateEnum::DISPLAY_STATE_ON); - pushedList.push_back(std::move(logEvent)); - - // create a simple config to get screen events - ShellSubscription config; - config.add_pushed()->set_atom_id(29); - - // this is the expected screen event atom. - ShellData shellData; - shellData.add_atom()->mutable_screen_state_changed()->set_state( - ::android::view::DisplayStateEnum::DISPLAY_STATE_ON); - - runShellTest(config, uidMap, pullerManager, pushedList, shellData); -} - -namespace { - -int kUid1 = 1000; -int kUid2 = 2000; - -int kCpuTime1 = 100; -int kCpuTime2 = 200; - -ShellData getExpectedShellData() { - ShellData shellData; - auto* atom1 = shellData.add_atom()->mutable_cpu_active_time(); - atom1->set_uid(kUid1); - atom1->set_time_millis(kCpuTime1); - - auto* atom2 = shellData.add_atom()->mutable_cpu_active_time(); - atom2->set_uid(kUid2); - atom2->set_time_millis(kCpuTime2); - - return shellData; -} - -ShellSubscription getPulledConfig() { - ShellSubscription config; - auto* pull_config = config.add_pulled(); - pull_config->mutable_matcher()->set_atom_id(10016); - pull_config->set_freq_millis(2000); - return config; -} - -shared_ptr makeCpuActiveTimeAtom(int32_t uid, int64_t timeMillis) { - AStatsEvent* statsEvent = AStatsEvent_obtain(); - AStatsEvent_setAtomId(statsEvent, 10016); - AStatsEvent_overwriteTimestamp(statsEvent, 1111L); - AStatsEvent_writeInt32(statsEvent, uid); - AStatsEvent_writeInt64(statsEvent, timeMillis); - - std::shared_ptr logEvent = std::make_shared(/*uid=*/0, /*pid=*/0); - parseStatsEventToLogEvent(statsEvent, logEvent.get()); - return logEvent; -} - -} // namespace - -TEST(ShellSubscriberTest, testPulledSubscription) { - sp uidMap = new NaggyMock(); - - sp pullerManager = new StrictMock(); - const vector uids = {AID_SYSTEM}; - EXPECT_CALL(*pullerManager, Pull(10016, uids, _, _)) - .WillRepeatedly(Invoke([](int tagId, const vector&, const int64_t, - vector>* data) { - data->clear(); - data->push_back(makeCpuActiveTimeAtom(/*uid=*/kUid1, /*timeMillis=*/kCpuTime1)); - data->push_back(makeCpuActiveTimeAtom(/*uid=*/kUid2, /*timeMillis=*/kCpuTime2)); - return true; - })); - runShellTest(getPulledConfig(), uidMap, pullerManager, vector>(), - getExpectedShellData()); -} - -#else -GTEST_LOG_(INFO) << "This test does nothing.\n"; -#endif diff --git a/bin/tests/state/StateTracker_test.cpp b/bin/tests/state/StateTracker_test.cpp deleted file mode 100644 index 6516c152..00000000 --- a/bin/tests/state/StateTracker_test.cpp +++ /dev/null @@ -1,571 +0,0 @@ -/* - * Copyright (C) 2019, 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 "state/StateTracker.h" - -#include -#include - -#include "state/StateListener.h" -#include "state/StateManager.h" -#include "state/StateTracker.h" -#include "stats_event.h" -#include "tests/statsd_test_util.h" - -#ifdef __ANDROID__ - -namespace android { -namespace os { -namespace statsd { - -const int32_t timestampNs = 1000; - -/** - * Mock StateListener class for testing. - * Stores primary key and state pairs. - */ -class TestStateListener : public virtual StateListener { -public: - TestStateListener(){}; - - virtual ~TestStateListener(){}; - - struct Update { - Update(const HashableDimensionKey& key, int state) : mKey(key), mState(state){}; - HashableDimensionKey mKey; - int mState; - }; - - std::vector updates; - - void onStateChanged(const int64_t eventTimeNs, const int32_t atomId, - const HashableDimensionKey& primaryKey, const FieldValue& oldState, - const FieldValue& newState) { - updates.emplace_back(primaryKey, newState.mValue.int_value); - } -}; - -int getStateInt(StateManager& mgr, int atomId, const HashableDimensionKey& queryKey) { - FieldValue output; - mgr.getStateValue(atomId, queryKey, &output); - return output.mValue.int_value; -} - -// START: build event functions. -// Incorrect event - missing fields -std::unique_ptr buildIncorrectOverlayEvent(int uid, const std::string& packageName, - int state) { - AStatsEvent* statsEvent = AStatsEvent_obtain(); - AStatsEvent_setAtomId(statsEvent, util::OVERLAY_STATE_CHANGED); - AStatsEvent_overwriteTimestamp(statsEvent, 1000); - - AStatsEvent_writeInt32(statsEvent, uid); - AStatsEvent_writeString(statsEvent, packageName.c_str()); - // Missing field 3 - using_alert_window. - AStatsEvent_writeInt32(statsEvent, state); - - std::unique_ptr logEvent = std::make_unique(/*uid=*/0, /*pid=*/0); - parseStatsEventToLogEvent(statsEvent, logEvent.get()); - return logEvent; -} - -// Incorrect event - exclusive state has wrong type -std::unique_ptr buildOverlayEventBadStateType(int uid, const std::string& packageName) { - AStatsEvent* statsEvent = AStatsEvent_obtain(); - AStatsEvent_setAtomId(statsEvent, util::OVERLAY_STATE_CHANGED); - AStatsEvent_overwriteTimestamp(statsEvent, 1000); - - AStatsEvent_writeInt32(statsEvent, uid); - AStatsEvent_writeString(statsEvent, packageName.c_str()); - AStatsEvent_writeInt32(statsEvent, true); // using_alert_window - AStatsEvent_writeString(statsEvent, "string"); // exclusive state: string instead of int - - std::unique_ptr logEvent = std::make_unique(/*uid=*/0, /*pid=*/0); - parseStatsEventToLogEvent(statsEvent, logEvent.get()); - return logEvent; -} -// END: build event functions. - -TEST(StateListenerTest, TestStateListenerWeakPointer) { - sp listener = new TestStateListener(); - wp wListener = listener; - listener = nullptr; // let go of listener - EXPECT_TRUE(wListener.promote() == nullptr); -} - -TEST(StateManagerTest, TestStateManagerGetInstance) { - sp listener1 = new TestStateListener(); - StateManager& mgr = StateManager::getInstance(); - mgr.clear(); - - mgr.registerListener(util::SCREEN_STATE_CHANGED, listener1); - EXPECT_EQ(1, mgr.getStateTrackersCount()); - EXPECT_EQ(1, StateManager::getInstance().getStateTrackersCount()); -} - -TEST(StateManagerTest, TestOnLogEvent) { - sp uidMap = makeMockUidMapForPackage("com.android.systemui", {10111}); - sp listener1 = new TestStateListener(); - StateManager mgr; - mgr.updateLogSources(uidMap); - // Add StateTracker by registering a listener. - mgr.registerListener(util::SCREEN_STATE_CHANGED, listener1); - - // log event using AID_ROOT - std::unique_ptr event = CreateScreenStateChangedEvent( - timestampNs, android::view::DisplayStateEnum::DISPLAY_STATE_ON); - mgr.onLogEvent(*event); - - // check StateTracker was updated by querying for state - HashableDimensionKey queryKey = DEFAULT_DIMENSION_KEY; - EXPECT_EQ(android::view::DisplayStateEnum::DISPLAY_STATE_ON, - getStateInt(mgr, util::SCREEN_STATE_CHANGED, queryKey)); - - // log event using mocked uid - event = CreateScreenStateChangedEvent( - timestampNs, android::view::DisplayStateEnum::DISPLAY_STATE_OFF, 10111); - mgr.onLogEvent(*event); - - // check StateTracker was updated by querying for state - queryKey = DEFAULT_DIMENSION_KEY; - EXPECT_EQ(android::view::DisplayStateEnum::DISPLAY_STATE_OFF, - getStateInt(mgr, util::SCREEN_STATE_CHANGED, queryKey)); - - // log event using non-whitelisted uid - event = CreateScreenStateChangedEvent(timestampNs, - android::view::DisplayStateEnum::DISPLAY_STATE_ON, 10112); - mgr.onLogEvent(*event); - - // check StateTracker was NOT updated by querying for state - queryKey = DEFAULT_DIMENSION_KEY; - EXPECT_EQ(android::view::DisplayStateEnum::DISPLAY_STATE_OFF, - getStateInt(mgr, util::SCREEN_STATE_CHANGED, queryKey)); - - // log event using AID_SYSTEM - event = CreateScreenStateChangedEvent( - timestampNs, android::view::DisplayStateEnum::DISPLAY_STATE_ON, AID_SYSTEM); - mgr.onLogEvent(*event); - - // check StateTracker was updated by querying for state - queryKey = DEFAULT_DIMENSION_KEY; - EXPECT_EQ(android::view::DisplayStateEnum::DISPLAY_STATE_ON, - getStateInt(mgr, util::SCREEN_STATE_CHANGED, queryKey)); -} - -/** - * Test registering listeners to StateTrackers - * - * - StateManager will create a new StateTracker if it doesn't already exist - * and then register the listener to the StateTracker. - * - If a listener is already registered to a StateTracker, it is not added again. - * - StateTrackers are only created for atoms that are state atoms. - */ -TEST(StateTrackerTest, TestRegisterListener) { - sp listener1 = new TestStateListener(); - sp listener2 = new TestStateListener(); - StateManager mgr; - - // Register listener to non-existing StateTracker - EXPECT_EQ(0, mgr.getStateTrackersCount()); - mgr.registerListener(util::SCREEN_STATE_CHANGED, listener1); - EXPECT_EQ(1, mgr.getStateTrackersCount()); - EXPECT_EQ(1, mgr.getListenersCount(util::SCREEN_STATE_CHANGED)); - - // Register listener to existing StateTracker - mgr.registerListener(util::SCREEN_STATE_CHANGED, listener2); - EXPECT_EQ(1, mgr.getStateTrackersCount()); - EXPECT_EQ(2, mgr.getListenersCount(util::SCREEN_STATE_CHANGED)); - - // Register already registered listener to existing StateTracker - mgr.registerListener(util::SCREEN_STATE_CHANGED, listener2); - EXPECT_EQ(1, mgr.getStateTrackersCount()); - EXPECT_EQ(2, mgr.getListenersCount(util::SCREEN_STATE_CHANGED)); - - // Register listener to non-state atom - mgr.registerListener(util::BATTERY_LEVEL_CHANGED, listener2); - EXPECT_EQ(2, mgr.getStateTrackersCount()); -} - -/** - * Test unregistering listeners from StateTrackers - * - * - StateManager will unregister listeners from a StateTracker only if the - * StateTracker exists and the listener is registered to the StateTracker. - * - Once all listeners are removed from a StateTracker, the StateTracker - * is also removed. - */ -TEST(StateTrackerTest, TestUnregisterListener) { - sp listener1 = new TestStateListener(); - sp listener2 = new TestStateListener(); - StateManager mgr; - - // Unregister listener from non-existing StateTracker - EXPECT_EQ(0, mgr.getStateTrackersCount()); - mgr.unregisterListener(util::SCREEN_STATE_CHANGED, listener1); - EXPECT_EQ(0, mgr.getStateTrackersCount()); - EXPECT_EQ(-1, mgr.getListenersCount(util::SCREEN_STATE_CHANGED)); - - // Unregister non-registered listener from existing StateTracker - mgr.registerListener(util::SCREEN_STATE_CHANGED, listener1); - EXPECT_EQ(1, mgr.getStateTrackersCount()); - EXPECT_EQ(1, mgr.getListenersCount(util::SCREEN_STATE_CHANGED)); - mgr.unregisterListener(util::SCREEN_STATE_CHANGED, listener2); - EXPECT_EQ(1, mgr.getStateTrackersCount()); - EXPECT_EQ(1, mgr.getListenersCount(util::SCREEN_STATE_CHANGED)); - - // Unregister second-to-last listener from StateTracker - mgr.registerListener(util::SCREEN_STATE_CHANGED, listener2); - mgr.unregisterListener(util::SCREEN_STATE_CHANGED, listener1); - EXPECT_EQ(1, mgr.getStateTrackersCount()); - EXPECT_EQ(1, mgr.getListenersCount(util::SCREEN_STATE_CHANGED)); - - // Unregister last listener from StateTracker - mgr.unregisterListener(util::SCREEN_STATE_CHANGED, listener2); - EXPECT_EQ(0, mgr.getStateTrackersCount()); - EXPECT_EQ(-1, mgr.getListenersCount(util::SCREEN_STATE_CHANGED)); -} - -/** - * Test a binary state atom with nested counting. - * - * To go from an "ON" state to an "OFF" state with nested counting, we must see - * an equal number of "OFF" events as "ON" events. - * For example, ACQUIRE, ACQUIRE, RELEASE will still be in the ACQUIRE state. - * ACQUIRE, ACQUIRE, RELEASE, RELEASE will be in the RELEASE state. - */ -TEST(StateTrackerTest, TestStateChangeNested) { - sp listener = new TestStateListener(); - StateManager mgr; - mgr.registerListener(util::WAKELOCK_STATE_CHANGED, listener); - - std::vector attributionUids1 = {1000}; - std::vector attributionTags1 = {"tag"}; - - std::unique_ptr event1 = CreateAcquireWakelockEvent(timestampNs, attributionUids1, - attributionTags1, "wakelockName"); - mgr.onLogEvent(*event1); - ASSERT_EQ(1, listener->updates.size()); - EXPECT_EQ(1000, listener->updates[0].mKey.getValues()[0].mValue.int_value); - EXPECT_EQ(1, listener->updates[0].mState); - listener->updates.clear(); - - std::unique_ptr event2 = CreateAcquireWakelockEvent( - timestampNs + 1000, attributionUids1, attributionTags1, "wakelockName"); - mgr.onLogEvent(*event2); - ASSERT_EQ(0, listener->updates.size()); - - std::unique_ptr event3 = CreateReleaseWakelockEvent( - timestampNs + 2000, attributionUids1, attributionTags1, "wakelockName"); - mgr.onLogEvent(*event3); - ASSERT_EQ(0, listener->updates.size()); - - std::unique_ptr event4 = CreateReleaseWakelockEvent( - timestampNs + 3000, attributionUids1, attributionTags1, "wakelockName"); - mgr.onLogEvent(*event4); - ASSERT_EQ(1, listener->updates.size()); - EXPECT_EQ(1000, listener->updates[0].mKey.getValues()[0].mValue.int_value); - EXPECT_EQ(0, listener->updates[0].mState); -} - -/** - * Test a state atom with a reset state. - * - * If the reset state value is seen, every state in the map is set to the default - * state and every listener is notified. - */ -TEST(StateTrackerTest, TestStateChangeReset) { - sp listener = new TestStateListener(); - StateManager mgr; - mgr.registerListener(util::BLE_SCAN_STATE_CHANGED, listener); - - std::vector attributionUids1 = {1000}; - std::vector attributionTags1 = {"tag1"}; - std::vector attributionUids2 = {2000}; - - std::unique_ptr event1 = - CreateBleScanStateChangedEvent(timestampNs, attributionUids1, attributionTags1, - BleScanStateChanged::ON, false, false, false); - mgr.onLogEvent(*event1); - ASSERT_EQ(1, listener->updates.size()); - EXPECT_EQ(1000, listener->updates[0].mKey.getValues()[0].mValue.int_value); - EXPECT_EQ(BleScanStateChanged::ON, listener->updates[0].mState); - FieldValue stateFieldValue; - mgr.getStateValue(util::BLE_SCAN_STATE_CHANGED, listener->updates[0].mKey, &stateFieldValue); - EXPECT_EQ(BleScanStateChanged::ON, stateFieldValue.mValue.int_value); - listener->updates.clear(); - - std::unique_ptr event2 = - CreateBleScanStateChangedEvent(timestampNs + 1000, attributionUids2, attributionTags1, - BleScanStateChanged::ON, false, false, false); - mgr.onLogEvent(*event2); - ASSERT_EQ(1, listener->updates.size()); - EXPECT_EQ(2000, listener->updates[0].mKey.getValues()[0].mValue.int_value); - EXPECT_EQ(BleScanStateChanged::ON, listener->updates[0].mState); - mgr.getStateValue(util::BLE_SCAN_STATE_CHANGED, listener->updates[0].mKey, &stateFieldValue); - EXPECT_EQ(BleScanStateChanged::ON, stateFieldValue.mValue.int_value); - listener->updates.clear(); - - std::unique_ptr event3 = - CreateBleScanStateChangedEvent(timestampNs + 2000, attributionUids2, attributionTags1, - BleScanStateChanged::RESET, false, false, false); - mgr.onLogEvent(*event3); - ASSERT_EQ(2, listener->updates.size()); - for (const TestStateListener::Update& update : listener->updates) { - EXPECT_EQ(BleScanStateChanged::OFF, update.mState); - - mgr.getStateValue(util::BLE_SCAN_STATE_CHANGED, update.mKey, &stateFieldValue); - EXPECT_EQ(BleScanStateChanged::OFF, stateFieldValue.mValue.int_value); - } -} - -/** - * Test StateManager's onLogEvent and StateListener's onStateChanged correctly - * updates listener for states without primary keys. - */ -TEST(StateTrackerTest, TestStateChangeNoPrimaryFields) { - sp listener1 = new TestStateListener(); - StateManager mgr; - mgr.registerListener(util::SCREEN_STATE_CHANGED, listener1); - - // log event - std::unique_ptr event = CreateScreenStateChangedEvent( - timestampNs, android::view::DisplayStateEnum::DISPLAY_STATE_ON); - mgr.onLogEvent(*event); - - // check listener was updated - ASSERT_EQ(1, listener1->updates.size()); - EXPECT_EQ(DEFAULT_DIMENSION_KEY, listener1->updates[0].mKey); - EXPECT_EQ(2, listener1->updates[0].mState); - - // check StateTracker was updated by querying for state - HashableDimensionKey queryKey = DEFAULT_DIMENSION_KEY; - EXPECT_EQ(android::view::DisplayStateEnum::DISPLAY_STATE_ON, - getStateInt(mgr, util::SCREEN_STATE_CHANGED, queryKey)); -} - -/** - * Test StateManager's onLogEvent and StateListener's onStateChanged correctly - * updates listener for states with one primary key. - */ -TEST(StateTrackerTest, TestStateChangeOnePrimaryField) { - sp listener1 = new TestStateListener(); - StateManager mgr; - mgr.registerListener(util::UID_PROCESS_STATE_CHANGED, listener1); - - // log event - std::unique_ptr event = CreateUidProcessStateChangedEvent( - timestampNs, 1000 /*uid*/, android::app::ProcessStateEnum::PROCESS_STATE_TOP); - mgr.onLogEvent(*event); - - // check listener was updated - ASSERT_EQ(1, listener1->updates.size()); - EXPECT_EQ(1000, listener1->updates[0].mKey.getValues()[0].mValue.int_value); - EXPECT_EQ(1002, listener1->updates[0].mState); - - // check StateTracker was updated by querying for state - HashableDimensionKey queryKey; - getUidProcessKey(1000 /* uid */, &queryKey); - EXPECT_EQ(android::app::ProcessStateEnum::PROCESS_STATE_TOP, - getStateInt(mgr, util::UID_PROCESS_STATE_CHANGED, queryKey)); -} - -TEST(StateTrackerTest, TestStateChangePrimaryFieldAttrChain) { - sp listener1 = new TestStateListener(); - StateManager mgr; - mgr.registerListener(util::WAKELOCK_STATE_CHANGED, listener1); - - // Log event. - std::vector attributionUids = {1001}; - std::vector attributionTags = {"tag1"}; - - std::unique_ptr event = CreateAcquireWakelockEvent(timestampNs, attributionUids, - attributionTags, "wakelockName"); - mgr.onLogEvent(*event); - EXPECT_EQ(1, mgr.getStateTrackersCount()); - EXPECT_EQ(1, mgr.getListenersCount(util::WAKELOCK_STATE_CHANGED)); - - // Check listener was updated. - ASSERT_EQ(1, listener1->updates.size()); - ASSERT_EQ(3, listener1->updates[0].mKey.getValues().size()); - EXPECT_EQ(1001, listener1->updates[0].mKey.getValues()[0].mValue.int_value); - EXPECT_EQ(1, listener1->updates[0].mKey.getValues()[1].mValue.int_value); - EXPECT_EQ("wakelockName", listener1->updates[0].mKey.getValues()[2].mValue.str_value); - EXPECT_EQ(WakelockStateChanged::ACQUIRE, listener1->updates[0].mState); - - // Check StateTracker was updated by querying for state. - HashableDimensionKey queryKey; - getPartialWakelockKey(1001 /* uid */, "wakelockName", &queryKey); - EXPECT_EQ(WakelockStateChanged::ACQUIRE, - getStateInt(mgr, util::WAKELOCK_STATE_CHANGED, queryKey)); - - // No state stored for this query key. - HashableDimensionKey queryKey2; - getPartialWakelockKey(1002 /* uid */, "tag1", &queryKey2); - EXPECT_EQ(-1 /*StateTracker::kStateUnknown*/, - getStateInt(mgr, util::WAKELOCK_STATE_CHANGED, queryKey2)); - - // Partial query fails. - HashableDimensionKey queryKey3; - getPartialWakelockKey(1001 /* uid */, &queryKey3); - EXPECT_EQ(-1 /*StateTracker::kStateUnknown*/, - getStateInt(mgr, util::WAKELOCK_STATE_CHANGED, queryKey3)); -} - -/** - * Test StateManager's onLogEvent and StateListener's onStateChanged correctly - * updates listener for states with multiple primary keys. - */ -TEST(StateTrackerTest, TestStateChangeMultiplePrimaryFields) { - sp listener1 = new TestStateListener(); - StateManager mgr; - mgr.registerListener(util::OVERLAY_STATE_CHANGED, listener1); - - // log event - std::unique_ptr event = CreateOverlayStateChangedEvent( - timestampNs, 1000 /* uid */, "package1", true /*using_alert_window*/, - OverlayStateChanged::ENTERED); - mgr.onLogEvent(*event); - - // check listener was updated - ASSERT_EQ(1, listener1->updates.size()); - EXPECT_EQ(1000, listener1->updates[0].mKey.getValues()[0].mValue.int_value); - EXPECT_EQ(1, listener1->updates[0].mState); - - // check StateTracker was updated by querying for state - HashableDimensionKey queryKey; - getOverlayKey(1000 /* uid */, "package1", &queryKey); - EXPECT_EQ(OverlayStateChanged::ENTERED, - getStateInt(mgr, util::OVERLAY_STATE_CHANGED, queryKey)); -} - -/** - * Test StateManager's onLogEvent and StateListener's onStateChanged - * when there is an error extracting state from log event. Listener is not - * updated of state change. - */ -TEST(StateTrackerTest, TestStateChangeEventError) { - sp listener1 = new TestStateListener(); - StateManager mgr; - mgr.registerListener(util::OVERLAY_STATE_CHANGED, listener1); - - // log event - std::shared_ptr event1 = - buildIncorrectOverlayEvent(1000 /* uid */, "package1", 1 /* state */); - std::shared_ptr event2 = buildOverlayEventBadStateType(1001 /* uid */, "package2"); - - // check listener was updated - mgr.onLogEvent(*event1); - ASSERT_EQ(0, listener1->updates.size()); - mgr.onLogEvent(*event2); - ASSERT_EQ(0, listener1->updates.size()); -} - -TEST(StateTrackerTest, TestStateQuery) { - sp listener1 = new TestStateListener(); - sp listener2 = new TestStateListener(); - sp listener3 = new TestStateListener(); - sp listener4 = new TestStateListener(); - StateManager mgr; - mgr.registerListener(util::SCREEN_STATE_CHANGED, listener1); - mgr.registerListener(util::UID_PROCESS_STATE_CHANGED, listener2); - mgr.registerListener(util::OVERLAY_STATE_CHANGED, listener3); - mgr.registerListener(util::WAKELOCK_STATE_CHANGED, listener4); - - std::unique_ptr event1 = CreateUidProcessStateChangedEvent( - timestampNs, 1000 /*uid*/, - android::app::ProcessStateEnum::PROCESS_STATE_TOP); // state value: 1002 - std::unique_ptr event2 = CreateUidProcessStateChangedEvent( - timestampNs + 1000, 1001 /*uid*/, - android::app::ProcessStateEnum::PROCESS_STATE_FOREGROUND_SERVICE); // state value: - // 1003 - std::unique_ptr event3 = CreateUidProcessStateChangedEvent( - timestampNs + 2000, 1002 /*uid*/, - android::app::ProcessStateEnum::PROCESS_STATE_PERSISTENT); // state value: 1000 - std::unique_ptr event4 = CreateUidProcessStateChangedEvent( - timestampNs + 3000, 1001 /*uid*/, - android::app::ProcessStateEnum::PROCESS_STATE_TOP); // state value: 1002 - std::unique_ptr event5 = CreateScreenStateChangedEvent( - timestampNs + 4000, android::view::DisplayStateEnum::DISPLAY_STATE_ON); - std::unique_ptr event6 = CreateOverlayStateChangedEvent( - timestampNs + 5000, 1000 /*uid*/, "package1", true /*using_alert_window*/, - OverlayStateChanged::ENTERED); - std::unique_ptr event7 = CreateOverlayStateChangedEvent( - timestampNs + 6000, 1000 /*uid*/, "package2", true /*using_alert_window*/, - OverlayStateChanged::EXITED); - - std::vector attributionUids = {1005}; - std::vector attributionTags = {"tag"}; - - std::unique_ptr event8 = CreateAcquireWakelockEvent( - timestampNs + 7000, attributionUids, attributionTags, "wakelock1"); - std::unique_ptr event9 = CreateReleaseWakelockEvent( - timestampNs + 8000, attributionUids, attributionTags, "wakelock2"); - - mgr.onLogEvent(*event1); - mgr.onLogEvent(*event2); - mgr.onLogEvent(*event3); - mgr.onLogEvent(*event5); - mgr.onLogEvent(*event5); - mgr.onLogEvent(*event6); - mgr.onLogEvent(*event7); - mgr.onLogEvent(*event8); - mgr.onLogEvent(*event9); - - // Query for UidProcessState of uid 1001 - HashableDimensionKey queryKey1; - getUidProcessKey(1001, &queryKey1); - EXPECT_EQ(android::app::ProcessStateEnum::PROCESS_STATE_FOREGROUND_SERVICE, - getStateInt(mgr, util::UID_PROCESS_STATE_CHANGED, queryKey1)); - - // Query for UidProcessState of uid 1004 - not in state map - HashableDimensionKey queryKey2; - getUidProcessKey(1004, &queryKey2); - EXPECT_EQ(-1, getStateInt(mgr, util::UID_PROCESS_STATE_CHANGED, - queryKey2)); // default state - - // Query for UidProcessState of uid 1001 - after change in state - mgr.onLogEvent(*event4); - EXPECT_EQ(android::app::ProcessStateEnum::PROCESS_STATE_TOP, - getStateInt(mgr, util::UID_PROCESS_STATE_CHANGED, queryKey1)); - - // Query for ScreenState - EXPECT_EQ(android::view::DisplayStateEnum::DISPLAY_STATE_ON, - getStateInt(mgr, util::SCREEN_STATE_CHANGED, DEFAULT_DIMENSION_KEY)); - - // Query for OverlayState of uid 1000, package name "package2" - HashableDimensionKey queryKey3; - getOverlayKey(1000, "package2", &queryKey3); - EXPECT_EQ(OverlayStateChanged::EXITED, - getStateInt(mgr, util::OVERLAY_STATE_CHANGED, queryKey3)); - - // Query for WakelockState of uid 1005, tag 2 - HashableDimensionKey queryKey4; - getPartialWakelockKey(1005, "wakelock2", &queryKey4); - EXPECT_EQ(WakelockStateChanged::RELEASE, - getStateInt(mgr, util::WAKELOCK_STATE_CHANGED, queryKey4)); - - // Query for WakelockState of uid 1005, tag 1 - HashableDimensionKey queryKey5; - getPartialWakelockKey(1005, "wakelock1", &queryKey5); - EXPECT_EQ(WakelockStateChanged::ACQUIRE, - getStateInt(mgr, util::WAKELOCK_STATE_CHANGED, queryKey5)); -} - -} // namespace statsd -} // namespace os -} // namespace android -#else -GTEST_LOG_(INFO) << "This test does nothing.\n"; -#endif diff --git a/bin/tests/statsd_test_util.cpp b/bin/tests/statsd_test_util.cpp deleted file mode 100644 index 65062724..00000000 --- a/bin/tests/statsd_test_util.cpp +++ /dev/null @@ -1,1624 +0,0 @@ -// 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 "statsd_test_util.h" - -#include - -#include "matchers/SimpleAtomMatchingTracker.h" -#include "stats_event.h" - -using aidl::android::util::StatsEventParcel; -using std::shared_ptr; - -namespace android { -namespace os { -namespace statsd { - -StatsLogReport outputStreamToProto(ProtoOutputStream* proto) { - vector bytes; - bytes.resize(proto->size()); - size_t pos = 0; - sp reader = proto->data(); - - while (reader->readBuffer() != NULL) { - size_t toRead = reader->currentToRead(); - std::memcpy(&((bytes)[pos]), reader->readBuffer(), toRead); - pos += toRead; - reader->move(toRead); - } - - StatsLogReport report; - report.ParseFromArray(bytes.data(), bytes.size()); - return report; -} - -AtomMatcher CreateSimpleAtomMatcher(const string& name, int atomId) { - 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(atomId); - return atom_matcher; -} - -AtomMatcher CreateTemperatureAtomMatcher() { - return CreateSimpleAtomMatcher("TemperatureMatcher", util::TEMPERATURE); -} - -AtomMatcher CreateScheduledJobStateChangedAtomMatcher(const string& name, - ScheduledJobStateChanged::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(util::SCHEDULED_JOB_STATE_CHANGED); - auto field_value_matcher = simple_atom_matcher->add_field_value_matcher(); - field_value_matcher->set_field(3); // State field. - field_value_matcher->set_eq_int(state); - return atom_matcher; -} - -AtomMatcher CreateStartScheduledJobAtomMatcher() { - return CreateScheduledJobStateChangedAtomMatcher("ScheduledJobStart", - ScheduledJobStateChanged::STARTED); -} - -AtomMatcher CreateFinishScheduledJobAtomMatcher() { - return CreateScheduledJobStateChangedAtomMatcher("ScheduledJobFinish", - ScheduledJobStateChanged::FINISHED); -} - -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(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(util::UID_PROCESS_STATE_CHANGED); - return atom_matcher; -} - -AtomMatcher CreateWakelockStateChangedAtomMatcher(const string& name, - WakelockStateChanged::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(util::WAKELOCK_STATE_CHANGED); - auto field_value_matcher = simple_atom_matcher->add_field_value_matcher(); - field_value_matcher->set_field(4); // State field. - field_value_matcher->set_eq_int(state); - return atom_matcher; -} - -AtomMatcher CreateAcquireWakelockAtomMatcher() { - return CreateWakelockStateChangedAtomMatcher("AcquireWakelock", WakelockStateChanged::ACQUIRE); -} - -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(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 CreateBatteryStateChangedAtomMatcher(const string& name, - BatteryPluggedStateEnum 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(util::PLUGGED_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 CreateBatteryStateNoneMatcher() { - return CreateBatteryStateChangedAtomMatcher("BatteryPluggedNone", - BatteryPluggedStateEnum::BATTERY_PLUGGED_NONE); -} - -AtomMatcher CreateBatteryStateUsbMatcher() { - return CreateBatteryStateChangedAtomMatcher("BatteryPluggedUsb", - BatteryPluggedStateEnum::BATTERY_PLUGGED_USB); -} - -AtomMatcher CreateScreenStateChangedAtomMatcher( - const string& name, android::view::DisplayStateEnum 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(util::SCREEN_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 CreateScreenTurnedOnAtomMatcher() { - return CreateScreenStateChangedAtomMatcher("ScreenTurnedOn", - android::view::DisplayStateEnum::DISPLAY_STATE_ON); -} - -AtomMatcher CreateScreenTurnedOffAtomMatcher() { - return CreateScreenStateChangedAtomMatcher("ScreenTurnedOff", - ::android::view::DisplayStateEnum::DISPLAY_STATE_OFF); -} - -AtomMatcher CreateSyncStateChangedAtomMatcher( - const string& name, SyncStateChanged::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(util::SYNC_STATE_CHANGED); - auto field_value_matcher = simple_atom_matcher->add_field_value_matcher(); - field_value_matcher->set_field(3); // State field. - field_value_matcher->set_eq_int(state); - return atom_matcher; -} - -AtomMatcher CreateSyncStartAtomMatcher() { - return CreateSyncStateChangedAtomMatcher("SyncStart", SyncStateChanged::ON); -} - -AtomMatcher CreateSyncEndAtomMatcher() { - return CreateSyncStateChangedAtomMatcher("SyncEnd", SyncStateChanged::OFF); -} - -AtomMatcher CreateActivityForegroundStateChangedAtomMatcher( - const string& name, ActivityForegroundStateChanged::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(util::ACTIVITY_FOREGROUND_STATE_CHANGED); - auto field_value_matcher = simple_atom_matcher->add_field_value_matcher(); - field_value_matcher->set_field(4); // Activity field. - field_value_matcher->set_eq_int(state); - return atom_matcher; -} - -AtomMatcher CreateMoveToBackgroundAtomMatcher() { - return CreateActivityForegroundStateChangedAtomMatcher( - "Background", ActivityForegroundStateChanged::BACKGROUND); -} - -AtomMatcher CreateMoveToForegroundAtomMatcher() { - return CreateActivityForegroundStateChangedAtomMatcher( - "Foreground", ActivityForegroundStateChanged::FOREGROUND); -} - -AtomMatcher CreateProcessLifeCycleStateChangedAtomMatcher( - const string& name, ProcessLifeCycleStateChanged::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(util::PROCESS_LIFE_CYCLE_STATE_CHANGED); - auto field_value_matcher = simple_atom_matcher->add_field_value_matcher(); - field_value_matcher->set_field(3); // Process state field. - field_value_matcher->set_eq_int(state); - return atom_matcher; -} - -AtomMatcher CreateProcessCrashAtomMatcher() { - return CreateProcessLifeCycleStateChangedAtomMatcher( - "Crashed", ProcessLifeCycleStateChanged::CRASHED); -} - -void addMatcherToMatcherCombination(const AtomMatcher& matcher, AtomMatcher* combinationMatcher) { - combinationMatcher->mutable_combination()->add_matcher(matcher.id()); -} - -Predicate CreateScheduledJobPredicate() { - Predicate predicate; - predicate.set_id(StringToId("ScheduledJobRunningPredicate")); - predicate.mutable_simple_predicate()->set_start(StringToId("ScheduledJobStart")); - predicate.mutable_simple_predicate()->set_stop(StringToId("ScheduledJobFinish")); - return predicate; -} - -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 CreateDeviceUnpluggedPredicate() { - Predicate predicate; - predicate.set_id(StringToId("DeviceUnplugged")); - predicate.mutable_simple_predicate()->set_start(StringToId("BatteryPluggedNone")); - predicate.mutable_simple_predicate()->set_stop(StringToId("BatteryPluggedUsb")); - return predicate; -} - -Predicate CreateScreenIsOnPredicate() { - Predicate predicate; - predicate.set_id(StringToId("ScreenIsOn")); - predicate.mutable_simple_predicate()->set_start(StringToId("ScreenTurnedOn")); - predicate.mutable_simple_predicate()->set_stop(StringToId("ScreenTurnedOff")); - return predicate; -} - -Predicate CreateScreenIsOffPredicate() { - Predicate predicate; - predicate.set_id(1111123); - predicate.mutable_simple_predicate()->set_start(StringToId("ScreenTurnedOff")); - predicate.mutable_simple_predicate()->set_stop(StringToId("ScreenTurnedOn")); - return predicate; -} - -Predicate CreateHoldingWakelockPredicate() { - Predicate predicate; - predicate.set_id(StringToId("HoldingWakelock")); - predicate.mutable_simple_predicate()->set_start(StringToId("AcquireWakelock")); - predicate.mutable_simple_predicate()->set_stop(StringToId("ReleaseWakelock")); - return predicate; -} - -Predicate CreateIsSyncingPredicate() { - Predicate predicate; - predicate.set_id(33333333333333); - predicate.mutable_simple_predicate()->set_start(StringToId("SyncStart")); - predicate.mutable_simple_predicate()->set_stop(StringToId("SyncEnd")); - return predicate; -} - -Predicate CreateIsInBackgroundPredicate() { - Predicate predicate; - predicate.set_id(StringToId("IsInBackground")); - predicate.mutable_simple_predicate()->set_start(StringToId("Background")); - predicate.mutable_simple_predicate()->set_stop(StringToId("Foreground")); - return predicate; -} - -State CreateScreenState() { - State state; - state.set_id(StringToId("ScreenState")); - state.set_atom_id(util::SCREEN_STATE_CHANGED); - return state; -} - -State CreateUidProcessState() { - State state; - state.set_id(StringToId("UidProcessState")); - state.set_atom_id(util::UID_PROCESS_STATE_CHANGED); - return state; -} - -State CreateOverlayState() { - State state; - state.set_id(StringToId("OverlayState")); - state.set_atom_id(util::OVERLAY_STATE_CHANGED); - return state; -} - -State CreateScreenStateWithOnOffMap(int64_t screenOnId, int64_t screenOffId) { - State state; - state.set_id(StringToId("ScreenStateOnOff")); - state.set_atom_id(util::SCREEN_STATE_CHANGED); - - auto map = CreateScreenStateOnOffMap(screenOnId, screenOffId); - *state.mutable_map() = map; - - return state; -} - -State CreateScreenStateWithSimpleOnOffMap(int64_t screenOnId, int64_t screenOffId) { - State state; - state.set_id(StringToId("ScreenStateSimpleOnOff")); - state.set_atom_id(util::SCREEN_STATE_CHANGED); - - auto map = CreateScreenStateSimpleOnOffMap(screenOnId, screenOffId); - *state.mutable_map() = map; - - return state; -} - -StateMap_StateGroup CreateScreenStateOnGroup(int64_t screenOnId) { - StateMap_StateGroup group; - group.set_group_id(screenOnId); - group.add_value(android::view::DisplayStateEnum::DISPLAY_STATE_ON); - group.add_value(android::view::DisplayStateEnum::DISPLAY_STATE_VR); - group.add_value(android::view::DisplayStateEnum::DISPLAY_STATE_ON_SUSPEND); - return group; -} - -StateMap_StateGroup CreateScreenStateOffGroup(int64_t screenOffId) { - StateMap_StateGroup group; - group.set_group_id(screenOffId); - group.add_value(android::view::DisplayStateEnum::DISPLAY_STATE_OFF); - group.add_value(android::view::DisplayStateEnum::DISPLAY_STATE_DOZE); - group.add_value(android::view::DisplayStateEnum::DISPLAY_STATE_DOZE_SUSPEND); - return group; -} - -StateMap_StateGroup CreateScreenStateSimpleOnGroup(int64_t screenOnId) { - StateMap_StateGroup group; - group.set_group_id(screenOnId); - group.add_value(android::view::DisplayStateEnum::DISPLAY_STATE_ON); - return group; -} - -StateMap_StateGroup CreateScreenStateSimpleOffGroup(int64_t screenOffId) { - StateMap_StateGroup group; - group.set_group_id(screenOffId); - group.add_value(android::view::DisplayStateEnum::DISPLAY_STATE_OFF); - return group; -} - -StateMap CreateScreenStateOnOffMap(int64_t screenOnId, int64_t screenOffId) { - StateMap map; - *map.add_group() = CreateScreenStateOnGroup(screenOnId); - *map.add_group() = CreateScreenStateOffGroup(screenOffId); - return map; -} - -StateMap CreateScreenStateSimpleOnOffMap(int64_t screenOnId, int64_t screenOffId) { - StateMap map; - *map.add_group() = CreateScreenStateSimpleOnGroup(screenOnId); - *map.add_group() = CreateScreenStateSimpleOffGroup(screenOffId); - return map; -} - -void addPredicateToPredicateCombination(const Predicate& predicate, - Predicate* combinationPredicate) { - combinationPredicate->mutable_combination()->add_predicate(predicate.id()); -} - -FieldMatcher CreateAttributionUidDimensions(const int atomId, - const std::vector& positions) { - FieldMatcher dimensions; - dimensions.set_field(atomId); - for (const auto position : positions) { - auto child = dimensions.add_child(); - child->set_field(1); - child->set_position(position); - child->add_child()->set_field(1); - } - return dimensions; -} - -FieldMatcher CreateAttributionUidAndTagDimensions(const int atomId, - const std::vector& positions) { - FieldMatcher dimensions; - dimensions.set_field(atomId); - for (const auto position : positions) { - auto child = dimensions.add_child(); - child->set_field(1); - child->set_position(position); - child->add_child()->set_field(1); - child->add_child()->set_field(2); - } - return dimensions; -} - -FieldMatcher CreateDimensions(const int atomId, const std::vector& fields) { - FieldMatcher dimensions; - dimensions.set_field(atomId); - for (const int field : fields) { - dimensions.add_child()->set_field(field); - } - return dimensions; -} - -FieldMatcher CreateAttributionUidAndOtherDimensions(const int atomId, - const std::vector& positions, - const std::vector& fields) { - FieldMatcher dimensions = CreateAttributionUidDimensions(atomId, positions); - - for (const int field : fields) { - dimensions.add_child()->set_field(field); - } - return dimensions; -} - -EventMetric createEventMetric(const string& name, const int64_t what, - const optional& condition) { - EventMetric metric; - metric.set_id(StringToId(name)); - metric.set_what(what); - if (condition) { - metric.set_condition(condition.value()); - } - return metric; -} - -CountMetric createCountMetric(const string& name, const int64_t what, - const optional& condition, const vector& states) { - CountMetric metric; - metric.set_id(StringToId(name)); - metric.set_what(what); - metric.set_bucket(TEN_MINUTES); - if (condition) { - metric.set_condition(condition.value()); - } - for (const int64_t state : states) { - metric.add_slice_by_state(state); - } - return metric; -} - -DurationMetric createDurationMetric(const string& name, const int64_t what, - const optional& condition, - const vector& states) { - DurationMetric metric; - metric.set_id(StringToId(name)); - metric.set_what(what); - metric.set_bucket(TEN_MINUTES); - if (condition) { - metric.set_condition(condition.value()); - } - for (const int64_t state : states) { - metric.add_slice_by_state(state); - } - return metric; -} - -GaugeMetric createGaugeMetric(const string& name, const int64_t what, - const GaugeMetric::SamplingType samplingType, - const optional& condition, - const optional& triggerEvent) { - GaugeMetric metric; - metric.set_id(StringToId(name)); - metric.set_what(what); - metric.set_bucket(TEN_MINUTES); - metric.set_sampling_type(samplingType); - if (condition) { - metric.set_condition(condition.value()); - } - if (triggerEvent) { - metric.set_trigger_event(triggerEvent.value()); - } - metric.mutable_gauge_fields_filter()->set_include_all(true); - return metric; -} - -ValueMetric createValueMetric(const string& name, const AtomMatcher& what, const int valueField, - const optional& condition, const vector& states) { - ValueMetric metric; - metric.set_id(StringToId(name)); - metric.set_what(what.id()); - metric.set_bucket(TEN_MINUTES); - metric.mutable_value_field()->set_field(what.simple_atom_matcher().atom_id()); - metric.mutable_value_field()->add_child()->set_field(valueField); - if (condition) { - metric.set_condition(condition.value()); - } - for (const int64_t state : states) { - metric.add_slice_by_state(state); - } - return metric; -} - -Alert createAlert(const string& name, const int64_t metricId, const int buckets, - const int64_t triggerSum) { - Alert alert; - alert.set_id(StringToId(name)); - alert.set_metric_id(metricId); - alert.set_num_buckets(buckets); - alert.set_trigger_if_sum_gt(triggerSum); - return alert; -} - -Alarm createAlarm(const string& name, const int64_t offsetMillis, const int64_t periodMillis) { - Alarm alarm; - alarm.set_id(StringToId(name)); - alarm.set_offset_millis(offsetMillis); - alarm.set_period_millis(periodMillis); - return alarm; -} - -Subscription createSubscription(const string& name, const Subscription_RuleType type, - const int64_t ruleId) { - Subscription subscription; - subscription.set_id(StringToId(name)); - subscription.set_rule_type(type); - subscription.set_rule_id(ruleId); - subscription.mutable_broadcast_subscriber_details(); - return subscription; -} - -// START: get primary key functions -void getUidProcessKey(int uid, HashableDimensionKey* key) { - int pos1[] = {1, 0, 0}; - Field field1(27 /* atom id */, pos1, 0 /* depth */); - Value value1((int32_t)uid); - - key->addValue(FieldValue(field1, value1)); -} - -void getOverlayKey(int uid, string packageName, HashableDimensionKey* key) { - int pos1[] = {1, 0, 0}; - int pos2[] = {2, 0, 0}; - - Field field1(59 /* atom id */, pos1, 0 /* depth */); - Field field2(59 /* atom id */, pos2, 0 /* depth */); - - Value value1((int32_t)uid); - Value value2(packageName); - - key->addValue(FieldValue(field1, value1)); - key->addValue(FieldValue(field2, value2)); -} - -void getPartialWakelockKey(int uid, const std::string& tag, HashableDimensionKey* key) { - int pos1[] = {1, 1, 1}; - int pos3[] = {2, 0, 0}; - int pos4[] = {3, 0, 0}; - - Field field1(10 /* atom id */, pos1, 2 /* depth */); - - Field field3(10 /* atom id */, pos3, 0 /* depth */); - Field field4(10 /* atom id */, pos4, 0 /* depth */); - - Value value1((int32_t)uid); - Value value3((int32_t)1 /*partial*/); - Value value4(tag); - - key->addValue(FieldValue(field1, value1)); - key->addValue(FieldValue(field3, value3)); - key->addValue(FieldValue(field4, value4)); -} - -void getPartialWakelockKey(int uid, HashableDimensionKey* key) { - int pos1[] = {1, 1, 1}; - int pos3[] = {2, 0, 0}; - - Field field1(10 /* atom id */, pos1, 2 /* depth */); - Field field3(10 /* atom id */, pos3, 0 /* depth */); - - Value value1((int32_t)uid); - Value value3((int32_t)1 /*partial*/); - - key->addValue(FieldValue(field1, value1)); - key->addValue(FieldValue(field3, value3)); -} -// END: get primary key functions - -void writeAttribution(AStatsEvent* statsEvent, const vector& attributionUids, - const vector& attributionTags) { - vector cTags(attributionTags.size()); - for (int i = 0; i < cTags.size(); i++) { - cTags[i] = attributionTags[i].c_str(); - } - - AStatsEvent_writeAttributionChain(statsEvent, - reinterpret_cast(attributionUids.data()), - cTags.data(), attributionUids.size()); -} - -void parseStatsEventToLogEvent(AStatsEvent* statsEvent, LogEvent* logEvent) { - AStatsEvent_build(statsEvent); - - size_t size; - uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size); - logEvent->parseBuffer(buf, size); - - AStatsEvent_release(statsEvent); -} - -void CreateTwoValueLogEvent(LogEvent* logEvent, int atomId, int64_t eventTimeNs, int32_t value1, - int32_t value2) { - AStatsEvent* statsEvent = AStatsEvent_obtain(); - AStatsEvent_setAtomId(statsEvent, atomId); - AStatsEvent_overwriteTimestamp(statsEvent, eventTimeNs); - - AStatsEvent_writeInt32(statsEvent, value1); - AStatsEvent_writeInt32(statsEvent, value2); - - parseStatsEventToLogEvent(statsEvent, logEvent); -} - -shared_ptr CreateTwoValueLogEvent(int atomId, int64_t eventTimeNs, int32_t value1, - int32_t value2) { - shared_ptr logEvent = std::make_shared(/*uid=*/0, /*pid=*/0); - CreateTwoValueLogEvent(logEvent.get(), atomId, eventTimeNs, value1, value2); - return logEvent; -} - -void CreateThreeValueLogEvent(LogEvent* logEvent, int atomId, int64_t eventTimeNs, int32_t value1, - int32_t value2, int32_t value3) { - AStatsEvent* statsEvent = AStatsEvent_obtain(); - AStatsEvent_setAtomId(statsEvent, atomId); - AStatsEvent_overwriteTimestamp(statsEvent, eventTimeNs); - - AStatsEvent_writeInt32(statsEvent, value1); - AStatsEvent_writeInt32(statsEvent, value2); - AStatsEvent_writeInt32(statsEvent, value3); - - parseStatsEventToLogEvent(statsEvent, logEvent); -} - -shared_ptr CreateThreeValueLogEvent(int atomId, int64_t eventTimeNs, int32_t value1, - int32_t value2, int32_t value3) { - shared_ptr logEvent = std::make_shared(/*uid=*/0, /*pid=*/0); - CreateThreeValueLogEvent(logEvent.get(), atomId, eventTimeNs, value1, value2, value3); - return logEvent; -} - -void CreateRepeatedValueLogEvent(LogEvent* logEvent, int atomId, int64_t eventTimeNs, - int32_t value) { - AStatsEvent* statsEvent = AStatsEvent_obtain(); - AStatsEvent_setAtomId(statsEvent, atomId); - AStatsEvent_overwriteTimestamp(statsEvent, eventTimeNs); - - AStatsEvent_writeInt32(statsEvent, value); - AStatsEvent_writeInt32(statsEvent, value); - - parseStatsEventToLogEvent(statsEvent, logEvent); -} - -shared_ptr CreateRepeatedValueLogEvent(int atomId, int64_t eventTimeNs, int32_t value) { - shared_ptr logEvent = std::make_shared(/*uid=*/0, /*pid=*/0); - CreateRepeatedValueLogEvent(logEvent.get(), atomId, eventTimeNs, value); - return logEvent; -} - -void CreateNoValuesLogEvent(LogEvent* logEvent, int atomId, int64_t eventTimeNs) { - AStatsEvent* statsEvent = AStatsEvent_obtain(); - AStatsEvent_setAtomId(statsEvent, atomId); - AStatsEvent_overwriteTimestamp(statsEvent, eventTimeNs); - - parseStatsEventToLogEvent(statsEvent, logEvent); -} - -shared_ptr CreateNoValuesLogEvent(int atomId, int64_t eventTimeNs) { - shared_ptr logEvent = std::make_shared(/*uid=*/0, /*pid=*/0); - CreateNoValuesLogEvent(logEvent.get(), atomId, eventTimeNs); - return logEvent; -} - -shared_ptr makeUidLogEvent(int atomId, int64_t eventTimeNs, int uid, int data1, - int data2) { - AStatsEvent* statsEvent = AStatsEvent_obtain(); - AStatsEvent_setAtomId(statsEvent, atomId); - AStatsEvent_overwriteTimestamp(statsEvent, eventTimeNs); - - AStatsEvent_writeInt32(statsEvent, uid); - AStatsEvent_addBoolAnnotation(statsEvent, ANNOTATION_ID_IS_UID, true); - AStatsEvent_writeInt32(statsEvent, data1); - AStatsEvent_writeInt32(statsEvent, data2); - - shared_ptr logEvent = std::make_shared(/*uid=*/0, /*pid=*/0); - parseStatsEventToLogEvent(statsEvent, logEvent.get()); - return logEvent; -} - -shared_ptr makeAttributionLogEvent(int atomId, int64_t eventTimeNs, - const vector& uids, const vector& tags, - int data1, int data2) { - AStatsEvent* statsEvent = AStatsEvent_obtain(); - AStatsEvent_setAtomId(statsEvent, atomId); - AStatsEvent_overwriteTimestamp(statsEvent, eventTimeNs); - - writeAttribution(statsEvent, uids, tags); - AStatsEvent_writeInt32(statsEvent, data1); - AStatsEvent_writeInt32(statsEvent, data2); - - shared_ptr logEvent = std::make_shared(/*uid=*/0, /*pid=*/0); - parseStatsEventToLogEvent(statsEvent, logEvent.get()); - return logEvent; -} - -sp makeMockUidMapForOneHost(int hostUid, const vector& isolatedUids) { - sp uidMap = new NaggyMock(); - EXPECT_CALL(*uidMap, getHostUidOrSelf(_)).WillRepeatedly(ReturnArg<0>()); - for (const int isolatedUid : isolatedUids) { - EXPECT_CALL(*uidMap, getHostUidOrSelf(isolatedUid)).WillRepeatedly(Return(hostUid)); - } - - return uidMap; -} - -sp makeMockUidMapForPackage(const string& pkg, const set& uids) { - sp uidMap = new StrictMock(); - EXPECT_CALL(*uidMap, getAppUid(_)).Times(AnyNumber()); - EXPECT_CALL(*uidMap, getAppUid(pkg)).WillRepeatedly(Return(uids)); - - return uidMap; -} - -std::unique_ptr CreateScreenStateChangedEvent(uint64_t timestampNs, - const android::view::DisplayStateEnum state, - int loggerUid) { - AStatsEvent* statsEvent = AStatsEvent_obtain(); - AStatsEvent_setAtomId(statsEvent, util::SCREEN_STATE_CHANGED); - AStatsEvent_overwriteTimestamp(statsEvent, timestampNs); - AStatsEvent_writeInt32(statsEvent, state); - AStatsEvent_addBoolAnnotation(statsEvent, util::ANNOTATION_ID_EXCLUSIVE_STATE, true); - AStatsEvent_addBoolAnnotation(statsEvent, util::ANNOTATION_ID_STATE_NESTED, false); - - std::unique_ptr logEvent = std::make_unique(loggerUid, /*pid=*/0); - parseStatsEventToLogEvent(statsEvent, logEvent.get()); - return logEvent; -} - -std::unique_ptr CreateBatterySaverOnEvent(uint64_t timestampNs) { - AStatsEvent* statsEvent = AStatsEvent_obtain(); - AStatsEvent_setAtomId(statsEvent, util::BATTERY_SAVER_MODE_STATE_CHANGED); - AStatsEvent_overwriteTimestamp(statsEvent, timestampNs); - AStatsEvent_writeInt32(statsEvent, BatterySaverModeStateChanged::ON); - AStatsEvent_addBoolAnnotation(statsEvent, util::ANNOTATION_ID_EXCLUSIVE_STATE, true); - AStatsEvent_addBoolAnnotation(statsEvent, util::ANNOTATION_ID_STATE_NESTED, false); - - std::unique_ptr logEvent = std::make_unique(/*uid=*/0, /*pid=*/0); - parseStatsEventToLogEvent(statsEvent, logEvent.get()); - return logEvent; -} - -std::unique_ptr CreateBatterySaverOffEvent(uint64_t timestampNs) { - AStatsEvent* statsEvent = AStatsEvent_obtain(); - AStatsEvent_setAtomId(statsEvent, util::BATTERY_SAVER_MODE_STATE_CHANGED); - AStatsEvent_overwriteTimestamp(statsEvent, timestampNs); - AStatsEvent_writeInt32(statsEvent, BatterySaverModeStateChanged::OFF); - AStatsEvent_addBoolAnnotation(statsEvent, util::ANNOTATION_ID_EXCLUSIVE_STATE, true); - AStatsEvent_addBoolAnnotation(statsEvent, util::ANNOTATION_ID_STATE_NESTED, false); - - std::unique_ptr logEvent = std::make_unique(/*uid=*/0, /*pid=*/0); - parseStatsEventToLogEvent(statsEvent, logEvent.get()); - return logEvent; -} - -std::unique_ptr CreateBatteryStateChangedEvent(const uint64_t timestampNs, const BatteryPluggedStateEnum state) { - AStatsEvent* statsEvent = AStatsEvent_obtain(); - AStatsEvent_setAtomId(statsEvent, util::PLUGGED_STATE_CHANGED); - AStatsEvent_overwriteTimestamp(statsEvent, timestampNs); - AStatsEvent_writeInt32(statsEvent, state); - - std::unique_ptr logEvent = std::make_unique(/*uid=*/0, /*pid=*/0); - parseStatsEventToLogEvent(statsEvent, logEvent.get()); - return logEvent; -} - -std::unique_ptr CreateScreenBrightnessChangedEvent(uint64_t timestampNs, int level) { - AStatsEvent* statsEvent = AStatsEvent_obtain(); - AStatsEvent_setAtomId(statsEvent, util::SCREEN_BRIGHTNESS_CHANGED); - AStatsEvent_overwriteTimestamp(statsEvent, timestampNs); - AStatsEvent_writeInt32(statsEvent, level); - - std::unique_ptr logEvent = std::make_unique(/*uid=*/0, /*pid=*/0); - parseStatsEventToLogEvent(statsEvent, logEvent.get()); - return logEvent; -} - -std::unique_ptr CreateScheduledJobStateChangedEvent( - const vector& attributionUids, const vector& attributionTags, - const string& jobName, const ScheduledJobStateChanged::State state, uint64_t timestampNs) { - AStatsEvent* statsEvent = AStatsEvent_obtain(); - AStatsEvent_setAtomId(statsEvent, util::SCHEDULED_JOB_STATE_CHANGED); - AStatsEvent_overwriteTimestamp(statsEvent, timestampNs); - - writeAttribution(statsEvent, attributionUids, attributionTags); - AStatsEvent_writeString(statsEvent, jobName.c_str()); - AStatsEvent_writeInt32(statsEvent, state); - - std::unique_ptr logEvent = std::make_unique(/*uid=*/0, /*pid=*/0); - parseStatsEventToLogEvent(statsEvent, logEvent.get()); - return logEvent; -} - -std::unique_ptr CreateStartScheduledJobEvent(uint64_t timestampNs, - const vector& attributionUids, - const vector& attributionTags, - const string& jobName) { - return CreateScheduledJobStateChangedEvent(attributionUids, attributionTags, jobName, - ScheduledJobStateChanged::STARTED, timestampNs); -} - -// Create log event when scheduled job finishes. -std::unique_ptr CreateFinishScheduledJobEvent(uint64_t timestampNs, - const vector& attributionUids, - const vector& attributionTags, - const string& jobName) { - return CreateScheduledJobStateChangedEvent(attributionUids, attributionTags, jobName, - ScheduledJobStateChanged::FINISHED, timestampNs); -} - -std::unique_ptr CreateWakelockStateChangedEvent(uint64_t timestampNs, - const vector& attributionUids, - const vector& attributionTags, - const string& wakelockName, - const WakelockStateChanged::State state) { - AStatsEvent* statsEvent = AStatsEvent_obtain(); - AStatsEvent_setAtomId(statsEvent, util::WAKELOCK_STATE_CHANGED); - AStatsEvent_overwriteTimestamp(statsEvent, timestampNs); - - writeAttribution(statsEvent, attributionUids, attributionTags); - AStatsEvent_addBoolAnnotation(statsEvent, util::ANNOTATION_ID_PRIMARY_FIELD_FIRST_UID, true); - AStatsEvent_writeInt32(statsEvent, android::os::WakeLockLevelEnum::PARTIAL_WAKE_LOCK); - AStatsEvent_addBoolAnnotation(statsEvent, util::ANNOTATION_ID_PRIMARY_FIELD, true); - AStatsEvent_writeString(statsEvent, wakelockName.c_str()); - AStatsEvent_addBoolAnnotation(statsEvent, util::ANNOTATION_ID_PRIMARY_FIELD, true); - AStatsEvent_writeInt32(statsEvent, state); - AStatsEvent_addBoolAnnotation(statsEvent, util::ANNOTATION_ID_EXCLUSIVE_STATE, true); - AStatsEvent_addBoolAnnotation(statsEvent, util::ANNOTATION_ID_STATE_NESTED, true); - - std::unique_ptr logEvent = std::make_unique(/*uid=*/0, /*pid=*/0); - parseStatsEventToLogEvent(statsEvent, logEvent.get()); - return logEvent; -} - -std::unique_ptr CreateAcquireWakelockEvent(uint64_t timestampNs, - const vector& attributionUids, - const vector& attributionTags, - const string& wakelockName) { - return CreateWakelockStateChangedEvent(timestampNs, attributionUids, attributionTags, - wakelockName, WakelockStateChanged::ACQUIRE); -} - -std::unique_ptr CreateReleaseWakelockEvent(uint64_t timestampNs, - const vector& attributionUids, - const vector& attributionTags, - const string& wakelockName) { - return CreateWakelockStateChangedEvent(timestampNs, attributionUids, attributionTags, - wakelockName, WakelockStateChanged::RELEASE); -} - -std::unique_ptr CreateActivityForegroundStateChangedEvent( - uint64_t timestampNs, const int uid, const ActivityForegroundStateChanged::State state) { - AStatsEvent* statsEvent = AStatsEvent_obtain(); - AStatsEvent_setAtomId(statsEvent, util::ACTIVITY_FOREGROUND_STATE_CHANGED); - AStatsEvent_overwriteTimestamp(statsEvent, timestampNs); - - AStatsEvent_writeInt32(statsEvent, uid); - AStatsEvent_writeString(statsEvent, "pkg_name"); - AStatsEvent_writeString(statsEvent, "class_name"); - AStatsEvent_writeInt32(statsEvent, state); - - std::unique_ptr logEvent = std::make_unique(/*uid=*/0, /*pid=*/0); - parseStatsEventToLogEvent(statsEvent, logEvent.get()); - return logEvent; -} - -std::unique_ptr CreateMoveToBackgroundEvent(uint64_t timestampNs, const int uid) { - return CreateActivityForegroundStateChangedEvent(timestampNs, uid, - ActivityForegroundStateChanged::BACKGROUND); -} - -std::unique_ptr CreateMoveToForegroundEvent(uint64_t timestampNs, const int uid) { - return CreateActivityForegroundStateChangedEvent(timestampNs, uid, - ActivityForegroundStateChanged::FOREGROUND); -} - -std::unique_ptr CreateSyncStateChangedEvent(uint64_t timestampNs, - const vector& attributionUids, - const vector& attributionTags, - const string& name, - const SyncStateChanged::State state) { - AStatsEvent* statsEvent = AStatsEvent_obtain(); - AStatsEvent_setAtomId(statsEvent, util::SYNC_STATE_CHANGED); - AStatsEvent_overwriteTimestamp(statsEvent, timestampNs); - - writeAttribution(statsEvent, attributionUids, attributionTags); - AStatsEvent_writeString(statsEvent, name.c_str()); - AStatsEvent_writeInt32(statsEvent, state); - - std::unique_ptr logEvent = std::make_unique(/*uid=*/0, /*pid=*/0); - parseStatsEventToLogEvent(statsEvent, logEvent.get()); - return logEvent; -} - -std::unique_ptr CreateSyncStartEvent(uint64_t timestampNs, - const vector& attributionUids, - const vector& attributionTags, - const string& name) { - return CreateSyncStateChangedEvent(timestampNs, attributionUids, attributionTags, name, - SyncStateChanged::ON); -} - -std::unique_ptr CreateSyncEndEvent(uint64_t timestampNs, - const vector& attributionUids, - const vector& attributionTags, - const string& name) { - return CreateSyncStateChangedEvent(timestampNs, attributionUids, attributionTags, name, - SyncStateChanged::OFF); -} - -std::unique_ptr CreateProcessLifeCycleStateChangedEvent( - uint64_t timestampNs, const int uid, const ProcessLifeCycleStateChanged::State state) { - AStatsEvent* statsEvent = AStatsEvent_obtain(); - AStatsEvent_setAtomId(statsEvent, util::PROCESS_LIFE_CYCLE_STATE_CHANGED); - AStatsEvent_overwriteTimestamp(statsEvent, timestampNs); - - AStatsEvent_writeInt32(statsEvent, uid); - AStatsEvent_writeString(statsEvent, ""); - AStatsEvent_writeInt32(statsEvent, state); - - std::unique_ptr logEvent = std::make_unique(/*uid=*/0, /*pid=*/0); - parseStatsEventToLogEvent(statsEvent, logEvent.get()); - return logEvent; -} - -std::unique_ptr CreateAppCrashEvent(uint64_t timestampNs, const int uid) { - return CreateProcessLifeCycleStateChangedEvent(timestampNs, uid, - ProcessLifeCycleStateChanged::CRASHED); -} - -std::unique_ptr CreateAppCrashOccurredEvent(uint64_t timestampNs, const int uid) { - AStatsEvent* statsEvent = AStatsEvent_obtain(); - AStatsEvent_setAtomId(statsEvent, util::APP_CRASH_OCCURRED); - AStatsEvent_overwriteTimestamp(statsEvent, timestampNs); - - AStatsEvent_writeInt32(statsEvent, uid); - AStatsEvent_writeString(statsEvent, "eventType"); - AStatsEvent_writeString(statsEvent, "processName"); - - std::unique_ptr logEvent = std::make_unique(/*uid=*/0, /*pid=*/0); - parseStatsEventToLogEvent(statsEvent, logEvent.get()); - return logEvent; -} - -std::unique_ptr CreateIsolatedUidChangedEvent(uint64_t timestampNs, int hostUid, - int isolatedUid, bool is_create) { - AStatsEvent* statsEvent = AStatsEvent_obtain(); - AStatsEvent_setAtomId(statsEvent, util::ISOLATED_UID_CHANGED); - AStatsEvent_overwriteTimestamp(statsEvent, timestampNs); - - AStatsEvent_writeInt32(statsEvent, hostUid); - AStatsEvent_writeInt32(statsEvent, isolatedUid); - AStatsEvent_writeInt32(statsEvent, is_create); - - std::unique_ptr logEvent = std::make_unique(/*uid=*/0, /*pid=*/0); - parseStatsEventToLogEvent(statsEvent, logEvent.get()); - return logEvent; -} - -std::unique_ptr CreateUidProcessStateChangedEvent( - uint64_t timestampNs, int uid, const android::app::ProcessStateEnum state) { - AStatsEvent* statsEvent = AStatsEvent_obtain(); - AStatsEvent_setAtomId(statsEvent, util::UID_PROCESS_STATE_CHANGED); - AStatsEvent_overwriteTimestamp(statsEvent, timestampNs); - - AStatsEvent_writeInt32(statsEvent, uid); - AStatsEvent_addBoolAnnotation(statsEvent, util::ANNOTATION_ID_IS_UID, true); - AStatsEvent_addBoolAnnotation(statsEvent, util::ANNOTATION_ID_PRIMARY_FIELD, true); - AStatsEvent_writeInt32(statsEvent, state); - AStatsEvent_addBoolAnnotation(statsEvent, util::ANNOTATION_ID_EXCLUSIVE_STATE, true); - AStatsEvent_addBoolAnnotation(statsEvent, util::ANNOTATION_ID_STATE_NESTED, false); - - std::unique_ptr logEvent = std::make_unique(/*uid=*/0, /*pid=*/0); - parseStatsEventToLogEvent(statsEvent, logEvent.get()); - return logEvent; -} - -std::unique_ptr CreateBleScanStateChangedEvent(uint64_t timestampNs, - const vector& attributionUids, - const vector& attributionTags, - const BleScanStateChanged::State state, - const bool filtered, const bool firstMatch, - const bool opportunistic) { - AStatsEvent* statsEvent = AStatsEvent_obtain(); - AStatsEvent_setAtomId(statsEvent, util::BLE_SCAN_STATE_CHANGED); - AStatsEvent_overwriteTimestamp(statsEvent, timestampNs); - - writeAttribution(statsEvent, attributionUids, attributionTags); - AStatsEvent_addBoolAnnotation(statsEvent, util::ANNOTATION_ID_PRIMARY_FIELD_FIRST_UID, true); - AStatsEvent_writeInt32(statsEvent, state); - AStatsEvent_addBoolAnnotation(statsEvent, util::ANNOTATION_ID_EXCLUSIVE_STATE, true); - AStatsEvent_addBoolAnnotation(statsEvent, util::ANNOTATION_ID_STATE_NESTED, true); - if (state == util::BLE_SCAN_STATE_CHANGED__STATE__RESET) { - AStatsEvent_addInt32Annotation(statsEvent, ANNOTATION_ID_TRIGGER_STATE_RESET, - util::BLE_SCAN_STATE_CHANGED__STATE__OFF); - } - AStatsEvent_writeBool(statsEvent, filtered); // filtered - AStatsEvent_addBoolAnnotation(statsEvent, util::ANNOTATION_ID_PRIMARY_FIELD, true); - AStatsEvent_writeBool(statsEvent, firstMatch); // first match - AStatsEvent_addBoolAnnotation(statsEvent, util::ANNOTATION_ID_PRIMARY_FIELD, true); - AStatsEvent_writeBool(statsEvent, opportunistic); // opportunistic - AStatsEvent_addBoolAnnotation(statsEvent, util::ANNOTATION_ID_PRIMARY_FIELD, true); - - std::unique_ptr logEvent = std::make_unique(/*uid=*/0, /*pid=*/0); - parseStatsEventToLogEvent(statsEvent, logEvent.get()); - return logEvent; -} - -std::unique_ptr CreateOverlayStateChangedEvent(int64_t timestampNs, const int32_t uid, - const string& packageName, - const bool usingAlertWindow, - const OverlayStateChanged::State state) { - AStatsEvent* statsEvent = AStatsEvent_obtain(); - AStatsEvent_setAtomId(statsEvent, util::OVERLAY_STATE_CHANGED); - AStatsEvent_overwriteTimestamp(statsEvent, timestampNs); - - AStatsEvent_writeInt32(statsEvent, uid); - AStatsEvent_addBoolAnnotation(statsEvent, util::ANNOTATION_ID_IS_UID, true); - AStatsEvent_addBoolAnnotation(statsEvent, util::ANNOTATION_ID_PRIMARY_FIELD, true); - AStatsEvent_writeString(statsEvent, packageName.c_str()); - AStatsEvent_addBoolAnnotation(statsEvent, util::ANNOTATION_ID_PRIMARY_FIELD, true); - AStatsEvent_writeBool(statsEvent, usingAlertWindow); - AStatsEvent_writeInt32(statsEvent, state); - AStatsEvent_addBoolAnnotation(statsEvent, util::ANNOTATION_ID_EXCLUSIVE_STATE, true); - AStatsEvent_addBoolAnnotation(statsEvent, util::ANNOTATION_ID_STATE_NESTED, false); - - std::unique_ptr logEvent = std::make_unique(/*uid=*/0, /*pid=*/0); - parseStatsEventToLogEvent(statsEvent, logEvent.get()); - return logEvent; -} - -std::unique_ptr CreateAppStartOccurredEvent( - uint64_t timestampNs, const int uid, const string& pkgName, - AppStartOccurred::TransitionType type, const string& activityName, - const string& callingPkgName, const bool isInstantApp, int64_t activityStartMs) { - AStatsEvent* statsEvent = AStatsEvent_obtain(); - AStatsEvent_setAtomId(statsEvent, util::APP_START_OCCURRED); - AStatsEvent_overwriteTimestamp(statsEvent, timestampNs); - - AStatsEvent_writeInt32(statsEvent, uid); - AStatsEvent_writeString(statsEvent, pkgName.c_str()); - AStatsEvent_writeInt32(statsEvent, type); - AStatsEvent_writeString(statsEvent, activityName.c_str()); - AStatsEvent_writeString(statsEvent, callingPkgName.c_str()); - AStatsEvent_writeInt32(statsEvent, isInstantApp); - AStatsEvent_writeInt32(statsEvent, activityStartMs); - - std::unique_ptr logEvent = std::make_unique(/*uid=*/0, /*pid=*/0); - parseStatsEventToLogEvent(statsEvent, logEvent.get()); - return logEvent; -} - -sp CreateStatsLogProcessor(const int64_t timeBaseNs, const int64_t currentTimeNs, - const StatsdConfig& config, const ConfigKey& key, - const shared_ptr& puller, - const int32_t atomTag, const sp uidMap) { - sp pullerManager = new StatsPullerManager(); - if (puller != nullptr) { - pullerManager->RegisterPullAtomCallback(/*uid=*/0, atomTag, NS_PER_SEC, NS_PER_SEC * 10, {}, - puller); - } - sp anomalyAlarmMonitor = - new AlarmMonitor(1, - [](const shared_ptr&, int64_t){}, - [](const shared_ptr&){}); - sp periodicAlarmMonitor = - new AlarmMonitor(1, - [](const shared_ptr&, int64_t){}, - [](const shared_ptr&){}); - sp processor = - new StatsLogProcessor(uidMap, pullerManager, anomalyAlarmMonitor, periodicAlarmMonitor, - timeBaseNs, [](const ConfigKey&) { return true; }, - [](const int&, const vector&) {return true;}); - processor->OnConfigUpdated(currentTimeNs, key, config); - return processor; -} - -void sortLogEventsByTimestamp(std::vector> *events) { - std::sort(events->begin(), events->end(), - [](const std::unique_ptr& a, const std::unique_ptr& b) { - return a->GetElapsedTimestampNs() < b->GetElapsedTimestampNs(); - }); -} - -int64_t StringToId(const string& str) { - return static_cast(std::hash()(str)); -} - -sp createEventMatcherWizard( - int tagId, int matcherIndex, const vector& fieldValueMatchers) { - sp uidMap = new UidMap(); - SimpleAtomMatcher atomMatcher; - atomMatcher.set_atom_id(tagId); - for (const FieldValueMatcher& fvm : fieldValueMatchers) { - *atomMatcher.add_field_value_matcher() = fvm; - } - uint64_t matcherHash = 0x12345678; - int64_t matcherId = 678; - return new EventMatcherWizard({new SimpleAtomMatchingTracker( - matcherId, matcherIndex, matcherHash, atomMatcher, uidMap)}); -} - -StatsDimensionsValueParcel CreateAttributionUidDimensionsValueParcel(const int atomId, - const int uid) { - StatsDimensionsValueParcel root; - root.field = atomId; - root.valueType = STATS_DIMENSIONS_VALUE_TUPLE_TYPE; - StatsDimensionsValueParcel attrNode; - attrNode.field = 1; - attrNode.valueType = STATS_DIMENSIONS_VALUE_TUPLE_TYPE; - StatsDimensionsValueParcel posInAttrChain; - posInAttrChain.field = 1; - posInAttrChain.valueType = STATS_DIMENSIONS_VALUE_TUPLE_TYPE; - StatsDimensionsValueParcel uidNode; - uidNode.field = 1; - uidNode.valueType = STATS_DIMENSIONS_VALUE_INT_TYPE; - uidNode.intValue = uid; - posInAttrChain.tupleValue.push_back(uidNode); - attrNode.tupleValue.push_back(posInAttrChain); - root.tupleValue.push_back(attrNode); - return root; -} - -void ValidateUidDimension(const DimensionsValue& value, int atomId, int uid) { - EXPECT_EQ(value.field(), atomId); - ASSERT_EQ(value.value_tuple().dimensions_value_size(), 1); - EXPECT_EQ(value.value_tuple().dimensions_value(0).field(), 1); - EXPECT_EQ(value.value_tuple().dimensions_value(0).value_int(), uid); -} - -void ValidateWakelockAttributionUidAndTagDimension(const DimensionsValue& value, const int atomId, - const int uid, const string& tag) { - EXPECT_EQ(value.field(), atomId); - ASSERT_EQ(value.value_tuple().dimensions_value_size(), 2); - // Attribution field. - EXPECT_EQ(value.value_tuple().dimensions_value(0).field(), 1); - // Uid field. - ASSERT_EQ(value.value_tuple().dimensions_value(0).value_tuple().dimensions_value_size(), 1); - EXPECT_EQ(value.value_tuple().dimensions_value(0).value_tuple().dimensions_value(0).field(), 1); - EXPECT_EQ(value.value_tuple().dimensions_value(0).value_tuple().dimensions_value(0).value_int(), - uid); - // Tag field. - EXPECT_EQ(value.value_tuple().dimensions_value(1).field(), 3); - EXPECT_EQ(value.value_tuple().dimensions_value(1).value_str(), tag); -} - -void ValidateAttributionUidDimension(const DimensionsValue& value, int atomId, int uid) { - EXPECT_EQ(value.field(), atomId); - ASSERT_EQ(value.value_tuple().dimensions_value_size(), 1); - // Attribution field. - EXPECT_EQ(value.value_tuple().dimensions_value(0).field(), 1); - // Uid only. - EXPECT_EQ(value.value_tuple().dimensions_value(0) - .value_tuple().dimensions_value_size(), 1); - EXPECT_EQ(value.value_tuple().dimensions_value(0) - .value_tuple().dimensions_value(0).field(), 1); - EXPECT_EQ(value.value_tuple().dimensions_value(0) - .value_tuple().dimensions_value(0).value_int(), uid); -} - -void ValidateUidDimension(const DimensionsValue& value, int node_idx, int atomId, int uid) { - EXPECT_EQ(value.field(), atomId); - ASSERT_GT(value.value_tuple().dimensions_value_size(), node_idx); - // Attribution field. - EXPECT_EQ(value.value_tuple().dimensions_value(node_idx).field(), 1); - EXPECT_EQ(value.value_tuple().dimensions_value(node_idx) - .value_tuple().dimensions_value(0).field(), 1); - EXPECT_EQ(value.value_tuple().dimensions_value(node_idx) - .value_tuple().dimensions_value(0).value_int(), uid); -} - -void ValidateAttributionUidAndTagDimension( - const DimensionsValue& value, int node_idx, int atomId, int uid, const std::string& tag) { - EXPECT_EQ(value.field(), atomId); - ASSERT_GT(value.value_tuple().dimensions_value_size(), node_idx); - // Attribution field. - EXPECT_EQ(1, value.value_tuple().dimensions_value(node_idx).field()); - // Uid only. - EXPECT_EQ(2, value.value_tuple().dimensions_value(node_idx) - .value_tuple().dimensions_value_size()); - EXPECT_EQ(1, value.value_tuple().dimensions_value(node_idx) - .value_tuple().dimensions_value(0).field()); - EXPECT_EQ(uid, value.value_tuple().dimensions_value(node_idx) - .value_tuple().dimensions_value(0).value_int()); - EXPECT_EQ(2, value.value_tuple().dimensions_value(node_idx) - .value_tuple().dimensions_value(1).field()); - EXPECT_EQ(tag, value.value_tuple().dimensions_value(node_idx) - .value_tuple().dimensions_value(1).value_str()); -} - -void ValidateAttributionUidAndTagDimension( - const DimensionsValue& value, int atomId, int uid, const std::string& tag) { - EXPECT_EQ(value.field(), atomId); - ASSERT_EQ(1, value.value_tuple().dimensions_value_size()); - // Attribution field. - EXPECT_EQ(1, value.value_tuple().dimensions_value(0).field()); - // Uid only. - EXPECT_EQ(value.value_tuple().dimensions_value(0) - .value_tuple().dimensions_value_size(), 2); - EXPECT_EQ(value.value_tuple().dimensions_value(0) - .value_tuple().dimensions_value(0).field(), 1); - EXPECT_EQ(value.value_tuple().dimensions_value(0) - .value_tuple().dimensions_value(0).value_int(), uid); - EXPECT_EQ(value.value_tuple().dimensions_value(0) - .value_tuple().dimensions_value(1).field(), 2); - EXPECT_EQ(value.value_tuple().dimensions_value(0) - .value_tuple().dimensions_value(1).value_str(), tag); -} - -void ValidateStateValue(const google::protobuf::RepeatedPtrField& stateValues, - int atomId, int64_t value) { - ASSERT_EQ(stateValues.size(), 1); - ASSERT_EQ(stateValues[0].atom_id(), atomId); - switch (stateValues[0].contents_case()) { - case StateValue::ContentsCase::kValue: - EXPECT_EQ(stateValues[0].value(), (int32_t)value); - break; - case StateValue::ContentsCase::kGroupId: - EXPECT_EQ(stateValues[0].group_id(), value); - break; - default: - FAIL() << "State value should have either a value or a group id"; - } -} - -void ValidateCountBucket(const CountBucketInfo& countBucket, int64_t startTimeNs, int64_t endTimeNs, - int64_t count) { - EXPECT_EQ(countBucket.start_bucket_elapsed_nanos(), startTimeNs); - EXPECT_EQ(countBucket.end_bucket_elapsed_nanos(), endTimeNs); - EXPECT_EQ(countBucket.count(), count); -} - -void ValidateDurationBucket(const DurationBucketInfo& bucket, int64_t startTimeNs, - int64_t endTimeNs, int64_t durationNs) { - EXPECT_EQ(bucket.start_bucket_elapsed_nanos(), startTimeNs); - EXPECT_EQ(bucket.end_bucket_elapsed_nanos(), endTimeNs); - EXPECT_EQ(bucket.duration_nanos(), durationNs); -} - -void ValidateGaugeBucketTimes(const GaugeBucketInfo& gaugeBucket, int64_t startTimeNs, - int64_t endTimeNs, vector eventTimesNs) { - EXPECT_EQ(gaugeBucket.start_bucket_elapsed_nanos(), startTimeNs); - EXPECT_EQ(gaugeBucket.end_bucket_elapsed_nanos(), endTimeNs); - EXPECT_EQ(gaugeBucket.elapsed_timestamp_nanos_size(), eventTimesNs.size()); - for (int i = 0; i < eventTimesNs.size(); i++) { - EXPECT_EQ(gaugeBucket.elapsed_timestamp_nanos(i), eventTimesNs[i]); - } -} - -void ValidateValueBucket(const ValueBucketInfo& bucket, int64_t startTimeNs, int64_t endTimeNs, - int64_t value, int64_t conditionTrueNs) { - EXPECT_EQ(bucket.start_bucket_elapsed_nanos(), startTimeNs); - EXPECT_EQ(bucket.end_bucket_elapsed_nanos(), endTimeNs); - ASSERT_EQ(bucket.values_size(), 1); - if (bucket.values(0).has_value_double()) { - EXPECT_EQ((int64_t)bucket.values(0).value_double(), value); - } else { - EXPECT_EQ(bucket.values(0).value_long(), value); - } - if (conditionTrueNs > 0) { - EXPECT_EQ(bucket.condition_true_nanos(), conditionTrueNs); - } -} - -bool EqualsTo(const DimensionsValue& s1, const DimensionsValue& s2) { - if (s1.field() != s2.field()) { - return false; - } - if (s1.value_case() != s2.value_case()) { - return false; - } - switch (s1.value_case()) { - case DimensionsValue::ValueCase::kValueStr: - return (s1.value_str() == s2.value_str()); - case DimensionsValue::ValueCase::kValueInt: - return s1.value_int() == s2.value_int(); - case DimensionsValue::ValueCase::kValueLong: - return s1.value_long() == s2.value_long(); - case DimensionsValue::ValueCase::kValueBool: - return s1.value_bool() == s2.value_bool(); - case DimensionsValue::ValueCase::kValueFloat: - return s1.value_float() == s2.value_float(); - case DimensionsValue::ValueCase::kValueTuple: { - if (s1.value_tuple().dimensions_value_size() != - s2.value_tuple().dimensions_value_size()) { - return false; - } - bool allMatched = true; - for (int i = 0; allMatched && i < s1.value_tuple().dimensions_value_size(); ++i) { - allMatched &= EqualsTo(s1.value_tuple().dimensions_value(i), - s2.value_tuple().dimensions_value(i)); - } - return allMatched; - } - case DimensionsValue::ValueCase::VALUE_NOT_SET: - default: - return true; - } -} - -bool LessThan(const google::protobuf::RepeatedPtrField& s1, - const google::protobuf::RepeatedPtrField& s2) { - if (s1.size() != s2.size()) { - return s1.size() < s2.size(); - } - for (int i = 0; i < s1.size(); i++) { - const StateValue& state1 = s1[i]; - const StateValue& state2 = s2[i]; - if (state1.atom_id() != state2.atom_id()) { - return state1.atom_id() < state2.atom_id(); - } - if (state1.value() != state2.value()) { - return state1.value() < state2.value(); - } - if (state1.group_id() != state2.group_id()) { - return state1.group_id() < state2.group_id(); - } - } - return false; -} - -bool LessThan(const DimensionsValue& s1, const DimensionsValue& s2) { - if (s1.field() != s2.field()) { - return s1.field() < s2.field(); - } - if (s1.value_case() != s2.value_case()) { - return s1.value_case() < s2.value_case(); - } - switch (s1.value_case()) { - case DimensionsValue::ValueCase::kValueStr: - return s1.value_str() < s2.value_str(); - case DimensionsValue::ValueCase::kValueInt: - return s1.value_int() < s2.value_int(); - case DimensionsValue::ValueCase::kValueLong: - return s1.value_long() < s2.value_long(); - case DimensionsValue::ValueCase::kValueBool: - return (int)s1.value_bool() < (int)s2.value_bool(); - case DimensionsValue::ValueCase::kValueFloat: - return s1.value_float() < s2.value_float(); - case DimensionsValue::ValueCase::kValueTuple: { - if (s1.value_tuple().dimensions_value_size() != - s2.value_tuple().dimensions_value_size()) { - return s1.value_tuple().dimensions_value_size() < - s2.value_tuple().dimensions_value_size(); - } - for (int i = 0; i < s1.value_tuple().dimensions_value_size(); ++i) { - if (EqualsTo(s1.value_tuple().dimensions_value(i), - s2.value_tuple().dimensions_value(i))) { - continue; - } else { - return LessThan(s1.value_tuple().dimensions_value(i), - s2.value_tuple().dimensions_value(i)); - } - } - return false; - } - case DimensionsValue::ValueCase::VALUE_NOT_SET: - default: - return false; - } -} - -bool LessThan(const DimensionsPair& s1, const DimensionsPair& s2) { - if (LessThan(s1.dimInWhat, s2.dimInWhat)) { - return true; - } else if (LessThan(s2.dimInWhat, s1.dimInWhat)) { - return false; - } - - return LessThan(s1.stateValues, s2.stateValues); -} - -void backfillStringInDimension(const std::map& str_map, - DimensionsValue* dimension) { - if (dimension->has_value_str_hash()) { - auto it = str_map.find((uint64_t)(dimension->value_str_hash())); - if (it != str_map.end()) { - dimension->clear_value_str_hash(); - dimension->set_value_str(it->second); - } else { - ALOGE("Can not find the string hash: %llu", - (unsigned long long)dimension->value_str_hash()); - } - } else if (dimension->has_value_tuple()) { - auto value_tuple = dimension->mutable_value_tuple(); - for (int i = 0; i < value_tuple->dimensions_value_size(); ++i) { - backfillStringInDimension(str_map, value_tuple->mutable_dimensions_value(i)); - } - } -} - -void backfillStringInReport(ConfigMetricsReport *config_report) { - std::map str_map; - for (const auto& str : config_report->strings()) { - uint64_t hash = Hash64(str); - if (str_map.find(hash) != str_map.end()) { - ALOGE("String hash conflicts: %s %s", str.c_str(), str_map[hash].c_str()); - } - str_map[hash] = str; - } - for (int i = 0; i < config_report->metrics_size(); ++i) { - auto metric_report = config_report->mutable_metrics(i); - if (metric_report->has_count_metrics()) { - backfillStringInDimension(str_map, metric_report->mutable_count_metrics()); - } else if (metric_report->has_duration_metrics()) { - backfillStringInDimension(str_map, metric_report->mutable_duration_metrics()); - } else if (metric_report->has_gauge_metrics()) { - backfillStringInDimension(str_map, metric_report->mutable_gauge_metrics()); - } else if (metric_report->has_value_metrics()) { - backfillStringInDimension(str_map, metric_report->mutable_value_metrics()); - } - } - // Backfill the package names. - for (int i = 0 ; i < config_report->uid_map().snapshots_size(); ++i) { - auto snapshot = config_report->mutable_uid_map()->mutable_snapshots(i); - for (int j = 0 ; j < snapshot->package_info_size(); ++j) { - auto package_info = snapshot->mutable_package_info(j); - if (package_info->has_name_hash()) { - auto it = str_map.find((uint64_t)(package_info->name_hash())); - if (it != str_map.end()) { - package_info->clear_name_hash(); - package_info->set_name(it->second); - } else { - ALOGE("Can not find the string package name hash: %llu", - (unsigned long long)package_info->name_hash()); - } - - } - } - } - // Backfill the app name in app changes. - for (int i = 0 ; i < config_report->uid_map().changes_size(); ++i) { - auto change = config_report->mutable_uid_map()->mutable_changes(i); - if (change->has_app_hash()) { - auto it = str_map.find((uint64_t)(change->app_hash())); - if (it != str_map.end()) { - change->clear_app_hash(); - change->set_app(it->second); - } else { - ALOGE("Can not find the string change app name hash: %llu", - (unsigned long long)change->app_hash()); - } - } - } -} - -void backfillStringInReport(ConfigMetricsReportList *config_report_list) { - for (int i = 0; i < config_report_list->reports_size(); ++i) { - backfillStringInReport(config_report_list->mutable_reports(i)); - } -} - -bool backfillDimensionPath(const DimensionsValue& path, - const google::protobuf::RepeatedPtrField& leafValues, - int* leafIndex, - DimensionsValue* dimension) { - dimension->set_field(path.field()); - if (path.has_value_tuple()) { - for (int i = 0; i < path.value_tuple().dimensions_value_size(); ++i) { - if (!backfillDimensionPath( - path.value_tuple().dimensions_value(i), leafValues, leafIndex, - dimension->mutable_value_tuple()->add_dimensions_value())) { - return false; - } - } - } else { - if (*leafIndex < 0 || *leafIndex >= leafValues.size()) { - return false; - } - dimension->MergeFrom(leafValues.Get(*leafIndex)); - (*leafIndex)++; - } - return true; -} - -bool backfillDimensionPath(const DimensionsValue& path, - const google::protobuf::RepeatedPtrField& leafValues, - DimensionsValue* dimension) { - int leafIndex = 0; - return backfillDimensionPath(path, leafValues, &leafIndex, dimension); -} - -void backfillDimensionPath(ConfigMetricsReportList *config_report_list) { - for (int i = 0; i < config_report_list->reports_size(); ++i) { - auto report = config_report_list->mutable_reports(i); - for (int j = 0; j < report->metrics_size(); ++j) { - auto metric_report = report->mutable_metrics(j); - if (metric_report->has_dimensions_path_in_what() || - metric_report->has_dimensions_path_in_condition()) { - auto whatPath = metric_report->dimensions_path_in_what(); - auto conditionPath = metric_report->dimensions_path_in_condition(); - if (metric_report->has_count_metrics()) { - backfillDimensionPath(whatPath, conditionPath, - metric_report->mutable_count_metrics()); - } else if (metric_report->has_duration_metrics()) { - backfillDimensionPath(whatPath, conditionPath, - metric_report->mutable_duration_metrics()); - } else if (metric_report->has_gauge_metrics()) { - backfillDimensionPath(whatPath, conditionPath, - metric_report->mutable_gauge_metrics()); - } else if (metric_report->has_value_metrics()) { - backfillDimensionPath(whatPath, conditionPath, - metric_report->mutable_value_metrics()); - } - metric_report->clear_dimensions_path_in_what(); - metric_report->clear_dimensions_path_in_condition(); - } - } - } -} - -void backfillStartEndTimestamp(StatsLogReport *report) { - const int64_t timeBaseNs = report->time_base_elapsed_nano_seconds(); - const int64_t bucketSizeNs = report->bucket_size_nano_seconds(); - if (report->has_count_metrics()) { - backfillStartEndTimestampForMetrics( - timeBaseNs, bucketSizeNs, report->mutable_count_metrics()); - } else if (report->has_duration_metrics()) { - backfillStartEndTimestampForMetrics( - timeBaseNs, bucketSizeNs, report->mutable_duration_metrics()); - } else if (report->has_gauge_metrics()) { - backfillStartEndTimestampForMetrics( - timeBaseNs, bucketSizeNs, report->mutable_gauge_metrics()); - if (report->gauge_metrics().skipped_size() > 0) { - backfillStartEndTimestampForSkippedBuckets( - timeBaseNs, report->mutable_gauge_metrics()); - } - } else if (report->has_value_metrics()) { - backfillStartEndTimestampForMetrics( - timeBaseNs, bucketSizeNs, report->mutable_value_metrics()); - if (report->value_metrics().skipped_size() > 0) { - backfillStartEndTimestampForSkippedBuckets( - timeBaseNs, report->mutable_value_metrics()); - } - } -} - -void backfillStartEndTimestamp(ConfigMetricsReport *config_report) { - for (int j = 0; j < config_report->metrics_size(); ++j) { - backfillStartEndTimestamp(config_report->mutable_metrics(j)); - } -} - -void backfillStartEndTimestamp(ConfigMetricsReportList *config_report_list) { - for (int i = 0; i < config_report_list->reports_size(); ++i) { - backfillStartEndTimestamp(config_report_list->mutable_reports(i)); - } -} - -Status FakeSubsystemSleepCallback::onPullAtom(int atomTag, - const shared_ptr& resultReceiver) { - // Convert stats_events into StatsEventParcels. - std::vector parcels; - for (int i = 1; i < 3; i++) { - AStatsEvent* event = AStatsEvent_obtain(); - AStatsEvent_setAtomId(event, atomTag); - std::string subsystemName = "subsystem_name_"; - subsystemName = subsystemName + std::to_string(i); - AStatsEvent_writeString(event, subsystemName.c_str()); - AStatsEvent_writeString(event, "subsystem_subname foo"); - AStatsEvent_writeInt64(event, /*count= */ i); - AStatsEvent_writeInt64(event, /*time_millis= */ pullNum * pullNum * 100 + i); - AStatsEvent_build(event); - size_t size; - uint8_t* buffer = AStatsEvent_getBuffer(event, &size); - - StatsEventParcel p; - // vector.assign() creates a copy, but this is inevitable unless - // stats_event.h/c uses a vector as opposed to a buffer. - p.buffer.assign(buffer, buffer + size); - parcels.push_back(std::move(p)); - AStatsEvent_release(event); - } - pullNum++; - resultReceiver->pullFinished(atomTag, /*success=*/true, parcels); - return Status::ok(); -} - -} // namespace statsd -} // namespace os -} // namespace android diff --git a/bin/tests/statsd_test_util.h b/bin/tests/statsd_test_util.h deleted file mode 100644 index 93d02608..00000000 --- a/bin/tests/statsd_test_util.h +++ /dev/null @@ -1,545 +0,0 @@ -// 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. - -#pragma once - -#include -#include -#include -#include -#include -#include - -#include "packages/modules/StatsD/bin/src/stats_log.pb.h" -#include "packages/modules/StatsD/bin/src/statsd_config.pb.h" -#include "src/StatsLogProcessor.h" -#include "src/hash.h" -#include "src/logd/LogEvent.h" -#include "src/matchers/EventMatcherWizard.h" -#include "src/packages/UidMap.h" -#include "src/stats_log_util.h" -#include "stats_event.h" -#include "statslog_statsdtest.h" - -namespace android { -namespace os { -namespace statsd { - -using namespace testing; -using ::aidl::android::os::BnPullAtomCallback; -using ::aidl::android::os::IPullAtomCallback; -using ::aidl::android::os::IPullAtomResultReceiver; -using android::util::ProtoReader; -using google::protobuf::RepeatedPtrField; -using Status = ::ndk::ScopedAStatus; - -const int SCREEN_STATE_ATOM_ID = util::SCREEN_STATE_CHANGED; -const int UID_PROCESS_STATE_ATOM_ID = util::UID_PROCESS_STATE_CHANGED; - -enum BucketSplitEvent { APP_UPGRADE, BOOT_COMPLETE }; - -class MockUidMap : public UidMap { -public: - MOCK_METHOD(int, getHostUidOrSelf, (int uid), (const)); - MOCK_METHOD(std::set, getAppUid, (const string& package), (const)); -}; - -class MockPendingIntentRef : public aidl::android::os::BnPendingIntentRef { -public: - MOCK_METHOD1(sendDataBroadcast, Status(int64_t lastReportTimeNs)); - MOCK_METHOD1(sendActiveConfigsChangedBroadcast, Status(const vector& configIds)); - MOCK_METHOD6(sendSubscriberBroadcast, - Status(int64_t configUid, int64_t configId, int64_t subscriptionId, - int64_t subscriptionRuleId, const vector& cookies, - const StatsDimensionsValueParcel& dimensionsValueParcel)); -}; - -// Converts a ProtoOutputStream to a StatsLogReport proto. -StatsLogReport outputStreamToProto(ProtoOutputStream* proto); - -// Create AtomMatcher proto to simply match a specific atom type. -AtomMatcher CreateSimpleAtomMatcher(const string& name, int atomId); - -// Create AtomMatcher proto for temperature atom. -AtomMatcher CreateTemperatureAtomMatcher(); - -// Create AtomMatcher proto for scheduled job state changed. -AtomMatcher CreateScheduledJobStateChangedAtomMatcher(); - -// Create AtomMatcher proto for starting a scheduled job. -AtomMatcher CreateStartScheduledJobAtomMatcher(); - -// Create AtomMatcher proto for a scheduled job is done. -AtomMatcher CreateFinishScheduledJobAtomMatcher(); - -// 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 battery state none mode. -AtomMatcher CreateBatteryStateNoneMatcher(); - -// Create AtomMatcher proto for battery state usb mode. -AtomMatcher CreateBatteryStateUsbMatcher(); - -// Create AtomMatcher proto for process state changed. -AtomMatcher CreateUidProcessStateChangedAtomMatcher(); - -// Create AtomMatcher proto for acquiring wakelock. -AtomMatcher CreateAcquireWakelockAtomMatcher(); - -// Create AtomMatcher proto for releasing wakelock. -AtomMatcher CreateReleaseWakelockAtomMatcher() ; - -// Create AtomMatcher proto for screen turned on. -AtomMatcher CreateScreenTurnedOnAtomMatcher(); - -// Create AtomMatcher proto for screen turned off. -AtomMatcher CreateScreenTurnedOffAtomMatcher(); - -// Create AtomMatcher proto for app sync turned on. -AtomMatcher CreateSyncStartAtomMatcher(); - -// Create AtomMatcher proto for app sync turned off. -AtomMatcher CreateSyncEndAtomMatcher(); - -// Create AtomMatcher proto for app sync moves to background. -AtomMatcher CreateMoveToBackgroundAtomMatcher(); - -// Create AtomMatcher proto for app sync moves to foreground. -AtomMatcher CreateMoveToForegroundAtomMatcher(); - -// Create AtomMatcher proto for process crashes -AtomMatcher CreateProcessCrashAtomMatcher() ; - -// Add an AtomMatcher to a combination AtomMatcher. -void addMatcherToMatcherCombination(const AtomMatcher& matcher, AtomMatcher* combinationMatcher); - -// Create Predicate proto for screen is on. -Predicate CreateScreenIsOnPredicate(); - -// Create Predicate proto for screen is off. -Predicate CreateScreenIsOffPredicate(); - -// Create Predicate proto for a running scheduled job. -Predicate CreateScheduledJobPredicate(); - -// Create Predicate proto for battery saver mode. -Predicate CreateBatterySaverModePredicate(); - -// Create Predicate proto for device unplogged mode. -Predicate CreateDeviceUnpluggedPredicate(); - -// Create Predicate proto for holding wakelock. -Predicate CreateHoldingWakelockPredicate(); - -// Create a Predicate proto for app syncing. -Predicate CreateIsSyncingPredicate(); - -// Create a Predicate proto for app is in background. -Predicate CreateIsInBackgroundPredicate(); - -// Create State proto for screen state atom. -State CreateScreenState(); - -// Create State proto for uid process state atom. -State CreateUidProcessState(); - -// Create State proto for overlay state atom. -State CreateOverlayState(); - -// Create State proto for screen state atom with on/off map. -State CreateScreenStateWithOnOffMap(int64_t screenOnId, int64_t screenOffId); - -// Create State proto for screen state atom with simple on/off map. -State CreateScreenStateWithSimpleOnOffMap(int64_t screenOnId, int64_t screenOffId); - -// Create StateGroup proto for ScreenState ON group -StateMap_StateGroup CreateScreenStateOnGroup(int64_t screenOnId); - -// Create StateGroup proto for ScreenState OFF group -StateMap_StateGroup CreateScreenStateOffGroup(int64_t screenOffId); - -// Create StateGroup proto for simple ScreenState ON group -StateMap_StateGroup CreateScreenStateSimpleOnGroup(int64_t screenOnId); - -// Create StateGroup proto for simple ScreenState OFF group -StateMap_StateGroup CreateScreenStateSimpleOffGroup(int64_t screenOffId); - -// Create StateMap proto for ScreenState ON/OFF map -StateMap CreateScreenStateOnOffMap(int64_t screenOnId, int64_t screenOffId); - -// Create StateMap proto for simple ScreenState ON/OFF map -StateMap CreateScreenStateSimpleOnOffMap(int64_t screenOnId, int64_t screenOffId); - -// Add a predicate to the predicate combination. -void addPredicateToPredicateCombination(const Predicate& predicate, Predicate* combination); - -// Create dimensions from primitive fields. -FieldMatcher CreateDimensions(const int atomId, const std::vector& fields); - -// Create dimensions by attribution uid and tag. -FieldMatcher CreateAttributionUidAndTagDimensions(const int atomId, - const std::vector& positions); - -// Create dimensions by attribution uid only. -FieldMatcher CreateAttributionUidDimensions(const int atomId, - const std::vector& positions); - -FieldMatcher CreateAttributionUidAndOtherDimensions(const int atomId, - const std::vector& positions, - const std::vector& fields); - -EventMetric createEventMetric(const string& name, const int64_t what, - const optional& condition); - -CountMetric createCountMetric(const string& name, const int64_t what, - const optional& condition, const vector& states); - -DurationMetric createDurationMetric(const string& name, const int64_t what, - const optional& condition, - const vector& states); - -GaugeMetric createGaugeMetric(const string& name, const int64_t what, - const GaugeMetric::SamplingType samplingType, - const optional& condition, - const optional& triggerEvent); - -ValueMetric createValueMetric(const string& name, const AtomMatcher& what, const int valueField, - const optional& condition, const vector& states); - -Alert createAlert(const string& name, const int64_t metricId, const int buckets, - const int64_t triggerSum); - -Alarm createAlarm(const string& name, const int64_t offsetMillis, const int64_t periodMillis); - -Subscription createSubscription(const string& name, const Subscription_RuleType type, - const int64_t ruleId); - -// START: get primary key functions -// These functions take in atom field information and create FieldValues which are stored in the -// given HashableDimensionKey. -void getUidProcessKey(int uid, HashableDimensionKey* key); - -void getOverlayKey(int uid, string packageName, HashableDimensionKey* key); - -void getPartialWakelockKey(int uid, const std::string& tag, HashableDimensionKey* key); - -void getPartialWakelockKey(int uid, HashableDimensionKey* key); -// END: get primary key functions - -void writeAttribution(AStatsEvent* statsEvent, const vector& attributionUids, - const vector& attributionTags); - -// Builds statsEvent to get buffer that is parsed into logEvent then releases statsEvent. -void parseStatsEventToLogEvent(AStatsEvent* statsEvent, LogEvent* logEvent); - -shared_ptr CreateTwoValueLogEvent(int atomId, int64_t eventTimeNs, int32_t value1, - int32_t value2); - -void CreateTwoValueLogEvent(LogEvent* logEvent, int atomId, int64_t eventTimeNs, int32_t value1, - int32_t value2); - -shared_ptr CreateThreeValueLogEvent(int atomId, int64_t eventTimeNs, int32_t value1, - int32_t value2, int32_t value3); - -void CreateThreeValueLogEvent(LogEvent* logEvent, int atomId, int64_t eventTimeNs, int32_t value1, - int32_t value2, int32_t value3); - -// The repeated value log event helpers create a log event with two int fields, both -// set to the same value. This is useful for testing metrics that are only interested -// in the value of the second field but still need the first field to be populated. -std::shared_ptr CreateRepeatedValueLogEvent(int atomId, int64_t eventTimeNs, - int32_t value); - -void CreateRepeatedValueLogEvent(LogEvent* logEvent, int atomId, int64_t eventTimeNs, - int32_t value); - -std::shared_ptr CreateNoValuesLogEvent(int atomId, int64_t eventTimeNs); - -void CreateNoValuesLogEvent(LogEvent* logEvent, int atomId, int64_t eventTimeNs); - -std::shared_ptr makeUidLogEvent(int atomId, int64_t eventTimeNs, int uid, int data1, - int data2); - -std::shared_ptr makeAttributionLogEvent(int atomId, int64_t eventTimeNs, - const vector& uids, - const vector& tags, int data1, int data2); - -sp makeMockUidMapForOneHost(int hostUid, const vector& isolatedUids); - -sp makeMockUidMapForPackage(const string& pkg, const set& uids); - -// Create log event for screen state changed. -std::unique_ptr CreateScreenStateChangedEvent(uint64_t timestampNs, - const android::view::DisplayStateEnum state, - int loggerUid = 0); - -// Create log event for screen brightness state changed. -std::unique_ptr CreateScreenBrightnessChangedEvent(uint64_t timestampNs, int level); - -// Create log event when scheduled job starts. -std::unique_ptr CreateStartScheduledJobEvent(uint64_t timestampNs, - const vector& attributionUids, - const vector& attributionTags, - const string& jobName); - -// Create log event when scheduled job finishes. -std::unique_ptr CreateFinishScheduledJobEvent(uint64_t timestampNs, - const vector& attributionUids, - const vector& attributionTags, - const string& jobName); - -// 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 when battery state changes. -std::unique_ptr CreateBatteryStateChangedEvent(const uint64_t timestampNs, const BatteryPluggedStateEnum state); - -// Create log event for app moving to background. -std::unique_ptr CreateMoveToBackgroundEvent(uint64_t timestampNs, const int uid); - -// Create log event for app moving to foreground. -std::unique_ptr CreateMoveToForegroundEvent(uint64_t timestampNs, const int uid); - -// Create log event when the app sync starts. -std::unique_ptr CreateSyncStartEvent(uint64_t timestampNs, const vector& uids, - const vector& tags, const string& name); - -// Create log event when the app sync ends. -std::unique_ptr CreateSyncEndEvent(uint64_t timestampNs, const vector& uids, - const vector& tags, const string& name); - -// Create log event when the app sync ends. -std::unique_ptr CreateAppCrashEvent(uint64_t timestampNs, const int uid); - -// Create log event for an app crash. -std::unique_ptr CreateAppCrashOccurredEvent(uint64_t timestampNs, const int uid); - -// Create log event for acquiring wakelock. -std::unique_ptr CreateAcquireWakelockEvent(uint64_t timestampNs, const vector& uids, - const vector& tags, - const string& wakelockName); - -// Create log event for releasing wakelock. -std::unique_ptr CreateReleaseWakelockEvent(uint64_t timestampNs, const vector& uids, - const vector& tags, - const string& wakelockName); - -// Create log event for releasing wakelock. -std::unique_ptr CreateIsolatedUidChangedEvent(uint64_t timestampNs, int hostUid, - int isolatedUid, bool is_create); - -// Create log event for uid process state change. -std::unique_ptr CreateUidProcessStateChangedEvent( - uint64_t timestampNs, int uid, const android::app::ProcessStateEnum state); - -std::unique_ptr CreateBleScanStateChangedEvent(uint64_t timestampNs, - const vector& attributionUids, - const vector& attributionTags, - const BleScanStateChanged::State state, - const bool filtered, const bool firstMatch, - const bool opportunistic); - -std::unique_ptr CreateOverlayStateChangedEvent(int64_t timestampNs, const int32_t uid, - const string& packageName, - const bool usingAlertWindow, - const OverlayStateChanged::State state); - -std::unique_ptr CreateAppStartOccurredEvent( - uint64_t timestampNs, const int uid, const string& pkg_name, - AppStartOccurred::TransitionType type, const string& activity_name, - const string& calling_pkg_name, const bool is_instant_app, int64_t activity_start_msec); - -// Create a statsd log event processor upon the start time in seconds, config and key. -sp CreateStatsLogProcessor(const int64_t timeBaseNs, const int64_t currentTimeNs, - const StatsdConfig& config, const ConfigKey& key, - const shared_ptr& puller = nullptr, - const int32_t atomTag = 0 /*for puller only*/, - const sp = new UidMap()); - -// Util function to sort the log events by timestamp. -void sortLogEventsByTimestamp(std::vector> *events); - -int64_t StringToId(const string& str); - -sp createEventMatcherWizard( - int tagId, int matcherIndex, const std::vector& fieldValueMatchers = {}); - -StatsDimensionsValueParcel CreateAttributionUidDimensionsValueParcel(const int atomId, - const int uid); - -void ValidateUidDimension(const DimensionsValue& value, int atomId, int uid); -void ValidateWakelockAttributionUidAndTagDimension(const DimensionsValue& value, const int atomId, - const int uid, const string& tag); -void ValidateUidDimension(const DimensionsValue& value, int node_idx, int atomId, int uid); -void ValidateAttributionUidDimension(const DimensionsValue& value, int atomId, int uid); -void ValidateAttributionUidAndTagDimension( - const DimensionsValue& value, int atomId, int uid, const std::string& tag); -void ValidateAttributionUidAndTagDimension( - const DimensionsValue& value, int node_idx, int atomId, int uid, const std::string& tag); -void ValidateStateValue(const google::protobuf::RepeatedPtrField& stateValues, - int atomId, int64_t value); - -void ValidateCountBucket(const CountBucketInfo& countBucket, int64_t startTimeNs, int64_t endTimeNs, - int64_t count); -void ValidateDurationBucket(const DurationBucketInfo& bucket, int64_t startTimeNs, - int64_t endTimeNs, int64_t durationNs); -void ValidateGaugeBucketTimes(const GaugeBucketInfo& gaugeBucket, int64_t startTimeNs, - int64_t endTimeNs, vector eventTimesNs); -void ValidateValueBucket(const ValueBucketInfo& bucket, int64_t startTimeNs, int64_t endTimeNs, - int64_t value, int64_t conditionTrueNs); - -struct DimensionsPair { - DimensionsPair(DimensionsValue m1, google::protobuf::RepeatedPtrField m2) - : dimInWhat(m1), stateValues(m2){}; - - DimensionsValue dimInWhat; - google::protobuf::RepeatedPtrField stateValues; -}; - -bool LessThan(const StateValue& s1, const StateValue& s2); -bool LessThan(const DimensionsValue& s1, const DimensionsValue& s2); -bool LessThan(const DimensionsPair& s1, const DimensionsPair& s2); - - -void backfillStartEndTimestamp(ConfigMetricsReport *config_report); -void backfillStartEndTimestamp(ConfigMetricsReportList *config_report_list); - -void backfillStringInReport(ConfigMetricsReportList *config_report_list); -void backfillStringInDimension(const std::map& str_map, - DimensionsValue* dimension); - -template -void backfillStringInDimension(const std::map& str_map, - T* metrics) { - for (int i = 0; i < metrics->data_size(); ++i) { - auto data = metrics->mutable_data(i); - if (data->has_dimensions_in_what()) { - backfillStringInDimension(str_map, data->mutable_dimensions_in_what()); - } - if (data->has_dimensions_in_condition()) { - backfillStringInDimension(str_map, data->mutable_dimensions_in_condition()); - } - } -} - -void backfillDimensionPath(ConfigMetricsReportList* config_report_list); - -bool backfillDimensionPath(const DimensionsValue& path, - const google::protobuf::RepeatedPtrField& leafValues, - DimensionsValue* dimension); - -class FakeSubsystemSleepCallback : public BnPullAtomCallback { -public: - // Track the number of pulls. - int pullNum = 1; - Status onPullAtom(int atomTag, - const shared_ptr& resultReceiver) override; -}; - -template -void backfillDimensionPath(const DimensionsValue& whatPath, - const DimensionsValue& conditionPath, - T* metricData) { - for (int i = 0; i < metricData->data_size(); ++i) { - auto data = metricData->mutable_data(i); - if (data->dimension_leaf_values_in_what_size() > 0) { - backfillDimensionPath(whatPath, data->dimension_leaf_values_in_what(), - data->mutable_dimensions_in_what()); - data->clear_dimension_leaf_values_in_what(); - } - if (data->dimension_leaf_values_in_condition_size() > 0) { - backfillDimensionPath(conditionPath, data->dimension_leaf_values_in_condition(), - data->mutable_dimensions_in_condition()); - data->clear_dimension_leaf_values_in_condition(); - } - } -} - -struct DimensionCompare { - bool operator()(const DimensionsPair& s1, const DimensionsPair& s2) const { - return LessThan(s1, s2); - } -}; - -template -void sortMetricDataByDimensionsValue(const T& metricData, T* sortedMetricData) { - std::map dimensionIndexMap; - for (int i = 0; i < metricData.data_size(); ++i) { - dimensionIndexMap.insert( - std::make_pair(DimensionsPair(metricData.data(i).dimensions_in_what(), - metricData.data(i).slice_by_state()), - i)); - } - for (const auto& itr : dimensionIndexMap) { - *sortedMetricData->add_data() = metricData.data(itr.second); - } -} - -template -void backfillStartEndTimestampForFullBucket( - const int64_t timeBaseNs, const int64_t bucketSizeNs, T* bucket) { - bucket->set_start_bucket_elapsed_nanos(timeBaseNs + bucketSizeNs * bucket->bucket_num()); - bucket->set_end_bucket_elapsed_nanos( - timeBaseNs + bucketSizeNs * bucket->bucket_num() + bucketSizeNs); - bucket->clear_bucket_num(); -} - -template -void backfillStartEndTimestampForPartialBucket(const int64_t timeBaseNs, T* bucket) { - if (bucket->has_start_bucket_elapsed_millis()) { - bucket->set_start_bucket_elapsed_nanos( - MillisToNano(bucket->start_bucket_elapsed_millis())); - bucket->clear_start_bucket_elapsed_millis(); - } - if (bucket->has_end_bucket_elapsed_millis()) { - bucket->set_end_bucket_elapsed_nanos( - MillisToNano(bucket->end_bucket_elapsed_millis())); - bucket->clear_end_bucket_elapsed_millis(); - } -} - -template -void backfillStartEndTimestampForMetrics(const int64_t timeBaseNs, const int64_t bucketSizeNs, - T* metrics) { - for (int i = 0; i < metrics->data_size(); ++i) { - auto data = metrics->mutable_data(i); - for (int j = 0; j < data->bucket_info_size(); ++j) { - auto bucket = data->mutable_bucket_info(j); - if (bucket->has_bucket_num()) { - backfillStartEndTimestampForFullBucket(timeBaseNs, bucketSizeNs, bucket); - } else { - backfillStartEndTimestampForPartialBucket(timeBaseNs, bucket); - } - } - } -} - -template -void backfillStartEndTimestampForSkippedBuckets(const int64_t timeBaseNs, T* metrics) { - for (int i = 0; i < metrics->skipped_size(); ++i) { - backfillStartEndTimestampForPartialBucket(timeBaseNs, metrics->mutable_skipped(i)); - } -} -} // namespace statsd -} // namespace os -} // namespace android diff --git a/bin/tests/storage/StorageManager_test.cpp b/bin/tests/storage/StorageManager_test.cpp deleted file mode 100644 index 74eafbf5..00000000 --- a/bin/tests/storage/StorageManager_test.cpp +++ /dev/null @@ -1,204 +0,0 @@ -// Copyright (C) 2019 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 -#include -#include -#include "src/storage/StorageManager.h" - -#ifdef __ANDROID__ - -namespace android { -namespace os { -namespace statsd { - -using namespace testing; -using std::make_shared; -using std::shared_ptr; -using std::vector; -using testing::Contains; - -TEST(StorageManagerTest, TrainInfoReadWriteTest) { - InstallTrainInfo trainInfo; - trainInfo.trainVersionCode = 12345; - trainInfo.trainName = "This is a train name #)$(&&$"; - trainInfo.status = 1; - const char* expIds = "test_ids"; - trainInfo.experimentIds.assign(expIds, expIds + strlen(expIds)); - - bool result; - - result = StorageManager::writeTrainInfo(trainInfo); - - EXPECT_TRUE(result); - - InstallTrainInfo trainInfoResult; - result = StorageManager::readTrainInfo(trainInfo.trainName, trainInfoResult); - EXPECT_TRUE(result); - - EXPECT_EQ(trainInfo.trainVersionCode, trainInfoResult.trainVersionCode); - ASSERT_EQ(trainInfo.trainName.size(), trainInfoResult.trainName.size()); - EXPECT_EQ(trainInfo.trainName, trainInfoResult.trainName); - EXPECT_EQ(trainInfo.status, trainInfoResult.status); - ASSERT_EQ(trainInfo.experimentIds.size(), trainInfoResult.experimentIds.size()); - EXPECT_EQ(trainInfo.experimentIds, trainInfoResult.experimentIds); -} - -TEST(StorageManagerTest, TrainInfoReadWriteTrainNameSizeOneTest) { - InstallTrainInfo trainInfo; - trainInfo.trainVersionCode = 12345; - trainInfo.trainName = "{"; - trainInfo.status = 1; - const char* expIds = "test_ids"; - trainInfo.experimentIds.assign(expIds, expIds + strlen(expIds)); - - bool result; - - result = StorageManager::writeTrainInfo(trainInfo); - - EXPECT_TRUE(result); - - InstallTrainInfo trainInfoResult; - result = StorageManager::readTrainInfo(trainInfo.trainName, trainInfoResult); - EXPECT_TRUE(result); - - EXPECT_EQ(trainInfo.trainVersionCode, trainInfoResult.trainVersionCode); - ASSERT_EQ(trainInfo.trainName.size(), trainInfoResult.trainName.size()); - EXPECT_EQ(trainInfo.trainName, trainInfoResult.trainName); - EXPECT_EQ(trainInfo.status, trainInfoResult.status); - ASSERT_EQ(trainInfo.experimentIds.size(), trainInfoResult.experimentIds.size()); - EXPECT_EQ(trainInfo.experimentIds, trainInfoResult.experimentIds); -} - -TEST(StorageManagerTest, SortFileTest) { - vector list; - // assume now sec is 500 - list.emplace_back("200_5000_123454", false, 20, 300); - list.emplace_back("300_2000_123454_history", true, 30, 200); - list.emplace_back("400_100009_123454_history", true, 40, 100); - list.emplace_back("100_2000_123454", false, 50, 400); - - StorageManager::sortFiles(&list); - EXPECT_EQ("200_5000_123454", list[0].mFileName); - EXPECT_EQ("100_2000_123454", list[1].mFileName); - EXPECT_EQ("400_100009_123454_history", list[2].mFileName); - EXPECT_EQ("300_2000_123454_history", list[3].mFileName); -} - -const string testDir = "/data/misc/stats-data/"; -const string file1 = testDir + "2557169347_1066_1"; -const string file2 = testDir + "2557169349_1066_1"; -const string file1_history = file1 + "_history"; -const string file2_history = file2 + "_history"; - -bool prepareLocalHistoryTestFiles() { - android::base::unique_fd fd(TEMP_FAILURE_RETRY( - open(file1.c_str(), O_WRONLY | O_CREAT | O_CLOEXEC, S_IRUSR | S_IWUSR))); - if (fd != -1) { - dprintf(fd, "content"); - } else { - return false; - } - - android::base::unique_fd fd2(TEMP_FAILURE_RETRY( - open(file2.c_str(), O_WRONLY | O_CREAT | O_CLOEXEC, S_IRUSR | S_IWUSR))); - if (fd2 != -1) { - dprintf(fd2, "content"); - } else { - return false; - } - return true; -} - -void clearLocalHistoryTestFiles() { - TEMP_FAILURE_RETRY(remove(file1.c_str())); - TEMP_FAILURE_RETRY(remove(file2.c_str())); - TEMP_FAILURE_RETRY(remove(file1_history.c_str())); - TEMP_FAILURE_RETRY(remove(file2_history.c_str())); -} - -bool fileExist(string name) { - android::base::unique_fd fd(TEMP_FAILURE_RETRY(open(name.c_str(), O_RDONLY | O_CLOEXEC))); - return fd != -1; -} - -/* The following AppendConfigReportTests test the 4 combinations of [whether erase data] [whether - * the caller is adb] */ -TEST(StorageManagerTest, AppendConfigReportTest1) { - EXPECT_TRUE(prepareLocalHistoryTestFiles()); - - ProtoOutputStream out; - StorageManager::appendConfigMetricsReport(ConfigKey(1066, 1), &out, false /*erase?*/, - false /*isAdb?*/); - - EXPECT_FALSE(fileExist(file1)); - EXPECT_FALSE(fileExist(file2)); - - EXPECT_TRUE(fileExist(file1_history)); - EXPECT_TRUE(fileExist(file2_history)); - clearLocalHistoryTestFiles(); -} - -TEST(StorageManagerTest, AppendConfigReportTest2) { - EXPECT_TRUE(prepareLocalHistoryTestFiles()); - - ProtoOutputStream out; - StorageManager::appendConfigMetricsReport(ConfigKey(1066, 1), &out, true /*erase?*/, - false /*isAdb?*/); - - EXPECT_FALSE(fileExist(file1)); - EXPECT_FALSE(fileExist(file2)); - EXPECT_FALSE(fileExist(file1_history)); - EXPECT_FALSE(fileExist(file2_history)); - - clearLocalHistoryTestFiles(); -} - -TEST(StorageManagerTest, AppendConfigReportTest3) { - EXPECT_TRUE(prepareLocalHistoryTestFiles()); - - ProtoOutputStream out; - StorageManager::appendConfigMetricsReport(ConfigKey(1066, 1), &out, false /*erase?*/, - true /*isAdb?*/); - - EXPECT_TRUE(fileExist(file1)); - EXPECT_TRUE(fileExist(file2)); - EXPECT_FALSE(fileExist(file1_history)); - EXPECT_FALSE(fileExist(file2_history)); - - clearLocalHistoryTestFiles(); -} - -TEST(StorageManagerTest, AppendConfigReportTest4) { - EXPECT_TRUE(prepareLocalHistoryTestFiles()); - - ProtoOutputStream out; - StorageManager::appendConfigMetricsReport(ConfigKey(1066, 1), &out, true /*erase?*/, - true /*isAdb?*/); - - EXPECT_FALSE(fileExist(file1)); - EXPECT_FALSE(fileExist(file2)); - EXPECT_FALSE(fileExist(file1_history)); - EXPECT_FALSE(fileExist(file2_history)); - - clearLocalHistoryTestFiles(); -} - -} // namespace statsd -} // namespace os -} // namespace android -#else -GTEST_LOG_(INFO) << "This test does nothing.\n"; -#endif diff --git a/bin/tests/subscriber/SubscriberReporter_test.cpp b/bin/tests/subscriber/SubscriberReporter_test.cpp deleted file mode 100644 index c6a888ff..00000000 --- a/bin/tests/subscriber/SubscriberReporter_test.cpp +++ /dev/null @@ -1,107 +0,0 @@ -// Copyright (C) 2020 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 "subscriber/SubscriberReporter.h" - -#include -#include -#include - -#include "tests/statsd_test_util.h" - -using namespace testing; -using ::ndk::SharedRefBase; -using std::unordered_map; - -#ifdef __ANDROID__ - -namespace android { -namespace os { -namespace statsd { - -namespace { - -const ConfigKey configKey1(0, 12345); -const ConfigKey configKey2(0, 123456); -const int64_t subscriptionId1 = 1, subscriptionId2 = 2; - -} // anonymous namespace - -class SubscriberReporterTest : public ::testing::Test { -public: - SubscriberReporterTest() { - } - const shared_ptr pir1 = - SharedRefBase::make>(); - const shared_ptr pir2 = - SharedRefBase::make>(); - const shared_ptr pir3 = - SharedRefBase::make>(); - // Two subscribers on config key 1, one on config key 2. - void SetUp() override { - SubscriberReporter::getInstance().setBroadcastSubscriber(configKey1, subscriptionId1, pir1); - SubscriberReporter::getInstance().setBroadcastSubscriber(configKey1, subscriptionId2, pir2); - SubscriberReporter::getInstance().setBroadcastSubscriber(configKey2, subscriptionId1, pir3); - } - - void TearDown() override { - SubscriberReporter::getInstance(); - SubscriberReporter::getInstance().unsetBroadcastSubscriber(configKey1, subscriptionId1); - SubscriberReporter::getInstance().unsetBroadcastSubscriber(configKey1, subscriptionId2); - SubscriberReporter::getInstance().unsetBroadcastSubscriber(configKey2, subscriptionId1); - EXPECT_EQ(pir1.use_count(), 1); - EXPECT_EQ(pir2.use_count(), 1); - EXPECT_EQ(pir3.use_count(), 1); - ASSERT_TRUE(SubscriberReporter::getInstance().mIntentMap.empty()); - } -}; - -TEST_F(SubscriberReporterTest, TestBroadcastSubscriberDeathRemovesPir) { - SubscriberReporter::broadcastSubscriberDied(pir1.get()); - EXPECT_EQ(pir1.use_count(), 1); - - unordered_map>> - expectedIntentMap = {{configKey1, {{subscriptionId2, pir2}}}, - {configKey2, {{subscriptionId1, pir3}}}}; - EXPECT_THAT(SubscriberReporter::getInstance().mIntentMap, ContainerEq(expectedIntentMap)); -} - -TEST_F(SubscriberReporterTest, TestBroadcastSubscriberDeathRemovesPirAndConfigKey) { - SubscriberReporter::broadcastSubscriberDied(pir3.get()); - - EXPECT_EQ(pir3.use_count(), 1); - unordered_map>> - expectedIntentMap = {{configKey1, {{subscriptionId1, pir1}, {subscriptionId2, pir2}}}}; - EXPECT_THAT(SubscriberReporter::getInstance().mIntentMap, ContainerEq(expectedIntentMap)); -} - -TEST_F(SubscriberReporterTest, TestBroadcastSubscriberDeathKeepsReplacedPir) { - const shared_ptr pir4 = - SharedRefBase::make>(); - SubscriberReporter::getInstance().setBroadcastSubscriber(configKey1, subscriptionId1, pir4); - - // pir1 dies, but pir4 has replaced it with the same keys. The map should remain the same. - SubscriberReporter::broadcastSubscriberDied(pir1.get()); - - unordered_map>> - expectedIntentMap = {{configKey1, {{subscriptionId1, pir4}, {subscriptionId2, pir2}}}, - {configKey2, {{subscriptionId1, pir3}}}}; - EXPECT_THAT(SubscriberReporter::getInstance().mIntentMap, ContainerEq(expectedIntentMap)); -} -} // namespace statsd -} // namespace os -} // namespace android -#else -GTEST_LOG_(INFO) << "This test does nothing.\n"; -#endif diff --git a/bin/tests/utils/MultiConditionTrigger_test.cpp b/bin/tests/utils/MultiConditionTrigger_test.cpp deleted file mode 100644 index 32cecd3b..00000000 --- a/bin/tests/utils/MultiConditionTrigger_test.cpp +++ /dev/null @@ -1,174 +0,0 @@ -/* - * Copyright (C) 2020 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 "utils/MultiConditionTrigger.h" - -#include - -#include -#include -#include -#include - -#ifdef __ANDROID__ - -using namespace std; -using std::this_thread::sleep_for; - -namespace android { -namespace os { -namespace statsd { - -TEST(MultiConditionTrigger, TestMultipleConditions) { - int numConditions = 5; - string t1 = "t1", t2 = "t2", t3 = "t3", t4 = "t4", t5 = "t5"; - set conditionNames = {t1, t2, t3, t4, t5}; - - mutex lock; - condition_variable cv; - bool triggerCalled = false; - - // Mark done as true and notify in the done. - MultiConditionTrigger trigger(conditionNames, [&lock, &cv, &triggerCalled] { - { - lock_guard lg(lock); - triggerCalled = true; - } - cv.notify_all(); - }); - - vector threads; - vector done(numConditions, 0); - - int i = 0; - for (const string& conditionName : conditionNames) { - threads.emplace_back([&done, &conditionName, &trigger, i] { - sleep_for(chrono::milliseconds(3)); - done[i] = 1; - trigger.markComplete(conditionName); - }); - i++; - } - - unique_lock unique_lk(lock); - cv.wait(unique_lk, [&triggerCalled] { - return triggerCalled; - }); - - for (i = 0; i < numConditions; i++) { - EXPECT_EQ(done[i], 1); - } - - for (i = 0; i < numConditions; i++) { - threads[i].join(); - } -} - -TEST(MultiConditionTrigger, TestNoConditions) { - mutex lock; - condition_variable cv; - bool triggerCalled = false; - - MultiConditionTrigger trigger({}, [&lock, &cv, &triggerCalled] { - { - lock_guard lg(lock); - triggerCalled = true; - } - cv.notify_all(); - }); - - unique_lock unique_lk(lock); - cv.wait(unique_lk, [&triggerCalled] { return triggerCalled; }); - EXPECT_TRUE(triggerCalled); - // Ensure that trigger occurs immediately if no events need to be completed. -} - -TEST(MultiConditionTrigger, TestMarkCompleteCalledBySameCondition) { - string t1 = "t1", t2 = "t2"; - set conditionNames = {t1, t2}; - - mutex lock; - condition_variable cv; - bool triggerCalled = false; - - MultiConditionTrigger trigger(conditionNames, [&lock, &cv, &triggerCalled] { - { - lock_guard lg(lock); - triggerCalled = true; - } - cv.notify_all(); - }); - - trigger.markComplete(t1); - trigger.markComplete(t1); - - // Ensure that the trigger still hasn't fired. - { - lock_guard lg(lock); - EXPECT_FALSE(triggerCalled); - } - - trigger.markComplete(t2); - unique_lock unique_lk(lock); - cv.wait(unique_lk, [&triggerCalled] { return triggerCalled; }); - EXPECT_TRUE(triggerCalled); -} - -TEST(MultiConditionTrigger, TestTriggerOnlyCalledOnce) { - string t1 = "t1"; - set conditionNames = {t1}; - - mutex lock; - condition_variable cv; - bool triggerCalled = false; - int triggerCount = 0; - - MultiConditionTrigger trigger(conditionNames, [&lock, &cv, &triggerCalled, &triggerCount] { - { - lock_guard lg(lock); - triggerCount++; - triggerCalled = true; - } - cv.notify_all(); - }); - - trigger.markComplete(t1); - - // Ensure that the trigger fired. - { - unique_lock unique_lk(lock); - cv.wait(unique_lk, [&triggerCalled] { return triggerCalled; }); - EXPECT_TRUE(triggerCalled); - EXPECT_EQ(triggerCount, 1); - triggerCalled = false; - } - - trigger.markComplete(t1); - - // Ensure that the trigger does not fire again. - { - unique_lock unique_lk(lock); - cv.wait_for(unique_lk, chrono::milliseconds(5), [&triggerCalled] { return triggerCalled; }); - EXPECT_FALSE(triggerCalled); - EXPECT_EQ(triggerCount, 1); - } -} - -} // namespace statsd -} // namespace os -} // namespace android -#else -GTEST_LOG_(INFO) << "This test does nothing.\n"; -#endif diff --git a/bin/tools/localtools/Android.bp b/bin/tools/localtools/Android.bp deleted file mode 100644 index 69a43a8f..00000000 --- a/bin/tools/localtools/Android.bp +++ /dev/null @@ -1,46 +0,0 @@ -java_binary_host { - name: "statsd_localdrive", - manifest: "localdrive_manifest.txt", - srcs: [ - "src/com/android/statsd/shelltools/localdrive/*.java", - "src/com/android/statsd/shelltools/Utils.java", - ], - static_libs: [ - "platformprotos", - "guava", - ], -} - -java_library_host { - name: "statsd_testdrive_lib", - srcs: [ - "src/com/android/statsd/shelltools/testdrive/*.java", - "src/com/android/statsd/shelltools/Utils.java", - ], - static_libs: [ - "platformprotos", - "guava", - ], -} - - -java_binary_host { - name: "statsd_testdrive", - manifest: "testdrive_manifest.txt", - static_libs: [ - "statsd_testdrive_lib", - ], -} - -java_test_host { - name: "statsd_testdrive_test", - test_suites: ["general-tests"], - srcs: ["test/com/android/statsd/shelltools/testdrive/*.java"], - static_libs: [ - "statsd_testdrive_lib", - "junit", - "platformprotos", - "guava", - ], -} - diff --git a/bin/tools/localtools/TEST_MAPPING b/bin/tools/localtools/TEST_MAPPING deleted file mode 100644 index 7c8a3db5..00000000 --- a/bin/tools/localtools/TEST_MAPPING +++ /dev/null @@ -1,8 +0,0 @@ -{ - "presubmit": [ - { - "name": "statsd_testdrive_test", - "host": true - } - ] -} diff --git a/bin/tools/localtools/localdrive_manifest.txt b/bin/tools/localtools/localdrive_manifest.txt deleted file mode 100644 index 035cea11..00000000 --- a/bin/tools/localtools/localdrive_manifest.txt +++ /dev/null @@ -1 +0,0 @@ -Main-class: com.android.statsd.shelltools.localdrive.LocalDrive diff --git a/bin/tools/localtools/src/com/android/statsd/shelltools/Utils.java b/bin/tools/localtools/src/com/android/statsd/shelltools/Utils.java deleted file mode 100644 index 6a74480b..00000000 --- a/bin/tools/localtools/src/com/android/statsd/shelltools/Utils.java +++ /dev/null @@ -1,284 +0,0 @@ -/* - * Copyright (C) 2018 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. - */ -package com.android.statsd.shelltools; - -import com.android.os.StatsLog.ConfigMetricsReportList; - -import com.google.common.io.Files; - -import java.io.BufferedReader; -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.io.InputStreamReader; -import java.nio.charset.Charset; -import java.util.ArrayList; -import java.util.List; -import java.util.logging.ConsoleHandler; -import java.util.logging.Formatter; -import java.util.logging.Level; -import java.util.logging.LogRecord; -import java.util.logging.Logger; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -/** - * Utilities for local use of statsd. - */ -public class Utils { - - public static final String CMD_DUMP_REPORT = "cmd stats dump-report"; - public static final String CMD_LOG_APP_BREADCRUMB = "cmd stats log-app-breadcrumb"; - public static final String CMD_REMOVE_CONFIG = "cmd stats config remove"; - public static final String CMD_UPDATE_CONFIG = "cmd stats config update"; - - public static final String SHELL_UID = "2000"; // Use shell, even if rooted. - - /** - * Runs adb shell command with output directed to outputFile if non-null. - */ - public static void runCommand(File outputFile, Logger logger, String... commands) - throws IOException, InterruptedException { - ProcessBuilder pb = new ProcessBuilder(commands); - if (outputFile != null && outputFile.exists() && outputFile.canWrite()) { - pb.redirectOutput(outputFile); - } - Process process = pb.start(); - - // Capture any errors - StringBuilder err = new StringBuilder(); - BufferedReader br = new BufferedReader(new InputStreamReader(process.getErrorStream())); - for (String line = br.readLine(); line != null; line = br.readLine()) { - err.append(line).append('\n'); - } - logger.severe(err.toString()); - - // Check result - if (process.waitFor() == 0) { - logger.fine("Adb command successful."); - } else { - logger.severe("Abnormal adb shell termination for: " + String.join(",", commands)); - throw new RuntimeException("Error running adb command: " + err.toString()); - } - } - - /** - * Dumps the report from the device and converts it to a ConfigMetricsReportList. - * Erases the data if clearData is true. - * @param configId id of the config - * @param clearData whether to erase the report data from statsd after getting the report. - * @param useShellUid Pulls data for the {@link SHELL_UID} instead of the caller's uid. - * @param logger Logger to log error messages - * @return - * @throws IOException - * @throws InterruptedException - */ - public static ConfigMetricsReportList getReportList(long configId, boolean clearData, - boolean useShellUid, Logger logger, String deviceSerial) - throws IOException, InterruptedException { - try { - File outputFile = File.createTempFile("statsdret", ".bin"); - outputFile.deleteOnExit(); - runCommand( - outputFile, - logger, - "adb", - "-s", - deviceSerial, - "shell", - CMD_DUMP_REPORT, - useShellUid ? SHELL_UID : "", - String.valueOf(configId), - clearData ? "" : "--keep_data", - "--include_current_bucket", - "--proto"); - ConfigMetricsReportList reportList = - ConfigMetricsReportList.parseFrom(new FileInputStream(outputFile)); - return reportList; - } catch (com.google.protobuf.InvalidProtocolBufferException e) { - logger.severe("Failed to fetch and parse the statsd output report. " - + "Perhaps there is not a valid statsd config for the requested " - + (useShellUid ? ("uid=" + SHELL_UID + ", ") : "") - + "configId=" + configId - + "."); - throw (e); - } - } - - /** - * Logs an AppBreadcrumbReported atom. - * @param label which label to log for the app breadcrumb atom. - * @param state which state to log for the app breadcrumb atom. - * @param logger Logger to log error messages - * - * @throws IOException - * @throws InterruptedException - */ - public static void logAppBreadcrumb(int label, int state, Logger logger, String deviceSerial) - throws IOException, InterruptedException { - runCommand( - null, - logger, - "adb", - "-s", - deviceSerial, - "shell", - CMD_LOG_APP_BREADCRUMB, - String.valueOf(label), - String.valueOf(state)); - } - public static void setUpLogger(Logger logger, boolean debug) { - ConsoleHandler handler = new ConsoleHandler(); - handler.setFormatter(new LocalToolsFormatter()); - logger.setUseParentHandlers(false); - if (debug) { - handler.setLevel(Level.ALL); - logger.setLevel(Level.ALL); - } - logger.addHandler(handler); - } - - /** - * Attempt to determine whether tool will work with this statsd, i.e. whether statsd is - * minCodename or higher. - * Algorithm: true if (sdk >= minSdk) || (sdk == minSdk-1 && codeName.startsWith(minCodeName)) - * If all else fails, assume it will work (letting future commands deal with any errors). - */ - public static boolean isAcceptableStatsd(Logger logger, int minSdk, String minCodename, - String deviceSerial) { - BufferedReader in = null; - try { - File outFileSdk = File.createTempFile("shelltools_sdk", "tmp"); - outFileSdk.deleteOnExit(); - runCommand(outFileSdk, logger, - "adb", "-s", deviceSerial, "shell", "getprop", "ro.build.version.sdk"); - in = new BufferedReader(new InputStreamReader(new FileInputStream(outFileSdk))); - // If NullPointerException/NumberFormatException/etc., just catch and return true. - int sdk = Integer.parseInt(in.readLine().trim()); - if (sdk >= minSdk) { - return true; - } else if (sdk == minSdk - 1) { // Could be minSdk-1, or could be minSdk development. - in.close(); - File outFileCode = File.createTempFile("shelltools_codename", "tmp"); - outFileCode.deleteOnExit(); - runCommand(outFileCode, logger, - "adb", "-s", deviceSerial, "shell", "getprop", "ro.build.version.codename"); - in = new BufferedReader(new InputStreamReader(new FileInputStream(outFileCode))); - return in.readLine().startsWith(minCodename); - } else { - return false; - } - } catch (Exception e) { - logger.fine("Could not determine whether statsd version is compatibile " - + "with tool: " + e.toString()); - } finally { - try { - if (in != null) { - in.close(); - } - } catch (IOException e) { - logger.fine("Could not close temporary file: " + e.toString()); - } - } - // Could not determine whether statsd is acceptable version. - // Just assume it is; if it isn't, we'll just get future errors via adb and deal with them. - return true; - } - - public static class LocalToolsFormatter extends Formatter { - public String format(LogRecord record) { - return record.getMessage() + "\n"; - } - } - - /** - * Parse the result of "adb devices" to return the list of connected devices. - * @param logger Logger to log error messages - * @return List of the serial numbers of the connected devices. - */ - public static List getDeviceSerials(Logger logger) { - try { - ArrayList devices = new ArrayList<>(); - File outFile = File.createTempFile("device_serial", "tmp"); - outFile.deleteOnExit(); - Utils.runCommand(outFile, logger, "adb", "devices"); - List outputLines = Files.readLines(outFile, Charset.defaultCharset()); - Pattern regex = Pattern.compile("^(.*)\tdevice$"); - for (String line : outputLines) { - Matcher m = regex.matcher(line); - if (m.find()) { - devices.add(m.group(1)); - } - } - return devices; - } catch (Exception ex) { - logger.log(Level.SEVERE, "Failed to list connected devices: " + ex.getMessage()); - } - return null; - } - - /** - * Returns ANDROID_SERIAL environment variable, or null if that is undefined or unavailable. - * @param logger Destination of error messages. - * @return String value of ANDROID_SERIAL environment variable, or null. - */ - public static String getDefaultDevice(Logger logger) { - try { - return System.getenv("ANDROID_SERIAL"); - } catch (Exception ex) { - logger.log(Level.SEVERE, "Failed to check ANDROID_SERIAL environment variable.", - ex); - } - return null; - } - - /** - * Returns the device to use if one can be deduced, or null. - * @param device Command-line specified device, or null. - * @param connectedDevices List of all connected devices. - * @param defaultDevice Environment-variable specified device, or null. - * @param logger Destination of error messages. - * @return Device to use, or null. - */ - public static String chooseDevice(String device, List connectedDevices, - String defaultDevice, Logger logger) { - if (connectedDevices == null || connectedDevices.isEmpty()) { - logger.severe("No connected device."); - return null; - } - if (device != null) { - if (connectedDevices.contains(device)) { - return device; - } - logger.severe("Device not connected: " + device); - return null; - } - if (connectedDevices.size() == 1) { - return connectedDevices.get(0); - } - if (defaultDevice != null) { - if (connectedDevices.contains(defaultDevice)) { - return defaultDevice; - } else { - logger.severe("ANDROID_SERIAL device is not connected: " + defaultDevice); - return null; - } - } - logger.severe("More than one device is connected. Choose one" - + " with -s DEVICE_SERIAL or environment variable ANDROID_SERIAL."); - return null; - } -} diff --git a/bin/tools/localtools/src/com/android/statsd/shelltools/localdrive/LocalDrive.java b/bin/tools/localtools/src/com/android/statsd/shelltools/localdrive/LocalDrive.java deleted file mode 100644 index ec3c7df7..00000000 --- a/bin/tools/localtools/src/com/android/statsd/shelltools/localdrive/LocalDrive.java +++ /dev/null @@ -1,379 +0,0 @@ -/* - * Copyright (C) 2018 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. - */ -package com.android.statsd.shelltools.localdrive; - -import com.android.internal.os.StatsdConfigProto.StatsdConfig; -import com.android.os.StatsLog.ConfigMetricsReport; -import com.android.os.StatsLog.ConfigMetricsReportList; -import com.android.statsd.shelltools.Utils; - -import com.google.common.io.Files; -import com.google.protobuf.TextFormat; - -import java.io.File; -import java.io.FileReader; -import java.io.IOException; -import java.util.List; -import java.util.logging.Logger; - -/** - * Tool for using statsd locally. Can upload a config and get the data. Handles - * both binary and human-readable protos. - * To make: make statsd_localdrive - * To run: statsd_localdrive (i.e. ./out/host/linux-x86/bin/statsd_localdrive) - */ -public class LocalDrive { - private static final boolean DEBUG = false; - - public static final int MIN_SDK = 29; - public static final String MIN_CODENAME = "Q"; - - public static final long DEFAULT_CONFIG_ID = 56789; - - public static final String BINARY_FLAG = "--binary"; - public static final String CLEAR_DATA = "--clear"; - public static final String NO_UID_MAP_FLAG = "--no-uid-map"; - - public static final String HELP_STRING = - "Usage:\n\n" + - - "statsd_localdrive [-s DEVICE_SERIAL] upload CONFIG_FILE [CONFIG_ID] [--binary]\n" + - " Uploads the given statsd config file (in binary or human-readable-text format).\n" + - " If a config with this id already exists, removes it first.\n" + - " CONFIG_FILE Location of config file on host.\n" + - " CONFIG_ID Long ID to associate with this config. If absent, uses " - + DEFAULT_CONFIG_ID + ".\n" + - " --binary Config is in binary format; otherwise, assumed human-readable text.\n" + - // Similar to: adb shell cmd stats config update SHELL_UID CONFIG_ID - "\n" + - - "statsd_localdrive [-s DEVICE_SERIAL] update CONFIG_FILE [CONFIG_ID] [--binary]\n" + - " Same as upload, but does not remove the old config first (if it already exists).\n" + - // Similar to: adb shell cmd stats config update SHELL_UID CONFIG_ID - "\n" + - - "statsd_localdrive [-s DEVICE_SERIAL] get-data [CONFIG_ID] [--clear] [--binary] [--no-uid-map]\n" + - " Prints the output statslog data (in binary or human-readable-text format).\n" + - " CONFIG_ID Long ID of the config. If absent, uses " + DEFAULT_CONFIG_ID + ".\n" + - " --binary Output should be in binary, instead of default human-readable text.\n" + - " Binary output can be redirected as usual (e.g. > FILENAME).\n" + - " --no-uid-map Do not include the uid-map (the very lengthy uid<-->pkgName map).\n" + - " --clear Erase the data from statsd afterwards. Does not remove the config.\n" + - // Similar to: adb shell cmd stats dump-report SHELL_UID CONFIG_ID [--keep_data] - // --include_current_bucket --proto - "\n" + - - "statsd_localdrive [-s DEVICE_SERIAL] remove [CONFIG_ID]\n" + - " Removes the config.\n" + - " CONFIG_ID Long ID of the config. If absent, uses " + DEFAULT_CONFIG_ID + ".\n" + - // Equivalent to: adb shell cmd stats config remove SHELL_UID CONFIG_ID - "\n" + - - "statsd_localdrive [-s DEVICE_SERIAL] clear [CONFIG_ID]\n" + - " Clears the data associated with the config.\n" + - " CONFIG_ID Long ID of the config. If absent, uses " + DEFAULT_CONFIG_ID + ".\n" + - // Similar to: adb shell cmd stats dump-report SHELL_UID CONFIG_ID - // --include_current_bucket --proto - ""; - - - private static final Logger sLogger = Logger.getLogger(LocalDrive.class.getName()); - - /** Usage: make statsd_localdrive && statsd_localdrive */ - public static void main(String[] args) { - Utils.setUpLogger(sLogger, DEBUG); - if (args.length == 0) { - printHelp(); - return; - } - - int remainingArgsLength = args.length; - String deviceSerial = null; - if (args[0].equals("-s")) { - if (args.length == 1) { - printHelp(); - } - deviceSerial = args[1]; - remainingArgsLength -= 2; - } - - List connectedDevices = Utils.getDeviceSerials(sLogger); - deviceSerial = Utils.chooseDevice(deviceSerial, connectedDevices, - Utils.getDefaultDevice(sLogger), sLogger); - if (deviceSerial == null) { - return; - } - - if (!Utils.isAcceptableStatsd(sLogger, MIN_SDK, MIN_CODENAME, deviceSerial)) { - sLogger.severe("LocalDrive only works with statsd versions for Android " - + MIN_CODENAME + " or higher."); - return; - } - - int idx = args.length - remainingArgsLength; - if (remainingArgsLength > 0) { - switch (args[idx]) { - case "clear": - cmdClear(args, idx, deviceSerial); - return; - case "get-data": - cmdGetData(args, idx, deviceSerial); - return; - case "remove": - cmdRemove(args, idx); - return; - case "update": - cmdUpdate(args, idx, deviceSerial); - return; - case "upload": - cmdUpload(args, idx, deviceSerial); - return; - } - } - printHelp(); - } - - private static void printHelp() { - sLogger.info(HELP_STRING); - } - - // upload CONFIG_FILE [CONFIG_ID] [--binary] - private static boolean cmdUpload(String[] args, int idx, String deviceSerial) { - return updateConfig(args, idx, true, deviceSerial); - } - - // update CONFIG_FILE [CONFIG_ID] [--binary] - private static boolean cmdUpdate(String[] args, int idx, String deviceSerial) { - return updateConfig(args, idx, false, deviceSerial); - } - - private static boolean updateConfig(String[] args, int idx, boolean removeOldConfig, - String deviceSerial) { - int argCount = args.length - 1 - idx; // Used up one for upload/update. - - // Get CONFIG_FILE - if (argCount < 1) { - sLogger.severe("No config file provided."); - printHelp(); - return false; - } - final String origConfigLocation = args[idx + 1]; - if (!new File(origConfigLocation).exists()) { - sLogger.severe("Error - Cannot find the provided config file: " + origConfigLocation); - return false; - } - argCount--; - - // Get --binary - boolean binary = contains(args, idx + 2, BINARY_FLAG); - if (binary) argCount --; - - // Get CONFIG_ID - long configId; - try { - configId = getConfigId(argCount < 1, args, idx + 2); - } catch (NumberFormatException e) { - sLogger.severe("Invalid config id provided."); - printHelp(); - return false; - } - sLogger.fine(String.format("updateConfig with %s %d %b %b", - origConfigLocation, configId, binary, removeOldConfig)); - - // Remove the old config. - if (removeOldConfig) { - try { - Utils.runCommand(null, sLogger, "adb", "shell", Utils.CMD_REMOVE_CONFIG, - Utils.SHELL_UID, String.valueOf(configId)); - Utils.getReportList(configId, true /* clearData */, true /* SHELL_UID */, sLogger, - deviceSerial); - } catch (InterruptedException | IOException e) { - sLogger.severe("Failed to remove config: " + e.getMessage()); - return false; - } - } - - // Upload the config. - String configLocation; - if (binary) { - configLocation = origConfigLocation; - } else { - StatsdConfig.Builder builder = StatsdConfig.newBuilder(); - try { - TextFormat.merge(new FileReader(origConfigLocation), builder); - } catch (IOException e) { - sLogger.severe("Failed to read config file " + origConfigLocation + ": " - + e.getMessage()); - return false; - } - - try { - File tempConfigFile = File.createTempFile("statsdconfig", ".config"); - tempConfigFile.deleteOnExit(); - Files.write(builder.build().toByteArray(), tempConfigFile); - configLocation = tempConfigFile.getAbsolutePath(); - } catch (IOException e) { - sLogger.severe("Failed to write temp config file: " + e.getMessage()); - return false; - } - } - String remotePath = "/data/local/tmp/statsdconfig.config"; - try { - Utils.runCommand(null, sLogger, "adb", "push", configLocation, remotePath); - Utils.runCommand(null, sLogger, "adb", "shell", "cat", remotePath, "|", - Utils.CMD_UPDATE_CONFIG, Utils.SHELL_UID, String.valueOf(configId)); - } catch (InterruptedException | IOException e) { - sLogger.severe("Failed to update config: " + e.getMessage()); - return false; - } - return true; - } - - // get-data [CONFIG_ID] [--clear] [--binary] [--no-uid-map] - private static boolean cmdGetData(String[] args, int idx, String deviceSerial) { - boolean binary = contains(args, idx + 1, BINARY_FLAG); - boolean noUidMap = contains(args, idx + 1, NO_UID_MAP_FLAG); - boolean clearData = contains(args, idx + 1, CLEAR_DATA); - - // Get CONFIG_ID - int argCount = args.length - 1 - idx; // Used up one for get-data. - if (binary) argCount--; - if (noUidMap) argCount--; - if (clearData) argCount--; - long configId; - try { - configId = getConfigId(argCount < 1, args, idx + 1); - } catch (NumberFormatException e) { - sLogger.severe("Invalid config id provided."); - printHelp(); - return false; - } - sLogger.fine(String.format("cmdGetData with %d %b %b %b", - configId, clearData, binary, noUidMap)); - - // Get the StatsLog - // Even if the args request no modifications, we still parse it to make sure it's valid. - ConfigMetricsReportList reportList; - try { - reportList = Utils.getReportList(configId, clearData, true /* SHELL_UID */, sLogger, - deviceSerial); - } catch (IOException | InterruptedException e) { - sLogger.severe("Failed to get report list: " + e.getMessage()); - return false; - } - if (noUidMap) { - ConfigMetricsReportList.Builder builder - = ConfigMetricsReportList.newBuilder(reportList); - // Clear the reports, then add them back without their UidMap. - builder.clearReports(); - for (ConfigMetricsReport report : reportList.getReportsList()) { - builder.addReports(ConfigMetricsReport.newBuilder(report).clearUidMap()); - } - reportList = builder.build(); - } - - if (!binary) { - sLogger.info(reportList.toString()); - } else { - try { - System.out.write(reportList.toByteArray()); - } catch (IOException e) { - sLogger.severe("Failed to output binary statslog proto: " - + e.getMessage()); - return false; - } - } - return true; - } - - // clear [CONFIG_ID] - private static boolean cmdClear(String[] args, int idx, String deviceSerial) { - // Get CONFIG_ID - long configId; - try { - configId = getConfigId(false, args, idx + 1); - } catch (NumberFormatException e) { - sLogger.severe("Invalid config id provided."); - printHelp(); - return false; - } - sLogger.fine(String.format("cmdClear with %d", configId)); - - try { - Utils.getReportList(configId, true /* clearData */, true /* SHELL_UID */, sLogger, - deviceSerial); - } catch (IOException | InterruptedException e) { - sLogger.severe("Failed to get report list: " + e.getMessage()); - return false; - } - return true; - } - - // remove [CONFIG_ID] - private static boolean cmdRemove(String[] args, int idx) { - // Get CONFIG_ID - long configId; - try { - configId = getConfigId(false, args, idx + 1); - } catch (NumberFormatException e) { - sLogger.severe("Invalid config id provided."); - printHelp(); - return false; - } - sLogger.fine(String.format("cmdRemove with %d", configId)); - - try { - Utils.runCommand(null, sLogger, "adb", "shell", Utils.CMD_REMOVE_CONFIG, - Utils.SHELL_UID, String.valueOf(configId)); - } catch (InterruptedException | IOException e) { - sLogger.severe("Failed to remove config: " + e.getMessage()); - return false; - } - return true; - } - - /** - * Searches through the array to see if it contains (precisely) the given value, starting - * at the given firstIdx. - */ - private static boolean contains(String[] array, int firstIdx, String value) { - if (value == null) return false; - if (firstIdx < 0) return false; - for (int i = firstIdx; i < array.length; i++) { - if (value.equals(array[i])) { - return true; - } - } - return false; - } - - /** - * Gets the config id from args[idx], or returns DEFAULT_CONFIG_ID if args[idx] does not exist. - * If justUseDefault, overrides and just uses DEFAULT_CONFIG_ID instead. - */ - private static long getConfigId(boolean justUseDefault, String[] args, int idx) - throws NumberFormatException { - if (justUseDefault || args.length <= idx || idx < 0) { - return DEFAULT_CONFIG_ID; - } - try { - return Long.valueOf(args[idx]); - } catch (NumberFormatException e) { - sLogger.severe("Bad config id provided: " + args[idx]); - throw e; - } - } -} diff --git a/bin/tools/localtools/src/com/android/statsd/shelltools/testdrive/TestDrive.java b/bin/tools/localtools/src/com/android/statsd/shelltools/testdrive/TestDrive.java deleted file mode 100644 index 51bcad11..00000000 --- a/bin/tools/localtools/src/com/android/statsd/shelltools/testdrive/TestDrive.java +++ /dev/null @@ -1,419 +0,0 @@ -/* - * Copyright (C) 2018 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. - */ -package com.android.statsd.shelltools.testdrive; - -import com.android.internal.os.StatsdConfigProto; -import com.android.internal.os.StatsdConfigProto.AtomMatcher; -import com.android.internal.os.StatsdConfigProto.EventMetric; -import com.android.internal.os.StatsdConfigProto.FieldFilter; -import com.android.internal.os.StatsdConfigProto.GaugeMetric; -import com.android.internal.os.StatsdConfigProto.PullAtomPackages; -import com.android.internal.os.StatsdConfigProto.SimpleAtomMatcher; -import com.android.internal.os.StatsdConfigProto.StatsdConfig; -import com.android.internal.os.StatsdConfigProto.TimeUnit; -import com.android.os.AtomsProto.Atom; -import com.android.os.StatsLog; -import com.android.os.StatsLog.ConfigMetricsReport; -import com.android.os.StatsLog.ConfigMetricsReportList; -import com.android.os.StatsLog.StatsLogReport; -import com.android.statsd.shelltools.Utils; - -import com.google.common.annotations.VisibleForTesting; -import com.google.common.io.Files; - -import java.io.File; -import java.io.IOException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import java.util.Set; -import java.util.TreeSet; -import java.util.logging.Level; -import java.util.logging.Logger; - -public class TestDrive { - - private static final int METRIC_ID_BASE = 1111; - private static final long ATOM_MATCHER_ID_BASE = 1234567; - private static final long APP_BREADCRUMB_MATCHER_ID = 1111111; - private static final int PULL_ATOM_START = 10000; - private static final int MAX_PLATFORM_ATOM_TAG = 100000; - private static final int VENDOR_PULLED_ATOM_START_TAG = 150000; - private static final long CONFIG_ID = 54321; - private static final String[] ALLOWED_LOG_SOURCES = { - "AID_GRAPHICS", - "AID_INCIDENTD", - "AID_STATSD", - "AID_RADIO", - "com.android.systemui", - "com.android.vending", - "AID_SYSTEM", - "AID_ROOT", - "AID_BLUETOOTH", - "AID_LMKD", - "com.android.managedprovisioning", - "AID_MEDIA", - "AID_NETWORK_STACK", - "com.google.android.providers.media.module", - }; - private static final String[] DEFAULT_PULL_SOURCES = { - "AID_SYSTEM", - "AID_RADIO" - }; - private static final Logger LOGGER = Logger.getLogger(TestDrive.class.getName()); - - @VisibleForTesting - String mDeviceSerial = null; - @VisibleForTesting - Dumper mDumper = new BasicDumper(); - - public static void main(String[] args) { - final Configuration configuration = new Configuration(); - final TestDrive testDrive = new TestDrive(); - Utils.setUpLogger(LOGGER, false); - - if (!testDrive.processArgs(configuration, args, - Utils.getDeviceSerials(LOGGER), Utils.getDefaultDevice(LOGGER))) { - return; - } - - final ConfigMetricsReportList reports = testDrive.testDriveAndGetReports( - configuration.createConfig(), configuration.hasPulledAtoms(), - configuration.hasPushedAtoms()); - if (reports != null) { - configuration.dumpMetrics(reports, testDrive.mDumper); - } - } - - boolean processArgs(Configuration configuration, String[] args, List connectedDevices, - String defaultDevice) { - if (args.length < 1) { - LOGGER.severe("Usage: ./test_drive [-one] " - + "[-p additional_allowed_package] " - + "[-s DEVICE_SERIAL_NUMBER] " - + " ... "); - return false; - } - - int first_arg = 0; - // Consume all flags, which must precede all atoms - for (; first_arg < args.length; ++first_arg) { - String arg = args[first_arg]; - int remaining_args = args.length - first_arg; - if (remaining_args >= 2 && arg.equals("-one")) { - LOGGER.info("Creating one event metric to catch all pushed atoms."); - configuration.mOnePushedAtomEvent = true; - } else if (remaining_args >= 2 && arg.equals("-terse")) { - LOGGER.info("Terse output format."); - mDumper = new TerseDumper(); - } else if (remaining_args >= 3 && arg.equals("-p")) { - configuration.mAdditionalAllowedPackage = args[++first_arg]; - } else if (remaining_args >= 3 && arg.equals("-s")) { - mDeviceSerial = args[++first_arg]; - } else { - break; // Found the atom list - } - } - - mDeviceSerial = Utils.chooseDevice(mDeviceSerial, connectedDevices, defaultDevice, LOGGER); - if (mDeviceSerial == null) { - return false; - } - - for ( ; first_arg < args.length; ++first_arg) { - String atom = args[first_arg]; - try { - configuration.addAtom(Integer.valueOf(atom)); - } catch (NumberFormatException e) { - LOGGER.severe("Bad atom id provided: " + atom); - } - } - - return configuration.hasPulledAtoms() || configuration.hasPushedAtoms(); - } - - private ConfigMetricsReportList testDriveAndGetReports(StatsdConfig config, - boolean hasPulledAtoms, boolean hasPushedAtoms) { - if (config == null) { - LOGGER.severe("Failed to create valid config."); - return null; - } - - String remoteConfigPath = null; - try { - remoteConfigPath = pushConfig(config, mDeviceSerial); - LOGGER.info("Pushed the following config to statsd on device '" + mDeviceSerial - + "':"); - LOGGER.info(config.toString()); - if (hasPushedAtoms) { - LOGGER.info("Now please play with the device to trigger the event."); - } - if (!hasPulledAtoms) { - LOGGER.info( - "All events should be dumped after 1 min ..."); - Thread.sleep(60_000); - } else { - LOGGER.info("All events should be dumped after 1.5 minutes ..."); - Thread.sleep(15_000); - Utils.logAppBreadcrumb(0, 0, LOGGER, mDeviceSerial); - Thread.sleep(75_000); - } - return Utils.getReportList(CONFIG_ID, true, false, LOGGER, - mDeviceSerial); - } catch (Exception e) { - LOGGER.log(Level.SEVERE, "Failed to test drive: " + e.getMessage(), e); - } finally { - removeConfig(mDeviceSerial); - if (remoteConfigPath != null) { - try { - Utils.runCommand(null, LOGGER, - "adb", "-s", mDeviceSerial, "shell", "rm", - remoteConfigPath); - } catch (Exception e) { - LOGGER.log(Level.WARNING, - "Unable to remove remote config file: " + remoteConfigPath, e); - } - } - } - return null; - } - - static class Configuration { - boolean mOnePushedAtomEvent = false; - @VisibleForTesting - Set mPushedAtoms = new TreeSet<>(); - @VisibleForTesting - Set mPulledAtoms = new TreeSet<>(); - @VisibleForTesting - String mAdditionalAllowedPackage = null; - private final Set mTrackedMetrics = new HashSet<>(); - - private void dumpMetrics(ConfigMetricsReportList reportList, Dumper dumper) { - // We may get multiple reports. Take the last one. - ConfigMetricsReport report = reportList.getReports(reportList.getReportsCount() - 1); - for (StatsLogReport statsLog : report.getMetricsList()) { - if (isTrackedMetric(statsLog.getMetricId())) { - dumper.dump(statsLog); - } - } - } - - boolean isTrackedMetric(long metricId) { - return mTrackedMetrics.contains(metricId); - } - - static boolean isPulledAtom(int atomId) { - return atomId >= PULL_ATOM_START && atomId <= MAX_PLATFORM_ATOM_TAG - || atomId >= VENDOR_PULLED_ATOM_START_TAG; - } - - void addAtom(Integer atom) { - if (Atom.getDescriptor().findFieldByNumber(atom) == null) { - LOGGER.severe("No such atom found: " + atom); - return; - } - if (isPulledAtom(atom)) { - mPulledAtoms.add(atom); - } else { - mPushedAtoms.add(atom); - } - } - - private boolean hasPulledAtoms() { - return !mPulledAtoms.isEmpty(); - } - - private boolean hasPushedAtoms() { - return !mPushedAtoms.isEmpty(); - } - - StatsdConfig createConfig() { - long metricId = METRIC_ID_BASE; - long atomMatcherId = ATOM_MATCHER_ID_BASE; - - StatsdConfig.Builder builder = baseBuilder(); - - if (hasPulledAtoms()) { - builder.addAtomMatcher( - createAtomMatcher( - Atom.APP_BREADCRUMB_REPORTED_FIELD_NUMBER, - APP_BREADCRUMB_MATCHER_ID)); - } - - for (int atomId : mPulledAtoms) { - builder.addAtomMatcher(createAtomMatcher(atomId, atomMatcherId)); - GaugeMetric.Builder gaugeMetricBuilder = GaugeMetric.newBuilder(); - gaugeMetricBuilder - .setId(metricId) - .setWhat(atomMatcherId) - .setTriggerEvent(APP_BREADCRUMB_MATCHER_ID) - .setGaugeFieldsFilter(FieldFilter.newBuilder().setIncludeAll(true).build()) - .setBucket(TimeUnit.ONE_MINUTE) - .setSamplingType(GaugeMetric.SamplingType.FIRST_N_SAMPLES) - .setMaxNumGaugeAtomsPerBucket(100); - builder.addGaugeMetric(gaugeMetricBuilder.build()); - atomMatcherId++; - mTrackedMetrics.add(metricId++); - } - - // A simple atom matcher for each pushed atom. - List simpleAtomMatchers = new ArrayList<>(); - for (int atomId : mPushedAtoms) { - final AtomMatcher atomMatcher = createAtomMatcher(atomId, atomMatcherId++); - simpleAtomMatchers.add(atomMatcher); - builder.addAtomMatcher(atomMatcher); - } - - if (mOnePushedAtomEvent) { - // Create a union event metric, using an matcher that matches all pulled atoms. - AtomMatcher unionAtomMatcher = createUnionMatcher(simpleAtomMatchers, - atomMatcherId); - builder.addAtomMatcher(unionAtomMatcher); - EventMetric.Builder eventMetricBuilder = EventMetric.newBuilder(); - eventMetricBuilder.setId(metricId).setWhat(unionAtomMatcher.getId()); - builder.addEventMetric(eventMetricBuilder.build()); - mTrackedMetrics.add(metricId++); - } else { - // Create multiple event metrics, one per pulled atom. - for (AtomMatcher atomMatcher : simpleAtomMatchers) { - EventMetric.Builder eventMetricBuilder = EventMetric.newBuilder(); - eventMetricBuilder - .setId(metricId) - .setWhat(atomMatcher.getId()); - builder.addEventMetric(eventMetricBuilder.build()); - mTrackedMetrics.add(metricId++); - } - } - - return builder.build(); - } - - private static AtomMatcher createAtomMatcher(int atomId, long matcherId) { - AtomMatcher.Builder atomMatcherBuilder = AtomMatcher.newBuilder(); - atomMatcherBuilder - .setId(matcherId) - .setSimpleAtomMatcher(SimpleAtomMatcher.newBuilder().setAtomId(atomId)); - return atomMatcherBuilder.build(); - } - - private AtomMatcher createUnionMatcher(List simpleAtomMatchers, - long atomMatcherId) { - AtomMatcher.Combination.Builder combinationBuilder = - AtomMatcher.Combination.newBuilder(); - combinationBuilder.setOperation(StatsdConfigProto.LogicalOperation.OR); - for (AtomMatcher matcher : simpleAtomMatchers) { - combinationBuilder.addMatcher(matcher.getId()); - } - AtomMatcher.Builder atomMatcherBuilder = AtomMatcher.newBuilder(); - atomMatcherBuilder.setId(atomMatcherId).setCombination(combinationBuilder.build()); - return atomMatcherBuilder.build(); - } - - private StatsdConfig.Builder baseBuilder() { - ArrayList allowedSources = new ArrayList<>(); - Collections.addAll(allowedSources, ALLOWED_LOG_SOURCES); - if (mAdditionalAllowedPackage != null) { - allowedSources.add(mAdditionalAllowedPackage); - } - return StatsdConfig.newBuilder() - .addAllAllowedLogSource(allowedSources) - .addAllDefaultPullPackages(Arrays.asList(DEFAULT_PULL_SOURCES)) - .addPullAtomPackages(PullAtomPackages.newBuilder() - .setAtomId(Atom.GPU_STATS_GLOBAL_INFO_FIELD_NUMBER) - .addPackages("AID_GPU_SERVICE")) - .addPullAtomPackages(PullAtomPackages.newBuilder() - .setAtomId(Atom.GPU_STATS_APP_INFO_FIELD_NUMBER) - .addPackages("AID_GPU_SERVICE")) - .addPullAtomPackages(PullAtomPackages.newBuilder() - .setAtomId(Atom.TRAIN_INFO_FIELD_NUMBER) - .addPackages("AID_STATSD")) - .addPullAtomPackages(PullAtomPackages.newBuilder() - .setAtomId(Atom.GENERAL_EXTERNAL_STORAGE_ACCESS_STATS_FIELD_NUMBER) - .addPackages("com.google.android.providers.media.module")) - .setHashStringsInMetricReport(false); - } - } - - interface Dumper { - void dump(StatsLogReport report); - } - - static class BasicDumper implements Dumper { - @Override - public void dump(StatsLogReport report) { - System.out.println(report.toString()); - } - } - - static class TerseDumper extends BasicDumper { - @Override - public void dump(StatsLogReport report) { - if (report.hasGaugeMetrics()) { - dumpGaugeMetrics(report); - } - if (report.hasEventMetrics()) { - dumpEventMetrics(report); - } - } - void dumpEventMetrics(StatsLogReport report) { - final List data = report.getEventMetrics().getDataList(); - if (data.isEmpty()) { - return; - } - long firstTimestampNanos = data.get(0).getElapsedTimestampNanos(); - for (StatsLog.EventMetricData event : data) { - final double deltaSec = (event.getElapsedTimestampNanos() - firstTimestampNanos) - / 1e9; - System.out.println( - String.format("+%.3fs: %s", deltaSec, event.getAtom().toString())); - } - } - void dumpGaugeMetrics(StatsLogReport report) { - final List data = report.getGaugeMetrics().getDataList(); - if (data.isEmpty()) { - return; - } - for (StatsLog.GaugeMetricData gauge : data) { - System.out.println(gauge.toString()); - } - } - } - - private static String pushConfig(StatsdConfig config, String deviceSerial) - throws IOException, InterruptedException { - File configFile = File.createTempFile("statsdconfig", ".config"); - configFile.deleteOnExit(); - Files.write(config.toByteArray(), configFile); - String remotePath = "/data/local/tmp/" + configFile.getName(); - Utils.runCommand(null, LOGGER, "adb", "-s", deviceSerial, - "push", configFile.getAbsolutePath(), remotePath); - Utils.runCommand(null, LOGGER, "adb", "-s", deviceSerial, - "shell", "cat", remotePath, "|", Utils.CMD_UPDATE_CONFIG, - String.valueOf(CONFIG_ID)); - return remotePath; - } - - private static void removeConfig(String deviceSerial) { - try { - Utils.runCommand(null, LOGGER, "adb", "-s", deviceSerial, - "shell", Utils.CMD_REMOVE_CONFIG, String.valueOf(CONFIG_ID)); - } catch (Exception e) { - LOGGER.severe("Failed to remove config: " + e.getMessage()); - } - } -} diff --git a/bin/tools/localtools/test/com/android/statsd/shelltools/testdrive/ConfigurationTest.java b/bin/tools/localtools/test/com/android/statsd/shelltools/testdrive/ConfigurationTest.java deleted file mode 100644 index b1cc60f7..00000000 --- a/bin/tools/localtools/test/com/android/statsd/shelltools/testdrive/ConfigurationTest.java +++ /dev/null @@ -1,326 +0,0 @@ -/* - * Copyright (C) 2020 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. - */ - -package com.android.statsd.shelltools.testdrive; - -import static org.junit.Assert.assertArrayEquals; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; - -import com.android.internal.os.StatsdConfigProto; -import com.android.internal.os.StatsdConfigProto.StatsdConfig; -import com.android.os.AtomsProto; - -import org.junit.Test; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -/** - * Tests for {@link TestDrive} - */ -public class ConfigurationTest { - - private StatsdConfigProto.AtomMatcher findAndRemoveAtomMatcherById( - List atomMatchers, long id) { - int numMatches = 0; - StatsdConfigProto.AtomMatcher match = null; - for (StatsdConfigProto.AtomMatcher atomMatcher : atomMatchers) { - if (id == atomMatcher.getId()) { - ++numMatches; - match = atomMatcher; - } - } - if (numMatches == 1) { - atomMatchers.remove(match); - return match; - } - return null; // Too many, or not found - } - - private final TestDrive.Configuration mConfiguration = new TestDrive.Configuration(); - - @Test - public void testOnePushed() { - final int atom = 90; - assertFalse(TestDrive.Configuration.isPulledAtom(atom)); - mConfiguration.addAtom(atom); - StatsdConfig config = mConfiguration.createConfig(); - - //event_metric { - // id: 1111 - // what: 1234567 - //} - //atom_matcher { - // id: 1234567 - // simple_atom_matcher { - // atom_id: 90 - // } - //} - - assertEquals(1, config.getEventMetricCount()); - assertEquals(0, config.getGaugeMetricCount()); - - assertTrue(mConfiguration.isTrackedMetric(config.getEventMetric(0).getId())); - - final List atomMatchers = - new ArrayList<>(config.getAtomMatcherList()); - assertEquals(atom, - findAndRemoveAtomMatcherById(atomMatchers, config.getEventMetric(0).getWhat()) - .getSimpleAtomMatcher().getAtomId()); - assertEquals(0, atomMatchers.size()); - } - - @Test - public void testOnePulled() { - final int atom = 10022; - assertTrue(TestDrive.Configuration.isPulledAtom(atom)); - mConfiguration.addAtom(atom); - StatsdConfig config = mConfiguration.createConfig(); - - //gauge_metric { - // id: 1111 - // what: 1234567 - // gauge_fields_filter { - // include_all: true - // } - // bucket: ONE_MINUTE - // sampling_type: FIRST_N_SAMPLES - // max_num_gauge_atoms_per_bucket: 100 - // trigger_event: 1111111 - //} - //atom_matcher { - // id: 1111111 - // simple_atom_matcher { - // atom_id: 47 - // } - //} - //atom_matcher { - // id: 1234567 - // simple_atom_matcher { - // atom_id: 10022 - // } - //} - - assertEquals(0, config.getEventMetricCount()); - assertEquals(1, config.getGaugeMetricCount()); - - assertTrue(mConfiguration.isTrackedMetric(config.getGaugeMetric(0).getId())); - - final StatsdConfigProto.GaugeMetric gaugeMetric = config.getGaugeMetric(0); - assertTrue(gaugeMetric.getGaugeFieldsFilter().getIncludeAll()); - - final List atomMatchers = - new ArrayList<>(config.getAtomMatcherList()); - assertEquals(atom, - findAndRemoveAtomMatcherById(atomMatchers, gaugeMetric.getWhat()) - .getSimpleAtomMatcher().getAtomId()); - assertEquals(AtomsProto.Atom.APP_BREADCRUMB_REPORTED_FIELD_NUMBER, - findAndRemoveAtomMatcherById(atomMatchers, gaugeMetric.getTriggerEvent()) - .getSimpleAtomMatcher().getAtomId()); - assertEquals(0, atomMatchers.size()); - } - - @Test - public void testOnePulledTwoPushed() { - final int pulledAtom = 10022; - assertTrue(TestDrive.Configuration.isPulledAtom(pulledAtom)); - mConfiguration.addAtom(pulledAtom); - - Integer[] pushedAtoms = new Integer[]{244, 245}; - for (int atom : pushedAtoms) { - assertFalse(TestDrive.Configuration.isPulledAtom(atom)); - mConfiguration.addAtom(atom); - } - StatsdConfig config = mConfiguration.createConfig(); - - // event_metric { - // id: 1111 - // what: 1234567 - // } - // event_metric { - // id: 1112 - // what: 1234568 - // } - // gauge_metric { - // id: 1114 - // what: 1234570 - // gauge_fields_filter { - // include_all: true - // } - // bucket: ONE_MINUTE - // sampling_type: FIRST_N_SAMPLES - // max_num_gauge_atoms_per_bucket: 100 - // trigger_event: 1111111 - // } - // atom_matcher { - // id: 1111111 - // simple_atom_matcher { - // atom_id: 47 - // } - // } - // atom_matcher { - // id: 1234567 - // simple_atom_matcher { - // atom_id: 244 - // } - // } - // atom_matcher { - // id: 1234568 - // simple_atom_matcher { - // atom_id: 245 - // } - // } - // atom_matcher { - // id: 1234570 - // simple_atom_matcher { - // atom_id: 10022 - // } - // } - - assertEquals(2, config.getEventMetricCount()); - assertEquals(1, config.getGaugeMetricCount()); - - final StatsdConfigProto.GaugeMetric gaugeMetric = config.getGaugeMetric(0); - assertTrue(mConfiguration.isTrackedMetric(gaugeMetric.getId())); - assertTrue(gaugeMetric.getGaugeFieldsFilter().getIncludeAll()); - for (StatsdConfigProto.EventMetric eventMetric : config.getEventMetricList()) { - assertTrue(mConfiguration.isTrackedMetric(eventMetric.getId())); - } - - final List atomMatchers = - new ArrayList<>(config.getAtomMatcherList()); - - assertEquals(pulledAtom, findAndRemoveAtomMatcherById(atomMatchers, gaugeMetric.getWhat()) - .getSimpleAtomMatcher().getAtomId()); - assertEquals(AtomsProto.Atom.APP_BREADCRUMB_REPORTED_FIELD_NUMBER, - findAndRemoveAtomMatcherById(atomMatchers, gaugeMetric.getTriggerEvent()) - .getSimpleAtomMatcher().getAtomId()); - - Integer[] actualAtoms = new Integer[]{ - findAndRemoveAtomMatcherById(atomMatchers, config.getEventMetric(0).getWhat()) - .getSimpleAtomMatcher().getAtomId(), - findAndRemoveAtomMatcherById(atomMatchers, config.getEventMetric(1).getWhat()) - .getSimpleAtomMatcher().getAtomId()}; - Arrays.sort(actualAtoms); - assertArrayEquals(pushedAtoms, actualAtoms); - - assertEquals(0, atomMatchers.size()); - } - - @Test - public void testOnePulledTwoPushedTogether() { - mConfiguration.mOnePushedAtomEvent = true; // Use one event grabbing all pushed atoms - - final int pulledAtom = 10022; - assertTrue(TestDrive.Configuration.isPulledAtom(pulledAtom)); - mConfiguration.addAtom(pulledAtom); - - Integer[] pushedAtoms = new Integer[]{244, 245}; - for (int atom : pushedAtoms) { - assertFalse(TestDrive.Configuration.isPulledAtom(atom)); - mConfiguration.addAtom(atom); - } - StatsdConfig config = mConfiguration.createConfig(); - - // event_metric { - // id: 1112 - // what: 1234570 - // } - // gauge_metric { - // id: 1111 - // what: 1234567 - // gauge_fields_filter { - // include_all: true - // } - // bucket: ONE_MINUTE - // sampling_type: FIRST_N_SAMPLES - // max_num_gauge_atoms_per_bucket: 100 - // trigger_event: 1111111 - // } - // atom_matcher { - // id: 1111111 - // simple_atom_matcher { - // atom_id: 47 - // } - // } - // atom_matcher { - // id: 1234567 - // simple_atom_matcher { - // atom_id: 10022 - // } - // } - // atom_matcher { - // id: 1234568 - // simple_atom_matcher { - // atom_id: 244 - // } - // } - // atom_matcher { - // id: 1234569 - // simple_atom_matcher { - // atom_id: 245 - // } - // } - // atom_matcher { - // id: 1234570 - // combination { - // operation: OR - // matcher: 1234568 - // matcher: 1234569 - // } - // } - - assertEquals(1, config.getEventMetricCount()); - assertEquals(1, config.getGaugeMetricCount()); - - final StatsdConfigProto.GaugeMetric gaugeMetric = config.getGaugeMetric(0); - assertTrue(mConfiguration.isTrackedMetric(gaugeMetric.getId())); - assertTrue(gaugeMetric.getGaugeFieldsFilter().getIncludeAll()); - - StatsdConfigProto.EventMetric eventMetric = config.getEventMetric(0); - assertTrue(mConfiguration.isTrackedMetric(eventMetric.getId())); - - final List atomMatchers = - new ArrayList<>(config.getAtomMatcherList()); - - assertEquals(pulledAtom, findAndRemoveAtomMatcherById(atomMatchers, gaugeMetric.getWhat()) - .getSimpleAtomMatcher().getAtomId()); - assertEquals(AtomsProto.Atom.APP_BREADCRUMB_REPORTED_FIELD_NUMBER, - findAndRemoveAtomMatcherById(atomMatchers, gaugeMetric.getTriggerEvent()) - .getSimpleAtomMatcher().getAtomId()); - - StatsdConfigProto.AtomMatcher unionMatcher = findAndRemoveAtomMatcherById(atomMatchers, - eventMetric.getWhat()); - assertNotNull(unionMatcher.getCombination()); - assertEquals(2, unionMatcher.getCombination().getMatcherCount()); - - Integer[] actualAtoms = new Integer[]{ - findAndRemoveAtomMatcherById(atomMatchers, - unionMatcher.getCombination().getMatcher(0)) - .getSimpleAtomMatcher().getAtomId(), - findAndRemoveAtomMatcherById(atomMatchers, - unionMatcher.getCombination().getMatcher(1)) - .getSimpleAtomMatcher().getAtomId()}; - Arrays.sort(actualAtoms); - assertArrayEquals(pushedAtoms, actualAtoms); - - assertEquals(0, atomMatchers.size()); - } -} diff --git a/bin/tools/localtools/test/com/android/statsd/shelltools/testdrive/TestDriveTest.java b/bin/tools/localtools/test/com/android/statsd/shelltools/testdrive/TestDriveTest.java deleted file mode 100644 index 363fac0c..00000000 --- a/bin/tools/localtools/test/com/android/statsd/shelltools/testdrive/TestDriveTest.java +++ /dev/null @@ -1,195 +0,0 @@ -/* - * Copyright (C) 2020 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. - */ - -package com.android.statsd.shelltools.testdrive; - -import static org.junit.Assert.assertArrayEquals; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; - -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; - -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.List; - -/** - * Tests for {@link TestDrive} - */ -@RunWith(Parameterized.class) -public class TestDriveTest { - /** - * Expected results of a single iteration of the paramerized test. - */ - static class Expect { - public boolean success; - public Integer[] atoms; - public boolean onePushedAtomEvent = false; - public String extraPackage = null; - public String target; - public boolean terse = false; - - static Expect success(Integer... atoms) { - return new Expect(true, atoms, - TARGET); - } - Expect(boolean success, Integer[] atoms, String target) { - this.success = success; - this.atoms = atoms; - this.target = target; - } - static final Expect FAILURE = new Expect(false, null, null); - Expect onePushedAtomEvent() { - this.onePushedAtomEvent = true; - return this; - } - Expect extraPackage() { - this.extraPackage = TestDriveTest.PACKAGE; - return this; - } - Expect terse() { - this.terse = true; - return this; - } - } - - @Parameterized.Parameter(0) - public String[] mArgs; - - @Parameterized.Parameter(1) - public List mConnectedDevices; - - @Parameterized.Parameter(2) - public String mDefaultDevice; - - @Parameterized.Parameter(3) - public Expect mExpect; - - private static final String TARGET = "target"; - private static final List TARGET_AND_OTHER = Arrays.asList("otherDevice", - TARGET); - private static final List TWO_OTHER_DEVICES = Arrays.asList( - "other1", "other2"); - private static final List TARGET_ONLY = Collections.singletonList(TARGET); - private static final List NOT_TARGET = Collections.singletonList("other"); - private static final List NO_DEVICES = Collections.emptyList(); - private static final String PACKAGE = "extraPackage"; - - @Parameterized.Parameters - public static Collection data() { - return Arrays.asList( - new Object[]{new String[]{}, null, null, - Expect.FAILURE}, // Usage explanation - new Object[]{new String[]{"244", "245"}, null, null, - Expect.FAILURE}, // Failure looking up connected devices - new Object[]{new String[]{"244", "245"}, NO_DEVICES, null, - Expect.FAILURE}, // No connected devices - new Object[]{new String[]{"-s", TARGET, "244", "245"}, NOT_TARGET, null, - Expect.FAILURE}, // Wrong device connected - new Object[]{new String[]{"244", "245"}, TWO_OTHER_DEVICES, null, - Expect.FAILURE}, // Wrong devices connected - new Object[]{new String[]{"244", "245"}, TARGET_ONLY, null, - Expect.success(244, 245)}, // If only one device connected, guess that one - new Object[]{new String[]{"244", "not_an_atom"}, TARGET_ONLY, null, - Expect.success(244)}, // Ignore non-atoms - new Object[]{new String[]{"not_an_atom"}, TARGET_ONLY, null, - Expect.FAILURE}, // Require at least one atom - new Object[]{new String[]{"244", "245"}, TWO_OTHER_DEVICES, TARGET, - Expect.FAILURE}, // ANDROID_SERIAL specifies non-connected target - new Object[]{new String[]{"244", "245"}, TARGET_AND_OTHER, TARGET, - Expect.success(244, 245)}, // ANDROID_SERIAL specifies a valid target - new Object[]{new String[]{"244", "245"}, TARGET_AND_OTHER, null, - Expect.FAILURE}, // Two connected devices, no indication of which to use - new Object[]{new String[]{"-one", "244", "245"}, TARGET_ONLY, null, - Expect.success(244, 245).onePushedAtomEvent()}, - new Object[]{new String[]{"-terse", "-one", "244", "245"}, TARGET_ONLY, null, - Expect.success(244, 245).onePushedAtomEvent().terse()}, - new Object[]{new String[]{"-one", "-terse", "244", "245"}, TARGET_ONLY, null, - Expect.success(244, 245).onePushedAtomEvent().terse()}, - new Object[]{new String[]{"-p", PACKAGE, "244", "245"}, TARGET_ONLY, null, - Expect.success(244, 245).extraPackage()}, - new Object[]{new String[]{"-p", PACKAGE, "-one", "244", "245"}, TARGET_ONLY, null, - Expect.success(244, 245).extraPackage().onePushedAtomEvent()}, - new Object[]{new String[]{"-one", "-p", PACKAGE, "244", "245"}, TARGET_ONLY, null, - Expect.success(244, 245).extraPackage().onePushedAtomEvent()}, - new Object[]{new String[]{"-s", TARGET, "-one", "-p", PACKAGE, "244", "245"}, - TARGET_AND_OTHER, null, - Expect.success(244, 245).extraPackage().onePushedAtomEvent()}, - new Object[]{new String[]{"-one", "-s", TARGET, "-p", PACKAGE, "244", "245"}, - TARGET_AND_OTHER, null, - Expect.success(244, 245).extraPackage().onePushedAtomEvent()}, - new Object[]{new String[]{"-one", "-p", PACKAGE, "-s", TARGET, "244", "245"}, - TARGET_AND_OTHER, null, - Expect.success(244, 245).extraPackage().onePushedAtomEvent()}, - new Object[]{new String[]{"-terse", "-one", "-p", PACKAGE, "-s", TARGET, - "244", "245"}, - TARGET_AND_OTHER, null, - Expect.success(244, 245).extraPackage().onePushedAtomEvent().terse()}, - new Object[]{new String[]{"-one", "-terse", "-p", PACKAGE, "-s", TARGET, - "244", "245"}, - TARGET_AND_OTHER, null, - Expect.success(244, 245).extraPackage().onePushedAtomEvent().terse()}, - new Object[]{new String[]{"-one", "-p", PACKAGE, "-terse", "-s", TARGET, - "244", "245"}, - TARGET_AND_OTHER, null, - Expect.success(244, 245).extraPackage().onePushedAtomEvent().terse()}, - new Object[]{new String[]{"-one", "-p", PACKAGE, "-s", TARGET, "-terse", - "244", "245"}, - TARGET_AND_OTHER, null, - Expect.success(244, 245).extraPackage().onePushedAtomEvent().terse()} - ); - } - - private final TestDrive.Configuration mConfiguration = new TestDrive.Configuration(); - private final TestDrive mTestDrive = new TestDrive(); - - private static Integer[] collectAtoms(TestDrive.Configuration configuration) { - Integer[] result = new Integer[configuration.mPulledAtoms.size() - + configuration.mPushedAtoms.size()]; - int result_index = 0; - for (Integer atom : configuration.mPushedAtoms) { - result[result_index++] = atom; - } - for (Integer atom : configuration.mPulledAtoms) { - result[result_index++] = atom; - } - Arrays.sort(result); - return result; - } - - @Test - public void testProcessArgs() { - boolean result = mTestDrive.processArgs(mConfiguration, mArgs, mConnectedDevices, - mDefaultDevice); - if (mExpect.success) { - assertTrue(result); - assertArrayEquals(mExpect.atoms, collectAtoms(mConfiguration)); - assertEquals(mExpect.onePushedAtomEvent, mConfiguration.mOnePushedAtomEvent); - assertEquals(mExpect.target, mTestDrive.mDeviceSerial); - if (mExpect.terse) { - assertEquals(TestDrive.TerseDumper.class, mTestDrive.mDumper.getClass()); - } else { - assertEquals(TestDrive.BasicDumper.class, mTestDrive.mDumper.getClass()); - } - } else { - assertFalse(result); - } - } -} diff --git a/bin/tools/localtools/testdrive_manifest.txt b/bin/tools/localtools/testdrive_manifest.txt deleted file mode 100644 index 625ebfa4..00000000 --- a/bin/tools/localtools/testdrive_manifest.txt +++ /dev/null @@ -1 +0,0 @@ -Main-class: com.android.statsd.shelltools.testdrive.TestDrive diff --git a/lib/libstatspull/Android.bp b/lib/libstatspull/Android.bp index c2a64906..24252ff1 100644 --- a/lib/libstatspull/Android.bp +++ b/lib/libstatspull/Android.bp @@ -69,7 +69,6 @@ cc_library_static { "libstatspull_defaults", ], visibility: [ - "//frameworks/base/apex/statsd/tests/libstatspull", "//packages/modules/StatsD/apex/tests/libstatspull", ], } diff --git a/lib/libstatssocket/Android.bp b/lib/libstatssocket/Android.bp index c402e661..9c6057c1 100644 --- a/lib/libstatssocket/Android.bp +++ b/lib/libstatssocket/Android.bp @@ -76,10 +76,8 @@ cc_test_library { "libstatssocket_defaults", ], visibility: [ - "//frameworks/base/apex/statsd/tests/libstatspull", - "//frameworks/base/cmds/statsd", "//packages/modules/StatsD/apex/tests/libstatspull", - "//packages/modules/StatsD/bin", + "//packages/modules/StatsD/statsd", ], } diff --git a/statsd/.clang-format b/statsd/.clang-format new file mode 100644 index 00000000..cead3a07 --- /dev/null +++ b/statsd/.clang-format @@ -0,0 +1,17 @@ +BasedOnStyle: Google +AllowShortIfStatementsOnASingleLine: true +AllowShortFunctionsOnASingleLine: false +AllowShortLoopsOnASingleLine: true +BinPackArguments: true +BinPackParameters: true +ColumnLimit: 100 +CommentPragmas: NOLINT:.* +ContinuationIndentWidth: 8 +DerivePointerAlignment: false +IndentWidth: 4 +PointerAlignment: Left +TabWidth: 4 +AccessModifierOffset: -4 +IncludeCategories: + - Regex: '^"Log\.h"' + Priority: -1 diff --git a/statsd/Android.bp b/statsd/Android.bp new file mode 100644 index 00000000..2f18d514 --- /dev/null +++ b/statsd/Android.bp @@ -0,0 +1,431 @@ +// +// 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. +// + +cc_defaults { + name: "statsd_defaults", + + srcs: [ + "src/active_config_list.proto", + "src/anomaly/AlarmMonitor.cpp", + "src/anomaly/AlarmTracker.cpp", + "src/anomaly/AnomalyTracker.cpp", + "src/anomaly/DurationAnomalyTracker.cpp", + "src/anomaly/subscriber_util.cpp", + "src/condition/CombinationConditionTracker.cpp", + "src/condition/condition_util.cpp", + "src/condition/ConditionWizard.cpp", + "src/condition/SimpleConditionTracker.cpp", + "src/config/ConfigKey.cpp", + "src/config/ConfigListener.cpp", + "src/config/ConfigManager.cpp", + "src/experiment_ids.proto", + "src/external/Perfetto.cpp", + "src/external/PullResultReceiver.cpp", + "src/external/puller_util.cpp", + "src/external/StatsCallbackPuller.cpp", + "src/external/StatsPuller.cpp", + "src/external/StatsPullerManager.cpp", + "src/external/TrainInfoPuller.cpp", + "src/FieldValue.cpp", + "src/flags/flags.cpp", + "src/guardrail/StatsdStats.cpp", + "src/hash.cpp", + "src/HashableDimensionKey.cpp", + "src/logd/LogEvent.cpp", + "src/logd/LogEventQueue.cpp", + "src/matchers/CombinationAtomMatchingTracker.cpp", + "src/matchers/EventMatcherWizard.cpp", + "src/matchers/matcher_util.cpp", + "src/matchers/SimpleAtomMatchingTracker.cpp", + "src/metadata_util.cpp", + "src/metrics/CountMetricProducer.cpp", + "src/metrics/duration_helper/MaxDurationTracker.cpp", + "src/metrics/duration_helper/OringDurationTracker.cpp", + "src/metrics/DurationMetricProducer.cpp", + "src/metrics/EventMetricProducer.cpp", + "src/metrics/GaugeMetricProducer.cpp", + "src/metrics/MetricProducer.cpp", + "src/metrics/MetricsManager.cpp", + "src/metrics/parsing_utils/config_update_utils.cpp", + "src/metrics/parsing_utils/metrics_manager_util.cpp", + "src/metrics/ValueMetricProducer.cpp", + "src/packages/UidMap.cpp", + "src/shell/shell_config.proto", + "src/shell/ShellSubscriber.cpp", + "src/socket/StatsSocketListener.cpp", + "src/state/StateManager.cpp", + "src/state/StateTracker.cpp", + "src/stats_log_util.cpp", + "src/statscompanion_util.cpp", + "src/statsd_config.proto", + "src/statsd_metadata.proto", + "src/StatsLogProcessor.cpp", + "src/StatsService.cpp", + "src/storage/StorageManager.cpp", + "src/subscriber/IncidentdReporter.cpp", + "src/subscriber/SubscriberReporter.cpp", + "src/uid_data.proto", + "src/utils/MultiConditionTrigger.cpp", + ], + + local_include_dirs: [ + "src", + ], + + static_libs: [ + "libbase", + "libcutils", + "libgtest_prod", + "libprotoutil", + "libstatslog_statsd", + "libsysutils", + "libutils", + "server_configurable_flags", + "statsd-aidl-ndk_platform", + ], + shared_libs: [ + "libbinder_ndk", + "libincident", + "liblog", + ], +} + +genrule { + name: "statslog_statsd.h", + tools: ["stats-log-api-gen"], + cmd: "$(location stats-log-api-gen) --header $(genDir)/statslog_statsd.h --module statsd --namespace android,os,statsd,util", + out: [ + "statslog_statsd.h", + ], +} + +genrule { + name: "statslog_statsd.cpp", + tools: ["stats-log-api-gen"], + cmd: "$(location stats-log-api-gen) --cpp $(genDir)/statslog_statsd.cpp --module statsd --namespace android,os,statsd,util --importHeader statslog_statsd.h", + out: [ + "statslog_statsd.cpp", + ], +} + +genrule { + name: "statslog_statsdtest.h", + tools: ["stats-log-api-gen"], + cmd: "$(location stats-log-api-gen) --header $(genDir)/statslog_statsdtest.h --module statsdtest --namespace android,os,statsd,util", + out: [ + "statslog_statsdtest.h", + ], +} + +genrule { + name: "statslog_statsdtest.cpp", + tools: ["stats-log-api-gen"], + cmd: "$(location stats-log-api-gen) --cpp $(genDir)/statslog_statsdtest.cpp --module statsdtest --namespace android,os,statsd,util --importHeader statslog_statsdtest.h", + out: [ + "statslog_statsdtest.cpp", + ], +} + +cc_library_static { + name: "libstatslog_statsdtest", + generated_sources: ["statslog_statsdtest.cpp"], + generated_headers: ["statslog_statsdtest.h"], + export_generated_headers: ["statslog_statsdtest.h"], + shared_libs: [ + "libstatssocket", + ] +} + +cc_library_static { + name: "libstatslog_statsd", + generated_sources: ["statslog_statsd.cpp"], + generated_headers: ["statslog_statsd.h"], + export_generated_headers: ["statslog_statsd.h"], + apex_available: [ + "com.android.os.statsd", + "test_com.android.os.statsd", + ], + shared_libs: [ + "libstatssocket", + ] +} + +// ========= +// statsd +// ========= + +cc_binary { + name: "statsd", + defaults: ["statsd_defaults"], + + srcs: ["src/main.cpp"], + + cflags: [ + "-Wall", + "-Wextra", + "-Werror", + "-Wno-unused-parameter", + // optimize for size (protobuf glop can get big) + "-Os", + // "-g", + // "-O0", + ], + + product_variables: { + eng: { + // Enable sanitizer ONLY on eng builds + //sanitize: { + // address: true, + //}, + }, + }, + + proto: { + type: "lite", + static: true, + }, + stl: "libc++_static", + + shared_libs: [ + "libstatssocket", + ], + + apex_available: [ + "com.android.os.statsd", + "test_com.android.os.statsd", + ], +} + +// ============== +// statsd_test +// ============== + +cc_test { + name: "statsd_test", + defaults: ["statsd_defaults"], + test_suites: ["device-tests", "mts-statsd"], + test_config: "statsd_test.xml", + + //TODO(b/153588990): Remove when the build system properly separates + //32bit and 64bit architectures. + compile_multilib: "both", + multilib: { + lib64: { + suffix: "64", + }, + lib32: { + suffix: "32", + }, + }, + + cflags: [ + "-Wall", + "-Werror", + "-Wno-missing-field-initializers", + "-Wno-unused-variable", + "-Wno-unused-function", + "-Wno-unused-parameter", + ], + + require_root: true, + + srcs: [ + // atom_field_options.proto needs field_options.proto, but that is + // not included in libprotobuf-cpp-lite, so compile it here. + ":libprotobuf-internal-protos", + ":libstats_internal_protos", + + "src/shell/shell_data.proto", + "src/stats_log.proto", + "tests/AlarmMonitor_test.cpp", + "tests/anomaly/AlarmTracker_test.cpp", + "tests/anomaly/AnomalyTracker_test.cpp", + "tests/condition/CombinationConditionTracker_test.cpp", + "tests/condition/ConditionTimer_test.cpp", + "tests/condition/SimpleConditionTracker_test.cpp", + "tests/ConfigManager_test.cpp", + "tests/e2e/Alarm_e2e_test.cpp", + "tests/e2e/Anomaly_count_e2e_test.cpp", + "tests/e2e/Anomaly_duration_sum_e2e_test.cpp", + "tests/e2e/Attribution_e2e_test.cpp", + "tests/e2e/ConfigTtl_e2e_test.cpp", + "tests/e2e/ConfigUpdate_e2e_ab_test.cpp", + "tests/e2e/ConfigUpdate_e2e_test.cpp", + "tests/e2e/CountMetric_e2e_test.cpp", + "tests/e2e/DurationMetric_e2e_test.cpp", + "tests/e2e/GaugeMetric_e2e_pull_test.cpp", + "tests/e2e/GaugeMetric_e2e_push_test.cpp", + "tests/e2e/MetricActivation_e2e_test.cpp", + "tests/e2e/MetricConditionLink_e2e_test.cpp", + "tests/e2e/PartialBucket_e2e_test.cpp", + "tests/e2e/ValueMetric_pull_e2e_test.cpp", + "tests/e2e/WakelockDuration_e2e_test.cpp", + "tests/external/puller_util_test.cpp", + "tests/external/StatsCallbackPuller_test.cpp", + "tests/external/StatsPuller_test.cpp", + "tests/external/StatsPullerManager_test.cpp", + "tests/FieldValue_test.cpp", + "tests/guardrail/StatsdStats_test.cpp", + "tests/HashableDimensionKey_test.cpp", + "tests/indexed_priority_queue_test.cpp", + "tests/log_event/LogEventQueue_test.cpp", + "tests/LogEntryMatcher_test.cpp", + "tests/LogEvent_test.cpp", + "tests/metadata_util_test.cpp", + "tests/metrics/CountMetricProducer_test.cpp", + "tests/metrics/DurationMetricProducer_test.cpp", + "tests/metrics/EventMetricProducer_test.cpp", + "tests/metrics/GaugeMetricProducer_test.cpp", + "tests/metrics/MaxDurationTracker_test.cpp", + "tests/metrics/metrics_test_helper.cpp", + "tests/metrics/OringDurationTracker_test.cpp", + "tests/metrics/ValueMetricProducer_test.cpp", + "tests/metrics/parsing_utils/config_update_utils_test.cpp", + "tests/metrics/parsing_utils/metrics_manager_util_test.cpp", + "tests/subscriber/SubscriberReporter_test.cpp", + "tests/MetricsManager_test.cpp", + "tests/shell/ShellSubscriber_test.cpp", + "tests/state/StateTracker_test.cpp", + "tests/statsd_test_util.cpp", + "tests/StatsLogProcessor_test.cpp", + "tests/StatsService_test.cpp", + "tests/storage/StorageManager_test.cpp", + "tests/UidMap_test.cpp", + "tests/utils/MultiConditionTrigger_test.cpp", + ], + + static_libs: [ + "libgmock", + "libplatformprotos", + "libstatslog_statsdtest", + "libstatssocket_private", + ], + + proto: { + type: "lite", + include_dirs: [ + "external/protobuf/src", + "frameworks/proto_logging/stats", + ], + }, + +} + +//############################# +// statsd micro benchmark +//############################# + +cc_benchmark { + name: "statsd_benchmark", + defaults: ["statsd_defaults"], + + srcs: [ + // atom_field_options.proto needs field_options.proto, but that is + // not included in libprotobuf-cpp-lite, so compile it here. + ":libprotobuf-internal-protos", + ":libstats_internal_protos", + + "benchmark/duration_metric_benchmark.cpp", + "benchmark/filter_value_benchmark.cpp", + "benchmark/get_dimensions_for_condition_benchmark.cpp", + "benchmark/hello_world_benchmark.cpp", + "benchmark/log_event_benchmark.cpp", + "benchmark/main.cpp", + "benchmark/metric_util.cpp", + "benchmark/stats_write_benchmark.cpp", + "src/stats_log.proto", + ], + + proto: { + type: "lite", + include_dirs: [ + "external/protobuf/src", + "frameworks/proto_logging/stats", + ], + }, + + cflags: [ + "-Wall", + "-Werror", + "-Wno-unused-parameter", + "-Wno-unused-variable", + "-Wno-unused-function", + + // Bug: http://b/29823425 Disable -Wvarargs for Clang update to r271374 + "-Wno-varargs", + ], + + static_libs: [ + "libplatformprotos", + "libstatssocket_private", + ], + + shared_libs: [ + "libgtest_prod", + "libprotobuf-cpp-lite", + "libstatslog", + ], +} + +// ==== java proto device library (for test only) ============================== +java_library { + name: "statsdprotolite", + sdk_version: "core_current", + proto: { + type: "lite", + include_dirs: [ + "external/protobuf/src", + "frameworks/proto_logging/stats", + ], + }, + + srcs: [ + ":libstats_atoms_proto", + "src/shell/shell_config.proto", + "src/shell/shell_data.proto", + "src/stats_log.proto", + "src/statsd_config.proto", + ], + + static_libs: [ + "platformprotoslite", + ], + // Protos have lots of MissingOverride and similar. + errorprone: { + javacflags: ["-XepDisableAllChecks"], + }, +} + +// Filegroup for statsd config proto definition. +filegroup { + name: "statsd-config-proto-def", + srcs: ["src/statsd_config.proto"], +} + +// Filegroup for all statsd protos +filegroup { + name: "statsd_internal_protos", + srcs: [ + "src/active_config_list.proto", + "src/experiment_ids.proto", + "src/shell/shell_config.proto", + "src/shell/shell_data.proto", + "src/statsd_config.proto", + "src/statsd_metadata.proto", + "src/stats_log.proto", + "src/uid_data.proto", + ], +} diff --git a/statsd/TEST_MAPPING b/statsd/TEST_MAPPING new file mode 100644 index 00000000..8dee073a --- /dev/null +++ b/statsd/TEST_MAPPING @@ -0,0 +1,7 @@ +{ + "presubmit" : [ + { + "name" : "statsd_test" + } + ] +} \ No newline at end of file diff --git a/statsd/benchmark/duration_metric_benchmark.cpp b/statsd/benchmark/duration_metric_benchmark.cpp new file mode 100644 index 00000000..2d315d93 --- /dev/null +++ b/statsd/benchmark/duration_metric_benchmark.cpp @@ -0,0 +1,314 @@ +/* + * Copyright (C) 2018 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 "benchmark/benchmark.h" +#include "FieldValue.h" +#include "HashableDimensionKey.h" +#include "logd/LogEvent.h" +#include "stats_log_util.h" +#include "metric_util.h" + +namespace android { +namespace os { +namespace statsd { + +using std::vector; + +static StatsdConfig CreateDurationMetricConfig_NoLink_AND_CombinationCondition( + DurationMetric::AggregationType aggregationType, bool addExtraDimensionInCondition) { + StatsdConfig config; + *config.add_atom_matcher() = CreateStartScheduledJobAtomMatcher(); + *config.add_atom_matcher() = CreateFinishScheduledJobAtomMatcher(); + *config.add_atom_matcher() = CreateSyncStartAtomMatcher(); + *config.add_atom_matcher() = CreateSyncEndAtomMatcher(); + *config.add_atom_matcher() = CreateScreenTurnedOnAtomMatcher(); + *config.add_atom_matcher() = CreateScreenTurnedOffAtomMatcher(); + + auto scheduledJobPredicate = CreateScheduledJobPredicate(); + auto dimensions = scheduledJobPredicate.mutable_simple_predicate()->mutable_dimensions(); + dimensions->set_field(android::util::SCHEDULED_JOB_STATE_CHANGED); + dimensions->add_child()->set_field(2); // job name field. + + auto screenIsOffPredicate = CreateScreenIsOffPredicate(); + + auto isSyncingPredicate = CreateIsSyncingPredicate(); + auto syncDimension = isSyncingPredicate.mutable_simple_predicate()->mutable_dimensions(); + *syncDimension = CreateAttributionUidAndTagDimensions(android::util::SYNC_STATE_CHANGED, + {Position::FIRST}); + if (addExtraDimensionInCondition) { + syncDimension->add_child()->set_field(2 /* name field*/); + } + + *config.add_predicate() = scheduledJobPredicate; + *config.add_predicate() = screenIsOffPredicate; + *config.add_predicate() = isSyncingPredicate; + auto combinationPredicate = config.add_predicate(); + combinationPredicate->set_id(StringToId("CombinationPredicate")); + combinationPredicate->mutable_combination()->set_operation(LogicalOperation::AND); + addPredicateToPredicateCombination(screenIsOffPredicate, combinationPredicate); + addPredicateToPredicateCombination(isSyncingPredicate, combinationPredicate); + + auto metric = config.add_duration_metric(); + metric->set_bucket(FIVE_MINUTES); + metric->set_id(StringToId("scheduledJob")); + metric->set_what(scheduledJobPredicate.id()); + metric->set_condition(combinationPredicate->id()); + metric->set_aggregation_type(aggregationType); + auto dimensionWhat = metric->mutable_dimensions_in_what(); + dimensionWhat->set_field(android::util::SCHEDULED_JOB_STATE_CHANGED); + dimensionWhat->add_child()->set_field(2); // job name field. + *metric->mutable_dimensions_in_condition() = CreateAttributionUidAndTagDimensions( + android::util::SYNC_STATE_CHANGED, {Position::FIRST}); + return config; +} + +static StatsdConfig CreateDurationMetricConfig_Link_AND_CombinationCondition( + DurationMetric::AggregationType aggregationType, bool addExtraDimensionInCondition) { + StatsdConfig config; + *config.add_atom_matcher() = CreateStartScheduledJobAtomMatcher(); + *config.add_atom_matcher() = CreateFinishScheduledJobAtomMatcher(); + *config.add_atom_matcher() = CreateSyncStartAtomMatcher(); + *config.add_atom_matcher() = CreateSyncEndAtomMatcher(); + *config.add_atom_matcher() = CreateScreenTurnedOnAtomMatcher(); + *config.add_atom_matcher() = CreateScreenTurnedOffAtomMatcher(); + + auto scheduledJobPredicate = CreateScheduledJobPredicate(); + auto dimensions = scheduledJobPredicate.mutable_simple_predicate()->mutable_dimensions(); + *dimensions = CreateAttributionUidDimensions( + android::util::SCHEDULED_JOB_STATE_CHANGED, {Position::FIRST}); + dimensions->add_child()->set_field(2); // job name field. + + auto isSyncingPredicate = CreateIsSyncingPredicate(); + auto syncDimension = isSyncingPredicate.mutable_simple_predicate()->mutable_dimensions(); + *syncDimension = CreateAttributionUidDimensions( + android::util::SYNC_STATE_CHANGED, {Position::FIRST}); + if (addExtraDimensionInCondition) { + syncDimension->add_child()->set_field(2 /* name field*/); + } + + auto screenIsOffPredicate = CreateScreenIsOffPredicate(); + + *config.add_predicate() = scheduledJobPredicate; + *config.add_predicate() = screenIsOffPredicate; + *config.add_predicate() = isSyncingPredicate; + auto combinationPredicate = config.add_predicate(); + combinationPredicate->set_id(StringToId("CombinationPredicate")); + combinationPredicate->mutable_combination()->set_operation(LogicalOperation::AND); + addPredicateToPredicateCombination(screenIsOffPredicate, combinationPredicate); + addPredicateToPredicateCombination(isSyncingPredicate, combinationPredicate); + + auto metric = config.add_duration_metric(); + metric->set_bucket(FIVE_MINUTES); + metric->set_id(StringToId("scheduledJob")); + metric->set_what(scheduledJobPredicate.id()); + metric->set_condition(combinationPredicate->id()); + metric->set_aggregation_type(aggregationType); + *metric->mutable_dimensions_in_what() = CreateAttributionUidDimensions( + android::util::SCHEDULED_JOB_STATE_CHANGED, {Position::FIRST}); + + auto links = metric->add_links(); + links->set_condition(isSyncingPredicate.id()); + *links->mutable_fields_in_what() = + CreateAttributionUidDimensions( + android::util::SCHEDULED_JOB_STATE_CHANGED, {Position::FIRST}); + *links->mutable_fields_in_condition() = + CreateAttributionUidDimensions(android::util::SYNC_STATE_CHANGED, {Position::FIRST}); + return config; +} + +static void BM_DurationMetricNoLink(benchmark::State& state) { + ConfigKey cfgKey; + auto config = CreateDurationMetricConfig_NoLink_AND_CombinationCondition( + DurationMetric::SUM, false); + int64_t bucketStartTimeNs = 10000000000; + int64_t bucketSizeNs = + TimeUnitToBucketSizeInMillis(config.duration_metric(0).bucket()) * 1000000LL; + + std::vector> events; + + events.push_back(CreateScreenStateChangedEvent(bucketStartTimeNs + 11, + android::view::DISPLAY_STATE_OFF)); + events.push_back( + CreateScreenStateChangedEvent(bucketStartTimeNs + 40, android::view::DISPLAY_STATE_ON)); + + events.push_back(CreateScreenStateChangedEvent(bucketStartTimeNs + 102, + android::view::DISPLAY_STATE_OFF)); + events.push_back(CreateScreenStateChangedEvent(bucketStartTimeNs + 450, + android::view::DISPLAY_STATE_ON)); + + events.push_back(CreateScreenStateChangedEvent(bucketStartTimeNs + 650, + android::view::DISPLAY_STATE_OFF)); + events.push_back(CreateScreenStateChangedEvent(bucketStartTimeNs + bucketSizeNs + 100, + android::view::DISPLAY_STATE_ON)); + + events.push_back(CreateScreenStateChangedEvent(bucketStartTimeNs + bucketSizeNs + 640, + android::view::DISPLAY_STATE_OFF)); + events.push_back(CreateScreenStateChangedEvent(bucketStartTimeNs + bucketSizeNs + 650, + android::view::DISPLAY_STATE_ON)); + + vector attributionUids1 = {9999}; + vector attributionTags1 = {""}; + events.push_back(CreateStartScheduledJobEvent(bucketStartTimeNs + 2, attributionUids1, + attributionTags1, "job0")); + events.push_back(CreateFinishScheduledJobEvent(bucketStartTimeNs + 101, attributionUids1, + attributionTags1, "job0")); + + events.push_back(CreateStartScheduledJobEvent(bucketStartTimeNs + 201, attributionUids1, + attributionTags1, "job2")); + events.push_back(CreateFinishScheduledJobEvent(bucketStartTimeNs + 500, attributionUids1, + attributionTags1, "job2")); + + vector attributionUids2 = {8888}; + events.push_back(CreateStartScheduledJobEvent(bucketStartTimeNs + 600, attributionUids2, + attributionTags1, "job2")); + events.push_back(CreateFinishScheduledJobEvent(bucketStartTimeNs + bucketSizeNs + 850, + attributionUids2, attributionTags1, "job2")); + + events.push_back(CreateStartScheduledJobEvent(bucketStartTimeNs + bucketSizeNs + 600, + attributionUids2, attributionTags1, "job1")); + events.push_back(CreateFinishScheduledJobEvent(bucketStartTimeNs + bucketSizeNs + 900, + attributionUids2, attributionTags1, "job1")); + + vector attributionUids3 = {111, 222, 222}; + vector attributionTags3 = {"App1", "GMSCoreModule1", "GMSCoreModule2"}; + events.push_back(CreateSyncStartEvent(bucketStartTimeNs + 10, attributionUids3, + attributionTags3, "ReadEmail")); + events.push_back(CreateSyncEndEvent(bucketStartTimeNs + 50, attributionUids3, attributionTags3, + "ReadEmail")); + + events.push_back(CreateSyncStartEvent(bucketStartTimeNs + 200, attributionUids3, + attributionTags3, "ReadEmail")); + events.push_back(CreateSyncEndEvent(bucketStartTimeNs + bucketSizeNs + 300, attributionUids3, + attributionTags3, "ReadEmail")); + + events.push_back(CreateSyncStartEvent(bucketStartTimeNs + 400, attributionUids3, + attributionTags3, "ReadDoc")); + events.push_back(CreateSyncEndEvent(bucketStartTimeNs + bucketSizeNs - 1, attributionUids3, + attributionTags3, "ReadDoc")); + + vector attributionUids4 = {333, 222, 555}; + vector attributionTags4 = {"App2", "GMSCoreModule1", "GMSCoreModule2"}; + events.push_back(CreateSyncStartEvent(bucketStartTimeNs + 401, attributionUids4, + attributionTags4, "ReadEmail")); + events.push_back(CreateSyncEndEvent(bucketStartTimeNs + bucketSizeNs + 700, attributionUids4, + attributionTags4, "ReadEmail")); + sortLogEventsByTimestamp(&events); + + while (state.KeepRunning()) { + auto processor = CreateStatsLogProcessor( + bucketStartTimeNs / NS_PER_SEC, config, cfgKey); + for (const auto& event : events) { + processor->OnLogEvent(event.get()); + } + } +} + +BENCHMARK(BM_DurationMetricNoLink); + + +static void BM_DurationMetricLink(benchmark::State& state) { + ConfigKey cfgKey; + auto config = CreateDurationMetricConfig_Link_AND_CombinationCondition( + DurationMetric::SUM, false); + int64_t bucketStartTimeNs = 10000000000; + int64_t bucketSizeNs = + TimeUnitToBucketSizeInMillis(config.duration_metric(0).bucket()) * 1000000LL; + + std::vector> events; + + events.push_back(CreateScreenStateChangedEvent(bucketStartTimeNs + 55, + android::view::DISPLAY_STATE_OFF)); + events.push_back(CreateScreenStateChangedEvent(bucketStartTimeNs + 120, + android::view::DISPLAY_STATE_ON)); + events.push_back(CreateScreenStateChangedEvent(bucketStartTimeNs + 121, + android::view::DISPLAY_STATE_OFF)); + events.push_back(CreateScreenStateChangedEvent(bucketStartTimeNs + 450, + android::view::DISPLAY_STATE_ON)); + + events.push_back(CreateScreenStateChangedEvent(bucketStartTimeNs + 501, + android::view::DISPLAY_STATE_OFF)); + events.push_back(CreateScreenStateChangedEvent(bucketStartTimeNs + bucketSizeNs + 100, + android::view::DISPLAY_STATE_ON)); + + vector attributionUids1 = {111}; + vector attributionTags1 = {"App1"}; + events.push_back(CreateStartScheduledJobEvent(bucketStartTimeNs + 1, attributionUids1, + attributionTags1, "job1")); + events.push_back(CreateFinishScheduledJobEvent(bucketStartTimeNs + 101, attributionUids1, + attributionTags1, "job1")); + + vector attributionUids2 = {333}; + vector attributionTags2 = {"App2"}; + events.push_back(CreateStartScheduledJobEvent(bucketStartTimeNs + 201, attributionUids2, + attributionTags2, "job2")); + events.push_back(CreateFinishScheduledJobEvent(bucketStartTimeNs + 500, attributionUids2, + attributionTags2, "job2")); + events.push_back(CreateStartScheduledJobEvent(bucketStartTimeNs + 600, attributionUids2, + attributionTags2, "job2")); + events.push_back(CreateFinishScheduledJobEvent(bucketStartTimeNs + bucketSizeNs + 850, + attributionUids2, attributionTags2, "job2")); + + vector attributionUids3 = {444}; + vector attributionTags3 = {"App3"}; + events.push_back(CreateStartScheduledJobEvent(bucketStartTimeNs + bucketSizeNs - 2, + attributionUids3, attributionTags3, "job3")); + events.push_back(CreateFinishScheduledJobEvent(bucketStartTimeNs + bucketSizeNs + 900, + attributionUids3, attributionTags3, "job3")); + + vector attributionUids4 = {111, 222, 222}; + vector attributionTags4 = {"App1", "GMSCoreModule1", "GMSCoreModule2"}; + events.push_back(CreateSyncStartEvent(bucketStartTimeNs + 50, attributionUids4, + attributionTags4, "ReadEmail")); + events.push_back(CreateSyncEndEvent(bucketStartTimeNs + 110, attributionUids4, attributionTags4, + "ReadEmail")); + + vector attributionUids5 = {333, 222, 555}; + vector attributionTags5 = {"App2", "GMSCoreModule1", "GMSCoreModule2"}; + events.push_back(CreateSyncStartEvent(bucketStartTimeNs + 300, attributionUids5, + attributionTags5, "ReadEmail")); + events.push_back(CreateSyncEndEvent(bucketStartTimeNs + bucketSizeNs + 700, attributionUids5, + attributionTags5, "ReadEmail")); + events.push_back(CreateSyncStartEvent(bucketStartTimeNs + 400, attributionUids5, + attributionTags5, "ReadDoc")); + events.push_back(CreateSyncEndEvent(bucketStartTimeNs + bucketSizeNs - 1, attributionUids5, + attributionTags5, "ReadDoc")); + + vector attributionUids6 = {444, 222, 555}; + vector attributionTags6 = {"App3", "GMSCoreModule1", "GMSCoreModule2"}; + events.push_back(CreateSyncStartEvent(bucketStartTimeNs + 550, attributionUids6, + attributionTags6, "ReadDoc")); + events.push_back(CreateSyncEndEvent(bucketStartTimeNs + 800, attributionUids6, attributionTags6, + "ReadDoc")); + events.push_back(CreateSyncStartEvent(bucketStartTimeNs + bucketSizeNs - 1, attributionUids6, + attributionTags6, "ReadDoc")); + events.push_back(CreateSyncEndEvent(bucketStartTimeNs + bucketSizeNs + 700, attributionUids6, + attributionTags6, "ReadDoc")); + sortLogEventsByTimestamp(&events); + + while (state.KeepRunning()) { + auto processor = CreateStatsLogProcessor( + bucketStartTimeNs / NS_PER_SEC, config, cfgKey); + for (const auto& event : events) { + processor->OnLogEvent(event.get()); + } + } +} + +BENCHMARK(BM_DurationMetricLink); + +} // namespace statsd +} // namespace os +} // namespace android diff --git a/statsd/benchmark/filter_value_benchmark.cpp b/statsd/benchmark/filter_value_benchmark.cpp new file mode 100644 index 00000000..743ccc4e --- /dev/null +++ b/statsd/benchmark/filter_value_benchmark.cpp @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2018 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 "FieldValue.h" +#include "HashableDimensionKey.h" +#include "benchmark/benchmark.h" +#include "logd/LogEvent.h" +#include "metric_util.h" +#include "stats_event.h" +#include "stats_log_util.h" + +namespace android { +namespace os { +namespace statsd { + +using std::vector; + +static void createLogEventAndMatcher(LogEvent* event, FieldMatcher* field_matcher) { + AStatsEvent* statsEvent = AStatsEvent_obtain(); + AStatsEvent_setAtomId(statsEvent, 1); + AStatsEvent_overwriteTimestamp(statsEvent, 100000); + + std::vector attributionUids = {100, 100}; + std::vector attributionTags = {"LOCATION", "LOCATION"}; + writeAttribution(statsEvent, attributionUids, attributionTags); + + AStatsEvent_writeFloat(statsEvent, 3.2f); + AStatsEvent_writeString(statsEvent, "LOCATION"); + AStatsEvent_writeInt64(statsEvent, 990); + + parseStatsEventToLogEvent(statsEvent, event); + + field_matcher->set_field(1); + auto child = field_matcher->add_child(); + child->set_field(1); + child->set_position(FIRST); + child->add_child()->set_field(1); +} + +static void BM_FilterValue(benchmark::State& state) { + LogEvent event(/*uid=*/0, /*pid=*/0); + FieldMatcher field_matcher; + createLogEventAndMatcher(&event, &field_matcher); + + std::vector matchers; + translateFieldMatcher(field_matcher, &matchers); + + while (state.KeepRunning()) { + HashableDimensionKey output; + filterValues(matchers, event.getValues(), &output); + } +} + +BENCHMARK(BM_FilterValue); + +} // namespace statsd +} // namespace os +} // namespace android diff --git a/statsd/benchmark/get_dimensions_for_condition_benchmark.cpp b/statsd/benchmark/get_dimensions_for_condition_benchmark.cpp new file mode 100644 index 00000000..7a455650 --- /dev/null +++ b/statsd/benchmark/get_dimensions_for_condition_benchmark.cpp @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2018 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 "FieldValue.h" +#include "HashableDimensionKey.h" +#include "benchmark/benchmark.h" +#include "logd/LogEvent.h" +#include "metric_util.h" +#include "stats_event.h" +#include "stats_log_util.h" + +namespace android { +namespace os { +namespace statsd { + +using std::vector; + +static void createLogEventAndLink(LogEvent* event, Metric2Condition *link) { + AStatsEvent* statsEvent = AStatsEvent_obtain(); + AStatsEvent_setAtomId(statsEvent, 1); + AStatsEvent_overwriteTimestamp(statsEvent, 100000); + + std::vector attributionUids = {100, 100}; + std::vector attributionTags = {"LOCATION", "LOCATION"}; + writeAttribution(statsEvent, attributionUids, attributionTags); + + AStatsEvent_writeFloat(statsEvent, 3.2f); + AStatsEvent_writeString(statsEvent, "LOCATION"); + AStatsEvent_writeInt64(statsEvent, 990); + + parseStatsEventToLogEvent(statsEvent, event); + + link->conditionId = 1; + + FieldMatcher field_matcher; + field_matcher.set_field(event->GetTagId()); + auto child = field_matcher.add_child(); + child->set_field(1); + child->set_position(FIRST); + child->add_child()->set_field(1); + + translateFieldMatcher(field_matcher, &link->metricFields); + field_matcher.set_field(event->GetTagId() + 1); + translateFieldMatcher(field_matcher, &link->conditionFields); +} + +static void BM_GetDimensionInCondition(benchmark::State& state) { + Metric2Condition link; + LogEvent event(/*uid=*/0, /*pid=*/0); + createLogEventAndLink(&event, &link); + + while (state.KeepRunning()) { + HashableDimensionKey output; + getDimensionForCondition(event.getValues(), link, &output); + } +} + +BENCHMARK(BM_GetDimensionInCondition); + + +} // namespace statsd +} // namespace os +} // namespace android diff --git a/statsd/benchmark/hello_world_benchmark.cpp b/statsd/benchmark/hello_world_benchmark.cpp new file mode 100644 index 00000000..c732d394 --- /dev/null +++ b/statsd/benchmark/hello_world_benchmark.cpp @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2018 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 "benchmark/benchmark.h" + +static void BM_StringCreation(benchmark::State& state) { + while (state.KeepRunning()) std::string empty_string; +} +// Register the function as a benchmark +BENCHMARK(BM_StringCreation); + +// Define another benchmark +static void BM_StringCopy(benchmark::State& state) { + std::string x = "hello"; + while (state.KeepRunning()) std::string copy(x); +} +BENCHMARK(BM_StringCopy); diff --git a/statsd/benchmark/log_event_benchmark.cpp b/statsd/benchmark/log_event_benchmark.cpp new file mode 100644 index 00000000..057e00bd --- /dev/null +++ b/statsd/benchmark/log_event_benchmark.cpp @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2018 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 "benchmark/benchmark.h" +#include "logd/LogEvent.h" +#include "stats_event.h" + +namespace android { +namespace os { +namespace statsd { + +static size_t createAndParseStatsEvent(uint8_t* msg) { + AStatsEvent* event = AStatsEvent_obtain(); + AStatsEvent_setAtomId(event, 100); + AStatsEvent_writeInt32(event, 2); + AStatsEvent_writeFloat(event, 2.0); + AStatsEvent_build(event); + + size_t size; + uint8_t* buf = AStatsEvent_getBuffer(event, &size); + memcpy(msg, buf, size); + return size; +} + +static void BM_LogEventCreation(benchmark::State& state) { + uint8_t msg[LOGGER_ENTRY_MAX_PAYLOAD]; + size_t size = createAndParseStatsEvent(msg); + while (state.KeepRunning()) { + LogEvent event(/*uid=*/ 1000, /*pid=*/ 1001); + benchmark::DoNotOptimize(event.parseBuffer(msg, size)); + } +} +BENCHMARK(BM_LogEventCreation); + +} // namespace statsd +} // namespace os +} // namespace android diff --git a/statsd/benchmark/main.cpp b/statsd/benchmark/main.cpp new file mode 100644 index 00000000..08921f3c --- /dev/null +++ b/statsd/benchmark/main.cpp @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2018 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 + +BENCHMARK_MAIN(); diff --git a/statsd/benchmark/metric_util.cpp b/statsd/benchmark/metric_util.cpp new file mode 100644 index 00000000..89fd3d9b --- /dev/null +++ b/statsd/benchmark/metric_util.cpp @@ -0,0 +1,379 @@ +// 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 "metric_util.h" + +#include "stats_event.h" + +namespace android { +namespace os { +namespace statsd { + +AtomMatcher CreateSimpleAtomMatcher(const string& name, int atomId) { + 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(atomId); + return atom_matcher; +} + +AtomMatcher CreateScheduledJobStateChangedAtomMatcher(const string& name, + ScheduledJobStateChanged::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::SCHEDULED_JOB_STATE_CHANGED); + auto field_value_matcher = simple_atom_matcher->add_field_value_matcher(); + field_value_matcher->set_field(3); // State field. + field_value_matcher->set_eq_int(state); + return atom_matcher; +} + +AtomMatcher CreateStartScheduledJobAtomMatcher() { + return CreateScheduledJobStateChangedAtomMatcher("ScheduledJobStart", + ScheduledJobStateChanged::STARTED); +} + +AtomMatcher CreateFinishScheduledJobAtomMatcher() { + return CreateScheduledJobStateChangedAtomMatcher("ScheduledJobFinish", + ScheduledJobStateChanged::FINISHED); +} + +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; + atom_matcher.set_id(StringToId(name)); + auto simple_atom_matcher = atom_matcher.mutable_simple_atom_matcher(); + simple_atom_matcher->set_atom_id(android::util::WAKELOCK_STATE_CHANGED); + auto field_value_matcher = simple_atom_matcher->add_field_value_matcher(); + field_value_matcher->set_field(4); // State field. + field_value_matcher->set_eq_int(state); + return atom_matcher; +} + +AtomMatcher CreateAcquireWakelockAtomMatcher() { + return CreateWakelockStateChangedAtomMatcher("AcquireWakelock", WakelockStateChanged::ACQUIRE); +} + +AtomMatcher CreateReleaseWakelockAtomMatcher() { + return CreateWakelockStateChangedAtomMatcher("ReleaseWakelock", WakelockStateChanged::RELEASE); +} + +AtomMatcher CreateScreenStateChangedAtomMatcher( + const string& name, android::view::DisplayStateEnum 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::SCREEN_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 CreateScreenTurnedOnAtomMatcher() { + return CreateScreenStateChangedAtomMatcher("ScreenTurnedOn", + android::view::DisplayStateEnum::DISPLAY_STATE_ON); +} + +AtomMatcher CreateScreenTurnedOffAtomMatcher() { + return CreateScreenStateChangedAtomMatcher("ScreenTurnedOff", + ::android::view::DisplayStateEnum::DISPLAY_STATE_OFF); +} + +AtomMatcher CreateSyncStateChangedAtomMatcher( + const string& name, SyncStateChanged::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::SYNC_STATE_CHANGED); + auto field_value_matcher = simple_atom_matcher->add_field_value_matcher(); + field_value_matcher->set_field(3); // State field. + field_value_matcher->set_eq_int(state); + return atom_matcher; +} + +AtomMatcher CreateSyncStartAtomMatcher() { + return CreateSyncStateChangedAtomMatcher("SyncStart", SyncStateChanged::ON); +} + +AtomMatcher CreateSyncEndAtomMatcher() { + return CreateSyncStateChangedAtomMatcher("SyncEnd", SyncStateChanged::OFF); +} + +AtomMatcher CreateActivityForegroundStateChangedAtomMatcher( + const string& name, ActivityForegroundStateChanged::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::ACTIVITY_FOREGROUND_STATE_CHANGED); + auto field_value_matcher = simple_atom_matcher->add_field_value_matcher(); + field_value_matcher->set_field(4); // Activity field. + field_value_matcher->set_eq_int(state); + return atom_matcher; +} + +AtomMatcher CreateMoveToBackgroundAtomMatcher() { + return CreateActivityForegroundStateChangedAtomMatcher( + "MoveToBackground", ActivityForegroundStateChanged::BACKGROUND); +} + +AtomMatcher CreateMoveToForegroundAtomMatcher() { + return CreateActivityForegroundStateChangedAtomMatcher( + "MoveToForeground", ActivityForegroundStateChanged::FOREGROUND); +} + +Predicate CreateScheduledJobPredicate() { + Predicate predicate; + predicate.set_id(StringToId("ScheduledJobRunningPredicate")); + predicate.mutable_simple_predicate()->set_start(StringToId("ScheduledJobStart")); + predicate.mutable_simple_predicate()->set_stop(StringToId("ScheduledJobFinish")); + return predicate; +} + +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; + predicate.set_id(StringToId("ScreenIsOn")); + predicate.mutable_simple_predicate()->set_start(StringToId("ScreenTurnedOn")); + predicate.mutable_simple_predicate()->set_stop(StringToId("ScreenTurnedOff")); + return predicate; +} + +Predicate CreateScreenIsOffPredicate() { + Predicate predicate; + predicate.set_id(1111123); + predicate.mutable_simple_predicate()->set_start(StringToId("ScreenTurnedOff")); + predicate.mutable_simple_predicate()->set_stop(StringToId("ScreenTurnedOn")); + return predicate; +} + +Predicate CreateHoldingWakelockPredicate() { + Predicate predicate; + predicate.set_id(StringToId("HoldingWakelock")); + predicate.mutable_simple_predicate()->set_start(StringToId("AcquireWakelock")); + predicate.mutable_simple_predicate()->set_stop(StringToId("ReleaseWakelock")); + return predicate; +} + +Predicate CreateIsSyncingPredicate() { + Predicate predicate; + predicate.set_id(33333333333333); + predicate.mutable_simple_predicate()->set_start(StringToId("SyncStart")); + predicate.mutable_simple_predicate()->set_stop(StringToId("SyncEnd")); + return predicate; +} + +Predicate CreateIsInBackgroundPredicate() { + Predicate predicate; + predicate.set_id(StringToId("IsInBackground")); + predicate.mutable_simple_predicate()->set_start(StringToId("MoveToBackground")); + predicate.mutable_simple_predicate()->set_stop(StringToId("MoveToForeground")); + return predicate; +} + +void addPredicateToPredicateCombination(const Predicate& predicate, + Predicate* combinationPredicate) { + combinationPredicate->mutable_combination()->add_predicate(predicate.id()); +} + +FieldMatcher CreateAttributionUidDimensions(const int atomId, + const std::vector& positions) { + FieldMatcher dimensions; + dimensions.set_field(atomId); + for (const auto position : positions) { + auto child = dimensions.add_child(); + child->set_field(1); + child->set_position(position); + child->add_child()->set_field(1); + } + return dimensions; +} + +FieldMatcher CreateAttributionUidAndTagDimensions(const int atomId, + const std::vector& positions) { + FieldMatcher dimensions; + dimensions.set_field(atomId); + for (const auto position : positions) { + auto child = dimensions.add_child(); + child->set_field(1); + child->set_position(position); + child->add_child()->set_field(1); + child->add_child()->set_field(2); + } + return dimensions; +} + +FieldMatcher CreateDimensions(const int atomId, const std::vector& fields) { + FieldMatcher dimensions; + dimensions.set_field(atomId); + for (const int field : fields) { + dimensions.add_child()->set_field(field); + } + return dimensions; +} + +void writeAttribution(AStatsEvent* statsEvent, const vector& attributionUids, + const vector& attributionTags) { + vector cTags(attributionTags.size()); + for (int i = 0; i < cTags.size(); i++) { + cTags[i] = attributionTags[i].c_str(); + } + + AStatsEvent_writeAttributionChain(statsEvent, + reinterpret_cast(attributionUids.data()), + cTags.data(), attributionUids.size()); +} + +void parseStatsEventToLogEvent(AStatsEvent* statsEvent, LogEvent* logEvent) { + AStatsEvent_build(statsEvent); + + size_t size; + uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size); + logEvent->parseBuffer(buf, size); + + AStatsEvent_release(statsEvent); +} + +std::unique_ptr CreateScreenStateChangedEvent( + uint64_t timestampNs, const android::view::DisplayStateEnum state) { + AStatsEvent* statsEvent = AStatsEvent_obtain(); + AStatsEvent_setAtomId(statsEvent, util::SCREEN_STATE_CHANGED); + AStatsEvent_overwriteTimestamp(statsEvent, timestampNs); + AStatsEvent_writeInt32(statsEvent, state); + + std::unique_ptr logEvent = std::make_unique(/*uid=*/0, /*pid=*/0); + parseStatsEventToLogEvent(statsEvent, logEvent.get()); + return logEvent; +} + +std::unique_ptr CreateScheduledJobStateChangedEvent( + const vector& attributionUids, const vector& attributionTags, + const string& jobName, const ScheduledJobStateChanged::State state, uint64_t timestampNs) { + AStatsEvent* statsEvent = AStatsEvent_obtain(); + AStatsEvent_setAtomId(statsEvent, util::SCHEDULED_JOB_STATE_CHANGED); + AStatsEvent_overwriteTimestamp(statsEvent, timestampNs); + + writeAttribution(statsEvent, attributionUids, attributionTags); + AStatsEvent_writeString(statsEvent, jobName.c_str()); + AStatsEvent_writeInt32(statsEvent, state); + + std::unique_ptr logEvent = std::make_unique(/*uid=*/0, /*pid=*/0); + parseStatsEventToLogEvent(statsEvent, logEvent.get()); + return logEvent; +} + +std::unique_ptr CreateStartScheduledJobEvent(uint64_t timestampNs, + const vector& attributionUids, + const vector& attributionTags, + const string& jobName) { + return CreateScheduledJobStateChangedEvent(attributionUids, attributionTags, jobName, + ScheduledJobStateChanged::STARTED, timestampNs); +} + +// Create log event when scheduled job finishes. +std::unique_ptr CreateFinishScheduledJobEvent(uint64_t timestampNs, + const vector& attributionUids, + const vector& attributionTags, + const string& jobName) { + return CreateScheduledJobStateChangedEvent(attributionUids, attributionTags, jobName, + ScheduledJobStateChanged::FINISHED, timestampNs); +} + +std::unique_ptr CreateSyncStateChangedEvent(uint64_t timestampNs, + const vector& attributionUids, + const vector& attributionTags, + const string& name, + const SyncStateChanged::State state) { + AStatsEvent* statsEvent = AStatsEvent_obtain(); + AStatsEvent_setAtomId(statsEvent, util::SYNC_STATE_CHANGED); + AStatsEvent_overwriteTimestamp(statsEvent, timestampNs); + + writeAttribution(statsEvent, attributionUids, attributionTags); + AStatsEvent_writeString(statsEvent, name.c_str()); + AStatsEvent_writeInt32(statsEvent, state); + + std::unique_ptr logEvent = std::make_unique(/*uid=*/0, /*pid=*/0); + parseStatsEventToLogEvent(statsEvent, logEvent.get()); + return logEvent; +} + +std::unique_ptr CreateSyncStartEvent(uint64_t timestampNs, + const vector& attributionUids, + const vector& attributionTags, + const string& name) { + return CreateSyncStateChangedEvent(timestampNs, attributionUids, attributionTags, name, + SyncStateChanged::ON); +} + +std::unique_ptr CreateSyncEndEvent(uint64_t timestampNs, + const vector& attributionUids, + const vector& attributionTags, + const string& name) { + return CreateSyncStateChangedEvent(timestampNs, attributionUids, attributionTags, name, + SyncStateChanged::OFF); +} + +sp CreateStatsLogProcessor(const long timeBaseSec, const StatsdConfig& config, + const ConfigKey& key) { + sp uidMap = new UidMap(); + sp pullerManager = new StatsPullerManager(); + sp anomalyAlarmMonitor; + sp periodicAlarmMonitor; + sp processor = + new StatsLogProcessor(uidMap, pullerManager, anomalyAlarmMonitor, periodicAlarmMonitor, + timeBaseSec * NS_PER_SEC, [](const ConfigKey&) { return true; }, + [](const int&, const vector&) { return true; }); + processor->OnConfigUpdated(timeBaseSec * NS_PER_SEC, key, config); + return processor; +} + +void sortLogEventsByTimestamp(std::vector> *events) { + std::sort(events->begin(), events->end(), + [](const std::unique_ptr& a, const std::unique_ptr& b) { + return a->GetElapsedTimestampNs() < b->GetElapsedTimestampNs(); + }); +} + +int64_t StringToId(const string& str) { + return static_cast(std::hash()(str)); +} + + +} // namespace statsd +} // namespace os +} // namespace android diff --git a/statsd/benchmark/metric_util.h b/statsd/benchmark/metric_util.h new file mode 100644 index 00000000..693bf459 --- /dev/null +++ b/statsd/benchmark/metric_util.h @@ -0,0 +1,140 @@ +// 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. + +#pragma once + +#include "src/stats_log.pb.h" +#include "src/statsd_config.pb.h" +#include "src/StatsLogProcessor.h" +#include "src/logd/LogEvent.h" +#include "stats_event.h" +#include "statslog.h" + +namespace android { +namespace os { +namespace statsd { + +// Create AtomMatcher proto to simply match a specific atom type. +AtomMatcher CreateSimpleAtomMatcher(const string& name, int atomId); + +// Create AtomMatcher proto for scheduled job state changed. +AtomMatcher CreateScheduledJobStateChangedAtomMatcher(); + +// Create AtomMatcher proto for starting a scheduled job. +AtomMatcher CreateStartScheduledJobAtomMatcher(); + +// Create AtomMatcher proto for a scheduled job is done. +AtomMatcher CreateFinishScheduledJobAtomMatcher(); + +// Create AtomMatcher proto for screen brightness state changed. +AtomMatcher CreateScreenBrightnessChangedAtomMatcher(); + +// Create AtomMatcher proto for acquiring wakelock. +AtomMatcher CreateAcquireWakelockAtomMatcher(); + +// Create AtomMatcher proto for releasing wakelock. +AtomMatcher CreateReleaseWakelockAtomMatcher() ; + +// Create AtomMatcher proto for screen turned on. +AtomMatcher CreateScreenTurnedOnAtomMatcher(); + +// Create AtomMatcher proto for screen turned off. +AtomMatcher CreateScreenTurnedOffAtomMatcher(); + +// Create AtomMatcher proto for app sync turned on. +AtomMatcher CreateSyncStartAtomMatcher(); + +// Create AtomMatcher proto for app sync turned off. +AtomMatcher CreateSyncEndAtomMatcher(); + +// Create AtomMatcher proto for app sync moves to background. +AtomMatcher CreateMoveToBackgroundAtomMatcher(); + +// Create AtomMatcher proto for app sync moves to foreground. +AtomMatcher CreateMoveToForegroundAtomMatcher(); + +// Create Predicate proto for screen is off. +Predicate CreateScreenIsOffPredicate(); + +// Create Predicate proto for a running scheduled job. +Predicate CreateScheduledJobPredicate(); + +// Create Predicate proto for holding wakelock. +Predicate CreateHoldingWakelockPredicate(); + +// Create a Predicate proto for app syncing. +Predicate CreateIsSyncingPredicate(); + +// Create a Predicate proto for app is in background. +Predicate CreateIsInBackgroundPredicate(); + +// Add a predicate to the predicate combination. +void addPredicateToPredicateCombination(const Predicate& predicate, Predicate* combination); + +// Create dimensions from primitive fields. +FieldMatcher CreateDimensions(const int atomId, const std::vector& fields); + +// Create dimensions by attribution uid and tag. +FieldMatcher CreateAttributionUidAndTagDimensions(const int atomId, + const std::vector& positions); + +// Create dimensions by attribution uid only. +FieldMatcher CreateAttributionUidDimensions(const int atomId, + const std::vector& positions); + +void writeAttribution(AStatsEvent* statsEvent, const vector& attributionUids, + const vector& attributionTags); + +void parseStatsEventToLogEvent(AStatsEvent* statsEvent, LogEvent* logEvent); + +// Create log event for screen state changed. +std::unique_ptr CreateScreenStateChangedEvent( + uint64_t timestampNs, const android::view::DisplayStateEnum state); + +// Create log event when scheduled job starts. +std::unique_ptr CreateStartScheduledJobEvent(uint64_t timestampNs, + const vector& attributionUids, + const vector& attributionTags, + const string& jobName); + +// Create log event when scheduled job finishes. +std::unique_ptr CreateFinishScheduledJobEvent(uint64_t timestampNs, + const vector& attributionUids, + const vector& attributionTags, + const string& jobName); + +// Create log event when the app sync starts. +std::unique_ptr CreateSyncStartEvent(uint64_t timestampNs, + const vector& attributionUids, + const vector& attributionTags, + const string& name); + +// Create log event when the app sync ends. +std::unique_ptr CreateSyncEndEvent(uint64_t timestampNs, + const vector& attributionUids, + const vector& attributionTags, + const string& name); + +// Create a statsd log event processor upon the start time in seconds, config and key. +sp CreateStatsLogProcessor(const long timeBaseSec, const StatsdConfig& config, + const ConfigKey& key); + +// Util function to sort the log events by timestamp. +void sortLogEventsByTimestamp(std::vector> *events); + +int64_t StringToId(const string& str); + +} // namespace statsd +} // namespace os +} // namespace android diff --git a/statsd/benchmark/stats_write_benchmark.cpp b/statsd/benchmark/stats_write_benchmark.cpp new file mode 100644 index 00000000..f5a0cd5d --- /dev/null +++ b/statsd/benchmark/stats_write_benchmark.cpp @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2018 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 "benchmark/benchmark.h" +#include + +namespace android { +namespace os { +namespace statsd { + +static void BM_StatsWrite(benchmark::State& state) { + const char* reason = "test"; + int64_t boot_end_time = 1234567; + int64_t total_duration = 100; + int64_t bootloader_duration = 10; + int64_t time_since_last_boot = 99999999; + while (state.KeepRunning()) { + android::util::stats_write( + android::util::BOOT_SEQUENCE_REPORTED, reason, reason, + boot_end_time, total_duration, bootloader_duration, time_since_last_boot); + total_duration++; + } +} +BENCHMARK(BM_StatsWrite); + +} // namespace statsd +} // namespace os +} // namespace android diff --git a/statsd/src/FieldValue.cpp b/statsd/src/FieldValue.cpp new file mode 100644 index 00000000..c9ccfb93 --- /dev/null +++ b/statsd/src/FieldValue.cpp @@ -0,0 +1,474 @@ +/* + * Copyright (C) 2018 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. + */ + +#define DEBUG false +#include "Log.h" +#include "FieldValue.h" +#include "HashableDimensionKey.h" +#include "math.h" + +namespace android { +namespace os { +namespace statsd { + +int32_t getEncodedField(int32_t pos[], int32_t depth, bool includeDepth) { + int32_t field = 0; + for (int32_t i = 0; i <= depth; i++) { + int32_t shiftBits = 8 * (kMaxLogDepth - i); + field |= (pos[i] << shiftBits); + } + + if (includeDepth) { + field |= (depth << 24); + } + return field; +} + +int32_t encodeMatcherMask(int32_t mask[], int32_t depth) { + return getEncodedField(mask, depth, false) | 0xff000000; +} + +bool Field::matches(const Matcher& matcher) const { + if (mTag != matcher.mMatcher.getTag()) { + return false; + } + if ((mField & matcher.mMask) == matcher.mMatcher.getField()) { + return true; + } + + if (matcher.hasAllPositionMatcher() && + (mField & (matcher.mMask & kClearAllPositionMatcherMask)) == matcher.mMatcher.getField()) { + return true; + } + + return false; +} + +void translateFieldMatcher(int tag, const FieldMatcher& matcher, int depth, int* pos, int* mask, + std::vector* output) { + if (depth > kMaxLogDepth) { + ALOGE("depth > 2"); + return; + } + + pos[depth] = matcher.field(); + mask[depth] = 0x7f; + + if (matcher.has_position()) { + depth++; + if (depth > 2) { + return; + } + switch (matcher.position()) { + case Position::ALL: + pos[depth] = 0x00; + mask[depth] = 0x7f; + break; + case Position::ANY: + pos[depth] = 0; + mask[depth] = 0; + break; + case Position::FIRST: + pos[depth] = 1; + mask[depth] = 0x7f; + break; + case Position::LAST: + pos[depth] = 0x80; + mask[depth] = 0x80; + break; + case Position::POSITION_UNKNOWN: + pos[depth] = 0; + mask[depth] = 0; + break; + } + } + + if (matcher.child_size() == 0) { + output->push_back(Matcher(Field(tag, pos, depth), encodeMatcherMask(mask, depth))); + } else { + for (const auto& child : matcher.child()) { + translateFieldMatcher(tag, child, depth + 1, pos, mask, output); + } + } +} + +void translateFieldMatcher(const FieldMatcher& matcher, std::vector* output) { + int pos[] = {1, 1, 1}; + int mask[] = {0x7f, 0x7f, 0x7f}; + int tag = matcher.field(); + for (const auto& child : matcher.child()) { + translateFieldMatcher(tag, child, 0, pos, mask, output); + } +} + +bool isAttributionUidField(const FieldValue& value) { + return isAttributionUidField(value.mField, value.mValue); +} + +int32_t getUidIfExists(const FieldValue& value) { + // the field is uid field if the field is the uid field in attribution node + // or annotated as such in the atom + bool isUid = isAttributionUidField(value) || isUidField(value); + return isUid ? value.mValue.int_value : -1; +} + +bool isAttributionUidField(const Field& field, const Value& value) { + int f = field.getField() & 0xff007f; + if (f == 0x10001 && value.getType() == INT) { + return true; + } + return false; +} + +bool isUidField(const FieldValue& fieldValue) { + return fieldValue.mAnnotations.isUidField(); +} + +Value::Value(const Value& from) { + type = from.getType(); + switch (type) { + case INT: + int_value = from.int_value; + break; + case LONG: + long_value = from.long_value; + break; + case FLOAT: + float_value = from.float_value; + break; + case DOUBLE: + double_value = from.double_value; + break; + case STRING: + str_value = from.str_value; + break; + case STORAGE: + storage_value = from.storage_value; + break; + default: + break; + } +} + +std::string Value::toString() const { + switch (type) { + case INT: + return std::to_string(int_value) + "[I]"; + case LONG: + return std::to_string(long_value) + "[L]"; + case FLOAT: + return std::to_string(float_value) + "[F]"; + case DOUBLE: + return std::to_string(double_value) + "[D]"; + case STRING: + return str_value + "[S]"; + case STORAGE: + return "bytes of size " + std::to_string(storage_value.size()) + "[ST]"; + default: + return "[UNKNOWN]"; + } +} + +bool Value::isZero() const { + switch (type) { + case INT: + return int_value == 0; + case LONG: + return long_value == 0; + case FLOAT: + return fabs(float_value) <= std::numeric_limits::epsilon(); + case DOUBLE: + return fabs(double_value) <= std::numeric_limits::epsilon(); + case STRING: + return str_value.size() == 0; + case STORAGE: + return storage_value.size() == 0; + default: + return false; + } +} + +bool Value::operator==(const Value& that) const { + if (type != that.getType()) return false; + + switch (type) { + case INT: + return int_value == that.int_value; + case LONG: + return long_value == that.long_value; + case FLOAT: + return float_value == that.float_value; + case DOUBLE: + return double_value == that.double_value; + case STRING: + return str_value == that.str_value; + case STORAGE: + return storage_value == that.storage_value; + default: + return false; + } +} + +bool Value::operator!=(const Value& that) const { + if (type != that.getType()) return true; + switch (type) { + case INT: + return int_value != that.int_value; + case LONG: + return long_value != that.long_value; + case FLOAT: + return float_value != that.float_value; + case DOUBLE: + return double_value != that.double_value; + case STRING: + return str_value != that.str_value; + case STORAGE: + return storage_value != that.storage_value; + default: + return false; + } +} + +bool Value::operator<(const Value& that) const { + if (type != that.getType()) return type < that.getType(); + + switch (type) { + case INT: + return int_value < that.int_value; + case LONG: + return long_value < that.long_value; + case FLOAT: + return float_value < that.float_value; + case DOUBLE: + return double_value < that.double_value; + case STRING: + return str_value < that.str_value; + case STORAGE: + return storage_value < that.storage_value; + default: + return false; + } +} + +bool Value::operator>(const Value& that) const { + if (type != that.getType()) return type > that.getType(); + + switch (type) { + case INT: + return int_value > that.int_value; + case LONG: + return long_value > that.long_value; + case FLOAT: + return float_value > that.float_value; + case DOUBLE: + return double_value > that.double_value; + case STRING: + return str_value > that.str_value; + case STORAGE: + return storage_value > that.storage_value; + default: + return false; + } +} + +bool Value::operator>=(const Value& that) const { + if (type != that.getType()) return type >= that.getType(); + + switch (type) { + case INT: + return int_value >= that.int_value; + case LONG: + return long_value >= that.long_value; + case FLOAT: + return float_value >= that.float_value; + case DOUBLE: + return double_value >= that.double_value; + case STRING: + return str_value >= that.str_value; + case STORAGE: + return storage_value >= that.storage_value; + default: + return false; + } +} + +Value Value::operator-(const Value& that) const { + Value v; + if (type != that.type) { + ALOGE("Can't operate on different value types, %d, %d", type, that.type); + return v; + } + if (type == STRING) { + ALOGE("Can't operate on string value type"); + return v; + } + + if (type == STORAGE) { + ALOGE("Can't operate on storage value type"); + return v; + } + + switch (type) { + case INT: + v.setInt(int_value - that.int_value); + break; + case LONG: + v.setLong(long_value - that.long_value); + break; + case FLOAT: + v.setFloat(float_value - that.float_value); + break; + case DOUBLE: + v.setDouble(double_value - that.double_value); + break; + default: + break; + } + return v; +} + +Value& Value::operator=(const Value& that) { + type = that.type; + switch (type) { + case INT: + int_value = that.int_value; + break; + case LONG: + long_value = that.long_value; + break; + case FLOAT: + float_value = that.float_value; + break; + case DOUBLE: + double_value = that.double_value; + break; + case STRING: + str_value = that.str_value; + break; + case STORAGE: + storage_value = that.storage_value; + break; + default: + break; + } + return *this; +} + +Value& Value::operator+=(const Value& that) { + if (type != that.type) { + ALOGE("Can't operate on different value types, %d, %d", type, that.type); + return *this; + } + if (type == STRING) { + ALOGE("Can't operate on string value type"); + return *this; + } + if (type == STORAGE) { + ALOGE("Can't operate on storage value type"); + return *this; + } + + switch (type) { + case INT: + int_value += that.int_value; + break; + case LONG: + long_value += that.long_value; + break; + case FLOAT: + float_value += that.float_value; + break; + case DOUBLE: + double_value += that.double_value; + break; + default: + break; + } + return *this; +} + +double Value::getDouble() const { + switch (type) { + case INT: + return int_value; + case LONG: + return long_value; + case FLOAT: + return float_value; + case DOUBLE: + return double_value; + default: + return 0; + } +} + +bool equalDimensions(const std::vector& dimension_a, + const std::vector& dimension_b) { + bool eq = dimension_a.size() == dimension_b.size(); + for (size_t i = 0; eq && i < dimension_a.size(); ++i) { + if (dimension_b[i] != dimension_a[i]) { + eq = false; + } + } + return eq; +} + +bool subsetDimensions(const std::vector& dimension_a, + const std::vector& dimension_b) { + if (dimension_a.size() > dimension_b.size()) { + return false; + } + for (size_t i = 0; i < dimension_a.size(); ++i) { + bool found = false; + for (size_t j = 0; j < dimension_b.size(); ++j) { + if (dimension_a[i] == dimension_b[j]) { + found = true; + } + } + if (!found) { + return false; + } + } + return true; +} + +bool HasPositionANY(const FieldMatcher& matcher) { + if (matcher.has_position() && matcher.position() == Position::ANY) { + return true; + } + for (const auto& child : matcher.child()) { + if (HasPositionANY(child)) { + return true; + } + } + return false; +} + +bool HasPositionALL(const FieldMatcher& matcher) { + if (matcher.has_position() && matcher.position() == Position::ALL) { + return true; + } + for (const auto& child : matcher.child()) { + if (HasPositionALL(child)) { + return true; + } + } + return false; +} + +} // namespace statsd +} // namespace os +} // namespace android diff --git a/statsd/src/FieldValue.h b/statsd/src/FieldValue.h new file mode 100644 index 00000000..538d7428 --- /dev/null +++ b/statsd/src/FieldValue.h @@ -0,0 +1,462 @@ +/* + * Copyright (C) 2018 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. + */ +#pragma once + +#include "src/statsd_config.pb.h" +#include "annotations.h" + +namespace android { +namespace os { +namespace statsd { + +class HashableDimensionKey; +struct Matcher; +struct Field; +struct FieldValue; + +const int32_t kMaxLogDepth = 2; +const int32_t kLastBitMask = 0x80; +const int32_t kClearLastBitDeco = 0x7f; +const int32_t kClearAllPositionMatcherMask = 0xffff00ff; + +enum Type { UNKNOWN, INT, LONG, FLOAT, DOUBLE, STRING, STORAGE }; + +int32_t getEncodedField(int32_t pos[], int32_t depth, bool includeDepth); + +int32_t encodeMatcherMask(int32_t mask[], int32_t depth); + +// Get the encoded field for a leaf with a [field] number at depth 0; +inline int32_t getSimpleField(size_t field) { + return ((int32_t)field << 8 * 2); +} + +/** + * Field is a wrapper class for 2 integers that represents the field of a log element in its Atom + * proto. + * [mTag]: the atom id. + * [mField]: encoded path from the root (atom) to leaf. + * + * For example: + * WakeLockStateChanged { + * repeated AttributionNode = 1; + * int state = 2; + * string tag = 3; + * } + * Read from logd, the items are structured as below: + * [[[1000, "tag"], [2000, "tag2"],], 2,"hello"] + * + * When we read through the list, we will encode each field in a 32bit integer. + * 8bit segments |--------|--------|--------|--------| + * Depth field0 [L]field1 [L]field1 + * + * The first 8 bits are the depth of the field. for example, the uid 1000 has depth 2. + * The following 3 8-bit are for the item's position at each level. + * The first bit of each 8bits field is reserved to mark if the item is the last item at that level + * this is to make matching easier later. + * + * The above wakelock event is translated into FieldValue pairs. + * 0x02010101->1000 + * 0x02010182->tag + * 0x02018201->2000 + * 0x02018282->tag2 + * 0x00020000->2 + * 0x00030000->"hello" + * + * This encoding is the building block for the later operations. + * Please see the definition for Matcher below to see how the matching is done. + */ +struct Field { +private: + int32_t mTag; + int32_t mField; + +public: + Field() {} + + Field(int32_t tag, int32_t pos[], int32_t depth) : mTag(tag) { + mField = getEncodedField(pos, depth, true); + } + + Field(const Field& from) : mTag(from.getTag()), mField(from.getField()) { + } + + Field(int32_t tag, int32_t field) : mTag(tag), mField(field){}; + + inline void setField(int32_t field) { + mField = field; + } + + inline void setTag(int32_t tag) { + mTag = tag; + } + + inline void decorateLastPos(int32_t depth) { + int32_t mask = kLastBitMask << 8 * (kMaxLogDepth - depth); + mField |= mask; + } + + inline int32_t getTag() const { + return mTag; + } + + inline int32_t getDepth() const { + return (mField >> 24); + } + + inline int32_t getPath(int32_t depth) const { + if (depth > 2 || depth < 0) return 0; + + int32_t field = (mField & 0x00ffffff); + int32_t mask = 0xffffffff; + return (field & (mask << 8 * (kMaxLogDepth - depth))); + } + + inline int32_t getPrefix(int32_t depth) const { + if (depth == 0) return 0; + return getPath(depth - 1); + } + + inline int32_t getField() const { + return mField; + } + + inline int32_t getRawPosAtDepth(int32_t depth) const { + int32_t field = (mField & 0x00ffffff); + int32_t shift = 8 * (kMaxLogDepth - depth); + int32_t mask = 0xff << shift; + + return (field & mask) >> shift; + } + + inline int32_t getPosAtDepth(int32_t depth) const { + return getRawPosAtDepth(depth) & kClearLastBitDeco; + } + + // Check if the first bit of the 8-bit segment for depth is 1 + inline bool isLastPos(int32_t depth) const { + int32_t field = (mField & 0x00ffffff); + int32_t mask = kLastBitMask << 8 * (kMaxLogDepth - depth); + return (field & mask) != 0; + } + + // if the 8-bit segment is all 0's + inline bool isAnyPosMatcher(int32_t depth) const { + return getDepth() >= depth && getRawPosAtDepth(depth) == 0; + } + // if the 8bit is 0x80 (1000 0000) + inline bool isLastPosMatcher(int32_t depth) const { + return getDepth() >= depth && getRawPosAtDepth(depth) == kLastBitMask; + } + + inline bool operator==(const Field& that) const { + return mTag == that.getTag() && mField == that.getField(); + }; + + inline bool operator!=(const Field& that) const { + return mTag != that.getTag() || mField != that.getField(); + }; + + bool operator<(const Field& that) const { + if (mTag != that.getTag()) { + return mTag < that.getTag(); + } + + if (mField != that.getField()) { + return mField < that.getField(); + } + + return false; + } + + bool matches(const Matcher& that) const; +}; + +/** + * Matcher represents a leaf matcher in the FieldMatcher in statsd_config. + * + * It contains all information needed to match one or more leaf node. + * All information is encoded in a Field(2 ints) and a bit mask(1 int). + * + * For example, to match the first/any/last uid field in attribution chain in Atom 10, + * we have the following FieldMatcher in statsd_config + * FieldMatcher { + * field:10 + * FieldMatcher { + * field:1 + * position: any/last/first + * FieldMatcher { + * field:1 + * } + * } + * } + * + * We translate the FieldMatcher into a Field, and mask + * First: [Matcher Field] 0x02010101 [Mask]0xff7f7f7f + * Last: [Matcher Field] 0x02018001 [Mask]0xff7f807f + * Any: [Matcher Field] 0x02010001 [Mask]0xff7f007f + * All: [Matcher Field] 0x02010001 [Mask]0xff7f7f7f + * + * [To match a log Field with a Matcher] we apply the bit mask to the log Field and check if + * the result is equal to the Matcher Field. That's a bit wise AND operation + check if 2 ints are + * equal. Nothing can beat the performance of this matching algorithm. + * + * TODO(b/110561213): ADD EXAMPLE HERE. + */ +struct Matcher { + Matcher(const Field& matcher, int32_t mask) : mMatcher(matcher), mMask(mask){}; + + const Field mMatcher; + const int32_t mMask; + + inline const Field& getMatcher() const { + return mMatcher; + } + + inline int32_t getMask() const { + return mMask; + } + + inline int32_t getRawMaskAtDepth(int32_t depth) const { + int32_t field = (mMask & 0x00ffffff); + int32_t shift = 8 * (kMaxLogDepth - depth); + int32_t mask = 0xff << shift; + + return (field & mask) >> shift; + } + + bool hasAllPositionMatcher() const { + return mMatcher.getDepth() == 2 && getRawMaskAtDepth(1) == 0x7f; + } + + bool hasAnyPositionMatcher(int* prefix) const { + if (mMatcher.getDepth() == 2 && mMatcher.getRawPosAtDepth(1) == 0) { + (*prefix) = mMatcher.getPrefix(1); + return true; + } + return false; + } + + inline bool operator!=(const Matcher& that) const { + return mMatcher != that.getMatcher() || mMask != that.getMask(); + } + + inline bool operator==(const Matcher& that) const { + return mMatcher == that.mMatcher && mMask == that.mMask; + } +}; + +inline Matcher getSimpleMatcher(int32_t tag, size_t field) { + return Matcher(Field(tag, getSimpleField(field)), 0xff7f0000); +} + +inline Matcher getFirstUidMatcher(int32_t atomId) { + int32_t pos[] = {1, 1, 1}; + return Matcher(Field(atomId, pos, 2), 0xff7f7f7f); +} + +/** + * A wrapper for a union type to contain multiple types of values. + * + */ +struct Value { + Value() : type(UNKNOWN) {} + + Value(int32_t v) { + int_value = v; + type = INT; + } + + Value(int64_t v) { + long_value = v; + type = LONG; + } + + Value(float v) { + float_value = v; + type = FLOAT; + } + + Value(double v) { + double_value = v; + type = DOUBLE; + } + + Value(const std::string& v) { + str_value = v; + type = STRING; + } + + Value(const std::vector& v) { + storage_value = v; + type = STORAGE; + } + + void setInt(int32_t v) { + int_value = v; + type = INT; + } + + void setLong(int64_t v) { + long_value = v; + type = LONG; + } + + void setFloat(float v) { + float_value = v; + type = FLOAT; + } + + void setDouble(double v) { + double_value = v; + type = DOUBLE; + } + + union { + int32_t int_value; + int64_t long_value; + float float_value; + double double_value; + }; + std::string str_value; + std::vector storage_value; + + Type type; + + std::string toString() const; + + bool isZero() const; + + Type getType() const { + return type; + } + + double getDouble() const; + + Value(const Value& from); + + bool operator==(const Value& that) const; + bool operator!=(const Value& that) const; + + bool operator<(const Value& that) const; + bool operator>(const Value& that) const; + bool operator>=(const Value& that) const; + Value operator-(const Value& that) const; + Value& operator+=(const Value& that); + Value& operator=(const Value& that); +}; + +class Annotations { +public: + Annotations() { + setNested(true); // Nested = true by default + } + + // This enum stores where particular annotations can be found in the + // bitmask. Note that these pos do not correspond to annotation ids. + enum { + NESTED_POS = 0x0, + PRIMARY_POS = 0x1, + EXCLUSIVE_POS = 0x2, + UID_POS = 0x3 + }; + + inline void setNested(bool nested) { setBitmaskAtPos(NESTED_POS, nested); } + + inline void setPrimaryField(bool primary) { setBitmaskAtPos(PRIMARY_POS, primary); } + + inline void setExclusiveState(bool exclusive) { setBitmaskAtPos(EXCLUSIVE_POS, exclusive); } + + inline void setUidField(bool isUid) { setBitmaskAtPos(UID_POS, isUid); } + + // Default value = false + inline bool isNested() const { return getValueFromBitmask(NESTED_POS); } + + // Default value = false + inline bool isPrimaryField() const { return getValueFromBitmask(PRIMARY_POS); } + + // Default value = false + inline bool isExclusiveState() const { return getValueFromBitmask(EXCLUSIVE_POS); } + + // Default value = false + inline bool isUidField() const { return getValueFromBitmask(UID_POS); } + +private: + inline void setBitmaskAtPos(int pos, bool value) { + mBooleanBitmask &= ~(1 << pos); // clear + mBooleanBitmask |= (value << pos); // set + } + + inline bool getValueFromBitmask(int pos) const { + return (mBooleanBitmask >> pos) & 0x1; + } + + // This is a bitmask over all annotations stored in boolean form. Because + // there are only 4 booleans, just one byte is required. + uint8_t mBooleanBitmask = 0; +}; + +/** + * Represents a log item, or a dimension item (They are essentially the same). + */ +struct FieldValue { + FieldValue() {} + FieldValue(const Field& field, const Value& value) : mField(field), mValue(value) { + } + bool operator==(const FieldValue& that) const { + return mField == that.mField && mValue == that.mValue; + } + bool operator!=(const FieldValue& that) const { + return mField != that.mField || mValue != that.mValue; + } + bool operator<(const FieldValue& that) const { + if (mField != that.mField) { + return mField < that.mField; + } + + if (mValue != that.mValue) { + return mValue < that.mValue; + } + + return false; + } + + Field mField; + Value mValue; + Annotations mAnnotations; +}; + +bool HasPositionANY(const FieldMatcher& matcher); +bool HasPositionALL(const FieldMatcher& matcher); + +bool isAttributionUidField(const FieldValue& value); + +/* returns uid if the field is uid field, or -1 if the field is not a uid field */ +int getUidIfExists(const FieldValue& value); + +void translateFieldMatcher(const FieldMatcher& matcher, std::vector* output); + +bool isAttributionUidField(const Field& field, const Value& value); +bool isUidField(const FieldValue& fieldValue); + +bool equalDimensions(const std::vector& dimension_a, + const std::vector& dimension_b); + +// Returns true if dimension_a is a subset of dimension_b. +bool subsetDimensions(const std::vector& dimension_a, + const std::vector& dimension_b); +} // namespace statsd +} // namespace os +} // namespace android diff --git a/statsd/src/HashableDimensionKey.cpp b/statsd/src/HashableDimensionKey.cpp new file mode 100644 index 00000000..037856aa --- /dev/null +++ b/statsd/src/HashableDimensionKey.cpp @@ -0,0 +1,372 @@ +/* + * 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. + */ +#define DEBUG false // STOPSHIP if true +#include "Log.h" + +#include "HashableDimensionKey.h" +#include "FieldValue.h" + +namespace android { +namespace os { +namespace statsd { + +using std::string; +using std::vector; +using android::base::StringPrintf; + +/** + * Recursive helper function that populates a parent StatsDimensionsValueParcel + * with children StatsDimensionsValueParcels. + * + * \param parent parcel that will be populated with children + * \param childDepth depth of children FieldValues + * \param childPrefix expected FieldValue prefix of children + * \param dims vector of FieldValues stored by HashableDimensionKey + * \param index position in dims to start reading children from + */ +static void populateStatsDimensionsValueParcelChildren(StatsDimensionsValueParcel& parent, + int childDepth, int childPrefix, + const vector& dims, + size_t& index) { + if (childDepth > 2) { + ALOGE("Depth > 2 not supported by StatsDimensionsValueParcel."); + return; + } + + while (index < dims.size()) { + const FieldValue& dim = dims[index]; + int fieldDepth = dim.mField.getDepth(); + int fieldPrefix = dim.mField.getPrefix(childDepth); + + StatsDimensionsValueParcel child; + child.field = dim.mField.getPosAtDepth(childDepth); + + if (fieldDepth == childDepth && fieldPrefix == childPrefix) { + switch (dim.mValue.getType()) { + case INT: + child.valueType = STATS_DIMENSIONS_VALUE_INT_TYPE; + child.intValue = dim.mValue.int_value; + break; + case LONG: + child.valueType = STATS_DIMENSIONS_VALUE_LONG_TYPE; + child.longValue = dim.mValue.long_value; + break; + case FLOAT: + child.valueType = STATS_DIMENSIONS_VALUE_FLOAT_TYPE; + child.floatValue = dim.mValue.float_value; + break; + case STRING: + child.valueType = STATS_DIMENSIONS_VALUE_STRING_TYPE; + child.stringValue = dim.mValue.str_value; + break; + default: + ALOGE("Encountered FieldValue with unsupported value type."); + break; + } + index++; + parent.tupleValue.push_back(child); + } else if (fieldDepth > childDepth && fieldPrefix == childPrefix) { + // This FieldValue is not a child of the current parent, but it is + // an indirect descendant. Thus, create a direct child of TUPLE_TYPE + // and recurse to parcel the indirect descendants. + child.valueType = STATS_DIMENSIONS_VALUE_TUPLE_TYPE; + populateStatsDimensionsValueParcelChildren(child, childDepth + 1, + dim.mField.getPrefix(childDepth + 1), dims, + index); + parent.tupleValue.push_back(child); + } else { + return; + } + } +} + +StatsDimensionsValueParcel HashableDimensionKey::toStatsDimensionsValueParcel() const { + StatsDimensionsValueParcel root; + if (mValues.size() == 0) { + return root; + } + + root.field = mValues[0].mField.getTag(); + root.valueType = STATS_DIMENSIONS_VALUE_TUPLE_TYPE; + + // Children of the root correspond to top-level (depth = 0) FieldValues. + int childDepth = 0; + int childPrefix = 0; + size_t index = 0; + populateStatsDimensionsValueParcelChildren(root, childDepth, childPrefix, mValues, index); + + return root; +} + +android::hash_t hashDimension(const HashableDimensionKey& value) { + android::hash_t hash = 0; + for (const auto& fieldValue : value.getValues()) { + hash = android::JenkinsHashMix(hash, android::hash_type((int)fieldValue.mField.getField())); + hash = android::JenkinsHashMix(hash, android::hash_type((int)fieldValue.mField.getTag())); + hash = android::JenkinsHashMix(hash, android::hash_type((int)fieldValue.mValue.getType())); + switch (fieldValue.mValue.getType()) { + case INT: + hash = android::JenkinsHashMix(hash, + android::hash_type(fieldValue.mValue.int_value)); + break; + case LONG: + hash = android::JenkinsHashMix(hash, + android::hash_type(fieldValue.mValue.long_value)); + break; + case STRING: + hash = android::JenkinsHashMix(hash, static_cast(std::hash()( + fieldValue.mValue.str_value))); + break; + case FLOAT: { + hash = android::JenkinsHashMix(hash, + android::hash_type(fieldValue.mValue.float_value)); + break; + } + default: + break; + } + } + return JenkinsHashWhiten(hash); +} + +bool filterValues(const Matcher& matcherField, const vector& values, + FieldValue* output) { + for (const auto& value : values) { + if (value.mField.matches(matcherField)) { + (*output) = value; + return true; + } + } + return false; +} + +bool filterValues(const vector& matcherFields, const vector& values, + HashableDimensionKey* output) { + size_t num_matches = 0; + for (const auto& value : values) { + for (size_t i = 0; i < matcherFields.size(); ++i) { + const auto& matcher = matcherFields[i]; + if (value.mField.matches(matcher)) { + output->addValue(value); + output->mutableValue(num_matches)->mField.setTag(value.mField.getTag()); + output->mutableValue(num_matches)->mField.setField( + value.mField.getField() & matcher.mMask); + num_matches++; + } + } + } + return num_matches > 0; +} + +bool filterPrimaryKey(const std::vector& values, HashableDimensionKey* output) { + size_t num_matches = 0; + const int32_t simpleFieldMask = 0xff7f0000; + const int32_t attributionUidFieldMask = 0xff7f7f7f; + for (const auto& value : values) { + if (value.mAnnotations.isPrimaryField()) { + output->addValue(value); + output->mutableValue(num_matches)->mField.setTag(value.mField.getTag()); + const int32_t mask = + isAttributionUidField(value) ? attributionUidFieldMask : simpleFieldMask; + output->mutableValue(num_matches)->mField.setField(value.mField.getField() & mask); + num_matches++; + } + } + return num_matches > 0; +} + +void filterGaugeValues(const std::vector& matcherFields, + const std::vector& values, std::vector* output) { + for (const auto& field : matcherFields) { + for (const auto& value : values) { + if (value.mField.matches(field)) { + output->push_back(value); + } + } + } +} + +void getDimensionForCondition(const std::vector& eventValues, + const Metric2Condition& links, + HashableDimensionKey* conditionDimension) { + // Get the dimension first by using dimension from what. + filterValues(links.metricFields, eventValues, conditionDimension); + + size_t count = conditionDimension->getValues().size(); + if (count != links.conditionFields.size()) { + return; + } + + for (size_t i = 0; i < count; i++) { + conditionDimension->mutableValue(i)->mField.setField( + links.conditionFields[i].mMatcher.getField()); + conditionDimension->mutableValue(i)->mField.setTag( + links.conditionFields[i].mMatcher.getTag()); + } +} + +void getDimensionForState(const std::vector& eventValues, const Metric2State& link, + HashableDimensionKey* statePrimaryKey) { + // First, get the dimension from the event using the "what" fields from the + // MetricStateLinks. + filterValues(link.metricFields, eventValues, statePrimaryKey); + + // Then check that the statePrimaryKey size equals the number of state fields + size_t count = statePrimaryKey->getValues().size(); + if (count != link.stateFields.size()) { + return; + } + + // For each dimension Value in the statePrimaryKey, set the field and tag + // using the state atom fields from MetricStateLinks. + for (size_t i = 0; i < count; i++) { + statePrimaryKey->mutableValue(i)->mField.setField(link.stateFields[i].mMatcher.getField()); + statePrimaryKey->mutableValue(i)->mField.setTag(link.stateFields[i].mMatcher.getTag()); + } +} + +bool containsLinkedStateValues(const HashableDimensionKey& whatKey, + const HashableDimensionKey& primaryKey, + const vector& stateLinks, const int32_t stateAtomId) { + if (whatKey.getValues().size() < primaryKey.getValues().size()) { + ALOGE("Contains linked values false: whatKey is too small"); + return false; + } + + for (const auto& primaryValue : primaryKey.getValues()) { + bool found = false; + for (const auto& whatValue : whatKey.getValues()) { + if (linked(stateLinks, stateAtomId, primaryValue.mField, whatValue.mField) && + primaryValue.mValue == whatValue.mValue) { + found = true; + break; + } + } + if (!found) { + return false; + } + } + return true; +} + +bool linked(const vector& stateLinks, const int32_t stateAtomId, + const Field& stateField, const Field& metricField) { + for (auto stateLink : stateLinks) { + if (stateLink.stateAtomId != stateAtomId) { + continue; + } + + for (size_t i = 0; i < stateLink.stateFields.size(); i++) { + if (stateLink.stateFields[i].mMatcher == stateField && + stateLink.metricFields[i].mMatcher == metricField) { + return true; + } + } + } + return false; +} + +bool LessThan(const vector& s1, const vector& s2) { + if (s1.size() != s2.size()) { + return s1.size() < s2.size(); + } + + size_t count = s1.size(); + for (size_t i = 0; i < count; i++) { + if (s1[i] != s2[i]) { + return s1[i] < s2[i]; + } + } + return false; +} + +bool HashableDimensionKey::operator!=(const HashableDimensionKey& that) const { + return !((*this) == that); +} + +bool HashableDimensionKey::operator==(const HashableDimensionKey& that) const { + if (mValues.size() != that.getValues().size()) { + return false; + } + size_t count = mValues.size(); + for (size_t i = 0; i < count; i++) { + if (mValues[i] != (that.getValues())[i]) { + return false; + } + } + return true; +}; + +bool HashableDimensionKey::operator<(const HashableDimensionKey& that) const { + return LessThan(getValues(), that.getValues()); +}; + +bool HashableDimensionKey::contains(const HashableDimensionKey& that) const { + if (mValues.size() < that.getValues().size()) { + return false; + } + + if (mValues.size() == that.getValues().size()) { + return (*this) == that; + } + + for (const auto& value : that.getValues()) { + bool found = false; + for (const auto& myValue : mValues) { + if (value.mField == myValue.mField && value.mValue == myValue.mValue) { + found = true; + break; + } + } + if (!found) { + return false; + } + } + + return true; +} + +string HashableDimensionKey::toString() const { + std::string output; + for (const auto& value : mValues) { + output += StringPrintf("(%d)%#x->%s ", value.mField.getTag(), value.mField.getField(), + value.mValue.toString().c_str()); + } + return output; +} + +bool MetricDimensionKey::operator==(const MetricDimensionKey& that) const { + return mDimensionKeyInWhat == that.getDimensionKeyInWhat() && + mStateValuesKey == that.getStateValuesKey(); +}; + +string MetricDimensionKey::toString() const { + return mDimensionKeyInWhat.toString() + mStateValuesKey.toString(); +} + +bool MetricDimensionKey::operator<(const MetricDimensionKey& that) const { + if (mDimensionKeyInWhat < that.getDimensionKeyInWhat()) { + return true; + } else if (that.getDimensionKeyInWhat() < mDimensionKeyInWhat) { + return false; + } + + return mStateValuesKey < that.getStateValuesKey(); +} + +} // namespace statsd +} // namespace os +} // namespace android diff --git a/statsd/src/HashableDimensionKey.h b/statsd/src/HashableDimensionKey.h new file mode 100644 index 00000000..096de5d2 --- /dev/null +++ b/statsd/src/HashableDimensionKey.h @@ -0,0 +1,247 @@ +/* + * 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. + */ + +#pragma once + +#include +#include +#include +#include "android-base/stringprintf.h" +#include "FieldValue.h" +#include "logd/LogEvent.h" + +namespace android { +namespace os { +namespace statsd { + +using ::aidl::android::os::StatsDimensionsValueParcel; + +// These constants must be kept in sync with those in StatsDimensionsValue.java +inline constexpr int STATS_DIMENSIONS_VALUE_STRING_TYPE = 2; +inline constexpr int STATS_DIMENSIONS_VALUE_INT_TYPE = 3; +inline constexpr int STATS_DIMENSIONS_VALUE_LONG_TYPE = 4; +// inline constexpr int STATS_DIMENSIONS_VALUE_BOOL_TYPE = 5; (commented out because +// unused -- statsd does not correctly support bool types) +inline constexpr int STATS_DIMENSIONS_VALUE_FLOAT_TYPE = 6; +inline constexpr int STATS_DIMENSIONS_VALUE_TUPLE_TYPE = 7; + +struct Metric2Condition { + int64_t conditionId; + std::vector metricFields; + std::vector conditionFields; +}; + +struct Metric2State { + int32_t stateAtomId; + std::vector metricFields; + std::vector stateFields; +}; + +class HashableDimensionKey { +public: + explicit HashableDimensionKey(const std::vector& values) { + mValues = values; + } + + HashableDimensionKey() {}; + + HashableDimensionKey(const HashableDimensionKey& that) : mValues(that.getValues()){}; + + inline void addValue(const FieldValue& value) { + mValues.push_back(value); + } + + inline const std::vector& getValues() const { + return mValues; + } + + inline std::vector* mutableValues() { + return &mValues; + } + + inline FieldValue* mutableValue(size_t i) { + if (i >= 0 && i < mValues.size()) { + return &(mValues[i]); + } + return nullptr; + } + + StatsDimensionsValueParcel toStatsDimensionsValueParcel() const; + + std::string toString() const; + + bool operator!=(const HashableDimensionKey& that) const; + + bool operator==(const HashableDimensionKey& that) const; + + bool operator<(const HashableDimensionKey& that) const; + + bool contains(const HashableDimensionKey& that) const; + +private: + std::vector mValues; +}; + +class MetricDimensionKey { +public: + explicit MetricDimensionKey(const HashableDimensionKey& dimensionKeyInWhat, + const HashableDimensionKey& stateValuesKey) + : mDimensionKeyInWhat(dimensionKeyInWhat), mStateValuesKey(stateValuesKey){}; + + MetricDimensionKey(){}; + + MetricDimensionKey(const MetricDimensionKey& that) + : mDimensionKeyInWhat(that.getDimensionKeyInWhat()), + mStateValuesKey(that.getStateValuesKey()){}; + + MetricDimensionKey& operator=(const MetricDimensionKey& from) = default; + + std::string toString() const; + + inline const HashableDimensionKey& getDimensionKeyInWhat() const { + return mDimensionKeyInWhat; + } + + inline const HashableDimensionKey& getStateValuesKey() const { + return mStateValuesKey; + } + + inline HashableDimensionKey* getMutableStateValuesKey() { + return &mStateValuesKey; + } + + inline void setStateValuesKey(const HashableDimensionKey& key) { + mStateValuesKey = key; + } + + bool hasStateValuesKey() const { + return mStateValuesKey.getValues().size() > 0; + } + + bool operator==(const MetricDimensionKey& that) const; + + bool operator<(const MetricDimensionKey& that) const; + +private: + HashableDimensionKey mDimensionKeyInWhat; + HashableDimensionKey mStateValuesKey; +}; + +android::hash_t hashDimension(const HashableDimensionKey& key); + +/** + * Returns true if a FieldValue field matches the matcher field. + * The value of the FieldValue is output. + */ +bool filterValues(const Matcher& matcherField, const std::vector& values, + FieldValue* output); + +/** + * Creating HashableDimensionKeys from FieldValues using matcher. + * + * This function may make modifications to the Field if the matcher has Position=FIRST,LAST or ALL + * in it. This is because: for example, when we create dimension from last uid in attribution chain, + * In one event, uid 1000 is at position 5 and it's the last + * In another event, uid 1000 is at position 6, and it's the last + * these 2 events should be mapped to the same dimension. So we will remove the original position + * from the dimension key for the uid field (by applying 0x80 bit mask). + */ +bool filterValues(const std::vector& matcherFields, const std::vector& values, + HashableDimensionKey* output); + +/** + * Creating HashableDimensionKeys from State Primary Keys in FieldValues. + * + * This function may make modifications to the Field if the matcher has Position=FIRST,LAST or ALL + * in it. This is because: for example, when we create dimension from last uid in attribution chain, + * In one event, uid 1000 is at position 5 and it's the last + * In another event, uid 1000 is at position 6, and it's the last + * these 2 events should be mapped to the same dimension. So we will remove the original position + * from the dimension key for the uid field (by applying 0x80 bit mask). + */ +bool filterPrimaryKey(const std::vector& values, HashableDimensionKey* output); + +/** + * Filter the values from FieldValues using the matchers. + * + * In contrast to the above function, this function will not do any modification to the original + * data. Considering it as taking a snapshot on the atom event. + */ +void filterGaugeValues(const std::vector& matchers, const std::vector& values, + std::vector* output); + +void getDimensionForCondition(const std::vector& eventValues, + const Metric2Condition& links, + HashableDimensionKey* conditionDimension); + +/** + * Get dimension values using metric's "what" fields and fill statePrimaryKey's + * mField information using "state" fields. + */ +void getDimensionForState(const std::vector& eventValues, const Metric2State& link, + HashableDimensionKey* statePrimaryKey); + +/** + * Returns true if the primaryKey values are a subset of the whatKey values. + * The values from the primaryKey come from the state atom, so we need to + * check that a link exists between the state atom field and what atom field. + * + * Example: + * whatKey = [Atom: 10, {uid: 1005, wakelock_name: "compose"}] + * statePrimaryKey = [Atom: 27, {uid: 1005}] + * Returns true IF one of the Metric2State links Atom 10's uid to Atom 27's uid + * + * Example: + * whatKey = [Atom: 10, {uid: 1005, wakelock_name: "compose"}] + * statePrimaryKey = [Atom: 59, {uid: 1005, package_name: "system"}] + * Returns false + */ +bool containsLinkedStateValues(const HashableDimensionKey& whatKey, + const HashableDimensionKey& primaryKey, + const std::vector& stateLinks, + const int32_t stateAtomId); + +/** + * Returns true if there is a Metric2State link that links the stateField and + * the metricField (they are equal fields from different atoms). + */ +bool linked(const std::vector& stateLinks, const int32_t stateAtomId, + const Field& stateField, const Field& metricField); +} // namespace statsd +} // namespace os +} // namespace android + +namespace std { + +using android::os::statsd::HashableDimensionKey; +using android::os::statsd::MetricDimensionKey; + +template <> +struct hash { + std::size_t operator()(const HashableDimensionKey& key) const { + return hashDimension(key); + } +}; + +template <> +struct hash { + std::size_t operator()(const MetricDimensionKey& key) const { + android::hash_t hash = hashDimension(key.getDimensionKeyInWhat()); + hash = android::JenkinsHashMix(hash, hashDimension(key.getStateValuesKey())); + return android::JenkinsHashWhiten(hash); + } +}; +} // namespace std diff --git a/statsd/src/Log.h b/statsd/src/Log.h new file mode 100644 index 00000000..87f4cbaf --- /dev/null +++ b/statsd/src/Log.h @@ -0,0 +1,33 @@ +/* + * 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. + */ + +/* + * This file must be included at the top of the file. Other header files + * occasionally include log.h, and if LOG_TAG isn't set when that happens + * we'll get a preprocesser error when we try to define it here. + */ + +#pragma once + +#define LOG_TAG "statsd" + +#include + +// Use the local value to turn on/off debug logs instead of using log.tag. properties. +// The advantage is that in production compiler can remove the logging code if the local +// DEBUG/VERBOSE is false. +#define VLOG(...) \ + if (DEBUG) ALOGD(__VA_ARGS__); diff --git a/statsd/src/StatsLogProcessor.cpp b/statsd/src/StatsLogProcessor.cpp new file mode 100644 index 00000000..836980a9 --- /dev/null +++ b/statsd/src/StatsLogProcessor.cpp @@ -0,0 +1,1147 @@ +/* + * 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. + */ + +#define DEBUG false // STOPSHIP if true +#include "Log.h" + +#include "StatsLogProcessor.h" + +#include +#include +#include +#include + +#include "StatsService.h" +#include "android-base/stringprintf.h" +#include "external/StatsPullerManager.h" +#include "flags/flags.h" +#include "guardrail/StatsdStats.h" +#include "logd/LogEvent.h" +#include "metrics/CountMetricProducer.h" +#include "state/StateManager.h" +#include "stats_log_util.h" +#include "stats_util.h" +#include "statslog_statsd.h" +#include "storage/StorageManager.h" + +using namespace android; +using android::base::StringPrintf; +using android::util::FIELD_COUNT_REPEATED; +using android::util::FIELD_TYPE_BOOL; +using android::util::FIELD_TYPE_FLOAT; +using android::util::FIELD_TYPE_INT32; +using android::util::FIELD_TYPE_INT64; +using android::util::FIELD_TYPE_MESSAGE; +using android::util::FIELD_TYPE_STRING; +using android::util::ProtoOutputStream; +using std::vector; + +namespace android { +namespace os { +namespace statsd { + +// for ConfigMetricsReportList +const int FIELD_ID_CONFIG_KEY = 1; +const int FIELD_ID_REPORTS = 2; +// for ConfigKey +const int FIELD_ID_UID = 1; +const int FIELD_ID_ID = 2; +// for ConfigMetricsReport +// const int FIELD_ID_METRICS = 1; // written in MetricsManager.cpp +const int FIELD_ID_UID_MAP = 2; +const int FIELD_ID_LAST_REPORT_ELAPSED_NANOS = 3; +const int FIELD_ID_CURRENT_REPORT_ELAPSED_NANOS = 4; +const int FIELD_ID_LAST_REPORT_WALL_CLOCK_NANOS = 5; +const int FIELD_ID_CURRENT_REPORT_WALL_CLOCK_NANOS = 6; +const int FIELD_ID_DUMP_REPORT_REASON = 8; +const int FIELD_ID_STRINGS = 9; + +// for ActiveConfigList +const int FIELD_ID_ACTIVE_CONFIG_LIST_CONFIG = 1; + +// for permissions checks +constexpr const char* kPermissionDump = "android.permission.DUMP"; +constexpr const char* kPermissionUsage = "android.permission.PACKAGE_USAGE_STATS"; + +#define NS_PER_HOUR 3600 * NS_PER_SEC + +#define STATS_ACTIVE_METRIC_DIR "/data/misc/stats-active-metric" +#define STATS_METADATA_DIR "/data/misc/stats-metadata" + +// Cool down period for writing data to disk to avoid overwriting files. +#define WRITE_DATA_COOL_DOWN_SEC 5 + +StatsLogProcessor::StatsLogProcessor(const sp& uidMap, + const sp& pullerManager, + const sp& anomalyAlarmMonitor, + const sp& periodicAlarmMonitor, + const int64_t timeBaseNs, + const std::function& sendBroadcast, + const std::function&)>& activateBroadcast) + : mUidMap(uidMap), + mPullerManager(pullerManager), + mAnomalyAlarmMonitor(anomalyAlarmMonitor), + mPeriodicAlarmMonitor(periodicAlarmMonitor), + mSendBroadcast(sendBroadcast), + mSendActivationBroadcast(activateBroadcast), + mTimeBaseNs(timeBaseNs), + mLargestTimestampSeen(0), + mLastTimestampSeen(0) { + mPullerManager->ForceClearPullerCache(); + StateManager::getInstance().updateLogSources(uidMap); +} + +StatsLogProcessor::~StatsLogProcessor() { +} + +static void flushProtoToBuffer(ProtoOutputStream& proto, vector* outData) { + outData->clear(); + outData->resize(proto.size()); + size_t pos = 0; + sp reader = proto.data(); + while (reader->readBuffer() != NULL) { + size_t toRead = reader->currentToRead(); + std::memcpy(&((*outData)[pos]), reader->readBuffer(), toRead); + pos += toRead; + reader->move(toRead); + } +} + +void StatsLogProcessor::processFiredAnomalyAlarmsLocked( + const int64_t& timestampNs, + unordered_set, SpHash> alarmSet) { + for (const auto& itr : mMetricsManagers) { + itr.second->onAnomalyAlarmFired(timestampNs, alarmSet); + } +} +void StatsLogProcessor::onPeriodicAlarmFired( + const int64_t& timestampNs, + unordered_set, SpHash> alarmSet) { + + std::lock_guard lock(mMetricsMutex); + for (const auto& itr : mMetricsManagers) { + itr.second->onPeriodicAlarmFired(timestampNs, alarmSet); + } +} + +void StatsLogProcessor::mapIsolatedUidToHostUidIfNecessaryLocked(LogEvent* event) const { + if (std::pair indexRange; event->hasAttributionChain(&indexRange)) { + vector* const fieldValues = event->getMutableValues(); + for (int i = indexRange.first; i <= indexRange.second; i++) { + FieldValue& fieldValue = fieldValues->at(i); + if (isAttributionUidField(fieldValue)) { + const int hostUid = mUidMap->getHostUidOrSelf(fieldValue.mValue.int_value); + fieldValue.mValue.setInt(hostUid); + } + } + } else { + int uidFieldIndex = event->getUidFieldIndex(); + if (uidFieldIndex != -1) { + Value& value = (*event->getMutableValues())[uidFieldIndex].mValue; + const int hostUid = mUidMap->getHostUidOrSelf(value.int_value); + value.setInt(hostUid); + } + } +} + +void StatsLogProcessor::onIsolatedUidChangedEventLocked(const LogEvent& event) { + status_t err = NO_ERROR, err2 = NO_ERROR, err3 = NO_ERROR; + bool is_create = event.GetBool(3, &err); + auto parent_uid = int(event.GetLong(1, &err2)); + auto isolated_uid = int(event.GetLong(2, &err3)); + if (err == NO_ERROR && err2 == NO_ERROR && err3 == NO_ERROR) { + if (is_create) { + mUidMap->assignIsolatedUid(isolated_uid, parent_uid); + } else { + mUidMap->removeIsolatedUid(isolated_uid); + } + } else { + ALOGE("Failed to parse uid in the isolated uid change event."); + } +} + +void StatsLogProcessor::onBinaryPushStateChangedEventLocked(LogEvent* event) { + pid_t pid = event->GetPid(); + uid_t uid = event->GetUid(); + if (!checkPermissionForIds(kPermissionDump, pid, uid) || + !checkPermissionForIds(kPermissionUsage, pid, uid)) { + return; + } + // The Get* functions don't modify the status on success, they only write in + // failure statuses, so we can use one status variable for all calls then + // check if it is no longer NO_ERROR. + status_t err = NO_ERROR; + InstallTrainInfo trainInfo; + trainInfo.trainName = string(event->GetString(1 /*train name field id*/, &err)); + trainInfo.trainVersionCode = event->GetLong(2 /*train version field id*/, &err); + trainInfo.requiresStaging = event->GetBool(3 /*requires staging field id*/, &err); + trainInfo.rollbackEnabled = event->GetBool(4 /*rollback enabled field id*/, &err); + trainInfo.requiresLowLatencyMonitor = + event->GetBool(5 /*requires low latency monitor field id*/, &err); + trainInfo.status = int32_t(event->GetLong(6 /*state field id*/, &err)); + std::vector trainExperimentIdBytes = + event->GetStorage(7 /*experiment ids field id*/, &err); + bool is_rollback = event->GetBool(10 /*is rollback field id*/, &err); + + if (err != NO_ERROR) { + ALOGE("Failed to parse fields in binary push state changed log event"); + return; + } + ExperimentIds trainExperimentIds; + if (!trainExperimentIds.ParseFromArray(trainExperimentIdBytes.data(), + trainExperimentIdBytes.size())) { + ALOGE("Failed to parse experimentids in binary push state changed."); + return; + } + trainInfo.experimentIds = {trainExperimentIds.experiment_id().begin(), + trainExperimentIds.experiment_id().end()}; + + // Update the train info on disk and get any data the logevent is missing. + getAndUpdateTrainInfoOnDisk(is_rollback, &trainInfo); + + std::vector trainExperimentIdProto; + writeExperimentIdsToProto(trainInfo.experimentIds, &trainExperimentIdProto); + int32_t userId = multiuser_get_user_id(uid); + + event->updateValue(2 /*train version field id*/, trainInfo.trainVersionCode, LONG); + event->updateValue(7 /*experiment ids field id*/, trainExperimentIdProto, STORAGE); + event->updateValue(8 /*user id field id*/, userId, INT); + + // If this event is a rollback event, then the following bits in the event + // are invalid and we will need to update them with the values we pulled + // from disk. + if (is_rollback) { + int bit = trainInfo.requiresStaging ? 1 : 0; + event->updateValue(3 /*requires staging field id*/, bit, INT); + bit = trainInfo.rollbackEnabled ? 1 : 0; + event->updateValue(4 /*rollback enabled field id*/, bit, INT); + bit = trainInfo.requiresLowLatencyMonitor ? 1 : 0; + event->updateValue(5 /*requires low latency monitor field id*/, bit, INT); + } +} + +void StatsLogProcessor::getAndUpdateTrainInfoOnDisk(bool is_rollback, + InstallTrainInfo* trainInfo) { + // If the train name is empty, we don't know which train to attribute the + // event to, so return early. + if (trainInfo->trainName.empty()) { + return; + } + bool readTrainInfoSuccess = false; + InstallTrainInfo trainInfoOnDisk; + readTrainInfoSuccess = StorageManager::readTrainInfo(trainInfo->trainName, trainInfoOnDisk); + + bool resetExperimentIds = false; + if (readTrainInfoSuccess) { + // Keep the old train version if we received an empty version. + if (trainInfo->trainVersionCode == -1) { + trainInfo->trainVersionCode = trainInfoOnDisk.trainVersionCode; + } else if (trainInfo->trainVersionCode != trainInfoOnDisk.trainVersionCode) { + // Reset experiment ids if we receive a new non-empty train version. + resetExperimentIds = true; + } + + // Reset if we received a different experiment id. + if (!trainInfo->experimentIds.empty() && + (trainInfoOnDisk.experimentIds.empty() || + trainInfo->experimentIds.at(0) != trainInfoOnDisk.experimentIds[0])) { + resetExperimentIds = true; + } + } + + // Find the right experiment IDs + if ((!resetExperimentIds || is_rollback) && readTrainInfoSuccess) { + trainInfo->experimentIds = trainInfoOnDisk.experimentIds; + } + + if (!trainInfo->experimentIds.empty()) { + int64_t firstId = trainInfo->experimentIds.at(0); + auto& ids = trainInfo->experimentIds; + switch (trainInfo->status) { + case android::os::statsd::util::BINARY_PUSH_STATE_CHANGED__STATE__INSTALL_SUCCESS: + if (find(ids.begin(), ids.end(), firstId + 1) == ids.end()) { + ids.push_back(firstId + 1); + } + break; + case android::os::statsd::util::BINARY_PUSH_STATE_CHANGED__STATE__INSTALLER_ROLLBACK_INITIATED: + if (find(ids.begin(), ids.end(), firstId + 2) == ids.end()) { + ids.push_back(firstId + 2); + } + break; + case android::os::statsd::util::BINARY_PUSH_STATE_CHANGED__STATE__INSTALLER_ROLLBACK_SUCCESS: + if (find(ids.begin(), ids.end(), firstId + 3) == ids.end()) { + ids.push_back(firstId + 3); + } + break; + } + } + + // If this event is a rollback event, the following fields are invalid and + // need to be replaced by the fields stored to disk. + if (is_rollback) { + trainInfo->requiresStaging = trainInfoOnDisk.requiresStaging; + trainInfo->rollbackEnabled = trainInfoOnDisk.rollbackEnabled; + trainInfo->requiresLowLatencyMonitor = trainInfoOnDisk.requiresLowLatencyMonitor; + } + + StorageManager::writeTrainInfo(*trainInfo); +} + +void StatsLogProcessor::onWatchdogRollbackOccurredLocked(LogEvent* event) { + pid_t pid = event->GetPid(); + uid_t uid = event->GetUid(); + if (!checkPermissionForIds(kPermissionDump, pid, uid) || + !checkPermissionForIds(kPermissionUsage, pid, uid)) { + return; + } + // The Get* functions don't modify the status on success, they only write in + // failure statuses, so we can use one status variable for all calls then + // check if it is no longer NO_ERROR. + status_t err = NO_ERROR; + int32_t rollbackType = int32_t(event->GetInt(1 /*rollback type field id*/, &err)); + string packageName = string(event->GetString(2 /*package name field id*/, &err)); + + if (err != NO_ERROR) { + ALOGE("Failed to parse fields in watchdog rollback occurred log event"); + return; + } + + vector experimentIds = + processWatchdogRollbackOccurred(rollbackType, packageName); + vector experimentIdProto; + writeExperimentIdsToProto(experimentIds, &experimentIdProto); + + event->updateValue(6 /*experiment ids field id*/, experimentIdProto, STORAGE); +} + +vector StatsLogProcessor::processWatchdogRollbackOccurred(const int32_t rollbackTypeIn, + const string& packageNameIn) { + // If the package name is empty, we can't attribute it to any train, so + // return early. + if (packageNameIn.empty()) { + return vector(); + } + bool readTrainInfoSuccess = false; + InstallTrainInfo trainInfoOnDisk; + // We use the package name of the event as the train name. + readTrainInfoSuccess = StorageManager::readTrainInfo(packageNameIn, trainInfoOnDisk); + + if (!readTrainInfoSuccess) { + return vector(); + } + + if (trainInfoOnDisk.experimentIds.empty()) { + return vector(); + } + + int64_t firstId = trainInfoOnDisk.experimentIds[0]; + auto& ids = trainInfoOnDisk.experimentIds; + switch (rollbackTypeIn) { + case android::os::statsd::util::WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_INITIATE: + if (find(ids.begin(), ids.end(), firstId + 4) == ids.end()) { + ids.push_back(firstId + 4); + } + StorageManager::writeTrainInfo(trainInfoOnDisk); + break; + case android::os::statsd::util::WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_SUCCESS: + if (find(ids.begin(), ids.end(), firstId + 5) == ids.end()) { + ids.push_back(firstId + 5); + } + StorageManager::writeTrainInfo(trainInfoOnDisk); + break; + } + + return trainInfoOnDisk.experimentIds; +} + +void StatsLogProcessor::resetConfigs() { + std::lock_guard lock(mMetricsMutex); + resetConfigsLocked(getElapsedRealtimeNs()); +} + +void StatsLogProcessor::resetConfigsLocked(const int64_t timestampNs) { + std::vector configKeys; + for (auto it = mMetricsManagers.begin(); it != mMetricsManagers.end(); it++) { + configKeys.push_back(it->first); + } + resetConfigsLocked(timestampNs, configKeys); +} + +void StatsLogProcessor::OnLogEvent(LogEvent* event) { + OnLogEvent(event, getElapsedRealtimeNs()); +} + +void StatsLogProcessor::OnLogEvent(LogEvent* event, int64_t elapsedRealtimeNs) { + std::lock_guard lock(mMetricsMutex); + + // Tell StatsdStats about new event + const int64_t eventElapsedTimeNs = event->GetElapsedTimestampNs(); + int atomId = event->GetTagId(); + StatsdStats::getInstance().noteAtomLogged(atomId, eventElapsedTimeNs / NS_PER_SEC); + if (!event->isValid()) { + StatsdStats::getInstance().noteAtomError(atomId); + return; + } + + // Hard-coded logic to update train info on disk and fill in any information + // this log event may be missing. + if (atomId == android::os::statsd::util::BINARY_PUSH_STATE_CHANGED) { + onBinaryPushStateChangedEventLocked(event); + } + + // Hard-coded logic to update experiment ids on disk for certain rollback + // types and fill the rollback atom with experiment ids + if (atomId == android::os::statsd::util::WATCHDOG_ROLLBACK_OCCURRED) { + onWatchdogRollbackOccurredLocked(event); + } + + if (mPrintAllLogs) { + ALOGI("%s", event->ToString().c_str()); + } + resetIfConfigTtlExpiredLocked(eventElapsedTimeNs); + + // Hard-coded logic to update the isolated uid's in the uid-map. + // The field numbers need to be currently updated by hand with atoms.proto + if (atomId == android::os::statsd::util::ISOLATED_UID_CHANGED) { + onIsolatedUidChangedEventLocked(*event); + } else { + // Map the isolated uid to host uid if necessary. + mapIsolatedUidToHostUidIfNecessaryLocked(event); + } + + StateManager::getInstance().onLogEvent(*event); + + if (mMetricsManagers.empty()) { + return; + } + + bool fireAlarm = false; + { + std::lock_guard anomalyLock(mAnomalyAlarmMutex); + if (mNextAnomalyAlarmTime != 0 && + MillisToNano(mNextAnomalyAlarmTime) <= elapsedRealtimeNs) { + mNextAnomalyAlarmTime = 0; + VLOG("informing anomaly alarm at time %lld", (long long)elapsedRealtimeNs); + fireAlarm = true; + } + } + if (fireAlarm) { + informAnomalyAlarmFiredLocked(NanoToMillis(elapsedRealtimeNs)); + } + + int64_t curTimeSec = getElapsedRealtimeSec(); + if (curTimeSec - mLastPullerCacheClearTimeSec > StatsdStats::kPullerCacheClearIntervalSec) { + mPullerManager->ClearPullerCacheIfNecessary(curTimeSec * NS_PER_SEC); + mLastPullerCacheClearTimeSec = curTimeSec; + } + + std::unordered_set uidsWithActiveConfigsChanged; + std::unordered_map> activeConfigsPerUid; + // pass the event to metrics managers. + for (auto& pair : mMetricsManagers) { + int uid = pair.first.GetUid(); + int64_t configId = pair.first.GetId(); + bool isPrevActive = pair.second->isActive(); + pair.second->onLogEvent(*event); + bool isCurActive = pair.second->isActive(); + // Map all active configs by uid. + if (isCurActive) { + auto activeConfigs = activeConfigsPerUid.find(uid); + if (activeConfigs != activeConfigsPerUid.end()) { + activeConfigs->second.push_back(configId); + } else { + vector newActiveConfigs; + newActiveConfigs.push_back(configId); + activeConfigsPerUid[uid] = newActiveConfigs; + } + } + // The activation state of this config changed. + if (isPrevActive != isCurActive) { + VLOG("Active status changed for uid %d", uid); + uidsWithActiveConfigsChanged.insert(uid); + StatsdStats::getInstance().noteActiveStatusChanged(pair.first, isCurActive); + } + flushIfNecessaryLocked(pair.first, *(pair.second)); + } + + // Don't use the event timestamp for the guardrail. + for (int uid : uidsWithActiveConfigsChanged) { + // Send broadcast so that receivers can pull data. + auto lastBroadcastTime = mLastActivationBroadcastTimes.find(uid); + if (lastBroadcastTime != mLastActivationBroadcastTimes.end()) { + if (elapsedRealtimeNs - lastBroadcastTime->second < + StatsdStats::kMinActivationBroadcastPeriodNs) { + StatsdStats::getInstance().noteActivationBroadcastGuardrailHit(uid); + VLOG("StatsD would've sent an activation broadcast but the rate limit stopped us."); + return; + } + } + auto activeConfigs = activeConfigsPerUid.find(uid); + if (activeConfigs != activeConfigsPerUid.end()) { + if (mSendActivationBroadcast(uid, activeConfigs->second)) { + VLOG("StatsD sent activation notice for uid %d", uid); + mLastActivationBroadcastTimes[uid] = elapsedRealtimeNs; + } + } else { + std::vector emptyActiveConfigs; + if (mSendActivationBroadcast(uid, emptyActiveConfigs)) { + VLOG("StatsD sent EMPTY activation notice for uid %d", uid); + mLastActivationBroadcastTimes[uid] = elapsedRealtimeNs; + } + } + } +} + +void StatsLogProcessor::GetActiveConfigs(const int uid, vector& outActiveConfigs) { + std::lock_guard lock(mMetricsMutex); + GetActiveConfigsLocked(uid, outActiveConfigs); +} + +void StatsLogProcessor::GetActiveConfigsLocked(const int uid, vector& outActiveConfigs) { + outActiveConfigs.clear(); + for (auto& pair : mMetricsManagers) { + if (pair.first.GetUid() == uid && pair.second->isActive()) { + outActiveConfigs.push_back(pair.first.GetId()); + } + } +} + +void StatsLogProcessor::OnConfigUpdated(const int64_t timestampNs, const ConfigKey& key, + const StatsdConfig& config, bool modularUpdate) { + std::lock_guard lock(mMetricsMutex); + WriteDataToDiskLocked(key, timestampNs, CONFIG_UPDATED, NO_TIME_CONSTRAINTS); + OnConfigUpdatedLocked(timestampNs, key, config, modularUpdate); +} + +void StatsLogProcessor::OnConfigUpdatedLocked(const int64_t timestampNs, const ConfigKey& key, + const StatsdConfig& config, bool modularUpdate) { + VLOG("Updated configuration for key %s", key.ToString().c_str()); + // Create new config if this is not a modular update or if this is a new config. + const auto& it = mMetricsManagers.find(key); + bool configValid = false; + if (!modularUpdate || it == mMetricsManagers.end()) { + sp newMetricsManager = + new MetricsManager(key, config, mTimeBaseNs, timestampNs, mUidMap, mPullerManager, + mAnomalyAlarmMonitor, mPeriodicAlarmMonitor); + configValid = newMetricsManager->isConfigValid(); + if (configValid) { + newMetricsManager->init(); + mUidMap->OnConfigUpdated(key); + newMetricsManager->refreshTtl(timestampNs); + mMetricsManagers[key] = newMetricsManager; + VLOG("StatsdConfig valid"); + } + } else { + // Preserve the existing MetricsManager, update necessary components and metadata in place. + configValid = it->second->updateConfig(config, mTimeBaseNs, timestampNs, + mAnomalyAlarmMonitor, mPeriodicAlarmMonitor); + if (configValid) { + mUidMap->OnConfigUpdated(key); + } + } + if (!configValid) { + // If there is any error in the config, don't use it. + // Remove any existing config with the same key. + ALOGE("StatsdConfig NOT valid"); + mMetricsManagers.erase(key); + } +} + +size_t StatsLogProcessor::GetMetricsSize(const ConfigKey& key) const { + std::lock_guard lock(mMetricsMutex); + auto it = mMetricsManagers.find(key); + if (it == mMetricsManagers.end()) { + ALOGW("Config source %s does not exist", key.ToString().c_str()); + return 0; + } + return it->second->byteSize(); +} + +void StatsLogProcessor::dumpStates(int out, bool verbose) { + std::lock_guard lock(mMetricsMutex); + FILE* fout = fdopen(out, "w"); + if (fout == NULL) { + return; + } + fprintf(fout, "MetricsManager count: %lu\n", (unsigned long)mMetricsManagers.size()); + for (auto metricsManager : mMetricsManagers) { + metricsManager.second->dumpStates(fout, verbose); + } + + fclose(fout); +} + +/* + * onDumpReport dumps serialized ConfigMetricsReportList into proto. + */ +void StatsLogProcessor::onDumpReport(const ConfigKey& key, const int64_t dumpTimeStampNs, + const bool include_current_partial_bucket, + const bool erase_data, + const DumpReportReason dumpReportReason, + const DumpLatency dumpLatency, + ProtoOutputStream* proto) { + std::lock_guard lock(mMetricsMutex); + + // Start of ConfigKey. + uint64_t configKeyToken = proto->start(FIELD_TYPE_MESSAGE | FIELD_ID_CONFIG_KEY); + proto->write(FIELD_TYPE_INT32 | FIELD_ID_UID, key.GetUid()); + proto->write(FIELD_TYPE_INT64 | FIELD_ID_ID, (long long)key.GetId()); + proto->end(configKeyToken); + // End of ConfigKey. + + bool keepFile = false; + auto it = mMetricsManagers.find(key); + if (it != mMetricsManagers.end() && it->second->shouldPersistLocalHistory()) { + keepFile = true; + } + + // Then, check stats-data directory to see there's any file containing + // ConfigMetricsReport from previous shutdowns to concatenate to reports. + StorageManager::appendConfigMetricsReport( + key, proto, erase_data && !keepFile /* should remove file after appending it */, + dumpReportReason == ADB_DUMP /*if caller is adb*/); + + if (it != mMetricsManagers.end()) { + // This allows another broadcast to be sent within the rate-limit period if we get close to + // filling the buffer again soon. + mLastBroadcastTimes.erase(key); + + vector buffer; + onConfigMetricsReportLocked(key, dumpTimeStampNs, include_current_partial_bucket, + erase_data, dumpReportReason, dumpLatency, + false /* is this data going to be saved on disk */, &buffer); + proto->write(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_REPORTS, + reinterpret_cast(buffer.data()), buffer.size()); + } else { + ALOGW("Config source %s does not exist", key.ToString().c_str()); + } +} + +/* + * onDumpReport dumps serialized ConfigMetricsReportList into outData. + */ +void StatsLogProcessor::onDumpReport(const ConfigKey& key, const int64_t dumpTimeStampNs, + const bool include_current_partial_bucket, + const bool erase_data, + const DumpReportReason dumpReportReason, + const DumpLatency dumpLatency, + vector* outData) { + ProtoOutputStream proto; + onDumpReport(key, dumpTimeStampNs, include_current_partial_bucket, erase_data, + dumpReportReason, dumpLatency, &proto); + + if (outData != nullptr) { + flushProtoToBuffer(proto, outData); + VLOG("output data size %zu", outData->size()); + } + + StatsdStats::getInstance().noteMetricsReportSent(key, proto.size()); +} + +/* + * onConfigMetricsReportLocked dumps serialized ConfigMetricsReport into outData. + */ +void StatsLogProcessor::onConfigMetricsReportLocked( + const ConfigKey& key, const int64_t dumpTimeStampNs, + const bool include_current_partial_bucket, const bool erase_data, + const DumpReportReason dumpReportReason, const DumpLatency dumpLatency, + const bool dataSavedOnDisk, vector* buffer) { + // We already checked whether key exists in mMetricsManagers in + // WriteDataToDisk. + auto it = mMetricsManagers.find(key); + if (it == mMetricsManagers.end()) { + return; + } + int64_t lastReportTimeNs = it->second->getLastReportTimeNs(); + int64_t lastReportWallClockNs = it->second->getLastReportWallClockNs(); + + std::set str_set; + + ProtoOutputStream tempProto; + // First, fill in ConfigMetricsReport using current data on memory, which + // starts from filling in StatsLogReport's. + it->second->onDumpReport(dumpTimeStampNs, include_current_partial_bucket, erase_data, + dumpLatency, &str_set, &tempProto); + + // Fill in UidMap if there is at least one metric to report. + // This skips the uid map if it's an empty config. + if (it->second->getNumMetrics() > 0) { + uint64_t uidMapToken = tempProto.start(FIELD_TYPE_MESSAGE | FIELD_ID_UID_MAP); + mUidMap->appendUidMap( + dumpTimeStampNs, key, it->second->hashStringInReport() ? &str_set : nullptr, + it->second->versionStringsInReport(), it->second->installerInReport(), &tempProto); + tempProto.end(uidMapToken); + } + + // Fill in the timestamps. + tempProto.write(FIELD_TYPE_INT64 | FIELD_ID_LAST_REPORT_ELAPSED_NANOS, + (long long)lastReportTimeNs); + tempProto.write(FIELD_TYPE_INT64 | FIELD_ID_CURRENT_REPORT_ELAPSED_NANOS, + (long long)dumpTimeStampNs); + tempProto.write(FIELD_TYPE_INT64 | FIELD_ID_LAST_REPORT_WALL_CLOCK_NANOS, + (long long)lastReportWallClockNs); + tempProto.write(FIELD_TYPE_INT64 | FIELD_ID_CURRENT_REPORT_WALL_CLOCK_NANOS, + (long long)getWallClockNs()); + // Dump report reason + tempProto.write(FIELD_TYPE_INT32 | FIELD_ID_DUMP_REPORT_REASON, dumpReportReason); + + for (const auto& str : str_set) { + tempProto.write(FIELD_TYPE_STRING | FIELD_COUNT_REPEATED | FIELD_ID_STRINGS, str); + } + + flushProtoToBuffer(tempProto, buffer); + + // save buffer to disk if needed + if (erase_data && !dataSavedOnDisk && it->second->shouldPersistLocalHistory()) { + VLOG("save history to disk"); + string file_name = StorageManager::getDataHistoryFileName((long)getWallClockSec(), + key.GetUid(), key.GetId()); + StorageManager::writeFile(file_name.c_str(), buffer->data(), buffer->size()); + } +} + +void StatsLogProcessor::resetConfigsLocked(const int64_t timestampNs, + const std::vector& configs) { + for (const auto& key : configs) { + StatsdConfig config; + if (StorageManager::readConfigFromDisk(key, &config)) { + // Force a full update when resetting a config. + OnConfigUpdatedLocked(timestampNs, key, config, /*modularUpdate=*/false); + StatsdStats::getInstance().noteConfigReset(key); + } else { + ALOGE("Failed to read backup config from disk for : %s", key.ToString().c_str()); + auto it = mMetricsManagers.find(key); + if (it != mMetricsManagers.end()) { + it->second->refreshTtl(timestampNs); + } + } + } +} + +void StatsLogProcessor::resetIfConfigTtlExpiredLocked(const int64_t eventTimeNs) { + std::vector configKeysTtlExpired; + for (auto it = mMetricsManagers.begin(); it != mMetricsManagers.end(); it++) { + if (it->second != nullptr && !it->second->isInTtl(eventTimeNs)) { + configKeysTtlExpired.push_back(it->first); + } + } + if (configKeysTtlExpired.size() > 0) { + WriteDataToDiskLocked(CONFIG_RESET, NO_TIME_CONSTRAINTS, getElapsedRealtimeNs()); + resetConfigsLocked(eventTimeNs, configKeysTtlExpired); + } +} + +void StatsLogProcessor::OnConfigRemoved(const ConfigKey& key) { + std::lock_guard lock(mMetricsMutex); + auto it = mMetricsManagers.find(key); + if (it != mMetricsManagers.end()) { + WriteDataToDiskLocked(key, getElapsedRealtimeNs(), CONFIG_REMOVED, + NO_TIME_CONSTRAINTS); + mMetricsManagers.erase(it); + mUidMap->OnConfigRemoved(key); + } + StatsdStats::getInstance().noteConfigRemoved(key); + + mLastBroadcastTimes.erase(key); + + int uid = key.GetUid(); + bool lastConfigForUid = true; + for (auto it : mMetricsManagers) { + if (it.first.GetUid() == uid) { + lastConfigForUid = false; + break; + } + } + if (lastConfigForUid) { + mLastActivationBroadcastTimes.erase(uid); + } + + if (mMetricsManagers.empty()) { + mPullerManager->ForceClearPullerCache(); + } +} + +void StatsLogProcessor::flushIfNecessaryLocked(const ConfigKey& key, + MetricsManager& metricsManager) { + int64_t elapsedRealtimeNs = getElapsedRealtimeNs(); + auto lastCheckTime = mLastByteSizeTimes.find(key); + if (lastCheckTime != mLastByteSizeTimes.end()) { + if (elapsedRealtimeNs - lastCheckTime->second < StatsdStats::kMinByteSizeCheckPeriodNs) { + return; + } + } + + // We suspect that the byteSize() computation is expensive, so we set a rate limit. + size_t totalBytes = metricsManager.byteSize(); + mLastByteSizeTimes[key] = elapsedRealtimeNs; + bool requestDump = false; + if (totalBytes > StatsdStats::kMaxMetricsBytesPerConfig) { + // Too late. We need to start clearing data. + metricsManager.dropData(elapsedRealtimeNs); + StatsdStats::getInstance().noteDataDropped(key, totalBytes); + VLOG("StatsD had to toss out metrics for %s", key.ToString().c_str()); + } else if ((totalBytes > StatsdStats::kBytesPerConfigTriggerGetData) || + (mOnDiskDataConfigs.find(key) != mOnDiskDataConfigs.end())) { + // Request to send a broadcast if: + // 1. in memory data > threshold OR + // 2. config has old data report on disk. + requestDump = true; + } + + if (requestDump) { + // Send broadcast so that receivers can pull data. + auto lastBroadcastTime = mLastBroadcastTimes.find(key); + if (lastBroadcastTime != mLastBroadcastTimes.end()) { + if (elapsedRealtimeNs - lastBroadcastTime->second < + StatsdStats::kMinBroadcastPeriodNs) { + VLOG("StatsD would've sent a broadcast but the rate limit stopped us."); + return; + } + } + if (mSendBroadcast(key)) { + mOnDiskDataConfigs.erase(key); + VLOG("StatsD triggered data fetch for %s", key.ToString().c_str()); + mLastBroadcastTimes[key] = elapsedRealtimeNs; + StatsdStats::getInstance().noteBroadcastSent(key); + } + } +} + +void StatsLogProcessor::WriteDataToDiskLocked(const ConfigKey& key, + const int64_t timestampNs, + const DumpReportReason dumpReportReason, + const DumpLatency dumpLatency) { + if (mMetricsManagers.find(key) == mMetricsManagers.end() || + !mMetricsManagers.find(key)->second->shouldWriteToDisk()) { + return; + } + vector buffer; + onConfigMetricsReportLocked(key, timestampNs, true /* include_current_partial_bucket*/, + true /* erase_data */, dumpReportReason, dumpLatency, true, + &buffer); + string file_name = + StorageManager::getDataFileName((long)getWallClockSec(), key.GetUid(), key.GetId()); + StorageManager::writeFile(file_name.c_str(), buffer.data(), buffer.size()); + + // We were able to write the ConfigMetricsReport to disk, so we should trigger collection ASAP. + mOnDiskDataConfigs.insert(key); +} + +void StatsLogProcessor::SaveActiveConfigsToDisk(int64_t currentTimeNs) { + std::lock_guard lock(mMetricsMutex); + const int64_t timeNs = getElapsedRealtimeNs(); + // Do not write to disk if we already have in the last few seconds. + if (static_cast (timeNs) < + mLastActiveMetricsWriteNs + WRITE_DATA_COOL_DOWN_SEC * NS_PER_SEC) { + ALOGI("Statsd skipping writing active metrics to disk. Already wrote data in last %d seconds", + WRITE_DATA_COOL_DOWN_SEC); + return; + } + mLastActiveMetricsWriteNs = timeNs; + + ProtoOutputStream proto; + WriteActiveConfigsToProtoOutputStreamLocked(currentTimeNs, DEVICE_SHUTDOWN, &proto); + + string file_name = StringPrintf("%s/active_metrics", STATS_ACTIVE_METRIC_DIR); + StorageManager::deleteFile(file_name.c_str()); + android::base::unique_fd fd( + open(file_name.c_str(), O_WRONLY | O_CREAT | O_CLOEXEC, S_IRUSR | S_IWUSR)); + if (fd == -1) { + ALOGE("Attempt to write %s but failed", file_name.c_str()); + return; + } + proto.flush(fd.get()); +} + +void StatsLogProcessor::SaveMetadataToDisk(int64_t currentWallClockTimeNs, + int64_t systemElapsedTimeNs) { + std::lock_guard lock(mMetricsMutex); + // Do not write to disk if we already have in the last few seconds. + if (static_cast (systemElapsedTimeNs) < + mLastMetadataWriteNs + WRITE_DATA_COOL_DOWN_SEC * NS_PER_SEC) { + ALOGI("Statsd skipping writing metadata to disk. Already wrote data in last %d seconds", + WRITE_DATA_COOL_DOWN_SEC); + return; + } + mLastMetadataWriteNs = systemElapsedTimeNs; + + metadata::StatsMetadataList metadataList; + WriteMetadataToProtoLocked( + currentWallClockTimeNs, systemElapsedTimeNs, &metadataList); + + string file_name = StringPrintf("%s/metadata", STATS_METADATA_DIR); + StorageManager::deleteFile(file_name.c_str()); + + if (metadataList.stats_metadata_size() == 0) { + // Skip the write if we have nothing to write. + return; + } + + std::string data; + metadataList.SerializeToString(&data); + StorageManager::writeFile(file_name.c_str(), data.c_str(), data.size()); +} + +void StatsLogProcessor::WriteMetadataToProto(int64_t currentWallClockTimeNs, + int64_t systemElapsedTimeNs, + metadata::StatsMetadataList* metadataList) { + std::lock_guard lock(mMetricsMutex); + WriteMetadataToProtoLocked(currentWallClockTimeNs, systemElapsedTimeNs, metadataList); +} + +void StatsLogProcessor::WriteMetadataToProtoLocked(int64_t currentWallClockTimeNs, + int64_t systemElapsedTimeNs, + metadata::StatsMetadataList* metadataList) { + for (const auto& pair : mMetricsManagers) { + const sp& metricsManager = pair.second; + metadata::StatsMetadata* statsMetadata = metadataList->add_stats_metadata(); + bool metadataWritten = metricsManager->writeMetadataToProto(currentWallClockTimeNs, + systemElapsedTimeNs, statsMetadata); + if (!metadataWritten) { + metadataList->mutable_stats_metadata()->RemoveLast(); + } + } +} + +void StatsLogProcessor::LoadMetadataFromDisk(int64_t currentWallClockTimeNs, + int64_t systemElapsedTimeNs) { + std::lock_guard lock(mMetricsMutex); + string file_name = StringPrintf("%s/metadata", STATS_METADATA_DIR); + int fd = open(file_name.c_str(), O_RDONLY | O_CLOEXEC); + if (-1 == fd) { + VLOG("Attempt to read %s but failed", file_name.c_str()); + StorageManager::deleteFile(file_name.c_str()); + return; + } + string content; + if (!android::base::ReadFdToString(fd, &content)) { + ALOGE("Attempt to read %s but failed", file_name.c_str()); + close(fd); + StorageManager::deleteFile(file_name.c_str()); + return; + } + + close(fd); + + metadata::StatsMetadataList statsMetadataList; + if (!statsMetadataList.ParseFromString(content)) { + ALOGE("Attempt to read %s but failed; failed to metadata", file_name.c_str()); + StorageManager::deleteFile(file_name.c_str()); + return; + } + SetMetadataStateLocked(statsMetadataList, currentWallClockTimeNs, systemElapsedTimeNs); + StorageManager::deleteFile(file_name.c_str()); +} + +void StatsLogProcessor::SetMetadataState(const metadata::StatsMetadataList& statsMetadataList, + int64_t currentWallClockTimeNs, + int64_t systemElapsedTimeNs) { + std::lock_guard lock(mMetricsMutex); + SetMetadataStateLocked(statsMetadataList, currentWallClockTimeNs, systemElapsedTimeNs); +} + +void StatsLogProcessor::SetMetadataStateLocked( + const metadata::StatsMetadataList& statsMetadataList, + int64_t currentWallClockTimeNs, + int64_t systemElapsedTimeNs) { + for (const metadata::StatsMetadata& metadata : statsMetadataList.stats_metadata()) { + ConfigKey key(metadata.config_key().uid(), metadata.config_key().config_id()); + auto it = mMetricsManagers.find(key); + if (it == mMetricsManagers.end()) { + ALOGE("No config found for configKey %s", key.ToString().c_str()); + continue; + } + VLOG("Setting metadata %s", key.ToString().c_str()); + it->second->loadMetadata(metadata, currentWallClockTimeNs, systemElapsedTimeNs); + } + VLOG("Successfully loaded %d metadata.", statsMetadataList.stats_metadata_size()); +} + +void StatsLogProcessor::WriteActiveConfigsToProtoOutputStream( + int64_t currentTimeNs, const DumpReportReason reason, ProtoOutputStream* proto) { + std::lock_guard lock(mMetricsMutex); + WriteActiveConfigsToProtoOutputStreamLocked(currentTimeNs, reason, proto); +} + +void StatsLogProcessor::WriteActiveConfigsToProtoOutputStreamLocked( + int64_t currentTimeNs, const DumpReportReason reason, ProtoOutputStream* proto) { + for (const auto& pair : mMetricsManagers) { + const sp& metricsManager = pair.second; + uint64_t configToken = proto->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | + FIELD_ID_ACTIVE_CONFIG_LIST_CONFIG); + metricsManager->writeActiveConfigToProtoOutputStream(currentTimeNs, reason, proto); + proto->end(configToken); + } +} +void StatsLogProcessor::LoadActiveConfigsFromDisk() { + std::lock_guard lock(mMetricsMutex); + string file_name = StringPrintf("%s/active_metrics", STATS_ACTIVE_METRIC_DIR); + int fd = open(file_name.c_str(), O_RDONLY | O_CLOEXEC); + if (-1 == fd) { + VLOG("Attempt to read %s but failed", file_name.c_str()); + StorageManager::deleteFile(file_name.c_str()); + return; + } + string content; + if (!android::base::ReadFdToString(fd, &content)) { + ALOGE("Attempt to read %s but failed", file_name.c_str()); + close(fd); + StorageManager::deleteFile(file_name.c_str()); + return; + } + + close(fd); + + ActiveConfigList activeConfigList; + if (!activeConfigList.ParseFromString(content)) { + ALOGE("Attempt to read %s but failed; failed to load active configs", file_name.c_str()); + StorageManager::deleteFile(file_name.c_str()); + return; + } + // Passing in mTimeBaseNs only works as long as we only load from disk is when statsd starts. + SetConfigsActiveStateLocked(activeConfigList, mTimeBaseNs); + StorageManager::deleteFile(file_name.c_str()); +} + +void StatsLogProcessor::SetConfigsActiveState(const ActiveConfigList& activeConfigList, + int64_t currentTimeNs) { + std::lock_guard lock(mMetricsMutex); + SetConfigsActiveStateLocked(activeConfigList, currentTimeNs); +} + +void StatsLogProcessor::SetConfigsActiveStateLocked(const ActiveConfigList& activeConfigList, + int64_t currentTimeNs) { + for (int i = 0; i < activeConfigList.config_size(); i++) { + const auto& config = activeConfigList.config(i); + ConfigKey key(config.uid(), config.id()); + auto it = mMetricsManagers.find(key); + if (it == mMetricsManagers.end()) { + ALOGE("No config found for config %s", key.ToString().c_str()); + continue; + } + VLOG("Setting active config %s", key.ToString().c_str()); + it->second->loadActiveConfig(config, currentTimeNs); + } + VLOG("Successfully loaded %d active configs.", activeConfigList.config_size()); +} + +void StatsLogProcessor::WriteDataToDiskLocked(const DumpReportReason dumpReportReason, + const DumpLatency dumpLatency, + const int64_t elapsedRealtimeNs) { + // Do not write to disk if we already have in the last few seconds. + // This is to avoid overwriting files that would have the same name if we + // write twice in the same second. + if (static_cast(elapsedRealtimeNs) < + mLastWriteTimeNs + WRITE_DATA_COOL_DOWN_SEC * NS_PER_SEC) { + ALOGI("Statsd skipping writing data to disk. Already wrote data in last %d seconds", + WRITE_DATA_COOL_DOWN_SEC); + return; + } + mLastWriteTimeNs = elapsedRealtimeNs; + for (auto& pair : mMetricsManagers) { + WriteDataToDiskLocked(pair.first, elapsedRealtimeNs, dumpReportReason, dumpLatency); + } +} + +void StatsLogProcessor::WriteDataToDisk(const DumpReportReason dumpReportReason, + const DumpLatency dumpLatency, + const int64_t elapsedRealtimeNs) { + std::lock_guard lock(mMetricsMutex); + WriteDataToDiskLocked(dumpReportReason, dumpLatency, elapsedRealtimeNs); +} + +void StatsLogProcessor::informPullAlarmFired(const int64_t timestampNs) { + std::lock_guard lock(mMetricsMutex); + mPullerManager->OnAlarmFired(timestampNs); +} + +int64_t StatsLogProcessor::getLastReportTimeNs(const ConfigKey& key) { + auto it = mMetricsManagers.find(key); + if (it == mMetricsManagers.end()) { + return 0; + } else { + return it->second->getLastReportTimeNs(); + } +} + +void StatsLogProcessor::notifyAppUpgrade(const int64_t& eventTimeNs, const string& apk, + const int uid, const int64_t version) { + std::lock_guard lock(mMetricsMutex); + VLOG("Received app upgrade"); + StateManager::getInstance().notifyAppChanged(apk, mUidMap); + for (const auto& it : mMetricsManagers) { + it.second->notifyAppUpgrade(eventTimeNs, apk, uid, version); + } +} + +void StatsLogProcessor::notifyAppRemoved(const int64_t& eventTimeNs, const string& apk, + const int uid) { + std::lock_guard lock(mMetricsMutex); + VLOG("Received app removed"); + StateManager::getInstance().notifyAppChanged(apk, mUidMap); + for (const auto& it : mMetricsManagers) { + it.second->notifyAppRemoved(eventTimeNs, apk, uid); + } +} + +void StatsLogProcessor::onUidMapReceived(const int64_t& eventTimeNs) { + std::lock_guard lock(mMetricsMutex); + VLOG("Received uid map"); + StateManager::getInstance().updateLogSources(mUidMap); + for (const auto& it : mMetricsManagers) { + it.second->onUidMapReceived(eventTimeNs); + } +} + +void StatsLogProcessor::onStatsdInitCompleted(const int64_t& elapsedTimeNs) { + std::lock_guard lock(mMetricsMutex); + VLOG("Received boot completed signal"); + for (const auto& it : mMetricsManagers) { + it.second->onStatsdInitCompleted(elapsedTimeNs); + } +} + +void StatsLogProcessor::noteOnDiskData(const ConfigKey& key) { + std::lock_guard lock(mMetricsMutex); + mOnDiskDataConfigs.insert(key); +} + +void StatsLogProcessor::setAnomalyAlarm(const int64_t elapsedTimeMillis) { + std::lock_guard lock(mAnomalyAlarmMutex); + mNextAnomalyAlarmTime = elapsedTimeMillis; +} + +void StatsLogProcessor::cancelAnomalyAlarm() { + std::lock_guard lock(mAnomalyAlarmMutex); + mNextAnomalyAlarmTime = 0; +} + +void StatsLogProcessor::informAnomalyAlarmFiredLocked(const int64_t elapsedTimeMillis) { + VLOG("StatsService::informAlarmForSubscriberTriggeringFired was called"); + std::unordered_set, SpHash> alarmSet = + mAnomalyAlarmMonitor->popSoonerThan(static_cast(elapsedTimeMillis / 1000)); + if (alarmSet.size() > 0) { + VLOG("Found periodic alarm fired."); + processFiredAnomalyAlarmsLocked(MillisToNano(elapsedTimeMillis), alarmSet); + } else { + ALOGW("Cannot find an periodic alarm that fired. Perhaps it was recently cancelled."); + } +} + +} // namespace statsd +} // namespace os +} // namespace android diff --git a/statsd/src/StatsLogProcessor.h b/statsd/src/StatsLogProcessor.h new file mode 100644 index 00000000..d00d1239 --- /dev/null +++ b/statsd/src/StatsLogProcessor.h @@ -0,0 +1,380 @@ +/* + * 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. + */ + +#pragma once + +#include +#include "config/ConfigListener.h" +#include "logd/LogEvent.h" +#include "metrics/MetricsManager.h" +#include "packages/UidMap.h" +#include "external/StatsPullerManager.h" + +#include "src/statsd_config.pb.h" +#include "src/statsd_metadata.pb.h" + +#include +#include + +namespace android { +namespace os { +namespace statsd { + + +class StatsLogProcessor : public ConfigListener, public virtual PackageInfoListener { +public: + StatsLogProcessor(const sp& uidMap, const sp& pullerManager, + const sp& anomalyAlarmMonitor, + const sp& subscriberTriggerAlarmMonitor, + const int64_t timeBaseNs, + const std::function& sendBroadcast, + const std::function&)>& sendActivationBroadcast); + virtual ~StatsLogProcessor(); + + void OnLogEvent(LogEvent* event); + + void OnConfigUpdated(const int64_t timestampNs, const ConfigKey& key, + const StatsdConfig& config, bool modularUpdate = true); + void OnConfigRemoved(const ConfigKey& key); + + size_t GetMetricsSize(const ConfigKey& key) const; + + void GetActiveConfigs(const int uid, vector& outActiveConfigs); + + void onDumpReport(const ConfigKey& key, const int64_t dumpTimeNs, + const bool include_current_partial_bucket, const bool erase_data, + const DumpReportReason dumpReportReason, + const DumpLatency dumpLatency, + vector* outData); + void onDumpReport(const ConfigKey& key, const int64_t dumpTimeNs, + const bool include_current_partial_bucket, const bool erase_data, + const DumpReportReason dumpReportReason, + const DumpLatency dumpLatency, + ProtoOutputStream* proto); + + /* Tells MetricsManager that the alarms in alarmSet have fired. Modifies periodic alarmSet. */ + void onPeriodicAlarmFired( + const int64_t& timestampNs, + unordered_set, SpHash> alarmSet); + + /* Flushes data to disk. Data on memory will be gone after written to disk. */ + void WriteDataToDisk(const DumpReportReason dumpReportReason, const DumpLatency dumpLatency, + const int64_t elapsedRealtimeNs); + + /* Persist configs containing metrics with active activations to disk. */ + void SaveActiveConfigsToDisk(int64_t currentTimeNs); + + /* Writes the current active status/ttl for all configs and metrics to ProtoOutputStream. */ + void WriteActiveConfigsToProtoOutputStream( + int64_t currentTimeNs, const DumpReportReason reason, ProtoOutputStream* proto); + + /* Load configs containing metrics with active activations from disk. */ + void LoadActiveConfigsFromDisk(); + + /* Persist metadata for configs and metrics to disk. */ + void SaveMetadataToDisk(int64_t currentWallClockTimeNs, int64_t systemElapsedTimeNs); + + /* Writes the statsd metadata for all configs and metrics to StatsMetadataList. */ + void WriteMetadataToProto(int64_t currentWallClockTimeNs, + int64_t systemElapsedTimeNs, + metadata::StatsMetadataList* metadataList); + + /* Load stats metadata for configs and metrics from disk. */ + void LoadMetadataFromDisk(int64_t currentWallClockTimeNs, + int64_t systemElapsedTimeNs); + + /* Sets the metadata for all configs and metrics */ + void SetMetadataState(const metadata::StatsMetadataList& statsMetadataList, + int64_t currentWallClockTimeNs, + int64_t systemElapsedTimeNs); + + /* Sets the active status/ttl for all configs and metrics to the status in ActiveConfigList. */ + void SetConfigsActiveState(const ActiveConfigList& activeConfigList, int64_t currentTimeNs); + + /* Notify all MetricsManagers of app upgrades */ + void notifyAppUpgrade(const int64_t& eventTimeNs, const string& apk, const int uid, + const int64_t version) override; + + /* Notify all MetricsManagers of app removals */ + void notifyAppRemoved(const int64_t& eventTimeNs, const string& apk, const int uid) override; + + /* Notify all MetricsManagers of uid map snapshots received */ + void onUidMapReceived(const int64_t& eventTimeNs) override; + + /* Notify all metrics managers of boot completed + * This will force a bucket split when the boot is finished. + */ + void onStatsdInitCompleted(const int64_t& elapsedTimeNs); + + // Reset all configs. + void resetConfigs(); + + inline sp getUidMap() { + return mUidMap; + } + + void dumpStates(int outFd, bool verbose); + + void informPullAlarmFired(const int64_t timestampNs); + + int64_t getLastReportTimeNs(const ConfigKey& key); + + inline void setPrintLogs(bool enabled) { + std::lock_guard lock(mMetricsMutex); + mPrintAllLogs = enabled; + } + + // Add a specific config key to the possible configs to dump ASAP. + void noteOnDiskData(const ConfigKey& key); + + void setAnomalyAlarm(const int64_t timeMillis); + + void cancelAnomalyAlarm(); + +private: + // For testing only. + inline sp getAnomalyAlarmMonitor() const { + return mAnomalyAlarmMonitor; + } + + inline sp getPeriodicAlarmMonitor() const { + return mPeriodicAlarmMonitor; + } + + mutable mutex mMetricsMutex; + + // Guards mNextAnomalyAlarmTime. A separate mutex is needed because alarms are set/cancelled + // in the onLogEvent code path, which is locked by mMetricsMutex. + // DO NOT acquire mMetricsMutex while holding mAnomalyAlarmMutex. This can lead to a deadlock. + mutable mutex mAnomalyAlarmMutex; + + std::unordered_map> mMetricsManagers; + + std::unordered_map mLastBroadcastTimes; + + // Last time we sent a broadcast to this uid that the active configs had changed. + std::unordered_map mLastActivationBroadcastTimes; + + // Tracks when we last checked the bytes consumed for each config key. + std::unordered_map mLastByteSizeTimes; + + // Tracks which config keys has metric reports on disk + std::set mOnDiskDataConfigs; + + sp mUidMap; // Reference to the UidMap to lookup app name and version for each uid. + + sp mPullerManager; // Reference to StatsPullerManager + + sp mAnomalyAlarmMonitor; + + sp mPeriodicAlarmMonitor; + + void OnLogEvent(LogEvent* event, int64_t elapsedRealtimeNs); + + void resetIfConfigTtlExpiredLocked(const int64_t eventTimeNs); + + void OnConfigUpdatedLocked(const int64_t currentTimestampNs, const ConfigKey& key, + const StatsdConfig& config, bool modularUpdate); + + void GetActiveConfigsLocked(const int uid, vector& outActiveConfigs); + + void WriteActiveConfigsToProtoOutputStreamLocked( + int64_t currentTimeNs, const DumpReportReason reason, ProtoOutputStream* proto); + + void SetConfigsActiveStateLocked(const ActiveConfigList& activeConfigList, + int64_t currentTimeNs); + + void SetMetadataStateLocked(const metadata::StatsMetadataList& statsMetadataList, + int64_t currentWallClockTimeNs, + int64_t systemElapsedTimeNs); + + void WriteMetadataToProtoLocked(int64_t currentWallClockTimeNs, + int64_t systemElapsedTimeNs, + metadata::StatsMetadataList* metadataList); + + void WriteDataToDiskLocked(const DumpReportReason dumpReportReason, + const DumpLatency dumpLatency, const int64_t elapsedRealtimeNs); + + void WriteDataToDiskLocked(const ConfigKey& key, const int64_t timestampNs, + const DumpReportReason dumpReportReason, + const DumpLatency dumpLatency); + + void onConfigMetricsReportLocked( + const ConfigKey& key, const int64_t dumpTimeStampNs, + const bool include_current_partial_bucket, const bool erase_data, + const DumpReportReason dumpReportReason, const DumpLatency dumpLatency, + /*if dataSavedToDisk is true, it indicates the caller will write the data to disk + (e.g., before reboot). So no need to further persist local history.*/ + const bool dataSavedToDisk, vector* proto); + + /* Check if we should send a broadcast if approaching memory limits and if we're over, we + * actually delete the data. */ + void flushIfNecessaryLocked(const ConfigKey& key, MetricsManager& metricsManager); + + // Maps the isolated uid in the log event to host uid if the log event contains uid fields. + void mapIsolatedUidToHostUidIfNecessaryLocked(LogEvent* event) const; + + // Handler over the isolated uid change event. + void onIsolatedUidChangedEventLocked(const LogEvent& event); + + // Handler over the binary push state changed event. + void onBinaryPushStateChangedEventLocked(LogEvent* event); + + // Handler over the watchdog rollback occurred event. + void onWatchdogRollbackOccurredLocked(LogEvent* event); + + // Updates train info on disk based on binary push state changed info and + // write disk info into parameters. + void getAndUpdateTrainInfoOnDisk(bool is_rollback, InstallTrainInfo* trainInfoIn); + + // Gets experiment ids on disk for associated train and updates them + // depending on rollback type. Then writes them back to disk and returns + // them. + std::vector processWatchdogRollbackOccurred(const int32_t rollbackTypeIn, + const string& packageName); + + // Reset all configs. + void resetConfigsLocked(const int64_t timestampNs); + // Reset the specified configs. + void resetConfigsLocked(const int64_t timestampNs, const std::vector& configs); + + // An anomaly alarm should have fired. + // Check with anomaly alarm manager to find the alarms and process the result. + void informAnomalyAlarmFiredLocked(const int64_t elapsedTimeMillis); + + /* Tells MetricsManager that the alarms in alarmSet have fired. Modifies anomaly alarmSet. */ + void processFiredAnomalyAlarmsLocked( + const int64_t& timestampNs, + unordered_set, SpHash> alarmSet); + + // Function used to send a broadcast so that receiver for the config key can call getData + // to retrieve the stored data. + std::function mSendBroadcast; + + // Function used to send a broadcast so that receiver can be notified of which configs + // are currently active. + std::function& configIds)> mSendActivationBroadcast; + + const int64_t mTimeBaseNs; + + // Largest timestamp of the events that we have processed. + int64_t mLargestTimestampSeen = 0; + + int64_t mLastTimestampSeen = 0; + + int64_t mLastPullerCacheClearTimeSec = 0; + + // Last time we wrote data to disk. + int64_t mLastWriteTimeNs = 0; + + // Last time we wrote active metrics to disk. + int64_t mLastActiveMetricsWriteNs = 0; + + //Last time we wrote metadata to disk. + int64_t mLastMetadataWriteNs = 0; + + // The time for the next anomaly alarm for alerts. + int64_t mNextAnomalyAlarmTime = 0; + + bool mPrintAllLogs = false; + + FRIEND_TEST(StatsLogProcessorTest, TestOutOfOrderLogs); + FRIEND_TEST(StatsLogProcessorTest, TestRateLimitByteSize); + FRIEND_TEST(StatsLogProcessorTest, TestRateLimitBroadcast); + FRIEND_TEST(StatsLogProcessorTest, TestDropWhenByteSizeTooLarge); + FRIEND_TEST(StatsLogProcessorTest, InvalidConfigRemoved); + FRIEND_TEST(StatsLogProcessorTest, TestActiveConfigMetricDiskWriteRead); + FRIEND_TEST(StatsLogProcessorTest, TestActivationOnBoot); + FRIEND_TEST(StatsLogProcessorTest, TestActivationOnBootMultipleActivations); + FRIEND_TEST(StatsLogProcessorTest, + TestActivationOnBootMultipleActivationsDifferentActivationTypes); + FRIEND_TEST(StatsLogProcessorTest, TestActivationsPersistAcrossSystemServerRestart); + + FRIEND_TEST(WakelockDurationE2eTest, TestAggregatedPredicateDimensionsForSumDuration1); + FRIEND_TEST(WakelockDurationE2eTest, TestAggregatedPredicateDimensionsForSumDuration2); + FRIEND_TEST(WakelockDurationE2eTest, TestAggregatedPredicateDimensionsForSumDuration3); + FRIEND_TEST(WakelockDurationE2eTest, TestAggregatedPredicateDimensionsForMaxDuration1); + FRIEND_TEST(WakelockDurationE2eTest, TestAggregatedPredicateDimensionsForMaxDuration2); + FRIEND_TEST(WakelockDurationE2eTest, TestAggregatedPredicateDimensionsForMaxDuration3); + FRIEND_TEST(MetricConditionLinkE2eTest, TestMultiplePredicatesAndLinks1); + FRIEND_TEST(MetricConditionLinkE2eTest, TestMultiplePredicatesAndLinks2); + FRIEND_TEST(AttributionE2eTest, TestAttributionMatchAndSliceByFirstUid); + FRIEND_TEST(AttributionE2eTest, TestAttributionMatchAndSliceByChain); + FRIEND_TEST(GaugeMetricE2eTest, TestMultipleFieldsForPushedEvent); + FRIEND_TEST(GaugeMetricE2eTest, TestRandomSamplePulledEvents); + FRIEND_TEST(GaugeMetricE2eTest, TestRandomSamplePulledEvent_LateAlarm); + FRIEND_TEST(GaugeMetricE2eTest, TestRandomSamplePulledEventsWithActivation); + FRIEND_TEST(GaugeMetricE2eTest, TestRandomSamplePulledEventsNoCondition); + FRIEND_TEST(GaugeMetricE2eTest, TestConditionChangeToTrueSamplePulledEvents); + + FRIEND_TEST(AnomalyDetectionE2eTest, TestSlicedCountMetric_single_bucket); + FRIEND_TEST(AnomalyDetectionE2eTest, TestSlicedCountMetric_multiple_buckets); + FRIEND_TEST(AnomalyDetectionE2eTest, TestCountMetric_save_refractory_to_disk_no_data_written); + FRIEND_TEST(AnomalyDetectionE2eTest, TestCountMetric_save_refractory_to_disk); + FRIEND_TEST(AnomalyDetectionE2eTest, TestCountMetric_load_refractory_from_disk); + FRIEND_TEST(AnomalyDetectionE2eTest, TestDurationMetric_SUM_single_bucket); + FRIEND_TEST(AnomalyDetectionE2eTest, TestDurationMetric_SUM_partial_bucket); + FRIEND_TEST(AnomalyDetectionE2eTest, TestDurationMetric_SUM_multiple_buckets); + FRIEND_TEST(AnomalyDetectionE2eTest, TestDurationMetric_SUM_long_refractory_period); + + FRIEND_TEST(AlarmE2eTest, TestMultipleAlarms); + FRIEND_TEST(ConfigTtlE2eTest, TestCountMetric); + FRIEND_TEST(MetricActivationE2eTest, TestCountMetric); + FRIEND_TEST(MetricActivationE2eTest, TestCountMetricWithOneDeactivation); + FRIEND_TEST(MetricActivationE2eTest, TestCountMetricWithTwoDeactivations); + FRIEND_TEST(MetricActivationE2eTest, TestCountMetricWithSameDeactivation); + FRIEND_TEST(MetricActivationE2eTest, TestCountMetricWithTwoMetricsTwoDeactivations); + + FRIEND_TEST(ConfigUpdateE2eTest, TestAlarms); + FRIEND_TEST(ConfigUpdateE2eTest, TestGaugeMetric); + FRIEND_TEST(ConfigUpdateE2eTest, TestValueMetric); + FRIEND_TEST(ConfigUpdateE2eTest, TestAnomalyDurationMetric); + FRIEND_TEST(ConfigUpdateE2eAbTest, TestHashStrings); + FRIEND_TEST(ConfigUpdateE2eAbTest, TestUidMapVersionStringInstaller); + FRIEND_TEST(ConfigUpdateE2eAbTest, TestConfigTtl); + + FRIEND_TEST(CountMetricE2eTest, TestInitialConditionChanges); + FRIEND_TEST(CountMetricE2eTest, TestSlicedState); + FRIEND_TEST(CountMetricE2eTest, TestSlicedStateWithMap); + FRIEND_TEST(CountMetricE2eTest, TestMultipleSlicedStates); + FRIEND_TEST(CountMetricE2eTest, TestSlicedStateWithPrimaryFields); + + FRIEND_TEST(DurationMetricE2eTest, TestOneBucket); + FRIEND_TEST(DurationMetricE2eTest, TestTwoBuckets); + FRIEND_TEST(DurationMetricE2eTest, TestWithActivation); + FRIEND_TEST(DurationMetricE2eTest, TestWithCondition); + FRIEND_TEST(DurationMetricE2eTest, TestWithSlicedCondition); + FRIEND_TEST(DurationMetricE2eTest, TestWithActivationAndSlicedCondition); + FRIEND_TEST(DurationMetricE2eTest, TestWithSlicedState); + FRIEND_TEST(DurationMetricE2eTest, TestWithConditionAndSlicedState); + FRIEND_TEST(DurationMetricE2eTest, TestWithSlicedStateMapped); + FRIEND_TEST(DurationMetricE2eTest, TestSlicedStatePrimaryFieldsNotSubsetDimInWhat); + FRIEND_TEST(DurationMetricE2eTest, TestWithSlicedStatePrimaryFieldsSubset); + FRIEND_TEST(DurationMetricE2eTest, TestUploadThreshold); + + FRIEND_TEST(ValueMetricE2eTest, TestInitialConditionChanges); + FRIEND_TEST(ValueMetricE2eTest, TestPulledEvents); + FRIEND_TEST(ValueMetricE2eTest, TestPulledEvents_LateAlarm); + FRIEND_TEST(ValueMetricE2eTest, TestPulledEvents_WithActivation); + FRIEND_TEST(ValueMetricE2eTest, TestInitWithSlicedState); + FRIEND_TEST(ValueMetricE2eTest, TestInitWithSlicedState_WithDimensions); + FRIEND_TEST(ValueMetricE2eTest, TestInitWithSlicedState_WithIncorrectDimensions); +}; + +} // namespace statsd +} // namespace os +} // namespace android diff --git a/statsd/src/StatsService.cpp b/statsd/src/StatsService.cpp new file mode 100644 index 00000000..a52b1147 --- /dev/null +++ b/statsd/src/StatsService.cpp @@ -0,0 +1,1330 @@ +/* + * 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. + */ + +#define DEBUG false // STOPSHIP if true +#include "Log.h" + +#include "StatsService.h" +#include "stats_log_util.h" +#include "android-base/stringprintf.h" +#include "config/ConfigKey.h" +#include "config/ConfigManager.h" +#include "guardrail/StatsdStats.h" +#include "storage/StorageManager.h" +#include "subscriber/SubscriberReporter.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace android; + +using android::base::StringPrintf; +using android::util::FIELD_COUNT_REPEATED; +using android::util::FIELD_TYPE_MESSAGE; + +using Status = ::ndk::ScopedAStatus; + +namespace android { +namespace os { +namespace statsd { + +constexpr const char* kPermissionDump = "android.permission.DUMP"; + +constexpr const char* kPermissionRegisterPullAtom = "android.permission.REGISTER_STATS_PULL_ATOM"; + +#define STATS_SERVICE_DIR "/data/misc/stats-service" + +// for StatsDataDumpProto +const int FIELD_ID_REPORTS_LIST = 1; + +static Status exception(int32_t code, const std::string& msg) { + ALOGE("%s (%d)", msg.c_str(), code); + return Status::fromExceptionCodeWithMessage(code, msg.c_str()); +} + +static bool checkPermission(const char* permission) { + pid_t pid = AIBinder_getCallingPid(); + uid_t uid = AIBinder_getCallingUid(); + return checkPermissionForIds(permission, pid, uid); +} + +Status checkUid(uid_t expectedUid) { + uid_t uid = AIBinder_getCallingUid(); + if (uid == expectedUid || uid == AID_ROOT) { + return Status::ok(); + } else { + return exception(EX_SECURITY, + StringPrintf("UID %d is not expected UID %d", uid, expectedUid)); + } +} + +#define ENFORCE_UID(uid) { \ + Status status = checkUid((uid)); \ + if (!status.isOk()) { \ + return status; \ + } \ +} + +StatsService::StatsService(const sp& handlerLooper, shared_ptr queue) + : mAnomalyAlarmMonitor(new AlarmMonitor( + MIN_DIFF_TO_UPDATE_REGISTERED_ALARM_SECS, + [this](const shared_ptr& /*sc*/, int64_t timeMillis) { + mProcessor->setAnomalyAlarm(timeMillis); + StatsdStats::getInstance().noteRegisteredAnomalyAlarmChanged(); + }, + [this](const shared_ptr& /*sc*/) { + mProcessor->cancelAnomalyAlarm(); + StatsdStats::getInstance().noteRegisteredAnomalyAlarmChanged(); + })), + mPeriodicAlarmMonitor(new AlarmMonitor( + MIN_DIFF_TO_UPDATE_REGISTERED_ALARM_SECS, + [](const shared_ptr& sc, int64_t timeMillis) { + if (sc != nullptr) { + sc->setAlarmForSubscriberTriggering(timeMillis); + StatsdStats::getInstance().noteRegisteredPeriodicAlarmChanged(); + } + }, + [](const shared_ptr& sc) { + if (sc != nullptr) { + sc->cancelAlarmForSubscriberTriggering(); + StatsdStats::getInstance().noteRegisteredPeriodicAlarmChanged(); + } + })), + mEventQueue(queue), + mBootCompleteTrigger({kBootCompleteTag, kUidMapReceivedTag, kAllPullersRegisteredTag}, + [this]() { mProcessor->onStatsdInitCompleted(getElapsedRealtimeNs()); }), + mStatsCompanionServiceDeathRecipient( + AIBinder_DeathRecipient_new(StatsService::statsCompanionServiceDied)) { + mUidMap = UidMap::getInstance(); + mPullerManager = new StatsPullerManager(); + StatsPuller::SetUidMap(mUidMap); + mConfigManager = new ConfigManager(); + mProcessor = new StatsLogProcessor( + mUidMap, mPullerManager, mAnomalyAlarmMonitor, mPeriodicAlarmMonitor, + getElapsedRealtimeNs(), + [this](const ConfigKey& key) { + shared_ptr receiver = mConfigManager->GetConfigReceiver(key); + if (receiver == nullptr) { + VLOG("Could not find a broadcast receiver for %s", key.ToString().c_str()); + return false; + } else if (receiver->sendDataBroadcast( + mProcessor->getLastReportTimeNs(key)).isOk()) { + return true; + } else { + VLOG("Failed to send a broadcast for receiver %s", key.ToString().c_str()); + return false; + } + }, + [this](const int& uid, const vector& activeConfigs) { + shared_ptr receiver = + mConfigManager->GetActiveConfigsChangedReceiver(uid); + if (receiver == nullptr) { + VLOG("Could not find receiver for uid %d", uid); + return false; + } else if (receiver->sendActiveConfigsChangedBroadcast(activeConfigs).isOk()) { + VLOG("StatsService::active configs broadcast succeeded for uid %d" , uid); + return true; + } else { + VLOG("StatsService::active configs broadcast failed for uid %d" , uid); + return false; + } + }); + + mUidMap->setListener(mProcessor); + mConfigManager->AddListener(mProcessor); + + init_system_properties(); + + if (mEventQueue != nullptr) { + std::thread pushedEventThread([this] { readLogs(); }); + pushedEventThread.detach(); + } +} + +StatsService::~StatsService() { +} + +/* Runs on a dedicated thread to process pushed events. */ +void StatsService::readLogs() { + // Read forever..... long live statsd + while (1) { + // Block until an event is available. + auto event = mEventQueue->waitPop(); + // Pass it to StatsLogProcess to all configs/metrics + // At this point, the LogEventQueue is not blocked, so that the socketListener + // can read events from the socket and write to buffer to avoid data drop. + mProcessor->OnLogEvent(event.get()); + // The ShellSubscriber is only used by shell for local debugging. + if (mShellSubscriber != nullptr) { + mShellSubscriber->onLogEvent(*event); + } + } +} + +void StatsService::init_system_properties() { + mEngBuild = false; + const prop_info* buildType = __system_property_find("ro.build.type"); + if (buildType != NULL) { + __system_property_read_callback(buildType, init_build_type_callback, this); + } +} + +void StatsService::init_build_type_callback(void* cookie, const char* /*name*/, const char* value, + uint32_t serial) { + if (0 == strcmp("eng", value) || 0 == strcmp("userdebug", value)) { + reinterpret_cast(cookie)->mEngBuild = true; + } +} + +/** + * Write data from statsd. + * Format for statsdStats: adb shell dumpsys stats --metadata [-v] [--proto] + * Format for data report: adb shell dumpsys stats [anything other than --metadata] [--proto] + * Anything ending in --proto will be in proto format. + * Anything without --metadata as the first argument will be report information. + * (bugreports call "adb shell dumpsys stats --dump-priority NORMAL -a --proto") + * TODO: Come up with a more robust method of enacting . + */ +status_t StatsService::dump(int fd, const char** args, uint32_t numArgs) { + if (!checkPermission(kPermissionDump)) { + return PERMISSION_DENIED; + } + + int lastArg = numArgs - 1; + bool asProto = false; + if (lastArg >= 0 && string(args[lastArg]) == "--proto") { // last argument + asProto = true; + lastArg--; + } + if (numArgs > 0 && string(args[0]) == "--metadata") { // first argument + // Request is to dump statsd stats. + bool verbose = false; + if (lastArg >= 0 && string(args[lastArg]) == "-v") { + verbose = true; + lastArg--; + } + dumpStatsdStats(fd, verbose, asProto); + } else { + // Request is to dump statsd report data. + if (asProto) { + dumpIncidentSection(fd); + } else { + dprintf(fd, "Non-proto format of stats data dump not available; see proto version.\n"); + } + } + + return NO_ERROR; +} + +/** + * Write debugging data about statsd in text or proto format. + */ +void StatsService::dumpStatsdStats(int out, bool verbose, bool proto) { + if (proto) { + vector data; + StatsdStats::getInstance().dumpStats(&data, false); // does not reset statsdStats. + for (size_t i = 0; i < data.size(); i ++) { + dprintf(out, "%c", data[i]); + } + } else { + StatsdStats::getInstance().dumpStats(out); + mProcessor->dumpStates(out, verbose); + } +} + +/** + * Write stats report data in StatsDataDumpProto incident section format. + */ +void StatsService::dumpIncidentSection(int out) { + ProtoOutputStream proto; + for (const ConfigKey& configKey : mConfigManager->GetAllConfigKeys()) { + uint64_t reportsListToken = + proto.start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_REPORTS_LIST); + // Don't include the current bucket to avoid skipping buckets. + // If we need to include the current bucket later, consider changing to NO_TIME_CONSTRAINTS + // or other alternatives to avoid skipping buckets for pulled metrics. + mProcessor->onDumpReport(configKey, getElapsedRealtimeNs(), + false /* includeCurrentBucket */, false /* erase_data */, + ADB_DUMP, + FAST, + &proto); + proto.end(reportsListToken); + proto.flush(out); + proto.clear(); + } +} + +/** + * Implementation of the adb shell cmd stats command. + */ +status_t StatsService::handleShellCommand(int in, int out, int err, const char** argv, + uint32_t argc) { + uid_t uid = AIBinder_getCallingUid(); + if (uid != AID_ROOT && uid != AID_SHELL) { + return PERMISSION_DENIED; + } + + Vector utf8Args; + utf8Args.setCapacity(argc); + for (uint32_t i = 0; i < argc; i++) { + utf8Args.push(String8(argv[i])); + } + + if (argc >= 1) { + // adb shell cmd stats config ... + if (!utf8Args[0].compare(String8("config"))) { + return cmd_config(in, out, err, utf8Args); + } + + if (!utf8Args[0].compare(String8("print-uid-map"))) { + return cmd_print_uid_map(out, utf8Args); + } + + if (!utf8Args[0].compare(String8("dump-report"))) { + return cmd_dump_report(out, utf8Args); + } + + if (!utf8Args[0].compare(String8("pull-source")) && argc > 1) { + return cmd_print_pulled_metrics(out, utf8Args); + } + + if (!utf8Args[0].compare(String8("send-broadcast"))) { + return cmd_trigger_broadcast(out, utf8Args); + } + + if (!utf8Args[0].compare(String8("print-stats"))) { + return cmd_print_stats(out, utf8Args); + } + + if (!utf8Args[0].compare(String8("meminfo"))) { + return cmd_dump_memory_info(out); + } + + if (!utf8Args[0].compare(String8("write-to-disk"))) { + return cmd_write_data_to_disk(out); + } + + if (!utf8Args[0].compare(String8("log-app-breadcrumb"))) { + return cmd_log_app_breadcrumb(out, utf8Args); + } + + if (!utf8Args[0].compare(String8("log-binary-push"))) { + return cmd_log_binary_push(out, utf8Args); + } + + if (!utf8Args[0].compare(String8("clear-puller-cache"))) { + return cmd_clear_puller_cache(out); + } + + if (!utf8Args[0].compare(String8("print-logs"))) { + return cmd_print_logs(out, utf8Args); + } + + if (!utf8Args[0].compare(String8("send-active-configs"))) { + return cmd_trigger_active_config_broadcast(out, utf8Args); + } + + if (!utf8Args[0].compare(String8("data-subscribe"))) { + { + std::lock_guard lock(mShellSubscriberMutex); + if (mShellSubscriber == nullptr) { + mShellSubscriber = new ShellSubscriber(mUidMap, mPullerManager); + } + } + int timeoutSec = -1; + if (argc >= 2) { + timeoutSec = atoi(utf8Args[1].c_str()); + } + mShellSubscriber->startNewSubscription(in, out, timeoutSec); + return NO_ERROR; + } + } + + print_cmd_help(out); + return NO_ERROR; +} + +void StatsService::print_cmd_help(int out) { + dprintf(out, + "usage: adb shell cmd stats print-stats-log [tag_required] " + "[timestamp_nsec_optional]\n"); + dprintf(out, "\n"); + dprintf(out, "\n"); + dprintf(out, "usage: adb shell cmd stats meminfo\n"); + dprintf(out, "\n"); + dprintf(out, " Prints the malloc debug information. You need to run the following first: \n"); + dprintf(out, " # adb shell stop\n"); + dprintf(out, " # adb shell setprop libc.debug.malloc.program statsd \n"); + dprintf(out, " # adb shell setprop libc.debug.malloc.options backtrace \n"); + dprintf(out, " # adb shell start\n"); + dprintf(out, "\n"); + dprintf(out, "\n"); + dprintf(out, "usage: adb shell cmd stats print-uid-map [PKG]\n"); + dprintf(out, "\n"); + dprintf(out, " Prints the UID, app name, version mapping.\n"); + dprintf(out, " PKG Optional package name to print the uids of the package\n"); + dprintf(out, "\n"); + dprintf(out, "\n"); + dprintf(out, "usage: adb shell cmd stats pull-source ATOM_TAG [PACKAGE] \n"); + dprintf(out, "\n"); + dprintf(out, " Prints the output of a pulled atom\n"); + dprintf(out, " UID The atom to pull\n"); + dprintf(out, " PACKAGE The package to pull from. Default is AID_SYSTEM\n"); + dprintf(out, "\n"); + dprintf(out, "\n"); + dprintf(out, "usage: adb shell cmd stats write-to-disk \n"); + dprintf(out, "\n"); + dprintf(out, " Flushes all data on memory to disk.\n"); + dprintf(out, "\n"); + dprintf(out, "\n"); + dprintf(out, "usage: adb shell cmd stats log-app-breadcrumb [UID] LABEL STATE\n"); + dprintf(out, " Writes an AppBreadcrumbReported event to the statslog buffer.\n"); + dprintf(out, " UID The uid to use. It is only possible to pass a UID\n"); + dprintf(out, " parameter on eng builds. If UID is omitted the calling\n"); + dprintf(out, " uid is used.\n"); + dprintf(out, " LABEL Integer in [0, 15], as per atoms.proto.\n"); + dprintf(out, " STATE Integer in [0, 3], as per atoms.proto.\n"); + dprintf(out, "\n"); + dprintf(out, "\n"); + dprintf(out, + "usage: adb shell cmd stats log-binary-push NAME VERSION STAGING ROLLBACK_ENABLED " + "LOW_LATENCY STATE EXPERIMENT_IDS\n"); + dprintf(out, " Log a binary push state changed event.\n"); + dprintf(out, " NAME The train name.\n"); + dprintf(out, " VERSION The train version code.\n"); + dprintf(out, " STAGING If this train requires a restart.\n"); + dprintf(out, " ROLLBACK_ENABLED If rollback should be enabled for this install.\n"); + dprintf(out, " LOW_LATENCY If the train requires low latency monitoring.\n"); + dprintf(out, " STATE The status of the train push.\n"); + dprintf(out, " Integer value of the enum in atoms.proto.\n"); + dprintf(out, " EXPERIMENT_IDS Comma separated list of experiment ids.\n"); + dprintf(out, " Leave blank for none.\n"); + dprintf(out, "\n"); + dprintf(out, "\n"); + dprintf(out, "usage: adb shell cmd stats config remove [UID] [NAME]\n"); + dprintf(out, "usage: adb shell cmd stats config update [UID] NAME\n"); + dprintf(out, "\n"); + dprintf(out, " Adds, updates or removes a configuration. The proto should be in\n"); + dprintf(out, " wire-encoded protobuf format and passed via stdin. If no UID and name is\n"); + dprintf(out, " provided, then all configs will be removed from memory and disk.\n"); + dprintf(out, "\n"); + dprintf(out, " UID The uid to use. It is only possible to pass the UID\n"); + dprintf(out, " parameter on eng builds. If UID is omitted the calling\n"); + dprintf(out, " uid is used.\n"); + dprintf(out, " NAME The per-uid name to use\n"); + dprintf(out, "\n"); + dprintf(out, "\n *Note: If both UID and NAME are omitted then all configs will\n"); + dprintf(out, "\n be removed from memory and disk!\n"); + dprintf(out, "\n"); + dprintf(out, + "usage: adb shell cmd stats dump-report [UID] NAME [--keep_data] " + "[--include_current_bucket] [--proto]\n"); + dprintf(out, " Dump all metric data for a configuration.\n"); + dprintf(out, " UID The uid of the configuration. It is only possible to pass\n"); + dprintf(out, " the UID parameter on eng builds. If UID is omitted the\n"); + dprintf(out, " calling uid is used.\n"); + dprintf(out, " NAME The name of the configuration\n"); + dprintf(out, " --keep_data Do NOT erase the data upon dumping it.\n"); + dprintf(out, " --proto Print proto binary.\n"); + dprintf(out, "\n"); + dprintf(out, "\n"); + dprintf(out, "usage: adb shell cmd stats send-broadcast [UID] NAME\n"); + dprintf(out, " Send a broadcast that triggers the subscriber to fetch metrics.\n"); + dprintf(out, " UID The uid of the configuration. It is only possible to pass\n"); + dprintf(out, " the UID parameter on eng builds. If UID is omitted the\n"); + dprintf(out, " calling uid is used.\n"); + dprintf(out, " NAME The name of the configuration\n"); + dprintf(out, "\n"); + dprintf(out, "\n"); + dprintf(out, + "usage: adb shell cmd stats send-active-configs [--uid=UID] [--configs] " + "[NAME1] [NAME2] [NAME3..]\n"); + dprintf(out, " Send a broadcast that informs the subscriber of the current active configs.\n"); + dprintf(out, " --uid=UID The uid of the configurations. It is only possible to pass\n"); + dprintf(out, " the UID parameter on eng builds. If UID is omitted the\n"); + dprintf(out, " calling uid is used.\n"); + dprintf(out, " --configs Send the list of configs in the name list instead of\n"); + dprintf(out, " the currently active configs\n"); + dprintf(out, " NAME LIST List of configuration names to be included in the broadcast.\n"); + dprintf(out, "\n"); + dprintf(out, "\n"); + dprintf(out, "usage: adb shell cmd stats print-stats\n"); + dprintf(out, " Prints some basic stats.\n"); + dprintf(out, " --proto Print proto binary instead of string format.\n"); + dprintf(out, "\n"); + dprintf(out, "\n"); + dprintf(out, "usage: adb shell cmd stats clear-puller-cache\n"); + dprintf(out, " Clear cached puller data.\n"); + dprintf(out, "\n"); + dprintf(out, "usage: adb shell cmd stats print-logs\n"); + dprintf(out, " Requires root privileges.\n"); + dprintf(out, " Can be disabled by calling adb shell cmd stats print-logs 0\n"); +} + +status_t StatsService::cmd_trigger_broadcast(int out, Vector& args) { + string name; + bool good = false; + int uid; + const int argCount = args.size(); + if (argCount == 2) { + // Automatically pick the UID + uid = AIBinder_getCallingUid(); + name.assign(args[1].c_str(), args[1].size()); + good = true; + } else if (argCount == 3) { + good = getUidFromArgs(args, 1, uid); + if (!good) { + dprintf(out, "Invalid UID. Note that the metrics can only be dumped for " + "other UIDs on eng or userdebug builds.\n"); + } + name.assign(args[2].c_str(), args[2].size()); + } + if (!good) { + print_cmd_help(out); + return UNKNOWN_ERROR; + } + ConfigKey key(uid, StrToInt64(name)); + shared_ptr receiver = mConfigManager->GetConfigReceiver(key); + if (receiver == nullptr) { + VLOG("Could not find receiver for %s, %s", args[1].c_str(), args[2].c_str()); + return UNKNOWN_ERROR; + } else if (receiver->sendDataBroadcast(mProcessor->getLastReportTimeNs(key)).isOk()) { + VLOG("StatsService::trigger broadcast succeeded to %s, %s", args[1].c_str(), + args[2].c_str()); + } else { + VLOG("StatsService::trigger broadcast failed to %s, %s", args[1].c_str(), args[2].c_str()); + return UNKNOWN_ERROR; + } + return NO_ERROR; +} + +status_t StatsService::cmd_trigger_active_config_broadcast(int out, Vector& args) { + const int argCount = args.size(); + int uid; + vector configIds; + if (argCount == 1) { + // Automatically pick the uid and send a broadcast that has no active configs. + uid = AIBinder_getCallingUid(); + mProcessor->GetActiveConfigs(uid, configIds); + } else { + int curArg = 1; + if(args[curArg].find("--uid=") == 0) { + string uidArgStr(args[curArg].c_str()); + string uidStr = uidArgStr.substr(6); + if (!getUidFromString(uidStr.c_str(), uid)) { + dprintf(out, "Invalid UID. Note that the config can only be set for " + "other UIDs on eng or userdebug builds.\n"); + return UNKNOWN_ERROR; + } + curArg++; + } else { + uid = AIBinder_getCallingUid(); + } + if (curArg == argCount || args[curArg] != "--configs") { + VLOG("Reached end of args, or specify configs not set. Sending actual active configs,"); + mProcessor->GetActiveConfigs(uid, configIds); + } else { + // Flag specified, use the given list of configs. + curArg++; + for (int i = curArg; i < argCount; i++) { + char* endp; + int64_t configID = strtoll(args[i].c_str(), &endp, 10); + if (endp == args[i].c_str() || *endp != '\0') { + dprintf(out, "Error parsing config ID.\n"); + return UNKNOWN_ERROR; + } + VLOG("Adding config id %ld", static_cast(configID)); + configIds.push_back(configID); + } + } + } + shared_ptr receiver = mConfigManager->GetActiveConfigsChangedReceiver(uid); + if (receiver == nullptr) { + VLOG("Could not find receiver for uid %d", uid); + return UNKNOWN_ERROR; + } else if (receiver->sendActiveConfigsChangedBroadcast(configIds).isOk()) { + VLOG("StatsService::trigger active configs changed broadcast succeeded for uid %d" , uid); + } else { + VLOG("StatsService::trigger active configs changed broadcast failed for uid %d", uid); + return UNKNOWN_ERROR; + } + return NO_ERROR; +} + +status_t StatsService::cmd_config(int in, int out, int err, Vector& args) { + const int argCount = args.size(); + if (argCount >= 2) { + if (args[1] == "update" || args[1] == "remove") { + bool good = false; + int uid = -1; + string name; + + if (argCount == 3) { + // Automatically pick the UID + uid = AIBinder_getCallingUid(); + name.assign(args[2].c_str(), args[2].size()); + good = true; + } else if (argCount == 4) { + good = getUidFromArgs(args, 2, uid); + if (!good) { + dprintf(err, "Invalid UID. Note that the config can only be set for " + "other UIDs on eng or userdebug builds.\n"); + } + name.assign(args[3].c_str(), args[3].size()); + } else if (argCount == 2 && args[1] == "remove") { + good = true; + } + + if (!good) { + // If arg parsing failed, print the help text and return an error. + print_cmd_help(out); + return UNKNOWN_ERROR; + } + + if (args[1] == "update") { + char* endp; + int64_t configID = strtoll(name.c_str(), &endp, 10); + if (endp == name.c_str() || *endp != '\0') { + dprintf(err, "Error parsing config ID.\n"); + return UNKNOWN_ERROR; + } + + // Read stream into buffer. + string buffer; + if (!android::base::ReadFdToString(in, &buffer)) { + dprintf(err, "Error reading stream for StatsConfig.\n"); + return UNKNOWN_ERROR; + } + + // Parse buffer. + StatsdConfig config; + if (!config.ParseFromString(buffer)) { + dprintf(err, "Error parsing proto stream for StatsConfig.\n"); + return UNKNOWN_ERROR; + } + + // Add / update the config. + mConfigManager->UpdateConfig(ConfigKey(uid, configID), config); + } else { + if (argCount == 2) { + cmd_remove_all_configs(out); + } else { + // Remove the config. + mConfigManager->RemoveConfig(ConfigKey(uid, StrToInt64(name))); + } + } + + return NO_ERROR; + } + } + print_cmd_help(out); + return UNKNOWN_ERROR; +} + +status_t StatsService::cmd_dump_report(int out, const Vector& args) { + if (mProcessor != nullptr) { + int argCount = args.size(); + bool good = false; + bool proto = false; + bool includeCurrentBucket = false; + bool eraseData = true; + int uid; + string name; + if (!std::strcmp("--proto", args[argCount-1].c_str())) { + proto = true; + argCount -= 1; + } + if (!std::strcmp("--include_current_bucket", args[argCount-1].c_str())) { + includeCurrentBucket = true; + argCount -= 1; + } + if (!std::strcmp("--keep_data", args[argCount-1].c_str())) { + eraseData = false; + argCount -= 1; + } + if (argCount == 2) { + // Automatically pick the UID + uid = AIBinder_getCallingUid(); + name.assign(args[1].c_str(), args[1].size()); + good = true; + } else if (argCount == 3) { + good = getUidFromArgs(args, 1, uid); + if (!good) { + dprintf(out, "Invalid UID. Note that the metrics can only be dumped for " + "other UIDs on eng or userdebug builds.\n"); + } + name.assign(args[2].c_str(), args[2].size()); + } + if (good) { + vector data; + mProcessor->onDumpReport(ConfigKey(uid, StrToInt64(name)), getElapsedRealtimeNs(), + includeCurrentBucket, eraseData, ADB_DUMP, + NO_TIME_CONSTRAINTS, + &data); + if (proto) { + for (size_t i = 0; i < data.size(); i ++) { + dprintf(out, "%c", data[i]); + } + } else { + dprintf(out, "Non-proto stats data dump not currently supported.\n"); + } + return android::OK; + } else { + // If arg parsing failed, print the help text and return an error. + print_cmd_help(out); + return UNKNOWN_ERROR; + } + } else { + dprintf(out, "Log processor does not exist...\n"); + return UNKNOWN_ERROR; + } +} + +status_t StatsService::cmd_print_stats(int out, const Vector& args) { + int argCount = args.size(); + bool proto = false; + if (!std::strcmp("--proto", args[argCount-1].c_str())) { + proto = true; + argCount -= 1; + } + StatsdStats& statsdStats = StatsdStats::getInstance(); + if (proto) { + vector data; + statsdStats.dumpStats(&data, false); // does not reset statsdStats. + for (size_t i = 0; i < data.size(); i ++) { + dprintf(out, "%c", data[i]); + } + + } else { + vector configs = mConfigManager->GetAllConfigKeys(); + for (const ConfigKey& key : configs) { + dprintf(out, "Config %s uses %zu bytes\n", key.ToString().c_str(), + mProcessor->GetMetricsSize(key)); + } + statsdStats.dumpStats(out); + } + return NO_ERROR; +} + +status_t StatsService::cmd_print_uid_map(int out, const Vector& args) { + if (args.size() > 1) { + string pkg; + pkg.assign(args[1].c_str(), args[1].size()); + auto uids = mUidMap->getAppUid(pkg); + dprintf(out, "%s -> [ ", pkg.c_str()); + for (const auto& uid : uids) { + dprintf(out, "%d ", uid); + } + dprintf(out, "]\n"); + } else { + mUidMap->printUidMap(out); + } + return NO_ERROR; +} + +status_t StatsService::cmd_write_data_to_disk(int out) { + dprintf(out, "Writing data to disk\n"); + mProcessor->WriteDataToDisk(ADB_DUMP, NO_TIME_CONSTRAINTS, getElapsedRealtimeNs()); + return NO_ERROR; +} + +status_t StatsService::cmd_log_app_breadcrumb(int out, const Vector& args) { + bool good = false; + int32_t uid; + int32_t label; + int32_t state; + const int argCount = args.size(); + if (argCount == 3) { + // Automatically pick the UID + uid = AIBinder_getCallingUid(); + label = atoi(args[1].c_str()); + state = atoi(args[2].c_str()); + good = true; + } else if (argCount == 4) { + good = getUidFromArgs(args, 1, uid); + if (!good) { + dprintf(out, + "Invalid UID. Note that selecting a UID for writing AppBreadcrumb can only be " + "done for other UIDs on eng or userdebug builds.\n"); + } + label = atoi(args[2].c_str()); + state = atoi(args[3].c_str()); + } + if (good) { + dprintf(out, "Logging AppBreadcrumbReported(%d, %d, %d) to statslog.\n", uid, label, state); + android::os::statsd::util::stats_write( + android::os::statsd::util::APP_BREADCRUMB_REPORTED, uid, label, state); + } else { + print_cmd_help(out); + return UNKNOWN_ERROR; + } + return NO_ERROR; +} + +status_t StatsService::cmd_log_binary_push(int out, const Vector& args) { + // Security checks are done in the sendBinaryPushStateChanged atom. + const int argCount = args.size(); + if (argCount != 7 && argCount != 8) { + dprintf(out, "Incorrect number of argument supplied\n"); + return UNKNOWN_ERROR; + } + string trainName = string(args[1].c_str()); + int64_t trainVersion = strtoll(args[2].c_str(), nullptr, 10); + int32_t state = atoi(args[6].c_str()); + vector experimentIds; + if (argCount == 8) { + vector experimentIdsString = android::base::Split(string(args[7].c_str()), ","); + for (string experimentIdString : experimentIdsString) { + int64_t experimentId = strtoll(experimentIdString.c_str(), nullptr, 10); + experimentIds.push_back(experimentId); + } + } + dprintf(out, "Logging BinaryPushStateChanged\n"); + vector experimentIdBytes; + writeExperimentIdsToProto(experimentIds, &experimentIdBytes); + LogEvent event(trainName, trainVersion, args[3], args[4], args[5], state, experimentIdBytes, 0); + mProcessor->OnLogEvent(&event); + return NO_ERROR; +} + +status_t StatsService::cmd_print_pulled_metrics(int out, const Vector& args) { + int s = atoi(args[1].c_str()); + vector uids; + if (args.size() > 2) { + string package = string(args[2].c_str()); + auto it = UidMap::sAidToUidMapping.find(package); + if (it != UidMap::sAidToUidMapping.end()) { + uids.push_back(it->second); + } else { + set uids_set = mUidMap->getAppUid(package); + uids.insert(uids.end(), uids_set.begin(), uids_set.end()); + } + } else { + uids.push_back(AID_SYSTEM); + } + vector> stats; + if (mPullerManager->Pull(s, uids, getElapsedRealtimeNs(), &stats)) { + for (const auto& it : stats) { + dprintf(out, "Pull from %d: %s\n", s, it->ToString().c_str()); + } + dprintf(out, "Pull from %d: Received %zu elements\n", s, stats.size()); + return NO_ERROR; + } + return UNKNOWN_ERROR; +} + +status_t StatsService::cmd_remove_all_configs(int out) { + dprintf(out, "Removing all configs...\n"); + VLOG("StatsService::cmd_remove_all_configs was called"); + mConfigManager->RemoveAllConfigs(); + StorageManager::deleteAllFiles(STATS_SERVICE_DIR); + return NO_ERROR; +} + +status_t StatsService::cmd_dump_memory_info(int out) { + dprintf(out, "meminfo not available.\n"); + return NO_ERROR; +} + +status_t StatsService::cmd_clear_puller_cache(int out) { + VLOG("StatsService::cmd_clear_puller_cache with Pid %i, Uid %i", + AIBinder_getCallingPid(), AIBinder_getCallingUid()); + if (checkPermission(kPermissionDump)) { + int cleared = mPullerManager->ForceClearPullerCache(); + dprintf(out, "Puller removed %d cached data!\n", cleared); + return NO_ERROR; + } else { + return PERMISSION_DENIED; + } +} + +status_t StatsService::cmd_print_logs(int out, const Vector& args) { + Status status = checkUid(AID_ROOT); + if (!status.isOk()) { + return PERMISSION_DENIED; + } + + VLOG("StatsService::cmd_print_logs with pid %i, uid %i", AIBinder_getCallingPid(), + AIBinder_getCallingUid()); + bool enabled = true; + if (args.size() >= 2) { + enabled = atoi(args[1].c_str()) != 0; + } + mProcessor->setPrintLogs(enabled); + return NO_ERROR; +} + +bool StatsService::getUidFromArgs(const Vector& args, size_t uidArgIndex, int32_t& uid) { + return getUidFromString(args[uidArgIndex].c_str(), uid); +} + +bool StatsService::getUidFromString(const char* s, int32_t& uid) { + if (*s == '\0') { + return false; + } + char* endc = NULL; + int64_t longUid = strtol(s, &endc, 0); + if (*endc != '\0') { + return false; + } + int32_t goodUid = static_cast(longUid); + if (longUid < 0 || static_cast(longUid) != static_cast(goodUid)) { + return false; // It was not of uid_t type. + } + uid = goodUid; + + int32_t callingUid = AIBinder_getCallingUid(); + return mEngBuild // UserDebug/EngBuild are allowed to impersonate uids. + || (callingUid == goodUid) // Anyone can 'impersonate' themselves. + || (callingUid == AID_ROOT && goodUid == AID_SHELL); // ROOT can impersonate SHELL. +} + +Status StatsService::informAllUidData(const ScopedFileDescriptor& fd) { + ENFORCE_UID(AID_SYSTEM); + // Read stream into buffer. + string buffer; + if (!android::base::ReadFdToString(fd.get(), &buffer)) { + return exception(EX_ILLEGAL_ARGUMENT, "Failed to read all data from the pipe."); + } + + // Parse buffer. + UidData uidData; + if (!uidData.ParseFromString(buffer)) { + return exception(EX_ILLEGAL_ARGUMENT, "Error parsing proto stream for UidData."); + } + + vector versionStrings; + vector installers; + vector packageNames; + vector uids; + vector versions; + + const auto numEntries = uidData.app_info_size(); + versionStrings.reserve(numEntries); + installers.reserve(numEntries); + packageNames.reserve(numEntries); + uids.reserve(numEntries); + versions.reserve(numEntries); + + for (const auto& appInfo: uidData.app_info()) { + packageNames.emplace_back(String16(appInfo.package_name().c_str())); + uids.push_back(appInfo.uid()); + versions.push_back(appInfo.version()); + versionStrings.emplace_back(String16(appInfo.version_string().c_str())); + installers.emplace_back(String16(appInfo.installer().c_str())); + } + + mUidMap->updateMap(getElapsedRealtimeNs(), + uids, + versions, + versionStrings, + packageNames, + installers); + + mBootCompleteTrigger.markComplete(kUidMapReceivedTag); + VLOG("StatsService::informAllUidData UidData proto parsed successfully."); + return Status::ok(); +} + +Status StatsService::informOnePackage(const string& app, int32_t uid, int64_t version, + const string& versionString, const string& installer) { + ENFORCE_UID(AID_SYSTEM); + + VLOG("StatsService::informOnePackage was called"); + String16 utf16App = String16(app.c_str()); + String16 utf16VersionString = String16(versionString.c_str()); + String16 utf16Installer = String16(installer.c_str()); + + mUidMap->updateApp(getElapsedRealtimeNs(), utf16App, uid, version, utf16VersionString, + utf16Installer); + return Status::ok(); +} + +Status StatsService::informOnePackageRemoved(const string& app, int32_t uid) { + ENFORCE_UID(AID_SYSTEM); + + VLOG("StatsService::informOnePackageRemoved was called"); + String16 utf16App = String16(app.c_str()); + mUidMap->removeApp(getElapsedRealtimeNs(), utf16App, uid); + mConfigManager->RemoveConfigs(uid); + return Status::ok(); +} + +Status StatsService::informAnomalyAlarmFired() { + ENFORCE_UID(AID_SYSTEM); + // Anomaly alarms are handled internally now. This code should be fully deleted. + return Status::ok(); +} + +Status StatsService::informAlarmForSubscriberTriggeringFired() { + ENFORCE_UID(AID_SYSTEM); + + VLOG("StatsService::informAlarmForSubscriberTriggeringFired was called"); + int64_t currentTimeSec = getElapsedRealtimeSec(); + std::unordered_set, SpHash> alarmSet = + mPeriodicAlarmMonitor->popSoonerThan(static_cast(currentTimeSec)); + if (alarmSet.size() > 0) { + VLOG("Found periodic alarm fired."); + mProcessor->onPeriodicAlarmFired(currentTimeSec * NS_PER_SEC, alarmSet); + } else { + ALOGW("Cannot find an periodic alarm that fired. Perhaps it was recently cancelled."); + } + return Status::ok(); +} + +Status StatsService::informPollAlarmFired() { + ENFORCE_UID(AID_SYSTEM); + + VLOG("StatsService::informPollAlarmFired was called"); + mProcessor->informPullAlarmFired(getElapsedRealtimeNs()); + VLOG("StatsService::informPollAlarmFired succeeded"); + return Status::ok(); +} + +Status StatsService::systemRunning() { + ENFORCE_UID(AID_SYSTEM); + + // When system_server is up and running, schedule the dropbox task to run. + VLOG("StatsService::systemRunning"); + sayHiToStatsCompanion(); + return Status::ok(); +} + +Status StatsService::informDeviceShutdown() { + ENFORCE_UID(AID_SYSTEM); + VLOG("StatsService::informDeviceShutdown"); + int64_t elapsedRealtimeNs = getElapsedRealtimeNs(); + mProcessor->WriteDataToDisk(DEVICE_SHUTDOWN, FAST, elapsedRealtimeNs); + mProcessor->SaveActiveConfigsToDisk(elapsedRealtimeNs); + mProcessor->SaveMetadataToDisk(getWallClockNs(), elapsedRealtimeNs); + return Status::ok(); +} + +void StatsService::sayHiToStatsCompanion() { + shared_ptr statsCompanion = getStatsCompanionService(); + if (statsCompanion != nullptr) { + VLOG("Telling statsCompanion that statsd is ready"); + statsCompanion->statsdReady(); + } else { + VLOG("Could not access statsCompanion"); + } +} + +Status StatsService::statsCompanionReady() { + ENFORCE_UID(AID_SYSTEM); + + VLOG("StatsService::statsCompanionReady was called"); + shared_ptr statsCompanion = getStatsCompanionService(); + if (statsCompanion == nullptr) { + return exception(EX_NULL_POINTER, + "StatsCompanion unavailable despite it contacting statsd."); + } + VLOG("StatsService::statsCompanionReady linking to statsCompanion."); + AIBinder_linkToDeath(statsCompanion->asBinder().get(), + mStatsCompanionServiceDeathRecipient.get(), this); + mPullerManager->SetStatsCompanionService(statsCompanion); + mAnomalyAlarmMonitor->setStatsCompanionService(statsCompanion); + mPeriodicAlarmMonitor->setStatsCompanionService(statsCompanion); + return Status::ok(); +} + +Status StatsService::bootCompleted() { + ENFORCE_UID(AID_SYSTEM); + + VLOG("StatsService::bootCompleted was called"); + mBootCompleteTrigger.markComplete(kBootCompleteTag); + return Status::ok(); +} + +void StatsService::Startup() { + mConfigManager->Startup(); + mProcessor->LoadActiveConfigsFromDisk(); + mProcessor->LoadMetadataFromDisk(getWallClockNs(), getElapsedRealtimeNs()); +} + +void StatsService::Terminate() { + ALOGI("StatsService::Terminating"); + if (mProcessor != nullptr) { + int64_t elapsedRealtimeNs = getElapsedRealtimeNs(); + mProcessor->WriteDataToDisk(TERMINATION_SIGNAL_RECEIVED, FAST, elapsedRealtimeNs); + mProcessor->SaveActiveConfigsToDisk(elapsedRealtimeNs); + mProcessor->SaveMetadataToDisk(getWallClockNs(), elapsedRealtimeNs); + } +} + +// Test only interface!!! +void StatsService::OnLogEvent(LogEvent* event) { + mProcessor->OnLogEvent(event); + if (mShellSubscriber != nullptr) { + mShellSubscriber->onLogEvent(*event); + } +} + +Status StatsService::getData(int64_t key, const int32_t callingUid, vector* output) { + ENFORCE_UID(AID_SYSTEM); + + VLOG("StatsService::getData with Uid %i", callingUid); + ConfigKey configKey(callingUid, key); + // TODO(b/149254662): Since libbinder_ndk uses int8_t instead of uint8_t, + // there are inconsistencies with internal statsd logic. Instead of + // modifying lots of files, we create a temporary output array of int8_t and + // copy its data into output. This is a bad hack, but hopefully + // libbinder_ndk will transition to using uint8_t soon: progress is tracked + // in b/144957764. Same applies to StatsService::getMetadata. + vector unsignedOutput; + // The dump latency does not matter here since we do not include the current bucket, we do not + // need to pull any new data anyhow. + mProcessor->onDumpReport(configKey, getElapsedRealtimeNs(), false /* include_current_bucket*/, + true /* erase_data */, GET_DATA_CALLED, FAST, &unsignedOutput); + *output = vector(unsignedOutput.begin(), unsignedOutput.end()); + return Status::ok(); +} + +Status StatsService::getMetadata(vector* output) { + ENFORCE_UID(AID_SYSTEM); + + vector unsignedOutput; + StatsdStats::getInstance().dumpStats(&unsignedOutput, false); // Don't reset the counters. + *output = vector(unsignedOutput.begin(), unsignedOutput.end()); + return Status::ok(); +} + +Status StatsService::addConfiguration(int64_t key, const vector & config, + const int32_t callingUid) { + ENFORCE_UID(AID_SYSTEM); + + if (addConfigurationChecked(callingUid, key, config)) { + return Status::ok(); + } else { + return exception(EX_ILLEGAL_ARGUMENT, "Could not parse malformatted StatsdConfig."); + } +} + +bool StatsService::addConfigurationChecked(int uid, int64_t key, const vector& config) { + ConfigKey configKey(uid, key); + StatsdConfig cfg; + if (config.size() > 0) { // If the config is empty, skip parsing. + if (!cfg.ParseFromArray(&config[0], config.size())) { + return false; + } + } + mConfigManager->UpdateConfig(configKey, cfg); + return true; +} + +Status StatsService::removeDataFetchOperation(int64_t key, + const int32_t callingUid) { + ENFORCE_UID(AID_SYSTEM); + ConfigKey configKey(callingUid, key); + mConfigManager->RemoveConfigReceiver(configKey); + return Status::ok(); +} + +Status StatsService::setDataFetchOperation(int64_t key, + const shared_ptr& pir, + const int32_t callingUid) { + ENFORCE_UID(AID_SYSTEM); + + ConfigKey configKey(callingUid, key); + mConfigManager->SetConfigReceiver(configKey, pir); + if (StorageManager::hasConfigMetricsReport(configKey)) { + VLOG("StatsService::setDataFetchOperation marking configKey %s to dump reports on disk", + configKey.ToString().c_str()); + mProcessor->noteOnDiskData(configKey); + } + return Status::ok(); +} + +Status StatsService::setActiveConfigsChangedOperation(const shared_ptr& pir, + const int32_t callingUid, + vector* output) { + ENFORCE_UID(AID_SYSTEM); + + mConfigManager->SetActiveConfigsChangedReceiver(callingUid, pir); + if (output != nullptr) { + mProcessor->GetActiveConfigs(callingUid, *output); + } else { + ALOGW("StatsService::setActiveConfigsChanged output was nullptr"); + } + return Status::ok(); +} + +Status StatsService::removeActiveConfigsChangedOperation(const int32_t callingUid) { + ENFORCE_UID(AID_SYSTEM); + + mConfigManager->RemoveActiveConfigsChangedReceiver(callingUid); + return Status::ok(); +} + +Status StatsService::removeConfiguration(int64_t key, const int32_t callingUid) { + ENFORCE_UID(AID_SYSTEM); + + ConfigKey configKey(callingUid, key); + mConfigManager->RemoveConfig(configKey); + return Status::ok(); +} + +Status StatsService::setBroadcastSubscriber(int64_t configId, + int64_t subscriberId, + const shared_ptr& pir, + const int32_t callingUid) { + ENFORCE_UID(AID_SYSTEM); + + VLOG("StatsService::setBroadcastSubscriber called."); + ConfigKey configKey(callingUid, configId); + SubscriberReporter::getInstance() + .setBroadcastSubscriber(configKey, subscriberId, pir); + return Status::ok(); +} + +Status StatsService::unsetBroadcastSubscriber(int64_t configId, + int64_t subscriberId, + const int32_t callingUid) { + ENFORCE_UID(AID_SYSTEM); + + VLOG("StatsService::unsetBroadcastSubscriber called."); + ConfigKey configKey(callingUid, configId); + SubscriberReporter::getInstance() + .unsetBroadcastSubscriber(configKey, subscriberId); + return Status::ok(); +} + +Status StatsService::allPullersFromBootRegistered() { + ENFORCE_UID(AID_SYSTEM); + + VLOG("StatsService::allPullersFromBootRegistered was called"); + mBootCompleteTrigger.markComplete(kAllPullersRegisteredTag); + return Status::ok(); +} + +Status StatsService::registerPullAtomCallback(int32_t uid, int32_t atomTag, int64_t coolDownMillis, + int64_t timeoutMillis, + const std::vector& additiveFields, + const shared_ptr& pullerCallback) { + ENFORCE_UID(AID_SYSTEM); + VLOG("StatsService::registerPullAtomCallback called."); + mPullerManager->RegisterPullAtomCallback(uid, atomTag, MillisToNano(coolDownMillis), + MillisToNano(timeoutMillis), additiveFields, + pullerCallback); + return Status::ok(); +} + +Status StatsService::registerNativePullAtomCallback( + int32_t atomTag, int64_t coolDownMillis, int64_t timeoutMillis, + const std::vector& additiveFields, + const shared_ptr& pullerCallback) { + if (!checkPermission(kPermissionRegisterPullAtom)) { + return exception( + EX_SECURITY, + StringPrintf("Uid %d does not have the %s permission when registering atom %d", + AIBinder_getCallingUid(), kPermissionRegisterPullAtom, atomTag)); + } + VLOG("StatsService::registerNativePullAtomCallback called."); + int32_t uid = AIBinder_getCallingUid(); + mPullerManager->RegisterPullAtomCallback(uid, atomTag, MillisToNano(coolDownMillis), + MillisToNano(timeoutMillis), additiveFields, + pullerCallback); + return Status::ok(); +} + +Status StatsService::unregisterPullAtomCallback(int32_t uid, int32_t atomTag) { + ENFORCE_UID(AID_SYSTEM); + VLOG("StatsService::unregisterPullAtomCallback called."); + mPullerManager->UnregisterPullAtomCallback(uid, atomTag); + return Status::ok(); +} + +Status StatsService::unregisterNativePullAtomCallback(int32_t atomTag) { + if (!checkPermission(kPermissionRegisterPullAtom)) { + return exception( + EX_SECURITY, + StringPrintf("Uid %d does not have the %s permission when unregistering atom %d", + AIBinder_getCallingUid(), kPermissionRegisterPullAtom, atomTag)); + } + VLOG("StatsService::unregisterNativePullAtomCallback called."); + int32_t uid = AIBinder_getCallingUid(); + mPullerManager->UnregisterPullAtomCallback(uid, atomTag); + return Status::ok(); +} + +Status StatsService::getRegisteredExperimentIds(std::vector* experimentIdsOut) { + ENFORCE_UID(AID_SYSTEM); + // TODO: add verifier permission + + experimentIdsOut->clear(); + // Read the latest train info + vector trainInfoList = StorageManager::readAllTrainInfo(); + if (trainInfoList.empty()) { + // No train info means no experiment IDs, return an empty list + return Status::ok(); + } + + // Copy the experiment IDs to the out vector + for (InstallTrainInfo& trainInfo : trainInfoList) { + experimentIdsOut->insert(experimentIdsOut->end(), + trainInfo.experimentIds.begin(), + trainInfo.experimentIds.end()); + } + return Status::ok(); +} + +void StatsService::statsCompanionServiceDied(void* cookie) { + auto thiz = static_cast(cookie); + thiz->statsCompanionServiceDiedImpl(); +} + +void StatsService::statsCompanionServiceDiedImpl() { + ALOGW("statscompanion service died"); + StatsdStats::getInstance().noteSystemServerRestart(getWallClockSec()); + if (mProcessor != nullptr) { + ALOGW("Reset statsd upon system server restarts."); + int64_t systemServerRestartNs = getElapsedRealtimeNs(); + ProtoOutputStream activeConfigsProto; + mProcessor->WriteActiveConfigsToProtoOutputStream(systemServerRestartNs, + STATSCOMPANION_DIED, &activeConfigsProto); + metadata::StatsMetadataList metadataList; + mProcessor->WriteMetadataToProto(getWallClockNs(), + systemServerRestartNs, &metadataList); + mProcessor->WriteDataToDisk(STATSCOMPANION_DIED, FAST, systemServerRestartNs); + mProcessor->resetConfigs(); + + std::string serializedActiveConfigs; + if (activeConfigsProto.serializeToString(&serializedActiveConfigs)) { + ActiveConfigList activeConfigs; + if (activeConfigs.ParseFromString(serializedActiveConfigs)) { + mProcessor->SetConfigsActiveState(activeConfigs, systemServerRestartNs); + } + } + mProcessor->SetMetadataState(metadataList, getWallClockNs(), systemServerRestartNs); + } + mAnomalyAlarmMonitor->setStatsCompanionService(nullptr); + mPeriodicAlarmMonitor->setStatsCompanionService(nullptr); + mPullerManager->SetStatsCompanionService(nullptr); +} + +} // namespace statsd +} // namespace os +} // namespace android diff --git a/statsd/src/StatsService.h b/statsd/src/StatsService.h new file mode 100644 index 00000000..c2b6a99a --- /dev/null +++ b/statsd/src/StatsService.h @@ -0,0 +1,420 @@ +/* + * 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. + */ + +#ifndef STATS_SERVICE_H +#define STATS_SERVICE_H + +#include +#include +#include +#include +#include + +#include + +#include "StatsLogProcessor.h" +#include "anomaly/AlarmMonitor.h" +#include "config/ConfigManager.h" +#include "external/StatsPullerManager.h" +#include "logd/LogEventQueue.h" +#include "packages/UidMap.h" +#include "shell/ShellSubscriber.h" +#include "statscompanion_util.h" +#include "utils/MultiConditionTrigger.h" + +using namespace android; +using namespace android::os; +using namespace std; + +using Status = ::ndk::ScopedAStatus; +using aidl::android::os::BnStatsd; +using aidl::android::os::IPendingIntentRef; +using aidl::android::os::IPullAtomCallback; +using ::ndk::ScopedAIBinder_DeathRecipient; +using ::ndk::ScopedFileDescriptor; +using std::shared_ptr; + +namespace android { +namespace os { +namespace statsd { + +class StatsService : public BnStatsd { +public: + StatsService(const sp& handlerLooper, std::shared_ptr queue); + virtual ~StatsService(); + + /** The anomaly alarm registered with AlarmManager won't be updated by less than this. */ + const uint32_t MIN_DIFF_TO_UPDATE_REGISTERED_ALARM_SECS = 5; + + virtual status_t dump(int fd, const char** args, uint32_t numArgs) override; + virtual status_t handleShellCommand(int in, int out, int err, const char** argv, + uint32_t argc) override; + + virtual Status systemRunning(); + virtual Status statsCompanionReady(); + virtual Status bootCompleted(); + virtual Status informAnomalyAlarmFired(); + virtual Status informPollAlarmFired(); + virtual Status informAlarmForSubscriberTriggeringFired(); + + virtual Status informAllUidData(const ScopedFileDescriptor& fd); + virtual Status informOnePackage(const string& app, int32_t uid, int64_t version, + const string& versionString, const string& installer); + virtual Status informOnePackageRemoved(const string& app, int32_t uid); + virtual Status informDeviceShutdown(); + + /** + * Called right before we start processing events. + */ + void Startup(); + + /** + * Called when terminiation signal received. + */ + void Terminate(); + + /** + * Test ONLY interface. In real world, StatsService reads from LogEventQueue. + */ + virtual void OnLogEvent(LogEvent* event); + + /** + * Binder call for clients to request data for this configuration key. + */ + virtual Status getData(int64_t key, + const int32_t callingUid, + vector* output) override; + + + /** + * Binder call for clients to get metadata across all configs in statsd. + */ + virtual Status getMetadata(vector* output) override; + + + /** + * Binder call to let clients send a configuration and indicate they're interested when they + * should requestData for this configuration. + */ + virtual Status addConfiguration(int64_t key, + const vector& config, + const int32_t callingUid) override; + + /** + * Binder call to let clients register the data fetch operation for a configuration. + */ + virtual Status setDataFetchOperation(int64_t key, + const shared_ptr& pir, + const int32_t callingUid) override; + + /** + * Binder call to remove the data fetch operation for the specified config key. + */ + virtual Status removeDataFetchOperation(int64_t key, + const int32_t callingUid) override; + + /** + * Binder call to let clients register the active configs changed operation. + */ + virtual Status setActiveConfigsChangedOperation(const shared_ptr& pir, + const int32_t callingUid, + vector* output) override; + + /** + * Binder call to remove the active configs changed operation for the specified package.. + */ + virtual Status removeActiveConfigsChangedOperation(const int32_t callingUid) override; + /** + * Binder call to allow clients to remove the specified configuration. + */ + virtual Status removeConfiguration(int64_t key, + const int32_t callingUid) override; + + /** + * Binder call to associate the given config's subscriberId with the given pendingIntentRef. + */ + virtual Status setBroadcastSubscriber(int64_t configId, + int64_t subscriberId, + const shared_ptr& pir, + const int32_t callingUid) override; + + /** + * Binder call to unassociate the given config's subscriberId with any pendingIntentRef. + */ + virtual Status unsetBroadcastSubscriber(int64_t configId, + int64_t subscriberId, + const int32_t callingUid) override; + + /** Inform statsCompanion that statsd is ready. */ + virtual void sayHiToStatsCompanion(); + + /** + * Binder call to notify statsd that all pullers from boot have been registered. + */ + virtual Status allPullersFromBootRegistered(); + + /** + * Binder call to register a callback function for a pulled atom. + */ + virtual Status registerPullAtomCallback( + int32_t uid, int32_t atomTag, int64_t coolDownMillis, int64_t timeoutMillis, + const std::vector& additiveFields, + const shared_ptr& pullerCallback) override; + + /** + * Binder call to register a callback function for a pulled atom. + */ + virtual Status registerNativePullAtomCallback( + int32_t atomTag, int64_t coolDownMillis, int64_t timeoutMillis, + const std::vector& additiveFields, + const shared_ptr& pullerCallback) override; + + /** + * Binder call to unregister any existing callback for the given uid and atom. + */ + virtual Status unregisterPullAtomCallback(int32_t uid, int32_t atomTag) override; + + /** + * Binder call to unregister any existing callback for the given atom and calling uid. + */ + virtual Status unregisterNativePullAtomCallback(int32_t atomTag) override; + + /** + * Binder call to get registered experiment IDs. + */ + virtual Status getRegisteredExperimentIds(std::vector* expIdsOut); + +private: + /** + * Load system properties at init. + */ + void init_system_properties(); + + /** + * Helper for loading system properties. + */ + static void init_build_type_callback(void* cookie, const char* name, const char* value, + uint32_t serial); + + /** + * Proto output of statsd report data dumpsys, wrapped in a StatsDataDumpProto. + */ + void dumpIncidentSection(int outFd); + + /** + * Text or proto output of statsdStats dumpsys. + */ + void dumpStatsdStats(int outFd, bool verbose, bool proto); + + /** + * Print usage information for the commands + */ + void print_cmd_help(int out); + + /* Runs on its dedicated thread to process pushed stats event from socket. */ + void readLogs(); + + /** + * Trigger a broadcast. + */ + status_t cmd_trigger_broadcast(int outFd, Vector& args); + + + /** + * Trigger an active configs changed broadcast. + */ + status_t cmd_trigger_active_config_broadcast(int outFd, Vector& args); + + /** + * Handle the config sub-command. + */ + status_t cmd_config(int inFd, int outFd, int err, Vector& args); + + /** + * Prints some basic stats to std out. + */ + status_t cmd_print_stats(int outFd, const Vector& args); + + /** + * Print the event log. + */ + status_t cmd_dump_report(int outFd, const Vector& args); + + /** + * Print the mapping of uids to package names. + */ + status_t cmd_print_uid_map(int outFd, const Vector& args); + + /** + * Flush the data to disk. + */ + status_t cmd_write_data_to_disk(int outFd); + + /** + * Write an AppBreadcrumbReported event to the StatsLog buffer, as if calling + * StatsLog.write(APP_BREADCRUMB_REPORTED). + */ + status_t cmd_log_app_breadcrumb(int outFd, const Vector& args); + + /** + * Write an BinaryPushStateChanged event, as if calling StatsLog.logBinaryPushStateChanged(). + */ + status_t cmd_log_binary_push(int outFd, const Vector& args); + + /** + * Print contents of a pulled metrics source. + */ + status_t cmd_print_pulled_metrics(int outFd, const Vector& args); + + /** + * Removes all configs stored on disk and on memory. + */ + status_t cmd_remove_all_configs(int outFd); + + /* + * Dump memory usage by statsd. + */ + status_t cmd_dump_memory_info(int outFd); + + /* + * Clear all puller cached data + */ + status_t cmd_clear_puller_cache(int outFd); + + /** + * Print all stats logs received to logcat. + */ + status_t cmd_print_logs(int outFd, const Vector& args); + + /** + * Writes the value of args[uidArgIndex] into uid. + * Returns whether the uid is reasonable (type uid_t) and whether + * 1. it is equal to the calling uid, or + * 2. the device is mEngBuild, or + * 3. the caller is AID_ROOT and the uid is AID_SHELL (i.e. ROOT can impersonate SHELL). + */ + bool getUidFromArgs(const Vector& args, size_t uidArgIndex, int32_t& uid); + + /** + * Writes the value of uidSting into uid. + * Returns whether the uid is reasonable (type uid_t) and whether + * 1. it is equal to the calling uid, or + * 2. the device is mEngBuild, or + * 3. the caller is AID_ROOT and the uid is AID_SHELL (i.e. ROOT can impersonate SHELL). + */ + bool getUidFromString(const char* uidString, int32_t& uid); + + /** + * Adds a configuration after checking permissions and obtaining UID from binder call. + */ + bool addConfigurationChecked(int uid, int64_t key, const vector& config); + + /** + * Update a configuration. + */ + void set_config(int uid, const string& name, const StatsdConfig& config); + + /** + * Death recipient callback that is called when StatsCompanionService dies. + * The cookie is a pointer to a StatsService object. + */ + static void statsCompanionServiceDied(void* cookie); + + /** + * Implementation of statsCompanionServiceDied. + */ + void statsCompanionServiceDiedImpl(); + + /** + * Tracks the uid <--> package name mapping. + */ + sp mUidMap; + + /** + * Fetches external metrics + */ + sp mPullerManager; + + /** + * Tracks the configurations that have been passed to statsd. + */ + sp mConfigManager; + + /** + * The metrics recorder. + */ + sp mProcessor; + + /** + * The alarm monitor for anomaly detection. + */ + const sp mAnomalyAlarmMonitor; + + /** + * The alarm monitor for alarms to directly trigger subscriber. + */ + const sp mPeriodicAlarmMonitor; + + /** + * Whether this is an eng build. + */ + bool mEngBuild; + + sp mShellSubscriber; + + /** + * Mutex for setting the shell subscriber + */ + mutable mutex mShellSubscriberMutex; + std::shared_ptr mEventQueue; + + MultiConditionTrigger mBootCompleteTrigger; + static const inline string kBootCompleteTag = "BOOT_COMPLETE"; + static const inline string kUidMapReceivedTag = "UID_MAP"; + static const inline string kAllPullersRegisteredTag = "PULLERS_REGISTERED"; + + ScopedAIBinder_DeathRecipient mStatsCompanionServiceDeathRecipient; + + FRIEND_TEST(StatsLogProcessorTest, TestActivationsPersistAcrossSystemServerRestart); + FRIEND_TEST(StatsServiceTest, TestAddConfig_simple); + FRIEND_TEST(StatsServiceTest, TestAddConfig_empty); + FRIEND_TEST(StatsServiceTest, TestAddConfig_invalid); + FRIEND_TEST(StatsServiceTest, TestGetUidFromArgs); + FRIEND_TEST(PartialBucketE2eTest, TestCountMetricNoSplitOnNewApp); + FRIEND_TEST(PartialBucketE2eTest, TestCountMetricSplitOnBoot); + FRIEND_TEST(PartialBucketE2eTest, TestCountMetricSplitOnUpgrade); + FRIEND_TEST(PartialBucketE2eTest, TestCountMetricSplitOnRemoval); + FRIEND_TEST(PartialBucketE2eTest, TestCountMetricWithoutSplit); + FRIEND_TEST(PartialBucketE2eTest, TestValueMetricOnBootWithoutMinPartialBucket); + FRIEND_TEST(PartialBucketE2eTest, TestValueMetricWithoutMinPartialBucket); + FRIEND_TEST(PartialBucketE2eTest, TestValueMetricWithMinPartialBucket); + FRIEND_TEST(PartialBucketE2eTest, TestGaugeMetricOnBootWithoutMinPartialBucket); + FRIEND_TEST(PartialBucketE2eTest, TestGaugeMetricWithoutMinPartialBucket); + FRIEND_TEST(PartialBucketE2eTest, TestGaugeMetricWithMinPartialBucket); + + FRIEND_TEST(ConfigUpdateE2eTest, TestAnomalyDurationMetric); + + FRIEND_TEST(AnomalyDetectionE2eTest, TestDurationMetric_SUM_single_bucket); + FRIEND_TEST(AnomalyDetectionE2eTest, TestDurationMetric_SUM_partial_bucket); + FRIEND_TEST(AnomalyDetectionE2eTest, TestDurationMetric_SUM_multiple_buckets); + FRIEND_TEST(AnomalyDetectionE2eTest, TestDurationMetric_SUM_long_refractory_period); +}; + +} // namespace statsd +} // namespace os +} // namespace android + +#endif // STATS_SERVICE_H diff --git a/statsd/src/active_config_list.proto b/statsd/src/active_config_list.proto new file mode 100644 index 00000000..99298335 --- /dev/null +++ b/statsd/src/active_config_list.proto @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2019 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. + */ + +syntax = "proto2"; + +package android.os.statsd; +option java_package = "com.android.os"; +option java_multiple_files = true; +option java_outer_classname = "ActiveConfigProto"; + +message ActiveEventActivation { + optional int32 atom_matcher_index = 1; + + // Time left in activation. When this proto is loaded after device boot, + // the activation should be set to active for this duration. + // This field will only be set when the state is ACTIVE + optional int64 remaining_ttl_nanos = 2; + + enum State { + UNNKNOWN = 0; + // This metric should activate for remaining_ttl_nanos when we load the activations. + ACTIVE = 1; + // When we load the activations, this metric should activate on next boot for the tll + // specified in the config. + ACTIVATE_ON_BOOT = 2; + } + optional State state = 3; +} + +message ActiveMetric { + optional int64 id = 1; + repeated ActiveEventActivation activation = 2; +} + +message ActiveConfig { + optional int64 id = 1; + optional int32 uid = 2; + repeated ActiveMetric metric = 3; +} + +// all configs and their metrics on device. +message ActiveConfigList { + repeated ActiveConfig config = 1; +} diff --git a/statsd/src/annotations.h b/statsd/src/annotations.h new file mode 100644 index 00000000..cf7f5433 --- /dev/null +++ b/statsd/src/annotations.h @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2020 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. + */ + +#pragma once + +namespace android { +namespace os { +namespace statsd { + +const uint8_t ANNOTATION_ID_IS_UID = 1; +const uint8_t ANNOTATION_ID_TRUNCATE_TIMESTAMP = 2; +const uint8_t ANNOTATION_ID_PRIMARY_FIELD = 3; +const uint8_t ANNOTATION_ID_EXCLUSIVE_STATE = 4; +const uint8_t ANNOTATION_ID_PRIMARY_FIELD_FIRST_UID = 5; +const uint8_t ANNOTATION_ID_TRIGGER_STATE_RESET = 7; +const uint8_t ANNOTATION_ID_STATE_NESTED = 8; + +} // namespace statsd +} // namespace os +} // namespace android diff --git a/statsd/src/anomaly/AlarmMonitor.cpp b/statsd/src/anomaly/AlarmMonitor.cpp new file mode 100644 index 00000000..b632d040 --- /dev/null +++ b/statsd/src/anomaly/AlarmMonitor.cpp @@ -0,0 +1,139 @@ +/* + * 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. + */ + +#define DEBUG false +#include "Log.h" + +#include "anomaly/AlarmMonitor.h" +#include "guardrail/StatsdStats.h" + +namespace android { +namespace os { +namespace statsd { + +AlarmMonitor::AlarmMonitor( + uint32_t minDiffToUpdateRegisteredAlarmTimeSec, + const std::function&, int64_t)>& updateAlarm, + const std::function&)>& cancelAlarm) + : mRegisteredAlarmTimeSec(0), + mMinUpdateTimeSec(minDiffToUpdateRegisteredAlarmTimeSec), + mUpdateAlarm(updateAlarm), + mCancelAlarm(cancelAlarm) {} + +AlarmMonitor::~AlarmMonitor() {} + +void AlarmMonitor::setStatsCompanionService( + shared_ptr statsCompanionService) { + std::lock_guard lock(mLock); + shared_ptr tmpForLock = mStatsCompanionService; + mStatsCompanionService = statsCompanionService; + if (statsCompanionService == nullptr) { + VLOG("Erasing link to statsCompanionService"); + return; + } + VLOG("Creating link to statsCompanionService"); + const sp top = mPq.top(); + if (top != nullptr) { + updateRegisteredAlarmTime_l(top->timestampSec); + } +} + +void AlarmMonitor::add(sp alarm) { + std::lock_guard lock(mLock); + if (alarm == nullptr) { + ALOGW("Asked to add a null alarm."); + return; + } + if (alarm->timestampSec < 1) { + // forbidden since a timestamp 0 is used to indicate no alarm registered + ALOGW("Asked to add a 0-time alarm."); + return; + } + // TODO(b/110563466): Ensure that refractory period is respected. + VLOG("Adding alarm with time %u", alarm->timestampSec); + mPq.push(alarm); + if (mRegisteredAlarmTimeSec < 1 || + alarm->timestampSec + mMinUpdateTimeSec < mRegisteredAlarmTimeSec) { + updateRegisteredAlarmTime_l(alarm->timestampSec); + } +} + +void AlarmMonitor::remove(sp alarm) { + std::lock_guard lock(mLock); + if (alarm == nullptr) { + ALOGW("Asked to remove a null alarm."); + return; + } + VLOG("Removing alarm with time %u", alarm->timestampSec); + bool wasPresent = mPq.remove(alarm); + if (!wasPresent) return; + if (mPq.empty()) { + VLOG("Queue is empty. Cancel any alarm."); + cancelRegisteredAlarmTime_l(); + return; + } + uint32_t soonestAlarmTimeSec = mPq.top()->timestampSec; + VLOG("Soonest alarm is %u", soonestAlarmTimeSec); + if (soonestAlarmTimeSec > mRegisteredAlarmTimeSec + mMinUpdateTimeSec) { + updateRegisteredAlarmTime_l(soonestAlarmTimeSec); + } +} + +// More efficient than repeatedly calling remove(mPq.top()) since it batches the +// updates to the registered alarm. +unordered_set, SpHash> AlarmMonitor::popSoonerThan( + uint32_t timestampSec) { + VLOG("Removing alarms with time <= %u", timestampSec); + unordered_set, SpHash> oldAlarms; + std::lock_guard lock(mLock); + + for (sp t = mPq.top(); t != nullptr && t->timestampSec <= timestampSec; + t = mPq.top()) { + oldAlarms.insert(t); + mPq.pop(); // remove t + } + // Always update registered alarm time (if anything has changed). + if (!oldAlarms.empty()) { + if (mPq.empty()) { + VLOG("Queue is empty. Cancel any alarm."); + cancelRegisteredAlarmTime_l(); + } else { + // Always update the registered alarm in this case (unlike remove()). + updateRegisteredAlarmTime_l(mPq.top()->timestampSec); + } + } + return oldAlarms; +} + +void AlarmMonitor::updateRegisteredAlarmTime_l(uint32_t timestampSec) { + VLOG("Updating reg alarm time to %u", timestampSec); + mRegisteredAlarmTimeSec = timestampSec; + mUpdateAlarm(mStatsCompanionService, secToMs(mRegisteredAlarmTimeSec)); +} + +void AlarmMonitor::cancelRegisteredAlarmTime_l() { + VLOG("Cancelling reg alarm."); + mRegisteredAlarmTimeSec = 0; + mCancelAlarm(mStatsCompanionService); +} + +int64_t AlarmMonitor::secToMs(uint32_t timeSec) { + return ((int64_t)timeSec) * 1000; +} + +} // namespace statsd +} // namespace os +} // namespace android diff --git a/statsd/src/anomaly/AlarmMonitor.h b/statsd/src/anomaly/AlarmMonitor.h new file mode 100644 index 00000000..5c34e381 --- /dev/null +++ b/statsd/src/anomaly/AlarmMonitor.h @@ -0,0 +1,162 @@ +/* + * 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. + */ + +#pragma once + +#include "anomaly/indexed_priority_queue.h" + +#include +#include + +#include +#include + +using namespace android; + +using aidl::android::os::IStatsCompanionService; +using std::function; +using std::shared_ptr; +using std::unordered_set; + +namespace android { +namespace os { +namespace statsd { + +/** + * Represents an alarm, associated with some aggregate metric, holding a + * projected time at which the metric is expected to exceed its anomaly + * threshold. + * Timestamps are in seconds since epoch in a uint32, so will fail in year 2106. + */ +struct InternalAlarm : public RefBase { + explicit InternalAlarm(uint32_t timestampSec) : timestampSec(timestampSec) { + } + + const uint32_t timestampSec; + + /** InternalAlarm a is smaller (higher priority) than b if its timestamp is sooner. */ + struct SmallerTimestamp { + bool operator()(sp a, sp b) const { + return (a->timestampSec < b->timestampSec); + } + }; +}; + +/** + * Manages internal alarms that may get registered with the AlarmManager. + */ +class AlarmMonitor : public RefBase { +public: + /** + * @param minDiffToUpdateRegisteredAlarmTimeSec If the soonest alarm differs + * from the registered alarm by more than this amount, update the registered + * alarm. + */ + AlarmMonitor(uint32_t minDiffToUpdateRegisteredAlarmTimeSec, + const function&, int64_t)>& + updateAlarm, + const function&)>& cancelAlarm); + ~AlarmMonitor(); + + /** + * Tells AnomalyMonitor what IStatsCompanionService to use and, if + * applicable, immediately registers an existing alarm with it. + * If nullptr, AnomalyMonitor will continue to add/remove alarms, but won't + * update IStatsCompanionService (until such time as it is set non-null). + */ + void setStatsCompanionService(shared_ptr statsCompanionService); + + /** + * Adds the given alarm (reference) to the queue. + */ + void add(sp alarm); + + /** + * Removes the given alarm (reference) from the queue. + * Note that alarm comparison is reference-based; if another alarm exists + * with the same timestampSec, that alarm will still remain in the queue. + */ + void remove(sp alarm); + + /** + * Returns and removes all alarms whose timestamp <= the given timestampSec. + * Always updates the registered alarm if return is non-empty. + */ + unordered_set, SpHash> popSoonerThan( + uint32_t timestampSec); + + /** + * Returns the projected alarm timestamp that is registered with + * StatsCompanionService. This may not be equal to the soonest alarm, + * but should be within minDiffToUpdateRegisteredAlarmTimeSec of it. + */ + uint32_t getRegisteredAlarmTimeSec() const { + return mRegisteredAlarmTimeSec; + } + +private: + std::mutex mLock; + + /** + * Timestamp (seconds since epoch) of the alarm registered with + * StatsCompanionService. This, in general, may not be equal to the soonest + * alarm stored in mPq, but should be within minUpdateTimeSec of it. + * A value of 0 indicates that no alarm is currently registered. + */ + uint32_t mRegisteredAlarmTimeSec; + + /** + * Priority queue of alarms, prioritized by soonest alarm.timestampSec. + */ + indexed_priority_queue mPq; + + /** + * Binder interface for communicating with StatsCompanionService. + */ + shared_ptr mStatsCompanionService = nullptr; + + /** + * Amount by which the soonest projected alarm must differ from + * mRegisteredAlarmTimeSec before updateRegisteredAlarmTime_l is called. + */ + uint32_t mMinUpdateTimeSec; + + /** + * Updates the alarm registered with StatsCompanionService to the given time. + * Also correspondingly updates mRegisteredAlarmTimeSec. + */ + void updateRegisteredAlarmTime_l(uint32_t timestampSec); + + /** + * Cancels the alarm registered with StatsCompanionService. + * Also correspondingly sets mRegisteredAlarmTimeSec to 0. + */ + void cancelRegisteredAlarmTime_l(); + + /** Converts uint32 timestamp in seconds to a Java long in msec. */ + int64_t secToMs(uint32_t timeSec); + + // Callback function to update the alarm via StatsCompanionService. + std::function, int64_t)> mUpdateAlarm; + + // Callback function to cancel the alarm via StatsCompanionService. + std::function)> mCancelAlarm; + +}; + +} // namespace statsd +} // namespace os +} // namespace android diff --git a/statsd/src/anomaly/AlarmTracker.cpp b/statsd/src/anomaly/AlarmTracker.cpp new file mode 100644 index 00000000..6d9beb8f --- /dev/null +++ b/statsd/src/anomaly/AlarmTracker.cpp @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2018 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. + */ + +#define DEBUG false // STOPSHIP if true +#include "Log.h" + +#include "anomaly/AlarmTracker.h" +#include "anomaly/subscriber_util.h" +#include "HashableDimensionKey.h" +#include "stats_util.h" +#include "storage/StorageManager.h" + +#include + +namespace android { +namespace os { +namespace statsd { + +AlarmTracker::AlarmTracker(const int64_t startMillis, + const int64_t currentMillis, + const Alarm& alarm, const ConfigKey& configKey, + const sp& alarmMonitor) + : mAlarmConfig(alarm), + mConfigKey(configKey), + mAlarmMonitor(alarmMonitor) { + VLOG("AlarmTracker() called"); + mAlarmSec = (startMillis + mAlarmConfig.offset_millis()) / MS_PER_SEC; + // startMillis is the time statsd is created. We need to find the 1st alarm timestamp after + // the config is added to statsd. + mAlarmSec = findNextAlarmSec(currentMillis / MS_PER_SEC); // round up + mInternalAlarm = new InternalAlarm{static_cast(mAlarmSec)}; + VLOG("AlarmTracker sets the periodic alarm at: %lld", (long long)mAlarmSec); + if (mAlarmMonitor != nullptr) { + mAlarmMonitor->add(mInternalAlarm); + } +} + +AlarmTracker::~AlarmTracker() { + VLOG("~AlarmTracker() called"); + if (mInternalAlarm != nullptr && mAlarmMonitor != nullptr) { + mAlarmMonitor->remove(mInternalAlarm); + } +} + +void AlarmTracker::addSubscription(const Subscription& subscription) { + mSubscriptions.push_back(subscription); +} + +int64_t AlarmTracker::findNextAlarmSec(int64_t currentTimeSec) { + if (currentTimeSec < mAlarmSec) { + return mAlarmSec; + } + int64_t periodsForward = + ((currentTimeSec - mAlarmSec) * MS_PER_SEC) / mAlarmConfig.period_millis() + 1; + return mAlarmSec + periodsForward * mAlarmConfig.period_millis() / MS_PER_SEC; +} + +void AlarmTracker::informAlarmsFired( + const int64_t& timestampNs, + unordered_set, SpHash>& firedAlarms) { + if (firedAlarms.empty() || mInternalAlarm == nullptr || + firedAlarms.find(mInternalAlarm) == firedAlarms.end()) { + return; + } + if (!mSubscriptions.empty()) { + VLOG("AlarmTracker triggers the subscribers."); + triggerSubscribers(mAlarmConfig.id(), 0 /*metricId N/A*/, DEFAULT_METRIC_DIMENSION_KEY, + 0 /* metricValue N/A */, mConfigKey, mSubscriptions); + } + firedAlarms.erase(mInternalAlarm); + mAlarmSec = findNextAlarmSec((timestampNs-1) / NS_PER_SEC + 1); // round up + mInternalAlarm = new InternalAlarm{static_cast(mAlarmSec)}; + VLOG("AlarmTracker sets the periodic alarm at: %lld", (long long)mAlarmSec); + if (mAlarmMonitor != nullptr) { + mAlarmMonitor->add(mInternalAlarm); + } +} + +} // namespace statsd +} // namespace os +} // namespace android diff --git a/statsd/src/anomaly/AlarmTracker.h b/statsd/src/anomaly/AlarmTracker.h new file mode 100644 index 00000000..4b8fab36 --- /dev/null +++ b/statsd/src/anomaly/AlarmTracker.h @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2018 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. + */ + +#pragma once + +#include + +#include "AlarmMonitor.h" +#include "config/ConfigKey.h" +#include "src/statsd_config.pb.h" // Alarm + +#include +#include + +namespace android { +namespace os { +namespace statsd { + +class AlarmTracker : public virtual RefBase { +public: + AlarmTracker(const int64_t startMillis, + const int64_t currentMillis, + const Alarm& alarm, const ConfigKey& configKey, + const sp& subscriberAlarmMonitor); + + virtual ~AlarmTracker(); + + void onAlarmFired(); + + void addSubscription(const Subscription& subscription); + + void informAlarmsFired(const int64_t& timestampNs, + unordered_set, SpHash>& firedAlarms); + +protected: + // For test only. Returns the alarm timestamp in seconds. Otherwise returns 0. + inline int32_t getAlarmTimestampSec() const { + return mInternalAlarm == nullptr ? 0 : mInternalAlarm->timestampSec; + } + + int64_t findNextAlarmSec(int64_t currentTimeMillis); + + // statsd_config.proto Alarm message that defines this tracker. + const Alarm mAlarmConfig; + + // A reference to the Alarm's config key. + const ConfigKey mConfigKey; + + // The subscriptions that depend on this alarm. + std::vector mSubscriptions; + + // Alarm monitor. + sp mAlarmMonitor; + + // The current expected alarm time in seconds. + int64_t mAlarmSec; + + // The current alarm. + sp mInternalAlarm; + + FRIEND_TEST(AlarmTrackerTest, TestTriggerTimestamp); + FRIEND_TEST(AlarmE2eTest, TestMultipleAlarms); + FRIEND_TEST(ConfigUpdateTest, TestUpdateAlarms); +}; + +} // namespace statsd +} // namespace os +} // namespace android diff --git a/statsd/src/anomaly/AnomalyTracker.cpp b/statsd/src/anomaly/AnomalyTracker.cpp new file mode 100644 index 00000000..6aa410b1 --- /dev/null +++ b/statsd/src/anomaly/AnomalyTracker.cpp @@ -0,0 +1,326 @@ +/* + * 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. + */ + +#define DEBUG false // STOPSHIP if true +#include "Log.h" + +#include "AnomalyTracker.h" +#include "external/Perfetto.h" +#include "guardrail/StatsdStats.h" +#include "metadata_util.h" +#include "stats_log_util.h" +#include "subscriber_util.h" +#include "subscriber/IncidentdReporter.h" +#include "subscriber/SubscriberReporter.h" + +#include +#include +#include + +namespace android { +namespace os { +namespace statsd { + +AnomalyTracker::AnomalyTracker(const Alert& alert, const ConfigKey& configKey) + : mAlert(alert), mConfigKey(configKey), mNumOfPastBuckets(mAlert.num_buckets() - 1) { + VLOG("AnomalyTracker() called"); + resetStorage(); // initialization +} + +AnomalyTracker::~AnomalyTracker() { + VLOG("~AnomalyTracker() called"); +} + +void AnomalyTracker::onConfigUpdated() { + mSubscriptions.clear(); +} + +void AnomalyTracker::resetStorage() { + VLOG("resetStorage() called."); + mPastBuckets.clear(); + // Excludes the current bucket. + mPastBuckets.resize(mNumOfPastBuckets); + mSumOverPastBuckets.clear(); +} + +size_t AnomalyTracker::index(int64_t bucketNum) const { + if (bucketNum < 0) { + ALOGE("index() was passed a negative bucket number (%lld)!", (long long)bucketNum); + } + return bucketNum % mNumOfPastBuckets; +} + +void AnomalyTracker::advanceMostRecentBucketTo(const int64_t& bucketNum) { + VLOG("advanceMostRecentBucketTo() called."); + if (mNumOfPastBuckets <= 0) { + return; + } + if (bucketNum <= mMostRecentBucketNum) { + ALOGW("Cannot advance buckets backwards (bucketNum=%lld but mMostRecentBucketNum=%lld)", + (long long)bucketNum, (long long)mMostRecentBucketNum); + return; + } + // If in the future (i.e. buckets are ancient), just empty out all past info. + if (bucketNum >= mMostRecentBucketNum + mNumOfPastBuckets) { + resetStorage(); + mMostRecentBucketNum = bucketNum; + return; + } + + // Clear out space by emptying out old mPastBuckets[i] values and update mSumOverPastBuckets. + for (int64_t i = mMostRecentBucketNum + 1; i <= bucketNum; i++) { + const int idx = index(i); + subtractBucketFromSum(mPastBuckets[idx]); + mPastBuckets[idx] = nullptr; // release (but not clear) the old bucket. + } + mMostRecentBucketNum = bucketNum; +} + +void AnomalyTracker::addPastBucket(const MetricDimensionKey& key, + const int64_t& bucketValue, + const int64_t& bucketNum) { + VLOG("addPastBucket(bucketValue) called."); + if (mNumOfPastBuckets == 0 || + bucketNum < 0 || bucketNum <= mMostRecentBucketNum - mNumOfPastBuckets) { + return; + } + + const int bucketIndex = index(bucketNum); + if (bucketNum <= mMostRecentBucketNum && (mPastBuckets[bucketIndex] != nullptr)) { + // We need to insert into an already existing past bucket. + std::shared_ptr& bucket = mPastBuckets[bucketIndex]; + auto itr = bucket->find(key); + if (itr != bucket->end()) { + // Old entry already exists; update it. + subtractValueFromSum(key, itr->second); + itr->second = bucketValue; + } else { + bucket->insert({key, bucketValue}); + } + mSumOverPastBuckets[key] += bucketValue; + } else { + // Bucket does not exist yet (in future or was never made), so we must make it. + std::shared_ptr bucket = std::make_shared(); + bucket->insert({key, bucketValue}); + addPastBucket(bucket, bucketNum); + } +} + +void AnomalyTracker::addPastBucket(std::shared_ptr bucket, + const int64_t& bucketNum) { + VLOG("addPastBucket(bucket) called."); + if (mNumOfPastBuckets == 0 || + bucketNum < 0 || bucketNum <= mMostRecentBucketNum - mNumOfPastBuckets) { + return; + } + + if (bucketNum <= mMostRecentBucketNum) { + // We are updating an old bucket, not adding a new one. + subtractBucketFromSum(mPastBuckets[index(bucketNum)]); + } else { + // Clear space for the new bucket to be at bucketNum. + advanceMostRecentBucketTo(bucketNum); + } + mPastBuckets[index(bucketNum)] = bucket; + addBucketToSum(bucket); +} + +void AnomalyTracker::subtractBucketFromSum(const shared_ptr& bucket) { + if (bucket == nullptr) { + return; + } + for (const auto& keyValuePair : *bucket) { + subtractValueFromSum(keyValuePair.first, keyValuePair.second); + } +} + + +void AnomalyTracker::subtractValueFromSum(const MetricDimensionKey& key, + const int64_t& bucketValue) { + auto itr = mSumOverPastBuckets.find(key); + if (itr == mSumOverPastBuckets.end()) { + return; + } + itr->second -= bucketValue; + if (itr->second == 0) { + mSumOverPastBuckets.erase(itr); + } +} + +void AnomalyTracker::addBucketToSum(const shared_ptr& bucket) { + if (bucket == nullptr) { + return; + } + // For each dimension present in the bucket, add its value to its corresponding sum. + for (const auto& keyValuePair : *bucket) { + mSumOverPastBuckets[keyValuePair.first] += keyValuePair.second; + } +} + +int64_t AnomalyTracker::getPastBucketValue(const MetricDimensionKey& key, + const int64_t& bucketNum) const { + if (bucketNum < 0 || mMostRecentBucketNum < 0 + || bucketNum <= mMostRecentBucketNum - mNumOfPastBuckets + || bucketNum > mMostRecentBucketNum) { + return 0; + } + + const auto& bucket = mPastBuckets[index(bucketNum)]; + if (bucket == nullptr) { + return 0; + } + const auto& itr = bucket->find(key); + return itr == bucket->end() ? 0 : itr->second; +} + +int64_t AnomalyTracker::getSumOverPastBuckets(const MetricDimensionKey& key) const { + const auto& itr = mSumOverPastBuckets.find(key); + if (itr != mSumOverPastBuckets.end()) { + return itr->second; + } + return 0; +} + +bool AnomalyTracker::detectAnomaly(const int64_t& currentBucketNum, + const MetricDimensionKey& key, + const int64_t& currentBucketValue) { + + // currentBucketNum should be the next bucket after pastBuckets. If not, advance so that it is. + if (currentBucketNum > mMostRecentBucketNum + 1) { + advanceMostRecentBucketTo(currentBucketNum - 1); + } + return mAlert.has_trigger_if_sum_gt() && + getSumOverPastBuckets(key) + currentBucketValue > mAlert.trigger_if_sum_gt(); +} + +void AnomalyTracker::declareAnomaly(const int64_t& timestampNs, int64_t metricId, + const MetricDimensionKey& key, int64_t metricValue) { + // TODO(b/110563466): 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"); + return; + } + if (mAlert.has_refractory_period_secs()) { + mRefractoryPeriodEndsSec[key] = ((timestampNs + NS_PER_SEC - 1) / NS_PER_SEC) // round up + + mAlert.refractory_period_secs(); + // TODO(b/110563466): If we had access to the bucket_size_millis, consider + // calling resetStorage() + // if (mAlert.refractory_period_secs() > mNumOfPastBuckets * bucketSizeNs) {resetStorage();} + } + + if (!mSubscriptions.empty()) { + ALOGI("An anomaly (%" PRId64 ") %s has occurred! Informing subscribers.", + mAlert.id(), key.toString().c_str()); + informSubscribers(key, metricId, metricValue); + } else { + ALOGI("An anomaly has occurred! (But no subscriber for that alert.)"); + } + + StatsdStats::getInstance().noteAnomalyDeclared(mConfigKey, mAlert.id()); + + // TODO(b/110564268): This should also take in the const MetricDimensionKey& key? + util::stats_write(util::ANOMALY_DETECTED, mConfigKey.GetUid(), + mConfigKey.GetId(), mAlert.id()); +} + +void AnomalyTracker::detectAndDeclareAnomaly(const int64_t& timestampNs, + const int64_t& currBucketNum, int64_t metricId, + const MetricDimensionKey& key, + const int64_t& currentBucketValue) { + if (detectAnomaly(currBucketNum, key, currentBucketValue)) { + declareAnomaly(timestampNs, metricId, key, currentBucketValue); + } +} + +bool AnomalyTracker::isInRefractoryPeriod(const int64_t& timestampNs, + const MetricDimensionKey& key) const { + const auto& it = mRefractoryPeriodEndsSec.find(key); + if (it != mRefractoryPeriodEndsSec.end()) { + return timestampNs < (it->second * (int64_t)NS_PER_SEC); + } + return false; +} + +std::pair AnomalyTracker::getProtoHash() const { + string serializedAlert; + if (!mAlert.SerializeToString(&serializedAlert)) { + ALOGW("Unable to serialize alert %lld", (long long)mAlert.id()); + return {false, 0}; + } + return {true, Hash64(serializedAlert)}; +} + +void AnomalyTracker::informSubscribers(const MetricDimensionKey& key, int64_t metric_id, + int64_t metricValue) { + triggerSubscribers(mAlert.id(), metric_id, key, metricValue, mConfigKey, mSubscriptions); +} + +bool AnomalyTracker::writeAlertMetadataToProto(int64_t currentWallClockTimeNs, + int64_t systemElapsedTimeNs, + metadata::AlertMetadata* alertMetadata) { + bool metadataWritten = false; + + if (mRefractoryPeriodEndsSec.empty()) { + return false; + } + + for (const auto& it: mRefractoryPeriodEndsSec) { + // Do not write the timestamp to disk if it has already expired + if (it.second < systemElapsedTimeNs / NS_PER_SEC) { + continue; + } + + metadataWritten = true; + if (alertMetadata->alert_dim_keyed_data_size() == 0) { + alertMetadata->set_alert_id(mAlert.id()); + } + + metadata::AlertDimensionKeyedData* keyedData = alertMetadata->add_alert_dim_keyed_data(); + // We convert and write the refractory_end_sec to wall clock time because we do not know + // when statsd will start again. + int32_t refractoryEndWallClockSec = (int32_t) ((currentWallClockTimeNs / NS_PER_SEC) + + (it.second - systemElapsedTimeNs / NS_PER_SEC)); + + keyedData->set_last_refractory_ends_sec(refractoryEndWallClockSec); + writeMetricDimensionKeyToMetadataDimensionKey( + it.first, keyedData->mutable_dimension_key()); + } + + return metadataWritten; +} + +void AnomalyTracker::loadAlertMetadata( + const metadata::AlertMetadata& alertMetadata, + int64_t currentWallClockTimeNs, + int64_t systemElapsedTimeNs) { + for (const metadata::AlertDimensionKeyedData& keyedData : + alertMetadata.alert_dim_keyed_data()) { + if ((uint64_t) keyedData.last_refractory_ends_sec() < currentWallClockTimeNs / NS_PER_SEC) { + // Do not update the timestamp if it has already expired. + continue; + } + MetricDimensionKey metricKey = loadMetricDimensionKeyFromProto( + keyedData.dimension_key()); + int32_t refractoryPeriodEndsSec = (int32_t) keyedData.last_refractory_ends_sec() - + currentWallClockTimeNs / NS_PER_SEC + systemElapsedTimeNs / NS_PER_SEC; + mRefractoryPeriodEndsSec[metricKey] = refractoryPeriodEndsSec; + } +} + +} // namespace statsd +} // namespace os +} // namespace android diff --git a/statsd/src/anomaly/AnomalyTracker.h b/statsd/src/anomaly/AnomalyTracker.h new file mode 100644 index 00000000..feb6bd9f --- /dev/null +++ b/statsd/src/anomaly/AnomalyTracker.h @@ -0,0 +1,230 @@ +/* + * 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. + */ + +#pragma once + +#include +#include +#include + +#include "AlarmMonitor.h" +#include "config/ConfigKey.h" +#include "src/statsd_config.pb.h" // Alert +#include "src/statsd_metadata.pb.h" // AlertMetadata +#include "hash.h" +#include "stats_util.h" // HashableDimensionKey and DimToValMap + +namespace android { +namespace os { +namespace statsd { + +using std::shared_ptr; +using std::unordered_map; + +// Does NOT allow negative values. +class AnomalyTracker : public virtual RefBase { +public: + AnomalyTracker(const Alert& alert, const ConfigKey& configKey); + + virtual ~AnomalyTracker(); + + // Reset appropriate state on a config update. Clear subscriptions so they can be reset. + void onConfigUpdated(); + + // Add subscriptions that depend on this alert. + void addSubscription(const Subscription& subscription) { + mSubscriptions.push_back(subscription); + } + + // Adds a bucket for the given bucketNum (index starting at 0). + // If a bucket for bucketNum already exists, it will be replaced. + // Also, advances to bucketNum (if not in the past), effectively filling any intervening + // buckets with 0s. + void addPastBucket(std::shared_ptr bucket, const int64_t& bucketNum); + + // Inserts (or replaces) the bucket entry for the given bucketNum at the given key to be the + // given bucketValue. If the bucket does not exist, it will be created. + // Also, advances to bucketNum (if not in the past), effectively filling any intervening + // buckets with 0s. + void addPastBucket(const MetricDimensionKey& key, const int64_t& bucketValue, + const int64_t& bucketNum); + + // Returns true if, based on past buckets plus the new currentBucketValue (which generally + // represents the partially-filled current bucket), an anomaly has happened. + // Also advances to currBucketNum-1. + bool detectAnomaly(const int64_t& currBucketNum, const MetricDimensionKey& key, + const int64_t& currentBucketValue); + + // Informs incidentd about the detected alert. + void declareAnomaly(const int64_t& timestampNs, int64_t metricId, const MetricDimensionKey& key, + int64_t metricValue); + + // Detects if, based on past buckets plus the new currentBucketValue (which generally + // represents the partially-filled current bucket), an anomaly has happened, and if so, + // declares an anomaly and informs relevant subscribers. + // Also advances to currBucketNum-1. + void detectAndDeclareAnomaly(const int64_t& timestampNs, const int64_t& currBucketNum, + int64_t metricId, const MetricDimensionKey& key, + const int64_t& currentBucketValue); + + // Init the AlarmMonitor which is shared across anomaly trackers. + virtual void setAlarmMonitor(const sp& alarmMonitor) { + return; // Base AnomalyTracker class has no need for the AlarmMonitor. + } + + // Returns the sum of all past bucket values for the given dimension key. + int64_t getSumOverPastBuckets(const MetricDimensionKey& key) const; + + // Returns the value for a past bucket, or 0 if that bucket doesn't exist. + int64_t getPastBucketValue(const MetricDimensionKey& key, const int64_t& bucketNum) const; + + // Returns the anomaly threshold set in the configuration. + inline int64_t getAnomalyThreshold() const { + return mAlert.trigger_if_sum_gt(); + } + + // Returns the refractory period ending timestamp (in seconds) for the given key. + // Before this moment, any detected anomaly will be ignored. + // If there is no stored refractory period ending timestamp, returns 0. + uint32_t getRefractoryPeriodEndsSec(const MetricDimensionKey& key) const { + const auto& it = mRefractoryPeriodEndsSec.find(key); + return it != mRefractoryPeriodEndsSec.end() ? it->second : 0; + } + + // Returns the (constant) number of past buckets this anomaly tracker can store. + inline int getNumOfPastBuckets() const { + return mNumOfPastBuckets; + } + + std::pair getProtoHash() const; + + // Sets an alarm for the given timestamp. + // Replaces previous alarm if one already exists. + virtual void startAlarm(const MetricDimensionKey& dimensionKey, const int64_t& eventTime) { + return; // The base AnomalyTracker class doesn't have alarms. + } + + // Stops the alarm. + // If it should have already fired, but hasn't yet (e.g. because the AlarmManager is delayed), + // declare the anomaly now. + virtual void stopAlarm(const MetricDimensionKey& dimensionKey, const int64_t& timestampNs) { + return; // The base AnomalyTracker class doesn't have alarms. + } + + // Stop all the alarms owned by this tracker. Does not declare any anomalies. + virtual void cancelAllAlarms() { + return; // The base AnomalyTracker class doesn't have alarms. + } + + // Declares an anomaly for each alarm in firedAlarms that belongs to this AnomalyTracker, + // and removes it from firedAlarms. Does NOT remove the alarm from the AlarmMonitor. + virtual void informAlarmsFired(const int64_t& timestampNs, + unordered_set, SpHash>& firedAlarms) { + return; // The base AnomalyTracker class doesn't have alarms. + } + + // Writes metadata of the alert (refractory_period_end_sec) to AlertMetadata. + // Returns true if at least one element is written to alertMetadata. + bool writeAlertMetadataToProto( + int64_t currentWallClockTimeNs, + int64_t systemElapsedTimeNs, metadata::AlertMetadata* alertMetadata); + + void loadAlertMetadata( + const metadata::AlertMetadata& alertMetadata, + int64_t currentWallClockTimeNs, + int64_t systemElapsedTimeNs); + +protected: + // For testing only. + // Returns the alarm timestamp in seconds for the query dimension if it exists. Otherwise + // returns 0. + virtual uint32_t getAlarmTimestampSec(const MetricDimensionKey& dimensionKey) const { + return 0; // The base AnomalyTracker class doesn't have alarms. + } + + // statsd_config.proto Alert message that defines this tracker. + const Alert mAlert; + + // The subscriptions that depend on this alert. + std::vector mSubscriptions; + + // A reference to the Alert's config key. + const ConfigKey mConfigKey; + + // Number of past buckets. One less than the total number of buckets needed + // for the anomaly detection (since the current bucket is not in the past). + const int mNumOfPastBuckets; + + // Values for each of the past mNumOfPastBuckets buckets. Always of size mNumOfPastBuckets. + // mPastBuckets[i] can be null, meaning that no data is present in that bucket. + std::vector> mPastBuckets; + + // Cached sum over all existing buckets in mPastBuckets. + // Its buckets never contain entries of 0. + DimToValMap mSumOverPastBuckets; + + // The bucket number of the last added bucket. + int64_t mMostRecentBucketNum = -1; + + // Map from each dimension to the timestamp that its refractory period (if this anomaly was + // declared for that dimension) ends, in seconds. From this moment and onwards, anomalies + // can be declared again. + // Entries may be, but are not guaranteed to be, removed after the period is finished. + unordered_map mRefractoryPeriodEndsSec; + + // Advances mMostRecentBucketNum to bucketNum, deleting any data that is now too old. + // Specifically, since it is now too old, removes the data for + // [mMostRecentBucketNum - mNumOfPastBuckets + 1, bucketNum - mNumOfPastBuckets]. + void advanceMostRecentBucketTo(const int64_t& bucketNum); + + // Add the information in the given bucket to mSumOverPastBuckets. + void addBucketToSum(const shared_ptr& bucket); + + // Subtract the information in the given bucket from mSumOverPastBuckets + // and remove any items with value 0. + void subtractBucketFromSum(const shared_ptr& bucket); + + // From mSumOverPastBuckets[key], subtracts bucketValue, removing it if it is now 0. + void subtractValueFromSum(const MetricDimensionKey& key, const int64_t& bucketValue); + + // Returns true if in the refractory period, else false. + bool isInRefractoryPeriod(const int64_t& timestampNs, const MetricDimensionKey& key) const; + + // Calculates the corresponding bucket index within the circular array. + // Requires bucketNum >= 0. + size_t index(int64_t bucketNum) const; + + // Resets all bucket data. For use when all the data gets stale. + virtual void resetStorage(); + + // Informs the subscribers (incidentd, perfetto, broadcasts, etc) that an anomaly has occurred. + void informSubscribers(const MetricDimensionKey& key, int64_t metricId, int64_t metricValue); + + FRIEND_TEST(AnomalyTrackerTest, TestConsecutiveBuckets); + FRIEND_TEST(AnomalyTrackerTest, TestSparseBuckets); + FRIEND_TEST(GaugeMetricProducerTest, TestAnomalyDetection); + FRIEND_TEST(CountMetricProducerTest, TestAnomalyDetectionUnSliced); + FRIEND_TEST(AnomalyDetectionE2eTest, TestDurationMetric_SUM_single_bucket); + FRIEND_TEST(AnomalyDetectionE2eTest, TestDurationMetric_SUM_partial_bucket); + FRIEND_TEST(AnomalyDetectionE2eTest, TestDurationMetric_SUM_multiple_buckets); + FRIEND_TEST(AnomalyDetectionE2eTest, TestDurationMetric_SUM_long_refractory_period); + + FRIEND_TEST(ConfigUpdateTest, TestUpdateAlerts); +}; + +} // namespace statsd +} // namespace os +} // namespace android diff --git a/statsd/src/anomaly/DurationAnomalyTracker.cpp b/statsd/src/anomaly/DurationAnomalyTracker.cpp new file mode 100644 index 00000000..2b568101 --- /dev/null +++ b/statsd/src/anomaly/DurationAnomalyTracker.cpp @@ -0,0 +1,115 @@ +/* + * 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. + */ + +#define DEBUG false // STOPSHIP if true +#include "Log.h" + +#include "DurationAnomalyTracker.h" +#include "guardrail/StatsdStats.h" + +namespace android { +namespace os { +namespace statsd { + +DurationAnomalyTracker::DurationAnomalyTracker(const Alert& alert, const ConfigKey& configKey, + const sp& alarmMonitor) + : AnomalyTracker(alert, configKey), mAlarmMonitor(alarmMonitor) { + VLOG("DurationAnomalyTracker() called"); +} + +DurationAnomalyTracker::~DurationAnomalyTracker() { + VLOG("~DurationAnomalyTracker() called"); + cancelAllAlarms(); +} + +void DurationAnomalyTracker::startAlarm(const MetricDimensionKey& dimensionKey, + const int64_t& timestampNs) { + // Alarms are stored in secs. Must round up, since if it fires early, it is ignored completely. + uint32_t timestampSec = static_cast((timestampNs -1) / NS_PER_SEC) + 1; // round up + if (isInRefractoryPeriod(timestampNs, dimensionKey)) { + VLOG("Not setting anomaly alarm since it would fall in the refractory period."); + return; + } + + auto itr = mAlarms.find(dimensionKey); + if (itr != mAlarms.end() && mAlarmMonitor != nullptr) { + mAlarmMonitor->remove(itr->second); + } + + sp alarm = new InternalAlarm{timestampSec}; + mAlarms[dimensionKey] = alarm; + if (mAlarmMonitor != nullptr) { + mAlarmMonitor->add(alarm); + } +} + +void DurationAnomalyTracker::stopAlarm(const MetricDimensionKey& dimensionKey, + const int64_t& timestampNs) { + const auto itr = mAlarms.find(dimensionKey); + if (itr == mAlarms.end()) { + return; + } + + // If the alarm is set in the past but hasn't fired yet (due to lag), catch it now. + if (itr->second != nullptr && timestampNs >= (int64_t)NS_PER_SEC * itr->second->timestampSec) { + declareAnomaly(timestampNs, mAlert.metric_id(), dimensionKey, + mAlert.trigger_if_sum_gt() + (timestampNs / NS_PER_SEC) - + itr->second->timestampSec); + } + if (mAlarmMonitor != nullptr) { + mAlarmMonitor->remove(itr->second); + } + mAlarms.erase(dimensionKey); +} + +void DurationAnomalyTracker::cancelAllAlarms() { + if (mAlarmMonitor != nullptr) { + for (const auto& itr : mAlarms) { + mAlarmMonitor->remove(itr.second); + } + } + mAlarms.clear(); +} + +void DurationAnomalyTracker::informAlarmsFired(const int64_t& timestampNs, + unordered_set, SpHash>& firedAlarms) { + + if (firedAlarms.empty() || mAlarms.empty()) return; + // Find the intersection of firedAlarms and mAlarms. + // The for loop is inefficient, since it loops over all keys, but that's okay since it is very + // seldomly called. The alternative would be having InternalAlarms 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; + for (const auto& kv : mAlarms) { + if (firedAlarms.count(kv.second) > 0) { + matchedAlarms.insert({kv.first, kv.second}); + } + } + + // Now declare each of these alarms to have fired. + for (const auto& kv : matchedAlarms) { + declareAnomaly( + timestampNs, mAlert.metric_id(), kv.first, + mAlert.trigger_if_sum_gt() + (timestampNs / NS_PER_SEC) - kv.second->timestampSec); + mAlarms.erase(kv.first); + firedAlarms.erase(kv.second); // No one else can also own it, so we're done with it. + } +} + +} // namespace statsd +} // namespace os +} // namespace android diff --git a/statsd/src/anomaly/DurationAnomalyTracker.h b/statsd/src/anomaly/DurationAnomalyTracker.h new file mode 100644 index 00000000..46419149 --- /dev/null +++ b/statsd/src/anomaly/DurationAnomalyTracker.h @@ -0,0 +1,79 @@ +/* + * 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. + */ + +#pragma once + +#include "AlarmMonitor.h" +#include "AnomalyTracker.h" + +namespace android { +namespace os { +namespace statsd { + +using std::unordered_map; + +class DurationAnomalyTracker : public virtual AnomalyTracker { +public: + DurationAnomalyTracker(const Alert& alert, const ConfigKey& configKey, + const sp& alarmMonitor); + + virtual ~DurationAnomalyTracker(); + + // Sets an alarm for the given timestamp. + // Replaces previous alarm if one already exists. + void startAlarm(const MetricDimensionKey& dimensionKey, const int64_t& eventTime) override; + + // Stops the alarm. + // If it should have already fired, but hasn't yet (e.g. because the AlarmManager is delayed), + // declare the anomaly now. + void stopAlarm(const MetricDimensionKey& dimensionKey, const int64_t& timestampNs) override; + + // Stop all the alarms owned by this tracker. Does not declare any anomalies. + void cancelAllAlarms() override; + + // Declares an anomaly for each alarm in firedAlarms that belongs to this DurationAnomalyTracker + // and removes it from firedAlarms. The AlarmMonitor is not informed. + // Note that this will generally be called from a different thread from the other functions; + // the caller is responsible for thread safety. + void informAlarmsFired(const int64_t& timestampNs, + unordered_set, SpHash>& firedAlarms) override; + +protected: + // Returns the alarm timestamp in seconds for the query dimension if it exists. Otherwise + // returns 0. + uint32_t getAlarmTimestampSec(const MetricDimensionKey& dimensionKey) const override { + auto it = mAlarms.find(dimensionKey); + return it == mAlarms.end() ? 0 : it->second->timestampSec; + } + + // The alarms owned by this tracker. The alarm monitor also shares the alarm pointers when they + // are still active. + std::unordered_map> mAlarms; + + // Anomaly alarm monitor. + sp mAlarmMonitor; + + FRIEND_TEST(OringDurationTrackerTest, TestPredictAnomalyTimestamp); + FRIEND_TEST(OringDurationTrackerTest, TestAnomalyDetectionExpiredAlarm); + FRIEND_TEST(OringDurationTrackerTest, TestAnomalyDetectionFiredAlarm); + FRIEND_TEST(MaxDurationTrackerTest, TestAnomalyDetection); + FRIEND_TEST(MaxDurationTrackerTest, TestAnomalyPredictedTimestamp); + FRIEND_TEST(MaxDurationTrackerTest, TestAnomalyPredictedTimestamp_UpdatedOnStop); +}; + +} // namespace statsd +} // namespace os +} // namespace android diff --git a/statsd/src/anomaly/indexed_priority_queue.h b/statsd/src/anomaly/indexed_priority_queue.h new file mode 100644 index 00000000..99882d03 --- /dev/null +++ b/statsd/src/anomaly/indexed_priority_queue.h @@ -0,0 +1,224 @@ +/* + * 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. + */ + +#pragma once + +#include +#include +#include + +using namespace android; + +namespace android { +namespace os { +namespace statsd { + +/** Defines a hash function for sp, returning the hash of the underlying pointer. */ +template +struct SpHash { + size_t operator()(const sp& k) const { + return std::hash()(k.get()); + } +}; + +/** + * Min priority queue for generic type AA. + * Unlike a regular priority queue, this class is also capable of removing interior elements. + * @tparam Comparator must implement [bool operator()(sp a, sp b)], returning + * whether a should be closer to the top of the queue than b. + */ +template +class indexed_priority_queue { +public: + indexed_priority_queue(); + /** Adds a into the priority queue. If already present or a==nullptr, does nothing. */ + void push(sp a); + /* + * Removes a from the priority queue. If not present or a==nullptr, does nothing. + * Returns true if a had been present (and is now removed), else false. + */ + bool remove(sp a); + /** Removes the top element, if there is one. */ + void pop(); + /** Removes all elements. */ + void clear(); + /** Returns whether priority queue contains a (not just a copy of a, but a itself). */ + bool contains(sp a) const; + /** Returns min element. Returns nullptr iff empty(). */ + sp top() const; + /** Returns number of elements in priority queue. */ + size_t size() const { + return pq.size() - 1; + } // pq is 1-indexed + /** Returns true iff priority queue is empty. */ + bool empty() const { + return size() < 1; + } + +private: + /** Vector representing a min-heap (1-indexed, with nullptr at 0). */ + std::vector> pq; + /** Mapping of each element in pq to its index in pq (i.e. the inverse of a=pq[i]). */ + std::unordered_map, size_t, SpHash> indices; + + void init(); + void sift_up(size_t idx); + void sift_down(size_t idx); + /** Returns whether pq[idx1] is considered higher than pq[idx2], according to Comparator. */ + bool higher(size_t idx1, size_t idx2) const; + void swap_indices(size_t i, size_t j); +}; + +// Implementation must be done in this file due to use of template. + +template +indexed_priority_queue::indexed_priority_queue() { + init(); +} + +template +void indexed_priority_queue::push(sp a) { + if (a == nullptr) return; + if (contains(a)) return; + pq.push_back(a); + size_t idx = size(); // index of last element since 1-indexed + indices.insert({a, idx}); + sift_up(idx); // get the pq back in order +} + +template +bool indexed_priority_queue::remove(sp a) { + if (a == nullptr) return false; + if (!contains(a)) return false; + size_t idx = indices[a]; + if (idx >= pq.size()) { + return false; + } + if (idx == size()) { // if a is the last element, i.e. at index idx == size() == (pq.size()-1) + pq.pop_back(); + indices.erase(a); + return true; + } + // move last element (guaranteed not to be at idx) to idx, then delete a + sp last_a = pq.back(); + pq[idx] = last_a; + pq.pop_back(); + indices[last_a] = idx; + indices.erase(a); + + // get the heap back in order (since the element at idx is not in order) + sift_up(idx); + sift_down(idx); + + return true; +} + +// The same as, but slightly more efficient than, remove(top()). +template +void indexed_priority_queue::pop() { + sp a = top(); + if (a == nullptr) return; + const size_t idx = 1; + if (idx == size()) { // if a is the last element + pq.pop_back(); + indices.erase(a); + return; + } + // move last element (guaranteed not to be at idx) to idx, then delete a + sp last_a = pq.back(); + pq[idx] = last_a; + pq.pop_back(); + indices[last_a] = idx; + indices.erase(a); + + // get the heap back in order (since the element at idx is not in order) + sift_down(idx); +} + +template +void indexed_priority_queue::clear() { + pq.clear(); + indices.clear(); + init(); +} + +template +sp indexed_priority_queue::top() const { + if (empty()) return nullptr; + return pq[1]; +} + +template +void indexed_priority_queue::init() { + pq.push_back(nullptr); // so that pq is 1-indexed. + indices.insert({nullptr, 0}); // just to be consistent with pq. +} + +template +void indexed_priority_queue::sift_up(size_t idx) { + while (idx > 1) { + size_t parent = idx / 2; + if (higher(idx, parent)) + swap_indices(idx, parent); + else + break; + idx = parent; + } +} + +template +void indexed_priority_queue::sift_down(size_t idx) { + while (2 * idx <= size()) { + size_t child = 2 * idx; + if (child < size() && higher(child + 1, child)) child++; + if (higher(child, idx)) + swap_indices(child, idx); + else + break; + idx = child; + } +} + +template +bool indexed_priority_queue::higher(size_t idx1, size_t idx2) const { + if (!(0u < idx1 && idx1 < pq.size() && 0u < idx2 && idx2 < pq.size())) { + return false; // got to do something. + } + return Comparator()(pq[idx1], pq[idx2]); +} + +template +bool indexed_priority_queue::contains(sp a) const { + if (a == nullptr) return false; // publicly, we pretend that nullptr is not actually in pq. + return indices.count(a) > 0; +} + +template +void indexed_priority_queue::swap_indices(size_t i, size_t j) { + if (!(0u < i && i < pq.size() && 0u < j && j < pq.size())) { + return; + } + sp val_i = pq[i]; + sp val_j = pq[j]; + pq[i] = val_j; + pq[j] = val_i; + indices[val_i] = j; + indices[val_j] = i; +} + +} // namespace statsd +} // namespace os +} // namespace android diff --git a/statsd/src/anomaly/subscriber_util.cpp b/statsd/src/anomaly/subscriber_util.cpp new file mode 100644 index 00000000..5a4a41d0 --- /dev/null +++ b/statsd/src/anomaly/subscriber_util.cpp @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2018 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. + */ + +#define DEBUG false // STOPSHIP if true +#include "Log.h" + +#include "external/Perfetto.h" +#include "subscriber/IncidentdReporter.h" +#include "subscriber/SubscriberReporter.h" + +namespace android { +namespace os { +namespace statsd { + +void triggerSubscribers(int64_t ruleId, int64_t metricId, const MetricDimensionKey& dimensionKey, + int64_t metricValue, const ConfigKey& configKey, + const std::vector& subscriptions) { + VLOG("informSubscribers called."); + if (subscriptions.empty()) { + VLOG("No Subscriptions were associated."); + return; + } + + for (const Subscription& subscription : subscriptions) { + if (subscription.probability_of_informing() < 1 + && ((float)rand() / (float)RAND_MAX) >= subscription.probability_of_informing()) { + // Note that due to float imprecision, 0.0 and 1.0 might not truly mean never/always. + // The config writer was advised to use -0.1 and 1.1 for never/always. + ALOGI("Fate decided that a subscriber would not be informed."); + continue; + } + switch (subscription.subscriber_information_case()) { + case Subscription::SubscriberInformationCase::kIncidentdDetails: + if (!GenerateIncidentReport(subscription.incidentd_details(), ruleId, metricId, + dimensionKey, metricValue, configKey)) { + ALOGW("Failed to generate incident report."); + } + break; + case Subscription::SubscriberInformationCase::kPerfettoDetails: + if (!CollectPerfettoTraceAndUploadToDropbox(subscription.perfetto_details(), + subscription.id(), ruleId, configKey)) { + ALOGW("Failed to generate perfetto traces."); + } + break; + case Subscription::SubscriberInformationCase::kBroadcastSubscriberDetails: + SubscriberReporter::getInstance().alertBroadcastSubscriber(configKey, subscription, + dimensionKey); + break; + default: + break; + } + } +} + +} // namespace statsd +} // namespace os +} // namespace android diff --git a/statsd/src/anomaly/subscriber_util.h b/statsd/src/anomaly/subscriber_util.h new file mode 100644 index 00000000..4d4c83b8 --- /dev/null +++ b/statsd/src/anomaly/subscriber_util.h @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2018 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. + */ + +#pragma once + +#include "config/ConfigKey.h" +#include "HashableDimensionKey.h" +#include "src/statsd_config.pb.h" + +namespace android { +namespace os { +namespace statsd { + +void triggerSubscribers(const int64_t ruleId, const int64_t metricId, + const MetricDimensionKey& dimensionKey, int64_t metricValue, + const ConfigKey& configKey, const std::vector& subscriptions); + +} // namespace statsd +} // namespace os +} // namespace android diff --git a/statsd/src/condition/CombinationConditionTracker.cpp b/statsd/src/condition/CombinationConditionTracker.cpp new file mode 100644 index 00000000..4574b2e3 --- /dev/null +++ b/statsd/src/condition/CombinationConditionTracker.cpp @@ -0,0 +1,244 @@ +/* + * 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. + */ + +#define DEBUG false // STOPSHIP if true +#include "Log.h" +#include "CombinationConditionTracker.h" + +namespace android { +namespace os { +namespace statsd { + +using std::unordered_map; +using std::vector; + +CombinationConditionTracker::CombinationConditionTracker(const int64_t& id, const int index, + const uint64_t protoHash) + : ConditionTracker(id, index, protoHash) { + VLOG("creating CombinationConditionTracker %lld", (long long)mConditionId); +} + +CombinationConditionTracker::~CombinationConditionTracker() { + VLOG("~CombinationConditionTracker() %lld", (long long)mConditionId); +} + +bool CombinationConditionTracker::init(const vector& allConditionConfig, + const vector>& allConditionTrackers, + const unordered_map& conditionIdIndexMap, + vector& stack, + vector& conditionCache) { + VLOG("Combination predicate init() %lld", (long long)mConditionId); + if (mInitialized) { + // All the children are guaranteed to be initialized, but the recursion is needed to + // fill the conditionCache properly, since another combination condition or metric + // might rely on this. The recursion is needed to compute the current condition. + + // Init is called instead of isConditionMet so that the ConditionKey can be filled with the + // default key for sliced conditions, since we do not know all indirect descendants here. + for (const int childIndex : mChildren) { + if (conditionCache[childIndex] == ConditionState::kNotEvaluated) { + allConditionTrackers[childIndex]->init(allConditionConfig, allConditionTrackers, + conditionIdIndexMap, stack, conditionCache); + } + } + conditionCache[mIndex] = + evaluateCombinationCondition(mChildren, mLogicalOperation, conditionCache); + return true; + } + + // mark this node as visited in the recursion stack. + stack[mIndex] = true; + + Predicate_Combination combinationCondition = allConditionConfig[mIndex].combination(); + + if (!combinationCondition.has_operation()) { + return false; + } + mLogicalOperation = combinationCondition.operation(); + + if (mLogicalOperation == LogicalOperation::NOT && combinationCondition.predicate_size() != 1) { + return false; + } + + for (auto child : combinationCondition.predicate()) { + auto it = conditionIdIndexMap.find(child); + + if (it == conditionIdIndexMap.end()) { + ALOGW("Predicate %lld not found in the config", (long long)child); + return false; + } + + int childIndex = it->second; + const auto& childTracker = allConditionTrackers[childIndex]; + // if the child is a visited node in the recursion -> circle detected. + if (stack[childIndex]) { + ALOGW("Circle detected!!!"); + return false; + } + + bool initChildSucceeded = childTracker->init(allConditionConfig, allConditionTrackers, + conditionIdIndexMap, stack, conditionCache); + + if (!initChildSucceeded) { + ALOGW("Child initialization failed %lld ", (long long)child); + return false; + } else { + VLOG("Child initialization success %lld ", (long long)child); + } + + if (allConditionTrackers[childIndex]->isSliced()) { + setSliced(true); + mSlicedChildren.push_back(childIndex); + } else { + mUnSlicedChildren.push_back(childIndex); + } + mChildren.push_back(childIndex); + mTrackerIndex.insert(childTracker->getAtomMatchingTrackerIndex().begin(), + childTracker->getAtomMatchingTrackerIndex().end()); + } + + mUnSlicedPartCondition = + evaluateCombinationCondition(mUnSlicedChildren, mLogicalOperation, conditionCache); + conditionCache[mIndex] = + evaluateCombinationCondition(mChildren, mLogicalOperation, conditionCache); + + // unmark this node in the recursion stack. + stack[mIndex] = false; + + mInitialized = true; + + return true; +} + +bool CombinationConditionTracker::onConfigUpdated( + const vector& allConditionProtos, const int index, + const vector>& allConditionTrackers, + const unordered_map& atomMatchingTrackerMap, + const unordered_map& conditionTrackerMap) { + ConditionTracker::onConfigUpdated(allConditionProtos, index, allConditionTrackers, + atomMatchingTrackerMap, conditionTrackerMap); + mTrackerIndex.clear(); + mChildren.clear(); + mUnSlicedChildren.clear(); + mSlicedChildren.clear(); + Predicate_Combination combinationCondition = allConditionProtos[mIndex].combination(); + + for (const int64_t child : combinationCondition.predicate()) { + const auto& it = conditionTrackerMap.find(child); + + if (it == conditionTrackerMap.end()) { + ALOGW("Predicate %lld not found in the config", (long long)child); + return false; + } + + int childIndex = it->second; + const sp& childTracker = allConditionTrackers[childIndex]; + + // Ensures that the child's tracker indices are updated. + if (!childTracker->onConfigUpdated(allConditionProtos, childIndex, allConditionTrackers, + atomMatchingTrackerMap, conditionTrackerMap)) { + ALOGW("Child update failed %lld ", (long long)child); + return false; + } + + if (allConditionTrackers[childIndex]->isSliced()) { + mSlicedChildren.push_back(childIndex); + } else { + mUnSlicedChildren.push_back(childIndex); + } + mChildren.push_back(childIndex); + mTrackerIndex.insert(childTracker->getAtomMatchingTrackerIndex().begin(), + childTracker->getAtomMatchingTrackerIndex().end()); + } + return true; +} + +void CombinationConditionTracker::isConditionMet( + const ConditionKey& conditionParameters, const vector>& allConditions, + const bool isPartialLink, + vector& conditionCache) 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, + isPartialLink, + conditionCache); + } + } + conditionCache[mIndex] = + evaluateCombinationCondition(mChildren, mLogicalOperation, conditionCache); +} + +void CombinationConditionTracker::evaluateCondition( + const LogEvent& event, const std::vector& eventMatcherValues, + const std::vector>& mAllConditions, + std::vector& nonSlicedConditionCache, + std::vector& conditionChangedCache) { + // value is up to date. + if (nonSlicedConditionCache[mIndex] != ConditionState::kNotEvaluated) { + return; + } + + 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, + nonSlicedConditionCache, conditionChangedCache); + } + } + + ConditionState newCondition = + evaluateCombinationCondition(mChildren, mLogicalOperation, nonSlicedConditionCache); + if (!mSliced) { + bool nonSlicedChanged = (mUnSlicedPartCondition != newCondition); + mUnSlicedPartCondition = newCondition; + + nonSlicedConditionCache[mIndex] = mUnSlicedPartCondition; + conditionChangedCache[mIndex] = nonSlicedChanged; + } else { + mUnSlicedPartCondition = evaluateCombinationCondition(mUnSlicedChildren, mLogicalOperation, + nonSlicedConditionCache); + + for (const int childIndex : mChildren) { + // If any of the sliced condition in children condition changes, the combination + // condition may be changed too. + if (conditionChangedCache[childIndex]) { + conditionChangedCache[mIndex] = true; + break; + } + } + nonSlicedConditionCache[mIndex] = newCondition; + VLOG("CombinationPredicate %lld sliced may changed? %d", (long long)mConditionId, + conditionChangedCache[mIndex] == true); + } +} + +bool CombinationConditionTracker::equalOutputDimensions( + const std::vector>& allConditions, + const vector& dimensions) const { + if (mSlicedChildren.size() != 1 || + mSlicedChildren.front() >= (int)allConditions.size() || + mLogicalOperation != LogicalOperation::AND) { + return false; + } + const sp& slicedChild = allConditions.at(mSlicedChildren.front()); + return slicedChild->equalOutputDimensions(allConditions, dimensions); +} + +} // namespace statsd +} // namespace os +} // namespace android diff --git a/statsd/src/condition/CombinationConditionTracker.h b/statsd/src/condition/CombinationConditionTracker.h new file mode 100644 index 00000000..3889e568 --- /dev/null +++ b/statsd/src/condition/CombinationConditionTracker.h @@ -0,0 +1,115 @@ +/* + * 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. + */ + +#ifndef COMBINATION_CONDITION_TRACKER_H +#define COMBINATION_CONDITION_TRACKER_H + +#include "ConditionTracker.h" +#include "src/statsd_config.pb.h" + +namespace android { +namespace os { +namespace statsd { + +class CombinationConditionTracker : public ConditionTracker { +public: + CombinationConditionTracker(const int64_t& id, const int index, const uint64_t protoHash); + + ~CombinationConditionTracker(); + + bool init(const std::vector& allConditionConfig, + const std::vector>& allConditionTrackers, + const std::unordered_map& conditionIdIndexMap, std::vector& stack, + std::vector& conditionCache) override; + + bool onConfigUpdated(const std::vector& allConditionProtos, const int index, + const std::vector>& allConditionTrackers, + const std::unordered_map& atomMatchingTrackerMap, + const std::unordered_map& conditionTrackerMap) override; + + void evaluateCondition(const LogEvent& event, + const std::vector& eventMatcherValues, + const std::vector>& mAllConditions, + std::vector& conditionCache, + std::vector& changedCache) override; + + void isConditionMet(const ConditionKey& conditionParameters, + const std::vector>& allConditions, + const bool isPartialLink, + std::vector& conditionCache) const override; + + // Only one child predicate can have dimension. + const std::set* getChangedToTrueDimensions( + const std::vector>& allConditions) const override { + for (const auto& child : mChildren) { + auto result = allConditions[child]->getChangedToTrueDimensions(allConditions); + if (result != nullptr) { + return result; + } + } + return nullptr; + } + + // Only one child predicate can have dimension. + const std::set* getChangedToFalseDimensions( + const std::vector>& allConditions) const override { + for (const auto& child : mChildren) { + auto result = allConditions[child]->getChangedToFalseDimensions(allConditions); + if (result != nullptr) { + return result; + } + } + return nullptr; + } + + bool IsSimpleCondition() const override { return false; } + + bool IsChangedDimensionTrackable() const override { + return mLogicalOperation == LogicalOperation::AND && mSlicedChildren.size() == 1; + } + + bool equalOutputDimensions( + const std::vector>& allConditions, + const vector& dimensions) const override; + + const std::map* getSlicedDimensionMap( + const std::vector>& allConditions) const override { + if (mSlicedChildren.size() == 1) { + return allConditions[mSlicedChildren.front()]->getSlicedDimensionMap(allConditions); + } + return nullptr; + } + +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 + // increases the risk of circular dependency and memory leak. + std::vector mChildren; + + std::vector mSlicedChildren; + std::vector mUnSlicedChildren; + + FRIEND_TEST(ConfigUpdateTest, TestUpdateConditions); +}; + +} // namespace statsd +} // namespace os +} // namespace android + +#endif // COMBINATION_CONDITION_TRACKER_H diff --git a/statsd/src/condition/ConditionTimer.h b/statsd/src/condition/ConditionTimer.h new file mode 100644 index 00000000..1fbe2527 --- /dev/null +++ b/statsd/src/condition/ConditionTimer.h @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2019 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. + */ +#pragma once + +#include +#include + +namespace android { +namespace os { +namespace statsd { + +/** + * A simple stopwatch to time the duration of condition being true. + * + * The owner of the stopwatch (MetricProducer) is responsible to notify the stopwatch when condition + * changes (start/pause), and when to start a new bucket (a new lap basically). All timestamps + * should be elapsedRealTime in nano seconds. + * + * Keep the timer simple and inline everything. This class is *NOT* thread safe. Caller is + * responsible for thread safety. + */ +class ConditionTimer { +public: + explicit ConditionTimer(bool initCondition, int64_t bucketStartNs) : mCondition(initCondition) { + if (initCondition) { + mLastConditionChangeTimestampNs = bucketStartNs; + } + }; + + // Tracks how long the condition has been stayed true in the *current* bucket. + // When a new bucket is created, this value will be reset to 0. + int64_t mTimerNs = 0; + + // Last elapsed real timestamp when condition changed. + int64_t mLastConditionChangeTimestampNs = 0; + + bool mCondition = false; + + int64_t newBucketStart(int64_t nextBucketStartNs) { + if (mCondition) { + // Normally, the next bucket happens after the last condition + // change. In this case, add the time between the condition becoming + // true to the next bucket start time. + // Otherwise, the next bucket start time is before the last + // condition change time, this means that the condition was false at + // the bucket boundary before the condition became true, so the + // timer should not get updated and the last condition change time + // remains as is. + if (nextBucketStartNs >= mLastConditionChangeTimestampNs) { + mTimerNs += (nextBucketStartNs - mLastConditionChangeTimestampNs); + mLastConditionChangeTimestampNs = nextBucketStartNs; + } + } else if (mLastConditionChangeTimestampNs > nextBucketStartNs) { + // The next bucket start time is before the last condition change + // time, this means that the condition was true at the bucket + // boundary before the condition became false, so adjust the timer + // to match how long the condition was true to the bucket boundary. + // This means remove the amount the condition stayed true in the + // next bucket from the current bucket. + mTimerNs -= (mLastConditionChangeTimestampNs - nextBucketStartNs); + } + + int64_t temp = mTimerNs; + mTimerNs = 0; + + if (!mCondition && (mLastConditionChangeTimestampNs > nextBucketStartNs)) { + // The next bucket start time is before the last condition change + // time, this means that the condition was true at the bucket + // boundary and remained true in the next bucket up to the condition + // change to false, so adjust the timer to match how long the + // condition stayed true in the next bucket (now the current bucket). + mTimerNs = mLastConditionChangeTimestampNs - nextBucketStartNs; + } + return temp; + } + + void onConditionChanged(bool newCondition, int64_t timestampNs) { + if (newCondition == mCondition) { + return; + } + mCondition = newCondition; + if (newCondition == false) { + mTimerNs += (timestampNs - mLastConditionChangeTimestampNs); + } + mLastConditionChangeTimestampNs = timestampNs; + } + + FRIEND_TEST(ConditionTimerTest, TestTimer_Inital_False); + FRIEND_TEST(ConditionTimerTest, TestTimer_Inital_True); +}; + +} // namespace statsd +} // namespace os +} // namespace android diff --git a/statsd/src/condition/ConditionTracker.h b/statsd/src/condition/ConditionTracker.h new file mode 100644 index 00000000..4ce8cb13 --- /dev/null +++ b/statsd/src/condition/ConditionTracker.h @@ -0,0 +1,186 @@ +/* + * 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. + */ + +#pragma once + +#include "condition/condition_util.h" +#include "src/statsd_config.pb.h" +#include "matchers/AtomMatchingTracker.h" +#include "matchers/matcher_util.h" + +#include + +#include + +namespace android { +namespace os { +namespace statsd { + +class ConditionTracker : public virtual RefBase { +public: + ConditionTracker(const int64_t& id, const int index, const uint64_t protoHash) + : mConditionId(id), + mIndex(index), + mInitialized(false), + mTrackerIndex(), + mUnSlicedPartCondition(ConditionState::kUnknown), + mSliced(false), + mProtoHash(protoHash){}; + + virtual ~ConditionTracker(){}; + + // Initialize this ConditionTracker. This initialization is done recursively (DFS). It can also + // be done in the constructor, but we do it separately because (1) easy to return a bool to + // indicate whether the initialization is successful. (2) makes unit test easier. + // This function can also be called on config updates, in which case it does nothing other than + // fill the condition cache with the current condition. + // allConditionConfig: the list of all Predicate config from statsd_config. + // allConditionTrackers: the list of all ConditionTrackers (this is needed because we may also + // need to call init() on child conditions) + // conditionIdIndexMap: the mapping from condition id to its index. + // stack: a bit map to keep track which nodes have been visited on the stack in the recursion. + // conditionCache: tracks initial conditions of all ConditionTrackers. returns the + // current condition if called on a config update. + virtual bool init(const std::vector& allConditionConfig, + const std::vector>& allConditionTrackers, + const std::unordered_map& conditionIdIndexMap, + std::vector& stack, std::vector& conditionCache) = 0; + + // Update appropriate state on config updates. Primarily, all indices need to be updated. + // This predicate and all of its children are guaranteed to be preserved across the update. + // This function is recursive and will call onConfigUpdated on child conditions. It does not + // manage cycle detection since all preserved conditions should not have any cycles. + // + // allConditionProtos: the new predicates. + // index: the new index of this tracker in allConditionProtos and allConditionTrackers. + // allConditionTrackers: the list of all ConditionTrackers (this is needed because we may also + // need to call onConfigUpdated() on child conditions) + // atomMatchingTrackerMap: map of atom matcher id to index after the config update. + // conditionTrackerMap: map of condition tracker id to index after the config update. + // returns whether or not the update is successful. + virtual bool onConfigUpdated(const std::vector& allConditionProtos, const int index, + const std::vector>& allConditionTrackers, + const std::unordered_map& atomMatchingTrackerMap, + const std::unordered_map& conditionTrackerMap) { + mIndex = index; + return true; + } + + // evaluate current condition given the new event. + // event: the new log event + // eventMatcherValues: the results of the AtomMatchingTrackers. AtomMatchingTrackers always + // process event before ConditionTrackers, because ConditionTracker depends + // on AtomMatchingTrackers. + // mAllConditions: the list of all ConditionTracker + // conditionCache: the cached non-sliced condition of the ConditionTrackers for this new event. + // conditionChanged: the bit map to record whether the condition has changed. + // If the condition has dimension, then any sub condition changes will report + // conditionChanged. + virtual void evaluateCondition(const LogEvent& event, + const std::vector& eventMatcherValues, + const std::vector>& mAllConditions, + std::vector& conditionCache, + std::vector& conditionChanged) = 0; + + // Query the condition with parameters. + // [conditionParameters]: a map from condition name to the HashableDimensionKey to query the + // condition. + // [allConditions]: all condition trackers. This is needed because the condition evaluation is + // done recursively + // [isPartialLink]: true if the link specified by 'conditionParameters' contains all the fields + // in the condition tracker output dimension. + // [conditionCache]: the cache holding the condition evaluation values. + virtual void isConditionMet( + const ConditionKey& conditionParameters, + const std::vector>& allConditions, + const bool isPartialLink, + std::vector& conditionCache) const = 0; + + // return the list of AtomMatchingTracker index that this ConditionTracker uses. + virtual const std::set& getAtomMatchingTrackerIndex() const { + return mTrackerIndex; + } + + virtual void setSliced(bool sliced) { + mSliced = mSliced | sliced; + } + + inline bool isSliced() const { + return mSliced; + } + + virtual const std::set* getChangedToTrueDimensions( + const std::vector>& allConditions) const = 0; + virtual const std::set* getChangedToFalseDimensions( + const std::vector>& allConditions) const = 0; + + inline int64_t getConditionId() const { + return mConditionId; + } + + inline uint64_t getProtoHash() const { + return mProtoHash; + } + + virtual const std::map* getSlicedDimensionMap( + const std::vector>& allConditions) const = 0; + + virtual bool IsChangedDimensionTrackable() const = 0; + + virtual bool IsSimpleCondition() const = 0; + + virtual bool equalOutputDimensions( + const std::vector>& allConditions, + const vector& dimensions) const = 0; + + // Return the current condition state of the unsliced part of the condition. + inline ConditionState getUnSlicedPartConditionState() const { + return mUnSlicedPartCondition; + } + +protected: + const int64_t mConditionId; + + // the index of this condition in the manager's condition list. + int mIndex; + + // if it's properly initialized. + bool mInitialized; + + // the list of AtomMatchingTracker index that this ConditionTracker uses. + std::set mTrackerIndex; + + // This variable is only used for CombinationConditionTrackers. + // SimpleConditionTrackers technically don't have an unsliced part because + // they are either sliced or unsliced. + // + // CombinationConditionTrackers have multiple children ConditionTrackers + // that can be a mixture of sliced or unsliced. This tracks the + // condition of the unsliced part of the combination condition. + ConditionState mUnSlicedPartCondition; + + bool mSliced; + + // Hash of the Predicate's proto bytes from StatsdConfig. + // Used to determine if the definition of this condition has changed across a config update. + const uint64_t mProtoHash; + + FRIEND_TEST(ConfigUpdateTest, TestUpdateConditions); +}; + +} // namespace statsd +} // namespace os +} // namespace android diff --git a/statsd/src/condition/ConditionWizard.cpp b/statsd/src/condition/ConditionWizard.cpp new file mode 100644 index 00000000..c542032b --- /dev/null +++ b/statsd/src/condition/ConditionWizard.cpp @@ -0,0 +1,70 @@ +/* + * 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 "ConditionWizard.h" + +namespace android { +namespace os { +namespace statsd { + +using std::vector; + +ConditionState ConditionWizard::query(const int index, const ConditionKey& parameters, + const bool isPartialLink) { + vector cache(mAllConditions.size(), ConditionState::kNotEvaluated); + + mAllConditions[index]->isConditionMet( + parameters, mAllConditions, isPartialLink, + cache); + return cache[index]; +} + +const set* ConditionWizard::getChangedToTrueDimensions( + const int index) const { + return mAllConditions[index]->getChangedToTrueDimensions(mAllConditions); +} + +const set* ConditionWizard::getChangedToFalseDimensions( + const int index) const { + return mAllConditions[index]->getChangedToFalseDimensions(mAllConditions); +} + +bool ConditionWizard::IsChangedDimensionTrackable(const int index) { + if (index >= 0 && index < (int)mAllConditions.size()) { + return mAllConditions[index]->IsChangedDimensionTrackable(); + } else { + return false; + } +} + +bool ConditionWizard::IsSimpleCondition(const int index) { + if (index >= 0 && index < (int)mAllConditions.size()) { + return mAllConditions[index]->IsSimpleCondition(); + } else { + return false; + } +} + +bool ConditionWizard::equalOutputDimensions(const int index, const vector& dimensions) { + if (index >= 0 && index < (int)mAllConditions.size()) { + return mAllConditions[index]->equalOutputDimensions(mAllConditions, dimensions); + } else { + return false; + } +} + +} // namespace statsd +} // namespace os +} // namespace android diff --git a/statsd/src/condition/ConditionWizard.h b/statsd/src/condition/ConditionWizard.h new file mode 100644 index 00000000..43db94cf --- /dev/null +++ b/statsd/src/condition/ConditionWizard.h @@ -0,0 +1,68 @@ +/* + * 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. + */ + +#ifndef CONDITION_WIZARD_H +#define CONDITION_WIZARD_H + +#include "ConditionTracker.h" +#include "condition_util.h" +#include "stats_util.h" + +namespace android { +namespace os { +namespace statsd { + +// Held by MetricProducer, to query a condition state with input defined in MetricConditionLink. +class ConditionWizard : public virtual android::RefBase { +public: + ConditionWizard(){}; // for testing + explicit ConditionWizard(std::vector>& conditionTrackers) + : mAllConditions(conditionTrackers){}; + + virtual ~ConditionWizard(){}; + + // Query condition state, for a ConditionTracker at [conditionIndex], with [conditionParameters] + // [conditionParameters] mapping from condition name to the HashableDimensionKey to query the + // condition. + // The ConditionTracker at [conditionIndex] can be a CombinationConditionTracker. In this case, + // the conditionParameters contains the parameters for it's children SimpleConditionTrackers. + virtual ConditionState query(const int conditionIndex, const ConditionKey& conditionParameters, + const bool isPartialLink); + + virtual const std::set* getChangedToTrueDimensions(const int index) const; + virtual const std::set* getChangedToFalseDimensions( + const int index) const; + bool equalOutputDimensions(const int index, const vector& dimensions); + + bool IsChangedDimensionTrackable(const int index); + bool IsSimpleCondition(const int index); + + ConditionState getUnSlicedPartConditionState(const int index) { + return mAllConditions[index]->getUnSlicedPartConditionState(); + } + + const std::map* getSlicedDimensionMap(const int index) const { + return mAllConditions[index]->getSlicedDimensionMap(mAllConditions); + } + +private: + std::vector> mAllConditions; +}; + +} // namespace statsd +} // namespace os +} // namespace android +#endif // CONDITION_WIZARD_H diff --git a/statsd/src/condition/SimpleConditionTracker.cpp b/statsd/src/condition/SimpleConditionTracker.cpp new file mode 100644 index 00000000..1dcc8f96 --- /dev/null +++ b/statsd/src/condition/SimpleConditionTracker.cpp @@ -0,0 +1,409 @@ +/* + * 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. + */ + +#define DEBUG false // STOPSHIP if true +#include "Log.h" + +#include "SimpleConditionTracker.h" +#include "guardrail/StatsdStats.h" + +namespace android { +namespace os { +namespace statsd { + +using std::unordered_map; + +SimpleConditionTracker::SimpleConditionTracker( + const ConfigKey& key, const int64_t& id, const uint64_t protoHash, const int index, + const SimplePredicate& simplePredicate, + const unordered_map& atomMatchingTrackerMap) + : ConditionTracker(id, index, protoHash), + mConfigKey(key), + mContainANYPositionInInternalDimensions(false) { + VLOG("creating SimpleConditionTracker %lld", (long long)mConditionId); + mCountNesting = simplePredicate.count_nesting(); + + setMatcherIndices(simplePredicate, atomMatchingTrackerMap); + + if (simplePredicate.has_dimensions()) { + translateFieldMatcher(simplePredicate.dimensions(), &mOutputDimensions); + if (mOutputDimensions.size() > 0) { + mSliced = true; + } + mContainANYPositionInInternalDimensions = HasPositionANY(simplePredicate.dimensions()); + } + + if (simplePredicate.initial_value() == SimplePredicate_InitialValue_FALSE) { + mInitialValue = ConditionState::kFalse; + } else { + mInitialValue = ConditionState::kUnknown; + } + + mInitialized = true; +} + +SimpleConditionTracker::~SimpleConditionTracker() { + VLOG("~SimpleConditionTracker()"); +} + +bool SimpleConditionTracker::init(const vector& allConditionConfig, + const vector>& allConditionTrackers, + const unordered_map& conditionIdIndexMap, + vector& stack, vector& conditionCache) { + // SimpleConditionTracker does not have dependency on other conditions, thus we just return + // if the initialization was successful. + ConditionKey conditionKey; + if (mSliced) { + conditionKey[mConditionId] = DEFAULT_DIMENSION_KEY; + } + isConditionMet(conditionKey, allConditionTrackers, mSliced, conditionCache); + return mInitialized; +} + +bool SimpleConditionTracker::onConfigUpdated( + const vector& allConditionProtos, const int index, + const vector>& allConditionTrackers, + const unordered_map& atomMatchingTrackerMap, + const unordered_map& conditionTrackerMap) { + ConditionTracker::onConfigUpdated(allConditionProtos, index, allConditionTrackers, + atomMatchingTrackerMap, conditionTrackerMap); + setMatcherIndices(allConditionProtos[index].simple_predicate(), atomMatchingTrackerMap); + return true; +} + +void SimpleConditionTracker::setMatcherIndices( + const SimplePredicate& simplePredicate, + const unordered_map& atomMatchingTrackerMap) { + mTrackerIndex.clear(); + if (simplePredicate.has_start()) { + auto pair = atomMatchingTrackerMap.find(simplePredicate.start()); + if (pair == atomMatchingTrackerMap.end()) { + ALOGW("Start matcher %lld not found in the config", (long long)simplePredicate.start()); + return; + } + mStartLogMatcherIndex = pair->second; + mTrackerIndex.insert(mStartLogMatcherIndex); + } else { + mStartLogMatcherIndex = -1; + } + + if (simplePredicate.has_stop()) { + auto pair = atomMatchingTrackerMap.find(simplePredicate.stop()); + if (pair == atomMatchingTrackerMap.end()) { + ALOGW("Stop matcher %lld not found in the config", (long long)simplePredicate.stop()); + return; + } + mStopLogMatcherIndex = pair->second; + mTrackerIndex.insert(mStopLogMatcherIndex); + } else { + mStopLogMatcherIndex = -1; + } + + if (simplePredicate.has_stop_all()) { + auto pair = atomMatchingTrackerMap.find(simplePredicate.stop_all()); + if (pair == atomMatchingTrackerMap.end()) { + ALOGW("Stop all matcher %lld found in the config", + (long long)simplePredicate.stop_all()); + return; + } + mStopAllLogMatcherIndex = pair->second; + mTrackerIndex.insert(mStopAllLogMatcherIndex); + } else { + mStopAllLogMatcherIndex = -1; + } +} + +void SimpleConditionTracker::dumpState() { + VLOG("%lld DUMP:", (long long)mConditionId); + for (const auto& pair : mSlicedConditionState) { + VLOG("\t%s : %d", pair.first.toString().c_str(), pair.second); + } + + VLOG("Changed to true keys: \n"); + for (const auto& key : mLastChangedToTrueDimensions) { + VLOG("%s", key.toString().c_str()); + } + VLOG("Changed to false keys: \n"); + for (const auto& key : mLastChangedToFalseDimensions) { + VLOG("%s", key.toString().c_str()); + } +} + +void SimpleConditionTracker::handleStopAll(std::vector& conditionCache, + std::vector& conditionChangedCache) { + // Unless the default condition is false, and there was nothing started, otherwise we have + // triggered a condition change. + conditionChangedCache[mIndex] = + (mInitialValue == ConditionState::kFalse && mSlicedConditionState.empty()) ? false + : true; + + for (const auto& cond : mSlicedConditionState) { + if (cond.second > 0) { + mLastChangedToFalseDimensions.insert(cond.first); + } + } + + // After StopAll, we know everything has stopped. From now on, default condition is false. + mInitialValue = ConditionState::kFalse; + mSlicedConditionState.clear(); + conditionCache[mIndex] = ConditionState::kFalse; +} + +bool SimpleConditionTracker::hitGuardRail(const HashableDimensionKey& newKey) { + if (!mSliced || mSlicedConditionState.find(newKey) != mSlicedConditionState.end()) { + // if the condition is not sliced or the key is not new, we are good! + return false; + } + // 1. Report the tuple count if the tuple count > soft limit + if (mSlicedConditionState.size() > StatsdStats::kDimensionKeySizeSoftLimit - 1) { + size_t newTupleCount = mSlicedConditionState.size() + 1; + StatsdStats::getInstance().noteConditionDimensionSize(mConfigKey, mConditionId, newTupleCount); + // 2. Don't add more tuples, we are above the allowed threshold. Drop the data. + if (newTupleCount > StatsdStats::kDimensionKeySizeHardLimit) { + ALOGE("Predicate %lld dropping data for dimension key %s", + (long long)mConditionId, newKey.toString().c_str()); + return true; + } + } + return false; +} + +void SimpleConditionTracker::handleConditionEvent(const HashableDimensionKey& outputKey, + bool matchStart, ConditionState* conditionCache, + bool* conditionChangedCache) { + bool changed = false; + auto outputIt = mSlicedConditionState.find(outputKey); + ConditionState newCondition; + if (hitGuardRail(outputKey)) { + (*conditionChangedCache) = false; + // Tells the caller it's evaluated. + (*conditionCache) = ConditionState::kUnknown; + return; + } + if (outputIt == mSlicedConditionState.end()) { + // We get a new output key. + newCondition = matchStart ? ConditionState::kTrue : ConditionState::kFalse; + if (matchStart && mInitialValue != ConditionState::kTrue) { + mSlicedConditionState[outputKey] = 1; + changed = true; + mLastChangedToTrueDimensions.insert(outputKey); + } else if (mInitialValue != ConditionState::kFalse) { + // it's a stop and we don't have history about it. + // If the default condition is not false, it means this stop is valuable to us. + mSlicedConditionState[outputKey] = 0; + mLastChangedToFalseDimensions.insert(outputKey); + changed = true; + } + } else { + // we have history about this output key. + auto& startedCount = outputIt->second; + // assign the old value first. + newCondition = startedCount > 0 ? ConditionState::kTrue : ConditionState::kFalse; + if (matchStart) { + if (startedCount == 0) { + mLastChangedToTrueDimensions.insert(outputKey); + // This condition for this output key will change from false -> true + changed = true; + } + + // it's ok to do ++ here, even if we don't count nesting. The >1 counts will be treated + // as 1 if not counting nesting. + startedCount++; + newCondition = ConditionState::kTrue; + } else { + // This is a stop event. + if (startedCount > 0) { + if (mCountNesting) { + startedCount--; + if (startedCount == 0) { + newCondition = ConditionState::kFalse; + } + } else { + // not counting nesting, so ignore the number of starts, stop now. + startedCount = 0; + newCondition = ConditionState::kFalse; + } + // if everything has stopped for this output key, condition true -> false; + if (startedCount == 0) { + mLastChangedToFalseDimensions.insert(outputKey); + changed = true; + } + } + + // if default condition is false, it means we don't need to keep the false values. + if (mInitialValue == ConditionState::kFalse && startedCount == 0) { + mSlicedConditionState.erase(outputIt); + VLOG("erase key %s", outputKey.toString().c_str()); + } + } + } + + // dump all dimensions for debugging + if (DEBUG) { + dumpState(); + } + + (*conditionChangedCache) = changed; + (*conditionCache) = newCondition; + + VLOG("SimplePredicate %lld nonSlicedChange? %d", (long long)mConditionId, + conditionChangedCache[mIndex] == true); +} + +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", + (long long)mConditionId, conditionCache[mIndex]); + return; + } + mLastChangedToTrueDimensions.clear(); + mLastChangedToFalseDimensions.clear(); + + if (mStopAllLogMatcherIndex >= 0 && mStopAllLogMatcherIndex < int(eventMatcherValues.size()) && + eventMatcherValues[mStopAllLogMatcherIndex] == MatchingState::kMatched) { + handleStopAll(conditionCache, conditionChangedCache); + return; + } + + int matchedState = -1; + // Note: The order to evaluate the following start, stop, stop_all matters. + // The priority of overwrite is stop_all > stop > start. + if (mStartLogMatcherIndex >= 0 && + eventMatcherValues[mStartLogMatcherIndex] == MatchingState::kMatched) { + matchedState = 1; + } + + if (mStopLogMatcherIndex >= 0 && + eventMatcherValues[mStopLogMatcherIndex] == MatchingState::kMatched) { + matchedState = 0; + } + + if (matchedState < 0) { + // The event doesn't match this condition. So we just report existing condition values. + conditionChangedCache[mIndex] = false; + if (mSliced) { + // if the condition result is sliced. The overall condition is true if any of the sliced + // condition is true + conditionCache[mIndex] = mInitialValue; + for (const auto& slicedCondition : mSlicedConditionState) { + if (slicedCondition.second > 0) { + conditionCache[mIndex] = ConditionState::kTrue; + break; + } + } + } else { + const auto& itr = mSlicedConditionState.find(DEFAULT_DIMENSION_KEY); + if (itr == mSlicedConditionState.end()) { + // condition not sliced, but we haven't seen the matched start or stop yet. so + // return initial value. + conditionCache[mIndex] = mInitialValue; + } else { + // return the cached condition. + conditionCache[mIndex] = + itr->second > 0 ? ConditionState::kTrue : ConditionState::kFalse; + } + } + return; + } + + ConditionState overallState = mInitialValue; + bool overallChanged = false; + + if (mOutputDimensions.size() == 0) { + handleConditionEvent(DEFAULT_DIMENSION_KEY, matchedState == 1, &overallState, + &overallChanged); + } else if (!mContainANYPositionInInternalDimensions) { + HashableDimensionKey outputValue; + filterValues(mOutputDimensions, event.getValues(), &outputValue); + + // If this event has multiple nodes in the attribution chain, this log event probably will + // generate multiple dimensions. If so, we will find if the condition changes for any + // dimension and ask the corresponding metric producer to verify whether the actual sliced + // condition has changed or not. + // A high level assumption is that a predicate is either sliced or unsliced. We will never + // have both sliced and unsliced version of a predicate. + handleConditionEvent(outputValue, matchedState == 1, &overallState, &overallChanged); + } else { + ALOGE("The condition tracker should not be sliced by ANY position matcher."); + } + conditionCache[mIndex] = overallState; + conditionChangedCache[mIndex] = overallChanged; +} + +void SimpleConditionTracker::isConditionMet( + const ConditionKey& conditionParameters, const vector>& allConditions, + const bool isPartialLink, + vector& conditionCache) const { + + if (conditionCache[mIndex] != ConditionState::kNotEvaluated) { + // it has been evaluated. + VLOG("Yes, already evaluated, %lld %d", + (long long)mConditionId, conditionCache[mIndex]); + return; + } + const auto pair = conditionParameters.find(mConditionId); + + if (pair == conditionParameters.end()) { + ConditionState conditionState = ConditionState::kNotEvaluated; + 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; + } + + ConditionState conditionState = ConditionState::kNotEvaluated; + const HashableDimensionKey& key = pair->second; + if (isPartialLink) { + // For unseen key, check whether the require dimensions are subset of sliced condition + // output. + conditionState = conditionState | mInitialValue; + for (const auto& slice : mSlicedConditionState) { + ConditionState sliceState = + slice.second > 0 ? ConditionState::kTrue : ConditionState::kFalse; + if (slice.first.contains(key)) { + conditionState = conditionState | sliceState; + } + } + } else { + auto startedCountIt = mSlicedConditionState.find(key); + conditionState = conditionState | mInitialValue; + if (startedCountIt != mSlicedConditionState.end()) { + ConditionState sliceState = + startedCountIt->second > 0 ? ConditionState::kTrue : ConditionState::kFalse; + conditionState = conditionState | sliceState; + } + + } + conditionCache[mIndex] = conditionState; + VLOG("Predicate %lld return %d", (long long)mConditionId, conditionCache[mIndex]); +} + +} // namespace statsd +} // namespace os +} // namespace android diff --git a/statsd/src/condition/SimpleConditionTracker.h b/statsd/src/condition/SimpleConditionTracker.h new file mode 100644 index 00000000..cd797c71 --- /dev/null +++ b/statsd/src/condition/SimpleConditionTracker.h @@ -0,0 +1,140 @@ +/* + * 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. + */ + +#ifndef SIMPLE_CONDITION_TRACKER_H +#define SIMPLE_CONDITION_TRACKER_H + +#include +#include "ConditionTracker.h" +#include "config/ConfigKey.h" +#include "src/statsd_config.pb.h" +#include "stats_util.h" + +namespace android { +namespace os { +namespace statsd { + +class SimpleConditionTracker : public ConditionTracker { +public: + SimpleConditionTracker(const ConfigKey& key, const int64_t& id, const uint64_t protoHash, + const int index, const SimplePredicate& simplePredicate, + const std::unordered_map& atomMatchingTrackerMap); + + ~SimpleConditionTracker(); + + bool init(const std::vector& allConditionConfig, + const std::vector>& allConditionTrackers, + const std::unordered_map& conditionIdIndexMap, std::vector& stack, + std::vector& conditionCache) override; + + bool onConfigUpdated(const std::vector& allConditionProtos, const int index, + const std::vector>& allConditionTrackers, + const std::unordered_map& atomMatchingTrackerMap, + const std::unordered_map& conditionTrackerMap) override; + + void evaluateCondition(const LogEvent& event, + const std::vector& eventMatcherValues, + const std::vector>& mAllConditions, + std::vector& conditionCache, + std::vector& changedCache) override; + + void isConditionMet(const ConditionKey& conditionParameters, + const std::vector>& allConditions, + const bool isPartialLink, + std::vector& conditionCache) const override; + + virtual const std::set* getChangedToTrueDimensions( + const std::vector>& allConditions) const { + if (mSliced) { + return &mLastChangedToTrueDimensions; + } else { + return nullptr; + } + } + + virtual const std::set* getChangedToFalseDimensions( + const std::vector>& allConditions) const { + if (mSliced) { + return &mLastChangedToFalseDimensions; + } else { + return nullptr; + } + } + + const std::map* getSlicedDimensionMap( + const std::vector>& allConditions) const override { + return &mSlicedConditionState; + } + + bool IsChangedDimensionTrackable() const override { return true; } + + bool IsSimpleCondition() const override { return true; } + + bool equalOutputDimensions( + const std::vector>& allConditions, + const vector& dimensions) const override { + return equalDimensions(mOutputDimensions, dimensions); + } + +private: + const ConfigKey mConfigKey; + // The index of the LogEventMatcher which defines the start. + int mStartLogMatcherIndex; + + // The index of the LogEventMatcher which defines the end. + int mStopLogMatcherIndex; + + // if the start end needs to be nested. + bool mCountNesting; + + // The index of the LogEventMatcher which defines the stop all. + int mStopAllLogMatcherIndex; + + ConditionState mInitialValue; + + std::vector mOutputDimensions; + + bool mContainANYPositionInInternalDimensions; + + std::set mLastChangedToTrueDimensions; + std::set mLastChangedToFalseDimensions; + + std::map mSlicedConditionState; + + void setMatcherIndices(const SimplePredicate& predicate, + const std::unordered_map& logTrackerMap); + + void handleStopAll(std::vector& conditionCache, + std::vector& changedCache); + + void handleConditionEvent(const HashableDimensionKey& outputKey, bool matchStart, + ConditionState* conditionCache, bool* changedCache); + + bool hitGuardRail(const HashableDimensionKey& newKey); + + void dumpState(); + + FRIEND_TEST(SimpleConditionTrackerTest, TestSlicedCondition); + FRIEND_TEST(SimpleConditionTrackerTest, TestSlicedWithNoOutputDim); + FRIEND_TEST(SimpleConditionTrackerTest, TestStopAll); + FRIEND_TEST(ConfigUpdateTest, TestUpdateConditions); +}; + +} // namespace statsd +} // namespace os +} // namespace android + +#endif // SIMPLE_CONDITION_TRACKER_H diff --git a/statsd/src/condition/condition_util.cpp b/statsd/src/condition/condition_util.cpp new file mode 100644 index 00000000..465c0783 --- /dev/null +++ b/statsd/src/condition/condition_util.cpp @@ -0,0 +1,93 @@ +/* + * 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 "Log.h" + +#include "condition_util.h" + +#include "../matchers/matcher_util.h" +#include "ConditionTracker.h" +#include "src/statsd_config.pb.h" +#include "stats_util.h" + +namespace android { +namespace os { +namespace statsd { + +using std::vector; + + +ConditionState evaluateCombinationCondition(const std::vector& children, + const LogicalOperation& operation, + const std::vector& conditionCache) { + ConditionState newCondition; + + bool hasUnknown = false; + bool hasFalse = false; + bool hasTrue = false; + + for (auto childIndex : children) { + ConditionState childState = conditionCache[childIndex]; + if (childState == ConditionState::kUnknown) { + hasUnknown = true; + break; + } + if (childState == ConditionState::kFalse) { + hasFalse = true; + } + if (childState == ConditionState::kTrue) { + hasTrue = true; + } + } + + // If any child condition is in unknown state, the condition is unknown too. + if (hasUnknown) { + return ConditionState::kUnknown; + } + + switch (operation) { + case LogicalOperation::AND: { + newCondition = hasFalse ? ConditionState::kFalse : ConditionState::kTrue; + break; + } + case LogicalOperation::OR: { + newCondition = hasTrue ? ConditionState::kTrue : ConditionState::kFalse; + break; + } + case LogicalOperation::NOT: + newCondition = children.empty() ? ConditionState::kUnknown : + ((conditionCache[children[0]] == ConditionState::kFalse) ? + ConditionState::kTrue : ConditionState::kFalse); + break; + case LogicalOperation::NAND: + newCondition = hasFalse ? ConditionState::kTrue : ConditionState::kFalse; + break; + case LogicalOperation::NOR: + newCondition = hasTrue ? ConditionState::kFalse : ConditionState::kTrue; + break; + case LogicalOperation::LOGICAL_OPERATION_UNSPECIFIED: + newCondition = ConditionState::kFalse; + break; + } + return newCondition; +} + +ConditionState operator|(ConditionState l, ConditionState r) { + return l >= r ? l : r; +} +} // namespace statsd +} // namespace os +} // namespace android diff --git a/statsd/src/condition/condition_util.h b/statsd/src/condition/condition_util.h new file mode 100644 index 00000000..f1045d6d --- /dev/null +++ b/statsd/src/condition/condition_util.h @@ -0,0 +1,43 @@ +/* + * 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. + */ + +#ifndef CONDITION_UTIL_H +#define CONDITION_UTIL_H + +#include +#include "../matchers/matcher_util.h" +#include "src/statsd_config.pb.h" + +namespace android { +namespace os { +namespace statsd { + +enum ConditionState { + kNotEvaluated = -2, + kUnknown = -1, + kFalse = 0, + kTrue = 1, +}; + +ConditionState operator|(ConditionState l, ConditionState r); + +ConditionState evaluateCombinationCondition(const std::vector& children, + const LogicalOperation& operation, + const std::vector& conditionCache); +} // namespace statsd +} // namespace os +} // namespace android +#endif // CONDITION_UTIL_H diff --git a/statsd/src/config/ConfigKey.cpp b/statsd/src/config/ConfigKey.cpp new file mode 100644 index 00000000..4a2bd279 --- /dev/null +++ b/statsd/src/config/ConfigKey.cpp @@ -0,0 +1,54 @@ +/* + * 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 "config/ConfigKey.h" + +namespace android { +namespace os { +namespace statsd { + +ConfigKey::ConfigKey() { +} + +ConfigKey::ConfigKey(const ConfigKey& that) : mId(that.mId), mUid(that.mUid) { +} + +ConfigKey::ConfigKey(int uid, const int64_t& id) : mId(id), mUid(uid) { +} + +ConfigKey::~ConfigKey() { +} + +string ConfigKey::ToString() const { + string s; + s += "(" + std::to_string(mUid) + " " + std::to_string(mId) + ")"; + return s; +} + + +int64_t StrToInt64(const string& str) { + char* endp; + int64_t value; + value = strtoll(str.c_str(), &endp, 0); + if (endp == str.c_str() || *endp != '\0') { + value = 0; + } + return value; +} + +} // namespace statsd +} // namespace os +} // namespace android diff --git a/statsd/src/config/ConfigKey.h b/statsd/src/config/ConfigKey.h new file mode 100644 index 00000000..0e5a7e3a --- /dev/null +++ b/statsd/src/config/ConfigKey.h @@ -0,0 +1,89 @@ +/* + * 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. + */ + +#pragma once + +#include "src/statsd_config.pb.h" + +#include + +namespace android { +namespace os { +namespace statsd { + +using std::hash; +using std::string; + +/** + * Uniquely identifies a configuration. + */ +class ConfigKey { +public: + ConfigKey(); + ConfigKey(const ConfigKey& that); + ConfigKey(int uid, const int64_t& id); + ~ConfigKey(); + + inline int GetUid() const { + return mUid; + } + inline const int64_t& GetId() const { + return mId; + } + + inline bool operator<(const ConfigKey& that) const { + if (mUid < that.mUid) { + return true; + } + if (mUid > that.mUid) { + return false; + } + return mId < that.mId; + }; + + inline bool operator==(const ConfigKey& that) const { + return mUid == that.mUid && mId == that.mId; + }; + + string ToString() const; + +private: + int64_t mId; + int mUid; +}; + +int64_t StrToInt64(const string& str); + +} // namespace statsd +} // namespace os +} // namespace android + +/** + * A hash function for ConfigKey so it can be used for unordered_map/set. + * Unfortunately this has to go in std namespace because C++ is fun! + */ +namespace std { + +using android::os::statsd::ConfigKey; + +template <> +struct hash { + std::size_t operator()(const ConfigKey& key) const { + return (7 * key.GetUid()) ^ ((hash()(key.GetId()))); + } +}; + +} // namespace std diff --git a/statsd/src/config/ConfigListener.cpp b/statsd/src/config/ConfigListener.cpp new file mode 100644 index 00000000..21a3f167 --- /dev/null +++ b/statsd/src/config/ConfigListener.cpp @@ -0,0 +1,31 @@ +/* + * 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 "config/ConfigListener.h" + +namespace android { +namespace os { +namespace statsd { + +ConfigListener::ConfigListener() { +} + +ConfigListener::~ConfigListener() { +} + +} // namespace statsd +} // namespace os +} // namespace android diff --git a/statsd/src/config/ConfigListener.h b/statsd/src/config/ConfigListener.h new file mode 100644 index 00000000..4f71a4e1 --- /dev/null +++ b/statsd/src/config/ConfigListener.h @@ -0,0 +1,52 @@ +/* + * 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. + */ + +#pragma once + +#include "config/ConfigKey.h" + +#include + +namespace android { +namespace os { +namespace statsd { + +using android::RefBase; + +/** + * Callback for different subsystems inside statsd to implement to find out + * when a configuration has been added, updated or removed. + */ +class ConfigListener : public virtual RefBase { +public: + ConfigListener(); + virtual ~ConfigListener(); + + /** + * A configuration was added or updated. + */ + virtual void OnConfigUpdated(const int64_t timestampNs, const ConfigKey& key, + const StatsdConfig& config, bool modularUpdate = true) = 0; + + /** + * A configuration was removed. + */ + virtual void OnConfigRemoved(const ConfigKey& key) = 0; +}; + +} // namespace statsd +} // namespace os +} // namespace android diff --git a/statsd/src/config/ConfigManager.cpp b/statsd/src/config/ConfigManager.cpp new file mode 100644 index 00000000..13020e06 --- /dev/null +++ b/statsd/src/config/ConfigManager.cpp @@ -0,0 +1,375 @@ +/* + * 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. + */ + +#define DEBUG false // STOPSHIP if true +#include "Log.h" + +#include "config/ConfigManager.h" +#include "storage/StorageManager.h" + +#include "guardrail/StatsdStats.h" +#include "stats_log_util.h" +#include "stats_util.h" +#include "stats_log_util.h" + +#include +#include +#include "android-base/stringprintf.h" + +namespace android { +namespace os { +namespace statsd { + +using std::pair; +using std::string; +using std::vector; + +#define STATS_SERVICE_DIR "/data/misc/stats-service" + +using android::base::StringPrintf; +using std::unique_ptr; + +struct ConfigReceiverDeathCookie { + ConfigReceiverDeathCookie(const wp& configManager, const ConfigKey& configKey, + const shared_ptr& pir) : + mConfigManager(configManager), mConfigKey(configKey), mPir(pir) { + } + + wp mConfigManager; + ConfigKey mConfigKey; + shared_ptr mPir; +}; + +void ConfigManager::configReceiverDied(void* cookie) { + auto cookie_ = static_cast(cookie); + sp thiz = cookie_->mConfigManager.promote(); + if (!thiz) { + return; + } + + ConfigKey& configKey = cookie_->mConfigKey; + shared_ptr& pir = cookie_->mPir; + + // Erase the mapping from the config key to the config receiver (pir) if the + // mapping still exists. + lock_guard lock(thiz->mMutex); + auto it = thiz->mConfigReceivers.find(configKey); + if (it != thiz->mConfigReceivers.end() && it->second == pir) { + thiz->mConfigReceivers.erase(configKey); + } + + // The death recipient corresponding to this specific pir can never be + // triggered again, so free up resources. + delete cookie_; +} + +struct ActiveConfigChangedReceiverDeathCookie { + ActiveConfigChangedReceiverDeathCookie(const wp& configManager, const int uid, + const shared_ptr& pir) : + mConfigManager(configManager), mUid(uid), mPir(pir) { + } + + wp mConfigManager; + int mUid; + shared_ptr mPir; +}; + +void ConfigManager::activeConfigChangedReceiverDied(void* cookie) { + auto cookie_ = static_cast(cookie); + sp thiz = cookie_->mConfigManager.promote(); + if (!thiz) { + return; + } + + int uid = cookie_->mUid; + shared_ptr& pir = cookie_->mPir; + + // Erase the mapping from the config key to the active config changed + // receiver (pir) if the mapping still exists. + lock_guard lock(thiz->mMutex); + auto it = thiz->mActiveConfigsChangedReceivers.find(uid); + if (it != thiz->mActiveConfigsChangedReceivers.end() && it->second == pir) { + thiz->mActiveConfigsChangedReceivers.erase(uid); + } + + // The death recipient corresponding to this specific pir can never + // be triggered again, so free up resources. + delete cookie_; +} + +ConfigManager::ConfigManager() : + mConfigReceiverDeathRecipient(AIBinder_DeathRecipient_new(configReceiverDied)), + mActiveConfigChangedReceiverDeathRecipient( + AIBinder_DeathRecipient_new(activeConfigChangedReceiverDied)) { +} + +ConfigManager::~ConfigManager() { +} + +void ConfigManager::Startup() { + map configsFromDisk; + StorageManager::readConfigFromDisk(configsFromDisk); + for (const auto& pair : configsFromDisk) { + UpdateConfig(pair.first, pair.second); + } +} + +void ConfigManager::StartupForTest() { + // No-op function to avoid reading configs from disks for tests. +} + +void ConfigManager::AddListener(const sp& listener) { + lock_guard lock(mMutex); + mListeners.push_back(listener); +} + +void ConfigManager::UpdateConfig(const ConfigKey& key, const StatsdConfig& config) { + vector> broadcastList; + { + lock_guard lock(mMutex); + + const int numBytes = config.ByteSize(); + vector buffer(numBytes); + config.SerializeToArray(&buffer[0], numBytes); + + auto uidIt = mConfigs.find(key.GetUid()); + // GuardRail: Limit the number of configs per uid. + if (uidIt != mConfigs.end()) { + auto it = uidIt->second.find(key); + if (it == uidIt->second.end() && + uidIt->second.size() >= StatsdStats::kMaxConfigCountPerUid) { + ALOGE("ConfigManager: uid %d has exceeded the config count limit", key.GetUid()); + return; + } + } + + // Check if it's a duplicate config. + if (uidIt != mConfigs.end() && uidIt->second.find(key) != uidIt->second.end() && + StorageManager::hasIdenticalConfig(key, buffer)) { + // This is a duplicate config. + ALOGI("ConfigManager This is a duplicate config %s", key.ToString().c_str()); + // Update saved file on disk. We still update timestamp of file when + // there exists a duplicate configuration to avoid garbage collection. + update_saved_configs_locked(key, buffer, numBytes); + return; + } + + // Update saved file on disk. + update_saved_configs_locked(key, buffer, numBytes); + + // Add to set. + mConfigs[key.GetUid()].insert(key); + + for (const sp& listener : mListeners) { + broadcastList.push_back(listener); + } + } + + const int64_t timestampNs = getElapsedRealtimeNs(); + // Tell everyone + for (const sp& listener : broadcastList) { + listener->OnConfigUpdated(timestampNs, key, config); + } +} + +void ConfigManager::SetConfigReceiver(const ConfigKey& key, + const shared_ptr& pir) { + lock_guard lock(mMutex); + mConfigReceivers[key] = pir; + AIBinder_linkToDeath(pir->asBinder().get(), mConfigReceiverDeathRecipient.get(), + new ConfigReceiverDeathCookie(this, key, pir)); +} + +void ConfigManager::RemoveConfigReceiver(const ConfigKey& key) { + lock_guard lock(mMutex); + mConfigReceivers.erase(key); +} + +void ConfigManager::SetActiveConfigsChangedReceiver(const int uid, + const shared_ptr& pir) { + { + lock_guard lock(mMutex); + mActiveConfigsChangedReceivers[uid] = pir; + } + AIBinder_linkToDeath(pir->asBinder().get(), mActiveConfigChangedReceiverDeathRecipient.get(), + new ActiveConfigChangedReceiverDeathCookie(this, uid, pir)); +} + +void ConfigManager::RemoveActiveConfigsChangedReceiver(const int uid) { + lock_guard lock(mMutex); + mActiveConfigsChangedReceivers.erase(uid); +} + +void ConfigManager::RemoveConfig(const ConfigKey& key) { + vector> broadcastList; + { + lock_guard lock(mMutex); + + auto uid = key.GetUid(); + auto uidIt = mConfigs.find(uid); + if (uidIt != mConfigs.end() && uidIt->second.find(key) != uidIt->second.end()) { + // Remove from map + uidIt->second.erase(key); + + for (const sp& listener : mListeners) { + broadcastList.push_back(listener); + } + } + + // Remove from disk. There can still be a lingering file on disk so we check + // whether or not the config was on memory. + remove_saved_configs(key); + } + + for (const sp& listener:broadcastList) { + listener->OnConfigRemoved(key); + } +} + +void ConfigManager::remove_saved_configs(const ConfigKey& key) { + string suffix = StringPrintf("%d_%lld", key.GetUid(), (long long)key.GetId()); + StorageManager::deleteSuffixedFiles(STATS_SERVICE_DIR, suffix.c_str()); +} + +void ConfigManager::RemoveConfigs(int uid) { + vector removed; + vector> broadcastList; + { + lock_guard lock(mMutex); + + auto uidIt = mConfigs.find(uid); + if (uidIt == mConfigs.end()) { + return; + } + + for (auto it = uidIt->second.begin(); it != uidIt->second.end(); ++it) { + // Remove from map + remove_saved_configs(*it); + removed.push_back(*it); + } + + mConfigs.erase(uidIt); + + for (const sp& listener : mListeners) { + broadcastList.push_back(listener); + } + } + + // Remove separately so if they do anything in the callback they can't mess up our iteration. + for (auto& key : removed) { + // Tell everyone + for (const sp& listener:broadcastList) { + listener->OnConfigRemoved(key); + } + } +} + +void ConfigManager::RemoveAllConfigs() { + vector removed; + vector> broadcastList; + { + lock_guard lock(mMutex); + + for (auto uidIt = mConfigs.begin(); uidIt != mConfigs.end();) { + for (auto it = uidIt->second.begin(); it != uidIt->second.end();) { + // Remove from map + removed.push_back(*it); + it = uidIt->second.erase(it); + } + uidIt = mConfigs.erase(uidIt); + } + + for (const sp& listener : mListeners) { + broadcastList.push_back(listener); + } + } + + // Remove separately so if they do anything in the callback they can't mess up our iteration. + for (auto& key : removed) { + // Tell everyone + for (const sp& listener:broadcastList) { + listener->OnConfigRemoved(key); + } + } +} + +vector ConfigManager::GetAllConfigKeys() const { + lock_guard lock(mMutex); + + vector ret; + for (auto uidIt = mConfigs.cbegin(); uidIt != mConfigs.cend(); ++uidIt) { + for (auto it = uidIt->second.cbegin(); it != uidIt->second.cend(); ++it) { + ret.push_back(*it); + } + } + return ret; +} + +const shared_ptr ConfigManager::GetConfigReceiver(const ConfigKey& key) const { + lock_guard lock(mMutex); + + auto it = mConfigReceivers.find(key); + if (it == mConfigReceivers.end()) { + return nullptr; + } else { + return it->second; + } +} + +const shared_ptr ConfigManager::GetActiveConfigsChangedReceiver(const int uid) + const { + lock_guard lock(mMutex); + + auto it = mActiveConfigsChangedReceivers.find(uid); + if (it == mActiveConfigsChangedReceivers.end()) { + return nullptr; + } else { + return it->second; + } +} + +void ConfigManager::Dump(FILE* out) { + lock_guard lock(mMutex); + + fprintf(out, "CONFIGURATIONS\n"); + fprintf(out, " uid name\n"); + for (auto uidIt = mConfigs.cbegin(); uidIt != mConfigs.cend(); ++uidIt) { + for (auto it = uidIt->second.cbegin(); it != uidIt->second.cend(); ++it) { + fprintf(out, " %6d %lld\n", it->GetUid(), (long long)it->GetId()); + auto receiverIt = mConfigReceivers.find(*it); + if (receiverIt != mConfigReceivers.end()) { + fprintf(out, " -> received by PendingIntent as binder\n"); + } + } + } +} + +void ConfigManager::update_saved_configs_locked(const ConfigKey& key, + const vector& buffer, + const int numBytes) { + // If there is a pre-existing config with same key we should first delete it. + remove_saved_configs(key); + + // Then we save the latest config. + string file_name = + StringPrintf("%s/%ld_%d_%lld", STATS_SERVICE_DIR, time(nullptr), + key.GetUid(), (long long)key.GetId()); + StorageManager::writeFile(file_name.c_str(), &buffer[0], numBytes); +} + +} // namespace statsd +} // namespace os +} // namespace android diff --git a/statsd/src/config/ConfigManager.h b/statsd/src/config/ConfigManager.h new file mode 100644 index 00000000..bef057f9 --- /dev/null +++ b/statsd/src/config/ConfigManager.h @@ -0,0 +1,181 @@ +/* + * 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. + */ + +#pragma once + +#include "config/ConfigKey.h" +#include "config/ConfigListener.h" + +#include +#include +#include + +#include + +using aidl::android::os::IPendingIntentRef; +using std::shared_ptr; + +namespace android { +namespace os { +namespace statsd { + +/** + * Keeps track of which configurations have been set from various sources. + */ +class ConfigManager : public virtual android::RefBase { +public: + ConfigManager(); + virtual ~ConfigManager(); + + /** + * Initialize ConfigListener by reading from disk and get updates. + */ + void Startup(); + + /* + * No-op initializer for tests. + */ + void StartupForTest(); + + /** + * Someone else wants to know about the configs. + */ + void AddListener(const sp& listener); + + /** + * A configuration was added or updated. + * + * Reports this to listeners. + */ + void UpdateConfig(const ConfigKey& key, const StatsdConfig& data); + + /** + * Sets the broadcast receiver for a configuration key. + */ + void SetConfigReceiver(const ConfigKey& key, const shared_ptr& pir); + + /** + * Returns the package name and class name representing the broadcast receiver for this config. + */ + const shared_ptr GetConfigReceiver(const ConfigKey& key) const; + + /** + * Returns all config keys registered. + */ + std::vector GetAllConfigKeys() const; + + /** + * Erase any broadcast receiver associated with this config key. + */ + void RemoveConfigReceiver(const ConfigKey& key); + + /** + * Sets the broadcast receiver that is notified whenever the list of active configs + * changes for this uid. + */ + void SetActiveConfigsChangedReceiver(const int uid, const shared_ptr& pir); + + /** + * Returns the broadcast receiver for active configs changed for this uid. + */ + + const shared_ptr GetActiveConfigsChangedReceiver(const int uid) const; + + /** + * Erase any active configs changed broadcast receiver associated with this uid. + */ + void RemoveActiveConfigsChangedReceiver(const int uid); + + /** + * A configuration was removed. + * + * Reports this to listeners. + */ + void RemoveConfig(const ConfigKey& key); + + /** + * Remove all of the configs for the given uid. + */ + void RemoveConfigs(int uid); + + /** + * Remove all of the configs from memory. + */ + void RemoveAllConfigs(); + + /** + * Text dump of our state for debugging. + */ + void Dump(FILE* out); + +private: + mutable std::mutex mMutex; + + /** + * Save the configs to disk. + */ + void update_saved_configs_locked(const ConfigKey& key, + const std::vector& buffer, + const int numBytes); + + /** + * Remove saved configs from disk. + */ + void remove_saved_configs(const ConfigKey& key); + + /** + * Maps from uid to the config keys that have been set. + */ + std::map> mConfigs; + + /** + * Each config key can be subscribed by up to one receiver, specified as IPendingIntentRef. + */ + std::map> mConfigReceivers; + + /** + * Each uid can be subscribed by up to one receiver to notify that the list of active configs + * for this uid has changed. The receiver is specified as IPendingIntentRef. + */ + std::map> mActiveConfigsChangedReceivers; + + /** + * The ConfigListeners that will be told about changes. + */ + std::vector> mListeners; + + // Death recipients that are triggered when the host process holding an + // IPendingIntentRef dies. + ::ndk::ScopedAIBinder_DeathRecipient mConfigReceiverDeathRecipient; + ::ndk::ScopedAIBinder_DeathRecipient mActiveConfigChangedReceiverDeathRecipient; + + /** + * Death recipient callback that is called when a config receiver dies. + * The cookie is a pointer to a ConfigReceiverDeathCookie. + */ + static void configReceiverDied(void* cookie); + + /** + * Death recipient callback that is called when an active config changed + * receiver dies. The cookie is a pointer to an + * ActiveConfigChangedReceiverDeathCookie. + */ + static void activeConfigChangedReceiverDied(void* cookie); +}; + +} // namespace statsd +} // namespace os +} // namespace android diff --git a/statsd/src/experiment_ids.proto b/statsd/src/experiment_ids.proto new file mode 100644 index 00000000..c2036314 --- /dev/null +++ b/statsd/src/experiment_ids.proto @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2020 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. + */ + +syntax = "proto2"; + +package android.os.statsd; + +option java_package = "com.android.internal.os"; +option java_outer_classname = "ExperimentIdsProto"; + +// StatsLogProcessor uses the proto to parse experiment ids from +// BinaryPushStateChanged atoms. This needs to be in sync with +// TrainExperimentIds in atoms.proto. +message ExperimentIds { + repeated int64 experiment_id = 1; +} diff --git a/statsd/src/external/Perfetto.cpp b/statsd/src/external/Perfetto.cpp new file mode 100644 index 00000000..b9ccfd2b --- /dev/null +++ b/statsd/src/external/Perfetto.cpp @@ -0,0 +1,139 @@ +/* + * Copyright (C) 2018 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. + */ + +#define DEBUG false // STOPSHIP if true +#include "config/ConfigKey.h" +#include "Log.h" + +#include "src/statsd_config.pb.h" // Alert + +#include +#include +#include + +#include + +namespace { +const char kDropboxTag[] = "perfetto"; +} + +namespace android { +namespace os { +namespace statsd { + +bool CollectPerfettoTraceAndUploadToDropbox(const PerfettoDetails& config, + int64_t subscription_id, + int64_t alert_id, + const ConfigKey& configKey) { + VLOG("Starting trace collection through perfetto"); + + if (!config.has_trace_config()) { + ALOGE("The perfetto trace config is empty, aborting"); + return false; + } + + char subscriptionId[25]; + char alertId[25]; + char configId[25]; + char configUid[25]; + snprintf(subscriptionId, sizeof(subscriptionId), "%" PRId64, subscription_id); + snprintf(alertId, sizeof(alertId), "%" PRId64, alert_id); + snprintf(configId, sizeof(configId), "%" PRId64, configKey.GetId()); + snprintf(configUid, sizeof(configUid), "%d", configKey.GetUid()); + + android::base::unique_fd readPipe; + android::base::unique_fd writePipe; + if (!android::base::Pipe(&readPipe, &writePipe)) { + ALOGE("pipe() failed while calling the Perfetto client: %s", strerror(errno)); + return false; + } + + pid_t pid = fork(); + if (pid < 0) { + ALOGE("fork() failed while calling the Perfetto client: %s", strerror(errno)); + return false; + } + + if (pid == 0) { + // Child process. + + // No malloc calls or library calls after this point. Remember that even + // ALOGx (aka android_printLog()) can use dynamic memory for vsprintf(). + + writePipe.reset(); // Close the write end (owned by the main process). + + // Replace stdin with |readPipe| so the main process can write into it. + if (dup2(readPipe.get(), STDIN_FILENO) < 0) _exit(1); + readPipe.reset(); + + // Replace stdout/stderr with /dev/null and close any other file + // descriptor. This is to avoid SELinux complaining about perfetto + // trying to access files accidentally left open by statsd (i.e. files + // that have been opened without the O_CLOEXEC flag). + int devNullFd = open("/dev/null", O_RDWR | O_CLOEXEC); + if (dup2(devNullFd, STDOUT_FILENO) < 0) _exit(2); + if (dup2(devNullFd, STDERR_FILENO) < 0) _exit(3); + close(devNullFd); + for (int i = 0; i < 1024; i++) { + if (i != STDIN_FILENO && i != STDOUT_FILENO && i != STDERR_FILENO) close(i); + } + + execl("/system/bin/perfetto", "perfetto", "--background", "--config", "-", "--dropbox", + kDropboxTag, "--alert-id", alertId, "--config-id", configId, "--config-uid", + configUid, "--subscription-id", subscriptionId, nullptr); + + // execl() doesn't return in case of success, if we get here something + // failed. + _exit(4); + } + + // Main process. + + readPipe.reset(); // Close the read end (owned by the child process). + + // Using fdopen() because fwrite() has the right logic to chunking write() + // over a pipe (see __sfvwrite()). + FILE* writePipeStream = android::base::Fdopen(std::move(writePipe), "wb"); + if (!writePipeStream) { + ALOGE("fdopen() failed while calling the Perfetto client: %s", strerror(errno)); + return false; + } + + const std::string& cfgProto = config.trace_config(); + size_t bytesWritten = fwrite(cfgProto.data(), 1, cfgProto.size(), writePipeStream); + fclose(writePipeStream); + if (bytesWritten != cfgProto.size() || cfgProto.size() == 0) { + ALOGE("fwrite() failed (ret: %zd) while calling the Perfetto client: %s", bytesWritten, + strerror(errno)); + return false; + } + + // This does NOT wait for the full duration of the trace. It just waits until + // the process has read the config from stdin and detached. + int childStatus = 0; + waitpid(pid, &childStatus, 0); + if (!WIFEXITED(childStatus) || WEXITSTATUS(childStatus) != 0) { + ALOGE("Child process failed (0x%x) while calling the Perfetto client", childStatus); + return false; + } + + VLOG("CollectPerfettoTraceAndUploadToDropbox() succeeded"); + return true; +} + +} // namespace statsd +} // namespace os +} // namespace android diff --git a/statsd/src/external/Perfetto.h b/statsd/src/external/Perfetto.h new file mode 100644 index 00000000..095782a4 --- /dev/null +++ b/statsd/src/external/Perfetto.h @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2018 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. + */ + +#pragma once + +namespace android { +namespace os { +namespace statsd { + +class ConfigKey; +class PerfettoDetails; // Declared in statsd_config.pb.h + +// Starts the collection of a Perfetto trace with the given |config|. +// The trace is uploaded to Dropbox by the perfetto cmdline util once done. +// This method returns immediately after passing the config and does NOT wait +// for the full duration of the trace. +bool CollectPerfettoTraceAndUploadToDropbox(const PerfettoDetails& config, + int64_t subscription_id, + int64_t alert_id, + const ConfigKey& configKey); + +} // namespace statsd +} // namespace os +} // namespace android diff --git a/statsd/src/external/PullDataReceiver.h b/statsd/src/external/PullDataReceiver.h new file mode 100644 index 00000000..dd5c0cfa --- /dev/null +++ b/statsd/src/external/PullDataReceiver.h @@ -0,0 +1,41 @@ +/* + * 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. + */ +#pragma once + +#include +#include "StatsPuller.h" +#include "logd/LogEvent.h" + +namespace android { +namespace os { +namespace statsd { + +class PullDataReceiver : virtual public RefBase{ + public: + virtual ~PullDataReceiver() {} + /** + * @param data The pulled data. + * @param pullSuccess Whether the pull succeeded. If the pull does not succeed, the data for the + * bucket should be invalidated. + * @param originalPullTimeNs This is when all the pulls have been initiated (elapsed time). + */ + virtual void onDataPulled(const std::vector>& data, + bool pullSuccess, int64_t originalPullTimeNs) = 0; +}; + +} // namespace statsd +} // namespace os +} // namespace android diff --git a/statsd/src/external/PullResultReceiver.cpp b/statsd/src/external/PullResultReceiver.cpp new file mode 100644 index 00000000..8aa4792d --- /dev/null +++ b/statsd/src/external/PullResultReceiver.cpp @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2019 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 "PullResultReceiver.h" + +namespace android { +namespace os { +namespace statsd { + +PullResultReceiver::PullResultReceiver( + std::function&)> pullFinishCb) + : pullFinishCallback(std::move(pullFinishCb)) { +} + +Status PullResultReceiver::pullFinished(int32_t atomTag, bool success, + const vector& output) { + pullFinishCallback(atomTag, success, output); + return Status::ok(); +} + +PullResultReceiver::~PullResultReceiver() { +} + +} // namespace statsd +} // namespace os +} // namespace android diff --git a/statsd/src/external/PullResultReceiver.h b/statsd/src/external/PullResultReceiver.h new file mode 100644 index 00000000..ceaae801 --- /dev/null +++ b/statsd/src/external/PullResultReceiver.h @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2019 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 + +using namespace std; + +using Status = ::ndk::ScopedAStatus; +using aidl::android::os::BnPullAtomResultReceiver; +using aidl::android::util::StatsEventParcel; + +namespace android { +namespace os { +namespace statsd { + +class PullResultReceiver : public BnPullAtomResultReceiver { +public: + PullResultReceiver(function&)> + pullFinishCallback); + ~PullResultReceiver(); + + /** + * Binder call for finishing a pull. + */ + Status pullFinished(int32_t atomTag, bool success, + const vector& output) override; + +private: + function&)> pullFinishCallback; +}; + +} // namespace statsd +} // namespace os +} // namespace android diff --git a/statsd/src/external/PullUidProvider.h b/statsd/src/external/PullUidProvider.h new file mode 100644 index 00000000..2318c501 --- /dev/null +++ b/statsd/src/external/PullUidProvider.h @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2020 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. + */ +#pragma once + +#include + +#include "StatsPuller.h" +#include "logd/LogEvent.h" + +namespace android { +namespace os { +namespace statsd { + +class PullUidProvider : virtual public RefBase { +public: + virtual ~PullUidProvider() {} + + /** + * @param atomId The atom for which to get the uids. + */ + virtual vector getPullAtomUids(int32_t atomId) = 0; +}; + +} // namespace statsd +} // namespace os +} // namespace android diff --git a/statsd/src/external/StatsCallbackPuller.cpp b/statsd/src/external/StatsCallbackPuller.cpp new file mode 100644 index 00000000..78e6f094 --- /dev/null +++ b/statsd/src/external/StatsCallbackPuller.cpp @@ -0,0 +1,116 @@ +/* + * Copyright (C) 2019 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. + */ + +#define DEBUG false // STOPSHIP if true +#include "Log.h" + +#include "StatsCallbackPuller.h" +#include "PullResultReceiver.h" +#include "StatsPullerManager.h" +#include "logd/LogEvent.h" +#include "stats_log_util.h" + +#include + +using namespace std; + +using Status = ::ndk::ScopedAStatus; +using aidl::android::util::StatsEventParcel; +using ::ndk::SharedRefBase; + +namespace android { +namespace os { +namespace statsd { + +StatsCallbackPuller::StatsCallbackPuller(int tagId, const shared_ptr& callback, + const int64_t coolDownNs, int64_t timeoutNs, + const vector additiveFields) + : StatsPuller(tagId, coolDownNs, timeoutNs, additiveFields), mCallback(callback) { + VLOG("StatsCallbackPuller created for tag %d", tagId); +} + +bool StatsCallbackPuller::PullInternal(vector>* data) { + VLOG("StatsCallbackPuller called for tag %d", mTagId); + if(mCallback == nullptr) { + ALOGW("No callback registered"); + return false; + } + + // Shared variables needed in the result receiver. + shared_ptr cv_mutex = make_shared(); + shared_ptr cv = make_shared(); + shared_ptr pullFinish = make_shared(false); + shared_ptr pullSuccess = make_shared(false); + shared_ptr>> sharedData = + make_shared>>(); + + shared_ptr resultReceiver = SharedRefBase::make( + [cv_mutex, cv, pullFinish, pullSuccess, sharedData]( + int32_t atomTag, bool success, const vector& output) { + // This is the result of the pull, executing in a statsd binder thread. + // The pull could have taken a long time, and we should only modify + // data (the output param) if the pointer is in scope and the pull did not time out. + { + lock_guard lk(*cv_mutex); + for (const StatsEventParcel& parcel: output) { + shared_ptr event = make_shared(/*uid=*/-1, /*pid=*/-1); + bool valid = event->parseBuffer((uint8_t*)parcel.buffer.data(), + parcel.buffer.size()); + if (valid) { + sharedData->push_back(event); + } else { + StatsdStats::getInstance().noteAtomError(event->GetTagId(), + /*pull=*/true); + } + } + *pullSuccess = success; + *pullFinish = true; + } + cv->notify_one(); + }); + + // Initiate the pull. This is a oneway call to a different process, except + // in unit tests. In process calls are not oneway. + Status status = mCallback->onPullAtom(mTagId, resultReceiver); + if (!status.isOk()) { + StatsdStats::getInstance().notePullBinderCallFailed(mTagId); + return false; + } + + { + unique_lock unique_lk(*cv_mutex); + // Wait until the pull finishes, or until the pull timeout. + cv->wait_for(unique_lk, chrono::nanoseconds(mPullTimeoutNs), + [pullFinish] { return *pullFinish; }); + if (!*pullFinish) { + // Note: The parent stats puller will also note that there was a timeout and that the + // cache should be cleared. Once we migrate all pullers to this callback, we could + // consolidate the logic. + return true; + } else { + // Only copy the data if we did not timeout and the pull was successful. + if (*pullSuccess) { + *data = std::move(*sharedData); + } + VLOG("StatsCallbackPuller::pull succeeded for %d", mTagId); + return *pullSuccess; + } + } +} + +} // namespace statsd +} // namespace os +} // namespace android diff --git a/statsd/src/external/StatsCallbackPuller.h b/statsd/src/external/StatsCallbackPuller.h new file mode 100644 index 00000000..e82e8bb5 --- /dev/null +++ b/statsd/src/external/StatsCallbackPuller.h @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2019 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. + */ + +#pragma once + +#include +#include "StatsPuller.h" + +using aidl::android::os::IPullAtomCallback; +using std::shared_ptr; + +namespace android { +namespace os { +namespace statsd { + +class StatsCallbackPuller : public StatsPuller { +public: + explicit StatsCallbackPuller(int tagId, const shared_ptr& callback, + const int64_t coolDownNs, const int64_t timeoutNs, + const std::vector additiveFields); + +private: + bool PullInternal(vector>* data) override; + const shared_ptr mCallback; + + FRIEND_TEST(StatsCallbackPullerTest, PullFail); + FRIEND_TEST(StatsCallbackPullerTest, PullSuccess); + FRIEND_TEST(StatsCallbackPullerTest, PullTimeout); +}; + +} // namespace statsd +} // namespace os +} // namespace android diff --git a/statsd/src/external/StatsPuller.cpp b/statsd/src/external/StatsPuller.cpp new file mode 100644 index 00000000..bb5d0a6b --- /dev/null +++ b/statsd/src/external/StatsPuller.cpp @@ -0,0 +1,126 @@ +/* + * 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. + */ + +#define DEBUG false // STOPSHIP if true +#include "Log.h" + +#include "StatsPuller.h" +#include "StatsPullerManager.h" +#include "guardrail/StatsdStats.h" +#include "puller_util.h" +#include "stats_log_util.h" + +namespace android { +namespace os { +namespace statsd { + +using std::lock_guard; + +sp StatsPuller::mUidMap = nullptr; +void StatsPuller::SetUidMap(const sp& uidMap) { mUidMap = uidMap; } + +StatsPuller::StatsPuller(const int tagId, const int64_t coolDownNs, const int64_t pullTimeoutNs, + const std::vector additiveFields) + : mTagId(tagId), + mPullTimeoutNs(pullTimeoutNs), + mCoolDownNs(coolDownNs), + mAdditiveFields(additiveFields), + mLastPullTimeNs(0), + mLastEventTimeNs(0) { +} + +bool StatsPuller::Pull(const int64_t eventTimeNs, std::vector>* data) { + lock_guard lock(mLock); + const int64_t elapsedTimeNs = getElapsedRealtimeNs(); + const int64_t systemUptimeMillis = getSystemUptimeMillis(); + StatsdStats::getInstance().notePull(mTagId); + const bool shouldUseCache = + (mLastEventTimeNs == eventTimeNs) || (elapsedTimeNs - mLastPullTimeNs < mCoolDownNs); + if (shouldUseCache) { + if (mHasGoodData) { + (*data) = mCachedData; + StatsdStats::getInstance().notePullFromCache(mTagId); + + } + return mHasGoodData; + } + if (mLastPullTimeNs > 0) { + StatsdStats::getInstance().updateMinPullIntervalSec( + mTagId, (elapsedTimeNs - mLastPullTimeNs) / NS_PER_SEC); + } + mCachedData.clear(); + mLastPullTimeNs = elapsedTimeNs; + mLastEventTimeNs = eventTimeNs; + mHasGoodData = PullInternal(&mCachedData); + if (!mHasGoodData) { + return mHasGoodData; + } + const int64_t pullElapsedDurationNs = getElapsedRealtimeNs() - elapsedTimeNs; + const int64_t pullSystemUptimeDurationMillis = getSystemUptimeMillis() - systemUptimeMillis; + StatsdStats::getInstance().notePullTime(mTagId, pullElapsedDurationNs); + const bool pullTimeOut = pullElapsedDurationNs > mPullTimeoutNs; + if (pullTimeOut) { + // Something went wrong. Discard the data. + mCachedData.clear(); + mHasGoodData = false; + StatsdStats::getInstance().notePullTimeout( + mTagId, pullSystemUptimeDurationMillis, NanoToMillis(pullElapsedDurationNs)); + ALOGW("Pull for atom %d exceeds timeout %lld nano seconds.", mTagId, + (long long)pullElapsedDurationNs); + return mHasGoodData; + } + + if (mCachedData.size() > 0) { + mapAndMergeIsolatedUidsToHostUid(mCachedData, mUidMap, mTagId, mAdditiveFields); + } + + if (mCachedData.empty()) { + VLOG("Data pulled is empty"); + StatsdStats::getInstance().noteEmptyData(mTagId); + } + + (*data) = mCachedData; + return mHasGoodData; +} + +int StatsPuller::ForceClearCache() { + return clearCache(); +} + +int StatsPuller::clearCache() { + lock_guard lock(mLock); + return clearCacheLocked(); +} + +int StatsPuller::clearCacheLocked() { + int ret = mCachedData.size(); + mCachedData.clear(); + mLastPullTimeNs = 0; + mLastEventTimeNs = 0; + return ret; +} + +int StatsPuller::ClearCacheIfNecessary(int64_t timestampNs) { + if (timestampNs - mLastPullTimeNs > mCoolDownNs) { + return clearCache(); + } else { + return 0; + } +} + +} // namespace statsd +} // namespace os +} // namespace android diff --git a/statsd/src/external/StatsPuller.h b/statsd/src/external/StatsPuller.h new file mode 100644 index 00000000..470d15e6 --- /dev/null +++ b/statsd/src/external/StatsPuller.h @@ -0,0 +1,119 @@ +/* + * 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. + */ + +#pragma once + +#include +#include +#include +#include +#include "packages/UidMap.h" + +#include "guardrail/StatsdStats.h" +#include "logd/LogEvent.h" +#include "puller_util.h" + +using aidl::android::os::IStatsCompanionService; +using std::shared_ptr; + +namespace android { +namespace os { +namespace statsd { + +class StatsPuller : public virtual RefBase { +public: + explicit StatsPuller(const int tagId, + const int64_t coolDownNs = NS_PER_SEC, + const int64_t pullTimeoutNs = StatsdStats::kPullMaxDelayNs, + const std::vector additiveFields = std::vector()); + + virtual ~StatsPuller() {} + + // Pulls the most recent data. + // The data may be served from cache if consecutive pulls come within + // predefined cooldown time. + // Returns true if the pull was successful. + // Returns false when + // 1) the pull fails + // 2) pull takes longer than mPullTimeoutNs (intrinsic to puller) + // If a metric wants to make any change to the data, like timestamps, it + // should make a copy as this data may be shared with multiple metrics. + bool Pull(const int64_t eventTimeNs, std::vector>* data); + + // Clear cache immediately + int ForceClearCache(); + + // Clear cache if elapsed time is more than cooldown time + int ClearCacheIfNecessary(int64_t timestampNs); + + static void SetUidMap(const sp& uidMap); + + virtual void SetStatsCompanionService( + shared_ptr statsCompanionService) {}; + +protected: + const int mTagId; + + // Max time allowed to pull this atom. + // We cannot reliably kill a pull thread. So we don't terminate the puller. + // The data is discarded if the pull takes longer than this and mHasGoodData + // marked as false. + const int64_t mPullTimeoutNs = StatsdStats::kPullMaxDelayNs; + +private: + mutable std::mutex mLock; + + // Real puller impl. + virtual bool PullInternal(std::vector>* data) = 0; + + bool mHasGoodData = false; + + // Minimum time before this puller does actual pull again. + // Pullers can cause significant impact to system health and battery. + // So that we don't pull too frequently. + // If a pull request comes before cooldown, a cached version from previous pull + // will be returned. + const int64_t mCoolDownNs = 1 * NS_PER_SEC; + + // The field numbers of the fields that need to be summed when merging + // isolated uid with host uid. + const std::vector mAdditiveFields; + + int64_t mLastPullTimeNs; + + // All pulls happen due to an event (app upgrade, bucket boundary, condition change, etc). + // If multiple pulls need to be done at the same event time, we will always use the cache after + // the first pull. + int64_t mLastEventTimeNs; + + // Cache of data from last pull. If next request comes before cool down finishes, + // cached data will be returned. + // Cached data is cleared when + // 1) A pull fails + // 2) A new pull request comes after cooldown time. + // 3) clearCache is called. + std::vector> mCachedData; + + int clearCache(); + + int clearCacheLocked(); + + static sp mUidMap; +}; + +} // namespace statsd +} // namespace os +} // namespace android diff --git a/statsd/src/external/StatsPullerManager.cpp b/statsd/src/external/StatsPullerManager.cpp new file mode 100644 index 00000000..8334b6b4 --- /dev/null +++ b/statsd/src/external/StatsPullerManager.cpp @@ -0,0 +1,371 @@ +/* + * 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. + */ + +#define DEBUG false +#include "Log.h" + +#include "StatsPullerManager.h" + +#include +#include +#include + +#include +#include + +#include "../StatsService.h" +#include "../logd/LogEvent.h" +#include "../stats_log_util.h" +#include "../statscompanion_util.h" +#include "StatsCallbackPuller.h" +#include "TrainInfoPuller.h" +#include "statslog_statsd.h" + +using std::shared_ptr; +using std::vector; + +namespace android { +namespace os { +namespace statsd { + +// Stores the puller as a wp to avoid holding a reference in case it is unregistered and +// pullAtomCallbackDied is never called. +struct PullAtomCallbackDeathCookie { + PullAtomCallbackDeathCookie(const wp& pullerManager, + const PullerKey& pullerKey, const wp& puller) : + mPullerManager(pullerManager), mPullerKey(pullerKey), mPuller(puller) { + } + + wp mPullerManager; + PullerKey mPullerKey; + wp mPuller; +}; + +void StatsPullerManager::pullAtomCallbackDied(void* cookie) { + PullAtomCallbackDeathCookie* cookie_ = static_cast(cookie); + sp thiz = cookie_->mPullerManager.promote(); + if (!thiz) { + return; + } + + const PullerKey& pullerKey = cookie_->mPullerKey; + wp puller = cookie_->mPuller; + + // Erase the mapping from the puller key to the puller if the mapping still exists. + // Note that we are removing the StatsPuller object, which internally holds the binder + // IPullAtomCallback. However, each new registration creates a new StatsPuller, so this works. + lock_guard lock(thiz->mLock); + const auto& it = thiz->kAllPullAtomInfo.find(pullerKey); + if (it != thiz->kAllPullAtomInfo.end() && puller != nullptr && puller == it->second) { + StatsdStats::getInstance().notePullerCallbackRegistrationChanged(pullerKey.atomTag, + /*registered=*/false); + thiz->kAllPullAtomInfo.erase(pullerKey); + } + // The death recipient corresponding to this specific IPullAtomCallback can never + // be triggered again, so free up resources. + delete cookie_; +} + +// Values smaller than this may require to update the alarm. +const int64_t NO_ALARM_UPDATE = INT64_MAX; + +StatsPullerManager::StatsPullerManager() + : kAllPullAtomInfo({ + // TrainInfo. + {{.atomTag = util::TRAIN_INFO, .uid = AID_STATSD}, new TrainInfoPuller()}, + }), + mNextPullTimeNs(NO_ALARM_UPDATE), + mPullAtomCallbackDeathRecipient(AIBinder_DeathRecipient_new(pullAtomCallbackDied)) { +} + +bool StatsPullerManager::Pull(int tagId, const ConfigKey& configKey, const int64_t eventTimeNs, + vector>* data) { + std::lock_guard _l(mLock); + return PullLocked(tagId, configKey, eventTimeNs, data); +} + +bool StatsPullerManager::Pull(int tagId, const vector& uids, const int64_t eventTimeNs, + vector>* data) { + std::lock_guard _l(mLock); + return PullLocked(tagId, uids, eventTimeNs, data); +} + +bool StatsPullerManager::PullLocked(int tagId, const ConfigKey& configKey, + const int64_t eventTimeNs, vector>* data) { + vector uids; + const auto& uidProviderIt = mPullUidProviders.find(configKey); + if (uidProviderIt == mPullUidProviders.end()) { + ALOGE("Error pulling tag %d. No pull uid provider for config key %s", tagId, + configKey.ToString().c_str()); + StatsdStats::getInstance().notePullUidProviderNotFound(tagId); + return false; + } + sp pullUidProvider = uidProviderIt->second.promote(); + if (pullUidProvider == nullptr) { + ALOGE("Error pulling tag %d, pull uid provider for config %s is gone.", tagId, + configKey.ToString().c_str()); + StatsdStats::getInstance().notePullUidProviderNotFound(tagId); + return false; + } + uids = pullUidProvider->getPullAtomUids(tagId); + return PullLocked(tagId, uids, eventTimeNs, data); +} + +bool StatsPullerManager::PullLocked(int tagId, const vector& uids, + const int64_t eventTimeNs, vector>* data) { + VLOG("Initiating pulling %d", tagId); + for (int32_t uid : uids) { + PullerKey key = {.atomTag = tagId, .uid = uid}; + auto pullerIt = kAllPullAtomInfo.find(key); + if (pullerIt != kAllPullAtomInfo.end()) { + bool ret = pullerIt->second->Pull(eventTimeNs, data); + VLOG("pulled %zu items", data->size()); + if (!ret) { + StatsdStats::getInstance().notePullFailed(tagId); + } + return ret; + } + } + StatsdStats::getInstance().notePullerNotFound(tagId); + ALOGW("StatsPullerManager: Unknown tagId %d", tagId); + return false; // Return early since we don't know what to pull. +} + +bool StatsPullerManager::PullerForMatcherExists(int tagId) const { + // Pulled atoms might be registered after we parse the config, so just make sure the id is in + // an appropriate range. + return isVendorPulledAtom(tagId) || isPulledAtom(tagId); +} + +void StatsPullerManager::updateAlarmLocked() { + if (mNextPullTimeNs == NO_ALARM_UPDATE) { + VLOG("No need to set alarms. Skipping"); + return; + } + + // TODO(b/151045771): do not hold a lock while making a binder call + if (mStatsCompanionService != nullptr) { + mStatsCompanionService->setPullingAlarm(mNextPullTimeNs / 1000000); + } else { + VLOG("StatsCompanionService not available. Alarm not set."); + } + return; +} + +void StatsPullerManager::SetStatsCompanionService( + shared_ptr statsCompanionService) { + std::lock_guard _l(mLock); + shared_ptr tmpForLock = mStatsCompanionService; + mStatsCompanionService = statsCompanionService; + for (const auto& pulledAtom : kAllPullAtomInfo) { + pulledAtom.second->SetStatsCompanionService(statsCompanionService); + } + if (mStatsCompanionService != nullptr) { + updateAlarmLocked(); + } +} + +void StatsPullerManager::RegisterReceiver(int tagId, const ConfigKey& configKey, + wp receiver, int64_t nextPullTimeNs, + int64_t intervalNs) { + std::lock_guard _l(mLock); + auto& receivers = mReceivers[{.atomTag = tagId, .configKey = configKey}]; + for (auto it = receivers.begin(); it != receivers.end(); it++) { + if (it->receiver == receiver) { + VLOG("Receiver already registered of %d", (int)receivers.size()); + return; + } + } + ReceiverInfo receiverInfo; + receiverInfo.receiver = receiver; + + // Round it to the nearest minutes. This is the limit of alarm manager. + // In practice, we should always have larger buckets. + int64_t roundedIntervalNs = intervalNs / NS_PER_SEC / 60 * NS_PER_SEC * 60; + // Scheduled pulling should be at least 1 min apart. + // This can be lower in cts tests, in which case we round it to 1 min. + if (roundedIntervalNs < 60 * (int64_t)NS_PER_SEC) { + roundedIntervalNs = 60 * (int64_t)NS_PER_SEC; + } + + receiverInfo.intervalNs = roundedIntervalNs; + receiverInfo.nextPullTimeNs = nextPullTimeNs; + receivers.push_back(receiverInfo); + + // There is only one alarm for all pulled events. So only set it to the smallest denom. + if (nextPullTimeNs < mNextPullTimeNs) { + VLOG("Updating next pull time %lld", (long long)mNextPullTimeNs); + mNextPullTimeNs = nextPullTimeNs; + updateAlarmLocked(); + } + VLOG("Puller for tagId %d registered of %d", tagId, (int)receivers.size()); +} + +void StatsPullerManager::UnRegisterReceiver(int tagId, const ConfigKey& configKey, + wp receiver) { + std::lock_guard _l(mLock); + auto receiversIt = mReceivers.find({.atomTag = tagId, .configKey = configKey}); + if (receiversIt == mReceivers.end()) { + VLOG("Unknown pull code or no receivers: %d", tagId); + return; + } + std::list& receivers = receiversIt->second; + for (auto it = receivers.begin(); it != receivers.end(); it++) { + if (receiver == it->receiver) { + receivers.erase(it); + VLOG("Puller for tagId %d unregistered of %d", tagId, (int)receivers.size()); + return; + } + } +} + +void StatsPullerManager::RegisterPullUidProvider(const ConfigKey& configKey, + wp provider) { + std::lock_guard _l(mLock); + mPullUidProviders[configKey] = provider; +} + +void StatsPullerManager::UnregisterPullUidProvider(const ConfigKey& configKey, + wp provider) { + std::lock_guard _l(mLock); + const auto& it = mPullUidProviders.find(configKey); + if (it != mPullUidProviders.end() && it->second == provider) { + mPullUidProviders.erase(it); + } +} + +void StatsPullerManager::OnAlarmFired(int64_t elapsedTimeNs) { + std::lock_guard _l(mLock); + int64_t wallClockNs = getWallClockNs(); + + int64_t minNextPullTimeNs = NO_ALARM_UPDATE; + + vector>> needToPull; + for (auto& pair : mReceivers) { + vector receivers; + if (pair.second.size() != 0) { + for (ReceiverInfo& receiverInfo : pair.second) { + if (receiverInfo.nextPullTimeNs <= elapsedTimeNs) { + receivers.push_back(&receiverInfo); + } else { + if (receiverInfo.nextPullTimeNs < minNextPullTimeNs) { + minNextPullTimeNs = receiverInfo.nextPullTimeNs; + } + } + } + if (receivers.size() > 0) { + needToPull.push_back(make_pair(&pair.first, receivers)); + } + } + } + for (const auto& pullInfo : needToPull) { + vector> data; + bool pullSuccess = PullLocked(pullInfo.first->atomTag, pullInfo.first->configKey, + elapsedTimeNs, &data); + if (!pullSuccess) { + VLOG("pull failed at %lld, will try again later", (long long)elapsedTimeNs); + } + + // Convention is to mark pull atom timestamp at request time. + // If we pull at t0, puller starts at t1, finishes at t2, and send back + // at t3, we mark t0 as its timestamp, which should correspond to its + // triggering event, such as condition change at t0. + // Here the triggering event is alarm fired from AlarmManager. + // In ValueMetricProducer and GaugeMetricProducer we do same thing + // when pull on condition change, etc. + for (auto& event : data) { + event->setElapsedTimestampNs(elapsedTimeNs); + event->setLogdWallClockTimestampNs(wallClockNs); + } + + for (const auto& receiverInfo : pullInfo.second) { + sp receiverPtr = receiverInfo->receiver.promote(); + if (receiverPtr != nullptr) { + receiverPtr->onDataPulled(data, pullSuccess, elapsedTimeNs); + // We may have just come out of a coma, compute next pull time. + int numBucketsAhead = + (elapsedTimeNs - receiverInfo->nextPullTimeNs) / receiverInfo->intervalNs; + receiverInfo->nextPullTimeNs += (numBucketsAhead + 1) * receiverInfo->intervalNs; + if (receiverInfo->nextPullTimeNs < minNextPullTimeNs) { + minNextPullTimeNs = receiverInfo->nextPullTimeNs; + } + } else { + VLOG("receiver already gone."); + } + } + } + + VLOG("mNextPullTimeNs: %lld updated to %lld", (long long)mNextPullTimeNs, + (long long)minNextPullTimeNs); + mNextPullTimeNs = minNextPullTimeNs; + updateAlarmLocked(); +} + +int StatsPullerManager::ForceClearPullerCache() { + std::lock_guard _l(mLock); + int totalCleared = 0; + for (const auto& pulledAtom : kAllPullAtomInfo) { + totalCleared += pulledAtom.second->ForceClearCache(); + } + return totalCleared; +} + +int StatsPullerManager::ClearPullerCacheIfNecessary(int64_t timestampNs) { + std::lock_guard _l(mLock); + int totalCleared = 0; + for (const auto& pulledAtom : kAllPullAtomInfo) { + totalCleared += pulledAtom.second->ClearCacheIfNecessary(timestampNs); + } + return totalCleared; +} + +void StatsPullerManager::RegisterPullAtomCallback(const int uid, const int32_t atomTag, + const int64_t coolDownNs, const int64_t timeoutNs, + const vector& additiveFields, + const shared_ptr& callback) { + std::lock_guard _l(mLock); + VLOG("RegisterPullerCallback: adding puller for tag %d", atomTag); + + if (callback == nullptr) { + ALOGW("SetPullAtomCallback called with null callback for atom %d.", atomTag); + return; + } + + StatsdStats::getInstance().notePullerCallbackRegistrationChanged(atomTag, /*registered=*/true); + int64_t actualCoolDownNs = coolDownNs < kMinCoolDownNs ? kMinCoolDownNs : coolDownNs; + int64_t actualTimeoutNs = timeoutNs > kMaxTimeoutNs ? kMaxTimeoutNs : timeoutNs; + + sp puller = new StatsCallbackPuller(atomTag, callback, actualCoolDownNs, + actualTimeoutNs, additiveFields); + PullerKey key = {.atomTag = atomTag, .uid = uid}; + AIBinder_linkToDeath(callback->asBinder().get(), mPullAtomCallbackDeathRecipient.get(), + new PullAtomCallbackDeathCookie(this, key, puller)); + kAllPullAtomInfo[key] = puller; +} + +void StatsPullerManager::UnregisterPullAtomCallback(const int uid, const int32_t atomTag) { + std::lock_guard _l(mLock); + PullerKey key = {.atomTag = atomTag, .uid = uid}; + if (kAllPullAtomInfo.find(key) != kAllPullAtomInfo.end()) { + StatsdStats::getInstance().notePullerCallbackRegistrationChanged(atomTag, + /*registered=*/false); + kAllPullAtomInfo.erase(key); + } +} + +} // namespace statsd +} // namespace os +} // namespace android diff --git a/statsd/src/external/StatsPullerManager.h b/statsd/src/external/StatsPullerManager.h new file mode 100644 index 00000000..7f880324 --- /dev/null +++ b/statsd/src/external/StatsPullerManager.h @@ -0,0 +1,192 @@ +/* + * 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. + */ + +#pragma once + +#include +#include +#include + +#include +#include + +#include "PullDataReceiver.h" +#include "PullUidProvider.h" +#include "StatsPuller.h" +#include "guardrail/StatsdStats.h" +#include "logd/LogEvent.h" +#include "packages/UidMap.h" + +using aidl::android::os::IPullAtomCallback; +using aidl::android::os::IStatsCompanionService; +using std::shared_ptr; + +namespace android { +namespace os { +namespace statsd { + +typedef struct PullerKey { + // The uid of the process that registers this puller. + const int uid = -1; + // The atom that this puller is for. + const int atomTag; + + bool operator<(const PullerKey& that) const { + if (uid < that.uid) { + return true; + } + if (uid > that.uid) { + return false; + } + return atomTag < that.atomTag; + }; + + bool operator==(const PullerKey& that) const { + return uid == that.uid && atomTag == that.atomTag; + }; +} PullerKey; + +class StatsPullerManager : public virtual RefBase { +public: + StatsPullerManager(); + + virtual ~StatsPullerManager() { + } + + + // Registers a receiver for tagId. It will be pulled on the nextPullTimeNs + // and then every intervalNs thereafter. + virtual void RegisterReceiver(int tagId, const ConfigKey& configKey, + wp receiver, int64_t nextPullTimeNs, + int64_t intervalNs); + + // Stop listening on a tagId. + virtual void UnRegisterReceiver(int tagId, const ConfigKey& configKey, + wp receiver); + + // Registers a pull uid provider for the config key. When pulling atoms, it will be used to + // determine which uids to pull from. + virtual void RegisterPullUidProvider(const ConfigKey& configKey, wp provider); + + // Unregister a pull uid provider. + virtual void UnregisterPullUidProvider(const ConfigKey& configKey, + wp provider); + + // Verify if we know how to pull for this matcher + bool PullerForMatcherExists(int tagId) const; + + void OnAlarmFired(int64_t elapsedTimeNs); + + // Pulls the most recent data. + // The data may be served from cache if consecutive pulls come within + // mCoolDownNs. + // Returns true if the pull was successful. + // Returns false when + // 1) the pull fails + // 2) pull takes longer than mPullTimeoutNs (intrinsic to puller) + // 3) Either a PullUidProvider was not registered for the config, or the there was no puller + // registered for any of the uids for this atom. + // If the metric wants to make any change to the data, like timestamps, they + // should make a copy as this data may be shared with multiple metrics. + virtual bool Pull(int tagId, const ConfigKey& configKey, const int64_t eventTimeNs, + vector>* data); + + // Same as above, but directly specify the allowed uids to pull from. + virtual bool Pull(int tagId, const vector& uids, const int64_t eventTimeNs, + vector>* data); + + // Clear pull data cache immediately. + int ForceClearPullerCache(); + + // Clear pull data cache if it is beyond respective cool down time. + int ClearPullerCacheIfNecessary(int64_t timestampNs); + + void SetStatsCompanionService(shared_ptr statsCompanionService); + + void RegisterPullAtomCallback(const int uid, const int32_t atomTag, const int64_t coolDownNs, + const int64_t timeoutNs, const vector& additiveFields, + const shared_ptr& callback); + + void UnregisterPullAtomCallback(const int uid, const int32_t atomTag); + + std::map> kAllPullAtomInfo; + +private: + const static int64_t kMinCoolDownNs = NS_PER_SEC; + const static int64_t kMaxTimeoutNs = 10 * NS_PER_SEC; + shared_ptr mStatsCompanionService = nullptr; + + // A struct containing an atom id and a Config Key + typedef struct ReceiverKey { + const int atomTag; + const ConfigKey configKey; + + inline bool operator<(const ReceiverKey& that) const { + return atomTag == that.atomTag ? configKey < that.configKey : atomTag < that.atomTag; + } + } ReceiverKey; + + typedef struct { + int64_t nextPullTimeNs; + int64_t intervalNs; + wp receiver; + } ReceiverInfo; + + // mapping from Receiver Key to receivers + std::map> mReceivers; + + // mapping from Config Key to the PullUidProvider for that config + std::map> mPullUidProviders; + + bool PullLocked(int tagId, const ConfigKey& configKey, const int64_t eventTimeNs, + vector>* data); + + bool PullLocked(int tagId, const vector& uids, const int64_t eventTimeNs, + vector>* data); + + // locks for data receiver and StatsCompanionService changes + std::mutex mLock; + + void updateAlarmLocked(); + + int64_t mNextPullTimeNs; + + // Death recipient that is triggered when the process holding the IPullAtomCallback has died. + ::ndk::ScopedAIBinder_DeathRecipient mPullAtomCallbackDeathRecipient; + + /** + * Death recipient callback that is called when a pull atom callback dies. + * The cookie is a pointer to a PullAtomCallbackDeathCookie. + */ + static void pullAtomCallbackDied(void* cookie); + + FRIEND_TEST(GaugeMetricE2eTest, TestRandomSamplePulledEvents); + FRIEND_TEST(GaugeMetricE2eTest, TestRandomSamplePulledEvent_LateAlarm); + FRIEND_TEST(GaugeMetricE2eTest, TestRandomSamplePulledEventsWithActivation); + FRIEND_TEST(GaugeMetricE2eTest, TestRandomSamplePulledEventsNoCondition); + FRIEND_TEST(ValueMetricE2eTest, TestPulledEvents); + FRIEND_TEST(ValueMetricE2eTest, TestPulledEvents_LateAlarm); + FRIEND_TEST(ValueMetricE2eTest, TestPulledEvents_WithActivation); + + FRIEND_TEST(StatsLogProcessorTest, TestPullUidProviderSetOnConfigUpdate); + + FRIEND_TEST(ConfigUpdateE2eTest, TestGaugeMetric); + FRIEND_TEST(ConfigUpdateE2eTest, TestValueMetric); +}; + +} // namespace statsd +} // namespace os +} // namespace android diff --git a/statsd/src/external/TrainInfoPuller.cpp b/statsd/src/external/TrainInfoPuller.cpp new file mode 100644 index 00000000..3837f4a1 --- /dev/null +++ b/statsd/src/external/TrainInfoPuller.cpp @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2019 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. + */ + +#define DEBUG false // STOPSHIP if true +#include "Log.h" + +#include "external/StatsPuller.h" + +#include "TrainInfoPuller.h" +#include "logd/LogEvent.h" +#include "stats_log_util.h" +#include "statslog_statsd.h" +#include "storage/StorageManager.h" + +using std::make_shared; +using std::shared_ptr; + +namespace android { +namespace os { +namespace statsd { + +TrainInfoPuller::TrainInfoPuller() : + StatsPuller(util::TRAIN_INFO) { +} + +bool TrainInfoPuller::PullInternal(vector>* data) { + vector trainInfoList = + StorageManager::readAllTrainInfo(); + if (trainInfoList.empty()) { + ALOGW("Train info was empty."); + return true; + } + for (InstallTrainInfo& trainInfo : trainInfoList) { + auto event = make_shared(getWallClockNs(), getElapsedRealtimeNs(), trainInfo); + data->push_back(event); + } + return true; +} + +} // namespace statsd +} // namespace os +} // namespace android diff --git a/statsd/src/external/TrainInfoPuller.h b/statsd/src/external/TrainInfoPuller.h new file mode 100644 index 00000000..615d0235 --- /dev/null +++ b/statsd/src/external/TrainInfoPuller.h @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2019 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. + */ + +#pragma once + +#include "StatsPuller.h" + +namespace android { +namespace os { +namespace statsd { + +/** + * Reads train info from disk. + */ +class TrainInfoPuller : public StatsPuller { + public: + TrainInfoPuller(); + + private: + bool PullInternal(vector>* data) override; +}; + +} // namespace statsd +} // namespace os +} // namespace android diff --git a/statsd/src/external/puller_util.cpp b/statsd/src/external/puller_util.cpp new file mode 100644 index 00000000..aa99d008 --- /dev/null +++ b/statsd/src/external/puller_util.cpp @@ -0,0 +1,153 @@ +/* + * Copyright (C) 2018 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. + */ + +#define DEBUG false // STOPSHIP if true +#include "Log.h" + +#include "puller_util.h" + +namespace android { +namespace os { +namespace statsd { + +using namespace std; + +/** + * Process all data and merge isolated with host if necessary. + * For example: + * NetworkBytesAtom { + * int uid = 1; + * State process_state = 2; + * int byte_send = 3; + * int byte_recv = 4; + * } + * additive fields are {3, 4} + * If we pulled the following events (uid1_child is an isolated uid which maps to uid1): + * [uid1, fg, 100, 200] + * [uid1_child, fg, 100, 200] + * [uid1, bg, 100, 200] + * + * We want to merge them and results should be: + * [uid1, fg, 200, 400] + * [uid1, bg, 100, 200] + * + * All atoms should be of the same tagId. All fields should be present. + */ +void mapAndMergeIsolatedUidsToHostUid(vector>& data, const sp& uidMap, + int tagId, const vector& additiveFieldsVec) { + // Check the first LogEvent for attribution chain or a uid field as either all atoms with this + // tagId have them or none of them do. + std::pair attrIndexRange; + const bool hasAttributionChain = data[0]->hasAttributionChain(&attrIndexRange); + bool hasUidField = (data[0]->getUidFieldIndex() != -1); + + if (!hasAttributionChain && !hasUidField) { + VLOG("No uid or attribution chain to merge, atom %d", tagId); + return; + } + + // 1. Map all isolated uid in-place to host uid + for (shared_ptr& event : data) { + if (event->GetTagId() != tagId) { + ALOGE("Wrong atom. Expecting %d, got %d", tagId, event->GetTagId()); + return; + } + if (hasAttributionChain) { + vector* const fieldValues = event->getMutableValues(); + for (int i = attrIndexRange.first; i <= attrIndexRange.second; i++) { + FieldValue& fieldValue = fieldValues->at(i); + if (isAttributionUidField(fieldValue)) { + const int hostUid = uidMap->getHostUidOrSelf(fieldValue.mValue.int_value); + fieldValue.mValue.setInt(hostUid); + } + } + } else { + int uidFieldIndex = event->getUidFieldIndex(); + if (uidFieldIndex != -1) { + Value& value = (*event->getMutableValues())[uidFieldIndex].mValue; + const int hostUid = uidMap->getHostUidOrSelf(value.int_value); + value.setInt(hostUid); + } else { + ALOGE("Malformed log, uid not found. %s", event->ToString().c_str()); + } + } + } + + // 2. sort the data, bit-wise + sort(data.begin(), data.end(), + [](const shared_ptr& lhs, const shared_ptr& rhs) { + if (lhs->size() != rhs->size()) { + return lhs->size() < rhs->size(); + } + const std::vector& lhsValues = lhs->getValues(); + const std::vector& rhsValues = rhs->getValues(); + for (int i = 0; i < (int)lhs->size(); i++) { + if (lhsValues[i] != rhsValues[i]) { + return lhsValues[i] < rhsValues[i]; + } + } + return false; + }); + + vector> mergedData; + const set additiveFields(additiveFieldsVec.begin(), additiveFieldsVec.end()); + bool needMerge = true; + + // 3. do the merge. + // The loop invariant is this: for every event, check if it differs on + // non-additive fields, or have different attribution chain length. + // If so, no need to merge, add itself to the result. + // Otherwise, merge the value onto the one immediately next to it. + for (int i = 0; i < (int)data.size() - 1; i++) { + // Size different, must be different chains. + if (data[i]->size() != data[i + 1]->size()) { + mergedData.push_back(data[i]); + continue; + } + vector* lhsValues = data[i]->getMutableValues(); + vector* rhsValues = data[i + 1]->getMutableValues(); + needMerge = true; + for (int p = 0; p < (int)lhsValues->size(); p++) { + if ((*lhsValues)[p] != (*rhsValues)[p]) { + int pos = (*lhsValues)[p].mField.getPosAtDepth(0); + // Differ on non-additive field, abort. + if (additiveFields.find(pos) == additiveFields.end()) { + needMerge = false; + break; + } + } + } + if (!needMerge) { + mergedData.push_back(data[i]); + continue; + } + // This should be infrequent operation. + for (int p = 0; p < (int)lhsValues->size(); p++) { + int pos = (*lhsValues)[p].mField.getPosAtDepth(0); + if (additiveFields.find(pos) != additiveFields.end()) { + (*rhsValues)[p].mValue += (*lhsValues)[p].mValue; + } + } + } + mergedData.push_back(data.back()); + + data.clear(); + data = mergedData; +} + +} // namespace statsd +} // namespace os +} // namespace android diff --git a/statsd/src/external/puller_util.h b/statsd/src/external/puller_util.h new file mode 100644 index 00000000..afcf68ca --- /dev/null +++ b/statsd/src/external/puller_util.h @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2018 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. + */ + +#pragma once + +#include +#include "StatsPuller.h" +#include "logd/LogEvent.h" +#include "packages/UidMap.h" + +namespace android { +namespace os { +namespace statsd { + +void mapAndMergeIsolatedUidsToHostUid(std::vector>& data, + const sp& uidMap, int tagId, + const vector& additiveFieldsVec); + +} // namespace statsd +} // namespace os +} // namespace android diff --git a/statsd/src/flags/flags.cpp b/statsd/src/flags/flags.cpp new file mode 100644 index 00000000..e9fceda7 --- /dev/null +++ b/statsd/src/flags/flags.cpp @@ -0,0 +1,37 @@ +/* + * 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 "flags.h" + +#include + +using server_configurable_flags::GetServerConfigurableFlag; +using std::string; + +namespace android { +namespace os { +namespace statsd { + +string getFlagString(const string& flagName, const string& defaultValue) { + return GetServerConfigurableFlag(STATSD_NATIVE_NAMESPACE, flagName, defaultValue); +} + +bool getFlagBool(const string& flagName, const string& defaultValue) { + return getFlagString(flagName, defaultValue) == "true"; +} +} // namespace statsd +} // namespace os +} // namespace android diff --git a/statsd/src/flags/flags.h b/statsd/src/flags/flags.h new file mode 100644 index 00000000..213e1a48 --- /dev/null +++ b/statsd/src/flags/flags.h @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2020 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. + */ + +#pragma once + +#include + +namespace android { +namespace os { +namespace statsd { + +const std::string STATSD_NATIVE_NAMESPACE = "statsd_native"; + +const std::string PARTIAL_CONFIG_UPDATE_FLAG = "partial_config_update"; + +std::string getFlagString(const std::string& flagName, const std::string& defaultValue); + +// Returns true IFF flagName has a value of "true". +bool getFlagBool(const std::string& flagName, const std::string& defaultValue); + +} // namespace statsd +} // namespace os +} // namespace android diff --git a/statsd/src/guardrail/StatsdStats.cpp b/statsd/src/guardrail/StatsdStats.cpp new file mode 100644 index 00000000..f4e01ced --- /dev/null +++ b/statsd/src/guardrail/StatsdStats.cpp @@ -0,0 +1,1110 @@ +/* + * Copyright 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. + */ +#define DEBUG false // STOPSHIP if true +#include "Log.h" + +#include "StatsdStats.h" + +#include +#include "../stats_log_util.h" +#include "statslog_statsd.h" +#include "storage/StorageManager.h" + +namespace android { +namespace os { +namespace statsd { + +using android::util::FIELD_COUNT_REPEATED; +using android::util::FIELD_TYPE_BOOL; +using android::util::FIELD_TYPE_FLOAT; +using android::util::FIELD_TYPE_INT32; +using android::util::FIELD_TYPE_INT64; +using android::util::FIELD_TYPE_MESSAGE; +using android::util::FIELD_TYPE_STRING; +using android::util::ProtoOutputStream; +using std::lock_guard; +using std::shared_ptr; +using std::string; +using std::to_string; +using std::vector; + +const int FIELD_ID_BEGIN_TIME = 1; +const int FIELD_ID_END_TIME = 2; +const int FIELD_ID_CONFIG_STATS = 3; +const int FIELD_ID_ATOM_STATS = 7; +const int FIELD_ID_UIDMAP_STATS = 8; +const int FIELD_ID_ANOMALY_ALARM_STATS = 9; +const int FIELD_ID_PERIODIC_ALARM_STATS = 12; +const int FIELD_ID_SYSTEM_SERVER_RESTART = 15; +const int FIELD_ID_LOGGER_ERROR_STATS = 16; +const int FIELD_ID_OVERFLOW = 18; +const int FIELD_ID_ACTIVATION_BROADCAST_GUARDRAIL = 19; + +const int FIELD_ID_ATOM_STATS_TAG = 1; +const int FIELD_ID_ATOM_STATS_COUNT = 2; +const int FIELD_ID_ATOM_STATS_ERROR_COUNT = 3; + +const int FIELD_ID_ANOMALY_ALARMS_REGISTERED = 1; +const int FIELD_ID_PERIODIC_ALARMS_REGISTERED = 1; + +const int FIELD_ID_LOG_LOSS_STATS_TIME = 1; +const int FIELD_ID_LOG_LOSS_STATS_COUNT = 2; +const int FIELD_ID_LOG_LOSS_STATS_ERROR = 3; +const int FIELD_ID_LOG_LOSS_STATS_TAG = 4; +const int FIELD_ID_LOG_LOSS_STATS_UID = 5; +const int FIELD_ID_LOG_LOSS_STATS_PID = 6; + +const int FIELD_ID_OVERFLOW_COUNT = 1; +const int FIELD_ID_OVERFLOW_MAX_HISTORY = 2; +const int FIELD_ID_OVERFLOW_MIN_HISTORY = 3; + +const int FIELD_ID_CONFIG_STATS_UID = 1; +const int FIELD_ID_CONFIG_STATS_ID = 2; +const int FIELD_ID_CONFIG_STATS_CREATION = 3; +const int FIELD_ID_CONFIG_STATS_RESET = 19; +const int FIELD_ID_CONFIG_STATS_DELETION = 4; +const int FIELD_ID_CONFIG_STATS_METRIC_COUNT = 5; +const int FIELD_ID_CONFIG_STATS_CONDITION_COUNT = 6; +const int FIELD_ID_CONFIG_STATS_MATCHER_COUNT = 7; +const int FIELD_ID_CONFIG_STATS_ALERT_COUNT = 8; +const int FIELD_ID_CONFIG_STATS_VALID = 9; +const int FIELD_ID_CONFIG_STATS_BROADCAST = 10; +const int FIELD_ID_CONFIG_STATS_DATA_DROP_TIME = 11; +const int FIELD_ID_CONFIG_STATS_DATA_DROP_BYTES = 21; +const int FIELD_ID_CONFIG_STATS_DUMP_REPORT_TIME = 12; +const int FIELD_ID_CONFIG_STATS_DUMP_REPORT_BYTES = 20; +const int FIELD_ID_CONFIG_STATS_MATCHER_STATS = 13; +const int FIELD_ID_CONFIG_STATS_CONDITION_STATS = 14; +const int FIELD_ID_CONFIG_STATS_METRIC_STATS = 15; +const int FIELD_ID_CONFIG_STATS_ALERT_STATS = 16; +const int FIELD_ID_CONFIG_STATS_METRIC_DIMENSION_IN_CONDITION_STATS = 17; +const int FIELD_ID_CONFIG_STATS_ANNOTATION = 18; +const int FIELD_ID_CONFIG_STATS_ACTIVATION = 22; +const int FIELD_ID_CONFIG_STATS_DEACTIVATION = 23; +const int FIELD_ID_CONFIG_STATS_ANNOTATION_INT64 = 1; +const int FIELD_ID_CONFIG_STATS_ANNOTATION_INT32 = 2; + +const int FIELD_ID_MATCHER_STATS_ID = 1; +const int FIELD_ID_MATCHER_STATS_COUNT = 2; +const int FIELD_ID_CONDITION_STATS_ID = 1; +const int FIELD_ID_CONDITION_STATS_COUNT = 2; +const int FIELD_ID_METRIC_STATS_ID = 1; +const int FIELD_ID_METRIC_STATS_COUNT = 2; +const int FIELD_ID_ALERT_STATS_ID = 1; +const int FIELD_ID_ALERT_STATS_COUNT = 2; + +const int FIELD_ID_UID_MAP_CHANGES = 1; +const int FIELD_ID_UID_MAP_BYTES_USED = 2; +const int FIELD_ID_UID_MAP_DROPPED_CHANGES = 3; +const int FIELD_ID_UID_MAP_DELETED_APPS = 4; + +const int FIELD_ID_ACTIVATION_BROADCAST_GUARDRAIL_UID = 1; +const int FIELD_ID_ACTIVATION_BROADCAST_GUARDRAIL_TIME = 2; + +const std::map> StatsdStats::kAtomDimensionKeySizeLimitMap = { + {util::BINDER_CALLS, {6000, 10000}}, + {util::LOOPER_STATS, {1500, 2500}}, + {util::CPU_TIME_PER_UID_FREQ, {6000, 10000}}, +}; + +StatsdStats::StatsdStats() { + mPushedAtomStats.resize(kMaxPushedAtomId + 1); + mStartTimeSec = getWallClockSec(); +} + +StatsdStats& StatsdStats::getInstance() { + static StatsdStats statsInstance; + return statsInstance; +} + +void StatsdStats::addToIceBoxLocked(shared_ptr& stats) { + // The size of mIceBox grows strictly by one at a time. It won't be > kMaxIceBoxSize. + if (mIceBox.size() == kMaxIceBoxSize) { + mIceBox.pop_front(); + } + mIceBox.push_back(stats); +} + +void StatsdStats::noteConfigReceived( + const ConfigKey& key, int metricsCount, int conditionsCount, int matchersCount, + int alertsCount, const std::list>& annotations, + bool isValid) { + lock_guard lock(mLock); + int32_t nowTimeSec = getWallClockSec(); + + // If there is an existing config for the same key, icebox the old config. + noteConfigRemovedInternalLocked(key); + + shared_ptr configStats = std::make_shared(); + configStats->uid = key.GetUid(); + configStats->id = key.GetId(); + configStats->creation_time_sec = nowTimeSec; + configStats->metric_count = metricsCount; + configStats->condition_count = conditionsCount; + configStats->matcher_count = matchersCount; + configStats->alert_count = alertsCount; + configStats->is_valid = isValid; + for (auto& v : annotations) { + configStats->annotations.emplace_back(v); + } + + if (isValid) { + mConfigStats[key] = configStats; + } else { + configStats->deletion_time_sec = nowTimeSec; + addToIceBoxLocked(configStats); + } +} + +void StatsdStats::noteConfigRemovedInternalLocked(const ConfigKey& key) { + auto it = mConfigStats.find(key); + if (it != mConfigStats.end()) { + int32_t nowTimeSec = getWallClockSec(); + it->second->deletion_time_sec = nowTimeSec; + addToIceBoxLocked(it->second); + mConfigStats.erase(it); + } +} + +void StatsdStats::noteConfigRemoved(const ConfigKey& key) { + lock_guard lock(mLock); + noteConfigRemovedInternalLocked(key); +} + +void StatsdStats::noteConfigResetInternalLocked(const ConfigKey& key) { + auto it = mConfigStats.find(key); + if (it != mConfigStats.end()) { + it->second->reset_time_sec = getWallClockSec(); + } +} + +void StatsdStats::noteConfigReset(const ConfigKey& key) { + lock_guard lock(mLock); + noteConfigResetInternalLocked(key); +} + +void StatsdStats::noteLogLost(int32_t wallClockTimeSec, int32_t count, int32_t lastError, + int32_t lastTag, int32_t uid, int32_t pid) { + lock_guard lock(mLock); + if (mLogLossStats.size() == kMaxLoggerErrors) { + mLogLossStats.pop_front(); + } + mLogLossStats.emplace_back(wallClockTimeSec, count, lastError, lastTag, uid, pid); +} + +void StatsdStats::noteBroadcastSent(const ConfigKey& key) { + noteBroadcastSent(key, getWallClockSec()); +} + +void StatsdStats::noteBroadcastSent(const ConfigKey& key, int32_t timeSec) { + lock_guard lock(mLock); + auto it = mConfigStats.find(key); + if (it == mConfigStats.end()) { + ALOGE("Config key %s not found!", key.ToString().c_str()); + return; + } + if (it->second->broadcast_sent_time_sec.size() == kMaxTimestampCount) { + it->second->broadcast_sent_time_sec.pop_front(); + } + it->second->broadcast_sent_time_sec.push_back(timeSec); +} + +void StatsdStats::noteActiveStatusChanged(const ConfigKey& key, bool activated) { + noteActiveStatusChanged(key, activated, getWallClockSec()); +} + +void StatsdStats::noteActiveStatusChanged(const ConfigKey& key, bool activated, int32_t timeSec) { + lock_guard lock(mLock); + auto it = mConfigStats.find(key); + if (it == mConfigStats.end()) { + ALOGE("Config key %s not found!", key.ToString().c_str()); + return; + } + auto& vec = activated ? it->second->activation_time_sec + : it->second->deactivation_time_sec; + if (vec.size() == kMaxTimestampCount) { + vec.pop_front(); + } + vec.push_back(timeSec); +} + +void StatsdStats::noteActivationBroadcastGuardrailHit(const int uid) { + noteActivationBroadcastGuardrailHit(uid, getWallClockSec()); +} + +void StatsdStats::noteActivationBroadcastGuardrailHit(const int uid, const int32_t timeSec) { + lock_guard lock(mLock); + auto& guardrailTimes = mActivationBroadcastGuardrailStats[uid]; + if (guardrailTimes.size() == kMaxTimestampCount) { + guardrailTimes.pop_front(); + } + guardrailTimes.push_back(timeSec); +} + +void StatsdStats::noteDataDropped(const ConfigKey& key, const size_t totalBytes) { + noteDataDropped(key, totalBytes, getWallClockSec()); +} + +void StatsdStats::noteEventQueueOverflow(int64_t oldestEventTimestampNs) { + lock_guard lock(mLock); + + mOverflowCount++; + + int64_t history = getElapsedRealtimeNs() - oldestEventTimestampNs; + + if (history > mMaxQueueHistoryNs) { + mMaxQueueHistoryNs = history; + } + + if (history < mMinQueueHistoryNs) { + mMinQueueHistoryNs = history; + } +} + +void StatsdStats::noteDataDropped(const ConfigKey& key, const size_t totalBytes, int32_t timeSec) { + lock_guard lock(mLock); + auto it = mConfigStats.find(key); + if (it == mConfigStats.end()) { + ALOGE("Config key %s not found!", key.ToString().c_str()); + return; + } + if (it->second->data_drop_time_sec.size() == kMaxTimestampCount) { + it->second->data_drop_time_sec.pop_front(); + it->second->data_drop_bytes.pop_front(); + } + it->second->data_drop_time_sec.push_back(timeSec); + it->second->data_drop_bytes.push_back(totalBytes); +} + +void StatsdStats::noteMetricsReportSent(const ConfigKey& key, const size_t num_bytes) { + noteMetricsReportSent(key, num_bytes, getWallClockSec()); +} + +void StatsdStats::noteMetricsReportSent(const ConfigKey& key, const size_t num_bytes, + int32_t timeSec) { + lock_guard lock(mLock); + auto it = mConfigStats.find(key); + if (it == mConfigStats.end()) { + ALOGE("Config key %s not found!", key.ToString().c_str()); + return; + } + if (it->second->dump_report_stats.size() == kMaxTimestampCount) { + it->second->dump_report_stats.pop_front(); + } + it->second->dump_report_stats.push_back(std::make_pair(timeSec, num_bytes)); +} + +void StatsdStats::noteUidMapDropped(int deltas) { + lock_guard lock(mLock); + mUidMapStats.dropped_changes += mUidMapStats.dropped_changes + deltas; +} + +void StatsdStats::noteUidMapAppDeletionDropped() { + lock_guard lock(mLock); + mUidMapStats.deleted_apps++; +} + +void StatsdStats::setUidMapChanges(int changes) { + lock_guard lock(mLock); + mUidMapStats.changes = changes; +} + +void StatsdStats::setCurrentUidMapMemory(int bytes) { + lock_guard lock(mLock); + mUidMapStats.bytes_used = bytes; +} + +void StatsdStats::noteConditionDimensionSize(const ConfigKey& key, const int64_t& id, int size) { + lock_guard lock(mLock); + // if name doesn't exist before, it will create the key with count 0. + auto statsIt = mConfigStats.find(key); + if (statsIt == mConfigStats.end()) { + return; + } + + auto& conditionSizeMap = statsIt->second->condition_stats; + if (size > conditionSizeMap[id]) { + conditionSizeMap[id] = size; + } +} + +void StatsdStats::noteMetricDimensionSize(const ConfigKey& key, const int64_t& id, int size) { + lock_guard lock(mLock); + // if name doesn't exist before, it will create the key with count 0. + auto statsIt = mConfigStats.find(key); + if (statsIt == mConfigStats.end()) { + return; + } + auto& metricsDimensionMap = statsIt->second->metric_stats; + if (size > metricsDimensionMap[id]) { + metricsDimensionMap[id] = size; + } +} + +void StatsdStats::noteMetricDimensionInConditionSize( + const ConfigKey& key, const int64_t& id, int size) { + lock_guard lock(mLock); + // if name doesn't exist before, it will create the key with count 0. + auto statsIt = mConfigStats.find(key); + if (statsIt == mConfigStats.end()) { + return; + } + auto& metricsDimensionMap = statsIt->second->metric_dimension_in_condition_stats; + if (size > metricsDimensionMap[id]) { + metricsDimensionMap[id] = size; + } +} + +void StatsdStats::noteMatcherMatched(const ConfigKey& key, const int64_t& id) { + lock_guard lock(mLock); + + auto statsIt = mConfigStats.find(key); + if (statsIt == mConfigStats.end()) { + return; + } + statsIt->second->matcher_stats[id]++; +} + +void StatsdStats::noteAnomalyDeclared(const ConfigKey& key, const int64_t& id) { + lock_guard lock(mLock); + auto statsIt = mConfigStats.find(key); + if (statsIt == mConfigStats.end()) { + return; + } + statsIt->second->alert_stats[id]++; +} + +void StatsdStats::noteRegisteredAnomalyAlarmChanged() { + lock_guard lock(mLock); + mAnomalyAlarmRegisteredStats++; +} + +void StatsdStats::noteRegisteredPeriodicAlarmChanged() { + lock_guard lock(mLock); + mPeriodicAlarmRegisteredStats++; +} + +void StatsdStats::updateMinPullIntervalSec(int pullAtomId, long intervalSec) { + lock_guard lock(mLock); + mPulledAtomStats[pullAtomId].minPullIntervalSec = + std::min(mPulledAtomStats[pullAtomId].minPullIntervalSec, intervalSec); +} + +void StatsdStats::notePull(int pullAtomId) { + lock_guard lock(mLock); + mPulledAtomStats[pullAtomId].totalPull++; +} + +void StatsdStats::notePullFromCache(int pullAtomId) { + lock_guard lock(mLock); + mPulledAtomStats[pullAtomId].totalPullFromCache++; +} + +void StatsdStats::notePullTime(int pullAtomId, int64_t pullTimeNs) { + lock_guard lock(mLock); + auto& pullStats = mPulledAtomStats[pullAtomId]; + pullStats.maxPullTimeNs = std::max(pullStats.maxPullTimeNs, pullTimeNs); + pullStats.avgPullTimeNs = (pullStats.avgPullTimeNs * pullStats.numPullTime + pullTimeNs) / + (pullStats.numPullTime + 1); + pullStats.numPullTime += 1; +} + +void StatsdStats::notePullDelay(int pullAtomId, int64_t pullDelayNs) { + lock_guard lock(mLock); + auto& pullStats = mPulledAtomStats[pullAtomId]; + pullStats.maxPullDelayNs = std::max(pullStats.maxPullDelayNs, pullDelayNs); + pullStats.avgPullDelayNs = + (pullStats.avgPullDelayNs * pullStats.numPullDelay + pullDelayNs) / + (pullStats.numPullDelay + 1); + pullStats.numPullDelay += 1; +} + +void StatsdStats::notePullDataError(int pullAtomId) { + lock_guard lock(mLock); + mPulledAtomStats[pullAtomId].dataError++; +} + +void StatsdStats::notePullTimeout(int pullAtomId, + int64_t pullUptimeMillis, + int64_t pullElapsedMillis) { + lock_guard lock(mLock); + PulledAtomStats& pulledAtomStats = mPulledAtomStats[pullAtomId]; + pulledAtomStats.pullTimeout++; + + if (pulledAtomStats.pullTimeoutMetadata.size() == kMaxTimestampCount) { + pulledAtomStats.pullTimeoutMetadata.pop_front(); + } + + pulledAtomStats.pullTimeoutMetadata.emplace_back(pullUptimeMillis, pullElapsedMillis); +} + +void StatsdStats::notePullExceedMaxDelay(int pullAtomId) { + lock_guard lock(mLock); + mPulledAtomStats[pullAtomId].pullExceedMaxDelay++; +} + +void StatsdStats::noteAtomLogged(int atomId, int32_t timeSec) { + lock_guard lock(mLock); + + if (atomId <= kMaxPushedAtomId) { + mPushedAtomStats[atomId]++; + } else { + if (mNonPlatformPushedAtomStats.size() < kMaxNonPlatformPushedAtoms) { + mNonPlatformPushedAtomStats[atomId]++; + } + } +} + +void StatsdStats::noteSystemServerRestart(int32_t timeSec) { + lock_guard lock(mLock); + + if (mSystemServerRestartSec.size() == kMaxSystemServerRestarts) { + mSystemServerRestartSec.pop_front(); + } + mSystemServerRestartSec.push_back(timeSec); +} + +void StatsdStats::notePullFailed(int atomId) { + lock_guard lock(mLock); + mPulledAtomStats[atomId].pullFailed++; +} + +void StatsdStats::notePullUidProviderNotFound(int atomId) { + lock_guard lock(mLock); + mPulledAtomStats[atomId].pullUidProviderNotFound++; +} + +void StatsdStats::notePullerNotFound(int atomId) { + lock_guard lock(mLock); + mPulledAtomStats[atomId].pullerNotFound++; +} + +void StatsdStats::notePullBinderCallFailed(int atomId) { + lock_guard lock(mLock); + mPulledAtomStats[atomId].binderCallFailCount++; +} + +void StatsdStats::noteEmptyData(int atomId) { + lock_guard lock(mLock); + mPulledAtomStats[atomId].emptyData++; +} + +void StatsdStats::notePullerCallbackRegistrationChanged(int atomId, bool registered) { + lock_guard lock(mLock); + if (registered) { + mPulledAtomStats[atomId].registeredCount++; + } else { + mPulledAtomStats[atomId].unregisteredCount++; + } +} + +void StatsdStats::noteHardDimensionLimitReached(int64_t metricId) { + lock_guard lock(mLock); + getAtomMetricStats(metricId).hardDimensionLimitReached++; +} + +void StatsdStats::noteLateLogEventSkipped(int64_t metricId) { + lock_guard lock(mLock); + getAtomMetricStats(metricId).lateLogEventSkipped++; +} + +void StatsdStats::noteLateLogEvent(int64_t metricId, int64_t extraDurationNs) { + lock_guard lock(mLock); + AtomMetricStats& metricStats = getAtomMetricStats(metricId); + metricStats.lateLogEvent++; + metricStats.sumLateLogEventExtraDurationNs += extraDurationNs; + metricStats.maxLateLogEventExtraDurationNs = + std::max(metricStats.maxLateLogEventExtraDurationNs, extraDurationNs); +} + +void StatsdStats::noteSkippedForwardBuckets(int64_t metricId) { + lock_guard lock(mLock); + getAtomMetricStats(metricId).skippedForwardBuckets++; +} + +void StatsdStats::noteBadValueType(int64_t metricId) { + lock_guard lock(mLock); + getAtomMetricStats(metricId).badValueType++; +} + +void StatsdStats::noteBucketDropped(int64_t metricId) { + lock_guard lock(mLock); + getAtomMetricStats(metricId).bucketDropped++; +} + +void StatsdStats::noteBucketUnknownCondition(int64_t metricId) { + lock_guard lock(mLock); + getAtomMetricStats(metricId).bucketUnknownCondition++; +} + +void StatsdStats::noteConditionChangeInNextBucket(int64_t metricId) { + lock_guard lock(mLock); + getAtomMetricStats(metricId).conditionChangeInNextBucket++; +} + +void StatsdStats::noteInvalidatedBucket(int64_t metricId) { + lock_guard lock(mLock); + getAtomMetricStats(metricId).invalidatedBucket++; +} + +void StatsdStats::noteBucketCount(int64_t metricId) { + lock_guard lock(mLock); + getAtomMetricStats(metricId).bucketCount++; +} + +void StatsdStats::noteBucketBoundaryDelayNs(int64_t metricId, int64_t timeDelayNs) { + lock_guard lock(mLock); + AtomMetricStats& metricStats = getAtomMetricStats(metricId); + metricStats.maxBucketBoundaryDelayNs = + std::max(metricStats.maxBucketBoundaryDelayNs, timeDelayNs); + metricStats.minBucketBoundaryDelayNs = + std::min(metricStats.minBucketBoundaryDelayNs, timeDelayNs); +} + +void StatsdStats::noteAtomError(int atomTag, bool pull) { + lock_guard lock(mLock); + if (pull) { + mPulledAtomStats[atomTag].atomErrorCount++; + return; + } + + bool present = (mPushedAtomErrorStats.find(atomTag) != mPushedAtomErrorStats.end()); + bool full = (mPushedAtomErrorStats.size() >= (size_t)kMaxPushedAtomErrorStatsSize); + if (!full || present) { + mPushedAtomErrorStats[atomTag]++; + } +} + +StatsdStats::AtomMetricStats& StatsdStats::getAtomMetricStats(int64_t metricId) { + auto atomMetricStatsIter = mAtomMetricStats.find(metricId); + if (atomMetricStatsIter != mAtomMetricStats.end()) { + return atomMetricStatsIter->second; + } + auto emplaceResult = mAtomMetricStats.emplace(metricId, AtomMetricStats()); + return emplaceResult.first->second; +} + +void StatsdStats::reset() { + lock_guard lock(mLock); + resetInternalLocked(); +} + +void StatsdStats::resetInternalLocked() { + // Reset the historical data, but keep the active ConfigStats + mStartTimeSec = getWallClockSec(); + mIceBox.clear(); + std::fill(mPushedAtomStats.begin(), mPushedAtomStats.end(), 0); + mNonPlatformPushedAtomStats.clear(); + mAnomalyAlarmRegisteredStats = 0; + mPeriodicAlarmRegisteredStats = 0; + mSystemServerRestartSec.clear(); + mLogLossStats.clear(); + mOverflowCount = 0; + mMinQueueHistoryNs = kInt64Max; + mMaxQueueHistoryNs = 0; + for (auto& config : mConfigStats) { + config.second->broadcast_sent_time_sec.clear(); + config.second->activation_time_sec.clear(); + config.second->deactivation_time_sec.clear(); + config.second->data_drop_time_sec.clear(); + config.second->data_drop_bytes.clear(); + config.second->dump_report_stats.clear(); + config.second->annotations.clear(); + config.second->matcher_stats.clear(); + config.second->condition_stats.clear(); + config.second->metric_stats.clear(); + config.second->metric_dimension_in_condition_stats.clear(); + config.second->alert_stats.clear(); + } + for (auto& pullStats : mPulledAtomStats) { + pullStats.second.totalPull = 0; + pullStats.second.totalPullFromCache = 0; + pullStats.second.minPullIntervalSec = LONG_MAX; + pullStats.second.avgPullTimeNs = 0; + pullStats.second.maxPullTimeNs = 0; + pullStats.second.numPullTime = 0; + pullStats.second.avgPullDelayNs = 0; + pullStats.second.maxPullDelayNs = 0; + pullStats.second.numPullDelay = 0; + pullStats.second.dataError = 0; + pullStats.second.pullTimeout = 0; + pullStats.second.pullExceedMaxDelay = 0; + pullStats.second.pullFailed = 0; + pullStats.second.pullUidProviderNotFound = 0; + pullStats.second.pullerNotFound = 0; + pullStats.second.registeredCount = 0; + pullStats.second.unregisteredCount = 0; + pullStats.second.atomErrorCount = 0; + pullStats.second.binderCallFailCount = 0; + pullStats.second.pullTimeoutMetadata.clear(); + } + mAtomMetricStats.clear(); + mActivationBroadcastGuardrailStats.clear(); + mPushedAtomErrorStats.clear(); +} + +string buildTimeString(int64_t timeSec) { + time_t t = timeSec; + struct tm* tm = localtime(&t); + char timeBuffer[80]; + strftime(timeBuffer, sizeof(timeBuffer), "%Y-%m-%d %I:%M%p", tm); + return string(timeBuffer); +} + +int StatsdStats::getPushedAtomErrors(int atomId) const { + const auto& it = mPushedAtomErrorStats.find(atomId); + if (it != mPushedAtomErrorStats.end()) { + return it->second; + } else { + return 0; + } +} + +void StatsdStats::dumpStats(int out) const { + lock_guard lock(mLock); + time_t t = mStartTimeSec; + struct tm* tm = localtime(&t); + char timeBuffer[80]; + strftime(timeBuffer, sizeof(timeBuffer), "%Y-%m-%d %I:%M%p\n", tm); + dprintf(out, "Stats collection start second: %s\n", timeBuffer); + dprintf(out, "%lu Config in icebox: \n", (unsigned long)mIceBox.size()); + for (const auto& configStats : mIceBox) { + dprintf(out, + "Config {%d_%lld}: creation=%d, deletion=%d, reset=%d, #metric=%d, #condition=%d, " + "#matcher=%d, #alert=%d, valid=%d\n", + configStats->uid, (long long)configStats->id, configStats->creation_time_sec, + configStats->deletion_time_sec, configStats->reset_time_sec, + configStats->metric_count, configStats->condition_count, configStats->matcher_count, + configStats->alert_count, configStats->is_valid); + + for (const auto& broadcastTime : configStats->broadcast_sent_time_sec) { + dprintf(out, "\tbroadcast time: %d\n", broadcastTime); + } + + for (const int& activationTime : configStats->activation_time_sec) { + dprintf(out, "\tactivation time: %d\n", activationTime); + } + + for (const int& deactivationTime : configStats->deactivation_time_sec) { + dprintf(out, "\tdeactivation time: %d\n", deactivationTime); + } + + auto dropTimePtr = configStats->data_drop_time_sec.begin(); + auto dropBytesPtr = configStats->data_drop_bytes.begin(); + for (int i = 0; i < (int)configStats->data_drop_time_sec.size(); + i++, dropTimePtr++, dropBytesPtr++) { + dprintf(out, "\tdata drop time: %d with size %lld", *dropTimePtr, + (long long)*dropBytesPtr); + } + } + dprintf(out, "%lu Active Configs\n", (unsigned long)mConfigStats.size()); + for (auto& pair : mConfigStats) { + auto& configStats = pair.second; + dprintf(out, + "Config {%d-%lld}: creation=%d, deletion=%d, #metric=%d, #condition=%d, " + "#matcher=%d, #alert=%d, valid=%d\n", + configStats->uid, (long long)configStats->id, configStats->creation_time_sec, + configStats->deletion_time_sec, configStats->metric_count, + configStats->condition_count, configStats->matcher_count, configStats->alert_count, + configStats->is_valid); + for (const auto& annotation : configStats->annotations) { + dprintf(out, "\tannotation: %lld, %d\n", (long long)annotation.first, + annotation.second); + } + + for (const auto& broadcastTime : configStats->broadcast_sent_time_sec) { + dprintf(out, "\tbroadcast time: %s(%lld)\n", buildTimeString(broadcastTime).c_str(), + (long long)broadcastTime); + } + + for (const int& activationTime : configStats->activation_time_sec) { + dprintf(out, "\tactivation time: %d\n", activationTime); + } + + for (const int& deactivationTime : configStats->deactivation_time_sec) { + dprintf(out, "\tdeactivation time: %d\n", deactivationTime); + } + + auto dropTimePtr = configStats->data_drop_time_sec.begin(); + auto dropBytesPtr = configStats->data_drop_bytes.begin(); + for (int i = 0; i < (int)configStats->data_drop_time_sec.size(); + i++, dropTimePtr++, dropBytesPtr++) { + dprintf(out, "\tdata drop time: %s(%lld) with %lld bytes\n", + buildTimeString(*dropTimePtr).c_str(), (long long)*dropTimePtr, + (long long)*dropBytesPtr); + } + + for (const auto& dump : configStats->dump_report_stats) { + dprintf(out, "\tdump report time: %s(%lld) bytes: %lld\n", + buildTimeString(dump.first).c_str(), (long long)dump.first, + (long long)dump.second); + } + + for (const auto& stats : pair.second->matcher_stats) { + dprintf(out, "matcher %lld matched %d times\n", (long long)stats.first, stats.second); + } + + for (const auto& stats : pair.second->condition_stats) { + dprintf(out, "condition %lld max output tuple size %d\n", (long long)stats.first, + stats.second); + } + + for (const auto& stats : pair.second->condition_stats) { + dprintf(out, "metrics %lld max output tuple size %d\n", (long long)stats.first, + stats.second); + } + + for (const auto& stats : pair.second->alert_stats) { + dprintf(out, "alert %lld declared %d times\n", (long long)stats.first, stats.second); + } + } + dprintf(out, "********Disk Usage stats***********\n"); + StorageManager::printStats(out); + dprintf(out, "********Pushed Atom stats***********\n"); + const size_t atomCounts = mPushedAtomStats.size(); + for (size_t i = 2; i < atomCounts; i++) { + if (mPushedAtomStats[i] > 0) { + dprintf(out, "Atom %zu->(total count)%d, (error count)%d\n", i, mPushedAtomStats[i], + getPushedAtomErrors((int)i)); + } + } + for (const auto& pair : mNonPlatformPushedAtomStats) { + dprintf(out, "Atom %d->(total count)%d, (error count)%d\n", pair.first, pair.second, + getPushedAtomErrors(pair.first)); + } + + dprintf(out, "********Pulled Atom stats***********\n"); + for (const auto& pair : mPulledAtomStats) { + dprintf(out, + "Atom %d->(total pull)%ld, (pull from cache)%ld, " + "(pull failed)%ld, (min pull interval)%ld \n" + " (average pull time nanos)%lld, (max pull time nanos)%lld, (average pull delay " + "nanos)%lld, " + " (max pull delay nanos)%lld, (data error)%ld\n" + " (pull timeout)%ld, (pull exceed max delay)%ld" + " (no uid provider count)%ld, (no puller found count)%ld\n" + " (registered count) %ld, (unregistered count) %ld" + " (atom error count) %d\n", + (int)pair.first, (long)pair.second.totalPull, (long)pair.second.totalPullFromCache, + (long)pair.second.pullFailed, (long)pair.second.minPullIntervalSec, + (long long)pair.second.avgPullTimeNs, (long long)pair.second.maxPullTimeNs, + (long long)pair.second.avgPullDelayNs, (long long)pair.second.maxPullDelayNs, + pair.second.dataError, pair.second.pullTimeout, pair.second.pullExceedMaxDelay, + pair.second.pullUidProviderNotFound, pair.second.pullerNotFound, + pair.second.registeredCount, pair.second.unregisteredCount, + pair.second.atomErrorCount); + if (pair.second.pullTimeoutMetadata.size() > 0) { + string uptimeMillis = "(pull timeout system uptime millis) "; + string pullTimeoutMillis = "(pull timeout elapsed time millis) "; + for (const auto& stats : pair.second.pullTimeoutMetadata) { + uptimeMillis.append(to_string(stats.pullTimeoutUptimeMillis)).append(",");; + pullTimeoutMillis.append(to_string(stats.pullTimeoutElapsedMillis)).append(","); + } + uptimeMillis.pop_back(); + uptimeMillis.push_back('\n'); + pullTimeoutMillis.pop_back(); + pullTimeoutMillis.push_back('\n'); + dprintf(out, "%s", uptimeMillis.c_str()); + dprintf(out, "%s", pullTimeoutMillis.c_str()); + } + } + + if (mAnomalyAlarmRegisteredStats > 0) { + dprintf(out, "********AnomalyAlarmStats stats***********\n"); + dprintf(out, "Anomaly alarm registrations: %d\n", mAnomalyAlarmRegisteredStats); + } + + if (mPeriodicAlarmRegisteredStats > 0) { + dprintf(out, "********SubscriberAlarmStats stats***********\n"); + dprintf(out, "Subscriber alarm registrations: %d\n", mPeriodicAlarmRegisteredStats); + } + + dprintf(out, "UID map stats: bytes=%d, changes=%d, deleted=%d, changes lost=%d\n", + mUidMapStats.bytes_used, mUidMapStats.changes, mUidMapStats.deleted_apps, + mUidMapStats.dropped_changes); + + for (const auto& restart : mSystemServerRestartSec) { + dprintf(out, "System server restarts at %s(%lld)\n", buildTimeString(restart).c_str(), + (long long)restart); + } + + for (const auto& loss : mLogLossStats) { + dprintf(out, + "Log loss: %lld (wall clock sec) - %d (count), %d (last error), %d (last tag), %d " + "(uid), %d (pid)\n", + (long long)loss.mWallClockSec, loss.mCount, loss.mLastError, loss.mLastTag, + loss.mUid, loss.mPid); + } + + dprintf(out, "Event queue overflow: %d; MaxHistoryNs: %lld; MinHistoryNs: %lld\n", + mOverflowCount, (long long)mMaxQueueHistoryNs, (long long)mMinQueueHistoryNs); + + if (mActivationBroadcastGuardrailStats.size() > 0) { + dprintf(out, "********mActivationBroadcastGuardrail stats***********\n"); + for (const auto& pair: mActivationBroadcastGuardrailStats) { + dprintf(out, "Uid %d: Times: ", pair.first); + for (const auto& guardrailHitTime : pair.second) { + dprintf(out, "%d ", guardrailHitTime); + } + } + dprintf(out, "\n"); + } +} + +void addConfigStatsToProto(const ConfigStats& configStats, ProtoOutputStream* proto) { + uint64_t token = + proto->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_CONFIG_STATS); + proto->write(FIELD_TYPE_INT32 | FIELD_ID_CONFIG_STATS_UID, configStats.uid); + proto->write(FIELD_TYPE_INT64 | FIELD_ID_CONFIG_STATS_ID, (long long)configStats.id); + proto->write(FIELD_TYPE_INT32 | FIELD_ID_CONFIG_STATS_CREATION, configStats.creation_time_sec); + if (configStats.reset_time_sec != 0) { + proto->write(FIELD_TYPE_INT32 | FIELD_ID_CONFIG_STATS_RESET, configStats.reset_time_sec); + } + if (configStats.deletion_time_sec != 0) { + proto->write(FIELD_TYPE_INT32 | FIELD_ID_CONFIG_STATS_DELETION, + configStats.deletion_time_sec); + } + proto->write(FIELD_TYPE_INT32 | FIELD_ID_CONFIG_STATS_METRIC_COUNT, configStats.metric_count); + proto->write(FIELD_TYPE_INT32 | FIELD_ID_CONFIG_STATS_CONDITION_COUNT, + configStats.condition_count); + proto->write(FIELD_TYPE_INT32 | FIELD_ID_CONFIG_STATS_MATCHER_COUNT, configStats.matcher_count); + proto->write(FIELD_TYPE_INT32 | FIELD_ID_CONFIG_STATS_ALERT_COUNT, configStats.alert_count); + proto->write(FIELD_TYPE_BOOL | FIELD_ID_CONFIG_STATS_VALID, configStats.is_valid); + + for (const auto& broadcast : configStats.broadcast_sent_time_sec) { + proto->write(FIELD_TYPE_INT32 | FIELD_ID_CONFIG_STATS_BROADCAST | FIELD_COUNT_REPEATED, + broadcast); + } + + for (const auto& activation : configStats.activation_time_sec) { + proto->write(FIELD_TYPE_INT32 | FIELD_ID_CONFIG_STATS_ACTIVATION | FIELD_COUNT_REPEATED, + activation); + } + + for (const auto& deactivation : configStats.deactivation_time_sec) { + proto->write(FIELD_TYPE_INT32 | FIELD_ID_CONFIG_STATS_DEACTIVATION | FIELD_COUNT_REPEATED, + deactivation); + } + + for (const auto& drop_time : configStats.data_drop_time_sec) { + proto->write(FIELD_TYPE_INT32 | FIELD_ID_CONFIG_STATS_DATA_DROP_TIME | FIELD_COUNT_REPEATED, + drop_time); + } + + for (const auto& drop_bytes : configStats.data_drop_bytes) { + proto->write( + FIELD_TYPE_INT64 | FIELD_ID_CONFIG_STATS_DATA_DROP_BYTES | FIELD_COUNT_REPEATED, + (long long)drop_bytes); + } + + for (const auto& dump : configStats.dump_report_stats) { + proto->write(FIELD_TYPE_INT32 | FIELD_ID_CONFIG_STATS_DUMP_REPORT_TIME | + FIELD_COUNT_REPEATED, + dump.first); + } + + for (const auto& dump : configStats.dump_report_stats) { + proto->write(FIELD_TYPE_INT64 | FIELD_ID_CONFIG_STATS_DUMP_REPORT_BYTES | + FIELD_COUNT_REPEATED, + (long long)dump.second); + } + + for (const auto& annotation : configStats.annotations) { + uint64_t token = proto->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | + FIELD_ID_CONFIG_STATS_ANNOTATION); + proto->write(FIELD_TYPE_INT64 | FIELD_ID_CONFIG_STATS_ANNOTATION_INT64, + (long long)annotation.first); + proto->write(FIELD_TYPE_INT32 | FIELD_ID_CONFIG_STATS_ANNOTATION_INT32, annotation.second); + proto->end(token); + } + + for (const auto& pair : configStats.matcher_stats) { + uint64_t tmpToken = proto->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | + FIELD_ID_CONFIG_STATS_MATCHER_STATS); + proto->write(FIELD_TYPE_INT64 | FIELD_ID_MATCHER_STATS_ID, (long long)pair.first); + proto->write(FIELD_TYPE_INT32 | FIELD_ID_MATCHER_STATS_COUNT, pair.second); + proto->end(tmpToken); + } + + for (const auto& pair : configStats.condition_stats) { + uint64_t tmpToken = proto->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | + FIELD_ID_CONFIG_STATS_CONDITION_STATS); + proto->write(FIELD_TYPE_INT64 | FIELD_ID_CONDITION_STATS_ID, (long long)pair.first); + proto->write(FIELD_TYPE_INT32 | FIELD_ID_CONDITION_STATS_COUNT, pair.second); + proto->end(tmpToken); + } + + for (const auto& pair : configStats.metric_stats) { + uint64_t tmpToken = proto->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | + FIELD_ID_CONFIG_STATS_METRIC_STATS); + proto->write(FIELD_TYPE_INT64 | FIELD_ID_METRIC_STATS_ID, (long long)pair.first); + proto->write(FIELD_TYPE_INT32 | FIELD_ID_METRIC_STATS_COUNT, pair.second); + proto->end(tmpToken); + } + for (const auto& pair : configStats.metric_dimension_in_condition_stats) { + uint64_t tmpToken = proto->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | + FIELD_ID_CONFIG_STATS_METRIC_DIMENSION_IN_CONDITION_STATS); + proto->write(FIELD_TYPE_INT64 | FIELD_ID_METRIC_STATS_ID, (long long)pair.first); + proto->write(FIELD_TYPE_INT32 | FIELD_ID_METRIC_STATS_COUNT, pair.second); + proto->end(tmpToken); + } + + for (const auto& pair : configStats.alert_stats) { + uint64_t tmpToken = proto->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | + FIELD_ID_CONFIG_STATS_ALERT_STATS); + proto->write(FIELD_TYPE_INT64 | FIELD_ID_ALERT_STATS_ID, (long long)pair.first); + proto->write(FIELD_TYPE_INT32 | FIELD_ID_ALERT_STATS_COUNT, pair.second); + proto->end(tmpToken); + } + + proto->end(token); +} + +void StatsdStats::dumpStats(std::vector* output, bool reset) { + lock_guard lock(mLock); + + ProtoOutputStream proto; + proto.write(FIELD_TYPE_INT32 | FIELD_ID_BEGIN_TIME, mStartTimeSec); + proto.write(FIELD_TYPE_INT32 | FIELD_ID_END_TIME, (int32_t)getWallClockSec()); + + for (const auto& configStats : mIceBox) { + addConfigStatsToProto(*configStats, &proto); + } + + for (auto& pair : mConfigStats) { + addConfigStatsToProto(*(pair.second), &proto); + } + + const size_t atomCounts = mPushedAtomStats.size(); + for (size_t i = 2; i < atomCounts; i++) { + if (mPushedAtomStats[i] > 0) { + uint64_t token = + proto.start(FIELD_TYPE_MESSAGE | FIELD_ID_ATOM_STATS | FIELD_COUNT_REPEATED); + proto.write(FIELD_TYPE_INT32 | FIELD_ID_ATOM_STATS_TAG, (int32_t)i); + proto.write(FIELD_TYPE_INT32 | FIELD_ID_ATOM_STATS_COUNT, mPushedAtomStats[i]); + int errors = getPushedAtomErrors(i); + if (errors > 0) { + proto.write(FIELD_TYPE_INT32 | FIELD_ID_ATOM_STATS_ERROR_COUNT, errors); + } + proto.end(token); + } + } + + for (const auto& pair : mNonPlatformPushedAtomStats) { + uint64_t token = + proto.start(FIELD_TYPE_MESSAGE | FIELD_ID_ATOM_STATS | FIELD_COUNT_REPEATED); + proto.write(FIELD_TYPE_INT32 | FIELD_ID_ATOM_STATS_TAG, pair.first); + proto.write(FIELD_TYPE_INT32 | FIELD_ID_ATOM_STATS_COUNT, pair.second); + int errors = getPushedAtomErrors(pair.first); + if (errors > 0) { + proto.write(FIELD_TYPE_INT32 | FIELD_ID_ATOM_STATS_ERROR_COUNT, errors); + } + proto.end(token); + } + + for (const auto& pair : mPulledAtomStats) { + android::os::statsd::writePullerStatsToStream(pair, &proto); + } + + for (const auto& pair : mAtomMetricStats) { + android::os::statsd::writeAtomMetricStatsToStream(pair, &proto); + } + + if (mAnomalyAlarmRegisteredStats > 0) { + uint64_t token = proto.start(FIELD_TYPE_MESSAGE | FIELD_ID_ANOMALY_ALARM_STATS); + proto.write(FIELD_TYPE_INT32 | FIELD_ID_ANOMALY_ALARMS_REGISTERED, + mAnomalyAlarmRegisteredStats); + proto.end(token); + } + + if (mPeriodicAlarmRegisteredStats > 0) { + uint64_t token = proto.start(FIELD_TYPE_MESSAGE | FIELD_ID_PERIODIC_ALARM_STATS); + proto.write(FIELD_TYPE_INT32 | FIELD_ID_PERIODIC_ALARMS_REGISTERED, + mPeriodicAlarmRegisteredStats); + proto.end(token); + } + + uint64_t uidMapToken = proto.start(FIELD_TYPE_MESSAGE | FIELD_ID_UIDMAP_STATS); + proto.write(FIELD_TYPE_INT32 | FIELD_ID_UID_MAP_CHANGES, mUidMapStats.changes); + proto.write(FIELD_TYPE_INT32 | FIELD_ID_UID_MAP_BYTES_USED, mUidMapStats.bytes_used); + proto.write(FIELD_TYPE_INT32 | FIELD_ID_UID_MAP_DROPPED_CHANGES, mUidMapStats.dropped_changes); + proto.write(FIELD_TYPE_INT32 | FIELD_ID_UID_MAP_DELETED_APPS, mUidMapStats.deleted_apps); + proto.end(uidMapToken); + + for (const auto& error : mLogLossStats) { + uint64_t token = proto.start(FIELD_TYPE_MESSAGE | FIELD_ID_LOGGER_ERROR_STATS | + FIELD_COUNT_REPEATED); + proto.write(FIELD_TYPE_INT32 | FIELD_ID_LOG_LOSS_STATS_TIME, error.mWallClockSec); + proto.write(FIELD_TYPE_INT32 | FIELD_ID_LOG_LOSS_STATS_COUNT, error.mCount); + proto.write(FIELD_TYPE_INT32 | FIELD_ID_LOG_LOSS_STATS_ERROR, error.mLastError); + proto.write(FIELD_TYPE_INT32 | FIELD_ID_LOG_LOSS_STATS_TAG, error.mLastTag); + proto.write(FIELD_TYPE_INT32 | FIELD_ID_LOG_LOSS_STATS_UID, error.mUid); + proto.write(FIELD_TYPE_INT32 | FIELD_ID_LOG_LOSS_STATS_PID, error.mPid); + proto.end(token); + } + + if (mOverflowCount > 0) { + uint64_t token = proto.start(FIELD_TYPE_MESSAGE | FIELD_ID_OVERFLOW); + proto.write(FIELD_TYPE_INT32 | FIELD_ID_OVERFLOW_COUNT, (int32_t)mOverflowCount); + proto.write(FIELD_TYPE_INT64 | FIELD_ID_OVERFLOW_MAX_HISTORY, + (long long)mMaxQueueHistoryNs); + proto.write(FIELD_TYPE_INT64 | FIELD_ID_OVERFLOW_MIN_HISTORY, + (long long)mMinQueueHistoryNs); + proto.end(token); + } + + for (const auto& restart : mSystemServerRestartSec) { + proto.write(FIELD_TYPE_INT32 | FIELD_ID_SYSTEM_SERVER_RESTART | FIELD_COUNT_REPEATED, + restart); + } + + for (const auto& pair: mActivationBroadcastGuardrailStats) { + uint64_t token = proto.start(FIELD_TYPE_MESSAGE | + FIELD_ID_ACTIVATION_BROADCAST_GUARDRAIL | + FIELD_COUNT_REPEATED); + proto.write(FIELD_TYPE_INT32 | FIELD_ID_ACTIVATION_BROADCAST_GUARDRAIL_UID, + (int32_t) pair.first); + for (const auto& guardrailHitTime : pair.second) { + proto.write(FIELD_TYPE_INT32 | FIELD_ID_ACTIVATION_BROADCAST_GUARDRAIL_TIME | + FIELD_COUNT_REPEATED, + guardrailHitTime); + } + proto.end(token); + } + + output->clear(); + size_t bufferSize = proto.size(); + output->resize(bufferSize); + + size_t pos = 0; + sp reader = proto.data(); + while (reader->readBuffer() != NULL) { + size_t toRead = reader->currentToRead(); + std::memcpy(&((*output)[pos]), reader->readBuffer(), toRead); + pos += toRead; + reader->move(toRead); + } + + if (reset) { + resetInternalLocked(); + } + + VLOG("reset=%d, returned proto size %lu", reset, (unsigned long)bufferSize); +} + +} // namespace statsd +} // namespace os +} // namespace android diff --git a/statsd/src/guardrail/StatsdStats.h b/statsd/src/guardrail/StatsdStats.h new file mode 100644 index 00000000..40cfa3ac --- /dev/null +++ b/statsd/src/guardrail/StatsdStats.h @@ -0,0 +1,686 @@ +/* + * Copyright 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. + */ +#pragma once + +#include "config/ConfigKey.h" + +#include +#include +#include +#include +#include +#include +#include + +namespace android { +namespace os { +namespace statsd { + +struct ConfigStats { + int32_t uid; + int64_t id; + int32_t creation_time_sec; + int32_t deletion_time_sec = 0; + int32_t reset_time_sec = 0; + int32_t metric_count; + int32_t condition_count; + int32_t matcher_count; + int32_t alert_count; + bool is_valid; + + std::list broadcast_sent_time_sec; + + // Times at which this config is activated. + std::list activation_time_sec; + + // Times at which this config is deactivated. + std::list deactivation_time_sec; + + std::list data_drop_time_sec; + // Number of bytes dropped at corresponding time. + std::list data_drop_bytes; + std::list> dump_report_stats; + + // Stores how many times a matcher have been matched. The map size is capped by kMaxConfigCount. + std::map matcher_stats; + + // Stores the number of output tuple of condition trackers when it's bigger than + // kDimensionKeySizeSoftLimit. When you see the number is kDimensionKeySizeHardLimit +1, + // it means some data has been dropped. The map size is capped by kMaxConfigCount. + std::map condition_stats; + + // Stores the number of output tuple of metric producers when it's bigger than + // kDimensionKeySizeSoftLimit. When you see the number is kDimensionKeySizeHardLimit +1, + // it means some data has been dropped. The map size is capped by kMaxConfigCount. + std::map metric_stats; + + // Stores the max number of output tuple of dimensions in condition across dimensions in what + // when it's bigger than kDimensionKeySizeSoftLimit. When you see the number is + // kDimensionKeySizeHardLimit +1, it means some data has been dropped. The map size is capped by + // kMaxConfigCount. + std::map metric_dimension_in_condition_stats; + + // Stores the number of times an anomaly detection alert has been declared. + // The map size is capped by kMaxConfigCount. + std::map alert_stats; + + // Stores the config ID for each sub-config used. + std::list> annotations; +}; + +struct UidMapStats { + int32_t changes; + int32_t bytes_used; + int32_t dropped_changes; + int32_t deleted_apps = 0; +}; + +// Keeps track of stats of statsd. +// Single instance shared across the process. All public methods are thread safe. +class StatsdStats { +public: + static StatsdStats& getInstance(); + ~StatsdStats(){}; + + const static int kDimensionKeySizeSoftLimit = 500; + const static int kDimensionKeySizeHardLimit = 800; + + // Per atom dimension key size limit + static const std::map> kAtomDimensionKeySizeLimitMap; + + const static int kMaxConfigCountPerUid = 20; + const static int kMaxAlertCountPerConfig = 100; + const static int kMaxConditionCountPerConfig = 300; + const static int kMaxMetricCountPerConfig = 1000; + const static int kMaxMatcherCountPerConfig = 1200; + + // The max number of old config stats we keep. + const static int kMaxIceBoxSize = 20; + + const static int kMaxLoggerErrors = 20; + + const static int kMaxSystemServerRestarts = 20; + + const static int kMaxTimestampCount = 20; + + const static int kMaxLogSourceCount = 150; + + const static int kMaxPullAtomPackages = 100; + + // Max memory allowed for storing metrics per configuration. If this limit is exceeded, statsd + // drops the metrics data in memory. + static const size_t kMaxMetricsBytesPerConfig = 2 * 1024 * 1024; + + // Soft memory limit per configuration. Once this limit is exceeded, we begin notifying the + // data subscriber that it's time to call getData. + static const size_t kBytesPerConfigTriggerGetData = 192 * 1024; + + // Cap the UID map's memory usage to this. This should be fairly high since the UID information + // is critical for understanding the metrics. + const static size_t kMaxBytesUsedUidMap = 50 * 1024; + + // The number of deleted apps that are stored in the uid map. + const static int kMaxDeletedAppsInUidMap = 100; + + /* Minimum period between two broadcasts in nanoseconds. */ + static const int64_t kMinBroadcastPeriodNs = 60 * NS_PER_SEC; + + /* Min period between two checks of byte size per config key in nanoseconds. */ + static const int64_t kMinByteSizeCheckPeriodNs = 60 * NS_PER_SEC; + + /* Minimum period between two activation broadcasts in nanoseconds. */ + static const int64_t kMinActivationBroadcastPeriodNs = 10 * NS_PER_SEC; + + // Maximum age (30 days) that files on disk can exist in seconds. + static const int kMaxAgeSecond = 60 * 60 * 24 * 30; + + // Maximum age (2 days) that local history files on disk can exist in seconds. + static const int kMaxLocalHistoryAgeSecond = 60 * 60 * 24 * 2; + + // Maximum number of files (1000) that can be in stats directory on disk. + static const int kMaxFileNumber = 1000; + + // Maximum size of all files that can be written to stats directory on disk. + static const int kMaxFileSize = 50 * 1024 * 1024; + + // How long to try to clear puller cache from last time + static const long kPullerCacheClearIntervalSec = 1; + + // Max time to do a pull. + static const int64_t kPullMaxDelayNs = 30 * NS_PER_SEC; + + // Maximum number of pushed atoms statsd stats will track above kMaxPushedAtomId. + static const int kMaxNonPlatformPushedAtoms = 400; + + // Maximum atom id value that we consider a platform pushed atom. + // This should be updated once highest pushed atom id in atoms.proto approaches this value. + static const int kMaxPushedAtomId = 500; + + // Atom id that is the start of the pulled atoms. + static const int kPullAtomStartTag = 10000; + + // Atom id that is the start of vendor atoms. + static const int kVendorAtomStartTag = 100000; + + // Vendor pulled atom start id. + static const int32_t kVendorPulledAtomStartTag = 150000; + + // Beginning of range for timestamp truncation. + static const int32_t kTimestampTruncationStartTag = 300000; + + // End of range for timestamp truncation. + static const int32_t kTimestampTruncationEndTag = 304999; + + // Max accepted atom id. + static const int32_t kMaxAtomTag = 200000; + + static const int64_t kInt64Max = 0x7fffffffffffffffLL; + + static const int32_t kMaxLoggedBucketDropEvents = 10; + + /** + * Report a new config has been received and report the static stats about the config. + * + * The static stats include: the count of metrics, conditions, matchers, and alerts. + * If the config is not valid, this config stats will be put into icebox immediately. + */ + void noteConfigReceived(const ConfigKey& key, int metricsCount, int conditionsCount, + int matchersCount, int alertCount, + const std::list>& annotations, + bool isValid); + /** + * Report a config has been removed. + */ + void noteConfigRemoved(const ConfigKey& key); + /** + * Report a config has been reset when ttl expires. + */ + void noteConfigReset(const ConfigKey& key); + + /** + * Report a broadcast has been sent to a config owner to collect the data. + */ + void noteBroadcastSent(const ConfigKey& key); + + /** + * Report that a config has become activated or deactivated. + * This can be different from whether or not a broadcast is sent if the + * guardrail prevented the broadcast from being sent. + */ + void noteActiveStatusChanged(const ConfigKey& key, bool activate); + + /** + * Report a config's metrics data has been dropped. + */ + void noteDataDropped(const ConfigKey& key, const size_t totalBytes); + + /** + * Report metrics data report has been sent. + * + * The report may be requested via StatsManager API, or through adb cmd. + */ + void noteMetricsReportSent(const ConfigKey& key, const size_t num_bytes); + + /** + * Report the size of output tuple of a condition. + * + * Note: only report when the condition has an output dimension, and the tuple + * count > kDimensionKeySizeSoftLimit. + * + * [key]: The config key that this condition belongs to. + * [id]: The id of the condition. + * [size]: The output tuple size. + */ + void noteConditionDimensionSize(const ConfigKey& key, const int64_t& id, int size); + + /** + * Report the size of output tuple of a metric. + * + * Note: only report when the metric has an output dimension, and the tuple + * count > kDimensionKeySizeSoftLimit. + * + * [key]: The config key that this metric belongs to. + * [id]: The id of the metric. + * [size]: The output tuple size. + */ + void noteMetricDimensionSize(const ConfigKey& key, const int64_t& id, int size); + + /** + * Report the max size of output tuple of dimension in condition across dimensions in what. + * + * Note: only report when the metric has an output dimension in condition, and the max tuple + * count > kDimensionKeySizeSoftLimit. + * + * [key]: The config key that this metric belongs to. + * [id]: The id of the metric. + * [size]: The output tuple size. + */ + void noteMetricDimensionInConditionSize(const ConfigKey& key, const int64_t& id, int size); + + /** + * Report a matcher has been matched. + * + * [key]: The config key that this matcher belongs to. + * [id]: The id of the matcher. + */ + void noteMatcherMatched(const ConfigKey& key, const int64_t& id); + + /** + * Report that an anomaly detection alert has been declared. + * + * [key]: The config key that this alert belongs to. + * [id]: The id of the alert. + */ + void noteAnomalyDeclared(const ConfigKey& key, const int64_t& id); + + /** + * Report an atom event has been logged. + */ + void noteAtomLogged(int atomId, int32_t timeSec); + + /** + * Report that statsd modified the anomaly alarm registered with StatsCompanionService. + */ + void noteRegisteredAnomalyAlarmChanged(); + + /** + * Report that statsd modified the periodic alarm registered with StatsCompanionService. + */ + void noteRegisteredPeriodicAlarmChanged(); + + /** + * Records the number of delta entries that are being dropped from the uid map. + */ + void noteUidMapDropped(int deltas); + + /** + * Records that an app was deleted (from statsd's map). + */ + void noteUidMapAppDeletionDropped(); + + /** + * Updates the number of changes currently stored in the uid map. + */ + void setUidMapChanges(int changes); + void setCurrentUidMapMemory(int bytes); + + /* + * Updates minimum interval between pulls for an pulled atom. + */ + void updateMinPullIntervalSec(int pullAtomId, long intervalSec); + + /* + * Notes an atom is pulled. + */ + void notePull(int pullAtomId); + + /* + * Notes an atom is served from puller cache. + */ + void notePullFromCache(int pullAtomId); + + /* + * Notify data error for pulled atom. + */ + void notePullDataError(int pullAtomId); + + /* + * Records time for actual pulling, not including those served from cache and not including + * statsd processing delays. + */ + void notePullTime(int pullAtomId, int64_t pullTimeNs); + + /* + * Records pull delay for a pulled atom, including those served from cache and including statsd + * processing delays. + */ + void notePullDelay(int pullAtomId, int64_t pullDelayNs); + + /* + * Records pull exceeds timeout for the puller. + */ + void notePullTimeout(int pullAtomId, int64_t pullUptimeMillis, int64_t pullElapsedMillis); + + /* + * Records pull exceeds max delay for a metric. + */ + void notePullExceedMaxDelay(int pullAtomId); + + /* + * Records when system server restarts. + */ + void noteSystemServerRestart(int32_t timeSec); + + /** + * Records statsd skipped an event. + */ + void noteLogLost(int32_t wallClockTimeSec, int32_t count, int32_t lastError, + int32_t lastAtomTag, int32_t uid, int32_t pid); + + /** + * Records that the pull of an atom has failed. Eg, if the client indicated the pull failed, if + * the pull timed out, or if the outgoing binder call failed. + * This count will only increment if the puller was actually invoked. + * + * It does not include a pull not occurring due to not finding the appropriate + * puller. These cases are covered in other counts. + */ + void notePullFailed(int atomId); + + /** + * Records that the pull of an atom has failed due to not having a uid provider. + */ + void notePullUidProviderNotFound(int atomId); + + /** + * Records that the pull of an atom has failed due not finding a puller registered by a + * trusted uid. + */ + void notePullerNotFound(int atomId); + + /** + * Records that the pull has failed due to the outgoing binder call failing. + */ + void notePullBinderCallFailed(int atomId); + + /** + * A pull with no data occurred + */ + void noteEmptyData(int atomId); + + /** + * Records that a puller callback for the given atomId was registered or unregistered. + * + * @param registered True if the callback was registered, false if was unregistered. + */ + void notePullerCallbackRegistrationChanged(int atomId, bool registered); + + /** + * Hard limit was reached in the cardinality of an atom + */ + void noteHardDimensionLimitReached(int64_t metricId); + + /** + * A log event was too late, arrived in the wrong bucket and was skipped + */ + void noteLateLogEventSkipped(int64_t metricId); + + /** + * A log event was too late, arrived in the wrong bucket. + */ + void noteLateLogEvent(int64_t metricId, int64_t extraDurationNs); + + /** + * Buckets were skipped as time elapsed without any data for them + */ + void noteSkippedForwardBuckets(int64_t metricId); + + /** + * An unsupported value type was received + */ + void noteBadValueType(int64_t metricId); + + /** + * Buckets were dropped due to reclaim memory. + */ + void noteBucketDropped(int64_t metricId); + + /** + * A condition change was too late, arrived in the wrong bucket and was skipped + */ + void noteConditionChangeInNextBucket(int64_t metricId); + + /** + * A bucket has been tagged as invalid. + */ + void noteInvalidatedBucket(int64_t metricId); + + /** + * Tracks the total number of buckets (include skipped/invalid buckets). + */ + void noteBucketCount(int64_t metricId); + + /** + * For pulls at bucket boundaries, it represents the misalignment between the real timestamp and + * the end of the bucket. + */ + void noteBucketBoundaryDelayNs(int64_t metricId, int64_t timeDelayNs); + + /** + * Number of buckets with unknown condition. + */ + void noteBucketUnknownCondition(int64_t metricId); + + /* Reports one event has been dropped due to queue overflow, and the oldest event timestamp in + * the queue */ + void noteEventQueueOverflow(int64_t oldestEventTimestampNs); + + /** + * Reports that the activation broadcast guardrail was hit for this uid. Namely, the broadcast + * should have been sent, but instead was skipped due to hitting the guardrail. + */ + void noteActivationBroadcastGuardrailHit(const int uid); + + /** + * Reports that an atom is erroneous or cannot be parsed successfully by + * statsd. An atom tag of 0 indicates that the client did not supply the + * atom id within the encoding. + * + * For pushed atoms only, this call should be preceded by a call to + * noteAtomLogged. + */ + void noteAtomError(int atomTag, bool pull=false); + + /** + * Reset the historical stats. Including all stats in icebox, and the tracked stats about + * metrics, matchers, and atoms. The active configs will be kept and StatsdStats will continue + * to collect stats after reset() has been called. + */ + void reset(); + + /** + * Output the stats in protobuf binary format to [buffer]. + * + * [reset]: whether to clear the historical stats after the call. + */ + void dumpStats(std::vector* buffer, bool reset); + + /** + * Output statsd stats in human readable format to [out] file descriptor. + */ + void dumpStats(int outFd) const; + + typedef struct PullTimeoutMetadata { + int64_t pullTimeoutUptimeMillis; + int64_t pullTimeoutElapsedMillis; + PullTimeoutMetadata(int64_t uptimeMillis, int64_t elapsedMillis) : + pullTimeoutUptimeMillis(uptimeMillis), + pullTimeoutElapsedMillis(elapsedMillis) {/* do nothing */} + } PullTimeoutMetadata; + + typedef struct { + long totalPull = 0; + long totalPullFromCache = 0; + long minPullIntervalSec = LONG_MAX; + int64_t avgPullTimeNs = 0; + int64_t maxPullTimeNs = 0; + long numPullTime = 0; + int64_t avgPullDelayNs = 0; + int64_t maxPullDelayNs = 0; + long numPullDelay = 0; + long dataError = 0; + long pullTimeout = 0; + long pullExceedMaxDelay = 0; + long pullFailed = 0; + long pullUidProviderNotFound = 0; + long pullerNotFound = 0; + long emptyData = 0; + long registeredCount = 0; + long unregisteredCount = 0; + int32_t atomErrorCount = 0; + long binderCallFailCount = 0; + std::list pullTimeoutMetadata; + } PulledAtomStats; + + typedef struct { + long hardDimensionLimitReached = 0; + long lateLogEventSkipped = 0; + long skippedForwardBuckets = 0; + long badValueType = 0; + long conditionChangeInNextBucket = 0; + long invalidatedBucket = 0; + long bucketDropped = 0; + int64_t minBucketBoundaryDelayNs = 0; + int64_t maxBucketBoundaryDelayNs = 0; + long bucketUnknownCondition = 0; + long bucketCount = 0; + long lateLogEvent = 0; + int64_t sumLateLogEventExtraDurationNs = 0; + int64_t maxLateLogEventExtraDurationNs = 0; + } AtomMetricStats; + +private: + StatsdStats(); + + mutable std::mutex mLock; + + int32_t mStartTimeSec; + + // Track the number of dropped entries used by the uid map. + UidMapStats mUidMapStats; + + // The stats about the configs that are still in use. + // The map size is capped by kMaxConfigCount. + std::map> mConfigStats; + + // Stores the stats for the configs that are no longer in use. + // The size of the vector is capped by kMaxIceBoxSize. + std::list> mIceBox; + + // Stores the number of times a pushed atom is logged. + // The size of the vector is the largest pushed atom id in atoms.proto + 1. Atoms + // out of that range will be put in mNonPlatformPushedAtomStats. + // This is a vector, not a map because it will be accessed A LOT -- for each stats log. + std::vector mPushedAtomStats; + + // Stores the number of times a pushed atom is logged for atom ids above kMaxPushedAtomId. + // The max size of the map is kMaxNonPlatformPushedAtoms. + std::unordered_map mNonPlatformPushedAtomStats; + + // Maps PullAtomId to its stats. The size is capped by the puller atom counts. + std::map mPulledAtomStats; + + // Stores the number of times a pushed atom was logged erroneously. The + // corresponding counts for pulled atoms are stored in PulledAtomStats. + // The max size of this map is kMaxAtomErrorsStatsSize. + std::map mPushedAtomErrorStats; + int kMaxPushedAtomErrorStatsSize = 100; + + // Maps metric ID to its stats. The size is capped by the number of metrics. + std::map mAtomMetricStats; + + // Maps uids to times when the activation changed broadcast not sent due to hitting the + // guardrail. The size is capped by the number of configs, and up to 20 times per uid. + std::map> mActivationBroadcastGuardrailStats; + + struct LogLossStats { + LogLossStats(int32_t sec, int32_t count, int32_t error, int32_t tag, int32_t uid, + int32_t pid) + : mWallClockSec(sec), + mCount(count), + mLastError(error), + mLastTag(tag), + mUid(uid), + mPid(pid) { + } + int32_t mWallClockSec; + int32_t mCount; + // error code defined in linux/errno.h + int32_t mLastError; + int32_t mLastTag; + int32_t mUid; + int32_t mPid; + }; + + // Max of {(now - oldestEventTimestamp) when overflow happens}. + // This number is helpful to understand how SLOW statsd can be. + int64_t mMaxQueueHistoryNs = 0; + + // Min of {(now - oldestEventTimestamp) when overflow happens}. + // This number is helpful to understand how FAST the events floods to statsd. + int64_t mMinQueueHistoryNs = kInt64Max; + + // Total number of events that are lost due to queue overflow. + int32_t mOverflowCount = 0; + + // Timestamps when we detect log loss, and the number of logs lost. + std::list mLogLossStats; + + std::list mSystemServerRestartSec; + + // Stores the number of times statsd modified the anomaly alarm registered with + // StatsCompanionService. + int mAnomalyAlarmRegisteredStats = 0; + + // Stores the number of times statsd registers the periodic alarm changes + int mPeriodicAlarmRegisteredStats = 0; + + void noteConfigResetInternalLocked(const ConfigKey& key); + + void noteConfigRemovedInternalLocked(const ConfigKey& key); + + void resetInternalLocked(); + + void noteDataDropped(const ConfigKey& key, const size_t totalBytes, int32_t timeSec); + + void noteMetricsReportSent(const ConfigKey& key, const size_t num_bytes, int32_t timeSec); + + void noteBroadcastSent(const ConfigKey& key, int32_t timeSec); + + void noteActiveStatusChanged(const ConfigKey& key, bool activate, int32_t timeSec); + + void noteActivationBroadcastGuardrailHit(const int uid, int32_t timeSec); + + void addToIceBoxLocked(std::shared_ptr& stats); + + int getPushedAtomErrors(int atomId) const; + + /** + * Get a reference to AtomMetricStats for a metric. If none exists, create it. The reference + * will live as long as `this`. + */ + StatsdStats::AtomMetricStats& getAtomMetricStats(int64_t metricId); + + FRIEND_TEST(StatsdStatsTest, TestValidConfigAdd); + FRIEND_TEST(StatsdStatsTest, TestInvalidConfigAdd); + FRIEND_TEST(StatsdStatsTest, TestConfigRemove); + FRIEND_TEST(StatsdStatsTest, TestSubStats); + FRIEND_TEST(StatsdStatsTest, TestAtomLog); + FRIEND_TEST(StatsdStatsTest, TestNonPlatformAtomLog); + FRIEND_TEST(StatsdStatsTest, TestTimestampThreshold); + FRIEND_TEST(StatsdStatsTest, TestAnomalyMonitor); + FRIEND_TEST(StatsdStatsTest, TestSystemServerCrash); + FRIEND_TEST(StatsdStatsTest, TestPullAtomStats); + FRIEND_TEST(StatsdStatsTest, TestAtomMetricsStats); + FRIEND_TEST(StatsdStatsTest, TestActivationBroadcastGuardrailHit); + FRIEND_TEST(StatsdStatsTest, TestAtomErrorStats); + + FRIEND_TEST(StatsLogProcessorTest, InvalidConfigRemoved); +}; + +} // namespace statsd +} // namespace os +} // namespace android diff --git a/statsd/src/hash.cpp b/statsd/src/hash.cpp new file mode 100644 index 00000000..543a748a --- /dev/null +++ b/statsd/src/hash.cpp @@ -0,0 +1,142 @@ +/* + * 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 "hash.h" + +#ifndef FALLTHROUGH_INTENDED +#define FALLTHROUGH_INTENDED [[fallthrough]] +#endif + +namespace android { +namespace os { +namespace statsd { + +namespace { +// Lower-level versions of Get... that read directly from a character buffer +// without any bounds checking. +inline uint32_t DecodeFixed32(const char *ptr) { + return ((static_cast(static_cast(ptr[0]))) | + (static_cast(static_cast(ptr[1])) << 8) | + (static_cast(static_cast(ptr[2])) << 16) | + (static_cast(static_cast(ptr[3])) << 24)); +} + +inline uint64_t DecodeFixed64(const char* ptr) { + uint64_t lo = DecodeFixed32(ptr); + uint64_t hi = DecodeFixed32(ptr + 4); + return (hi << 32) | lo; +} + +// 0xff is in case char is signed. +static inline uint32_t ByteAs32(char c) { return static_cast(c) & 0xff; } +static inline uint64_t ByteAs64(char c) { return static_cast(c) & 0xff; } + +} // namespace + +uint32_t Hash32(const char *data, size_t n, uint32_t seed) { + // 'm' and 'r' are mixing constants generated offline. + // They're not really 'magic', they just happen to work well. + const uint32_t m = 0x5bd1e995; + const int r = 24; + + // Initialize the hash to a 'random' value + uint32_t h = static_cast(seed ^ n); + + // Mix 4 bytes at a time into the hash + while (n >= 4) { + uint32_t k = DecodeFixed32(data); + k *= m; + k ^= k >> r; + k *= m; + h *= m; + h ^= k; + data += 4; + n -= 4; + } + + // Handle the last few bytes of the input array + switch (n) { + case 3: + h ^= ByteAs32(data[2]) << 16; + FALLTHROUGH_INTENDED; + case 2: + h ^= ByteAs32(data[1]) << 8; + FALLTHROUGH_INTENDED; + case 1: + h ^= ByteAs32(data[0]); + h *= m; + } + + // Do a few final mixes of the hash to ensure the last few + // bytes are well-incorporated. + h ^= h >> 13; + h *= m; + h ^= h >> 15; + return h; +} + +uint64_t Hash64(const char* data, size_t n, uint64_t seed) { + const uint64_t m = 0xc6a4a7935bd1e995; + const int r = 47; + + uint64_t h = seed ^ (n * m); + + while (n >= 8) { + uint64_t k = DecodeFixed64(data); + data += 8; + n -= 8; + + k *= m; + k ^= k >> r; + k *= m; + + h ^= k; + h *= m; + } + + switch (n) { + case 7: + h ^= ByteAs64(data[6]) << 48; + FALLTHROUGH_INTENDED; + case 6: + h ^= ByteAs64(data[5]) << 40; + FALLTHROUGH_INTENDED; + case 5: + h ^= ByteAs64(data[4]) << 32; + FALLTHROUGH_INTENDED; + case 4: + h ^= ByteAs64(data[3]) << 24; + FALLTHROUGH_INTENDED; + case 3: + h ^= ByteAs64(data[2]) << 16; + FALLTHROUGH_INTENDED; + case 2: + h ^= ByteAs64(data[1]) << 8; + FALLTHROUGH_INTENDED; + case 1: + h ^= ByteAs64(data[0]); + h *= m; + } + + h ^= h >> r; + h *= m; + h ^= h >> r; + + return h; +} +} // namespace statsd +} // namespace os +} // namespace android diff --git a/statsd/src/hash.h b/statsd/src/hash.h new file mode 100644 index 00000000..bd6b0cd4 --- /dev/null +++ b/statsd/src/hash.h @@ -0,0 +1,47 @@ +/* + * 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. + */ + +#pragma once + +#include + +namespace android { +namespace os { +namespace statsd { + +// Uses murmur2 hashing algorithm. +extern uint32_t Hash32(const char *data, size_t n, uint32_t seed); +extern uint64_t Hash64(const char* data, size_t n, uint64_t seed); + +inline uint32_t Hash32(const char *data, size_t n) { + return Hash32(data, n, 0xBEEF); +} + +inline uint32_t Hash32(const std::string &input) { + return Hash32(input.data(), input.size()); +} + +inline uint64_t Hash64(const char* data, size_t n) { + return Hash64(data, n, 0xDECAFCAFFE); +} + +inline uint64_t Hash64(const std::string& str) { + return Hash64(str.data(), str.size()); +} + +} // namespace statsd +} // namespace os +} // namespace android diff --git a/statsd/src/logd/LogEvent.cpp b/statsd/src/logd/LogEvent.cpp new file mode 100644 index 00000000..4f031724 --- /dev/null +++ b/statsd/src/logd/LogEvent.cpp @@ -0,0 +1,618 @@ +/* + * 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. + */ + +#define DEBUG false // STOPSHIP if true +#include "logd/LogEvent.h" + +#include +#include +#include +#include + +#include "annotations.h" +#include "stats_log_util.h" +#include "statslog_statsd.h" + +namespace android { +namespace os { +namespace statsd { + +// for TrainInfo experiment id serialization +const int FIELD_ID_EXPERIMENT_ID = 1; + +using namespace android::util; +using android::base::StringPrintf; +using android::util::ProtoOutputStream; +using std::string; +using std::vector; + +// stats_event.h socket types. Keep in sync. +/* ERRORS */ +#define ERROR_NO_TIMESTAMP 0x1 +#define ERROR_NO_ATOM_ID 0x2 +#define ERROR_OVERFLOW 0x4 +#define ERROR_ATTRIBUTION_CHAIN_TOO_LONG 0x8 +#define ERROR_TOO_MANY_KEY_VALUE_PAIRS 0x10 +#define ERROR_ANNOTATION_DOES_NOT_FOLLOW_FIELD 0x20 +#define ERROR_INVALID_ANNOTATION_ID 0x40 +#define ERROR_ANNOTATION_ID_TOO_LARGE 0x80 +#define ERROR_TOO_MANY_ANNOTATIONS 0x100 +#define ERROR_TOO_MANY_FIELDS 0x200 +#define ERROR_INVALID_VALUE_TYPE 0x400 +#define ERROR_STRING_NOT_NULL_TERMINATED 0x800 + +/* TYPE IDS */ +#define INT32_TYPE 0x00 +#define INT64_TYPE 0x01 +#define STRING_TYPE 0x02 +#define LIST_TYPE 0x03 +#define FLOAT_TYPE 0x04 +#define BOOL_TYPE 0x05 +#define BYTE_ARRAY_TYPE 0x06 +#define OBJECT_TYPE 0x07 +#define KEY_VALUE_PAIRS_TYPE 0x08 +#define ATTRIBUTION_CHAIN_TYPE 0x09 +#define ERROR_TYPE 0x0F + +LogEvent::LogEvent(int32_t uid, int32_t pid) + : mLogdTimestampNs(time(nullptr)), mLogUid(uid), mLogPid(pid) { +} + +LogEvent::LogEvent(const string& trainName, int64_t trainVersionCode, bool requiresStaging, + bool rollbackEnabled, bool requiresLowLatencyMonitor, int32_t state, + const std::vector& experimentIds, int32_t userId) { + mLogdTimestampNs = getWallClockNs(); + mElapsedTimestampNs = getElapsedRealtimeNs(); + mTagId = util::BINARY_PUSH_STATE_CHANGED; + mLogUid = AIBinder_getCallingUid(); + mLogPid = AIBinder_getCallingPid(); + + mValues.push_back(FieldValue(Field(mTagId, getSimpleField(1)), Value(trainName))); + mValues.push_back(FieldValue(Field(mTagId, getSimpleField(2)), Value(trainVersionCode))); + mValues.push_back(FieldValue(Field(mTagId, getSimpleField(3)), Value((int)requiresStaging))); + mValues.push_back(FieldValue(Field(mTagId, getSimpleField(4)), Value((int)rollbackEnabled))); + mValues.push_back( + FieldValue(Field(mTagId, getSimpleField(5)), Value((int)requiresLowLatencyMonitor))); + mValues.push_back(FieldValue(Field(mTagId, getSimpleField(6)), Value(state))); + mValues.push_back(FieldValue(Field(mTagId, getSimpleField(7)), Value(experimentIds))); + mValues.push_back(FieldValue(Field(mTagId, getSimpleField(8)), Value(userId))); +} + +LogEvent::LogEvent(int64_t wallClockTimestampNs, int64_t elapsedTimestampNs, + const InstallTrainInfo& trainInfo) { + mLogdTimestampNs = wallClockTimestampNs; + mElapsedTimestampNs = elapsedTimestampNs; + mTagId = util::TRAIN_INFO; + + mValues.push_back( + FieldValue(Field(mTagId, getSimpleField(1)), Value(trainInfo.trainVersionCode))); + std::vector experimentIdsProto; + writeExperimentIdsToProto(trainInfo.experimentIds, &experimentIdsProto); + mValues.push_back(FieldValue(Field(mTagId, getSimpleField(2)), Value(experimentIdsProto))); + mValues.push_back(FieldValue(Field(mTagId, getSimpleField(3)), Value(trainInfo.trainName))); + mValues.push_back(FieldValue(Field(mTagId, getSimpleField(4)), Value(trainInfo.status))); +} + +void LogEvent::parseInt32(int32_t* pos, int32_t depth, bool* last, uint8_t numAnnotations) { + int32_t value = readNextValue(); + addToValues(pos, depth, value, last); + parseAnnotations(numAnnotations); +} + +void LogEvent::parseInt64(int32_t* pos, int32_t depth, bool* last, uint8_t numAnnotations) { + int64_t value = readNextValue(); + addToValues(pos, depth, value, last); + parseAnnotations(numAnnotations); +} + +void LogEvent::parseString(int32_t* pos, int32_t depth, bool* last, uint8_t numAnnotations) { + int32_t numBytes = readNextValue(); + if ((uint32_t)numBytes > mRemainingLen) { + mValid = false; + return; + } + + string value = string((char*)mBuf, numBytes); + mBuf += numBytes; + mRemainingLen -= numBytes; + addToValues(pos, depth, value, last); + parseAnnotations(numAnnotations); +} + +void LogEvent::parseFloat(int32_t* pos, int32_t depth, bool* last, uint8_t numAnnotations) { + float value = readNextValue(); + addToValues(pos, depth, value, last); + parseAnnotations(numAnnotations); +} + +void LogEvent::parseBool(int32_t* pos, int32_t depth, bool* last, uint8_t numAnnotations) { + // cast to int32_t because FieldValue does not support bools + int32_t value = (int32_t)readNextValue(); + addToValues(pos, depth, value, last); + parseAnnotations(numAnnotations); +} + +void LogEvent::parseByteArray(int32_t* pos, int32_t depth, bool* last, uint8_t numAnnotations) { + int32_t numBytes = readNextValue(); + if ((uint32_t)numBytes > mRemainingLen) { + mValid = false; + return; + } + + vector value(mBuf, mBuf + numBytes); + mBuf += numBytes; + mRemainingLen -= numBytes; + addToValues(pos, depth, value, last); + parseAnnotations(numAnnotations); +} + +void LogEvent::parseKeyValuePairs(int32_t* pos, int32_t depth, bool* last, uint8_t numAnnotations) { + int32_t numPairs = readNextValue(); + + for (pos[1] = 1; pos[1] <= numPairs; pos[1]++) { + last[1] = (pos[1] == numPairs); + + // parse key + pos[2] = 1; + parseInt32(pos, /*depth=*/2, last, /*numAnnotations=*/0); + + // parse value + last[2] = true; + + uint8_t typeInfo = readNextValue(); + switch (getTypeId(typeInfo)) { + case INT32_TYPE: + pos[2] = 2; // pos[2] determined by index of type in KeyValuePair in atoms.proto + parseInt32(pos, /*depth=*/2, last, /*numAnnotations=*/0); + break; + case INT64_TYPE: + pos[2] = 3; + parseInt64(pos, /*depth=*/2, last, /*numAnnotations=*/0); + break; + case STRING_TYPE: + pos[2] = 4; + parseString(pos, /*depth=*/2, last, /*numAnnotations=*/0); + break; + case FLOAT_TYPE: + pos[2] = 5; + parseFloat(pos, /*depth=*/2, last, /*numAnnotations=*/0); + break; + default: + mValid = false; + } + } + + parseAnnotations(numAnnotations); + + pos[1] = pos[2] = 1; + last[1] = last[2] = false; +} + +void LogEvent::parseAttributionChain(int32_t* pos, int32_t depth, bool* last, + uint8_t numAnnotations) { + const unsigned int firstUidInChainIndex = mValues.size(); + const int32_t numNodes = readNextValue(); + for (pos[1] = 1; pos[1] <= numNodes; pos[1]++) { + last[1] = (pos[1] == numNodes); + + // parse uid + pos[2] = 1; + parseInt32(pos, /*depth=*/2, last, /*numAnnotations=*/0); + + // parse tag + pos[2] = 2; + last[2] = true; + parseString(pos, /*depth=*/2, last, /*numAnnotations=*/0); + } + + if (mValues.size() - 1 > INT8_MAX) { + mValid = false; + } else if (mValues.size() - 1 > firstUidInChainIndex) { + // At least one node was successfully parsed. + mAttributionChainStartIndex = static_cast(firstUidInChainIndex); + mAttributionChainEndIndex = static_cast(mValues.size() - 1); + } + + if (mValid) { + parseAnnotations(numAnnotations, firstUidInChainIndex); + } + + pos[1] = pos[2] = 1; + last[1] = last[2] = false; +} + +// Assumes that mValues is not empty +bool LogEvent::checkPreviousValueType(Type expected) { + return mValues[mValues.size() - 1].mValue.getType() == expected; +} + +void LogEvent::parseIsUidAnnotation(uint8_t annotationType) { + if (mValues.empty() || mValues.size() - 1 > INT8_MAX || !checkPreviousValueType(INT) + || annotationType != BOOL_TYPE) { + mValid = false; + return; + } + + bool isUid = readNextValue(); + if (isUid) mUidFieldIndex = static_cast(mValues.size() - 1); + mValues[mValues.size() - 1].mAnnotations.setUidField(isUid); +} + +void LogEvent::parseTruncateTimestampAnnotation(uint8_t annotationType) { + if (!mValues.empty() || annotationType != BOOL_TYPE) { + mValid = false; + return; + } + + mTruncateTimestamp = readNextValue(); +} + +void LogEvent::parsePrimaryFieldAnnotation(uint8_t annotationType) { + if (mValues.empty() || annotationType != BOOL_TYPE) { + mValid = false; + return; + } + + const bool primaryField = readNextValue(); + mValues[mValues.size() - 1].mAnnotations.setPrimaryField(primaryField); +} + +void LogEvent::parsePrimaryFieldFirstUidAnnotation(uint8_t annotationType, + int firstUidInChainIndex) { + if (mValues.empty() || annotationType != BOOL_TYPE || -1 == firstUidInChainIndex) { + mValid = false; + return; + } + + if (static_cast(mValues.size() - 1) < firstUidInChainIndex) { // AttributionChain is empty. + mValid = false; + android_errorWriteLog(0x534e4554, "174485572"); + return; + } + + const bool primaryField = readNextValue(); + mValues[firstUidInChainIndex].mAnnotations.setPrimaryField(primaryField); +} + +void LogEvent::parseExclusiveStateAnnotation(uint8_t annotationType) { + if (mValues.empty() || annotationType != BOOL_TYPE) { + mValid = false; + return; + } + + if (mValues.size() - 1 > INT8_MAX) { + android_errorWriteLog(0x534e4554, "174488848"); + mValid = false; + return; + } + + const bool exclusiveState = readNextValue(); + mExclusiveStateFieldIndex = static_cast(mValues.size() - 1); + mValues[getExclusiveStateFieldIndex()].mAnnotations.setExclusiveState(exclusiveState); +} + +void LogEvent::parseTriggerStateResetAnnotation(uint8_t annotationType) { + if (mValues.empty() || annotationType != INT32_TYPE) { + mValid = false; + return; + } + + mResetState = readNextValue(); +} + +void LogEvent::parseStateNestedAnnotation(uint8_t annotationType) { + if (mValues.empty() || annotationType != BOOL_TYPE) { + mValid = false; + return; + } + + bool nested = readNextValue(); + mValues[mValues.size() - 1].mAnnotations.setNested(nested); +} + +// firstUidInChainIndex is a default parameter that is only needed when parsing +// annotations for attribution chains. +void LogEvent::parseAnnotations(uint8_t numAnnotations, int firstUidInChainIndex) { + for (uint8_t i = 0; i < numAnnotations; i++) { + uint8_t annotationId = readNextValue(); + uint8_t annotationType = readNextValue(); + + switch (annotationId) { + case ANNOTATION_ID_IS_UID: + parseIsUidAnnotation(annotationType); + break; + case ANNOTATION_ID_TRUNCATE_TIMESTAMP: + parseTruncateTimestampAnnotation(annotationType); + break; + case ANNOTATION_ID_PRIMARY_FIELD: + parsePrimaryFieldAnnotation(annotationType); + break; + case ANNOTATION_ID_PRIMARY_FIELD_FIRST_UID: + parsePrimaryFieldFirstUidAnnotation(annotationType, firstUidInChainIndex); + break; + case ANNOTATION_ID_EXCLUSIVE_STATE: + parseExclusiveStateAnnotation(annotationType); + break; + case ANNOTATION_ID_TRIGGER_STATE_RESET: + parseTriggerStateResetAnnotation(annotationType); + break; + case ANNOTATION_ID_STATE_NESTED: + parseStateNestedAnnotation(annotationType); + break; + default: + mValid = false; + return; + } + } +} + +// This parsing logic is tied to the encoding scheme used in StatsEvent.java and +// stats_event.c +bool LogEvent::parseBuffer(uint8_t* buf, size_t len) { + mBuf = buf; + mRemainingLen = (uint32_t)len; + + int32_t pos[] = {1, 1, 1}; + bool last[] = {false, false, false}; + + // Beginning of buffer is OBJECT_TYPE | NUM_FIELDS | TIMESTAMP | ATOM_ID + uint8_t typeInfo = readNextValue(); + if (getTypeId(typeInfo) != OBJECT_TYPE) mValid = false; + + uint8_t numElements = readNextValue(); + if (numElements < 2 || numElements > 127) mValid = false; + + typeInfo = readNextValue(); + if (getTypeId(typeInfo) != INT64_TYPE) mValid = false; + mElapsedTimestampNs = readNextValue(); + numElements--; + + typeInfo = readNextValue(); + if (getTypeId(typeInfo) != INT32_TYPE) mValid = false; + mTagId = readNextValue(); + numElements--; + parseAnnotations(getNumAnnotations(typeInfo)); // atom-level annotations + + for (pos[0] = 1; pos[0] <= numElements && mValid; pos[0]++) { + last[0] = (pos[0] == numElements); + + typeInfo = readNextValue(); + uint8_t typeId = getTypeId(typeInfo); + + switch (typeId) { + case BOOL_TYPE: + parseBool(pos, /*depth=*/0, last, getNumAnnotations(typeInfo)); + break; + case INT32_TYPE: + parseInt32(pos, /*depth=*/0, last, getNumAnnotations(typeInfo)); + break; + case INT64_TYPE: + parseInt64(pos, /*depth=*/0, last, getNumAnnotations(typeInfo)); + break; + case FLOAT_TYPE: + parseFloat(pos, /*depth=*/0, last, getNumAnnotations(typeInfo)); + break; + case BYTE_ARRAY_TYPE: + parseByteArray(pos, /*depth=*/0, last, getNumAnnotations(typeInfo)); + break; + case STRING_TYPE: + parseString(pos, /*depth=*/0, last, getNumAnnotations(typeInfo)); + break; + case KEY_VALUE_PAIRS_TYPE: + parseKeyValuePairs(pos, /*depth=*/0, last, getNumAnnotations(typeInfo)); + break; + case ATTRIBUTION_CHAIN_TYPE: + parseAttributionChain(pos, /*depth=*/0, last, getNumAnnotations(typeInfo)); + break; + case ERROR_TYPE: + /* mErrorBitmask =*/ readNextValue(); + mValid = false; + break; + default: + mValid = false; + break; + } + } + + if (mRemainingLen != 0) mValid = false; + mBuf = nullptr; + return mValid; +} + +uint8_t LogEvent::getTypeId(uint8_t typeInfo) { + return typeInfo & 0x0F; // type id in lower 4 bytes +} + +uint8_t LogEvent::getNumAnnotations(uint8_t typeInfo) { + return (typeInfo >> 4) & 0x0F; // num annotations in upper 4 bytes +} + +int64_t LogEvent::GetLong(size_t key, status_t* err) const { + // TODO(b/110561208): encapsulate the magical operations in Field struct as static functions + int field = getSimpleField(key); + for (const auto& value : mValues) { + if (value.mField.getField() == field) { + if (value.mValue.getType() == LONG) { + return value.mValue.long_value; + } else if (value.mValue.getType() == INT) { + return value.mValue.int_value; + } else { + *err = BAD_TYPE; + return 0; + } + } + if ((size_t)value.mField.getPosAtDepth(0) > key) { + break; + } + } + + *err = BAD_INDEX; + return 0; +} + +int LogEvent::GetInt(size_t key, status_t* err) const { + int field = getSimpleField(key); + for (const auto& value : mValues) { + if (value.mField.getField() == field) { + if (value.mValue.getType() == INT) { + return value.mValue.int_value; + } else { + *err = BAD_TYPE; + return 0; + } + } + if ((size_t)value.mField.getPosAtDepth(0) > key) { + break; + } + } + + *err = BAD_INDEX; + return 0; +} + +const char* LogEvent::GetString(size_t key, status_t* err) const { + int field = getSimpleField(key); + for (const auto& value : mValues) { + if (value.mField.getField() == field) { + if (value.mValue.getType() == STRING) { + return value.mValue.str_value.c_str(); + } else { + *err = BAD_TYPE; + return 0; + } + } + if ((size_t)value.mField.getPosAtDepth(0) > key) { + break; + } + } + + *err = BAD_INDEX; + return NULL; +} + +bool LogEvent::GetBool(size_t key, status_t* err) const { + int field = getSimpleField(key); + for (const auto& value : mValues) { + if (value.mField.getField() == field) { + if (value.mValue.getType() == INT) { + return value.mValue.int_value != 0; + } else if (value.mValue.getType() == LONG) { + return value.mValue.long_value != 0; + } else { + *err = BAD_TYPE; + return false; + } + } + if ((size_t)value.mField.getPosAtDepth(0) > key) { + break; + } + } + + *err = BAD_INDEX; + return false; +} + +float LogEvent::GetFloat(size_t key, status_t* err) const { + int field = getSimpleField(key); + for (const auto& value : mValues) { + if (value.mField.getField() == field) { + if (value.mValue.getType() == FLOAT) { + return value.mValue.float_value; + } else { + *err = BAD_TYPE; + return 0.0; + } + } + if ((size_t)value.mField.getPosAtDepth(0) > key) { + break; + } + } + + *err = BAD_INDEX; + return 0.0; +} + +std::vector LogEvent::GetStorage(size_t key, status_t* err) const { + int field = getSimpleField(key); + for (const auto& value : mValues) { + if (value.mField.getField() == field) { + if (value.mValue.getType() == STORAGE) { + return value.mValue.storage_value; + } else { + *err = BAD_TYPE; + return vector(); + } + } + if ((size_t)value.mField.getPosAtDepth(0) > key) { + break; + } + } + + *err = BAD_INDEX; + return vector(); +} + +string LogEvent::ToString() const { + string result; + result += StringPrintf("{ uid(%d) %lld %lld (%d)", mLogUid, (long long)mLogdTimestampNs, + (long long)mElapsedTimestampNs, mTagId); + for (const auto& value : mValues) { + result += + StringPrintf("%#x", value.mField.getField()) + "->" + value.mValue.toString() + " "; + } + result += " }"; + return result; +} + +void LogEvent::ToProto(ProtoOutputStream& protoOutput) const { + writeFieldValueTreeToStream(mTagId, getValues(), &protoOutput); +} + +bool LogEvent::hasAttributionChain(std::pair* indexRange) const { + if (mAttributionChainStartIndex == -1 || mAttributionChainEndIndex == -1) { + return false; + } + + if (nullptr != indexRange) { + indexRange->first = static_cast(mAttributionChainStartIndex); + indexRange->second = static_cast(mAttributionChainEndIndex); + } + + return true; +} + +void writeExperimentIdsToProto(const std::vector& experimentIds, + std::vector* protoOut) { + ProtoOutputStream proto; + for (const auto& expId : experimentIds) { + proto.write(FIELD_TYPE_INT64 | FIELD_COUNT_REPEATED | FIELD_ID_EXPERIMENT_ID, + (long long)expId); + } + + protoOut->resize(proto.size()); + size_t pos = 0; + sp reader = proto.data(); + while (reader->readBuffer() != NULL) { + size_t toRead = reader->currentToRead(); + std::memcpy(protoOut->data() + pos, reader->readBuffer(), toRead); + pos += toRead; + reader->move(toRead); + } +} + +} // namespace statsd +} // namespace os +} // namespace android diff --git a/statsd/src/logd/LogEvent.h b/statsd/src/logd/LogEvent.h new file mode 100644 index 00000000..a5f24603 --- /dev/null +++ b/statsd/src/logd/LogEvent.h @@ -0,0 +1,331 @@ +/* + * 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. + */ + +#pragma once + +#include "FieldValue.h" + +#include +#include + +#include +#include + +namespace android { +namespace os { +namespace statsd { + +struct InstallTrainInfo { + int64_t trainVersionCode; + std::string trainName; + int32_t status; + std::vector experimentIds; + bool requiresStaging; + bool rollbackEnabled; + bool requiresLowLatencyMonitor; +}; + +/** + * This class decodes the structured, serialized encoding of an atom into a + * vector of FieldValues. + */ +class LogEvent { +public: + /** + * \param uid user id of the logging caller + * \param pid process id of the logging caller + */ + explicit LogEvent(int32_t uid, int32_t pid); + + /** + * Parses the atomId, timestamp, and vector of values from a buffer + * containing the StatsEvent/AStatsEvent encoding of an atom. + * + * \param buf a buffer that begins at the start of the serialized atom (it + * should not include the android_log_header_t or the StatsEventTag) + * \param len size of the buffer + * + * \return success of the initialization + */ + bool parseBuffer(uint8_t* buf, size_t len); + + // Constructs a BinaryPushStateChanged LogEvent from API call. + explicit LogEvent(const std::string& trainName, int64_t trainVersionCode, bool requiresStaging, + bool rollbackEnabled, bool requiresLowLatencyMonitor, int32_t state, + const std::vector& experimentIds, int32_t userId); + + explicit LogEvent(int64_t wallClockTimestampNs, int64_t elapsedTimestampNs, + const InstallTrainInfo& installTrainInfo); + + ~LogEvent() {} + + /** + * Get the timestamp associated with this event. + */ + inline int64_t GetLogdTimestampNs() const { return mLogdTimestampNs; } + inline int64_t GetElapsedTimestampNs() const { return mElapsedTimestampNs; } + + /** + * Get the tag for this event. + */ + inline int GetTagId() const { return mTagId; } + + /** + * Get the uid of the logging client. + * Returns -1 if the uid is unknown/has not been set. + */ + inline int32_t GetUid() const { return mLogUid; } + + /** + * Get the pid of the logging client. + * Returns -1 if the pid is unknown/has not been set. + */ + inline int32_t GetPid() const { return mLogPid; } + + /** + * Get the nth value, starting at 1. + * + * Returns BAD_INDEX if the index is larger than the number of elements. + * Returns BAD_TYPE if the index is available but the data is the wrong type. + */ + int64_t GetLong(size_t key, status_t* err) const; + int GetInt(size_t key, status_t* err) const; + const char* GetString(size_t key, status_t* err) const; + bool GetBool(size_t key, status_t* err) const; + float GetFloat(size_t key, status_t* err) const; + std::vector GetStorage(size_t key, status_t* err) const; + + /** + * Return a string representation of this event. + */ + std::string ToString() const; + + /** + * Write this object to a ProtoOutputStream. + */ + void ToProto(android::util::ProtoOutputStream& out) const; + + /** + * Set elapsed timestamp if the original timestamp is missing. + */ + void setElapsedTimestampNs(int64_t timestampNs) { + mElapsedTimestampNs = timestampNs; + } + + /** + * Set the timestamp if the original logd timestamp is missing. + */ + void setLogdWallClockTimestampNs(int64_t timestampNs) { + mLogdTimestampNs = timestampNs; + } + + inline int size() const { + return mValues.size(); + } + + const std::vector& getValues() const { + return mValues; + } + + std::vector* getMutableValues() { + return &mValues; + } + + // Default value = false + inline bool shouldTruncateTimestamp() const { + return mTruncateTimestamp; + } + + // Returns the index of the uid field within the FieldValues vector if the + // uid exists. If there is no uid field, returns -1. + // + // If the index within the atom definition is desired, do the following: + // int vectorIndex = LogEvent.getUidFieldIndex(); + // if (vectorIndex != -1) { + // FieldValue& v = LogEvent.getValues()[vectorIndex]; + // int atomIndex = v.mField.getPosAtDepth(0); + // } + // Note that atomIndex is 1-indexed. + inline int getUidFieldIndex() { + return static_cast(mUidFieldIndex); + } + + // Returns whether this LogEvent has an AttributionChain. + // If it does and indexRange is not a nullptr, populate indexRange with the start and end index + // of the AttributionChain within mValues. + bool hasAttributionChain(std::pair* indexRange = nullptr) const; + + // Returns the index of the exclusive state field within the FieldValues vector if + // an exclusive state exists. If there is no exclusive state field, returns -1. + // + // If the index within the atom definition is desired, do the following: + // int vectorIndex = LogEvent.getExclusiveStateFieldIndex(); + // if (vectorIndex != -1) { + // FieldValue& v = LogEvent.getValues()[vectorIndex]; + // int atomIndex = v.mField.getPosAtDepth(0); + // } + // Note that atomIndex is 1-indexed. + inline int getExclusiveStateFieldIndex() const { + return static_cast(mExclusiveStateFieldIndex); + } + + // If a reset state is not sent in the StatsEvent, returns -1. Note that a + // reset state is sent if and only if a reset should be triggered. + inline int getResetState() const { + return mResetState; + } + + inline LogEvent makeCopy() { + return LogEvent(*this); + } + + template + status_t updateValue(size_t key, T& value, Type type) { + int field = getSimpleField(key); + for (auto& fieldValue : mValues) { + if (fieldValue.mField.getField() == field) { + if (fieldValue.mValue.getType() == type) { + fieldValue.mValue = Value(value); + return OK; + } else { + return BAD_TYPE; + } + } + } + return BAD_INDEX; + } + + bool isValid() const { + return mValid; + } + +private: + /** + * Only use this if copy is absolutely needed. + */ + LogEvent(const LogEvent&) = default; + + void parseInt32(int32_t* pos, int32_t depth, bool* last, uint8_t numAnnotations); + void parseInt64(int32_t* pos, int32_t depth, bool* last, uint8_t numAnnotations); + void parseString(int32_t* pos, int32_t depth, bool* last, uint8_t numAnnotations); + void parseFloat(int32_t* pos, int32_t depth, bool* last, uint8_t numAnnotations); + void parseBool(int32_t* pos, int32_t depth, bool* last, uint8_t numAnnotations); + void parseByteArray(int32_t* pos, int32_t depth, bool* last, uint8_t numAnnotations); + void parseKeyValuePairs(int32_t* pos, int32_t depth, bool* last, uint8_t numAnnotations); + void parseAttributionChain(int32_t* pos, int32_t depth, bool* last, uint8_t numAnnotations); + + void parseAnnotations(uint8_t numAnnotations, int firstUidInChainIndex = -1); + void parseIsUidAnnotation(uint8_t annotationType); + void parseTruncateTimestampAnnotation(uint8_t annotationType); + void parsePrimaryFieldAnnotation(uint8_t annotationType); + void parsePrimaryFieldFirstUidAnnotation(uint8_t annotationType, int firstUidInChainIndex); + void parseExclusiveStateAnnotation(uint8_t annotationType); + void parseTriggerStateResetAnnotation(uint8_t annotationType); + void parseStateNestedAnnotation(uint8_t annotationType); + bool checkPreviousValueType(Type expected); + + /** + * The below two variables are only valid during the execution of + * parseBuffer. There are no guarantees about the state of these variables + * before/after. + */ + uint8_t* mBuf; + uint32_t mRemainingLen; // number of valid bytes left in the buffer being parsed + + bool mValid = true; // stores whether the event we received from the socket is valid + + /** + * Side-effects: + * If there is enough space in buffer to read value of type T + * - move mBuf past the value that was just read + * - decrement mRemainingLen by size of T + * Else + * - set mValid to false + */ + template + T readNextValue() { + T value; + if (mRemainingLen < sizeof(T)) { + mValid = false; + value = 0; // all primitive types can successfully cast 0 + } else { + // When alignof(T) == 1, hopefully the compiler can optimize away + // this conditional as always true. + if ((reinterpret_cast(mBuf) % alignof(T)) == 0) { + // We're properly aligned, and can safely make this assignment. + value = *((T*)mBuf); + } else { + // We need to use memcpy. It's slower, but safe. + memcpy(&value, mBuf, sizeof(T)); + } + mBuf += sizeof(T); + mRemainingLen -= sizeof(T); + } + return value; + } + + template + void addToValues(int32_t* pos, int32_t depth, T& value, bool* last) { + Field f = Field(mTagId, pos, depth); + // do not decorate last position at depth 0 + for (int i = 1; i < depth; i++) { + if (last[i]) f.decorateLastPos(i); + } + + Value v = Value(value); + mValues.push_back(FieldValue(f, v)); + } + + uint8_t getTypeId(uint8_t typeInfo); + uint8_t getNumAnnotations(uint8_t typeInfo); + + // The items are naturally sorted in DFS order as we read them. this allows us to do fast + // matching. + std::vector mValues; + + // The timestamp set by the logd. + int64_t mLogdTimestampNs; + + // The elapsed timestamp set by statsd log writer. + int64_t mElapsedTimestampNs; + + // The atom tag of the event (defaults to 0 if client does not + // appropriately set the atom id). + int mTagId = 0; + + // The uid of the logging client (defaults to -1). + int32_t mLogUid = -1; + + // The pid of the logging client (defaults to -1). + int32_t mLogPid = -1; + + // Annotations + bool mTruncateTimestamp = false; + int mResetState = -1; + + // Indexes within the FieldValue vector can be stored in 7 bits because + // that's the assumption enforced by the encoding used in FieldValue. + int8_t mUidFieldIndex = -1; + int8_t mAttributionChainStartIndex = -1; + int8_t mAttributionChainEndIndex = -1; + int8_t mExclusiveStateFieldIndex = -1; +}; + +void writeExperimentIdsToProto(const std::vector& experimentIds, std::vector* protoOut); + +} // namespace statsd +} // namespace os +} // namespace android diff --git a/statsd/src/logd/LogEventQueue.cpp b/statsd/src/logd/LogEventQueue.cpp new file mode 100644 index 00000000..146464bb --- /dev/null +++ b/statsd/src/logd/LogEventQueue.cpp @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2019 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. + */ + +#define DEBUG false // STOPSHIP if true +#include "Log.h" + +#include "LogEventQueue.h" + +namespace android { +namespace os { +namespace statsd { + +using std::unique_lock; +using std::unique_ptr; + +unique_ptr LogEventQueue::waitPop() { + std::unique_lock lock(mMutex); + + if (mQueue.empty()) { + mCondition.wait(lock, [this] { return !this->mQueue.empty(); }); + } + + unique_ptr item = std::move(mQueue.front()); + mQueue.pop(); + + return item; +} + +bool LogEventQueue::push(unique_ptr item, int64_t* oldestTimestampNs) { + bool success; + { + std::unique_lock lock(mMutex); + if (mQueue.size() < mQueueLimit) { + mQueue.push(std::move(item)); + success = true; + } else { + // safe operation as queue must not be empty. + *oldestTimestampNs = mQueue.front()->GetElapsedTimestampNs(); + success = false; + } + } + + mCondition.notify_one(); + return success; +} + +} // namespace statsd +} // namespace os +} // namespace android diff --git a/statsd/src/logd/LogEventQueue.h b/statsd/src/logd/LogEventQueue.h new file mode 100644 index 00000000..9dda3d24 --- /dev/null +++ b/statsd/src/logd/LogEventQueue.h @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2019 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. + */ + +#pragma once + +#include "LogEvent.h" + +#include +#include +#include + +namespace android { +namespace os { +namespace statsd { + +/** + * A zero copy thread safe queue buffer for producing and consuming LogEvent. + */ +class LogEventQueue { +public: + explicit LogEventQueue(size_t maxSize) : mQueueLimit(maxSize){}; + + /** + * Blocking read one event from the queue. + */ + std::unique_ptr waitPop(); + + /** + * Puts a LogEvent ptr to the end of the queue. + * Returns false on failure when the queue is full, and output the oldest event timestamp + * in the queue. + */ + bool push(std::unique_ptr event, int64_t* oldestTimestampNs); + +private: + const size_t mQueueLimit; + std::condition_variable mCondition; + std::mutex mMutex; + std::queue> mQueue; +}; + +} // namespace statsd +} // namespace os +} // namespace android diff --git a/statsd/src/main.cpp b/statsd/src/main.cpp new file mode 100644 index 00000000..03b178a9 --- /dev/null +++ b/statsd/src/main.cpp @@ -0,0 +1,113 @@ +/* + * 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. + */ + +#define DEBUG false // STOPSHIP if true +#include "Log.h" + +#include "StatsService.h" +#include "socket/StatsSocketListener.h" + +#include +#include +#include +#include + +#include +#include +#include +#include + +using namespace android; +using namespace android::os::statsd; +using ::ndk::SharedRefBase; +using std::shared_ptr; +using std::make_shared; + +shared_ptr gStatsService = nullptr; +sp gSocketListener = nullptr; + +void signalHandler(int sig) { + if (sig == SIGPIPE) { + // ShellSubscriber uses SIGPIPE as a signal to detect the end of the + // client process. Don't prematurely exit(1) here. Instead, ignore the + // signal and allow the write call to return EPIPE. + ALOGI("statsd received SIGPIPE. Ignoring signal."); + return; + } + + if (gSocketListener != nullptr) gSocketListener->stopListener(); + if (gStatsService != nullptr) gStatsService->Terminate(); + ALOGW("statsd terminated on receiving signal %d.", sig); + exit(1); +} + +void registerSignalHandlers() +{ + struct sigaction sa; + sigemptyset(&sa.sa_mask); + sa.sa_flags = 0; + sa.sa_handler = signalHandler; + sigaction(SIGPIPE, &sa, nullptr); + sigaction(SIGHUP, &sa, nullptr); + sigaction(SIGINT, &sa, nullptr); + sigaction(SIGQUIT, &sa, nullptr); + sigaction(SIGTERM, &sa, nullptr); +} + +int main(int /*argc*/, char** /*argv*/) { + // Set up the looper + sp looper(Looper::prepare(0 /* opts */)); + + // Set up the binder + ABinderProcess_setThreadPoolMaxThreadCount(9); + ABinderProcess_startThreadPool(); + + std::shared_ptr eventQueue = + std::make_shared(4000 /*buffer limit. Buffer is NOT pre-allocated*/); + + // Create the service + gStatsService = SharedRefBase::make(looper, eventQueue); + // TODO(b/149582373): Set DUMP_FLAG_PROTO once libbinder_ndk supports + // setting dumpsys priorities. + binder_status_t status = AServiceManager_addService(gStatsService->asBinder().get(), "stats"); + if (status != STATUS_OK) { + ALOGE("Failed to add service as AIDL service"); + return -1; + } + + registerSignalHandlers(); + + gStatsService->sayHiToStatsCompanion(); + + gStatsService->Startup(); + + gSocketListener = new StatsSocketListener(eventQueue); + + ALOGI("Statsd starts to listen to socket."); + // Backlog and /proc/sys/net/unix/max_dgram_qlen set to large value + if (gSocketListener->startListener(600)) { + exit(1); + } + + // Loop forever -- the reports run on this thread in a handler, and the + // binder calls remain responsive in their pool of one thread. + while (true) { + looper->pollAll(-1 /* timeoutMillis */); + } + ALOGW("statsd escaped from its loop."); + + return 1; +} diff --git a/statsd/src/matchers/AtomMatchingTracker.h b/statsd/src/matchers/AtomMatchingTracker.h new file mode 100644 index 00000000..cdb17165 --- /dev/null +++ b/statsd/src/matchers/AtomMatchingTracker.h @@ -0,0 +1,118 @@ +/* + * 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. + */ + +#ifndef ATOM_MATCHING_TRACKER_H +#define ATOM_MATCHING_TRACKER_H + +#include "src/statsd_config.pb.h" +#include "logd/LogEvent.h" +#include "matchers/matcher_util.h" + +#include + +#include +#include +#include + +namespace android { +namespace os { +namespace statsd { + +class AtomMatchingTracker : public virtual RefBase { +public: + AtomMatchingTracker(const int64_t& id, const int index, const uint64_t protoHash) + : mId(id), mIndex(index), mInitialized(false), mProtoHash(protoHash){}; + + virtual ~AtomMatchingTracker(){}; + + // Initialize this AtomMatchingTracker. + // allAtomMatchers: the list of the AtomMatcher proto config. This is needed because we don't + // store the proto object in memory. We only need it during initilization. + // allAtomMatchingTrackers: the list of the AtomMatchingTracker objects. It's a one-to-one + // mapping with allAtomMatchers. This is needed because the + // initialization is done recursively for + // CombinationAtomMatchingTrackers using DFS. + // stack: a bit map to record which matcher has been visited on the stack. This is for detecting + // circle dependency. + virtual bool init(const std::vector& allAtomMatchers, + const std::vector>& allAtomMatchingTrackers, + const std::unordered_map& matcherMap, + std::vector& stack) = 0; + + // Update appropriate state on config updates. Primarily, all indices need to be updated. + // This matcher and all of its children are guaranteed to be preserved across the update. + // matcher: the AtomMatcher proto from the config. + // index: the index of this matcher in mAllAtomMatchingTrackers. + // atomMatchingTrackerMap: map from matcher id to index in mAllAtomMatchingTrackers + virtual bool onConfigUpdated( + const AtomMatcher& matcher, const int index, + const std::unordered_map& atomMatchingTrackerMap) = 0; + + // Called when a log event comes. + // event: the log event. + // allAtomMatchingTrackers: the list of all AtomMatchingTrackers. This is needed because the log + // processing is done recursively. + // matcherResults: The cached results for all matchers for this event. Parent matchers can + // directly access the children's matching results if they have been evaluated. + // Otherwise, call children matchers' onLogEvent. + virtual void onLogEvent(const LogEvent& event, + const std::vector>& allAtomMatchingTrackers, + std::vector& matcherResults) = 0; + + // Get the tagIds that this matcher cares about. The combined collection is stored + // in MetricMananger, so that we can pass any LogEvents that are not interest of us. It uses + // some memory but hopefully it can save us much CPU time when there is flood of events. + virtual const std::set& getAtomIds() const { + return mAtomIds; + } + + int64_t getId() const { + return mId; + } + + uint64_t getProtoHash() const { + return mProtoHash; + } + +protected: + // Name of this matching. We don't really need the name, but it makes log message easy to debug. + const int64_t mId; + + // Index of this AtomMatchingTracker in MetricsManager's container. + int mIndex; + + // Whether this AtomMatchingTracker has been properly initialized. + bool mInitialized; + + // The collection of the event tag ids that this AtomMatchingTracker cares. So we can quickly + // return kNotMatched when we receive an event with an id not in the list. This is especially + // useful when we have a complex CombinationAtomMatchingTracker. + std::set mAtomIds; + + // Hash of the AtomMatcher's proto bytes from StatsdConfig. + // Used to determine if the definition of this matcher has changed across a config update. + const uint64_t mProtoHash; + + FRIEND_TEST(MetricsManagerTest, TestCreateAtomMatchingTrackerSimple); + FRIEND_TEST(MetricsManagerTest, TestCreateAtomMatchingTrackerCombination); + FRIEND_TEST(ConfigUpdateTest, TestUpdateMatchers); +}; + +} // namespace statsd +} // namespace os +} // namespace android + +#endif // ATOM_MATCHING_TRACKER_H diff --git a/statsd/src/matchers/CombinationAtomMatchingTracker.cpp b/statsd/src/matchers/CombinationAtomMatchingTracker.cpp new file mode 100644 index 00000000..45685ce5 --- /dev/null +++ b/statsd/src/matchers/CombinationAtomMatchingTracker.cpp @@ -0,0 +1,140 @@ +/* + * 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 "Log.h" + +#include "CombinationAtomMatchingTracker.h" + +#include "matchers/matcher_util.h" + +namespace android { +namespace os { +namespace statsd { + +using std::set; +using std::unordered_map; +using std::vector; + +CombinationAtomMatchingTracker::CombinationAtomMatchingTracker(const int64_t& id, const int index, + const uint64_t protoHash) + : AtomMatchingTracker(id, index, protoHash) { +} + +CombinationAtomMatchingTracker::~CombinationAtomMatchingTracker() { +} + +bool CombinationAtomMatchingTracker::init( + const vector& allAtomMatchers, + const vector>& allAtomMatchingTrackers, + const unordered_map& matcherMap, vector& stack) { + if (mInitialized) { + return true; + } + + // mark this node as visited in the recursion stack. + stack[mIndex] = true; + + AtomMatcher_Combination matcher = allAtomMatchers[mIndex].combination(); + + // LogicalOperation is missing in the config + if (!matcher.has_operation()) { + return false; + } + + mLogicalOperation = matcher.operation(); + + if (mLogicalOperation == LogicalOperation::NOT && matcher.matcher_size() != 1) { + return false; + } + + for (const auto& child : matcher.matcher()) { + auto pair = matcherMap.find(child); + if (pair == matcherMap.end()) { + ALOGW("Matcher %lld not found in the config", (long long)child); + return false; + } + + int childIndex = pair->second; + + // if the child is a visited node in the recursion -> circle detected. + if (stack[childIndex]) { + ALOGE("Circle detected in matcher config"); + return false; + } + + if (!allAtomMatchingTrackers[childIndex]->init(allAtomMatchers, allAtomMatchingTrackers, + matcherMap, stack)) { + ALOGW("child matcher init failed %lld", (long long)child); + return false; + } + + mChildren.push_back(childIndex); + + const set& childTagIds = allAtomMatchingTrackers[childIndex]->getAtomIds(); + mAtomIds.insert(childTagIds.begin(), childTagIds.end()); + } + + mInitialized = true; + // unmark this node in the recursion stack. + stack[mIndex] = false; + return true; +} + +bool CombinationAtomMatchingTracker::onConfigUpdated( + const AtomMatcher& matcher, const int index, + const unordered_map& atomMatchingTrackerMap) { + mIndex = index; + mChildren.clear(); + AtomMatcher_Combination combinationMatcher = matcher.combination(); + for (const int64_t child : combinationMatcher.matcher()) { + const auto& pair = atomMatchingTrackerMap.find(child); + if (pair == atomMatchingTrackerMap.end()) { + ALOGW("Matcher %lld not found in the config", (long long)child); + return false; + } + mChildren.push_back(pair->second); + } + return true; +} + +void CombinationAtomMatchingTracker::onLogEvent( + const LogEvent& event, const vector>& allAtomMatchingTrackers, + vector& matcherResults) { + // this event has been processed. + if (matcherResults[mIndex] != MatchingState::kNotComputed) { + return; + } + + if (mAtomIds.find(event.GetTagId()) == mAtomIds.end()) { + matcherResults[mIndex] = MatchingState::kNotMatched; + return; + } + + // evaluate children matchers if they haven't been evaluated. + for (const int childIndex : mChildren) { + if (matcherResults[childIndex] == MatchingState::kNotComputed) { + const sp& child = allAtomMatchingTrackers[childIndex]; + child->onLogEvent(event, allAtomMatchingTrackers, matcherResults); + } + } + + bool matched = combinationMatch(mChildren, mLogicalOperation, matcherResults); + matcherResults[mIndex] = matched ? MatchingState::kMatched : MatchingState::kNotMatched; +} + +} // namespace statsd +} // namespace os +} // namespace android diff --git a/statsd/src/matchers/CombinationAtomMatchingTracker.h b/statsd/src/matchers/CombinationAtomMatchingTracker.h new file mode 100644 index 00000000..cf02086b --- /dev/null +++ b/statsd/src/matchers/CombinationAtomMatchingTracker.h @@ -0,0 +1,58 @@ +/* + * 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. + */ +#ifndef COMBINATION_ATOM_MATCHING_TRACKER_H +#define COMBINATION_ATOM_MATCHING_TRACKER_H + +#include +#include + +#include "AtomMatchingTracker.h" +#include "src/statsd_config.pb.h" + +namespace android { +namespace os { +namespace statsd { + +// Represents a AtomMatcher_Combination in the StatsdConfig. +class CombinationAtomMatchingTracker : public AtomMatchingTracker { +public: + CombinationAtomMatchingTracker(const int64_t& id, const int index, const uint64_t protoHash); + + bool init(const std::vector& allAtomMatchers, + const std::vector>& allAtomMatchingTrackers, + const std::unordered_map& matcherMap, std::vector& stack); + + bool onConfigUpdated(const AtomMatcher& matcher, const int index, + const std::unordered_map& atomMatchingTrackerMap) override; + + ~CombinationAtomMatchingTracker(); + + void onLogEvent(const LogEvent& event, + const std::vector>& allAtomMatchingTrackers, + std::vector& matcherResults) override; + +private: + LogicalOperation mLogicalOperation; + + std::vector mChildren; + + FRIEND_TEST(ConfigUpdateTest, TestUpdateMatchers); +}; + +} // namespace statsd +} // namespace os +} // namespace android +#endif // COMBINATION_ATOM_MATCHING_TRACKER_H diff --git a/statsd/src/matchers/EventMatcherWizard.cpp b/statsd/src/matchers/EventMatcherWizard.cpp new file mode 100644 index 00000000..025c9a87 --- /dev/null +++ b/statsd/src/matchers/EventMatcherWizard.cpp @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2018 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 "EventMatcherWizard.h" + +namespace android { +namespace os { +namespace statsd { + +using std::vector; + +MatchingState EventMatcherWizard::matchLogEvent(const LogEvent& event, int matcher_index) { + if (matcher_index < 0 || matcher_index >= (int)mAllEventMatchers.size()) { + return MatchingState::kNotComputed; + } + vector matcherCache(mAllEventMatchers.size(), MatchingState::kNotComputed); + mAllEventMatchers[matcher_index]->onLogEvent(event, mAllEventMatchers, matcherCache); + return matcherCache[matcher_index]; +} + +} // namespace statsd +} // namespace os +} // namespace android \ No newline at end of file diff --git a/statsd/src/matchers/EventMatcherWizard.h b/statsd/src/matchers/EventMatcherWizard.h new file mode 100644 index 00000000..5d780f24 --- /dev/null +++ b/statsd/src/matchers/EventMatcherWizard.h @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2018 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. + */ + +#pragma once + +#include "AtomMatchingTracker.h" + +namespace android { +namespace os { +namespace statsd { + +class EventMatcherWizard : public virtual android::RefBase { +public: + EventMatcherWizard(){}; // for testing + EventMatcherWizard(const std::vector>& eventTrackers) + : mAllEventMatchers(eventTrackers){}; + + virtual ~EventMatcherWizard(){}; + + MatchingState matchLogEvent(const LogEvent& event, int matcher_index); + +private: + std::vector> mAllEventMatchers; +}; + +} // namespace statsd +} // namespace os +} // namespace android diff --git a/statsd/src/matchers/SimpleAtomMatchingTracker.cpp b/statsd/src/matchers/SimpleAtomMatchingTracker.cpp new file mode 100644 index 00000000..423da5bd --- /dev/null +++ b/statsd/src/matchers/SimpleAtomMatchingTracker.cpp @@ -0,0 +1,81 @@ +/* + * 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. + */ + +#define DEBUG false // STOPSHIP if true +#include "Log.h" + +#include "SimpleAtomMatchingTracker.h" + +namespace android { +namespace os { +namespace statsd { + +using std::unordered_map; +using std::vector; + +SimpleAtomMatchingTracker::SimpleAtomMatchingTracker(const int64_t& id, const int index, + const uint64_t protoHash, + const SimpleAtomMatcher& matcher, + const sp& uidMap) + : AtomMatchingTracker(id, index, protoHash), mMatcher(matcher), mUidMap(uidMap) { + if (!matcher.has_atom_id()) { + mInitialized = false; + } else { + mAtomIds.insert(matcher.atom_id()); + mInitialized = true; + } +} + +SimpleAtomMatchingTracker::~SimpleAtomMatchingTracker() { +} + +bool SimpleAtomMatchingTracker::init(const vector& allAtomMatchers, + const vector>& allAtomMatchingTrackers, + const unordered_map& matcherMap, + vector& stack) { + // no need to do anything. + return mInitialized; +} + +bool SimpleAtomMatchingTracker::onConfigUpdated( + const AtomMatcher& matcher, const int index, + const unordered_map& atomMatchingTrackerMap) { + mIndex = index; + // Do not need to update mMatcher since the matcher must be identical across the update. + return mInitialized; +} + +void SimpleAtomMatchingTracker::onLogEvent( + const LogEvent& event, const vector>& allAtomMatchingTrackers, + vector& matcherResults) { + if (matcherResults[mIndex] != MatchingState::kNotComputed) { + VLOG("Matcher %lld already evaluated ", (long long)mId); + return; + } + + if (mAtomIds.find(event.GetTagId()) == mAtomIds.end()) { + matcherResults[mIndex] = MatchingState::kNotMatched; + return; + } + + bool matched = matchesSimple(mUidMap, mMatcher, event); + matcherResults[mIndex] = matched ? MatchingState::kMatched : MatchingState::kNotMatched; + VLOG("Stats SimpleAtomMatcher %lld matched? %d", (long long)mId, matched); +} + +} // namespace statsd +} // namespace os +} // namespace android diff --git a/statsd/src/matchers/SimpleAtomMatchingTracker.h b/statsd/src/matchers/SimpleAtomMatchingTracker.h new file mode 100644 index 00000000..13ef344d --- /dev/null +++ b/statsd/src/matchers/SimpleAtomMatchingTracker.h @@ -0,0 +1,58 @@ +/* + * 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. + */ + +#ifndef SIMPLE_ATOM_MATCHING_TRACKER_H +#define SIMPLE_ATOM_MATCHING_TRACKER_H + +#include +#include + +#include "AtomMatchingTracker.h" +#include "src/statsd_config.pb.h" +#include "packages/UidMap.h" + +namespace android { +namespace os { +namespace statsd { + +class SimpleAtomMatchingTracker : public AtomMatchingTracker { +public: + SimpleAtomMatchingTracker(const int64_t& id, const int index, const uint64_t protoHash, + const SimpleAtomMatcher& matcher, const sp& uidMap); + + ~SimpleAtomMatchingTracker(); + + bool init(const std::vector& allAtomMatchers, + const std::vector>& allAtomMatchingTrackers, + const std::unordered_map& matcherMap, + std::vector& stack) override; + + bool onConfigUpdated(const AtomMatcher& matcher, const int index, + const std::unordered_map& atomMatchingTrackerMap) override; + + void onLogEvent(const LogEvent& event, + const std::vector>& allAtomMatchingTrackers, + std::vector& matcherResults) override; + +private: + const SimpleAtomMatcher mMatcher; + const sp mUidMap; +}; + +} // namespace statsd +} // namespace os +} // namespace android +#endif // SIMPLE_ATOM_MATCHING_TRACKER_H diff --git a/statsd/src/matchers/matcher_util.cpp b/statsd/src/matchers/matcher_util.cpp new file mode 100644 index 00000000..4ea117fa --- /dev/null +++ b/statsd/src/matchers/matcher_util.cpp @@ -0,0 +1,373 @@ +/* + * 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. + */ +#define DEBUG false // STOPSHIP if true +#include "Log.h" + +#include "src/statsd_config.pb.h" +#include "matchers/AtomMatchingTracker.h" +#include "matchers/matcher_util.h" +#include "stats_util.h" + +using std::set; +using std::string; +using std::vector; + +namespace android { +namespace os { +namespace statsd { + +bool combinationMatch(const vector& children, const LogicalOperation& operation, + const vector& matcherResults) { + bool matched; + switch (operation) { + case LogicalOperation::AND: { + matched = true; + for (const int childIndex : children) { + if (matcherResults[childIndex] != MatchingState::kMatched) { + matched = false; + break; + } + } + break; + } + case LogicalOperation::OR: { + matched = false; + for (const int childIndex : children) { + if (matcherResults[childIndex] == MatchingState::kMatched) { + matched = true; + break; + } + } + break; + } + case LogicalOperation::NOT: + matched = matcherResults[children[0]] == MatchingState::kNotMatched; + break; + case LogicalOperation::NAND: + matched = false; + for (const int childIndex : children) { + if (matcherResults[childIndex] != MatchingState::kMatched) { + matched = true; + break; + } + } + break; + case LogicalOperation::NOR: + matched = true; + for (const int childIndex : children) { + if (matcherResults[childIndex] == MatchingState::kMatched) { + matched = false; + break; + } + } + break; + case LogicalOperation::LOGICAL_OPERATION_UNSPECIFIED: + matched = false; + break; + } + return matched; +} + +bool tryMatchString(const sp& uidMap, const FieldValue& fieldValue, + const string& str_match) { + if (isAttributionUidField(fieldValue) || isUidField(fieldValue)) { + int uid = fieldValue.mValue.int_value; + auto aidIt = UidMap::sAidToUidMapping.find(str_match); + if (aidIt != UidMap::sAidToUidMapping.end()) { + return ((int)aidIt->second) == uid; + } + std::set packageNames = uidMap->getAppNamesFromUid(uid, true /* normalize*/); + return packageNames.find(str_match) != packageNames.end(); + } else if (fieldValue.mValue.getType() == STRING) { + return fieldValue.mValue.str_value == str_match; + } + return false; +} + +bool matchesSimple(const sp& uidMap, const FieldValueMatcher& matcher, + const vector& values, int start, int end, int depth) { + if (depth > 2) { + ALOGE("Depth > 3 not supported"); + return false; + } + + if (start >= end) { + return false; + } + + // Filter by entry field first + int newStart = -1; + int newEnd = end; + // because the fields are naturally sorted in the DFS order. we can safely + // break when pos is larger than the one we are searching for. + for (int i = start; i < end; i++) { + int pos = values[i].mField.getPosAtDepth(depth); + if (pos == matcher.field()) { + if (newStart == -1) { + newStart = i; + } + newEnd = i + 1; + } else if (pos > matcher.field()) { + break; + } + } + + // Now we have zoomed in to a new range + start = newStart; + end = newEnd; + + if (start == -1) { + // No such field found. + return false; + } + + vector> ranges; // the ranges are for matching ANY position + if (matcher.has_position()) { + // Repeated fields position is stored as a node in the path. + depth++; + if (depth > 2) { + return false; + } + switch (matcher.position()) { + case Position::FIRST: { + for (int i = start; i < end; i++) { + int pos = values[i].mField.getPosAtDepth(depth); + if (pos != 1) { + // Again, the log elements are stored in sorted order. so + // once the position is > 1, we break; + end = i; + break; + } + } + ranges.push_back(std::make_pair(start, end)); + break; + } + case Position::LAST: { + // move the starting index to the first LAST field at the depth. + for (int i = start; i < end; i++) { + if (values[i].mField.isLastPos(depth)) { + start = i; + break; + } + } + ranges.push_back(std::make_pair(start, end)); + break; + } + case Position::ANY: { + // ANY means all the children matchers match in any of the sub trees, it's a match + newStart = start; + newEnd = end; + // Here start is guaranteed to be a valid index. + int currentPos = values[start].mField.getPosAtDepth(depth); + // Now find all sub trees ranges. + for (int i = start; i < end; i++) { + int newPos = values[i].mField.getPosAtDepth(depth); + if (newPos != currentPos) { + ranges.push_back(std::make_pair(newStart, i)); + newStart = i; + currentPos = newPos; + } + } + ranges.push_back(std::make_pair(newStart, end)); + break; + } + case Position::ALL: + ALOGE("Not supported: field matcher with ALL position."); + break; + case Position::POSITION_UNKNOWN: + break; + } + } else { + // No position + ranges.push_back(std::make_pair(start, end)); + } + // start and end are still pointing to the matched range. + switch (matcher.value_matcher_case()) { + case FieldValueMatcher::kMatchesTuple: { + ++depth; + // If any range matches all matchers, good. + for (const auto& range : ranges) { + bool matched = true; + for (const auto& subMatcher : matcher.matches_tuple().field_value_matcher()) { + if (!matchesSimple(uidMap, subMatcher, values, range.first, range.second, + depth)) { + matched = false; + break; + } + } + if (matched) return true; + } + return false; + } + // Finally, we get to the point of real value matching. + // If the field matcher ends with ANY, then we have [start, end) range > 1. + // In the following, we should return true, when ANY of the values matches. + case FieldValueMatcher::ValueMatcherCase::kEqBool: { + for (int i = start; i < end; i++) { + if ((values[i].mValue.getType() == INT && + (values[i].mValue.int_value != 0) == matcher.eq_bool()) || + (values[i].mValue.getType() == LONG && + (values[i].mValue.long_value != 0) == matcher.eq_bool())) { + return true; + } + } + return false; + } + case FieldValueMatcher::ValueMatcherCase::kEqString: { + for (int i = start; i < end; i++) { + if (tryMatchString(uidMap, values[i], matcher.eq_string())) { + return true; + } + } + return false; + } + case FieldValueMatcher::ValueMatcherCase::kNeqAnyString: { + const auto& str_list = matcher.neq_any_string(); + for (int i = start; i < end; i++) { + bool notEqAll = true; + for (const auto& str : str_list.str_value()) { + if (tryMatchString(uidMap, values[i], str)) { + notEqAll = false; + break; + } + } + if (notEqAll) { + return true; + } + } + return false; + } + case FieldValueMatcher::ValueMatcherCase::kEqAnyString: { + const auto& str_list = matcher.eq_any_string(); + for (int i = start; i < end; i++) { + for (const auto& str : str_list.str_value()) { + if (tryMatchString(uidMap, values[i], str)) { + return true; + } + } + } + return false; + } + case FieldValueMatcher::ValueMatcherCase::kEqInt: { + for (int i = start; i < end; i++) { + if (values[i].mValue.getType() == INT && + (matcher.eq_int() == values[i].mValue.int_value)) { + return true; + } + // eq_int covers both int and long. + if (values[i].mValue.getType() == LONG && + (matcher.eq_int() == values[i].mValue.long_value)) { + return true; + } + } + return false; + } + case FieldValueMatcher::ValueMatcherCase::kLtInt: { + for (int i = start; i < end; i++) { + if (values[i].mValue.getType() == INT && + (values[i].mValue.int_value < matcher.lt_int())) { + return true; + } + // lt_int covers both int and long. + if (values[i].mValue.getType() == LONG && + (values[i].mValue.long_value < matcher.lt_int())) { + return true; + } + } + return false; + } + case FieldValueMatcher::ValueMatcherCase::kGtInt: { + for (int i = start; i < end; i++) { + if (values[i].mValue.getType() == INT && + (values[i].mValue.int_value > matcher.gt_int())) { + return true; + } + // gt_int covers both int and long. + if (values[i].mValue.getType() == LONG && + (values[i].mValue.long_value > matcher.gt_int())) { + return true; + } + } + return false; + } + case FieldValueMatcher::ValueMatcherCase::kLtFloat: { + for (int i = start; i < end; i++) { + if (values[i].mValue.getType() == FLOAT && + (values[i].mValue.float_value < matcher.lt_float())) { + return true; + } + } + return false; + } + case FieldValueMatcher::ValueMatcherCase::kGtFloat: { + for (int i = start; i < end; i++) { + if (values[i].mValue.getType() == FLOAT && + (values[i].mValue.float_value > matcher.gt_float())) { + return true; + } + } + return false; + } + case FieldValueMatcher::ValueMatcherCase::kLteInt: { + for (int i = start; i < end; i++) { + if (values[i].mValue.getType() == INT && + (values[i].mValue.int_value <= matcher.lte_int())) { + return true; + } + // lte_int covers both int and long. + if (values[i].mValue.getType() == LONG && + (values[i].mValue.long_value <= matcher.lte_int())) { + return true; + } + } + return false; + } + case FieldValueMatcher::ValueMatcherCase::kGteInt: { + for (int i = start; i < end; i++) { + if (values[i].mValue.getType() == INT && + (values[i].mValue.int_value >= matcher.gte_int())) { + return true; + } + // gte_int covers both int and long. + if (values[i].mValue.getType() == LONG && + (values[i].mValue.long_value >= matcher.gte_int())) { + return true; + } + } + return false; + } + default: + return false; + } +} + +bool matchesSimple(const sp& uidMap, const SimpleAtomMatcher& simpleMatcher, + const LogEvent& event) { + if (event.GetTagId() != simpleMatcher.atom_id()) { + return false; + } + + for (const auto& matcher : simpleMatcher.field_value_matcher()) { + if (!matchesSimple(uidMap, matcher, event.getValues(), 0, event.getValues().size(), 0)) { + return false; + } + } + return true; +} + +} // namespace statsd +} // namespace os +} // namespace android diff --git a/statsd/src/matchers/matcher_util.h b/statsd/src/matchers/matcher_util.h new file mode 100644 index 00000000..597b74f7 --- /dev/null +++ b/statsd/src/matchers/matcher_util.h @@ -0,0 +1,44 @@ +/* + * 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. + */ + +#pragma once + +#include "logd/LogEvent.h" + +#include +#include "src/statsd_config.pb.h" +#include "packages/UidMap.h" +#include "stats_util.h" + +namespace android { +namespace os { +namespace statsd { + +enum MatchingState { + kNotComputed = -1, + kNotMatched = 0, + kMatched = 1, +}; + +bool combinationMatch(const std::vector& children, const LogicalOperation& operation, + const std::vector& matcherResults); + +bool matchesSimple(const sp& uidMap, const SimpleAtomMatcher& simpleMatcher, + const LogEvent& wrapper); + +} // namespace statsd +} // namespace os +} // namespace android diff --git a/statsd/src/metadata_util.cpp b/statsd/src/metadata_util.cpp new file mode 100644 index 00000000..27ee59b3 --- /dev/null +++ b/statsd/src/metadata_util.cpp @@ -0,0 +1,122 @@ +/* + * Copyright (C) 2020 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 "FieldValue.h" +#include "metadata_util.h" + +namespace android { +namespace os { +namespace statsd { + +using google::protobuf::RepeatedPtrField; + +void writeValueToProto(metadata::FieldValue* metadataFieldValue, const Value& value) { + std::string storage_value; + switch (value.getType()) { + case INT: + metadataFieldValue->set_value_int(value.int_value); + break; + case LONG: + metadataFieldValue->set_value_long(value.long_value); + break; + case FLOAT: + metadataFieldValue->set_value_float(value.float_value); + break; + case DOUBLE: + metadataFieldValue->set_value_double(value.double_value); + break; + case STRING: + metadataFieldValue->set_value_str(value.str_value.c_str()); + break; + case STORAGE: // byte array + storage_value = ((char*) value.storage_value.data()); + metadataFieldValue->set_value_storage(storage_value); + break; + default: + break; + } +} + +void writeMetricDimensionKeyToMetadataDimensionKey( + const MetricDimensionKey& metricKey, + metadata::MetricDimensionKey* metadataMetricKey) { + for (const FieldValue& fieldValue : metricKey.getDimensionKeyInWhat().getValues()) { + metadata::FieldValue* metadataFieldValue = metadataMetricKey->add_dimension_key_in_what(); + metadata::Field* metadataField = metadataFieldValue->mutable_field(); + metadataField->set_tag(fieldValue.mField.getTag()); + metadataField->set_field(fieldValue.mField.getField()); + writeValueToProto(metadataFieldValue, fieldValue.mValue); + } + + for (const FieldValue& fieldValue : metricKey.getStateValuesKey().getValues()) { + metadata::FieldValue* metadataFieldValue = metadataMetricKey->add_state_values_key(); + metadata::Field* metadataField = metadataFieldValue->mutable_field(); + metadataField->set_tag(fieldValue.mField.getTag()); + metadataField->set_field(fieldValue.mField.getField()); + writeValueToProto(metadataFieldValue, fieldValue.mValue); + } +} + +void writeFieldValuesFromMetadata( + const RepeatedPtrField& repeatedFieldValueList, + std::vector* fieldValues) { + for (const metadata::FieldValue& metadataFieldValue : repeatedFieldValueList) { + Field field(metadataFieldValue.field().tag(), metadataFieldValue.field().field()); + Value value; + switch (metadataFieldValue.value_case()) { + case metadata::FieldValue::ValueCase::kValueInt: + value = Value(metadataFieldValue.value_int()); + break; + case metadata::FieldValue::ValueCase::kValueLong: + value = Value(metadataFieldValue.value_long()); + break; + case metadata::FieldValue::ValueCase::kValueFloat: + value = Value(metadataFieldValue.value_float()); + break; + case metadata::FieldValue::ValueCase::kValueDouble: + value = Value(metadataFieldValue.value_double()); + break; + case metadata::FieldValue::ValueCase::kValueStr: + value = Value(metadataFieldValue.value_str()); + break; + case metadata::FieldValue::ValueCase::kValueStorage: + value = Value(metadataFieldValue.value_storage()); + break; + default: + break; + } + FieldValue fieldValue(field, value); + fieldValues->emplace_back(field, value); + } +} + +MetricDimensionKey loadMetricDimensionKeyFromProto( + const metadata::MetricDimensionKey& metricDimensionKey) { + std::vector dimKeyInWhatFieldValues; + writeFieldValuesFromMetadata(metricDimensionKey.dimension_key_in_what(), + &dimKeyInWhatFieldValues); + std::vector stateValuesFieldValues; + writeFieldValuesFromMetadata(metricDimensionKey.state_values_key(), &stateValuesFieldValues); + + HashableDimensionKey dimKeyInWhat(dimKeyInWhatFieldValues); + HashableDimensionKey stateValues(stateValuesFieldValues); + MetricDimensionKey metricKey(dimKeyInWhat, stateValues); + return metricKey; +} + +} // namespace statsd +} // namespace os +} // namespace android \ No newline at end of file diff --git a/statsd/src/metadata_util.h b/statsd/src/metadata_util.h new file mode 100644 index 00000000..cb925e44 --- /dev/null +++ b/statsd/src/metadata_util.h @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2020 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 "HashableDimensionKey.h" + +#include "src/statsd_metadata.pb.h" // AlertMetadata + +namespace android { +namespace os { +namespace statsd { + +void writeMetricDimensionKeyToMetadataDimensionKey(const MetricDimensionKey& metricKey, + metadata::MetricDimensionKey* metadataMetricKey); + +MetricDimensionKey loadMetricDimensionKeyFromProto( + const metadata::MetricDimensionKey& metricDimensionKey); + +} // namespace statsd +} // namespace os +} // namespace android \ No newline at end of file diff --git a/statsd/src/metrics/CountMetricProducer.cpp b/statsd/src/metrics/CountMetricProducer.cpp new file mode 100644 index 00000000..a8ef54a3 --- /dev/null +++ b/statsd/src/metrics/CountMetricProducer.cpp @@ -0,0 +1,440 @@ +/* + * 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. + */ + +#define DEBUG false // STOPSHIP if true +#include "Log.h" + +#include "CountMetricProducer.h" + +#include +#include +#include + +#include "guardrail/StatsdStats.h" +#include "metrics/parsing_utils/metrics_manager_util.h" +#include "stats_log_util.h" +#include "stats_util.h" + +using android::util::FIELD_COUNT_REPEATED; +using android::util::FIELD_TYPE_BOOL; +using android::util::FIELD_TYPE_FLOAT; +using android::util::FIELD_TYPE_INT32; +using android::util::FIELD_TYPE_INT64; +using android::util::FIELD_TYPE_MESSAGE; +using android::util::FIELD_TYPE_STRING; +using android::util::ProtoOutputStream; +using std::map; +using std::string; +using std::unordered_map; +using std::vector; +using std::shared_ptr; + +namespace android { +namespace os { +namespace statsd { + +// for StatsLogReport +const int FIELD_ID_ID = 1; +const int FIELD_ID_COUNT_METRICS = 5; +const int FIELD_ID_TIME_BASE = 9; +const int FIELD_ID_BUCKET_SIZE = 10; +const int FIELD_ID_DIMENSION_PATH_IN_WHAT = 11; +const int FIELD_ID_IS_ACTIVE = 14; + +// for CountMetricDataWrapper +const int FIELD_ID_DATA = 1; +// for CountMetricData +const int FIELD_ID_DIMENSION_IN_WHAT = 1; +const int FIELD_ID_SLICE_BY_STATE = 6; +const int FIELD_ID_BUCKET_INFO = 3; +const int FIELD_ID_DIMENSION_LEAF_IN_WHAT = 4; +// for CountBucketInfo +const int FIELD_ID_COUNT = 3; +const int FIELD_ID_BUCKET_NUM = 4; +const int FIELD_ID_START_BUCKET_ELAPSED_MILLIS = 5; +const int FIELD_ID_END_BUCKET_ELAPSED_MILLIS = 6; + +CountMetricProducer::CountMetricProducer( + const ConfigKey& key, const CountMetric& metric, const int conditionIndex, + const vector& initialConditionCache, const sp& wizard, + const uint64_t protoHash, const int64_t timeBaseNs, const int64_t startTimeNs, + const unordered_map>& eventActivationMap, + const unordered_map>>& eventDeactivationMap, + const vector& slicedStateAtoms, + const unordered_map>& stateGroupMap) + : MetricProducer(metric.id(), key, timeBaseNs, conditionIndex, initialConditionCache, wizard, + protoHash, eventActivationMap, eventDeactivationMap, slicedStateAtoms, + stateGroupMap) { + if (metric.has_bucket()) { + mBucketSizeNs = + TimeUnitToBucketSizeInMillisGuardrailed(key.GetUid(), metric.bucket()) * 1000000; + } else { + mBucketSizeNs = LLONG_MAX; + } + + if (metric.has_dimensions_in_what()) { + translateFieldMatcher(metric.dimensions_in_what(), &mDimensionsInWhat); + mContainANYPositionInDimensionsInWhat = HasPositionANY(metric.dimensions_in_what()); + } + + mSliceByPositionALL = HasPositionALL(metric.dimensions_in_what()); + + if (metric.links().size() > 0) { + for (const auto& link : metric.links()) { + Metric2Condition mc; + mc.conditionId = link.condition(); + translateFieldMatcher(link.fields_in_what(), &mc.metricFields); + translateFieldMatcher(link.fields_in_condition(), &mc.conditionFields); + mMetric2ConditionLinks.push_back(mc); + } + mConditionSliced = true; + } + + for (const auto& stateLink : metric.state_link()) { + Metric2State ms; + ms.stateAtomId = stateLink.state_atom_id(); + translateFieldMatcher(stateLink.fields_in_what(), &ms.metricFields); + translateFieldMatcher(stateLink.fields_in_state(), &ms.stateFields); + mMetric2StateLinks.push_back(ms); + } + + flushIfNeededLocked(startTimeNs); + // Adjust start for partial bucket + mCurrentBucketStartTimeNs = startTimeNs; + + VLOG("metric %lld created. bucket size %lld start_time: %lld", (long long)metric.id(), + (long long)mBucketSizeNs, (long long)mTimeBaseNs); +} + +CountMetricProducer::~CountMetricProducer() { + VLOG("~CountMetricProducer() called"); +} + +bool CountMetricProducer::onConfigUpdatedLocked( + const StatsdConfig& config, const int configIndex, const int metricIndex, + const vector>& allAtomMatchingTrackers, + const unordered_map& oldAtomMatchingTrackerMap, + const unordered_map& newAtomMatchingTrackerMap, + const sp& matcherWizard, + const vector>& allConditionTrackers, + const unordered_map& conditionTrackerMap, const sp& wizard, + const unordered_map& metricToActivationMap, + unordered_map>& trackerToMetricMap, + unordered_map>& conditionToMetricMap, + unordered_map>& activationAtomTrackerToMetricMap, + unordered_map>& deactivationAtomTrackerToMetricMap, + vector& metricsWithActivation) { + if (!MetricProducer::onConfigUpdatedLocked( + config, configIndex, metricIndex, allAtomMatchingTrackers, + oldAtomMatchingTrackerMap, newAtomMatchingTrackerMap, matcherWizard, + allConditionTrackers, conditionTrackerMap, wizard, metricToActivationMap, + trackerToMetricMap, conditionToMetricMap, activationAtomTrackerToMetricMap, + deactivationAtomTrackerToMetricMap, metricsWithActivation)) { + return false; + } + + const CountMetric& metric = config.count_metric(configIndex); + int trackerIndex; + // Update appropriate indices, specifically mConditionIndex and MetricsManager maps. + if (!handleMetricWithAtomMatchingTrackers(metric.what(), metricIndex, false, + allAtomMatchingTrackers, newAtomMatchingTrackerMap, + trackerToMetricMap, trackerIndex)) { + return false; + } + + if (metric.has_condition() && + !handleMetricWithConditions(metric.condition(), metricIndex, conditionTrackerMap, + metric.links(), allConditionTrackers, mConditionTrackerIndex, + conditionToMetricMap)) { + return false; + } + return true; +} + +void CountMetricProducer::onStateChanged(const int64_t eventTimeNs, const int32_t atomId, + const HashableDimensionKey& primaryKey, + const FieldValue& oldState, const FieldValue& newState) { + VLOG("CountMetric %lld onStateChanged time %lld, State%d, key %s, %d -> %d", + (long long)mMetricId, (long long)eventTimeNs, atomId, primaryKey.toString().c_str(), + oldState.mValue.int_value, newState.mValue.int_value); +} + +void CountMetricProducer::dumpStatesLocked(FILE* out, bool verbose) const { + if (mCurrentSlicedCounter == nullptr || + mCurrentSlicedCounter->size() == 0) { + return; + } + + fprintf(out, "CountMetric %lld dimension size %lu\n", (long long)mMetricId, + (unsigned long)mCurrentSlicedCounter->size()); + if (verbose) { + for (const auto& it : *mCurrentSlicedCounter) { + fprintf(out, "\t(what)%s\t(state)%s %lld\n", + it.first.getDimensionKeyInWhat().toString().c_str(), + it.first.getStateValuesKey().toString().c_str(), (unsigned long long)it.second); + } + } +} + +void CountMetricProducer::onSlicedConditionMayChangeLocked(bool overallCondition, + const int64_t eventTime) { + VLOG("Metric %lld onSlicedConditionMayChange", (long long)mMetricId); +} + + +void CountMetricProducer::clearPastBucketsLocked(const int64_t dumpTimeNs) { + mPastBuckets.clear(); +} + +void CountMetricProducer::onDumpReportLocked(const int64_t dumpTimeNs, + const bool include_current_partial_bucket, + const bool erase_data, + const DumpLatency dumpLatency, + std::set *str_set, + ProtoOutputStream* protoOutput) { + if (include_current_partial_bucket) { + flushLocked(dumpTimeNs); + } else { + flushIfNeededLocked(dumpTimeNs); + } + protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_ID, (long long)mMetricId); + protoOutput->write(FIELD_TYPE_BOOL | FIELD_ID_IS_ACTIVE, isActiveLocked()); + + + if (mPastBuckets.empty()) { + return; + } + protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_TIME_BASE, (long long)mTimeBaseNs); + protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_BUCKET_SIZE, (long long)mBucketSizeNs); + + // Fills the dimension path if not slicing by ALL. + if (!mSliceByPositionALL) { + if (!mDimensionsInWhat.empty()) { + uint64_t dimenPathToken = protoOutput->start( + FIELD_TYPE_MESSAGE | FIELD_ID_DIMENSION_PATH_IN_WHAT); + writeDimensionPathToProto(mDimensionsInWhat, protoOutput); + protoOutput->end(dimenPathToken); + } + } + + uint64_t protoToken = protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_ID_COUNT_METRICS); + + for (const auto& counter : mPastBuckets) { + const MetricDimensionKey& dimensionKey = counter.first; + VLOG(" dimension key %s", dimensionKey.toString().c_str()); + + uint64_t wrapperToken = + protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_DATA); + + // First fill dimension. + if (mSliceByPositionALL) { + uint64_t dimensionToken = protoOutput->start( + FIELD_TYPE_MESSAGE | FIELD_ID_DIMENSION_IN_WHAT); + writeDimensionToProto(dimensionKey.getDimensionKeyInWhat(), str_set, protoOutput); + protoOutput->end(dimensionToken); + } else { + writeDimensionLeafNodesToProto(dimensionKey.getDimensionKeyInWhat(), + FIELD_ID_DIMENSION_LEAF_IN_WHAT, str_set, protoOutput); + } + // Then fill slice_by_state. + for (auto state : dimensionKey.getStateValuesKey().getValues()) { + uint64_t stateToken = protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | + FIELD_ID_SLICE_BY_STATE); + writeStateToProto(state, protoOutput); + protoOutput->end(stateToken); + } + // Then fill bucket_info (CountBucketInfo). + for (const auto& bucket : counter.second) { + uint64_t bucketInfoToken = protoOutput->start( + FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_BUCKET_INFO); + // Partial bucket. + if (bucket.mBucketEndNs - bucket.mBucketStartNs != mBucketSizeNs) { + protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_START_BUCKET_ELAPSED_MILLIS, + (long long)NanoToMillis(bucket.mBucketStartNs)); + protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_END_BUCKET_ELAPSED_MILLIS, + (long long)NanoToMillis(bucket.mBucketEndNs)); + } else { + protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_BUCKET_NUM, + (long long)(getBucketNumFromEndTimeNs(bucket.mBucketEndNs))); + } + protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_COUNT, (long long)bucket.mCount); + protoOutput->end(bucketInfoToken); + VLOG("\t bucket [%lld - %lld] count: %lld", (long long)bucket.mBucketStartNs, + (long long)bucket.mBucketEndNs, (long long)bucket.mCount); + } + protoOutput->end(wrapperToken); + } + + protoOutput->end(protoToken); + + if (erase_data) { + mPastBuckets.clear(); + } +} + +void CountMetricProducer::dropDataLocked(const int64_t dropTimeNs) { + flushIfNeededLocked(dropTimeNs); + StatsdStats::getInstance().noteBucketDropped(mMetricId); + mPastBuckets.clear(); +} + +void CountMetricProducer::onConditionChangedLocked(const bool conditionMet, + const int64_t eventTime) { + VLOG("Metric %lld onConditionChanged", (long long)mMetricId); + mCondition = conditionMet ? ConditionState::kTrue : ConditionState::kFalse; +} + +bool CountMetricProducer::hitGuardRailLocked(const MetricDimensionKey& newKey) { + if (mCurrentSlicedCounter->find(newKey) != mCurrentSlicedCounter->end()) { + return false; + } + // ===========GuardRail============== + // 1. Report the tuple count if the tuple count > soft limit + if (mCurrentSlicedCounter->size() > StatsdStats::kDimensionKeySizeSoftLimit - 1) { + size_t newTupleCount = mCurrentSlicedCounter->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) { + ALOGE("CountMetric %lld dropping data for dimension key %s", + (long long)mMetricId, newKey.toString().c_str()); + StatsdStats::getInstance().noteHardDimensionLimitReached(mMetricId); + return true; + } + } + + return false; +} + +void CountMetricProducer::onMatchedLogEventInternalLocked( + const size_t matcherIndex, const MetricDimensionKey& eventKey, + const ConditionKey& conditionKey, bool condition, const LogEvent& event, + const map& statePrimaryKeys) { + int64_t eventTimeNs = event.GetElapsedTimestampNs(); + flushIfNeededLocked(eventTimeNs); + + if (!condition) { + return; + } + + auto it = mCurrentSlicedCounter->find(eventKey); + if (it == mCurrentSlicedCounter->end()) { + // ===========GuardRail============== + if (hitGuardRailLocked(eventKey)) { + return; + } + // create a counter for the new key + (*mCurrentSlicedCounter)[eventKey] = 1; + } else { + // increment the existing value + auto& count = it->second; + count++; + } + for (auto& tracker : mAnomalyTrackers) { + int64_t countWholeBucket = mCurrentSlicedCounter->find(eventKey)->second; + auto prev = mCurrentFullCounters->find(eventKey); + if (prev != mCurrentFullCounters->end()) { + countWholeBucket += prev->second; + } + tracker->detectAndDeclareAnomaly(eventTimeNs, mCurrentBucketNum, mMetricId, eventKey, + countWholeBucket); + } + + VLOG("metric %lld %s->%lld", (long long)mMetricId, eventKey.toString().c_str(), + (long long)(*mCurrentSlicedCounter)[eventKey]); +} + +// When a new matched event comes in, we check if event falls into the current +// bucket. If not, flush the old counter to past buckets and initialize the new bucket. +void CountMetricProducer::flushIfNeededLocked(const int64_t& eventTimeNs) { + int64_t currentBucketEndTimeNs = getCurrentBucketEndTimeNs(); + if (eventTimeNs < currentBucketEndTimeNs) { + return; + } + + // Setup the bucket start time and number. + int64_t numBucketsForward = 1 + (eventTimeNs - currentBucketEndTimeNs) / mBucketSizeNs; + int64_t nextBucketNs = currentBucketEndTimeNs + (numBucketsForward - 1) * mBucketSizeNs; + flushCurrentBucketLocked(eventTimeNs, nextBucketNs); + + mCurrentBucketNum += numBucketsForward; + VLOG("metric %lld: new bucket start time: %lld", (long long)mMetricId, + (long long)mCurrentBucketStartTimeNs); +} + +void CountMetricProducer::flushCurrentBucketLocked(const int64_t& eventTimeNs, + const int64_t& nextBucketStartTimeNs) { + int64_t fullBucketEndTimeNs = getCurrentBucketEndTimeNs(); + CountBucket info; + info.mBucketStartNs = mCurrentBucketStartTimeNs; + if (eventTimeNs < fullBucketEndTimeNs) { + info.mBucketEndNs = eventTimeNs; + } else { + info.mBucketEndNs = fullBucketEndTimeNs; + } + for (const auto& counter : *mCurrentSlicedCounter) { + info.mCount = counter.second; + auto& bucketList = mPastBuckets[counter.first]; + bucketList.push_back(info); + VLOG("metric %lld, dump key value: %s -> %lld", (long long)mMetricId, + counter.first.toString().c_str(), + (long long)counter.second); + } + + // If we have finished a full bucket, then send this to anomaly tracker. + if (eventTimeNs > fullBucketEndTimeNs) { + // Accumulate partial buckets with current value and then send to anomaly tracker. + if (mCurrentFullCounters->size() > 0) { + for (const auto& keyValuePair : *mCurrentSlicedCounter) { + (*mCurrentFullCounters)[keyValuePair.first] += keyValuePair.second; + } + for (auto& tracker : mAnomalyTrackers) { + tracker->addPastBucket(mCurrentFullCounters, mCurrentBucketNum); + } + mCurrentFullCounters = std::make_shared(); + } else { + // Skip aggregating the partial buckets since there's no previous partial bucket. + for (auto& tracker : mAnomalyTrackers) { + tracker->addPastBucket(mCurrentSlicedCounter, mCurrentBucketNum); + } + } + } else { + // Accumulate partial bucket. + for (const auto& keyValuePair : *mCurrentSlicedCounter) { + (*mCurrentFullCounters)[keyValuePair.first] += keyValuePair.second; + } + } + + StatsdStats::getInstance().noteBucketCount(mMetricId); + // Only resets the counters, but doesn't setup the times nor numbers. + // (Do not clear since the old one is still referenced in mAnomalyTrackers). + mCurrentSlicedCounter = std::make_shared(); + mCurrentBucketStartTimeNs = nextBucketStartTimeNs; +} + +// Rough estimate of CountMetricProducer buffer stored. This number will be +// greater than actual data size as it contains each dimension of +// CountMetricData is duplicated. +size_t CountMetricProducer::byteSizeLocked() const { + size_t totalSize = 0; + for (const auto& pair : mPastBuckets) { + totalSize += pair.second.size() * kBucketSize; + } + return totalSize; +} + +} // namespace statsd +} // namespace os +} // namespace android diff --git a/statsd/src/metrics/CountMetricProducer.h b/statsd/src/metrics/CountMetricProducer.h new file mode 100644 index 00000000..90c3ac8f --- /dev/null +++ b/statsd/src/metrics/CountMetricProducer.h @@ -0,0 +1,143 @@ +/* + * 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. + */ + +#ifndef COUNT_METRIC_PRODUCER_H +#define COUNT_METRIC_PRODUCER_H + +#include +#include + +#include + +#include "MetricProducer.h" +#include "anomaly/AnomalyTracker.h" +#include "condition/ConditionTracker.h" +#include "src/statsd_config.pb.h" +#include "matchers/matcher_util.h" +#include "stats_util.h" + +namespace android { +namespace os { +namespace statsd { + +struct CountBucket { + int64_t mBucketStartNs; + int64_t mBucketEndNs; + int64_t mCount; +}; + +class CountMetricProducer : public MetricProducer { +public: + CountMetricProducer( + const ConfigKey& key, const CountMetric& countMetric, const int conditionIndex, + const vector& initialConditionCache, const sp& wizard, + const uint64_t protoHash, const int64_t timeBaseNs, const int64_t startTimeNs, + const std::unordered_map>& eventActivationMap = {}, + const std::unordered_map>>& + eventDeactivationMap = {}, + const vector& slicedStateAtoms = {}, + const unordered_map>& stateGroupMap = {}); + + virtual ~CountMetricProducer(); + + void onStateChanged(const int64_t eventTimeNs, const int32_t atomId, + const HashableDimensionKey& primaryKey, const FieldValue& oldState, + const FieldValue& newState) override; + + MetricType getMetricType() const override { + return METRIC_TYPE_COUNT; + } + +protected: + void onMatchedLogEventInternalLocked( + const size_t matcherIndex, const MetricDimensionKey& eventKey, + const ConditionKey& conditionKey, bool condition, const LogEvent& event, + const std::map& statePrimaryKeys) override; + +private: + + void onDumpReportLocked(const int64_t dumpTimeNs, + const bool include_current_partial_bucket, + const bool erase_data, + const DumpLatency dumpLatency, + std::set *str_set, + android::util::ProtoOutputStream* protoOutput) override; + + void clearPastBucketsLocked(const int64_t dumpTimeNs) override; + + // Internal interface to handle condition change. + void onConditionChangedLocked(const bool conditionMet, const int64_t eventTime) override; + + // Internal interface to handle sliced condition change. + void onSlicedConditionMayChangeLocked(bool overallCondition, const int64_t eventTime) override; + + // Internal function to calculate the current used bytes. + size_t byteSizeLocked() const override; + + void dumpStatesLocked(FILE* out, bool verbose) const override; + + void dropDataLocked(const int64_t dropTimeNs) override; + + // Util function to flush the old packet. + void flushIfNeededLocked(const int64_t& newEventTime) override; + + void flushCurrentBucketLocked(const int64_t& eventTimeNs, + const int64_t& nextBucketStartTimeNs) override; + + bool onConfigUpdatedLocked( + const StatsdConfig& config, const int configIndex, const int metricIndex, + const std::vector>& allAtomMatchingTrackers, + const std::unordered_map& oldAtomMatchingTrackerMap, + const std::unordered_map& newAtomMatchingTrackerMap, + const sp& matcherWizard, + const std::vector>& allConditionTrackers, + const std::unordered_map& conditionTrackerMap, + const sp& wizard, + const std::unordered_map& metricToActivationMap, + std::unordered_map>& trackerToMetricMap, + std::unordered_map>& conditionToMetricMap, + std::unordered_map>& activationAtomTrackerToMetricMap, + std::unordered_map>& deactivationAtomTrackerToMetricMap, + std::vector& metricsWithActivation) override; + + std::unordered_map> mPastBuckets; + + // The current bucket (may be a partial bucket). + std::shared_ptr mCurrentSlicedCounter = std::make_shared(); + + // The sum of previous partial buckets in the current full bucket (excluding the current + // partial bucket). This is only updated while flushing the current bucket. + std::shared_ptr mCurrentFullCounters = std::make_shared(); + + static const size_t kBucketSize = sizeof(CountBucket{}); + + bool hitGuardRailLocked(const MetricDimensionKey& newKey); + + FRIEND_TEST(CountMetricProducerTest, TestNonDimensionalEvents); + FRIEND_TEST(CountMetricProducerTest, TestEventsWithNonSlicedCondition); + FRIEND_TEST(CountMetricProducerTest, TestEventsWithSlicedCondition); + FRIEND_TEST(CountMetricProducerTest, TestAnomalyDetectionUnSliced); + FRIEND_TEST(CountMetricProducerTest, TestFirstBucket); + FRIEND_TEST(CountMetricProducerTest, TestOneWeekTimeUnit); + + FRIEND_TEST(CountMetricProducerTest_PartialBucket, TestSplitInCurrentBucket); + FRIEND_TEST(CountMetricProducerTest_PartialBucket, TestSplitInNextBucket); +}; + +} // namespace statsd +} // namespace os +} // namespace android +#endif // COUNT_METRIC_PRODUCER_H diff --git a/statsd/src/metrics/DurationMetricProducer.cpp b/statsd/src/metrics/DurationMetricProducer.cpp new file mode 100644 index 00000000..733fb388 --- /dev/null +++ b/statsd/src/metrics/DurationMetricProducer.cpp @@ -0,0 +1,822 @@ +/* + * 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. + */ + +#define DEBUG false + +#include "Log.h" + +#include "DurationMetricProducer.h" + +#include +#include + +#include "guardrail/StatsdStats.h" +#include "metrics/parsing_utils/metrics_manager_util.h" +#include "stats_log_util.h" +#include "stats_util.h" + +using android::util::FIELD_COUNT_REPEATED; +using android::util::FIELD_TYPE_BOOL; +using android::util::FIELD_TYPE_FLOAT; +using android::util::FIELD_TYPE_INT32; +using android::util::FIELD_TYPE_INT64; +using android::util::FIELD_TYPE_MESSAGE; +using android::util::FIELD_TYPE_STRING; +using android::util::ProtoOutputStream; +using std::string; +using std::unordered_map; +using std::vector; +using std::shared_ptr; + +namespace android { +namespace os { +namespace statsd { + +// for StatsLogReport +const int FIELD_ID_ID = 1; +const int FIELD_ID_DURATION_METRICS = 6; +const int FIELD_ID_TIME_BASE = 9; +const int FIELD_ID_BUCKET_SIZE = 10; +const int FIELD_ID_DIMENSION_PATH_IN_WHAT = 11; +const int FIELD_ID_IS_ACTIVE = 14; +// for DurationMetricDataWrapper +const int FIELD_ID_DATA = 1; +// for DurationMetricData +const int FIELD_ID_DIMENSION_IN_WHAT = 1; +const int FIELD_ID_BUCKET_INFO = 3; +const int FIELD_ID_DIMENSION_LEAF_IN_WHAT = 4; +const int FIELD_ID_SLICE_BY_STATE = 6; +// for DurationBucketInfo +const int FIELD_ID_DURATION = 3; +const int FIELD_ID_BUCKET_NUM = 4; +const int FIELD_ID_START_BUCKET_ELAPSED_MILLIS = 5; +const int FIELD_ID_END_BUCKET_ELAPSED_MILLIS = 6; + +DurationMetricProducer::DurationMetricProducer( + const ConfigKey& key, const DurationMetric& metric, const int conditionIndex, + const vector& initialConditionCache, const int whatIndex, + const int startIndex, const int stopIndex, const int stopAllIndex, const bool nesting, + const sp& wizard, const uint64_t protoHash, + const FieldMatcher& internalDimensions, const int64_t timeBaseNs, const int64_t startTimeNs, + const unordered_map>& eventActivationMap, + const unordered_map>>& eventDeactivationMap, + const vector& slicedStateAtoms, + const unordered_map>& stateGroupMap) + : MetricProducer(metric.id(), key, timeBaseNs, conditionIndex, initialConditionCache, wizard, + protoHash, eventActivationMap, eventDeactivationMap, slicedStateAtoms, + stateGroupMap), + mAggregationType(metric.aggregation_type()), + mStartIndex(startIndex), + mStopIndex(stopIndex), + mStopAllIndex(stopAllIndex), + mNested(nesting), + mContainANYPositionInInternalDimensions(false) { + if (metric.has_bucket()) { + mBucketSizeNs = + TimeUnitToBucketSizeInMillisGuardrailed(key.GetUid(), metric.bucket()) * 1000000; + } else { + mBucketSizeNs = LLONG_MAX; + } + + if (metric.has_threshold()) { + mUploadThreshold = metric.threshold(); + } + + if (metric.has_dimensions_in_what()) { + translateFieldMatcher(metric.dimensions_in_what(), &mDimensionsInWhat); + mContainANYPositionInDimensionsInWhat = HasPositionANY(metric.dimensions_in_what()); + } + + if (internalDimensions.has_field()) { + translateFieldMatcher(internalDimensions, &mInternalDimensions); + mContainANYPositionInInternalDimensions = HasPositionANY(internalDimensions); + } + if (mContainANYPositionInInternalDimensions) { + ALOGE("Position ANY in internal dimension not supported."); + } + if (mContainANYPositionInDimensionsInWhat) { + ALOGE("Position ANY in dimension_in_what not supported."); + } + + // Dimensions in what must be subset of internal dimensions + if (!subsetDimensions(mDimensionsInWhat, mInternalDimensions)) { + ALOGE("Dimensions in what must be a subset of the internal dimensions"); + mValid = false; + } + + mSliceByPositionALL = HasPositionALL(metric.dimensions_in_what()); + + if (metric.links().size() > 0) { + for (const auto& link : metric.links()) { + Metric2Condition mc; + mc.conditionId = link.condition(); + translateFieldMatcher(link.fields_in_what(), &mc.metricFields); + translateFieldMatcher(link.fields_in_condition(), &mc.conditionFields); + if (!subsetDimensions(mc.metricFields, mInternalDimensions)) { + ALOGE(("Condition links must be a subset of the internal dimensions")); + mValid = false; + } + mMetric2ConditionLinks.push_back(mc); + } + mConditionSliced = true; + } + mUnSlicedPartCondition = ConditionState::kUnknown; + + for (const auto& stateLink : metric.state_link()) { + Metric2State ms; + ms.stateAtomId = stateLink.state_atom_id(); + translateFieldMatcher(stateLink.fields_in_what(), &ms.metricFields); + translateFieldMatcher(stateLink.fields_in_state(), &ms.stateFields); + if (!subsetDimensions(ms.metricFields, mInternalDimensions)) { + ALOGE(("State links must be a subset of the dimensions in what internal dimensions")); + mValid = false; + } + mMetric2StateLinks.push_back(ms); + } + + mUseWhatDimensionAsInternalDimension = equalDimensions(mDimensionsInWhat, mInternalDimensions); + if (mWizard != nullptr && mConditionTrackerIndex >= 0 && + mMetric2ConditionLinks.size() == 1) { + mHasLinksToAllConditionDimensionsInTracker = mWizard->equalOutputDimensions( + mConditionTrackerIndex, mMetric2ConditionLinks.begin()->conditionFields); + } + flushIfNeededLocked(startTimeNs); + // Adjust start for partial bucket + mCurrentBucketStartTimeNs = startTimeNs; + VLOG("metric %lld created. bucket size %lld start_time: %lld", (long long)metric.id(), + (long long)mBucketSizeNs, (long long)mTimeBaseNs); + + initTrueDimensions(whatIndex, startTimeNs); +} + +DurationMetricProducer::~DurationMetricProducer() { + VLOG("~DurationMetric() called"); +} + +bool DurationMetricProducer::onConfigUpdatedLocked( + const StatsdConfig& config, const int configIndex, const int metricIndex, + const vector>& allAtomMatchingTrackers, + const unordered_map& oldAtomMatchingTrackerMap, + const unordered_map& newAtomMatchingTrackerMap, + const sp& matcherWizard, + const vector>& allConditionTrackers, + const unordered_map& conditionTrackerMap, const sp& wizard, + const unordered_map& metricToActivationMap, + unordered_map>& trackerToMetricMap, + unordered_map>& conditionToMetricMap, + unordered_map>& activationAtomTrackerToMetricMap, + unordered_map>& deactivationAtomTrackerToMetricMap, + vector& metricsWithActivation) { + if (!MetricProducer::onConfigUpdatedLocked( + config, configIndex, metricIndex, allAtomMatchingTrackers, + oldAtomMatchingTrackerMap, newAtomMatchingTrackerMap, matcherWizard, + allConditionTrackers, conditionTrackerMap, wizard, metricToActivationMap, + trackerToMetricMap, conditionToMetricMap, activationAtomTrackerToMetricMap, + deactivationAtomTrackerToMetricMap, metricsWithActivation)) { + return false; + } + + const DurationMetric& metric = config.duration_metric(configIndex); + const auto& what_it = conditionTrackerMap.find(metric.what()); + if (what_it == conditionTrackerMap.end()) { + ALOGE("DurationMetric's \"what\" is not present in the config"); + return false; + } + + const Predicate& durationWhat = config.predicate(what_it->second); + if (durationWhat.contents_case() != Predicate::ContentsCase::kSimplePredicate) { + ALOGE("DurationMetric's \"what\" must be a simple condition"); + return false; + } + + const SimplePredicate& simplePredicate = durationWhat.simple_predicate(); + + // Update indices: mStartIndex, mStopIndex, mStopAllIndex, mConditionIndex and MetricsManager + // maps. + if (!handleMetricWithAtomMatchingTrackers(simplePredicate.start(), metricIndex, + metric.has_dimensions_in_what(), + allAtomMatchingTrackers, newAtomMatchingTrackerMap, + trackerToMetricMap, mStartIndex)) { + ALOGE("Duration metrics must specify a valid start event matcher"); + return false; + } + + if (simplePredicate.has_stop() && + !handleMetricWithAtomMatchingTrackers(simplePredicate.stop(), metricIndex, + metric.has_dimensions_in_what(), + allAtomMatchingTrackers, newAtomMatchingTrackerMap, + trackerToMetricMap, mStopIndex)) { + return false; + } + + if (simplePredicate.has_stop_all() && + !handleMetricWithAtomMatchingTrackers(simplePredicate.stop_all(), metricIndex, + metric.has_dimensions_in_what(), + allAtomMatchingTrackers, newAtomMatchingTrackerMap, + trackerToMetricMap, mStopAllIndex)) { + return false; + } + + if (metric.has_condition() && + !handleMetricWithConditions(metric.condition(), metricIndex, conditionTrackerMap, + metric.links(), allConditionTrackers, mConditionTrackerIndex, + conditionToMetricMap)) { + return false; + } + + for (const auto& it : mCurrentSlicedDurationTrackerMap) { + it.second->onConfigUpdated(wizard, mConditionTrackerIndex); + } + + return true; +} + +void DurationMetricProducer::initTrueDimensions(const int whatIndex, const int64_t startTimeNs) { + std::lock_guard lock(mMutex); + // Currently whatIndex will only be -1 in tests. In the future, we might want to avoid creating + // a ConditionTracker if the condition is only used in the "what" of a duration metric. In that + // scenario, -1 can also be passed. + if (whatIndex == -1) { + return; + } + const map* slicedWhatMap = mWizard->getSlicedDimensionMap(whatIndex); + for (const auto& [internalDimKey, count] : *slicedWhatMap) { + for (int i = 0; i < count; i++) { + // Fake start events. + handleMatchedLogEventValuesLocked(mStartIndex, internalDimKey.getValues(), startTimeNs); + } + } +} + +sp DurationMetricProducer::addAnomalyTracker( + const Alert& alert, const sp& anomalyAlarmMonitor, + const UpdateStatus& updateStatus, const int64_t updateTimeNs) { + std::lock_guard lock(mMutex); + if (mAggregationType == DurationMetric_AggregationType_SUM) { + if (alert.trigger_if_sum_gt() > alert.num_buckets() * mBucketSizeNs) { + ALOGW("invalid alert for SUM: threshold (%f) > possible recordable value (%d x %lld)", + alert.trigger_if_sum_gt(), alert.num_buckets(), (long long)mBucketSizeNs); + return nullptr; + } + } + sp anomalyTracker = + new DurationAnomalyTracker(alert, mConfigKey, anomalyAlarmMonitor); + // The update status is either new or replaced. + addAnomalyTrackerLocked(anomalyTracker, updateStatus, updateTimeNs); + return anomalyTracker; +} + +// Adds an AnomalyTracker that has already been created. +// Note: this gets called on config updates, and will only get called if the metric and the +// associated alert are preserved, which means the AnomalyTracker must be a DurationAnomalyTracker. +void DurationMetricProducer::addAnomalyTracker(sp& anomalyTracker, + const int64_t updateTimeNs) { + std::lock_guard lock(mMutex); + addAnomalyTrackerLocked(anomalyTracker, UpdateStatus::UPDATE_PRESERVE, updateTimeNs); +} + +void DurationMetricProducer::addAnomalyTrackerLocked(sp& anomalyTracker, + const UpdateStatus& updateStatus, + const int64_t updateTimeNs) { + mAnomalyTrackers.push_back(anomalyTracker); + for (const auto& [_, durationTracker] : mCurrentSlicedDurationTrackerMap) { + durationTracker->addAnomalyTracker(anomalyTracker, updateStatus, updateTimeNs); + } +} +void DurationMetricProducer::onStateChanged(const int64_t eventTimeNs, const int32_t atomId, + const HashableDimensionKey& primaryKey, + const FieldValue& oldState, + const FieldValue& newState) { + // Check if this metric has a StateMap. If so, map the new state value to + // the correct state group id. + FieldValue newStateCopy = newState; + mapStateValue(atomId, &newStateCopy); + + flushIfNeededLocked(eventTimeNs); + + // Log late event and extra duration. + if (eventTimeNs < mCurrentBucketStartTimeNs) { + StatsdStats::getInstance().noteLateLogEvent(mMetricId, + mCurrentBucketStartTimeNs - eventTimeNs); + } + + // Each duration tracker is mapped to a different whatKey (a set of values from the + // dimensionsInWhat fields). We notify all trackers iff the primaryKey field values from the + // state change event are a subset of the tracker's whatKey field values. + // + // Ex. For a duration metric dimensioned on uid and tag: + // DurationTracker1 whatKey = uid: 1001, tag: 1 + // DurationTracker2 whatKey = uid: 1002, tag 1 + // + // If the state change primaryKey = uid: 1001, we only notify DurationTracker1 of a state + // change. + for (auto& whatIt : mCurrentSlicedDurationTrackerMap) { + if (!containsLinkedStateValues(whatIt.first, primaryKey, mMetric2StateLinks, atomId)) { + continue; + } + whatIt.second->onStateChanged(eventTimeNs, atomId, newStateCopy); + } +} + +unique_ptr DurationMetricProducer::createDurationTracker( + const MetricDimensionKey& eventKey) const { + switch (mAggregationType) { + case DurationMetric_AggregationType_SUM: + return make_unique( + mConfigKey, mMetricId, eventKey, mWizard, mConditionTrackerIndex, mNested, + mCurrentBucketStartTimeNs, mCurrentBucketNum, mTimeBaseNs, mBucketSizeNs, + mConditionSliced, mHasLinksToAllConditionDimensionsInTracker, mAnomalyTrackers); + case DurationMetric_AggregationType_MAX_SPARSE: + return make_unique( + mConfigKey, mMetricId, eventKey, mWizard, mConditionTrackerIndex, mNested, + mCurrentBucketStartTimeNs, mCurrentBucketNum, mTimeBaseNs, mBucketSizeNs, + mConditionSliced, mHasLinksToAllConditionDimensionsInTracker, mAnomalyTrackers); + } +} + +// SlicedConditionChange optimization case 1: +// 1. If combination condition, logical operation is AND, only one sliced child predicate. +// 2. The links covers all dimension fields in the sliced child condition predicate. +void DurationMetricProducer::onSlicedConditionMayChangeLocked_opt1(bool condition, + const int64_t eventTime) { + if (mMetric2ConditionLinks.size() != 1 || + !mHasLinksToAllConditionDimensionsInTracker) { + return; + } + + bool currentUnSlicedPartCondition = true; + if (!mWizard->IsSimpleCondition(mConditionTrackerIndex)) { + ConditionState unslicedPartState = + mWizard->getUnSlicedPartConditionState(mConditionTrackerIndex); + // When the unsliced part is still false, return directly. + if (mUnSlicedPartCondition == ConditionState::kFalse && + unslicedPartState == ConditionState::kFalse) { + return; + } + mUnSlicedPartCondition = unslicedPartState; + currentUnSlicedPartCondition = mUnSlicedPartCondition > 0; + } + + auto dimensionsChangedToTrue = mWizard->getChangedToTrueDimensions(mConditionTrackerIndex); + auto dimensionsChangedToFalse = mWizard->getChangedToFalseDimensions(mConditionTrackerIndex); + + // The condition change is from the unsliced predicates. + // We need to find out the true dimensions from the sliced predicate and flip their condition + // state based on the new unsliced condition state. + if (dimensionsChangedToTrue == nullptr || dimensionsChangedToFalse == nullptr || + (dimensionsChangedToTrue->empty() && dimensionsChangedToFalse->empty())) { + const map* slicedConditionMap = + mWizard->getSlicedDimensionMap(mConditionTrackerIndex); + for (auto& whatIt : mCurrentSlicedDurationTrackerMap) { + HashableDimensionKey linkedConditionDimensionKey; + getDimensionForCondition(whatIt.first.getValues(), mMetric2ConditionLinks[0], + &linkedConditionDimensionKey); + const auto& slicedConditionIt = slicedConditionMap->find(linkedConditionDimensionKey); + if (slicedConditionIt != slicedConditionMap->end() && slicedConditionIt->second > 0) { + whatIt.second->onConditionChanged(currentUnSlicedPartCondition, eventTime); + } + } + } else { + // Handle the condition change from the sliced predicate. + if (currentUnSlicedPartCondition) { + for (auto& whatIt : mCurrentSlicedDurationTrackerMap) { + HashableDimensionKey linkedConditionDimensionKey; + getDimensionForCondition(whatIt.first.getValues(), mMetric2ConditionLinks[0], + &linkedConditionDimensionKey); + if (dimensionsChangedToTrue->find(linkedConditionDimensionKey) != + dimensionsChangedToTrue->end()) { + whatIt.second->onConditionChanged(true, eventTime); + } + if (dimensionsChangedToFalse->find(linkedConditionDimensionKey) != + dimensionsChangedToFalse->end()) { + whatIt.second->onConditionChanged(false, eventTime); + } + } + } + } +} + +void DurationMetricProducer::onSlicedConditionMayChangeInternalLocked(bool overallCondition, + const int64_t eventTimeNs) { + bool changeDimTrackable = mWizard->IsChangedDimensionTrackable(mConditionTrackerIndex); + if (changeDimTrackable && mHasLinksToAllConditionDimensionsInTracker) { + onSlicedConditionMayChangeLocked_opt1(overallCondition, eventTimeNs); + return; + } + + // Now for each of the on-going event, check if the condition has changed for them. + for (auto& whatIt : mCurrentSlicedDurationTrackerMap) { + whatIt.second->onSlicedConditionMayChange(overallCondition, eventTimeNs); + } +} + +void DurationMetricProducer::onSlicedConditionMayChangeLocked(bool overallCondition, + const int64_t eventTime) { + VLOG("Metric %lld onSlicedConditionMayChange", (long long)mMetricId); + + if (!mIsActive) { + return; + } + + // Log late event and extra duration. + if (eventTime < mCurrentBucketStartTimeNs) { + StatsdStats::getInstance().noteLateLogEvent(mMetricId, + mCurrentBucketStartTimeNs - eventTime); + } + + flushIfNeededLocked(eventTime); + + if (!mConditionSliced) { + return; + } + + onSlicedConditionMayChangeInternalLocked(overallCondition, eventTime); +} + +void DurationMetricProducer::onActiveStateChangedLocked(const int64_t& eventTimeNs) { + MetricProducer::onActiveStateChangedLocked(eventTimeNs); + + if (!mConditionSliced) { + if (ConditionState::kTrue != mCondition) { + return; + } + + // Log late event and extra duration. + if (eventTimeNs < mCurrentBucketStartTimeNs) { + StatsdStats::getInstance().noteLateLogEvent(mMetricId, + mCurrentBucketStartTimeNs - eventTimeNs); + } + + if (mIsActive) { + flushIfNeededLocked(eventTimeNs); + } + + for (auto& whatIt : mCurrentSlicedDurationTrackerMap) { + whatIt.second->onConditionChanged(mIsActive, eventTimeNs); + } + } else if (mIsActive) { + flushIfNeededLocked(eventTimeNs); + onSlicedConditionMayChangeInternalLocked(mIsActive, eventTimeNs); + } else { // mConditionSliced == true && !mIsActive + for (auto& whatIt : mCurrentSlicedDurationTrackerMap) { + whatIt.second->onConditionChanged(mIsActive, eventTimeNs); + } + } +} + +void DurationMetricProducer::onConditionChangedLocked(const bool conditionMet, + const int64_t eventTime) { + VLOG("Metric %lld onConditionChanged", (long long)mMetricId); + mCondition = conditionMet ? ConditionState::kTrue : ConditionState::kFalse; + + if (!mIsActive) { + return; + } + + // Log late event and extra duration. + if (eventTime < mCurrentBucketStartTimeNs) { + StatsdStats::getInstance().noteLateLogEvent(mMetricId, + mCurrentBucketStartTimeNs - eventTime); + } + + flushIfNeededLocked(eventTime); + for (auto& whatIt : mCurrentSlicedDurationTrackerMap) { + whatIt.second->onConditionChanged(conditionMet, eventTime); + } +} + +void DurationMetricProducer::dropDataLocked(const int64_t dropTimeNs) { + flushIfNeededLocked(dropTimeNs); + StatsdStats::getInstance().noteBucketDropped(mMetricId); + mPastBuckets.clear(); +} + +void DurationMetricProducer::clearPastBucketsLocked(const int64_t dumpTimeNs) { + flushIfNeededLocked(dumpTimeNs); + mPastBuckets.clear(); +} + +void DurationMetricProducer::onDumpReportLocked(const int64_t dumpTimeNs, + const bool include_current_partial_bucket, + const bool erase_data, + const DumpLatency dumpLatency, + std::set *str_set, + ProtoOutputStream* protoOutput) { + if (include_current_partial_bucket) { + flushLocked(dumpTimeNs); + } else { + flushIfNeededLocked(dumpTimeNs); + } + protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_ID, (long long)mMetricId); + protoOutput->write(FIELD_TYPE_BOOL | FIELD_ID_IS_ACTIVE, isActiveLocked()); + + if (mPastBuckets.empty()) { + VLOG(" Duration metric, empty return"); + return; + } + + protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_TIME_BASE, (long long)mTimeBaseNs); + protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_BUCKET_SIZE, (long long)mBucketSizeNs); + + if (!mSliceByPositionALL) { + if (!mDimensionsInWhat.empty()) { + uint64_t dimenPathToken = protoOutput->start( + FIELD_TYPE_MESSAGE | FIELD_ID_DIMENSION_PATH_IN_WHAT); + writeDimensionPathToProto(mDimensionsInWhat, protoOutput); + protoOutput->end(dimenPathToken); + } + } + + uint64_t protoToken = protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_ID_DURATION_METRICS); + + VLOG("Duration metric %lld dump report now...", (long long)mMetricId); + + for (const auto& pair : mPastBuckets) { + const MetricDimensionKey& dimensionKey = pair.first; + VLOG(" dimension key %s", dimensionKey.toString().c_str()); + + uint64_t wrapperToken = + protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_DATA); + + // First fill dimension. + if (mSliceByPositionALL) { + uint64_t dimensionToken = protoOutput->start( + FIELD_TYPE_MESSAGE | FIELD_ID_DIMENSION_IN_WHAT); + writeDimensionToProto(dimensionKey.getDimensionKeyInWhat(), str_set, protoOutput); + protoOutput->end(dimensionToken); + } else { + writeDimensionLeafNodesToProto(dimensionKey.getDimensionKeyInWhat(), + FIELD_ID_DIMENSION_LEAF_IN_WHAT, str_set, protoOutput); + } + // Then fill slice_by_state. + for (auto state : dimensionKey.getStateValuesKey().getValues()) { + uint64_t stateToken = protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | + FIELD_ID_SLICE_BY_STATE); + writeStateToProto(state, protoOutput); + protoOutput->end(stateToken); + } + // Then fill bucket_info (DurationBucketInfo). + for (const auto& bucket : pair.second) { + uint64_t bucketInfoToken = protoOutput->start( + FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_BUCKET_INFO); + if (bucket.mBucketEndNs - bucket.mBucketStartNs != mBucketSizeNs) { + protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_START_BUCKET_ELAPSED_MILLIS, + (long long)NanoToMillis(bucket.mBucketStartNs)); + protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_END_BUCKET_ELAPSED_MILLIS, + (long long)NanoToMillis(bucket.mBucketEndNs)); + } else { + protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_BUCKET_NUM, + (long long)(getBucketNumFromEndTimeNs(bucket.mBucketEndNs))); + } + protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_DURATION, (long long)bucket.mDuration); + protoOutput->end(bucketInfoToken); + VLOG("\t bucket [%lld - %lld] duration: %lld", (long long)bucket.mBucketStartNs, + (long long)bucket.mBucketEndNs, (long long)bucket.mDuration); + } + + protoOutput->end(wrapperToken); + } + + protoOutput->end(protoToken); + if (erase_data) { + mPastBuckets.clear(); + } +} + +void DurationMetricProducer::flushIfNeededLocked(const int64_t& eventTimeNs) { + int64_t currentBucketEndTimeNs = getCurrentBucketEndTimeNs(); + + if (currentBucketEndTimeNs > eventTimeNs) { + return; + } + VLOG("flushing..........."); + int numBucketsForward = 1 + (eventTimeNs - currentBucketEndTimeNs) / mBucketSizeNs; + int64_t nextBucketNs = currentBucketEndTimeNs + (numBucketsForward - 1) * mBucketSizeNs; + flushCurrentBucketLocked(eventTimeNs, nextBucketNs); + + mCurrentBucketNum += numBucketsForward; +} + +void DurationMetricProducer::flushCurrentBucketLocked(const int64_t& eventTimeNs, + const int64_t& nextBucketStartTimeNs) { + for (auto whatIt = mCurrentSlicedDurationTrackerMap.begin(); + whatIt != mCurrentSlicedDurationTrackerMap.end();) { + if (whatIt->second->flushCurrentBucket(eventTimeNs, mUploadThreshold, &mPastBuckets)) { + VLOG("erase bucket for key %s", whatIt->first.toString().c_str()); + whatIt = mCurrentSlicedDurationTrackerMap.erase(whatIt); + } else { + ++whatIt; + } + } + StatsdStats::getInstance().noteBucketCount(mMetricId); + mCurrentBucketStartTimeNs = nextBucketStartTimeNs; +} + +void DurationMetricProducer::dumpStatesLocked(FILE* out, bool verbose) const { + if (mCurrentSlicedDurationTrackerMap.size() == 0) { + return; + } + + fprintf(out, "DurationMetric %lld dimension size %lu\n", (long long)mMetricId, + (unsigned long)mCurrentSlicedDurationTrackerMap.size()); + if (verbose) { + for (const auto& whatIt : mCurrentSlicedDurationTrackerMap) { + fprintf(out, "\t(what)%s\n", whatIt.first.toString().c_str()); + whatIt.second->dumpStates(out, verbose); + } + } +} + +bool DurationMetricProducer::hitGuardRailLocked(const MetricDimensionKey& newKey) { + auto whatIt = mCurrentSlicedDurationTrackerMap.find(newKey.getDimensionKeyInWhat()); + if (whatIt == mCurrentSlicedDurationTrackerMap.end()) { + // 1. Report the tuple count if the tuple count > soft limit + 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) { + ALOGE("DurationMetric %lld dropping data for what dimension key %s", + (long long)mMetricId, newKey.getDimensionKeyInWhat().toString().c_str()); + StatsdStats::getInstance().noteHardDimensionLimitReached(mMetricId); + return true; + } + } + } + return false; +} + +void DurationMetricProducer::handleStartEvent(const MetricDimensionKey& eventKey, + const ConditionKey& conditionKeys, bool condition, + const int64_t eventTimeNs, + const vector& eventValues) { + const auto& whatKey = eventKey.getDimensionKeyInWhat(); + auto whatIt = mCurrentSlicedDurationTrackerMap.find(whatKey); + if (whatIt == mCurrentSlicedDurationTrackerMap.end()) { + if (hitGuardRailLocked(eventKey)) { + return; + } + mCurrentSlicedDurationTrackerMap[whatKey] = createDurationTracker(eventKey); + } + + auto it = mCurrentSlicedDurationTrackerMap.find(whatKey); + if (mUseWhatDimensionAsInternalDimension) { + it->second->noteStart(whatKey, condition, eventTimeNs, conditionKeys); + return; + } + + if (mInternalDimensions.empty()) { + it->second->noteStart(DEFAULT_DIMENSION_KEY, condition, eventTimeNs, conditionKeys); + } else { + HashableDimensionKey dimensionKey = DEFAULT_DIMENSION_KEY; + filterValues(mInternalDimensions, eventValues, &dimensionKey); + it->second->noteStart(dimensionKey, condition, eventTimeNs, conditionKeys); + } +} + +void DurationMetricProducer::onMatchedLogEventInternalLocked( + const size_t matcherIndex, const MetricDimensionKey& eventKey, + const ConditionKey& conditionKeys, bool condition, const LogEvent& event, + const map& statePrimaryKeys) { + ALOGW("Not used in duration tracker."); +} + +void DurationMetricProducer::onMatchedLogEventLocked(const size_t matcherIndex, + const LogEvent& event) { + handleMatchedLogEventValuesLocked(matcherIndex, event.getValues(), + event.GetElapsedTimestampNs()); +} + +void DurationMetricProducer::handleMatchedLogEventValuesLocked(const size_t matcherIndex, + const vector& values, + const int64_t eventTimeNs) { + if (eventTimeNs < mTimeBaseNs) { + return; + } + + // Log late event and extra duration. + if (eventTimeNs < mCurrentBucketStartTimeNs) { + StatsdStats::getInstance().noteLateLogEvent(mMetricId, + mCurrentBucketStartTimeNs - eventTimeNs); + } + + if (mIsActive) { + flushIfNeededLocked(eventTimeNs); + } + + // Handles Stopall events. + if ((int)matcherIndex == mStopAllIndex) { + for (auto& whatIt : mCurrentSlicedDurationTrackerMap) { + whatIt.second->noteStopAll(eventTimeNs); + } + return; + } + + HashableDimensionKey dimensionInWhat = DEFAULT_DIMENSION_KEY; + if (!mDimensionsInWhat.empty()) { + filterValues(mDimensionsInWhat, values, &dimensionInWhat); + } + + // Stores atom id to primary key pairs for each state atom that the metric is + // sliced by. + std::map statePrimaryKeys; + + // For states with primary fields, use MetricStateLinks to get the primary + // field values from the log event. These values will form a primary key + // that will be used to query StateTracker for the correct state value. + for (const auto& stateLink : mMetric2StateLinks) { + getDimensionForState(values, stateLink, &statePrimaryKeys[stateLink.stateAtomId]); + } + + // For each sliced state, query StateTracker for the state value using + // either the primary key from the previous step or the DEFAULT_DIMENSION_KEY. + // + // Expected functionality: for any case where the MetricStateLinks are + // initialized incorrectly (ex. # of state links != # of primary fields, no + // links are provided for a state with primary fields, links are provided + // in the wrong order, etc.), StateTracker will simply return kStateUnknown + // when queried using an incorrect key. + HashableDimensionKey stateValuesKey = DEFAULT_DIMENSION_KEY; + for (auto atomId : mSlicedStateAtoms) { + FieldValue value; + if (statePrimaryKeys.find(atomId) != statePrimaryKeys.end()) { + // found a primary key for this state, query using the key + queryStateValue(atomId, statePrimaryKeys[atomId], &value); + } else { + // if no MetricStateLinks exist for this state atom, + // query using the default dimension key (empty HashableDimensionKey) + queryStateValue(atomId, DEFAULT_DIMENSION_KEY, &value); + } + mapStateValue(atomId, &value); + stateValuesKey.addValue(value); + } + + // Handles Stop events. + if ((int)matcherIndex == mStopIndex) { + if (mUseWhatDimensionAsInternalDimension) { + auto whatIt = mCurrentSlicedDurationTrackerMap.find(dimensionInWhat); + if (whatIt != mCurrentSlicedDurationTrackerMap.end()) { + whatIt->second->noteStop(dimensionInWhat, eventTimeNs, false); + } + return; + } + + HashableDimensionKey internalDimensionKey = DEFAULT_DIMENSION_KEY; + if (!mInternalDimensions.empty()) { + filterValues(mInternalDimensions, values, &internalDimensionKey); + } + + auto whatIt = mCurrentSlicedDurationTrackerMap.find(dimensionInWhat); + if (whatIt != mCurrentSlicedDurationTrackerMap.end()) { + whatIt->second->noteStop(internalDimensionKey, eventTimeNs, false); + } + return; + } + + bool condition; + ConditionKey conditionKey; + if (mConditionSliced) { + for (const auto& link : mMetric2ConditionLinks) { + getDimensionForCondition(values, link, &conditionKey[link.conditionId]); + } + + auto conditionState = + mWizard->query(mConditionTrackerIndex, conditionKey, + !mHasLinksToAllConditionDimensionsInTracker); + condition = conditionState == ConditionState::kTrue; + } else { + // TODO: The unknown condition state is not handled here, we should fix it. + condition = mCondition == ConditionState::kTrue; + } + + condition = condition && mIsActive; + + handleStartEvent(MetricDimensionKey(dimensionInWhat, stateValuesKey), conditionKey, condition, + eventTimeNs, values); +} + +size_t DurationMetricProducer::byteSizeLocked() const { + size_t totalSize = 0; + for (const auto& pair : mPastBuckets) { + totalSize += pair.second.size() * kBucketSize; + } + return totalSize; +} + +} // namespace statsd +} // namespace os +} // namespace android diff --git a/statsd/src/metrics/DurationMetricProducer.h b/statsd/src/metrics/DurationMetricProducer.h new file mode 100644 index 00000000..f35fd9dd --- /dev/null +++ b/statsd/src/metrics/DurationMetricProducer.h @@ -0,0 +1,204 @@ +/* + * 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. + */ + +#pragma once + +#include + +#include + +#include "../anomaly/DurationAnomalyTracker.h" +#include "../condition/ConditionTracker.h" +#include "../matchers/matcher_util.h" +#include "MetricProducer.h" +#include "duration_helper/DurationTracker.h" +#include "duration_helper/MaxDurationTracker.h" +#include "duration_helper/OringDurationTracker.h" +#include "src/statsd_config.pb.h" +#include "stats_util.h" + +using namespace std; + +namespace android { +namespace os { +namespace statsd { + +class DurationMetricProducer : public MetricProducer { +public: + DurationMetricProducer( + const ConfigKey& key, const DurationMetric& durationMetric, const int conditionIndex, + const vector& initialConditionCache, const int whatIndex, + const int startIndex, const int stopIndex, const int stopAllIndex, const bool nesting, + const sp& wizard, const uint64_t protoHash, + const FieldMatcher& internalDimensions, const int64_t timeBaseNs, + const int64_t startTimeNs, + const unordered_map>& eventActivationMap = {}, + const unordered_map>>& eventDeactivationMap = {}, + const vector& slicedStateAtoms = {}, + const unordered_map>& stateGroupMap = {}); + + virtual ~DurationMetricProducer(); + + sp addAnomalyTracker(const Alert& alert, + const sp& anomalyAlarmMonitor, + const UpdateStatus& updateStatus, + const int64_t updateTimeNs) override; + + void addAnomalyTracker(sp& anomalyTracker, const int64_t updateTimeNs) override; + + void onStateChanged(const int64_t eventTimeNs, const int32_t atomId, + const HashableDimensionKey& primaryKey, const FieldValue& oldState, + const FieldValue& newState) override; + + MetricType getMetricType() const override { + return METRIC_TYPE_DURATION; + } + +protected: + void onMatchedLogEventLocked(const size_t matcherIndex, const LogEvent& event) override; + + void onMatchedLogEventInternalLocked( + const size_t matcherIndex, const MetricDimensionKey& eventKey, + const ConditionKey& conditionKeys, bool condition, const LogEvent& event, + const std::map& statePrimaryKeys) override; + +private: + // Initializes true dimensions of the 'what' predicate. Only to be called during initialization. + void initTrueDimensions(const int whatIndex, const int64_t startTimeNs); + + void handleMatchedLogEventValuesLocked(const size_t matcherIndex, + const std::vector& values, + const int64_t eventTimeNs); + void handleStartEvent(const MetricDimensionKey& eventKey, const ConditionKey& conditionKeys, + bool condition, const int64_t eventTimeNs, + const vector& eventValues); + + void onDumpReportLocked(const int64_t dumpTimeNs, + const bool include_current_partial_bucket, + const bool erase_data, + const DumpLatency dumpLatency, + std::set *str_set, + android::util::ProtoOutputStream* protoOutput) override; + + void clearPastBucketsLocked(const int64_t dumpTimeNs) override; + + // Internal interface to handle condition change. + void onConditionChangedLocked(const bool conditionMet, const int64_t eventTime) override; + + // Internal interface to handle active state change. + void onActiveStateChangedLocked(const int64_t& eventTimeNs) override; + + // Internal interface to handle sliced condition change. + void onSlicedConditionMayChangeLocked(bool overallCondition, const int64_t eventTime) override; + + void onSlicedConditionMayChangeInternalLocked(bool overallCondition, + const int64_t eventTimeNs); + + void onSlicedConditionMayChangeLocked_opt1(bool overallCondition, const int64_t eventTime); + void onSlicedConditionMayChangeLocked_opt2(bool overallCondition, const int64_t eventTime); + + // Internal function to calculate the current used bytes. + size_t byteSizeLocked() const override; + + void dumpStatesLocked(FILE* out, bool verbose) const override; + + void dropDataLocked(const int64_t dropTimeNs) override; + + // Util function to flush the old packet. + void flushIfNeededLocked(const int64_t& eventTime); + + void flushCurrentBucketLocked(const int64_t& eventTimeNs, + const int64_t& nextBucketStartTimeNs) override; + + bool onConfigUpdatedLocked( + const StatsdConfig& config, const int configIndex, const int metricIndex, + const std::vector>& allAtomMatchingTrackers, + const std::unordered_map& oldAtomMatchingTrackerMap, + const std::unordered_map& newAtomMatchingTrackerMap, + const sp& matcherWizard, + const std::vector>& allConditionTrackers, + const std::unordered_map& conditionTrackerMap, + const sp& wizard, + const std::unordered_map& metricToActivationMap, + std::unordered_map>& trackerToMetricMap, + std::unordered_map>& conditionToMetricMap, + std::unordered_map>& activationAtomTrackerToMetricMap, + std::unordered_map>& deactivationAtomTrackerToMetricMap, + std::vector& metricsWithActivation) override; + + void addAnomalyTrackerLocked(sp& anomalyTracker, + const UpdateStatus& updateStatus, const int64_t updateTimeNs); + + const DurationMetric_AggregationType mAggregationType; + + // Index of the SimpleAtomMatcher which defines the start. + int mStartIndex; + + // Index of the SimpleAtomMatcher which defines the stop. + int mStopIndex; + + // Index of the SimpleAtomMatcher which defines the stop all for all dimensions. + int mStopAllIndex; + + // nest counting -- for the same key, stops must match the number of starts to make real stop + const bool mNested; + + // The dimension from the atom predicate. e.g., uid, wakelock name. + vector mInternalDimensions; + + bool mContainANYPositionInInternalDimensions; + + // This boolean is true iff When mInternalDimensions == mDimensionsInWhat + bool mUseWhatDimensionAsInternalDimension; + + // Caches the current unsliced part condition. + ConditionState mUnSlicedPartCondition; + + // Save the past buckets and we can clear when the StatsLogReport is dumped. + std::unordered_map> mPastBuckets; + + // The duration trackers in the current bucket. + std::unordered_map> + mCurrentSlicedDurationTrackerMap; + + // Helper function to create a duration tracker given the metric aggregation type. + std::unique_ptr createDurationTracker( + const MetricDimensionKey& eventKey) const; + + // Util function to check whether the specified dimension hits the guardrail. + bool hitGuardRailLocked(const MetricDimensionKey& newKey); + + static const size_t kBucketSize = sizeof(DurationBucket{}); + + FRIEND_TEST(DurationMetricTrackerTest, TestNoCondition); + FRIEND_TEST(DurationMetricTrackerTest, TestNonSlicedCondition); + FRIEND_TEST(DurationMetricTrackerTest, TestNonSlicedConditionUnknownState); + FRIEND_TEST(WakelockDurationE2eTest, TestAggregatedPredicates); + FRIEND_TEST(DurationMetricTrackerTest, TestFirstBucket); + + FRIEND_TEST(DurationMetricProducerTest_PartialBucket, TestSumDuration); + FRIEND_TEST(DurationMetricProducerTest_PartialBucket, + TestSumDurationWithSplitInFollowingBucket); + FRIEND_TEST(DurationMetricProducerTest_PartialBucket, TestMaxDuration); + FRIEND_TEST(DurationMetricProducerTest_PartialBucket, TestMaxDurationWithSplitInNextBucket); + + FRIEND_TEST(ConfigUpdateTest, TestUpdateDurationMetrics); + FRIEND_TEST(ConfigUpdateTest, TestUpdateAlerts); +}; + +} // namespace statsd +} // namespace os +} // namespace android diff --git a/statsd/src/metrics/EventMetricProducer.cpp b/statsd/src/metrics/EventMetricProducer.cpp new file mode 100644 index 00000000..ca302c0e --- /dev/null +++ b/statsd/src/metrics/EventMetricProducer.cpp @@ -0,0 +1,214 @@ +/* + * 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. + */ + +#define DEBUG false // STOPSHIP if true +#include "Log.h" + +#include "EventMetricProducer.h" + +#include +#include + +#include "metrics/parsing_utils/metrics_manager_util.h" +#include "stats_log_util.h" +#include "stats_util.h" + +using android::util::FIELD_COUNT_REPEATED; +using android::util::FIELD_TYPE_BOOL; +using android::util::FIELD_TYPE_FLOAT; +using android::util::FIELD_TYPE_INT32; +using android::util::FIELD_TYPE_INT64; +using android::util::FIELD_TYPE_STRING; +using android::util::FIELD_TYPE_MESSAGE; +using android::util::ProtoOutputStream; +using std::map; +using std::string; +using std::unordered_map; +using std::vector; +using std::shared_ptr; + +namespace android { +namespace os { +namespace statsd { + +// for StatsLogReport +const int FIELD_ID_ID = 1; +const int FIELD_ID_EVENT_METRICS = 4; +const int FIELD_ID_IS_ACTIVE = 14; +// for EventMetricDataWrapper +const int FIELD_ID_DATA = 1; +// for EventMetricData +const int FIELD_ID_ELAPSED_TIMESTAMP_NANOS = 1; +const int FIELD_ID_ATOMS = 2; + +EventMetricProducer::EventMetricProducer( + const ConfigKey& key, const EventMetric& metric, const int conditionIndex, + const vector& initialConditionCache, const sp& wizard, + const uint64_t protoHash, const int64_t startTimeNs, + const unordered_map>& eventActivationMap, + const unordered_map>>& eventDeactivationMap, + const vector& slicedStateAtoms, + const unordered_map>& stateGroupMap) + : MetricProducer(metric.id(), key, startTimeNs, conditionIndex, initialConditionCache, wizard, + protoHash, eventActivationMap, eventDeactivationMap, slicedStateAtoms, + stateGroupMap) { + if (metric.links().size() > 0) { + for (const auto& link : metric.links()) { + Metric2Condition mc; + mc.conditionId = link.condition(); + translateFieldMatcher(link.fields_in_what(), &mc.metricFields); + translateFieldMatcher(link.fields_in_condition(), &mc.conditionFields); + mMetric2ConditionLinks.push_back(mc); + } + mConditionSliced = true; + } + mProto = std::make_unique(); + VLOG("metric %lld created. bucket size %lld start_time: %lld", (long long)metric.id(), + (long long)mBucketSizeNs, (long long)mTimeBaseNs); +} + +EventMetricProducer::~EventMetricProducer() { + VLOG("~EventMetricProducer() called"); +} + +bool EventMetricProducer::onConfigUpdatedLocked( + const StatsdConfig& config, const int configIndex, const int metricIndex, + const vector>& allAtomMatchingTrackers, + const unordered_map& oldAtomMatchingTrackerMap, + const unordered_map& newAtomMatchingTrackerMap, + const sp& matcherWizard, + const vector>& allConditionTrackers, + const unordered_map& conditionTrackerMap, const sp& wizard, + const unordered_map& metricToActivationMap, + unordered_map>& trackerToMetricMap, + unordered_map>& conditionToMetricMap, + unordered_map>& activationAtomTrackerToMetricMap, + unordered_map>& deactivationAtomTrackerToMetricMap, + vector& metricsWithActivation) { + if (!MetricProducer::onConfigUpdatedLocked( + config, configIndex, metricIndex, allAtomMatchingTrackers, + oldAtomMatchingTrackerMap, newAtomMatchingTrackerMap, matcherWizard, + allConditionTrackers, conditionTrackerMap, wizard, metricToActivationMap, + trackerToMetricMap, conditionToMetricMap, activationAtomTrackerToMetricMap, + deactivationAtomTrackerToMetricMap, metricsWithActivation)) { + return false; + } + + const EventMetric& metric = config.event_metric(configIndex); + int trackerIndex; + // Update appropriate indices, specifically mConditionIndex and MetricsManager maps. + if (!handleMetricWithAtomMatchingTrackers(metric.what(), metricIndex, false, + allAtomMatchingTrackers, newAtomMatchingTrackerMap, + trackerToMetricMap, trackerIndex)) { + return false; + } + + if (metric.has_condition() && + !handleMetricWithConditions(metric.condition(), metricIndex, conditionTrackerMap, + metric.links(), allConditionTrackers, mConditionTrackerIndex, + conditionToMetricMap)) { + return false; + } + return true; +} + +void EventMetricProducer::dropDataLocked(const int64_t dropTimeNs) { + mProto->clear(); + StatsdStats::getInstance().noteBucketDropped(mMetricId); +} + +void EventMetricProducer::onSlicedConditionMayChangeLocked(bool overallCondition, + const int64_t eventTime) { +} + +std::unique_ptr> serializeProtoLocked(ProtoOutputStream& protoOutput) { + size_t bufferSize = protoOutput.size(); + + std::unique_ptr> buffer(new std::vector(bufferSize)); + + size_t pos = 0; + sp reader = protoOutput.data(); + while (reader->readBuffer() != NULL) { + size_t toRead = reader->currentToRead(); + std::memcpy(&((*buffer)[pos]), reader->readBuffer(), toRead); + pos += toRead; + reader->move(toRead); + } + + return buffer; +} + +void EventMetricProducer::clearPastBucketsLocked(const int64_t dumpTimeNs) { + mProto->clear(); +} + +void EventMetricProducer::onDumpReportLocked(const int64_t dumpTimeNs, + const bool include_current_partial_bucket, + const bool erase_data, + const DumpLatency dumpLatency, + std::set *str_set, + ProtoOutputStream* protoOutput) { + protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_ID, (long long)mMetricId); + protoOutput->write(FIELD_TYPE_BOOL | FIELD_ID_IS_ACTIVE, isActiveLocked()); + if (mProto->size() <= 0) { + return; + } + + size_t bufferSize = mProto->size(); + VLOG("metric %lld dump report now... proto size: %zu ", + (long long)mMetricId, bufferSize); + std::unique_ptr> buffer = serializeProtoLocked(*mProto); + + protoOutput->write(FIELD_TYPE_MESSAGE | FIELD_ID_EVENT_METRICS, + reinterpret_cast(buffer.get()->data()), buffer.get()->size()); + + if (erase_data) { + mProto->clear(); + } +} + +void EventMetricProducer::onConditionChangedLocked(const bool conditionMet, + const int64_t eventTime) { + VLOG("Metric %lld onConditionChanged", (long long)mMetricId); + mCondition = conditionMet ? ConditionState::kTrue : ConditionState::kFalse; +} + +void EventMetricProducer::onMatchedLogEventInternalLocked( + const size_t matcherIndex, const MetricDimensionKey& eventKey, + const ConditionKey& conditionKey, bool condition, const LogEvent& event, + const map& statePrimaryKeys) { + if (!condition) { + return; + } + + uint64_t wrapperToken = + mProto->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_DATA); + const int64_t elapsedTimeNs = truncateTimestampIfNecessary(event); + mProto->write(FIELD_TYPE_INT64 | FIELD_ID_ELAPSED_TIMESTAMP_NANOS, (long long) elapsedTimeNs); + + uint64_t eventToken = mProto->start(FIELD_TYPE_MESSAGE | FIELD_ID_ATOMS); + event.ToProto(*mProto); + mProto->end(eventToken); + mProto->end(wrapperToken); +} + +size_t EventMetricProducer::byteSizeLocked() const { + return mProto->bytesWritten(); +} + +} // namespace statsd +} // namespace os +} // namespace android diff --git a/statsd/src/metrics/EventMetricProducer.h b/statsd/src/metrics/EventMetricProducer.h new file mode 100644 index 00000000..8ef75bc6 --- /dev/null +++ b/statsd/src/metrics/EventMetricProducer.h @@ -0,0 +1,103 @@ +/* + * 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. + */ + +#ifndef EVENT_METRIC_PRODUCER_H +#define EVENT_METRIC_PRODUCER_H + +#include + +#include + +#include "../condition/ConditionTracker.h" +#include "../matchers/matcher_util.h" +#include "MetricProducer.h" +#include "src/statsd_config.pb.h" +#include "stats_util.h" + +namespace android { +namespace os { +namespace statsd { + +class EventMetricProducer : public MetricProducer { +public: + EventMetricProducer( + const ConfigKey& key, const EventMetric& eventMetric, const int conditionIndex, + const vector& initialConditionCache, const sp& wizard, + const uint64_t protoHash, const int64_t startTimeNs, + const std::unordered_map>& eventActivationMap = {}, + const std::unordered_map>>& + eventDeactivationMap = {}, + const vector& slicedStateAtoms = {}, + const unordered_map>& stateGroupMap = {}); + + virtual ~EventMetricProducer(); + + MetricType getMetricType() const override { + return METRIC_TYPE_EVENT; + } + +private: + void onMatchedLogEventInternalLocked( + const size_t matcherIndex, const MetricDimensionKey& eventKey, + const ConditionKey& conditionKey, bool condition, const LogEvent& event, + const std::map& statePrimaryKeys) override; + + void onDumpReportLocked(const int64_t dumpTimeNs, + const bool include_current_partial_bucket, + const bool erase_data, + const DumpLatency dumpLatency, + std::set *str_set, + android::util::ProtoOutputStream* protoOutput) override; + void clearPastBucketsLocked(const int64_t dumpTimeNs) override; + + // Internal interface to handle condition change. + void onConditionChangedLocked(const bool conditionMet, const int64_t eventTime) override; + + // Internal interface to handle sliced condition change. + void onSlicedConditionMayChangeLocked(bool overallCondition, const int64_t eventTime) override; + + bool onConfigUpdatedLocked( + const StatsdConfig& config, const int configIndex, const int metricIndex, + const std::vector>& allAtomMatchingTrackers, + const std::unordered_map& oldAtomMatchingTrackerMap, + const std::unordered_map& newAtomMatchingTrackerMap, + const sp& matcherWizard, + const std::vector>& allConditionTrackers, + const std::unordered_map& conditionTrackerMap, + const sp& wizard, + const std::unordered_map& metricToActivationMap, + std::unordered_map>& trackerToMetricMap, + std::unordered_map>& conditionToMetricMap, + std::unordered_map>& activationAtomTrackerToMetricMap, + std::unordered_map>& deactivationAtomTrackerToMetricMap, + std::vector& metricsWithActivation) override; + + void dropDataLocked(const int64_t dropTimeNs) override; + + // Internal function to calculate the current used bytes. + size_t byteSizeLocked() const override; + + void dumpStatesLocked(FILE* out, bool verbose) const override{}; + + // Maps to a EventMetricDataWrapper. Storing atom events in ProtoOutputStream + // is more space efficient than storing LogEvent. + std::unique_ptr mProto; +}; + +} // namespace statsd +} // namespace os +} // namespace android +#endif // EVENT_METRIC_PRODUCER_H diff --git a/statsd/src/metrics/GaugeMetricProducer.cpp b/statsd/src/metrics/GaugeMetricProducer.cpp new file mode 100644 index 00000000..bca5c17b --- /dev/null +++ b/statsd/src/metrics/GaugeMetricProducer.cpp @@ -0,0 +1,681 @@ +/* +* 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. +*/ + +#define DEBUG false // STOPSHIP if true +#include "Log.h" + +#include "GaugeMetricProducer.h" + +#include "guardrail/StatsdStats.h" +#include "metrics/parsing_utils/metrics_manager_util.h" +#include "stats_log_util.h" + +using android::util::FIELD_COUNT_REPEATED; +using android::util::FIELD_TYPE_BOOL; +using android::util::FIELD_TYPE_FLOAT; +using android::util::FIELD_TYPE_INT32; +using android::util::FIELD_TYPE_INT64; +using android::util::FIELD_TYPE_MESSAGE; +using android::util::FIELD_TYPE_STRING; +using android::util::ProtoOutputStream; +using std::map; +using std::string; +using std::unordered_map; +using std::vector; +using std::make_shared; +using std::shared_ptr; + +namespace android { +namespace os { +namespace statsd { + +// for StatsLogReport +const int FIELD_ID_ID = 1; +const int FIELD_ID_GAUGE_METRICS = 8; +const int FIELD_ID_TIME_BASE = 9; +const int FIELD_ID_BUCKET_SIZE = 10; +const int FIELD_ID_DIMENSION_PATH_IN_WHAT = 11; +const int FIELD_ID_IS_ACTIVE = 14; +// for GaugeMetricDataWrapper +const int FIELD_ID_DATA = 1; +const int FIELD_ID_SKIPPED = 2; +// for SkippedBuckets +const int FIELD_ID_SKIPPED_START_MILLIS = 3; +const int FIELD_ID_SKIPPED_END_MILLIS = 4; +const int FIELD_ID_SKIPPED_DROP_EVENT = 5; +// for DumpEvent Proto +const int FIELD_ID_BUCKET_DROP_REASON = 1; +const int FIELD_ID_DROP_TIME = 2; +// for GaugeMetricData +const int FIELD_ID_DIMENSION_IN_WHAT = 1; +const int FIELD_ID_BUCKET_INFO = 3; +const int FIELD_ID_DIMENSION_LEAF_IN_WHAT = 4; +// for GaugeBucketInfo +const int FIELD_ID_ATOM = 3; +const int FIELD_ID_ELAPSED_ATOM_TIMESTAMP = 4; +const int FIELD_ID_BUCKET_NUM = 6; +const int FIELD_ID_START_BUCKET_ELAPSED_MILLIS = 7; +const int FIELD_ID_END_BUCKET_ELAPSED_MILLIS = 8; + +GaugeMetricProducer::GaugeMetricProducer( + const ConfigKey& key, const GaugeMetric& metric, const int conditionIndex, + const vector& initialConditionCache, const sp& wizard, + const uint64_t protoHash, const int whatMatcherIndex, + const sp& matcherWizard, const int pullTagId, const int triggerAtomId, + const int atomId, const int64_t timeBaseNs, const int64_t startTimeNs, + const sp& pullerManager, + const unordered_map>& eventActivationMap, + const unordered_map>>& eventDeactivationMap) + : MetricProducer(metric.id(), key, timeBaseNs, conditionIndex, initialConditionCache, wizard, + protoHash, eventActivationMap, eventDeactivationMap, /*slicedStateAtoms=*/{}, + /*stateGroupMap=*/{}), + mWhatMatcherIndex(whatMatcherIndex), + mEventMatcherWizard(matcherWizard), + mPullerManager(pullerManager), + mPullTagId(pullTagId), + mTriggerAtomId(triggerAtomId), + mAtomId(atomId), + mIsPulled(pullTagId != -1), + mMinBucketSizeNs(metric.min_bucket_size_nanos()), + mMaxPullDelayNs(metric.max_pull_delay_sec() > 0 ? metric.max_pull_delay_sec() * NS_PER_SEC + : StatsdStats::kPullMaxDelayNs), + mDimensionSoftLimit(StatsdStats::kAtomDimensionKeySizeLimitMap.find(pullTagId) != + StatsdStats::kAtomDimensionKeySizeLimitMap.end() + ? StatsdStats::kAtomDimensionKeySizeLimitMap.at(pullTagId).first + : StatsdStats::kDimensionKeySizeSoftLimit), + mDimensionHardLimit(StatsdStats::kAtomDimensionKeySizeLimitMap.find(pullTagId) != + StatsdStats::kAtomDimensionKeySizeLimitMap.end() + ? StatsdStats::kAtomDimensionKeySizeLimitMap.at(pullTagId).second + : StatsdStats::kDimensionKeySizeHardLimit), + mGaugeAtomsPerDimensionLimit(metric.max_num_gauge_atoms_per_bucket()), + mSplitBucketForAppUpgrade(metric.split_bucket_for_app_upgrade()) { + mCurrentSlicedBucket = std::make_shared(); + mCurrentSlicedBucketForAnomaly = std::make_shared(); + int64_t bucketSizeMills = 0; + if (metric.has_bucket()) { + bucketSizeMills = TimeUnitToBucketSizeInMillisGuardrailed(key.GetUid(), metric.bucket()); + } else { + bucketSizeMills = TimeUnitToBucketSizeInMillis(ONE_HOUR); + } + mBucketSizeNs = bucketSizeMills * 1000000; + + mSamplingType = metric.sampling_type(); + if (!metric.gauge_fields_filter().include_all()) { + translateFieldMatcher(metric.gauge_fields_filter().fields(), &mFieldMatchers); + } + + if (metric.has_dimensions_in_what()) { + translateFieldMatcher(metric.dimensions_in_what(), &mDimensionsInWhat); + mContainANYPositionInDimensionsInWhat = HasPositionANY(metric.dimensions_in_what()); + } + + if (metric.links().size() > 0) { + for (const auto& link : metric.links()) { + Metric2Condition mc; + mc.conditionId = link.condition(); + translateFieldMatcher(link.fields_in_what(), &mc.metricFields); + translateFieldMatcher(link.fields_in_condition(), &mc.conditionFields); + mMetric2ConditionLinks.push_back(mc); + } + mConditionSliced = true; + } + mSliceByPositionALL = HasPositionALL(metric.dimensions_in_what()); + + flushIfNeededLocked(startTimeNs); + // Kicks off the puller immediately. + if (mIsPulled && mSamplingType == GaugeMetric::RANDOM_ONE_SAMPLE) { + mPullerManager->RegisterReceiver(mPullTagId, mConfigKey, this, getCurrentBucketEndTimeNs(), + mBucketSizeNs); + } + + // Adjust start for partial first bucket and then pull if needed + mCurrentBucketStartTimeNs = startTimeNs; + + VLOG("Gauge metric %lld created. bucket size %lld start_time: %lld sliced %d", + (long long)metric.id(), (long long)mBucketSizeNs, (long long)mTimeBaseNs, + mConditionSliced); +} + +GaugeMetricProducer::~GaugeMetricProducer() { + VLOG("~GaugeMetricProducer() called"); + if (mIsPulled && mSamplingType == GaugeMetric::RANDOM_ONE_SAMPLE) { + mPullerManager->UnRegisterReceiver(mPullTagId, mConfigKey, this); + } +} + +bool GaugeMetricProducer::onConfigUpdatedLocked( + const StatsdConfig& config, const int configIndex, const int metricIndex, + const vector>& allAtomMatchingTrackers, + const unordered_map& oldAtomMatchingTrackerMap, + const unordered_map& newAtomMatchingTrackerMap, + const sp& matcherWizard, + const vector>& allConditionTrackers, + const unordered_map& conditionTrackerMap, const sp& wizard, + const unordered_map& metricToActivationMap, + unordered_map>& trackerToMetricMap, + unordered_map>& conditionToMetricMap, + unordered_map>& activationAtomTrackerToMetricMap, + unordered_map>& deactivationAtomTrackerToMetricMap, + vector& metricsWithActivation) { + if (!MetricProducer::onConfigUpdatedLocked( + config, configIndex, metricIndex, allAtomMatchingTrackers, + oldAtomMatchingTrackerMap, newAtomMatchingTrackerMap, matcherWizard, + allConditionTrackers, conditionTrackerMap, wizard, metricToActivationMap, + trackerToMetricMap, conditionToMetricMap, activationAtomTrackerToMetricMap, + deactivationAtomTrackerToMetricMap, metricsWithActivation)) { + return false; + } + + const GaugeMetric& metric = config.gauge_metric(configIndex); + // Update appropriate indices: mWhatMatcherIndex, mConditionIndex and MetricsManager maps. + if (!handleMetricWithAtomMatchingTrackers(metric.what(), metricIndex, /*enforceOneAtom=*/false, + allAtomMatchingTrackers, newAtomMatchingTrackerMap, + trackerToMetricMap, mWhatMatcherIndex)) { + return false; + } + + // Need to update maps since the index changed, but mTriggerAtomId will not change. + int triggerTrackerIndex; + if (metric.has_trigger_event() && + !handleMetricWithAtomMatchingTrackers(metric.trigger_event(), metricIndex, + /*enforceOneAtom=*/true, allAtomMatchingTrackers, + newAtomMatchingTrackerMap, trackerToMetricMap, + triggerTrackerIndex)) { + return false; + } + + if (metric.has_condition() && + !handleMetricWithConditions(metric.condition(), metricIndex, conditionTrackerMap, + metric.links(), allConditionTrackers, mConditionTrackerIndex, + conditionToMetricMap)) { + return false; + } + sp tmpEventWizard = mEventMatcherWizard; + mEventMatcherWizard = matcherWizard; + + // If this is a config update, we must have just forced a partial bucket. Pull if needed to get + // data for the new bucket. + if (mIsActive && mIsPulled && mSamplingType == GaugeMetric::RANDOM_ONE_SAMPLE) { + pullAndMatchEventsLocked(mCurrentBucketStartTimeNs); + } + return true; +} + +void GaugeMetricProducer::dumpStatesLocked(FILE* out, bool verbose) const { + if (mCurrentSlicedBucket == nullptr || + mCurrentSlicedBucket->size() == 0) { + return; + } + + fprintf(out, "GaugeMetric %lld dimension size %lu\n", (long long)mMetricId, + (unsigned long)mCurrentSlicedBucket->size()); + if (verbose) { + for (const auto& it : *mCurrentSlicedBucket) { + fprintf(out, "\t(what)%s\t(states)%s %d atoms\n", + it.first.getDimensionKeyInWhat().toString().c_str(), + it.first.getStateValuesKey().toString().c_str(), (int)it.second.size()); + } + } +} + +void GaugeMetricProducer::clearPastBucketsLocked(const int64_t dumpTimeNs) { + flushIfNeededLocked(dumpTimeNs); + mPastBuckets.clear(); + mSkippedBuckets.clear(); +} + +void GaugeMetricProducer::onDumpReportLocked(const int64_t dumpTimeNs, + const bool include_current_partial_bucket, + const bool erase_data, + const DumpLatency dumpLatency, + std::set *str_set, + ProtoOutputStream* protoOutput) { + VLOG("Gauge metric %lld report now...", (long long)mMetricId); + if (include_current_partial_bucket) { + flushLocked(dumpTimeNs); + } else { + flushIfNeededLocked(dumpTimeNs); + } + + protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_ID, (long long)mMetricId); + protoOutput->write(FIELD_TYPE_BOOL | FIELD_ID_IS_ACTIVE, isActiveLocked()); + + if (mPastBuckets.empty() && mSkippedBuckets.empty()) { + return; + } + + protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_TIME_BASE, (long long)mTimeBaseNs); + protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_BUCKET_SIZE, (long long)mBucketSizeNs); + + // Fills the dimension path if not slicing by ALL. + if (!mSliceByPositionALL) { + if (!mDimensionsInWhat.empty()) { + uint64_t dimenPathToken = protoOutput->start( + FIELD_TYPE_MESSAGE | FIELD_ID_DIMENSION_PATH_IN_WHAT); + writeDimensionPathToProto(mDimensionsInWhat, protoOutput); + protoOutput->end(dimenPathToken); + } + } + + uint64_t protoToken = protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_ID_GAUGE_METRICS); + + for (const auto& skippedBucket : mSkippedBuckets) { + uint64_t wrapperToken = + protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_SKIPPED); + protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_SKIPPED_START_MILLIS, + (long long)(NanoToMillis(skippedBucket.bucketStartTimeNs))); + protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_SKIPPED_END_MILLIS, + (long long)(NanoToMillis(skippedBucket.bucketEndTimeNs))); + + for (const auto& dropEvent : skippedBucket.dropEvents) { + uint64_t dropEventToken = protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | + FIELD_ID_SKIPPED_DROP_EVENT); + protoOutput->write(FIELD_TYPE_INT32 | FIELD_ID_BUCKET_DROP_REASON, dropEvent.reason); + protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_DROP_TIME, (long long) (NanoToMillis(dropEvent.dropTimeNs))); + protoOutput->end(dropEventToken); + } + protoOutput->end(wrapperToken); + } + + for (const auto& pair : mPastBuckets) { + const MetricDimensionKey& dimensionKey = pair.first; + + VLOG("Gauge dimension key %s", dimensionKey.toString().c_str()); + uint64_t wrapperToken = + protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_DATA); + + // First fill dimension. + if (mSliceByPositionALL) { + uint64_t dimensionToken = protoOutput->start( + FIELD_TYPE_MESSAGE | FIELD_ID_DIMENSION_IN_WHAT); + writeDimensionToProto(dimensionKey.getDimensionKeyInWhat(), str_set, protoOutput); + protoOutput->end(dimensionToken); + } else { + writeDimensionLeafNodesToProto(dimensionKey.getDimensionKeyInWhat(), + FIELD_ID_DIMENSION_LEAF_IN_WHAT, str_set, protoOutput); + } + + // Then fill bucket_info (GaugeBucketInfo). + for (const auto& bucket : pair.second) { + uint64_t bucketInfoToken = protoOutput->start( + FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_BUCKET_INFO); + + if (bucket.mBucketEndNs - bucket.mBucketStartNs != mBucketSizeNs) { + protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_START_BUCKET_ELAPSED_MILLIS, + (long long)NanoToMillis(bucket.mBucketStartNs)); + protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_END_BUCKET_ELAPSED_MILLIS, + (long long)NanoToMillis(bucket.mBucketEndNs)); + } else { + protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_BUCKET_NUM, + (long long)(getBucketNumFromEndTimeNs(bucket.mBucketEndNs))); + } + + if (!bucket.mGaugeAtoms.empty()) { + for (const auto& atom : bucket.mGaugeAtoms) { + uint64_t atomsToken = + protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | + FIELD_ID_ATOM); + writeFieldValueTreeToStream(mAtomId, *(atom.mFields), protoOutput); + protoOutput->end(atomsToken); + } + for (const auto& atom : bucket.mGaugeAtoms) { + protoOutput->write(FIELD_TYPE_INT64 | FIELD_COUNT_REPEATED | + FIELD_ID_ELAPSED_ATOM_TIMESTAMP, + (long long)atom.mElapsedTimestampNs); + } + } + protoOutput->end(bucketInfoToken); + VLOG("Gauge \t bucket [%lld - %lld] includes %d atoms.", + (long long)bucket.mBucketStartNs, (long long)bucket.mBucketEndNs, + (int)bucket.mGaugeAtoms.size()); + } + protoOutput->end(wrapperToken); + } + protoOutput->end(protoToken); + + + if (erase_data) { + mPastBuckets.clear(); + mSkippedBuckets.clear(); + } +} + +void GaugeMetricProducer::prepareFirstBucketLocked() { + if (mIsActive && mIsPulled && mSamplingType == GaugeMetric::RANDOM_ONE_SAMPLE) { + pullAndMatchEventsLocked(mCurrentBucketStartTimeNs); + } +} + +void GaugeMetricProducer::pullAndMatchEventsLocked(const int64_t timestampNs) { + bool triggerPuller = false; + switch(mSamplingType) { + // When the metric wants to do random sampling and there is already one gauge atom for the + // current bucket, do not do it again. + case GaugeMetric::RANDOM_ONE_SAMPLE: { + triggerPuller = mCondition == ConditionState::kTrue && mCurrentSlicedBucket->empty(); + break; + } + case GaugeMetric::CONDITION_CHANGE_TO_TRUE: { + triggerPuller = mCondition == ConditionState::kTrue; + break; + } + case GaugeMetric::FIRST_N_SAMPLES: { + triggerPuller = mCondition == ConditionState::kTrue; + break; + } + default: + break; + } + if (!triggerPuller) { + return; + } + vector> allData; + if (!mPullerManager->Pull(mPullTagId, mConfigKey, timestampNs, &allData)) { + ALOGE("Gauge Stats puller failed for tag: %d at %lld", mPullTagId, (long long)timestampNs); + return; + } + const int64_t pullDelayNs = getElapsedRealtimeNs() - timestampNs; + StatsdStats::getInstance().notePullDelay(mPullTagId, pullDelayNs); + if (pullDelayNs > mMaxPullDelayNs) { + ALOGE("Pull finish too late for atom %d", mPullTagId); + StatsdStats::getInstance().notePullExceedMaxDelay(mPullTagId); + return; + } + for (const auto& data : allData) { + LogEvent localCopy = data->makeCopy(); + localCopy.setElapsedTimestampNs(timestampNs); + if (mEventMatcherWizard->matchLogEvent(localCopy, mWhatMatcherIndex) == + MatchingState::kMatched) { + onMatchedLogEventLocked(mWhatMatcherIndex, localCopy); + } + } +} + +void GaugeMetricProducer::onActiveStateChangedLocked(const int64_t& eventTimeNs) { + MetricProducer::onActiveStateChangedLocked(eventTimeNs); + if (ConditionState::kTrue != mCondition || !mIsPulled) { + return; + } + if (mTriggerAtomId == -1 || (mIsActive && mSamplingType == GaugeMetric::RANDOM_ONE_SAMPLE)) { + pullAndMatchEventsLocked(eventTimeNs); + } + +} + +void GaugeMetricProducer::onConditionChangedLocked(const bool conditionMet, + const int64_t eventTimeNs) { + VLOG("GaugeMetric %lld onConditionChanged", (long long)mMetricId); + + mCondition = conditionMet ? ConditionState::kTrue : ConditionState::kFalse; + if (!mIsActive) { + return; + } + + flushIfNeededLocked(eventTimeNs); + if (mIsPulled && mTriggerAtomId == -1) { + pullAndMatchEventsLocked(eventTimeNs); + } // else: Push mode. No need to proactively pull the gauge data. +} + +void GaugeMetricProducer::onSlicedConditionMayChangeLocked(bool overallCondition, + const int64_t eventTimeNs) { + VLOG("GaugeMetric %lld onSlicedConditionMayChange overall condition %d", (long long)mMetricId, + overallCondition); + mCondition = overallCondition ? ConditionState::kTrue : ConditionState::kFalse; + if (!mIsActive) { + return; + } + + flushIfNeededLocked(eventTimeNs); + // If the condition is sliced, mCondition is true if any of the dimensions is true. And we will + // pull for every dimension. + if (mIsPulled && mTriggerAtomId == -1) { + pullAndMatchEventsLocked(eventTimeNs); + } // else: Push mode. No need to proactively pull the gauge data. +} + +std::shared_ptr> GaugeMetricProducer::getGaugeFields(const LogEvent& event) { + std::shared_ptr> gaugeFields; + if (mFieldMatchers.size() > 0) { + gaugeFields = std::make_shared>(); + filterGaugeValues(mFieldMatchers, event.getValues(), gaugeFields.get()); + } else { + gaugeFields = std::make_shared>(event.getValues()); + } + // Trim all dimension fields from output. Dimensions will appear in output report and will + // benefit from dictionary encoding. For large pulled atoms, this can give the benefit of + // optional repeated field. + for (const auto& field : mDimensionsInWhat) { + for (auto it = gaugeFields->begin(); it != gaugeFields->end();) { + if (it->mField.matches(field)) { + it = gaugeFields->erase(it); + } else { + it++; + } + } + } + return gaugeFields; +} + +void GaugeMetricProducer::onDataPulled(const std::vector>& allData, + bool pullSuccess, int64_t originalPullTimeNs) { + std::lock_guard lock(mMutex); + if (!pullSuccess || allData.size() == 0) { + return; + } + const int64_t pullDelayNs = getElapsedRealtimeNs() - originalPullTimeNs; + StatsdStats::getInstance().notePullDelay(mPullTagId, pullDelayNs); + if (pullDelayNs > mMaxPullDelayNs) { + ALOGE("Pull finish too late for atom %d", mPullTagId); + StatsdStats::getInstance().notePullExceedMaxDelay(mPullTagId); + return; + } + for (const auto& data : allData) { + if (mEventMatcherWizard->matchLogEvent( + *data, mWhatMatcherIndex) == MatchingState::kMatched) { + onMatchedLogEventLocked(mWhatMatcherIndex, *data); + } + } +} + +bool GaugeMetricProducer::hitGuardRailLocked(const MetricDimensionKey& newKey) { + if (mCurrentSlicedBucket->find(newKey) != mCurrentSlicedBucket->end()) { + return false; + } + // 1. Report the tuple count if the tuple count > soft limit + if (mCurrentSlicedBucket->size() > mDimensionSoftLimit - 1) { + size_t newTupleCount = mCurrentSlicedBucket->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 > mDimensionHardLimit) { + ALOGE("GaugeMetric %lld dropping data for dimension key %s", + (long long)mMetricId, newKey.toString().c_str()); + StatsdStats::getInstance().noteHardDimensionLimitReached(mMetricId); + return true; + } + } + + return false; +} + +void GaugeMetricProducer::onMatchedLogEventInternalLocked( + const size_t matcherIndex, const MetricDimensionKey& eventKey, + const ConditionKey& conditionKey, bool condition, const LogEvent& event, + const map& statePrimaryKeys) { + if (condition == false) { + return; + } + int64_t eventTimeNs = event.GetElapsedTimestampNs(); + if (eventTimeNs < mCurrentBucketStartTimeNs) { + VLOG("Gauge Skip event due to late arrival: %lld vs %lld", (long long)eventTimeNs, + (long long)mCurrentBucketStartTimeNs); + return; + } + flushIfNeededLocked(eventTimeNs); + + if (mTriggerAtomId == event.GetTagId()) { + pullAndMatchEventsLocked(eventTimeNs); + return; + } + + // When gauge metric wants to randomly sample the output atom, we just simply use the first + // gauge in the given bucket. + if (mCurrentSlicedBucket->find(eventKey) != mCurrentSlicedBucket->end() && + mSamplingType == GaugeMetric::RANDOM_ONE_SAMPLE) { + return; + } + if (hitGuardRailLocked(eventKey)) { + return; + } + if ((*mCurrentSlicedBucket)[eventKey].size() >= mGaugeAtomsPerDimensionLimit) { + return; + } + + const int64_t truncatedElapsedTimestampNs = truncateTimestampIfNecessary(event); + GaugeAtom gaugeAtom(getGaugeFields(event), truncatedElapsedTimestampNs); + (*mCurrentSlicedBucket)[eventKey].push_back(gaugeAtom); + // Anomaly detection on gauge metric only works when there is one numeric + // field specified. + if (mAnomalyTrackers.size() > 0) { + if (gaugeAtom.mFields->size() == 1) { + const Value& value = gaugeAtom.mFields->begin()->mValue; + long gaugeVal = 0; + if (value.getType() == INT) { + gaugeVal = (long)value.int_value; + } else if (value.getType() == LONG) { + gaugeVal = value.long_value; + } + for (auto& tracker : mAnomalyTrackers) { + tracker->detectAndDeclareAnomaly(eventTimeNs, mCurrentBucketNum, mMetricId, + eventKey, gaugeVal); + } + } + } +} + +void GaugeMetricProducer::updateCurrentSlicedBucketForAnomaly() { + for (const auto& slice : *mCurrentSlicedBucket) { + if (slice.second.empty()) { + continue; + } + const Value& value = slice.second.front().mFields->front().mValue; + long gaugeVal = 0; + if (value.getType() == INT) { + gaugeVal = (long)value.int_value; + } else if (value.getType() == LONG) { + gaugeVal = value.long_value; + } + (*mCurrentSlicedBucketForAnomaly)[slice.first] = gaugeVal; + } +} + +void GaugeMetricProducer::dropDataLocked(const int64_t dropTimeNs) { + flushIfNeededLocked(dropTimeNs); + StatsdStats::getInstance().noteBucketDropped(mMetricId); + mPastBuckets.clear(); +} + +// When a new matched event comes in, we check if event falls into the current +// bucket. If not, flush the old counter to past buckets and initialize the new +// bucket. +// if data is pushed, onMatchedLogEvent will only be called through onConditionChanged() inside +// the GaugeMetricProducer while holding the lock. +void GaugeMetricProducer::flushIfNeededLocked(const int64_t& eventTimeNs) { + int64_t currentBucketEndTimeNs = getCurrentBucketEndTimeNs(); + + if (eventTimeNs < currentBucketEndTimeNs) { + VLOG("Gauge eventTime is %lld, less than next bucket start time %lld", + (long long)eventTimeNs, (long long)(mCurrentBucketStartTimeNs + mBucketSizeNs)); + return; + } + + // Adjusts the bucket start and end times. + int64_t numBucketsForward = 1 + (eventTimeNs - currentBucketEndTimeNs) / mBucketSizeNs; + int64_t nextBucketNs = currentBucketEndTimeNs + (numBucketsForward - 1) * mBucketSizeNs; + flushCurrentBucketLocked(eventTimeNs, nextBucketNs); + + mCurrentBucketNum += numBucketsForward; + VLOG("Gauge metric %lld: new bucket start time: %lld", (long long)mMetricId, + (long long)mCurrentBucketStartTimeNs); +} + +void GaugeMetricProducer::flushCurrentBucketLocked(const int64_t& eventTimeNs, + const int64_t& nextBucketStartTimeNs) { + int64_t fullBucketEndTimeNs = getCurrentBucketEndTimeNs(); + int64_t bucketEndTime = eventTimeNs < fullBucketEndTimeNs ? eventTimeNs : fullBucketEndTimeNs; + + GaugeBucket info; + info.mBucketStartNs = mCurrentBucketStartTimeNs; + info.mBucketEndNs = bucketEndTime; + + // Add bucket to mPastBuckets if bucket is large enough. + // Otherwise, drop the bucket data and add bucket metadata to mSkippedBuckets. + bool isBucketLargeEnough = info.mBucketEndNs - mCurrentBucketStartTimeNs >= mMinBucketSizeNs; + if (isBucketLargeEnough) { + for (const auto& slice : *mCurrentSlicedBucket) { + info.mGaugeAtoms = slice.second; + auto& bucketList = mPastBuckets[slice.first]; + bucketList.push_back(info); + VLOG("Gauge gauge metric %lld, dump key value: %s", (long long)mMetricId, + slice.first.toString().c_str()); + } + } else { + mCurrentSkippedBucket.bucketStartTimeNs = mCurrentBucketStartTimeNs; + mCurrentSkippedBucket.bucketEndTimeNs = bucketEndTime; + if (!maxDropEventsReached()) { + mCurrentSkippedBucket.dropEvents.emplace_back( + buildDropEvent(eventTimeNs, BucketDropReason::BUCKET_TOO_SMALL)); + } + mSkippedBuckets.emplace_back(mCurrentSkippedBucket); + } + + // If we have anomaly trackers, we need to update the partial bucket values. + if (mAnomalyTrackers.size() > 0) { + updateCurrentSlicedBucketForAnomaly(); + + if (eventTimeNs > fullBucketEndTimeNs) { + // This is known to be a full bucket, so send this data to the anomaly tracker. + for (auto& tracker : mAnomalyTrackers) { + tracker->addPastBucket(mCurrentSlicedBucketForAnomaly, mCurrentBucketNum); + } + mCurrentSlicedBucketForAnomaly = std::make_shared(); + } + } + + StatsdStats::getInstance().noteBucketCount(mMetricId); + mCurrentSlicedBucket = std::make_shared(); + mCurrentBucketStartTimeNs = nextBucketStartTimeNs; + mCurrentSkippedBucket.reset(); +} + +size_t GaugeMetricProducer::byteSizeLocked() const { + size_t totalSize = 0; + for (const auto& pair : mPastBuckets) { + for (const auto& bucket : pair.second) { + totalSize += bucket.mGaugeAtoms.size() * sizeof(GaugeAtom); + for (const auto& atom : bucket.mGaugeAtoms) { + if (atom.mFields != nullptr) { + totalSize += atom.mFields->size() * sizeof(FieldValue); + } + } + } + } + return totalSize; +} + +} // namespace statsd +} // namespace os +} // namespace android diff --git a/statsd/src/metrics/GaugeMetricProducer.h b/statsd/src/metrics/GaugeMetricProducer.h new file mode 100644 index 00000000..5e056f32 --- /dev/null +++ b/statsd/src/metrics/GaugeMetricProducer.h @@ -0,0 +1,234 @@ +/* + * 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. + */ + +#pragma once + +#include + +#include +#include +#include "../condition/ConditionTracker.h" +#include "../external/PullDataReceiver.h" +#include "../external/StatsPullerManager.h" +#include "../matchers/matcher_util.h" +#include "../matchers/EventMatcherWizard.h" +#include "MetricProducer.h" +#include "src/statsd_config.pb.h" +#include "../stats_util.h" + +namespace android { +namespace os { +namespace statsd { + +struct GaugeAtom { + GaugeAtom(std::shared_ptr> fields, int64_t elapsedTimeNs) + : mFields(fields), mElapsedTimestampNs(elapsedTimeNs) { + } + std::shared_ptr> mFields; + int64_t mElapsedTimestampNs; +}; + +struct GaugeBucket { + int64_t mBucketStartNs; + int64_t mBucketEndNs; + std::vector mGaugeAtoms; +}; + +typedef std::unordered_map> + DimToGaugeAtomsMap; + +// This gauge metric producer first register the puller to automatically pull the gauge at the +// beginning of each bucket. If the condition is met, insert it to the bucket info. Otherwise +// proactively pull the gauge when the condition is changed to be true. Therefore, the gauge metric +// producer always reports the gauge at the earliest time of the bucket when the condition is met. +class GaugeMetricProducer : public MetricProducer, public virtual PullDataReceiver { +public: + GaugeMetricProducer( + const ConfigKey& key, const GaugeMetric& gaugeMetric, const int conditionIndex, + const vector& initialConditionCache, + const sp& conditionWizard, const uint64_t protoHash, + const int whatMatcherIndex, const sp& matcherWizard, + const int pullTagId, const int triggerAtomId, const int atomId, + const int64_t timeBaseNs, const int64_t startTimeNs, + const sp& pullerManager, + const std::unordered_map>& eventActivationMap = {}, + const std::unordered_map>>& + eventDeactivationMap = {}); + + virtual ~GaugeMetricProducer(); + + // Handles when the pulled data arrives. + void onDataPulled(const std::vector>& data, + bool pullSuccess, int64_t originalPullTimeNs) override; + + // GaugeMetric needs to immediately trigger another pull when we create the partial bucket. + void notifyAppUpgrade(const int64_t& eventTimeNs) override { + std::lock_guard lock(mMutex); + + if (!mSplitBucketForAppUpgrade) { + return; + } + flushLocked(eventTimeNs); + if (mIsPulled && mSamplingType == GaugeMetric::RANDOM_ONE_SAMPLE) { + pullAndMatchEventsLocked(eventTimeNs); + } + }; + + // GaugeMetric needs to immediately trigger another pull when we create the partial bucket. + void onStatsdInitCompleted(const int64_t& eventTimeNs) override { + std::lock_guard lock(mMutex); + + flushLocked(eventTimeNs); + if (mIsPulled && mSamplingType == GaugeMetric::RANDOM_ONE_SAMPLE) { + pullAndMatchEventsLocked(eventTimeNs); + } + }; + + MetricType getMetricType() const override { + return METRIC_TYPE_GAUGE; + } + +protected: + void onMatchedLogEventInternalLocked( + const size_t matcherIndex, const MetricDimensionKey& eventKey, + const ConditionKey& conditionKey, bool condition, const LogEvent& event, + const std::map& statePrimaryKeys) override; + +private: + void onDumpReportLocked(const int64_t dumpTimeNs, + const bool include_current_partial_bucket, + const bool erase_data, + const DumpLatency dumpLatency, + std::set *str_set, + android::util::ProtoOutputStream* protoOutput) override; + void clearPastBucketsLocked(const int64_t dumpTimeNs) override; + + // Internal interface to handle condition change. + void onConditionChangedLocked(const bool conditionMet, const int64_t eventTime) override; + + // Internal interface to handle active state change. + void onActiveStateChangedLocked(const int64_t& eventTimeNs) override; + + // Internal interface to handle sliced condition change. + void onSlicedConditionMayChangeLocked(bool overallCondition, const int64_t eventTime) override; + + // Internal function to calculate the current used bytes. + size_t byteSizeLocked() const override; + + void dumpStatesLocked(FILE* out, bool verbose) const override; + + void dropDataLocked(const int64_t dropTimeNs) override; + + // Util function to flush the old packet. + void flushIfNeededLocked(const int64_t& eventTime) override; + + void flushCurrentBucketLocked(const int64_t& eventTimeNs, + const int64_t& nextBucketStartTimeNs) override; + + void prepareFirstBucketLocked() override; + + void pullAndMatchEventsLocked(const int64_t timestampNs); + + bool onConfigUpdatedLocked( + const StatsdConfig& config, const int configIndex, const int metricIndex, + const std::vector>& allAtomMatchingTrackers, + const std::unordered_map& oldAtomMatchingTrackerMap, + const std::unordered_map& newAtomMatchingTrackerMap, + const sp& matcherWizard, + const std::vector>& allConditionTrackers, + const std::unordered_map& conditionTrackerMap, + const sp& wizard, + const std::unordered_map& metricToActivationMap, + std::unordered_map>& trackerToMetricMap, + std::unordered_map>& conditionToMetricMap, + std::unordered_map>& activationAtomTrackerToMetricMap, + std::unordered_map>& deactivationAtomTrackerToMetricMap, + std::vector& metricsWithActivation) override; + + int mWhatMatcherIndex; + + sp mEventMatcherWizard; + + sp mPullerManager; + // tagId for pulled data. -1 if this is not pulled + const int mPullTagId; + + // tagId for atoms that trigger the pulling, if any + const int mTriggerAtomId; + + // tagId for output atom + const int mAtomId; + + // if this is pulled metric + const bool mIsPulled; + + // Save the past buckets and we can clear when the StatsLogReport is dumped. + std::unordered_map> mPastBuckets; + + // The current partial bucket. + std::shared_ptr mCurrentSlicedBucket; + + // The current full bucket for anomaly detection. This is updated to the latest value seen for + // this slice (ie, for partial buckets, we use the last partial bucket in this full bucket). + std::shared_ptr mCurrentSlicedBucketForAnomaly; + + const int64_t mMinBucketSizeNs; + + // Translate Atom based bucket to single numeric value bucket for anomaly and updates the map + // for each slice with the latest value. + void updateCurrentSlicedBucketForAnomaly(); + + // Allowlist of fields to report. Empty means all are reported. + std::vector mFieldMatchers; + + GaugeMetric::SamplingType mSamplingType; + + const int64_t mMaxPullDelayNs; + + // apply an allowlist on the original input + std::shared_ptr> getGaugeFields(const LogEvent& event); + + // Util function to check whether the specified dimension hits the guardrail. + bool hitGuardRailLocked(const MetricDimensionKey& newKey); + + static const size_t kBucketSize = sizeof(GaugeBucket{}); + + const size_t mDimensionSoftLimit; + + const size_t mDimensionHardLimit; + + const size_t mGaugeAtomsPerDimensionLimit; + + const bool mSplitBucketForAppUpgrade; + + FRIEND_TEST(GaugeMetricProducerTest, TestPulledEventsWithCondition); + FRIEND_TEST(GaugeMetricProducerTest, TestPulledEventsWithSlicedCondition); + FRIEND_TEST(GaugeMetricProducerTest, TestPulledEventsNoCondition); + FRIEND_TEST(GaugeMetricProducerTest, TestPulledWithAppUpgradeDisabled); + FRIEND_TEST(GaugeMetricProducerTest, TestPulledEventsAnomalyDetection); + FRIEND_TEST(GaugeMetricProducerTest, TestFirstBucket); + FRIEND_TEST(GaugeMetricProducerTest, TestPullOnTrigger); + FRIEND_TEST(GaugeMetricProducerTest, TestRemoveDimensionInOutput); + + FRIEND_TEST(GaugeMetricProducerTest_PartialBucket, TestPushedEvents); + FRIEND_TEST(GaugeMetricProducerTest_PartialBucket, TestPulled); + + FRIEND_TEST(ConfigUpdateTest, TestUpdateGaugeMetrics); +}; + +} // namespace statsd +} // namespace os +} // namespace android diff --git a/statsd/src/metrics/MetricProducer.cpp b/statsd/src/metrics/MetricProducer.cpp new file mode 100644 index 00000000..c68e61e7 --- /dev/null +++ b/statsd/src/metrics/MetricProducer.cpp @@ -0,0 +1,356 @@ +/* + * 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. + */ + +#define DEBUG false // STOPSHIP if true +#include "Log.h" + +#include "MetricProducer.h" + +#include "../guardrail/StatsdStats.h" +#include "metrics/parsing_utils/metrics_manager_util.h" +#include "state/StateTracker.h" + +using android::util::FIELD_COUNT_REPEATED; +using android::util::FIELD_TYPE_ENUM; +using android::util::FIELD_TYPE_INT32; +using android::util::FIELD_TYPE_INT64; +using android::util::FIELD_TYPE_MESSAGE; +using android::util::ProtoOutputStream; + +namespace android { +namespace os { +namespace statsd { + + +// for ActiveMetric +const int FIELD_ID_ACTIVE_METRIC_ID = 1; +const int FIELD_ID_ACTIVE_METRIC_ACTIVATION = 2; + +// for ActiveEventActivation +const int FIELD_ID_ACTIVE_EVENT_ACTIVATION_ATOM_MATCHER_INDEX = 1; +const int FIELD_ID_ACTIVE_EVENT_ACTIVATION_REMAINING_TTL_NANOS = 2; +const int FIELD_ID_ACTIVE_EVENT_ACTIVATION_STATE = 3; + +MetricProducer::MetricProducer( + const int64_t& metricId, const ConfigKey& key, const int64_t timeBaseNs, + const int conditionIndex, const vector& initialConditionCache, + const sp& wizard, const uint64_t protoHash, + const std::unordered_map>& eventActivationMap, + const std::unordered_map>>& + eventDeactivationMap, + const vector& slicedStateAtoms, + const unordered_map>& stateGroupMap) + : mMetricId(metricId), + mProtoHash(protoHash), + mConfigKey(key), + mValid(true), + mTimeBaseNs(timeBaseNs), + mCurrentBucketStartTimeNs(timeBaseNs), + mCurrentBucketNum(0), + mCondition(initialCondition(conditionIndex, initialConditionCache)), + mConditionTrackerIndex(conditionIndex), + mConditionSliced(false), + mWizard(wizard), + mContainANYPositionInDimensionsInWhat(false), + mSliceByPositionALL(false), + mHasLinksToAllConditionDimensionsInTracker(false), + mEventActivationMap(eventActivationMap), + mEventDeactivationMap(eventDeactivationMap), + mIsActive(mEventActivationMap.empty()), + mSlicedStateAtoms(slicedStateAtoms), + mStateGroupMap(stateGroupMap) { +} + +bool MetricProducer::onConfigUpdatedLocked( + const StatsdConfig& config, const int configIndex, const int metricIndex, + const vector>& allAtomMatchingTrackers, + const unordered_map& oldAtomMatchingTrackerMap, + const unordered_map& newAtomMatchingTrackerMap, + const sp& matcherWizard, + const vector>& allConditionTrackers, + const unordered_map& conditionTrackerMap, const sp& wizard, + const unordered_map& metricToActivationMap, + unordered_map>& trackerToMetricMap, + unordered_map>& conditionToMetricMap, + unordered_map>& activationAtomTrackerToMetricMap, + unordered_map>& deactivationAtomTrackerToMetricMap, + vector& metricsWithActivation) { + sp tmpWizard = mWizard; + mWizard = wizard; + + unordered_map> newEventActivationMap; + unordered_map>> newEventDeactivationMap; + if (!handleMetricActivationOnConfigUpdate( + config, mMetricId, metricIndex, metricToActivationMap, oldAtomMatchingTrackerMap, + newAtomMatchingTrackerMap, mEventActivationMap, activationAtomTrackerToMetricMap, + deactivationAtomTrackerToMetricMap, metricsWithActivation, newEventActivationMap, + newEventDeactivationMap)) { + return false; + } + mEventActivationMap = newEventActivationMap; + mEventDeactivationMap = newEventDeactivationMap; + mAnomalyTrackers.clear(); + return true; +} + +void MetricProducer::onMatchedLogEventLocked(const size_t matcherIndex, const LogEvent& event) { + if (!mIsActive) { + return; + } + int64_t eventTimeNs = event.GetElapsedTimestampNs(); + // this is old event, maybe statsd restarted? + if (eventTimeNs < mTimeBaseNs) { + return; + } + + bool condition; + ConditionKey conditionKey; + if (mConditionSliced) { + for (const auto& link : mMetric2ConditionLinks) { + getDimensionForCondition(event.getValues(), link, &conditionKey[link.conditionId]); + } + auto conditionState = + mWizard->query(mConditionTrackerIndex, conditionKey, + !mHasLinksToAllConditionDimensionsInTracker); + condition = (conditionState == ConditionState::kTrue); + } else { + // TODO: The unknown condition state is not handled here, we should fix it. + condition = mCondition == ConditionState::kTrue; + } + + // Stores atom id to primary key pairs for each state atom that the metric is + // sliced by. + std::map statePrimaryKeys; + + // For states with primary fields, use MetricStateLinks to get the primary + // field values from the log event. These values will form a primary key + // that will be used to query StateTracker for the correct state value. + for (const auto& stateLink : mMetric2StateLinks) { + getDimensionForState(event.getValues(), stateLink, + &statePrimaryKeys[stateLink.stateAtomId]); + } + + // For each sliced state, query StateTracker for the state value using + // either the primary key from the previous step or the DEFAULT_DIMENSION_KEY. + // + // Expected functionality: for any case where the MetricStateLinks are + // initialized incorrectly (ex. # of state links != # of primary fields, no + // links are provided for a state with primary fields, links are provided + // in the wrong order, etc.), StateTracker will simply return kStateUnknown + // when queried using an incorrect key. + HashableDimensionKey stateValuesKey; + for (auto atomId : mSlicedStateAtoms) { + FieldValue value; + if (statePrimaryKeys.find(atomId) != statePrimaryKeys.end()) { + // found a primary key for this state, query using the key + queryStateValue(atomId, statePrimaryKeys[atomId], &value); + } else { + // if no MetricStateLinks exist for this state atom, + // query using the default dimension key (empty HashableDimensionKey) + queryStateValue(atomId, DEFAULT_DIMENSION_KEY, &value); + } + mapStateValue(atomId, &value); + stateValuesKey.addValue(value); + } + + HashableDimensionKey dimensionInWhat; + filterValues(mDimensionsInWhat, event.getValues(), &dimensionInWhat); + MetricDimensionKey metricKey(dimensionInWhat, stateValuesKey); + onMatchedLogEventInternalLocked(matcherIndex, metricKey, conditionKey, condition, event, + statePrimaryKeys); +} + +bool MetricProducer::evaluateActiveStateLocked(int64_t elapsedTimestampNs) { + bool isActive = mEventActivationMap.empty(); + for (auto& it : mEventActivationMap) { + if (it.second->state == ActivationState::kActive && + elapsedTimestampNs > it.second->ttl_ns + it.second->start_ns) { + it.second->state = ActivationState::kNotActive; + } + if (it.second->state == ActivationState::kActive) { + isActive = true; + } + } + return isActive; +} + +void MetricProducer::flushIfExpire(int64_t elapsedTimestampNs) { + std::lock_guard lock(mMutex); + if (!mIsActive) { + return; + } + mIsActive = evaluateActiveStateLocked(elapsedTimestampNs); + if (!mIsActive) { + onActiveStateChangedLocked(elapsedTimestampNs); + } +} + +void MetricProducer::activateLocked(int activationTrackerIndex, int64_t elapsedTimestampNs) { + auto it = mEventActivationMap.find(activationTrackerIndex); + if (it == mEventActivationMap.end()) { + return; + } + auto& activation = it->second; + if (ACTIVATE_ON_BOOT == activation->activationType) { + if (ActivationState::kNotActive == activation->state) { + activation->state = ActivationState::kActiveOnBoot; + } + // If the Activation is already active or set to kActiveOnBoot, do nothing. + return; + } + activation->start_ns = elapsedTimestampNs; + activation->state = ActivationState::kActive; + bool oldActiveState = mIsActive; + mIsActive = true; + if (!oldActiveState) { // Metric went from not active to active. + onActiveStateChangedLocked(elapsedTimestampNs); + } +} + +void MetricProducer::cancelEventActivationLocked(int deactivationTrackerIndex) { + auto it = mEventDeactivationMap.find(deactivationTrackerIndex); + if (it == mEventDeactivationMap.end()) { + return; + } + for (auto activationToCancelIt : it->second) { + activationToCancelIt->state = ActivationState::kNotActive; + } +} + +void MetricProducer::loadActiveMetricLocked(const ActiveMetric& activeMetric, + int64_t currentTimeNs) { + if (mEventActivationMap.size() == 0) { + return; + } + for (int i = 0; i < activeMetric.activation_size(); i++) { + const auto& activeEventActivation = activeMetric.activation(i); + auto it = mEventActivationMap.find(activeEventActivation.atom_matcher_index()); + if (it == mEventActivationMap.end()) { + ALOGE("Saved event activation not found"); + continue; + } + auto& activation = it->second; + // If the event activation does not have a state, assume it is active. + if (!activeEventActivation.has_state() || + activeEventActivation.state() == ActiveEventActivation::ACTIVE) { + // We don't want to change the ttl for future activations, so we set the start_ns + // such that start_ns + ttl_ns == currentTimeNs + remaining_ttl_nanos + activation->start_ns = + currentTimeNs + activeEventActivation.remaining_ttl_nanos() - activation->ttl_ns; + activation->state = ActivationState::kActive; + mIsActive = true; + } else if (activeEventActivation.state() == ActiveEventActivation::ACTIVATE_ON_BOOT) { + activation->state = ActivationState::kActiveOnBoot; + } + } +} + +void MetricProducer::writeActiveMetricToProtoOutputStream( + int64_t currentTimeNs, const DumpReportReason reason, ProtoOutputStream* proto) { + proto->write(FIELD_TYPE_INT64 | FIELD_ID_ACTIVE_METRIC_ID, (long long)mMetricId); + for (auto& it : mEventActivationMap) { + const int atom_matcher_index = it.first; + const std::shared_ptr& activation = it.second; + + if (ActivationState::kNotActive == activation->state || + (ActivationState::kActive == activation->state && + activation->start_ns + activation->ttl_ns < currentTimeNs)) { + continue; + } + + const uint64_t activationToken = proto->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | + FIELD_ID_ACTIVE_METRIC_ACTIVATION); + proto->write(FIELD_TYPE_INT32 | FIELD_ID_ACTIVE_EVENT_ACTIVATION_ATOM_MATCHER_INDEX, + atom_matcher_index); + if (ActivationState::kActive == activation->state) { + const int64_t remainingTtlNs = + activation->start_ns + activation->ttl_ns - currentTimeNs; + proto->write(FIELD_TYPE_INT64 | FIELD_ID_ACTIVE_EVENT_ACTIVATION_REMAINING_TTL_NANOS, + (long long)remainingTtlNs); + proto->write(FIELD_TYPE_ENUM | FIELD_ID_ACTIVE_EVENT_ACTIVATION_STATE, + ActiveEventActivation::ACTIVE); + + } else if (ActivationState::kActiveOnBoot == activation->state) { + if (reason == DEVICE_SHUTDOWN || reason == TERMINATION_SIGNAL_RECEIVED) { + proto->write( + FIELD_TYPE_INT64 | FIELD_ID_ACTIVE_EVENT_ACTIVATION_REMAINING_TTL_NANOS, + (long long)activation->ttl_ns); + proto->write(FIELD_TYPE_ENUM | FIELD_ID_ACTIVE_EVENT_ACTIVATION_STATE, + ActiveEventActivation::ACTIVE); + } else if (reason == STATSCOMPANION_DIED) { + // We are saving because of system server death, not due to a device shutdown. + // Next time we load, we do not want to activate metrics that activate on boot. + proto->write(FIELD_TYPE_ENUM | FIELD_ID_ACTIVE_EVENT_ACTIVATION_STATE, + ActiveEventActivation::ACTIVATE_ON_BOOT); + } + } + proto->end(activationToken); + } +} + +void MetricProducer::queryStateValue(const int32_t atomId, const HashableDimensionKey& queryKey, + FieldValue* value) { + if (!StateManager::getInstance().getStateValue(atomId, queryKey, value)) { + value->mValue = Value(StateTracker::kStateUnknown); + value->mField.setTag(atomId); + ALOGW("StateTracker not found for state atom %d", atomId); + return; + } +} + +void MetricProducer::mapStateValue(const int32_t atomId, FieldValue* value) { + // check if there is a state map for this atom + auto atomIt = mStateGroupMap.find(atomId); + if (atomIt == mStateGroupMap.end()) { + return; + } + auto valueIt = atomIt->second.find(value->mValue.int_value); + if (valueIt == atomIt->second.end()) { + // state map exists, but value was not put in a state group + // so set mValue to kStateUnknown + // TODO(tsaichristine): handle incomplete state maps + value->mValue.setInt(StateTracker::kStateUnknown); + } else { + // set mValue to group_id + value->mValue.setLong(valueIt->second); + } +} + +HashableDimensionKey MetricProducer::getUnknownStateKey() { + HashableDimensionKey stateKey; + for (auto atom : mSlicedStateAtoms) { + FieldValue fieldValue; + fieldValue.mField.setTag(atom); + fieldValue.mValue.setInt(StateTracker::kStateUnknown); + stateKey.addValue(fieldValue); + } + return stateKey; +} + +DropEvent MetricProducer::buildDropEvent(const int64_t dropTimeNs, const BucketDropReason reason) { + DropEvent event; + event.reason = reason; + event.dropTimeNs = dropTimeNs; + return event; +} + +bool MetricProducer::maxDropEventsReached() { + return mCurrentSkippedBucket.dropEvents.size() >= StatsdStats::kMaxLoggedBucketDropEvents; +} + +} // namespace statsd +} // namespace os +} // namespace android diff --git a/statsd/src/metrics/MetricProducer.h b/statsd/src/metrics/MetricProducer.h new file mode 100644 index 00000000..9fc79d8a --- /dev/null +++ b/statsd/src/metrics/MetricProducer.h @@ -0,0 +1,593 @@ +/* + * 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. + */ + +#ifndef METRIC_PRODUCER_H +#define METRIC_PRODUCER_H + +#include +#include + +#include + +#include "HashableDimensionKey.h" +#include "anomaly/AnomalyTracker.h" +#include "condition/ConditionWizard.h" +#include "config/ConfigKey.h" +#include "matchers/EventMatcherWizard.h" +#include "matchers/matcher_util.h" +#include "packages/PackageInfoListener.h" +#include "state/StateListener.h" +#include "state/StateManager.h" + +namespace android { +namespace os { +namespace statsd { + +// Keep this in sync with DumpReportReason enum in stats_log.proto +enum DumpReportReason { + DEVICE_SHUTDOWN = 1, + CONFIG_UPDATED = 2, + CONFIG_REMOVED = 3, + GET_DATA_CALLED = 4, + ADB_DUMP = 5, + CONFIG_RESET = 6, + STATSCOMPANION_DIED = 7, + TERMINATION_SIGNAL_RECEIVED = 8 +}; + +// If the metric has no activation requirement, it will be active once the metric producer is +// created. +// If the metric needs to be activated by atoms, the metric producer will start +// with kNotActive state, turn to kActive or kActiveOnBoot when the activation event arrives, become +// kNotActive when it reaches the duration limit (timebomb). If the activation event arrives again +// before or after it expires, the event producer will be re-activated and ttl will be reset. +enum ActivationState { + kNotActive = 0, + kActive = 1, + kActiveOnBoot = 2, +}; + +enum DumpLatency { + // In some cases, we only have a short time range to do the dump, e.g. statsd is being killed. + // We might be able to return all the data in this mode. For instance, pull metrics might need + // to be pulled when the current bucket is requested. + FAST = 1, + // In other cases, it is fine for a dump to take more than a few milliseconds, e.g. config + // updates. + NO_TIME_CONSTRAINTS = 2 +}; + +// Keep this in sync with BucketDropReason enum in stats_log.proto +enum BucketDropReason { + // For ValueMetric, a bucket is dropped during a dump report request iff + // current bucket should be included, a pull is needed (pulled metric and + // condition is true), and we are under fast time constraints. + DUMP_REPORT_REQUESTED = 1, + EVENT_IN_WRONG_BUCKET = 2, + CONDITION_UNKNOWN = 3, + PULL_FAILED = 4, + PULL_DELAYED = 5, + DIMENSION_GUARDRAIL_REACHED = 6, + MULTIPLE_BUCKETS_SKIPPED = 7, + // Not an invalid bucket case, but the bucket is dropped. + BUCKET_TOO_SMALL = 8, + // Not an invalid bucket case, but the bucket is skipped. + NO_DATA = 9 +}; + +enum MetricType { + METRIC_TYPE_EVENT = 1, + METRIC_TYPE_COUNT = 2, + METRIC_TYPE_DURATION = 3, + METRIC_TYPE_GAUGE = 4, + METRIC_TYPE_VALUE = 5, +}; +struct Activation { + Activation(const ActivationType& activationType, const int64_t ttlNs) + : ttl_ns(ttlNs), + start_ns(0), + state(ActivationState::kNotActive), + activationType(activationType) {} + + const int64_t ttl_ns; + int64_t start_ns; + ActivationState state; + const ActivationType activationType; +}; + +struct DropEvent { + // Reason for dropping the bucket and/or marking the bucket invalid. + BucketDropReason reason; + // The timestamp of the drop event. + int64_t dropTimeNs; +}; + +struct SkippedBucket { + // Start time of the dropped bucket. + int64_t bucketStartTimeNs; + // End time of the dropped bucket. + int64_t bucketEndTimeNs; + // List of events that invalidated this bucket. + std::vector dropEvents; + + void reset() { + bucketStartTimeNs = 0; + bucketEndTimeNs = 0; + dropEvents.clear(); + } +}; + +// A MetricProducer is responsible for compute one single metrics, creating stats log report, and +// writing the report to dropbox. MetricProducers should respond to package changes as required in +// PackageInfoListener, but if none of the metrics are slicing by package name, then the update can +// be a no-op. +class MetricProducer : public virtual android::RefBase, public virtual StateListener { +public: + MetricProducer(const int64_t& metricId, const ConfigKey& key, const int64_t timeBaseNs, + const int conditionIndex, const vector& initialConditionCache, + const sp& wizard, const uint64_t protoHash, + const std::unordered_map>& eventActivationMap, + const std::unordered_map>>& + eventDeactivationMap, + const vector& slicedStateAtoms, + const unordered_map>& stateGroupMap); + + virtual ~MetricProducer(){}; + + ConditionState initialCondition(const int conditionIndex, + const vector& initialConditionCache) const { + return conditionIndex >= 0 ? initialConditionCache[conditionIndex] : ConditionState::kTrue; + } + + // Update appropriate state on config updates. Primarily, all indices need to be updated. + // This metric and all of its dependencies are guaranteed to be preserved across the update. + // This function also updates several maps used by metricsManager. + // This function clears all anomaly trackers. All anomaly trackers need to be added again. + bool onConfigUpdated( + const StatsdConfig& config, const int configIndex, const int metricIndex, + const std::vector>& allAtomMatchingTrackers, + const std::unordered_map& oldAtomMatchingTrackerMap, + const std::unordered_map& newAtomMatchingTrackerMap, + const sp& matcherWizard, + const std::vector>& allConditionTrackers, + const std::unordered_map& conditionTrackerMap, + const sp& wizard, + const std::unordered_map& metricToActivationMap, + std::unordered_map>& trackerToMetricMap, + std::unordered_map>& conditionToMetricMap, + std::unordered_map>& activationAtomTrackerToMetricMap, + std::unordered_map>& deactivationAtomTrackerToMetricMap, + std::vector& metricsWithActivation) { + std::lock_guard lock(mMutex); + return onConfigUpdatedLocked(config, configIndex, metricIndex, allAtomMatchingTrackers, + oldAtomMatchingTrackerMap, newAtomMatchingTrackerMap, + matcherWizard, allConditionTrackers, conditionTrackerMap, + wizard, metricToActivationMap, trackerToMetricMap, + conditionToMetricMap, activationAtomTrackerToMetricMap, + deactivationAtomTrackerToMetricMap, metricsWithActivation); + }; + + /** + * Force a partial bucket split on app upgrade + */ + virtual void notifyAppUpgrade(const int64_t& eventTimeNs) { + std::lock_guard lock(mMutex); + flushLocked(eventTimeNs); + }; + + void notifyAppRemoved(const int64_t& eventTimeNs) { + // Force buckets to split on removal also. + notifyAppUpgrade(eventTimeNs); + }; + + /** + * Force a partial bucket split on boot complete. + */ + virtual void onStatsdInitCompleted(const int64_t& eventTimeNs) { + std::lock_guard lock(mMutex); + flushLocked(eventTimeNs); + } + // Consume the parsed stats log entry that already matched the "what" of the metric. + void onMatchedLogEvent(const size_t matcherIndex, const LogEvent& event) { + std::lock_guard lock(mMutex); + onMatchedLogEventLocked(matcherIndex, event); + } + + void onConditionChanged(const bool condition, const int64_t eventTime) { + std::lock_guard lock(mMutex); + onConditionChangedLocked(condition, eventTime); + } + + void onSlicedConditionMayChange(bool overallCondition, const int64_t eventTime) { + std::lock_guard lock(mMutex); + onSlicedConditionMayChangeLocked(overallCondition, eventTime); + } + + bool isConditionSliced() const { + std::lock_guard lock(mMutex); + return mConditionSliced; + }; + + void onStateChanged(const int64_t eventTimeNs, const int32_t atomId, + const HashableDimensionKey& primaryKey, const FieldValue& oldState, + const FieldValue& newState){}; + + // Output the metrics data to [protoOutput]. All metrics reports end with the same timestamp. + // This method clears all the past buckets. + void onDumpReport(const int64_t dumpTimeNs, + const bool include_current_partial_bucket, + const bool erase_data, + const DumpLatency dumpLatency, + std::set *str_set, + android::util::ProtoOutputStream* protoOutput) { + std::lock_guard lock(mMutex); + return onDumpReportLocked(dumpTimeNs, include_current_partial_bucket, erase_data, + dumpLatency, str_set, protoOutput); + } + + virtual bool onConfigUpdatedLocked( + const StatsdConfig& config, const int configIndex, const int metricIndex, + const std::vector>& allAtomMatchingTrackers, + const std::unordered_map& oldAtomMatchingTrackerMap, + const std::unordered_map& newAtomMatchingTrackerMap, + const sp& matcherWizard, + const std::vector>& allConditionTrackers, + const std::unordered_map& conditionTrackerMap, + const sp& wizard, + const std::unordered_map& metricToActivationMap, + std::unordered_map>& trackerToMetricMap, + std::unordered_map>& conditionToMetricMap, + std::unordered_map>& activationAtomTrackerToMetricMap, + std::unordered_map>& deactivationAtomTrackerToMetricMap, + std::vector& metricsWithActivation); + + void clearPastBuckets(const int64_t dumpTimeNs) { + std::lock_guard lock(mMutex); + return clearPastBucketsLocked(dumpTimeNs); + } + + void prepareFirstBucket() { + std::lock_guard lock(mMutex); + prepareFirstBucketLocked(); + } + + // Returns the memory in bytes currently used to store this metric's data. Does not change + // state. + size_t byteSize() const { + std::lock_guard lock(mMutex); + return byteSizeLocked(); + } + + void dumpStates(FILE* out, bool verbose) const { + std::lock_guard lock(mMutex); + dumpStatesLocked(out, verbose); + } + + // Let MetricProducer drop in-memory data to save memory. + // We still need to keep future data valid and anomaly tracking work, which means we will + // have to flush old data, informing anomaly trackers then safely drop old data. + // We still keep current bucket data for future metrics' validity. + void dropData(const int64_t dropTimeNs) { + std::lock_guard lock(mMutex); + dropDataLocked(dropTimeNs); + } + + void loadActiveMetric(const ActiveMetric& activeMetric, int64_t currentTimeNs) { + std::lock_guard lock(mMutex); + loadActiveMetricLocked(activeMetric, currentTimeNs); + } + + void activate(int activationTrackerIndex, int64_t elapsedTimestampNs) { + std::lock_guard lock(mMutex); + activateLocked(activationTrackerIndex, elapsedTimestampNs); + } + + void cancelEventActivation(int deactivationTrackerIndex) { + std::lock_guard lock(mMutex); + cancelEventActivationLocked(deactivationTrackerIndex); + } + + bool isActive() const { + std::lock_guard lock(mMutex); + return isActiveLocked(); + } + + void flushIfExpire(int64_t elapsedTimestampNs); + + void writeActiveMetricToProtoOutputStream( + int64_t currentTimeNs, const DumpReportReason reason, ProtoOutputStream* proto); + + // Start: getters/setters + inline int64_t getMetricId() const { + return mMetricId; + } + + inline uint64_t getProtoHash() const { + return mProtoHash; + } + + virtual MetricType getMetricType() const = 0; + + // For test only. + inline int64_t getCurrentBucketNum() const { + return mCurrentBucketNum; + } + + int64_t getBucketSizeInNs() const { + std::lock_guard lock(mMutex); + return mBucketSizeNs; + } + + inline const std::vector getSlicedStateAtoms() { + std::lock_guard lock(mMutex); + return mSlicedStateAtoms; + } + + inline bool isValid() const { + return mValid; + } + + /* Adds an AnomalyTracker and returns it. */ + virtual sp addAnomalyTracker(const Alert& alert, + const sp& anomalyAlarmMonitor, + const UpdateStatus& updateStatus, + const int64_t updateTimeNs) { + std::lock_guard lock(mMutex); + sp anomalyTracker = new AnomalyTracker(alert, mConfigKey); + mAnomalyTrackers.push_back(anomalyTracker); + return anomalyTracker; + } + + /* Adds an AnomalyTracker that has already been created */ + virtual void addAnomalyTracker(sp& anomalyTracker, const int64_t updateTimeNs) { + std::lock_guard lock(mMutex); + mAnomalyTrackers.push_back(anomalyTracker); + } + // End: getters/setters +protected: + /** + * Flushes the current bucket if the eventTime is after the current bucket's end time. + */ + virtual void flushIfNeededLocked(const int64_t& eventTime){}; + + /** + * For metrics that aggregate (ie, every metric producer except for EventMetricProducer), + * we need to be able to flush the current buckets on demand (ie, end the current bucket and + * start new bucket). If this function is called when eventTimeNs is greater than the current + * bucket's end timestamp, than we flush up to the end of the latest full bucket; otherwise, + * we assume that we want to flush a partial bucket. The bucket start timestamp and bucket + * number are not changed by this function. This method should only be called by + * flushIfNeededLocked or flushLocked or the app upgrade handler; the caller MUST update the + * bucket timestamp and bucket number as needed. + */ + virtual void flushCurrentBucketLocked(const int64_t& eventTimeNs, + const int64_t& nextBucketStartTimeNs) {}; + + /** + * Flushes all the data including the current partial bucket. + */ + virtual void flushLocked(const int64_t& eventTimeNs) { + flushIfNeededLocked(eventTimeNs); + flushCurrentBucketLocked(eventTimeNs, eventTimeNs); + }; + + /* + * Individual metrics can implement their own business logic here. All pre-processing is done. + * + * [matcherIndex]: the index of the matcher which matched this event. This is interesting to + * DurationMetric, because it has start/stop/stop_all 3 matchers. + * [eventKey]: the extracted dimension key for the final output. if the metric doesn't have + * dimensions, it will be DEFAULT_DIMENSION_KEY + * [conditionKey]: the keys of conditions which should be used to query the condition for this + * target event (from MetricConditionLink). This is passed to individual metrics + * because DurationMetric needs it to be cached. + * [condition]: whether condition is met. If condition is sliced, this is the result coming from + * query with ConditionWizard; If condition is not sliced, this is the + * nonSlicedCondition. + * [event]: the log event, just in case the metric needs its data, e.g., EventMetric. + */ + virtual void onMatchedLogEventInternalLocked( + const size_t matcherIndex, const MetricDimensionKey& eventKey, + const ConditionKey& conditionKey, bool condition, const LogEvent& event, + const map& statePrimaryKeys) = 0; + + // Consume the parsed stats log entry that already matched the "what" of the metric. + virtual void onMatchedLogEventLocked(const size_t matcherIndex, const LogEvent& event); + virtual void onConditionChangedLocked(const bool condition, const int64_t eventTime) = 0; + virtual void onSlicedConditionMayChangeLocked(bool overallCondition, + const int64_t eventTime) = 0; + virtual void onDumpReportLocked(const int64_t dumpTimeNs, + const bool include_current_partial_bucket, + const bool erase_data, + const DumpLatency dumpLatency, + std::set *str_set, + android::util::ProtoOutputStream* protoOutput) = 0; + virtual void clearPastBucketsLocked(const int64_t dumpTimeNs) = 0; + virtual void prepareFirstBucketLocked(){}; + virtual size_t byteSizeLocked() const = 0; + virtual void dumpStatesLocked(FILE* out, bool verbose) const = 0; + virtual void dropDataLocked(const int64_t dropTimeNs) = 0; + void loadActiveMetricLocked(const ActiveMetric& activeMetric, int64_t currentTimeNs); + void activateLocked(int activationTrackerIndex, int64_t elapsedTimestampNs); + void cancelEventActivationLocked(int deactivationTrackerIndex); + + bool evaluateActiveStateLocked(int64_t elapsedTimestampNs); + + virtual void onActiveStateChangedLocked(const int64_t& eventTimeNs) { + if (!mIsActive) { + flushLocked(eventTimeNs); + } + } + + inline bool isActiveLocked() const { + return mIsActive; + } + + // Convenience to compute the current bucket's end time, which is always aligned with the + // start time of the metric. + int64_t getCurrentBucketEndTimeNs() const { + return mTimeBaseNs + (mCurrentBucketNum + 1) * mBucketSizeNs; + } + + int64_t getBucketNumFromEndTimeNs(const int64_t endNs) { + return (endNs - mTimeBaseNs) / mBucketSizeNs - 1; + } + + // Query StateManager for original state value using the queryKey. + // The field and value are output. + void queryStateValue(const int32_t atomId, const HashableDimensionKey& queryKey, + FieldValue* value); + + // If a state map exists for the given atom, replace the original state + // value with the group id mapped to the value. + // If no state map exists, keep the original state value. + void mapStateValue(const int32_t atomId, FieldValue* value); + + // Returns a HashableDimensionKey with unknown state value for each state + // atom. + HashableDimensionKey getUnknownStateKey(); + + DropEvent buildDropEvent(const int64_t dropTimeNs, const BucketDropReason reason); + + // Returns true if the number of drop events in the current bucket has + // exceeded the maximum number allowed, which is currently capped at 10. + bool maxDropEventsReached(); + + const int64_t mMetricId; + + // Hash of the Metric's proto bytes from StatsdConfig, including any activations. + // Used to determine if the definition of this metric has changed across a config update. + const uint64_t mProtoHash; + + const ConfigKey mConfigKey; + + bool mValid; + + // The time when this metric producer was first created. The end time for the current bucket + // can be computed from this based on mCurrentBucketNum. + int64_t mTimeBaseNs; + + // Start time may not be aligned with the start of statsd if there is an app upgrade in the + // middle of a bucket. + int64_t mCurrentBucketStartTimeNs; + + // Used by anomaly detector to track which bucket we are in. This is not sent with the produced + // report. + int64_t mCurrentBucketNum; + + int64_t mBucketSizeNs; + + ConditionState mCondition; + + int mConditionTrackerIndex; + + bool mConditionSliced; + + sp mWizard; + + bool mContainANYPositionInDimensionsInWhat; + + bool mSliceByPositionALL; + + vector mDimensionsInWhat; // The dimensions_in_what defined in statsd_config + + // True iff the metric to condition links cover all dimension fields in the condition tracker. + // This field is always false for combinational condition trackers. + bool mHasLinksToAllConditionDimensionsInTracker; + + std::vector mMetric2ConditionLinks; + + std::vector> mAnomalyTrackers; + + mutable std::mutex mMutex; + + // When the metric producer has multiple activations, these activations are ORed to determine + // whether the metric producer is ready to generate metrics. + std::unordered_map> mEventActivationMap; + + // Maps index of atom matcher for deactivation to a list of Activation structs. + std::unordered_map>> mEventDeactivationMap; + + bool mIsActive; + + // The slice_by_state atom ids defined in statsd_config. + const std::vector mSlicedStateAtoms; + + // Maps atom ids and state values to group_ids (>). + const std::unordered_map> mStateGroupMap; + + // MetricStateLinks defined in statsd_config that link fields in the state + // atom to fields in the "what" atom. + std::vector mMetric2StateLinks; + + optional mUploadThreshold; + + SkippedBucket mCurrentSkippedBucket; + // Buckets that were invalidated and had their data dropped. + std::vector mSkippedBuckets; + + FRIEND_TEST(CountMetricE2eTest, TestSlicedState); + FRIEND_TEST(CountMetricE2eTest, TestSlicedStateWithMap); + FRIEND_TEST(CountMetricE2eTest, TestMultipleSlicedStates); + FRIEND_TEST(CountMetricE2eTest, TestSlicedStateWithPrimaryFields); + FRIEND_TEST(CountMetricE2eTest, TestInitialConditionChanges); + + FRIEND_TEST(DurationMetricE2eTest, TestOneBucket); + FRIEND_TEST(DurationMetricE2eTest, TestTwoBuckets); + FRIEND_TEST(DurationMetricE2eTest, TestWithActivation); + FRIEND_TEST(DurationMetricE2eTest, TestWithCondition); + FRIEND_TEST(DurationMetricE2eTest, TestWithSlicedCondition); + FRIEND_TEST(DurationMetricE2eTest, TestWithActivationAndSlicedCondition); + FRIEND_TEST(DurationMetricE2eTest, TestWithSlicedState); + FRIEND_TEST(DurationMetricE2eTest, TestWithConditionAndSlicedState); + FRIEND_TEST(DurationMetricE2eTest, TestWithSlicedStateMapped); + FRIEND_TEST(DurationMetricE2eTest, TestSlicedStatePrimaryFieldsNotSubsetDimInWhat); + FRIEND_TEST(DurationMetricE2eTest, TestWithSlicedStatePrimaryFieldsSubset); + FRIEND_TEST(DurationMetricE2eTest, TestUploadThreshold); + + FRIEND_TEST(MetricActivationE2eTest, TestCountMetric); + FRIEND_TEST(MetricActivationE2eTest, TestCountMetricWithOneDeactivation); + FRIEND_TEST(MetricActivationE2eTest, TestCountMetricWithTwoDeactivations); + FRIEND_TEST(MetricActivationE2eTest, TestCountMetricWithSameDeactivation); + FRIEND_TEST(MetricActivationE2eTest, TestCountMetricWithTwoMetricsTwoDeactivations); + + FRIEND_TEST(StatsLogProcessorTest, TestActiveConfigMetricDiskWriteRead); + FRIEND_TEST(StatsLogProcessorTest, TestActivationOnBoot); + FRIEND_TEST(StatsLogProcessorTest, TestActivationOnBootMultipleActivations); + FRIEND_TEST(StatsLogProcessorTest, + TestActivationOnBootMultipleActivationsDifferentActivationTypes); + FRIEND_TEST(StatsLogProcessorTest, TestActivationsPersistAcrossSystemServerRestart); + + FRIEND_TEST(ValueMetricE2eTest, TestInitWithSlicedState); + FRIEND_TEST(ValueMetricE2eTest, TestInitWithSlicedState_WithDimensions); + FRIEND_TEST(ValueMetricE2eTest, TestInitWithSlicedState_WithIncorrectDimensions); + FRIEND_TEST(ValueMetricE2eTest, TestInitialConditionChanges); + + FRIEND_TEST(MetricsManagerTest, TestInitialConditions); + + FRIEND_TEST(ConfigUpdateTest, TestUpdateMetricActivations); + FRIEND_TEST(ConfigUpdateTest, TestUpdateCountMetrics); + FRIEND_TEST(ConfigUpdateTest, TestUpdateEventMetrics); + FRIEND_TEST(ConfigUpdateTest, TestUpdateGaugeMetrics); + FRIEND_TEST(ConfigUpdateTest, TestUpdateDurationMetrics); + FRIEND_TEST(ConfigUpdateTest, TestUpdateMetricsMultipleTypes); + FRIEND_TEST(ConfigUpdateTest, TestUpdateAlerts); +}; + +} // namespace statsd +} // namespace os +} // namespace android +#endif // METRIC_PRODUCER_H diff --git a/statsd/src/metrics/MetricsManager.cpp b/statsd/src/metrics/MetricsManager.cpp new file mode 100644 index 00000000..f9b0a103 --- /dev/null +++ b/statsd/src/metrics/MetricsManager.cpp @@ -0,0 +1,773 @@ +/* + * 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. + */ +#define DEBUG false // STOPSHIP if true +#include "Log.h" + +#include "MetricsManager.h" + +#include + +#include "CountMetricProducer.h" +#include "condition/CombinationConditionTracker.h" +#include "condition/SimpleConditionTracker.h" +#include "guardrail/StatsdStats.h" +#include "matchers/CombinationAtomMatchingTracker.h" +#include "matchers/SimpleAtomMatchingTracker.h" +#include "parsing_utils/config_update_utils.h" +#include "parsing_utils/metrics_manager_util.h" +#include "state/StateManager.h" +#include "stats_log_util.h" +#include "stats_util.h" +#include "statslog_statsd.h" + +using android::util::FIELD_COUNT_REPEATED; +using android::util::FIELD_TYPE_INT32; +using android::util::FIELD_TYPE_INT64; +using android::util::FIELD_TYPE_MESSAGE; +using android::util::FIELD_TYPE_STRING; +using android::util::ProtoOutputStream; + +using std::set; +using std::string; +using std::vector; + +namespace android { +namespace os { +namespace statsd { + +const int FIELD_ID_METRICS = 1; +const int FIELD_ID_ANNOTATIONS = 7; +const int FIELD_ID_ANNOTATIONS_INT64 = 1; +const int FIELD_ID_ANNOTATIONS_INT32 = 2; + +// for ActiveConfig +const int FIELD_ID_ACTIVE_CONFIG_ID = 1; +const int FIELD_ID_ACTIVE_CONFIG_UID = 2; +const int FIELD_ID_ACTIVE_CONFIG_METRIC = 3; + +MetricsManager::MetricsManager(const ConfigKey& key, const StatsdConfig& config, + const int64_t timeBaseNs, const int64_t currentTimeNs, + const sp& uidMap, + const sp& pullerManager, + const sp& anomalyAlarmMonitor, + const sp& periodicAlarmMonitor) + : mConfigKey(key), + mUidMap(uidMap), + mTtlNs(config.has_ttl_in_seconds() ? config.ttl_in_seconds() * NS_PER_SEC : -1), + mTtlEndNs(-1), + mLastReportTimeNs(currentTimeNs), + mLastReportWallClockNs(getWallClockNs()), + mPullerManager(pullerManager), + mWhitelistedAtomIds(config.whitelisted_atom_ids().begin(), + config.whitelisted_atom_ids().end()), + mShouldPersistHistory(config.persist_locally()) { + // Init the ttl end timestamp. + refreshTtl(timeBaseNs); + + mConfigValid = initStatsdConfig( + key, config, uidMap, pullerManager, anomalyAlarmMonitor, periodicAlarmMonitor, + timeBaseNs, currentTimeNs, mTagIds, mAllAtomMatchingTrackers, mAtomMatchingTrackerMap, + mAllConditionTrackers, mConditionTrackerMap, mAllMetricProducers, mMetricProducerMap, + mAllAnomalyTrackers, mAllPeriodicAlarmTrackers, mConditionToMetricMap, + mTrackerToMetricMap, mTrackerToConditionMap, mActivationAtomTrackerToMetricMap, + mDeactivationAtomTrackerToMetricMap, mAlertTrackerMap, mMetricIndexesWithActivation, + mStateProtoHashes, mNoReportMetricIds); + + mHashStringsInReport = config.hash_strings_in_metric_report(); + mVersionStringsInReport = config.version_strings_in_metric_report(); + mInstallerInReport = config.installer_in_metric_report(); + + createAllLogSourcesFromConfig(config); + mPullerManager->RegisterPullUidProvider(mConfigKey, this); + + // Store the sub-configs used. + for (const auto& annotation : config.annotation()) { + mAnnotations.emplace_back(annotation.field_int64(), annotation.field_int32()); + } + verifyGuardrailsAndUpdateStatsdStats(); + initializeConfigActiveStatus(); +} + +MetricsManager::~MetricsManager() { + for (auto it : mAllMetricProducers) { + for (int atomId : it->getSlicedStateAtoms()) { + StateManager::getInstance().unregisterListener(atomId, it); + } + } + mPullerManager->UnregisterPullUidProvider(mConfigKey, this); + + VLOG("~MetricsManager()"); +} + +bool MetricsManager::updateConfig(const StatsdConfig& config, const int64_t timeBaseNs, + const int64_t currentTimeNs, + const sp& anomalyAlarmMonitor, + const sp& periodicAlarmMonitor) { + vector> newAtomMatchingTrackers; + unordered_map newAtomMatchingTrackerMap; + vector> newConditionTrackers; + unordered_map newConditionTrackerMap; + map newStateProtoHashes; + vector> newMetricProducers; + unordered_map newMetricProducerMap; + vector> newAnomalyTrackers; + unordered_map newAlertTrackerMap; + vector> newPeriodicAlarmTrackers; + mTagIds.clear(); + mConditionToMetricMap.clear(); + mTrackerToMetricMap.clear(); + mTrackerToConditionMap.clear(); + mActivationAtomTrackerToMetricMap.clear(); + mDeactivationAtomTrackerToMetricMap.clear(); + mMetricIndexesWithActivation.clear(); + mNoReportMetricIds.clear(); + mConfigValid = updateStatsdConfig( + mConfigKey, config, mUidMap, mPullerManager, anomalyAlarmMonitor, periodicAlarmMonitor, + timeBaseNs, currentTimeNs, mAllAtomMatchingTrackers, mAtomMatchingTrackerMap, + mAllConditionTrackers, mConditionTrackerMap, mAllMetricProducers, mMetricProducerMap, + mAllAnomalyTrackers, mAlertTrackerMap, mStateProtoHashes, mTagIds, + newAtomMatchingTrackers, newAtomMatchingTrackerMap, newConditionTrackers, + newConditionTrackerMap, newMetricProducers, newMetricProducerMap, newAnomalyTrackers, + newAlertTrackerMap, newPeriodicAlarmTrackers, mConditionToMetricMap, + mTrackerToMetricMap, mTrackerToConditionMap, mActivationAtomTrackerToMetricMap, + mDeactivationAtomTrackerToMetricMap, mMetricIndexesWithActivation, newStateProtoHashes, + mNoReportMetricIds); + mAllAtomMatchingTrackers = newAtomMatchingTrackers; + mAtomMatchingTrackerMap = newAtomMatchingTrackerMap; + mAllConditionTrackers = newConditionTrackers; + mConditionTrackerMap = newConditionTrackerMap; + mAllMetricProducers = newMetricProducers; + mMetricProducerMap = newMetricProducerMap; + mStateProtoHashes = newStateProtoHashes; + mAllAnomalyTrackers = newAnomalyTrackers; + mAlertTrackerMap = newAlertTrackerMap; + mAllPeriodicAlarmTrackers = newPeriodicAlarmTrackers; + + mTtlNs = config.has_ttl_in_seconds() ? config.ttl_in_seconds() * NS_PER_SEC : -1; + refreshTtl(currentTimeNs); + + mHashStringsInReport = config.hash_strings_in_metric_report(); + mVersionStringsInReport = config.version_strings_in_metric_report(); + mInstallerInReport = config.installer_in_metric_report(); + mWhitelistedAtomIds.clear(); + mWhitelistedAtomIds.insert(config.whitelisted_atom_ids().begin(), + config.whitelisted_atom_ids().end()); + mShouldPersistHistory = config.persist_locally(); + + // Store the sub-configs used. + mAnnotations.clear(); + for (const auto& annotation : config.annotation()) { + mAnnotations.emplace_back(annotation.field_int64(), annotation.field_int32()); + } + + mAllowedUid.clear(); + mAllowedPkg.clear(); + mDefaultPullUids.clear(); + mPullAtomUids.clear(); + mPullAtomPackages.clear(); + createAllLogSourcesFromConfig(config); + + verifyGuardrailsAndUpdateStatsdStats(); + initializeConfigActiveStatus(); + return mConfigValid; +} + +void MetricsManager::createAllLogSourcesFromConfig(const StatsdConfig& config) { + // Init allowed pushed atom uids. + if (config.allowed_log_source_size() == 0) { + mConfigValid = false; + ALOGE("Log source allowlist is empty! This config won't get any data. Suggest adding at " + "least AID_SYSTEM and AID_STATSD to the allowed_log_source field."); + } else { + for (const auto& source : config.allowed_log_source()) { + auto it = UidMap::sAidToUidMapping.find(source); + if (it != UidMap::sAidToUidMapping.end()) { + mAllowedUid.push_back(it->second); + } else { + mAllowedPkg.push_back(source); + } + } + + if (mAllowedUid.size() + mAllowedPkg.size() > StatsdStats::kMaxLogSourceCount) { + ALOGE("Too many log sources. This is likely to be an error in the config."); + mConfigValid = false; + } else { + initAllowedLogSources(); + } + } + + // Init default allowed pull atom uids. + int numPullPackages = 0; + for (const string& pullSource : config.default_pull_packages()) { + auto it = UidMap::sAidToUidMapping.find(pullSource); + if (it != UidMap::sAidToUidMapping.end()) { + numPullPackages++; + mDefaultPullUids.insert(it->second); + } else { + ALOGE("Default pull atom packages must be in sAidToUidMapping"); + mConfigValid = false; + } + } + // Init per-atom pull atom packages. + for (const PullAtomPackages& pullAtomPackages : config.pull_atom_packages()) { + int32_t atomId = pullAtomPackages.atom_id(); + for (const string& pullPackage : pullAtomPackages.packages()) { + numPullPackages++; + auto it = UidMap::sAidToUidMapping.find(pullPackage); + if (it != UidMap::sAidToUidMapping.end()) { + mPullAtomUids[atomId].insert(it->second); + } else { + mPullAtomPackages[atomId].insert(pullPackage); + } + } + } + if (numPullPackages > StatsdStats::kMaxPullAtomPackages) { + ALOGE("Too many sources in default_pull_packages and pull_atom_packages. This is likely to " + "be an error in the config"); + mConfigValid = false; + } else { + initPullAtomSources(); + } +} + +void MetricsManager::verifyGuardrailsAndUpdateStatsdStats() { + // Guardrail. Reject the config if it's too big. + if (mAllMetricProducers.size() > StatsdStats::kMaxMetricCountPerConfig || + mAllConditionTrackers.size() > StatsdStats::kMaxConditionCountPerConfig || + mAllAtomMatchingTrackers.size() > StatsdStats::kMaxMatcherCountPerConfig) { + ALOGE("This config is too big! Reject!"); + mConfigValid = false; + } + if (mAllAnomalyTrackers.size() > StatsdStats::kMaxAlertCountPerConfig) { + ALOGE("This config has too many alerts! Reject!"); + mConfigValid = false; + } + // no matter whether this config is valid, log it in the stats. + StatsdStats::getInstance().noteConfigReceived( + mConfigKey, mAllMetricProducers.size(), mAllConditionTrackers.size(), + mAllAtomMatchingTrackers.size(), mAllAnomalyTrackers.size(), mAnnotations, + mConfigValid); +} + +void MetricsManager::initializeConfigActiveStatus() { + mIsAlwaysActive = (mMetricIndexesWithActivation.size() != mAllMetricProducers.size()) || + (mAllMetricProducers.size() == 0); + mIsActive = mIsAlwaysActive; + for (int metric : mMetricIndexesWithActivation) { + mIsActive |= mAllMetricProducers[metric]->isActive(); + } + VLOG("mIsActive is initialized to %d", mIsActive); +} + +void MetricsManager::initAllowedLogSources() { + std::lock_guard lock(mAllowedLogSourcesMutex); + mAllowedLogSources.clear(); + mAllowedLogSources.insert(mAllowedUid.begin(), mAllowedUid.end()); + + for (const auto& pkg : mAllowedPkg) { + auto uids = mUidMap->getAppUid(pkg); + mAllowedLogSources.insert(uids.begin(), uids.end()); + } + if (DEBUG) { + for (const auto& uid : mAllowedLogSources) { + VLOG("Allowed uid %d", uid); + } + } +} + +void MetricsManager::initPullAtomSources() { + std::lock_guard lock(mAllowedLogSourcesMutex); + mCombinedPullAtomUids.clear(); + for (const auto& [atomId, uids] : mPullAtomUids) { + mCombinedPullAtomUids[atomId].insert(uids.begin(), uids.end()); + } + for (const auto& [atomId, packages] : mPullAtomPackages) { + for (const string& pkg : packages) { + set uids = mUidMap->getAppUid(pkg); + mCombinedPullAtomUids[atomId].insert(uids.begin(), uids.end()); + } + } +} + +bool MetricsManager::isConfigValid() const { + return mConfigValid; +} + +void MetricsManager::notifyAppUpgrade(const int64_t& eventTimeNs, const string& apk, const int uid, + const int64_t version) { + // Inform all metric producers. + for (const auto& it : mAllMetricProducers) { + it->notifyAppUpgrade(eventTimeNs); + } + // check if we care this package + if (std::find(mAllowedPkg.begin(), mAllowedPkg.end(), apk) != mAllowedPkg.end()) { + // We will re-initialize the whole list because we don't want to keep the multi mapping of + // UID<->pkg inside MetricsManager to reduce the memory usage. + initAllowedLogSources(); + } + + for (const auto& it : mPullAtomPackages) { + if (it.second.find(apk) != it.second.end()) { + initPullAtomSources(); + return; + } + } +} + +void MetricsManager::notifyAppRemoved(const int64_t& eventTimeNs, const string& apk, + const int uid) { + // Inform all metric producers. + for (const auto& it : mAllMetricProducers) { + it->notifyAppRemoved(eventTimeNs); + } + // check if we care this package + if (std::find(mAllowedPkg.begin(), mAllowedPkg.end(), apk) != mAllowedPkg.end()) { + // We will re-initialize the whole list because we don't want to keep the multi mapping of + // UID<->pkg inside MetricsManager to reduce the memory usage. + initAllowedLogSources(); + } + + for (const auto& it : mPullAtomPackages) { + if (it.second.find(apk) != it.second.end()) { + initPullAtomSources(); + return; + } + } +} + +void MetricsManager::onUidMapReceived(const int64_t& eventTimeNs) { + // Purposefully don't inform metric producers on a new snapshot + // because we don't need to flush partial buckets. + // This occurs if a new user is added/removed or statsd crashes. + initPullAtomSources(); + + if (mAllowedPkg.size() == 0) { + return; + } + initAllowedLogSources(); +} + +void MetricsManager::onStatsdInitCompleted(const int64_t& eventTimeNs) { + // Inform all metric producers. + for (const auto& it : mAllMetricProducers) { + it->onStatsdInitCompleted(eventTimeNs); + } +} + +void MetricsManager::init() { + for (const auto& producer : mAllMetricProducers) { + producer->prepareFirstBucket(); + } +} + +vector MetricsManager::getPullAtomUids(int32_t atomId) { + std::lock_guard lock(mAllowedLogSourcesMutex); + vector uids; + const auto& it = mCombinedPullAtomUids.find(atomId); + if (it != mCombinedPullAtomUids.end()) { + uids.insert(uids.end(), it->second.begin(), it->second.end()); + } + uids.insert(uids.end(), mDefaultPullUids.begin(), mDefaultPullUids.end()); + return uids; +} + +void MetricsManager::dumpStates(FILE* out, bool verbose) { + fprintf(out, "ConfigKey %s, allowed source:", mConfigKey.ToString().c_str()); + { + std::lock_guard lock(mAllowedLogSourcesMutex); + for (const auto& source : mAllowedLogSources) { + fprintf(out, "%d ", source); + } + } + fprintf(out, "\n"); + for (const auto& producer : mAllMetricProducers) { + producer->dumpStates(out, verbose); + } +} + +void MetricsManager::dropData(const int64_t dropTimeNs) { + for (const auto& producer : mAllMetricProducers) { + producer->dropData(dropTimeNs); + } +} + +void MetricsManager::onDumpReport(const int64_t dumpTimeStampNs, + const bool include_current_partial_bucket, + const bool erase_data, + const DumpLatency dumpLatency, + std::set *str_set, + ProtoOutputStream* protoOutput) { + VLOG("=========================Metric Reports Start=========================="); + // one StatsLogReport per MetricProduer + for (const auto& producer : mAllMetricProducers) { + if (mNoReportMetricIds.find(producer->getMetricId()) == mNoReportMetricIds.end()) { + uint64_t token = protoOutput->start( + FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_METRICS); + if (mHashStringsInReport) { + producer->onDumpReport(dumpTimeStampNs, include_current_partial_bucket, erase_data, + dumpLatency, str_set, protoOutput); + } else { + producer->onDumpReport(dumpTimeStampNs, include_current_partial_bucket, erase_data, + dumpLatency, nullptr, protoOutput); + } + protoOutput->end(token); + } else { + producer->clearPastBuckets(dumpTimeStampNs); + } + } + for (const auto& annotation : mAnnotations) { + uint64_t token = protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | + FIELD_ID_ANNOTATIONS); + protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_ANNOTATIONS_INT64, + (long long)annotation.first); + protoOutput->write(FIELD_TYPE_INT32 | FIELD_ID_ANNOTATIONS_INT32, annotation.second); + protoOutput->end(token); + } + + // Do not update the timestamps when data is not cleared to avoid timestamps from being + // misaligned. + if (erase_data) { + mLastReportTimeNs = dumpTimeStampNs; + mLastReportWallClockNs = getWallClockNs(); + } + VLOG("=========================Metric Reports End=========================="); +} + + +bool MetricsManager::checkLogCredentials(const LogEvent& event) { + if (mWhitelistedAtomIds.find(event.GetTagId()) != mWhitelistedAtomIds.end()) { + return true; + } + std::lock_guard lock(mAllowedLogSourcesMutex); + if (mAllowedLogSources.find(event.GetUid()) == mAllowedLogSources.end()) { + VLOG("log source %d not on the whitelist", event.GetUid()); + return false; + } + return true; +} + +bool MetricsManager::eventSanityCheck(const LogEvent& event) { + if (event.GetTagId() == util::APP_BREADCRUMB_REPORTED) { + // Check that app breadcrumb reported fields are valid. + status_t err = NO_ERROR; + + // Uid is 3rd from last field and must match the caller's uid, + // unless that caller is statsd itself (statsd is allowed to spoof uids). + long appHookUid = event.GetLong(event.size()-2, &err); + if (err != NO_ERROR) { + VLOG("APP_BREADCRUMB_REPORTED had error when parsing the uid"); + return false; + } + + // Because the uid within the LogEvent may have been mapped from + // isolated to host, map the loggerUid similarly before comparing. + int32_t loggerUid = mUidMap->getHostUidOrSelf(event.GetUid()); + if (loggerUid != appHookUid && loggerUid != AID_STATSD) { + VLOG("APP_BREADCRUMB_REPORTED has invalid uid: claimed %ld but caller is %d", + appHookUid, loggerUid); + return false; + } + + // The state must be from 0,3. This part of code must be manually updated. + long appHookState = event.GetLong(event.size(), &err); + if (err != NO_ERROR) { + VLOG("APP_BREADCRUMB_REPORTED had error when parsing the state field"); + return false; + } else if (appHookState < 0 || appHookState > 3) { + VLOG("APP_BREADCRUMB_REPORTED does not have valid state %ld", appHookState); + return false; + } + } else if (event.GetTagId() == util::DAVEY_OCCURRED) { + // Daveys can be logged from any app since they are logged in libs/hwui/JankTracker.cpp. + // Check that the davey duration is reasonable. Max length check is for privacy. + status_t err = NO_ERROR; + + // Uid is the first field provided. + long jankUid = event.GetLong(1, &err); + if (err != NO_ERROR) { + VLOG("Davey occurred had error when parsing the uid"); + return false; + } + int32_t loggerUid = event.GetUid(); + if (loggerUid != jankUid && loggerUid != AID_STATSD) { + VLOG("DAVEY_OCCURRED has invalid uid: claimed %ld but caller is %d", jankUid, + loggerUid); + return false; + } + + long duration = event.GetLong(event.size(), &err); + if (err != NO_ERROR) { + VLOG("Davey occurred had error when parsing the duration"); + return false; + } else if (duration > 100000) { + VLOG("Davey duration is unreasonably long: %ld", duration); + return false; + } + } + + return true; +} + +// Consume the stats log if it's interesting to this metric. +void MetricsManager::onLogEvent(const LogEvent& event) { + if (!mConfigValid) { + return; + } + + if (!checkLogCredentials(event)) { + return; + } + + if (!eventSanityCheck(event)) { + return; + } + + int tagId = event.GetTagId(); + int64_t eventTimeNs = event.GetElapsedTimestampNs(); + + bool isActive = mIsAlwaysActive; + + // Set of metrics that are still active after flushing. + unordered_set activeMetricsIndices; + + // Update state of all metrics w/ activation conditions as of eventTimeNs. + for (int metricIndex : mMetricIndexesWithActivation) { + const sp& metric = mAllMetricProducers[metricIndex]; + metric->flushIfExpire(eventTimeNs); + if (metric->isActive()) { + // If this metric w/ activation condition is still active after + // flushing, remember it. + activeMetricsIndices.insert(metricIndex); + } + } + + mIsActive = isActive || !activeMetricsIndices.empty(); + + if (mTagIds.find(tagId) == mTagIds.end()) { + // Not interesting... + return; + } + + vector matcherCache(mAllAtomMatchingTrackers.size(), + MatchingState::kNotComputed); + + // Evaluate all atom matchers. + for (auto& matcher : mAllAtomMatchingTrackers) { + matcher->onLogEvent(event, mAllAtomMatchingTrackers, matcherCache); + } + + // Set of metrics that received an activation cancellation. + unordered_set metricIndicesWithCanceledActivations; + + // Determine which metric activations received a cancellation and cancel them. + for (const auto& it : mDeactivationAtomTrackerToMetricMap) { + if (matcherCache[it.first] == MatchingState::kMatched) { + for (int metricIndex : it.second) { + mAllMetricProducers[metricIndex]->cancelEventActivation(it.first); + metricIndicesWithCanceledActivations.insert(metricIndex); + } + } + } + + // Determine whether any metrics are no longer active after cancelling metric activations. + for (const int metricIndex : metricIndicesWithCanceledActivations) { + const sp& metric = mAllMetricProducers[metricIndex]; + metric->flushIfExpire(eventTimeNs); + if (!metric->isActive()) { + activeMetricsIndices.erase(metricIndex); + } + } + + isActive |= !activeMetricsIndices.empty(); + + + // Determine which metric activations should be turned on and turn them on + for (const auto& it : mActivationAtomTrackerToMetricMap) { + if (matcherCache[it.first] == MatchingState::kMatched) { + for (int metricIndex : it.second) { + mAllMetricProducers[metricIndex]->activate(it.first, eventTimeNs); + isActive |= mAllMetricProducers[metricIndex]->isActive(); + } + } + } + + mIsActive = isActive; + + // A bitmap to see which ConditionTracker needs to be re-evaluated. + vector conditionToBeEvaluated(mAllConditionTrackers.size(), false); + + for (const auto& pair : mTrackerToConditionMap) { + if (matcherCache[pair.first] == MatchingState::kMatched) { + const auto& conditionList = pair.second; + for (const int conditionIndex : conditionList) { + conditionToBeEvaluated[conditionIndex] = true; + } + } + } + + vector conditionCache(mAllConditionTrackers.size(), + ConditionState::kNotEvaluated); + // A bitmap to track if a condition has changed value. + vector changedCache(mAllConditionTrackers.size(), false); + for (size_t i = 0; i < mAllConditionTrackers.size(); i++) { + if (conditionToBeEvaluated[i] == false) { + continue; + } + sp& condition = mAllConditionTrackers[i]; + condition->evaluateCondition(event, matcherCache, mAllConditionTrackers, conditionCache, + changedCache); + } + + for (size_t i = 0; i < mAllConditionTrackers.size(); i++) { + if (changedCache[i] == false) { + continue; + } + auto pair = mConditionToMetricMap.find(i); + if (pair != mConditionToMetricMap.end()) { + auto& metricList = pair->second; + for (auto metricIndex : metricList) { + // Metric cares about non sliced condition, and it's changed. + // Push the new condition to it directly. + if (!mAllMetricProducers[metricIndex]->isConditionSliced()) { + mAllMetricProducers[metricIndex]->onConditionChanged(conditionCache[i], + eventTimeNs); + // Metric cares about sliced conditions, and it may have changed. Send + // notification, and the metric can query the sliced conditions that are + // interesting to it. + } else { + mAllMetricProducers[metricIndex]->onSlicedConditionMayChange(conditionCache[i], + eventTimeNs); + } + } + } + } + + // For matched AtomMatchers, tell relevant metrics that a matched event has come. + for (size_t i = 0; i < mAllAtomMatchingTrackers.size(); i++) { + if (matcherCache[i] == MatchingState::kMatched) { + StatsdStats::getInstance().noteMatcherMatched(mConfigKey, + mAllAtomMatchingTrackers[i]->getId()); + auto pair = mTrackerToMetricMap.find(i); + if (pair != mTrackerToMetricMap.end()) { + auto& metricList = pair->second; + for (const int metricIndex : metricList) { + // pushed metrics are never scheduled pulls + mAllMetricProducers[metricIndex]->onMatchedLogEvent(i, event); + } + } + } + } +} + +void MetricsManager::onAnomalyAlarmFired( + const int64_t& timestampNs, + unordered_set, SpHash>& alarmSet) { + for (const auto& itr : mAllAnomalyTrackers) { + itr->informAlarmsFired(timestampNs, alarmSet); + } +} + +void MetricsManager::onPeriodicAlarmFired( + const int64_t& timestampNs, + unordered_set, SpHash>& alarmSet) { + for (const auto& itr : mAllPeriodicAlarmTrackers) { + itr->informAlarmsFired(timestampNs, alarmSet); + } +} + +// Returns the total byte size of all metrics managed by a single config source. +size_t MetricsManager::byteSize() { + size_t totalSize = 0; + for (const auto& metricProducer : mAllMetricProducers) { + totalSize += metricProducer->byteSize(); + } + return totalSize; +} + +void MetricsManager::loadActiveConfig(const ActiveConfig& config, int64_t currentTimeNs) { + if (config.metric_size() == 0) { + ALOGW("No active metric for config %s", mConfigKey.ToString().c_str()); + return; + } + + for (int i = 0; i < config.metric_size(); i++) { + const auto& activeMetric = config.metric(i); + for (int metricIndex : mMetricIndexesWithActivation) { + const auto& metric = mAllMetricProducers[metricIndex]; + if (metric->getMetricId() == activeMetric.id()) { + VLOG("Setting active metric: %lld", (long long)metric->getMetricId()); + metric->loadActiveMetric(activeMetric, currentTimeNs); + if (!mIsActive && metric->isActive()) { + StatsdStats::getInstance().noteActiveStatusChanged(mConfigKey, + /*activate=*/ true); + } + mIsActive |= metric->isActive(); + } + } + } +} + +void MetricsManager::writeActiveConfigToProtoOutputStream( + int64_t currentTimeNs, const DumpReportReason reason, ProtoOutputStream* proto) { + proto->write(FIELD_TYPE_INT64 | FIELD_ID_ACTIVE_CONFIG_ID, (long long)mConfigKey.GetId()); + proto->write(FIELD_TYPE_INT32 | FIELD_ID_ACTIVE_CONFIG_UID, mConfigKey.GetUid()); + for (int metricIndex : mMetricIndexesWithActivation) { + const auto& metric = mAllMetricProducers[metricIndex]; + const uint64_t metricToken = proto->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | + FIELD_ID_ACTIVE_CONFIG_METRIC); + metric->writeActiveMetricToProtoOutputStream(currentTimeNs, reason, proto); + proto->end(metricToken); + } +} + +bool MetricsManager::writeMetadataToProto(int64_t currentWallClockTimeNs, + int64_t systemElapsedTimeNs, + metadata::StatsMetadata* statsMetadata) { + bool metadataWritten = false; + metadata::ConfigKey* configKey = statsMetadata->mutable_config_key(); + configKey->set_config_id(mConfigKey.GetId()); + configKey->set_uid(mConfigKey.GetUid()); + for (const auto& anomalyTracker : mAllAnomalyTrackers) { + metadata::AlertMetadata* alertMetadata = statsMetadata->add_alert_metadata(); + bool alertWritten = anomalyTracker->writeAlertMetadataToProto(currentWallClockTimeNs, + systemElapsedTimeNs, alertMetadata); + if (!alertWritten) { + statsMetadata->mutable_alert_metadata()->RemoveLast(); + } + metadataWritten |= alertWritten; + } + return metadataWritten; +} + +void MetricsManager::loadMetadata(const metadata::StatsMetadata& metadata, + int64_t currentWallClockTimeNs, + int64_t systemElapsedTimeNs) { + for (const metadata::AlertMetadata& alertMetadata : metadata.alert_metadata()) { + int64_t alertId = alertMetadata.alert_id(); + auto it = mAlertTrackerMap.find(alertId); + if (it == mAlertTrackerMap.end()) { + ALOGE("No anomalyTracker found for alertId %lld", (long long) alertId); + continue; + } + mAllAnomalyTrackers[it->second]->loadAlertMetadata(alertMetadata, + currentWallClockTimeNs, + systemElapsedTimeNs); + } +} + +} // namespace statsd +} // namespace os +} // namespace android diff --git a/statsd/src/metrics/MetricsManager.h b/statsd/src/metrics/MetricsManager.h new file mode 100644 index 00000000..b9e8f662 --- /dev/null +++ b/statsd/src/metrics/MetricsManager.h @@ -0,0 +1,385 @@ +/* + * 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. + */ + +#pragma once + +#include "anomaly/AlarmMonitor.h" +#include "anomaly/AlarmTracker.h" +#include "anomaly/AnomalyTracker.h" +#include "condition/ConditionTracker.h" +#include "config/ConfigKey.h" +#include "external/StatsPullerManager.h" +#include "src/statsd_config.pb.h" +#include "src/statsd_metadata.pb.h" +#include "logd/LogEvent.h" +#include "matchers/AtomMatchingTracker.h" +#include "metrics/MetricProducer.h" +#include "packages/UidMap.h" + +#include + +namespace android { +namespace os { +namespace statsd { + +// A MetricsManager is responsible for managing metrics from one single config source. +class MetricsManager : public virtual android::RefBase, public virtual PullUidProvider { +public: + MetricsManager(const ConfigKey& configKey, const StatsdConfig& config, const int64_t timeBaseNs, + const int64_t currentTimeNs, const sp& uidMap, + const sp& pullerManager, + const sp& anomalyAlarmMonitor, + const sp& periodicAlarmMonitor); + + virtual ~MetricsManager(); + + bool updateConfig(const StatsdConfig& config, const int64_t timeBaseNs, + const int64_t currentTimeNs, const sp& anomalyAlarmMonitor, + const sp& periodicAlarmMonitor); + + // Return whether the configuration is valid. + bool isConfigValid() const; + + bool checkLogCredentials(const LogEvent& event); + + bool eventSanityCheck(const LogEvent& event); + + void onLogEvent(const LogEvent& event); + + void onAnomalyAlarmFired( + const int64_t& timestampNs, + unordered_set, SpHash>& alarmSet); + + void onPeriodicAlarmFired( + const int64_t& timestampNs, + unordered_set, SpHash>& alarmSet); + + void notifyAppUpgrade(const int64_t& eventTimeNs, const string& apk, const int uid, + const int64_t version); + + void notifyAppRemoved(const int64_t& eventTimeNs, const string& apk, const int uid); + + void onUidMapReceived(const int64_t& eventTimeNs); + + void onStatsdInitCompleted(const int64_t& elapsedTimeNs); + + void init(); + + vector getPullAtomUids(int32_t atomId) override; + + bool shouldWriteToDisk() const { + return mNoReportMetricIds.size() != mAllMetricProducers.size(); + } + + bool shouldPersistLocalHistory() const { + return mShouldPersistHistory; + } + + void dumpStates(FILE* out, bool verbose); + + inline bool isInTtl(const int64_t timestampNs) const { + return mTtlNs <= 0 || timestampNs < mTtlEndNs; + }; + + inline bool hashStringInReport() const { + return mHashStringsInReport; + }; + + inline bool versionStringsInReport() const { + return mVersionStringsInReport; + }; + + inline bool installerInReport() const { + return mInstallerInReport; + }; + + void refreshTtl(const int64_t currentTimestampNs) { + if (mTtlNs > 0) { + mTtlEndNs = currentTimestampNs + mTtlNs; + } + }; + + // Returns the elapsed realtime when this metric manager last reported metrics. If this config + // has not yet dumped any reports, this is the time the metricsmanager was initialized. + inline int64_t getLastReportTimeNs() const { + return mLastReportTimeNs; + }; + + inline int64_t getLastReportWallClockNs() const { + return mLastReportWallClockNs; + }; + + inline size_t getNumMetrics() const { + return mAllMetricProducers.size(); + } + + virtual void dropData(const int64_t dropTimeNs); + + virtual void onDumpReport(const int64_t dumpTimeNs, + const bool include_current_partial_bucket, + const bool erase_data, + const DumpLatency dumpLatency, + std::set *str_set, + android::util::ProtoOutputStream* protoOutput); + + // Computes the total byte size of all metrics managed by a single config source. + // Does not change the state. + virtual size_t byteSize(); + + // Returns whether or not this config is active. + // The config is active if any metric in the config is active. + inline bool isActive() const { + return mIsActive; + } + + void loadActiveConfig(const ActiveConfig& config, int64_t currentTimeNs); + + void writeActiveConfigToProtoOutputStream( + int64_t currentTimeNs, const DumpReportReason reason, ProtoOutputStream* proto); + + // Returns true if at least one piece of metadata is written. + bool writeMetadataToProto(int64_t currentWallClockTimeNs, + int64_t systemElapsedTimeNs, + metadata::StatsMetadata* statsMetadata); + + void loadMetadata(const metadata::StatsMetadata& metadata, + int64_t currentWallClockTimeNs, + int64_t systemElapsedTimeNs); +private: + // For test only. + inline int64_t getTtlEndNs() const { return mTtlEndNs; } + + const ConfigKey mConfigKey; + + sp mUidMap; + + bool mConfigValid = false; + + bool mHashStringsInReport = false; + bool mVersionStringsInReport = false; + bool mInstallerInReport = false; + + int64_t mTtlNs; + int64_t mTtlEndNs; + + int64_t mLastReportTimeNs; + int64_t mLastReportWallClockNs; + + sp mPullerManager; + + // The uid log sources from StatsdConfig. + std::vector mAllowedUid; + + // The pkg log sources from StatsdConfig. + std::vector mAllowedPkg; + + // The combined uid sources (after translating pkg name to uid). + // Logs from uids that are not in the list will be ignored to avoid spamming. + std::set mAllowedLogSources; + + // To guard access to mAllowedLogSources + mutable std::mutex mAllowedLogSourcesMutex; + + std::set mWhitelistedAtomIds; + + // We can pull any atom from these uids. + std::set mDefaultPullUids; + + // Uids that specific atoms can pull from. + // This is a map> + std::map> mPullAtomUids; + + // Packages that specific atoms can be pulled from. + std::map> mPullAtomPackages; + + // All uids to pull for this atom. NOTE: Does not include the default uids for memory. + std::map> mCombinedPullAtomUids; + + // Contains the annotations passed in with StatsdConfig. + std::list> mAnnotations; + + bool mShouldPersistHistory; + + // All event tags that are interesting to my metrics. + std::set mTagIds; + + // We only store the sp of AtomMatchingTracker, MetricProducer, and ConditionTracker in + // MetricsManager. There are relationships between them, and the relationships are denoted by + // index instead of pointers. The reasons for this are: (1) the relationship between them are + // complicated, so storing index instead of pointers reduces the risk that A holds B's sp, and B + // holds A's sp. (2) When we evaluate matcher results, or condition results, we can quickly get + // the related results from a cache using the index. + + // Hold all the atom matchers from the config. + std::vector> mAllAtomMatchingTrackers; + + // Hold all the conditions from the config. + std::vector> mAllConditionTrackers; + + // Hold all metrics from the config. + std::vector> mAllMetricProducers; + + // Hold all alert trackers. + std::vector> mAllAnomalyTrackers; + + // Hold all periodic alarm trackers. + std::vector> mAllPeriodicAlarmTrackers; + + // To make updating configs faster, we map the id of a AtomMatchingTracker, MetricProducer, and + // ConditionTracker to its index in the corresponding vector. + + // Maps the id of an atom matching tracker to its index in mAllAtomMatchingTrackers. + std::unordered_map mAtomMatchingTrackerMap; + + // Maps the id of a condition tracker to its index in mAllConditionTrackers. + std::unordered_map mConditionTrackerMap; + + // Maps the id of a metric producer to its index in mAllMetricProducers. + std::unordered_map mMetricProducerMap; + + // To make the log processing more efficient, we want to do as much filtering as possible + // before we go into individual trackers and conditions to match. + + // 1st filter: check if the event tag id is in mTagIds. + // 2nd filter: if it is, we parse the event because there is at least one member is interested. + // then pass to all AtomMatchingTrackers (itself also filter events by ids). + // 3nd filter: for AtomMatchingTrackers that matched this event, we pass this event to the + // ConditionTrackers and MetricProducers that use this matcher. + // 4th filter: for ConditionTrackers that changed value due to this event, we pass + // new conditions to metrics that use this condition. + + // The following map is initialized from the statsd_config. + + // Maps from the index of the AtomMatchingTracker to index of MetricProducer. + std::unordered_map> mTrackerToMetricMap; + + // Maps from AtomMatchingTracker to ConditionTracker + std::unordered_map> mTrackerToConditionMap; + + // Maps from ConditionTracker to MetricProducer + std::unordered_map> mConditionToMetricMap; + + // Maps from life span triggering event to MetricProducers. + std::unordered_map> mActivationAtomTrackerToMetricMap; + + // Maps deactivation triggering event to MetricProducers. + std::unordered_map> mDeactivationAtomTrackerToMetricMap; + + // Maps AlertIds to the index of the corresponding AnomalyTracker stored in mAllAnomalyTrackers. + // The map is used in LoadMetadata to more efficiently lookup AnomalyTrackers from an AlertId. + std::unordered_map mAlertTrackerMap; + + std::vector mMetricIndexesWithActivation; + + void initAllowedLogSources(); + + void initPullAtomSources(); + + // Only called on config creation/update to initialize log sources from the config. + // Calls initAllowedLogSources and initPullAtomSources. Sets mConfigValid to false on error. + void createAllLogSourcesFromConfig(const StatsdConfig& config); + + // Verifies the config meets guardrails and updates statsdStats. + // Sets mConfigValid to false on error. Should be called on config creation/update + void verifyGuardrailsAndUpdateStatsdStats(); + + // Initializes mIsAlwaysActive and mIsActive. + // Should be called on config creation/update. + void initializeConfigActiveStatus(); + + // The metrics that don't need to be uploaded or even reported. + std::set mNoReportMetricIds; + + // The config is active if any metric in the config is active. + bool mIsActive; + + // The config is always active if any metric in the config does not have an activation signal. + bool mIsAlwaysActive; + + // Hashes of the States used in this config, keyed by the state id, used in config updates. + std::map mStateProtoHashes; + + FRIEND_TEST(WakelockDurationE2eTest, TestAggregatedPredicateDimensions); + FRIEND_TEST(MetricConditionLinkE2eTest, TestMultiplePredicatesAndLinks); + FRIEND_TEST(AttributionE2eTest, TestAttributionMatchAndSliceByFirstUid); + FRIEND_TEST(AttributionE2eTest, TestAttributionMatchAndSliceByChain); + FRIEND_TEST(GaugeMetricE2eTest, TestMultipleFieldsForPushedEvent); + FRIEND_TEST(GaugeMetricE2eTest, TestRandomSamplePulledEvents); + FRIEND_TEST(GaugeMetricE2eTest, TestRandomSamplePulledEvent_LateAlarm); + FRIEND_TEST(GaugeMetricE2eTest, TestRandomSamplePulledEventsWithActivation); + FRIEND_TEST(GaugeMetricE2eTest, TestRandomSamplePulledEventsNoCondition); + FRIEND_TEST(GaugeMetricE2eTest, TestConditionChangeToTrueSamplePulledEvents); + + FRIEND_TEST(AnomalyDetectionE2eTest, TestSlicedCountMetric_single_bucket); + FRIEND_TEST(AnomalyDetectionE2eTest, TestSlicedCountMetric_multiple_buckets); + FRIEND_TEST(AnomalyDetectionE2eTest, TestCountMetric_save_refractory_to_disk_no_data_written); + FRIEND_TEST(AnomalyDetectionE2eTest, TestCountMetric_save_refractory_to_disk); + FRIEND_TEST(AnomalyDetectionE2eTest, TestCountMetric_load_refractory_from_disk); + FRIEND_TEST(AnomalyDetectionE2eTest, TestDurationMetric_SUM_single_bucket); + FRIEND_TEST(AnomalyDetectionE2eTest, TestDurationMetric_SUM_partial_bucket); + FRIEND_TEST(AnomalyDetectionE2eTest, TestDurationMetric_SUM_multiple_buckets); + FRIEND_TEST(AnomalyDetectionE2eTest, TestDurationMetric_SUM_long_refractory_period); + + FRIEND_TEST(AlarmE2eTest, TestMultipleAlarms); + FRIEND_TEST(ConfigTtlE2eTest, TestCountMetric); + FRIEND_TEST(ConfigUpdateE2eAbTest, TestConfigTtl); + FRIEND_TEST(MetricActivationE2eTest, TestCountMetric); + FRIEND_TEST(MetricActivationE2eTest, TestCountMetricWithOneDeactivation); + FRIEND_TEST(MetricActivationE2eTest, TestCountMetricWithTwoDeactivations); + FRIEND_TEST(MetricActivationE2eTest, TestCountMetricWithSameDeactivation); + FRIEND_TEST(MetricActivationE2eTest, TestCountMetricWithTwoMetricsTwoDeactivations); + + FRIEND_TEST(MetricsManagerTest, TestLogSources); + FRIEND_TEST(MetricsManagerTest, TestLogSourcesOnConfigUpdate); + + FRIEND_TEST(StatsLogProcessorTest, TestActiveConfigMetricDiskWriteRead); + FRIEND_TEST(StatsLogProcessorTest, TestActivationOnBoot); + FRIEND_TEST(StatsLogProcessorTest, TestActivationOnBootMultipleActivations); + FRIEND_TEST(StatsLogProcessorTest, + TestActivationOnBootMultipleActivationsDifferentActivationTypes); + FRIEND_TEST(StatsLogProcessorTest, TestActivationsPersistAcrossSystemServerRestart); + + FRIEND_TEST(CountMetricE2eTest, TestInitialConditionChanges); + FRIEND_TEST(CountMetricE2eTest, TestSlicedState); + FRIEND_TEST(CountMetricE2eTest, TestSlicedStateWithMap); + FRIEND_TEST(CountMetricE2eTest, TestMultipleSlicedStates); + FRIEND_TEST(CountMetricE2eTest, TestSlicedStateWithPrimaryFields); + + FRIEND_TEST(DurationMetricE2eTest, TestOneBucket); + FRIEND_TEST(DurationMetricE2eTest, TestTwoBuckets); + FRIEND_TEST(DurationMetricE2eTest, TestWithActivation); + FRIEND_TEST(DurationMetricE2eTest, TestWithCondition); + FRIEND_TEST(DurationMetricE2eTest, TestWithSlicedCondition); + FRIEND_TEST(DurationMetricE2eTest, TestWithActivationAndSlicedCondition); + FRIEND_TEST(DurationMetricE2eTest, TestWithSlicedState); + FRIEND_TEST(DurationMetricE2eTest, TestWithConditionAndSlicedState); + FRIEND_TEST(DurationMetricE2eTest, TestWithSlicedStateMapped); + FRIEND_TEST(DurationMetricE2eTest, TestWithSlicedStatePrimaryFieldsSuperset); + FRIEND_TEST(DurationMetricE2eTest, TestWithSlicedStatePrimaryFieldsSubset); + FRIEND_TEST(DurationMetricE2eTest, TestUploadThreshold); + + FRIEND_TEST(ValueMetricE2eTest, TestInitialConditionChanges); + FRIEND_TEST(ValueMetricE2eTest, TestPulledEvents); + FRIEND_TEST(ValueMetricE2eTest, TestPulledEvents_LateAlarm); + FRIEND_TEST(ValueMetricE2eTest, TestPulledEvents_WithActivation); + FRIEND_TEST(ValueMetricE2eTest, TestInitWithSlicedState); + FRIEND_TEST(ValueMetricE2eTest, TestInitWithSlicedState_WithDimensions); + FRIEND_TEST(ValueMetricE2eTest, TestInitWithSlicedState_WithIncorrectDimensions); +}; + +} // namespace statsd +} // namespace os +} // namespace android diff --git a/statsd/src/metrics/ValueMetricProducer.cpp b/statsd/src/metrics/ValueMetricProducer.cpp new file mode 100644 index 00000000..e766289f --- /dev/null +++ b/statsd/src/metrics/ValueMetricProducer.cpp @@ -0,0 +1,1282 @@ +/* + * 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. + */ + +#define DEBUG false // STOPSHIP if true +#include "Log.h" + +#include "ValueMetricProducer.h" + +#include +#include + +#include "../guardrail/StatsdStats.h" +#include "../stats_log_util.h" +#include "metrics/parsing_utils/metrics_manager_util.h" + +using android::util::FIELD_COUNT_REPEATED; +using android::util::FIELD_TYPE_BOOL; +using android::util::FIELD_TYPE_DOUBLE; +using android::util::FIELD_TYPE_INT32; +using android::util::FIELD_TYPE_INT64; +using android::util::FIELD_TYPE_MESSAGE; +using android::util::FIELD_TYPE_STRING; +using android::util::ProtoOutputStream; +using std::map; +using std::shared_ptr; +using std::unordered_map; + +namespace android { +namespace os { +namespace statsd { + +// for StatsLogReport +const int FIELD_ID_ID = 1; +const int FIELD_ID_VALUE_METRICS = 7; +const int FIELD_ID_TIME_BASE = 9; +const int FIELD_ID_BUCKET_SIZE = 10; +const int FIELD_ID_DIMENSION_PATH_IN_WHAT = 11; +const int FIELD_ID_IS_ACTIVE = 14; +// for ValueMetricDataWrapper +const int FIELD_ID_DATA = 1; +const int FIELD_ID_SKIPPED = 2; +// for SkippedBuckets +const int FIELD_ID_SKIPPED_START_MILLIS = 3; +const int FIELD_ID_SKIPPED_END_MILLIS = 4; +const int FIELD_ID_SKIPPED_DROP_EVENT = 5; +// for DumpEvent Proto +const int FIELD_ID_BUCKET_DROP_REASON = 1; +const int FIELD_ID_DROP_TIME = 2; +// for ValueMetricData +const int FIELD_ID_DIMENSION_IN_WHAT = 1; +const int FIELD_ID_BUCKET_INFO = 3; +const int FIELD_ID_DIMENSION_LEAF_IN_WHAT = 4; +const int FIELD_ID_SLICE_BY_STATE = 6; +// for ValueBucketInfo +const int FIELD_ID_VALUE_INDEX = 1; +const int FIELD_ID_VALUE_LONG = 2; +const int FIELD_ID_VALUE_DOUBLE = 3; +const int FIELD_ID_VALUES = 9; +const int FIELD_ID_BUCKET_NUM = 4; +const int FIELD_ID_START_BUCKET_ELAPSED_MILLIS = 5; +const int FIELD_ID_END_BUCKET_ELAPSED_MILLIS = 6; +const int FIELD_ID_CONDITION_TRUE_NS = 10; + +const Value ZERO_LONG((int64_t)0); +const Value ZERO_DOUBLE((int64_t)0); + +// ValueMetric has a minimum bucket size of 10min so that we don't pull too frequently +ValueMetricProducer::ValueMetricProducer( + const ConfigKey& key, const ValueMetric& metric, const int conditionIndex, + const vector& initialConditionCache, + const sp& conditionWizard, const uint64_t protoHash, + const int whatMatcherIndex, const sp& matcherWizard, + const int pullTagId, const int64_t timeBaseNs, const int64_t startTimeNs, + const sp& pullerManager, + const unordered_map>& eventActivationMap, + const unordered_map>>& eventDeactivationMap, + const vector& slicedStateAtoms, + const unordered_map>& stateGroupMap) + : MetricProducer(metric.id(), key, timeBaseNs, conditionIndex, initialConditionCache, + conditionWizard, protoHash, eventActivationMap, eventDeactivationMap, + slicedStateAtoms, stateGroupMap), + mWhatMatcherIndex(whatMatcherIndex), + mEventMatcherWizard(matcherWizard), + mPullerManager(pullerManager), + mPullTagId(pullTagId), + mIsPulled(pullTagId != -1), + mMinBucketSizeNs(metric.min_bucket_size_nanos()), + mDimensionSoftLimit(StatsdStats::kAtomDimensionKeySizeLimitMap.find(pullTagId) != + StatsdStats::kAtomDimensionKeySizeLimitMap.end() + ? StatsdStats::kAtomDimensionKeySizeLimitMap.at(pullTagId).first + : StatsdStats::kDimensionKeySizeSoftLimit), + mDimensionHardLimit(StatsdStats::kAtomDimensionKeySizeLimitMap.find(pullTagId) != + StatsdStats::kAtomDimensionKeySizeLimitMap.end() + ? StatsdStats::kAtomDimensionKeySizeLimitMap.at(pullTagId).second + : StatsdStats::kDimensionKeySizeHardLimit), + mUseAbsoluteValueOnReset(metric.use_absolute_value_on_reset()), + mAggregationType(metric.aggregation_type()), + mUseDiff(metric.has_use_diff() ? metric.use_diff() : (mIsPulled ? true : false)), + mValueDirection(metric.value_direction()), + mSkipZeroDiffOutput(metric.skip_zero_diff_output()), + mUseZeroDefaultBase(metric.use_zero_default_base()), + mHasGlobalBase(false), + mCurrentBucketIsSkipped(false), + mMaxPullDelayNs(metric.max_pull_delay_sec() > 0 ? metric.max_pull_delay_sec() * NS_PER_SEC + : StatsdStats::kPullMaxDelayNs), + mSplitBucketForAppUpgrade(metric.split_bucket_for_app_upgrade()), + // Condition timer will be set later within the constructor after pulling events + mConditionTimer(false, timeBaseNs) { + int64_t bucketSizeMills = 0; + if (metric.has_bucket()) { + bucketSizeMills = TimeUnitToBucketSizeInMillisGuardrailed(key.GetUid(), metric.bucket()); + } else { + bucketSizeMills = TimeUnitToBucketSizeInMillis(ONE_HOUR); + } + + mBucketSizeNs = bucketSizeMills * 1000000; + + translateFieldMatcher(metric.value_field(), &mFieldMatchers); + + if (metric.has_dimensions_in_what()) { + translateFieldMatcher(metric.dimensions_in_what(), &mDimensionsInWhat); + mContainANYPositionInDimensionsInWhat = HasPositionANY(metric.dimensions_in_what()); + mSliceByPositionALL = HasPositionALL(metric.dimensions_in_what()); + } + + if (metric.links().size() > 0) { + for (const auto& link : metric.links()) { + Metric2Condition mc; + mc.conditionId = link.condition(); + translateFieldMatcher(link.fields_in_what(), &mc.metricFields); + translateFieldMatcher(link.fields_in_condition(), &mc.conditionFields); + mMetric2ConditionLinks.push_back(mc); + } + mConditionSliced = true; + } + + for (const auto& stateLink : metric.state_link()) { + Metric2State ms; + ms.stateAtomId = stateLink.state_atom_id(); + translateFieldMatcher(stateLink.fields_in_what(), &ms.metricFields); + translateFieldMatcher(stateLink.fields_in_state(), &ms.stateFields); + mMetric2StateLinks.push_back(ms); + } + + int64_t numBucketsForward = calcBucketsForwardCount(startTimeNs); + mCurrentBucketNum += numBucketsForward; + + flushIfNeededLocked(startTimeNs); + + if (mIsPulled) { + mPullerManager->RegisterReceiver(mPullTagId, mConfigKey, this, getCurrentBucketEndTimeNs(), + mBucketSizeNs); + } + + // Only do this for partial buckets like first bucket. All other buckets should use + // flushIfNeeded to adjust start and end to bucket boundaries. + // Adjust start for partial bucket + mCurrentBucketStartTimeNs = startTimeNs; + mConditionTimer.newBucketStart(mCurrentBucketStartTimeNs); + + // Now that activations are processed, start the condition timer if needed. + mConditionTimer.onConditionChanged(mIsActive && mCondition == ConditionState::kTrue, + mCurrentBucketStartTimeNs); + + VLOG("value metric %lld created. bucket size %lld start_time: %lld", (long long)metric.id(), + (long long)mBucketSizeNs, (long long)mTimeBaseNs); +} + +ValueMetricProducer::~ValueMetricProducer() { + VLOG("~ValueMetricProducer() called"); + if (mIsPulled) { + mPullerManager->UnRegisterReceiver(mPullTagId, mConfigKey, this); + } +} + +bool ValueMetricProducer::onConfigUpdatedLocked( + const StatsdConfig& config, const int configIndex, const int metricIndex, + const vector>& allAtomMatchingTrackers, + const unordered_map& oldAtomMatchingTrackerMap, + const unordered_map& newAtomMatchingTrackerMap, + const sp& matcherWizard, + const vector>& allConditionTrackers, + const unordered_map& conditionTrackerMap, const sp& wizard, + const unordered_map& metricToActivationMap, + unordered_map>& trackerToMetricMap, + unordered_map>& conditionToMetricMap, + unordered_map>& activationAtomTrackerToMetricMap, + unordered_map>& deactivationAtomTrackerToMetricMap, + vector& metricsWithActivation) { + if (!MetricProducer::onConfigUpdatedLocked( + config, configIndex, metricIndex, allAtomMatchingTrackers, + oldAtomMatchingTrackerMap, newAtomMatchingTrackerMap, matcherWizard, + allConditionTrackers, conditionTrackerMap, wizard, metricToActivationMap, + trackerToMetricMap, conditionToMetricMap, activationAtomTrackerToMetricMap, + deactivationAtomTrackerToMetricMap, metricsWithActivation)) { + return false; + } + + const ValueMetric& metric = config.value_metric(configIndex); + // Update appropriate indices: mWhatMatcherIndex, mConditionIndex and MetricsManager maps. + if (!handleMetricWithAtomMatchingTrackers(metric.what(), metricIndex, /*enforceOneAtom=*/false, + allAtomMatchingTrackers, newAtomMatchingTrackerMap, + trackerToMetricMap, mWhatMatcherIndex)) { + return false; + } + + if (metric.has_condition() && + !handleMetricWithConditions(metric.condition(), metricIndex, conditionTrackerMap, + metric.links(), allConditionTrackers, mConditionTrackerIndex, + conditionToMetricMap)) { + return false; + } + sp tmpEventWizard = mEventMatcherWizard; + mEventMatcherWizard = matcherWizard; + return true; +} + +void ValueMetricProducer::onStateChanged(int64_t eventTimeNs, int32_t atomId, + const HashableDimensionKey& primaryKey, + const FieldValue& oldState, const FieldValue& newState) { + VLOG("ValueMetric %lld onStateChanged time %lld, State %d, key %s, %d -> %d", + (long long)mMetricId, (long long)eventTimeNs, atomId, primaryKey.toString().c_str(), + oldState.mValue.int_value, newState.mValue.int_value); + + // If old and new states are in the same StateGroup, then we do not need to + // pull for this state change. + FieldValue oldStateCopy = oldState; + FieldValue newStateCopy = newState; + mapStateValue(atomId, &oldStateCopy); + mapStateValue(atomId, &newStateCopy); + if (oldStateCopy == newStateCopy) { + return; + } + + // If condition is not true or metric is not active, we do not need to pull + // for this state change. + if (mCondition != ConditionState::kTrue || !mIsActive) { + return; + } + + bool isEventLate = eventTimeNs < mCurrentBucketStartTimeNs; + if (isEventLate) { + VLOG("Skip event due to late arrival: %lld vs %lld", (long long)eventTimeNs, + (long long)mCurrentBucketStartTimeNs); + invalidateCurrentBucket(eventTimeNs, BucketDropReason::EVENT_IN_WRONG_BUCKET); + return; + } + mStateChangePrimaryKey.first = atomId; + mStateChangePrimaryKey.second = primaryKey; + if (mIsPulled) { + pullAndMatchEventsLocked(eventTimeNs); + } + mStateChangePrimaryKey.first = 0; + mStateChangePrimaryKey.second = DEFAULT_DIMENSION_KEY; + flushIfNeededLocked(eventTimeNs); +} + +void ValueMetricProducer::onSlicedConditionMayChangeLocked(bool overallCondition, + const int64_t eventTime) { + VLOG("Metric %lld onSlicedConditionMayChange", (long long)mMetricId); +} + +void ValueMetricProducer::dropDataLocked(const int64_t dropTimeNs) { + StatsdStats::getInstance().noteBucketDropped(mMetricId); + + // The current partial bucket is not flushed and does not require a pull, + // so the data is still valid. + flushIfNeededLocked(dropTimeNs); + clearPastBucketsLocked(dropTimeNs); +} + +void ValueMetricProducer::clearPastBucketsLocked(const int64_t dumpTimeNs) { + mPastBuckets.clear(); + mSkippedBuckets.clear(); +} + +void ValueMetricProducer::onDumpReportLocked(const int64_t dumpTimeNs, + const bool include_current_partial_bucket, + const bool erase_data, + const DumpLatency dumpLatency, + std::set *str_set, + ProtoOutputStream* protoOutput) { + VLOG("metric %lld dump report now...", (long long)mMetricId); + if (include_current_partial_bucket) { + // For pull metrics, we need to do a pull at bucket boundaries. If we do not do that the + // current bucket will have incomplete data and the next will have the wrong snapshot to do + // a diff against. If the condition is false, we are fine since the base data is reset and + // we are not tracking anything. + bool pullNeeded = mIsPulled && mCondition == ConditionState::kTrue; + if (pullNeeded) { + switch (dumpLatency) { + case FAST: + invalidateCurrentBucket(dumpTimeNs, BucketDropReason::DUMP_REPORT_REQUESTED); + break; + case NO_TIME_CONSTRAINTS: + pullAndMatchEventsLocked(dumpTimeNs); + break; + } + } + flushCurrentBucketLocked(dumpTimeNs, dumpTimeNs); + } + protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_ID, (long long)mMetricId); + protoOutput->write(FIELD_TYPE_BOOL | FIELD_ID_IS_ACTIVE, isActiveLocked()); + + if (mPastBuckets.empty() && mSkippedBuckets.empty()) { + return; + } + protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_TIME_BASE, (long long)mTimeBaseNs); + protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_BUCKET_SIZE, (long long)mBucketSizeNs); + // Fills the dimension path if not slicing by ALL. + if (!mSliceByPositionALL) { + if (!mDimensionsInWhat.empty()) { + uint64_t dimenPathToken = + protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_ID_DIMENSION_PATH_IN_WHAT); + writeDimensionPathToProto(mDimensionsInWhat, protoOutput); + protoOutput->end(dimenPathToken); + } + } + + uint64_t protoToken = protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_ID_VALUE_METRICS); + + for (const auto& skippedBucket : mSkippedBuckets) { + uint64_t wrapperToken = + protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_SKIPPED); + protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_SKIPPED_START_MILLIS, + (long long)(NanoToMillis(skippedBucket.bucketStartTimeNs))); + protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_SKIPPED_END_MILLIS, + (long long)(NanoToMillis(skippedBucket.bucketEndTimeNs))); + for (const auto& dropEvent : skippedBucket.dropEvents) { + uint64_t dropEventToken = protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | + FIELD_ID_SKIPPED_DROP_EVENT); + protoOutput->write(FIELD_TYPE_INT32 | FIELD_ID_BUCKET_DROP_REASON, dropEvent.reason); + protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_DROP_TIME, + (long long)(NanoToMillis(dropEvent.dropTimeNs))); + protoOutput->end(dropEventToken); + } + protoOutput->end(wrapperToken); + } + + for (const auto& pair : mPastBuckets) { + const MetricDimensionKey& dimensionKey = pair.first; + VLOG(" dimension key %s", dimensionKey.toString().c_str()); + uint64_t wrapperToken = + protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_DATA); + + // First fill dimension. + if (mSliceByPositionALL) { + uint64_t dimensionToken = + protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_ID_DIMENSION_IN_WHAT); + writeDimensionToProto(dimensionKey.getDimensionKeyInWhat(), str_set, protoOutput); + protoOutput->end(dimensionToken); + } else { + writeDimensionLeafNodesToProto(dimensionKey.getDimensionKeyInWhat(), + FIELD_ID_DIMENSION_LEAF_IN_WHAT, str_set, protoOutput); + } + + // Then fill slice_by_state. + for (auto state : dimensionKey.getStateValuesKey().getValues()) { + uint64_t stateToken = protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | + FIELD_ID_SLICE_BY_STATE); + writeStateToProto(state, protoOutput); + protoOutput->end(stateToken); + } + + // Then fill bucket_info (ValueBucketInfo). + for (const auto& bucket : pair.second) { + uint64_t bucketInfoToken = protoOutput->start( + FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_BUCKET_INFO); + + if (bucket.mBucketEndNs - bucket.mBucketStartNs != mBucketSizeNs) { + protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_START_BUCKET_ELAPSED_MILLIS, + (long long)NanoToMillis(bucket.mBucketStartNs)); + protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_END_BUCKET_ELAPSED_MILLIS, + (long long)NanoToMillis(bucket.mBucketEndNs)); + } else { + protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_BUCKET_NUM, + (long long)(getBucketNumFromEndTimeNs(bucket.mBucketEndNs))); + } + // We only write the condition timer value if the metric has a + // condition and/or is sliced by state. + // If the metric is sliced by state, the condition timer value is + // also sliced by state to reflect time spent in that state. + if (mConditionTrackerIndex >= 0 || !mSlicedStateAtoms.empty()) { + protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_CONDITION_TRUE_NS, + (long long)bucket.mConditionTrueNs); + } + for (int i = 0; i < (int)bucket.valueIndex.size(); i++) { + int index = bucket.valueIndex[i]; + const Value& value = bucket.values[i]; + uint64_t valueToken = protoOutput->start( + FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_VALUES); + protoOutput->write(FIELD_TYPE_INT32 | FIELD_ID_VALUE_INDEX, + index); + if (value.getType() == LONG) { + protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_VALUE_LONG, + (long long)value.long_value); + VLOG("\t bucket [%lld - %lld] value %d: %lld", (long long)bucket.mBucketStartNs, + (long long)bucket.mBucketEndNs, index, (long long)value.long_value); + } else if (value.getType() == DOUBLE) { + protoOutput->write(FIELD_TYPE_DOUBLE | FIELD_ID_VALUE_DOUBLE, + value.double_value); + VLOG("\t bucket [%lld - %lld] value %d: %.2f", (long long)bucket.mBucketStartNs, + (long long)bucket.mBucketEndNs, index, value.double_value); + } else { + VLOG("Wrong value type for ValueMetric output: %d", value.getType()); + } + protoOutput->end(valueToken); + } + protoOutput->end(bucketInfoToken); + } + protoOutput->end(wrapperToken); + } + protoOutput->end(protoToken); + + VLOG("metric %lld done with dump report...", (long long)mMetricId); + if (erase_data) { + mPastBuckets.clear(); + mSkippedBuckets.clear(); + } +} + +void ValueMetricProducer::invalidateCurrentBucketWithoutResetBase(const int64_t dropTimeNs, + const BucketDropReason reason) { + if (!mCurrentBucketIsSkipped) { + // Only report to StatsdStats once per invalid bucket. + StatsdStats::getInstance().noteInvalidatedBucket(mMetricId); + } + + skipCurrentBucket(dropTimeNs, reason); +} + +void ValueMetricProducer::invalidateCurrentBucket(const int64_t dropTimeNs, + const BucketDropReason reason) { + invalidateCurrentBucketWithoutResetBase(dropTimeNs, reason); + resetBase(); +} + +void ValueMetricProducer::skipCurrentBucket(const int64_t dropTimeNs, + const BucketDropReason reason) { + if (!maxDropEventsReached()) { + mCurrentSkippedBucket.dropEvents.emplace_back(buildDropEvent(dropTimeNs, reason)); + } + mCurrentBucketIsSkipped = true; +} + +void ValueMetricProducer::resetBase() { + for (auto& slice : mCurrentBaseInfo) { + for (auto& baseInfo : slice.second.baseInfos) { + baseInfo.hasBase = false; + } + } + mHasGlobalBase = false; +} + +// Handle active state change. Active state change is treated like a condition change: +// - drop bucket if active state change event arrives too late +// - if condition is true, pull data on active state changes +// - ConditionTimer tracks changes based on AND of condition and active state. +void ValueMetricProducer::onActiveStateChangedLocked(const int64_t& eventTimeNs) { + bool isEventTooLate = eventTimeNs < mCurrentBucketStartTimeNs; + if (isEventTooLate) { + // Drop bucket because event arrived too late, ie. we are missing data for this bucket. + StatsdStats::getInstance().noteLateLogEventSkipped(mMetricId); + invalidateCurrentBucket(eventTimeNs, BucketDropReason::EVENT_IN_WRONG_BUCKET); + } + + // Call parent method once we've verified the validity of current bucket. + MetricProducer::onActiveStateChangedLocked(eventTimeNs); + + if (ConditionState::kTrue != mCondition) { + return; + } + + // Pull on active state changes. + if (!isEventTooLate) { + if (mIsPulled) { + pullAndMatchEventsLocked(eventTimeNs); + } + // When active state changes from true to false, clear diff base but don't + // reset other counters as we may accumulate more value in the bucket. + if (mUseDiff && !mIsActive) { + resetBase(); + } + } + + flushIfNeededLocked(eventTimeNs); + + // Let condition timer know of new active state. + mConditionTimer.onConditionChanged(mIsActive, eventTimeNs); + + updateCurrentSlicedBucketConditionTimers(mIsActive, eventTimeNs); +} + +void ValueMetricProducer::onConditionChangedLocked(const bool condition, + const int64_t eventTimeNs) { + ConditionState newCondition = condition ? ConditionState::kTrue : ConditionState::kFalse; + bool isEventTooLate = eventTimeNs < mCurrentBucketStartTimeNs; + + // If the config is not active, skip the event. + if (!mIsActive) { + mCondition = isEventTooLate ? ConditionState::kUnknown : newCondition; + return; + } + + // If the event arrived late, mark the bucket as invalid and skip the event. + if (isEventTooLate) { + VLOG("Skip event due to late arrival: %lld vs %lld", (long long)eventTimeNs, + (long long)mCurrentBucketStartTimeNs); + StatsdStats::getInstance().noteLateLogEventSkipped(mMetricId); + StatsdStats::getInstance().noteConditionChangeInNextBucket(mMetricId); + invalidateCurrentBucket(eventTimeNs, BucketDropReason::EVENT_IN_WRONG_BUCKET); + mCondition = ConditionState::kUnknown; + mConditionTimer.onConditionChanged(mCondition, eventTimeNs); + + updateCurrentSlicedBucketConditionTimers(mCondition, eventTimeNs); + return; + } + + // If the previous condition was unknown, mark the bucket as invalid + // because the bucket will contain partial data. For example, the condition + // change might happen close to the end of the bucket and we might miss a + // lot of data. + // + // We still want to pull to set the base. + if (mCondition == ConditionState::kUnknown) { + invalidateCurrentBucket(eventTimeNs, BucketDropReason::CONDITION_UNKNOWN); + } + + // Pull and match for the following condition change cases: + // unknown/false -> true - condition changed + // true -> false - condition changed + // true -> true - old condition was true so we can flush the bucket at the + // end if needed. + // + // We don’t need to pull for unknown -> false or false -> false. + // + // onConditionChangedLocked might happen on bucket boundaries if this is + // called before #onDataPulled. + if (mIsPulled && + (newCondition == ConditionState::kTrue || mCondition == ConditionState::kTrue)) { + pullAndMatchEventsLocked(eventTimeNs); + } + + // For metrics that use diff, when condition changes from true to false, + // clear diff base but don't reset other counts because we may accumulate + // more value in the bucket. + if (mUseDiff && + (mCondition == ConditionState::kTrue && newCondition == ConditionState::kFalse)) { + resetBase(); + } + + // Update condition state after pulling. + mCondition = newCondition; + + flushIfNeededLocked(eventTimeNs); + mConditionTimer.onConditionChanged(mCondition, eventTimeNs); + + updateCurrentSlicedBucketConditionTimers(mCondition, eventTimeNs); +} + +void ValueMetricProducer::updateCurrentSlicedBucketConditionTimers(bool newCondition, + int64_t eventTimeNs) { + if (mSlicedStateAtoms.empty()) { + return; + } + + // Utilize the current state key of each DimensionsInWhat key to determine + // which condition timers to update. + // + // Assumes that the MetricDimensionKey exists in `mCurrentSlicedBucket`. + bool inPulledData; + for (const auto& [dimensionInWhatKey, dimensionInWhatInfo] : mCurrentBaseInfo) { + // If the new condition is true, turn ON the condition timer only if + // the DimensionInWhat key was present in the pulled data. + inPulledData = dimensionInWhatInfo.hasCurrentState; + mCurrentSlicedBucket[MetricDimensionKey(dimensionInWhatKey, + dimensionInWhatInfo.currentState)] + .conditionTimer.onConditionChanged(newCondition && inPulledData, eventTimeNs); + } +} + +void ValueMetricProducer::prepareFirstBucketLocked() { + // Kicks off the puller immediately if condition is true and diff based. + if (mIsActive && mIsPulled && mCondition == ConditionState::kTrue && mUseDiff) { + pullAndMatchEventsLocked(mCurrentBucketStartTimeNs); + } +} + +void ValueMetricProducer::pullAndMatchEventsLocked(const int64_t timestampNs) { + vector> allData; + if (!mPullerManager->Pull(mPullTagId, mConfigKey, timestampNs, &allData)) { + ALOGE("Stats puller failed for tag: %d at %lld", mPullTagId, (long long)timestampNs); + invalidateCurrentBucket(timestampNs, BucketDropReason::PULL_FAILED); + return; + } + + accumulateEvents(allData, timestampNs, timestampNs); +} + +int64_t ValueMetricProducer::calcPreviousBucketEndTime(const int64_t currentTimeNs) { + return mTimeBaseNs + ((currentTimeNs - mTimeBaseNs) / mBucketSizeNs) * mBucketSizeNs; +} + +// By design, statsd pulls data at bucket boundaries using AlarmManager. These pulls are likely +// to be delayed. Other events like condition changes or app upgrade which are not based on +// AlarmManager might have arrived earlier and close the bucket. +void ValueMetricProducer::onDataPulled(const std::vector>& allData, + bool pullSuccess, int64_t originalPullTimeNs) { + std::lock_guard lock(mMutex); + if (mCondition == ConditionState::kTrue) { + // If the pull failed, we won't be able to compute a diff. + if (!pullSuccess) { + invalidateCurrentBucket(originalPullTimeNs, BucketDropReason::PULL_FAILED); + } else { + bool isEventLate = originalPullTimeNs < getCurrentBucketEndTimeNs(); + if (isEventLate) { + // If the event is late, we are in the middle of a bucket. Just + // process the data without trying to snap the data to the nearest bucket. + accumulateEvents(allData, originalPullTimeNs, originalPullTimeNs); + } else { + // For scheduled pulled data, the effective event time is snap to the nearest + // bucket end. In the case of waking up from a deep sleep state, we will + // attribute to the previous bucket end. If the sleep was long but not very + // long, we will be in the immediate next bucket. Previous bucket may get a + // larger number as we pull at a later time than real bucket end. + // + // If the sleep was very long, we skip more than one bucket before sleep. In + // this case, if the diff base will be cleared and this new data will serve as + // new diff base. + int64_t bucketEndTime = calcPreviousBucketEndTime(originalPullTimeNs) - 1; + StatsdStats::getInstance().noteBucketBoundaryDelayNs( + mMetricId, originalPullTimeNs - bucketEndTime); + accumulateEvents(allData, originalPullTimeNs, bucketEndTime); + } + } + } + + // We can probably flush the bucket. Since we used bucketEndTime when calling + // #onMatchedLogEventInternalLocked, the current bucket will not have been flushed. + flushIfNeededLocked(originalPullTimeNs); +} + +void ValueMetricProducer::accumulateEvents(const std::vector>& allData, + int64_t originalPullTimeNs, int64_t eventElapsedTimeNs) { + bool isEventLate = eventElapsedTimeNs < mCurrentBucketStartTimeNs; + if (isEventLate) { + VLOG("Skip bucket end pull due to late arrival: %lld vs %lld", + (long long)eventElapsedTimeNs, (long long)mCurrentBucketStartTimeNs); + StatsdStats::getInstance().noteLateLogEventSkipped(mMetricId); + invalidateCurrentBucket(eventElapsedTimeNs, BucketDropReason::EVENT_IN_WRONG_BUCKET); + return; + } + + const int64_t elapsedRealtimeNs = getElapsedRealtimeNs(); + const int64_t pullDelayNs = elapsedRealtimeNs - originalPullTimeNs; + StatsdStats::getInstance().notePullDelay(mPullTagId, pullDelayNs); + if (pullDelayNs > mMaxPullDelayNs) { + ALOGE("Pull finish too late for atom %d, longer than %lld", mPullTagId, + (long long)mMaxPullDelayNs); + StatsdStats::getInstance().notePullExceedMaxDelay(mPullTagId); + // We are missing one pull from the bucket which means we will not have a complete view of + // what's going on. + invalidateCurrentBucket(eventElapsedTimeNs, BucketDropReason::PULL_DELAYED); + return; + } + + mMatchedMetricDimensionKeys.clear(); + for (const auto& data : allData) { + LogEvent localCopy = data->makeCopy(); + if (mEventMatcherWizard->matchLogEvent(localCopy, mWhatMatcherIndex) == + MatchingState::kMatched) { + localCopy.setElapsedTimestampNs(eventElapsedTimeNs); + onMatchedLogEventLocked(mWhatMatcherIndex, localCopy); + } + } + // If a key that is: + // 1. Tracked in mCurrentSlicedBucket and + // 2. A superset of the current mStateChangePrimaryKey + // was not found in the new pulled data (i.e. not in mMatchedDimensionInWhatKeys) + // then we need to reset the base. + for (auto& [metricDimensionKey, currentValueBucket] : mCurrentSlicedBucket) { + const auto& whatKey = metricDimensionKey.getDimensionKeyInWhat(); + bool presentInPulledData = + mMatchedMetricDimensionKeys.find(whatKey) != mMatchedMetricDimensionKeys.end(); + if (!presentInPulledData && whatKey.contains(mStateChangePrimaryKey.second)) { + auto it = mCurrentBaseInfo.find(whatKey); + for (auto& baseInfo : it->second.baseInfos) { + baseInfo.hasBase = false; + } + // Set to false when DimensionInWhat key is not present in a pull. + // Used in onMatchedLogEventInternalLocked() to ensure the condition + // timer is turned on the next pull when data is present. + it->second.hasCurrentState = false; + // Turn OFF condition timer for keys not present in pulled data. + currentValueBucket.conditionTimer.onConditionChanged(false, eventElapsedTimeNs); + } + } + mMatchedMetricDimensionKeys.clear(); + mHasGlobalBase = true; + + // If we reach the guardrail, we might have dropped some data which means the bucket is + // incomplete. + // + // The base also needs to be reset. If we do not have the full data, we might + // incorrectly compute the diff when mUseZeroDefaultBase is true since an existing key + // might be missing from mCurrentSlicedBucket. + if (hasReachedGuardRailLimit()) { + invalidateCurrentBucket(eventElapsedTimeNs, BucketDropReason::DIMENSION_GUARDRAIL_REACHED); + mCurrentSlicedBucket.clear(); + } +} + +void ValueMetricProducer::dumpStatesLocked(FILE* out, bool verbose) const { + if (mCurrentSlicedBucket.size() == 0) { + return; + } + + fprintf(out, "ValueMetric %lld dimension size %lu\n", (long long)mMetricId, + (unsigned long)mCurrentSlicedBucket.size()); + if (verbose) { + for (const auto& it : mCurrentSlicedBucket) { + for (const auto& interval : it.second.intervals) { + fprintf(out, "\t(what)%s\t(states)%s (value)%s\n", + it.first.getDimensionKeyInWhat().toString().c_str(), + it.first.getStateValuesKey().toString().c_str(), + interval.value.toString().c_str()); + } + } + } +} + +bool ValueMetricProducer::hasReachedGuardRailLimit() const { + return mCurrentSlicedBucket.size() >= mDimensionHardLimit; +} + +bool ValueMetricProducer::hitGuardRailLocked(const MetricDimensionKey& newKey) { + // ===========GuardRail============== + // 1. Report the tuple count if the tuple count > soft limit + if (mCurrentSlicedBucket.find(newKey) != mCurrentSlicedBucket.end()) { + return false; + } + if (mCurrentSlicedBucket.size() > mDimensionSoftLimit - 1) { + size_t newTupleCount = mCurrentSlicedBucket.size() + 1; + StatsdStats::getInstance().noteMetricDimensionSize(mConfigKey, mMetricId, newTupleCount); + // 2. Don't add more tuples, we are above the allowed threshold. Drop the data. + if (hasReachedGuardRailLimit()) { + ALOGE("ValueMetric %lld dropping data for dimension key %s", (long long)mMetricId, + newKey.toString().c_str()); + StatsdStats::getInstance().noteHardDimensionLimitReached(mMetricId); + return true; + } + } + + return false; +} + +bool ValueMetricProducer::hitFullBucketGuardRailLocked(const MetricDimensionKey& newKey) { + // ===========GuardRail============== + // 1. Report the tuple count if the tuple count > soft limit + if (mCurrentFullBucket.find(newKey) != mCurrentFullBucket.end()) { + return false; + } + if (mCurrentFullBucket.size() > mDimensionSoftLimit - 1) { + size_t newTupleCount = mCurrentFullBucket.size() + 1; + // 2. Don't add more tuples, we are above the allowed threshold. Drop the data. + if (newTupleCount > mDimensionHardLimit) { + ALOGE("ValueMetric %lld dropping data for full bucket dimension key %s", + (long long)mMetricId, + newKey.toString().c_str()); + return true; + } + } + + return false; +} + +bool getDoubleOrLong(const LogEvent& event, const Matcher& matcher, Value& ret) { + for (const FieldValue& value : event.getValues()) { + if (value.mField.matches(matcher)) { + switch (value.mValue.type) { + case INT: + ret.setLong(value.mValue.int_value); + break; + case LONG: + ret.setLong(value.mValue.long_value); + break; + case FLOAT: + ret.setDouble(value.mValue.float_value); + break; + case DOUBLE: + ret.setDouble(value.mValue.double_value); + break; + default: + return false; + break; + } + return true; + } + } + return false; +} + +bool ValueMetricProducer::multipleBucketsSkipped(const int64_t numBucketsForward) { + // Skip buckets if this is a pulled metric or a pushed metric that is diffed. + return numBucketsForward > 1 && (mIsPulled || mUseDiff); +} + +void ValueMetricProducer::onMatchedLogEventInternalLocked( + const size_t matcherIndex, const MetricDimensionKey& eventKey, + const ConditionKey& conditionKey, bool condition, const LogEvent& event, + const map& statePrimaryKeys) { + auto whatKey = eventKey.getDimensionKeyInWhat(); + auto stateKey = eventKey.getStateValuesKey(); + + // Skip this event if a state changed occurred for a different primary key. + auto it = statePrimaryKeys.find(mStateChangePrimaryKey.first); + // Check that both the atom id and the primary key are equal. + if (it != statePrimaryKeys.end() && it->second != mStateChangePrimaryKey.second) { + VLOG("ValueMetric skip event with primary key %s because state change primary key " + "is %s", + it->second.toString().c_str(), mStateChangePrimaryKey.second.toString().c_str()); + return; + } + + int64_t eventTimeNs = event.GetElapsedTimestampNs(); + if (eventTimeNs < mCurrentBucketStartTimeNs) { + VLOG("Skip event due to late arrival: %lld vs %lld", (long long)eventTimeNs, + (long long)mCurrentBucketStartTimeNs); + return; + } + mMatchedMetricDimensionKeys.insert(whatKey); + + if (!mIsPulled) { + // We cannot flush without doing a pull first. + flushIfNeededLocked(eventTimeNs); + } + + // We should not accumulate the data for pushed metrics when the condition is false. + bool shouldSkipForPushMetric = !mIsPulled && !condition; + // For pulled metrics, there are two cases: + // - to compute diffs, we need to process all the state changes + // - for non-diffs metrics, we should ignore the data if the condition wasn't true. If we have a + // state change from + // + True -> True: we should process the data, it might be a bucket boundary + // + True -> False: we als need to process the data. + bool shouldSkipForPulledMetric = mIsPulled && !mUseDiff + && mCondition != ConditionState::kTrue; + if (shouldSkipForPushMetric || shouldSkipForPulledMetric) { + VLOG("ValueMetric skip event because condition is false and we are not using diff (for " + "pulled metric)"); + return; + } + + if (hitGuardRailLocked(eventKey)) { + return; + } + + const auto& returnVal = + mCurrentBaseInfo.emplace(whatKey, DimensionsInWhatInfo(getUnknownStateKey())); + DimensionsInWhatInfo& dimensionsInWhatInfo = returnVal.first->second; + const HashableDimensionKey oldStateKey = dimensionsInWhatInfo.currentState; + vector& baseInfos = dimensionsInWhatInfo.baseInfos; + if (baseInfos.size() < mFieldMatchers.size()) { + VLOG("Resizing number of intervals to %d", (int)mFieldMatchers.size()); + baseInfos.resize(mFieldMatchers.size()); + } + + // Ensure we turn on the condition timer in the case where dimensions + // were missing on a previous pull due to a state change. + bool stateChange = oldStateKey != stateKey; + if (!dimensionsInWhatInfo.hasCurrentState) { + stateChange = true; + dimensionsInWhatInfo.hasCurrentState = true; + } + + // We need to get the intervals stored with the previous state key so we can + // close these value intervals. + vector& intervals = + mCurrentSlicedBucket[MetricDimensionKey(whatKey, oldStateKey)].intervals; + if (intervals.size() < mFieldMatchers.size()) { + VLOG("Resizing number of intervals to %d", (int)mFieldMatchers.size()); + intervals.resize(mFieldMatchers.size()); + } + + // We only use anomaly detection under certain cases. + // N.B.: The anomaly detection cases were modified in order to fix an issue with value metrics + // containing multiple values. We tried to retain all previous behaviour, but we are unsure the + // previous behaviour was correct. At the time of the fix, anomaly detection had no owner. + // Whoever next works on it should look into the cases where it is triggered in this function. + // Discussion here: http://ag/6124370. + bool useAnomalyDetection = true; + + dimensionsInWhatInfo.hasCurrentState = true; + dimensionsInWhatInfo.currentState = stateKey; + for (int i = 0; i < (int)mFieldMatchers.size(); i++) { + const Matcher& matcher = mFieldMatchers[i]; + BaseInfo& baseInfo = baseInfos[i]; + Interval& interval = intervals[i]; + interval.valueIndex = i; + Value value; + if (!getDoubleOrLong(event, matcher, value)) { + VLOG("Failed to get value %d from event %s", i, event.ToString().c_str()); + StatsdStats::getInstance().noteBadValueType(mMetricId); + return; + } + interval.seenNewData = true; + + if (mUseDiff) { + if (!baseInfo.hasBase) { + if (mHasGlobalBase && mUseZeroDefaultBase) { + // The bucket has global base. This key does not. + // Optionally use zero as base. + baseInfo.base = (value.type == LONG ? ZERO_LONG : ZERO_DOUBLE); + baseInfo.hasBase = true; + } else { + // no base. just update base and return. + baseInfo.base = value; + baseInfo.hasBase = true; + // If we're missing a base, do not use anomaly detection on incomplete data + useAnomalyDetection = false; + // Continue (instead of return) here in order to set baseInfo.base and + // baseInfo.hasBase for other baseInfos + continue; + } + } + + Value diff; + switch (mValueDirection) { + case ValueMetric::INCREASING: + if (value >= baseInfo.base) { + diff = value - baseInfo.base; + } else if (mUseAbsoluteValueOnReset) { + diff = value; + } else { + VLOG("Unexpected decreasing value"); + StatsdStats::getInstance().notePullDataError(mPullTagId); + baseInfo.base = value; + // If we've got bad data, do not use anomaly detection + useAnomalyDetection = false; + continue; + } + break; + case ValueMetric::DECREASING: + if (baseInfo.base >= value) { + diff = baseInfo.base - value; + } else if (mUseAbsoluteValueOnReset) { + diff = value; + } else { + VLOG("Unexpected increasing value"); + StatsdStats::getInstance().notePullDataError(mPullTagId); + baseInfo.base = value; + // If we've got bad data, do not use anomaly detection + useAnomalyDetection = false; + continue; + } + break; + case ValueMetric::ANY: + diff = value - baseInfo.base; + break; + default: + break; + } + baseInfo.base = value; + value = diff; + } + + if (interval.hasValue) { + switch (mAggregationType) { + case ValueMetric::SUM: + // for AVG, we add up and take average when flushing the bucket + case ValueMetric::AVG: + interval.value += value; + break; + case ValueMetric::MIN: + interval.value = std::min(value, interval.value); + break; + case ValueMetric::MAX: + interval.value = std::max(value, interval.value); + break; + default: + break; + } + } else { + interval.value = value; + interval.hasValue = true; + } + interval.sampleSize += 1; + } + + // State change. + if (!mSlicedStateAtoms.empty() && stateChange) { + // Turn OFF the condition timer for the previous state key. + mCurrentSlicedBucket[MetricDimensionKey(whatKey, oldStateKey)] + .conditionTimer.onConditionChanged(false, eventTimeNs); + + // Turn ON the condition timer for the new state key. + mCurrentSlicedBucket[MetricDimensionKey(whatKey, stateKey)] + .conditionTimer.onConditionChanged(true, eventTimeNs); + } + + // Only trigger the tracker if all intervals are correct and we have not skipped the bucket due + // to MULTIPLE_BUCKETS_SKIPPED. + if (useAnomalyDetection && !multipleBucketsSkipped(calcBucketsForwardCount(eventTimeNs))) { + // TODO: propgate proper values down stream when anomaly support doubles + long wholeBucketVal = intervals[0].value.long_value; + auto prev = mCurrentFullBucket.find(eventKey); + if (prev != mCurrentFullBucket.end()) { + wholeBucketVal += prev->second; + } + for (auto& tracker : mAnomalyTrackers) { + tracker->detectAndDeclareAnomaly(eventTimeNs, mCurrentBucketNum, mMetricId, eventKey, + wholeBucketVal); + } + } +} + +// For pulled metrics, we always need to make sure we do a pull before flushing the bucket +// if mCondition is true! +void ValueMetricProducer::flushIfNeededLocked(const int64_t& eventTimeNs) { + int64_t currentBucketEndTimeNs = getCurrentBucketEndTimeNs(); + if (eventTimeNs < currentBucketEndTimeNs) { + VLOG("eventTime is %lld, less than current bucket end time %lld", (long long)eventTimeNs, + (long long)(currentBucketEndTimeNs)); + return; + } + int64_t numBucketsForward = calcBucketsForwardCount(eventTimeNs); + int64_t nextBucketStartTimeNs = currentBucketEndTimeNs + (numBucketsForward - 1) * mBucketSizeNs; + flushCurrentBucketLocked(eventTimeNs, nextBucketStartTimeNs); +} + +int64_t ValueMetricProducer::calcBucketsForwardCount(const int64_t& eventTimeNs) const { + int64_t currentBucketEndTimeNs = getCurrentBucketEndTimeNs(); + if (eventTimeNs < currentBucketEndTimeNs) { + return 0; + } + return 1 + (eventTimeNs - currentBucketEndTimeNs) / mBucketSizeNs; +} + +void ValueMetricProducer::flushCurrentBucketLocked(const int64_t& eventTimeNs, + const int64_t& nextBucketStartTimeNs) { + if (mCondition == ConditionState::kUnknown) { + StatsdStats::getInstance().noteBucketUnknownCondition(mMetricId); + invalidateCurrentBucketWithoutResetBase(eventTimeNs, BucketDropReason::CONDITION_UNKNOWN); + } + + VLOG("finalizing bucket for %ld, dumping %d slices", (long)mCurrentBucketStartTimeNs, + (int)mCurrentSlicedBucket.size()); + + int64_t fullBucketEndTimeNs = getCurrentBucketEndTimeNs(); + int64_t bucketEndTime = fullBucketEndTimeNs; + int64_t numBucketsForward = calcBucketsForwardCount(eventTimeNs); + + if (multipleBucketsSkipped(numBucketsForward)) { + VLOG("Skipping forward %lld buckets", (long long)numBucketsForward); + StatsdStats::getInstance().noteSkippedForwardBuckets(mMetricId); + // Something went wrong. Maybe the device was sleeping for a long time. It is better + // to mark the current bucket as invalid. The last pull might have been successful through. + invalidateCurrentBucketWithoutResetBase(eventTimeNs, + BucketDropReason::MULTIPLE_BUCKETS_SKIPPED); + // End the bucket at the next bucket start time so the entire interval is skipped. + bucketEndTime = nextBucketStartTimeNs; + } else if (eventTimeNs < fullBucketEndTimeNs) { + bucketEndTime = eventTimeNs; + } + + // Close the current bucket. + int64_t conditionTrueDuration = mConditionTimer.newBucketStart(bucketEndTime); + bool isBucketLargeEnough = bucketEndTime - mCurrentBucketStartTimeNs >= mMinBucketSizeNs; + if (!isBucketLargeEnough) { + skipCurrentBucket(eventTimeNs, BucketDropReason::BUCKET_TOO_SMALL); + } + if (!mCurrentBucketIsSkipped) { + bool bucketHasData = false; + // The current bucket is large enough to keep. + for (auto& [metricDimensionKey, currentValueBucket] : mCurrentSlicedBucket) { + PastValueBucket bucket = + buildPartialBucket(bucketEndTime, currentValueBucket.intervals); + if (!mSlicedStateAtoms.empty()) { + bucket.mConditionTrueNs = + currentValueBucket.conditionTimer.newBucketStart(bucketEndTime); + } else { + bucket.mConditionTrueNs = conditionTrueDuration; + } + // it will auto create new vector of ValuebucketInfo if the key is not found. + if (bucket.valueIndex.size() > 0) { + auto& bucketList = mPastBuckets[metricDimensionKey]; + bucketList.push_back(bucket); + bucketHasData = true; + } + } + if (!bucketHasData) { + skipCurrentBucket(eventTimeNs, BucketDropReason::NO_DATA); + } + } + + if (mCurrentBucketIsSkipped) { + mCurrentSkippedBucket.bucketStartTimeNs = mCurrentBucketStartTimeNs; + mCurrentSkippedBucket.bucketEndTimeNs = bucketEndTime; + mSkippedBuckets.emplace_back(mCurrentSkippedBucket); + } + + // This means that the current bucket was not flushed before a forced bucket split. + // This can happen if an app update or a dump report with include_current_partial_bucket is + // requested before we get a chance to flush the bucket due to receiving new data, either from + // the statsd socket or the StatsPullerManager. + if (bucketEndTime < nextBucketStartTimeNs) { + SkippedBucket bucketInGap; + bucketInGap.bucketStartTimeNs = bucketEndTime; + bucketInGap.bucketEndTimeNs = nextBucketStartTimeNs; + bucketInGap.dropEvents.emplace_back( + buildDropEvent(eventTimeNs, BucketDropReason::NO_DATA)); + mSkippedBuckets.emplace_back(bucketInGap); + } + appendToFullBucket(eventTimeNs > fullBucketEndTimeNs); + initCurrentSlicedBucket(nextBucketStartTimeNs); + // Update the condition timer again, in case we skipped buckets. + mConditionTimer.newBucketStart(nextBucketStartTimeNs); + + // NOTE: Update the condition timers in `mCurrentSlicedBucket` only when slicing + // by state. Otherwise, the "global" condition timer will be used. + if (!mSlicedStateAtoms.empty()) { + for (auto& [metricDimensionKey, currentValueBucket] : mCurrentSlicedBucket) { + currentValueBucket.conditionTimer.newBucketStart(nextBucketStartTimeNs); + } + } + mCurrentBucketNum += numBucketsForward; +} + +PastValueBucket ValueMetricProducer::buildPartialBucket(int64_t bucketEndTime, + const std::vector& intervals) { + PastValueBucket bucket; + bucket.mBucketStartNs = mCurrentBucketStartTimeNs; + bucket.mBucketEndNs = bucketEndTime; + for (const auto& interval : intervals) { + if (interval.hasValue) { + // skip the output if the diff is zero + if (mSkipZeroDiffOutput && mUseDiff && interval.value.isZero()) { + continue; + } + bucket.valueIndex.push_back(interval.valueIndex); + if (mAggregationType != ValueMetric::AVG) { + bucket.values.push_back(interval.value); + } else { + double sum = interval.value.type == LONG ? (double)interval.value.long_value + : interval.value.double_value; + bucket.values.push_back(Value((double)sum / interval.sampleSize)); + } + } + } + return bucket; +} + +void ValueMetricProducer::initCurrentSlicedBucket(int64_t nextBucketStartTimeNs) { + StatsdStats::getInstance().noteBucketCount(mMetricId); + // Cleanup data structure to aggregate values. + for (auto it = mCurrentSlicedBucket.begin(); it != mCurrentSlicedBucket.end();) { + bool obsolete = true; + for (auto& interval : it->second.intervals) { + interval.hasValue = false; + interval.sampleSize = 0; + if (interval.seenNewData) { + obsolete = false; + } + interval.seenNewData = false; + } + + if (obsolete && !mSlicedStateAtoms.empty()) { + // When slicing by state, only delete the MetricDimensionKey when the + // state key in the MetricDimensionKey is not the current state key. + const HashableDimensionKey& dimensionInWhatKey = it->first.getDimensionKeyInWhat(); + const auto& currentBaseInfoItr = mCurrentBaseInfo.find(dimensionInWhatKey); + + if ((currentBaseInfoItr != mCurrentBaseInfo.end()) && + (it->first.getStateValuesKey() == currentBaseInfoItr->second.currentState)) { + obsolete = false; + } + } + if (obsolete) { + it = mCurrentSlicedBucket.erase(it); + } else { + it++; + } + // TODO(b/157655103): remove mCurrentBaseInfo entries when obsolete + } + + mCurrentBucketIsSkipped = false; + mCurrentSkippedBucket.reset(); + + // If we do not have a global base when the condition is true, + // we will have incomplete bucket for the next bucket. + if (mUseDiff && !mHasGlobalBase && mCondition) { + mCurrentBucketIsSkipped = false; + } + mCurrentBucketStartTimeNs = nextBucketStartTimeNs; + VLOG("metric %lld: new bucket start time: %lld", (long long)mMetricId, + (long long)mCurrentBucketStartTimeNs); +} + +void ValueMetricProducer::appendToFullBucket(const bool isFullBucketReached) { + if (mCurrentBucketIsSkipped) { + if (isFullBucketReached) { + // If the bucket is invalid, we ignore the full bucket since it contains invalid data. + mCurrentFullBucket.clear(); + } + // Current bucket is invalid, we do not add it to the full bucket. + return; + } + + if (isFullBucketReached) { // If full bucket, send to anomaly tracker. + // Accumulate partial buckets with current value and then send to anomaly tracker. + if (mCurrentFullBucket.size() > 0) { + for (const auto& slice : mCurrentSlicedBucket) { + if (hitFullBucketGuardRailLocked(slice.first) || slice.second.intervals.empty()) { + continue; + } + // TODO: fix this when anomaly can accept double values + auto& interval = slice.second.intervals[0]; + if (interval.hasValue) { + mCurrentFullBucket[slice.first] += interval.value.long_value; + } + } + for (const auto& slice : mCurrentFullBucket) { + for (auto& tracker : mAnomalyTrackers) { + if (tracker != nullptr) { + tracker->addPastBucket(slice.first, slice.second, mCurrentBucketNum); + } + } + } + mCurrentFullBucket.clear(); + } else { + // Skip aggregating the partial buckets since there's no previous partial bucket. + for (const auto& slice : mCurrentSlicedBucket) { + for (auto& tracker : mAnomalyTrackers) { + if (tracker != nullptr && !slice.second.intervals.empty()) { + // TODO: fix this when anomaly can accept double values + auto& interval = slice.second.intervals[0]; + if (interval.hasValue) { + tracker->addPastBucket(slice.first, interval.value.long_value, + mCurrentBucketNum); + } + } + } + } + } + } else { + // Accumulate partial bucket. + for (const auto& slice : mCurrentSlicedBucket) { + if (!slice.second.intervals.empty()) { + // TODO: fix this when anomaly can accept double values + auto& interval = slice.second.intervals[0]; + if (interval.hasValue) { + mCurrentFullBucket[slice.first] += interval.value.long_value; + } + } + } + } +} + +size_t ValueMetricProducer::byteSizeLocked() const { + size_t totalSize = 0; + for (const auto& pair : mPastBuckets) { + totalSize += pair.second.size() * kBucketSize; + } + return totalSize; +} + +} // namespace statsd +} // namespace os +} // namespace android diff --git a/statsd/src/metrics/ValueMetricProducer.h b/statsd/src/metrics/ValueMetricProducer.h new file mode 100644 index 00000000..3da5c174 --- /dev/null +++ b/statsd/src/metrics/ValueMetricProducer.h @@ -0,0 +1,396 @@ +/* + * 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. + */ + +#pragma once + +#include +#include "anomaly/AnomalyTracker.h" +#include "condition/ConditionTimer.h" +#include "condition/ConditionTracker.h" +#include "external/PullDataReceiver.h" +#include "external/StatsPullerManager.h" +#include "matchers/EventMatcherWizard.h" +#include "stats_log_util.h" +#include "MetricProducer.h" +#include "src/statsd_config.pb.h" + +namespace android { +namespace os { +namespace statsd { + +struct PastValueBucket { + int64_t mBucketStartNs; + int64_t mBucketEndNs; + std::vector valueIndex; + std::vector values; + // If the metric has no condition, then this field is just wasted. + // When we tune statsd memory usage in the future, this is a candidate to optimize. + int64_t mConditionTrueNs; +}; + +// Aggregates values within buckets. +// +// There are different events that might complete a bucket +// - a condition change +// - an app upgrade +// - an alarm set to the end of the bucket +class ValueMetricProducer : public MetricProducer, public virtual PullDataReceiver { +public: + ValueMetricProducer( + const ConfigKey& key, const ValueMetric& valueMetric, const int conditionIndex, + const vector& initialConditionCache, + const sp& conditionWizard, const uint64_t protoHash, + const int whatMatcherIndex, const sp& matcherWizard, + const int pullTagId, const int64_t timeBaseNs, const int64_t startTimeNs, + const sp& pullerManager, + const std::unordered_map>& eventActivationMap = {}, + const std::unordered_map>>& + eventDeactivationMap = {}, + const vector& slicedStateAtoms = {}, + const unordered_map>& stateGroupMap = {}); + + virtual ~ValueMetricProducer(); + + // Process data pulled on bucket boundary. + void onDataPulled(const std::vector>& data, + bool pullSuccess, int64_t originalPullTimeNs) override; + + // ValueMetric needs special logic if it's a pulled atom. + void notifyAppUpgrade(const int64_t& eventTimeNs) override { + std::lock_guard lock(mMutex); + if (!mSplitBucketForAppUpgrade) { + return; + } + if (mIsPulled && mCondition == ConditionState::kTrue) { + pullAndMatchEventsLocked(eventTimeNs); + } + flushCurrentBucketLocked(eventTimeNs, eventTimeNs); + }; + + // ValueMetric needs special logic if it's a pulled atom. + void onStatsdInitCompleted(const int64_t& eventTimeNs) override { + std::lock_guard lock(mMutex); + if (mIsPulled && mCondition == ConditionState::kTrue) { + pullAndMatchEventsLocked(eventTimeNs); + } + flushCurrentBucketLocked(eventTimeNs, eventTimeNs); + }; + + void onStateChanged(int64_t eventTimeNs, int32_t atomId, const HashableDimensionKey& primaryKey, + const FieldValue& oldState, const FieldValue& newState) override; + + MetricType getMetricType() const override { + return METRIC_TYPE_VALUE; + } + +protected: + void onMatchedLogEventInternalLocked( + const size_t matcherIndex, const MetricDimensionKey& eventKey, + const ConditionKey& conditionKey, bool condition, const LogEvent& event, + const std::map& statePrimaryKeys) override; + +private: + void onDumpReportLocked(const int64_t dumpTimeNs, + const bool include_current_partial_bucket, + const bool erase_data, + const DumpLatency dumpLatency, + std::set *str_set, + android::util::ProtoOutputStream* protoOutput) override; + void clearPastBucketsLocked(const int64_t dumpTimeNs) override; + + // Internal interface to handle active state change. + void onActiveStateChangedLocked(const int64_t& eventTimeNs) override; + + // Internal interface to handle condition change. + void onConditionChangedLocked(const bool conditionMet, const int64_t eventTime) override; + + // Internal interface to handle sliced condition change. + void onSlicedConditionMayChangeLocked(bool overallCondition, const int64_t eventTime) override; + + // Internal function to calculate the current used bytes. + size_t byteSizeLocked() const override; + + void dumpStatesLocked(FILE* out, bool verbose) const override; + + // For pulled metrics, this method should only be called if a pull has be done. Else we will + // not have complete data for the bucket. + void flushIfNeededLocked(const int64_t& eventTime) override; + + // For pulled metrics, this method should only be called if a pulled have be done. Else we will + // not have complete data for the bucket. + void flushCurrentBucketLocked(const int64_t& eventTimeNs, + const int64_t& nextBucketStartTimeNs) override; + + void prepareFirstBucketLocked() override; + + void dropDataLocked(const int64_t dropTimeNs) override; + + // Calculate previous bucket end time based on current time. + int64_t calcPreviousBucketEndTime(const int64_t currentTimeNs); + + // Calculate how many buckets are present between the current bucket and eventTimeNs. + int64_t calcBucketsForwardCount(const int64_t& eventTimeNs) const; + + // Mark the data as invalid. + void invalidateCurrentBucket(const int64_t dropTimeNs, const BucketDropReason reason); + + void invalidateCurrentBucketWithoutResetBase(const int64_t dropTimeNs, + const BucketDropReason reason); + + // Skips the current bucket without notifying StatsdStats of the skipped bucket. + // This should only be called from #flushCurrentBucketLocked. Otherwise, a future event that + // causes the bucket to be invalidated will not notify StatsdStats. + void skipCurrentBucket(const int64_t dropTimeNs, const BucketDropReason reason); + + bool onConfigUpdatedLocked( + const StatsdConfig& config, const int configIndex, const int metricIndex, + const std::vector>& allAtomMatchingTrackers, + const std::unordered_map& oldAtomMatchingTrackerMap, + const std::unordered_map& newAtomMatchingTrackerMap, + const sp& matcherWizard, + const std::vector>& allConditionTrackers, + const std::unordered_map& conditionTrackerMap, + const sp& wizard, + const std::unordered_map& metricToActivationMap, + std::unordered_map>& trackerToMetricMap, + std::unordered_map>& conditionToMetricMap, + std::unordered_map>& activationAtomTrackerToMetricMap, + std::unordered_map>& deactivationAtomTrackerToMetricMap, + std::vector& metricsWithActivation) override; + + int mWhatMatcherIndex; + + sp mEventMatcherWizard; + + sp mPullerManager; + + // Value fields for matching. + std::vector mFieldMatchers; + + // Value fields for matching. + std::set mMatchedMetricDimensionKeys; + + // Holds the atom id, primary key pair from a state change. + pair mStateChangePrimaryKey; + + // tagId for pulled data. -1 if this is not pulled + const int mPullTagId; + + // if this is pulled metric + const bool mIsPulled; + + // Tracks the value information of one value field. + typedef struct { + // Index in multi value aggregation. + int valueIndex; + // Current value, depending on the aggregation type. + Value value; + // Number of samples collected. + int sampleSize; + // If this dimension has any non-tainted value. If not, don't report the + // dimension. + bool hasValue = false; + // Whether new data is seen in the bucket. + bool seenNewData = false; + } Interval; + + // Internal state of an ongoing aggregation bucket. + typedef struct CurrentValueBucket { + // If the `MetricDimensionKey` state key is the current state key, then + // the condition timer will be updated later (e.g. condition/state/active + // state change) with the correct condition and time. + CurrentValueBucket() : intervals(), conditionTimer(ConditionTimer(false, 0)) {} + // Value information for each value field of the metric. + std::vector intervals; + // Tracks how long the condition is true. + ConditionTimer conditionTimer; + } CurrentValueBucket; + + // Holds base information for diffing values from one value field. + typedef struct { + // Holds current base value of the dimension. Take diff and update if necessary. + Value base; + // Whether there is a base to diff to. + bool hasBase; + } BaseInfo; + + // State key and base information for a specific DimensionsInWhat key. + typedef struct DimensionsInWhatInfo { + DimensionsInWhatInfo(const HashableDimensionKey& stateKey) + : baseInfos(), currentState(stateKey), hasCurrentState(false) { + } + std::vector baseInfos; + // Last seen state value(s). + HashableDimensionKey currentState; + // Whether this dimensions in what key has a current state key. + bool hasCurrentState; + } DimensionsInWhatInfo; + + // Tracks the internal state in the ongoing aggregation bucket for each DimensionsInWhat + // key and StateValuesKey pair. + std::unordered_map mCurrentSlicedBucket; + + // Tracks current state key and base information for each DimensionsInWhat key. + std::unordered_map mCurrentBaseInfo; + + std::unordered_map mCurrentFullBucket; + + // Save the past buckets and we can clear when the StatsLogReport is dumped. + std::unordered_map> mPastBuckets; + + const int64_t mMinBucketSizeNs; + + // Util function to check whether the specified dimension hits the guardrail. + bool hitGuardRailLocked(const MetricDimensionKey& newKey); + + bool hasReachedGuardRailLimit() const; + + bool hitFullBucketGuardRailLocked(const MetricDimensionKey& newKey); + + void pullAndMatchEventsLocked(const int64_t timestampNs); + + bool multipleBucketsSkipped(const int64_t numBucketsForward); + + void accumulateEvents(const std::vector>& allData, + int64_t originalPullTimeNs, int64_t eventElapsedTimeNs); + + PastValueBucket buildPartialBucket(int64_t bucketEndTime, + const std::vector& intervals); + + void initCurrentSlicedBucket(int64_t nextBucketStartTimeNs); + + void appendToFullBucket(const bool isFullBucketReached); + + // Reset diff base and mHasGlobalBase + void resetBase(); + + // Updates the condition timers in the current sliced bucket when there is a + // condition change or an active state change. + void updateCurrentSlicedBucketConditionTimers(bool newCondition, int64_t eventTimeNs); + + static const size_t kBucketSize = sizeof(PastValueBucket{}); + + const size_t mDimensionSoftLimit; + + const size_t mDimensionHardLimit; + + const bool mUseAbsoluteValueOnReset; + + const ValueMetric::AggregationType mAggregationType; + + const bool mUseDiff; + + const ValueMetric::ValueDirection mValueDirection; + + const bool mSkipZeroDiffOutput; + + // If true, use a zero value as base to compute the diff. + // This is used for new keys which are present in the new data but was not + // present in the base data. + // The default base will only be used if we have a global base. + const bool mUseZeroDefaultBase; + + // For pulled metrics, this is always set to true whenever a pull succeeds. + // It is set to false when a pull fails, or upon condition change to false. + // This is used to decide if we have the right base data to compute the + // diff against. + bool mHasGlobalBase; + + // This is to track whether or not the bucket is skipped for any of the reasons listed in + // BucketDropReason, many of which make the bucket potentially invalid. + bool mCurrentBucketIsSkipped; + + const int64_t mMaxPullDelayNs; + + const bool mSplitBucketForAppUpgrade; + + ConditionTimer mConditionTimer; + + FRIEND_TEST(ValueMetricProducerTest, TestAnomalyDetection); + FRIEND_TEST(ValueMetricProducerTest, TestBaseSetOnConditionChange); + FRIEND_TEST(ValueMetricProducerTest, TestBucketBoundariesOnConditionChange); + FRIEND_TEST(ValueMetricProducerTest, TestBucketBoundaryNoCondition); + FRIEND_TEST(ValueMetricProducerTest, TestBucketBoundaryWithCondition); + FRIEND_TEST(ValueMetricProducerTest, TestBucketBoundaryWithCondition2); + FRIEND_TEST(ValueMetricProducerTest, TestBucketInvalidIfGlobalBaseIsNotSet); + FRIEND_TEST(ValueMetricProducerTest, TestCalcPreviousBucketEndTime); + FRIEND_TEST(ValueMetricProducerTest, TestDataIsNotUpdatedWhenNoConditionChanged); + FRIEND_TEST(ValueMetricProducerTest, TestEmptyDataResetsBase_onBucketBoundary); + FRIEND_TEST(ValueMetricProducerTest, TestEmptyDataResetsBase_onConditionChanged); + FRIEND_TEST(ValueMetricProducerTest, TestEmptyDataResetsBase_onDataPulled); + FRIEND_TEST(ValueMetricProducerTest, TestEventsWithNonSlicedCondition); + FRIEND_TEST(ValueMetricProducerTest, TestFirstBucket); + FRIEND_TEST(ValueMetricProducerTest, TestLateOnDataPulledWithDiff); + FRIEND_TEST(ValueMetricProducerTest, TestLateOnDataPulledWithoutDiff); + FRIEND_TEST(ValueMetricProducerTest, TestPartialResetOnBucketBoundaries); + FRIEND_TEST(ValueMetricProducerTest, TestPulledData_noDiff_bucketBoundaryFalse); + FRIEND_TEST(ValueMetricProducerTest, TestPulledData_noDiff_bucketBoundaryTrue); + FRIEND_TEST(ValueMetricProducerTest, TestPulledData_noDiff_withFailure); + FRIEND_TEST(ValueMetricProducerTest, TestPulledData_noDiff_withMultipleConditionChanges); + FRIEND_TEST(ValueMetricProducerTest, TestPulledData_noDiff_withoutCondition); + FRIEND_TEST(ValueMetricProducerTest, TestPulledEventsNoCondition); + FRIEND_TEST(ValueMetricProducerTest, TestPulledEventsTakeAbsoluteValueOnReset); + FRIEND_TEST(ValueMetricProducerTest, TestPulledEventsTakeZeroOnReset); + FRIEND_TEST(ValueMetricProducerTest, TestPulledEventsWithFiltering); + FRIEND_TEST(ValueMetricProducerTest, TestPulledWithAppUpgradeDisabled); + FRIEND_TEST(ValueMetricProducerTest, TestPushedAggregateAvg); + FRIEND_TEST(ValueMetricProducerTest, TestPushedAggregateMax); + FRIEND_TEST(ValueMetricProducerTest, TestPushedAggregateMin); + FRIEND_TEST(ValueMetricProducerTest, TestPushedAggregateSum); + FRIEND_TEST(ValueMetricProducerTest, TestPushedEventsWithCondition); + FRIEND_TEST(ValueMetricProducerTest, TestPushedEventsWithoutCondition); + FRIEND_TEST(ValueMetricProducerTest, TestResetBaseOnPullDelayExceeded); + FRIEND_TEST(ValueMetricProducerTest, TestResetBaseOnPullFailAfterConditionChange); + FRIEND_TEST(ValueMetricProducerTest, TestResetBaseOnPullFailAfterConditionChange_EndOfBucket); + FRIEND_TEST(ValueMetricProducerTest, TestResetBaseOnPullFailBeforeConditionChange); + FRIEND_TEST(ValueMetricProducerTest, TestResetBaseOnPullTooLate); + FRIEND_TEST(ValueMetricProducerTest, TestSkipZeroDiffOutput); + FRIEND_TEST(ValueMetricProducerTest, TestSkipZeroDiffOutputMultiValue); + FRIEND_TEST(ValueMetricProducerTest, TestSlicedState); + FRIEND_TEST(ValueMetricProducerTest, TestSlicedStateWithMap); + FRIEND_TEST(ValueMetricProducerTest, TestSlicedStateWithPrimaryField_WithDimensions); + FRIEND_TEST(ValueMetricProducerTest, TestSlicedStateWithCondition); + FRIEND_TEST(ValueMetricProducerTest, TestTrimUnusedDimensionKey); + FRIEND_TEST(ValueMetricProducerTest, TestUseZeroDefaultBase); + FRIEND_TEST(ValueMetricProducerTest, TestUseZeroDefaultBaseWithPullFailures); + FRIEND_TEST(ValueMetricProducerTest, TestSlicedStateWithMultipleDimensions); + FRIEND_TEST(ValueMetricProducerTest, TestSlicedStateWithMissingDataInStateChange); + FRIEND_TEST(ValueMetricProducerTest, TestSlicedStateWithDataMissingInConditionChange); + FRIEND_TEST(ValueMetricProducerTest, TestSlicedStateWithMissingDataThenFlushBucket); + FRIEND_TEST(ValueMetricProducerTest, TestSlicedStateWithNoPullOnBucketBoundary); + + FRIEND_TEST(ValueMetricProducerTest_BucketDrop, TestInvalidBucketWhenOneConditionFailed); + FRIEND_TEST(ValueMetricProducerTest_BucketDrop, TestInvalidBucketWhenInitialPullFailed); + FRIEND_TEST(ValueMetricProducerTest_BucketDrop, TestInvalidBucketWhenLastPullFailed); + FRIEND_TEST(ValueMetricProducerTest_BucketDrop, TestInvalidBucketWhenGuardRailHit); + FRIEND_TEST(ValueMetricProducerTest_BucketDrop, + TestInvalidBucketWhenAccumulateEventWrongBucket); + + FRIEND_TEST(ValueMetricProducerTest_PartialBucket, TestBucketBoundariesOnPartialBucket); + FRIEND_TEST(ValueMetricProducerTest_PartialBucket, TestFullBucketResetWhenLastBucketInvalid); + FRIEND_TEST(ValueMetricProducerTest_PartialBucket, TestPartialBucketCreated); + FRIEND_TEST(ValueMetricProducerTest_PartialBucket, TestPushedEvents); + FRIEND_TEST(ValueMetricProducerTest_PartialBucket, TestPulledValue); + FRIEND_TEST(ValueMetricProducerTest_PartialBucket, TestPulledValueWhileConditionFalse); + + FRIEND_TEST(ConfigUpdateTest, TestUpdateValueMetrics); + + friend class ValueMetricProducerTestHelper; +}; + +} // namespace statsd +} // namespace os +} // namespace android diff --git a/statsd/src/metrics/duration_helper/DurationTracker.h b/statsd/src/metrics/duration_helper/DurationTracker.h new file mode 100644 index 00000000..849effa1 --- /dev/null +++ b/statsd/src/metrics/duration_helper/DurationTracker.h @@ -0,0 +1,281 @@ +/* + * 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. + */ + +#ifndef DURATION_TRACKER_H +#define DURATION_TRACKER_H + +#include "anomaly/DurationAnomalyTracker.h" +#include "condition/ConditionWizard.h" +#include "config/ConfigKey.h" +#include "metrics/parsing_utils/config_update_utils.h" +#include "stats_util.h" + +namespace android { +namespace os { +namespace statsd { + +enum DurationState { + kStopped = 0, // The event is stopped. + kStarted = 1, // The event is on going. + kPaused = 2, // The event is started, but condition is false, clock is paused. When condition + // turns to true, kPaused will become kStarted. +}; + +// Hold duration information for one atom level duration in current on-going bucket. +struct DurationInfo { + DurationState state; + + // the number of starts seen. + int32_t startCount; + + // most recent start time. + int64_t lastStartTime; + // existing duration in current bucket. + int64_t lastDuration; + // cache the HashableDimensionKeys we need to query the condition for this duration event. + ConditionKey conditionKeys; + + DurationInfo() : state(kStopped), startCount(0), lastStartTime(0), lastDuration(0){}; +}; + +struct DurationBucket { + int64_t mBucketStartNs; + int64_t mBucketEndNs; + int64_t mDuration; +}; + +struct DurationValues { + // Recorded duration for current partial bucket. + int64_t mDuration; + + // Sum of past partial bucket durations in current full bucket. + // Used for anomaly detection. + int64_t mDurationFullBucket; +}; + +class DurationTracker { +public: + DurationTracker(const ConfigKey& key, const int64_t& id, const MetricDimensionKey& eventKey, + sp wizard, int conditionIndex, bool nesting, + int64_t currentBucketStartNs, int64_t currentBucketNum, int64_t startTimeNs, + int64_t bucketSizeNs, bool conditionSliced, bool fullLink, + const std::vector>& anomalyTrackers) + : mConfigKey(key), + mTrackerId(id), + mEventKey(eventKey), + mWizard(wizard), + mConditionTrackerIndex(conditionIndex), + mBucketSizeNs(bucketSizeNs), + mNested(nesting), + mCurrentBucketStartTimeNs(currentBucketStartNs), + mDuration(0), + mCurrentBucketNum(currentBucketNum), + mStartTimeNs(startTimeNs), + mConditionSliced(conditionSliced), + mHasLinksToAllConditionDimensionsInTracker(fullLink), + mAnomalyTrackers(anomalyTrackers){}; + + virtual ~DurationTracker(){}; + + void onConfigUpdated(const sp& wizard, const int conditionTrackerIndex) { + sp tmpWizard = mWizard; + mWizard = wizard; + mConditionTrackerIndex = conditionTrackerIndex; + mAnomalyTrackers.clear(); + }; + + virtual void noteStart(const HashableDimensionKey& key, bool condition, const int64_t eventTime, + const ConditionKey& conditionKey) = 0; + virtual void noteStop(const HashableDimensionKey& key, const int64_t eventTime, + const bool stopAll) = 0; + virtual void noteStopAll(const int64_t eventTime) = 0; + + virtual void onSlicedConditionMayChange(bool overallCondition, const int64_t timestamp) = 0; + virtual void onConditionChanged(bool condition, const int64_t timestamp) = 0; + + virtual void onStateChanged(const int64_t timestamp, const int32_t atomId, + const FieldValue& newState) = 0; + + // Flush stale buckets if needed, and return true if the tracker has no on-going duration + // events, so that the owner can safely remove the tracker. + virtual bool flushIfNeeded( + int64_t timestampNs, const optional& uploadThreshold, + std::unordered_map>* output) = 0; + + // Should only be called during an app upgrade or from this tracker's flushIfNeeded. If from + // an app upgrade, we assume that we're trying to form a partial bucket. + virtual bool flushCurrentBucket( + const int64_t& eventTimeNs, const optional& uploadThreshold, + std::unordered_map>* output) = 0; + + // Predict the anomaly timestamp given the current status. + virtual int64_t predictAnomalyTimestampNs(const AnomalyTracker& anomalyTracker, + const int64_t currentTimestamp) const = 0; + // Dump internal states for debugging + virtual void dumpStates(FILE* out, bool verbose) const = 0; + + virtual int64_t getCurrentStateKeyDuration() const = 0; + + virtual int64_t getCurrentStateKeyFullBucketDuration() const = 0; + + // Replace old value with new value for the given state atom. + virtual void updateCurrentStateKey(const int32_t atomId, const FieldValue& newState) = 0; + + void addAnomalyTracker(sp& anomalyTracker, const UpdateStatus& updateStatus, + const int64_t updateTimeNs) { + mAnomalyTrackers.push_back(anomalyTracker); + // Preserved anomaly trackers will have the correct alarm times. + // New/replaced alerts will need to set alarms for pending durations, or may have already + // fired if the full bucket duration is high enough. + // NB: this depends on a config updating that splits a partial bucket having just happened. + // If this constraint changes, predict will return the wrong timestamp. + if (updateStatus == UpdateStatus::UPDATE_NEW || + updateStatus == UpdateStatus::UPDATE_PRESERVE) { + const int64_t alarmTimeNs = predictAnomalyTimestampNs(*anomalyTracker, updateTimeNs); + if (alarmTimeNs <= updateTimeNs || hasAccumulatingDuration()) { + anomalyTracker->startAlarm(mEventKey, std::max(alarmTimeNs, updateTimeNs)); + } + } + } + +protected: + virtual bool hasAccumulatingDuration() = 0; + + int64_t getCurrentBucketEndTimeNs() const { + return mStartTimeNs + (mCurrentBucketNum + 1) * mBucketSizeNs; + } + + // Starts the anomaly alarm. + void startAnomalyAlarm(const int64_t eventTime) { + for (auto& anomalyTracker : mAnomalyTrackers) { + if (anomalyTracker != nullptr) { + const int64_t alarmTimestampNs = + predictAnomalyTimestampNs(*anomalyTracker, eventTime); + if (alarmTimestampNs > 0) { + anomalyTracker->startAlarm(mEventKey, alarmTimestampNs); + } + } + } + } + + // Stops the anomaly alarm. If it should have already fired, declare the anomaly now. + void stopAnomalyAlarm(const int64_t timestamp) { + for (auto& anomalyTracker : mAnomalyTrackers) { + if (anomalyTracker != nullptr) { + anomalyTracker->stopAlarm(mEventKey, timestamp); + } + } + } + + void addPastBucketToAnomalyTrackers(const MetricDimensionKey eventKey, + const int64_t& bucketValue, const int64_t& bucketNum) { + for (auto& anomalyTracker : mAnomalyTrackers) { + if (anomalyTracker != nullptr) { + anomalyTracker->addPastBucket(eventKey, bucketValue, bucketNum); + } + } + } + + void detectAndDeclareAnomaly(const int64_t& timestamp, const int64_t& currBucketNum, + const int64_t& currentBucketValue) { + for (auto& anomalyTracker : mAnomalyTrackers) { + if (anomalyTracker != nullptr) { + anomalyTracker->detectAndDeclareAnomaly(timestamp, currBucketNum, mTrackerId, + mEventKey, currentBucketValue); + } + } + } + + // Convenience to compute the current bucket's end time, which is always aligned with the + // start time of the metric. + int64_t getCurrentBucketEndTimeNs() { + return mStartTimeNs + (mCurrentBucketNum + 1) * mBucketSizeNs; + } + + void setEventKey(const MetricDimensionKey& eventKey) { + mEventKey = eventKey; + } + + bool durationPassesThreshold(const optional& uploadThreshold, + int64_t duration) { + if (duration <= 0) { + return false; + } + + if (uploadThreshold == nullopt) { + return true; + } + + switch (uploadThreshold->value_comparison_case()) { + case UploadThreshold::kLtInt: + return duration < uploadThreshold->lt_int(); + case UploadThreshold::kGtInt: + return duration > uploadThreshold->gt_int(); + case UploadThreshold::kLteInt: + return duration <= uploadThreshold->lte_int(); + case UploadThreshold::kGteInt: + return duration >= uploadThreshold->gte_int(); + default: + ALOGE("Duration metric incorrect upload threshold type used"); + return false; + } + } + + // A reference to the DurationMetricProducer's config key. + const ConfigKey& mConfigKey; + + const int64_t mTrackerId; + + MetricDimensionKey mEventKey; + + sp mWizard; + + int mConditionTrackerIndex; + + const int64_t mBucketSizeNs; + + const bool mNested; + + int64_t mCurrentBucketStartTimeNs; + + int64_t mDuration; // current recorded duration result (for partial bucket) + + // Recorded duration results for each state key in the current partial bucket. + std::unordered_map mStateKeyDurationMap; + + int64_t mCurrentBucketNum; + + const int64_t mStartTimeNs; + + const bool mConditionSliced; + + bool mHasLinksToAllConditionDimensionsInTracker; + + std::vector> mAnomalyTrackers; + + FRIEND_TEST(OringDurationTrackerTest, TestPredictAnomalyTimestamp); + FRIEND_TEST(OringDurationTrackerTest, TestAnomalyDetectionExpiredAlarm); + FRIEND_TEST(OringDurationTrackerTest, TestAnomalyDetectionFiredAlarm); + + FRIEND_TEST(ConfigUpdateTest, TestUpdateDurationMetrics); + FRIEND_TEST(ConfigUpdateTest, TestUpdateAlerts); +}; + +} // namespace statsd +} // namespace os +} // namespace android + +#endif // DURATION_TRACKER_H diff --git a/statsd/src/metrics/duration_helper/MaxDurationTracker.cpp b/statsd/src/metrics/duration_helper/MaxDurationTracker.cpp new file mode 100644 index 00000000..0ee5e46d --- /dev/null +++ b/statsd/src/metrics/duration_helper/MaxDurationTracker.cpp @@ -0,0 +1,334 @@ +/* + * 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. + */ + +#define DEBUG false + +#include "Log.h" +#include "MaxDurationTracker.h" +#include "guardrail/StatsdStats.h" + +namespace android { +namespace os { +namespace statsd { + +MaxDurationTracker::MaxDurationTracker(const ConfigKey& key, const int64_t& id, + const MetricDimensionKey& eventKey, + sp wizard, int conditionIndex, bool nesting, + int64_t currentBucketStartNs, int64_t currentBucketNum, + int64_t startTimeNs, int64_t bucketSizeNs, + bool conditionSliced, bool fullLink, + const vector>& anomalyTrackers) + : DurationTracker(key, id, eventKey, wizard, conditionIndex, nesting, currentBucketStartNs, + currentBucketNum, startTimeNs, bucketSizeNs, conditionSliced, fullLink, + anomalyTrackers) { +} + +bool MaxDurationTracker::hitGuardRail(const HashableDimensionKey& newKey) { + // ===========GuardRail============== + if (mInfos.find(newKey) != mInfos.end()) { + // if the key existed, we are good! + return false; + } + // 1. Report the tuple count if the tuple count > soft limit + if (mInfos.size() > StatsdStats::kDimensionKeySizeSoftLimit - 1) { + size_t newTupleCount = mInfos.size() + 1; + StatsdStats::getInstance().noteMetricDimensionSize(mConfigKey, mTrackerId, newTupleCount); + // 2. Don't add more tuples, we are above the allowed threshold. Drop the data. + if (newTupleCount > StatsdStats::kDimensionKeySizeHardLimit) { + ALOGE("MaxDurTracker %lld dropping data for dimension key %s", + (long long)mTrackerId, newKey.toString().c_str()); + return true; + } + } + return false; +} + +void MaxDurationTracker::noteStart(const HashableDimensionKey& key, bool condition, + const int64_t eventTime, const ConditionKey& conditionKey) { + // this will construct a new DurationInfo if this key didn't exist. + if (hitGuardRail(key)) { + return; + } + + DurationInfo& duration = mInfos[key]; + if (mConditionSliced) { + duration.conditionKeys = conditionKey; + } + VLOG("MaxDuration: key %s start condition %d", key.toString().c_str(), condition); + + switch (duration.state) { + case kStarted: + duration.startCount++; + break; + case kPaused: + duration.startCount++; + break; + case kStopped: + if (!condition) { + // event started, but we need to wait for the condition to become true. + duration.state = DurationState::kPaused; + } else { + duration.state = DurationState::kStarted; + duration.lastStartTime = eventTime; + startAnomalyAlarm(eventTime); + } + duration.startCount = 1; + break; + } +} + +void MaxDurationTracker::noteStop(const HashableDimensionKey& key, const int64_t eventTime, + bool forceStop) { + VLOG("MaxDuration: key %s stop", key.toString().c_str()); + if (mInfos.find(key) == mInfos.end()) { + // we didn't see a start event before. do nothing. + return; + } + DurationInfo& duration = mInfos[key]; + + switch (duration.state) { + case DurationState::kStopped: + // already stopped, do nothing. + break; + case DurationState::kStarted: { + duration.startCount--; + if (forceStop || !mNested || duration.startCount <= 0) { + stopAnomalyAlarm(eventTime); + duration.state = DurationState::kStopped; + int64_t durationTime = eventTime - duration.lastStartTime; + VLOG("Max, key %s, Stop %lld %lld %lld", key.toString().c_str(), + (long long)duration.lastStartTime, (long long)eventTime, + (long long)durationTime); + duration.lastDuration += durationTime; + if (hasAccumulatingDuration()) { + // In case any other dimensions are still started, we need to keep the alarm + // set. + startAnomalyAlarm(eventTime); + } + VLOG(" record duration: %lld ", (long long)duration.lastDuration); + } + break; + } + case DurationState::kPaused: { + duration.startCount--; + if (forceStop || !mNested || duration.startCount <= 0) { + duration.state = DurationState::kStopped; + } + break; + } + } + + if (duration.lastDuration > mDuration) { + mDuration = duration.lastDuration; + VLOG("Max: new max duration: %lld", (long long)mDuration); + } + // Once an atom duration ends, we erase it. Next time, if we see another atom event with the + // same name, they are still considered as different atom durations. + if (duration.state == DurationState::kStopped) { + mInfos.erase(key); + } +} + +bool MaxDurationTracker::hasAccumulatingDuration() { + for (auto& pair : mInfos) { + if (pair.second.state == kStarted) { + return true; + } + } + return false; +} + +void MaxDurationTracker::noteStopAll(const int64_t eventTime) { + std::set keys; + for (const auto& pair : mInfos) { + keys.insert(pair.first); + } + for (auto& key : keys) { + noteStop(key, eventTime, true); + } +} + +bool MaxDurationTracker::flushCurrentBucket( + const int64_t& eventTimeNs, const optional& uploadThreshold, + std::unordered_map>* output) { + VLOG("MaxDurationTracker flushing....."); + + // adjust the bucket start time + int numBucketsForward = 0; + int64_t fullBucketEnd = getCurrentBucketEndTimeNs(); + int64_t currentBucketEndTimeNs; + if (eventTimeNs >= fullBucketEnd) { + numBucketsForward = 1 + (eventTimeNs - fullBucketEnd) / mBucketSizeNs; + currentBucketEndTimeNs = fullBucketEnd; + } else { + // This must be a partial bucket. + currentBucketEndTimeNs = eventTimeNs; + } + + bool hasPendingEvent = + false; // has either a kStarted or kPaused event across bucket boundaries + // meaning we need to carry them over to the new bucket. + for (auto it = mInfos.begin(); it != mInfos.end();) { + if (it->second.state == DurationState::kStopped) { + // No need to keep buckets for events that were stopped before. + it = mInfos.erase(it); + } else { + ++it; + hasPendingEvent = true; + } + } + + // mDuration is updated in noteStop to the maximum duration that ended in the current bucket. + if (durationPassesThreshold(uploadThreshold, mDuration)) { + DurationBucket info; + info.mBucketStartNs = mCurrentBucketStartTimeNs; + info.mBucketEndNs = currentBucketEndTimeNs; + info.mDuration = mDuration; + (*output)[mEventKey].push_back(info); + VLOG(" final duration for last bucket: %lld", (long long)mDuration); + } else { + VLOG(" duration: %lld does not pass set threshold", (long long)mDuration); + } + + if (numBucketsForward > 0) { + mCurrentBucketStartTimeNs = fullBucketEnd + (numBucketsForward - 1) * mBucketSizeNs; + mCurrentBucketNum += numBucketsForward; + } else { // We must be forming a partial bucket. + mCurrentBucketStartTimeNs = eventTimeNs; + } + + mDuration = 0; + // If this tracker has no pending events, tell owner to remove. + return !hasPendingEvent; +} + +bool MaxDurationTracker::flushIfNeeded( + int64_t eventTimeNs, const optional& uploadThreshold, + unordered_map>* output) { + if (eventTimeNs < getCurrentBucketEndTimeNs()) { + return false; + } + return flushCurrentBucket(eventTimeNs, uploadThreshold, output); +} + +void MaxDurationTracker::onSlicedConditionMayChange(bool overallCondition, + const int64_t timestamp) { + // Now for each of the on-going event, check if the condition has changed for them. + for (auto& pair : mInfos) { + if (pair.second.state == kStopped) { + continue; + } + ConditionState conditionState = mWizard->query( + mConditionTrackerIndex, pair.second.conditionKeys, + !mHasLinksToAllConditionDimensionsInTracker); + bool conditionMet = (conditionState == ConditionState::kTrue); + + VLOG("key: %s, condition: %d", pair.first.toString().c_str(), conditionMet); + noteConditionChanged(pair.first, conditionMet, timestamp); + } +} + +void MaxDurationTracker::onStateChanged(const int64_t timestamp, const int32_t atomId, + const FieldValue& newState) { + ALOGE("MaxDurationTracker does not handle sliced state changes."); +} + +void MaxDurationTracker::onConditionChanged(bool condition, const int64_t timestamp) { + for (auto& pair : mInfos) { + noteConditionChanged(pair.first, condition, timestamp); + } +} + +void MaxDurationTracker::noteConditionChanged(const HashableDimensionKey& key, bool conditionMet, + const int64_t timestamp) { + auto it = mInfos.find(key); + if (it == mInfos.end()) { + return; + } + + switch (it->second.state) { + case kStarted: + // If condition becomes false, kStarted -> kPaused. Record the current duration and + // stop anomaly alarm. + if (!conditionMet) { + stopAnomalyAlarm(timestamp); + it->second.state = DurationState::kPaused; + it->second.lastDuration += (timestamp - it->second.lastStartTime); + if (hasAccumulatingDuration()) { + // In case any other dimensions are still started, we need to set the alarm. + startAnomalyAlarm(timestamp); + } + VLOG("MaxDurationTracker Key: %s Started->Paused ", key.toString().c_str()); + } + break; + case kStopped: + // Nothing to do if it's stopped. + break; + case kPaused: + // If condition becomes true, kPaused -> kStarted. and the start time is the condition + // change time. + if (conditionMet) { + it->second.state = DurationState::kStarted; + it->second.lastStartTime = timestamp; + startAnomalyAlarm(timestamp); + VLOG("MaxDurationTracker Key: %s Paused->Started", key.toString().c_str()); + } + break; + } + // Note that we don't update mDuration here since it's only updated during noteStop. +} + +int64_t MaxDurationTracker::predictAnomalyTimestampNs(const AnomalyTracker& anomalyTracker, + const int64_t currentTimestamp) const { + // The allowed time we can continue in the current state is the + // (anomaly threshold) - max(elapsed time of the started mInfos). + int64_t maxElapsed = 0; + for (auto it = mInfos.begin(); it != mInfos.end(); ++it) { + if (it->second.state == DurationState::kStarted) { + int64_t duration = + it->second.lastDuration + (currentTimestamp - it->second.lastStartTime); + if (duration > maxElapsed) { + maxElapsed = duration; + } + } + } + int64_t anomalyTimeNs = currentTimestamp + anomalyTracker.getAnomalyThreshold() - maxElapsed; + int64_t refractoryEndNs = anomalyTracker.getRefractoryPeriodEndsSec(mEventKey) * NS_PER_SEC; + return std::max(anomalyTimeNs, refractoryEndNs); +} + +void MaxDurationTracker::dumpStates(FILE* out, bool verbose) const { + fprintf(out, "\t\t sub-durations %lu\n", (unsigned long)mInfos.size()); + fprintf(out, "\t\t current duration %lld\n", (long long)mDuration); +} + +int64_t MaxDurationTracker::getCurrentStateKeyDuration() const { + ALOGE("MaxDurationTracker does not handle sliced state changes."); + return -1; +} + +int64_t MaxDurationTracker::getCurrentStateKeyFullBucketDuration() const { + ALOGE("MaxDurationTracker does not handle sliced state changes."); + return -1; +} + +void MaxDurationTracker::updateCurrentStateKey(const int32_t atomId, const FieldValue& newState) { + ALOGE("MaxDurationTracker does not handle sliced state changes."); +} + +} // namespace statsd +} // namespace os +} // namespace android diff --git a/statsd/src/metrics/duration_helper/MaxDurationTracker.h b/statsd/src/metrics/duration_helper/MaxDurationTracker.h new file mode 100644 index 00000000..b1fdd6e6 --- /dev/null +++ b/statsd/src/metrics/duration_helper/MaxDurationTracker.h @@ -0,0 +1,94 @@ +/* + * 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. + */ + +#ifndef MAX_DURATION_TRACKER_H +#define MAX_DURATION_TRACKER_H + +#include "DurationTracker.h" + +namespace android { +namespace os { +namespace statsd { + +// Tracks a pool of atom durations, and output the max duration for each bucket. +// To get max duration, we need to keep track of each individual durations, and compare them when +// they stop or bucket expires. +class MaxDurationTracker : public DurationTracker { +public: + MaxDurationTracker(const ConfigKey& key, const int64_t& id, const MetricDimensionKey& eventKey, + sp wizard, int conditionIndex, bool nesting, + int64_t currentBucketStartNs, int64_t currentBucketNum, int64_t startTimeNs, + int64_t bucketSizeNs, bool conditionSliced, bool fullLink, + const std::vector>& anomalyTrackers); + + MaxDurationTracker(const MaxDurationTracker& tracker) = default; + + void noteStart(const HashableDimensionKey& key, bool condition, const int64_t eventTime, + const ConditionKey& conditionKey) override; + void noteStop(const HashableDimensionKey& key, const int64_t eventTime, + const bool stopAll) override; + void noteStopAll(const int64_t eventTime) override; + + bool flushIfNeeded( + int64_t timestampNs, const optional& uploadThreshold, + std::unordered_map>* output) override; + bool flushCurrentBucket( + const int64_t& eventTimeNs, const optional& uploadThreshold, + std::unordered_map>*) override; + + void onSlicedConditionMayChange(bool overallCondition, const int64_t timestamp) override; + void onConditionChanged(bool condition, const int64_t timestamp) override; + + void onStateChanged(const int64_t timestamp, const int32_t atomId, + const FieldValue& newState) override; + + int64_t predictAnomalyTimestampNs(const AnomalyTracker& anomalyTracker, + const int64_t currentTimestamp) const override; + void dumpStates(FILE* out, bool verbose) const override; + + int64_t getCurrentStateKeyDuration() const override; + + int64_t getCurrentStateKeyFullBucketDuration() const override; + + void updateCurrentStateKey(const int32_t atomId, const FieldValue& newState); + +protected: + // Returns true if at least one of the mInfos is started. + bool hasAccumulatingDuration() override; + +private: + std::unordered_map mInfos; + + void noteConditionChanged(const HashableDimensionKey& key, bool conditionMet, + const int64_t timestamp); + + // return true if we should not allow newKey to be tracked because we are above the threshold + bool hitGuardRail(const HashableDimensionKey& newKey); + + FRIEND_TEST(MaxDurationTrackerTest, TestSimpleMaxDuration); + FRIEND_TEST(MaxDurationTrackerTest, TestCrossBucketBoundary); + FRIEND_TEST(MaxDurationTrackerTest, TestMaxDurationWithCondition); + FRIEND_TEST(MaxDurationTrackerTest, TestStopAll); + FRIEND_TEST(MaxDurationTrackerTest, TestAnomalyDetection); + FRIEND_TEST(MaxDurationTrackerTest, TestAnomalyPredictedTimestamp); + FRIEND_TEST(MaxDurationTrackerTest, TestUploadThreshold); +}; + +} // namespace statsd +} // namespace os +} // namespace android + +#endif // MAX_DURATION_TRACKER_H diff --git a/statsd/src/metrics/duration_helper/OringDurationTracker.cpp b/statsd/src/metrics/duration_helper/OringDurationTracker.cpp new file mode 100644 index 00000000..b67e25c4 --- /dev/null +++ b/statsd/src/metrics/duration_helper/OringDurationTracker.cpp @@ -0,0 +1,473 @@ +/* + * 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. + */ +#define DEBUG false +#include "Log.h" +#include "OringDurationTracker.h" +#include "guardrail/StatsdStats.h" + +namespace android { +namespace os { +namespace statsd { + +using std::pair; + +OringDurationTracker::OringDurationTracker( + const ConfigKey& key, const int64_t& id, const MetricDimensionKey& eventKey, + sp wizard, int conditionIndex, bool nesting, int64_t currentBucketStartNs, + int64_t currentBucketNum, int64_t startTimeNs, int64_t bucketSizeNs, bool conditionSliced, + bool fullLink, const vector>& anomalyTrackers) + : DurationTracker(key, id, eventKey, wizard, conditionIndex, nesting, currentBucketStartNs, + currentBucketNum, startTimeNs, bucketSizeNs, conditionSliced, fullLink, + anomalyTrackers), + mStarted(), + mPaused() { + mLastStartTime = 0; +} + +bool OringDurationTracker::hitGuardRail(const HashableDimensionKey& newKey) { + // ===========GuardRail============== + // 1. Report the tuple count if the tuple count > soft limit + if (mConditionKeyMap.find(newKey) != mConditionKeyMap.end()) { + return false; + } + if (mConditionKeyMap.size() > StatsdStats::kDimensionKeySizeSoftLimit - 1) { + size_t newTupleCount = mConditionKeyMap.size() + 1; + StatsdStats::getInstance().noteMetricDimensionSize(mConfigKey, mTrackerId, newTupleCount); + // 2. Don't add more tuples, we are above the allowed threshold. Drop the data. + if (newTupleCount > StatsdStats::kDimensionKeySizeHardLimit) { + ALOGE("OringDurTracker %lld dropping data for dimension key %s", + (long long)mTrackerId, newKey.toString().c_str()); + return true; + } + } + return false; +} + +void OringDurationTracker::noteStart(const HashableDimensionKey& key, bool condition, + const int64_t eventTime, const ConditionKey& conditionKey) { + if (hitGuardRail(key)) { + return; + } + if (condition) { + if (mStarted.size() == 0) { + mLastStartTime = eventTime; + VLOG("record first start...."); + startAnomalyAlarm(eventTime); + } + mStarted[key]++; + } else { + mPaused[key]++; + } + + if (mConditionSliced && mConditionKeyMap.find(key) == mConditionKeyMap.end()) { + mConditionKeyMap[key] = conditionKey; + } + VLOG("Oring: %s start, condition %d", key.toString().c_str(), condition); +} + +void OringDurationTracker::noteStop(const HashableDimensionKey& key, const int64_t timestamp, + const bool stopAll) { + VLOG("Oring: %s stop", key.toString().c_str()); + auto it = mStarted.find(key); + if (it != mStarted.end()) { + (it->second)--; + if (stopAll || !mNested || it->second <= 0) { + mStarted.erase(it); + mConditionKeyMap.erase(key); + } + if (mStarted.empty()) { + mStateKeyDurationMap[mEventKey.getStateValuesKey()].mDuration += + (timestamp - mLastStartTime); + detectAndDeclareAnomaly( + timestamp, mCurrentBucketNum, + getCurrentStateKeyDuration() + getCurrentStateKeyFullBucketDuration()); + VLOG("record duration %lld, total duration %lld for state key %s", + (long long)timestamp - mLastStartTime, (long long)getCurrentStateKeyDuration(), + mEventKey.getStateValuesKey().toString().c_str()); + } + } + + auto pausedIt = mPaused.find(key); + if (pausedIt != mPaused.end()) { + (pausedIt->second)--; + if (stopAll || !mNested || pausedIt->second <= 0) { + mPaused.erase(pausedIt); + mConditionKeyMap.erase(key); + } + } + if (mStarted.empty()) { + stopAnomalyAlarm(timestamp); + } +} + +void OringDurationTracker::noteStopAll(const int64_t timestamp) { + if (!mStarted.empty()) { + mStateKeyDurationMap[mEventKey.getStateValuesKey()].mDuration += + (timestamp - mLastStartTime); + VLOG("Oring Stop all: record duration %lld, total duration %lld for state key %s", + (long long)timestamp - mLastStartTime, (long long)getCurrentStateKeyDuration(), + mEventKey.getStateValuesKey().toString().c_str()); + detectAndDeclareAnomaly( + timestamp, mCurrentBucketNum, + getCurrentStateKeyDuration() + getCurrentStateKeyFullBucketDuration()); + } + + stopAnomalyAlarm(timestamp); + mStarted.clear(); + mPaused.clear(); + mConditionKeyMap.clear(); +} + +bool OringDurationTracker::flushCurrentBucket( + const int64_t& eventTimeNs, const optional& uploadThreshold, + std::unordered_map>* output) { + VLOG("OringDurationTracker Flushing............."); + + // Note that we have to mimic the bucket time changes we do in the + // MetricProducer#notifyAppUpgrade. + + int numBucketsForward = 0; + int64_t fullBucketEnd = getCurrentBucketEndTimeNs(); + int64_t currentBucketEndTimeNs; + + bool isFullBucket = eventTimeNs >= fullBucketEnd; + if (isFullBucket) { + numBucketsForward = 1 + (eventTimeNs - fullBucketEnd) / mBucketSizeNs; + currentBucketEndTimeNs = fullBucketEnd; + } else { + // This must be a partial bucket. + currentBucketEndTimeNs = eventTimeNs; + } + + // Process the current bucket. + if (mStarted.size() > 0) { + // Calculate the duration for the current state key. + mStateKeyDurationMap[mEventKey.getStateValuesKey()].mDuration += + (currentBucketEndTimeNs - mLastStartTime); + } + // Store DurationBucket info for each whatKey, stateKey pair. + // Note: The whatKey stored in mEventKey is constant for each DurationTracker, while the + // stateKey stored in mEventKey is only the current stateKey. mStateKeyDurationMap is used to + // store durations for each stateKey, so we need to flush the bucket by creating a + // DurationBucket for each stateKey. + for (auto& durationIt : mStateKeyDurationMap) { + if (durationPassesThreshold(uploadThreshold, durationIt.second.mDuration)) { + DurationBucket current_info; + current_info.mBucketStartNs = mCurrentBucketStartTimeNs; + current_info.mBucketEndNs = currentBucketEndTimeNs; + current_info.mDuration = durationIt.second.mDuration; + (*output)[MetricDimensionKey(mEventKey.getDimensionKeyInWhat(), durationIt.first)] + .push_back(current_info); + + durationIt.second.mDurationFullBucket += durationIt.second.mDuration; + VLOG(" duration: %lld", (long long)current_info.mDuration); + } else { + VLOG(" duration: %lld does not pass set threshold", (long long)mDuration); + } + + if (isFullBucket) { + // End of full bucket, can send to anomaly tracker now. + addPastBucketToAnomalyTrackers( + MetricDimensionKey(mEventKey.getDimensionKeyInWhat(), durationIt.first), + getCurrentStateKeyFullBucketDuration(), mCurrentBucketNum); + durationIt.second.mDurationFullBucket = 0; + } + durationIt.second.mDuration = 0; + } + + if (mStarted.size() > 0) { + for (int i = 1; i < numBucketsForward; i++) { + DurationBucket info; + info.mBucketStartNs = fullBucketEnd + mBucketSizeNs * (i - 1); + info.mBucketEndNs = info.mBucketStartNs + mBucketSizeNs; + info.mDuration = mBucketSizeNs; + // Full duration buckets are attributed to the current stateKey. + (*output)[mEventKey].push_back(info); + // Safe to send these buckets to anomaly tracker since they must be full buckets. + // If it's a partial bucket, numBucketsForward would be 0. + addPastBucketToAnomalyTrackers(mEventKey, info.mDuration, mCurrentBucketNum + i); + VLOG(" add filling bucket with duration %lld", (long long)info.mDuration); + } + } else { + if (numBucketsForward >= 2) { + addPastBucketToAnomalyTrackers(mEventKey, 0, mCurrentBucketNum + numBucketsForward - 1); + } + } + + if (numBucketsForward > 0) { + mCurrentBucketStartTimeNs = fullBucketEnd + (numBucketsForward - 1) * mBucketSizeNs; + mCurrentBucketNum += numBucketsForward; + } else { // We must be forming a partial bucket. + mCurrentBucketStartTimeNs = eventTimeNs; + } + mLastStartTime = mCurrentBucketStartTimeNs; + + // If all stopped, then tell owner it's safe to remove this tracker on a full bucket. + // On a partial bucket, only clear if no anomaly trackers, as full bucket duration is used + // for anomaly detection. + // Note: Anomaly trackers can be added on config updates, in which case mAnomalyTrackers > 0 and + // the full bucket duration could be used, but this is very rare so it is okay to clear. + return mStarted.empty() && mPaused.empty() && (isFullBucket || mAnomalyTrackers.size() == 0); +} + +bool OringDurationTracker::flushIfNeeded( + int64_t eventTimeNs, const optional& uploadThreshold, + unordered_map>* output) { + if (eventTimeNs < getCurrentBucketEndTimeNs()) { + return false; + } + return flushCurrentBucket(eventTimeNs, uploadThreshold, output); +} + +void OringDurationTracker::onSlicedConditionMayChange(bool overallCondition, + const int64_t timestamp) { + vector> startedToPaused; + vector> pausedToStarted; + if (!mStarted.empty()) { + for (auto it = mStarted.begin(); it != mStarted.end();) { + const auto& key = it->first; + const auto& condIt = mConditionKeyMap.find(key); + if (condIt == mConditionKeyMap.end()) { + VLOG("Key %s dont have condition key", key.toString().c_str()); + ++it; + continue; + } + ConditionState conditionState = + mWizard->query(mConditionTrackerIndex, condIt->second, + !mHasLinksToAllConditionDimensionsInTracker); + if (conditionState != ConditionState::kTrue) { + startedToPaused.push_back(*it); + it = mStarted.erase(it); + VLOG("Key %s started -> paused", key.toString().c_str()); + } else { + ++it; + } + } + + if (mStarted.empty()) { + mStateKeyDurationMap[mEventKey.getStateValuesKey()].mDuration += + (timestamp - mLastStartTime); + VLOG("record duration %lld, total duration %lld for state key %s", + (long long)(timestamp - mLastStartTime), (long long)getCurrentStateKeyDuration(), + mEventKey.getStateValuesKey().toString().c_str()); + detectAndDeclareAnomaly( + timestamp, mCurrentBucketNum, + getCurrentStateKeyDuration() + getCurrentStateKeyFullBucketDuration()); + } + } + + if (!mPaused.empty()) { + for (auto it = mPaused.begin(); it != mPaused.end();) { + const auto& key = it->first; + if (mConditionKeyMap.find(key) == mConditionKeyMap.end()) { + VLOG("Key %s dont have condition key", key.toString().c_str()); + ++it; + continue; + } + ConditionState conditionState = + mWizard->query(mConditionTrackerIndex, mConditionKeyMap[key], + !mHasLinksToAllConditionDimensionsInTracker); + if (conditionState == ConditionState::kTrue) { + pausedToStarted.push_back(*it); + it = mPaused.erase(it); + VLOG("Key %s paused -> started", key.toString().c_str()); + } else { + ++it; + } + } + + if (mStarted.empty() && pausedToStarted.size() > 0) { + mLastStartTime = timestamp; + } + } + + if (mStarted.empty() && !pausedToStarted.empty()) { + startAnomalyAlarm(timestamp); + } + mStarted.insert(pausedToStarted.begin(), pausedToStarted.end()); + mPaused.insert(startedToPaused.begin(), startedToPaused.end()); + + if (mStarted.empty()) { + stopAnomalyAlarm(timestamp); + } +} + +void OringDurationTracker::onConditionChanged(bool condition, const int64_t timestamp) { + if (condition) { + if (!mPaused.empty()) { + VLOG("Condition true, all started"); + if (mStarted.empty()) { + mLastStartTime = timestamp; + } + if (mStarted.empty() && !mPaused.empty()) { + startAnomalyAlarm(timestamp); + } + mStarted.insert(mPaused.begin(), mPaused.end()); + mPaused.clear(); + } + } else { + if (!mStarted.empty()) { + VLOG("Condition false, all paused"); + mStateKeyDurationMap[mEventKey.getStateValuesKey()].mDuration += + (timestamp - mLastStartTime); + mPaused.insert(mStarted.begin(), mStarted.end()); + mStarted.clear(); + detectAndDeclareAnomaly( + timestamp, mCurrentBucketNum, + getCurrentStateKeyDuration() + getCurrentStateKeyFullBucketDuration()); + } + } + if (mStarted.empty()) { + stopAnomalyAlarm(timestamp); + } +} + +void OringDurationTracker::onStateChanged(const int64_t timestamp, const int32_t atomId, + const FieldValue& newState) { + // Nothing needs to be done on a state change if we have not seen a start + // event, the metric is currently not active, or condition is false. + // For these cases, no keys are being tracked in mStarted, so update + // the current state key and return. + if (mStarted.empty()) { + updateCurrentStateKey(atomId, newState); + return; + } + // Add the current duration length to the previous state key and then update + // the last start time and current state key. + mStateKeyDurationMap[mEventKey.getStateValuesKey()].mDuration += (timestamp - mLastStartTime); + mLastStartTime = timestamp; + updateCurrentStateKey(atomId, newState); +} + +bool OringDurationTracker::hasAccumulatingDuration() { + return !mStarted.empty(); +} +int64_t OringDurationTracker::predictAnomalyTimestampNs(const AnomalyTracker& anomalyTracker, + const int64_t eventTimestampNs) const { + // The anomaly threshold. + const int64_t thresholdNs = anomalyTracker.getAnomalyThreshold(); + + // The timestamp of the current bucket end. + const int64_t currentBucketEndNs = getCurrentBucketEndTimeNs(); + + // The past duration ns for the current bucket of the current stateKey. + int64_t currentStateBucketPastNs = + getCurrentStateKeyDuration() + getCurrentStateKeyFullBucketDuration(); + + // As we move into the future, old buckets get overwritten (so their old data is erased). + // Sum of past durations. Will change as we overwrite old buckets. + int64_t pastNs = currentStateBucketPastNs + anomalyTracker.getSumOverPastBuckets(mEventKey); + + // The refractory period end timestamp for dimension mEventKey. + const int64_t refractoryPeriodEndNs = + anomalyTracker.getRefractoryPeriodEndsSec(mEventKey) * NS_PER_SEC; + + // The anomaly should happen when accumulated wakelock duration is above the threshold and + // not within the refractory period. + int64_t anomalyTimestampNs = + std::max(eventTimestampNs + thresholdNs - pastNs, refractoryPeriodEndNs); + // If the predicted the anomaly timestamp is within the current bucket, return it directly. + if (anomalyTimestampNs <= currentBucketEndNs) { + return std::max(eventTimestampNs, anomalyTimestampNs); + } + + // Remove the old bucket. + if (anomalyTracker.getNumOfPastBuckets() > 0) { + pastNs -= anomalyTracker.getPastBucketValue( + mEventKey, + mCurrentBucketNum - anomalyTracker.getNumOfPastBuckets()); + // Add the remaining of the current bucket to the accumulated wakelock duration. + pastNs += (currentBucketEndNs - eventTimestampNs); + } else { + // The anomaly depends on only one bucket. + pastNs = 0; + } + + // The anomaly will not happen in the current bucket. We need to iterate over the future buckets + // to predict the accumulated wakelock duration and determine the anomaly timestamp accordingly. + for (int futureBucketIdx = 1; futureBucketIdx <= anomalyTracker.getNumOfPastBuckets() + 1; + futureBucketIdx++) { + // The alarm candidate timestamp should meet two requirements: + // 1. the accumulated wakelock duration is above the threshold. + // 2. it is not within the refractory period. + // 3. the alarm timestamp falls in this bucket. Otherwise we need to flush the past buckets, + // find the new alarm candidate timestamp and check these requirements again. + const int64_t bucketEndNs = currentBucketEndNs + futureBucketIdx * mBucketSizeNs; + int64_t anomalyTimestampNs = + std::max(bucketEndNs - mBucketSizeNs + thresholdNs - pastNs, refractoryPeriodEndNs); + if (anomalyTimestampNs <= bucketEndNs) { + return anomalyTimestampNs; + } + if (anomalyTracker.getNumOfPastBuckets() <= 0) { + continue; + } + + // No valid alarm timestamp is found in this bucket. The clock moves to the end of the + // bucket. Update the pastNs. + pastNs += mBucketSizeNs; + // 1. If the oldest past bucket is still in the past bucket window, we could fetch the past + // bucket and erase it from pastNs. + // 2. If the oldest past bucket is the current bucket, we should compute the + // wakelock duration in the current bucket and erase it from pastNs. + // 3. Otherwise all othe past buckets are ancient. + if (futureBucketIdx < anomalyTracker.getNumOfPastBuckets()) { + pastNs -= anomalyTracker.getPastBucketValue( + mEventKey, + mCurrentBucketNum - anomalyTracker.getNumOfPastBuckets() + futureBucketIdx); + } else if (futureBucketIdx == anomalyTracker.getNumOfPastBuckets()) { + pastNs -= (currentStateBucketPastNs + (currentBucketEndNs - eventTimestampNs)); + } + } + + return std::max(eventTimestampNs + thresholdNs, refractoryPeriodEndNs); +} + +void OringDurationTracker::dumpStates(FILE* out, bool verbose) const { + fprintf(out, "\t\t started count %lu\n", (unsigned long)mStarted.size()); + fprintf(out, "\t\t paused count %lu\n", (unsigned long)mPaused.size()); + fprintf(out, "\t\t current duration %lld\n", (long long)getCurrentStateKeyDuration()); +} + +int64_t OringDurationTracker::getCurrentStateKeyDuration() const { + auto it = mStateKeyDurationMap.find(mEventKey.getStateValuesKey()); + if (it == mStateKeyDurationMap.end()) { + return 0; + } else { + return it->second.mDuration; + } +} + +int64_t OringDurationTracker::getCurrentStateKeyFullBucketDuration() const { + auto it = mStateKeyDurationMap.find(mEventKey.getStateValuesKey()); + if (it == mStateKeyDurationMap.end()) { + return 0; + } else { + return it->second.mDurationFullBucket; + } +} + +void OringDurationTracker::updateCurrentStateKey(const int32_t atomId, const FieldValue& newState) { + HashableDimensionKey* stateValuesKey = mEventKey.getMutableStateValuesKey(); + for (size_t i = 0; i < stateValuesKey->getValues().size(); i++) { + if (stateValuesKey->getValues()[i].mField.getTag() == atomId) { + stateValuesKey->mutableValue(i)->mValue = newState.mValue; + } + } +} + +} // namespace statsd +} // namespace os +} // namespace android diff --git a/statsd/src/metrics/duration_helper/OringDurationTracker.h b/statsd/src/metrics/duration_helper/OringDurationTracker.h new file mode 100644 index 00000000..8f8d5355 --- /dev/null +++ b/statsd/src/metrics/duration_helper/OringDurationTracker.h @@ -0,0 +1,98 @@ +/* + * 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. + */ + +#ifndef ORING_DURATION_TRACKER_H +#define ORING_DURATION_TRACKER_H + +#include "DurationTracker.h" + +namespace android { +namespace os { +namespace statsd { + +// Tracks the "Or'd" duration -- if 2 durations are overlapping, they won't be double counted. +class OringDurationTracker : public DurationTracker { +public: + OringDurationTracker(const ConfigKey& key, const int64_t& id, + const MetricDimensionKey& eventKey, sp wizard, + int conditionIndex, bool nesting, int64_t currentBucketStartNs, + int64_t currentBucketNum, int64_t startTimeNs, int64_t bucketSizeNs, + bool conditionSliced, bool fullLink, + const std::vector>& anomalyTrackers); + + OringDurationTracker(const OringDurationTracker& tracker) = default; + + void noteStart(const HashableDimensionKey& key, bool condition, const int64_t eventTime, + const ConditionKey& conditionKey) override; + void noteStop(const HashableDimensionKey& key, const int64_t eventTime, + const bool stopAll) override; + void noteStopAll(const int64_t eventTime) override; + + void onSlicedConditionMayChange(bool overallCondition, const int64_t timestamp) override; + void onConditionChanged(bool condition, const int64_t timestamp) override; + + void onStateChanged(const int64_t timestamp, const int32_t atomId, + const FieldValue& newState) override; + + bool flushCurrentBucket( + const int64_t& eventTimeNs, const optional& uploadThreshold, + std::unordered_map>* output) override; + bool flushIfNeeded( + int64_t timestampNs, const optional& uploadThreshold, + std::unordered_map>* output) override; + + int64_t predictAnomalyTimestampNs(const AnomalyTracker& anomalyTracker, + const int64_t currentTimestamp) const override; + void dumpStates(FILE* out, bool verbose) const override; + + int64_t getCurrentStateKeyDuration() const override; + + int64_t getCurrentStateKeyFullBucketDuration() const override; + + void updateCurrentStateKey(const int32_t atomId, const FieldValue& newState); + +protected: + // Returns true if at least one of the mInfos is started. + bool hasAccumulatingDuration() override; + +private: + // We don't need to keep track of individual durations. The information that's needed is: + // 1) which keys are started. We record the first start time. + // 2) which keys are paused (started but condition was false) + // 3) whenever a key stops, we remove it from the started set. And if the set becomes empty, + // it means everything has stopped, we then record the end time. + std::unordered_map mStarted; + std::unordered_map mPaused; + int64_t mLastStartTime; + std::unordered_map mConditionKeyMap; + + // return true if we should not allow newKey to be tracked because we are above the threshold + bool hitGuardRail(const HashableDimensionKey& newKey); + + FRIEND_TEST(OringDurationTrackerTest, TestDurationOverlap); + FRIEND_TEST(OringDurationTrackerTest, TestCrossBucketBoundary); + FRIEND_TEST(OringDurationTrackerTest, TestDurationConditionChange); + FRIEND_TEST(OringDurationTrackerTest, TestPredictAnomalyTimestamp); + FRIEND_TEST(OringDurationTrackerTest, TestAnomalyDetectionExpiredAlarm); + FRIEND_TEST(OringDurationTrackerTest, TestAnomalyDetectionFiredAlarm); + FRIEND_TEST(OringDurationTrackerTest, TestUploadThreshold); +}; + +} // namespace statsd +} // namespace os +} // namespace android + +#endif // ORING_DURATION_TRACKER_H diff --git a/statsd/src/metrics/parsing_utils/config_update_utils.cpp b/statsd/src/metrics/parsing_utils/config_update_utils.cpp new file mode 100644 index 00000000..b8c505ee --- /dev/null +++ b/statsd/src/metrics/parsing_utils/config_update_utils.cpp @@ -0,0 +1,1121 @@ +/* + * Copyright (C) 2020 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. + */ + +#define DEBUG false // STOPSHIP if true +#include "Log.h" + +#include "config_update_utils.h" + +#include "external/StatsPullerManager.h" +#include "hash.h" +#include "matchers/EventMatcherWizard.h" +#include "metrics_manager_util.h" + +using google::protobuf::MessageLite; + +namespace android { +namespace os { +namespace statsd { + +// Recursive function to determine if a matcher needs to be updated. Populates matcherToUpdate. +// Returns whether the function was successful or not. +bool determineMatcherUpdateStatus(const StatsdConfig& config, const int matcherIdx, + const unordered_map& oldAtomMatchingTrackerMap, + const vector>& oldAtomMatchingTrackers, + const unordered_map& newAtomMatchingTrackerMap, + vector& matchersToUpdate, + vector& cycleTracker) { + // Have already examined this matcher. + if (matchersToUpdate[matcherIdx] != UPDATE_UNKNOWN) { + return true; + } + + const AtomMatcher& matcher = config.atom_matcher(matcherIdx); + int64_t id = matcher.id(); + // Check if new matcher. + const auto& oldAtomMatchingTrackerIt = oldAtomMatchingTrackerMap.find(id); + if (oldAtomMatchingTrackerIt == oldAtomMatchingTrackerMap.end()) { + matchersToUpdate[matcherIdx] = UPDATE_NEW; + return true; + } + + // This is an existing matcher. Check if it has changed. + string serializedMatcher; + if (!matcher.SerializeToString(&serializedMatcher)) { + ALOGE("Unable to serialize matcher %lld", (long long)id); + return false; + } + uint64_t newProtoHash = Hash64(serializedMatcher); + if (newProtoHash != oldAtomMatchingTrackers[oldAtomMatchingTrackerIt->second]->getProtoHash()) { + matchersToUpdate[matcherIdx] = UPDATE_REPLACE; + return true; + } + + switch (matcher.contents_case()) { + case AtomMatcher::ContentsCase::kSimpleAtomMatcher: { + matchersToUpdate[matcherIdx] = UPDATE_PRESERVE; + return true; + } + case AtomMatcher::ContentsCase::kCombination: { + // Recurse to check if children have changed. + cycleTracker[matcherIdx] = true; + UpdateStatus status = UPDATE_PRESERVE; + for (const int64_t childMatcherId : matcher.combination().matcher()) { + const auto& childIt = newAtomMatchingTrackerMap.find(childMatcherId); + if (childIt == newAtomMatchingTrackerMap.end()) { + ALOGW("Matcher %lld not found in the config", (long long)childMatcherId); + return false; + } + const int childIdx = childIt->second; + if (cycleTracker[childIdx]) { + ALOGE("Cycle detected in matcher config"); + return false; + } + if (!determineMatcherUpdateStatus( + config, childIdx, oldAtomMatchingTrackerMap, oldAtomMatchingTrackers, + newAtomMatchingTrackerMap, matchersToUpdate, cycleTracker)) { + return false; + } + + if (matchersToUpdate[childIdx] == UPDATE_REPLACE) { + status = UPDATE_REPLACE; + break; + } + } + matchersToUpdate[matcherIdx] = status; + cycleTracker[matcherIdx] = false; + return true; + } + default: { + ALOGE("Matcher \"%lld\" malformed", (long long)id); + return false; + } + } + return true; +} + +bool updateAtomMatchingTrackers(const StatsdConfig& config, const sp& uidMap, + const unordered_map& oldAtomMatchingTrackerMap, + const vector>& oldAtomMatchingTrackers, + set& allTagIds, + unordered_map& newAtomMatchingTrackerMap, + vector>& newAtomMatchingTrackers, + set& replacedMatchers) { + const int atomMatcherCount = config.atom_matcher_size(); + vector matcherProtos; + matcherProtos.reserve(atomMatcherCount); + newAtomMatchingTrackers.reserve(atomMatcherCount); + + // Maps matcher id to their position in the config. For fast lookup of dependencies. + for (int i = 0; i < atomMatcherCount; i++) { + const AtomMatcher& matcher = config.atom_matcher(i); + if (newAtomMatchingTrackerMap.find(matcher.id()) != newAtomMatchingTrackerMap.end()) { + ALOGE("Duplicate atom matcher found for id %lld", (long long)matcher.id()); + return false; + } + newAtomMatchingTrackerMap[matcher.id()] = i; + matcherProtos.push_back(matcher); + } + + // For combination matchers, we need to determine if any children need to be updated. + vector matchersToUpdate(atomMatcherCount, UPDATE_UNKNOWN); + vector cycleTracker(atomMatcherCount, false); + for (int i = 0; i < atomMatcherCount; i++) { + if (!determineMatcherUpdateStatus(config, i, oldAtomMatchingTrackerMap, + oldAtomMatchingTrackers, newAtomMatchingTrackerMap, + matchersToUpdate, cycleTracker)) { + return false; + } + } + + for (int i = 0; i < atomMatcherCount; i++) { + const AtomMatcher& matcher = config.atom_matcher(i); + const int64_t id = matcher.id(); + switch (matchersToUpdate[i]) { + case UPDATE_PRESERVE: { + const auto& oldAtomMatchingTrackerIt = oldAtomMatchingTrackerMap.find(id); + if (oldAtomMatchingTrackerIt == oldAtomMatchingTrackerMap.end()) { + ALOGE("Could not find AtomMatcher %lld in the previous config, but expected it " + "to be there", + (long long)id); + return false; + } + const sp& tracker = + oldAtomMatchingTrackers[oldAtomMatchingTrackerIt->second]; + if (!tracker->onConfigUpdated(matcherProtos[i], i, newAtomMatchingTrackerMap)) { + ALOGW("Config update failed for matcher %lld", (long long)id); + return false; + } + newAtomMatchingTrackers.push_back(tracker); + break; + } + case UPDATE_REPLACE: + replacedMatchers.insert(id); + [[fallthrough]]; // Intentionally fallthrough to create the new matcher. + case UPDATE_NEW: { + sp tracker = createAtomMatchingTracker(matcher, i, uidMap); + if (tracker == nullptr) { + return false; + } + newAtomMatchingTrackers.push_back(tracker); + break; + } + default: { + ALOGE("Matcher \"%lld\" update state is unknown. This should never happen", + (long long)id); + return false; + } + } + } + + std::fill(cycleTracker.begin(), cycleTracker.end(), false); + for (auto& matcher : newAtomMatchingTrackers) { + if (!matcher->init(matcherProtos, newAtomMatchingTrackers, newAtomMatchingTrackerMap, + cycleTracker)) { + return false; + } + // Collect all the tag ids that are interesting. TagIds exist in leaf nodes only. + const set& tagIds = matcher->getAtomIds(); + allTagIds.insert(tagIds.begin(), tagIds.end()); + } + + return true; +} + +// Recursive function to determine if a condition needs to be updated. Populates conditionsToUpdate. +// Returns whether the function was successful or not. +bool determineConditionUpdateStatus(const StatsdConfig& config, const int conditionIdx, + const unordered_map& oldConditionTrackerMap, + const vector>& oldConditionTrackers, + const unordered_map& newConditionTrackerMap, + const set& replacedMatchers, + vector& conditionsToUpdate, + vector& cycleTracker) { + // Have already examined this condition. + if (conditionsToUpdate[conditionIdx] != UPDATE_UNKNOWN) { + return true; + } + + const Predicate& predicate = config.predicate(conditionIdx); + int64_t id = predicate.id(); + // Check if new condition. + const auto& oldConditionTrackerIt = oldConditionTrackerMap.find(id); + if (oldConditionTrackerIt == oldConditionTrackerMap.end()) { + conditionsToUpdate[conditionIdx] = UPDATE_NEW; + return true; + } + + // This is an existing condition. Check if it has changed. + string serializedCondition; + if (!predicate.SerializeToString(&serializedCondition)) { + ALOGE("Unable to serialize matcher %lld", (long long)id); + return false; + } + uint64_t newProtoHash = Hash64(serializedCondition); + if (newProtoHash != oldConditionTrackers[oldConditionTrackerIt->second]->getProtoHash()) { + conditionsToUpdate[conditionIdx] = UPDATE_REPLACE; + return true; + } + + switch (predicate.contents_case()) { + case Predicate::ContentsCase::kSimplePredicate: { + // Need to check if any of the underlying matchers changed. + const SimplePredicate& simplePredicate = predicate.simple_predicate(); + if (simplePredicate.has_start()) { + if (replacedMatchers.find(simplePredicate.start()) != replacedMatchers.end()) { + conditionsToUpdate[conditionIdx] = UPDATE_REPLACE; + return true; + } + } + if (simplePredicate.has_stop()) { + if (replacedMatchers.find(simplePredicate.stop()) != replacedMatchers.end()) { + conditionsToUpdate[conditionIdx] = UPDATE_REPLACE; + return true; + } + } + if (simplePredicate.has_stop_all()) { + if (replacedMatchers.find(simplePredicate.stop_all()) != replacedMatchers.end()) { + conditionsToUpdate[conditionIdx] = UPDATE_REPLACE; + return true; + } + } + conditionsToUpdate[conditionIdx] = UPDATE_PRESERVE; + return true; + } + case Predicate::ContentsCase::kCombination: { + // Need to recurse on the children to see if any of the child predicates changed. + cycleTracker[conditionIdx] = true; + UpdateStatus status = UPDATE_PRESERVE; + for (const int64_t childPredicateId : predicate.combination().predicate()) { + const auto& childIt = newConditionTrackerMap.find(childPredicateId); + if (childIt == newConditionTrackerMap.end()) { + ALOGW("Predicate %lld not found in the config", (long long)childPredicateId); + return false; + } + const int childIdx = childIt->second; + if (cycleTracker[childIdx]) { + ALOGE("Cycle detected in predicate config"); + return false; + } + if (!determineConditionUpdateStatus(config, childIdx, oldConditionTrackerMap, + oldConditionTrackers, newConditionTrackerMap, + replacedMatchers, conditionsToUpdate, + cycleTracker)) { + return false; + } + + if (conditionsToUpdate[childIdx] == UPDATE_REPLACE) { + status = UPDATE_REPLACE; + break; + } + } + conditionsToUpdate[conditionIdx] = status; + cycleTracker[conditionIdx] = false; + return true; + } + default: { + ALOGE("Predicate \"%lld\" malformed", (long long)id); + return false; + } + } + + return true; +} + +bool updateConditions(const ConfigKey& key, const StatsdConfig& config, + const unordered_map& atomMatchingTrackerMap, + const set& replacedMatchers, + const unordered_map& oldConditionTrackerMap, + const vector>& oldConditionTrackers, + unordered_map& newConditionTrackerMap, + vector>& newConditionTrackers, + unordered_map>& trackerToConditionMap, + vector& conditionCache, set& replacedConditions) { + vector conditionProtos; + const int conditionTrackerCount = config.predicate_size(); + conditionProtos.reserve(conditionTrackerCount); + newConditionTrackers.reserve(conditionTrackerCount); + conditionCache.assign(conditionTrackerCount, ConditionState::kNotEvaluated); + + for (int i = 0; i < conditionTrackerCount; i++) { + const Predicate& condition = config.predicate(i); + if (newConditionTrackerMap.find(condition.id()) != newConditionTrackerMap.end()) { + ALOGE("Duplicate Predicate found!"); + return false; + } + newConditionTrackerMap[condition.id()] = i; + conditionProtos.push_back(condition); + } + + vector conditionsToUpdate(conditionTrackerCount, UPDATE_UNKNOWN); + vector cycleTracker(conditionTrackerCount, false); + for (int i = 0; i < conditionTrackerCount; i++) { + if (!determineConditionUpdateStatus(config, i, oldConditionTrackerMap, oldConditionTrackers, + newConditionTrackerMap, replacedMatchers, + conditionsToUpdate, cycleTracker)) { + return false; + } + } + + // Update status has been determined for all conditions. Now perform the update. + set preservedConditions; + for (int i = 0; i < conditionTrackerCount; i++) { + const Predicate& predicate = config.predicate(i); + const int64_t id = predicate.id(); + switch (conditionsToUpdate[i]) { + case UPDATE_PRESERVE: { + preservedConditions.insert(i); + const auto& oldConditionTrackerIt = oldConditionTrackerMap.find(id); + if (oldConditionTrackerIt == oldConditionTrackerMap.end()) { + ALOGE("Could not find Predicate %lld in the previous config, but expected it " + "to be there", + (long long)id); + return false; + } + const int oldIndex = oldConditionTrackerIt->second; + newConditionTrackers.push_back(oldConditionTrackers[oldIndex]); + break; + } + case UPDATE_REPLACE: + replacedConditions.insert(id); + [[fallthrough]]; // Intentionally fallthrough to create the new condition tracker. + case UPDATE_NEW: { + sp tracker = + createConditionTracker(key, predicate, i, atomMatchingTrackerMap); + if (tracker == nullptr) { + return false; + } + newConditionTrackers.push_back(tracker); + break; + } + default: { + ALOGE("Condition \"%lld\" update state is unknown. This should never happen", + (long long)id); + return false; + } + } + } + + // Update indices of preserved predicates. + for (const int conditionIndex : preservedConditions) { + if (!newConditionTrackers[conditionIndex]->onConfigUpdated( + conditionProtos, conditionIndex, newConditionTrackers, atomMatchingTrackerMap, + newConditionTrackerMap)) { + ALOGE("Failed to update condition %lld", + (long long)newConditionTrackers[conditionIndex]->getConditionId()); + return false; + } + } + + std::fill(cycleTracker.begin(), cycleTracker.end(), false); + for (int conditionIndex = 0; conditionIndex < conditionTrackerCount; conditionIndex++) { + const sp& conditionTracker = newConditionTrackers[conditionIndex]; + // Calling init on preserved conditions is OK. It is needed to fill the condition cache. + if (!conditionTracker->init(conditionProtos, newConditionTrackers, newConditionTrackerMap, + cycleTracker, conditionCache)) { + return false; + } + for (const int trackerIndex : conditionTracker->getAtomMatchingTrackerIndex()) { + vector& conditionList = trackerToConditionMap[trackerIndex]; + conditionList.push_back(conditionIndex); + } + } + return true; +} + +bool updateStates(const StatsdConfig& config, const map& oldStateProtoHashes, + unordered_map& stateAtomIdMap, + unordered_map>& allStateGroupMaps, + map& newStateProtoHashes, set& replacedStates) { + // Share with metrics_manager_util. + if (!initStates(config, stateAtomIdMap, allStateGroupMaps, newStateProtoHashes)) { + return false; + } + + for (const auto& [stateId, stateHash] : oldStateProtoHashes) { + const auto& it = newStateProtoHashes.find(stateId); + if (it != newStateProtoHashes.end() && it->second != stateHash) { + replacedStates.insert(stateId); + } + } + return true; +} +// Returns true if any matchers in the metric activation were replaced. +bool metricActivationDepsChange(const StatsdConfig& config, + const unordered_map& metricToActivationMap, + const int64_t metricId, const set& replacedMatchers) { + const auto& metricActivationIt = metricToActivationMap.find(metricId); + if (metricActivationIt == metricToActivationMap.end()) { + return false; + } + const MetricActivation& metricActivation = config.metric_activation(metricActivationIt->second); + for (int i = 0; i < metricActivation.event_activation_size(); i++) { + const EventActivation& activation = metricActivation.event_activation(i); + if (replacedMatchers.find(activation.atom_matcher_id()) != replacedMatchers.end()) { + return true; + } + if (activation.has_deactivation_atom_matcher_id()) { + if (replacedMatchers.find(activation.deactivation_atom_matcher_id()) != + replacedMatchers.end()) { + return true; + } + } + } + return false; +} + +bool determineMetricUpdateStatus( + const StatsdConfig& config, const MessageLite& metric, const int64_t metricId, + const MetricType metricType, const set& matcherDependencies, + const set& conditionDependencies, + const ::google::protobuf::RepeatedField& stateDependencies, + const ::google::protobuf::RepeatedPtrField& conditionLinks, + const unordered_map& oldMetricProducerMap, + const vector>& oldMetricProducers, + const unordered_map& metricToActivationMap, + const set& replacedMatchers, const set& replacedConditions, + const set& replacedStates, UpdateStatus& updateStatus) { + // Check if new metric + const auto& oldMetricProducerIt = oldMetricProducerMap.find(metricId); + if (oldMetricProducerIt == oldMetricProducerMap.end()) { + updateStatus = UPDATE_NEW; + return true; + } + + // This is an existing metric, check if it has changed. + uint64_t metricHash; + if (!getMetricProtoHash(config, metric, metricId, metricToActivationMap, metricHash)) { + return false; + } + const sp oldMetricProducer = oldMetricProducers[oldMetricProducerIt->second]; + if (oldMetricProducer->getMetricType() != metricType || + oldMetricProducer->getProtoHash() != metricHash) { + updateStatus = UPDATE_REPLACE; + return true; + } + + // Take intersections of the matchers/predicates/states that the metric + // depends on with those that have been replaced. If a metric depends on any + // replaced component, it too must be replaced. + set intersection; + set_intersection(matcherDependencies.begin(), matcherDependencies.end(), + replacedMatchers.begin(), replacedMatchers.end(), + inserter(intersection, intersection.begin())); + if (intersection.size() > 0) { + updateStatus = UPDATE_REPLACE; + return true; + } + set_intersection(conditionDependencies.begin(), conditionDependencies.end(), + replacedConditions.begin(), replacedConditions.end(), + inserter(intersection, intersection.begin())); + if (intersection.size() > 0) { + updateStatus = UPDATE_REPLACE; + return true; + } + set_intersection(stateDependencies.begin(), stateDependencies.end(), replacedStates.begin(), + replacedStates.end(), inserter(intersection, intersection.begin())); + if (intersection.size() > 0) { + updateStatus = UPDATE_REPLACE; + return true; + } + + for (const auto& metricConditionLink : conditionLinks) { + if (replacedConditions.find(metricConditionLink.condition()) != replacedConditions.end()) { + updateStatus = UPDATE_REPLACE; + return true; + } + } + + if (metricActivationDepsChange(config, metricToActivationMap, metricId, replacedMatchers)) { + updateStatus = UPDATE_REPLACE; + return true; + } + + updateStatus = UPDATE_PRESERVE; + return true; +} + +bool determineAllMetricUpdateStatuses(const StatsdConfig& config, + const unordered_map& oldMetricProducerMap, + const vector>& oldMetricProducers, + const unordered_map& metricToActivationMap, + const set& replacedMatchers, + const set& replacedConditions, + const set& replacedStates, + vector& metricsToUpdate) { + int metricIndex = 0; + for (int i = 0; i < config.count_metric_size(); i++, metricIndex++) { + const CountMetric& metric = config.count_metric(i); + set conditionDependencies; + if (metric.has_condition()) { + conditionDependencies.insert(metric.condition()); + } + if (!determineMetricUpdateStatus( + config, metric, metric.id(), METRIC_TYPE_COUNT, {metric.what()}, + conditionDependencies, metric.slice_by_state(), metric.links(), + oldMetricProducerMap, oldMetricProducers, metricToActivationMap, + replacedMatchers, replacedConditions, replacedStates, + metricsToUpdate[metricIndex])) { + return false; + } + } + for (int i = 0; i < config.duration_metric_size(); i++, metricIndex++) { + const DurationMetric& metric = config.duration_metric(i); + set conditionDependencies({metric.what()}); + if (metric.has_condition()) { + conditionDependencies.insert(metric.condition()); + } + if (!determineMetricUpdateStatus( + config, metric, metric.id(), METRIC_TYPE_DURATION, /*matcherDependencies=*/{}, + conditionDependencies, metric.slice_by_state(), metric.links(), + oldMetricProducerMap, oldMetricProducers, metricToActivationMap, + replacedMatchers, replacedConditions, replacedStates, + metricsToUpdate[metricIndex])) { + return false; + } + } + for (int i = 0; i < config.event_metric_size(); i++, metricIndex++) { + const EventMetric& metric = config.event_metric(i); + set conditionDependencies; + if (metric.has_condition()) { + conditionDependencies.insert(metric.condition()); + } + if (!determineMetricUpdateStatus( + config, metric, metric.id(), METRIC_TYPE_EVENT, {metric.what()}, + conditionDependencies, ::google::protobuf::RepeatedField(), + metric.links(), oldMetricProducerMap, oldMetricProducers, metricToActivationMap, + replacedMatchers, replacedConditions, replacedStates, + metricsToUpdate[metricIndex])) { + return false; + } + } + for (int i = 0; i < config.value_metric_size(); i++, metricIndex++) { + const ValueMetric& metric = config.value_metric(i); + set conditionDependencies; + if (metric.has_condition()) { + conditionDependencies.insert(metric.condition()); + } + if (!determineMetricUpdateStatus( + config, metric, metric.id(), METRIC_TYPE_VALUE, {metric.what()}, + conditionDependencies, metric.slice_by_state(), metric.links(), + oldMetricProducerMap, oldMetricProducers, metricToActivationMap, + replacedMatchers, replacedConditions, replacedStates, + metricsToUpdate[metricIndex])) { + return false; + } + } + for (int i = 0; i < config.gauge_metric_size(); i++, metricIndex++) { + const GaugeMetric& metric = config.gauge_metric(i); + set conditionDependencies; + if (metric.has_condition()) { + conditionDependencies.insert(metric.condition()); + } + set matcherDependencies({metric.what()}); + if (metric.has_trigger_event()) { + matcherDependencies.insert(metric.trigger_event()); + } + if (!determineMetricUpdateStatus( + config, metric, metric.id(), METRIC_TYPE_GAUGE, matcherDependencies, + conditionDependencies, ::google::protobuf::RepeatedField(), + metric.links(), oldMetricProducerMap, oldMetricProducers, metricToActivationMap, + replacedMatchers, replacedConditions, replacedStates, + metricsToUpdate[metricIndex])) { + return false; + } + } + return true; +} + +// Called when a metric is preserved during a config update. Finds the metric in oldMetricProducers +// and calls onConfigUpdated to update all indices. +optional> updateMetric( + const StatsdConfig& config, const int configIndex, const int metricIndex, + const int64_t metricId, const vector>& allAtomMatchingTrackers, + const unordered_map& oldAtomMatchingTrackerMap, + const unordered_map& newAtomMatchingTrackerMap, + const sp& matcherWizard, + const vector>& allConditionTrackers, + const unordered_map& conditionTrackerMap, const sp& wizard, + const unordered_map& oldMetricProducerMap, + const vector>& oldMetricProducers, + const unordered_map& metricToActivationMap, + unordered_map>& trackerToMetricMap, + unordered_map>& conditionToMetricMap, + unordered_map>& activationAtomTrackerToMetricMap, + unordered_map>& deactivationAtomTrackerToMetricMap, + vector& metricsWithActivation) { + const auto& oldMetricProducerIt = oldMetricProducerMap.find(metricId); + if (oldMetricProducerIt == oldMetricProducerMap.end()) { + ALOGE("Could not find Metric %lld in the previous config, but expected it " + "to be there", + (long long)metricId); + return nullopt; + } + const int oldIndex = oldMetricProducerIt->second; + sp producer = oldMetricProducers[oldIndex]; + if (!producer->onConfigUpdated(config, configIndex, metricIndex, allAtomMatchingTrackers, + oldAtomMatchingTrackerMap, newAtomMatchingTrackerMap, + matcherWizard, allConditionTrackers, conditionTrackerMap, wizard, + metricToActivationMap, trackerToMetricMap, conditionToMetricMap, + activationAtomTrackerToMetricMap, + deactivationAtomTrackerToMetricMap, metricsWithActivation)) { + return nullopt; + } + return {producer}; +} + +bool updateMetrics(const ConfigKey& key, const StatsdConfig& config, const int64_t timeBaseNs, + const int64_t currentTimeNs, const sp& pullerManager, + const unordered_map& oldAtomMatchingTrackerMap, + const unordered_map& newAtomMatchingTrackerMap, + const set& replacedMatchers, + const vector>& allAtomMatchingTrackers, + const unordered_map& conditionTrackerMap, + const set& replacedConditions, + vector>& allConditionTrackers, + const vector& initialConditionCache, + const unordered_map& stateAtomIdMap, + const unordered_map>& allStateGroupMaps, + const set& replacedStates, + const unordered_map& oldMetricProducerMap, + const vector>& oldMetricProducers, + unordered_map& newMetricProducerMap, + vector>& newMetricProducers, + unordered_map>& conditionToMetricMap, + unordered_map>& trackerToMetricMap, + set& noReportMetricIds, + unordered_map>& activationAtomTrackerToMetricMap, + unordered_map>& deactivationAtomTrackerToMetricMap, + vector& metricsWithActivation, set& replacedMetrics) { + sp wizard = new ConditionWizard(allConditionTrackers); + sp matcherWizard = new EventMatcherWizard(allAtomMatchingTrackers); + const int allMetricsCount = config.count_metric_size() + config.duration_metric_size() + + config.event_metric_size() + config.gauge_metric_size() + + config.value_metric_size(); + newMetricProducers.reserve(allMetricsCount); + + // Construct map from metric id to metric activation index. The map will be used to determine + // the metric activation corresponding to a metric. + unordered_map metricToActivationMap; + for (int i = 0; i < config.metric_activation_size(); i++) { + const MetricActivation& metricActivation = config.metric_activation(i); + int64_t metricId = metricActivation.metric_id(); + if (metricToActivationMap.find(metricId) != metricToActivationMap.end()) { + ALOGE("Metric %lld has multiple MetricActivations", (long long)metricId); + return false; + } + metricToActivationMap.insert({metricId, i}); + } + + vector metricsToUpdate(allMetricsCount, UPDATE_UNKNOWN); + if (!determineAllMetricUpdateStatuses(config, oldMetricProducerMap, oldMetricProducers, + metricToActivationMap, replacedMatchers, + replacedConditions, replacedStates, metricsToUpdate)) { + return false; + } + + // Now, perform the update. Must iterate the metric types in the same order + int metricIndex = 0; + for (int i = 0; i < config.count_metric_size(); i++, metricIndex++) { + const CountMetric& metric = config.count_metric(i); + newMetricProducerMap[metric.id()] = metricIndex; + optional> producer; + switch (metricsToUpdate[metricIndex]) { + case UPDATE_PRESERVE: { + producer = updateMetric( + config, i, metricIndex, metric.id(), allAtomMatchingTrackers, + oldAtomMatchingTrackerMap, newAtomMatchingTrackerMap, matcherWizard, + allConditionTrackers, conditionTrackerMap, wizard, oldMetricProducerMap, + oldMetricProducers, metricToActivationMap, trackerToMetricMap, + conditionToMetricMap, activationAtomTrackerToMetricMap, + deactivationAtomTrackerToMetricMap, metricsWithActivation); + break; + } + case UPDATE_REPLACE: + replacedMetrics.insert(metric.id()); + [[fallthrough]]; // Intentionally fallthrough to create the new metric producer. + case UPDATE_NEW: { + producer = createCountMetricProducerAndUpdateMetadata( + key, config, timeBaseNs, currentTimeNs, metric, metricIndex, + allAtomMatchingTrackers, newAtomMatchingTrackerMap, allConditionTrackers, + conditionTrackerMap, initialConditionCache, wizard, stateAtomIdMap, + allStateGroupMaps, metricToActivationMap, trackerToMetricMap, + conditionToMetricMap, activationAtomTrackerToMetricMap, + deactivationAtomTrackerToMetricMap, metricsWithActivation); + break; + } + default: { + ALOGE("Metric \"%lld\" update state is unknown. This should never happen", + (long long)metric.id()); + return false; + } + } + if (!producer) { + return false; + } + newMetricProducers.push_back(producer.value()); + } + for (int i = 0; i < config.duration_metric_size(); i++, metricIndex++) { + const DurationMetric& metric = config.duration_metric(i); + newMetricProducerMap[metric.id()] = metricIndex; + optional> producer; + switch (metricsToUpdate[metricIndex]) { + case UPDATE_PRESERVE: { + producer = updateMetric( + config, i, metricIndex, metric.id(), allAtomMatchingTrackers, + oldAtomMatchingTrackerMap, newAtomMatchingTrackerMap, matcherWizard, + allConditionTrackers, conditionTrackerMap, wizard, oldMetricProducerMap, + oldMetricProducers, metricToActivationMap, trackerToMetricMap, + conditionToMetricMap, activationAtomTrackerToMetricMap, + deactivationAtomTrackerToMetricMap, metricsWithActivation); + break; + } + case UPDATE_REPLACE: + replacedMetrics.insert(metric.id()); + [[fallthrough]]; // Intentionally fallthrough to create the new metric producer. + case UPDATE_NEW: { + producer = createDurationMetricProducerAndUpdateMetadata( + key, config, timeBaseNs, currentTimeNs, metric, metricIndex, + allAtomMatchingTrackers, newAtomMatchingTrackerMap, allConditionTrackers, + conditionTrackerMap, initialConditionCache, wizard, stateAtomIdMap, + allStateGroupMaps, metricToActivationMap, trackerToMetricMap, + conditionToMetricMap, activationAtomTrackerToMetricMap, + deactivationAtomTrackerToMetricMap, metricsWithActivation); + break; + } + default: { + ALOGE("Metric \"%lld\" update state is unknown. This should never happen", + (long long)metric.id()); + return false; + } + } + if (!producer) { + return false; + } + newMetricProducers.push_back(producer.value()); + } + for (int i = 0; i < config.event_metric_size(); i++, metricIndex++) { + const EventMetric& metric = config.event_metric(i); + newMetricProducerMap[metric.id()] = metricIndex; + optional> producer; + switch (metricsToUpdate[metricIndex]) { + case UPDATE_PRESERVE: { + producer = updateMetric( + config, i, metricIndex, metric.id(), allAtomMatchingTrackers, + oldAtomMatchingTrackerMap, newAtomMatchingTrackerMap, matcherWizard, + allConditionTrackers, conditionTrackerMap, wizard, oldMetricProducerMap, + oldMetricProducers, metricToActivationMap, trackerToMetricMap, + conditionToMetricMap, activationAtomTrackerToMetricMap, + deactivationAtomTrackerToMetricMap, metricsWithActivation); + break; + } + case UPDATE_REPLACE: + replacedMetrics.insert(metric.id()); + [[fallthrough]]; // Intentionally fallthrough to create the new metric producer. + case UPDATE_NEW: { + producer = createEventMetricProducerAndUpdateMetadata( + key, config, timeBaseNs, metric, metricIndex, allAtomMatchingTrackers, + newAtomMatchingTrackerMap, allConditionTrackers, conditionTrackerMap, + initialConditionCache, wizard, metricToActivationMap, trackerToMetricMap, + conditionToMetricMap, activationAtomTrackerToMetricMap, + deactivationAtomTrackerToMetricMap, metricsWithActivation); + break; + } + default: { + ALOGE("Metric \"%lld\" update state is unknown. This should never happen", + (long long)metric.id()); + return false; + } + } + if (!producer) { + return false; + } + newMetricProducers.push_back(producer.value()); + } + + for (int i = 0; i < config.value_metric_size(); i++, metricIndex++) { + const ValueMetric& metric = config.value_metric(i); + newMetricProducerMap[metric.id()] = metricIndex; + optional> producer; + switch (metricsToUpdate[metricIndex]) { + case UPDATE_PRESERVE: { + producer = updateMetric( + config, i, metricIndex, metric.id(), allAtomMatchingTrackers, + oldAtomMatchingTrackerMap, newAtomMatchingTrackerMap, matcherWizard, + allConditionTrackers, conditionTrackerMap, wizard, oldMetricProducerMap, + oldMetricProducers, metricToActivationMap, trackerToMetricMap, + conditionToMetricMap, activationAtomTrackerToMetricMap, + deactivationAtomTrackerToMetricMap, metricsWithActivation); + break; + } + case UPDATE_REPLACE: + replacedMetrics.insert(metric.id()); + [[fallthrough]]; // Intentionally fallthrough to create the new metric producer. + case UPDATE_NEW: { + producer = createValueMetricProducerAndUpdateMetadata( + key, config, timeBaseNs, currentTimeNs, pullerManager, metric, metricIndex, + allAtomMatchingTrackers, newAtomMatchingTrackerMap, allConditionTrackers, + conditionTrackerMap, initialConditionCache, wizard, matcherWizard, + stateAtomIdMap, allStateGroupMaps, metricToActivationMap, + trackerToMetricMap, conditionToMetricMap, activationAtomTrackerToMetricMap, + deactivationAtomTrackerToMetricMap, metricsWithActivation); + break; + } + default: { + ALOGE("Metric \"%lld\" update state is unknown. This should never happen", + (long long)metric.id()); + return false; + } + } + if (!producer) { + return false; + } + newMetricProducers.push_back(producer.value()); + } + + for (int i = 0; i < config.gauge_metric_size(); i++, metricIndex++) { + const GaugeMetric& metric = config.gauge_metric(i); + newMetricProducerMap[metric.id()] = metricIndex; + optional> producer; + switch (metricsToUpdate[metricIndex]) { + case UPDATE_PRESERVE: { + producer = updateMetric( + config, i, metricIndex, metric.id(), allAtomMatchingTrackers, + oldAtomMatchingTrackerMap, newAtomMatchingTrackerMap, matcherWizard, + allConditionTrackers, conditionTrackerMap, wizard, oldMetricProducerMap, + oldMetricProducers, metricToActivationMap, trackerToMetricMap, + conditionToMetricMap, activationAtomTrackerToMetricMap, + deactivationAtomTrackerToMetricMap, metricsWithActivation); + break; + } + case UPDATE_REPLACE: + replacedMetrics.insert(metric.id()); + [[fallthrough]]; // Intentionally fallthrough to create the new metric producer. + case UPDATE_NEW: { + producer = createGaugeMetricProducerAndUpdateMetadata( + key, config, timeBaseNs, currentTimeNs, pullerManager, metric, metricIndex, + allAtomMatchingTrackers, newAtomMatchingTrackerMap, allConditionTrackers, + conditionTrackerMap, initialConditionCache, wizard, matcherWizard, + metricToActivationMap, trackerToMetricMap, conditionToMetricMap, + activationAtomTrackerToMetricMap, deactivationAtomTrackerToMetricMap, + metricsWithActivation); + break; + } + default: { + ALOGE("Metric \"%lld\" update state is unknown. This should never happen", + (long long)metric.id()); + return false; + } + } + if (!producer) { + return false; + } + newMetricProducers.push_back(producer.value()); + } + + for (int i = 0; i < config.no_report_metric_size(); ++i) { + const int64_t noReportMetric = config.no_report_metric(i); + if (newMetricProducerMap.find(noReportMetric) == newMetricProducerMap.end()) { + ALOGW("no_report_metric %" PRId64 " not exist", noReportMetric); + return false; + } + noReportMetricIds.insert(noReportMetric); + } + const set atomsAllowedFromAnyUid(config.whitelisted_atom_ids().begin(), + config.whitelisted_atom_ids().end()); + for (int i = 0; i < allMetricsCount; i++) { + sp producer = newMetricProducers[i]; + // Register metrics to StateTrackers + for (int atomId : producer->getSlicedStateAtoms()) { + // Register listener for atoms that use allowed_log_sources. + // Using atoms allowed from any uid as a sliced state atom is not allowed. + // Redo this check for all metrics in case the atoms allowed from any uid changed. + if (atomsAllowedFromAnyUid.find(atomId) != atomsAllowedFromAnyUid.end()) { + return false; + // Preserved metrics should've already registered.` + } else if (metricsToUpdate[i] != UPDATE_PRESERVE) { + StateManager::getInstance().registerListener(atomId, producer); + } + } + } + + // Init new/replaced metrics. + for (size_t i = 0; i < newMetricProducers.size(); i++) { + if (metricsToUpdate[i] == UPDATE_REPLACE || metricsToUpdate[i] == UPDATE_NEW) { + newMetricProducers[i]->prepareFirstBucket(); + } + } + return true; +} + +bool determineAlertUpdateStatus(const Alert& alert, + const unordered_map& oldAlertTrackerMap, + const vector>& oldAnomalyTrackers, + const set& replacedMetrics, UpdateStatus& updateStatus) { + // Check if new alert. + const auto& oldAnomalyTrackerIt = oldAlertTrackerMap.find(alert.id()); + if (oldAnomalyTrackerIt == oldAlertTrackerMap.end()) { + updateStatus = UPDATE_NEW; + return true; + } + + // This is an existing alert, check if it has changed. + string serializedAlert; + if (!alert.SerializeToString(&serializedAlert)) { + ALOGW("Unable to serialize alert %lld", (long long)alert.id()); + return false; + } + uint64_t newProtoHash = Hash64(serializedAlert); + const auto [success, oldProtoHash] = + oldAnomalyTrackers[oldAnomalyTrackerIt->second]->getProtoHash(); + if (!success) { + return false; + } + if (newProtoHash != oldProtoHash) { + updateStatus = UPDATE_REPLACE; + return true; + } + + // Check if the metric this alert relies on has changed. + if (replacedMetrics.find(alert.metric_id()) != replacedMetrics.end()) { + updateStatus = UPDATE_REPLACE; + return true; + } + + updateStatus = UPDATE_PRESERVE; + return true; +} + +bool updateAlerts(const StatsdConfig& config, const int64_t currentTimeNs, + const unordered_map& metricProducerMap, + const set& replacedMetrics, + const unordered_map& oldAlertTrackerMap, + const vector>& oldAnomalyTrackers, + const sp& anomalyAlarmMonitor, + vector>& allMetricProducers, + unordered_map& newAlertTrackerMap, + vector>& newAnomalyTrackers) { + int alertCount = config.alert_size(); + vector alertUpdateStatuses(alertCount); + for (int i = 0; i < alertCount; i++) { + if (!determineAlertUpdateStatus(config.alert(i), oldAlertTrackerMap, oldAnomalyTrackers, + replacedMetrics, alertUpdateStatuses[i])) { + return false; + } + } + + for (int i = 0; i < alertCount; i++) { + const Alert& alert = config.alert(i); + newAlertTrackerMap[alert.id()] = newAnomalyTrackers.size(); + switch (alertUpdateStatuses[i]) { + case UPDATE_PRESERVE: { + // Find the alert and update it. + const auto& oldAnomalyTrackerIt = oldAlertTrackerMap.find(alert.id()); + if (oldAnomalyTrackerIt == oldAlertTrackerMap.end()) { + ALOGW("Could not find AnomalyTracker %lld in the previous config, but " + "expected it to be there", + (long long)alert.id()); + return false; + } + sp anomalyTracker = oldAnomalyTrackers[oldAnomalyTrackerIt->second]; + anomalyTracker->onConfigUpdated(); + // Add the alert to the relevant metric. + const auto& metricProducerIt = metricProducerMap.find(alert.metric_id()); + if (metricProducerIt == metricProducerMap.end()) { + ALOGW("alert \"%lld\" has unknown metric id: \"%lld\"", (long long)alert.id(), + (long long)alert.metric_id()); + return false; + } + allMetricProducers[metricProducerIt->second]->addAnomalyTracker(anomalyTracker, + currentTimeNs); + newAnomalyTrackers.push_back(anomalyTracker); + break; + } + case UPDATE_REPLACE: + case UPDATE_NEW: { + optional> anomalyTracker = + createAnomalyTracker(alert, anomalyAlarmMonitor, alertUpdateStatuses[i], + currentTimeNs, metricProducerMap, allMetricProducers); + if (!anomalyTracker) { + return false; + } + newAnomalyTrackers.push_back(anomalyTracker.value()); + break; + } + default: { + ALOGE("Alert \"%lld\" update state is unknown. This should never happen", + (long long)alert.id()); + return false; + } + } + } + if (!initSubscribersForSubscriptionType(config, Subscription::ALERT, newAlertTrackerMap, + newAnomalyTrackers)) { + return false; + } + return true; +} + +bool updateStatsdConfig(const ConfigKey& key, const StatsdConfig& config, const sp& uidMap, + const sp& pullerManager, + const sp& anomalyAlarmMonitor, + const sp& periodicAlarmMonitor, const int64_t timeBaseNs, + const int64_t currentTimeNs, + const vector>& oldAtomMatchingTrackers, + const unordered_map& oldAtomMatchingTrackerMap, + const vector>& oldConditionTrackers, + const unordered_map& oldConditionTrackerMap, + const vector>& oldMetricProducers, + const unordered_map& oldMetricProducerMap, + const vector>& oldAnomalyTrackers, + const unordered_map& oldAlertTrackerMap, + const map& oldStateProtoHashes, set& allTagIds, + vector>& newAtomMatchingTrackers, + unordered_map& newAtomMatchingTrackerMap, + vector>& newConditionTrackers, + unordered_map& newConditionTrackerMap, + vector>& newMetricProducers, + unordered_map& newMetricProducerMap, + vector>& newAnomalyTrackers, + unordered_map& newAlertTrackerMap, + vector>& newPeriodicAlarmTrackers, + unordered_map>& conditionToMetricMap, + unordered_map>& trackerToMetricMap, + unordered_map>& trackerToConditionMap, + unordered_map>& activationTrackerToMetricMap, + unordered_map>& deactivationTrackerToMetricMap, + vector& metricsWithActivation, + map& newStateProtoHashes, + set& noReportMetricIds) { + set replacedMatchers; + set replacedConditions; + set replacedStates; + set replacedMetrics; + vector conditionCache; + unordered_map stateAtomIdMap; + unordered_map> allStateGroupMaps; + + if (!updateAtomMatchingTrackers(config, uidMap, oldAtomMatchingTrackerMap, + oldAtomMatchingTrackers, allTagIds, newAtomMatchingTrackerMap, + newAtomMatchingTrackers, replacedMatchers)) { + ALOGE("updateAtomMatchingTrackers failed"); + return false; + } + + if (!updateConditions(key, config, newAtomMatchingTrackerMap, replacedMatchers, + oldConditionTrackerMap, oldConditionTrackers, newConditionTrackerMap, + newConditionTrackers, trackerToConditionMap, conditionCache, + replacedConditions)) { + ALOGE("updateConditions failed"); + return false; + } + + if (!updateStates(config, oldStateProtoHashes, stateAtomIdMap, allStateGroupMaps, + newStateProtoHashes, replacedStates)) { + ALOGE("updateStates failed"); + return false; + } + if (!updateMetrics(key, config, timeBaseNs, currentTimeNs, pullerManager, + oldAtomMatchingTrackerMap, newAtomMatchingTrackerMap, replacedMatchers, + newAtomMatchingTrackers, newConditionTrackerMap, replacedConditions, + newConditionTrackers, conditionCache, stateAtomIdMap, allStateGroupMaps, + replacedStates, oldMetricProducerMap, oldMetricProducers, + newMetricProducerMap, newMetricProducers, conditionToMetricMap, + trackerToMetricMap, noReportMetricIds, activationTrackerToMetricMap, + deactivationTrackerToMetricMap, metricsWithActivation, replacedMetrics)) { + ALOGE("updateMetrics failed"); + return false; + } + + if (!updateAlerts(config, currentTimeNs, newMetricProducerMap, replacedMetrics, + oldAlertTrackerMap, oldAnomalyTrackers, anomalyAlarmMonitor, + newMetricProducers, newAlertTrackerMap, newAnomalyTrackers)) { + ALOGE("updateAlerts failed"); + return false; + } + + // Alarms do not have any state, so we can reuse the initialization logic. + if (!initAlarms(config, key, periodicAlarmMonitor, timeBaseNs, currentTimeNs, + newPeriodicAlarmTrackers)) { + ALOGE("initAlarms failed"); + return false; + } + return true; +} + +} // namespace statsd +} // namespace os +} // namespace android diff --git a/statsd/src/metrics/parsing_utils/config_update_utils.h b/statsd/src/metrics/parsing_utils/config_update_utils.h new file mode 100644 index 00000000..24f8c850 --- /dev/null +++ b/statsd/src/metrics/parsing_utils/config_update_utils.h @@ -0,0 +1,267 @@ +/* + * Copyright (C) 2020 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. + */ + +#pragma once + +#include + +#include "anomaly/AlarmMonitor.h" +#include "anomaly/AlarmTracker.h" +#include "condition/ConditionTracker.h" +#include "external/StatsPullerManager.h" +#include "matchers/AtomMatchingTracker.h" +#include "metrics/MetricProducer.h" + +namespace android { +namespace os { +namespace statsd { + +// Helper functions for MetricsManager to update itself from a new StatsdConfig. +// *Note*: only updateStatsdConfig() should be called from outside this file. +// All other functions are intermediate steps, created to make unit testing easier. + +// Recursive function to determine if a matcher needs to be updated. +// input: +// [config]: the input StatsdConfig +// [matcherIdx]: the index of the current matcher to be updated +// [oldAtomMatchingTrackerMap]: matcher id to index mapping in the existing MetricsManager +// [oldAtomMatchingTrackers]: stores the existing AtomMatchingTrackers +// [newAtomMatchingTrackerMap]: matcher id to index mapping in the input StatsdConfig +// output: +// [matchersToUpdate]: vector of the update status of each matcher. The matcherIdx index will +// be updated from UPDATE_UNKNOWN after this call. +// [cycleTracker]: intermediate param used during recursion. +// Returns whether the function was successful or not. +bool determineMatcherUpdateStatus( + const StatsdConfig& config, const int matcherIdx, + const std::unordered_map& oldAtomMatchingTrackerMap, + const std::vector>& oldAtomMatchingTrackers, + const std::unordered_map& newAtomMatchingTrackerMap, + std::vector& matchersToUpdate, std::vector& cycleTracker); + +// Updates the AtomMatchingTrackers. +// input: +// [config]: the input StatsdConfig +// [oldAtomMatchingTrackerMap]: existing matcher id to index mapping +// [oldAtomMatchingTrackers]: stores the existing AtomMatchingTrackers +// output: +// [allTagIds]: contains the set of all interesting tag ids to this config. +// [newAtomMatchingTrackerMap]: new matcher id to index mapping +// [newAtomMatchingTrackers]: stores the new AtomMatchingTrackers +// [replacedMatchers]: set of matcher ids that changed and have been replaced +bool updateAtomMatchingTrackers(const StatsdConfig& config, const sp& uidMap, + const std::unordered_map& oldAtomMatchingTrackerMap, + const std::vector>& oldAtomMatchingTrackers, + std::set& allTagIds, + std::unordered_map& newAtomMatchingTrackerMap, + std::vector>& newAtomMatchingTrackers, + std::set& replacedMatchers); + +// Recursive function to determine if a condition needs to be updated. +// input: +// [config]: the input StatsdConfig +// [conditionIdx]: the index of the current condition to be updated +// [oldConditionTrackerMap]: condition id to index mapping in the existing MetricsManager +// [oldConditionTrackers]: stores the existing ConditionTrackers +// [newConditionTrackerMap]: condition id to index mapping in the input StatsdConfig +// [replacedMatchers]: set of replaced matcher ids. conditions using these matchers must be replaced +// output: +// [conditionsToUpdate]: vector of the update status of each condition. The conditionIdx index will +// be updated from UPDATE_UNKNOWN after this call. +// [cycleTracker]: intermediate param used during recursion. +// Returns whether the function was successful or not. +bool determineConditionUpdateStatus(const StatsdConfig& config, const int conditionIdx, + const std::unordered_map& oldConditionTrackerMap, + const std::vector>& oldConditionTrackers, + const std::unordered_map& newConditionTrackerMap, + const std::set& replacedMatchers, + std::vector& conditionsToUpdate, + std::vector& cycleTracker); + +// Updates ConditionTrackers +// input: +// [config]: the input config +// [atomMatchingTrackerMap]: AtomMatchingTracker name to index mapping from previous step. +// [replacedMatchers]: ids of replaced matchers. conditions depending on these must also be replaced +// [oldConditionTrackerMap]: existing matcher id to index mapping +// [oldConditionTrackers]: stores the existing ConditionTrackers +// output: +// [newConditionTrackerMap]: new condition id to index mapping +// [newConditionTrackers]: stores the sp to all the ConditionTrackers +// [trackerToConditionMap]: contains the mapping from the index of an atom matcher +// to indices of condition trackers that use the matcher +// [conditionCache]: stores the current conditions for each ConditionTracker +// [replacedConditions]: set of condition ids that have changed and have been replaced +bool updateConditions(const ConfigKey& key, const StatsdConfig& config, + const std::unordered_map& atomMatchingTrackerMap, + const std::set& replacedMatchers, + const std::unordered_map& oldConditionTrackerMap, + const std::vector>& oldConditionTrackers, + std::unordered_map& newConditionTrackerMap, + std::vector>& newConditionTrackers, + std::unordered_map>& trackerToConditionMap, + std::vector& conditionCache, + std::set& replacedConditions); + +bool updateStates(const StatsdConfig& config, + const std::map& oldStateProtoHashes, + std::unordered_map& stateAtomIdMap, + std::unordered_map>& allStateGroupMaps, + std::map& newStateProtoHashes, + std::set& replacedStates); + +// Function to determine the update status (preserve/replace/new) of all metrics in the config. +// [config]: the input StatsdConfig +// [oldMetricProducerMap]: metric id to index mapping in the existing MetricsManager +// [oldMetricProducers]: stores the existing MetricProducers +// [metricToActivationMap]: map from metric id to metric activation index +// [replacedMatchers]: set of replaced matcher ids. metrics using these matchers must be replaced +// [replacedConditions]: set of replaced conditions. metrics using these conditions must be replaced +// [replacedStates]: set of replaced state ids. metrics using these states must be replaced +// output: +// [metricsToUpdate]: update status of each metric. Will be changed from UPDATE_UNKNOWN +// Returns whether the function was successful or not. +bool determineAllMetricUpdateStatuses(const StatsdConfig& config, + const unordered_map& oldMetricProducerMap, + const vector>& oldMetricProducers, + const unordered_map& metricToActivationMap, + const set& replacedMatchers, + const set& replacedConditions, + const set& replacedStates, + vector& metricsToUpdate); + +// Update MetricProducers. +// input: +// [key]: the config key that this config belongs to +// [config]: the input config +// [timeBaseNs]: start time base for all metrics +// [currentTimeNs]: time of the config update +// [atomMatchingTrackerMap]: AtomMatchingTracker name to index mapping from previous step. +// [replacedMatchers]: ids of replaced matchers. Metrics depending on these must also be replaced +// [allAtomMatchingTrackers]: stores the sp of the atom matchers. +// [conditionTrackerMap]: condition name to index mapping +// [replacedConditions]: set of condition ids that have changed and have been replaced +// [stateAtomIdMap]: contains the mapping from state ids to atom ids +// [allStateGroupMaps]: contains the mapping from atom ids and state values to +// state group ids for all states +// output: +// [allMetricProducers]: contains the list of sp to the MetricProducers created. +// [conditionToMetricMap]: contains the mapping from condition tracker index to +// the list of MetricProducer index +// [trackerToMetricMap]: contains the mapping from log tracker to MetricProducer index. +bool updateMetrics( + const ConfigKey& key, const StatsdConfig& config, const int64_t timeBaseNs, + const int64_t currentTimeNs, const sp& pullerManager, + const std::unordered_map& oldAtomMatchingTrackerMap, + const std::unordered_map& newAtomMatchingTrackerMap, + const std::set& replacedMatchers, + const std::vector>& allAtomMatchingTrackers, + const std::unordered_map& conditionTrackerMap, + const std::set& replacedConditions, + std::vector>& allConditionTrackers, + const std::vector& initialConditionCache, + const std::unordered_map& stateAtomIdMap, + const std::unordered_map>& allStateGroupMaps, + const std::set& replacedStates, + const std::unordered_map& oldMetricProducerMap, + const std::vector>& oldMetricProducers, + std::unordered_map& newMetricProducerMap, + std::vector>& newMetricProducers, + std::unordered_map>& conditionToMetricMap, + std::unordered_map>& trackerToMetricMap, + std::set& noReportMetricIds, + std::unordered_map>& activationAtomTrackerToMetricMap, + std::unordered_map>& deactivationAtomTrackerToMetricMap, + std::vector& metricsWithActivation, std::set& replacedMetrics); + +// Function to determine the update status (preserve/replace/new) of an alert. +// [alert]: the input Alert +// [oldAlertTrackerMap]: alert id to index mapping in the existing MetricsManager +// [oldAnomalyTrackers]: stores the existing AnomalyTrackers +// [replacedMetrics]: set of replaced metric ids. alerts using these metrics must be replaced +// output: +// [updateStatus]: update status of the alert. Will be changed from UPDATE_UNKNOWN +// Returns whether the function was successful or not. +bool determineAlertUpdateStatus(const Alert& alert, + const std::unordered_map& oldAlertTrackerMap, + const std::vector>& oldAnomalyTrackers, + const std::set& replacedMetrics, + UpdateStatus& updateStatus); + +// Update MetricProducers. +// input: +// [config]: the input config +// [currentTimeNs]: time of the config update +// [metricProducerMap]: metric id to index mapping in the new config +// [replacedMetrics]: set of metric ids that have changed and were replaced +// [oldAlertTrackerMap]: alert id to index mapping in the existing MetricsManager. +// [oldAnomalyTrackers]: stores the existing AnomalyTrackers +// [anomalyAlarmMonitor]: AlarmMonitor used for duration metric anomaly detection +// [allMetricProducers]: stores the sp of the metric producers, AnomalyTrackers need to be added. +// [stateAtomIdMap]: contains the mapping from state ids to atom ids +// [allStateGroupMaps]: contains the mapping from atom ids and state values to +// state group ids for all states +// output: +// [newAlertTrackerMap]: mapping of alert id to index in the new config +// [newAnomalyTrackers]: contains the list of sp to the AnomalyTrackers created. +bool updateAlerts(const StatsdConfig& config, const int64_t currentTimeNs, + const std::unordered_map& metricProducerMap, + const std::set& replacedMetrics, + const std::unordered_map& oldAlertTrackerMap, + const std::vector>& oldAnomalyTrackers, + const sp& anomalyAlarmMonitor, + std::vector>& allMetricProducers, + std::unordered_map& newAlertTrackerMap, + std::vector>& newAnomalyTrackers); + +// Updates the existing MetricsManager from a new StatsdConfig. +// Parameters are the members of MetricsManager. See MetricsManager for declaration. +bool updateStatsdConfig(const ConfigKey& key, const StatsdConfig& config, const sp& uidMap, + const sp& pullerManager, + const sp& anomalyAlarmMonitor, + const sp& periodicAlarmMonitor, const int64_t timeBaseNs, + const int64_t currentTimeNs, + const std::vector>& oldAtomMatchingTrackers, + const std::unordered_map& oldAtomMatchingTrackerMap, + const std::vector>& oldConditionTrackers, + const std::unordered_map& oldConditionTrackerMap, + const std::vector>& oldMetricProducers, + const std::unordered_map& oldMetricProducerMap, + const std::vector>& oldAnomalyTrackers, + const std::unordered_map& oldAlertTrackerMap, + const std::map& oldStateProtoHashes, + std::set& allTagIds, + std::vector>& newAtomMatchingTrackers, + std::unordered_map& newAtomMatchingTrackerMap, + std::vector>& newConditionTrackers, + std::unordered_map& newConditionTrackerMap, + std::vector>& newMetricProducers, + std::unordered_map& newMetricProducerMap, + std::vector>& newAlertTrackers, + std::unordered_map& newAlertTrackerMap, + std::vector>& newPeriodicAlarmTrackers, + std::unordered_map>& conditionToMetricMap, + std::unordered_map>& trackerToMetricMap, + std::unordered_map>& trackerToConditionMap, + std::unordered_map>& activationTrackerToMetricMap, + std::unordered_map>& deactivationTrackerToMetricMap, + std::vector& metricsWithActivation, + std::map& newStateProtoHashes, + std::set& noReportMetricIds); + +} // namespace statsd +} // namespace os +} // namespace android diff --git a/statsd/src/metrics/parsing_utils/metrics_manager_util.cpp b/statsd/src/metrics/parsing_utils/metrics_manager_util.cpp new file mode 100644 index 00000000..1e230008 --- /dev/null +++ b/statsd/src/metrics/parsing_utils/metrics_manager_util.cpp @@ -0,0 +1,1246 @@ +/* + * 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. + */ + +#define DEBUG false // STOPSHIP if true +#include "Log.h" + +#include "metrics_manager_util.h" + +#include + +#include "FieldValue.h" +#include "condition/CombinationConditionTracker.h" +#include "condition/SimpleConditionTracker.h" +#include "external/StatsPullerManager.h" +#include "hash.h" +#include "matchers/CombinationAtomMatchingTracker.h" +#include "matchers/EventMatcherWizard.h" +#include "matchers/SimpleAtomMatchingTracker.h" +#include "metrics/CountMetricProducer.h" +#include "metrics/DurationMetricProducer.h" +#include "metrics/EventMetricProducer.h" +#include "metrics/GaugeMetricProducer.h" +#include "metrics/MetricProducer.h" +#include "metrics/ValueMetricProducer.h" +#include "state/StateManager.h" +#include "stats_util.h" + +using google::protobuf::MessageLite; +using std::set; +using std::unordered_map; +using std::vector; + +namespace android { +namespace os { +namespace statsd { + +namespace { + +bool hasLeafNode(const FieldMatcher& matcher) { + if (!matcher.has_field()) { + return false; + } + for (int i = 0; i < matcher.child_size(); ++i) { + if (hasLeafNode(matcher.child(i))) { + return true; + } + } + return true; +} + +} // namespace + +sp createAtomMatchingTracker(const AtomMatcher& logMatcher, const int index, + const sp& uidMap) { + string serializedMatcher; + if (!logMatcher.SerializeToString(&serializedMatcher)) { + ALOGE("Unable to serialize matcher %lld", (long long)logMatcher.id()); + return nullptr; + } + uint64_t protoHash = Hash64(serializedMatcher); + switch (logMatcher.contents_case()) { + case AtomMatcher::ContentsCase::kSimpleAtomMatcher: + return new SimpleAtomMatchingTracker(logMatcher.id(), index, protoHash, + logMatcher.simple_atom_matcher(), uidMap); + case AtomMatcher::ContentsCase::kCombination: + return new CombinationAtomMatchingTracker(logMatcher.id(), index, protoHash); + default: + ALOGE("Matcher \"%lld\" malformed", (long long)logMatcher.id()); + return nullptr; + } +} + +sp createConditionTracker( + const ConfigKey& key, const Predicate& predicate, const int index, + const unordered_map& atomMatchingTrackerMap) { + string serializedPredicate; + if (!predicate.SerializeToString(&serializedPredicate)) { + ALOGE("Unable to serialize predicate %lld", (long long)predicate.id()); + return nullptr; + } + uint64_t protoHash = Hash64(serializedPredicate); + switch (predicate.contents_case()) { + case Predicate::ContentsCase::kSimplePredicate: { + return new SimpleConditionTracker(key, predicate.id(), protoHash, index, + predicate.simple_predicate(), atomMatchingTrackerMap); + } + case Predicate::ContentsCase::kCombination: { + return new CombinationConditionTracker(predicate.id(), index, protoHash); + } + default: + ALOGE("Predicate \"%lld\" malformed", (long long)predicate.id()); + return nullptr; + } +} + +bool getMetricProtoHash(const StatsdConfig& config, const MessageLite& metric, const int64_t id, + const unordered_map& metricToActivationMap, + uint64_t& metricHash) { + string serializedMetric; + if (!metric.SerializeToString(&serializedMetric)) { + ALOGE("Unable to serialize metric %lld", (long long)id); + return false; + } + metricHash = Hash64(serializedMetric); + + // Combine with activation hash, if applicable + const auto& metricActivationIt = metricToActivationMap.find(id); + if (metricActivationIt != metricToActivationMap.end()) { + string serializedActivation; + const MetricActivation& activation = config.metric_activation(metricActivationIt->second); + if (!activation.SerializeToString(&serializedActivation)) { + ALOGE("Unable to serialize metric activation for metric %lld", (long long)id); + return false; + } + metricHash = Hash64(to_string(metricHash).append(to_string(Hash64(serializedActivation)))); + } + return true; +} + +bool handleMetricWithAtomMatchingTrackers( + const int64_t matcherId, const int metricIndex, const bool enforceOneAtom, + const vector>& allAtomMatchingTrackers, + const unordered_map& atomMatchingTrackerMap, + unordered_map>& trackerToMetricMap, int& logTrackerIndex) { + auto logTrackerIt = atomMatchingTrackerMap.find(matcherId); + if (logTrackerIt == atomMatchingTrackerMap.end()) { + ALOGW("cannot find the AtomMatcher \"%lld\" in config", (long long)matcherId); + return false; + } + if (enforceOneAtom && allAtomMatchingTrackers[logTrackerIt->second]->getAtomIds().size() > 1) { + ALOGE("AtomMatcher \"%lld\" has more than one tag ids. When a metric has dimension, " + "the \"what\" can only be about one atom type. trigger_event matchers can also only " + "be about one atom type.", + (long long)matcherId); + return false; + } + logTrackerIndex = logTrackerIt->second; + auto& metric_list = trackerToMetricMap[logTrackerIndex]; + metric_list.push_back(metricIndex); + return true; +} + +bool handleMetricWithConditions( + const int64_t condition, const int metricIndex, + const unordered_map& conditionTrackerMap, + const ::google::protobuf::RepeatedPtrField<::android::os::statsd::MetricConditionLink>& + links, + const vector>& allConditionTrackers, int& conditionIndex, + unordered_map>& conditionToMetricMap) { + auto condition_it = conditionTrackerMap.find(condition); + if (condition_it == conditionTrackerMap.end()) { + ALOGW("cannot find Predicate \"%lld\" in the config", (long long)condition); + return false; + } + + for (const auto& link : links) { + auto it = conditionTrackerMap.find(link.condition()); + if (it == conditionTrackerMap.end()) { + ALOGW("cannot find Predicate \"%lld\" in the config", (long long)link.condition()); + return false; + } + } + conditionIndex = condition_it->second; + + // will create new vector if not exist before. + auto& metricList = conditionToMetricMap[condition_it->second]; + metricList.push_back(metricIndex); + return true; +} + +// Initializes state data structures for a metric. +// input: +// [config]: the input config +// [stateIds]: the slice_by_state ids for this metric +// [stateAtomIdMap]: this map contains the mapping from all state ids to atom ids +// [allStateGroupMaps]: this map contains the mapping from state ids and state +// values to state group ids for all states +// output: +// [slicedStateAtoms]: a vector of atom ids of all the slice_by_states +// [stateGroupMap]: this map should contain the mapping from states ids and state +// values to state group ids for all states that this metric +// is interested in +bool handleMetricWithStates( + const StatsdConfig& config, const ::google::protobuf::RepeatedField& stateIds, + const unordered_map& stateAtomIdMap, + const unordered_map>& allStateGroupMaps, + vector& slicedStateAtoms, + unordered_map>& stateGroupMap) { + for (const auto& stateId : stateIds) { + auto it = stateAtomIdMap.find(stateId); + if (it == stateAtomIdMap.end()) { + ALOGW("cannot find State %" PRId64 " in the config", stateId); + return false; + } + int atomId = it->second; + slicedStateAtoms.push_back(atomId); + + auto stateIt = allStateGroupMaps.find(stateId); + if (stateIt != allStateGroupMaps.end()) { + stateGroupMap[atomId] = stateIt->second; + } + } + return true; +} + +bool handleMetricWithStateLink(const FieldMatcher& stateMatcher, + const vector& dimensionsInWhat) { + vector stateMatchers; + translateFieldMatcher(stateMatcher, &stateMatchers); + + return subsetDimensions(stateMatchers, dimensionsInWhat); +} + +// Validates a metricActivation and populates state. +// EventActivationMap and EventDeactivationMap are supplied to a MetricProducer +// to provide the producer with state about its activators and deactivators. +// Returns false if there are errors. +bool handleMetricActivation( + const StatsdConfig& config, const int64_t metricId, const int metricIndex, + const unordered_map& metricToActivationMap, + const unordered_map& atomMatchingTrackerMap, + unordered_map>& activationAtomTrackerToMetricMap, + unordered_map>& deactivationAtomTrackerToMetricMap, + vector& metricsWithActivation, + unordered_map>& eventActivationMap, + unordered_map>>& eventDeactivationMap) { + // Check if metric has an associated activation + auto itr = metricToActivationMap.find(metricId); + if (itr == metricToActivationMap.end()) { + return true; + } + + int activationIndex = itr->second; + const MetricActivation& metricActivation = config.metric_activation(activationIndex); + + for (int i = 0; i < metricActivation.event_activation_size(); i++) { + const EventActivation& activation = metricActivation.event_activation(i); + + auto itr = atomMatchingTrackerMap.find(activation.atom_matcher_id()); + if (itr == atomMatchingTrackerMap.end()) { + ALOGE("Atom matcher not found for event activation."); + return false; + } + + ActivationType activationType = (activation.has_activation_type()) + ? activation.activation_type() + : metricActivation.activation_type(); + std::shared_ptr activationWrapper = + std::make_shared(activationType, activation.ttl_seconds() * NS_PER_SEC); + + int atomMatcherIndex = itr->second; + activationAtomTrackerToMetricMap[atomMatcherIndex].push_back(metricIndex); + eventActivationMap.emplace(atomMatcherIndex, activationWrapper); + + if (activation.has_deactivation_atom_matcher_id()) { + itr = atomMatchingTrackerMap.find(activation.deactivation_atom_matcher_id()); + if (itr == atomMatchingTrackerMap.end()) { + ALOGE("Atom matcher not found for event deactivation."); + return false; + } + int deactivationAtomMatcherIndex = itr->second; + deactivationAtomTrackerToMetricMap[deactivationAtomMatcherIndex].push_back(metricIndex); + eventDeactivationMap[deactivationAtomMatcherIndex].push_back(activationWrapper); + } + } + + metricsWithActivation.push_back(metricIndex); + return true; +} + +// Validates a metricActivation and populates state. +// Fills the new event activation/deactivation maps, preserving the existing activations +// Returns false if there are errors. +bool handleMetricActivationOnConfigUpdate( + const StatsdConfig& config, const int64_t metricId, const int metricIndex, + const unordered_map& metricToActivationMap, + const unordered_map& oldAtomMatchingTrackerMap, + const unordered_map& newAtomMatchingTrackerMap, + const unordered_map>& oldEventActivationMap, + unordered_map>& activationAtomTrackerToMetricMap, + unordered_map>& deactivationAtomTrackerToMetricMap, + vector& metricsWithActivation, + unordered_map>& newEventActivationMap, + unordered_map>>& newEventDeactivationMap) { + // Check if metric has an associated activation. + const auto& itr = metricToActivationMap.find(metricId); + if (itr == metricToActivationMap.end()) { + return true; + } + + int activationIndex = itr->second; + const MetricActivation& metricActivation = config.metric_activation(activationIndex); + + for (int i = 0; i < metricActivation.event_activation_size(); i++) { + const int64_t activationMatcherId = metricActivation.event_activation(i).atom_matcher_id(); + + const auto& newActivationIt = newAtomMatchingTrackerMap.find(activationMatcherId); + if (newActivationIt == newAtomMatchingTrackerMap.end()) { + ALOGE("Atom matcher not found in new config for event activation."); + return false; + } + int newActivationMatcherIndex = newActivationIt->second; + + // Find the old activation struct and copy it over. + const auto& oldActivationIt = oldAtomMatchingTrackerMap.find(activationMatcherId); + if (oldActivationIt == oldAtomMatchingTrackerMap.end()) { + ALOGE("Atom matcher not found in existing config for event activation."); + return false; + } + int oldActivationMatcherIndex = oldActivationIt->second; + const auto& oldEventActivationIt = oldEventActivationMap.find(oldActivationMatcherIndex); + if (oldEventActivationIt == oldEventActivationMap.end()) { + ALOGE("Could not find existing event activation to update"); + return false; + } + newEventActivationMap.emplace(newActivationMatcherIndex, oldEventActivationIt->second); + activationAtomTrackerToMetricMap[newActivationMatcherIndex].push_back(metricIndex); + + if (metricActivation.event_activation(i).has_deactivation_atom_matcher_id()) { + const int64_t deactivationMatcherId = + metricActivation.event_activation(i).deactivation_atom_matcher_id(); + const auto& newDeactivationIt = newAtomMatchingTrackerMap.find(deactivationMatcherId); + if (newDeactivationIt == newAtomMatchingTrackerMap.end()) { + ALOGE("Deactivation atom matcher not found in new config for event activation."); + return false; + } + int newDeactivationMatcherIndex = newDeactivationIt->second; + newEventDeactivationMap[newDeactivationMatcherIndex].push_back( + oldEventActivationIt->second); + deactivationAtomTrackerToMetricMap[newDeactivationMatcherIndex].push_back(metricIndex); + } + } + + metricsWithActivation.push_back(metricIndex); + return true; +} + +optional> createCountMetricProducerAndUpdateMetadata( + const ConfigKey& key, const StatsdConfig& config, const int64_t timeBaseNs, + const int64_t currentTimeNs, const CountMetric& metric, const int metricIndex, + const vector>& allAtomMatchingTrackers, + const unordered_map& atomMatchingTrackerMap, + vector>& allConditionTrackers, + const unordered_map& conditionTrackerMap, + const vector& initialConditionCache, const sp& wizard, + const unordered_map& stateAtomIdMap, + const unordered_map>& allStateGroupMaps, + const unordered_map& metricToActivationMap, + unordered_map>& trackerToMetricMap, + unordered_map>& conditionToMetricMap, + unordered_map>& activationAtomTrackerToMetricMap, + unordered_map>& deactivationAtomTrackerToMetricMap, + vector& metricsWithActivation) { + if (!metric.has_id() || !metric.has_what()) { + ALOGE("cannot find metric id or \"what\" in CountMetric \"%lld\"", (long long)metric.id()); + return nullopt; + } + int trackerIndex; + if (!handleMetricWithAtomMatchingTrackers(metric.what(), metricIndex, + metric.has_dimensions_in_what(), + allAtomMatchingTrackers, atomMatchingTrackerMap, + trackerToMetricMap, trackerIndex)) { + return nullopt; + } + + int conditionIndex = -1; + if (metric.has_condition()) { + if (!handleMetricWithConditions(metric.condition(), metricIndex, conditionTrackerMap, + metric.links(), allConditionTrackers, conditionIndex, + conditionToMetricMap)) { + return nullopt; + } + } else { + if (metric.links_size() > 0) { + ALOGW("metrics has a MetricConditionLink but doesn't have a condition"); + return nullopt; + } + } + + std::vector slicedStateAtoms; + unordered_map> stateGroupMap; + if (metric.slice_by_state_size() > 0) { + if (!handleMetricWithStates(config, metric.slice_by_state(), stateAtomIdMap, + allStateGroupMaps, slicedStateAtoms, stateGroupMap)) { + return nullopt; + } + } else { + if (metric.state_link_size() > 0) { + ALOGW("CountMetric has a MetricStateLink but doesn't have a slice_by_state"); + return nullopt; + } + } + + unordered_map> eventActivationMap; + unordered_map>> eventDeactivationMap; + if (!handleMetricActivation(config, metric.id(), metricIndex, metricToActivationMap, + atomMatchingTrackerMap, activationAtomTrackerToMetricMap, + deactivationAtomTrackerToMetricMap, metricsWithActivation, + eventActivationMap, eventDeactivationMap)) { + return nullopt; + } + + uint64_t metricHash; + if (!getMetricProtoHash(config, metric, metric.id(), metricToActivationMap, metricHash)) { + return nullopt; + } + + return {new CountMetricProducer(key, metric, conditionIndex, initialConditionCache, wizard, + metricHash, timeBaseNs, currentTimeNs, eventActivationMap, + eventDeactivationMap, slicedStateAtoms, stateGroupMap)}; +} + +optional> createDurationMetricProducerAndUpdateMetadata( + const ConfigKey& key, const StatsdConfig& config, const int64_t timeBaseNs, + const int64_t currentTimeNs, const DurationMetric& metric, const int metricIndex, + const vector>& allAtomMatchingTrackers, + const unordered_map& atomMatchingTrackerMap, + vector>& allConditionTrackers, + const unordered_map& conditionTrackerMap, + const vector& initialConditionCache, const sp& wizard, + const unordered_map& stateAtomIdMap, + const unordered_map>& allStateGroupMaps, + const unordered_map& metricToActivationMap, + unordered_map>& trackerToMetricMap, + unordered_map>& conditionToMetricMap, + unordered_map>& activationAtomTrackerToMetricMap, + unordered_map>& deactivationAtomTrackerToMetricMap, + vector& metricsWithActivation) { + if (!metric.has_id() || !metric.has_what()) { + ALOGE("cannot find metric id or \"what\" in DurationMetric \"%lld\"", + (long long)metric.id()); + return nullopt; + } + const auto& what_it = conditionTrackerMap.find(metric.what()); + if (what_it == conditionTrackerMap.end()) { + ALOGE("DurationMetric's \"what\" is not present in the condition trackers"); + return nullopt; + } + + const int whatIndex = what_it->second; + const Predicate& durationWhat = config.predicate(whatIndex); + if (durationWhat.contents_case() != Predicate::ContentsCase::kSimplePredicate) { + ALOGE("DurationMetric's \"what\" must be a simple condition"); + return nullopt; + } + + const SimplePredicate& simplePredicate = durationWhat.simple_predicate(); + bool nesting = simplePredicate.count_nesting(); + + int startIndex = -1, stopIndex = -1, stopAllIndex = -1; + if (!simplePredicate.has_start() || + !handleMetricWithAtomMatchingTrackers( + simplePredicate.start(), metricIndex, metric.has_dimensions_in_what(), + allAtomMatchingTrackers, atomMatchingTrackerMap, trackerToMetricMap, startIndex)) { + ALOGE("Duration metrics must specify a valid start event matcher"); + return nullopt; + } + + if (simplePredicate.has_stop() && + !handleMetricWithAtomMatchingTrackers( + simplePredicate.stop(), metricIndex, metric.has_dimensions_in_what(), + allAtomMatchingTrackers, atomMatchingTrackerMap, trackerToMetricMap, stopIndex)) { + return nullopt; + } + + if (simplePredicate.has_stop_all() && + !handleMetricWithAtomMatchingTrackers(simplePredicate.stop_all(), metricIndex, + metric.has_dimensions_in_what(), + allAtomMatchingTrackers, atomMatchingTrackerMap, + trackerToMetricMap, stopAllIndex)) { + return nullopt; + } + + FieldMatcher internalDimensions = simplePredicate.dimensions(); + + int conditionIndex = -1; + if (metric.has_condition()) { + if (!handleMetricWithConditions(metric.condition(), metricIndex, conditionTrackerMap, + metric.links(), allConditionTrackers, conditionIndex, + conditionToMetricMap)) { + return nullopt; + } + } else if (metric.links_size() > 0) { + ALOGW("metrics has a MetricConditionLink but doesn't have a condition"); + return nullopt; + } + + std::vector slicedStateAtoms; + unordered_map> stateGroupMap; + if (metric.slice_by_state_size() > 0) { + if (metric.aggregation_type() == DurationMetric::MAX_SPARSE) { + ALOGE("DurationMetric with aggregation type MAX_SPARSE cannot be sliced by state"); + return nullopt; + } + if (!handleMetricWithStates(config, metric.slice_by_state(), stateAtomIdMap, + allStateGroupMaps, slicedStateAtoms, stateGroupMap)) { + return nullopt; + } + } else if (metric.state_link_size() > 0) { + ALOGW("DurationMetric has a MetricStateLink but doesn't have a sliced state"); + return nullopt; + } + + // Check that all metric state links are a subset of dimensions_in_what fields. + std::vector dimensionsInWhat; + translateFieldMatcher(metric.dimensions_in_what(), &dimensionsInWhat); + for (const auto& stateLink : metric.state_link()) { + if (!handleMetricWithStateLink(stateLink.fields_in_what(), dimensionsInWhat)) { + ALOGW("DurationMetric's MetricStateLinks must be a subset of dimensions in what"); + return nullopt; + } + } + + unordered_map> eventActivationMap; + unordered_map>> eventDeactivationMap; + if (!handleMetricActivation(config, metric.id(), metricIndex, metricToActivationMap, + atomMatchingTrackerMap, activationAtomTrackerToMetricMap, + deactivationAtomTrackerToMetricMap, metricsWithActivation, + eventActivationMap, eventDeactivationMap)) { + return nullopt; + } + + uint64_t metricHash; + if (!getMetricProtoHash(config, metric, metric.id(), metricToActivationMap, metricHash)) { + return nullopt; + } + + if (metric.has_threshold()) { + switch (metric.threshold().value_comparison_case()) { + case UploadThreshold::kLtInt: + case UploadThreshold::kGtInt: + case UploadThreshold::kLteInt: + case UploadThreshold::kGteInt: + break; + default: + ALOGE("Duration metric incorrect upload threshold type or no type used"); + return nullopt; + break; + } + } + + sp producer = new DurationMetricProducer( + key, metric, conditionIndex, initialConditionCache, whatIndex, startIndex, stopIndex, + stopAllIndex, nesting, wizard, metricHash, internalDimensions, timeBaseNs, + currentTimeNs, eventActivationMap, eventDeactivationMap, slicedStateAtoms, + stateGroupMap); + if (!producer->isValid()) { + return nullopt; + } + return {producer}; +} + +optional> createEventMetricProducerAndUpdateMetadata( + const ConfigKey& key, const StatsdConfig& config, const int64_t timeBaseNs, + const EventMetric& metric, const int metricIndex, + const vector>& allAtomMatchingTrackers, + const unordered_map& atomMatchingTrackerMap, + vector>& allConditionTrackers, + const unordered_map& conditionTrackerMap, + const vector& initialConditionCache, const sp& wizard, + const unordered_map& metricToActivationMap, + unordered_map>& trackerToMetricMap, + unordered_map>& conditionToMetricMap, + unordered_map>& activationAtomTrackerToMetricMap, + unordered_map>& deactivationAtomTrackerToMetricMap, + vector& metricsWithActivation) { + if (!metric.has_id() || !metric.has_what()) { + ALOGE("cannot find the metric name or what in config"); + return nullopt; + } + int trackerIndex; + if (!handleMetricWithAtomMatchingTrackers(metric.what(), metricIndex, false, + allAtomMatchingTrackers, atomMatchingTrackerMap, + trackerToMetricMap, trackerIndex)) { + return nullopt; + } + + int conditionIndex = -1; + if (metric.has_condition()) { + if (!handleMetricWithConditions(metric.condition(), metricIndex, conditionTrackerMap, + metric.links(), allConditionTrackers, conditionIndex, + conditionToMetricMap)) { + return nullopt; + } + } else { + if (metric.links_size() > 0) { + ALOGW("metrics has a MetricConditionLink but doesn't have a condition"); + return nullopt; + } + } + + unordered_map> eventActivationMap; + unordered_map>> eventDeactivationMap; + bool success = handleMetricActivation(config, metric.id(), metricIndex, metricToActivationMap, + atomMatchingTrackerMap, activationAtomTrackerToMetricMap, + deactivationAtomTrackerToMetricMap, metricsWithActivation, + eventActivationMap, eventDeactivationMap); + if (!success) return nullptr; + + uint64_t metricHash; + if (!getMetricProtoHash(config, metric, metric.id(), metricToActivationMap, metricHash)) { + return nullopt; + } + + return {new EventMetricProducer(key, metric, conditionIndex, initialConditionCache, wizard, + metricHash, timeBaseNs, eventActivationMap, + eventDeactivationMap)}; +} + +optional> createValueMetricProducerAndUpdateMetadata( + const ConfigKey& key, const StatsdConfig& config, const int64_t timeBaseNs, + const int64_t currentTimeNs, const sp& pullerManager, + const ValueMetric& metric, const int metricIndex, + const vector>& allAtomMatchingTrackers, + const unordered_map& atomMatchingTrackerMap, + vector>& allConditionTrackers, + const unordered_map& conditionTrackerMap, + const vector& initialConditionCache, const sp& wizard, + const sp& matcherWizard, + const unordered_map& stateAtomIdMap, + const unordered_map>& allStateGroupMaps, + const unordered_map& metricToActivationMap, + unordered_map>& trackerToMetricMap, + unordered_map>& conditionToMetricMap, + unordered_map>& activationAtomTrackerToMetricMap, + unordered_map>& deactivationAtomTrackerToMetricMap, + vector& metricsWithActivation) { + if (!metric.has_id() || !metric.has_what()) { + ALOGE("cannot find metric id or \"what\" in ValueMetric \"%lld\"", (long long)metric.id()); + return nullopt; + } + if (!metric.has_value_field()) { + ALOGE("cannot find \"value_field\" in ValueMetric \"%lld\"", (long long)metric.id()); + return nullopt; + } + std::vector fieldMatchers; + translateFieldMatcher(metric.value_field(), &fieldMatchers); + if (fieldMatchers.size() < 1) { + ALOGE("incorrect \"value_field\" in ValueMetric \"%lld\"", (long long)metric.id()); + return nullopt; + } + + int trackerIndex; + if (!handleMetricWithAtomMatchingTrackers(metric.what(), metricIndex, + metric.has_dimensions_in_what(), + allAtomMatchingTrackers, atomMatchingTrackerMap, + trackerToMetricMap, trackerIndex)) { + return nullopt; + } + + sp atomMatcher = allAtomMatchingTrackers.at(trackerIndex); + // If it is pulled atom, it should be simple matcher with one tagId. + if (atomMatcher->getAtomIds().size() != 1) { + return nullopt; + } + int atomTagId = *(atomMatcher->getAtomIds().begin()); + int pullTagId = pullerManager->PullerForMatcherExists(atomTagId) ? atomTagId : -1; + + int conditionIndex = -1; + if (metric.has_condition()) { + if (!handleMetricWithConditions(metric.condition(), metricIndex, conditionTrackerMap, + metric.links(), allConditionTrackers, conditionIndex, + conditionToMetricMap)) { + return nullopt; + } + } else if (metric.links_size() > 0) { + ALOGE("metrics has a MetricConditionLink but doesn't have a condition"); + return nullopt; + } + + std::vector slicedStateAtoms; + unordered_map> stateGroupMap; + if (metric.slice_by_state_size() > 0) { + if (!handleMetricWithStates(config, metric.slice_by_state(), stateAtomIdMap, + allStateGroupMaps, slicedStateAtoms, stateGroupMap)) { + return nullopt; + } + } else if (metric.state_link_size() > 0) { + ALOGE("ValueMetric has a MetricStateLink but doesn't have a sliced state"); + return nullopt; + } + + // Check that all metric state links are a subset of dimensions_in_what fields. + std::vector dimensionsInWhat; + translateFieldMatcher(metric.dimensions_in_what(), &dimensionsInWhat); + for (const auto& stateLink : metric.state_link()) { + if (!handleMetricWithStateLink(stateLink.fields_in_what(), dimensionsInWhat)) { + ALOGW("ValueMetric's MetricStateLinks must be a subset of the dimensions in what"); + return nullopt; + } + } + + unordered_map> eventActivationMap; + unordered_map>> eventDeactivationMap; + if (!handleMetricActivation(config, metric.id(), metricIndex, metricToActivationMap, + atomMatchingTrackerMap, activationAtomTrackerToMetricMap, + deactivationAtomTrackerToMetricMap, metricsWithActivation, + eventActivationMap, eventDeactivationMap)) { + return nullopt; + } + + uint64_t metricHash; + if (!getMetricProtoHash(config, metric, metric.id(), metricToActivationMap, metricHash)) { + return nullopt; + } + + return {new ValueMetricProducer(key, metric, conditionIndex, initialConditionCache, wizard, + metricHash, trackerIndex, matcherWizard, pullTagId, timeBaseNs, + currentTimeNs, pullerManager, eventActivationMap, + eventDeactivationMap, slicedStateAtoms, stateGroupMap)}; +} + +optional> createGaugeMetricProducerAndUpdateMetadata( + const ConfigKey& key, const StatsdConfig& config, const int64_t timeBaseNs, + const int64_t currentTimeNs, const sp& pullerManager, + const GaugeMetric& metric, const int metricIndex, + const vector>& allAtomMatchingTrackers, + const unordered_map& atomMatchingTrackerMap, + vector>& allConditionTrackers, + const unordered_map& conditionTrackerMap, + const vector& initialConditionCache, const sp& wizard, + const sp& matcherWizard, + const unordered_map& metricToActivationMap, + unordered_map>& trackerToMetricMap, + unordered_map>& conditionToMetricMap, + unordered_map>& activationAtomTrackerToMetricMap, + unordered_map>& deactivationAtomTrackerToMetricMap, + vector& metricsWithActivation) { + if (!metric.has_id() || !metric.has_what()) { + ALOGE("cannot find metric id or \"what\" in GaugeMetric \"%lld\"", (long long)metric.id()); + return nullopt; + } + + if ((!metric.gauge_fields_filter().has_include_all() || + (metric.gauge_fields_filter().include_all() == false)) && + !hasLeafNode(metric.gauge_fields_filter().fields())) { + ALOGW("Incorrect field filter setting in GaugeMetric %lld", (long long)metric.id()); + return nullopt; + } + if ((metric.gauge_fields_filter().has_include_all() && + metric.gauge_fields_filter().include_all() == true) && + hasLeafNode(metric.gauge_fields_filter().fields())) { + ALOGW("Incorrect field filter setting in GaugeMetric %lld", (long long)metric.id()); + return nullopt; + } + + int trackerIndex; + if (!handleMetricWithAtomMatchingTrackers(metric.what(), metricIndex, + metric.has_dimensions_in_what(), + allAtomMatchingTrackers, atomMatchingTrackerMap, + trackerToMetricMap, trackerIndex)) { + return nullopt; + } + + sp atomMatcher = allAtomMatchingTrackers.at(trackerIndex); + // For GaugeMetric atom, it should be simple matcher with one tagId. + if (atomMatcher->getAtomIds().size() != 1) { + return nullopt; + } + int atomTagId = *(atomMatcher->getAtomIds().begin()); + int pullTagId = pullerManager->PullerForMatcherExists(atomTagId) ? atomTagId : -1; + + int triggerTrackerIndex; + int triggerAtomId = -1; + if (metric.has_trigger_event()) { + if (pullTagId == -1) { + ALOGW("Pull atom not specified for trigger"); + return nullopt; + } + // trigger_event should be used with FIRST_N_SAMPLES + if (metric.sampling_type() != GaugeMetric::FIRST_N_SAMPLES) { + ALOGW("Gauge Metric with trigger event must have sampling type FIRST_N_SAMPLES"); + return nullopt; + } + if (!handleMetricWithAtomMatchingTrackers(metric.trigger_event(), metricIndex, + /*enforceOneAtom=*/true, allAtomMatchingTrackers, + atomMatchingTrackerMap, trackerToMetricMap, + triggerTrackerIndex)) { + return nullopt; + } + sp triggerAtomMatcher = + allAtomMatchingTrackers.at(triggerTrackerIndex); + triggerAtomId = *(triggerAtomMatcher->getAtomIds().begin()); + } + + if (!metric.has_trigger_event() && pullTagId != -1 && + metric.sampling_type() == GaugeMetric::FIRST_N_SAMPLES) { + ALOGW("FIRST_N_SAMPLES is only for pushed event or pull_on_trigger"); + return nullopt; + } + + int conditionIndex = -1; + if (metric.has_condition()) { + if (!handleMetricWithConditions(metric.condition(), metricIndex, conditionTrackerMap, + metric.links(), allConditionTrackers, conditionIndex, + conditionToMetricMap)) { + return nullopt; + } + } else { + if (metric.links_size() > 0) { + ALOGW("metrics has a MetricConditionLink but doesn't have a condition"); + return nullopt; + } + } + + unordered_map> eventActivationMap; + unordered_map>> eventDeactivationMap; + if (!handleMetricActivation(config, metric.id(), metricIndex, metricToActivationMap, + atomMatchingTrackerMap, activationAtomTrackerToMetricMap, + deactivationAtomTrackerToMetricMap, metricsWithActivation, + eventActivationMap, eventDeactivationMap)) { + return nullopt; + } + + uint64_t metricHash; + if (!getMetricProtoHash(config, metric, metric.id(), metricToActivationMap, metricHash)) { + return nullopt; + } + + return {new GaugeMetricProducer(key, metric, conditionIndex, initialConditionCache, wizard, + metricHash, trackerIndex, matcherWizard, pullTagId, + triggerAtomId, atomTagId, timeBaseNs, currentTimeNs, + pullerManager, eventActivationMap, eventDeactivationMap)}; +} + +optional> createAnomalyTracker( + const Alert& alert, const sp& anomalyAlarmMonitor, + const UpdateStatus& updateStatus, const int64_t currentTimeNs, + const unordered_map& metricProducerMap, + vector>& allMetricProducers) { + const auto& itr = metricProducerMap.find(alert.metric_id()); + if (itr == metricProducerMap.end()) { + ALOGW("alert \"%lld\" has unknown metric id: \"%lld\"", (long long)alert.id(), + (long long)alert.metric_id()); + return nullopt; + } + if (!alert.has_trigger_if_sum_gt()) { + ALOGW("invalid alert: missing threshold"); + return nullopt; + } + if (alert.trigger_if_sum_gt() < 0 || alert.num_buckets() <= 0) { + ALOGW("invalid alert: threshold=%f num_buckets= %d", alert.trigger_if_sum_gt(), + alert.num_buckets()); + return nullopt; + } + const int metricIndex = itr->second; + sp metric = allMetricProducers[metricIndex]; + sp anomalyTracker = + metric->addAnomalyTracker(alert, anomalyAlarmMonitor, updateStatus, currentTimeNs); + if (anomalyTracker == nullptr) { + // The ALOGW for this invalid alert was already displayed in addAnomalyTracker(). + return nullopt; + } + return {anomalyTracker}; +} + +bool initAtomMatchingTrackers(const StatsdConfig& config, const sp& uidMap, + unordered_map& atomMatchingTrackerMap, + vector>& allAtomMatchingTrackers, + set& allTagIds) { + vector matcherConfigs; + const int atomMatcherCount = config.atom_matcher_size(); + matcherConfigs.reserve(atomMatcherCount); + allAtomMatchingTrackers.reserve(atomMatcherCount); + + for (int i = 0; i < atomMatcherCount; i++) { + const AtomMatcher& logMatcher = config.atom_matcher(i); + sp tracker = createAtomMatchingTracker(logMatcher, i, uidMap); + if (tracker == nullptr) { + return false; + } + allAtomMatchingTrackers.push_back(tracker); + if (atomMatchingTrackerMap.find(logMatcher.id()) != atomMatchingTrackerMap.end()) { + ALOGE("Duplicate AtomMatcher found!"); + return false; + } + atomMatchingTrackerMap[logMatcher.id()] = i; + matcherConfigs.push_back(logMatcher); + } + + vector stackTracker2(allAtomMatchingTrackers.size(), false); + for (auto& matcher : allAtomMatchingTrackers) { + if (!matcher->init(matcherConfigs, allAtomMatchingTrackers, atomMatchingTrackerMap, + stackTracker2)) { + return false; + } + // Collect all the tag ids that are interesting. TagIds exist in leaf nodes only. + const set& tagIds = matcher->getAtomIds(); + allTagIds.insert(tagIds.begin(), tagIds.end()); + } + return true; +} + +bool initConditions(const ConfigKey& key, const StatsdConfig& config, + const unordered_map& atomMatchingTrackerMap, + unordered_map& conditionTrackerMap, + vector>& allConditionTrackers, + unordered_map>& trackerToConditionMap, + vector& initialConditionCache) { + vector conditionConfigs; + const int conditionTrackerCount = config.predicate_size(); + conditionConfigs.reserve(conditionTrackerCount); + allConditionTrackers.reserve(conditionTrackerCount); + initialConditionCache.assign(conditionTrackerCount, ConditionState::kNotEvaluated); + + for (int i = 0; i < conditionTrackerCount; i++) { + const Predicate& condition = config.predicate(i); + sp tracker = + createConditionTracker(key, condition, i, atomMatchingTrackerMap); + if (tracker == nullptr) { + return false; + } + allConditionTrackers.push_back(tracker); + if (conditionTrackerMap.find(condition.id()) != conditionTrackerMap.end()) { + ALOGE("Duplicate Predicate found!"); + return false; + } + conditionTrackerMap[condition.id()] = i; + conditionConfigs.push_back(condition); + } + + vector stackTracker(allConditionTrackers.size(), false); + for (size_t i = 0; i < allConditionTrackers.size(); i++) { + auto& conditionTracker = allConditionTrackers[i]; + if (!conditionTracker->init(conditionConfigs, allConditionTrackers, conditionTrackerMap, + stackTracker, initialConditionCache)) { + return false; + } + for (const int trackerIndex : conditionTracker->getAtomMatchingTrackerIndex()) { + auto& conditionList = trackerToConditionMap[trackerIndex]; + conditionList.push_back(i); + } + } + return true; +} + +bool initStates(const StatsdConfig& config, unordered_map& stateAtomIdMap, + unordered_map>& allStateGroupMaps, + map& stateProtoHashes) { + for (int i = 0; i < config.state_size(); i++) { + const State& state = config.state(i); + const int64_t stateId = state.id(); + stateAtomIdMap[stateId] = state.atom_id(); + + string serializedState; + if (!state.SerializeToString(&serializedState)) { + ALOGE("Unable to serialize state %lld", (long long)stateId); + return false; + } + stateProtoHashes[stateId] = Hash64(serializedState); + + const StateMap& stateMap = state.map(); + for (auto group : stateMap.group()) { + for (auto value : group.value()) { + allStateGroupMaps[stateId][value] = group.group_id(); + } + } + } + + return true; +} + +bool initMetrics(const ConfigKey& key, const StatsdConfig& config, const int64_t timeBaseTimeNs, + const int64_t currentTimeNs, const sp& pullerManager, + const unordered_map& atomMatchingTrackerMap, + const unordered_map& conditionTrackerMap, + const vector>& allAtomMatchingTrackers, + const unordered_map& stateAtomIdMap, + const unordered_map>& allStateGroupMaps, + vector>& allConditionTrackers, + const vector& initialConditionCache, + vector>& allMetricProducers, + unordered_map>& conditionToMetricMap, + unordered_map>& trackerToMetricMap, + unordered_map& metricMap, std::set& noReportMetricIds, + unordered_map>& activationAtomTrackerToMetricMap, + unordered_map>& deactivationAtomTrackerToMetricMap, + vector& metricsWithActivation) { + sp wizard = new ConditionWizard(allConditionTrackers); + sp matcherWizard = new EventMatcherWizard(allAtomMatchingTrackers); + const int allMetricsCount = config.count_metric_size() + config.duration_metric_size() + + config.event_metric_size() + config.gauge_metric_size() + + config.value_metric_size(); + allMetricProducers.reserve(allMetricsCount); + + // Construct map from metric id to metric activation index. The map will be used to determine + // the metric activation corresponding to a metric. + unordered_map metricToActivationMap; + for (int i = 0; i < config.metric_activation_size(); i++) { + const MetricActivation& metricActivation = config.metric_activation(i); + int64_t metricId = metricActivation.metric_id(); + if (metricToActivationMap.find(metricId) != metricToActivationMap.end()) { + ALOGE("Metric %lld has multiple MetricActivations", (long long)metricId); + return false; + } + metricToActivationMap.insert({metricId, i}); + } + + // Build MetricProducers for each metric defined in config. + // build CountMetricProducer + for (int i = 0; i < config.count_metric_size(); i++) { + int metricIndex = allMetricProducers.size(); + const CountMetric& metric = config.count_metric(i); + metricMap.insert({metric.id(), metricIndex}); + optional> producer = createCountMetricProducerAndUpdateMetadata( + key, config, timeBaseTimeNs, currentTimeNs, metric, metricIndex, + allAtomMatchingTrackers, atomMatchingTrackerMap, allConditionTrackers, + conditionTrackerMap, initialConditionCache, wizard, stateAtomIdMap, + allStateGroupMaps, metricToActivationMap, trackerToMetricMap, conditionToMetricMap, + activationAtomTrackerToMetricMap, deactivationAtomTrackerToMetricMap, + metricsWithActivation); + if (!producer) { + return false; + } + allMetricProducers.push_back(producer.value()); + } + + // build DurationMetricProducer + for (int i = 0; i < config.duration_metric_size(); i++) { + int metricIndex = allMetricProducers.size(); + const DurationMetric& metric = config.duration_metric(i); + metricMap.insert({metric.id(), metricIndex}); + + optional> producer = createDurationMetricProducerAndUpdateMetadata( + key, config, timeBaseTimeNs, currentTimeNs, metric, metricIndex, + allAtomMatchingTrackers, atomMatchingTrackerMap, allConditionTrackers, + conditionTrackerMap, initialConditionCache, wizard, stateAtomIdMap, + allStateGroupMaps, metricToActivationMap, trackerToMetricMap, conditionToMetricMap, + activationAtomTrackerToMetricMap, deactivationAtomTrackerToMetricMap, + metricsWithActivation); + if (!producer) { + return false; + } + allMetricProducers.push_back(producer.value()); + } + + // build EventMetricProducer + for (int i = 0; i < config.event_metric_size(); i++) { + int metricIndex = allMetricProducers.size(); + const EventMetric& metric = config.event_metric(i); + metricMap.insert({metric.id(), metricIndex}); + optional> producer = createEventMetricProducerAndUpdateMetadata( + key, config, timeBaseTimeNs, metric, metricIndex, allAtomMatchingTrackers, + atomMatchingTrackerMap, allConditionTrackers, conditionTrackerMap, + initialConditionCache, wizard, metricToActivationMap, trackerToMetricMap, + conditionToMetricMap, activationAtomTrackerToMetricMap, + deactivationAtomTrackerToMetricMap, metricsWithActivation); + if (!producer) { + return false; + } + allMetricProducers.push_back(producer.value()); + } + + // build ValueMetricProducer + for (int i = 0; i < config.value_metric_size(); i++) { + int metricIndex = allMetricProducers.size(); + const ValueMetric& metric = config.value_metric(i); + metricMap.insert({metric.id(), metricIndex}); + optional> producer = createValueMetricProducerAndUpdateMetadata( + key, config, timeBaseTimeNs, currentTimeNs, pullerManager, metric, metricIndex, + allAtomMatchingTrackers, atomMatchingTrackerMap, allConditionTrackers, + conditionTrackerMap, initialConditionCache, wizard, matcherWizard, stateAtomIdMap, + allStateGroupMaps, metricToActivationMap, trackerToMetricMap, conditionToMetricMap, + activationAtomTrackerToMetricMap, deactivationAtomTrackerToMetricMap, + metricsWithActivation); + if (!producer) { + return false; + } + allMetricProducers.push_back(producer.value()); + } + + // Gauge metrics. + for (int i = 0; i < config.gauge_metric_size(); i++) { + int metricIndex = allMetricProducers.size(); + const GaugeMetric& metric = config.gauge_metric(i); + metricMap.insert({metric.id(), metricIndex}); + optional> producer = createGaugeMetricProducerAndUpdateMetadata( + key, config, timeBaseTimeNs, currentTimeNs, pullerManager, metric, metricIndex, + allAtomMatchingTrackers, atomMatchingTrackerMap, allConditionTrackers, + conditionTrackerMap, initialConditionCache, wizard, matcherWizard, + metricToActivationMap, trackerToMetricMap, conditionToMetricMap, + activationAtomTrackerToMetricMap, deactivationAtomTrackerToMetricMap, + metricsWithActivation); + if (!producer) { + return false; + } + allMetricProducers.push_back(producer.value()); + } + for (int i = 0; i < config.no_report_metric_size(); ++i) { + const auto no_report_metric = config.no_report_metric(i); + if (metricMap.find(no_report_metric) == metricMap.end()) { + ALOGW("no_report_metric %" PRId64 " not exist", no_report_metric); + return false; + } + noReportMetricIds.insert(no_report_metric); + } + + const set whitelistedAtomIds(config.whitelisted_atom_ids().begin(), + config.whitelisted_atom_ids().end()); + for (const auto& it : allMetricProducers) { + // Register metrics to StateTrackers + for (int atomId : it->getSlicedStateAtoms()) { + // Register listener for non-whitelisted atoms only. Using whitelisted atom as a sliced + // state atom is not allowed. + if (whitelistedAtomIds.find(atomId) == whitelistedAtomIds.end()) { + StateManager::getInstance().registerListener(atomId, it); + } else { + return false; + } + } + } + return true; +} + +bool initAlerts(const StatsdConfig& config, const int64_t currentTimeNs, + const unordered_map& metricProducerMap, + unordered_map& alertTrackerMap, + const sp& anomalyAlarmMonitor, + vector>& allMetricProducers, + vector>& allAnomalyTrackers) { + for (int i = 0; i < config.alert_size(); i++) { + const Alert& alert = config.alert(i); + alertTrackerMap.insert(std::make_pair(alert.id(), allAnomalyTrackers.size())); + optional> anomalyTracker = + createAnomalyTracker(alert, anomalyAlarmMonitor, UpdateStatus::UPDATE_NEW, + currentTimeNs, metricProducerMap, allMetricProducers); + if (!anomalyTracker) { + return false; + } + allAnomalyTrackers.push_back(anomalyTracker.value()); + } + if (!initSubscribersForSubscriptionType(config, Subscription::ALERT, alertTrackerMap, + allAnomalyTrackers)) { + return false; + } + return true; +} + +bool initAlarms(const StatsdConfig& config, const ConfigKey& key, + const sp& periodicAlarmMonitor, const int64_t timeBaseNs, + const int64_t currentTimeNs, vector>& allAlarmTrackers) { + unordered_map alarmTrackerMap; + int64_t startMillis = timeBaseNs / 1000 / 1000; + int64_t currentTimeMillis = currentTimeNs / 1000 / 1000; + for (int i = 0; i < config.alarm_size(); i++) { + const Alarm& alarm = config.alarm(i); + if (alarm.offset_millis() <= 0) { + ALOGW("Alarm offset_millis should be larger than 0."); + return false; + } + if (alarm.period_millis() <= 0) { + ALOGW("Alarm period_millis should be larger than 0."); + return false; + } + alarmTrackerMap.insert(std::make_pair(alarm.id(), allAlarmTrackers.size())); + allAlarmTrackers.push_back( + new AlarmTracker(startMillis, currentTimeMillis, alarm, key, periodicAlarmMonitor)); + } + if (!initSubscribersForSubscriptionType(config, Subscription::ALARM, alarmTrackerMap, + allAlarmTrackers)) { + return false; + } + return true; +} + +bool initStatsdConfig(const ConfigKey& key, const StatsdConfig& config, const sp& uidMap, + const sp& pullerManager, + const sp& anomalyAlarmMonitor, + const sp& periodicAlarmMonitor, const int64_t timeBaseNs, + const int64_t currentTimeNs, set& allTagIds, + vector>& allAtomMatchingTrackers, + unordered_map& atomMatchingTrackerMap, + vector>& allConditionTrackers, + unordered_map& conditionTrackerMap, + vector>& allMetricProducers, + unordered_map& metricProducerMap, + vector>& allAnomalyTrackers, + vector>& allPeriodicAlarmTrackers, + unordered_map>& conditionToMetricMap, + unordered_map>& trackerToMetricMap, + unordered_map>& trackerToConditionMap, + unordered_map>& activationAtomTrackerToMetricMap, + unordered_map>& deactivationAtomTrackerToMetricMap, + unordered_map& alertTrackerMap, + vector& metricsWithActivation, map& stateProtoHashes, + set& noReportMetricIds) { + vector initialConditionCache; + unordered_map stateAtomIdMap; + unordered_map> allStateGroupMaps; + + if (!initAtomMatchingTrackers(config, uidMap, atomMatchingTrackerMap, allAtomMatchingTrackers, + allTagIds)) { + ALOGE("initAtomMatchingTrackers failed"); + return false; + } + VLOG("initAtomMatchingTrackers succeed..."); + + if (!initConditions(key, config, atomMatchingTrackerMap, conditionTrackerMap, + allConditionTrackers, trackerToConditionMap, initialConditionCache)) { + ALOGE("initConditionTrackers failed"); + return false; + } + + if (!initStates(config, stateAtomIdMap, allStateGroupMaps, stateProtoHashes)) { + ALOGE("initStates failed"); + return false; + } + if (!initMetrics(key, config, timeBaseNs, currentTimeNs, pullerManager, atomMatchingTrackerMap, + conditionTrackerMap, allAtomMatchingTrackers, stateAtomIdMap, + allStateGroupMaps, allConditionTrackers, initialConditionCache, + allMetricProducers, conditionToMetricMap, trackerToMetricMap, + metricProducerMap, noReportMetricIds, activationAtomTrackerToMetricMap, + deactivationAtomTrackerToMetricMap, metricsWithActivation)) { + ALOGE("initMetricProducers failed"); + return false; + } + if (!initAlerts(config, currentTimeNs, metricProducerMap, alertTrackerMap, anomalyAlarmMonitor, + allMetricProducers, allAnomalyTrackers)) { + ALOGE("initAlerts failed"); + return false; + } + if (!initAlarms(config, key, periodicAlarmMonitor, timeBaseNs, currentTimeNs, + allPeriodicAlarmTrackers)) { + ALOGE("initAlarms failed"); + return false; + } + + return true; +} + +} // namespace statsd +} // namespace os +} // namespace android diff --git a/statsd/src/metrics/parsing_utils/metrics_manager_util.h b/statsd/src/metrics/parsing_utils/metrics_manager_util.h new file mode 100644 index 00000000..5d66a0f1 --- /dev/null +++ b/statsd/src/metrics/parsing_utils/metrics_manager_util.h @@ -0,0 +1,343 @@ +/* + * 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. + */ + +#pragma once + +#include +#include +#include + +#include "anomaly/AlarmTracker.h" +#include "condition/ConditionTracker.h" +#include "external/StatsPullerManager.h" +#include "matchers/AtomMatchingTracker.h" +#include "metrics/MetricProducer.h" + +namespace android { +namespace os { +namespace statsd { + +// Helper functions for creating, validating, and updating config components from StatsdConfig. +// Should only be called from metrics_manager_util and config_update_utils. + +// Create a AtomMatchingTracker. +// input: +// [logMatcher]: the input AtomMatcher from the StatsdConfig +// [index]: the index of the matcher +// output: +// new AtomMatchingTracker, or null if the tracker is unable to be created +sp createAtomMatchingTracker(const AtomMatcher& logMatcher, const int index, + const sp& uidMap); + +// Create a ConditionTracker. +// input: +// [predicate]: the input Predicate from the StatsdConfig +// [index]: the index of the condition tracker +// [atomMatchingTrackerMap]: map of atom matcher id to its index in allAtomMatchingTrackers +// output: +// new ConditionTracker, or null if the tracker is unable to be created +sp createConditionTracker( + const ConfigKey& key, const Predicate& predicate, const int index, + const unordered_map& atomMatchingTrackerMap); + +// Get the hash of a metric, combining the activation if the metric has one. +bool getMetricProtoHash(const StatsdConfig& config, const google::protobuf::MessageLite& metric, + const int64_t id, + const std::unordered_map& metricToActivationMap, + uint64_t& metricHash); + +// 1. Validates matcher existence +// 2. Enforces matchers with dimensions and those used for trigger_event are about one atom +// 3. Gets matcher index and updates tracker to metric map +bool handleMetricWithAtomMatchingTrackers( + const int64_t matcherId, const int metricIndex, const bool enforceOneAtom, + const std::vector>& allAtomMatchingTrackers, + const std::unordered_map& atomMatchingTrackerMap, + std::unordered_map>& trackerToMetricMap, int& logTrackerIndex); + +// 1. Validates condition existence, including those in links +// 2. Gets condition index and updates condition to metric map +bool handleMetricWithConditions( + const int64_t condition, const int metricIndex, + const std::unordered_map& conditionTrackerMap, + const ::google::protobuf::RepeatedPtrField<::android::os::statsd::MetricConditionLink>& + links, + const std::vector>& allConditionTrackers, int& conditionIndex, + std::unordered_map>& conditionToMetricMap); + +// Validates a metricActivation and populates state. +// Fills the new event activation/deactivation maps, preserving the existing activations. +// Returns false if there are errors. +bool handleMetricActivationOnConfigUpdate( + const StatsdConfig& config, const int64_t metricId, const int metricIndex, + const std::unordered_map& metricToActivationMap, + const std::unordered_map& oldAtomMatchingTrackerMap, + const std::unordered_map& newAtomMatchingTrackerMap, + const std::unordered_map>& oldEventActivationMap, + std::unordered_map>& activationAtomTrackerToMetricMap, + std::unordered_map>& deactivationAtomTrackerToMetricMap, + std::vector& metricsWithActivation, + std::unordered_map>& newEventActivationMap, + std::unordered_map>>& newEventDeactivationMap); + +// Creates a CountMetricProducer and updates the vectors/maps used by MetricsManager with +// the appropriate indices. Returns an sp to the producer, or nullopt if there was an error. +optional> createCountMetricProducerAndUpdateMetadata( + const ConfigKey& key, const StatsdConfig& config, const int64_t timeBaseNs, + const int64_t currentTimeNs, const CountMetric& metric, const int metricIndex, + const std::vector>& allAtomMatchingTrackers, + const std::unordered_map& atomMatchingTrackerMap, + std::vector>& allConditionTrackers, + const std::unordered_map& conditionTrackerMap, + const std::vector& initialConditionCache, const sp& wizard, + const std::unordered_map& stateAtomIdMap, + const std::unordered_map>& allStateGroupMaps, + const std::unordered_map& metricToActivationMap, + std::unordered_map>& trackerToMetricMap, + std::unordered_map>& conditionToMetricMap, + std::unordered_map>& activationAtomTrackerToMetricMap, + std::unordered_map>& deactivationAtomTrackerToMetricMap, + std::vector& metricsWithActivation); + +// Creates a DurationMetricProducer and updates the vectors/maps used by MetricsManager with +// the appropriate indices. Returns an sp to the producer, or nullopt if there was an error. +optional> createDurationMetricProducerAndUpdateMetadata( + const ConfigKey& key, const StatsdConfig& config, const int64_t timeBaseNs, + const int64_t currentTimeNs, const DurationMetric& metric, const int metricIndex, + const std::vector>& allAtomMatchingTrackers, + const std::unordered_map& atomMatchingTrackerMap, + std::vector>& allConditionTrackers, + const std::unordered_map& conditionTrackerMap, + const std::vector& initialConditionCache, const sp& wizard, + const std::unordered_map& stateAtomIdMap, + const std::unordered_map>& allStateGroupMaps, + const std::unordered_map& metricToActivationMap, + std::unordered_map>& trackerToMetricMap, + std::unordered_map>& conditionToMetricMap, + std::unordered_map>& activationAtomTrackerToMetricMap, + std::unordered_map>& deactivationAtomTrackerToMetricMap, + std::vector& metricsWithActivation); + +// Creates an EventMetricProducer and updates the vectors/maps used by MetricsManager with +// the appropriate indices. Returns an sp to the producer, or nullopt if there was an error. +optional> createEventMetricProducerAndUpdateMetadata( + const ConfigKey& key, const StatsdConfig& config, const int64_t timeBaseNs, + const EventMetric& metric, const int metricIndex, + const std::vector>& allAtomMatchingTrackers, + const std::unordered_map& atomMatchingTrackerMap, + std::vector>& allConditionTrackers, + const std::unordered_map& conditionTrackerMap, + const std::vector& initialConditionCache, const sp& wizard, + const std::unordered_map& metricToActivationMap, + std::unordered_map>& trackerToMetricMap, + std::unordered_map>& conditionToMetricMap, + std::unordered_map>& activationAtomTrackerToMetricMap, + std::unordered_map>& deactivationAtomTrackerToMetricMap, + std::vector& metricsWithActivation); + +// Creates a CountMetricProducer and updates the vectors/maps used by MetricsManager with +// the appropriate indices. Returns an sp to the producer, or nullopt if there was an error. +optional> createValueMetricProducerAndUpdateMetadata( + const ConfigKey& key, const StatsdConfig& config, const int64_t timeBaseNs, + const int64_t currentTimeNs, const sp& pullerManager, + const ValueMetric& metric, const int metricIndex, + const std::vector>& allAtomMatchingTrackers, + const std::unordered_map& atomMatchingTrackerMap, + std::vector>& allConditionTrackers, + const std::unordered_map& conditionTrackerMap, + const std::vector& initialConditionCache, const sp& wizard, + const sp& matcherWizard, + const std::unordered_map& stateAtomIdMap, + const std::unordered_map>& allStateGroupMaps, + const std::unordered_map& metricToActivationMap, + std::unordered_map>& trackerToMetricMap, + std::unordered_map>& conditionToMetricMap, + std::unordered_map>& activationAtomTrackerToMetricMap, + std::unordered_map>& deactivationAtomTrackerToMetricMap, + std::vector& metricsWithActivation); + +// Creates a GaugeMetricProducer and updates the vectors/maps used by MetricsManager with +// the appropriate indices. Returns an sp to the producer, or nullopt if there was an error. +optional> createGaugeMetricProducerAndUpdateMetadata( + const ConfigKey& key, const StatsdConfig& config, const int64_t timeBaseNs, + const int64_t currentTimeNs, const sp& pullerManager, + const GaugeMetric& metric, const int metricIndex, + const std::vector>& allAtomMatchingTrackers, + const std::unordered_map& atomMatchingTrackerMap, + std::vector>& allConditionTrackers, + const std::unordered_map& conditionTrackerMap, + const std::vector& initialConditionCache, const sp& wizard, + const sp& matcherWizard, + const std::unordered_map& metricToActivationMap, + std::unordered_map>& trackerToMetricMap, + std::unordered_map>& conditionToMetricMap, + std::unordered_map>& activationAtomTrackerToMetricMap, + std::unordered_map>& deactivationAtomTrackerToMetricMap, + std::vector& metricsWithActivation); + +// Creates an AnomalyTracker and adds it to the appropriate metric. +// Returns an sp to the AnomalyTracker, or nullopt if there was an error. +optional> createAnomalyTracker( + const Alert& alert, const sp& anomalyAlarmMonitor, + const UpdateStatus& updateStatus, const int64_t currentTimeNs, + const std::unordered_map& metricProducerMap, + std::vector>& allMetricProducers); + +// Templated function for adding subscriptions to alarms or alerts. Returns true if successful. +template +bool initSubscribersForSubscriptionType(const StatsdConfig& config, + const Subscription_RuleType ruleType, + const std::unordered_map& ruleMap, + std::vector& allRules) { + for (int i = 0; i < config.subscription_size(); ++i) { + const Subscription& subscription = config.subscription(i); + if (subscription.rule_type() != ruleType) { + continue; + } + if (subscription.subscriber_information_case() == + Subscription::SubscriberInformationCase::SUBSCRIBER_INFORMATION_NOT_SET) { + ALOGW("subscription \"%lld\" has no subscriber info.\"", (long long)subscription.id()); + return false; + } + const auto& itr = ruleMap.find(subscription.rule_id()); + if (itr == ruleMap.end()) { + ALOGW("subscription \"%lld\" has unknown rule id: \"%lld\"", + (long long)subscription.id(), (long long)subscription.rule_id()); + return false; + } + const int ruleIndex = itr->second; + allRules[ruleIndex]->addSubscription(subscription); + } + return true; +} + +// Helper functions for MetricsManager to initialize from StatsdConfig. +// *Note*: only initStatsdConfig() should be called from outside. +// All other functions are intermediate +// steps, created to make unit tests easier. And most of the parameters in these +// functions are temporary objects in the initialization phase. + +// Initialize the AtomMatchingTrackers. +// input: +// [key]: the config key that this config belongs to +// [config]: the input StatsdConfig +// output: +// [atomMatchingTrackerMap]: this map should contain matcher name to index mapping +// [allAtomMatchingTrackers]: should store the sp to all the AtomMatchingTracker +// [allTagIds]: contains the set of all interesting tag ids to this config. +bool initAtomMatchingTrackers(const StatsdConfig& config, const sp& uidMap, + std::unordered_map& atomMatchingTrackerMap, + std::vector>& allAtomMatchingTrackers, + std::set& allTagIds); + +// Initialize ConditionTrackers +// input: +// [key]: the config key that this config belongs to +// [config]: the input config +// [atomMatchingTrackerMap]: AtomMatchingTracker name to index mapping from previous step. +// output: +// [conditionTrackerMap]: this map should contain condition name to index mapping +// [allConditionTrackers]: stores the sp to all the ConditionTrackers +// [trackerToConditionMap]: contain the mapping from index of +// log tracker to condition trackers that use the log tracker +// [initialConditionCache]: stores the initial conditions for each ConditionTracker +bool initConditions(const ConfigKey& key, const StatsdConfig& config, + const std::unordered_map& atomMatchingTrackerMap, + std::unordered_map& conditionTrackerMap, + std::vector>& allConditionTrackers, + std::unordered_map>& trackerToConditionMap, + std::vector& initialConditionCache); + +// Initialize State maps using State protos in the config. These maps will +// eventually be passed to MetricProducers to initialize their state info. +// input: +// [config]: the input config +// output: +// [stateAtomIdMap]: this map should contain the mapping from state ids to atom ids +// [allStateGroupMaps]: this map should contain the mapping from states ids and state +// values to state group ids for all states +// [stateProtoHashes]: contains a map of state id to the hash of the State proto from the config +bool initStates(const StatsdConfig& config, unordered_map& stateAtomIdMap, + unordered_map>& allStateGroupMaps, + std::map& stateProtoHashes); + +// Initialize MetricProducers. +// input: +// [key]: the config key that this config belongs to +// [config]: the input config +// [timeBaseSec]: start time base for all metrics +// [atomMatchingTrackerMap]: AtomMatchingTracker name to index mapping from previous step. +// [conditionTrackerMap]: condition name to index mapping +// [stateAtomIdMap]: contains the mapping from state ids to atom ids +// [allStateGroupMaps]: contains the mapping from atom ids and state values to +// state group ids for all states +// output: +// [allMetricProducers]: contains the list of sp to the MetricProducers created. +// [conditionToMetricMap]: contains the mapping from condition tracker index to +// the list of MetricProducer index +// [trackerToMetricMap]: contains the mapping from log tracker to MetricProducer index. +bool initMetrics( + const ConfigKey& key, const StatsdConfig& config, const int64_t timeBaseTimeNs, + const int64_t currentTimeNs, const sp& pullerManager, + const std::unordered_map& atomMatchingTrackerMap, + const std::unordered_map& conditionTrackerMap, + const vector>& allAtomMatchingTrackers, + const unordered_map& stateAtomIdMap, + const unordered_map>& allStateGroupMaps, + vector>& allConditionTrackers, + const std::vector& initialConditionCache, + std::vector>& allMetricProducers, + std::unordered_map>& conditionToMetricMap, + std::unordered_map>& trackerToMetricMap, + std::set& noReportMetricIds, + std::unordered_map>& activationAtomTrackerToMetricMap, + std::unordered_map>& deactivationAtomTrackerToMetricMap, + std::vector& metricsWithActivation); + +// Initialize alarms +// Is called both on initialize new configs and config updates since alarms do not have any state. +bool initAlarms(const StatsdConfig& config, const ConfigKey& key, + const sp& periodicAlarmMonitor, const int64_t timeBaseNs, + const int64_t currentTimeNs, std::vector>& allAlarmTrackers); + +// Initialize MetricsManager from StatsdConfig. +// Parameters are the members of MetricsManager. See MetricsManager for declaration. +bool initStatsdConfig(const ConfigKey& key, const StatsdConfig& config, const sp& uidMap, + const sp& pullerManager, + const sp& anomalyAlarmMonitor, + const sp& periodicAlarmMonitor, const int64_t timeBaseNs, + const int64_t currentTimeNs, std::set& allTagIds, + std::vector>& allAtomMatchingTrackers, + std::unordered_map& atomMatchingTrackerMap, + std::vector>& allConditionTrackers, + std::unordered_map& conditionTrackerMap, + std::vector>& allMetricProducers, + std::unordered_map& metricProducerMap, + vector>& allAnomalyTrackers, + vector>& allPeriodicAlarmTrackers, + std::unordered_map>& conditionToMetricMap, + std::unordered_map>& trackerToMetricMap, + std::unordered_map>& trackerToConditionMap, + std::unordered_map>& activationAtomTrackerToMetricMap, + std::unordered_map>& deactivationAtomTrackerToMetricMap, + std::unordered_map& alertTrackerMap, + std::vector& metricsWithActivation, + std::map& stateProtoHashes, + std::set& noReportMetricIds); + +} // namespace statsd +} // namespace os +} // namespace android diff --git a/statsd/src/packages/PackageInfoListener.h b/statsd/src/packages/PackageInfoListener.h new file mode 100644 index 00000000..1bc84c54 --- /dev/null +++ b/statsd/src/packages/PackageInfoListener.h @@ -0,0 +1,47 @@ +/* + * 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. + */ + +#ifndef STATSD_PACKAGE_INFO_LISTENER_H +#define STATSD_PACKAGE_INFO_LISTENER_H + +#include + +#include + +namespace android { +namespace os { +namespace statsd { + +class PackageInfoListener : public virtual android::RefBase { +public: + // Uid map will notify this listener that the app with apk name and uid has been upgraded to + // the specified version. + virtual void notifyAppUpgrade(const int64_t& eventTimeNs, const std::string& apk, + const int uid, const int64_t version) = 0; + + // Notify interested listeners that the given apk and uid combination no longer exits. + virtual void notifyAppRemoved(const int64_t& eventTimeNs, const std::string& apk, + const int uid) = 0; + + // Notify the listener that the UidMap snapshot is available. + virtual void onUidMapReceived(const int64_t& eventTimeNs) = 0; +}; + +} // namespace statsd +} // namespace os +} // namespace android + +#endif // STATSD_PACKAGE_INFO_LISTENER_H diff --git a/statsd/src/packages/UidMap.cpp b/statsd/src/packages/UidMap.cpp new file mode 100644 index 00000000..32be1c6b --- /dev/null +++ b/statsd/src/packages/UidMap.cpp @@ -0,0 +1,564 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, versionCode 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. + */ +#define DEBUG false // STOPSHIP if true +#include "Log.h" + +#include "hash.h" +#include "stats_log_util.h" +#include "guardrail/StatsdStats.h" +#include "packages/UidMap.h" + +#include + +using namespace android; + +using android::base::StringPrintf; +using android::util::FIELD_COUNT_REPEATED; +using android::util::FIELD_TYPE_BOOL; +using android::util::FIELD_TYPE_FLOAT; +using android::util::FIELD_TYPE_INT32; +using android::util::FIELD_TYPE_INT64; +using android::util::FIELD_TYPE_UINT64; +using android::util::FIELD_TYPE_MESSAGE; +using android::util::FIELD_TYPE_STRING; +using android::util::ProtoOutputStream; + +namespace android { +namespace os { +namespace statsd { + +const int FIELD_ID_SNAPSHOT_PACKAGE_NAME = 1; +const int FIELD_ID_SNAPSHOT_PACKAGE_VERSION = 2; +const int FIELD_ID_SNAPSHOT_PACKAGE_UID = 3; +const int FIELD_ID_SNAPSHOT_PACKAGE_DELETED = 4; +const int FIELD_ID_SNAPSHOT_PACKAGE_NAME_HASH = 5; +const int FIELD_ID_SNAPSHOT_PACKAGE_VERSION_STRING = 6; +const int FIELD_ID_SNAPSHOT_PACKAGE_VERSION_STRING_HASH = 7; +const int FIELD_ID_SNAPSHOT_PACKAGE_INSTALLER = 8; +const int FIELD_ID_SNAPSHOT_PACKAGE_INSTALLER_HASH = 9; +const int FIELD_ID_SNAPSHOT_TIMESTAMP = 1; +const int FIELD_ID_SNAPSHOT_PACKAGE_INFO = 2; +const int FIELD_ID_SNAPSHOTS = 1; +const int FIELD_ID_CHANGES = 2; +const int FIELD_ID_CHANGE_DELETION = 1; +const int FIELD_ID_CHANGE_TIMESTAMP = 2; +const int FIELD_ID_CHANGE_PACKAGE = 3; +const int FIELD_ID_CHANGE_UID = 4; +const int FIELD_ID_CHANGE_NEW_VERSION = 5; +const int FIELD_ID_CHANGE_PREV_VERSION = 6; +const int FIELD_ID_CHANGE_PACKAGE_HASH = 7; +const int FIELD_ID_CHANGE_NEW_VERSION_STRING = 8; +const int FIELD_ID_CHANGE_PREV_VERSION_STRING = 9; +const int FIELD_ID_CHANGE_NEW_VERSION_STRING_HASH = 10; +const int FIELD_ID_CHANGE_PREV_VERSION_STRING_HASH = 11; + +UidMap::UidMap() : mBytesUsed(0) {} + +UidMap::~UidMap() {} + +sp UidMap::getInstance() { + static sp sInstance = new UidMap(); + return sInstance; +} + +bool UidMap::hasApp(int uid, const string& packageName) const { + lock_guard lock(mMutex); + + auto it = mMap.find(std::make_pair(uid, packageName)); + return it != mMap.end() && !it->second.deleted; +} + +string UidMap::normalizeAppName(const string& appName) const { + string normalizedName = appName; + std::transform(normalizedName.begin(), normalizedName.end(), normalizedName.begin(), ::tolower); + return normalizedName; +} + +std::set UidMap::getAppNamesFromUid(const int32_t& uid, bool returnNormalized) const { + lock_guard lock(mMutex); + return getAppNamesFromUidLocked(uid,returnNormalized); +} + +std::set UidMap::getAppNamesFromUidLocked(const int32_t& uid, bool returnNormalized) const { + std::set names; + for (const auto& kv : mMap) { + if (kv.first.first == uid && !kv.second.deleted) { + names.insert(returnNormalized ? normalizeAppName(kv.first.second) : kv.first.second); + } + } + return names; +} + +int64_t UidMap::getAppVersion(int uid, const string& packageName) const { + lock_guard lock(mMutex); + + auto it = mMap.find(std::make_pair(uid, packageName)); + if (it == mMap.end() || it->second.deleted) { + return 0; + } + return it->second.versionCode; +} + +void UidMap::updateMap(const int64_t& timestamp, const vector& uid, + const vector& versionCode, const vector& versionString, + const vector& packageName, const vector& installer) { + wp broadcast = NULL; + { + lock_guard lock(mMutex); // Exclusively lock for updates. + + std::unordered_map, AppData, PairHash> deletedApps; + + // Copy all the deleted apps. + for (const auto& kv : mMap) { + if (kv.second.deleted) { + deletedApps[kv.first] = kv.second; + } + } + + mMap.clear(); + for (size_t j = 0; j < uid.size(); j++) { + string package = string(String8(packageName[j]).string()); + mMap[std::make_pair(uid[j], package)] = + AppData(versionCode[j], string(String8(versionString[j]).string()), + string(String8(installer[j]).string())); + } + + for (const auto& kv : deletedApps) { + auto mMapIt = mMap.find(kv.first); + if (mMapIt != mMap.end()) { + // Insert this deleted app back into the current map. + mMap[kv.first] = kv.second; + } + } + + ensureBytesUsedBelowLimit(); + StatsdStats::getInstance().setCurrentUidMapMemory(mBytesUsed); + broadcast = mSubscriber; + } + // To avoid invoking callback while holding the internal lock. we get a copy of the listener + // and invoke the callback. It's still possible that after we copy the listener, it removes + // itself before we call it. It's then the listener's job to handle it (expect the callback to + // be called after listener is removed, and the listener should properly ignore it). + auto strongPtr = broadcast.promote(); + if (strongPtr != NULL) { + strongPtr->onUidMapReceived(timestamp); + } +} + +void UidMap::updateApp(const int64_t& timestamp, const String16& app_16, const int32_t& uid, + const int64_t& versionCode, const String16& versionString, + const String16& installer) { + wp broadcast = NULL; + string appName = string(String8(app_16).string()); + { + lock_guard lock(mMutex); + int32_t prevVersion = 0; + string prevVersionString = ""; + string newVersionString = string(String8(versionString).string()); + bool found = false; + auto it = mMap.find(std::make_pair(uid, appName)); + if (it != mMap.end()) { + found = true; + prevVersion = it->second.versionCode; + prevVersionString = it->second.versionString; + it->second.versionCode = versionCode; + it->second.versionString = newVersionString; + it->second.installer = string(String8(installer).string()); + it->second.deleted = false; + } + if (!found) { + // Otherwise, we need to add an app at this uid. + mMap[std::make_pair(uid, appName)] = + AppData(versionCode, newVersionString, string(String8(installer).string())); + } else { + // Only notify the listeners if this is an app upgrade. If this app is being installed + // for the first time, then we don't notify the listeners. + // It's also OK to split again if we're forming a partial bucket after re-installing an + // app after deletion. + broadcast = mSubscriber; + } + mChanges.emplace_back(false, timestamp, appName, uid, versionCode, newVersionString, + prevVersion, prevVersionString); + mBytesUsed += kBytesChangeRecord; + ensureBytesUsedBelowLimit(); + StatsdStats::getInstance().setCurrentUidMapMemory(mBytesUsed); + StatsdStats::getInstance().setUidMapChanges(mChanges.size()); + } + + auto strongPtr = broadcast.promote(); + if (strongPtr != NULL) { + strongPtr->notifyAppUpgrade(timestamp, appName, uid, versionCode); + } +} + +void UidMap::ensureBytesUsedBelowLimit() { + size_t limit; + if (maxBytesOverride <= 0) { + limit = StatsdStats::kMaxBytesUsedUidMap; + } else { + limit = maxBytesOverride; + } + while (mBytesUsed > limit) { + ALOGI("Bytes used %zu is above limit %zu, need to delete something", mBytesUsed, limit); + if (mChanges.size() > 0) { + mBytesUsed -= kBytesChangeRecord; + mChanges.pop_front(); + StatsdStats::getInstance().noteUidMapDropped(1); + } + } +} + +void UidMap::removeApp(const int64_t& timestamp, const String16& app_16, const int32_t& uid) { + wp broadcast = NULL; + string app = string(String8(app_16).string()); + { + lock_guard lock(mMutex); + + int64_t prevVersion = 0; + string prevVersionString = ""; + auto key = std::make_pair(uid, app); + auto it = mMap.find(key); + if (it != mMap.end() && !it->second.deleted) { + prevVersion = it->second.versionCode; + prevVersionString = it->second.versionString; + it->second.deleted = true; + mDeletedApps.push_back(key); + } + if (mDeletedApps.size() > StatsdStats::kMaxDeletedAppsInUidMap) { + // Delete the oldest one. + auto oldest = mDeletedApps.front(); + mDeletedApps.pop_front(); + mMap.erase(oldest); + StatsdStats::getInstance().noteUidMapAppDeletionDropped(); + } + mChanges.emplace_back(true, timestamp, app, uid, 0, "", prevVersion, prevVersionString); + mBytesUsed += kBytesChangeRecord; + ensureBytesUsedBelowLimit(); + StatsdStats::getInstance().setCurrentUidMapMemory(mBytesUsed); + StatsdStats::getInstance().setUidMapChanges(mChanges.size()); + broadcast = mSubscriber; + } + + auto strongPtr = broadcast.promote(); + if (strongPtr != NULL) { + strongPtr->notifyAppRemoved(timestamp, app, uid); + } +} + +void UidMap::setListener(wp listener) { + lock_guard lock(mMutex); // Lock for updates + mSubscriber = listener; +} + +void UidMap::assignIsolatedUid(int isolatedUid, int parentUid) { + lock_guard lock(mIsolatedMutex); + + mIsolatedUidMap[isolatedUid] = parentUid; +} + +void UidMap::removeIsolatedUid(int isolatedUid) { + lock_guard lock(mIsolatedMutex); + + auto it = mIsolatedUidMap.find(isolatedUid); + if (it != mIsolatedUidMap.end()) { + mIsolatedUidMap.erase(it); + } +} + +int UidMap::getHostUidOrSelf(int uid) const { + lock_guard lock(mIsolatedMutex); + + auto it = mIsolatedUidMap.find(uid); + if (it != mIsolatedUidMap.end()) { + return it->second; + } + return uid; +} + +void UidMap::clearOutput() { + mChanges.clear(); + // Also update the guardrail trackers. + StatsdStats::getInstance().setUidMapChanges(0); + mBytesUsed = 0; + StatsdStats::getInstance().setCurrentUidMapMemory(mBytesUsed); +} + +int64_t UidMap::getMinimumTimestampNs() { + int64_t m = 0; + for (const auto& kv : mLastUpdatePerConfigKey) { + if (m == 0) { + m = kv.second; + } else if (kv.second < m) { + m = kv.second; + } + } + return m; +} + +size_t UidMap::getBytesUsed() const { + return mBytesUsed; +} + +void UidMap::writeUidMapSnapshot(int64_t timestamp, bool includeVersionStrings, + bool includeInstaller, const std::set& interestingUids, + std::set* str_set, ProtoOutputStream* proto) { + lock_guard lock(mMutex); + + writeUidMapSnapshotLocked(timestamp, includeVersionStrings, includeInstaller, interestingUids, + str_set, proto); +} + +void UidMap::writeUidMapSnapshotLocked(int64_t timestamp, bool includeVersionStrings, + bool includeInstaller, + const std::set& interestingUids, + std::set* str_set, ProtoOutputStream* proto) { + proto->write(FIELD_TYPE_INT64 | FIELD_ID_SNAPSHOT_TIMESTAMP, (long long)timestamp); + for (const auto& kv : mMap) { + if (!interestingUids.empty() && + interestingUids.find(kv.first.first) == interestingUids.end()) { + continue; + } + uint64_t token = proto->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | + FIELD_ID_SNAPSHOT_PACKAGE_INFO); + if (str_set != nullptr) { + str_set->insert(kv.first.second); + proto->write(FIELD_TYPE_UINT64 | FIELD_ID_SNAPSHOT_PACKAGE_NAME_HASH, + (long long)Hash64(kv.first.second)); + if (includeVersionStrings) { + str_set->insert(kv.second.versionString); + proto->write(FIELD_TYPE_UINT64 | FIELD_ID_SNAPSHOT_PACKAGE_VERSION_STRING_HASH, + (long long)Hash64(kv.second.versionString)); + } + if (includeInstaller) { + str_set->insert(kv.second.installer); + proto->write(FIELD_TYPE_UINT64 | FIELD_ID_SNAPSHOT_PACKAGE_INSTALLER_HASH, + (long long)Hash64(kv.second.installer)); + } + } else { + proto->write(FIELD_TYPE_STRING | FIELD_ID_SNAPSHOT_PACKAGE_NAME, kv.first.second); + if (includeVersionStrings) { + proto->write(FIELD_TYPE_STRING | FIELD_ID_SNAPSHOT_PACKAGE_VERSION_STRING, + kv.second.versionString); + } + if (includeInstaller) { + proto->write(FIELD_TYPE_STRING | FIELD_ID_SNAPSHOT_PACKAGE_INSTALLER, + kv.second.installer); + } + } + + proto->write(FIELD_TYPE_INT64 | FIELD_ID_SNAPSHOT_PACKAGE_VERSION, + (long long)kv.second.versionCode); + proto->write(FIELD_TYPE_INT32 | FIELD_ID_SNAPSHOT_PACKAGE_UID, kv.first.first); + proto->write(FIELD_TYPE_BOOL | FIELD_ID_SNAPSHOT_PACKAGE_DELETED, kv.second.deleted); + proto->end(token); + } +} + +void UidMap::appendUidMap(const int64_t& timestamp, const ConfigKey& key, std::set* str_set, + bool includeVersionStrings, bool includeInstaller, + ProtoOutputStream* proto) { + lock_guard lock(mMutex); // Lock for updates + + for (const ChangeRecord& record : mChanges) { + if (record.timestampNs > mLastUpdatePerConfigKey[key]) { + uint64_t changesToken = + proto->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_CHANGES); + proto->write(FIELD_TYPE_BOOL | FIELD_ID_CHANGE_DELETION, (bool)record.deletion); + proto->write(FIELD_TYPE_INT64 | FIELD_ID_CHANGE_TIMESTAMP, + (long long)record.timestampNs); + if (str_set != nullptr) { + str_set->insert(record.package); + proto->write(FIELD_TYPE_UINT64 | FIELD_ID_CHANGE_PACKAGE_HASH, + (long long)Hash64(record.package)); + if (includeVersionStrings) { + str_set->insert(record.versionString); + proto->write(FIELD_TYPE_UINT64 | FIELD_ID_CHANGE_NEW_VERSION_STRING_HASH, + (long long)Hash64(record.versionString)); + str_set->insert(record.prevVersionString); + proto->write(FIELD_TYPE_UINT64 | FIELD_ID_CHANGE_PREV_VERSION_STRING_HASH, + (long long)Hash64(record.prevVersionString)); + } + } else { + proto->write(FIELD_TYPE_STRING | FIELD_ID_CHANGE_PACKAGE, record.package); + if (includeVersionStrings) { + proto->write(FIELD_TYPE_STRING | FIELD_ID_CHANGE_NEW_VERSION_STRING, + record.versionString); + proto->write(FIELD_TYPE_STRING | FIELD_ID_CHANGE_PREV_VERSION_STRING, + record.prevVersionString); + } + } + + proto->write(FIELD_TYPE_INT32 | FIELD_ID_CHANGE_UID, (int)record.uid); + proto->write(FIELD_TYPE_INT64 | FIELD_ID_CHANGE_NEW_VERSION, (long long)record.version); + proto->write(FIELD_TYPE_INT64 | FIELD_ID_CHANGE_PREV_VERSION, + (long long)record.prevVersion); + proto->end(changesToken); + } + } + + // Write snapshot from current uid map state. + uint64_t snapshotsToken = + proto->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_SNAPSHOTS); + writeUidMapSnapshotLocked(timestamp, includeVersionStrings, includeInstaller, + std::set() /*empty uid set means including every uid*/, + str_set, proto); + proto->end(snapshotsToken); + + int64_t prevMin = getMinimumTimestampNs(); + mLastUpdatePerConfigKey[key] = timestamp; + int64_t newMin = getMinimumTimestampNs(); + + if (newMin > prevMin) { // Delete anything possible now that the minimum has + // moved forward. + int64_t cutoff_nanos = newMin; + for (auto it_changes = mChanges.begin(); it_changes != mChanges.end();) { + if (it_changes->timestampNs < cutoff_nanos) { + mBytesUsed -= kBytesChangeRecord; + it_changes = mChanges.erase(it_changes); + } else { + ++it_changes; + } + } + } + StatsdStats::getInstance().setCurrentUidMapMemory(mBytesUsed); + StatsdStats::getInstance().setUidMapChanges(mChanges.size()); +} + +void UidMap::printUidMap(int out) const { + lock_guard lock(mMutex); + + for (const auto& kv : mMap) { + if (!kv.second.deleted) { + dprintf(out, "%s, v%" PRId64 ", %s, %s (%i)\n", kv.first.second.c_str(), + kv.second.versionCode, kv.second.versionString.c_str(), + kv.second.installer.c_str(), kv.first.first); + } + } +} + +void UidMap::OnConfigUpdated(const ConfigKey& key) { + mLastUpdatePerConfigKey[key] = -1; +} + +void UidMap::OnConfigRemoved(const ConfigKey& key) { + mLastUpdatePerConfigKey.erase(key); +} + +set UidMap::getAppUid(const string& package) const { + lock_guard lock(mMutex); + + set results; + for (const auto& kv : mMap) { + if (kv.first.second == package && !kv.second.deleted) { + results.insert(kv.first.first); + } + } + return results; +} + +// Note not all the following AIDs are used as uids. Some are used only for gids. +// It's ok to leave them in the map, but we won't ever see them in the log's uid field. +// App's uid starts from 10000, and will not overlap with the following AIDs. +const std::map UidMap::sAidToUidMapping = {{"AID_ROOT", 0}, + {"AID_SYSTEM", 1000}, + {"AID_RADIO", 1001}, + {"AID_BLUETOOTH", 1002}, + {"AID_GRAPHICS", 1003}, + {"AID_INPUT", 1004}, + {"AID_AUDIO", 1005}, + {"AID_CAMERA", 1006}, + {"AID_LOG", 1007}, + {"AID_COMPASS", 1008}, + {"AID_MOUNT", 1009}, + {"AID_WIFI", 1010}, + {"AID_ADB", 1011}, + {"AID_INSTALL", 1012}, + {"AID_MEDIA", 1013}, + {"AID_DHCP", 1014}, + {"AID_SDCARD_RW", 1015}, + {"AID_VPN", 1016}, + {"AID_KEYSTORE", 1017}, + {"AID_USB", 1018}, + {"AID_DRM", 1019}, + {"AID_MDNSR", 1020}, + {"AID_GPS", 1021}, + // {"AID_UNUSED1", 1022}, + {"AID_MEDIA_RW", 1023}, + {"AID_MTP", 1024}, + // {"AID_UNUSED2", 1025}, + {"AID_DRMRPC", 1026}, + {"AID_NFC", 1027}, + {"AID_SDCARD_R", 1028}, + {"AID_CLAT", 1029}, + {"AID_LOOP_RADIO", 1030}, + {"AID_MEDIA_DRM", 1031}, + {"AID_PACKAGE_INFO", 1032}, + {"AID_SDCARD_PICS", 1033}, + {"AID_SDCARD_AV", 1034}, + {"AID_SDCARD_ALL", 1035}, + {"AID_LOGD", 1036}, + {"AID_SHARED_RELRO", 1037}, + {"AID_DBUS", 1038}, + {"AID_TLSDATE", 1039}, + {"AID_MEDIA_EX", 1040}, + {"AID_AUDIOSERVER", 1041}, + {"AID_METRICS_COLL", 1042}, + {"AID_METRICSD", 1043}, + {"AID_WEBSERV", 1044}, + {"AID_DEBUGGERD", 1045}, + {"AID_MEDIA_CODEC", 1046}, + {"AID_CAMERASERVER", 1047}, + {"AID_FIREWALL", 1048}, + {"AID_TRUNKS", 1049}, + {"AID_NVRAM", 1050}, + {"AID_DNS", 1051}, + {"AID_DNS_TETHER", 1052}, + {"AID_WEBVIEW_ZYGOTE", 1053}, + {"AID_VEHICLE_NETWORK", 1054}, + {"AID_MEDIA_AUDIO", 1055}, + {"AID_MEDIA_VIDEO", 1056}, + {"AID_MEDIA_IMAGE", 1057}, + {"AID_TOMBSTONED", 1058}, + {"AID_MEDIA_OBB", 1059}, + {"AID_ESE", 1060}, + {"AID_OTA_UPDATE", 1061}, + {"AID_AUTOMOTIVE_EVS", 1062}, + {"AID_LOWPAN", 1063}, + {"AID_HSM", 1064}, + {"AID_RESERVED_DISK", 1065}, + {"AID_STATSD", 1066}, + {"AID_INCIDENTD", 1067}, + {"AID_SECURE_ELEMENT", 1068}, + {"AID_LMKD", 1069}, + {"AID_LLKD", 1070}, + {"AID_IORAPD", 1071}, + {"AID_GPU_SERVICE", 1072}, + {"AID_NETWORK_STACK", 1073}, + {"AID_GSID", 1074}, + {"AID_FSVERITY_CERT", 1075}, + {"AID_CREDSTORE", 1076}, + {"AID_EXTERNAL_STORAGE", 1077}, + {"AID_EXT_DATA_RW", 1078}, + {"AID_EXT_OBB_RW", 1079}, + {"AID_CONTEXT_HUB", 1080}, + {"AID_SHELL", 2000}, + {"AID_CACHE", 2001}, + {"AID_DIAG", 2002}, + {"AID_NOBODY", 9999}}; + +} // namespace statsd +} // namespace os +} // namespace android diff --git a/statsd/src/packages/UidMap.h b/statsd/src/packages/UidMap.h new file mode 100644 index 00000000..622321b8 --- /dev/null +++ b/statsd/src/packages/UidMap.h @@ -0,0 +1,226 @@ +/* + * 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. + */ + +#pragma once + +#include "config/ConfigKey.h" +#include "packages/PackageInfoListener.h" +#include "stats_util.h" + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +using namespace android; +using namespace std; + +using android::util::ProtoOutputStream; + +namespace android { +namespace os { +namespace statsd { + +struct AppData { + int64_t versionCode; + string versionString; + string installer; + bool deleted; + + // Empty constructor needed for unordered map. + AppData() { + } + + AppData(const int64_t v, const string& versionString, const string& installer) + : versionCode(v), versionString(versionString), installer(installer), deleted(false){}; +}; + +// When calling appendUidMap, we retrieve all the ChangeRecords since the last +// timestamp we called appendUidMap for this configuration key. +struct ChangeRecord { + const bool deletion; + const int64_t timestampNs; + const string package; + const int32_t uid; + const int64_t version; + const int64_t prevVersion; + const string versionString; + const string prevVersionString; + + ChangeRecord(const bool isDeletion, const int64_t timestampNs, const string& package, + const int32_t uid, const int64_t version, const string versionString, + const int64_t prevVersion, const string prevVersionString) + : deletion(isDeletion), + timestampNs(timestampNs), + package(package), + uid(uid), + version(version), + prevVersion(prevVersion), + versionString(versionString), + prevVersionString(prevVersionString) { + } +}; + +const unsigned int kBytesChangeRecord = sizeof(struct ChangeRecord); + +// UidMap keeps track of what the corresponding app name (APK name) and version code for every uid +// at any given moment. This map must be updated by StatsCompanionService. +class UidMap : public virtual android::RefBase { +public: + UidMap(); + ~UidMap(); + static const std::map sAidToUidMapping; + + static sp getInstance(); + /* + * All three inputs must be the same size, and the jth element in each array refers to the same + * tuple, ie. uid[j] corresponds to packageName[j] with versionCode[j]. + */ + void updateMap(const int64_t& timestamp, const vector& uid, + const vector& versionCode, const vector& versionString, + const vector& packageName, const vector& installer); + + void updateApp(const int64_t& timestamp, const String16& packageName, const int32_t& uid, + const int64_t& versionCode, const String16& versionString, + const String16& installer); + void removeApp(const int64_t& timestamp, const String16& packageName, const int32_t& uid); + + // Returns true if the given uid contains the specified app (eg. com.google.android.gms). + bool hasApp(int uid, const string& packageName) const; + + // Returns the app names from uid. + std::set getAppNamesFromUid(const int32_t& uid, bool returnNormalized) const; + + int64_t getAppVersion(int uid, const string& packageName) const; + + // Helper for debugging contents of this uid map. Can be triggered with: + // adb shell cmd stats print-uid-map + void printUidMap(int outFd) const; + + // Command for indicating to the map that StatsLogProcessor should be notified if an app is + // updated. This allows metric producers and managers to distinguish when the same uid or app + // represents a different version of an app. + void setListener(wp listener); + + // Informs uid map that a config is added/updated. Used for keeping mConfigKeys up to date. + void OnConfigUpdated(const ConfigKey& key); + + // Informs uid map that a config is removed. Used for keeping mConfigKeys up to date. + void OnConfigRemoved(const ConfigKey& key); + + void assignIsolatedUid(int isolatedUid, int parentUid); + void removeIsolatedUid(int isolatedUid); + + // Returns the host uid if it exists. Otherwise, returns the same uid that was passed-in. + virtual int getHostUidOrSelf(int uid) const; + + // Gets all snapshots and changes that have occurred since the last output. + // If every config key has received a change or snapshot record, then this + // record is deleted. + void appendUidMap(const int64_t& timestamp, const ConfigKey& key, std::set* str_set, + bool includeVersionStrings, bool includeInstaller, + ProtoOutputStream* proto); + + // Forces the output to be cleared. We still generate a snapshot based on the current state. + // This results in extra data uploaded but helps us reconstruct the uid mapping on the server + // in case we lose a previous upload. + void clearOutput(); + + // Get currently cached value of memory used by UID map. + size_t getBytesUsed() const; + + virtual std::set getAppUid(const string& package) const; + + // Write current PackageInfoSnapshot to ProtoOutputStream. + // interestingUids: If not empty, only write the package info for these uids. If empty, write + // package info for all uids. + // str_set: if not null, add new string to the set and write str_hash to proto + // if null, write string to proto. + void writeUidMapSnapshot(int64_t timestamp, bool includeVersionStrings, bool includeInstaller, + const std::set& interestingUids, std::set* str_set, + ProtoOutputStream* proto); + +private: + std::set getAppNamesFromUidLocked(const int32_t& uid, bool returnNormalized) const; + string normalizeAppName(const string& appName) const; + + void writeUidMapSnapshotLocked(int64_t timestamp, bool includeVersionStrings, + bool includeInstaller, const std::set& interestingUids, + std::set* str_set, ProtoOutputStream* proto); + + mutable mutex mMutex; + mutable mutex mIsolatedMutex; + + struct PairHash { + size_t operator()(std::pair p) const noexcept { + std::hash hash_fn; + return hash_fn(std::to_string(p.first) + p.second); + } + }; + // Maps uid and package name to application data. + std::unordered_map, AppData, PairHash> mMap; + + // Maps isolated uid to the parent uid. Any metrics for an isolated uid will instead contribute + // to the parent uid. + std::unordered_map mIsolatedUidMap; + + // Record the changes that can be provided with the uploads. + std::list mChanges; + + // Store which uid and apps represent deleted ones. + std::list> mDeletedApps; + + // Notify StatsLogProcessor if there's an upgrade/removal in any app. + wp mSubscriber; + + // Mapping of config keys we're aware of to the epoch time they last received an update. This + // lets us know it's safe to delete events older than the oldest update. The value is nanosec. + // Value of -1 denotes this config key has never received an upload. + std::unordered_map mLastUpdatePerConfigKey; + + // Returns the minimum value from mConfigKeys. + int64_t getMinimumTimestampNs(); + + // If our current used bytes is above the limit, then we clear out the earliest snapshot. If + // there are no more snapshots, then we clear out the earliest delta. We repeat the deletions + // until the memory consumed by mOutput is below the specified limit. + void ensureBytesUsedBelowLimit(); + + // Override used for testing the max memory allowed by uid map. 0 means we use the value + // specified in StatsdStats.h with the rest of the guardrails. + size_t maxBytesOverride = 0; + + // Cache the size of mOutput; + size_t mBytesUsed; + + // Allows unit-test to access private methods. + FRIEND_TEST(UidMapTest, TestClearingOutput); + FRIEND_TEST(UidMapTest, TestRemovedAppRetained); + FRIEND_TEST(UidMapTest, TestRemovedAppOverGuardrail); + FRIEND_TEST(UidMapTest, TestOutputIncludesAtLeastOneSnapshot); + FRIEND_TEST(UidMapTest, TestMemoryComputed); + FRIEND_TEST(UidMapTest, TestMemoryGuardrail); +}; + +} // namespace statsd +} // namespace os +} // namespace android diff --git a/statsd/src/shell/ShellSubscriber.cpp b/statsd/src/shell/ShellSubscriber.cpp new file mode 100644 index 00000000..9d8f0c24 --- /dev/null +++ b/statsd/src/shell/ShellSubscriber.cpp @@ -0,0 +1,245 @@ +/* + * Copyright (C) 2018 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. + */ +#define DEBUG false // STOPSHIP if true +#include "Log.h" + +#include "ShellSubscriber.h" + +#include + +#include "matchers/matcher_util.h" +#include "stats_log_util.h" + +using android::util::ProtoOutputStream; + +namespace android { +namespace os { +namespace statsd { + +const static int FIELD_ID_ATOM = 1; + +void ShellSubscriber::startNewSubscription(int in, int out, int timeoutSec) { + int myToken = claimToken(); + VLOG("ShellSubscriber: new subscription %d has come in", myToken); + mSubscriptionShouldEnd.notify_one(); + + shared_ptr mySubscriptionInfo = make_shared(in, out); + if (!readConfig(mySubscriptionInfo)) return; + + { + std::unique_lock lock(mMutex); + mSubscriptionInfo = mySubscriptionInfo; + spawnHelperThread(myToken); + waitForSubscriptionToEndLocked(mySubscriptionInfo, myToken, lock, timeoutSec); + + if (mSubscriptionInfo == mySubscriptionInfo) { + mSubscriptionInfo = nullptr; + } + + } +} + +void ShellSubscriber::spawnHelperThread(int myToken) { + std::thread t([this, myToken] { pullAndSendHeartbeats(myToken); }); + t.detach(); +} + +void ShellSubscriber::waitForSubscriptionToEndLocked(shared_ptr myInfo, + int myToken, + std::unique_lock& lock, + int timeoutSec) { + if (timeoutSec > 0) { + mSubscriptionShouldEnd.wait_for(lock, timeoutSec * 1s, [this, myToken, &myInfo] { + return mToken != myToken || !myInfo->mClientAlive; + }); + } else { + mSubscriptionShouldEnd.wait(lock, [this, myToken, &myInfo] { + return mToken != myToken || !myInfo->mClientAlive; + }); + } +} + +// Atomically claim the next token. Token numbers denote subscriber ordering. +int ShellSubscriber::claimToken() { + std::unique_lock lock(mMutex); + int myToken = ++mToken; + return myToken; +} + +// Read and parse single config. There should only one config per input. +bool ShellSubscriber::readConfig(shared_ptr subscriptionInfo) { + // Read the size of the config. + size_t bufferSize; + if (!android::base::ReadFully(subscriptionInfo->mInputFd, &bufferSize, sizeof(bufferSize))) { + return false; + } + + // Read the config. + vector buffer(bufferSize); + if (!android::base::ReadFully(subscriptionInfo->mInputFd, buffer.data(), bufferSize)) { + return false; + } + + // Parse the config. + ShellSubscription config; + if (!config.ParseFromArray(buffer.data(), bufferSize)) { + return false; + } + + // Update SubscriptionInfo with state from config + for (const auto& pushed : config.pushed()) { + subscriptionInfo->mPushedMatchers.push_back(pushed); + } + + for (const auto& pulled : config.pulled()) { + vector packages; + vector uids; + for (const string& pkg : pulled.packages()) { + auto it = UidMap::sAidToUidMapping.find(pkg); + if (it != UidMap::sAidToUidMapping.end()) { + uids.push_back(it->second); + } else { + packages.push_back(pkg); + } + } + + subscriptionInfo->mPulledInfo.emplace_back(pulled.matcher(), pulled.freq_millis(), packages, + uids); + VLOG("adding matcher for pulled atom %d", pulled.matcher().atom_id()); + } + + return true; +} + +void ShellSubscriber::pullAndSendHeartbeats(int myToken) { + VLOG("ShellSubscriber: helper thread %d starting", myToken); + while (true) { + int64_t sleepTimeMs = INT_MAX; + { + std::lock_guard lock(mMutex); + if (!mSubscriptionInfo || mToken != myToken) { + VLOG("ShellSubscriber: helper thread %d done!", myToken); + return; + } + + int64_t nowMillis = getElapsedRealtimeMillis(); + int64_t nowNanos = getElapsedRealtimeNs(); + for (PullInfo& pullInfo : mSubscriptionInfo->mPulledInfo) { + if (pullInfo.mPrevPullElapsedRealtimeMs + pullInfo.mInterval >= nowMillis) { + continue; + } + + vector uids; + getUidsForPullAtom(&uids, pullInfo); + + vector> data; + mPullerMgr->Pull(pullInfo.mPullerMatcher.atom_id(), uids, nowNanos, &data); + VLOG("Pulled %zu atoms with id %d", data.size(), pullInfo.mPullerMatcher.atom_id()); + writePulledAtomsLocked(data, pullInfo.mPullerMatcher); + + pullInfo.mPrevPullElapsedRealtimeMs = nowMillis; + } + + // Send a heartbeat, consisting of a data size of 0, if perfd hasn't recently received + // data from statsd. When it receives the data size of 0, perfd will not expect any + // atoms and recheck whether the subscription should end. + if (nowMillis - mLastWriteMs > kMsBetweenHeartbeats) { + attemptWriteToPipeLocked(/*dataSize=*/0); + } + + // Determine how long to sleep before doing more work. + for (PullInfo& pullInfo : mSubscriptionInfo->mPulledInfo) { + int64_t nextPullTime = pullInfo.mPrevPullElapsedRealtimeMs + pullInfo.mInterval; + int64_t timeBeforePull = nextPullTime - nowMillis; // guaranteed to be non-negative + if (timeBeforePull < sleepTimeMs) sleepTimeMs = timeBeforePull; + } + int64_t timeBeforeHeartbeat = (mLastWriteMs + kMsBetweenHeartbeats) - nowMillis; + if (timeBeforeHeartbeat < sleepTimeMs) sleepTimeMs = timeBeforeHeartbeat; + } + + VLOG("ShellSubscriber: helper thread %d sleeping for %lld ms", myToken, + (long long)sleepTimeMs); + std::this_thread::sleep_for(std::chrono::milliseconds(sleepTimeMs)); + } +} + +void ShellSubscriber::getUidsForPullAtom(vector* uids, const PullInfo& pullInfo) { + uids->insert(uids->end(), pullInfo.mPullUids.begin(), pullInfo.mPullUids.end()); + // This is slow. Consider storing the uids per app and listening to uidmap updates. + for (const string& pkg : pullInfo.mPullPackages) { + set uidsForPkg = mUidMap->getAppUid(pkg); + uids->insert(uids->end(), uidsForPkg.begin(), uidsForPkg.end()); + } + uids->push_back(DEFAULT_PULL_UID); +} + +void ShellSubscriber::writePulledAtomsLocked(const vector>& data, + const SimpleAtomMatcher& matcher) { + mProto.clear(); + int count = 0; + for (const auto& event : data) { + if (matchesSimple(mUidMap, matcher, *event)) { + count++; + uint64_t atomToken = mProto.start(util::FIELD_TYPE_MESSAGE | + util::FIELD_COUNT_REPEATED | FIELD_ID_ATOM); + event->ToProto(mProto); + mProto.end(atomToken); + } + } + + if (count > 0) attemptWriteToPipeLocked(mProto.size()); +} + +void ShellSubscriber::onLogEvent(const LogEvent& event) { + std::lock_guard lock(mMutex); + if (!mSubscriptionInfo) return; + + mProto.clear(); + for (const auto& matcher : mSubscriptionInfo->mPushedMatchers) { + if (matchesSimple(mUidMap, matcher, event)) { + uint64_t atomToken = mProto.start(util::FIELD_TYPE_MESSAGE | + util::FIELD_COUNT_REPEATED | FIELD_ID_ATOM); + event.ToProto(mProto); + mProto.end(atomToken); + attemptWriteToPipeLocked(mProto.size()); + } + } +} + +// Tries to write the atom encoded in mProto to the pipe. If the write fails +// because the read end of the pipe has closed, signals to other threads that +// the subscription should end. +void ShellSubscriber::attemptWriteToPipeLocked(size_t dataSize) { + // First, write the payload size. + if (!android::base::WriteFully(mSubscriptionInfo->mOutputFd, &dataSize, sizeof(dataSize))) { + mSubscriptionInfo->mClientAlive = false; + mSubscriptionShouldEnd.notify_one(); + return; + } + + // Then, write the payload if this is not just a heartbeat. + if (dataSize > 0 && !mProto.flush(mSubscriptionInfo->mOutputFd)) { + mSubscriptionInfo->mClientAlive = false; + mSubscriptionShouldEnd.notify_one(); + return; + } + + mLastWriteMs = getElapsedRealtimeMillis(); +} + +} // namespace statsd +} // namespace os +} // namespace android diff --git a/statsd/src/shell/ShellSubscriber.h b/statsd/src/shell/ShellSubscriber.h new file mode 100644 index 00000000..826ad31f --- /dev/null +++ b/statsd/src/shell/ShellSubscriber.h @@ -0,0 +1,146 @@ +/* + * Copyright (C) 2018 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. + */ + +#pragma once + +#include +#include + +#include +#include +#include + +#include "external/StatsPullerManager.h" +#include "src/shell/shell_config.pb.h" +#include "src/statsd_config.pb.h" +#include "logd/LogEvent.h" +#include "packages/UidMap.h" + +namespace android { +namespace os { +namespace statsd { + +/** + * Handles atoms subscription via shell cmd. + * + * A shell subscription lasts *until shell exits*. Unlike config based clients, a shell client + * communicates with statsd via file descriptors. They can subscribe pushed and pulled atoms. + * The atoms are sent back to the client in real time, as opposed to keeping the data in memory. + * Shell clients do not subscribe aggregated metrics, as they are responsible for doing the + * aggregation after receiving the atom events. + * + * Shell clients pass ShellSubscription in the proto binary format. Clients can update the + * subscription by sending a new subscription. The new subscription would replace the old one. + * Input data stream format is: + * + * |size_t|subscription proto|size_t|subscription proto|.... + * + * statsd sends the events back in Atom proto binary format. Each Atom message is preceded + * with sizeof(size_t) bytes indicating the size of the proto message payload. + * + * The stream would be in the following format: + * |size_t|shellData proto|size_t|shellData proto|.... + * + * Only one shell subscriber is allowed at a time because each shell subscriber blocks one thread + * until it exits. + */ +class ShellSubscriber : public virtual RefBase { +public: + ShellSubscriber(sp uidMap, sp pullerMgr) + : mUidMap(uidMap), mPullerMgr(pullerMgr){}; + + void startNewSubscription(int inFd, int outFd, int timeoutSec); + + void onLogEvent(const LogEvent& event); + +private: + struct PullInfo { + PullInfo(const SimpleAtomMatcher& matcher, int64_t interval, + const std::vector& packages, const std::vector& uids) + : mPullerMatcher(matcher), + mInterval(interval), + mPrevPullElapsedRealtimeMs(0), + mPullPackages(packages), + mPullUids(uids) { + } + SimpleAtomMatcher mPullerMatcher; + int64_t mInterval; + int64_t mPrevPullElapsedRealtimeMs; + std::vector mPullPackages; + std::vector mPullUids; + }; + + struct SubscriptionInfo { + SubscriptionInfo(const int& inputFd, const int& outputFd) + : mInputFd(inputFd), mOutputFd(outputFd), mClientAlive(true) { + } + + int mInputFd; + int mOutputFd; + std::vector mPushedMatchers; + std::vector mPulledInfo; + bool mClientAlive; + }; + + int claimToken(); + + bool readConfig(std::shared_ptr subscriptionInfo); + + void spawnHelperThread(int myToken); + + void waitForSubscriptionToEndLocked(std::shared_ptr myInfo, + int myToken, + std::unique_lock& lock, + int timeoutSec); + + // Helper thread that pulls atoms at a regular frequency and sends + // heartbeats to perfd if statsd hasn't recently sent any data. Statsd must + // send heartbeats for perfd to escape a blocking read call and recheck if + // the user has terminated the subscription. + void pullAndSendHeartbeats(int myToken); + + void writePulledAtomsLocked(const vector>& data, + const SimpleAtomMatcher& matcher); + + void getUidsForPullAtom(vector* uids, const PullInfo& pullInfo); + + void attemptWriteToPipeLocked(size_t dataSize); + + sp mUidMap; + + sp mPullerMgr; + + android::util::ProtoOutputStream mProto; + + mutable std::mutex mMutex; + + std::condition_variable mSubscriptionShouldEnd; + + std::shared_ptr mSubscriptionInfo = nullptr; + + int mToken = 0; + + const int32_t DEFAULT_PULL_UID = AID_SYSTEM; + + // Tracks when we last send data to perfd. We need that time to determine + // when next to send a heartbeat. + int64_t mLastWriteMs = 0; + const int64_t kMsBetweenHeartbeats = 1000; +}; + +} // namespace statsd +} // namespace os +} // namespace android diff --git a/statsd/src/shell/shell_config.proto b/statsd/src/shell/shell_config.proto new file mode 100644 index 00000000..9c18c4ff --- /dev/null +++ b/statsd/src/shell/shell_config.proto @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2018 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. + */ + +syntax = "proto2"; + +package android.os.statsd; + +option java_package = "com.android.os"; +option java_outer_classname = "ShellConfig"; + +import "packages/modules/StatsD/statsd/src/statsd_config.proto"; + +message PulledAtomSubscription { + optional SimpleAtomMatcher matcher = 1; + + /* gap between two pulls in milliseconds */ + optional int32 freq_millis = 2; + + /* Packages that the pull is requested from */ + repeated string packages = 3; +} + +message ShellSubscription { + repeated SimpleAtomMatcher pushed = 1; + repeated PulledAtomSubscription pulled = 2; +} \ No newline at end of file diff --git a/statsd/src/shell/shell_data.proto b/statsd/src/shell/shell_data.proto new file mode 100644 index 00000000..ec41cbc5 --- /dev/null +++ b/statsd/src/shell/shell_data.proto @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2018 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. + */ + +syntax = "proto2"; + +package android.os.statsd; + +option java_package = "com.android.os.statsd"; +option java_outer_classname = "ShellDataProto"; + +import "frameworks/proto_logging/stats/atoms.proto"; + +// The output of shell subscription, including both pulled and pushed subscriptions. +message ShellData { + repeated Atom atom = 1; +} diff --git a/statsd/src/socket/StatsSocketListener.cpp b/statsd/src/socket/StatsSocketListener.cpp new file mode 100755 index 00000000..b877cc9c --- /dev/null +++ b/statsd/src/socket/StatsSocketListener.cpp @@ -0,0 +1,156 @@ +/* + * Copyright (C) 2018 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. + */ +#define DEBUG false // STOPSHIP if true +#include "Log.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "StatsSocketListener.h" +#include "guardrail/StatsdStats.h" +#include "stats_log_util.h" + +namespace android { +namespace os { +namespace statsd { + +StatsSocketListener::StatsSocketListener(std::shared_ptr queue) + : SocketListener(getLogSocket(), false /*start listen*/), mQueue(queue) { +} + +StatsSocketListener::~StatsSocketListener() { +} + +bool StatsSocketListener::onDataAvailable(SocketClient* cli) { + static bool name_set; + if (!name_set) { + prctl(PR_SET_NAME, "statsd.writer"); + name_set = true; + } + + // + 1 to ensure null terminator if MAX_PAYLOAD buffer is received + char buffer[sizeof(android_log_header_t) + LOGGER_ENTRY_MAX_PAYLOAD + 1]; + struct iovec iov = {buffer, sizeof(buffer) - 1}; + + alignas(4) char control[CMSG_SPACE(sizeof(struct ucred))]; + struct msghdr hdr = { + NULL, 0, &iov, 1, control, sizeof(control), 0, + }; + + int socket = cli->getSocket(); + + // To clear the entire buffer is secure/safe, but this contributes to 1.68% + // overhead under logging load. We are safe because we check counts, but + // still need to clear null terminator + // memset(buffer, 0, sizeof(buffer)); + ssize_t n = recvmsg(socket, &hdr, 0); + if (n <= (ssize_t)(sizeof(android_log_header_t))) { + return false; + } + + buffer[n] = 0; + + struct ucred* cred = NULL; + + struct cmsghdr* cmsg = CMSG_FIRSTHDR(&hdr); + while (cmsg != NULL) { + if (cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_CREDENTIALS) { + cred = (struct ucred*)CMSG_DATA(cmsg); + break; + } + cmsg = CMSG_NXTHDR(&hdr, cmsg); + } + + struct ucred fake_cred; + if (cred == NULL) { + cred = &fake_cred; + cred->pid = 0; + cred->uid = DEFAULT_OVERFLOWUID; + } + + uint8_t* ptr = ((uint8_t*)buffer) + sizeof(android_log_header_t); + n -= sizeof(android_log_header_t); + + // When a log failed to write to statsd socket (e.g., due ot EBUSY), a special message would + // be sent to statsd when the socket communication becomes available again. + // The format is android_log_event_int_t with a single integer in the payload indicating the + // number of logs that failed. (*FORMAT MUST BE IN SYNC WITH system/core/libstats*) + // Note that all normal stats logs are in the format of event_list, so there won't be confusion. + // + // TODO(b/80538532): In addition to log it in StatsdStats, we should properly reset the config. + if (n == sizeof(android_log_event_long_t)) { + android_log_event_long_t* long_event = reinterpret_cast(ptr); + if (long_event->payload.type == EVENT_TYPE_LONG) { + int64_t composed_long = long_event->payload.data; + + // format: + // |last_tag|dropped_count| + int32_t dropped_count = (int32_t)(0xffffffff & composed_long); + int32_t last_atom_tag = (int32_t)((0xffffffff00000000 & (uint64_t)composed_long) >> 32); + + ALOGE("Found dropped events: %d error %d last atom tag %d from uid %d", dropped_count, + long_event->header.tag, last_atom_tag, cred->uid); + StatsdStats::getInstance().noteLogLost((int32_t)getWallClockSec(), dropped_count, + long_event->header.tag, last_atom_tag, cred->uid, + cred->pid); + return true; + } + } + + // move past the 4-byte StatsEventTag + uint8_t* msg = ptr + sizeof(uint32_t); + uint32_t len = n - sizeof(uint32_t); + uint32_t uid = cred->uid; + uint32_t pid = cred->pid; + + int64_t oldestTimestamp; + std::unique_ptr logEvent = std::make_unique(uid, pid); + logEvent->parseBuffer(msg, len); + + if (!mQueue->push(std::move(logEvent), &oldestTimestamp)) { + StatsdStats::getInstance().noteEventQueueOverflow(oldestTimestamp); + } + + return true; +} + +int StatsSocketListener::getLogSocket() { + static const char socketName[] = "statsdw"; + int sock = android_get_control_socket(socketName); + + if (sock < 0) { // statsd started up in init.sh + sock = socket_local_server(socketName, ANDROID_SOCKET_NAMESPACE_RESERVED, SOCK_DGRAM); + + int on = 1; + if (setsockopt(sock, SOL_SOCKET, SO_PASSCRED, &on, sizeof(on))) { + return -1; + } + } + return sock; +} + +} // namespace statsd +} // namespace os +} // namespace android diff --git a/statsd/src/socket/StatsSocketListener.h b/statsd/src/socket/StatsSocketListener.h new file mode 100644 index 00000000..2167a564 --- /dev/null +++ b/statsd/src/socket/StatsSocketListener.h @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2018 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. + */ +#pragma once + +#include +#include +#include "logd/LogEventQueue.h" + +// DEFAULT_OVERFLOWUID is defined in linux/highuid.h, which is not part of +// the uapi headers for userspace to use. This value is filled in on the +// out-of-band socket credentials if the OS fails to find one available. +// One of the causes of this is if SO_PASSCRED is set, all the packets before +// that point will have this value. We also use it in a fake credential if +// no socket credentials are supplied. +#ifndef DEFAULT_OVERFLOWUID +#define DEFAULT_OVERFLOWUID 65534 +#endif + +namespace android { +namespace os { +namespace statsd { + +class StatsSocketListener : public SocketListener, public virtual android::RefBase { +public: + explicit StatsSocketListener(std::shared_ptr queue); + + virtual ~StatsSocketListener(); + +protected: + virtual bool onDataAvailable(SocketClient* cli); + +private: + static int getLogSocket(); + /** + * Who is going to get the events when they're read. + */ + std::shared_ptr mQueue; +}; +} // namespace statsd +} // namespace os +} // namespace android diff --git a/statsd/src/state/StateListener.h b/statsd/src/state/StateListener.h new file mode 100644 index 00000000..63880017 --- /dev/null +++ b/statsd/src/state/StateListener.h @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2019, 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. + */ +#pragma once + +#include + +#include "HashableDimensionKey.h" + +namespace android { +namespace os { +namespace statsd { + +class StateListener : public virtual RefBase { +public: + StateListener(){}; + + virtual ~StateListener(){}; + + /** + * Interface for handling a state change. + * + * The old and new state values map to the original state values. + * StateTrackers only track the original state values and are unaware + * of higher-level state groups. MetricProducers hold information on + * state groups and are responsible for mapping original state values to + * the correct state group. + * + * [eventTimeNs]: Time of the state change log event. + * [atomId]: The id of the state atom + * [primaryKey]: The primary field values of the state atom + * [oldState]: Previous state value before state change + * [newState]: Current state value after state change + */ + virtual void onStateChanged(const int64_t eventTimeNs, const int32_t atomId, + const HashableDimensionKey& primaryKey, const FieldValue& oldState, + const FieldValue& newState) = 0; +}; + +} // namespace statsd +} // namespace os +} // namespace android diff --git a/statsd/src/state/StateManager.cpp b/statsd/src/state/StateManager.cpp new file mode 100644 index 00000000..c29afeb7 --- /dev/null +++ b/statsd/src/state/StateManager.cpp @@ -0,0 +1,112 @@ +/* + * Copyright (C) 2019, 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. + */ + +#define DEBUG false // STOPSHIP if true +#include "Log.h" + +#include "StateManager.h" + +#include + +namespace android { +namespace os { +namespace statsd { + +StateManager::StateManager() + : mAllowedPkg({ + "com.android.systemui", + }) { +} + +StateManager& StateManager::getInstance() { + static StateManager sStateManager; + return sStateManager; +} + +void StateManager::clear() { + mStateTrackers.clear(); +} + +void StateManager::onLogEvent(const LogEvent& event) { + // Only process state events from uids in AID_* and packages that are whitelisted in + // mAllowedPkg. + // Whitelisted AIDs are AID_ROOT and all AIDs in [1000, 2000) + if (event.GetUid() == AID_ROOT || (event.GetUid() >= 1000 && event.GetUid() < 2000) || + mAllowedLogSources.find(event.GetUid()) != mAllowedLogSources.end()) { + if (mStateTrackers.find(event.GetTagId()) != mStateTrackers.end()) { + mStateTrackers[event.GetTagId()]->onLogEvent(event); + } + } +} + +void StateManager::registerListener(const int32_t atomId, wp listener) { + // Check if state tracker already exists. + if (mStateTrackers.find(atomId) == mStateTrackers.end()) { + mStateTrackers[atomId] = new StateTracker(atomId); + } + mStateTrackers[atomId]->registerListener(listener); +} + +void StateManager::unregisterListener(const int32_t atomId, wp listener) { + std::unique_lock lock(mMutex); + + // Hold the sp<> until the lock is released so that ~StateTracker() is + // not called while the lock is held. + sp toRemove; + + // Unregister listener from correct StateTracker + auto it = mStateTrackers.find(atomId); + if (it != mStateTrackers.end()) { + it->second->unregisterListener(listener); + + // Remove the StateTracker if it has no listeners + if (it->second->getListenersCount() == 0) { + toRemove = it->second; + mStateTrackers.erase(it); + } + } else { + ALOGE("StateManager cannot unregister listener, StateTracker for atom %d does not exist", + atomId); + } + lock.unlock(); +} + +bool StateManager::getStateValue(const int32_t atomId, const HashableDimensionKey& key, + FieldValue* output) const { + auto it = mStateTrackers.find(atomId); + if (it != mStateTrackers.end()) { + return it->second->getStateValue(key, output); + } + return false; +} + +void StateManager::updateLogSources(const sp& uidMap) { + mAllowedLogSources.clear(); + for (const auto& pkg : mAllowedPkg) { + auto uids = uidMap->getAppUid(pkg); + mAllowedLogSources.insert(uids.begin(), uids.end()); + } +} + +void StateManager::notifyAppChanged(const string& apk, const sp& uidMap) { + if (mAllowedPkg.find(apk) != mAllowedPkg.end()) { + updateLogSources(uidMap); + } +} + +} // namespace statsd +} // namespace os +} // namespace android diff --git a/statsd/src/state/StateManager.h b/statsd/src/state/StateManager.h new file mode 100644 index 00000000..18c404c2 --- /dev/null +++ b/statsd/src/state/StateManager.h @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2019, 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. + */ +#pragma once + +#include +#include + +#include +#include +#include + +#include "HashableDimensionKey.h" +#include "packages/UidMap.h" +#include "state/StateListener.h" +#include "state/StateTracker.h" + +namespace android { +namespace os { +namespace statsd { + +/** + * This class is NOT thread safe. + * It should only be used while StatsLogProcessor's lock is held. + */ +class StateManager : public virtual RefBase { +public: + StateManager(); + + ~StateManager(){}; + + // Returns a pointer to the single, shared StateManager object. + static StateManager& getInstance(); + + // Unregisters all listeners and removes all trackers from StateManager. + void clear(); + + // Notifies the correct StateTracker of an event. + void onLogEvent(const LogEvent& event); + + // Notifies the StateTracker for the given atomId to register listener. + // If the correct StateTracker does not exist, a new StateTracker is created. + // Note: StateTrackers can be created for non-state atoms. They are essentially empty and + // do not perform any actions. + void registerListener(const int32_t atomId, wp listener); + + // Notifies the correct StateTracker to unregister a listener + // and removes the tracker if it no longer has any listeners. + void unregisterListener(const int32_t atomId, wp listener); + + // Returns true if the StateTracker exists and queries for the + // original state value mapped to the given query key. The state value is + // stored and output in a FieldValue class. + // Returns false if the StateTracker doesn't exist. + bool getStateValue(const int32_t atomId, const HashableDimensionKey& queryKey, + FieldValue* output) const; + + // Updates mAllowedLogSources with the latest uids for the packages that are allowed to log. + void updateLogSources(const sp& uidMap); + + void notifyAppChanged(const string& apk, const sp& uidMap); + + inline int getStateTrackersCount() const { + return mStateTrackers.size(); + } + + inline int getListenersCount(const int32_t atomId) const { + auto it = mStateTrackers.find(atomId); + if (it != mStateTrackers.end()) { + return it->second->getListenersCount(); + } + return -1; + } + +private: + mutable std::mutex mMutex; + + // Maps state atom ids to StateTrackers + std::unordered_map> mStateTrackers; + + // The package names that can log state events. + const std::set mAllowedPkg; + + // The combined uid sources (after translating pkg name to uid). + // State events from uids that are not in the list will be ignored to avoid state pollution. + std::set mAllowedLogSources; +}; + +} // namespace statsd +} // namespace os +} // namespace android diff --git a/statsd/src/state/StateTracker.cpp b/statsd/src/state/StateTracker.cpp new file mode 100644 index 00000000..41e525c3 --- /dev/null +++ b/statsd/src/state/StateTracker.cpp @@ -0,0 +1,190 @@ +/* + * Copyright (C) 2019, 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. + */ + +#define DEBUG true // STOPSHIP if true +#include "Log.h" + +#include "stats_util.h" + +#include "StateTracker.h" + +namespace android { +namespace os { +namespace statsd { + +StateTracker::StateTracker(const int32_t atomId) : mField(atomId, 0) { +} + +void StateTracker::onLogEvent(const LogEvent& event) { + const int64_t eventTimeNs = event.GetElapsedTimestampNs(); + + // Parse event for primary field values i.e. primary key. + HashableDimensionKey primaryKey; + filterPrimaryKey(event.getValues(), &primaryKey); + + FieldValue newState; + if (!getStateFieldValueFromLogEvent(event, &newState)) { + ALOGE("StateTracker error extracting state from log event. Missing exclusive state field."); + clearStateForPrimaryKey(eventTimeNs, primaryKey); + return; + } + + mField.setField(newState.mField.getField()); + + if (newState.mValue.getType() != INT) { + ALOGE("StateTracker error extracting state from log event. Type: %d", + newState.mValue.getType()); + clearStateForPrimaryKey(eventTimeNs, primaryKey); + return; + } + + if (int resetState = event.getResetState(); resetState != -1) { + VLOG("StateTracker new reset state: %d", resetState); + const FieldValue resetStateFieldValue(mField, Value(resetState)); + handleReset(eventTimeNs, resetStateFieldValue); + return; + } + + const bool nested = newState.mAnnotations.isNested(); + StateValueInfo* stateValueInfo = &mStateMap[primaryKey]; + updateStateForPrimaryKey(eventTimeNs, primaryKey, newState, nested, stateValueInfo); +} + +void StateTracker::registerListener(wp listener) { + mListeners.insert(listener); +} + +void StateTracker::unregisterListener(wp listener) { + mListeners.erase(listener); +} + +bool StateTracker::getStateValue(const HashableDimensionKey& queryKey, FieldValue* output) const { + output->mField = mField; + + if (const auto it = mStateMap.find(queryKey); it != mStateMap.end()) { + output->mValue = it->second.state; + return true; + } + + // Set the state value to kStateUnknown if query key is not found in state map. + output->mValue = kStateUnknown; + return false; +} + +void StateTracker::handleReset(const int64_t eventTimeNs, const FieldValue& newState) { + VLOG("StateTracker handle reset"); + for (auto& [primaryKey, stateValueInfo] : mStateMap) { + updateStateForPrimaryKey(eventTimeNs, primaryKey, newState, + false /* nested; treat this state change as not nested */, + &stateValueInfo); + } +} + +void StateTracker::clearStateForPrimaryKey(const int64_t eventTimeNs, + const HashableDimensionKey& primaryKey) { + VLOG("StateTracker clear state for primary key"); + const std::unordered_map::iterator it = + mStateMap.find(primaryKey); + + // If there is no entry for the primaryKey in mStateMap, then the state is already + // kStateUnknown. + const FieldValue state(mField, Value(kStateUnknown)); + if (it != mStateMap.end()) { + updateStateForPrimaryKey(eventTimeNs, primaryKey, state, + false /* nested; treat this state change as not nested */, + &it->second); + } +} + +void StateTracker::updateStateForPrimaryKey(const int64_t eventTimeNs, + const HashableDimensionKey& primaryKey, + const FieldValue& newState, const bool nested, + StateValueInfo* stateValueInfo) { + FieldValue oldState; + oldState.mField = mField; + oldState.mValue.setInt(stateValueInfo->state); + const int32_t oldStateValue = stateValueInfo->state; + const int32_t newStateValue = newState.mValue.int_value; + + if (kStateUnknown == newStateValue) { + mStateMap.erase(primaryKey); + } + + // Update state map for non-nested counting case. + // Every state event triggers a state overwrite. + if (!nested) { + stateValueInfo->state = newStateValue; + stateValueInfo->count = 1; + + // Notify listeners if state has changed. + if (oldStateValue != newStateValue) { + notifyListeners(eventTimeNs, primaryKey, oldState, newState); + } + return; + } + + // Update state map for nested counting case. + // + // Nested counting is only allowed for binary state events such as ON/OFF or + // ACQUIRE/RELEASE. For example, WakelockStateChanged might have the state + // events: ON, ON, OFF. The state will still be ON until we see the same + // number of OFF events as ON events. + // + // In atoms.proto, a state atom with nested counting enabled + // must only have 2 states. There is no enforcemnt here of this requirement. + // The atom must be logged correctly. + if (kStateUnknown == newStateValue) { + if (kStateUnknown != oldStateValue) { + notifyListeners(eventTimeNs, primaryKey, oldState, newState); + } + } else if (oldStateValue == kStateUnknown) { + stateValueInfo->state = newStateValue; + stateValueInfo->count = 1; + notifyListeners(eventTimeNs, primaryKey, oldState, newState); + } else if (oldStateValue == newStateValue) { + stateValueInfo->count++; + } else if (--stateValueInfo->count == 0) { + stateValueInfo->state = newStateValue; + stateValueInfo->count = 1; + notifyListeners(eventTimeNs, primaryKey, oldState, newState); + } +} + +void StateTracker::notifyListeners(const int64_t eventTimeNs, + const HashableDimensionKey& primaryKey, + const FieldValue& oldState, const FieldValue& newState) { + for (auto l : mListeners) { + auto sl = l.promote(); + if (sl != nullptr) { + sl->onStateChanged(eventTimeNs, mField.getTag(), primaryKey, oldState, newState); + } + } +} + +bool getStateFieldValueFromLogEvent(const LogEvent& event, FieldValue* output) { + const int exclusiveStateFieldIndex = event.getExclusiveStateFieldIndex(); + if (-1 == exclusiveStateFieldIndex) { + ALOGE("error extracting state from log event. Missing exclusive state field."); + return false; + } + + *output = event.getValues()[exclusiveStateFieldIndex]; + return true; +} + +} // namespace statsd +} // namespace os +} // namespace android diff --git a/statsd/src/state/StateTracker.h b/statsd/src/state/StateTracker.h new file mode 100644 index 00000000..abd579e7 --- /dev/null +++ b/statsd/src/state/StateTracker.h @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2019, 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. + */ +#pragma once + +#include +#include "HashableDimensionKey.h" +#include "logd/LogEvent.h" + +#include "state/StateListener.h" + +#include + +namespace android { +namespace os { +namespace statsd { + +class StateTracker : public virtual RefBase { +public: + StateTracker(const int32_t atomId); + + virtual ~StateTracker(){}; + + // Updates state map and notifies all listeners if a state change occurs. + // Checks if a state change has occurred by getting the state value from + // the log event and comparing the old and new states. + void onLogEvent(const LogEvent& event); + + // Adds new listeners to set of StateListeners. If a listener is already + // registered, it is ignored. + void registerListener(wp listener); + + void unregisterListener(wp listener); + + // The output is a FieldValue object that has mStateField as the field and + // the original state value (found using the given query key) as the value. + // + // If the key isn't mapped to a state or the key size doesn't match the + // number of primary fields, the output value is set to kStateUnknown. + bool getStateValue(const HashableDimensionKey& queryKey, FieldValue* output) const; + + inline int getListenersCount() const { + return mListeners.size(); + } + + const static int kStateUnknown = -1; + +private: + struct StateValueInfo { + int32_t state = kStateUnknown; // state value + int count = 0; // nested count (only used for binary states) + }; + + Field mField; + + // Maps primary key to state value info + std::unordered_map mStateMap; + + // Set of all StateListeners (objects listening for state changes) + std::set> mListeners; + + // Reset all state values in map to the given state. + void handleReset(const int64_t eventTimeNs, const FieldValue& newState); + + // Clears the state value mapped to the given primary key by setting it to kStateUnknown. + void clearStateForPrimaryKey(const int64_t eventTimeNs, const HashableDimensionKey& primaryKey); + + // Update the StateMap based on the received state value. + void updateStateForPrimaryKey(const int64_t eventTimeNs, const HashableDimensionKey& primaryKey, + const FieldValue& newState, const bool nested, + StateValueInfo* stateValueInfo); + + // Notify registered state listeners of state change. + void notifyListeners(const int64_t eventTimeNs, const HashableDimensionKey& primaryKey, + const FieldValue& oldState, const FieldValue& newState); +}; + +bool getStateFieldValueFromLogEvent(const LogEvent& event, FieldValue* output); + +} // namespace statsd +} // namespace os +} // namespace android diff --git a/statsd/src/stats_log.proto b/statsd/src/stats_log.proto new file mode 100644 index 00000000..f91c4c08 --- /dev/null +++ b/statsd/src/stats_log.proto @@ -0,0 +1,556 @@ +/* + * 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. + */ + +syntax = "proto2"; + +package android.os.statsd; + +option java_package = "com.android.os"; +option java_outer_classname = "StatsLog"; + +import "frameworks/proto_logging/stats/atoms.proto"; + +message DimensionsValue { + optional int32 field = 1; + + oneof value { + string value_str = 2; + int32 value_int = 3; + int64 value_long = 4; + bool value_bool = 5; + float value_float = 6; + DimensionsValueTuple value_tuple = 7; + uint64 value_str_hash = 8; + } +} + +message DimensionsValueTuple { + repeated DimensionsValue dimensions_value = 1; +} + +message StateValue { + optional int32 atom_id = 1; + + oneof contents { + int64 group_id = 2; + int32 value = 3; + } +} + +message EventMetricData { + optional int64 elapsed_timestamp_nanos = 1; + + optional Atom atom = 2; + + optional int64 wall_clock_timestamp_nanos = 3 [deprecated = true]; +} + +message CountBucketInfo { + optional int64 start_bucket_elapsed_nanos = 1; + + optional int64 end_bucket_elapsed_nanos = 2; + + optional int64 count = 3; + + optional int64 bucket_num = 4; + + optional int64 start_bucket_elapsed_millis = 5; + + optional int64 end_bucket_elapsed_millis = 6; +} + +message CountMetricData { + optional DimensionsValue dimensions_in_what = 1; + + repeated StateValue slice_by_state = 6; + + repeated CountBucketInfo bucket_info = 3; + + repeated DimensionsValue dimension_leaf_values_in_what = 4; + + optional DimensionsValue dimensions_in_condition = 2 [deprecated = true]; + + repeated DimensionsValue dimension_leaf_values_in_condition = 5 [deprecated = true]; +} + +message DurationBucketInfo { + optional int64 start_bucket_elapsed_nanos = 1; + + optional int64 end_bucket_elapsed_nanos = 2; + + optional int64 duration_nanos = 3; + + optional int64 bucket_num = 4; + + optional int64 start_bucket_elapsed_millis = 5; + + optional int64 end_bucket_elapsed_millis = 6; +} + +message DurationMetricData { + optional DimensionsValue dimensions_in_what = 1; + + repeated StateValue slice_by_state = 6; + + repeated DurationBucketInfo bucket_info = 3; + + repeated DimensionsValue dimension_leaf_values_in_what = 4; + + optional DimensionsValue dimensions_in_condition = 2 [deprecated = true]; + + repeated DimensionsValue dimension_leaf_values_in_condition = 5 [deprecated = true]; +} + +message ValueBucketInfo { + optional int64 start_bucket_elapsed_nanos = 1; + + optional int64 end_bucket_elapsed_nanos = 2; + + optional int64 value = 3 [deprecated = true]; + + oneof single_value { + int64 value_long = 7 [deprecated = true]; + + double value_double = 8 [deprecated = true]; + } + + message Value { + optional int32 index = 1; + oneof value { + int64 value_long = 2; + double value_double = 3; + } + } + + repeated Value values = 9; + + optional int64 bucket_num = 4; + + optional int64 start_bucket_elapsed_millis = 5; + + optional int64 end_bucket_elapsed_millis = 6; + + optional int64 condition_true_nanos = 10; +} + +message ValueMetricData { + optional DimensionsValue dimensions_in_what = 1; + + repeated StateValue slice_by_state = 6; + + repeated ValueBucketInfo bucket_info = 3; + + repeated DimensionsValue dimension_leaf_values_in_what = 4; + + optional DimensionsValue dimensions_in_condition = 2 [deprecated = true]; + + repeated DimensionsValue dimension_leaf_values_in_condition = 5 [deprecated = true]; +} + +message GaugeBucketInfo { + optional int64 start_bucket_elapsed_nanos = 1; + + optional int64 end_bucket_elapsed_nanos = 2; + + repeated Atom atom = 3; + + repeated int64 elapsed_timestamp_nanos = 4; + + repeated int64 wall_clock_timestamp_nanos = 5 [deprecated = true]; + + optional int64 bucket_num = 6; + + optional int64 start_bucket_elapsed_millis = 7; + + optional int64 end_bucket_elapsed_millis = 8; +} + +message GaugeMetricData { + optional DimensionsValue dimensions_in_what = 1; + + // Currently unsupported + repeated StateValue slice_by_state = 6; + + repeated GaugeBucketInfo bucket_info = 3; + + repeated DimensionsValue dimension_leaf_values_in_what = 4; + + optional DimensionsValue dimensions_in_condition = 2 [deprecated = true]; + + repeated DimensionsValue dimension_leaf_values_in_condition = 5 [deprecated = true]; +} + +message StatsLogReport { + optional int64 metric_id = 1; + + // Fields 2 and 3 are reserved. + + // Keep this in sync with BucketDropReason enum in MetricProducer.h. + enum BucketDropReason { + // For ValueMetric, a bucket is dropped during a dump report request iff + // current bucket should be included, a pull is needed (pulled metric and + // condition is true), and we are under fast time constraints. + DUMP_REPORT_REQUESTED = 1; + EVENT_IN_WRONG_BUCKET = 2; + CONDITION_UNKNOWN = 3; + PULL_FAILED = 4; + PULL_DELAYED = 5; + DIMENSION_GUARDRAIL_REACHED = 6; + MULTIPLE_BUCKETS_SKIPPED = 7; + // Not an invalid bucket case, but the bucket is dropped. + BUCKET_TOO_SMALL = 8; + // Not an invalid bucket case, but the bucket is skipped. + NO_DATA = 9; + }; + + message DropEvent { + optional BucketDropReason drop_reason = 1; + + optional int64 drop_time_millis = 2; + } + + message SkippedBuckets { + optional int64 start_bucket_elapsed_nanos = 1; + + optional int64 end_bucket_elapsed_nanos = 2; + + optional int64 start_bucket_elapsed_millis = 3; + + optional int64 end_bucket_elapsed_millis = 4; + + // The number of drop events is capped by StatsdStats::kMaxLoggedBucketDropEvents. + // The current maximum is 10 drop events. + repeated DropEvent drop_event = 5; + } + + message EventMetricDataWrapper { + repeated EventMetricData data = 1; + } + message CountMetricDataWrapper { + repeated CountMetricData data = 1; + } + message DurationMetricDataWrapper { + repeated DurationMetricData data = 1; + } + message ValueMetricDataWrapper { + repeated ValueMetricData data = 1; + repeated SkippedBuckets skipped = 2; + } + + message GaugeMetricDataWrapper { + repeated GaugeMetricData data = 1; + repeated SkippedBuckets skipped = 2; + } + + oneof data { + EventMetricDataWrapper event_metrics = 4; + CountMetricDataWrapper count_metrics = 5; + DurationMetricDataWrapper duration_metrics = 6; + ValueMetricDataWrapper value_metrics = 7; + GaugeMetricDataWrapper gauge_metrics = 8; + } + + optional int64 time_base_elapsed_nano_seconds = 9; + + optional int64 bucket_size_nano_seconds = 10; + + optional DimensionsValue dimensions_path_in_what = 11; + + optional DimensionsValue dimensions_path_in_condition = 12 [deprecated = true]; + + // DO NOT USE field 13. + + optional bool is_active = 14; +} + +message UidMapping { + message PackageInfoSnapshot { + message PackageInfo { + optional string name = 1; + + optional int64 version = 2; + + optional int32 uid = 3; + + optional bool deleted = 4; + + optional uint64 name_hash = 5; + + optional string version_string = 6; + + optional uint64 version_string_hash = 7; + + optional string installer = 8; + + optional uint64 installer_hash = 9; + } + optional int64 elapsed_timestamp_nanos = 1; + + repeated PackageInfo package_info = 2; + } + repeated PackageInfoSnapshot snapshots = 1; + + message Change { + optional bool deletion = 1; + + optional int64 elapsed_timestamp_nanos = 2; + optional string app = 3; + optional int32 uid = 4; + + optional int64 new_version = 5; + optional int64 prev_version = 6; + optional uint64 app_hash = 7; + optional string new_version_string = 8; + optional string prev_version_string = 9; + optional uint64 new_version_string_hash = 10; + optional uint64 prev_version_string_hash = 11; + } + repeated Change changes = 2; +} + +message ConfigMetricsReport { + repeated StatsLogReport metrics = 1; + + optional UidMapping uid_map = 2; + + optional int64 last_report_elapsed_nanos = 3; + + optional int64 current_report_elapsed_nanos = 4; + + optional int64 last_report_wall_clock_nanos = 5; + + optional int64 current_report_wall_clock_nanos = 6; + + message Annotation { + optional int64 field_int64 = 1; + optional int32 field_int32 = 2; + } + repeated Annotation annotation = 7; + + enum DumpReportReason { + DEVICE_SHUTDOWN = 1; + CONFIG_UPDATED = 2; + CONFIG_REMOVED = 3; + GET_DATA_CALLED = 4; + ADB_DUMP = 5; + CONFIG_RESET = 6; + STATSCOMPANION_DIED = 7; + TERMINATION_SIGNAL_RECEIVED = 8; + } + optional DumpReportReason dump_report_reason = 8; + + repeated string strings = 9; +} + +message ConfigMetricsReportList { + message ConfigKey { + optional int32 uid = 1; + optional int64 id = 2; + } + optional ConfigKey config_key = 1; + + repeated ConfigMetricsReport reports = 2; + + reserved 10; +} + +message StatsdStatsReport { + optional int32 stats_begin_time_sec = 1; + + optional int32 stats_end_time_sec = 2; + + message MatcherStats { + optional int64 id = 1; + optional int32 matched_times = 2; + } + + message ConditionStats { + optional int64 id = 1; + optional int32 max_tuple_counts = 2; + } + + message MetricStats { + optional int64 id = 1; + optional int32 max_tuple_counts = 2; + } + + message AlertStats { + optional int64 id = 1; + optional int32 alerted_times = 2; + } + + message ConfigStats { + optional int32 uid = 1; + optional int64 id = 2; + optional int32 creation_time_sec = 3; + optional int32 deletion_time_sec = 4; + optional int32 reset_time_sec = 19; + optional int32 metric_count = 5; + optional int32 condition_count = 6; + optional int32 matcher_count = 7; + optional int32 alert_count = 8; + optional bool is_valid = 9; + repeated int32 broadcast_sent_time_sec = 10; + repeated int32 data_drop_time_sec = 11; + repeated int64 data_drop_bytes = 21; + repeated int32 dump_report_time_sec = 12; + repeated int32 dump_report_data_size = 20; + repeated MatcherStats matcher_stats = 13; + repeated ConditionStats condition_stats = 14; + repeated MetricStats metric_stats = 15; + repeated AlertStats alert_stats = 16; + repeated MetricStats metric_dimension_in_condition_stats = 17 [deprecated = true]; + message Annotation { + optional int64 field_int64 = 1; + optional int32 field_int32 = 2; + } + repeated Annotation annotation = 18; + repeated int32 activation_time_sec = 22; + repeated int32 deactivation_time_sec = 23; + } + + repeated ConfigStats config_stats = 3; + + message AtomStats { + optional int32 tag = 1; + optional int32 count = 2; + optional int32 error_count = 3; + } + + repeated AtomStats atom_stats = 7; + + message UidMapStats { + optional int32 changes = 1; + optional int32 bytes_used = 2; + optional int32 dropped_changes = 3; + optional int32 deleted_apps = 4; + } + optional UidMapStats uidmap_stats = 8; + + message AnomalyAlarmStats { + optional int32 alarms_registered = 1; + } + optional AnomalyAlarmStats anomaly_alarm_stats = 9; + + message PulledAtomStats { + optional int32 atom_id = 1; + optional int64 total_pull = 2; + optional int64 total_pull_from_cache = 3; + optional int64 min_pull_interval_sec = 4; + optional int64 average_pull_time_nanos = 5; + optional int64 max_pull_time_nanos = 6; + optional int64 average_pull_delay_nanos = 7; + optional int64 max_pull_delay_nanos = 8; + optional int64 data_error = 9; + optional int64 pull_timeout = 10; + optional int64 pull_exceed_max_delay = 11; + optional int64 pull_failed = 12; + optional int64 stats_companion_pull_failed = 13 [deprecated = true]; + optional int64 stats_companion_pull_binder_transaction_failed = 14 [deprecated = true]; + optional int64 empty_data = 15; + optional int64 registered_count = 16; + optional int64 unregistered_count = 17; + optional int32 atom_error_count = 18; + optional int64 binder_call_failed = 19; + optional int64 failed_uid_provider_not_found = 20; + optional int64 puller_not_found = 21; + message PullTimeoutMetadata { + optional int64 pull_timeout_uptime_millis = 1; + optional int64 pull_timeout_elapsed_millis = 2; + } + repeated PullTimeoutMetadata pull_atom_metadata = 22; + } + repeated PulledAtomStats pulled_atom_stats = 10; + + message AtomMetricStats { + optional int64 metric_id = 1; + optional int64 hard_dimension_limit_reached = 2; + optional int64 late_log_event_skipped = 3; + optional int64 skipped_forward_buckets = 4; + optional int64 bad_value_type = 5; + optional int64 condition_change_in_next_bucket = 6; + optional int64 invalidated_bucket = 7; + optional int64 bucket_dropped = 8; + optional int64 min_bucket_boundary_delay_ns = 9; + optional int64 max_bucket_boundary_delay_ns = 10; + optional int64 bucket_unknown_condition = 11; + optional int64 bucket_count = 12; + optional int64 late_log_event = 13; + optional int64 sum_late_log_event_extra_duration_ns = 14; + optional int64 max_late_log_event_extra_duration_ns = 15; + } + repeated AtomMetricStats atom_metric_stats = 17; + + message LoggerErrorStats { + optional int32 logger_disconnection_sec = 1; + optional int32 error_code = 2; + } + repeated LoggerErrorStats logger_error_stats = 11; + + message PeriodicAlarmStats { + optional int32 alarms_registered = 1; + } + optional PeriodicAlarmStats periodic_alarm_stats = 12; + + message SkippedLogEventStats { + optional int32 tag = 1; + optional int64 elapsed_timestamp_nanos = 2; + } + repeated SkippedLogEventStats skipped_log_event_stats = 13; + + repeated int64 log_loss_stats = 14; + + repeated int32 system_restart_sec = 15; + + message LogLossStats { + optional int32 detected_time_sec = 1; + optional int32 count = 2; + optional int32 last_error = 3; + optional int32 last_tag = 4; + optional int32 uid = 5; + optional int32 pid = 6; + } + repeated LogLossStats detected_log_loss = 16; + + message EventQueueOverflow { + optional int32 count = 1; + optional int64 max_queue_history_ns = 2; + optional int64 min_queue_history_ns = 3; + } + + optional EventQueueOverflow queue_overflow = 18; + + message ActivationBroadcastGuardrail { + optional int32 uid = 1; + repeated int32 guardrail_met_sec = 2; + } + + repeated ActivationBroadcastGuardrail activation_guardrail_stats = 19; +} + +message AlertTriggerDetails { + message MetricValue { + optional int64 metric_id = 1; + optional DimensionsValue dimension_in_what = 2; + optional DimensionsValue dimension_in_condition = 3 [deprecated = true]; + optional int64 value = 4; + } + oneof value { + MetricValue trigger_metric = 1; + EventMetricData trigger_event = 2; + } + optional UidMapping.PackageInfoSnapshot package_info = 3; +} diff --git a/statsd/src/stats_log_util.cpp b/statsd/src/stats_log_util.cpp new file mode 100644 index 00000000..a7e4d703 --- /dev/null +++ b/statsd/src/stats_log_util.cpp @@ -0,0 +1,625 @@ +/* + * 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 "hash.h" +#include "stats_log_util.h" + +#include +#include +#include +#include + +#include "statscompanion_util.h" + +using android::util::FIELD_COUNT_REPEATED; +using android::util::FIELD_TYPE_BOOL; +using android::util::FIELD_TYPE_FIXED64; +using android::util::FIELD_TYPE_FLOAT; +using android::util::FIELD_TYPE_INT32; +using android::util::FIELD_TYPE_INT64; +using android::util::FIELD_TYPE_MESSAGE; +using android::util::FIELD_TYPE_STRING; +using android::util::FIELD_TYPE_UINT64; +using android::util::ProtoOutputStream; + +using aidl::android::os::IStatsCompanionService; +using std::shared_ptr; +using std::string; + +namespace android { +namespace os { +namespace statsd { + +// for DimensionsValue Proto +const int DIMENSIONS_VALUE_FIELD = 1; +const int DIMENSIONS_VALUE_VALUE_STR = 2; +const int DIMENSIONS_VALUE_VALUE_INT = 3; +const int DIMENSIONS_VALUE_VALUE_LONG = 4; +// const int DIMENSIONS_VALUE_VALUE_BOOL = 5; // logd doesn't have bool data type. +const int DIMENSIONS_VALUE_VALUE_FLOAT = 6; +const int DIMENSIONS_VALUE_VALUE_TUPLE = 7; +const int DIMENSIONS_VALUE_VALUE_STR_HASH = 8; + +const int DIMENSIONS_VALUE_TUPLE_VALUE = 1; + +// for StateValue Proto +const int STATE_VALUE_ATOM_ID = 1; +const int STATE_VALUE_CONTENTS_GROUP_ID = 2; +const int STATE_VALUE_CONTENTS_VALUE = 3; + +// for PulledAtomStats proto +const int FIELD_ID_PULLED_ATOM_STATS = 10; +const int FIELD_ID_PULL_ATOM_ID = 1; +const int FIELD_ID_TOTAL_PULL = 2; +const int FIELD_ID_TOTAL_PULL_FROM_CACHE = 3; +const int FIELD_ID_MIN_PULL_INTERVAL_SEC = 4; +const int FIELD_ID_AVERAGE_PULL_TIME_NANOS = 5; +const int FIELD_ID_MAX_PULL_TIME_NANOS = 6; +const int FIELD_ID_AVERAGE_PULL_DELAY_NANOS = 7; +const int FIELD_ID_MAX_PULL_DELAY_NANOS = 8; +const int FIELD_ID_DATA_ERROR = 9; +const int FIELD_ID_PULL_TIMEOUT = 10; +const int FIELD_ID_PULL_EXCEED_MAX_DELAY = 11; +const int FIELD_ID_PULL_FAILED = 12; +const int FIELD_ID_EMPTY_DATA = 15; +const int FIELD_ID_PULL_REGISTERED_COUNT = 16; +const int FIELD_ID_PULL_UNREGISTERED_COUNT = 17; +const int FIELD_ID_ATOM_ERROR_COUNT = 18; +const int FIELD_ID_BINDER_CALL_FAIL_COUNT = 19; +const int FIELD_ID_PULL_UID_PROVIDER_NOT_FOUND = 20; +const int FIELD_ID_PULLER_NOT_FOUND = 21; +const int FIELD_ID_PULL_TIMEOUT_METADATA = 22; +const int FIELD_ID_PULL_TIMEOUT_METADATA_UPTIME_MILLIS = 1; +const int FIELD_ID_PULL_TIMEOUT_METADATA_ELAPSED_MILLIS = 2; + +// for AtomMetricStats proto +const int FIELD_ID_ATOM_METRIC_STATS = 17; +const int FIELD_ID_METRIC_ID = 1; +const int FIELD_ID_HARD_DIMENSION_LIMIT_REACHED = 2; +const int FIELD_ID_LATE_LOG_EVENT_SKIPPED = 3; +const int FIELD_ID_SKIPPED_FORWARD_BUCKETS = 4; +const int FIELD_ID_BAD_VALUE_TYPE = 5; +const int FIELD_ID_CONDITION_CHANGE_IN_NEXT_BUCKET = 6; +const int FIELD_ID_INVALIDATED_BUCKET = 7; +const int FIELD_ID_BUCKET_DROPPED = 8; +const int FIELD_ID_MIN_BUCKET_BOUNDARY_DELAY_NS = 9; +const int FIELD_ID_MAX_BUCKET_BOUNDARY_DELAY_NS = 10; +const int FIELD_ID_BUCKET_UNKNOWN_CONDITION = 11; +const int FIELD_ID_BUCKET_COUNT = 12; +const int FIELD_ID_LATE_LOG_EVENT = 13; +const int FIELD_ID_SUM_LATE_LOG_EVENT_EXTRA_DURATION_NS = 14; +const int FIELD_ID_MAX_LATE_LOG_EVENT_EXTRA_DURATION_NS = 15; + +namespace { + +void writeDimensionToProtoHelper(const std::vector& dims, size_t* index, int depth, + int prefix, std::set *str_set, + ProtoOutputStream* protoOutput) { + size_t count = dims.size(); + while (*index < count) { + const auto& dim = dims[*index]; + const int valueDepth = dim.mField.getDepth(); + const int valuePrefix = dim.mField.getPrefix(depth); + const int fieldNum = dim.mField.getPosAtDepth(depth); + if (valueDepth > 2) { + ALOGE("Depth > 2 not supported"); + return; + } + + if (depth == valueDepth && valuePrefix == prefix) { + uint64_t token = protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | + DIMENSIONS_VALUE_TUPLE_VALUE); + protoOutput->write(FIELD_TYPE_INT32 | DIMENSIONS_VALUE_FIELD, fieldNum); + switch (dim.mValue.getType()) { + case INT: + protoOutput->write(FIELD_TYPE_INT32 | DIMENSIONS_VALUE_VALUE_INT, + dim.mValue.int_value); + break; + case LONG: + protoOutput->write(FIELD_TYPE_INT64 | DIMENSIONS_VALUE_VALUE_LONG, + (long long)dim.mValue.long_value); + break; + case FLOAT: + protoOutput->write(FIELD_TYPE_FLOAT | DIMENSIONS_VALUE_VALUE_FLOAT, + dim.mValue.float_value); + break; + case STRING: + if (str_set == nullptr) { + protoOutput->write(FIELD_TYPE_STRING | DIMENSIONS_VALUE_VALUE_STR, + dim.mValue.str_value); + } else { + str_set->insert(dim.mValue.str_value); + protoOutput->write( + FIELD_TYPE_UINT64 | DIMENSIONS_VALUE_VALUE_STR_HASH, + (long long)Hash64(dim.mValue.str_value)); + } + break; + default: + break; + } + if (token != 0) { + protoOutput->end(token); + } + (*index)++; + } else if (valueDepth > depth && valuePrefix == prefix) { + // Writing the sub tree + uint64_t dimensionToken = protoOutput->start( + FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | DIMENSIONS_VALUE_TUPLE_VALUE); + protoOutput->write(FIELD_TYPE_INT32 | DIMENSIONS_VALUE_FIELD, fieldNum); + uint64_t tupleToken = + protoOutput->start(FIELD_TYPE_MESSAGE | DIMENSIONS_VALUE_VALUE_TUPLE); + writeDimensionToProtoHelper(dims, index, valueDepth, dim.mField.getPrefix(valueDepth), + str_set, protoOutput); + protoOutput->end(tupleToken); + protoOutput->end(dimensionToken); + } else { + // Done with the prev sub tree + return; + } + } +} + +void writeDimensionLeafToProtoHelper(const std::vector& dims, + const int dimensionLeafField, + size_t* index, int depth, + int prefix, std::set *str_set, + ProtoOutputStream* protoOutput) { + size_t count = dims.size(); + while (*index < count) { + const auto& dim = dims[*index]; + const int valueDepth = dim.mField.getDepth(); + const int valuePrefix = dim.mField.getPrefix(depth); + if (valueDepth > 2) { + ALOGE("Depth > 2 not supported"); + return; + } + + if (depth == valueDepth && valuePrefix == prefix) { + uint64_t token = protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | + dimensionLeafField); + switch (dim.mValue.getType()) { + case INT: + protoOutput->write(FIELD_TYPE_INT32 | DIMENSIONS_VALUE_VALUE_INT, + dim.mValue.int_value); + break; + case LONG: + protoOutput->write(FIELD_TYPE_INT64 | DIMENSIONS_VALUE_VALUE_LONG, + (long long)dim.mValue.long_value); + break; + case FLOAT: + protoOutput->write(FIELD_TYPE_FLOAT | DIMENSIONS_VALUE_VALUE_FLOAT, + dim.mValue.float_value); + break; + case STRING: + if (str_set == nullptr) { + protoOutput->write(FIELD_TYPE_STRING | DIMENSIONS_VALUE_VALUE_STR, + dim.mValue.str_value); + } else { + str_set->insert(dim.mValue.str_value); + protoOutput->write( + FIELD_TYPE_UINT64 | DIMENSIONS_VALUE_VALUE_STR_HASH, + (long long)Hash64(dim.mValue.str_value)); + } + break; + default: + break; + } + if (token != 0) { + protoOutput->end(token); + } + (*index)++; + } else if (valueDepth > depth && valuePrefix == prefix) { + writeDimensionLeafToProtoHelper(dims, dimensionLeafField, + index, valueDepth, dim.mField.getPrefix(valueDepth), + str_set, protoOutput); + } else { + // Done with the prev sub tree + return; + } + } +} + +void writeDimensionPathToProtoHelper(const std::vector& fieldMatchers, + size_t* index, int depth, int prefix, + ProtoOutputStream* protoOutput) { + size_t count = fieldMatchers.size(); + while (*index < count) { + const Field& field = fieldMatchers[*index].mMatcher; + const int valueDepth = field.getDepth(); + const int valuePrefix = field.getPrefix(depth); + const int fieldNum = field.getPosAtDepth(depth); + if (valueDepth > 2) { + ALOGE("Depth > 2 not supported"); + return; + } + + if (depth == valueDepth && valuePrefix == prefix) { + uint64_t token = protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | + DIMENSIONS_VALUE_TUPLE_VALUE); + protoOutput->write(FIELD_TYPE_INT32 | DIMENSIONS_VALUE_FIELD, fieldNum); + if (token != 0) { + protoOutput->end(token); + } + (*index)++; + } else if (valueDepth > depth && valuePrefix == prefix) { + // Writing the sub tree + uint64_t dimensionToken = protoOutput->start( + FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | DIMENSIONS_VALUE_TUPLE_VALUE); + protoOutput->write(FIELD_TYPE_INT32 | DIMENSIONS_VALUE_FIELD, fieldNum); + uint64_t tupleToken = + protoOutput->start(FIELD_TYPE_MESSAGE | DIMENSIONS_VALUE_VALUE_TUPLE); + writeDimensionPathToProtoHelper(fieldMatchers, index, valueDepth, + field.getPrefix(valueDepth), protoOutput); + protoOutput->end(tupleToken); + protoOutput->end(dimensionToken); + } else { + // Done with the prev sub tree + return; + } + } +} + +} // namespace + +void writeDimensionToProto(const HashableDimensionKey& dimension, std::set *str_set, + ProtoOutputStream* protoOutput) { + if (dimension.getValues().size() == 0) { + return; + } + protoOutput->write(FIELD_TYPE_INT32 | DIMENSIONS_VALUE_FIELD, + dimension.getValues()[0].mField.getTag()); + uint64_t topToken = protoOutput->start(FIELD_TYPE_MESSAGE | DIMENSIONS_VALUE_VALUE_TUPLE); + size_t index = 0; + writeDimensionToProtoHelper(dimension.getValues(), &index, 0, 0, str_set, protoOutput); + protoOutput->end(topToken); +} + +void writeDimensionLeafNodesToProto(const HashableDimensionKey& dimension, + const int dimensionLeafFieldId, + std::set *str_set, + ProtoOutputStream* protoOutput) { + if (dimension.getValues().size() == 0) { + return; + } + size_t index = 0; + writeDimensionLeafToProtoHelper(dimension.getValues(), dimensionLeafFieldId, + &index, 0, 0, str_set, protoOutput); +} + +void writeDimensionPathToProto(const std::vector& fieldMatchers, + ProtoOutputStream* protoOutput) { + if (fieldMatchers.size() == 0) { + return; + } + protoOutput->write(FIELD_TYPE_INT32 | DIMENSIONS_VALUE_FIELD, + fieldMatchers[0].mMatcher.getTag()); + uint64_t topToken = protoOutput->start(FIELD_TYPE_MESSAGE | DIMENSIONS_VALUE_VALUE_TUPLE); + size_t index = 0; + writeDimensionPathToProtoHelper(fieldMatchers, &index, 0, 0, protoOutput); + protoOutput->end(topToken); +} + +// Supported Atoms format +// XYZ_Atom { +// repeated SubMsg field_1 = 1; +// SubMsg2 field_2 = 2; +// int32/float/string/int63 field_3 = 3; +// } +// logd's msg format, doesn't allow us to distinguish between the 2 cases below +// Case (1): +// Atom { +// SubMsg { +// int i = 1; +// int j = 2; +// } +// repeated SubMsg +// } +// +// and case (2): +// Atom { +// SubMsg { +// repeated int i = 1; +// repeated int j = 2; +// } +// optional SubMsg = 1; +// } +// +// +void writeFieldValueTreeToStreamHelper(int tagId, const std::vector& dims, + size_t* index, int depth, int prefix, + ProtoOutputStream* protoOutput) { + size_t count = dims.size(); + while (*index < count) { + const auto& dim = dims[*index]; + const int valueDepth = dim.mField.getDepth(); + const int valuePrefix = dim.mField.getPrefix(depth); + const int fieldNum = dim.mField.getPosAtDepth(depth); + if (valueDepth > 2) { + ALOGE("Depth > 2 not supported"); + return; + } + + if (depth == valueDepth && valuePrefix == prefix) { + switch (dim.mValue.getType()) { + case INT: + protoOutput->write(FIELD_TYPE_INT32 | fieldNum, dim.mValue.int_value); + break; + case LONG: + protoOutput->write(FIELD_TYPE_INT64 | fieldNum, + (long long)dim.mValue.long_value); + break; + case FLOAT: + protoOutput->write(FIELD_TYPE_FLOAT | fieldNum, dim.mValue.float_value); + break; + case STRING: { + protoOutput->write(FIELD_TYPE_STRING | fieldNum, dim.mValue.str_value); + break; + } + case STORAGE: + protoOutput->write(FIELD_TYPE_MESSAGE | fieldNum, + (const char*)dim.mValue.storage_value.data(), + dim.mValue.storage_value.size()); + break; + default: + break; + } + (*index)++; + } else if (valueDepth > depth && valuePrefix == prefix) { + // Writing the sub tree + uint64_t msg_token = 0ULL; + if (valueDepth == depth + 2) { + msg_token = + protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | fieldNum); + } else if (valueDepth == depth + 1) { + msg_token = protoOutput->start(FIELD_TYPE_MESSAGE | fieldNum); + } + // Directly jump to the leaf value because the repeated position field is implied + // by the position of the sub msg in the parent field. + writeFieldValueTreeToStreamHelper(tagId, dims, index, valueDepth, + dim.mField.getPrefix(valueDepth), protoOutput); + if (msg_token != 0) { + protoOutput->end(msg_token); + } + } else { + // Done with the prev sub tree + return; + } + } +} + +void writeFieldValueTreeToStream(int tagId, const std::vector& values, + util::ProtoOutputStream* protoOutput) { + uint64_t atomToken = protoOutput->start(FIELD_TYPE_MESSAGE | tagId); + + size_t index = 0; + writeFieldValueTreeToStreamHelper(tagId, values, &index, 0, 0, protoOutput); + protoOutput->end(atomToken); +} + +void writeStateToProto(const FieldValue& state, util::ProtoOutputStream* protoOutput) { + protoOutput->write(FIELD_TYPE_INT32 | STATE_VALUE_ATOM_ID, state.mField.getTag()); + + switch (state.mValue.getType()) { + case INT: + protoOutput->write(FIELD_TYPE_INT32 | STATE_VALUE_CONTENTS_VALUE, + state.mValue.int_value); + break; + case LONG: + protoOutput->write(FIELD_TYPE_INT64 | STATE_VALUE_CONTENTS_GROUP_ID, + state.mValue.long_value); + break; + default: + break; + } +} + +int64_t TimeUnitToBucketSizeInMillisGuardrailed(int uid, TimeUnit unit) { + int64_t bucketSizeMillis = TimeUnitToBucketSizeInMillis(unit); + if (bucketSizeMillis > 1000 && bucketSizeMillis < 5 * 60 * 1000LL && uid != AID_SHELL && + uid != AID_ROOT) { + bucketSizeMillis = 5 * 60 * 1000LL; + } + return bucketSizeMillis; +} + +int64_t TimeUnitToBucketSizeInMillis(TimeUnit unit) { + switch (unit) { + case ONE_MINUTE: + return 60 * 1000LL; + case FIVE_MINUTES: + return 5 * 60 * 1000LL; + case TEN_MINUTES: + return 10 * 60 * 1000LL; + case THIRTY_MINUTES: + return 30 * 60 * 1000LL; + case ONE_HOUR: + return 60 * 60 * 1000LL; + case THREE_HOURS: + return 3 * 60 * 60 * 1000LL; + case SIX_HOURS: + return 6 * 60 * 60 * 1000LL; + case TWELVE_HOURS: + return 12 * 60 * 60 * 1000LL; + case ONE_DAY: + return 24 * 60 * 60 * 1000LL; + case ONE_WEEK: + return 7 * 24 * 60 * 60 * 1000LL; + case CTS: + return 1000; + case TIME_UNIT_UNSPECIFIED: + default: + return -1; + } +} + +void writeNonZeroStatToStream(const uint64_t fieldId, const int64_t value, + util::ProtoOutputStream* protoOutput) { + if (value != 0) { + protoOutput->write(fieldId, value); + } +} + +void writePullerStatsToStream(const std::pair& pair, + util::ProtoOutputStream* protoOutput) { + uint64_t token = protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_ID_PULLED_ATOM_STATS | + FIELD_COUNT_REPEATED); + protoOutput->write(FIELD_TYPE_INT32 | FIELD_ID_PULL_ATOM_ID, (int32_t)pair.first); + protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_TOTAL_PULL, (long long)pair.second.totalPull); + protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_TOTAL_PULL_FROM_CACHE, + (long long)pair.second.totalPullFromCache); + protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_MIN_PULL_INTERVAL_SEC, + (long long)pair.second.minPullIntervalSec); + protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_AVERAGE_PULL_TIME_NANOS, + (long long)pair.second.avgPullTimeNs); + protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_MAX_PULL_TIME_NANOS, + (long long)pair.second.maxPullTimeNs); + protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_AVERAGE_PULL_DELAY_NANOS, + (long long)pair.second.avgPullDelayNs); + protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_MAX_PULL_DELAY_NANOS, + (long long)pair.second.maxPullDelayNs); + protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_DATA_ERROR, (long long)pair.second.dataError); + protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_PULL_TIMEOUT, + (long long)pair.second.pullTimeout); + protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_PULL_EXCEED_MAX_DELAY, + (long long)pair.second.pullExceedMaxDelay); + protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_PULL_FAILED, (long long)pair.second.pullFailed); + protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_EMPTY_DATA, (long long)pair.second.emptyData); + protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_PULL_REGISTERED_COUNT, + (long long)pair.second.registeredCount); + protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_PULL_UNREGISTERED_COUNT, + (long long)pair.second.unregisteredCount); + protoOutput->write(FIELD_TYPE_INT32 | FIELD_ID_ATOM_ERROR_COUNT, pair.second.atomErrorCount); + protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_BINDER_CALL_FAIL_COUNT, + (long long)pair.second.binderCallFailCount); + protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_PULL_UID_PROVIDER_NOT_FOUND, + (long long)pair.second.pullUidProviderNotFound); + protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_PULLER_NOT_FOUND, + (long long)pair.second.pullerNotFound); + for (const auto& pullTimeoutMetadata : pair.second.pullTimeoutMetadata) { + uint64_t timeoutMetadataToken = protoOutput->start(FIELD_TYPE_MESSAGE | + FIELD_ID_PULL_TIMEOUT_METADATA | + FIELD_COUNT_REPEATED); + protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_PULL_TIMEOUT_METADATA_UPTIME_MILLIS, + pullTimeoutMetadata.pullTimeoutUptimeMillis); + protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_PULL_TIMEOUT_METADATA_ELAPSED_MILLIS, + pullTimeoutMetadata.pullTimeoutElapsedMillis); + protoOutput->end(timeoutMetadataToken); + } + protoOutput->end(token); +} + +void writeAtomMetricStatsToStream(const std::pair &pair, + util::ProtoOutputStream *protoOutput) { + uint64_t token = protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_ID_ATOM_METRIC_STATS | + FIELD_COUNT_REPEATED); + + writeNonZeroStatToStream(FIELD_TYPE_INT64 | FIELD_ID_METRIC_ID, (long long)pair.first, + protoOutput); + writeNonZeroStatToStream(FIELD_TYPE_INT64 | FIELD_ID_HARD_DIMENSION_LIMIT_REACHED, + (long long)pair.second.hardDimensionLimitReached, protoOutput); + writeNonZeroStatToStream(FIELD_TYPE_INT64 | FIELD_ID_LATE_LOG_EVENT_SKIPPED, + (long long)pair.second.lateLogEventSkipped, protoOutput); + writeNonZeroStatToStream(FIELD_TYPE_INT64 | FIELD_ID_SKIPPED_FORWARD_BUCKETS, + (long long)pair.second.skippedForwardBuckets, protoOutput); + writeNonZeroStatToStream(FIELD_TYPE_INT64 | FIELD_ID_BAD_VALUE_TYPE, + (long long)pair.second.badValueType, protoOutput); + writeNonZeroStatToStream(FIELD_TYPE_INT64 | FIELD_ID_CONDITION_CHANGE_IN_NEXT_BUCKET, + (long long)pair.second.conditionChangeInNextBucket, protoOutput); + writeNonZeroStatToStream(FIELD_TYPE_INT64 | FIELD_ID_INVALIDATED_BUCKET, + (long long)pair.second.invalidatedBucket, protoOutput); + writeNonZeroStatToStream(FIELD_TYPE_INT64 | FIELD_ID_BUCKET_DROPPED, + (long long)pair.second.bucketDropped, protoOutput); + writeNonZeroStatToStream(FIELD_TYPE_INT64 | FIELD_ID_MIN_BUCKET_BOUNDARY_DELAY_NS, + (long long)pair.second.minBucketBoundaryDelayNs, protoOutput); + writeNonZeroStatToStream(FIELD_TYPE_INT64 | FIELD_ID_MAX_BUCKET_BOUNDARY_DELAY_NS, + (long long)pair.second.maxBucketBoundaryDelayNs, protoOutput); + writeNonZeroStatToStream(FIELD_TYPE_INT64 | FIELD_ID_BUCKET_UNKNOWN_CONDITION, + (long long)pair.second.bucketUnknownCondition, protoOutput); + writeNonZeroStatToStream(FIELD_TYPE_INT64 | FIELD_ID_BUCKET_COUNT, + (long long)pair.second.bucketCount, protoOutput); + writeNonZeroStatToStream(FIELD_TYPE_INT64 | FIELD_ID_LATE_LOG_EVENT, + (long long)pair.second.lateLogEvent, protoOutput); + writeNonZeroStatToStream(FIELD_TYPE_INT64 | FIELD_ID_SUM_LATE_LOG_EVENT_EXTRA_DURATION_NS, + (long long)pair.second.sumLateLogEventExtraDurationNs, protoOutput); + writeNonZeroStatToStream(FIELD_TYPE_INT64 | FIELD_ID_MAX_LATE_LOG_EVENT_EXTRA_DURATION_NS, + (long long)pair.second.maxLateLogEventExtraDurationNs, protoOutput); + protoOutput->end(token); +} + +int64_t getElapsedRealtimeNs() { + return ::android::elapsedRealtimeNano(); +} + +int64_t getElapsedRealtimeSec() { + return ::android::elapsedRealtimeNano() / NS_PER_SEC; +} + +int64_t getElapsedRealtimeMillis() { + return ::android::elapsedRealtime(); +} + +int64_t getSystemUptimeMillis() { + return ::android::uptimeMillis(); +} + +int64_t getWallClockNs() { + return time(nullptr) * NS_PER_SEC; +} + +int64_t getWallClockSec() { + return time(nullptr); +} + +int64_t getWallClockMillis() { + return time(nullptr) * MS_PER_SEC; +} + +int64_t truncateTimestampIfNecessary(const LogEvent& event) { + if (event.shouldTruncateTimestamp() || + (event.GetTagId() >= StatsdStats::kTimestampTruncationStartTag && + event.GetTagId() <= StatsdStats::kTimestampTruncationEndTag)) { + return event.GetElapsedTimestampNs() / NS_PER_SEC / (5 * 60) * NS_PER_SEC * (5 * 60); + } else { + return event.GetElapsedTimestampNs(); + } +} + +int64_t NanoToMillis(const int64_t nano) { + return nano / 1000000; +} + +int64_t MillisToNano(const int64_t millis) { + return millis * 1000000; +} + +bool checkPermissionForIds(const char* permission, pid_t pid, uid_t uid) { + shared_ptr scs = getStatsCompanionService(); + if (scs == nullptr) { + return false; + } + + bool success; + ::ndk::ScopedAStatus status = scs->checkPermission(string(permission), pid, uid, &success); + if (!status.isOk()) { + return false; + } + + return success; +} + +} // namespace statsd +} // namespace os +} // namespace android diff --git a/statsd/src/stats_log_util.h b/statsd/src/stats_log_util.h new file mode 100644 index 00000000..8034e88f --- /dev/null +++ b/statsd/src/stats_log_util.h @@ -0,0 +1,121 @@ +/* + * 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. + */ + +#pragma once + +#include + +#include "FieldValue.h" +#include "HashableDimensionKey.h" +#include "src/statsd_config.pb.h" +#include "guardrail/StatsdStats.h" +#include "logd/LogEvent.h" + +using android::util::ProtoOutputStream; + +namespace android { +namespace os { +namespace statsd { + +void writeFieldValueTreeToStream(int tagId, const std::vector& values, + ProtoOutputStream* protoOutput); +void writeDimensionToProto(const HashableDimensionKey& dimension, std::set *str_set, + ProtoOutputStream* protoOutput); + +void writeDimensionLeafNodesToProto(const HashableDimensionKey& dimension, + const int dimensionLeafFieldId, + std::set *str_set, + ProtoOutputStream* protoOutput); + +void writeDimensionPathToProto(const std::vector& fieldMatchers, + ProtoOutputStream* protoOutput); + +void writeStateToProto(const FieldValue& state, ProtoOutputStream* protoOutput); + +// Convert the TimeUnit enum to the bucket size in millis with a guardrail on +// bucket size. +int64_t TimeUnitToBucketSizeInMillisGuardrailed(int uid, TimeUnit unit); + +// Convert the TimeUnit enum to the bucket size in millis. +int64_t TimeUnitToBucketSizeInMillis(TimeUnit unit); + +// Gets the elapsed timestamp in ns. +int64_t getElapsedRealtimeNs(); + +// Gets the elapsed timestamp in millis. +int64_t getElapsedRealtimeMillis(); + +// Gets the elapsed timestamp in seconds. +int64_t getElapsedRealtimeSec(); + +// Gets the system uptime in millis. +int64_t getSystemUptimeMillis(); + +// Gets the wall clock timestamp in ns. +int64_t getWallClockNs(); + +// Gets the wall clock timestamp in millis. +int64_t getWallClockMillis(); + +// Gets the wall clock timestamp in seconds. +int64_t getWallClockSec(); + +int64_t NanoToMillis(const int64_t nano); + +int64_t MillisToNano(const int64_t millis); + +// Helper function to write a stats field to ProtoOutputStream if it's a non-zero value. +void writeNonZeroStatToStream(const uint64_t fieldId, const int64_t value, + ProtoOutputStream* protoOutput); + +// Helper function to write PulledAtomStats to ProtoOutputStream +void writePullerStatsToStream(const std::pair& pair, + ProtoOutputStream* protoOutput); + +// Helper function to write AtomMetricStats to ProtoOutputStream +void writeAtomMetricStatsToStream(const std::pair &pair, + ProtoOutputStream *protoOutput); + +template +bool parseProtoOutputStream(ProtoOutputStream& protoOutput, T* message) { + std::string pbBytes; + sp reader = protoOutput.data(); + while (reader->readBuffer() != NULL) { + size_t toRead = reader->currentToRead(); + pbBytes.append(reinterpret_cast(reader->readBuffer()), toRead); + reader->move(toRead); + } + return message->ParseFromArray(pbBytes.c_str(), pbBytes.size()); +} + +// Checks the truncate timestamp annotation as well as the restricted range of 300,000 - 304,999. +// Returns the truncated timestamp to the nearest 5 minutes if needed. +int64_t truncateTimestampIfNecessary(const LogEvent& event); + +// Checks permission for given pid and uid. +bool checkPermissionForIds(const char* permission, pid_t pid, uid_t uid); + +inline bool isVendorPulledAtom(int atomId) { + return atomId >= StatsdStats::kVendorPulledAtomStartTag && atomId < StatsdStats::kMaxAtomTag; +} + +inline bool isPulledAtom(int atomId) { + return atomId >= StatsdStats::kPullAtomStartTag && atomId < StatsdStats::kVendorAtomStartTag; +} + +} // namespace statsd +} // namespace os +} // namespace android diff --git a/statsd/src/stats_util.h b/statsd/src/stats_util.h new file mode 100644 index 00000000..c60babcc --- /dev/null +++ b/statsd/src/stats_util.h @@ -0,0 +1,46 @@ +/* + * 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. + */ + +#pragma once + +#include "HashableDimensionKey.h" + +#include + +namespace android { +namespace os { +namespace statsd { + +// Possible update states for a component. PRESERVE means we should keep the existing one. +// REPLACE means we should create a new one because the existing one changed +// NEW means we should create a new one because one does not currently exist. +enum UpdateStatus { + UPDATE_UNKNOWN = 0, + UPDATE_PRESERVE = 1, + UPDATE_REPLACE = 2, + UPDATE_NEW = 3, +}; + +const HashableDimensionKey DEFAULT_DIMENSION_KEY = HashableDimensionKey(); +const MetricDimensionKey DEFAULT_METRIC_DIMENSION_KEY = MetricDimensionKey(); + +typedef std::map ConditionKey; + +typedef std::unordered_map DimToValMap; + +} // namespace statsd +} // namespace os +} // namespace android diff --git a/statsd/src/statscompanion_util.cpp b/statsd/src/statscompanion_util.cpp new file mode 100644 index 00000000..ce07ec0e --- /dev/null +++ b/statsd/src/statscompanion_util.cpp @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2018 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. + */ + +#define DEBUG false // STOPSHIP if true +#include "Log.h" + +#include "statscompanion_util.h" +#include +#include + +namespace android { +namespace os { +namespace statsd { + +shared_ptr getStatsCompanionService() { + ::ndk::SpAIBinder binder(AServiceManager_getService("statscompanion")); + return IStatsCompanionService::fromBinder(binder); +} + +} // namespace statsd +} // namespace os +} // namespace android diff --git a/statsd/src/statscompanion_util.h b/statsd/src/statscompanion_util.h new file mode 100644 index 00000000..e20c40bb --- /dev/null +++ b/statsd/src/statscompanion_util.h @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2018 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. + */ + +#pragma once + +#include + +using aidl::android::os::IStatsCompanionService; +using std::shared_ptr; + +namespace android { +namespace os { +namespace statsd { + +/** Fetches and returns the StatsCompanionService. */ +shared_ptr getStatsCompanionService(); + +} // namespace statsd +} // namespace os +} // namespace android diff --git a/statsd/src/statsd_config.proto b/statsd/src/statsd_config.proto new file mode 100644 index 00000000..53bcba29 --- /dev/null +++ b/statsd/src/statsd_config.proto @@ -0,0 +1,524 @@ +/* + * 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. + */ + +syntax = "proto2"; + +package android.os.statsd; + +option java_package = "com.android.internal.os"; +option java_outer_classname = "StatsdConfigProto"; + +enum Position { + POSITION_UNKNOWN = 0; + + FIRST = 1; + + LAST = 2; + + ANY = 3; + + ALL = 4; +} + +enum TimeUnit { + TIME_UNIT_UNSPECIFIED = 0; + ONE_MINUTE = 1; // WILL BE GUARDRAILED TO 5 MINS UNLESS UID = SHELL OR ROOT + FIVE_MINUTES = 2; + TEN_MINUTES = 3; + THIRTY_MINUTES = 4; + ONE_HOUR = 5; + THREE_HOURS = 6; + SIX_HOURS = 7; + TWELVE_HOURS = 8; + ONE_DAY = 9; + ONE_WEEK = 10; + CTS = 1000; +} + +message FieldMatcher { + optional int32 field = 1; + + optional Position position = 2; + + repeated FieldMatcher child = 3; +} + +message FieldValueMatcher { + optional int32 field = 1; + + optional Position position = 2; + + oneof value_matcher { + bool eq_bool = 3; + string eq_string = 4; + int64 eq_int = 5; + + int64 lt_int = 6; + int64 gt_int = 7; + float lt_float = 8; + float gt_float = 9; + + int64 lte_int = 10; + int64 gte_int = 11; + + MessageMatcher matches_tuple = 12; + + StringListMatcher eq_any_string = 13; + StringListMatcher neq_any_string = 14; + } +} + +message MessageMatcher { + repeated FieldValueMatcher field_value_matcher = 1; +} + +message StringListMatcher { + repeated string str_value = 1; +} + +enum LogicalOperation { + LOGICAL_OPERATION_UNSPECIFIED = 0; + AND = 1; + OR = 2; + NOT = 3; + NAND = 4; + NOR = 5; +} + +message SimpleAtomMatcher { + optional int32 atom_id = 1; + + repeated FieldValueMatcher field_value_matcher = 2; +} + +message AtomMatcher { + optional int64 id = 1; + + message Combination { + optional LogicalOperation operation = 1; + + repeated int64 matcher = 2; + } + oneof contents { + SimpleAtomMatcher simple_atom_matcher = 2; + Combination combination = 3; + } +} + +message SimplePredicate { + optional int64 start = 1; + + optional int64 stop = 2; + + optional bool count_nesting = 3 [default = true]; + + optional int64 stop_all = 4; + + enum InitialValue { + UNKNOWN = 0; + FALSE = 1; + } + optional InitialValue initial_value = 5 [default = UNKNOWN]; + + optional FieldMatcher dimensions = 6; +} + +message Predicate { + optional int64 id = 1; + + message Combination { + optional LogicalOperation operation = 1; + + repeated int64 predicate = 2; + } + + oneof contents { + SimplePredicate simple_predicate = 2; + Combination combination = 3; + } +} + +message StateMap { + message StateGroup { + optional int64 group_id = 1; + + repeated int32 value = 2; + } + + repeated StateGroup group = 1; +} + +message State { + optional int64 id = 1; + + optional int32 atom_id = 2; + + optional StateMap map = 3; +} + +message MetricConditionLink { + optional int64 condition = 1; + + optional FieldMatcher fields_in_what = 2; + + optional FieldMatcher fields_in_condition = 3; +} + +message MetricStateLink { + optional int32 state_atom_id = 1; + + optional FieldMatcher fields_in_what = 2; + + optional FieldMatcher fields_in_state = 3; +} + +message FieldFilter { + optional bool include_all = 1 [default = false]; + optional FieldMatcher fields = 2; +} + +message UploadThreshold { + oneof value_comparison { + int64 lt_int = 1; + int64 gt_int = 2; + float lt_float = 3; + float gt_float = 4; + int64 lte_int = 5; + int64 gte_int = 6; + } +} + +message EventMetric { + optional int64 id = 1; + + optional int64 what = 2; + + optional int64 condition = 3; + + repeated MetricConditionLink links = 4; + + reserved 100; + reserved 101; +} + +message CountMetric { + optional int64 id = 1; + + optional int64 what = 2; + + optional int64 condition = 3; + + optional FieldMatcher dimensions_in_what = 4; + + repeated int64 slice_by_state = 8; + + optional TimeUnit bucket = 5; + + repeated MetricConditionLink links = 6; + + repeated MetricStateLink state_link = 9; + + optional FieldMatcher dimensions_in_condition = 7 [deprecated = true]; + + reserved 100; + reserved 101; +} + +message DurationMetric { + optional int64 id = 1; + + optional int64 what = 2; + + optional int64 condition = 3; + + repeated int64 slice_by_state = 9; + + repeated MetricConditionLink links = 4; + + repeated MetricStateLink state_link = 10; + + enum AggregationType { + SUM = 1; + + MAX_SPARSE = 2; + } + optional AggregationType aggregation_type = 5 [default = SUM]; + + optional FieldMatcher dimensions_in_what = 6; + + optional TimeUnit bucket = 7; + + optional UploadThreshold threshold = 11; + + optional FieldMatcher dimensions_in_condition = 8 [deprecated = true]; + + reserved 100; + reserved 101; +} + +message GaugeMetric { + optional int64 id = 1; + + optional int64 what = 2; + + optional int64 trigger_event = 12; + + optional FieldFilter gauge_fields_filter = 3; + + optional int64 condition = 4; + + optional FieldMatcher dimensions_in_what = 5; + + optional FieldMatcher dimensions_in_condition = 8 [deprecated = true]; + + optional TimeUnit bucket = 6; + + repeated MetricConditionLink links = 7; + + enum SamplingType { + RANDOM_ONE_SAMPLE = 1; + ALL_CONDITION_CHANGES = 2 [deprecated = true]; + CONDITION_CHANGE_TO_TRUE = 3; + FIRST_N_SAMPLES = 4; + } + optional SamplingType sampling_type = 9 [default = RANDOM_ONE_SAMPLE] ; + + optional int64 min_bucket_size_nanos = 10; + + optional int64 max_num_gauge_atoms_per_bucket = 11 [default = 10]; + + optional int32 max_pull_delay_sec = 13 [default = 30]; + + optional bool split_bucket_for_app_upgrade = 14 [default = true]; + + reserved 100; + reserved 101; +} + +message ValueMetric { + optional int64 id = 1; + + optional int64 what = 2; + + optional FieldMatcher value_field = 3; + + optional int64 condition = 4; + + optional FieldMatcher dimensions_in_what = 5; + + repeated int64 slice_by_state = 18; + + optional TimeUnit bucket = 6; + + repeated MetricConditionLink links = 7; + + repeated MetricStateLink state_link = 19; + + enum AggregationType { + SUM = 1; + MIN = 2; + MAX = 3; + AVG = 4; + } + optional AggregationType aggregation_type = 8 [default = SUM]; + + optional int64 min_bucket_size_nanos = 10; + + optional bool use_absolute_value_on_reset = 11 [default = false]; + + optional bool use_diff = 12; + + optional bool use_zero_default_base = 15 [default = false]; + + enum ValueDirection { + UNKNOWN = 0; + INCREASING = 1; + DECREASING = 2; + ANY = 3; + } + optional ValueDirection value_direction = 13 [default = INCREASING]; + + optional bool skip_zero_diff_output = 14 [default = true]; + + optional int32 max_pull_delay_sec = 16 [default = 30]; + + optional bool split_bucket_for_app_upgrade = 17 [default = true]; + + optional FieldMatcher dimensions_in_condition = 9 [deprecated = true]; + + reserved 100; + reserved 101; +} + +message Alert { + optional int64 id = 1; + + optional int64 metric_id = 2; + + optional int32 num_buckets = 3; + + optional int32 refractory_period_secs = 4; + + optional double trigger_if_sum_gt = 5; +} + +message Alarm { + optional int64 id = 1; + + optional int64 offset_millis = 2; + + optional int64 period_millis = 3; +} + +message IncidentdDetails { + repeated int32 section = 1; + + enum Destination { + AUTOMATIC = 0; + EXPLICIT = 1; + } + optional Destination dest = 2; + + // Package name of the incident report receiver. + optional string receiver_pkg = 3; + + // Class name of the incident report receiver. + optional string receiver_cls = 4; + + optional string alert_description = 5; +} + +message PerfettoDetails { + // The |trace_config| field is a proto-encoded message of type + // perfetto.protos.TraceConfig defined in + // //external/perfetto/protos/perfetto/config/. On device, + // statsd doesn't need to deserialize the message as it's just + // passed binary-encoded to the perfetto cmdline client. + optional bytes trace_config = 1; +} + +message BroadcastSubscriberDetails { + optional int64 subscriber_id = 1; + repeated string cookie = 2; +} + +message Subscription { + optional int64 id = 1; + + enum RuleType { + RULE_TYPE_UNSPECIFIED = 0; + ALARM = 1; + ALERT = 2; + } + optional RuleType rule_type = 2; + + optional int64 rule_id = 3; + + oneof subscriber_information { + IncidentdDetails incidentd_details = 4; + PerfettoDetails perfetto_details = 5; + BroadcastSubscriberDetails broadcast_subscriber_details = 6; + } + + optional float probability_of_informing = 7 [default = 1.1]; + + // This was used for perfprofd historically. + reserved 8; +} + +enum ActivationType { + ACTIVATION_TYPE_UNKNOWN = 0; + ACTIVATE_IMMEDIATELY = 1; + ACTIVATE_ON_BOOT = 2; +} + +message EventActivation { + optional int64 atom_matcher_id = 1; + optional int64 ttl_seconds = 2; + optional int64 deactivation_atom_matcher_id = 3; + optional ActivationType activation_type = 4; +} + +message MetricActivation { + optional int64 metric_id = 1; + + optional ActivationType activation_type = 3 [deprecated = true]; + + repeated EventActivation event_activation = 2; +} + +message PullAtomPackages { + optional int32 atom_id = 1; + + repeated string packages = 2; +} + +message StatsdConfig { + optional int64 id = 1; + + repeated EventMetric event_metric = 2; + + repeated CountMetric count_metric = 3; + + repeated ValueMetric value_metric = 4; + + repeated GaugeMetric gauge_metric = 5; + + repeated DurationMetric duration_metric = 6; + + repeated AtomMatcher atom_matcher = 7; + + repeated Predicate predicate = 8; + + repeated Alert alert = 9; + + repeated Alarm alarm = 10; + + repeated Subscription subscription = 11; + + repeated string allowed_log_source = 12; + + repeated int64 no_report_metric = 13; + + message Annotation { + optional int64 field_int64 = 1; + optional int32 field_int32 = 2; + } + repeated Annotation annotation = 14; + + optional int64 ttl_in_seconds = 15; + + optional bool hash_strings_in_metric_report = 16 [default = true]; + + repeated MetricActivation metric_activation = 17; + + optional bool version_strings_in_metric_report = 18; + + optional bool installer_in_metric_report = 19; + + optional bool persist_locally = 20 [default = false]; + + repeated State state = 21; + + repeated string default_pull_packages = 22; + + repeated PullAtomPackages pull_atom_packages = 23; + + repeated int32 whitelisted_atom_ids = 24; + + // Field number 1000 is reserved for later use. + reserved 1000; +} diff --git a/statsd/src/statsd_metadata.proto b/statsd/src/statsd_metadata.proto new file mode 100644 index 00000000..200b392f --- /dev/null +++ b/statsd/src/statsd_metadata.proto @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2020 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. + */ + +syntax = "proto2"; + +package android.os.statsd.metadata; + +message ConfigKey { + optional int64 config_id = 1; + optional int32 uid = 2; +} + +message Field { + optional int32 tag = 1; + optional int32 field = 2; +} + +message FieldValue { + optional Field field = 1; + oneof value { + int32 value_int = 2; + int64 value_long = 3; + float value_float = 4; + double value_double = 5; + string value_str = 6; + bytes value_storage = 7; + } +} + +message MetricDimensionKey { + repeated FieldValue dimension_key_in_what = 1; + repeated FieldValue state_values_key = 2; +} + +message AlertDimensionKeyedData { + // The earliest time the alert can be fired again in wall clock time. + optional int32 last_refractory_ends_sec = 1; + optional MetricDimensionKey dimension_key = 2; +} + +message AlertMetadata { + optional int64 alert_id = 1; + repeated AlertDimensionKeyedData alert_dim_keyed_data = 2; +} + +// All metadata for a config in statsd +message StatsMetadata { + optional ConfigKey config_key = 1; + repeated AlertMetadata alert_metadata = 2; +} + +message StatsMetadataList { + repeated StatsMetadata stats_metadata = 1; +} \ No newline at end of file diff --git a/statsd/src/storage/StorageManager.cpp b/statsd/src/storage/StorageManager.cpp new file mode 100644 index 00000000..782b020b --- /dev/null +++ b/statsd/src/storage/StorageManager.cpp @@ -0,0 +1,780 @@ +/* + * 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. + */ + +#define DEBUG false // STOPSHIP if true +#include "Log.h" + +#include "android-base/stringprintf.h" +#include "guardrail/StatsdStats.h" +#include "storage/StorageManager.h" +#include "stats_log_util.h" + +#include +#include +#include + +namespace android { +namespace os { +namespace statsd { + +using android::util::FIELD_COUNT_REPEATED; +using android::util::FIELD_TYPE_MESSAGE; +using std::map; + +/** + * NOTE: these directories are protected by SELinux, any changes here must also update + * the SELinux policies. + */ +#define STATS_DATA_DIR "/data/misc/stats-data" +#define STATS_SERVICE_DIR "/data/misc/stats-service" +#define TRAIN_INFO_DIR "/data/misc/train-info" +#define TRAIN_INFO_PATH "/data/misc/train-info/train-info.bin" + +// Magic word at the start of the train info file, change this if changing the file format +const uint32_t TRAIN_INFO_FILE_MAGIC = 0xfb7447bf; + +// for ConfigMetricsReportList +const int FIELD_ID_REPORTS = 2; + +std::mutex StorageManager::sTrainInfoMutex; + +using android::base::StringPrintf; +using std::unique_ptr; + +struct FileName { + int64_t mTimestampSec; + int mUid; + int64_t mConfigId; + bool mIsHistory; + string getFullFileName(const char* path) { + return StringPrintf("%s/%lld_%d_%lld%s", path, (long long)mTimestampSec, (int)mUid, + (long long)mConfigId, (mIsHistory ? "_history" : "")); + }; +}; + +string StorageManager::getDataFileName(long wallClockSec, int uid, int64_t id) { + return StringPrintf("%s/%ld_%d_%lld", STATS_DATA_DIR, wallClockSec, uid, + (long long)id); +} + +string StorageManager::getDataHistoryFileName(long wallClockSec, int uid, int64_t id) { + return StringPrintf("%s/%ld_%d_%lld_history", STATS_DATA_DIR, wallClockSec, uid, + (long long)id); +} + +static string findTrainInfoFileNameLocked(const string& trainName) { + unique_ptr dir(opendir(TRAIN_INFO_DIR), closedir); + if (dir == NULL) { + VLOG("Path %s does not exist", TRAIN_INFO_DIR); + return ""; + } + dirent* de; + while ((de = readdir(dir.get()))) { + char* fileName = de->d_name; + if (fileName[0] == '.') continue; + + size_t fileNameLength = strlen(fileName); + if (fileNameLength >= trainName.length()) { + if (0 == strncmp(fileName + fileNameLength - trainName.length(), trainName.c_str(), + trainName.length())) { + return string(fileName); + } + } + } + + return ""; +} + +// Returns array of int64_t which contains timestamp in seconds, uid, +// configID and whether the file is a local history file. +static void parseFileName(char* name, FileName* output) { + int64_t result[3]; + int index = 0; + char* substr = strtok(name, "_"); + while (substr != nullptr && index < 3) { + result[index] = StrToInt64(substr); + index++; + substr = strtok(nullptr, "_"); + } + // When index ends before hitting 3, file name is corrupted. We + // intentionally put -1 at index 0 to indicate the error to caller. + // TODO(b/110563137): consider removing files with unexpected name format. + if (index < 3) { + result[0] = -1; + } + + output->mTimestampSec = result[0]; + output->mUid = result[1]; + output->mConfigId = result[2]; + // check if the file is a local history. + output->mIsHistory = (substr != nullptr && strcmp("history", substr) == 0); +} + +void StorageManager::writeFile(const char* file, const void* buffer, int numBytes) { + int fd = open(file, O_WRONLY | O_CREAT | O_CLOEXEC, S_IRUSR | S_IWUSR); + if (fd == -1) { + VLOG("Attempt to access %s but failed", file); + return; + } + trimToFit(STATS_SERVICE_DIR); + trimToFit(STATS_DATA_DIR); + + if (android::base::WriteFully(fd, buffer, numBytes)) { + VLOG("Successfully wrote %s", file); + } else { + ALOGE("Failed to write %s", file); + } + + int result = fchown(fd, AID_STATSD, AID_STATSD); + if (result) { + VLOG("Failed to chown %s to statsd", file); + } + + close(fd); +} + +bool StorageManager::writeTrainInfo(const InstallTrainInfo& trainInfo) { + std::lock_guard lock(sTrainInfoMutex); + + if (trainInfo.trainName.empty()) { + return false; + } + deleteSuffixedFiles(TRAIN_INFO_DIR, trainInfo.trainName.c_str()); + + std::string fileName = + StringPrintf("%s/%ld_%s", TRAIN_INFO_DIR, (long) getWallClockSec(), + trainInfo.trainName.c_str()); + + int fd = open(fileName.c_str(), O_WRONLY | O_CREAT | O_CLOEXEC, S_IRUSR | S_IWUSR); + if (fd == -1) { + VLOG("Attempt to access %s but failed", fileName.c_str()); + return false; + } + + size_t result; + // Write the magic word + result = write(fd, &TRAIN_INFO_FILE_MAGIC, sizeof(TRAIN_INFO_FILE_MAGIC)); + if (result != sizeof(TRAIN_INFO_FILE_MAGIC)) { + VLOG("Failed to wrtie train info magic"); + close(fd); + return false; + } + + // Write the train version + const size_t trainVersionCodeByteCount = sizeof(trainInfo.trainVersionCode); + result = write(fd, &trainInfo.trainVersionCode, trainVersionCodeByteCount); + if (result != trainVersionCodeByteCount) { + VLOG("Failed to wrtie train version code"); + close(fd); + return false; + } + + // Write # of bytes in trainName to file + const size_t trainNameSize = trainInfo.trainName.size(); + const size_t trainNameSizeByteCount = sizeof(trainNameSize); + result = write(fd, (uint8_t*)&trainNameSize, trainNameSizeByteCount); + if (result != trainNameSizeByteCount) { + VLOG("Failed to write train name size"); + close(fd); + return false; + } + + // Write trainName to file + result = write(fd, trainInfo.trainName.c_str(), trainNameSize); + if (result != trainNameSize) { + VLOG("Failed to write train name"); + close(fd); + return false; + } + + // Write status to file + const size_t statusByteCount = sizeof(trainInfo.status); + result = write(fd, (uint8_t*)&trainInfo.status, statusByteCount); + if (result != statusByteCount) { + VLOG("Failed to write status"); + close(fd); + return false; + } + + // Write experiment id count to file. + const size_t experimentIdsCount = trainInfo.experimentIds.size(); + const size_t experimentIdsCountByteCount = sizeof(experimentIdsCount); + result = write(fd, (uint8_t*) &experimentIdsCount, experimentIdsCountByteCount); + if (result != experimentIdsCountByteCount) { + VLOG("Failed to write experiment id count"); + close(fd); + return false; + } + + // Write experimentIds to file + for (size_t i = 0; i < experimentIdsCount; i++) { + const int64_t experimentId = trainInfo.experimentIds[i]; + const size_t experimentIdByteCount = sizeof(experimentId); + result = write(fd, &experimentId, experimentIdByteCount); + if (result == experimentIdByteCount) { + VLOG("Successfully wrote experiment IDs"); + } else { + VLOG("Failed to write experiment ids"); + close(fd); + return false; + } + } + + // Write bools to file + const size_t boolByteCount = sizeof(trainInfo.requiresStaging); + result = write(fd, (uint8_t*)&trainInfo.requiresStaging, boolByteCount); + if (result != boolByteCount) { + VLOG("Failed to write requires staging"); + close(fd); + return false; + } + + result = write(fd, (uint8_t*)&trainInfo.rollbackEnabled, boolByteCount); + if (result != boolByteCount) { + VLOG("Failed to write rollback enabled"); + close(fd); + return false; + } + + result = write(fd, (uint8_t*)&trainInfo.requiresLowLatencyMonitor, boolByteCount); + if (result != boolByteCount) { + VLOG("Failed to write requires log latency monitor"); + close(fd); + return false; + } + + close(fd); + return true; +} + +bool StorageManager::readTrainInfo(const std::string& trainName, InstallTrainInfo& trainInfo) { + std::lock_guard lock(sTrainInfoMutex); + return readTrainInfoLocked(trainName, trainInfo); +} + +bool StorageManager::readTrainInfoLocked(const std::string& trainName, InstallTrainInfo& trainInfo) { + trimToFit(TRAIN_INFO_DIR, /*parseTimestampOnly=*/ true); + string fileName = findTrainInfoFileNameLocked(trainName); + if (fileName.empty()) { + return false; + } + int fd = open(StringPrintf("%s/%s", TRAIN_INFO_DIR, fileName.c_str()).c_str(), O_RDONLY | O_CLOEXEC); + if (fd == -1) { + VLOG("Failed to open %s", fileName.c_str()); + return false; + } + + // Read the magic word + uint32_t magic; + size_t result = read(fd, &magic, sizeof(magic)); + if (result != sizeof(magic)) { + VLOG("Failed to read train info magic"); + close(fd); + return false; + } + + if (magic != TRAIN_INFO_FILE_MAGIC) { + VLOG("Train info magic was 0x%08x, expected 0x%08x", magic, TRAIN_INFO_FILE_MAGIC); + close(fd); + return false; + } + + // Read the train version code + const size_t trainVersionCodeByteCount(sizeof(trainInfo.trainVersionCode)); + result = read(fd, &trainInfo.trainVersionCode, trainVersionCodeByteCount); + if (result != trainVersionCodeByteCount) { + VLOG("Failed to read train version code from train info file"); + close(fd); + return false; + } + + // Read # of bytes taken by trainName in the file. + size_t trainNameSize; + result = read(fd, &trainNameSize, sizeof(size_t)); + if (result != sizeof(size_t)) { + VLOG("Failed to read train name size from train info file"); + close(fd); + return false; + } + + // Read trainName + trainInfo.trainName.resize(trainNameSize); + result = read(fd, trainInfo.trainName.data(), trainNameSize); + if (result != trainNameSize) { + VLOG("Failed to read train name from train info file"); + close(fd); + return false; + } + + // Read status + const size_t statusByteCount = sizeof(trainInfo.status); + result = read(fd, &trainInfo.status, statusByteCount); + if (result != statusByteCount) { + VLOG("Failed to read train status from train info file"); + close(fd); + return false; + } + + // Read experiment ids count. + size_t experimentIdsCount; + result = read(fd, &experimentIdsCount, sizeof(size_t)); + if (result != sizeof(size_t)) { + VLOG("Failed to read train experiment id count from train info file"); + close(fd); + return false; + } + + // Read experimentIds + for (size_t i = 0; i < experimentIdsCount; i++) { + int64_t experimentId; + result = read(fd, &experimentId, sizeof(experimentId)); + if (result != sizeof(experimentId)) { + VLOG("Failed to read train experiment id from train info file"); + close(fd); + return false; + } + trainInfo.experimentIds.push_back(experimentId); + } + + // Read bools + const size_t boolByteCount = sizeof(trainInfo.requiresStaging); + result = read(fd, &trainInfo.requiresStaging, boolByteCount); + if (result != boolByteCount) { + VLOG("Failed to read requires requires staging from train info file"); + close(fd); + return false; + } + + result = read(fd, &trainInfo.rollbackEnabled, boolByteCount); + if (result != boolByteCount) { + VLOG("Failed to read requires rollback enabled from train info file"); + close(fd); + return false; + } + + result = read(fd, &trainInfo.requiresLowLatencyMonitor, boolByteCount); + if (result != boolByteCount) { + VLOG("Failed to read requires requires low latency monitor from train info file"); + close(fd); + return false; + } + + // Expect to be at EOF. + char c; + result = read(fd, &c, 1); + if (result != 0) { + VLOG("Failed to read train info from file. Did not get expected EOF."); + close(fd); + return false; + } + + VLOG("Read train info file successful"); + close(fd); + return true; +} + +vector StorageManager::readAllTrainInfo() { + std::lock_guard lock(sTrainInfoMutex); + vector trainInfoList; + unique_ptr dir(opendir(TRAIN_INFO_DIR), closedir); + if (dir == NULL) { + VLOG("Directory does not exist: %s", TRAIN_INFO_DIR); + return trainInfoList; + } + + dirent* de; + while ((de = readdir(dir.get()))) { + char* name = de->d_name; + if (name[0] == '.') { + continue; + } + + InstallTrainInfo trainInfo; + bool readSuccess = StorageManager::readTrainInfoLocked(name, trainInfo); + if (!readSuccess) { + continue; + } + trainInfoList.push_back(trainInfo); + } + return trainInfoList; +} + +void StorageManager::deleteFile(const char* file) { + if (remove(file) != 0) { + VLOG("Attempt to delete %s but is not found", file); + } else { + VLOG("Successfully deleted %s", file); + } +} + +void StorageManager::deleteAllFiles(const char* path) { + unique_ptr dir(opendir(path), closedir); + if (dir == NULL) { + VLOG("Directory does not exist: %s", path); + return; + } + + dirent* de; + while ((de = readdir(dir.get()))) { + char* name = de->d_name; + if (name[0] == '.') continue; + deleteFile(StringPrintf("%s/%s", path, name).c_str()); + } +} + +void StorageManager::deleteSuffixedFiles(const char* path, const char* suffix) { + unique_ptr dir(opendir(path), closedir); + if (dir == NULL) { + VLOG("Directory does not exist: %s", path); + return; + } + + dirent* de; + while ((de = readdir(dir.get()))) { + char* name = de->d_name; + if (name[0] == '.') { + continue; + } + size_t nameLen = strlen(name); + size_t suffixLen = strlen(suffix); + if (suffixLen <= nameLen && strncmp(name + nameLen - suffixLen, suffix, suffixLen) == 0) { + deleteFile(StringPrintf("%s/%s", path, name).c_str()); + } + } +} + +void StorageManager::sendBroadcast(const char* path, + const std::function& sendBroadcast) { + unique_ptr dir(opendir(path), closedir); + if (dir == NULL) { + VLOG("no stats-data directory on disk"); + return; + } + + dirent* de; + while ((de = readdir(dir.get()))) { + char* name = de->d_name; + if (name[0] == '.') continue; + VLOG("file %s", name); + + FileName output; + parseFileName(name, &output); + if (output.mTimestampSec == -1 || output.mIsHistory) continue; + sendBroadcast(ConfigKey((int)output.mUid, output.mConfigId)); + } +} + +bool StorageManager::hasConfigMetricsReport(const ConfigKey& key) { + unique_ptr dir(opendir(STATS_DATA_DIR), closedir); + if (dir == NULL) { + VLOG("Path %s does not exist", STATS_DATA_DIR); + return false; + } + + string suffix = StringPrintf("%d_%lld", key.GetUid(), (long long)key.GetId()); + + dirent* de; + while ((de = readdir(dir.get()))) { + char* name = de->d_name; + if (name[0] == '.') continue; + + size_t nameLen = strlen(name); + size_t suffixLen = suffix.length(); + if (suffixLen <= nameLen && + strncmp(name + nameLen - suffixLen, suffix.c_str(), suffixLen) == 0) { + // Check again that the file name is parseable. + FileName output; + parseFileName(name, &output); + if (output.mTimestampSec == -1 || output.mIsHistory) continue; + return true; + } + } + return false; +} + +void StorageManager::appendConfigMetricsReport(const ConfigKey& key, ProtoOutputStream* proto, + bool erase_data, bool isAdb) { + unique_ptr dir(opendir(STATS_DATA_DIR), closedir); + if (dir == NULL) { + VLOG("Path %s does not exist", STATS_DATA_DIR); + return; + } + + dirent* de; + while ((de = readdir(dir.get()))) { + char* name = de->d_name; + string fileName(name); + if (name[0] == '.') continue; + FileName output; + parseFileName(name, &output); + + if (output.mTimestampSec == -1 || (output.mIsHistory && !isAdb) || + output.mUid != key.GetUid() || output.mConfigId != key.GetId()) { + continue; + } + + auto fullPathName = StringPrintf("%s/%s", STATS_DATA_DIR, fileName.c_str()); + int fd = open(fullPathName.c_str(), O_RDONLY | O_CLOEXEC); + if (fd != -1) { + string content; + if (android::base::ReadFdToString(fd, &content)) { + proto->write(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_REPORTS, + content.c_str(), content.size()); + } + close(fd); + } else { + ALOGE("file cannot be opened"); + } + + if (erase_data) { + remove(fullPathName.c_str()); + } else if (!output.mIsHistory && !isAdb) { + // This means a real data owner has called to get this data. But the config says it + // wants to keep a local history. So now this file must be renamed as a history file. + // So that next time, when owner calls getData() again, this data won't be uploaded + // again. rename returns 0 on success + if (rename(fullPathName.c_str(), (fullPathName + "_history").c_str())) { + ALOGE("Failed to rename file %s", fullPathName.c_str()); + } + } + } +} + +bool StorageManager::readFileToString(const char* file, string* content) { + int fd = open(file, O_RDONLY | O_CLOEXEC); + bool res = false; + if (fd != -1) { + if (android::base::ReadFdToString(fd, content)) { + res = true; + } else { + VLOG("Failed to read file %s\n", file); + } + close(fd); + } + return res; +} + +void StorageManager::readConfigFromDisk(map& configsMap) { + unique_ptr dir(opendir(STATS_SERVICE_DIR), closedir); + if (dir == NULL) { + VLOG("no default config on disk"); + return; + } + trimToFit(STATS_SERVICE_DIR); + + dirent* de; + while ((de = readdir(dir.get()))) { + char* name = de->d_name; + if (name[0] == '.') continue; + + FileName output; + parseFileName(name, &output); + if (output.mTimestampSec == -1) continue; + string file_name = output.getFullFileName(STATS_SERVICE_DIR); + int fd = open(file_name.c_str(), O_RDONLY | O_CLOEXEC); + if (fd != -1) { + string content; + if (android::base::ReadFdToString(fd, &content)) { + StatsdConfig config; + if (config.ParseFromString(content)) { + configsMap[ConfigKey(output.mUid, output.mConfigId)] = config; + VLOG("map key uid=%lld|configID=%lld", (long long)output.mUid, + (long long)output.mConfigId); + } + } + close(fd); + } + } +} + +bool StorageManager::readConfigFromDisk(const ConfigKey& key, StatsdConfig* config) { + string content; + return config != nullptr && + StorageManager::readConfigFromDisk(key, &content) && config->ParseFromString(content); +} + +bool StorageManager::readConfigFromDisk(const ConfigKey& key, string* content) { + unique_ptr dir(opendir(STATS_SERVICE_DIR), + closedir); + if (dir == NULL) { + VLOG("Directory does not exist: %s", STATS_SERVICE_DIR); + return false; + } + + string suffix = StringPrintf("%d_%lld", key.GetUid(), (long long)key.GetId()); + dirent* de; + while ((de = readdir(dir.get()))) { + char* name = de->d_name; + if (name[0] == '.') { + continue; + } + size_t nameLen = strlen(name); + size_t suffixLen = suffix.length(); + // There can be at most one file that matches this suffix (config key). + if (suffixLen <= nameLen && + strncmp(name + nameLen - suffixLen, suffix.c_str(), suffixLen) == 0) { + int fd = open(StringPrintf("%s/%s", STATS_SERVICE_DIR, name).c_str(), + O_RDONLY | O_CLOEXEC); + if (fd != -1) { + bool success = android::base::ReadFdToString(fd, content); + close(fd); + return success; + } + } + } + return false; +} + +bool StorageManager::hasIdenticalConfig(const ConfigKey& key, + const vector& config) { + string content; + if (StorageManager::readConfigFromDisk(key, &content)) { + vector vec(content.begin(), content.end()); + if (vec == config) { + return true; + } + } + return false; +} + +void StorageManager::sortFiles(vector* fileNames) { + // Reverse sort to effectively remove from the back (oldest entries). + // This will sort files in reverse-chronological order. Local history files have lower + // priority than regular data files. + sort(fileNames->begin(), fileNames->end(), [](FileInfo& lhs, FileInfo& rhs) { + // first consider if the file is a local history + if (lhs.mIsHistory && !rhs.mIsHistory) { + return false; + } else if (rhs.mIsHistory && !lhs.mIsHistory) { + return true; + } + + // then consider the age. + if (lhs.mFileAgeSec < rhs.mFileAgeSec) { + return true; + } else if (lhs.mFileAgeSec > rhs.mFileAgeSec) { + return false; + } + + // then good luck.... use string::compare + return lhs.mFileName.compare(rhs.mFileName) > 0; + }); +} + +void StorageManager::trimToFit(const char* path, bool parseTimestampOnly) { + unique_ptr dir(opendir(path), closedir); + if (dir == NULL) { + VLOG("Path %s does not exist", path); + return; + } + dirent* de; + int totalFileSize = 0; + vector fileNames; + auto nowSec = getWallClockSec(); + while ((de = readdir(dir.get()))) { + char* name = de->d_name; + if (name[0] == '.') continue; + + FileName output; + string file_name; + if (parseTimestampOnly) { + file_name = StringPrintf("%s/%s", path, name); + output.mTimestampSec = StrToInt64(strtok(name, "_")); + output.mIsHistory = false; + } else { + parseFileName(name, &output); + file_name = output.getFullFileName(path); + } + if (output.mTimestampSec == -1) continue; + + // Check for timestamp and delete if it's too old. + long fileAge = nowSec - output.mTimestampSec; + if (fileAge > StatsdStats::kMaxAgeSecond || + (output.mIsHistory && fileAge > StatsdStats::kMaxLocalHistoryAgeSecond)) { + deleteFile(file_name.c_str()); + continue; + } + + ifstream file(file_name.c_str(), ifstream::in | ifstream::binary); + int fileSize = 0; + if (file.is_open()) { + file.seekg(0, ios::end); + fileSize = file.tellg(); + file.close(); + totalFileSize += fileSize; + } + fileNames.emplace_back(file_name, output.mIsHistory, fileSize, fileAge); + } + + if (fileNames.size() > StatsdStats::kMaxFileNumber || + totalFileSize > StatsdStats::kMaxFileSize) { + sortFiles(&fileNames); + } + + // Start removing files from oldest to be under the limit. + while (fileNames.size() > 0 && (fileNames.size() > StatsdStats::kMaxFileNumber || + totalFileSize > StatsdStats::kMaxFileSize)) { + totalFileSize -= fileNames.at(fileNames.size() - 1).mFileSizeBytes; + deleteFile(fileNames.at(fileNames.size() - 1).mFileName.c_str()); + fileNames.pop_back(); + } +} + +void StorageManager::printStats(int outFd) { + printDirStats(outFd, STATS_SERVICE_DIR); + printDirStats(outFd, STATS_DATA_DIR); +} + +void StorageManager::printDirStats(int outFd, const char* path) { + dprintf(outFd, "Printing stats of %s\n", path); + unique_ptr dir(opendir(path), closedir); + if (dir == NULL) { + VLOG("Path %s does not exist", path); + return; + } + dirent* de; + int fileCount = 0; + int totalFileSize = 0; + while ((de = readdir(dir.get()))) { + char* name = de->d_name; + if (name[0] == '.') { + continue; + } + FileName output; + parseFileName(name, &output); + if (output.mTimestampSec == -1) continue; + dprintf(outFd, "\t #%d, Last updated: %lld, UID: %d, Config ID: %lld, %s", fileCount + 1, + (long long)output.mTimestampSec, output.mUid, (long long)output.mConfigId, + (output.mIsHistory ? "local history" : "")); + string file_name = output.getFullFileName(path); + ifstream file(file_name.c_str(), ifstream::in | ifstream::binary); + if (file.is_open()) { + file.seekg(0, ios::end); + int fileSize = file.tellg(); + file.close(); + dprintf(outFd, ", File Size: %d bytes", fileSize); + totalFileSize += fileSize; + } + dprintf(outFd, "\n"); + fileCount++; + } + dprintf(outFd, "\tTotal number of files: %d, Total size of files: %d bytes.\n", fileCount, + totalFileSize); +} + +} // namespace statsd +} // namespace os +} // namespace android diff --git a/statsd/src/storage/StorageManager.h b/statsd/src/storage/StorageManager.h new file mode 100644 index 00000000..d59046df --- /dev/null +++ b/statsd/src/storage/StorageManager.h @@ -0,0 +1,168 @@ +/* + * 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. + */ + +#ifndef STORAGE_MANAGER_H +#define STORAGE_MANAGER_H + +#include +#include +#include + +#include "packages/UidMap.h" + +namespace android { +namespace os { +namespace statsd { + +using android::util::ProtoOutputStream; + +class StorageManager : public virtual RefBase { +public: + struct FileInfo { + FileInfo(std::string name, bool isHistory, int fileSize, long fileAge) + : mFileName(name), + mIsHistory(isHistory), + mFileSizeBytes(fileSize), + mFileAgeSec(fileAge) { + } + std::string mFileName; + bool mIsHistory; + int mFileSizeBytes; + long mFileAgeSec; + }; + + /** + * Writes a given byte array as a file to the specified file path. + */ + static void writeFile(const char* file, const void* buffer, int numBytes); + + /** + * Writes train info. + */ + static bool writeTrainInfo(const InstallTrainInfo& trainInfo); + + /** + * Reads train info. + */ + static bool readTrainInfo(const std::string& trainName, InstallTrainInfo& trainInfo); + + /** + * Reads train info assuming lock is obtained. + */ + static bool readTrainInfoLocked(const std::string& trainName, InstallTrainInfo& trainInfo); + + /** + * Reads all train info and returns a vector of train info. + */ + static vector readAllTrainInfo(); + + /** + * Reads the file content to the buffer. + */ + static bool readFileToString(const char* file, string* content); + + /** + * Deletes a single file given a file name. + */ + static void deleteFile(const char* file); + + /** + * Deletes all files in a given directory. + */ + static void deleteAllFiles(const char* path); + + /** + * Deletes all files whose name matches with a provided suffix. + */ + static void deleteSuffixedFiles(const char* path, const char* suffix); + + /** + * Send broadcasts to relevant receiver for each data stored on disk. + */ + static void sendBroadcast(const char* path, + const std::function& sendBroadcast); + + /** + * Returns true if there's at least one report on disk. + */ + static bool hasConfigMetricsReport(const ConfigKey& key); + + /** + * Appends the ConfigMetricsReport found on disk to the specifid proto + * and, if erase_data, deletes it from disk. + * + * [isAdb]: if the caller is adb dump. This includes local adb dump or dumpsys by + * bugreport or incidentd. When true, we will append any local history data too. + * + * When + * erase_data=true, isAdb=true: append history data to output, remove all data after read + * erase_data=false, isAdb=true: append history data to output, keep data after read + * erase_data=true, isAdb=false: do not append history data, and remove data after read + * erase_data=false, isAdb=false: do not append history data and *rename* all data files to + * history files. + */ + static void appendConfigMetricsReport(const ConfigKey& key, ProtoOutputStream* proto, + bool erase_data, bool isAdb); + + /** + * Call to load the saved configs from disk. + */ + static void readConfigFromDisk(std::map& configsMap); + + /** + * Call to load the specified config from disk. Returns false if the config file does not + * exist or error occurs when reading the file. + */ + static bool readConfigFromDisk(const ConfigKey& key, StatsdConfig* config); + static bool readConfigFromDisk(const ConfigKey& key, string* config); + + /** + * Trims files in the provided directory to limit the total size, number of + * files, accumulation of outdated files. + */ + static void trimToFit(const char* dir, bool parseTimestampOnly = false); + + /** + * Returns true if there already exists identical configuration on device. + */ + static bool hasIdenticalConfig(const ConfigKey& key, + const vector& config); + + /** + * Prints disk usage statistics related to statsd. + */ + static void printStats(int out); + + static string getDataFileName(long wallClockSec, int uid, int64_t id); + + static string getDataHistoryFileName(long wallClockSec, int uid, int64_t id); + + static void sortFiles(vector* fileNames); + +private: + /** + * Prints disk usage statistics about a directory related to statsd. + */ + static void printDirStats(int out, const char* path); + + static std::mutex sTrainInfoMutex; +}; + +} // namespace statsd +} // namespace os +} // namespace android + +#endif // STORAGE_MANAGER_H diff --git a/statsd/src/subscriber/IncidentdReporter.cpp b/statsd/src/subscriber/IncidentdReporter.cpp new file mode 100644 index 00000000..1d77513d --- /dev/null +++ b/statsd/src/subscriber/IncidentdReporter.cpp @@ -0,0 +1,169 @@ +/* + * Copyright (C) 2018 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. + */ +#define DEBUG false +#include "Log.h" + +#include "FieldValue.h" +#include "IncidentdReporter.h" +#include "packages/UidMap.h" +#include "stats_log_util.h" + +#include +#include + +#include + +namespace android { +namespace os { +namespace statsd { + +using android::util::ProtoOutputStream; +using std::vector; + +using util::FIELD_TYPE_INT32; +using util::FIELD_TYPE_INT64; +using util::FIELD_TYPE_MESSAGE; +using util::FIELD_TYPE_STRING; + +// field ids in IncidentHeaderProto +const int FIELD_ID_ALERT_ID = 1; +const int FIELD_ID_REASON = 2; +const int FIELD_ID_CONFIG_KEY = 3; +const int FIELD_ID_CONFIG_KEY_UID = 1; +const int FIELD_ID_CONFIG_KEY_ID = 2; + +const int FIELD_ID_TRIGGER_DETAILS = 4; +const int FIELD_ID_TRIGGER_DETAILS_TRIGGER_METRIC = 1; +const int FIELD_ID_METRIC_VALUE_METRIC_ID = 1; +const int FIELD_ID_METRIC_VALUE_DIMENSION_IN_WHAT = 2; +const int FIELD_ID_METRIC_VALUE_VALUE = 4; + +const int FIELD_ID_PACKAGE_INFO = 3; + +namespace { +void getProtoData(const int64_t& rule_id, int64_t metricId, const MetricDimensionKey& dimensionKey, + int64_t metricValue, const ConfigKey& configKey, const string& reason, + vector* protoData) { + ProtoOutputStream headerProto; + headerProto.write(FIELD_TYPE_INT64 | FIELD_ID_ALERT_ID, (long long)rule_id); + headerProto.write(FIELD_TYPE_STRING | FIELD_ID_REASON, reason); + uint64_t token = + headerProto.start(FIELD_TYPE_MESSAGE | FIELD_ID_CONFIG_KEY); + headerProto.write(FIELD_TYPE_INT32 | FIELD_ID_CONFIG_KEY_UID, configKey.GetUid()); + headerProto.write(FIELD_TYPE_INT64 | FIELD_ID_CONFIG_KEY_ID, (long long)configKey.GetId()); + headerProto.end(token); + + token = headerProto.start(FIELD_TYPE_MESSAGE | FIELD_ID_TRIGGER_DETAILS); + + // MetricValue trigger_metric = 1; + uint64_t metricToken = + headerProto.start(FIELD_TYPE_MESSAGE | FIELD_ID_TRIGGER_DETAILS_TRIGGER_METRIC); + // message MetricValue { + // optional int64 metric_id = 1; + headerProto.write(FIELD_TYPE_INT64 | FIELD_ID_METRIC_VALUE_METRIC_ID, (long long)metricId); + // optional DimensionsValue dimension_in_what = 2; + uint64_t dimToken = + headerProto.start(FIELD_TYPE_MESSAGE | FIELD_ID_METRIC_VALUE_DIMENSION_IN_WHAT); + writeDimensionToProto(dimensionKey.getDimensionKeyInWhat(), nullptr, &headerProto); + headerProto.end(dimToken); + + // deprecated field + // optional DimensionsValue dimension_in_condition = 3; + + // optional int64 value = 4; + headerProto.write(FIELD_TYPE_INT64 | FIELD_ID_METRIC_VALUE_VALUE, (long long)metricValue); + + // } + headerProto.end(metricToken); + + // write relevant uid package info + std::set uids; + + for (const auto& dim : dimensionKey.getDimensionKeyInWhat().getValues()) { + int uid = getUidIfExists(dim); + // any uid <= 2000 are predefined AID_* + if (uid > 2000) { + uids.insert(uid); + } + } + + if (!uids.empty()) { + uint64_t token = headerProto.start(FIELD_TYPE_MESSAGE | FIELD_ID_PACKAGE_INFO); + UidMap::getInstance()->writeUidMapSnapshot(getElapsedRealtimeNs(), true, true, uids, + nullptr /*string set*/, &headerProto); + headerProto.end(token); + } + + headerProto.end(token); + + protoData->resize(headerProto.size()); + size_t pos = 0; + sp reader = headerProto.data(); + while (reader->readBuffer() != NULL) { + size_t toRead = reader->currentToRead(); + std::memcpy(&((*protoData)[pos]), reader->readBuffer(), toRead); + pos += toRead; + reader->move(toRead); + } +} +} // namespace + +bool GenerateIncidentReport(const IncidentdDetails& config, int64_t rule_id, int64_t metricId, + const MetricDimensionKey& dimensionKey, int64_t metricValue, + const ConfigKey& configKey) { + if (config.section_size() == 0) { + VLOG("The alert %lld contains zero section in config(%d,%lld)", (unsigned long long)rule_id, + configKey.GetUid(), (long long)configKey.GetId()); + return false; + } + + AIncidentReportArgs* args = AIncidentReportArgs_init(); + + vector protoData; + getProtoData(rule_id, metricId, dimensionKey, metricValue, configKey, + config.alert_description(), &protoData); + AIncidentReportArgs_addHeader(args, protoData.data(), protoData.size()); + + for (int i = 0; i < config.section_size(); i++) { + AIncidentReportArgs_addSection(args, config.section(i)); + } + + uint8_t dest; + switch (config.dest()) { + case IncidentdDetails_Destination_AUTOMATIC: + dest = INCIDENT_REPORT_PRIVACY_POLICY_AUTOMATIC; + break; + case IncidentdDetails_Destination_EXPLICIT: + dest = INCIDENT_REPORT_PRIVACY_POLICY_EXPLICIT; + break; + default: + dest = INCIDENT_REPORT_PRIVACY_POLICY_AUTOMATIC; + } + AIncidentReportArgs_setPrivacyPolicy(args, dest); + + AIncidentReportArgs_setReceiverPackage(args, config.receiver_pkg().c_str()); + + AIncidentReportArgs_setReceiverClass(args, config.receiver_cls().c_str()); + + int err = AIncidentReportArgs_takeReport(args); + AIncidentReportArgs_delete(args); + + return err == NO_ERROR; +} + +} // namespace statsd +} // namespace os +} // namespace android diff --git a/statsd/src/subscriber/IncidentdReporter.h b/statsd/src/subscriber/IncidentdReporter.h new file mode 100644 index 00000000..03d79fae --- /dev/null +++ b/statsd/src/subscriber/IncidentdReporter.h @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2018 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. + */ + +#pragma once + +#include "HashableDimensionKey.h" +#include "config/ConfigKey.h" +#include "src/statsd_config.pb.h" // Alert, IncidentdDetails + +namespace android { +namespace os { +namespace statsd { + +/** + * Calls incidentd to trigger an incident report and put in dropbox for uploading. + */ +bool GenerateIncidentReport(const IncidentdDetails& config, int64_t rule_id, int64_t metricId, + const MetricDimensionKey& dimensionKey, int64_t metricValue, + const ConfigKey& configKey); + +} // namespace statsd +} // namespace os +} // namespace android diff --git a/statsd/src/subscriber/SubscriberReporter.cpp b/statsd/src/subscriber/SubscriberReporter.cpp new file mode 100644 index 00000000..c6bdc3fe --- /dev/null +++ b/statsd/src/subscriber/SubscriberReporter.cpp @@ -0,0 +1,149 @@ +/* + * Copyright (C) 2018 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. + */ + +#define DEBUG false // STOPSHIP if true +#include "Log.h" + +#include "SubscriberReporter.h" + +using std::lock_guard; + +namespace android { +namespace os { +namespace statsd { + +using std::vector; + +void SubscriberReporter::broadcastSubscriberDied(void* rawPir) { + SubscriberReporter& thiz = getInstance(); + + // Erase the mapping from a (config_key, subscriberId) to a pir if the + // mapping exists. This requires iterating over the map, but this operation + // should be rare and the map is expected to be small. + lock_guard lock(thiz.mLock); + for (auto subscriberMapIt = thiz.mIntentMap.begin(); subscriberMapIt != thiz.mIntentMap.end(); + subscriberMapIt++) { + unordered_map>& subscriberMap = + subscriberMapIt->second; + for (auto pirIt = subscriberMap.begin(); pirIt != subscriberMap.end(); pirIt++) { + if (pirIt->second.get() == rawPir) { + subscriberMap.erase(pirIt); + if (subscriberMap.empty()) { + thiz.mIntentMap.erase(subscriberMapIt); + } + // pirIt and subscriberMapIt are now invalid. + return; + } + } + } +} + +SubscriberReporter::SubscriberReporter() : + mBroadcastSubscriberDeathRecipient(AIBinder_DeathRecipient_new(broadcastSubscriberDied)) { +} + +void SubscriberReporter::setBroadcastSubscriber(const ConfigKey& configKey, + int64_t subscriberId, + const shared_ptr& pir) { + VLOG("SubscriberReporter::setBroadcastSubscriber called with configKey %s and subscriberId " + "%lld.", + configKey.ToString().c_str(), (long long)subscriberId); + { + lock_guard lock(mLock); + mIntentMap[configKey][subscriberId] = pir; + } + // Pass the raw binder pointer address to be the cookie of the death recipient. While the death + // notification is fired, the cookie is used for identifying which binder was died. Because + // the NDK binder doesn't pass dead binder pointer to binder death handler, the binder death + // handler can't know who died. + // If a dedicated cookie is used to store metadata (config key, subscriber id) for direct + // lookup, a data structure is needed manage the cookies. + AIBinder_linkToDeath(pir->asBinder().get(), mBroadcastSubscriberDeathRecipient.get(), + pir.get()); +} + +void SubscriberReporter::unsetBroadcastSubscriber(const ConfigKey& configKey, + int64_t subscriberId) { + VLOG("SubscriberReporter::unsetBroadcastSubscriber called."); + lock_guard lock(mLock); + auto subscriberMapIt = mIntentMap.find(configKey); + if (subscriberMapIt != mIntentMap.end()) { + subscriberMapIt->second.erase(subscriberId); + if (subscriberMapIt->second.empty()) { + mIntentMap.erase(configKey); + } + } +} + +void SubscriberReporter::alertBroadcastSubscriber(const ConfigKey& configKey, + const Subscription& subscription, + 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) + // subscriber_id - name of the PendingIntent to use to send the broadcast + // config uid - the uid that uploaded the config (and therefore gave the PendingIntent, + // although the intent may be to broadcast to a different uid) + // config id - the name of this config (for this particular uid) + + VLOG("SubscriberReporter::alertBroadcastSubscriber called."); + lock_guard lock(mLock); + + if (!subscription.has_broadcast_subscriber_details() + || !subscription.broadcast_subscriber_details().has_subscriber_id()) { + ALOGE("Broadcast subscriber does not have an id."); + return; + } + int64_t subscriberId = subscription.broadcast_subscriber_details().subscriber_id(); + + vector cookies; + cookies.reserve(subscription.broadcast_subscriber_details().cookie_size()); + for (auto& cookie : subscription.broadcast_subscriber_details().cookie()) { + cookies.push_back(cookie); + } + + auto it1 = mIntentMap.find(configKey); + if (it1 == mIntentMap.end()) { + ALOGW("Cannot inform subscriber for missing config key %s ", configKey.ToString().c_str()); + return; + } + auto it2 = it1->second.find(subscriberId); + if (it2 == it1->second.end()) { + ALOGW("Cannot inform subscriber of config %s for missing subscriberId %lld ", + configKey.ToString().c_str(), (long long)subscriberId); + return; + } + sendBroadcastLocked(it2->second, configKey, subscription, cookies, dimKey); +} + +void SubscriberReporter::sendBroadcastLocked(const shared_ptr& pir, + const ConfigKey& configKey, + const Subscription& subscription, + const vector& cookies, + const MetricDimensionKey& dimKey) const { + VLOG("SubscriberReporter::sendBroadcastLocked called."); + pir->sendSubscriberBroadcast( + configKey.GetUid(), + configKey.GetId(), + subscription.id(), + subscription.rule_id(), + cookies, + dimKey.getDimensionKeyInWhat().toStatsDimensionsValueParcel()); +} + +} // namespace statsd +} // namespace os +} // namespace android diff --git a/statsd/src/subscriber/SubscriberReporter.h b/statsd/src/subscriber/SubscriberReporter.h new file mode 100644 index 00000000..8c829bd9 --- /dev/null +++ b/statsd/src/subscriber/SubscriberReporter.h @@ -0,0 +1,112 @@ +/* + * Copyright (C) 2018 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. + */ + +#pragma once +#include +#include +#include +#include + +#include +#include +#include + +#include "HashableDimensionKey.h" +#include "config/ConfigKey.h" +#include "src/statsd_config.pb.h" // subscription + +using aidl::android::os::IPendingIntentRef; +using std::mutex; +using std::shared_ptr; +using std::string; +using std::unordered_map; +using std::vector; + +namespace android { +namespace os { +namespace statsd { + +// Reports information to subscribers. +// Single instance shared across the process. All methods are thread safe. +class SubscriberReporter { +public: + /** Get (singleton) instance of SubscriberReporter. */ + static SubscriberReporter& getInstance() { + static SubscriberReporter subscriberReporter; + return subscriberReporter; + } + + ~SubscriberReporter(){}; + SubscriberReporter(SubscriberReporter const&) = delete; + void operator=(SubscriberReporter const&) = delete; + + /** + * Stores the given intentSender, associating it with the given (configKey, subscriberId) pair. + */ + void setBroadcastSubscriber(const ConfigKey& configKey, + int64_t subscriberId, + const shared_ptr& pir); + + /** + * Erases any intentSender information from the given (configKey, subscriberId) pair. + */ + void unsetBroadcastSubscriber(const ConfigKey& configKey, int64_t subscriberId); + + /** + * Sends a broadcast via the intentSender previously stored for the + * given (configKey, subscriberId) pair by setBroadcastSubscriber. + * Information about the subscriber, as well as information extracted from the dimKey, is sent. + */ + void alertBroadcastSubscriber(const ConfigKey& configKey, + const Subscription& subscription, + const MetricDimensionKey& dimKey) const; + +private: + SubscriberReporter(); + + mutable mutex mLock; + + /** Maps -> IPendingIntentRef (which represents a PendingIntent). */ + unordered_map>> mIntentMap; + + /** + * Sends a broadcast via the given intentSender (using mStatsCompanionService), along + * with the information in the other parameters. + */ + void sendBroadcastLocked(const shared_ptr& pir, + const ConfigKey& configKey, + const Subscription& subscription, + const vector& cookies, + const MetricDimensionKey& dimKey) const; + + ::ndk::ScopedAIBinder_DeathRecipient mBroadcastSubscriberDeathRecipient; + + /** + * Death recipient callback that is called when a broadcast subscriber dies. + * The cookie is a raw pointer to a PendingIntentReference. It is only used for identifying + * which binder has died and must not be dereferenced. + */ + static void broadcastSubscriberDied(void* cookie); + + friend class SubscriberReporterTest; + FRIEND_TEST(SubscriberReporterTest, TestBroadcastSubscriberDeathRemovesPir); + FRIEND_TEST(SubscriberReporterTest, TestBroadcastSubscriberDeathRemovesPirAndConfigKey); + FRIEND_TEST(SubscriberReporterTest, TestBroadcastSubscriberDeathKeepsReplacedPir); +}; + +} // namespace statsd +} // namespace os +} // namespace android diff --git a/statsd/src/uid_data.proto b/statsd/src/uid_data.proto new file mode 100644 index 00000000..a6fa26cd --- /dev/null +++ b/statsd/src/uid_data.proto @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2019 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. + */ + +syntax = "proto2"; + +package android.os.statsd; + +option java_package = "com.android.internal.os"; +option java_outer_classname = "UidDataProto"; + +message ApplicationInfo { + optional int32 uid = 1; + optional int64 version = 2; + optional string version_string = 3; + optional string package_name = 4; + optional string installer = 5; +} + +// StatsServiceCompanion uses the proto to supply statsd with uid-package +// mapping updates. +message UidData { + repeated ApplicationInfo app_info = 1; +} diff --git a/statsd/src/utils/MultiConditionTrigger.cpp b/statsd/src/utils/MultiConditionTrigger.cpp new file mode 100644 index 00000000..43a69337 --- /dev/null +++ b/statsd/src/utils/MultiConditionTrigger.cpp @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2020 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. + */ +#define DEBUG false // STOPSHIP if true + +#include "MultiConditionTrigger.h" + +#include + +using namespace std; + +namespace android { +namespace os { +namespace statsd { + +MultiConditionTrigger::MultiConditionTrigger(const set& conditionNames, + function trigger) + : mRemainingConditionNames(conditionNames), + mTrigger(trigger), + mCompleted(mRemainingConditionNames.empty()) { + if (mCompleted) { + thread executorThread([this] { mTrigger(); }); + executorThread.detach(); + } +} + +void MultiConditionTrigger::markComplete(const string& conditionName) { + bool doTrigger = false; + { + lock_guard lg(mMutex); + if (mCompleted) { + return; + } + mRemainingConditionNames.erase(conditionName); + mCompleted = mRemainingConditionNames.empty(); + doTrigger = mCompleted; + } + if (doTrigger) { + std::thread executorThread([this] { mTrigger(); }); + executorThread.detach(); + } +} +} // namespace statsd +} // namespace os +} // namespace android diff --git a/statsd/src/utils/MultiConditionTrigger.h b/statsd/src/utils/MultiConditionTrigger.h new file mode 100644 index 00000000..51f60299 --- /dev/null +++ b/statsd/src/utils/MultiConditionTrigger.h @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2020 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. + */ +#pragma once + +#include + +#include +#include + +namespace android { +namespace os { +namespace statsd { + +/** + * This class provides a utility to wait for a set of named conditions to occur. + * + * It will execute the trigger runnable in a detached thread once all conditions have been marked + * true. + */ +class MultiConditionTrigger { +public: + explicit MultiConditionTrigger(const std::set& conditionNames, + std::function trigger); + + MultiConditionTrigger(const MultiConditionTrigger&) = delete; + MultiConditionTrigger& operator=(const MultiConditionTrigger&) = delete; + + // Mark a specific condition as true. If this condition has called markComplete already or if + // the event was not specified in the constructor, the function is a no-op. + void markComplete(const std::string& eventName); + +private: + mutable std::mutex mMutex; + std::set mRemainingConditionNames; + std::function mTrigger; + bool mCompleted; + + FRIEND_TEST(MultiConditionTriggerTest, TestCountDownCalledBySameEventName); +}; +} // namespace statsd +} // namespace os +} // namespace android diff --git a/statsd/statsd_test.xml b/statsd/statsd_test.xml new file mode 100644 index 00000000..8f9bb1cb --- /dev/null +++ b/statsd/statsd_test.xml @@ -0,0 +1,37 @@ + + + + diff --git a/statsd/tests/AlarmMonitor_test.cpp b/statsd/tests/AlarmMonitor_test.cpp new file mode 100644 index 00000000..1dc9795d --- /dev/null +++ b/statsd/tests/AlarmMonitor_test.cpp @@ -0,0 +1,69 @@ +// 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 "anomaly/AlarmMonitor.h" + +#include + +using namespace android::os::statsd; +using std::shared_ptr; + +#ifdef __ANDROID__ +TEST(AlarmMonitor, popSoonerThan) { + std::string emptyMetricId; + std::string emptyDimensionId; + unordered_set, SpHash> set; + AlarmMonitor am(2, + [](const shared_ptr&, int64_t){}, + [](const shared_ptr&){}); + + set = am.popSoonerThan(5); + EXPECT_TRUE(set.empty()); + + sp a = new InternalAlarm{10}; + sp b = new InternalAlarm{20}; + sp c = new InternalAlarm{20}; + sp d = new InternalAlarm{30}; + sp e = new InternalAlarm{40}; + sp f = new InternalAlarm{50}; + + am.add(a); + am.add(b); + am.add(c); + am.add(d); + am.add(e); + am.add(f); + + set = am.popSoonerThan(5); + EXPECT_TRUE(set.empty()); + + set = am.popSoonerThan(30); + ASSERT_EQ(4u, set.size()); + EXPECT_EQ(1u, set.count(a)); + EXPECT_EQ(1u, set.count(b)); + EXPECT_EQ(1u, set.count(c)); + EXPECT_EQ(1u, set.count(d)); + + set = am.popSoonerThan(60); + ASSERT_EQ(2u, set.size()); + EXPECT_EQ(1u, set.count(e)); + EXPECT_EQ(1u, set.count(f)); + + set = am.popSoonerThan(80); + ASSERT_EQ(0u, set.size()); +} + +#else +GTEST_LOG_(INFO) << "This test does nothing.\n"; +#endif diff --git a/statsd/tests/ConfigManager_test.cpp b/statsd/tests/ConfigManager_test.cpp new file mode 100644 index 00000000..7b8f74d0 --- /dev/null +++ b/statsd/tests/ConfigManager_test.cpp @@ -0,0 +1,159 @@ +// 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 "src/config/ConfigManager.h" +#include "src/metrics/MetricsManager.h" +#include "statsd_test_util.h" + +#include +#include + +#include +#include + +using namespace android; +using namespace android::os::statsd; +using namespace testing; +using namespace std; + +namespace android { +namespace os { +namespace statsd { + +static ostream& operator<<(ostream& os, const StatsdConfig& config) { + return os << "StatsdConfig{id=" << config.id() << "}"; +} + +} // namespace statsd +} // namespace os +} // namespace android + +/** + * Mock ConfigListener + */ +class MockListener : public ConfigListener { +public: + MOCK_METHOD4(OnConfigUpdated, void(const int64_t timestampNs, const ConfigKey& key, + const StatsdConfig& config, bool modularUpdate)); + MOCK_METHOD1(OnConfigRemoved, void(const ConfigKey& key)); +}; + +/** + * Validate that the ConfigKey is the one we wanted. + */ +MATCHER_P2(ConfigKeyEq, uid, id, "") { + return arg.GetUid() == uid && (long long)arg.GetId() == (long long)id; +} + +/** + * Validate that the StatsdConfig is the one we wanted. + */ +MATCHER_P(StatsdConfigEq, id, 0) { + return (long long)arg.id() == (long long)id; +} + +const int64_t testConfigId = 12345; + +/** + * Test the addOrUpdate and remove methods + */ +TEST(ConfigManagerTest, TestAddUpdateRemove) { + sp listener = new StrictMock(); + + sp manager = new ConfigManager(); + manager->AddListener(listener); + + StatsdConfig config91; + config91.set_id(91); + StatsdConfig config92; + config92.set_id(92); + StatsdConfig config93; + config93.set_id(93); + StatsdConfig config94; + config94.set_id(94); + + { + InSequence s; + + manager->StartupForTest(); + + // Add another one + EXPECT_CALL(*(listener.get()), + OnConfigUpdated(_, ConfigKeyEq(1, StringToId("zzz")), StatsdConfigEq(91), true)) + .RetiresOnSaturation(); + manager->UpdateConfig(ConfigKey(1, StringToId("zzz")), config91); + + // Update It + EXPECT_CALL(*(listener.get()), + OnConfigUpdated(_, ConfigKeyEq(1, StringToId("zzz")), StatsdConfigEq(92), true)) + .RetiresOnSaturation(); + manager->UpdateConfig(ConfigKey(1, StringToId("zzz")), config92); + + // Add one with the same uid but a different name + EXPECT_CALL(*(listener.get()), + OnConfigUpdated(_, ConfigKeyEq(1, StringToId("yyy")), StatsdConfigEq(93), true)) + .RetiresOnSaturation(); + manager->UpdateConfig(ConfigKey(1, StringToId("yyy")), config93); + + // Add one with the same name but a different uid + EXPECT_CALL(*(listener.get()), + OnConfigUpdated(_, ConfigKeyEq(2, StringToId("zzz")), StatsdConfigEq(94), true)) + .RetiresOnSaturation(); + manager->UpdateConfig(ConfigKey(2, StringToId("zzz")), config94); + + // Remove (1,yyy) + EXPECT_CALL(*(listener.get()), OnConfigRemoved(ConfigKeyEq(1, StringToId("yyy")))) + .RetiresOnSaturation(); + manager->RemoveConfig(ConfigKey(1, StringToId("yyy"))); + + // Remove (2,zzz) + EXPECT_CALL(*(listener.get()), OnConfigRemoved(ConfigKeyEq(2, StringToId("zzz")))) + .RetiresOnSaturation(); + manager->RemoveConfig(ConfigKey(2, StringToId("zzz"))); + + // Remove (1,zzz) + EXPECT_CALL(*(listener.get()), OnConfigRemoved(ConfigKeyEq(1, StringToId("zzz")))) + .RetiresOnSaturation(); + manager->RemoveConfig(ConfigKey(1, StringToId("zzz"))); + + // Remove (2,zzz) again and we shouldn't get the callback + manager->RemoveConfig(ConfigKey(2, StringToId("zzz"))); + } +} + +/** + * Test removing all of the configs for a uid. + */ +TEST(ConfigManagerTest, TestRemoveUid) { + sp listener = new StrictMock(); + + sp manager = new ConfigManager(); + manager->AddListener(listener); + + StatsdConfig config; + + EXPECT_CALL(*(listener.get()), OnConfigUpdated(_, _, _, true)).Times(5); + EXPECT_CALL(*(listener.get()), OnConfigRemoved(ConfigKeyEq(2, StringToId("xxx")))); + EXPECT_CALL(*(listener.get()), OnConfigRemoved(ConfigKeyEq(2, StringToId("yyy")))); + EXPECT_CALL(*(listener.get()), OnConfigRemoved(ConfigKeyEq(2, StringToId("zzz")))); + + manager->StartupForTest(); + manager->UpdateConfig(ConfigKey(1, StringToId("aaa")), config); + manager->UpdateConfig(ConfigKey(2, StringToId("xxx")), config); + manager->UpdateConfig(ConfigKey(2, StringToId("yyy")), config); + manager->UpdateConfig(ConfigKey(2, StringToId("zzz")), config); + manager->UpdateConfig(ConfigKey(3, StringToId("bbb")), config); + + manager->RemoveConfigs(2); +} diff --git a/statsd/tests/FieldValue_test.cpp b/statsd/tests/FieldValue_test.cpp new file mode 100644 index 00000000..f4ac8e5e --- /dev/null +++ b/statsd/tests/FieldValue_test.cpp @@ -0,0 +1,653 @@ +/* + * 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/stats_log.pb.h" +#include "src/statsd_config.pb.h" +#include "matchers/matcher_util.h" +#include "src/logd/LogEvent.h" +#include "stats_event.h" +#include "stats_log_util.h" +#include "stats_util.h" +#include "subscriber/SubscriberReporter.h" +#include "tests/statsd_test_util.h" + +#ifdef __ANDROID__ + +using android::util::ProtoReader; + +namespace android { +namespace os { +namespace statsd { + +namespace { +void makeLogEvent(LogEvent* logEvent, const int32_t atomId, const int64_t timestamp, + const vector& attributionUids, const vector& attributionTags, + const string& name) { + AStatsEvent* statsEvent = AStatsEvent_obtain(); + AStatsEvent_setAtomId(statsEvent, atomId); + AStatsEvent_overwriteTimestamp(statsEvent, timestamp); + + writeAttribution(statsEvent, attributionUids, attributionTags); + AStatsEvent_writeString(statsEvent, name.c_str()); + + parseStatsEventToLogEvent(statsEvent, logEvent); +} + +void makeLogEvent(LogEvent* logEvent, const int32_t atomId, const int64_t timestamp, + const vector& attributionUids, const vector& attributionTags, + const int32_t value) { + AStatsEvent* statsEvent = AStatsEvent_obtain(); + AStatsEvent_setAtomId(statsEvent, atomId); + AStatsEvent_overwriteTimestamp(statsEvent, timestamp); + + writeAttribution(statsEvent, attributionUids, attributionTags); + AStatsEvent_writeInt32(statsEvent, value); + + parseStatsEventToLogEvent(statsEvent, logEvent); +} +} // anonymous namespace + +TEST(AtomMatcherTest, TestFieldTranslation) { + FieldMatcher matcher1; + matcher1.set_field(10); + FieldMatcher* child = matcher1.add_child(); + child->set_field(1); + child->set_position(Position::ANY); + + child = child->add_child(); + child->set_field(1); + + vector output; + translateFieldMatcher(matcher1, &output); + + ASSERT_EQ((size_t)1, output.size()); + + const auto& matcher12 = output[0]; + EXPECT_EQ((int32_t)10, matcher12.mMatcher.getTag()); + EXPECT_EQ((int32_t)0x02010001, matcher12.mMatcher.getField()); + EXPECT_EQ((int32_t)0xff7f007f, matcher12.mMask); +} + +TEST(AtomMatcherTest, TestFieldTranslation_ALL) { + FieldMatcher matcher1; + matcher1.set_field(10); + FieldMatcher* child = matcher1.add_child(); + child->set_field(1); + child->set_position(Position::ALL); + + child = child->add_child(); + child->set_field(1); + + vector output; + translateFieldMatcher(matcher1, &output); + + ASSERT_EQ((size_t)1, output.size()); + + const auto& matcher12 = output[0]; + EXPECT_EQ((int32_t)10, matcher12.mMatcher.getTag()); + EXPECT_EQ((int32_t)0x02010001, matcher12.mMatcher.getField()); + EXPECT_EQ((int32_t)0xff7f7f7f, matcher12.mMask); +} + +TEST(AtomMatcherTest, TestFilter_ALL) { + FieldMatcher matcher1; + matcher1.set_field(10); + FieldMatcher* child = matcher1.add_child(); + child->set_field(1); + child->set_position(Position::ALL); + + child->add_child()->set_field(1); + child->add_child()->set_field(2); + + child = matcher1.add_child(); + child->set_field(2); + + vector matchers; + translateFieldMatcher(matcher1, &matchers); + + std::vector attributionUids = {1111, 2222, 3333}; + std::vector attributionTags = {"location1", "location2", "location3"}; + + LogEvent event(/*uid=*/0, /*pid=*/0); + makeLogEvent(&event, 10 /*atomId*/, 1012345, attributionUids, attributionTags, "some value"); + HashableDimensionKey output; + + filterValues(matchers, event.getValues(), &output); + + ASSERT_EQ((size_t)7, output.getValues().size()); + EXPECT_EQ((int32_t)0x02010101, output.getValues()[0].mField.getField()); + EXPECT_EQ((int32_t)1111, output.getValues()[0].mValue.int_value); + EXPECT_EQ((int32_t)0x02010102, output.getValues()[1].mField.getField()); + EXPECT_EQ("location1", output.getValues()[1].mValue.str_value); + + EXPECT_EQ((int32_t)0x02010201, output.getValues()[2].mField.getField()); + EXPECT_EQ((int32_t)2222, output.getValues()[2].mValue.int_value); + EXPECT_EQ((int32_t)0x02010202, output.getValues()[3].mField.getField()); + EXPECT_EQ("location2", output.getValues()[3].mValue.str_value); + + EXPECT_EQ((int32_t)0x02010301, output.getValues()[4].mField.getField()); + EXPECT_EQ((int32_t)3333, output.getValues()[4].mValue.int_value); + EXPECT_EQ((int32_t)0x02010302, output.getValues()[5].mField.getField()); + EXPECT_EQ("location3", output.getValues()[5].mValue.str_value); + + EXPECT_EQ((int32_t)0x00020000, output.getValues()[6].mField.getField()); + EXPECT_EQ("some value", output.getValues()[6].mValue.str_value); +} + +TEST(AtomMatcherTest, TestSubDimension) { + HashableDimensionKey dim; + + int pos1[] = {1, 1, 1}; + int pos2[] = {1, 1, 2}; + int pos3[] = {1, 1, 3}; + int pos4[] = {2, 0, 0}; + Field field1(10, pos1, 2); + Field field2(10, pos2, 2); + + Field field3(10, pos3, 2); + Field field4(10, pos4, 0); + + Value value1((int32_t)10025); + Value value2("tag"); + + Value value11((int32_t)10026); + Value value22("tag2"); + + dim.addValue(FieldValue(field1, value1)); + dim.addValue(FieldValue(field2, value2)); + + HashableDimensionKey subDim1; + subDim1.addValue(FieldValue(field1, value1)); + + HashableDimensionKey subDim2; + subDim1.addValue(FieldValue(field2, value2)); + + EXPECT_TRUE(dim.contains(dim)); + EXPECT_TRUE(dim.contains(subDim1)); + EXPECT_TRUE(dim.contains(subDim2)); + + HashableDimensionKey subDim3; + subDim3.addValue(FieldValue(field1, value11)); + EXPECT_FALSE(dim.contains(subDim3)); + + HashableDimensionKey subDim4; + // Empty dimension is always a sub dimension of other dimensions + EXPECT_TRUE(dim.contains(subDim4)); +} + +TEST(AtomMatcherTest, TestMetric2ConditionLink) { + std::vector attributionUids = {1111, 2222, 3333}; + std::vector attributionTags = {"location1", "location2", "location3"}; + + LogEvent event(/*uid=*/0, /*pid=*/0); + makeLogEvent(&event, 10 /*atomId*/, 12345, attributionUids, attributionTags, "some value"); + + FieldMatcher whatMatcher; + whatMatcher.set_field(10); + FieldMatcher* child11 = whatMatcher.add_child(); + child11->set_field(1); + child11->set_position(Position::ANY); + child11 = child11->add_child(); + child11->set_field(1); + + FieldMatcher conditionMatcher; + conditionMatcher.set_field(27); + FieldMatcher* child2 = conditionMatcher.add_child(); + child2->set_field(2); + child2->set_position(Position::LAST); + + child2 = child2->add_child(); + child2->set_field(2); + + Metric2Condition link; + + translateFieldMatcher(whatMatcher, &link.metricFields); + translateFieldMatcher(conditionMatcher, &link.conditionFields); + + ASSERT_EQ((size_t)1, link.metricFields.size()); + EXPECT_EQ((int32_t)0x02010001, link.metricFields[0].mMatcher.getField()); + EXPECT_EQ((int32_t)0xff7f007f, link.metricFields[0].mMask); + EXPECT_EQ((int32_t)10, link.metricFields[0].mMatcher.getTag()); + + ASSERT_EQ((size_t)1, link.conditionFields.size()); + EXPECT_EQ((int32_t)0x02028002, link.conditionFields[0].mMatcher.getField()); + EXPECT_EQ((int32_t)0xff7f807f, link.conditionFields[0].mMask); + EXPECT_EQ((int32_t)27, link.conditionFields[0].mMatcher.getTag()); +} + +TEST(AtomMatcherTest, TestWriteDimensionPath) { + for (auto position : {Position::ANY, Position::ALL, Position::FIRST, Position::LAST}) { + FieldMatcher matcher1; + matcher1.set_field(10); + FieldMatcher* child = matcher1.add_child(); + child->set_field(2); + child->set_position(position); + child->add_child()->set_field(1); + child->add_child()->set_field(3); + + child = matcher1.add_child(); + child->set_field(4); + + child = matcher1.add_child(); + child->set_field(6); + child->add_child()->set_field(2); + + vector matchers; + translateFieldMatcher(matcher1, &matchers); + + android::util::ProtoOutputStream protoOut; + writeDimensionPathToProto(matchers, &protoOut); + + vector outData; + outData.resize(protoOut.size()); + size_t pos = 0; + sp reader = protoOut.data(); + while (reader->readBuffer() != NULL) { + size_t toRead = reader->currentToRead(); + std::memcpy(&(outData[pos]), reader->readBuffer(), toRead); + pos += toRead; + reader->move(toRead); + } + + DimensionsValue result; + ASSERT_EQ(true, result.ParseFromArray(&outData[0], outData.size())); + + EXPECT_EQ(10, result.field()); + EXPECT_EQ(DimensionsValue::ValueCase::kValueTuple, result.value_case()); + ASSERT_EQ(3, result.value_tuple().dimensions_value_size()); + + const auto& dim1 = result.value_tuple().dimensions_value(0); + EXPECT_EQ(2, dim1.field()); + ASSERT_EQ(2, dim1.value_tuple().dimensions_value_size()); + + const auto& dim11 = dim1.value_tuple().dimensions_value(0); + EXPECT_EQ(1, dim11.field()); + + const auto& dim12 = dim1.value_tuple().dimensions_value(1); + EXPECT_EQ(3, dim12.field()); + + const auto& dim2 = result.value_tuple().dimensions_value(1); + EXPECT_EQ(4, dim2.field()); + + const auto& dim3 = result.value_tuple().dimensions_value(2); + EXPECT_EQ(6, dim3.field()); + ASSERT_EQ(1, dim3.value_tuple().dimensions_value_size()); + const auto& dim31 = dim3.value_tuple().dimensions_value(0); + EXPECT_EQ(2, dim31.field()); + } +} + +void checkAttributionNodeInDimensionsValueParcel(StatsDimensionsValueParcel& attributionNodeParcel, + int32_t nodeDepthInAttributionChain, + int32_t uid, string tag) { + EXPECT_EQ(attributionNodeParcel.field, nodeDepthInAttributionChain /*position at depth 1*/); + ASSERT_EQ(attributionNodeParcel.valueType, STATS_DIMENSIONS_VALUE_TUPLE_TYPE); + ASSERT_EQ(attributionNodeParcel.tupleValue.size(), 2); + + StatsDimensionsValueParcel uidParcel = attributionNodeParcel.tupleValue[0]; + EXPECT_EQ(uidParcel.field, 1 /*position at depth 2*/); + EXPECT_EQ(uidParcel.valueType, STATS_DIMENSIONS_VALUE_INT_TYPE); + EXPECT_EQ(uidParcel.intValue, uid); + + StatsDimensionsValueParcel tagParcel = attributionNodeParcel.tupleValue[1]; + EXPECT_EQ(tagParcel.field, 2 /*position at depth 2*/); + EXPECT_EQ(tagParcel.valueType, STATS_DIMENSIONS_VALUE_STRING_TYPE); + EXPECT_EQ(tagParcel.stringValue, tag); +} + +// Test conversion of a HashableDimensionKey into a StatsDimensionValueParcel +TEST(AtomMatcherTest, TestSubscriberDimensionWrite) { + int atomId = 10; + // First four fields form an attribution chain + int pos1[] = {1, 1, 1}; + int pos2[] = {1, 1, 2}; + int pos3[] = {1, 2, 1}; + int pos4[] = {1, 2, 2}; + int pos5[] = {2, 1, 1}; + + Field field1(atomId, pos1, /*depth=*/2); + Field field2(atomId, pos2, /*depth=*/2); + Field field3(atomId, pos3, /*depth=*/2); + Field field4(atomId, pos4, /*depth=*/2); + Field field5(atomId, pos5, /*depth=*/0); + + Value value1((int32_t)1); + Value value2("string2"); + Value value3((int32_t)3); + Value value4("string4"); + Value value5((float)5.0); + + HashableDimensionKey dimensionKey; + dimensionKey.addValue(FieldValue(field1, value1)); + dimensionKey.addValue(FieldValue(field2, value2)); + dimensionKey.addValue(FieldValue(field3, value3)); + dimensionKey.addValue(FieldValue(field4, value4)); + dimensionKey.addValue(FieldValue(field5, value5)); + + StatsDimensionsValueParcel rootParcel = dimensionKey.toStatsDimensionsValueParcel(); + EXPECT_EQ(rootParcel.field, atomId); + ASSERT_EQ(rootParcel.valueType, STATS_DIMENSIONS_VALUE_TUPLE_TYPE); + ASSERT_EQ(rootParcel.tupleValue.size(), 2); + + // Check that attribution chain is populated correctly + StatsDimensionsValueParcel attributionChainParcel = rootParcel.tupleValue[0]; + EXPECT_EQ(attributionChainParcel.field, 1 /*position at depth 0*/); + ASSERT_EQ(attributionChainParcel.valueType, STATS_DIMENSIONS_VALUE_TUPLE_TYPE); + ASSERT_EQ(attributionChainParcel.tupleValue.size(), 2); + checkAttributionNodeInDimensionsValueParcel(attributionChainParcel.tupleValue[0], + /*nodeDepthInAttributionChain=*/1, + value1.int_value, value2.str_value); + checkAttributionNodeInDimensionsValueParcel(attributionChainParcel.tupleValue[1], + /*nodeDepthInAttributionChain=*/2, + value3.int_value, value4.str_value); + + // Check that the float is populated correctly + StatsDimensionsValueParcel floatParcel = rootParcel.tupleValue[1]; + EXPECT_EQ(floatParcel.field, 2 /*position at depth 0*/); + EXPECT_EQ(floatParcel.valueType, STATS_DIMENSIONS_VALUE_FLOAT_TYPE); + EXPECT_EQ(floatParcel.floatValue, value5.float_value); +} + +TEST(AtomMatcherTest, TestWriteDimensionToProto) { + HashableDimensionKey dim; + int pos1[] = {1, 1, 1}; + int pos2[] = {1, 1, 2}; + int pos3[] = {1, 1, 3}; + int pos4[] = {2, 0, 0}; + Field field1(10, pos1, 2); + Field field2(10, pos2, 2); + Field field3(10, pos3, 2); + Field field4(10, pos4, 0); + + Value value1((int32_t)10025); + Value value2("tag"); + Value value3((int32_t)987654); + Value value4((int32_t)99999); + + dim.addValue(FieldValue(field1, value1)); + dim.addValue(FieldValue(field2, value2)); + dim.addValue(FieldValue(field3, value3)); + dim.addValue(FieldValue(field4, value4)); + + android::util::ProtoOutputStream protoOut; + writeDimensionToProto(dim, nullptr /* include strings */, &protoOut); + + vector outData; + outData.resize(protoOut.size()); + size_t pos = 0; + sp reader = protoOut.data(); + while (reader->readBuffer() != NULL) { + size_t toRead = reader->currentToRead(); + std::memcpy(&(outData[pos]), reader->readBuffer(), toRead); + pos += toRead; + reader->move(toRead); + } + + DimensionsValue result; + ASSERT_EQ(true, result.ParseFromArray(&outData[0], outData.size())); + EXPECT_EQ(10, result.field()); + EXPECT_EQ(DimensionsValue::ValueCase::kValueTuple, result.value_case()); + ASSERT_EQ(2, result.value_tuple().dimensions_value_size()); + + const auto& dim1 = result.value_tuple().dimensions_value(0); + EXPECT_EQ(DimensionsValue::ValueCase::kValueTuple, dim1.value_case()); + ASSERT_EQ(3, dim1.value_tuple().dimensions_value_size()); + + const auto& dim11 = dim1.value_tuple().dimensions_value(0); + EXPECT_EQ(DimensionsValue::ValueCase::kValueInt, dim11.value_case()); + EXPECT_EQ(10025, dim11.value_int()); + + const auto& dim12 = dim1.value_tuple().dimensions_value(1); + EXPECT_EQ(DimensionsValue::ValueCase::kValueStr, dim12.value_case()); + EXPECT_EQ("tag", dim12.value_str()); + + const auto& dim13 = dim1.value_tuple().dimensions_value(2); + EXPECT_EQ(DimensionsValue::ValueCase::kValueInt, dim13.value_case()); + EXPECT_EQ(987654, dim13.value_int()); + + const auto& dim2 = result.value_tuple().dimensions_value(1); + EXPECT_EQ(DimensionsValue::ValueCase::kValueInt, dim2.value_case()); + EXPECT_EQ(99999, dim2.value_int()); +} + +TEST(AtomMatcherTest, TestWriteDimensionLeafNodesToProto) { + HashableDimensionKey dim; + int pos1[] = {1, 1, 1}; + int pos2[] = {1, 1, 2}; + int pos3[] = {1, 1, 3}; + int pos4[] = {2, 0, 0}; + Field field1(10, pos1, 2); + Field field2(10, pos2, 2); + Field field3(10, pos3, 2); + Field field4(10, pos4, 0); + + Value value1((int32_t)10025); + Value value2("tag"); + Value value3((int32_t)987654); + Value value4((int64_t)99999); + + dim.addValue(FieldValue(field1, value1)); + dim.addValue(FieldValue(field2, value2)); + dim.addValue(FieldValue(field3, value3)); + dim.addValue(FieldValue(field4, value4)); + + android::util::ProtoOutputStream protoOut; + writeDimensionLeafNodesToProto(dim, 1, nullptr /* include strings */, &protoOut); + + vector outData; + outData.resize(protoOut.size()); + size_t pos = 0; + sp reader = protoOut.data(); + while (reader->readBuffer() != NULL) { + size_t toRead = reader->currentToRead(); + std::memcpy(&(outData[pos]), reader->readBuffer(), toRead); + pos += toRead; + reader->move(toRead); + } + + DimensionsValueTuple result; + ASSERT_EQ(true, result.ParseFromArray(&outData[0], outData.size())); + ASSERT_EQ(4, result.dimensions_value_size()); + + const auto& dim1 = result.dimensions_value(0); + EXPECT_EQ(DimensionsValue::ValueCase::kValueInt, dim1.value_case()); + EXPECT_EQ(10025, dim1.value_int()); + + const auto& dim2 = result.dimensions_value(1); + EXPECT_EQ(DimensionsValue::ValueCase::kValueStr, dim2.value_case()); + EXPECT_EQ("tag", dim2.value_str()); + + const auto& dim3 = result.dimensions_value(2); + EXPECT_EQ(DimensionsValue::ValueCase::kValueInt, dim3.value_case()); + EXPECT_EQ(987654, dim3.value_int()); + + const auto& dim4 = result.dimensions_value(3); + EXPECT_EQ(DimensionsValue::ValueCase::kValueLong, dim4.value_case()); + EXPECT_EQ(99999, dim4.value_long()); +} + +TEST(AtomMatcherTest, TestWriteAtomToProto) { + std::vector attributionUids = {1111, 2222}; + std::vector attributionTags = {"location1", "location2"}; + + LogEvent event(/*uid=*/0, /*pid=*/0); + makeLogEvent(&event, 4 /*atomId*/, 12345, attributionUids, attributionTags, 999); + + android::util::ProtoOutputStream protoOutput; + writeFieldValueTreeToStream(event.GetTagId(), event.getValues(), &protoOutput); + + vector outData; + outData.resize(protoOutput.size()); + size_t pos = 0; + sp reader = protoOutput.data(); + while (reader->readBuffer() != NULL) { + size_t toRead = reader->currentToRead(); + std::memcpy(&(outData[pos]), reader->readBuffer(), toRead); + pos += toRead; + reader->move(toRead); + } + + Atom result; + ASSERT_EQ(true, result.ParseFromArray(&outData[0], outData.size())); + EXPECT_EQ(Atom::PushedCase::kBleScanResultReceived, result.pushed_case()); + const auto& atom = result.ble_scan_result_received(); + ASSERT_EQ(2, atom.attribution_node_size()); + EXPECT_EQ(1111, atom.attribution_node(0).uid()); + EXPECT_EQ("location1", atom.attribution_node(0).tag()); + EXPECT_EQ(2222, atom.attribution_node(1).uid()); + EXPECT_EQ("location2", atom.attribution_node(1).tag()); + EXPECT_EQ(999, atom.num_results()); +} + +/* + * Test two Matchers is not a subset of one Matcher. + * Test one Matcher is subset of two Matchers. + */ +TEST(AtomMatcherTest, TestSubsetDimensions1) { + // Initialize first set of matchers + FieldMatcher matcher1; + matcher1.set_field(10); + + FieldMatcher* child = matcher1.add_child(); + child->set_field(1); + child->set_position(Position::ALL); + child->add_child()->set_field(1); + child->add_child()->set_field(2); + + vector matchers1; + translateFieldMatcher(matcher1, &matchers1); + ASSERT_EQ(2, matchers1.size()); + + // Initialize second set of matchers + FieldMatcher matcher2; + matcher2.set_field(10); + + child = matcher2.add_child(); + child->set_field(1); + child->set_position(Position::ALL); + child->add_child()->set_field(1); + + vector matchers2; + translateFieldMatcher(matcher2, &matchers2); + ASSERT_EQ(1, matchers2.size()); + + EXPECT_FALSE(subsetDimensions(matchers1, matchers2)); + EXPECT_TRUE(subsetDimensions(matchers2, matchers1)); +} +/* + * Test not a subset with one matching Matcher, one non-matching Matcher. + */ +TEST(AtomMatcherTest, TestSubsetDimensions2) { + // Initialize first set of matchers + FieldMatcher matcher1; + matcher1.set_field(10); + + FieldMatcher* child = matcher1.add_child(); + child->set_field(1); + + child = matcher1.add_child(); + child->set_field(2); + + vector matchers1; + translateFieldMatcher(matcher1, &matchers1); + + // Initialize second set of matchers + FieldMatcher matcher2; + matcher2.set_field(10); + + child = matcher2.add_child(); + child->set_field(1); + + child = matcher2.add_child(); + child->set_field(3); + + vector matchers2; + translateFieldMatcher(matcher2, &matchers2); + + EXPECT_FALSE(subsetDimensions(matchers1, matchers2)); +} + +/* + * Test not a subset if parent field is not equal. + */ +TEST(AtomMatcherTest, TestSubsetDimensions3) { + // Initialize first set of matchers + FieldMatcher matcher1; + matcher1.set_field(10); + + FieldMatcher* child = matcher1.add_child(); + child->set_field(1); + + vector matchers1; + translateFieldMatcher(matcher1, &matchers1); + + // Initialize second set of matchers + FieldMatcher matcher2; + matcher2.set_field(5); + + child = matcher2.add_child(); + child->set_field(1); + + vector matchers2; + translateFieldMatcher(matcher2, &matchers2); + + EXPECT_FALSE(subsetDimensions(matchers1, matchers2)); +} + +/* + * Test is subset with two matching Matchers. + */ +TEST(AtomMatcherTest, TestSubsetDimensions4) { + // Initialize first set of matchers + FieldMatcher matcher1; + matcher1.set_field(10); + + FieldMatcher* child = matcher1.add_child(); + child->set_field(1); + + child = matcher1.add_child(); + child->set_field(2); + + vector matchers1; + translateFieldMatcher(matcher1, &matchers1); + + // Initialize second set of matchers + FieldMatcher matcher2; + matcher2.set_field(10); + + child = matcher2.add_child(); + child->set_field(1); + + child = matcher2.add_child(); + child->set_field(2); + + child = matcher2.add_child(); + child->set_field(3); + + vector matchers2; + translateFieldMatcher(matcher2, &matchers2); + + EXPECT_TRUE(subsetDimensions(matchers1, matchers2)); + EXPECT_FALSE(subsetDimensions(matchers2, matchers1)); +} + +} // namespace statsd +} // namespace os +} // namespace android +#else +GTEST_LOG_(INFO) << "This test does nothing.\n"; +#endif diff --git a/statsd/tests/HashableDimensionKey_test.cpp b/statsd/tests/HashableDimensionKey_test.cpp new file mode 100644 index 00000000..8fe555e7 --- /dev/null +++ b/statsd/tests/HashableDimensionKey_test.cpp @@ -0,0 +1,137 @@ +/* + * Copyright (C) 2020 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 "src/HashableDimensionKey.h" + +#include + +#include "src/statsd_config.pb.h" +#include "statsd_test_util.h" + +#ifdef __ANDROID__ + +using android::util::ProtoReader; + +namespace android { +namespace os { +namespace statsd { + +/** + * Test that #containsLinkedStateValues returns false when the whatKey is + * smaller than the primaryKey. + */ +TEST(HashableDimensionKeyTest, TestContainsLinkedStateValues_WhatKeyTooSmall) { + std::vector mMetric2StateLinks; + + int32_t uid1 = 1000; + HashableDimensionKey whatKey = DEFAULT_DIMENSION_KEY; + HashableDimensionKey primaryKey; + getUidProcessKey(uid1, &primaryKey); + + EXPECT_FALSE(containsLinkedStateValues(whatKey, primaryKey, mMetric2StateLinks, + UID_PROCESS_STATE_ATOM_ID)); +} + +/** + * Test that #containsLinkedStateValues returns false when the linked values + * are not equal. + */ +TEST(HashableDimensionKeyTest, TestContainsLinkedStateValues_UnequalLinkedValues) { + int stateAtomId = UID_PROCESS_STATE_ATOM_ID; + + FieldMatcher whatMatcher; + whatMatcher.set_field(util::OVERLAY_STATE_CHANGED); + FieldMatcher* child11 = whatMatcher.add_child(); + child11->set_field(1); + + FieldMatcher stateMatcher; + stateMatcher.set_field(stateAtomId); + FieldMatcher* child21 = stateMatcher.add_child(); + child21->set_field(1); + + std::vector mMetric2StateLinks; + Metric2State ms; + ms.stateAtomId = stateAtomId; + translateFieldMatcher(whatMatcher, &ms.metricFields); + translateFieldMatcher(stateMatcher, &ms.stateFields); + mMetric2StateLinks.push_back(ms); + + int32_t uid1 = 1000; + int32_t uid2 = 1001; + HashableDimensionKey whatKey; + getOverlayKey(uid2, "package", &whatKey); + HashableDimensionKey primaryKey; + getUidProcessKey(uid1, &primaryKey); + + EXPECT_FALSE(containsLinkedStateValues(whatKey, primaryKey, mMetric2StateLinks, stateAtomId)); +} + +/** + * Test that #containsLinkedStateValues returns false when there is no link + * between the key values. + */ +TEST(HashableDimensionKeyTest, TestContainsLinkedStateValues_MissingMetric2StateLinks) { + int stateAtomId = UID_PROCESS_STATE_ATOM_ID; + + std::vector mMetric2StateLinks; + + int32_t uid1 = 1000; + HashableDimensionKey whatKey; + getOverlayKey(uid1, "package", &whatKey); + HashableDimensionKey primaryKey; + getUidProcessKey(uid1, &primaryKey); + + EXPECT_FALSE(containsLinkedStateValues(whatKey, primaryKey, mMetric2StateLinks, stateAtomId)); +} + +/** + * Test that #containsLinkedStateValues returns true when the key values are + * linked and equal. + */ +TEST(HashableDimensionKeyTest, TestContainsLinkedStateValues_AllConditionsMet) { + int stateAtomId = UID_PROCESS_STATE_ATOM_ID; + + FieldMatcher whatMatcher; + whatMatcher.set_field(util::OVERLAY_STATE_CHANGED); + FieldMatcher* child11 = whatMatcher.add_child(); + child11->set_field(1); + + FieldMatcher stateMatcher; + stateMatcher.set_field(stateAtomId); + FieldMatcher* child21 = stateMatcher.add_child(); + child21->set_field(1); + + std::vector mMetric2StateLinks; + Metric2State ms; + ms.stateAtomId = stateAtomId; + translateFieldMatcher(whatMatcher, &ms.metricFields); + translateFieldMatcher(stateMatcher, &ms.stateFields); + mMetric2StateLinks.push_back(ms); + + int32_t uid1 = 1000; + HashableDimensionKey whatKey; + getOverlayKey(uid1, "package", &whatKey); + HashableDimensionKey primaryKey; + getUidProcessKey(uid1, &primaryKey); + + EXPECT_TRUE(containsLinkedStateValues(whatKey, primaryKey, mMetric2StateLinks, stateAtomId)); +} + +} // namespace statsd +} // namespace os +} // namespace android +#else +GTEST_LOG_(INFO) << "This test does nothing.\n"; +#endif diff --git a/statsd/tests/LogEntryMatcher_test.cpp b/statsd/tests/LogEntryMatcher_test.cpp new file mode 100644 index 00000000..12ac5cc1 --- /dev/null +++ b/statsd/tests/LogEntryMatcher_test.cpp @@ -0,0 +1,809 @@ +// 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 + +#include "annotations.h" +#include "src/statsd_config.pb.h" +#include "matchers/matcher_util.h" +#include "stats_event.h" +#include "stats_log_util.h" +#include "stats_util.h" +#include "statsd_test_util.h" + +using namespace android::os::statsd; +using std::unordered_map; +using std::vector; + +const int32_t TAG_ID = 123; +const int32_t TAG_ID_2 = 28; // hardcoded tag of atom with uid field +const int FIELD_ID_1 = 1; +const int FIELD_ID_2 = 2; +const int FIELD_ID_3 = 2; + +const int ATTRIBUTION_UID_FIELD_ID = 1; +const int ATTRIBUTION_TAG_FIELD_ID = 2; + + +#ifdef __ANDROID__ + +namespace { + +void makeIntLogEvent(LogEvent* logEvent, const int32_t atomId, const int64_t timestamp, + const int32_t value) { + AStatsEvent* statsEvent = AStatsEvent_obtain(); + AStatsEvent_setAtomId(statsEvent, atomId); + AStatsEvent_overwriteTimestamp(statsEvent, timestamp); + AStatsEvent_writeInt32(statsEvent, value); + + parseStatsEventToLogEvent(statsEvent, logEvent); +} + +void makeFloatLogEvent(LogEvent* logEvent, const int32_t atomId, const int64_t timestamp, + const float floatValue) { + AStatsEvent* statsEvent = AStatsEvent_obtain(); + AStatsEvent_setAtomId(statsEvent, atomId); + AStatsEvent_overwriteTimestamp(statsEvent, timestamp); + AStatsEvent_writeFloat(statsEvent, floatValue); + + parseStatsEventToLogEvent(statsEvent, logEvent); +} + +void makeStringLogEvent(LogEvent* logEvent, const int32_t atomId, const int64_t timestamp, + const string& name) { + AStatsEvent* statsEvent = AStatsEvent_obtain(); + AStatsEvent_setAtomId(statsEvent, atomId); + AStatsEvent_overwriteTimestamp(statsEvent, timestamp); + AStatsEvent_writeString(statsEvent, name.c_str()); + + parseStatsEventToLogEvent(statsEvent, logEvent); +} + +void makeIntWithBoolAnnotationLogEvent(LogEvent* logEvent, const int32_t atomId, + const int32_t field, const uint8_t annotationId, + const bool annotationValue) { + AStatsEvent* statsEvent = AStatsEvent_obtain(); + AStatsEvent_setAtomId(statsEvent, atomId); + AStatsEvent_writeInt32(statsEvent, field); + AStatsEvent_addBoolAnnotation(statsEvent, annotationId, annotationValue); + + parseStatsEventToLogEvent(statsEvent, logEvent); +} + +void makeAttributionLogEvent(LogEvent* logEvent, const int32_t atomId, const int64_t timestamp, + const vector& attributionUids, + const vector& attributionTags, const string& name) { + AStatsEvent* statsEvent = AStatsEvent_obtain(); + AStatsEvent_setAtomId(statsEvent, atomId); + AStatsEvent_overwriteTimestamp(statsEvent, timestamp); + + writeAttribution(statsEvent, attributionUids, attributionTags); + AStatsEvent_writeString(statsEvent, name.c_str()); + + parseStatsEventToLogEvent(statsEvent, logEvent); +} + +void makeBoolLogEvent(LogEvent* logEvent, const int32_t atomId, const int64_t timestamp, + const bool bool1, const bool bool2) { + AStatsEvent* statsEvent = AStatsEvent_obtain(); + AStatsEvent_setAtomId(statsEvent, atomId); + AStatsEvent_overwriteTimestamp(statsEvent, timestamp); + + AStatsEvent_writeBool(statsEvent, bool1); + AStatsEvent_writeBool(statsEvent, bool2); + + parseStatsEventToLogEvent(statsEvent, logEvent); +} + +} // anonymous namespace + +TEST(AtomMatcherTest, TestSimpleMatcher) { + sp uidMap = new UidMap(); + + // Set up the matcher + AtomMatcher matcher; + auto simpleMatcher = matcher.mutable_simple_atom_matcher(); + simpleMatcher->set_atom_id(TAG_ID); + + LogEvent event(/*uid=*/0, /*pid=*/0); + makeIntLogEvent(&event, TAG_ID, 0, 11); + + // Test + EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); + + // Wrong tag id. + simpleMatcher->set_atom_id(TAG_ID + 1); + EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event)); +} + +TEST(AtomMatcherTest, TestAttributionMatcher) { + sp uidMap = new UidMap(); + std::vector attributionUids = {1111, 2222, 3333}; + std::vector attributionTags = {"location1", "location2", "location3"}; + + // Set up the log event. + LogEvent event(/*uid=*/0, /*pid=*/0); + makeAttributionLogEvent(&event, TAG_ID, 0, attributionUids, attributionTags, "some value"); + + // Set up the matcher + AtomMatcher matcher; + auto simpleMatcher = matcher.mutable_simple_atom_matcher(); + simpleMatcher->set_atom_id(TAG_ID); + + // Match first node. + auto attributionMatcher = simpleMatcher->add_field_value_matcher(); + attributionMatcher->set_field(FIELD_ID_1); + attributionMatcher->set_position(Position::FIRST); + attributionMatcher->mutable_matches_tuple()->add_field_value_matcher()->set_field( + ATTRIBUTION_TAG_FIELD_ID); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_string( + "tag"); + + auto fieldMatcher = simpleMatcher->add_field_value_matcher(); + fieldMatcher->set_field(FIELD_ID_2); + fieldMatcher->set_eq_string("some value"); + + // Tag not matched. + EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event)); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_string( + "location3"); + EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event)); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_string( + "location1"); + EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); + + // Match last node. + attributionMatcher->set_position(Position::LAST); + EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event)); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_string( + "location3"); + EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); + + // Match any node. + attributionMatcher->set_position(Position::ANY); + EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_string( + "location1"); + EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_string( + "location2"); + EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_string( + "location3"); + EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_string( + "location4"); + EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event)); + + // Attribution match but primitive field not match. + attributionMatcher->set_position(Position::ANY); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_string( + "location2"); + fieldMatcher->set_eq_string("wrong value"); + EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event)); + + fieldMatcher->set_eq_string("some value"); + + // Uid match. + attributionMatcher->set_position(Position::ANY); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_field( + ATTRIBUTION_UID_FIELD_ID); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_string( + "pkg0"); + EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event)); + + uidMap->updateMap( + 1, {1111, 1111, 2222, 3333, 3333} /* uid list */, {1, 1, 2, 1, 2} /* version list */, + {android::String16("v1"), android::String16("v1"), android::String16("v2"), + android::String16("v1"), android::String16("v2")}, + {android::String16("pkg0"), android::String16("pkg1"), android::String16("pkg1"), + android::String16("Pkg2"), android::String16("PkG3")} /* package name list */, + {android::String16(""), android::String16(""), android::String16(""), + android::String16(""), android::String16("")}); + + EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_string( + "pkg3"); + EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_string( + "pkg2"); + EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_string( + "pkg1"); + EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_string( + "pkg0"); + EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); + + attributionMatcher->set_position(Position::FIRST); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_string( + "pkg0"); + EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_string( + "pkg3"); + EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event)); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_string( + "pkg2"); + EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event)); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_string( + "pkg1"); + EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); + + attributionMatcher->set_position(Position::LAST); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_string( + "pkg0"); + EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event)); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_string( + "pkg3"); + EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_string( + "pkg2"); + EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_string( + "pkg1"); + EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event)); + + // Uid + tag. + attributionMatcher->set_position(Position::ANY); + attributionMatcher->mutable_matches_tuple()->add_field_value_matcher()->set_field( + ATTRIBUTION_TAG_FIELD_ID); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_string( + "pkg0"); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(1)->set_eq_string( + "location1"); + EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_string( + "pkg1"); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(1)->set_eq_string( + "location1"); + EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_string( + "pkg1"); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(1)->set_eq_string( + "location2"); + EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_string( + "pkg2"); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(1)->set_eq_string( + "location3"); + EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_string( + "pkg3"); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(1)->set_eq_string( + "location3"); + EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_string( + "pkg3"); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(1)->set_eq_string( + "location1"); + EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event)); + + attributionMatcher->set_position(Position::FIRST); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_string( + "pkg0"); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(1)->set_eq_string( + "location1"); + EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_string( + "pkg1"); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(1)->set_eq_string( + "location1"); + EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_string( + "pkg1"); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(1)->set_eq_string( + "location2"); + EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event)); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_string( + "pkg2"); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(1)->set_eq_string( + "location3"); + EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event)); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_string( + "pkg3"); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(1)->set_eq_string( + "location3"); + EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event)); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_string( + "pkg3"); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(1)->set_eq_string( + "location1"); + EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event)); + + attributionMatcher->set_position(Position::LAST); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_string( + "pkg0"); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(1)->set_eq_string( + "location1"); + EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event)); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_string( + "pkg1"); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(1)->set_eq_string( + "location1"); + EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event)); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_string( + "pkg1"); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(1)->set_eq_string( + "location2"); + EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event)); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_string( + "pkg2"); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(1)->set_eq_string( + "location3"); + EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_string( + "pkg3"); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(1)->set_eq_string( + "location3"); + EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_string( + "pkg3"); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(1)->set_eq_string( + "location1"); + EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event)); +} + +TEST(AtomMatcherTest, TestUidFieldMatcher) { + sp uidMap = new UidMap(); + uidMap->updateMap( + 1, {1111, 1111, 2222, 3333, 3333} /* uid list */, {1, 1, 2, 1, 2} /* version list */, + {android::String16("v1"), android::String16("v1"), android::String16("v2"), + android::String16("v1"), android::String16("v2")}, + {android::String16("pkg0"), android::String16("pkg1"), android::String16("pkg1"), + android::String16("Pkg2"), android::String16("PkG3")} /* package name list */, + {android::String16(""), android::String16(""), android::String16(""), + android::String16(""), android::String16("")}); + + // Set up matcher + AtomMatcher matcher; + auto simpleMatcher = matcher.mutable_simple_atom_matcher(); + simpleMatcher->set_atom_id(TAG_ID); + simpleMatcher->add_field_value_matcher()->set_field(1); + simpleMatcher->mutable_field_value_matcher(0)->set_eq_string("pkg0"); + + // Make event without is_uid annotation. + LogEvent event1(/*uid=*/0, /*pid=*/0); + makeIntLogEvent(&event1, TAG_ID, 0, 1111); + EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event1)); + + // Make event with is_uid annotation. + LogEvent event2(/*uid=*/0, /*pid=*/0); + makeIntWithBoolAnnotationLogEvent(&event2, TAG_ID_2, 1111, ANNOTATION_ID_IS_UID, true); + + // Event has is_uid annotation, so mapping from uid to package name occurs. + simpleMatcher->set_atom_id(TAG_ID_2); + EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event2)); + + // Event has is_uid annotation, but uid maps to different package name. + simpleMatcher->mutable_field_value_matcher(0)->set_eq_string("Pkg2"); + EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event2)); +} + +TEST(AtomMatcherTest, TestNeqAnyStringMatcher) { + sp uidMap = new UidMap(); + uidMap->updateMap( + 1, {1111, 1111, 2222, 3333, 3333} /* uid list */, {1, 1, 2, 1, 2} /* version list */, + {android::String16("v1"), android::String16("v1"), android::String16("v2"), + android::String16("v1"), android::String16("v2")}, + {android::String16("pkg0"), android::String16("pkg1"), android::String16("pkg1"), + android::String16("Pkg2"), android::String16("PkG3")} /* package name list */, + {android::String16(""), android::String16(""), android::String16(""), + android::String16(""), android::String16("")}); + + std::vector attributionUids = {1111, 2222, 3333, 1066}; + std::vector attributionTags = {"location1", "location2", "location3", "location3"}; + + // Set up the event + LogEvent event(/*uid=*/0, /*pid=*/0); + makeAttributionLogEvent(&event, TAG_ID, 0, attributionUids, attributionTags, "some value"); + + // Set up the matcher + AtomMatcher matcher; + auto simpleMatcher = matcher.mutable_simple_atom_matcher(); + simpleMatcher->set_atom_id(TAG_ID); + + // Match first node. + auto attributionMatcher = simpleMatcher->add_field_value_matcher(); + attributionMatcher->set_field(FIELD_ID_1); + attributionMatcher->set_position(Position::FIRST); + attributionMatcher->mutable_matches_tuple()->add_field_value_matcher()->set_field( + ATTRIBUTION_UID_FIELD_ID); + auto neqStringList = attributionMatcher->mutable_matches_tuple() + ->mutable_field_value_matcher(0) + ->mutable_neq_any_string(); + neqStringList->add_str_value("pkg2"); + neqStringList->add_str_value("pkg3"); + + auto fieldMatcher = simpleMatcher->add_field_value_matcher(); + fieldMatcher->set_field(FIELD_ID_2); + fieldMatcher->set_eq_string("some value"); + + EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); + + neqStringList->Clear(); + neqStringList->add_str_value("pkg1"); + neqStringList->add_str_value("pkg3"); + EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event)); + + attributionMatcher->set_position(Position::ANY); + neqStringList->Clear(); + neqStringList->add_str_value("maps.com"); + EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); + + neqStringList->Clear(); + neqStringList->add_str_value("PkG3"); + EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); + + attributionMatcher->set_position(Position::LAST); + neqStringList->Clear(); + neqStringList->add_str_value("AID_STATSD"); + EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event)); +} + +TEST(AtomMatcherTest, TestEqAnyStringMatcher) { + sp uidMap = new UidMap(); + uidMap->updateMap( + 1, {1111, 1111, 2222, 3333, 3333} /* uid list */, {1, 1, 2, 1, 2} /* version list */, + {android::String16("v1"), android::String16("v1"), android::String16("v2"), + android::String16("v1"), android::String16("v2")}, + {android::String16("pkg0"), android::String16("pkg1"), android::String16("pkg1"), + android::String16("Pkg2"), android::String16("PkG3")} /* package name list */, + {android::String16(""), android::String16(""), android::String16(""), + android::String16(""), android::String16("")}); + + std::vector attributionUids = {1067, 2222, 3333, 1066}; + std::vector attributionTags = {"location1", "location2", "location3", "location3"}; + + // Set up the event + LogEvent event(/*uid=*/0, /*pid=*/0); + makeAttributionLogEvent(&event, TAG_ID, 0, attributionUids, attributionTags, "some value"); + + // Set up the matcher + AtomMatcher matcher; + auto simpleMatcher = matcher.mutable_simple_atom_matcher(); + simpleMatcher->set_atom_id(TAG_ID); + + // Match first node. + auto attributionMatcher = simpleMatcher->add_field_value_matcher(); + attributionMatcher->set_field(FIELD_ID_1); + attributionMatcher->set_position(Position::FIRST); + attributionMatcher->mutable_matches_tuple()->add_field_value_matcher()->set_field( + ATTRIBUTION_UID_FIELD_ID); + auto eqStringList = attributionMatcher->mutable_matches_tuple() + ->mutable_field_value_matcher(0) + ->mutable_eq_any_string(); + eqStringList->add_str_value("AID_ROOT"); + eqStringList->add_str_value("AID_INCIDENTD"); + + auto fieldMatcher = simpleMatcher->add_field_value_matcher(); + fieldMatcher->set_field(FIELD_ID_2); + fieldMatcher->set_eq_string("some value"); + + EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); + + attributionMatcher->set_position(Position::ANY); + eqStringList->Clear(); + eqStringList->add_str_value("AID_STATSD"); + EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); + + eqStringList->Clear(); + eqStringList->add_str_value("pkg1"); + EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); + + auto normalStringField = fieldMatcher->mutable_eq_any_string(); + normalStringField->add_str_value("some value123"); + normalStringField->add_str_value("some value"); + EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); + + normalStringField->Clear(); + normalStringField->add_str_value("AID_STATSD"); + EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event)); + + eqStringList->Clear(); + eqStringList->add_str_value("maps.com"); + EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event)); +} + +TEST(AtomMatcherTest, TestBoolMatcher) { + sp uidMap = new UidMap(); + // Set up the matcher + AtomMatcher matcher; + auto simpleMatcher = matcher.mutable_simple_atom_matcher(); + simpleMatcher->set_atom_id(TAG_ID); + auto keyValue1 = simpleMatcher->add_field_value_matcher(); + keyValue1->set_field(FIELD_ID_1); + auto keyValue2 = simpleMatcher->add_field_value_matcher(); + keyValue2->set_field(FIELD_ID_2); + + // Set up the event + LogEvent event(/*uid=*/0, /*pid=*/0); + makeBoolLogEvent(&event, TAG_ID, 0, true, false); + + // Test + keyValue1->set_eq_bool(true); + keyValue2->set_eq_bool(false); + EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); + + keyValue1->set_eq_bool(false); + keyValue2->set_eq_bool(false); + EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event)); + + keyValue1->set_eq_bool(false); + keyValue2->set_eq_bool(true); + EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event)); + + keyValue1->set_eq_bool(true); + keyValue2->set_eq_bool(true); + EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event)); +} + +TEST(AtomMatcherTest, TestStringMatcher) { + sp uidMap = new UidMap(); + // Set up the matcher + AtomMatcher matcher; + auto simpleMatcher = matcher.mutable_simple_atom_matcher(); + simpleMatcher->set_atom_id(TAG_ID); + auto keyValue = simpleMatcher->add_field_value_matcher(); + keyValue->set_field(FIELD_ID_1); + keyValue->set_eq_string("some value"); + + // Set up the event + LogEvent event(/*uid=*/0, /*pid=*/0); + makeStringLogEvent(&event, TAG_ID, 0, "some value"); + + // Test + EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); +} + +TEST(AtomMatcherTest, TestMultiFieldsMatcher) { + sp uidMap = new UidMap(); + // Set up the matcher + AtomMatcher matcher; + auto simpleMatcher = matcher.mutable_simple_atom_matcher(); + simpleMatcher->set_atom_id(TAG_ID); + auto keyValue1 = simpleMatcher->add_field_value_matcher(); + keyValue1->set_field(FIELD_ID_1); + auto keyValue2 = simpleMatcher->add_field_value_matcher(); + keyValue2->set_field(FIELD_ID_2); + + // Set up the event + LogEvent event(/*uid=*/0, /*pid=*/0); + CreateTwoValueLogEvent(&event, TAG_ID, 0, 2, 3); + + // Test + keyValue1->set_eq_int(2); + keyValue2->set_eq_int(3); + EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); + + keyValue1->set_eq_int(2); + keyValue2->set_eq_int(4); + EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event)); + + keyValue1->set_eq_int(4); + keyValue2->set_eq_int(3); + EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event)); +} + +TEST(AtomMatcherTest, TestIntComparisonMatcher) { + sp uidMap = new UidMap(); + // Set up the matcher + AtomMatcher matcher; + auto simpleMatcher = matcher.mutable_simple_atom_matcher(); + + simpleMatcher->set_atom_id(TAG_ID); + auto keyValue = simpleMatcher->add_field_value_matcher(); + keyValue->set_field(FIELD_ID_1); + + // Set up the event + LogEvent event(/*uid=*/0, /*pid=*/0); + makeIntLogEvent(&event, TAG_ID, 0, 11); + + // Test + + // eq_int + keyValue->set_eq_int(10); + EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event)); + keyValue->set_eq_int(11); + EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); + keyValue->set_eq_int(12); + EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event)); + + // lt_int + keyValue->set_lt_int(10); + EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event)); + keyValue->set_lt_int(11); + EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event)); + keyValue->set_lt_int(12); + EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); + + // lte_int + keyValue->set_lte_int(10); + EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event)); + keyValue->set_lte_int(11); + EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); + keyValue->set_lte_int(12); + EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); + + // gt_int + keyValue->set_gt_int(10); + EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); + keyValue->set_gt_int(11); + EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event)); + keyValue->set_gt_int(12); + EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event)); + + // gte_int + keyValue->set_gte_int(10); + EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); + keyValue->set_gte_int(11); + EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); + keyValue->set_gte_int(12); + EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event)); +} + +TEST(AtomMatcherTest, TestFloatComparisonMatcher) { + sp uidMap = new UidMap(); + // Set up the matcher + AtomMatcher matcher; + auto simpleMatcher = matcher.mutable_simple_atom_matcher(); + simpleMatcher->set_atom_id(TAG_ID); + + auto keyValue = simpleMatcher->add_field_value_matcher(); + keyValue->set_field(FIELD_ID_1); + + LogEvent event1(/*uid=*/0, /*pid=*/0); + makeFloatLogEvent(&event1, TAG_ID, 0, 10.1f); + keyValue->set_lt_float(10.0); + EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event1)); + + LogEvent event2(/*uid=*/0, /*pid=*/0); + makeFloatLogEvent(&event2, TAG_ID, 0, 9.9f); + EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event2)); + + LogEvent event3(/*uid=*/0, /*pid=*/0); + makeFloatLogEvent(&event3, TAG_ID, 0, 10.1f); + keyValue->set_gt_float(10.0); + EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event3)); + + LogEvent event4(/*uid=*/0, /*pid=*/0); + makeFloatLogEvent(&event4, TAG_ID, 0, 9.9f); + EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event4)); +} + +// Helper for the composite matchers. +void addSimpleMatcher(SimpleAtomMatcher* simpleMatcher, int tag, int key, int val) { + simpleMatcher->set_atom_id(tag); + auto keyValue = simpleMatcher->add_field_value_matcher(); + keyValue->set_field(key); + keyValue->set_eq_int(val); +} + +TEST(AtomMatcherTest, TestAndMatcher) { + // Set up the matcher + LogicalOperation operation = LogicalOperation::AND; + + vector children; + children.push_back(0); + children.push_back(1); + children.push_back(2); + + vector matcherResults; + matcherResults.push_back(MatchingState::kMatched); + matcherResults.push_back(MatchingState::kNotMatched); + matcherResults.push_back(MatchingState::kMatched); + + EXPECT_FALSE(combinationMatch(children, operation, matcherResults)); + + matcherResults.clear(); + matcherResults.push_back(MatchingState::kMatched); + matcherResults.push_back(MatchingState::kMatched); + matcherResults.push_back(MatchingState::kMatched); + + EXPECT_TRUE(combinationMatch(children, operation, matcherResults)); +} + +TEST(AtomMatcherTest, TestOrMatcher) { + // Set up the matcher + LogicalOperation operation = LogicalOperation::OR; + + vector children; + children.push_back(0); + children.push_back(1); + children.push_back(2); + + vector matcherResults; + matcherResults.push_back(MatchingState::kMatched); + matcherResults.push_back(MatchingState::kNotMatched); + matcherResults.push_back(MatchingState::kMatched); + + EXPECT_TRUE(combinationMatch(children, operation, matcherResults)); + + matcherResults.clear(); + matcherResults.push_back(MatchingState::kNotMatched); + matcherResults.push_back(MatchingState::kNotMatched); + matcherResults.push_back(MatchingState::kNotMatched); + + EXPECT_FALSE(combinationMatch(children, operation, matcherResults)); +} + +TEST(AtomMatcherTest, TestNotMatcher) { + // Set up the matcher + LogicalOperation operation = LogicalOperation::NOT; + + vector children; + children.push_back(0); + + vector matcherResults; + matcherResults.push_back(MatchingState::kMatched); + + EXPECT_FALSE(combinationMatch(children, operation, matcherResults)); + + matcherResults.clear(); + matcherResults.push_back(MatchingState::kNotMatched); + EXPECT_TRUE(combinationMatch(children, operation, matcherResults)); +} + +TEST(AtomMatcherTest, TestNandMatcher) { + // Set up the matcher + LogicalOperation operation = LogicalOperation::NAND; + + vector children; + children.push_back(0); + children.push_back(1); + + vector matcherResults; + matcherResults.push_back(MatchingState::kMatched); + matcherResults.push_back(MatchingState::kNotMatched); + + EXPECT_TRUE(combinationMatch(children, operation, matcherResults)); + + matcherResults.clear(); + matcherResults.push_back(MatchingState::kNotMatched); + matcherResults.push_back(MatchingState::kNotMatched); + EXPECT_TRUE(combinationMatch(children, operation, matcherResults)); + + matcherResults.clear(); + matcherResults.push_back(MatchingState::kMatched); + matcherResults.push_back(MatchingState::kMatched); + EXPECT_FALSE(combinationMatch(children, operation, matcherResults)); +} + +TEST(AtomMatcherTest, TestNorMatcher) { + // Set up the matcher + LogicalOperation operation = LogicalOperation::NOR; + + vector children; + children.push_back(0); + children.push_back(1); + + vector matcherResults; + matcherResults.push_back(MatchingState::kMatched); + matcherResults.push_back(MatchingState::kNotMatched); + + EXPECT_FALSE(combinationMatch(children, operation, matcherResults)); + + matcherResults.clear(); + matcherResults.push_back(MatchingState::kNotMatched); + matcherResults.push_back(MatchingState::kNotMatched); + EXPECT_TRUE(combinationMatch(children, operation, matcherResults)); + + matcherResults.clear(); + matcherResults.push_back(MatchingState::kMatched); + matcherResults.push_back(MatchingState::kMatched); + EXPECT_FALSE(combinationMatch(children, operation, matcherResults)); +} +#else +GTEST_LOG_(INFO) << "This test does nothing.\n"; +#endif diff --git a/statsd/tests/LogEvent_test.cpp b/statsd/tests/LogEvent_test.cpp new file mode 100644 index 00000000..13c23d22 --- /dev/null +++ b/statsd/tests/LogEvent_test.cpp @@ -0,0 +1,481 @@ +// 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 "src/logd/LogEvent.h" + +#include + +#include "frameworks/proto_logging/stats/atoms.pb.h" +#include "frameworks/proto_logging/stats/enums/stats/launcher/launcher.pb.h" +#include "log/log_event_list.h" +#include "stats_event.h" + +#ifdef __ANDROID__ + +namespace android { +namespace os { +namespace statsd { + +using std::string; +using std::vector; +using util::ProtoOutputStream; +using util::ProtoReader; + +namespace { + +Field getField(int32_t tag, const vector& pos, int32_t depth, const vector& last) { + Field f(tag, (int32_t*)pos.data(), depth); + + // For loop starts at 1 because the last field at depth 0 is not decorated. + for (int i = 1; i < depth; i++) { + if (last[i]) f.decorateLastPos(i); + } + + return f; +} + +void createIntWithBoolAnnotationLogEvent(LogEvent* logEvent, uint8_t annotationId, + bool annotationValue) { + AStatsEvent* statsEvent = AStatsEvent_obtain(); + AStatsEvent_setAtomId(statsEvent, /*atomId=*/100); + AStatsEvent_writeInt32(statsEvent, 10); + AStatsEvent_addBoolAnnotation(statsEvent, annotationId, annotationValue); + AStatsEvent_build(statsEvent); + + size_t size; + uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size); + EXPECT_TRUE(logEvent->parseBuffer(buf, size)); + + AStatsEvent_release(statsEvent); +} + +void createIntWithIntAnnotationLogEvent(LogEvent* logEvent, uint8_t annotationId, + int annotationValue) { + AStatsEvent* statsEvent = AStatsEvent_obtain(); + AStatsEvent_setAtomId(statsEvent, /*atomId=*/100); + AStatsEvent_writeInt32(statsEvent, 10); + AStatsEvent_addInt32Annotation(statsEvent, annotationId, annotationValue); + AStatsEvent_build(statsEvent); + + size_t size; + uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size); + EXPECT_TRUE(logEvent->parseBuffer(buf, size)); + + AStatsEvent_release(statsEvent); +} + +} // anonymous namespace + +TEST(LogEventTest, TestPrimitiveParsing) { + AStatsEvent* event = AStatsEvent_obtain(); + AStatsEvent_setAtomId(event, 100); + AStatsEvent_writeInt32(event, 10); + AStatsEvent_writeInt64(event, 0x123456789); + AStatsEvent_writeFloat(event, 2.0); + AStatsEvent_writeBool(event, true); + AStatsEvent_build(event); + + size_t size; + uint8_t* buf = AStatsEvent_getBuffer(event, &size); + + LogEvent logEvent(/*uid=*/1000, /*pid=*/1001); + EXPECT_TRUE(logEvent.parseBuffer(buf, size)); + + EXPECT_EQ(100, logEvent.GetTagId()); + EXPECT_EQ(1000, logEvent.GetUid()); + EXPECT_EQ(1001, logEvent.GetPid()); + EXPECT_FALSE(logEvent.hasAttributionChain()); + + const vector& values = logEvent.getValues(); + ASSERT_EQ(4, values.size()); + + const FieldValue& int32Item = values[0]; + Field expectedField = getField(100, {1, 1, 1}, 0, {false, false, false}); + EXPECT_EQ(expectedField, int32Item.mField); + EXPECT_EQ(Type::INT, int32Item.mValue.getType()); + EXPECT_EQ(10, int32Item.mValue.int_value); + + const FieldValue& int64Item = values[1]; + expectedField = getField(100, {2, 1, 1}, 0, {false, false, false}); + EXPECT_EQ(expectedField, int64Item.mField); + EXPECT_EQ(Type::LONG, int64Item.mValue.getType()); + EXPECT_EQ(0x123456789, int64Item.mValue.long_value); + + const FieldValue& floatItem = values[2]; + expectedField = getField(100, {3, 1, 1}, 0, {false, false, false}); + EXPECT_EQ(expectedField, floatItem.mField); + EXPECT_EQ(Type::FLOAT, floatItem.mValue.getType()); + EXPECT_EQ(2.0, floatItem.mValue.float_value); + + const FieldValue& boolItem = values[3]; + expectedField = getField(100, {4, 1, 1}, 0, {true, false, false}); + EXPECT_EQ(expectedField, boolItem.mField); + EXPECT_EQ(Type::INT, boolItem.mValue.getType()); // FieldValue does not support boolean type + EXPECT_EQ(1, boolItem.mValue.int_value); + + AStatsEvent_release(event); +} + +TEST(LogEventTest, TestStringAndByteArrayParsing) { + AStatsEvent* event = AStatsEvent_obtain(); + AStatsEvent_setAtomId(event, 100); + string str = "test"; + AStatsEvent_writeString(event, str.c_str()); + AStatsEvent_writeByteArray(event, (uint8_t*)str.c_str(), str.length()); + AStatsEvent_build(event); + + size_t size; + uint8_t* buf = AStatsEvent_getBuffer(event, &size); + + LogEvent logEvent(/*uid=*/1000, /*pid=*/1001); + EXPECT_TRUE(logEvent.parseBuffer(buf, size)); + + EXPECT_EQ(100, logEvent.GetTagId()); + EXPECT_EQ(1000, logEvent.GetUid()); + EXPECT_EQ(1001, logEvent.GetPid()); + EXPECT_FALSE(logEvent.hasAttributionChain()); + + const vector& values = logEvent.getValues(); + ASSERT_EQ(2, values.size()); + + const FieldValue& stringItem = values[0]; + Field expectedField = getField(100, {1, 1, 1}, 0, {false, false, false}); + EXPECT_EQ(expectedField, stringItem.mField); + EXPECT_EQ(Type::STRING, stringItem.mValue.getType()); + EXPECT_EQ(str, stringItem.mValue.str_value); + + const FieldValue& storageItem = values[1]; + expectedField = getField(100, {2, 1, 1}, 0, {true, false, false}); + EXPECT_EQ(expectedField, storageItem.mField); + EXPECT_EQ(Type::STORAGE, storageItem.mValue.getType()); + vector expectedValue = {'t', 'e', 's', 't'}; + EXPECT_EQ(expectedValue, storageItem.mValue.storage_value); + + AStatsEvent_release(event); +} + +TEST(LogEventTest, TestEmptyString) { + AStatsEvent* event = AStatsEvent_obtain(); + AStatsEvent_setAtomId(event, 100); + string empty = ""; + AStatsEvent_writeString(event, empty.c_str()); + AStatsEvent_build(event); + + size_t size; + uint8_t* buf = AStatsEvent_getBuffer(event, &size); + + LogEvent logEvent(/*uid=*/1000, /*pid=*/1001); + EXPECT_TRUE(logEvent.parseBuffer(buf, size)); + + EXPECT_EQ(100, logEvent.GetTagId()); + EXPECT_EQ(1000, logEvent.GetUid()); + EXPECT_EQ(1001, logEvent.GetPid()); + EXPECT_FALSE(logEvent.hasAttributionChain()); + + const vector& values = logEvent.getValues(); + ASSERT_EQ(1, values.size()); + + const FieldValue& item = values[0]; + Field expectedField = getField(100, {1, 1, 1}, 0, {true, false, false}); + EXPECT_EQ(expectedField, item.mField); + EXPECT_EQ(Type::STRING, item.mValue.getType()); + EXPECT_EQ(empty, item.mValue.str_value); + + AStatsEvent_release(event); +} + +TEST(LogEventTest, TestByteArrayWithNullCharacter) { + AStatsEvent* event = AStatsEvent_obtain(); + AStatsEvent_setAtomId(event, 100); + uint8_t message[] = {'\t', 'e', '\0', 's', 't'}; + AStatsEvent_writeByteArray(event, message, 5); + AStatsEvent_build(event); + + size_t size; + uint8_t* buf = AStatsEvent_getBuffer(event, &size); + + LogEvent logEvent(/*uid=*/1000, /*pid=*/1001); + EXPECT_TRUE(logEvent.parseBuffer(buf, size)); + + EXPECT_EQ(100, logEvent.GetTagId()); + EXPECT_EQ(1000, logEvent.GetUid()); + EXPECT_EQ(1001, logEvent.GetPid()); + + const vector& values = logEvent.getValues(); + ASSERT_EQ(1, values.size()); + + const FieldValue& item = values[0]; + Field expectedField = getField(100, {1, 1, 1}, 0, {true, false, false}); + EXPECT_EQ(expectedField, item.mField); + EXPECT_EQ(Type::STORAGE, item.mValue.getType()); + vector expectedValue(message, message + 5); + EXPECT_EQ(expectedValue, item.mValue.storage_value); + + AStatsEvent_release(event); +} + +TEST(LogEventTest, TestAttributionChain) { + AStatsEvent* event = AStatsEvent_obtain(); + AStatsEvent_setAtomId(event, 100); + + string tag1 = "tag1"; + string tag2 = "tag2"; + + uint32_t uids[] = {1001, 1002}; + const char* tags[] = {tag1.c_str(), tag2.c_str()}; + + AStatsEvent_writeAttributionChain(event, uids, tags, 2); + AStatsEvent_build(event); + + size_t size; + uint8_t* buf = AStatsEvent_getBuffer(event, &size); + + LogEvent logEvent(/*uid=*/1000, /*pid=*/1001); + EXPECT_TRUE(logEvent.parseBuffer(buf, size)); + + EXPECT_EQ(100, logEvent.GetTagId()); + EXPECT_EQ(1000, logEvent.GetUid()); + EXPECT_EQ(1001, logEvent.GetPid()); + + const vector& values = logEvent.getValues(); + ASSERT_EQ(4, values.size()); // 2 per attribution node + + std::pair attrIndexRange; + EXPECT_TRUE(logEvent.hasAttributionChain(&attrIndexRange)); + EXPECT_EQ(0, attrIndexRange.first); + EXPECT_EQ(3, attrIndexRange.second); + + // Check first attribution node + const FieldValue& uid1Item = values[0]; + Field expectedField = getField(100, {1, 1, 1}, 2, {true, false, false}); + EXPECT_EQ(expectedField, uid1Item.mField); + EXPECT_EQ(Type::INT, uid1Item.mValue.getType()); + EXPECT_EQ(1001, uid1Item.mValue.int_value); + + const FieldValue& tag1Item = values[1]; + expectedField = getField(100, {1, 1, 2}, 2, {true, false, true}); + EXPECT_EQ(expectedField, tag1Item.mField); + EXPECT_EQ(Type::STRING, tag1Item.mValue.getType()); + EXPECT_EQ(tag1, tag1Item.mValue.str_value); + + // Check second attribution nodes + const FieldValue& uid2Item = values[2]; + expectedField = getField(100, {1, 2, 1}, 2, {true, true, false}); + EXPECT_EQ(expectedField, uid2Item.mField); + EXPECT_EQ(Type::INT, uid2Item.mValue.getType()); + EXPECT_EQ(1002, uid2Item.mValue.int_value); + + const FieldValue& tag2Item = values[3]; + expectedField = getField(100, {1, 2, 2}, 2, {true, true, true}); + EXPECT_EQ(expectedField, tag2Item.mField); + EXPECT_EQ(Type::STRING, tag2Item.mValue.getType()); + EXPECT_EQ(tag2, tag2Item.mValue.str_value); + + AStatsEvent_release(event); +} + +TEST(LogEventTest, TestAnnotationIdIsUid) { + LogEvent event(/*uid=*/0, /*pid=*/0); + createIntWithBoolAnnotationLogEvent(&event, ANNOTATION_ID_IS_UID, true); + + const vector& values = event.getValues(); + ASSERT_EQ(values.size(), 1); + EXPECT_EQ(event.getUidFieldIndex(), 0); +} + +TEST(LogEventTest, TestAnnotationIdStateNested) { + LogEvent event(/*uid=*/0, /*pid=*/0); + createIntWithBoolAnnotationLogEvent(&event, ANNOTATION_ID_STATE_NESTED, true); + + const vector& values = event.getValues(); + ASSERT_EQ(values.size(), 1); + EXPECT_TRUE(values[0].mAnnotations.isNested()); +} + +TEST(LogEventTest, TestPrimaryFieldAnnotation) { + LogEvent event(/*uid=*/0, /*pid=*/0); + createIntWithBoolAnnotationLogEvent(&event, ANNOTATION_ID_PRIMARY_FIELD, true); + + const vector& values = event.getValues(); + ASSERT_EQ(values.size(), 1); + EXPECT_TRUE(values[0].mAnnotations.isPrimaryField()); +} + +TEST(LogEventTest, TestExclusiveStateAnnotation) { + LogEvent event(/*uid=*/0, /*pid=*/0); + createIntWithBoolAnnotationLogEvent(&event, ANNOTATION_ID_EXCLUSIVE_STATE, true); + + const vector& values = event.getValues(); + ASSERT_EQ(values.size(), 1); + EXPECT_TRUE(values[0].mAnnotations.isExclusiveState()); +} + +TEST(LogEventTest, TestPrimaryFieldFirstUidAnnotation) { + // Event has 10 ints and then an attribution chain + int numInts = 10; + int firstUidInChainIndex = numInts; + string tag1 = "tag1"; + string tag2 = "tag2"; + uint32_t uids[] = {1001, 1002}; + const char* tags[] = {tag1.c_str(), tag2.c_str()}; + + // Construct AStatsEvent + AStatsEvent* statsEvent = AStatsEvent_obtain(); + AStatsEvent_setAtomId(statsEvent, 100); + for (int i = 0; i < numInts; i++) { + AStatsEvent_writeInt32(statsEvent, 10); + } + AStatsEvent_writeAttributionChain(statsEvent, uids, tags, 2); + AStatsEvent_addBoolAnnotation(statsEvent, ANNOTATION_ID_PRIMARY_FIELD_FIRST_UID, true); + AStatsEvent_build(statsEvent); + + // Construct LogEvent + size_t size; + uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size); + LogEvent logEvent(/*uid=*/0, /*pid=*/0); + EXPECT_TRUE(logEvent.parseBuffer(buf, size)); + AStatsEvent_release(statsEvent); + + // Check annotation + const vector& values = logEvent.getValues(); + ASSERT_EQ(values.size(), numInts + 4); + EXPECT_TRUE(values[firstUidInChainIndex].mAnnotations.isPrimaryField()); +} + +TEST(LogEventTest, TestResetStateAnnotation) { + int32_t resetState = 10; + LogEvent event(/*uid=*/0, /*pid=*/0); + createIntWithIntAnnotationLogEvent(&event, ANNOTATION_ID_TRIGGER_STATE_RESET, resetState); + + const vector& values = event.getValues(); + ASSERT_EQ(values.size(), 1); + EXPECT_EQ(event.getResetState(), resetState); +} + +TEST(LogEventTest, TestExclusiveStateAnnotationAfterTooManyFields) { + AStatsEvent* event = AStatsEvent_obtain(); + AStatsEvent_setAtomId(event, 100); + + const unsigned int numAttributionNodes = 64; + + uint32_t uids[numAttributionNodes]; + const char* tags[numAttributionNodes]; + + for (unsigned int i = 1; i <= numAttributionNodes; i++) { + uids[i-1] = i; + tags[i-1] = std::to_string(i).c_str(); + } + + AStatsEvent_writeAttributionChain(event, uids, tags, numAttributionNodes); + AStatsEvent_writeInt32(event, 1); + AStatsEvent_addBoolAnnotation(event, ANNOTATION_ID_EXCLUSIVE_STATE, true); + + AStatsEvent_build(event); + + size_t size; + uint8_t* buf = AStatsEvent_getBuffer(event, &size); + + LogEvent logEvent(/*uid=*/1000, /*pid=*/1001); + EXPECT_FALSE(logEvent.parseBuffer(buf, size)); + EXPECT_EQ(-1, logEvent.getExclusiveStateFieldIndex()); + + AStatsEvent_release(event); +} + +TEST(LogEventTest, TestUidAnnotationAfterTooManyFields) { + AStatsEvent* event = AStatsEvent_obtain(); + AStatsEvent_setAtomId(event, 100); + + const unsigned int numAttributionNodes = 64; + + uint32_t uids[numAttributionNodes]; + const char* tags[numAttributionNodes]; + + for (unsigned int i = 1; i <= numAttributionNodes; i++) { + uids[i-1] = i; + tags[i-1] = std::to_string(i).c_str(); + } + + AStatsEvent_writeAttributionChain(event, uids, tags, numAttributionNodes); + AStatsEvent_writeInt32(event, 1); + AStatsEvent_addBoolAnnotation(event, ANNOTATION_ID_IS_UID, true); + + AStatsEvent_build(event); + + size_t size; + uint8_t* buf = AStatsEvent_getBuffer(event, &size); + + LogEvent logEvent(/*uid=*/1000, /*pid=*/1001); + EXPECT_FALSE(logEvent.parseBuffer(buf, size)); + EXPECT_EQ(-1, logEvent.getUidFieldIndex()); + + AStatsEvent_release(event); +} + +TEST(LogEventTest, TestAttributionChainEndIndexAfterTooManyFields) { + AStatsEvent* event = AStatsEvent_obtain(); + AStatsEvent_setAtomId(event, 100); + + const unsigned int numAttributionNodes = 65; + + uint32_t uids[numAttributionNodes]; + const char* tags[numAttributionNodes]; + + for (unsigned int i = 1; i <= numAttributionNodes; i++) { + uids[i-1] = i; + tags[i-1] = std::to_string(i).c_str(); + } + + AStatsEvent_writeAttributionChain(event, uids, tags, numAttributionNodes); + + AStatsEvent_build(event); + + size_t size; + uint8_t* buf = AStatsEvent_getBuffer(event, &size); + + LogEvent logEvent(/*uid=*/1000, /*pid=*/1001); + EXPECT_FALSE(logEvent.parseBuffer(buf, size)); + EXPECT_FALSE(logEvent.hasAttributionChain()); + + AStatsEvent_release(event); +} + +TEST(LogEventTest, TestEmptyAttributionChainWithPrimaryFieldFirstUidAnnotation) { + AStatsEvent* event = AStatsEvent_obtain(); + AStatsEvent_setAtomId(event, 100); + + uint32_t uids[] = {}; + const char* tags[] = {}; + + AStatsEvent_writeInt32(event, 10); + AStatsEvent_writeAttributionChain(event, uids, tags, 0); + AStatsEvent_addBoolAnnotation(event, ANNOTATION_ID_PRIMARY_FIELD_FIRST_UID, true); + + AStatsEvent_build(event); + + size_t size; + uint8_t* buf = AStatsEvent_getBuffer(event, &size); + + LogEvent logEvent(/*uid=*/1000, /*pid=*/1001); + EXPECT_FALSE(logEvent.parseBuffer(buf, size)); + + AStatsEvent_release(event); +} + +} // namespace statsd +} // namespace os +} // namespace android +#else +GTEST_LOG_(INFO) << "This test does nothing.\n"; +#endif diff --git a/statsd/tests/LogReader_test.cpp b/statsd/tests/LogReader_test.cpp new file mode 100644 index 00000000..7ce1d6a7 --- /dev/null +++ b/statsd/tests/LogReader_test.cpp @@ -0,0 +1,21 @@ +// 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 + +TEST(LogReaderTest, TestNothingAtAll) { + printf("yay!"); +} diff --git a/statsd/tests/MetricsManager_test.cpp b/statsd/tests/MetricsManager_test.cpp new file mode 100644 index 00000000..a33a1a3c --- /dev/null +++ b/statsd/tests/MetricsManager_test.cpp @@ -0,0 +1,325 @@ +// 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 +#include +#include + +#include +#include +#include + +#include "src/statsd_config.pb.h" +#include "metrics/metrics_test_helper.h" +#include "src/condition/ConditionTracker.h" +#include "src/matchers/AtomMatchingTracker.h" +#include "src/metrics/CountMetricProducer.h" +#include "src/metrics/GaugeMetricProducer.h" +#include "src/metrics/MetricProducer.h" +#include "src/metrics/ValueMetricProducer.h" +#include "src/metrics/parsing_utils/metrics_manager_util.h" +#include "src/state/StateManager.h" +#include "statsd_test_util.h" + +using namespace testing; +using android::sp; +using android::os::statsd::Predicate; +using std::map; +using std::set; +using std::unordered_map; +using std::vector; + +#ifdef __ANDROID__ + +namespace android { +namespace os { +namespace statsd { + +namespace { +const ConfigKey kConfigKey(0, 12345); + +const long timeBaseSec = 1000; + +StatsdConfig buildGoodConfig() { + StatsdConfig config; + config.set_id(12345); + + AtomMatcher* eventMatcher = config.add_atom_matcher(); + eventMatcher->set_id(StringToId("SCREEN_IS_ON")); + + SimpleAtomMatcher* simpleAtomMatcher = eventMatcher->mutable_simple_atom_matcher(); + simpleAtomMatcher->set_atom_id(2 /*SCREEN_STATE_CHANGE*/); + simpleAtomMatcher->add_field_value_matcher()->set_field( + 1 /*SCREEN_STATE_CHANGE__DISPLAY_STATE*/); + simpleAtomMatcher->mutable_field_value_matcher(0)->set_eq_int( + 2 /*SCREEN_STATE_CHANGE__DISPLAY_STATE__STATE_ON*/); + + eventMatcher = config.add_atom_matcher(); + eventMatcher->set_id(StringToId("SCREEN_IS_OFF")); + + simpleAtomMatcher = eventMatcher->mutable_simple_atom_matcher(); + simpleAtomMatcher->set_atom_id(2 /*SCREEN_STATE_CHANGE*/); + simpleAtomMatcher->add_field_value_matcher()->set_field( + 1 /*SCREEN_STATE_CHANGE__DISPLAY_STATE*/); + simpleAtomMatcher->mutable_field_value_matcher(0)->set_eq_int( + 1 /*SCREEN_STATE_CHANGE__DISPLAY_STATE__STATE_OFF*/); + + eventMatcher = config.add_atom_matcher(); + eventMatcher->set_id(StringToId("SCREEN_ON_OR_OFF")); + + AtomMatcher_Combination* combination = eventMatcher->mutable_combination(); + combination->set_operation(LogicalOperation::OR); + combination->add_matcher(StringToId("SCREEN_IS_ON")); + combination->add_matcher(StringToId("SCREEN_IS_OFF")); + + CountMetric* metric = config.add_count_metric(); + metric->set_id(3); + metric->set_what(StringToId("SCREEN_IS_ON")); + metric->set_bucket(ONE_MINUTE); + metric->mutable_dimensions_in_what()->set_field(2 /*SCREEN_STATE_CHANGE*/); + metric->mutable_dimensions_in_what()->add_child()->set_field(1); + return config; +} + +set unionSet(const vector> sets) { + set toRet; + for (const set& s : sets) { + toRet.insert(s.begin(), s.end()); + } + return toRet; +} +} // anonymous namespace + +TEST(MetricsManagerTest, TestLogSources) { + string app1 = "app1"; + set app1Uids = {1111, 11111}; + string app2 = "app2"; + set app2Uids = {2222}; + string app3 = "app3"; + set app3Uids = {3333, 1111}; + + map> pkgToUids; + pkgToUids[app1] = app1Uids; + pkgToUids[app2] = app2Uids; + pkgToUids[app3] = app3Uids; + + int32_t atom1 = 10, atom2 = 20, atom3 = 30; + sp uidMap = new StrictMock(); + EXPECT_CALL(*uidMap, getAppUid(_)) + .Times(4) + .WillRepeatedly(Invoke([&pkgToUids](const string& pkg) { + const auto& it = pkgToUids.find(pkg); + if (it != pkgToUids.end()) { + return it->second; + } + return set(); + })); + sp pullerManager = new StrictMock(); + EXPECT_CALL(*pullerManager, RegisterPullUidProvider(kConfigKey, _)).Times(1); + EXPECT_CALL(*pullerManager, UnregisterPullUidProvider(kConfigKey, _)).Times(1); + + sp anomalyAlarmMonitor; + sp periodicAlarmMonitor; + + StatsdConfig config; + config.add_allowed_log_source("AID_SYSTEM"); + config.add_allowed_log_source(app1); + config.add_default_pull_packages("AID_SYSTEM"); + config.add_default_pull_packages("AID_ROOT"); + + const set defaultPullUids = {AID_SYSTEM, AID_ROOT}; + + PullAtomPackages* pullAtomPackages = config.add_pull_atom_packages(); + pullAtomPackages->set_atom_id(atom1); + pullAtomPackages->add_packages(app1); + pullAtomPackages->add_packages(app3); + + pullAtomPackages = config.add_pull_atom_packages(); + pullAtomPackages->set_atom_id(atom2); + pullAtomPackages->add_packages(app2); + pullAtomPackages->add_packages("AID_STATSD"); + + MetricsManager metricsManager(kConfigKey, config, timeBaseSec, timeBaseSec, uidMap, + pullerManager, anomalyAlarmMonitor, periodicAlarmMonitor); + EXPECT_TRUE(metricsManager.isConfigValid()); + + EXPECT_THAT(metricsManager.mAllowedUid, ElementsAre(AID_SYSTEM)); + EXPECT_THAT(metricsManager.mAllowedPkg, ElementsAre(app1)); + EXPECT_THAT(metricsManager.mAllowedLogSources, + ContainerEq(unionSet(vector>({app1Uids, {AID_SYSTEM}})))); + EXPECT_THAT(metricsManager.mDefaultPullUids, ContainerEq(defaultPullUids)); + + vector atom1Uids = metricsManager.getPullAtomUids(atom1); + EXPECT_THAT(atom1Uids, + UnorderedElementsAreArray(unionSet({defaultPullUids, app1Uids, app3Uids}))); + + vector atom2Uids = metricsManager.getPullAtomUids(atom2); + EXPECT_THAT(atom2Uids, + UnorderedElementsAreArray(unionSet({defaultPullUids, app2Uids, {AID_STATSD}}))); + + vector atom3Uids = metricsManager.getPullAtomUids(atom3); + EXPECT_THAT(atom3Uids, UnorderedElementsAreArray(defaultPullUids)); +} + +TEST(MetricsManagerTest, TestLogSourcesOnConfigUpdate) { + string app1 = "app1"; + set app1Uids = {1111, 11111}; + string app2 = "app2"; + set app2Uids = {2222}; + string app3 = "app3"; + set app3Uids = {3333, 1111}; + + map> pkgToUids; + pkgToUids[app1] = app1Uids; + pkgToUids[app2] = app2Uids; + pkgToUids[app3] = app3Uids; + + int32_t atom1 = 10, atom2 = 20, atom3 = 30; + sp uidMap = new StrictMock(); + EXPECT_CALL(*uidMap, getAppUid(_)) + .Times(8) + .WillRepeatedly(Invoke([&pkgToUids](const string& pkg) { + const auto& it = pkgToUids.find(pkg); + if (it != pkgToUids.end()) { + return it->second; + } + return set(); + })); + sp pullerManager = new StrictMock(); + EXPECT_CALL(*pullerManager, RegisterPullUidProvider(kConfigKey, _)).Times(1); + EXPECT_CALL(*pullerManager, UnregisterPullUidProvider(kConfigKey, _)).Times(1); + + sp anomalyAlarmMonitor; + sp periodicAlarmMonitor; + + StatsdConfig config; + config.add_allowed_log_source("AID_SYSTEM"); + config.add_allowed_log_source(app1); + config.add_default_pull_packages("AID_SYSTEM"); + config.add_default_pull_packages("AID_ROOT"); + + PullAtomPackages* pullAtomPackages = config.add_pull_atom_packages(); + pullAtomPackages->set_atom_id(atom1); + pullAtomPackages->add_packages(app1); + pullAtomPackages->add_packages(app3); + + pullAtomPackages = config.add_pull_atom_packages(); + pullAtomPackages->set_atom_id(atom2); + pullAtomPackages->add_packages(app2); + pullAtomPackages->add_packages("AID_STATSD"); + + MetricsManager metricsManager(kConfigKey, config, timeBaseSec, timeBaseSec, uidMap, + pullerManager, anomalyAlarmMonitor, periodicAlarmMonitor); + EXPECT_TRUE(metricsManager.isConfigValid()); + + // Update with new allowed log sources. + StatsdConfig newConfig; + newConfig.add_allowed_log_source("AID_ROOT"); + newConfig.add_allowed_log_source(app2); + newConfig.add_default_pull_packages("AID_SYSTEM"); + newConfig.add_default_pull_packages("AID_STATSD"); + + pullAtomPackages = newConfig.add_pull_atom_packages(); + pullAtomPackages->set_atom_id(atom2); + pullAtomPackages->add_packages(app1); + pullAtomPackages->add_packages(app3); + + pullAtomPackages = newConfig.add_pull_atom_packages(); + pullAtomPackages->set_atom_id(atom3); + pullAtomPackages->add_packages(app2); + pullAtomPackages->add_packages("AID_ADB"); + + metricsManager.updateConfig(newConfig, timeBaseSec, timeBaseSec, anomalyAlarmMonitor, + periodicAlarmMonitor); + EXPECT_TRUE(metricsManager.isConfigValid()); + + EXPECT_THAT(metricsManager.mAllowedUid, ElementsAre(AID_ROOT)); + EXPECT_THAT(metricsManager.mAllowedPkg, ElementsAre(app2)); + EXPECT_THAT(metricsManager.mAllowedLogSources, + ContainerEq(unionSet(vector>({app2Uids, {AID_ROOT}})))); + const set defaultPullUids = {AID_SYSTEM, AID_STATSD}; + EXPECT_THAT(metricsManager.mDefaultPullUids, ContainerEq(defaultPullUids)); + + vector atom1Uids = metricsManager.getPullAtomUids(atom1); + EXPECT_THAT(atom1Uids, UnorderedElementsAreArray(defaultPullUids)); + + vector atom2Uids = metricsManager.getPullAtomUids(atom2); + EXPECT_THAT(atom2Uids, + UnorderedElementsAreArray(unionSet({defaultPullUids, app1Uids, app3Uids}))); + + vector atom3Uids = metricsManager.getPullAtomUids(atom3); + EXPECT_THAT(atom3Uids, + UnorderedElementsAreArray(unionSet({defaultPullUids, app2Uids, {AID_ADB}}))); +} + +TEST(MetricsManagerTest, TestCheckLogCredentialsWhitelistedAtom) { + sp uidMap; + sp pullerManager = new StatsPullerManager(); + sp anomalyAlarmMonitor; + sp periodicAlarmMonitor; + + StatsdConfig config; + config.add_whitelisted_atom_ids(3); + config.add_whitelisted_atom_ids(4); + + MetricsManager metricsManager(kConfigKey, config, timeBaseSec, timeBaseSec, uidMap, + pullerManager, anomalyAlarmMonitor, periodicAlarmMonitor); + + LogEvent event(0 /* uid */, 0 /* pid */); + CreateNoValuesLogEvent(&event, 10 /* atom id */, 0 /* timestamp */); + EXPECT_FALSE(metricsManager.checkLogCredentials(event)); + + CreateNoValuesLogEvent(&event, 3 /* atom id */, 0 /* timestamp */); + EXPECT_TRUE(metricsManager.checkLogCredentials(event)); + + CreateNoValuesLogEvent(&event, 4 /* atom id */, 0 /* timestamp */); + EXPECT_TRUE(metricsManager.checkLogCredentials(event)); +} + +TEST(MetricsManagerTest, TestWhitelistedAtomStateTracker) { + sp uidMap; + sp pullerManager = new StatsPullerManager(); + sp anomalyAlarmMonitor; + sp periodicAlarmMonitor; + + StatsdConfig config = buildGoodConfig(); + config.add_allowed_log_source("AID_SYSTEM"); + config.add_whitelisted_atom_ids(3); + config.add_whitelisted_atom_ids(4); + + State state; + state.set_id(1); + state.set_atom_id(3); + + *config.add_state() = state; + + config.mutable_count_metric(0)->add_slice_by_state(state.id()); + + StateManager::getInstance().clear(); + + MetricsManager metricsManager(kConfigKey, config, timeBaseSec, timeBaseSec, uidMap, + pullerManager, anomalyAlarmMonitor, periodicAlarmMonitor); + + EXPECT_EQ(0, StateManager::getInstance().getStateTrackersCount()); + EXPECT_FALSE(metricsManager.isConfigValid()); +} + +} // namespace statsd +} // namespace os +} // namespace android + +#else +GTEST_LOG_(INFO) << "This test does nothing.\n"; +#endif diff --git a/statsd/tests/StatsLogProcessor_test.cpp b/statsd/tests/StatsLogProcessor_test.cpp new file mode 100644 index 00000000..4ae26f71 --- /dev/null +++ b/statsd/tests/StatsLogProcessor_test.cpp @@ -0,0 +1,1886 @@ +// 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 "StatsLogProcessor.h" + +#include +#include +#include + +#include "StatsService.h" +#include "config/ConfigKey.h" +#include "src/stats_log.pb.h" +#include "src/statsd_config.pb.h" +#include "guardrail/StatsdStats.h" +#include "logd/LogEvent.h" +#include "packages/UidMap.h" +#include "statslog_statsdtest.h" +#include "storage/StorageManager.h" +#include "tests/statsd_test_util.h" + +using namespace android; +using namespace testing; +using ::ndk::SharedRefBase; +using std::shared_ptr; + +namespace android { +namespace os { +namespace statsd { + +using android::util::ProtoOutputStream; + +#ifdef __ANDROID__ + +/** + * Mock MetricsManager (ByteSize() is called). + */ +class MockMetricsManager : public MetricsManager { +public: + MockMetricsManager() + : MetricsManager(ConfigKey(1, 12345), StatsdConfig(), 1000, 1000, new UidMap(), + new StatsPullerManager(), + new AlarmMonitor(10, + [](const shared_ptr&, int64_t) {}, + [](const shared_ptr&) {}), + new AlarmMonitor(10, + [](const shared_ptr&, int64_t) {}, + [](const shared_ptr&) {})) { + } + + MOCK_METHOD0(byteSize, size_t()); + + MOCK_METHOD1(dropData, void(const int64_t dropTimeNs)); +}; + +TEST(StatsLogProcessorTest, TestRateLimitByteSize) { + sp m = new UidMap(); + sp pullerManager = new StatsPullerManager(); + sp anomalyAlarmMonitor; + sp periodicAlarmMonitor; + // Construct the processor with a no-op sendBroadcast function that does nothing. + StatsLogProcessor p(m, pullerManager, anomalyAlarmMonitor, periodicAlarmMonitor, 0, + [](const ConfigKey& key) { return true; }, + [](const int&, const vector&) {return true;}); + + MockMetricsManager mockMetricsManager; + + ConfigKey key(100, 12345); + // Expect only the first flush to trigger a check for byte size since the last two are + // rate-limited. + EXPECT_CALL(mockMetricsManager, byteSize()).Times(1); + p.flushIfNecessaryLocked(key, mockMetricsManager); + p.flushIfNecessaryLocked(key, mockMetricsManager); + p.flushIfNecessaryLocked(key, mockMetricsManager); +} + +TEST(StatsLogProcessorTest, TestRateLimitBroadcast) { + sp m = new UidMap(); + sp pullerManager = new StatsPullerManager(); + sp anomalyAlarmMonitor; + sp subscriberAlarmMonitor; + int broadcastCount = 0; + StatsLogProcessor p(m, pullerManager, anomalyAlarmMonitor, subscriberAlarmMonitor, 0, + [&broadcastCount](const ConfigKey& key) { + broadcastCount++; + return true; + }, + [](const int&, const vector&) {return true;}); + + MockMetricsManager mockMetricsManager; + + ConfigKey key(100, 12345); + EXPECT_CALL(mockMetricsManager, byteSize()) + .Times(1) + .WillRepeatedly(::testing::Return(int( + StatsdStats::kMaxMetricsBytesPerConfig * .95))); + + // Expect only one broadcast despite always returning a size that should trigger broadcast. + p.flushIfNecessaryLocked(key, mockMetricsManager); + EXPECT_EQ(1, broadcastCount); + + // b/73089712 + // This next call to flush should not trigger a broadcast. + // p.mLastByteSizeTimes.clear(); // Force another check for byte size. + // p.flushIfNecessaryLocked(2, key, mockMetricsManager); + // EXPECT_EQ(1, broadcastCount); +} + +TEST(StatsLogProcessorTest, TestDropWhenByteSizeTooLarge) { + sp m = new UidMap(); + sp pullerManager = new StatsPullerManager(); + sp anomalyAlarmMonitor; + sp subscriberAlarmMonitor; + int broadcastCount = 0; + StatsLogProcessor p(m, pullerManager, anomalyAlarmMonitor, subscriberAlarmMonitor, 0, + [&broadcastCount](const ConfigKey& key) { + broadcastCount++; + return true; + }, + [](const int&, const vector&) {return true;}); + + MockMetricsManager mockMetricsManager; + + ConfigKey key(100, 12345); + EXPECT_CALL(mockMetricsManager, byteSize()) + .Times(1) + .WillRepeatedly(::testing::Return(int(StatsdStats::kMaxMetricsBytesPerConfig * 1.2))); + + EXPECT_CALL(mockMetricsManager, dropData(_)).Times(1); + + // Expect to call the onDumpReport and skip the broadcast. + p.flushIfNecessaryLocked(key, mockMetricsManager); + EXPECT_EQ(0, broadcastCount); +} + +StatsdConfig MakeConfig(bool includeMetric) { + StatsdConfig config; + config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root. + + if (includeMetric) { + auto appCrashMatcher = CreateProcessCrashAtomMatcher(); + *config.add_atom_matcher() = appCrashMatcher; + auto countMetric = config.add_count_metric(); + countMetric->set_id(StringToId("AppCrashes")); + countMetric->set_what(appCrashMatcher.id()); + countMetric->set_bucket(FIVE_MINUTES); + } + return config; +} + +TEST(StatsLogProcessorTest, TestUidMapHasSnapshot) { + // Setup simple config key corresponding to empty config. + sp m = new UidMap(); + sp pullerManager = new StatsPullerManager(); + m->updateMap(1, {1, 2}, {1, 2}, {String16("v1"), String16("v2")}, + {String16("p1"), String16("p2")}, {String16(""), String16("")}); + sp anomalyAlarmMonitor; + sp subscriberAlarmMonitor; + int broadcastCount = 0; + StatsLogProcessor p(m, pullerManager, anomalyAlarmMonitor, subscriberAlarmMonitor, 0, + [&broadcastCount](const ConfigKey& key) { + broadcastCount++; + return true; + }, + [](const int&, const vector&) {return true;}); + ConfigKey key(3, 4); + StatsdConfig config = MakeConfig(true); + p.OnConfigUpdated(0, key, config); + + // Expect to get no metrics, but snapshot specified above in uidmap. + vector bytes; + p.onDumpReport(key, 1, false, true, ADB_DUMP, FAST, &bytes); + + ConfigMetricsReportList output; + output.ParseFromArray(bytes.data(), bytes.size()); + EXPECT_TRUE(output.reports_size() > 0); + auto uidmap = output.reports(0).uid_map(); + EXPECT_TRUE(uidmap.snapshots_size() > 0); + ASSERT_EQ(2, uidmap.snapshots(0).package_info_size()); +} + +TEST(StatsLogProcessorTest, TestEmptyConfigHasNoUidMap) { + // Setup simple config key corresponding to empty config. + sp m = new UidMap(); + sp pullerManager = new StatsPullerManager(); + m->updateMap(1, {1, 2}, {1, 2}, {String16("v1"), String16("v2")}, + {String16("p1"), String16("p2")}, {String16(""), String16("")}); + sp anomalyAlarmMonitor; + sp subscriberAlarmMonitor; + int broadcastCount = 0; + StatsLogProcessor p(m, pullerManager, anomalyAlarmMonitor, subscriberAlarmMonitor, 0, + [&broadcastCount](const ConfigKey& key) { + broadcastCount++; + return true; + }, + [](const int&, const vector&) {return true;}); + ConfigKey key(3, 4); + StatsdConfig config = MakeConfig(false); + p.OnConfigUpdated(0, key, config); + + // Expect to get no metrics, but snapshot specified above in uidmap. + vector bytes; + p.onDumpReport(key, 1, false, true, ADB_DUMP, FAST, &bytes); + + ConfigMetricsReportList output; + output.ParseFromArray(bytes.data(), bytes.size()); + EXPECT_TRUE(output.reports_size() > 0); + EXPECT_FALSE(output.reports(0).has_uid_map()); +} + +TEST(StatsLogProcessorTest, TestReportIncludesSubConfig) { + // Setup simple config key corresponding to empty config. + sp m = new UidMap(); + sp pullerManager = new StatsPullerManager(); + sp anomalyAlarmMonitor; + sp subscriberAlarmMonitor; + int broadcastCount = 0; + StatsLogProcessor p(m, pullerManager, anomalyAlarmMonitor, subscriberAlarmMonitor, 0, + [&broadcastCount](const ConfigKey& key) { + broadcastCount++; + return true; + }, + [](const int&, const vector&) {return true;}); + ConfigKey key(3, 4); + StatsdConfig config; + auto annotation = config.add_annotation(); + annotation->set_field_int64(1); + annotation->set_field_int32(2); + config.add_allowed_log_source("AID_ROOT"); + p.OnConfigUpdated(1, key, config); + + // Expect to get no metrics, but snapshot specified above in uidmap. + vector bytes; + p.onDumpReport(key, 1, false, true, ADB_DUMP, FAST, &bytes); + + ConfigMetricsReportList output; + output.ParseFromArray(bytes.data(), bytes.size()); + EXPECT_TRUE(output.reports_size() > 0); + auto report = output.reports(0); + ASSERT_EQ(1, report.annotation_size()); + EXPECT_EQ(1, report.annotation(0).field_int64()); + EXPECT_EQ(2, report.annotation(0).field_int32()); +} + +TEST(StatsLogProcessorTest, TestOnDumpReportEraseData) { + // Setup a simple config. + StatsdConfig config; + config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root. + auto wakelockAcquireMatcher = CreateAcquireWakelockAtomMatcher(); + *config.add_atom_matcher() = wakelockAcquireMatcher; + + auto countMetric = config.add_count_metric(); + countMetric->set_id(123456); + countMetric->set_what(wakelockAcquireMatcher.id()); + countMetric->set_bucket(FIVE_MINUTES); + + ConfigKey cfgKey; + sp processor = CreateStatsLogProcessor(1, 1, config, cfgKey); + + std::vector attributionUids = {111}; + std::vector attributionTags = {"App1"}; + std::unique_ptr event = + CreateAcquireWakelockEvent(2 /*timestamp*/, attributionUids, attributionTags, "wl1"); + processor->OnLogEvent(event.get()); + + vector bytes; + ConfigMetricsReportList output; + + // Dump report WITHOUT erasing data. + processor->onDumpReport(cfgKey, 3, true, false /* Do NOT erase data. */, ADB_DUMP, FAST, + &bytes); + output.ParseFromArray(bytes.data(), bytes.size()); + ASSERT_EQ(output.reports_size(), 1); + ASSERT_EQ(output.reports(0).metrics_size(), 1); + ASSERT_EQ(output.reports(0).metrics(0).count_metrics().data_size(), 1); + + // Dump report WITH erasing data. There should be data since we didn't previously erase it. + processor->onDumpReport(cfgKey, 4, true, true /* DO erase data. */, ADB_DUMP, FAST, &bytes); + output.ParseFromArray(bytes.data(), bytes.size()); + ASSERT_EQ(output.reports_size(), 1); + ASSERT_EQ(output.reports(0).metrics_size(), 1); + ASSERT_EQ(output.reports(0).metrics(0).count_metrics().data_size(), 1); + + // Dump report again. There should be no data since we erased it. + processor->onDumpReport(cfgKey, 5, true, true /* DO erase data. */, ADB_DUMP, FAST, &bytes); + output.ParseFromArray(bytes.data(), bytes.size()); + // We don't care whether statsd has a report, as long as it has no count metrics in it. + bool noData = output.reports_size() == 0 || output.reports(0).metrics_size() == 0 || + output.reports(0).metrics(0).count_metrics().data_size() == 0; + EXPECT_TRUE(noData); +} + +TEST(StatsLogProcessorTest, TestPullUidProviderSetOnConfigUpdate) { + // Setup simple config key corresponding to empty config. + sp m = new UidMap(); + sp pullerManager = new StatsPullerManager(); + sp anomalyAlarmMonitor; + sp subscriberAlarmMonitor; + StatsLogProcessor p( + m, pullerManager, anomalyAlarmMonitor, subscriberAlarmMonitor, 0, + [](const ConfigKey& key) { return true; }, + [](const int&, const vector&) { return true; }); + ConfigKey key(3, 4); + StatsdConfig config = MakeConfig(false); + p.OnConfigUpdated(0, key, config); + EXPECT_NE(pullerManager->mPullUidProviders.find(key), pullerManager->mPullUidProviders.end()); + + config.add_default_pull_packages("AID_STATSD"); + p.OnConfigUpdated(5, key, config); + EXPECT_NE(pullerManager->mPullUidProviders.find(key), pullerManager->mPullUidProviders.end()); + + p.OnConfigRemoved(key); + EXPECT_EQ(pullerManager->mPullUidProviders.find(key), pullerManager->mPullUidProviders.end()); +} + +TEST(StatsLogProcessorTest, InvalidConfigRemoved) { + // Setup simple config key corresponding to empty config. + StatsdStats::getInstance().reset(); + sp m = new UidMap(); + sp pullerManager = new StatsPullerManager(); + m->updateMap(1, {1, 2}, {1, 2}, {String16("v1"), String16("v2")}, + {String16("p1"), String16("p2")}, {String16(""), String16("")}); + sp anomalyAlarmMonitor; + sp subscriberAlarmMonitor; + StatsLogProcessor p(m, pullerManager, anomalyAlarmMonitor, subscriberAlarmMonitor, 0, + [](const ConfigKey& key) { return true; }, + [](const int&, const vector&) {return true;}); + ConfigKey key(3, 4); + StatsdConfig config = MakeConfig(true); + p.OnConfigUpdated(0, key, config); + EXPECT_EQ(1, p.mMetricsManagers.size()); + EXPECT_NE(p.mMetricsManagers.find(key), p.mMetricsManagers.end()); + // Cannot assert the size of mConfigStats since it is static and does not get cleared on reset. + EXPECT_NE(StatsdStats::getInstance().mConfigStats.end(), + StatsdStats::getInstance().mConfigStats.find(key)); + EXPECT_EQ(0, StatsdStats::getInstance().mIceBox.size()); + + StatsdConfig invalidConfig = MakeConfig(true); + invalidConfig.clear_allowed_log_source(); + p.OnConfigUpdated(0, key, invalidConfig); + EXPECT_EQ(0, p.mMetricsManagers.size()); + // The current configs should not contain the invalid config. + EXPECT_EQ(StatsdStats::getInstance().mConfigStats.end(), + StatsdStats::getInstance().mConfigStats.find(key)); + // Both "config" and "invalidConfig" should be in the icebox. + EXPECT_EQ(2, StatsdStats::getInstance().mIceBox.size()); + +} + + +TEST(StatsLogProcessorTest, TestActiveConfigMetricDiskWriteRead) { + int uid = 1111; + + // Setup a simple config, no activation + StatsdConfig config1; + int64_t cfgId1 = 12341; + config1.set_id(cfgId1); + config1.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root. + auto wakelockAcquireMatcher = CreateAcquireWakelockAtomMatcher(); + *config1.add_atom_matcher() = wakelockAcquireMatcher; + + long metricId1 = 1234561; + long metricId2 = 1234562; + auto countMetric1 = config1.add_count_metric(); + countMetric1->set_id(metricId1); + countMetric1->set_what(wakelockAcquireMatcher.id()); + countMetric1->set_bucket(FIVE_MINUTES); + + auto countMetric2 = config1.add_count_metric(); + countMetric2->set_id(metricId2); + countMetric2->set_what(wakelockAcquireMatcher.id()); + countMetric2->set_bucket(FIVE_MINUTES); + + ConfigKey cfgKey1(uid, cfgId1); + + // Add another config, with two metrics, one with activation + StatsdConfig config2; + int64_t cfgId2 = 12342; + config2.set_id(cfgId2); + config2.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root. + *config2.add_atom_matcher() = wakelockAcquireMatcher; + + long metricId3 = 1234561; + long metricId4 = 1234562; + + auto countMetric3 = config2.add_count_metric(); + countMetric3->set_id(metricId3); + countMetric3->set_what(wakelockAcquireMatcher.id()); + countMetric3->set_bucket(FIVE_MINUTES); + + auto countMetric4 = config2.add_count_metric(); + countMetric4->set_id(metricId4); + countMetric4->set_what(wakelockAcquireMatcher.id()); + countMetric4->set_bucket(FIVE_MINUTES); + + auto metric3Activation = config2.add_metric_activation(); + metric3Activation->set_metric_id(metricId3); + metric3Activation->set_activation_type(ACTIVATE_IMMEDIATELY); + auto metric3ActivationTrigger = metric3Activation->add_event_activation(); + metric3ActivationTrigger->set_atom_matcher_id(wakelockAcquireMatcher.id()); + metric3ActivationTrigger->set_ttl_seconds(100); + + ConfigKey cfgKey2(uid, cfgId2); + + // Add another config, with two metrics, both with activations + StatsdConfig config3; + int64_t cfgId3 = 12343; + config3.set_id(cfgId3); + config3.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root. + *config3.add_atom_matcher() = wakelockAcquireMatcher; + + long metricId5 = 1234565; + long metricId6 = 1234566; + auto countMetric5 = config3.add_count_metric(); + countMetric5->set_id(metricId5); + countMetric5->set_what(wakelockAcquireMatcher.id()); + countMetric5->set_bucket(FIVE_MINUTES); + + auto countMetric6 = config3.add_count_metric(); + countMetric6->set_id(metricId6); + countMetric6->set_what(wakelockAcquireMatcher.id()); + countMetric6->set_bucket(FIVE_MINUTES); + + auto metric5Activation = config3.add_metric_activation(); + metric5Activation->set_metric_id(metricId5); + metric5Activation->set_activation_type(ACTIVATE_IMMEDIATELY); + auto metric5ActivationTrigger = metric5Activation->add_event_activation(); + metric5ActivationTrigger->set_atom_matcher_id(wakelockAcquireMatcher.id()); + metric5ActivationTrigger->set_ttl_seconds(100); + + auto metric6Activation = config3.add_metric_activation(); + metric6Activation->set_metric_id(metricId6); + metric6Activation->set_activation_type(ACTIVATE_IMMEDIATELY); + auto metric6ActivationTrigger = metric6Activation->add_event_activation(); + metric6ActivationTrigger->set_atom_matcher_id(wakelockAcquireMatcher.id()); + metric6ActivationTrigger->set_ttl_seconds(200); + + ConfigKey cfgKey3(uid, cfgId3); + + sp m = new UidMap(); + sp pullerManager = new StatsPullerManager(); + sp anomalyAlarmMonitor; + sp subscriberAlarmMonitor; + vector activeConfigsBroadcast; + + long timeBase1 = 1; + int broadcastCount = 0; + StatsLogProcessor processor( + m, pullerManager, anomalyAlarmMonitor, subscriberAlarmMonitor, timeBase1, + [](const ConfigKey& key) { return true; }, + [&uid, &broadcastCount, &activeConfigsBroadcast](const int& broadcastUid, + const vector& activeConfigs) { + broadcastCount++; + EXPECT_EQ(broadcastUid, uid); + activeConfigsBroadcast.clear(); + activeConfigsBroadcast.insert(activeConfigsBroadcast.end(), activeConfigs.begin(), + activeConfigs.end()); + return true; + }); + + processor.OnConfigUpdated(1, cfgKey1, config1); + processor.OnConfigUpdated(2, cfgKey2, config2); + processor.OnConfigUpdated(3, cfgKey3, config3); + + ASSERT_EQ(3, processor.mMetricsManagers.size()); + + // Expect the first config and both metrics in it to be active. + auto it = processor.mMetricsManagers.find(cfgKey1); + EXPECT_TRUE(it != processor.mMetricsManagers.end()); + auto& metricsManager1 = it->second; + EXPECT_TRUE(metricsManager1->isActive()); + + auto metricIt = metricsManager1->mAllMetricProducers.begin(); + for (; metricIt != metricsManager1->mAllMetricProducers.end(); metricIt++) { + if ((*metricIt)->getMetricId() == metricId1) { + break; + } + } + EXPECT_TRUE(metricIt != metricsManager1->mAllMetricProducers.end()); + auto& metricProducer1 = *metricIt; + EXPECT_TRUE(metricProducer1->isActive()); + + metricIt = metricsManager1->mAllMetricProducers.begin(); + for (; metricIt != metricsManager1->mAllMetricProducers.end(); metricIt++) { + if ((*metricIt)->getMetricId() == metricId2) { + break; + } + } + EXPECT_TRUE(metricIt != metricsManager1->mAllMetricProducers.end()); + auto& metricProducer2 = *metricIt; + EXPECT_TRUE(metricProducer2->isActive()); + + // Expect config 2 to be active. Metric 3 shouldn't be active, metric 4 should be active. + it = processor.mMetricsManagers.find(cfgKey2); + EXPECT_TRUE(it != processor.mMetricsManagers.end()); + auto& metricsManager2 = it->second; + EXPECT_TRUE(metricsManager2->isActive()); + + metricIt = metricsManager2->mAllMetricProducers.begin(); + for (; metricIt != metricsManager2->mAllMetricProducers.end(); metricIt++) { + if ((*metricIt)->getMetricId() == metricId3) { + break; + } + } + EXPECT_TRUE(metricIt != metricsManager2->mAllMetricProducers.end()); + auto& metricProducer3 = *metricIt; + EXPECT_FALSE(metricProducer3->isActive()); + + metricIt = metricsManager2->mAllMetricProducers.begin(); + for (; metricIt != metricsManager2->mAllMetricProducers.end(); metricIt++) { + if ((*metricIt)->getMetricId() == metricId4) { + break; + } + } + EXPECT_TRUE(metricIt != metricsManager2->mAllMetricProducers.end()); + auto& metricProducer4 = *metricIt; + EXPECT_TRUE(metricProducer4->isActive()); + + // Expect the third config and both metrics in it to be inactive. + it = processor.mMetricsManagers.find(cfgKey3); + EXPECT_TRUE(it != processor.mMetricsManagers.end()); + auto& metricsManager3 = it->second; + EXPECT_FALSE(metricsManager3->isActive()); + + metricIt = metricsManager3->mAllMetricProducers.begin(); + for (; metricIt != metricsManager2->mAllMetricProducers.end(); metricIt++) { + if ((*metricIt)->getMetricId() == metricId5) { + break; + } + } + EXPECT_TRUE(metricIt != metricsManager3->mAllMetricProducers.end()); + auto& metricProducer5 = *metricIt; + EXPECT_FALSE(metricProducer5->isActive()); + + metricIt = metricsManager3->mAllMetricProducers.begin(); + for (; metricIt != metricsManager3->mAllMetricProducers.end(); metricIt++) { + if ((*metricIt)->getMetricId() == metricId6) { + break; + } + } + EXPECT_TRUE(metricIt != metricsManager3->mAllMetricProducers.end()); + auto& metricProducer6 = *metricIt; + EXPECT_FALSE(metricProducer6->isActive()); + + // No broadcast for active configs should have happened yet. + EXPECT_EQ(broadcastCount, 0); + + // Activate all 3 metrics that were not active. + std::vector attributionUids = {111}; + std::vector attributionTags = {"App1"}; + std::unique_ptr event = + CreateAcquireWakelockEvent(timeBase1 + 100, attributionUids, attributionTags, "wl1"); + processor.OnLogEvent(event.get()); + + // Assert that all 3 configs are active. + EXPECT_TRUE(metricsManager1->isActive()); + EXPECT_TRUE(metricsManager2->isActive()); + EXPECT_TRUE(metricsManager3->isActive()); + + // A broadcast should have happened, and all 3 configs should be active in the broadcast. + EXPECT_EQ(broadcastCount, 1); + ASSERT_EQ(activeConfigsBroadcast.size(), 3); + EXPECT_TRUE(std::find(activeConfigsBroadcast.begin(), activeConfigsBroadcast.end(), cfgId1) != + activeConfigsBroadcast.end()); + EXPECT_TRUE(std::find(activeConfigsBroadcast.begin(), activeConfigsBroadcast.end(), cfgId2) != + activeConfigsBroadcast.end()); + EXPECT_TRUE(std::find(activeConfigsBroadcast.begin(), activeConfigsBroadcast.end(), cfgId3) != + activeConfigsBroadcast.end()); + + // When we shut down, metrics 3 & 5 have 100ns remaining, metric 6 has 100s + 100ns. + int64_t shutDownTime = timeBase1 + 100 * NS_PER_SEC; + processor.SaveActiveConfigsToDisk(shutDownTime); + const int64_t ttl3 = event->GetElapsedTimestampNs() + + metric3ActivationTrigger->ttl_seconds() * NS_PER_SEC - shutDownTime; + const int64_t ttl5 = event->GetElapsedTimestampNs() + + metric5ActivationTrigger->ttl_seconds() * NS_PER_SEC - shutDownTime; + const int64_t ttl6 = event->GetElapsedTimestampNs() + + metric6ActivationTrigger->ttl_seconds() * NS_PER_SEC - shutDownTime; + + // Create a second StatsLogProcessor and push the same 3 configs. + long timeBase2 = 1000; + sp processor2 = + CreateStatsLogProcessor(timeBase2, timeBase2, config1, cfgKey1); + processor2->OnConfigUpdated(timeBase2, cfgKey2, config2); + processor2->OnConfigUpdated(timeBase2, cfgKey3, config3); + + ASSERT_EQ(3, processor2->mMetricsManagers.size()); + + // First config and both metrics are active. + it = processor2->mMetricsManagers.find(cfgKey1); + EXPECT_TRUE(it != processor2->mMetricsManagers.end()); + auto& metricsManager1001 = it->second; + EXPECT_TRUE(metricsManager1001->isActive()); + + metricIt = metricsManager1001->mAllMetricProducers.begin(); + for (; metricIt != metricsManager1001->mAllMetricProducers.end(); metricIt++) { + if ((*metricIt)->getMetricId() == metricId1) { + break; + } + } + EXPECT_TRUE(metricIt != metricsManager1001->mAllMetricProducers.end()); + auto& metricProducer1001 = *metricIt; + EXPECT_TRUE(metricProducer1001->isActive()); + + metricIt = metricsManager1001->mAllMetricProducers.begin(); + for (; metricIt != metricsManager1001->mAllMetricProducers.end(); metricIt++) { + if ((*metricIt)->getMetricId() == metricId2) { + break; + } + } + EXPECT_TRUE(metricIt != metricsManager1001->mAllMetricProducers.end()); + auto& metricProducer1002 = *metricIt; + EXPECT_TRUE(metricProducer1002->isActive()); + + // Second config is active. Metric 3 is inactive, metric 4 is active. + it = processor2->mMetricsManagers.find(cfgKey2); + EXPECT_TRUE(it != processor2->mMetricsManagers.end()); + auto& metricsManager1002 = it->second; + EXPECT_TRUE(metricsManager1002->isActive()); + + metricIt = metricsManager1002->mAllMetricProducers.begin(); + for (; metricIt != metricsManager1002->mAllMetricProducers.end(); metricIt++) { + if ((*metricIt)->getMetricId() == metricId3) { + break; + } + } + EXPECT_TRUE(metricIt != metricsManager1002->mAllMetricProducers.end()); + auto& metricProducer1003 = *metricIt; + EXPECT_FALSE(metricProducer1003->isActive()); + + metricIt = metricsManager1002->mAllMetricProducers.begin(); + for (; metricIt != metricsManager1002->mAllMetricProducers.end(); metricIt++) { + if ((*metricIt)->getMetricId() == metricId4) { + break; + } + } + EXPECT_TRUE(metricIt != metricsManager1002->mAllMetricProducers.end()); + auto& metricProducer1004 = *metricIt; + EXPECT_TRUE(metricProducer1004->isActive()); + + // Config 3 is inactive. both metrics are inactive. + it = processor2->mMetricsManagers.find(cfgKey3); + EXPECT_TRUE(it != processor2->mMetricsManagers.end()); + auto& metricsManager1003 = it->second; + EXPECT_FALSE(metricsManager1003->isActive()); + ASSERT_EQ(2, metricsManager1003->mAllMetricProducers.size()); + + metricIt = metricsManager1003->mAllMetricProducers.begin(); + for (; metricIt != metricsManager1002->mAllMetricProducers.end(); metricIt++) { + if ((*metricIt)->getMetricId() == metricId5) { + break; + } + } + EXPECT_TRUE(metricIt != metricsManager1003->mAllMetricProducers.end()); + auto& metricProducer1005 = *metricIt; + EXPECT_FALSE(metricProducer1005->isActive()); + + metricIt = metricsManager1003->mAllMetricProducers.begin(); + for (; metricIt != metricsManager1003->mAllMetricProducers.end(); metricIt++) { + if ((*metricIt)->getMetricId() == metricId6) { + break; + } + } + EXPECT_TRUE(metricIt != metricsManager1003->mAllMetricProducers.end()); + auto& metricProducer1006 = *metricIt; + EXPECT_FALSE(metricProducer1006->isActive()); + + // Assert that all 3 metrics with activation are inactive and that the ttls were properly set. + EXPECT_FALSE(metricProducer1003->isActive()); + const auto& activation1003 = metricProducer1003->mEventActivationMap.begin()->second; + EXPECT_EQ(100 * NS_PER_SEC, activation1003->ttl_ns); + EXPECT_EQ(0, activation1003->start_ns); + EXPECT_FALSE(metricProducer1005->isActive()); + const auto& activation1005 = metricProducer1005->mEventActivationMap.begin()->second; + EXPECT_EQ(100 * NS_PER_SEC, activation1005->ttl_ns); + EXPECT_EQ(0, activation1005->start_ns); + EXPECT_FALSE(metricProducer1006->isActive()); + const auto& activation1006 = metricProducer1006->mEventActivationMap.begin()->second; + EXPECT_EQ(200 * NS_PER_SEC, activation1006->ttl_ns); + EXPECT_EQ(0, activation1006->start_ns); + + processor2->LoadActiveConfigsFromDisk(); + + // After loading activations from disk, assert that all 3 metrics are active. + EXPECT_TRUE(metricProducer1003->isActive()); + EXPECT_EQ(timeBase2 + ttl3 - activation1003->ttl_ns, activation1003->start_ns); + EXPECT_TRUE(metricProducer1005->isActive()); + EXPECT_EQ(timeBase2 + ttl5 - activation1005->ttl_ns, activation1005->start_ns); + EXPECT_TRUE(metricProducer1006->isActive()); + EXPECT_EQ(timeBase2 + ttl6 - activation1006->ttl_ns, activation1003->start_ns); + + // Make sure no more broadcasts have happened. + EXPECT_EQ(broadcastCount, 1); +} + +TEST(StatsLogProcessorTest, TestActivationOnBoot) { + int uid = 1111; + + StatsdConfig config1; + config1.set_id(12341); + config1.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root. + auto wakelockAcquireMatcher = CreateAcquireWakelockAtomMatcher(); + *config1.add_atom_matcher() = wakelockAcquireMatcher; + + long metricId1 = 1234561; + long metricId2 = 1234562; + auto countMetric1 = config1.add_count_metric(); + countMetric1->set_id(metricId1); + countMetric1->set_what(wakelockAcquireMatcher.id()); + countMetric1->set_bucket(FIVE_MINUTES); + + auto countMetric2 = config1.add_count_metric(); + countMetric2->set_id(metricId2); + countMetric2->set_what(wakelockAcquireMatcher.id()); + countMetric2->set_bucket(FIVE_MINUTES); + + auto metric1Activation = config1.add_metric_activation(); + metric1Activation->set_metric_id(metricId1); + metric1Activation->set_activation_type(ACTIVATE_ON_BOOT); + auto metric1ActivationTrigger = metric1Activation->add_event_activation(); + metric1ActivationTrigger->set_atom_matcher_id(wakelockAcquireMatcher.id()); + metric1ActivationTrigger->set_ttl_seconds(100); + + ConfigKey cfgKey1(uid, 12341); + long timeBase1 = 1; + sp processor = + CreateStatsLogProcessor(timeBase1, timeBase1, config1, cfgKey1); + + ASSERT_EQ(1, processor->mMetricsManagers.size()); + auto it = processor->mMetricsManagers.find(cfgKey1); + EXPECT_TRUE(it != processor->mMetricsManagers.end()); + auto& metricsManager1 = it->second; + EXPECT_TRUE(metricsManager1->isActive()); + + auto metricIt = metricsManager1->mAllMetricProducers.begin(); + for (; metricIt != metricsManager1->mAllMetricProducers.end(); metricIt++) { + if ((*metricIt)->getMetricId() == metricId1) { + break; + } + } + EXPECT_TRUE(metricIt != metricsManager1->mAllMetricProducers.end()); + auto& metricProducer1 = *metricIt; + EXPECT_FALSE(metricProducer1->isActive()); + + metricIt = metricsManager1->mAllMetricProducers.begin(); + for (; metricIt != metricsManager1->mAllMetricProducers.end(); metricIt++) { + if ((*metricIt)->getMetricId() == metricId2) { + break; + } + } + EXPECT_TRUE(metricIt != metricsManager1->mAllMetricProducers.end()); + auto& metricProducer2 = *metricIt; + EXPECT_TRUE(metricProducer2->isActive()); + + const auto& activation1 = metricProducer1->mEventActivationMap.begin()->second; + EXPECT_EQ(100 * NS_PER_SEC, activation1->ttl_ns); + EXPECT_EQ(0, activation1->start_ns); + EXPECT_EQ(kNotActive, activation1->state); + + std::vector attributionUids = {111}; + std::vector attributionTags = {"App1"}; + std::unique_ptr event = + CreateAcquireWakelockEvent(timeBase1 + 100, attributionUids, attributionTags, "wl1"); + processor->OnLogEvent(event.get()); + + EXPECT_FALSE(metricProducer1->isActive()); + EXPECT_EQ(0, activation1->start_ns); + EXPECT_EQ(kActiveOnBoot, activation1->state); + + int64_t shutDownTime = timeBase1 + 100 * NS_PER_SEC; + processor->SaveActiveConfigsToDisk(shutDownTime); + EXPECT_FALSE(metricProducer1->isActive()); + const int64_t ttl1 = metric1ActivationTrigger->ttl_seconds() * NS_PER_SEC; + + long timeBase2 = 1000; + sp processor2 = + CreateStatsLogProcessor(timeBase2, timeBase2, config1, cfgKey1); + + ASSERT_EQ(1, processor2->mMetricsManagers.size()); + it = processor2->mMetricsManagers.find(cfgKey1); + EXPECT_TRUE(it != processor2->mMetricsManagers.end()); + auto& metricsManager1001 = it->second; + EXPECT_TRUE(metricsManager1001->isActive()); + + metricIt = metricsManager1001->mAllMetricProducers.begin(); + for (; metricIt != metricsManager1001->mAllMetricProducers.end(); metricIt++) { + if ((*metricIt)->getMetricId() == metricId1) { + break; + } + } + EXPECT_TRUE(metricIt != metricsManager1001->mAllMetricProducers.end()); + auto& metricProducer1001 = *metricIt; + EXPECT_FALSE(metricProducer1001->isActive()); + + metricIt = metricsManager1001->mAllMetricProducers.begin(); + for (; metricIt != metricsManager1001->mAllMetricProducers.end(); metricIt++) { + if ((*metricIt)->getMetricId() == metricId2) { + break; + } + } + EXPECT_TRUE(metricIt != metricsManager1001->mAllMetricProducers.end()); + auto& metricProducer1002 = *metricIt; + EXPECT_TRUE(metricProducer1002->isActive()); + + const auto& activation1001 = metricProducer1001->mEventActivationMap.begin()->second; + EXPECT_EQ(100 * NS_PER_SEC, activation1001->ttl_ns); + EXPECT_EQ(0, activation1001->start_ns); + EXPECT_EQ(kNotActive, activation1001->state); + + processor2->LoadActiveConfigsFromDisk(); + + EXPECT_TRUE(metricProducer1001->isActive()); + EXPECT_EQ(timeBase2 + ttl1 - activation1001->ttl_ns, activation1001->start_ns); + EXPECT_EQ(kActive, activation1001->state); +} + +TEST(StatsLogProcessorTest, TestActivationOnBootMultipleActivations) { + int uid = 1111; + + // Create config with 2 metrics: + // Metric 1: Activate on boot with 2 activations + // Metric 2: Always active + StatsdConfig config1; + config1.set_id(12341); + config1.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root. + auto wakelockAcquireMatcher = CreateAcquireWakelockAtomMatcher(); + auto screenOnMatcher = CreateScreenTurnedOnAtomMatcher(); + *config1.add_atom_matcher() = wakelockAcquireMatcher; + *config1.add_atom_matcher() = screenOnMatcher; + + long metricId1 = 1234561; + long metricId2 = 1234562; + + auto countMetric1 = config1.add_count_metric(); + countMetric1->set_id(metricId1); + countMetric1->set_what(wakelockAcquireMatcher.id()); + countMetric1->set_bucket(FIVE_MINUTES); + + auto countMetric2 = config1.add_count_metric(); + countMetric2->set_id(metricId2); + countMetric2->set_what(wakelockAcquireMatcher.id()); + countMetric2->set_bucket(FIVE_MINUTES); + + auto metric1Activation = config1.add_metric_activation(); + metric1Activation->set_metric_id(metricId1); + metric1Activation->set_activation_type(ACTIVATE_ON_BOOT); + auto metric1ActivationTrigger1 = metric1Activation->add_event_activation(); + metric1ActivationTrigger1->set_atom_matcher_id(wakelockAcquireMatcher.id()); + metric1ActivationTrigger1->set_ttl_seconds(100); + auto metric1ActivationTrigger2 = metric1Activation->add_event_activation(); + metric1ActivationTrigger2->set_atom_matcher_id(screenOnMatcher.id()); + metric1ActivationTrigger2->set_ttl_seconds(200); + + ConfigKey cfgKey1(uid, 12341); + long timeBase1 = 1; + sp processor = + CreateStatsLogProcessor(timeBase1, timeBase1, config1, cfgKey1); + + // Metric 1 is not active. + // Metric 2 is active. + // {{{--------------------------------------------------------------------------- + ASSERT_EQ(1, processor->mMetricsManagers.size()); + auto it = processor->mMetricsManagers.find(cfgKey1); + EXPECT_TRUE(it != processor->mMetricsManagers.end()); + auto& metricsManager1 = it->second; + EXPECT_TRUE(metricsManager1->isActive()); + + auto metricIt = metricsManager1->mAllMetricProducers.begin(); + for (; metricIt != metricsManager1->mAllMetricProducers.end(); metricIt++) { + if ((*metricIt)->getMetricId() == metricId1) { + break; + } + } + EXPECT_TRUE(metricIt != metricsManager1->mAllMetricProducers.end()); + auto& metricProducer1 = *metricIt; + EXPECT_FALSE(metricProducer1->isActive()); + + metricIt = metricsManager1->mAllMetricProducers.begin(); + for (; metricIt != metricsManager1->mAllMetricProducers.end(); metricIt++) { + if ((*metricIt)->getMetricId() == metricId2) { + break; + } + } + EXPECT_TRUE(metricIt != metricsManager1->mAllMetricProducers.end()); + auto& metricProducer2 = *metricIt; + EXPECT_TRUE(metricProducer2->isActive()); + + int i = 0; + for (; i < metricsManager1->mAllAtomMatchingTrackers.size(); i++) { + if (metricsManager1->mAllAtomMatchingTrackers[i]->getId() == + metric1ActivationTrigger1->atom_matcher_id()) { + break; + } + } + const auto& activation1 = metricProducer1->mEventActivationMap.at(i); + EXPECT_EQ(100 * NS_PER_SEC, activation1->ttl_ns); + EXPECT_EQ(0, activation1->start_ns); + EXPECT_EQ(kNotActive, activation1->state); + + i = 0; + for (; i < metricsManager1->mAllAtomMatchingTrackers.size(); i++) { + if (metricsManager1->mAllAtomMatchingTrackers[i]->getId() == + metric1ActivationTrigger2->atom_matcher_id()) { + break; + } + } + const auto& activation2 = metricProducer1->mEventActivationMap.at(i); + EXPECT_EQ(200 * NS_PER_SEC, activation2->ttl_ns); + EXPECT_EQ(0, activation2->start_ns); + EXPECT_EQ(kNotActive, activation2->state); + // }}}------------------------------------------------------------------------------ + + // Trigger Activation 1 for Metric 1 + std::vector attributionUids = {111}; + std::vector attributionTags = {"App1"}; + std::unique_ptr event = + CreateAcquireWakelockEvent(timeBase1 + 100, attributionUids, attributionTags, "wl1"); + processor->OnLogEvent(event.get()); + + // Metric 1 is not active; Activation 1 set to kActiveOnBoot + // Metric 2 is active. + // {{{--------------------------------------------------------------------------- + EXPECT_FALSE(metricProducer1->isActive()); + EXPECT_EQ(0, activation1->start_ns); + EXPECT_EQ(kActiveOnBoot, activation1->state); + EXPECT_EQ(0, activation2->start_ns); + EXPECT_EQ(kNotActive, activation2->state); + + EXPECT_TRUE(metricProducer2->isActive()); + // }}}----------------------------------------------------------------------------- + + // Simulate shutdown by saving state to disk + int64_t shutDownTime = timeBase1 + 100 * NS_PER_SEC; + processor->SaveActiveConfigsToDisk(shutDownTime); + EXPECT_FALSE(metricProducer1->isActive()); + int64_t ttl1 = metric1ActivationTrigger1->ttl_seconds() * NS_PER_SEC; + + // Simulate device restarted state by creating new instance of StatsLogProcessor with the + // same config. + long timeBase2 = 1000; + sp processor2 = + CreateStatsLogProcessor(timeBase2, timeBase2, config1, cfgKey1); + + // Metric 1 is not active. + // Metric 2 is active. + // {{{--------------------------------------------------------------------------- + ASSERT_EQ(1, processor2->mMetricsManagers.size()); + it = processor2->mMetricsManagers.find(cfgKey1); + EXPECT_TRUE(it != processor2->mMetricsManagers.end()); + auto& metricsManager1001 = it->second; + EXPECT_TRUE(metricsManager1001->isActive()); + + metricIt = metricsManager1001->mAllMetricProducers.begin(); + for (; metricIt != metricsManager1001->mAllMetricProducers.end(); metricIt++) { + if ((*metricIt)->getMetricId() == metricId1) { + break; + } + } + EXPECT_TRUE(metricIt != metricsManager1001->mAllMetricProducers.end()); + auto& metricProducer1001 = *metricIt; + EXPECT_FALSE(metricProducer1001->isActive()); + + metricIt = metricsManager1001->mAllMetricProducers.begin(); + for (; metricIt != metricsManager1001->mAllMetricProducers.end(); metricIt++) { + if ((*metricIt)->getMetricId() == metricId2) { + break; + } + } + EXPECT_TRUE(metricIt != metricsManager1001->mAllMetricProducers.end()); + auto& metricProducer1002 = *metricIt; + EXPECT_TRUE(metricProducer1002->isActive()); + + i = 0; + for (; i < metricsManager1001->mAllAtomMatchingTrackers.size(); i++) { + if (metricsManager1001->mAllAtomMatchingTrackers[i]->getId() == + metric1ActivationTrigger1->atom_matcher_id()) { + break; + } + } + const auto& activation1001_1 = metricProducer1001->mEventActivationMap.at(i); + EXPECT_EQ(100 * NS_PER_SEC, activation1001_1->ttl_ns); + EXPECT_EQ(0, activation1001_1->start_ns); + EXPECT_EQ(kNotActive, activation1001_1->state); + + i = 0; + for (; i < metricsManager1001->mAllAtomMatchingTrackers.size(); i++) { + if (metricsManager1001->mAllAtomMatchingTrackers[i]->getId() == + metric1ActivationTrigger2->atom_matcher_id()) { + break; + } + } + + const auto& activation1001_2 = metricProducer1001->mEventActivationMap.at(i); + EXPECT_EQ(200 * NS_PER_SEC, activation1001_2->ttl_ns); + EXPECT_EQ(0, activation1001_2->start_ns); + EXPECT_EQ(kNotActive, activation1001_2->state); + // }}}----------------------------------------------------------------------------------- + + // Load saved state from disk. + processor2->LoadActiveConfigsFromDisk(); + + // Metric 1 active; Activation 1 is active, Activation 2 is not active + // Metric 2 is active. + // {{{--------------------------------------------------------------------------- + EXPECT_TRUE(metricProducer1001->isActive()); + EXPECT_EQ(timeBase2 + ttl1 - activation1001_1->ttl_ns, activation1001_1->start_ns); + EXPECT_EQ(kActive, activation1001_1->state); + EXPECT_EQ(0, activation1001_2->start_ns); + EXPECT_EQ(kNotActive, activation1001_2->state); + + EXPECT_TRUE(metricProducer1002->isActive()); + // }}}-------------------------------------------------------------------------------- + + // Trigger Activation 2 for Metric 1. + auto screenOnEvent = + CreateScreenStateChangedEvent(timeBase2 + 200, android::view::DISPLAY_STATE_ON); + processor2->OnLogEvent(screenOnEvent.get()); + + // Metric 1 active; Activation 1 is active, Activation 2 is set to kActiveOnBoot + // Metric 2 is active. + // {{{--------------------------------------------------------------------------- + EXPECT_TRUE(metricProducer1001->isActive()); + EXPECT_EQ(timeBase2 + ttl1 - activation1001_1->ttl_ns, activation1001_1->start_ns); + EXPECT_EQ(kActive, activation1001_1->state); + EXPECT_EQ(0, activation1001_2->start_ns); + EXPECT_EQ(kActiveOnBoot, activation1001_2->state); + + EXPECT_TRUE(metricProducer1002->isActive()); + // }}}--------------------------------------------------------------------------- + + // Simulate shutdown by saving state to disk + shutDownTime = timeBase2 + 50 * NS_PER_SEC; + processor2->SaveActiveConfigsToDisk(shutDownTime); + EXPECT_TRUE(metricProducer1001->isActive()); + EXPECT_TRUE(metricProducer1002->isActive()); + ttl1 = timeBase2 + metric1ActivationTrigger1->ttl_seconds() * NS_PER_SEC - shutDownTime; + int64_t ttl2 = metric1ActivationTrigger2->ttl_seconds() * NS_PER_SEC; + + // Simulate device restarted state by creating new instance of StatsLogProcessor with the + // same config. + long timeBase3 = timeBase2 + 120 * NS_PER_SEC; + sp processor3 = + CreateStatsLogProcessor(timeBase3, timeBase3, config1, cfgKey1); + + // Metric 1 is not active. + // Metric 2 is active. + // {{{--------------------------------------------------------------------------- + ASSERT_EQ(1, processor3->mMetricsManagers.size()); + it = processor3->mMetricsManagers.find(cfgKey1); + EXPECT_TRUE(it != processor3->mMetricsManagers.end()); + auto& metricsManagerTimeBase3 = it->second; + EXPECT_TRUE(metricsManagerTimeBase3->isActive()); + + metricIt = metricsManagerTimeBase3->mAllMetricProducers.begin(); + for (; metricIt != metricsManagerTimeBase3->mAllMetricProducers.end(); metricIt++) { + if ((*metricIt)->getMetricId() == metricId1) { + break; + } + } + EXPECT_TRUE(metricIt != metricsManagerTimeBase3->mAllMetricProducers.end()); + auto& metricProducerTimeBase3_1 = *metricIt; + EXPECT_FALSE(metricProducerTimeBase3_1->isActive()); + + metricIt = metricsManagerTimeBase3->mAllMetricProducers.begin(); + for (; metricIt != metricsManagerTimeBase3->mAllMetricProducers.end(); metricIt++) { + if ((*metricIt)->getMetricId() == metricId2) { + break; + } + } + EXPECT_TRUE(metricIt != metricsManagerTimeBase3->mAllMetricProducers.end()); + auto& metricProducerTimeBase3_2 = *metricIt; + EXPECT_TRUE(metricProducerTimeBase3_2->isActive()); + + i = 0; + for (; i < metricsManagerTimeBase3->mAllAtomMatchingTrackers.size(); i++) { + if (metricsManagerTimeBase3->mAllAtomMatchingTrackers[i]->getId() == + metric1ActivationTrigger1->atom_matcher_id()) { + break; + } + } + const auto& activationTimeBase3_1 = metricProducerTimeBase3_1->mEventActivationMap.at(i); + EXPECT_EQ(100 * NS_PER_SEC, activationTimeBase3_1->ttl_ns); + EXPECT_EQ(0, activationTimeBase3_1->start_ns); + EXPECT_EQ(kNotActive, activationTimeBase3_1->state); + + i = 0; + for (; i < metricsManagerTimeBase3->mAllAtomMatchingTrackers.size(); i++) { + if (metricsManagerTimeBase3->mAllAtomMatchingTrackers[i]->getId() == + metric1ActivationTrigger2->atom_matcher_id()) { + break; + } + } + + const auto& activationTimeBase3_2 = metricProducerTimeBase3_1->mEventActivationMap.at(i); + EXPECT_EQ(200 * NS_PER_SEC, activationTimeBase3_2->ttl_ns); + EXPECT_EQ(0, activationTimeBase3_2->start_ns); + EXPECT_EQ(kNotActive, activationTimeBase3_2->state); + + EXPECT_TRUE(metricProducerTimeBase3_2->isActive()); + // }}}---------------------------------------------------------------------------------- + + // Load saved state from disk. + processor3->LoadActiveConfigsFromDisk(); + + // Metric 1 active: Activation 1 is active, Activation 2 is active + // Metric 2 is active. + // {{{--------------------------------------------------------------------------- + EXPECT_TRUE(metricProducerTimeBase3_1->isActive()); + EXPECT_EQ(timeBase3 + ttl1 - activationTimeBase3_1->ttl_ns, activationTimeBase3_1->start_ns); + EXPECT_EQ(kActive, activationTimeBase3_1->state); + EXPECT_EQ(timeBase3 + ttl2 - activationTimeBase3_2->ttl_ns, activationTimeBase3_2->start_ns); + EXPECT_EQ(kActive, activationTimeBase3_2->state); + + EXPECT_TRUE(metricProducerTimeBase3_2->isActive()); + // }}}------------------------------------------------------------------------------- + + // Trigger Activation 2 for Metric 1 again. + screenOnEvent = CreateScreenStateChangedEvent(timeBase3 + 100 * NS_PER_SEC, + android::view::DISPLAY_STATE_ON); + processor3->OnLogEvent(screenOnEvent.get()); + + // Metric 1 active; Activation 1 is not active, Activation 2 is set to active + // Metric 2 is active. + // {{{--------------------------------------------------------------------------- + EXPECT_TRUE(metricProducerTimeBase3_1->isActive()); + EXPECT_EQ(kNotActive, activationTimeBase3_1->state); + EXPECT_EQ(timeBase3 + ttl2 - activationTimeBase3_2->ttl_ns, activationTimeBase3_2->start_ns); + EXPECT_EQ(kActive, activationTimeBase3_2->state); + + EXPECT_TRUE(metricProducerTimeBase3_2->isActive()); + // }}}--------------------------------------------------------------------------- + + // Simulate shutdown by saving state to disk. + shutDownTime = timeBase3 + 500 * NS_PER_SEC; + processor3->SaveActiveConfigsToDisk(shutDownTime); + EXPECT_TRUE(metricProducer1001->isActive()); + EXPECT_TRUE(metricProducer1002->isActive()); + ttl1 = timeBase3 + ttl1 - shutDownTime; + ttl2 = timeBase3 + metric1ActivationTrigger2->ttl_seconds() * NS_PER_SEC - shutDownTime; + + // Simulate device restarted state by creating new instance of StatsLogProcessor with the + // same config. + long timeBase4 = timeBase3 + 600 * NS_PER_SEC; + sp processor4 = + CreateStatsLogProcessor(timeBase4, timeBase4, config1, cfgKey1); + + // Metric 1 is not active. + // Metric 2 is active. + // {{{--------------------------------------------------------------------------- + ASSERT_EQ(1, processor4->mMetricsManagers.size()); + it = processor4->mMetricsManagers.find(cfgKey1); + EXPECT_TRUE(it != processor4->mMetricsManagers.end()); + auto& metricsManagerTimeBase4 = it->second; + EXPECT_TRUE(metricsManagerTimeBase4->isActive()); + + metricIt = metricsManagerTimeBase4->mAllMetricProducers.begin(); + for (; metricIt != metricsManagerTimeBase4->mAllMetricProducers.end(); metricIt++) { + if ((*metricIt)->getMetricId() == metricId1) { + break; + } + } + EXPECT_TRUE(metricIt != metricsManagerTimeBase4->mAllMetricProducers.end()); + auto& metricProducerTimeBase4_1 = *metricIt; + EXPECT_FALSE(metricProducerTimeBase4_1->isActive()); + + metricIt = metricsManagerTimeBase4->mAllMetricProducers.begin(); + for (; metricIt != metricsManagerTimeBase4->mAllMetricProducers.end(); metricIt++) { + if ((*metricIt)->getMetricId() == metricId2) { + break; + } + } + EXPECT_TRUE(metricIt != metricsManagerTimeBase4->mAllMetricProducers.end()); + auto& metricProducerTimeBase4_2 = *metricIt; + EXPECT_TRUE(metricProducerTimeBase4_2->isActive()); + + i = 0; + for (; i < metricsManagerTimeBase4->mAllAtomMatchingTrackers.size(); i++) { + if (metricsManagerTimeBase4->mAllAtomMatchingTrackers[i]->getId() == + metric1ActivationTrigger1->atom_matcher_id()) { + break; + } + } + const auto& activationTimeBase4_1 = metricProducerTimeBase4_1->mEventActivationMap.at(i); + EXPECT_EQ(100 * NS_PER_SEC, activationTimeBase4_1->ttl_ns); + EXPECT_EQ(0, activationTimeBase4_1->start_ns); + EXPECT_EQ(kNotActive, activationTimeBase4_1->state); + + i = 0; + for (; i < metricsManagerTimeBase4->mAllAtomMatchingTrackers.size(); i++) { + if (metricsManagerTimeBase4->mAllAtomMatchingTrackers[i]->getId() == + metric1ActivationTrigger2->atom_matcher_id()) { + break; + } + } + + const auto& activationTimeBase4_2 = metricProducerTimeBase4_1->mEventActivationMap.at(i); + EXPECT_EQ(200 * NS_PER_SEC, activationTimeBase4_2->ttl_ns); + EXPECT_EQ(0, activationTimeBase4_2->start_ns); + EXPECT_EQ(kNotActive, activationTimeBase4_2->state); + + EXPECT_TRUE(metricProducerTimeBase4_2->isActive()); + // }}}---------------------------------------------------------------------------------- + + // Load saved state from disk. + processor4->LoadActiveConfigsFromDisk(); + + // Metric 1 active: Activation 1 is not active, Activation 2 is not active + // Metric 2 is active. + // {{{--------------------------------------------------------------------------- + EXPECT_FALSE(metricProducerTimeBase4_1->isActive()); + EXPECT_EQ(kNotActive, activationTimeBase4_1->state); + EXPECT_EQ(kNotActive, activationTimeBase4_2->state); + + EXPECT_TRUE(metricProducerTimeBase4_2->isActive()); + // }}}------------------------------------------------------------------------------- +} + +TEST(StatsLogProcessorTest, TestActivationOnBootMultipleActivationsDifferentActivationTypes) { + int uid = 1111; + + // Create config with 2 metrics: + // Metric 1: Activate on boot with 2 activations + // Metric 2: Always active + StatsdConfig config1; + config1.set_id(12341); + config1.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root. + auto wakelockAcquireMatcher = CreateAcquireWakelockAtomMatcher(); + auto screenOnMatcher = CreateScreenTurnedOnAtomMatcher(); + *config1.add_atom_matcher() = wakelockAcquireMatcher; + *config1.add_atom_matcher() = screenOnMatcher; + + long metricId1 = 1234561; + long metricId2 = 1234562; + + auto countMetric1 = config1.add_count_metric(); + countMetric1->set_id(metricId1); + countMetric1->set_what(wakelockAcquireMatcher.id()); + countMetric1->set_bucket(FIVE_MINUTES); + + auto countMetric2 = config1.add_count_metric(); + countMetric2->set_id(metricId2); + countMetric2->set_what(wakelockAcquireMatcher.id()); + countMetric2->set_bucket(FIVE_MINUTES); + + auto metric1Activation = config1.add_metric_activation(); + metric1Activation->set_metric_id(metricId1); + metric1Activation->set_activation_type(ACTIVATE_ON_BOOT); + auto metric1ActivationTrigger1 = metric1Activation->add_event_activation(); + metric1ActivationTrigger1->set_atom_matcher_id(wakelockAcquireMatcher.id()); + metric1ActivationTrigger1->set_ttl_seconds(100); + auto metric1ActivationTrigger2 = metric1Activation->add_event_activation(); + metric1ActivationTrigger2->set_atom_matcher_id(screenOnMatcher.id()); + metric1ActivationTrigger2->set_ttl_seconds(200); + metric1ActivationTrigger2->set_activation_type(ACTIVATE_IMMEDIATELY); + + ConfigKey cfgKey1(uid, 12341); + long timeBase1 = 1; + sp processor1 = + CreateStatsLogProcessor(timeBase1, timeBase1, config1, cfgKey1); + + // Metric 1 is not active. + // Metric 2 is active. + // {{{--------------------------------------------------------------------------- + ASSERT_EQ(1, processor1->mMetricsManagers.size()); + auto it = processor1->mMetricsManagers.find(cfgKey1); + EXPECT_TRUE(it != processor1->mMetricsManagers.end()); + auto& metricsManager1 = it->second; + EXPECT_TRUE(metricsManager1->isActive()); + + ASSERT_EQ(metricsManager1->mAllMetricProducers.size(), 2); + // We assume that the index of a MetricProducer within the mAllMetricProducers + // array follows the order in which metrics are added to the config. + auto& metricProducer1_1 = metricsManager1->mAllMetricProducers[0]; + EXPECT_EQ(metricProducer1_1->getMetricId(), metricId1); + EXPECT_FALSE(metricProducer1_1->isActive()); // inactive due to associated MetricActivation + + auto& metricProducer1_2 = metricsManager1->mAllMetricProducers[1]; + EXPECT_EQ(metricProducer1_2->getMetricId(), metricId2); + EXPECT_TRUE(metricProducer1_2->isActive()); + + ASSERT_EQ(metricProducer1_1->mEventActivationMap.size(), 2); + // The key in mEventActivationMap is the index of the associated atom matcher. We assume + // that matchers are indexed in the order that they are added to the config. + const auto& activation1_1_1 = metricProducer1_1->mEventActivationMap.at(0); + EXPECT_EQ(100 * NS_PER_SEC, activation1_1_1->ttl_ns); + EXPECT_EQ(0, activation1_1_1->start_ns); + EXPECT_EQ(kNotActive, activation1_1_1->state); + EXPECT_EQ(ACTIVATE_ON_BOOT, activation1_1_1->activationType); + + const auto& activation1_1_2 = metricProducer1_1->mEventActivationMap.at(1); + EXPECT_EQ(200 * NS_PER_SEC, activation1_1_2->ttl_ns); + EXPECT_EQ(0, activation1_1_2->start_ns); + EXPECT_EQ(kNotActive, activation1_1_2->state); + EXPECT_EQ(ACTIVATE_IMMEDIATELY, activation1_1_2->activationType); + // }}}------------------------------------------------------------------------------ + + // Trigger Activation 1 for Metric 1 + std::vector attributionUids = {111}; + std::vector attributionTags = {"App1"}; + std::unique_ptr event = + CreateAcquireWakelockEvent(timeBase1 + 100, attributionUids, attributionTags, "wl1"); + processor1->OnLogEvent(event.get()); + + // Metric 1 is not active; Activation 1 set to kActiveOnBoot + // Metric 2 is active. + // {{{--------------------------------------------------------------------------- + EXPECT_FALSE(metricProducer1_1->isActive()); + EXPECT_EQ(0, activation1_1_1->start_ns); + EXPECT_EQ(kActiveOnBoot, activation1_1_1->state); + EXPECT_EQ(0, activation1_1_2->start_ns); + EXPECT_EQ(kNotActive, activation1_1_2->state); + + EXPECT_TRUE(metricProducer1_2->isActive()); + // }}}----------------------------------------------------------------------------- + + // Simulate shutdown by saving state to disk + int64_t shutDownTime = timeBase1 + 100 * NS_PER_SEC; + processor1->SaveActiveConfigsToDisk(shutDownTime); + EXPECT_FALSE(metricProducer1_1->isActive()); + + // Simulate device restarted state by creating new instance of StatsLogProcessor with the + // same config. + long timeBase2 = 1000; + sp processor2 = + CreateStatsLogProcessor(timeBase2, timeBase2, config1, cfgKey1); + + // Metric 1 is not active. + // Metric 2 is active. + // {{{--------------------------------------------------------------------------- + ASSERT_EQ(1, processor2->mMetricsManagers.size()); + it = processor2->mMetricsManagers.find(cfgKey1); + EXPECT_TRUE(it != processor2->mMetricsManagers.end()); + auto& metricsManager2 = it->second; + EXPECT_TRUE(metricsManager2->isActive()); + + ASSERT_EQ(metricsManager2->mAllMetricProducers.size(), 2); + // We assume that the index of a MetricProducer within the mAllMetricProducers + // array follows the order in which metrics are added to the config. + auto& metricProducer2_1 = metricsManager2->mAllMetricProducers[0]; + EXPECT_EQ(metricProducer2_1->getMetricId(), metricId1); + EXPECT_FALSE(metricProducer2_1->isActive()); + + auto& metricProducer2_2 = metricsManager2->mAllMetricProducers[1]; + EXPECT_EQ(metricProducer2_2->getMetricId(), metricId2); + EXPECT_TRUE(metricProducer2_2->isActive()); + + ASSERT_EQ(metricProducer2_1->mEventActivationMap.size(), 2); + // The key in mEventActivationMap is the index of the associated atom matcher. We assume + // that matchers are indexed in the order that they are added to the config. + const auto& activation2_1_1 = metricProducer2_1->mEventActivationMap.at(0); + EXPECT_EQ(100 * NS_PER_SEC, activation2_1_1->ttl_ns); + EXPECT_EQ(0, activation2_1_1->start_ns); + EXPECT_EQ(kNotActive, activation2_1_1->state); + EXPECT_EQ(ACTIVATE_ON_BOOT, activation2_1_1->activationType); + + const auto& activation2_1_2 = metricProducer2_1->mEventActivationMap.at(1); + EXPECT_EQ(200 * NS_PER_SEC, activation2_1_2->ttl_ns); + EXPECT_EQ(0, activation2_1_2->start_ns); + EXPECT_EQ(kNotActive, activation2_1_2->state); + EXPECT_EQ(ACTIVATE_IMMEDIATELY, activation2_1_2->activationType); + // }}}----------------------------------------------------------------------------------- + + // Load saved state from disk. + processor2->LoadActiveConfigsFromDisk(); + + // Metric 1 active; Activation 1 is active, Activation 2 is not active + // Metric 2 is active. + // {{{--------------------------------------------------------------------------- + EXPECT_TRUE(metricProducer2_1->isActive()); + int64_t ttl1 = metric1ActivationTrigger1->ttl_seconds() * NS_PER_SEC; + EXPECT_EQ(timeBase2 + ttl1 - activation2_1_1->ttl_ns, activation2_1_1->start_ns); + EXPECT_EQ(kActive, activation2_1_1->state); + EXPECT_EQ(0, activation2_1_2->start_ns); + EXPECT_EQ(kNotActive, activation2_1_2->state); + + EXPECT_TRUE(metricProducer2_2->isActive()); + // }}}-------------------------------------------------------------------------------- + + // Trigger Activation 2 for Metric 1. + auto screenOnEvent = + CreateScreenStateChangedEvent(timeBase2 + 200, android::view::DISPLAY_STATE_ON); + processor2->OnLogEvent(screenOnEvent.get()); + + // Metric 1 active; Activation 1 is active, Activation 2 is active + // Metric 2 is active. + // {{{--------------------------------------------------------------------------- + EXPECT_TRUE(metricProducer2_1->isActive()); + EXPECT_EQ(timeBase2 + ttl1 - activation2_1_1->ttl_ns, activation2_1_1->start_ns); + EXPECT_EQ(kActive, activation2_1_1->state); + EXPECT_EQ(screenOnEvent->GetElapsedTimestampNs(), activation2_1_2->start_ns); + EXPECT_EQ(kActive, activation2_1_2->state); + + EXPECT_TRUE(metricProducer2_2->isActive()); + // }}}--------------------------------------------------------------------------- + + // Simulate shutdown by saving state to disk + shutDownTime = timeBase2 + 50 * NS_PER_SEC; + processor2->SaveActiveConfigsToDisk(shutDownTime); + EXPECT_TRUE(metricProducer2_1->isActive()); + EXPECT_TRUE(metricProducer2_2->isActive()); + ttl1 -= shutDownTime - timeBase2; + int64_t ttl2 = metric1ActivationTrigger2->ttl_seconds() * NS_PER_SEC - + (shutDownTime - screenOnEvent->GetElapsedTimestampNs()); + + // Simulate device restarted state by creating new instance of StatsLogProcessor with the + // same config. + long timeBase3 = timeBase2 + 120 * NS_PER_SEC; + sp processor3 = + CreateStatsLogProcessor(timeBase3, timeBase3, config1, cfgKey1); + + // Metric 1 is not active. + // Metric 2 is active. + // {{{--------------------------------------------------------------------------- + ASSERT_EQ(1, processor3->mMetricsManagers.size()); + it = processor3->mMetricsManagers.find(cfgKey1); + EXPECT_TRUE(it != processor3->mMetricsManagers.end()); + auto& metricsManager3 = it->second; + EXPECT_TRUE(metricsManager3->isActive()); + + ASSERT_EQ(metricsManager3->mAllMetricProducers.size(), 2); + // We assume that the index of a MetricProducer within the mAllMetricProducers + // array follows the order in which metrics are added to the config. + auto& metricProducer3_1 = metricsManager3->mAllMetricProducers[0]; + EXPECT_EQ(metricProducer3_1->getMetricId(), metricId1); + EXPECT_FALSE(metricProducer3_1->isActive()); + + auto& metricProducer3_2 = metricsManager3->mAllMetricProducers[1]; + EXPECT_EQ(metricProducer3_2->getMetricId(), metricId2); + EXPECT_TRUE(metricProducer3_2->isActive()); + + ASSERT_EQ(metricProducer3_1->mEventActivationMap.size(), 2); + // The key in mEventActivationMap is the index of the associated atom matcher. We assume + // that matchers are indexed in the order that they are added to the config. + const auto& activation3_1_1 = metricProducer3_1->mEventActivationMap.at(0); + EXPECT_EQ(100 * NS_PER_SEC, activation3_1_1->ttl_ns); + EXPECT_EQ(0, activation3_1_1->start_ns); + EXPECT_EQ(kNotActive, activation3_1_1->state); + EXPECT_EQ(ACTIVATE_ON_BOOT, activation3_1_1->activationType); + + const auto& activation3_1_2 = metricProducer3_1->mEventActivationMap.at(1); + EXPECT_EQ(200 * NS_PER_SEC, activation3_1_2->ttl_ns); + EXPECT_EQ(0, activation3_1_2->start_ns); + EXPECT_EQ(kNotActive, activation3_1_2->state); + EXPECT_EQ(ACTIVATE_IMMEDIATELY, activation3_1_2->activationType); + // }}}---------------------------------------------------------------------------------- + + // Load saved state from disk. + processor3->LoadActiveConfigsFromDisk(); + + // Metric 1 active: Activation 1 is active, Activation 2 is active + // Metric 2 is active. + // {{{--------------------------------------------------------------------------- + EXPECT_TRUE(metricProducer3_1->isActive()); + EXPECT_EQ(timeBase3 + ttl1 - activation3_1_1->ttl_ns, activation3_1_1->start_ns); + EXPECT_EQ(kActive, activation3_1_1->state); + EXPECT_EQ(timeBase3 + ttl2 - activation3_1_2->ttl_ns, activation3_1_2->start_ns); + EXPECT_EQ(kActive, activation3_1_2->state); + + EXPECT_TRUE(metricProducer3_2->isActive()); + // }}}------------------------------------------------------------------------------- + + // Trigger Activation 2 for Metric 1 again. + screenOnEvent = CreateScreenStateChangedEvent(timeBase3 + 100 * NS_PER_SEC, + android::view::DISPLAY_STATE_ON); + processor3->OnLogEvent(screenOnEvent.get()); + + // Metric 1 active; Activation 1 is inactive (above screenOnEvent causes ttl1 to expire), + // Activation 2 is set to active + // Metric 2 is active. + // {{{--------------------------------------------------------------------------- + EXPECT_TRUE(metricProducer3_1->isActive()); + EXPECT_EQ(kNotActive, activation3_1_1->state); + EXPECT_EQ(screenOnEvent->GetElapsedTimestampNs(), activation3_1_2->start_ns); + EXPECT_EQ(kActive, activation3_1_2->state); + + EXPECT_TRUE(metricProducer3_2->isActive()); + // }}}--------------------------------------------------------------------------- +} + +TEST(StatsLogProcessorTest, TestActivationsPersistAcrossSystemServerRestart) { + int uid = 9876; + long configId = 12341; + + // Create config with 3 metrics: + // Metric 1: Activate on 2 activations, 1 on boot, 1 immediate. + // Metric 2: Activate on 2 activations, 1 on boot, 1 immediate. + // Metric 3: Always active + StatsdConfig config1; + config1.set_id(configId); + config1.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root. + auto wakelockAcquireMatcher = CreateAcquireWakelockAtomMatcher(); + auto screenOnMatcher = CreateScreenTurnedOnAtomMatcher(); + auto jobStartMatcher = CreateStartScheduledJobAtomMatcher(); + auto jobFinishMatcher = CreateFinishScheduledJobAtomMatcher(); + *config1.add_atom_matcher() = wakelockAcquireMatcher; + *config1.add_atom_matcher() = screenOnMatcher; + *config1.add_atom_matcher() = jobStartMatcher; + *config1.add_atom_matcher() = jobFinishMatcher; + + long metricId1 = 1234561; + long metricId2 = 1234562; + long metricId3 = 1234563; + + auto countMetric1 = config1.add_count_metric(); + countMetric1->set_id(metricId1); + countMetric1->set_what(wakelockAcquireMatcher.id()); + countMetric1->set_bucket(FIVE_MINUTES); + + auto countMetric2 = config1.add_count_metric(); + countMetric2->set_id(metricId2); + countMetric2->set_what(wakelockAcquireMatcher.id()); + countMetric2->set_bucket(FIVE_MINUTES); + + auto countMetric3 = config1.add_count_metric(); + countMetric3->set_id(metricId3); + countMetric3->set_what(wakelockAcquireMatcher.id()); + countMetric3->set_bucket(FIVE_MINUTES); + + // Metric 1 activates on boot for wakelock acquire, immediately for screen on. + auto metric1Activation = config1.add_metric_activation(); + metric1Activation->set_metric_id(metricId1); + auto metric1ActivationTrigger1 = metric1Activation->add_event_activation(); + metric1ActivationTrigger1->set_atom_matcher_id(wakelockAcquireMatcher.id()); + metric1ActivationTrigger1->set_ttl_seconds(100); + metric1ActivationTrigger1->set_activation_type(ACTIVATE_ON_BOOT); + auto metric1ActivationTrigger2 = metric1Activation->add_event_activation(); + metric1ActivationTrigger2->set_atom_matcher_id(screenOnMatcher.id()); + metric1ActivationTrigger2->set_ttl_seconds(200); + metric1ActivationTrigger2->set_activation_type(ACTIVATE_IMMEDIATELY); + + // Metric 2 activates on boot for scheduled job start, immediately for scheduled job finish. + auto metric2Activation = config1.add_metric_activation(); + metric2Activation->set_metric_id(metricId2); + auto metric2ActivationTrigger1 = metric2Activation->add_event_activation(); + metric2ActivationTrigger1->set_atom_matcher_id(jobStartMatcher.id()); + metric2ActivationTrigger1->set_ttl_seconds(100); + metric2ActivationTrigger1->set_activation_type(ACTIVATE_ON_BOOT); + auto metric2ActivationTrigger2 = metric2Activation->add_event_activation(); + metric2ActivationTrigger2->set_atom_matcher_id(jobFinishMatcher.id()); + metric2ActivationTrigger2->set_ttl_seconds(200); + metric2ActivationTrigger2->set_activation_type(ACTIVATE_IMMEDIATELY); + + // Send the config. + shared_ptr service = SharedRefBase::make(nullptr, nullptr); + string serialized = config1.SerializeAsString(); + service->addConfigurationChecked(uid, configId, {serialized.begin(), serialized.end()}); + + // Make sure the config is stored on disk. Otherwise, we will not reset on system server death. + StatsdConfig tmpConfig; + ConfigKey cfgKey1(uid, configId); + EXPECT_TRUE(StorageManager::readConfigFromDisk(cfgKey1, &tmpConfig)); + + // Metric 1 is not active. + // Metric 2 is not active. + // Metric 3 is active. + // {{{--------------------------------------------------------------------------- + sp processor = service->mProcessor; + ASSERT_EQ(1, processor->mMetricsManagers.size()); + auto it = processor->mMetricsManagers.find(cfgKey1); + EXPECT_TRUE(it != processor->mMetricsManagers.end()); + auto& metricsManager1 = it->second; + EXPECT_TRUE(metricsManager1->isActive()); + ASSERT_EQ(3, metricsManager1->mAllMetricProducers.size()); + + auto& metricProducer1 = metricsManager1->mAllMetricProducers[0]; + EXPECT_EQ(metricId1, metricProducer1->getMetricId()); + EXPECT_FALSE(metricProducer1->isActive()); + + auto& metricProducer2 = metricsManager1->mAllMetricProducers[1]; + EXPECT_EQ(metricId2, metricProducer2->getMetricId()); + EXPECT_FALSE(metricProducer2->isActive()); + + auto& metricProducer3 = metricsManager1->mAllMetricProducers[2]; + EXPECT_EQ(metricId3, metricProducer3->getMetricId()); + EXPECT_TRUE(metricProducer3->isActive()); + + // Check event activations. + ASSERT_EQ(metricsManager1->mAllAtomMatchingTrackers.size(), 4); + EXPECT_EQ(metricsManager1->mAllAtomMatchingTrackers[0]->getId(), + metric1ActivationTrigger1->atom_matcher_id()); + const auto& activation1 = metricProducer1->mEventActivationMap.at(0); + EXPECT_EQ(100 * NS_PER_SEC, activation1->ttl_ns); + EXPECT_EQ(0, activation1->start_ns); + EXPECT_EQ(kNotActive, activation1->state); + EXPECT_EQ(ACTIVATE_ON_BOOT, activation1->activationType); + + EXPECT_EQ(metricsManager1->mAllAtomMatchingTrackers[1]->getId(), + metric1ActivationTrigger2->atom_matcher_id()); + const auto& activation2 = metricProducer1->mEventActivationMap.at(1); + EXPECT_EQ(200 * NS_PER_SEC, activation2->ttl_ns); + EXPECT_EQ(0, activation2->start_ns); + EXPECT_EQ(kNotActive, activation2->state); + EXPECT_EQ(ACTIVATE_IMMEDIATELY, activation2->activationType); + + EXPECT_EQ(metricsManager1->mAllAtomMatchingTrackers[2]->getId(), + metric2ActivationTrigger1->atom_matcher_id()); + const auto& activation3 = metricProducer2->mEventActivationMap.at(2); + EXPECT_EQ(100 * NS_PER_SEC, activation3->ttl_ns); + EXPECT_EQ(0, activation3->start_ns); + EXPECT_EQ(kNotActive, activation3->state); + EXPECT_EQ(ACTIVATE_ON_BOOT, activation3->activationType); + + EXPECT_EQ(metricsManager1->mAllAtomMatchingTrackers[3]->getId(), + metric2ActivationTrigger2->atom_matcher_id()); + const auto& activation4 = metricProducer2->mEventActivationMap.at(3); + EXPECT_EQ(200 * NS_PER_SEC, activation4->ttl_ns); + EXPECT_EQ(0, activation4->start_ns); + EXPECT_EQ(kNotActive, activation4->state); + EXPECT_EQ(ACTIVATE_IMMEDIATELY, activation4->activationType); + // }}}------------------------------------------------------------------------------ + + // Trigger Activation 1 for Metric 1. Should activate on boot. + // Trigger Activation 4 for Metric 2. Should activate immediately. + int64_t configAddedTimeNs = metricsManager1->mLastReportTimeNs; + std::vector attributionUids = {111}; + std::vector attributionTags = {"App1"}; + std::unique_ptr event1 = CreateAcquireWakelockEvent( + 1 + configAddedTimeNs, attributionUids, attributionTags, "wl1"); + processor->OnLogEvent(event1.get()); + + std::unique_ptr event2 = CreateFinishScheduledJobEvent( + 2 + configAddedTimeNs, attributionUids, attributionTags, "finish1"); + processor->OnLogEvent(event2.get()); + + // Metric 1 is not active; Activation 1 set to kActiveOnBoot + // Metric 2 is active. Activation 4 set to kActive + // Metric 3 is active. + // {{{--------------------------------------------------------------------------- + EXPECT_FALSE(metricProducer1->isActive()); + EXPECT_EQ(0, activation1->start_ns); + EXPECT_EQ(kActiveOnBoot, activation1->state); + EXPECT_EQ(0, activation2->start_ns); + EXPECT_EQ(kNotActive, activation2->state); + + EXPECT_TRUE(metricProducer2->isActive()); + EXPECT_EQ(0, activation3->start_ns); + EXPECT_EQ(kNotActive, activation3->state); + EXPECT_EQ(2 + configAddedTimeNs, activation4->start_ns); + EXPECT_EQ(kActive, activation4->state); + + EXPECT_TRUE(metricProducer3->isActive()); + // }}}----------------------------------------------------------------------------- + + // Can't fake time with StatsService. + // Lets get a time close to the system server death time and make sure it's sane. + int64_t approximateSystemServerDeath = getElapsedRealtimeNs(); + EXPECT_TRUE(approximateSystemServerDeath > 2 + configAddedTimeNs); + EXPECT_TRUE(approximateSystemServerDeath < NS_PER_SEC + configAddedTimeNs); + + // System server dies. + service->statsCompanionServiceDiedImpl(); + + // We should have a new metrics manager. Lets get it and ensure activation status is restored. + // {{{--------------------------------------------------------------------------- + ASSERT_EQ(1, processor->mMetricsManagers.size()); + it = processor->mMetricsManagers.find(cfgKey1); + EXPECT_TRUE(it != processor->mMetricsManagers.end()); + auto& metricsManager2 = it->second; + EXPECT_TRUE(metricsManager2->isActive()); + ASSERT_EQ(3, metricsManager2->mAllMetricProducers.size()); + + auto& metricProducer1001 = metricsManager2->mAllMetricProducers[0]; + EXPECT_EQ(metricId1, metricProducer1001->getMetricId()); + EXPECT_FALSE(metricProducer1001->isActive()); + + auto& metricProducer1002 = metricsManager2->mAllMetricProducers[1]; + EXPECT_EQ(metricId2, metricProducer1002->getMetricId()); + EXPECT_TRUE(metricProducer1002->isActive()); + + auto& metricProducer1003 = metricsManager2->mAllMetricProducers[2]; + EXPECT_EQ(metricId3, metricProducer1003->getMetricId()); + EXPECT_TRUE(metricProducer1003->isActive()); + + // Check event activations. + // Activation 1 is kActiveOnBoot. + // Activation 2 and 3 are not active. + // Activation 4 is active. + ASSERT_EQ(metricsManager2->mAllAtomMatchingTrackers.size(), 4); + EXPECT_EQ(metricsManager2->mAllAtomMatchingTrackers[0]->getId(), + metric1ActivationTrigger1->atom_matcher_id()); + const auto& activation1001 = metricProducer1001->mEventActivationMap.at(0); + EXPECT_EQ(100 * NS_PER_SEC, activation1001->ttl_ns); + EXPECT_EQ(0, activation1001->start_ns); + EXPECT_EQ(kActiveOnBoot, activation1001->state); + EXPECT_EQ(ACTIVATE_ON_BOOT, activation1001->activationType); + + EXPECT_EQ(metricsManager2->mAllAtomMatchingTrackers[1]->getId(), + metric1ActivationTrigger2->atom_matcher_id()); + const auto& activation1002 = metricProducer1001->mEventActivationMap.at(1); + EXPECT_EQ(200 * NS_PER_SEC, activation1002->ttl_ns); + EXPECT_EQ(0, activation1002->start_ns); + EXPECT_EQ(kNotActive, activation1002->state); + EXPECT_EQ(ACTIVATE_IMMEDIATELY, activation1002->activationType); + + EXPECT_EQ(metricsManager2->mAllAtomMatchingTrackers[2]->getId(), + metric2ActivationTrigger1->atom_matcher_id()); + const auto& activation1003 = metricProducer1002->mEventActivationMap.at(2); + EXPECT_EQ(100 * NS_PER_SEC, activation1003->ttl_ns); + EXPECT_EQ(0, activation1003->start_ns); + EXPECT_EQ(kNotActive, activation1003->state); + EXPECT_EQ(ACTIVATE_ON_BOOT, activation1003->activationType); + + EXPECT_EQ(metricsManager2->mAllAtomMatchingTrackers[3]->getId(), + metric2ActivationTrigger2->atom_matcher_id()); + const auto& activation1004 = metricProducer1002->mEventActivationMap.at(3); + EXPECT_EQ(200 * NS_PER_SEC, activation1004->ttl_ns); + EXPECT_EQ(2 + configAddedTimeNs, activation1004->start_ns); + EXPECT_EQ(kActive, activation1004->state); + EXPECT_EQ(ACTIVATE_IMMEDIATELY, activation1004->activationType); + // }}}------------------------------------------------------------------------------ + + // Clear the data stored on disk as a result of the system server death. + vector buffer; + processor->onDumpReport(cfgKey1, configAddedTimeNs + NS_PER_SEC, false, true, ADB_DUMP, FAST, + &buffer); +} + +TEST(StatsLogProcessorTest_mapIsolatedUidToHostUid, LogHostUid) { + int hostUid = 20; + int isolatedUid = 30; + uint64_t eventTimeNs = 12355; + int atomId = 89; + int field1 = 90; + int field2 = 28; + sp mockUidMap = makeMockUidMapForOneHost(hostUid, {isolatedUid}); + ConfigKey cfgKey; + StatsdConfig config = MakeConfig(false); + sp processor = + CreateStatsLogProcessor(1, 1, config, cfgKey, nullptr, 0, mockUidMap); + + shared_ptr logEvent = makeUidLogEvent(atomId, eventTimeNs, hostUid, field1, field2); + + processor->OnLogEvent(logEvent.get()); + + const vector* actualFieldValues = &logEvent->getValues(); + ASSERT_EQ(3, actualFieldValues->size()); + EXPECT_EQ(hostUid, actualFieldValues->at(0).mValue.int_value); + EXPECT_EQ(field1, actualFieldValues->at(1).mValue.int_value); + EXPECT_EQ(field2, actualFieldValues->at(2).mValue.int_value); +} + +TEST(StatsLogProcessorTest_mapIsolatedUidToHostUid, LogIsolatedUid) { + int hostUid = 20; + int isolatedUid = 30; + uint64_t eventTimeNs = 12355; + int atomId = 89; + int field1 = 90; + int field2 = 28; + sp mockUidMap = makeMockUidMapForOneHost(hostUid, {isolatedUid}); + ConfigKey cfgKey; + StatsdConfig config = MakeConfig(false); + sp processor = + CreateStatsLogProcessor(1, 1, config, cfgKey, nullptr, 0, mockUidMap); + + shared_ptr logEvent = + makeUidLogEvent(atomId, eventTimeNs, isolatedUid, field1, field2); + + processor->OnLogEvent(logEvent.get()); + + const vector* actualFieldValues = &logEvent->getValues(); + ASSERT_EQ(3, actualFieldValues->size()); + EXPECT_EQ(hostUid, actualFieldValues->at(0).mValue.int_value); + EXPECT_EQ(field1, actualFieldValues->at(1).mValue.int_value); + EXPECT_EQ(field2, actualFieldValues->at(2).mValue.int_value); +} + +TEST(StatsLogProcessorTest_mapIsolatedUidToHostUid, LogHostUidAttributionChain) { + int hostUid = 20; + int isolatedUid = 30; + uint64_t eventTimeNs = 12355; + int atomId = 89; + int field1 = 90; + int field2 = 28; + sp mockUidMap = makeMockUidMapForOneHost(hostUid, {isolatedUid}); + ConfigKey cfgKey; + StatsdConfig config = MakeConfig(false); + sp processor = + CreateStatsLogProcessor(1, 1, config, cfgKey, nullptr, 0, mockUidMap); + + shared_ptr logEvent = makeAttributionLogEvent(atomId, eventTimeNs, {hostUid, 200}, + {"tag1", "tag2"}, field1, field2); + + processor->OnLogEvent(logEvent.get()); + + const vector* actualFieldValues = &logEvent->getValues(); + ASSERT_EQ(6, actualFieldValues->size()); + EXPECT_EQ(hostUid, actualFieldValues->at(0).mValue.int_value); + EXPECT_EQ("tag1", actualFieldValues->at(1).mValue.str_value); + EXPECT_EQ(200, actualFieldValues->at(2).mValue.int_value); + EXPECT_EQ("tag2", actualFieldValues->at(3).mValue.str_value); + EXPECT_EQ(field1, actualFieldValues->at(4).mValue.int_value); + EXPECT_EQ(field2, actualFieldValues->at(5).mValue.int_value); +} + +TEST(StatsLogProcessorTest_mapIsolatedUidToHostUid, LogIsolatedUidAttributionChain) { + int hostUid = 20; + int isolatedUid = 30; + uint64_t eventTimeNs = 12355; + int atomId = 89; + int field1 = 90; + int field2 = 28; + sp mockUidMap = makeMockUidMapForOneHost(hostUid, {isolatedUid}); + ConfigKey cfgKey; + StatsdConfig config = MakeConfig(false); + sp processor = + CreateStatsLogProcessor(1, 1, config, cfgKey, nullptr, 0, mockUidMap); + + shared_ptr logEvent = makeAttributionLogEvent(atomId, eventTimeNs, {isolatedUid, 200}, + {"tag1", "tag2"}, field1, field2); + + processor->OnLogEvent(logEvent.get()); + + const vector* actualFieldValues = &logEvent->getValues(); + ASSERT_EQ(6, actualFieldValues->size()); + EXPECT_EQ(hostUid, actualFieldValues->at(0).mValue.int_value); + EXPECT_EQ("tag1", actualFieldValues->at(1).mValue.str_value); + EXPECT_EQ(200, actualFieldValues->at(2).mValue.int_value); + EXPECT_EQ("tag2", actualFieldValues->at(3).mValue.str_value); + EXPECT_EQ(field1, actualFieldValues->at(4).mValue.int_value); + EXPECT_EQ(field2, actualFieldValues->at(5).mValue.int_value); +} + +TEST(StatsLogProcessorTest, TestDumpReportWithoutErasingDataDoesNotUpdateTimestamp) { + int hostUid = 20; + int isolatedUid = 30; + sp mockUidMap = makeMockUidMapForOneHost(hostUid, {isolatedUid}); + ConfigKey key(3, 4); + + // TODO: All tests should not persist state on disk. This removes any reports that were present. + ProtoOutputStream proto; + StorageManager::appendConfigMetricsReport(key, &proto, /*erase data=*/true, /*isAdb=*/false); + + StatsdConfig config = MakeConfig(false); + sp processor = + CreateStatsLogProcessor(1, 1, config, key, nullptr, 0, mockUidMap); + vector bytes; + + int64_t dumpTime1Ns = 1 * NS_PER_SEC; + processor->onDumpReport(key, dumpTime1Ns, false /* include_current_bucket */, + true /* erase_data */, ADB_DUMP, FAST, &bytes); + + ConfigMetricsReportList output; + output.ParseFromArray(bytes.data(), bytes.size()); + EXPECT_EQ(output.reports_size(), 1); + EXPECT_EQ(output.reports(0).current_report_elapsed_nanos(), dumpTime1Ns); + + int64_t dumpTime2Ns = 5 * NS_PER_SEC; + processor->onDumpReport(key, dumpTime2Ns, false /* include_current_bucket */, + false /* erase_data */, ADB_DUMP, FAST, &bytes); + + // Check that the dump report without clearing data is successful. + output.ParseFromArray(bytes.data(), bytes.size()); + EXPECT_EQ(output.reports_size(), 1); + EXPECT_EQ(output.reports(0).current_report_elapsed_nanos(), dumpTime2Ns); + EXPECT_EQ(output.reports(0).last_report_elapsed_nanos(), dumpTime1Ns); + + int64_t dumpTime3Ns = 10 * NS_PER_SEC; + processor->onDumpReport(key, dumpTime3Ns, false /* include_current_bucket */, + true /* erase_data */, ADB_DUMP, FAST, &bytes); + + // Check that the previous dump report that didn't clear data did not overwrite the first dump's + // timestamps. + output.ParseFromArray(bytes.data(), bytes.size()); + EXPECT_EQ(output.reports_size(), 1); + EXPECT_EQ(output.reports(0).current_report_elapsed_nanos(), dumpTime3Ns); + EXPECT_EQ(output.reports(0).last_report_elapsed_nanos(), dumpTime1Ns); + +} + +#else +GTEST_LOG_(INFO) << "This test does nothing.\n"; +#endif + +} // namespace statsd +} // namespace os +} // namespace android diff --git a/statsd/tests/StatsService_test.cpp b/statsd/tests/StatsService_test.cpp new file mode 100644 index 00000000..9d01b0c9 --- /dev/null +++ b/statsd/tests/StatsService_test.cpp @@ -0,0 +1,104 @@ +// 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 "StatsService.h" +#include "config/ConfigKey.h" +#include "src/statsd_config.pb.h" + +#include +#include +#include + +#include + +using namespace android; +using namespace testing; + +namespace android { +namespace os { +namespace statsd { + +using android::util::ProtoOutputStream; +using ::ndk::SharedRefBase; + +#ifdef __ANDROID__ + +TEST(StatsServiceTest, TestAddConfig_simple) { + shared_ptr service = SharedRefBase::make(nullptr, nullptr); + StatsdConfig config; + config.set_id(12345); + string serialized = config.SerializeAsString(); + + EXPECT_TRUE( + service->addConfigurationChecked(123, 12345, {serialized.begin(), serialized.end()})); +} + +TEST(StatsServiceTest, TestAddConfig_empty) { + shared_ptr service = SharedRefBase::make(nullptr, nullptr); + string serialized = ""; + + EXPECT_TRUE( + service->addConfigurationChecked(123, 12345, {serialized.begin(), serialized.end()})); +} + +TEST(StatsServiceTest, TestAddConfig_invalid) { + shared_ptr service = SharedRefBase::make(nullptr, nullptr); + string serialized = "Invalid config!"; + + EXPECT_FALSE( + service->addConfigurationChecked(123, 12345, {serialized.begin(), serialized.end()})); +} + +TEST(StatsServiceTest, TestGetUidFromArgs) { + Vector args; + args.push(String8("-1")); + args.push(String8("0")); + args.push(String8("1")); + args.push(String8("a1")); + args.push(String8("")); + + int32_t uid; + + shared_ptr service = SharedRefBase::make(nullptr, nullptr); + service->mEngBuild = true; + + // "-1" + EXPECT_FALSE(service->getUidFromArgs(args, 0, uid)); + + // "0" + EXPECT_TRUE(service->getUidFromArgs(args, 1, uid)); + EXPECT_EQ(0, uid); + + // "1" + EXPECT_TRUE(service->getUidFromArgs(args, 2, uid)); + EXPECT_EQ(1, uid); + + // "a1" + EXPECT_FALSE(service->getUidFromArgs(args, 3, uid)); + + // "" + EXPECT_FALSE(service->getUidFromArgs(args, 4, uid)); + + // For a non-userdebug, uid "1" cannot be impersonated. + service->mEngBuild = false; + EXPECT_FALSE(service->getUidFromArgs(args, 2, uid)); +} + +#else +GTEST_LOG_(INFO) << "This test does nothing.\n"; +#endif + +} // namespace statsd +} // namespace os +} // namespace android diff --git a/statsd/tests/UidMap_test.cpp b/statsd/tests/UidMap_test.cpp new file mode 100644 index 00000000..33bdc643 --- /dev/null +++ b/statsd/tests/UidMap_test.cpp @@ -0,0 +1,426 @@ +// 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 "packages/UidMap.h" +#include "StatsLogProcessor.h" +#include "config/ConfigKey.h" +#include "guardrail/StatsdStats.h" +#include "logd/LogEvent.h" +#include "hash.h" +#include "statslog_statsdtest.h" +#include "statsd_test_util.h" + +#include +#include + +#include + +using namespace android; + +namespace android { +namespace os { +namespace statsd { + +using android::util::ProtoOutputStream; +using android::util::ProtoReader; + +#ifdef __ANDROID__ +const string kApp1 = "app1.sharing.1"; +const string kApp2 = "app2.sharing.1"; + +TEST(UidMapTest, TestIsolatedUID) { + sp m = new UidMap(); + sp pullerManager = new StatsPullerManager(); + sp anomalyAlarmMonitor; + sp subscriberAlarmMonitor; + // Construct the processor with a no-op sendBroadcast function that does nothing. + StatsLogProcessor p( + m, pullerManager, anomalyAlarmMonitor, subscriberAlarmMonitor, 0, + [](const ConfigKey& key) { return true; }, + [](const int&, const vector&) { return true; }); + + std::unique_ptr addEvent = CreateIsolatedUidChangedEvent( + 1 /*timestamp*/, 100 /*hostUid*/, 101 /*isolatedUid*/, 1 /*is_create*/); + EXPECT_EQ(101, m->getHostUidOrSelf(101)); + p.OnLogEvent(addEvent.get()); + EXPECT_EQ(100, m->getHostUidOrSelf(101)); + + std::unique_ptr removeEvent = CreateIsolatedUidChangedEvent( + 1 /*timestamp*/, 100 /*hostUid*/, 101 /*isolatedUid*/, 0 /*is_create*/); + p.OnLogEvent(removeEvent.get()); + EXPECT_EQ(101, m->getHostUidOrSelf(101)); +} + +TEST(UidMapTest, TestMatching) { + UidMap m; + vector uids; + vector versions; + vector apps; + vector versionStrings; + vector installers; + + uids.push_back(1000); + uids.push_back(1000); + versionStrings.push_back(String16("v1")); + versionStrings.push_back(String16("v1")); + installers.push_back(String16("")); + installers.push_back(String16("")); + apps.push_back(String16(kApp1.c_str())); + apps.push_back(String16(kApp2.c_str())); + versions.push_back(4); + versions.push_back(5); + m.updateMap(1, uids, versions, versionStrings, apps, installers); + EXPECT_TRUE(m.hasApp(1000, kApp1)); + EXPECT_TRUE(m.hasApp(1000, kApp2)); + EXPECT_FALSE(m.hasApp(1000, "not.app")); + + std::set name_set = m.getAppNamesFromUid(1000u, true /* returnNormalized */); + ASSERT_EQ(name_set.size(), 2u); + EXPECT_TRUE(name_set.find(kApp1) != name_set.end()); + EXPECT_TRUE(name_set.find(kApp2) != name_set.end()); + + name_set = m.getAppNamesFromUid(12345, true /* returnNormalized */); + EXPECT_TRUE(name_set.empty()); +} + +TEST(UidMapTest, TestAddAndRemove) { + UidMap m; + vector uids; + vector versions; + vector apps; + vector versionStrings; + vector installers; + + uids.push_back(1000); + uids.push_back(1000); + versionStrings.push_back(String16("v1")); + versionStrings.push_back(String16("v1")); + installers.push_back(String16("")); + installers.push_back(String16("")); + apps.push_back(String16(kApp1.c_str())); + apps.push_back(String16(kApp2.c_str())); + versions.push_back(4); + versions.push_back(5); + m.updateMap(1, uids, versions, versionStrings, apps, installers); + + std::set name_set = m.getAppNamesFromUid(1000, true /* returnNormalized */); + ASSERT_EQ(name_set.size(), 2u); + EXPECT_TRUE(name_set.find(kApp1) != name_set.end()); + EXPECT_TRUE(name_set.find(kApp2) != name_set.end()); + + // Update the app1 version. + m.updateApp(2, String16(kApp1.c_str()), 1000, 40, String16("v40"), String16("")); + EXPECT_EQ(40, m.getAppVersion(1000, kApp1)); + + name_set = m.getAppNamesFromUid(1000, true /* returnNormalized */); + ASSERT_EQ(name_set.size(), 2u); + EXPECT_TRUE(name_set.find(kApp1) != name_set.end()); + EXPECT_TRUE(name_set.find(kApp2) != name_set.end()); + + m.removeApp(3, String16(kApp1.c_str()), 1000); + EXPECT_FALSE(m.hasApp(1000, kApp1)); + EXPECT_TRUE(m.hasApp(1000, kApp2)); + name_set = m.getAppNamesFromUid(1000, true /* returnNormalized */); + ASSERT_EQ(name_set.size(), 1u); + EXPECT_TRUE(name_set.find(kApp1) == name_set.end()); + EXPECT_TRUE(name_set.find(kApp2) != name_set.end()); + + // Remove app2. + m.removeApp(4, String16(kApp2.c_str()), 1000); + EXPECT_FALSE(m.hasApp(1000, kApp1)); + EXPECT_FALSE(m.hasApp(1000, kApp2)); + name_set = m.getAppNamesFromUid(1000, true /* returnNormalized */); + EXPECT_TRUE(name_set.empty()); +} + +TEST(UidMapTest, TestUpdateApp) { + UidMap m; + m.updateMap(1, {1000, 1000}, {4, 5}, {String16("v4"), String16("v5")}, + {String16(kApp1.c_str()), String16(kApp2.c_str())}, {String16(""), String16("")}); + std::set name_set = m.getAppNamesFromUid(1000, true /* returnNormalized */); + ASSERT_EQ(name_set.size(), 2u); + EXPECT_TRUE(name_set.find(kApp1) != name_set.end()); + EXPECT_TRUE(name_set.find(kApp2) != name_set.end()); + + // Adds a new name for uid 1000. + m.updateApp(2, String16("NeW_aPP1_NAmE"), 1000, 40, String16("v40"), String16("")); + name_set = m.getAppNamesFromUid(1000, true /* returnNormalized */); + ASSERT_EQ(name_set.size(), 3u); + EXPECT_TRUE(name_set.find(kApp1) != name_set.end()); + EXPECT_TRUE(name_set.find(kApp2) != name_set.end()); + EXPECT_TRUE(name_set.find("NeW_aPP1_NAmE") == name_set.end()); + EXPECT_TRUE(name_set.find("new_app1_name") != name_set.end()); + + // This name is also reused by another uid 2000. + m.updateApp(3, String16("NeW_aPP1_NAmE"), 2000, 1, String16("v1"), String16("")); + name_set = m.getAppNamesFromUid(2000, true /* returnNormalized */); + ASSERT_EQ(name_set.size(), 1u); + EXPECT_TRUE(name_set.find("NeW_aPP1_NAmE") == name_set.end()); + EXPECT_TRUE(name_set.find("new_app1_name") != name_set.end()); +} + +static void protoOutputStreamToUidMapping(ProtoOutputStream* proto, UidMapping* results) { + vector bytes; + bytes.resize(proto->size()); + size_t pos = 0; + sp reader = proto->data(); + while (reader->readBuffer() != NULL) { + size_t toRead = reader->currentToRead(); + std::memcpy(&((bytes)[pos]), reader->readBuffer(), toRead); + pos += toRead; + reader->move(toRead); + } + results->ParseFromArray(bytes.data(), bytes.size()); +} + +// Test that uid map returns at least one snapshot even if we already obtained +// this snapshot from a previous call to getData. +TEST(UidMapTest, TestOutputIncludesAtLeastOneSnapshot) { + UidMap m; + // Initialize single config key. + ConfigKey config1(1, StringToId("config1")); + m.OnConfigUpdated(config1); + vector uids; + vector versions; + vector apps; + vector versionStrings; + vector installers; + uids.push_back(1000); + apps.push_back(String16(kApp2.c_str())); + versionStrings.push_back(String16("v1")); + installers.push_back(String16("")); + versions.push_back(5); + m.updateMap(1, uids, versions, versionStrings, apps, installers); + + // Set the last timestamp for this config key to be newer. + m.mLastUpdatePerConfigKey[config1] = 2; + + ProtoOutputStream proto; + m.appendUidMap(3, config1, nullptr, true, true, &proto); + + // Check there's still a uidmap attached this one. + UidMapping results; + protoOutputStreamToUidMapping(&proto, &results); + ASSERT_EQ(1, results.snapshots_size()); + EXPECT_EQ("v1", results.snapshots(0).package_info(0).version_string()); +} + +TEST(UidMapTest, TestRemovedAppRetained) { + UidMap m; + // Initialize single config key. + ConfigKey config1(1, StringToId("config1")); + m.OnConfigUpdated(config1); + vector uids; + vector versions; + vector versionStrings; + vector installers; + vector apps; + uids.push_back(1000); + apps.push_back(String16(kApp2.c_str())); + versions.push_back(5); + versionStrings.push_back(String16("v5")); + installers.push_back(String16("")); + m.updateMap(1, uids, versions, versionStrings, apps, installers); + m.removeApp(2, String16(kApp2.c_str()), 1000); + + ProtoOutputStream proto; + m.appendUidMap(3, config1, nullptr, true, true, &proto); + + // Snapshot should still contain this item as deleted. + UidMapping results; + protoOutputStreamToUidMapping(&proto, &results); + ASSERT_EQ(1, results.snapshots(0).package_info_size()); + EXPECT_EQ(true, results.snapshots(0).package_info(0).deleted()); +} + +TEST(UidMapTest, TestRemovedAppOverGuardrail) { + UidMap m; + // Initialize single config key. + ConfigKey config1(1, StringToId("config1")); + m.OnConfigUpdated(config1); + vector uids; + vector versions; + vector versionStrings; + vector installers; + vector apps; + const int maxDeletedApps = StatsdStats::kMaxDeletedAppsInUidMap; + for (int j = 0; j < maxDeletedApps + 10; j++) { + uids.push_back(j); + apps.push_back(String16(kApp1.c_str())); + versions.push_back(j); + versionStrings.push_back(String16("v")); + installers.push_back(String16("")); + } + m.updateMap(1, uids, versions, versionStrings, apps, installers); + + // First, verify that we have the expected number of items. + UidMapping results; + ProtoOutputStream proto; + m.appendUidMap(3, config1, nullptr, true, true, &proto); + protoOutputStreamToUidMapping(&proto, &results); + ASSERT_EQ(maxDeletedApps + 10, results.snapshots(0).package_info_size()); + + // Now remove all the apps. + m.updateMap(1, uids, versions, versionStrings, apps, installers); + for (int j = 0; j < maxDeletedApps + 10; j++) { + m.removeApp(4, String16(kApp1.c_str()), j); + } + + proto.clear(); + m.appendUidMap(5, config1, nullptr, true, true, &proto); + // Snapshot drops the first nine items. + protoOutputStreamToUidMapping(&proto, &results); + ASSERT_EQ(maxDeletedApps, results.snapshots(0).package_info_size()); +} + +TEST(UidMapTest, TestClearingOutput) { + UidMap m; + + ConfigKey config1(1, StringToId("config1")); + ConfigKey config2(1, StringToId("config2")); + + m.OnConfigUpdated(config1); + + vector uids; + vector versions; + vector versionStrings; + vector installers; + vector apps; + uids.push_back(1000); + uids.push_back(1000); + apps.push_back(String16(kApp1.c_str())); + apps.push_back(String16(kApp2.c_str())); + versions.push_back(4); + versions.push_back(5); + versionStrings.push_back(String16("v4")); + versionStrings.push_back(String16("v5")); + installers.push_back(String16("")); + installers.push_back(String16("")); + m.updateMap(1, uids, versions, versionStrings, apps, installers); + + ProtoOutputStream proto; + m.appendUidMap(2, config1, nullptr, true, true, &proto); + UidMapping results; + protoOutputStreamToUidMapping(&proto, &results); + ASSERT_EQ(1, results.snapshots_size()); + + // We have to keep at least one snapshot in memory at all times. + proto.clear(); + m.appendUidMap(2, config1, nullptr, true, true, &proto); + protoOutputStreamToUidMapping(&proto, &results); + ASSERT_EQ(1, results.snapshots_size()); + + // Now add another configuration. + m.OnConfigUpdated(config2); + m.updateApp(5, String16(kApp1.c_str()), 1000, 40, String16("v40"), String16("")); + ASSERT_EQ(1U, m.mChanges.size()); + proto.clear(); + m.appendUidMap(6, config1, nullptr, true, true, &proto); + protoOutputStreamToUidMapping(&proto, &results); + ASSERT_EQ(1, results.snapshots_size()); + ASSERT_EQ(1, results.changes_size()); + ASSERT_EQ(1U, m.mChanges.size()); + + // Add another delta update. + m.updateApp(7, String16(kApp2.c_str()), 1001, 41, String16("v41"), String16("")); + ASSERT_EQ(2U, m.mChanges.size()); + + // We still can't remove anything. + proto.clear(); + m.appendUidMap(8, config1, nullptr, true, true, &proto); + protoOutputStreamToUidMapping(&proto, &results); + ASSERT_EQ(1, results.snapshots_size()); + ASSERT_EQ(1, results.changes_size()); + ASSERT_EQ(2U, m.mChanges.size()); + + proto.clear(); + m.appendUidMap(9, config2, nullptr, true, true, &proto); + protoOutputStreamToUidMapping(&proto, &results); + ASSERT_EQ(1, results.snapshots_size()); + ASSERT_EQ(2, results.changes_size()); + // At this point both should be cleared. + ASSERT_EQ(0U, m.mChanges.size()); +} + +TEST(UidMapTest, TestMemoryComputed) { + UidMap m; + + ConfigKey config1(1, StringToId("config1")); + m.OnConfigUpdated(config1); + + size_t startBytes = m.mBytesUsed; + vector uids; + vector versions; + vector apps; + vector versionStrings; + vector installers; + uids.push_back(1000); + apps.push_back(String16(kApp1.c_str())); + versions.push_back(1); + versionStrings.push_back(String16("v1")); + installers.push_back(String16("")); + m.updateMap(1, uids, versions, versionStrings, apps, installers); + + m.updateApp(3, String16(kApp1.c_str()), 1000, 40, String16("v40"), String16("")); + + ProtoOutputStream proto; + vector bytes; + m.appendUidMap(2, config1, nullptr, true, true, &proto); + size_t prevBytes = m.mBytesUsed; + + m.appendUidMap(4, config1, nullptr, true, true, &proto); + EXPECT_TRUE(m.mBytesUsed < prevBytes); +} + +TEST(UidMapTest, TestMemoryGuardrail) { + UidMap m; + string buf; + + ConfigKey config1(1, StringToId("config1")); + m.OnConfigUpdated(config1); + + size_t startBytes = m.mBytesUsed; + vector uids; + vector versions; + vector versionStrings; + vector installers; + vector apps; + for (int i = 0; i < 100; i++) { + uids.push_back(1); + buf = "EXTREMELY_LONG_STRING_FOR_APP_TO_WASTE_MEMORY." + to_string(i); + apps.push_back(String16(buf.c_str())); + versions.push_back(1); + versionStrings.push_back(String16("v1")); + installers.push_back(String16("")); + } + m.updateMap(1, uids, versions, versionStrings, apps, installers); + + m.updateApp(3, String16("EXTREMELY_LONG_STRING_FOR_APP_TO_WASTE_MEMORY.0"), 1000, 2, + String16("v2"), String16("")); + ASSERT_EQ(1U, m.mChanges.size()); + + // Now force deletion by limiting the memory to hold one delta change. + m.maxBytesOverride = 120; // Since the app string alone requires >45 characters. + m.updateApp(5, String16("EXTREMELY_LONG_STRING_FOR_APP_TO_WASTE_MEMORY.0"), 1000, 4, + String16("v4"), String16("")); + ASSERT_EQ(1U, m.mChanges.size()); +} + +#else +GTEST_LOG_(INFO) << "This test does nothing.\n"; +#endif + +} // namespace statsd +} // namespace os +} // namespace android diff --git a/statsd/tests/anomaly/AlarmTracker_test.cpp b/statsd/tests/anomaly/AlarmTracker_test.cpp new file mode 100644 index 00000000..64ea219c --- /dev/null +++ b/statsd/tests/anomaly/AlarmTracker_test.cpp @@ -0,0 +1,94 @@ +// Copyright (C) 2018 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 "src/anomaly/AlarmTracker.h" + +#include +#include +#include +#include + +using namespace testing; +using android::sp; +using std::set; +using std::shared_ptr; +using std::unordered_map; +using std::vector; + +#ifdef __ANDROID__ + +namespace android { +namespace os { +namespace statsd { + +const ConfigKey kConfigKey(0, 12345); + +TEST(AlarmTrackerTest, TestTriggerTimestamp) { + sp subscriberAlarmMonitor = + new AlarmMonitor(100, + [](const shared_ptr&, int64_t){}, + [](const shared_ptr&){}); + Alarm alarm; + alarm.set_offset_millis(15 * MS_PER_SEC); + alarm.set_period_millis(60 * 60 * MS_PER_SEC); // 1hr + int64_t startMillis = 100000000 * MS_PER_SEC; + int64_t nextAlarmTime = startMillis / MS_PER_SEC + 15; + AlarmTracker tracker(startMillis, startMillis, alarm, kConfigKey, subscriberAlarmMonitor); + + EXPECT_EQ(tracker.mAlarmSec, nextAlarmTime); + + uint64_t currentTimeSec = startMillis / MS_PER_SEC + 10; + std::unordered_set, SpHash> firedAlarmSet = + subscriberAlarmMonitor->popSoonerThan(static_cast(currentTimeSec)); + EXPECT_TRUE(firedAlarmSet.empty()); + tracker.informAlarmsFired(currentTimeSec * NS_PER_SEC, firedAlarmSet); + EXPECT_EQ(tracker.mAlarmSec, nextAlarmTime); + EXPECT_EQ(tracker.getAlarmTimestampSec(), nextAlarmTime); + + currentTimeSec = startMillis / MS_PER_SEC + 7000; + nextAlarmTime = startMillis / MS_PER_SEC + 15 + 2 * 60 * 60; + firedAlarmSet = subscriberAlarmMonitor->popSoonerThan(static_cast(currentTimeSec)); + ASSERT_EQ(firedAlarmSet.size(), 1u); + tracker.informAlarmsFired(currentTimeSec * NS_PER_SEC, firedAlarmSet); + EXPECT_TRUE(firedAlarmSet.empty()); + EXPECT_EQ(tracker.mAlarmSec, nextAlarmTime); + EXPECT_EQ(tracker.getAlarmTimestampSec(), nextAlarmTime); + + // Alarm fires exactly on time. + currentTimeSec = startMillis / MS_PER_SEC + 15 + 2 * 60 * 60; + nextAlarmTime = startMillis / MS_PER_SEC + 15 + 3 * 60 * 60; + firedAlarmSet = subscriberAlarmMonitor->popSoonerThan(static_cast(currentTimeSec)); + ASSERT_EQ(firedAlarmSet.size(), 1u); + tracker.informAlarmsFired(currentTimeSec * NS_PER_SEC, firedAlarmSet); + EXPECT_TRUE(firedAlarmSet.empty()); + EXPECT_EQ(tracker.mAlarmSec, nextAlarmTime); + EXPECT_EQ(tracker.getAlarmTimestampSec(), nextAlarmTime); + + // Alarm fires exactly 1 period late. + currentTimeSec = startMillis / MS_PER_SEC + 15 + 4 * 60 * 60; + nextAlarmTime = startMillis / MS_PER_SEC + 15 + 5 * 60 * 60; + firedAlarmSet = subscriberAlarmMonitor->popSoonerThan(static_cast(currentTimeSec)); + ASSERT_EQ(firedAlarmSet.size(), 1u); + tracker.informAlarmsFired(currentTimeSec * NS_PER_SEC, firedAlarmSet); + EXPECT_TRUE(firedAlarmSet.empty()); + EXPECT_EQ(tracker.mAlarmSec, nextAlarmTime); + EXPECT_EQ(tracker.getAlarmTimestampSec(), nextAlarmTime); +} + +} // namespace statsd +} // namespace os +} // namespace android +#else +GTEST_LOG_(INFO) << "This test does nothing.\n"; +#endif diff --git a/statsd/tests/anomaly/AnomalyTracker_test.cpp b/statsd/tests/anomaly/AnomalyTracker_test.cpp new file mode 100644 index 00000000..0cc8af16 --- /dev/null +++ b/statsd/tests/anomaly/AnomalyTracker_test.cpp @@ -0,0 +1,408 @@ +// 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 "src/anomaly/AnomalyTracker.h" + +#include +#include +#include + +#include + +#include "tests/statsd_test_util.h" + +using namespace testing; +using android::sp; +using std::set; +using std::unordered_map; +using std::vector; + +#ifdef __ANDROID__ + +namespace android { +namespace os { +namespace statsd { + +const ConfigKey kConfigKey(0, 12345); + +MetricDimensionKey getMockMetricDimensionKey(int key, string value) { + int pos[] = {key, 0, 0}; + HashableDimensionKey dim; + dim.addValue(FieldValue(Field(1, pos, 0), Value(value))); + return MetricDimensionKey(dim, DEFAULT_DIMENSION_KEY); +} + +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; + } +} + +std::shared_ptr MockBucket( + const std::vector>& key_value_pair_list) { + std::shared_ptr bucket = std::make_shared(); + AddValueToBucket(key_value_pair_list, bucket); + return bucket; +} + +// Returns the value, for the given key, in that bucket, or 0 if not present. +int64_t getBucketValue(const std::shared_ptr& bucket, + const MetricDimensionKey& key) { + const auto& itr = bucket->find(key); + if (itr != bucket->end()) { + return itr->second; + } + return 0; +} + +// Returns true if keys in trueList are detected as anomalies and keys in falseList are not. +bool detectAnomaliesPass(AnomalyTracker& tracker, + const int64_t& bucketNum, + const std::shared_ptr& currentBucket, + const std::set& trueList, + const std::set& falseList) { + for (const MetricDimensionKey& key : trueList) { + if (!tracker.detectAnomaly(bucketNum, key, getBucketValue(currentBucket, key))) { + return false; + } + } + for (const MetricDimensionKey& key : falseList) { + if (tracker.detectAnomaly(bucketNum, key, getBucketValue(currentBucket, key))) { + return false; + } + } + return true; +} + +// Calls tracker.detectAndDeclareAnomaly on each key in the bucket. +void detectAndDeclareAnomalies(AnomalyTracker& tracker, + const int64_t& bucketNum, + const std::shared_ptr& bucket, + const int64_t& eventTimestamp) { + for (const auto& kv : *bucket) { + tracker.detectAndDeclareAnomaly(eventTimestamp, bucketNum, 0 /*metric_id*/, kv.first, + kv.second); + } +} + +// Asserts that the refractory time for each key in timestamps is the corresponding +// timestamp (in ns) + refractoryPeriodSec. +// If a timestamp value is negative, instead asserts that the refractory period is inapplicable +// (either non-existant or already past). +void checkRefractoryTimes(AnomalyTracker& tracker, + const int64_t& currTimestampNs, + const int32_t& refractoryPeriodSec, + 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. + EXPECT_LT(tracker.getRefractoryPeriodEndsSec(kv.first) * NS_PER_SEC, + (uint64_t)currTimestampNs) + << "Failure was at currTimestampNs " << currTimestampNs; + } else { + EXPECT_EQ(tracker.getRefractoryPeriodEndsSec(kv.first), + std::ceil(1.0 * kv.second / NS_PER_SEC) + refractoryPeriodSec) + << "Failure was at currTimestampNs " << currTimestampNs; + } + } +} + +TEST(AnomalyTrackerTest, TestConsecutiveBuckets) { + const int64_t bucketSizeNs = 30 * NS_PER_SEC; + const int32_t refractoryPeriodSec = 2 * bucketSizeNs / NS_PER_SEC; + Alert alert; + alert.set_num_buckets(3); + alert.set_refractory_period_secs(refractoryPeriodSec); + alert.set_trigger_if_sum_gt(2); + + AnomalyTracker anomalyTracker(alert, kConfigKey); + 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; + int64_t eventTimestamp2 = 2 * bucketSizeNs + 12 * NS_PER_SEC; + int64_t eventTimestamp3 = 3 * bucketSizeNs + 13 * NS_PER_SEC; + int64_t eventTimestamp4 = 4 * bucketSizeNs + 14 * NS_PER_SEC; + int64_t eventTimestamp5 = 5 * bucketSizeNs + 5 * NS_PER_SEC; + int64_t eventTimestamp6 = 6 * bucketSizeNs + 16 * NS_PER_SEC; + + std::shared_ptr bucket0 = MockBucket({{keyA, 1}, {keyB, 2}, {keyC, 1}}); + std::shared_ptr bucket1 = MockBucket({{keyA, 1}}); + std::shared_ptr bucket2 = MockBucket({{keyB, 1}}); + std::shared_ptr bucket3 = MockBucket({{keyA, 2}}); + std::shared_ptr bucket4 = MockBucket({{keyB, 5}}); + std::shared_ptr bucket5 = MockBucket({{keyA, 2}}); + std::shared_ptr bucket6 = MockBucket({{keyA, 2}}); + + // Start time with no events. + ASSERT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 0u); + EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, -1LL); + + // Event from bucket #0 occurs. + EXPECT_TRUE(detectAnomaliesPass(anomalyTracker, 0, bucket0, {}, {keyA, keyB, keyC})); + detectAndDeclareAnomalies(anomalyTracker, 0, bucket0, eventTimestamp1); + checkRefractoryTimes(anomalyTracker, eventTimestamp0, refractoryPeriodSec, + {{keyA, -1}, {keyB, -1}, {keyC, -1}}); + + // Adds past bucket #0 + anomalyTracker.addPastBucket(bucket0, 0); + ASSERT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 3u); + EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyA), 1LL); + EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyB), 2LL); + EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyC), 1LL); + EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 0LL); + + // Event from bucket #1 occurs. + EXPECT_TRUE(detectAnomaliesPass(anomalyTracker, 1, bucket1, {}, {keyA, keyB, keyC})); + detectAndDeclareAnomalies(anomalyTracker, 1, bucket1, eventTimestamp1); + checkRefractoryTimes(anomalyTracker, eventTimestamp1, refractoryPeriodSec, + {{keyA, -1}, {keyB, -1}, {keyC, -1}}); + + // Adds past bucket #0 again. The sum does not change. + anomalyTracker.addPastBucket(bucket0, 0); + ASSERT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 3u); + EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyA), 1LL); + EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyB), 2LL); + EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyC), 1LL); + EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 0LL); + EXPECT_TRUE(detectAnomaliesPass(anomalyTracker, 1, bucket1, {}, {keyA, keyB, keyC})); + detectAndDeclareAnomalies(anomalyTracker, 1, bucket1, eventTimestamp1 + 1); + checkRefractoryTimes(anomalyTracker, eventTimestamp1, refractoryPeriodSec, + {{keyA, -1}, {keyB, -1}, {keyC, -1}}); + + // Adds past bucket #1. + anomalyTracker.addPastBucket(bucket1, 1); + EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 1L); + ASSERT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 3UL); + EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyA), 2LL); + EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyB), 2LL); + EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyC), 1LL); + + // Event from bucket #2 occurs. New anomaly on keyB. + EXPECT_TRUE(detectAnomaliesPass(anomalyTracker, 2, bucket2, {keyB}, {keyA, keyC})); + detectAndDeclareAnomalies(anomalyTracker, 2, bucket2, eventTimestamp2); + checkRefractoryTimes(anomalyTracker, eventTimestamp2, refractoryPeriodSec, + {{keyA, -1}, {keyB, eventTimestamp2}, {keyC, -1}}); + + // Adds past bucket #1 again. Nothing changes. + anomalyTracker.addPastBucket(bucket1, 1); + EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 1L); + ASSERT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 3UL); + EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyA), 2LL); + EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyB), 2LL); + EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyC), 1LL); + // Event from bucket #2 occurs (again). + EXPECT_TRUE(detectAnomaliesPass(anomalyTracker, 2, bucket2, {keyB}, {keyA, keyC})); + detectAndDeclareAnomalies(anomalyTracker, 2, bucket2, eventTimestamp2 + 1); + checkRefractoryTimes(anomalyTracker, eventTimestamp2, refractoryPeriodSec, + {{keyA, -1}, {keyB, eventTimestamp2}, {keyC, -1}}); + + // Adds past bucket #2. + anomalyTracker.addPastBucket(bucket2, 2); + EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 2L); + ASSERT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 2UL); + EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyA), 1LL); + EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyB), 1LL); + + // Event from bucket #3 occurs. New anomaly on keyA. + EXPECT_TRUE(detectAnomaliesPass(anomalyTracker, 3, bucket3, {keyA}, {keyB, keyC})); + detectAndDeclareAnomalies(anomalyTracker, 3, bucket3, eventTimestamp3); + checkRefractoryTimes(anomalyTracker, eventTimestamp3, refractoryPeriodSec, + {{keyA, eventTimestamp3}, {keyB, eventTimestamp2}, {keyC, -1}}); + + // Adds bucket #3. + anomalyTracker.addPastBucket(bucket3, 3L); + EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 3L); + ASSERT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 2UL); + EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyA), 2LL); + EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyB), 1LL); + + // Event from bucket #4 occurs. New anomaly on keyB. + EXPECT_TRUE(detectAnomaliesPass(anomalyTracker, 4, bucket4, {keyB}, {keyA, keyC})); + detectAndDeclareAnomalies(anomalyTracker, 4, bucket4, eventTimestamp4); + checkRefractoryTimes(anomalyTracker, eventTimestamp4, refractoryPeriodSec, + {{keyA, eventTimestamp3}, {keyB, eventTimestamp4}, {keyC, -1}}); + + // Adds bucket #4. + anomalyTracker.addPastBucket(bucket4, 4); + EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 4L); + ASSERT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 2UL); + EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyA), 2LL); + EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyB), 5LL); + + // Event from bucket #5 occurs. New anomaly on keyA, which is still in refractory. + EXPECT_TRUE(detectAnomaliesPass(anomalyTracker, 5, bucket5, {keyA, keyB}, {keyC})); + detectAndDeclareAnomalies(anomalyTracker, 5, bucket5, eventTimestamp5); + checkRefractoryTimes(anomalyTracker, eventTimestamp5, refractoryPeriodSec, + {{keyA, eventTimestamp3}, {keyB, eventTimestamp4}, {keyC, -1}}); + + // Adds bucket #5. + anomalyTracker.addPastBucket(bucket5, 5); + EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 5L); + ASSERT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 2UL); + EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyA), 2LL); + EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyB), 5LL); + + // Event from bucket #6 occurs. New anomaly on keyA, which is now out of refractory. + EXPECT_TRUE(detectAnomaliesPass(anomalyTracker, 6, bucket6, {keyA, keyB}, {keyC})); + detectAndDeclareAnomalies(anomalyTracker, 6, bucket6, eventTimestamp6); + checkRefractoryTimes(anomalyTracker, eventTimestamp6, refractoryPeriodSec, + {{keyA, eventTimestamp6}, {keyB, eventTimestamp4}, {keyC, -1}}); +} + +TEST(AnomalyTrackerTest, TestSparseBuckets) { + const int64_t bucketSizeNs = 30 * NS_PER_SEC; + const int32_t refractoryPeriodSec = 2 * bucketSizeNs / NS_PER_SEC; + Alert alert; + alert.set_num_buckets(3); + alert.set_refractory_period_secs(refractoryPeriodSec); + alert.set_trigger_if_sum_gt(2); + + AnomalyTracker anomalyTracker(alert, kConfigKey); + 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}}); + std::shared_ptr bucket18 = MockBucket({{keyB, 1}, {keyC, 1}}); + std::shared_ptr bucket20 = MockBucket({{keyB, 3}, {keyC, 1}}); + std::shared_ptr bucket25 = MockBucket({{keyD, 1}}); + std::shared_ptr bucket28 = MockBucket({{keyE, 2}}); + + int64_t eventTimestamp1 = bucketSizeNs * 8 + 1; + int64_t eventTimestamp2 = bucketSizeNs * 15 + 11; + int64_t eventTimestamp3 = bucketSizeNs * 17 + 1; + int64_t eventTimestamp4 = bucketSizeNs * 19 + 2; + int64_t eventTimestamp5 = bucketSizeNs * 24 + 3; + int64_t eventTimestamp6 = bucketSizeNs * 27 + 3; + + EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, -1LL); + ASSERT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 0UL); + EXPECT_TRUE(detectAnomaliesPass(anomalyTracker, 9, bucket9, {}, {keyA, keyB, keyC, keyD})); + detectAndDeclareAnomalies(anomalyTracker, 9, bucket9, eventTimestamp1); + checkRefractoryTimes(anomalyTracker, eventTimestamp1, refractoryPeriodSec, + {{keyA, -1}, {keyB, -1}, {keyC, -1}, {keyD, -1}, {keyE, -1}}); + + // Add past bucket #9 + anomalyTracker.addPastBucket(bucket9, 9); + EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 9L); + ASSERT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 3UL); + EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyA), 1LL); + EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyB), 2LL); + EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyC), 1LL); + EXPECT_TRUE(detectAnomaliesPass(anomalyTracker, 16, bucket16, {keyB}, {keyA, keyC, keyD})); + ASSERT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 0UL); + EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 15L); + detectAndDeclareAnomalies(anomalyTracker, 16, bucket16, eventTimestamp2); + ASSERT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 0UL); + EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 15L); + checkRefractoryTimes(anomalyTracker, eventTimestamp2, refractoryPeriodSec, + {{keyA, -1}, {keyB, eventTimestamp2}, {keyC, -1}, {keyD, -1}, {keyE, -1}}); + + // Add past bucket #16 + anomalyTracker.addPastBucket(bucket16, 16); + EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 16L); + ASSERT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 1UL); + EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyB), 4LL); + EXPECT_TRUE(detectAnomaliesPass(anomalyTracker, 18, bucket18, {keyB}, {keyA, keyC, keyD})); + ASSERT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 1UL); + EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyB), 4LL); + // Within refractory period. + detectAndDeclareAnomalies(anomalyTracker, 18, bucket18, eventTimestamp3); + checkRefractoryTimes(anomalyTracker, eventTimestamp3, refractoryPeriodSec, + {{keyA, -1}, {keyB, eventTimestamp2}, {keyC, -1}, {keyD, -1}, {keyE, -1}}); + ASSERT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 1UL); + EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyB), 4LL); + + // Add past bucket #18 + anomalyTracker.addPastBucket(bucket18, 18); + EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 18L); + ASSERT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 2UL); + EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyB), 1LL); + EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyC), 1LL); + EXPECT_TRUE(detectAnomaliesPass(anomalyTracker, 20, bucket20, {keyB}, {keyA, keyC, keyD})); + EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 19L); + ASSERT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 2UL); + EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyB), 1LL); + EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyC), 1LL); + detectAndDeclareAnomalies(anomalyTracker, 20, bucket20, eventTimestamp4); + checkRefractoryTimes(anomalyTracker, eventTimestamp4, refractoryPeriodSec, + {{keyA, -1}, {keyB, eventTimestamp4}, {keyC, -1}, {keyD, -1}, {keyE, -1}}); + + // Add bucket #18 again. Nothing changes. + anomalyTracker.addPastBucket(bucket18, 18); + EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 19L); + ASSERT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 2UL); + EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyB), 1LL); + EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyC), 1LL); + EXPECT_TRUE(detectAnomaliesPass(anomalyTracker, 20, bucket20, {keyB}, {keyA, keyC, keyD})); + ASSERT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 2UL); + EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyB), 1LL); + EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyC), 1LL); + detectAndDeclareAnomalies(anomalyTracker, 20, bucket20, eventTimestamp4 + 1); + // Within refractory period. + checkRefractoryTimes(anomalyTracker, eventTimestamp4 + 1, refractoryPeriodSec, + {{keyA, -1}, {keyB, eventTimestamp4}, {keyC, -1}, {keyD, -1}, {keyE, -1}}); + + // Add past bucket #20 + anomalyTracker.addPastBucket(bucket20, 20); + EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 20L); + ASSERT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 2UL); + EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyB), 3LL); + EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyC), 1LL); + EXPECT_TRUE(detectAnomaliesPass(anomalyTracker, 25, bucket25, {}, {keyA, keyB, keyC, keyD})); + EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 24L); + ASSERT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 0UL); + detectAndDeclareAnomalies(anomalyTracker, 25, bucket25, eventTimestamp5); + checkRefractoryTimes(anomalyTracker, eventTimestamp5, refractoryPeriodSec, + {{keyA, -1}, {keyB, eventTimestamp4}, {keyC, -1}, {keyD, -1}, {keyE, -1}}); + + // Add past bucket #25 + anomalyTracker.addPastBucket(bucket25, 25); + EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 25L); + ASSERT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 1UL); + EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyD), 1LL); + EXPECT_TRUE(detectAnomaliesPass(anomalyTracker, 28, bucket28, {}, + {keyA, keyB, keyC, keyD, keyE})); + EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 27L); + ASSERT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 0UL); + detectAndDeclareAnomalies(anomalyTracker, 28, bucket28, eventTimestamp6); + ASSERT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 0UL); + checkRefractoryTimes(anomalyTracker, eventTimestamp6, refractoryPeriodSec, + {{keyA, -1}, {keyB, -1}, {keyC, -1}, {keyD, -1}, {keyE, -1}}); + + // Updates current bucket #28. + (*bucket28)[keyE] = 5; + EXPECT_TRUE(detectAnomaliesPass(anomalyTracker, 28, bucket28, {keyE}, + {keyA, keyB, keyC, keyD})); + EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 27L); + ASSERT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 0UL); + detectAndDeclareAnomalies(anomalyTracker, 28, bucket28, eventTimestamp6 + 7); + ASSERT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 0UL); + checkRefractoryTimes(anomalyTracker, eventTimestamp6, refractoryPeriodSec, + {{keyA, -1}, {keyB, -1}, {keyC, -1}, {keyD, -1}, {keyE, eventTimestamp6 + 7}}); +} + +} // namespace statsd +} // namespace os +} // namespace android +#else +GTEST_LOG_(INFO) << "This test does nothing.\n"; +#endif diff --git a/statsd/tests/condition/CombinationConditionTracker_test.cpp b/statsd/tests/condition/CombinationConditionTracker_test.cpp new file mode 100644 index 00000000..492143b1 --- /dev/null +++ b/statsd/tests/condition/CombinationConditionTracker_test.cpp @@ -0,0 +1,167 @@ +// 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 "condition/condition_util.h" +#include "src/statsd_config.pb.h" + +#include + +#include +#include + +using namespace android::os::statsd; +using std::vector; + +#ifdef __ANDROID__ + +TEST(ConditionTrackerTest, TestUnknownCondition) { + LogicalOperation operation = LogicalOperation::AND; + + vector children; + children.push_back(0); + children.push_back(1); + children.push_back(2); + + vector conditionResults; + conditionResults.push_back(ConditionState::kUnknown); + conditionResults.push_back(ConditionState::kFalse); + conditionResults.push_back(ConditionState::kTrue); + + EXPECT_EQ(evaluateCombinationCondition(children, operation, conditionResults), + ConditionState::kUnknown); +} + +TEST(ConditionTrackerTest, TestAndCondition) { + // Set up the matcher + LogicalOperation operation = LogicalOperation::AND; + + vector children; + children.push_back(0); + children.push_back(1); + children.push_back(2); + + vector conditionResults; + conditionResults.push_back(ConditionState::kTrue); + conditionResults.push_back(ConditionState::kFalse); + conditionResults.push_back(ConditionState::kTrue); + + EXPECT_FALSE(evaluateCombinationCondition(children, operation, conditionResults)); + + conditionResults.clear(); + conditionResults.push_back(ConditionState::kTrue); + conditionResults.push_back(ConditionState::kTrue); + conditionResults.push_back(ConditionState::kTrue); + + EXPECT_TRUE(evaluateCombinationCondition(children, operation, conditionResults)); +} + +TEST(ConditionTrackerTest, TestOrCondition) { + // Set up the matcher + LogicalOperation operation = LogicalOperation::OR; + + vector children; + children.push_back(0); + children.push_back(1); + children.push_back(2); + + vector conditionResults; + conditionResults.push_back(ConditionState::kTrue); + conditionResults.push_back(ConditionState::kFalse); + conditionResults.push_back(ConditionState::kTrue); + + EXPECT_TRUE(evaluateCombinationCondition(children, operation, conditionResults)); + + conditionResults.clear(); + conditionResults.push_back(ConditionState::kFalse); + conditionResults.push_back(ConditionState::kFalse); + conditionResults.push_back(ConditionState::kFalse); + + EXPECT_FALSE(evaluateCombinationCondition(children, operation, conditionResults)); +} + +TEST(ConditionTrackerTest, TestNotCondition) { + // Set up the matcher + LogicalOperation operation = LogicalOperation::NOT; + + vector children; + children.push_back(0); + + vector conditionResults; + conditionResults.push_back(ConditionState::kTrue); + + EXPECT_FALSE(evaluateCombinationCondition(children, operation, conditionResults)); + + conditionResults.clear(); + conditionResults.push_back(ConditionState::kFalse); + EXPECT_TRUE(evaluateCombinationCondition(children, operation, conditionResults)); + + children.clear(); + conditionResults.clear(); + EXPECT_EQ(evaluateCombinationCondition(children, operation, conditionResults), + ConditionState::kUnknown); +} + +TEST(ConditionTrackerTest, TestNandCondition) { + // Set up the matcher + LogicalOperation operation = LogicalOperation::NAND; + + vector children; + children.push_back(0); + children.push_back(1); + + vector conditionResults; + conditionResults.push_back(ConditionState::kTrue); + conditionResults.push_back(ConditionState::kFalse); + + EXPECT_TRUE(evaluateCombinationCondition(children, operation, conditionResults)); + + conditionResults.clear(); + conditionResults.push_back(ConditionState::kFalse); + conditionResults.push_back(ConditionState::kFalse); + EXPECT_TRUE(evaluateCombinationCondition(children, operation, conditionResults)); + + conditionResults.clear(); + conditionResults.push_back(ConditionState::kTrue); + conditionResults.push_back(ConditionState::kTrue); + EXPECT_FALSE(evaluateCombinationCondition(children, operation, conditionResults)); +} + +TEST(ConditionTrackerTest, TestNorCondition) { + // Set up the matcher + LogicalOperation operation = LogicalOperation::NOR; + + vector children; + children.push_back(0); + children.push_back(1); + + vector conditionResults; + conditionResults.push_back(ConditionState::kTrue); + conditionResults.push_back(ConditionState::kFalse); + + EXPECT_FALSE(evaluateCombinationCondition(children, operation, conditionResults)); + + conditionResults.clear(); + conditionResults.push_back(ConditionState::kFalse); + conditionResults.push_back(ConditionState::kFalse); + EXPECT_TRUE(evaluateCombinationCondition(children, operation, conditionResults)); + + conditionResults.clear(); + conditionResults.push_back(ConditionState::kTrue); + conditionResults.push_back(ConditionState::kTrue); + EXPECT_FALSE(evaluateCombinationCondition(children, operation, conditionResults)); +} + +#else +GTEST_LOG_(INFO) << "This test does nothing.\n"; +#endif diff --git a/statsd/tests/condition/ConditionTimer_test.cpp b/statsd/tests/condition/ConditionTimer_test.cpp new file mode 100644 index 00000000..46dc9a9d --- /dev/null +++ b/statsd/tests/condition/ConditionTimer_test.cpp @@ -0,0 +1,68 @@ +// Copyright (C) 2019 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 "src/condition/ConditionTimer.h" + +#include +#include + +#ifdef __ANDROID__ + +namespace android { +namespace os { +namespace statsd { + +static int64_t time_base = 10; +static int64_t ct_start_time = 200; + +TEST(ConditionTimerTest, TestTimer_Inital_False) { + ConditionTimer timer(false, time_base); + EXPECT_EQ(false, timer.mCondition); + EXPECT_EQ(0, timer.mTimerNs); + + EXPECT_EQ(0, timer.newBucketStart(ct_start_time)); + EXPECT_EQ(0, timer.mTimerNs); + + timer.onConditionChanged(true, ct_start_time + 5); + EXPECT_EQ(ct_start_time + 5, timer.mLastConditionChangeTimestampNs); + EXPECT_EQ(true, timer.mCondition); + + EXPECT_EQ(95, timer.newBucketStart(ct_start_time + 100)); + EXPECT_EQ(ct_start_time + 100, timer.mLastConditionChangeTimestampNs); + EXPECT_EQ(true, timer.mCondition); +} + +TEST(ConditionTimerTest, TestTimer_Inital_True) { + ConditionTimer timer(true, time_base); + EXPECT_EQ(true, timer.mCondition); + EXPECT_EQ(0, timer.mTimerNs); + + EXPECT_EQ(ct_start_time - time_base, timer.newBucketStart(ct_start_time)); + EXPECT_EQ(true, timer.mCondition); + EXPECT_EQ(0, timer.mTimerNs); + EXPECT_EQ(ct_start_time, timer.mLastConditionChangeTimestampNs); + + timer.onConditionChanged(false, ct_start_time + 5); + EXPECT_EQ(5, timer.mTimerNs); + + EXPECT_EQ(5, timer.newBucketStart(ct_start_time + 100)); + EXPECT_EQ(0, timer.mTimerNs); +} + +} // namespace statsd +} // namespace os +} // namespace android +#else +GTEST_LOG_(INFO) << "This test does nothing.\n"; +#endif diff --git a/statsd/tests/condition/SimpleConditionTracker_test.cpp b/statsd/tests/condition/SimpleConditionTracker_test.cpp new file mode 100644 index 00000000..8998b5f9 --- /dev/null +++ b/statsd/tests/condition/SimpleConditionTracker_test.cpp @@ -0,0 +1,741 @@ +// 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 "src/condition/SimpleConditionTracker.h" +#include "stats_event.h" +#include "tests/statsd_test_util.h" + +#include +#include +#include +#include +#include + +using std::map; +using std::unordered_map; +using std::vector; + +#ifdef __ANDROID__ + +namespace android { +namespace os { +namespace statsd { + +namespace { + +const ConfigKey kConfigKey(0, 12345); + +const int ATTRIBUTION_NODE_FIELD_ID = 1; +const int ATTRIBUTION_UID_FIELD_ID = 1; +const int TAG_ID = 1; +const uint64_t protoHash = 0x123456789; + +SimplePredicate getWakeLockHeldCondition(bool countNesting, bool defaultFalse, + bool outputSlicedUid, Position position) { + SimplePredicate simplePredicate; + simplePredicate.set_start(StringToId("WAKE_LOCK_ACQUIRE")); + simplePredicate.set_stop(StringToId("WAKE_LOCK_RELEASE")); + simplePredicate.set_stop_all(StringToId("RELEASE_ALL")); + if (outputSlicedUid) { + simplePredicate.mutable_dimensions()->set_field(TAG_ID); + simplePredicate.mutable_dimensions()->add_child()->set_field(ATTRIBUTION_NODE_FIELD_ID); + simplePredicate.mutable_dimensions()->mutable_child(0)->set_position(position); + simplePredicate.mutable_dimensions()->mutable_child(0)->add_child()->set_field( + ATTRIBUTION_UID_FIELD_ID); + } + + simplePredicate.set_count_nesting(countNesting); + simplePredicate.set_initial_value(defaultFalse ? SimplePredicate_InitialValue_FALSE + : SimplePredicate_InitialValue_UNKNOWN); + return simplePredicate; +} + +void makeWakeLockEvent(LogEvent* logEvent, uint32_t atomId, uint64_t timestamp, + const vector& uids, const string& wl, int acquire) { + AStatsEvent* statsEvent = AStatsEvent_obtain(); + AStatsEvent_setAtomId(statsEvent, atomId); + AStatsEvent_overwriteTimestamp(statsEvent, timestamp); + + vector tags(uids.size()); // vector of empty strings + writeAttribution(statsEvent, uids, tags); + + AStatsEvent_writeString(statsEvent, wl.c_str()); + AStatsEvent_writeInt32(statsEvent, acquire); + + parseStatsEventToLogEvent(statsEvent, logEvent); +} + +} // anonymous namespace + + +std::map getWakeLockQueryKey( + const Position position, + const std::vector &uids, const string& conditionName) { + std::map outputKeyMap; + std::vector uid_indexes; + int pos[] = {1, 1, 1}; + int depth = 2; + Field field(1, pos, depth); + switch(position) { + case Position::FIRST: + uid_indexes.push_back(0); + break; + case Position::LAST: + uid_indexes.push_back(uids.size() - 1); + field.setField(0x02018001); + break; + case Position::ANY: + uid_indexes.resize(uids.size()); + std::iota(uid_indexes.begin(), uid_indexes.end(), 0); + field.setField(0x02010001); + break; + default: + break; + } + + for (const int idx : uid_indexes) { + Value value((int32_t)uids[idx]); + HashableDimensionKey dim; + dim.addValue(FieldValue(field, value)); + outputKeyMap[StringToId(conditionName)] = dim; + } + return outputKeyMap; +} + +TEST(SimpleConditionTrackerTest, TestNonSlicedInitialValueFalse) { + SimplePredicate simplePredicate; + simplePredicate.set_start(StringToId("SCREEN_TURNED_ON")); + simplePredicate.set_stop(StringToId("SCREEN_TURNED_OFF")); + simplePredicate.set_count_nesting(false); + simplePredicate.set_initial_value(SimplePredicate_InitialValue_FALSE); + + unordered_map trackerNameIndexMap; + trackerNameIndexMap[StringToId("SCREEN_TURNED_ON")] = 0; + trackerNameIndexMap[StringToId("SCREEN_TURNED_OFF")] = 1; + + SimpleConditionTracker conditionTracker(kConfigKey, StringToId("SCREEN_IS_ON"), protoHash, + 0 /*tracker index*/, simplePredicate, + trackerNameIndexMap); + + ConditionKey queryKey; + vector> allPredicates; + vector conditionCache(1, ConditionState::kNotEvaluated); + + // Check that initial condition is false. + conditionTracker.isConditionMet(queryKey, allPredicates, false, conditionCache); + EXPECT_EQ(ConditionState::kFalse, conditionCache[0]); + + vector matcherState; + vector changedCache(1, false); + + // Matched stop event. + // Check that condition is still false. + unique_ptr screenOffEvent = + CreateScreenStateChangedEvent(/*timestamp=*/50, android::view::DISPLAY_STATE_OFF); + matcherState.clear(); + matcherState.push_back(MatchingState::kNotMatched); // On matcher not matched + matcherState.push_back(MatchingState::kMatched); // Off matcher matched + conditionCache[0] = ConditionState::kNotEvaluated; + conditionTracker.evaluateCondition(*screenOffEvent, matcherState, allPredicates, conditionCache, + changedCache); + EXPECT_EQ(ConditionState::kFalse, conditionCache[0]); + EXPECT_FALSE(changedCache[0]); + + // Matched start event. + // Check that condition has changed to true. + unique_ptr screenOnEvent = + CreateScreenStateChangedEvent(/*timestamp=*/100, android::view::DISPLAY_STATE_ON); + matcherState.clear(); + matcherState.push_back(MatchingState::kMatched); // On matcher matched + matcherState.push_back(MatchingState::kNotMatched); // Off matcher not matched + conditionCache[0] = ConditionState::kNotEvaluated; + changedCache[0] = false; + conditionTracker.evaluateCondition(*screenOnEvent, matcherState, allPredicates, conditionCache, + changedCache); + EXPECT_EQ(ConditionState::kTrue, conditionCache[0]); + EXPECT_TRUE(changedCache[0]); +} + +TEST(SimpleConditionTrackerTest, TestNonSlicedInitialValueUnknown) { + SimplePredicate simplePredicate; + simplePredicate.set_start(StringToId("SCREEN_TURNED_ON")); + simplePredicate.set_stop(StringToId("SCREEN_TURNED_OFF")); + simplePredicate.set_count_nesting(false); + simplePredicate.set_initial_value(SimplePredicate_InitialValue_UNKNOWN); + + unordered_map trackerNameIndexMap; + trackerNameIndexMap[StringToId("SCREEN_TURNED_ON")] = 0; + trackerNameIndexMap[StringToId("SCREEN_TURNED_OFF")] = 1; + + SimpleConditionTracker conditionTracker(kConfigKey, StringToId("SCREEN_IS_ON"), protoHash, + 0 /*tracker index*/, simplePredicate, + trackerNameIndexMap); + + ConditionKey queryKey; + vector> allPredicates; + vector conditionCache(1, ConditionState::kNotEvaluated); + + // Check that initial condition is unknown. + conditionTracker.isConditionMet(queryKey, allPredicates, false, conditionCache); + EXPECT_EQ(ConditionState::kUnknown, conditionCache[0]); + + vector matcherState; + vector changedCache(1, false); + + // Matched stop event. + // Check that condition is changed to false. + unique_ptr screenOffEvent = + CreateScreenStateChangedEvent(/*timestamp=*/50, android::view::DISPLAY_STATE_OFF); + matcherState.clear(); + matcherState.push_back(MatchingState::kNotMatched); // On matcher not matched + matcherState.push_back(MatchingState::kMatched); // Off matcher matched + conditionCache[0] = ConditionState::kNotEvaluated; + conditionTracker.evaluateCondition(*screenOffEvent, matcherState, allPredicates, conditionCache, + changedCache); + EXPECT_EQ(ConditionState::kFalse, conditionCache[0]); + EXPECT_TRUE(changedCache[0]); + + // Matched start event. + // Check that condition has changed to true. + unique_ptr screenOnEvent = + CreateScreenStateChangedEvent(/*timestamp=*/100, android::view::DISPLAY_STATE_ON); + matcherState.clear(); + matcherState.push_back(MatchingState::kMatched); // On matcher matched + matcherState.push_back(MatchingState::kNotMatched); // Off matcher not matched + conditionCache[0] = ConditionState::kNotEvaluated; + changedCache[0] = false; + conditionTracker.evaluateCondition(*screenOnEvent, matcherState, allPredicates, conditionCache, + changedCache); + EXPECT_EQ(ConditionState::kTrue, conditionCache[0]); + EXPECT_TRUE(changedCache[0]); +} + +TEST(SimpleConditionTrackerTest, TestNonSlicedCondition) { + SimplePredicate simplePredicate; + simplePredicate.set_start(StringToId("SCREEN_TURNED_ON")); + simplePredicate.set_stop(StringToId("SCREEN_TURNED_OFF")); + simplePredicate.set_count_nesting(false); + simplePredicate.set_initial_value(SimplePredicate_InitialValue_UNKNOWN); + + unordered_map trackerNameIndexMap; + trackerNameIndexMap[StringToId("SCREEN_TURNED_ON")] = 0; + trackerNameIndexMap[StringToId("SCREEN_TURNED_OFF")] = 1; + + SimpleConditionTracker conditionTracker(kConfigKey, StringToId("SCREEN_IS_ON"), protoHash, + 0 /*tracker index*/, simplePredicate, + trackerNameIndexMap); + EXPECT_FALSE(conditionTracker.isSliced()); + + // This event is not accessed in this test besides dimensions which is why this is okay. + // This is technically an invalid LogEvent because we do not call parseBuffer. + LogEvent event(/*uid=*/0, /*pid=*/0); + + vector matcherState; + matcherState.push_back(MatchingState::kNotMatched); + matcherState.push_back(MatchingState::kNotMatched); + + vector> allPredicates; + vector conditionCache(1, ConditionState::kNotEvaluated); + vector changedCache(1, false); + + conditionTracker.evaluateCondition(event, matcherState, allPredicates, conditionCache, + changedCache); + // not matched start or stop. condition doesn't change + EXPECT_EQ(ConditionState::kUnknown, conditionCache[0]); + EXPECT_FALSE(changedCache[0]); + + // prepare a case for match start. + matcherState.clear(); + matcherState.push_back(MatchingState::kMatched); + matcherState.push_back(MatchingState::kNotMatched); + conditionCache[0] = ConditionState::kNotEvaluated; + changedCache[0] = false; + + conditionTracker.evaluateCondition(event, matcherState, allPredicates, conditionCache, + changedCache); + // now condition should change to true. + EXPECT_EQ(ConditionState::kTrue, conditionCache[0]); + EXPECT_TRUE(changedCache[0]); + + // match nothing. + matcherState.clear(); + matcherState.push_back(MatchingState::kNotMatched); + matcherState.push_back(MatchingState::kNotMatched); + conditionCache[0] = ConditionState::kNotEvaluated; + changedCache[0] = false; + + conditionTracker.evaluateCondition(event, matcherState, allPredicates, conditionCache, + changedCache); + EXPECT_EQ(ConditionState::kTrue, conditionCache[0]); + EXPECT_FALSE(changedCache[0]); + + // the case for match stop. + matcherState.clear(); + matcherState.push_back(MatchingState::kNotMatched); + matcherState.push_back(MatchingState::kMatched); + conditionCache[0] = ConditionState::kNotEvaluated; + changedCache[0] = false; + + conditionTracker.evaluateCondition(event, matcherState, allPredicates, conditionCache, + changedCache); + + // condition changes to false. + EXPECT_EQ(ConditionState::kFalse, conditionCache[0]); + EXPECT_TRUE(changedCache[0]); + + // match stop again. + matcherState.clear(); + matcherState.push_back(MatchingState::kNotMatched); + matcherState.push_back(MatchingState::kMatched); + conditionCache[0] = ConditionState::kNotEvaluated; + changedCache[0] = false; + + conditionTracker.evaluateCondition(event, matcherState, allPredicates, conditionCache, + changedCache); + // condition should still be false. not changed. + EXPECT_EQ(ConditionState::kFalse, conditionCache[0]); + EXPECT_FALSE(changedCache[0]); +} + +TEST(SimpleConditionTrackerTest, TestNonSlicedConditionNestCounting) { + std::vector> allConditions; + SimplePredicate simplePredicate; + simplePredicate.set_start(StringToId("SCREEN_TURNED_ON")); + simplePredicate.set_stop(StringToId("SCREEN_TURNED_OFF")); + simplePredicate.set_count_nesting(true); + + unordered_map trackerNameIndexMap; + trackerNameIndexMap[StringToId("SCREEN_TURNED_ON")] = 0; + trackerNameIndexMap[StringToId("SCREEN_TURNED_OFF")] = 1; + + SimpleConditionTracker conditionTracker(kConfigKey, StringToId("SCREEN_IS_ON"), protoHash, + 0 /*condition tracker index*/, simplePredicate, + trackerNameIndexMap); + EXPECT_FALSE(conditionTracker.isSliced()); + + // This event is not accessed in this test besides dimensions which is why this is okay. + // This is technically an invalid LogEvent because we do not call parseBuffer. + LogEvent event(/*uid=*/0, /*pid=*/0); + + // one matched start + vector matcherState; + matcherState.push_back(MatchingState::kMatched); + matcherState.push_back(MatchingState::kNotMatched); + vector> allPredicates; + vector conditionCache(1, ConditionState::kNotEvaluated); + vector changedCache(1, false); + + conditionTracker.evaluateCondition(event, matcherState, allPredicates, conditionCache, + changedCache); + + EXPECT_EQ(ConditionState::kTrue, conditionCache[0]); + EXPECT_TRUE(changedCache[0]); + + // prepare for another matched start. + matcherState.clear(); + matcherState.push_back(MatchingState::kMatched); + matcherState.push_back(MatchingState::kNotMatched); + conditionCache[0] = ConditionState::kNotEvaluated; + changedCache[0] = false; + + conditionTracker.evaluateCondition(event, matcherState, allPredicates, conditionCache, + changedCache); + + EXPECT_EQ(ConditionState::kTrue, conditionCache[0]); + EXPECT_FALSE(changedCache[0]); + + // ONE MATCHED STOP + matcherState.clear(); + matcherState.push_back(MatchingState::kNotMatched); + matcherState.push_back(MatchingState::kMatched); + conditionCache[0] = ConditionState::kNotEvaluated; + changedCache[0] = false; + + conditionTracker.evaluateCondition(event, matcherState, allPredicates, conditionCache, + changedCache); + // result should still be true + EXPECT_EQ(ConditionState::kTrue, conditionCache[0]); + EXPECT_FALSE(changedCache[0]); + + // ANOTHER MATCHED STOP + matcherState.clear(); + matcherState.push_back(MatchingState::kNotMatched); + matcherState.push_back(MatchingState::kMatched); + conditionCache[0] = ConditionState::kNotEvaluated; + changedCache[0] = false; + + conditionTracker.evaluateCondition(event, matcherState, allPredicates, conditionCache, + changedCache); + EXPECT_EQ(ConditionState::kFalse, conditionCache[0]); + EXPECT_TRUE(changedCache[0]); +} + +TEST(SimpleConditionTrackerTest, TestSlicedCondition) { + std::vector> allConditions; + for (Position position : {Position::FIRST, Position::LAST}) { + SimplePredicate simplePredicate = getWakeLockHeldCondition( + true /*nesting*/, true /*default to false*/, true /*output slice by uid*/, + position); + string conditionName = "WL_HELD_BY_UID2"; + + unordered_map trackerNameIndexMap; + trackerNameIndexMap[StringToId("WAKE_LOCK_ACQUIRE")] = 0; + trackerNameIndexMap[StringToId("WAKE_LOCK_RELEASE")] = 1; + trackerNameIndexMap[StringToId("RELEASE_ALL")] = 2; + + SimpleConditionTracker conditionTracker(kConfigKey, StringToId(conditionName), protoHash, + 0 /*condition tracker index*/, simplePredicate, + trackerNameIndexMap); + + std::vector uids = {111, 222, 333}; + + LogEvent event1(/*uid=*/0, /*pid=*/0); + makeWakeLockEvent(&event1, /*atomId=*/1, /*timestamp=*/0, uids, "wl1", /*acquire=*/1); + + // one matched start + vector matcherState; + matcherState.push_back(MatchingState::kMatched); + matcherState.push_back(MatchingState::kNotMatched); + matcherState.push_back(MatchingState::kNotMatched); + vector> allPredicates; + vector conditionCache(1, ConditionState::kNotEvaluated); + vector changedCache(1, false); + + conditionTracker.evaluateCondition(event1, matcherState, allPredicates, conditionCache, + changedCache); + + if (position == Position::FIRST || position == Position::LAST) { + ASSERT_EQ(1UL, conditionTracker.mSlicedConditionState.size()); + } else { + ASSERT_EQ(uids.size(), conditionTracker.mSlicedConditionState.size()); + } + EXPECT_TRUE(changedCache[0]); + if (position == Position::FIRST || position == Position::LAST) { + ASSERT_EQ(conditionTracker.getChangedToTrueDimensions(allConditions)->size(), 1u); + EXPECT_TRUE(conditionTracker.getChangedToFalseDimensions(allConditions)->empty()); + } else { + EXPECT_EQ(conditionTracker.getChangedToTrueDimensions(allConditions)->size(), + uids.size()); + } + + // Now test query + const auto queryKey = getWakeLockQueryKey(position, uids, conditionName); + conditionCache[0] = ConditionState::kNotEvaluated; + + conditionTracker.isConditionMet(queryKey, allPredicates, false, conditionCache); + EXPECT_EQ(ConditionState::kTrue, conditionCache[0]); + + // another wake lock acquired by this uid + LogEvent event2(/*uid=*/0, /*pid=*/0); + makeWakeLockEvent(&event2, /*atomId=*/1, /*timestamp=*/0, uids, "wl2", /*acquire=*/1); + matcherState.clear(); + matcherState.push_back(MatchingState::kMatched); + matcherState.push_back(MatchingState::kNotMatched); + conditionCache[0] = ConditionState::kNotEvaluated; + changedCache[0] = false; + conditionTracker.evaluateCondition(event2, matcherState, allPredicates, conditionCache, + changedCache); + EXPECT_FALSE(changedCache[0]); + if (position == Position::FIRST || position == Position::LAST) { + ASSERT_EQ(1UL, conditionTracker.mSlicedConditionState.size()); + } else { + ASSERT_EQ(uids.size(), conditionTracker.mSlicedConditionState.size()); + } + EXPECT_TRUE(conditionTracker.getChangedToTrueDimensions(allConditions)->empty()); + EXPECT_TRUE(conditionTracker.getChangedToFalseDimensions(allConditions)->empty()); + + + // wake lock 1 release + LogEvent event3(/*uid=*/0, /*pid=*/0); + makeWakeLockEvent(&event3, /*atomId=*/1, /*timestamp=*/0, uids, "wl1", /*acquire=*/0); + matcherState.clear(); + matcherState.push_back(MatchingState::kNotMatched); + matcherState.push_back(MatchingState::kMatched); + conditionCache[0] = ConditionState::kNotEvaluated; + changedCache[0] = false; + conditionTracker.evaluateCondition(event3, matcherState, allPredicates, conditionCache, + changedCache); + // nothing changes, because wake lock 2 is still held for this uid + EXPECT_FALSE(changedCache[0]); + if (position == Position::FIRST || position == Position::LAST) { + ASSERT_EQ(1UL, conditionTracker.mSlicedConditionState.size()); + } else { + ASSERT_EQ(uids.size(), conditionTracker.mSlicedConditionState.size()); + } + EXPECT_TRUE(conditionTracker.getChangedToTrueDimensions(allConditions)->empty()); + EXPECT_TRUE(conditionTracker.getChangedToFalseDimensions(allConditions)->empty()); + + LogEvent event4(/*uid=*/0, /*pid=*/0); + makeWakeLockEvent(&event4, /*atomId=*/1, /*timestamp=*/0, uids, "wl2", /*acquire=*/0); + matcherState.clear(); + matcherState.push_back(MatchingState::kNotMatched); + matcherState.push_back(MatchingState::kMatched); + conditionCache[0] = ConditionState::kNotEvaluated; + changedCache[0] = false; + conditionTracker.evaluateCondition(event4, matcherState, allPredicates, conditionCache, + changedCache); + ASSERT_EQ(0UL, conditionTracker.mSlicedConditionState.size()); + EXPECT_TRUE(changedCache[0]); + if (position == Position::FIRST || position == Position::LAST) { + ASSERT_EQ(conditionTracker.getChangedToFalseDimensions(allConditions)->size(), 1u); + EXPECT_TRUE(conditionTracker.getChangedToTrueDimensions(allConditions)->empty()); + } else { + EXPECT_EQ(conditionTracker.getChangedToFalseDimensions(allConditions)->size(), + uids.size()); + } + + // query again + conditionCache[0] = ConditionState::kNotEvaluated; + conditionTracker.isConditionMet(queryKey, allPredicates, false, conditionCache); + EXPECT_EQ(ConditionState::kFalse, conditionCache[0]); + } + +} + +TEST(SimpleConditionTrackerTest, TestSlicedWithNoOutputDim) { + std::vector> allConditions; + + SimplePredicate simplePredicate = + getWakeLockHeldCondition(true /*nesting*/, true /*default to false*/, + false /*slice output by uid*/, Position::ANY /* position */); + string conditionName = "WL_HELD"; + + unordered_map trackerNameIndexMap; + trackerNameIndexMap[StringToId("WAKE_LOCK_ACQUIRE")] = 0; + trackerNameIndexMap[StringToId("WAKE_LOCK_RELEASE")] = 1; + trackerNameIndexMap[StringToId("RELEASE_ALL")] = 2; + + SimpleConditionTracker conditionTracker(kConfigKey, StringToId(conditionName), protoHash, + 0 /*condition tracker index*/, simplePredicate, + trackerNameIndexMap); + + EXPECT_FALSE(conditionTracker.isSliced()); + + std::vector uids1 = {111, 1111, 11111}; + string uid1_wl1 = "wl1_1"; + std::vector uids2 = {222, 2222, 22222}; + string uid2_wl1 = "wl2_1"; + + LogEvent event1(/*uid=*/0, /*pid=*/0); + makeWakeLockEvent(&event1, /*atomId=*/1, /*timestamp=*/0, uids1, uid1_wl1, /*acquire=*/1); + + // one matched start for uid1 + vector matcherState; + matcherState.push_back(MatchingState::kMatched); + matcherState.push_back(MatchingState::kNotMatched); + matcherState.push_back(MatchingState::kNotMatched); + vector> allPredicates; + vector conditionCache(1, ConditionState::kNotEvaluated); + vector changedCache(1, false); + + conditionTracker.evaluateCondition(event1, matcherState, allPredicates, conditionCache, + changedCache); + + ASSERT_EQ(1UL, conditionTracker.mSlicedConditionState.size()); + EXPECT_TRUE(changedCache[0]); + + // Now test query + ConditionKey queryKey; + conditionCache[0] = ConditionState::kNotEvaluated; + + conditionTracker.isConditionMet(queryKey, allPredicates, true, conditionCache); + EXPECT_EQ(ConditionState::kTrue, conditionCache[0]); + + // another wake lock acquired by this uid + LogEvent event2(/*uid=*/0, /*pid=*/0); + makeWakeLockEvent(&event2, /*atomId=*/1, /*timestamp=*/0, uids2, uid2_wl1, /*acquire=*/1); + + matcherState.clear(); + matcherState.push_back(MatchingState::kMatched); + matcherState.push_back(MatchingState::kNotMatched); + conditionCache[0] = ConditionState::kNotEvaluated; + changedCache[0] = false; + conditionTracker.evaluateCondition(event2, matcherState, allPredicates, conditionCache, + changedCache); + EXPECT_FALSE(changedCache[0]); + + // uid1 wake lock 1 release + LogEvent event3(/*uid=*/0, /*pid=*/0); + makeWakeLockEvent(&event3, /*atomId=*/1, /*timestamp=*/0, uids1, uid1_wl1, + /*release=*/0); // now release it. + + matcherState.clear(); + matcherState.push_back(MatchingState::kNotMatched); + matcherState.push_back(MatchingState::kMatched); + conditionCache[0] = ConditionState::kNotEvaluated; + changedCache[0] = false; + conditionTracker.evaluateCondition(event3, matcherState, allPredicates, conditionCache, + changedCache); + // nothing changes, because uid2 is still holding wl. + EXPECT_FALSE(changedCache[0]); + + LogEvent event4(/*uid=*/0, /*pid=*/0); + makeWakeLockEvent(&event4, /*atomId=*/1, /*timestamp=*/0, uids2, uid2_wl1, + /*acquire=*/0); // now release it. + matcherState.clear(); + matcherState.push_back(MatchingState::kNotMatched); + matcherState.push_back(MatchingState::kMatched); + conditionCache[0] = ConditionState::kNotEvaluated; + changedCache[0] = false; + conditionTracker.evaluateCondition(event4, matcherState, allPredicates, conditionCache, + changedCache); + ASSERT_EQ(0UL, conditionTracker.mSlicedConditionState.size()); + EXPECT_TRUE(changedCache[0]); + + // query again + conditionCache[0] = ConditionState::kNotEvaluated; + conditionTracker.isConditionMet(queryKey, allPredicates, true, conditionCache); + EXPECT_EQ(ConditionState::kFalse, conditionCache[0]); +} + +TEST(SimpleConditionTrackerTest, TestStopAll) { + std::vector> allConditions; + for (Position position : {Position::FIRST, Position::LAST}) { + SimplePredicate simplePredicate = + getWakeLockHeldCondition(true /*nesting*/, true /*default to false*/, + true /*output slice by uid*/, position); + string conditionName = "WL_HELD_BY_UID3"; + + unordered_map trackerNameIndexMap; + trackerNameIndexMap[StringToId("WAKE_LOCK_ACQUIRE")] = 0; + trackerNameIndexMap[StringToId("WAKE_LOCK_RELEASE")] = 1; + trackerNameIndexMap[StringToId("RELEASE_ALL")] = 2; + + SimpleConditionTracker conditionTracker(kConfigKey, StringToId(conditionName), protoHash, + 0 /*condition tracker index*/, simplePredicate, + trackerNameIndexMap); + + std::vector uids1 = {111, 1111, 11111}; + std::vector uids2 = {222, 2222, 22222}; + + LogEvent event1(/*uid=*/0, /*pid=*/0); + makeWakeLockEvent(&event1, /*atomId=*/1, /*timestamp=*/0, uids1, "wl1", /*acquire=*/1); + + // one matched start + vector matcherState; + matcherState.push_back(MatchingState::kMatched); + matcherState.push_back(MatchingState::kNotMatched); + matcherState.push_back(MatchingState::kNotMatched); + vector> allPredicates; + vector conditionCache(1, ConditionState::kNotEvaluated); + vector changedCache(1, false); + + conditionTracker.evaluateCondition(event1, matcherState, allPredicates, conditionCache, + changedCache); + if (position == Position::FIRST || position == Position::LAST) { + ASSERT_EQ(1UL, conditionTracker.mSlicedConditionState.size()); + } else { + ASSERT_EQ(uids1.size(), conditionTracker.mSlicedConditionState.size()); + } + EXPECT_TRUE(changedCache[0]); + { + if (position == Position::FIRST || position == Position::LAST) { + ASSERT_EQ(1UL, conditionTracker.getChangedToTrueDimensions(allConditions)->size()); + EXPECT_TRUE(conditionTracker.getChangedToFalseDimensions(allConditions)->empty()); + } else { + EXPECT_EQ(uids1.size(), + conditionTracker.getChangedToTrueDimensions(allConditions)->size()); + EXPECT_TRUE(conditionTracker.getChangedToFalseDimensions(allConditions)->empty()); + } + } + + // Now test query + const auto queryKey = getWakeLockQueryKey(position, uids1, conditionName); + conditionCache[0] = ConditionState::kNotEvaluated; + + conditionTracker.isConditionMet(queryKey, allPredicates, false, conditionCache); + EXPECT_EQ(ConditionState::kTrue, conditionCache[0]); + + // another wake lock acquired by uid2 + LogEvent event2(/*uid=*/0, /*pid=*/0); + makeWakeLockEvent(&event2, /*atomId=*/1, /*timestamp=*/0, uids2, "wl2", /*acquire=*/1); + + matcherState.clear(); + matcherState.push_back(MatchingState::kMatched); + matcherState.push_back(MatchingState::kNotMatched); + matcherState.push_back(MatchingState::kNotMatched); + conditionCache[0] = ConditionState::kNotEvaluated; + changedCache[0] = false; + conditionTracker.evaluateCondition(event2, matcherState, allPredicates, conditionCache, + changedCache); + if (position == Position::FIRST || position == Position::LAST) { + ASSERT_EQ(2UL, conditionTracker.mSlicedConditionState.size()); + } else { + ASSERT_EQ(uids1.size() + uids2.size(), conditionTracker.mSlicedConditionState.size()); + } + EXPECT_TRUE(changedCache[0]); + { + if (position == Position::FIRST || position == Position::LAST) { + ASSERT_EQ(1UL, conditionTracker.getChangedToTrueDimensions(allConditions)->size()); + EXPECT_TRUE(conditionTracker.getChangedToFalseDimensions(allConditions)->empty()); + } else { + EXPECT_EQ(uids2.size(), + conditionTracker.getChangedToTrueDimensions(allConditions)->size()); + EXPECT_TRUE(conditionTracker.getChangedToFalseDimensions(allConditions)->empty()); + } + } + + // TEST QUERY + const auto queryKey2 = getWakeLockQueryKey(position, uids2, conditionName); + conditionCache[0] = ConditionState::kNotEvaluated; + conditionTracker.isConditionMet(queryKey, allPredicates, false, conditionCache); + + EXPECT_EQ(ConditionState::kTrue, conditionCache[0]); + + // stop all event + LogEvent event3(/*uid=*/0, /*pid=*/0); + makeWakeLockEvent(&event3, /*atomId=*/1, /*timestamp=*/0, uids2, "wl2", /*acquire=*/1); + + matcherState.clear(); + matcherState.push_back(MatchingState::kNotMatched); + matcherState.push_back(MatchingState::kNotMatched); + matcherState.push_back(MatchingState::kMatched); + + conditionCache[0] = ConditionState::kNotEvaluated; + changedCache[0] = false; + conditionTracker.evaluateCondition(event3, matcherState, allPredicates, conditionCache, + changedCache); + EXPECT_TRUE(changedCache[0]); + ASSERT_EQ(0UL, conditionTracker.mSlicedConditionState.size()); + { + if (position == Position::FIRST || position == Position::LAST) { + ASSERT_EQ(2UL, conditionTracker.getChangedToFalseDimensions(allConditions)->size()); + EXPECT_TRUE(conditionTracker.getChangedToTrueDimensions(allConditions)->empty()); + } else { + EXPECT_EQ(uids1.size() + uids2.size(), + conditionTracker.getChangedToFalseDimensions(allConditions)->size()); + EXPECT_TRUE(conditionTracker.getChangedToTrueDimensions(allConditions)->empty()); + } + } + + // TEST QUERY + const auto queryKey3 = getWakeLockQueryKey(position, uids1, conditionName); + conditionCache[0] = ConditionState::kNotEvaluated; + conditionTracker.isConditionMet(queryKey, allPredicates, false, conditionCache); + EXPECT_EQ(ConditionState::kFalse, conditionCache[0]); + + // TEST QUERY + const auto queryKey4 = getWakeLockQueryKey(position, uids2, conditionName); + conditionCache[0] = ConditionState::kNotEvaluated; + conditionTracker.isConditionMet(queryKey, allPredicates, false, conditionCache); + EXPECT_EQ(ConditionState::kFalse, conditionCache[0]); + } +} + +} // namespace statsd +} // namespace os +} // namespace android +#else +GTEST_LOG_(INFO) << "This test does nothing.\n"; +#endif diff --git a/statsd/tests/e2e/Alarm_e2e_test.cpp b/statsd/tests/e2e/Alarm_e2e_test.cpp new file mode 100644 index 00000000..93b27838 --- /dev/null +++ b/statsd/tests/e2e/Alarm_e2e_test.cpp @@ -0,0 +1,92 @@ +// 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 CreateStatsdConfig() { + StatsdConfig config; + config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root. + + auto alarm = config.add_alarm(); + alarm->set_id(123456); + alarm->set_offset_millis(TimeUnitToBucketSizeInMillis(TEN_MINUTES)); + alarm->set_period_millis(TimeUnitToBucketSizeInMillis(ONE_HOUR)); + + alarm = config.add_alarm(); + alarm->set_id(654321); + alarm->set_offset_millis(TimeUnitToBucketSizeInMillis(FIVE_MINUTES)); + alarm->set_period_millis(TimeUnitToBucketSizeInMillis(THIRTY_MINUTES)); + return config; +} + +} // namespace + +TEST(AlarmE2eTest, TestMultipleAlarms) { + auto config = CreateStatsdConfig(); + int64_t bucketStartTimeNs = 10000000000; + + ConfigKey cfgKey; + auto processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey); + ASSERT_EQ(processor->mMetricsManagers.size(), 1u); + EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid()); + ASSERT_EQ(2u, processor->mMetricsManagers.begin()->second->mAllPeriodicAlarmTrackers.size()); + + auto alarmTracker1 = processor->mMetricsManagers.begin()->second->mAllPeriodicAlarmTrackers[0]; + auto alarmTracker2 = processor->mMetricsManagers.begin()->second->mAllPeriodicAlarmTrackers[1]; + + int64_t alarmTimestampSec0 = bucketStartTimeNs / NS_PER_SEC + 10 * 60; + int64_t alarmTimestampSec1 = bucketStartTimeNs / NS_PER_SEC + 5 * 60; + EXPECT_EQ(alarmTimestampSec0, alarmTracker1->getAlarmTimestampSec()); + EXPECT_EQ(alarmTimestampSec1, alarmTracker2->getAlarmTimestampSec()); + + // Alarm fired. + const int64_t alarmFiredTimestampSec0 = alarmTimestampSec1 + 5; + auto alarmSet = processor->getPeriodicAlarmMonitor()->popSoonerThan( + static_cast(alarmFiredTimestampSec0)); + ASSERT_EQ(1u, alarmSet.size()); + processor->onPeriodicAlarmFired(alarmFiredTimestampSec0 * NS_PER_SEC, alarmSet); + EXPECT_EQ(alarmTimestampSec0, alarmTracker1->getAlarmTimestampSec()); + EXPECT_EQ(alarmTimestampSec1 + 30 * 60, alarmTracker2->getAlarmTimestampSec()); + + // Alarms fired very late. + const int64_t alarmFiredTimestampSec1 = alarmTimestampSec0 + 2 * 60 * 60 + 125; + alarmSet = processor->getPeriodicAlarmMonitor()->popSoonerThan( + static_cast(alarmFiredTimestampSec1)); + ASSERT_EQ(2u, alarmSet.size()); + processor->onPeriodicAlarmFired(alarmFiredTimestampSec1 * NS_PER_SEC, alarmSet); + EXPECT_EQ(alarmTimestampSec0 + 60 * 60 * 3, alarmTracker1->getAlarmTimestampSec()); + EXPECT_EQ(alarmTimestampSec1 + 30 * 60 * 5, alarmTracker2->getAlarmTimestampSec()); +} + +#else +GTEST_LOG_(INFO) << "This test does nothing.\n"; +#endif + +} // namespace statsd +} // namespace os +} // namespace android diff --git a/statsd/tests/e2e/Anomaly_count_e2e_test.cpp b/statsd/tests/e2e/Anomaly_count_e2e_test.cpp new file mode 100644 index 00000000..ec74df63 --- /dev/null +++ b/statsd/tests/e2e/Anomaly_count_e2e_test.cpp @@ -0,0 +1,390 @@ +// Copyright (C) 2018 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/statsd_metadata.pb.h" +#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 CreateStatsdConfig(int num_buckets, int threshold, int refractory_period_sec) { + StatsdConfig config; + config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root. + auto wakelockAcquireMatcher = CreateAcquireWakelockAtomMatcher(); + + *config.add_atom_matcher() = wakelockAcquireMatcher; + + auto countMetric = config.add_count_metric(); + countMetric->set_id(123456); + countMetric->set_what(wakelockAcquireMatcher.id()); + *countMetric->mutable_dimensions_in_what() = CreateAttributionUidDimensions( + util::WAKELOCK_STATE_CHANGED, {Position::FIRST}); + countMetric->set_bucket(FIVE_MINUTES); + + auto alert = config.add_alert(); + alert->set_id(StringToId("alert")); + alert->set_metric_id(123456); + alert->set_num_buckets(num_buckets); + alert->set_refractory_period_secs(refractory_period_sec); + alert->set_trigger_if_sum_gt(threshold); + return config; +} + +} // namespace + +TEST(AnomalyDetectionE2eTest, TestSlicedCountMetric_single_bucket) { + const int num_buckets = 1; + const int threshold = 3; + const int refractory_period_sec = 10; + auto config = CreateStatsdConfig(num_buckets, threshold, refractory_period_sec); + const uint64_t alert_id = config.alert(0).id(); + + int64_t bucketStartTimeNs = 10000000000; + int64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(config.count_metric(0).bucket()) * 1000000; + + ConfigKey cfgKey; + auto processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey); + ASSERT_EQ(processor->mMetricsManagers.size(), 1u); + EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid()); + ASSERT_EQ(1u, processor->mMetricsManagers.begin()->second->mAllAnomalyTrackers.size()); + + sp anomalyTracker = + processor->mMetricsManagers.begin()->second->mAllAnomalyTrackers[0]; + + std::vector attributionUids1 = {111}; + std::vector attributionTags1 = {"App1"}; + std::vector attributionUids2 = {111, 222}; + std::vector attributionTags2 = {"App1", "GMSCoreModule1"}; + std::vector attributionUids3 = {111, 333}; + std::vector attributionTags3 = {"App1", "App3"}; + std::vector attributionUids4 = {222, 333}; + std::vector attributionTags4 = {"GMSCoreModule1", "App3"}; + std::vector attributionUids5 = {222}; + std::vector attributionTags5 = {"GMSCoreModule1"}; + + FieldValue fieldValue1(Field(util::WAKELOCK_STATE_CHANGED, (int32_t)0x02010101), + Value((int32_t)111)); + HashableDimensionKey whatKey1({fieldValue1}); + MetricDimensionKey dimensionKey1(whatKey1, DEFAULT_DIMENSION_KEY); + + FieldValue fieldValue2(Field(util::WAKELOCK_STATE_CHANGED, (int32_t)0x02010101), + Value((int32_t)222)); + HashableDimensionKey whatKey2({fieldValue2}); + MetricDimensionKey dimensionKey2(whatKey2, DEFAULT_DIMENSION_KEY); + + auto event = CreateAcquireWakelockEvent(bucketStartTimeNs + 2, attributionUids1, + attributionTags1, "wl1"); + processor->OnLogEvent(event.get()); + EXPECT_EQ(0u, anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1)); + + event = CreateAcquireWakelockEvent(bucketStartTimeNs + 2, attributionUids4, attributionTags4, + "wl2"); + processor->OnLogEvent(event.get()); + EXPECT_EQ(0u, anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey2)); + + event = CreateAcquireWakelockEvent(bucketStartTimeNs + 3, attributionUids2, attributionTags2, + "wl1"); + processor->OnLogEvent(event.get()); + EXPECT_EQ(0u, anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1)); + + event = CreateAcquireWakelockEvent(bucketStartTimeNs + 3, attributionUids5, attributionTags5, + "wl2"); + processor->OnLogEvent(event.get()); + EXPECT_EQ(0u, anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey2)); + + event = CreateAcquireWakelockEvent(bucketStartTimeNs + 4, attributionUids3, attributionTags3, + "wl1"); + processor->OnLogEvent(event.get()); + EXPECT_EQ(0u, anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1)); + + event = CreateAcquireWakelockEvent(bucketStartTimeNs + 4, attributionUids5, attributionTags5, + "wl2"); + processor->OnLogEvent(event.get()); + EXPECT_EQ(0u, anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey2)); + + // Fired alarm and refractory period end timestamp updated. + event = CreateAcquireWakelockEvent(bucketStartTimeNs + 5, attributionUids1, attributionTags1, + "wl1"); + processor->OnLogEvent(event.get()); + EXPECT_EQ(refractory_period_sec + bucketStartTimeNs / NS_PER_SEC + 1, + anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1)); + + event = CreateAcquireWakelockEvent(bucketStartTimeNs + 100, attributionUids1, attributionTags1, + "wl1"); + processor->OnLogEvent(event.get()); + EXPECT_EQ(refractory_period_sec + bucketStartTimeNs / NS_PER_SEC + 1, + anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1)); + + event = CreateAcquireWakelockEvent(bucketStartTimeNs + bucketSizeNs - 1, attributionUids1, + attributionTags1, "wl1"); + processor->OnLogEvent(event.get()); + EXPECT_EQ(refractory_period_sec + (bucketStartTimeNs + bucketSizeNs - 1) / NS_PER_SEC + 1, + anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1)); + + event = CreateAcquireWakelockEvent(bucketStartTimeNs + bucketSizeNs + 1, attributionUids1, + attributionTags1, "wl1"); + processor->OnLogEvent(event.get()); + EXPECT_EQ(refractory_period_sec + (bucketStartTimeNs + bucketSizeNs - 1) / NS_PER_SEC + 1, + anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1)); + + event = CreateAcquireWakelockEvent(bucketStartTimeNs + bucketSizeNs + 1, attributionUids4, + attributionTags4, "wl2"); + processor->OnLogEvent(event.get()); + EXPECT_EQ(0u, anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey2)); + + event = CreateAcquireWakelockEvent(bucketStartTimeNs + bucketSizeNs + 2, attributionUids5, + attributionTags5, "wl2"); + processor->OnLogEvent(event.get()); + EXPECT_EQ(0u, anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey2)); + + event = CreateAcquireWakelockEvent(bucketStartTimeNs + bucketSizeNs + 3, attributionUids5, + attributionTags5, "wl2"); + processor->OnLogEvent(event.get()); + EXPECT_EQ(0u, anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey2)); + + event = CreateAcquireWakelockEvent(bucketStartTimeNs + bucketSizeNs + 4, attributionUids5, + attributionTags5, "wl2"); + processor->OnLogEvent(event.get()); + EXPECT_EQ(refractory_period_sec + (bucketStartTimeNs + bucketSizeNs + 4) / NS_PER_SEC + 1, + anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey2)); +} + +TEST(AnomalyDetectionE2eTest, TestSlicedCountMetric_multiple_buckets) { + const int num_buckets = 3; + const int threshold = 3; + const int refractory_period_sec = 10; + auto config = CreateStatsdConfig(num_buckets, threshold, refractory_period_sec); + const uint64_t alert_id = config.alert(0).id(); + + int64_t bucketStartTimeNs = 10000000000; + int64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(config.count_metric(0).bucket()) * 1000000; + + ConfigKey cfgKey; + auto processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey); + ASSERT_EQ(processor->mMetricsManagers.size(), 1u); + EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid()); + ASSERT_EQ(1u, processor->mMetricsManagers.begin()->second->mAllAnomalyTrackers.size()); + + sp anomalyTracker = + processor->mMetricsManagers.begin()->second->mAllAnomalyTrackers[0]; + + std::vector attributionUids1 = {111}; + std::vector attributionTags1 = {"App1"}; + std::vector attributionUids2 = {111, 222}; + std::vector attributionTags2 = {"App1", "GMSCoreModule1"}; + + FieldValue fieldValue1(Field(util::WAKELOCK_STATE_CHANGED, (int32_t)0x02010101), + Value((int32_t)111)); + HashableDimensionKey whatKey1({fieldValue1}); + MetricDimensionKey dimensionKey1(whatKey1, DEFAULT_DIMENSION_KEY); + + auto event = CreateAcquireWakelockEvent(bucketStartTimeNs + 2, attributionUids1, + attributionTags1, "wl1"); + processor->OnLogEvent(event.get()); + EXPECT_EQ(0u, anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1)); + + event = CreateAcquireWakelockEvent(bucketStartTimeNs + 3, attributionUids2, attributionTags2, + "wl1"); + processor->OnLogEvent(event.get()); + EXPECT_EQ(0u, anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1)); + + // Fired alarm and refractory period end timestamp updated. + event = CreateAcquireWakelockEvent(bucketStartTimeNs + 4, attributionUids1, attributionTags1, + "wl1"); + processor->OnLogEvent(event.get()); + EXPECT_EQ(0u, anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1)); + + event = CreateAcquireWakelockEvent(bucketStartTimeNs + bucketSizeNs + 1, attributionUids1, + attributionTags1, "wl1"); + processor->OnLogEvent(event.get()); + EXPECT_EQ(refractory_period_sec + (bucketStartTimeNs + bucketSizeNs + 1) / NS_PER_SEC + 1, + anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1)); + + event = CreateAcquireWakelockEvent(bucketStartTimeNs + bucketSizeNs + 2, attributionUids2, + attributionTags2, "wl1"); + processor->OnLogEvent(event.get()); + EXPECT_EQ(refractory_period_sec + (bucketStartTimeNs + bucketSizeNs + 1) / NS_PER_SEC + 1, + anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1)); + + event = CreateAcquireWakelockEvent(bucketStartTimeNs + 3 * bucketSizeNs + 1, attributionUids2, + attributionTags2, "wl1"); + processor->OnLogEvent(event.get()); + EXPECT_EQ(refractory_period_sec + (bucketStartTimeNs + bucketSizeNs + 1) / NS_PER_SEC + 1, + anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1)); + + event = CreateAcquireWakelockEvent(bucketStartTimeNs + 3 * bucketSizeNs + 2, attributionUids2, + attributionTags2, "wl1"); + processor->OnLogEvent(event.get()); + EXPECT_EQ(refractory_period_sec + (bucketStartTimeNs + 3 * bucketSizeNs + 2) / NS_PER_SEC + 1, + anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1)); +} + +TEST(AnomalyDetectionE2eTest, TestCountMetric_save_refractory_to_disk_no_data_written) { + const int num_buckets = 1; + const int threshold = 0; + const int refractory_period_sec = 86400 * 365; // 1 year + auto config = CreateStatsdConfig(num_buckets, threshold, refractory_period_sec); + const int64_t alert_id = config.alert(0).id(); + + int64_t bucketStartTimeNs = 10000000000; + + int configUid = 2000; + int64_t configId = 1000; + ConfigKey cfgKey(configUid, configId); + auto processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey); + ASSERT_EQ(processor->mMetricsManagers.size(), 1u); + EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid()); + ASSERT_EQ(1u, processor->mMetricsManagers.begin()->second->mAllAnomalyTrackers.size()); + + metadata::StatsMetadataList result; + int64_t mockWallClockNs = 1584991200 * NS_PER_SEC; + int64_t mockElapsedTimeNs = bucketStartTimeNs + 5000 * NS_PER_SEC; + processor->WriteMetadataToProto(mockWallClockNs, mockElapsedTimeNs, &result); + + ASSERT_EQ(result.stats_metadata_size(), 0); +} + +TEST(AnomalyDetectionE2eTest, TestCountMetric_save_refractory_to_disk) { + const int num_buckets = 1; + const int threshold = 0; + const int refractory_period_sec = 86400 * 365; // 1 year + auto config = CreateStatsdConfig(num_buckets, threshold, refractory_period_sec); + const int64_t alert_id = config.alert(0).id(); + + int64_t bucketStartTimeNs = 10000000000; + + int configUid = 2000; + int64_t configId = 1000; + ConfigKey cfgKey(configUid, configId); + auto processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey); + ASSERT_EQ(processor->mMetricsManagers.size(), 1u); + EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid()); + ASSERT_EQ(1u, processor->mMetricsManagers.begin()->second->mAllAnomalyTrackers.size()); + + sp anomalyTracker = + processor->mMetricsManagers.begin()->second->mAllAnomalyTrackers[0]; + + std::vector attributionUids1 = {111}; + std::vector attributionTags1 = {"App1"}; + std::vector attributionUids2 = {111, 222}; + std::vector attributionTags2 = {"App1", "GMSCoreModule1"}; + + FieldValue fieldValue1(Field(util::WAKELOCK_STATE_CHANGED, (int32_t)0x02010101), + Value((int32_t)111)); + HashableDimensionKey whatKey1({fieldValue1}); + MetricDimensionKey dimensionKey1(whatKey1, DEFAULT_DIMENSION_KEY); + + auto event = CreateAcquireWakelockEvent(bucketStartTimeNs + 2, attributionUids1, + attributionTags1, "wl1"); + processor->OnLogEvent(event.get()); + EXPECT_EQ(refractory_period_sec + (bucketStartTimeNs + 2) / NS_PER_SEC + 1, + anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1)); + + metadata::StatsMetadataList result; + int64_t mockWallClockNs = 1584991200 * NS_PER_SEC; + int64_t mockElapsedTimeNs = bucketStartTimeNs + 5000 * NS_PER_SEC; + processor->WriteMetadataToProto(mockWallClockNs, mockElapsedTimeNs, &result); + + metadata::StatsMetadata statsMetadata = result.stats_metadata(0); + ASSERT_EQ(result.stats_metadata_size(), 1); + EXPECT_EQ(statsMetadata.config_key().config_id(), configId); + EXPECT_EQ(statsMetadata.config_key().uid(), configUid); + + metadata::AlertMetadata alertMetadata = statsMetadata.alert_metadata(0); + ASSERT_EQ(statsMetadata.alert_metadata_size(), 1); + EXPECT_EQ(alertMetadata.alert_id(), alert_id); + metadata::AlertDimensionKeyedData keyedData = alertMetadata.alert_dim_keyed_data(0); + ASSERT_EQ(alertMetadata.alert_dim_keyed_data_size(), 1); + EXPECT_EQ(keyedData.last_refractory_ends_sec(), + anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1) - + mockElapsedTimeNs / NS_PER_SEC + + mockWallClockNs / NS_PER_SEC); + + metadata::MetricDimensionKey metadataDimKey = keyedData.dimension_key(); + metadata::FieldValue dimKeyInWhat = metadataDimKey.dimension_key_in_what(0); + EXPECT_EQ(dimKeyInWhat.field().tag(), fieldValue1.mField.getTag()); + EXPECT_EQ(dimKeyInWhat.field().field(), fieldValue1.mField.getField()); + EXPECT_EQ(dimKeyInWhat.value_int(), fieldValue1.mValue.int_value); +} + +TEST(AnomalyDetectionE2eTest, TestCountMetric_load_refractory_from_disk) { + const int num_buckets = 1; + const int threshold = 0; + const int refractory_period_sec = 86400 * 365; // 1 year + auto config = CreateStatsdConfig(num_buckets, threshold, refractory_period_sec); + const int64_t alert_id = config.alert(0).id(); + + int64_t bucketStartTimeNs = 10000000000; + + int configUid = 2000; + int64_t configId = 1000; + ConfigKey cfgKey(configUid, configId); + auto processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey); + ASSERT_EQ(processor->mMetricsManagers.size(), 1u); + EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid()); + ASSERT_EQ(1u, processor->mMetricsManagers.begin()->second->mAllAnomalyTrackers.size()); + + sp anomalyTracker = + processor->mMetricsManagers.begin()->second->mAllAnomalyTrackers[0]; + + std::vector attributionUids1 = {111}; + std::vector attributionTags1 = {"App1"}; + std::vector attributionUids2 = {111, 222}; + std::vector attributionTags2 = {"App1", "GMSCoreModule1"}; + + FieldValue fieldValue1(Field(util::WAKELOCK_STATE_CHANGED, (int32_t)0x02010101), + Value((int32_t)111)); + HashableDimensionKey whatKey1({fieldValue1}); + MetricDimensionKey dimensionKey1(whatKey1, DEFAULT_DIMENSION_KEY); + + auto event = CreateAcquireWakelockEvent(bucketStartTimeNs + 2, attributionUids1, + attributionTags1, "wl1"); + processor->OnLogEvent(event.get()); + EXPECT_EQ(refractory_period_sec + (bucketStartTimeNs + 2) / NS_PER_SEC + 1, + anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1)); + + int64_t mockWallClockNs = 1584991200 * NS_PER_SEC; + int64_t mockElapsedTimeNs = bucketStartTimeNs + 5000 * NS_PER_SEC; + processor->SaveMetadataToDisk(mockWallClockNs, mockElapsedTimeNs); + + auto processor2 = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey); + int64_t mockElapsedTimeSinceBoot = 10 * NS_PER_SEC; + processor2->LoadMetadataFromDisk(mockWallClockNs, mockElapsedTimeSinceBoot); + + sp anomalyTracker2 = + processor2->mMetricsManagers.begin()->second->mAllAnomalyTrackers[0]; + EXPECT_EQ(anomalyTracker2->getRefractoryPeriodEndsSec(dimensionKey1) - + mockElapsedTimeSinceBoot / NS_PER_SEC, + anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1) - + mockElapsedTimeNs / NS_PER_SEC); +} + +#else +GTEST_LOG_(INFO) << "This test does nothing.\n"; +#endif + +} // namespace statsd +} // namespace os +} // namespace android diff --git a/statsd/tests/e2e/Anomaly_duration_sum_e2e_test.cpp b/statsd/tests/e2e/Anomaly_duration_sum_e2e_test.cpp new file mode 100644 index 00000000..e8014805 --- /dev/null +++ b/statsd/tests/e2e/Anomaly_duration_sum_e2e_test.cpp @@ -0,0 +1,615 @@ +// Copyright (C) 2018 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 +#include + +#include + +#include "src/StatsLogProcessor.h" +#include "src/StatsService.h" +#include "src/anomaly/DurationAnomalyTracker.h" +#include "src/stats_log_util.h" +#include "tests/statsd_test_util.h" + +using ::ndk::SharedRefBase; + +namespace android { +namespace os { +namespace statsd { + +#ifdef __ANDROID__ + +namespace { + +const int kConfigKey = 789130124; +const int kCallingUid = 0; + +StatsdConfig CreateStatsdConfig(int num_buckets, + uint64_t threshold_ns, + DurationMetric::AggregationType aggregationType, + bool nesting) { + StatsdConfig config; + config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root. + *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(); + FieldMatcher dimensions = CreateAttributionUidDimensions( + util::WAKELOCK_STATE_CHANGED, {Position::FIRST}); + dimensions.add_child()->set_field(3); // The wakelock tag is set in field 3 of the wakelock. + *holdingWakelockPredicate.mutable_simple_predicate()->mutable_dimensions() = dimensions; + holdingWakelockPredicate.mutable_simple_predicate()->set_count_nesting(nesting); + *config.add_predicate() = holdingWakelockPredicate; + + auto durationMetric = config.add_duration_metric(); + durationMetric->set_id(StringToId("WakelockDuration")); + durationMetric->set_what(holdingWakelockPredicate.id()); + durationMetric->set_condition(screenIsOffPredicate.id()); + durationMetric->set_aggregation_type(aggregationType); + *durationMetric->mutable_dimensions_in_what() = + CreateAttributionUidDimensions(util::WAKELOCK_STATE_CHANGED, {Position::FIRST}); + durationMetric->set_bucket(FIVE_MINUTES); + + auto alert = config.add_alert(); + alert->set_id(StringToId("alert")); + alert->set_metric_id(StringToId("WakelockDuration")); + alert->set_num_buckets(num_buckets); + alert->set_refractory_period_secs(2); + alert->set_trigger_if_sum_gt(threshold_ns); + return config; +} + +std::vector attributionUids1 = {111, 222}; +std::vector attributionTags1 = {"App1", "GMSCoreModule1"}; + +std::vector attributionUids2 = {111, 222}; +std::vector attributionTags2 = {"App2", "GMSCoreModule1"}; + +std::vector attributionUids3 = {222}; +std::vector attributionTags3 = {"GMSCoreModule1"}; + +MetricDimensionKey dimensionKey1( + HashableDimensionKey({FieldValue(Field(util::WAKELOCK_STATE_CHANGED, + (int32_t)0x02010101), + Value((int32_t)111))}), + DEFAULT_DIMENSION_KEY); + +MetricDimensionKey dimensionKey2( + HashableDimensionKey({FieldValue(Field(util::WAKELOCK_STATE_CHANGED, + (int32_t)0x02010101), Value((int32_t)222))}), + DEFAULT_DIMENSION_KEY); + +void sendConfig(shared_ptr& service, const StatsdConfig& config) { + string str; + config.SerializeToString(&str); + std::vector configAsVec(str.begin(), str.end()); + service->addConfiguration(kConfigKey, configAsVec, kCallingUid); +} + +} // namespace + +TEST(AnomalyDetectionE2eTest, TestDurationMetric_SUM_single_bucket) { + const int num_buckets = 1; + const uint64_t threshold_ns = NS_PER_SEC; + auto config = CreateStatsdConfig(num_buckets, threshold_ns, DurationMetric::SUM, true); + const uint64_t alert_id = config.alert(0).id(); + const uint32_t refractory_period_sec = config.alert(0).refractory_period_secs(); + + shared_ptr service = SharedRefBase::make(nullptr, nullptr); + sendConfig(service, config); + + auto processor = service->mProcessor; + ASSERT_EQ(processor->mMetricsManagers.size(), 1u); + EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid()); + ASSERT_EQ(1u, processor->mMetricsManagers.begin()->second->mAllAnomalyTrackers.size()); + + int64_t bucketStartTimeNs = processor->mTimeBaseNs; + int64_t roundedBucketStartTimeNs = bucketStartTimeNs / NS_PER_SEC * NS_PER_SEC; + int64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(config.duration_metric(0).bucket()) * 1e6; + + sp anomalyTracker = + processor->mMetricsManagers.begin()->second->mAllAnomalyTrackers[0]; + + auto screen_on_event = CreateScreenStateChangedEvent( + bucketStartTimeNs + 1, android::view::DisplayStateEnum::DISPLAY_STATE_ON); + auto screen_off_event = CreateScreenStateChangedEvent( + bucketStartTimeNs + 10, android::view::DisplayStateEnum::DISPLAY_STATE_OFF); + processor->OnLogEvent(screen_on_event.get()); + processor->OnLogEvent(screen_off_event.get()); + + // Acquire wakelock wl1. + auto acquire_event = CreateAcquireWakelockEvent(bucketStartTimeNs + 11, attributionUids1, + attributionTags1, "wl1"); + processor->OnLogEvent(acquire_event.get()); + EXPECT_EQ((bucketStartTimeNs + 11 + threshold_ns) / NS_PER_SEC + 1, + anomalyTracker->getAlarmTimestampSec(dimensionKey1)); + EXPECT_EQ(0u, anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1)); + + // Release wakelock wl1. No anomaly detected. Alarm cancelled at the "release" event. + auto release_event = CreateReleaseWakelockEvent(bucketStartTimeNs + 101, attributionUids1, + attributionTags1, "wl1"); + processor->OnLogEvent(release_event.get()); + EXPECT_EQ(0u, anomalyTracker->getAlarmTimestampSec(dimensionKey1)); + EXPECT_EQ(0u, anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1)); + + // Acquire wakelock wl1 within bucket #0. + acquire_event = CreateAcquireWakelockEvent(bucketStartTimeNs + 110, attributionUids2, + attributionTags2, "wl1"); + processor->OnLogEvent(acquire_event.get()); + EXPECT_EQ((bucketStartTimeNs + 110 + threshold_ns - 90) / NS_PER_SEC + 1, + anomalyTracker->getAlarmTimestampSec(dimensionKey1)); + EXPECT_EQ(0u, anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1)); + + // Release wakelock wl1. One anomaly detected. + release_event = CreateReleaseWakelockEvent(bucketStartTimeNs + NS_PER_SEC + 109, + attributionUids2, attributionTags2, "wl1"); + processor->OnLogEvent(release_event.get()); + EXPECT_EQ(0u, anomalyTracker->getAlarmTimestampSec(dimensionKey1)); + EXPECT_EQ(refractory_period_sec + (bucketStartTimeNs + NS_PER_SEC + 109) / NS_PER_SEC + 1, + anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1)); + + // Acquire wakelock wl1. + acquire_event = CreateAcquireWakelockEvent(bucketStartTimeNs + NS_PER_SEC + 112, + attributionUids1, attributionTags1, "wl1"); + processor->OnLogEvent(acquire_event.get()); + // Wakelock has been hold longer than the threshold in bucket #0. The alarm is set at the + // end of the refractory period. + const int64_t alarmFiredTimestampSec0 = anomalyTracker->getAlarmTimestampSec(dimensionKey1); + EXPECT_EQ(refractory_period_sec + (bucketStartTimeNs + NS_PER_SEC + 109) / NS_PER_SEC + 1, + (uint32_t)alarmFiredTimestampSec0); + EXPECT_EQ(alarmFiredTimestampSec0, + processor->getAnomalyAlarmMonitor()->getRegisteredAlarmTimeSec()); + + // Anomaly alarm fired. + auto alarmTriggerEvent = CreateBatterySaverOnEvent(alarmFiredTimestampSec0 * NS_PER_SEC); + processor->OnLogEvent(alarmTriggerEvent.get(), alarmFiredTimestampSec0 * NS_PER_SEC); + + EXPECT_EQ(0u, anomalyTracker->getAlarmTimestampSec(dimensionKey1)); + EXPECT_EQ(refractory_period_sec + alarmFiredTimestampSec0, + anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1)); + + // Release wakelock wl1. + release_event = + CreateReleaseWakelockEvent(alarmFiredTimestampSec0 * NS_PER_SEC + NS_PER_SEC + 1, + attributionUids1, attributionTags1, "wl1"); + processor->OnLogEvent(release_event.get()); + EXPECT_EQ(0u, anomalyTracker->getAlarmTimestampSec(dimensionKey1)); + // Within refractory period. No more anomaly detected. + EXPECT_EQ(refractory_period_sec + alarmFiredTimestampSec0, + anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1)); + + // Acquire wakelock wl1. + acquire_event = CreateAcquireWakelockEvent( + roundedBucketStartTimeNs + bucketSizeNs - 5 * NS_PER_SEC - 11, attributionUids2, + attributionTags2, "wl1"); + processor->OnLogEvent(acquire_event.get()); + const int64_t alarmFiredTimestampSec1 = anomalyTracker->getAlarmTimestampSec(dimensionKey1); + EXPECT_EQ((bucketStartTimeNs + bucketSizeNs - 5 * NS_PER_SEC) / NS_PER_SEC, + (uint64_t)alarmFiredTimestampSec1); + + // Release wakelock wl1. + int64_t release_event_time = roundedBucketStartTimeNs + bucketSizeNs - 4 * NS_PER_SEC - 10; + release_event = CreateReleaseWakelockEvent(release_event_time, attributionUids2, + attributionTags2, "wl1"); + processor->OnLogEvent(release_event.get(), release_event_time); + EXPECT_EQ(0u, anomalyTracker->getAlarmTimestampSec(dimensionKey1)); + EXPECT_EQ(refractory_period_sec + (release_event_time) / NS_PER_SEC + 1, + anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1)); + + auto alarmSet = processor->getAnomalyAlarmMonitor()->popSoonerThan( + static_cast(alarmFiredTimestampSec1)); + ASSERT_EQ(0u, alarmSet.size()); + + // Acquire wakelock wl1 near the end of bucket #0. + acquire_event = CreateAcquireWakelockEvent(roundedBucketStartTimeNs + bucketSizeNs - 2, + attributionUids1, attributionTags1, "wl1"); + processor->OnLogEvent(acquire_event.get()); + EXPECT_EQ((bucketStartTimeNs + bucketSizeNs) / NS_PER_SEC, + anomalyTracker->getAlarmTimestampSec(dimensionKey1)); + + // Release the event at early bucket #1. + release_event_time = roundedBucketStartTimeNs + bucketSizeNs + NS_PER_SEC - 1; + release_event = CreateReleaseWakelockEvent(release_event_time, attributionUids1, + attributionTags1, "wl1"); + processor->OnLogEvent(release_event.get(), release_event_time); + EXPECT_EQ(0u, anomalyTracker->getAlarmTimestampSec(dimensionKey1)); + // Anomaly detected when stopping the alarm. The refractory period does not change. + EXPECT_EQ(refractory_period_sec + (bucketStartTimeNs + bucketSizeNs + NS_PER_SEC) / NS_PER_SEC, + anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1)); + + // Condition changes to false. + screen_on_event = + CreateScreenStateChangedEvent(bucketStartTimeNs + 2 * bucketSizeNs + 20, + android::view::DisplayStateEnum::DISPLAY_STATE_ON); + processor->OnLogEvent(screen_on_event.get()); + EXPECT_EQ(refractory_period_sec + (bucketStartTimeNs + bucketSizeNs + NS_PER_SEC) / NS_PER_SEC, + anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1)); + EXPECT_EQ(0u, anomalyTracker->getAlarmTimestampSec(dimensionKey1)); + + acquire_event = CreateAcquireWakelockEvent(bucketStartTimeNs + 2 * bucketSizeNs + 30, + attributionUids2, attributionTags2, "wl1"); + processor->OnLogEvent(acquire_event.get()); + // The condition is false. Do not start the alarm. + EXPECT_EQ(0u, anomalyTracker->getAlarmTimestampSec(dimensionKey1)); + EXPECT_EQ(refractory_period_sec + (bucketStartTimeNs + bucketSizeNs + NS_PER_SEC) / NS_PER_SEC, + anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1)); + + // Condition turns true. + screen_off_event = + CreateScreenStateChangedEvent(roundedBucketStartTimeNs + 2 * bucketSizeNs + NS_PER_SEC, + android::view::DisplayStateEnum::DISPLAY_STATE_OFF); + processor->OnLogEvent(screen_off_event.get()); + EXPECT_EQ((bucketStartTimeNs + 2 * bucketSizeNs + NS_PER_SEC + threshold_ns) / NS_PER_SEC, + anomalyTracker->getAlarmTimestampSec(dimensionKey1)); + + // Condition turns to false. + int64_t condition_false_time = bucketStartTimeNs + 2 * bucketSizeNs + 2 * NS_PER_SEC + 1; + screen_on_event = CreateScreenStateChangedEvent( + condition_false_time, android::view::DisplayStateEnum::DISPLAY_STATE_ON); + processor->OnLogEvent(screen_on_event.get(), condition_false_time); + // Condition turns to false. Cancelled the alarm. + EXPECT_EQ(0u, anomalyTracker->getAlarmTimestampSec(dimensionKey1)); + // Detected one anomaly. + EXPECT_EQ(refractory_period_sec + + (bucketStartTimeNs + 2 * bucketSizeNs + 2 * NS_PER_SEC + 1) / NS_PER_SEC + 1, + anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1)); + + // Condition turns to true again. + screen_off_event = + CreateScreenStateChangedEvent(bucketStartTimeNs + 2 * bucketSizeNs + 2 * NS_PER_SEC + 2, + android::view::DisplayStateEnum::DISPLAY_STATE_OFF); + processor->OnLogEvent(screen_off_event.get()); + EXPECT_EQ((bucketStartTimeNs + 2 * bucketSizeNs) / NS_PER_SEC + 2 + 2 + 1, + anomalyTracker->getAlarmTimestampSec(dimensionKey1)); + + release_event_time = roundedBucketStartTimeNs + 2 * bucketSizeNs + 5 * NS_PER_SEC; + release_event = CreateReleaseWakelockEvent(release_event_time, attributionUids2, + attributionTags2, "wl1"); + processor->OnLogEvent(release_event.get()); + EXPECT_EQ(refractory_period_sec + (release_event_time) / NS_PER_SEC, + anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1)); + EXPECT_EQ(0u, anomalyTracker->getAlarmTimestampSec(dimensionKey1)); +} + +TEST(AnomalyDetectionE2eTest, TestDurationMetric_SUM_multiple_buckets) { + const int num_buckets = 3; + const uint64_t threshold_ns = NS_PER_SEC; + auto config = CreateStatsdConfig(num_buckets, threshold_ns, DurationMetric::SUM, true); + const uint64_t alert_id = config.alert(0).id(); + const uint32_t refractory_period_sec = config.alert(0).refractory_period_secs(); + + shared_ptr service = SharedRefBase::make(nullptr, nullptr); + sendConfig(service, config); + + auto processor = service->mProcessor; + ASSERT_EQ(processor->mMetricsManagers.size(), 1u); + EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid()); + ASSERT_EQ(1u, processor->mMetricsManagers.begin()->second->mAllAnomalyTrackers.size()); + + int64_t bucketStartTimeNs = processor->mTimeBaseNs; + int64_t roundedBucketStartTimeNs = bucketStartTimeNs / NS_PER_SEC * NS_PER_SEC; + int64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(config.duration_metric(0).bucket()) * 1e6; + + sp anomalyTracker = + processor->mMetricsManagers.begin()->second->mAllAnomalyTrackers[0]; + + auto screen_off_event = CreateScreenStateChangedEvent( + bucketStartTimeNs + 1, android::view::DisplayStateEnum::DISPLAY_STATE_OFF); + processor->OnLogEvent(screen_off_event.get()); + + // Acquire wakelock "wc1" in bucket #0. + auto acquire_event = + CreateAcquireWakelockEvent(roundedBucketStartTimeNs + bucketSizeNs - NS_PER_SEC / 2 - 1, + attributionUids1, attributionTags1, "wl1"); + processor->OnLogEvent(acquire_event.get()); + EXPECT_EQ((roundedBucketStartTimeNs + bucketSizeNs) / NS_PER_SEC + 1, + anomalyTracker->getAlarmTimestampSec(dimensionKey1)); + EXPECT_EQ(0u, anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1)); + + // Release wakelock "wc1" in bucket #0. + int64_t release_event_time = roundedBucketStartTimeNs + bucketSizeNs - 1; + auto release_event = CreateReleaseWakelockEvent(release_event_time, attributionUids1, + attributionTags1, "wl1"); + processor->OnLogEvent(release_event.get(), release_event_time); + EXPECT_EQ(0u, anomalyTracker->getAlarmTimestampSec(dimensionKey1)); + EXPECT_EQ(0u, anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1)); + + // Acquire wakelock "wc1" in bucket #1. + acquire_event = + CreateAcquireWakelockEvent(roundedBucketStartTimeNs + bucketSizeNs + NS_PER_SEC + 1, + attributionUids2, attributionTags2, "wl1"); + processor->OnLogEvent(acquire_event.get()); + EXPECT_EQ((bucketStartTimeNs + bucketSizeNs + NS_PER_SEC) / NS_PER_SEC + 1, + anomalyTracker->getAlarmTimestampSec(dimensionKey1)); + EXPECT_EQ(0u, anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1)); + + release_event_time = roundedBucketStartTimeNs + bucketSizeNs + NS_PER_SEC + 100; + release_event = CreateReleaseWakelockEvent(release_event_time, attributionUids2, + attributionTags2, "wl1"); + processor->OnLogEvent(release_event.get(), release_event_time); + EXPECT_EQ(0u, anomalyTracker->getAlarmTimestampSec(dimensionKey1)); + EXPECT_EQ(0u, anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1)); + + // Acquire wakelock "wc2" in bucket #2. + acquire_event = + CreateAcquireWakelockEvent(roundedBucketStartTimeNs + 2 * bucketSizeNs + NS_PER_SEC + 1, + attributionUids3, attributionTags3, "wl2"); + processor->OnLogEvent(acquire_event.get()); + EXPECT_EQ((bucketStartTimeNs + 2 * bucketSizeNs) / NS_PER_SEC + 3, + anomalyTracker->getAlarmTimestampSec(dimensionKey2)); + EXPECT_EQ(0u, anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey2)); + + // Release wakelock "wc2" in bucket #2. + release_event_time = roundedBucketStartTimeNs + 2 * bucketSizeNs + 3 * NS_PER_SEC; + release_event = CreateReleaseWakelockEvent(release_event_time, attributionUids3, + attributionTags3, "wl2"); + processor->OnLogEvent(release_event.get(), release_event_time); + EXPECT_EQ(0u, anomalyTracker->getAlarmTimestampSec(dimensionKey2)); + EXPECT_EQ(refractory_period_sec + (release_event_time) / NS_PER_SEC, + anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey2)); + + // Acquire wakelock "wc1" in bucket #2. + acquire_event = + CreateAcquireWakelockEvent(roundedBucketStartTimeNs + 2 * bucketSizeNs + 3 * NS_PER_SEC, + attributionUids2, attributionTags2, "wl1"); + processor->OnLogEvent(acquire_event.get()); + EXPECT_EQ((roundedBucketStartTimeNs + 2 * bucketSizeNs) / NS_PER_SEC + 3 + 1, + anomalyTracker->getAlarmTimestampSec(dimensionKey1)); + EXPECT_EQ(0u, anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1)); + + // Release wakelock "wc1" in bucket #2. + release_event_time = roundedBucketStartTimeNs + 2 * bucketSizeNs + 3.5 * NS_PER_SEC; + release_event = CreateReleaseWakelockEvent(release_event_time, attributionUids2, + attributionTags2, "wl1"); + processor->OnLogEvent(release_event.get(), release_event_time); + EXPECT_EQ(0u, anomalyTracker->getAlarmTimestampSec(dimensionKey1)); + EXPECT_EQ(refractory_period_sec + (release_event_time) / NS_PER_SEC + 1, + anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1)); + + acquire_event = CreateAcquireWakelockEvent(roundedBucketStartTimeNs + 6 * bucketSizeNs + 4, + attributionUids3, attributionTags3, "wl2"); + processor->OnLogEvent(acquire_event.get()); + acquire_event = CreateAcquireWakelockEvent(roundedBucketStartTimeNs + 6 * bucketSizeNs + 5, + attributionUids1, attributionTags1, "wl1"); + processor->OnLogEvent(acquire_event.get()); + EXPECT_EQ((roundedBucketStartTimeNs + 6 * bucketSizeNs) / NS_PER_SEC + 2, + anomalyTracker->getAlarmTimestampSec(dimensionKey1)); + EXPECT_EQ((roundedBucketStartTimeNs + 6 * bucketSizeNs) / NS_PER_SEC + 2, + anomalyTracker->getAlarmTimestampSec(dimensionKey2)); + + release_event_time = roundedBucketStartTimeNs + 6 * bucketSizeNs + NS_PER_SEC + 2; + release_event = CreateReleaseWakelockEvent(release_event_time, attributionUids3, + attributionTags3, "wl2"); + processor->OnLogEvent(release_event.get(), release_event_time); + release_event = CreateReleaseWakelockEvent(release_event_time + 4, attributionUids1, + attributionTags1, "wl1"); + processor->OnLogEvent(release_event.get(), release_event_time + 4); + EXPECT_EQ(0u, anomalyTracker->getAlarmTimestampSec(dimensionKey1)); + EXPECT_EQ(0u, anomalyTracker->getAlarmTimestampSec(dimensionKey2)); + // The buckets are not messed up across dimensions. Only one dimension has anomaly triggered. + EXPECT_EQ(refractory_period_sec + + (int64_t)(roundedBucketStartTimeNs + 6 * bucketSizeNs + NS_PER_SEC) / + NS_PER_SEC + + 1, + anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1)); +} + +TEST(AnomalyDetectionE2eTest, TestDurationMetric_SUM_partial_bucket) { + const int num_buckets = 1; + const uint64_t threshold_ns = NS_PER_SEC; + auto config = CreateStatsdConfig(num_buckets, threshold_ns, DurationMetric::SUM, true); + const uint64_t alert_id = config.alert(0).id(); + const uint32_t refractory_period_sec = config.alert(0).refractory_period_secs(); + + shared_ptr service = SharedRefBase::make(nullptr, nullptr); + sendConfig(service, config); + + auto processor = service->mProcessor; + ASSERT_EQ(processor->mMetricsManagers.size(), 1u); + EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid()); + ASSERT_EQ(1u, processor->mMetricsManagers.begin()->second->mAllAnomalyTrackers.size()); + + int64_t bucketStartTimeNs = processor->mTimeBaseNs; + int64_t roundedBucketStartTimeNs = bucketStartTimeNs / NS_PER_SEC * NS_PER_SEC; + int64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(config.duration_metric(0).bucket()) * 1e6; + + service->mUidMap->updateMap(bucketStartTimeNs, {1}, {1}, {String16("v1")}, + {String16("randomApp")}, {String16("")}); + + sp anomalyTracker = + processor->mMetricsManagers.begin()->second->mAllAnomalyTrackers[0]; + + auto screen_off_event = CreateScreenStateChangedEvent( + bucketStartTimeNs + 10, android::view::DisplayStateEnum::DISPLAY_STATE_OFF); + processor->OnLogEvent(screen_off_event.get()); + + // Acquire wakelock wl1. + auto acquire_event = CreateAcquireWakelockEvent(bucketStartTimeNs + 11, attributionUids1, + attributionTags1, "wl1"); + processor->OnLogEvent(acquire_event.get()); + EXPECT_EQ((bucketStartTimeNs + 11 + threshold_ns) / NS_PER_SEC + 1, + anomalyTracker->getAlarmTimestampSec(dimensionKey1)); + EXPECT_EQ(0u, anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1)); + + // Release wakelock wl1. No anomaly detected. Alarm cancelled at the "release" event. + // 90 ns accumulated. + auto release_event = CreateReleaseWakelockEvent(bucketStartTimeNs + 101, attributionUids1, + attributionTags1, "wl1"); + processor->OnLogEvent(release_event.get()); + EXPECT_EQ(0u, anomalyTracker->getAlarmTimestampSec(dimensionKey1)); + EXPECT_EQ(0u, anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1)); + + // Acquire wakelock wl2. + acquire_event = CreateAcquireWakelockEvent(bucketStartTimeNs + 110, attributionUids3, + attributionTags3, "wl2"); + processor->OnLogEvent(acquire_event.get()); + int64_t wl2AlarmTimeNs = bucketStartTimeNs + 110 + threshold_ns; + EXPECT_EQ(wl2AlarmTimeNs / NS_PER_SEC + 1, anomalyTracker->getAlarmTimestampSec(dimensionKey2)); + EXPECT_EQ(0u, anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey2)); + + // Partial bucket split. + int64_t appUpgradeTimeNs = bucketStartTimeNs + 500; + service->mUidMap->updateApp(appUpgradeTimeNs, String16("randomApp"), 1, 2, String16("v2"), + String16("")); + EXPECT_EQ(0u, anomalyTracker->getAlarmTimestampSec(dimensionKey1)); + EXPECT_EQ(0u, anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1)); + EXPECT_EQ((bucketStartTimeNs + 110 + threshold_ns) / NS_PER_SEC + 1, + anomalyTracker->getAlarmTimestampSec(dimensionKey2)); + EXPECT_EQ(0u, anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey2)); + + // Acquire wakelock wl1. Subtract 100 ns since that accumulated before the bucket split. + acquire_event = CreateAcquireWakelockEvent(bucketStartTimeNs + 510, attributionUids1, + attributionTags1, "wl1"); + processor->OnLogEvent(acquire_event.get()); + int64_t wl1AlarmTimeNs = bucketStartTimeNs + 510 + threshold_ns - 90; + EXPECT_EQ(wl1AlarmTimeNs / NS_PER_SEC + 1, anomalyTracker->getAlarmTimestampSec(dimensionKey1)); + EXPECT_EQ(0u, anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1)); + + // Release wakelock wl1. One anomaly detected. + release_event = CreateReleaseWakelockEvent(wl1AlarmTimeNs + 1, attributionUids2, + attributionTags2, "wl1"); + processor->OnLogEvent(release_event.get()); + EXPECT_EQ(0u, anomalyTracker->getAlarmTimestampSec(dimensionKey1)); + EXPECT_EQ(refractory_period_sec + (wl1AlarmTimeNs + 1) / NS_PER_SEC + 1, + anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1)); + + // Anomaly alarm fired. + auto alarmTriggerEvent = CreateBatterySaverOnEvent(wl2AlarmTimeNs); + processor->OnLogEvent(alarmTriggerEvent.get(), wl2AlarmTimeNs); + + EXPECT_EQ(0u, anomalyTracker->getAlarmTimestampSec(dimensionKey1)); + EXPECT_EQ(refractory_period_sec + wl2AlarmTimeNs / NS_PER_SEC + 1, + anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1)); +} + +TEST(AnomalyDetectionE2eTest, TestDurationMetric_SUM_long_refractory_period) { + const int num_buckets = 2; + const uint64_t threshold_ns = 3 * NS_PER_SEC; + auto config = CreateStatsdConfig(num_buckets, threshold_ns, DurationMetric::SUM, false); + const uint64_t alert_id = config.alert(0).id(); + int64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(config.duration_metric(0).bucket()) * 1e6; + const uint32_t refractory_period_sec = 3 * bucketSizeNs / NS_PER_SEC; + config.mutable_alert(0)->set_refractory_period_secs(refractory_period_sec); + + shared_ptr service = SharedRefBase::make(nullptr, nullptr); + sendConfig(service, config); + + auto processor = service->mProcessor; + ASSERT_EQ(processor->mMetricsManagers.size(), 1u); + EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid()); + ASSERT_EQ(1u, processor->mMetricsManagers.begin()->second->mAllAnomalyTrackers.size()); + + int64_t bucketStartTimeNs = processor->mTimeBaseNs; + int64_t roundedBucketStartTimeNs = bucketStartTimeNs / NS_PER_SEC * NS_PER_SEC; + + sp anomalyTracker = + processor->mMetricsManagers.begin()->second->mAllAnomalyTrackers[0]; + + auto screen_off_event = CreateScreenStateChangedEvent( + bucketStartTimeNs + 1, android::view::DisplayStateEnum::DISPLAY_STATE_OFF); + processor->OnLogEvent(screen_off_event.get()); + + // Acquire wakelock "wc1" in bucket #0. + auto acquire_event = CreateAcquireWakelockEvent(roundedBucketStartTimeNs + bucketSizeNs - 100, + attributionUids1, attributionTags1, "wl1"); + processor->OnLogEvent(acquire_event.get()); + EXPECT_EQ((roundedBucketStartTimeNs + bucketSizeNs) / NS_PER_SEC + 3, + anomalyTracker->getAlarmTimestampSec(dimensionKey1)); + EXPECT_EQ(0u, anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1)); + + // Acquire the wakelock "wc1" again. + acquire_event = + CreateAcquireWakelockEvent(roundedBucketStartTimeNs + bucketSizeNs + 2 * NS_PER_SEC + 1, + attributionUids1, attributionTags1, "wl1"); + processor->OnLogEvent(acquire_event.get()); + // The alarm does not change. + EXPECT_EQ((roundedBucketStartTimeNs + bucketSizeNs) / NS_PER_SEC + 3, + anomalyTracker->getAlarmTimestampSec(dimensionKey1)); + EXPECT_EQ(0u, anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1)); + + // Anomaly alarm fired late. + const int64_t firedAlarmTimestampNs = roundedBucketStartTimeNs + 2 * bucketSizeNs - NS_PER_SEC; + auto alarmTriggerEvent = CreateBatterySaverOnEvent(firedAlarmTimestampNs); + processor->OnLogEvent(alarmTriggerEvent.get(), firedAlarmTimestampNs); + EXPECT_EQ(0u, anomalyTracker->getAlarmTimestampSec(dimensionKey1)); + EXPECT_EQ(refractory_period_sec + firedAlarmTimestampNs / NS_PER_SEC, + anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1)); + + acquire_event = CreateAcquireWakelockEvent(roundedBucketStartTimeNs + 2 * bucketSizeNs - 100, + attributionUids1, attributionTags1, "wl1"); + processor->OnLogEvent(acquire_event.get()); + EXPECT_EQ(0u, anomalyTracker->getAlarmTimestampSec(dimensionKey1)); + EXPECT_EQ(refractory_period_sec + firedAlarmTimestampNs / NS_PER_SEC, + anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1)); + + int64_t release_event_time = bucketStartTimeNs + 2 * bucketSizeNs + 1; + auto release_event = CreateReleaseWakelockEvent(release_event_time, attributionUids1, + attributionTags1, "wl1"); + processor->OnLogEvent(release_event.get(), release_event_time); + EXPECT_EQ(0u, anomalyTracker->getAlarmTimestampSec(dimensionKey1)); + // Within the refractory period. No anomaly. + EXPECT_EQ(refractory_period_sec + firedAlarmTimestampNs / NS_PER_SEC, + anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1)); + + // A new wakelock, but still within refractory period. + acquire_event = CreateAcquireWakelockEvent( + roundedBucketStartTimeNs + 2 * bucketSizeNs + 10 * NS_PER_SEC, attributionUids1, + attributionTags1, "wl1"); + processor->OnLogEvent(acquire_event.get()); + EXPECT_EQ(refractory_period_sec + firedAlarmTimestampNs / NS_PER_SEC, + anomalyTracker->getAlarmTimestampSec(dimensionKey1)); + + release_event = + CreateReleaseWakelockEvent(roundedBucketStartTimeNs + 3 * bucketSizeNs - NS_PER_SEC, + attributionUids1, attributionTags1, "wl1"); + // Still in the refractory period. No anomaly. + processor->OnLogEvent(release_event.get()); + EXPECT_EQ(refractory_period_sec + firedAlarmTimestampNs / NS_PER_SEC, + anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1)); + + acquire_event = CreateAcquireWakelockEvent( + roundedBucketStartTimeNs + 5 * bucketSizeNs - 2 * NS_PER_SEC - 5, attributionUids1, + attributionTags1, "wl1"); + processor->OnLogEvent(acquire_event.get()); + EXPECT_EQ((roundedBucketStartTimeNs + 5 * bucketSizeNs) / NS_PER_SEC + 1, + anomalyTracker->getAlarmTimestampSec(dimensionKey1)); + + release_event_time = roundedBucketStartTimeNs + 5 * bucketSizeNs - 2 * NS_PER_SEC - 4; + release_event = CreateReleaseWakelockEvent(release_event_time, attributionUids1, + attributionTags1, "wl1"); + processor->OnLogEvent(release_event.get(), release_event_time); + EXPECT_EQ(0u, anomalyTracker->getAlarmTimestampSec(dimensionKey1)); + + acquire_event = CreateAcquireWakelockEvent( + roundedBucketStartTimeNs + 5 * bucketSizeNs - 2 * NS_PER_SEC - 3, attributionUids1, + attributionTags1, "wl1"); + processor->OnLogEvent(acquire_event.get()); + EXPECT_EQ((roundedBucketStartTimeNs + 5 * bucketSizeNs) / NS_PER_SEC + 1, + anomalyTracker->getAlarmTimestampSec(dimensionKey1)); +} + +#else +GTEST_LOG_(INFO) << "This test does nothing.\n"; +#endif + +} // namespace statsd +} // namespace os +} // namespace android diff --git a/statsd/tests/e2e/Attribution_e2e_test.cpp b/statsd/tests/e2e/Attribution_e2e_test.cpp new file mode 100644 index 00000000..4c2caa90 --- /dev/null +++ b/statsd/tests/e2e/Attribution_e2e_test.cpp @@ -0,0 +1,375 @@ +// 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 +#include + +namespace android { +namespace os { +namespace statsd { + +#ifdef __ANDROID__ + +namespace { + +StatsdConfig CreateStatsdConfig(const Position position) { + StatsdConfig config; + config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root. + auto wakelockAcquireMatcher = CreateAcquireWakelockAtomMatcher(); + auto attributionNodeMatcher = + wakelockAcquireMatcher.mutable_simple_atom_matcher()->add_field_value_matcher(); + attributionNodeMatcher->set_field(1); + attributionNodeMatcher->set_position(Position::ANY); + auto uidMatcher = attributionNodeMatcher->mutable_matches_tuple()->add_field_value_matcher(); + uidMatcher->set_field(1); // uid field. + uidMatcher->set_eq_string("com.android.gmscore"); + + *config.add_atom_matcher() = wakelockAcquireMatcher; + + auto countMetric = config.add_count_metric(); + countMetric->set_id(123456); + countMetric->set_what(wakelockAcquireMatcher.id()); + *countMetric->mutable_dimensions_in_what() = + CreateAttributionUidAndTagDimensions( + util::WAKELOCK_STATE_CHANGED, {position}); + countMetric->set_bucket(FIVE_MINUTES); + return config; +} + +// GMS core node is in the middle. +std::vector attributionUids1 = {111, 222, 333}; +std::vector attributionTags1 = {"App1", "GMSCoreModule1", "App3"}; + +// GMS core node is the last one. +std::vector attributionUids2 = {111, 333, 222}; +std::vector attributionTags2 = {"App1", "App3", "GMSCoreModule1"}; + +// GMS core node is the first one. +std::vector attributionUids3 = {222, 333}; +std::vector attributionTags3 = {"GMSCoreModule1", "App3"}; + +// Single GMS core node. +std::vector attributionUids4 = {222}; +std::vector attributionTags4 = {"GMSCoreModule1"}; + +// GMS core has another uid. +std::vector attributionUids5 = {111, 444, 333}; +std::vector attributionTags5 = {"App1", "GMSCoreModule2", "App3"}; + +// Multiple GMS core nodes. +std::vector attributionUids6 = {444, 222}; +std::vector attributionTags6 = {"GMSCoreModule2", "GMSCoreModule1"}; + +// No GMS core nodes +std::vector attributionUids7 = {111, 333}; +std::vector attributionTags7 = {"App1", "App3"}; + +std::vector attributionUids8 = {111}; +std::vector attributionTags8 = {"App1"}; + +// GMS core node with isolated uid. +const int isolatedUid = 666; +std::vector attributionUids9 = {isolatedUid}; +std::vector attributionTags9 = {"GMSCoreModule3"}; + +std::vector attributionUids10 = {isolatedUid}; +std::vector attributionTags10 = {"GMSCoreModule1"}; + +} // namespace + +TEST(AttributionE2eTest, TestAttributionMatchAndSliceByFirstUid) { + auto config = CreateStatsdConfig(Position::FIRST); + int64_t bucketStartTimeNs = 10000000000; + int64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(config.count_metric(0).bucket()) * 1000000; + + ConfigKey cfgKey; + auto processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey); + ASSERT_EQ(processor->mMetricsManagers.size(), 1u); + EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid()); + + // Here it assumes that GMS core has two uids. + processor->getUidMap()->updateMap( + 1, {222, 444, 111, 333}, {1, 1, 2, 2}, + {String16("v1"), String16("v1"), String16("v2"), String16("v2")}, + {String16("com.android.gmscore"), String16("com.android.gmscore"), String16("app1"), + String16("APP3")}, + {String16(""), String16(""), String16(""), String16("")}); + + std::vector> events; + // Events 1~4 are in the 1st bucket. + events.push_back(CreateAcquireWakelockEvent(bucketStartTimeNs + 2, attributionUids1, + attributionTags1, "wl1")); + events.push_back(CreateAcquireWakelockEvent(bucketStartTimeNs + 200, attributionUids2, + attributionTags2, "wl1")); + events.push_back(CreateAcquireWakelockEvent(bucketStartTimeNs + bucketSizeNs - 1, + attributionUids3, attributionTags3, "wl1")); + events.push_back(CreateAcquireWakelockEvent(bucketStartTimeNs + bucketSizeNs, attributionUids4, + attributionTags4, "wl1")); + + // Events 5~8 are in the 3rd bucket. + events.push_back(CreateAcquireWakelockEvent(bucketStartTimeNs + 2 * bucketSizeNs + 1, + attributionUids5, attributionTags5, "wl2")); + events.push_back(CreateAcquireWakelockEvent(bucketStartTimeNs + 2 * bucketSizeNs + 100, + attributionUids6, attributionTags6, "wl2")); + events.push_back(CreateAcquireWakelockEvent(bucketStartTimeNs + 3 * bucketSizeNs - 2, + attributionUids7, attributionTags7, "wl2")); + events.push_back(CreateAcquireWakelockEvent(bucketStartTimeNs + 3 * bucketSizeNs, + attributionUids8, attributionTags8, "wl2")); + events.push_back(CreateAcquireWakelockEvent(bucketStartTimeNs + 3 * bucketSizeNs + 1, + attributionUids9, attributionTags9, "wl2")); + events.push_back(CreateAcquireWakelockEvent(bucketStartTimeNs + 3 * bucketSizeNs + 100, + attributionUids9, attributionTags9, "wl2")); + events.push_back(CreateIsolatedUidChangedEvent(bucketStartTimeNs + 3 * bucketSizeNs - 1, 222, + isolatedUid, true /*is_create*/)); + events.push_back(CreateIsolatedUidChangedEvent(bucketStartTimeNs + 3 * bucketSizeNs + 10, 222, + isolatedUid, false /*is_create*/)); + + sortLogEventsByTimestamp(&events); + + for (const auto& event : events) { + processor->OnLogEvent(event.get()); + } + ConfigMetricsReportList reports; + vector buffer; + processor->onDumpReport(cfgKey, bucketStartTimeNs + 4 * bucketSizeNs + 1, false, true, ADB_DUMP, + FAST, &buffer); + EXPECT_TRUE(buffer.size() > 0); + EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size())); + backfillDimensionPath(&reports); + backfillStringInReport(&reports); + backfillStartEndTimestamp(&reports); + ASSERT_EQ(reports.reports_size(), 1); + ASSERT_EQ(reports.reports(0).metrics_size(), 1); + + StatsLogReport::CountMetricDataWrapper countMetrics; + sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).count_metrics(), &countMetrics); + ASSERT_EQ(countMetrics.data_size(), 4); + + auto data = countMetrics.data(0); + ValidateAttributionUidAndTagDimension(data.dimensions_in_what(), + util::WAKELOCK_STATE_CHANGED, 111, "App1"); + ASSERT_EQ(data.bucket_info_size(), 2); + EXPECT_EQ(data.bucket_info(0).count(), 2); + EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), bucketStartTimeNs); + EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(), bucketStartTimeNs + bucketSizeNs); + EXPECT_EQ(data.bucket_info(1).count(), 1); + EXPECT_EQ(data.bucket_info(1).start_bucket_elapsed_nanos(), + bucketStartTimeNs + 2 * bucketSizeNs); + EXPECT_EQ(data.bucket_info(1).end_bucket_elapsed_nanos(), bucketStartTimeNs + 3 * bucketSizeNs); + + data = countMetrics.data(1); + ValidateAttributionUidAndTagDimension(data.dimensions_in_what(), + util::WAKELOCK_STATE_CHANGED, 222, + "GMSCoreModule1"); + ASSERT_EQ(data.bucket_info_size(), 2); + EXPECT_EQ(data.bucket_info(0).count(), 1); + EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), bucketStartTimeNs); + EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(), bucketStartTimeNs + bucketSizeNs); + EXPECT_EQ(data.bucket_info(1).count(), 1); + EXPECT_EQ(data.bucket_info(1).start_bucket_elapsed_nanos(), bucketStartTimeNs + bucketSizeNs); + EXPECT_EQ(data.bucket_info(1).end_bucket_elapsed_nanos(), bucketStartTimeNs + 2 * bucketSizeNs); + + data = countMetrics.data(2); + ValidateAttributionUidAndTagDimension(data.dimensions_in_what(), + util::WAKELOCK_STATE_CHANGED, 222, + "GMSCoreModule3"); + ASSERT_EQ(data.bucket_info_size(), 1); + EXPECT_EQ(data.bucket_info(0).count(), 1); + EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), + bucketStartTimeNs + 3 * bucketSizeNs); + EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(), bucketStartTimeNs + 4 * bucketSizeNs); + + data = countMetrics.data(3); + ValidateAttributionUidAndTagDimension(data.dimensions_in_what(), + util::WAKELOCK_STATE_CHANGED, 444, + "GMSCoreModule2"); + ASSERT_EQ(data.bucket_info_size(), 1); + EXPECT_EQ(data.bucket_info(0).count(), 1); + EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), + bucketStartTimeNs + 2 * bucketSizeNs); + EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(), bucketStartTimeNs + 3 * bucketSizeNs); +} + +TEST(AttributionE2eTest, TestAttributionMatchAndSliceByChain) { + auto config = CreateStatsdConfig(Position::ALL); + int64_t bucketStartTimeNs = 10000000000; + int64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(config.count_metric(0).bucket()) * 1000000; + + ConfigKey cfgKey; + auto processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey); + ASSERT_EQ(processor->mMetricsManagers.size(), 1u); + EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid()); + + // Here it assumes that GMS core has two uids. + processor->getUidMap()->updateMap( + 1, {222, 444, 111, 333}, {1, 1, 2, 2}, + {String16("v1"), String16("v1"), String16("v2"), String16("v2")}, + {String16("com.android.gmscore"), String16("com.android.gmscore"), String16("app1"), + String16("APP3")}, + {String16(""), String16(""), String16(""), String16("")}); + + std::vector> events; + // Events 1~4 are in the 1st bucket. + events.push_back(CreateAcquireWakelockEvent(bucketStartTimeNs + 2, attributionUids1, + attributionTags1, "wl1")); + events.push_back(CreateAcquireWakelockEvent(bucketStartTimeNs + 200, attributionUids2, + attributionTags2, "wl1")); + events.push_back(CreateAcquireWakelockEvent(bucketStartTimeNs + bucketSizeNs - 1, + attributionUids3, attributionTags3, "wl1")); + events.push_back(CreateAcquireWakelockEvent(bucketStartTimeNs + bucketSizeNs, attributionUids4, + attributionTags4, "wl1")); + + // Events 5~8 are in the 3rd bucket. + events.push_back(CreateAcquireWakelockEvent(bucketStartTimeNs + 2 * bucketSizeNs + 1, + attributionUids5, attributionTags5, "wl2")); + events.push_back(CreateAcquireWakelockEvent(bucketStartTimeNs + 2 * bucketSizeNs + 100, + attributionUids6, attributionTags6, "wl2")); + events.push_back(CreateAcquireWakelockEvent(bucketStartTimeNs + 3 * bucketSizeNs - 2, + attributionUids7, attributionTags7, "wl2")); + events.push_back(CreateAcquireWakelockEvent(bucketStartTimeNs + 3 * bucketSizeNs, + attributionUids8, attributionTags8, "wl2")); + events.push_back(CreateAcquireWakelockEvent(bucketStartTimeNs + 3 * bucketSizeNs + 1, + attributionUids10, attributionTags10, "wl2")); + events.push_back(CreateAcquireWakelockEvent(bucketStartTimeNs + 3 * bucketSizeNs + 100, + attributionUids10, attributionTags10, "wl2")); + events.push_back(CreateIsolatedUidChangedEvent(bucketStartTimeNs + 3 * bucketSizeNs - 1, 222, + isolatedUid, true /*is_create*/)); + events.push_back(CreateIsolatedUidChangedEvent(bucketStartTimeNs + 3 * bucketSizeNs + 10, 222, + isolatedUid, false /*is_create*/)); + + sortLogEventsByTimestamp(&events); + + for (const auto& event : events) { + processor->OnLogEvent(event.get()); + } + ConfigMetricsReportList reports; + vector buffer; + processor->onDumpReport(cfgKey, bucketStartTimeNs + 4 * bucketSizeNs + 1, false, true, ADB_DUMP, + FAST, &buffer); + EXPECT_TRUE(buffer.size() > 0); + EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size())); + backfillDimensionPath(&reports); + backfillStringInReport(&reports); + backfillStartEndTimestamp(&reports); + ASSERT_EQ(reports.reports_size(), 1); + ASSERT_EQ(reports.reports(0).metrics_size(), 1); + + StatsLogReport::CountMetricDataWrapper countMetrics; + sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).count_metrics(), &countMetrics); + ASSERT_EQ(countMetrics.data_size(), 6); + + auto data = countMetrics.data(0); + ValidateAttributionUidAndTagDimension(data.dimensions_in_what(), + util::WAKELOCK_STATE_CHANGED, 222, + "GMSCoreModule1"); + ASSERT_EQ(2, data.bucket_info_size()); + EXPECT_EQ(1, data.bucket_info(0).count()); + EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, data.bucket_info(0).start_bucket_elapsed_nanos()); + EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs, data.bucket_info(0).end_bucket_elapsed_nanos()); + EXPECT_EQ(1, data.bucket_info(1).count()); + EXPECT_EQ(bucketStartTimeNs + 3 * bucketSizeNs, + data.bucket_info(1).start_bucket_elapsed_nanos()); + EXPECT_EQ(bucketStartTimeNs + 4 * bucketSizeNs, data.bucket_info(1).end_bucket_elapsed_nanos()); + + data = countMetrics.data(1); + ValidateUidDimension(data.dimensions_in_what(), 0, util::WAKELOCK_STATE_CHANGED, 222); + ValidateAttributionUidAndTagDimension(data.dimensions_in_what(), 0, + util::WAKELOCK_STATE_CHANGED, 222, + "GMSCoreModule1"); + ValidateUidDimension(data.dimensions_in_what(), 1, util::WAKELOCK_STATE_CHANGED, 333); + ValidateAttributionUidAndTagDimension(data.dimensions_in_what(), 1, + util::WAKELOCK_STATE_CHANGED, 333, "App3"); + ASSERT_EQ(data.bucket_info_size(), 1); + EXPECT_EQ(data.bucket_info(0).count(), 1); + EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), bucketStartTimeNs); + EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(), bucketStartTimeNs + bucketSizeNs); + + data = countMetrics.data(2); + ValidateUidDimension(data.dimensions_in_what(), 0, util::WAKELOCK_STATE_CHANGED, 444); + ValidateAttributionUidAndTagDimension(data.dimensions_in_what(), 0, + util::WAKELOCK_STATE_CHANGED, 444, + "GMSCoreModule2"); + ValidateUidDimension(data.dimensions_in_what(), 1, util::WAKELOCK_STATE_CHANGED, 222); + ValidateAttributionUidAndTagDimension(data.dimensions_in_what(), 1, + util::WAKELOCK_STATE_CHANGED, 222, + "GMSCoreModule1"); + ASSERT_EQ(data.bucket_info_size(), 1); + EXPECT_EQ(data.bucket_info(0).count(), 1); + EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs, + data.bucket_info(0).start_bucket_elapsed_nanos()); + EXPECT_EQ(bucketStartTimeNs + 3 * bucketSizeNs, data.bucket_info(0).end_bucket_elapsed_nanos()); + + data = countMetrics.data(3); + ValidateUidDimension(data.dimensions_in_what(), 0, util::WAKELOCK_STATE_CHANGED, 111); + ValidateAttributionUidAndTagDimension(data.dimensions_in_what(), 0, + util::WAKELOCK_STATE_CHANGED, 111, "App1"); + ValidateUidDimension(data.dimensions_in_what(), 1, util::WAKELOCK_STATE_CHANGED, 222); + ValidateAttributionUidAndTagDimension(data.dimensions_in_what(), 1, + util::WAKELOCK_STATE_CHANGED, 222, + "GMSCoreModule1"); + ValidateUidDimension(data.dimensions_in_what(), 2, util::WAKELOCK_STATE_CHANGED, 333); + ValidateAttributionUidAndTagDimension(data.dimensions_in_what(), 2, + util::WAKELOCK_STATE_CHANGED, 333, "App3"); + ASSERT_EQ(data.bucket_info_size(), 1); + EXPECT_EQ(data.bucket_info(0).count(), 1); + EXPECT_EQ(bucketStartTimeNs, data.bucket_info(0).start_bucket_elapsed_nanos()); + EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, data.bucket_info(0).end_bucket_elapsed_nanos()); + + data = countMetrics.data(4); + ValidateUidDimension(data.dimensions_in_what(), 0, util::WAKELOCK_STATE_CHANGED, 111); + ValidateAttributionUidAndTagDimension(data.dimensions_in_what(), 0, + util::WAKELOCK_STATE_CHANGED, 111, "App1"); + ValidateUidDimension(data.dimensions_in_what(), 1, util::WAKELOCK_STATE_CHANGED, 333); + ValidateAttributionUidAndTagDimension(data.dimensions_in_what(), 1, + util::WAKELOCK_STATE_CHANGED, 333, "App3"); + ValidateUidDimension(data.dimensions_in_what(), 2, util::WAKELOCK_STATE_CHANGED, 222); + ValidateAttributionUidAndTagDimension(data.dimensions_in_what(), 2, + util::WAKELOCK_STATE_CHANGED, 222, + "GMSCoreModule1"); + ASSERT_EQ(data.bucket_info_size(), 1); + EXPECT_EQ(data.bucket_info(0).count(), 1); + EXPECT_EQ(bucketStartTimeNs, data.bucket_info(0).start_bucket_elapsed_nanos()); + EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, data.bucket_info(0).end_bucket_elapsed_nanos()); + + data = countMetrics.data(5); + ValidateUidDimension(data.dimensions_in_what(), 0, util::WAKELOCK_STATE_CHANGED, 111); + ValidateAttributionUidAndTagDimension(data.dimensions_in_what(), 0, + util::WAKELOCK_STATE_CHANGED, 111, "App1"); + ValidateUidDimension(data.dimensions_in_what(), 1, util::WAKELOCK_STATE_CHANGED, 444); + ValidateAttributionUidAndTagDimension(data.dimensions_in_what(), 1, + util::WAKELOCK_STATE_CHANGED, 444, + "GMSCoreModule2"); + ValidateUidDimension(data.dimensions_in_what(), 2, util::WAKELOCK_STATE_CHANGED, 333); + ValidateAttributionUidAndTagDimension(data.dimensions_in_what(), 2, + util::WAKELOCK_STATE_CHANGED, 333, "App3"); + ASSERT_EQ(data.bucket_info_size(), 1); + EXPECT_EQ(data.bucket_info(0).count(), 1); + EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs, + data.bucket_info(0).start_bucket_elapsed_nanos()); + EXPECT_EQ(bucketStartTimeNs + 3 * bucketSizeNs, data.bucket_info(0).end_bucket_elapsed_nanos()); +} + +#else +GTEST_LOG_(INFO) << "This test does nothing.\n"; +#endif + +} // namespace statsd +} // namespace os +} // namespace android diff --git a/statsd/tests/e2e/ConfigTtl_e2e_test.cpp b/statsd/tests/e2e/ConfigTtl_e2e_test.cpp new file mode 100644 index 00000000..0bce0baa --- /dev/null +++ b/statsd/tests/e2e/ConfigTtl_e2e_test.cpp @@ -0,0 +1,114 @@ +// Copyright (C) 2018 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 CreateStatsdConfig(int num_buckets, int threshold) { + StatsdConfig config; + config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root. + auto wakelockAcquireMatcher = CreateAcquireWakelockAtomMatcher(); + + *config.add_atom_matcher() = wakelockAcquireMatcher; + + auto countMetric = config.add_count_metric(); + countMetric->set_id(123456); + countMetric->set_what(wakelockAcquireMatcher.id()); + *countMetric->mutable_dimensions_in_what() = CreateAttributionUidDimensions( + util::WAKELOCK_STATE_CHANGED, {Position::FIRST}); + countMetric->set_bucket(FIVE_MINUTES); + + auto alert = config.add_alert(); + alert->set_id(StringToId("alert")); + alert->set_metric_id(123456); + alert->set_num_buckets(num_buckets); + alert->set_refractory_period_secs(10); + alert->set_trigger_if_sum_gt(threshold); + + // Two hours + config.set_ttl_in_seconds(2 * 3600); + return config; +} + +} // namespace + +TEST(ConfigTtlE2eTest, TestCountMetric) { + const int num_buckets = 1; + const int threshold = 3; + auto config = CreateStatsdConfig(num_buckets, threshold); + const uint64_t alert_id = config.alert(0).id(); + const uint32_t refractory_period_sec = config.alert(0).refractory_period_secs(); + + int64_t bucketStartTimeNs = 10000000000; + int64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(config.count_metric(0).bucket()) * 1000000; + + ConfigKey cfgKey; + auto processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey); + ASSERT_EQ(processor->mMetricsManagers.size(), 1u); + EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid()); + + std::vector attributionUids1 = {111}; + std::vector attributionTags1 = {"App1"}; + + FieldValue fieldValue1(Field(util::WAKELOCK_STATE_CHANGED, (int32_t)0x02010101), + Value((int32_t)111)); + HashableDimensionKey whatKey1({fieldValue1}); + MetricDimensionKey dimensionKey1(whatKey1, DEFAULT_DIMENSION_KEY); + + FieldValue fieldValue2(Field(util::WAKELOCK_STATE_CHANGED, (int32_t)0x02010101), + Value((int32_t)222)); + HashableDimensionKey whatKey2({fieldValue2}); + MetricDimensionKey dimensionKey2(whatKey2, DEFAULT_DIMENSION_KEY); + + auto event = CreateAcquireWakelockEvent(bucketStartTimeNs + 2, attributionUids1, + attributionTags1, "wl1"); + processor->OnLogEvent(event.get()); + + event = CreateAcquireWakelockEvent(bucketStartTimeNs + bucketSizeNs + 2, attributionUids1, + attributionTags1, "wl2"); + processor->OnLogEvent(event.get()); + + event = CreateAcquireWakelockEvent(bucketStartTimeNs + 25 * bucketSizeNs + 2, attributionUids1, + attributionTags1, "wl1"); + processor->OnLogEvent(event.get()); + + EXPECT_EQ((int64_t)(bucketStartTimeNs + 25 * bucketSizeNs + 2 + 2 * 3600 * NS_PER_SEC), + processor->mMetricsManagers.begin()->second->getTtlEndNs()); + + // Clear the data stored on disk as a result of the ttl. + vector buffer; + processor->onDumpReport(cfgKey, bucketStartTimeNs + 25 * bucketSizeNs + 3, false, true, + ADB_DUMP, FAST, &buffer); +} + +#else +GTEST_LOG_(INFO) << "This test does nothing.\n"; +#endif + +} // namespace statsd +} // namespace os +} // namespace android diff --git a/statsd/tests/e2e/ConfigUpdate_e2e_ab_test.cpp b/statsd/tests/e2e/ConfigUpdate_e2e_ab_test.cpp new file mode 100644 index 00000000..9b1cb12d --- /dev/null +++ b/statsd/tests/e2e/ConfigUpdate_e2e_ab_test.cpp @@ -0,0 +1,376 @@ +// Copyright (C) 2020 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 +#include +#include + +#include "flags/flags.h" +#include "src/StatsLogProcessor.h" +#include "src/storage/StorageManager.h" +#include "tests/statsd_test_util.h" + +namespace android { +namespace os { +namespace statsd { + +#ifdef __ANDROID__ +#define STATS_DATA_DIR "/data/misc/stats-data" + +using android::base::SetProperty; +using android::base::StringPrintf; +using ::ndk::SharedRefBase; +using namespace std; + +namespace { + +StatsdConfig CreateSimpleConfig() { + StatsdConfig config; + config.add_allowed_log_source("AID_STATSD"); + config.set_hash_strings_in_metric_report(false); + + *config.add_atom_matcher() = CreateBatteryStateUsbMatcher(); + // Simple count metric so the config isn't empty. + CountMetric* countMetric1 = config.add_count_metric(); + countMetric1->set_id(StringToId("Count1")); + countMetric1->set_what(config.atom_matcher(0).id()); + countMetric1->set_bucket(FIVE_MINUTES); + return config; +} +} // namespace + +// Setup for parameterized tests. +class ConfigUpdateE2eAbTest : public TestWithParam { +}; + +INSTANTIATE_TEST_SUITE_P(ConfigUpdateE2eAbTest, ConfigUpdateE2eAbTest, testing::Bool()); + +TEST_P(ConfigUpdateE2eAbTest, TestUidMapVersionStringInstaller) { + sp uidMap = new UidMap(); + vector uids({1000}); + vector versions({1}); + vector apps({String16("app1")}); + vector versionStrings({String16("v1")}); + vector installers({String16("installer1")}); + uidMap->updateMap(1, uids, versions, versionStrings, apps, installers); + + StatsdConfig config = CreateSimpleConfig(); + config.set_version_strings_in_metric_report(true); + config.set_installer_in_metric_report(false); + int64_t baseTimeNs = getElapsedRealtimeNs(); + + ConfigKey cfgKey(0, 12345); + sp processor = + CreateStatsLogProcessor(baseTimeNs, baseTimeNs, config, cfgKey, nullptr, 0, uidMap); + EXPECT_EQ(processor->mMetricsManagers.size(), 1u); + sp metricsManager = processor->mMetricsManagers.begin()->second; + EXPECT_TRUE(metricsManager->isConfigValid()); + + // Now update. + config.set_version_strings_in_metric_report(false); + config.set_installer_in_metric_report(true); + processor->OnConfigUpdated(baseTimeNs + 1000, cfgKey, config, GetParam()); + EXPECT_EQ(processor->mMetricsManagers.size(), 1u); + EXPECT_EQ(metricsManager == processor->mMetricsManagers.begin()->second, GetParam()); + EXPECT_TRUE(metricsManager->isConfigValid()); + + ConfigMetricsReportList reports; + vector buffer; + processor->onDumpReport(cfgKey, baseTimeNs + 1001, false, true, ADB_DUMP, FAST, &buffer); + EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size())); + // First report is written to disk when the update happens. + ASSERT_EQ(reports.reports_size(), 2); + UidMapping uidMapping = reports.reports(1).uid_map(); + ASSERT_EQ(uidMapping.snapshots_size(), 1); + ASSERT_EQ(uidMapping.snapshots(0).package_info_size(), 1); + EXPECT_FALSE(uidMapping.snapshots(0).package_info(0).has_version_string()); + EXPECT_EQ(uidMapping.snapshots(0).package_info(0).installer(), "installer1"); +} + +TEST_P(ConfigUpdateE2eAbTest, TestHashStrings) { + sp uidMap = new UidMap(); + vector uids({1000}); + vector versions({1}); + vector apps({String16("app1")}); + vector versionStrings({String16("v1")}); + vector installers({String16("installer1")}); + uidMap->updateMap(1, uids, versions, versionStrings, apps, installers); + + StatsdConfig config = CreateSimpleConfig(); + config.set_version_strings_in_metric_report(true); + config.set_hash_strings_in_metric_report(true); + int64_t baseTimeNs = getElapsedRealtimeNs(); + + ConfigKey cfgKey(0, 12345); + sp processor = + CreateStatsLogProcessor(baseTimeNs, baseTimeNs, config, cfgKey, nullptr, 0, uidMap); + EXPECT_EQ(processor->mMetricsManagers.size(), 1u); + sp metricsManager = processor->mMetricsManagers.begin()->second; + EXPECT_TRUE(metricsManager->isConfigValid()); + + // Now update. + config.set_hash_strings_in_metric_report(false); + processor->OnConfigUpdated(baseTimeNs + 1000, cfgKey, config, GetParam()); + EXPECT_EQ(processor->mMetricsManagers.size(), 1u); + EXPECT_EQ(metricsManager == processor->mMetricsManagers.begin()->second, GetParam()); + EXPECT_TRUE(metricsManager->isConfigValid()); + + ConfigMetricsReportList reports; + vector buffer; + processor->onDumpReport(cfgKey, baseTimeNs + 1001, false, true, ADB_DUMP, FAST, &buffer); + EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size())); + // First report is written to disk when the update happens. + ASSERT_EQ(reports.reports_size(), 2); + UidMapping uidMapping = reports.reports(1).uid_map(); + ASSERT_EQ(uidMapping.snapshots_size(), 1); + ASSERT_EQ(uidMapping.snapshots(0).package_info_size(), 1); + EXPECT_TRUE(uidMapping.snapshots(0).package_info(0).has_version_string()); + EXPECT_FALSE(uidMapping.snapshots(0).package_info(0).has_version_string_hash()); +} + +TEST_P(ConfigUpdateE2eAbTest, TestAnnotations) { + StatsdConfig config = CreateSimpleConfig(); + StatsdConfig_Annotation* annotation = config.add_annotation(); + annotation->set_field_int64(11); + annotation->set_field_int32(1); + int64_t baseTimeNs = getElapsedRealtimeNs(); + ConfigKey cfgKey(0, 12345); + sp processor = + CreateStatsLogProcessor(baseTimeNs, baseTimeNs, config, cfgKey); + + // Now update + config.clear_annotation(); + annotation = config.add_annotation(); + annotation->set_field_int64(22); + annotation->set_field_int32(2); + processor->OnConfigUpdated(baseTimeNs + 1000, cfgKey, config, GetParam()); + + ConfigMetricsReportList reports; + vector buffer; + processor->onDumpReport(cfgKey, baseTimeNs + 1001, false, true, ADB_DUMP, FAST, &buffer); + EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size())); + // First report is written to disk when the update happens. + ASSERT_EQ(reports.reports_size(), 2); + ConfigMetricsReport report = reports.reports(1); + EXPECT_EQ(report.annotation_size(), 1); + EXPECT_EQ(report.annotation(0).field_int64(), 22); + EXPECT_EQ(report.annotation(0).field_int32(), 2); +} + +TEST_P(ConfigUpdateE2eAbTest, TestPersistLocally) { + StatsdConfig config = CreateSimpleConfig(); + config.set_persist_locally(false); + int64_t baseTimeNs = getElapsedRealtimeNs(); + ConfigKey cfgKey(0, 12345); + sp processor = + CreateStatsLogProcessor(baseTimeNs, baseTimeNs, config, cfgKey); + ConfigMetricsReportList reports; + vector buffer; + processor->onDumpReport(cfgKey, baseTimeNs + 1001, false, true, ADB_DUMP, FAST, &buffer); + EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size())); + ASSERT_EQ(reports.reports_size(), 1); + // Number of reports should still be 1 since persist_locally is false. + reports.Clear(); + buffer.clear(); + processor->onDumpReport(cfgKey, baseTimeNs + 1001, false, true, ADB_DUMP, FAST, &buffer); + EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size())); + ASSERT_EQ(reports.reports_size(), 1); + + // Now update. + config.set_persist_locally(true); + processor->OnConfigUpdated(baseTimeNs + 1000, cfgKey, config, GetParam()); + + // Should get 2: 1 in memory + 1 on disk. Both should be saved on disk. + reports.Clear(); + buffer.clear(); + processor->onDumpReport(cfgKey, baseTimeNs + 1001, false, true, ADB_DUMP, FAST, &buffer); + EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size())); + ASSERT_EQ(reports.reports_size(), 2); + // Should get 3, 2 on disk + 1 in memory. + reports.Clear(); + buffer.clear(); + processor->onDumpReport(cfgKey, baseTimeNs + 1001, false, true, ADB_DUMP, FAST, &buffer); + EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size())); + ASSERT_EQ(reports.reports_size(), 3); + string suffix = StringPrintf("%d_%lld", cfgKey.GetUid(), (long long)cfgKey.GetId()); + StorageManager::deleteSuffixedFiles(STATS_DATA_DIR, suffix.c_str()); + string historySuffix = + StringPrintf("%d_%lld_history", cfgKey.GetUid(), (long long)cfgKey.GetId()); + StorageManager::deleteSuffixedFiles(STATS_DATA_DIR, historySuffix.c_str()); +} + +TEST_P(ConfigUpdateE2eAbTest, TestNoReportMetrics) { + StatsdConfig config = CreateSimpleConfig(); + // Second simple count metric. + CountMetric* countMetric = config.add_count_metric(); + countMetric->set_id(StringToId("Count2")); + countMetric->set_what(config.atom_matcher(0).id()); + countMetric->set_bucket(FIVE_MINUTES); + config.add_no_report_metric(config.count_metric(0).id()); + int64_t baseTimeNs = getElapsedRealtimeNs(); + ConfigKey cfgKey(0, 12345); + sp processor = + CreateStatsLogProcessor(baseTimeNs, baseTimeNs, config, cfgKey); + + // Now update. + config.clear_no_report_metric(); + config.add_no_report_metric(config.count_metric(1).id()); + processor->OnConfigUpdated(baseTimeNs + 1000, cfgKey, config, GetParam()); + + ConfigMetricsReportList reports; + vector buffer; + processor->onDumpReport(cfgKey, baseTimeNs + 1001, false, true, ADB_DUMP, FAST, &buffer); + EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size())); + // First report is written to disk when the update happens. + ASSERT_EQ(reports.reports_size(), 2); + // First report (before update) has the first count metric. + ASSERT_EQ(reports.reports(0).metrics_size(), 1); + EXPECT_EQ(reports.reports(0).metrics(0).metric_id(), config.count_metric(1).id()); + // Second report (after update) has the first count metric. + ASSERT_EQ(reports.reports(1).metrics_size(), 1); + EXPECT_EQ(reports.reports(1).metrics(0).metric_id(), config.count_metric(0).id()); +} + +TEST_P(ConfigUpdateE2eAbTest, TestAtomsAllowedFromAnyUid) { + StatsdConfig config = CreateSimpleConfig(); + int64_t baseTimeNs = getElapsedRealtimeNs(); + ConfigKey cfgKey(0, 12345); + sp processor = + CreateStatsLogProcessor(baseTimeNs, baseTimeNs, config, cfgKey); + // Uses AID_ROOT, which isn't in allowed log sources. + unique_ptr event = CreateBatteryStateChangedEvent( + baseTimeNs + 2, BatteryPluggedStateEnum::BATTERY_PLUGGED_USB); + processor->OnLogEvent(event.get()); + ConfigMetricsReportList reports; + vector buffer; + processor->onDumpReport(cfgKey, baseTimeNs + 1001, true, true, ADB_DUMP, FAST, &buffer); + EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size())); + ASSERT_EQ(reports.reports_size(), 1); + // Check the metric and make sure it has 0 count. + ASSERT_EQ(reports.reports(0).metrics_size(), 1); + EXPECT_FALSE(reports.reports(0).metrics(0).has_count_metrics()); + + // Now update. Allow plugged state to be logged from any uid, so the atom will be counted. + config.add_whitelisted_atom_ids(util::PLUGGED_STATE_CHANGED); + processor->OnConfigUpdated(baseTimeNs + 1000, cfgKey, config, GetParam()); + unique_ptr event2 = CreateBatteryStateChangedEvent( + baseTimeNs + 2000, BatteryPluggedStateEnum::BATTERY_PLUGGED_USB); + processor->OnLogEvent(event.get()); + reports.Clear(); + buffer.clear(); + processor->onDumpReport(cfgKey, baseTimeNs + 3000, true, true, ADB_DUMP, FAST, &buffer); + EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size())); + ASSERT_EQ(reports.reports_size(), 2); + // Check the metric and make sure it has 0 count. + ASSERT_EQ(reports.reports(1).metrics_size(), 1); + EXPECT_TRUE(reports.reports(1).metrics(0).has_count_metrics()); + ASSERT_EQ(reports.reports(1).metrics(0).count_metrics().data_size(), 1); + ASSERT_EQ(reports.reports(1).metrics(0).count_metrics().data(0).bucket_info_size(), 1); + EXPECT_EQ(reports.reports(1).metrics(0).count_metrics().data(0).bucket_info(0).count(), 1); +} + +TEST_P(ConfigUpdateE2eAbTest, TestConfigTtl) { + StatsdConfig config = CreateSimpleConfig(); + config.set_ttl_in_seconds(1); + int64_t baseTimeNs = getElapsedRealtimeNs(); + ConfigKey cfgKey(0, 12345); + sp processor = + CreateStatsLogProcessor(baseTimeNs, baseTimeNs, config, cfgKey); + EXPECT_EQ(processor->mMetricsManagers.size(), 1u); + sp metricsManager = processor->mMetricsManagers.begin()->second; + EXPECT_EQ(metricsManager->getTtlEndNs(), baseTimeNs + NS_PER_SEC); + + config.set_ttl_in_seconds(5); + processor->OnConfigUpdated(baseTimeNs + 2 * NS_PER_SEC, cfgKey, config, GetParam()); + metricsManager = processor->mMetricsManagers.begin()->second; + EXPECT_EQ(metricsManager->getTtlEndNs(), baseTimeNs + 7 * NS_PER_SEC); + + // Clear the data stored on disk as a result of the update. + vector buffer; + processor->onDumpReport(cfgKey, baseTimeNs + 3 * NS_PER_SEC, false, true, ADB_DUMP, FAST, + &buffer); +} + +TEST_P(ConfigUpdateE2eAbTest, TestExistingGaugePullRandomOneSample) { + StatsdConfig config; + config.add_allowed_log_source("AID_ROOT"); + config.add_default_pull_packages("AID_ROOT"); // Fake puller is registered with root. + + AtomMatcher subsystemSleepMatcher = + CreateSimpleAtomMatcher("SubsystemSleep", util::SUBSYSTEM_SLEEP_STATE); + *config.add_atom_matcher() = subsystemSleepMatcher; + + GaugeMetric metric = createGaugeMetric("GaugeSubsystemSleep", subsystemSleepMatcher.id(), + GaugeMetric::RANDOM_ONE_SAMPLE, nullopt, nullopt); + *metric.mutable_dimensions_in_what() = + CreateDimensions(util::SUBSYSTEM_SLEEP_STATE, {1 /* subsystem name */}); + *config.add_gauge_metric() = metric; + + ConfigKey key(123, 987); + uint64_t bucketStartTimeNs = getElapsedRealtimeNs(); + uint64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(TEN_MINUTES) * 1000000LL; + sp processor = CreateStatsLogProcessor( + bucketStartTimeNs, bucketStartTimeNs, config, key, + SharedRefBase::make(), util::SUBSYSTEM_SLEEP_STATE); + + uint64_t updateTimeNs = bucketStartTimeNs + 60 * NS_PER_SEC; + processor->OnConfigUpdated(updateTimeNs, key, config, GetParam()); + uint64_t dumpTimeNs = bucketStartTimeNs + 90 * NS_PER_SEC; + ConfigMetricsReportList reports; + vector buffer; + processor->onDumpReport(key, dumpTimeNs, true, true, ADB_DUMP, NO_TIME_CONSTRAINTS, &buffer); + EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size())); + backfillDimensionPath(&reports); + backfillStringInReport(&reports); + backfillStartEndTimestamp(&reports); + ASSERT_EQ(reports.reports_size(), 2); + + // From after the update + ConfigMetricsReport report = reports.reports(1); + ASSERT_EQ(report.metrics_size(), 1); + // Count screen on while screen is on. There was 1 after the update. + StatsLogReport metricData = report.metrics(0); + EXPECT_EQ(metricData.metric_id(), metric.id()); + EXPECT_TRUE(metricData.has_gauge_metrics()); + StatsLogReport::GaugeMetricDataWrapper gaugeMetrics; + sortMetricDataByDimensionsValue(metricData.gauge_metrics(), &gaugeMetrics); + ASSERT_EQ(gaugeMetrics.data_size(), 2); + + GaugeMetricData data = metricData.gauge_metrics().data(0); + EXPECT_EQ(util::SUBSYSTEM_SLEEP_STATE, data.dimensions_in_what().field()); + ASSERT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size()); + EXPECT_EQ(1 /* subsystem name field */, + data.dimensions_in_what().value_tuple().dimensions_value(0).field()); + EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).value_str(), + "subsystem_name_1"); + ASSERT_EQ(data.bucket_info_size(), 1); + ASSERT_EQ(1, data.bucket_info(0).atom_size()); + ASSERT_EQ(1, data.bucket_info(0).elapsed_timestamp_nanos_size()); + EXPECT_EQ(updateTimeNs, data.bucket_info(0).elapsed_timestamp_nanos(0)); + EXPECT_EQ(MillisToNano(NanoToMillis(updateTimeNs)), + data.bucket_info(0).start_bucket_elapsed_nanos()); + EXPECT_EQ(MillisToNano(NanoToMillis(dumpTimeNs)), + data.bucket_info(0).end_bucket_elapsed_nanos()); + EXPECT_TRUE(data.bucket_info(0).atom(0).subsystem_sleep_state().subsystem_name().empty()); + EXPECT_GT(data.bucket_info(0).atom(0).subsystem_sleep_state().time_millis(), 0); +} + +#else +GTEST_LOG_(INFO) << "This test does nothing.\n"; +#endif + +} // namespace statsd +} // namespace os +} // namespace android diff --git a/statsd/tests/e2e/ConfigUpdate_e2e_test.cpp b/statsd/tests/e2e/ConfigUpdate_e2e_test.cpp new file mode 100644 index 00000000..efb8274d --- /dev/null +++ b/statsd/tests/e2e/ConfigUpdate_e2e_test.cpp @@ -0,0 +1,2895 @@ +// Copyright (C) 2020 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 +#include +#include +#include + +#include + +#include "flags/flags.h" +#include "src/StatsLogProcessor.h" +#include "src/StatsService.h" +#include "src/storage/StorageManager.h" +#include "src/subscriber/SubscriberReporter.h" +#include "tests/statsd_test_util.h" + +namespace android { +namespace os { +namespace statsd { + +#ifdef __ANDROID__ + +using aidl::android::os::StatsDimensionsValueParcel; +using android::base::SetProperty; +using android::base::StringPrintf; +using ::ndk::SharedRefBase; +using namespace std; + +// Tests that only run with the partial config update feature turned on. +namespace { +// Setup for test fixture. +class ConfigUpdateE2eTest : public ::testing::Test { +}; + +void ValidateSubsystemSleepDimension(const DimensionsValue& value, string name) { + EXPECT_EQ(value.field(), util::SUBSYSTEM_SLEEP_STATE); + ASSERT_EQ(value.value_tuple().dimensions_value_size(), 1); + EXPECT_EQ(value.value_tuple().dimensions_value(0).field(), 1 /* subsystem name field */); + EXPECT_EQ(value.value_tuple().dimensions_value(0).value_str(), name); +} + +} // Anonymous namespace. + +TEST_F(ConfigUpdateE2eTest, TestEventMetric) { + StatsdConfig config; + config.add_allowed_log_source("AID_ROOT"); + + AtomMatcher syncStartMatcher = CreateSyncStartAtomMatcher(); + *config.add_atom_matcher() = syncStartMatcher; + AtomMatcher wakelockAcquireMatcher = CreateAcquireWakelockAtomMatcher(); + *config.add_atom_matcher() = wakelockAcquireMatcher; + AtomMatcher screenOnMatcher = CreateScreenTurnedOnAtomMatcher(); + *config.add_atom_matcher() = screenOnMatcher; + AtomMatcher screenOffMatcher = CreateScreenTurnedOffAtomMatcher(); + *config.add_atom_matcher() = screenOffMatcher; + AtomMatcher batteryPluggedUsbMatcher = CreateBatteryStateUsbMatcher(); + *config.add_atom_matcher() = batteryPluggedUsbMatcher; + AtomMatcher unpluggedMatcher = CreateBatteryStateNoneMatcher(); + *config.add_atom_matcher() = unpluggedMatcher; + + AtomMatcher* combinationMatcher = config.add_atom_matcher(); + combinationMatcher->set_id(StringToId("SyncOrWakelockMatcher")); + combinationMatcher->mutable_combination()->set_operation(LogicalOperation::OR); + addMatcherToMatcherCombination(syncStartMatcher, combinationMatcher); + addMatcherToMatcherCombination(wakelockAcquireMatcher, combinationMatcher); + + Predicate screenOnPredicate = CreateScreenIsOnPredicate(); + *config.add_predicate() = screenOnPredicate; + Predicate unpluggedPredicate = CreateDeviceUnpluggedPredicate(); + *config.add_predicate() = unpluggedPredicate; + + Predicate* combinationPredicate = config.add_predicate(); + combinationPredicate->set_id(StringToId("ScreenOnOrUnpluggedPred)")); + combinationPredicate->mutable_combination()->set_operation(LogicalOperation::OR); + addPredicateToPredicateCombination(screenOnPredicate, combinationPredicate); + addPredicateToPredicateCombination(unpluggedPredicate, combinationPredicate); + + EventMetric eventPersist = + createEventMetric("SyncOrWlWhileScreenOnOrUnplugged", combinationMatcher->id(), + combinationPredicate->id()); + EventMetric eventChange = createEventMetric( + "WakelockWhileScreenOn", wakelockAcquireMatcher.id(), screenOnPredicate.id()); + EventMetric eventRemove = createEventMetric("Syncs", syncStartMatcher.id(), nullopt); + + *config.add_event_metric() = eventRemove; + *config.add_event_metric() = eventPersist; + *config.add_event_metric() = eventChange; + + ConfigKey key(123, 987); + uint64_t bucketStartTimeNs = 10000000000; // 0:10 + sp processor = + CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, key); + + int app1Uid = 123; + vector attributionUids1 = {app1Uid}; + vector attributionTags1 = {"App1"}; + + // Initialize log events before update. + std::vector> events; + events.push_back(CreateAcquireWakelockEvent(bucketStartTimeNs + 5 * NS_PER_SEC, + attributionUids1, attributionTags1, + "wl1")); // Not kept. + events.push_back(CreateScreenStateChangedEvent( + bucketStartTimeNs + 10 * NS_PER_SEC, + android::view::DISPLAY_STATE_ON)); // Condition true for change. + events.push_back(CreateSyncStartEvent(bucketStartTimeNs + 15 * NS_PER_SEC, attributionUids1, + attributionTags1, + "sync1")); // Kept for persist & remove. + events.push_back(CreateBatteryStateChangedEvent( + bucketStartTimeNs + 20 * NS_PER_SEC, + BatteryPluggedStateEnum::BATTERY_PLUGGED_NONE)); // Condition true for preserve. + events.push_back(CreateAcquireWakelockEvent(bucketStartTimeNs + 25 * NS_PER_SEC, + attributionUids1, attributionTags1, + "wl2")); // Kept by persist and change. + events.push_back(CreateScreenStateChangedEvent( + bucketStartTimeNs + 30 * NS_PER_SEC, + android::view::DISPLAY_STATE_OFF)); // Condition false for change. + events.push_back(CreateSyncStartEvent(bucketStartTimeNs + 35 * NS_PER_SEC, attributionUids1, + attributionTags1, + "sync2")); // Kept for persist & remove. + events.push_back(CreateAcquireWakelockEvent(bucketStartTimeNs + 40 * NS_PER_SEC, + attributionUids1, attributionTags1, + "wl3")); // Kept by persist. + + // Send log events to StatsLogProcessor. + for (auto& event : events) { + processor->OnLogEvent(event.get()); + } + + // Do update. Add matchers/conditions in different order to force indices to change. + StatsdConfig newConfig; + newConfig.add_allowed_log_source("AID_ROOT"); + + *newConfig.add_atom_matcher() = screenOnMatcher; + *newConfig.add_atom_matcher() = batteryPluggedUsbMatcher; + *newConfig.add_atom_matcher() = syncStartMatcher; + *newConfig.add_atom_matcher() = *combinationMatcher; + *newConfig.add_atom_matcher() = wakelockAcquireMatcher; + *newConfig.add_atom_matcher() = screenOffMatcher; + *newConfig.add_atom_matcher() = unpluggedMatcher; + *newConfig.add_predicate() = *combinationPredicate; + *newConfig.add_predicate() = unpluggedPredicate; + *newConfig.add_predicate() = screenOnPredicate; + + // Add metrics. Note that the condition of eventChange will go from false to true. + eventChange.set_condition(unpluggedPredicate.id()); + *newConfig.add_event_metric() = eventChange; + EventMetric eventNew = createEventMetric("ScreenOn", screenOnMatcher.id(), nullopt); + *newConfig.add_event_metric() = eventNew; + *newConfig.add_event_metric() = eventPersist; + + int64_t updateTimeNs = bucketStartTimeNs + 60 * NS_PER_SEC; + processor->OnConfigUpdated(updateTimeNs, key, newConfig); + + // Send events after the update. + events.clear(); + events.push_back(CreateAcquireWakelockEvent(bucketStartTimeNs + 65 * NS_PER_SEC, + attributionUids1, attributionTags1, + "wl4")); // Kept by preserve & change. + events.push_back(CreateBatteryStateChangedEvent( + bucketStartTimeNs + 70 * NS_PER_SEC, + BatteryPluggedStateEnum::BATTERY_PLUGGED_USB)); // All conditions are false. + events.push_back(CreateAcquireWakelockEvent(bucketStartTimeNs + 75 * NS_PER_SEC, + attributionUids1, attributionTags1, + "wl5")); // Not kept. + events.push_back(CreateScreenStateChangedEvent( + bucketStartTimeNs + 80 * NS_PER_SEC, + android::view::DISPLAY_STATE_ON)); // Condition true for preserve, event kept by new. + events.push_back(CreateAcquireWakelockEvent(bucketStartTimeNs + 85 * NS_PER_SEC, + attributionUids1, attributionTags1, + "wl6")); // Kept by preserve. + events.push_back(CreateSyncStartEvent(bucketStartTimeNs + 90 * NS_PER_SEC, attributionUids1, + attributionTags1, "sync3")); // Kept by preserve. + + // Send log events to StatsLogProcessor. + for (auto& event : events) { + processor->OnLogEvent(event.get()); + } + uint64_t dumpTimeNs = bucketStartTimeNs + 100 * NS_PER_SEC; + ConfigMetricsReportList reports; + vector buffer; + processor->onDumpReport(key, dumpTimeNs, true, true, ADB_DUMP, FAST, &buffer); + EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size())); + backfillDimensionPath(&reports); + backfillStringInReport(&reports); + backfillStartEndTimestamp(&reports); + ASSERT_EQ(reports.reports_size(), 2); + + // Report from before update. + ConfigMetricsReport report = reports.reports(0); + ASSERT_EQ(report.metrics_size(), 3); + // Event remove. Captured sync events. There were 2 syncs before the update. + StatsLogReport eventRemoveBefore = report.metrics(0); + EXPECT_EQ(eventRemoveBefore.metric_id(), eventRemove.id()); + EXPECT_TRUE(eventRemoveBefore.has_event_metrics()); + ASSERT_EQ(eventRemoveBefore.event_metrics().data_size(), 2); + auto data = eventRemoveBefore.event_metrics().data(0); + EXPECT_EQ(data.elapsed_timestamp_nanos(), bucketStartTimeNs + 15 * NS_PER_SEC); + EXPECT_EQ(data.atom().sync_state_changed().sync_name(), "sync1"); + data = eventRemoveBefore.event_metrics().data(1); + EXPECT_EQ(data.elapsed_timestamp_nanos(), bucketStartTimeNs + 35 * NS_PER_SEC); + EXPECT_EQ(data.atom().sync_state_changed().sync_name(), "sync2"); + + // Captured wakelocks & syncs while screen on or unplugged. There were 2 wakelocks and 2 syncs. + StatsLogReport eventPersistBefore = report.metrics(1); + EXPECT_EQ(eventPersistBefore.metric_id(), eventPersist.id()); + EXPECT_TRUE(eventPersistBefore.has_event_metrics()); + ASSERT_EQ(eventPersistBefore.event_metrics().data_size(), 3); + data = eventPersistBefore.event_metrics().data(0); + EXPECT_EQ(data.elapsed_timestamp_nanos(), bucketStartTimeNs + 25 * NS_PER_SEC); + EXPECT_EQ(data.atom().wakelock_state_changed().tag(), "wl2"); + data = eventPersistBefore.event_metrics().data(1); + EXPECT_EQ(data.elapsed_timestamp_nanos(), bucketStartTimeNs + 35 * NS_PER_SEC); + EXPECT_EQ(data.atom().sync_state_changed().sync_name(), "sync2"); + data = eventPersistBefore.event_metrics().data(2); + EXPECT_EQ(data.elapsed_timestamp_nanos(), bucketStartTimeNs + 40 * NS_PER_SEC); + EXPECT_EQ(data.atom().wakelock_state_changed().tag(), "wl3"); + + // Captured wakelock events while screen on. There was 1 before the update. + StatsLogReport eventChangeBefore = report.metrics(2); + EXPECT_EQ(eventChangeBefore.metric_id(), eventChange.id()); + EXPECT_TRUE(eventChangeBefore.has_event_metrics()); + ASSERT_EQ(eventChangeBefore.event_metrics().data_size(), 1); + data = eventChangeBefore.event_metrics().data(0); + EXPECT_EQ(data.elapsed_timestamp_nanos(), bucketStartTimeNs + 25 * NS_PER_SEC); + EXPECT_EQ(data.atom().wakelock_state_changed().tag(), "wl2"); + + // Report from after update. + report = reports.reports(1); + ASSERT_EQ(report.metrics_size(), 3); + // Captured wakelocks while unplugged. There was 1 after the update. + StatsLogReport eventChangeAfter = report.metrics(0); + EXPECT_EQ(eventChangeAfter.metric_id(), eventChange.id()); + EXPECT_TRUE(eventChangeAfter.has_event_metrics()); + ASSERT_EQ(eventChangeAfter.event_metrics().data_size(), 1); + data = eventChangeAfter.event_metrics().data(0); + EXPECT_EQ(data.elapsed_timestamp_nanos(), bucketStartTimeNs + 65 * NS_PER_SEC); + EXPECT_EQ(data.atom().wakelock_state_changed().tag(), "wl4"); + + // Captured screen on events. There was 1 after the update. + StatsLogReport eventNewAfter = report.metrics(1); + EXPECT_EQ(eventNewAfter.metric_id(), eventNew.id()); + EXPECT_TRUE(eventNewAfter.has_event_metrics()); + ASSERT_EQ(eventNewAfter.event_metrics().data_size(), 1); + data = eventNewAfter.event_metrics().data(0); + EXPECT_EQ(data.elapsed_timestamp_nanos(), bucketStartTimeNs + 80 * NS_PER_SEC); + EXPECT_EQ(data.atom().screen_state_changed().state(), android::view::DISPLAY_STATE_ON); + + // There were 2 wakelocks and 1 sync after the update while the condition was true. + StatsLogReport eventPersistAfter = report.metrics(2); + EXPECT_EQ(eventPersistAfter.metric_id(), eventPersist.id()); + EXPECT_TRUE(eventPersistAfter.has_event_metrics()); + ASSERT_EQ(eventPersistAfter.event_metrics().data_size(), 3); + data = eventPersistAfter.event_metrics().data(0); + EXPECT_EQ(data.elapsed_timestamp_nanos(), bucketStartTimeNs + 65 * NS_PER_SEC); + EXPECT_EQ(data.atom().wakelock_state_changed().tag(), "wl4"); + data = eventPersistAfter.event_metrics().data(1); + EXPECT_EQ(data.elapsed_timestamp_nanos(), bucketStartTimeNs + 85 * NS_PER_SEC); + EXPECT_EQ(data.atom().wakelock_state_changed().tag(), "wl6"); + data = eventPersistAfter.event_metrics().data(2); + EXPECT_EQ(data.elapsed_timestamp_nanos(), bucketStartTimeNs + 90 * NS_PER_SEC); + EXPECT_EQ(data.atom().sync_state_changed().sync_name(), "sync3"); +} + +TEST_F(ConfigUpdateE2eTest, TestCountMetric) { + StatsdConfig config; + config.add_allowed_log_source("AID_ROOT"); + + AtomMatcher syncStartMatcher = CreateSyncStartAtomMatcher(); + *config.add_atom_matcher() = syncStartMatcher; + AtomMatcher wakelockAcquireMatcher = CreateAcquireWakelockAtomMatcher(); + *config.add_atom_matcher() = wakelockAcquireMatcher; + AtomMatcher wakelockReleaseMatcher = CreateReleaseWakelockAtomMatcher(); + *config.add_atom_matcher() = wakelockReleaseMatcher; + AtomMatcher screenOnMatcher = CreateScreenTurnedOnAtomMatcher(); + *config.add_atom_matcher() = screenOnMatcher; + AtomMatcher screenOffMatcher = CreateScreenTurnedOffAtomMatcher(); + *config.add_atom_matcher() = screenOffMatcher; + + Predicate holdingWakelockPredicate = CreateHoldingWakelockPredicate(); + // The predicate is dimensioning by first attribution node by uid. + *holdingWakelockPredicate.mutable_simple_predicate()->mutable_dimensions() = + CreateAttributionUidDimensions(util::WAKELOCK_STATE_CHANGED, {Position::FIRST}); + *config.add_predicate() = holdingWakelockPredicate; + + Predicate screenOnPredicate = CreateScreenIsOnPredicate(); + *config.add_predicate() = screenOnPredicate; + + Predicate* combination = config.add_predicate(); + combination->set_id(StringToId("ScreenOnAndHoldingWL)")); + combination->mutable_combination()->set_operation(LogicalOperation::AND); + addPredicateToPredicateCombination(screenOnPredicate, combination); + addPredicateToPredicateCombination(holdingWakelockPredicate, combination); + + State uidProcessState = CreateUidProcessState(); + *config.add_state() = uidProcessState; + + CountMetric countPersist = + createCountMetric("CountSyncPerUidWhileScreenOnHoldingWLSliceProcessState", + syncStartMatcher.id(), combination->id(), {uidProcessState.id()}); + *countPersist.mutable_dimensions_in_what() = + CreateAttributionUidDimensions(util::SYNC_STATE_CHANGED, {Position::FIRST}); + // Links between sync state atom and condition of uid is holding wakelock. + MetricConditionLink* links = countPersist.add_links(); + links->set_condition(holdingWakelockPredicate.id()); + *links->mutable_fields_in_what() = + CreateAttributionUidDimensions(util::SYNC_STATE_CHANGED, {Position::FIRST}); + *links->mutable_fields_in_condition() = + CreateAttributionUidDimensions(util::WAKELOCK_STATE_CHANGED, {Position::FIRST}); + MetricStateLink* stateLink = countPersist.add_state_link(); + stateLink->set_state_atom_id(util::UID_PROCESS_STATE_CHANGED); + *stateLink->mutable_fields_in_what() = + CreateAttributionUidDimensions(util::SYNC_STATE_CHANGED, {Position::FIRST}); + *stateLink->mutable_fields_in_state() = + CreateDimensions(util::UID_PROCESS_STATE_CHANGED, {1 /*uid*/}); + + CountMetric countChange = createCountMetric("Count*WhileScreenOn", syncStartMatcher.id(), + screenOnPredicate.id(), {}); + CountMetric countRemove = createCountMetric("CountSync", syncStartMatcher.id(), nullopt, {}); + *config.add_count_metric() = countRemove; + *config.add_count_metric() = countPersist; + *config.add_count_metric() = countChange; + + ConfigKey key(123, 987); + uint64_t bucketStartTimeNs = 10000000000; // 0:10 + uint64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(TEN_MINUTES) * 1000000LL; + sp processor = + CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, key); + + int app1Uid = 123, app2Uid = 456; + vector attributionUids1 = {app1Uid}; + vector attributionTags1 = {"App1"}; + vector attributionUids2 = {app2Uid}; + vector attributionTags2 = {"App2"}; + + // Initialize log events before update. Counts are for countPersist since others are simpler. + std::vector> events; + events.push_back(CreateUidProcessStateChangedEvent( + bucketStartTimeNs + 2 * NS_PER_SEC, app1Uid, + android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_BACKGROUND)); + events.push_back(CreateUidProcessStateChangedEvent( + bucketStartTimeNs + 3 * NS_PER_SEC, app2Uid, + android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_FOREGROUND)); + events.push_back(CreateSyncStartEvent(bucketStartTimeNs + 5 * NS_PER_SEC, attributionUids1, + attributionTags1, "sync_name")); // Not counted. + events.push_back( + CreateScreenStateChangedEvent(bucketStartTimeNs + 10 * NS_PER_SEC, + android::view::DisplayStateEnum::DISPLAY_STATE_ON)); + events.push_back(CreateAcquireWakelockEvent(bucketStartTimeNs + 15 * NS_PER_SEC, + attributionUids1, attributionTags1, "wl1")); + events.push_back(CreateSyncStartEvent(bucketStartTimeNs + 20 * NS_PER_SEC, attributionUids1, + attributionTags1, "sync_name")); // Counted. uid1 = 1. + events.push_back(CreateSyncStartEvent(bucketStartTimeNs + 21 * NS_PER_SEC, attributionUids2, + attributionTags2, "sync_name")); // Not counted. + events.push_back(CreateAcquireWakelockEvent(bucketStartTimeNs + 25 * NS_PER_SEC, + attributionUids2, attributionTags2, "wl2")); + events.push_back(CreateSyncStartEvent(bucketStartTimeNs + 30 * NS_PER_SEC, attributionUids1, + attributionTags1, "sync_name")); // Counted. uid1 = 2. + events.push_back(CreateSyncStartEvent(bucketStartTimeNs + 31 * NS_PER_SEC, attributionUids2, + attributionTags2, "sync_name")); // Counted. uid2 = 1 + events.push_back(CreateReleaseWakelockEvent(bucketStartTimeNs + 35 * NS_PER_SEC, + attributionUids1, attributionTags1, "wl1")); + // Send log events to StatsLogProcessor. + for (auto& event : events) { + processor->OnLogEvent(event.get()); + } + + // Do update. Add matchers/conditions in different order to force indices to change. + StatsdConfig newConfig; + newConfig.add_allowed_log_source("AID_ROOT"); + + *newConfig.add_atom_matcher() = screenOnMatcher; + *newConfig.add_atom_matcher() = screenOffMatcher; + *newConfig.add_atom_matcher() = syncStartMatcher; + *newConfig.add_atom_matcher() = wakelockAcquireMatcher; + *newConfig.add_atom_matcher() = wakelockReleaseMatcher; + *newConfig.add_predicate() = *combination; + *newConfig.add_predicate() = holdingWakelockPredicate; + *newConfig.add_predicate() = screenOnPredicate; + *newConfig.add_state() = uidProcessState; + + countChange.set_what(screenOnMatcher.id()); + *newConfig.add_count_metric() = countChange; + CountMetric countNew = createCountMetric("CountWlWhileScreenOn", wakelockAcquireMatcher.id(), + screenOnPredicate.id(), {}); + *newConfig.add_count_metric() = countNew; + *newConfig.add_count_metric() = countPersist; + + int64_t updateTimeNs = bucketStartTimeNs + 60 * NS_PER_SEC; + processor->OnConfigUpdated(updateTimeNs, key, newConfig); + + // Send events after the update. Counts reset to 0 since this is a new bucket. + events.clear(); + events.push_back(CreateSyncStartEvent(bucketStartTimeNs + 65 * NS_PER_SEC, attributionUids1, + attributionTags1, "sync_name")); // Not counted. + events.push_back(CreateSyncStartEvent(bucketStartTimeNs + 66 * NS_PER_SEC, attributionUids2, + attributionTags2, "sync_name")); // Counted. uid2 = 1. + events.push_back(CreateAcquireWakelockEvent(bucketStartTimeNs + 70 * NS_PER_SEC, + attributionUids1, attributionTags1, "wl1")); + events.push_back( + CreateScreenStateChangedEvent(bucketStartTimeNs + 75 * NS_PER_SEC, + android::view::DisplayStateEnum::DISPLAY_STATE_OFF)); + events.push_back(CreateSyncStartEvent(bucketStartTimeNs + 80 * NS_PER_SEC, attributionUids2, + attributionTags2, "sync_name")); // Not counted. + events.push_back( + CreateScreenStateChangedEvent(bucketStartTimeNs + 85 * NS_PER_SEC, + android::view::DisplayStateEnum::DISPLAY_STATE_ON)); + events.push_back(CreateSyncStartEvent(bucketStartTimeNs + 90 * NS_PER_SEC, attributionUids1, + attributionTags1, "sync_name")); // Counted. uid1 = 1. + events.push_back(CreateSyncStartEvent(bucketStartTimeNs + 11 * NS_PER_SEC, attributionUids2, + attributionTags2, "sync_name")); // Counted. uid2 = 2. + // Flushes bucket. + events.push_back(CreateAcquireWakelockEvent(bucketStartTimeNs + bucketSizeNs + NS_PER_SEC, + attributionUids1, attributionTags1, "wl2")); + // Send log events to StatsLogProcessor. + for (auto& event : events) { + processor->OnLogEvent(event.get()); + } + uint64_t dumpTimeNs = bucketStartTimeNs + bucketSizeNs + 10 * NS_PER_SEC; + ConfigMetricsReportList reports; + vector buffer; + processor->onDumpReport(key, dumpTimeNs, true, true, ADB_DUMP, FAST, &buffer); + EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size())); + backfillDimensionPath(&reports); + backfillStringInReport(&reports); + backfillStartEndTimestamp(&reports); + ASSERT_EQ(reports.reports_size(), 2); + + // Report from before update. + ConfigMetricsReport report = reports.reports(0); + ASSERT_EQ(report.metrics_size(), 3); + // Count syncs. There were 5 syncs before the update. + StatsLogReport countRemoveBefore = report.metrics(0); + EXPECT_EQ(countRemoveBefore.metric_id(), countRemove.id()); + EXPECT_TRUE(countRemoveBefore.has_count_metrics()); + ASSERT_EQ(countRemoveBefore.count_metrics().data_size(), 1); + auto data = countRemoveBefore.count_metrics().data(0); + ASSERT_EQ(data.bucket_info_size(), 1); + ValidateCountBucket(data.bucket_info(0), bucketStartTimeNs, updateTimeNs, 5); + + // Uid 1 had 2 syncs, uid 2 had 1 sync. + StatsLogReport countPersistBefore = report.metrics(1); + EXPECT_EQ(countPersistBefore.metric_id(), countPersist.id()); + EXPECT_TRUE(countPersistBefore.has_count_metrics()); + StatsLogReport::CountMetricDataWrapper countMetrics; + sortMetricDataByDimensionsValue(countPersistBefore.count_metrics(), &countMetrics); + ASSERT_EQ(countMetrics.data_size(), 2); + data = countMetrics.data(0); + ValidateAttributionUidDimension(data.dimensions_in_what(), util::SYNC_STATE_CHANGED, app1Uid); + ValidateStateValue(data.slice_by_state(), util::UID_PROCESS_STATE_CHANGED, + android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_BACKGROUND); + ASSERT_EQ(data.bucket_info_size(), 1); + ValidateCountBucket(data.bucket_info(0), bucketStartTimeNs, updateTimeNs, 2); + + data = countMetrics.data(1); + ValidateAttributionUidDimension(data.dimensions_in_what(), util::SYNC_STATE_CHANGED, app2Uid); + ValidateStateValue(data.slice_by_state(), util::UID_PROCESS_STATE_CHANGED, + android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_FOREGROUND); + ASSERT_EQ(data.bucket_info_size(), 1); + ValidateCountBucket(data.bucket_info(0), bucketStartTimeNs, updateTimeNs, 1); + + // Counts syncs while screen on. There were 4 before the update. + StatsLogReport countChangeBefore = report.metrics(2); + EXPECT_EQ(countChangeBefore.metric_id(), countChange.id()); + EXPECT_TRUE(countChangeBefore.has_count_metrics()); + ASSERT_EQ(countChangeBefore.count_metrics().data_size(), 1); + data = countChangeBefore.count_metrics().data(0); + ASSERT_EQ(data.bucket_info_size(), 1); + ValidateCountBucket(data.bucket_info(0), bucketStartTimeNs, updateTimeNs, 4); + + // Report from after update. + report = reports.reports(1); + ASSERT_EQ(report.metrics_size(), 3); + // Count screen on while screen is on. There was 1 after the update. + StatsLogReport countChangeAfter = report.metrics(0); + EXPECT_EQ(countChangeAfter.metric_id(), countChange.id()); + EXPECT_TRUE(countChangeAfter.has_count_metrics()); + ASSERT_EQ(countChangeAfter.count_metrics().data_size(), 1); + data = countChangeAfter.count_metrics().data(0); + ASSERT_EQ(data.bucket_info_size(), 1); + ValidateCountBucket(data.bucket_info(0), updateTimeNs, bucketStartTimeNs + bucketSizeNs, 1); + + // Count wl acquires while screen on. There were 2, one in each bucket. + StatsLogReport countNewAfter = report.metrics(1); + EXPECT_EQ(countNewAfter.metric_id(), countNew.id()); + EXPECT_TRUE(countNewAfter.has_count_metrics()); + ASSERT_EQ(countNewAfter.count_metrics().data_size(), 1); + data = countNewAfter.count_metrics().data(0); + ASSERT_EQ(data.bucket_info_size(), 2); + ValidateCountBucket(data.bucket_info(0), updateTimeNs, bucketStartTimeNs + bucketSizeNs, 1); + ValidateCountBucket(data.bucket_info(1), bucketStartTimeNs + bucketSizeNs, dumpTimeNs, 1); + + // Uid 1 had 1 sync, uid 2 had 2 syncs. + StatsLogReport countPersistAfter = report.metrics(2); + EXPECT_EQ(countPersistAfter.metric_id(), countPersist.id()); + EXPECT_TRUE(countPersistAfter.has_count_metrics()); + countMetrics.Clear(); + sortMetricDataByDimensionsValue(countPersistAfter.count_metrics(), &countMetrics); + ASSERT_EQ(countMetrics.data_size(), 2); + data = countMetrics.data(0); + ValidateAttributionUidDimension(data.dimensions_in_what(), util::SYNC_STATE_CHANGED, app1Uid); + ValidateStateValue(data.slice_by_state(), util::UID_PROCESS_STATE_CHANGED, + android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_BACKGROUND); + ASSERT_EQ(data.bucket_info_size(), 1); + ValidateCountBucket(data.bucket_info(0), updateTimeNs, bucketStartTimeNs + bucketSizeNs, 1); + + data = countMetrics.data(1); + ValidateAttributionUidDimension(data.dimensions_in_what(), util::SYNC_STATE_CHANGED, app2Uid); + ValidateStateValue(data.slice_by_state(), util::UID_PROCESS_STATE_CHANGED, + android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_FOREGROUND); + ASSERT_EQ(data.bucket_info_size(), 1); + ValidateCountBucket(data.bucket_info(0), updateTimeNs, bucketStartTimeNs + bucketSizeNs, 2); +} + +TEST_F(ConfigUpdateE2eTest, TestDurationMetric) { + StatsdConfig config; + config.add_allowed_log_source("AID_ROOT"); + + AtomMatcher syncStartMatcher = CreateSyncStartAtomMatcher(); + *config.add_atom_matcher() = syncStartMatcher; + AtomMatcher syncStopMatcher = CreateSyncEndAtomMatcher(); + *config.add_atom_matcher() = syncStopMatcher; + AtomMatcher wakelockAcquireMatcher = CreateAcquireWakelockAtomMatcher(); + *config.add_atom_matcher() = wakelockAcquireMatcher; + AtomMatcher wakelockReleaseMatcher = CreateReleaseWakelockAtomMatcher(); + *config.add_atom_matcher() = wakelockReleaseMatcher; + AtomMatcher screenOnMatcher = CreateScreenTurnedOnAtomMatcher(); + *config.add_atom_matcher() = screenOnMatcher; + AtomMatcher screenOffMatcher = CreateScreenTurnedOffAtomMatcher(); + *config.add_atom_matcher() = screenOffMatcher; + + Predicate holdingWakelockPredicate = CreateHoldingWakelockPredicate(); + // The predicate is dimensioning by first attribution node by uid. + *holdingWakelockPredicate.mutable_simple_predicate()->mutable_dimensions() = + CreateAttributionUidDimensions(util::WAKELOCK_STATE_CHANGED, {Position::FIRST}); + *config.add_predicate() = holdingWakelockPredicate; + + Predicate screenOnPredicate = CreateScreenIsOnPredicate(); + *config.add_predicate() = screenOnPredicate; + + Predicate syncPredicate = CreateIsSyncingPredicate(); + // The predicate is dimensioning by first attribution node by uid. + *syncPredicate.mutable_simple_predicate()->mutable_dimensions() = + CreateAttributionUidDimensions(util::SYNC_STATE_CHANGED, {Position::FIRST}); + *config.add_predicate() = syncPredicate; + + State uidProcessState = CreateUidProcessState(); + *config.add_state() = uidProcessState; + + DurationMetric durationSumPersist = + createDurationMetric("DurSyncPerUidWhileHoldingWLSliceProcessState", syncPredicate.id(), + holdingWakelockPredicate.id(), {uidProcessState.id()}); + *durationSumPersist.mutable_dimensions_in_what() = + CreateAttributionUidDimensions(util::SYNC_STATE_CHANGED, {Position::FIRST}); + // Links between sync state atom and condition of uid is holding wakelock. + MetricConditionLink* links = durationSumPersist.add_links(); + links->set_condition(holdingWakelockPredicate.id()); + *links->mutable_fields_in_what() = + CreateAttributionUidDimensions(util::SYNC_STATE_CHANGED, {Position::FIRST}); + *links->mutable_fields_in_condition() = + CreateAttributionUidDimensions(util::WAKELOCK_STATE_CHANGED, {Position::FIRST}); + MetricStateLink* stateLink = durationSumPersist.add_state_link(); + stateLink->set_state_atom_id(util::UID_PROCESS_STATE_CHANGED); + *stateLink->mutable_fields_in_what() = + CreateAttributionUidDimensions(util::SYNC_STATE_CHANGED, {Position::FIRST}); + *stateLink->mutable_fields_in_state() = + CreateDimensions(util::UID_PROCESS_STATE_CHANGED, {1 /*uid*/}); + + DurationMetric durationMaxPersist = + createDurationMetric("DurMaxSyncPerUidWhileHoldingWL", syncPredicate.id(), + holdingWakelockPredicate.id(), {}); + durationMaxPersist.set_aggregation_type(DurationMetric::MAX_SPARSE); + *durationMaxPersist.mutable_dimensions_in_what() = + CreateAttributionUidDimensions(util::SYNC_STATE_CHANGED, {Position::FIRST}); + // Links between sync state atom and condition of uid is holding wakelock. + links = durationMaxPersist.add_links(); + links->set_condition(holdingWakelockPredicate.id()); + *links->mutable_fields_in_what() = + CreateAttributionUidDimensions(util::SYNC_STATE_CHANGED, {Position::FIRST}); + *links->mutable_fields_in_condition() = + CreateAttributionUidDimensions(util::WAKELOCK_STATE_CHANGED, {Position::FIRST}); + + DurationMetric durationChange = createDurationMetric("Dur*WhileScreenOn", syncPredicate.id(), + screenOnPredicate.id(), {}); + DurationMetric durationRemove = + createDurationMetric("DurScreenOn", screenOnPredicate.id(), nullopt, {}); + *config.add_duration_metric() = durationMaxPersist; + *config.add_duration_metric() = durationRemove; + *config.add_duration_metric() = durationSumPersist; + *config.add_duration_metric() = durationChange; + + ConfigKey key(123, 987); + uint64_t bucketStartTimeNs = 10000000000; // 0:10 + uint64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(TEN_MINUTES) * 1000000LL; + sp processor = + CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, key); + + int app1Uid = 123, app2Uid = 456, app3Uid = 789; + vector attributionUids1 = {app1Uid}; + vector attributionTags1 = {"App1"}; + vector attributionUids2 = {app2Uid}; + vector attributionTags2 = {"App2"}; + vector attributionUids3 = {app3Uid}; + vector attributionTags3 = {"App3"}; + + // Initialize log events before update. Comments provided for durations of persisted metrics. + std::vector> events; + events.push_back(CreateUidProcessStateChangedEvent( + bucketStartTimeNs + 2 * NS_PER_SEC, app1Uid, + android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_BACKGROUND)); + events.push_back(CreateUidProcessStateChangedEvent( + bucketStartTimeNs + 3 * NS_PER_SEC, app2Uid, + android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_FOREGROUND)); + events.push_back(CreateScreenStateChangedEvent( + bucketStartTimeNs + 5 * NS_PER_SEC, android::view::DisplayStateEnum::DISPLAY_STATE_ON)); + events.push_back(CreateUidProcessStateChangedEvent( + bucketStartTimeNs + 6 * NS_PER_SEC, app3Uid, + android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_BACKGROUND)); + events.push_back(CreateSyncStartEvent(bucketStartTimeNs + 10 * NS_PER_SEC, attributionUids1, + attributionTags1, "sync_name")); // uid1 paused. + events.push_back(CreateAcquireWakelockEvent(bucketStartTimeNs + 15 * NS_PER_SEC, + attributionUids2, attributionTags2, + "wl2")); // uid2 cond true. + events.push_back( + CreateScreenStateChangedEvent(bucketStartTimeNs + 20 * NS_PER_SEC, + android::view::DisplayStateEnum::DISPLAY_STATE_OFF)); + events.push_back(CreateSyncStartEvent(bucketStartTimeNs + 25 * NS_PER_SEC, attributionUids2, + attributionTags2, "sync_name")); // Uid 2 start t=25. + events.push_back(CreateAcquireWakelockEvent(bucketStartTimeNs + 27 * NS_PER_SEC, + attributionUids1, attributionTags1, + "wl1")); // Uid 1 start t=27. + events.push_back(CreateSyncStartEvent(bucketStartTimeNs + 35 * NS_PER_SEC, attributionUids3, + attributionTags3, "sync_name")); // Uid 3 paused. + events.push_back( + CreateScreenStateChangedEvent(bucketStartTimeNs + 40 * NS_PER_SEC, + android::view::DisplayStateEnum::DISPLAY_STATE_ON)); + events.push_back(CreateSyncEndEvent(bucketStartTimeNs + 45 * NS_PER_SEC, attributionUids2, + attributionTags2, + "sync_name")); // Uid 2 stop. sum/max = 20. + // Send log events to StatsLogProcessor. + for (auto& event : events) { + processor->OnLogEvent(event.get()); + } + + // Do update. Add matchers/conditions in different order to force indices to change. + StatsdConfig newConfig; + newConfig.add_allowed_log_source("AID_ROOT"); + + *newConfig.add_atom_matcher() = wakelockReleaseMatcher; + *newConfig.add_atom_matcher() = syncStopMatcher; + *newConfig.add_atom_matcher() = screenOnMatcher; + *newConfig.add_atom_matcher() = screenOffMatcher; + *newConfig.add_atom_matcher() = syncStartMatcher; + *newConfig.add_atom_matcher() = wakelockAcquireMatcher; + *newConfig.add_predicate() = syncPredicate; + *newConfig.add_predicate() = screenOnPredicate; + *newConfig.add_predicate() = holdingWakelockPredicate; + *newConfig.add_state() = uidProcessState; + + durationChange.set_what(holdingWakelockPredicate.id()); + *newConfig.add_duration_metric() = durationChange; + DurationMetric durationNew = + createDurationMetric("DurationSync", syncPredicate.id(), nullopt, {}); + *newConfig.add_duration_metric() = durationNew; + *newConfig.add_duration_metric() = durationMaxPersist; + *newConfig.add_duration_metric() = durationSumPersist; + + // At update, only uid 1 is syncing & holding a wakelock, duration=33. Max is paused for uid3. + // Before the update, only uid2 will report a duration for max, since others are started/paused. + int64_t updateTimeNs = bucketStartTimeNs + 60 * NS_PER_SEC; + processor->OnConfigUpdated(updateTimeNs, key, newConfig); + + // Send events after the update. + events.clear(); + events.push_back(CreateAcquireWakelockEvent(bucketStartTimeNs + 65 * NS_PER_SEC, + attributionUids3, attributionTags3, + "wl3")); // Uid3 start t=65. + events.push_back(CreateSyncStartEvent(bucketStartTimeNs + 70 * NS_PER_SEC, attributionUids2, + attributionTags2, "sync_name")); // Uid2 start t=70. + events.push_back(CreateReleaseWakelockEvent(bucketStartTimeNs + 75 * NS_PER_SEC, + attributionUids1, attributionTags1, + "wl1")); // Uid1 Pause. Dur = 15. + events.push_back( + CreateScreenStateChangedEvent(bucketStartTimeNs + 81 * NS_PER_SEC, + android::view::DisplayStateEnum::DISPLAY_STATE_OFF)); + events.push_back(CreateUidProcessStateChangedEvent( + bucketStartTimeNs + 85 * NS_PER_SEC, app3Uid, + android::app::ProcessStateEnum:: + PROCESS_STATE_IMPORTANT_FOREGROUND)); // Uid3 Sum in BG: 20. FG starts. Max is + // unchanged. + events.push_back(CreateSyncEndEvent(bucketStartTimeNs + 90 * NS_PER_SEC, attributionUids3, + attributionTags3, + "sync_name")); // Uid3 stop. Sum in FG: 5. MAX: 25. + + // Flushes bucket. Sum: uid1=15, uid2=bucketSize - 70, uid3 = 20 in FG, 5 in BG. Max: uid3=25, + // others don't report. + events.push_back(CreateSyncEndEvent(bucketStartTimeNs + bucketSizeNs + NS_PER_SEC, + attributionUids1, attributionTags1, + "sync_name")); // Uid1 stop. Max=15+33=48, Sum is 0. + + // Send log events to StatsLogProcessor. + for (auto& event : events) { + processor->OnLogEvent(event.get()); + } + uint64_t bucketEndTimeNs = bucketStartTimeNs + bucketSizeNs; + uint64_t dumpTimeNs = bucketStartTimeNs + bucketSizeNs + 10 * NS_PER_SEC; + ConfigMetricsReportList reports; + vector buffer; + // In the partial bucket, Sum for uid2 = 10, Max for Uid1 = 48. Rest are unreported. + processor->onDumpReport(key, dumpTimeNs, true, true, ADB_DUMP, FAST, &buffer); + EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size())); + backfillDimensionPath(&reports); + backfillStringInReport(&reports); + backfillStartEndTimestamp(&reports); + ASSERT_EQ(reports.reports_size(), 2); + + // Report from before update. + ConfigMetricsReport report = reports.reports(0); + ASSERT_EQ(report.metrics_size(), 4); + // Max duration of syncs per uid while uid holding WL. Only uid2 should have data. + StatsLogReport durationMaxPersistBefore = report.metrics(0); + EXPECT_EQ(durationMaxPersistBefore.metric_id(), durationMaxPersist.id()); + EXPECT_TRUE(durationMaxPersistBefore.has_duration_metrics()); + StatsLogReport::DurationMetricDataWrapper durationMetrics; + sortMetricDataByDimensionsValue(durationMaxPersistBefore.duration_metrics(), &durationMetrics); + ASSERT_EQ(durationMetrics.data_size(), 1); + auto data = durationMetrics.data(0); + ValidateAttributionUidDimension(data.dimensions_in_what(), util::SYNC_STATE_CHANGED, app2Uid); + ASSERT_EQ(data.bucket_info_size(), 1); + ValidateDurationBucket(data.bucket_info(0), bucketStartTimeNs, updateTimeNs, 20 * NS_PER_SEC); + + // Screen on time. ON: 5, OFF: 20, ON: 40. Update: 60. Result: 35 + StatsLogReport durationRemoveBefore = report.metrics(1); + EXPECT_EQ(durationRemoveBefore.metric_id(), durationRemove.id()); + EXPECT_TRUE(durationRemoveBefore.has_duration_metrics()); + durationMetrics.Clear(); + sortMetricDataByDimensionsValue(durationRemoveBefore.duration_metrics(), &durationMetrics); + ASSERT_EQ(durationMetrics.data_size(), 1); + data = durationMetrics.data(0); + ASSERT_EQ(data.bucket_info_size(), 1); + ValidateDurationBucket(data.bucket_info(0), bucketStartTimeNs, updateTimeNs, 35 * NS_PER_SEC); + + // Duration of syncs per uid while uid holding WL, slice screen. Uid1=33, uid2=20. + StatsLogReport durationSumPersistBefore = report.metrics(2); + EXPECT_EQ(durationSumPersistBefore.metric_id(), durationSumPersist.id()); + EXPECT_TRUE(durationSumPersistBefore.has_duration_metrics()); + durationMetrics.Clear(); + sortMetricDataByDimensionsValue(durationSumPersistBefore.duration_metrics(), &durationMetrics); + ASSERT_EQ(durationMetrics.data_size(), 2); + data = durationMetrics.data(0); + ValidateAttributionUidDimension(data.dimensions_in_what(), util::SYNC_STATE_CHANGED, app1Uid); + ValidateStateValue(data.slice_by_state(), util::UID_PROCESS_STATE_CHANGED, + android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_BACKGROUND); + ASSERT_EQ(data.bucket_info_size(), 1); + ValidateDurationBucket(data.bucket_info(0), bucketStartTimeNs, updateTimeNs, 33 * NS_PER_SEC); + + data = durationMetrics.data(1); + ValidateAttributionUidDimension(data.dimensions_in_what(), util::SYNC_STATE_CHANGED, app2Uid); + ValidateStateValue(data.slice_by_state(), util::UID_PROCESS_STATE_CHANGED, + android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_FOREGROUND); + ASSERT_EQ(data.bucket_info_size(), 1); + ValidateDurationBucket(data.bucket_info(0), bucketStartTimeNs, updateTimeNs, 20 * NS_PER_SEC); + + // Duration of syncs while screen on. Start: 10, pause: 20, start: 40 Update: 60. Total: 30. + StatsLogReport durationChangeBefore = report.metrics(3); + EXPECT_EQ(durationChangeBefore.metric_id(), durationChange.id()); + EXPECT_TRUE(durationChangeBefore.has_duration_metrics()); + durationMetrics.Clear(); + sortMetricDataByDimensionsValue(durationChangeBefore.duration_metrics(), &durationMetrics); + ASSERT_EQ(durationMetrics.data_size(), 1); + data = durationMetrics.data(0); + ASSERT_EQ(data.bucket_info_size(), 1); + ValidateDurationBucket(data.bucket_info(0), bucketStartTimeNs, updateTimeNs, 30 * NS_PER_SEC); + + // Report from after update. + report = reports.reports(1); + ASSERT_EQ(report.metrics_size(), 4); + // Duration of WL while screen on. Update: 60, pause: 81. Total: 21. + StatsLogReport durationChangeAfter = report.metrics(0); + EXPECT_EQ(durationChangeAfter.metric_id(), durationChange.id()); + EXPECT_TRUE(durationChangeAfter.has_duration_metrics()); + durationMetrics.Clear(); + sortMetricDataByDimensionsValue(durationChangeAfter.duration_metrics(), &durationMetrics); + ASSERT_EQ(durationMetrics.data_size(), 1); + data = durationMetrics.data(0); + ASSERT_EQ(data.bucket_info_size(), 1); + ValidateDurationBucket(data.bucket_info(0), updateTimeNs, bucketEndTimeNs, 21 * NS_PER_SEC); + + // Duration of syncs. Always true since at least 1 uid is always syncing. + StatsLogReport durationNewAfter = report.metrics(1); + EXPECT_EQ(durationNewAfter.metric_id(), durationNew.id()); + EXPECT_TRUE(durationNewAfter.has_duration_metrics()); + durationMetrics.Clear(); + sortMetricDataByDimensionsValue(durationNewAfter.duration_metrics(), &durationMetrics); + ASSERT_EQ(durationMetrics.data_size(), 1); + data = durationMetrics.data(0); + ASSERT_EQ(data.bucket_info_size(), 2); + ValidateDurationBucket(data.bucket_info(0), updateTimeNs, bucketEndTimeNs, + bucketEndTimeNs - updateTimeNs); + ValidateDurationBucket(data.bucket_info(1), bucketEndTimeNs, dumpTimeNs, + dumpTimeNs - bucketEndTimeNs); + + // Max duration of syncs per uid while uid holding WL. + StatsLogReport durationMaxPersistAfter = report.metrics(2); + EXPECT_EQ(durationMaxPersistAfter.metric_id(), durationMaxPersist.id()); + EXPECT_TRUE(durationMaxPersistAfter.has_duration_metrics()); + durationMetrics.Clear(); + sortMetricDataByDimensionsValue(durationMaxPersistAfter.duration_metrics(), &durationMetrics); + ASSERT_EQ(durationMetrics.data_size(), 2); + + // Uid 1. Duration = 48 in the later bucket. + data = durationMetrics.data(0); + ValidateAttributionUidDimension(data.dimensions_in_what(), util::SYNC_STATE_CHANGED, app1Uid); + ASSERT_EQ(data.bucket_info_size(), 1); + ValidateDurationBucket(data.bucket_info(0), bucketEndTimeNs, dumpTimeNs, 48 * NS_PER_SEC); + + // Uid 3. Duration = 25. + data = durationMetrics.data(1); + ValidateAttributionUidDimension(data.dimensions_in_what(), util::SYNC_STATE_CHANGED, app3Uid); + ASSERT_EQ(data.bucket_info_size(), 1); + ValidateDurationBucket(data.bucket_info(0), updateTimeNs, bucketEndTimeNs, 25 * NS_PER_SEC); + + // Duration of syncs per uid while uid holding WL, slice screen. + StatsLogReport durationSumPersistAfter = report.metrics(3); + EXPECT_EQ(durationSumPersistAfter.metric_id(), durationSumPersist.id()); + EXPECT_TRUE(durationSumPersistAfter.has_duration_metrics()); + durationMetrics.Clear(); + sortMetricDataByDimensionsValue(durationSumPersistAfter.duration_metrics(), &durationMetrics); + ASSERT_EQ(durationMetrics.data_size(), 4); + + // Uid 1 in BG. Duration = 15. + data = durationMetrics.data(0); + ValidateAttributionUidDimension(data.dimensions_in_what(), util::SYNC_STATE_CHANGED, app1Uid); + ValidateStateValue(data.slice_by_state(), util::UID_PROCESS_STATE_CHANGED, + android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_BACKGROUND); + ASSERT_EQ(data.bucket_info_size(), 1); + ValidateDurationBucket(data.bucket_info(0), updateTimeNs, bucketEndTimeNs, 15 * NS_PER_SEC); + + // Uid 2 in FG. Duration = bucketSize - 70 in first bucket, 10 in second bucket. + data = durationMetrics.data(1); + ValidateAttributionUidDimension(data.dimensions_in_what(), util::SYNC_STATE_CHANGED, app2Uid); + ValidateStateValue(data.slice_by_state(), util::UID_PROCESS_STATE_CHANGED, + android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_FOREGROUND); + ASSERT_EQ(data.bucket_info_size(), 2); + ValidateDurationBucket(data.bucket_info(0), updateTimeNs, bucketEndTimeNs, + bucketSizeNs - 70 * NS_PER_SEC); + ValidateDurationBucket(data.bucket_info(1), bucketEndTimeNs, dumpTimeNs, 10 * NS_PER_SEC); + + // Uid 3 in FG. Duration = 5. + data = durationMetrics.data(2); + ValidateAttributionUidDimension(data.dimensions_in_what(), util::SYNC_STATE_CHANGED, app3Uid); + ValidateStateValue(data.slice_by_state(), util::UID_PROCESS_STATE_CHANGED, + android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_FOREGROUND); + ASSERT_EQ(data.bucket_info_size(), 1); + ValidateDurationBucket(data.bucket_info(0), updateTimeNs, bucketEndTimeNs, 5 * NS_PER_SEC); + + // Uid 3 in BG. Duration = 20. + data = durationMetrics.data(3); + ValidateAttributionUidDimension(data.dimensions_in_what(), util::SYNC_STATE_CHANGED, app3Uid); + ValidateStateValue(data.slice_by_state(), util::UID_PROCESS_STATE_CHANGED, + android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_BACKGROUND); + ASSERT_EQ(data.bucket_info_size(), 1); + ValidateDurationBucket(data.bucket_info(0), updateTimeNs, bucketEndTimeNs, 20 * NS_PER_SEC); +} + +TEST_F(ConfigUpdateE2eTest, TestGaugeMetric) { + StatsdConfig config; + config.add_allowed_log_source("AID_ROOT"); + config.add_default_pull_packages("AID_ROOT"); // Fake puller is registered with root. + + AtomMatcher appStartMatcher = CreateSimpleAtomMatcher("AppStart", util::APP_START_OCCURRED); + *config.add_atom_matcher() = appStartMatcher; + AtomMatcher backgroundMatcher = CreateMoveToBackgroundAtomMatcher(); + *config.add_atom_matcher() = backgroundMatcher; + AtomMatcher foregroundMatcher = CreateMoveToForegroundAtomMatcher(); + *config.add_atom_matcher() = foregroundMatcher; + AtomMatcher screenOnMatcher = CreateScreenTurnedOnAtomMatcher(); + *config.add_atom_matcher() = screenOnMatcher; + AtomMatcher screenOffMatcher = CreateScreenTurnedOffAtomMatcher(); + *config.add_atom_matcher() = screenOffMatcher; + AtomMatcher subsystemSleepMatcher = + CreateSimpleAtomMatcher("SubsystemSleep", util::SUBSYSTEM_SLEEP_STATE); + *config.add_atom_matcher() = subsystemSleepMatcher; + + Predicate isInBackgroundPredicate = CreateIsInBackgroundPredicate(); + *isInBackgroundPredicate.mutable_simple_predicate()->mutable_dimensions() = + CreateDimensions(util::ACTIVITY_FOREGROUND_STATE_CHANGED, {1 /*uid field*/}); + *config.add_predicate() = isInBackgroundPredicate; + + Predicate screenOnPredicate = CreateScreenIsOnPredicate(); + *config.add_predicate() = screenOnPredicate; + + GaugeMetric gaugePullPersist = + createGaugeMetric("SubsystemSleepWhileScreenOn", subsystemSleepMatcher.id(), + GaugeMetric::RANDOM_ONE_SAMPLE, screenOnPredicate.id(), {}); + *gaugePullPersist.mutable_dimensions_in_what() = + CreateDimensions(util::SUBSYSTEM_SLEEP_STATE, {1 /* subsystem name */}); + + GaugeMetric gaugePushPersist = + createGaugeMetric("AppStartWhileInBg", appStartMatcher.id(), + GaugeMetric::FIRST_N_SAMPLES, isInBackgroundPredicate.id(), nullopt); + *gaugePushPersist.mutable_dimensions_in_what() = + CreateDimensions(util::APP_START_OCCURRED, {1 /*uid field*/}); + // Links between sync state atom and condition of uid is holding wakelock. + MetricConditionLink* links = gaugePushPersist.add_links(); + links->set_condition(isInBackgroundPredicate.id()); + *links->mutable_fields_in_what() = + CreateDimensions(util::APP_START_OCCURRED, {1 /*uid field*/}); + *links->mutable_fields_in_condition() = + CreateDimensions(util::ACTIVITY_FOREGROUND_STATE_CHANGED, {1 /*uid field*/}); + + GaugeMetric gaugeChange = createGaugeMetric("GaugeScrOn", screenOnMatcher.id(), + GaugeMetric::RANDOM_ONE_SAMPLE, nullopt, nullopt); + GaugeMetric gaugeRemove = + createGaugeMetric("GaugeSubsysTriggerScr", subsystemSleepMatcher.id(), + GaugeMetric::FIRST_N_SAMPLES, nullopt, screenOnMatcher.id()); + *gaugeRemove.mutable_dimensions_in_what() = + CreateDimensions(util::SUBSYSTEM_SLEEP_STATE, {1 /* subsystem name */}); + *config.add_gauge_metric() = gaugeRemove; + *config.add_gauge_metric() = gaugePullPersist; + *config.add_gauge_metric() = gaugeChange; + *config.add_gauge_metric() = gaugePushPersist; + + ConfigKey key(123, 987); + uint64_t bucketStartTimeNs = getElapsedRealtimeNs(); // 0:10 + uint64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(TEN_MINUTES) * 1000000LL; + sp processor = CreateStatsLogProcessor( + bucketStartTimeNs, bucketStartTimeNs, config, key, + SharedRefBase::make(), util::SUBSYSTEM_SLEEP_STATE); + + int app1Uid = 123, app2Uid = 456; + + // Initialize log events before update. + std::vector> events; + events.push_back(CreateMoveToBackgroundEvent(bucketStartTimeNs + 5 * NS_PER_SEC, app1Uid)); + events.push_back(CreateAppStartOccurredEvent(bucketStartTimeNs + 10 * NS_PER_SEC, app1Uid, + "app1", AppStartOccurred::WARM, "", "", true, + /*start_msec*/ 101)); // Kept by gaugePushPersist. + events.push_back( + CreateAppStartOccurredEvent(bucketStartTimeNs + 15 * NS_PER_SEC, app2Uid, "app2", + AppStartOccurred::WARM, "", "", true, + /*start_msec*/ 201)); // Not kept by gaugePushPersist. + events.push_back(CreateScreenStateChangedEvent( + bucketStartTimeNs + 20 * NS_PER_SEC, + android::view::DISPLAY_STATE_ON)); // Pulls gaugePullPersist and gaugeRemove. + events.push_back(CreateMoveToBackgroundEvent(bucketStartTimeNs + 25 * NS_PER_SEC, app2Uid)); + events.push_back( + CreateScreenStateChangedEvent(bucketStartTimeNs + 30 * NS_PER_SEC, + android::view::DisplayStateEnum::DISPLAY_STATE_OFF)); + events.push_back(CreateAppStartOccurredEvent(bucketStartTimeNs + 35 * NS_PER_SEC, app1Uid, + "app1", AppStartOccurred::WARM, "", "", true, + /*start_msec*/ 102)); // Kept by gaugePushPersist. + events.push_back(CreateAppStartOccurredEvent(bucketStartTimeNs + 40 * NS_PER_SEC, app2Uid, + "app2", AppStartOccurred::WARM, "", "", true, + /*start_msec*/ 202)); // Kept by gaugePushPersist. + events.push_back(CreateScreenStateChangedEvent( + bucketStartTimeNs + 45 * NS_PER_SEC, + android::view::DisplayStateEnum::DISPLAY_STATE_ON)); // Pulls gaugeRemove only. + events.push_back(CreateMoveToForegroundEvent(bucketStartTimeNs + 50 * NS_PER_SEC, app1Uid)); + + // Send log events to StatsLogProcessor. + for (auto& event : events) { + processor->mPullerManager->ForceClearPullerCache(); + processor->OnLogEvent(event.get()); + } + processor->mPullerManager->ForceClearPullerCache(); + + // Do the update. Add matchers/conditions in different order to force indices to change. + StatsdConfig newConfig; + newConfig.add_allowed_log_source("AID_ROOT"); + newConfig.add_default_pull_packages("AID_ROOT"); // Fake puller is registered with root. + + *newConfig.add_atom_matcher() = screenOffMatcher; + *newConfig.add_atom_matcher() = foregroundMatcher; + *newConfig.add_atom_matcher() = appStartMatcher; + *newConfig.add_atom_matcher() = subsystemSleepMatcher; + *newConfig.add_atom_matcher() = backgroundMatcher; + *newConfig.add_atom_matcher() = screenOnMatcher; + + *newConfig.add_predicate() = isInBackgroundPredicate; + *newConfig.add_predicate() = screenOnPredicate; + + gaugeChange.set_sampling_type(GaugeMetric::FIRST_N_SAMPLES); + *newConfig.add_gauge_metric() = gaugeChange; + GaugeMetric gaugeNew = createGaugeMetric("GaugeSubsys", subsystemSleepMatcher.id(), + GaugeMetric::RANDOM_ONE_SAMPLE, {}, {}); + *gaugeNew.mutable_dimensions_in_what() = + CreateDimensions(util::SUBSYSTEM_SLEEP_STATE, {1 /* subsystem name */}); + *newConfig.add_gauge_metric() = gaugeNew; + *newConfig.add_gauge_metric() = gaugePushPersist; + *newConfig.add_gauge_metric() = gaugePullPersist; + + int64_t updateTimeNs = bucketStartTimeNs + 60 * NS_PER_SEC; + // Update pulls gaugePullPersist and gaugeNew. + processor->OnConfigUpdated(updateTimeNs, key, newConfig); + + // Verify puller manager is properly set. + sp pullerManager = processor->mPullerManager; + EXPECT_EQ(pullerManager->mNextPullTimeNs, bucketStartTimeNs + bucketSizeNs); + ASSERT_EQ(pullerManager->mReceivers.size(), 1); + ASSERT_EQ(pullerManager->mReceivers.begin()->second.size(), 2); + + // Send events after the update. Counts reset to 0 since this is a new bucket. + events.clear(); + events.push_back( + CreateAppStartOccurredEvent(bucketStartTimeNs + 65 * NS_PER_SEC, app1Uid, "app1", + AppStartOccurred::WARM, "", "", true, + /*start_msec*/ 103)); // Not kept by gaugePushPersist. + events.push_back(CreateAppStartOccurredEvent(bucketStartTimeNs + 70 * NS_PER_SEC, app2Uid, + "app2", AppStartOccurred::WARM, "", "", true, + /*start_msec*/ 203)); // Kept by gaugePushPersist. + events.push_back( + CreateScreenStateChangedEvent(bucketStartTimeNs + 75 * NS_PER_SEC, + android::view::DisplayStateEnum::DISPLAY_STATE_OFF)); + events.push_back( + CreateScreenStateChangedEvent(bucketStartTimeNs + 80 * NS_PER_SEC, + android::view::DisplayStateEnum::DISPLAY_STATE_ON)); + events.push_back(CreateMoveToBackgroundEvent(bucketStartTimeNs + 85 * NS_PER_SEC, app1Uid)); + events.push_back( + CreateScreenStateChangedEvent(bucketStartTimeNs + 90 * NS_PER_SEC, + android::view::DisplayStateEnum::DISPLAY_STATE_OFF)); + events.push_back(CreateAppStartOccurredEvent(bucketStartTimeNs + 95 * NS_PER_SEC, app1Uid, + "app1", AppStartOccurred::WARM, "", "", true, + /*start_msec*/ 104)); // Kept by gaugePushPersist. + events.push_back(CreateAppStartOccurredEvent(bucketStartTimeNs + 100 * NS_PER_SEC, app2Uid, + "app2", AppStartOccurred::WARM, "", "", true, + /*start_msec*/ 204)); // Kept by gaugePushPersist. + events.push_back( + CreateScreenStateChangedEvent(bucketStartTimeNs + 105 * NS_PER_SEC, + android::view::DisplayStateEnum::DISPLAY_STATE_ON)); + events.push_back( + CreateScreenStateChangedEvent(bucketStartTimeNs + 110 * NS_PER_SEC, + android::view::DisplayStateEnum::DISPLAY_STATE_OFF)); + // Send log events to StatsLogProcessor. + for (auto& event : events) { + processor->mPullerManager->ForceClearPullerCache(); + processor->OnLogEvent(event.get()); + } + processor->mPullerManager->ForceClearPullerCache(); + // Pulling alarm arrive, triggering a bucket split. Only gaugeNew keeps the data since the + // condition is false for gaugeNew. + processor->informPullAlarmFired(bucketStartTimeNs + bucketSizeNs); + + uint64_t dumpTimeNs = bucketStartTimeNs + bucketSizeNs + 10 * NS_PER_SEC; + ConfigMetricsReportList reports; + vector buffer; + processor->onDumpReport(key, dumpTimeNs, true, true, ADB_DUMP, FAST, &buffer); + EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size())); + backfillDimensionPath(&reports); + backfillStringInReport(&reports); + backfillStartEndTimestamp(&reports); + ASSERT_EQ(reports.reports_size(), 2); + + int64_t roundedBucketStartNs = MillisToNano(NanoToMillis(bucketStartTimeNs)); + int64_t roundedUpdateTimeNs = MillisToNano(NanoToMillis(updateTimeNs)); + int64_t roundedBucketEndNs = MillisToNano(NanoToMillis(bucketStartTimeNs + bucketSizeNs)); + int64_t roundedDumpTimeNs = MillisToNano(NanoToMillis(dumpTimeNs)); + + // Report from before update. + ConfigMetricsReport report = reports.reports(0); + ASSERT_EQ(report.metrics_size(), 4); + // Gauge subsystem sleep state trigger screen on. 2 pulls occurred. + StatsLogReport gaugeRemoveBefore = report.metrics(0); + EXPECT_EQ(gaugeRemoveBefore.metric_id(), gaugeRemove.id()); + EXPECT_TRUE(gaugeRemoveBefore.has_gauge_metrics()); + StatsLogReport::GaugeMetricDataWrapper gaugeMetrics; + sortMetricDataByDimensionsValue(gaugeRemoveBefore.gauge_metrics(), &gaugeMetrics); + ASSERT_EQ(gaugeMetrics.data_size(), 2); + auto data = gaugeMetrics.data(0); + ValidateSubsystemSleepDimension(data.dimensions_in_what(), "subsystem_name_1"); + ASSERT_EQ(data.bucket_info_size(), 1); + ValidateGaugeBucketTimes(data.bucket_info(0), roundedBucketStartNs, roundedUpdateTimeNs, + {(int64_t)(bucketStartTimeNs + 20 * NS_PER_SEC), + (int64_t)(bucketStartTimeNs + 45 * NS_PER_SEC)}); + ASSERT_EQ(data.bucket_info(0).atom_size(), 2); + EXPECT_EQ(data.bucket_info(0).atom(0).subsystem_sleep_state().time_millis(), 101); + EXPECT_EQ(data.bucket_info(0).atom(1).subsystem_sleep_state().time_millis(), 401); + + data = gaugeMetrics.data(1); + ValidateSubsystemSleepDimension(data.dimensions_in_what(), "subsystem_name_2"); + ASSERT_EQ(data.bucket_info_size(), 1); + ValidateGaugeBucketTimes(data.bucket_info(0), roundedBucketStartNs, roundedUpdateTimeNs, + {(int64_t)(bucketStartTimeNs + 20 * NS_PER_SEC), + (int64_t)(bucketStartTimeNs + 45 * NS_PER_SEC)}); + ASSERT_EQ(data.bucket_info(0).atom_size(), 2); + EXPECT_EQ(data.bucket_info(0).atom(0).subsystem_sleep_state().time_millis(), 102); + EXPECT_EQ(data.bucket_info(0).atom(1).subsystem_sleep_state().time_millis(), 402); + + // Gauge subsystem sleep state when screen is on. One pull when the screen turned on + StatsLogReport gaugePullPersistBefore = report.metrics(1); + EXPECT_EQ(gaugePullPersistBefore.metric_id(), gaugePullPersist.id()); + EXPECT_TRUE(gaugePullPersistBefore.has_gauge_metrics()); + gaugeMetrics.Clear(); + sortMetricDataByDimensionsValue(gaugePullPersistBefore.gauge_metrics(), &gaugeMetrics); + ASSERT_EQ(gaugeMetrics.data_size(), 2); + data = gaugeMetrics.data(0); + ValidateSubsystemSleepDimension(data.dimensions_in_what(), "subsystem_name_1"); + ASSERT_EQ(data.bucket_info_size(), 1); + ValidateGaugeBucketTimes(data.bucket_info(0), roundedBucketStartNs, roundedUpdateTimeNs, + {(int64_t)(bucketStartTimeNs + 20 * NS_PER_SEC)}); + ASSERT_EQ(data.bucket_info(0).atom_size(), 1); + EXPECT_EQ(data.bucket_info(0).atom(0).subsystem_sleep_state().time_millis(), 101); + + data = gaugeMetrics.data(1); + ValidateSubsystemSleepDimension(data.dimensions_in_what(), "subsystem_name_2"); + ASSERT_EQ(data.bucket_info_size(), 1); + ValidateGaugeBucketTimes(data.bucket_info(0), roundedBucketStartNs, roundedUpdateTimeNs, + {(int64_t)(bucketStartTimeNs + 20 * NS_PER_SEC)}); + ASSERT_EQ(data.bucket_info(0).atom_size(), 1); + EXPECT_EQ(data.bucket_info(0).atom(0).subsystem_sleep_state().time_millis(), 102); + + // Gauge screen on events, one per bucket. + StatsLogReport gaugeChangeBefore = report.metrics(2); + EXPECT_EQ(gaugeChangeBefore.metric_id(), gaugeChange.id()); + EXPECT_TRUE(gaugeChangeBefore.has_gauge_metrics()); + gaugeMetrics.Clear(); + sortMetricDataByDimensionsValue(gaugeChangeBefore.gauge_metrics(), &gaugeMetrics); + ASSERT_EQ(gaugeMetrics.data_size(), 1); + data = gaugeMetrics.data(0); + ASSERT_EQ(data.bucket_info_size(), 1); + ValidateGaugeBucketTimes(data.bucket_info(0), roundedBucketStartNs, roundedUpdateTimeNs, + {(int64_t)(bucketStartTimeNs + 20 * NS_PER_SEC)}); + ASSERT_EQ(data.bucket_info(0).atom_size(), 1); + EXPECT_EQ(data.bucket_info(0).atom(0).screen_state_changed().state(), + android::view::DISPLAY_STATE_ON); + + // Gauge app start while app is in the background. App 1 started twice, app 2 started once. + StatsLogReport gaugePushPersistBefore = report.metrics(3); + EXPECT_EQ(gaugePushPersistBefore.metric_id(), gaugePushPersist.id()); + EXPECT_TRUE(gaugePushPersistBefore.has_gauge_metrics()); + gaugeMetrics.Clear(); + sortMetricDataByDimensionsValue(gaugePushPersistBefore.gauge_metrics(), &gaugeMetrics); + ASSERT_EQ(gaugeMetrics.data_size(), 2); + data = gaugeMetrics.data(0); + ValidateUidDimension(data.dimensions_in_what(), util::APP_START_OCCURRED, app1Uid); + ASSERT_EQ(data.bucket_info_size(), 1); + ValidateGaugeBucketTimes(data.bucket_info(0), roundedBucketStartNs, roundedUpdateTimeNs, + {(int64_t)(bucketStartTimeNs + 10 * NS_PER_SEC), + (int64_t)(bucketStartTimeNs + 35 * NS_PER_SEC)}); + ASSERT_EQ(data.bucket_info(0).atom_size(), 2); + EXPECT_EQ(data.bucket_info(0).atom(0).app_start_occurred().pkg_name(), "app1"); + EXPECT_EQ(data.bucket_info(0).atom(0).app_start_occurred().activity_start_millis(), 101); + EXPECT_EQ(data.bucket_info(0).atom(1).app_start_occurred().pkg_name(), "app1"); + EXPECT_EQ(data.bucket_info(0).atom(1).app_start_occurred().activity_start_millis(), 102); + + data = gaugeMetrics.data(1); + ValidateUidDimension(data.dimensions_in_what(), util::APP_START_OCCURRED, app2Uid); + ASSERT_EQ(data.bucket_info_size(), 1); + ValidateGaugeBucketTimes(data.bucket_info(0), roundedBucketStartNs, roundedUpdateTimeNs, + {(int64_t)(bucketStartTimeNs + 40 * NS_PER_SEC)}); + ASSERT_EQ(data.bucket_info(0).atom_size(), 1); + EXPECT_EQ(data.bucket_info(0).atom(0).app_start_occurred().pkg_name(), "app2"); + EXPECT_EQ(data.bucket_info(0).atom(0).app_start_occurred().activity_start_millis(), 202); + + // Report from after update. + report = reports.reports(1); + ASSERT_EQ(report.metrics_size(), 4); + // Gauge screen on events FIRST_N_SAMPLES. There were 2. + StatsLogReport gaugeChangeAfter = report.metrics(0); + EXPECT_EQ(gaugeChangeAfter.metric_id(), gaugeChange.id()); + EXPECT_TRUE(gaugeChangeAfter.has_gauge_metrics()); + gaugeMetrics.Clear(); + sortMetricDataByDimensionsValue(gaugeChangeAfter.gauge_metrics(), &gaugeMetrics); + ASSERT_EQ(gaugeMetrics.data_size(), 1); + data = gaugeMetrics.data(0); + ASSERT_EQ(data.bucket_info_size(), 1); + ValidateGaugeBucketTimes(data.bucket_info(0), roundedUpdateTimeNs, roundedBucketEndNs, + {(int64_t)(bucketStartTimeNs + 80 * NS_PER_SEC), + (int64_t)(bucketStartTimeNs + 105 * NS_PER_SEC)}); + ASSERT_EQ(data.bucket_info(0).atom_size(), 2); + EXPECT_EQ(data.bucket_info(0).atom(0).screen_state_changed().state(), + android::view::DISPLAY_STATE_ON); + EXPECT_EQ(data.bucket_info(0).atom(1).screen_state_changed().state(), + android::view::DISPLAY_STATE_ON); + + // Gauge subsystem sleep state, random one sample, no condition. + // Pulled at update time and after the normal bucket end. + StatsLogReport gaugeNewAfter = report.metrics(1); + EXPECT_EQ(gaugeNewAfter.metric_id(), gaugeNew.id()); + EXPECT_TRUE(gaugeNewAfter.has_gauge_metrics()); + gaugeMetrics.Clear(); + sortMetricDataByDimensionsValue(gaugeNewAfter.gauge_metrics(), &gaugeMetrics); + ASSERT_EQ(gaugeMetrics.data_size(), 2); + data = gaugeMetrics.data(0); + ValidateSubsystemSleepDimension(data.dimensions_in_what(), "subsystem_name_1"); + ASSERT_EQ(data.bucket_info_size(), 2); + ValidateGaugeBucketTimes(data.bucket_info(0), roundedUpdateTimeNs, roundedBucketEndNs, + {updateTimeNs}); + ASSERT_EQ(data.bucket_info(0).atom_size(), 1); + EXPECT_EQ(data.bucket_info(0).atom(0).subsystem_sleep_state().time_millis(), 901); + ValidateGaugeBucketTimes(data.bucket_info(1), roundedBucketEndNs, roundedDumpTimeNs, + {(int64_t)(bucketStartTimeNs + bucketSizeNs)}); + ASSERT_EQ(data.bucket_info(1).atom_size(), 1); + EXPECT_EQ(data.bucket_info(1).atom(0).subsystem_sleep_state().time_millis(), 1601); + + data = gaugeMetrics.data(1); + ValidateSubsystemSleepDimension(data.dimensions_in_what(), "subsystem_name_2"); + ASSERT_EQ(data.bucket_info_size(), 2); + ValidateGaugeBucketTimes(data.bucket_info(0), roundedUpdateTimeNs, roundedBucketEndNs, + {updateTimeNs}); + ASSERT_EQ(data.bucket_info(0).atom_size(), 1); + EXPECT_EQ(data.bucket_info(0).atom(0).subsystem_sleep_state().time_millis(), 902); + ValidateGaugeBucketTimes(data.bucket_info(1), roundedBucketEndNs, roundedDumpTimeNs, + {(int64_t)(bucketStartTimeNs + bucketSizeNs)}); + ASSERT_EQ(data.bucket_info(1).atom_size(), 1); + EXPECT_EQ(data.bucket_info(1).atom(0).subsystem_sleep_state().time_millis(), 1602); + + // Gauge app start while app is in the background. App 1 started once, app 2 started twice. + StatsLogReport gaugePushPersistAfter = report.metrics(2); + EXPECT_EQ(gaugePushPersistAfter.metric_id(), gaugePushPersist.id()); + EXPECT_TRUE(gaugePushPersistAfter.has_gauge_metrics()); + gaugeMetrics.Clear(); + sortMetricDataByDimensionsValue(gaugePushPersistAfter.gauge_metrics(), &gaugeMetrics); + ASSERT_EQ(gaugeMetrics.data_size(), 2); + data = gaugeMetrics.data(0); + ValidateUidDimension(data.dimensions_in_what(), util::APP_START_OCCURRED, app1Uid); + ASSERT_EQ(data.bucket_info_size(), 1); + ValidateGaugeBucketTimes(data.bucket_info(0), roundedUpdateTimeNs, roundedBucketEndNs, + {(int64_t)(bucketStartTimeNs + 95 * NS_PER_SEC)}); + ASSERT_EQ(data.bucket_info(0).atom_size(), 1); + EXPECT_EQ(data.bucket_info(0).atom(0).app_start_occurred().pkg_name(), "app1"); + EXPECT_EQ(data.bucket_info(0).atom(0).app_start_occurred().activity_start_millis(), 104); + + data = gaugeMetrics.data(1); + ValidateUidDimension(data.dimensions_in_what(), util::APP_START_OCCURRED, app2Uid); + ASSERT_EQ(data.bucket_info_size(), 1); + ValidateGaugeBucketTimes(data.bucket_info(0), roundedUpdateTimeNs, roundedBucketEndNs, + {(int64_t)(bucketStartTimeNs + 70 * NS_PER_SEC), + (int64_t)(bucketStartTimeNs + 100 * NS_PER_SEC)}); + ASSERT_EQ(data.bucket_info(0).atom_size(), 2); + EXPECT_EQ(data.bucket_info(0).atom(0).app_start_occurred().pkg_name(), "app2"); + EXPECT_EQ(data.bucket_info(0).atom(0).app_start_occurred().activity_start_millis(), 203); + EXPECT_EQ(data.bucket_info(0).atom(1).app_start_occurred().pkg_name(), "app2"); + EXPECT_EQ(data.bucket_info(0).atom(1).app_start_occurred().activity_start_millis(), 204); + + // Gauge subsystem sleep state when screen is on. One pull at update since screen is on then. + StatsLogReport gaugePullPersistAfter = report.metrics(3); + EXPECT_EQ(gaugePullPersistAfter.metric_id(), gaugePullPersist.id()); + EXPECT_TRUE(gaugePullPersistAfter.has_gauge_metrics()); + gaugeMetrics.Clear(); + sortMetricDataByDimensionsValue(gaugePullPersistAfter.gauge_metrics(), &gaugeMetrics); + ASSERT_EQ(gaugeMetrics.data_size(), 2); + data = gaugeMetrics.data(0); + ValidateSubsystemSleepDimension(data.dimensions_in_what(), "subsystem_name_1"); + ASSERT_EQ(data.bucket_info_size(), 1); + ValidateGaugeBucketTimes(data.bucket_info(0), roundedUpdateTimeNs, roundedBucketEndNs, + {updateTimeNs}); + ASSERT_EQ(data.bucket_info(0).atom_size(), 1); + EXPECT_EQ(data.bucket_info(0).atom(0).subsystem_sleep_state().time_millis(), 901); + + data = gaugeMetrics.data(1); + ValidateSubsystemSleepDimension(data.dimensions_in_what(), "subsystem_name_2"); + ASSERT_EQ(data.bucket_info_size(), 1); + ValidateGaugeBucketTimes(data.bucket_info(0), roundedUpdateTimeNs, roundedBucketEndNs, + {updateTimeNs}); + ASSERT_EQ(data.bucket_info(0).atom_size(), 1); + EXPECT_EQ(data.bucket_info(0).atom(0).subsystem_sleep_state().time_millis(), 902); +} + +TEST_F(ConfigUpdateE2eTest, TestValueMetric) { + StatsdConfig config; + config.add_allowed_log_source("AID_ROOT"); + config.add_default_pull_packages("AID_ROOT"); // Fake puller is registered with root. + + AtomMatcher brightnessMatcher = CreateScreenBrightnessChangedAtomMatcher(); + *config.add_atom_matcher() = brightnessMatcher; + AtomMatcher screenOnMatcher = CreateScreenTurnedOnAtomMatcher(); + *config.add_atom_matcher() = screenOnMatcher; + AtomMatcher screenOffMatcher = CreateScreenTurnedOffAtomMatcher(); + *config.add_atom_matcher() = screenOffMatcher; + AtomMatcher batteryPluggedUsbMatcher = CreateBatteryStateUsbMatcher(); + *config.add_atom_matcher() = batteryPluggedUsbMatcher; + AtomMatcher unpluggedMatcher = CreateBatteryStateNoneMatcher(); + *config.add_atom_matcher() = unpluggedMatcher; + AtomMatcher subsystemSleepMatcher = + CreateSimpleAtomMatcher("SubsystemSleep", util::SUBSYSTEM_SLEEP_STATE); + *config.add_atom_matcher() = subsystemSleepMatcher; + + Predicate screenOnPredicate = CreateScreenIsOnPredicate(); + *config.add_predicate() = screenOnPredicate; + Predicate unpluggedPredicate = CreateDeviceUnpluggedPredicate(); + *config.add_predicate() = unpluggedPredicate; + + State screenState = CreateScreenState(); + *config.add_state() = screenState; + + ValueMetric valuePullPersist = + createValueMetric("SubsystemSleepWhileUnpluggedSliceScreen", subsystemSleepMatcher, 4, + unpluggedPredicate.id(), {screenState.id()}); + *valuePullPersist.mutable_dimensions_in_what() = + CreateDimensions(util::SUBSYSTEM_SLEEP_STATE, {1 /* subsystem name */}); + + ValueMetric valuePushPersist = createValueMetric( + "MinScreenBrightnessWhileScreenOn", brightnessMatcher, 1, screenOnPredicate.id(), {}); + valuePushPersist.set_aggregation_type(ValueMetric::MIN); + + ValueMetric valueChange = + createValueMetric("SubsystemSleep", subsystemSleepMatcher, 4, nullopt, {}); + *valueChange.mutable_dimensions_in_what() = + CreateDimensions(util::SUBSYSTEM_SLEEP_STATE, {1 /* subsystem name */}); + + ValueMetric valueRemove = + createValueMetric("AvgScreenBrightness", brightnessMatcher, 1, nullopt, {}); + valueRemove.set_aggregation_type(ValueMetric::AVG); + + *config.add_value_metric() = valuePullPersist; + *config.add_value_metric() = valueRemove; + *config.add_value_metric() = valuePushPersist; + *config.add_value_metric() = valueChange; + + ConfigKey key(123, 987); + uint64_t bucketStartTimeNs = getElapsedRealtimeNs(); + uint64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(TEN_MINUTES) * 1000000LL; + // Config creation triggers pull #1. + sp processor = CreateStatsLogProcessor( + bucketStartTimeNs, bucketStartTimeNs, config, key, + SharedRefBase::make(), util::SUBSYSTEM_SLEEP_STATE); + + // Initialize log events before update. + // ValuePushPersist and ValuePullPersist will skip the bucket due to condition unknown. + std::vector> events; + events.push_back(CreateScreenBrightnessChangedEvent(bucketStartTimeNs + 5 * NS_PER_SEC, 5)); + events.push_back(CreateScreenStateChangedEvent(bucketStartTimeNs + 10 * NS_PER_SEC, + android::view::DISPLAY_STATE_ON)); + events.push_back(CreateScreenBrightnessChangedEvent(bucketStartTimeNs + 15 * NS_PER_SEC, 15)); + events.push_back(CreateBatteryStateChangedEvent( + bucketStartTimeNs + 20 * NS_PER_SEC, + BatteryPluggedStateEnum::BATTERY_PLUGGED_NONE)); // Pull #2. + events.push_back(CreateScreenBrightnessChangedEvent(bucketStartTimeNs + 25 * NS_PER_SEC, 40)); + + // Send log events to StatsLogProcessor. + for (auto& event : events) { + processor->mPullerManager->ForceClearPullerCache(); + processor->OnLogEvent(event.get()); + } + processor->mPullerManager->ForceClearPullerCache(); + + // Do the update. Add matchers/conditions in different order to force indices to change. + StatsdConfig newConfig; + newConfig.add_allowed_log_source("AID_ROOT"); + newConfig.add_default_pull_packages("AID_ROOT"); // Fake puller is registered with root. + + *newConfig.add_atom_matcher() = screenOffMatcher; + *newConfig.add_atom_matcher() = unpluggedMatcher; + *newConfig.add_atom_matcher() = batteryPluggedUsbMatcher; + *newConfig.add_atom_matcher() = subsystemSleepMatcher; + *newConfig.add_atom_matcher() = brightnessMatcher; + *newConfig.add_atom_matcher() = screenOnMatcher; + + *newConfig.add_predicate() = unpluggedPredicate; + *newConfig.add_predicate() = screenOnPredicate; + + *config.add_state() = screenState; + + valueChange.set_condition(screenOnPredicate.id()); + *newConfig.add_value_metric() = valueChange; + ValueMetric valueNew = createValueMetric("MaxScrBrightness", brightnessMatcher, 1, nullopt, {}); + valueNew.set_aggregation_type(ValueMetric::MAX); + *newConfig.add_value_metric() = valueNew; + *newConfig.add_value_metric() = valuePushPersist; + *newConfig.add_value_metric() = valuePullPersist; + + int64_t updateTimeNs = bucketStartTimeNs + 30 * NS_PER_SEC; + // Update pulls valuePullPersist and valueNew. Pull #3. + processor->OnConfigUpdated(updateTimeNs, key, newConfig); + + // Verify puller manager is properly set. + sp pullerManager = processor->mPullerManager; + EXPECT_EQ(pullerManager->mNextPullTimeNs, bucketStartTimeNs + bucketSizeNs); + ASSERT_EQ(pullerManager->mReceivers.size(), 1); + ASSERT_EQ(pullerManager->mReceivers.begin()->second.size(), 2); + + // Send events after the update. Values reset since this is a new bucket. + events.clear(); + events.push_back(CreateScreenBrightnessChangedEvent(bucketStartTimeNs + 35 * NS_PER_SEC, 30)); + events.push_back(CreateScreenStateChangedEvent(bucketStartTimeNs + 40 * NS_PER_SEC, + android::view::DISPLAY_STATE_OFF)); // Pull #4. + events.push_back(CreateScreenBrightnessChangedEvent(bucketStartTimeNs + 45 * NS_PER_SEC, 20)); + events.push_back(CreateBatteryStateChangedEvent( + bucketStartTimeNs + 50 * NS_PER_SEC, + BatteryPluggedStateEnum::BATTERY_PLUGGED_USB)); // Pull #5. + events.push_back(CreateScreenBrightnessChangedEvent(bucketStartTimeNs + 55 * NS_PER_SEC, 25)); + events.push_back(CreateScreenStateChangedEvent(bucketStartTimeNs + 60 * NS_PER_SEC, + android::view::DISPLAY_STATE_ON)); // Pull #6. + events.push_back(CreateBatteryStateChangedEvent( + bucketStartTimeNs + 65 * NS_PER_SEC, + BatteryPluggedStateEnum::BATTERY_PLUGGED_NONE)); // Pull #7. + events.push_back(CreateScreenBrightnessChangedEvent(bucketStartTimeNs + 70 * NS_PER_SEC, 40)); + + // Send log events to StatsLogProcessor. + for (auto& event : events) { + processor->mPullerManager->ForceClearPullerCache(); + processor->OnLogEvent(event.get()); + } + processor->mPullerManager->ForceClearPullerCache(); + + // Pulling alarm arrive, triggering a bucket split. + // Both valuePullPersist and valueChange use the value since both conditions are true. Pull #8. + processor->informPullAlarmFired(bucketStartTimeNs + bucketSizeNs); + processor->OnLogEvent(CreateScreenBrightnessChangedEvent( + bucketStartTimeNs + bucketSizeNs + 5 * NS_PER_SEC, 50) + .get()); + + uint64_t dumpTimeNs = bucketStartTimeNs + bucketSizeNs + 10 * NS_PER_SEC; + ConfigMetricsReportList reports; + vector buffer; + processor->onDumpReport(key, dumpTimeNs, true, true, ADB_DUMP, FAST, &buffer); + EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size())); + backfillDimensionPath(&reports); + backfillStringInReport(&reports); + backfillStartEndTimestamp(&reports); + ASSERT_EQ(reports.reports_size(), 2); + + int64_t roundedBucketStartNs = MillisToNano(NanoToMillis(bucketStartTimeNs)); + int64_t roundedUpdateTimeNs = MillisToNano(NanoToMillis(updateTimeNs)); + int64_t roundedBucketEndNs = MillisToNano(NanoToMillis(bucketStartTimeNs + bucketSizeNs)); + int64_t roundedDumpTimeNs = MillisToNano(NanoToMillis(dumpTimeNs)); + + // Report from before update. + ConfigMetricsReport report = reports.reports(0); + ASSERT_EQ(report.metrics_size(), 4); + // Pull subsystem sleep while unplugged slice screen. Bucket skipped due to condition unknown. + StatsLogReport valuePullPersistBefore = report.metrics(0); + EXPECT_EQ(valuePullPersistBefore.metric_id(), valuePullPersist.id()); + EXPECT_TRUE(valuePullPersistBefore.has_value_metrics()); + ASSERT_EQ(valuePullPersistBefore.value_metrics().data_size(), 0); + ASSERT_EQ(valuePullPersistBefore.value_metrics().skipped_size(), 1); + StatsLogReport::SkippedBuckets skipBucket = valuePullPersistBefore.value_metrics().skipped(0); + EXPECT_EQ(skipBucket.start_bucket_elapsed_nanos(), roundedBucketStartNs); + EXPECT_EQ(skipBucket.end_bucket_elapsed_nanos(), roundedUpdateTimeNs); + ASSERT_EQ(skipBucket.drop_event_size(), 1); + EXPECT_EQ(skipBucket.drop_event(0).drop_reason(), BucketDropReason::CONDITION_UNKNOWN); + + // Average screen brightness. Values were 5, 15, 40. Avg: 20. + StatsLogReport valueRemoveBefore = report.metrics(1); + EXPECT_EQ(valueRemoveBefore.metric_id(), valueRemove.id()); + EXPECT_TRUE(valueRemoveBefore.has_value_metrics()); + StatsLogReport::ValueMetricDataWrapper valueMetrics; + sortMetricDataByDimensionsValue(valueRemoveBefore.value_metrics(), &valueMetrics); + ASSERT_EQ(valueMetrics.data_size(), 1); + ValueMetricData data = valueMetrics.data(0); + EXPECT_FALSE(data.has_dimensions_in_what()); + EXPECT_EQ(data.slice_by_state_size(), 0); + ASSERT_EQ(data.bucket_info_size(), 1); + ValidateValueBucket(data.bucket_info(0), roundedBucketStartNs, roundedUpdateTimeNs, 20, 0); + + // Min screen brightness while screen on. Bucket skipped due to condition unknown. + StatsLogReport valuePushPersistBefore = report.metrics(2); + EXPECT_EQ(valuePushPersistBefore.metric_id(), valuePushPersist.id()); + EXPECT_TRUE(valuePushPersistBefore.has_value_metrics()); + ASSERT_EQ(valuePushPersistBefore.value_metrics().data_size(), 0); + ASSERT_EQ(valuePushPersistBefore.value_metrics().skipped_size(), 1); + skipBucket = valuePushPersistBefore.value_metrics().skipped(0); + EXPECT_EQ(skipBucket.start_bucket_elapsed_nanos(), roundedBucketStartNs); + EXPECT_EQ(skipBucket.end_bucket_elapsed_nanos(), roundedUpdateTimeNs); + ASSERT_EQ(skipBucket.drop_event_size(), 1); + EXPECT_EQ(skipBucket.drop_event(0).drop_reason(), BucketDropReason::CONDITION_UNKNOWN); + + // Pull Subsystem sleep state. Value is Pull #3 (900) - Pull#1 (100). + StatsLogReport valueChangeBefore = report.metrics(3); + EXPECT_EQ(valueChangeBefore.metric_id(), valueChange.id()); + EXPECT_TRUE(valueChangeBefore.has_value_metrics()); + valueMetrics.Clear(); + sortMetricDataByDimensionsValue(valueChangeBefore.value_metrics(), &valueMetrics); + ASSERT_EQ(valueMetrics.data_size(), 2); + data = valueMetrics.data(0); + ValidateSubsystemSleepDimension(data.dimensions_in_what(), "subsystem_name_1"); + ASSERT_EQ(data.bucket_info_size(), 1); + ValidateValueBucket(data.bucket_info(0), roundedBucketStartNs, roundedUpdateTimeNs, 800, 0); + data = valueMetrics.data(1); + ValidateSubsystemSleepDimension(data.dimensions_in_what(), "subsystem_name_2"); + ASSERT_EQ(data.bucket_info_size(), 1); + ValidateValueBucket(data.bucket_info(0), roundedBucketStartNs, roundedUpdateTimeNs, 800, 0); + + // Report from after update. + report = reports.reports(1); + ASSERT_EQ(report.metrics_size(), 4); + // Pull subsystem sleep while screen on. + // Pull#4 (1600) - pull#3 (900) + pull#8 (6400) - pull#6 (3600) + StatsLogReport valueChangeAfter = report.metrics(0); + EXPECT_EQ(valueChangeAfter.metric_id(), valueChange.id()); + EXPECT_TRUE(valueChangeAfter.has_value_metrics()); + valueMetrics.Clear(); + sortMetricDataByDimensionsValue(valueChangeAfter.value_metrics(), &valueMetrics); + int64_t conditionTrueNs = bucketSizeNs - 60 * NS_PER_SEC + 10 * NS_PER_SEC; + ASSERT_EQ(valueMetrics.data_size(), 2); + data = valueMetrics.data(0); + ValidateSubsystemSleepDimension(data.dimensions_in_what(), "subsystem_name_1"); + ASSERT_EQ(data.bucket_info_size(), 1); + ValidateValueBucket(data.bucket_info(0), roundedUpdateTimeNs, roundedBucketEndNs, 3500, + conditionTrueNs); + ASSERT_EQ(valueMetrics.data_size(), 2); + data = valueMetrics.data(1); + ValidateSubsystemSleepDimension(data.dimensions_in_what(), "subsystem_name_2"); + ASSERT_EQ(data.bucket_info_size(), 1); + ValidateValueBucket(data.bucket_info(0), roundedUpdateTimeNs, roundedBucketEndNs, 3500, + conditionTrueNs); + + ASSERT_EQ(valueChangeAfter.value_metrics().skipped_size(), 1); + skipBucket = valueChangeAfter.value_metrics().skipped(0); + EXPECT_EQ(skipBucket.start_bucket_elapsed_nanos(), roundedBucketEndNs); + EXPECT_EQ(skipBucket.end_bucket_elapsed_nanos(), roundedDumpTimeNs); + ASSERT_EQ(skipBucket.drop_event_size(), 1); + EXPECT_EQ(skipBucket.drop_event(0).drop_reason(), BucketDropReason::DUMP_REPORT_REQUESTED); + + // Max screen brightness, no condition. Val is 40 in first bucket, 50 in second. + StatsLogReport valueNewAfter = report.metrics(1); + EXPECT_EQ(valueNewAfter.metric_id(), valueNew.id()); + EXPECT_TRUE(valueNewAfter.has_value_metrics()); + valueMetrics.Clear(); + sortMetricDataByDimensionsValue(valueNewAfter.value_metrics(), &valueMetrics); + ASSERT_EQ(valueMetrics.data_size(), 1); + data = valueMetrics.data(0); + ASSERT_EQ(data.bucket_info_size(), 2); + ValidateValueBucket(data.bucket_info(0), roundedUpdateTimeNs, roundedBucketEndNs, 40, 0); + ValidateValueBucket(data.bucket_info(1), roundedBucketEndNs, roundedDumpTimeNs, 50, 0); + + // Min screen brightness when screen on. Val is 30 in first bucket, 50 in second. + StatsLogReport valuePushPersistAfter = report.metrics(2); + EXPECT_EQ(valuePushPersistAfter.metric_id(), valuePushPersist.id()); + EXPECT_TRUE(valuePushPersistAfter.has_value_metrics()); + valueMetrics.Clear(); + sortMetricDataByDimensionsValue(valuePushPersistAfter.value_metrics(), &valueMetrics); + ASSERT_EQ(valueMetrics.data_size(), 1); + data = valueMetrics.data(0); + ASSERT_EQ(data.bucket_info_size(), 2); + conditionTrueNs = bucketSizeNs - 60 * NS_PER_SEC + 10 * NS_PER_SEC; + ValidateValueBucket(data.bucket_info(0), roundedUpdateTimeNs, roundedBucketEndNs, 30, + conditionTrueNs); + ValidateValueBucket(data.bucket_info(1), roundedBucketEndNs, roundedDumpTimeNs, 50, + 10 * NS_PER_SEC); + + // TODO(b/179725160): fix assertions. + // Subsystem sleep state while unplugged slice screen. + StatsLogReport valuePullPersistAfter = report.metrics(3); + EXPECT_EQ(valuePullPersistAfter.metric_id(), valuePullPersist.id()); + EXPECT_TRUE(valuePullPersistAfter.has_value_metrics()); + valueMetrics.Clear(); + sortMetricDataByDimensionsValue(valuePullPersistAfter.value_metrics(), &valueMetrics); + ASSERT_EQ(valueMetrics.data_size(), 4); + // Name 1, screen OFF. Pull#5 (2500) - pull#4 (1600). + data = valueMetrics.data(0); + conditionTrueNs = 10 * NS_PER_SEC; + ValidateSubsystemSleepDimension(data.dimensions_in_what(), "subsystem_name_1"); + ValidateStateValue(data.slice_by_state(), util::SCREEN_STATE_CHANGED, + android::view::DisplayStateEnum::DISPLAY_STATE_OFF); + ASSERT_EQ(data.bucket_info_size(), 1); + ValidateValueBucket(data.bucket_info(0), roundedUpdateTimeNs, roundedBucketEndNs, 900, -1); + // Name 1, screen ON. Pull#4 (1600) - pull#3 (900) + pull#8 (6400) - pull#7 (4900). + data = valueMetrics.data(1); + conditionTrueNs = 10 * NS_PER_SEC + bucketSizeNs - 65 * NS_PER_SEC; + ValidateSubsystemSleepDimension(data.dimensions_in_what(), "subsystem_name_1"); + ValidateStateValue(data.slice_by_state(), util::SCREEN_STATE_CHANGED, + android::view::DisplayStateEnum::DISPLAY_STATE_ON); + ASSERT_EQ(data.bucket_info_size(), 1); + ValidateValueBucket(data.bucket_info(0), roundedUpdateTimeNs, roundedBucketEndNs, 2200, -1); + // Name 2, screen OFF. Pull#5 (2500) - pull#4 (1600). + data = valueMetrics.data(2); + conditionTrueNs = 10 * NS_PER_SEC; + ValidateSubsystemSleepDimension(data.dimensions_in_what(), "subsystem_name_2"); + ValidateStateValue(data.slice_by_state(), util::SCREEN_STATE_CHANGED, + android::view::DisplayStateEnum::DISPLAY_STATE_OFF); + ASSERT_EQ(data.bucket_info_size(), 1); + ValidateValueBucket(data.bucket_info(0), roundedUpdateTimeNs, roundedBucketEndNs, 900, -1); + // Name 2, screen ON. Pull#4 (1600) - pull#3 (900) + pull#8 (6400) - pull#7 (4900). + data = valueMetrics.data(3); + conditionTrueNs = 10 * NS_PER_SEC + bucketSizeNs - 65 * NS_PER_SEC; + ValidateSubsystemSleepDimension(data.dimensions_in_what(), "subsystem_name_2"); + ValidateStateValue(data.slice_by_state(), util::SCREEN_STATE_CHANGED, + android::view::DisplayStateEnum::DISPLAY_STATE_ON); + ASSERT_EQ(data.bucket_info_size(), 1); + ValidateValueBucket(data.bucket_info(0), roundedUpdateTimeNs, roundedBucketEndNs, 2200, -1); + + ASSERT_EQ(valuePullPersistAfter.value_metrics().skipped_size(), 1); + skipBucket = valuePullPersistAfter.value_metrics().skipped(0); + EXPECT_EQ(skipBucket.start_bucket_elapsed_nanos(), roundedBucketEndNs); + EXPECT_EQ(skipBucket.end_bucket_elapsed_nanos(), roundedDumpTimeNs); + ASSERT_EQ(skipBucket.drop_event_size(), 1); + EXPECT_EQ(skipBucket.drop_event(0).drop_reason(), BucketDropReason::DUMP_REPORT_REQUESTED); +} + +TEST_F(ConfigUpdateE2eTest, TestMetricActivation) { + StatsdConfig config; + config.add_allowed_log_source("AID_ROOT"); + + string immediateTag = "immediate", bootTag = "boot", childTag = "child"; + + AtomMatcher syncStartMatcher = CreateSyncStartAtomMatcher(); + *config.add_atom_matcher() = syncStartMatcher; + + AtomMatcher immediateMatcher = + CreateSimpleAtomMatcher("immediateMatcher", util::WAKELOCK_STATE_CHANGED); + FieldValueMatcher* fvm = + immediateMatcher.mutable_simple_atom_matcher()->add_field_value_matcher(); + fvm->set_field(3); // Tag. + fvm->set_eq_string(immediateTag); + *config.add_atom_matcher() = immediateMatcher; + + AtomMatcher bootMatcher = CreateSimpleAtomMatcher("bootMatcher", util::WAKELOCK_STATE_CHANGED); + fvm = bootMatcher.mutable_simple_atom_matcher()->add_field_value_matcher(); + fvm->set_field(3); // Tag. + fvm->set_eq_string(bootTag); + *config.add_atom_matcher() = bootMatcher; + + AtomMatcher childMatcher = + CreateSimpleAtomMatcher("childMatcher", util::WAKELOCK_STATE_CHANGED); + fvm = childMatcher.mutable_simple_atom_matcher()->add_field_value_matcher(); + fvm->set_field(3); // Tag. + fvm->set_eq_string(childTag); + *config.add_atom_matcher() = childMatcher; + + AtomMatcher acquireMatcher = CreateAcquireWakelockAtomMatcher(); + *config.add_atom_matcher() = acquireMatcher; + + AtomMatcher combinationMatcher; + combinationMatcher.set_id(StringToId("combination")); + AtomMatcher_Combination* combination = combinationMatcher.mutable_combination(); + combination->set_operation(LogicalOperation::OR); + combination->add_matcher(acquireMatcher.id()); + combination->add_matcher(childMatcher.id()); + *config.add_atom_matcher() = combinationMatcher; + + CountMetric immediateMetric = + createCountMetric("ImmediateMetric", syncStartMatcher.id(), nullopt, {}); + CountMetric bootMetric = createCountMetric("BootMetric", syncStartMatcher.id(), nullopt, {}); + CountMetric combinationMetric = + createCountMetric("CombinationMetric", syncStartMatcher.id(), nullopt, {}); + *config.add_count_metric() = immediateMetric; + *config.add_count_metric() = bootMetric; + *config.add_count_metric() = combinationMetric; + + MetricActivation immediateMetricActivation; + immediateMetricActivation.set_metric_id(immediateMetric.id()); + auto eventActivation = immediateMetricActivation.add_event_activation(); + eventActivation->set_activation_type(ActivationType::ACTIVATE_IMMEDIATELY); + eventActivation->set_atom_matcher_id(immediateMatcher.id()); + eventActivation->set_ttl_seconds(60); // One minute. + *config.add_metric_activation() = immediateMetricActivation; + + MetricActivation bootMetricActivation; + bootMetricActivation.set_metric_id(bootMetric.id()); + eventActivation = bootMetricActivation.add_event_activation(); + eventActivation->set_activation_type(ActivationType::ACTIVATE_ON_BOOT); + eventActivation->set_atom_matcher_id(bootMatcher.id()); + eventActivation->set_ttl_seconds(60); // One minute. + *config.add_metric_activation() = bootMetricActivation; + + MetricActivation combinationMetricActivation; + combinationMetricActivation.set_metric_id(combinationMetric.id()); + eventActivation = combinationMetricActivation.add_event_activation(); + eventActivation->set_activation_type(ActivationType::ACTIVATE_IMMEDIATELY); + eventActivation->set_atom_matcher_id(combinationMatcher.id()); + eventActivation->set_ttl_seconds(60); // One minute. + *config.add_metric_activation() = combinationMetricActivation; + + ConfigKey key(123, 987); + uint64_t bucketStartTimeNs = 10000000000; // 0:10 + uint64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(TEN_MINUTES) * 1000000LL; + sp processor = + CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, key); + int uid1 = 55555; + + // Initialize log events before update. + // Counts provided in order of immediate, boot, and combination metric. + std::vector> events; + + events.push_back(CreateSyncStartEvent(bucketStartTimeNs + 5 * NS_PER_SEC, {uid1}, {""}, + "")); // Count: 0, 0, 0. + events.push_back(CreateReleaseWakelockEvent(bucketStartTimeNs + 10 * NS_PER_SEC, {uid1}, {""}, + immediateTag)); // Activate immediate metric. + events.push_back(CreateSyncStartEvent(bucketStartTimeNs + 15 * NS_PER_SEC, {uid1}, {""}, + "")); // Count: 1, 0, 0. + events.push_back(CreateAcquireWakelockEvent(bucketStartTimeNs + 20 * NS_PER_SEC, {uid1}, {""}, + "foo")); // Activate combination metric. + events.push_back(CreateSyncStartEvent(bucketStartTimeNs + 25 * NS_PER_SEC, {uid1}, {""}, + "")); // Count: 2, 0, 1. + events.push_back(CreateReleaseWakelockEvent(bucketStartTimeNs + 30 * NS_PER_SEC, {uid1}, {""}, + bootTag)); // Boot metric pending activation. + events.push_back(CreateSyncStartEvent(bucketStartTimeNs + 35 * NS_PER_SEC, {uid1}, {""}, + "")); // Count: 3, 0, 2. + // Send log events to StatsLogProcessor. + for (auto& event : events) { + processor->OnLogEvent(event.get()); + } + + // Do update. Add matchers/conditions in different order to force indices to change. + StatsdConfig newConfig; + newConfig.add_allowed_log_source("AID_ROOT"); + newConfig.set_hash_strings_in_metric_report(false); // Modify metadata for fun. + + // Change combination matcher, will mean combination metric isn't active after update. + combinationMatcher.mutable_combination()->set_operation(LogicalOperation::AND); + *newConfig.add_atom_matcher() = acquireMatcher; + *newConfig.add_atom_matcher() = bootMatcher; + *newConfig.add_atom_matcher() = combinationMatcher; + *newConfig.add_atom_matcher() = childMatcher; + *newConfig.add_atom_matcher() = syncStartMatcher; + *newConfig.add_atom_matcher() = immediateMatcher; + + *newConfig.add_count_metric() = bootMetric; + *newConfig.add_count_metric() = combinationMetric; + *newConfig.add_count_metric() = immediateMetric; + + *newConfig.add_metric_activation() = bootMetricActivation; + *newConfig.add_metric_activation() = combinationMetricActivation; + *newConfig.add_metric_activation() = immediateMetricActivation; + + int64_t updateTimeNs = bucketStartTimeNs + 40 * NS_PER_SEC; + processor->OnConfigUpdated(updateTimeNs, key, newConfig); + + // The reboot will write to disk again, so sleep for 1 second to avoid this. + // TODO(b/178887128): clean this up. + std::this_thread::sleep_for(1000ms); + // Send event after the update. Counts reset to 0 since this is a new bucket. + processor->OnLogEvent( + CreateSyncStartEvent(bucketStartTimeNs + 45 * NS_PER_SEC, {uid1}, {""}, "") + .get()); // Count: 1, 0, 0. + + // Fake a reboot. Code is from StatsService::informDeviceShutdown. + int64_t shutDownTimeNs = bucketStartTimeNs + 50 * NS_PER_SEC; + processor->WriteDataToDisk(DEVICE_SHUTDOWN, FAST, shutDownTimeNs); + processor->SaveActiveConfigsToDisk(shutDownTimeNs); + processor->SaveMetadataToDisk(getWallClockNs(), shutDownTimeNs); + + // On boot, use StartUp. However, skip config manager for simplicity. + int64_t bootTimeNs = bucketStartTimeNs + 55 * NS_PER_SEC; + processor = CreateStatsLogProcessor(bootTimeNs, bootTimeNs, newConfig, key); + processor->LoadActiveConfigsFromDisk(); + processor->LoadMetadataFromDisk(getWallClockNs(), bootTimeNs); + + // Send events after boot. Counts reset to 0 since this is a new bucket. Boot metric now active. + events.clear(); + events.push_back(CreateSyncStartEvent(bucketStartTimeNs + 60 * NS_PER_SEC, {uid1}, {""}, + "")); // Count: 1, 1, 0. + int64_t deactivationTimeNs = bucketStartTimeNs + 76 * NS_PER_SEC; + events.push_back(CreateScreenStateChangedEvent( + deactivationTimeNs, + android::view::DisplayStateEnum::DISPLAY_STATE_OFF)); // TTLs immediate metric. + events.push_back(CreateSyncStartEvent(bucketStartTimeNs + 80 * NS_PER_SEC, {uid1}, {""}, + "")); // Count: 1, 2, 0. + events.push_back(CreateAcquireWakelockEvent(bucketStartTimeNs + 85 * NS_PER_SEC, {uid1}, {""}, + childTag)); // Activate combination metric. + events.push_back(CreateSyncStartEvent(bucketStartTimeNs + 90 * NS_PER_SEC, {uid1}, {""}, + "")); // Count: 1, 3, 1. + + // Send log events to StatsLogProcessor. + for (auto& event : events) { + processor->OnLogEvent(event.get()); + } + uint64_t dumpTimeNs = bucketStartTimeNs + 90 * NS_PER_SEC; + ConfigMetricsReportList reports; + vector buffer; + processor->onDumpReport(key, dumpTimeNs, true, true, ADB_DUMP, FAST, &buffer); + EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size())); + backfillDimensionPath(&reports); + backfillStringInReport(&reports); + backfillStartEndTimestamp(&reports); + ASSERT_EQ(reports.reports_size(), 3); + + // Report from before update. + ConfigMetricsReport report = reports.reports(0); + EXPECT_EQ(report.last_report_elapsed_nanos(), bucketStartTimeNs); + EXPECT_EQ(report.current_report_elapsed_nanos(), updateTimeNs); + ASSERT_EQ(report.metrics_size(), 3); + // Immediate metric. Count = 3. + StatsLogReport metricReport = report.metrics(0); + EXPECT_EQ(metricReport.metric_id(), immediateMetric.id()); + EXPECT_TRUE(metricReport.is_active()); + EXPECT_TRUE(metricReport.has_count_metrics()); + ASSERT_EQ(metricReport.count_metrics().data_size(), 1); + auto data = metricReport.count_metrics().data(0); + ASSERT_EQ(data.bucket_info_size(), 1); + ValidateCountBucket(data.bucket_info(0), bucketStartTimeNs, updateTimeNs, 3); + + // Boot metric. Count = 0. + metricReport = report.metrics(1); + EXPECT_EQ(metricReport.metric_id(), bootMetric.id()); + EXPECT_FALSE(metricReport.is_active()); + EXPECT_FALSE(metricReport.has_count_metrics()); + + // Combination metric. Count = 2. + metricReport = report.metrics(2); + EXPECT_EQ(metricReport.metric_id(), combinationMetric.id()); + EXPECT_TRUE(metricReport.is_active()); + EXPECT_TRUE(metricReport.has_count_metrics()); + ASSERT_EQ(metricReport.count_metrics().data_size(), 1); + data = metricReport.count_metrics().data(0); + ASSERT_EQ(data.bucket_info_size(), 1); + ValidateCountBucket(data.bucket_info(0), bucketStartTimeNs, updateTimeNs, 2); + + // Report from after update, before boot. + report = reports.reports(1); + EXPECT_EQ(report.last_report_elapsed_nanos(), updateTimeNs); + EXPECT_EQ(report.current_report_elapsed_nanos(), shutDownTimeNs); + ASSERT_EQ(report.metrics_size(), 3); + // Boot metric. Count = 0. + metricReport = report.metrics(0); + EXPECT_EQ(metricReport.metric_id(), bootMetric.id()); + EXPECT_FALSE(metricReport.is_active()); + EXPECT_FALSE(metricReport.has_count_metrics()); + + // Combination metric. Count = 0. + metricReport = report.metrics(1); + EXPECT_EQ(metricReport.metric_id(), combinationMetric.id()); + EXPECT_FALSE(metricReport.is_active()); + EXPECT_FALSE(metricReport.has_count_metrics()); + + // Immediate metric. Count = 1. + metricReport = report.metrics(2); + EXPECT_EQ(metricReport.metric_id(), immediateMetric.id()); + EXPECT_TRUE(metricReport.is_active()); + EXPECT_TRUE(metricReport.has_count_metrics()); + ASSERT_EQ(metricReport.count_metrics().data_size(), 1); + data = metricReport.count_metrics().data(0); + ASSERT_EQ(data.bucket_info_size(), 1); + ValidateCountBucket(data.bucket_info(0), updateTimeNs, shutDownTimeNs, 1); + + // Report from after reboot. + report = reports.reports(2); + EXPECT_EQ(report.last_report_elapsed_nanos(), bootTimeNs); + EXPECT_EQ(report.current_report_elapsed_nanos(), dumpTimeNs); + ASSERT_EQ(report.metrics_size(), 3); + // Boot metric. Count = 3. + metricReport = report.metrics(0); + EXPECT_EQ(metricReport.metric_id(), bootMetric.id()); + EXPECT_TRUE(metricReport.is_active()); + EXPECT_TRUE(metricReport.has_count_metrics()); + ASSERT_EQ(metricReport.count_metrics().data_size(), 1); + data = metricReport.count_metrics().data(0); + ASSERT_EQ(data.bucket_info_size(), 1); + ValidateCountBucket(data.bucket_info(0), bootTimeNs, dumpTimeNs, 3); + + // Combination metric. Count = 1. + metricReport = report.metrics(1); + EXPECT_EQ(metricReport.metric_id(), combinationMetric.id()); + EXPECT_TRUE(metricReport.is_active()); + EXPECT_TRUE(metricReport.has_count_metrics()); + ASSERT_EQ(metricReport.count_metrics().data_size(), 1); + data = metricReport.count_metrics().data(0); + ASSERT_EQ(data.bucket_info_size(), 1); + ValidateCountBucket(data.bucket_info(0), bootTimeNs, dumpTimeNs, 1); + + // Immediate metric. Count = 1. + metricReport = report.metrics(2); + EXPECT_EQ(metricReport.metric_id(), immediateMetric.id()); + EXPECT_FALSE(metricReport.is_active()); + EXPECT_TRUE(metricReport.has_count_metrics()); + ASSERT_EQ(metricReport.count_metrics().data_size(), 1); + data = metricReport.count_metrics().data(0); + ASSERT_EQ(data.bucket_info_size(), 1); + ValidateCountBucket(data.bucket_info(0), bootTimeNs, deactivationTimeNs, 1); +} + +TEST_F(ConfigUpdateE2eTest, TestAnomalyCountMetric) { + StatsdConfig config; + config.add_allowed_log_source("AID_ROOT"); + + AtomMatcher syncStartMatcher = CreateSyncStartAtomMatcher(); + *config.add_atom_matcher() = syncStartMatcher; + AtomMatcher wakelockAcquireMatcher = CreateAcquireWakelockAtomMatcher(); + *config.add_atom_matcher() = wakelockAcquireMatcher; + + CountMetric countWakelock = + createCountMetric("CountWakelock", wakelockAcquireMatcher.id(), nullopt, {}); + *countWakelock.mutable_dimensions_in_what() = + CreateAttributionUidDimensions(util::WAKELOCK_STATE_CHANGED, {Position::FIRST}); + + CountMetric countSync = createCountMetric("CountSync", syncStartMatcher.id(), nullopt, {}); + *countSync.mutable_dimensions_in_what() = + CreateAttributionUidDimensions(util::SYNC_STATE_CHANGED, {Position::FIRST}); + + *config.add_count_metric() = countWakelock; + *config.add_count_metric() = countSync; + + Alert alertPreserve = + createAlert("AlertPreserve", countWakelock.id(), /*buckets=*/2, /*triggerSumGt=*/1); + alertPreserve.set_refractory_period_secs(20); + Alert alertReplace = createAlert("AlertReplace", countSync.id(), 1, 1); + alertReplace.set_refractory_period_secs(1); + Alert alertRemove = createAlert("AlertRemove", countWakelock.id(), 1, 0); + alertRemove.set_refractory_period_secs(1); + *config.add_alert() = alertReplace; + *config.add_alert() = alertPreserve; + *config.add_alert() = alertRemove; + + int preserveSubId = 1, replaceSubId = 2, removeSubId = 3; + Subscription preserveSub = createSubscription("S1", Subscription::ALERT, alertPreserve.id()); + preserveSub.mutable_broadcast_subscriber_details()->set_subscriber_id(preserveSubId); + Subscription replaceSub = createSubscription("S2", Subscription::ALERT, alertReplace.id()); + replaceSub.mutable_broadcast_subscriber_details()->set_subscriber_id(replaceSubId); + Subscription removeSub = createSubscription("S3", Subscription::ALERT, alertRemove.id()); + removeSub.mutable_broadcast_subscriber_details()->set_subscriber_id(removeSubId); + *config.add_subscription() = preserveSub; + *config.add_subscription() = removeSub; + *config.add_subscription() = replaceSub; + + int app1Uid = 123, app2Uid = 456; + vector attributionUids1 = {app1Uid}; + vector attributionTags1 = {"App1"}; + vector attributionUids2 = {app2Uid}; + vector attributionTags2 = {"App2"}; + int64_t configUid = 123, configId = 987; + ConfigKey key(configUid, configId); + + int alertPreserveCount = 0, alertRemoveCount = 0; + StatsDimensionsValueParcel alertPreserveDims; + StatsDimensionsValueParcel alertRemoveDims; + + // The binder calls here will happen synchronously because they are in-process. + shared_ptr preserveBroadcast = + SharedRefBase::make>(); + EXPECT_CALL(*preserveBroadcast, sendSubscriberBroadcast(configUid, configId, preserveSub.id(), + alertPreserve.id(), _, _)) + .Times(2) + .WillRepeatedly( + Invoke([&alertPreserveCount, &alertPreserveDims]( + int64_t, int64_t, int64_t, int64_t, const vector&, + const StatsDimensionsValueParcel& dimensionsValueParcel) { + alertPreserveCount++; + alertPreserveDims = dimensionsValueParcel; + return Status::ok(); + })); + + shared_ptr replaceBroadcast = + SharedRefBase::make>(); + EXPECT_CALL(*replaceBroadcast, sendSubscriberBroadcast(configUid, configId, replaceSub.id(), + alertReplace.id(), _, _)) + .Times(0); + + shared_ptr removeBroadcast = + SharedRefBase::make>(); + EXPECT_CALL(*removeBroadcast, sendSubscriberBroadcast(configUid, configId, removeSub.id(), + alertRemove.id(), _, _)) + .Times(3) + .WillRepeatedly( + Invoke([&alertRemoveCount, &alertRemoveDims]( + int64_t, int64_t, int64_t, int64_t, const vector&, + const StatsDimensionsValueParcel& dimensionsValueParcel) { + alertRemoveCount++; + alertRemoveDims = dimensionsValueParcel; + return Status::ok(); + })); + + SubscriberReporter::getInstance().setBroadcastSubscriber(key, preserveSubId, preserveBroadcast); + SubscriberReporter::getInstance().setBroadcastSubscriber(key, replaceSubId, replaceBroadcast); + SubscriberReporter::getInstance().setBroadcastSubscriber(key, removeSubId, removeBroadcast); + + uint64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(TEN_MINUTES) * 1000000LL; + uint64_t bucketStartTimeNs = 10000000000; // 0:10 + uint64_t bucket2StartTimeNs = bucketStartTimeNs + bucketSizeNs; + sp processor = + CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, key); + + StatsDimensionsValueParcel wlUid1 = + CreateAttributionUidDimensionsValueParcel(util::WAKELOCK_STATE_CHANGED, app1Uid); + StatsDimensionsValueParcel wlUid2 = + CreateAttributionUidDimensionsValueParcel(util::WAKELOCK_STATE_CHANGED, app2Uid); + + processor->OnLogEvent(CreateAcquireWakelockEvent(bucketStartTimeNs + 15 * NS_PER_SEC, + attributionUids1, attributionTags1, "wl1") + .get()); + EXPECT_EQ(alertPreserveCount, 0); + EXPECT_EQ(alertRemoveCount, 1); +// EXPECT_EQ(alertRemoveDims, wlUid1); + + processor->OnLogEvent(CreateAcquireWakelockEvent(bucketStartTimeNs + 20 * NS_PER_SEC, + attributionUids2, attributionTags2, "wl2") + .get()); + EXPECT_EQ(alertPreserveCount, 0); + EXPECT_EQ(alertRemoveCount, 2); +// EXPECT_EQ(alertRemoveDims, wlUid2); + + processor->OnLogEvent(CreateSyncStartEvent(bucket2StartTimeNs + 5 * NS_PER_SEC, + attributionUids1, attributionTags1, "sync1") + .get()); + EXPECT_EQ(alertPreserveCount, 0); + EXPECT_EQ(alertRemoveCount, 2); + + // AlertPreserve enters 30 sec refractory period for uid2. + processor->OnLogEvent(CreateAcquireWakelockEvent(bucket2StartTimeNs + 10 * NS_PER_SEC, + attributionUids2, attributionTags2, "wl2") + .get()); + EXPECT_EQ(alertPreserveCount, 1); +// EXPECT_EQ(alertPreserveDims, wlUid2); + EXPECT_EQ(alertRemoveCount, 3); +// EXPECT_EQ(alertRemoveDims, wlUid2); + + // Do config update. + StatsdConfig newConfig; + newConfig.add_allowed_log_source("AID_ROOT"); + *newConfig.add_atom_matcher() = wakelockAcquireMatcher; + *newConfig.add_atom_matcher() = syncStartMatcher; + + // Clear dims of sync metric, will result in alertReplace getting replaced. + countSync.clear_dimensions_in_what(); + *newConfig.add_count_metric() = countSync; + *newConfig.add_count_metric() = countWakelock; + + // New alert on existing metric. Should get current full bucket, but not history of 1st bucket. + Alert alertNew = createAlert("AlertNew", countWakelock.id(), /*buckets=*/1, /*triggerSumGt=*/1); + *newConfig.add_alert() = alertPreserve; + *newConfig.add_alert() = alertNew; + *newConfig.add_alert() = alertReplace; + + int newSubId = 4; + Subscription newSub = createSubscription("S4", Subscription::ALERT, alertNew.id()); + newSub.mutable_broadcast_subscriber_details()->set_subscriber_id(newSubId); + *newConfig.add_subscription() = newSub; + *newConfig.add_subscription() = replaceSub; + *newConfig.add_subscription() = preserveSub; + + int alertNewCount = 0; + StatsDimensionsValueParcel alertNewDims; + shared_ptr newBroadcast = + SharedRefBase::make>(); + EXPECT_CALL(*newBroadcast, + sendSubscriberBroadcast(configUid, configId, newSub.id(), alertNew.id(), _, _)) + .Times(1) + .WillRepeatedly( + Invoke([&alertNewCount, &alertNewDims]( + int64_t, int64_t, int64_t, int64_t, const vector&, + const StatsDimensionsValueParcel& dimensionsValueParcel) { + alertNewCount++; + alertNewDims = dimensionsValueParcel; + return Status::ok(); + })); + SubscriberReporter::getInstance().setBroadcastSubscriber(key, newSubId, newBroadcast); + + int64_t updateTimeNs = bucket2StartTimeNs + 15 * NS_PER_SEC; + processor->OnConfigUpdated(updateTimeNs, key, newConfig); + + // Within refractory of AlertPreserve, but AlertNew should fire since the full bucket has 2. + processor->OnLogEvent(CreateAcquireWakelockEvent(bucket2StartTimeNs + 20 * NS_PER_SEC, + attributionUids2, attributionTags2, "wl2") + .get()); + EXPECT_EQ(alertPreserveCount, 1); + EXPECT_EQ(alertNewCount, 1); +// EXPECT_EQ(alertNewDims, wlUid2); + + // Wakelock for uid1 fired in first bucket, alert preserve should keep the history and fire. + processor->OnLogEvent(CreateAcquireWakelockEvent(bucket2StartTimeNs + 25 * NS_PER_SEC, + attributionUids1, attributionTags1, "wl1") + .get()); + EXPECT_EQ(alertPreserveCount, 2); +// EXPECT_EQ(alertPreserveDims, wlUid1); + EXPECT_EQ(alertNewCount, 1); + + processor->OnLogEvent(CreateSyncStartEvent(bucket2StartTimeNs + 30 * NS_PER_SEC, + attributionUids1, attributionTags1, "sync1") + .get()); + EXPECT_EQ(alertPreserveCount, 2); + EXPECT_EQ(alertNewCount, 1); + EXPECT_EQ(alertRemoveCount, 3); + + // Clear data so it doesn't stay on disk. + vector buffer; + processor->onDumpReport(key, bucket2StartTimeNs + 100 * NS_PER_SEC, true, true, ADB_DUMP, FAST, + &buffer); + SubscriberReporter::getInstance().unsetBroadcastSubscriber(key, preserveSubId); + SubscriberReporter::getInstance().unsetBroadcastSubscriber(key, replaceSubId); + SubscriberReporter::getInstance().unsetBroadcastSubscriber(key, removeSubId); + SubscriberReporter::getInstance().unsetBroadcastSubscriber(key, newSubId); +} + +TEST_F(ConfigUpdateE2eTest, TestAnomalyDurationMetric) { + StatsdConfig config; + config.add_allowed_log_source("AID_ROOT"); + + AtomMatcher wakelockAcquireMatcher = CreateAcquireWakelockAtomMatcher(); + *config.add_atom_matcher() = wakelockAcquireMatcher; + AtomMatcher wakelockReleaseMatcher = CreateReleaseWakelockAtomMatcher(); + *config.add_atom_matcher() = wakelockReleaseMatcher; + AtomMatcher screenOnMatcher = CreateScreenTurnedOnAtomMatcher(); + *config.add_atom_matcher() = screenOnMatcher; + AtomMatcher screenOffMatcher = CreateScreenTurnedOffAtomMatcher(); + *config.add_atom_matcher() = screenOffMatcher; + + Predicate holdingWakelockPredicate = CreateHoldingWakelockPredicate(); + *holdingWakelockPredicate.mutable_simple_predicate()->mutable_dimensions() = + CreateAttributionUidDimensions(util::WAKELOCK_STATE_CHANGED, {Position::FIRST}); + *config.add_predicate() = holdingWakelockPredicate; + Predicate screenOnPredicate = CreateScreenIsOnPredicate(); + *config.add_predicate() = screenOnPredicate; + + DurationMetric durationWakelock = + createDurationMetric("DurWakelock", holdingWakelockPredicate.id(), nullopt, {}); + *durationWakelock.mutable_dimensions_in_what() = + CreateAttributionUidDimensions(util::WAKELOCK_STATE_CHANGED, {Position::FIRST}); + DurationMetric durationScreen = + createDurationMetric("DurScreen", screenOnPredicate.id(), nullopt, {}); + *config.add_duration_metric() = durationScreen; + *config.add_duration_metric() = durationWakelock; + + Alert alertPreserve = createAlert("AlertPreserve", durationWakelock.id(), /*buckets=*/2, + /*triggerSumGt=*/30 * NS_PER_SEC); + alertPreserve.set_refractory_period_secs(300); + Alert alertReplace = createAlert("AlertReplace", durationScreen.id(), 2, 30 * NS_PER_SEC); + alertReplace.set_refractory_period_secs(1); + Alert alertRemove = createAlert("AlertRemove", durationWakelock.id(), 5, 10 * NS_PER_SEC); + alertRemove.set_refractory_period_secs(1); + *config.add_alert() = alertReplace; + *config.add_alert() = alertPreserve; + *config.add_alert() = alertRemove; + + int preserveSubId = 1, replaceSubId = 2, removeSubId = 3; + Subscription preserveSub = createSubscription("S1", Subscription::ALERT, alertPreserve.id()); + preserveSub.mutable_broadcast_subscriber_details()->set_subscriber_id(preserveSubId); + Subscription replaceSub = createSubscription("S2", Subscription::ALERT, alertReplace.id()); + replaceSub.mutable_broadcast_subscriber_details()->set_subscriber_id(replaceSubId); + Subscription removeSub = createSubscription("S3", Subscription::ALERT, alertRemove.id()); + removeSub.mutable_broadcast_subscriber_details()->set_subscriber_id(removeSubId); + *config.add_subscription() = preserveSub; + *config.add_subscription() = removeSub; + *config.add_subscription() = replaceSub; + + int app1Uid = 123, app2Uid = 456, app3Uid = 789, app4Uid = 111; + vector attributionUids1 = {app1Uid}, attributionUids2 = {app2Uid}, + attributionUids3 = {app3Uid}, attributionUids4 = {app4Uid}; + vector attributionTags1 = {"App1"}, attributionTags2 = {"App2"}, + attributionTags3 = {"App3"}, attributionTags4 = {"App4"}; + + int64_t configUid = 123, configId = 987; + ConfigKey key(configUid, configId); + + int alertPreserveCount = 0, alertRemoveCount = 0; + StatsDimensionsValueParcel alertPreserveDims; + StatsDimensionsValueParcel alertRemoveDims; + + // The binder calls here will happen synchronously because they are in-process. + shared_ptr preserveBroadcast = + SharedRefBase::make>(); + EXPECT_CALL(*preserveBroadcast, sendSubscriberBroadcast(configUid, configId, preserveSub.id(), + alertPreserve.id(), _, _)) + .Times(4) + .WillRepeatedly( + Invoke([&alertPreserveCount, &alertPreserveDims]( + int64_t, int64_t, int64_t, int64_t, const vector&, + const StatsDimensionsValueParcel& dimensionsValueParcel) { + alertPreserveCount++; + alertPreserveDims = dimensionsValueParcel; + return Status::ok(); + })); + + shared_ptr replaceBroadcast = + SharedRefBase::make>(); + EXPECT_CALL(*replaceBroadcast, sendSubscriberBroadcast(configUid, configId, replaceSub.id(), + alertReplace.id(), _, _)) + .Times(0); + + shared_ptr removeBroadcast = + SharedRefBase::make>(); + EXPECT_CALL(*removeBroadcast, sendSubscriberBroadcast(configUid, configId, removeSub.id(), + alertRemove.id(), _, _)) + .Times(6) + .WillRepeatedly( + Invoke([&alertRemoveCount, &alertRemoveDims]( + int64_t, int64_t, int64_t, int64_t, const vector&, + const StatsDimensionsValueParcel& dimensionsValueParcel) { + alertRemoveCount++; + alertRemoveDims = dimensionsValueParcel; + return Status::ok(); + })); + + SubscriberReporter::getInstance().setBroadcastSubscriber(key, preserveSubId, preserveBroadcast); + SubscriberReporter::getInstance().setBroadcastSubscriber(key, replaceSubId, replaceBroadcast); + SubscriberReporter::getInstance().setBroadcastSubscriber(key, removeSubId, removeBroadcast); + + shared_ptr service = SharedRefBase::make(nullptr, nullptr); + sp processor = service->mProcessor; + uint64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(TEN_MINUTES) * 1000000LL; + int64_t bucketStartTimeNs = processor->mTimeBaseNs; + int64_t roundedBucketStartTimeNs = bucketStartTimeNs / NS_PER_SEC * NS_PER_SEC; + uint64_t bucket2StartTimeNs = bucketStartTimeNs + bucketSizeNs; + processor->OnConfigUpdated(bucketStartTimeNs, key, config); + + StatsDimensionsValueParcel wlUid1 = + CreateAttributionUidDimensionsValueParcel(util::WAKELOCK_STATE_CHANGED, app1Uid); + StatsDimensionsValueParcel wlUid2 = + CreateAttributionUidDimensionsValueParcel(util::WAKELOCK_STATE_CHANGED, app2Uid); + StatsDimensionsValueParcel wlUid3 = + CreateAttributionUidDimensionsValueParcel(util::WAKELOCK_STATE_CHANGED, app3Uid); + StatsDimensionsValueParcel wlUid4 = + CreateAttributionUidDimensionsValueParcel(util::WAKELOCK_STATE_CHANGED, app4Uid); + + int64_t eventTimeNs = bucketStartTimeNs + 15 * NS_PER_SEC; + processor->OnLogEvent( + CreateAcquireWakelockEvent(eventTimeNs, attributionUids1, attributionTags1, "wl1") + .get(), + eventTimeNs); + EXPECT_EQ(alertPreserveCount, 0); + EXPECT_EQ(alertRemoveCount, 0); + + eventTimeNs = bucketStartTimeNs + 20 * NS_PER_SEC; + processor->OnLogEvent(CreateScreenStateChangedEvent( + eventTimeNs, android::view::DisplayStateEnum::DISPLAY_STATE_ON) + .get(), + eventTimeNs); + EXPECT_EQ(alertPreserveCount, 0); + EXPECT_EQ(alertRemoveCount, 0); + + // Uid 1 accumulates 15 seconds in bucket #1. + eventTimeNs = bucketStartTimeNs + 30 * NS_PER_SEC; + processor->OnLogEvent( + CreateReleaseWakelockEvent(eventTimeNs, attributionUids1, attributionTags1, "wl1") + .get(), + eventTimeNs); + EXPECT_EQ(alertPreserveCount, 0); + EXPECT_EQ(alertRemoveCount, 1); +// EXPECT_EQ(alertRemoveDims, wlUid1); + + // 20 seconds accumulated in bucket #1. + eventTimeNs = bucketStartTimeNs + 40 * NS_PER_SEC; + processor->OnLogEvent(CreateScreenStateChangedEvent( + eventTimeNs, android::view::DisplayStateEnum::DISPLAY_STATE_OFF) + .get(), + eventTimeNs); + EXPECT_EQ(alertPreserveCount, 0); + EXPECT_EQ(alertRemoveCount, 1); + + eventTimeNs = bucket2StartTimeNs + 2 * NS_PER_SEC; + processor->OnLogEvent( + CreateAcquireWakelockEvent(eventTimeNs, attributionUids4, attributionTags4, "wl4") + .get(), + eventTimeNs); + EXPECT_EQ(alertPreserveCount, 0); + EXPECT_EQ(alertRemoveCount, 1); + + eventTimeNs = bucket2StartTimeNs + 5 * NS_PER_SEC; + processor->OnLogEvent( + CreateAcquireWakelockEvent(eventTimeNs, attributionUids2, attributionTags2, "wl2") + .get(), + eventTimeNs); + EXPECT_EQ(alertPreserveCount, 0); + EXPECT_EQ(alertRemoveCount, 1); + + // Alarm for alert remove for uid 4. + eventTimeNs = bucket2StartTimeNs + 13 * NS_PER_SEC; + processor->OnLogEvent(CreateBatteryStateChangedEvent( + eventTimeNs, BatteryPluggedStateEnum::BATTERY_PLUGGED_USB) + .get(), + eventTimeNs); + EXPECT_EQ(alertPreserveCount, 0); + EXPECT_EQ(alertRemoveCount, 2); +// EXPECT_EQ(alertRemoveDims, wlUid4); + + // Uid3 will be pending at the update. + // Also acts as the alarm for alert remove for uid 2. + eventTimeNs = bucket2StartTimeNs + 30 * NS_PER_SEC; + processor->OnLogEvent( + CreateAcquireWakelockEvent(eventTimeNs, attributionUids3, attributionTags3, "wl3") + .get(), + eventTimeNs); + EXPECT_EQ(alertPreserveCount, 0); + EXPECT_EQ(alertRemoveCount, 3); +// EXPECT_EQ(alertRemoveDims, wlUid2); + + // Alarm for alert preserve for uid 4, enters 5 min refractory period. + eventTimeNs = bucket2StartTimeNs + 33 * NS_PER_SEC; + processor->OnLogEvent(CreateBatteryStateChangedEvent( + eventTimeNs, BatteryPluggedStateEnum::BATTERY_PLUGGED_USB) + .get(), + eventTimeNs); + EXPECT_EQ(alertPreserveCount, 1); +// EXPECT_EQ(alertPreserveDims, wlUid4); + EXPECT_EQ(alertRemoveCount, 3); + + // Uid 2 accumulates 32 seconds in partial bucket before the update. Alert preserve fires. + // Preserve enters 5 min refractory for uid 2. + // Alert remove fires again for uid 2 since the refractory has expired. + eventTimeNs = bucket2StartTimeNs + 37 * NS_PER_SEC; + processor->OnLogEvent( + CreateReleaseWakelockEvent(eventTimeNs, attributionUids2, attributionTags2, "wl2") + .get(), + eventTimeNs); + EXPECT_EQ(alertPreserveCount, 2); +// EXPECT_EQ(alertPreserveDims, wlUid2); + EXPECT_EQ(alertRemoveCount, 4); +// EXPECT_EQ(alertRemoveDims, wlUid2); + + // Alarm for alert remove for uid 3. + eventTimeNs = bucket2StartTimeNs + 41 * NS_PER_SEC; + processor->OnLogEvent(CreateBatteryStateChangedEvent( + eventTimeNs, BatteryPluggedStateEnum::BATTERY_PLUGGED_USB) + .get(), + eventTimeNs); + EXPECT_EQ(alertPreserveCount, 2); + EXPECT_EQ(alertRemoveCount, 5); +// EXPECT_EQ(alertRemoveDims, wlUid3); + + // Release wl for uid 4, has accumulated 41 seconds in partial bucket before update. + // Acts as alarm for uid3 of alert remove. + eventTimeNs = bucket2StartTimeNs + 43 * NS_PER_SEC; + processor->OnLogEvent( + CreateReleaseWakelockEvent(eventTimeNs, attributionUids4, attributionTags4, "wl4") + .get(), + eventTimeNs); + EXPECT_EQ(alertPreserveCount, 2); + EXPECT_EQ(alertRemoveCount, 6); +// EXPECT_EQ(alertRemoveDims, wlUid4); + + // Starts the timer for screen on. + eventTimeNs = bucket2StartTimeNs + 46 * NS_PER_SEC; + processor->OnLogEvent(CreateScreenStateChangedEvent( + eventTimeNs, android::view::DisplayStateEnum::DISPLAY_STATE_ON) + .get(), + eventTimeNs); + EXPECT_EQ(alertPreserveCount, 2); + EXPECT_EQ(alertRemoveCount, 6); + + // Do config update. + StatsdConfig newConfig; + newConfig.add_allowed_log_source("AID_ROOT"); + *newConfig.add_atom_matcher() = wakelockAcquireMatcher; + *newConfig.add_atom_matcher() = screenOffMatcher; + *newConfig.add_atom_matcher() = wakelockReleaseMatcher; + *newConfig.add_atom_matcher() = screenOnMatcher; + *newConfig.add_predicate() = screenOnPredicate; + *newConfig.add_predicate() = holdingWakelockPredicate; + *newConfig.add_duration_metric() = durationWakelock; + *newConfig.add_duration_metric() = durationScreen; + + alertReplace.set_refractory_period_secs(2); // Causes replacement. + // New alert on existing metric. Should get current full bucket, but not history of 1st bucket. + Alert alertNew = createAlert("AlertNew", durationWakelock.id(), /*buckets=*/2, + /*triggerSumGt=*/40 * NS_PER_SEC); + *newConfig.add_alert() = alertPreserve; + *newConfig.add_alert() = alertNew; + *newConfig.add_alert() = alertReplace; + + int newSubId = 4; + Subscription newSub = createSubscription("S4", Subscription::ALERT, alertNew.id()); + newSub.mutable_broadcast_subscriber_details()->set_subscriber_id(newSubId); + *newConfig.add_subscription() = newSub; + *newConfig.add_subscription() = replaceSub; + *newConfig.add_subscription() = preserveSub; + + int alertNewCount = 0; + StatsDimensionsValueParcel alertNewDims; + shared_ptr newBroadcast = + SharedRefBase::make>(); + EXPECT_CALL(*newBroadcast, + sendSubscriberBroadcast(configUid, configId, newSub.id(), alertNew.id(), _, _)) + .Times(3) + .WillRepeatedly( + Invoke([&alertNewCount, &alertNewDims]( + int64_t, int64_t, int64_t, int64_t, const vector&, + const StatsDimensionsValueParcel& dimensionsValueParcel) { + alertNewCount++; + alertNewDims = dimensionsValueParcel; + return Status::ok(); + })); + SubscriberReporter::getInstance().setBroadcastSubscriber(key, newSubId, newBroadcast); + + int64_t updateTimeNs = bucket2StartTimeNs + 50 * NS_PER_SEC; + processor->OnConfigUpdated(updateTimeNs, key, newConfig); + + // Alert preserve will set alarm after the refractory period, but alert new will set it for + // 8 seconds after this event. + // Alert new should fire for uid 4, since it has already accumulated 41s and should fire on the + // first event after the update. + eventTimeNs = bucket2StartTimeNs + 55 * NS_PER_SEC; + processor->OnLogEvent( + CreateAcquireWakelockEvent(eventTimeNs, attributionUids2, attributionTags2, "wl2") + .get(), + eventTimeNs); + EXPECT_EQ(alertPreserveCount, 2); + EXPECT_EQ(alertNewCount, 1); +// EXPECT_EQ(alertNewDims, wlUid4); + + eventTimeNs = bucket2StartTimeNs + 60 * NS_PER_SEC; + // Alert replace doesn't fire because it has lost history. + processor->OnLogEvent(CreateScreenStateChangedEvent( + eventTimeNs, android::view::DisplayStateEnum::DISPLAY_STATE_OFF) + .get(), + eventTimeNs); + EXPECT_EQ(alertPreserveCount, 2); + EXPECT_EQ(alertNewCount, 1); + + // Alert preserve has 15 seconds from 1st bucket, so alert should fire at bucket2Start + 80. + // Serves as alarm for alert new for uid2. + // Also serves as alarm for alert preserve for uid 3, which began at bucket2Start + 30. + eventTimeNs = bucket2StartTimeNs + 65 * NS_PER_SEC; + processor->OnLogEvent( + CreateAcquireWakelockEvent(eventTimeNs, attributionUids1, attributionTags1, "wl1") + .get(), + eventTimeNs); + EXPECT_EQ(alertPreserveCount, 3); +// EXPECT_EQ(alertPreserveDims, wlUid3); + EXPECT_EQ(alertNewCount, 2); +// EXPECT_EQ(alertNewDims, wlUid2); + + // Release wakelock for uid1, causing alert preserve to fire for uid1. + // Also serves as alarm for alert new for uid3. + eventTimeNs = bucket2StartTimeNs + 81 * NS_PER_SEC; + processor->OnLogEvent( + CreateReleaseWakelockEvent(eventTimeNs, attributionUids1, attributionTags1, "wl1") + .get(), + eventTimeNs); + EXPECT_EQ(alertPreserveCount, 4); +// EXPECT_EQ(alertPreserveDims, wlUid1); + EXPECT_EQ(alertNewCount, 3); +// EXPECT_EQ(alertNewDims, wlUid3); + + // Clear data so it doesn't stay on disk. + vector buffer; + processor->onDumpReport(key, bucket2StartTimeNs + 100 * NS_PER_SEC, true, true, ADB_DUMP, FAST, + &buffer); + SubscriberReporter::getInstance().unsetBroadcastSubscriber(key, preserveSubId); + SubscriberReporter::getInstance().unsetBroadcastSubscriber(key, replaceSubId); + SubscriberReporter::getInstance().unsetBroadcastSubscriber(key, removeSubId); + SubscriberReporter::getInstance().unsetBroadcastSubscriber(key, newSubId); +} + +TEST_F(ConfigUpdateE2eTest, TestAlarms) { + StatsdConfig config; + config.add_allowed_log_source("AID_ROOT"); + Alarm alarmPreserve = createAlarm("AlarmPreserve", /*offset*/ 5 * MS_PER_SEC, + /*period*/ TimeUnitToBucketSizeInMillis(ONE_MINUTE)); + Alarm alarmReplace = createAlarm("AlarmReplace", /*offset*/ 1, + /*period*/ TimeUnitToBucketSizeInMillis(FIVE_MINUTES)); + Alarm alarmRemove = createAlarm("AlarmRemove", /*offset*/ 1, + /*period*/ TimeUnitToBucketSizeInMillis(ONE_MINUTE)); + *config.add_alarm() = alarmReplace; + *config.add_alarm() = alarmPreserve; + *config.add_alarm() = alarmRemove; + + int preserveSubId = 1, replaceSubId = 2, removeSubId = 3; + Subscription preserveSub = createSubscription("S1", Subscription::ALARM, alarmPreserve.id()); + preserveSub.mutable_broadcast_subscriber_details()->set_subscriber_id(preserveSubId); + Subscription replaceSub = createSubscription("S2", Subscription::ALARM, alarmReplace.id()); + replaceSub.mutable_broadcast_subscriber_details()->set_subscriber_id(replaceSubId); + Subscription removeSub = createSubscription("S3", Subscription::ALARM, alarmRemove.id()); + removeSub.mutable_broadcast_subscriber_details()->set_subscriber_id(removeSubId); + *config.add_subscription() = preserveSub; + *config.add_subscription() = removeSub; + *config.add_subscription() = replaceSub; + + int64_t configUid = 123, configId = 987; + ConfigKey key(configUid, configId); + + int alarmPreserveCount = 0, alarmReplaceCount = 0, alarmRemoveCount = 0; + + // The binder calls here will happen synchronously because they are in-process. + shared_ptr preserveBroadcast = + SharedRefBase::make>(); + EXPECT_CALL(*preserveBroadcast, sendSubscriberBroadcast(configUid, configId, preserveSub.id(), + alarmPreserve.id(), _, _)) + .Times(4) + .WillRepeatedly([&alarmPreserveCount](int64_t, int64_t, int64_t, int64_t, + const vector&, + const StatsDimensionsValueParcel&) { + alarmPreserveCount++; + return Status::ok(); + }); + + shared_ptr replaceBroadcast = + SharedRefBase::make>(); + EXPECT_CALL(*replaceBroadcast, sendSubscriberBroadcast(configUid, configId, replaceSub.id(), + alarmReplace.id(), _, _)) + .Times(2) + .WillRepeatedly([&alarmReplaceCount](int64_t, int64_t, int64_t, int64_t, + const vector&, + const StatsDimensionsValueParcel&) { + alarmReplaceCount++; + return Status::ok(); + }); + + shared_ptr removeBroadcast = + SharedRefBase::make>(); + EXPECT_CALL(*removeBroadcast, sendSubscriberBroadcast(configUid, configId, removeSub.id(), + alarmRemove.id(), _, _)) + .Times(1) + .WillRepeatedly([&alarmRemoveCount](int64_t, int64_t, int64_t, int64_t, + const vector&, + const StatsDimensionsValueParcel&) { + alarmRemoveCount++; + return Status::ok(); + }); + + SubscriberReporter::getInstance().setBroadcastSubscriber(key, preserveSubId, preserveBroadcast); + SubscriberReporter::getInstance().setBroadcastSubscriber(key, replaceSubId, replaceBroadcast); + SubscriberReporter::getInstance().setBroadcastSubscriber(key, removeSubId, removeBroadcast); + + int64_t startTimeSec = 10; + sp processor = CreateStatsLogProcessor( + startTimeSec * NS_PER_SEC, startTimeSec * NS_PER_SEC, config, key); + + sp alarmMonitor = processor->getPeriodicAlarmMonitor(); + // First alarm is for alarm preserve's offset of 5 seconds. + EXPECT_EQ(alarmMonitor->getRegisteredAlarmTimeSec(), startTimeSec + 5); + + // Alarm fired at 5. AlarmPreserve should fire. + int32_t alarmFiredTimestampSec = startTimeSec + 5; + auto alarmSet = alarmMonitor->popSoonerThan(static_cast(alarmFiredTimestampSec)); + processor->onPeriodicAlarmFired(alarmFiredTimestampSec * NS_PER_SEC, alarmSet); + EXPECT_EQ(alarmPreserveCount, 1); + EXPECT_EQ(alarmReplaceCount, 0); + EXPECT_EQ(alarmRemoveCount, 0); + EXPECT_EQ(alarmMonitor->getRegisteredAlarmTimeSec(), startTimeSec + 60); + + // Alarm fired at 75. AlarmPreserve and AlarmRemove should fire. + alarmFiredTimestampSec = startTimeSec + 75; + alarmSet = alarmMonitor->popSoonerThan(static_cast(alarmFiredTimestampSec)); + processor->onPeriodicAlarmFired(alarmFiredTimestampSec * NS_PER_SEC, alarmSet); + EXPECT_EQ(alarmPreserveCount, 2); + EXPECT_EQ(alarmReplaceCount, 0); + EXPECT_EQ(alarmRemoveCount, 1); + EXPECT_EQ(alarmMonitor->getRegisteredAlarmTimeSec(), startTimeSec + 120); + + // Do config update. + StatsdConfig newConfig; + newConfig.add_allowed_log_source("AID_ROOT"); + + // Change alarm replace's definition. + alarmReplace.set_period_millis(TimeUnitToBucketSizeInMillis(ONE_MINUTE)); + Alarm alarmNew = createAlarm("AlarmNew", /*offset*/ 1, + /*period*/ TimeUnitToBucketSizeInMillis(FIVE_MINUTES)); + *newConfig.add_alarm() = alarmNew; + *newConfig.add_alarm() = alarmPreserve; + *newConfig.add_alarm() = alarmReplace; + + int newSubId = 4; + Subscription newSub = createSubscription("S4", Subscription::ALARM, alarmNew.id()); + newSub.mutable_broadcast_subscriber_details()->set_subscriber_id(newSubId); + *newConfig.add_subscription() = newSub; + *newConfig.add_subscription() = replaceSub; + *newConfig.add_subscription() = preserveSub; + + int alarmNewCount = 0; + shared_ptr newBroadcast = + SharedRefBase::make>(); + EXPECT_CALL(*newBroadcast, + sendSubscriberBroadcast(configUid, configId, newSub.id(), alarmNew.id(), _, _)) + .Times(1) + .WillRepeatedly([&alarmNewCount](int64_t, int64_t, int64_t, int64_t, + const vector&, + const StatsDimensionsValueParcel&) { + alarmNewCount++; + return Status::ok(); + }); + SubscriberReporter::getInstance().setBroadcastSubscriber(key, newSubId, newBroadcast); + + processor->OnConfigUpdated((startTimeSec + 90) * NS_PER_SEC, key, newConfig); + // After the update, the alarm time should remain unchanged since alarm replace now fires every + // minute with no offset. + EXPECT_EQ(alarmMonitor->getRegisteredAlarmTimeSec(), startTimeSec + 120); + + // Alarm fired at 120. AlermReplace should fire. + alarmFiredTimestampSec = startTimeSec + 120; + alarmSet = alarmMonitor->popSoonerThan(static_cast(alarmFiredTimestampSec)); + processor->onPeriodicAlarmFired(alarmFiredTimestampSec * NS_PER_SEC, alarmSet); + EXPECT_EQ(alarmPreserveCount, 2); + EXPECT_EQ(alarmReplaceCount, 1); + EXPECT_EQ(alarmNewCount, 0); + EXPECT_EQ(alarmMonitor->getRegisteredAlarmTimeSec(), startTimeSec + 125); + + // Alarm fired at 130. AlarmPreserve should fire. + alarmFiredTimestampSec = startTimeSec + 130; + alarmSet = alarmMonitor->popSoonerThan(static_cast(alarmFiredTimestampSec)); + processor->onPeriodicAlarmFired(alarmFiredTimestampSec * NS_PER_SEC, alarmSet); + EXPECT_EQ(alarmPreserveCount, 3); + EXPECT_EQ(alarmReplaceCount, 1); + EXPECT_EQ(alarmNewCount, 0); + EXPECT_EQ(alarmMonitor->getRegisteredAlarmTimeSec(), startTimeSec + 180); + + // Alarm fired late at 310. All alerms should fire. + alarmFiredTimestampSec = startTimeSec + 310; + alarmSet = alarmMonitor->popSoonerThan(static_cast(alarmFiredTimestampSec)); + processor->onPeriodicAlarmFired(alarmFiredTimestampSec * NS_PER_SEC, alarmSet); + EXPECT_EQ(alarmPreserveCount, 4); + EXPECT_EQ(alarmReplaceCount, 2); + EXPECT_EQ(alarmNewCount, 1); + EXPECT_EQ(alarmMonitor->getRegisteredAlarmTimeSec(), startTimeSec + 360); + + // Clear subscribers + SubscriberReporter::getInstance().unsetBroadcastSubscriber(key, preserveSubId); + SubscriberReporter::getInstance().unsetBroadcastSubscriber(key, replaceSubId); + SubscriberReporter::getInstance().unsetBroadcastSubscriber(key, removeSubId); + SubscriberReporter::getInstance().unsetBroadcastSubscriber(key, newSubId); +} + +TEST_F(ConfigUpdateE2eTest, TestNewDurationExistingWhat) { + StatsdConfig config; + config.add_allowed_log_source("AID_ROOT"); + *config.add_atom_matcher() = CreateAcquireWakelockAtomMatcher(); + *config.add_atom_matcher() = CreateReleaseWakelockAtomMatcher(); + + Predicate holdingWakelockPredicate = CreateHoldingWakelockPredicate(); + *config.add_predicate() = holdingWakelockPredicate; + + ConfigKey key(123, 987); + uint64_t bucketStartTimeNs = 10000000000; // 0:10 + uint64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(FIVE_MINUTES) * 1000000LL; + sp processor = + CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, key); + + int app1Uid = 123; + vector attributionUids1 = {app1Uid}; + vector attributionTags1 = {"App1"}; + // Create a wakelock acquire, causing the condition to be true. + unique_ptr event = CreateAcquireWakelockEvent(bucketStartTimeNs + 10 * NS_PER_SEC, + attributionUids1, attributionTags1, + "wl1"); // 0:10 + processor->OnLogEvent(event.get()); + + // Add metric. + DurationMetric* durationMetric = config.add_duration_metric(); + durationMetric->set_id(StringToId("WakelockDuration")); + durationMetric->set_what(holdingWakelockPredicate.id()); + durationMetric->set_aggregation_type(DurationMetric::SUM); + durationMetric->set_bucket(FIVE_MINUTES); + + uint64_t updateTimeNs = bucketStartTimeNs + 60 * NS_PER_SEC; // 1:00 + processor->OnConfigUpdated(updateTimeNs, key, config); + + event = CreateReleaseWakelockEvent(bucketStartTimeNs + 80 * NS_PER_SEC, attributionUids1, + attributionTags1, + "wl1"); // 1:20 + processor->OnLogEvent(event.get()); + uint64_t dumpTimeNs = bucketStartTimeNs + 90 * NS_PER_SEC; // 1:30 + ConfigMetricsReportList reports; + vector buffer; + processor->onDumpReport(key, dumpTimeNs, true, true, ADB_DUMP, FAST, &buffer); + EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size())); + backfillDimensionPath(&reports); + backfillStringInReport(&reports); + backfillStartEndTimestamp(&reports); + ASSERT_EQ(reports.reports_size(), 1); + ASSERT_EQ(reports.reports(0).metrics_size(), 1); + EXPECT_TRUE(reports.reports(0).metrics(0).has_duration_metrics()); + + StatsLogReport::DurationMetricDataWrapper metricData; + sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).duration_metrics(), &metricData); + ASSERT_EQ(metricData.data_size(), 1); + DurationMetricData data = metricData.data(0); + ASSERT_EQ(data.bucket_info_size(), 1); + + DurationBucketInfo bucketInfo = data.bucket_info(0); + EXPECT_EQ(bucketInfo.start_bucket_elapsed_nanos(), updateTimeNs); + EXPECT_EQ(bucketInfo.end_bucket_elapsed_nanos(), dumpTimeNs); + EXPECT_EQ(bucketInfo.duration_nanos(), 20 * NS_PER_SEC); +} + +TEST_F(ConfigUpdateE2eTest, TestNewDurationExistingWhatSlicedCondition) { + StatsdConfig config; + config.add_allowed_log_source("AID_ROOT"); + *config.add_atom_matcher() = CreateAcquireWakelockAtomMatcher(); + *config.add_atom_matcher() = CreateReleaseWakelockAtomMatcher(); + *config.add_atom_matcher() = CreateMoveToBackgroundAtomMatcher(); + *config.add_atom_matcher() = CreateMoveToForegroundAtomMatcher(); + + Predicate holdingWakelockPredicate = CreateHoldingWakelockPredicate(); + // The predicate is dimensioning by first attribution node by uid. + *holdingWakelockPredicate.mutable_simple_predicate()->mutable_dimensions() = + CreateAttributionUidDimensions(util::WAKELOCK_STATE_CHANGED, {Position::FIRST}); + *config.add_predicate() = holdingWakelockPredicate; + + Predicate isInBackgroundPredicate = CreateIsInBackgroundPredicate(); + *isInBackgroundPredicate.mutable_simple_predicate()->mutable_dimensions() = + CreateDimensions(util::ACTIVITY_FOREGROUND_STATE_CHANGED, {1 /*uid*/}); + *config.add_predicate() = isInBackgroundPredicate; + + ConfigKey key(123, 987); + uint64_t bucketStartTimeNs = 10000000000; // 0:10 + uint64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(FIVE_MINUTES) * 1000000LL; + sp processor = + CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, key); + + int app1Uid = 123, app2Uid = 456; + vector attributionUids1 = {app1Uid}; + vector attributionTags1 = {"App1"}; + vector attributionUids2 = {app2Uid}; + vector attributionTags2 = {"App2"}; + unique_ptr event = CreateAcquireWakelockEvent(bucketStartTimeNs + 10 * NS_PER_SEC, + attributionUids1, attributionTags1, + "wl1"); // 0:10 + processor->OnLogEvent(event.get()); + event = CreateMoveToBackgroundEvent(bucketStartTimeNs + 22 * NS_PER_SEC, app1Uid); // 0:22 + processor->OnLogEvent(event.get()); + event = CreateAcquireWakelockEvent(bucketStartTimeNs + 35 * NS_PER_SEC, attributionUids2, + attributionTags2, + "wl1"); // 0:35 + processor->OnLogEvent(event.get()); + + // Add metric. + DurationMetric* durationMetric = config.add_duration_metric(); + durationMetric->set_id(StringToId("WakelockDuration")); + durationMetric->set_what(holdingWakelockPredicate.id()); + durationMetric->set_condition(isInBackgroundPredicate.id()); + durationMetric->set_aggregation_type(DurationMetric::SUM); + // The metric is dimensioning by first attribution node and only by uid. + *durationMetric->mutable_dimensions_in_what() = + CreateAttributionUidDimensions(util::WAKELOCK_STATE_CHANGED, {Position::FIRST}); + durationMetric->set_bucket(FIVE_MINUTES); + // Links between wakelock state atom and condition of app is in background. + auto links = durationMetric->add_links(); + links->set_condition(isInBackgroundPredicate.id()); + *links->mutable_fields_in_what() = + CreateAttributionUidDimensions(util::WAKELOCK_STATE_CHANGED, {Position::FIRST}); + *links->mutable_fields_in_condition() = + CreateDimensions(util::ACTIVITY_FOREGROUND_STATE_CHANGED, {1 /*uid*/}); + + uint64_t updateTimeNs = bucketStartTimeNs + 60 * NS_PER_SEC; // 1:00 + processor->OnConfigUpdated(updateTimeNs, key, config); + + event = CreateMoveToBackgroundEvent(bucketStartTimeNs + 73 * NS_PER_SEC, app2Uid); // 1:13 + processor->OnLogEvent(event.get()); + event = CreateReleaseWakelockEvent(bucketStartTimeNs + 84 * NS_PER_SEC, attributionUids1, + attributionTags1, "wl1"); // 1:24 + processor->OnLogEvent(event.get()); + + uint64_t dumpTimeNs = bucketStartTimeNs + 90 * NS_PER_SEC; // 1:30 + ConfigMetricsReportList reports; + vector buffer; + processor->onDumpReport(key, dumpTimeNs, true, true, ADB_DUMP, FAST, &buffer); + EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size())); + backfillDimensionPath(&reports); + backfillStringInReport(&reports); + backfillStartEndTimestamp(&reports); + ASSERT_EQ(reports.reports_size(), 1); + ASSERT_EQ(reports.reports(0).metrics_size(), 1); + EXPECT_TRUE(reports.reports(0).metrics(0).has_duration_metrics()); + + StatsLogReport::DurationMetricDataWrapper metricData; + sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).duration_metrics(), &metricData); + ASSERT_EQ(metricData.data_size(), 2); + + DurationMetricData data = metricData.data(0); + ValidateAttributionUidDimension(data.dimensions_in_what(), util::WAKELOCK_STATE_CHANGED, + app1Uid); + ASSERT_EQ(data.bucket_info_size(), 1); + DurationBucketInfo bucketInfo = data.bucket_info(0); + EXPECT_EQ(bucketInfo.duration_nanos(), 24 * NS_PER_SEC); + + data = metricData.data(1); + ValidateAttributionUidDimension(data.dimensions_in_what(), util::WAKELOCK_STATE_CHANGED, + app2Uid); + ASSERT_EQ(data.bucket_info_size(), 1); + bucketInfo = data.bucket_info(0); + EXPECT_EQ(bucketInfo.duration_nanos(), 17 * NS_PER_SEC); +} + +TEST_F(ConfigUpdateE2eTest, TestNewDurationExistingWhatSlicedState) { + StatsdConfig config; + config.add_allowed_log_source("AID_ROOT"); + *config.add_atom_matcher() = CreateAcquireWakelockAtomMatcher(); + *config.add_atom_matcher() = CreateReleaseWakelockAtomMatcher(); + + Predicate holdingWakelockPredicate = CreateHoldingWakelockPredicate(); + // The predicate is dimensioning by first attribution node by uid. + *holdingWakelockPredicate.mutable_simple_predicate()->mutable_dimensions() = + CreateAttributionUidDimensions(util::WAKELOCK_STATE_CHANGED, {Position::FIRST}); + *config.add_predicate() = holdingWakelockPredicate; + + auto uidProcessState = CreateUidProcessState(); + *config.add_state() = uidProcessState; + + // Count metric. We don't care about this one. Only use it so the StateTracker gets persisted. + CountMetric* countMetric = config.add_count_metric(); + countMetric->set_id(StringToId("Tmp")); + countMetric->set_what(config.atom_matcher(0).id()); + countMetric->add_slice_by_state(uidProcessState.id()); + // The metric is dimensioning by first attribution node and only by uid. + *countMetric->mutable_dimensions_in_what() = + CreateAttributionUidDimensions(util::WAKELOCK_STATE_CHANGED, {Position::FIRST}); + countMetric->set_bucket(FIVE_MINUTES); + auto stateLink = countMetric->add_state_link(); + stateLink->set_state_atom_id(util::UID_PROCESS_STATE_CHANGED); + *stateLink->mutable_fields_in_what() = + CreateAttributionUidDimensions(util::WAKELOCK_STATE_CHANGED, {Position::FIRST}); + *stateLink->mutable_fields_in_state() = + CreateDimensions(util::UID_PROCESS_STATE_CHANGED, {1 /*uid*/}); + config.add_no_report_metric(countMetric->id()); + + ConfigKey key(123, 987); + uint64_t bucketStartTimeNs = 10000000000; // 0:10 + uint64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(FIVE_MINUTES) * 1000000LL; + sp processor = + CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, key); + + int app1Uid = 123, app2Uid = 456; + vector attributionUids1 = {app1Uid}; + vector attributionTags1 = {"App1"}; + vector attributionUids2 = {app2Uid}; + vector attributionTags2 = {"App2"}; + unique_ptr event = CreateUidProcessStateChangedEvent( + bucketStartTimeNs + 10 * NS_PER_SEC, app1Uid, + android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_FOREGROUND); // 0:10 + processor->OnLogEvent(event.get()); + event = CreateAcquireWakelockEvent(bucketStartTimeNs + 22 * NS_PER_SEC, attributionUids1, + attributionTags1, + "wl1"); // 0:22 + processor->OnLogEvent(event.get()); + event = CreateUidProcessStateChangedEvent( + bucketStartTimeNs + 30 * NS_PER_SEC, app2Uid, + android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_FOREGROUND); // 0:30 + processor->OnLogEvent(event.get()); + + // Add metric. + DurationMetric* durationMetric = config.add_duration_metric(); + durationMetric->set_id(StringToId("WakelockDuration")); + durationMetric->set_what(holdingWakelockPredicate.id()); + durationMetric->add_slice_by_state(uidProcessState.id()); + durationMetric->set_aggregation_type(DurationMetric::SUM); + // The metric is dimensioning by first attribution node and only by uid. + *durationMetric->mutable_dimensions_in_what() = + CreateAttributionUidDimensions(util::WAKELOCK_STATE_CHANGED, {Position::FIRST}); + durationMetric->set_bucket(FIVE_MINUTES); + // Links between wakelock state atom and condition of app is in background. + stateLink = durationMetric->add_state_link(); + stateLink->set_state_atom_id(util::UID_PROCESS_STATE_CHANGED); + *stateLink->mutable_fields_in_what() = + CreateAttributionUidDimensions(util::WAKELOCK_STATE_CHANGED, {Position::FIRST}); + *stateLink->mutable_fields_in_state() = + CreateDimensions(util::UID_PROCESS_STATE_CHANGED, {1 /*uid*/}); + + uint64_t updateTimeNs = bucketStartTimeNs + 60 * NS_PER_SEC; // 1:00 + processor->OnConfigUpdated(updateTimeNs, key, config); + + event = CreateAcquireWakelockEvent(bucketStartTimeNs + 72 * NS_PER_SEC, attributionUids2, + attributionTags2, + "wl1"); // 1:13 + processor->OnLogEvent(event.get()); + event = CreateUidProcessStateChangedEvent( + bucketStartTimeNs + 75 * NS_PER_SEC, app1Uid, + android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_BACKGROUND); // 1:15 + processor->OnLogEvent(event.get()); + event = CreateReleaseWakelockEvent(bucketStartTimeNs + 84 * NS_PER_SEC, attributionUids1, + attributionTags1, "wl1"); // 1:24 + processor->OnLogEvent(event.get()); + + uint64_t dumpTimeNs = bucketStartTimeNs + 90 * NS_PER_SEC; // 1:30 + ConfigMetricsReportList reports; + vector buffer; + processor->onDumpReport(key, dumpTimeNs, true, true, ADB_DUMP, FAST, &buffer); + EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size())); + backfillDimensionPath(&reports); + backfillStringInReport(&reports); + backfillStartEndTimestamp(&reports); + ASSERT_EQ(reports.reports_size(), 1); + ASSERT_EQ(reports.reports(0).metrics_size(), 1); + EXPECT_TRUE(reports.reports(0).metrics(0).has_duration_metrics()); + + StatsLogReport::DurationMetricDataWrapper metricData; + sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).duration_metrics(), &metricData); + ASSERT_EQ(metricData.data_size(), 3); + + DurationMetricData data = metricData.data(0); + ValidateAttributionUidDimension(data.dimensions_in_what(), util::WAKELOCK_STATE_CHANGED, + app1Uid); + ValidateStateValue(data.slice_by_state(), util::UID_PROCESS_STATE_CHANGED, + android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_FOREGROUND); + ASSERT_EQ(data.bucket_info_size(), 1); + DurationBucketInfo bucketInfo = data.bucket_info(0); + EXPECT_EQ(bucketInfo.duration_nanos(), 15 * NS_PER_SEC); + + data = metricData.data(1); + ValidateAttributionUidDimension(data.dimensions_in_what(), util::WAKELOCK_STATE_CHANGED, + app1Uid); + ValidateStateValue(data.slice_by_state(), util::UID_PROCESS_STATE_CHANGED, + android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_BACKGROUND); + ASSERT_EQ(data.bucket_info_size(), 1); + bucketInfo = data.bucket_info(0); + EXPECT_EQ(bucketInfo.duration_nanos(), 9 * NS_PER_SEC); + + data = metricData.data(2); + ValidateAttributionUidDimension(data.dimensions_in_what(), util::WAKELOCK_STATE_CHANGED, + app2Uid); + ValidateStateValue(data.slice_by_state(), util::UID_PROCESS_STATE_CHANGED, + android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_FOREGROUND); + ASSERT_EQ(data.bucket_info_size(), 1); + bucketInfo = data.bucket_info(0); + EXPECT_EQ(bucketInfo.duration_nanos(), 18 * NS_PER_SEC); +} + +#else +GTEST_LOG_(INFO) << "This test does nothing.\n"; +#endif + +} // namespace statsd +} // namespace os +} // namespace android diff --git a/statsd/tests/e2e/CountMetric_e2e_test.cpp b/statsd/tests/e2e/CountMetric_e2e_test.cpp new file mode 100644 index 00000000..04eb4008 --- /dev/null +++ b/statsd/tests/e2e/CountMetric_e2e_test.cpp @@ -0,0 +1,901 @@ +/* + * Copyright (C) 2019, 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/state/StateManager.h" +#include "src/state/StateTracker.h" +#include "tests/statsd_test_util.h" + +namespace android { +namespace os { +namespace statsd { + +#ifdef __ANDROID__ + +/** + * Tests the initial condition and condition after the first log events for + * count metrics with either a combination condition or simple condition. + * + * Metrics should be initialized with condition kUnknown (given that the + * predicate is using the default InitialValue of UNKNOWN). The condition should + * be updated to either kFalse or kTrue if a condition event is logged for all + * children conditions. + */ +TEST(CountMetricE2eTest, TestInitialConditionChanges) { + // Initialize config. + StatsdConfig config; + config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root. + config.add_default_pull_packages("AID_ROOT"); // Fake puller is registered with root. + + auto syncStartMatcher = CreateSyncStartAtomMatcher(); + *config.add_atom_matcher() = syncStartMatcher; + *config.add_atom_matcher() = CreateScreenTurnedOnAtomMatcher(); + *config.add_atom_matcher() = CreateScreenTurnedOffAtomMatcher(); + *config.add_atom_matcher() = CreateBatteryStateNoneMatcher(); + *config.add_atom_matcher() = CreateBatteryStateUsbMatcher(); + + auto screenOnPredicate = CreateScreenIsOnPredicate(); + *config.add_predicate() = screenOnPredicate; + + auto deviceUnpluggedPredicate = CreateDeviceUnpluggedPredicate(); + *config.add_predicate() = deviceUnpluggedPredicate; + + auto screenOnOnBatteryPredicate = config.add_predicate(); + screenOnOnBatteryPredicate->set_id(StringToId("screenOnOnBatteryPredicate")); + screenOnOnBatteryPredicate->mutable_combination()->set_operation(LogicalOperation::AND); + addPredicateToPredicateCombination(screenOnPredicate, screenOnOnBatteryPredicate); + addPredicateToPredicateCombination(deviceUnpluggedPredicate, screenOnOnBatteryPredicate); + + // CountSyncStartWhileScreenOnOnBattery (CombinationCondition) + CountMetric* countMetric1 = config.add_count_metric(); + countMetric1->set_id(StringToId("CountSyncStartWhileScreenOnOnBattery")); + countMetric1->set_what(syncStartMatcher.id()); + countMetric1->set_condition(screenOnOnBatteryPredicate->id()); + countMetric1->set_bucket(FIVE_MINUTES); + + // CountSyncStartWhileOnBattery (SimpleCondition) + CountMetric* countMetric2 = config.add_count_metric(); + countMetric2->set_id(StringToId("CountSyncStartWhileOnBatterySliceScreen")); + countMetric2->set_what(syncStartMatcher.id()); + countMetric2->set_condition(deviceUnpluggedPredicate.id()); + countMetric2->set_bucket(FIVE_MINUTES); + + const uint64_t bucketStartTimeNs = 10000000000; // 0:10 + const uint64_t bucketSizeNs = + TimeUnitToBucketSizeInMillis(config.count_metric(0).bucket()) * 1000000LL; + int uid = 12345; + int64_t cfgId = 98765; + ConfigKey cfgKey(uid, cfgId); + auto processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey); + + EXPECT_EQ(processor->mMetricsManagers.size(), 1u); + sp metricsManager = processor->mMetricsManagers.begin()->second; + EXPECT_TRUE(metricsManager->isConfigValid()); + EXPECT_EQ(2, metricsManager->mAllMetricProducers.size()); + + sp metricProducer1 = metricsManager->mAllMetricProducers[0]; + sp metricProducer2 = metricsManager->mAllMetricProducers[1]; + + EXPECT_EQ(ConditionState::kUnknown, metricProducer1->mCondition); + EXPECT_EQ(ConditionState::kUnknown, metricProducer2->mCondition); + + auto screenOnEvent = + CreateScreenStateChangedEvent(bucketStartTimeNs + 30, android::view::DISPLAY_STATE_ON); + processor->OnLogEvent(screenOnEvent.get()); + EXPECT_EQ(ConditionState::kUnknown, metricProducer1->mCondition); + EXPECT_EQ(ConditionState::kUnknown, metricProducer2->mCondition); + + auto pluggedUsbEvent = CreateBatteryStateChangedEvent( + bucketStartTimeNs + 50, BatteryPluggedStateEnum::BATTERY_PLUGGED_USB); + processor->OnLogEvent(pluggedUsbEvent.get()); + EXPECT_EQ(ConditionState::kFalse, metricProducer1->mCondition); + EXPECT_EQ(ConditionState::kFalse, metricProducer2->mCondition); + + auto pluggedNoneEvent = CreateBatteryStateChangedEvent( + bucketStartTimeNs + 70, BatteryPluggedStateEnum::BATTERY_PLUGGED_NONE); + processor->OnLogEvent(pluggedNoneEvent.get()); + EXPECT_EQ(ConditionState::kTrue, metricProducer1->mCondition); + EXPECT_EQ(ConditionState::kTrue, metricProducer2->mCondition); +} + +/** +* Test a count metric that has one slice_by_state with no primary fields. +* +* Once the CountMetricProducer is initialized, it has one atom id in +* mSlicedStateAtoms and no entries in mStateGroupMap. + +* One StateTracker tracks the state atom, and it has one listener which is the +* CountMetricProducer that was initialized. +*/ +TEST(CountMetricE2eTest, TestSlicedState) { + // Initialize config. + StatsdConfig config; + config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root. + + auto syncStartMatcher = CreateSyncStartAtomMatcher(); + *config.add_atom_matcher() = syncStartMatcher; + + auto state = CreateScreenState(); + *config.add_state() = state; + + // Create count metric that slices by screen state. + int64_t metricId = 123456; + auto countMetric = config.add_count_metric(); + countMetric->set_id(metricId); + countMetric->set_what(syncStartMatcher.id()); + countMetric->set_bucket(TimeUnit::FIVE_MINUTES); + countMetric->add_slice_by_state(state.id()); + + // Initialize StatsLogProcessor. + const uint64_t bucketStartTimeNs = 10000000000; // 0:10 + const uint64_t bucketSizeNs = + TimeUnitToBucketSizeInMillis(config.count_metric(0).bucket()) * 1000000LL; + int uid = 12345; + int64_t cfgId = 98765; + ConfigKey cfgKey(uid, cfgId); + auto processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey); + + // Check that CountMetricProducer was initialized correctly. + ASSERT_EQ(processor->mMetricsManagers.size(), 1u); + sp metricsManager = processor->mMetricsManagers.begin()->second; + EXPECT_TRUE(metricsManager->isConfigValid()); + ASSERT_EQ(metricsManager->mAllMetricProducers.size(), 1); + sp metricProducer = metricsManager->mAllMetricProducers[0]; + ASSERT_EQ(metricProducer->mSlicedStateAtoms.size(), 1); + EXPECT_EQ(metricProducer->mSlicedStateAtoms.at(0), SCREEN_STATE_ATOM_ID); + ASSERT_EQ(metricProducer->mStateGroupMap.size(), 0); + + // Check that StateTrackers were initialized correctly. + EXPECT_EQ(1, StateManager::getInstance().getStateTrackersCount()); + EXPECT_EQ(1, StateManager::getInstance().getListenersCount(SCREEN_STATE_ATOM_ID)); + + /* + bucket #1 bucket #2 + | 1 2 3 4 5 6 7 8 9 10 (minutes) + |-----------------------------|-----------------------------|-- + x x x x x x (syncStartEvents) + | | (ScreenIsOnEvent) + | | (ScreenIsOffEvent) + | (ScreenDozeEvent) + */ + // Initialize log events - first bucket. + std::vector attributionUids1 = {123}; + std::vector attributionTags1 = {"App1"}; + + std::vector> events; + events.push_back(CreateScreenStateChangedEvent( + bucketStartTimeNs + 50 * NS_PER_SEC, + android::view::DisplayStateEnum::DISPLAY_STATE_ON)); // 1:00 + events.push_back(CreateSyncStartEvent(bucketStartTimeNs + 75 * NS_PER_SEC, attributionUids1, + attributionTags1, "sync_name")); // 1:25 + events.push_back(CreateScreenStateChangedEvent( + bucketStartTimeNs + 150 * NS_PER_SEC, + android::view::DisplayStateEnum::DISPLAY_STATE_OFF)); // 2:40 + events.push_back(CreateScreenStateChangedEvent( + bucketStartTimeNs + 200 * NS_PER_SEC, + android::view::DisplayStateEnum::DISPLAY_STATE_OFF)); // 3:30 + events.push_back(CreateSyncStartEvent(bucketStartTimeNs + 250 * NS_PER_SEC, attributionUids1, + attributionTags1, "sync_name")); // 4:20 + + // Initialize log events - second bucket. + events.push_back(CreateSyncStartEvent(bucketStartTimeNs + 350 * NS_PER_SEC, attributionUids1, + attributionTags1, "sync_name")); // 6:00 + events.push_back(CreateSyncStartEvent(bucketStartTimeNs + 400 * NS_PER_SEC, attributionUids1, + attributionTags1, "sync_name")); // 6:50 + events.push_back(CreateScreenStateChangedEvent( + bucketStartTimeNs + 450 * NS_PER_SEC, + android::view::DisplayStateEnum::DISPLAY_STATE_ON)); // 7:40 + events.push_back(CreateSyncStartEvent(bucketStartTimeNs + 475 * NS_PER_SEC, attributionUids1, + attributionTags1, "sync_name")); // 8:05 + events.push_back(CreateScreenStateChangedEvent( + bucketStartTimeNs + 500 * NS_PER_SEC, + android::view::DisplayStateEnum::DISPLAY_STATE_UNKNOWN)); // 8:30 + events.push_back(CreateSyncStartEvent(bucketStartTimeNs + 520 * NS_PER_SEC, attributionUids1, + attributionTags1, "sync_name")); // 8:50 + + // Send log events to StatsLogProcessor. + for (auto& event : events) { + processor->OnLogEvent(event.get()); + } + + // Check dump report. + vector buffer; + ConfigMetricsReportList reports; + processor->onDumpReport(cfgKey, bucketStartTimeNs + bucketSizeNs * 2 + 1, false, true, ADB_DUMP, + FAST, &buffer); + ASSERT_GT(buffer.size(), 0); + EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size())); + backfillDimensionPath(&reports); + backfillStringInReport(&reports); + backfillStartEndTimestamp(&reports); + + ASSERT_EQ(1, reports.reports_size()); + ASSERT_EQ(1, reports.reports(0).metrics_size()); + EXPECT_TRUE(reports.reports(0).metrics(0).has_count_metrics()); + StatsLogReport::CountMetricDataWrapper countMetrics; + sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).count_metrics(), &countMetrics); + ASSERT_EQ(3, countMetrics.data_size()); + + // For each CountMetricData, check StateValue info is correct and buckets + // have correct counts. + auto data = countMetrics.data(0); + ASSERT_EQ(1, data.slice_by_state_size()); + EXPECT_EQ(SCREEN_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); + EXPECT_TRUE(data.slice_by_state(0).has_value()); + EXPECT_EQ(android::view::DisplayStateEnum::DISPLAY_STATE_UNKNOWN, + data.slice_by_state(0).value()); + ASSERT_EQ(1, data.bucket_info_size()); + EXPECT_EQ(1, data.bucket_info(0).count()); + + data = countMetrics.data(1); + ASSERT_EQ(1, data.slice_by_state_size()); + EXPECT_EQ(SCREEN_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); + EXPECT_TRUE(data.slice_by_state(0).has_value()); + EXPECT_EQ(android::view::DisplayStateEnum::DISPLAY_STATE_OFF, data.slice_by_state(0).value()); + ASSERT_EQ(2, data.bucket_info_size()); + EXPECT_EQ(1, data.bucket_info(0).count()); + EXPECT_EQ(2, data.bucket_info(1).count()); + + data = countMetrics.data(2); + ASSERT_EQ(1, data.slice_by_state_size()); + EXPECT_EQ(SCREEN_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); + EXPECT_TRUE(data.slice_by_state(0).has_value()); + EXPECT_EQ(android::view::DisplayStateEnum::DISPLAY_STATE_ON, data.slice_by_state(0).value()); + ASSERT_EQ(2, data.bucket_info_size()); + EXPECT_EQ(1, data.bucket_info(0).count()); + EXPECT_EQ(1, data.bucket_info(1).count()); +} + +/** + * Test a count metric that has one slice_by_state with a mapping and no + * primary fields. + * + * Once the CountMetricProducer is initialized, it has one atom id in + * mSlicedStateAtoms and has one entry per state value in mStateGroupMap. + * + * One StateTracker tracks the state atom, and it has one listener which is the + * CountMetricProducer that was initialized. + */ +TEST(CountMetricE2eTest, TestSlicedStateWithMap) { + // Initialize config. + StatsdConfig config; + config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root. + + auto syncStartMatcher = CreateSyncStartAtomMatcher(); + *config.add_atom_matcher() = syncStartMatcher; + + int64_t screenOnId = 4444; + int64_t screenOffId = 9876; + auto state = CreateScreenStateWithOnOffMap(screenOnId, screenOffId); + *config.add_state() = state; + + // Create count metric that slices by screen state with on/off map. + int64_t metricId = 123456; + auto countMetric = config.add_count_metric(); + countMetric->set_id(metricId); + countMetric->set_what(syncStartMatcher.id()); + countMetric->set_bucket(TimeUnit::FIVE_MINUTES); + countMetric->add_slice_by_state(state.id()); + + // Initialize StatsLogProcessor. + const uint64_t bucketStartTimeNs = 10000000000; // 0:10 + const uint64_t bucketSizeNs = + TimeUnitToBucketSizeInMillis(config.count_metric(0).bucket()) * 1000000LL; + int uid = 12345; + int64_t cfgId = 98765; + ConfigKey cfgKey(uid, cfgId); + auto processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey); + + // Check that StateTrackers were initialized correctly. + EXPECT_EQ(1, StateManager::getInstance().getStateTrackersCount()); + EXPECT_EQ(1, StateManager::getInstance().getListenersCount(SCREEN_STATE_ATOM_ID)); + + // Check that CountMetricProducer was initialized correctly. + ASSERT_EQ(processor->mMetricsManagers.size(), 1u); + sp metricsManager = processor->mMetricsManagers.begin()->second; + EXPECT_TRUE(metricsManager->isConfigValid()); + ASSERT_EQ(metricsManager->mAllMetricProducers.size(), 1); + sp metricProducer = metricsManager->mAllMetricProducers[0]; + ASSERT_EQ(metricProducer->mSlicedStateAtoms.size(), 1); + EXPECT_EQ(metricProducer->mSlicedStateAtoms.at(0), SCREEN_STATE_ATOM_ID); + ASSERT_EQ(metricProducer->mStateGroupMap.size(), 1); + + StateMap map = state.map(); + for (auto group : map.group()) { + for (auto value : group.value()) { + EXPECT_EQ(metricProducer->mStateGroupMap.at(SCREEN_STATE_ATOM_ID).at(value), + group.group_id()); + } + } + + /* + bucket #1 bucket #2 + | 1 2 3 4 5 6 7 8 9 10 (minutes) + |-----------------------------|-----------------------------|-- + x x x x x x x x x (syncStartEvents) + -----------------------------------------------------------SCREEN_OFF events + | | (ScreenStateOffEvent = 1) + | | (ScreenStateDozeEvent = 3) + | (ScreenStateDozeSuspendEvent = + 4) + -----------------------------------------------------------SCREEN_ON events + | | (ScreenStateOnEvent = 2) + | (ScreenStateVrEvent = 5) + | (ScreenStateOnSuspendEvent = 6) + */ + // Initialize log events - first bucket. + std::vector attributionUids1 = {123}; + std::vector attributionTags1 = {"App1"}; + + std::vector> events; + events.push_back(CreateSyncStartEvent(bucketStartTimeNs + 20 * NS_PER_SEC, attributionUids1, + attributionTags1, "sync_name")); // 0:30 + events.push_back(CreateScreenStateChangedEvent( + bucketStartTimeNs + 30 * NS_PER_SEC, + android::view::DisplayStateEnum::DISPLAY_STATE_DOZE)); // 0:40 + events.push_back(CreateSyncStartEvent(bucketStartTimeNs + 60 * NS_PER_SEC, attributionUids1, + attributionTags1, "sync_name")); // 1:10 + events.push_back(CreateScreenStateChangedEvent( + bucketStartTimeNs + 90 * NS_PER_SEC, + android::view::DisplayStateEnum::DISPLAY_STATE_OFF)); // 1:40 + events.push_back(CreateSyncStartEvent(bucketStartTimeNs + 120 * NS_PER_SEC, attributionUids1, + attributionTags1, "sync_name")); // 2:10 + events.push_back(CreateScreenStateChangedEvent( + bucketStartTimeNs + 150 * NS_PER_SEC, + android::view::DisplayStateEnum::DISPLAY_STATE_ON)); // 2:40 + events.push_back(CreateScreenStateChangedEvent( + bucketStartTimeNs + 180 * NS_PER_SEC, + android::view::DisplayStateEnum::DISPLAY_STATE_VR)); // 3:10 + events.push_back(CreateSyncStartEvent(bucketStartTimeNs + 200 * NS_PER_SEC, attributionUids1, + attributionTags1, "sync_name")); // 3:30 + events.push_back(CreateScreenStateChangedEvent( + bucketStartTimeNs + 210 * NS_PER_SEC, + android::view::DisplayStateEnum::DISPLAY_STATE_DOZE)); // 3:40 + events.push_back(CreateSyncStartEvent(bucketStartTimeNs + 250 * NS_PER_SEC, attributionUids1, + attributionTags1, "sync_name")); // 4:20 + events.push_back(CreateScreenStateChangedEvent( + bucketStartTimeNs + 280 * NS_PER_SEC, + android::view::DisplayStateEnum::DISPLAY_STATE_OFF)); // 4:50 + events.push_back(CreateSyncStartEvent(bucketStartTimeNs + 285 * NS_PER_SEC, attributionUids1, + attributionTags1, "sync_name")); // 4:55 + + // Initialize log events - second bucket. + events.push_back(CreateSyncStartEvent(bucketStartTimeNs + 360 * NS_PER_SEC, attributionUids1, + attributionTags1, "sync_name")); // 6:10 + events.push_back(CreateScreenStateChangedEvent( + bucketStartTimeNs + 390 * NS_PER_SEC, + android::view::DisplayStateEnum::DISPLAY_STATE_ON_SUSPEND)); // 6:40 + events.push_back(CreateScreenStateChangedEvent( + bucketStartTimeNs + 430 * NS_PER_SEC, + android::view::DisplayStateEnum::DISPLAY_STATE_DOZE_SUSPEND)); // 7:20 + events.push_back(CreateSyncStartEvent(bucketStartTimeNs + 440 * NS_PER_SEC, attributionUids1, + attributionTags1, "sync_name")); // 7:30 + events.push_back(CreateScreenStateChangedEvent( + bucketStartTimeNs + 540 * NS_PER_SEC, + android::view::DisplayStateEnum::DISPLAY_STATE_ON)); // 9:10 + events.push_back(CreateSyncStartEvent(bucketStartTimeNs + 570 * NS_PER_SEC, attributionUids1, + attributionTags1, "sync_name")); // 9:40 + + // Send log events to StatsLogProcessor. + for (auto& event : events) { + processor->OnLogEvent(event.get()); + } + + // Check dump report. + vector buffer; + ConfigMetricsReportList reports; + processor->onDumpReport(cfgKey, bucketStartTimeNs + bucketSizeNs * 2 + 1, false, true, ADB_DUMP, + FAST, &buffer); + ASSERT_GT(buffer.size(), 0); + EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size())); + backfillDimensionPath(&reports); + backfillStringInReport(&reports); + backfillStartEndTimestamp(&reports); + + ASSERT_EQ(1, reports.reports_size()); + ASSERT_EQ(1, reports.reports(0).metrics_size()); + EXPECT_TRUE(reports.reports(0).metrics(0).has_count_metrics()); + StatsLogReport::CountMetricDataWrapper countMetrics; + sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).count_metrics(), &countMetrics); + ASSERT_EQ(3, countMetrics.data_size()); + + // For each CountMetricData, check StateValue info is correct and buckets + // have correct counts. + auto data = countMetrics.data(0); + ASSERT_EQ(1, data.slice_by_state_size()); + EXPECT_EQ(SCREEN_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); + EXPECT_TRUE(data.slice_by_state(0).has_value()); + EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, data.slice_by_state(0).value()); + ASSERT_EQ(1, data.bucket_info_size()); + EXPECT_EQ(1, data.bucket_info(0).count()); + + data = countMetrics.data(1); + ASSERT_EQ(1, data.slice_by_state_size()); + EXPECT_EQ(SCREEN_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); + EXPECT_TRUE(data.slice_by_state(0).has_group_id()); + EXPECT_EQ(screenOnId, data.slice_by_state(0).group_id()); + ASSERT_EQ(2, data.bucket_info_size()); + EXPECT_EQ(1, data.bucket_info(0).count()); + EXPECT_EQ(1, data.bucket_info(1).count()); + + data = countMetrics.data(2); + ASSERT_EQ(1, data.slice_by_state_size()); + EXPECT_EQ(SCREEN_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); + EXPECT_TRUE(data.slice_by_state(0).has_group_id()); + EXPECT_EQ(screenOffId, data.slice_by_state(0).group_id()); + ASSERT_EQ(2, data.bucket_info_size()); + EXPECT_EQ(4, data.bucket_info(0).count()); + EXPECT_EQ(2, data.bucket_info(1).count()); +} + +/** +* Test a count metric that has one slice_by_state with a primary field. + +* Once the CountMetricProducer is initialized, it should have one +* MetricStateLink stored. State querying using a non-empty primary key +* should also work as intended. +*/ +TEST(CountMetricE2eTest, TestSlicedStateWithPrimaryFields) { + // Initialize config. + StatsdConfig config; + config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root. + + auto appCrashMatcher = + CreateSimpleAtomMatcher("APP_CRASH_OCCURRED", util::APP_CRASH_OCCURRED); + *config.add_atom_matcher() = appCrashMatcher; + + auto state = CreateUidProcessState(); + *config.add_state() = state; + + // Create count metric that slices by uid process state. + int64_t metricId = 123456; + auto countMetric = config.add_count_metric(); + countMetric->set_id(metricId); + countMetric->set_what(appCrashMatcher.id()); + countMetric->set_bucket(TimeUnit::FIVE_MINUTES); + countMetric->add_slice_by_state(state.id()); + MetricStateLink* stateLink = countMetric->add_state_link(); + stateLink->set_state_atom_id(UID_PROCESS_STATE_ATOM_ID); + auto fieldsInWhat = stateLink->mutable_fields_in_what(); + *fieldsInWhat = CreateDimensions(util::APP_CRASH_OCCURRED, {1 /*uid*/}); + auto fieldsInState = stateLink->mutable_fields_in_state(); + *fieldsInState = CreateDimensions(UID_PROCESS_STATE_ATOM_ID, {1 /*uid*/}); + + // Initialize StatsLogProcessor. + const uint64_t bucketStartTimeNs = 10000000000; // 0:10 + const uint64_t bucketSizeNs = + TimeUnitToBucketSizeInMillis(config.count_metric(0).bucket()) * 1000000LL; + int uid = 12345; + int64_t cfgId = 98765; + ConfigKey cfgKey(uid, cfgId); + auto processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey); + + // Check that StateTrackers were initialized correctly. + EXPECT_EQ(1, StateManager::getInstance().getStateTrackersCount()); + EXPECT_EQ(1, StateManager::getInstance().getListenersCount(UID_PROCESS_STATE_ATOM_ID)); + + // Check that CountMetricProducer was initialized correctly. + ASSERT_EQ(processor->mMetricsManagers.size(), 1u); + sp metricsManager = processor->mMetricsManagers.begin()->second; + EXPECT_TRUE(metricsManager->isConfigValid()); + ASSERT_EQ(metricsManager->mAllMetricProducers.size(), 1); + sp metricProducer = metricsManager->mAllMetricProducers[0]; + ASSERT_EQ(metricProducer->mSlicedStateAtoms.size(), 1); + EXPECT_EQ(metricProducer->mSlicedStateAtoms.at(0), UID_PROCESS_STATE_ATOM_ID); + ASSERT_EQ(metricProducer->mStateGroupMap.size(), 0); + ASSERT_EQ(metricProducer->mMetric2StateLinks.size(), 1); + + /* + NOTE: "1" or "2" represents the uid associated with the state/app crash event + bucket #1 bucket #2 + | 1 2 3 4 5 6 7 8 9 10 + |------------------------|-------------------------|-- + 1 1 1 1 1 2 1 1 2 (AppCrashEvents) + -----------------------------------------------------PROCESS STATE events + 1 2 (TopEvent = 1002) + 1 1 (ForegroundServiceEvent = 1003) + 2 (ImportantBackgroundEvent = 1006) + 1 1 1 (ImportantForegroundEvent = 1005) + + Based on the diagram above, an AppCrashEvent querying for process state value would return: + - StateTracker::kStateUnknown + - Important foreground + - Top + - Important foreground + - Foreground service + - Top (both the app crash and state still have matching uid = 2) + + - Foreground service + - Foreground service + - Important background + */ + // Initialize log events - first bucket. + std::vector> events; + events.push_back( + CreateAppCrashOccurredEvent(bucketStartTimeNs + 20 * NS_PER_SEC, 1 /*uid*/)); // 0:30 + events.push_back(CreateUidProcessStateChangedEvent( + bucketStartTimeNs + 30 * NS_PER_SEC, 1 /*uid*/, + android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_FOREGROUND)); // 0:40 + events.push_back( + CreateAppCrashOccurredEvent(bucketStartTimeNs + 60 * NS_PER_SEC, 1 /*uid*/)); // 1:10 + events.push_back(CreateUidProcessStateChangedEvent( + bucketStartTimeNs + 90 * NS_PER_SEC, 1 /*uid*/, + android::app::ProcessStateEnum::PROCESS_STATE_TOP)); // 1:40 + events.push_back( + CreateAppCrashOccurredEvent(bucketStartTimeNs + 120 * NS_PER_SEC, 1 /*uid*/)); // 2:10 + events.push_back(CreateUidProcessStateChangedEvent( + bucketStartTimeNs + 150 * NS_PER_SEC, 1 /*uid*/, + android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_FOREGROUND)); // 2:40 + events.push_back( + CreateAppCrashOccurredEvent(bucketStartTimeNs + 200 * NS_PER_SEC, 1 /*uid*/)); // 3:30 + events.push_back(CreateUidProcessStateChangedEvent( + bucketStartTimeNs + 210 * NS_PER_SEC, 1 /*uid*/, + android::app::ProcessStateEnum::PROCESS_STATE_FOREGROUND_SERVICE)); // 3:40 + events.push_back( + CreateAppCrashOccurredEvent(bucketStartTimeNs + 250 * NS_PER_SEC, 1 /*uid*/)); // 4:20 + events.push_back(CreateUidProcessStateChangedEvent( + bucketStartTimeNs + 280 * NS_PER_SEC, 2 /*uid*/, + android::app::ProcessStateEnum::PROCESS_STATE_TOP)); // 4:50 + events.push_back( + CreateAppCrashOccurredEvent(bucketStartTimeNs + 285 * NS_PER_SEC, 2 /*uid*/)); // 4:55 + + // Initialize log events - second bucket. + events.push_back( + CreateAppCrashOccurredEvent(bucketStartTimeNs + 360 * NS_PER_SEC, 1 /*uid*/)); // 6:10 + events.push_back(CreateUidProcessStateChangedEvent( + bucketStartTimeNs + 390 * NS_PER_SEC, 1 /*uid*/, + android::app::ProcessStateEnum::PROCESS_STATE_FOREGROUND_SERVICE)); // 6:40 + events.push_back(CreateUidProcessStateChangedEvent( + bucketStartTimeNs + 430 * NS_PER_SEC, 2 /*uid*/, + android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_BACKGROUND)); // 7:20 + events.push_back( + CreateAppCrashOccurredEvent(bucketStartTimeNs + 440 * NS_PER_SEC, 1 /*uid*/)); // 7:30 + events.push_back(CreateUidProcessStateChangedEvent( + bucketStartTimeNs + 540 * NS_PER_SEC, 1 /*uid*/, + android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_FOREGROUND)); // 9:10 + events.push_back( + CreateAppCrashOccurredEvent(bucketStartTimeNs + 570 * NS_PER_SEC, 2 /*uid*/)); // 9:40 + + // Send log events to StatsLogProcessor. + for (auto& event : events) { + processor->OnLogEvent(event.get()); + } + + // Check dump report. + vector buffer; + ConfigMetricsReportList reports; + processor->onDumpReport(cfgKey, bucketStartTimeNs + bucketSizeNs * 2 + 1, false, true, ADB_DUMP, + FAST, &buffer); + ASSERT_GT(buffer.size(), 0); + EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size())); + backfillDimensionPath(&reports); + backfillStringInReport(&reports); + backfillStartEndTimestamp(&reports); + + ASSERT_EQ(1, reports.reports_size()); + ASSERT_EQ(1, reports.reports(0).metrics_size()); + EXPECT_TRUE(reports.reports(0).metrics(0).has_count_metrics()); + StatsLogReport::CountMetricDataWrapper countMetrics; + sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).count_metrics(), &countMetrics); + ASSERT_EQ(5, countMetrics.data_size()); + + // For each CountMetricData, check StateValue info is correct and buckets + // have correct counts. + auto data = countMetrics.data(0); + ASSERT_EQ(1, data.slice_by_state_size()); + EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); + EXPECT_TRUE(data.slice_by_state(0).has_value()); + EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, data.slice_by_state(0).value()); + ASSERT_EQ(1, data.bucket_info_size()); + EXPECT_EQ(1, data.bucket_info(0).count()); + + data = countMetrics.data(1); + ASSERT_EQ(1, data.slice_by_state_size()); + EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); + EXPECT_TRUE(data.slice_by_state(0).has_value()); + EXPECT_EQ(android::app::PROCESS_STATE_TOP, data.slice_by_state(0).value()); + ASSERT_EQ(1, data.bucket_info_size()); + EXPECT_EQ(2, data.bucket_info(0).count()); + + data = countMetrics.data(2); + ASSERT_EQ(1, data.slice_by_state_size()); + EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); + EXPECT_TRUE(data.slice_by_state(0).has_value()); + EXPECT_EQ(android::app::PROCESS_STATE_FOREGROUND_SERVICE, data.slice_by_state(0).value()); + ASSERT_EQ(2, data.bucket_info_size()); + EXPECT_EQ(1, data.bucket_info(0).count()); + EXPECT_EQ(2, data.bucket_info(1).count()); + + data = countMetrics.data(3); + ASSERT_EQ(1, data.slice_by_state_size()); + EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); + EXPECT_TRUE(data.slice_by_state(0).has_value()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_FOREGROUND, data.slice_by_state(0).value()); + ASSERT_EQ(1, data.bucket_info_size()); + EXPECT_EQ(2, data.bucket_info(0).count()); + + data = countMetrics.data(4); + ASSERT_EQ(1, data.slice_by_state_size()); + EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); + EXPECT_TRUE(data.slice_by_state(0).has_value()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND, data.slice_by_state(0).value()); + ASSERT_EQ(1, data.bucket_info_size()); + EXPECT_EQ(1, data.bucket_info(0).count()); +} + +TEST(CountMetricE2eTest, TestMultipleSlicedStates) { + // Initialize config. + StatsdConfig config; + config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root. + + auto appCrashMatcher = + CreateSimpleAtomMatcher("APP_CRASH_OCCURRED", util::APP_CRASH_OCCURRED); + *config.add_atom_matcher() = appCrashMatcher; + + int64_t screenOnId = 4444; + int64_t screenOffId = 9876; + auto state1 = CreateScreenStateWithOnOffMap(screenOnId, screenOffId); + *config.add_state() = state1; + auto state2 = CreateUidProcessState(); + *config.add_state() = state2; + + // Create count metric that slices by screen state with on/off map and + // slices by uid process state. + int64_t metricId = 123456; + auto countMetric = config.add_count_metric(); + countMetric->set_id(metricId); + countMetric->set_what(appCrashMatcher.id()); + countMetric->set_bucket(TimeUnit::FIVE_MINUTES); + countMetric->add_slice_by_state(state1.id()); + countMetric->add_slice_by_state(state2.id()); + MetricStateLink* stateLink = countMetric->add_state_link(); + stateLink->set_state_atom_id(UID_PROCESS_STATE_ATOM_ID); + auto fieldsInWhat = stateLink->mutable_fields_in_what(); + *fieldsInWhat = CreateDimensions(util::APP_CRASH_OCCURRED, {1 /*uid*/}); + auto fieldsInState = stateLink->mutable_fields_in_state(); + *fieldsInState = CreateDimensions(UID_PROCESS_STATE_ATOM_ID, {1 /*uid*/}); + + // Initialize StatsLogProcessor. + const uint64_t bucketStartTimeNs = 10000000000; // 0:10 + const uint64_t bucketSizeNs = + TimeUnitToBucketSizeInMillis(config.count_metric(0).bucket()) * 1000000LL; + int uid = 12345; + int64_t cfgId = 98765; + ConfigKey cfgKey(uid, cfgId); + auto processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey); + + // Check that StateTrackers were properly initialized. + EXPECT_EQ(2, StateManager::getInstance().getStateTrackersCount()); + EXPECT_EQ(1, StateManager::getInstance().getListenersCount(SCREEN_STATE_ATOM_ID)); + EXPECT_EQ(1, StateManager::getInstance().getListenersCount(UID_PROCESS_STATE_ATOM_ID)); + + // Check that CountMetricProducer was initialized correctly. + ASSERT_EQ(processor->mMetricsManagers.size(), 1u); + sp metricsManager = processor->mMetricsManagers.begin()->second; + EXPECT_TRUE(metricsManager->isConfigValid()); + ASSERT_EQ(metricsManager->mAllMetricProducers.size(), 1); + sp metricProducer = metricsManager->mAllMetricProducers[0]; + ASSERT_EQ(metricProducer->mSlicedStateAtoms.size(), 2); + EXPECT_EQ(metricProducer->mSlicedStateAtoms.at(0), SCREEN_STATE_ATOM_ID); + EXPECT_EQ(metricProducer->mSlicedStateAtoms.at(1), UID_PROCESS_STATE_ATOM_ID); + ASSERT_EQ(metricProducer->mStateGroupMap.size(), 1); + ASSERT_EQ(metricProducer->mMetric2StateLinks.size(), 1); + + StateMap map = state1.map(); + for (auto group : map.group()) { + for (auto value : group.value()) { + EXPECT_EQ(metricProducer->mStateGroupMap.at(SCREEN_STATE_ATOM_ID).at(value), + group.group_id()); + } + } + + /* + bucket #1 bucket #2 + | 1 2 3 4 5 6 7 8 9 10 (minutes) + |------------------------|------------------------|-- + 1 1 1 1 1 2 1 1 2 (AppCrashEvents) + ---------------------------------------------------SCREEN_OFF events + | | (ScreenOffEvent = 1) + | | (ScreenDozeEvent = 3) + ---------------------------------------------------SCREEN_ON events + | | (ScreenOnEvent = 2) + | (ScreenOnSuspendEvent = 6) + ---------------------------------------------------PROCESS STATE events + 1 2 (TopEvent = 1002) + 1 (ForegroundServiceEvent = 1003) + 2 (ImportantBackgroundEvent = 1006) + 1 1 1 (ImportantForegroundEvent = 1005) + + Based on the diagram above, Screen State / Process State pairs for each + AppCrashEvent are: + - StateTracker::kStateUnknown / important foreground + - off / important foreground + - off / Top + - on / important foreground + - off / important foreground + - off / top + + - off / important foreground + - off / foreground service + - on / important background + + */ + // Initialize log events - first bucket. + std::vector> events; + events.push_back(CreateUidProcessStateChangedEvent( + bucketStartTimeNs + 5 * NS_PER_SEC, 1 /*uid*/, + android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_FOREGROUND)); // 0:15 + events.push_back( + CreateAppCrashOccurredEvent(bucketStartTimeNs + 20 * NS_PER_SEC, 1 /*uid*/)); // 0:30 + events.push_back(CreateScreenStateChangedEvent( + bucketStartTimeNs + 30 * NS_PER_SEC, + android::view::DisplayStateEnum::DISPLAY_STATE_DOZE)); // 0:40 + events.push_back( + CreateAppCrashOccurredEvent(bucketStartTimeNs + 60 * NS_PER_SEC, 1 /*uid*/)); // 1:10 + events.push_back(CreateUidProcessStateChangedEvent( + bucketStartTimeNs + 90 * NS_PER_SEC, 1 /*uid*/, + android::app::ProcessStateEnum::PROCESS_STATE_TOP)); // 1:40 + events.push_back(CreateScreenStateChangedEvent( + bucketStartTimeNs + 90 * NS_PER_SEC, + android::view::DisplayStateEnum::DISPLAY_STATE_OFF)); // 1:40 + events.push_back( + CreateAppCrashOccurredEvent(bucketStartTimeNs + 120 * NS_PER_SEC, 1 /*uid*/)); // 2:10 + events.push_back(CreateUidProcessStateChangedEvent( + bucketStartTimeNs + 150 * NS_PER_SEC, 1 /*uid*/, + android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_FOREGROUND)); // 2:40 + events.push_back(CreateScreenStateChangedEvent( + bucketStartTimeNs + 160 * NS_PER_SEC, + android::view::DisplayStateEnum::DISPLAY_STATE_ON)); // 2:50 + events.push_back( + CreateAppCrashOccurredEvent(bucketStartTimeNs + 200 * NS_PER_SEC, 1 /*uid*/)); // 3:30 + events.push_back(CreateScreenStateChangedEvent( + bucketStartTimeNs + 210 * NS_PER_SEC, + android::view::DisplayStateEnum::DISPLAY_STATE_DOZE)); // 3:40 + events.push_back( + CreateAppCrashOccurredEvent(bucketStartTimeNs + 250 * NS_PER_SEC, 1 /*uid*/)); // 4:20 + events.push_back(CreateUidProcessStateChangedEvent( + bucketStartTimeNs + 280 * NS_PER_SEC, 2 /*uid*/, + android::app::ProcessStateEnum::PROCESS_STATE_TOP)); // 4:50 + events.push_back( + CreateAppCrashOccurredEvent(bucketStartTimeNs + 285 * NS_PER_SEC, 2 /*uid*/)); // 4:55 + + // Initialize log events - second bucket. + events.push_back( + CreateAppCrashOccurredEvent(bucketStartTimeNs + 360 * NS_PER_SEC, 1 /*uid*/)); // 6:10 + events.push_back(CreateUidProcessStateChangedEvent( + bucketStartTimeNs + 380 * NS_PER_SEC, 1 /*uid*/, + android::app::ProcessStateEnum::PROCESS_STATE_FOREGROUND_SERVICE)); // 6:30 + events.push_back(CreateScreenStateChangedEvent( + bucketStartTimeNs + 390 * NS_PER_SEC, + android::view::DisplayStateEnum::DISPLAY_STATE_ON_SUSPEND)); // 6:40 + events.push_back(CreateUidProcessStateChangedEvent( + bucketStartTimeNs + 420 * NS_PER_SEC, 2 /*uid*/, + android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_BACKGROUND)); // 7:10 + events.push_back(CreateScreenStateChangedEvent( + bucketStartTimeNs + 440 * NS_PER_SEC, + android::view::DisplayStateEnum::DISPLAY_STATE_OFF)); // 7:30 + events.push_back( + CreateAppCrashOccurredEvent(bucketStartTimeNs + 450 * NS_PER_SEC, 1 /*uid*/)); // 7:40 + events.push_back(CreateScreenStateChangedEvent( + bucketStartTimeNs + 520 * NS_PER_SEC, + android::view::DisplayStateEnum::DISPLAY_STATE_ON)); // 8:50 + events.push_back(CreateUidProcessStateChangedEvent( + bucketStartTimeNs + 540 * NS_PER_SEC, 1 /*uid*/, + android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_FOREGROUND)); // 9:10 + events.push_back( + CreateAppCrashOccurredEvent(bucketStartTimeNs + 570 * NS_PER_SEC, 2 /*uid*/)); // 9:40 + + // Send log events to StatsLogProcessor. + for (auto& event : events) { + processor->OnLogEvent(event.get()); + } + + // Check dump report. + vector buffer; + ConfigMetricsReportList reports; + processor->onDumpReport(cfgKey, bucketStartTimeNs + bucketSizeNs * 2 + 1, false, true, ADB_DUMP, + FAST, &buffer); + ASSERT_GT(buffer.size(), 0); + EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size())); + backfillDimensionPath(&reports); + backfillStringInReport(&reports); + backfillStartEndTimestamp(&reports); + + ASSERT_EQ(1, reports.reports_size()); + ASSERT_EQ(1, reports.reports(0).metrics_size()); + EXPECT_TRUE(reports.reports(0).metrics(0).has_count_metrics()); + StatsLogReport::CountMetricDataWrapper countMetrics; + sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).count_metrics(), &countMetrics); + ASSERT_EQ(6, countMetrics.data_size()); + + // For each CountMetricData, check StateValue info is correct and buckets + // have correct counts. + auto data = countMetrics.data(0); + ASSERT_EQ(2, data.slice_by_state_size()); + EXPECT_EQ(SCREEN_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); + EXPECT_TRUE(data.slice_by_state(0).has_value()); + EXPECT_EQ(-1, data.slice_by_state(0).value()); + EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(1).atom_id()); + EXPECT_TRUE(data.slice_by_state(1).has_value()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_FOREGROUND, data.slice_by_state(1).value()); + ASSERT_EQ(1, data.bucket_info_size()); + EXPECT_EQ(1, data.bucket_info(0).count()); + + data = countMetrics.data(1); + ASSERT_EQ(2, data.slice_by_state_size()); + EXPECT_EQ(SCREEN_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); + EXPECT_TRUE(data.slice_by_state(0).has_group_id()); + EXPECT_EQ(screenOnId, data.slice_by_state(0).group_id()); + EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(1).atom_id()); + EXPECT_TRUE(data.slice_by_state(1).has_value()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_FOREGROUND, data.slice_by_state(1).value()); + ASSERT_EQ(1, data.bucket_info_size()); + EXPECT_EQ(1, data.bucket_info(0).count()); + + data = countMetrics.data(2); + ASSERT_EQ(2, data.slice_by_state_size()); + EXPECT_EQ(SCREEN_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); + EXPECT_TRUE(data.slice_by_state(0).has_group_id()); + EXPECT_EQ(screenOnId, data.slice_by_state(0).group_id()); + EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(1).atom_id()); + EXPECT_TRUE(data.slice_by_state(1).has_value()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND, data.slice_by_state(1).value()); + ASSERT_EQ(1, data.bucket_info_size()); + EXPECT_EQ(1, data.bucket_info(0).count()); + + data = countMetrics.data(3); + ASSERT_EQ(2, data.slice_by_state_size()); + EXPECT_EQ(SCREEN_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); + EXPECT_TRUE(data.slice_by_state(0).has_group_id()); + EXPECT_EQ(screenOffId, data.slice_by_state(0).group_id()); + EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(1).atom_id()); + EXPECT_TRUE(data.slice_by_state(1).has_value()); + EXPECT_EQ(android::app::PROCESS_STATE_TOP, data.slice_by_state(1).value()); + ASSERT_EQ(1, data.bucket_info_size()); + EXPECT_EQ(2, data.bucket_info(0).count()); + + data = countMetrics.data(4); + ASSERT_EQ(2, data.slice_by_state_size()); + EXPECT_EQ(SCREEN_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); + EXPECT_TRUE(data.slice_by_state(0).has_group_id()); + EXPECT_EQ(screenOffId, data.slice_by_state(0).group_id()); + EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(1).atom_id()); + EXPECT_TRUE(data.slice_by_state(1).has_value()); + EXPECT_EQ(android::app::PROCESS_STATE_FOREGROUND_SERVICE, data.slice_by_state(1).value()); + ASSERT_EQ(1, data.bucket_info_size()); + EXPECT_EQ(1, data.bucket_info(0).count()); + + data = countMetrics.data(5); + ASSERT_EQ(2, data.slice_by_state_size()); + EXPECT_EQ(SCREEN_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); + EXPECT_TRUE(data.slice_by_state(0).has_group_id()); + EXPECT_EQ(screenOffId, data.slice_by_state(0).group_id()); + EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(1).atom_id()); + EXPECT_TRUE(data.slice_by_state(1).has_value()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_FOREGROUND, data.slice_by_state(1).value()); + ASSERT_EQ(2, data.bucket_info_size()); + EXPECT_EQ(2, data.bucket_info(0).count()); + EXPECT_EQ(1, data.bucket_info(1).count()); +} + +} // namespace statsd +} // namespace os +} // namespace android +#else +GTEST_LOG_(INFO) << "This test does nothing.\n"; +#endif diff --git a/statsd/tests/e2e/DurationMetric_e2e_test.cpp b/statsd/tests/e2e/DurationMetric_e2e_test.cpp new file mode 100644 index 00000000..8a4c9627 --- /dev/null +++ b/statsd/tests/e2e/DurationMetric_e2e_test.cpp @@ -0,0 +1,1571 @@ +// Copyright (C) 2019 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 + +#include "src/StatsLogProcessor.h" +#include "src/state/StateTracker.h" +#include "src/stats_log_util.h" +#include "tests/statsd_test_util.h" + +namespace android { +namespace os { +namespace statsd { + +#ifdef __ANDROID__ + +TEST(DurationMetricE2eTest, TestOneBucket) { + StatsdConfig config; + config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root. + + auto screenOnMatcher = CreateScreenTurnedOnAtomMatcher(); + auto screenOffMatcher = CreateScreenTurnedOffAtomMatcher(); + *config.add_atom_matcher() = screenOnMatcher; + *config.add_atom_matcher() = screenOffMatcher; + + auto durationPredicate = CreateScreenIsOnPredicate(); + *config.add_predicate() = durationPredicate; + + int64_t metricId = 123456; + auto durationMetric = config.add_duration_metric(); + durationMetric->set_id(metricId); + durationMetric->set_what(durationPredicate.id()); + durationMetric->set_bucket(FIVE_MINUTES); + durationMetric->set_aggregation_type(DurationMetric_AggregationType_SUM); + + const int64_t baseTimeNs = 0; // 0:00 + const int64_t configAddedTimeNs = baseTimeNs + 1 * NS_PER_SEC; // 0:01 + const int64_t bucketSizeNs = + TimeUnitToBucketSizeInMillis(config.duration_metric(0).bucket()) * 1000LL * 1000LL; + + int uid = 12345; + int64_t cfgId = 98765; + ConfigKey cfgKey(uid, cfgId); + + auto processor = CreateStatsLogProcessor(baseTimeNs, configAddedTimeNs, config, cfgKey); + + ASSERT_EQ(processor->mMetricsManagers.size(), 1u); + sp metricsManager = processor->mMetricsManagers.begin()->second; + EXPECT_TRUE(metricsManager->isConfigValid()); + ASSERT_EQ(metricsManager->mAllMetricProducers.size(), 1); + sp metricProducer = metricsManager->mAllMetricProducers[0]; + EXPECT_TRUE(metricsManager->isActive()); + EXPECT_TRUE(metricProducer->mIsActive); + + std::unique_ptr event; + + // Screen is off at start of bucket. + event = CreateScreenStateChangedEvent(configAddedTimeNs, + android::view::DISPLAY_STATE_OFF); // 0:01 + processor->OnLogEvent(event.get()); + + // Turn screen on. + const int64_t durationStartNs = configAddedTimeNs + 10 * NS_PER_SEC; // 0:11 + event = CreateScreenStateChangedEvent(durationStartNs, android::view::DISPLAY_STATE_ON); + processor->OnLogEvent(event.get()); + + // Turn off screen 30 seconds after turning on. + const int64_t durationEndNs = durationStartNs + 30 * NS_PER_SEC; // 0:41 + event = CreateScreenStateChangedEvent(durationEndNs, android::view::DISPLAY_STATE_OFF); + processor->OnLogEvent(event.get()); + + event = CreateScreenBrightnessChangedEvent(durationEndNs + 1 * NS_PER_SEC, 64); // 0:42 + processor->OnLogEvent(event.get()); + + ConfigMetricsReportList reports; + vector buffer; + processor->onDumpReport(cfgKey, configAddedTimeNs + bucketSizeNs + 1 * NS_PER_SEC, false, true, + ADB_DUMP, FAST, &buffer); // 5:01 + EXPECT_TRUE(buffer.size() > 0); + EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size())); + backfillDimensionPath(&reports); + backfillStartEndTimestamp(&reports); + ASSERT_EQ(1, reports.reports_size()); + ASSERT_EQ(1, reports.reports(0).metrics_size()); + EXPECT_EQ(metricId, reports.reports(0).metrics(0).metric_id()); + EXPECT_TRUE(reports.reports(0).metrics(0).has_duration_metrics()); + + StatsLogReport::DurationMetricDataWrapper durationMetrics; + sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).duration_metrics(), + &durationMetrics); + ASSERT_EQ(1, durationMetrics.data_size()); + + DurationMetricData data = durationMetrics.data(0); + ASSERT_EQ(1, data.bucket_info_size()); + EXPECT_EQ(durationEndNs - durationStartNs, data.bucket_info(0).duration_nanos()); + EXPECT_EQ(configAddedTimeNs, data.bucket_info(0).start_bucket_elapsed_nanos()); + EXPECT_EQ(baseTimeNs + bucketSizeNs, data.bucket_info(0).end_bucket_elapsed_nanos()); +} + +TEST(DurationMetricE2eTest, TestTwoBuckets) { + StatsdConfig config; + config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root. + + auto screenOnMatcher = CreateScreenTurnedOnAtomMatcher(); + auto screenOffMatcher = CreateScreenTurnedOffAtomMatcher(); + *config.add_atom_matcher() = screenOnMatcher; + *config.add_atom_matcher() = screenOffMatcher; + + auto durationPredicate = CreateScreenIsOnPredicate(); + *config.add_predicate() = durationPredicate; + + int64_t metricId = 123456; + auto durationMetric = config.add_duration_metric(); + durationMetric->set_id(metricId); + durationMetric->set_what(durationPredicate.id()); + durationMetric->set_bucket(FIVE_MINUTES); + durationMetric->set_aggregation_type(DurationMetric_AggregationType_SUM); + + const int64_t baseTimeNs = 0; // 0:00 + const int64_t configAddedTimeNs = baseTimeNs + 1 * NS_PER_SEC; // 0:01 + const int64_t bucketSizeNs = + TimeUnitToBucketSizeInMillis(config.duration_metric(0).bucket()) * 1000LL * 1000LL; + + int uid = 12345; + int64_t cfgId = 98765; + ConfigKey cfgKey(uid, cfgId); + + auto processor = CreateStatsLogProcessor(baseTimeNs, configAddedTimeNs, config, cfgKey); + + ASSERT_EQ(processor->mMetricsManagers.size(), 1u); + sp metricsManager = processor->mMetricsManagers.begin()->second; + EXPECT_TRUE(metricsManager->isConfigValid()); + ASSERT_EQ(metricsManager->mAllMetricProducers.size(), 1); + sp metricProducer = metricsManager->mAllMetricProducers[0]; + EXPECT_TRUE(metricsManager->isActive()); + EXPECT_TRUE(metricProducer->mIsActive); + + std::unique_ptr event; + + // Screen is off at start of bucket. + event = CreateScreenStateChangedEvent(configAddedTimeNs, + android::view::DISPLAY_STATE_OFF); // 0:01 + processor->OnLogEvent(event.get()); + + // Turn screen on. + const int64_t durationStartNs = configAddedTimeNs + 10 * NS_PER_SEC; // 0:11 + event = CreateScreenStateChangedEvent(durationStartNs, android::view::DISPLAY_STATE_ON); + processor->OnLogEvent(event.get()); + + // Turn off screen 30 seconds after turning on. + const int64_t durationEndNs = durationStartNs + 30 * NS_PER_SEC; // 0:41 + event = CreateScreenStateChangedEvent(durationEndNs, android::view::DISPLAY_STATE_OFF); + processor->OnLogEvent(event.get()); + + event = CreateScreenBrightnessChangedEvent(durationEndNs + 1 * NS_PER_SEC, 64); // 0:42 + processor->OnLogEvent(event.get()); + + ConfigMetricsReportList reports; + vector buffer; + processor->onDumpReport(cfgKey, configAddedTimeNs + 2 * bucketSizeNs + 1 * NS_PER_SEC, false, + true, ADB_DUMP, FAST, &buffer); // 10:01 + EXPECT_TRUE(buffer.size() > 0); + EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size())); + backfillDimensionPath(&reports); + backfillStartEndTimestamp(&reports); + ASSERT_EQ(1, reports.reports_size()); + ASSERT_EQ(1, reports.reports(0).metrics_size()); + EXPECT_EQ(metricId, reports.reports(0).metrics(0).metric_id()); + EXPECT_TRUE(reports.reports(0).metrics(0).has_duration_metrics()); + + StatsLogReport::DurationMetricDataWrapper durationMetrics; + sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).duration_metrics(), + &durationMetrics); + ASSERT_EQ(1, durationMetrics.data_size()); + + DurationMetricData data = durationMetrics.data(0); + ASSERT_EQ(1, data.bucket_info_size()); + + auto bucketInfo = data.bucket_info(0); + EXPECT_EQ(0, bucketInfo.bucket_num()); + EXPECT_EQ(durationEndNs - durationStartNs, bucketInfo.duration_nanos()); + EXPECT_EQ(configAddedTimeNs, bucketInfo.start_bucket_elapsed_nanos()); + EXPECT_EQ(baseTimeNs + bucketSizeNs, bucketInfo.end_bucket_elapsed_nanos()); +} + +TEST(DurationMetricE2eTest, TestWithActivation) { + StatsdConfig config; + config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root. + + auto screenOnMatcher = CreateScreenTurnedOnAtomMatcher(); + auto screenOffMatcher = CreateScreenTurnedOffAtomMatcher(); + auto crashMatcher = CreateProcessCrashAtomMatcher(); + *config.add_atom_matcher() = screenOnMatcher; + *config.add_atom_matcher() = screenOffMatcher; + *config.add_atom_matcher() = crashMatcher; + + auto durationPredicate = CreateScreenIsOnPredicate(); + *config.add_predicate() = durationPredicate; + + int64_t metricId = 123456; + auto durationMetric = config.add_duration_metric(); + durationMetric->set_id(metricId); + durationMetric->set_what(durationPredicate.id()); + durationMetric->set_bucket(FIVE_MINUTES); + durationMetric->set_aggregation_type(DurationMetric_AggregationType_SUM); + + auto metric_activation1 = config.add_metric_activation(); + metric_activation1->set_metric_id(metricId); + auto event_activation1 = metric_activation1->add_event_activation(); + event_activation1->set_atom_matcher_id(crashMatcher.id()); + event_activation1->set_ttl_seconds(30); // 30 secs. + + const int64_t bucketStartTimeNs = 10000000000; + const int64_t bucketSizeNs = + TimeUnitToBucketSizeInMillis(config.duration_metric(0).bucket()) * 1000LL * 1000LL; + + int uid = 12345; + int64_t cfgId = 98765; + ConfigKey cfgKey(uid, cfgId); + + sp m = new UidMap(); + sp pullerManager = new StatsPullerManager(); + sp anomalyAlarmMonitor; + sp subscriberAlarmMonitor; + vector activeConfigsBroadcast; + + int broadcastCount = 0; + StatsLogProcessor processor( + m, pullerManager, anomalyAlarmMonitor, subscriberAlarmMonitor, bucketStartTimeNs, + [](const ConfigKey& key) { return true; }, + [&uid, &broadcastCount, &activeConfigsBroadcast](const int& broadcastUid, + const vector& activeConfigs) { + broadcastCount++; + EXPECT_EQ(broadcastUid, uid); + activeConfigsBroadcast.clear(); + activeConfigsBroadcast.insert(activeConfigsBroadcast.end(), activeConfigs.begin(), + activeConfigs.end()); + return true; + }); + + processor.OnConfigUpdated(bucketStartTimeNs, cfgKey, config); // 0:00 + + ASSERT_EQ(processor.mMetricsManagers.size(), 1u); + sp metricsManager = processor.mMetricsManagers.begin()->second; + EXPECT_TRUE(metricsManager->isConfigValid()); + ASSERT_EQ(metricsManager->mAllMetricProducers.size(), 1); + sp metricProducer = metricsManager->mAllMetricProducers[0]; + auto& eventActivationMap = metricProducer->mEventActivationMap; + + EXPECT_FALSE(metricsManager->isActive()); + EXPECT_FALSE(metricProducer->mIsActive); + ASSERT_EQ(eventActivationMap.size(), 1u); + EXPECT_TRUE(eventActivationMap.find(2) != eventActivationMap.end()); + EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kNotActive); + EXPECT_EQ(eventActivationMap[2]->start_ns, 0); + EXPECT_EQ(eventActivationMap[2]->ttl_ns, event_activation1->ttl_seconds() * NS_PER_SEC); + + std::unique_ptr event; + + // Turn screen off. + event = CreateScreenStateChangedEvent(bucketStartTimeNs + 2 * NS_PER_SEC, + android::view::DISPLAY_STATE_OFF); // 0:02 + processor.OnLogEvent(event.get(), bucketStartTimeNs + 2 * NS_PER_SEC); + + // Turn screen on. + const int64_t durationStartNs = bucketStartTimeNs + 5 * NS_PER_SEC; // 0:05 + event = CreateScreenStateChangedEvent(durationStartNs, android::view::DISPLAY_STATE_ON); + processor.OnLogEvent(event.get(), durationStartNs); + + // Activate metric. + const int64_t activationStartNs = bucketStartTimeNs + 5 * NS_PER_SEC; // 0:10 + const int64_t activationEndNs = + activationStartNs + event_activation1->ttl_seconds() * NS_PER_SEC; // 0:40 + event = CreateAppCrashEvent(activationStartNs, 111); + processor.OnLogEvent(event.get(), activationStartNs); + EXPECT_TRUE(metricsManager->isActive()); + EXPECT_TRUE(metricProducer->mIsActive); + EXPECT_EQ(broadcastCount, 1); + ASSERT_EQ(activeConfigsBroadcast.size(), 1); + EXPECT_EQ(activeConfigsBroadcast[0], cfgId); + EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kActive); + EXPECT_EQ(eventActivationMap[2]->start_ns, activationStartNs); + EXPECT_EQ(eventActivationMap[2]->ttl_ns, event_activation1->ttl_seconds() * NS_PER_SEC); + + // Expire activation. + const int64_t expirationNs = activationEndNs + 7 * NS_PER_SEC; + event = CreateScreenBrightnessChangedEvent(expirationNs, 64); // 0:47 + processor.OnLogEvent(event.get(), expirationNs); + EXPECT_FALSE(metricsManager->isActive()); + EXPECT_FALSE(metricProducer->mIsActive); + EXPECT_EQ(broadcastCount, 2); + ASSERT_EQ(activeConfigsBroadcast.size(), 0); + ASSERT_EQ(eventActivationMap.size(), 1u); + EXPECT_TRUE(eventActivationMap.find(2) != eventActivationMap.end()); + EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kNotActive); + EXPECT_EQ(eventActivationMap[2]->start_ns, activationStartNs); + EXPECT_EQ(eventActivationMap[2]->ttl_ns, event_activation1->ttl_seconds() * NS_PER_SEC); + + // Turn off screen 10 seconds after activation expiration. + const int64_t durationEndNs = activationEndNs + 10 * NS_PER_SEC; // 0:50 + event = CreateScreenStateChangedEvent(durationEndNs, android::view::DISPLAY_STATE_OFF); + processor.OnLogEvent(event.get(), durationEndNs); + + // Turn screen on. + const int64_t duration2StartNs = durationEndNs + 5 * NS_PER_SEC; // 0:55 + event = CreateScreenStateChangedEvent(duration2StartNs, android::view::DISPLAY_STATE_ON); + processor.OnLogEvent(event.get(), duration2StartNs); + + // Turn off screen. + const int64_t duration2EndNs = duration2StartNs + 10 * NS_PER_SEC; // 1:05 + event = CreateScreenStateChangedEvent(duration2EndNs, android::view::DISPLAY_STATE_OFF); + processor.OnLogEvent(event.get(), duration2EndNs); + + // Activate metric. + const int64_t activation2StartNs = duration2EndNs + 5 * NS_PER_SEC; // 1:10 + const int64_t activation2EndNs = + activation2StartNs + event_activation1->ttl_seconds() * NS_PER_SEC; // 1:40 + event = CreateAppCrashEvent(activation2StartNs, 211); + processor.OnLogEvent(event.get(), activation2StartNs); + EXPECT_TRUE(metricsManager->isActive()); + EXPECT_TRUE(metricProducer->mIsActive); + EXPECT_EQ(broadcastCount, 3); + ASSERT_EQ(activeConfigsBroadcast.size(), 1); + EXPECT_EQ(activeConfigsBroadcast[0], cfgId); + EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kActive); + EXPECT_EQ(eventActivationMap[2]->start_ns, activation2StartNs); + EXPECT_EQ(eventActivationMap[2]->ttl_ns, event_activation1->ttl_seconds() * NS_PER_SEC); + + ConfigMetricsReportList reports; + vector buffer; + processor.onDumpReport(cfgKey, bucketStartTimeNs + bucketSizeNs + 1 * NS_PER_SEC, false, true, + ADB_DUMP, FAST, &buffer); // 5:01 + EXPECT_TRUE(buffer.size() > 0); + EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size())); + backfillDimensionPath(&reports); + backfillStartEndTimestamp(&reports); + ASSERT_EQ(1, reports.reports_size()); + ASSERT_EQ(1, reports.reports(0).metrics_size()); + EXPECT_EQ(metricId, reports.reports(0).metrics(0).metric_id()); + EXPECT_TRUE(reports.reports(0).metrics(0).has_duration_metrics()); + + StatsLogReport::DurationMetricDataWrapper durationMetrics; + sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).duration_metrics(), + &durationMetrics); + ASSERT_EQ(1, durationMetrics.data_size()); + + DurationMetricData data = durationMetrics.data(0); + ASSERT_EQ(1, data.bucket_info_size()); + + auto bucketInfo = data.bucket_info(0); + EXPECT_EQ(0, bucketInfo.bucket_num()); + EXPECT_EQ(bucketStartTimeNs, bucketInfo.start_bucket_elapsed_nanos()); + EXPECT_EQ(expirationNs, bucketInfo.end_bucket_elapsed_nanos()); + EXPECT_EQ(expirationNs - durationStartNs, bucketInfo.duration_nanos()); +} + +TEST(DurationMetricE2eTest, TestWithCondition) { + StatsdConfig config; + config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root. + *config.add_atom_matcher() = CreateAcquireWakelockAtomMatcher(); + *config.add_atom_matcher() = CreateReleaseWakelockAtomMatcher(); + *config.add_atom_matcher() = CreateMoveToBackgroundAtomMatcher(); + *config.add_atom_matcher() = CreateMoveToForegroundAtomMatcher(); + + auto holdingWakelockPredicate = CreateHoldingWakelockPredicate(); + *config.add_predicate() = holdingWakelockPredicate; + + auto isInBackgroundPredicate = CreateIsInBackgroundPredicate(); + *config.add_predicate() = isInBackgroundPredicate; + + auto durationMetric = config.add_duration_metric(); + durationMetric->set_id(StringToId("WakelockDuration")); + durationMetric->set_what(holdingWakelockPredicate.id()); + durationMetric->set_condition(isInBackgroundPredicate.id()); + durationMetric->set_aggregation_type(DurationMetric::SUM); + durationMetric->set_bucket(FIVE_MINUTES); + + ConfigKey cfgKey; + uint64_t bucketStartTimeNs = 10000000000; + uint64_t bucketSizeNs = + TimeUnitToBucketSizeInMillis(config.duration_metric(0).bucket()) * 1000000LL; + auto processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey); + ASSERT_EQ(processor->mMetricsManagers.size(), 1u); + sp metricsManager = processor->mMetricsManagers.begin()->second; + EXPECT_TRUE(metricsManager->isConfigValid()); + ASSERT_EQ(metricsManager->mAllMetricProducers.size(), 1); + sp metricProducer = metricsManager->mAllMetricProducers[0]; + auto& eventActivationMap = metricProducer->mEventActivationMap; + EXPECT_TRUE(metricsManager->isActive()); + EXPECT_TRUE(metricProducer->mIsActive); + EXPECT_TRUE(eventActivationMap.empty()); + + int appUid = 123; + vector attributionUids1 = {appUid}; + vector attributionTags1 = {"App1"}; + + auto event = CreateAcquireWakelockEvent(bucketStartTimeNs + 10 * NS_PER_SEC, attributionUids1, + attributionTags1, + "wl1"); // 0:10 + processor->OnLogEvent(event.get()); + + event = CreateMoveToBackgroundEvent(bucketStartTimeNs + 22 * NS_PER_SEC, appUid); // 0:22 + processor->OnLogEvent(event.get()); + + event = CreateMoveToForegroundEvent(bucketStartTimeNs + (3 * 60 + 15) * NS_PER_SEC, + appUid); // 3:15 + processor->OnLogEvent(event.get()); + + event = CreateReleaseWakelockEvent(bucketStartTimeNs + 4 * 60 * NS_PER_SEC, attributionUids1, + attributionTags1, + "wl1"); // 4:00 + processor->OnLogEvent(event.get()); + + vector buffer; + ConfigMetricsReportList reports; + processor->onDumpReport(cfgKey, bucketStartTimeNs + bucketSizeNs + 1, false, true, ADB_DUMP, + FAST, &buffer); + ASSERT_GT(buffer.size(), 0); + EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size())); + backfillDimensionPath(&reports); + backfillStringInReport(&reports); + backfillStartEndTimestamp(&reports); + + ASSERT_EQ(1, reports.reports_size()); + ASSERT_EQ(1, reports.reports(0).metrics_size()); + StatsLogReport::DurationMetricDataWrapper durationMetrics; + sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).duration_metrics(), + &durationMetrics); + ASSERT_EQ(1, durationMetrics.data_size()); + + DurationMetricData data = durationMetrics.data(0); + + // Validate bucket info. + ASSERT_EQ(1, data.bucket_info_size()); + + auto bucketInfo = data.bucket_info(0); + EXPECT_EQ(bucketStartTimeNs, bucketInfo.start_bucket_elapsed_nanos()); + EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, bucketInfo.end_bucket_elapsed_nanos()); + EXPECT_EQ((2 * 60 + 53) * NS_PER_SEC, bucketInfo.duration_nanos()); +} + +TEST(DurationMetricE2eTest, TestWithSlicedCondition) { + StatsdConfig config; + config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root. + auto screenOnMatcher = CreateScreenTurnedOnAtomMatcher(); + *config.add_atom_matcher() = CreateAcquireWakelockAtomMatcher(); + *config.add_atom_matcher() = CreateReleaseWakelockAtomMatcher(); + *config.add_atom_matcher() = CreateMoveToBackgroundAtomMatcher(); + *config.add_atom_matcher() = CreateMoveToForegroundAtomMatcher(); + + auto holdingWakelockPredicate = CreateHoldingWakelockPredicate(); + // The predicate is dimensioning by first attribution node by uid. + FieldMatcher dimensions = CreateAttributionUidDimensions(util::WAKELOCK_STATE_CHANGED, + {Position::FIRST}); + *holdingWakelockPredicate.mutable_simple_predicate()->mutable_dimensions() = dimensions; + *config.add_predicate() = holdingWakelockPredicate; + + auto isInBackgroundPredicate = CreateIsInBackgroundPredicate(); + *isInBackgroundPredicate.mutable_simple_predicate()->mutable_dimensions() = + CreateDimensions(util::ACTIVITY_FOREGROUND_STATE_CHANGED, {Position::FIRST}); + *config.add_predicate() = isInBackgroundPredicate; + + auto durationMetric = config.add_duration_metric(); + durationMetric->set_id(StringToId("WakelockDuration")); + durationMetric->set_what(holdingWakelockPredicate.id()); + durationMetric->set_condition(isInBackgroundPredicate.id()); + durationMetric->set_aggregation_type(DurationMetric::SUM); + // The metric is dimensioning by first attribution node and only by uid. + *durationMetric->mutable_dimensions_in_what() = CreateAttributionUidDimensions( + util::WAKELOCK_STATE_CHANGED, {Position::FIRST}); + durationMetric->set_bucket(FIVE_MINUTES); + + // Links between wakelock state atom and condition of app is in background. + auto links = durationMetric->add_links(); + links->set_condition(isInBackgroundPredicate.id()); + *links->mutable_fields_in_what() = + CreateAttributionUidDimensions(util::WAKELOCK_STATE_CHANGED, {Position::FIRST}); + auto dimensionCondition = links->mutable_fields_in_condition(); + dimensionCondition->set_field(util::ACTIVITY_FOREGROUND_STATE_CHANGED); + dimensionCondition->add_child()->set_field(1); // uid field. + + ConfigKey cfgKey; + uint64_t bucketStartTimeNs = 10000000000; + uint64_t bucketSizeNs = + TimeUnitToBucketSizeInMillis(config.duration_metric(0).bucket()) * 1000000LL; + auto processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey); + ASSERT_EQ(processor->mMetricsManagers.size(), 1u); + sp metricsManager = processor->mMetricsManagers.begin()->second; + EXPECT_TRUE(metricsManager->isConfigValid()); + ASSERT_EQ(metricsManager->mAllMetricProducers.size(), 1); + sp metricProducer = metricsManager->mAllMetricProducers[0]; + auto& eventActivationMap = metricProducer->mEventActivationMap; + EXPECT_TRUE(metricsManager->isActive()); + EXPECT_TRUE(metricProducer->mIsActive); + EXPECT_TRUE(eventActivationMap.empty()); + + int appUid = 123; + std::vector attributionUids1 = {appUid}; + std::vector attributionTags1 = {"App1"}; + + auto event = CreateAcquireWakelockEvent(bucketStartTimeNs + 10 * NS_PER_SEC, attributionUids1, + attributionTags1, "wl1"); // 0:10 + processor->OnLogEvent(event.get()); + + event = CreateMoveToBackgroundEvent(bucketStartTimeNs + 22 * NS_PER_SEC, appUid); // 0:22 + processor->OnLogEvent(event.get()); + + event = CreateReleaseWakelockEvent(bucketStartTimeNs + 60 * NS_PER_SEC, attributionUids1, + attributionTags1, "wl1"); // 1:00 + processor->OnLogEvent(event.get()); + + event = CreateMoveToForegroundEvent(bucketStartTimeNs + (3 * 60 + 15) * NS_PER_SEC, + appUid); // 3:15 + processor->OnLogEvent(event.get()); + + vector buffer; + ConfigMetricsReportList reports; + processor->onDumpReport(cfgKey, bucketStartTimeNs + bucketSizeNs + 1, false, true, ADB_DUMP, + FAST, &buffer); + ASSERT_GT(buffer.size(), 0); + EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size())); + backfillDimensionPath(&reports); + backfillStringInReport(&reports); + backfillStartEndTimestamp(&reports); + + ASSERT_EQ(1, reports.reports_size()); + ASSERT_EQ(1, reports.reports(0).metrics_size()); + StatsLogReport::DurationMetricDataWrapper durationMetrics; + sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).duration_metrics(), + &durationMetrics); + ASSERT_EQ(1, durationMetrics.data_size()); + + DurationMetricData data = durationMetrics.data(0); + // Validate dimension value. + ValidateAttributionUidDimension(data.dimensions_in_what(), + util::WAKELOCK_STATE_CHANGED, appUid); + // Validate bucket info. + ASSERT_EQ(1, data.bucket_info_size()); + + auto bucketInfo = data.bucket_info(0); + EXPECT_EQ(bucketStartTimeNs, bucketInfo.start_bucket_elapsed_nanos()); + EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, bucketInfo.end_bucket_elapsed_nanos()); + EXPECT_EQ(38 * NS_PER_SEC, bucketInfo.duration_nanos()); +} + +TEST(DurationMetricE2eTest, TestWithActivationAndSlicedCondition) { + StatsdConfig config; + config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root. + auto screenOnMatcher = CreateScreenTurnedOnAtomMatcher(); + *config.add_atom_matcher() = CreateAcquireWakelockAtomMatcher(); + *config.add_atom_matcher() = CreateReleaseWakelockAtomMatcher(); + *config.add_atom_matcher() = CreateMoveToBackgroundAtomMatcher(); + *config.add_atom_matcher() = CreateMoveToForegroundAtomMatcher(); + *config.add_atom_matcher() = screenOnMatcher; + + auto holdingWakelockPredicate = CreateHoldingWakelockPredicate(); + // The predicate is dimensioning by first attribution node by uid. + FieldMatcher dimensions = CreateAttributionUidDimensions(util::WAKELOCK_STATE_CHANGED, + {Position::FIRST}); + *holdingWakelockPredicate.mutable_simple_predicate()->mutable_dimensions() = dimensions; + *config.add_predicate() = holdingWakelockPredicate; + + auto isInBackgroundPredicate = CreateIsInBackgroundPredicate(); + *isInBackgroundPredicate.mutable_simple_predicate()->mutable_dimensions() = + CreateDimensions(util::ACTIVITY_FOREGROUND_STATE_CHANGED, {Position::FIRST}); + *config.add_predicate() = isInBackgroundPredicate; + + auto durationMetric = config.add_duration_metric(); + durationMetric->set_id(StringToId("WakelockDuration")); + durationMetric->set_what(holdingWakelockPredicate.id()); + durationMetric->set_condition(isInBackgroundPredicate.id()); + durationMetric->set_aggregation_type(DurationMetric::SUM); + // The metric is dimensioning by first attribution node and only by uid. + *durationMetric->mutable_dimensions_in_what() = CreateAttributionUidDimensions( + util::WAKELOCK_STATE_CHANGED, {Position::FIRST}); + durationMetric->set_bucket(FIVE_MINUTES); + + // Links between wakelock state atom and condition of app is in background. + auto links = durationMetric->add_links(); + links->set_condition(isInBackgroundPredicate.id()); + *links->mutable_fields_in_what() = + CreateAttributionUidDimensions(util::WAKELOCK_STATE_CHANGED, {Position::FIRST}); + auto dimensionCondition = links->mutable_fields_in_condition(); + dimensionCondition->set_field(util::ACTIVITY_FOREGROUND_STATE_CHANGED); + dimensionCondition->add_child()->set_field(1); // uid field. + + auto metric_activation1 = config.add_metric_activation(); + metric_activation1->set_metric_id(durationMetric->id()); + auto event_activation1 = metric_activation1->add_event_activation(); + event_activation1->set_atom_matcher_id(screenOnMatcher.id()); + event_activation1->set_ttl_seconds(60 * 2); // 2 minutes. + + ConfigKey cfgKey; + uint64_t bucketStartTimeNs = 10000000000; + uint64_t bucketSizeNs = + TimeUnitToBucketSizeInMillis(config.duration_metric(0).bucket()) * 1000000LL; + auto processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey); + ASSERT_EQ(processor->mMetricsManagers.size(), 1u); + sp metricsManager = processor->mMetricsManagers.begin()->second; + EXPECT_TRUE(metricsManager->isConfigValid()); + ASSERT_EQ(metricsManager->mAllMetricProducers.size(), 1); + sp metricProducer = metricsManager->mAllMetricProducers[0]; + auto& eventActivationMap = metricProducer->mEventActivationMap; + EXPECT_FALSE(metricsManager->isActive()); + EXPECT_FALSE(metricProducer->mIsActive); + ASSERT_EQ(eventActivationMap.size(), 1u); + EXPECT_TRUE(eventActivationMap.find(4) != eventActivationMap.end()); + EXPECT_EQ(eventActivationMap[4]->state, ActivationState::kNotActive); + EXPECT_EQ(eventActivationMap[4]->start_ns, 0); + EXPECT_EQ(eventActivationMap[4]->ttl_ns, event_activation1->ttl_seconds() * NS_PER_SEC); + + int appUid = 123; + std::vector attributionUids1 = {appUid}; + std::vector attributionTags1 = {"App1"}; + + auto event = CreateAcquireWakelockEvent(bucketStartTimeNs + 10 * NS_PER_SEC, attributionUids1, + attributionTags1, "wl1"); // 0:10 + processor->OnLogEvent(event.get()); + EXPECT_FALSE(metricsManager->isActive()); + EXPECT_FALSE(metricProducer->mIsActive); + EXPECT_EQ(eventActivationMap[4]->state, ActivationState::kNotActive); + EXPECT_EQ(eventActivationMap[4]->start_ns, 0); + EXPECT_EQ(eventActivationMap[4]->ttl_ns, event_activation1->ttl_seconds() * NS_PER_SEC); + + event = CreateMoveToBackgroundEvent(bucketStartTimeNs + 22 * NS_PER_SEC, appUid); // 0:22 + processor->OnLogEvent(event.get()); + EXPECT_FALSE(metricsManager->isActive()); + EXPECT_FALSE(metricProducer->mIsActive); + EXPECT_EQ(eventActivationMap[4]->state, ActivationState::kNotActive); + EXPECT_EQ(eventActivationMap[4]->start_ns, 0); + EXPECT_EQ(eventActivationMap[4]->ttl_ns, event_activation1->ttl_seconds() * NS_PER_SEC); + + const int64_t durationStartNs = bucketStartTimeNs + 30 * NS_PER_SEC; // 0:30 + event = CreateScreenStateChangedEvent(durationStartNs, android::view::DISPLAY_STATE_ON); + processor->OnLogEvent(event.get()); + EXPECT_TRUE(metricsManager->isActive()); + EXPECT_TRUE(metricProducer->mIsActive); + EXPECT_EQ(eventActivationMap[4]->state, ActivationState::kActive); + EXPECT_EQ(eventActivationMap[4]->start_ns, durationStartNs); + EXPECT_EQ(eventActivationMap[4]->ttl_ns, event_activation1->ttl_seconds() * NS_PER_SEC); + + const int64_t durationEndNs = + durationStartNs + (event_activation1->ttl_seconds() + 30) * NS_PER_SEC; // 3:00 + event = CreateAppCrashEvent(durationEndNs, 333); + processor->OnLogEvent(event.get()); + EXPECT_FALSE(metricsManager->isActive()); + EXPECT_FALSE(metricProducer->mIsActive); + EXPECT_EQ(eventActivationMap[4]->state, ActivationState::kNotActive); + EXPECT_EQ(eventActivationMap[4]->start_ns, durationStartNs); + EXPECT_EQ(eventActivationMap[4]->ttl_ns, event_activation1->ttl_seconds() * NS_PER_SEC); + + event = CreateMoveToForegroundEvent(bucketStartTimeNs + (3 * 60 + 15) * NS_PER_SEC, + appUid); // 3:15 + processor->OnLogEvent(event.get()); + + event = CreateReleaseWakelockEvent(bucketStartTimeNs + (4 * 60 + 17) * NS_PER_SEC, + attributionUids1, attributionTags1, "wl1"); // 4:17 + processor->OnLogEvent(event.get()); + + event = CreateMoveToBackgroundEvent(bucketStartTimeNs + (4 * 60 + 20) * NS_PER_SEC, + appUid); // 4:20 + processor->OnLogEvent(event.get()); + + event = CreateAcquireWakelockEvent(bucketStartTimeNs + (4 * 60 + 25) * NS_PER_SEC, + attributionUids1, attributionTags1, "wl1"); // 4:25 + processor->OnLogEvent(event.get()); + + const int64_t duration2StartNs = bucketStartTimeNs + (4 * 60 + 30) * NS_PER_SEC; // 4:30 + event = CreateScreenStateChangedEvent(duration2StartNs, android::view::DISPLAY_STATE_ON); + processor->OnLogEvent(event.get()); + EXPECT_TRUE(metricsManager->isActive()); + EXPECT_TRUE(metricProducer->mIsActive); + EXPECT_EQ(eventActivationMap[4]->state, ActivationState::kActive); + EXPECT_EQ(eventActivationMap[4]->start_ns, duration2StartNs); + EXPECT_EQ(eventActivationMap[4]->ttl_ns, event_activation1->ttl_seconds() * NS_PER_SEC); + + vector buffer; + ConfigMetricsReportList reports; + processor->onDumpReport(cfgKey, bucketStartTimeNs + bucketSizeNs + 1, false, true, ADB_DUMP, + FAST, &buffer); + ASSERT_GT(buffer.size(), 0); + EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size())); + backfillDimensionPath(&reports); + backfillStringInReport(&reports); + backfillStartEndTimestamp(&reports); + + ASSERT_EQ(1, reports.reports_size()); + ASSERT_EQ(1, reports.reports(0).metrics_size()); + StatsLogReport::DurationMetricDataWrapper durationMetrics; + sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).duration_metrics(), + &durationMetrics); + ASSERT_EQ(1, durationMetrics.data_size()); + + DurationMetricData data = durationMetrics.data(0); + // Validate dimension value. + ValidateAttributionUidDimension(data.dimensions_in_what(), + util::WAKELOCK_STATE_CHANGED, appUid); + // Validate bucket info. + ASSERT_EQ(2, data.bucket_info_size()); + + auto bucketInfo = data.bucket_info(0); + EXPECT_EQ(bucketStartTimeNs, bucketInfo.start_bucket_elapsed_nanos()); + EXPECT_EQ(durationEndNs, bucketInfo.end_bucket_elapsed_nanos()); + EXPECT_EQ(durationEndNs - durationStartNs, bucketInfo.duration_nanos()); + + bucketInfo = data.bucket_info(1); + EXPECT_EQ(durationEndNs, bucketInfo.start_bucket_elapsed_nanos()); + EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, bucketInfo.end_bucket_elapsed_nanos()); + EXPECT_EQ(bucketStartTimeNs + bucketSizeNs - duration2StartNs, bucketInfo.duration_nanos()); +} + +TEST(DurationMetricE2eTest, TestWithSlicedState) { + // Initialize config. + StatsdConfig config; + config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root. + + *config.add_atom_matcher() = CreateBatterySaverModeStartAtomMatcher(); + *config.add_atom_matcher() = CreateBatterySaverModeStopAtomMatcher(); + + auto batterySaverModePredicate = CreateBatterySaverModePredicate(); + *config.add_predicate() = batterySaverModePredicate; + + auto screenState = CreateScreenState(); + *config.add_state() = screenState; + + // Create duration metric that slices by screen state. + auto durationMetric = config.add_duration_metric(); + durationMetric->set_id(StringToId("DurationBatterySaverModeSliceScreen")); + durationMetric->set_what(batterySaverModePredicate.id()); + durationMetric->add_slice_by_state(screenState.id()); + durationMetric->set_aggregation_type(DurationMetric::SUM); + durationMetric->set_bucket(FIVE_MINUTES); + + // Initialize StatsLogProcessor. + int uid = 12345; + int64_t cfgId = 98765; + ConfigKey cfgKey(uid, cfgId); + uint64_t bucketStartTimeNs = 10000000000; // 0:10 + uint64_t bucketSizeNs = + TimeUnitToBucketSizeInMillis(config.duration_metric(0).bucket()) * 1000000LL; + auto processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey); + + ASSERT_EQ(processor->mMetricsManagers.size(), 1u); + sp metricsManager = processor->mMetricsManagers.begin()->second; + EXPECT_TRUE(metricsManager->isConfigValid()); + ASSERT_EQ(metricsManager->mAllMetricProducers.size(), 1); + EXPECT_TRUE(metricsManager->isActive()); + sp metricProducer = metricsManager->mAllMetricProducers[0]; + EXPECT_TRUE(metricProducer->mIsActive); + ASSERT_EQ(metricProducer->mSlicedStateAtoms.size(), 1); + EXPECT_EQ(metricProducer->mSlicedStateAtoms.at(0), SCREEN_STATE_ATOM_ID); + ASSERT_EQ(metricProducer->mStateGroupMap.size(), 0); + + // Check that StateTrackers were initialized correctly. + EXPECT_EQ(1, StateManager::getInstance().getStateTrackersCount()); + EXPECT_EQ(1, StateManager::getInstance().getListenersCount(SCREEN_STATE_ATOM_ID)); + + /* + bucket #1 bucket #2 + | 1 2 3 4 5 6 7 8 9 10 (minutes) + |-----------------------------|-----------------------------|-- + ON OFF ON (BatterySaverMode) + | | | (ScreenIsOnEvent) + | | (ScreenIsOffEvent) + | (ScreenDozeEvent) + */ + // Initialize log events. + std::vector> events; + events.push_back(CreateScreenStateChangedEvent( + bucketStartTimeNs + 10 * NS_PER_SEC, + android::view::DisplayStateEnum::DISPLAY_STATE_ON)); // 0:20 + events.push_back(CreateBatterySaverOnEvent(bucketStartTimeNs + 20 * NS_PER_SEC)); // 0:30 + events.push_back(CreateScreenStateChangedEvent( + bucketStartTimeNs + 50 * NS_PER_SEC, + android::view::DisplayStateEnum::DISPLAY_STATE_OFF)); // 1:00 + events.push_back(CreateScreenStateChangedEvent( + bucketStartTimeNs + 80 * NS_PER_SEC, + android::view::DisplayStateEnum::DISPLAY_STATE_DOZE)); // 1:30 + events.push_back(CreateScreenStateChangedEvent( + bucketStartTimeNs + 120 * NS_PER_SEC, + android::view::DisplayStateEnum::DISPLAY_STATE_ON)); // 2:10 + events.push_back(CreateBatterySaverOffEvent(bucketStartTimeNs + 200 * NS_PER_SEC)); // 3:30 + events.push_back(CreateScreenStateChangedEvent( + bucketStartTimeNs + 250 * NS_PER_SEC, + android::view::DisplayStateEnum::DISPLAY_STATE_OFF)); // 4:20 + events.push_back(CreateBatterySaverOnEvent(bucketStartTimeNs + 280 * NS_PER_SEC)); // 4:50 + + // Bucket boundary. + events.push_back(CreateScreenStateChangedEvent( + bucketStartTimeNs + 310 * NS_PER_SEC, + android::view::DisplayStateEnum::DISPLAY_STATE_ON)); // 5:20 + + // Send log events to StatsLogProcessor. + for (auto& event : events) { + processor->OnLogEvent(event.get()); + } + + // Check dump report. + vector buffer; + ConfigMetricsReportList reports; + processor->onDumpReport(cfgKey, bucketStartTimeNs + 360 * NS_PER_SEC, + true /* include current partial bucket */, true, ADB_DUMP, FAST, + &buffer); // 6:10 + ASSERT_GT(buffer.size(), 0); + EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size())); + backfillDimensionPath(&reports); + backfillStringInReport(&reports); + backfillStartEndTimestamp(&reports); + + ASSERT_EQ(1, reports.reports_size()); + ASSERT_EQ(1, reports.reports(0).metrics_size()); + EXPECT_TRUE(reports.reports(0).metrics(0).has_duration_metrics()); + StatsLogReport::DurationMetricDataWrapper durationMetrics; + sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).duration_metrics(), + &durationMetrics); + ASSERT_EQ(3, durationMetrics.data_size()); + + DurationMetricData data = durationMetrics.data(0); + ASSERT_EQ(1, data.slice_by_state_size()); + EXPECT_EQ(SCREEN_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); + EXPECT_TRUE(data.slice_by_state(0).has_value()); + EXPECT_EQ(android::view::DisplayStateEnum::DISPLAY_STATE_OFF, data.slice_by_state(0).value()); + ASSERT_EQ(2, data.bucket_info_size()); + EXPECT_EQ(50 * NS_PER_SEC, data.bucket_info(0).duration_nanos()); + EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).start_bucket_elapsed_nanos()); + EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(0).end_bucket_elapsed_nanos()); + EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(1).duration_nanos()); + EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(1).start_bucket_elapsed_nanos()); + EXPECT_EQ(370 * NS_PER_SEC, data.bucket_info(1).end_bucket_elapsed_nanos()); + + data = durationMetrics.data(1); + ASSERT_EQ(1, data.slice_by_state_size()); + EXPECT_EQ(SCREEN_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); + EXPECT_TRUE(data.slice_by_state(0).has_value()); + EXPECT_EQ(android::view::DisplayStateEnum::DISPLAY_STATE_ON, data.slice_by_state(0).value()); + ASSERT_EQ(2, data.bucket_info_size()); + EXPECT_EQ(110 * NS_PER_SEC, data.bucket_info(0).duration_nanos()); + EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).start_bucket_elapsed_nanos()); + EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(0).end_bucket_elapsed_nanos()); + EXPECT_EQ(50 * NS_PER_SEC, data.bucket_info(1).duration_nanos()); + EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(1).start_bucket_elapsed_nanos()); + EXPECT_EQ(370 * NS_PER_SEC, data.bucket_info(1).end_bucket_elapsed_nanos()); + + data = durationMetrics.data(2); + ASSERT_EQ(1, data.slice_by_state_size()); + EXPECT_EQ(SCREEN_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); + EXPECT_TRUE(data.slice_by_state(0).has_value()); + EXPECT_EQ(android::view::DisplayStateEnum::DISPLAY_STATE_DOZE, data.slice_by_state(0).value()); + ASSERT_EQ(1, data.bucket_info_size()); + EXPECT_EQ(40 * NS_PER_SEC, data.bucket_info(0).duration_nanos()); + EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).start_bucket_elapsed_nanos()); + EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(0).end_bucket_elapsed_nanos()); +} + +TEST(DurationMetricE2eTest, TestWithConditionAndSlicedState) { + // Initialize config. + StatsdConfig config; + config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root. + + *config.add_atom_matcher() = CreateBatterySaverModeStartAtomMatcher(); + *config.add_atom_matcher() = CreateBatterySaverModeStopAtomMatcher(); + *config.add_atom_matcher() = CreateBatteryStateNoneMatcher(); + *config.add_atom_matcher() = CreateBatteryStateUsbMatcher(); + + auto batterySaverModePredicate = CreateBatterySaverModePredicate(); + *config.add_predicate() = batterySaverModePredicate; + + auto deviceUnpluggedPredicate = CreateDeviceUnpluggedPredicate(); + *config.add_predicate() = deviceUnpluggedPredicate; + + auto screenState = CreateScreenState(); + *config.add_state() = screenState; + + // Create duration metric that has a condition and slices by screen state. + auto durationMetric = config.add_duration_metric(); + durationMetric->set_id(StringToId("DurationBatterySaverModeOnBatterySliceScreen")); + durationMetric->set_what(batterySaverModePredicate.id()); + durationMetric->set_condition(deviceUnpluggedPredicate.id()); + durationMetric->add_slice_by_state(screenState.id()); + durationMetric->set_aggregation_type(DurationMetric::SUM); + durationMetric->set_bucket(FIVE_MINUTES); + + // Initialize StatsLogProcessor. + int uid = 12345; + int64_t cfgId = 98765; + ConfigKey cfgKey(uid, cfgId); + uint64_t bucketStartTimeNs = 10000000000; // 0:10 + uint64_t bucketSizeNs = + TimeUnitToBucketSizeInMillis(config.duration_metric(0).bucket()) * 1000000LL; + auto processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey); + + ASSERT_EQ(processor->mMetricsManagers.size(), 1u); + sp metricsManager = processor->mMetricsManagers.begin()->second; + EXPECT_TRUE(metricsManager->isConfigValid()); + ASSERT_EQ(metricsManager->mAllMetricProducers.size(), 1); + EXPECT_TRUE(metricsManager->isActive()); + sp metricProducer = metricsManager->mAllMetricProducers[0]; + EXPECT_TRUE(metricProducer->mIsActive); + ASSERT_EQ(metricProducer->mSlicedStateAtoms.size(), 1); + EXPECT_EQ(metricProducer->mSlicedStateAtoms.at(0), SCREEN_STATE_ATOM_ID); + ASSERT_EQ(metricProducer->mStateGroupMap.size(), 0); + + // Check that StateTrackers were initialized correctly. + EXPECT_EQ(1, StateManager::getInstance().getStateTrackersCount()); + EXPECT_EQ(1, StateManager::getInstance().getListenersCount(SCREEN_STATE_ATOM_ID)); + + /* + bucket #1 bucket #2 + | 1 2 3 4 5 6 7 8 (minutes) + |---------------------------------------|------------------ + ON OFF ON (BatterySaverMode) + T F T (DeviceUnpluggedPredicate) + | | | (ScreenIsOnEvent) + | | | (ScreenIsOffEvent) + | (ScreenDozeEvent) + */ + // Initialize log events. + std::vector> events; + events.push_back(CreateScreenStateChangedEvent( + bucketStartTimeNs + 20 * NS_PER_SEC, + android::view::DisplayStateEnum::DISPLAY_STATE_ON)); // 0:30 + events.push_back(CreateBatterySaverOnEvent(bucketStartTimeNs + 60 * NS_PER_SEC)); // 1:10 + events.push_back(CreateScreenStateChangedEvent( + bucketStartTimeNs + 80 * NS_PER_SEC, + android::view::DisplayStateEnum::DISPLAY_STATE_OFF)); // 1:30 + events.push_back( + CreateBatteryStateChangedEvent(bucketStartTimeNs + 110 * NS_PER_SEC, + BatteryPluggedStateEnum::BATTERY_PLUGGED_NONE)); // 2:00 + events.push_back(CreateScreenStateChangedEvent( + bucketStartTimeNs + 145 * NS_PER_SEC, + android::view::DisplayStateEnum::DISPLAY_STATE_ON)); // 2:35 + events.push_back(CreateScreenStateChangedEvent( + bucketStartTimeNs + 170 * NS_PER_SEC, + android::view::DisplayStateEnum::DISPLAY_STATE_OFF)); // 3:00 + events.push_back( + CreateBatteryStateChangedEvent(bucketStartTimeNs + 180 * NS_PER_SEC, + BatteryPluggedStateEnum::BATTERY_PLUGGED_USB)); // 3:10 + events.push_back(CreateScreenStateChangedEvent( + bucketStartTimeNs + 200 * NS_PER_SEC, + android::view::DisplayStateEnum::DISPLAY_STATE_DOZE)); // 3:30 + events.push_back( + CreateBatteryStateChangedEvent(bucketStartTimeNs + 230 * NS_PER_SEC, + BatteryPluggedStateEnum::BATTERY_PLUGGED_NONE)); // 4:00 + events.push_back(CreateScreenStateChangedEvent( + bucketStartTimeNs + 260 * NS_PER_SEC, + android::view::DisplayStateEnum::DISPLAY_STATE_ON)); // 4:30 + events.push_back(CreateBatterySaverOffEvent(bucketStartTimeNs + 280 * NS_PER_SEC)); // 4:50 + + // Bucket boundary. + events.push_back(CreateBatterySaverOnEvent(bucketStartTimeNs + 320 * NS_PER_SEC)); // 5:30 + events.push_back(CreateScreenStateChangedEvent( + bucketStartTimeNs + 380 * NS_PER_SEC, + android::view::DisplayStateEnum::DISPLAY_STATE_OFF)); // 6:30 + + // Send log events to StatsLogProcessor. + for (auto& event : events) { + processor->OnLogEvent(event.get()); + } + + // Check dump report. + vector buffer; + ConfigMetricsReportList reports; + processor->onDumpReport(cfgKey, bucketStartTimeNs + 410 * NS_PER_SEC, + true /* include current partial bucket */, true, ADB_DUMP, FAST, + &buffer); + ASSERT_GT(buffer.size(), 0); + EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size())); + backfillDimensionPath(&reports); + backfillStringInReport(&reports); + backfillStartEndTimestamp(&reports); + + ASSERT_EQ(1, reports.reports_size()); + ASSERT_EQ(1, reports.reports(0).metrics_size()); + EXPECT_TRUE(reports.reports(0).metrics(0).has_duration_metrics()); + StatsLogReport::DurationMetricDataWrapper durationMetrics; + sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).duration_metrics(), + &durationMetrics); + ASSERT_EQ(3, durationMetrics.data_size()); + + DurationMetricData data = durationMetrics.data(0); + ASSERT_EQ(1, data.slice_by_state_size()); + EXPECT_EQ(SCREEN_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); + EXPECT_TRUE(data.slice_by_state(0).has_value()); + EXPECT_EQ(android::view::DisplayStateEnum::DISPLAY_STATE_OFF, data.slice_by_state(0).value()); + ASSERT_EQ(2, data.bucket_info_size()); + EXPECT_EQ(45 * NS_PER_SEC, data.bucket_info(0).duration_nanos()); + EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).start_bucket_elapsed_nanos()); + EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(0).end_bucket_elapsed_nanos()); + EXPECT_EQ(30 * NS_PER_SEC, data.bucket_info(1).duration_nanos()); + EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(1).start_bucket_elapsed_nanos()); + EXPECT_EQ(420 * NS_PER_SEC, data.bucket_info(1).end_bucket_elapsed_nanos()); + + data = durationMetrics.data(1); + ASSERT_EQ(1, data.slice_by_state_size()); + EXPECT_EQ(SCREEN_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); + EXPECT_TRUE(data.slice_by_state(0).has_value()); + EXPECT_EQ(android::view::DisplayStateEnum::DISPLAY_STATE_ON, data.slice_by_state(0).value()); + ASSERT_EQ(2, data.bucket_info_size()); + EXPECT_EQ(45 * NS_PER_SEC, data.bucket_info(0).duration_nanos()); + EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).start_bucket_elapsed_nanos()); + EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(0).end_bucket_elapsed_nanos()); + EXPECT_EQ(60 * NS_PER_SEC, data.bucket_info(1).duration_nanos()); + EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(1).start_bucket_elapsed_nanos()); + EXPECT_EQ(420 * NS_PER_SEC, data.bucket_info(1).end_bucket_elapsed_nanos()); + + data = durationMetrics.data(2); + ASSERT_EQ(1, data.slice_by_state_size()); + EXPECT_EQ(SCREEN_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); + EXPECT_TRUE(data.slice_by_state(0).has_value()); + EXPECT_EQ(android::view::DisplayStateEnum::DISPLAY_STATE_DOZE, data.slice_by_state(0).value()); + ASSERT_EQ(1, data.bucket_info_size()); + EXPECT_EQ(30 * NS_PER_SEC, data.bucket_info(0).duration_nanos()); + EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).start_bucket_elapsed_nanos()); + EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(0).end_bucket_elapsed_nanos()); +} + +TEST(DurationMetricE2eTest, TestWithSlicedStateMapped) { + // Initialize config. + StatsdConfig config; + config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root. + + *config.add_atom_matcher() = CreateBatterySaverModeStartAtomMatcher(); + *config.add_atom_matcher() = CreateBatterySaverModeStopAtomMatcher(); + + auto batterySaverModePredicate = CreateBatterySaverModePredicate(); + *config.add_predicate() = batterySaverModePredicate; + + int64_t screenOnId = 4444; + int64_t screenOffId = 9876; + auto screenStateWithMap = CreateScreenStateWithOnOffMap(screenOnId, screenOffId); + *config.add_state() = screenStateWithMap; + + // Create duration metric that slices by mapped screen state. + auto durationMetric = config.add_duration_metric(); + durationMetric->set_id(StringToId("DurationBatterySaverModeSliceScreenMapped")); + durationMetric->set_what(batterySaverModePredicate.id()); + durationMetric->add_slice_by_state(screenStateWithMap.id()); + durationMetric->set_aggregation_type(DurationMetric::SUM); + durationMetric->set_bucket(FIVE_MINUTES); + + // Initialize StatsLogProcessor. + int uid = 12345; + int64_t cfgId = 98765; + ConfigKey cfgKey(uid, cfgId); + uint64_t bucketStartTimeNs = 10000000000; // 0:10 + uint64_t bucketSizeNs = + TimeUnitToBucketSizeInMillis(config.duration_metric(0).bucket()) * 1000000LL; + auto processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey); + + ASSERT_EQ(processor->mMetricsManagers.size(), 1u); + sp metricsManager = processor->mMetricsManagers.begin()->second; + EXPECT_TRUE(metricsManager->isConfigValid()); + ASSERT_EQ(metricsManager->mAllMetricProducers.size(), 1); + EXPECT_TRUE(metricsManager->isActive()); + sp metricProducer = metricsManager->mAllMetricProducers[0]; + EXPECT_TRUE(metricProducer->mIsActive); + ASSERT_EQ(metricProducer->mSlicedStateAtoms.size(), 1); + EXPECT_EQ(metricProducer->mSlicedStateAtoms.at(0), SCREEN_STATE_ATOM_ID); + ASSERT_EQ(metricProducer->mStateGroupMap.size(), 1); + + // Check that StateTrackers were initialized correctly. + EXPECT_EQ(1, StateManager::getInstance().getStateTrackersCount()); + EXPECT_EQ(1, StateManager::getInstance().getListenersCount(SCREEN_STATE_ATOM_ID)); + + /* + bucket #1 bucket #2 + | 1 2 3 4 5 6 7 8 9 10 (minutes) + |-----------------------------|-----------------------------|-- + ON OFF ON (BatterySaverMode) + ---------------------------------------------------------SCREEN_OFF events + | | (ScreenStateOffEvent = 1) + | (ScreenStateDozeEvent = 3) + | (ScreenStateDozeSuspendEvent = 4) + ---------------------------------------------------------SCREEN_ON events + | | | (ScreenStateOnEvent = 2) + | (ScreenStateVrEvent = 5) + | (ScreenStateOnSuspendEvent = 6) + */ + // Initialize log events. + std::vector> events; + events.push_back(CreateScreenStateChangedEvent( + bucketStartTimeNs + 10 * NS_PER_SEC, + android::view::DisplayStateEnum::DISPLAY_STATE_ON)); // 0:20 + events.push_back(CreateBatterySaverOnEvent(bucketStartTimeNs + 20 * NS_PER_SEC)); // 0:30 + events.push_back(CreateScreenStateChangedEvent( + bucketStartTimeNs + 70 * NS_PER_SEC, + android::view::DisplayStateEnum::DISPLAY_STATE_OFF)); // 1:20 + events.push_back(CreateScreenStateChangedEvent( + bucketStartTimeNs + 100 * NS_PER_SEC, + android::view::DisplayStateEnum::DISPLAY_STATE_DOZE)); // 1:50 + events.push_back(CreateScreenStateChangedEvent( + bucketStartTimeNs + 120 * NS_PER_SEC, + android::view::DisplayStateEnum::DISPLAY_STATE_ON)); // 2:10 + events.push_back(CreateScreenStateChangedEvent( + bucketStartTimeNs + 170 * NS_PER_SEC, + android::view::DisplayStateEnum::DISPLAY_STATE_VR)); // 3:00 + events.push_back(CreateBatterySaverOffEvent(bucketStartTimeNs + 200 * NS_PER_SEC)); // 3:30 + events.push_back(CreateScreenStateChangedEvent( + bucketStartTimeNs + 250 * NS_PER_SEC, + android::view::DisplayStateEnum::DISPLAY_STATE_OFF)); // 4:20 + events.push_back(CreateBatterySaverOnEvent(bucketStartTimeNs + 280 * NS_PER_SEC)); // 4:50 + + // Bucket boundary 5:10. + events.push_back(CreateScreenStateChangedEvent( + bucketStartTimeNs + 320 * NS_PER_SEC, + android::view::DisplayStateEnum::DISPLAY_STATE_ON)); // 5:30 + events.push_back(CreateScreenStateChangedEvent( + bucketStartTimeNs + 390 * NS_PER_SEC, + android::view::DisplayStateEnum::DISPLAY_STATE_ON_SUSPEND)); // 6:40 + events.push_back(CreateScreenStateChangedEvent( + bucketStartTimeNs + 430 * NS_PER_SEC, + android::view::DisplayStateEnum::DISPLAY_STATE_DOZE_SUSPEND)); // 7:20 + // Send log events to StatsLogProcessor. + for (auto& event : events) { + processor->OnLogEvent(event.get()); + } + + // Check dump report. + vector buffer; + ConfigMetricsReportList reports; + processor->onDumpReport(cfgKey, bucketStartTimeNs + 490 * NS_PER_SEC, + true /* include current partial bucket */, true, ADB_DUMP, FAST, + &buffer); + ASSERT_GT(buffer.size(), 0); + EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size())); + backfillDimensionPath(&reports); + backfillStringInReport(&reports); + backfillStartEndTimestamp(&reports); + + ASSERT_EQ(1, reports.reports_size()); + ASSERT_EQ(1, reports.reports(0).metrics_size()); + EXPECT_TRUE(reports.reports(0).metrics(0).has_duration_metrics()); + StatsLogReport::DurationMetricDataWrapper durationMetrics; + sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).duration_metrics(), + &durationMetrics); + ASSERT_EQ(2, durationMetrics.data_size()); + + DurationMetricData data = durationMetrics.data(0); + ASSERT_EQ(1, data.slice_by_state_size()); + EXPECT_EQ(SCREEN_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); + EXPECT_TRUE(data.slice_by_state(0).has_group_id()); + EXPECT_EQ(screenOnId, data.slice_by_state(0).group_id()); + ASSERT_EQ(2, data.bucket_info_size()); + EXPECT_EQ(130 * NS_PER_SEC, data.bucket_info(0).duration_nanos()); + EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).start_bucket_elapsed_nanos()); + EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(0).end_bucket_elapsed_nanos()); + EXPECT_EQ(110 * NS_PER_SEC, data.bucket_info(1).duration_nanos()); + EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(1).start_bucket_elapsed_nanos()); + EXPECT_EQ(500 * NS_PER_SEC, data.bucket_info(1).end_bucket_elapsed_nanos()); + + data = durationMetrics.data(1); + ASSERT_EQ(1, data.slice_by_state_size()); + EXPECT_EQ(SCREEN_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); + EXPECT_TRUE(data.slice_by_state(0).has_group_id()); + EXPECT_EQ(screenOffId, data.slice_by_state(0).group_id()); + ASSERT_EQ(2, data.bucket_info_size()); + EXPECT_EQ(70 * NS_PER_SEC, data.bucket_info(0).duration_nanos()); + EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).start_bucket_elapsed_nanos()); + EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(0).end_bucket_elapsed_nanos()); + EXPECT_EQ(80 * NS_PER_SEC, data.bucket_info(1).duration_nanos()); + EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(1).start_bucket_elapsed_nanos()); + EXPECT_EQ(500 * NS_PER_SEC, data.bucket_info(1).end_bucket_elapsed_nanos()); +} + +TEST(DurationMetricE2eTest, TestSlicedStatePrimaryFieldsNotSubsetDimInWhat) { + // Initialize config. + StatsdConfig config; + config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root. + + *config.add_atom_matcher() = CreateAcquireWakelockAtomMatcher(); + *config.add_atom_matcher() = CreateReleaseWakelockAtomMatcher(); + + auto holdingWakelockPredicate = CreateHoldingWakelockPredicate(); + *config.add_predicate() = holdingWakelockPredicate; + + auto uidProcessState = CreateUidProcessState(); + *config.add_state() = uidProcessState; + + // Create duration metric that slices by uid process state. + auto durationMetric = config.add_duration_metric(); + durationMetric->set_id(StringToId("DurationHoldingWakelockSliceUidProcessState")); + durationMetric->set_what(holdingWakelockPredicate.id()); + durationMetric->add_slice_by_state(uidProcessState.id()); + durationMetric->set_aggregation_type(DurationMetric::SUM); + durationMetric->set_bucket(FIVE_MINUTES); + + // The state has only one primary field (uid). + auto stateLink = durationMetric->add_state_link(); + stateLink->set_state_atom_id(UID_PROCESS_STATE_ATOM_ID); + auto fieldsInWhat = stateLink->mutable_fields_in_what(); + *fieldsInWhat = CreateAttributionUidDimensions(util::WAKELOCK_STATE_CHANGED, {Position::FIRST}); + auto fieldsInState = stateLink->mutable_fields_in_state(); + *fieldsInState = CreateDimensions(UID_PROCESS_STATE_ATOM_ID, {1 /* uid */}); + + // Initialize StatsLogProcessor. + int uid = 12345; + int64_t cfgId = 98765; + ConfigKey cfgKey(uid, cfgId); + uint64_t bucketStartTimeNs = 10000000000; // 0:10 + uint64_t bucketSizeNs = + TimeUnitToBucketSizeInMillis(config.duration_metric(0).bucket()) * 1000000LL; + auto processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey); + + // This config is rejected because the dimension in what fields are not a superset of the sliced + // state primary fields. + ASSERT_EQ(processor->mMetricsManagers.size(), 0); +} + +TEST(DurationMetricE2eTest, TestWithSlicedStatePrimaryFieldsSubset) { + // Initialize config. + StatsdConfig config; + config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root. + + *config.add_atom_matcher() = CreateAcquireWakelockAtomMatcher(); + *config.add_atom_matcher() = CreateReleaseWakelockAtomMatcher(); + + auto holdingWakelockPredicate = CreateHoldingWakelockPredicate(); + *(holdingWakelockPredicate.mutable_simple_predicate()->mutable_dimensions()) = + CreateAttributionUidAndOtherDimensions(util::WAKELOCK_STATE_CHANGED, {Position::FIRST}, + {3 /* tag */}); + *config.add_predicate() = holdingWakelockPredicate; + + auto uidProcessState = CreateUidProcessState(); + *config.add_state() = uidProcessState; + + // Create duration metric that slices by uid process state. + auto durationMetric = config.add_duration_metric(); + durationMetric->set_id(StringToId("DurationPartialWakelockPerTagUidSliceProcessState")); + durationMetric->set_what(holdingWakelockPredicate.id()); + durationMetric->add_slice_by_state(uidProcessState.id()); + durationMetric->set_aggregation_type(DurationMetric::SUM); + durationMetric->set_bucket(FIVE_MINUTES); + + // The metric is dimensioning by first uid of attribution node and tag. + *durationMetric->mutable_dimensions_in_what() = CreateAttributionUidAndOtherDimensions( + util::WAKELOCK_STATE_CHANGED, {Position::FIRST}, {3 /* tag */}); + // The state has only one primary field (uid). + auto stateLink = durationMetric->add_state_link(); + stateLink->set_state_atom_id(UID_PROCESS_STATE_ATOM_ID); + auto fieldsInWhat = stateLink->mutable_fields_in_what(); + *fieldsInWhat = CreateAttributionUidDimensions(util::WAKELOCK_STATE_CHANGED, {Position::FIRST}); + auto fieldsInState = stateLink->mutable_fields_in_state(); + *fieldsInState = CreateDimensions(UID_PROCESS_STATE_ATOM_ID, {1 /* uid */}); + + // Initialize StatsLogProcessor. + int uid = 12345; + int64_t cfgId = 98765; + ConfigKey cfgKey(uid, cfgId); + uint64_t bucketStartTimeNs = 10000000000; // 0:10 + uint64_t bucketSizeNs = + TimeUnitToBucketSizeInMillis(config.duration_metric(0).bucket()) * 1000000LL; + auto processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey); + + ASSERT_EQ(processor->mMetricsManagers.size(), 1u); + sp metricsManager = processor->mMetricsManagers.begin()->second; + EXPECT_TRUE(metricsManager->isConfigValid()); + ASSERT_EQ(metricsManager->mAllMetricProducers.size(), 1); + EXPECT_TRUE(metricsManager->isActive()); + sp metricProducer = metricsManager->mAllMetricProducers[0]; + EXPECT_TRUE(metricProducer->mIsActive); + ASSERT_EQ(metricProducer->mSlicedStateAtoms.size(), 1); + EXPECT_EQ(metricProducer->mSlicedStateAtoms.at(0), UID_PROCESS_STATE_ATOM_ID); + ASSERT_EQ(metricProducer->mStateGroupMap.size(), 0); + + // Check that StateTrackers were initialized correctly. + EXPECT_EQ(1, StateManager::getInstance().getStateTrackersCount()); + EXPECT_EQ(1, StateManager::getInstance().getListenersCount(UID_PROCESS_STATE_ATOM_ID)); + + // Initialize log events. + int appUid1 = 1001; + int appUid2 = 1002; + std::vector attributionUids1 = {appUid1}; + std::vector attributionTags1 = {"App1"}; + + std::vector attributionUids2 = {appUid2}; + std::vector attributionTags2 = {"App2"}; + + std::vector> events; + events.push_back(CreateUidProcessStateChangedEvent( + bucketStartTimeNs + 10 * NS_PER_SEC, appUid1, + android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_FOREGROUND)); // 0:20 + events.push_back(CreateAcquireWakelockEvent(bucketStartTimeNs + 20 * NS_PER_SEC, + attributionUids1, attributionTags1, + "wakelock1")); // 0:30 + events.push_back(CreateAcquireWakelockEvent(bucketStartTimeNs + 25 * NS_PER_SEC, + attributionUids1, attributionTags1, + "wakelock2")); // 0:35 + events.push_back(CreateAcquireWakelockEvent(bucketStartTimeNs + 30 * NS_PER_SEC, + attributionUids2, attributionTags2, + "wakelock1")); // 0:40 + events.push_back(CreateAcquireWakelockEvent(bucketStartTimeNs + 35 * NS_PER_SEC, + attributionUids2, attributionTags2, + "wakelock2")); // 0:45 + events.push_back(CreateUidProcessStateChangedEvent( + bucketStartTimeNs + 50 * NS_PER_SEC, appUid2, + android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_BACKGROUND)); // 1:00 + events.push_back(CreateUidProcessStateChangedEvent( + bucketStartTimeNs + 60 * NS_PER_SEC, appUid1, + android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_BACKGROUND)); // 1:10 + events.push_back(CreateReleaseWakelockEvent(bucketStartTimeNs + 100 * NS_PER_SEC, + attributionUids2, attributionTags2, + "wakelock1")); // 1:50 + events.push_back(CreateUidProcessStateChangedEvent( + bucketStartTimeNs + 120 * NS_PER_SEC, appUid2, + android::app::ProcessStateEnum::PROCESS_STATE_FOREGROUND_SERVICE)); // 2:10 + events.push_back(CreateReleaseWakelockEvent(bucketStartTimeNs + 200 * NS_PER_SEC, + attributionUids1, attributionTags1, + "wakelock2")); // 3:30 + + // Send log events to StatsLogProcessor. + for (auto& event : events) { + processor->OnLogEvent(event.get()); + } + + // Check dump report. + vector buffer; + ConfigMetricsReportList reports; + processor->onDumpReport(cfgKey, bucketStartTimeNs + 320 * NS_PER_SEC, + true /* include current partial bucket */, true, ADB_DUMP, FAST, + &buffer); + ASSERT_GT(buffer.size(), 0); + EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size())); + backfillDimensionPath(&reports); + backfillStringInReport(&reports); + backfillStartEndTimestamp(&reports); + + ASSERT_EQ(1, reports.reports_size()); + ASSERT_EQ(1, reports.reports(0).metrics_size()); + EXPECT_TRUE(reports.reports(0).metrics(0).has_duration_metrics()); + StatsLogReport::DurationMetricDataWrapper durationMetrics; + sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).duration_metrics(), + &durationMetrics); + ASSERT_EQ(9, durationMetrics.data_size()); + + DurationMetricData data = durationMetrics.data(0); + ValidateWakelockAttributionUidAndTagDimension(data.dimensions_in_what(), 10, appUid1, + "wakelock1"); + ASSERT_EQ(1, data.slice_by_state_size()); + EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); + EXPECT_TRUE(data.slice_by_state(0).has_value()); + EXPECT_EQ(android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_FOREGROUND, + data.slice_by_state(0).value()); + ASSERT_EQ(1, data.bucket_info_size()); + EXPECT_EQ(40 * NS_PER_SEC, data.bucket_info(0).duration_nanos()); + EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).start_bucket_elapsed_nanos()); + EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(0).end_bucket_elapsed_nanos()); + + data = durationMetrics.data(1); + ValidateWakelockAttributionUidAndTagDimension(data.dimensions_in_what(), 10, appUid1, + "wakelock1"); + ASSERT_EQ(1, data.slice_by_state_size()); + EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); + EXPECT_TRUE(data.slice_by_state(0).has_value()); + EXPECT_EQ(android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_BACKGROUND, + data.slice_by_state(0).value()); + ASSERT_EQ(2, data.bucket_info_size()); + EXPECT_EQ(240 * NS_PER_SEC, data.bucket_info(0).duration_nanos()); + EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).start_bucket_elapsed_nanos()); + EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(0).end_bucket_elapsed_nanos()); + EXPECT_EQ(20 * NS_PER_SEC, data.bucket_info(1).duration_nanos()); + EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(1).start_bucket_elapsed_nanos()); + EXPECT_EQ(330 * NS_PER_SEC, data.bucket_info(1).end_bucket_elapsed_nanos()); + + data = durationMetrics.data(2); + ValidateWakelockAttributionUidAndTagDimension(data.dimensions_in_what(), 10, appUid1, + "wakelock2"); + ASSERT_EQ(1, data.slice_by_state_size()); + EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); + EXPECT_TRUE(data.slice_by_state(0).has_value()); + EXPECT_EQ(android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_FOREGROUND, + data.slice_by_state(0).value()); + ASSERT_EQ(1, data.bucket_info_size()); + EXPECT_EQ(35 * NS_PER_SEC, data.bucket_info(0).duration_nanos()); + EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).start_bucket_elapsed_nanos()); + EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(0).end_bucket_elapsed_nanos()); + + data = durationMetrics.data(3); + ValidateWakelockAttributionUidAndTagDimension(data.dimensions_in_what(), 10, appUid1, + "wakelock2"); + ASSERT_EQ(1, data.slice_by_state_size()); + EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); + EXPECT_TRUE(data.slice_by_state(0).has_value()); + EXPECT_EQ(android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_BACKGROUND, + data.slice_by_state(0).value()); + ASSERT_EQ(1, data.bucket_info_size()); + EXPECT_EQ(140 * NS_PER_SEC, data.bucket_info(0).duration_nanos()); + EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).start_bucket_elapsed_nanos()); + EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(0).end_bucket_elapsed_nanos()); + + data = durationMetrics.data(4); + ValidateWakelockAttributionUidAndTagDimension(data.dimensions_in_what(), 10, appUid2, + "wakelock1"); + ASSERT_EQ(1, data.slice_by_state_size()); + EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); + EXPECT_TRUE(data.slice_by_state(0).has_value()); + EXPECT_EQ(-1 /* StateTracker:: kStateUnknown */, data.slice_by_state(0).value()); + ASSERT_EQ(1, data.bucket_info_size()); + EXPECT_EQ(20 * NS_PER_SEC, data.bucket_info(0).duration_nanos()); + EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).start_bucket_elapsed_nanos()); + EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(0).end_bucket_elapsed_nanos()); + + data = durationMetrics.data(5); + ValidateWakelockAttributionUidAndTagDimension(data.dimensions_in_what(), 10, appUid2, + "wakelock1"); + ASSERT_EQ(1, data.slice_by_state_size()); + EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); + EXPECT_TRUE(data.slice_by_state(0).has_value()); + EXPECT_EQ(android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_BACKGROUND, + data.slice_by_state(0).value()); + ASSERT_EQ(1, data.bucket_info_size()); + EXPECT_EQ(50 * NS_PER_SEC, data.bucket_info(0).duration_nanos()); + EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).start_bucket_elapsed_nanos()); + EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(0).end_bucket_elapsed_nanos()); + + data = durationMetrics.data(6); + ValidateWakelockAttributionUidAndTagDimension(data.dimensions_in_what(), 10, appUid2, + "wakelock2"); + ASSERT_EQ(1, data.slice_by_state_size()); + EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); + EXPECT_TRUE(data.slice_by_state(0).has_value()); + EXPECT_EQ(-1 /* StateTracker:: kStateUnknown */, data.slice_by_state(0).value()); + ASSERT_EQ(1, data.bucket_info_size()); + EXPECT_EQ(15 * NS_PER_SEC, data.bucket_info(0).duration_nanos()); + EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).start_bucket_elapsed_nanos()); + EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(0).end_bucket_elapsed_nanos()); + + data = durationMetrics.data(7); + ValidateWakelockAttributionUidAndTagDimension(data.dimensions_in_what(), 10, appUid2, + "wakelock2"); + ASSERT_EQ(1, data.slice_by_state_size()); + EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); + EXPECT_TRUE(data.slice_by_state(0).has_value()); + EXPECT_EQ(android::app::ProcessStateEnum::PROCESS_STATE_FOREGROUND_SERVICE, + data.slice_by_state(0).value()); + ASSERT_EQ(2, data.bucket_info_size()); + EXPECT_EQ(180 * NS_PER_SEC, data.bucket_info(0).duration_nanos()); + EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).start_bucket_elapsed_nanos()); + EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(0).end_bucket_elapsed_nanos()); + EXPECT_EQ(20 * NS_PER_SEC, data.bucket_info(1).duration_nanos()); + EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(1).start_bucket_elapsed_nanos()); + EXPECT_EQ(330 * NS_PER_SEC, data.bucket_info(1).end_bucket_elapsed_nanos()); + + data = durationMetrics.data(8); + ValidateWakelockAttributionUidAndTagDimension(data.dimensions_in_what(), 10, appUid2, + "wakelock2"); + ASSERT_EQ(1, data.slice_by_state_size()); + EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); + EXPECT_TRUE(data.slice_by_state(0).has_value()); + EXPECT_EQ(android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_BACKGROUND, + data.slice_by_state(0).value()); + ASSERT_EQ(1, data.bucket_info_size()); + EXPECT_EQ(70 * NS_PER_SEC, data.bucket_info(0).duration_nanos()); + EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).start_bucket_elapsed_nanos()); + EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(0).end_bucket_elapsed_nanos()); +} + +TEST(DurationMetricE2eTest, TestUploadThreshold) { + StatsdConfig config; + config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root. + + auto screenOnMatcher = CreateScreenTurnedOnAtomMatcher(); + auto screenOffMatcher = CreateScreenTurnedOffAtomMatcher(); + *config.add_atom_matcher() = screenOnMatcher; + *config.add_atom_matcher() = screenOffMatcher; + + auto durationPredicate = CreateScreenIsOnPredicate(); + *config.add_predicate() = durationPredicate; + + int64_t thresholdDurationNs = 30 * 1000 * 1000 * 1000LL; // 30 seconds + UploadThreshold threshold; + threshold.set_gt_int(thresholdDurationNs); + + int64_t metricId = 123456; + auto durationMetric = config.add_duration_metric(); + durationMetric->set_id(metricId); + durationMetric->set_what(durationPredicate.id()); + durationMetric->set_bucket(FIVE_MINUTES); + durationMetric->set_aggregation_type(DurationMetric_AggregationType_SUM); + *durationMetric->mutable_threshold() = threshold; + + const int64_t baseTimeNs = 0; // 0:00 + const int64_t configAddedTimeNs = baseTimeNs + 1 * NS_PER_SEC; // 0:01 + const int64_t bucketSizeNs = + TimeUnitToBucketSizeInMillis(config.duration_metric(0).bucket()) * 1000LL * 1000LL; + + int uid = 12345; + int64_t cfgId = 98765; + ConfigKey cfgKey(uid, cfgId); + + auto processor = CreateStatsLogProcessor(baseTimeNs, configAddedTimeNs, config, cfgKey); + + ASSERT_EQ(processor->mMetricsManagers.size(), 1u); + sp metricsManager = processor->mMetricsManagers.begin()->second; + EXPECT_TRUE(metricsManager->isConfigValid()); + ASSERT_EQ(metricsManager->mAllMetricProducers.size(), 1); + sp metricProducer = metricsManager->mAllMetricProducers[0]; + EXPECT_TRUE(metricsManager->isActive()); + EXPECT_TRUE(metricProducer->mIsActive); + + std::unique_ptr event; + + // Screen is off at start of first bucket. + event = CreateScreenStateChangedEvent(configAddedTimeNs, + android::view::DISPLAY_STATE_OFF); // 0:01 + processor->OnLogEvent(event.get()); + + // Turn screen on. + const int64_t durationStartNs = configAddedTimeNs + 10 * NS_PER_SEC; // 0:11 + event = CreateScreenStateChangedEvent(durationStartNs, android::view::DISPLAY_STATE_ON); + processor->OnLogEvent(event.get()); + + // Turn off screen 30 seconds after turning on. + const int64_t durationEndNs = durationStartNs + 30 * NS_PER_SEC; // 0:41 + event = CreateScreenStateChangedEvent(durationEndNs, android::view::DISPLAY_STATE_OFF); + processor->OnLogEvent(event.get()); + + // Turn screen on in second bucket. + const int64_t duration2StartNs = configAddedTimeNs + bucketSizeNs + 10 * NS_PER_SEC; // 5:11 + event = CreateScreenStateChangedEvent(duration2StartNs, android::view::DISPLAY_STATE_ON); + processor->OnLogEvent(event.get()); + + // Turn off screen 31 seconds after turning on. + const int64_t duration2EndNs = duration2StartNs + 31 * NS_PER_SEC; // 5:42 + event = CreateScreenStateChangedEvent(duration2EndNs, android::view::DISPLAY_STATE_OFF); + processor->OnLogEvent(event.get()); + + ConfigMetricsReportList reports; + vector buffer; + processor->onDumpReport(cfgKey, configAddedTimeNs + bucketSizeNs * 2 + 1 * NS_PER_SEC, false, + true, ADB_DUMP, FAST, &buffer); // 10:01 + EXPECT_TRUE(buffer.size() > 0); + EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size())); + backfillDimensionPath(&reports); + backfillStartEndTimestamp(&reports); + ASSERT_EQ(1, reports.reports_size()); + ASSERT_EQ(1, reports.reports(0).metrics_size()); + EXPECT_EQ(metricId, reports.reports(0).metrics(0).metric_id()); + EXPECT_TRUE(reports.reports(0).metrics(0).has_duration_metrics()); + + StatsLogReport::DurationMetricDataWrapper durationMetrics; + sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).duration_metrics(), + &durationMetrics); + ASSERT_EQ(1, durationMetrics.data_size()); + + DurationMetricData data = durationMetrics.data(0); + ASSERT_EQ(1, data.bucket_info_size()); + EXPECT_EQ(duration2EndNs - duration2StartNs, data.bucket_info(0).duration_nanos()); + EXPECT_EQ(baseTimeNs + bucketSizeNs, data.bucket_info(0).start_bucket_elapsed_nanos()); + EXPECT_EQ(baseTimeNs + bucketSizeNs * 2, data.bucket_info(0).end_bucket_elapsed_nanos()); +} + +#else +GTEST_LOG_(INFO) << "This test does nothing.\n"; +#endif + +} // namespace statsd +} // namespace os +} // namespace android diff --git a/statsd/tests/e2e/GaugeMetric_e2e_pull_test.cpp b/statsd/tests/e2e/GaugeMetric_e2e_pull_test.cpp new file mode 100644 index 00000000..1be26129 --- /dev/null +++ b/statsd/tests/e2e/GaugeMetric_e2e_pull_test.cpp @@ -0,0 +1,624 @@ +// 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 + +#include + +#include "src/StatsLogProcessor.h" +#include "src/stats_log_util.h" +#include "tests/statsd_test_util.h" + +using ::ndk::SharedRefBase; + +namespace android { +namespace os { +namespace statsd { + +#ifdef __ANDROID__ + +namespace { + +const int64_t metricId = 123456; +const int32_t ATOM_TAG = util::SUBSYSTEM_SLEEP_STATE; + +StatsdConfig CreateStatsdConfig(const GaugeMetric::SamplingType sampling_type, + bool useCondition = true) { + StatsdConfig config; + config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root. + config.add_default_pull_packages("AID_ROOT"); // Fake puller is registered with root. + auto atomMatcher = CreateSimpleAtomMatcher("TestMatcher", ATOM_TAG); + *config.add_atom_matcher() = atomMatcher; + *config.add_atom_matcher() = CreateScreenTurnedOnAtomMatcher(); + *config.add_atom_matcher() = CreateScreenTurnedOffAtomMatcher(); + + auto screenIsOffPredicate = CreateScreenIsOffPredicate(); + *config.add_predicate() = screenIsOffPredicate; + + auto gaugeMetric = config.add_gauge_metric(); + gaugeMetric->set_id(metricId); + gaugeMetric->set_what(atomMatcher.id()); + if (useCondition) { + gaugeMetric->set_condition(screenIsOffPredicate.id()); + } + gaugeMetric->set_sampling_type(sampling_type); + gaugeMetric->mutable_gauge_fields_filter()->set_include_all(true); + *gaugeMetric->mutable_dimensions_in_what() = + CreateDimensions(ATOM_TAG, {1 /* subsystem name */}); + gaugeMetric->set_bucket(FIVE_MINUTES); + gaugeMetric->set_max_pull_delay_sec(INT_MAX); + config.set_hash_strings_in_metric_report(false); + + return config; +} + +} // namespaces + +TEST(GaugeMetricE2eTest, TestRandomSamplePulledEvents) { + auto config = CreateStatsdConfig(GaugeMetric::RANDOM_ONE_SAMPLE); + int64_t baseTimeNs = getElapsedRealtimeNs(); + int64_t configAddedTimeNs = 10 * 60 * NS_PER_SEC + baseTimeNs; + int64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(config.gauge_metric(0).bucket()) * 1000000; + + ConfigKey cfgKey; + auto processor = + CreateStatsLogProcessor(baseTimeNs, configAddedTimeNs, config, cfgKey, + SharedRefBase::make(), ATOM_TAG); + ASSERT_EQ(processor->mMetricsManagers.size(), 1u); + EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid()); + processor->mPullerManager->ForceClearPullerCache(); + + int startBucketNum = processor->mMetricsManagers.begin() + ->second->mAllMetricProducers[0] + ->getCurrentBucketNum(); + EXPECT_GT(startBucketNum, (int64_t)0); + + // When creating the config, the gauge metric producer should register the alarm at the + // end of the current bucket. + ASSERT_EQ((size_t)1, processor->mPullerManager->mReceivers.size()); + EXPECT_EQ(bucketSizeNs, + processor->mPullerManager->mReceivers.begin()->second.front().intervalNs); + int64_t& nextPullTimeNs = + processor->mPullerManager->mReceivers.begin()->second.front().nextPullTimeNs; + EXPECT_EQ(baseTimeNs + startBucketNum * bucketSizeNs + bucketSizeNs, nextPullTimeNs); + + auto screenOffEvent = + CreateScreenStateChangedEvent(configAddedTimeNs + 55, android::view::DISPLAY_STATE_OFF); + processor->OnLogEvent(screenOffEvent.get()); + + // Pulling alarm arrives on time and reset the sequential pulling alarm. + processor->informPullAlarmFired(nextPullTimeNs + 1); + EXPECT_EQ(baseTimeNs + startBucketNum * bucketSizeNs + 2 * bucketSizeNs, nextPullTimeNs); + + auto screenOnEvent = CreateScreenStateChangedEvent(configAddedTimeNs + bucketSizeNs + 10, + android::view::DISPLAY_STATE_ON); + processor->OnLogEvent(screenOnEvent.get()); + + screenOffEvent = CreateScreenStateChangedEvent(configAddedTimeNs + bucketSizeNs + 100, + android::view::DISPLAY_STATE_OFF); + processor->OnLogEvent(screenOffEvent.get()); + + processor->informPullAlarmFired(nextPullTimeNs + 1); + EXPECT_EQ(baseTimeNs + startBucketNum * bucketSizeNs + 3 * bucketSizeNs, nextPullTimeNs); + + processor->informPullAlarmFired(nextPullTimeNs + 1); + EXPECT_EQ(baseTimeNs + startBucketNum * bucketSizeNs + 4 * bucketSizeNs, nextPullTimeNs); + + screenOnEvent = CreateScreenStateChangedEvent(configAddedTimeNs + 3 * bucketSizeNs + 2, + android::view::DISPLAY_STATE_ON); + processor->OnLogEvent(screenOnEvent.get()); + + processor->informPullAlarmFired(nextPullTimeNs + 3); + EXPECT_EQ(baseTimeNs + startBucketNum * bucketSizeNs + 5 * bucketSizeNs, nextPullTimeNs); + + screenOffEvent = CreateScreenStateChangedEvent(configAddedTimeNs + 5 * bucketSizeNs + 1, + android::view::DISPLAY_STATE_OFF); + processor->OnLogEvent(screenOffEvent.get()); + + processor->informPullAlarmFired(nextPullTimeNs + 2); + EXPECT_EQ(baseTimeNs + startBucketNum * bucketSizeNs + 6 * bucketSizeNs, nextPullTimeNs); + + processor->informPullAlarmFired(nextPullTimeNs + 2); + + ConfigMetricsReportList reports; + vector buffer; + processor->onDumpReport(cfgKey, configAddedTimeNs + 7 * bucketSizeNs + 10, false, true, + ADB_DUMP, FAST, &buffer); + EXPECT_TRUE(buffer.size() > 0); + EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size())); + backfillDimensionPath(&reports); + backfillStringInReport(&reports); + backfillStartEndTimestamp(&reports); + ASSERT_EQ(1, reports.reports_size()); + ASSERT_EQ(1, reports.reports(0).metrics_size()); + StatsLogReport::GaugeMetricDataWrapper gaugeMetrics; + sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).gauge_metrics(), &gaugeMetrics); + ASSERT_GT((int)gaugeMetrics.data_size(), 1); + + auto data = gaugeMetrics.data(0); + EXPECT_EQ(ATOM_TAG, data.dimensions_in_what().field()); + ASSERT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size()); + EXPECT_EQ(1 /* subsystem name field */, + data.dimensions_in_what().value_tuple().dimensions_value(0).field()); + EXPECT_FALSE(data.dimensions_in_what().value_tuple().dimensions_value(0).value_str().empty()); + ASSERT_EQ(6, data.bucket_info_size()); + + ASSERT_EQ(1, data.bucket_info(0).atom_size()); + ASSERT_EQ(1, data.bucket_info(0).elapsed_timestamp_nanos_size()); + EXPECT_EQ(configAddedTimeNs + 55, data.bucket_info(0).elapsed_timestamp_nanos(0)); + ASSERT_EQ(0, data.bucket_info(0).wall_clock_timestamp_nanos_size()); + EXPECT_EQ(baseTimeNs + 2 * bucketSizeNs, data.bucket_info(0).start_bucket_elapsed_nanos()); + EXPECT_EQ(baseTimeNs + 3 * bucketSizeNs, data.bucket_info(0).end_bucket_elapsed_nanos()); + EXPECT_TRUE(data.bucket_info(0).atom(0).subsystem_sleep_state().subsystem_name().empty()); + EXPECT_GT(data.bucket_info(0).atom(0).subsystem_sleep_state().time_millis(), 0); + + ASSERT_EQ(1, data.bucket_info(1).atom_size()); + EXPECT_EQ(baseTimeNs + 3 * bucketSizeNs + 1, data.bucket_info(1).elapsed_timestamp_nanos(0)); + EXPECT_EQ(baseTimeNs + 3 * bucketSizeNs + 1, data.bucket_info(1).elapsed_timestamp_nanos(0)); + EXPECT_EQ(baseTimeNs + 3 * bucketSizeNs, data.bucket_info(1).start_bucket_elapsed_nanos()); + EXPECT_EQ(baseTimeNs + 4 * bucketSizeNs, data.bucket_info(1).end_bucket_elapsed_nanos()); + EXPECT_TRUE(data.bucket_info(1).atom(0).subsystem_sleep_state().subsystem_name().empty()); + EXPECT_GT(data.bucket_info(1).atom(0).subsystem_sleep_state().time_millis(), 0); + + ASSERT_EQ(1, data.bucket_info(2).atom_size()); + ASSERT_EQ(1, data.bucket_info(2).elapsed_timestamp_nanos_size()); + EXPECT_EQ(baseTimeNs + 4 * bucketSizeNs + 1, data.bucket_info(2).elapsed_timestamp_nanos(0)); + EXPECT_EQ(baseTimeNs + 4 * bucketSizeNs, data.bucket_info(2).start_bucket_elapsed_nanos()); + EXPECT_EQ(baseTimeNs + 5 * bucketSizeNs, data.bucket_info(2).end_bucket_elapsed_nanos()); + EXPECT_TRUE(data.bucket_info(2).atom(0).subsystem_sleep_state().subsystem_name().empty()); + EXPECT_GT(data.bucket_info(2).atom(0).subsystem_sleep_state().time_millis(), 0); + + ASSERT_EQ(1, data.bucket_info(3).atom_size()); + ASSERT_EQ(1, data.bucket_info(3).elapsed_timestamp_nanos_size()); + EXPECT_EQ(baseTimeNs + 5 * bucketSizeNs + 1, data.bucket_info(3).elapsed_timestamp_nanos(0)); + EXPECT_EQ(baseTimeNs + 5 * bucketSizeNs, data.bucket_info(3).start_bucket_elapsed_nanos()); + EXPECT_EQ(baseTimeNs + 6 * bucketSizeNs, data.bucket_info(3).end_bucket_elapsed_nanos()); + EXPECT_TRUE(data.bucket_info(3).atom(0).subsystem_sleep_state().subsystem_name().empty()); + EXPECT_GT(data.bucket_info(3).atom(0).subsystem_sleep_state().time_millis(), 0); + + ASSERT_EQ(1, data.bucket_info(4).atom_size()); + ASSERT_EQ(1, data.bucket_info(4).elapsed_timestamp_nanos_size()); + EXPECT_EQ(baseTimeNs + 7 * bucketSizeNs + 1, data.bucket_info(4).elapsed_timestamp_nanos(0)); + EXPECT_EQ(baseTimeNs + 7 * bucketSizeNs, data.bucket_info(4).start_bucket_elapsed_nanos()); + EXPECT_EQ(baseTimeNs + 8 * bucketSizeNs, data.bucket_info(4).end_bucket_elapsed_nanos()); + EXPECT_TRUE(data.bucket_info(4).atom(0).subsystem_sleep_state().subsystem_name().empty()); + EXPECT_GT(data.bucket_info(4).atom(0).subsystem_sleep_state().time_millis(), 0); + + ASSERT_EQ(1, data.bucket_info(5).atom_size()); + ASSERT_EQ(1, data.bucket_info(5).elapsed_timestamp_nanos_size()); + EXPECT_EQ(baseTimeNs + 8 * bucketSizeNs + 2, data.bucket_info(5).elapsed_timestamp_nanos(0)); + EXPECT_EQ(baseTimeNs + 8 * bucketSizeNs, data.bucket_info(5).start_bucket_elapsed_nanos()); + EXPECT_EQ(baseTimeNs + 9 * bucketSizeNs, data.bucket_info(5).end_bucket_elapsed_nanos()); + EXPECT_TRUE(data.bucket_info(5).atom(0).subsystem_sleep_state().subsystem_name().empty()); + EXPECT_GT(data.bucket_info(5).atom(0).subsystem_sleep_state().time_millis(), 0); +} + +TEST(GaugeMetricE2eTest, TestConditionChangeToTrueSamplePulledEvents) { + auto config = CreateStatsdConfig(GaugeMetric::CONDITION_CHANGE_TO_TRUE); + int64_t baseTimeNs = getElapsedRealtimeNs(); + int64_t configAddedTimeNs = 10 * 60 * NS_PER_SEC + baseTimeNs; + int64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(config.gauge_metric(0).bucket()) * 1000000; + + ConfigKey cfgKey; + auto processor = + CreateStatsLogProcessor(baseTimeNs, configAddedTimeNs, config, cfgKey, + SharedRefBase::make(), ATOM_TAG); + ASSERT_EQ(processor->mMetricsManagers.size(), 1u); + EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid()); + processor->mPullerManager->ForceClearPullerCache(); + + int startBucketNum = processor->mMetricsManagers.begin() + ->second->mAllMetricProducers[0] + ->getCurrentBucketNum(); + EXPECT_GT(startBucketNum, (int64_t)0); + + auto screenOffEvent = + CreateScreenStateChangedEvent(configAddedTimeNs + 55, android::view::DISPLAY_STATE_OFF); + processor->OnLogEvent(screenOffEvent.get()); + + auto screenOnEvent = CreateScreenStateChangedEvent(configAddedTimeNs + bucketSizeNs + 10, + android::view::DISPLAY_STATE_ON); + processor->OnLogEvent(screenOnEvent.get()); + + screenOffEvent = CreateScreenStateChangedEvent(configAddedTimeNs + bucketSizeNs + 100, + android::view::DISPLAY_STATE_OFF); + processor->OnLogEvent(screenOffEvent.get()); + + screenOnEvent = CreateScreenStateChangedEvent(configAddedTimeNs + 3 * bucketSizeNs + 2, + android::view::DISPLAY_STATE_ON); + processor->OnLogEvent(screenOnEvent.get()); + + screenOffEvent = CreateScreenStateChangedEvent(configAddedTimeNs + 5 * bucketSizeNs + 1, + android::view::DISPLAY_STATE_OFF); + processor->OnLogEvent(screenOffEvent.get()); + screenOnEvent = CreateScreenStateChangedEvent(configAddedTimeNs + 5 * bucketSizeNs + 3, + android::view::DISPLAY_STATE_ON); + processor->OnLogEvent(screenOnEvent.get()); + screenOffEvent = CreateScreenStateChangedEvent(configAddedTimeNs + 5 * bucketSizeNs + 10, + android::view::DISPLAY_STATE_OFF); + processor->OnLogEvent(screenOffEvent.get()); + + ConfigMetricsReportList reports; + vector buffer; + processor->onDumpReport(cfgKey, configAddedTimeNs + 8 * bucketSizeNs + 10, false, true, + ADB_DUMP, FAST, &buffer); + EXPECT_TRUE(buffer.size() > 0); + EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size())); + backfillDimensionPath(&reports); + backfillStringInReport(&reports); + backfillStartEndTimestamp(&reports); + ASSERT_EQ(1, reports.reports_size()); + ASSERT_EQ(1, reports.reports(0).metrics_size()); + StatsLogReport::GaugeMetricDataWrapper gaugeMetrics; + sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).gauge_metrics(), &gaugeMetrics); + ASSERT_GT((int)gaugeMetrics.data_size(), 1); + + auto data = gaugeMetrics.data(0); + EXPECT_EQ(ATOM_TAG, data.dimensions_in_what().field()); + ASSERT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size()); + EXPECT_EQ(1 /* subsystem name field */, + data.dimensions_in_what().value_tuple().dimensions_value(0).field()); + EXPECT_FALSE(data.dimensions_in_what().value_tuple().dimensions_value(0).value_str().empty()); + ASSERT_EQ(3, data.bucket_info_size()); + + ASSERT_EQ(1, data.bucket_info(0).atom_size()); + ASSERT_EQ(1, data.bucket_info(0).elapsed_timestamp_nanos_size()); + EXPECT_EQ(configAddedTimeNs + 55, data.bucket_info(0).elapsed_timestamp_nanos(0)); + ASSERT_EQ(0, data.bucket_info(0).wall_clock_timestamp_nanos_size()); + EXPECT_EQ(baseTimeNs + 2 * bucketSizeNs, data.bucket_info(0).start_bucket_elapsed_nanos()); + EXPECT_EQ(baseTimeNs + 3 * bucketSizeNs, data.bucket_info(0).end_bucket_elapsed_nanos()); + EXPECT_TRUE(data.bucket_info(0).atom(0).subsystem_sleep_state().subsystem_name().empty()); + EXPECT_GT(data.bucket_info(0).atom(0).subsystem_sleep_state().time_millis(), 0); + + ASSERT_EQ(1, data.bucket_info(1).atom_size()); + EXPECT_EQ(baseTimeNs + 3 * bucketSizeNs + 100, data.bucket_info(1).elapsed_timestamp_nanos(0)); + EXPECT_EQ(configAddedTimeNs + 55, data.bucket_info(0).elapsed_timestamp_nanos(0)); + EXPECT_EQ(baseTimeNs + 3 * bucketSizeNs, data.bucket_info(1).start_bucket_elapsed_nanos()); + EXPECT_EQ(baseTimeNs + 4 * bucketSizeNs, data.bucket_info(1).end_bucket_elapsed_nanos()); + EXPECT_TRUE(data.bucket_info(1).atom(0).subsystem_sleep_state().subsystem_name().empty()); + EXPECT_GT(data.bucket_info(1).atom(0).subsystem_sleep_state().time_millis(), 0); + + ASSERT_EQ(2, data.bucket_info(2).atom_size()); + ASSERT_EQ(2, data.bucket_info(2).elapsed_timestamp_nanos_size()); + EXPECT_EQ(baseTimeNs + 7 * bucketSizeNs + 1, data.bucket_info(2).elapsed_timestamp_nanos(0)); + EXPECT_EQ(baseTimeNs + 7 * bucketSizeNs + 10, data.bucket_info(2).elapsed_timestamp_nanos(1)); + EXPECT_EQ(baseTimeNs + 7 * bucketSizeNs, data.bucket_info(2).start_bucket_elapsed_nanos()); + EXPECT_EQ(baseTimeNs + 8 * bucketSizeNs, data.bucket_info(2).end_bucket_elapsed_nanos()); + EXPECT_TRUE(data.bucket_info(2).atom(0).subsystem_sleep_state().subsystem_name().empty()); + EXPECT_GT(data.bucket_info(2).atom(0).subsystem_sleep_state().time_millis(), 0); + EXPECT_TRUE(data.bucket_info(2).atom(1).subsystem_sleep_state().subsystem_name().empty()); + EXPECT_GT(data.bucket_info(2).atom(1).subsystem_sleep_state().time_millis(), 0); +} + +TEST(GaugeMetricE2eTest, TestRandomSamplePulledEvent_LateAlarm) { + auto config = CreateStatsdConfig(GaugeMetric::RANDOM_ONE_SAMPLE); + int64_t baseTimeNs = getElapsedRealtimeNs(); + int64_t configAddedTimeNs = 10 * 60 * NS_PER_SEC + baseTimeNs; + int64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(config.gauge_metric(0).bucket()) * 1000000; + + ConfigKey cfgKey; + auto processor = + CreateStatsLogProcessor(baseTimeNs, configAddedTimeNs, config, cfgKey, + SharedRefBase::make(), ATOM_TAG); + ASSERT_EQ(processor->mMetricsManagers.size(), 1u); + EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid()); + processor->mPullerManager->ForceClearPullerCache(); + + int startBucketNum = processor->mMetricsManagers.begin() + ->second->mAllMetricProducers[0] + ->getCurrentBucketNum(); + EXPECT_GT(startBucketNum, (int64_t)0); + + // When creating the config, the gauge metric producer should register the alarm at the + // end of the current bucket. + ASSERT_EQ((size_t)1, processor->mPullerManager->mReceivers.size()); + EXPECT_EQ(bucketSizeNs, + processor->mPullerManager->mReceivers.begin()->second.front().intervalNs); + int64_t& nextPullTimeNs = + processor->mPullerManager->mReceivers.begin()->second.front().nextPullTimeNs; + EXPECT_EQ(baseTimeNs + startBucketNum * bucketSizeNs + bucketSizeNs, nextPullTimeNs); + + auto screenOffEvent = + CreateScreenStateChangedEvent(configAddedTimeNs + 55, android::view::DISPLAY_STATE_OFF); + processor->OnLogEvent(screenOffEvent.get()); + + auto screenOnEvent = CreateScreenStateChangedEvent(configAddedTimeNs + bucketSizeNs + 10, + android::view::DISPLAY_STATE_ON); + processor->OnLogEvent(screenOnEvent.get()); + + // Pulling alarm arrives one bucket size late. + processor->informPullAlarmFired(nextPullTimeNs + bucketSizeNs); + EXPECT_EQ(baseTimeNs + startBucketNum * bucketSizeNs + 3 * bucketSizeNs, nextPullTimeNs); + + screenOffEvent = CreateScreenStateChangedEvent(configAddedTimeNs + 3 * bucketSizeNs + 11, + android::view::DISPLAY_STATE_OFF); + processor->OnLogEvent(screenOffEvent.get()); + + // Pulling alarm arrives more than one bucket size late. + processor->informPullAlarmFired(nextPullTimeNs + bucketSizeNs + 12); + EXPECT_EQ(baseTimeNs + startBucketNum * bucketSizeNs + 5 * bucketSizeNs, nextPullTimeNs); + + ConfigMetricsReportList reports; + vector buffer; + processor->onDumpReport(cfgKey, configAddedTimeNs + 7 * bucketSizeNs + 10, false, true, + ADB_DUMP, FAST, &buffer); + EXPECT_TRUE(buffer.size() > 0); + EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size())); + backfillDimensionPath(&reports); + backfillStringInReport(&reports); + backfillStartEndTimestamp(&reports); + ASSERT_EQ(1, reports.reports_size()); + ASSERT_EQ(1, reports.reports(0).metrics_size()); + StatsLogReport::GaugeMetricDataWrapper gaugeMetrics; + sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).gauge_metrics(), &gaugeMetrics); + ASSERT_GT((int)gaugeMetrics.data_size(), 1); + + auto data = gaugeMetrics.data(0); + EXPECT_EQ(ATOM_TAG, data.dimensions_in_what().field()); + ASSERT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size()); + EXPECT_EQ(1 /* subsystem name field */, + data.dimensions_in_what().value_tuple().dimensions_value(0).field()); + EXPECT_FALSE(data.dimensions_in_what().value_tuple().dimensions_value(0).value_str().empty()); + ASSERT_EQ(3, data.bucket_info_size()); + + ASSERT_EQ(1, data.bucket_info(0).atom_size()); + ASSERT_EQ(1, data.bucket_info(0).elapsed_timestamp_nanos_size()); + EXPECT_EQ(configAddedTimeNs + 55, data.bucket_info(0).elapsed_timestamp_nanos(0)); + EXPECT_EQ(baseTimeNs + 2 * bucketSizeNs, data.bucket_info(0).start_bucket_elapsed_nanos()); + EXPECT_EQ(baseTimeNs + 3 * bucketSizeNs, data.bucket_info(0).end_bucket_elapsed_nanos()); + EXPECT_TRUE(data.bucket_info(0).atom(0).subsystem_sleep_state().subsystem_name().empty()); + EXPECT_GT(data.bucket_info(0).atom(0).subsystem_sleep_state().time_millis(), 0); + + ASSERT_EQ(1, data.bucket_info(1).atom_size()); + EXPECT_EQ(configAddedTimeNs + 3 * bucketSizeNs + 11, + data.bucket_info(1).elapsed_timestamp_nanos(0)); + EXPECT_EQ(configAddedTimeNs + 55, data.bucket_info(0).elapsed_timestamp_nanos(0)); + EXPECT_EQ(baseTimeNs + 5 * bucketSizeNs, data.bucket_info(1).start_bucket_elapsed_nanos()); + EXPECT_EQ(baseTimeNs + 6 * bucketSizeNs, data.bucket_info(1).end_bucket_elapsed_nanos()); + EXPECT_TRUE(data.bucket_info(1).atom(0).subsystem_sleep_state().subsystem_name().empty()); + EXPECT_GT(data.bucket_info(1).atom(0).subsystem_sleep_state().time_millis(), 0); + + ASSERT_EQ(1, data.bucket_info(2).atom_size()); + ASSERT_EQ(1, data.bucket_info(2).elapsed_timestamp_nanos_size()); + EXPECT_EQ(baseTimeNs + 6 * bucketSizeNs + 12, data.bucket_info(2).elapsed_timestamp_nanos(0)); + EXPECT_EQ(baseTimeNs + 6 * bucketSizeNs, data.bucket_info(2).start_bucket_elapsed_nanos()); + EXPECT_EQ(baseTimeNs + 7 * bucketSizeNs, data.bucket_info(2).end_bucket_elapsed_nanos()); + EXPECT_TRUE(data.bucket_info(2).atom(0).subsystem_sleep_state().subsystem_name().empty()); + EXPECT_GT(data.bucket_info(2).atom(0).subsystem_sleep_state().time_millis(), 0); +} + +TEST(GaugeMetricE2eTest, TestRandomSamplePulledEventsWithActivation) { + auto config = CreateStatsdConfig(GaugeMetric::RANDOM_ONE_SAMPLE, /*useCondition=*/false); + + int64_t baseTimeNs = getElapsedRealtimeNs(); + int64_t configAddedTimeNs = 10 * 60 * NS_PER_SEC + baseTimeNs; + int64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(config.gauge_metric(0).bucket()) * 1000000; + + auto batterySaverStartMatcher = CreateBatterySaverModeStartAtomMatcher(); + *config.add_atom_matcher() = batterySaverStartMatcher; + const int64_t ttlNs = 2 * bucketSizeNs; // Two buckets. + auto metric_activation = config.add_metric_activation(); + metric_activation->set_metric_id(metricId); + metric_activation->set_activation_type(ACTIVATE_IMMEDIATELY); + auto event_activation = metric_activation->add_event_activation(); + event_activation->set_atom_matcher_id(batterySaverStartMatcher.id()); + event_activation->set_ttl_seconds(ttlNs / 1000000000); + + ConfigKey cfgKey; + auto processor = + CreateStatsLogProcessor(baseTimeNs, configAddedTimeNs, config, cfgKey, + SharedRefBase::make(), ATOM_TAG); + ASSERT_EQ(processor->mMetricsManagers.size(), 1u); + EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid()); + processor->mPullerManager->ForceClearPullerCache(); + + int startBucketNum = processor->mMetricsManagers.begin() + ->second->mAllMetricProducers[0] + ->getCurrentBucketNum(); + EXPECT_GT(startBucketNum, (int64_t)0); + EXPECT_FALSE(processor->mMetricsManagers.begin()->second->mAllMetricProducers[0]->isActive()); + + // When creating the config, the gauge metric producer should register the alarm at the + // end of the current bucket. + ASSERT_EQ((size_t)1, processor->mPullerManager->mReceivers.size()); + EXPECT_EQ(bucketSizeNs, + processor->mPullerManager->mReceivers.begin()->second.front().intervalNs); + int64_t& nextPullTimeNs = + processor->mPullerManager->mReceivers.begin()->second.front().nextPullTimeNs; + EXPECT_EQ(baseTimeNs + startBucketNum * bucketSizeNs + bucketSizeNs, nextPullTimeNs); + + // Pulling alarm arrives on time and reset the sequential pulling alarm. + // Event should not be kept. + processor->informPullAlarmFired(nextPullTimeNs + 1); // 15 mins + 1 ns. + EXPECT_EQ(baseTimeNs + startBucketNum * bucketSizeNs + 2 * bucketSizeNs, nextPullTimeNs); + EXPECT_FALSE(processor->mMetricsManagers.begin()->second->mAllMetricProducers[0]->isActive()); + + // Activate the metric. A pull occurs upon activation. + const int64_t activationNs = configAddedTimeNs + bucketSizeNs + (2 * 1000 * 1000); // 2 millis. + auto batterySaverOnEvent = CreateBatterySaverOnEvent(activationNs); + processor->OnLogEvent(batterySaverOnEvent.get()); // 15 mins + 2 ms. + EXPECT_TRUE(processor->mMetricsManagers.begin()->second->mAllMetricProducers[0]->isActive()); + + // This event should be kept. 2 total. + processor->informPullAlarmFired(nextPullTimeNs + 1); // 20 mins + 1 ns. + EXPECT_EQ(baseTimeNs + startBucketNum * bucketSizeNs + 3 * bucketSizeNs, nextPullTimeNs); + + // This event should be kept. 3 total. + processor->informPullAlarmFired(nextPullTimeNs + 2); // 25 mins + 2 ns. + EXPECT_EQ(baseTimeNs + startBucketNum * bucketSizeNs + 4 * bucketSizeNs, nextPullTimeNs); + + // Create random event to deactivate metric. + auto deactivationEvent = CreateScreenBrightnessChangedEvent(activationNs + ttlNs + 1, 50); + processor->OnLogEvent(deactivationEvent.get()); + EXPECT_FALSE(processor->mMetricsManagers.begin()->second->mAllMetricProducers[0]->isActive()); + + // Event should not be kept. 3 total. + processor->informPullAlarmFired(nextPullTimeNs + 3); + EXPECT_EQ(baseTimeNs + startBucketNum * bucketSizeNs + 5 * bucketSizeNs, nextPullTimeNs); + + processor->informPullAlarmFired(nextPullTimeNs + 2); + + ConfigMetricsReportList reports; + vector buffer; + processor->onDumpReport(cfgKey, configAddedTimeNs + 7 * bucketSizeNs + 10, false, true, + ADB_DUMP, FAST, &buffer); + EXPECT_TRUE(buffer.size() > 0); + EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size())); + backfillDimensionPath(&reports); + backfillStringInReport(&reports); + backfillStartEndTimestamp(&reports); + ASSERT_EQ(1, reports.reports_size()); + ASSERT_EQ(1, reports.reports(0).metrics_size()); + StatsLogReport::GaugeMetricDataWrapper gaugeMetrics; + sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).gauge_metrics(), &gaugeMetrics); + ASSERT_GT((int)gaugeMetrics.data_size(), 0); + + auto data = gaugeMetrics.data(0); + EXPECT_EQ(ATOM_TAG, data.dimensions_in_what().field()); + ASSERT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size()); + EXPECT_EQ(1 /* subsystem name field */, + data.dimensions_in_what().value_tuple().dimensions_value(0).field()); + EXPECT_FALSE(data.dimensions_in_what().value_tuple().dimensions_value(0).value_str().empty()); + ASSERT_EQ(3, data.bucket_info_size()); + + auto bucketInfo = data.bucket_info(0); + ASSERT_EQ(1, bucketInfo.atom_size()); + ASSERT_EQ(1, bucketInfo.elapsed_timestamp_nanos_size()); + EXPECT_EQ(activationNs, bucketInfo.elapsed_timestamp_nanos(0)); + ASSERT_EQ(0, bucketInfo.wall_clock_timestamp_nanos_size()); + EXPECT_EQ(baseTimeNs + 3 * bucketSizeNs, bucketInfo.start_bucket_elapsed_nanos()); + EXPECT_EQ(baseTimeNs + 4 * bucketSizeNs, bucketInfo.end_bucket_elapsed_nanos()); + EXPECT_TRUE(bucketInfo.atom(0).subsystem_sleep_state().subsystem_name().empty()); + EXPECT_GT(bucketInfo.atom(0).subsystem_sleep_state().time_millis(), 0); + + bucketInfo = data.bucket_info(1); + ASSERT_EQ(1, bucketInfo.atom_size()); + ASSERT_EQ(1, bucketInfo.elapsed_timestamp_nanos_size()); + EXPECT_EQ(baseTimeNs + 4 * bucketSizeNs + 1, bucketInfo.elapsed_timestamp_nanos(0)); + ASSERT_EQ(0, bucketInfo.wall_clock_timestamp_nanos_size()); + EXPECT_EQ(baseTimeNs + 4 * bucketSizeNs, bucketInfo.start_bucket_elapsed_nanos()); + EXPECT_EQ(baseTimeNs + 5 * bucketSizeNs, bucketInfo.end_bucket_elapsed_nanos()); + EXPECT_TRUE(bucketInfo.atom(0).subsystem_sleep_state().subsystem_name().empty()); + EXPECT_GT(bucketInfo.atom(0).subsystem_sleep_state().time_millis(), 0); + + bucketInfo = data.bucket_info(2); + ASSERT_EQ(1, bucketInfo.atom_size()); + ASSERT_EQ(1, bucketInfo.elapsed_timestamp_nanos_size()); + EXPECT_EQ(baseTimeNs + 5 * bucketSizeNs + 2, bucketInfo.elapsed_timestamp_nanos(0)); + ASSERT_EQ(0, bucketInfo.wall_clock_timestamp_nanos_size()); + EXPECT_EQ(MillisToNano(NanoToMillis(baseTimeNs + 5 * bucketSizeNs)), + bucketInfo.start_bucket_elapsed_nanos()); + EXPECT_EQ(MillisToNano(NanoToMillis(activationNs + ttlNs + 1)), + bucketInfo.end_bucket_elapsed_nanos()); + EXPECT_TRUE(bucketInfo.atom(0).subsystem_sleep_state().subsystem_name().empty()); + EXPECT_GT(bucketInfo.atom(0).subsystem_sleep_state().time_millis(), 0); +} + +TEST(GaugeMetricE2eTest, TestRandomSamplePulledEventsNoCondition) { + auto config = CreateStatsdConfig(GaugeMetric::RANDOM_ONE_SAMPLE, /*useCondition=*/false); + + int64_t baseTimeNs = getElapsedRealtimeNs(); + int64_t configAddedTimeNs = 10 * 60 * NS_PER_SEC + baseTimeNs; + int64_t bucketSizeNs = + TimeUnitToBucketSizeInMillis(config.gauge_metric(0).bucket()) * 1000000; + + ConfigKey cfgKey; + auto processor = CreateStatsLogProcessor(baseTimeNs, configAddedTimeNs, config, cfgKey, + SharedRefBase::make(), + ATOM_TAG); + ASSERT_EQ(processor->mMetricsManagers.size(), 1u); + EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid()); + processor->mPullerManager->ForceClearPullerCache(); + + int startBucketNum = processor->mMetricsManagers.begin()->second-> + mAllMetricProducers[0]->getCurrentBucketNum(); + EXPECT_GT(startBucketNum, (int64_t)0); + + // When creating the config, the gauge metric producer should register the alarm at the + // end of the current bucket. + ASSERT_EQ((size_t)1, processor->mPullerManager->mReceivers.size()); + EXPECT_EQ(bucketSizeNs, + processor->mPullerManager->mReceivers.begin()->second.front().intervalNs); + int64_t& nextPullTimeNs = + processor->mPullerManager->mReceivers.begin()->second.front().nextPullTimeNs; + EXPECT_EQ(baseTimeNs + startBucketNum * bucketSizeNs + bucketSizeNs, nextPullTimeNs); + + // Pulling alarm arrives on time and reset the sequential pulling alarm. + processor->informPullAlarmFired(nextPullTimeNs + 1); + EXPECT_EQ(baseTimeNs + startBucketNum * bucketSizeNs + 2 * bucketSizeNs, nextPullTimeNs); + + processor->informPullAlarmFired(nextPullTimeNs + 4); + EXPECT_EQ(baseTimeNs + startBucketNum * bucketSizeNs + 3 * bucketSizeNs, + nextPullTimeNs); + + ConfigMetricsReportList reports; + vector buffer; + processor->onDumpReport(cfgKey, configAddedTimeNs + 7 * bucketSizeNs + 10, false, true, + ADB_DUMP, FAST, &buffer); + EXPECT_TRUE(buffer.size() > 0); + EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size())); + backfillDimensionPath(&reports); + backfillStringInReport(&reports); + backfillStartEndTimestamp(&reports); + ASSERT_EQ(1, reports.reports_size()); + ASSERT_EQ(1, reports.reports(0).metrics_size()); + StatsLogReport::GaugeMetricDataWrapper gaugeMetrics; + sortMetricDataByDimensionsValue( + reports.reports(0).metrics(0).gauge_metrics(), &gaugeMetrics); + ASSERT_GT((int)gaugeMetrics.data_size(), 0); + + auto data = gaugeMetrics.data(0); + EXPECT_EQ(ATOM_TAG, data.dimensions_in_what().field()); + ASSERT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size()); + EXPECT_EQ(1 /* subsystem name field */, + data.dimensions_in_what().value_tuple().dimensions_value(0).field()); + EXPECT_FALSE(data.dimensions_in_what().value_tuple().dimensions_value(0).value_str().empty()); + ASSERT_EQ(3, data.bucket_info_size()); + + ASSERT_EQ(1, data.bucket_info(0).atom_size()); + ASSERT_EQ(1, data.bucket_info(0).elapsed_timestamp_nanos_size()); + EXPECT_EQ(configAddedTimeNs, data.bucket_info(0).elapsed_timestamp_nanos(0)); + ASSERT_EQ(0, data.bucket_info(0).wall_clock_timestamp_nanos_size()); + EXPECT_EQ(baseTimeNs + 2 * bucketSizeNs, data.bucket_info(0).start_bucket_elapsed_nanos()); + EXPECT_EQ(baseTimeNs + 3 * bucketSizeNs, data.bucket_info(0).end_bucket_elapsed_nanos()); + EXPECT_TRUE(data.bucket_info(0).atom(0).subsystem_sleep_state().subsystem_name().empty()); + EXPECT_GT(data.bucket_info(0).atom(0).subsystem_sleep_state().time_millis(), 0); + + ASSERT_EQ(1, data.bucket_info(1).atom_size()); + ASSERT_EQ(1, data.bucket_info(1).elapsed_timestamp_nanos_size()); + EXPECT_EQ(baseTimeNs + 3 * bucketSizeNs + 1, data.bucket_info(1).elapsed_timestamp_nanos(0)); + ASSERT_EQ(0, data.bucket_info(1).wall_clock_timestamp_nanos_size()); + EXPECT_EQ(baseTimeNs + 3 * bucketSizeNs, data.bucket_info(1).start_bucket_elapsed_nanos()); + EXPECT_EQ(baseTimeNs + 4 * bucketSizeNs, data.bucket_info(1).end_bucket_elapsed_nanos()); + EXPECT_TRUE(data.bucket_info(1).atom(0).subsystem_sleep_state().subsystem_name().empty()); + EXPECT_GT(data.bucket_info(1).atom(0).subsystem_sleep_state().time_millis(), 0); + + ASSERT_EQ(1, data.bucket_info(2).atom_size()); + ASSERT_EQ(1, data.bucket_info(2).elapsed_timestamp_nanos_size()); + EXPECT_EQ(baseTimeNs + 4 * bucketSizeNs + 4, data.bucket_info(2).elapsed_timestamp_nanos(0)); + ASSERT_EQ(0, data.bucket_info(2).wall_clock_timestamp_nanos_size()); + EXPECT_EQ(baseTimeNs + 4 * bucketSizeNs, data.bucket_info(2).start_bucket_elapsed_nanos()); + EXPECT_EQ(baseTimeNs + 5 * bucketSizeNs, data.bucket_info(2).end_bucket_elapsed_nanos()); + EXPECT_TRUE(data.bucket_info(2).atom(0).subsystem_sleep_state().subsystem_name().empty()); + EXPECT_GT(data.bucket_info(2).atom(0).subsystem_sleep_state().time_millis(), 0); +} + +#else +GTEST_LOG_(INFO) << "This test does nothing.\n"; +#endif + +} // namespace statsd +} // namespace os +} // namespace android diff --git a/statsd/tests/e2e/GaugeMetric_e2e_push_test.cpp b/statsd/tests/e2e/GaugeMetric_e2e_push_test.cpp new file mode 100644 index 00000000..04874aa9 --- /dev/null +++ b/statsd/tests/e2e/GaugeMetric_e2e_push_test.cpp @@ -0,0 +1,274 @@ +// 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 + +#include "src/StatsLogProcessor.h" +#include "src/stats_log_util.h" +#include "stats_event.h" +#include "tests/statsd_test_util.h" + +namespace android { +namespace os { +namespace statsd { + +#ifdef __ANDROID__ + +namespace { + +StatsdConfig CreateStatsdConfigForPushedEvent(const GaugeMetric::SamplingType sampling_type) { + StatsdConfig config; + config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root. + *config.add_atom_matcher() = CreateMoveToBackgroundAtomMatcher(); + *config.add_atom_matcher() = CreateMoveToForegroundAtomMatcher(); + + auto atomMatcher = CreateSimpleAtomMatcher("", util::APP_START_OCCURRED); + *config.add_atom_matcher() = atomMatcher; + + auto isInBackgroundPredicate = CreateIsInBackgroundPredicate(); + *isInBackgroundPredicate.mutable_simple_predicate()->mutable_dimensions() = + CreateDimensions(util::ACTIVITY_FOREGROUND_STATE_CHANGED, {1 /* uid field */ }); + *config.add_predicate() = isInBackgroundPredicate; + + auto gaugeMetric = config.add_gauge_metric(); + gaugeMetric->set_id(123456); + gaugeMetric->set_what(atomMatcher.id()); + gaugeMetric->set_condition(isInBackgroundPredicate.id()); + gaugeMetric->mutable_gauge_fields_filter()->set_include_all(false); + gaugeMetric->set_sampling_type(sampling_type); + auto fieldMatcher = gaugeMetric->mutable_gauge_fields_filter()->mutable_fields(); + fieldMatcher->set_field(util::APP_START_OCCURRED); + fieldMatcher->add_child()->set_field(3); // type (enum) + fieldMatcher->add_child()->set_field(4); // activity_name(str) + fieldMatcher->add_child()->set_field(7); // activity_start_msec(int64) + *gaugeMetric->mutable_dimensions_in_what() = + CreateDimensions(util::APP_START_OCCURRED, {1 /* uid field */ }); + gaugeMetric->set_bucket(FIVE_MINUTES); + + auto links = gaugeMetric->add_links(); + links->set_condition(isInBackgroundPredicate.id()); + auto dimensionWhat = links->mutable_fields_in_what(); + dimensionWhat->set_field(util::APP_START_OCCURRED); + dimensionWhat->add_child()->set_field(1); // uid field. + auto dimensionCondition = links->mutable_fields_in_condition(); + dimensionCondition->set_field(util::ACTIVITY_FOREGROUND_STATE_CHANGED); + dimensionCondition->add_child()->set_field(1); // uid field. + return config; +} + +} // namespace + +TEST(GaugeMetricE2eTest, TestMultipleFieldsForPushedEvent) { + for (const auto& sampling_type : + {GaugeMetric::FIRST_N_SAMPLES, GaugeMetric::RANDOM_ONE_SAMPLE}) { + auto config = CreateStatsdConfigForPushedEvent(sampling_type); + int64_t bucketStartTimeNs = 10000000000; + int64_t bucketSizeNs = + TimeUnitToBucketSizeInMillis(config.gauge_metric(0).bucket()) * 1000000; + + ConfigKey cfgKey; + auto processor = + CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey); + ASSERT_EQ(processor->mMetricsManagers.size(), 1u); + EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid()); + + int appUid1 = 123; + int appUid2 = 456; + std::vector> events; + events.push_back(CreateMoveToBackgroundEvent(bucketStartTimeNs + 15, appUid1)); + events.push_back( + CreateMoveToForegroundEvent(bucketStartTimeNs + bucketSizeNs + 250, appUid1)); + events.push_back( + CreateMoveToBackgroundEvent(bucketStartTimeNs + bucketSizeNs + 350, appUid1)); + events.push_back( + CreateMoveToForegroundEvent(bucketStartTimeNs + 2 * bucketSizeNs + 100, appUid1)); + + events.push_back(CreateAppStartOccurredEvent( + bucketStartTimeNs + 10, appUid1, "app1", AppStartOccurred::WARM, "activity_name1", + "calling_pkg_name1", true /*is_instant_app*/, 101 /*activity_start_msec*/)); + events.push_back(CreateAppStartOccurredEvent( + bucketStartTimeNs + 20, appUid1, "app1", AppStartOccurred::HOT, "activity_name2", + "calling_pkg_name2", true /*is_instant_app*/, 102 /*activity_start_msec*/)); + events.push_back(CreateAppStartOccurredEvent( + bucketStartTimeNs + 30, appUid1, "app1", AppStartOccurred::COLD, "activity_name3", + "calling_pkg_name3", true /*is_instant_app*/, 103 /*activity_start_msec*/)); + events.push_back(CreateAppStartOccurredEvent( + bucketStartTimeNs + bucketSizeNs + 30, appUid1, "app1", AppStartOccurred::WARM, + "activity_name4", "calling_pkg_name4", true /*is_instant_app*/, + 104 /*activity_start_msec*/)); + events.push_back(CreateAppStartOccurredEvent( + bucketStartTimeNs + 2 * bucketSizeNs, appUid1, "app1", AppStartOccurred::COLD, + "activity_name5", "calling_pkg_name5", true /*is_instant_app*/, + 105 /*activity_start_msec*/)); + events.push_back(CreateAppStartOccurredEvent( + bucketStartTimeNs + 2 * bucketSizeNs + 10, appUid1, "app1", AppStartOccurred::HOT, + "activity_name6", "calling_pkg_name6", false /*is_instant_app*/, + 106 /*activity_start_msec*/)); + + events.push_back( + CreateMoveToBackgroundEvent(bucketStartTimeNs + bucketSizeNs + 10, appUid2)); + events.push_back(CreateAppStartOccurredEvent( + bucketStartTimeNs + 2 * bucketSizeNs + 10, appUid2, "app2", AppStartOccurred::COLD, + "activity_name7", "calling_pkg_name7", true /*is_instant_app*/, + 201 /*activity_start_msec*/)); + + sortLogEventsByTimestamp(&events); + + for (const auto& event : events) { + processor->OnLogEvent(event.get()); + } + ConfigMetricsReportList reports; + vector buffer; + processor->onDumpReport(cfgKey, bucketStartTimeNs + 3 * bucketSizeNs, false, true, ADB_DUMP, + FAST, &buffer); + EXPECT_TRUE(buffer.size() > 0); + EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size())); + backfillDimensionPath(&reports); + backfillStringInReport(&reports); + backfillStartEndTimestamp(&reports); + ASSERT_EQ(1, reports.reports_size()); + ASSERT_EQ(1, reports.reports(0).metrics_size()); + StatsLogReport::GaugeMetricDataWrapper gaugeMetrics; + sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).gauge_metrics(), + &gaugeMetrics); + ASSERT_EQ(2, gaugeMetrics.data_size()); + + auto data = gaugeMetrics.data(0); + EXPECT_EQ(util::APP_START_OCCURRED, data.dimensions_in_what().field()); + ASSERT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size()); + EXPECT_EQ(1 /* uid field */, + data.dimensions_in_what().value_tuple().dimensions_value(0).field()); + EXPECT_EQ(appUid1, data.dimensions_in_what().value_tuple().dimensions_value(0).value_int()); + ASSERT_EQ(3, data.bucket_info_size()); + if (sampling_type == GaugeMetric::FIRST_N_SAMPLES) { + ASSERT_EQ(2, data.bucket_info(0).atom_size()); + ASSERT_EQ(2, data.bucket_info(0).elapsed_timestamp_nanos_size()); + ASSERT_EQ(0, data.bucket_info(0).wall_clock_timestamp_nanos_size()); + EXPECT_EQ(bucketStartTimeNs, data.bucket_info(0).start_bucket_elapsed_nanos()); + EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, + data.bucket_info(0).end_bucket_elapsed_nanos()); + EXPECT_EQ(AppStartOccurred::HOT, + data.bucket_info(0).atom(0).app_start_occurred().type()); + EXPECT_EQ("activity_name2", + data.bucket_info(0).atom(0).app_start_occurred().activity_name()); + EXPECT_EQ(102L, + data.bucket_info(0).atom(0).app_start_occurred().activity_start_millis()); + EXPECT_EQ(AppStartOccurred::COLD, + data.bucket_info(0).atom(1).app_start_occurred().type()); + EXPECT_EQ("activity_name3", + data.bucket_info(0).atom(1).app_start_occurred().activity_name()); + EXPECT_EQ(103L, + data.bucket_info(0).atom(1).app_start_occurred().activity_start_millis()); + + ASSERT_EQ(1, data.bucket_info(1).atom_size()); + ASSERT_EQ(1, data.bucket_info(1).elapsed_timestamp_nanos_size()); + EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, + data.bucket_info(1).start_bucket_elapsed_nanos()); + EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs, + data.bucket_info(1).end_bucket_elapsed_nanos()); + EXPECT_EQ(AppStartOccurred::WARM, + data.bucket_info(1).atom(0).app_start_occurred().type()); + EXPECT_EQ("activity_name4", + data.bucket_info(1).atom(0).app_start_occurred().activity_name()); + EXPECT_EQ(104L, + data.bucket_info(1).atom(0).app_start_occurred().activity_start_millis()); + + ASSERT_EQ(2, data.bucket_info(2).atom_size()); + ASSERT_EQ(2, data.bucket_info(2).elapsed_timestamp_nanos_size()); + EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs, + data.bucket_info(2).start_bucket_elapsed_nanos()); + EXPECT_EQ(bucketStartTimeNs + 3 * bucketSizeNs, + data.bucket_info(2).end_bucket_elapsed_nanos()); + EXPECT_EQ(AppStartOccurred::COLD, + data.bucket_info(2).atom(0).app_start_occurred().type()); + EXPECT_EQ("activity_name5", + data.bucket_info(2).atom(0).app_start_occurred().activity_name()); + EXPECT_EQ(105L, + data.bucket_info(2).atom(0).app_start_occurred().activity_start_millis()); + EXPECT_EQ(AppStartOccurred::HOT, + data.bucket_info(2).atom(1).app_start_occurred().type()); + EXPECT_EQ("activity_name6", + data.bucket_info(2).atom(1).app_start_occurred().activity_name()); + EXPECT_EQ(106L, + data.bucket_info(2).atom(1).app_start_occurred().activity_start_millis()); + } else { + ASSERT_EQ(1, data.bucket_info(0).atom_size()); + ASSERT_EQ(1, data.bucket_info(0).elapsed_timestamp_nanos_size()); + EXPECT_EQ(bucketStartTimeNs, data.bucket_info(0).start_bucket_elapsed_nanos()); + EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, + data.bucket_info(0).end_bucket_elapsed_nanos()); + EXPECT_EQ(AppStartOccurred::HOT, + data.bucket_info(0).atom(0).app_start_occurred().type()); + EXPECT_EQ("activity_name2", + data.bucket_info(0).atom(0).app_start_occurred().activity_name()); + EXPECT_EQ(102L, + data.bucket_info(0).atom(0).app_start_occurred().activity_start_millis()); + + ASSERT_EQ(1, data.bucket_info(1).atom_size()); + ASSERT_EQ(1, data.bucket_info(1).elapsed_timestamp_nanos_size()); + EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, + data.bucket_info(1).start_bucket_elapsed_nanos()); + EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs, + data.bucket_info(1).end_bucket_elapsed_nanos()); + EXPECT_EQ(AppStartOccurred::WARM, + data.bucket_info(1).atom(0).app_start_occurred().type()); + EXPECT_EQ("activity_name4", + data.bucket_info(1).atom(0).app_start_occurred().activity_name()); + EXPECT_EQ(104L, + data.bucket_info(1).atom(0).app_start_occurred().activity_start_millis()); + + ASSERT_EQ(1, data.bucket_info(2).atom_size()); + ASSERT_EQ(1, data.bucket_info(2).elapsed_timestamp_nanos_size()); + EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs, + data.bucket_info(2).start_bucket_elapsed_nanos()); + EXPECT_EQ(bucketStartTimeNs + 3 * bucketSizeNs, + data.bucket_info(2).end_bucket_elapsed_nanos()); + EXPECT_EQ(AppStartOccurred::COLD, + data.bucket_info(2).atom(0).app_start_occurred().type()); + EXPECT_EQ("activity_name5", + data.bucket_info(2).atom(0).app_start_occurred().activity_name()); + EXPECT_EQ(105L, + data.bucket_info(2).atom(0).app_start_occurred().activity_start_millis()); + } + + data = gaugeMetrics.data(1); + + EXPECT_EQ(data.dimensions_in_what().field(), util::APP_START_OCCURRED); + ASSERT_EQ(data.dimensions_in_what().value_tuple().dimensions_value_size(), 1); + EXPECT_EQ(1 /* uid field */, + data.dimensions_in_what().value_tuple().dimensions_value(0).field()); + EXPECT_EQ(appUid2, data.dimensions_in_what().value_tuple().dimensions_value(0).value_int()); + ASSERT_EQ(1, data.bucket_info_size()); + ASSERT_EQ(1, data.bucket_info(0).atom_size()); + ASSERT_EQ(1, data.bucket_info(0).elapsed_timestamp_nanos_size()); + EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs, + data.bucket_info(0).start_bucket_elapsed_nanos()); + EXPECT_EQ(bucketStartTimeNs + 3 * bucketSizeNs, + data.bucket_info(0).end_bucket_elapsed_nanos()); + EXPECT_EQ(AppStartOccurred::COLD, data.bucket_info(0).atom(0).app_start_occurred().type()); + EXPECT_EQ("activity_name7", + data.bucket_info(0).atom(0).app_start_occurred().activity_name()); + EXPECT_EQ(201L, data.bucket_info(0).atom(0).app_start_occurred().activity_start_millis()); + } +} + +#else +GTEST_LOG_(INFO) << "This test does nothing.\n"; +#endif + +} // namespace statsd +} // namespace os +} // namespace android diff --git a/statsd/tests/e2e/MetricActivation_e2e_test.cpp b/statsd/tests/e2e/MetricActivation_e2e_test.cpp new file mode 100644 index 00000000..e320419a --- /dev/null +++ b/statsd/tests/e2e/MetricActivation_e2e_test.cpp @@ -0,0 +1,1833 @@ +// Copyright (C) 2018 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 CreateStatsdConfig() { + StatsdConfig config; + config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root. + auto saverModeMatcher = CreateBatterySaverModeStartAtomMatcher(); + auto crashMatcher = CreateProcessCrashAtomMatcher(); + auto screenOnMatcher = CreateScreenTurnedOnAtomMatcher(); + + *config.add_atom_matcher() = saverModeMatcher; + *config.add_atom_matcher() = crashMatcher; + *config.add_atom_matcher() = screenOnMatcher; + + int64_t metricId = 123456; + auto countMetric = config.add_count_metric(); + countMetric->set_id(metricId); + countMetric->set_what(crashMatcher.id()); + countMetric->set_bucket(FIVE_MINUTES); + countMetric->mutable_dimensions_in_what()->set_field( + util::PROCESS_LIFE_CYCLE_STATE_CHANGED); + countMetric->mutable_dimensions_in_what()->add_child()->set_field(1); // uid field + + auto metric_activation1 = config.add_metric_activation(); + metric_activation1->set_metric_id(metricId); + auto event_activation1 = metric_activation1->add_event_activation(); + event_activation1->set_atom_matcher_id(saverModeMatcher.id()); + event_activation1->set_ttl_seconds(60 * 6); // 6 minutes + auto event_activation2 = metric_activation1->add_event_activation(); + event_activation2->set_atom_matcher_id(screenOnMatcher.id()); + event_activation2->set_ttl_seconds(60 * 2); // 2 minutes + + return config; +} + +StatsdConfig CreateStatsdConfigWithOneDeactivation() { + StatsdConfig config; + config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root. + auto saverModeMatcher = CreateBatterySaverModeStartAtomMatcher(); + auto crashMatcher = CreateProcessCrashAtomMatcher(); + auto screenOnMatcher = CreateScreenTurnedOnAtomMatcher(); + auto brightnessChangedMatcher = CreateScreenBrightnessChangedAtomMatcher(); + + *config.add_atom_matcher() = saverModeMatcher; + *config.add_atom_matcher() = crashMatcher; + *config.add_atom_matcher() = screenOnMatcher; + *config.add_atom_matcher() = brightnessChangedMatcher; + + int64_t metricId = 123456; + auto countMetric = config.add_count_metric(); + countMetric->set_id(metricId); + countMetric->set_what(crashMatcher.id()); + countMetric->set_bucket(FIVE_MINUTES); + countMetric->mutable_dimensions_in_what()->set_field( + util::PROCESS_LIFE_CYCLE_STATE_CHANGED); + countMetric->mutable_dimensions_in_what()->add_child()->set_field(1); // uid field + + auto metric_activation1 = config.add_metric_activation(); + metric_activation1->set_metric_id(metricId); + auto event_activation1 = metric_activation1->add_event_activation(); + event_activation1->set_atom_matcher_id(saverModeMatcher.id()); + event_activation1->set_ttl_seconds(60 * 6); // 6 minutes + event_activation1->set_deactivation_atom_matcher_id(brightnessChangedMatcher.id()); + auto event_activation2 = metric_activation1->add_event_activation(); + event_activation2->set_atom_matcher_id(screenOnMatcher.id()); + event_activation2->set_ttl_seconds(60 * 2); // 2 minutes + + return config; +} + +StatsdConfig CreateStatsdConfigWithTwoDeactivations() { + StatsdConfig config; + config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root. + auto saverModeMatcher = CreateBatterySaverModeStartAtomMatcher(); + auto crashMatcher = CreateProcessCrashAtomMatcher(); + auto screenOnMatcher = CreateScreenTurnedOnAtomMatcher(); + auto brightnessChangedMatcher = CreateScreenBrightnessChangedAtomMatcher(); + auto brightnessChangedMatcher2 = CreateScreenBrightnessChangedAtomMatcher(); + brightnessChangedMatcher2.set_id(StringToId("ScreenBrightnessChanged2")); + + *config.add_atom_matcher() = saverModeMatcher; + *config.add_atom_matcher() = crashMatcher; + *config.add_atom_matcher() = screenOnMatcher; + *config.add_atom_matcher() = brightnessChangedMatcher; + *config.add_atom_matcher() = brightnessChangedMatcher2; + + int64_t metricId = 123456; + auto countMetric = config.add_count_metric(); + countMetric->set_id(metricId); + countMetric->set_what(crashMatcher.id()); + countMetric->set_bucket(FIVE_MINUTES); + countMetric->mutable_dimensions_in_what()->set_field( + util::PROCESS_LIFE_CYCLE_STATE_CHANGED); + countMetric->mutable_dimensions_in_what()->add_child()->set_field(1); // uid field + + auto metric_activation1 = config.add_metric_activation(); + metric_activation1->set_metric_id(metricId); + auto event_activation1 = metric_activation1->add_event_activation(); + event_activation1->set_atom_matcher_id(saverModeMatcher.id()); + event_activation1->set_ttl_seconds(60 * 6); // 6 minutes + event_activation1->set_deactivation_atom_matcher_id(brightnessChangedMatcher.id()); + auto event_activation2 = metric_activation1->add_event_activation(); + event_activation2->set_atom_matcher_id(screenOnMatcher.id()); + event_activation2->set_ttl_seconds(60 * 2); // 2 minutes + event_activation2->set_deactivation_atom_matcher_id(brightnessChangedMatcher2.id()); + + return config; +} + +StatsdConfig CreateStatsdConfigWithSameDeactivations() { + StatsdConfig config; + config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root. + auto saverModeMatcher = CreateBatterySaverModeStartAtomMatcher(); + auto crashMatcher = CreateProcessCrashAtomMatcher(); + auto screenOnMatcher = CreateScreenTurnedOnAtomMatcher(); + auto brightnessChangedMatcher = CreateScreenBrightnessChangedAtomMatcher(); + + *config.add_atom_matcher() = saverModeMatcher; + *config.add_atom_matcher() = crashMatcher; + *config.add_atom_matcher() = screenOnMatcher; + *config.add_atom_matcher() = brightnessChangedMatcher; + + int64_t metricId = 123456; + auto countMetric = config.add_count_metric(); + countMetric->set_id(metricId); + countMetric->set_what(crashMatcher.id()); + countMetric->set_bucket(FIVE_MINUTES); + countMetric->mutable_dimensions_in_what()->set_field( + util::PROCESS_LIFE_CYCLE_STATE_CHANGED); + countMetric->mutable_dimensions_in_what()->add_child()->set_field(1); // uid field + + auto metric_activation1 = config.add_metric_activation(); + metric_activation1->set_metric_id(metricId); + auto event_activation1 = metric_activation1->add_event_activation(); + event_activation1->set_atom_matcher_id(saverModeMatcher.id()); + event_activation1->set_ttl_seconds(60 * 6); // 6 minutes + event_activation1->set_deactivation_atom_matcher_id(brightnessChangedMatcher.id()); + auto event_activation2 = metric_activation1->add_event_activation(); + event_activation2->set_atom_matcher_id(screenOnMatcher.id()); + event_activation2->set_ttl_seconds(60 * 2); // 2 minutes + event_activation2->set_deactivation_atom_matcher_id(brightnessChangedMatcher.id()); + + return config; +} + +StatsdConfig CreateStatsdConfigWithTwoMetricsTwoDeactivations() { + StatsdConfig config; + config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root. + auto saverModeMatcher = CreateBatterySaverModeStartAtomMatcher(); + auto crashMatcher = CreateProcessCrashAtomMatcher(); + auto foregroundMatcher = CreateMoveToForegroundAtomMatcher(); + auto screenOnMatcher = CreateScreenTurnedOnAtomMatcher(); + auto brightnessChangedMatcher = CreateScreenBrightnessChangedAtomMatcher(); + auto brightnessChangedMatcher2 = CreateScreenBrightnessChangedAtomMatcher(); + brightnessChangedMatcher2.set_id(StringToId("ScreenBrightnessChanged2")); + + *config.add_atom_matcher() = saverModeMatcher; + *config.add_atom_matcher() = crashMatcher; + *config.add_atom_matcher() = screenOnMatcher; + *config.add_atom_matcher() = brightnessChangedMatcher; + *config.add_atom_matcher() = brightnessChangedMatcher2; + *config.add_atom_matcher() = foregroundMatcher; + + int64_t metricId = 123456; + auto countMetric = config.add_count_metric(); + countMetric->set_id(metricId); + countMetric->set_what(crashMatcher.id()); + countMetric->set_bucket(FIVE_MINUTES); + countMetric->mutable_dimensions_in_what()->set_field( + util::PROCESS_LIFE_CYCLE_STATE_CHANGED); + countMetric->mutable_dimensions_in_what()->add_child()->set_field(1); // uid field + + int64_t metricId2 = 234567; + countMetric = config.add_count_metric(); + countMetric->set_id(metricId2); + countMetric->set_what(foregroundMatcher.id()); + countMetric->set_bucket(FIVE_MINUTES); + countMetric->mutable_dimensions_in_what()->set_field( + util::ACTIVITY_FOREGROUND_STATE_CHANGED); + countMetric->mutable_dimensions_in_what()->add_child()->set_field(1); // uid field + + auto metric_activation1 = config.add_metric_activation(); + metric_activation1->set_metric_id(metricId); + auto event_activation1 = metric_activation1->add_event_activation(); + event_activation1->set_atom_matcher_id(saverModeMatcher.id()); + event_activation1->set_ttl_seconds(60 * 6); // 6 minutes + event_activation1->set_deactivation_atom_matcher_id(brightnessChangedMatcher.id()); + auto event_activation2 = metric_activation1->add_event_activation(); + event_activation2->set_atom_matcher_id(screenOnMatcher.id()); + event_activation2->set_ttl_seconds(60 * 2); // 2 minutes + event_activation2->set_deactivation_atom_matcher_id(brightnessChangedMatcher2.id()); + + metric_activation1 = config.add_metric_activation(); + metric_activation1->set_metric_id(metricId2); + event_activation1 = metric_activation1->add_event_activation(); + event_activation1->set_atom_matcher_id(saverModeMatcher.id()); + event_activation1->set_ttl_seconds(60 * 6); // 6 minutes + event_activation1->set_deactivation_atom_matcher_id(brightnessChangedMatcher.id()); + event_activation2 = metric_activation1->add_event_activation(); + event_activation2->set_atom_matcher_id(screenOnMatcher.id()); + event_activation2->set_ttl_seconds(60 * 2); // 2 minutes + event_activation2->set_deactivation_atom_matcher_id(brightnessChangedMatcher2.id()); + + return config; +} + +} // namespace + +TEST(MetricActivationE2eTest, TestCountMetric) { + auto config = CreateStatsdConfig(); + + int64_t bucketStartTimeNs = NS_PER_SEC * 10; // 10 secs + int64_t bucketSizeNs = + TimeUnitToBucketSizeInMillis(config.count_metric(0).bucket()) * 1000LL * 1000LL; + + int uid = 12345; + int64_t cfgId = 98765; + ConfigKey cfgKey(uid, cfgId); + + sp m = new UidMap(); + sp pullerManager = new StatsPullerManager(); + sp anomalyAlarmMonitor; + sp subscriberAlarmMonitor; + vector activeConfigsBroadcast; + + long timeBase1 = 1; + int broadcastCount = 0; + StatsLogProcessor processor( + m, pullerManager, anomalyAlarmMonitor, subscriberAlarmMonitor, bucketStartTimeNs, + [](const ConfigKey& key) { return true; }, + [&uid, &broadcastCount, &activeConfigsBroadcast](const int& broadcastUid, + const vector& activeConfigs) { + broadcastCount++; + EXPECT_EQ(broadcastUid, uid); + activeConfigsBroadcast.clear(); + activeConfigsBroadcast.insert(activeConfigsBroadcast.end(), activeConfigs.begin(), + activeConfigs.end()); + return true; + }); + + processor.OnConfigUpdated(bucketStartTimeNs, cfgKey, config); + + ASSERT_EQ(processor.mMetricsManagers.size(), 1u); + sp metricsManager = processor.mMetricsManagers.begin()->second; + EXPECT_TRUE(metricsManager->isConfigValid()); + ASSERT_EQ(metricsManager->mAllMetricProducers.size(), 1); + sp metricProducer = metricsManager->mAllMetricProducers[0]; + auto& eventActivationMap = metricProducer->mEventActivationMap; + + EXPECT_FALSE(metricsManager->isActive()); + EXPECT_FALSE(metricProducer->mIsActive); + // Two activations: one is triggered by battery saver mode (tracker index 0), the other is + // triggered by screen on event (tracker index 2). + ASSERT_EQ(eventActivationMap.size(), 2u); + EXPECT_TRUE(eventActivationMap.find(0) != eventActivationMap.end()); + EXPECT_TRUE(eventActivationMap.find(2) != eventActivationMap.end()); + EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kNotActive); + EXPECT_EQ(eventActivationMap[0]->start_ns, 0); + EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC); + EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kNotActive); + EXPECT_EQ(eventActivationMap[2]->start_ns, 0); + EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC); + + std::unique_ptr event; + + event = CreateAppCrashEvent(bucketStartTimeNs + 5, 111); + processor.OnLogEvent(event.get(), bucketStartTimeNs + 5); + EXPECT_FALSE(metricsManager->isActive()); + EXPECT_FALSE(metricProducer->mIsActive); + EXPECT_EQ(broadcastCount, 0); + + // Activated by battery save mode. + event = CreateBatterySaverOnEvent(bucketStartTimeNs + 10); + processor.OnLogEvent(event.get(), bucketStartTimeNs + 10); + EXPECT_TRUE(metricsManager->isActive()); + EXPECT_TRUE(metricProducer->mIsActive); + EXPECT_EQ(broadcastCount, 1); + ASSERT_EQ(activeConfigsBroadcast.size(), 1); + EXPECT_EQ(activeConfigsBroadcast[0], cfgId); + EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kActive); + EXPECT_EQ(eventActivationMap[0]->start_ns, bucketStartTimeNs + 10); + EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC); + EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kNotActive); + EXPECT_EQ(eventActivationMap[2]->start_ns, 0); + EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC); + + // First processed event. + event = CreateAppCrashEvent(bucketStartTimeNs + 15, 222); + processor.OnLogEvent(event.get(), bucketStartTimeNs + 15); + + // Activated by screen on event. + event = CreateScreenStateChangedEvent(bucketStartTimeNs + 20, android::view::DISPLAY_STATE_ON); + processor.OnLogEvent(event.get(), bucketStartTimeNs + 20); + EXPECT_TRUE(metricsManager->isActive()); + EXPECT_TRUE(metricProducer->mIsActive); + EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kActive); + EXPECT_EQ(eventActivationMap[0]->start_ns, bucketStartTimeNs + 10); + EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC); + EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kActive); + EXPECT_EQ(eventActivationMap[2]->start_ns, bucketStartTimeNs + 20); + EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC); + + // 2nd processed event. + // The activation by screen_on event expires, but the one by battery save mode is still active. + event = CreateAppCrashEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 2 + 25, 333); + processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 2 + 25); + EXPECT_TRUE(metricsManager->isActive()); + EXPECT_TRUE(metricProducer->mIsActive); + EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kActive); + EXPECT_EQ(eventActivationMap[0]->start_ns, bucketStartTimeNs + 10); + EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC); + EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kNotActive); + EXPECT_EQ(eventActivationMap[2]->start_ns, bucketStartTimeNs + 20); + EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC); + // No new broadcast since the config should still be active. + EXPECT_EQ(broadcastCount, 1); + + // 3rd processed event. + event = CreateAppCrashEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 5 + 25, 444); + processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 5 + 25); + + // All activations expired. + event = CreateAppCrashEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 8, 555); + processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 8); + EXPECT_FALSE(metricsManager->isActive()); + EXPECT_FALSE(metricProducer->mIsActive); + // New broadcast since the config is no longer active. + EXPECT_EQ(broadcastCount, 2); + ASSERT_EQ(activeConfigsBroadcast.size(), 0); + EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kNotActive); + EXPECT_EQ(eventActivationMap[0]->start_ns, bucketStartTimeNs + 10); + EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC); + EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kNotActive); + EXPECT_EQ(eventActivationMap[2]->start_ns, bucketStartTimeNs + 20); + EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC); + + // Re-activate metric via screen on. + event = CreateScreenStateChangedEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10, + android::view::DISPLAY_STATE_ON); + processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10); + EXPECT_TRUE(metricsManager->isActive()); + EXPECT_TRUE(metricProducer->mIsActive); + EXPECT_EQ(broadcastCount, 3); + ASSERT_EQ(activeConfigsBroadcast.size(), 1); + EXPECT_EQ(activeConfigsBroadcast[0], cfgId); + EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kNotActive); + EXPECT_EQ(eventActivationMap[0]->start_ns, bucketStartTimeNs + 10); + EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC); + EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kActive); + EXPECT_EQ(eventActivationMap[2]->start_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10); + EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC); + + // 4th processed event. + event = CreateAppCrashEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 1, 666); + processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 1); + + ConfigMetricsReportList reports; + vector buffer; + processor.onDumpReport(cfgKey, bucketStartTimeNs + NS_PER_SEC * 60 * 15 + 1, false, true, + ADB_DUMP, FAST, &buffer); + EXPECT_TRUE(buffer.size() > 0); + EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size())); + backfillDimensionPath(&reports); + backfillStartEndTimestamp(&reports); + ASSERT_EQ(1, reports.reports_size()); + ASSERT_EQ(1, reports.reports(0).metrics_size()); + + StatsLogReport::CountMetricDataWrapper countMetrics; + sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).count_metrics(), &countMetrics); + ASSERT_EQ(4, countMetrics.data_size()); + + auto data = countMetrics.data(0); + EXPECT_EQ(util::PROCESS_LIFE_CYCLE_STATE_CHANGED, data.dimensions_in_what().field()); + ASSERT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size()); + EXPECT_EQ(1 /* uid field */, + data.dimensions_in_what().value_tuple().dimensions_value(0).field()); + EXPECT_EQ(222, data.dimensions_in_what().value_tuple().dimensions_value(0).value_int()); + ASSERT_EQ(1, data.bucket_info_size()); + EXPECT_EQ(1, data.bucket_info(0).count()); + EXPECT_EQ(bucketStartTimeNs, data.bucket_info(0).start_bucket_elapsed_nanos()); + EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, data.bucket_info(0).end_bucket_elapsed_nanos()); + + data = countMetrics.data(1); + EXPECT_EQ(util::PROCESS_LIFE_CYCLE_STATE_CHANGED, data.dimensions_in_what().field()); + ASSERT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size()); + EXPECT_EQ(1 /* uid field */, + data.dimensions_in_what().value_tuple().dimensions_value(0).field()); + EXPECT_EQ(333, data.dimensions_in_what().value_tuple().dimensions_value(0).value_int()); + ASSERT_EQ(1, data.bucket_info_size()); + EXPECT_EQ(1, data.bucket_info(0).count()); + EXPECT_EQ(bucketStartTimeNs, data.bucket_info(0).start_bucket_elapsed_nanos()); + EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, data.bucket_info(0).end_bucket_elapsed_nanos()); + + data = countMetrics.data(2); + EXPECT_EQ(util::PROCESS_LIFE_CYCLE_STATE_CHANGED, data.dimensions_in_what().field()); + ASSERT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size()); + EXPECT_EQ(1 /* uid field */, + data.dimensions_in_what().value_tuple().dimensions_value(0).field()); + EXPECT_EQ(444, data.dimensions_in_what().value_tuple().dimensions_value(0).value_int()); + ASSERT_EQ(1, data.bucket_info_size()); + EXPECT_EQ(1, data.bucket_info(0).count()); + // Partial bucket as metric is deactivated. + EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, data.bucket_info(0).start_bucket_elapsed_nanos()); + EXPECT_EQ(bucketStartTimeNs + NS_PER_SEC * 60 * 8, + data.bucket_info(0).end_bucket_elapsed_nanos()); + + data = countMetrics.data(3); + EXPECT_EQ(util::PROCESS_LIFE_CYCLE_STATE_CHANGED, data.dimensions_in_what().field()); + ASSERT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size()); + EXPECT_EQ(1 /* uid field */, + data.dimensions_in_what().value_tuple().dimensions_value(0).field()); + EXPECT_EQ(666, data.dimensions_in_what().value_tuple().dimensions_value(0).value_int()); + ASSERT_EQ(1, data.bucket_info_size()); + EXPECT_EQ(1, data.bucket_info(0).count()); + EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs, + data.bucket_info(0).start_bucket_elapsed_nanos()); + EXPECT_EQ(bucketStartTimeNs + 3 * bucketSizeNs, data.bucket_info(0).end_bucket_elapsed_nanos()); +} + +TEST(MetricActivationE2eTest, TestCountMetricWithOneDeactivation) { + auto config = CreateStatsdConfigWithOneDeactivation(); + + int64_t bucketStartTimeNs = NS_PER_SEC * 10; // 10 secs + int64_t bucketSizeNs = + TimeUnitToBucketSizeInMillis(config.count_metric(0).bucket()) * 1000LL * 1000LL; + + int uid = 12345; + int64_t cfgId = 98765; + ConfigKey cfgKey(uid, cfgId); + + sp m = new UidMap(); + sp pullerManager = new StatsPullerManager(); + sp anomalyAlarmMonitor; + sp subscriberAlarmMonitor; + vector activeConfigsBroadcast; + + long timeBase1 = 1; + int broadcastCount = 0; + StatsLogProcessor processor( + m, pullerManager, anomalyAlarmMonitor, subscriberAlarmMonitor, bucketStartTimeNs, + [](const ConfigKey& key) { return true; }, + [&uid, &broadcastCount, &activeConfigsBroadcast](const int& broadcastUid, + const vector& activeConfigs) { + broadcastCount++; + EXPECT_EQ(broadcastUid, uid); + activeConfigsBroadcast.clear(); + activeConfigsBroadcast.insert(activeConfigsBroadcast.end(), activeConfigs.begin(), + activeConfigs.end()); + return true; + }); + + processor.OnConfigUpdated(bucketStartTimeNs, cfgKey, config); + + ASSERT_EQ(processor.mMetricsManagers.size(), 1u); + sp metricsManager = processor.mMetricsManagers.begin()->second; + EXPECT_TRUE(metricsManager->isConfigValid()); + ASSERT_EQ(metricsManager->mAllMetricProducers.size(), 1); + sp metricProducer = metricsManager->mAllMetricProducers[0]; + auto& eventActivationMap = metricProducer->mEventActivationMap; + auto& eventDeactivationMap = metricProducer->mEventDeactivationMap; + + EXPECT_FALSE(metricsManager->isActive()); + EXPECT_FALSE(metricProducer->mIsActive); + // Two activations: one is triggered by battery saver mode (tracker index 0), the other is + // triggered by screen on event (tracker index 2). + ASSERT_EQ(eventActivationMap.size(), 2u); + EXPECT_TRUE(eventActivationMap.find(0) != eventActivationMap.end()); + EXPECT_TRUE(eventActivationMap.find(2) != eventActivationMap.end()); + EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kNotActive); + EXPECT_EQ(eventActivationMap[0]->start_ns, 0); + EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC); + EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kNotActive); + EXPECT_EQ(eventActivationMap[2]->start_ns, 0); + EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC); + ASSERT_EQ(eventDeactivationMap.size(), 1u); + EXPECT_TRUE(eventDeactivationMap.find(3) != eventDeactivationMap.end()); + ASSERT_EQ(eventDeactivationMap[3].size(), 1u); + EXPECT_EQ(eventDeactivationMap[3][0], eventActivationMap[0]); + + std::unique_ptr event; + + event = CreateAppCrashEvent(bucketStartTimeNs + 5, 111); + processor.OnLogEvent(event.get(), bucketStartTimeNs + 5); + EXPECT_FALSE(metricsManager->isActive()); + EXPECT_FALSE(metricProducer->mIsActive); + EXPECT_EQ(broadcastCount, 0); + + // Activated by battery save mode. + event = CreateBatterySaverOnEvent(bucketStartTimeNs + 10); + processor.OnLogEvent(event.get(), bucketStartTimeNs + 10); + EXPECT_TRUE(metricsManager->isActive()); + EXPECT_TRUE(metricProducer->mIsActive); + EXPECT_EQ(broadcastCount, 1); + ASSERT_EQ(activeConfigsBroadcast.size(), 1); + EXPECT_EQ(activeConfigsBroadcast[0], cfgId); + EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kActive); + EXPECT_EQ(eventActivationMap[0]->start_ns, bucketStartTimeNs + 10); + EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC); + EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kNotActive); + EXPECT_EQ(eventActivationMap[2]->start_ns, 0); + EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC); + EXPECT_EQ(eventDeactivationMap[3][0], eventActivationMap[0]); + + // First processed event. + event = CreateAppCrashEvent(bucketStartTimeNs + 15, 222); + processor.OnLogEvent(event.get(), bucketStartTimeNs + 15); + + // Activated by screen on event. + event = CreateScreenStateChangedEvent(bucketStartTimeNs + 20, android::view::DISPLAY_STATE_ON); + processor.OnLogEvent(event.get(), bucketStartTimeNs + 20); + EXPECT_TRUE(metricsManager->isActive()); + EXPECT_TRUE(metricProducer->mIsActive); + EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kActive); + EXPECT_EQ(eventActivationMap[0]->start_ns, bucketStartTimeNs + 10); + EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC); + EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kActive); + EXPECT_EQ(eventActivationMap[2]->start_ns, bucketStartTimeNs + 20); + EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC); + EXPECT_EQ(eventDeactivationMap[3][0], eventActivationMap[0]); + + // 2nd processed event. + // The activation by screen_on event expires, but the one by battery save mode is still active. + event = CreateAppCrashEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 2 + 25, 333); + processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 2 + 25); + EXPECT_TRUE(metricsManager->isActive()); + EXPECT_TRUE(metricProducer->mIsActive); + EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kActive); + EXPECT_EQ(eventActivationMap[0]->start_ns, bucketStartTimeNs + 10); + EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC); + EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kNotActive); + EXPECT_EQ(eventActivationMap[2]->start_ns, bucketStartTimeNs + 20); + EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC); + EXPECT_EQ(eventDeactivationMap[3][0], eventActivationMap[0]); + // No new broadcast since the config should still be active. + EXPECT_EQ(broadcastCount, 1); + + // 3rd processed event. + event = CreateAppCrashEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 5 + 25, 444); + processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 5 + 25); + + // All activations expired. + event = CreateAppCrashEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 8, 555); + processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 8); + EXPECT_FALSE(metricsManager->isActive()); + EXPECT_FALSE(metricProducer->mIsActive); + // New broadcast since the config is no longer active. + EXPECT_EQ(broadcastCount, 2); + ASSERT_EQ(activeConfigsBroadcast.size(), 0); + EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kNotActive); + EXPECT_EQ(eventActivationMap[0]->start_ns, bucketStartTimeNs + 10); + EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC); + EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kNotActive); + EXPECT_EQ(eventActivationMap[2]->start_ns, bucketStartTimeNs + 20); + EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC); + EXPECT_EQ(eventDeactivationMap[3][0], eventActivationMap[0]); + + // Re-activate metric via screen on. + event = CreateScreenStateChangedEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10, + android::view::DISPLAY_STATE_ON); + processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10); + EXPECT_TRUE(metricsManager->isActive()); + EXPECT_TRUE(metricProducer->mIsActive); + EXPECT_EQ(broadcastCount, 3); + ASSERT_EQ(activeConfigsBroadcast.size(), 1); + EXPECT_EQ(activeConfigsBroadcast[0], cfgId); + EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kNotActive); + EXPECT_EQ(eventActivationMap[0]->start_ns, bucketStartTimeNs + 10); + EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC); + EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kActive); + EXPECT_EQ(eventActivationMap[2]->start_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10); + EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC); + EXPECT_EQ(eventDeactivationMap[3][0], eventActivationMap[0]); + + // 4th processed event. + event = CreateAppCrashEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 1, 666); + processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 1); + + // Re-enable battery saver mode activation. + event = CreateBatterySaverOnEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 15); + processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 15); + EXPECT_TRUE(metricsManager->isActive()); + EXPECT_TRUE(metricProducer->mIsActive); + EXPECT_EQ(broadcastCount, 3); + ASSERT_EQ(activeConfigsBroadcast.size(), 1); + EXPECT_EQ(activeConfigsBroadcast[0], cfgId); + EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kActive); + EXPECT_EQ(eventActivationMap[0]->start_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 15); + EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC); + EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kActive); + EXPECT_EQ(eventActivationMap[2]->start_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10); + EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC); + EXPECT_EQ(eventDeactivationMap[3][0], eventActivationMap[0]); + + // 5th processed event. + event = CreateAppCrashEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 40, 777); + processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 40); + + // Cancel battery saver mode activation. + event = CreateScreenBrightnessChangedEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 60, 64); + processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 60); + EXPECT_TRUE(metricsManager->isActive()); + EXPECT_TRUE(metricProducer->mIsActive); + EXPECT_EQ(broadcastCount, 3); + ASSERT_EQ(activeConfigsBroadcast.size(), 1); + EXPECT_EQ(activeConfigsBroadcast[0], cfgId); + EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kNotActive); + EXPECT_EQ(eventActivationMap[0]->start_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 15); + EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC); + EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kActive); + EXPECT_EQ(eventActivationMap[2]->start_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10); + EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC); + EXPECT_EQ(eventDeactivationMap[3][0], eventActivationMap[0]); + + // Screen-on activation expired. + event = CreateAppCrashEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 13, 888); + processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 13); + EXPECT_FALSE(metricsManager->isActive()); + EXPECT_FALSE(metricProducer->mIsActive); + // New broadcast since the config is no longer active. + EXPECT_EQ(broadcastCount, 4); + ASSERT_EQ(activeConfigsBroadcast.size(), 0); + EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kNotActive); + EXPECT_EQ(eventActivationMap[0]->start_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 15); + EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC); + EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kNotActive); + EXPECT_EQ(eventActivationMap[2]->start_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10); + EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC); + EXPECT_EQ(eventDeactivationMap[3][0], eventActivationMap[0]); + + event = CreateAppCrashEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 14 + 1, 999); + processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 14 + 1); + + // Re-enable battery saver mode activation. + event = CreateBatterySaverOnEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 15 + 15); + processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 15 + 15); + EXPECT_TRUE(metricsManager->isActive()); + EXPECT_TRUE(metricProducer->mIsActive); + EXPECT_EQ(broadcastCount, 5); + ASSERT_EQ(activeConfigsBroadcast.size(), 1); + EXPECT_EQ(activeConfigsBroadcast[0], cfgId); + EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kActive); + EXPECT_EQ(eventActivationMap[0]->start_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 15 + 15); + EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC); + EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kNotActive); + EXPECT_EQ(eventActivationMap[2]->start_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10); + EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC); + EXPECT_EQ(eventDeactivationMap[3][0], eventActivationMap[0]); + + // Cancel battery saver mode activation. + event = CreateScreenBrightnessChangedEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 16, 140); + processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 16); + EXPECT_FALSE(metricsManager->isActive()); + EXPECT_FALSE(metricProducer->mIsActive); + EXPECT_EQ(broadcastCount, 6); + ASSERT_EQ(activeConfigsBroadcast.size(), 0); + EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kNotActive); + EXPECT_EQ(eventActivationMap[0]->start_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 15 + 15); + EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC); + EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kNotActive); + EXPECT_EQ(eventActivationMap[2]->start_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10); + EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC); + EXPECT_EQ(eventDeactivationMap[3][0], eventActivationMap[0]); + + ConfigMetricsReportList reports; + vector buffer; + processor.onDumpReport(cfgKey, bucketStartTimeNs + NS_PER_SEC * 60 * 15 + 1, false, true, + ADB_DUMP, FAST, &buffer); + EXPECT_TRUE(buffer.size() > 0); + EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size())); + backfillDimensionPath(&reports); + backfillStartEndTimestamp(&reports); + ASSERT_EQ(1, reports.reports_size()); + ASSERT_EQ(1, reports.reports(0).metrics_size()); + + StatsLogReport::CountMetricDataWrapper countMetrics; + sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).count_metrics(), &countMetrics); + ASSERT_EQ(5, countMetrics.data_size()); + + auto data = countMetrics.data(0); + EXPECT_EQ(util::PROCESS_LIFE_CYCLE_STATE_CHANGED, data.dimensions_in_what().field()); + ASSERT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size()); + EXPECT_EQ(1 /* uid field */, + data.dimensions_in_what().value_tuple().dimensions_value(0).field()); + EXPECT_EQ(222, data.dimensions_in_what().value_tuple().dimensions_value(0).value_int()); + ASSERT_EQ(1, data.bucket_info_size()); + EXPECT_EQ(1, data.bucket_info(0).count()); + EXPECT_EQ(bucketStartTimeNs, data.bucket_info(0).start_bucket_elapsed_nanos()); + EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, data.bucket_info(0).end_bucket_elapsed_nanos()); + + data = countMetrics.data(1); + EXPECT_EQ(util::PROCESS_LIFE_CYCLE_STATE_CHANGED, data.dimensions_in_what().field()); + ASSERT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size()); + EXPECT_EQ(1 /* uid field */, + data.dimensions_in_what().value_tuple().dimensions_value(0).field()); + EXPECT_EQ(333, data.dimensions_in_what().value_tuple().dimensions_value(0).value_int()); + ASSERT_EQ(1, data.bucket_info_size()); + EXPECT_EQ(1, data.bucket_info(0).count()); + EXPECT_EQ(bucketStartTimeNs, data.bucket_info(0).start_bucket_elapsed_nanos()); + EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, data.bucket_info(0).end_bucket_elapsed_nanos()); + + data = countMetrics.data(2); + EXPECT_EQ(util::PROCESS_LIFE_CYCLE_STATE_CHANGED, data.dimensions_in_what().field()); + ASSERT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size()); + EXPECT_EQ(1 /* uid field */, + data.dimensions_in_what().value_tuple().dimensions_value(0).field()); + EXPECT_EQ(444, data.dimensions_in_what().value_tuple().dimensions_value(0).value_int()); + ASSERT_EQ(1, data.bucket_info_size()); + EXPECT_EQ(1, data.bucket_info(0).count()); + // Partial bucket as metric is deactivated. + EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, data.bucket_info(0).start_bucket_elapsed_nanos()); + EXPECT_EQ(bucketStartTimeNs + NS_PER_SEC * 60 * 8, + data.bucket_info(0).end_bucket_elapsed_nanos()); + + data = countMetrics.data(3); + EXPECT_EQ(util::PROCESS_LIFE_CYCLE_STATE_CHANGED, data.dimensions_in_what().field()); + ASSERT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size()); + EXPECT_EQ(1 /* uid field */, + data.dimensions_in_what().value_tuple().dimensions_value(0).field()); + EXPECT_EQ(666, data.dimensions_in_what().value_tuple().dimensions_value(0).value_int()); + ASSERT_EQ(1, data.bucket_info_size()); + EXPECT_EQ(1, data.bucket_info(0).count()); + EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs, + data.bucket_info(0).start_bucket_elapsed_nanos()); + EXPECT_EQ(bucketStartTimeNs + NS_PER_SEC * 60 * 13, + data.bucket_info(0).end_bucket_elapsed_nanos()); + + data = countMetrics.data(4); + EXPECT_EQ(util::PROCESS_LIFE_CYCLE_STATE_CHANGED, data.dimensions_in_what().field()); + ASSERT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size()); + EXPECT_EQ(1 /* uid field */, + data.dimensions_in_what().value_tuple().dimensions_value(0).field()); + EXPECT_EQ(777, data.dimensions_in_what().value_tuple().dimensions_value(0).value_int()); + ASSERT_EQ(1, data.bucket_info_size()); + EXPECT_EQ(1, data.bucket_info(0).count()); + EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs, + data.bucket_info(0).start_bucket_elapsed_nanos()); + EXPECT_EQ(bucketStartTimeNs + NS_PER_SEC * 60 * 13, + data.bucket_info(0).end_bucket_elapsed_nanos()); +} + +TEST(MetricActivationE2eTest, TestCountMetricWithTwoDeactivations) { + auto config = CreateStatsdConfigWithTwoDeactivations(); + + int64_t bucketStartTimeNs = NS_PER_SEC * 10; // 10 secs + int64_t bucketSizeNs = + TimeUnitToBucketSizeInMillis(config.count_metric(0).bucket()) * 1000LL * 1000LL; + + int uid = 12345; + int64_t cfgId = 98765; + ConfigKey cfgKey(uid, cfgId); + + sp m = new UidMap(); + sp pullerManager = new StatsPullerManager(); + sp anomalyAlarmMonitor; + sp subscriberAlarmMonitor; + vector activeConfigsBroadcast; + + long timeBase1 = 1; + int broadcastCount = 0; + StatsLogProcessor processor( + m, pullerManager, anomalyAlarmMonitor, subscriberAlarmMonitor, bucketStartTimeNs, + [](const ConfigKey& key) { return true; }, + [&uid, &broadcastCount, &activeConfigsBroadcast](const int& broadcastUid, + const vector& activeConfigs) { + broadcastCount++; + EXPECT_EQ(broadcastUid, uid); + activeConfigsBroadcast.clear(); + activeConfigsBroadcast.insert(activeConfigsBroadcast.end(), activeConfigs.begin(), + activeConfigs.end()); + return true; + }); + + processor.OnConfigUpdated(bucketStartTimeNs, cfgKey, config); + + ASSERT_EQ(processor.mMetricsManagers.size(), 1u); + sp metricsManager = processor.mMetricsManagers.begin()->second; + EXPECT_TRUE(metricsManager->isConfigValid()); + ASSERT_EQ(metricsManager->mAllMetricProducers.size(), 1); + sp metricProducer = metricsManager->mAllMetricProducers[0]; + auto& eventActivationMap = metricProducer->mEventActivationMap; + auto& eventDeactivationMap = metricProducer->mEventDeactivationMap; + + EXPECT_FALSE(metricsManager->isActive()); + EXPECT_FALSE(metricProducer->mIsActive); + // Two activations: one is triggered by battery saver mode (tracker index 0), the other is + // triggered by screen on event (tracker index 2). + ASSERT_EQ(eventActivationMap.size(), 2u); + EXPECT_TRUE(eventActivationMap.find(0) != eventActivationMap.end()); + EXPECT_TRUE(eventActivationMap.find(2) != eventActivationMap.end()); + EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kNotActive); + EXPECT_EQ(eventActivationMap[0]->start_ns, 0); + EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC); + EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kNotActive); + EXPECT_EQ(eventActivationMap[2]->start_ns, 0); + EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC); + ASSERT_EQ(eventDeactivationMap.size(), 2u); + EXPECT_TRUE(eventDeactivationMap.find(3) != eventDeactivationMap.end()); + EXPECT_TRUE(eventDeactivationMap.find(4) != eventDeactivationMap.end()); + ASSERT_EQ(eventDeactivationMap[3].size(), 1u); + ASSERT_EQ(eventDeactivationMap[4].size(), 1u); + EXPECT_EQ(eventDeactivationMap[3][0], eventActivationMap[0]); + EXPECT_EQ(eventDeactivationMap[4][0], eventActivationMap[2]); + + std::unique_ptr event; + + event = CreateAppCrashEvent(bucketStartTimeNs + 5, 111); + processor.OnLogEvent(event.get(), bucketStartTimeNs + 5); + EXPECT_FALSE(metricsManager->isActive()); + EXPECT_FALSE(metricProducer->mIsActive); + EXPECT_EQ(broadcastCount, 0); + + // Activated by battery save mode. + event = CreateBatterySaverOnEvent(bucketStartTimeNs + 10); + processor.OnLogEvent(event.get(), bucketStartTimeNs + 10); + EXPECT_TRUE(metricsManager->isActive()); + EXPECT_TRUE(metricProducer->mIsActive); + EXPECT_EQ(broadcastCount, 1); + ASSERT_EQ(activeConfigsBroadcast.size(), 1); + EXPECT_EQ(activeConfigsBroadcast[0], cfgId); + EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kActive); + EXPECT_EQ(eventActivationMap[0]->start_ns, bucketStartTimeNs + 10); + EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC); + EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kNotActive); + EXPECT_EQ(eventActivationMap[2]->start_ns, 0); + EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC); + EXPECT_EQ(eventDeactivationMap[3][0], eventActivationMap[0]); + EXPECT_EQ(eventDeactivationMap[4][0], eventActivationMap[2]); + + // First processed event. + event = CreateAppCrashEvent(bucketStartTimeNs + 15, 222); + processor.OnLogEvent(event.get(), bucketStartTimeNs + 15); + + // Activated by screen on event. + event = CreateScreenStateChangedEvent(bucketStartTimeNs + 20, android::view::DISPLAY_STATE_ON); + processor.OnLogEvent(event.get(), bucketStartTimeNs + 20); + EXPECT_TRUE(metricsManager->isActive()); + EXPECT_TRUE(metricProducer->mIsActive); + EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kActive); + EXPECT_EQ(eventActivationMap[0]->start_ns, bucketStartTimeNs + 10); + EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC); + EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kActive); + EXPECT_EQ(eventActivationMap[2]->start_ns, bucketStartTimeNs + 20); + EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC); + EXPECT_EQ(eventDeactivationMap[3][0], eventActivationMap[0]); + EXPECT_EQ(eventDeactivationMap[4][0], eventActivationMap[2]); + + // 2nd processed event. + // The activation by screen_on event expires, but the one by battery save mode is still active. + event = CreateAppCrashEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 2 + 25, 333); + processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 2 + 25); + EXPECT_TRUE(metricsManager->isActive()); + EXPECT_TRUE(metricProducer->mIsActive); + EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kActive); + EXPECT_EQ(eventActivationMap[0]->start_ns, bucketStartTimeNs + 10); + EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC); + EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kNotActive); + EXPECT_EQ(eventActivationMap[2]->start_ns, bucketStartTimeNs + 20); + EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC); + EXPECT_EQ(eventDeactivationMap[3][0], eventActivationMap[0]); + EXPECT_EQ(eventDeactivationMap[4][0], eventActivationMap[2]); + // No new broadcast since the config should still be active. + EXPECT_EQ(broadcastCount, 1); + + // 3rd processed event. + event = CreateAppCrashEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 5 + 25, 444); + processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 5 + 25); + + // All activations expired. + event = CreateAppCrashEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 8, 555); + processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 8); + EXPECT_FALSE(metricsManager->isActive()); + EXPECT_FALSE(metricProducer->mIsActive); + // New broadcast since the config is no longer active. + EXPECT_EQ(broadcastCount, 2); + ASSERT_EQ(activeConfigsBroadcast.size(), 0); + EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kNotActive); + EXPECT_EQ(eventActivationMap[0]->start_ns, bucketStartTimeNs + 10); + EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC); + EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kNotActive); + EXPECT_EQ(eventActivationMap[2]->start_ns, bucketStartTimeNs + 20); + EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC); + EXPECT_EQ(eventDeactivationMap[3][0], eventActivationMap[0]); + EXPECT_EQ(eventDeactivationMap[4][0], eventActivationMap[2]); + + // Re-activate metric via screen on. + event = CreateScreenStateChangedEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10, + android::view::DISPLAY_STATE_ON); + processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10); + EXPECT_TRUE(metricsManager->isActive()); + EXPECT_TRUE(metricProducer->mIsActive); + EXPECT_EQ(broadcastCount, 3); + ASSERT_EQ(activeConfigsBroadcast.size(), 1); + EXPECT_EQ(activeConfigsBroadcast[0], cfgId); + EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kNotActive); + EXPECT_EQ(eventActivationMap[0]->start_ns, bucketStartTimeNs + 10); + EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC); + EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kActive); + EXPECT_EQ(eventActivationMap[2]->start_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10); + EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC); + EXPECT_EQ(eventDeactivationMap[3][0], eventActivationMap[0]); + EXPECT_EQ(eventDeactivationMap[4][0], eventActivationMap[2]); + + // 4th processed event. + event = CreateAppCrashEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 1, 666); + processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 1); + + // Re-enable battery saver mode activation. + event = CreateBatterySaverOnEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 15); + processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 15); + EXPECT_TRUE(metricsManager->isActive()); + EXPECT_TRUE(metricProducer->mIsActive); + EXPECT_EQ(broadcastCount, 3); + ASSERT_EQ(activeConfigsBroadcast.size(), 1); + EXPECT_EQ(activeConfigsBroadcast[0], cfgId); + EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kActive); + EXPECT_EQ(eventActivationMap[0]->start_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 15); + EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC); + EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kActive); + EXPECT_EQ(eventActivationMap[2]->start_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10); + EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC); + EXPECT_EQ(eventDeactivationMap[3][0], eventActivationMap[0]); + EXPECT_EQ(eventDeactivationMap[4][0], eventActivationMap[2]); + + // 5th processed event. + event = CreateAppCrashEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 40, 777); + processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 40); + + // Cancel battery saver mode and screen on activation. + event = CreateScreenBrightnessChangedEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 60, 64); + processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 60); + EXPECT_FALSE(metricsManager->isActive()); + EXPECT_FALSE(metricProducer->mIsActive); + // New broadcast since the config is no longer active. + EXPECT_EQ(broadcastCount, 4); + ASSERT_EQ(activeConfigsBroadcast.size(), 0); + EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kNotActive); + EXPECT_EQ(eventActivationMap[0]->start_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 15); + EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC); + EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kNotActive); + EXPECT_EQ(eventActivationMap[2]->start_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10); + EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC); + EXPECT_EQ(eventDeactivationMap[3][0], eventActivationMap[0]); + EXPECT_EQ(eventDeactivationMap[4][0], eventActivationMap[2]); + + // Screen-on activation expired. + event = CreateAppCrashEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 13, 888); + processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 13); + EXPECT_FALSE(metricsManager->isActive()); + EXPECT_FALSE(metricProducer->mIsActive); + EXPECT_EQ(broadcastCount, 4); + ASSERT_EQ(activeConfigsBroadcast.size(), 0); + EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kNotActive); + EXPECT_EQ(eventActivationMap[0]->start_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 15); + EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC); + EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kNotActive); + EXPECT_EQ(eventActivationMap[2]->start_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10); + EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC); + EXPECT_EQ(eventDeactivationMap[3][0], eventActivationMap[0]); + EXPECT_EQ(eventDeactivationMap[4][0], eventActivationMap[2]); + + event = CreateAppCrashEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 14 + 1, 999); + processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 14 + 1); + + // Re-enable battery saver mode activation. + event = CreateBatterySaverOnEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 15 + 15); + processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 15 + 15); + EXPECT_TRUE(metricsManager->isActive()); + EXPECT_TRUE(metricProducer->mIsActive); + EXPECT_EQ(broadcastCount, 5); + ASSERT_EQ(activeConfigsBroadcast.size(), 1); + EXPECT_EQ(activeConfigsBroadcast[0], cfgId); + EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kActive); + EXPECT_EQ(eventActivationMap[0]->start_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 15 + 15); + EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC); + EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kNotActive); + EXPECT_EQ(eventActivationMap[2]->start_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10); + EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC); + EXPECT_EQ(eventDeactivationMap[3][0], eventActivationMap[0]); + EXPECT_EQ(eventDeactivationMap[4][0], eventActivationMap[2]); + + // Cancel battery saver mode and screen on activation. + event = CreateScreenBrightnessChangedEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 16, 140); + processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 16); + EXPECT_FALSE(metricsManager->isActive()); + EXPECT_FALSE(metricProducer->mIsActive); + EXPECT_EQ(broadcastCount, 6); + ASSERT_EQ(activeConfigsBroadcast.size(), 0); + EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kNotActive); + EXPECT_EQ(eventActivationMap[0]->start_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 15 + 15); + EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC); + EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kNotActive); + EXPECT_EQ(eventActivationMap[2]->start_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10); + EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC); + EXPECT_EQ(eventDeactivationMap[3][0], eventActivationMap[0]); + EXPECT_EQ(eventDeactivationMap[4][0], eventActivationMap[2]); + + ConfigMetricsReportList reports; + vector buffer; + processor.onDumpReport(cfgKey, bucketStartTimeNs + NS_PER_SEC * 60 * 15 + 1, false, true, + ADB_DUMP, FAST, &buffer); + EXPECT_TRUE(buffer.size() > 0); + EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size())); + backfillDimensionPath(&reports); + backfillStartEndTimestamp(&reports); + ASSERT_EQ(1, reports.reports_size()); + ASSERT_EQ(1, reports.reports(0).metrics_size()); + + StatsLogReport::CountMetricDataWrapper countMetrics; + sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).count_metrics(), &countMetrics); + ASSERT_EQ(5, countMetrics.data_size()); + + auto data = countMetrics.data(0); + EXPECT_EQ(util::PROCESS_LIFE_CYCLE_STATE_CHANGED, data.dimensions_in_what().field()); + ASSERT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size()); + EXPECT_EQ(1 /* uid field */, + data.dimensions_in_what().value_tuple().dimensions_value(0).field()); + EXPECT_EQ(222, data.dimensions_in_what().value_tuple().dimensions_value(0).value_int()); + ASSERT_EQ(1, data.bucket_info_size()); + EXPECT_EQ(1, data.bucket_info(0).count()); + EXPECT_EQ(bucketStartTimeNs, data.bucket_info(0).start_bucket_elapsed_nanos()); + EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, data.bucket_info(0).end_bucket_elapsed_nanos()); + + data = countMetrics.data(1); + EXPECT_EQ(util::PROCESS_LIFE_CYCLE_STATE_CHANGED, data.dimensions_in_what().field()); + ASSERT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size()); + EXPECT_EQ(1 /* uid field */, + data.dimensions_in_what().value_tuple().dimensions_value(0).field()); + EXPECT_EQ(333, data.dimensions_in_what().value_tuple().dimensions_value(0).value_int()); + ASSERT_EQ(1, data.bucket_info_size()); + EXPECT_EQ(1, data.bucket_info(0).count()); + EXPECT_EQ(bucketStartTimeNs, data.bucket_info(0).start_bucket_elapsed_nanos()); + EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, data.bucket_info(0).end_bucket_elapsed_nanos()); + + data = countMetrics.data(2); + EXPECT_EQ(util::PROCESS_LIFE_CYCLE_STATE_CHANGED, data.dimensions_in_what().field()); + ASSERT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size()); + EXPECT_EQ(1 /* uid field */, + data.dimensions_in_what().value_tuple().dimensions_value(0).field()); + EXPECT_EQ(444, data.dimensions_in_what().value_tuple().dimensions_value(0).value_int()); + ASSERT_EQ(1, data.bucket_info_size()); + EXPECT_EQ(1, data.bucket_info(0).count()); + // Partial bucket as metric is deactivated. + EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, data.bucket_info(0).start_bucket_elapsed_nanos()); + EXPECT_EQ(bucketStartTimeNs + NS_PER_SEC * 60 * 8, + data.bucket_info(0).end_bucket_elapsed_nanos()); + + data = countMetrics.data(3); + EXPECT_EQ(util::PROCESS_LIFE_CYCLE_STATE_CHANGED, data.dimensions_in_what().field()); + ASSERT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size()); + EXPECT_EQ(1 /* uid field */, + data.dimensions_in_what().value_tuple().dimensions_value(0).field()); + EXPECT_EQ(666, data.dimensions_in_what().value_tuple().dimensions_value(0).value_int()); + ASSERT_EQ(1, data.bucket_info_size()); + EXPECT_EQ(1, data.bucket_info(0).count()); + EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs, + data.bucket_info(0).start_bucket_elapsed_nanos()); + EXPECT_EQ(bucketStartTimeNs + NS_PER_SEC * 60 * 11, + data.bucket_info(0).end_bucket_elapsed_nanos()); + + data = countMetrics.data(4); + EXPECT_EQ(util::PROCESS_LIFE_CYCLE_STATE_CHANGED, data.dimensions_in_what().field()); + ASSERT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size()); + EXPECT_EQ(1 /* uid field */, + data.dimensions_in_what().value_tuple().dimensions_value(0).field()); + EXPECT_EQ(777, data.dimensions_in_what().value_tuple().dimensions_value(0).value_int()); + ASSERT_EQ(1, data.bucket_info_size()); + EXPECT_EQ(1, data.bucket_info(0).count()); + EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs, + data.bucket_info(0).start_bucket_elapsed_nanos()); + EXPECT_EQ(bucketStartTimeNs + NS_PER_SEC * 60 * 11, + data.bucket_info(0).end_bucket_elapsed_nanos()); +} + +TEST(MetricActivationE2eTest, TestCountMetricWithSameDeactivation) { + auto config = CreateStatsdConfigWithSameDeactivations(); + + int64_t bucketStartTimeNs = NS_PER_SEC * 10; // 10 secs + int64_t bucketSizeNs = + TimeUnitToBucketSizeInMillis(config.count_metric(0).bucket()) * 1000LL * 1000LL; + + int uid = 12345; + int64_t cfgId = 98765; + ConfigKey cfgKey(uid, cfgId); + + sp m = new UidMap(); + sp pullerManager = new StatsPullerManager(); + sp anomalyAlarmMonitor; + sp subscriberAlarmMonitor; + vector activeConfigsBroadcast; + + long timeBase1 = 1; + int broadcastCount = 0; + StatsLogProcessor processor( + m, pullerManager, anomalyAlarmMonitor, subscriberAlarmMonitor, bucketStartTimeNs, + [](const ConfigKey& key) { return true; }, + [&uid, &broadcastCount, &activeConfigsBroadcast](const int& broadcastUid, + const vector& activeConfigs) { + broadcastCount++; + EXPECT_EQ(broadcastUid, uid); + activeConfigsBroadcast.clear(); + activeConfigsBroadcast.insert(activeConfigsBroadcast.end(), activeConfigs.begin(), + activeConfigs.end()); + return true; + }); + + processor.OnConfigUpdated(bucketStartTimeNs, cfgKey, config); + + ASSERT_EQ(processor.mMetricsManagers.size(), 1u); + sp metricsManager = processor.mMetricsManagers.begin()->second; + EXPECT_TRUE(metricsManager->isConfigValid()); + ASSERT_EQ(metricsManager->mAllMetricProducers.size(), 1); + sp metricProducer = metricsManager->mAllMetricProducers[0]; + auto& eventActivationMap = metricProducer->mEventActivationMap; + auto& eventDeactivationMap = metricProducer->mEventDeactivationMap; + + EXPECT_FALSE(metricsManager->isActive()); + EXPECT_FALSE(metricProducer->mIsActive); + // Two activations: one is triggered by battery saver mode (tracker index 0), the other is + // triggered by screen on event (tracker index 2). + ASSERT_EQ(eventActivationMap.size(), 2u); + EXPECT_TRUE(eventActivationMap.find(0) != eventActivationMap.end()); + EXPECT_TRUE(eventActivationMap.find(2) != eventActivationMap.end()); + EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kNotActive); + EXPECT_EQ(eventActivationMap[0]->start_ns, 0); + EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC); + EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kNotActive); + EXPECT_EQ(eventActivationMap[2]->start_ns, 0); + EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC); + ASSERT_EQ(eventDeactivationMap.size(), 1u); + EXPECT_TRUE(eventDeactivationMap.find(3) != eventDeactivationMap.end()); + ASSERT_EQ(eventDeactivationMap[3].size(), 2u); + EXPECT_EQ(eventDeactivationMap[3][0], eventActivationMap[0]); + EXPECT_EQ(eventDeactivationMap[3][1], eventActivationMap[2]); + EXPECT_EQ(broadcastCount, 0); + + std::unique_ptr event; + + // Event that should be ignored. + event = CreateAppCrashEvent(bucketStartTimeNs + 1, 111); + processor.OnLogEvent(event.get(), bucketStartTimeNs + 1); + + // Activate metric via screen on for 2 minutes. + event = CreateScreenStateChangedEvent(bucketStartTimeNs + 10, android::view::DISPLAY_STATE_ON); + processor.OnLogEvent(event.get(), bucketStartTimeNs + 10); + EXPECT_TRUE(metricsManager->isActive()); + EXPECT_TRUE(metricProducer->mIsActive); + EXPECT_EQ(broadcastCount, 1); + ASSERT_EQ(activeConfigsBroadcast.size(), 1); + EXPECT_EQ(activeConfigsBroadcast[0], cfgId); + EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kNotActive); + EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kActive); + EXPECT_EQ(eventActivationMap[2]->start_ns, bucketStartTimeNs + 10); + + // 1st processed event. + event = CreateAppCrashEvent(bucketStartTimeNs + 15, 222); + processor.OnLogEvent(event.get(), bucketStartTimeNs + 15); + + // Enable battery saver mode activation for 5 minutes. + event = CreateBatterySaverOnEvent(bucketStartTimeNs + NS_PER_SEC * 60 + 10); + processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 + 10); + EXPECT_TRUE(metricsManager->isActive()); + EXPECT_TRUE(metricProducer->mIsActive); + EXPECT_EQ(broadcastCount, 1); + EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kActive); + EXPECT_EQ(eventActivationMap[0]->start_ns, bucketStartTimeNs + NS_PER_SEC * 60 + 10); + EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kActive); + EXPECT_EQ(eventActivationMap[2]->start_ns, bucketStartTimeNs + 10); + + // 2nd processed event. + event = CreateAppCrashEvent(bucketStartTimeNs + NS_PER_SEC * 60 + 40, 333); + processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 + 40); + + // Cancel battery saver mode and screen on activation. + int64_t firstDeactivation = bucketStartTimeNs + NS_PER_SEC * 61; + event = CreateScreenBrightnessChangedEvent(firstDeactivation, 64); + processor.OnLogEvent(event.get(), firstDeactivation); + EXPECT_FALSE(metricsManager->isActive()); + EXPECT_FALSE(metricProducer->mIsActive); + // New broadcast since the config is no longer active. + EXPECT_EQ(broadcastCount, 2); + ASSERT_EQ(activeConfigsBroadcast.size(), 0); + EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kNotActive); + EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kNotActive); + + // Should be ignored + event = CreateAppCrashEvent(bucketStartTimeNs + NS_PER_SEC * 61 + 80, 444); + processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 61 + 80); + + // Re-enable battery saver mode activation. + event = CreateBatterySaverOnEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 15); + processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 15); + EXPECT_TRUE(metricsManager->isActive()); + EXPECT_TRUE(metricProducer->mIsActive); + EXPECT_EQ(broadcastCount, 3); + ASSERT_EQ(activeConfigsBroadcast.size(), 1); + EXPECT_EQ(activeConfigsBroadcast[0], cfgId); + EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kActive); + EXPECT_EQ(eventActivationMap[0]->start_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 15); + EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kNotActive); + + // 3rd processed event. + event = CreateAppCrashEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 80, 555); + processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 80); + + // Cancel battery saver mode activation. + int64_t secondDeactivation = bucketStartTimeNs + NS_PER_SEC * 60 * 13; + event = CreateScreenBrightnessChangedEvent(secondDeactivation, 140); + processor.OnLogEvent(event.get(), secondDeactivation); + EXPECT_FALSE(metricsManager->isActive()); + EXPECT_FALSE(metricProducer->mIsActive); + EXPECT_EQ(broadcastCount, 4); + ASSERT_EQ(activeConfigsBroadcast.size(), 0); + EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kNotActive); + EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kNotActive); + + // Should be ignored. + event = CreateAppCrashEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 13 + 80, 666); + processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 13 + 80); + + ConfigMetricsReportList reports; + vector buffer; + processor.onDumpReport(cfgKey, bucketStartTimeNs + NS_PER_SEC * 60 * 15 + 1, false, true, + ADB_DUMP, FAST, &buffer); + EXPECT_TRUE(buffer.size() > 0); + EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size())); + backfillDimensionPath(&reports); + backfillStartEndTimestamp(&reports); + ASSERT_EQ(1, reports.reports_size()); + ASSERT_EQ(1, reports.reports(0).metrics_size()); + + StatsLogReport::CountMetricDataWrapper countMetrics; + sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).count_metrics(), &countMetrics); + ASSERT_EQ(3, countMetrics.data_size()); + + auto data = countMetrics.data(0); + EXPECT_EQ(util::PROCESS_LIFE_CYCLE_STATE_CHANGED, data.dimensions_in_what().field()); + ASSERT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size()); + EXPECT_EQ(1 /* uid field */, + data.dimensions_in_what().value_tuple().dimensions_value(0).field()); + EXPECT_EQ(222, data.dimensions_in_what().value_tuple().dimensions_value(0).value_int()); + ASSERT_EQ(1, data.bucket_info_size()); + EXPECT_EQ(1, data.bucket_info(0).count()); + EXPECT_EQ(bucketStartTimeNs, data.bucket_info(0).start_bucket_elapsed_nanos()); + EXPECT_EQ(firstDeactivation, data.bucket_info(0).end_bucket_elapsed_nanos()); + + data = countMetrics.data(1); + EXPECT_EQ(util::PROCESS_LIFE_CYCLE_STATE_CHANGED, data.dimensions_in_what().field()); + ASSERT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size()); + EXPECT_EQ(1 /* uid field */, + data.dimensions_in_what().value_tuple().dimensions_value(0).field()); + EXPECT_EQ(333, data.dimensions_in_what().value_tuple().dimensions_value(0).value_int()); + ASSERT_EQ(1, data.bucket_info_size()); + EXPECT_EQ(1, data.bucket_info(0).count()); + EXPECT_EQ(bucketStartTimeNs, data.bucket_info(0).start_bucket_elapsed_nanos()); + EXPECT_EQ(firstDeactivation, data.bucket_info(0).end_bucket_elapsed_nanos()); + + data = countMetrics.data(2); + EXPECT_EQ(util::PROCESS_LIFE_CYCLE_STATE_CHANGED, data.dimensions_in_what().field()); + ASSERT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size()); + EXPECT_EQ(1 /* uid field */, + data.dimensions_in_what().value_tuple().dimensions_value(0).field()); + EXPECT_EQ(555, data.dimensions_in_what().value_tuple().dimensions_value(0).value_int()); + ASSERT_EQ(1, data.bucket_info_size()); + EXPECT_EQ(1, data.bucket_info(0).count()); + // Partial bucket as metric is deactivated. + EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs, + data.bucket_info(0).start_bucket_elapsed_nanos()); + EXPECT_EQ(secondDeactivation, data.bucket_info(0).end_bucket_elapsed_nanos()); +} + +TEST(MetricActivationE2eTest, TestCountMetricWithTwoMetricsTwoDeactivations) { + auto config = CreateStatsdConfigWithTwoMetricsTwoDeactivations(); + + int64_t bucketStartTimeNs = NS_PER_SEC * 10; // 10 secs + int64_t bucketSizeNs = + TimeUnitToBucketSizeInMillis(config.count_metric(0).bucket()) * 1000LL * 1000LL; + + int uid = 12345; + int64_t cfgId = 98765; + ConfigKey cfgKey(uid, cfgId); + + sp m = new UidMap(); + sp pullerManager = new StatsPullerManager(); + sp anomalyAlarmMonitor; + sp subscriberAlarmMonitor; + vector activeConfigsBroadcast; + + long timeBase1 = 1; + int broadcastCount = 0; + StatsLogProcessor processor( + m, pullerManager, anomalyAlarmMonitor, subscriberAlarmMonitor, bucketStartTimeNs, + [](const ConfigKey& key) { return true; }, + [&uid, &broadcastCount, &activeConfigsBroadcast](const int& broadcastUid, + const vector& activeConfigs) { + broadcastCount++; + EXPECT_EQ(broadcastUid, uid); + activeConfigsBroadcast.clear(); + activeConfigsBroadcast.insert(activeConfigsBroadcast.end(), activeConfigs.begin(), + activeConfigs.end()); + return true; + }); + + processor.OnConfigUpdated(bucketStartTimeNs, cfgKey, config); + + ASSERT_EQ(processor.mMetricsManagers.size(), 1u); + sp metricsManager = processor.mMetricsManagers.begin()->second; + EXPECT_TRUE(metricsManager->isConfigValid()); + ASSERT_EQ(metricsManager->mAllMetricProducers.size(), 2); + sp metricProducer = metricsManager->mAllMetricProducers[0]; + auto& eventActivationMap = metricProducer->mEventActivationMap; + auto& eventDeactivationMap = metricProducer->mEventDeactivationMap; + sp metricProducer2 = metricsManager->mAllMetricProducers[1]; + auto& eventActivationMap2 = metricProducer2->mEventActivationMap; + auto& eventDeactivationMap2 = metricProducer2->mEventDeactivationMap; + + EXPECT_FALSE(metricsManager->isActive()); + EXPECT_FALSE(metricProducer->mIsActive); + EXPECT_FALSE(metricProducer2->mIsActive); + // Two activations: one is triggered by battery saver mode (tracker index 0), the other is + // triggered by screen on event (tracker index 2). + ASSERT_EQ(eventActivationMap.size(), 2u); + EXPECT_TRUE(eventActivationMap.find(0) != eventActivationMap.end()); + EXPECT_TRUE(eventActivationMap.find(2) != eventActivationMap.end()); + EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kNotActive); + EXPECT_EQ(eventActivationMap[0]->start_ns, 0); + EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC); + EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kNotActive); + EXPECT_EQ(eventActivationMap[2]->start_ns, 0); + EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC); + ASSERT_EQ(eventDeactivationMap.size(), 2u); + EXPECT_TRUE(eventDeactivationMap.find(3) != eventDeactivationMap.end()); + EXPECT_TRUE(eventDeactivationMap.find(4) != eventDeactivationMap.end()); + ASSERT_EQ(eventDeactivationMap[3].size(), 1u); + ASSERT_EQ(eventDeactivationMap[4].size(), 1u); + EXPECT_EQ(eventDeactivationMap[3][0], eventActivationMap[0]); + EXPECT_EQ(eventDeactivationMap[4][0], eventActivationMap[2]); + + ASSERT_EQ(eventActivationMap2.size(), 2u); + EXPECT_TRUE(eventActivationMap2.find(0) != eventActivationMap2.end()); + EXPECT_TRUE(eventActivationMap2.find(2) != eventActivationMap2.end()); + EXPECT_EQ(eventActivationMap2[0]->state, ActivationState::kNotActive); + EXPECT_EQ(eventActivationMap2[0]->start_ns, 0); + EXPECT_EQ(eventActivationMap2[0]->ttl_ns, 60 * 6 * NS_PER_SEC); + EXPECT_EQ(eventActivationMap2[2]->state, ActivationState::kNotActive); + EXPECT_EQ(eventActivationMap2[2]->start_ns, 0); + EXPECT_EQ(eventActivationMap2[2]->ttl_ns, 60 * 2 * NS_PER_SEC); + ASSERT_EQ(eventDeactivationMap2.size(), 2u); + EXPECT_TRUE(eventDeactivationMap2.find(3) != eventDeactivationMap2.end()); + EXPECT_TRUE(eventDeactivationMap2.find(4) != eventDeactivationMap2.end()); + ASSERT_EQ(eventDeactivationMap[3].size(), 1u); + ASSERT_EQ(eventDeactivationMap[4].size(), 1u); + EXPECT_EQ(eventDeactivationMap2[3][0], eventActivationMap2[0]); + EXPECT_EQ(eventDeactivationMap2[4][0], eventActivationMap2[2]); + + std::unique_ptr event; + + event = CreateAppCrashEvent(bucketStartTimeNs + 5, 111); + processor.OnLogEvent(event.get(), bucketStartTimeNs + 5); + event = CreateMoveToForegroundEvent(bucketStartTimeNs + 5, 1111); + processor.OnLogEvent(event.get(), bucketStartTimeNs + 5); + EXPECT_FALSE(metricsManager->isActive()); + EXPECT_FALSE(metricProducer->mIsActive); + EXPECT_FALSE(metricProducer2->mIsActive); + EXPECT_EQ(broadcastCount, 0); + + // Activated by battery save mode. + event = CreateBatterySaverOnEvent(bucketStartTimeNs + 10); + processor.OnLogEvent(event.get(), bucketStartTimeNs + 10); + EXPECT_TRUE(metricsManager->isActive()); + EXPECT_EQ(broadcastCount, 1); + ASSERT_EQ(activeConfigsBroadcast.size(), 1); + EXPECT_EQ(activeConfigsBroadcast[0], cfgId); + EXPECT_TRUE(metricProducer->mIsActive); + EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kActive); + EXPECT_EQ(eventActivationMap[0]->start_ns, bucketStartTimeNs + 10); + EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC); + EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kNotActive); + EXPECT_EQ(eventActivationMap[2]->start_ns, 0); + EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC); + EXPECT_EQ(eventDeactivationMap[3][0], eventActivationMap[0]); + EXPECT_EQ(eventDeactivationMap[4][0], eventActivationMap[2]); + EXPECT_TRUE(metricProducer2->mIsActive); + EXPECT_EQ(eventActivationMap2[0]->state, ActivationState::kActive); + EXPECT_EQ(eventActivationMap2[0]->start_ns, bucketStartTimeNs + 10); + EXPECT_EQ(eventActivationMap2[0]->ttl_ns, 60 * 6 * NS_PER_SEC); + EXPECT_EQ(eventActivationMap2[2]->state, ActivationState::kNotActive); + EXPECT_EQ(eventActivationMap2[2]->start_ns, 0); + EXPECT_EQ(eventActivationMap2[2]->ttl_ns, 60 * 2 * NS_PER_SEC); + EXPECT_EQ(eventDeactivationMap2[3][0], eventActivationMap2[0]); + EXPECT_EQ(eventDeactivationMap2[4][0], eventActivationMap2[2]); + + // First processed event. + event = CreateAppCrashEvent(bucketStartTimeNs + 15, 222); + processor.OnLogEvent(event.get(), bucketStartTimeNs + 15); + event = CreateMoveToForegroundEvent(bucketStartTimeNs + 15, 2222); + processor.OnLogEvent(event.get(), bucketStartTimeNs + 15); + + // Activated by screen on event. + event = CreateScreenStateChangedEvent(bucketStartTimeNs + 20, android::view::DISPLAY_STATE_ON); + processor.OnLogEvent(event.get(), bucketStartTimeNs + 20); + EXPECT_TRUE(metricsManager->isActive()); + EXPECT_TRUE(metricProducer->mIsActive); + EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kActive); + EXPECT_EQ(eventActivationMap[0]->start_ns, bucketStartTimeNs + 10); + EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC); + EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kActive); + EXPECT_EQ(eventActivationMap[2]->start_ns, bucketStartTimeNs + 20); + EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC); + EXPECT_EQ(eventDeactivationMap[3][0], eventActivationMap[0]); + EXPECT_EQ(eventDeactivationMap[4][0], eventActivationMap[2]); + EXPECT_TRUE(metricProducer2->mIsActive); + EXPECT_EQ(eventActivationMap2[0]->state, ActivationState::kActive); + EXPECT_EQ(eventActivationMap2[0]->start_ns, bucketStartTimeNs + 10); + EXPECT_EQ(eventActivationMap2[0]->ttl_ns, 60 * 6 * NS_PER_SEC); + EXPECT_EQ(eventActivationMap2[2]->state, ActivationState::kActive); + EXPECT_EQ(eventActivationMap2[2]->start_ns, bucketStartTimeNs + 20); + EXPECT_EQ(eventActivationMap2[2]->ttl_ns, 60 * 2 * NS_PER_SEC); + EXPECT_EQ(eventDeactivationMap2[3][0], eventActivationMap2[0]); + EXPECT_EQ(eventDeactivationMap2[4][0], eventActivationMap2[2]); + + // 2nd processed event. + // The activation by screen_on event expires, but the one by battery save mode is still active. + event = CreateAppCrashEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 2 + 25, 333); + processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 2 + 25); + event = CreateMoveToForegroundEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 2 + 25, 3333); + processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 2 + 25); + EXPECT_TRUE(metricsManager->isActive()); + EXPECT_TRUE(metricProducer->mIsActive); + EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kActive); + EXPECT_EQ(eventActivationMap[0]->start_ns, bucketStartTimeNs + 10); + EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC); + EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kNotActive); + EXPECT_EQ(eventActivationMap[2]->start_ns, bucketStartTimeNs + 20); + EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC); + EXPECT_EQ(eventDeactivationMap[3][0], eventActivationMap[0]); + EXPECT_EQ(eventDeactivationMap[4][0], eventActivationMap[2]); + EXPECT_TRUE(metricProducer2->mIsActive); + EXPECT_EQ(eventActivationMap2[0]->state, ActivationState::kActive); + EXPECT_EQ(eventActivationMap2[0]->start_ns, bucketStartTimeNs + 10); + EXPECT_EQ(eventActivationMap2[0]->ttl_ns, 60 * 6 * NS_PER_SEC); + EXPECT_EQ(eventActivationMap2[2]->state, ActivationState::kNotActive); + EXPECT_EQ(eventActivationMap2[2]->start_ns, bucketStartTimeNs + 20); + EXPECT_EQ(eventActivationMap2[2]->ttl_ns, 60 * 2 * NS_PER_SEC); + EXPECT_EQ(eventDeactivationMap2[3][0], eventActivationMap2[0]); + EXPECT_EQ(eventDeactivationMap2[4][0], eventActivationMap2[2]); + // No new broadcast since the config should still be active. + EXPECT_EQ(broadcastCount, 1); + + // 3rd processed event. + event = CreateAppCrashEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 5 + 25, 444); + processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 5 + 25); + event = CreateMoveToForegroundEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 5 + 25, 4444); + processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 5 + 25); + + // All activations expired. + event = CreateAppCrashEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 8, 555); + processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 8); + event = CreateMoveToForegroundEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 8, 5555); + processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 8); + EXPECT_FALSE(metricsManager->isActive()); + // New broadcast since the config is no longer active. + EXPECT_EQ(broadcastCount, 2); + ASSERT_EQ(activeConfigsBroadcast.size(), 0); + EXPECT_FALSE(metricProducer->mIsActive); + EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kNotActive); + EXPECT_EQ(eventActivationMap[0]->start_ns, bucketStartTimeNs + 10); + EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC); + EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kNotActive); + EXPECT_EQ(eventActivationMap[2]->start_ns, bucketStartTimeNs + 20); + EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC); + EXPECT_EQ(eventDeactivationMap[3][0], eventActivationMap[0]); + EXPECT_EQ(eventDeactivationMap[4][0], eventActivationMap[2]); + EXPECT_FALSE(metricProducer2->mIsActive); + EXPECT_EQ(eventActivationMap2[0]->state, ActivationState::kNotActive); + EXPECT_EQ(eventActivationMap2[0]->start_ns, bucketStartTimeNs + 10); + EXPECT_EQ(eventActivationMap2[0]->ttl_ns, 60 * 6 * NS_PER_SEC); + EXPECT_EQ(eventActivationMap2[2]->state, ActivationState::kNotActive); + EXPECT_EQ(eventActivationMap2[2]->start_ns, bucketStartTimeNs + 20); + EXPECT_EQ(eventActivationMap2[2]->ttl_ns, 60 * 2 * NS_PER_SEC); + EXPECT_EQ(eventDeactivationMap2[3][0], eventActivationMap2[0]); + EXPECT_EQ(eventDeactivationMap2[4][0], eventActivationMap2[2]); + + // Re-activate metric via screen on. + event = CreateScreenStateChangedEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10, + android::view::DISPLAY_STATE_ON); + processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10); + EXPECT_TRUE(metricsManager->isActive()); + EXPECT_EQ(broadcastCount, 3); + ASSERT_EQ(activeConfigsBroadcast.size(), 1); + EXPECT_EQ(activeConfigsBroadcast[0], cfgId); + EXPECT_TRUE(metricProducer->mIsActive); + EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kNotActive); + EXPECT_EQ(eventActivationMap[0]->start_ns, bucketStartTimeNs + 10); + EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC); + EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kActive); + EXPECT_EQ(eventActivationMap[2]->start_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10); + EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC); + EXPECT_EQ(eventDeactivationMap[3][0], eventActivationMap[0]); + EXPECT_EQ(eventDeactivationMap[4][0], eventActivationMap[2]); + EXPECT_TRUE(metricProducer2->mIsActive); + EXPECT_EQ(eventActivationMap2[0]->state, ActivationState::kNotActive); + EXPECT_EQ(eventActivationMap2[0]->start_ns, bucketStartTimeNs + 10); + EXPECT_EQ(eventActivationMap2[0]->ttl_ns, 60 * 6 * NS_PER_SEC); + EXPECT_EQ(eventActivationMap2[2]->state, ActivationState::kActive); + EXPECT_EQ(eventActivationMap2[2]->start_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10); + EXPECT_EQ(eventActivationMap2[2]->ttl_ns, 60 * 2 * NS_PER_SEC); + EXPECT_EQ(eventDeactivationMap2[3][0], eventActivationMap2[0]); + EXPECT_EQ(eventDeactivationMap2[4][0], eventActivationMap2[2]); + + // 4th processed event. + event = CreateAppCrashEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 1, 666); + processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 1); + event = CreateMoveToForegroundEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 1, 6666); + processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 1); + + // Re-enable battery saver mode activation. + event = CreateBatterySaverOnEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 15); + processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 15); + EXPECT_TRUE(metricsManager->isActive()); + EXPECT_EQ(broadcastCount, 3); + ASSERT_EQ(activeConfigsBroadcast.size(), 1); + EXPECT_EQ(activeConfigsBroadcast[0], cfgId); + EXPECT_TRUE(metricProducer->mIsActive); + EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kActive); + EXPECT_EQ(eventActivationMap[0]->start_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 15); + EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC); + EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kActive); + EXPECT_EQ(eventActivationMap[2]->start_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10); + EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC); + EXPECT_EQ(eventDeactivationMap[3][0], eventActivationMap[0]); + EXPECT_EQ(eventDeactivationMap[4][0], eventActivationMap[2]); + EXPECT_TRUE(metricProducer2->mIsActive); + EXPECT_EQ(eventActivationMap2[0]->state, ActivationState::kActive); + EXPECT_EQ(eventActivationMap2[0]->start_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 15); + EXPECT_EQ(eventActivationMap2[0]->ttl_ns, 60 * 6 * NS_PER_SEC); + EXPECT_EQ(eventActivationMap2[2]->state, ActivationState::kActive); + EXPECT_EQ(eventActivationMap2[2]->start_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10); + EXPECT_EQ(eventActivationMap2[2]->ttl_ns, 60 * 2 * NS_PER_SEC); + EXPECT_EQ(eventDeactivationMap2[3][0], eventActivationMap2[0]); + EXPECT_EQ(eventDeactivationMap2[4][0], eventActivationMap2[2]); + + // 5th processed event. + event = CreateAppCrashEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 40, 777); + processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 40); + event = CreateMoveToForegroundEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 40, 7777); + processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 40); + + // Cancel battery saver mode and screen on activation. + event = CreateScreenBrightnessChangedEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 60, 64); + processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 60); + EXPECT_FALSE(metricsManager->isActive()); + // New broadcast since the config is no longer active. + EXPECT_EQ(broadcastCount, 4); + ASSERT_EQ(activeConfigsBroadcast.size(), 0); + EXPECT_FALSE(metricProducer->mIsActive); + EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kNotActive); + EXPECT_EQ(eventActivationMap[0]->start_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 15); + EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC); + EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kNotActive); + EXPECT_EQ(eventActivationMap[2]->start_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10); + EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC); + EXPECT_EQ(eventDeactivationMap[3][0], eventActivationMap[0]); + EXPECT_EQ(eventDeactivationMap[4][0], eventActivationMap[2]); + EXPECT_FALSE(metricProducer2->mIsActive); + EXPECT_EQ(eventActivationMap2[0]->state, ActivationState::kNotActive); + EXPECT_EQ(eventActivationMap2[0]->start_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 15); + EXPECT_EQ(eventActivationMap2[0]->ttl_ns, 60 * 6 * NS_PER_SEC); + EXPECT_EQ(eventActivationMap2[2]->state, ActivationState::kNotActive); + EXPECT_EQ(eventActivationMap2[2]->start_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10); + EXPECT_EQ(eventActivationMap2[2]->ttl_ns, 60 * 2 * NS_PER_SEC); + EXPECT_EQ(eventDeactivationMap2[3][0], eventActivationMap2[0]); + EXPECT_EQ(eventDeactivationMap2[4][0], eventActivationMap2[2]); + + // Screen-on activation expired. + event = CreateAppCrashEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 13, 888); + processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 13); + event = CreateMoveToForegroundEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 13, 8888); + processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 13); + EXPECT_FALSE(metricsManager->isActive()); + EXPECT_EQ(broadcastCount, 4); + ASSERT_EQ(activeConfigsBroadcast.size(), 0); + EXPECT_FALSE(metricProducer->mIsActive); + EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kNotActive); + EXPECT_EQ(eventActivationMap[0]->start_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 15); + EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC); + EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kNotActive); + EXPECT_EQ(eventActivationMap[2]->start_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10); + EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC); + EXPECT_EQ(eventDeactivationMap[3][0], eventActivationMap[0]); + EXPECT_EQ(eventDeactivationMap[4][0], eventActivationMap[2]); + EXPECT_FALSE(metricProducer2->mIsActive); + EXPECT_EQ(eventActivationMap2[0]->state, ActivationState::kNotActive); + EXPECT_EQ(eventActivationMap2[0]->start_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 15); + EXPECT_EQ(eventActivationMap2[0]->ttl_ns, 60 * 6 * NS_PER_SEC); + EXPECT_EQ(eventActivationMap2[2]->state, ActivationState::kNotActive); + EXPECT_EQ(eventActivationMap2[2]->start_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10); + EXPECT_EQ(eventActivationMap2[2]->ttl_ns, 60 * 2 * NS_PER_SEC); + EXPECT_EQ(eventDeactivationMap2[3][0], eventActivationMap2[0]); + EXPECT_EQ(eventDeactivationMap2[4][0], eventActivationMap2[2]); + + event = CreateAppCrashEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 14 + 1, 999); + processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 14 + 1); + event = CreateMoveToForegroundEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 14 + 1, 9999); + processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 14 + 1); + + // Re-enable battery saver mode activation. + event = CreateBatterySaverOnEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 15 + 15); + processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 15 + 15); + EXPECT_TRUE(metricsManager->isActive()); + EXPECT_EQ(broadcastCount, 5); + ASSERT_EQ(activeConfigsBroadcast.size(), 1); + EXPECT_EQ(activeConfigsBroadcast[0], cfgId); + EXPECT_TRUE(metricProducer->mIsActive); + EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kActive); + EXPECT_EQ(eventActivationMap[0]->start_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 15 + 15); + EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC); + EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kNotActive); + EXPECT_EQ(eventActivationMap[2]->start_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10); + EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC); + EXPECT_EQ(eventDeactivationMap[3][0], eventActivationMap[0]); + EXPECT_EQ(eventDeactivationMap[4][0], eventActivationMap[2]); + EXPECT_TRUE(metricProducer2->mIsActive); + EXPECT_EQ(eventActivationMap2[0]->state, ActivationState::kActive); + EXPECT_EQ(eventActivationMap2[0]->start_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 15 + 15); + EXPECT_EQ(eventActivationMap2[0]->ttl_ns, 60 * 6 * NS_PER_SEC); + EXPECT_EQ(eventActivationMap2[2]->state, ActivationState::kNotActive); + EXPECT_EQ(eventActivationMap2[2]->start_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10); + EXPECT_EQ(eventActivationMap2[2]->ttl_ns, 60 * 2 * NS_PER_SEC); + EXPECT_EQ(eventDeactivationMap2[3][0], eventActivationMap2[0]); + EXPECT_EQ(eventDeactivationMap2[4][0], eventActivationMap2[2]); + + // Cancel battery saver mode and screen on activation. + event = CreateScreenBrightnessChangedEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 16, 140); + processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 16); + EXPECT_FALSE(metricsManager->isActive()); + EXPECT_EQ(broadcastCount, 6); + ASSERT_EQ(activeConfigsBroadcast.size(), 0); + EXPECT_FALSE(metricProducer->mIsActive); + EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kNotActive); + EXPECT_EQ(eventActivationMap[0]->start_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 15 + 15); + EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC); + EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kNotActive); + EXPECT_EQ(eventActivationMap[2]->start_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10); + EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC); + EXPECT_EQ(eventDeactivationMap[3][0], eventActivationMap[0]); + EXPECT_EQ(eventDeactivationMap[4][0], eventActivationMap[2]); + EXPECT_FALSE(metricProducer2->mIsActive); + EXPECT_EQ(eventActivationMap2[0]->state, ActivationState::kNotActive); + EXPECT_EQ(eventActivationMap2[0]->start_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 15 + 15); + EXPECT_EQ(eventActivationMap2[0]->ttl_ns, 60 * 6 * NS_PER_SEC); + EXPECT_EQ(eventActivationMap2[2]->state, ActivationState::kNotActive); + EXPECT_EQ(eventActivationMap2[2]->start_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10); + EXPECT_EQ(eventActivationMap2[2]->ttl_ns, 60 * 2 * NS_PER_SEC); + EXPECT_EQ(eventDeactivationMap2[3][0], eventActivationMap2[0]); + EXPECT_EQ(eventDeactivationMap2[4][0], eventActivationMap2[2]); + + ConfigMetricsReportList reports; + vector buffer; + processor.onDumpReport(cfgKey, bucketStartTimeNs + NS_PER_SEC * 60 * 15 + 1, false, true, + ADB_DUMP, FAST, &buffer); + EXPECT_TRUE(buffer.size() > 0); + EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size())); + backfillDimensionPath(&reports); + backfillStartEndTimestamp(&reports); + ASSERT_EQ(1, reports.reports_size()); + ASSERT_EQ(2, reports.reports(0).metrics_size()); + + StatsLogReport::CountMetricDataWrapper countMetrics; + + sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).count_metrics(), &countMetrics); + ASSERT_EQ(5, countMetrics.data_size()); + + auto data = countMetrics.data(0); + EXPECT_EQ(util::PROCESS_LIFE_CYCLE_STATE_CHANGED, data.dimensions_in_what().field()); + ASSERT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size()); + EXPECT_EQ(1 /* uid field */, + data.dimensions_in_what().value_tuple().dimensions_value(0).field()); + EXPECT_EQ(222, data.dimensions_in_what().value_tuple().dimensions_value(0).value_int()); + ASSERT_EQ(1, data.bucket_info_size()); + EXPECT_EQ(1, data.bucket_info(0).count()); + EXPECT_EQ(bucketStartTimeNs, data.bucket_info(0).start_bucket_elapsed_nanos()); + EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, data.bucket_info(0).end_bucket_elapsed_nanos()); + + data = countMetrics.data(1); + EXPECT_EQ(util::PROCESS_LIFE_CYCLE_STATE_CHANGED, data.dimensions_in_what().field()); + ASSERT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size()); + EXPECT_EQ(1 /* uid field */, + data.dimensions_in_what().value_tuple().dimensions_value(0).field()); + EXPECT_EQ(333, data.dimensions_in_what().value_tuple().dimensions_value(0).value_int()); + ASSERT_EQ(1, data.bucket_info_size()); + EXPECT_EQ(1, data.bucket_info(0).count()); + EXPECT_EQ(bucketStartTimeNs, data.bucket_info(0).start_bucket_elapsed_nanos()); + EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, data.bucket_info(0).end_bucket_elapsed_nanos()); + + data = countMetrics.data(2); + EXPECT_EQ(util::PROCESS_LIFE_CYCLE_STATE_CHANGED, data.dimensions_in_what().field()); + ASSERT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size()); + EXPECT_EQ(1 /* uid field */, + data.dimensions_in_what().value_tuple().dimensions_value(0).field()); + EXPECT_EQ(444, data.dimensions_in_what().value_tuple().dimensions_value(0).value_int()); + ASSERT_EQ(1, data.bucket_info_size()); + EXPECT_EQ(1, data.bucket_info(0).count()); + // Partial bucket as metric is deactivated. + EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, data.bucket_info(0).start_bucket_elapsed_nanos()); + EXPECT_EQ(bucketStartTimeNs + NS_PER_SEC * 60 * 8, + data.bucket_info(0).end_bucket_elapsed_nanos()); + + data = countMetrics.data(3); + EXPECT_EQ(util::PROCESS_LIFE_CYCLE_STATE_CHANGED, data.dimensions_in_what().field()); + ASSERT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size()); + EXPECT_EQ(1 /* uid field */, + data.dimensions_in_what().value_tuple().dimensions_value(0).field()); + EXPECT_EQ(666, data.dimensions_in_what().value_tuple().dimensions_value(0).value_int()); + ASSERT_EQ(1, data.bucket_info_size()); + EXPECT_EQ(1, data.bucket_info(0).count()); + EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs, + data.bucket_info(0).start_bucket_elapsed_nanos()); + EXPECT_EQ(bucketStartTimeNs + NS_PER_SEC * 60 * 11, + data.bucket_info(0).end_bucket_elapsed_nanos()); + + data = countMetrics.data(4); + EXPECT_EQ(util::PROCESS_LIFE_CYCLE_STATE_CHANGED, data.dimensions_in_what().field()); + ASSERT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size()); + EXPECT_EQ(1 /* uid field */, + data.dimensions_in_what().value_tuple().dimensions_value(0).field()); + EXPECT_EQ(777, data.dimensions_in_what().value_tuple().dimensions_value(0).value_int()); + ASSERT_EQ(1, data.bucket_info_size()); + EXPECT_EQ(1, data.bucket_info(0).count()); + EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs, + data.bucket_info(0).start_bucket_elapsed_nanos()); + EXPECT_EQ(bucketStartTimeNs + NS_PER_SEC * 60 * 11, + data.bucket_info(0).end_bucket_elapsed_nanos()); + + countMetrics.clear_data(); + sortMetricDataByDimensionsValue(reports.reports(0).metrics(1).count_metrics(), &countMetrics); + ASSERT_EQ(5, countMetrics.data_size()); + + data = countMetrics.data(0); + EXPECT_EQ(util::ACTIVITY_FOREGROUND_STATE_CHANGED, data.dimensions_in_what().field()); + ASSERT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size()); + EXPECT_EQ(1 /* uid field */, + data.dimensions_in_what().value_tuple().dimensions_value(0).field()); + EXPECT_EQ(2222, data.dimensions_in_what().value_tuple().dimensions_value(0).value_int()); + ASSERT_EQ(1, data.bucket_info_size()); + EXPECT_EQ(1, data.bucket_info(0).count()); + EXPECT_EQ(bucketStartTimeNs, data.bucket_info(0).start_bucket_elapsed_nanos()); + EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, data.bucket_info(0).end_bucket_elapsed_nanos()); + + data = countMetrics.data(1); + EXPECT_EQ(util::ACTIVITY_FOREGROUND_STATE_CHANGED, data.dimensions_in_what().field()); + ASSERT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size()); + EXPECT_EQ(1 /* uid field */, + data.dimensions_in_what().value_tuple().dimensions_value(0).field()); + EXPECT_EQ(3333, data.dimensions_in_what().value_tuple().dimensions_value(0).value_int()); + ASSERT_EQ(1, data.bucket_info_size()); + EXPECT_EQ(1, data.bucket_info(0).count()); + EXPECT_EQ(bucketStartTimeNs, data.bucket_info(0).start_bucket_elapsed_nanos()); + EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, data.bucket_info(0).end_bucket_elapsed_nanos()); + + data = countMetrics.data(2); + EXPECT_EQ(util::ACTIVITY_FOREGROUND_STATE_CHANGED, data.dimensions_in_what().field()); + ASSERT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size()); + EXPECT_EQ(1 /* uid field */, + data.dimensions_in_what().value_tuple().dimensions_value(0).field()); + EXPECT_EQ(4444, data.dimensions_in_what().value_tuple().dimensions_value(0).value_int()); + ASSERT_EQ(1, data.bucket_info_size()); + EXPECT_EQ(1, data.bucket_info(0).count()); + // Partial bucket as metric is deactivated. + EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, data.bucket_info(0).start_bucket_elapsed_nanos()); + EXPECT_EQ(bucketStartTimeNs + NS_PER_SEC * 60 * 8, + data.bucket_info(0).end_bucket_elapsed_nanos()); + + data = countMetrics.data(3); + EXPECT_EQ(util::ACTIVITY_FOREGROUND_STATE_CHANGED, data.dimensions_in_what().field()); + ASSERT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size()); + EXPECT_EQ(1 /* uid field */, + data.dimensions_in_what().value_tuple().dimensions_value(0).field()); + EXPECT_EQ(6666, data.dimensions_in_what().value_tuple().dimensions_value(0).value_int()); + ASSERT_EQ(1, data.bucket_info_size()); + EXPECT_EQ(1, data.bucket_info(0).count()); + EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs, + data.bucket_info(0).start_bucket_elapsed_nanos()); + EXPECT_EQ(bucketStartTimeNs + NS_PER_SEC * 60 * 11, + data.bucket_info(0).end_bucket_elapsed_nanos()); + + data = countMetrics.data(4); + EXPECT_EQ(util::ACTIVITY_FOREGROUND_STATE_CHANGED, data.dimensions_in_what().field()); + ASSERT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size()); + EXPECT_EQ(1 /* uid field */, + data.dimensions_in_what().value_tuple().dimensions_value(0).field()); + EXPECT_EQ(7777, data.dimensions_in_what().value_tuple().dimensions_value(0).value_int()); + ASSERT_EQ(1, data.bucket_info_size()); + EXPECT_EQ(1, data.bucket_info(0).count()); + EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs, + data.bucket_info(0).start_bucket_elapsed_nanos()); + EXPECT_EQ(bucketStartTimeNs + NS_PER_SEC * 60 * 11, + data.bucket_info(0).end_bucket_elapsed_nanos()); +} + +#else +GTEST_LOG_(INFO) << "This test does nothing.\n"; +#endif + +} // namespace statsd +} // namespace os +} // namespace android diff --git a/statsd/tests/e2e/MetricConditionLink_e2e_test.cpp b/statsd/tests/e2e/MetricConditionLink_e2e_test.cpp new file mode 100644 index 00000000..5e77ee0f --- /dev/null +++ b/statsd/tests/e2e/MetricConditionLink_e2e_test.cpp @@ -0,0 +1,348 @@ +// 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 CreateStatsdConfig() { + StatsdConfig config; + config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root. + + *config.add_atom_matcher() = CreateScreenTurnedOnAtomMatcher(); + *config.add_atom_matcher() = CreateScreenTurnedOffAtomMatcher(); + + *config.add_atom_matcher() = CreateSyncStartAtomMatcher(); + *config.add_atom_matcher() = CreateSyncEndAtomMatcher(); + + *config.add_atom_matcher() = CreateMoveToBackgroundAtomMatcher(); + *config.add_atom_matcher() = CreateMoveToForegroundAtomMatcher(); + + auto appCrashMatcher = CreateProcessCrashAtomMatcher(); + *config.add_atom_matcher() = appCrashMatcher; + + auto screenIsOffPredicate = CreateScreenIsOffPredicate(); + + auto isSyncingPredicate = CreateIsSyncingPredicate(); + auto syncDimension = isSyncingPredicate.mutable_simple_predicate()->mutable_dimensions(); + *syncDimension = CreateAttributionUidDimensions( + util::SYNC_STATE_CHANGED, {Position::FIRST}); + syncDimension->add_child()->set_field(2 /* name field*/); + + auto isInBackgroundPredicate = CreateIsInBackgroundPredicate(); + *isInBackgroundPredicate.mutable_simple_predicate()->mutable_dimensions() = + CreateDimensions(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(StringToId("combinationPredicate")); + combinationPredicate->mutable_combination()->set_operation(LogicalOperation::AND); + addPredicateToPredicateCombination(screenIsOffPredicate, combinationPredicate); + addPredicateToPredicateCombination(isSyncingPredicate, combinationPredicate); + addPredicateToPredicateCombination(isInBackgroundPredicate, combinationPredicate); + + auto countMetric = config.add_count_metric(); + countMetric->set_id(StringToId("AppCrashes")); + countMetric->set_what(appCrashMatcher.id()); + countMetric->set_condition(combinationPredicate->id()); + // The metric is dimensioning by uid only. + *countMetric->mutable_dimensions_in_what() = + CreateDimensions(util::PROCESS_LIFE_CYCLE_STATE_CHANGED, {1}); + countMetric->set_bucket(FIVE_MINUTES); + + // Links between crash atom and condition of app is in syncing. + auto links = countMetric->add_links(); + links->set_condition(isSyncingPredicate.id()); + auto dimensionWhat = links->mutable_fields_in_what(); + dimensionWhat->set_field(util::PROCESS_LIFE_CYCLE_STATE_CHANGED); + dimensionWhat->add_child()->set_field(1); // uid field. + *links->mutable_fields_in_condition() = CreateAttributionUidDimensions( + util::SYNC_STATE_CHANGED, {Position::FIRST}); + + // Links between crash atom and condition of app is in background. + links = countMetric->add_links(); + links->set_condition(isInBackgroundPredicate.id()); + dimensionWhat = links->mutable_fields_in_what(); + dimensionWhat->set_field(util::PROCESS_LIFE_CYCLE_STATE_CHANGED); + dimensionWhat->add_child()->set_field(1); // uid field. + auto dimensionCondition = links->mutable_fields_in_condition(); + dimensionCondition->set_field(util::ACTIVITY_FOREGROUND_STATE_CHANGED); + dimensionCondition->add_child()->set_field(1); // uid field. + return config; +} +} // namespace + +// If we want to test multiple dump data, we must do it in separate tests, because in the e2e tests, +// we should use the real API which will clear the data after dump data is called. +TEST(MetricConditionLinkE2eTest, TestMultiplePredicatesAndLinks1) { + auto config = CreateStatsdConfig(); + uint64_t bucketStartTimeNs = 10000000000; + uint64_t bucketSizeNs = + TimeUnitToBucketSizeInMillis(config.count_metric(0).bucket()) * 1000000LL; + + ConfigKey cfgKey; + auto processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey); + ASSERT_EQ(processor->mMetricsManagers.size(), 1u); + EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid()); + + int appUid = 123; + auto crashEvent1 = CreateAppCrashEvent(bucketStartTimeNs + 1, appUid); + auto crashEvent2 = CreateAppCrashEvent(bucketStartTimeNs + 201, appUid); + auto crashEvent3 = CreateAppCrashEvent(bucketStartTimeNs + 2 * bucketSizeNs - 101, appUid); + + auto crashEvent4 = CreateAppCrashEvent(bucketStartTimeNs + 51, appUid); + auto crashEvent5 = CreateAppCrashEvent(bucketStartTimeNs + bucketSizeNs + 299, appUid); + auto crashEvent6 = CreateAppCrashEvent(bucketStartTimeNs + bucketSizeNs + 2001, appUid); + + auto crashEvent7 = CreateAppCrashEvent(bucketStartTimeNs + 16, appUid); + auto crashEvent8 = CreateAppCrashEvent(bucketStartTimeNs + bucketSizeNs + 249, appUid); + + auto crashEvent9 = CreateAppCrashEvent(bucketStartTimeNs + bucketSizeNs + 351, appUid); + auto crashEvent10 = CreateAppCrashEvent(bucketStartTimeNs + 2 * bucketSizeNs - 2, appUid); + + auto screenTurnedOnEvent = CreateScreenStateChangedEvent( + bucketStartTimeNs + 2, android::view::DisplayStateEnum::DISPLAY_STATE_ON); + auto screenTurnedOffEvent = CreateScreenStateChangedEvent( + bucketStartTimeNs + 200, android::view::DisplayStateEnum::DISPLAY_STATE_OFF); + auto screenTurnedOnEvent2 = + CreateScreenStateChangedEvent(bucketStartTimeNs + 2 * bucketSizeNs - 100, + android::view::DisplayStateEnum::DISPLAY_STATE_ON); + + std::vector attributionUids = {appUid, appUid + 1}; + std::vector attributionTags = {"App1", "GMSCoreModule1"}; + + auto syncOnEvent1 = CreateSyncStartEvent(bucketStartTimeNs + 50, attributionUids, + attributionTags, "ReadEmail"); + auto syncOffEvent1 = CreateSyncEndEvent(bucketStartTimeNs + bucketSizeNs + 300, attributionUids, + attributionTags, "ReadEmail"); + auto syncOnEvent2 = CreateSyncStartEvent(bucketStartTimeNs + bucketSizeNs + 2000, + attributionUids, attributionTags, "ReadDoc"); + + auto moveToBackgroundEvent1 = CreateMoveToBackgroundEvent(bucketStartTimeNs + 15, appUid); + auto moveToForegroundEvent1 = + CreateMoveToForegroundEvent(bucketStartTimeNs + bucketSizeNs + 250, appUid); + + auto moveToBackgroundEvent2 = + CreateMoveToBackgroundEvent(bucketStartTimeNs + bucketSizeNs + 350, appUid); + auto moveToForegroundEvent2 = + CreateMoveToForegroundEvent(bucketStartTimeNs + 2 * bucketSizeNs - 1, appUid); + + /* + bucket #1 bucket #2 + + + | | | | | | | | | | (crashEvents) + |-------------------------------------|-----------------------------------|--------- + + | | (MoveToBkground) + + | | (MoveToForeground) + + | | (SyncIsOn) + | (SyncIsOff) + | | (ScreenIsOn) + | (ScreenIsOff) + */ + std::vector> events; + events.push_back(std::move(crashEvent1)); + events.push_back(std::move(crashEvent2)); + events.push_back(std::move(crashEvent3)); + events.push_back(std::move(crashEvent4)); + events.push_back(std::move(crashEvent5)); + events.push_back(std::move(crashEvent6)); + events.push_back(std::move(crashEvent7)); + events.push_back(std::move(crashEvent8)); + events.push_back(std::move(crashEvent9)); + events.push_back(std::move(crashEvent10)); + events.push_back(std::move(screenTurnedOnEvent)); + events.push_back(std::move(screenTurnedOffEvent)); + events.push_back(std::move(screenTurnedOnEvent2)); + events.push_back(std::move(syncOnEvent1)); + events.push_back(std::move(syncOffEvent1)); + events.push_back(std::move(syncOnEvent2)); + events.push_back(std::move(moveToBackgroundEvent1)); + events.push_back(std::move(moveToForegroundEvent1)); + events.push_back(std::move(moveToBackgroundEvent2)); + events.push_back(std::move(moveToForegroundEvent2)); + + sortLogEventsByTimestamp(&events); + + for (const auto& event : events) { + processor->OnLogEvent(event.get()); + } + ConfigMetricsReportList reports; + vector buffer; + processor->onDumpReport(cfgKey, bucketStartTimeNs + 2 * bucketSizeNs - 1, false, true, ADB_DUMP, + FAST, &buffer); + EXPECT_TRUE(buffer.size() > 0); + EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size())); + backfillDimensionPath(&reports); + backfillStringInReport(&reports); + backfillStartEndTimestamp(&reports); + ASSERT_EQ(reports.reports_size(), 1); + ASSERT_EQ(reports.reports(0).metrics_size(), 1); + ASSERT_EQ(reports.reports(0).metrics(0).count_metrics().data_size(), 1); + ASSERT_EQ(reports.reports(0).metrics(0).count_metrics().data(0).bucket_info_size(), 1); + EXPECT_EQ(reports.reports(0).metrics(0).count_metrics().data(0).bucket_info(0).count(), 1); + auto data = reports.reports(0).metrics(0).count_metrics().data(0); + // Validate dimension value. + EXPECT_EQ(data.dimensions_in_what().field(), util::PROCESS_LIFE_CYCLE_STATE_CHANGED); + ASSERT_EQ(data.dimensions_in_what().value_tuple().dimensions_value_size(), 1); + // Uid field. + 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(), appUid); +} + +TEST(MetricConditionLinkE2eTest, TestMultiplePredicatesAndLinks2) { + auto config = CreateStatsdConfig(); + uint64_t bucketStartTimeNs = 10000000000; + uint64_t bucketSizeNs = + TimeUnitToBucketSizeInMillis(config.count_metric(0).bucket()) * 1000000LL; + + ConfigKey cfgKey; + auto processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey); + ASSERT_EQ(processor->mMetricsManagers.size(), 1u); + EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid()); + + int appUid = 123; + auto crashEvent1 = CreateAppCrashEvent(bucketStartTimeNs + 1, appUid); + auto crashEvent2 = CreateAppCrashEvent(bucketStartTimeNs + 201, appUid); + auto crashEvent3 = CreateAppCrashEvent(bucketStartTimeNs + 2 * bucketSizeNs - 101, appUid); + + auto crashEvent4 = CreateAppCrashEvent(bucketStartTimeNs + 51, appUid); + auto crashEvent5 = CreateAppCrashEvent(bucketStartTimeNs + bucketSizeNs + 299, appUid); + auto crashEvent6 = CreateAppCrashEvent(bucketStartTimeNs + bucketSizeNs + 2001, appUid); + + auto crashEvent7 = CreateAppCrashEvent(bucketStartTimeNs + 16, appUid); + auto crashEvent8 = CreateAppCrashEvent(bucketStartTimeNs + bucketSizeNs + 249, appUid); + + auto crashEvent9 = CreateAppCrashEvent(bucketStartTimeNs + bucketSizeNs + 351, appUid); + auto crashEvent10 = CreateAppCrashEvent(bucketStartTimeNs + 2 * bucketSizeNs - 2, appUid); + + auto screenTurnedOnEvent = CreateScreenStateChangedEvent( + bucketStartTimeNs + 2, android::view::DisplayStateEnum::DISPLAY_STATE_ON); + auto screenTurnedOffEvent = CreateScreenStateChangedEvent( + bucketStartTimeNs + 200, android::view::DisplayStateEnum::DISPLAY_STATE_OFF); + auto screenTurnedOnEvent2 = + CreateScreenStateChangedEvent(bucketStartTimeNs + 2 * bucketSizeNs - 100, + android::view::DisplayStateEnum::DISPLAY_STATE_ON); + + std::vector attributionUids = {appUid, appUid + 1}; + std::vector attributionTags = {"App1", "GMSCoreModule1"}; + + auto syncOnEvent1 = CreateSyncStartEvent(bucketStartTimeNs + 50, attributionUids, + attributionTags, "ReadEmail"); + auto syncOffEvent1 = CreateSyncEndEvent(bucketStartTimeNs + bucketSizeNs + 300, attributionUids, + attributionTags, "ReadEmail"); + auto syncOnEvent2 = CreateSyncStartEvent(bucketStartTimeNs + bucketSizeNs + 2000, + attributionUids, attributionTags, "ReadDoc"); + + auto moveToBackgroundEvent1 = CreateMoveToBackgroundEvent(bucketStartTimeNs + 15, appUid); + auto moveToForegroundEvent1 = + CreateMoveToForegroundEvent(bucketStartTimeNs + bucketSizeNs + 250, appUid); + + auto moveToBackgroundEvent2 = + CreateMoveToBackgroundEvent(bucketStartTimeNs + bucketSizeNs + 350, appUid); + auto moveToForegroundEvent2 = + CreateMoveToForegroundEvent(bucketStartTimeNs + 2 * bucketSizeNs - 1, appUid); + + /* + bucket #1 bucket #2 + + + | | | | | | | | | | (crashEvents) + |-------------------------------------|-----------------------------------|--------- + + | | (MoveToBkground) + + | | (MoveToForeground) + + | | (SyncIsOn) + | (SyncIsOff) + | | (ScreenIsOn) + | (ScreenIsOff) + */ + std::vector> events; + events.push_back(std::move(crashEvent1)); + events.push_back(std::move(crashEvent2)); + events.push_back(std::move(crashEvent3)); + events.push_back(std::move(crashEvent4)); + events.push_back(std::move(crashEvent5)); + events.push_back(std::move(crashEvent6)); + events.push_back(std::move(crashEvent7)); + events.push_back(std::move(crashEvent8)); + events.push_back(std::move(crashEvent9)); + events.push_back(std::move(crashEvent10)); + events.push_back(std::move(screenTurnedOnEvent)); + events.push_back(std::move(screenTurnedOffEvent)); + events.push_back(std::move(screenTurnedOnEvent2)); + events.push_back(std::move(syncOnEvent1)); + events.push_back(std::move(syncOffEvent1)); + events.push_back(std::move(syncOnEvent2)); + events.push_back(std::move(moveToBackgroundEvent1)); + events.push_back(std::move(moveToForegroundEvent1)); + events.push_back(std::move(moveToBackgroundEvent2)); + events.push_back(std::move(moveToForegroundEvent2)); + + sortLogEventsByTimestamp(&events); + + for (const auto& event : events) { + processor->OnLogEvent(event.get()); + } + ConfigMetricsReportList reports; + vector buffer; + + processor->onDumpReport(cfgKey, bucketStartTimeNs + 2 * bucketSizeNs + 1, false, true, ADB_DUMP, + FAST, &buffer); + EXPECT_TRUE(buffer.size() > 0); + EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size())); + backfillDimensionPath(&reports); + backfillStringInReport(&reports); + backfillStartEndTimestamp(&reports); + ASSERT_EQ(reports.reports_size(), 1); + ASSERT_EQ(reports.reports(0).metrics_size(), 1); + ASSERT_EQ(reports.reports(0).metrics(0).count_metrics().data_size(), 1); + ASSERT_EQ(reports.reports(0).metrics(0).count_metrics().data(0).bucket_info_size(), 2); + EXPECT_EQ(reports.reports(0).metrics(0).count_metrics().data(0).bucket_info(0).count(), 1); + EXPECT_EQ(reports.reports(0).metrics(0).count_metrics().data(0).bucket_info(1).count(), 3); + auto data = reports.reports(0).metrics(0).count_metrics().data(0); + // Validate dimension value. + EXPECT_EQ(data.dimensions_in_what().field(), util::PROCESS_LIFE_CYCLE_STATE_CHANGED); + ASSERT_EQ(data.dimensions_in_what().value_tuple().dimensions_value_size(), 1); + // Uid field. + 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(), appUid); +} + +#else +GTEST_LOG_(INFO) << "This test does nothing.\n"; +#endif + +} // namespace statsd +} // namespace os +} // namespace android diff --git a/statsd/tests/e2e/PartialBucket_e2e_test.cpp b/statsd/tests/e2e/PartialBucket_e2e_test.cpp new file mode 100644 index 00000000..783f31c7 --- /dev/null +++ b/statsd/tests/e2e/PartialBucket_e2e_test.cpp @@ -0,0 +1,433 @@ +// 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 +#include + +#include + +#include "src/StatsLogProcessor.h" +#include "src/StatsService.h" +#include "src/stats_log_util.h" +#include "tests/statsd_test_util.h" + +using::ndk::SharedRefBase; +using std::shared_ptr; + +namespace android { +namespace os { +namespace statsd { + +#ifdef __ANDROID__ +namespace { +const string kApp1 = "app1.sharing.1"; +const int kConfigKey = 789130123; // Randomly chosen to avoid collisions with existing configs. +const int kCallingUid = 0; // Randomly chosen + +void SendConfig(shared_ptr& service, const StatsdConfig& config) { + string str; + config.SerializeToString(&str); + std::vector configAsVec(str.begin(), str.end()); + service->addConfiguration(kConfigKey, configAsVec, kCallingUid); +} + +ConfigMetricsReport GetReports(sp processor, int64_t timestamp, + bool include_current = false) { + vector output; + ConfigKey configKey(AIBinder_getCallingUid(), kConfigKey); + processor->onDumpReport(configKey, timestamp, include_current /* include_current_bucket*/, + true /* erase_data */, ADB_DUMP, NO_TIME_CONSTRAINTS, &output); + ConfigMetricsReportList reports; + reports.ParseFromArray(output.data(), output.size()); + EXPECT_EQ(1, reports.reports_size()); + return reports.reports(kCallingUid); +} + +StatsdConfig MakeConfig() { + StatsdConfig config; + config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root. + + auto appCrashMatcher = CreateProcessCrashAtomMatcher(); + *config.add_atom_matcher() = appCrashMatcher; + auto countMetric = config.add_count_metric(); + countMetric->set_id(StringToId("AppCrashes")); + countMetric->set_what(appCrashMatcher.id()); + countMetric->set_bucket(FIVE_MINUTES); + return config; +} + +StatsdConfig MakeValueMetricConfig(int64_t minTime) { + StatsdConfig config; + config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root. + config.add_default_pull_packages("AID_ROOT"); // Fake puller is registered with root. + + auto pulledAtomMatcher = + CreateSimpleAtomMatcher("TestMatcher", util::SUBSYSTEM_SLEEP_STATE); + *config.add_atom_matcher() = pulledAtomMatcher; + *config.add_atom_matcher() = CreateScreenTurnedOnAtomMatcher(); + *config.add_atom_matcher() = CreateScreenTurnedOffAtomMatcher(); + + auto valueMetric = config.add_value_metric(); + valueMetric->set_id(123456); + valueMetric->set_what(pulledAtomMatcher.id()); + *valueMetric->mutable_value_field() = + CreateDimensions(util::SUBSYSTEM_SLEEP_STATE, {4 /* time sleeping field */}); + *valueMetric->mutable_dimensions_in_what() = + CreateDimensions(util::SUBSYSTEM_SLEEP_STATE, {1 /* subsystem name */}); + valueMetric->set_bucket(FIVE_MINUTES); + valueMetric->set_min_bucket_size_nanos(minTime); + valueMetric->set_use_absolute_value_on_reset(true); + valueMetric->set_skip_zero_diff_output(false); + return config; +} + +StatsdConfig MakeGaugeMetricConfig(int64_t minTime) { + StatsdConfig config; + config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root. + config.add_default_pull_packages("AID_ROOT"); // Fake puller is registered with root. + + auto pulledAtomMatcher = + CreateSimpleAtomMatcher("TestMatcher", util::SUBSYSTEM_SLEEP_STATE); + *config.add_atom_matcher() = pulledAtomMatcher; + *config.add_atom_matcher() = CreateScreenTurnedOnAtomMatcher(); + *config.add_atom_matcher() = CreateScreenTurnedOffAtomMatcher(); + + auto gaugeMetric = config.add_gauge_metric(); + gaugeMetric->set_id(123456); + gaugeMetric->set_what(pulledAtomMatcher.id()); + gaugeMetric->mutable_gauge_fields_filter()->set_include_all(true); + *gaugeMetric->mutable_dimensions_in_what() = + CreateDimensions(util::SUBSYSTEM_SLEEP_STATE, {1 /* subsystem name */}); + gaugeMetric->set_bucket(FIVE_MINUTES); + gaugeMetric->set_min_bucket_size_nanos(minTime); + return config; +} +} // anonymous namespace + +TEST(PartialBucketE2eTest, TestCountMetricWithoutSplit) { + shared_ptr service = SharedRefBase::make(nullptr, nullptr); + SendConfig(service, MakeConfig()); + int64_t start = getElapsedRealtimeNs(); // This is the start-time the metrics producers are + // initialized with. + + service->mProcessor->OnLogEvent(CreateAppCrashEvent(start + 1, 100).get()); + service->mProcessor->OnLogEvent(CreateAppCrashEvent(start + 2, 100).get()); + + ConfigMetricsReport report = GetReports(service->mProcessor, start + 3); + // Expect no metrics since the bucket has not finished yet. + ASSERT_EQ(1, report.metrics_size()); + ASSERT_EQ(0, report.metrics(0).count_metrics().data_size()); +} + +TEST(PartialBucketE2eTest, TestCountMetricNoSplitOnNewApp) { + shared_ptr service = SharedRefBase::make(nullptr, nullptr); + SendConfig(service, MakeConfig()); + int64_t start = getElapsedRealtimeNs(); // This is the start-time the metrics producers are + // initialized with. + + // Force the uidmap to update at timestamp 2. + service->mProcessor->OnLogEvent(CreateAppCrashEvent(start + 1, 100).get()); + // This is a new installation, so there shouldn't be a split (should be same as the without + // split case). + service->mUidMap->updateApp(start + 2, String16(kApp1.c_str()), 1, 2, String16("v2"), + String16("")); + // Goes into the second bucket. + service->mProcessor->OnLogEvent(CreateAppCrashEvent(start + 3, 100).get()); + + ConfigMetricsReport report = GetReports(service->mProcessor, start + 4); + ASSERT_EQ(1, report.metrics_size()); + ASSERT_EQ(0, report.metrics(0).count_metrics().data_size()); +} + +TEST(PartialBucketE2eTest, TestCountMetricSplitOnUpgrade) { + shared_ptr service = SharedRefBase::make(nullptr, nullptr); + SendConfig(service, MakeConfig()); + int64_t start = getElapsedRealtimeNs(); // This is the start-time the metrics producers are + // initialized with. + service->mUidMap->updateMap(start, {1}, {1}, {String16("v1")}, {String16(kApp1.c_str())}, + {String16("")}); + + // Force the uidmap to update at timestamp 2. + service->mProcessor->OnLogEvent(CreateAppCrashEvent(start + 1, 100).get()); + service->mUidMap->updateApp(start + 2, String16(kApp1.c_str()), 1, 2, String16("v2"), + String16("")); + // Goes into the second bucket. + service->mProcessor->OnLogEvent(CreateAppCrashEvent(start + 3, 100).get()); + + ConfigMetricsReport report = GetReports(service->mProcessor, start + 4); + backfillStartEndTimestamp(&report); + + ASSERT_EQ(1, report.metrics_size()); + ASSERT_EQ(1, report.metrics(0).count_metrics().data_size()); + ASSERT_EQ(1, report.metrics(0).count_metrics().data(0).bucket_info_size()); + EXPECT_TRUE(report.metrics(0) + .count_metrics() + .data(0) + .bucket_info(0) + .has_start_bucket_elapsed_nanos()); + EXPECT_TRUE(report.metrics(0) + .count_metrics() + .data(0) + .bucket_info(0) + .has_end_bucket_elapsed_nanos()); + EXPECT_EQ(1, report.metrics(0).count_metrics().data(0).bucket_info(0).count()); +} + +TEST(PartialBucketE2eTest, TestCountMetricSplitOnRemoval) { + shared_ptr service = SharedRefBase::make(nullptr, nullptr); + SendConfig(service, MakeConfig()); + int64_t start = getElapsedRealtimeNs(); // This is the start-time the metrics producers are + // initialized with. + service->mUidMap->updateMap(start, {1}, {1}, {String16("v1")}, {String16(kApp1.c_str())}, + {String16("")}); + + // Force the uidmap to update at timestamp 2. + service->mProcessor->OnLogEvent(CreateAppCrashEvent(start + 1, 100).get()); + service->mUidMap->removeApp(start + 2, String16(kApp1.c_str()), 1); + // Goes into the second bucket. + service->mProcessor->OnLogEvent(CreateAppCrashEvent(start + 3, 100).get()); + + ConfigMetricsReport report = GetReports(service->mProcessor, start + 4); + backfillStartEndTimestamp(&report); + + ASSERT_EQ(1, report.metrics_size()); + ASSERT_EQ(1, report.metrics(0).count_metrics().data_size()); + ASSERT_EQ(1, report.metrics(0).count_metrics().data(0).bucket_info_size()); + EXPECT_TRUE(report.metrics(0) + .count_metrics() + .data(0) + .bucket_info(0) + .has_start_bucket_elapsed_nanos()); + EXPECT_TRUE(report.metrics(0) + .count_metrics() + .data(0) + .bucket_info(0) + .has_end_bucket_elapsed_nanos()); + EXPECT_EQ(1, report.metrics(0).count_metrics().data(0).bucket_info(0).count()); +} + +TEST(PartialBucketE2eTest, TestCountMetricSplitOnBoot) { + shared_ptr service = SharedRefBase::make(nullptr, nullptr); + SendConfig(service, MakeConfig()); + int64_t start = getElapsedRealtimeNs(); // This is the start-time the metrics producers are + // initialized with. + + // Goes into the first bucket + service->mProcessor->OnLogEvent(CreateAppCrashEvent(start + NS_PER_SEC, 100).get()); + int64_t bootCompleteTimeNs = start + 2 * NS_PER_SEC; + service->mProcessor->onStatsdInitCompleted(bootCompleteTimeNs); + // Goes into the second bucket. + service->mProcessor->OnLogEvent(CreateAppCrashEvent(start + 3 * NS_PER_SEC, 100).get()); + + ConfigMetricsReport report = GetReports(service->mProcessor, start + 4 * NS_PER_SEC); + backfillStartEndTimestamp(&report); + + ASSERT_EQ(1, report.metrics_size()); + ASSERT_EQ(1, report.metrics(0).count_metrics().data_size()); + ASSERT_EQ(1, report.metrics(0).count_metrics().data(0).bucket_info_size()); + EXPECT_TRUE(report.metrics(0) + .count_metrics() + .data(0) + .bucket_info(0) + .has_start_bucket_elapsed_nanos()); + EXPECT_EQ(MillisToNano(NanoToMillis(bootCompleteTimeNs)), + report.metrics(0).count_metrics().data(0).bucket_info(0).end_bucket_elapsed_nanos()); + EXPECT_EQ(1, report.metrics(0).count_metrics().data(0).bucket_info(0).count()); +} + +TEST(PartialBucketE2eTest, TestValueMetricWithoutMinPartialBucket) { + shared_ptr service = SharedRefBase::make(nullptr, nullptr); + service->mPullerManager->RegisterPullAtomCallback( + /*uid=*/0, util::SUBSYSTEM_SLEEP_STATE, NS_PER_SEC, NS_PER_SEC * 10, {}, + SharedRefBase::make()); + // Partial buckets don't occur when app is first installed. + service->mUidMap->updateApp(1, String16(kApp1.c_str()), 1, 1, String16("v1"), String16("")); + SendConfig(service, MakeValueMetricConfig(0)); + int64_t start = getElapsedRealtimeNs(); // This is the start-time the metrics producers are + // initialized with. + + service->mProcessor->informPullAlarmFired(5 * 60 * NS_PER_SEC + start); + int64_t appUpgradeTimeNs = 5 * 60 * NS_PER_SEC + start + 2 * NS_PER_SEC; + service->mUidMap->updateApp(appUpgradeTimeNs, String16(kApp1.c_str()), 1, 2, String16("v2"), + String16("")); + + ConfigMetricsReport report = + GetReports(service->mProcessor, 5 * 60 * NS_PER_SEC + start + 100 * NS_PER_SEC); + backfillStartEndTimestamp(&report); + + ASSERT_EQ(1, report.metrics_size()); + ASSERT_EQ(0, report.metrics(0).value_metrics().skipped_size()); + + // The fake subsystem state sleep puller returns two atoms. + ASSERT_EQ(2, report.metrics(0).value_metrics().data_size()); + ASSERT_EQ(2, report.metrics(0).value_metrics().data(0).bucket_info_size()); + EXPECT_EQ(MillisToNano(NanoToMillis(appUpgradeTimeNs)), + report.metrics(0).value_metrics().data(0).bucket_info(1).end_bucket_elapsed_nanos()); +} + +TEST(PartialBucketE2eTest, TestValueMetricWithMinPartialBucket) { + shared_ptr service = SharedRefBase::make(nullptr, nullptr); + service->mPullerManager->RegisterPullAtomCallback( + /*uid=*/0, util::SUBSYSTEM_SLEEP_STATE, NS_PER_SEC, NS_PER_SEC * 10, {}, + SharedRefBase::make()); + // Partial buckets don't occur when app is first installed. + service->mUidMap->updateApp(1, String16(kApp1.c_str()), 1, 1, String16("v1"), String16("")); + SendConfig(service, MakeValueMetricConfig(60 * NS_PER_SEC /* One minute */)); + int64_t start = getElapsedRealtimeNs(); // This is the start-time the metrics producers are + // initialized with. + + const int64_t endSkipped = 5 * 60 * NS_PER_SEC + start + 2 * NS_PER_SEC; + service->mProcessor->informPullAlarmFired(5 * 60 * NS_PER_SEC + start); + service->mUidMap->updateApp(endSkipped, String16(kApp1.c_str()), 1, 2, String16("v2"), + String16("")); + + ConfigMetricsReport report = + GetReports(service->mProcessor, 5 * 60 * NS_PER_SEC + start + 100 * NS_PER_SEC); + backfillStartEndTimestamp(&report); + + ASSERT_EQ(1, report.metrics_size()); + ASSERT_EQ(1, report.metrics(0).value_metrics().skipped_size()); + EXPECT_TRUE(report.metrics(0).value_metrics().skipped(0).has_start_bucket_elapsed_nanos()); + // Can't test the start time since it will be based on the actual time when the pulling occurs. + EXPECT_EQ(MillisToNano(NanoToMillis(endSkipped)), + report.metrics(0).value_metrics().skipped(0).end_bucket_elapsed_nanos()); + + ASSERT_EQ(2, report.metrics(0).value_metrics().data_size()); + ASSERT_EQ(1, report.metrics(0).value_metrics().data(0).bucket_info_size()); +} + +TEST(PartialBucketE2eTest, TestValueMetricOnBootWithoutMinPartialBucket) { + shared_ptr service = SharedRefBase::make(nullptr, nullptr); + // Initial pull will fail since puller is not registered. + SendConfig(service, MakeValueMetricConfig(0)); + int64_t start = getElapsedRealtimeNs(); // This is the start-time the metrics producers are + // initialized with. + + service->mPullerManager->RegisterPullAtomCallback( + /*uid=*/0, util::SUBSYSTEM_SLEEP_STATE, NS_PER_SEC, NS_PER_SEC * 10, {}, + SharedRefBase::make()); + + int64_t bootCompleteTimeNs = start + NS_PER_SEC; + service->mProcessor->onStatsdInitCompleted(bootCompleteTimeNs); + + service->mProcessor->informPullAlarmFired(5 * 60 * NS_PER_SEC + start); + + ConfigMetricsReport report = GetReports(service->mProcessor, 5 * 60 * NS_PER_SEC + start + 100); + backfillStartEndTimestamp(&report); + + // First bucket is dropped due to the initial pull failing + ASSERT_EQ(1, report.metrics_size()); + ASSERT_EQ(1, report.metrics(0).value_metrics().skipped_size()); + EXPECT_EQ(MillisToNano(NanoToMillis(bootCompleteTimeNs)), + report.metrics(0).value_metrics().skipped(0).end_bucket_elapsed_nanos()); + + // The fake subsystem state sleep puller returns two atoms. + ASSERT_EQ(2, report.metrics(0).value_metrics().data_size()); + ASSERT_EQ(1, report.metrics(0).value_metrics().data(0).bucket_info_size()); + EXPECT_EQ( + MillisToNano(NanoToMillis(bootCompleteTimeNs)), + report.metrics(0).value_metrics().data(0).bucket_info(0).start_bucket_elapsed_nanos()); +} + +TEST(PartialBucketE2eTest, TestGaugeMetricWithoutMinPartialBucket) { + shared_ptr service = SharedRefBase::make(nullptr, nullptr); + service->mPullerManager->RegisterPullAtomCallback( + /*uid=*/0, util::SUBSYSTEM_SLEEP_STATE, NS_PER_SEC, NS_PER_SEC * 10, {}, + SharedRefBase::make()); + // Partial buckets don't occur when app is first installed. + service->mUidMap->updateApp(1, String16(kApp1.c_str()), 1, 1, String16("v1"), String16("")); + SendConfig(service, MakeGaugeMetricConfig(0)); + int64_t start = getElapsedRealtimeNs(); // This is the start-time the metrics producers are + // initialized with. + + service->mProcessor->informPullAlarmFired(5 * 60 * NS_PER_SEC + start); + service->mUidMap->updateApp(5 * 60 * NS_PER_SEC + start + 2, String16(kApp1.c_str()), 1, 2, + String16("v2"), String16("")); + + ConfigMetricsReport report = GetReports(service->mProcessor, 5 * 60 * NS_PER_SEC + start + 100); + backfillStartEndTimestamp(&report); + ASSERT_EQ(1, report.metrics_size()); + ASSERT_EQ(0, report.metrics(0).gauge_metrics().skipped_size()); + // The fake subsystem state sleep puller returns two atoms. + ASSERT_EQ(2, report.metrics(0).gauge_metrics().data_size()); + ASSERT_EQ(2, report.metrics(0).gauge_metrics().data(0).bucket_info_size()); +} + +TEST(PartialBucketE2eTest, TestGaugeMetricWithMinPartialBucket) { + shared_ptr service = SharedRefBase::make(nullptr, nullptr); + // Partial buckets don't occur when app is first installed. + service->mUidMap->updateApp(1, String16(kApp1.c_str()), 1, 1, String16("v1"), String16("")); + service->mPullerManager->RegisterPullAtomCallback( + /*uid=*/0, util::SUBSYSTEM_SLEEP_STATE, NS_PER_SEC, NS_PER_SEC * 10, {}, + SharedRefBase::make()); + SendConfig(service, MakeGaugeMetricConfig(60 * NS_PER_SEC /* One minute */)); + int64_t start = getElapsedRealtimeNs(); // This is the start-time the metrics producers are + // initialized with. + + const int64_t endSkipped = 5 * 60 * NS_PER_SEC + start + 2; + service->mProcessor->informPullAlarmFired(5 * 60 * NS_PER_SEC + start); + service->mUidMap->updateApp(endSkipped, String16(kApp1.c_str()), 1, 2, String16("v2"), + String16("")); + + ConfigMetricsReport report = + GetReports(service->mProcessor, 5 * 60 * NS_PER_SEC + start + 100 * NS_PER_SEC); + backfillStartEndTimestamp(&report); + ASSERT_EQ(1, report.metrics_size()); + ASSERT_EQ(1, report.metrics(0).gauge_metrics().skipped_size()); + // Can't test the start time since it will be based on the actual time when the pulling occurs. + EXPECT_TRUE(report.metrics(0).gauge_metrics().skipped(0).has_start_bucket_elapsed_nanos()); + EXPECT_EQ(MillisToNano(NanoToMillis(endSkipped)), + report.metrics(0).gauge_metrics().skipped(0).end_bucket_elapsed_nanos()); + ASSERT_EQ(2, report.metrics(0).gauge_metrics().data_size()); + ASSERT_EQ(1, report.metrics(0).gauge_metrics().data(0).bucket_info_size()); +} + +TEST(PartialBucketE2eTest, TestGaugeMetricOnBootWithoutMinPartialBucket) { + shared_ptr service = SharedRefBase::make(nullptr, nullptr); + // Initial pull will fail since puller hasn't been registered. + SendConfig(service, MakeGaugeMetricConfig(0)); + int64_t start = getElapsedRealtimeNs(); // This is the start-time the metrics producers are + // initialized with. + + service->mPullerManager->RegisterPullAtomCallback( + /*uid=*/0, util::SUBSYSTEM_SLEEP_STATE, NS_PER_SEC, NS_PER_SEC * 10, {}, + SharedRefBase::make()); + + int64_t bootCompleteTimeNs = start + NS_PER_SEC; + service->mProcessor->onStatsdInitCompleted(bootCompleteTimeNs); + + service->mProcessor->informPullAlarmFired(5 * 60 * NS_PER_SEC + start); + + ConfigMetricsReport report = GetReports(service->mProcessor, 5 * 60 * NS_PER_SEC + start + 100); + backfillStartEndTimestamp(&report); + + ASSERT_EQ(1, report.metrics_size()); + ASSERT_EQ(0, report.metrics(0).gauge_metrics().skipped_size()); + // The fake subsystem state sleep puller returns two atoms. + ASSERT_EQ(2, report.metrics(0).gauge_metrics().data_size()); + // No data in the first bucket, so nothing is reported + ASSERT_EQ(1, report.metrics(0).gauge_metrics().data(0).bucket_info_size()); + EXPECT_EQ( + MillisToNano(NanoToMillis(bootCompleteTimeNs)), + report.metrics(0).gauge_metrics().data(0).bucket_info(0).start_bucket_elapsed_nanos()); +} + +#else +GTEST_LOG_(INFO) << "This test does nothing.\n"; +#endif + +} // namespace statsd +} // namespace os +} // namespace android diff --git a/statsd/tests/e2e/ValueMetric_pull_e2e_test.cpp b/statsd/tests/e2e/ValueMetric_pull_e2e_test.cpp new file mode 100644 index 00000000..4d392827 --- /dev/null +++ b/statsd/tests/e2e/ValueMetric_pull_e2e_test.cpp @@ -0,0 +1,679 @@ +// 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 + +#include "src/StatsLogProcessor.h" +#include "src/stats_log_util.h" +#include "tests/statsd_test_util.h" + +#include + +using ::ndk::SharedRefBase; + +namespace android { +namespace os { +namespace statsd { + +#ifdef __ANDROID__ + +namespace { + +const int64_t metricId = 123456; + +StatsdConfig CreateStatsdConfig(bool useCondition = true) { + StatsdConfig config; + config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root. + config.add_default_pull_packages("AID_ROOT"); // Fake puller is registered with root. + auto pulledAtomMatcher = + CreateSimpleAtomMatcher("TestMatcher", util::SUBSYSTEM_SLEEP_STATE); + *config.add_atom_matcher() = pulledAtomMatcher; + *config.add_atom_matcher() = CreateScreenTurnedOnAtomMatcher(); + *config.add_atom_matcher() = CreateScreenTurnedOffAtomMatcher(); + + auto screenIsOffPredicate = CreateScreenIsOffPredicate(); + *config.add_predicate() = screenIsOffPredicate; + + auto valueMetric = config.add_value_metric(); + valueMetric->set_id(metricId); + valueMetric->set_what(pulledAtomMatcher.id()); + if (useCondition) { + valueMetric->set_condition(screenIsOffPredicate.id()); + } + *valueMetric->mutable_value_field() = + CreateDimensions(util::SUBSYSTEM_SLEEP_STATE, {4 /* time sleeping field */}); + *valueMetric->mutable_dimensions_in_what() = + CreateDimensions(util::SUBSYSTEM_SLEEP_STATE, {1 /* subsystem name */}); + valueMetric->set_bucket(FIVE_MINUTES); + valueMetric->set_use_absolute_value_on_reset(true); + valueMetric->set_skip_zero_diff_output(false); + valueMetric->set_max_pull_delay_sec(INT_MAX); + return config; +} + +StatsdConfig CreateStatsdConfigWithStates() { + StatsdConfig config; + config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root. + config.add_default_pull_packages("AID_ROOT"); // Fake puller is registered with root. + + auto pulledAtomMatcher = CreateSimpleAtomMatcher("TestMatcher", util::SUBSYSTEM_SLEEP_STATE); + *config.add_atom_matcher() = pulledAtomMatcher; + *config.add_atom_matcher() = CreateScreenTurnedOnAtomMatcher(); + *config.add_atom_matcher() = CreateScreenTurnedOffAtomMatcher(); + *config.add_atom_matcher() = CreateBatteryStateNoneMatcher(); + *config.add_atom_matcher() = CreateBatteryStateUsbMatcher(); + + auto screenOnPredicate = CreateScreenIsOnPredicate(); + *config.add_predicate() = screenOnPredicate; + + auto screenOffPredicate = CreateScreenIsOffPredicate(); + *config.add_predicate() = screenOffPredicate; + + auto deviceUnpluggedPredicate = CreateDeviceUnpluggedPredicate(); + *config.add_predicate() = deviceUnpluggedPredicate; + + auto screenOnOnBatteryPredicate = config.add_predicate(); + screenOnOnBatteryPredicate->set_id(StringToId("screenOnOnBatteryPredicate")); + screenOnOnBatteryPredicate->mutable_combination()->set_operation(LogicalOperation::AND); + addPredicateToPredicateCombination(screenOnPredicate, screenOnOnBatteryPredicate); + addPredicateToPredicateCombination(deviceUnpluggedPredicate, screenOnOnBatteryPredicate); + + auto screenOffOnBatteryPredicate = config.add_predicate(); + screenOffOnBatteryPredicate->set_id(StringToId("ScreenOffOnBattery")); + screenOffOnBatteryPredicate->mutable_combination()->set_operation(LogicalOperation::AND); + addPredicateToPredicateCombination(screenOffPredicate, screenOffOnBatteryPredicate); + addPredicateToPredicateCombination(deviceUnpluggedPredicate, screenOffOnBatteryPredicate); + + const State screenState = + CreateScreenStateWithSimpleOnOffMap(/*screen on id=*/321, /*screen off id=*/123); + *config.add_state() = screenState; + + // ValueMetricSubsystemSleepWhileScreenOnOnBattery + auto valueMetric1 = config.add_value_metric(); + valueMetric1->set_id(metricId); + valueMetric1->set_what(pulledAtomMatcher.id()); + valueMetric1->set_condition(screenOnOnBatteryPredicate->id()); + *valueMetric1->mutable_value_field() = + CreateDimensions(util::SUBSYSTEM_SLEEP_STATE, {4 /* time sleeping field */}); + valueMetric1->set_bucket(FIVE_MINUTES); + valueMetric1->set_use_absolute_value_on_reset(true); + valueMetric1->set_skip_zero_diff_output(false); + valueMetric1->set_max_pull_delay_sec(INT_MAX); + + // ValueMetricSubsystemSleepWhileScreenOffOnBattery + ValueMetric* valueMetric2 = config.add_value_metric(); + valueMetric2->set_id(StringToId("ValueMetricSubsystemSleepWhileScreenOffOnBattery")); + valueMetric2->set_what(pulledAtomMatcher.id()); + valueMetric2->set_condition(screenOffOnBatteryPredicate->id()); + *valueMetric2->mutable_value_field() = + CreateDimensions(util::SUBSYSTEM_SLEEP_STATE, {4 /* time sleeping field */}); + valueMetric2->set_bucket(FIVE_MINUTES); + valueMetric2->set_use_absolute_value_on_reset(true); + valueMetric2->set_skip_zero_diff_output(false); + valueMetric2->set_max_pull_delay_sec(INT_MAX); + + // ValueMetricSubsystemSleepWhileOnBatterySliceScreen + ValueMetric* valueMetric3 = config.add_value_metric(); + valueMetric3->set_id(StringToId("ValueMetricSubsystemSleepWhileOnBatterySliceScreen")); + valueMetric3->set_what(pulledAtomMatcher.id()); + valueMetric3->set_condition(deviceUnpluggedPredicate.id()); + *valueMetric3->mutable_value_field() = + CreateDimensions(util::SUBSYSTEM_SLEEP_STATE, {4 /* time sleeping field */}); + valueMetric3->add_slice_by_state(screenState.id()); + valueMetric3->set_bucket(FIVE_MINUTES); + valueMetric3->set_use_absolute_value_on_reset(true); + valueMetric3->set_skip_zero_diff_output(false); + valueMetric3->set_max_pull_delay_sec(INT_MAX); + return config; +} + +} // namespace + +/** + * Tests the initial condition and condition after the first log events for + * value metrics with either a combination condition or simple condition. + * + * Metrics should be initialized with condition kUnknown (given that the + * predicate is using the default InitialValue of UNKNOWN). The condition should + * be updated to either kFalse or kTrue if a condition event is logged for all + * children conditions. + */ +TEST(ValueMetricE2eTest, TestInitialConditionChanges) { + StatsdConfig config = CreateStatsdConfigWithStates(); + int64_t baseTimeNs = getElapsedRealtimeNs(); + int64_t configAddedTimeNs = 10 * 60 * NS_PER_SEC + baseTimeNs; + int64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(config.value_metric(0).bucket()) * 1000000; + + ConfigKey cfgKey; + int32_t tagId = util::SUBSYSTEM_SLEEP_STATE; + auto processor = + CreateStatsLogProcessor(baseTimeNs, configAddedTimeNs, config, cfgKey, + SharedRefBase::make(), tagId); + + EXPECT_EQ(processor->mMetricsManagers.size(), 1u); + sp metricsManager = processor->mMetricsManagers.begin()->second; + EXPECT_TRUE(metricsManager->isConfigValid()); + EXPECT_EQ(3, metricsManager->mAllMetricProducers.size()); + + // Combination condition metric - screen on and device unplugged + sp metricProducer1 = metricsManager->mAllMetricProducers[0]; + // Simple condition metric - device unplugged + sp metricProducer2 = metricsManager->mAllMetricProducers[2]; + + EXPECT_EQ(ConditionState::kUnknown, metricProducer1->mCondition); + EXPECT_EQ(ConditionState::kUnknown, metricProducer2->mCondition); + + auto screenOnEvent = + CreateScreenStateChangedEvent(configAddedTimeNs + 30, android::view::DISPLAY_STATE_ON); + processor->OnLogEvent(screenOnEvent.get()); + EXPECT_EQ(ConditionState::kUnknown, metricProducer1->mCondition); + EXPECT_EQ(ConditionState::kUnknown, metricProducer2->mCondition); + + auto screenOffEvent = + CreateScreenStateChangedEvent(configAddedTimeNs + 40, android::view::DISPLAY_STATE_OFF); + processor->OnLogEvent(screenOffEvent.get()); + EXPECT_EQ(ConditionState::kUnknown, metricProducer1->mCondition); + EXPECT_EQ(ConditionState::kUnknown, metricProducer2->mCondition); + + auto pluggedUsbEvent = CreateBatteryStateChangedEvent( + configAddedTimeNs + 50, BatteryPluggedStateEnum::BATTERY_PLUGGED_USB); + processor->OnLogEvent(pluggedUsbEvent.get()); + EXPECT_EQ(ConditionState::kFalse, metricProducer1->mCondition); + EXPECT_EQ(ConditionState::kFalse, metricProducer2->mCondition); + + auto pluggedNoneEvent = CreateBatteryStateChangedEvent( + configAddedTimeNs + 70, BatteryPluggedStateEnum::BATTERY_PLUGGED_NONE); + processor->OnLogEvent(pluggedNoneEvent.get()); + EXPECT_EQ(ConditionState::kFalse, metricProducer1->mCondition); + EXPECT_EQ(ConditionState::kTrue, metricProducer2->mCondition); +} + +TEST(ValueMetricE2eTest, TestPulledEvents) { + auto config = CreateStatsdConfig(); + int64_t baseTimeNs = getElapsedRealtimeNs(); + int64_t configAddedTimeNs = 10 * 60 * NS_PER_SEC + baseTimeNs; + int64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(config.value_metric(0).bucket()) * 1000000; + + ConfigKey cfgKey; + auto processor = CreateStatsLogProcessor(baseTimeNs, configAddedTimeNs, config, cfgKey, + SharedRefBase::make(), + util::SUBSYSTEM_SLEEP_STATE); + ASSERT_EQ(processor->mMetricsManagers.size(), 1u); + EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid()); + processor->mPullerManager->ForceClearPullerCache(); + + int startBucketNum = processor->mMetricsManagers.begin() + ->second->mAllMetricProducers[0] + ->getCurrentBucketNum(); + EXPECT_GT(startBucketNum, (int64_t)0); + + // When creating the config, the value metric producer should register the alarm at the + // end of the current bucket. + ASSERT_EQ((size_t)1, processor->mPullerManager->mReceivers.size()); + EXPECT_EQ(bucketSizeNs, + processor->mPullerManager->mReceivers.begin()->second.front().intervalNs); + int64_t& expectedPullTimeNs = + processor->mPullerManager->mReceivers.begin()->second.front().nextPullTimeNs; + EXPECT_EQ(baseTimeNs + startBucketNum * bucketSizeNs + bucketSizeNs, expectedPullTimeNs); + + auto screenOffEvent = + CreateScreenStateChangedEvent(configAddedTimeNs + 55, android::view::DISPLAY_STATE_OFF); + processor->OnLogEvent(screenOffEvent.get()); + + auto screenOnEvent = + CreateScreenStateChangedEvent(configAddedTimeNs + 65, android::view::DISPLAY_STATE_ON); + processor->OnLogEvent(screenOnEvent.get()); + + screenOffEvent = + CreateScreenStateChangedEvent(configAddedTimeNs + 75, android::view::DISPLAY_STATE_OFF); + processor->OnLogEvent(screenOffEvent.get()); + + // Pulling alarm arrives on time and reset the sequential pulling alarm. + processor->informPullAlarmFired(expectedPullTimeNs + 1); + EXPECT_EQ(baseTimeNs + startBucketNum * bucketSizeNs + 2 * bucketSizeNs, expectedPullTimeNs); + + processor->informPullAlarmFired(expectedPullTimeNs + 1); + + screenOnEvent = CreateScreenStateChangedEvent(configAddedTimeNs + 2 * bucketSizeNs + 15, + android::view::DISPLAY_STATE_ON); + processor->OnLogEvent(screenOnEvent.get()); + + processor->informPullAlarmFired(expectedPullTimeNs + 1); + + processor->informPullAlarmFired(expectedPullTimeNs + 1); + + screenOffEvent = CreateScreenStateChangedEvent(configAddedTimeNs + 4 * bucketSizeNs + 11, + android::view::DISPLAY_STATE_OFF); + processor->OnLogEvent(screenOffEvent.get()); + + processor->informPullAlarmFired(expectedPullTimeNs + 1); + + processor->informPullAlarmFired(expectedPullTimeNs + 1); + + ConfigMetricsReportList reports; + vector buffer; + processor->onDumpReport(cfgKey, configAddedTimeNs + 7 * bucketSizeNs + 10, false, true, + ADB_DUMP, FAST, &buffer); + EXPECT_TRUE(buffer.size() > 0); + EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size())); + backfillDimensionPath(&reports); + backfillStringInReport(&reports); + backfillStartEndTimestamp(&reports); + ASSERT_EQ(1, reports.reports_size()); + ASSERT_EQ(1, reports.reports(0).metrics_size()); + StatsLogReport::ValueMetricDataWrapper valueMetrics; + sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).value_metrics(), &valueMetrics); + ASSERT_GT((int)valueMetrics.data_size(), 1); + + auto data = valueMetrics.data(0); + EXPECT_EQ(util::SUBSYSTEM_SLEEP_STATE, data.dimensions_in_what().field()); + ASSERT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size()); + EXPECT_EQ(1 /* subsystem name field */, + data.dimensions_in_what().value_tuple().dimensions_value(0).field()); + EXPECT_FALSE(data.dimensions_in_what().value_tuple().dimensions_value(0).value_str().empty()); + // We have 4 buckets, the first one was incomplete since the condition was unknown. + ASSERT_EQ(4, data.bucket_info_size()); + + EXPECT_EQ(baseTimeNs + 3 * bucketSizeNs, data.bucket_info(0).start_bucket_elapsed_nanos()); + EXPECT_EQ(baseTimeNs + 4 * bucketSizeNs, data.bucket_info(0).end_bucket_elapsed_nanos()); + ASSERT_EQ(1, data.bucket_info(0).values_size()); + + EXPECT_EQ(baseTimeNs + 4 * bucketSizeNs, data.bucket_info(1).start_bucket_elapsed_nanos()); + EXPECT_EQ(baseTimeNs + 5 * bucketSizeNs, data.bucket_info(1).end_bucket_elapsed_nanos()); + ASSERT_EQ(1, data.bucket_info(1).values_size()); + + EXPECT_EQ(baseTimeNs + 6 * bucketSizeNs, data.bucket_info(2).start_bucket_elapsed_nanos()); + EXPECT_EQ(baseTimeNs + 7 * bucketSizeNs, data.bucket_info(2).end_bucket_elapsed_nanos()); + ASSERT_EQ(1, data.bucket_info(2).values_size()); + + EXPECT_EQ(baseTimeNs + 7 * bucketSizeNs, data.bucket_info(3).start_bucket_elapsed_nanos()); + EXPECT_EQ(baseTimeNs + 8 * bucketSizeNs, data.bucket_info(3).end_bucket_elapsed_nanos()); + ASSERT_EQ(1, data.bucket_info(3).values_size()); +} + +TEST(ValueMetricE2eTest, TestPulledEvents_LateAlarm) { + auto config = CreateStatsdConfig(); + int64_t baseTimeNs = getElapsedRealtimeNs(); + // 10 mins == 2 bucket durations. + int64_t configAddedTimeNs = 10 * 60 * NS_PER_SEC + baseTimeNs; + int64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(config.value_metric(0).bucket()) * 1000000; + + ConfigKey cfgKey; + auto processor = CreateStatsLogProcessor(baseTimeNs, configAddedTimeNs, config, cfgKey, + SharedRefBase::make(), + util::SUBSYSTEM_SLEEP_STATE); + ASSERT_EQ(processor->mMetricsManagers.size(), 1u); + EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid()); + processor->mPullerManager->ForceClearPullerCache(); + + int startBucketNum = processor->mMetricsManagers.begin() + ->second->mAllMetricProducers[0] + ->getCurrentBucketNum(); + EXPECT_GT(startBucketNum, (int64_t)0); + + // When creating the config, the value metric producer should register the alarm at the + // end of the current bucket. + ASSERT_EQ((size_t)1, processor->mPullerManager->mReceivers.size()); + EXPECT_EQ(bucketSizeNs, + processor->mPullerManager->mReceivers.begin()->second.front().intervalNs); + int64_t& expectedPullTimeNs = + processor->mPullerManager->mReceivers.begin()->second.front().nextPullTimeNs; + EXPECT_EQ(baseTimeNs + startBucketNum * bucketSizeNs + bucketSizeNs, expectedPullTimeNs); + + // Screen off/on/off events. + auto screenOffEvent = + CreateScreenStateChangedEvent(configAddedTimeNs + 55, android::view::DISPLAY_STATE_OFF); + processor->OnLogEvent(screenOffEvent.get()); + + auto screenOnEvent = + CreateScreenStateChangedEvent(configAddedTimeNs + 65, android::view::DISPLAY_STATE_ON); + processor->OnLogEvent(screenOnEvent.get()); + + screenOffEvent = + CreateScreenStateChangedEvent(configAddedTimeNs + 75, android::view::DISPLAY_STATE_OFF); + processor->OnLogEvent(screenOffEvent.get()); + + // Pulling alarm arrives late by 2 buckets and 1 ns. 2 buckets late is too far away in the + // future, data will be skipped. + processor->informPullAlarmFired(expectedPullTimeNs + 2 * bucketSizeNs + 1); + EXPECT_EQ(baseTimeNs + startBucketNum * bucketSizeNs + 4 * bucketSizeNs, expectedPullTimeNs); + + // This screen state change will start a new bucket. + screenOnEvent = CreateScreenStateChangedEvent(configAddedTimeNs + 4 * bucketSizeNs + 65, + android::view::DISPLAY_STATE_ON); + processor->OnLogEvent(screenOnEvent.get()); + + // The alarm is delayed but we already created a bucket thanks to the screen state condition. + // This bucket does not have to be skipped since the alarm arrives in time for the next bucket. + processor->informPullAlarmFired(expectedPullTimeNs + bucketSizeNs + 21); + EXPECT_EQ(baseTimeNs + startBucketNum * bucketSizeNs + 6 * bucketSizeNs, expectedPullTimeNs); + + screenOffEvent = CreateScreenStateChangedEvent(configAddedTimeNs + 6 * bucketSizeNs + 31, + android::view::DISPLAY_STATE_OFF); + processor->OnLogEvent(screenOffEvent.get()); + + processor->informPullAlarmFired(expectedPullTimeNs + bucketSizeNs + 21); + EXPECT_EQ(baseTimeNs + startBucketNum * bucketSizeNs + 8 * bucketSizeNs, expectedPullTimeNs); + + processor->informPullAlarmFired(expectedPullTimeNs + 1); + EXPECT_EQ(baseTimeNs + startBucketNum * bucketSizeNs + 9 * bucketSizeNs, expectedPullTimeNs); + + ConfigMetricsReportList reports; + vector buffer; + processor->onDumpReport(cfgKey, configAddedTimeNs + 9 * bucketSizeNs + 10, false, true, + ADB_DUMP, FAST, &buffer); + EXPECT_TRUE(buffer.size() > 0); + EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size())); + backfillDimensionPath(&reports); + backfillStringInReport(&reports); + backfillStartEndTimestamp(&reports); + ASSERT_EQ(1, reports.reports_size()); + ASSERT_EQ(1, reports.reports(0).metrics_size()); + StatsLogReport::ValueMetricDataWrapper valueMetrics; + sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).value_metrics(), &valueMetrics); + ASSERT_GT((int)valueMetrics.data_size(), 1); + + auto data = valueMetrics.data(0); + EXPECT_EQ(util::SUBSYSTEM_SLEEP_STATE, data.dimensions_in_what().field()); + ASSERT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size()); + EXPECT_EQ(1 /* subsystem name field */, + data.dimensions_in_what().value_tuple().dimensions_value(0).field()); + EXPECT_FALSE(data.dimensions_in_what().value_tuple().dimensions_value(0).value_str().empty()); + ASSERT_EQ(3, data.bucket_info_size()); + + EXPECT_EQ(baseTimeNs + 5 * bucketSizeNs, data.bucket_info(0).start_bucket_elapsed_nanos()); + EXPECT_EQ(baseTimeNs + 6 * bucketSizeNs, data.bucket_info(0).end_bucket_elapsed_nanos()); + ASSERT_EQ(1, data.bucket_info(0).values_size()); + + EXPECT_EQ(baseTimeNs + 8 * bucketSizeNs, data.bucket_info(1).start_bucket_elapsed_nanos()); + EXPECT_EQ(baseTimeNs + 9 * bucketSizeNs, data.bucket_info(1).end_bucket_elapsed_nanos()); + ASSERT_EQ(1, data.bucket_info(1).values_size()); + + EXPECT_EQ(baseTimeNs + 9 * bucketSizeNs, data.bucket_info(2).start_bucket_elapsed_nanos()); + EXPECT_EQ(baseTimeNs + 10 * bucketSizeNs, data.bucket_info(2).end_bucket_elapsed_nanos()); + ASSERT_EQ(1, data.bucket_info(2).values_size()); +} + +TEST(ValueMetricE2eTest, TestPulledEvents_WithActivation) { + auto config = CreateStatsdConfig(false); + int64_t baseTimeNs = getElapsedRealtimeNs(); + int64_t configAddedTimeNs = 10 * 60 * NS_PER_SEC + baseTimeNs; + int64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(config.value_metric(0).bucket()) * 1000000; + + auto batterySaverStartMatcher = CreateBatterySaverModeStartAtomMatcher(); + *config.add_atom_matcher() = batterySaverStartMatcher; + const int64_t ttlNs = 2 * bucketSizeNs; // Two buckets. + auto metric_activation = config.add_metric_activation(); + metric_activation->set_metric_id(metricId); + metric_activation->set_activation_type(ACTIVATE_IMMEDIATELY); + auto event_activation = metric_activation->add_event_activation(); + event_activation->set_atom_matcher_id(batterySaverStartMatcher.id()); + event_activation->set_ttl_seconds(ttlNs / 1000000000); + + ConfigKey cfgKey; + auto processor = CreateStatsLogProcessor(baseTimeNs, configAddedTimeNs, config, cfgKey, + SharedRefBase::make(), + util::SUBSYSTEM_SLEEP_STATE); + ASSERT_EQ(processor->mMetricsManagers.size(), 1u); + EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid()); + processor->mPullerManager->ForceClearPullerCache(); + + int startBucketNum = processor->mMetricsManagers.begin() + ->second->mAllMetricProducers[0] + ->getCurrentBucketNum(); + EXPECT_GT(startBucketNum, (int64_t)0); + EXPECT_FALSE(processor->mMetricsManagers.begin()->second->mAllMetricProducers[0]->isActive()); + + // When creating the config, the value metric producer should register the alarm at the + // end of the current bucket. + ASSERT_EQ((size_t)1, processor->mPullerManager->mReceivers.size()); + EXPECT_EQ(bucketSizeNs, + processor->mPullerManager->mReceivers.begin()->second.front().intervalNs); + int64_t& expectedPullTimeNs = + processor->mPullerManager->mReceivers.begin()->second.front().nextPullTimeNs; + EXPECT_EQ(baseTimeNs + startBucketNum * bucketSizeNs + bucketSizeNs, expectedPullTimeNs); + + // Pulling alarm arrives on time and reset the sequential pulling alarm. + processor->informPullAlarmFired(expectedPullTimeNs + 1); // 15 mins + 1 ns. + EXPECT_EQ(baseTimeNs + startBucketNum * bucketSizeNs + 2 * bucketSizeNs, expectedPullTimeNs); + EXPECT_FALSE(processor->mMetricsManagers.begin()->second->mAllMetricProducers[0]->isActive()); + + // Activate the metric. A pull occurs here + const int64_t activationNs = configAddedTimeNs + bucketSizeNs + (2 * 1000 * 1000); // 2 millis. + auto batterySaverOnEvent = CreateBatterySaverOnEvent(activationNs); + processor->OnLogEvent(batterySaverOnEvent.get()); // 15 mins + 2 ms. + EXPECT_TRUE(processor->mMetricsManagers.begin()->second->mAllMetricProducers[0]->isActive()); + + processor->informPullAlarmFired(expectedPullTimeNs + 1); // 20 mins + 1 ns. + EXPECT_EQ(baseTimeNs + startBucketNum * bucketSizeNs + 3 * bucketSizeNs, expectedPullTimeNs); + + processor->informPullAlarmFired(expectedPullTimeNs + 2); // 25 mins + 2 ns. + EXPECT_EQ(baseTimeNs + startBucketNum * bucketSizeNs + 4 * bucketSizeNs, expectedPullTimeNs); + + // Create random event to deactivate metric. + auto deactivationEvent = CreateScreenBrightnessChangedEvent(activationNs + ttlNs + 1, 50); + processor->OnLogEvent(deactivationEvent.get()); + EXPECT_FALSE(processor->mMetricsManagers.begin()->second->mAllMetricProducers[0]->isActive()); + + processor->informPullAlarmFired(expectedPullTimeNs + 3); + EXPECT_EQ(baseTimeNs + startBucketNum * bucketSizeNs + 5 * bucketSizeNs, expectedPullTimeNs); + + processor->informPullAlarmFired(expectedPullTimeNs + 4); + EXPECT_EQ(baseTimeNs + startBucketNum * bucketSizeNs + 6 * bucketSizeNs, expectedPullTimeNs); + + ConfigMetricsReportList reports; + vector buffer; + processor->onDumpReport(cfgKey, configAddedTimeNs + 7 * bucketSizeNs + 10, false, true, + ADB_DUMP, FAST, &buffer); + EXPECT_TRUE(buffer.size() > 0); + EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size())); + backfillDimensionPath(&reports); + backfillStringInReport(&reports); + backfillStartEndTimestamp(&reports); + ASSERT_EQ(1, reports.reports_size()); + ASSERT_EQ(1, reports.reports(0).metrics_size()); + StatsLogReport::ValueMetricDataWrapper valueMetrics; + sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).value_metrics(), &valueMetrics); + ASSERT_GT((int)valueMetrics.data_size(), 0); + + auto data = valueMetrics.data(0); + EXPECT_EQ(util::SUBSYSTEM_SLEEP_STATE, data.dimensions_in_what().field()); + ASSERT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size()); + EXPECT_EQ(1 /* subsystem name field */, + data.dimensions_in_what().value_tuple().dimensions_value(0).field()); + EXPECT_FALSE(data.dimensions_in_what().value_tuple().dimensions_value(0).value_str().empty()); + // We have 2 full buckets, the two surrounding the activation are dropped. + ASSERT_EQ(2, data.bucket_info_size()); + + auto bucketInfo = data.bucket_info(0); + EXPECT_EQ(baseTimeNs + 3 * bucketSizeNs, bucketInfo.start_bucket_elapsed_nanos()); + EXPECT_EQ(baseTimeNs + 4 * bucketSizeNs, bucketInfo.end_bucket_elapsed_nanos()); + ASSERT_EQ(1, bucketInfo.values_size()); + + bucketInfo = data.bucket_info(1); + EXPECT_EQ(baseTimeNs + 4 * bucketSizeNs, bucketInfo.start_bucket_elapsed_nanos()); + EXPECT_EQ(baseTimeNs + 5 * bucketSizeNs, bucketInfo.end_bucket_elapsed_nanos()); + ASSERT_EQ(1, bucketInfo.values_size()); +} + +/** + * Test initialization of a simple value metric that is sliced by a state. + * + * ValueCpuUserTimePerScreenState + */ +TEST(ValueMetricE2eTest, TestInitWithSlicedState) { + // Create config. + StatsdConfig config; + config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root. + + auto pulledAtomMatcher = + CreateSimpleAtomMatcher("TestMatcher", util::SUBSYSTEM_SLEEP_STATE); + *config.add_atom_matcher() = pulledAtomMatcher; + + auto screenState = CreateScreenState(); + *config.add_state() = screenState; + + // Create value metric that slices by screen state without a map. + int64_t metricId = 123456; + auto valueMetric = config.add_value_metric(); + valueMetric->set_id(metricId); + valueMetric->set_bucket(TimeUnit::FIVE_MINUTES); + valueMetric->set_what(pulledAtomMatcher.id()); + *valueMetric->mutable_value_field() = + CreateDimensions(util::CPU_TIME_PER_UID, {2 /* user_time_micros */}); + valueMetric->add_slice_by_state(screenState.id()); + valueMetric->set_max_pull_delay_sec(INT_MAX); + + // Initialize StatsLogProcessor. + const uint64_t bucketStartTimeNs = 10000000000; // 0:10 + const uint64_t bucketSizeNs = + TimeUnitToBucketSizeInMillis(config.value_metric(0).bucket()) * 1000000LL; + int uid = 12345; + int64_t cfgId = 98765; + ConfigKey cfgKey(uid, cfgId); + + auto processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey); + + // Check that StateTrackers were initialized correctly. + EXPECT_EQ(1, StateManager::getInstance().getStateTrackersCount()); + EXPECT_EQ(1, StateManager::getInstance().getListenersCount(SCREEN_STATE_ATOM_ID)); + + // Check that ValueMetricProducer was initialized correctly. + ASSERT_EQ(1U, processor->mMetricsManagers.size()); + sp metricsManager = processor->mMetricsManagers.begin()->second; + EXPECT_TRUE(metricsManager->isConfigValid()); + ASSERT_EQ(1, metricsManager->mAllMetricProducers.size()); + sp metricProducer = metricsManager->mAllMetricProducers[0]; + ASSERT_EQ(1, metricProducer->mSlicedStateAtoms.size()); + EXPECT_EQ(SCREEN_STATE_ATOM_ID, metricProducer->mSlicedStateAtoms.at(0)); + ASSERT_EQ(0, metricProducer->mStateGroupMap.size()); +} + +/** + * Test initialization of a value metric that is sliced by state and has + * dimensions_in_what. + * + * ValueCpuUserTimePerUidPerUidProcessState + */ +TEST(ValueMetricE2eTest, TestInitWithSlicedState_WithDimensions) { + // Create config. + StatsdConfig config; + config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root. + + auto cpuTimePerUidMatcher = + CreateSimpleAtomMatcher("CpuTimePerUidMatcher", util::CPU_TIME_PER_UID); + *config.add_atom_matcher() = cpuTimePerUidMatcher; + + auto uidProcessState = CreateUidProcessState(); + *config.add_state() = uidProcessState; + + // Create value metric that slices by screen state with a complete map. + int64_t metricId = 123456; + auto valueMetric = config.add_value_metric(); + valueMetric->set_id(metricId); + valueMetric->set_bucket(TimeUnit::FIVE_MINUTES); + valueMetric->set_what(cpuTimePerUidMatcher.id()); + *valueMetric->mutable_value_field() = + CreateDimensions(util::CPU_TIME_PER_UID, {2 /* user_time_micros */}); + *valueMetric->mutable_dimensions_in_what() = + CreateDimensions(util::CPU_TIME_PER_UID, {1 /* uid */}); + valueMetric->add_slice_by_state(uidProcessState.id()); + MetricStateLink* stateLink = valueMetric->add_state_link(); + stateLink->set_state_atom_id(UID_PROCESS_STATE_ATOM_ID); + auto fieldsInWhat = stateLink->mutable_fields_in_what(); + *fieldsInWhat = CreateDimensions(util::CPU_TIME_PER_UID, {1 /* uid */}); + auto fieldsInState = stateLink->mutable_fields_in_state(); + *fieldsInState = CreateDimensions(UID_PROCESS_STATE_ATOM_ID, {1 /* uid */}); + valueMetric->set_max_pull_delay_sec(INT_MAX); + + // Initialize StatsLogProcessor. + const uint64_t bucketStartTimeNs = 10000000000; // 0:10 + int uid = 12345; + int64_t cfgId = 98765; + ConfigKey cfgKey(uid, cfgId); + + auto processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey); + + // Check that StateTrackers were initialized correctly. + EXPECT_EQ(1, StateManager::getInstance().getStateTrackersCount()); + EXPECT_EQ(1, StateManager::getInstance().getListenersCount(UID_PROCESS_STATE_ATOM_ID)); + + // Check that ValueMetricProducer was initialized correctly. + ASSERT_EQ(1U, processor->mMetricsManagers.size()); + sp metricsManager = processor->mMetricsManagers.begin()->second; + EXPECT_TRUE(metricsManager->isConfigValid()); + ASSERT_EQ(1, metricsManager->mAllMetricProducers.size()); + sp metricProducer = metricsManager->mAllMetricProducers[0]; + ASSERT_EQ(1, metricProducer->mSlicedStateAtoms.size()); + EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, metricProducer->mSlicedStateAtoms.at(0)); + ASSERT_EQ(0, metricProducer->mStateGroupMap.size()); +} + +/** + * Test initialization of a value metric that is sliced by state and has + * dimensions_in_what. + * + * ValueCpuUserTimePerUidPerUidProcessState + */ +TEST(ValueMetricE2eTest, TestInitWithSlicedState_WithIncorrectDimensions) { + // Create config. + StatsdConfig config; + config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root. + + auto cpuTimePerUidMatcher = + CreateSimpleAtomMatcher("CpuTimePerUidMatcher", util::CPU_TIME_PER_UID); + *config.add_atom_matcher() = cpuTimePerUidMatcher; + + auto uidProcessState = CreateUidProcessState(); + *config.add_state() = uidProcessState; + + // Create value metric that slices by screen state with a complete map. + int64_t metricId = 123456; + auto valueMetric = config.add_value_metric(); + valueMetric->set_id(metricId); + valueMetric->set_bucket(TimeUnit::FIVE_MINUTES); + valueMetric->set_what(cpuTimePerUidMatcher.id()); + *valueMetric->mutable_value_field() = + CreateDimensions(util::CPU_TIME_PER_UID, {2 /* user_time_micros */}); + valueMetric->add_slice_by_state(uidProcessState.id()); + MetricStateLink* stateLink = valueMetric->add_state_link(); + stateLink->set_state_atom_id(UID_PROCESS_STATE_ATOM_ID); + auto fieldsInWhat = stateLink->mutable_fields_in_what(); + *fieldsInWhat = CreateDimensions(util::CPU_TIME_PER_UID, {1 /* uid */}); + auto fieldsInState = stateLink->mutable_fields_in_state(); + *fieldsInState = CreateDimensions(UID_PROCESS_STATE_ATOM_ID, {1 /* uid */}); + valueMetric->set_max_pull_delay_sec(INT_MAX); + + // Initialize StatsLogProcessor. + const uint64_t bucketStartTimeNs = 10000000000; // 0:10 + int uid = 12345; + int64_t cfgId = 98765; + ConfigKey cfgKey(uid, cfgId); + auto processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey); + + // No StateTrackers are initialized. + EXPECT_EQ(0, StateManager::getInstance().getStateTrackersCount()); + + // Config initialization fails. + ASSERT_EQ(0, processor->mMetricsManagers.size()); +} + +#else +GTEST_LOG_(INFO) << "This test does nothing.\n"; +#endif + +} // namespace statsd +} // namespace os +} // namespace android diff --git a/statsd/tests/e2e/WakelockDuration_e2e_test.cpp b/statsd/tests/e2e/WakelockDuration_e2e_test.cpp new file mode 100644 index 00000000..52bc222e --- /dev/null +++ b/statsd/tests/e2e/WakelockDuration_e2e_test.cpp @@ -0,0 +1,355 @@ +// 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 CreateStatsdConfig(DurationMetric::AggregationType aggregationType) { + StatsdConfig config; + config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root. + *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. + FieldMatcher dimensions = CreateAttributionUidAndTagDimensions( + util::WAKELOCK_STATE_CHANGED, {Position::FIRST, Position::LAST}); + // Also slice by the wakelock tag + dimensions.add_child()->set_field(3); // The wakelock tag is set in field 3 of the wakelock. + *holdingWakelockPredicate.mutable_simple_predicate()->mutable_dimensions() = dimensions; + *config.add_predicate() = holdingWakelockPredicate; + + auto durationMetric = config.add_duration_metric(); + durationMetric->set_id(StringToId("WakelockDuration")); + durationMetric->set_what(holdingWakelockPredicate.id()); + durationMetric->set_condition(screenIsOffPredicate.id()); + durationMetric->set_aggregation_type(aggregationType); + // The metric is dimensioning by first attribution node and only by uid. + *durationMetric->mutable_dimensions_in_what() = + CreateAttributionUidDimensions( + util::WAKELOCK_STATE_CHANGED, {Position::FIRST}); + durationMetric->set_bucket(FIVE_MINUTES); + return config; +} + +std::vector attributionUids1 = {111, 222, 222}; +std::vector attributionTags1 = {"App1", "GMSCoreModule1", "GMSCoreModule2"}; + +std::vector attributionUids2 = {111, 222, 222}; +std::vector attributionTags2 = {"App2", "GMSCoreModule1", "GMSCoreModule2"}; + +/* +Events: +Screen off is met from (200ns,1 min+500ns]. +Acquire event for wl1 from 2ns to 1min+2ns +Acquire event for wl2 from 1min-10ns to 2min-15ns +*/ +void FeedEvents(StatsdConfig config, sp processor) { + uint64_t bucketStartTimeNs = 10000000000; + uint64_t bucketSizeNs = + TimeUnitToBucketSizeInMillis(config.duration_metric(0).bucket()) * 1000000LL; + + auto screenTurnedOnEvent = CreateScreenStateChangedEvent( + bucketStartTimeNs + 1, android::view::DisplayStateEnum::DISPLAY_STATE_ON); + auto screenTurnedOffEvent = CreateScreenStateChangedEvent( + bucketStartTimeNs + 200, android::view::DisplayStateEnum::DISPLAY_STATE_OFF); + auto screenTurnedOnEvent2 = + CreateScreenStateChangedEvent(bucketStartTimeNs + bucketSizeNs + 500, + android::view::DisplayStateEnum::DISPLAY_STATE_ON); + + auto acquireEvent1 = CreateAcquireWakelockEvent(bucketStartTimeNs + 2, attributionUids1, + attributionTags1, "wl1"); + auto releaseEvent1 = CreateReleaseWakelockEvent(bucketStartTimeNs + bucketSizeNs + 2, + attributionUids1, attributionTags1, "wl1"); + auto acquireEvent2 = CreateAcquireWakelockEvent(bucketStartTimeNs + bucketSizeNs - 10, + attributionUids2, attributionTags2, "wl2"); + auto releaseEvent2 = CreateReleaseWakelockEvent(bucketStartTimeNs + 2 * bucketSizeNs - 15, + attributionUids2, attributionTags2, "wl2"); + + std::vector> events; + + events.push_back(std::move(screenTurnedOnEvent)); + events.push_back(std::move(screenTurnedOffEvent)); + events.push_back(std::move(screenTurnedOnEvent2)); + events.push_back(std::move(acquireEvent1)); + events.push_back(std::move(acquireEvent2)); + events.push_back(std::move(releaseEvent1)); + events.push_back(std::move(releaseEvent2)); + + sortLogEventsByTimestamp(&events); + + for (const auto& event : events) { + processor->OnLogEvent(event.get()); + } +} + +} // namespace + +TEST(WakelockDurationE2eTest, TestAggregatedPredicateDimensionsForSumDuration1) { + ConfigKey cfgKey; + auto config = CreateStatsdConfig(DurationMetric::SUM); + uint64_t bucketStartTimeNs = 10000000000; + uint64_t bucketSizeNs = + TimeUnitToBucketSizeInMillis(config.duration_metric(0).bucket()) * 1000000LL; + auto processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey); + ASSERT_EQ(processor->mMetricsManagers.size(), 1u); + EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid()); + FeedEvents(config, processor); + vector buffer; + ConfigMetricsReportList reports; + processor->onDumpReport(cfgKey, bucketStartTimeNs + 2 * bucketSizeNs - 1, false, true, ADB_DUMP, + FAST, &buffer); + EXPECT_TRUE(buffer.size() > 0); + EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size())); + backfillDimensionPath(&reports); + backfillStringInReport(&reports); + backfillStartEndTimestamp(&reports); + + ASSERT_EQ(reports.reports_size(), 1); + ASSERT_EQ(reports.reports(0).metrics_size(), 1); + // Only 1 dimension output. The tag dimension in the predicate has been aggregated. + ASSERT_EQ(reports.reports(0).metrics(0).duration_metrics().data_size(), 1); + + auto data = reports.reports(0).metrics(0).duration_metrics().data(0); + // Validate dimension value. + ValidateAttributionUidDimension(data.dimensions_in_what(), + util::WAKELOCK_STATE_CHANGED, 111); + // Validate bucket info. + ASSERT_EQ(reports.reports(0).metrics(0).duration_metrics().data(0).bucket_info_size(), 1); + data = reports.reports(0).metrics(0).duration_metrics().data(0); + // The wakelock holding interval starts from the screen off event and to the end of the 1st + // bucket. + EXPECT_EQ((unsigned long long)data.bucket_info(0).duration_nanos(), bucketSizeNs - 200); +} + +TEST(WakelockDurationE2eTest, TestAggregatedPredicateDimensionsForSumDuration2) { + ConfigKey cfgKey; + auto config = CreateStatsdConfig(DurationMetric::SUM); + uint64_t bucketStartTimeNs = 10000000000; + uint64_t bucketSizeNs = + TimeUnitToBucketSizeInMillis(config.duration_metric(0).bucket()) * 1000000LL; + auto processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey); + ASSERT_EQ(processor->mMetricsManagers.size(), 1u); + EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid()); + FeedEvents(config, processor); + vector buffer; + ConfigMetricsReportList reports; + processor->onDumpReport(cfgKey, bucketStartTimeNs + 2 * bucketSizeNs + 1, false, true, ADB_DUMP, + FAST, &buffer); + EXPECT_TRUE(buffer.size() > 0); + EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size())); + backfillDimensionPath(&reports); + backfillStringInReport(&reports); + backfillStartEndTimestamp(&reports); + ASSERT_EQ(reports.reports_size(), 1); + ASSERT_EQ(reports.reports(0).metrics_size(), 1); + ASSERT_EQ(reports.reports(0).metrics(0).duration_metrics().data_size(), 1); + // Dump the report after the end of 2nd bucket. + ASSERT_EQ(reports.reports(0).metrics(0).duration_metrics().data(0).bucket_info_size(), 2); + auto data = reports.reports(0).metrics(0).duration_metrics().data(0); + // Validate dimension value. + ValidateAttributionUidDimension(data.dimensions_in_what(), + util::WAKELOCK_STATE_CHANGED, 111); + // Two output buckets. + // The wakelock holding interval in the 1st bucket starts from the screen off event and to + // the end of the 1st bucket. + EXPECT_EQ((unsigned long long)data.bucket_info(0).duration_nanos(), + bucketStartTimeNs + bucketSizeNs - (bucketStartTimeNs + 200)); + // The wakelock holding interval in the 2nd bucket starts at the beginning of the bucket and + // ends at the second screen on event. + EXPECT_EQ((unsigned long long)data.bucket_info(1).duration_nanos(), 500UL); +} + +TEST(WakelockDurationE2eTest, TestAggregatedPredicateDimensionsForSumDuration3) { + ConfigKey cfgKey; + auto config = CreateStatsdConfig(DurationMetric::SUM); + uint64_t bucketStartTimeNs = 10000000000; + uint64_t bucketSizeNs = + TimeUnitToBucketSizeInMillis(config.duration_metric(0).bucket()) * 1000000LL; + auto processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey); + ASSERT_EQ(processor->mMetricsManagers.size(), 1u); + EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid()); + FeedEvents(config, processor); + vector buffer; + ConfigMetricsReportList reports; + + std::vector> events; + events.push_back( + CreateScreenStateChangedEvent(bucketStartTimeNs + 2 * bucketSizeNs + 90, + android::view::DisplayStateEnum::DISPLAY_STATE_OFF)); + events.push_back(CreateAcquireWakelockEvent(bucketStartTimeNs + 2 * bucketSizeNs + 100, + attributionUids1, attributionTags1, "wl3")); + events.push_back(CreateReleaseWakelockEvent(bucketStartTimeNs + 5 * bucketSizeNs + 100, + attributionUids1, attributionTags1, "wl3")); + sortLogEventsByTimestamp(&events); + for (const auto& event : events) { + processor->OnLogEvent(event.get()); + } + + processor->onDumpReport(cfgKey, bucketStartTimeNs + 6 * bucketSizeNs + 1, false, true, ADB_DUMP, + FAST, &buffer); + EXPECT_TRUE(buffer.size() > 0); + EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size())); + backfillDimensionPath(&reports); + backfillStringInReport(&reports); + backfillStartEndTimestamp(&reports); + ASSERT_EQ(reports.reports_size(), 1); + ASSERT_EQ(reports.reports(0).metrics_size(), 1); + ASSERT_EQ(reports.reports(0).metrics(0).duration_metrics().data_size(), 1); + ASSERT_EQ(reports.reports(0).metrics(0).duration_metrics().data(0).bucket_info_size(), 6); + auto data = reports.reports(0).metrics(0).duration_metrics().data(0); + ValidateAttributionUidDimension(data.dimensions_in_what(), + util::WAKELOCK_STATE_CHANGED, 111); + // The last wakelock holding spans 4 buckets. + EXPECT_EQ((unsigned long long)data.bucket_info(2).duration_nanos(), bucketSizeNs - 100); + EXPECT_EQ((unsigned long long)data.bucket_info(3).duration_nanos(), bucketSizeNs); + EXPECT_EQ((unsigned long long)data.bucket_info(4).duration_nanos(), bucketSizeNs); + EXPECT_EQ((unsigned long long)data.bucket_info(5).duration_nanos(), 100UL); +} + +TEST(WakelockDurationE2eTest, TestAggregatedPredicateDimensionsForMaxDuration1) { + ConfigKey cfgKey; + auto config = CreateStatsdConfig(DurationMetric::MAX_SPARSE); + uint64_t bucketStartTimeNs = 10000000000; + uint64_t bucketSizeNs = + TimeUnitToBucketSizeInMillis(config.duration_metric(0).bucket()) * 1000000LL; + auto processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey); + ASSERT_EQ(processor->mMetricsManagers.size(), 1u); + EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid()); + FeedEvents(config, processor); + ConfigMetricsReportList reports; + vector buffer; + processor->onDumpReport(cfgKey, bucketStartTimeNs + 2 * bucketSizeNs - 1, false, true, ADB_DUMP, + FAST, &buffer); + EXPECT_TRUE(buffer.size() > 0); + + EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size())); + backfillDimensionPath(&reports); + backfillStringInReport(&reports); + backfillStartEndTimestamp(&reports); + + ASSERT_EQ(reports.reports_size(), 1); + + // When using ProtoOutputStream, if nothing written to a sub msg, it won't be treated as + // one. It was previsouly 1 because we had a fake onDumpReport which calls add_metric() by + // itself. + ASSERT_EQ(1, reports.reports(0).metrics_size()); + ASSERT_EQ(0, reports.reports(0).metrics(0).duration_metrics().data_size()); +} + +TEST(WakelockDurationE2eTest, TestAggregatedPredicateDimensionsForMaxDuration2) { + ConfigKey cfgKey; + auto config = CreateStatsdConfig(DurationMetric::MAX_SPARSE); + uint64_t bucketStartTimeNs = 10000000000; + uint64_t bucketSizeNs = + TimeUnitToBucketSizeInMillis(config.duration_metric(0).bucket()) * 1000000LL; + auto processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey); + ASSERT_EQ(processor->mMetricsManagers.size(), 1u); + EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid()); + FeedEvents(config, processor); + ConfigMetricsReportList reports; + vector buffer; + processor->onDumpReport(cfgKey, bucketStartTimeNs + 2 * bucketSizeNs + 1, false, true, ADB_DUMP, + FAST, &buffer); + EXPECT_TRUE(buffer.size() > 0); + EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size())); + backfillDimensionPath(&reports); + backfillStringInReport(&reports); + backfillStartEndTimestamp(&reports); + ASSERT_EQ(reports.reports_size(), 1); + ASSERT_EQ(reports.reports(0).metrics_size(), 1); + ASSERT_EQ(reports.reports(0).metrics(0).duration_metrics().data_size(), 1); + // Dump the report after the end of 2nd bucket. One dimension with one bucket. + ASSERT_EQ(reports.reports(0).metrics(0).duration_metrics().data(0).bucket_info_size(), 1); + auto data = reports.reports(0).metrics(0).duration_metrics().data(0); + // Validate dimension value. + ValidateAttributionUidDimension(data.dimensions_in_what(), + util::WAKELOCK_STATE_CHANGED, 111); + // The max is acquire event for wl1 to screen off start. + EXPECT_EQ((unsigned long long)data.bucket_info(0).duration_nanos(), bucketSizeNs + 2 - 200); +} + +TEST(WakelockDurationE2eTest, TestAggregatedPredicateDimensionsForMaxDuration3) { + ConfigKey cfgKey; + auto config = CreateStatsdConfig(DurationMetric::MAX_SPARSE); + uint64_t bucketStartTimeNs = 10000000000; + uint64_t bucketSizeNs = + TimeUnitToBucketSizeInMillis(config.duration_metric(0).bucket()) * 1000000LL; + auto processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey); + ASSERT_EQ(processor->mMetricsManagers.size(), 1u); + EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid()); + FeedEvents(config, processor); + ConfigMetricsReportList reports; + vector buffer; + + std::vector> events; + events.push_back( + CreateScreenStateChangedEvent(bucketStartTimeNs + 2 * bucketSizeNs + 90, + android::view::DisplayStateEnum::DISPLAY_STATE_OFF)); + events.push_back(CreateAcquireWakelockEvent(bucketStartTimeNs + 2 * bucketSizeNs + 100, + attributionUids1, attributionTags1, "wl3")); + events.push_back(CreateReleaseWakelockEvent(bucketStartTimeNs + 5 * bucketSizeNs + 100, + attributionUids1, attributionTags1, "wl3")); + sortLogEventsByTimestamp(&events); + for (const auto& event : events) { + processor->OnLogEvent(event.get()); + } + + processor->onDumpReport(cfgKey, bucketStartTimeNs + 6 * bucketSizeNs + 1, false, true, ADB_DUMP, + FAST, &buffer); + EXPECT_TRUE(buffer.size() > 0); + EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size())); + backfillDimensionPath(&reports); + backfillStringInReport(&reports); + backfillStartEndTimestamp(&reports); + ASSERT_EQ(reports.reports_size(), 1); + ASSERT_EQ(reports.reports(0).metrics_size(), 1); + ASSERT_EQ(reports.reports(0).metrics(0).duration_metrics().data_size(), 1); + ASSERT_EQ(reports.reports(0).metrics(0).duration_metrics().data(0).bucket_info_size(), 2); + auto data = reports.reports(0).metrics(0).duration_metrics().data(0); + ValidateAttributionUidDimension(data.dimensions_in_what(), + util::WAKELOCK_STATE_CHANGED, 111); + // The last wakelock holding spans 4 buckets. + EXPECT_EQ((unsigned long long)data.bucket_info(1).duration_nanos(), 3 * bucketSizeNs); + EXPECT_EQ((unsigned long long)data.bucket_info(1).start_bucket_elapsed_nanos(), + bucketStartTimeNs + 5 * bucketSizeNs); + EXPECT_EQ((unsigned long long)data.bucket_info(1).end_bucket_elapsed_nanos(), + bucketStartTimeNs + 6 * bucketSizeNs); +} + +#else +GTEST_LOG_(INFO) << "This test does nothing.\n"; +#endif + +} // namespace statsd +} // namespace os +} // namespace android diff --git a/statsd/tests/external/StatsCallbackPuller_test.cpp b/statsd/tests/external/StatsCallbackPuller_test.cpp new file mode 100644 index 00000000..85a60886 --- /dev/null +++ b/statsd/tests/external/StatsCallbackPuller_test.cpp @@ -0,0 +1,219 @@ +// Copyright (C) 2019 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 "src/external/StatsCallbackPuller.h" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "../metrics/metrics_test_helper.h" +#include "src/stats_log_util.h" +#include "stats_event.h" +#include "tests/statsd_test_util.h" + +#ifdef __ANDROID__ + +namespace android { +namespace os { +namespace statsd { + +using namespace testing; +using Status = ::ndk::ScopedAStatus; +using aidl::android::os::BnPullAtomCallback; +using aidl::android::os::IPullAtomResultReceiver; +using aidl::android::util::StatsEventParcel; +using ::ndk::SharedRefBase; +using std::make_shared; +using std::shared_ptr; +using std::vector; +using std::this_thread::sleep_for; +using testing::Contains; + +namespace { +int pullTagId = -12; +bool pullSuccess; +vector values; +int64_t pullDelayNs; +int64_t pullTimeoutNs; +int64_t pullCoolDownNs; +std::thread pullThread; + +AStatsEvent* createSimpleEvent(int64_t value) { + AStatsEvent* event = AStatsEvent_obtain(); + AStatsEvent_setAtomId(event, pullTagId); + AStatsEvent_writeInt64(event, value); + AStatsEvent_build(event); + return event; +} + +void executePull(const shared_ptr& resultReceiver) { + // Convert stats_events into StatsEventParcels. + vector parcels; + for (int i = 0; i < values.size(); i++) { + AStatsEvent* event = createSimpleEvent(values[i]); + size_t size; + uint8_t* buffer = AStatsEvent_getBuffer(event, &size); + + StatsEventParcel p; + // vector.assign() creates a copy, but this is inevitable unless + // stats_event.h/c uses a vector as opposed to a buffer. + p.buffer.assign(buffer, buffer + size); + parcels.push_back(std::move(p)); + AStatsEvent_release(event); + } + + sleep_for(std::chrono::nanoseconds(pullDelayNs)); + resultReceiver->pullFinished(pullTagId, pullSuccess, parcels); +} + +class FakePullAtomCallback : public BnPullAtomCallback { +public: + Status onPullAtom(int atomTag, + const shared_ptr& resultReceiver) override { + // Force pull to happen in separate thread to simulate binder. + pullThread = std::thread(executePull, resultReceiver); + return Status::ok(); + } +}; + +class StatsCallbackPullerTest : public ::testing::Test { +public: + StatsCallbackPullerTest() { + } + + void SetUp() override { + pullSuccess = false; + pullDelayNs = 0; + values.clear(); + pullTimeoutNs = 10000000000LL; // 10 seconds. + pullCoolDownNs = 1000000000; // 1 second. + } + + void TearDown() override { + if (pullThread.joinable()) { + pullThread.join(); + } + values.clear(); + } +}; +} // Anonymous namespace. + +TEST_F(StatsCallbackPullerTest, PullSuccess) { + shared_ptr cb = SharedRefBase::make(); + int64_t value = 43; + pullSuccess = true; + values.push_back(value); + + StatsCallbackPuller puller(pullTagId, cb, pullCoolDownNs, pullTimeoutNs, {}); + + vector> dataHolder; + int64_t startTimeNs = getElapsedRealtimeNs(); + EXPECT_TRUE(puller.PullInternal(&dataHolder)); + int64_t endTimeNs = getElapsedRealtimeNs(); + + ASSERT_EQ(1, dataHolder.size()); + EXPECT_EQ(pullTagId, dataHolder[0]->GetTagId()); + EXPECT_LT(startTimeNs, dataHolder[0]->GetElapsedTimestampNs()); + EXPECT_GT(endTimeNs, dataHolder[0]->GetElapsedTimestampNs()); + ASSERT_EQ(1, dataHolder[0]->size()); + EXPECT_EQ(value, dataHolder[0]->getValues()[0].mValue.int_value); +} + +TEST_F(StatsCallbackPullerTest, PullFail) { + shared_ptr cb = SharedRefBase::make(); + pullSuccess = false; + int64_t value = 1234; + values.push_back(value); + + StatsCallbackPuller puller(pullTagId, cb, pullCoolDownNs, pullTimeoutNs, {}); + + vector> dataHolder; + EXPECT_FALSE(puller.PullInternal(&dataHolder)); + ASSERT_EQ(0, dataHolder.size()); +} + +TEST_F(StatsCallbackPullerTest, PullTimeout) { + shared_ptr cb = SharedRefBase::make(); + pullSuccess = true; + pullDelayNs = MillisToNano(5); // 5ms. + pullTimeoutNs = 10000; // 10 microseconds. + int64_t value = 4321; + values.push_back(value); + + StatsCallbackPuller puller(pullTagId, cb, pullCoolDownNs, pullTimeoutNs, {}); + + vector> dataHolder; + int64_t startTimeNs = getElapsedRealtimeNs(); + // Returns true to let StatsPuller code evaluate the timeout. + EXPECT_TRUE(puller.PullInternal(&dataHolder)); + int64_t endTimeNs = getElapsedRealtimeNs(); + int64_t actualPullDurationNs = endTimeNs - startTimeNs; + + // Pull should take at least the timeout amount of time, but should stop early because the delay + // is bigger. + EXPECT_LT(pullTimeoutNs, actualPullDurationNs); + EXPECT_GT(pullDelayNs, actualPullDurationNs); + ASSERT_EQ(0, dataHolder.size()); + + // Let the pull return and make sure that the dataHolder is not modified. + pullThread.join(); + ASSERT_EQ(0, dataHolder.size()); +} + +// Register a puller and ensure that the timeout logic works. +TEST_F(StatsCallbackPullerTest, RegisterAndTimeout) { + shared_ptr cb = SharedRefBase::make(); + pullSuccess = true; + pullDelayNs = MillisToNano(5); // 5 ms. + pullTimeoutNs = 10000; // 10 microsseconds. + int64_t value = 4321; + int32_t uid = 123; + values.push_back(value); + + sp pullerManager = new StatsPullerManager(); + pullerManager->RegisterPullAtomCallback(uid, pullTagId, pullCoolDownNs, pullTimeoutNs, + vector(), cb); + vector> dataHolder; + int64_t startTimeNs = getElapsedRealtimeNs(); + // Returns false, since StatsPuller code will evaluate the timeout. + EXPECT_FALSE(pullerManager->Pull(pullTagId, {uid}, startTimeNs, &dataHolder)); + int64_t endTimeNs = getElapsedRealtimeNs(); + int64_t actualPullDurationNs = endTimeNs - startTimeNs; + + // Pull should take at least the timeout amount of time, but should stop early because the delay + // is bigger. Make sure that the time is closer to the timeout, than to the intended delay. + EXPECT_LT(pullTimeoutNs, actualPullDurationNs); + EXPECT_GT(pullDelayNs / 5, actualPullDurationNs); + ASSERT_EQ(0, dataHolder.size()); + + // Let the pull return and make sure that the dataHolder is not modified. + pullThread.join(); + ASSERT_EQ(0, dataHolder.size()); +} + +} // namespace statsd +} // namespace os +} // namespace android +#else +GTEST_LOG_(INFO) << "This test does nothing.\n"; +#endif diff --git a/statsd/tests/external/StatsPullerManager_test.cpp b/statsd/tests/external/StatsPullerManager_test.cpp new file mode 100644 index 00000000..0d539f47 --- /dev/null +++ b/statsd/tests/external/StatsPullerManager_test.cpp @@ -0,0 +1,150 @@ +// Copyright (C) 2020 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 "src/external/StatsPullerManager.h" + +#include +#include +#include +#include + +#include "stats_event.h" +#include "tests/statsd_test_util.h" + +using aidl::android::util::StatsEventParcel; +using ::ndk::SharedRefBase; +using std::make_shared; +using std::shared_ptr; +using std::vector; + +namespace android { +namespace os { +namespace statsd { + +namespace { + +int pullTagId1 = 10101; +int pullTagId2 = 10102; +int uid1 = 9999; +int uid2 = 8888; +ConfigKey configKey(50, 12345); +ConfigKey badConfigKey(60, 54321); +int unregisteredUid = 98765; +int64_t coolDownNs = NS_PER_SEC; +int64_t timeoutNs = NS_PER_SEC / 2; + +AStatsEvent* createSimpleEvent(int32_t atomId, int32_t value) { + AStatsEvent* event = AStatsEvent_obtain(); + AStatsEvent_setAtomId(event, atomId); + AStatsEvent_writeInt32(event, value); + AStatsEvent_build(event); + return event; +} + +class FakePullAtomCallback : public BnPullAtomCallback { +public: + FakePullAtomCallback(int32_t uid) : mUid(uid){}; + Status onPullAtom(int atomTag, + const shared_ptr& resultReceiver) override { + vector parcels; + AStatsEvent* event = createSimpleEvent(atomTag, mUid); + size_t size; + uint8_t* buffer = AStatsEvent_getBuffer(event, &size); + + StatsEventParcel p; + // vector.assign() creates a copy, but this is inevitable unless + // stats_event.h/c uses a vector as opposed to a buffer. + p.buffer.assign(buffer, buffer + size); + parcels.push_back(std::move(p)); + AStatsEvent_release(event); + resultReceiver->pullFinished(atomTag, /*success*/ true, parcels); + return Status::ok(); + } + int32_t mUid; +}; + +class FakePullUidProvider : public PullUidProvider { +public: + vector getPullAtomUids(int atomId) override { + if (atomId == pullTagId1) { + return {uid2, uid1}; + } else if (atomId == pullTagId2) { + return {uid2}; + } + return {}; + } +}; + +sp createPullerManagerAndRegister() { + sp pullerManager = new StatsPullerManager(); + shared_ptr cb1 = SharedRefBase::make(uid1); + pullerManager->RegisterPullAtomCallback(uid1, pullTagId1, coolDownNs, timeoutNs, {}, cb1); + shared_ptr cb2 = SharedRefBase::make(uid2); + pullerManager->RegisterPullAtomCallback(uid2, pullTagId1, coolDownNs, timeoutNs, {}, cb2); + pullerManager->RegisterPullAtomCallback(uid1, pullTagId2, coolDownNs, timeoutNs, {}, cb1); + return pullerManager; +} +} // anonymous namespace + +TEST(StatsPullerManagerTest, TestPullInvalidUid) { + sp pullerManager = createPullerManagerAndRegister(); + + vector> data; + EXPECT_FALSE(pullerManager->Pull(pullTagId1, {unregisteredUid}, /*timestamp =*/1, &data)); +} + +TEST(StatsPullerManagerTest, TestPullChoosesCorrectUid) { + sp pullerManager = createPullerManagerAndRegister(); + + vector> data; + EXPECT_TRUE(pullerManager->Pull(pullTagId1, {uid1}, /*timestamp =*/1, &data)); + ASSERT_EQ(data.size(), 1); + EXPECT_EQ(data[0]->GetTagId(), pullTagId1); + ASSERT_EQ(data[0]->getValues().size(), 1); + EXPECT_EQ(data[0]->getValues()[0].mValue.int_value, uid1); +} + +TEST(StatsPullerManagerTest, TestPullInvalidConfigKey) { + sp pullerManager = createPullerManagerAndRegister(); + sp uidProvider = new FakePullUidProvider(); + pullerManager->RegisterPullUidProvider(configKey, uidProvider); + + vector> data; + EXPECT_FALSE(pullerManager->Pull(pullTagId1, badConfigKey, /*timestamp =*/1, &data)); +} + +TEST(StatsPullerManagerTest, TestPullConfigKeyGood) { + sp pullerManager = createPullerManagerAndRegister(); + sp uidProvider = new FakePullUidProvider(); + pullerManager->RegisterPullUidProvider(configKey, uidProvider); + + vector> data; + EXPECT_TRUE(pullerManager->Pull(pullTagId1, configKey, /*timestamp =*/1, &data)); + EXPECT_EQ(data[0]->GetTagId(), pullTagId1); + ASSERT_EQ(data[0]->getValues().size(), 1); + EXPECT_EQ(data[0]->getValues()[0].mValue.int_value, uid2); +} + +TEST(StatsPullerManagerTest, TestPullConfigKeyNoPullerWithUid) { + sp pullerManager = createPullerManagerAndRegister(); + sp uidProvider = new FakePullUidProvider(); + pullerManager->RegisterPullUidProvider(configKey, uidProvider); + + vector> data; + EXPECT_FALSE(pullerManager->Pull(pullTagId2, configKey, /*timestamp =*/1, &data)); +} + +} // namespace statsd +} // namespace os +} // namespace android \ No newline at end of file diff --git a/statsd/tests/external/StatsPuller_test.cpp b/statsd/tests/external/StatsPuller_test.cpp new file mode 100644 index 00000000..55a90365 --- /dev/null +++ b/statsd/tests/external/StatsPuller_test.cpp @@ -0,0 +1,316 @@ +// Copyright (C) 2018 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 +#include + +#include +#include +#include + +#include "../metrics/metrics_test_helper.h" +#include "src/stats_log_util.h" +#include "stats_event.h" +#include "tests/statsd_test_util.h" + +#ifdef __ANDROID__ + +namespace android { +namespace os { +namespace statsd { + +using namespace testing; +using std::make_shared; +using std::shared_ptr; +using std::vector; +using std::this_thread::sleep_for; +using testing::Contains; + +namespace { +int pullTagId = 10014; + +bool pullSuccess; +vector> pullData; +long pullDelayNs; + +class FakePuller : public StatsPuller { +public: + FakePuller() + : StatsPuller(pullTagId, /*coolDownNs=*/MillisToNano(10), /*timeoutNs=*/MillisToNano(5)){}; + +private: + bool PullInternal(vector>* data) override { + (*data) = pullData; + sleep_for(std::chrono::nanoseconds(pullDelayNs)); + return pullSuccess; + } +}; + +FakePuller puller; + +std::unique_ptr createSimpleEvent(int64_t eventTimeNs, int64_t value) { + AStatsEvent* statsEvent = AStatsEvent_obtain(); + AStatsEvent_setAtomId(statsEvent, pullTagId); + AStatsEvent_overwriteTimestamp(statsEvent, eventTimeNs); + AStatsEvent_writeInt64(statsEvent, value); + + std::unique_ptr logEvent = std::make_unique(/*uid=*/0, /*pid=*/0); + parseStatsEventToLogEvent(statsEvent, logEvent.get()); + return logEvent; +} + +class StatsPullerTest : public ::testing::Test { +public: + StatsPullerTest() { + } + + void SetUp() override { + puller.ForceClearCache(); + pullSuccess = false; + pullDelayNs = 0; + pullData.clear(); + } +}; + +} // Anonymous namespace. + +TEST_F(StatsPullerTest, PullSuccess) { + pullData.push_back(createSimpleEvent(1111L, 33)); + + pullSuccess = true; + + vector> dataHolder; + EXPECT_TRUE(puller.Pull(getElapsedRealtimeNs(), &dataHolder)); + ASSERT_EQ(1, dataHolder.size()); + EXPECT_EQ(pullTagId, dataHolder[0]->GetTagId()); + EXPECT_EQ(1111L, dataHolder[0]->GetElapsedTimestampNs()); + ASSERT_EQ(1, dataHolder[0]->size()); + EXPECT_EQ(33, dataHolder[0]->getValues()[0].mValue.int_value); + + sleep_for(std::chrono::milliseconds(11)); + + pullData.clear(); + pullData.push_back(createSimpleEvent(2222L, 44)); + + pullSuccess = true; + + EXPECT_TRUE(puller.Pull(getElapsedRealtimeNs(), &dataHolder)); + ASSERT_EQ(1, dataHolder.size()); + EXPECT_EQ(pullTagId, dataHolder[0]->GetTagId()); + EXPECT_EQ(2222L, dataHolder[0]->GetElapsedTimestampNs()); + ASSERT_EQ(1, dataHolder[0]->size()); + EXPECT_EQ(44, dataHolder[0]->getValues()[0].mValue.int_value); +} + +TEST_F(StatsPullerTest, PullFailAfterSuccess) { + pullData.push_back(createSimpleEvent(1111L, 33)); + + pullSuccess = true; + + vector> dataHolder; + EXPECT_TRUE(puller.Pull(getElapsedRealtimeNs(), &dataHolder)); + ASSERT_EQ(1, dataHolder.size()); + EXPECT_EQ(pullTagId, dataHolder[0]->GetTagId()); + EXPECT_EQ(1111L, dataHolder[0]->GetElapsedTimestampNs()); + ASSERT_EQ(1, dataHolder[0]->size()); + EXPECT_EQ(33, dataHolder[0]->getValues()[0].mValue.int_value); + + sleep_for(std::chrono::milliseconds(11)); + + pullData.clear(); + pullData.push_back(createSimpleEvent(2222L, 44)); + + pullSuccess = false; + dataHolder.clear(); + EXPECT_FALSE(puller.Pull(getElapsedRealtimeNs(), &dataHolder)); + ASSERT_EQ(0, dataHolder.size()); + + // Fails due to hitting the cool down. + pullSuccess = true; + dataHolder.clear(); + EXPECT_FALSE(puller.Pull(getElapsedRealtimeNs(), &dataHolder)); + ASSERT_EQ(0, dataHolder.size()); +} + +// Test pull takes longer than timeout, 2nd pull happens shorter than cooldown +TEST_F(StatsPullerTest, PullTakeTooLongAndPullFast) { + pullData.push_back(createSimpleEvent(1111L, 33)); + pullSuccess = true; + // timeout is 5ms + pullDelayNs = MillisToNano(6); + + vector> dataHolder; + EXPECT_FALSE(puller.Pull(getElapsedRealtimeNs(), &dataHolder)); + ASSERT_EQ(0, dataHolder.size()); + + pullData.clear(); + pullData.push_back(createSimpleEvent(2222L, 44)); + pullDelayNs = 0; + + pullSuccess = true; + dataHolder.clear(); + EXPECT_FALSE(puller.Pull(getElapsedRealtimeNs(), &dataHolder)); + ASSERT_EQ(0, dataHolder.size()); +} + +TEST_F(StatsPullerTest, PullFail) { + pullData.push_back(createSimpleEvent(1111L, 33)); + + pullSuccess = false; + + vector> dataHolder; + EXPECT_FALSE(puller.Pull(getElapsedRealtimeNs(), &dataHolder)); + ASSERT_EQ(0, dataHolder.size()); +} + +TEST_F(StatsPullerTest, PullTakeTooLong) { + pullData.push_back(createSimpleEvent(1111L, 33)); + + pullSuccess = true; + pullDelayNs = MillisToNano(6); + + vector> dataHolder; + EXPECT_FALSE(puller.Pull(getElapsedRealtimeNs(), &dataHolder)); + ASSERT_EQ(0, dataHolder.size()); +} + +TEST_F(StatsPullerTest, PullTooFast) { + pullData.push_back(createSimpleEvent(1111L, 33)); + + pullSuccess = true; + + vector> dataHolder; + EXPECT_TRUE(puller.Pull(getElapsedRealtimeNs(), &dataHolder)); + ASSERT_EQ(1, dataHolder.size()); + EXPECT_EQ(pullTagId, dataHolder[0]->GetTagId()); + EXPECT_EQ(1111L, dataHolder[0]->GetElapsedTimestampNs()); + ASSERT_EQ(1, dataHolder[0]->size()); + EXPECT_EQ(33, dataHolder[0]->getValues()[0].mValue.int_value); + + pullData.clear(); + pullData.push_back(createSimpleEvent(2222L, 44)); + + pullSuccess = true; + + dataHolder.clear(); + EXPECT_TRUE(puller.Pull(getElapsedRealtimeNs(), &dataHolder)); + ASSERT_EQ(1, dataHolder.size()); + EXPECT_EQ(pullTagId, dataHolder[0]->GetTagId()); + EXPECT_EQ(1111L, dataHolder[0]->GetElapsedTimestampNs()); + ASSERT_EQ(1, dataHolder[0]->size()); + EXPECT_EQ(33, dataHolder[0]->getValues()[0].mValue.int_value); +} + +TEST_F(StatsPullerTest, PullFailsAndTooFast) { + pullData.push_back(createSimpleEvent(1111L, 33)); + + pullSuccess = false; + + vector> dataHolder; + EXPECT_FALSE(puller.Pull(getElapsedRealtimeNs(), &dataHolder)); + ASSERT_EQ(0, dataHolder.size()); + + pullData.clear(); + pullData.push_back(createSimpleEvent(2222L, 44)); + + pullSuccess = true; + + EXPECT_FALSE(puller.Pull(getElapsedRealtimeNs(), &dataHolder)); + ASSERT_EQ(0, dataHolder.size()); +} + +TEST_F(StatsPullerTest, PullSameEventTime) { + pullData.push_back(createSimpleEvent(1111L, 33)); + + pullSuccess = true; + int64_t eventTimeNs = getElapsedRealtimeNs(); + + vector> dataHolder; + EXPECT_TRUE(puller.Pull(eventTimeNs, &dataHolder)); + ASSERT_EQ(1, dataHolder.size()); + EXPECT_EQ(pullTagId, dataHolder[0]->GetTagId()); + EXPECT_EQ(1111L, dataHolder[0]->GetElapsedTimestampNs()); + ASSERT_EQ(1, dataHolder[0]->size()); + EXPECT_EQ(33, dataHolder[0]->getValues()[0].mValue.int_value); + + pullData.clear(); + pullData.push_back(createSimpleEvent(2222L, 44)); + + // Sleep to ensure the cool down expires. + sleep_for(std::chrono::milliseconds(11)); + pullSuccess = true; + + dataHolder.clear(); + EXPECT_TRUE(puller.Pull(eventTimeNs, &dataHolder)); + ASSERT_EQ(1, dataHolder.size()); + EXPECT_EQ(pullTagId, dataHolder[0]->GetTagId()); + EXPECT_EQ(1111L, dataHolder[0]->GetElapsedTimestampNs()); + ASSERT_EQ(1, dataHolder[0]->size()); + EXPECT_EQ(33, dataHolder[0]->getValues()[0].mValue.int_value); +} + +// Test pull takes longer than timeout, 2nd pull happens at same event time +TEST_F(StatsPullerTest, PullTakeTooLongAndPullSameEventTime) { + pullData.push_back(createSimpleEvent(1111L, 33)); + pullSuccess = true; + int64_t eventTimeNs = getElapsedRealtimeNs(); + // timeout is 5ms + pullDelayNs = MillisToNano(6); + + vector> dataHolder; + EXPECT_FALSE(puller.Pull(eventTimeNs, &dataHolder)); + ASSERT_EQ(0, dataHolder.size()); + + // Sleep to ensure the cool down expires. 6ms is taken by the delay, so only 5 is needed here. + sleep_for(std::chrono::milliseconds(5)); + + pullData.clear(); + pullData.push_back(createSimpleEvent(2222L, 44)); + pullDelayNs = 0; + + pullSuccess = true; + dataHolder.clear(); + EXPECT_FALSE(puller.Pull(eventTimeNs, &dataHolder)); + ASSERT_EQ(0, dataHolder.size()); +} + +TEST_F(StatsPullerTest, PullFailsAndPullSameEventTime) { + pullData.push_back(createSimpleEvent(1111L, 33)); + + pullSuccess = false; + int64_t eventTimeNs = getElapsedRealtimeNs(); + + vector> dataHolder; + EXPECT_FALSE(puller.Pull(eventTimeNs, &dataHolder)); + ASSERT_EQ(0, dataHolder.size()); + + // Sleep to ensure the cool down expires. + sleep_for(std::chrono::milliseconds(11)); + + pullData.clear(); + pullData.push_back(createSimpleEvent(2222L, 44)); + + pullSuccess = true; + + EXPECT_FALSE(puller.Pull(eventTimeNs, &dataHolder)); + ASSERT_EQ(0, dataHolder.size()); +} + +} // namespace statsd +} // namespace os +} // namespace android +#else +GTEST_LOG_(INFO) << "This test does nothing.\n"; +#endif diff --git a/statsd/tests/external/puller_util_test.cpp b/statsd/tests/external/puller_util_test.cpp new file mode 100644 index 00000000..a21dc871 --- /dev/null +++ b/statsd/tests/external/puller_util_test.cpp @@ -0,0 +1,408 @@ +// Copyright (C) 2018 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 "external/puller_util.h" + +#include +#include +#include + +#include + +#include "../metrics/metrics_test_helper.h" +#include "FieldValue.h" +#include "annotations.h" +#include "stats_event.h" +#include "tests/statsd_test_util.h" + +#ifdef __ANDROID__ + +namespace android { +namespace os { +namespace statsd { + +using namespace testing; +using std::shared_ptr; +using std::vector; +/* + * Test merge isolated and host uid + */ +namespace { +const int uidAtomTagId = 100; +const vector additiveFields = {3}; +const int nonUidAtomTagId = 200; +const int timestamp = 1234; +const int isolatedUid1 = 30; +const int isolatedUid2 = 40; +const int isolatedNonAdditiveData = 32; +const int isolatedAdditiveData = 31; +const int hostUid = 20; +const int hostNonAdditiveData = 22; +const int hostAdditiveData = 21; +const int attributionAtomTagId = 300; + +sp makeMockUidMap() { + return makeMockUidMapForOneHost(hostUid, {isolatedUid1, isolatedUid2}); +} + +} // anonymous namespace + +TEST(PullerUtilTest, MergeNoDimension) { + vector> data = { + // 30->22->31 + makeUidLogEvent(uidAtomTagId, timestamp, isolatedUid1, hostNonAdditiveData, + isolatedAdditiveData), + + // 20->22->21 + makeUidLogEvent(uidAtomTagId, timestamp, hostUid, hostNonAdditiveData, + hostAdditiveData), + }; + + sp uidMap = makeMockUidMap(); + mapAndMergeIsolatedUidsToHostUid(data, uidMap, uidAtomTagId, additiveFields); + + ASSERT_EQ(1, (int)data.size()); + const vector* actualFieldValues = &data[0]->getValues(); + ASSERT_EQ(3, actualFieldValues->size()); + EXPECT_EQ(hostUid, actualFieldValues->at(0).mValue.int_value); + EXPECT_EQ(hostNonAdditiveData, actualFieldValues->at(1).mValue.int_value); + EXPECT_EQ(isolatedAdditiveData + hostAdditiveData, actualFieldValues->at(2).mValue.int_value); +} + +TEST(PullerUtilTest, MergeWithDimension) { + vector> data = { + // 30->32->31 + makeUidLogEvent(uidAtomTagId, timestamp, isolatedUid1, isolatedNonAdditiveData, + isolatedAdditiveData), + + // 20->32->21 + makeUidLogEvent(uidAtomTagId, timestamp, hostUid, isolatedNonAdditiveData, + hostAdditiveData), + + // 20->22->21 + makeUidLogEvent(uidAtomTagId, timestamp, hostUid, hostNonAdditiveData, + hostAdditiveData), + }; + + sp uidMap = makeMockUidMap(); + mapAndMergeIsolatedUidsToHostUid(data, uidMap, uidAtomTagId, additiveFields); + + ASSERT_EQ(2, (int)data.size()); + + const vector* actualFieldValues = &data[0]->getValues(); + ASSERT_EQ(3, actualFieldValues->size()); + EXPECT_EQ(hostUid, actualFieldValues->at(0).mValue.int_value); + EXPECT_EQ(hostNonAdditiveData, actualFieldValues->at(1).mValue.int_value); + EXPECT_EQ(hostAdditiveData, actualFieldValues->at(2).mValue.int_value); + + actualFieldValues = &data[1]->getValues(); + ASSERT_EQ(3, actualFieldValues->size()); + EXPECT_EQ(hostUid, actualFieldValues->at(0).mValue.int_value); + EXPECT_EQ(isolatedNonAdditiveData, actualFieldValues->at(1).mValue.int_value); + EXPECT_EQ(hostAdditiveData + isolatedAdditiveData, actualFieldValues->at(2).mValue.int_value); +} + +TEST(PullerUtilTest, NoMergeHostUidOnly) { + vector> data = { + // 20->32->31 + makeUidLogEvent(uidAtomTagId, timestamp, hostUid, isolatedNonAdditiveData, + isolatedAdditiveData), + + // 20->22->21 + makeUidLogEvent(uidAtomTagId, timestamp, hostUid, hostNonAdditiveData, + hostAdditiveData), + }; + + sp uidMap = makeMockUidMap(); + mapAndMergeIsolatedUidsToHostUid(data, uidMap, uidAtomTagId, additiveFields); + + ASSERT_EQ(2, (int)data.size()); + + const vector* actualFieldValues = &data[0]->getValues(); + ASSERT_EQ(3, actualFieldValues->size()); + EXPECT_EQ(hostUid, actualFieldValues->at(0).mValue.int_value); + EXPECT_EQ(hostNonAdditiveData, actualFieldValues->at(1).mValue.int_value); + EXPECT_EQ(hostAdditiveData, actualFieldValues->at(2).mValue.int_value); + + actualFieldValues = &data[1]->getValues(); + ASSERT_EQ(3, actualFieldValues->size()); + EXPECT_EQ(hostUid, actualFieldValues->at(0).mValue.int_value); + EXPECT_EQ(isolatedNonAdditiveData, actualFieldValues->at(1).mValue.int_value); + EXPECT_EQ(isolatedAdditiveData, actualFieldValues->at(2).mValue.int_value); +} + +TEST(PullerUtilTest, IsolatedUidOnly) { + vector> data = { + // 30->32->31 + makeUidLogEvent(uidAtomTagId, timestamp, isolatedUid1, isolatedNonAdditiveData, + isolatedAdditiveData), + + // 30->22->21 + makeUidLogEvent(uidAtomTagId, timestamp, isolatedUid1, hostNonAdditiveData, + hostAdditiveData), + }; + + sp uidMap = makeMockUidMap(); + mapAndMergeIsolatedUidsToHostUid(data, uidMap, uidAtomTagId, additiveFields); + + ASSERT_EQ(2, (int)data.size()); + + // 20->32->31 + const vector* actualFieldValues = &data[0]->getValues(); + ASSERT_EQ(3, actualFieldValues->size()); + EXPECT_EQ(hostUid, actualFieldValues->at(0).mValue.int_value); + EXPECT_EQ(hostNonAdditiveData, actualFieldValues->at(1).mValue.int_value); + EXPECT_EQ(hostAdditiveData, actualFieldValues->at(2).mValue.int_value); + + // 20->22->21 + actualFieldValues = &data[1]->getValues(); + ASSERT_EQ(3, actualFieldValues->size()); + EXPECT_EQ(hostUid, actualFieldValues->at(0).mValue.int_value); + EXPECT_EQ(isolatedNonAdditiveData, actualFieldValues->at(1).mValue.int_value); + EXPECT_EQ(isolatedAdditiveData, actualFieldValues->at(2).mValue.int_value); +} + +TEST(PullerUtilTest, MultipleIsolatedUidToOneHostUid) { + vector> data = { + // 30->32->31 + makeUidLogEvent(uidAtomTagId, timestamp, isolatedUid1, isolatedNonAdditiveData, + isolatedAdditiveData), + + // 31->32->21 + makeUidLogEvent(uidAtomTagId, timestamp, isolatedUid2, isolatedNonAdditiveData, + hostAdditiveData), + + // 20->32->21 + makeUidLogEvent(uidAtomTagId, timestamp, hostUid, isolatedNonAdditiveData, + hostAdditiveData), + }; + + sp uidMap = makeMockUidMap(); + mapAndMergeIsolatedUidsToHostUid(data, uidMap, uidAtomTagId, additiveFields); + + ASSERT_EQ(1, (int)data.size()); + + const vector* actualFieldValues = &data[0]->getValues(); + ASSERT_EQ(3, actualFieldValues->size()); + EXPECT_EQ(hostUid, actualFieldValues->at(0).mValue.int_value); + EXPECT_EQ(isolatedNonAdditiveData, actualFieldValues->at(1).mValue.int_value); + EXPECT_EQ(isolatedAdditiveData + hostAdditiveData + hostAdditiveData, + actualFieldValues->at(2).mValue.int_value); +} + +TEST(PullerUtilTest, NoNeedToMerge) { + vector> data = { + // 32->31 + CreateTwoValueLogEvent(nonUidAtomTagId, timestamp, isolatedNonAdditiveData, + isolatedAdditiveData), + + // 22->21 + CreateTwoValueLogEvent(nonUidAtomTagId, timestamp, hostNonAdditiveData, + hostAdditiveData), + + }; + + sp uidMap = makeMockUidMap(); + mapAndMergeIsolatedUidsToHostUid(data, uidMap, nonUidAtomTagId, {} /*no additive fields*/); + + ASSERT_EQ(2, (int)data.size()); + + const vector* actualFieldValues = &data[0]->getValues(); + ASSERT_EQ(2, actualFieldValues->size()); + EXPECT_EQ(isolatedNonAdditiveData, actualFieldValues->at(0).mValue.int_value); + EXPECT_EQ(isolatedAdditiveData, actualFieldValues->at(1).mValue.int_value); + + actualFieldValues = &data[1]->getValues(); + ASSERT_EQ(2, actualFieldValues->size()); + EXPECT_EQ(hostNonAdditiveData, actualFieldValues->at(0).mValue.int_value); + EXPECT_EQ(hostAdditiveData, actualFieldValues->at(1).mValue.int_value); +} + +TEST(PullerUtilTest, MergeNoDimensionAttributionChain) { + vector> data = { + // 30->tag1->400->tag2->22->31 + makeAttributionLogEvent(attributionAtomTagId, timestamp, {isolatedUid1, 400}, + {"tag1", "tag2"}, hostNonAdditiveData, isolatedAdditiveData), + + // 20->tag1->400->tag2->22->21 + makeAttributionLogEvent(attributionAtomTagId, timestamp, {hostUid, 400}, + {"tag1", "tag2"}, hostNonAdditiveData, hostAdditiveData), + }; + + sp uidMap = makeMockUidMap(); + mapAndMergeIsolatedUidsToHostUid(data, uidMap, attributionAtomTagId, additiveFields); + + ASSERT_EQ(1, (int)data.size()); + const vector* actualFieldValues = &data[0]->getValues(); + ASSERT_EQ(6, actualFieldValues->size()); + EXPECT_EQ(hostUid, actualFieldValues->at(0).mValue.int_value); + EXPECT_EQ("tag1", actualFieldValues->at(1).mValue.str_value); + EXPECT_EQ(400, actualFieldValues->at(2).mValue.int_value); + EXPECT_EQ("tag2", actualFieldValues->at(3).mValue.str_value); + EXPECT_EQ(hostNonAdditiveData, actualFieldValues->at(4).mValue.int_value); + EXPECT_EQ(isolatedAdditiveData + hostAdditiveData, actualFieldValues->at(5).mValue.int_value); +} + +TEST(PullerUtilTest, MergeWithDimensionAttributionChain) { + vector> data = { + // 200->tag1->30->tag2->32->31 + makeAttributionLogEvent(attributionAtomTagId, timestamp, {200, isolatedUid1}, + {"tag1", "tag2"}, isolatedNonAdditiveData, + isolatedAdditiveData), + + // 200->tag1->20->tag2->32->21 + makeAttributionLogEvent(attributionAtomTagId, timestamp, {200, hostUid}, + {"tag1", "tag2"}, isolatedNonAdditiveData, hostAdditiveData), + + // 200->tag1->20->tag2->22->21 + makeAttributionLogEvent(attributionAtomTagId, timestamp, {200, hostUid}, + {"tag1", "tag2"}, hostNonAdditiveData, hostAdditiveData), + }; + + sp uidMap = makeMockUidMap(); + mapAndMergeIsolatedUidsToHostUid(data, uidMap, attributionAtomTagId, additiveFields); + + ASSERT_EQ(2, (int)data.size()); + + const vector* actualFieldValues = &data[0]->getValues(); + ASSERT_EQ(6, actualFieldValues->size()); + EXPECT_EQ(200, actualFieldValues->at(0).mValue.int_value); + EXPECT_EQ("tag1", actualFieldValues->at(1).mValue.str_value); + EXPECT_EQ(hostUid, actualFieldValues->at(2).mValue.int_value); + EXPECT_EQ("tag2", actualFieldValues->at(3).mValue.str_value); + EXPECT_EQ(hostNonAdditiveData, actualFieldValues->at(4).mValue.int_value); + EXPECT_EQ(hostAdditiveData, actualFieldValues->at(5).mValue.int_value); + + actualFieldValues = &data[1]->getValues(); + ASSERT_EQ(6, actualFieldValues->size()); + EXPECT_EQ(200, actualFieldValues->at(0).mValue.int_value); + EXPECT_EQ("tag1", actualFieldValues->at(1).mValue.str_value); + EXPECT_EQ(hostUid, actualFieldValues->at(2).mValue.int_value); + EXPECT_EQ("tag2", actualFieldValues->at(3).mValue.str_value); + EXPECT_EQ(isolatedNonAdditiveData, actualFieldValues->at(4).mValue.int_value); + EXPECT_EQ(hostAdditiveData + isolatedAdditiveData, actualFieldValues->at(5).mValue.int_value); +} + +TEST(PullerUtilTest, NoMergeHostUidOnlyAttributionChain) { + vector> data = { + // 20->tag1->400->tag2->32->31 + makeAttributionLogEvent(attributionAtomTagId, timestamp, {hostUid, 400}, + {"tag1", "tag2"}, isolatedNonAdditiveData, + isolatedAdditiveData), + + // 20->tag1->400->tag2->22->21 + makeAttributionLogEvent(attributionAtomTagId, timestamp, {hostUid, 400}, + {"tag1", "tag2"}, hostNonAdditiveData, hostAdditiveData), + }; + + sp uidMap = makeMockUidMap(); + mapAndMergeIsolatedUidsToHostUid(data, uidMap, attributionAtomTagId, additiveFields); + + ASSERT_EQ(2, (int)data.size()); + + const vector* actualFieldValues = &data[0]->getValues(); + ASSERT_EQ(6, actualFieldValues->size()); + EXPECT_EQ(hostUid, actualFieldValues->at(0).mValue.int_value); + EXPECT_EQ("tag1", actualFieldValues->at(1).mValue.str_value); + EXPECT_EQ(400, actualFieldValues->at(2).mValue.int_value); + EXPECT_EQ("tag2", actualFieldValues->at(3).mValue.str_value); + EXPECT_EQ(hostNonAdditiveData, actualFieldValues->at(4).mValue.int_value); + EXPECT_EQ(hostAdditiveData, actualFieldValues->at(5).mValue.int_value); + + actualFieldValues = &data[1]->getValues(); + ASSERT_EQ(6, actualFieldValues->size()); + EXPECT_EQ(hostUid, actualFieldValues->at(0).mValue.int_value); + EXPECT_EQ("tag1", actualFieldValues->at(1).mValue.str_value); + EXPECT_EQ(400, actualFieldValues->at(2).mValue.int_value); + EXPECT_EQ("tag2", actualFieldValues->at(3).mValue.str_value); + EXPECT_EQ(isolatedNonAdditiveData, actualFieldValues->at(4).mValue.int_value); + EXPECT_EQ(isolatedAdditiveData, actualFieldValues->at(5).mValue.int_value); +} + +TEST(PullerUtilTest, IsolatedUidOnlyAttributionChain) { + vector> data = { + // 30->tag1->400->tag2->32->31 + makeAttributionLogEvent(attributionAtomTagId, timestamp, {isolatedUid1, 400}, + {"tag1", "tag2"}, isolatedNonAdditiveData, + isolatedAdditiveData), + + // 30->tag1->400->tag2->22->21 + makeAttributionLogEvent(attributionAtomTagId, timestamp, {isolatedUid1, 400}, + {"tag1", "tag2"}, hostNonAdditiveData, hostAdditiveData), + }; + + sp uidMap = makeMockUidMap(); + mapAndMergeIsolatedUidsToHostUid(data, uidMap, attributionAtomTagId, additiveFields); + + ASSERT_EQ(2, (int)data.size()); + + // 20->tag1->400->tag2->32->31 + const vector* actualFieldValues = &data[0]->getValues(); + ASSERT_EQ(6, actualFieldValues->size()); + EXPECT_EQ(hostUid, actualFieldValues->at(0).mValue.int_value); + EXPECT_EQ("tag1", actualFieldValues->at(1).mValue.str_value); + EXPECT_EQ(400, actualFieldValues->at(2).mValue.int_value); + EXPECT_EQ("tag2", actualFieldValues->at(3).mValue.str_value); + EXPECT_EQ(hostNonAdditiveData, actualFieldValues->at(4).mValue.int_value); + EXPECT_EQ(hostAdditiveData, actualFieldValues->at(5).mValue.int_value); + + // 20->tag1->400->tag2->22->21 + actualFieldValues = &data[1]->getValues(); + ASSERT_EQ(6, actualFieldValues->size()); + EXPECT_EQ(hostUid, actualFieldValues->at(0).mValue.int_value); + EXPECT_EQ("tag1", actualFieldValues->at(1).mValue.str_value); + EXPECT_EQ(400, actualFieldValues->at(2).mValue.int_value); + EXPECT_EQ("tag2", actualFieldValues->at(3).mValue.str_value); + EXPECT_EQ(isolatedNonAdditiveData, actualFieldValues->at(4).mValue.int_value); + EXPECT_EQ(isolatedAdditiveData, actualFieldValues->at(5).mValue.int_value); +} + +TEST(PullerUtilTest, MultipleIsolatedUidToOneHostUidAttributionChain) { + vector> data = { + // 30->tag1->400->tag2->32->31 + makeAttributionLogEvent(attributionAtomTagId, timestamp, {isolatedUid1, 400}, + {"tag1", "tag2"}, isolatedNonAdditiveData, + isolatedAdditiveData), + + // 31->tag1->400->tag2->32->21 + makeAttributionLogEvent(attributionAtomTagId, timestamp, {isolatedUid2, 400}, + {"tag1", "tag2"}, isolatedNonAdditiveData, hostAdditiveData), + + // 20->tag1->400->tag2->32->21 + makeAttributionLogEvent(attributionAtomTagId, timestamp, {hostUid, 400}, + {"tag1", "tag2"}, isolatedNonAdditiveData, hostAdditiveData), + }; + + sp uidMap = makeMockUidMap(); + mapAndMergeIsolatedUidsToHostUid(data, uidMap, attributionAtomTagId, additiveFields); + + ASSERT_EQ(1, (int)data.size()); + + const vector* actualFieldValues = &data[0]->getValues(); + ASSERT_EQ(6, actualFieldValues->size()); + EXPECT_EQ(hostUid, actualFieldValues->at(0).mValue.int_value); + EXPECT_EQ("tag1", actualFieldValues->at(1).mValue.str_value); + EXPECT_EQ(400, actualFieldValues->at(2).mValue.int_value); + EXPECT_EQ("tag2", actualFieldValues->at(3).mValue.str_value); + EXPECT_EQ(isolatedNonAdditiveData, actualFieldValues->at(4).mValue.int_value); + EXPECT_EQ(isolatedAdditiveData + hostAdditiveData + hostAdditiveData, + actualFieldValues->at(5).mValue.int_value); +} + +} // namespace statsd +} // namespace os +} // namespace android +#else +GTEST_LOG_(INFO) << "This test does nothing.\n"; +#endif diff --git a/statsd/tests/guardrail/StatsdStats_test.cpp b/statsd/tests/guardrail/StatsdStats_test.cpp new file mode 100644 index 00000000..5a824c53 --- /dev/null +++ b/statsd/tests/guardrail/StatsdStats_test.cpp @@ -0,0 +1,553 @@ +// 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 "src/guardrail/StatsdStats.h" +#include "statslog_statsdtest.h" +#include "tests/statsd_test_util.h" + +#include +#include + +#ifdef __ANDROID__ + +namespace android { +namespace os { +namespace statsd { + +using std::vector; + +TEST(StatsdStatsTest, TestValidConfigAdd) { + StatsdStats stats; + ConfigKey key(0, 12345); + const int metricsCount = 10; + const int conditionsCount = 20; + const int matchersCount = 30; + const int alertsCount = 10; + stats.noteConfigReceived(key, metricsCount, conditionsCount, matchersCount, alertsCount, {}, + true /*valid config*/); + vector output; + stats.dumpStats(&output, false /*reset stats*/); + + StatsdStatsReport report; + bool good = report.ParseFromArray(&output[0], output.size()); + EXPECT_TRUE(good); + ASSERT_EQ(1, report.config_stats_size()); + const auto& configReport = report.config_stats(0); + EXPECT_EQ(0, configReport.uid()); + EXPECT_EQ(12345, configReport.id()); + EXPECT_EQ(metricsCount, configReport.metric_count()); + EXPECT_EQ(conditionsCount, configReport.condition_count()); + EXPECT_EQ(matchersCount, configReport.matcher_count()); + EXPECT_EQ(alertsCount, configReport.alert_count()); + EXPECT_EQ(true, configReport.is_valid()); + EXPECT_FALSE(configReport.has_deletion_time_sec()); +} + +TEST(StatsdStatsTest, TestInvalidConfigAdd) { + StatsdStats stats; + ConfigKey key(0, 12345); + const int metricsCount = 10; + const int conditionsCount = 20; + const int matchersCount = 30; + const int alertsCount = 10; + stats.noteConfigReceived(key, metricsCount, conditionsCount, matchersCount, alertsCount, {}, + false /*bad config*/); + vector output; + stats.dumpStats(&output, false); + + StatsdStatsReport report; + bool good = report.ParseFromArray(&output[0], output.size()); + EXPECT_TRUE(good); + ASSERT_EQ(1, report.config_stats_size()); + const auto& configReport = report.config_stats(0); + // The invalid config should be put into icebox with a deletion time. + EXPECT_TRUE(configReport.has_deletion_time_sec()); +} + +TEST(StatsdStatsTest, TestConfigRemove) { + StatsdStats stats; + ConfigKey key(0, 12345); + const int metricsCount = 10; + const int conditionsCount = 20; + const int matchersCount = 30; + const int alertsCount = 10; + stats.noteConfigReceived(key, metricsCount, conditionsCount, matchersCount, alertsCount, {}, + true); + vector output; + stats.dumpStats(&output, false); + StatsdStatsReport report; + bool good = report.ParseFromArray(&output[0], output.size()); + EXPECT_TRUE(good); + ASSERT_EQ(1, report.config_stats_size()); + const auto& configReport = report.config_stats(0); + EXPECT_FALSE(configReport.has_deletion_time_sec()); + + stats.noteConfigRemoved(key); + stats.dumpStats(&output, false); + good = report.ParseFromArray(&output[0], output.size()); + EXPECT_TRUE(good); + ASSERT_EQ(1, report.config_stats_size()); + const auto& configReport2 = report.config_stats(0); + EXPECT_TRUE(configReport2.has_deletion_time_sec()); +} + +TEST(StatsdStatsTest, TestSubStats) { + StatsdStats stats; + ConfigKey key(0, 12345); + stats.noteConfigReceived(key, 2, 3, 4, 5, {std::make_pair(123, 456)}, true); + + stats.noteMatcherMatched(key, StringToId("matcher1")); + stats.noteMatcherMatched(key, StringToId("matcher1")); + stats.noteMatcherMatched(key, StringToId("matcher2")); + + stats.noteConditionDimensionSize(key, StringToId("condition1"), 250); + stats.noteConditionDimensionSize(key, StringToId("condition1"), 240); + + stats.noteMetricDimensionSize(key, StringToId("metric1"), 201); + stats.noteMetricDimensionSize(key, StringToId("metric1"), 202); + + stats.noteAnomalyDeclared(key, StringToId("alert1")); + stats.noteAnomalyDeclared(key, StringToId("alert1")); + stats.noteAnomalyDeclared(key, StringToId("alert2")); + + // broadcast-> 2 + stats.noteBroadcastSent(key); + stats.noteBroadcastSent(key); + + // data drop -> 1 + stats.noteDataDropped(key, 123); + + // dump report -> 3 + stats.noteMetricsReportSent(key, 0); + stats.noteMetricsReportSent(key, 0); + stats.noteMetricsReportSent(key, 0); + + // activation_time_sec -> 2 + stats.noteActiveStatusChanged(key, true); + stats.noteActiveStatusChanged(key, true); + + // deactivation_time_sec -> 1 + stats.noteActiveStatusChanged(key, false); + + vector output; + stats.dumpStats(&output, true); // Dump and reset stats + StatsdStatsReport report; + bool good = report.ParseFromArray(&output[0], output.size()); + EXPECT_TRUE(good); + ASSERT_EQ(1, report.config_stats_size()); + const auto& configReport = report.config_stats(0); + ASSERT_EQ(2, configReport.broadcast_sent_time_sec_size()); + ASSERT_EQ(1, configReport.data_drop_time_sec_size()); + ASSERT_EQ(1, configReport.data_drop_bytes_size()); + EXPECT_EQ(123, configReport.data_drop_bytes(0)); + ASSERT_EQ(3, configReport.dump_report_time_sec_size()); + ASSERT_EQ(3, configReport.dump_report_data_size_size()); + ASSERT_EQ(2, configReport.activation_time_sec_size()); + ASSERT_EQ(1, configReport.deactivation_time_sec_size()); + ASSERT_EQ(1, configReport.annotation_size()); + EXPECT_EQ(123, configReport.annotation(0).field_int64()); + EXPECT_EQ(456, configReport.annotation(0).field_int32()); + + ASSERT_EQ(2, configReport.matcher_stats_size()); + // matcher1 is the first in the list + if (configReport.matcher_stats(0).id() == StringToId("matcher1")) { + EXPECT_EQ(2, configReport.matcher_stats(0).matched_times()); + EXPECT_EQ(1, configReport.matcher_stats(1).matched_times()); + EXPECT_EQ(StringToId("matcher2"), configReport.matcher_stats(1).id()); + } else { + // matcher1 is the second in the list. + EXPECT_EQ(1, configReport.matcher_stats(0).matched_times()); + EXPECT_EQ(StringToId("matcher2"), configReport.matcher_stats(0).id()); + + EXPECT_EQ(2, configReport.matcher_stats(1).matched_times()); + EXPECT_EQ(StringToId("matcher1"), configReport.matcher_stats(1).id()); + } + + ASSERT_EQ(2, configReport.alert_stats_size()); + bool alert1first = configReport.alert_stats(0).id() == StringToId("alert1"); + EXPECT_EQ(StringToId("alert1"), configReport.alert_stats(alert1first ? 0 : 1).id()); + EXPECT_EQ(2, configReport.alert_stats(alert1first ? 0 : 1).alerted_times()); + EXPECT_EQ(StringToId("alert2"), configReport.alert_stats(alert1first ? 1 : 0).id()); + EXPECT_EQ(1, configReport.alert_stats(alert1first ? 1 : 0).alerted_times()); + + ASSERT_EQ(1, configReport.condition_stats_size()); + EXPECT_EQ(StringToId("condition1"), configReport.condition_stats(0).id()); + EXPECT_EQ(250, configReport.condition_stats(0).max_tuple_counts()); + + ASSERT_EQ(1, configReport.metric_stats_size()); + EXPECT_EQ(StringToId("metric1"), configReport.metric_stats(0).id()); + EXPECT_EQ(202, configReport.metric_stats(0).max_tuple_counts()); + + // after resetting the stats, some new events come + stats.noteMatcherMatched(key, StringToId("matcher99")); + stats.noteConditionDimensionSize(key, StringToId("condition99"), 300); + stats.noteMetricDimensionSize(key, StringToId("metric99tion99"), 270); + stats.noteAnomalyDeclared(key, StringToId("alert99")); + + // now the config stats should only contain the stats about the new event. + stats.dumpStats(&output, false); + good = report.ParseFromArray(&output[0], output.size()); + EXPECT_TRUE(good); + ASSERT_EQ(1, report.config_stats_size()); + const auto& configReport2 = report.config_stats(0); + ASSERT_EQ(1, configReport2.matcher_stats_size()); + EXPECT_EQ(StringToId("matcher99"), configReport2.matcher_stats(0).id()); + EXPECT_EQ(1, configReport2.matcher_stats(0).matched_times()); + + ASSERT_EQ(1, configReport2.condition_stats_size()); + EXPECT_EQ(StringToId("condition99"), configReport2.condition_stats(0).id()); + EXPECT_EQ(300, configReport2.condition_stats(0).max_tuple_counts()); + + ASSERT_EQ(1, configReport2.metric_stats_size()); + EXPECT_EQ(StringToId("metric99tion99"), configReport2.metric_stats(0).id()); + EXPECT_EQ(270, configReport2.metric_stats(0).max_tuple_counts()); + + ASSERT_EQ(1, configReport2.alert_stats_size()); + EXPECT_EQ(StringToId("alert99"), configReport2.alert_stats(0).id()); + EXPECT_EQ(1, configReport2.alert_stats(0).alerted_times()); +} + +TEST(StatsdStatsTest, TestAtomLog) { + StatsdStats stats; + time_t now = time(nullptr); + // old event, we get it from the stats buffer. should be ignored. + stats.noteAtomLogged(util::SENSOR_STATE_CHANGED, 1000); + + stats.noteAtomLogged(util::SENSOR_STATE_CHANGED, now + 1); + stats.noteAtomLogged(util::SENSOR_STATE_CHANGED, now + 2); + stats.noteAtomLogged(util::APP_CRASH_OCCURRED, now + 3); + + vector output; + stats.dumpStats(&output, false); + StatsdStatsReport report; + bool good = report.ParseFromArray(&output[0], output.size()); + EXPECT_TRUE(good); + + ASSERT_EQ(2, report.atom_stats_size()); + bool sensorAtomGood = false; + bool dropboxAtomGood = false; + + for (const auto& atomStats : report.atom_stats()) { + if (atomStats.tag() == util::SENSOR_STATE_CHANGED && atomStats.count() == 3) { + sensorAtomGood = true; + } + if (atomStats.tag() == util::APP_CRASH_OCCURRED && atomStats.count() == 1) { + dropboxAtomGood = true; + } + } + + EXPECT_TRUE(dropboxAtomGood); + EXPECT_TRUE(sensorAtomGood); +} + +TEST(StatsdStatsTest, TestNonPlatformAtomLog) { + StatsdStats stats; + time_t now = time(nullptr); + int newAtom1 = StatsdStats::kMaxPushedAtomId + 1; + int newAtom2 = StatsdStats::kMaxPushedAtomId + 2; + + stats.noteAtomLogged(newAtom1, now + 1); + stats.noteAtomLogged(newAtom1, now + 2); + stats.noteAtomLogged(newAtom2, now + 3); + + vector output; + stats.dumpStats(&output, false); + StatsdStatsReport report; + bool good = report.ParseFromArray(&output[0], output.size()); + EXPECT_TRUE(good); + + ASSERT_EQ(2, report.atom_stats_size()); + bool newAtom1Good = false; + bool newAtom2Good = false; + + for (const auto& atomStats : report.atom_stats()) { + if (atomStats.tag() == newAtom1 && atomStats.count() == 2) { + newAtom1Good = true; + } + if (atomStats.tag() == newAtom2 && atomStats.count() == 1) { + newAtom2Good = true; + } + } + + EXPECT_TRUE(newAtom1Good); + EXPECT_TRUE(newAtom2Good); +} + +TEST(StatsdStatsTest, TestPullAtomStats) { + StatsdStats stats; + + stats.updateMinPullIntervalSec(util::DISK_SPACE, 3333L); + stats.updateMinPullIntervalSec(util::DISK_SPACE, 2222L); + stats.updateMinPullIntervalSec(util::DISK_SPACE, 4444L); + + stats.notePull(util::DISK_SPACE); + stats.notePullTime(util::DISK_SPACE, 1111L); + stats.notePullDelay(util::DISK_SPACE, 1111L); + stats.notePull(util::DISK_SPACE); + stats.notePullTime(util::DISK_SPACE, 3333L); + stats.notePullDelay(util::DISK_SPACE, 3335L); + stats.notePull(util::DISK_SPACE); + stats.notePullFromCache(util::DISK_SPACE); + stats.notePullerCallbackRegistrationChanged(util::DISK_SPACE, true); + stats.notePullerCallbackRegistrationChanged(util::DISK_SPACE, false); + stats.notePullerCallbackRegistrationChanged(util::DISK_SPACE, true); + stats.notePullBinderCallFailed(util::DISK_SPACE); + stats.notePullUidProviderNotFound(util::DISK_SPACE); + stats.notePullerNotFound(util::DISK_SPACE); + stats.notePullerNotFound(util::DISK_SPACE); + stats.notePullTimeout(util::DISK_SPACE, 3000L, 6000L); + stats.notePullTimeout(util::DISK_SPACE, 4000L, 7000L); + + vector output; + stats.dumpStats(&output, false); + StatsdStatsReport report; + bool good = report.ParseFromArray(&output[0], output.size()); + EXPECT_TRUE(good); + + ASSERT_EQ(1, report.pulled_atom_stats_size()); + + EXPECT_EQ(util::DISK_SPACE, report.pulled_atom_stats(0).atom_id()); + EXPECT_EQ(3, report.pulled_atom_stats(0).total_pull()); + EXPECT_EQ(1, report.pulled_atom_stats(0).total_pull_from_cache()); + EXPECT_EQ(2222L, report.pulled_atom_stats(0).min_pull_interval_sec()); + EXPECT_EQ(2222L, report.pulled_atom_stats(0).average_pull_time_nanos()); + EXPECT_EQ(3333L, report.pulled_atom_stats(0).max_pull_time_nanos()); + EXPECT_EQ(2223L, report.pulled_atom_stats(0).average_pull_delay_nanos()); + EXPECT_EQ(3335L, report.pulled_atom_stats(0).max_pull_delay_nanos()); + EXPECT_EQ(2L, report.pulled_atom_stats(0).registered_count()); + EXPECT_EQ(1L, report.pulled_atom_stats(0).unregistered_count()); + EXPECT_EQ(1L, report.pulled_atom_stats(0).binder_call_failed()); + EXPECT_EQ(1L, report.pulled_atom_stats(0).failed_uid_provider_not_found()); + EXPECT_EQ(2L, report.pulled_atom_stats(0).puller_not_found()); + ASSERT_EQ(2, report.pulled_atom_stats(0).pull_atom_metadata_size()); + EXPECT_EQ(3000L, report.pulled_atom_stats(0).pull_atom_metadata(0).pull_timeout_uptime_millis()); + EXPECT_EQ(4000L, report.pulled_atom_stats(0).pull_atom_metadata(1).pull_timeout_uptime_millis()); + EXPECT_EQ(6000L, report.pulled_atom_stats(0).pull_atom_metadata(0) + .pull_timeout_elapsed_millis()); + EXPECT_EQ(7000L, report.pulled_atom_stats(0).pull_atom_metadata(1) + .pull_timeout_elapsed_millis()); +} + +TEST(StatsdStatsTest, TestAtomMetricsStats) { + StatsdStats stats; + time_t now = time(nullptr); + // old event, we get it from the stats buffer. should be ignored. + stats.noteBucketDropped(10000000000LL); + + stats.noteLateLogEvent(10000000000LL, 10L); + stats.noteLateLogEvent(10000000000LL, 50L); + + stats.noteBucketBoundaryDelayNs(10000000000LL, -1L); + stats.noteBucketBoundaryDelayNs(10000000000LL, -10L); + stats.noteBucketBoundaryDelayNs(10000000000LL, 2L); + + stats.noteBucketBoundaryDelayNs(10000000001LL, 1L); + + vector output; + stats.dumpStats(&output, false); + StatsdStatsReport report; + bool good = report.ParseFromArray(&output[0], output.size()); + EXPECT_TRUE(good); + + ASSERT_EQ(2, report.atom_metric_stats().size()); + + auto atomStats = report.atom_metric_stats(0); + EXPECT_EQ(10000000000LL, atomStats.metric_id()); + EXPECT_EQ(1L, atomStats.bucket_dropped()); + EXPECT_EQ(-10L, atomStats.min_bucket_boundary_delay_ns()); + EXPECT_EQ(2L, atomStats.max_bucket_boundary_delay_ns()); + EXPECT_EQ(2L, atomStats.late_log_event()); + EXPECT_EQ(60L, atomStats.sum_late_log_event_extra_duration_ns()); + EXPECT_EQ(50L, atomStats.max_late_log_event_extra_duration_ns()); + + auto atomStats2 = report.atom_metric_stats(1); + EXPECT_EQ(10000000001LL, atomStats2.metric_id()); + EXPECT_EQ(0L, atomStats2.bucket_dropped()); + EXPECT_EQ(0L, atomStats2.min_bucket_boundary_delay_ns()); + EXPECT_EQ(1L, atomStats2.max_bucket_boundary_delay_ns()); + EXPECT_EQ(0L, atomStats2.late_log_event()); + EXPECT_EQ(0L, atomStats2.sum_late_log_event_extra_duration_ns()); + EXPECT_EQ(0L, atomStats2.max_late_log_event_extra_duration_ns()); +} + +TEST(StatsdStatsTest, TestAnomalyMonitor) { + StatsdStats stats; + stats.noteRegisteredAnomalyAlarmChanged(); + stats.noteRegisteredAnomalyAlarmChanged(); + + vector output; + stats.dumpStats(&output, false); + StatsdStatsReport report; + bool good = report.ParseFromArray(&output[0], output.size()); + EXPECT_TRUE(good); + + EXPECT_EQ(2, report.anomaly_alarm_stats().alarms_registered()); +} + +TEST(StatsdStatsTest, TestTimestampThreshold) { + StatsdStats stats; + vector timestamps; + for (int i = 0; i < StatsdStats::kMaxTimestampCount; i++) { + timestamps.push_back(i); + } + ConfigKey key(0, 12345); + stats.noteConfigReceived(key, 2, 3, 4, 5, {}, true); + + for (int i = 0; i < StatsdStats::kMaxTimestampCount; i++) { + stats.noteDataDropped(key, timestamps[i]); + stats.noteBroadcastSent(key, timestamps[i]); + stats.noteMetricsReportSent(key, 0, timestamps[i]); + stats.noteActiveStatusChanged(key, true, timestamps[i]); + stats.noteActiveStatusChanged(key, false, timestamps[i]); + } + + int32_t newTimestamp = 10000; + + // now it should trigger removing oldest timestamp + stats.noteDataDropped(key, 123, 10000); + stats.noteBroadcastSent(key, 10000); + stats.noteMetricsReportSent(key, 0, 10000); + stats.noteActiveStatusChanged(key, true, 10000); + stats.noteActiveStatusChanged(key, false, 10000); + + EXPECT_TRUE(stats.mConfigStats.find(key) != stats.mConfigStats.end()); + const auto& configStats = stats.mConfigStats[key]; + + size_t maxCount = StatsdStats::kMaxTimestampCount; + ASSERT_EQ(maxCount, configStats->broadcast_sent_time_sec.size()); + ASSERT_EQ(maxCount, configStats->data_drop_time_sec.size()); + ASSERT_EQ(maxCount, configStats->dump_report_stats.size()); + ASSERT_EQ(maxCount, configStats->activation_time_sec.size()); + ASSERT_EQ(maxCount, configStats->deactivation_time_sec.size()); + + // the oldest timestamp is the second timestamp in history + EXPECT_EQ(1, configStats->broadcast_sent_time_sec.front()); + EXPECT_EQ(1, configStats->data_drop_bytes.front()); + EXPECT_EQ(1, configStats->dump_report_stats.front().first); + EXPECT_EQ(1, configStats->activation_time_sec.front()); + EXPECT_EQ(1, configStats->deactivation_time_sec.front()); + + // the last timestamp is the newest timestamp. + EXPECT_EQ(newTimestamp, configStats->broadcast_sent_time_sec.back()); + EXPECT_EQ(newTimestamp, configStats->data_drop_time_sec.back()); + EXPECT_EQ(123, configStats->data_drop_bytes.back()); + EXPECT_EQ(newTimestamp, configStats->dump_report_stats.back().first); + EXPECT_EQ(newTimestamp, configStats->activation_time_sec.back()); + EXPECT_EQ(newTimestamp, configStats->deactivation_time_sec.back()); +} + +TEST(StatsdStatsTest, TestSystemServerCrash) { + StatsdStats stats; + vector timestamps; + for (int i = 0; i < StatsdStats::kMaxSystemServerRestarts; i++) { + timestamps.push_back(i); + stats.noteSystemServerRestart(timestamps[i]); + } + vector output; + stats.dumpStats(&output, false); + StatsdStatsReport report; + EXPECT_TRUE(report.ParseFromArray(&output[0], output.size())); + const int maxCount = StatsdStats::kMaxSystemServerRestarts; + ASSERT_EQ(maxCount, (int)report.system_restart_sec_size()); + + stats.noteSystemServerRestart(StatsdStats::kMaxSystemServerRestarts + 1); + output.clear(); + stats.dumpStats(&output, false); + EXPECT_TRUE(report.ParseFromArray(&output[0], output.size())); + ASSERT_EQ(maxCount, (int)report.system_restart_sec_size()); + EXPECT_EQ(StatsdStats::kMaxSystemServerRestarts + 1, report.system_restart_sec(maxCount - 1)); +} + +TEST(StatsdStatsTest, TestActivationBroadcastGuardrailHit) { + StatsdStats stats; + int uid1 = 1; + int uid2 = 2; + stats.noteActivationBroadcastGuardrailHit(uid1, 10); + stats.noteActivationBroadcastGuardrailHit(uid1, 20); + + // Test that we only keep 20 timestamps. + for (int i = 0; i < 100; i++) { + stats.noteActivationBroadcastGuardrailHit(uid2, i); + } + + vector output; + stats.dumpStats(&output, false); + StatsdStatsReport report; + EXPECT_TRUE(report.ParseFromArray(&output[0], output.size())); + + ASSERT_EQ(2, report.activation_guardrail_stats_size()); + bool uid1Good = false; + bool uid2Good = false; + for (const auto& guardrailTimes : report.activation_guardrail_stats()) { + if (uid1 == guardrailTimes.uid()) { + uid1Good = true; + ASSERT_EQ(2, guardrailTimes.guardrail_met_sec_size()); + EXPECT_EQ(10, guardrailTimes.guardrail_met_sec(0)); + EXPECT_EQ(20, guardrailTimes.guardrail_met_sec(1)); + } else if (uid2 == guardrailTimes.uid()) { + int maxCount = StatsdStats::kMaxTimestampCount; + uid2Good = true; + ASSERT_EQ(maxCount, guardrailTimes.guardrail_met_sec_size()); + for (int i = 0; i < maxCount; i++) { + EXPECT_EQ(100 - maxCount + i, guardrailTimes.guardrail_met_sec(i)); + } + } else { + FAIL() << "Unexpected uid."; + } + } + EXPECT_TRUE(uid1Good); + EXPECT_TRUE(uid2Good); +} + +TEST(StatsdStatsTest, TestAtomErrorStats) { + StatsdStats stats; + + int pushAtomTag = 100; + int pullAtomTag = 1000; + int numErrors = 10; + + for (int i = 0; i < numErrors; i++) { + // We must call noteAtomLogged as well because only those pushed atoms + // that have been logged will have stats printed about them in the + // proto. + stats.noteAtomLogged(pushAtomTag, /*timeSec=*/0); + stats.noteAtomError(pushAtomTag, /*pull=*/false); + + stats.noteAtomError(pullAtomTag, /*pull=*/true); + } + + vector output; + stats.dumpStats(&output, false); + StatsdStatsReport report; + EXPECT_TRUE(report.ParseFromArray(&output[0], output.size())); + + // Check error count = numErrors for push atom + ASSERT_EQ(1, report.atom_stats_size()); + const auto& pushedAtomStats = report.atom_stats(0); + EXPECT_EQ(pushAtomTag, pushedAtomStats.tag()); + EXPECT_EQ(numErrors, pushedAtomStats.error_count()); + + // Check error count = numErrors for pull atom + ASSERT_EQ(1, report.pulled_atom_stats_size()); + const auto& pulledAtomStats = report.pulled_atom_stats(0); + EXPECT_EQ(pullAtomTag, pulledAtomStats.atom_id()); + EXPECT_EQ(numErrors, pulledAtomStats.atom_error_count()); +} + +} // namespace statsd +} // namespace os +} // namespace android +#else +GTEST_LOG_(INFO) << "This test does nothing.\n"; +#endif diff --git a/statsd/tests/indexed_priority_queue_test.cpp b/statsd/tests/indexed_priority_queue_test.cpp new file mode 100644 index 00000000..3a654565 --- /dev/null +++ b/statsd/tests/indexed_priority_queue_test.cpp @@ -0,0 +1,235 @@ +/* + * 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 "src/anomaly/indexed_priority_queue.h" + +#include + +using namespace android::os::statsd; + +/** struct for template in indexed_priority_queue */ +struct AATest : public RefBase { + AATest(uint32_t val, std::string a, std::string b) : val(val), a(a), b(b) { + } + + const int val; + const std::string a; + const std::string b; + + struct Smaller { + bool operator()(const sp a, const sp b) const { + return (a->val < b->val); + } + }; +}; + +#ifdef __ANDROID__ +TEST(indexed_priority_queue, empty_and_size) { + std::string emptyMetricId; + std::string emptyDimensionId; + indexed_priority_queue ipq; + sp aa4 = new AATest{4, emptyMetricId, emptyDimensionId}; + sp aa8 = new AATest{8, emptyMetricId, emptyDimensionId}; + + ASSERT_EQ(0u, ipq.size()); + EXPECT_TRUE(ipq.empty()); + + ipq.push(aa4); + ASSERT_EQ(1u, ipq.size()); + EXPECT_FALSE(ipq.empty()); + + ipq.push(aa8); + ASSERT_EQ(2u, ipq.size()); + EXPECT_FALSE(ipq.empty()); + + ipq.remove(aa4); + ASSERT_EQ(1u, ipq.size()); + EXPECT_FALSE(ipq.empty()); + + ipq.remove(aa8); + ASSERT_EQ(0u, ipq.size()); + EXPECT_TRUE(ipq.empty()); +} + +TEST(indexed_priority_queue, top) { + std::string emptyMetricId; + std::string emptyDimensionId; + indexed_priority_queue ipq; + sp aa2 = new AATest{2, emptyMetricId, emptyDimensionId}; + sp aa4 = new AATest{4, emptyMetricId, emptyDimensionId}; + sp aa8 = new AATest{8, emptyMetricId, emptyDimensionId}; + sp aa12 = new AATest{12, emptyMetricId, emptyDimensionId}; + sp aa16 = new AATest{16, emptyMetricId, emptyDimensionId}; + sp aa20 = new AATest{20, emptyMetricId, emptyDimensionId}; + + EXPECT_EQ(ipq.top(), nullptr); + + // add 8, 4, 12 + ipq.push(aa8); + EXPECT_EQ(ipq.top(), aa8); + + ipq.push(aa12); + EXPECT_EQ(ipq.top(), aa8); + + ipq.push(aa4); + EXPECT_EQ(ipq.top(), aa4); + + // remove 12, 4 + ipq.remove(aa12); + EXPECT_EQ(ipq.top(), aa4); + + ipq.remove(aa4); + EXPECT_EQ(ipq.top(), aa8); + + // add 16, 2, 20 + ipq.push(aa16); + EXPECT_EQ(ipq.top(), aa8); + + ipq.push(aa2); + EXPECT_EQ(ipq.top(), aa2); + + ipq.push(aa20); + EXPECT_EQ(ipq.top(), aa2); + + // remove 2, 20, 16, 8 + ipq.remove(aa2); + EXPECT_EQ(ipq.top(), aa8); + + ipq.remove(aa20); + EXPECT_EQ(ipq.top(), aa8); + + ipq.remove(aa16); + EXPECT_EQ(ipq.top(), aa8); + + ipq.remove(aa8); + EXPECT_EQ(ipq.top(), nullptr); +} + +TEST(indexed_priority_queue, push_same_aa) { + std::string emptyMetricId; + std::string emptyDimensionId; + indexed_priority_queue ipq; + sp aa4_a = new AATest{4, emptyMetricId, emptyDimensionId}; + sp aa4_b = new AATest{4, emptyMetricId, emptyDimensionId}; + + ipq.push(aa4_a); + ASSERT_EQ(1u, ipq.size()); + EXPECT_TRUE(ipq.contains(aa4_a)); + EXPECT_FALSE(ipq.contains(aa4_b)); + + ipq.push(aa4_a); + ASSERT_EQ(1u, ipq.size()); + EXPECT_TRUE(ipq.contains(aa4_a)); + EXPECT_FALSE(ipq.contains(aa4_b)); + + ipq.push(aa4_b); + ASSERT_EQ(2u, ipq.size()); + EXPECT_TRUE(ipq.contains(aa4_a)); + EXPECT_TRUE(ipq.contains(aa4_b)); +} + +TEST(indexed_priority_queue, remove_nonexistant) { + std::string emptyMetricId; + std::string emptyDimensionId; + indexed_priority_queue ipq; + sp aa4 = new AATest{4, emptyMetricId, emptyDimensionId}; + sp aa5 = new AATest{5, emptyMetricId, emptyDimensionId}; + + ipq.push(aa4); + ipq.remove(aa5); + ASSERT_EQ(1u, ipq.size()); + EXPECT_TRUE(ipq.contains(aa4)); + EXPECT_FALSE(ipq.contains(aa5)); +} + +TEST(indexed_priority_queue, remove_same_aa) { + indexed_priority_queue ipq; + std::string emptyMetricId; + std::string emptyDimensionId; + sp aa4_a = new AATest{4, emptyMetricId, emptyDimensionId}; + sp aa4_b = new AATest{4, emptyMetricId, emptyDimensionId}; + + ipq.push(aa4_a); + ipq.push(aa4_b); + ASSERT_EQ(2u, ipq.size()); + EXPECT_TRUE(ipq.contains(aa4_a)); + EXPECT_TRUE(ipq.contains(aa4_b)); + + ipq.remove(aa4_b); + ASSERT_EQ(1u, ipq.size()); + EXPECT_TRUE(ipq.contains(aa4_a)); + EXPECT_FALSE(ipq.contains(aa4_b)); + + ipq.remove(aa4_a); + ASSERT_EQ(0u, ipq.size()); + EXPECT_FALSE(ipq.contains(aa4_a)); + EXPECT_FALSE(ipq.contains(aa4_b)); +} + +TEST(indexed_priority_queue, nulls) { + indexed_priority_queue ipq; + + EXPECT_TRUE(ipq.empty()); + EXPECT_FALSE(ipq.contains(nullptr)); + + ipq.push(nullptr); + EXPECT_TRUE(ipq.empty()); + EXPECT_FALSE(ipq.contains(nullptr)); + + ipq.remove(nullptr); + EXPECT_TRUE(ipq.empty()); + EXPECT_FALSE(ipq.contains(nullptr)); +} + +TEST(indexed_priority_queue, pop) { + indexed_priority_queue ipq; + std::string emptyMetricId; + std::string emptyDimensionId; + sp a = new AATest{1, emptyMetricId, emptyDimensionId}; + sp b = new AATest{2, emptyMetricId, emptyDimensionId}; + sp c = new AATest{3, emptyMetricId, emptyDimensionId}; + + ipq.push(c); + ipq.push(b); + ipq.push(a); + ASSERT_EQ(3u, ipq.size()); + + ipq.pop(); + ASSERT_EQ(2u, ipq.size()); + EXPECT_FALSE(ipq.contains(a)); + EXPECT_TRUE(ipq.contains(b)); + EXPECT_TRUE(ipq.contains(c)); + + ipq.pop(); + ASSERT_EQ(1u, ipq.size()); + EXPECT_FALSE(ipq.contains(a)); + EXPECT_FALSE(ipq.contains(b)); + EXPECT_TRUE(ipq.contains(c)); + + ipq.pop(); + ASSERT_EQ(0u, ipq.size()); + EXPECT_FALSE(ipq.contains(a)); + EXPECT_FALSE(ipq.contains(b)); + EXPECT_FALSE(ipq.contains(c)); + EXPECT_TRUE(ipq.empty()); + + ipq.pop(); // pop an empty queue + EXPECT_TRUE(ipq.empty()); +} + +#else +GTEST_LOG_(INFO) << "This test does nothing.\n"; +#endif diff --git a/statsd/tests/log_event/LogEventQueue_test.cpp b/statsd/tests/log_event/LogEventQueue_test.cpp new file mode 100644 index 00000000..a15f95be --- /dev/null +++ b/statsd/tests/log_event/LogEventQueue_test.cpp @@ -0,0 +1,115 @@ +// Copyright (C) 2019 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 "logd/LogEventQueue.h" + +#include +#include +#include + +#include + +#include "stats_event.h" +#include "tests/statsd_test_util.h" + +namespace android { +namespace os { +namespace statsd { + +using namespace android; +using namespace testing; + +using std::unique_ptr; + +namespace { + +std::unique_ptr makeLogEvent(uint64_t timestampNs) { + AStatsEvent* statsEvent = AStatsEvent_obtain(); + AStatsEvent_setAtomId(statsEvent, 10); + AStatsEvent_overwriteTimestamp(statsEvent, timestampNs); + + std::unique_ptr logEvent = std::make_unique(/*uid=*/0, /*pid=*/0); + parseStatsEventToLogEvent(statsEvent, logEvent.get()); + return logEvent; +} + +} // anonymous namespace + +#ifdef __ANDROID__ +TEST(LogEventQueue_test, TestGoodConsumer) { + LogEventQueue queue(50); + int64_t timeBaseNs = 100; + std::thread writer([&queue, timeBaseNs] { + for (int i = 0; i < 100; i++) { + int64_t oldestEventNs; + bool success = queue.push(makeLogEvent(timeBaseNs + i * 1000), &oldestEventNs); + EXPECT_TRUE(success); + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + } + }); + + std::thread reader([&queue, timeBaseNs] { + for (int i = 0; i < 100; i++) { + auto event = queue.waitPop(); + EXPECT_TRUE(event != nullptr); + // All events are in right order. + EXPECT_EQ(timeBaseNs + i * 1000, event->GetElapsedTimestampNs()); + } + }); + + reader.join(); + writer.join(); +} + +TEST(LogEventQueue_test, TestSlowConsumer) { + LogEventQueue queue(50); + int64_t timeBaseNs = 100; + std::thread writer([&queue, timeBaseNs] { + int failure_count = 0; + int64_t oldestEventNs; + for (int i = 0; i < 100; i++) { + bool success = queue.push(makeLogEvent(timeBaseNs + i * 1000), &oldestEventNs); + if (!success) failure_count++; + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + } + + // There is some remote chance that reader thread not get chance to run before writer thread + // ends. That's why the following comparison is not "==". + // There will be at least 45 events lost due to overflow. + EXPECT_TRUE(failure_count >= 45); + // The oldest event must be at least the 6th event. + EXPECT_TRUE(oldestEventNs <= (100 + 5 * 1000)); + }); + + std::thread reader([&queue, timeBaseNs] { + // The consumer quickly processed 5 events, then it got stuck (not reading anymore). + for (int i = 0; i < 5; i++) { + auto event = queue.waitPop(); + EXPECT_TRUE(event != nullptr); + // All events are in right order. + EXPECT_EQ(timeBaseNs + i * 1000, event->GetElapsedTimestampNs()); + } + }); + + reader.join(); + writer.join(); +} + +#else +GTEST_LOG_(INFO) << "This test does nothing.\n"; +#endif + +} // namespace statsd +} // namespace os +} // namespace android diff --git a/statsd/tests/metadata_util_test.cpp b/statsd/tests/metadata_util_test.cpp new file mode 100644 index 00000000..7707890c --- /dev/null +++ b/statsd/tests/metadata_util_test.cpp @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2020 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 "metadata_util.h" +#include "tests/statsd_test_util.h" + +#ifdef __ANDROID__ + +namespace android { +namespace os { +namespace statsd { + +TEST(MetadataUtilTest, TestWriteAndReadMetricDimensionKey) { + HashableDimensionKey dim; + HashableDimensionKey dim2; + int pos1[] = {1, 1, 1}; + int pos2[] = {1, 1, 2}; + int pos3[] = {1, 1, 3}; + int pos4[] = {2, 0, 0}; + Field field1(10, pos1, 2); + Field field2(10, pos2, 2); + Field field3(10, pos3, 2); + Field field4(10, pos4, 0); + + Value value1((int32_t)10025); + Value value2("tag"); + Value value3((int32_t)987654); + Value value4((int32_t)99999); + + dim.addValue(FieldValue(field1, value1)); + dim.addValue(FieldValue(field2, value2)); + dim.addValue(FieldValue(field3, value3)); + dim.addValue(FieldValue(field4, value4)); + + dim2.addValue(FieldValue(field1, value1)); + dim2.addValue(FieldValue(field2, value2)); + + MetricDimensionKey dimKey(dim, dim2); + + metadata::MetricDimensionKey metadataDimKey; + writeMetricDimensionKeyToMetadataDimensionKey(dimKey, &metadataDimKey); + + MetricDimensionKey loadedDimKey = loadMetricDimensionKeyFromProto(metadataDimKey); + + ASSERT_EQ(loadedDimKey, dimKey); + ASSERT_EQ(std::hash{}(loadedDimKey), + std::hash{}(dimKey)); +} + +} // namespace statsd +} // namespace os +} // namespace android +#else +GTEST_LOG_(INFO) << "This test does nothing.\n"; +#endif diff --git a/statsd/tests/metrics/CountMetricProducer_test.cpp b/statsd/tests/metrics/CountMetricProducer_test.cpp new file mode 100644 index 00000000..aab29f21 --- /dev/null +++ b/statsd/tests/metrics/CountMetricProducer_test.cpp @@ -0,0 +1,479 @@ +// 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 "src/metrics/CountMetricProducer.h" + +#include +#include +#include +#include + +#include + +#include "metrics_test_helper.h" +#include "src/stats_log_util.h" +#include "stats_event.h" +#include "tests/statsd_test_util.h" + +using namespace testing; +using android::sp; +using std::set; +using std::unordered_map; +using std::vector; + +#ifdef __ANDROID__ + +namespace android { +namespace os { +namespace statsd { + + +namespace { +const ConfigKey kConfigKey(0, 12345); +const uint64_t protoHash = 0x1234567890; + +void makeLogEvent(LogEvent* logEvent, int64_t timestampNs, int atomId) { + AStatsEvent* statsEvent = AStatsEvent_obtain(); + AStatsEvent_setAtomId(statsEvent, atomId); + AStatsEvent_overwriteTimestamp(statsEvent, timestampNs); + + parseStatsEventToLogEvent(statsEvent, logEvent); +} + +void makeLogEvent(LogEvent* logEvent, int64_t timestampNs, int atomId, string uid) { + AStatsEvent* statsEvent = AStatsEvent_obtain(); + AStatsEvent_setAtomId(statsEvent, atomId); + AStatsEvent_overwriteTimestamp(statsEvent, timestampNs); + AStatsEvent_writeString(statsEvent, uid.c_str()); + + parseStatsEventToLogEvent(statsEvent, logEvent); +} + +} // namespace + +// Setup for parameterized tests. +class CountMetricProducerTest_PartialBucket : public TestWithParam {}; + +INSTANTIATE_TEST_SUITE_P(CountMetricProducerTest_PartialBucket, + CountMetricProducerTest_PartialBucket, + testing::Values(APP_UPGRADE, BOOT_COMPLETE)); + +TEST(CountMetricProducerTest, TestFirstBucket) { + CountMetric metric; + metric.set_id(1); + metric.set_bucket(ONE_MINUTE); + sp wizard = new NaggyMock(); + + CountMetricProducer countProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, {}, + wizard, protoHash, 5, 600 * NS_PER_SEC + NS_PER_SEC / 2); + EXPECT_EQ(600500000000, countProducer.mCurrentBucketStartTimeNs); + EXPECT_EQ(10, countProducer.mCurrentBucketNum); + EXPECT_EQ(660000000005, countProducer.getCurrentBucketEndTimeNs()); +} + +TEST(CountMetricProducerTest, TestNonDimensionalEvents) { + int64_t bucketStartTimeNs = 10000000000; + int64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(ONE_MINUTE) * 1000000LL; + int64_t bucket2StartTimeNs = bucketStartTimeNs + bucketSizeNs; + int64_t bucket3StartTimeNs = bucketStartTimeNs + 2 * bucketSizeNs; + int tagId = 1; + + CountMetric metric; + metric.set_id(1); + metric.set_bucket(ONE_MINUTE); + + sp wizard = new NaggyMock(); + + CountMetricProducer countProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, {}, + wizard, protoHash, bucketStartTimeNs, bucketStartTimeNs); + + // 2 events in bucket 1. + LogEvent event1(/*uid=*/0, /*pid=*/0); + makeLogEvent(&event1, bucketStartTimeNs + 1, tagId); + LogEvent event2(/*uid=*/0, /*pid=*/0); + makeLogEvent(&event2, bucketStartTimeNs + 2, tagId); + + countProducer.onMatchedLogEvent(1 /*log matcher index*/, event1); + countProducer.onMatchedLogEvent(1 /*log matcher index*/, event2); + + // Flushes at event #2. + countProducer.flushIfNeededLocked(bucketStartTimeNs + 2); + ASSERT_EQ(0UL, countProducer.mPastBuckets.size()); + + // Flushes. + countProducer.flushIfNeededLocked(bucketStartTimeNs + bucketSizeNs + 1); + ASSERT_EQ(1UL, countProducer.mPastBuckets.size()); + EXPECT_TRUE(countProducer.mPastBuckets.find(DEFAULT_METRIC_DIMENSION_KEY) != + countProducer.mPastBuckets.end()); + const auto& buckets = countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY]; + ASSERT_EQ(1UL, buckets.size()); + EXPECT_EQ(bucketStartTimeNs, buckets[0].mBucketStartNs); + EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, buckets[0].mBucketEndNs); + EXPECT_EQ(2LL, buckets[0].mCount); + + // 1 matched event happens in bucket 2. + LogEvent event3(/*uid=*/0, /*pid=*/0); + makeLogEvent(&event3, bucketStartTimeNs + bucketSizeNs + 2, tagId); + + countProducer.onMatchedLogEvent(1 /*log matcher index*/, event3); + + countProducer.flushIfNeededLocked(bucketStartTimeNs + 2 * bucketSizeNs + 1); + ASSERT_EQ(1UL, countProducer.mPastBuckets.size()); + EXPECT_TRUE(countProducer.mPastBuckets.find(DEFAULT_METRIC_DIMENSION_KEY) != + countProducer.mPastBuckets.end()); + ASSERT_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); + + // nothing happens in bucket 3. we should not record anything for bucket 3. + countProducer.flushIfNeededLocked(bucketStartTimeNs + 3 * bucketSizeNs + 1); + ASSERT_EQ(1UL, countProducer.mPastBuckets.size()); + EXPECT_TRUE(countProducer.mPastBuckets.find(DEFAULT_METRIC_DIMENSION_KEY) != + countProducer.mPastBuckets.end()); + const auto& buckets3 = countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY]; + ASSERT_EQ(2UL, buckets3.size()); +} + +TEST(CountMetricProducerTest, TestEventsWithNonSlicedCondition) { + int64_t bucketStartTimeNs = 10000000000; + int64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(ONE_MINUTE) * 1000000LL; + + CountMetric metric; + metric.set_id(1); + metric.set_bucket(ONE_MINUTE); + metric.set_condition(StringToId("SCREEN_ON")); + + sp wizard = new NaggyMock(); + + CountMetricProducer countProducer(kConfigKey, metric, 0, {ConditionState::kUnknown}, wizard, + protoHash, bucketStartTimeNs, bucketStartTimeNs); + + countProducer.onConditionChanged(true, bucketStartTimeNs); + + LogEvent event1(/*uid=*/0, /*pid=*/0); + makeLogEvent(&event1, bucketStartTimeNs + 1, /*atomId=*/1); + countProducer.onMatchedLogEvent(1 /*matcher index*/, event1); + + ASSERT_EQ(0UL, countProducer.mPastBuckets.size()); + + countProducer.onConditionChanged(false /*new condition*/, bucketStartTimeNs + 2); + + // Upon this match event, the matched event1 is flushed. + LogEvent event2(/*uid=*/0, /*pid=*/0); + makeLogEvent(&event2, bucketStartTimeNs + 10, /*atomId=*/1); + countProducer.onMatchedLogEvent(1 /*matcher index*/, event2); + ASSERT_EQ(0UL, countProducer.mPastBuckets.size()); + + countProducer.flushIfNeededLocked(bucketStartTimeNs + bucketSizeNs + 1); + ASSERT_EQ(1UL, countProducer.mPastBuckets.size()); + EXPECT_TRUE(countProducer.mPastBuckets.find(DEFAULT_METRIC_DIMENSION_KEY) != + countProducer.mPastBuckets.end()); + + const auto& buckets = countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY]; + ASSERT_EQ(1UL, buckets.size()); + const auto& bucketInfo = buckets[0]; + EXPECT_EQ(bucketStartTimeNs, bucketInfo.mBucketStartNs); + EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, bucketInfo.mBucketEndNs); + EXPECT_EQ(1LL, bucketInfo.mCount); +} + +TEST(CountMetricProducerTest, TestEventsWithSlicedCondition) { + int64_t bucketStartTimeNs = 10000000000; + int64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(ONE_MINUTE) * 1000000LL; + + int tagId = 1; + int conditionTagId = 2; + + CountMetric metric; + metric.set_id(1); + metric.set_bucket(ONE_MINUTE); + metric.set_condition(StringToId("APP_IN_BACKGROUND_PER_UID_AND_SCREEN_ON")); + MetricConditionLink* link = metric.add_links(); + link->set_condition(StringToId("APP_IN_BACKGROUND_PER_UID")); + buildSimpleAtomFieldMatcher(tagId, 1, link->mutable_fields_in_what()); + buildSimpleAtomFieldMatcher(conditionTagId, 2, link->mutable_fields_in_condition()); + + LogEvent event1(/*uid=*/0, /*pid=*/0); + makeLogEvent(&event1, bucketStartTimeNs + 1, tagId, /*uid=*/"111"); + + LogEvent event2(/*uid=*/0, /*pid=*/0); + makeLogEvent(&event2, bucketStartTimeNs + 10, tagId, /*uid=*/"222"); + + ConditionKey key1; + key1[StringToId("APP_IN_BACKGROUND_PER_UID")] = { + getMockedDimensionKey(conditionTagId, 2, "111")}; + + ConditionKey key2; + 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(_, key2, _)).WillOnce(Return(ConditionState::kTrue)); + + CountMetricProducer countProducer(kConfigKey, metric, 0 /*condition tracker index*/, + {ConditionState::kUnknown}, wizard, protoHash, + bucketStartTimeNs, bucketStartTimeNs); + + countProducer.onMatchedLogEvent(1 /*log matcher index*/, event1); + countProducer.flushIfNeededLocked(bucketStartTimeNs + 1); + ASSERT_EQ(0UL, countProducer.mPastBuckets.size()); + + countProducer.onMatchedLogEvent(1 /*log matcher index*/, event2); + countProducer.flushIfNeededLocked(bucketStartTimeNs + bucketSizeNs + 1); + ASSERT_EQ(1UL, countProducer.mPastBuckets.size()); + EXPECT_TRUE(countProducer.mPastBuckets.find(DEFAULT_METRIC_DIMENSION_KEY) != + countProducer.mPastBuckets.end()); + const auto& buckets = countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY]; + ASSERT_EQ(1UL, buckets.size()); + const auto& bucketInfo = buckets[0]; + EXPECT_EQ(bucketStartTimeNs, bucketInfo.mBucketStartNs); + EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, bucketInfo.mBucketEndNs); + EXPECT_EQ(1LL, bucketInfo.mCount); +} + +TEST_P(CountMetricProducerTest_PartialBucket, TestSplitInCurrentBucket) { + sp alarmMonitor; + int64_t bucketStartTimeNs = 10000000000; + int64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(ONE_MINUTE) * 1000000LL; + int64_t eventTimeNs = bucketStartTimeNs + 15 * NS_PER_SEC; + + int tagId = 1; + int conditionTagId = 2; + + CountMetric metric; + metric.set_id(1); + metric.set_bucket(ONE_MINUTE); + Alert alert; + alert.set_num_buckets(3); + alert.set_trigger_if_sum_gt(2); + + sp wizard = new NaggyMock(); + + CountMetricProducer countProducer(kConfigKey, metric, -1 /* no condition */, {}, wizard, + protoHash, bucketStartTimeNs, bucketStartTimeNs); + + sp anomalyTracker = + countProducer.addAnomalyTracker(alert, alarmMonitor, UPDATE_NEW, bucketStartTimeNs); + EXPECT_TRUE(anomalyTracker != nullptr); + + // Bucket is not flushed yet. + LogEvent event1(/*uid=*/0, /*pid=*/0); + makeLogEvent(&event1, bucketStartTimeNs + 1, tagId, /*uid=*/"111"); + countProducer.onMatchedLogEvent(1 /*log matcher index*/, event1); + ASSERT_EQ(0UL, countProducer.mPastBuckets.size()); + EXPECT_EQ(0, anomalyTracker->getSumOverPastBuckets(DEFAULT_METRIC_DIMENSION_KEY)); + + // App upgrade or boot complete forces bucket flush. + // Check that there's a past bucket and the bucket end is not adjusted. + switch (GetParam()) { + case APP_UPGRADE: + countProducer.notifyAppUpgrade(eventTimeNs); + break; + case BOOT_COMPLETE: + countProducer.onStatsdInitCompleted(eventTimeNs); + break; + } + ASSERT_EQ(1UL, countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size()); + EXPECT_EQ(bucketStartTimeNs, + countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY][0].mBucketStartNs); + EXPECT_EQ(eventTimeNs, + countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY][0].mBucketEndNs); + EXPECT_EQ(0, countProducer.getCurrentBucketNum()); + EXPECT_EQ(eventTimeNs, countProducer.mCurrentBucketStartTimeNs); + // Anomaly tracker only contains full buckets. + EXPECT_EQ(0, anomalyTracker->getSumOverPastBuckets(DEFAULT_METRIC_DIMENSION_KEY)); + + int64_t lastEndTimeNs = countProducer.getCurrentBucketEndTimeNs(); + // Next event occurs in same bucket as partial bucket created. + LogEvent event2(/*uid=*/0, /*pid=*/0); + makeLogEvent(&event2, bucketStartTimeNs + 59 * NS_PER_SEC + 10, tagId, /*uid=*/"222"); + countProducer.onMatchedLogEvent(1 /*log matcher index*/, event2); + ASSERT_EQ(1UL, countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size()); + EXPECT_EQ(eventTimeNs, countProducer.mCurrentBucketStartTimeNs); + EXPECT_EQ(0, countProducer.getCurrentBucketNum()); + EXPECT_EQ(0, anomalyTracker->getSumOverPastBuckets(DEFAULT_METRIC_DIMENSION_KEY)); + + // Third event in following bucket. + LogEvent event3(/*uid=*/0, /*pid=*/0); + makeLogEvent(&event3, bucketStartTimeNs + 62 * NS_PER_SEC + 10, tagId, /*uid=*/"333"); + countProducer.onMatchedLogEvent(1 /*log matcher index*/, event3); + ASSERT_EQ(2UL, countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size()); + EXPECT_EQ(lastEndTimeNs, countProducer.mCurrentBucketStartTimeNs); + EXPECT_EQ(1, countProducer.getCurrentBucketNum()); + EXPECT_EQ(2, anomalyTracker->getSumOverPastBuckets(DEFAULT_METRIC_DIMENSION_KEY)); +} + +TEST_P(CountMetricProducerTest_PartialBucket, TestSplitInNextBucket) { + int64_t bucketStartTimeNs = 10000000000; + int64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(ONE_MINUTE) * 1000000LL; + int64_t eventTimeNs = bucketStartTimeNs + 65 * NS_PER_SEC; + + int tagId = 1; + int conditionTagId = 2; + + CountMetric metric; + metric.set_id(1); + metric.set_bucket(ONE_MINUTE); + + sp wizard = new NaggyMock(); + + CountMetricProducer countProducer(kConfigKey, metric, -1 /* no condition */, {}, wizard, + protoHash, bucketStartTimeNs, bucketStartTimeNs); + + // Bucket is flushed yet. + LogEvent event1(/*uid=*/0, /*pid=*/0); + makeLogEvent(&event1, bucketStartTimeNs + 1, tagId, /*uid=*/"111"); + countProducer.onMatchedLogEvent(1 /*log matcher index*/, event1); + ASSERT_EQ(0UL, countProducer.mPastBuckets.size()); + + // App upgrade or boot complete forces bucket flush. + // Check that there's a past bucket and the bucket end is not adjusted since the upgrade + // occurred after the bucket end time. + switch (GetParam()) { + case APP_UPGRADE: + countProducer.notifyAppUpgrade(eventTimeNs); + break; + case BOOT_COMPLETE: + countProducer.onStatsdInitCompleted(eventTimeNs); + break; + } + ASSERT_EQ(1UL, countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size()); + EXPECT_EQ(bucketStartTimeNs, + countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY][0].mBucketStartNs); + EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, + countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY][0].mBucketEndNs); + EXPECT_EQ(eventTimeNs, countProducer.mCurrentBucketStartTimeNs); + + // Next event occurs in same bucket as partial bucket created. + LogEvent event2(/*uid=*/0, /*pid=*/0); + makeLogEvent(&event2, bucketStartTimeNs + 70 * NS_PER_SEC + 10, tagId, /*uid=*/"222"); + countProducer.onMatchedLogEvent(1 /*log matcher index*/, event2); + ASSERT_EQ(1UL, countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size()); + + // Third event in following bucket. + LogEvent event3(/*uid=*/0, /*pid=*/0); + makeLogEvent(&event3, bucketStartTimeNs + 121 * NS_PER_SEC + 10, tagId, /*uid=*/"333"); + countProducer.onMatchedLogEvent(1 /*log matcher index*/, event3); + ASSERT_EQ(2UL, countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size()); + EXPECT_EQ((int64_t)eventTimeNs, + countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY][1].mBucketStartNs); + EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs, + countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY][1].mBucketEndNs); +} + +TEST(CountMetricProducerTest, TestAnomalyDetectionUnSliced) { + sp alarmMonitor; + Alert alert; + alert.set_id(11); + alert.set_metric_id(1); + alert.set_trigger_if_sum_gt(2); + alert.set_num_buckets(2); + const int32_t refPeriodSec = 1; + alert.set_refractory_period_secs(refPeriodSec); + + int64_t bucketStartTimeNs = 10000000000; + int64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(ONE_MINUTE) * 1000000LL; + int64_t bucket2StartTimeNs = bucketStartTimeNs + bucketSizeNs; + int64_t bucket3StartTimeNs = bucketStartTimeNs + 2 * bucketSizeNs; + + CountMetric metric; + metric.set_id(1); + metric.set_bucket(ONE_MINUTE); + + sp wizard = new NaggyMock(); + + CountMetricProducer countProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, {}, + wizard, protoHash, bucketStartTimeNs, bucketStartTimeNs); + + sp anomalyTracker = + countProducer.addAnomalyTracker(alert, alarmMonitor, UPDATE_NEW, bucketStartTimeNs); + + int tagId = 1; + LogEvent event1(/*uid=*/0, /*pid=*/0); + makeLogEvent(&event1, bucketStartTimeNs + 1, tagId); + LogEvent event2(/*uid=*/0, /*pid=*/0); + makeLogEvent(&event2, bucketStartTimeNs + 2, tagId); + LogEvent event3(/*uid=*/0, /*pid=*/0); + makeLogEvent(&event3, bucketStartTimeNs + 2 * bucketSizeNs + 1, tagId); + LogEvent event4(/*uid=*/0, /*pid=*/0); + makeLogEvent(&event4, bucketStartTimeNs + 3 * bucketSizeNs + 1, tagId); + LogEvent event5(/*uid=*/0, /*pid=*/0); + makeLogEvent(&event5, bucketStartTimeNs + 3 * bucketSizeNs + 2, tagId); + LogEvent event6(/*uid=*/0, /*pid=*/0); + makeLogEvent(&event6, bucketStartTimeNs + 3 * bucketSizeNs + 3, tagId); + LogEvent event7(/*uid=*/0, /*pid=*/0); + makeLogEvent(&event7, bucketStartTimeNs + 3 * bucketSizeNs + 2 * NS_PER_SEC, tagId); + + // Two events in bucket #0. + countProducer.onMatchedLogEvent(1 /*log matcher index*/, event1); + countProducer.onMatchedLogEvent(1 /*log matcher index*/, event2); + + ASSERT_EQ(1UL, countProducer.mCurrentSlicedCounter->size()); + EXPECT_EQ(2L, countProducer.mCurrentSlicedCounter->begin()->second); + 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); + ASSERT_EQ(1UL, countProducer.mCurrentSlicedCounter->size()); + EXPECT_EQ(1L, countProducer.mCurrentSlicedCounter->begin()->second); + EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(DEFAULT_METRIC_DIMENSION_KEY), 0U); + + // Two events in bucket #3. + countProducer.onMatchedLogEvent(1 /*log matcher index*/, event4); + countProducer.onMatchedLogEvent(1 /*log matcher index*/, event5); + countProducer.onMatchedLogEvent(1 /*log matcher index*/, event6); + ASSERT_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_METRIC_DIMENSION_KEY), + std::ceil(1.0 * event5.GetElapsedTimestampNs() / NS_PER_SEC + refPeriodSec)); + + countProducer.onMatchedLogEvent(1 /*log matcher index*/, event7); + ASSERT_EQ(1UL, countProducer.mCurrentSlicedCounter->size()); + EXPECT_EQ(4L, countProducer.mCurrentSlicedCounter->begin()->second); + EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(DEFAULT_METRIC_DIMENSION_KEY), + std::ceil(1.0 * event7.GetElapsedTimestampNs() / NS_PER_SEC + refPeriodSec)); +} + +TEST(CountMetricProducerTest, TestOneWeekTimeUnit) { + CountMetric metric; + metric.set_id(1); + metric.set_bucket(ONE_WEEK); + + sp wizard = new NaggyMock(); + + int64_t oneDayNs = 24 * 60 * 60 * 1e9; + int64_t fiveWeeksNs = 5 * 7 * oneDayNs; + + CountMetricProducer countProducer(kConfigKey, metric, -1 /* meaning no condition */, {}, wizard, + protoHash, oneDayNs, fiveWeeksNs); + + int64_t fiveWeeksOneDayNs = fiveWeeksNs + oneDayNs; + + EXPECT_EQ(fiveWeeksNs, countProducer.mCurrentBucketStartTimeNs); + EXPECT_EQ(4, countProducer.mCurrentBucketNum); + EXPECT_EQ(fiveWeeksOneDayNs, countProducer.getCurrentBucketEndTimeNs()); +} + +} // namespace statsd +} // namespace os +} // namespace android +#else +GTEST_LOG_(INFO) << "This test does nothing.\n"; +#endif diff --git a/statsd/tests/metrics/DurationMetricProducer_test.cpp b/statsd/tests/metrics/DurationMetricProducer_test.cpp new file mode 100644 index 00000000..c0805e87 --- /dev/null +++ b/statsd/tests/metrics/DurationMetricProducer_test.cpp @@ -0,0 +1,519 @@ +// 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 "src/metrics/DurationMetricProducer.h" + +#include +#include +#include + +#include +#include +#include + +#include "metrics_test_helper.h" +#include "src/condition/ConditionWizard.h" +#include "src/stats_log_util.h" +#include "stats_event.h" +#include "tests/statsd_test_util.h" + +using namespace android::os::statsd; +using namespace testing; +using android::sp; +using std::set; +using std::unordered_map; +using std::vector; + +#ifdef __ANDROID__ + +namespace android { +namespace os { +namespace statsd { + + +namespace { + +const ConfigKey kConfigKey(0, 12345); +const uint64_t protoHash = 0x1234567890; +void makeLogEvent(LogEvent* logEvent, int64_t timestampNs, int atomId) { + AStatsEvent* statsEvent = AStatsEvent_obtain(); + AStatsEvent_setAtomId(statsEvent, atomId); + AStatsEvent_overwriteTimestamp(statsEvent, timestampNs); + + parseStatsEventToLogEvent(statsEvent, logEvent); +} + +} // namespace + +// Setup for parameterized tests. +class DurationMetricProducerTest_PartialBucket : public TestWithParam {}; + +INSTANTIATE_TEST_SUITE_P(DurationMetricProducerTest_PartialBucket, + DurationMetricProducerTest_PartialBucket, + testing::Values(APP_UPGRADE, BOOT_COMPLETE)); + +TEST(DurationMetricTrackerTest, TestFirstBucket) { + sp wizard = new NaggyMock(); + DurationMetric metric; + metric.set_id(1); + metric.set_bucket(ONE_MINUTE); + metric.set_aggregation_type(DurationMetric_AggregationType_SUM); + + FieldMatcher dimensions; + + DurationMetricProducer durationProducer( + kConfigKey, metric, -1 /*no condition*/, {}, -1 /*what index not needed*/, + 1 /* start index */, 2 /* stop index */, 3 /* stop_all index */, false /*nesting*/, + wizard, protoHash, dimensions, 5, 600 * NS_PER_SEC + NS_PER_SEC / 2); + + EXPECT_EQ(600500000000, durationProducer.mCurrentBucketStartTimeNs); + EXPECT_EQ(10, durationProducer.mCurrentBucketNum); + EXPECT_EQ(660000000005, durationProducer.getCurrentBucketEndTimeNs()); +} + +TEST(DurationMetricTrackerTest, TestNoCondition) { + sp wizard = new NaggyMock(); + int64_t bucketStartTimeNs = 10000000000; + int64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(ONE_MINUTE) * 1000000LL; + + DurationMetric metric; + metric.set_id(1); + metric.set_bucket(ONE_MINUTE); + metric.set_aggregation_type(DurationMetric_AggregationType_SUM); + + int tagId = 1; + LogEvent event1(/*uid=*/0, /*pid=*/0); + makeLogEvent(&event1, bucketStartTimeNs + 1, tagId); + LogEvent event2(/*uid=*/0, /*pid=*/0); + makeLogEvent(&event2, bucketStartTimeNs + bucketSizeNs + 2, tagId); + + FieldMatcher dimensions; + + DurationMetricProducer durationProducer( + kConfigKey, metric, -1 /*no condition*/, {}, -1 /*what index not needed*/, + 1 /* start index */, 2 /* stop index */, 3 /* stop_all index */, false /*nesting*/, + wizard, protoHash, dimensions, bucketStartTimeNs, bucketStartTimeNs); + + durationProducer.onMatchedLogEvent(1 /* start index*/, event1); + durationProducer.onMatchedLogEvent(2 /* stop index*/, event2); + durationProducer.flushIfNeededLocked(bucketStartTimeNs + 2 * bucketSizeNs + 1); + ASSERT_EQ(1UL, durationProducer.mPastBuckets.size()); + EXPECT_TRUE(durationProducer.mPastBuckets.find(DEFAULT_METRIC_DIMENSION_KEY) != + durationProducer.mPastBuckets.end()); + const auto& buckets = durationProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY]; + ASSERT_EQ(2UL, buckets.size()); + EXPECT_EQ(bucketStartTimeNs, buckets[0].mBucketStartNs); + EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, buckets[0].mBucketEndNs); + EXPECT_EQ(bucketSizeNs - 1LL, buckets[0].mDuration); + EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, buckets[1].mBucketStartNs); + EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs, buckets[1].mBucketEndNs); + EXPECT_EQ(2LL, buckets[1].mDuration); +} + +TEST(DurationMetricTrackerTest, TestNonSlicedCondition) { + sp wizard = new NaggyMock(); + int64_t bucketStartTimeNs = 10000000000; + int64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(ONE_MINUTE) * 1000000LL; + + DurationMetric metric; + metric.set_id(1); + metric.set_bucket(ONE_MINUTE); + metric.set_aggregation_type(DurationMetric_AggregationType_SUM); + + int tagId = 1; + LogEvent event1(/*uid=*/0, /*pid=*/0); + makeLogEvent(&event1, bucketStartTimeNs + 1, tagId); + LogEvent event2(/*uid=*/0, /*pid=*/0); + makeLogEvent(&event2, bucketStartTimeNs + 2, tagId); + LogEvent event3(/*uid=*/0, /*pid=*/0); + makeLogEvent(&event3, bucketStartTimeNs + bucketSizeNs + 1, tagId); + LogEvent event4(/*uid=*/0, /*pid=*/0); + makeLogEvent(&event4, bucketStartTimeNs + bucketSizeNs + 3, tagId); + + FieldMatcher dimensions; + + DurationMetricProducer durationProducer( + kConfigKey, metric, 0 /* condition index */, {ConditionState::kUnknown}, + -1 /*what index not needed*/, 1 /* start index */, 2 /* stop index */, + 3 /* stop_all index */, false /*nesting*/, wizard, protoHash, dimensions, + bucketStartTimeNs, bucketStartTimeNs); + durationProducer.mCondition = ConditionState::kFalse; + + EXPECT_FALSE(durationProducer.mCondition); + EXPECT_FALSE(durationProducer.isConditionSliced()); + + durationProducer.onMatchedLogEvent(1 /* start index*/, event1); + durationProducer.onMatchedLogEvent(2 /* stop index*/, event2); + durationProducer.flushIfNeededLocked(bucketStartTimeNs + bucketSizeNs + 1); + ASSERT_EQ(0UL, durationProducer.mPastBuckets.size()); + + durationProducer.onMatchedLogEvent(1 /* start index*/, event3); + durationProducer.onConditionChanged(true /* condition */, bucketStartTimeNs + bucketSizeNs + 2); + durationProducer.onMatchedLogEvent(2 /* stop index*/, event4); + durationProducer.flushIfNeededLocked(bucketStartTimeNs + 2 * bucketSizeNs + 1); + ASSERT_EQ(1UL, durationProducer.mPastBuckets.size()); + EXPECT_TRUE(durationProducer.mPastBuckets.find(DEFAULT_METRIC_DIMENSION_KEY) != + durationProducer.mPastBuckets.end()); + const auto& buckets2 = durationProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY]; + ASSERT_EQ(1UL, buckets2.size()); + EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, buckets2[0].mBucketStartNs); + EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs, buckets2[0].mBucketEndNs); + EXPECT_EQ(1LL, buckets2[0].mDuration); +} + +TEST(DurationMetricTrackerTest, TestNonSlicedConditionUnknownState) { + sp wizard = new NaggyMock(); + int64_t bucketStartTimeNs = 10000000000; + int64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(ONE_MINUTE) * 1000000LL; + + DurationMetric metric; + metric.set_id(1); + metric.set_bucket(ONE_MINUTE); + metric.set_aggregation_type(DurationMetric_AggregationType_SUM); + + int tagId = 1; + LogEvent event1(/*uid=*/0, /*pid=*/0); + makeLogEvent(&event1, bucketStartTimeNs + 1, tagId); + LogEvent event2(/*uid=*/0, /*pid=*/0); + makeLogEvent(&event2, bucketStartTimeNs + 2, tagId); + LogEvent event3(/*uid=*/0, /*pid=*/0); + makeLogEvent(&event3, bucketStartTimeNs + bucketSizeNs + 1, tagId); + LogEvent event4(/*uid=*/0, /*pid=*/0); + makeLogEvent(&event4, bucketStartTimeNs + bucketSizeNs + 3, tagId); + + FieldMatcher dimensions; + + DurationMetricProducer durationProducer( + kConfigKey, metric, 0 /* condition index */, {ConditionState::kUnknown}, + -1 /*what index not needed*/, 1 /* start index */, 2 /* stop index */, + 3 /* stop_all index */, false /*nesting*/, wizard, protoHash, dimensions, + bucketStartTimeNs, bucketStartTimeNs); + + EXPECT_EQ(ConditionState::kUnknown, durationProducer.mCondition); + EXPECT_FALSE(durationProducer.isConditionSliced()); + + durationProducer.onMatchedLogEvent(1 /* start index*/, event1); + durationProducer.onMatchedLogEvent(2 /* stop index*/, event2); + durationProducer.flushIfNeededLocked(bucketStartTimeNs + bucketSizeNs + 1); + ASSERT_EQ(0UL, durationProducer.mPastBuckets.size()); + + durationProducer.onMatchedLogEvent(1 /* start index*/, event3); + durationProducer.onConditionChanged(true /* condition */, bucketStartTimeNs + bucketSizeNs + 2); + durationProducer.onMatchedLogEvent(2 /* stop index*/, event4); + durationProducer.flushIfNeededLocked(bucketStartTimeNs + 2 * bucketSizeNs + 1); + ASSERT_EQ(1UL, durationProducer.mPastBuckets.size()); + const auto& buckets2 = durationProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY]; + ASSERT_EQ(1UL, buckets2.size()); + EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, buckets2[0].mBucketStartNs); + EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs, buckets2[0].mBucketEndNs); + EXPECT_EQ(1LL, buckets2[0].mDuration); +} + +TEST_P(DurationMetricProducerTest_PartialBucket, TestSumDuration) { + /** + * The duration starts from the first bucket, through the two partial buckets (10-70sec), + * another bucket, and ends at the beginning of the next full bucket. + * Expected buckets: + * - [10,25]: 14 secs + * - [25,70]: All 45 secs + * - [70,130]: All 60 secs + * - [130, 210]: Only 5 secs (event ended at 135sec) + */ + int64_t bucketStartTimeNs = 10000000000; + int64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(ONE_MINUTE) * 1000000LL; + int tagId = 1; + + DurationMetric metric; + metric.set_id(1); + metric.set_bucket(ONE_MINUTE); + metric.set_aggregation_type(DurationMetric_AggregationType_SUM); + sp wizard = new NaggyMock(); + FieldMatcher dimensions; + + DurationMetricProducer durationProducer( + kConfigKey, metric, -1 /* no condition */, {}, -1 /*what index not needed*/, + 1 /* start index */, 2 /* stop index */, 3 /* stop_all index */, false /*nesting*/, + wizard, protoHash, dimensions, bucketStartTimeNs, bucketStartTimeNs); + + int64_t startTimeNs = bucketStartTimeNs + 1 * NS_PER_SEC; + LogEvent event1(/*uid=*/0, /*pid=*/0); + makeLogEvent(&event1, startTimeNs, tagId); + durationProducer.onMatchedLogEvent(1 /* start index*/, event1); + ASSERT_EQ(0UL, durationProducer.mPastBuckets.size()); + EXPECT_EQ(bucketStartTimeNs, durationProducer.mCurrentBucketStartTimeNs); + + int64_t partialBucketSplitTimeNs = bucketStartTimeNs + 15 * NS_PER_SEC; + switch (GetParam()) { + case APP_UPGRADE: + durationProducer.notifyAppUpgrade(partialBucketSplitTimeNs); + break; + case BOOT_COMPLETE: + durationProducer.onStatsdInitCompleted(partialBucketSplitTimeNs); + break; + } + ASSERT_EQ(1UL, durationProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size()); + std::vector buckets = + durationProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY]; + EXPECT_EQ(bucketStartTimeNs, buckets[0].mBucketStartNs); + EXPECT_EQ(partialBucketSplitTimeNs, buckets[0].mBucketEndNs); + EXPECT_EQ(partialBucketSplitTimeNs - startTimeNs, buckets[0].mDuration); + EXPECT_EQ(partialBucketSplitTimeNs, durationProducer.mCurrentBucketStartTimeNs); + EXPECT_EQ(0, durationProducer.getCurrentBucketNum()); + + // We skip ahead one bucket, so we fill in the first two partial buckets and one full bucket. + int64_t endTimeNs = startTimeNs + 125 * NS_PER_SEC; + LogEvent event2(/*uid=*/0, /*pid=*/0); + makeLogEvent(&event2, endTimeNs, tagId); + durationProducer.onMatchedLogEvent(2 /* stop index*/, event2); + buckets = durationProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY]; + ASSERT_EQ(3UL, buckets.size()); + EXPECT_EQ(partialBucketSplitTimeNs, buckets[1].mBucketStartNs); + EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, buckets[1].mBucketEndNs); + EXPECT_EQ(bucketStartTimeNs + bucketSizeNs - partialBucketSplitTimeNs, buckets[1].mDuration); + EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, buckets[2].mBucketStartNs); + EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs, buckets[2].mBucketEndNs); + EXPECT_EQ(bucketSizeNs, buckets[2].mDuration); +} + +TEST_P(DurationMetricProducerTest_PartialBucket, TestSumDurationWithSplitInFollowingBucket) { + /** + * Expected buckets (start at 11s, upgrade at 75s, end at 135s): + * - [10,70]: 59 secs + * - [70,75]: 5 sec + * - [75,130]: 55 secs + */ + int64_t bucketStartTimeNs = 10000000000; + int64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(ONE_MINUTE) * 1000000LL; + int tagId = 1; + + DurationMetric metric; + metric.set_id(1); + metric.set_bucket(ONE_MINUTE); + metric.set_aggregation_type(DurationMetric_AggregationType_SUM); + sp wizard = new NaggyMock(); + FieldMatcher dimensions; + + DurationMetricProducer durationProducer( + kConfigKey, metric, -1 /* no condition */, {}, -1 /*what index not needed*/, + 1 /* start index */, 2 /* stop index */, 3 /* stop_all index */, false /*nesting*/, + wizard, protoHash, dimensions, bucketStartTimeNs, bucketStartTimeNs); + + int64_t startTimeNs = bucketStartTimeNs + 1 * NS_PER_SEC; + LogEvent event1(/*uid=*/0, /*pid=*/0); + makeLogEvent(&event1, startTimeNs, tagId); + durationProducer.onMatchedLogEvent(1 /* start index*/, event1); + ASSERT_EQ(0UL, durationProducer.mPastBuckets.size()); + EXPECT_EQ(bucketStartTimeNs, durationProducer.mCurrentBucketStartTimeNs); + + int64_t partialBucketSplitTimeNs = bucketStartTimeNs + 65 * NS_PER_SEC; + switch (GetParam()) { + case APP_UPGRADE: + durationProducer.notifyAppUpgrade(partialBucketSplitTimeNs); + break; + case BOOT_COMPLETE: + durationProducer.onStatsdInitCompleted(partialBucketSplitTimeNs); + break; + } + ASSERT_EQ(2UL, durationProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size()); + std::vector buckets = + durationProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY]; + EXPECT_EQ(bucketStartTimeNs, buckets[0].mBucketStartNs); + EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, buckets[0].mBucketEndNs); + EXPECT_EQ(bucketStartTimeNs + bucketSizeNs - startTimeNs, buckets[0].mDuration); + EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, buckets[1].mBucketStartNs); + EXPECT_EQ(partialBucketSplitTimeNs, buckets[1].mBucketEndNs); + EXPECT_EQ(partialBucketSplitTimeNs - (bucketStartTimeNs + bucketSizeNs), buckets[1].mDuration); + EXPECT_EQ(partialBucketSplitTimeNs, durationProducer.mCurrentBucketStartTimeNs); + EXPECT_EQ(1, durationProducer.getCurrentBucketNum()); + + // We skip ahead one bucket, so we fill in the first two partial buckets and one full bucket. + int64_t endTimeNs = startTimeNs + 125 * NS_PER_SEC; + LogEvent event2(/*uid=*/0, /*pid=*/0); + makeLogEvent(&event2, endTimeNs, tagId); + durationProducer.onMatchedLogEvent(2 /* stop index*/, event2); + buckets = durationProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY]; + ASSERT_EQ(3UL, buckets.size()); + EXPECT_EQ(partialBucketSplitTimeNs, buckets[2].mBucketStartNs); + EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs, buckets[2].mBucketEndNs); + EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs - partialBucketSplitTimeNs, + buckets[2].mDuration); +} + +TEST_P(DurationMetricProducerTest_PartialBucket, TestSumDurationAnomaly) { + sp alarmMonitor; + int64_t bucketStartTimeNs = 10000000000; + int64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(ONE_MINUTE) * 1000000LL; + int tagId = 1; + + // Setup metric with alert. + DurationMetric metric; + metric.set_id(1); + metric.set_bucket(ONE_MINUTE); + metric.set_aggregation_type(DurationMetric_AggregationType_SUM); + Alert alert; + alert.set_num_buckets(3); + alert.set_trigger_if_sum_gt(2); + + sp wizard = new NaggyMock(); + FieldMatcher dimensions; + + DurationMetricProducer durationProducer( + kConfigKey, metric, -1 /* no condition */, {}, -1 /*what index not needed*/, + 1 /* start index */, 2 /* stop index */, 3 /* stop_all index */, false /*nesting*/, + wizard, protoHash, dimensions, bucketStartTimeNs, bucketStartTimeNs); + + sp anomalyTracker = + durationProducer.addAnomalyTracker(alert, alarmMonitor, UPDATE_NEW, bucketStartTimeNs); + EXPECT_TRUE(anomalyTracker != nullptr); + + int64_t startTimeNs = bucketStartTimeNs + 1; + LogEvent event1(/*uid=*/0, /*pid=*/0); + makeLogEvent(&event1, startTimeNs, tagId); + durationProducer.onMatchedLogEvent(1 /* start index*/, event1); + + int64_t partialBucketSplitTimeNs = bucketStartTimeNs + 15 * NS_PER_SEC; + switch (GetParam()) { + case APP_UPGRADE: + durationProducer.notifyAppUpgrade(partialBucketSplitTimeNs); + break; + case BOOT_COMPLETE: + durationProducer.onStatsdInitCompleted(partialBucketSplitTimeNs); + break; + } + + // We skip ahead one bucket, so we fill in the first two partial buckets and one full bucket. + int64_t endTimeNs = startTimeNs + 65 * NS_PER_SEC; + LogEvent event2(/*uid=*/0, /*pid=*/0); + makeLogEvent(&event2, endTimeNs, tagId); + durationProducer.onMatchedLogEvent(2 /* stop index*/, event2); + + EXPECT_EQ(bucketStartTimeNs + bucketSizeNs - startTimeNs, + anomalyTracker->getSumOverPastBuckets(DEFAULT_METRIC_DIMENSION_KEY)); +} + +TEST_P(DurationMetricProducerTest_PartialBucket, TestMaxDuration) { + int64_t bucketStartTimeNs = 10000000000; + int64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(ONE_MINUTE) * 1000000LL; + int tagId = 1; + + DurationMetric metric; + metric.set_id(1); + metric.set_bucket(ONE_MINUTE); + metric.set_aggregation_type(DurationMetric_AggregationType_MAX_SPARSE); + + sp wizard = new NaggyMock(); + FieldMatcher dimensions; + + DurationMetricProducer durationProducer( + kConfigKey, metric, -1 /* no condition */, {}, -1 /*what index not needed*/, + 1 /* start index */, 2 /* stop index */, 3 /* stop_all index */, false /*nesting*/, + wizard, protoHash, dimensions, bucketStartTimeNs, bucketStartTimeNs); + + int64_t startTimeNs = bucketStartTimeNs + 1; + LogEvent event1(/*uid=*/0, /*pid=*/0); + makeLogEvent(&event1, startTimeNs, tagId); + durationProducer.onMatchedLogEvent(1 /* start index*/, event1); + ASSERT_EQ(0UL, durationProducer.mPastBuckets.size()); + EXPECT_EQ(bucketStartTimeNs, durationProducer.mCurrentBucketStartTimeNs); + + int64_t partialBucketSplitTimeNs = bucketStartTimeNs + 15 * NS_PER_SEC; + switch (GetParam()) { + case APP_UPGRADE: + durationProducer.notifyAppUpgrade(partialBucketSplitTimeNs); + break; + case BOOT_COMPLETE: + durationProducer.onStatsdInitCompleted(partialBucketSplitTimeNs); + break; + } + ASSERT_EQ(0UL, durationProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size()); + EXPECT_EQ(partialBucketSplitTimeNs, durationProducer.mCurrentBucketStartTimeNs); + EXPECT_EQ(0, durationProducer.getCurrentBucketNum()); + + // We skip ahead one bucket, so we fill in the first two partial buckets and one full bucket. + int64_t endTimeNs = startTimeNs + 125 * NS_PER_SEC; + LogEvent event2(/*uid=*/0, /*pid=*/0); + makeLogEvent(&event2, endTimeNs, tagId); + durationProducer.onMatchedLogEvent(2 /* stop index*/, event2); + ASSERT_EQ(0UL, durationProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size()); + + durationProducer.flushIfNeededLocked(bucketStartTimeNs + 3 * bucketSizeNs + 1); + std::vector buckets = + durationProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY]; + ASSERT_EQ(1UL, buckets.size()); + EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs, buckets[0].mBucketStartNs); + EXPECT_EQ(bucketStartTimeNs + 3 * bucketSizeNs, buckets[0].mBucketEndNs); + EXPECT_EQ(endTimeNs - startTimeNs, buckets[0].mDuration); +} + +TEST_P(DurationMetricProducerTest_PartialBucket, TestMaxDurationWithSplitInNextBucket) { + int64_t bucketStartTimeNs = 10000000000; + int64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(ONE_MINUTE) * 1000000LL; + int tagId = 1; + + DurationMetric metric; + metric.set_id(1); + metric.set_bucket(ONE_MINUTE); + metric.set_aggregation_type(DurationMetric_AggregationType_MAX_SPARSE); + + sp wizard = new NaggyMock(); + FieldMatcher dimensions; + + DurationMetricProducer durationProducer( + kConfigKey, metric, -1 /* no condition */, {}, -1 /*what index not needed*/, + 1 /* start index */, 2 /* stop index */, 3 /* stop_all index */, false /*nesting*/, + wizard, protoHash, dimensions, bucketStartTimeNs, bucketStartTimeNs); + + int64_t startTimeNs = bucketStartTimeNs + 1; + LogEvent event1(/*uid=*/0, /*pid=*/0); + makeLogEvent(&event1, startTimeNs, tagId); + durationProducer.onMatchedLogEvent(1 /* start index*/, event1); + ASSERT_EQ(0UL, durationProducer.mPastBuckets.size()); + EXPECT_EQ(bucketStartTimeNs, durationProducer.mCurrentBucketStartTimeNs); + + int64_t partialBucketSplitTimeNs = bucketStartTimeNs + 65 * NS_PER_SEC; + switch (GetParam()) { + case APP_UPGRADE: + durationProducer.notifyAppUpgrade(partialBucketSplitTimeNs); + break; + case BOOT_COMPLETE: + durationProducer.onStatsdInitCompleted(partialBucketSplitTimeNs); + break; + } + ASSERT_EQ(0UL, durationProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size()); + EXPECT_EQ(partialBucketSplitTimeNs, durationProducer.mCurrentBucketStartTimeNs); + EXPECT_EQ(1, durationProducer.getCurrentBucketNum()); + + // Stop occurs in the same partial bucket as created for the app upgrade. + int64_t endTimeNs = startTimeNs + 115 * NS_PER_SEC; + LogEvent event2(/*uid=*/0, /*pid=*/0); + makeLogEvent(&event2, endTimeNs, tagId); + durationProducer.onMatchedLogEvent(2 /* stop index*/, event2); + ASSERT_EQ(0UL, durationProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size()); + EXPECT_EQ(partialBucketSplitTimeNs, durationProducer.mCurrentBucketStartTimeNs); + + durationProducer.flushIfNeededLocked(bucketStartTimeNs + 2 * bucketSizeNs + 1); + std::vector buckets = + durationProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY]; + ASSERT_EQ(1UL, buckets.size()); + EXPECT_EQ(partialBucketSplitTimeNs, buckets[0].mBucketStartNs); + EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs, buckets[0].mBucketEndNs); + EXPECT_EQ(endTimeNs - startTimeNs, buckets[0].mDuration); +} + +} // namespace statsd +} // namespace os +} // namespace android +#else +GTEST_LOG_(INFO) << "This test does nothing.\n"; +#endif diff --git a/statsd/tests/metrics/EventMetricProducer_test.cpp b/statsd/tests/metrics/EventMetricProducer_test.cpp new file mode 100644 index 00000000..4bbbd2cb --- /dev/null +++ b/statsd/tests/metrics/EventMetricProducer_test.cpp @@ -0,0 +1,186 @@ +// 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 "src/metrics/EventMetricProducer.h" + +#include +#include +#include + +#include + +#include "metrics_test_helper.h" +#include "stats_event.h" +#include "tests/statsd_test_util.h" + +using namespace testing; +using android::sp; +using std::set; +using std::unordered_map; +using std::vector; + +#ifdef __ANDROID__ + +namespace android { +namespace os { +namespace statsd { + + +namespace { +const ConfigKey kConfigKey(0, 12345); +const uint64_t protoHash = 0x1234567890; + +void makeLogEvent(LogEvent* logEvent, int32_t atomId, int64_t timestampNs, string str) { + AStatsEvent* statsEvent = AStatsEvent_obtain(); + AStatsEvent_setAtomId(statsEvent, atomId); + AStatsEvent_overwriteTimestamp(statsEvent, timestampNs); + AStatsEvent_writeString(statsEvent, str.c_str()); + + parseStatsEventToLogEvent(statsEvent, logEvent); +} +} // anonymous namespace + +TEST(EventMetricProducerTest, TestNoCondition) { + int64_t bucketStartTimeNs = 10000000000; + int64_t eventStartTimeNs = bucketStartTimeNs + 1; + int64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL; + + EventMetric metric; + metric.set_id(1); + + LogEvent event1(/*uid=*/0, /*pid=*/0); + CreateNoValuesLogEvent(&event1, 1 /*tagId*/, bucketStartTimeNs + 1); + + LogEvent event2(/*uid=*/0, /*pid=*/0); + CreateNoValuesLogEvent(&event2, 1 /*tagId*/, bucketStartTimeNs + 2); + + sp wizard = new NaggyMock(); + + EventMetricProducer eventProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, {}, + wizard, protoHash, bucketStartTimeNs); + + eventProducer.onMatchedLogEvent(1 /*matcher index*/, event1); + eventProducer.onMatchedLogEvent(1 /*matcher index*/, event2); + + // Check dump report content. + ProtoOutputStream output; + std::set strSet; + eventProducer.onDumpReport(bucketStartTimeNs + 20, true /*include current partial bucket*/, + true /*erase data*/, FAST, &strSet, &output); + + StatsLogReport report = outputStreamToProto(&output); + EXPECT_TRUE(report.has_event_metrics()); + ASSERT_EQ(2, report.event_metrics().data_size()); + EXPECT_EQ(bucketStartTimeNs + 1, report.event_metrics().data(0).elapsed_timestamp_nanos()); + EXPECT_EQ(bucketStartTimeNs + 2, report.event_metrics().data(1).elapsed_timestamp_nanos()); +} + +TEST(EventMetricProducerTest, TestEventsWithNonSlicedCondition) { + int64_t bucketStartTimeNs = 10000000000; + int64_t eventStartTimeNs = bucketStartTimeNs + 1; + int64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL; + + EventMetric metric; + metric.set_id(1); + metric.set_condition(StringToId("SCREEN_ON")); + + LogEvent event1(/*uid=*/0, /*pid=*/0); + CreateNoValuesLogEvent(&event1, 1 /*tagId*/, bucketStartTimeNs + 1); + + LogEvent event2(/*uid=*/0, /*pid=*/0); + CreateNoValuesLogEvent(&event2, 1 /*tagId*/, bucketStartTimeNs + 10); + + sp wizard = new NaggyMock(); + + EventMetricProducer eventProducer(kConfigKey, metric, 0 /*condition index*/, + {ConditionState::kUnknown}, wizard, protoHash, + bucketStartTimeNs); + + eventProducer.onConditionChanged(true /*condition*/, bucketStartTimeNs); + eventProducer.onMatchedLogEvent(1 /*matcher index*/, event1); + + eventProducer.onConditionChanged(false /*condition*/, bucketStartTimeNs + 2); + + eventProducer.onMatchedLogEvent(1 /*matcher index*/, event2); + + // Check dump report content. + ProtoOutputStream output; + std::set strSet; + eventProducer.onDumpReport(bucketStartTimeNs + 20, true /*include current partial bucket*/, + true /*erase data*/, FAST, &strSet, &output); + + StatsLogReport report = outputStreamToProto(&output); + EXPECT_TRUE(report.has_event_metrics()); + ASSERT_EQ(1, report.event_metrics().data_size()); + EXPECT_EQ(bucketStartTimeNs + 1, report.event_metrics().data(0).elapsed_timestamp_nanos()); +} + +TEST(EventMetricProducerTest, TestEventsWithSlicedCondition) { + int64_t bucketStartTimeNs = 10000000000; + int64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL; + + int tagId = 1; + int conditionTagId = 2; + + EventMetric metric; + metric.set_id(1); + metric.set_condition(StringToId("APP_IN_BACKGROUND_PER_UID_AND_SCREEN_ON")); + MetricConditionLink* link = metric.add_links(); + link->set_condition(StringToId("APP_IN_BACKGROUND_PER_UID")); + buildSimpleAtomFieldMatcher(tagId, 1, link->mutable_fields_in_what()); + buildSimpleAtomFieldMatcher(conditionTagId, 2, link->mutable_fields_in_condition()); + + LogEvent event1(/*uid=*/0, /*pid=*/0); + makeLogEvent(&event1, 1 /*tagId*/, bucketStartTimeNs + 1, "111"); + ConditionKey key1; + key1[StringToId("APP_IN_BACKGROUND_PER_UID")] = { + getMockedDimensionKey(conditionTagId, 2, "111")}; + + LogEvent event2(/*uid=*/0, /*pid=*/0); + makeLogEvent(&event2, 1 /*tagId*/, bucketStartTimeNs + 10, "222"); + ConditionKey key2; + key2[StringToId("APP_IN_BACKGROUND_PER_UID")] = { + getMockedDimensionKey(conditionTagId, 2, "222")}; + + sp wizard = new NaggyMock(); + // Condition is false for first event. + EXPECT_CALL(*wizard, query(_, key1, _)).WillOnce(Return(ConditionState::kFalse)); + // Condition is true for second event. + EXPECT_CALL(*wizard, query(_, key2, _)).WillOnce(Return(ConditionState::kTrue)); + + EventMetricProducer eventProducer(kConfigKey, metric, 0 /*condition index*/, + {ConditionState::kUnknown}, wizard, protoHash, + bucketStartTimeNs); + + eventProducer.onMatchedLogEvent(1 /*matcher index*/, event1); + eventProducer.onMatchedLogEvent(1 /*matcher index*/, event2); + + // Check dump report content. + ProtoOutputStream output; + std::set strSet; + eventProducer.onDumpReport(bucketStartTimeNs + 20, true /*include current partial bucket*/, + true /*erase data*/, FAST, &strSet, &output); + + StatsLogReport report = outputStreamToProto(&output); + EXPECT_TRUE(report.has_event_metrics()); + ASSERT_EQ(1, report.event_metrics().data_size()); + EXPECT_EQ(bucketStartTimeNs + 10, report.event_metrics().data(0).elapsed_timestamp_nanos()); +} + +} // namespace statsd +} // namespace os +} // namespace android +#else +GTEST_LOG_(INFO) << "This test does nothing.\n"; +#endif diff --git a/statsd/tests/metrics/GaugeMetricProducer_test.cpp b/statsd/tests/metrics/GaugeMetricProducer_test.cpp new file mode 100644 index 00000000..c7443481 --- /dev/null +++ b/statsd/tests/metrics/GaugeMetricProducer_test.cpp @@ -0,0 +1,830 @@ +// 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 "src/metrics/GaugeMetricProducer.h" + +#include +#include +#include +#include + +#include + +#include "logd/LogEvent.h" +#include "metrics_test_helper.h" +#include "src/matchers/SimpleAtomMatchingTracker.h" +#include "src/metrics/MetricProducer.h" +#include "src/stats_log_util.h" +#include "stats_event.h" +#include "tests/statsd_test_util.h" + +using namespace testing; +using android::sp; +using std::set; +using std::unordered_map; +using std::vector; +using std::make_shared; + +#ifdef __ANDROID__ + +namespace android { +namespace os { +namespace statsd { + +namespace { + +const ConfigKey kConfigKey(0, 12345); +const int tagId = 1; +const int64_t metricId = 123; +const uint64_t protoHash = 0x123456789; +const int logEventMatcherIndex = 0; +const int64_t bucketStartTimeNs = 10 * NS_PER_SEC; +const int64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(ONE_MINUTE) * 1000000LL; +const int64_t bucket2StartTimeNs = bucketStartTimeNs + bucketSizeNs; +const int64_t bucket3StartTimeNs = bucketStartTimeNs + 2 * bucketSizeNs; +const int64_t bucket4StartTimeNs = bucketStartTimeNs + 3 * bucketSizeNs; +const int64_t partialBucketSplitTimeNs = bucketStartTimeNs + 15 * NS_PER_SEC; + +shared_ptr makeLogEvent(int32_t atomId, int64_t timestampNs, int32_t value1, string str1, + int32_t value2) { + AStatsEvent* statsEvent = AStatsEvent_obtain(); + AStatsEvent_setAtomId(statsEvent, atomId); + AStatsEvent_overwriteTimestamp(statsEvent, timestampNs); + + AStatsEvent_writeInt32(statsEvent, value1); + AStatsEvent_writeString(statsEvent, str1.c_str()); + AStatsEvent_writeInt32(statsEvent, value2); + + shared_ptr logEvent = std::make_shared(/*uid=*/0, /*pid=*/0); + parseStatsEventToLogEvent(statsEvent, logEvent.get()); + return logEvent; +} +} // anonymous namespace + +// Setup for parameterized tests. +class GaugeMetricProducerTest_PartialBucket : public TestWithParam {}; + +INSTANTIATE_TEST_SUITE_P(GaugeMetricProducerTest_PartialBucket, + GaugeMetricProducerTest_PartialBucket, + testing::Values(APP_UPGRADE, BOOT_COMPLETE)); + +/* + * Tests that the first bucket works correctly + */ +TEST(GaugeMetricProducerTest, TestFirstBucket) { + GaugeMetric metric; + metric.set_id(metricId); + metric.set_bucket(ONE_MINUTE); + metric.mutable_gauge_fields_filter()->set_include_all(false); + auto gaugeFieldMatcher = metric.mutable_gauge_fields_filter()->mutable_fields(); + gaugeFieldMatcher->set_field(tagId); + gaugeFieldMatcher->add_child()->set_field(1); + gaugeFieldMatcher->add_child()->set_field(3); + + sp wizard = new NaggyMock(); + + sp eventMatcherWizard = + createEventMatcherWizard(tagId, logEventMatcherIndex); + + sp pullerManager = new StrictMock(); + + // statsd started long ago. + // The metric starts in the middle of the bucket + GaugeMetricProducer gaugeProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, {}, + wizard, protoHash, logEventMatcherIndex, eventMatcherWizard, + -1, -1, tagId, 5, 600 * NS_PER_SEC + NS_PER_SEC / 2, + pullerManager); + gaugeProducer.prepareFirstBucket(); + + EXPECT_EQ(600500000000, gaugeProducer.mCurrentBucketStartTimeNs); + EXPECT_EQ(10, gaugeProducer.mCurrentBucketNum); + EXPECT_EQ(660000000005, gaugeProducer.getCurrentBucketEndTimeNs()); +} + +TEST(GaugeMetricProducerTest, TestPulledEventsNoCondition) { + GaugeMetric metric; + metric.set_id(metricId); + metric.set_bucket(ONE_MINUTE); + metric.mutable_gauge_fields_filter()->set_include_all(false); + metric.set_max_pull_delay_sec(INT_MAX); + auto gaugeFieldMatcher = metric.mutable_gauge_fields_filter()->mutable_fields(); + gaugeFieldMatcher->set_field(tagId); + gaugeFieldMatcher->add_child()->set_field(1); + gaugeFieldMatcher->add_child()->set_field(3); + + sp wizard = new NaggyMock(); + + sp eventMatcherWizard = + createEventMatcherWizard(tagId, logEventMatcherIndex); + + sp pullerManager = new StrictMock(); + EXPECT_CALL(*pullerManager, RegisterReceiver(tagId, kConfigKey, _, _, _)).WillOnce(Return()); + EXPECT_CALL(*pullerManager, UnRegisterReceiver(tagId, kConfigKey, _)).WillOnce(Return()); + EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, bucketStartTimeNs, _)) + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector>* data) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs); + data->clear(); + data->push_back(makeLogEvent(tagId, eventTimeNs + 10, 3, "some value", 11)); + return true; + })); + + GaugeMetricProducer gaugeProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, {}, + wizard, protoHash, logEventMatcherIndex, eventMatcherWizard, + tagId, -1, tagId, bucketStartTimeNs, bucketStartTimeNs, + pullerManager); + gaugeProducer.prepareFirstBucket(); + + vector> allData; + allData.clear(); + allData.push_back(makeLogEvent(tagId, bucket2StartTimeNs + 1, 10, "some value", 11)); + + gaugeProducer.onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs); + ASSERT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->size()); + auto it = gaugeProducer.mCurrentSlicedBucket->begin()->second.front().mFields->begin(); + EXPECT_EQ(INT, it->mValue.getType()); + EXPECT_EQ(10, it->mValue.int_value); + it++; + EXPECT_EQ(11, it->mValue.int_value); + ASSERT_EQ(1UL, gaugeProducer.mPastBuckets.size()); + EXPECT_EQ(3, gaugeProducer.mPastBuckets.begin() + ->second.back() + .mGaugeAtoms.front() + .mFields->begin() + ->mValue.int_value); + + allData.clear(); + allData.push_back(makeLogEvent(tagId, bucket3StartTimeNs + 10, 24, "some value", 25)); + gaugeProducer.onDataPulled(allData, /** succeed */ true, bucket3StartTimeNs); + ASSERT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->size()); + it = gaugeProducer.mCurrentSlicedBucket->begin()->second.front().mFields->begin(); + EXPECT_EQ(INT, it->mValue.getType()); + EXPECT_EQ(24, it->mValue.int_value); + it++; + EXPECT_EQ(INT, it->mValue.getType()); + EXPECT_EQ(25, it->mValue.int_value); + // One dimension. + ASSERT_EQ(1UL, gaugeProducer.mPastBuckets.size()); + ASSERT_EQ(2UL, gaugeProducer.mPastBuckets.begin()->second.size()); + it = gaugeProducer.mPastBuckets.begin()->second.back().mGaugeAtoms.front().mFields->begin(); + EXPECT_EQ(INT, it->mValue.getType()); + EXPECT_EQ(10L, it->mValue.int_value); + it++; + EXPECT_EQ(INT, it->mValue.getType()); + EXPECT_EQ(11L, it->mValue.int_value); + + gaugeProducer.flushIfNeededLocked(bucket4StartTimeNs); + ASSERT_EQ(0UL, gaugeProducer.mCurrentSlicedBucket->size()); + // One dimension. + ASSERT_EQ(1UL, gaugeProducer.mPastBuckets.size()); + ASSERT_EQ(3UL, gaugeProducer.mPastBuckets.begin()->second.size()); + it = gaugeProducer.mPastBuckets.begin()->second.back().mGaugeAtoms.front().mFields->begin(); + EXPECT_EQ(INT, it->mValue.getType()); + EXPECT_EQ(24L, it->mValue.int_value); + it++; + EXPECT_EQ(INT, it->mValue.getType()); + EXPECT_EQ(25L, it->mValue.int_value); +} + +TEST_P(GaugeMetricProducerTest_PartialBucket, TestPushedEvents) { + sp alarmMonitor; + GaugeMetric metric; + metric.set_id(metricId); + metric.set_bucket(ONE_MINUTE); + metric.mutable_gauge_fields_filter()->set_include_all(true); + + Alert alert; + alert.set_id(101); + alert.set_metric_id(metricId); + alert.set_trigger_if_sum_gt(25); + alert.set_num_buckets(100); + sp wizard = new NaggyMock(); + sp pullerManager = new StrictMock(); + + sp eventMatcherWizard = + createEventMatcherWizard(tagId, logEventMatcherIndex); + + GaugeMetricProducer gaugeProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, {}, + wizard, protoHash, logEventMatcherIndex, eventMatcherWizard, + -1 /* -1 means no pulling */, -1, tagId, bucketStartTimeNs, + bucketStartTimeNs, pullerManager); + gaugeProducer.prepareFirstBucket(); + + sp anomalyTracker = + gaugeProducer.addAnomalyTracker(alert, alarmMonitor, UPDATE_NEW, bucketStartTimeNs); + EXPECT_TRUE(anomalyTracker != nullptr); + + LogEvent event1(/*uid=*/0, /*pid=*/0); + CreateTwoValueLogEvent(&event1, tagId, bucketStartTimeNs + 10, 1, 10); + gaugeProducer.onMatchedLogEvent(1 /*log matcher index*/, event1); + EXPECT_EQ(1UL, (*gaugeProducer.mCurrentSlicedBucket).count(DEFAULT_METRIC_DIMENSION_KEY)); + + switch (GetParam()) { + case APP_UPGRADE: + gaugeProducer.notifyAppUpgrade(partialBucketSplitTimeNs); + break; + case BOOT_COMPLETE: + gaugeProducer.onStatsdInitCompleted(partialBucketSplitTimeNs); + break; + } + EXPECT_EQ(0UL, (*gaugeProducer.mCurrentSlicedBucket).count(DEFAULT_METRIC_DIMENSION_KEY)); + ASSERT_EQ(1UL, gaugeProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size()); + EXPECT_EQ(bucketStartTimeNs, + gaugeProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY][0].mBucketStartNs); + EXPECT_EQ(partialBucketSplitTimeNs, + gaugeProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY][0].mBucketEndNs); + EXPECT_EQ(0L, gaugeProducer.mCurrentBucketNum); + EXPECT_EQ(partialBucketSplitTimeNs, gaugeProducer.mCurrentBucketStartTimeNs); + // Partial buckets are not sent to anomaly tracker. + EXPECT_EQ(0, anomalyTracker->getSumOverPastBuckets(DEFAULT_METRIC_DIMENSION_KEY)); + + // Create an event in the same partial bucket. + LogEvent event2(/*uid=*/0, /*pid=*/0); + CreateTwoValueLogEvent(&event2, tagId, bucketStartTimeNs + 59 * NS_PER_SEC, 1, 10); + gaugeProducer.onMatchedLogEvent(1 /*log matcher index*/, event2); + EXPECT_EQ(0L, gaugeProducer.mCurrentBucketNum); + ASSERT_EQ(1UL, gaugeProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size()); + EXPECT_EQ(bucketStartTimeNs, + gaugeProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY][0].mBucketStartNs); + EXPECT_EQ(partialBucketSplitTimeNs, + gaugeProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY][0].mBucketEndNs); + EXPECT_EQ((int64_t)partialBucketSplitTimeNs, gaugeProducer.mCurrentBucketStartTimeNs); + // Partial buckets are not sent to anomaly tracker. + EXPECT_EQ(0, anomalyTracker->getSumOverPastBuckets(DEFAULT_METRIC_DIMENSION_KEY)); + + // Next event should trigger creation of new bucket and send previous full bucket to anomaly + // tracker. + LogEvent event3(/*uid=*/0, /*pid=*/0); + CreateTwoValueLogEvent(&event3, tagId, bucketStartTimeNs + 65 * NS_PER_SEC, 1, 10); + gaugeProducer.onMatchedLogEvent(1 /*log matcher index*/, event3); + EXPECT_EQ(1L, gaugeProducer.mCurrentBucketNum); + ASSERT_EQ(2UL, gaugeProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size()); + EXPECT_EQ((int64_t)bucketStartTimeNs + bucketSizeNs, gaugeProducer.mCurrentBucketStartTimeNs); + EXPECT_EQ(1, anomalyTracker->getSumOverPastBuckets(DEFAULT_METRIC_DIMENSION_KEY)); + + // Next event should trigger creation of new bucket. + LogEvent event4(/*uid=*/0, /*pid=*/0); + CreateTwoValueLogEvent(&event4, tagId, bucketStartTimeNs + 125 * NS_PER_SEC, 1, 10); + gaugeProducer.onMatchedLogEvent(1 /*log matcher index*/, event4); + EXPECT_EQ(2L, gaugeProducer.mCurrentBucketNum); + ASSERT_EQ(3UL, gaugeProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size()); + EXPECT_EQ(2, anomalyTracker->getSumOverPastBuckets(DEFAULT_METRIC_DIMENSION_KEY)); +} + +TEST_P(GaugeMetricProducerTest_PartialBucket, TestPulled) { + GaugeMetric metric; + metric.set_id(metricId); + metric.set_bucket(ONE_MINUTE); + metric.set_max_pull_delay_sec(INT_MAX); + auto gaugeFieldMatcher = metric.mutable_gauge_fields_filter()->mutable_fields(); + gaugeFieldMatcher->set_field(tagId); + gaugeFieldMatcher->add_child()->set_field(2); + + sp wizard = new NaggyMock(); + + sp eventMatcherWizard = + createEventMatcherWizard(tagId, logEventMatcherIndex); + + sp pullerManager = new StrictMock(); + EXPECT_CALL(*pullerManager, RegisterReceiver(tagId, kConfigKey, _, _, _)).WillOnce(Return()); + EXPECT_CALL(*pullerManager, UnRegisterReceiver(tagId, kConfigKey, _)).WillOnce(Return()); + EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _)) + .WillOnce(Return(false)) + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector>* data) { + EXPECT_EQ(eventTimeNs, partialBucketSplitTimeNs); + data->clear(); + data->push_back(CreateRepeatedValueLogEvent(tagId, eventTimeNs, 2)); + return true; + })); + + GaugeMetricProducer gaugeProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, {}, + wizard, protoHash, logEventMatcherIndex, eventMatcherWizard, + tagId, -1, tagId, bucketStartTimeNs, bucketStartTimeNs, + pullerManager); + gaugeProducer.prepareFirstBucket(); + + vector> allData; + allData.push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 1, 1)); + gaugeProducer.onDataPulled(allData, /** succeed */ true, bucketStartTimeNs); + ASSERT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->size()); + EXPECT_EQ(1, gaugeProducer.mCurrentSlicedBucket->begin() + ->second.front() + .mFields->begin() + ->mValue.int_value); + + switch (GetParam()) { + case APP_UPGRADE: + gaugeProducer.notifyAppUpgrade(partialBucketSplitTimeNs); + break; + case BOOT_COMPLETE: + gaugeProducer.onStatsdInitCompleted(partialBucketSplitTimeNs); + break; + } + ASSERT_EQ(1UL, gaugeProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size()); + EXPECT_EQ(bucketStartTimeNs, + gaugeProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY][0].mBucketStartNs); + EXPECT_EQ(partialBucketSplitTimeNs, + gaugeProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY][0].mBucketEndNs); + EXPECT_EQ(0L, gaugeProducer.mCurrentBucketNum); + EXPECT_EQ(partialBucketSplitTimeNs, gaugeProducer.mCurrentBucketStartTimeNs); + ASSERT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->size()); + EXPECT_EQ(2, gaugeProducer.mCurrentSlicedBucket->begin() + ->second.front() + .mFields->begin() + ->mValue.int_value); + + allData.clear(); + allData.push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + bucketSizeNs + 1, 3)); + gaugeProducer.onDataPulled(allData, /** succeed */ true, bucketStartTimeNs + bucketSizeNs); + ASSERT_EQ(2UL, gaugeProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size()); + ASSERT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->size()); + EXPECT_EQ(3, gaugeProducer.mCurrentSlicedBucket->begin() + ->second.front() + .mFields->begin() + ->mValue.int_value); +} + +TEST(GaugeMetricProducerTest, TestPulledWithAppUpgradeDisabled) { + GaugeMetric metric; + metric.set_id(metricId); + metric.set_bucket(ONE_MINUTE); + metric.set_max_pull_delay_sec(INT_MAX); + metric.set_split_bucket_for_app_upgrade(false); + auto gaugeFieldMatcher = metric.mutable_gauge_fields_filter()->mutable_fields(); + gaugeFieldMatcher->set_field(tagId); + gaugeFieldMatcher->add_child()->set_field(2); + + sp wizard = new NaggyMock(); + + sp eventMatcherWizard = + createEventMatcherWizard(tagId, logEventMatcherIndex); + + sp pullerManager = new StrictMock(); + EXPECT_CALL(*pullerManager, RegisterReceiver(tagId, kConfigKey, _, _, _)).WillOnce(Return()); + EXPECT_CALL(*pullerManager, UnRegisterReceiver(tagId, kConfigKey, _)).WillOnce(Return()); + EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, bucketStartTimeNs, _)) + .WillOnce(Return(false)); + + GaugeMetricProducer gaugeProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, {}, + wizard, protoHash, logEventMatcherIndex, eventMatcherWizard, + tagId, -1, tagId, bucketStartTimeNs, bucketStartTimeNs, + pullerManager); + gaugeProducer.prepareFirstBucket(); + + vector> allData; + allData.push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 1, 1)); + gaugeProducer.onDataPulled(allData, /** succeed */ true, bucketStartTimeNs); + ASSERT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->size()); + EXPECT_EQ(1, gaugeProducer.mCurrentSlicedBucket->begin() + ->second.front() + .mFields->begin() + ->mValue.int_value); + + gaugeProducer.notifyAppUpgrade(partialBucketSplitTimeNs); + ASSERT_EQ(0UL, gaugeProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size()); + EXPECT_EQ(0L, gaugeProducer.mCurrentBucketNum); + EXPECT_EQ(bucketStartTimeNs, gaugeProducer.mCurrentBucketStartTimeNs); + ASSERT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->size()); + EXPECT_EQ(1, gaugeProducer.mCurrentSlicedBucket->begin() + ->second.front() + .mFields->begin() + ->mValue.int_value); +} + +TEST(GaugeMetricProducerTest, TestPulledEventsWithCondition) { + GaugeMetric metric; + metric.set_id(metricId); + metric.set_bucket(ONE_MINUTE); + metric.set_max_pull_delay_sec(INT_MAX); + auto gaugeFieldMatcher = metric.mutable_gauge_fields_filter()->mutable_fields(); + gaugeFieldMatcher->set_field(tagId); + gaugeFieldMatcher->add_child()->set_field(2); + metric.set_condition(StringToId("SCREEN_ON")); + + sp wizard = new NaggyMock(); + + sp eventMatcherWizard = + createEventMatcherWizard(tagId, logEventMatcherIndex); + + int64_t conditionChangeNs = bucketStartTimeNs + 8; + + sp pullerManager = new StrictMock(); + EXPECT_CALL(*pullerManager, RegisterReceiver(tagId, kConfigKey, _, _, _)).WillOnce(Return()); + EXPECT_CALL(*pullerManager, UnRegisterReceiver(tagId, kConfigKey, _)).WillOnce(Return()); + EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, conditionChangeNs, _)) + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector>* data) { + data->clear(); + data->push_back(CreateRepeatedValueLogEvent(tagId, eventTimeNs + 10, 100)); + return true; + })); + + GaugeMetricProducer gaugeProducer(kConfigKey, metric, 0 /*condition index*/, + {ConditionState::kUnknown}, wizard, protoHash, + logEventMatcherIndex, eventMatcherWizard, tagId, -1, tagId, + bucketStartTimeNs, bucketStartTimeNs, pullerManager); + gaugeProducer.prepareFirstBucket(); + + gaugeProducer.onConditionChanged(true, conditionChangeNs); + ASSERT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->size()); + EXPECT_EQ(100, gaugeProducer.mCurrentSlicedBucket->begin() + ->second.front() + .mFields->begin() + ->mValue.int_value); + ASSERT_EQ(0UL, gaugeProducer.mPastBuckets.size()); + + vector> allData; + allData.clear(); + allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs + 1, 110)); + gaugeProducer.onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs); + + ASSERT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->size()); + EXPECT_EQ(110, gaugeProducer.mCurrentSlicedBucket->begin() + ->second.front() + .mFields->begin() + ->mValue.int_value); + ASSERT_EQ(1UL, gaugeProducer.mPastBuckets.size()); + EXPECT_EQ(100, gaugeProducer.mPastBuckets.begin() + ->second.back() + .mGaugeAtoms.front() + .mFields->begin() + ->mValue.int_value); + + gaugeProducer.onConditionChanged(false, bucket2StartTimeNs + 10); + gaugeProducer.flushIfNeededLocked(bucket3StartTimeNs + 10); + ASSERT_EQ(1UL, gaugeProducer.mPastBuckets.size()); + ASSERT_EQ(2UL, gaugeProducer.mPastBuckets.begin()->second.size()); + EXPECT_EQ(110L, gaugeProducer.mPastBuckets.begin() + ->second.back() + .mGaugeAtoms.front() + .mFields->begin() + ->mValue.int_value); +} + +TEST(GaugeMetricProducerTest, TestPulledEventsWithSlicedCondition) { + const int conditionTag = 65; + GaugeMetric metric; + metric.set_id(1111111); + metric.set_bucket(ONE_MINUTE); + metric.mutable_gauge_fields_filter()->set_include_all(true); + metric.set_condition(StringToId("APP_DIED")); + metric.set_max_pull_delay_sec(INT_MAX); + auto dim = metric.mutable_dimensions_in_what(); + dim->set_field(tagId); + dim->add_child()->set_field(1); + + sp eventMatcherWizard = + createEventMatcherWizard(tagId, logEventMatcherIndex); + + sp wizard = new NaggyMock(); + EXPECT_CALL(*wizard, query(_, _, _)) + .WillRepeatedly( + Invoke([](const int conditionIndex, const ConditionKey& conditionParameters, + const bool isPartialLink) { + int pos[] = {1, 0, 0}; + Field f(conditionTag, pos, 0); + HashableDimensionKey key; + key.mutableValues()->emplace_back(f, Value((int32_t)1000000)); + + return ConditionState::kTrue; + })); + + int64_t sliceConditionChangeNs = bucketStartTimeNs + 8; + + sp pullerManager = new StrictMock(); + EXPECT_CALL(*pullerManager, RegisterReceiver(tagId, kConfigKey, _, _, _)).WillOnce(Return()); + EXPECT_CALL(*pullerManager, UnRegisterReceiver(tagId, kConfigKey, _)).WillOnce(Return()); + EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, sliceConditionChangeNs, _)) + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector>* data) { + data->clear(); + data->push_back(CreateTwoValueLogEvent(tagId, eventTimeNs + 10, 1000, 100)); + return true; + })); + + GaugeMetricProducer gaugeProducer(kConfigKey, metric, 0 /*condition index*/, + {ConditionState::kUnknown}, wizard, protoHash, + logEventMatcherIndex, eventMatcherWizard, tagId, -1, tagId, + bucketStartTimeNs, bucketStartTimeNs, pullerManager); + gaugeProducer.prepareFirstBucket(); + + gaugeProducer.onSlicedConditionMayChange(true, sliceConditionChangeNs); + + ASSERT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->size()); + const auto& key = gaugeProducer.mCurrentSlicedBucket->begin()->first; + ASSERT_EQ(1UL, key.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(1000, key.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + + ASSERT_EQ(0UL, gaugeProducer.mPastBuckets.size()); + + vector> allData; + allData.clear(); + allData.push_back(CreateTwoValueLogEvent(tagId, bucket2StartTimeNs + 1, 1000, 110)); + gaugeProducer.onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs); + + ASSERT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->size()); + ASSERT_EQ(1UL, gaugeProducer.mPastBuckets.size()); +} + +TEST(GaugeMetricProducerTest, TestPulledEventsAnomalyDetection) { + sp alarmMonitor; + sp wizard = new NaggyMock(); + + sp pullerManager = new StrictMock(); + EXPECT_CALL(*pullerManager, RegisterReceiver(tagId, kConfigKey, _, _, _)).WillOnce(Return()); + EXPECT_CALL(*pullerManager, UnRegisterReceiver(tagId, kConfigKey, _)).WillOnce(Return()); + EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, bucketStartTimeNs, _)) + .WillOnce(Return(false)); + + GaugeMetric metric; + metric.set_id(metricId); + metric.set_bucket(ONE_MINUTE); + metric.set_max_pull_delay_sec(INT_MAX); + auto gaugeFieldMatcher = metric.mutable_gauge_fields_filter()->mutable_fields(); + gaugeFieldMatcher->set_field(tagId); + gaugeFieldMatcher->add_child()->set_field(2); + + sp eventMatcherWizard = + createEventMatcherWizard(tagId, logEventMatcherIndex); + + GaugeMetricProducer gaugeProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, {}, + wizard, protoHash, logEventMatcherIndex, eventMatcherWizard, + tagId, -1, tagId, bucketStartTimeNs, bucketStartTimeNs, + pullerManager); + gaugeProducer.prepareFirstBucket(); + + Alert alert; + alert.set_id(101); + alert.set_metric_id(metricId); + alert.set_trigger_if_sum_gt(25); + alert.set_num_buckets(2); + const int32_t refPeriodSec = 60; + alert.set_refractory_period_secs(refPeriodSec); + sp anomalyTracker = + gaugeProducer.addAnomalyTracker(alert, alarmMonitor, UPDATE_NEW, bucketStartTimeNs); + + int tagId = 1; + vector> allData; + allData.clear(); + allData.push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 1, 13)); + gaugeProducer.onDataPulled(allData, /** succeed */ true, bucketStartTimeNs); + ASSERT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->size()); + EXPECT_EQ(13L, gaugeProducer.mCurrentSlicedBucket->begin() + ->second.front() + .mFields->begin() + ->mValue.int_value); + EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(DEFAULT_METRIC_DIMENSION_KEY), 0U); + + std::shared_ptr event2 = + CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + bucketSizeNs + 20, 15); + + allData.clear(); + allData.push_back(event2); + gaugeProducer.onDataPulled(allData, /** succeed */ true, bucketStartTimeNs + bucketSizeNs); + ASSERT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->size()); + EXPECT_EQ(15L, gaugeProducer.mCurrentSlicedBucket->begin() + ->second.front() + .mFields->begin() + ->mValue.int_value); + EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(DEFAULT_METRIC_DIMENSION_KEY), + std::ceil(1.0 * event2->GetElapsedTimestampNs() / NS_PER_SEC) + refPeriodSec); + + allData.clear(); + allData.push_back( + CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 2 * bucketSizeNs + 10, 26)); + gaugeProducer.onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs + 2 * bucketSizeNs); + ASSERT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->size()); + EXPECT_EQ(26L, gaugeProducer.mCurrentSlicedBucket->begin() + ->second.front() + .mFields->begin() + ->mValue.int_value); + EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(DEFAULT_METRIC_DIMENSION_KEY), + std::ceil(1.0 * event2->GetElapsedTimestampNs() / NS_PER_SEC + refPeriodSec)); + + // This event does not have the gauge field. Thus the current bucket value is 0. + allData.clear(); + allData.push_back(CreateNoValuesLogEvent(tagId, bucketStartTimeNs + 3 * bucketSizeNs + 10)); + gaugeProducer.onDataPulled(allData, /** succeed */ true, bucketStartTimeNs + 3 * bucketSizeNs); + ASSERT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->size()); + EXPECT_TRUE(gaugeProducer.mCurrentSlicedBucket->begin()->second.front().mFields->empty()); +} + +TEST(GaugeMetricProducerTest, TestPullOnTrigger) { + GaugeMetric metric; + metric.set_id(metricId); + metric.set_bucket(ONE_MINUTE); + metric.set_sampling_type(GaugeMetric::FIRST_N_SAMPLES); + metric.mutable_gauge_fields_filter()->set_include_all(false); + metric.set_max_pull_delay_sec(INT_MAX); + auto gaugeFieldMatcher = metric.mutable_gauge_fields_filter()->mutable_fields(); + gaugeFieldMatcher->set_field(tagId); + gaugeFieldMatcher->add_child()->set_field(1); + + sp wizard = new NaggyMock(); + + sp eventMatcherWizard = + createEventMatcherWizard(tagId, logEventMatcherIndex); + + sp pullerManager = new StrictMock(); + EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _)) + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector>* data) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 10); + data->clear(); + data->push_back(CreateRepeatedValueLogEvent(tagId, eventTimeNs, 4)); + return true; + })) + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector>* data) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 20); + data->clear(); + data->push_back(CreateRepeatedValueLogEvent(tagId, eventTimeNs, 5)); + return true; + })) + .WillOnce(Return(true)); + + int triggerId = 5; + GaugeMetricProducer gaugeProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, {}, + wizard, protoHash, logEventMatcherIndex, eventMatcherWizard, + tagId, triggerId, tagId, bucketStartTimeNs, bucketStartTimeNs, + pullerManager); + gaugeProducer.prepareFirstBucket(); + + ASSERT_EQ(0UL, gaugeProducer.mCurrentSlicedBucket->size()); + + LogEvent triggerEvent(/*uid=*/0, /*pid=*/0); + CreateNoValuesLogEvent(&triggerEvent, triggerId, bucketStartTimeNs + 10); + gaugeProducer.onMatchedLogEvent(1 /*log matcher index*/, triggerEvent); + ASSERT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->begin()->second.size()); + triggerEvent.setElapsedTimestampNs(bucketStartTimeNs + 20); + gaugeProducer.onMatchedLogEvent(1 /*log matcher index*/, triggerEvent); + ASSERT_EQ(2UL, gaugeProducer.mCurrentSlicedBucket->begin()->second.size()); + triggerEvent.setElapsedTimestampNs(bucket2StartTimeNs + 1); + gaugeProducer.onMatchedLogEvent(1 /*log matcher index*/, triggerEvent); + + ASSERT_EQ(1UL, gaugeProducer.mPastBuckets.size()); + ASSERT_EQ(2UL, gaugeProducer.mPastBuckets.begin()->second.back().mGaugeAtoms.size()); + EXPECT_EQ(4, gaugeProducer.mPastBuckets.begin() + ->second.back() + .mGaugeAtoms[0] + .mFields->begin() + ->mValue.int_value); + EXPECT_EQ(5, gaugeProducer.mPastBuckets.begin() + ->second.back() + .mGaugeAtoms[1] + .mFields->begin() + ->mValue.int_value); +} + +TEST(GaugeMetricProducerTest, TestRemoveDimensionInOutput) { + GaugeMetric metric; + metric.set_id(metricId); + metric.set_bucket(ONE_MINUTE); + metric.set_sampling_type(GaugeMetric::FIRST_N_SAMPLES); + metric.mutable_gauge_fields_filter()->set_include_all(true); + metric.set_max_pull_delay_sec(INT_MAX); + auto dimensionMatcher = metric.mutable_dimensions_in_what(); + // use field 1 as dimension. + dimensionMatcher->set_field(tagId); + dimensionMatcher->add_child()->set_field(1); + + sp wizard = new NaggyMock(); + + sp eventMatcherWizard = + createEventMatcherWizard(tagId, logEventMatcherIndex); + + sp pullerManager = new StrictMock(); + EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _)) + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector>* data) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 3); + data->clear(); + data->push_back(CreateTwoValueLogEvent(tagId, eventTimeNs, 3, 4)); + return true; + })) + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector>* data) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 10); + data->clear(); + data->push_back(CreateTwoValueLogEvent(tagId, eventTimeNs, 4, 5)); + return true; + })) + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector>* data) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 20); + data->clear(); + data->push_back(CreateTwoValueLogEvent(tagId, eventTimeNs, 4, 6)); + return true; + })) + .WillOnce(Return(true)); + + int triggerId = 5; + GaugeMetricProducer gaugeProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, {}, + wizard, protoHash, logEventMatcherIndex, eventMatcherWizard, + tagId, triggerId, tagId, bucketStartTimeNs, bucketStartTimeNs, + pullerManager); + gaugeProducer.prepareFirstBucket(); + + LogEvent triggerEvent(/*uid=*/0, /*pid=*/0); + CreateNoValuesLogEvent(&triggerEvent, triggerId, bucketStartTimeNs + 3); + gaugeProducer.onMatchedLogEvent(1 /*log matcher index*/, triggerEvent); + ASSERT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->size()); + triggerEvent.setElapsedTimestampNs(bucketStartTimeNs + 10); + gaugeProducer.onMatchedLogEvent(1 /*log matcher index*/, triggerEvent); + ASSERT_EQ(2UL, gaugeProducer.mCurrentSlicedBucket->size()); + ASSERT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->begin()->second.size()); + triggerEvent.setElapsedTimestampNs(bucketStartTimeNs + 20); + gaugeProducer.onMatchedLogEvent(1 /*log matcher index*/, triggerEvent); + ASSERT_EQ(2UL, gaugeProducer.mCurrentSlicedBucket->begin()->second.size()); + triggerEvent.setElapsedTimestampNs(bucket2StartTimeNs + 1); + gaugeProducer.onMatchedLogEvent(1 /*log matcher index*/, triggerEvent); + + ASSERT_EQ(2UL, gaugeProducer.mPastBuckets.size()); + auto bucketIt = gaugeProducer.mPastBuckets.begin(); + ASSERT_EQ(1UL, bucketIt->second.back().mGaugeAtoms.size()); + EXPECT_EQ(3, bucketIt->first.getDimensionKeyInWhat().getValues().begin()->mValue.int_value); + EXPECT_EQ(4, bucketIt->second.back().mGaugeAtoms[0].mFields->begin()->mValue.int_value); + bucketIt++; + ASSERT_EQ(2UL, bucketIt->second.back().mGaugeAtoms.size()); + EXPECT_EQ(4, bucketIt->first.getDimensionKeyInWhat().getValues().begin()->mValue.int_value); + EXPECT_EQ(5, bucketIt->second.back().mGaugeAtoms[0].mFields->begin()->mValue.int_value); + EXPECT_EQ(6, bucketIt->second.back().mGaugeAtoms[1].mFields->begin()->mValue.int_value); +} + +/* + * Test that BUCKET_TOO_SMALL dump reason is logged when a flushed bucket size + * is smaller than the "min_bucket_size_nanos" specified in the metric config. + */ +TEST(GaugeMetricProducerTest_BucketDrop, TestBucketDropWhenBucketTooSmall) { + GaugeMetric metric; + metric.set_id(metricId); + metric.set_bucket(FIVE_MINUTES); + metric.set_sampling_type(GaugeMetric::FIRST_N_SAMPLES); + metric.set_min_bucket_size_nanos(10000000000); // 10 seconds + + sp wizard = new NaggyMock(); + + sp eventMatcherWizard = + createEventMatcherWizard(tagId, logEventMatcherIndex); + + sp pullerManager = new StrictMock(); + EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, bucketStartTimeNs + 3, _)) + // Bucket start. + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector>* data) { + data->clear(); + data->push_back(CreateRepeatedValueLogEvent(tagId, eventTimeNs, 10)); + return true; + })); + + int triggerId = 5; + GaugeMetricProducer gaugeProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, {}, + wizard, protoHash, logEventMatcherIndex, eventMatcherWizard, + tagId, triggerId, tagId, bucketStartTimeNs, bucketStartTimeNs, + pullerManager); + gaugeProducer.prepareFirstBucket(); + + LogEvent triggerEvent(/*uid=*/0, /*pid=*/0); + CreateNoValuesLogEvent(&triggerEvent, triggerId, bucketStartTimeNs + 3); + gaugeProducer.onMatchedLogEvent(1 /*log matcher index*/, triggerEvent); + + // Check dump report. + ProtoOutputStream output; + std::set strSet; + gaugeProducer.onDumpReport(bucketStartTimeNs + 9000000, true /* include recent buckets */, true, + FAST /* dump_latency */, &strSet, &output); + + StatsLogReport report = outputStreamToProto(&output); + EXPECT_TRUE(report.has_gauge_metrics()); + ASSERT_EQ(0, report.gauge_metrics().data_size()); + ASSERT_EQ(1, report.gauge_metrics().skipped_size()); + + EXPECT_EQ(NanoToMillis(bucketStartTimeNs), + report.gauge_metrics().skipped(0).start_bucket_elapsed_millis()); + EXPECT_EQ(NanoToMillis(bucketStartTimeNs + 9000000), + report.gauge_metrics().skipped(0).end_bucket_elapsed_millis()); + ASSERT_EQ(1, report.gauge_metrics().skipped(0).drop_event_size()); + + auto dropEvent = report.gauge_metrics().skipped(0).drop_event(0); + EXPECT_EQ(BucketDropReason::BUCKET_TOO_SMALL, dropEvent.drop_reason()); + EXPECT_EQ(NanoToMillis(bucketStartTimeNs + 9000000), dropEvent.drop_time_millis()); +} + +} // namespace statsd +} // namespace os +} // namespace android +#else +GTEST_LOG_(INFO) << "This test does nothing.\n"; +#endif diff --git a/statsd/tests/metrics/MaxDurationTracker_test.cpp b/statsd/tests/metrics/MaxDurationTracker_test.cpp new file mode 100644 index 00000000..25eaf887 --- /dev/null +++ b/statsd/tests/metrics/MaxDurationTracker_test.cpp @@ -0,0 +1,459 @@ +// 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 "src/metrics/duration_helper/MaxDurationTracker.h" +#include "src/condition/ConditionWizard.h" +#include "metrics_test_helper.h" +#include "tests/statsd_test_util.h" + +#include +#include +#include +#include +#include +#include + +using namespace android::os::statsd; +using namespace testing; +using android::sp; +using std::set; +using std::unordered_map; +using std::vector; + +#ifdef __ANDROID__ + +namespace android { +namespace os { +namespace statsd { + +const ConfigKey kConfigKey(0, 12345); + +const int TagId = 1; +const int64_t metricId = 123; +const optional emptyThreshold; + +const MetricDimensionKey eventKey = getMockedMetricDimensionKey(TagId, 0, "1"); +const HashableDimensionKey conditionKey = getMockedDimensionKey(TagId, 4, "1"); +const HashableDimensionKey key1 = getMockedDimensionKey(TagId, 1, "1"); +const HashableDimensionKey key2 = getMockedDimensionKey(TagId, 1, "2"); +const int64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL; + +TEST(MaxDurationTrackerTest, TestSimpleMaxDuration) { + const MetricDimensionKey eventKey = getMockedMetricDimensionKey(TagId, 0, "1"); + const HashableDimensionKey key1 = getMockedDimensionKey(TagId, 1, "1"); + const HashableDimensionKey key2 = getMockedDimensionKey(TagId, 1, "2"); + + + sp wizard = new NaggyMock(); + + unordered_map> buckets; + + int64_t bucketStartTimeNs = 10000000000; + int64_t bucketEndTimeNs = bucketStartTimeNs + bucketSizeNs; + int64_t bucketNum = 0; + + int64_t metricId = 1; + MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, -1, false, bucketStartTimeNs, + bucketNum, bucketStartTimeNs, bucketSizeNs, false, false, {}); + + tracker.noteStart(key1, true, bucketStartTimeNs, ConditionKey()); + // Event starts again. This would not change anything as it already starts. + tracker.noteStart(key1, true, bucketStartTimeNs + 3, ConditionKey()); + // Stopped. + tracker.noteStop(key1, bucketStartTimeNs + 10, false); + + // Another event starts in this bucket. + tracker.noteStart(key2, true, bucketStartTimeNs + 20, ConditionKey()); + tracker.noteStop(key2, bucketStartTimeNs + 40, false /*stop all*/); + + tracker.flushIfNeeded(bucketStartTimeNs + bucketSizeNs + 1, emptyThreshold, &buckets); + EXPECT_TRUE(buckets.find(eventKey) != buckets.end()); + ASSERT_EQ(1u, buckets[eventKey].size()); + EXPECT_EQ(20LL, buckets[eventKey][0].mDuration); +} + +TEST(MaxDurationTrackerTest, TestStopAll) { + const MetricDimensionKey eventKey = getMockedMetricDimensionKey(TagId, 0, "1"); + const HashableDimensionKey key1 = getMockedDimensionKey(TagId, 1, "1"); + const HashableDimensionKey key2 = getMockedDimensionKey(TagId, 1, "2"); + + sp wizard = new NaggyMock(); + + unordered_map> buckets; + + int64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL; + int64_t bucketStartTimeNs = 10000000000; + int64_t bucketEndTimeNs = bucketStartTimeNs + bucketSizeNs; + int64_t bucketNum = 0; + + int64_t metricId = 1; + MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, -1, false, bucketStartTimeNs, + bucketNum, bucketStartTimeNs, bucketSizeNs, false, false, {}); + + tracker.noteStart(key1, true, bucketStartTimeNs + 1, ConditionKey()); + + // Another event starts in this bucket. + tracker.noteStart(key2, true, bucketStartTimeNs + 20, ConditionKey()); + tracker.flushIfNeeded(bucketStartTimeNs + bucketSizeNs + 40, emptyThreshold, &buckets); + tracker.noteStopAll(bucketStartTimeNs + bucketSizeNs + 40); + EXPECT_TRUE(tracker.mInfos.empty()); + EXPECT_TRUE(buckets.find(eventKey) == buckets.end()); + + tracker.flushIfNeeded(bucketStartTimeNs + 3 * bucketSizeNs + 40, emptyThreshold, &buckets); + EXPECT_TRUE(buckets.find(eventKey) != buckets.end()); + ASSERT_EQ(1u, buckets[eventKey].size()); + EXPECT_EQ(bucketSizeNs + 40 - 1, buckets[eventKey][0].mDuration); + EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, buckets[eventKey][0].mBucketStartNs); + EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs, buckets[eventKey][0].mBucketEndNs); +} + +TEST(MaxDurationTrackerTest, TestCrossBucketBoundary) { + const MetricDimensionKey eventKey = getMockedMetricDimensionKey(TagId, 0, "1"); + const HashableDimensionKey key1 = getMockedDimensionKey(TagId, 1, "1"); + const HashableDimensionKey key2 = getMockedDimensionKey(TagId, 1, "2"); + sp wizard = new NaggyMock(); + + unordered_map> buckets; + + int64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL; + int64_t bucketStartTimeNs = 10000000000; + int64_t bucketEndTimeNs = bucketStartTimeNs + bucketSizeNs; + int64_t bucketNum = 0; + + int64_t metricId = 1; + MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, -1, false, bucketStartTimeNs, + bucketNum, bucketStartTimeNs, bucketSizeNs, false, false, {}); + + // The event starts. + tracker.noteStart(DEFAULT_DIMENSION_KEY, true, bucketStartTimeNs + 1, ConditionKey()); + + // Starts again. Does not DEFAULT_DIMENSION_KEY anything. + tracker.noteStart(DEFAULT_DIMENSION_KEY, true, bucketStartTimeNs + bucketSizeNs + 1, + ConditionKey()); + + // The event stops at early 4th bucket. + // Notestop is called from DurationMetricProducer's onMatchedLogEvent, which calls + // flushIfneeded. + tracker.flushIfNeeded(bucketStartTimeNs + (3 * bucketSizeNs) + 20, emptyThreshold, &buckets); + tracker.noteStop(DEFAULT_DIMENSION_KEY, bucketStartTimeNs + (3 * bucketSizeNs) + 20, + false /*stop all*/); + EXPECT_TRUE(buckets.find(eventKey) == buckets.end()); + + tracker.flushIfNeeded(bucketStartTimeNs + 4 * bucketSizeNs, emptyThreshold, &buckets); + ASSERT_EQ(1u, buckets[eventKey].size()); + EXPECT_EQ((3 * bucketSizeNs) + 20 - 1, buckets[eventKey][0].mDuration); + EXPECT_EQ(bucketStartTimeNs + 3 * bucketSizeNs, buckets[eventKey][0].mBucketStartNs); + EXPECT_EQ(bucketStartTimeNs + 4 * bucketSizeNs, buckets[eventKey][0].mBucketEndNs); +} + +TEST(MaxDurationTrackerTest, TestCrossBucketBoundary_nested) { + const MetricDimensionKey eventKey = getMockedMetricDimensionKey(TagId, 0, "1"); + const HashableDimensionKey key1 = getMockedDimensionKey(TagId, 1, "1"); + const HashableDimensionKey key2 = getMockedDimensionKey(TagId, 1, "2"); + sp wizard = new NaggyMock(); + + unordered_map> buckets; + + int64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL; + int64_t bucketStartTimeNs = 10000000000; + int64_t bucketEndTimeNs = bucketStartTimeNs + bucketSizeNs; + int64_t bucketNum = 0; + + int64_t metricId = 1; + MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, -1, true, bucketStartTimeNs, + bucketNum, bucketStartTimeNs, bucketSizeNs, false, false, {}); + + // 2 starts + tracker.noteStart(DEFAULT_DIMENSION_KEY, true, bucketStartTimeNs + 1, ConditionKey()); + tracker.noteStart(DEFAULT_DIMENSION_KEY, true, bucketStartTimeNs + 10, ConditionKey()); + // one stop + tracker.noteStop(DEFAULT_DIMENSION_KEY, bucketStartTimeNs + 20, false /*stop all*/); + + tracker.flushIfNeeded(bucketStartTimeNs + (2 * bucketSizeNs) + 1, emptyThreshold, &buckets); + // Because of nesting, still not stopped. + EXPECT_TRUE(buckets.find(eventKey) == buckets.end()); + + // real stop now. + tracker.noteStop(DEFAULT_DIMENSION_KEY, + bucketStartTimeNs + (2 * bucketSizeNs) + 5, false); + tracker.flushIfNeeded(bucketStartTimeNs + (3 * bucketSizeNs) + 1, emptyThreshold, &buckets); + + ASSERT_EQ(1u, buckets[eventKey].size()); + EXPECT_EQ(2 * bucketSizeNs + 5 - 1, buckets[eventKey][0].mDuration); +} + +TEST(MaxDurationTrackerTest, TestMaxDurationWithCondition) { + const HashableDimensionKey conditionDimKey = key1; + + sp wizard = new NaggyMock(); + + ConditionKey conditionKey1; + MetricDimensionKey eventKey = getMockedMetricDimensionKey(TagId, 1, "1"); + conditionKey1[StringToId("APP_BACKGROUND")] = conditionDimKey; + + /** + Start in first bucket, stop in second bucket. Condition turns on and off in the first bucket + and again turns on and off in the second bucket. + */ + int64_t bucketStartTimeNs = 10000000000; + int64_t bucketEndTimeNs = bucketStartTimeNs + bucketSizeNs; + int64_t eventStartTimeNs = bucketStartTimeNs + 1 * NS_PER_SEC; + int64_t conditionStarts1 = bucketStartTimeNs + 11 * NS_PER_SEC; + int64_t conditionStops1 = bucketStartTimeNs + 14 * NS_PER_SEC; + int64_t conditionStarts2 = bucketStartTimeNs + bucketSizeNs + 5 * NS_PER_SEC; + int64_t conditionStops2 = conditionStarts2 + 10 * NS_PER_SEC; + int64_t eventStopTimeNs = conditionStops2 + 8 * NS_PER_SEC; + + int64_t metricId = 1; + MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, false, bucketStartTimeNs, + 0, bucketStartTimeNs, bucketSizeNs, true, false, {}); + EXPECT_TRUE(tracker.mAnomalyTrackers.empty()); + + tracker.noteStart(key1, false, eventStartTimeNs, conditionKey1); + tracker.noteConditionChanged(key1, true, conditionStarts1); + tracker.noteConditionChanged(key1, false, conditionStops1); + unordered_map> buckets; + tracker.flushIfNeeded(bucketStartTimeNs + bucketSizeNs + 1, emptyThreshold, &buckets); + ASSERT_EQ(0U, buckets.size()); + + tracker.noteConditionChanged(key1, true, conditionStarts2); + tracker.noteConditionChanged(key1, false, conditionStops2); + tracker.noteStop(key1, eventStopTimeNs, false); + tracker.flushIfNeeded(bucketStartTimeNs + 2 * bucketSizeNs + 1, emptyThreshold, &buckets); + ASSERT_EQ(1U, buckets.size()); + vector item = buckets.begin()->second; + ASSERT_EQ(1UL, item.size()); + EXPECT_EQ((int64_t)(13LL * NS_PER_SEC), item[0].mDuration); +} + +TEST(MaxDurationTrackerTest, TestAnomalyDetection) { + sp wizard = new NaggyMock(); + + ConditionKey conditionKey1; + MetricDimensionKey eventKey = getMockedMetricDimensionKey(TagId, 2, "maps"); + conditionKey1[StringToId("APP_BACKGROUND")] = conditionKey; + + unordered_map> buckets; + + int64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL; + int64_t bucketStartTimeNs = 10000000000; + int64_t bucketEndTimeNs = bucketStartTimeNs + bucketSizeNs; + int64_t bucketNum = 0; + int64_t eventStartTimeNs = 13000000000; + int64_t durationTimeNs = 2 * 1000; + + int64_t metricId = 1; + Alert alert; + alert.set_id(101); + alert.set_metric_id(1); + alert.set_trigger_if_sum_gt(40 * NS_PER_SEC); + alert.set_num_buckets(2); + const int32_t refPeriodSec = 45; + alert.set_refractory_period_secs(refPeriodSec); + sp alarmMonitor; + sp anomalyTracker = + new DurationAnomalyTracker(alert, kConfigKey, alarmMonitor); + MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, false, bucketStartTimeNs, + bucketNum, bucketStartTimeNs, bucketSizeNs, true, false, + {anomalyTracker}); + + tracker.noteStart(key1, true, eventStartTimeNs, conditionKey1); + sp alarm = anomalyTracker->mAlarms.begin()->second; + EXPECT_EQ((long long)(53ULL * NS_PER_SEC), (long long)(alarm->timestampSec * NS_PER_SEC)); + + // Remove the anomaly alarm when the duration is no longer fully met. + tracker.noteConditionChanged(key1, false, eventStartTimeNs + 15 * NS_PER_SEC); + ASSERT_EQ(0U, anomalyTracker->mAlarms.size()); + + // Since the condition was off for 10 seconds, the anomaly should trigger 10 sec later. + tracker.noteConditionChanged(key1, true, eventStartTimeNs + 25 * NS_PER_SEC); + ASSERT_EQ(1U, anomalyTracker->mAlarms.size()); + alarm = anomalyTracker->mAlarms.begin()->second; + EXPECT_EQ((long long)(63ULL * NS_PER_SEC), (long long)(alarm->timestampSec * NS_PER_SEC)); +} + +// This tests that we correctly compute the predicted time of an anomaly assuming that the current +// state continues forward as-is. +TEST(MaxDurationTrackerTest, TestAnomalyPredictedTimestamp) { + sp wizard = new NaggyMock(); + + ConditionKey conditionKey1; + MetricDimensionKey eventKey = getMockedMetricDimensionKey(TagId, 2, "maps"); + conditionKey1[StringToId("APP_BACKGROUND")] = conditionKey; + ConditionKey conditionKey2; + conditionKey2[StringToId("APP_BACKGROUND")] = getMockedDimensionKey(TagId, 4, "2"); + + unordered_map> buckets; + + /** + * Suppose we have two sub-dimensions that we're taking the MAX over. In the first of these + * nested dimensions, we enter the pause state after 3 seconds. When we resume, the second + * dimension has already been running for 4 seconds. Thus, we have 40-4=36 seconds remaining + * before we trigger the anomaly. + */ + int64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL; + int64_t bucketStartTimeNs = 10000000000; + int64_t bucketEndTimeNs = bucketStartTimeNs + bucketSizeNs; + int64_t bucketNum = 0; + int64_t eventStartTimeNs = bucketStartTimeNs + 5 * NS_PER_SEC; // Condition is off at start. + int64_t conditionStarts1 = bucketStartTimeNs + 11 * NS_PER_SEC; + int64_t conditionStops1 = bucketStartTimeNs + 14 * NS_PER_SEC; + int64_t conditionStarts2 = bucketStartTimeNs + 20 * NS_PER_SEC; + int64_t eventStartTimeNs2 = conditionStarts2 - 4 * NS_PER_SEC; + + int64_t metricId = 1; + Alert alert; + alert.set_id(101); + alert.set_metric_id(1); + alert.set_trigger_if_sum_gt(40 * NS_PER_SEC); + alert.set_num_buckets(2); + const int32_t refPeriodSec = 45; + alert.set_refractory_period_secs(refPeriodSec); + sp alarmMonitor; + sp anomalyTracker = + new DurationAnomalyTracker(alert, kConfigKey, alarmMonitor); + MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, false, bucketStartTimeNs, + bucketNum, bucketStartTimeNs, bucketSizeNs, true, false, + {anomalyTracker}); + + tracker.noteStart(key1, false, eventStartTimeNs, conditionKey1); + tracker.noteConditionChanged(key1, true, conditionStarts1); + tracker.noteConditionChanged(key1, false, conditionStops1); + tracker.noteStart(key2, true, eventStartTimeNs2, conditionKey2); // Condition is on already. + tracker.noteConditionChanged(key1, true, conditionStarts2); + ASSERT_EQ(1U, anomalyTracker->mAlarms.size()); + auto alarm = anomalyTracker->mAlarms.begin()->second; + int64_t anomalyFireTimeSec = alarm->timestampSec; + EXPECT_EQ(conditionStarts2 + 36 * NS_PER_SEC, + (long long)anomalyFireTimeSec * NS_PER_SEC); + + // Now we test the calculation now that there's a refractory period. + // At the correct time, declare the anomaly. This will set a refractory period. Make sure it + // gets correctly taken into account in future predictAnomalyTimestampNs calculations. + std::unordered_set, SpHash> firedAlarms({alarm}); + anomalyTracker->informAlarmsFired(anomalyFireTimeSec * NS_PER_SEC, firedAlarms); + ASSERT_EQ(0u, anomalyTracker->mAlarms.size()); + int64_t refractoryPeriodEndsSec = anomalyFireTimeSec + refPeriodSec; + EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(eventKey), refractoryPeriodEndsSec); + + // Now stop and start again. Make sure the new predictAnomalyTimestampNs takes into account + // the refractory period correctly. + int64_t eventStopTimeNs = anomalyFireTimeSec * NS_PER_SEC + 10; + tracker.noteStop(key1, eventStopTimeNs, false); + tracker.noteStop(key2, eventStopTimeNs, false); + tracker.noteStart(key1, true, eventStopTimeNs + 1000000, conditionKey1); + // Anomaly is ongoing, but we're still in the refractory period. + ASSERT_EQ(1U, anomalyTracker->mAlarms.size()); + alarm = anomalyTracker->mAlarms.begin()->second; + EXPECT_EQ(refractoryPeriodEndsSec, (long long)(alarm->timestampSec)); + + // Makes sure it is correct after the refractory period is over. + tracker.noteStop(key1, eventStopTimeNs + 2000000, false); + int64_t justBeforeRefPeriodNs = (refractoryPeriodEndsSec - 2) * NS_PER_SEC; + tracker.noteStart(key1, true, justBeforeRefPeriodNs, conditionKey1); + alarm = anomalyTracker->mAlarms.begin()->second; + EXPECT_EQ(justBeforeRefPeriodNs + 40 * NS_PER_SEC, + (unsigned long long)(alarm->timestampSec * NS_PER_SEC)); +} + +// Suppose that within one tracker there are two dimensions A and B. +// Suppose A starts, then B starts, and then A stops. We still need to set an anomaly based on the +// elapsed duration of B. +TEST(MaxDurationTrackerTest, TestAnomalyPredictedTimestamp_UpdatedOnStop) { + sp wizard = new NaggyMock(); + + ConditionKey conditionKey1; + MetricDimensionKey eventKey = getMockedMetricDimensionKey(TagId, 2, "maps"); + conditionKey1[StringToId("APP_BACKGROUND")] = conditionKey; + ConditionKey conditionKey2; + conditionKey2[StringToId("APP_BACKGROUND")] = getMockedDimensionKey(TagId, 4, "2"); + + unordered_map> buckets; + + /** + * Suppose we have two sub-dimensions that we're taking the MAX over. In the first of these + * nested dimensions, are started for 8 seconds. When we stop, the other nested dimension has + * been started for 5 seconds. So we can only allow 35 more seconds from now. + */ + int64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL; + int64_t bucketStartTimeNs = 10000000000; + int64_t bucketEndTimeNs = bucketStartTimeNs + bucketSizeNs; + int64_t bucketNum = 0; + int64_t eventStartTimeNs1 = bucketStartTimeNs + 5 * NS_PER_SEC; // Condition is off at start. + int64_t eventStopTimeNs1 = bucketStartTimeNs + 13 * NS_PER_SEC; + int64_t eventStartTimeNs2 = bucketStartTimeNs + 8 * NS_PER_SEC; + + int64_t metricId = 1; + Alert alert; + alert.set_id(101); + alert.set_metric_id(1); + alert.set_trigger_if_sum_gt(40 * NS_PER_SEC); + alert.set_num_buckets(2); + const int32_t refPeriodSec = 45; + alert.set_refractory_period_secs(refPeriodSec); + sp alarmMonitor; + sp anomalyTracker = + new DurationAnomalyTracker(alert, kConfigKey, alarmMonitor); + MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, false, bucketStartTimeNs, + bucketNum, bucketStartTimeNs, bucketSizeNs, true, false, + {anomalyTracker}); + + tracker.noteStart(key1, true, eventStartTimeNs1, conditionKey1); + tracker.noteStart(key2, true, eventStartTimeNs2, conditionKey2); + tracker.noteStop(key1, eventStopTimeNs1, false); + ASSERT_EQ(1U, anomalyTracker->mAlarms.size()); + auto alarm = anomalyTracker->mAlarms.begin()->second; + EXPECT_EQ(eventStopTimeNs1 + 35 * NS_PER_SEC, + (unsigned long long)(alarm->timestampSec * NS_PER_SEC)); +} + +TEST(MaxDurationTrackerTest, TestUploadThreshold) { + sp wizard = new NaggyMock(); + + unordered_map> buckets; + + int64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL; + int64_t bucketStartTimeNs = 10000000000; + int64_t bucketNum = 0; + int64_t eventStartTimeNs = bucketStartTimeNs + 1; + int64_t event2StartTimeNs = bucketStartTimeNs + bucketSizeNs + 1; + int64_t thresholdDurationNs = 2000; + + UploadThreshold threshold; + threshold.set_gt_int(thresholdDurationNs); + + MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, false, bucketStartTimeNs, + bucketNum, bucketStartTimeNs, bucketSizeNs, false, false, {}); + + // Duration below the gt_int threshold should not be added to past buckets. + tracker.noteStart(key1, true, eventStartTimeNs, ConditionKey()); + tracker.noteStop(key1, eventStartTimeNs + thresholdDurationNs, false); + tracker.flushIfNeeded(eventStartTimeNs + bucketSizeNs + 1, threshold, &buckets); + EXPECT_TRUE(buckets.find(eventKey) == buckets.end()); + + // Duration above the gt_int threshold should be added to past buckets. + tracker.noteStart(key1, true, event2StartTimeNs, ConditionKey()); + tracker.noteStop(key1, event2StartTimeNs + thresholdDurationNs + 1, false); + tracker.flushIfNeeded(event2StartTimeNs + bucketSizeNs + 1, threshold, &buckets); + EXPECT_TRUE(buckets.find(eventKey) != buckets.end()); + ASSERT_EQ(1u, buckets[eventKey].size()); + EXPECT_EQ(thresholdDurationNs + 1, buckets[eventKey][0].mDuration); +} + +} // namespace statsd +} // namespace os +} // namespace android +#else +GTEST_LOG_(INFO) << "This test does nothing.\n"; +#endif diff --git a/statsd/tests/metrics/OringDurationTracker_test.cpp b/statsd/tests/metrics/OringDurationTracker_test.cpp new file mode 100644 index 00000000..369f4738 --- /dev/null +++ b/statsd/tests/metrics/OringDurationTracker_test.cpp @@ -0,0 +1,611 @@ +// 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 "src/metrics/duration_helper/OringDurationTracker.h" +#include "src/condition/ConditionWizard.h" +#include "metrics_test_helper.h" +#include "tests/statsd_test_util.h" + +#include +#include +#include +#include +#include +#include +#include + +using namespace testing; +using android::sp; +using std::set; +using std::unordered_map; +using std::vector; + +#ifdef __ANDROID__ +namespace android { +namespace os { +namespace statsd { + +const ConfigKey kConfigKey(0, 12345); +const int TagId = 1; +const int64_t metricId = 123; +const optional emptyThreshold; +const MetricDimensionKey eventKey = getMockedMetricDimensionKey(TagId, 0, "event"); + +const HashableDimensionKey kConditionKey1 = getMockedDimensionKey(TagId, 1, "maps"); +const HashableDimensionKey kEventKey1 = getMockedDimensionKey(TagId, 2, "maps"); +const HashableDimensionKey kEventKey2 = getMockedDimensionKey(TagId, 3, "maps"); +const int64_t bucketSizeNs = 30 * NS_PER_SEC; + +TEST(OringDurationTrackerTest, TestDurationOverlap) { + const MetricDimensionKey eventKey = getMockedMetricDimensionKey(TagId, 0, "event"); + + const HashableDimensionKey kEventKey1 = getMockedDimensionKey(TagId, 2, "maps"); + const HashableDimensionKey kEventKey2 = getMockedDimensionKey(TagId, 3, "maps"); + sp wizard = new NaggyMock(); + + unordered_map> buckets; + + int64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL; + int64_t bucketStartTimeNs = 10000000000; + int64_t bucketNum = 0; + int64_t eventStartTimeNs = bucketStartTimeNs + 1; + int64_t durationTimeNs = 2 * 1000; + + OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, false, + bucketStartTimeNs, bucketNum, bucketStartTimeNs, bucketSizeNs, + false, false, {}); + + tracker.noteStart(kEventKey1, true, eventStartTimeNs, ConditionKey()); + EXPECT_EQ((long long)eventStartTimeNs, tracker.mLastStartTime); + tracker.noteStart(kEventKey1, true, eventStartTimeNs + 10, ConditionKey()); // overlapping wl + EXPECT_EQ((long long)eventStartTimeNs, tracker.mLastStartTime); + + tracker.noteStop(kEventKey1, eventStartTimeNs + durationTimeNs, false); + tracker.flushIfNeeded(eventStartTimeNs + bucketSizeNs + 1, emptyThreshold, &buckets); + EXPECT_TRUE(buckets.find(eventKey) != buckets.end()); + + ASSERT_EQ(1u, buckets[eventKey].size()); + EXPECT_EQ(durationTimeNs, buckets[eventKey][0].mDuration); +} + +TEST(OringDurationTrackerTest, TestDurationNested) { + const MetricDimensionKey eventKey = getMockedMetricDimensionKey(TagId, 0, "event"); + + const HashableDimensionKey kEventKey1 = getMockedDimensionKey(TagId, 2, "maps"); + const HashableDimensionKey kEventKey2 = getMockedDimensionKey(TagId, 3, "maps"); + sp wizard = new NaggyMock(); + + unordered_map> buckets; + + int64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL; + int64_t bucketStartTimeNs = 10000000000; + int64_t bucketNum = 0; + int64_t eventStartTimeNs = bucketStartTimeNs + 1; + + OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, true, bucketStartTimeNs, + bucketNum, bucketStartTimeNs, bucketSizeNs, false, false, {}); + + tracker.noteStart(kEventKey1, true, eventStartTimeNs, ConditionKey()); + tracker.noteStart(kEventKey1, true, eventStartTimeNs + 10, ConditionKey()); // overlapping wl + + tracker.noteStop(kEventKey1, eventStartTimeNs + 2000, false); + tracker.noteStop(kEventKey1, eventStartTimeNs + 2003, false); + + tracker.flushIfNeeded(bucketStartTimeNs + bucketSizeNs + 1, emptyThreshold, &buckets); + EXPECT_TRUE(buckets.find(eventKey) != buckets.end()); + ASSERT_EQ(1u, buckets[eventKey].size()); + EXPECT_EQ(2003LL, buckets[eventKey][0].mDuration); +} + +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"); + sp wizard = new NaggyMock(); + + unordered_map> buckets; + + int64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL; + int64_t bucketStartTimeNs = 10000000000; + int64_t bucketNum = 0; + int64_t eventStartTimeNs = bucketStartTimeNs + 1; + + OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, true, bucketStartTimeNs, + bucketNum, bucketStartTimeNs, bucketSizeNs, false, false, {}); + + tracker.noteStart(kEventKey1, true, eventStartTimeNs, ConditionKey()); + tracker.noteStart(kEventKey2, true, eventStartTimeNs + 10, ConditionKey()); // overlapping wl + + tracker.noteStopAll(eventStartTimeNs + 2003); + + tracker.flushIfNeeded(bucketStartTimeNs + bucketSizeNs + 1, emptyThreshold, &buckets); + EXPECT_TRUE(buckets.find(eventKey) != buckets.end()); + ASSERT_EQ(1u, buckets[eventKey].size()); + EXPECT_EQ(2003LL, buckets[eventKey][0].mDuration); +} + +TEST(OringDurationTrackerTest, TestCrossBucketBoundary) { + const MetricDimensionKey eventKey = getMockedMetricDimensionKey(TagId, 0, "event"); + + const HashableDimensionKey kEventKey1 = getMockedDimensionKey(TagId, 2, "maps"); + const HashableDimensionKey kEventKey2 = getMockedDimensionKey(TagId, 3, "maps"); + sp wizard = new NaggyMock(); + + unordered_map> buckets; + + int64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL; + int64_t bucketStartTimeNs = 10000000000; + int64_t bucketNum = 0; + int64_t eventStartTimeNs = bucketStartTimeNs + 1; + int64_t durationTimeNs = 2 * 1000; + + OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, true, bucketStartTimeNs, + bucketNum, bucketStartTimeNs, bucketSizeNs, false, false, {}); + + tracker.noteStart(kEventKey1, true, eventStartTimeNs, ConditionKey()); + EXPECT_EQ((long long)eventStartTimeNs, tracker.mLastStartTime); + tracker.flushIfNeeded(eventStartTimeNs + 2 * bucketSizeNs, emptyThreshold, &buckets); + tracker.noteStart(kEventKey1, true, eventStartTimeNs + 2 * bucketSizeNs, ConditionKey()); + EXPECT_EQ((long long)(bucketStartTimeNs + 2 * bucketSizeNs), tracker.mLastStartTime); + + ASSERT_EQ(2u, buckets[eventKey].size()); + EXPECT_EQ(bucketSizeNs - 1, buckets[eventKey][0].mDuration); + EXPECT_EQ(bucketSizeNs, buckets[eventKey][1].mDuration); + + tracker.noteStop(kEventKey1, eventStartTimeNs + 2 * bucketSizeNs + 10, false); + tracker.noteStop(kEventKey1, eventStartTimeNs + 2 * bucketSizeNs + 12, false); + tracker.flushIfNeeded(eventStartTimeNs + 2 * bucketSizeNs + 12, emptyThreshold, &buckets); + EXPECT_TRUE(buckets.find(eventKey) != buckets.end()); + ASSERT_EQ(2u, buckets[eventKey].size()); + EXPECT_EQ(bucketSizeNs - 1, buckets[eventKey][0].mDuration); + EXPECT_EQ(bucketSizeNs, buckets[eventKey][1].mDuration); +} + +TEST(OringDurationTrackerTest, TestDurationConditionChange) { + const MetricDimensionKey eventKey = getMockedMetricDimensionKey(TagId, 0, "event"); + + const HashableDimensionKey kEventKey1 = getMockedDimensionKey(TagId, 2, "maps"); + const HashableDimensionKey kEventKey2 = getMockedDimensionKey(TagId, 3, "maps"); + sp wizard = new NaggyMock(); + + ConditionKey key1; + key1[StringToId("APP_BACKGROUND")] = kConditionKey1; + + EXPECT_CALL(*wizard, query(_, key1, _)) // #4 + .WillOnce(Return(ConditionState::kFalse)); + + unordered_map> buckets; + + int64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL; + int64_t bucketStartTimeNs = 10000000000; + int64_t bucketNum = 0; + int64_t eventStartTimeNs = bucketStartTimeNs + 1; + int64_t durationTimeNs = 2 * 1000; + + OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, false, + bucketStartTimeNs, bucketNum, bucketStartTimeNs, bucketSizeNs, + true, false, {}); + + tracker.noteStart(kEventKey1, true, eventStartTimeNs, key1); + + tracker.onSlicedConditionMayChange(true, eventStartTimeNs + 5); + + tracker.noteStop(kEventKey1, eventStartTimeNs + durationTimeNs, false); + + tracker.flushIfNeeded(bucketStartTimeNs + bucketSizeNs + 1, emptyThreshold, &buckets); + EXPECT_TRUE(buckets.find(eventKey) != buckets.end()); + ASSERT_EQ(1u, buckets[eventKey].size()); + EXPECT_EQ(5LL, buckets[eventKey][0].mDuration); +} + +TEST(OringDurationTrackerTest, TestDurationConditionChange2) { + const MetricDimensionKey eventKey = getMockedMetricDimensionKey(TagId, 0, "event"); + + const HashableDimensionKey kEventKey1 = getMockedDimensionKey(TagId, 2, "maps"); + const HashableDimensionKey kEventKey2 = getMockedDimensionKey(TagId, 3, "maps"); + sp wizard = new NaggyMock(); + + ConditionKey key1; + key1[StringToId("APP_BACKGROUND")] = kConditionKey1; + + EXPECT_CALL(*wizard, query(_, key1, _)) + .Times(2) + .WillOnce(Return(ConditionState::kFalse)) + .WillOnce(Return(ConditionState::kTrue)); + + unordered_map> buckets; + + int64_t bucketStartTimeNs = 10000000000; + int64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL; + int64_t bucketNum = 0; + int64_t eventStartTimeNs = bucketStartTimeNs + 1; + int64_t durationTimeNs = 2 * 1000; + + OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, false, + bucketStartTimeNs, bucketNum, bucketStartTimeNs, bucketSizeNs, + true, false, {}); + + tracker.noteStart(kEventKey1, true, eventStartTimeNs, key1); + // condition to false; record duration 5n + tracker.onSlicedConditionMayChange(true, eventStartTimeNs + 5); + // condition to true. + tracker.onSlicedConditionMayChange(true, eventStartTimeNs + 1000); + // 2nd duration: 1000ns + tracker.noteStop(kEventKey1, eventStartTimeNs + durationTimeNs, false); + + tracker.flushIfNeeded(bucketStartTimeNs + bucketSizeNs + 1, emptyThreshold, &buckets); + EXPECT_TRUE(buckets.find(eventKey) != buckets.end()); + ASSERT_EQ(1u, buckets[eventKey].size()); + EXPECT_EQ(1005LL, buckets[eventKey][0].mDuration); +} + +TEST(OringDurationTrackerTest, TestDurationConditionChangeNested) { + const MetricDimensionKey eventKey = getMockedMetricDimensionKey(TagId, 0, "event"); + + const HashableDimensionKey kEventKey1 = getMockedDimensionKey(TagId, 2, "maps"); + const HashableDimensionKey kEventKey2 = getMockedDimensionKey(TagId, 3, "maps"); + sp wizard = new NaggyMock(); + + ConditionKey key1; + key1[StringToId("APP_BACKGROUND")] = kConditionKey1; + + EXPECT_CALL(*wizard, query(_, key1, _)) // #4 + .WillOnce(Return(ConditionState::kFalse)); + + unordered_map> buckets; + + int64_t bucketStartTimeNs = 10000000000; + int64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL; + int64_t bucketNum = 0; + int64_t eventStartTimeNs = bucketStartTimeNs + 1; + + OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, true, bucketStartTimeNs, + bucketNum, bucketStartTimeNs, bucketSizeNs, true, false, {}); + + tracker.noteStart(kEventKey1, true, eventStartTimeNs, key1); + tracker.noteStart(kEventKey1, true, eventStartTimeNs + 2, key1); + + tracker.noteStop(kEventKey1, eventStartTimeNs + 3, false); + + tracker.onSlicedConditionMayChange(true, eventStartTimeNs + 15); + + tracker.noteStop(kEventKey1, eventStartTimeNs + 2003, false); + + tracker.flushIfNeeded(bucketStartTimeNs + bucketSizeNs + 1, emptyThreshold, &buckets); + EXPECT_TRUE(buckets.find(eventKey) != buckets.end()); + ASSERT_EQ(1u, buckets[eventKey].size()); + EXPECT_EQ(15LL, buckets[eventKey][0].mDuration); +} + +TEST(OringDurationTrackerTest, TestPredictAnomalyTimestamp) { + const MetricDimensionKey eventKey = getMockedMetricDimensionKey(TagId, 0, "event"); + + const HashableDimensionKey kEventKey1 = getMockedDimensionKey(TagId, 2, "maps"); + const HashableDimensionKey kEventKey2 = getMockedDimensionKey(TagId, 3, "maps"); + Alert alert; + alert.set_id(101); + alert.set_metric_id(1); + alert.set_trigger_if_sum_gt(40 * NS_PER_SEC); + alert.set_num_buckets(2); + alert.set_refractory_period_secs(1); + + unordered_map> buckets; + sp wizard = new NaggyMock(); + + int64_t bucketStartTimeNs = 10 * NS_PER_SEC; + int64_t bucketNum = 0; + int64_t eventStartTimeNs = bucketStartTimeNs + NS_PER_SEC + 1; + + sp alarmMonitor; + sp anomalyTracker = + new DurationAnomalyTracker(alert, kConfigKey, alarmMonitor); + OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, true, bucketStartTimeNs, + bucketNum, bucketStartTimeNs, bucketSizeNs, true, false, + {anomalyTracker}); + + // Nothing in the past bucket. + tracker.noteStart(DEFAULT_DIMENSION_KEY, true, eventStartTimeNs, ConditionKey()); + EXPECT_EQ((long long)(alert.trigger_if_sum_gt() + eventStartTimeNs), + tracker.predictAnomalyTimestampNs(*anomalyTracker, eventStartTimeNs)); + + tracker.noteStop(DEFAULT_DIMENSION_KEY, eventStartTimeNs + 3, false); + ASSERT_EQ(0u, buckets[eventKey].size()); + + int64_t event1StartTimeNs = eventStartTimeNs + 10; + tracker.noteStart(kEventKey1, true, event1StartTimeNs, ConditionKey()); + // No past buckets. The anomaly will happen in bucket #0. + EXPECT_EQ((long long)(event1StartTimeNs + alert.trigger_if_sum_gt() - 3), + tracker.predictAnomalyTimestampNs(*anomalyTracker, event1StartTimeNs)); + + int64_t event1StopTimeNs = eventStartTimeNs + bucketSizeNs + 10; + tracker.flushIfNeeded(event1StopTimeNs, emptyThreshold, &buckets); + tracker.noteStop(kEventKey1, event1StopTimeNs, false); + + EXPECT_TRUE(buckets.find(eventKey) != buckets.end()); + ASSERT_EQ(1u, buckets[eventKey].size()); + EXPECT_EQ(3LL + bucketStartTimeNs + bucketSizeNs - eventStartTimeNs - 10, + buckets[eventKey][0].mDuration); + + const int64_t bucket0Duration = 3ULL + bucketStartTimeNs + bucketSizeNs - eventStartTimeNs - 10; + const int64_t bucket1Duration = eventStartTimeNs + 10 - bucketStartTimeNs; + + // One past buckets. The anomaly will happen in bucket #1. + int64_t event2StartTimeNs = eventStartTimeNs + bucketSizeNs + 15; + tracker.noteStart(kEventKey1, true, event2StartTimeNs, ConditionKey()); + EXPECT_EQ((long long)(event2StartTimeNs + alert.trigger_if_sum_gt() - bucket0Duration - + bucket1Duration), + tracker.predictAnomalyTimestampNs(*anomalyTracker, event2StartTimeNs)); + tracker.noteStop(kEventKey1, event2StartTimeNs + 1, false); + + // Only one past buckets is applicable. Bucket +0 should be trashed. The anomaly will happen in + // bucket #2. + int64_t event3StartTimeNs = bucketStartTimeNs + 2 * bucketSizeNs - 9 * NS_PER_SEC; + tracker.noteStart(kEventKey1, true, event3StartTimeNs, ConditionKey()); + EXPECT_EQ((long long)(event3StartTimeNs + alert.trigger_if_sum_gt() - bucket1Duration - 1LL), + tracker.predictAnomalyTimestampNs(*anomalyTracker, event3StartTimeNs)); +} + +TEST(OringDurationTrackerTest, TestPredictAnomalyTimestamp2) { + Alert alert; + alert.set_id(101); + alert.set_metric_id(1); + alert.set_trigger_if_sum_gt(5 * NS_PER_SEC); + alert.set_num_buckets(1); + alert.set_refractory_period_secs(20); + + int64_t bucketStartTimeNs = 10 * NS_PER_SEC; + int64_t bucketNum = 0; + + sp wizard = new NaggyMock(); + sp alarmMonitor; + sp anomalyTracker = + new DurationAnomalyTracker(alert, kConfigKey, alarmMonitor); + OringDurationTracker tracker(kConfigKey, metricId, DEFAULT_METRIC_DIMENSION_KEY, wizard, 1, + + true, bucketStartTimeNs, bucketNum, bucketStartTimeNs, + bucketSizeNs, true, false, {anomalyTracker}); + + int64_t eventStartTimeNs = bucketStartTimeNs + 9 * NS_PER_SEC; + tracker.noteStart(DEFAULT_DIMENSION_KEY, true, eventStartTimeNs, ConditionKey()); + // Anomaly happens in the bucket #1. + EXPECT_EQ((long long)(bucketStartTimeNs + 14 * NS_PER_SEC), + tracker.predictAnomalyTimestampNs(*anomalyTracker, eventStartTimeNs)); + + tracker.noteStop(DEFAULT_DIMENSION_KEY, bucketStartTimeNs + 14 * NS_PER_SEC, false); + + EXPECT_EQ((long long)(bucketStartTimeNs + 34 * NS_PER_SEC) / NS_PER_SEC, + anomalyTracker->getRefractoryPeriodEndsSec(DEFAULT_METRIC_DIMENSION_KEY)); + + int64_t event2StartTimeNs = bucketStartTimeNs + 22 * NS_PER_SEC; + EXPECT_EQ((long long)(bucketStartTimeNs + 34 * NS_PER_SEC) / NS_PER_SEC, + anomalyTracker->getRefractoryPeriodEndsSec(DEFAULT_METRIC_DIMENSION_KEY)); + EXPECT_EQ((long long)(bucketStartTimeNs + 35 * NS_PER_SEC), + tracker.predictAnomalyTimestampNs(*anomalyTracker, event2StartTimeNs)); +} + +TEST(OringDurationTrackerTest, TestPredictAnomalyTimestamp3) { + // Test the cases where the refractory period is smaller than the bucket size, longer than + // the bucket size, and longer than 2x of the anomaly detection window. + for (int j = 0; j < 3; j++) { + int64_t thresholdNs = j * bucketSizeNs + 5 * NS_PER_SEC; + for (int i = 0; i <= 7; ++i) { + + Alert alert; + alert.set_id(101); + alert.set_metric_id(1); + alert.set_trigger_if_sum_gt(thresholdNs); + alert.set_num_buckets(3); + alert.set_refractory_period_secs( + bucketSizeNs / NS_PER_SEC / 2 + i * bucketSizeNs / NS_PER_SEC); + + int64_t bucketStartTimeNs = 10 * NS_PER_SEC; + int64_t bucketNum = 101; + + sp wizard = new NaggyMock(); + sp alarmMonitor; + sp anomalyTracker = + new DurationAnomalyTracker(alert, kConfigKey, alarmMonitor); + OringDurationTracker tracker(kConfigKey, metricId, DEFAULT_METRIC_DIMENSION_KEY, wizard, + 1, true, bucketStartTimeNs, bucketNum, bucketStartTimeNs, + bucketSizeNs, true, false, {anomalyTracker}); + + int64_t eventStartTimeNs = bucketStartTimeNs + 9 * NS_PER_SEC; + tracker.noteStart(DEFAULT_DIMENSION_KEY, true, eventStartTimeNs, ConditionKey()); + EXPECT_EQ((long long)(eventStartTimeNs + thresholdNs), + tracker.predictAnomalyTimestampNs(*anomalyTracker, eventStartTimeNs)); + int64_t eventStopTimeNs = eventStartTimeNs + thresholdNs + NS_PER_SEC; + tracker.noteStop(DEFAULT_DIMENSION_KEY, eventStopTimeNs, false); + + int64_t refractoryPeriodEndSec = + anomalyTracker->getRefractoryPeriodEndsSec(DEFAULT_METRIC_DIMENSION_KEY); + EXPECT_EQ(eventStopTimeNs / (int64_t)NS_PER_SEC + alert.refractory_period_secs(), + refractoryPeriodEndSec); + + // Acquire and release a wakelock in the next bucket. + int64_t event2StartTimeNs = eventStopTimeNs + bucketSizeNs; + tracker.noteStart(DEFAULT_DIMENSION_KEY, true, event2StartTimeNs, ConditionKey()); + int64_t event2StopTimeNs = event2StartTimeNs + 4 * NS_PER_SEC; + tracker.noteStop(DEFAULT_DIMENSION_KEY, event2StopTimeNs, false); + + // Test the alarm prediction works well when seeing another wakelock start event. + for (int k = 0; k <= 2; ++k) { + int64_t event3StartTimeNs = event2StopTimeNs + NS_PER_SEC + k * bucketSizeNs; + int64_t alarmTimestampNs = + tracker.predictAnomalyTimestampNs(*anomalyTracker, event3StartTimeNs); + EXPECT_GT(alarmTimestampNs, 0u); + EXPECT_GE(alarmTimestampNs, event3StartTimeNs); + EXPECT_GE(alarmTimestampNs, refractoryPeriodEndSec *(int64_t) NS_PER_SEC); + } + } + } +} + +TEST(OringDurationTrackerTest, TestAnomalyDetectionExpiredAlarm) { + const MetricDimensionKey eventKey = getMockedMetricDimensionKey(TagId, 0, "event"); + + const HashableDimensionKey kEventKey1 = getMockedDimensionKey(TagId, 2, "maps"); + const HashableDimensionKey kEventKey2 = getMockedDimensionKey(TagId, 3, "maps"); + Alert alert; + alert.set_id(101); + alert.set_metric_id(1); + alert.set_trigger_if_sum_gt(40 * NS_PER_SEC); + alert.set_num_buckets(2); + const int32_t refPeriodSec = 45; + alert.set_refractory_period_secs(refPeriodSec); + + unordered_map> buckets; + sp wizard = new NaggyMock(); + + int64_t bucketStartTimeNs = 10 * NS_PER_SEC; + int64_t bucketNum = 0; + int64_t eventStartTimeNs = bucketStartTimeNs + NS_PER_SEC + 1; + + sp alarmMonitor; + sp anomalyTracker = + new DurationAnomalyTracker(alert, kConfigKey, alarmMonitor); + OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, true /*nesting*/, + bucketStartTimeNs, bucketNum, bucketStartTimeNs, bucketSizeNs, + false, false, {anomalyTracker}); + + tracker.noteStart(kEventKey1, true, eventStartTimeNs, ConditionKey()); + tracker.noteStop(kEventKey1, eventStartTimeNs + 10, false); + EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(eventKey), 0U); + EXPECT_TRUE(tracker.mStarted.empty()); + EXPECT_EQ(10LL, tracker.mStateKeyDurationMap[DEFAULT_DIMENSION_KEY].mDuration); // 10ns + + ASSERT_EQ(0u, tracker.mStarted.size()); + + tracker.noteStart(kEventKey1, true, eventStartTimeNs + 20, ConditionKey()); + ASSERT_EQ(1u, anomalyTracker->mAlarms.size()); + EXPECT_EQ((long long)(52ULL * NS_PER_SEC), // (10s + 1s + 1ns + 20ns) - 10ns + 40s, rounded up + (long long)(anomalyTracker->mAlarms.begin()->second->timestampSec * NS_PER_SEC)); + // The alarm is set to fire at 52s, and when it does, an anomaly would be declared. However, + // because this is a unit test, the alarm won't actually fire at all. Since the alarm fails + // to fire in time, the anomaly is instead caught when noteStop is called, at around 71s. + tracker.flushIfNeeded(eventStartTimeNs + 2 * bucketSizeNs + 25, emptyThreshold, &buckets); + tracker.noteStop(kEventKey1, eventStartTimeNs + 2 * bucketSizeNs + 25, false); + EXPECT_EQ(anomalyTracker->getSumOverPastBuckets(eventKey), (long long)(bucketSizeNs)); + EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(eventKey), + std::ceil((eventStartTimeNs + 2 * bucketSizeNs + 25.0) / NS_PER_SEC + refPeriodSec)); +} + +TEST(OringDurationTrackerTest, TestAnomalyDetectionFiredAlarm) { + const MetricDimensionKey eventKey = getMockedMetricDimensionKey(TagId, 0, "event"); + + const HashableDimensionKey kEventKey1 = getMockedDimensionKey(TagId, 2, "maps"); + const HashableDimensionKey kEventKey2 = getMockedDimensionKey(TagId, 3, "maps"); + Alert alert; + alert.set_id(101); + alert.set_metric_id(1); + alert.set_trigger_if_sum_gt(40 * NS_PER_SEC); + alert.set_num_buckets(2); + const int32_t refPeriodSec = 45; + alert.set_refractory_period_secs(refPeriodSec); + + unordered_map> buckets; + sp wizard = new NaggyMock(); + ConditionKey conkey; + conkey[StringToId("APP_BACKGROUND")] = kConditionKey1; + int64_t bucketStartTimeNs = 10 * NS_PER_SEC; + int64_t bucketSizeNs = 30 * NS_PER_SEC; + + sp alarmMonitor; + sp anomalyTracker = + new DurationAnomalyTracker(alert, kConfigKey, alarmMonitor); + OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, true /*nesting*/, + bucketStartTimeNs, 0, bucketStartTimeNs, bucketSizeNs, false, + false, {anomalyTracker}); + + tracker.noteStart(kEventKey1, true, 15 * NS_PER_SEC, conkey); // start key1 + ASSERT_EQ(1u, anomalyTracker->mAlarms.size()); + sp alarm = anomalyTracker->mAlarms.begin()->second; + EXPECT_EQ((long long)(55ULL * NS_PER_SEC), (long long)(alarm->timestampSec * NS_PER_SEC)); + EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(eventKey), 0U); + + tracker.noteStop(kEventKey1, 17 * NS_PER_SEC, false); // stop key1 (2 seconds later) + ASSERT_EQ(0u, anomalyTracker->mAlarms.size()); + EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(eventKey), 0U); + + tracker.noteStart(kEventKey1, true, 22 * NS_PER_SEC, conkey); // start key1 again + ASSERT_EQ(1u, anomalyTracker->mAlarms.size()); + alarm = anomalyTracker->mAlarms.begin()->second; + EXPECT_EQ((long long)(60ULL * NS_PER_SEC), (long long)(alarm->timestampSec * NS_PER_SEC)); + EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(eventKey), 0U); + + tracker.noteStart(kEventKey2, true, 32 * NS_PER_SEC, conkey); // start key2 + ASSERT_EQ(1u, anomalyTracker->mAlarms.size()); + alarm = anomalyTracker->mAlarms.begin()->second; + EXPECT_EQ((long long)(60ULL * NS_PER_SEC), (long long)(alarm->timestampSec * NS_PER_SEC)); + EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(eventKey), 0U); + + tracker.noteStop(kEventKey1, 47 * NS_PER_SEC, false); // stop key1 + ASSERT_EQ(1u, anomalyTracker->mAlarms.size()); + alarm = anomalyTracker->mAlarms.begin()->second; + EXPECT_EQ((long long)(60ULL * NS_PER_SEC), (long long)(alarm->timestampSec * NS_PER_SEC)); + EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(eventKey), 0U); + + // Now, at 60s, which is 38s after key1 started again, we have reached 40s of 'on' time. + std::unordered_set, SpHash> firedAlarms({alarm}); + anomalyTracker->informAlarmsFired(62 * NS_PER_SEC, firedAlarms); + ASSERT_EQ(0u, anomalyTracker->mAlarms.size()); + EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(eventKey), 62U + refPeriodSec); + + tracker.noteStop(kEventKey2, 69 * NS_PER_SEC, false); // stop key2 + ASSERT_EQ(0u, anomalyTracker->mAlarms.size()); + EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(eventKey), 62U + refPeriodSec); +} + +TEST(OringDurationTrackerTest, TestUploadThreshold) { + sp wizard = new NaggyMock(); + + unordered_map> buckets; + + int64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL; + int64_t bucketStartTimeNs = 10000000000; + int64_t bucketNum = 0; + int64_t eventStartTimeNs = bucketStartTimeNs + 1; + int64_t event2StartTimeNs = bucketStartTimeNs + bucketSizeNs + 1; + int64_t thresholdDurationNs = 2000; + + UploadThreshold threshold; + threshold.set_gt_int(thresholdDurationNs); + + OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, false, + bucketStartTimeNs, bucketNum, bucketStartTimeNs, bucketSizeNs, + false, false, {}); + + // Duration below the gt_int threshold should not be added to past buckets. + tracker.noteStart(kEventKey1, true, eventStartTimeNs, ConditionKey()); + tracker.noteStop(kEventKey1, eventStartTimeNs + thresholdDurationNs, false); + tracker.flushIfNeeded(eventStartTimeNs + bucketSizeNs + 1, threshold, &buckets); + EXPECT_TRUE(buckets.find(eventKey) == buckets.end()); + + // Duration above the gt_int threshold should be added to past buckets. + tracker.noteStart(kEventKey1, true, event2StartTimeNs, ConditionKey()); + tracker.noteStop(kEventKey1, event2StartTimeNs + thresholdDurationNs + 1, false); + tracker.flushIfNeeded(event2StartTimeNs + bucketSizeNs + 1, threshold, &buckets); + EXPECT_TRUE(buckets.find(eventKey) != buckets.end()); + ASSERT_EQ(1u, buckets[eventKey].size()); + EXPECT_EQ(thresholdDurationNs + 1, buckets[eventKey][0].mDuration); +} + +} // namespace statsd +} // namespace os +} // namespace android +#else +GTEST_LOG_(INFO) << "This test does nothing.\n"; +#endif diff --git a/statsd/tests/metrics/ValueMetricProducer_test.cpp b/statsd/tests/metrics/ValueMetricProducer_test.cpp new file mode 100644 index 00000000..e74039b2 --- /dev/null +++ b/statsd/tests/metrics/ValueMetricProducer_test.cpp @@ -0,0 +1,6921 @@ +// 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 "src/metrics/ValueMetricProducer.h" + +#include +#include +#include +#include + +#include + +#include "metrics_test_helper.h" +#include "src/matchers/SimpleAtomMatchingTracker.h" +#include "src/metrics/MetricProducer.h" +#include "src/stats_log_util.h" +#include "tests/statsd_test_util.h" + +using namespace testing; +using android::sp; +using std::make_shared; +using std::set; +using std::shared_ptr; +using std::unordered_map; +using std::vector; + +#ifdef __ANDROID__ + +namespace android { +namespace os { +namespace statsd { + +namespace { + +const ConfigKey kConfigKey(0, 12345); +const int tagId = 1; +const int64_t metricId = 123; +const uint64_t protoHash = 0x1234567890; +const int logEventMatcherIndex = 0; +const int64_t bucketStartTimeNs = 10000000000; +const int64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(ONE_MINUTE) * 1000000LL; +const int64_t bucket2StartTimeNs = bucketStartTimeNs + bucketSizeNs; +const int64_t bucket3StartTimeNs = bucketStartTimeNs + 2 * bucketSizeNs; +const int64_t bucket4StartTimeNs = bucketStartTimeNs + 3 * bucketSizeNs; +const int64_t bucket5StartTimeNs = bucketStartTimeNs + 4 * bucketSizeNs; +const int64_t bucket6StartTimeNs = bucketStartTimeNs + 5 * bucketSizeNs; +double epsilon = 0.001; + +static void assertPastBucketValuesSingleKey( + const std::unordered_map>& mPastBuckets, + const std::initializer_list& expectedValuesList, + const std::initializer_list& expectedDurationNsList, + const std::initializer_list& expectedStartTimeNsList, + const std::initializer_list& expectedEndTimeNsList) { + vector expectedValues(expectedValuesList); + vector expectedDurationNs(expectedDurationNsList); + vector expectedStartTimeNs(expectedStartTimeNsList); + vector expectedEndTimeNs(expectedEndTimeNsList); + + ASSERT_EQ(expectedValues.size(), expectedDurationNs.size()); + ASSERT_EQ(expectedValues.size(), expectedStartTimeNs.size()); + ASSERT_EQ(expectedValues.size(), expectedEndTimeNs.size()); + + if (expectedValues.size() == 0) { + ASSERT_EQ(0, mPastBuckets.size()); + return; + } + + ASSERT_EQ(1, mPastBuckets.size()); + ASSERT_EQ(expectedValues.size(), mPastBuckets.begin()->second.size()); + + const vector& buckets = mPastBuckets.begin()->second; + for (int i = 0; i < expectedValues.size(); i++) { + EXPECT_EQ(expectedValues[i], buckets[i].values[0].long_value) + << "Values differ at index " << i; + EXPECT_EQ(expectedDurationNs[i], buckets[i].mConditionTrueNs) + << "Condition duration value differ at index " << i; + EXPECT_EQ(expectedStartTimeNs[i], buckets[i].mBucketStartNs) + << "Start time differs at index " << i; + EXPECT_EQ(expectedEndTimeNs[i], buckets[i].mBucketEndNs) + << "End time differs at index " << i; + } +} + +static void assertConditionTimer(const ConditionTimer& conditionTimer, bool condition, + int64_t timerNs, int64_t lastConditionTrueTimestampNs) { + EXPECT_EQ(condition, conditionTimer.mCondition); + EXPECT_EQ(timerNs, conditionTimer.mTimerNs); + EXPECT_EQ(lastConditionTrueTimestampNs, conditionTimer.mLastConditionChangeTimestampNs); +} + +} // anonymous namespace + +class ValueMetricProducerTestHelper { +public: + static sp createValueProducerNoConditions( + sp& pullerManager, ValueMetric& metric) { + sp eventMatcherWizard = + createEventMatcherWizard(tagId, logEventMatcherIndex); + sp wizard = new NaggyMock(); + EXPECT_CALL(*pullerManager, RegisterReceiver(tagId, kConfigKey, _, _, _)) + .WillOnce(Return()); + EXPECT_CALL(*pullerManager, UnRegisterReceiver(tagId, kConfigKey, _)) + .WillRepeatedly(Return()); + + sp valueProducer = + new ValueMetricProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, {}, + wizard, protoHash, logEventMatcherIndex, eventMatcherWizard, + tagId, bucketStartTimeNs, bucketStartTimeNs, pullerManager); + valueProducer->prepareFirstBucket(); + return valueProducer; + } + + static sp createValueProducerWithCondition( + sp& pullerManager, ValueMetric& metric, + ConditionState conditionAfterFirstBucketPrepared) { + sp eventMatcherWizard = + createEventMatcherWizard(tagId, logEventMatcherIndex); + sp wizard = new NaggyMock(); + EXPECT_CALL(*pullerManager, RegisterReceiver(tagId, kConfigKey, _, _, _)) + .WillOnce(Return()); + EXPECT_CALL(*pullerManager, UnRegisterReceiver(tagId, kConfigKey, _)) + .WillRepeatedly(Return()); + + sp valueProducer = new ValueMetricProducer( + kConfigKey, metric, 0 /*condition index*/, {ConditionState::kUnknown}, wizard, + protoHash, logEventMatcherIndex, eventMatcherWizard, tagId, bucketStartTimeNs, + bucketStartTimeNs, pullerManager); + valueProducer->prepareFirstBucket(); + valueProducer->mCondition = conditionAfterFirstBucketPrepared; + return valueProducer; + } + + static sp createValueProducerWithState( + sp& pullerManager, ValueMetric& metric, + vector slicedStateAtoms, + unordered_map> stateGroupMap) { + sp eventMatcherWizard = + createEventMatcherWizard(tagId, logEventMatcherIndex); + sp wizard = new NaggyMock(); + EXPECT_CALL(*pullerManager, RegisterReceiver(tagId, kConfigKey, _, _, _)) + .WillOnce(Return()); + EXPECT_CALL(*pullerManager, UnRegisterReceiver(tagId, kConfigKey, _)) + .WillRepeatedly(Return()); + + sp valueProducer = new ValueMetricProducer( + kConfigKey, metric, -1 /* no condition */, {}, wizard, protoHash, + logEventMatcherIndex, eventMatcherWizard, tagId, bucketStartTimeNs, + bucketStartTimeNs, pullerManager, {}, {}, slicedStateAtoms, stateGroupMap); + valueProducer->prepareFirstBucket(); + return valueProducer; + } + + static sp createValueProducerWithConditionAndState( + sp& pullerManager, ValueMetric& metric, + vector slicedStateAtoms, + unordered_map> stateGroupMap, + ConditionState conditionAfterFirstBucketPrepared) { + sp eventMatcherWizard = + createEventMatcherWizard(tagId, logEventMatcherIndex); + sp wizard = new NaggyMock(); + EXPECT_CALL(*pullerManager, RegisterReceiver(tagId, kConfigKey, _, _, _)) + .WillOnce(Return()); + EXPECT_CALL(*pullerManager, UnRegisterReceiver(tagId, kConfigKey, _)) + .WillRepeatedly(Return()); + + sp valueProducer = new ValueMetricProducer( + kConfigKey, metric, 0 /* condition tracker index */, {ConditionState::kUnknown}, + wizard, protoHash, logEventMatcherIndex, eventMatcherWizard, tagId, + bucketStartTimeNs, bucketStartTimeNs, pullerManager, {}, {}, slicedStateAtoms, + stateGroupMap); + valueProducer->prepareFirstBucket(); + valueProducer->mCondition = conditionAfterFirstBucketPrepared; + return valueProducer; + } + + static ValueMetric createMetric() { + ValueMetric metric; + metric.set_id(metricId); + metric.set_bucket(ONE_MINUTE); + metric.mutable_value_field()->set_field(tagId); + metric.mutable_value_field()->add_child()->set_field(2); + metric.set_max_pull_delay_sec(INT_MAX); + return metric; + } + + static ValueMetric createMetricWithCondition() { + ValueMetric metric = ValueMetricProducerTestHelper::createMetric(); + metric.set_condition(StringToId("SCREEN_ON")); + return metric; + } + + static ValueMetric createMetricWithState(string state) { + ValueMetric metric = ValueMetricProducerTestHelper::createMetric(); + metric.add_slice_by_state(StringToId(state)); + return metric; + } + + static ValueMetric createMetricWithConditionAndState(string state) { + ValueMetric metric = ValueMetricProducerTestHelper::createMetric(); + metric.set_condition(StringToId("SCREEN_ON")); + metric.add_slice_by_state(StringToId(state)); + return metric; + } +}; + +// Setup for parameterized tests. +class ValueMetricProducerTest_PartialBucket : public TestWithParam {}; + +INSTANTIATE_TEST_SUITE_P(ValueMetricProducerTest_PartialBucket, + ValueMetricProducerTest_PartialBucket, + testing::Values(APP_UPGRADE, BOOT_COMPLETE)); + +/* + * Tests that the first bucket works correctly + */ +TEST(ValueMetricProducerTest, TestCalcPreviousBucketEndTime) { + ValueMetric metric = ValueMetricProducerTestHelper::createMetric(); + + int64_t startTimeBase = 11; + sp eventMatcherWizard = + createEventMatcherWizard(tagId, logEventMatcherIndex); + sp wizard = new NaggyMock(); + sp pullerManager = new StrictMock(); + + // statsd started long ago. + // The metric starts in the middle of the bucket + ValueMetricProducer valueProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, {}, + wizard, protoHash, logEventMatcherIndex, eventMatcherWizard, + -1, startTimeBase, 22, pullerManager); + valueProducer.prepareFirstBucket(); + + EXPECT_EQ(startTimeBase, valueProducer.calcPreviousBucketEndTime(60 * NS_PER_SEC + 10)); + EXPECT_EQ(startTimeBase, valueProducer.calcPreviousBucketEndTime(60 * NS_PER_SEC + 10)); + EXPECT_EQ(60 * NS_PER_SEC + startTimeBase, + valueProducer.calcPreviousBucketEndTime(2 * 60 * NS_PER_SEC)); + EXPECT_EQ(2 * 60 * NS_PER_SEC + startTimeBase, + valueProducer.calcPreviousBucketEndTime(3 * 60 * NS_PER_SEC)); +} + +/* + * Tests that the first bucket works correctly + */ +TEST(ValueMetricProducerTest, TestFirstBucket) { + ValueMetric metric = ValueMetricProducerTestHelper::createMetric(); + + sp eventMatcherWizard = + createEventMatcherWizard(tagId, logEventMatcherIndex); + sp wizard = new NaggyMock(); + sp pullerManager = new StrictMock(); + + // statsd started long ago. + // The metric starts in the middle of the bucket + ValueMetricProducer valueProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, {}, + wizard, protoHash, logEventMatcherIndex, eventMatcherWizard, + -1, 5, 600 * NS_PER_SEC + NS_PER_SEC / 2, pullerManager); + valueProducer.prepareFirstBucket(); + + EXPECT_EQ(600500000000, valueProducer.mCurrentBucketStartTimeNs); + EXPECT_EQ(10, valueProducer.mCurrentBucketNum); + EXPECT_EQ(660000000005, valueProducer.getCurrentBucketEndTimeNs()); +} + +/* + * Tests pulled atoms with no conditions + */ +TEST(ValueMetricProducerTest, TestPulledEventsNoCondition) { + ValueMetric metric = ValueMetricProducerTestHelper::createMetric(); + sp pullerManager = new StrictMock(); + EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, bucketStartTimeNs, _)) + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t, + vector>* data) { + data->clear(); + data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs, 3)); + return true; + })); + + sp valueProducer = + ValueMetricProducerTestHelper::createValueProducerNoConditions(pullerManager, metric); + + vector> allData; + allData.clear(); + allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs + 1, 11)); + + valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs); + // has one slice + ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); + ValueMetricProducer::Interval curInterval = + valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0]; + ValueMetricProducer::BaseInfo curBaseInfo = + valueProducer->mCurrentBaseInfo.begin()->second.baseInfos[0]; + + EXPECT_EQ(true, curBaseInfo.hasBase); + EXPECT_EQ(11, curBaseInfo.base.long_value); + EXPECT_EQ(false, curInterval.hasValue); + EXPECT_EQ(8, curInterval.value.long_value); + ASSERT_EQ(1UL, valueProducer->mPastBuckets.size()); + EXPECT_EQ(8, valueProducer->mPastBuckets.begin()->second[0].values[0].long_value); + EXPECT_EQ(bucketSizeNs, valueProducer->mPastBuckets.begin()->second[0].mConditionTrueNs); + + allData.clear(); + allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket3StartTimeNs + 1, 23)); + valueProducer->onDataPulled(allData, /** succeed */ true, bucket3StartTimeNs); + // has one slice + ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); + curInterval = valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0]; + curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second.baseInfos[0]; + + EXPECT_EQ(true, curBaseInfo.hasBase); + EXPECT_EQ(23, curBaseInfo.base.long_value); + EXPECT_EQ(false, curInterval.hasValue); + EXPECT_EQ(12, curInterval.value.long_value); + ASSERT_EQ(1UL, valueProducer->mPastBuckets.size()); + ASSERT_EQ(2UL, valueProducer->mPastBuckets.begin()->second.size()); + EXPECT_EQ(8, valueProducer->mPastBuckets.begin()->second[0].values[0].long_value); + EXPECT_EQ(bucketSizeNs, valueProducer->mPastBuckets.begin()->second[0].mConditionTrueNs); + EXPECT_EQ(12, valueProducer->mPastBuckets.begin()->second.back().values[0].long_value); + EXPECT_EQ(bucketSizeNs, valueProducer->mPastBuckets.begin()->second.back().mConditionTrueNs); + + allData.clear(); + allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket4StartTimeNs + 1, 36)); + valueProducer->onDataPulled(allData, /** succeed */ true, bucket4StartTimeNs); + ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); + curInterval = valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0]; + curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second.baseInfos[0]; + + EXPECT_EQ(true, curBaseInfo.hasBase); + EXPECT_EQ(36, curBaseInfo.base.long_value); + EXPECT_EQ(false, curInterval.hasValue); + EXPECT_EQ(13, curInterval.value.long_value); + ASSERT_EQ(1UL, valueProducer->mPastBuckets.size()); + ASSERT_EQ(3UL, valueProducer->mPastBuckets.begin()->second.size()); + EXPECT_EQ(8, valueProducer->mPastBuckets.begin()->second[0].values[0].long_value); + EXPECT_EQ(bucketSizeNs, valueProducer->mPastBuckets.begin()->second[0].mConditionTrueNs); + EXPECT_EQ(12, valueProducer->mPastBuckets.begin()->second[1].values[0].long_value); + EXPECT_EQ(bucketSizeNs, valueProducer->mPastBuckets.begin()->second[1].mConditionTrueNs); + EXPECT_EQ(13, valueProducer->mPastBuckets.begin()->second[2].values[0].long_value); + EXPECT_EQ(bucketSizeNs, valueProducer->mPastBuckets.begin()->second[2].mConditionTrueNs); +} + +TEST_P(ValueMetricProducerTest_PartialBucket, TestPartialBucketCreated) { + ValueMetric metric = ValueMetricProducerTestHelper::createMetric(); + sp pullerManager = new StrictMock(); + int64_t partialBucketSplitTimeNs = bucket2StartTimeNs + 2; + EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _)) + // Initialize bucket. + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector>* data) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs); + data->clear(); + data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 1, 1)); + return true; + })) + // Partial bucket. + .WillOnce(Invoke([partialBucketSplitTimeNs](int tagId, const ConfigKey&, + const int64_t eventTimeNs, + vector>* data) { + EXPECT_EQ(eventTimeNs, partialBucketSplitTimeNs); + data->clear(); + data->push_back( + CreateRepeatedValueLogEvent(tagId, partialBucketSplitTimeNs + 8, 5)); + return true; + })); + + sp valueProducer = + ValueMetricProducerTestHelper::createValueProducerNoConditions(pullerManager, metric); + + // First bucket ends. + vector> allData; + allData.clear(); + allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs + 10, 2)); + valueProducer->onDataPulled(allData, /** success */ true, bucket2StartTimeNs); + + // Partial buckets created in 2nd bucket. + switch (GetParam()) { + case APP_UPGRADE: + valueProducer->notifyAppUpgrade(partialBucketSplitTimeNs); + break; + case BOOT_COMPLETE: + valueProducer->onStatsdInitCompleted(partialBucketSplitTimeNs); + break; + } + EXPECT_EQ(partialBucketSplitTimeNs, valueProducer->mCurrentBucketStartTimeNs); + EXPECT_EQ(1, valueProducer->getCurrentBucketNum()); + + assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {1, 3}, + {bucketSizeNs, partialBucketSplitTimeNs - bucket2StartTimeNs}, + {bucketStartTimeNs, bucket2StartTimeNs}, + {bucket2StartTimeNs, partialBucketSplitTimeNs}); +} + +/* + * Tests pulled atoms with filtering + */ +TEST(ValueMetricProducerTest, TestPulledEventsWithFiltering) { + ValueMetric metric = ValueMetricProducerTestHelper::createMetric(); + + FieldValueMatcher fvm; + fvm.set_field(1); + fvm.set_eq_int(3); + sp eventMatcherWizard = + createEventMatcherWizard(tagId, logEventMatcherIndex, {fvm}); + sp wizard = new NaggyMock(); + sp pullerManager = new StrictMock(); + EXPECT_CALL(*pullerManager, RegisterReceiver(tagId, kConfigKey, _, _, _)).WillOnce(Return()); + EXPECT_CALL(*pullerManager, UnRegisterReceiver(tagId, kConfigKey, _)).WillOnce(Return()); + EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, bucketStartTimeNs, _)) + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t, + vector>* data) { + data->clear(); + data->push_back(CreateTwoValueLogEvent(tagId, bucketStartTimeNs, 3, 3)); + return true; + })); + + sp valueProducer = + new ValueMetricProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, {}, wizard, + protoHash, logEventMatcherIndex, eventMatcherWizard, tagId, + bucketStartTimeNs, bucketStartTimeNs, pullerManager); + valueProducer->prepareFirstBucket(); + + vector> allData; + allData.clear(); + allData.push_back(CreateTwoValueLogEvent(tagId, bucket2StartTimeNs + 1, 3, 11)); + + valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs); + // has one slice + ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); + ValueMetricProducer::Interval curInterval = + valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0]; + ValueMetricProducer::BaseInfo curBaseInfo = + valueProducer->mCurrentBaseInfo.begin()->second.baseInfos[0]; + + EXPECT_EQ(true, curBaseInfo.hasBase); + EXPECT_EQ(11, curBaseInfo.base.long_value); + EXPECT_EQ(false, curInterval.hasValue); + EXPECT_EQ(8, curInterval.value.long_value); + ASSERT_EQ(1UL, valueProducer->mPastBuckets.size()); + EXPECT_EQ(8, valueProducer->mPastBuckets.begin()->second[0].values[0].long_value); + EXPECT_EQ(bucketSizeNs, valueProducer->mPastBuckets.begin()->second[0].mConditionTrueNs); + + allData.clear(); + allData.push_back(CreateTwoValueLogEvent(tagId, bucket3StartTimeNs + 1, 4, 23)); + valueProducer->onDataPulled(allData, /** succeed */ true, bucket3StartTimeNs); + // No new data seen, so data has been cleared. + ASSERT_EQ(0UL, valueProducer->mCurrentSlicedBucket.size()); + + EXPECT_EQ(true, curBaseInfo.hasBase); + EXPECT_EQ(11, curBaseInfo.base.long_value); + EXPECT_EQ(false, curInterval.hasValue); + EXPECT_EQ(8, curInterval.value.long_value); + ASSERT_EQ(1UL, valueProducer->mPastBuckets.size()); + EXPECT_EQ(8, valueProducer->mPastBuckets.begin()->second[0].values[0].long_value); + EXPECT_EQ(bucketSizeNs, valueProducer->mPastBuckets.begin()->second[0].mConditionTrueNs); + + allData.clear(); + allData.push_back(CreateTwoValueLogEvent(tagId, bucket4StartTimeNs + 1, 3, 36)); + valueProducer->onDataPulled(allData, /** succeed */ true, bucket4StartTimeNs); + ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); + curInterval = valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0]; + curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second.baseInfos[0]; + + // the base was reset + EXPECT_EQ(true, curBaseInfo.hasBase); + EXPECT_EQ(36, curBaseInfo.base.long_value); + EXPECT_EQ(false, curInterval.hasValue); + ASSERT_EQ(1UL, valueProducer->mPastBuckets.size()); + ASSERT_EQ(1UL, valueProducer->mPastBuckets.begin()->second.size()); + EXPECT_EQ(8, valueProducer->mPastBuckets.begin()->second.back().values[0].long_value); + EXPECT_EQ(bucketSizeNs, valueProducer->mPastBuckets.begin()->second.back().mConditionTrueNs); +} + +/* + * Tests pulled atoms with no conditions and take absolute value after reset + */ +TEST(ValueMetricProducerTest, TestPulledEventsTakeAbsoluteValueOnReset) { + ValueMetric metric = ValueMetricProducerTestHelper::createMetric(); + metric.set_use_absolute_value_on_reset(true); + + sp pullerManager = new StrictMock(); + EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, bucketStartTimeNs, _)) + .WillOnce(Return(true)); + sp valueProducer = + ValueMetricProducerTestHelper::createValueProducerNoConditions(pullerManager, metric); + + vector> allData; + allData.clear(); + allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs + 1, 11)); + + valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs); + // has one slice + ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); + ValueMetricProducer::Interval curInterval = + valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0]; + ValueMetricProducer::BaseInfo curBaseInfo = + valueProducer->mCurrentBaseInfo.begin()->second.baseInfos[0]; + + EXPECT_EQ(true, curBaseInfo.hasBase); + EXPECT_EQ(11, curBaseInfo.base.long_value); + EXPECT_EQ(false, curInterval.hasValue); + ASSERT_EQ(0UL, valueProducer->mPastBuckets.size()); + + allData.clear(); + allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket3StartTimeNs + 1, 10)); + valueProducer->onDataPulled(allData, /** succeed */ true, bucket3StartTimeNs); + // has one slice + ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); + curInterval = valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0]; + curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second.baseInfos[0]; + EXPECT_EQ(true, curBaseInfo.hasBase); + EXPECT_EQ(10, curBaseInfo.base.long_value); + EXPECT_EQ(false, curInterval.hasValue); + EXPECT_EQ(10, curInterval.value.long_value); + ASSERT_EQ(1UL, valueProducer->mPastBuckets.size()); + EXPECT_EQ(10, valueProducer->mPastBuckets.begin()->second.back().values[0].long_value); + EXPECT_EQ(bucketSizeNs, valueProducer->mPastBuckets.begin()->second.back().mConditionTrueNs); + + allData.clear(); + allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket4StartTimeNs + 1, 36)); + valueProducer->onDataPulled(allData, /** succeed */ true, bucket4StartTimeNs); + ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); + curInterval = valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0]; + curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second.baseInfos[0]; + EXPECT_EQ(true, curBaseInfo.hasBase); + EXPECT_EQ(36, curBaseInfo.base.long_value); + EXPECT_EQ(false, curInterval.hasValue); + EXPECT_EQ(26, curInterval.value.long_value); + ASSERT_EQ(1UL, valueProducer->mPastBuckets.size()); + ASSERT_EQ(2UL, valueProducer->mPastBuckets.begin()->second.size()); + EXPECT_EQ(10, valueProducer->mPastBuckets.begin()->second[0].values[0].long_value); + EXPECT_EQ(bucketSizeNs, valueProducer->mPastBuckets.begin()->second[0].mConditionTrueNs); + EXPECT_EQ(26, valueProducer->mPastBuckets.begin()->second[1].values[0].long_value); + EXPECT_EQ(bucketSizeNs, valueProducer->mPastBuckets.begin()->second[1].mConditionTrueNs); +} + +/* + * Tests pulled atoms with no conditions and take zero value after reset + */ +TEST(ValueMetricProducerTest, TestPulledEventsTakeZeroOnReset) { + ValueMetric metric = ValueMetricProducerTestHelper::createMetric(); + sp pullerManager = new StrictMock(); + EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, bucketStartTimeNs, _)) + .WillOnce(Return(false)); + sp valueProducer = + ValueMetricProducerTestHelper::createValueProducerNoConditions(pullerManager, metric); + + vector> allData; + allData.clear(); + allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs + 1, 11)); + + valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs); + // has one slice + ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); + ValueMetricProducer::Interval curInterval = + valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0]; + ValueMetricProducer::BaseInfo curBaseInfo = + valueProducer->mCurrentBaseInfo.begin()->second.baseInfos[0]; + + EXPECT_EQ(true, curBaseInfo.hasBase); + EXPECT_EQ(11, curBaseInfo.base.long_value); + EXPECT_EQ(false, curInterval.hasValue); + ASSERT_EQ(0UL, valueProducer->mPastBuckets.size()); + + allData.clear(); + allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket3StartTimeNs + 1, 10)); + valueProducer->onDataPulled(allData, /** succeed */ true, bucket3StartTimeNs); + // has one slice + ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); + curInterval = valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0]; + curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second.baseInfos[0]; + EXPECT_EQ(true, curBaseInfo.hasBase); + EXPECT_EQ(10, curBaseInfo.base.long_value); + EXPECT_EQ(false, curInterval.hasValue); + ASSERT_EQ(0UL, valueProducer->mPastBuckets.size()); + + allData.clear(); + allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket4StartTimeNs + 1, 36)); + valueProducer->onDataPulled(allData, /** succeed */ true, bucket4StartTimeNs); + ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); + curInterval = valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0]; + curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second.baseInfos[0]; + EXPECT_EQ(true, curBaseInfo.hasBase); + EXPECT_EQ(36, curBaseInfo.base.long_value); + EXPECT_EQ(false, curInterval.hasValue); + EXPECT_EQ(26, curInterval.value.long_value); + ASSERT_EQ(1UL, valueProducer->mPastBuckets.size()); + EXPECT_EQ(26, valueProducer->mPastBuckets.begin()->second[0].values[0].long_value); + EXPECT_EQ(bucketSizeNs, valueProducer->mPastBuckets.begin()->second[0].mConditionTrueNs); +} + +/* + * Test pulled event with non sliced condition. + */ +TEST(ValueMetricProducerTest, TestEventsWithNonSlicedCondition) { + ValueMetric metric = ValueMetricProducerTestHelper::createMetricWithCondition(); + + sp pullerManager = new StrictMock(); + + EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _)) + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector>* data) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 8); // First condition change. + data->clear(); + data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 8, 100)); + return true; + })) + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector>* data) { + EXPECT_EQ(eventTimeNs, bucket2StartTimeNs + 1); // Second condition change. + data->clear(); + data->push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs + 1, 130)); + return true; + })) + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector>* data) { + EXPECT_EQ(eventTimeNs, bucket3StartTimeNs + 1); // Third condition change. + data->clear(); + data->push_back(CreateRepeatedValueLogEvent(tagId, bucket3StartTimeNs + 1, 180)); + return true; + })); + + sp valueProducer = + ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric, + ConditionState::kFalse); + + valueProducer->onConditionChanged(true, bucketStartTimeNs + 8); + + // has one slice + ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); + ValueMetricProducer::Interval curInterval = + valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0]; + ValueMetricProducer::BaseInfo curBaseInfo = + valueProducer->mCurrentBaseInfo.begin()->second.baseInfos[0]; + // startUpdated:false sum:0 start:100 + EXPECT_EQ(true, curBaseInfo.hasBase); + EXPECT_EQ(100, curBaseInfo.base.long_value); + EXPECT_EQ(false, curInterval.hasValue); + ASSERT_EQ(0UL, valueProducer->mPastBuckets.size()); + + vector> allData; + allData.clear(); + allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs + 1, 110)); + valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs); + assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {10}, {bucketSizeNs - 8}, + {bucketStartTimeNs}, {bucket2StartTimeNs}); + + // has one slice + ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); + curInterval = valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0]; + curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second.baseInfos[0]; + EXPECT_EQ(true, curBaseInfo.hasBase); + EXPECT_EQ(110, curBaseInfo.base.long_value); + EXPECT_EQ(false, curInterval.hasValue); + EXPECT_EQ(10, curInterval.value.long_value); + + valueProducer->onConditionChanged(false, bucket2StartTimeNs + 1); + assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {10}, {bucketSizeNs - 8}, + {bucketStartTimeNs}, {bucket2StartTimeNs}); + + // has one slice + ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); + curInterval = valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0]; + curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second.baseInfos[0]; + EXPECT_EQ(true, curInterval.hasValue); + EXPECT_EQ(20, curInterval.value.long_value); + EXPECT_EQ(false, curBaseInfo.hasBase); + + valueProducer->onConditionChanged(true, bucket3StartTimeNs + 1); + assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {10, 20}, {bucketSizeNs - 8, 1}, + {bucketStartTimeNs, bucket2StartTimeNs}, + {bucket2StartTimeNs, bucket3StartTimeNs}); +} + +TEST_P(ValueMetricProducerTest_PartialBucket, TestPushedEvents) { + ValueMetric metric = ValueMetricProducerTestHelper::createMetric(); + + sp eventMatcherWizard = + createEventMatcherWizard(tagId, logEventMatcherIndex); + sp wizard = new NaggyMock(); + sp pullerManager = new StrictMock(); + + ValueMetricProducer valueProducer(kConfigKey, metric, -1, {}, wizard, protoHash, + logEventMatcherIndex, eventMatcherWizard, -1, + bucketStartTimeNs, bucketStartTimeNs, pullerManager); + valueProducer.prepareFirstBucket(); + + LogEvent event1(/*uid=*/0, /*pid=*/0); + CreateRepeatedValueLogEvent(&event1, tagId, bucketStartTimeNs + 10, 10); + valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event1); + ASSERT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); + + int64_t partialBucketSplitTimeNs = bucketStartTimeNs + 150; + switch (GetParam()) { + case APP_UPGRADE: + valueProducer.notifyAppUpgrade(partialBucketSplitTimeNs); + break; + case BOOT_COMPLETE: + valueProducer.onStatsdInitCompleted(partialBucketSplitTimeNs); + break; + } + assertPastBucketValuesSingleKey(valueProducer.mPastBuckets, {10}, + {partialBucketSplitTimeNs - bucketStartTimeNs}, + {bucketStartTimeNs}, {partialBucketSplitTimeNs}); + EXPECT_EQ(partialBucketSplitTimeNs, valueProducer.mCurrentBucketStartTimeNs); + EXPECT_EQ(0, valueProducer.getCurrentBucketNum()); + + // Event arrives after the bucket split. + LogEvent event2(/*uid=*/0, /*pid=*/0); + CreateRepeatedValueLogEvent(&event2, tagId, bucketStartTimeNs + 59 * NS_PER_SEC, 20); + valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event2); + + assertPastBucketValuesSingleKey(valueProducer.mPastBuckets, {10}, + {partialBucketSplitTimeNs - bucketStartTimeNs}, + {bucketStartTimeNs}, {partialBucketSplitTimeNs}); + EXPECT_EQ(partialBucketSplitTimeNs, valueProducer.mCurrentBucketStartTimeNs); + EXPECT_EQ(0, valueProducer.getCurrentBucketNum()); + + // Next value should create a new bucket. + LogEvent event3(/*uid=*/0, /*pid=*/0); + CreateRepeatedValueLogEvent(&event3, tagId, bucket2StartTimeNs + 5 * NS_PER_SEC, 10); + valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event3); + assertPastBucketValuesSingleKey(valueProducer.mPastBuckets, {10, 20}, + {partialBucketSplitTimeNs - bucketStartTimeNs, + bucket2StartTimeNs - partialBucketSplitTimeNs}, + {bucketStartTimeNs, partialBucketSplitTimeNs}, + {partialBucketSplitTimeNs, bucket2StartTimeNs}); + EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, valueProducer.mCurrentBucketStartTimeNs); + EXPECT_EQ(1, valueProducer.getCurrentBucketNum()); +} + +TEST_P(ValueMetricProducerTest_PartialBucket, TestPulledValue) { + ValueMetric metric = ValueMetricProducerTestHelper::createMetric(); + + sp eventMatcherWizard = + createEventMatcherWizard(tagId, logEventMatcherIndex); + sp wizard = new NaggyMock(); + sp pullerManager = new StrictMock(); + int64_t partialBucketSplitTimeNs = bucket2StartTimeNs + 150; + EXPECT_CALL(*pullerManager, RegisterReceiver(tagId, kConfigKey, _, _, _)).WillOnce(Return()); + EXPECT_CALL(*pullerManager, UnRegisterReceiver(tagId, kConfigKey, _)).WillOnce(Return()); + EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _)) + .WillOnce(Return(true)) + .WillOnce(Invoke([partialBucketSplitTimeNs](int tagId, const ConfigKey&, + const int64_t eventTimeNs, + vector>* data) { + EXPECT_EQ(eventTimeNs, partialBucketSplitTimeNs); + data->clear(); + data->push_back(CreateRepeatedValueLogEvent(tagId, partialBucketSplitTimeNs, 120)); + return true; + })); + + ValueMetricProducer valueProducer(kConfigKey, metric, -1, {}, wizard, protoHash, + logEventMatcherIndex, eventMatcherWizard, tagId, + bucketStartTimeNs, bucketStartTimeNs, pullerManager); + valueProducer.prepareFirstBucket(); + + vector> allData; + allData.clear(); + allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs + 1, 100)); + + valueProducer.onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs); + ASSERT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); + + switch (GetParam()) { + case APP_UPGRADE: + valueProducer.notifyAppUpgrade(partialBucketSplitTimeNs); + break; + case BOOT_COMPLETE: + valueProducer.onStatsdInitCompleted(partialBucketSplitTimeNs); + break; + } + EXPECT_EQ(partialBucketSplitTimeNs, valueProducer.mCurrentBucketStartTimeNs); + EXPECT_EQ(1, valueProducer.getCurrentBucketNum()); + assertPastBucketValuesSingleKey(valueProducer.mPastBuckets, {20}, {150}, {bucket2StartTimeNs}, + {partialBucketSplitTimeNs}); + + allData.clear(); + allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket3StartTimeNs + 1, 150)); + valueProducer.onDataPulled(allData, /** succeed */ true, bucket3StartTimeNs); + EXPECT_EQ(bucket3StartTimeNs, valueProducer.mCurrentBucketStartTimeNs); + EXPECT_EQ(2, valueProducer.getCurrentBucketNum()); + assertPastBucketValuesSingleKey(valueProducer.mPastBuckets, {20, 30}, {150, bucketSizeNs - 150}, + {bucket2StartTimeNs, partialBucketSplitTimeNs}, + {partialBucketSplitTimeNs, bucket3StartTimeNs}); +} + +TEST(ValueMetricProducerTest, TestPulledWithAppUpgradeDisabled) { + ValueMetric metric = ValueMetricProducerTestHelper::createMetric(); + metric.set_split_bucket_for_app_upgrade(false); + + sp eventMatcherWizard = + createEventMatcherWizard(tagId, logEventMatcherIndex); + sp wizard = new NaggyMock(); + sp pullerManager = new StrictMock(); + EXPECT_CALL(*pullerManager, RegisterReceiver(tagId, kConfigKey, _, _, _)).WillOnce(Return()); + EXPECT_CALL(*pullerManager, UnRegisterReceiver(tagId, kConfigKey, _)).WillOnce(Return()); + EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, bucketStartTimeNs, _)) + .WillOnce(Return(true)); + + ValueMetricProducer valueProducer(kConfigKey, metric, -1, {}, wizard, protoHash, + logEventMatcherIndex, eventMatcherWizard, tagId, + bucketStartTimeNs, bucketStartTimeNs, pullerManager); + valueProducer.prepareFirstBucket(); + + vector> allData; + allData.clear(); + allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs + 1, 100)); + + valueProducer.onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs); + ASSERT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); + + valueProducer.notifyAppUpgrade(bucket2StartTimeNs + 150); + ASSERT_EQ(0UL, valueProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size()); + EXPECT_EQ(bucket2StartTimeNs, valueProducer.mCurrentBucketStartTimeNs); +} + +TEST_P(ValueMetricProducerTest_PartialBucket, TestPulledValueWhileConditionFalse) { + ValueMetric metric = ValueMetricProducerTestHelper::createMetricWithCondition(); + + sp pullerManager = new StrictMock(); + EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _)) + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector>* data) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 1); // Condition change to true time. + data->clear(); + data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 1, 100)); + return true; + })) + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector>* data) { + EXPECT_EQ(eventTimeNs, + bucket2StartTimeNs - 100); // Condition change to false time. + data->clear(); + data->push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs - 100, 120)); + return true; + })); + sp valueProducer = + ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric, + ConditionState::kFalse); + + valueProducer->onConditionChanged(true, bucketStartTimeNs + 1); + + valueProducer->onConditionChanged(false, bucket2StartTimeNs - 100); + EXPECT_FALSE(valueProducer->mCondition); + + int64_t partialBucketSplitTimeNs = bucket2StartTimeNs - 50; + switch (GetParam()) { + case APP_UPGRADE: + valueProducer->notifyAppUpgrade(partialBucketSplitTimeNs); + break; + case BOOT_COMPLETE: + valueProducer->onStatsdInitCompleted(partialBucketSplitTimeNs); + break; + } + // Expect one full buckets already done and starting a partial bucket. + EXPECT_EQ(partialBucketSplitTimeNs, valueProducer->mCurrentBucketStartTimeNs); + EXPECT_EQ(0, valueProducer->getCurrentBucketNum()); + assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {20}, + {(bucket2StartTimeNs - 100) - (bucketStartTimeNs + 1)}, + {bucketStartTimeNs}, {partialBucketSplitTimeNs}); + EXPECT_FALSE(valueProducer->mCondition); +} + +TEST(ValueMetricProducerTest, TestPushedEventsWithoutCondition) { + ValueMetric metric = ValueMetricProducerTestHelper::createMetric(); + + sp eventMatcherWizard = + createEventMatcherWizard(tagId, logEventMatcherIndex); + sp wizard = new NaggyMock(); + sp pullerManager = new StrictMock(); + + ValueMetricProducer valueProducer(kConfigKey, metric, -1, {}, wizard, protoHash, + logEventMatcherIndex, eventMatcherWizard, -1, + bucketStartTimeNs, bucketStartTimeNs, pullerManager); + valueProducer.prepareFirstBucket(); + + LogEvent event1(/*uid=*/0, /*pid=*/0); + CreateRepeatedValueLogEvent(&event1, tagId, bucketStartTimeNs + 10, 10); + + LogEvent event2(/*uid=*/0, /*pid=*/0); + CreateRepeatedValueLogEvent(&event2, tagId, bucketStartTimeNs + 20, 20); + + valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event1); + // has one slice + ASSERT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); + ValueMetricProducer::Interval curInterval = + valueProducer.mCurrentSlicedBucket.begin()->second.intervals[0]; + ValueMetricProducer::BaseInfo curBaseInfo = + valueProducer.mCurrentBaseInfo.begin()->second.baseInfos[0]; + EXPECT_EQ(10, curInterval.value.long_value); + EXPECT_EQ(true, curInterval.hasValue); + + valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event2); + + // has one slice + ASSERT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); + curInterval = valueProducer.mCurrentSlicedBucket.begin()->second.intervals[0]; + EXPECT_EQ(30, curInterval.value.long_value); + + valueProducer.flushIfNeededLocked(bucket2StartTimeNs); + assertPastBucketValuesSingleKey(valueProducer.mPastBuckets, {30}, {bucketSizeNs}, + {bucketStartTimeNs}, {bucket2StartTimeNs}); +} + +TEST(ValueMetricProducerTest, TestPushedEventsWithCondition) { + ValueMetric metric = ValueMetricProducerTestHelper::createMetric(); + + sp eventMatcherWizard = + createEventMatcherWizard(tagId, logEventMatcherIndex); + sp wizard = new NaggyMock(); + sp pullerManager = new StrictMock(); + + ValueMetricProducer valueProducer(kConfigKey, metric, 0, {ConditionState::kUnknown}, wizard, + protoHash, logEventMatcherIndex, eventMatcherWizard, -1, + bucketStartTimeNs, bucketStartTimeNs, pullerManager); + valueProducer.prepareFirstBucket(); + valueProducer.mCondition = ConditionState::kFalse; + + LogEvent event1(/*uid=*/0, /*pid=*/0); + CreateRepeatedValueLogEvent(&event1, tagId, bucketStartTimeNs + 10, 10); + valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event1); + // has 1 slice + ASSERT_EQ(0UL, valueProducer.mCurrentSlicedBucket.size()); + + valueProducer.onConditionChangedLocked(true, bucketStartTimeNs + 15); + + LogEvent event2(/*uid=*/0, /*pid=*/0); + CreateRepeatedValueLogEvent(&event2, tagId, bucketStartTimeNs + 20, 20); + valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event2); + + // has one slice + ASSERT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); + ValueMetricProducer::Interval curInterval = + valueProducer.mCurrentSlicedBucket.begin()->second.intervals[0]; + curInterval = valueProducer.mCurrentSlicedBucket.begin()->second.intervals[0]; + EXPECT_EQ(20, curInterval.value.long_value); + + LogEvent event3(/*uid=*/0, /*pid=*/0); + CreateRepeatedValueLogEvent(&event3, tagId, bucketStartTimeNs + 30, 30); + valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event3); + + // has one slice + ASSERT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); + curInterval = valueProducer.mCurrentSlicedBucket.begin()->second.intervals[0]; + EXPECT_EQ(50, curInterval.value.long_value); + + valueProducer.onConditionChangedLocked(false, bucketStartTimeNs + 35); + + LogEvent event4(/*uid=*/0, /*pid=*/0); + CreateRepeatedValueLogEvent(&event4, tagId, bucketStartTimeNs + 40, 40); + valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event4); + + // has one slice + ASSERT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); + curInterval = valueProducer.mCurrentSlicedBucket.begin()->second.intervals[0]; + EXPECT_EQ(50, curInterval.value.long_value); + + valueProducer.flushIfNeededLocked(bucket2StartTimeNs); + assertPastBucketValuesSingleKey(valueProducer.mPastBuckets, {50}, {20}, {bucketStartTimeNs}, + {bucket2StartTimeNs}); +} + +TEST(ValueMetricProducerTest, TestAnomalyDetection) { + sp alarmMonitor; + Alert alert; + alert.set_id(101); + alert.set_metric_id(metricId); + alert.set_trigger_if_sum_gt(130); + alert.set_num_buckets(2); + const int32_t refPeriodSec = 3; + alert.set_refractory_period_secs(refPeriodSec); + + ValueMetric metric = ValueMetricProducerTestHelper::createMetric(); + + sp eventMatcherWizard = + createEventMatcherWizard(tagId, logEventMatcherIndex); + sp wizard = new NaggyMock(); + sp pullerManager = new StrictMock(); + + ValueMetricProducer valueProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, {}, + wizard, protoHash, logEventMatcherIndex, eventMatcherWizard, + -1 /*not pulled*/, bucketStartTimeNs, bucketStartTimeNs, + pullerManager); + valueProducer.prepareFirstBucket(); + + sp anomalyTracker = + valueProducer.addAnomalyTracker(alert, alarmMonitor, UPDATE_NEW, bucketStartTimeNs); + + LogEvent event1(/*uid=*/0, /*pid=*/0); + CreateRepeatedValueLogEvent(&event1, tagId, bucketStartTimeNs + 1 * NS_PER_SEC, 10); + + LogEvent event2(/*uid=*/0, /*pid=*/0); + CreateRepeatedValueLogEvent(&event2, tagId, bucketStartTimeNs + 2 + NS_PER_SEC, 20); + + LogEvent event3(/*uid=*/0, /*pid=*/0); + CreateRepeatedValueLogEvent(&event3, tagId, + bucketStartTimeNs + 2 * bucketSizeNs + 1 * NS_PER_SEC, 130); + + LogEvent event4(/*uid=*/0, /*pid=*/0); + CreateRepeatedValueLogEvent(&event4, tagId, + bucketStartTimeNs + 3 * bucketSizeNs + 1 * NS_PER_SEC, 1); + + LogEvent event5(/*uid=*/0, /*pid=*/0); + CreateRepeatedValueLogEvent(&event5, tagId, + bucketStartTimeNs + 3 * bucketSizeNs + 2 * NS_PER_SEC, 150); + + LogEvent event6(/*uid=*/0, /*pid=*/0); + CreateRepeatedValueLogEvent(&event6, tagId, + bucketStartTimeNs + 3 * bucketSizeNs + 10 * NS_PER_SEC, 160); + + // Two events in bucket #0. + valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event1); + valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event2); + // Value sum == 30 <= 130. + 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_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_METRIC_DIMENSION_KEY), + std::ceil(1.0 * event4.GetElapsedTimestampNs() / 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_METRIC_DIMENSION_KEY), + std::ceil(1.0 * event4.GetElapsedTimestampNs() / 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_METRIC_DIMENSION_KEY), + std::ceil(1.0 * event6.GetElapsedTimestampNs() / NS_PER_SEC + refPeriodSec)); +} + +TEST(ValueMetricProducerTest, TestAnomalyDetectionMultipleBucketsSkipped) { + sp alarmMonitor; + Alert alert; + alert.set_id(101); + alert.set_metric_id(metricId); + alert.set_trigger_if_sum_gt(100); + alert.set_num_buckets(1); + const int32_t refPeriodSec = 3; + alert.set_refractory_period_secs(refPeriodSec); + + ValueMetric metric = ValueMetricProducerTestHelper::createMetricWithCondition(); + + sp pullerManager = new StrictMock(); + EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _)) + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector>* data) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 1); // Condition change to true time. + data->clear(); + data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 1, 0)); + return true; + })) + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector>* data) { + EXPECT_EQ(eventTimeNs, + bucket3StartTimeNs + 100); // Condition changed to false time. + data->clear(); + data->push_back(CreateRepeatedValueLogEvent(tagId, bucket3StartTimeNs + 100, 120)); + return true; + })); + sp valueProducer = + ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric, + ConditionState::kFalse); + sp anomalyTracker = + valueProducer->addAnomalyTracker(alert, alarmMonitor, UPDATE_NEW, bucketStartTimeNs); + + valueProducer->onConditionChanged(true, bucketStartTimeNs + 1); + + // multiple buckets should be skipped here. + valueProducer->onConditionChanged(false, bucket3StartTimeNs + 100); + + // No alert is fired when multiple buckets are skipped. + EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(DEFAULT_METRIC_DIMENSION_KEY), 0U); +} + +// Test value metric no condition, the pull on bucket boundary come in time and too late +TEST(ValueMetricProducerTest, TestBucketBoundaryNoCondition) { + ValueMetric metric = ValueMetricProducerTestHelper::createMetric(); + sp pullerManager = new StrictMock(); + EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, bucketStartTimeNs, _)) + .WillOnce(Return(true)); + sp valueProducer = + ValueMetricProducerTestHelper::createValueProducerNoConditions(pullerManager, metric); + + vector> allData; + // pull 1 + allData.clear(); + allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs + 1, 11)); + + valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs); + // has one slice + ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); + ValueMetricProducer::Interval curInterval = + valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0]; + ValueMetricProducer::BaseInfo curBaseInfo = + valueProducer->mCurrentBaseInfo.begin()->second.baseInfos[0]; + + // startUpdated:true sum:0 start:11 + EXPECT_EQ(true, curBaseInfo.hasBase); + EXPECT_EQ(11, curBaseInfo.base.long_value); + EXPECT_EQ(false, curInterval.hasValue); + ASSERT_EQ(0UL, valueProducer->mPastBuckets.size()); + + // pull 2 at correct time + allData.clear(); + allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket3StartTimeNs + 1, 23)); + valueProducer->onDataPulled(allData, /** succeed */ true, bucket3StartTimeNs); + // has one slice + ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); + curInterval = valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0]; + curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second.baseInfos[0]; + // tartUpdated:false sum:12 + EXPECT_EQ(true, curBaseInfo.hasBase); + EXPECT_EQ(23, curBaseInfo.base.long_value); + EXPECT_EQ(false, curInterval.hasValue); + assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {12}, {bucketSizeNs}, + {bucket2StartTimeNs}, {bucket3StartTimeNs}); + + // pull 3 come late. + // The previous bucket gets closed with error. (Has start value 23, no ending) + // Another bucket gets closed with error. (No start, but ending with 36) + // The new bucket is back to normal. + allData.clear(); + allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket6StartTimeNs + 1, 36)); + valueProducer->onDataPulled(allData, /** succeed */ true, bucket6StartTimeNs); + ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); + curInterval = valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0]; + curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second.baseInfos[0]; + // startUpdated:false sum:12 + EXPECT_EQ(true, curBaseInfo.hasBase); + EXPECT_EQ(36, curBaseInfo.base.long_value); + EXPECT_EQ(false, curInterval.hasValue); + assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {12}, {bucketSizeNs}, + {bucket2StartTimeNs}, {bucket3StartTimeNs}); + // The 1st bucket is dropped because of no data + // The 3rd bucket is dropped due to multiple buckets being skipped. + ASSERT_EQ(2, valueProducer->mSkippedBuckets.size()); + + EXPECT_EQ(bucketStartTimeNs, valueProducer->mSkippedBuckets[0].bucketStartTimeNs); + EXPECT_EQ(bucket2StartTimeNs, valueProducer->mSkippedBuckets[0].bucketEndTimeNs); + ASSERT_EQ(1, valueProducer->mSkippedBuckets[0].dropEvents.size()); + EXPECT_EQ(NO_DATA, valueProducer->mSkippedBuckets[0].dropEvents[0].reason); + EXPECT_EQ(bucket2StartTimeNs, valueProducer->mSkippedBuckets[0].dropEvents[0].dropTimeNs); + + EXPECT_EQ(bucket3StartTimeNs, valueProducer->mSkippedBuckets[1].bucketStartTimeNs); + EXPECT_EQ(bucket6StartTimeNs, valueProducer->mSkippedBuckets[1].bucketEndTimeNs); + ASSERT_EQ(1, valueProducer->mSkippedBuckets[1].dropEvents.size()); + EXPECT_EQ(MULTIPLE_BUCKETS_SKIPPED, valueProducer->mSkippedBuckets[1].dropEvents[0].reason); + EXPECT_EQ(bucket6StartTimeNs, valueProducer->mSkippedBuckets[1].dropEvents[0].dropTimeNs); +} + +/* + * Test pulled event with non sliced condition. The pull on boundary come late because the alarm + * was delivered late. + */ +TEST(ValueMetricProducerTest, TestBucketBoundaryWithCondition) { + ValueMetric metric = ValueMetricProducerTestHelper::createMetricWithCondition(); + + sp pullerManager = new StrictMock(); + EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _)) + // condition becomes true + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector>* data) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 8); // First condition change. + data->clear(); + data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 8, 100)); + return true; + })) + // condition becomes false + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector>* data) { + EXPECT_EQ(eventTimeNs, bucket2StartTimeNs + 1); // Second condition change. + data->clear(); + data->push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs + 1, 120)); + return true; + })); + sp valueProducer = + ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric, + ConditionState::kFalse); + + valueProducer->onConditionChanged(true, bucketStartTimeNs + 8); + + // has one slice + ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); + ValueMetricProducer::Interval curInterval = + valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0]; + ValueMetricProducer::BaseInfo curBaseInfo = + valueProducer->mCurrentBaseInfo.begin()->second.baseInfos[0]; + EXPECT_EQ(true, curBaseInfo.hasBase); + EXPECT_EQ(100, curBaseInfo.base.long_value); + EXPECT_EQ(false, curInterval.hasValue); + ASSERT_EQ(0UL, valueProducer->mPastBuckets.size()); + + // pull on bucket boundary come late, condition change happens before it + valueProducer->onConditionChanged(false, bucket2StartTimeNs + 1); + curInterval = valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0]; + curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second.baseInfos[0]; + assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {20}, {bucketSizeNs - 8}, + {bucketStartTimeNs}, {bucket2StartTimeNs}); + EXPECT_EQ(false, curBaseInfo.hasBase); + + // Now the alarm is delivered. + // since the condition turned to off before this pull finish, it has no effect + vector> allData; + allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs + 30, 110)); + valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs); + + assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {20}, {bucketSizeNs - 8}, + {bucketStartTimeNs}, {bucket2StartTimeNs}); + curInterval = valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0]; + curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second.baseInfos[0]; + EXPECT_EQ(false, curBaseInfo.hasBase); + EXPECT_EQ(false, curInterval.hasValue); +} + +/* + * Test pulled event with non sliced condition. The pull on boundary come late, after the condition + * change to false, and then true again. This is due to alarm delivered late. + */ +TEST(ValueMetricProducerTest, TestBucketBoundaryWithCondition2) { + ValueMetric metric = ValueMetricProducerTestHelper::createMetricWithCondition(); + + sp pullerManager = new StrictMock(); + EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _)) + // condition becomes true + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector>* data) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 8); + data->clear(); + data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 8, 100)); + return true; + })) + // condition becomes false + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector>* data) { + EXPECT_EQ(eventTimeNs, bucket2StartTimeNs + 1); + data->clear(); + data->push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs + 1, 120)); + return true; + })) + // condition becomes true again + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector>* data) { + EXPECT_EQ(eventTimeNs, bucket2StartTimeNs + 25); + data->clear(); + data->push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs + 25, 130)); + return true; + })); + + sp valueProducer = + ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric, + ConditionState::kFalse); + + valueProducer->onConditionChanged(true, bucketStartTimeNs + 8); + + // has one slice + ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); + ValueMetricProducer::Interval curInterval = + valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0]; + ValueMetricProducer::BaseInfo curBaseInfo = + valueProducer->mCurrentBaseInfo.begin()->second.baseInfos[0]; + // startUpdated:false sum:0 start:100 + EXPECT_EQ(true, curBaseInfo.hasBase); + EXPECT_EQ(100, curBaseInfo.base.long_value); + EXPECT_EQ(false, curInterval.hasValue); + ASSERT_EQ(0UL, valueProducer->mPastBuckets.size()); + + // pull on bucket boundary come late, condition change happens before it + valueProducer->onConditionChanged(false, bucket2StartTimeNs + 1); + assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {20}, {bucketSizeNs - 8}, + {bucketStartTimeNs}, {bucket2StartTimeNs}); + ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); + curInterval = valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0]; + curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second.baseInfos[0]; + EXPECT_EQ(false, curBaseInfo.hasBase); + EXPECT_EQ(false, curInterval.hasValue); + + // condition changed to true again, before the pull alarm is delivered + valueProducer->onConditionChanged(true, bucket2StartTimeNs + 25); + assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {20}, {bucketSizeNs - 8}, + {bucketStartTimeNs}, {bucket2StartTimeNs}); + curInterval = valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0]; + curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second.baseInfos[0]; + EXPECT_EQ(true, curBaseInfo.hasBase); + EXPECT_EQ(130, curBaseInfo.base.long_value); + EXPECT_EQ(false, curInterval.hasValue); + + // Now the alarm is delivered, but it is considered late, the data will be used + // for the new bucket since it was just pulled. + vector> allData; + allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs + 50, 140)); + valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs + 50); + + curInterval = valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0]; + curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second.baseInfos[0]; + EXPECT_EQ(true, curBaseInfo.hasBase); + EXPECT_EQ(140, curBaseInfo.base.long_value); + EXPECT_EQ(true, curInterval.hasValue); + EXPECT_EQ(10, curInterval.value.long_value); + assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {20}, {bucketSizeNs - 8}, + {bucketStartTimeNs}, {bucket2StartTimeNs}); + + allData.clear(); + allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket3StartTimeNs, 160)); + valueProducer->onDataPulled(allData, /** succeed */ true, bucket3StartTimeNs); + assertPastBucketValuesSingleKey( + valueProducer->mPastBuckets, {20, 30}, {bucketSizeNs - 8, bucketSizeNs - 24}, + {bucketStartTimeNs, bucket2StartTimeNs}, {bucket2StartTimeNs, bucket3StartTimeNs}); +} + +TEST(ValueMetricProducerTest, TestPushedAggregateMin) { + ValueMetric metric = ValueMetricProducerTestHelper::createMetric(); + metric.set_aggregation_type(ValueMetric::MIN); + + sp eventMatcherWizard = + createEventMatcherWizard(tagId, logEventMatcherIndex); + sp wizard = new NaggyMock(); + sp pullerManager = new StrictMock(); + + ValueMetricProducer valueProducer(kConfigKey, metric, -1, {}, wizard, protoHash, + logEventMatcherIndex, eventMatcherWizard, -1, + bucketStartTimeNs, bucketStartTimeNs, pullerManager); + valueProducer.prepareFirstBucket(); + + LogEvent event1(/*uid=*/0, /*pid=*/0); + CreateRepeatedValueLogEvent(&event1, tagId, bucketStartTimeNs + 10, 10); + + LogEvent event2(/*uid=*/0, /*pid=*/0); + CreateRepeatedValueLogEvent(&event2, tagId, bucketStartTimeNs + 20, 20); + + valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event1); + // has one slice + ASSERT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); + ValueMetricProducer::Interval curInterval = + valueProducer.mCurrentSlicedBucket.begin()->second.intervals[0]; + EXPECT_EQ(10, curInterval.value.long_value); + EXPECT_EQ(true, curInterval.hasValue); + + valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event2); + + // has one slice + ASSERT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); + curInterval = valueProducer.mCurrentSlicedBucket.begin()->second.intervals[0]; + EXPECT_EQ(10, curInterval.value.long_value); + + valueProducer.flushIfNeededLocked(bucket2StartTimeNs); + assertPastBucketValuesSingleKey(valueProducer.mPastBuckets, {10}, {bucketSizeNs}, + {bucketStartTimeNs}, {bucket2StartTimeNs}); +} + +TEST(ValueMetricProducerTest, TestPushedAggregateMax) { + ValueMetric metric = ValueMetricProducerTestHelper::createMetric(); + metric.set_aggregation_type(ValueMetric::MAX); + + sp eventMatcherWizard = + createEventMatcherWizard(tagId, logEventMatcherIndex); + sp wizard = new NaggyMock(); + sp pullerManager = new StrictMock(); + + ValueMetricProducer valueProducer(kConfigKey, metric, -1, {}, wizard, protoHash, + logEventMatcherIndex, eventMatcherWizard, -1, + bucketStartTimeNs, bucketStartTimeNs, pullerManager); + valueProducer.prepareFirstBucket(); + + LogEvent event1(/*uid=*/0, /*pid=*/0); + CreateRepeatedValueLogEvent(&event1, tagId, bucketStartTimeNs + 10, 10); + valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event1); + + // has one slice + ASSERT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); + ValueMetricProducer::Interval curInterval = + valueProducer.mCurrentSlicedBucket.begin()->second.intervals[0]; + EXPECT_EQ(10, curInterval.value.long_value); + EXPECT_EQ(true, curInterval.hasValue); + + LogEvent event2(/*uid=*/0, /*pid=*/0); + CreateRepeatedValueLogEvent(&event2, tagId, bucketStartTimeNs + 20, 20); + valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event2); + + // has one slice + ASSERT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); + curInterval = valueProducer.mCurrentSlicedBucket.begin()->second.intervals[0]; + EXPECT_EQ(20, curInterval.value.long_value); + + valueProducer.flushIfNeededLocked(bucket2StartTimeNs); + assertPastBucketValuesSingleKey(valueProducer.mPastBuckets, {20}, {bucketSizeNs}, + {bucketStartTimeNs}, {bucket2StartTimeNs}); +} + +TEST(ValueMetricProducerTest, TestPushedAggregateAvg) { + ValueMetric metric = ValueMetricProducerTestHelper::createMetric(); + metric.set_aggregation_type(ValueMetric::AVG); + + sp eventMatcherWizard = + createEventMatcherWizard(tagId, logEventMatcherIndex); + sp wizard = new NaggyMock(); + sp pullerManager = new StrictMock(); + + ValueMetricProducer valueProducer(kConfigKey, metric, -1, {}, wizard, protoHash, + logEventMatcherIndex, eventMatcherWizard, -1, + bucketStartTimeNs, bucketStartTimeNs, pullerManager); + valueProducer.prepareFirstBucket(); + + LogEvent event1(/*uid=*/0, /*pid=*/0); + CreateRepeatedValueLogEvent(&event1, tagId, bucketStartTimeNs + 10, 10); + + LogEvent event2(/*uid=*/0, /*pid=*/0); + CreateRepeatedValueLogEvent(&event2, tagId, bucketStartTimeNs + 20, 15); + valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event1); + // has one slice + ASSERT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); + ValueMetricProducer::Interval curInterval; + curInterval = valueProducer.mCurrentSlicedBucket.begin()->second.intervals[0]; + EXPECT_EQ(10, curInterval.value.long_value); + EXPECT_EQ(true, curInterval.hasValue); + EXPECT_EQ(1, curInterval.sampleSize); + + valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event2); + + // has one slice + ASSERT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); + curInterval = valueProducer.mCurrentSlicedBucket.begin()->second.intervals[0]; + EXPECT_EQ(25, curInterval.value.long_value); + EXPECT_EQ(2, curInterval.sampleSize); + + valueProducer.flushIfNeededLocked(bucket2StartTimeNs); + ASSERT_EQ(1UL, valueProducer.mPastBuckets.size()); + ASSERT_EQ(1UL, valueProducer.mPastBuckets.begin()->second.size()); + + EXPECT_TRUE(std::abs(valueProducer.mPastBuckets.begin()->second.back().values[0].double_value - + 12.5) < epsilon); +} + +TEST(ValueMetricProducerTest, TestPushedAggregateSum) { + ValueMetric metric = ValueMetricProducerTestHelper::createMetric(); + metric.set_aggregation_type(ValueMetric::SUM); + + sp eventMatcherWizard = + createEventMatcherWizard(tagId, logEventMatcherIndex); + sp wizard = new NaggyMock(); + sp pullerManager = new StrictMock(); + + ValueMetricProducer valueProducer(kConfigKey, metric, -1, {}, wizard, protoHash, + logEventMatcherIndex, eventMatcherWizard, -1, + bucketStartTimeNs, bucketStartTimeNs, pullerManager); + valueProducer.prepareFirstBucket(); + + LogEvent event1(/*uid=*/0, /*pid=*/0); + CreateRepeatedValueLogEvent(&event1, tagId, bucketStartTimeNs + 10, 10); + + LogEvent event2(/*uid=*/0, /*pid=*/0); + CreateRepeatedValueLogEvent(&event2, tagId, bucketStartTimeNs + 20, 15); + valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event1); + // has one slice + ASSERT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); + ValueMetricProducer::Interval curInterval = + valueProducer.mCurrentSlicedBucket.begin()->second.intervals[0]; + EXPECT_EQ(10, curInterval.value.long_value); + EXPECT_EQ(true, curInterval.hasValue); + + valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event2); + + // has one slice + ASSERT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); + curInterval = valueProducer.mCurrentSlicedBucket.begin()->second.intervals[0]; + EXPECT_EQ(25, curInterval.value.long_value); + + valueProducer.flushIfNeededLocked(bucket2StartTimeNs); + assertPastBucketValuesSingleKey(valueProducer.mPastBuckets, {25}, {bucketSizeNs}, + {bucketStartTimeNs}, {bucket2StartTimeNs}); +} + +TEST(ValueMetricProducerTest, TestSkipZeroDiffOutput) { + ValueMetric metric = ValueMetricProducerTestHelper::createMetric(); + metric.set_aggregation_type(ValueMetric::MIN); + metric.set_use_diff(true); + + sp eventMatcherWizard = + createEventMatcherWizard(tagId, logEventMatcherIndex); + sp wizard = new NaggyMock(); + sp pullerManager = new StrictMock(); + + ValueMetricProducer valueProducer(kConfigKey, metric, -1, {}, wizard, protoHash, + logEventMatcherIndex, eventMatcherWizard, -1, + bucketStartTimeNs, bucketStartTimeNs, pullerManager); + valueProducer.prepareFirstBucket(); + + LogEvent event1(/*uid=*/0, /*pid=*/0); + CreateRepeatedValueLogEvent(&event1, tagId, bucketStartTimeNs + 10, 10); + valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event1); + + // has one slice + ASSERT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); + ValueMetricProducer::Interval curInterval = + valueProducer.mCurrentSlicedBucket.begin()->second.intervals[0]; + ValueMetricProducer::BaseInfo curBaseInfo = + valueProducer.mCurrentBaseInfo.begin()->second.baseInfos[0]; + EXPECT_EQ(true, curBaseInfo.hasBase); + EXPECT_EQ(10, curBaseInfo.base.long_value); + EXPECT_EQ(false, curInterval.hasValue); + + LogEvent event2(/*uid=*/0, /*pid=*/0); + CreateRepeatedValueLogEvent(&event2, tagId, bucketStartTimeNs + 15, 15); + valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event2); + + // has one slice + ASSERT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); + curInterval = valueProducer.mCurrentSlicedBucket.begin()->second.intervals[0]; + EXPECT_EQ(true, curInterval.hasValue); + EXPECT_EQ(5, curInterval.value.long_value); + + // no change in data. + LogEvent event3(/*uid=*/0, /*pid=*/0); + CreateRepeatedValueLogEvent(&event3, tagId, bucket2StartTimeNs + 10, 15); + valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event3); + + ASSERT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); + curInterval = valueProducer.mCurrentSlicedBucket.begin()->second.intervals[0]; + curBaseInfo = valueProducer.mCurrentBaseInfo.begin()->second.baseInfos[0]; + EXPECT_EQ(true, curBaseInfo.hasBase); + EXPECT_EQ(15, curBaseInfo.base.long_value); + EXPECT_EQ(true, curInterval.hasValue); + EXPECT_EQ(0, curInterval.value.long_value); + + LogEvent event4(/*uid=*/0, /*pid=*/0); + CreateRepeatedValueLogEvent(&event4, tagId, bucket2StartTimeNs + 15, 15); + valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event4); + ASSERT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); + curInterval = valueProducer.mCurrentSlicedBucket.begin()->second.intervals[0]; + curBaseInfo = valueProducer.mCurrentBaseInfo.begin()->second.baseInfos[0]; + EXPECT_EQ(true, curBaseInfo.hasBase); + EXPECT_EQ(15, curBaseInfo.base.long_value); + EXPECT_EQ(true, curInterval.hasValue); + EXPECT_EQ(0, curInterval.value.long_value); + + valueProducer.flushIfNeededLocked(bucket3StartTimeNs); + assertPastBucketValuesSingleKey(valueProducer.mPastBuckets, {5}, {bucketSizeNs}, + {bucketStartTimeNs}, {bucket2StartTimeNs}); +} + +TEST(ValueMetricProducerTest, TestSkipZeroDiffOutputMultiValue) { + ValueMetric metric = ValueMetricProducerTestHelper::createMetric(); + metric.mutable_value_field()->add_child()->set_field(3); + metric.set_aggregation_type(ValueMetric::MIN); + metric.set_use_diff(true); + + sp eventMatcherWizard = + createEventMatcherWizard(tagId, logEventMatcherIndex); + sp wizard = new NaggyMock(); + sp pullerManager = new StrictMock(); + + ValueMetricProducer valueProducer(kConfigKey, metric, -1, {}, wizard, protoHash, + logEventMatcherIndex, eventMatcherWizard, -1, + bucketStartTimeNs, bucketStartTimeNs, pullerManager); + valueProducer.prepareFirstBucket(); + + LogEvent event1(/*uid=*/0, /*pid=*/0); + CreateThreeValueLogEvent(&event1, tagId, bucketStartTimeNs + 10, 1, 10, 20); + + LogEvent event2(/*uid=*/0, /*pid=*/0); + CreateThreeValueLogEvent(&event2, tagId, bucketStartTimeNs + 15, 1, 15, 22); + + valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event1); + // has one slice + ASSERT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); + ValueMetricProducer::Interval curInterval = + valueProducer.mCurrentSlicedBucket.begin()->second.intervals[0]; + ValueMetricProducer::BaseInfo curBaseInfo = + valueProducer.mCurrentBaseInfo.begin()->second.baseInfos[0]; + EXPECT_EQ(true, curBaseInfo.hasBase); + EXPECT_EQ(10, curBaseInfo.base.long_value); + EXPECT_EQ(false, curInterval.hasValue); + curBaseInfo = valueProducer.mCurrentBaseInfo.begin()->second.baseInfos[1]; + EXPECT_EQ(true, curBaseInfo.hasBase); + EXPECT_EQ(20, curBaseInfo.base.long_value); + EXPECT_EQ(false, curInterval.hasValue); + + valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event2); + + // has one slice + ASSERT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); + curInterval = valueProducer.mCurrentSlicedBucket.begin()->second.intervals[0]; + curBaseInfo = valueProducer.mCurrentBaseInfo.begin()->second.baseInfos[0]; + EXPECT_EQ(true, curInterval.hasValue); + EXPECT_EQ(5, curInterval.value.long_value); + curInterval = valueProducer.mCurrentSlicedBucket.begin()->second.intervals[1]; + curBaseInfo = valueProducer.mCurrentBaseInfo.begin()->second.baseInfos[1]; + EXPECT_EQ(true, curInterval.hasValue); + EXPECT_EQ(2, curInterval.value.long_value); + + // no change in first value field + LogEvent event3(/*uid=*/0, /*pid=*/0); + CreateThreeValueLogEvent(&event3, tagId, bucket2StartTimeNs + 10, 1, 15, 25); + + valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event3); + ASSERT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); + curInterval = valueProducer.mCurrentSlicedBucket.begin()->second.intervals[0]; + curBaseInfo = valueProducer.mCurrentBaseInfo.begin()->second.baseInfos[0]; + + EXPECT_EQ(true, curBaseInfo.hasBase); + EXPECT_EQ(15, curBaseInfo.base.long_value); + EXPECT_EQ(true, curInterval.hasValue); + curInterval = valueProducer.mCurrentSlicedBucket.begin()->second.intervals[1]; + curBaseInfo = valueProducer.mCurrentBaseInfo.begin()->second.baseInfos[1]; + EXPECT_EQ(true, curBaseInfo.hasBase); + EXPECT_EQ(25, curBaseInfo.base.long_value); + EXPECT_EQ(true, curInterval.hasValue); + + LogEvent event4(/*uid=*/0, /*pid=*/0); + CreateThreeValueLogEvent(&event4, tagId, bucket2StartTimeNs + 15, 1, 15, 29); + + valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event4); + ASSERT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); + curInterval = valueProducer.mCurrentSlicedBucket.begin()->second.intervals[0]; + curBaseInfo = valueProducer.mCurrentBaseInfo.begin()->second.baseInfos[0]; + EXPECT_EQ(true, curBaseInfo.hasBase); + EXPECT_EQ(15, curBaseInfo.base.long_value); + EXPECT_EQ(true, curInterval.hasValue); + curInterval = valueProducer.mCurrentSlicedBucket.begin()->second.intervals[1]; + curBaseInfo = valueProducer.mCurrentBaseInfo.begin()->second.baseInfos[1]; + EXPECT_EQ(true, curBaseInfo.hasBase); + EXPECT_EQ(29, curBaseInfo.base.long_value); + EXPECT_EQ(true, curInterval.hasValue); + + valueProducer.flushIfNeededLocked(bucket3StartTimeNs); + + ASSERT_EQ(1UL, valueProducer.mPastBuckets.size()); + ASSERT_EQ(2UL, valueProducer.mPastBuckets.begin()->second.size()); + ASSERT_EQ(2UL, valueProducer.mPastBuckets.begin()->second[0].values.size()); + ASSERT_EQ(1UL, valueProducer.mPastBuckets.begin()->second[1].values.size()); + + EXPECT_EQ(bucketSizeNs, valueProducer.mPastBuckets.begin()->second[0].mConditionTrueNs); + EXPECT_EQ(5, valueProducer.mPastBuckets.begin()->second[0].values[0].long_value); + EXPECT_EQ(0, valueProducer.mPastBuckets.begin()->second[0].valueIndex[0]); + EXPECT_EQ(2, valueProducer.mPastBuckets.begin()->second[0].values[1].long_value); + EXPECT_EQ(1, valueProducer.mPastBuckets.begin()->second[0].valueIndex[1]); + + EXPECT_EQ(bucketSizeNs, valueProducer.mPastBuckets.begin()->second[1].mConditionTrueNs); + EXPECT_EQ(3, valueProducer.mPastBuckets.begin()->second[1].values[0].long_value); + EXPECT_EQ(1, valueProducer.mPastBuckets.begin()->second[1].valueIndex[0]); +} + +/* + * Tests zero default base. + */ +TEST(ValueMetricProducerTest, TestUseZeroDefaultBase) { + ValueMetric metric = ValueMetricProducerTestHelper::createMetric(); + metric.mutable_dimensions_in_what()->set_field(tagId); + metric.mutable_dimensions_in_what()->add_child()->set_field(1); + metric.set_use_zero_default_base(true); + + sp pullerManager = new StrictMock(); + EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, bucketStartTimeNs, _)) + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t, + vector>* data) { + data->clear(); + data->push_back(CreateTwoValueLogEvent(tagId, bucketStartTimeNs, 1, 3)); + return true; + })); + + sp valueProducer = + ValueMetricProducerTestHelper::createValueProducerNoConditions(pullerManager, metric); + + ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); + auto iter = valueProducer->mCurrentSlicedBucket.begin(); + auto& interval1 = iter->second.intervals[0]; + auto iterBase = valueProducer->mCurrentBaseInfo.begin(); + auto& baseInfo1 = iterBase->second.baseInfos[0]; + EXPECT_EQ(1, iter->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + EXPECT_EQ(true, baseInfo1.hasBase); + EXPECT_EQ(3, baseInfo1.base.long_value); + EXPECT_EQ(false, interval1.hasValue); + EXPECT_EQ(true, valueProducer->mHasGlobalBase); + ASSERT_EQ(0UL, valueProducer->mPastBuckets.size()); + vector> allData; + + allData.clear(); + allData.push_back(CreateTwoValueLogEvent(tagId, bucket2StartTimeNs + 1, 2, 4)); + allData.push_back(CreateTwoValueLogEvent(tagId, bucket2StartTimeNs + 1, 1, 11)); + + valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs); + ASSERT_EQ(2UL, valueProducer->mCurrentSlicedBucket.size()); + EXPECT_EQ(true, baseInfo1.hasBase); + EXPECT_EQ(11, baseInfo1.base.long_value); + EXPECT_EQ(false, interval1.hasValue); + EXPECT_EQ(8, interval1.value.long_value); + + auto it = valueProducer->mCurrentSlicedBucket.begin(); + for (; it != valueProducer->mCurrentSlicedBucket.end(); it++) { + if (it != iter) { + break; + } + } + auto itBase = valueProducer->mCurrentBaseInfo.begin(); + for (; itBase != valueProducer->mCurrentBaseInfo.end(); it++) { + if (itBase != iterBase) { + break; + } + } + EXPECT_TRUE(it != iter); + EXPECT_TRUE(itBase != iterBase); + auto& interval2 = it->second.intervals[0]; + auto& baseInfo2 = itBase->second.baseInfos[0]; + EXPECT_EQ(2, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + EXPECT_EQ(true, baseInfo2.hasBase); + EXPECT_EQ(4, baseInfo2.base.long_value); + EXPECT_EQ(false, interval2.hasValue); + EXPECT_EQ(4, interval2.value.long_value); + + ASSERT_EQ(2UL, valueProducer->mPastBuckets.size()); + auto iterator = valueProducer->mPastBuckets.begin(); + EXPECT_EQ(bucketSizeNs, iterator->second[0].mConditionTrueNs); + EXPECT_EQ(8, iterator->second[0].values[0].long_value); + iterator++; + EXPECT_EQ(bucketSizeNs, iterator->second[0].mConditionTrueNs); + EXPECT_EQ(4, iterator->second[0].values[0].long_value); +} + +/* + * Tests using zero default base with failed pull. + */ +TEST(ValueMetricProducerTest, TestUseZeroDefaultBaseWithPullFailures) { + ValueMetric metric = ValueMetricProducerTestHelper::createMetric(); + metric.mutable_dimensions_in_what()->set_field(tagId); + metric.mutable_dimensions_in_what()->add_child()->set_field(1); + metric.set_use_zero_default_base(true); + + sp pullerManager = new StrictMock(); + EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, bucketStartTimeNs, _)) + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t, + vector>* data) { + data->clear(); + data->push_back(CreateTwoValueLogEvent(tagId, bucketStartTimeNs, 1, 3)); + return true; + })); + + sp valueProducer = + ValueMetricProducerTestHelper::createValueProducerNoConditions(pullerManager, metric); + + ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); + const auto& it = valueProducer->mCurrentSlicedBucket.begin(); + ValueMetricProducer::Interval& interval1 = it->second.intervals[0]; + ValueMetricProducer::BaseInfo& baseInfo1 = + valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()) + ->second.baseInfos[0]; + EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + EXPECT_EQ(true, baseInfo1.hasBase); + EXPECT_EQ(3, baseInfo1.base.long_value); + EXPECT_EQ(false, interval1.hasValue); + EXPECT_EQ(true, valueProducer->mHasGlobalBase); + ASSERT_EQ(0UL, valueProducer->mPastBuckets.size()); + vector> allData; + + allData.clear(); + allData.push_back(CreateTwoValueLogEvent(tagId, bucket2StartTimeNs + 1, 2, 4)); + allData.push_back(CreateTwoValueLogEvent(tagId, bucket2StartTimeNs + 1, 1, 11)); + + valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs); + ASSERT_EQ(2UL, valueProducer->mCurrentSlicedBucket.size()); + EXPECT_EQ(true, baseInfo1.hasBase); + EXPECT_EQ(11, baseInfo1.base.long_value); + EXPECT_EQ(false, interval1.hasValue); + EXPECT_EQ(8, interval1.value.long_value); + + auto it2 = valueProducer->mCurrentSlicedBucket.begin(); + for (; it2 != valueProducer->mCurrentSlicedBucket.end(); it2++) { + if (it2 != it) { + break; + } + } + EXPECT_TRUE(it2 != it); + ValueMetricProducer::Interval& interval2 = it2->second.intervals[0]; + ValueMetricProducer::BaseInfo& baseInfo2 = + valueProducer->mCurrentBaseInfo.find(it2->first.getDimensionKeyInWhat()) + ->second.baseInfos[0]; + EXPECT_EQ(2, it2->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + EXPECT_EQ(true, baseInfo2.hasBase); + EXPECT_EQ(4, baseInfo2.base.long_value); + EXPECT_EQ(false, interval2.hasValue); + EXPECT_EQ(4, interval2.value.long_value); + ASSERT_EQ(2UL, valueProducer->mPastBuckets.size()); + + // next pull somehow did not happen, skip to end of bucket 3 + allData.clear(); + allData.push_back(CreateTwoValueLogEvent(tagId, bucket4StartTimeNs + 1, 2, 5)); + valueProducer->onDataPulled(allData, /** succeed */ true, bucket4StartTimeNs); + + ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); + EXPECT_EQ(true, baseInfo2.hasBase); + EXPECT_EQ(5, baseInfo2.base.long_value); + EXPECT_EQ(false, interval2.hasValue); + EXPECT_EQ(true, valueProducer->mHasGlobalBase); + ASSERT_EQ(2UL, valueProducer->mPastBuckets.size()); + + allData.clear(); + allData.push_back(CreateTwoValueLogEvent(tagId, bucket5StartTimeNs + 1, 2, 13)); + allData.push_back(CreateTwoValueLogEvent(tagId, bucket5StartTimeNs + 1, 1, 5)); + valueProducer->onDataPulled(allData, /** succeed */ true, bucket5StartTimeNs); + + ASSERT_EQ(2UL, valueProducer->mCurrentSlicedBucket.size()); + // Get new references now that entries have been deleted from the map + const auto& it3 = valueProducer->mCurrentSlicedBucket.begin(); + const auto& it4 = std::next(valueProducer->mCurrentSlicedBucket.begin()); + ASSERT_EQ(it3->second.intervals.size(), 1); + ASSERT_EQ(it4->second.intervals.size(), 1); + ValueMetricProducer::Interval& interval3 = it3->second.intervals[0]; + ValueMetricProducer::Interval& interval4 = it4->second.intervals[0]; + ValueMetricProducer::BaseInfo& baseInfo3 = + valueProducer->mCurrentBaseInfo.find(it3->first.getDimensionKeyInWhat()) + ->second.baseInfos[0]; + ValueMetricProducer::BaseInfo& baseInfo4 = + valueProducer->mCurrentBaseInfo.find(it4->first.getDimensionKeyInWhat()) + ->second.baseInfos[0]; + + EXPECT_EQ(true, baseInfo3.hasBase); + EXPECT_EQ(5, baseInfo3.base.long_value); + EXPECT_EQ(false, interval3.hasValue); + EXPECT_EQ(5, interval3.value.long_value); + EXPECT_EQ(true, valueProducer->mHasGlobalBase); + + EXPECT_EQ(true, baseInfo4.hasBase); + EXPECT_EQ(13, baseInfo4.base.long_value); + EXPECT_EQ(false, interval4.hasValue); + EXPECT_EQ(8, interval4.value.long_value); + + ASSERT_EQ(2UL, valueProducer->mPastBuckets.size()); +} + +/* + * Tests trim unused dimension key if no new data is seen in an entire bucket. + */ +TEST(ValueMetricProducerTest, TestTrimUnusedDimensionKey) { + ValueMetric metric = ValueMetricProducerTestHelper::createMetric(); + metric.mutable_dimensions_in_what()->set_field(tagId); + metric.mutable_dimensions_in_what()->add_child()->set_field(1); + + sp pullerManager = new StrictMock(); + EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, bucketStartTimeNs, _)) + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t, + vector>* data) { + data->clear(); + data->push_back(CreateTwoValueLogEvent(tagId, bucketStartTimeNs, 1, 3)); + return true; + })); + + sp valueProducer = + ValueMetricProducerTestHelper::createValueProducerNoConditions(pullerManager, metric); + + ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); + auto iter = valueProducer->mCurrentSlicedBucket.begin(); + auto& interval1 = iter->second.intervals[0]; + auto iterBase = valueProducer->mCurrentBaseInfo.begin(); + auto& baseInfo1 = iterBase->second.baseInfos[0]; + EXPECT_EQ(1, iter->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + EXPECT_EQ(true, baseInfo1.hasBase); + EXPECT_EQ(3, baseInfo1.base.long_value); + EXPECT_EQ(false, interval1.hasValue); + ASSERT_EQ(0UL, valueProducer->mPastBuckets.size()); + + vector> allData; + allData.clear(); + allData.push_back(CreateTwoValueLogEvent(tagId, bucket2StartTimeNs + 1, 2, 4)); + allData.push_back(CreateTwoValueLogEvent(tagId, bucket2StartTimeNs + 1, 1, 11)); + valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs); + + ASSERT_EQ(2UL, valueProducer->mCurrentSlicedBucket.size()); + EXPECT_EQ(true, baseInfo1.hasBase); + EXPECT_EQ(11, baseInfo1.base.long_value); + EXPECT_EQ(false, interval1.hasValue); + EXPECT_EQ(8, interval1.value.long_value); + EXPECT_FALSE(interval1.seenNewData); + assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {8}, {bucketSizeNs}, + {bucketStartTimeNs}, {bucket2StartTimeNs}); + + auto it = valueProducer->mCurrentSlicedBucket.begin(); + for (; it != valueProducer->mCurrentSlicedBucket.end(); it++) { + if (it != iter) { + break; + } + } + auto itBase = valueProducer->mCurrentBaseInfo.begin(); + for (; itBase != valueProducer->mCurrentBaseInfo.end(); it++) { + if (itBase != iterBase) { + break; + } + } + EXPECT_TRUE(it != iter); + EXPECT_TRUE(itBase != iterBase); + auto interval2 = it->second.intervals[0]; + auto baseInfo2 = itBase->second.baseInfos[0]; + EXPECT_EQ(2, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + EXPECT_EQ(true, baseInfo2.hasBase); + EXPECT_EQ(4, baseInfo2.base.long_value); + EXPECT_EQ(false, interval2.hasValue); + EXPECT_FALSE(interval2.seenNewData); + + // next pull somehow did not happen, skip to end of bucket 3 + allData.clear(); + allData.push_back(CreateTwoValueLogEvent(tagId, bucket4StartTimeNs + 1, 2, 5)); + valueProducer->onDataPulled(allData, /** succeed */ true, bucket4StartTimeNs); + // Only one interval left. One was trimmed. + ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); + interval2 = valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0]; + baseInfo2 = valueProducer->mCurrentBaseInfo.begin()->second.baseInfos[0]; + EXPECT_EQ(2, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + EXPECT_EQ(true, baseInfo2.hasBase); + EXPECT_EQ(5, baseInfo2.base.long_value); + EXPECT_EQ(false, interval2.hasValue); + EXPECT_FALSE(interval2.seenNewData); + assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {8}, {bucketSizeNs}, + {bucketStartTimeNs}, {bucket2StartTimeNs}); + + allData.clear(); + allData.push_back(CreateTwoValueLogEvent(tagId, bucket5StartTimeNs + 1, 2, 14)); + valueProducer->onDataPulled(allData, /** succeed */ true, bucket5StartTimeNs); + + interval2 = valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0]; + baseInfo2 = valueProducer->mCurrentBaseInfo.begin()->second.baseInfos[0]; + EXPECT_EQ(true, baseInfo2.hasBase); + EXPECT_EQ(14, baseInfo2.base.long_value); + EXPECT_EQ(false, interval2.hasValue); + EXPECT_FALSE(interval2.seenNewData); + ASSERT_EQ(2UL, valueProducer->mPastBuckets.size()); + auto iterator = valueProducer->mPastBuckets.begin(); + EXPECT_EQ(bucket4StartTimeNs, iterator->second[0].mBucketStartNs); + EXPECT_EQ(bucket5StartTimeNs, iterator->second[0].mBucketEndNs); + EXPECT_EQ(9, iterator->second[0].values[0].long_value); + EXPECT_EQ(bucketSizeNs, iterator->second[0].mConditionTrueNs); + iterator++; + EXPECT_EQ(bucketStartTimeNs, iterator->second[0].mBucketStartNs); + EXPECT_EQ(bucket2StartTimeNs, iterator->second[0].mBucketEndNs); + EXPECT_EQ(8, iterator->second[0].values[0].long_value); + EXPECT_EQ(bucketSizeNs, iterator->second[0].mConditionTrueNs); +} + +TEST(ValueMetricProducerTest, TestResetBaseOnPullFailAfterConditionChange_EndOfBucket) { + ValueMetric metric = ValueMetricProducerTestHelper::createMetricWithCondition(); + + sp pullerManager = new StrictMock(); + // Used by onConditionChanged. + EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, bucketStartTimeNs + 8, _)) + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t, + vector>* data) { + data->clear(); + data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 8, 100)); + return true; + })); + + sp valueProducer = + ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric, + ConditionState::kFalse); + + valueProducer->onConditionChanged(true, bucketStartTimeNs + 8); + // has one slice + ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); + ValueMetricProducer::Interval& curInterval = + valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0]; + ValueMetricProducer::BaseInfo& curBaseInfo = + valueProducer->mCurrentBaseInfo.begin()->second.baseInfos[0]; + EXPECT_EQ(true, curBaseInfo.hasBase); + EXPECT_EQ(100, curBaseInfo.base.long_value); + EXPECT_EQ(false, curInterval.hasValue); + + vector> allData; + valueProducer->onDataPulled(allData, /** succeed */ false, bucket2StartTimeNs); + ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); + EXPECT_EQ(false, curBaseInfo.hasBase); + EXPECT_EQ(false, curInterval.hasValue); + EXPECT_EQ(false, valueProducer->mHasGlobalBase); +} + +TEST(ValueMetricProducerTest, TestResetBaseOnPullFailAfterConditionChange) { + ValueMetric metric = ValueMetricProducerTestHelper::createMetricWithCondition(); + + sp pullerManager = new StrictMock(); + EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _)) + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector>* data) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 8); // Condition change to true. + data->clear(); + data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 8, 100)); + return true; + })) + .WillOnce(Return(false)); + + sp valueProducer = + ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric, + ConditionState::kFalse); + + valueProducer->onConditionChanged(true, bucketStartTimeNs + 8); + + // has one slice + ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); + ValueMetricProducer::Interval& curInterval = + valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0]; + ValueMetricProducer::BaseInfo& curBaseInfo = + valueProducer->mCurrentBaseInfo.begin()->second.baseInfos[0]; + EXPECT_EQ(true, curBaseInfo.hasBase); + EXPECT_EQ(100, curBaseInfo.base.long_value); + EXPECT_EQ(false, curInterval.hasValue); + ASSERT_EQ(0UL, valueProducer->mPastBuckets.size()); + + valueProducer->onConditionChanged(false, bucketStartTimeNs + 20); + + // has one slice + ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); + EXPECT_EQ(false, curInterval.hasValue); + EXPECT_EQ(false, curBaseInfo.hasBase); + EXPECT_EQ(false, valueProducer->mHasGlobalBase); +} + +TEST(ValueMetricProducerTest, TestResetBaseOnPullFailBeforeConditionChange) { + ValueMetric metric = ValueMetricProducerTestHelper::createMetricWithCondition(); + + sp pullerManager = new StrictMock(); + EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _)) + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector>* data) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs); + data->clear(); + data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs, 50)); + return false; + })) + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector>* data) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 1); // Condition change to false. + data->clear(); + data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 8, 100)); + return true; + })); + + sp valueProducer = + ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric, + ConditionState::kFalse); + + // Don't directly set mCondition; the real code never does that. Go through regular code path + // to avoid unexpected behaviors. + // valueProducer->mCondition = ConditionState::kTrue; + valueProducer->onConditionChanged(true, bucketStartTimeNs); + + ASSERT_EQ(0UL, valueProducer->mCurrentSlicedBucket.size()); + + valueProducer->onConditionChanged(false, bucketStartTimeNs + 1); + ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); + ValueMetricProducer::Interval& curInterval = + valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0]; + ValueMetricProducer::BaseInfo curBaseInfo = + valueProducer->mCurrentBaseInfo.begin()->second.baseInfos[0]; + EXPECT_EQ(false, curBaseInfo.hasBase); + EXPECT_EQ(false, curInterval.hasValue); + EXPECT_EQ(false, valueProducer->mHasGlobalBase); +} + +TEST(ValueMetricProducerTest, TestResetBaseOnPullDelayExceeded) { + ValueMetric metric = ValueMetricProducerTestHelper::createMetric(); + metric.set_condition(StringToId("SCREEN_ON")); + metric.set_max_pull_delay_sec(0); + + sp pullerManager = new StrictMock(); + EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, bucketStartTimeNs + 1, _)) + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t, + vector>* data) { + data->clear(); + data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 1, 120)); + return true; + })); + + sp valueProducer = + ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric, + ConditionState::kFalse); + + // Max delay is set to 0 so pull will exceed max delay. + valueProducer->onConditionChanged(true, bucketStartTimeNs + 1); + ASSERT_EQ(0UL, valueProducer->mCurrentSlicedBucket.size()); +} + +TEST(ValueMetricProducerTest, TestResetBaseOnPullTooLate) { + ValueMetric metric = ValueMetricProducerTestHelper::createMetricWithCondition(); + + sp eventMatcherWizard = + createEventMatcherWizard(tagId, logEventMatcherIndex); + sp wizard = new NaggyMock(); + sp pullerManager = new StrictMock(); + EXPECT_CALL(*pullerManager, RegisterReceiver(tagId, kConfigKey, _, _, _)).WillOnce(Return()); + EXPECT_CALL(*pullerManager, UnRegisterReceiver(tagId, kConfigKey, _)).WillRepeatedly(Return()); + + ValueMetricProducer valueProducer(kConfigKey, metric, 0, {ConditionState::kUnknown}, wizard, + protoHash, logEventMatcherIndex, eventMatcherWizard, tagId, + bucket2StartTimeNs, bucket2StartTimeNs, pullerManager); + valueProducer.prepareFirstBucket(); + valueProducer.mCondition = ConditionState::kFalse; + + // Event should be skipped since it is from previous bucket. + // Pull should not be called. + valueProducer.onConditionChanged(true, bucketStartTimeNs); + ASSERT_EQ(0UL, valueProducer.mCurrentSlicedBucket.size()); +} + +TEST(ValueMetricProducerTest, TestBaseSetOnConditionChange) { + ValueMetric metric = ValueMetricProducerTestHelper::createMetricWithCondition(); + + sp pullerManager = new StrictMock(); + EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, bucketStartTimeNs + 1, _)) + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t, + vector>* data) { + data->clear(); + data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 1, 100)); + return true; + })); + + sp valueProducer = + ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric, + ConditionState::kFalse); + valueProducer->mHasGlobalBase = false; + + valueProducer->onConditionChanged(true, bucketStartTimeNs + 1); + valueProducer->mHasGlobalBase = true; + ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); + ValueMetricProducer::Interval& curInterval = + valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0]; + ValueMetricProducer::BaseInfo curBaseInfo = + valueProducer->mCurrentBaseInfo.begin()->second.baseInfos[0]; + EXPECT_EQ(true, curBaseInfo.hasBase); + EXPECT_EQ(100, curBaseInfo.base.long_value); + EXPECT_EQ(false, curInterval.hasValue); + EXPECT_EQ(true, valueProducer->mHasGlobalBase); +} + +/* + * Tests that a bucket is marked invalid when a condition change pull fails. + */ +TEST(ValueMetricProducerTest_BucketDrop, TestInvalidBucketWhenOneConditionFailed) { + ValueMetric metric = ValueMetricProducerTestHelper::createMetricWithCondition(); + + sp pullerManager = new StrictMock(); + EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _)) + // First onConditionChanged + .WillOnce(Return(false)) + // Second onConditionChanged + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector>* data) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 3); + data->clear(); + data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 8, 130)); + return true; + })); + + sp valueProducer = + ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric, + ConditionState::kTrue); + + // Bucket start. + vector> allData; + allData.clear(); + allData.push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 1, 110)); + valueProducer->onDataPulled(allData, /** succeed */ true, bucketStartTimeNs); + + // This will fail and should invalidate the whole bucket since we do not have all the data + // needed to compute the metric value when the screen was on. + valueProducer->onConditionChanged(false, bucketStartTimeNs + 2); + valueProducer->onConditionChanged(true, bucketStartTimeNs + 3); + + // Bucket end. + allData.clear(); + allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs + 1, 140)); + valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs); + + valueProducer->flushIfNeededLocked(bucket2StartTimeNs + 1); + + ASSERT_EQ(0UL, valueProducer->mPastBuckets.size()); + // Contains base from last pull which was successful. + ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); + ValueMetricProducer::Interval& curInterval = + valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0]; + ValueMetricProducer::BaseInfo curBaseInfo = + valueProducer->mCurrentBaseInfo.begin()->second.baseInfos[0]; + EXPECT_EQ(true, curBaseInfo.hasBase); + EXPECT_EQ(140, curBaseInfo.base.long_value); + EXPECT_EQ(false, curInterval.hasValue); + EXPECT_EQ(true, valueProducer->mHasGlobalBase); + + // Check dump report. + ProtoOutputStream output; + std::set strSet; + valueProducer->onDumpReport(bucket2StartTimeNs + 10, false /* include partial bucket */, true, + FAST /* dumpLatency */, &strSet, &output); + + StatsLogReport report = outputStreamToProto(&output); + EXPECT_TRUE(report.has_value_metrics()); + ASSERT_EQ(0, report.value_metrics().data_size()); + ASSERT_EQ(1, report.value_metrics().skipped_size()); + + EXPECT_EQ(NanoToMillis(bucketStartTimeNs), + report.value_metrics().skipped(0).start_bucket_elapsed_millis()); + EXPECT_EQ(NanoToMillis(bucket2StartTimeNs), + report.value_metrics().skipped(0).end_bucket_elapsed_millis()); + ASSERT_EQ(1, report.value_metrics().skipped(0).drop_event_size()); + + auto dropEvent = report.value_metrics().skipped(0).drop_event(0); + EXPECT_EQ(BucketDropReason::PULL_FAILED, dropEvent.drop_reason()); + EXPECT_EQ(NanoToMillis(bucketStartTimeNs + 2), dropEvent.drop_time_millis()); +} + +/* + * Tests that a bucket is marked invalid when the guardrail is hit. + */ +TEST(ValueMetricProducerTest_BucketDrop, TestInvalidBucketWhenGuardRailHit) { + ValueMetric metric = ValueMetricProducerTestHelper::createMetric(); + metric.mutable_dimensions_in_what()->set_field(tagId); + metric.mutable_dimensions_in_what()->add_child()->set_field(1); + metric.set_condition(StringToId("SCREEN_ON")); + + sp pullerManager = new StrictMock(); + EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, bucketStartTimeNs + 2, _)) + // First onConditionChanged + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t, + vector>* data) { + for (int i = 0; i < 2000; i++) { + data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 1, i)); + } + return true; + })); + + sp valueProducer = + ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric, + ConditionState::kFalse); + + valueProducer->onConditionChanged(true, bucketStartTimeNs + 2); + EXPECT_EQ(true, valueProducer->mCurrentBucketIsSkipped); + ASSERT_EQ(0UL, valueProducer->mCurrentSlicedBucket.size()); + ASSERT_EQ(0UL, valueProducer->mSkippedBuckets.size()); + + // Bucket 2 start. + vector> allData; + allData.clear(); + allData.push_back(CreateTwoValueLogEvent(tagId, bucket2StartTimeNs + 1, 1, 10)); + valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs); + + // First bucket added to mSkippedBuckets after flush. + ASSERT_EQ(1UL, valueProducer->mSkippedBuckets.size()); + + // Check dump report. + ProtoOutputStream output; + std::set strSet; + valueProducer->onDumpReport(bucket2StartTimeNs + 10000, false /* include recent buckets */, + true, FAST /* dumpLatency */, &strSet, &output); + + StatsLogReport report = outputStreamToProto(&output); + EXPECT_TRUE(report.has_value_metrics()); + ASSERT_EQ(0, report.value_metrics().data_size()); + ASSERT_EQ(1, report.value_metrics().skipped_size()); + + EXPECT_EQ(NanoToMillis(bucketStartTimeNs), + report.value_metrics().skipped(0).start_bucket_elapsed_millis()); + EXPECT_EQ(NanoToMillis(bucket2StartTimeNs), + report.value_metrics().skipped(0).end_bucket_elapsed_millis()); + ASSERT_EQ(1, report.value_metrics().skipped(0).drop_event_size()); + + auto dropEvent = report.value_metrics().skipped(0).drop_event(0); + EXPECT_EQ(BucketDropReason::DIMENSION_GUARDRAIL_REACHED, dropEvent.drop_reason()); + EXPECT_EQ(NanoToMillis(bucketStartTimeNs + 2), dropEvent.drop_time_millis()); +} + +/* + * Tests that a bucket is marked invalid when the bucket's initial pull fails. + */ +TEST(ValueMetricProducerTest_BucketDrop, TestInvalidBucketWhenInitialPullFailed) { + ValueMetric metric = ValueMetricProducerTestHelper::createMetricWithCondition(); + + sp pullerManager = new StrictMock(); + EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _)) + // First onConditionChanged + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector>* data) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 2); + data->clear(); + data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 8, 120)); + return true; + })) + // Second onConditionChanged + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector>* data) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 3); + data->clear(); + data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 8, 130)); + return true; + })); + + sp valueProducer = + ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric, + ConditionState::kTrue); + + // Bucket start. + vector> allData; + allData.clear(); + allData.push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 1, 110)); + valueProducer->onDataPulled(allData, /** succeed */ false, bucketStartTimeNs); + + valueProducer->onConditionChanged(false, bucketStartTimeNs + 2); + valueProducer->onConditionChanged(true, bucketStartTimeNs + 3); + + // Bucket end. + allData.clear(); + allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs + 1, 140)); + valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs); + + valueProducer->flushIfNeededLocked(bucket2StartTimeNs + 1); + + ASSERT_EQ(0UL, valueProducer->mPastBuckets.size()); + // Contains base from last pull which was successful. + ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); + ValueMetricProducer::Interval& curInterval = + valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0]; + ValueMetricProducer::BaseInfo curBaseInfo = + valueProducer->mCurrentBaseInfo.begin()->second.baseInfos[0]; + EXPECT_EQ(true, curBaseInfo.hasBase); + EXPECT_EQ(140, curBaseInfo.base.long_value); + EXPECT_EQ(false, curInterval.hasValue); + EXPECT_EQ(true, valueProducer->mHasGlobalBase); + + // Check dump report. + ProtoOutputStream output; + std::set strSet; + valueProducer->onDumpReport(bucket2StartTimeNs + 10000, false /* include recent buckets */, + true, FAST /* dumpLatency */, &strSet, &output); + + StatsLogReport report = outputStreamToProto(&output); + EXPECT_TRUE(report.has_value_metrics()); + ASSERT_EQ(0, report.value_metrics().data_size()); + ASSERT_EQ(1, report.value_metrics().skipped_size()); + + EXPECT_EQ(NanoToMillis(bucketStartTimeNs), + report.value_metrics().skipped(0).start_bucket_elapsed_millis()); + EXPECT_EQ(NanoToMillis(bucket2StartTimeNs), + report.value_metrics().skipped(0).end_bucket_elapsed_millis()); + ASSERT_EQ(1, report.value_metrics().skipped(0).drop_event_size()); + + auto dropEvent = report.value_metrics().skipped(0).drop_event(0); + EXPECT_EQ(BucketDropReason::PULL_FAILED, dropEvent.drop_reason()); + EXPECT_EQ(NanoToMillis(bucketStartTimeNs + 2), dropEvent.drop_time_millis()); +} + +/* + * Tests that a bucket is marked invalid when the bucket's final pull fails + * (i.e. failed pull on bucket boundary). + */ +TEST(ValueMetricProducerTest_BucketDrop, TestInvalidBucketWhenLastPullFailed) { + ValueMetric metric = ValueMetricProducerTestHelper::createMetricWithCondition(); + + sp pullerManager = new StrictMock(); + EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _)) + // First onConditionChanged + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector>* data) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 2); + data->clear(); + data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 8, 120)); + return true; + })) + // Second onConditionChanged + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector>* data) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 3); + data->clear(); + data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 8, 130)); + return true; + })); + + sp valueProducer = + ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric, + ConditionState::kTrue); + + // Bucket start. + vector> allData; + allData.clear(); + allData.push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 1, 110)); + valueProducer->onDataPulled(allData, /** succeed */ true, bucketStartTimeNs); + + valueProducer->onConditionChanged(false, bucketStartTimeNs + 2); + valueProducer->onConditionChanged(true, bucketStartTimeNs + 3); + + // Bucket end. + allData.clear(); + allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs + 1, 140)); + valueProducer->onDataPulled(allData, /** succeed */ false, bucket2StartTimeNs); + + valueProducer->flushIfNeededLocked(bucket2StartTimeNs + 1); + + ASSERT_EQ(0UL, valueProducer->mPastBuckets.size()); + // Last pull failed so base has been reset. + ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); + ValueMetricProducer::Interval& curInterval = + valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0]; + ValueMetricProducer::BaseInfo curBaseInfo = + valueProducer->mCurrentBaseInfo.begin()->second.baseInfos[0]; + EXPECT_EQ(false, curBaseInfo.hasBase); + EXPECT_EQ(false, curInterval.hasValue); + EXPECT_EQ(false, valueProducer->mHasGlobalBase); + + // Check dump report. + ProtoOutputStream output; + std::set strSet; + valueProducer->onDumpReport(bucket2StartTimeNs + 10000, false /* include recent buckets */, + true, FAST /* dumpLatency */, &strSet, &output); + + StatsLogReport report = outputStreamToProto(&output); + EXPECT_TRUE(report.has_value_metrics()); + ASSERT_EQ(0, report.value_metrics().data_size()); + ASSERT_EQ(1, report.value_metrics().skipped_size()); + + EXPECT_EQ(NanoToMillis(bucketStartTimeNs), + report.value_metrics().skipped(0).start_bucket_elapsed_millis()); + EXPECT_EQ(NanoToMillis(bucket2StartTimeNs), + report.value_metrics().skipped(0).end_bucket_elapsed_millis()); + ASSERT_EQ(1, report.value_metrics().skipped(0).drop_event_size()); + + auto dropEvent = report.value_metrics().skipped(0).drop_event(0); + EXPECT_EQ(BucketDropReason::PULL_FAILED, dropEvent.drop_reason()); + EXPECT_EQ(NanoToMillis(bucket2StartTimeNs), dropEvent.drop_time_millis()); +} + +TEST(ValueMetricProducerTest, TestEmptyDataResetsBase_onDataPulled) { + ValueMetric metric = ValueMetricProducerTestHelper::createMetric(); + sp pullerManager = new StrictMock(); + EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, bucketStartTimeNs, _)) + // Start bucket. + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t, + vector>* data) { + data->clear(); + data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs, 3)); + return true; + })); + + sp valueProducer = + ValueMetricProducerTestHelper::createValueProducerNoConditions(pullerManager, metric); + + // Bucket 2 start. + vector> allData; + allData.clear(); + allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs + 1, 110)); + valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs); + ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); + ASSERT_EQ(1UL, valueProducer->mPastBuckets.size()); + + // Bucket 3 empty. + allData.clear(); + allData.push_back(CreateNoValuesLogEvent(tagId, bucket3StartTimeNs + 1)); + valueProducer->onDataPulled(allData, /** succeed */ true, bucket3StartTimeNs); + // Data has been trimmed. + ASSERT_EQ(0UL, valueProducer->mCurrentSlicedBucket.size()); + ASSERT_EQ(1UL, valueProducer->mPastBuckets.size()); +} + +TEST(ValueMetricProducerTest, TestEmptyDataResetsBase_onConditionChanged) { + ValueMetric metric = ValueMetricProducerTestHelper::createMetricWithCondition(); + + sp pullerManager = new StrictMock(); + EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _)) + // First onConditionChanged + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector>* data) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 10); + data->clear(); + data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs, 3)); + return true; + })) + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector>* data) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 10); + data->clear(); + return true; + })); + + sp valueProducer = + ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric, + ConditionState::kFalse); + + valueProducer->onConditionChanged(true, bucketStartTimeNs + 10); + ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); + ValueMetricProducer::Interval& curInterval = + valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0]; + ValueMetricProducer::BaseInfo curBaseInfo = + valueProducer->mCurrentBaseInfo.begin()->second.baseInfos[0]; + EXPECT_EQ(true, curBaseInfo.hasBase); + EXPECT_EQ(false, curInterval.hasValue); + EXPECT_EQ(true, valueProducer->mHasGlobalBase); + + // Empty pull. + valueProducer->onConditionChanged(false, bucketStartTimeNs + 10); + ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); + curInterval = valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0]; + curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second.baseInfos[0]; + EXPECT_EQ(false, curBaseInfo.hasBase); + EXPECT_EQ(false, curInterval.hasValue); + EXPECT_EQ(false, valueProducer->mHasGlobalBase); +} + +TEST(ValueMetricProducerTest, TestEmptyDataResetsBase_onBucketBoundary) { + ValueMetric metric = ValueMetricProducerTestHelper::createMetricWithCondition(); + + sp pullerManager = new StrictMock(); + EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _)) + // First onConditionChanged + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector>* data) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 10); + data->clear(); + data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs, 1)); + return true; + })) + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector>* data) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 11); + data->clear(); + data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs, 2)); + return true; + })) + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector>* data) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 12); + data->clear(); + data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs, 5)); + return true; + })); + + sp valueProducer = + ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric, + ConditionState::kFalse); + + valueProducer->onConditionChanged(true, bucketStartTimeNs + 10); + valueProducer->onConditionChanged(false, bucketStartTimeNs + 11); + valueProducer->onConditionChanged(true, bucketStartTimeNs + 12); + ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); + ValueMetricProducer::Interval& curInterval = + valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0]; + ValueMetricProducer::BaseInfo curBaseInfo = + valueProducer->mCurrentBaseInfo.begin()->second.baseInfos[0]; + EXPECT_EQ(true, curBaseInfo.hasBase); + EXPECT_EQ(true, curInterval.hasValue); + EXPECT_EQ(true, valueProducer->mHasGlobalBase); + + // End of bucket + vector> allData; + allData.clear(); + valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs); + ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); + curInterval = valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0]; + curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second.baseInfos[0]; + // Data is empty, base should be reset. + EXPECT_EQ(false, curBaseInfo.hasBase); + EXPECT_EQ(5, curBaseInfo.base.long_value); + EXPECT_EQ(false, curInterval.hasValue); + EXPECT_EQ(true, valueProducer->mHasGlobalBase); + + ASSERT_EQ(1UL, valueProducer->mPastBuckets.size()); + assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {1}, {bucketSizeNs - 12 + 1}, + {bucketStartTimeNs}, {bucket2StartTimeNs}); +} + +TEST(ValueMetricProducerTest, TestPartialResetOnBucketBoundaries) { + ValueMetric metric = ValueMetricProducerTestHelper::createMetric(); + metric.mutable_dimensions_in_what()->set_field(tagId); + metric.mutable_dimensions_in_what()->add_child()->set_field(1); + metric.set_condition(StringToId("SCREEN_ON")); + + sp pullerManager = new StrictMock(); + EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, bucketStartTimeNs + 10, _)) + // First onConditionChanged + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t, + vector>* data) { + data->clear(); + data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs, 1)); + return true; + })); + + sp valueProducer = + ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric, + ConditionState::kFalse); + + valueProducer->onConditionChanged(true, bucketStartTimeNs + 10); + ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); + + // End of bucket + vector> allData; + allData.clear(); + allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs + 1, 2)); + valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs); + + // Key 1 should be reset since in not present in the most pull. + ASSERT_EQ(2UL, valueProducer->mCurrentSlicedBucket.size()); + auto iterator = valueProducer->mCurrentSlicedBucket.begin(); + auto baseInfoIter = valueProducer->mCurrentBaseInfo.begin(); + EXPECT_EQ(true, baseInfoIter->second.baseInfos[0].hasBase); + EXPECT_EQ(2, baseInfoIter->second.baseInfos[0].base.long_value); + EXPECT_EQ(false, iterator->second.intervals[0].hasValue); + iterator++; + baseInfoIter++; + EXPECT_EQ(false, baseInfoIter->second.baseInfos[0].hasBase); + EXPECT_EQ(1, baseInfoIter->second.baseInfos[0].base.long_value); + EXPECT_EQ(false, iterator->second.intervals[0].hasValue); + + EXPECT_EQ(true, valueProducer->mHasGlobalBase); +} + +TEST_P(ValueMetricProducerTest_PartialBucket, TestFullBucketResetWhenLastBucketInvalid) { + ValueMetric metric = ValueMetricProducerTestHelper::createMetric(); + + sp pullerManager = new StrictMock(); + int64_t partialBucketSplitTimeNs = bucketStartTimeNs + bucketSizeNs / 2; + EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _)) + // Initialization. + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector>* data) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs); + data->clear(); + data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs, 1)); + return true; + })) + // notifyAppUpgrade. + .WillOnce(Invoke([partialBucketSplitTimeNs](int tagId, const ConfigKey&, + const int64_t eventTimeNs, + vector>* data) { + EXPECT_EQ(eventTimeNs, partialBucketSplitTimeNs); + data->clear(); + data->push_back(CreateRepeatedValueLogEvent(tagId, partialBucketSplitTimeNs, 10)); + return true; + })); + sp valueProducer = + ValueMetricProducerTestHelper::createValueProducerNoConditions(pullerManager, metric); + ASSERT_EQ(0UL, valueProducer->mCurrentFullBucket.size()); + + switch (GetParam()) { + case APP_UPGRADE: + valueProducer->notifyAppUpgrade(partialBucketSplitTimeNs); + break; + case BOOT_COMPLETE: + valueProducer->onStatsdInitCompleted(partialBucketSplitTimeNs); + break; + } + EXPECT_EQ(partialBucketSplitTimeNs, valueProducer->mCurrentBucketStartTimeNs); + EXPECT_EQ(0, valueProducer->getCurrentBucketNum()); + assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {9}, + {partialBucketSplitTimeNs - bucketStartTimeNs}, + {bucketStartTimeNs}, {partialBucketSplitTimeNs}); + ASSERT_EQ(1UL, valueProducer->mCurrentFullBucket.size()); + + vector> allData; + allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket3StartTimeNs + 1, 4)); + // Pull fails and arrives late. + valueProducer->onDataPulled(allData, /** fails */ false, bucket3StartTimeNs + 1); + assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {9}, + {partialBucketSplitTimeNs - bucketStartTimeNs}, + {bucketStartTimeNs}, {partialBucketSplitTimeNs}); + ASSERT_EQ(1, valueProducer->mSkippedBuckets.size()); + ASSERT_EQ(2, valueProducer->mSkippedBuckets[0].dropEvents.size()); + EXPECT_EQ(PULL_FAILED, valueProducer->mSkippedBuckets[0].dropEvents[0].reason); + EXPECT_EQ(MULTIPLE_BUCKETS_SKIPPED, valueProducer->mSkippedBuckets[0].dropEvents[1].reason); + EXPECT_EQ(partialBucketSplitTimeNs, valueProducer->mSkippedBuckets[0].bucketStartTimeNs); + EXPECT_EQ(bucket3StartTimeNs, valueProducer->mSkippedBuckets[0].bucketEndTimeNs); + ASSERT_EQ(0UL, valueProducer->mCurrentFullBucket.size()); +} + +TEST(ValueMetricProducerTest, TestBucketBoundariesOnConditionChange) { + ValueMetric metric = ValueMetricProducerTestHelper::createMetricWithCondition(); + sp pullerManager = new StrictMock(); + EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _)) + // Second onConditionChanged. + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector>* data) { + EXPECT_EQ(eventTimeNs, bucket2StartTimeNs + 10); + data->clear(); + data->push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs + 10, 5)); + return true; + })) + // Third onConditionChanged. + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector>* data) { + EXPECT_EQ(eventTimeNs, bucket3StartTimeNs + 10); + data->clear(); + data->push_back(CreateRepeatedValueLogEvent(tagId, bucket3StartTimeNs + 10, 7)); + return true; + })); + + sp valueProducer = + ValueMetricProducerTestHelper::createValueProducerWithCondition( + pullerManager, metric, ConditionState::kUnknown); + + valueProducer->onConditionChanged(false, bucketStartTimeNs); + ASSERT_EQ(0UL, valueProducer->mCurrentSlicedBucket.size()); + + // End of first bucket + vector> allData; + allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs + 1, 4)); + valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs + 1); + ASSERT_EQ(0UL, valueProducer->mCurrentSlicedBucket.size()); + + valueProducer->onConditionChanged(true, bucket2StartTimeNs + 10); + ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); + auto curInterval = valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0]; + auto curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second.baseInfos[0]; + EXPECT_EQ(true, curBaseInfo.hasBase); + EXPECT_EQ(5, curBaseInfo.base.long_value); + EXPECT_EQ(false, curInterval.hasValue); + + valueProducer->onConditionChanged(false, bucket3StartTimeNs + 10); + + // Bucket should have been completed. + assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {2}, {bucketSizeNs - 10}, + {bucket2StartTimeNs}, {bucket3StartTimeNs}); +} + +TEST(ValueMetricProducerTest, TestLateOnDataPulledWithoutDiff) { + ValueMetric metric = ValueMetricProducerTestHelper::createMetric(); + metric.set_use_diff(false); + + sp pullerManager = new StrictMock(); + sp valueProducer = + ValueMetricProducerTestHelper::createValueProducerNoConditions(pullerManager, metric); + + vector> allData; + allData.push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 30, 10)); + valueProducer->onDataPulled(allData, /** succeed */ true, bucketStartTimeNs + 30); + + allData.clear(); + allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs, 20)); + valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs); + + // Bucket should have been completed. + assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {30}, {bucketSizeNs}, + {bucketStartTimeNs}, {bucket2StartTimeNs}); +} + +TEST(ValueMetricProducerTest, TestLateOnDataPulledWithDiff) { + ValueMetric metric = ValueMetricProducerTestHelper::createMetric(); + + sp pullerManager = new StrictMock(); + EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, bucketStartTimeNs, _)) + // Initialization. + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t, + vector>* data) { + data->clear(); + data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs, 1)); + return true; + })); + + sp valueProducer = + ValueMetricProducerTestHelper::createValueProducerNoConditions(pullerManager, metric); + + vector> allData; + allData.push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 30, 10)); + valueProducer->onDataPulled(allData, /** succeed */ true, bucketStartTimeNs + 30); + + allData.clear(); + allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs, 20)); + valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs); + + // Bucket should have been completed. + assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {19}, {bucketSizeNs}, + {bucketStartTimeNs}, {bucket2StartTimeNs}); +} + +TEST_P(ValueMetricProducerTest_PartialBucket, TestBucketBoundariesOnPartialBucket) { + ValueMetric metric = ValueMetricProducerTestHelper::createMetric(); + + int64_t partialBucketSplitTimeNs = bucket2StartTimeNs + 2; + sp pullerManager = new StrictMock(); + EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _)) + // Initialization. + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector>* data) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs); + data->clear(); + data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs, 1)); + return true; + })) + // notifyAppUpgrade. + .WillOnce(Invoke([partialBucketSplitTimeNs](int tagId, const ConfigKey&, + const int64_t eventTimeNs, + vector>* data) { + EXPECT_EQ(eventTimeNs, partialBucketSplitTimeNs); + data->clear(); + data->push_back(CreateRepeatedValueLogEvent(tagId, partialBucketSplitTimeNs, 10)); + return true; + })); + + sp valueProducer = + ValueMetricProducerTestHelper::createValueProducerNoConditions(pullerManager, metric); + + switch (GetParam()) { + case APP_UPGRADE: + valueProducer->notifyAppUpgrade(partialBucketSplitTimeNs); + break; + case BOOT_COMPLETE: + valueProducer->onStatsdInitCompleted(partialBucketSplitTimeNs); + break; + } + + // Bucket should have been completed. + assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {9}, {bucketSizeNs}, + {bucketStartTimeNs}, {bucket2StartTimeNs}); +} + +TEST(ValueMetricProducerTest, TestDataIsNotUpdatedWhenNoConditionChanged) { + ValueMetric metric = ValueMetricProducerTestHelper::createMetricWithCondition(); + + sp pullerManager = new StrictMock(); + EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _)) + // First on condition changed. + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector>* data) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 8); + data->clear(); + data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs, 1)); + return true; + })) + // Second on condition changed. + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector>* data) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 10); + data->clear(); + data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs, 3)); + return true; + })); + + sp valueProducer = + ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric, + ConditionState::kFalse); + + valueProducer->onConditionChanged(true, bucketStartTimeNs + 8); + valueProducer->onConditionChanged(false, bucketStartTimeNs + 10); + valueProducer->onConditionChanged(false, bucketStartTimeNs + 12); + + ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); + auto curInterval = valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0]; + auto curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second.baseInfos[0]; + EXPECT_EQ(true, curInterval.hasValue); + EXPECT_EQ(2, curInterval.value.long_value); + + vector> allData; + allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs + 1, 10)); + valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs + 1); + + assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {2}, {2}, {bucketStartTimeNs}, + {bucket2StartTimeNs}); +} + +// TODO: b/145705635 fix or delete this test +TEST(ValueMetricProducerTest, TestBucketInvalidIfGlobalBaseIsNotSet) { + ValueMetric metric = ValueMetricProducerTestHelper::createMetricWithCondition(); + + sp pullerManager = new StrictMock(); + EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _)) + // First condition change. + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector>* data) { + EXPECT_EQ(eventTimeNs, bucket2StartTimeNs + 10); + data->clear(); + data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs, 1)); + return true; + })) + // 2nd condition change. + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector>* data) { + EXPECT_EQ(eventTimeNs, bucket2StartTimeNs + 8); + data->clear(); + data->push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs, 1)); + return true; + })) + // 3rd condition change. + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector>* data) { + EXPECT_EQ(eventTimeNs, bucket2StartTimeNs + 10); + data->clear(); + data->push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs, 1)); + return true; + })); + + sp valueProducer = + ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric, + ConditionState::kFalse); + valueProducer->onConditionChanged(true, bucket2StartTimeNs + 10); + + vector> allData; + allData.push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 3, 10)); + valueProducer->onDataPulled(allData, /** succeed */ false, bucketStartTimeNs + 3); + + allData.clear(); + allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs, 20)); + valueProducer->onDataPulled(allData, /** succeed */ false, bucket2StartTimeNs); + + valueProducer->onConditionChanged(false, bucket2StartTimeNs + 8); + valueProducer->onConditionChanged(true, bucket2StartTimeNs + 10); + + allData.clear(); + allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket3StartTimeNs, 30)); + valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs); + + // There was not global base available so all buckets are invalid. + assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {}, {}, {}, {}); +} + +TEST(ValueMetricProducerTest, TestPullNeededFastDump) { + ValueMetric metric = ValueMetricProducerTestHelper::createMetric(); + + sp eventMatcherWizard = + createEventMatcherWizard(tagId, logEventMatcherIndex); + sp wizard = new NaggyMock(); + sp pullerManager = new StrictMock(); + EXPECT_CALL(*pullerManager, RegisterReceiver(tagId, kConfigKey, _, _, _)).WillOnce(Return()); + EXPECT_CALL(*pullerManager, UnRegisterReceiver(tagId, kConfigKey, _)).WillRepeatedly(Return()); + + EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, bucketStartTimeNs, _)) + // Initial pull. + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t, + vector>* data) { + data->clear(); + data->push_back(CreateThreeValueLogEvent(tagId, bucketStartTimeNs, tagId, 1, 1)); + return true; + })); + + ValueMetricProducer valueProducer(kConfigKey, metric, -1, {}, wizard, protoHash, + logEventMatcherIndex, eventMatcherWizard, tagId, + bucketStartTimeNs, bucketStartTimeNs, pullerManager); + valueProducer.prepareFirstBucket(); + + ProtoOutputStream output; + std::set strSet; + valueProducer.onDumpReport(bucketStartTimeNs + 10, true /* include recent buckets */, true, + FAST, &strSet, &output); + + StatsLogReport report = outputStreamToProto(&output); + // Bucket is invalid since we did not pull when dump report was called. + ASSERT_EQ(0, report.value_metrics().data_size()); +} + +TEST(ValueMetricProducerTest, TestFastDumpWithoutCurrentBucket) { + ValueMetric metric = ValueMetricProducerTestHelper::createMetric(); + + sp eventMatcherWizard = + createEventMatcherWizard(tagId, logEventMatcherIndex); + sp wizard = new NaggyMock(); + sp pullerManager = new StrictMock(); + EXPECT_CALL(*pullerManager, RegisterReceiver(tagId, kConfigKey, _, _, _)).WillOnce(Return()); + EXPECT_CALL(*pullerManager, UnRegisterReceiver(tagId, kConfigKey, _)).WillRepeatedly(Return()); + + EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, bucketStartTimeNs, _)) + // Initial pull. + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t, + vector>* data) { + data->clear(); + data->push_back(CreateThreeValueLogEvent(tagId, bucketStartTimeNs, tagId, 1, 1)); + return true; + })); + + ValueMetricProducer valueProducer(kConfigKey, metric, -1, {}, wizard, protoHash, + logEventMatcherIndex, eventMatcherWizard, tagId, + bucketStartTimeNs, bucketStartTimeNs, pullerManager); + valueProducer.prepareFirstBucket(); + + vector> allData; + allData.clear(); + allData.push_back(CreateThreeValueLogEvent(tagId, bucket2StartTimeNs + 1, tagId, 2, 2)); + valueProducer.onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs); + + ProtoOutputStream output; + std::set strSet; + valueProducer.onDumpReport(bucket4StartTimeNs, false /* include recent buckets */, true, FAST, + &strSet, &output); + + StatsLogReport report = outputStreamToProto(&output); + // Previous bucket is part of the report. + ASSERT_EQ(1, report.value_metrics().data_size()); + EXPECT_EQ(0, report.value_metrics().data(0).bucket_info(0).bucket_num()); +} + +TEST(ValueMetricProducerTest, TestPullNeededNoTimeConstraints) { + ValueMetric metric = ValueMetricProducerTestHelper::createMetric(); + + sp eventMatcherWizard = + createEventMatcherWizard(tagId, logEventMatcherIndex); + sp wizard = new NaggyMock(); + sp pullerManager = new StrictMock(); + EXPECT_CALL(*pullerManager, RegisterReceiver(tagId, kConfigKey, _, _, _)).WillOnce(Return()); + EXPECT_CALL(*pullerManager, UnRegisterReceiver(tagId, kConfigKey, _)).WillRepeatedly(Return()); + + EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _)) + // Initial pull. + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector>* data) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs); + data->clear(); + data->push_back(CreateThreeValueLogEvent(tagId, bucketStartTimeNs, tagId, 1, 1)); + return true; + })) + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector>* data) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 10); + data->clear(); + data->push_back( + CreateThreeValueLogEvent(tagId, bucketStartTimeNs + 10, tagId, 3, 3)); + return true; + })); + + ValueMetricProducer valueProducer(kConfigKey, metric, -1, {}, wizard, protoHash, + logEventMatcherIndex, eventMatcherWizard, tagId, + bucketStartTimeNs, bucketStartTimeNs, pullerManager); + valueProducer.prepareFirstBucket(); + + ProtoOutputStream output; + std::set strSet; + valueProducer.onDumpReport(bucketStartTimeNs + 10, true /* include recent buckets */, true, + NO_TIME_CONSTRAINTS, &strSet, &output); + + StatsLogReport report = outputStreamToProto(&output); + ASSERT_EQ(1, report.value_metrics().data_size()); + ASSERT_EQ(1, report.value_metrics().data(0).bucket_info_size()); + EXPECT_EQ(2, report.value_metrics().data(0).bucket_info(0).values(0).value_long()); +} + +TEST(ValueMetricProducerTest, TestPulledData_noDiff_withoutCondition) { + ValueMetric metric = ValueMetricProducerTestHelper::createMetric(); + metric.set_use_diff(false); + + sp pullerManager = new StrictMock(); + sp valueProducer = + ValueMetricProducerTestHelper::createValueProducerNoConditions(pullerManager, metric); + + vector> allData; + allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs + 30, 10)); + valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs + 30); + + // Bucket should have been completed. + assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {10}, {bucketSizeNs}, + {bucketStartTimeNs}, {bucket2StartTimeNs}); +} + +TEST(ValueMetricProducerTest, TestPulledData_noDiff_withMultipleConditionChanges) { + ValueMetric metric = ValueMetricProducerTestHelper::createMetricWithCondition(); + metric.set_use_diff(false); + + sp pullerManager = new StrictMock(); + EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _)) + // condition becomes true + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector>* data) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 8); + data->clear(); + data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 30, 10)); + return true; + })) + // condition becomes false + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector>* data) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 50); + data->clear(); + data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 50, 20)); + return true; + })); + sp valueProducer = + ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric, + ConditionState::kFalse); + + valueProducer->onConditionChanged(true, bucketStartTimeNs + 8); + valueProducer->onConditionChanged(false, bucketStartTimeNs + 50); + // has one slice + ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); + ValueMetricProducer::Interval curInterval = + valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0]; + ValueMetricProducer::BaseInfo curBaseInfo = + valueProducer->mCurrentBaseInfo.begin()->second.baseInfos[0]; + EXPECT_EQ(false, curBaseInfo.hasBase); + EXPECT_EQ(true, curInterval.hasValue); + EXPECT_EQ(20, curInterval.value.long_value); + + // Now the alarm is delivered. Condition is off though. + vector> allData; + allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs + 30, 110)); + valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs); + + assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {20}, {50 - 8}, + {bucketStartTimeNs}, {bucket2StartTimeNs}); + curInterval = valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0]; + curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second.baseInfos[0]; + EXPECT_EQ(false, curBaseInfo.hasBase); + EXPECT_EQ(false, curInterval.hasValue); +} + +TEST(ValueMetricProducerTest, TestPulledData_noDiff_bucketBoundaryTrue) { + ValueMetric metric = ValueMetricProducerTestHelper::createMetricWithCondition(); + metric.set_use_diff(false); + + sp pullerManager = new StrictMock(); + EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, bucketStartTimeNs + 8, _)) + // condition becomes true + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t, + vector>* data) { + data->clear(); + data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 30, 10)); + return true; + })); + sp valueProducer = + ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric, + ConditionState::kFalse); + + valueProducer->onConditionChanged(true, bucketStartTimeNs + 8); + + // Now the alarm is delivered. Condition is off though. + vector> allData; + allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs + 30, 30)); + valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs); + + assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {30}, {bucketSizeNs - 8}, + {bucketStartTimeNs}, {bucket2StartTimeNs}); + ValueMetricProducer::Interval curInterval = + valueProducer->mCurrentSlicedBucket.begin()->second.intervals[0]; + ValueMetricProducer::BaseInfo curBaseInfo = + valueProducer->mCurrentBaseInfo.begin()->second.baseInfos[0]; + EXPECT_EQ(false, curBaseInfo.hasBase); + EXPECT_EQ(false, curInterval.hasValue); +} + +TEST(ValueMetricProducerTest, TestPulledData_noDiff_bucketBoundaryFalse) { + ValueMetric metric = ValueMetricProducerTestHelper::createMetricWithCondition(); + metric.set_use_diff(false); + + sp pullerManager = new StrictMock(); + sp valueProducer = + ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric, + ConditionState::kFalse); + + // Now the alarm is delivered. Condition is off though. + vector> allData; + allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs + 30, 30)); + valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs); + + // Condition was always false. + assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {}, {}, {}, {}); +} + +TEST(ValueMetricProducerTest, TestPulledData_noDiff_withFailure) { + ValueMetric metric = ValueMetricProducerTestHelper::createMetricWithCondition(); + metric.set_use_diff(false); + + sp pullerManager = new StrictMock(); + EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _)) + // condition becomes true + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector>* data) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 8); + data->clear(); + data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 30, 10)); + return true; + })) + .WillOnce(Return(false)); + sp valueProducer = + ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric, + ConditionState::kFalse); + + valueProducer->onConditionChanged(true, bucketStartTimeNs + 8); + valueProducer->onConditionChanged(false, bucketStartTimeNs + 50); + + // Now the alarm is delivered. Condition is off though. + vector> allData; + allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs + 30, 30)); + valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs); + + // No buckets, we had a failure. + assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {}, {}, {}, {}); +} + +/* + * Test that DUMP_REPORT_REQUESTED dump reason is logged. + * + * For the bucket to be marked invalid during a dump report requested, + * three things must be true: + * - we want to include the current partial bucket + * - we need a pull (metric is pulled and condition is true) + * - the dump latency must be FAST + */ + +TEST(ValueMetricProducerTest_BucketDrop, TestInvalidBucketWhenDumpReportRequested) { + ValueMetric metric = ValueMetricProducerTestHelper::createMetricWithCondition(); + + sp pullerManager = new StrictMock(); + EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, bucketStartTimeNs + 20, _)) + // Condition change to true. + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t, + vector>* data) { + data->clear(); + data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 20, 10)); + return true; + })); + + sp valueProducer = + ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric, + ConditionState::kFalse); + + // Condition change event. + valueProducer->onConditionChanged(true, bucketStartTimeNs + 20); + + // Check dump report. + ProtoOutputStream output; + std::set strSet; + valueProducer->onDumpReport(bucketStartTimeNs + 40, true /* include recent buckets */, true, + FAST /* dumpLatency */, &strSet, &output); + + StatsLogReport report = outputStreamToProto(&output); + EXPECT_TRUE(report.has_value_metrics()); + ASSERT_EQ(0, report.value_metrics().data_size()); + ASSERT_EQ(1, report.value_metrics().skipped_size()); + + EXPECT_EQ(NanoToMillis(bucketStartTimeNs), + report.value_metrics().skipped(0).start_bucket_elapsed_millis()); + EXPECT_EQ(NanoToMillis(bucketStartTimeNs + 40), + report.value_metrics().skipped(0).end_bucket_elapsed_millis()); + ASSERT_EQ(1, report.value_metrics().skipped(0).drop_event_size()); + + auto dropEvent = report.value_metrics().skipped(0).drop_event(0); + EXPECT_EQ(BucketDropReason::DUMP_REPORT_REQUESTED, dropEvent.drop_reason()); + EXPECT_EQ(NanoToMillis(bucketStartTimeNs + 40), dropEvent.drop_time_millis()); +} + +/* + * Test that EVENT_IN_WRONG_BUCKET dump reason is logged for a late condition + * change event (i.e. the condition change occurs in the wrong bucket). + */ +TEST(ValueMetricProducerTest_BucketDrop, TestInvalidBucketWhenConditionEventWrongBucket) { + ValueMetric metric = ValueMetricProducerTestHelper::createMetricWithCondition(); + + sp pullerManager = new StrictMock(); + EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, bucketStartTimeNs + 50, _)) + // Condition change to true. + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t, + vector>* data) { + data->clear(); + data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 50, 10)); + return true; + })); + + sp valueProducer = + ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric, + ConditionState::kFalse); + + // Condition change event. + valueProducer->onConditionChanged(true, bucketStartTimeNs + 50); + + // Bucket boundary pull. + vector> allData; + allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs, 15)); + valueProducer->onDataPulled(allData, /** succeeds */ true, bucket2StartTimeNs + 1); + + // Late condition change event. + valueProducer->onConditionChanged(false, bucket2StartTimeNs - 100); + + // Check dump report. + ProtoOutputStream output; + std::set strSet; + valueProducer->onDumpReport(bucket2StartTimeNs + 100, true /* include recent buckets */, true, + NO_TIME_CONSTRAINTS /* dumpLatency */, &strSet, &output); + + StatsLogReport report = outputStreamToProto(&output); + EXPECT_TRUE(report.has_value_metrics()); + ASSERT_EQ(1, report.value_metrics().data_size()); + ASSERT_EQ(1, report.value_metrics().skipped_size()); + + EXPECT_EQ(NanoToMillis(bucket2StartTimeNs), + report.value_metrics().skipped(0).start_bucket_elapsed_millis()); + EXPECT_EQ(NanoToMillis(bucket2StartTimeNs + 100), + report.value_metrics().skipped(0).end_bucket_elapsed_millis()); + ASSERT_EQ(2, report.value_metrics().skipped(0).drop_event_size()); + + auto dropEvent = report.value_metrics().skipped(0).drop_event(0); + EXPECT_EQ(BucketDropReason::EVENT_IN_WRONG_BUCKET, dropEvent.drop_reason()); + EXPECT_EQ(NanoToMillis(bucket2StartTimeNs - 100), dropEvent.drop_time_millis()); + + dropEvent = report.value_metrics().skipped(0).drop_event(1); + EXPECT_EQ(BucketDropReason::CONDITION_UNKNOWN, dropEvent.drop_reason()); + EXPECT_EQ(NanoToMillis(bucket2StartTimeNs + 100), dropEvent.drop_time_millis()); +} + +/* + * Test that EVENT_IN_WRONG_BUCKET dump reason is logged for a late accumulate + * event (i.e. the accumulate events call occurs in the wrong bucket). + */ +TEST(ValueMetricProducerTest_BucketDrop, TestInvalidBucketWhenAccumulateEventWrongBucket) { + ValueMetric metric = ValueMetricProducerTestHelper::createMetricWithCondition(); + + sp pullerManager = new StrictMock(); + EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _)) + // Condition change to true. + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector>* data) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 50); + data->clear(); + data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 50, 10)); + return true; + })) + // Dump report requested. + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector>* data) { + EXPECT_EQ(eventTimeNs, bucket2StartTimeNs + 100); + data->clear(); + data->push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs + 100, 15)); + return true; + })); + + sp valueProducer = + ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric, + ConditionState::kFalse); + + // Condition change event. + valueProducer->onConditionChanged(true, bucketStartTimeNs + 50); + + // Bucket boundary pull. + vector> allData; + allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs, 15)); + valueProducer->onDataPulled(allData, /** succeeds */ true, bucket2StartTimeNs + 1); + + allData.clear(); + allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs - 100, 20)); + + // Late accumulateEvents event. + valueProducer->accumulateEvents(allData, bucket2StartTimeNs - 100, bucket2StartTimeNs - 100); + + // Check dump report. + ProtoOutputStream output; + std::set strSet; + valueProducer->onDumpReport(bucket2StartTimeNs + 100, true /* include recent buckets */, true, + NO_TIME_CONSTRAINTS /* dumpLatency */, &strSet, &output); + + StatsLogReport report = outputStreamToProto(&output); + EXPECT_TRUE(report.has_value_metrics()); + ASSERT_EQ(1, report.value_metrics().data_size()); + ASSERT_EQ(1, report.value_metrics().skipped_size()); + + EXPECT_EQ(NanoToMillis(bucket2StartTimeNs), + report.value_metrics().skipped(0).start_bucket_elapsed_millis()); + EXPECT_EQ(NanoToMillis(bucket2StartTimeNs + 100), + report.value_metrics().skipped(0).end_bucket_elapsed_millis()); + ASSERT_EQ(1, report.value_metrics().skipped(0).drop_event_size()); + + auto dropEvent = report.value_metrics().skipped(0).drop_event(0); + EXPECT_EQ(BucketDropReason::EVENT_IN_WRONG_BUCKET, dropEvent.drop_reason()); + EXPECT_EQ(NanoToMillis(bucket2StartTimeNs - 100), dropEvent.drop_time_millis()); +} + +/* + * Test that CONDITION_UNKNOWN dump reason is logged due to an unknown condition + * when a metric is initialized. + */ +TEST(ValueMetricProducerTest_BucketDrop, TestInvalidBucketWhenConditionUnknown) { + ValueMetric metric = ValueMetricProducerTestHelper::createMetricWithCondition(); + + sp pullerManager = new StrictMock(); + EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _)) + // Condition change to true. + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector>* data) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 50); + data->clear(); + data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 50, 10)); + return true; + })) + // Dump report requested. + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector>* data) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 10000); + data->clear(); + data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 100, 15)); + return true; + })); + + sp valueProducer = + ValueMetricProducerTestHelper::createValueProducerWithCondition( + pullerManager, metric, ConditionState::kUnknown); + + // Condition change event. + valueProducer->onConditionChanged(true, bucketStartTimeNs + 50); + + // Check dump report. + ProtoOutputStream output; + std::set strSet; + int64_t dumpReportTimeNs = bucketStartTimeNs + 10000; + valueProducer->onDumpReport(dumpReportTimeNs, true /* include recent buckets */, true, + NO_TIME_CONSTRAINTS /* dumpLatency */, &strSet, &output); + + StatsLogReport report = outputStreamToProto(&output); + EXPECT_TRUE(report.has_value_metrics()); + ASSERT_EQ(0, report.value_metrics().data_size()); + ASSERT_EQ(1, report.value_metrics().skipped_size()); + + EXPECT_EQ(NanoToMillis(bucketStartTimeNs), + report.value_metrics().skipped(0).start_bucket_elapsed_millis()); + EXPECT_EQ(NanoToMillis(dumpReportTimeNs), + report.value_metrics().skipped(0).end_bucket_elapsed_millis()); + ASSERT_EQ(1, report.value_metrics().skipped(0).drop_event_size()); + + auto dropEvent = report.value_metrics().skipped(0).drop_event(0); + EXPECT_EQ(BucketDropReason::CONDITION_UNKNOWN, dropEvent.drop_reason()); + EXPECT_EQ(NanoToMillis(dumpReportTimeNs), dropEvent.drop_time_millis()); +} + +/* + * Test that PULL_FAILED dump reason is logged due to a pull failure in + * #pullAndMatchEventsLocked. + */ +TEST(ValueMetricProducerTest_BucketDrop, TestInvalidBucketWhenPullFailed) { + ValueMetric metric = ValueMetricProducerTestHelper::createMetricWithCondition(); + + sp pullerManager = new StrictMock(); + EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _)) + // Condition change to true. + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector>* data) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 50); + data->clear(); + data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 50, 10)); + return true; + })) + // Dump report requested, pull fails. + .WillOnce(Return(false)); + + sp valueProducer = + ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric, + ConditionState::kFalse); + + // Condition change event. + valueProducer->onConditionChanged(true, bucketStartTimeNs + 50); + + // Check dump report. + ProtoOutputStream output; + std::set strSet; + int64_t dumpReportTimeNs = bucketStartTimeNs + 10000; + valueProducer->onDumpReport(dumpReportTimeNs, true /* include recent buckets */, true, + NO_TIME_CONSTRAINTS /* dumpLatency */, &strSet, &output); + + StatsLogReport report = outputStreamToProto(&output); + EXPECT_TRUE(report.has_value_metrics()); + ASSERT_EQ(0, report.value_metrics().data_size()); + ASSERT_EQ(1, report.value_metrics().skipped_size()); + + EXPECT_EQ(NanoToMillis(bucketStartTimeNs), + report.value_metrics().skipped(0).start_bucket_elapsed_millis()); + EXPECT_EQ(NanoToMillis(dumpReportTimeNs), + report.value_metrics().skipped(0).end_bucket_elapsed_millis()); + ASSERT_EQ(1, report.value_metrics().skipped(0).drop_event_size()); + + auto dropEvent = report.value_metrics().skipped(0).drop_event(0); + EXPECT_EQ(BucketDropReason::PULL_FAILED, dropEvent.drop_reason()); + EXPECT_EQ(NanoToMillis(dumpReportTimeNs), dropEvent.drop_time_millis()); +} + +/* + * Test that MULTIPLE_BUCKETS_SKIPPED dump reason is logged when a log event + * skips over more than one bucket. + */ +TEST(ValueMetricProducerTest_BucketDrop, TestInvalidBucketWhenMultipleBucketsSkipped) { + ValueMetric metric = ValueMetricProducerTestHelper::createMetricWithCondition(); + + sp pullerManager = new StrictMock(); + EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _)) + // Condition change to true. + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector>* data) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 10); + data->clear(); + data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 10, 10)); + return true; + })) + // Dump report requested. + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector>* data) { + EXPECT_EQ(eventTimeNs, bucket4StartTimeNs + 10); + data->clear(); + data->push_back(CreateRepeatedValueLogEvent(tagId, bucket4StartTimeNs + 1000, 15)); + return true; + })); + + sp valueProducer = + ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric, + ConditionState::kFalse); + + // Condition change event. + valueProducer->onConditionChanged(true, bucketStartTimeNs + 10); + + // Condition change event that skips forward by three buckets. + valueProducer->onConditionChanged(false, bucket4StartTimeNs + 10); + + int64_t dumpTimeNs = bucket4StartTimeNs + 1000; + + // Check dump report. + ProtoOutputStream output; + std::set strSet; + valueProducer->onDumpReport(dumpTimeNs, true /* include current buckets */, true, + NO_TIME_CONSTRAINTS /* dumpLatency */, &strSet, &output); + + StatsLogReport report = outputStreamToProto(&output); + EXPECT_TRUE(report.has_value_metrics()); + ASSERT_EQ(0, report.value_metrics().data_size()); + ASSERT_EQ(2, report.value_metrics().skipped_size()); + + EXPECT_EQ(NanoToMillis(bucketStartTimeNs), + report.value_metrics().skipped(0).start_bucket_elapsed_millis()); + EXPECT_EQ(NanoToMillis(bucket4StartTimeNs), + report.value_metrics().skipped(0).end_bucket_elapsed_millis()); + ASSERT_EQ(1, report.value_metrics().skipped(0).drop_event_size()); + + auto dropEvent = report.value_metrics().skipped(0).drop_event(0); + EXPECT_EQ(BucketDropReason::MULTIPLE_BUCKETS_SKIPPED, dropEvent.drop_reason()); + EXPECT_EQ(NanoToMillis(bucket4StartTimeNs + 10), dropEvent.drop_time_millis()); + + // This bucket is skipped because a dumpReport with include current buckets is called. + // This creates a new bucket from bucket4StartTimeNs to dumpTimeNs in which we have no data + // since the condition is false for the entire bucket interval. + EXPECT_EQ(NanoToMillis(bucket4StartTimeNs), + report.value_metrics().skipped(1).start_bucket_elapsed_millis()); + EXPECT_EQ(NanoToMillis(dumpTimeNs), + report.value_metrics().skipped(1).end_bucket_elapsed_millis()); + ASSERT_EQ(1, report.value_metrics().skipped(1).drop_event_size()); + + dropEvent = report.value_metrics().skipped(1).drop_event(0); + EXPECT_EQ(BucketDropReason::NO_DATA, dropEvent.drop_reason()); + EXPECT_EQ(NanoToMillis(dumpTimeNs), dropEvent.drop_time_millis()); +} + +/* + * Test that BUCKET_TOO_SMALL dump reason is logged when a flushed bucket size + * is smaller than the "min_bucket_size_nanos" specified in the metric config. + */ +TEST(ValueMetricProducerTest_BucketDrop, TestBucketDropWhenBucketTooSmall) { + ValueMetric metric = ValueMetricProducerTestHelper::createMetricWithCondition(); + metric.set_min_bucket_size_nanos(10000000000); // 10 seconds + + sp pullerManager = new StrictMock(); + EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _)) + // Condition change to true. + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector>* data) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 10); + data->clear(); + data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 10, 10)); + return true; + })) + // Dump report requested. + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector>* data) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 9000000); + data->clear(); + data->push_back( + CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 9000000, 15)); + return true; + })); + + sp valueProducer = + ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric, + ConditionState::kFalse); + + // Condition change event. + valueProducer->onConditionChanged(true, bucketStartTimeNs + 10); + + // Check dump report. + ProtoOutputStream output; + std::set strSet; + int64_t dumpReportTimeNs = bucketStartTimeNs + 9000000; + valueProducer->onDumpReport(dumpReportTimeNs, true /* include recent buckets */, true, + NO_TIME_CONSTRAINTS /* dumpLatency */, &strSet, &output); + + StatsLogReport report = outputStreamToProto(&output); + EXPECT_TRUE(report.has_value_metrics()); + ASSERT_EQ(0, report.value_metrics().data_size()); + ASSERT_EQ(1, report.value_metrics().skipped_size()); + + EXPECT_EQ(NanoToMillis(bucketStartTimeNs), + report.value_metrics().skipped(0).start_bucket_elapsed_millis()); + EXPECT_EQ(NanoToMillis(dumpReportTimeNs), + report.value_metrics().skipped(0).end_bucket_elapsed_millis()); + ASSERT_EQ(1, report.value_metrics().skipped(0).drop_event_size()); + + auto dropEvent = report.value_metrics().skipped(0).drop_event(0); + EXPECT_EQ(BucketDropReason::BUCKET_TOO_SMALL, dropEvent.drop_reason()); + EXPECT_EQ(NanoToMillis(dumpReportTimeNs), dropEvent.drop_time_millis()); +} + +/* + * Test that NO_DATA dump reason is logged when a flushed bucket contains no data. + */ +TEST(ValueMetricProducerTest_BucketDrop, TestBucketDropWhenDataUnavailable) { + ValueMetric metric = ValueMetricProducerTestHelper::createMetricWithCondition(); + + sp pullerManager = new StrictMock(); + + sp valueProducer = + ValueMetricProducerTestHelper::createValueProducerWithCondition( + pullerManager, metric, ConditionState::kFalse); + + // Check dump report. + ProtoOutputStream output; + std::set strSet; + int64_t dumpReportTimeNs = bucketStartTimeNs + 10000000000; // 10 seconds + valueProducer->onDumpReport(dumpReportTimeNs, true /* include current bucket */, true, + NO_TIME_CONSTRAINTS /* dumpLatency */, &strSet, &output); + + StatsLogReport report = outputStreamToProto(&output); + EXPECT_TRUE(report.has_value_metrics()); + ASSERT_EQ(0, report.value_metrics().data_size()); + ASSERT_EQ(1, report.value_metrics().skipped_size()); + + EXPECT_EQ(NanoToMillis(bucketStartTimeNs), + report.value_metrics().skipped(0).start_bucket_elapsed_millis()); + EXPECT_EQ(NanoToMillis(dumpReportTimeNs), + report.value_metrics().skipped(0).end_bucket_elapsed_millis()); + ASSERT_EQ(1, report.value_metrics().skipped(0).drop_event_size()); + + auto dropEvent = report.value_metrics().skipped(0).drop_event(0); + EXPECT_EQ(BucketDropReason::NO_DATA, dropEvent.drop_reason()); + EXPECT_EQ(NanoToMillis(dumpReportTimeNs), dropEvent.drop_time_millis()); +} + +/* + * Test that all buckets are dropped due to condition unknown until the first onConditionChanged. + */ +TEST(ValueMetricProducerTest_BucketDrop, TestConditionUnknownMultipleBuckets) { + ValueMetric metric = ValueMetricProducerTestHelper::createMetricWithCondition(); + + sp pullerManager = new StrictMock(); + EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _)) + // Condition change to true. + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector>* data) { + EXPECT_EQ(eventTimeNs, bucket2StartTimeNs + 10 * NS_PER_SEC); + data->clear(); + data->push_back(CreateRepeatedValueLogEvent( + tagId, bucket2StartTimeNs + 10 * NS_PER_SEC, 10)); + return true; + })) + // Dump report requested. + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector>* data) { + EXPECT_EQ(eventTimeNs, bucket2StartTimeNs + 15 * NS_PER_SEC); + data->clear(); + data->push_back(CreateRepeatedValueLogEvent( + tagId, bucket2StartTimeNs + 15 * NS_PER_SEC, 15)); + return true; + })); + + sp valueProducer = + ValueMetricProducerTestHelper::createValueProducerWithCondition( + pullerManager, metric, ConditionState::kUnknown); + + // Bucket should be dropped because of condition unknown. + int64_t appUpgradeTimeNs = bucketStartTimeNs + 5 * NS_PER_SEC; + valueProducer->notifyAppUpgrade(appUpgradeTimeNs); + + // Bucket also dropped due to condition unknown + vector> allData; + allData.clear(); + allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs + 1, 3)); + valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs); + + // This bucket is also dropped due to condition unknown. + int64_t conditionChangeTimeNs = bucket2StartTimeNs + 10 * NS_PER_SEC; + valueProducer->onConditionChanged(true, conditionChangeTimeNs); + + // Check dump report. + ProtoOutputStream output; + std::set strSet; + int64_t dumpReportTimeNs = bucket2StartTimeNs + 15 * NS_PER_SEC; // 15 seconds + valueProducer->onDumpReport(dumpReportTimeNs, true /* include current bucket */, true, + NO_TIME_CONSTRAINTS /* dumpLatency */, &strSet, &output); + + StatsLogReport report = outputStreamToProto(&output); + EXPECT_TRUE(report.has_value_metrics()); + ASSERT_EQ(0, report.value_metrics().data_size()); + ASSERT_EQ(3, report.value_metrics().skipped_size()); + + EXPECT_EQ(NanoToMillis(bucketStartTimeNs), + report.value_metrics().skipped(0).start_bucket_elapsed_millis()); + EXPECT_EQ(NanoToMillis(appUpgradeTimeNs), + report.value_metrics().skipped(0).end_bucket_elapsed_millis()); + ASSERT_EQ(1, report.value_metrics().skipped(0).drop_event_size()); + + auto dropEvent = report.value_metrics().skipped(0).drop_event(0); + EXPECT_EQ(BucketDropReason::CONDITION_UNKNOWN, dropEvent.drop_reason()); + EXPECT_EQ(NanoToMillis(appUpgradeTimeNs), dropEvent.drop_time_millis()); + + EXPECT_EQ(NanoToMillis(appUpgradeTimeNs), + report.value_metrics().skipped(1).start_bucket_elapsed_millis()); + EXPECT_EQ(NanoToMillis(bucket2StartTimeNs), + report.value_metrics().skipped(1).end_bucket_elapsed_millis()); + ASSERT_EQ(1, report.value_metrics().skipped(1).drop_event_size()); + + dropEvent = report.value_metrics().skipped(1).drop_event(0); + EXPECT_EQ(BucketDropReason::CONDITION_UNKNOWN, dropEvent.drop_reason()); + EXPECT_EQ(NanoToMillis(bucket2StartTimeNs), dropEvent.drop_time_millis()); + + EXPECT_EQ(NanoToMillis(bucket2StartTimeNs), + report.value_metrics().skipped(2).start_bucket_elapsed_millis()); + EXPECT_EQ(NanoToMillis(dumpReportTimeNs), + report.value_metrics().skipped(2).end_bucket_elapsed_millis()); + ASSERT_EQ(1, report.value_metrics().skipped(2).drop_event_size()); + + dropEvent = report.value_metrics().skipped(2).drop_event(0); + EXPECT_EQ(BucketDropReason::CONDITION_UNKNOWN, dropEvent.drop_reason()); + EXPECT_EQ(NanoToMillis(conditionChangeTimeNs), dropEvent.drop_time_millis()); +} + +/* + * Test that a skipped bucket is logged when a forced bucket split occurs when the previous bucket + * was not flushed in time. + */ +TEST(ValueMetricProducerTest_BucketDrop, TestBucketDropWhenForceBucketSplitBeforeBucketFlush) { + ValueMetric metric = ValueMetricProducerTestHelper::createMetricWithCondition(); + + sp pullerManager = new StrictMock(); + EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _)) + // Condition change to true. + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector>* data) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 10); + data->clear(); + data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 10, 10)); + return true; + })) + // App Update. + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector>* data) { + EXPECT_EQ(eventTimeNs, bucket2StartTimeNs + 1000); + data->clear(); + data->push_back( + CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs + 1000, 15)); + return true; + })); + + sp valueProducer = + ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric, + ConditionState::kFalse); + + // Condition changed event + int64_t conditionChangeTimeNs = bucketStartTimeNs + 10; + valueProducer->onConditionChanged(true, conditionChangeTimeNs); + + // App update event. + int64_t appUpdateTimeNs = bucket2StartTimeNs + 1000; + valueProducer->notifyAppUpgrade(appUpdateTimeNs); + + // Check dump report. + ProtoOutputStream output; + std::set strSet; + int64_t dumpReportTimeNs = bucket2StartTimeNs + 10000000000; // 10 seconds + valueProducer->onDumpReport(dumpReportTimeNs, false /* include current buckets */, true, + NO_TIME_CONSTRAINTS /* dumpLatency */, &strSet, &output); + + StatsLogReport report = outputStreamToProto(&output); + EXPECT_TRUE(report.has_value_metrics()); + ASSERT_EQ(1, report.value_metrics().data_size()); + ASSERT_EQ(1, report.value_metrics().skipped_size()); + + ASSERT_EQ(1, report.value_metrics().data(0).bucket_info_size()); + auto data = report.value_metrics().data(0); + ASSERT_EQ(0, data.bucket_info(0).bucket_num()); + EXPECT_EQ(5, data.bucket_info(0).values(0).value_long()); + + EXPECT_EQ(NanoToMillis(bucket2StartTimeNs), + report.value_metrics().skipped(0).start_bucket_elapsed_millis()); + EXPECT_EQ(NanoToMillis(appUpdateTimeNs), + report.value_metrics().skipped(0).end_bucket_elapsed_millis()); + ASSERT_EQ(1, report.value_metrics().skipped(0).drop_event_size()); + + auto dropEvent = report.value_metrics().skipped(0).drop_event(0); + EXPECT_EQ(BucketDropReason::NO_DATA, dropEvent.drop_reason()); + EXPECT_EQ(NanoToMillis(appUpdateTimeNs), dropEvent.drop_time_millis()); +} + +/* + * Test multiple bucket drop events in the same bucket. + */ +TEST(ValueMetricProducerTest_BucketDrop, TestMultipleBucketDropEvents) { + ValueMetric metric = ValueMetricProducerTestHelper::createMetricWithCondition(); + + sp pullerManager = new StrictMock(); + EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, bucketStartTimeNs + 10, _)) + // Condition change to true. + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t, + vector>* data) { + data->clear(); + data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 10, 10)); + return true; + })); + + sp valueProducer = + ValueMetricProducerTestHelper::createValueProducerWithCondition( + pullerManager, metric, ConditionState::kUnknown); + + // Condition change event. + valueProducer->onConditionChanged(true, bucketStartTimeNs + 10); + + // Check dump report. + ProtoOutputStream output; + std::set strSet; + int64_t dumpReportTimeNs = bucketStartTimeNs + 1000; + valueProducer->onDumpReport(dumpReportTimeNs, true /* include recent buckets */, true, + FAST /* dumpLatency */, &strSet, &output); + + StatsLogReport report = outputStreamToProto(&output); + EXPECT_TRUE(report.has_value_metrics()); + ASSERT_EQ(0, report.value_metrics().data_size()); + ASSERT_EQ(1, report.value_metrics().skipped_size()); + + EXPECT_EQ(NanoToMillis(bucketStartTimeNs), + report.value_metrics().skipped(0).start_bucket_elapsed_millis()); + EXPECT_EQ(NanoToMillis(dumpReportTimeNs), + report.value_metrics().skipped(0).end_bucket_elapsed_millis()); + ASSERT_EQ(2, report.value_metrics().skipped(0).drop_event_size()); + + auto dropEvent = report.value_metrics().skipped(0).drop_event(0); + EXPECT_EQ(BucketDropReason::CONDITION_UNKNOWN, dropEvent.drop_reason()); + EXPECT_EQ(NanoToMillis(bucketStartTimeNs + 10), dropEvent.drop_time_millis()); + + dropEvent = report.value_metrics().skipped(0).drop_event(1); + EXPECT_EQ(BucketDropReason::DUMP_REPORT_REQUESTED, dropEvent.drop_reason()); + EXPECT_EQ(NanoToMillis(dumpReportTimeNs), dropEvent.drop_time_millis()); +} + +/* + * Test that the number of logged bucket drop events is capped at the maximum. + * The maximum is currently 10 and is set in MetricProducer::maxDropEventsReached(). + */ +TEST(ValueMetricProducerTest_BucketDrop, TestMaxBucketDropEvents) { + ValueMetric metric = ValueMetricProducerTestHelper::createMetricWithCondition(); + + sp pullerManager = new StrictMock(); + EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _)) + // First condition change event. + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector>* data) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 10); + for (int i = 0; i < 2000; i++) { + data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 1, i)); + } + return true; + })) + .WillOnce(Return(false)) + .WillOnce(Return(false)) + .WillOnce(Return(false)) + .WillOnce(Return(false)) + .WillOnce(Return(false)) + .WillOnce(Return(false)) + .WillOnce(Return(false)) + .WillOnce(Return(false)) + .WillOnce(Return(false)) + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector>* data) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 220); + data->clear(); + data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 220, 10)); + return true; + })); + + sp valueProducer = + ValueMetricProducerTestHelper::createValueProducerWithCondition( + pullerManager, metric, ConditionState::kUnknown); + + // First condition change event causes guardrail to be reached. + valueProducer->onConditionChanged(true, bucketStartTimeNs + 10); + + // 2-10 condition change events result in failed pulls. + valueProducer->onConditionChanged(false, bucketStartTimeNs + 30); + valueProducer->onConditionChanged(true, bucketStartTimeNs + 50); + valueProducer->onConditionChanged(false, bucketStartTimeNs + 70); + valueProducer->onConditionChanged(true, bucketStartTimeNs + 90); + valueProducer->onConditionChanged(false, bucketStartTimeNs + 100); + valueProducer->onConditionChanged(true, bucketStartTimeNs + 150); + valueProducer->onConditionChanged(false, bucketStartTimeNs + 170); + valueProducer->onConditionChanged(true, bucketStartTimeNs + 190); + valueProducer->onConditionChanged(false, bucketStartTimeNs + 200); + + // Condition change event 11 + valueProducer->onConditionChanged(true, bucketStartTimeNs + 220); + + // Check dump report. + ProtoOutputStream output; + std::set strSet; + int64_t dumpReportTimeNs = bucketStartTimeNs + 1000; + // Because we already have 10 dump events in the current bucket, + // this case should not be added to the list of dump events. + valueProducer->onDumpReport(bucketStartTimeNs + 1000, true /* include recent buckets */, true, + FAST /* dumpLatency */, &strSet, &output); + + StatsLogReport report = outputStreamToProto(&output); + EXPECT_TRUE(report.has_value_metrics()); + ASSERT_EQ(0, report.value_metrics().data_size()); + ASSERT_EQ(1, report.value_metrics().skipped_size()); + + EXPECT_EQ(NanoToMillis(bucketStartTimeNs), + report.value_metrics().skipped(0).start_bucket_elapsed_millis()); + EXPECT_EQ(NanoToMillis(dumpReportTimeNs), + report.value_metrics().skipped(0).end_bucket_elapsed_millis()); + ASSERT_EQ(10, report.value_metrics().skipped(0).drop_event_size()); + + auto dropEvent = report.value_metrics().skipped(0).drop_event(0); + EXPECT_EQ(BucketDropReason::CONDITION_UNKNOWN, dropEvent.drop_reason()); + EXPECT_EQ(NanoToMillis(bucketStartTimeNs + 10), dropEvent.drop_time_millis()); + + dropEvent = report.value_metrics().skipped(0).drop_event(1); + EXPECT_EQ(BucketDropReason::PULL_FAILED, dropEvent.drop_reason()); + EXPECT_EQ(NanoToMillis(bucketStartTimeNs + 30), dropEvent.drop_time_millis()); + + dropEvent = report.value_metrics().skipped(0).drop_event(2); + EXPECT_EQ(BucketDropReason::PULL_FAILED, dropEvent.drop_reason()); + EXPECT_EQ(NanoToMillis(bucketStartTimeNs + 50), dropEvent.drop_time_millis()); + + dropEvent = report.value_metrics().skipped(0).drop_event(3); + EXPECT_EQ(BucketDropReason::PULL_FAILED, dropEvent.drop_reason()); + EXPECT_EQ(NanoToMillis(bucketStartTimeNs + 70), dropEvent.drop_time_millis()); + + dropEvent = report.value_metrics().skipped(0).drop_event(4); + EXPECT_EQ(BucketDropReason::PULL_FAILED, dropEvent.drop_reason()); + EXPECT_EQ(NanoToMillis(bucketStartTimeNs + 90), dropEvent.drop_time_millis()); + + dropEvent = report.value_metrics().skipped(0).drop_event(5); + EXPECT_EQ(BucketDropReason::PULL_FAILED, dropEvent.drop_reason()); + EXPECT_EQ(NanoToMillis(bucketStartTimeNs + 100), dropEvent.drop_time_millis()); + + dropEvent = report.value_metrics().skipped(0).drop_event(6); + EXPECT_EQ(BucketDropReason::PULL_FAILED, dropEvent.drop_reason()); + EXPECT_EQ(NanoToMillis(bucketStartTimeNs + 150), dropEvent.drop_time_millis()); + + dropEvent = report.value_metrics().skipped(0).drop_event(7); + EXPECT_EQ(BucketDropReason::PULL_FAILED, dropEvent.drop_reason()); + EXPECT_EQ(NanoToMillis(bucketStartTimeNs + 170), dropEvent.drop_time_millis()); + + dropEvent = report.value_metrics().skipped(0).drop_event(8); + EXPECT_EQ(BucketDropReason::PULL_FAILED, dropEvent.drop_reason()); + EXPECT_EQ(NanoToMillis(bucketStartTimeNs + 190), dropEvent.drop_time_millis()); + + dropEvent = report.value_metrics().skipped(0).drop_event(9); + EXPECT_EQ(BucketDropReason::PULL_FAILED, dropEvent.drop_reason()); + EXPECT_EQ(NanoToMillis(bucketStartTimeNs + 200), dropEvent.drop_time_millis()); +} + +/* + * Test metric with a simple sliced state + * - Increasing values + * - Using diff + * - Second field is value field + */ +TEST(ValueMetricProducerTest, TestSlicedState) { + // Set up ValueMetricProducer. + ValueMetric metric = ValueMetricProducerTestHelper::createMetricWithState("SCREEN_STATE"); + sp pullerManager = new StrictMock(); + EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _)) + // ValueMetricProducer initialized. + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector>* data) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs); + data->clear(); + data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs, 3)); + return true; + })) + // Screen state change to ON. + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector>* data) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 5 * NS_PER_SEC); + data->clear(); + data->push_back( + CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 5 * NS_PER_SEC, 5)); + return true; + })) + // Screen state change to OFF. + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector>* data) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 10 * NS_PER_SEC); + data->clear(); + data->push_back( + CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 10 * NS_PER_SEC, 9)); + return true; + })) + // Screen state change to ON. + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector>* data) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 15 * NS_PER_SEC); + data->clear(); + data->push_back(CreateRepeatedValueLogEvent( + tagId, bucketStartTimeNs + 15 * NS_PER_SEC, 21)); + return true; + })) + // Dump report requested. + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector>* data) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 50 * NS_PER_SEC); + data->clear(); + data->push_back(CreateRepeatedValueLogEvent( + tagId, bucketStartTimeNs + 50 * NS_PER_SEC, 30)); + return true; + })); + + StateManager::getInstance().clear(); + sp valueProducer = + ValueMetricProducerTestHelper::createValueProducerWithState( + pullerManager, metric, {util::SCREEN_STATE_CHANGED}, {}); + EXPECT_EQ(1, valueProducer->mSlicedStateAtoms.size()); + + // Set up StateManager and check that StateTrackers are initialized. + StateManager::getInstance().registerListener(SCREEN_STATE_ATOM_ID, valueProducer); + EXPECT_EQ(1, StateManager::getInstance().getStateTrackersCount()); + EXPECT_EQ(1, StateManager::getInstance().getListenersCount(SCREEN_STATE_ATOM_ID)); + + // Bucket status after metric initialized. + ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); + // Base for dimension key {} + auto it = valueProducer->mCurrentSlicedBucket.begin(); + auto itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); + EXPECT_TRUE(itBase->second.baseInfos[0].hasBase); + EXPECT_EQ(3, itBase->second.baseInfos[0].base.long_value); + EXPECT_TRUE(itBase->second.hasCurrentState); + ASSERT_EQ(1, itBase->second.currentState.getValues().size()); + EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, + itBase->second.currentState.getValues()[0].mValue.int_value); + // Value for dimension, state key {{}, kStateUnknown} + EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + EXPECT_FALSE(it->second.intervals[0].hasValue); + assertConditionTimer(it->second.conditionTimer, true, 0, bucketStartTimeNs); + + // Bucket status after screen state change kStateUnknown->ON. + auto screenEvent = CreateScreenStateChangedEvent( + bucketStartTimeNs + 5 * NS_PER_SEC, android::view::DisplayStateEnum::DISPLAY_STATE_ON); + StateManager::getInstance().onLogEvent(*screenEvent); + ASSERT_EQ(2UL, valueProducer->mCurrentSlicedBucket.size()); + // Base for dimension key {} + it = valueProducer->mCurrentSlicedBucket.begin(); + itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); + EXPECT_TRUE(itBase->second.baseInfos[0].hasBase); + EXPECT_EQ(5, itBase->second.baseInfos[0].base.long_value); + EXPECT_TRUE(itBase->second.hasCurrentState); + ASSERT_EQ(1, itBase->second.currentState.getValues().size()); + EXPECT_EQ(android::view::DisplayStateEnum::DISPLAY_STATE_ON, + itBase->second.currentState.getValues()[0].mValue.int_value); + // Value for dimension, state key {{}, ON} + EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(android::view::DisplayStateEnum::DISPLAY_STATE_ON, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + EXPECT_EQ(0, it->second.intervals.size()); + assertConditionTimer(it->second.conditionTimer, true, 0, bucketStartTimeNs + 5 * NS_PER_SEC); + // Value for dimension, state key {{}, kStateUnknown} + it++; + EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + EXPECT_TRUE(it->second.intervals[0].hasValue); + EXPECT_EQ(2, it->second.intervals[0].value.long_value); + assertConditionTimer(it->second.conditionTimer, false, 5 * NS_PER_SEC, + bucketStartTimeNs + 5 * NS_PER_SEC); + + // Bucket status after screen state change ON->OFF. + screenEvent = CreateScreenStateChangedEvent(bucketStartTimeNs + 10 * NS_PER_SEC, + android::view::DisplayStateEnum::DISPLAY_STATE_OFF); + StateManager::getInstance().onLogEvent(*screenEvent); + ASSERT_EQ(3UL, valueProducer->mCurrentSlicedBucket.size()); + // Base for dimension key {} + it = valueProducer->mCurrentSlicedBucket.begin(); + itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); + EXPECT_TRUE(itBase->second.baseInfos[0].hasBase); + EXPECT_EQ(9, itBase->second.baseInfos[0].base.long_value); + EXPECT_TRUE(itBase->second.hasCurrentState); + EXPECT_EQ(android::view::DisplayStateEnum::DISPLAY_STATE_OFF, + itBase->second.currentState.getValues()[0].mValue.int_value); + // Value for dimension, state key {{}, OFF} + EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(android::view::DisplayStateEnum::DISPLAY_STATE_OFF, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + EXPECT_EQ(0, it->second.intervals.size()); + assertConditionTimer(it->second.conditionTimer, true, 0, bucketStartTimeNs + 10 * NS_PER_SEC); + // Value for dimension, state key {{}, ON} + it++; + EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(android::view::DisplayStateEnum::DISPLAY_STATE_ON, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + EXPECT_TRUE(it->second.intervals[0].hasValue); + EXPECT_EQ(4, it->second.intervals[0].value.long_value); + assertConditionTimer(it->second.conditionTimer, false, 5 * NS_PER_SEC, + bucketStartTimeNs + 10 * NS_PER_SEC); + // Value for dimension, state key {{}, kStateUnknown} + it++; + EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + EXPECT_TRUE(it->second.intervals[0].hasValue); + EXPECT_EQ(2, it->second.intervals[0].value.long_value); + assertConditionTimer(it->second.conditionTimer, false, 5 * NS_PER_SEC, + bucketStartTimeNs + 5 * NS_PER_SEC); + + // Bucket status after screen state change OFF->ON. + screenEvent = CreateScreenStateChangedEvent(bucketStartTimeNs + 15 * NS_PER_SEC, + android::view::DisplayStateEnum::DISPLAY_STATE_ON); + StateManager::getInstance().onLogEvent(*screenEvent); + ASSERT_EQ(3UL, valueProducer->mCurrentSlicedBucket.size()); + // Base for dimension key {} + it = valueProducer->mCurrentSlicedBucket.begin(); + itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); + EXPECT_TRUE(itBase->second.baseInfos[0].hasBase); + EXPECT_EQ(21, itBase->second.baseInfos[0].base.long_value); + EXPECT_TRUE(itBase->second.hasCurrentState); + ASSERT_EQ(1, itBase->second.currentState.getValues().size()); + EXPECT_EQ(android::view::DisplayStateEnum::DISPLAY_STATE_ON, + itBase->second.currentState.getValues()[0].mValue.int_value); + // Value for dimension, state key {{}, OFF} + EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(android::view::DisplayStateEnum::DISPLAY_STATE_OFF, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + EXPECT_TRUE(it->second.intervals[0].hasValue); + EXPECT_EQ(12, it->second.intervals[0].value.long_value); + assertConditionTimer(it->second.conditionTimer, false, 5 * NS_PER_SEC, + bucketStartTimeNs + 15 * NS_PER_SEC); + // Value for dimension, state key {{}, ON} + it++; + EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(android::view::DisplayStateEnum::DISPLAY_STATE_ON, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + EXPECT_TRUE(it->second.intervals[0].hasValue); + EXPECT_EQ(4, it->second.intervals[0].value.long_value); + assertConditionTimer(it->second.conditionTimer, true, 5 * NS_PER_SEC, + bucketStartTimeNs + 15 * NS_PER_SEC); + // Value for dimension, state key {{}, kStateUnknown} + it++; + EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + EXPECT_TRUE(it->second.intervals[0].hasValue); + EXPECT_EQ(2, it->second.intervals[0].value.long_value); + assertConditionTimer(it->second.conditionTimer, false, 5 * NS_PER_SEC, + bucketStartTimeNs + 5 * NS_PER_SEC); + + // Start dump report and check output. + ProtoOutputStream output; + std::set strSet; + valueProducer->onDumpReport(bucketStartTimeNs + 50 * NS_PER_SEC, + true /* include recent buckets */, true, NO_TIME_CONSTRAINTS, + &strSet, &output); + + StatsLogReport report = outputStreamToProto(&output); + EXPECT_TRUE(report.has_value_metrics()); + ASSERT_EQ(3, report.value_metrics().data_size()); + + // {{}, kStateUnknown} + auto data = report.value_metrics().data(0); + ASSERT_EQ(1, data.bucket_info_size()); + EXPECT_EQ(2, report.value_metrics().data(0).bucket_info(0).values(0).value_long()); + EXPECT_EQ(SCREEN_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); + EXPECT_TRUE(data.slice_by_state(0).has_value()); + EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, data.slice_by_state(0).value()); + EXPECT_EQ(5 * NS_PER_SEC, data.bucket_info(0).condition_true_nanos()); + + // {{}, ON} + data = report.value_metrics().data(1); + ASSERT_EQ(1, report.value_metrics().data(1).bucket_info_size()); + EXPECT_EQ(13, report.value_metrics().data(1).bucket_info(0).values(0).value_long()); + EXPECT_EQ(SCREEN_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); + EXPECT_TRUE(data.slice_by_state(0).has_value()); + EXPECT_EQ(android::view::DisplayStateEnum::DISPLAY_STATE_ON, data.slice_by_state(0).value()); + EXPECT_EQ(40 * NS_PER_SEC, data.bucket_info(0).condition_true_nanos()); + + // {{}, OFF} + data = report.value_metrics().data(2); + ASSERT_EQ(1, report.value_metrics().data(2).bucket_info_size()); + EXPECT_EQ(12, report.value_metrics().data(2).bucket_info(0).values(0).value_long()); + EXPECT_EQ(SCREEN_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); + EXPECT_TRUE(data.slice_by_state(0).has_value()); + EXPECT_EQ(android::view::DisplayStateEnum::DISPLAY_STATE_OFF, data.slice_by_state(0).value()); + EXPECT_EQ(5 * NS_PER_SEC, data.bucket_info(0).condition_true_nanos()); +} + +/* + * Test metric with sliced state with map + * - Increasing values + * - Using diff + * - Second field is value field + */ +TEST(ValueMetricProducerTest, TestSlicedStateWithMap) { + // Set up ValueMetricProducer. + ValueMetric metric = ValueMetricProducerTestHelper::createMetricWithState("SCREEN_STATE_ONOFF"); + sp pullerManager = new StrictMock(); + EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _)) + // ValueMetricProducer initialized. + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector>* data) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs); + data->clear(); + data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs, 3)); + return true; + })) + // Screen state change to ON. + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector>* data) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 5 * NS_PER_SEC); + data->clear(); + data->push_back( + CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 5 * NS_PER_SEC, 5)); + return true; + })) + // Screen state change to VR has no pull because it is in the same + // state group as ON. + + // Screen state change to ON has no pull because it is in the same + // state group as VR. + + // Screen state change to OFF. + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector>* data) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 15 * NS_PER_SEC); + data->clear(); + data->push_back(CreateRepeatedValueLogEvent( + tagId, bucketStartTimeNs + 15 * NS_PER_SEC, 21)); + return true; + })) + // Dump report requested. + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector>* data) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 50 * NS_PER_SEC); + data->clear(); + data->push_back(CreateRepeatedValueLogEvent( + tagId, bucketStartTimeNs + 50 * NS_PER_SEC, 30)); + return true; + })); + + const StateMap& stateMap = + CreateScreenStateOnOffMap(/*screen on id=*/321, /*screen off id=*/123); + const StateMap_StateGroup screenOnGroup = stateMap.group(0); + const StateMap_StateGroup screenOffGroup = stateMap.group(1); + + unordered_map> stateGroupMap; + for (auto group : stateMap.group()) { + for (auto value : group.value()) { + stateGroupMap[SCREEN_STATE_ATOM_ID][value] = group.group_id(); + } + } + + StateManager::getInstance().clear(); + sp valueProducer = + ValueMetricProducerTestHelper::createValueProducerWithState( + pullerManager, metric, {util::SCREEN_STATE_CHANGED}, stateGroupMap); + + // Set up StateManager and check that StateTrackers are initialized. + StateManager::getInstance().registerListener(SCREEN_STATE_ATOM_ID, valueProducer); + EXPECT_EQ(1, StateManager::getInstance().getStateTrackersCount()); + EXPECT_EQ(1, StateManager::getInstance().getListenersCount(SCREEN_STATE_ATOM_ID)); + + // Bucket status after metric initialized. + ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); + // Base for dimension key {} + auto it = valueProducer->mCurrentSlicedBucket.begin(); + auto itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); + EXPECT_TRUE(itBase->second.baseInfos[0].hasBase); + EXPECT_EQ(3, itBase->second.baseInfos[0].base.long_value); + EXPECT_TRUE(itBase->second.hasCurrentState); + ASSERT_EQ(1, itBase->second.currentState.getValues().size()); + EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, + itBase->second.currentState.getValues()[0].mValue.int_value); + // Value for dimension, state key {{}, {kStateUnknown}} + EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + EXPECT_FALSE(it->second.intervals[0].hasValue); + assertConditionTimer(it->second.conditionTimer, true, 0, bucketStartTimeNs); + + // Bucket status after screen state change kStateUnknown->ON. + auto screenEvent = CreateScreenStateChangedEvent( + bucketStartTimeNs + 5 * NS_PER_SEC, android::view::DisplayStateEnum::DISPLAY_STATE_ON); + StateManager::getInstance().onLogEvent(*screenEvent); + ASSERT_EQ(2UL, valueProducer->mCurrentSlicedBucket.size()); + // Base for dimension key {} + it = valueProducer->mCurrentSlicedBucket.begin(); + itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); + EXPECT_TRUE(itBase->second.baseInfos[0].hasBase); + EXPECT_EQ(5, itBase->second.baseInfos[0].base.long_value); + EXPECT_TRUE(itBase->second.hasCurrentState); + ASSERT_EQ(1, itBase->second.currentState.getValues().size()); + EXPECT_EQ(screenOnGroup.group_id(), + itBase->second.currentState.getValues()[0].mValue.long_value); + // Value for dimension, state key {{}, ON GROUP} + EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(screenOnGroup.group_id(), + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, true, 0, bucketStartTimeNs + 5 * NS_PER_SEC); + // Value for dimension, state key {{}, kStateUnknown} + it++; + EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + EXPECT_TRUE(it->second.intervals[0].hasValue); + EXPECT_EQ(2, it->second.intervals[0].value.long_value); + assertConditionTimer(it->second.conditionTimer, false, 5 * NS_PER_SEC, + bucketStartTimeNs + 5 * NS_PER_SEC); + + // Bucket status after screen state change ON->VR. + // Both ON and VR are in the same state group, so the base should not change. + screenEvent = CreateScreenStateChangedEvent(bucketStartTimeNs + 10 * NS_PER_SEC, + android::view::DisplayStateEnum::DISPLAY_STATE_VR); + StateManager::getInstance().onLogEvent(*screenEvent); + ASSERT_EQ(2UL, valueProducer->mCurrentSlicedBucket.size()); + // Base for dimension key {} + it = valueProducer->mCurrentSlicedBucket.begin(); + itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); + EXPECT_TRUE(itBase->second.baseInfos[0].hasBase); + EXPECT_EQ(5, itBase->second.baseInfos[0].base.long_value); + EXPECT_TRUE(itBase->second.hasCurrentState); + ASSERT_EQ(1, itBase->second.currentState.getValues().size()); + EXPECT_EQ(screenOnGroup.group_id(), + itBase->second.currentState.getValues()[0].mValue.int_value); + // Value for dimension, state key {{}, ON GROUP} + EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(screenOnGroup.group_id(), + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, true, 0, bucketStartTimeNs + 5 * NS_PER_SEC); + // Value for dimension, state key {{}, kStateUnknown} + it++; + EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + EXPECT_TRUE(it->second.intervals[0].hasValue); + EXPECT_EQ(2, it->second.intervals[0].value.long_value); + assertConditionTimer(it->second.conditionTimer, false, 5 * NS_PER_SEC, + bucketStartTimeNs + 5 * NS_PER_SEC); + + // Bucket status after screen state change VR->ON. + // Both ON and VR are in the same state group, so the base should not change. + screenEvent = CreateScreenStateChangedEvent(bucketStartTimeNs + 12 * NS_PER_SEC, + android::view::DisplayStateEnum::DISPLAY_STATE_ON); + StateManager::getInstance().onLogEvent(*screenEvent); + ASSERT_EQ(2UL, valueProducer->mCurrentSlicedBucket.size()); + // Base for dimension key {} + it = valueProducer->mCurrentSlicedBucket.begin(); + itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); + EXPECT_TRUE(itBase->second.baseInfos[0].hasBase); + EXPECT_EQ(5, itBase->second.baseInfos[0].base.long_value); + EXPECT_TRUE(itBase->second.hasCurrentState); + ASSERT_EQ(1, itBase->second.currentState.getValues().size()); + EXPECT_EQ(screenOnGroup.group_id(), + itBase->second.currentState.getValues()[0].mValue.int_value); + // Value for dimension, state key {{}, ON GROUP} + EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(screenOnGroup.group_id(), + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, true, 0, bucketStartTimeNs + 5 * NS_PER_SEC); + // Value for dimension, state key {{}, kStateUnknown} + it++; + EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + EXPECT_TRUE(it->second.intervals[0].hasValue); + EXPECT_EQ(2, it->second.intervals[0].value.long_value); + assertConditionTimer(it->second.conditionTimer, false, 5 * NS_PER_SEC, + bucketStartTimeNs + 5 * NS_PER_SEC); + + // Bucket status after screen state change VR->OFF. + screenEvent = CreateScreenStateChangedEvent(bucketStartTimeNs + 15 * NS_PER_SEC, + android::view::DisplayStateEnum::DISPLAY_STATE_OFF); + StateManager::getInstance().onLogEvent(*screenEvent); + ASSERT_EQ(3UL, valueProducer->mCurrentSlicedBucket.size()); + // Base for dimension key {} + it = valueProducer->mCurrentSlicedBucket.begin(); + itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); + EXPECT_TRUE(itBase->second.baseInfos[0].hasBase); + EXPECT_EQ(21, itBase->second.baseInfos[0].base.long_value); + EXPECT_TRUE(itBase->second.hasCurrentState); + ASSERT_EQ(1, itBase->second.currentState.getValues().size()); + EXPECT_EQ(screenOffGroup.group_id(), + itBase->second.currentState.getValues()[0].mValue.int_value); + // Value for dimension, state key {{}, OFF GROUP} + EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(screenOffGroup.group_id(), + it->first.getStateValuesKey().getValues()[0].mValue.long_value); + assertConditionTimer(it->second.conditionTimer, true, 0, bucketStartTimeNs + 15 * NS_PER_SEC); + // Value for dimension, state key {{}, ON GROUP} + it++; + EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(screenOnGroup.group_id(), + it->first.getStateValuesKey().getValues()[0].mValue.long_value); + EXPECT_TRUE(it->second.intervals[0].hasValue); + EXPECT_EQ(16, it->second.intervals[0].value.long_value); + assertConditionTimer(it->second.conditionTimer, false, 10 * NS_PER_SEC, + bucketStartTimeNs + 15 * NS_PER_SEC); + // Value for dimension, state key {{}, kStateUnknown} + it++; + EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + EXPECT_TRUE(it->second.intervals[0].hasValue); + EXPECT_EQ(2, it->second.intervals[0].value.long_value); + assertConditionTimer(it->second.conditionTimer, false, 5 * NS_PER_SEC, + bucketStartTimeNs + 5 * NS_PER_SEC); + + // Start dump report and check output. + ProtoOutputStream output; + std::set strSet; + valueProducer->onDumpReport(bucketStartTimeNs + 50 * NS_PER_SEC, + true /* include recent buckets */, true, NO_TIME_CONSTRAINTS, + &strSet, &output); + + StatsLogReport report = outputStreamToProto(&output); + EXPECT_TRUE(report.has_value_metrics()); + ASSERT_EQ(3, report.value_metrics().data_size()); + + // {{}, kStateUnknown} + auto data = report.value_metrics().data(0); + ASSERT_EQ(1, data.bucket_info_size()); + EXPECT_EQ(2, report.value_metrics().data(0).bucket_info(0).values(0).value_long()); + EXPECT_EQ(SCREEN_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); + EXPECT_TRUE(data.slice_by_state(0).has_value()); + EXPECT_EQ(-1 /*StateTracker::kStateUnknown*/, data.slice_by_state(0).value()); + EXPECT_EQ(5 * NS_PER_SEC, data.bucket_info(0).condition_true_nanos()); + + // {{}, ON GROUP} + data = report.value_metrics().data(1); + ASSERT_EQ(1, report.value_metrics().data(1).bucket_info_size()); + EXPECT_EQ(16, report.value_metrics().data(1).bucket_info(0).values(0).value_long()); + EXPECT_EQ(SCREEN_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); + EXPECT_TRUE(data.slice_by_state(0).has_group_id()); + EXPECT_EQ(screenOnGroup.group_id(), data.slice_by_state(0).group_id()); + EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).condition_true_nanos()); + + // {{}, OFF GROUP} + data = report.value_metrics().data(2); + ASSERT_EQ(1, report.value_metrics().data(2).bucket_info_size()); + EXPECT_EQ(9, report.value_metrics().data(2).bucket_info(0).values(0).value_long()); + EXPECT_EQ(SCREEN_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); + EXPECT_TRUE(data.slice_by_state(0).has_group_id()); + EXPECT_EQ(screenOffGroup.group_id(), data.slice_by_state(0).group_id()); + EXPECT_EQ(35 * NS_PER_SEC, data.bucket_info(0).condition_true_nanos()); +} + +/* + * Test metric that slices by state with a primary field and has dimensions + * - Increasing values + * - Using diff + * - Second field is value field + */ +TEST(ValueMetricProducerTest, TestSlicedStateWithPrimaryField_WithDimensions) { + // Set up ValueMetricProducer. + ValueMetric metric = ValueMetricProducerTestHelper::createMetricWithState("UID_PROCESS_STATE"); + metric.mutable_dimensions_in_what()->set_field(tagId); + metric.mutable_dimensions_in_what()->add_child()->set_field(1); + + MetricStateLink* stateLink = metric.add_state_link(); + stateLink->set_state_atom_id(UID_PROCESS_STATE_ATOM_ID); + auto fieldsInWhat = stateLink->mutable_fields_in_what(); + *fieldsInWhat = CreateDimensions(tagId, {1 /* uid */}); + auto fieldsInState = stateLink->mutable_fields_in_state(); + *fieldsInState = CreateDimensions(UID_PROCESS_STATE_ATOM_ID, {1 /* uid */}); + + /* + NOTE: "1" denotes uid 1 and "2" denotes uid 2. + bucket # 1 bucket # 2 + 10 20 30 40 50 60 70 80 90 100 110 120 (seconds) + |------------------------------------------|---------------------------------|-- + + (kStateUnknown) + 1 + |-------------| + 20 + + 2 + |----------------------------| + 40 + + (FOREGROUND) + 1 1 + |----------------------------|-------------| |------| + 40 20 10 + + + (BACKGROUND) + 1 + |------------| + 20 + 2 + |-------------|---------------------------------| + 20 50 + */ + sp pullerManager = new StrictMock(); + EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _)) + // ValueMetricProducer initialized. + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector>* data) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs); + data->clear(); + data->push_back(CreateTwoValueLogEvent(tagId, bucketStartTimeNs, 2 /*uid*/, 7)); + data->push_back(CreateTwoValueLogEvent(tagId, bucketStartTimeNs, 1 /*uid*/, 3)); + return true; + })) + // Uid 1 process state change from kStateUnknown -> Foreground + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector>* data) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 20 * NS_PER_SEC); + data->clear(); + data->push_back(CreateTwoValueLogEvent(tagId, bucketStartTimeNs + 20 * NS_PER_SEC, + 1 /*uid*/, 6)); + + // This event should be skipped. + data->push_back(CreateTwoValueLogEvent(tagId, bucketStartTimeNs + 20 * NS_PER_SEC, + 2 /*uid*/, 8)); + return true; + })) + // Uid 2 process state change from kStateUnknown -> Background + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector>* data) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 40 * NS_PER_SEC); + data->clear(); + data->push_back(CreateTwoValueLogEvent(tagId, bucketStartTimeNs + 40 * NS_PER_SEC, + 2 /*uid*/, 9)); + + // This event should be skipped. + data->push_back(CreateTwoValueLogEvent(tagId, bucketStartTimeNs + 40 * NS_PER_SEC, + 1 /*uid*/, 12)); + return true; + })) + // Uid 1 process state change from Foreground -> Background + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector>* data) { + EXPECT_EQ(eventTimeNs, bucket2StartTimeNs + 20 * NS_PER_SEC); + data->clear(); + data->push_back(CreateTwoValueLogEvent(tagId, bucket2StartTimeNs + 20 * NS_PER_SEC, + 1 /*uid*/, 13)); + + // This event should be skipped. + data->push_back(CreateTwoValueLogEvent(tagId, bucket2StartTimeNs + 20 * NS_PER_SEC, + 2 /*uid*/, 11)); + return true; + })) + // Uid 1 process state change from Background -> Foreground + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector>* data) { + EXPECT_EQ(eventTimeNs, bucket2StartTimeNs + 40 * NS_PER_SEC); + data->clear(); + data->push_back(CreateTwoValueLogEvent(tagId, bucket2StartTimeNs + 40 * NS_PER_SEC, + 1 /*uid*/, 17)); + + // This event should be skipped. + data->push_back(CreateTwoValueLogEvent(tagId, bucket2StartTimeNs + 40 * NS_PER_SEC, + 2 /*uid */, 15)); + return true; + })) + // Dump report pull. + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector>* data) { + EXPECT_EQ(eventTimeNs, bucket2StartTimeNs + 50 * NS_PER_SEC); + data->clear(); + data->push_back(CreateTwoValueLogEvent(tagId, bucket2StartTimeNs + 50 * NS_PER_SEC, + 2 /*uid*/, 20)); + data->push_back(CreateTwoValueLogEvent(tagId, bucket2StartTimeNs + 50 * NS_PER_SEC, + 1 /*uid*/, 21)); + return true; + })); + + StateManager::getInstance().clear(); + sp valueProducer = + ValueMetricProducerTestHelper::createValueProducerWithState( + pullerManager, metric, {UID_PROCESS_STATE_ATOM_ID}, {}); + + // Set up StateManager and check that StateTrackers are initialized. + StateManager::getInstance().registerListener(UID_PROCESS_STATE_ATOM_ID, valueProducer); + EXPECT_EQ(1, StateManager::getInstance().getStateTrackersCount()); + EXPECT_EQ(1, StateManager::getInstance().getListenersCount(UID_PROCESS_STATE_ATOM_ID)); + + // Bucket status after metric initialized. + ASSERT_EQ(2UL, valueProducer->mCurrentSlicedBucket.size()); + // Base for dimension key {uid 1}. + auto it = valueProducer->mCurrentSlicedBucket.begin(); + auto itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); + EXPECT_TRUE(itBase->second.baseInfos[0].hasBase); + EXPECT_EQ(3, itBase->second.baseInfos[0].base.long_value); + EXPECT_TRUE(itBase->second.hasCurrentState); + ASSERT_EQ(1, itBase->second.currentState.getValues().size()); + EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, + itBase->second.currentState.getValues()[0].mValue.int_value); + // Value for dimension, state key {{uid 1}, kStateUnknown} + ASSERT_EQ(1, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + EXPECT_FALSE(it->second.intervals[0].hasValue); + assertConditionTimer(it->second.conditionTimer, true, 0, bucketStartTimeNs); + // Base for dimension key {uid 2} + it++; + itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); + EXPECT_TRUE(itBase->second.baseInfos[0].hasBase); + EXPECT_EQ(7, itBase->second.baseInfos[0].base.long_value); + EXPECT_TRUE(itBase->second.hasCurrentState); + ASSERT_EQ(1, itBase->second.currentState.getValues().size()); + EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, + itBase->second.currentState.getValues()[0].mValue.int_value); + // Value for dimension, state key {{uid 2}, kStateUnknown} + ASSERT_EQ(1, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(2, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + EXPECT_FALSE(it->second.intervals[0].hasValue); + assertConditionTimer(it->second.conditionTimer, true, 0, bucketStartTimeNs); + + // Bucket status after uid 1 process state change kStateUnknown -> Foreground. + auto uidProcessEvent = + CreateUidProcessStateChangedEvent(bucketStartTimeNs + 20 * NS_PER_SEC, 1 /* uid */, + android::app::PROCESS_STATE_IMPORTANT_FOREGROUND); + StateManager::getInstance().onLogEvent(*uidProcessEvent); + ASSERT_EQ(3UL, valueProducer->mCurrentSlicedBucket.size()); + // Base for dimension key {uid 1}. + it = valueProducer->mCurrentSlicedBucket.begin(); + itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); + EXPECT_TRUE(itBase->second.baseInfos[0].hasBase); + EXPECT_EQ(6, itBase->second.baseInfos[0].base.long_value); + EXPECT_TRUE(itBase->second.hasCurrentState); + ASSERT_EQ(1, itBase->second.currentState.getValues().size()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_FOREGROUND, + itBase->second.currentState.getValues()[0].mValue.int_value); + // Value for key {uid 1, kStateUnknown}. + ASSERT_EQ(1, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + EXPECT_TRUE(it->second.intervals[0].hasValue); + EXPECT_EQ(3, it->second.intervals[0].value.long_value); + assertConditionTimer(it->second.conditionTimer, false, 20 * NS_PER_SEC, + bucketStartTimeNs + 20 * NS_PER_SEC); + // Value for key {uid 1, FOREGROUND}. + it++; + ASSERT_EQ(1, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_FOREGROUND, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, true, 0, bucketStartTimeNs + 20 * NS_PER_SEC); + + // Base for dimension key {uid 2}. + it++; + itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); + EXPECT_TRUE(itBase->second.baseInfos[0].hasBase); + EXPECT_EQ(7, itBase->second.baseInfos[0].base.long_value); + EXPECT_TRUE(itBase->second.hasCurrentState); + ASSERT_EQ(1, itBase->second.currentState.getValues().size()); + EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, + itBase->second.currentState.getValues()[0].mValue.int_value); + // Value for key {uid 2, kStateUnknown}. + ASSERT_EQ(1, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(2, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + EXPECT_FALSE(it->second.intervals[0].hasValue); + assertConditionTimer(it->second.conditionTimer, true, 0, bucketStartTimeNs); + + // Bucket status after uid 2 process state change kStateUnknown -> Background. + uidProcessEvent = + CreateUidProcessStateChangedEvent(bucketStartTimeNs + 40 * NS_PER_SEC, 2 /* uid */, + android::app::PROCESS_STATE_IMPORTANT_BACKGROUND); + StateManager::getInstance().onLogEvent(*uidProcessEvent); + ASSERT_EQ(4UL, valueProducer->mCurrentSlicedBucket.size()); + // Base for dimension key {uid 2}. + it = valueProducer->mCurrentSlicedBucket.begin(); + itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); + EXPECT_TRUE(itBase->second.baseInfos[0].hasBase); + EXPECT_EQ(9, itBase->second.baseInfos[0].base.long_value); + EXPECT_TRUE(itBase->second.hasCurrentState); + ASSERT_EQ(1, itBase->second.currentState.getValues().size()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND, + itBase->second.currentState.getValues()[0].mValue.int_value); + // Value for key {uid 2, BACKGROUND}. + ASSERT_EQ(1, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(2, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, true, 0, bucketStartTimeNs + 40 * NS_PER_SEC); + + // Base for dimension key {uid 1}. + it++; + itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); + EXPECT_TRUE(itBase->second.baseInfos[0].hasBase); + EXPECT_EQ(6, itBase->second.baseInfos[0].base.long_value); + EXPECT_TRUE(itBase->second.hasCurrentState); + ASSERT_EQ(1, itBase->second.currentState.getValues().size()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_FOREGROUND, + itBase->second.currentState.getValues()[0].mValue.int_value); + // Value for key {uid 1, kStateUnknown}. + ASSERT_EQ(1, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + EXPECT_TRUE(it->second.intervals[0].hasValue); + EXPECT_EQ(3, it->second.intervals[0].value.long_value); + assertConditionTimer(it->second.conditionTimer, false, 20 * NS_PER_SEC, + bucketStartTimeNs + 20 * NS_PER_SEC); + + // Value for key {uid 1, FOREGROUND}. + it++; + ASSERT_EQ(1, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_FOREGROUND, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, true, 0, bucketStartTimeNs + 20 * NS_PER_SEC); + + // Value for key {uid 2, kStateUnknown} + it++; + ASSERT_EQ(1, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(2, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + EXPECT_TRUE(it->second.intervals[0].hasValue); + EXPECT_EQ(2, it->second.intervals[0].value.long_value); + assertConditionTimer(it->second.conditionTimer, false, 40 * NS_PER_SEC, + bucketStartTimeNs + 40 * NS_PER_SEC); + + // Pull at end of first bucket. + vector> allData; + allData.push_back(CreateTwoValueLogEvent(tagId, bucket2StartTimeNs, 1 /*uid*/, 10)); + allData.push_back(CreateTwoValueLogEvent(tagId, bucket2StartTimeNs, 2 /*uid*/, 15)); + valueProducer->onDataPulled(allData, /** succeeds */ true, bucket2StartTimeNs + 1); + + // Buckets flushed after end of first bucket. + // None of the buckets should have a value. + ASSERT_EQ(4UL, valueProducer->mCurrentSlicedBucket.size()); + ASSERT_EQ(4UL, valueProducer->mPastBuckets.size()); + ASSERT_EQ(2UL, valueProducer->mCurrentBaseInfo.size()); + // Base for dimension key {uid 2}. + it = valueProducer->mCurrentSlicedBucket.begin(); + EXPECT_EQ(2, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); + EXPECT_TRUE(itBase->second.baseInfos[0].hasBase); + EXPECT_EQ(15, itBase->second.baseInfos[0].base.long_value); + EXPECT_TRUE(itBase->second.hasCurrentState); + ASSERT_EQ(1, itBase->second.currentState.getValues().size()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND, + itBase->second.currentState.getValues()[0].mValue.int_value); + // Value for key {uid 2, BACKGROUND}. + ASSERT_EQ(1, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(2, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + EXPECT_FALSE(it->second.intervals[0].hasValue); + assertConditionTimer(it->second.conditionTimer, true, 0, bucket2StartTimeNs); + EXPECT_EQ(20 * NS_PER_SEC, valueProducer->mPastBuckets[it->first][0].mConditionTrueNs); + + // Base for dimension key {uid 1} + it++; + itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); + EXPECT_TRUE(itBase->second.baseInfos[0].hasBase); + EXPECT_EQ(10, itBase->second.baseInfos[0].base.long_value); + EXPECT_TRUE(itBase->second.hasCurrentState); + ASSERT_EQ(1, itBase->second.currentState.getValues().size()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_FOREGROUND, + itBase->second.currentState.getValues()[0].mValue.int_value); + // Value for key {uid 1, kStateUnknown} + ASSERT_EQ(1, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(-1 /* kStateTracker::kUnknown */, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + EXPECT_FALSE(it->second.intervals[0].hasValue); + assertConditionTimer(it->second.conditionTimer, false, 0, bucketStartTimeNs + 20 * NS_PER_SEC); + EXPECT_EQ(20 * NS_PER_SEC, valueProducer->mPastBuckets[it->first][0].mConditionTrueNs); + + // Value for key {uid 1, FOREGROUND} + it++; + ASSERT_EQ(1, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_FOREGROUND, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + EXPECT_FALSE(it->second.intervals[0].hasValue); + assertConditionTimer(it->second.conditionTimer, true, 0, bucket2StartTimeNs); + EXPECT_EQ(40 * NS_PER_SEC, valueProducer->mPastBuckets[it->first][0].mConditionTrueNs); + + // Value for key {uid 2, kStateUnknown} + it++; + ASSERT_EQ(1, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(2, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(-1 /* kStateTracker::kUnknown */, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + EXPECT_FALSE(it->second.intervals[0].hasValue); + assertConditionTimer(it->second.conditionTimer, false, 0, bucketStartTimeNs + 40 * NS_PER_SEC); + EXPECT_EQ(40 * NS_PER_SEC, valueProducer->mPastBuckets[it->first][0].mConditionTrueNs); + + // Bucket status after uid 1 process state change from Foreground -> Background. + uidProcessEvent = + CreateUidProcessStateChangedEvent(bucket2StartTimeNs + 20 * NS_PER_SEC, 1 /* uid */, + android::app::PROCESS_STATE_IMPORTANT_BACKGROUND); + StateManager::getInstance().onLogEvent(*uidProcessEvent); + + ASSERT_EQ(5UL, valueProducer->mCurrentSlicedBucket.size()); + ASSERT_EQ(4UL, valueProducer->mPastBuckets.size()); + ASSERT_EQ(2UL, valueProducer->mCurrentBaseInfo.size()); + // Base for dimension key {uid 2}. + it = valueProducer->mCurrentSlicedBucket.begin(); + itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); + EXPECT_TRUE(itBase->second.baseInfos[0].hasBase); + EXPECT_EQ(15, itBase->second.baseInfos[0].base.long_value); + EXPECT_TRUE(itBase->second.hasCurrentState); + ASSERT_EQ(1, itBase->second.currentState.getValues().size()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND, + itBase->second.currentState.getValues()[0].mValue.int_value); + // Value for key {uid 2, BACKGROUND}. + ASSERT_EQ(1, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(2, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + EXPECT_FALSE(it->second.intervals[0].hasValue); + assertConditionTimer(it->second.conditionTimer, true, 0, bucket2StartTimeNs); + + // Base for dimension key {uid 1} + it++; + itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); + EXPECT_TRUE(itBase->second.baseInfos[0].hasBase); + EXPECT_EQ(13, itBase->second.baseInfos[0].base.long_value); + EXPECT_TRUE(itBase->second.hasCurrentState); + ASSERT_EQ(1, itBase->second.currentState.getValues().size()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND, + itBase->second.currentState.getValues()[0].mValue.int_value); + // Value for key {uid 1, kStateUnknown} + ASSERT_EQ(1, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + EXPECT_FALSE(it->second.intervals[0].hasValue); + assertConditionTimer(it->second.conditionTimer, false, 0, bucketStartTimeNs + 20 * NS_PER_SEC); + + // Value for key {uid 1, BACKGROUND} + it++; + ASSERT_EQ(1, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, true, 0, bucket2StartTimeNs + 20 * NS_PER_SEC); + + // Value for key {uid 1, FOREGROUND} + it++; + ASSERT_EQ(1, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_FOREGROUND, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + EXPECT_TRUE(it->second.intervals[0].hasValue); + EXPECT_EQ(3, it->second.intervals[0].value.long_value); + assertConditionTimer(it->second.conditionTimer, false, 20 * NS_PER_SEC, + bucket2StartTimeNs + 20 * NS_PER_SEC); + + // Value for key {uid 2, kStateUnknown} + it++; + ASSERT_EQ(1, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(2, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + EXPECT_FALSE(it->second.intervals[0].hasValue); + assertConditionTimer(it->second.conditionTimer, false, 0, bucketStartTimeNs + 40 * NS_PER_SEC); + + // Bucket status after uid 1 process state change Background->Foreground. + uidProcessEvent = + CreateUidProcessStateChangedEvent(bucket2StartTimeNs + 40 * NS_PER_SEC, 1 /* uid */, + android::app::PROCESS_STATE_IMPORTANT_FOREGROUND); + StateManager::getInstance().onLogEvent(*uidProcessEvent); + + ASSERT_EQ(5UL, valueProducer->mCurrentSlicedBucket.size()); + ASSERT_EQ(2UL, valueProducer->mCurrentBaseInfo.size()); + // Base for dimension key {uid 2} + it = valueProducer->mCurrentSlicedBucket.begin(); + itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); + EXPECT_TRUE(itBase->second.baseInfos[0].hasBase); + EXPECT_EQ(15, itBase->second.baseInfos[0].base.long_value); + EXPECT_TRUE(itBase->second.hasCurrentState); + ASSERT_EQ(1, itBase->second.currentState.getValues().size()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND, + itBase->second.currentState.getValues()[0].mValue.int_value); + // Value for key {uid 2, BACKGROUND} + ASSERT_EQ(1, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(2, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + EXPECT_FALSE(it->second.intervals[0].hasValue); + assertConditionTimer(it->second.conditionTimer, true, 0, bucket2StartTimeNs); + + // Base for dimension key {uid 1} + it++; + itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); + EXPECT_TRUE(itBase->second.baseInfos[0].hasBase); + EXPECT_EQ(17, itBase->second.baseInfos[0].base.long_value); + EXPECT_TRUE(itBase->second.hasCurrentState); + ASSERT_EQ(1, itBase->second.currentState.getValues().size()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_FOREGROUND, + itBase->second.currentState.getValues()[0].mValue.int_value); + // Value for key {uid 1, kStateUnknown} + ASSERT_EQ(1, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + EXPECT_FALSE(it->second.intervals[0].hasValue); + assertConditionTimer(it->second.conditionTimer, false, 0, bucketStartTimeNs + 20 * NS_PER_SEC); + + // Value for key {uid 1, BACKGROUND} + it++; + ASSERT_EQ(1, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + EXPECT_TRUE(it->second.intervals[0].hasValue); + EXPECT_EQ(4, it->second.intervals[0].value.long_value); + assertConditionTimer(it->second.conditionTimer, false, 20 * NS_PER_SEC, + bucket2StartTimeNs + 40 * NS_PER_SEC); + + // Value for key {uid 1, FOREGROUND} + it++; + ASSERT_EQ(1, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_FOREGROUND, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + EXPECT_TRUE(it->second.intervals[0].hasValue); + EXPECT_EQ(3, it->second.intervals[0].value.long_value); + assertConditionTimer(it->second.conditionTimer, true, 20 * NS_PER_SEC, + bucket2StartTimeNs + 40 * NS_PER_SEC); + + // Value for key {uid 2, kStateUnknown} + it++; + ASSERT_EQ(1, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(2, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, false, 0, bucketStartTimeNs + 40 * NS_PER_SEC); + + // Start dump report and check output. + ProtoOutputStream output; + std::set strSet; + valueProducer->onDumpReport(bucket2StartTimeNs + 50 * NS_PER_SEC, + true /* include recent buckets */, true, NO_TIME_CONSTRAINTS, + &strSet, &output); + + StatsLogReport report = outputStreamToProto(&output); + EXPECT_TRUE(report.has_value_metrics()); + ASSERT_EQ(5, report.value_metrics().data_size()); + + // {uid 1, BACKGROUND} + auto data = report.value_metrics().data(0); + ASSERT_EQ(1, data.bucket_info_size()); + EXPECT_EQ(4, report.value_metrics().data(0).bucket_info(0).values(0).value_long()); + EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); + EXPECT_TRUE(data.slice_by_state(0).has_value()); + EXPECT_EQ(android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_BACKGROUND, + data.slice_by_state(0).value()); + EXPECT_EQ(20 * NS_PER_SEC, data.bucket_info(0).condition_true_nanos()); + + // {uid 2, kStateUnknown} + data = report.value_metrics().data(1); + ASSERT_EQ(1, report.value_metrics().data(1).bucket_info_size()); + EXPECT_EQ(2, report.value_metrics().data(1).bucket_info(0).values(0).value_long()); + EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); + EXPECT_TRUE(data.slice_by_state(0).has_value()); + EXPECT_EQ(-1 /*StateTracker::kStateUnknown*/, data.slice_by_state(0).value()); + EXPECT_EQ(40 * NS_PER_SEC, data.bucket_info(0).condition_true_nanos()); + + // {uid 1, FOREGROUND} + data = report.value_metrics().data(2); + EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); + EXPECT_TRUE(data.slice_by_state(0).has_value()); + EXPECT_EQ(android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_FOREGROUND, + data.slice_by_state(0).value()); + ASSERT_EQ(2, report.value_metrics().data(2).bucket_info_size()); + EXPECT_EQ(4, report.value_metrics().data(2).bucket_info(0).values(0).value_long()); + EXPECT_EQ(7, report.value_metrics().data(2).bucket_info(1).values(0).value_long()); + EXPECT_EQ(40 * NS_PER_SEC, data.bucket_info(0).condition_true_nanos()); + EXPECT_EQ(30 * NS_PER_SEC, data.bucket_info(1).condition_true_nanos()); + + // {uid 1, kStateUnknown} + data = report.value_metrics().data(3); + ASSERT_EQ(1, report.value_metrics().data(3).bucket_info_size()); + EXPECT_EQ(3, report.value_metrics().data(3).bucket_info(0).values(0).value_long()); + EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); + EXPECT_TRUE(data.slice_by_state(0).has_value()); + EXPECT_EQ(-1 /*StateTracker::kStateUnknown*/, data.slice_by_state(0).value()); + EXPECT_EQ(20 * NS_PER_SEC, data.bucket_info(0).condition_true_nanos()); + + // {uid 2, BACKGROUND} + data = report.value_metrics().data(4); + EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); + EXPECT_TRUE(data.slice_by_state(0).has_value()); + EXPECT_EQ(android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_BACKGROUND, + data.slice_by_state(0).value()); + ASSERT_EQ(2, report.value_metrics().data(4).bucket_info_size()); + EXPECT_EQ(6, report.value_metrics().data(4).bucket_info(0).values(0).value_long()); + EXPECT_EQ(5, report.value_metrics().data(4).bucket_info(1).values(0).value_long()); + EXPECT_EQ(20 * NS_PER_SEC, data.bucket_info(0).condition_true_nanos()); + EXPECT_EQ(50 * NS_PER_SEC, data.bucket_info(1).condition_true_nanos()); +} + +/* + * Test slicing condition_true_nanos by state for metric that slices by state when data is not + * present in pulled data during a state change. + */ +TEST(ValueMetricProducerTest, TestSlicedStateWithMissingDataInStateChange) { + // Set up ValueMetricProducer. + ValueMetric metric = + ValueMetricProducerTestHelper::createMetricWithState("BATTERY_SAVER_MODE_STATE"); + sp pullerManager = new StrictMock(); + /* + NOTE: "-" means that the data was not present in the pulled data. + + bucket # 1 + 10 20 30 40 50 60 (seconds) + |-------------------------------------------------------|-- + x (kStateUnknown) + |-----------| + 10 + + x x (ON) + |---------------------| |-----------| + 20 10 + + - (OFF) + */ + EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _)) + // ValueMetricProducer initialized. + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector>* data) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs); + data->clear(); + data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs, 3)); + return true; + })) + // Battery saver mode state changed to ON. + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector>* data) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 10 * NS_PER_SEC); + data->clear(); + data->push_back( + CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 10 * NS_PER_SEC, 5)); + return true; + })) + // Battery saver mode state changed to OFF but data for dimension key {} is not present + // in the pulled data. + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector>* data) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 30 * NS_PER_SEC); + data->clear(); + return true; + })) + // Battery saver mode state changed to ON. + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector>* data) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 40 * NS_PER_SEC); + data->clear(); + data->push_back( + CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 40 * NS_PER_SEC, 7)); + return true; + })) + // Dump report pull. + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector>* data) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 50 * NS_PER_SEC); + data->clear(); + data->push_back(CreateRepeatedValueLogEvent( + tagId, bucketStartTimeNs + 50 * NS_PER_SEC, 15)); + return true; + })); + + StateManager::getInstance().clear(); + sp valueProducer = + ValueMetricProducerTestHelper::createValueProducerWithState( + pullerManager, metric, {util::BATTERY_SAVER_MODE_STATE_CHANGED}, {}); + EXPECT_EQ(1, valueProducer->mSlicedStateAtoms.size()); + + // Set up StateManager and check that StateTrackers are initialized. + StateManager::getInstance().registerListener(util::BATTERY_SAVER_MODE_STATE_CHANGED, + valueProducer); + EXPECT_EQ(1, StateManager::getInstance().getStateTrackersCount()); + EXPECT_EQ(1, StateManager::getInstance().getListenersCount( + util::BATTERY_SAVER_MODE_STATE_CHANGED)); + + // Bucket status after metric initialized. + ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); + // Base for dimension key {} + auto it = valueProducer->mCurrentSlicedBucket.begin(); + auto itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); + EXPECT_TRUE(itBase->second.hasCurrentState); + ASSERT_EQ(1, itBase->second.currentState.getValues().size()); + EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, + itBase->second.currentState.getValues()[0].mValue.int_value); + // Value for dimension, state key {{}, kStateUnknown} + EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, true, 0, bucketStartTimeNs); + + // Bucket status after battery saver mode ON event. + unique_ptr batterySaverOnEvent = + CreateBatterySaverOnEvent(/*timestamp=*/bucketStartTimeNs + 10 * NS_PER_SEC); + StateManager::getInstance().onLogEvent(*batterySaverOnEvent); + + // Base for dimension key {} + + ASSERT_EQ(1UL, valueProducer->mCurrentBaseInfo.size()); + ASSERT_EQ(2UL, valueProducer->mCurrentSlicedBucket.size()); + it = valueProducer->mCurrentSlicedBucket.begin(); + itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); + EXPECT_TRUE(itBase->second.hasCurrentState); + ASSERT_EQ(1, itBase->second.currentState.getValues().size()); + EXPECT_EQ(BatterySaverModeStateChanged::ON, + itBase->second.currentState.getValues()[0].mValue.int_value); + // Value for key {{}, ON} + EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(BatterySaverModeStateChanged::ON, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, true, 0, bucketStartTimeNs + 10 * NS_PER_SEC); + + // Value for key {{}, -1} + it++; + EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(-1 /*StateTracker::kUnknown*/, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, false, 10 * NS_PER_SEC, + bucketStartTimeNs + 10 * NS_PER_SEC); + + // Bucket status after battery saver mode OFF event which is not present + // in the pulled data. + unique_ptr batterySaverOffEvent = + CreateBatterySaverOffEvent(/*timestamp=*/bucketStartTimeNs + 30 * NS_PER_SEC); + StateManager::getInstance().onLogEvent(*batterySaverOffEvent); + + // Base for dimension key {} + ASSERT_EQ(1UL, valueProducer->mCurrentBaseInfo.size()); + ASSERT_EQ(2UL, valueProducer->mCurrentSlicedBucket.size()); + it = valueProducer->mCurrentSlicedBucket.begin(); + itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); + EXPECT_FALSE(itBase->second.hasCurrentState); + ASSERT_EQ(1, itBase->second.currentState.getValues().size()); + EXPECT_EQ(BatterySaverModeStateChanged::ON, + itBase->second.currentState.getValues()[0].mValue.int_value); + // Value for key {{}, ON} + EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(BatterySaverModeStateChanged::ON, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, false, 20 * NS_PER_SEC, + bucketStartTimeNs + 30 * NS_PER_SEC); + + // Value for key {{}, -1} + it++; + EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(-1 /*StateTracker::kUnknown*/, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, false, 10 * NS_PER_SEC, + bucketStartTimeNs + 10 * NS_PER_SEC); + + // Bucket status after battery saver mode ON event. + batterySaverOnEvent = + CreateBatterySaverOnEvent(/*timestamp=*/bucketStartTimeNs + 40 * NS_PER_SEC); + StateManager::getInstance().onLogEvent(*batterySaverOnEvent); + + // Base for dimension key {} + ASSERT_EQ(1UL, valueProducer->mCurrentBaseInfo.size()); + ASSERT_EQ(2UL, valueProducer->mCurrentSlicedBucket.size()); + it = valueProducer->mCurrentSlicedBucket.begin(); + itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); + ASSERT_EQ(1, itBase->second.currentState.getValues().size()); + EXPECT_EQ(BatterySaverModeStateChanged::ON, + itBase->second.currentState.getValues()[0].mValue.int_value); + // Value for key {{}, ON} + EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(BatterySaverModeStateChanged::ON, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, true, 20 * NS_PER_SEC, + bucketStartTimeNs + 40 * NS_PER_SEC); + + // Value for key {{}, -1} + it++; + EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(-1 /*StateTracker::kUnknown*/, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, false, 10 * NS_PER_SEC, + bucketStartTimeNs + 10 * NS_PER_SEC); + + // Start dump report and check output. + ProtoOutputStream output; + std::set strSet; + valueProducer->onDumpReport(bucketStartTimeNs + 50 * NS_PER_SEC, + true /* include recent buckets */, true, NO_TIME_CONSTRAINTS, + &strSet, &output); + + StatsLogReport report = outputStreamToProto(&output); + EXPECT_TRUE(report.has_value_metrics()); + ASSERT_EQ(2, report.value_metrics().data_size()); + + // {{}, kStateUnknown} + ValueMetricData data = report.value_metrics().data(0); + EXPECT_EQ(util::BATTERY_SAVER_MODE_STATE_CHANGED, data.slice_by_state(0).atom_id()); + EXPECT_EQ(-1 /*StateTracker::kUnknown*/, data.slice_by_state(0).value()); + ASSERT_EQ(1, data.bucket_info_size()); + EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).condition_true_nanos()); + + // {{}, ON} + data = report.value_metrics().data(1); + EXPECT_EQ(util::BATTERY_SAVER_MODE_STATE_CHANGED, data.slice_by_state(0).atom_id()); + EXPECT_EQ(BatterySaverModeStateChanged::ON, data.slice_by_state(0).value()); + ASSERT_EQ(1, data.bucket_info_size()); + EXPECT_EQ(30 * NS_PER_SEC, data.bucket_info(0).condition_true_nanos()); +} + +/* + * Test for metric that slices by state when data is not present in pulled data + * during an event and then a flush occurs for the current bucket. With the new + * condition timer behavior, a "new" MetricDimensionKey is inserted into + * `mCurrentSlicedBucket` before intervals are closed/added to that new + * MetricDimensionKey. + */ +TEST(ValueMetricProducerTest, TestSlicedStateWithMissingDataThenFlushBucket) { + // Set up ValueMetricProducer. + ValueMetric metric = + ValueMetricProducerTestHelper::createMetricWithState("BATTERY_SAVER_MODE_STATE"); + sp pullerManager = new StrictMock(); + /* + NOTE: "-" means that the data was not present in the pulled data. + + bucket # 1 + 10 20 30 40 50 60 (seconds) + |-------------------------------------------------------|-- + - (kStateUnknown) + + - (ON) + */ + EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _)) + // ValueMetricProducer initialized but data for dimension key {} is not present + // in the pulled data.. + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector>* data) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs); + data->clear(); + return true; + })) + // Battery saver mode state changed to ON but data for dimension key {} is not present + // in the pulled data. + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector>* data) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 10 * NS_PER_SEC); + data->clear(); + return true; + })) + // Dump report pull. + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector>* data) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 50 * NS_PER_SEC); + data->clear(); + data->push_back(CreateRepeatedValueLogEvent( + tagId, bucketStartTimeNs + 50 * NS_PER_SEC, 15)); + return true; + })); + + StateManager::getInstance().clear(); + sp valueProducer = + ValueMetricProducerTestHelper::createValueProducerWithState( + pullerManager, metric, {util::BATTERY_SAVER_MODE_STATE_CHANGED}, {}); + EXPECT_EQ(1, valueProducer->mSlicedStateAtoms.size()); + + // Set up StateManager and check that StateTrackers are initialized. + StateManager::getInstance().registerListener(util::BATTERY_SAVER_MODE_STATE_CHANGED, + valueProducer); + EXPECT_EQ(1, StateManager::getInstance().getStateTrackersCount()); + EXPECT_EQ(1, StateManager::getInstance().getListenersCount( + util::BATTERY_SAVER_MODE_STATE_CHANGED)); + + // Bucket status after metric initialized. + ASSERT_EQ(0UL, valueProducer->mCurrentSlicedBucket.size()); + ASSERT_EQ(0UL, valueProducer->mCurrentBaseInfo.size()); + + // Bucket status after battery saver mode ON event which is not present + // in the pulled data. + unique_ptr batterySaverOnEvent = + CreateBatterySaverOnEvent(/*timestamp=*/bucketStartTimeNs + 10 * NS_PER_SEC); + StateManager::getInstance().onLogEvent(*batterySaverOnEvent); + + ASSERT_EQ(0UL, valueProducer->mCurrentBaseInfo.size()); + ASSERT_EQ(0UL, valueProducer->mCurrentSlicedBucket.size()); + + // Start dump report and check output. + ProtoOutputStream output; + std::set strSet; + valueProducer->onDumpReport(bucketStartTimeNs + 50 * NS_PER_SEC, + true /* include recent buckets */, true, NO_TIME_CONSTRAINTS, + &strSet, &output); + + StatsLogReport report = outputStreamToProto(&output); + EXPECT_TRUE(report.has_value_metrics()); + ASSERT_EQ(0, report.value_metrics().data_size()); + ASSERT_EQ(2UL, valueProducer->mCurrentSlicedBucket.size()); + ASSERT_EQ(1UL, valueProducer->mCurrentBaseInfo.size()); +} + +TEST(ValueMetricProducerTest, TestSlicedStateWithNoPullOnBucketBoundary) { + // Set up ValueMetricProducer. + ValueMetric metric = + ValueMetricProducerTestHelper::createMetricWithState("BATTERY_SAVER_MODE_STATE"); + sp pullerManager = new StrictMock(); + /* + bucket # 1 bucket # 2 + 10 20 30 40 50 60 70 80 90 100 110 120 (seconds) + |------------------------------------|---------------------------|-- + x (kStateUnknown) + |-----| + 10 + x x (ON) + |-----| |-----------| + 10 20 + x (OFF) + |------------------------| + 40 + */ + EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _)) + // ValueMetricProducer initialized. + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector>* data) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs); + data->clear(); + data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs, 3)); + return true; + })) + // Battery saver mode state changed to ON. + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector>* data) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 10 * NS_PER_SEC); + data->clear(); + data->push_back( + CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 10 * NS_PER_SEC, 5)); + return true; + })) + // Battery saver mode state changed to OFF. + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector>* data) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 20 * NS_PER_SEC); + data->clear(); + data->push_back( + CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 20 * NS_PER_SEC, 7)); + return true; + })) + // Battery saver mode state changed to ON. + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector>* data) { + EXPECT_EQ(eventTimeNs, bucket2StartTimeNs + 30 * NS_PER_SEC); + data->clear(); + data->push_back(CreateRepeatedValueLogEvent( + tagId, bucket2StartTimeNs + 30 * NS_PER_SEC, 10)); + return true; + })) + // Dump report pull. + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector>* data) { + EXPECT_EQ(eventTimeNs, bucket2StartTimeNs + 50 * NS_PER_SEC); + data->clear(); + data->push_back(CreateRepeatedValueLogEvent( + tagId, bucket2StartTimeNs + 50 * NS_PER_SEC, 15)); + return true; + })); + + StateManager::getInstance().clear(); + sp valueProducer = + ValueMetricProducerTestHelper::createValueProducerWithState( + pullerManager, metric, {util::BATTERY_SAVER_MODE_STATE_CHANGED}, {}); + EXPECT_EQ(1, valueProducer->mSlicedStateAtoms.size()); + + // Set up StateManager and check that StateTrackers are initialized. + StateManager::getInstance().registerListener(util::BATTERY_SAVER_MODE_STATE_CHANGED, + valueProducer); + EXPECT_EQ(1, StateManager::getInstance().getStateTrackersCount()); + EXPECT_EQ(1, StateManager::getInstance().getListenersCount( + util::BATTERY_SAVER_MODE_STATE_CHANGED)); + + // Bucket status after metric initialized. + ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); + ASSERT_EQ(1UL, valueProducer->mCurrentBaseInfo.size()); + auto it = valueProducer->mCurrentSlicedBucket.begin(); + auto itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); + EXPECT_TRUE(itBase->second.hasCurrentState); + ASSERT_EQ(1, itBase->second.currentState.getValues().size()); + EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, + itBase->second.currentState.getValues()[0].mValue.int_value); + // Value for dimension, state key {{}, kStateUnknown} + EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, true, 0, bucketStartTimeNs); + + // Bucket status after battery saver mode ON event. + unique_ptr batterySaverOnEvent = + CreateBatterySaverOnEvent(/*timestamp=*/bucketStartTimeNs + 10 * NS_PER_SEC); + StateManager::getInstance().onLogEvent(*batterySaverOnEvent); + + ASSERT_EQ(1UL, valueProducer->mCurrentBaseInfo.size()); + ASSERT_EQ(2UL, valueProducer->mCurrentSlicedBucket.size()); + it = valueProducer->mCurrentSlicedBucket.begin(); + itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); + ASSERT_EQ(1, itBase->second.currentState.getValues().size()); + EXPECT_EQ(BatterySaverModeStateChanged::ON, + itBase->second.currentState.getValues()[0].mValue.int_value); + // Value for key {{}, ON} + EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(BatterySaverModeStateChanged::ON, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, true, 0, bucketStartTimeNs + 10 * NS_PER_SEC); + + // Value for key {{}, -1} + it++; + EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(-1 /*StateTracker::kUnknown*/, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, false, 10 * NS_PER_SEC, + bucketStartTimeNs + 10 * NS_PER_SEC); + + // Bucket status after battery saver mode OFF event. + unique_ptr batterySaverOffEvent = + CreateBatterySaverOffEvent(/*timestamp=*/bucketStartTimeNs + 20 * NS_PER_SEC); + StateManager::getInstance().onLogEvent(*batterySaverOffEvent); + + ASSERT_EQ(1UL, valueProducer->mCurrentBaseInfo.size()); + ASSERT_EQ(3UL, valueProducer->mCurrentSlicedBucket.size()); + it = valueProducer->mCurrentSlicedBucket.begin(); + itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); + ASSERT_EQ(1, itBase->second.currentState.getValues().size()); + EXPECT_EQ(BatterySaverModeStateChanged::OFF, + itBase->second.currentState.getValues()[0].mValue.int_value); + // Value for key {{}, OFF} + EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(BatterySaverModeStateChanged::OFF, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, true, 0, bucketStartTimeNs + 20 * NS_PER_SEC); + + // Value for key {{}, ON} + it++; + EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(BatterySaverModeStateChanged::ON, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, false, 10 * NS_PER_SEC, + bucketStartTimeNs + 20 * NS_PER_SEC); + + // Value for key {{}, -1} + it++; + EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(-1 /*StateTracker::kUnknown*/, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, false, 10 * NS_PER_SEC, + bucketStartTimeNs + 10 * NS_PER_SEC); + + // Bucket status after battery saver mode ON event. + batterySaverOnEvent = + CreateBatterySaverOnEvent(/*timestamp=*/bucket2StartTimeNs + 30 * NS_PER_SEC); + StateManager::getInstance().onLogEvent(*batterySaverOnEvent); + + ASSERT_EQ(1UL, valueProducer->mCurrentBaseInfo.size()); + ASSERT_EQ(3UL, valueProducer->mCurrentSlicedBucket.size()); + it = valueProducer->mCurrentSlicedBucket.begin(); + itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); + ASSERT_EQ(1, itBase->second.currentState.getValues().size()); + EXPECT_EQ(BatterySaverModeStateChanged::ON, + itBase->second.currentState.getValues()[0].mValue.int_value); + // Value for key {{}, OFF} + EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(BatterySaverModeStateChanged::OFF, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, false, 30 * NS_PER_SEC, + bucket2StartTimeNs + 30 * NS_PER_SEC); + + // Value for key {{}, ON} + it++; + EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(BatterySaverModeStateChanged::ON, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, true, 0, bucket2StartTimeNs + 30 * NS_PER_SEC); + + // Value for key {{}, -1} + it++; + EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(-1 /*StateTracker::kUnknown*/, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, false, 0, bucketStartTimeNs + 10 * NS_PER_SEC); + + // Start dump report and check output. + ProtoOutputStream output; + std::set strSet; + valueProducer->onDumpReport(bucket2StartTimeNs + 50 * NS_PER_SEC, + true /* include recent buckets */, true, NO_TIME_CONSTRAINTS, + &strSet, &output); + + StatsLogReport report = outputStreamToProto(&output); + EXPECT_TRUE(report.has_value_metrics()); + ASSERT_EQ(3, report.value_metrics().data_size()); + + // {{}, kStateUnknown} + ValueMetricData data = report.value_metrics().data(0); + EXPECT_EQ(util::BATTERY_SAVER_MODE_STATE_CHANGED, data.slice_by_state(0).atom_id()); + EXPECT_EQ(-1 /*StateTracker::kUnknown*/, data.slice_by_state(0).value()); + ASSERT_EQ(1, data.bucket_info_size()); + EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).condition_true_nanos()); + + // {{}, ON} + data = report.value_metrics().data(1); + EXPECT_EQ(util::BATTERY_SAVER_MODE_STATE_CHANGED, data.slice_by_state(0).atom_id()); + EXPECT_EQ(BatterySaverModeStateChanged::ON, data.slice_by_state(0).value()); + ASSERT_EQ(2, data.bucket_info_size()); + EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).condition_true_nanos()); + EXPECT_EQ(20 * NS_PER_SEC, data.bucket_info(1).condition_true_nanos()); + + // {{}, OFF} + data = report.value_metrics().data(2); + EXPECT_EQ(util::BATTERY_SAVER_MODE_STATE_CHANGED, data.slice_by_state(0).atom_id()); + EXPECT_EQ(BatterySaverModeStateChanged::OFF, data.slice_by_state(0).value()); + ASSERT_EQ(1, data.bucket_info_size()); + EXPECT_EQ(40 * NS_PER_SEC, data.bucket_info(0).condition_true_nanos()); +} + +/* + * Test slicing condition_true_nanos by state for metric that slices by state when data is not + * present in pulled data during a condition change. + */ +TEST(ValueMetricProducerTest, TestSlicedStateWithDataMissingInConditionChange) { + // Set up ValueMetricProducer. + ValueMetric metric = ValueMetricProducerTestHelper::createMetricWithConditionAndState( + "BATTERY_SAVER_MODE_STATE"); + sp pullerManager = new StrictMock(); + /* + NOTE: "-" means that the data was not present in the pulled data. + + bucket # 1 + 10 20 30 40 50 60 (seconds) + |-------------------------------------------------------|-- + + T F T (Condition) + x (ON) + |----------------------| - + 20 + */ + EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _)) + // Battery saver mode state changed to ON. + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector>* data) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 10 * NS_PER_SEC); + data->clear(); + data->push_back( + CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 10 * NS_PER_SEC, 3)); + return true; + })) + // Condition changed to false. + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector>* data) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 30 * NS_PER_SEC); + data->clear(); + data->push_back( + CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 30 * NS_PER_SEC, 5)); + return true; + })) + // Condition changed to true but data for dimension key {} is not present in the + // pulled data. + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector>* data) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 40 * NS_PER_SEC); + data->clear(); + return true; + })) + // Dump report pull. + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector>* data) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 50 * NS_PER_SEC); + data->clear(); + data->push_back(CreateRepeatedValueLogEvent( + tagId, bucketStartTimeNs + 50 * NS_PER_SEC, 20)); + return true; + })); + + StateManager::getInstance().clear(); + sp valueProducer = + ValueMetricProducerTestHelper::createValueProducerWithConditionAndState( + pullerManager, metric, {util::BATTERY_SAVER_MODE_STATE_CHANGED}, {}, + ConditionState::kTrue); + EXPECT_EQ(1, valueProducer->mSlicedStateAtoms.size()); + + // Set up StateManager and check that StateTrackers are initialized. + StateManager::getInstance().registerListener(util::BATTERY_SAVER_MODE_STATE_CHANGED, + valueProducer); + EXPECT_EQ(1, StateManager::getInstance().getStateTrackersCount()); + EXPECT_EQ(1, StateManager::getInstance().getListenersCount( + util::BATTERY_SAVER_MODE_STATE_CHANGED)); + + // Bucket status after battery saver mode ON event. + unique_ptr batterySaverOnEvent = + CreateBatterySaverOnEvent(/*timestamp=*/bucketStartTimeNs + 10 * NS_PER_SEC); + StateManager::getInstance().onLogEvent(*batterySaverOnEvent); + // Base for dimension key {} + ASSERT_EQ(1UL, valueProducer->mCurrentBaseInfo.size()); + ASSERT_EQ(2UL, valueProducer->mCurrentSlicedBucket.size()); + auto it = valueProducer->mCurrentSlicedBucket.begin(); + auto itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); + EXPECT_TRUE(itBase->second.hasCurrentState); + ASSERT_EQ(1, itBase->second.currentState.getValues().size()); + EXPECT_EQ(BatterySaverModeStateChanged::ON, + itBase->second.currentState.getValues()[0].mValue.int_value); + // Value for key {{}, ON} + EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(BatterySaverModeStateChanged::ON, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, true, 0, bucketStartTimeNs + 10 * NS_PER_SEC); + + // Value for key {{}, -1} + it++; + EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(-1 /*StateTracker::kUnknown*/, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, false, 0, 0); + + // Bucket status after condition change to false. + valueProducer->onConditionChanged(false, bucketStartTimeNs + 30 * NS_PER_SEC); + // Base for dimension key {} + ASSERT_EQ(1UL, valueProducer->mCurrentBaseInfo.size()); + ASSERT_EQ(2UL, valueProducer->mCurrentSlicedBucket.size()); + it = valueProducer->mCurrentSlicedBucket.begin(); + itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); + EXPECT_TRUE(itBase->second.hasCurrentState); + ASSERT_EQ(1, itBase->second.currentState.getValues().size()); + EXPECT_EQ(BatterySaverModeStateChanged::ON, + itBase->second.currentState.getValues()[0].mValue.int_value); + // Value for key {{}, ON} + EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(BatterySaverModeStateChanged::ON, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, false, 20 * NS_PER_SEC, + bucketStartTimeNs + 30 * NS_PER_SEC); + + // Value for key {{}, -1} + it++; + EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(-1 /*StateTracker::kUnknown*/, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, false, 0, 0); + + // Bucket status after condition change to true. + valueProducer->onConditionChanged(true, bucketStartTimeNs + 40 * NS_PER_SEC); + // Base for dimension key {} + ASSERT_EQ(1UL, valueProducer->mCurrentBaseInfo.size()); + ASSERT_EQ(2UL, valueProducer->mCurrentSlicedBucket.size()); + it = valueProducer->mCurrentSlicedBucket.begin(); + itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); + EXPECT_FALSE(itBase->second.hasCurrentState); + ASSERT_EQ(1, itBase->second.currentState.getValues().size()); + EXPECT_EQ(BatterySaverModeStateChanged::ON, + itBase->second.currentState.getValues()[0].mValue.int_value); + // Value for key {{}, ON} + EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(BatterySaverModeStateChanged::ON, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, false, 20 * NS_PER_SEC, + bucketStartTimeNs + 30 * NS_PER_SEC); + + // Value for key {{}, -1} + it++; + EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(-1 /*StateTracker::kUnknown*/, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, false, 0, 0); + + // Start dump report and check output. + ProtoOutputStream output; + std::set strSet; + valueProducer->onDumpReport(bucketStartTimeNs + 50 * NS_PER_SEC, + true /* include recent buckets */, true, NO_TIME_CONSTRAINTS, + &strSet, &output); + + StatsLogReport report = outputStreamToProto(&output); + EXPECT_TRUE(report.has_value_metrics()); + ASSERT_EQ(1, report.value_metrics().data_size()); + + // {{}, ON} + ValueMetricData data = report.value_metrics().data(0); + EXPECT_EQ(util::BATTERY_SAVER_MODE_STATE_CHANGED, data.slice_by_state(0).atom_id()); + EXPECT_EQ(BatterySaverModeStateChanged::ON, data.slice_by_state(0).value()); + ASSERT_EQ(1, data.bucket_info_size()); + EXPECT_EQ(20 * NS_PER_SEC, data.bucket_info(0).condition_true_nanos()); +} + +/* + * Test slicing condition_true_nanos by state for metric that slices by state with a primary field, + * condition, and has multiple dimensions. + */ +TEST(ValueMetricProducerTest, TestSlicedStateWithMultipleDimensions) { + // Set up ValueMetricProducer. + ValueMetric metric = + ValueMetricProducerTestHelper::createMetricWithConditionAndState("UID_PROCESS_STATE"); + metric.mutable_dimensions_in_what()->set_field(tagId); + metric.mutable_dimensions_in_what()->add_child()->set_field(1); + metric.mutable_dimensions_in_what()->add_child()->set_field(3); + + MetricStateLink* stateLink = metric.add_state_link(); + stateLink->set_state_atom_id(UID_PROCESS_STATE_ATOM_ID); + auto fieldsInWhat = stateLink->mutable_fields_in_what(); + *fieldsInWhat = CreateDimensions(tagId, {1 /* uid */}); + auto fieldsInState = stateLink->mutable_fields_in_state(); + *fieldsInState = CreateDimensions(UID_PROCESS_STATE_ATOM_ID, {1 /* uid */}); + + /* + bucket # 1 bucket # 2 + 10 20 30 40 50 60 70 80 90 100 110 120 (seconds) + |------------------------------------------|---------------------------------|-- + + T F T (Condition) + (FOREGROUND) + x {1, 14} + |------| + 10 + + x {1, 16} + |------| + 10 + x {2, 8} + |-------------| + 20 + + (BACKGROUND) + x {1, 14} + |-------------| |----------|---------------------------------| + 20 15 50 + + x {1, 16} + |-------------| |----------|---------------------------------| + 20 15 50 + + x {2, 8} + |----------| |----------|-------------------| + 15 15 30 + */ + sp pullerManager = new StrictMock(); + EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _)) + // Uid 1 process state change from kStateUnknown -> Foreground + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector>* data) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 10 * NS_PER_SEC); + data->clear(); + data->push_back(CreateThreeValueLogEvent(tagId, bucketStartTimeNs + 10 * NS_PER_SEC, + 1 /*uid*/, 3, 14 /*tag*/)); + data->push_back(CreateThreeValueLogEvent(tagId, bucketStartTimeNs + 10 * NS_PER_SEC, + 1 /*uid*/, 3, 16 /*tag*/)); + + // This event should be skipped. + data->push_back(CreateThreeValueLogEvent(tagId, bucketStartTimeNs + 10 * NS_PER_SEC, + 2 /*uid*/, 5, 8 /*tag*/)); + return true; + })) + // Uid 1 process state change from Foreground -> Background + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector>* data) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 20 * NS_PER_SEC); + data->clear(); + data->push_back(CreateThreeValueLogEvent(tagId, bucketStartTimeNs + 20 * NS_PER_SEC, + 1 /*uid*/, 5, 14 /*tag*/)); + data->push_back(CreateThreeValueLogEvent(tagId, bucketStartTimeNs + 20 * NS_PER_SEC, + 1 /*uid*/, 5, 16 /*tag*/)); + + // This event should be skipped. + data->push_back(CreateThreeValueLogEvent(tagId, bucketStartTimeNs + 20 * NS_PER_SEC, + 2 /*uid*/, 7, 8 /*tag*/)); + + return true; + })) + // Uid 2 process state change from kStateUnknown -> Background + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector>* data) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 25 * NS_PER_SEC); + data->clear(); + data->push_back(CreateThreeValueLogEvent(tagId, bucketStartTimeNs + 25 * NS_PER_SEC, + 2 /*uid*/, 9, 8 /*tag*/)); + + // This event should be skipped. + data->push_back(CreateThreeValueLogEvent(tagId, bucketStartTimeNs + 25 * NS_PER_SEC, + 1 /*uid*/, 9, 14 /* tag */)); + + // This event should be skipped. + data->push_back(CreateThreeValueLogEvent(tagId, bucketStartTimeNs + 25 * NS_PER_SEC, + 1 /*uid*/, 9, 16 /* tag */)); + + return true; + })) + // Condition changed to false. + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector>* data) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 40 * NS_PER_SEC); + data->clear(); + data->push_back(CreateThreeValueLogEvent(tagId, bucketStartTimeNs + 40 * NS_PER_SEC, + 1 /*uid*/, 11, 14 /* tag */)); + data->push_back(CreateThreeValueLogEvent(tagId, bucketStartTimeNs + 40 * NS_PER_SEC, + 1 /*uid*/, 11, 16 /* tag */)); + data->push_back(CreateThreeValueLogEvent(tagId, bucketStartTimeNs + 40 * NS_PER_SEC, + 2 /*uid*/, 11, 8 /*tag*/)); + + return true; + })) + // Condition changed to true. + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector>* data) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 45 * NS_PER_SEC); + data->clear(); + data->push_back(CreateThreeValueLogEvent(tagId, bucketStartTimeNs + 45 * NS_PER_SEC, + 1 /*uid*/, 13, 14 /* tag */)); + data->push_back(CreateThreeValueLogEvent(tagId, bucketStartTimeNs + 45 * NS_PER_SEC, + 1 /*uid*/, 13, 16 /* tag */)); + data->push_back(CreateThreeValueLogEvent(tagId, bucketStartTimeNs + 45 * NS_PER_SEC, + 2 /*uid*/, 13, 8 /*tag*/)); + return true; + })) + // Uid 2 process state change from Background -> Foreground + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector>* data) { + EXPECT_EQ(eventTimeNs, bucket2StartTimeNs + 30 * NS_PER_SEC); + data->clear(); + data->push_back(CreateThreeValueLogEvent( + tagId, bucket2StartTimeNs + 30 * NS_PER_SEC, 2 /*uid*/, 18, 8 /*tag*/)); + + // This event should be skipped. + data->push_back(CreateThreeValueLogEvent( + tagId, bucket2StartTimeNs + 30 * NS_PER_SEC, 1 /*uid*/, 18, 14 /* tag */)); + // This event should be skipped. + data->push_back(CreateThreeValueLogEvent( + tagId, bucket2StartTimeNs + 30 * NS_PER_SEC, 1 /*uid*/, 18, 16 /* tag */)); + + return true; + })) + // Dump report pull. + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector>* data) { + EXPECT_EQ(eventTimeNs, bucket2StartTimeNs + 50 * NS_PER_SEC); + data->clear(); + data->push_back(CreateThreeValueLogEvent( + tagId, bucket2StartTimeNs + 50 * NS_PER_SEC, 1 /*uid*/, 21, 14 /* tag */)); + data->push_back(CreateThreeValueLogEvent( + tagId, bucket2StartTimeNs + 50 * NS_PER_SEC, 1 /*uid*/, 21, 16 /* tag */)); + data->push_back(CreateThreeValueLogEvent( + tagId, bucket2StartTimeNs + 50 * NS_PER_SEC, 2 /*uid*/, 21, 8 /*tag*/)); + return true; + })); + + StateManager::getInstance().clear(); + sp valueProducer = + ValueMetricProducerTestHelper::createValueProducerWithConditionAndState( + pullerManager, metric, {UID_PROCESS_STATE_ATOM_ID}, {}, ConditionState::kTrue); + EXPECT_EQ(1, valueProducer->mSlicedStateAtoms.size()); + + // Set up StateManager and check that StateTrackers are initialized. + StateManager::getInstance().registerListener(UID_PROCESS_STATE_ATOM_ID, valueProducer); + EXPECT_EQ(1, StateManager::getInstance().getStateTrackersCount()); + EXPECT_EQ(1, StateManager::getInstance().getListenersCount(UID_PROCESS_STATE_ATOM_ID)); + + // Condition is true. + // Bucket status after uid 1 process state change kStateUnknown -> Foreground. + auto uidProcessEvent = + CreateUidProcessStateChangedEvent(bucketStartTimeNs + 10 * NS_PER_SEC, 1 /* uid */, + android::app::PROCESS_STATE_IMPORTANT_FOREGROUND); + StateManager::getInstance().onLogEvent(*uidProcessEvent); + ASSERT_EQ(2UL, valueProducer->mCurrentBaseInfo.size()); + ASSERT_EQ(4UL, valueProducer->mCurrentSlicedBucket.size()); + // Base for dimension {uid 1, tag 16}. + auto it = valueProducer->mCurrentSlicedBucket.begin(); + auto itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); + ASSERT_EQ(1, itBase->second.currentState.getValues().size()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_FOREGROUND, + itBase->second.currentState.getValues()[0].mValue.int_value); + // Value for key {{uid 1, uid 16}, FOREGROUND}. + ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + EXPECT_EQ(16, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_FOREGROUND, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, true, 0, bucketStartTimeNs + 10 * NS_PER_SEC); + // Value for key {{uid 1, tag 16}, kStateUnknown}. + it++; + ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + EXPECT_EQ(16, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, false, 0, 0); + + // Base for dimension key {uid 1, tag 14}. + it++; + itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); + ASSERT_EQ(1, itBase->second.currentState.getValues().size()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_FOREGROUND, + itBase->second.currentState.getValues()[0].mValue.int_value); + // Value for key {{uid 1, tag 14}, FOREGROUND}. + ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + EXPECT_EQ(14, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_FOREGROUND, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, true, 0, bucketStartTimeNs + 10 * NS_PER_SEC); + // Value for key {{uid 1, tag 14}, kStateUnknown}. + it++; + ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + EXPECT_EQ(14, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, false, 0, 0); + + // Bucket status after uid 1 process state change Foreground -> Background. + uidProcessEvent = + CreateUidProcessStateChangedEvent(bucketStartTimeNs + 20 * NS_PER_SEC, 1 /* uid */, + android::app::PROCESS_STATE_IMPORTANT_BACKGROUND); + StateManager::getInstance().onLogEvent(*uidProcessEvent); + ASSERT_EQ(2UL, valueProducer->mCurrentBaseInfo.size()); + ASSERT_EQ(6UL, valueProducer->mCurrentSlicedBucket.size()); + // Base for dimension {uid 1, tag 16}. + it = valueProducer->mCurrentSlicedBucket.begin(); + itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); + ASSERT_EQ(1, itBase->second.currentState.getValues().size()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND, + itBase->second.currentState.getValues()[0].mValue.int_value); + // Value for key {{uid 1, uid 16}, BACKGROUND}. + ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + EXPECT_EQ(16, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, true, 0, bucketStartTimeNs + 20 * NS_PER_SEC); + + // Base for dimension key {uid 1, tag 14}. + it++; + itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); + ASSERT_EQ(1, itBase->second.currentState.getValues().size()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND, + itBase->second.currentState.getValues()[0].mValue.int_value); + // Value for key {{uid 1, tag 14}, BACKGROUND}. + ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + EXPECT_EQ(14, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, true, 0, bucketStartTimeNs + 20 * NS_PER_SEC); + + // Value for key {{uid 1, uid 16}, FOREGROUND}. + it++; + ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + EXPECT_EQ(16, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_FOREGROUND, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, false, 10 * NS_PER_SEC, + bucketStartTimeNs + 20 * NS_PER_SEC); + + // Value for key {{uid 1, tag 16}, kStateUnknown}. + it++; + ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + EXPECT_EQ(16, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, false, 0, 0); + + // Value for key {{uid 1, tag 14}, FOREGROUND}. + it++; + ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + EXPECT_EQ(14, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_FOREGROUND, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, false, 10 * NS_PER_SEC, + bucketStartTimeNs + 20 * NS_PER_SEC); + + // Value for key {{uid 1, tag 14}, kStateUnknown}. + it++; + ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + EXPECT_EQ(14, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, false, 0, 0); + + // Bucket status after uid 2 process state change kStateUnknown -> Background. + uidProcessEvent = + CreateUidProcessStateChangedEvent(bucketStartTimeNs + 25 * NS_PER_SEC, 2 /* uid */, + android::app::PROCESS_STATE_IMPORTANT_BACKGROUND); + StateManager::getInstance().onLogEvent(*uidProcessEvent); + ASSERT_EQ(3UL, valueProducer->mCurrentBaseInfo.size()); + ASSERT_EQ(8UL, valueProducer->mCurrentSlicedBucket.size()); + // Base for dimension {uid 2, tag 8}. + it = valueProducer->mCurrentSlicedBucket.begin(); + itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); + ASSERT_EQ(1, itBase->second.currentState.getValues().size()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND, + itBase->second.currentState.getValues()[0].mValue.int_value); + // Value for key {{uid 2, uid 8}, BACKGROUND}. + ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(2, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + EXPECT_EQ(8, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, true, 0, bucketStartTimeNs + 25 * NS_PER_SEC); + + // Value for key {{uid 2, uid 8}, kStateUnknown}. + it++; + ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(2, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + EXPECT_EQ(8, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, false, 0, 0); + + // Base for dimension {uid 1, tag 16}. + it++; + itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); + ASSERT_EQ(1, itBase->second.currentState.getValues().size()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND, + itBase->second.currentState.getValues()[0].mValue.int_value); + // Value for key {{uid 1, uid 16}, BACKGROUND}. + ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + EXPECT_EQ(16, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, true, 0, bucketStartTimeNs + 20 * NS_PER_SEC); + + // Base for dimension key {uid 1, tag 14}. + it++; + itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); + ASSERT_EQ(1, itBase->second.currentState.getValues().size()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND, + itBase->second.currentState.getValues()[0].mValue.int_value); + // Value for key {{uid 1, tag 14}, BACKGROUND}. + ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + EXPECT_EQ(14, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, true, 0, bucketStartTimeNs + 20 * NS_PER_SEC); + + // Value for key {{uid 1, uid 16}, FOREGROUND}. + it++; + ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + EXPECT_EQ(16, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_FOREGROUND, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, false, 10 * NS_PER_SEC, + bucketStartTimeNs + 20 * NS_PER_SEC); + + // Value for key {{uid 1, tag 16}, kStateUnknown}. + it++; + ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + EXPECT_EQ(16, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, false, 0, 0); + + // Value for key {{uid 1, tag 14}, FOREGROUND}. + it++; + ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + EXPECT_EQ(14, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_FOREGROUND, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, false, 10 * NS_PER_SEC, + bucketStartTimeNs + 20 * NS_PER_SEC); + + // Value for key {{uid 1, tag 14}, kStateUnknown}. + it++; + ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + EXPECT_EQ(14, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, false, 0, 0); + + // Bucket 1 status after condition change to false. + // All condition timers should be turned off. + valueProducer->onConditionChanged(false, bucketStartTimeNs + 40 * NS_PER_SEC); + ASSERT_EQ(3UL, valueProducer->mCurrentBaseInfo.size()); + ASSERT_EQ(8UL, valueProducer->mCurrentSlicedBucket.size()); + // Base for dimension {uid 2, tag 8}. + it = valueProducer->mCurrentSlicedBucket.begin(); + itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); + ASSERT_EQ(1, itBase->second.currentState.getValues().size()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND, + itBase->second.currentState.getValues()[0].mValue.int_value); + // Value for key {{uid 2, uid 8}, BACKGROUND}. + ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(2, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + EXPECT_EQ(8, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, false, 15 * NS_PER_SEC, + bucketStartTimeNs + 40 * NS_PER_SEC); + + // Value for key {{uid 2, uid 8}, kStateUnknown}. + it++; + ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(2, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + EXPECT_EQ(8, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, false, 0, 0); + + // Base for dimension {uid 1, tag 16}. + it++; + itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); + ASSERT_EQ(1, itBase->second.currentState.getValues().size()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND, + itBase->second.currentState.getValues()[0].mValue.int_value); + // Value for key {{uid 1, uid 16}, BACKGROUND}. + ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + EXPECT_EQ(16, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, false, 20 * NS_PER_SEC, + bucketStartTimeNs + 40 * NS_PER_SEC); + + // Base for dimension key {uid 1, tag 14}. + it++; + itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); + ASSERT_EQ(1, itBase->second.currentState.getValues().size()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND, + itBase->second.currentState.getValues()[0].mValue.int_value); + // Value for key {{uid 1, tag 14}, BACKGROUND}. + ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + EXPECT_EQ(14, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, false, 20 * NS_PER_SEC, + bucketStartTimeNs + 40 * NS_PER_SEC); + + // Value for key {{uid 1, uid 16}, FOREGROUND}. + it++; + ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + EXPECT_EQ(16, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_FOREGROUND, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, false, 10 * NS_PER_SEC, + bucketStartTimeNs + 20 * NS_PER_SEC); + + // Value for key {{uid 1, tag 16}, kStateUnknown}. + it++; + ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + EXPECT_EQ(16, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, false, 0, 0); + + // Value for key {{uid 1, tag 14}, FOREGROUND}. + it++; + ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + EXPECT_EQ(14, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_FOREGROUND, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, false, 10 * NS_PER_SEC, + bucketStartTimeNs + 20 * NS_PER_SEC); + + // Value for key {{uid 1, tag 14}, kStateUnknown}. + it++; + ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + EXPECT_EQ(14, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, false, 0, 0); + + // Bucket 1 status after condition change to true. + valueProducer->onConditionChanged(true, bucketStartTimeNs + 45 * NS_PER_SEC); + ASSERT_EQ(3UL, valueProducer->mCurrentBaseInfo.size()); + ASSERT_EQ(8UL, valueProducer->mCurrentSlicedBucket.size()); + // Base for dimension {uid 2, tag 8}. + it = valueProducer->mCurrentSlicedBucket.begin(); + itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); + ASSERT_EQ(1, itBase->second.currentState.getValues().size()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND, + itBase->second.currentState.getValues()[0].mValue.int_value); + // Value for key {{uid 2, uid 8}, BACKGROUND}. + ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(2, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + EXPECT_EQ(8, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, true, 15 * NS_PER_SEC, + bucketStartTimeNs + 45 * NS_PER_SEC); + + // Value for key {{uid 2, uid 8}, kStateUnknown}. + it++; + ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(2, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + EXPECT_EQ(8, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, false, 0, 0); + + // Base for dimension {uid 1, tag 16}. + it++; + itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); + ASSERT_EQ(1, itBase->second.currentState.getValues().size()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND, + itBase->second.currentState.getValues()[0].mValue.int_value); + // Value for key {{uid 1, uid 16}, BACKGROUND}. + ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + EXPECT_EQ(16, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, true, 20 * NS_PER_SEC, + bucketStartTimeNs + 45 * NS_PER_SEC); + + // Base for dimension key {uid 1, tag 14}. + it++; + itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND, + itBase->second.currentState.getValues()[0].mValue.int_value); + // Value for key {{uid 1, tag 14}, BACKGROUND}. + ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + EXPECT_EQ(14, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, true, 20 * NS_PER_SEC, + bucketStartTimeNs + 45 * NS_PER_SEC); + + // Value for key {{uid 1, uid 16}, FOREGROUND}. + it++; + ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + EXPECT_EQ(16, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_FOREGROUND, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, false, 10 * NS_PER_SEC, + bucketStartTimeNs + 20 * NS_PER_SEC); + + // Value for key {{uid 1, tag 16}, kStateUnknown}. + it++; + ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + EXPECT_EQ(16, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, false, 0, 0); + + // Value for key {{uid 1, tag 14}, FOREGROUND}. + it++; + ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + EXPECT_EQ(14, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_FOREGROUND, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, false, 10 * NS_PER_SEC, + bucketStartTimeNs + 20 * NS_PER_SEC); + + // Value for key {{uid 1, tag 14}, kStateUnknown}. + it++; + ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + EXPECT_EQ(14, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, false, 0, 0); + + // Pull at end of first bucket. + vector> allData; + allData.push_back( + CreateThreeValueLogEvent(tagId, bucket2StartTimeNs, 1 /*uid*/, 13, 14 /* tag */)); + allData.push_back( + CreateThreeValueLogEvent(tagId, bucket2StartTimeNs, 1 /*uid*/, 13, 16 /* tag */)); + allData.push_back( + CreateThreeValueLogEvent(tagId, bucket2StartTimeNs, 2 /*uid*/, 13, 8 /*tag*/)); + valueProducer->onDataPulled(allData, /** succeeds */ true, bucket2StartTimeNs + 1); + + // Buckets flushed after end of first bucket. + // All condition timers' behavior should rollover to bucket 2. + ASSERT_EQ(8UL, valueProducer->mCurrentSlicedBucket.size()); + ASSERT_EQ(5UL, valueProducer->mPastBuckets.size()); + ASSERT_EQ(3UL, valueProducer->mCurrentBaseInfo.size()); + // Base for dimension {uid 2, tag 8}. + it = valueProducer->mCurrentSlicedBucket.begin(); + itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); + ASSERT_EQ(1, itBase->second.currentState.getValues().size()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND, + itBase->second.currentState.getValues()[0].mValue.int_value); + // Value for key {{uid 2, uid 8}, BACKGROUND}. + ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(2, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + EXPECT_EQ(8, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, true, 0, bucket2StartTimeNs); + ASSERT_EQ(1, valueProducer->mPastBuckets[it->first].size()); + EXPECT_EQ(30 * NS_PER_SEC, valueProducer->mPastBuckets[it->first][0].mConditionTrueNs); + + // Value for key {{uid 2, uid 8}, kStateUnknown}. + it++; + ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(2, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + EXPECT_EQ(8, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, false, 0, 0); + + // Base for dimension {uid 1, tag 16}. + it++; + itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); + ASSERT_EQ(1, itBase->second.currentState.getValues().size()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND, + itBase->second.currentState.getValues()[0].mValue.int_value); + // Value for key {{uid 1, uid 16}, BACKGROUND}. + ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + EXPECT_EQ(16, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, true, 0, bucket2StartTimeNs); + ASSERT_EQ(1, valueProducer->mPastBuckets[it->first].size()); + EXPECT_EQ(35 * NS_PER_SEC, valueProducer->mPastBuckets[it->first][0].mConditionTrueNs); + + // Base for dimension key {uid 1, tag 14}. + it++; + itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND, + itBase->second.currentState.getValues()[0].mValue.int_value); + // Value for key {{uid 1, tag 14}, BACKGROUND}. + ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + EXPECT_EQ(14, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, true, 0, bucket2StartTimeNs); + ASSERT_EQ(1, valueProducer->mPastBuckets[it->first].size()); + EXPECT_EQ(35 * NS_PER_SEC, valueProducer->mPastBuckets[it->first][0].mConditionTrueNs); + + // Value for key {{uid 1, uid 16}, FOREGROUND}. + it++; + ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + EXPECT_EQ(16, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_FOREGROUND, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, false, 0, bucketStartTimeNs + 20 * NS_PER_SEC); + ASSERT_EQ(1, valueProducer->mPastBuckets[it->first].size()); + EXPECT_EQ(10 * NS_PER_SEC, valueProducer->mPastBuckets[it->first][0].mConditionTrueNs); + + // Value for key {{uid 1, tag 16}, kStateUnknown}. + it++; + ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + EXPECT_EQ(16, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, false, 0, 0); + + // Value for key {{uid 1, tag 14}, FOREGROUND}. + it++; + ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + EXPECT_EQ(14, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_FOREGROUND, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, false, 0, bucketStartTimeNs + 20 * NS_PER_SEC); + ASSERT_EQ(1, valueProducer->mPastBuckets[it->first].size()); + EXPECT_EQ(10 * NS_PER_SEC, valueProducer->mPastBuckets[it->first][0].mConditionTrueNs); + + // Value for key {{uid 1, tag 14}, kStateUnknown}. + it++; + ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + EXPECT_EQ(14, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, false, 0, 0); + + // Bucket 2 status after uid 2 process state change Background->Foreground. + uidProcessEvent = + CreateUidProcessStateChangedEvent(bucket2StartTimeNs + 30 * NS_PER_SEC, 2 /* uid */, + android::app::PROCESS_STATE_IMPORTANT_FOREGROUND); + StateManager::getInstance().onLogEvent(*uidProcessEvent); + + ASSERT_EQ(9UL, valueProducer->mCurrentSlicedBucket.size()); + ASSERT_EQ(3UL, valueProducer->mCurrentBaseInfo.size()); + // Base for dimension {uid 2, tag 8}. + it = valueProducer->mCurrentSlicedBucket.begin(); + itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); + ASSERT_EQ(1, itBase->second.currentState.getValues().size()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_FOREGROUND, + itBase->second.currentState.getValues()[0].mValue.int_value); + // Value for key {{uid 2, uid 8}, FOREGROUND}. + ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(2, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + EXPECT_EQ(8, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_FOREGROUND, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, true, 0, bucket2StartTimeNs + 30 * NS_PER_SEC); + + // Value for key {{uid 2, uid 8}, BACKGROUND}. + it++; + ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(2, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + EXPECT_EQ(8, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, false, 30 * NS_PER_SEC, + bucket2StartTimeNs + 30 * NS_PER_SEC); + + // Value for key {{uid 2, uid 8}, kStateUnknown}. + it++; + ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(2, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + EXPECT_EQ(8, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, false, 0, 0); + + // Base for dimension {uid 1, tag 16}. + it++; + itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); + ASSERT_EQ(1, itBase->second.currentState.getValues().size()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND, + itBase->second.currentState.getValues()[0].mValue.int_value); + // Value for key {{uid 1, uid 16}, BACKGROUND}. + ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + EXPECT_EQ(16, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, true, 0, bucket2StartTimeNs); + + // Base for dimension key {uid 1, tag 14}. + it++; + itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); + ASSERT_EQ(1, itBase->second.currentState.getValues().size()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND, + itBase->second.currentState.getValues()[0].mValue.int_value); + // Value for key {{uid 1, tag 14}, BACKGROUND}. + ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + EXPECT_EQ(14, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, true, 0, bucket2StartTimeNs); + + // Value for key {{uid 1, uid 16}, FOREGROUND}. + it++; + ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + EXPECT_EQ(16, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_FOREGROUND, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, false, 0, bucketStartTimeNs + 20 * NS_PER_SEC); + + // Value for key {{uid 1, tag 16}, kStateUnknown}. + it++; + ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + EXPECT_EQ(16, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, false, 0, 0); + + // Value for key {{uid 1, tag 14}, FOREGROUND}. + it++; + ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + EXPECT_EQ(14, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_FOREGROUND, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, false, 0, bucketStartTimeNs + 20 * NS_PER_SEC); + + // Value for key {{uid 1, tag 14}, kStateUnknown}. + it++; + ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + EXPECT_EQ(14, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, false, 0, 0); + + // Start dump report and check output. + ProtoOutputStream output; + std::set strSet; + valueProducer->onDumpReport(bucket2StartTimeNs + 50 * NS_PER_SEC, + true /* include recent buckets */, true, NO_TIME_CONSTRAINTS, + &strSet, &output); + + StatsLogReport report = outputStreamToProto(&output); + EXPECT_TRUE(report.has_value_metrics()); + ASSERT_EQ(6, report.value_metrics().data_size()); + + // {{uid 1, tag 14}, FOREGROUND}. + auto data = report.value_metrics().data(0); + EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); + EXPECT_EQ(android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_FOREGROUND, + data.slice_by_state(0).value()); + ASSERT_EQ(1, data.bucket_info_size()); + EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).condition_true_nanos()); + + // {{uid 1, tag 16}, BACKGROUND}. + data = report.value_metrics().data(1); + EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); + EXPECT_EQ(android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_BACKGROUND, + data.slice_by_state(0).value()); + ASSERT_EQ(2, data.bucket_info_size()); + EXPECT_EQ(35 * NS_PER_SEC, data.bucket_info(0).condition_true_nanos()); + EXPECT_EQ(50 * NS_PER_SEC, data.bucket_info(1).condition_true_nanos()); + + // {{uid 1, tag 14}, BACKGROUND}. + data = report.value_metrics().data(2); + EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); + EXPECT_EQ(android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_BACKGROUND, + data.slice_by_state(0).value()); + ASSERT_EQ(2, data.bucket_info_size()); + EXPECT_EQ(35 * NS_PER_SEC, data.bucket_info(0).condition_true_nanos()); + EXPECT_EQ(50 * NS_PER_SEC, data.bucket_info(1).condition_true_nanos()); + + // {{uid 1, tag 16}, FOREGROUND}. + data = report.value_metrics().data(3); + EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); + EXPECT_EQ(android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_FOREGROUND, + data.slice_by_state(0).value()); + ASSERT_EQ(1, data.bucket_info_size()); + EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).condition_true_nanos()); + + // {{uid 2, tag 8}, FOREGROUND}. + data = report.value_metrics().data(4); + EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); + EXPECT_EQ(android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_FOREGROUND, + data.slice_by_state(0).value()); + ASSERT_EQ(1, data.bucket_info_size()); + EXPECT_EQ(20 * NS_PER_SEC, data.bucket_info(0).condition_true_nanos()); + + // {{uid 2, tag 8}, BACKGROUND}. + data = report.value_metrics().data(5); + EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); + EXPECT_EQ(android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_BACKGROUND, + data.slice_by_state(0).value()); + ASSERT_EQ(2, data.bucket_info_size()); + EXPECT_EQ(30 * NS_PER_SEC, data.bucket_info(0).condition_true_nanos()); + EXPECT_EQ(30 * NS_PER_SEC, data.bucket_info(1).condition_true_nanos()); +} + +TEST(ValueMetricProducerTest, TestSlicedStateWithCondition) { + // Set up ValueMetricProducer. + ValueMetric metric = ValueMetricProducerTestHelper::createMetricWithConditionAndState( + "BATTERY_SAVER_MODE_STATE"); + sp pullerManager = new StrictMock(); + EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _)) + // Condition changed to true. + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector>* data) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 20 * NS_PER_SEC); + data->clear(); + data->push_back( + CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 20 * NS_PER_SEC, 3)); + return true; + })) + // Battery saver mode state changed to OFF. + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector>* data) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 30 * NS_PER_SEC); + data->clear(); + data->push_back( + CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 30 * NS_PER_SEC, 5)); + return true; + })) + // Condition changed to false. + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector>* data) { + EXPECT_EQ(eventTimeNs, bucket2StartTimeNs + 10 * NS_PER_SEC); + data->clear(); + data->push_back(CreateRepeatedValueLogEvent( + tagId, bucket2StartTimeNs + 10 * NS_PER_SEC, 15)); + return true; + })); + + StateManager::getInstance().clear(); + sp valueProducer = + ValueMetricProducerTestHelper::createValueProducerWithConditionAndState( + pullerManager, metric, {util::BATTERY_SAVER_MODE_STATE_CHANGED}, {}, + ConditionState::kFalse); + EXPECT_EQ(1, valueProducer->mSlicedStateAtoms.size()); + + // Set up StateManager and check that StateTrackers are initialized. + StateManager::getInstance().registerListener(util::BATTERY_SAVER_MODE_STATE_CHANGED, + valueProducer); + EXPECT_EQ(1, StateManager::getInstance().getStateTrackersCount()); + EXPECT_EQ(1, StateManager::getInstance().getListenersCount( + util::BATTERY_SAVER_MODE_STATE_CHANGED)); + + // Bucket status after battery saver mode ON event. + // Condition is false so we do nothing. + unique_ptr batterySaverOnEvent = + CreateBatterySaverOnEvent(/*timestamp=*/bucketStartTimeNs + 10 * NS_PER_SEC); + StateManager::getInstance().onLogEvent(*batterySaverOnEvent); + EXPECT_EQ(0UL, valueProducer->mCurrentSlicedBucket.size()); + EXPECT_EQ(0UL, valueProducer->mCurrentBaseInfo.size()); + + // Bucket status after condition change to true. + valueProducer->onConditionChanged(true, bucketStartTimeNs + 20 * NS_PER_SEC); + // Base for dimension key {} + ASSERT_EQ(1UL, valueProducer->mCurrentBaseInfo.size()); + std::unordered_map::iterator + itBase = valueProducer->mCurrentBaseInfo.find(DEFAULT_DIMENSION_KEY); + EXPECT_TRUE(itBase->second.baseInfos[0].hasBase); + EXPECT_EQ(3, itBase->second.baseInfos[0].base.long_value); + EXPECT_TRUE(itBase->second.hasCurrentState); + ASSERT_EQ(1, itBase->second.currentState.getValues().size()); + EXPECT_EQ(BatterySaverModeStateChanged::ON, + itBase->second.currentState.getValues()[0].mValue.int_value); + // Value for key {{}, ON} + ASSERT_EQ(2UL, valueProducer->mCurrentSlicedBucket.size()); + std::unordered_map::iterator it = + valueProducer->mCurrentSlicedBucket.begin(); + EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(BatterySaverModeStateChanged::ON, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, true, 0, bucketStartTimeNs + 20 * NS_PER_SEC); + // Value for key {{}, -1} + it++; + EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(-1 /*StateTracker::kUnknown*/, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + EXPECT_FALSE(it->second.intervals[0].hasValue); + assertConditionTimer(it->second.conditionTimer, false, 0, 0); + + // Bucket status after battery saver mode OFF event. + unique_ptr batterySaverOffEvent = + CreateBatterySaverOffEvent(/*timestamp=*/bucketStartTimeNs + 30 * NS_PER_SEC); + StateManager::getInstance().onLogEvent(*batterySaverOffEvent); + // Base for dimension key {} + ASSERT_EQ(1UL, valueProducer->mCurrentBaseInfo.size()); + itBase = valueProducer->mCurrentBaseInfo.find(DEFAULT_DIMENSION_KEY); + EXPECT_TRUE(itBase->second.baseInfos[0].hasBase); + EXPECT_EQ(5, itBase->second.baseInfos[0].base.long_value); + EXPECT_TRUE(itBase->second.hasCurrentState); + ASSERT_EQ(1, itBase->second.currentState.getValues().size()); + EXPECT_EQ(BatterySaverModeStateChanged::OFF, + itBase->second.currentState.getValues()[0].mValue.int_value); + // Value for key {{}, OFF} + ASSERT_EQ(3UL, valueProducer->mCurrentSlicedBucket.size()); + it = valueProducer->mCurrentSlicedBucket.begin(); + EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(BatterySaverModeStateChanged::OFF, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + assertConditionTimer(it->second.conditionTimer, true, 0, bucketStartTimeNs + 30 * NS_PER_SEC); + // Value for key {{}, ON} + it++; + EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(BatterySaverModeStateChanged::ON, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + EXPECT_TRUE(it->second.intervals[0].hasValue); + EXPECT_EQ(2, it->second.intervals[0].value.long_value); + assertConditionTimer(it->second.conditionTimer, false, 10 * NS_PER_SEC, + bucketStartTimeNs + 30 * NS_PER_SEC); + // Value for key {{}, -1} + it++; + assertConditionTimer(it->second.conditionTimer, false, 0, 0); + + // Pull at end of first bucket. + vector> allData; + allData.clear(); + allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs, 11)); + valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs); + + EXPECT_EQ(2UL, valueProducer->mPastBuckets.size()); + EXPECT_EQ(3UL, valueProducer->mCurrentSlicedBucket.size()); + // Base for dimension key {} + ASSERT_EQ(1UL, valueProducer->mCurrentBaseInfo.size()); + itBase = valueProducer->mCurrentBaseInfo.find(DEFAULT_DIMENSION_KEY); + EXPECT_TRUE(itBase->second.baseInfos[0].hasBase); + EXPECT_EQ(11, itBase->second.baseInfos[0].base.long_value); + EXPECT_TRUE(itBase->second.hasCurrentState); + ASSERT_EQ(1, itBase->second.currentState.getValues().size()); + EXPECT_EQ(BatterySaverModeStateChanged::OFF, + itBase->second.currentState.getValues()[0].mValue.int_value); + // Value for key {{}, OFF} + it = valueProducer->mCurrentSlicedBucket.begin(); + assertConditionTimer(it->second.conditionTimer, true, 0, bucket2StartTimeNs); + // Value for key {{}, ON} + it++; + assertConditionTimer(it->second.conditionTimer, false, 0, bucketStartTimeNs + 30 * NS_PER_SEC); + // Value for key {{}, -1} + it++; + assertConditionTimer(it->second.conditionTimer, false, 0, 0); + + // Bucket 2 status after condition change to false. + valueProducer->onConditionChanged(false, bucket2StartTimeNs + 10 * NS_PER_SEC); + // Base for dimension key {} + ASSERT_EQ(1UL, valueProducer->mCurrentBaseInfo.size()); + itBase = valueProducer->mCurrentBaseInfo.find(DEFAULT_DIMENSION_KEY); + EXPECT_FALSE(itBase->second.baseInfos[0].hasBase); + EXPECT_TRUE(itBase->second.hasCurrentState); + ASSERT_EQ(1, itBase->second.currentState.getValues().size()); + EXPECT_EQ(BatterySaverModeStateChanged::OFF, + itBase->second.currentState.getValues()[0].mValue.int_value); + // Value for key {{}, OFF} + ASSERT_EQ(3UL, valueProducer->mCurrentSlicedBucket.size()); + it = valueProducer->mCurrentSlicedBucket.begin(); + EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(BatterySaverModeStateChanged::OFF, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + EXPECT_TRUE(it->second.intervals[0].hasValue); + EXPECT_EQ(4, it->second.intervals[0].value.long_value); + assertConditionTimer(it->second.conditionTimer, false, 10 * NS_PER_SEC, + bucket2StartTimeNs + 10 * NS_PER_SEC); + // Value for key {{}, ON} + it++; + EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(BatterySaverModeStateChanged::ON, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + EXPECT_FALSE(it->second.intervals[0].hasValue); + assertConditionTimer(it->second.conditionTimer, false, 0, bucketStartTimeNs + 30 * NS_PER_SEC); + // Value for key {{}, -1} + it++; + assertConditionTimer(it->second.conditionTimer, false, 0, 0); + + // Start dump report and check output. + ProtoOutputStream output; + std::set strSet; + valueProducer->onDumpReport(bucket2StartTimeNs + 50 * NS_PER_SEC, + true /* include recent buckets */, true, NO_TIME_CONSTRAINTS, + &strSet, &output); + + StatsLogReport report = outputStreamToProto(&output); + EXPECT_TRUE(report.has_value_metrics()); + ASSERT_EQ(2, report.value_metrics().data_size()); + + ValueMetricData data = report.value_metrics().data(0); + EXPECT_EQ(util::BATTERY_SAVER_MODE_STATE_CHANGED, data.slice_by_state(0).atom_id()); + EXPECT_TRUE(data.slice_by_state(0).has_value()); + EXPECT_EQ(BatterySaverModeStateChanged::ON, data.slice_by_state(0).value()); + ASSERT_EQ(1, data.bucket_info_size()); + EXPECT_EQ(2, data.bucket_info(0).values(0).value_long()); + EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).condition_true_nanos()); + + data = report.value_metrics().data(1); + EXPECT_EQ(util::BATTERY_SAVER_MODE_STATE_CHANGED, data.slice_by_state(0).atom_id()); + EXPECT_TRUE(data.slice_by_state(0).has_value()); + EXPECT_EQ(BatterySaverModeStateChanged::OFF, data.slice_by_state(0).value()); + ASSERT_EQ(2, data.bucket_info_size()); + EXPECT_EQ(6, data.bucket_info(0).values(0).value_long()); + EXPECT_EQ(4, data.bucket_info(1).values(0).value_long()); + EXPECT_EQ(30 * NS_PER_SEC, data.bucket_info(0).condition_true_nanos()); + EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(1).condition_true_nanos()); +} + +/* + * Test bucket splits when condition is unknown. + */ +TEST(ValueMetricProducerTest, TestForcedBucketSplitWhenConditionUnknownSkipsBucket) { + ValueMetric metric = ValueMetricProducerTestHelper::createMetricWithCondition(); + + sp pullerManager = new StrictMock(); + + sp valueProducer = + ValueMetricProducerTestHelper::createValueProducerWithCondition( + pullerManager, metric, + ConditionState::kUnknown); + + // App update event. + int64_t appUpdateTimeNs = bucketStartTimeNs + 1000; + valueProducer->notifyAppUpgrade(appUpdateTimeNs); + + // Check dump report. + ProtoOutputStream output; + std::set strSet; + int64_t dumpReportTimeNs = bucketStartTimeNs + 10000000000; // 10 seconds + valueProducer->onDumpReport(dumpReportTimeNs, false /* include current buckets */, true, + NO_TIME_CONSTRAINTS /* dumpLatency */, &strSet, &output); + + StatsLogReport report = outputStreamToProto(&output); + EXPECT_TRUE(report.has_value_metrics()); + ASSERT_EQ(0, report.value_metrics().data_size()); + ASSERT_EQ(1, report.value_metrics().skipped_size()); + + EXPECT_EQ(NanoToMillis(bucketStartTimeNs), + report.value_metrics().skipped(0).start_bucket_elapsed_millis()); + EXPECT_EQ(NanoToMillis(appUpdateTimeNs), + report.value_metrics().skipped(0).end_bucket_elapsed_millis()); + ASSERT_EQ(1, report.value_metrics().skipped(0).drop_event_size()); + + auto dropEvent = report.value_metrics().skipped(0).drop_event(0); + EXPECT_EQ(BucketDropReason::CONDITION_UNKNOWN, dropEvent.drop_reason()); + EXPECT_EQ(NanoToMillis(appUpdateTimeNs), dropEvent.drop_time_millis()); +} + +} // namespace statsd +} // namespace os +} // namespace android +#else +GTEST_LOG_(INFO) << "This test does nothing.\n"; +#endif diff --git a/statsd/tests/metrics/metrics_test_helper.cpp b/statsd/tests/metrics/metrics_test_helper.cpp new file mode 100644 index 00000000..108df04b --- /dev/null +++ b/statsd/tests/metrics/metrics_test_helper.cpp @@ -0,0 +1,57 @@ +// 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 "metrics_test_helper.h" + +namespace android { +namespace os { +namespace statsd { + +HashableDimensionKey getMockedDimensionKey(int tagId, int key, string value) { + HashableDimensionKey dimension; + int pos[] = {key, 0, 0}; + dimension.addValue(FieldValue(Field(tagId, pos, 0), Value(value))); + + return dimension; +} + +HashableDimensionKey getMockedDimensionKeyLongValue(int tagId, int key, int64_t value) { + HashableDimensionKey dimension; + int pos[] = {key, 0, 0}; + dimension.addValue(FieldValue(Field(tagId, pos, 0), Value(value))); + + return dimension; +} + +MetricDimensionKey getMockedMetricDimensionKey(int tagId, int key, string value) { + return MetricDimensionKey(getMockedDimensionKey(tagId, key, value), DEFAULT_DIMENSION_KEY); +} + +MetricDimensionKey getMockedStateDimensionKey(int tagId, int key, int64_t value) { + return MetricDimensionKey(DEFAULT_DIMENSION_KEY, + getMockedDimensionKeyLongValue(tagId, key, value)); +} + +void buildSimpleAtomFieldMatcher(const int tagId, FieldMatcher* matcher) { + matcher->set_field(tagId); +} + +void buildSimpleAtomFieldMatcher(const int tagId, const int fieldNum, FieldMatcher* matcher) { + matcher->set_field(tagId); + matcher->add_child()->set_field(fieldNum); +} + +} // namespace statsd +} // namespace os +} // namespace android diff --git a/statsd/tests/metrics/metrics_test_helper.h b/statsd/tests/metrics/metrics_test_helper.h new file mode 100644 index 00000000..39232c19 --- /dev/null +++ b/statsd/tests/metrics/metrics_test_helper.h @@ -0,0 +1,63 @@ +// 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. +#pragma once + +#include "src/condition/ConditionWizard.h" +#include "src/external/StatsPullerManager.h" +#include "src/packages/UidMap.h" + +#include +#include + +namespace android { +namespace os { +namespace statsd { + +class MockConditionWizard : public ConditionWizard { +public: + MOCK_METHOD3(query, + ConditionState(const int conditionIndex, const ConditionKey& conditionParameters, + const bool isPartialLink)); +}; + +class MockStatsPullerManager : public StatsPullerManager { +public: + MOCK_METHOD5(RegisterReceiver, + void(int tagId, const ConfigKey& key, wp receiver, + int64_t nextPulltimeNs, int64_t intervalNs)); + MOCK_METHOD3(UnRegisterReceiver, + void(int tagId, const ConfigKey& key, wp receiver)); + MOCK_METHOD4(Pull, bool(const int pullCode, const ConfigKey& key, const int64_t eventTimeNs, + vector>* data)); + MOCK_METHOD4(Pull, bool(const int pullCode, const vector& uids, + const int64_t eventTimeNs, vector>* data)); + MOCK_METHOD2(RegisterPullUidProvider, + void(const ConfigKey& configKey, wp provider)); + MOCK_METHOD2(UnregisterPullUidProvider, + void(const ConfigKey& configKey, wp provider)); +}; + +HashableDimensionKey getMockedDimensionKey(int tagId, int key, std::string value); +MetricDimensionKey getMockedMetricDimensionKey(int tagId, int key, std::string value); + +HashableDimensionKey getMockedDimensionKeyLongValue(int tagId, int key, int64_t value); +MetricDimensionKey getMockedStateDimensionKey(int tagId, int key, int64_t value); + +// Utils to build FieldMatcher proto for simple one-depth atoms. +void buildSimpleAtomFieldMatcher(const int tagId, const int atomFieldNum, FieldMatcher* matcher); +void buildSimpleAtomFieldMatcher(const int tagId, FieldMatcher* matcher); + +} // namespace statsd +} // namespace os +} // namespace android diff --git a/statsd/tests/metrics/parsing_utils/config_update_utils_test.cpp b/statsd/tests/metrics/parsing_utils/config_update_utils_test.cpp new file mode 100644 index 00000000..fc0bafe4 --- /dev/null +++ b/statsd/tests/metrics/parsing_utils/config_update_utils_test.cpp @@ -0,0 +1,3533 @@ +// Copyright (C) 2020 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 "src/metrics/parsing_utils/config_update_utils.h" + +#include +#include +#include +#include + +#include +#include +#include + +#include "src/statsd_config.pb.h" +#include "src/condition/CombinationConditionTracker.h" +#include "src/condition/SimpleConditionTracker.h" +#include "src/matchers/CombinationAtomMatchingTracker.h" +#include "src/metrics/DurationMetricProducer.h" +#include "src/metrics/GaugeMetricProducer.h" +#include "src/metrics/ValueMetricProducer.h" +#include "src/metrics/parsing_utils/metrics_manager_util.h" +#include "tests/statsd_test_util.h" + +using namespace testing; +using android::sp; +using android::os::statsd::Predicate; +using std::map; +using std::nullopt; +using std::optional; +using std::set; +using std::unordered_map; +using std::vector; + +#ifdef __ANDROID__ + +namespace android { +namespace os { +namespace statsd { + +namespace { + +ConfigKey key(123, 456); +const int64_t timeBaseNs = 1000 * NS_PER_SEC; + +sp uidMap = new UidMap(); +sp pullerManager = new StatsPullerManager(); +sp anomalyAlarmMonitor; +sp periodicAlarmMonitor = new AlarmMonitor( + /*minDiffToUpdateRegisteredAlarmTimeSec=*/0, + [](const shared_ptr&, int64_t) {}, + [](const shared_ptr&) {}); +set allTagIds; +vector> oldAtomMatchingTrackers; +unordered_map oldAtomMatchingTrackerMap; +vector> oldConditionTrackers; +unordered_map oldConditionTrackerMap; +vector> oldMetricProducers; +unordered_map oldMetricProducerMap; +std::vector> oldAnomalyTrackers; +unordered_map oldAlertTrackerMap; +std::vector> oldAlarmTrackers; +unordered_map> tmpConditionToMetricMap; +unordered_map> tmpTrackerToMetricMap; +unordered_map> tmpTrackerToConditionMap; +unordered_map> tmpActivationAtomTrackerToMetricMap; +unordered_map> tmpDeactivationAtomTrackerToMetricMap; +vector metricsWithActivation; +map oldStateHashes; +std::set noReportMetricIds; + +class ConfigUpdateTest : public ::testing::Test { +public: + ConfigUpdateTest() { + } + + void SetUp() override { + allTagIds.clear(); + oldAtomMatchingTrackers.clear(); + oldAtomMatchingTrackerMap.clear(); + oldConditionTrackers.clear(); + oldConditionTrackerMap.clear(); + oldMetricProducers.clear(); + oldMetricProducerMap.clear(); + oldAnomalyTrackers.clear(); + oldAlarmTrackers.clear(); + tmpConditionToMetricMap.clear(); + tmpTrackerToMetricMap.clear(); + tmpTrackerToConditionMap.clear(); + tmpActivationAtomTrackerToMetricMap.clear(); + tmpDeactivationAtomTrackerToMetricMap.clear(); + oldAlertTrackerMap.clear(); + metricsWithActivation.clear(); + oldStateHashes.clear(); + noReportMetricIds.clear(); + StateManager::getInstance().clear(); + } +}; + +bool initConfig(const StatsdConfig& config) { + return initStatsdConfig( + key, config, uidMap, pullerManager, anomalyAlarmMonitor, periodicAlarmMonitor, + timeBaseNs, timeBaseNs, allTagIds, oldAtomMatchingTrackers, oldAtomMatchingTrackerMap, + oldConditionTrackers, oldConditionTrackerMap, oldMetricProducers, oldMetricProducerMap, + oldAnomalyTrackers, oldAlarmTrackers, tmpConditionToMetricMap, tmpTrackerToMetricMap, + tmpTrackerToConditionMap, tmpActivationAtomTrackerToMetricMap, + tmpDeactivationAtomTrackerToMetricMap, oldAlertTrackerMap, metricsWithActivation, + oldStateHashes, noReportMetricIds); +} +} // anonymous namespace + +TEST_F(ConfigUpdateTest, TestSimpleMatcherPreserve) { + StatsdConfig config; + AtomMatcher matcher = CreateSimpleAtomMatcher("TEST", /*atom=*/10); + int64_t matcherId = matcher.id(); + *config.add_atom_matcher() = matcher; + + // Create an initial config. + EXPECT_TRUE(initConfig(config)); + + vector matchersToUpdate(1, UPDATE_UNKNOWN); + vector cycleTracker(1, false); + unordered_map newAtomMatchingTrackerMap; + newAtomMatchingTrackerMap[matcherId] = 0; + EXPECT_TRUE(determineMatcherUpdateStatus(config, 0, oldAtomMatchingTrackerMap, + oldAtomMatchingTrackers, newAtomMatchingTrackerMap, + matchersToUpdate, cycleTracker)); + EXPECT_EQ(matchersToUpdate[0], UPDATE_PRESERVE); +} + +TEST_F(ConfigUpdateTest, TestSimpleMatcherReplace) { + StatsdConfig config; + AtomMatcher matcher = CreateSimpleAtomMatcher("TEST", /*atom=*/10); + *config.add_atom_matcher() = matcher; + + EXPECT_TRUE(initConfig(config)); + + StatsdConfig newConfig; + // Same id, different atom, so should be replaced. + AtomMatcher newMatcher = CreateSimpleAtomMatcher("TEST", /*atom=*/11); + int64_t matcherId = newMatcher.id(); + EXPECT_EQ(matcherId, matcher.id()); + *newConfig.add_atom_matcher() = newMatcher; + + vector matchersToUpdate(1, UPDATE_UNKNOWN); + vector cycleTracker(1, false); + unordered_map newAtomMatchingTrackerMap; + newAtomMatchingTrackerMap[matcherId] = 0; + EXPECT_TRUE(determineMatcherUpdateStatus(newConfig, 0, oldAtomMatchingTrackerMap, + oldAtomMatchingTrackers, newAtomMatchingTrackerMap, + matchersToUpdate, cycleTracker)); + EXPECT_EQ(matchersToUpdate[0], UPDATE_REPLACE); +} + +TEST_F(ConfigUpdateTest, TestSimpleMatcherNew) { + StatsdConfig config; + AtomMatcher matcher = CreateSimpleAtomMatcher("TEST", /*atom=*/10); + *config.add_atom_matcher() = matcher; + + EXPECT_TRUE(initConfig(config)); + + StatsdConfig newConfig; + // Different id, so should be a new matcher. + AtomMatcher newMatcher = CreateSimpleAtomMatcher("DIFFERENT_NAME", /*atom=*/10); + int64_t matcherId = newMatcher.id(); + EXPECT_NE(matcherId, matcher.id()); + *newConfig.add_atom_matcher() = newMatcher; + + vector matchersToUpdate(1, UPDATE_UNKNOWN); + vector cycleTracker(1, false); + unordered_map newAtomMatchingTrackerMap; + newAtomMatchingTrackerMap[matcherId] = 0; + EXPECT_TRUE(determineMatcherUpdateStatus(newConfig, 0, oldAtomMatchingTrackerMap, + oldAtomMatchingTrackers, newAtomMatchingTrackerMap, + matchersToUpdate, cycleTracker)); + EXPECT_EQ(matchersToUpdate[0], UPDATE_NEW); +} + +TEST_F(ConfigUpdateTest, TestCombinationMatcherPreserve) { + StatsdConfig config; + AtomMatcher matcher1 = CreateSimpleAtomMatcher("TEST1", /*atom=*/10); + int64_t matcher1Id = matcher1.id(); + *config.add_atom_matcher() = matcher1; + + AtomMatcher matcher2 = CreateSimpleAtomMatcher("TEST2", /*atom=*/11); + *config.add_atom_matcher() = matcher2; + int64_t matcher2Id = matcher2.id(); + + AtomMatcher matcher3; + matcher3.set_id(StringToId("TEST3")); + AtomMatcher_Combination* combination = matcher3.mutable_combination(); + combination->set_operation(LogicalOperation::OR); + combination->add_matcher(matcher1Id); + combination->add_matcher(matcher2Id); + int64_t matcher3Id = matcher3.id(); + *config.add_atom_matcher() = matcher3; + + EXPECT_TRUE(initConfig(config)); + + StatsdConfig newConfig; + unordered_map newAtomMatchingTrackerMap; + // Same matchers, different order, all should be preserved. + *newConfig.add_atom_matcher() = matcher2; + newAtomMatchingTrackerMap[matcher2Id] = 0; + *newConfig.add_atom_matcher() = matcher3; + newAtomMatchingTrackerMap[matcher3Id] = 1; + *newConfig.add_atom_matcher() = matcher1; + newAtomMatchingTrackerMap[matcher1Id] = 2; + + vector matchersToUpdate(3, UPDATE_UNKNOWN); + vector cycleTracker(3, false); + // Only update the combination. It should recurse the two child matchers and preserve all 3. + EXPECT_TRUE(determineMatcherUpdateStatus(newConfig, 1, oldAtomMatchingTrackerMap, + oldAtomMatchingTrackers, newAtomMatchingTrackerMap, + matchersToUpdate, cycleTracker)); + EXPECT_EQ(matchersToUpdate[0], UPDATE_PRESERVE); + EXPECT_EQ(matchersToUpdate[1], UPDATE_PRESERVE); + EXPECT_EQ(matchersToUpdate[2], UPDATE_PRESERVE); +} + +TEST_F(ConfigUpdateTest, TestCombinationMatcherReplace) { + StatsdConfig config; + AtomMatcher matcher1 = CreateSimpleAtomMatcher("TEST1", /*atom=*/10); + int64_t matcher1Id = matcher1.id(); + *config.add_atom_matcher() = matcher1; + + AtomMatcher matcher2 = CreateSimpleAtomMatcher("TEST2", /*atom=*/11); + *config.add_atom_matcher() = matcher2; + int64_t matcher2Id = matcher2.id(); + + AtomMatcher matcher3; + matcher3.set_id(StringToId("TEST3")); + AtomMatcher_Combination* combination = matcher3.mutable_combination(); + combination->set_operation(LogicalOperation::OR); + combination->add_matcher(matcher1Id); + combination->add_matcher(matcher2Id); + int64_t matcher3Id = matcher3.id(); + *config.add_atom_matcher() = matcher3; + + EXPECT_TRUE(initConfig(config)); + + // Change the logical operation of the combination matcher, causing a replacement. + matcher3.mutable_combination()->set_operation(LogicalOperation::AND); + + StatsdConfig newConfig; + unordered_map newAtomMatchingTrackerMap; + *newConfig.add_atom_matcher() = matcher2; + newAtomMatchingTrackerMap[matcher2Id] = 0; + *newConfig.add_atom_matcher() = matcher3; + newAtomMatchingTrackerMap[matcher3Id] = 1; + *newConfig.add_atom_matcher() = matcher1; + newAtomMatchingTrackerMap[matcher1Id] = 2; + + vector matchersToUpdate(3, UPDATE_UNKNOWN); + vector cycleTracker(3, false); + // Only update the combination. The simple matchers should not be evaluated. + EXPECT_TRUE(determineMatcherUpdateStatus(newConfig, 1, oldAtomMatchingTrackerMap, + oldAtomMatchingTrackers, newAtomMatchingTrackerMap, + matchersToUpdate, cycleTracker)); + EXPECT_EQ(matchersToUpdate[0], UPDATE_UNKNOWN); + EXPECT_EQ(matchersToUpdate[1], UPDATE_REPLACE); + EXPECT_EQ(matchersToUpdate[2], UPDATE_UNKNOWN); +} + +TEST_F(ConfigUpdateTest, TestCombinationMatcherDepsChange) { + StatsdConfig config; + AtomMatcher matcher1 = CreateSimpleAtomMatcher("TEST1", /*atom=*/10); + int64_t matcher1Id = matcher1.id(); + *config.add_atom_matcher() = matcher1; + + AtomMatcher matcher2 = CreateSimpleAtomMatcher("TEST2", /*atom=*/11); + *config.add_atom_matcher() = matcher2; + int64_t matcher2Id = matcher2.id(); + + AtomMatcher matcher3; + matcher3.set_id(StringToId("TEST3")); + AtomMatcher_Combination* combination = matcher3.mutable_combination(); + combination->set_operation(LogicalOperation::OR); + combination->add_matcher(matcher1Id); + combination->add_matcher(matcher2Id); + int64_t matcher3Id = matcher3.id(); + *config.add_atom_matcher() = matcher3; + + EXPECT_TRUE(initConfig(config)); + + // Change a dependency of matcher 3. + matcher2.mutable_simple_atom_matcher()->set_atom_id(12); + + StatsdConfig newConfig; + unordered_map newAtomMatchingTrackerMap; + *newConfig.add_atom_matcher() = matcher2; + newAtomMatchingTrackerMap[matcher2Id] = 0; + *newConfig.add_atom_matcher() = matcher3; + newAtomMatchingTrackerMap[matcher3Id] = 1; + *newConfig.add_atom_matcher() = matcher1; + newAtomMatchingTrackerMap[matcher1Id] = 2; + + vector matchersToUpdate(3, UPDATE_UNKNOWN); + vector cycleTracker(3, false); + // Only update the combination. + EXPECT_TRUE(determineMatcherUpdateStatus(newConfig, 1, oldAtomMatchingTrackerMap, + oldAtomMatchingTrackers, newAtomMatchingTrackerMap, + matchersToUpdate, cycleTracker)); + // Matcher 2 and matcher3 must be reevaluated. Matcher 1 might, but does not need to be. + EXPECT_EQ(matchersToUpdate[0], UPDATE_REPLACE); + EXPECT_EQ(matchersToUpdate[1], UPDATE_REPLACE); +} + +TEST_F(ConfigUpdateTest, TestUpdateMatchers) { + StatsdConfig config; + // Will be preserved. + AtomMatcher simple1 = CreateSimpleAtomMatcher("SIMPLE1", /*atom=*/10); + int64_t simple1Id = simple1.id(); + *config.add_atom_matcher() = simple1; + + // Will be replaced. + AtomMatcher simple2 = CreateSimpleAtomMatcher("SIMPLE2", /*atom=*/11); + *config.add_atom_matcher() = simple2; + int64_t simple2Id = simple2.id(); + + // Will be removed. + AtomMatcher simple3 = CreateSimpleAtomMatcher("SIMPLE3", /*atom=*/12); + *config.add_atom_matcher() = simple3; + int64_t simple3Id = simple3.id(); + + // Will be preserved. + AtomMatcher combination1; + combination1.set_id(StringToId("combination1")); + AtomMatcher_Combination* combination = combination1.mutable_combination(); + combination->set_operation(LogicalOperation::NOT); + combination->add_matcher(simple1Id); + int64_t combination1Id = combination1.id(); + *config.add_atom_matcher() = combination1; + + // Will be replaced since it depends on simple2. + AtomMatcher combination2; + combination2.set_id(StringToId("combination2")); + combination = combination2.mutable_combination(); + combination->set_operation(LogicalOperation::AND); + combination->add_matcher(simple1Id); + combination->add_matcher(simple2Id); + int64_t combination2Id = combination2.id(); + *config.add_atom_matcher() = combination2; + + EXPECT_TRUE(initConfig(config)); + + // Change simple2, causing simple2 and combination2 to be replaced. + simple2.mutable_simple_atom_matcher()->set_atom_id(111); + + // 2 new matchers: simple4 and combination3: + AtomMatcher simple4 = CreateSimpleAtomMatcher("SIMPLE4", /*atom=*/13); + int64_t simple4Id = simple4.id(); + + AtomMatcher combination3; + combination3.set_id(StringToId("combination3")); + combination = combination3.mutable_combination(); + combination->set_operation(LogicalOperation::AND); + combination->add_matcher(simple4Id); + combination->add_matcher(simple2Id); + int64_t combination3Id = combination3.id(); + + StatsdConfig newConfig; + *newConfig.add_atom_matcher() = combination3; + *newConfig.add_atom_matcher() = simple2; + *newConfig.add_atom_matcher() = combination2; + *newConfig.add_atom_matcher() = simple1; + *newConfig.add_atom_matcher() = simple4; + *newConfig.add_atom_matcher() = combination1; + + set newTagIds; + unordered_map newAtomMatchingTrackerMap; + vector> newAtomMatchingTrackers; + set replacedMatchers; + EXPECT_TRUE(updateAtomMatchingTrackers( + newConfig, uidMap, oldAtomMatchingTrackerMap, oldAtomMatchingTrackers, newTagIds, + newAtomMatchingTrackerMap, newAtomMatchingTrackers, replacedMatchers)); + + ASSERT_EQ(newTagIds.size(), 3); + EXPECT_EQ(newTagIds.count(10), 1); + EXPECT_EQ(newTagIds.count(111), 1); + EXPECT_EQ(newTagIds.count(13), 1); + + ASSERT_EQ(newAtomMatchingTrackerMap.size(), 6); + EXPECT_EQ(newAtomMatchingTrackerMap.at(combination3Id), 0); + EXPECT_EQ(newAtomMatchingTrackerMap.at(simple2Id), 1); + EXPECT_EQ(newAtomMatchingTrackerMap.at(combination2Id), 2); + EXPECT_EQ(newAtomMatchingTrackerMap.at(simple1Id), 3); + EXPECT_EQ(newAtomMatchingTrackerMap.at(simple4Id), 4); + EXPECT_EQ(newAtomMatchingTrackerMap.at(combination1Id), 5); + + ASSERT_EQ(newAtomMatchingTrackers.size(), 6); + // Make sure all atom matchers are initialized: + for (const sp& tracker : newAtomMatchingTrackers) { + EXPECT_TRUE(tracker->mInitialized); + } + // Make sure preserved atom matchers are the same. + EXPECT_EQ(oldAtomMatchingTrackers[oldAtomMatchingTrackerMap.at(simple1Id)], + newAtomMatchingTrackers[newAtomMatchingTrackerMap.at(simple1Id)]); + EXPECT_EQ(oldAtomMatchingTrackers[oldAtomMatchingTrackerMap.at(combination1Id)], + newAtomMatchingTrackers[newAtomMatchingTrackerMap.at(combination1Id)]); + // Make sure replaced matchers are different. + EXPECT_NE(oldAtomMatchingTrackers[oldAtomMatchingTrackerMap.at(simple2Id)], + newAtomMatchingTrackers[newAtomMatchingTrackerMap.at(simple2Id)]); + EXPECT_NE(oldAtomMatchingTrackers[oldAtomMatchingTrackerMap.at(combination2Id)], + newAtomMatchingTrackers[newAtomMatchingTrackerMap.at(combination2Id)]); + + // Validation, make sure the matchers have the proper ids/indices. Could do more checks here. + EXPECT_EQ(newAtomMatchingTrackers[0]->getId(), combination3Id); + EXPECT_EQ(newAtomMatchingTrackers[0]->mIndex, 0); + EXPECT_EQ(newAtomMatchingTrackers[1]->getId(), simple2Id); + EXPECT_EQ(newAtomMatchingTrackers[1]->mIndex, 1); + EXPECT_EQ(newAtomMatchingTrackers[2]->getId(), combination2Id); + EXPECT_EQ(newAtomMatchingTrackers[2]->mIndex, 2); + EXPECT_EQ(newAtomMatchingTrackers[3]->getId(), simple1Id); + EXPECT_EQ(newAtomMatchingTrackers[3]->mIndex, 3); + EXPECT_EQ(newAtomMatchingTrackers[4]->getId(), simple4Id); + EXPECT_EQ(newAtomMatchingTrackers[4]->mIndex, 4); + EXPECT_EQ(newAtomMatchingTrackers[5]->getId(), combination1Id); + EXPECT_EQ(newAtomMatchingTrackers[5]->mIndex, 5); + + // Verify child indices of Combination Matchers are correct. + CombinationAtomMatchingTracker* combinationTracker1 = + static_cast(newAtomMatchingTrackers[5].get()); + vector* childMatchers = &combinationTracker1->mChildren; + EXPECT_EQ(childMatchers->size(), 1); + EXPECT_NE(std::find(childMatchers->begin(), childMatchers->end(), 3), childMatchers->end()); + + CombinationAtomMatchingTracker* combinationTracker2 = + static_cast(newAtomMatchingTrackers[2].get()); + childMatchers = &combinationTracker2->mChildren; + EXPECT_EQ(childMatchers->size(), 2); + EXPECT_NE(std::find(childMatchers->begin(), childMatchers->end(), 1), childMatchers->end()); + EXPECT_NE(std::find(childMatchers->begin(), childMatchers->end(), 3), childMatchers->end()); + + CombinationAtomMatchingTracker* combinationTracker3 = + static_cast(newAtomMatchingTrackers[0].get()); + childMatchers = &combinationTracker3->mChildren; + EXPECT_EQ(childMatchers->size(), 2); + EXPECT_NE(std::find(childMatchers->begin(), childMatchers->end(), 1), childMatchers->end()); + EXPECT_NE(std::find(childMatchers->begin(), childMatchers->end(), 4), childMatchers->end()); + + // Expect replacedMatchers to have simple2 and combination2 + ASSERT_EQ(replacedMatchers.size(), 2); + EXPECT_NE(replacedMatchers.find(simple2Id), replacedMatchers.end()); + EXPECT_NE(replacedMatchers.find(combination2Id), replacedMatchers.end()); +} + +TEST_F(ConfigUpdateTest, TestSimpleConditionPreserve) { + StatsdConfig config; + AtomMatcher startMatcher = CreateScreenTurnedOnAtomMatcher(); + *config.add_atom_matcher() = startMatcher; + AtomMatcher stopMatcher = CreateScreenTurnedOffAtomMatcher(); + *config.add_atom_matcher() = stopMatcher; + + Predicate predicate = CreateScreenIsOnPredicate(); + *config.add_predicate() = predicate; + + // Create an initial config. + EXPECT_TRUE(initConfig(config)); + + set replacedMatchers; + vector conditionsToUpdate(1, UPDATE_UNKNOWN); + vector cycleTracker(1, false); + unordered_map newConditionTrackerMap; + newConditionTrackerMap[predicate.id()] = 0; + EXPECT_TRUE(determineConditionUpdateStatus(config, 0, oldConditionTrackerMap, + oldConditionTrackers, newConditionTrackerMap, + replacedMatchers, conditionsToUpdate, cycleTracker)); + EXPECT_EQ(conditionsToUpdate[0], UPDATE_PRESERVE); +} + +TEST_F(ConfigUpdateTest, TestSimpleConditionReplace) { + StatsdConfig config; + AtomMatcher startMatcher = CreateScreenTurnedOnAtomMatcher(); + *config.add_atom_matcher() = startMatcher; + AtomMatcher stopMatcher = CreateScreenTurnedOffAtomMatcher(); + *config.add_atom_matcher() = stopMatcher; + + Predicate predicate = CreateScreenIsOnPredicate(); + *config.add_predicate() = predicate; + + EXPECT_TRUE(initConfig(config)); + + // Modify the predicate. + config.mutable_predicate(0)->mutable_simple_predicate()->set_count_nesting(true); + + set replacedMatchers; + vector conditionsToUpdate(1, UPDATE_UNKNOWN); + vector cycleTracker(1, false); + unordered_map newConditionTrackerMap; + newConditionTrackerMap[predicate.id()] = 0; + EXPECT_TRUE(determineConditionUpdateStatus(config, 0, oldConditionTrackerMap, + oldConditionTrackers, newConditionTrackerMap, + replacedMatchers, conditionsToUpdate, cycleTracker)); + EXPECT_EQ(conditionsToUpdate[0], UPDATE_REPLACE); +} + +TEST_F(ConfigUpdateTest, TestSimpleConditionDepsChange) { + StatsdConfig config; + AtomMatcher startMatcher = CreateScreenTurnedOnAtomMatcher(); + int64_t startMatcherId = startMatcher.id(); + *config.add_atom_matcher() = startMatcher; + AtomMatcher stopMatcher = CreateScreenTurnedOffAtomMatcher(); + *config.add_atom_matcher() = stopMatcher; + + Predicate predicate = CreateScreenIsOnPredicate(); + *config.add_predicate() = predicate; + + EXPECT_TRUE(initConfig(config)); + + // Start matcher was replaced. + set replacedMatchers; + replacedMatchers.insert(startMatcherId); + + vector conditionsToUpdate(1, UPDATE_UNKNOWN); + vector cycleTracker(1, false); + unordered_map newConditionTrackerMap; + newConditionTrackerMap[predicate.id()] = 0; + EXPECT_TRUE(determineConditionUpdateStatus(config, 0, oldConditionTrackerMap, + oldConditionTrackers, newConditionTrackerMap, + replacedMatchers, conditionsToUpdate, cycleTracker)); + EXPECT_EQ(conditionsToUpdate[0], UPDATE_REPLACE); +} + +TEST_F(ConfigUpdateTest, TestCombinationConditionPreserve) { + StatsdConfig config; + AtomMatcher screenOnMatcher = CreateScreenTurnedOnAtomMatcher(); + *config.add_atom_matcher() = screenOnMatcher; + AtomMatcher screenOffMatcher = CreateScreenTurnedOffAtomMatcher(); + *config.add_atom_matcher() = screenOffMatcher; + + Predicate simple1 = CreateScreenIsOnPredicate(); + *config.add_predicate() = simple1; + Predicate simple2 = CreateScreenIsOffPredicate(); + *config.add_predicate() = simple2; + + Predicate combination1; + combination1.set_id(StringToId("COMBINATION1")); + Predicate_Combination* combinationInternal = combination1.mutable_combination(); + combinationInternal->set_operation(LogicalOperation::NAND); + combinationInternal->add_predicate(simple1.id()); + combinationInternal->add_predicate(simple2.id()); + *config.add_predicate() = combination1; + + EXPECT_TRUE(initConfig(config)); + + // Same predicates, different order + StatsdConfig newConfig; + unordered_map newConditionTrackerMap; + *newConfig.add_predicate() = combination1; + newConditionTrackerMap[combination1.id()] = 0; + *newConfig.add_predicate() = simple2; + newConditionTrackerMap[simple2.id()] = 1; + *newConfig.add_predicate() = simple1; + newConditionTrackerMap[simple1.id()] = 2; + + set replacedMatchers; + vector conditionsToUpdate(3, UPDATE_UNKNOWN); + vector cycleTracker(3, false); + // Only update the combination. It should recurse the two child predicates and preserve all 3. + EXPECT_TRUE(determineConditionUpdateStatus(newConfig, 0, oldConditionTrackerMap, + oldConditionTrackers, newConditionTrackerMap, + replacedMatchers, conditionsToUpdate, cycleTracker)); + EXPECT_EQ(conditionsToUpdate[0], UPDATE_PRESERVE); + EXPECT_EQ(conditionsToUpdate[1], UPDATE_PRESERVE); + EXPECT_EQ(conditionsToUpdate[2], UPDATE_PRESERVE); +} + +TEST_F(ConfigUpdateTest, TestCombinationConditionReplace) { + StatsdConfig config; + AtomMatcher screenOnMatcher = CreateScreenTurnedOnAtomMatcher(); + *config.add_atom_matcher() = screenOnMatcher; + AtomMatcher screenOffMatcher = CreateScreenTurnedOffAtomMatcher(); + *config.add_atom_matcher() = screenOffMatcher; + + Predicate simple1 = CreateScreenIsOnPredicate(); + *config.add_predicate() = simple1; + Predicate simple2 = CreateScreenIsOffPredicate(); + *config.add_predicate() = simple2; + + Predicate combination1; + combination1.set_id(StringToId("COMBINATION1")); + Predicate_Combination* combinationInternal = combination1.mutable_combination(); + combinationInternal->set_operation(LogicalOperation::NAND); + combinationInternal->add_predicate(simple1.id()); + combinationInternal->add_predicate(simple2.id()); + *config.add_predicate() = combination1; + + EXPECT_TRUE(initConfig(config)); + + // Changing the logical operation changes the predicate definition, so it should be replaced. + combination1.mutable_combination()->set_operation(LogicalOperation::OR); + + StatsdConfig newConfig; + unordered_map newConditionTrackerMap; + *newConfig.add_predicate() = combination1; + newConditionTrackerMap[combination1.id()] = 0; + *newConfig.add_predicate() = simple2; + newConditionTrackerMap[simple2.id()] = 1; + *newConfig.add_predicate() = simple1; + newConditionTrackerMap[simple1.id()] = 2; + + set replacedMatchers; + vector conditionsToUpdate(3, UPDATE_UNKNOWN); + vector cycleTracker(3, false); + // Only update the combination. The simple conditions should not be evaluated. + EXPECT_TRUE(determineConditionUpdateStatus(newConfig, 0, oldConditionTrackerMap, + oldConditionTrackers, newConditionTrackerMap, + replacedMatchers, conditionsToUpdate, cycleTracker)); + EXPECT_EQ(conditionsToUpdate[0], UPDATE_REPLACE); + EXPECT_EQ(conditionsToUpdate[1], UPDATE_UNKNOWN); + EXPECT_EQ(conditionsToUpdate[2], UPDATE_UNKNOWN); +} + +TEST_F(ConfigUpdateTest, TestCombinationConditionDepsChange) { + StatsdConfig config; + AtomMatcher screenOnMatcher = CreateScreenTurnedOnAtomMatcher(); + *config.add_atom_matcher() = screenOnMatcher; + AtomMatcher screenOffMatcher = CreateScreenTurnedOffAtomMatcher(); + *config.add_atom_matcher() = screenOffMatcher; + + Predicate simple1 = CreateScreenIsOnPredicate(); + *config.add_predicate() = simple1; + Predicate simple2 = CreateScreenIsOffPredicate(); + *config.add_predicate() = simple2; + + Predicate combination1; + combination1.set_id(StringToId("COMBINATION1")); + Predicate_Combination* combinationInternal = combination1.mutable_combination(); + combinationInternal->set_operation(LogicalOperation::NAND); + combinationInternal->add_predicate(simple1.id()); + combinationInternal->add_predicate(simple2.id()); + *config.add_predicate() = combination1; + + EXPECT_TRUE(initConfig(config)); + + simple2.mutable_simple_predicate()->set_count_nesting(false); + + StatsdConfig newConfig; + unordered_map newConditionTrackerMap; + *newConfig.add_predicate() = combination1; + newConditionTrackerMap[combination1.id()] = 0; + *newConfig.add_predicate() = simple2; + newConditionTrackerMap[simple2.id()] = 1; + *newConfig.add_predicate() = simple1; + newConditionTrackerMap[simple1.id()] = 2; + + set replacedMatchers; + vector conditionsToUpdate(3, UPDATE_UNKNOWN); + vector cycleTracker(3, false); + // Only update the combination. Simple2 and combination1 must be evaluated. + EXPECT_TRUE(determineConditionUpdateStatus(newConfig, 0, oldConditionTrackerMap, + oldConditionTrackers, newConditionTrackerMap, + replacedMatchers, conditionsToUpdate, cycleTracker)); + EXPECT_EQ(conditionsToUpdate[0], UPDATE_REPLACE); + EXPECT_EQ(conditionsToUpdate[1], UPDATE_REPLACE); +} + +TEST_F(ConfigUpdateTest, TestUpdateConditions) { + StatsdConfig config; + // Add atom matchers. These are mostly needed for initStatsdConfig + AtomMatcher matcher1 = CreateScreenTurnedOnAtomMatcher(); + int64_t matcher1Id = matcher1.id(); + *config.add_atom_matcher() = matcher1; + + AtomMatcher matcher2 = CreateScreenTurnedOffAtomMatcher(); + int64_t matcher2Id = matcher2.id(); + *config.add_atom_matcher() = matcher2; + + AtomMatcher matcher3 = CreateStartScheduledJobAtomMatcher(); + int64_t matcher3Id = matcher3.id(); + *config.add_atom_matcher() = matcher3; + + AtomMatcher matcher4 = CreateFinishScheduledJobAtomMatcher(); + int64_t matcher4Id = matcher4.id(); + *config.add_atom_matcher() = matcher4; + + AtomMatcher matcher5 = CreateBatterySaverModeStartAtomMatcher(); + int64_t matcher5Id = matcher5.id(); + *config.add_atom_matcher() = matcher5; + + AtomMatcher matcher6 = CreateBatterySaverModeStopAtomMatcher(); + int64_t matcher6Id = matcher6.id(); + *config.add_atom_matcher() = matcher6; + + // Add the predicates. + // Will be preserved. + Predicate simple1 = CreateScreenIsOnPredicate(); + int64_t simple1Id = simple1.id(); + *config.add_predicate() = simple1; + + // Will be preserved. + Predicate simple2 = CreateScheduledJobPredicate(); + int64_t simple2Id = simple2.id(); + *config.add_predicate() = simple2; + + // Will be replaced. + Predicate simple3 = CreateBatterySaverModePredicate(); + int64_t simple3Id = simple3.id(); + *config.add_predicate() = simple3; + + // Will be preserved + Predicate combination1; + combination1.set_id(StringToId("COMBINATION1")); + combination1.mutable_combination()->set_operation(LogicalOperation::AND); + combination1.mutable_combination()->add_predicate(simple1Id); + combination1.mutable_combination()->add_predicate(simple2Id); + int64_t combination1Id = combination1.id(); + *config.add_predicate() = combination1; + + // Will be replaced since simple3 will be replaced. + Predicate combination2; + combination2.set_id(StringToId("COMBINATION2")); + combination2.mutable_combination()->set_operation(LogicalOperation::OR); + combination2.mutable_combination()->add_predicate(simple1Id); + combination2.mutable_combination()->add_predicate(simple3Id); + int64_t combination2Id = combination2.id(); + *config.add_predicate() = combination2; + + // Will be removed. + Predicate combination3; + combination3.set_id(StringToId("COMBINATION3")); + combination3.mutable_combination()->set_operation(LogicalOperation::NOT); + combination3.mutable_combination()->add_predicate(simple2Id); + int64_t combination3Id = combination3.id(); + *config.add_predicate() = combination3; + + EXPECT_TRUE(initConfig(config)); + + // Mark marcher 5 as replaced. Causes simple3, and therefore combination2 to be replaced. + set replacedMatchers; + replacedMatchers.insert(matcher6Id); + + // Change the condition of simple1 to false. + ASSERT_EQ(oldConditionTrackers[0]->getConditionId(), simple1Id); + LogEvent event(/*uid=*/0, /*pid=*/0); // Empty event is fine since there are no dimensions. + // Mark the stop matcher as matched, condition should be false. + vector eventMatcherValues(6, MatchingState::kNotMatched); + eventMatcherValues[1] = MatchingState::kMatched; + vector tmpConditionCache(6, ConditionState::kNotEvaluated); + vector conditionChangeCache(6, false); + oldConditionTrackers[0]->evaluateCondition(event, eventMatcherValues, oldConditionTrackers, + tmpConditionCache, conditionChangeCache); + EXPECT_EQ(tmpConditionCache[0], ConditionState::kFalse); + EXPECT_EQ(conditionChangeCache[0], true); + + // New combination predicate. Should have an initial condition of true since it is NOT(simple1). + Predicate combination4; + combination4.set_id(StringToId("COMBINATION4")); + combination4.mutable_combination()->set_operation(LogicalOperation::NOT); + combination4.mutable_combination()->add_predicate(simple1Id); + int64_t combination4Id = combination4.id(); + *config.add_predicate() = combination4; + + // Map the matchers in reverse order to force the indices to change. + std::unordered_map newAtomMatchingTrackerMap; + const int matcher6Index = 0; + newAtomMatchingTrackerMap[matcher6Id] = 0; + const int matcher5Index = 1; + newAtomMatchingTrackerMap[matcher5Id] = 1; + const int matcher4Index = 2; + newAtomMatchingTrackerMap[matcher4Id] = 2; + const int matcher3Index = 3; + newAtomMatchingTrackerMap[matcher3Id] = 3; + const int matcher2Index = 4; + newAtomMatchingTrackerMap[matcher2Id] = 4; + const int matcher1Index = 5; + newAtomMatchingTrackerMap[matcher1Id] = 5; + + StatsdConfig newConfig; + *newConfig.add_predicate() = simple3; + const int simple3Index = 0; + *newConfig.add_predicate() = combination2; + const int combination2Index = 1; + *newConfig.add_predicate() = combination4; + const int combination4Index = 2; + *newConfig.add_predicate() = simple2; + const int simple2Index = 3; + *newConfig.add_predicate() = combination1; + const int combination1Index = 4; + *newConfig.add_predicate() = simple1; + const int simple1Index = 5; + + unordered_map newConditionTrackerMap; + vector> newConditionTrackers; + unordered_map> trackerToConditionMap; + std::vector conditionCache; + std::set replacedConditions; + EXPECT_TRUE(updateConditions(key, newConfig, newAtomMatchingTrackerMap, replacedMatchers, + oldConditionTrackerMap, oldConditionTrackers, + newConditionTrackerMap, newConditionTrackers, + trackerToConditionMap, conditionCache, replacedConditions)); + + unordered_map expectedConditionTrackerMap = { + {simple1Id, simple1Index}, {simple2Id, simple2Index}, + {simple3Id, simple3Index}, {combination1Id, combination1Index}, + {combination2Id, combination2Index}, {combination4Id, combination4Index}, + }; + EXPECT_THAT(newConditionTrackerMap, ContainerEq(expectedConditionTrackerMap)); + + ASSERT_EQ(newConditionTrackers.size(), 6); + // Make sure all conditions are initialized: + for (const sp& tracker : newConditionTrackers) { + EXPECT_TRUE(tracker->mInitialized); + } + + // Make sure preserved conditions are the same. + EXPECT_EQ(oldConditionTrackers[oldConditionTrackerMap.at(simple1Id)], + newConditionTrackers[newConditionTrackerMap.at(simple1Id)]); + EXPECT_EQ(oldConditionTrackers[oldConditionTrackerMap.at(simple2Id)], + newConditionTrackers[newConditionTrackerMap.at(simple2Id)]); + EXPECT_EQ(oldConditionTrackers[oldConditionTrackerMap.at(combination1Id)], + newConditionTrackers[newConditionTrackerMap.at(combination1Id)]); + + // Make sure replaced conditions are different and included in replacedConditions. + EXPECT_NE(oldConditionTrackers[oldConditionTrackerMap.at(simple3Id)], + newConditionTrackers[newConditionTrackerMap.at(simple3Id)]); + EXPECT_NE(oldConditionTrackers[oldConditionTrackerMap.at(combination2Id)], + newConditionTrackers[newConditionTrackerMap.at(combination2Id)]); + EXPECT_THAT(replacedConditions, ContainerEq(set({simple3Id, combination2Id}))); + + // Verify the trackerToConditionMap + ASSERT_EQ(trackerToConditionMap.size(), 6); + const vector& matcher1Conditions = trackerToConditionMap[matcher1Index]; + EXPECT_THAT(matcher1Conditions, UnorderedElementsAre(simple1Index, combination1Index, + combination2Index, combination4Index)); + const vector& matcher2Conditions = trackerToConditionMap[matcher2Index]; + EXPECT_THAT(matcher2Conditions, UnorderedElementsAre(simple1Index, combination1Index, + combination2Index, combination4Index)); + const vector& matcher3Conditions = trackerToConditionMap[matcher3Index]; + EXPECT_THAT(matcher3Conditions, UnorderedElementsAre(simple2Index, combination1Index)); + const vector& matcher4Conditions = trackerToConditionMap[matcher4Index]; + EXPECT_THAT(matcher4Conditions, UnorderedElementsAre(simple2Index, combination1Index)); + const vector& matcher5Conditions = trackerToConditionMap[matcher5Index]; + EXPECT_THAT(matcher5Conditions, UnorderedElementsAre(simple3Index, combination2Index)); + const vector& matcher6Conditions = trackerToConditionMap[matcher6Index]; + EXPECT_THAT(matcher6Conditions, UnorderedElementsAre(simple3Index, combination2Index)); + + // Verify the conditionCache. Specifically, simple1 is false and combination4 is true. + ASSERT_EQ(conditionCache.size(), 6); + EXPECT_EQ(conditionCache[simple1Index], ConditionState::kFalse); + EXPECT_EQ(conditionCache[simple2Index], ConditionState::kUnknown); + EXPECT_EQ(conditionCache[simple3Index], ConditionState::kUnknown); + EXPECT_EQ(conditionCache[combination1Index], ConditionState::kUnknown); + EXPECT_EQ(conditionCache[combination2Index], ConditionState::kUnknown); + EXPECT_EQ(conditionCache[combination4Index], ConditionState::kTrue); + + // Verify tracker indices/ids are correct. + EXPECT_EQ(newConditionTrackers[simple1Index]->getConditionId(), simple1Id); + EXPECT_EQ(newConditionTrackers[simple1Index]->mIndex, simple1Index); + EXPECT_TRUE(newConditionTrackers[simple1Index]->IsSimpleCondition()); + EXPECT_EQ(newConditionTrackers[simple2Index]->getConditionId(), simple2Id); + EXPECT_EQ(newConditionTrackers[simple2Index]->mIndex, simple2Index); + EXPECT_TRUE(newConditionTrackers[simple2Index]->IsSimpleCondition()); + EXPECT_EQ(newConditionTrackers[simple3Index]->getConditionId(), simple3Id); + EXPECT_EQ(newConditionTrackers[simple3Index]->mIndex, simple3Index); + EXPECT_TRUE(newConditionTrackers[simple3Index]->IsSimpleCondition()); + EXPECT_EQ(newConditionTrackers[combination1Index]->getConditionId(), combination1Id); + EXPECT_EQ(newConditionTrackers[combination1Index]->mIndex, combination1Index); + EXPECT_FALSE(newConditionTrackers[combination1Index]->IsSimpleCondition()); + EXPECT_EQ(newConditionTrackers[combination2Index]->getConditionId(), combination2Id); + EXPECT_EQ(newConditionTrackers[combination2Index]->mIndex, combination2Index); + EXPECT_FALSE(newConditionTrackers[combination2Index]->IsSimpleCondition()); + EXPECT_EQ(newConditionTrackers[combination4Index]->getConditionId(), combination4Id); + EXPECT_EQ(newConditionTrackers[combination4Index]->mIndex, combination4Index); + EXPECT_FALSE(newConditionTrackers[combination4Index]->IsSimpleCondition()); + + // Verify preserved trackers have indices updated. + SimpleConditionTracker* simpleTracker1 = + static_cast(newConditionTrackers[simple1Index].get()); + EXPECT_EQ(simpleTracker1->mStartLogMatcherIndex, matcher1Index); + EXPECT_EQ(simpleTracker1->mStopLogMatcherIndex, matcher2Index); + EXPECT_EQ(simpleTracker1->mStopAllLogMatcherIndex, -1); + + SimpleConditionTracker* simpleTracker2 = + static_cast(newConditionTrackers[simple2Index].get()); + EXPECT_EQ(simpleTracker2->mStartLogMatcherIndex, matcher3Index); + EXPECT_EQ(simpleTracker2->mStopLogMatcherIndex, matcher4Index); + EXPECT_EQ(simpleTracker2->mStopAllLogMatcherIndex, -1); + + CombinationConditionTracker* combinationTracker1 = static_cast( + newConditionTrackers[combination1Index].get()); + EXPECT_THAT(combinationTracker1->mChildren, UnorderedElementsAre(simple1Index, simple2Index)); + EXPECT_THAT(combinationTracker1->mUnSlicedChildren, + UnorderedElementsAre(simple1Index, simple2Index)); + EXPECT_THAT(combinationTracker1->mSlicedChildren, IsEmpty()); +} + +TEST_F(ConfigUpdateTest, TestUpdateStates) { + StatsdConfig config; + // Add states. + // Will be replaced because we add a state map. + State state1 = CreateScreenState(); + int64_t state1Id = state1.id(); + *config.add_state() = state1; + + // Will be preserved. + State state2 = CreateUidProcessState(); + int64_t state2Id = state2.id(); + *config.add_state() = state2; + + // Will be replaced since the atom changes from overlay to screen. + State state3 = CreateOverlayState(); + int64_t state3Id = state3.id(); + *config.add_state() = state3; + + EXPECT_TRUE(initConfig(config)); + + // Change definitions of state1 and state3. + int64_t screenOnId = 0x4321, screenOffId = 0x1234; + *state1.mutable_map() = CreateScreenStateSimpleOnOffMap(screenOnId, screenOffId); + state3.set_atom_id(util::SCREEN_STATE_CHANGED); + + StatsdConfig newConfig; + *newConfig.add_state() = state3; + *newConfig.add_state() = state1; + *newConfig.add_state() = state2; + + unordered_map stateAtomIdMap; + unordered_map> allStateGroupMaps; + map newStateProtoHashes; + set replacedStates; + EXPECT_TRUE(updateStates(newConfig, oldStateHashes, stateAtomIdMap, allStateGroupMaps, + newStateProtoHashes, replacedStates)); + EXPECT_THAT(replacedStates, ContainerEq(set({state1Id, state3Id}))); + + unordered_map expectedStateAtomIdMap = { + {state1Id, util::SCREEN_STATE_CHANGED}, + {state2Id, util::UID_PROCESS_STATE_CHANGED}, + {state3Id, util::SCREEN_STATE_CHANGED}}; + EXPECT_THAT(stateAtomIdMap, ContainerEq(expectedStateAtomIdMap)); + + unordered_map> expectedStateGroupMaps = { + {state1Id, + {{android::view::DisplayStateEnum::DISPLAY_STATE_OFF, screenOffId}, + {android::view::DisplayStateEnum::DISPLAY_STATE_ON, screenOnId}}}}; + EXPECT_THAT(allStateGroupMaps, ContainerEq(expectedStateGroupMaps)); +} + +TEST_F(ConfigUpdateTest, TestEventMetricPreserve) { + StatsdConfig config; + AtomMatcher startMatcher = CreateScreenTurnedOnAtomMatcher(); + *config.add_atom_matcher() = startMatcher; + AtomMatcher stopMatcher = CreateScreenTurnedOffAtomMatcher(); + *config.add_atom_matcher() = stopMatcher; + AtomMatcher whatMatcher = CreateScreenBrightnessChangedAtomMatcher(); + *config.add_atom_matcher() = whatMatcher; + + Predicate predicate = CreateScreenIsOnPredicate(); + *config.add_predicate() = predicate; + + EventMetric* metric = config.add_event_metric(); + metric->set_id(12345); + metric->set_what(whatMatcher.id()); + metric->set_condition(predicate.id()); + + // Create an initial config. + EXPECT_TRUE(initConfig(config)); + + unordered_map metricToActivationMap; + vector metricsToUpdate(1, UPDATE_UNKNOWN); + EXPECT_TRUE(determineAllMetricUpdateStatuses(config, oldMetricProducerMap, oldMetricProducers, + metricToActivationMap, + /*replacedMatchers*/ {}, /*replacedConditions=*/{}, + /*replacedStates=*/{}, metricsToUpdate)); + EXPECT_EQ(metricsToUpdate[0], UPDATE_PRESERVE); +} + +TEST_F(ConfigUpdateTest, TestEventMetricActivationAdded) { + StatsdConfig config; + AtomMatcher startMatcher = CreateScreenTurnedOnAtomMatcher(); + *config.add_atom_matcher() = startMatcher; + AtomMatcher stopMatcher = CreateScreenTurnedOffAtomMatcher(); + *config.add_atom_matcher() = stopMatcher; + AtomMatcher whatMatcher = CreateScreenBrightnessChangedAtomMatcher(); + *config.add_atom_matcher() = whatMatcher; + + Predicate predicate = CreateScreenIsOnPredicate(); + *config.add_predicate() = predicate; + + EventMetric* metric = config.add_event_metric(); + metric->set_id(12345); + metric->set_what(whatMatcher.id()); + metric->set_condition(predicate.id()); + + // Create an initial config. + EXPECT_TRUE(initConfig(config)); + + // Add a metric activation, which should change the proto, causing replacement. + MetricActivation* activation = config.add_metric_activation(); + activation->set_metric_id(12345); + EventActivation* eventActivation = activation->add_event_activation(); + eventActivation->set_atom_matcher_id(startMatcher.id()); + eventActivation->set_ttl_seconds(5); + + unordered_map metricToActivationMap = {{12345, 0}}; + vector metricsToUpdate(1, UPDATE_UNKNOWN); + EXPECT_TRUE(determineAllMetricUpdateStatuses(config, oldMetricProducerMap, oldMetricProducers, + metricToActivationMap, + /*replacedMatchers*/ {}, /*replacedConditions=*/{}, + /*replacedStates=*/{}, metricsToUpdate)); + EXPECT_EQ(metricsToUpdate[0], UPDATE_REPLACE); +} + +TEST_F(ConfigUpdateTest, TestEventMetricWhatChanged) { + StatsdConfig config; + AtomMatcher startMatcher = CreateScreenTurnedOnAtomMatcher(); + *config.add_atom_matcher() = startMatcher; + AtomMatcher stopMatcher = CreateScreenTurnedOffAtomMatcher(); + *config.add_atom_matcher() = stopMatcher; + AtomMatcher whatMatcher = CreateScreenBrightnessChangedAtomMatcher(); + *config.add_atom_matcher() = whatMatcher; + + Predicate predicate = CreateScreenIsOnPredicate(); + *config.add_predicate() = predicate; + + EventMetric* metric = config.add_event_metric(); + metric->set_id(12345); + metric->set_what(whatMatcher.id()); + metric->set_condition(predicate.id()); + + // Create an initial config. + EXPECT_TRUE(initConfig(config)); + + unordered_map metricToActivationMap; + vector metricsToUpdate(1, UPDATE_UNKNOWN); + EXPECT_TRUE(determineAllMetricUpdateStatuses( + config, oldMetricProducerMap, oldMetricProducers, metricToActivationMap, + /*replacedMatchers*/ {whatMatcher.id()}, /*replacedConditions=*/{}, + /*replacedStates=*/{}, metricsToUpdate)); + EXPECT_EQ(metricsToUpdate[0], UPDATE_REPLACE); +} + +TEST_F(ConfigUpdateTest, TestEventMetricConditionChanged) { + StatsdConfig config; + AtomMatcher startMatcher = CreateScreenTurnedOnAtomMatcher(); + *config.add_atom_matcher() = startMatcher; + AtomMatcher stopMatcher = CreateScreenTurnedOffAtomMatcher(); + *config.add_atom_matcher() = stopMatcher; + AtomMatcher whatMatcher = CreateScreenBrightnessChangedAtomMatcher(); + *config.add_atom_matcher() = whatMatcher; + + Predicate predicate = CreateScreenIsOnPredicate(); + *config.add_predicate() = predicate; + + EventMetric* metric = config.add_event_metric(); + metric->set_id(12345); + metric->set_what(whatMatcher.id()); + metric->set_condition(predicate.id()); + + // Create an initial config. + EXPECT_TRUE(initConfig(config)); + + unordered_map metricToActivationMap; + vector metricsToUpdate(1, UPDATE_UNKNOWN); + EXPECT_TRUE(determineAllMetricUpdateStatuses( + config, oldMetricProducerMap, oldMetricProducers, metricToActivationMap, + /*replacedMatchers*/ {}, /*replacedConditions=*/{predicate.id()}, + /*replacedStates=*/{}, metricsToUpdate)); + EXPECT_EQ(metricsToUpdate[0], UPDATE_REPLACE); +} + +TEST_F(ConfigUpdateTest, TestMetricConditionLinkDepsChanged) { + StatsdConfig config; + AtomMatcher startMatcher = CreateScreenTurnedOnAtomMatcher(); + *config.add_atom_matcher() = startMatcher; + AtomMatcher stopMatcher = CreateScreenTurnedOffAtomMatcher(); + *config.add_atom_matcher() = stopMatcher; + AtomMatcher whatMatcher = CreateScreenBrightnessChangedAtomMatcher(); + *config.add_atom_matcher() = whatMatcher; + + Predicate predicate = CreateScreenIsOnPredicate(); + *config.add_predicate() = predicate; + + Predicate linkPredicate = CreateScreenIsOffPredicate(); + *config.add_predicate() = linkPredicate; + + EventMetric* metric = config.add_event_metric(); + metric->set_id(12345); + metric->set_what(whatMatcher.id()); + metric->set_condition(predicate.id()); + // Doesn't make sense as a real metric definition, but suffices as a separate predicate + // From the one in the condition. + MetricConditionLink* link = metric->add_links(); + link->set_condition(linkPredicate.id()); + + // Create an initial config. + EXPECT_TRUE(initConfig(config)); + + unordered_map metricToActivationMap; + vector metricsToUpdate(1, UPDATE_UNKNOWN); + EXPECT_TRUE(determineAllMetricUpdateStatuses( + config, oldMetricProducerMap, oldMetricProducers, metricToActivationMap, + /*replacedMatchers*/ {}, /*replacedConditions=*/{linkPredicate.id()}, + /*replacedStates=*/{}, metricsToUpdate)); + EXPECT_EQ(metricsToUpdate[0], UPDATE_REPLACE); +} + +TEST_F(ConfigUpdateTest, TestEventMetricActivationDepsChange) { + StatsdConfig config; + AtomMatcher startMatcher = CreateScreenTurnedOnAtomMatcher(); + *config.add_atom_matcher() = startMatcher; + AtomMatcher stopMatcher = CreateScreenTurnedOffAtomMatcher(); + *config.add_atom_matcher() = stopMatcher; + AtomMatcher whatMatcher = CreateScreenBrightnessChangedAtomMatcher(); + *config.add_atom_matcher() = whatMatcher; + + Predicate predicate = CreateScreenIsOnPredicate(); + *config.add_predicate() = predicate; + + EventMetric* metric = config.add_event_metric(); + metric->set_id(12345); + metric->set_what(whatMatcher.id()); + metric->set_condition(predicate.id()); + + MetricActivation* activation = config.add_metric_activation(); + activation->set_metric_id(12345); + EventActivation* eventActivation = activation->add_event_activation(); + eventActivation->set_atom_matcher_id(startMatcher.id()); + eventActivation->set_ttl_seconds(5); + + // Create an initial config. + EXPECT_TRUE(initConfig(config)); + + unordered_map metricToActivationMap = {{12345, 0}}; + vector metricsToUpdate(1, UPDATE_UNKNOWN); + EXPECT_TRUE(determineAllMetricUpdateStatuses( + config, oldMetricProducerMap, oldMetricProducers, metricToActivationMap, + /*replacedMatchers*/ {startMatcher.id()}, /*replacedConditions=*/{}, + /*replacedStates=*/{}, metricsToUpdate)); + EXPECT_EQ(metricsToUpdate[0], UPDATE_REPLACE); +} + +TEST_F(ConfigUpdateTest, TestCountMetricPreserve) { + StatsdConfig config; + AtomMatcher startMatcher = CreateScreenTurnedOnAtomMatcher(); + *config.add_atom_matcher() = startMatcher; + AtomMatcher stopMatcher = CreateScreenTurnedOffAtomMatcher(); + *config.add_atom_matcher() = stopMatcher; + AtomMatcher whatMatcher = CreateScreenBrightnessChangedAtomMatcher(); + *config.add_atom_matcher() = whatMatcher; + + Predicate predicate = CreateScreenIsOnPredicate(); + *config.add_predicate() = predicate; + State sliceState = CreateScreenState(); + *config.add_state() = sliceState; + + CountMetric* metric = config.add_count_metric(); + metric->set_id(12345); + metric->set_what(whatMatcher.id()); + metric->set_condition(predicate.id()); + metric->add_slice_by_state(sliceState.id()); + metric->set_bucket(ONE_HOUR); + + // Create an initial config. + EXPECT_TRUE(initConfig(config)); + + unordered_map metricToActivationMap; + vector metricsToUpdate(1, UPDATE_UNKNOWN); + EXPECT_TRUE(determineAllMetricUpdateStatuses(config, oldMetricProducerMap, oldMetricProducers, + metricToActivationMap, + /*replacedMatchers*/ {}, /*replacedConditions=*/{}, + /*replacedStates=*/{}, metricsToUpdate)); + EXPECT_EQ(metricsToUpdate[0], UPDATE_PRESERVE); +} + +TEST_F(ConfigUpdateTest, TestCountMetricDefinitionChange) { + StatsdConfig config; + AtomMatcher startMatcher = CreateScreenTurnedOnAtomMatcher(); + *config.add_atom_matcher() = startMatcher; + AtomMatcher stopMatcher = CreateScreenTurnedOffAtomMatcher(); + *config.add_atom_matcher() = stopMatcher; + AtomMatcher whatMatcher = CreateScreenBrightnessChangedAtomMatcher(); + *config.add_atom_matcher() = whatMatcher; + + Predicate predicate = CreateScreenIsOnPredicate(); + *config.add_predicate() = predicate; + + CountMetric* metric = config.add_count_metric(); + metric->set_id(12345); + metric->set_what(whatMatcher.id()); + metric->set_condition(predicate.id()); + metric->set_bucket(ONE_HOUR); + + // Create an initial config. + EXPECT_TRUE(initConfig(config)); + + // Change bucket size, which should change the proto, causing replacement. + metric->set_bucket(TEN_MINUTES); + + unordered_map metricToActivationMap; + vector metricsToUpdate(1, UPDATE_UNKNOWN); + EXPECT_TRUE(determineAllMetricUpdateStatuses(config, oldMetricProducerMap, oldMetricProducers, + metricToActivationMap, + /*replacedMatchers*/ {}, /*replacedConditions=*/{}, + /*replacedStates=*/{}, metricsToUpdate)); + EXPECT_EQ(metricsToUpdate[0], UPDATE_REPLACE); +} + +TEST_F(ConfigUpdateTest, TestCountMetricWhatChanged) { + StatsdConfig config; + AtomMatcher startMatcher = CreateScreenTurnedOnAtomMatcher(); + *config.add_atom_matcher() = startMatcher; + AtomMatcher stopMatcher = CreateScreenTurnedOffAtomMatcher(); + *config.add_atom_matcher() = stopMatcher; + AtomMatcher whatMatcher = CreateScreenBrightnessChangedAtomMatcher(); + *config.add_atom_matcher() = whatMatcher; + + Predicate predicate = CreateScreenIsOnPredicate(); + *config.add_predicate() = predicate; + + CountMetric* metric = config.add_count_metric(); + metric->set_id(12345); + metric->set_what(whatMatcher.id()); + metric->set_condition(predicate.id()); + metric->set_bucket(ONE_HOUR); + + // Create an initial config. + EXPECT_TRUE(initConfig(config)); + + unordered_map metricToActivationMap; + vector metricsToUpdate(1, UPDATE_UNKNOWN); + EXPECT_TRUE(determineAllMetricUpdateStatuses( + config, oldMetricProducerMap, oldMetricProducers, metricToActivationMap, + /*replacedMatchers*/ {whatMatcher.id()}, /*replacedConditions=*/{}, + /*replacedStates=*/{}, metricsToUpdate)); + EXPECT_EQ(metricsToUpdate[0], UPDATE_REPLACE); +} + +TEST_F(ConfigUpdateTest, TestCountMetricConditionChanged) { + StatsdConfig config; + AtomMatcher startMatcher = CreateScreenTurnedOnAtomMatcher(); + *config.add_atom_matcher() = startMatcher; + AtomMatcher stopMatcher = CreateScreenTurnedOffAtomMatcher(); + *config.add_atom_matcher() = stopMatcher; + AtomMatcher whatMatcher = CreateScreenBrightnessChangedAtomMatcher(); + *config.add_atom_matcher() = whatMatcher; + + Predicate predicate = CreateScreenIsOnPredicate(); + *config.add_predicate() = predicate; + + CountMetric* metric = config.add_count_metric(); + metric->set_id(12345); + metric->set_what(whatMatcher.id()); + metric->set_condition(predicate.id()); + metric->set_bucket(ONE_HOUR); + + // Create an initial config. + EXPECT_TRUE(initConfig(config)); + + unordered_map metricToActivationMap; + vector metricsToUpdate(1, UPDATE_UNKNOWN); + EXPECT_TRUE(determineAllMetricUpdateStatuses( + config, oldMetricProducerMap, oldMetricProducers, metricToActivationMap, + /*replacedMatchers*/ {}, /*replacedConditions=*/{predicate.id()}, + /*replacedStates=*/{}, metricsToUpdate)); + EXPECT_EQ(metricsToUpdate[0], UPDATE_REPLACE); +} + +TEST_F(ConfigUpdateTest, TestCountMetricStateChanged) { + StatsdConfig config; + AtomMatcher startMatcher = CreateScreenTurnedOnAtomMatcher(); + *config.add_atom_matcher() = startMatcher; + AtomMatcher stopMatcher = CreateScreenTurnedOffAtomMatcher(); + *config.add_atom_matcher() = stopMatcher; + AtomMatcher whatMatcher = CreateScreenBrightnessChangedAtomMatcher(); + *config.add_atom_matcher() = whatMatcher; + + State sliceState = CreateScreenState(); + *config.add_state() = sliceState; + + CountMetric* metric = config.add_count_metric(); + metric->set_id(12345); + metric->set_what(whatMatcher.id()); + metric->add_slice_by_state(sliceState.id()); + metric->set_bucket(ONE_HOUR); + + // Create an initial config. + EXPECT_TRUE(initConfig(config)); + + unordered_map metricToActivationMap; + vector metricsToUpdate(1, UPDATE_UNKNOWN); + EXPECT_TRUE(determineAllMetricUpdateStatuses( + config, oldMetricProducerMap, oldMetricProducers, metricToActivationMap, + /*replacedMatchers*/ {}, /*replacedConditions=*/{}, + /*replacedStates=*/{sliceState.id()}, metricsToUpdate)); + EXPECT_EQ(metricsToUpdate[0], UPDATE_REPLACE); +} + +TEST_F(ConfigUpdateTest, TestGaugeMetricPreserve) { + StatsdConfig config; + AtomMatcher startMatcher = CreateScreenTurnedOnAtomMatcher(); + *config.add_atom_matcher() = startMatcher; + AtomMatcher stopMatcher = CreateScreenTurnedOffAtomMatcher(); + *config.add_atom_matcher() = stopMatcher; + AtomMatcher whatMatcher = CreateScreenBrightnessChangedAtomMatcher(); + *config.add_atom_matcher() = whatMatcher; + + Predicate predicate = CreateScreenIsOnPredicate(); + *config.add_predicate() = predicate; + + *config.add_gauge_metric() = createGaugeMetric( + "GAUGE1", whatMatcher.id(), GaugeMetric::RANDOM_ONE_SAMPLE, predicate.id(), nullopt); + + EXPECT_TRUE(initConfig(config)); + + unordered_map metricToActivationMap; + vector metricsToUpdate(1, UPDATE_UNKNOWN); + EXPECT_TRUE(determineAllMetricUpdateStatuses(config, oldMetricProducerMap, oldMetricProducers, + metricToActivationMap, + /*replacedMatchers*/ {}, /*replacedConditions=*/{}, + /*replacedStates=*/{}, metricsToUpdate)); + EXPECT_EQ(metricsToUpdate[0], UPDATE_PRESERVE); +} + +TEST_F(ConfigUpdateTest, TestGaugeMetricDefinitionChange) { + StatsdConfig config; + AtomMatcher whatMatcher = CreateScreenBrightnessChangedAtomMatcher(); + *config.add_atom_matcher() = whatMatcher; + + *config.add_gauge_metric() = createGaugeMetric( + "GAUGE1", whatMatcher.id(), GaugeMetric::RANDOM_ONE_SAMPLE, nullopt, nullopt); + + EXPECT_TRUE(initConfig(config)); + + // Change split bucket on app upgrade, which should change the proto, causing replacement. + config.mutable_gauge_metric(0)->set_split_bucket_for_app_upgrade(false); + + unordered_map metricToActivationMap; + vector metricsToUpdate(1, UPDATE_UNKNOWN); + EXPECT_TRUE(determineAllMetricUpdateStatuses(config, oldMetricProducerMap, oldMetricProducers, + metricToActivationMap, + /*replacedMatchers*/ {}, /*replacedConditions=*/{}, + /*replacedStates=*/{}, metricsToUpdate)); + EXPECT_EQ(metricsToUpdate[0], UPDATE_REPLACE); +} + +TEST_F(ConfigUpdateTest, TestGaugeMetricWhatChanged) { + StatsdConfig config; + AtomMatcher whatMatcher = CreateScreenBrightnessChangedAtomMatcher(); + *config.add_atom_matcher() = whatMatcher; + + *config.add_gauge_metric() = createGaugeMetric( + "GAUGE1", whatMatcher.id(), GaugeMetric::RANDOM_ONE_SAMPLE, nullopt, nullopt); + + EXPECT_TRUE(initConfig(config)); + + unordered_map metricToActivationMap; + vector metricsToUpdate(1, UPDATE_UNKNOWN); + EXPECT_TRUE(determineAllMetricUpdateStatuses( + config, oldMetricProducerMap, oldMetricProducers, metricToActivationMap, + /*replacedMatchers*/ {whatMatcher.id()}, /*replacedConditions=*/{}, + /*replacedStates=*/{}, metricsToUpdate)); + EXPECT_EQ(metricsToUpdate[0], UPDATE_REPLACE); +} + +TEST_F(ConfigUpdateTest, TestGaugeMetricConditionChanged) { + StatsdConfig config; + AtomMatcher startMatcher = CreateScreenTurnedOnAtomMatcher(); + *config.add_atom_matcher() = startMatcher; + AtomMatcher stopMatcher = CreateScreenTurnedOffAtomMatcher(); + *config.add_atom_matcher() = stopMatcher; + AtomMatcher whatMatcher = CreateScreenBrightnessChangedAtomMatcher(); + *config.add_atom_matcher() = whatMatcher; + + Predicate predicate = CreateScreenIsOnPredicate(); + *config.add_predicate() = predicate; + + *config.add_gauge_metric() = createGaugeMetric( + "GAUGE1", whatMatcher.id(), GaugeMetric::RANDOM_ONE_SAMPLE, predicate.id(), nullopt); + + EXPECT_TRUE(initConfig(config)); + + unordered_map metricToActivationMap; + vector metricsToUpdate(1, UPDATE_UNKNOWN); + EXPECT_TRUE(determineAllMetricUpdateStatuses( + config, oldMetricProducerMap, oldMetricProducers, metricToActivationMap, + /*replacedMatchers*/ {}, /*replacedConditions=*/{predicate.id()}, + /*replacedStates=*/{}, metricsToUpdate)); + EXPECT_EQ(metricsToUpdate[0], UPDATE_REPLACE); +} + +TEST_F(ConfigUpdateTest, TestGaugeMetricTriggerEventChanged) { + StatsdConfig config; + AtomMatcher triggerEvent = CreateScreenTurnedOnAtomMatcher(); + *config.add_atom_matcher() = triggerEvent; + AtomMatcher whatMatcher = CreateTemperatureAtomMatcher(); + *config.add_atom_matcher() = whatMatcher; + + *config.add_gauge_metric() = createGaugeMetric( + "GAUGE1", whatMatcher.id(), GaugeMetric::FIRST_N_SAMPLES, nullopt, triggerEvent.id()); + + EXPECT_TRUE(initConfig(config)); + + unordered_map metricToActivationMap; + vector metricsToUpdate(1, UPDATE_UNKNOWN); + EXPECT_TRUE(determineAllMetricUpdateStatuses( + config, oldMetricProducerMap, oldMetricProducers, metricToActivationMap, + /*replacedMatchers*/ {triggerEvent.id()}, /*replacedConditions=*/{}, + /*replacedStates=*/{}, metricsToUpdate)); + EXPECT_EQ(metricsToUpdate[0], UPDATE_REPLACE); +} + +TEST_F(ConfigUpdateTest, TestDurationMetricPreserve) { + StatsdConfig config; + AtomMatcher startMatcher = CreateScreenTurnedOnAtomMatcher(); + *config.add_atom_matcher() = startMatcher; + AtomMatcher stopMatcher = CreateScreenTurnedOffAtomMatcher(); + *config.add_atom_matcher() = stopMatcher; + + Predicate what = CreateScreenIsOnPredicate(); + *config.add_predicate() = what; + Predicate condition = CreateScreenIsOffPredicate(); + *config.add_predicate() = condition; + + State sliceState = CreateScreenState(); + *config.add_state() = sliceState; + + *config.add_duration_metric() = + createDurationMetric("DURATION1", what.id(), condition.id(), {sliceState.id()}); + EXPECT_TRUE(initConfig(config)); + + unordered_map metricToActivationMap; + vector metricsToUpdate(1, UPDATE_UNKNOWN); + EXPECT_TRUE(determineAllMetricUpdateStatuses(config, oldMetricProducerMap, oldMetricProducers, + metricToActivationMap, + /*replacedMatchers*/ {}, /*replacedConditions=*/{}, + /*replacedStates=*/{}, metricsToUpdate)); + EXPECT_EQ(metricsToUpdate[0], UPDATE_PRESERVE); +} + +TEST_F(ConfigUpdateTest, TestDurationMetricDefinitionChange) { + StatsdConfig config; + AtomMatcher startMatcher = CreateScreenTurnedOnAtomMatcher(); + *config.add_atom_matcher() = startMatcher; + AtomMatcher stopMatcher = CreateScreenTurnedOffAtomMatcher(); + *config.add_atom_matcher() = stopMatcher; + + Predicate what = CreateScreenIsOnPredicate(); + *config.add_predicate() = what; + + *config.add_duration_metric() = createDurationMetric("DURATION1", what.id(), nullopt, {}); + EXPECT_TRUE(initConfig(config)); + + config.mutable_duration_metric(0)->set_aggregation_type(DurationMetric::MAX_SPARSE); + + unordered_map metricToActivationMap; + vector metricsToUpdate(1, UPDATE_UNKNOWN); + EXPECT_TRUE(determineAllMetricUpdateStatuses(config, oldMetricProducerMap, oldMetricProducers, + metricToActivationMap, /*replacedMatchers*/ {}, + /*replacedConditions=*/{}, /*replacedStates=*/{}, + metricsToUpdate)); + EXPECT_EQ(metricsToUpdate[0], UPDATE_REPLACE); +} + +TEST_F(ConfigUpdateTest, TestDurationMetricWhatChanged) { + StatsdConfig config; + AtomMatcher startMatcher = CreateScreenTurnedOnAtomMatcher(); + *config.add_atom_matcher() = startMatcher; + AtomMatcher stopMatcher = CreateScreenTurnedOffAtomMatcher(); + *config.add_atom_matcher() = stopMatcher; + + Predicate what = CreateScreenIsOnPredicate(); + *config.add_predicate() = what; + + *config.add_duration_metric() = createDurationMetric("DURATION1", what.id(), nullopt, {}); + EXPECT_TRUE(initConfig(config)); + + unordered_map metricToActivationMap; + vector metricsToUpdate(1, UPDATE_UNKNOWN); + EXPECT_TRUE(determineAllMetricUpdateStatuses( + config, oldMetricProducerMap, oldMetricProducers, metricToActivationMap, + /*replacedMatchers*/ {}, /*replacedConditions=*/{what.id()}, + /*replacedStates=*/{}, metricsToUpdate)); + EXPECT_EQ(metricsToUpdate[0], UPDATE_REPLACE); +} + +TEST_F(ConfigUpdateTest, TestDurationMetricConditionChanged) { + StatsdConfig config; + AtomMatcher startMatcher = CreateScreenTurnedOnAtomMatcher(); + *config.add_atom_matcher() = startMatcher; + AtomMatcher stopMatcher = CreateScreenTurnedOffAtomMatcher(); + *config.add_atom_matcher() = stopMatcher; + + Predicate what = CreateScreenIsOnPredicate(); + *config.add_predicate() = what; + Predicate condition = CreateScreenIsOffPredicate(); + *config.add_predicate() = condition; + + *config.add_duration_metric() = createDurationMetric("DURATION", what.id(), condition.id(), {}); + EXPECT_TRUE(initConfig(config)); + + unordered_map metricToActivationMap; + vector metricsToUpdate(1, UPDATE_UNKNOWN); + EXPECT_TRUE(determineAllMetricUpdateStatuses( + config, oldMetricProducerMap, oldMetricProducers, metricToActivationMap, + /*replacedMatchers*/ {}, /*replacedConditions=*/{condition.id()}, + /*replacedStates=*/{}, metricsToUpdate)); + EXPECT_EQ(metricsToUpdate[0], UPDATE_REPLACE); +} + +TEST_F(ConfigUpdateTest, TestDurationMetricStateChange) { + StatsdConfig config; + AtomMatcher startMatcher = CreateScreenTurnedOnAtomMatcher(); + *config.add_atom_matcher() = startMatcher; + AtomMatcher stopMatcher = CreateScreenTurnedOffAtomMatcher(); + *config.add_atom_matcher() = stopMatcher; + + Predicate what = CreateScreenIsOnPredicate(); + *config.add_predicate() = what; + + State sliceState = CreateScreenState(); + *config.add_state() = sliceState; + + *config.add_duration_metric() = + createDurationMetric("DURATION1", what.id(), nullopt, {sliceState.id()}); + EXPECT_TRUE(initConfig(config)); + + unordered_map metricToActivationMap; + vector metricsToUpdate(1, UPDATE_UNKNOWN); + EXPECT_TRUE(determineAllMetricUpdateStatuses( + config, oldMetricProducerMap, oldMetricProducers, metricToActivationMap, + /*replacedMatchers*/ {}, /*replacedConditions=*/{}, + /*replacedStates=*/{sliceState.id()}, metricsToUpdate)); + EXPECT_EQ(metricsToUpdate[0], UPDATE_REPLACE); +} + +TEST_F(ConfigUpdateTest, TestValueMetricPreserve) { + StatsdConfig config; + AtomMatcher startMatcher = CreateScreenTurnedOnAtomMatcher(); + *config.add_atom_matcher() = startMatcher; + AtomMatcher stopMatcher = CreateScreenTurnedOffAtomMatcher(); + *config.add_atom_matcher() = stopMatcher; + AtomMatcher whatMatcher = CreateTemperatureAtomMatcher(); + *config.add_atom_matcher() = whatMatcher; + + Predicate predicate = CreateScreenIsOnPredicate(); + *config.add_predicate() = predicate; + State sliceState = CreateScreenState(); + *config.add_state() = sliceState; + + *config.add_value_metric() = + createValueMetric("VALUE1", whatMatcher, 2, predicate.id(), {sliceState.id()}); + EXPECT_TRUE(initConfig(config)); + + unordered_map metricToActivationMap; + vector metricsToUpdate(1, UPDATE_UNKNOWN); + EXPECT_TRUE(determineAllMetricUpdateStatuses(config, oldMetricProducerMap, oldMetricProducers, + metricToActivationMap, + /*replacedMatchers*/ {}, /*replacedConditions=*/{}, + /*replacedStates=*/{}, metricsToUpdate)); + EXPECT_EQ(metricsToUpdate[0], UPDATE_PRESERVE); +} + +TEST_F(ConfigUpdateTest, TestValueMetricDefinitionChange) { + StatsdConfig config; + AtomMatcher whatMatcher = CreateScreenBrightnessChangedAtomMatcher(); + *config.add_atom_matcher() = whatMatcher; + + *config.add_value_metric() = createValueMetric("VALUE1", whatMatcher, 2, nullopt, {}); + EXPECT_TRUE(initConfig(config)); + + // Change skip zero diff output, which should change the proto, causing replacement. + config.mutable_value_metric(0)->set_skip_zero_diff_output(true); + + unordered_map metricToActivationMap; + vector metricsToUpdate(1, UPDATE_UNKNOWN); + EXPECT_TRUE(determineAllMetricUpdateStatuses(config, oldMetricProducerMap, oldMetricProducers, + metricToActivationMap, + /*replacedMatchers*/ {}, /*replacedConditions=*/{}, + /*replacedStates=*/{}, metricsToUpdate)); + EXPECT_EQ(metricsToUpdate[0], UPDATE_REPLACE); +} + +TEST_F(ConfigUpdateTest, TestValueMetricWhatChanged) { + StatsdConfig config; + AtomMatcher whatMatcher = CreateTemperatureAtomMatcher(); + *config.add_atom_matcher() = whatMatcher; + + *config.add_value_metric() = createValueMetric("VALUE1", whatMatcher, 2, nullopt, {}); + EXPECT_TRUE(initConfig(config)); + + unordered_map metricToActivationMap; + vector metricsToUpdate(1, UPDATE_UNKNOWN); + EXPECT_TRUE(determineAllMetricUpdateStatuses( + config, oldMetricProducerMap, oldMetricProducers, metricToActivationMap, + /*replacedMatchers*/ {whatMatcher.id()}, /*replacedConditions=*/{}, + /*replacedStates=*/{}, metricsToUpdate)); + EXPECT_EQ(metricsToUpdate[0], UPDATE_REPLACE); +} + +TEST_F(ConfigUpdateTest, TestValueMetricConditionChanged) { + StatsdConfig config; + AtomMatcher startMatcher = CreateScreenTurnedOnAtomMatcher(); + *config.add_atom_matcher() = startMatcher; + AtomMatcher stopMatcher = CreateScreenTurnedOffAtomMatcher(); + *config.add_atom_matcher() = stopMatcher; + AtomMatcher whatMatcher = CreateTemperatureAtomMatcher(); + *config.add_atom_matcher() = whatMatcher; + + Predicate predicate = CreateScreenIsOnPredicate(); + *config.add_predicate() = predicate; + + *config.add_value_metric() = createValueMetric("VALUE1", whatMatcher, 2, predicate.id(), {}); + EXPECT_TRUE(initConfig(config)); + + unordered_map metricToActivationMap; + vector metricsToUpdate(1, UPDATE_UNKNOWN); + EXPECT_TRUE(determineAllMetricUpdateStatuses( + config, oldMetricProducerMap, oldMetricProducers, metricToActivationMap, + /*replacedMatchers*/ {}, /*replacedConditions=*/{predicate.id()}, + /*replacedStates=*/{}, metricsToUpdate)); + EXPECT_EQ(metricsToUpdate[0], UPDATE_REPLACE); +} + +TEST_F(ConfigUpdateTest, TestValueMetricStateChanged) { + StatsdConfig config; + AtomMatcher whatMatcher = CreateScreenBrightnessChangedAtomMatcher(); + *config.add_atom_matcher() = whatMatcher; + + State sliceState = CreateScreenState(); + *config.add_state() = sliceState; + + *config.add_value_metric() = + createValueMetric("VALUE1", whatMatcher, 2, nullopt, {sliceState.id()}); + EXPECT_TRUE(initConfig(config)); + + unordered_map metricToActivationMap; + vector metricsToUpdate(1, UPDATE_UNKNOWN); + EXPECT_TRUE(determineAllMetricUpdateStatuses( + config, oldMetricProducerMap, oldMetricProducers, metricToActivationMap, + /*replacedMatchers*/ {}, /*replacedConditions=*/{}, + /*replacedStates=*/{sliceState.id()}, metricsToUpdate)); + EXPECT_EQ(metricsToUpdate[0], UPDATE_REPLACE); +} + +TEST_F(ConfigUpdateTest, TestUpdateEventMetrics) { + StatsdConfig config; + + // Add atom matchers/predicates. These are mostly needed for initStatsdConfig + AtomMatcher matcher1 = CreateScreenTurnedOnAtomMatcher(); + int64_t matcher1Id = matcher1.id(); + *config.add_atom_matcher() = matcher1; + + AtomMatcher matcher2 = CreateScreenTurnedOffAtomMatcher(); + int64_t matcher2Id = matcher2.id(); + *config.add_atom_matcher() = matcher2; + + AtomMatcher matcher3 = CreateStartScheduledJobAtomMatcher(); + int64_t matcher3Id = matcher3.id(); + *config.add_atom_matcher() = matcher3; + + AtomMatcher matcher4 = CreateFinishScheduledJobAtomMatcher(); + int64_t matcher4Id = matcher4.id(); + *config.add_atom_matcher() = matcher4; + + AtomMatcher matcher5 = CreateBatterySaverModeStartAtomMatcher(); + int64_t matcher5Id = matcher5.id(); + *config.add_atom_matcher() = matcher5; + + Predicate predicate1 = CreateScreenIsOnPredicate(); + int64_t predicate1Id = predicate1.id(); + *config.add_predicate() = predicate1; + + Predicate predicate2 = CreateScheduledJobPredicate(); + int64_t predicate2Id = predicate2.id(); + *config.add_predicate() = predicate2; + + // Add a few event metrics. + // Will be preserved. + EventMetric event1 = createEventMetric("EVENT1", matcher1Id, predicate2Id); + int64_t event1Id = event1.id(); + *config.add_event_metric() = event1; + + // Will be replaced. + EventMetric event2 = createEventMetric("EVENT2", matcher2Id, nullopt); + int64_t event2Id = event2.id(); + *config.add_event_metric() = event2; + + // Will be replaced. + EventMetric event3 = createEventMetric("EVENT3", matcher3Id, nullopt); + int64_t event3Id = event3.id(); + *config.add_event_metric() = event3; + + MetricActivation event3Activation; + event3Activation.set_metric_id(event3Id); + EventActivation* eventActivation = event3Activation.add_event_activation(); + eventActivation->set_atom_matcher_id(matcher5Id); + eventActivation->set_ttl_seconds(5); + *config.add_metric_activation() = event3Activation; + + // Will be replaced. + EventMetric event4 = createEventMetric("EVENT4", matcher4Id, predicate1Id); + int64_t event4Id = event4.id(); + *config.add_event_metric() = event4; + + // Will be deleted. + EventMetric event5 = createEventMetric("EVENT5", matcher5Id, nullopt); + int64_t event5Id = event5.id(); + *config.add_event_metric() = event5; + + EXPECT_TRUE(initConfig(config)); + + // Used later to ensure the condition wizard is replaced. Get it before doing the update. + sp oldConditionWizard = oldMetricProducers[0]->mWizard; + EXPECT_EQ(oldConditionWizard->getStrongCount(), oldMetricProducers.size() + 1); + + // Add a condition to event2, causing it to be replaced. + event2.set_condition(predicate1Id); + + // Mark matcher 5 as replaced. Causes event3 to be replaced. + set replacedMatchers; + replacedMatchers.insert(matcher5Id); + + // Mark predicate 1 as replaced. Causes event4 to be replaced. + set replacedConditions; + replacedConditions.insert(predicate1Id); + + // Fake that predicate 2 is true. + ASSERT_EQ(oldMetricProducers[0]->getMetricId(), event1Id); + oldMetricProducers[0]->onConditionChanged(true, /*timestamp=*/0); + EXPECT_EQ(oldMetricProducers[0]->mCondition, ConditionState::kTrue); + + // New event metric. Should have an initial condition of true since it depends on predicate2. + EventMetric event6 = createEventMetric("EVENT6", matcher3Id, predicate2Id); + int64_t event6Id = event6.id(); + MetricActivation event6Activation; + event6Activation.set_metric_id(event6Id); + eventActivation = event6Activation.add_event_activation(); + eventActivation->set_atom_matcher_id(matcher5Id); + eventActivation->set_ttl_seconds(20); + + // Map the matchers and predicates in reverse order to force the indices to change. + std::unordered_map newAtomMatchingTrackerMap; + const int matcher5Index = 0; + newAtomMatchingTrackerMap[matcher5Id] = 0; + const int matcher4Index = 1; + newAtomMatchingTrackerMap[matcher4Id] = 1; + const int matcher3Index = 2; + newAtomMatchingTrackerMap[matcher3Id] = 2; + const int matcher2Index = 3; + newAtomMatchingTrackerMap[matcher2Id] = 3; + const int matcher1Index = 4; + newAtomMatchingTrackerMap[matcher1Id] = 4; + // Use the existing matchers. A bit hacky, but saves code and we don't rely on them. + vector> newAtomMatchingTrackers(5); + std::reverse_copy(oldAtomMatchingTrackers.begin(), oldAtomMatchingTrackers.end(), + newAtomMatchingTrackers.begin()); + + std::unordered_map newConditionTrackerMap; + const int predicate2Index = 0; + newConditionTrackerMap[predicate2Id] = 0; + const int predicate1Index = 1; + newConditionTrackerMap[predicate1Id] = 1; + // Use the existing conditionTrackers. A bit hacky, but saves code and we don't rely on them. + vector> newConditionTrackers(2); + std::reverse_copy(oldConditionTrackers.begin(), oldConditionTrackers.end(), + newConditionTrackers.begin()); + // Fake that predicate2 is true. + vector conditionCache = {ConditionState::kTrue, ConditionState::kUnknown}; + + StatsdConfig newConfig; + *newConfig.add_event_metric() = event6; + const int event6Index = 0; + *newConfig.add_event_metric() = event3; + const int event3Index = 1; + *newConfig.add_event_metric() = event1; + const int event1Index = 2; + *newConfig.add_event_metric() = event4; + const int event4Index = 3; + *newConfig.add_event_metric() = event2; + const int event2Index = 4; + *newConfig.add_metric_activation() = event3Activation; + *newConfig.add_metric_activation() = event6Activation; + + // Output data structures to validate. + unordered_map newMetricProducerMap; + vector> newMetricProducers; + unordered_map> conditionToMetricMap; + unordered_map> trackerToMetricMap; + set noReportMetricIds; + unordered_map> activationAtomTrackerToMetricMap; + unordered_map> deactivationAtomTrackerToMetricMap; + vector metricsWithActivation; + set replacedMetrics; + EXPECT_TRUE(updateMetrics( + key, newConfig, /*timeBaseNs=*/123, /*currentTimeNs=*/12345, new StatsPullerManager(), + oldAtomMatchingTrackerMap, newAtomMatchingTrackerMap, replacedMatchers, + newAtomMatchingTrackers, newConditionTrackerMap, replacedConditions, + newConditionTrackers, conditionCache, /*stateAtomIdMap=*/{}, /*allStateGroupMaps=*/{}, + /*replacedStates=*/{}, oldMetricProducerMap, oldMetricProducers, newMetricProducerMap, + newMetricProducers, conditionToMetricMap, trackerToMetricMap, noReportMetricIds, + activationAtomTrackerToMetricMap, deactivationAtomTrackerToMetricMap, + metricsWithActivation, replacedMetrics)); + + unordered_map expectedMetricProducerMap = { + {event1Id, event1Index}, {event2Id, event2Index}, {event3Id, event3Index}, + {event4Id, event4Index}, {event6Id, event6Index}, + }; + EXPECT_THAT(newMetricProducerMap, ContainerEq(expectedMetricProducerMap)); + EXPECT_EQ(replacedMetrics, set({event2Id, event3Id, event4Id})); + + // Make sure preserved metrics are the same. + ASSERT_EQ(newMetricProducers.size(), 5); + EXPECT_EQ(oldMetricProducers[oldMetricProducerMap.at(event1Id)], + newMetricProducers[newMetricProducerMap.at(event1Id)]); + + // Make sure replaced metrics are different. + EXPECT_NE(oldMetricProducers[oldMetricProducerMap.at(event2Id)], + newMetricProducers[newMetricProducerMap.at(event2Id)]); + EXPECT_NE(oldMetricProducers[oldMetricProducerMap.at(event3Id)], + newMetricProducers[newMetricProducerMap.at(event3Id)]); + EXPECT_NE(oldMetricProducers[oldMetricProducerMap.at(event4Id)], + newMetricProducers[newMetricProducerMap.at(event4Id)]); + + // Verify the conditionToMetricMap. + ASSERT_EQ(conditionToMetricMap.size(), 2); + const vector& condition1Metrics = conditionToMetricMap[predicate1Index]; + EXPECT_THAT(condition1Metrics, UnorderedElementsAre(event2Index, event4Index)); + const vector& condition2Metrics = conditionToMetricMap[predicate2Index]; + EXPECT_THAT(condition2Metrics, UnorderedElementsAre(event1Index, event6Index)); + + // Verify the trackerToMetricMap. + ASSERT_EQ(trackerToMetricMap.size(), 4); + const vector& matcher1Metrics = trackerToMetricMap[matcher1Index]; + EXPECT_THAT(matcher1Metrics, UnorderedElementsAre(event1Index)); + const vector& matcher2Metrics = trackerToMetricMap[matcher2Index]; + EXPECT_THAT(matcher2Metrics, UnorderedElementsAre(event2Index)); + const vector& matcher3Metrics = trackerToMetricMap[matcher3Index]; + EXPECT_THAT(matcher3Metrics, UnorderedElementsAre(event3Index, event6Index)); + const vector& matcher4Metrics = trackerToMetricMap[matcher4Index]; + EXPECT_THAT(matcher4Metrics, UnorderedElementsAre(event4Index)); + + // Verify event activation/deactivation maps. + ASSERT_EQ(activationAtomTrackerToMetricMap.size(), 1); + EXPECT_THAT(activationAtomTrackerToMetricMap[matcher5Index], + UnorderedElementsAre(event3Index, event6Index)); + ASSERT_EQ(deactivationAtomTrackerToMetricMap.size(), 0); + ASSERT_EQ(metricsWithActivation.size(), 2); + EXPECT_THAT(metricsWithActivation, UnorderedElementsAre(event3Index, event6Index)); + + // Verify tracker indices/ids/conditions are correct. + EXPECT_EQ(newMetricProducers[event1Index]->getMetricId(), event1Id); + EXPECT_EQ(newMetricProducers[event1Index]->mConditionTrackerIndex, predicate2Index); + EXPECT_EQ(newMetricProducers[event1Index]->mCondition, ConditionState::kTrue); + EXPECT_EQ(newMetricProducers[event2Index]->getMetricId(), event2Id); + EXPECT_EQ(newMetricProducers[event2Index]->mConditionTrackerIndex, predicate1Index); + EXPECT_EQ(newMetricProducers[event2Index]->mCondition, ConditionState::kUnknown); + EXPECT_EQ(newMetricProducers[event3Index]->getMetricId(), event3Id); + EXPECT_EQ(newMetricProducers[event3Index]->mConditionTrackerIndex, -1); + EXPECT_EQ(newMetricProducers[event3Index]->mCondition, ConditionState::kTrue); + EXPECT_EQ(newMetricProducers[event4Index]->getMetricId(), event4Id); + EXPECT_EQ(newMetricProducers[event4Index]->mConditionTrackerIndex, predicate1Index); + EXPECT_EQ(newMetricProducers[event4Index]->mCondition, ConditionState::kUnknown); + EXPECT_EQ(newMetricProducers[event6Index]->getMetricId(), event6Id); + EXPECT_EQ(newMetricProducers[event6Index]->mConditionTrackerIndex, predicate2Index); + EXPECT_EQ(newMetricProducers[event6Index]->mCondition, ConditionState::kTrue); + + sp newConditionWizard = newMetricProducers[0]->mWizard; + EXPECT_NE(newConditionWizard, oldConditionWizard); + EXPECT_EQ(newConditionWizard->getStrongCount(), newMetricProducers.size() + 1); + oldMetricProducers.clear(); + // Only reference to the old wizard should be the one in the test. + EXPECT_EQ(oldConditionWizard->getStrongCount(), 1); +} + +TEST_F(ConfigUpdateTest, TestUpdateCountMetrics) { + StatsdConfig config; + + // Add atom matchers/predicates/states. These are mostly needed for initStatsdConfig. + AtomMatcher matcher1 = CreateScreenTurnedOnAtomMatcher(); + int64_t matcher1Id = matcher1.id(); + *config.add_atom_matcher() = matcher1; + + AtomMatcher matcher2 = CreateScreenTurnedOffAtomMatcher(); + int64_t matcher2Id = matcher2.id(); + *config.add_atom_matcher() = matcher2; + + AtomMatcher matcher3 = CreateStartScheduledJobAtomMatcher(); + int64_t matcher3Id = matcher3.id(); + *config.add_atom_matcher() = matcher3; + + AtomMatcher matcher4 = CreateFinishScheduledJobAtomMatcher(); + int64_t matcher4Id = matcher4.id(); + *config.add_atom_matcher() = matcher4; + + AtomMatcher matcher5 = CreateBatterySaverModeStartAtomMatcher(); + int64_t matcher5Id = matcher5.id(); + *config.add_atom_matcher() = matcher5; + + Predicate predicate1 = CreateScreenIsOnPredicate(); + int64_t predicate1Id = predicate1.id(); + *config.add_predicate() = predicate1; + + State state1 = CreateScreenStateWithOnOffMap(0x123, 0x321); + int64_t state1Id = state1.id(); + *config.add_state() = state1; + + State state2 = CreateScreenState(); + int64_t state2Id = state2.id(); + *config.add_state() = state2; + + // Add a few count metrics. + // Will be preserved. + CountMetric count1 = createCountMetric("COUNT1", matcher1Id, predicate1Id, {state1Id}); + int64_t count1Id = count1.id(); + *config.add_count_metric() = count1; + + // Will be replaced. + CountMetric count2 = createCountMetric("COUNT2", matcher2Id, nullopt, {}); + int64_t count2Id = count2.id(); + *config.add_count_metric() = count2; + + // Will be replaced. + CountMetric count3 = createCountMetric("COUNT3", matcher3Id, nullopt, {}); + int64_t count3Id = count3.id(); + *config.add_count_metric() = count3; + + // Will be replaced. + CountMetric count4 = createCountMetric("COUNT4", matcher4Id, nullopt, {state2Id}); + int64_t count4Id = count4.id(); + *config.add_count_metric() = count4; + + // Will be deleted. + CountMetric count5 = createCountMetric("COUNT5", matcher5Id, nullopt, {}); + int64_t count5Id = count5.id(); + *config.add_count_metric() = count5; + + EXPECT_TRUE(initConfig(config)); + + // Change bucket size of count2, causing it to be replaced. + count2.set_bucket(ONE_HOUR); + + // Mark matcher 3 as replaced. Causes count3 to be replaced. + set replacedMatchers; + replacedMatchers.insert(matcher3Id); + + // Mark state 2 as replaced and change the state to be about a different atom. + // Causes count4 to be replaced. + set replacedStates; + replacedStates.insert(state2Id); + state2.set_atom_id(util::BATTERY_SAVER_MODE_STATE_CHANGED); + + // Fake that predicate 1 is true for count metric 1. + ASSERT_EQ(oldMetricProducers[0]->getMetricId(), count1Id); + oldMetricProducers[0]->onConditionChanged(true, /*timestamp=*/0); + EXPECT_EQ(oldMetricProducers[0]->mCondition, ConditionState::kTrue); + + EXPECT_EQ(StateManager::getInstance().getStateTrackersCount(), 1); + // Tell the StateManager that the screen is on. + unique_ptr event = + CreateScreenStateChangedEvent(0, android::view::DisplayStateEnum::DISPLAY_STATE_ON); + StateManager::getInstance().onLogEvent(*event); + + // New count metric. Should have an initial condition of true since it depends on predicate1. + CountMetric count6 = createCountMetric("EVENT6", matcher2Id, predicate1Id, {state1Id}); + int64_t count6Id = count6.id(); + + // Map the matchers and predicates in reverse order to force the indices to change. + std::unordered_map newAtomMatchingTrackerMap; + const int matcher5Index = 0; + newAtomMatchingTrackerMap[matcher5Id] = 0; + const int matcher4Index = 1; + newAtomMatchingTrackerMap[matcher4Id] = 1; + const int matcher3Index = 2; + newAtomMatchingTrackerMap[matcher3Id] = 2; + const int matcher2Index = 3; + newAtomMatchingTrackerMap[matcher2Id] = 3; + const int matcher1Index = 4; + newAtomMatchingTrackerMap[matcher1Id] = 4; + // Use the existing matchers. A bit hacky, but saves code and we don't rely on them. + vector> newAtomMatchingTrackers(5); + std::reverse_copy(oldAtomMatchingTrackers.begin(), oldAtomMatchingTrackers.end(), + newAtomMatchingTrackers.begin()); + + std::unordered_map newConditionTrackerMap; + const int predicate1Index = 0; + newConditionTrackerMap[predicate1Id] = 0; + // Use the existing conditionTrackers. A bit hacky, but saves code and we don't rely on them. + vector> newConditionTrackers(1); + std::reverse_copy(oldConditionTrackers.begin(), oldConditionTrackers.end(), + newConditionTrackers.begin()); + // Fake that predicate1 is true for all new metrics. + vector conditionCache = {ConditionState::kTrue}; + + StatsdConfig newConfig; + *newConfig.add_count_metric() = count6; + const int count6Index = 0; + *newConfig.add_count_metric() = count3; + const int count3Index = 1; + *newConfig.add_count_metric() = count1; + const int count1Index = 2; + *newConfig.add_count_metric() = count4; + const int count4Index = 3; + *newConfig.add_count_metric() = count2; + const int count2Index = 4; + + *newConfig.add_state() = state1; + *newConfig.add_state() = state2; + + unordered_map stateAtomIdMap; + unordered_map> allStateGroupMaps; + map stateProtoHashes; + EXPECT_TRUE(initStates(newConfig, stateAtomIdMap, allStateGroupMaps, stateProtoHashes)); + EXPECT_EQ(stateAtomIdMap[state2Id], util::BATTERY_SAVER_MODE_STATE_CHANGED); + + // Output data structures to validate. + unordered_map newMetricProducerMap; + vector> newMetricProducers; + unordered_map> conditionToMetricMap; + unordered_map> trackerToMetricMap; + set noReportMetricIds; + unordered_map> activationAtomTrackerToMetricMap; + unordered_map> deactivationAtomTrackerToMetricMap; + vector metricsWithActivation; + set replacedMetrics; + EXPECT_TRUE(updateMetrics( + key, newConfig, /*timeBaseNs=*/123, /*currentTimeNs=*/12345, new StatsPullerManager(), + oldAtomMatchingTrackerMap, newAtomMatchingTrackerMap, replacedMatchers, + newAtomMatchingTrackers, newConditionTrackerMap, /*replacedConditions=*/{}, + newConditionTrackers, conditionCache, stateAtomIdMap, allStateGroupMaps, replacedStates, + oldMetricProducerMap, oldMetricProducers, newMetricProducerMap, newMetricProducers, + conditionToMetricMap, trackerToMetricMap, noReportMetricIds, + activationAtomTrackerToMetricMap, deactivationAtomTrackerToMetricMap, + metricsWithActivation, replacedMetrics)); + + unordered_map expectedMetricProducerMap = { + {count1Id, count1Index}, {count2Id, count2Index}, {count3Id, count3Index}, + {count4Id, count4Index}, {count6Id, count6Index}, + }; + EXPECT_THAT(newMetricProducerMap, ContainerEq(expectedMetricProducerMap)); + EXPECT_EQ(replacedMetrics, set({count2Id, count3Id, count4Id})); + + // Make sure preserved metrics are the same. + ASSERT_EQ(newMetricProducers.size(), 5); + EXPECT_EQ(oldMetricProducers[oldMetricProducerMap.at(count1Id)], + newMetricProducers[newMetricProducerMap.at(count1Id)]); + + // Make sure replaced metrics are different. + EXPECT_NE(oldMetricProducers[oldMetricProducerMap.at(count2Id)], + newMetricProducers[newMetricProducerMap.at(count2Id)]); + EXPECT_NE(oldMetricProducers[oldMetricProducerMap.at(count3Id)], + newMetricProducers[newMetricProducerMap.at(count3Id)]); + EXPECT_NE(oldMetricProducers[oldMetricProducerMap.at(count4Id)], + newMetricProducers[newMetricProducerMap.at(count4Id)]); + + // Verify the conditionToMetricMap. + ASSERT_EQ(conditionToMetricMap.size(), 1); + const vector& condition1Metrics = conditionToMetricMap[predicate1Index]; + EXPECT_THAT(condition1Metrics, UnorderedElementsAre(count1Index, count6Index)); + + // Verify the trackerToMetricMap. + ASSERT_EQ(trackerToMetricMap.size(), 4); + const vector& matcher1Metrics = trackerToMetricMap[matcher1Index]; + EXPECT_THAT(matcher1Metrics, UnorderedElementsAre(count1Index)); + const vector& matcher2Metrics = trackerToMetricMap[matcher2Index]; + EXPECT_THAT(matcher2Metrics, UnorderedElementsAre(count2Index, count6Index)); + const vector& matcher3Metrics = trackerToMetricMap[matcher3Index]; + EXPECT_THAT(matcher3Metrics, UnorderedElementsAre(count3Index)); + const vector& matcher4Metrics = trackerToMetricMap[matcher4Index]; + EXPECT_THAT(matcher4Metrics, UnorderedElementsAre(count4Index)); + + // Verify event activation/deactivation maps. + ASSERT_EQ(activationAtomTrackerToMetricMap.size(), 0); + ASSERT_EQ(deactivationAtomTrackerToMetricMap.size(), 0); + ASSERT_EQ(metricsWithActivation.size(), 0); + + // Verify tracker indices/ids/conditions/states are correct. + EXPECT_EQ(newMetricProducers[count1Index]->getMetricId(), count1Id); + EXPECT_EQ(newMetricProducers[count1Index]->mConditionTrackerIndex, predicate1Index); + EXPECT_EQ(newMetricProducers[count1Index]->mCondition, ConditionState::kTrue); + EXPECT_THAT(newMetricProducers[count1Index]->getSlicedStateAtoms(), + UnorderedElementsAre(util::SCREEN_STATE_CHANGED)); + EXPECT_EQ(newMetricProducers[count2Index]->getMetricId(), count2Id); + EXPECT_EQ(newMetricProducers[count2Index]->mConditionTrackerIndex, -1); + EXPECT_EQ(newMetricProducers[count2Index]->mCondition, ConditionState::kTrue); + EXPECT_TRUE(newMetricProducers[count2Index]->getSlicedStateAtoms().empty()); + EXPECT_EQ(newMetricProducers[count3Index]->getMetricId(), count3Id); + EXPECT_EQ(newMetricProducers[count3Index]->mConditionTrackerIndex, -1); + EXPECT_EQ(newMetricProducers[count3Index]->mCondition, ConditionState::kTrue); + EXPECT_TRUE(newMetricProducers[count3Index]->getSlicedStateAtoms().empty()); + EXPECT_EQ(newMetricProducers[count4Index]->getMetricId(), count4Id); + EXPECT_EQ(newMetricProducers[count4Index]->mConditionTrackerIndex, -1); + EXPECT_EQ(newMetricProducers[count4Index]->mCondition, ConditionState::kTrue); + EXPECT_THAT(newMetricProducers[count4Index]->getSlicedStateAtoms(), + UnorderedElementsAre(util::BATTERY_SAVER_MODE_STATE_CHANGED)); + EXPECT_EQ(newMetricProducers[count6Index]->getMetricId(), count6Id); + EXPECT_EQ(newMetricProducers[count6Index]->mConditionTrackerIndex, predicate1Index); + EXPECT_EQ(newMetricProducers[count6Index]->mCondition, ConditionState::kTrue); + EXPECT_THAT(newMetricProducers[count6Index]->getSlicedStateAtoms(), + UnorderedElementsAre(util::SCREEN_STATE_CHANGED)); + + oldMetricProducers.clear(); + // Ensure that the screen state StateTracker did not get deleted and replaced. + EXPECT_EQ(StateManager::getInstance().getStateTrackersCount(), 2); + FieldValue screenState; + StateManager::getInstance().getStateValue(util::SCREEN_STATE_CHANGED, DEFAULT_DIMENSION_KEY, + &screenState); + EXPECT_EQ(screenState.mValue.int_value, android::view::DisplayStateEnum::DISPLAY_STATE_ON); +} + +TEST_F(ConfigUpdateTest, TestUpdateGaugeMetrics) { + StatsdConfig config; + + // Add atom matchers/predicates/states. These are mostly needed for initStatsdConfig. + AtomMatcher matcher1 = CreateScreenTurnedOnAtomMatcher(); + int64_t matcher1Id = matcher1.id(); + *config.add_atom_matcher() = matcher1; + + AtomMatcher matcher2 = CreateScreenTurnedOffAtomMatcher(); + int64_t matcher2Id = matcher2.id(); + *config.add_atom_matcher() = matcher2; + + AtomMatcher matcher3 = CreateStartScheduledJobAtomMatcher(); + int64_t matcher3Id = matcher3.id(); + *config.add_atom_matcher() = matcher3; + + AtomMatcher matcher4 = CreateTemperatureAtomMatcher(); + int64_t matcher4Id = matcher4.id(); + *config.add_atom_matcher() = matcher4; + + AtomMatcher matcher5 = CreateSimpleAtomMatcher("SubsystemSleep", util::SUBSYSTEM_SLEEP_STATE); + int64_t matcher5Id = matcher5.id(); + *config.add_atom_matcher() = matcher5; + + Predicate predicate1 = CreateScreenIsOnPredicate(); + int64_t predicate1Id = predicate1.id(); + *config.add_predicate() = predicate1; + + // Add a few gauge metrics. + // Will be preserved. + GaugeMetric gauge1 = createGaugeMetric("GAUGE1", matcher4Id, GaugeMetric::FIRST_N_SAMPLES, + predicate1Id, matcher1Id); + int64_t gauge1Id = gauge1.id(); + *config.add_gauge_metric() = gauge1; + + // Will be replaced. + GaugeMetric gauge2 = + createGaugeMetric("GAUGE2", matcher1Id, GaugeMetric::FIRST_N_SAMPLES, nullopt, nullopt); + int64_t gauge2Id = gauge2.id(); + *config.add_gauge_metric() = gauge2; + + // Will be replaced. + GaugeMetric gauge3 = createGaugeMetric("GAUGE3", matcher5Id, GaugeMetric::FIRST_N_SAMPLES, + nullopt, matcher3Id); + int64_t gauge3Id = gauge3.id(); + *config.add_gauge_metric() = gauge3; + + // Will be replaced. + GaugeMetric gauge4 = createGaugeMetric("GAUGE4", matcher3Id, GaugeMetric::RANDOM_ONE_SAMPLE, + predicate1Id, nullopt); + int64_t gauge4Id = gauge4.id(); + *config.add_gauge_metric() = gauge4; + + // Will be deleted. + GaugeMetric gauge5 = + createGaugeMetric("GAUGE5", matcher2Id, GaugeMetric::RANDOM_ONE_SAMPLE, nullopt, {}); + int64_t gauge5Id = gauge5.id(); + *config.add_gauge_metric() = gauge5; + + EXPECT_TRUE(initConfig(config)); + + // Used later to ensure the condition wizard is replaced. Get it before doing the update. + sp oldMatcherWizard = + static_cast(oldMetricProducers[0].get())->mEventMatcherWizard; + EXPECT_EQ(oldMatcherWizard->getStrongCount(), 6); + + // Change gauge2, causing it to be replaced. + gauge2.set_max_num_gauge_atoms_per_bucket(50); + + // Mark matcher 3 as replaced. Causes gauge3 and gauge4 to be replaced. + set replacedMatchers = {matcher3Id}; + + // New gauge metric. + GaugeMetric gauge6 = createGaugeMetric("GAUGE6", matcher5Id, GaugeMetric::FIRST_N_SAMPLES, + predicate1Id, matcher3Id); + int64_t gauge6Id = gauge6.id(); + + // Map the matchers and predicates in reverse order to force the indices to change. + std::unordered_map newAtomMatchingTrackerMap; + const int matcher5Index = 0; + newAtomMatchingTrackerMap[matcher5Id] = 0; + const int matcher4Index = 1; + newAtomMatchingTrackerMap[matcher4Id] = 1; + const int matcher3Index = 2; + newAtomMatchingTrackerMap[matcher3Id] = 2; + const int matcher2Index = 3; + newAtomMatchingTrackerMap[matcher2Id] = 3; + const int matcher1Index = 4; + newAtomMatchingTrackerMap[matcher1Id] = 4; + // Use the existing matchers. A bit hacky, but saves code and we don't rely on them. + vector> newAtomMatchingTrackers(5); + std::reverse_copy(oldAtomMatchingTrackers.begin(), oldAtomMatchingTrackers.end(), + newAtomMatchingTrackers.begin()); + + std::unordered_map newConditionTrackerMap; + const int predicate1Index = 0; + newConditionTrackerMap[predicate1Id] = 0; + // Use the existing conditionTrackers. A bit hacky, but saves code and we don't rely on them. + vector> newConditionTrackers(1); + std::reverse_copy(oldConditionTrackers.begin(), oldConditionTrackers.end(), + newConditionTrackers.begin()); + // Say that predicate1 is unknown since the initial condition never changed. + vector conditionCache = {ConditionState::kUnknown}; + + StatsdConfig newConfig; + *newConfig.add_gauge_metric() = gauge6; + const int gauge6Index = 0; + *newConfig.add_gauge_metric() = gauge3; + const int gauge3Index = 1; + *newConfig.add_gauge_metric() = gauge1; + const int gauge1Index = 2; + *newConfig.add_gauge_metric() = gauge4; + const int gauge4Index = 3; + *newConfig.add_gauge_metric() = gauge2; + const int gauge2Index = 4; + + // Output data structures to validate. + unordered_map newMetricProducerMap; + vector> newMetricProducers; + unordered_map> conditionToMetricMap; + unordered_map> trackerToMetricMap; + set noReportMetricIds; + unordered_map> activationAtomTrackerToMetricMap; + unordered_map> deactivationAtomTrackerToMetricMap; + vector metricsWithActivation; + set replacedMetrics; + EXPECT_TRUE(updateMetrics( + key, newConfig, /*timeBaseNs=*/123, /*currentTimeNs=*/12345, new StatsPullerManager(), + oldAtomMatchingTrackerMap, newAtomMatchingTrackerMap, replacedMatchers, + newAtomMatchingTrackers, newConditionTrackerMap, /*replacedConditions=*/{}, + newConditionTrackers, conditionCache, /*stateAtomIdMap=*/{}, /*allStateGroupMaps=*/{}, + /*replacedStates=*/{}, oldMetricProducerMap, oldMetricProducers, newMetricProducerMap, + newMetricProducers, conditionToMetricMap, trackerToMetricMap, noReportMetricIds, + activationAtomTrackerToMetricMap, deactivationAtomTrackerToMetricMap, + metricsWithActivation, replacedMetrics)); + + unordered_map expectedMetricProducerMap = { + {gauge1Id, gauge1Index}, {gauge2Id, gauge2Index}, {gauge3Id, gauge3Index}, + {gauge4Id, gauge4Index}, {gauge6Id, gauge6Index}, + }; + EXPECT_THAT(newMetricProducerMap, ContainerEq(expectedMetricProducerMap)); + EXPECT_EQ(replacedMetrics, set({gauge2Id, gauge3Id, gauge4Id})); + + // Make sure preserved metrics are the same. + ASSERT_EQ(newMetricProducers.size(), 5); + EXPECT_EQ(oldMetricProducers[oldMetricProducerMap.at(gauge1Id)], + newMetricProducers[newMetricProducerMap.at(gauge1Id)]); + + // Make sure replaced metrics are different. + EXPECT_NE(oldMetricProducers[oldMetricProducerMap.at(gauge2Id)], + newMetricProducers[newMetricProducerMap.at(gauge2Id)]); + EXPECT_NE(oldMetricProducers[oldMetricProducerMap.at(gauge3Id)], + newMetricProducers[newMetricProducerMap.at(gauge3Id)]); + EXPECT_NE(oldMetricProducers[oldMetricProducerMap.at(gauge4Id)], + newMetricProducers[newMetricProducerMap.at(gauge4Id)]); + + // Verify the conditionToMetricMap. + ASSERT_EQ(conditionToMetricMap.size(), 1); + const vector& condition1Metrics = conditionToMetricMap[predicate1Index]; + EXPECT_THAT(condition1Metrics, UnorderedElementsAre(gauge1Index, gauge4Index, gauge6Index)); + + // Verify the trackerToMetricMap. + ASSERT_EQ(trackerToMetricMap.size(), 4); + const vector& matcher1Metrics = trackerToMetricMap[matcher1Index]; + EXPECT_THAT(matcher1Metrics, UnorderedElementsAre(gauge1Index, gauge2Index)); + const vector& matcher3Metrics = trackerToMetricMap[matcher3Index]; + EXPECT_THAT(matcher3Metrics, UnorderedElementsAre(gauge3Index, gauge4Index, gauge6Index)); + const vector& matcher4Metrics = trackerToMetricMap[matcher4Index]; + EXPECT_THAT(matcher4Metrics, UnorderedElementsAre(gauge1Index)); + const vector& matcher5Metrics = trackerToMetricMap[matcher5Index]; + EXPECT_THAT(matcher5Metrics, UnorderedElementsAre(gauge3Index, gauge6Index)); + + // Verify event activation/deactivation maps. + ASSERT_EQ(activationAtomTrackerToMetricMap.size(), 0); + ASSERT_EQ(deactivationAtomTrackerToMetricMap.size(), 0); + ASSERT_EQ(metricsWithActivation.size(), 0); + + // Verify tracker indices/ids/conditions/states are correct. + GaugeMetricProducer* gaugeProducer1 = + static_cast(newMetricProducers[gauge1Index].get()); + EXPECT_EQ(gaugeProducer1->getMetricId(), gauge1Id); + EXPECT_EQ(gaugeProducer1->mConditionTrackerIndex, predicate1Index); + EXPECT_EQ(gaugeProducer1->mCondition, ConditionState::kUnknown); + EXPECT_EQ(gaugeProducer1->mWhatMatcherIndex, matcher4Index); + GaugeMetricProducer* gaugeProducer2 = + static_cast(newMetricProducers[gauge2Index].get()); + EXPECT_EQ(gaugeProducer2->getMetricId(), gauge2Id); + EXPECT_EQ(gaugeProducer2->mConditionTrackerIndex, -1); + EXPECT_EQ(gaugeProducer2->mCondition, ConditionState::kTrue); + EXPECT_EQ(gaugeProducer2->mWhatMatcherIndex, matcher1Index); + GaugeMetricProducer* gaugeProducer3 = + static_cast(newMetricProducers[gauge3Index].get()); + EXPECT_EQ(gaugeProducer3->getMetricId(), gauge3Id); + EXPECT_EQ(gaugeProducer3->mConditionTrackerIndex, -1); + EXPECT_EQ(gaugeProducer3->mCondition, ConditionState::kTrue); + EXPECT_EQ(gaugeProducer3->mWhatMatcherIndex, matcher5Index); + GaugeMetricProducer* gaugeProducer4 = + static_cast(newMetricProducers[gauge4Index].get()); + EXPECT_EQ(gaugeProducer4->getMetricId(), gauge4Id); + EXPECT_EQ(gaugeProducer4->mConditionTrackerIndex, predicate1Index); + EXPECT_EQ(gaugeProducer4->mCondition, ConditionState::kUnknown); + EXPECT_EQ(gaugeProducer4->mWhatMatcherIndex, matcher3Index); + GaugeMetricProducer* gaugeProducer6 = + static_cast(newMetricProducers[gauge6Index].get()); + EXPECT_EQ(gaugeProducer6->getMetricId(), gauge6Id); + EXPECT_EQ(gaugeProducer6->mConditionTrackerIndex, predicate1Index); + EXPECT_EQ(gaugeProducer6->mCondition, ConditionState::kUnknown); + EXPECT_EQ(gaugeProducer6->mWhatMatcherIndex, matcher5Index); + + sp newMatcherWizard = gaugeProducer1->mEventMatcherWizard; + EXPECT_NE(newMatcherWizard, oldMatcherWizard); + EXPECT_EQ(newMatcherWizard->getStrongCount(), 6); + oldMetricProducers.clear(); + // Only reference to the old wizard should be the one in the test. + EXPECT_EQ(oldMatcherWizard->getStrongCount(), 1); +} + +TEST_F(ConfigUpdateTest, TestUpdateDurationMetrics) { + StatsdConfig config; + // Add atom matchers/predicates/states. These are mostly needed for initStatsdConfig. + AtomMatcher matcher1 = CreateScreenTurnedOnAtomMatcher(); + int64_t matcher1Id = matcher1.id(); + *config.add_atom_matcher() = matcher1; + + AtomMatcher matcher2 = CreateScreenTurnedOffAtomMatcher(); + int64_t matcher2Id = matcher2.id(); + *config.add_atom_matcher() = matcher2; + + AtomMatcher matcher3 = CreateAcquireWakelockAtomMatcher(); + int64_t matcher3Id = matcher3.id(); + *config.add_atom_matcher() = matcher3; + + AtomMatcher matcher4 = CreateReleaseWakelockAtomMatcher(); + int64_t matcher4Id = matcher4.id(); + *config.add_atom_matcher() = matcher4; + + AtomMatcher matcher5 = CreateMoveToForegroundAtomMatcher(); + int64_t matcher5Id = matcher5.id(); + *config.add_atom_matcher() = matcher5; + + AtomMatcher matcher6 = CreateMoveToBackgroundAtomMatcher(); + int64_t matcher6Id = matcher6.id(); + *config.add_atom_matcher() = matcher6; + + AtomMatcher matcher7 = CreateBatteryStateNoneMatcher(); + int64_t matcher7Id = matcher7.id(); + *config.add_atom_matcher() = matcher7; + + AtomMatcher matcher8 = CreateBatteryStateUsbMatcher(); + int64_t matcher8Id = matcher8.id(); + *config.add_atom_matcher() = matcher8; + + Predicate predicate1 = CreateScreenIsOnPredicate(); + int64_t predicate1Id = predicate1.id(); + *config.add_predicate() = predicate1; + + Predicate predicate2 = CreateScreenIsOffPredicate(); + int64_t predicate2Id = predicate2.id(); + *config.add_predicate() = predicate2; + + Predicate predicate3 = CreateDeviceUnpluggedPredicate(); + int64_t predicate3Id = predicate3.id(); + *config.add_predicate() = predicate3; + + Predicate predicate4 = CreateIsInBackgroundPredicate(); + *predicate4.mutable_simple_predicate()->mutable_dimensions() = + CreateDimensions(util::ACTIVITY_FOREGROUND_STATE_CHANGED, {1}); + int64_t predicate4Id = predicate4.id(); + *config.add_predicate() = predicate4; + + Predicate predicate5 = CreateHoldingWakelockPredicate(); + *predicate5.mutable_simple_predicate()->mutable_dimensions() = + CreateAttributionUidDimensions(util::WAKELOCK_STATE_CHANGED, {Position::FIRST}); + predicate5.mutable_simple_predicate()->set_stop_all(matcher7Id); + int64_t predicate5Id = predicate5.id(); + *config.add_predicate() = predicate5; + + State state1 = CreateScreenStateWithOnOffMap(0x123, 0x321); + int64_t state1Id = state1.id(); + *config.add_state() = state1; + + State state2 = CreateScreenState(); + int64_t state2Id = state2.id(); + *config.add_state() = state2; + + // Add a few duration metrics. + // Will be preserved. + DurationMetric duration1 = + createDurationMetric("DURATION1", predicate5Id, predicate4Id, {state2Id}); + *duration1.mutable_dimensions_in_what() = + CreateAttributionUidDimensions(util::WAKELOCK_STATE_CHANGED, {Position::FIRST}); + MetricConditionLink* link = duration1.add_links(); + link->set_condition(predicate4Id); + *link->mutable_fields_in_what() = + CreateAttributionUidDimensions(util::WAKELOCK_STATE_CHANGED, {Position::FIRST}); + *link->mutable_fields_in_condition() = + CreateDimensions(util::ACTIVITY_FOREGROUND_STATE_CHANGED, {1} /*uid field*/); + int64_t duration1Id = duration1.id(); + *config.add_duration_metric() = duration1; + + // Will be replaced. + DurationMetric duration2 = createDurationMetric("DURATION2", predicate1Id, nullopt, {}); + int64_t duration2Id = duration2.id(); + *config.add_duration_metric() = duration2; + + // Will be replaced. + DurationMetric duration3 = createDurationMetric("DURATION3", predicate3Id, nullopt, {state1Id}); + int64_t duration3Id = duration3.id(); + *config.add_duration_metric() = duration3; + + // Will be replaced. + DurationMetric duration4 = createDurationMetric("DURATION4", predicate3Id, predicate2Id, {}); + int64_t duration4Id = duration4.id(); + *config.add_duration_metric() = duration4; + + // Will be deleted. + DurationMetric duration5 = createDurationMetric("DURATION5", predicate2Id, nullopt, {}); + int64_t duration5Id = duration5.id(); + *config.add_duration_metric() = duration5; + + EXPECT_TRUE(initConfig(config)); + + // Make some sliced conditions true. + int uid1 = 10; + int uid2 = 11; + vector matchingStates(8, MatchingState::kNotMatched); + matchingStates[2] = kMatched; + vector conditionCache(5, ConditionState::kNotEvaluated); + vector changedCache(5, false); + unique_ptr event = CreateAcquireWakelockEvent(timeBaseNs + 3, {uid1}, {"tag"}, "wl1"); + oldConditionTrackers[4]->evaluateCondition(*event.get(), matchingStates, oldConditionTrackers, + conditionCache, changedCache); + EXPECT_TRUE(oldConditionTrackers[4]->isSliced()); + EXPECT_TRUE(changedCache[4]); + EXPECT_EQ(conditionCache[4], ConditionState::kTrue); + oldMetricProducers[0]->onMatchedLogEvent(2, *event.get()); + + fill(conditionCache.begin(), conditionCache.end(), ConditionState::kNotEvaluated); + fill(changedCache.begin(), changedCache.end(), false); + event = CreateAcquireWakelockEvent(timeBaseNs + 3, {uid2}, {"tag"}, "wl2"); + oldConditionTrackers[4]->evaluateCondition(*event.get(), matchingStates, oldConditionTrackers, + conditionCache, changedCache); + EXPECT_TRUE(changedCache[4]); + EXPECT_EQ(conditionCache[4], ConditionState::kTrue); + oldMetricProducers[0]->onMatchedLogEvent(2, *event.get()); + + // Used later to ensure the condition wizard is replaced. Get it before doing the update. + // The duration trackers have a pointer to the wizard, and 2 trackers were created above. + sp oldConditionWizard = oldMetricProducers[0]->mWizard; + EXPECT_EQ(oldConditionWizard->getStrongCount(), 8); + + // Replace predicate1, predicate3, and state1. Causes duration2/3/4 to be replaced. + set replacedConditions({predicate1Id, predicate2Id}); + set replacedStates({state1Id}); + + // New duration metric. + DurationMetric duration6 = createDurationMetric("DURATION6", predicate4Id, predicate5Id, {}); + *duration6.mutable_dimensions_in_what() = + CreateDimensions(util::ACTIVITY_FOREGROUND_STATE_CHANGED, {1} /*uid field*/); + link = duration6.add_links(); + link->set_condition(predicate5Id); + *link->mutable_fields_in_what() = + CreateDimensions(util::ACTIVITY_FOREGROUND_STATE_CHANGED, {1} /*uid field*/); + *link->mutable_fields_in_condition() = + CreateAttributionUidDimensions(util::WAKELOCK_STATE_CHANGED, {Position::FIRST}); + int64_t duration6Id = duration6.id(); + + // Map the matchers and predicates in reverse order to force the indices to change. + const int matcher8Index = 0, matcher7Index = 1, matcher6Index = 2, matcher5Index = 3, + matcher4Index = 4, matcher3Index = 5, matcher2Index = 6, matcher1Index = 7; + std::unordered_map newAtomMatchingTrackerMap({{matcher8Id, matcher8Index}, + {matcher7Id, matcher7Index}, + {matcher6Id, matcher6Index}, + {matcher5Id, matcher5Index}, + {matcher4Id, matcher4Index}, + {matcher3Id, matcher3Index}, + {matcher2Id, matcher2Index}, + {matcher1Id, matcher1Index}}); + // Use the existing matchers. A bit hacky, but saves code and we don't rely on them. + vector> newAtomMatchingTrackers(8); + reverse_copy(oldAtomMatchingTrackers.begin(), oldAtomMatchingTrackers.end(), + newAtomMatchingTrackers.begin()); + + const int predicate5Index = 0, predicate4Index = 1, predicate3Index = 2, predicate2Index = 3, + predicate1Index = 4; + std::unordered_map newConditionTrackerMap({ + {predicate5Id, predicate5Index}, + {predicate4Id, predicate4Index}, + {predicate3Id, predicate3Index}, + {predicate2Id, predicate2Index}, + {predicate1Id, predicate1Index}, + }); + // Use the existing conditionTrackers and reinitialize them to get the initial condition cache. + vector> newConditionTrackers(5); + reverse_copy(oldConditionTrackers.begin(), oldConditionTrackers.end(), + newConditionTrackers.begin()); + vector conditionProtos(5); + reverse_copy(config.predicate().begin(), config.predicate().end(), conditionProtos.begin()); + for (int i = 0; i < newConditionTrackers.size(); i++) { + EXPECT_TRUE(newConditionTrackers[i]->onConfigUpdated( + conditionProtos, i, newConditionTrackers, newAtomMatchingTrackerMap, + newConditionTrackerMap)); + } + vector cycleTracker(5, false); + fill(conditionCache.begin(), conditionCache.end(), ConditionState::kNotEvaluated); + for (int i = 0; i < newConditionTrackers.size(); i++) { + EXPECT_TRUE(newConditionTrackers[i]->init(conditionProtos, newConditionTrackers, + newConditionTrackerMap, cycleTracker, + conditionCache)); + } + // Predicate5 should be true since 2 uids have wakelocks + EXPECT_EQ(conditionCache, vector({kTrue, kUnknown, kUnknown, kUnknown, kUnknown})); + + StatsdConfig newConfig; + *newConfig.add_duration_metric() = duration6; + const int duration6Index = 0; + *newConfig.add_duration_metric() = duration3; + const int duration3Index = 1; + *newConfig.add_duration_metric() = duration1; + const int duration1Index = 2; + *newConfig.add_duration_metric() = duration4; + const int duration4Index = 3; + *newConfig.add_duration_metric() = duration2; + const int duration2Index = 4; + + for (const Predicate& predicate : conditionProtos) { + *newConfig.add_predicate() = predicate; + } + *newConfig.add_state() = state1; + *newConfig.add_state() = state2; + unordered_map stateAtomIdMap; + unordered_map> allStateGroupMaps; + map stateProtoHashes; + EXPECT_TRUE(initStates(newConfig, stateAtomIdMap, allStateGroupMaps, stateProtoHashes)); + + // Output data structures to validate. + unordered_map newMetricProducerMap; + vector> newMetricProducers; + unordered_map> conditionToMetricMap; + unordered_map> trackerToMetricMap; + set noReportMetricIds; + unordered_map> activationAtomTrackerToMetricMap; + unordered_map> deactivationAtomTrackerToMetricMap; + vector metricsWithActivation; + set replacedMetrics; + EXPECT_TRUE(updateMetrics( + key, newConfig, /*timeBaseNs=*/123, /*currentTimeNs=*/12345, new StatsPullerManager(), + oldAtomMatchingTrackerMap, newAtomMatchingTrackerMap, /*replacedMatchers=*/{}, + newAtomMatchingTrackers, newConditionTrackerMap, replacedConditions, + newConditionTrackers, conditionCache, stateAtomIdMap, allStateGroupMaps, replacedStates, + oldMetricProducerMap, oldMetricProducers, newMetricProducerMap, newMetricProducers, + conditionToMetricMap, trackerToMetricMap, noReportMetricIds, + activationAtomTrackerToMetricMap, deactivationAtomTrackerToMetricMap, + metricsWithActivation, replacedMetrics)); + + unordered_map expectedMetricProducerMap = { + {duration1Id, duration1Index}, {duration2Id, duration2Index}, + {duration3Id, duration3Index}, {duration4Id, duration4Index}, + {duration6Id, duration6Index}, + }; + EXPECT_THAT(newMetricProducerMap, ContainerEq(expectedMetricProducerMap)); + EXPECT_EQ(replacedMetrics, set({duration2Id, duration3Id, duration4Id})); + // Make sure preserved metrics are the same. + ASSERT_EQ(newMetricProducers.size(), 5); + EXPECT_EQ(oldMetricProducers[oldMetricProducerMap.at(duration1Id)], + newMetricProducers[newMetricProducerMap.at(duration1Id)]); + + // Make sure replaced metrics are different. + EXPECT_NE(oldMetricProducers[oldMetricProducerMap.at(duration2Id)], + newMetricProducers[newMetricProducerMap.at(duration2Id)]); + EXPECT_NE(oldMetricProducers[oldMetricProducerMap.at(duration3Id)], + newMetricProducers[newMetricProducerMap.at(duration3Id)]); + EXPECT_NE(oldMetricProducers[oldMetricProducerMap.at(duration4Id)], + newMetricProducers[newMetricProducerMap.at(duration4Id)]); + + // Verify the conditionToMetricMap. Note that the "what" is not in this map. + ASSERT_EQ(conditionToMetricMap.size(), 3); + const vector& condition2Metrics = conditionToMetricMap[predicate2Index]; + EXPECT_THAT(condition2Metrics, UnorderedElementsAre(duration4Index)); + const vector& condition4Metrics = conditionToMetricMap[predicate4Index]; + EXPECT_THAT(condition4Metrics, UnorderedElementsAre(duration1Index)); + const vector& condition5Metrics = conditionToMetricMap[predicate5Index]; + EXPECT_THAT(condition5Metrics, UnorderedElementsAre(duration6Index)); + + // Verify the trackerToMetricMap. The start/stop/stopall indices from the "what" should be here. + ASSERT_EQ(trackerToMetricMap.size(), 8); + const vector& matcher1Metrics = trackerToMetricMap[matcher1Index]; + EXPECT_THAT(matcher1Metrics, UnorderedElementsAre(duration2Index)); + const vector& matcher2Metrics = trackerToMetricMap[matcher2Index]; + EXPECT_THAT(matcher2Metrics, UnorderedElementsAre(duration2Index)); + const vector& matcher3Metrics = trackerToMetricMap[matcher3Index]; + EXPECT_THAT(matcher3Metrics, UnorderedElementsAre(duration1Index)); + const vector& matcher4Metrics = trackerToMetricMap[matcher4Index]; + EXPECT_THAT(matcher4Metrics, UnorderedElementsAre(duration1Index)); + const vector& matcher5Metrics = trackerToMetricMap[matcher5Index]; + EXPECT_THAT(matcher5Metrics, UnorderedElementsAre(duration6Index)); + const vector& matcher6Metrics = trackerToMetricMap[matcher6Index]; + EXPECT_THAT(matcher6Metrics, UnorderedElementsAre(duration6Index)); + const vector& matcher7Metrics = trackerToMetricMap[matcher7Index]; + EXPECT_THAT(matcher7Metrics, + UnorderedElementsAre(duration1Index, duration3Index, duration4Index)); + const vector& matcher8Metrics = trackerToMetricMap[matcher8Index]; + EXPECT_THAT(matcher8Metrics, UnorderedElementsAre(duration3Index, duration4Index)); + + // Verify event activation/deactivation maps. + ASSERT_EQ(activationAtomTrackerToMetricMap.size(), 0); + ASSERT_EQ(deactivationAtomTrackerToMetricMap.size(), 0); + ASSERT_EQ(metricsWithActivation.size(), 0); + + // Verify tracker indices/ids/conditions are correct. + DurationMetricProducer* durationProducer1 = + static_cast(newMetricProducers[duration1Index].get()); + EXPECT_EQ(durationProducer1->getMetricId(), duration1Id); + EXPECT_EQ(durationProducer1->mConditionTrackerIndex, predicate4Index); + EXPECT_EQ(durationProducer1->mCondition, ConditionState::kUnknown); + EXPECT_EQ(durationProducer1->mStartIndex, matcher3Index); + EXPECT_EQ(durationProducer1->mStopIndex, matcher4Index); + EXPECT_EQ(durationProducer1->mStopAllIndex, matcher7Index); + EXPECT_EQ(durationProducer1->mCurrentSlicedDurationTrackerMap.size(), 2); + for (const auto& durationTrackerIt : durationProducer1->mCurrentSlicedDurationTrackerMap) { + EXPECT_EQ(durationTrackerIt.second->mConditionTrackerIndex, predicate4Index); + } + DurationMetricProducer* durationProducer2 = + static_cast(newMetricProducers[duration2Index].get()); + EXPECT_EQ(durationProducer2->getMetricId(), duration2Id); + EXPECT_EQ(durationProducer2->mConditionTrackerIndex, -1); + EXPECT_EQ(durationProducer2->mCondition, ConditionState::kTrue); + EXPECT_EQ(durationProducer2->mStartIndex, matcher1Index); + EXPECT_EQ(durationProducer2->mStopIndex, matcher2Index); + EXPECT_EQ(durationProducer2->mStopAllIndex, -1); + DurationMetricProducer* durationProducer3 = + static_cast(newMetricProducers[duration3Index].get()); + EXPECT_EQ(durationProducer3->getMetricId(), duration3Id); + EXPECT_EQ(durationProducer3->mConditionTrackerIndex, -1); + EXPECT_EQ(durationProducer3->mCondition, ConditionState::kTrue); + EXPECT_EQ(durationProducer3->mStartIndex, matcher7Index); + EXPECT_EQ(durationProducer3->mStopIndex, matcher8Index); + EXPECT_EQ(durationProducer3->mStopAllIndex, -1); + DurationMetricProducer* durationProducer4 = + static_cast(newMetricProducers[duration4Index].get()); + EXPECT_EQ(durationProducer4->getMetricId(), duration4Id); + EXPECT_EQ(durationProducer4->mConditionTrackerIndex, predicate2Index); + EXPECT_EQ(durationProducer4->mCondition, ConditionState::kUnknown); + EXPECT_EQ(durationProducer4->mStartIndex, matcher7Index); + EXPECT_EQ(durationProducer4->mStopIndex, matcher8Index); + EXPECT_EQ(durationProducer4->mStopAllIndex, -1); + DurationMetricProducer* durationProducer6 = + static_cast(newMetricProducers[duration6Index].get()); + EXPECT_EQ(durationProducer6->getMetricId(), duration6Id); + EXPECT_EQ(durationProducer6->mConditionTrackerIndex, predicate5Index); + // TODO(b/167491517): should this be unknown since the condition is sliced? + EXPECT_EQ(durationProducer6->mCondition, ConditionState::kTrue); + EXPECT_EQ(durationProducer6->mStartIndex, matcher6Index); + EXPECT_EQ(durationProducer6->mStopIndex, matcher5Index); + EXPECT_EQ(durationProducer6->mStopAllIndex, -1); + + sp newConditionWizard = newMetricProducers[0]->mWizard; + EXPECT_NE(newConditionWizard, oldConditionWizard); + EXPECT_EQ(newConditionWizard->getStrongCount(), 8); + oldMetricProducers.clear(); + // Only reference to the old wizard should be the one in the test. + EXPECT_EQ(oldConditionWizard->getStrongCount(), 1); +} + +TEST_F(ConfigUpdateTest, TestUpdateValueMetrics) { + StatsdConfig config; + + // Add atom matchers/predicates/states. These are mostly needed for initStatsdConfig. + AtomMatcher matcher1 = CreateScreenTurnedOnAtomMatcher(); + int64_t matcher1Id = matcher1.id(); + *config.add_atom_matcher() = matcher1; + + AtomMatcher matcher2 = CreateScreenTurnedOffAtomMatcher(); + int64_t matcher2Id = matcher2.id(); + *config.add_atom_matcher() = matcher2; + + AtomMatcher matcher3 = CreateStartScheduledJobAtomMatcher(); + int64_t matcher3Id = matcher3.id(); + *config.add_atom_matcher() = matcher3; + + AtomMatcher matcher4 = CreateTemperatureAtomMatcher(); + int64_t matcher4Id = matcher4.id(); + *config.add_atom_matcher() = matcher4; + + AtomMatcher matcher5 = CreateSimpleAtomMatcher("SubsystemSleep", util::SUBSYSTEM_SLEEP_STATE); + int64_t matcher5Id = matcher5.id(); + *config.add_atom_matcher() = matcher5; + + Predicate predicate1 = CreateScreenIsOnPredicate(); + int64_t predicate1Id = predicate1.id(); + *config.add_predicate() = predicate1; + + Predicate predicate2 = CreateScreenIsOffPredicate(); + int64_t predicate2Id = predicate2.id(); + *config.add_predicate() = predicate2; + + State state1 = CreateScreenStateWithOnOffMap(0x123, 0x321); + int64_t state1Id = state1.id(); + *config.add_state() = state1; + + State state2 = CreateScreenState(); + int64_t state2Id = state2.id(); + *config.add_state() = state2; + + // Add a few value metrics. + // Note that these will not work as "real" metrics since the value field is always 2. + // Will be preserved. + ValueMetric value1 = createValueMetric("VALUE1", matcher4, 2, predicate1Id, {state1Id}); + int64_t value1Id = value1.id(); + *config.add_value_metric() = value1; + + // Will be replaced - definition change. + ValueMetric value2 = createValueMetric("VALUE2", matcher1, 2, nullopt, {}); + int64_t value2Id = value2.id(); + *config.add_value_metric() = value2; + + // Will be replaced - condition change. + ValueMetric value3 = createValueMetric("VALUE3", matcher5, 2, predicate2Id, {}); + int64_t value3Id = value3.id(); + *config.add_value_metric() = value3; + + // Will be replaced - state change. + ValueMetric value4 = createValueMetric("VALUE4", matcher3, 2, nullopt, {state2Id}); + int64_t value4Id = value4.id(); + *config.add_value_metric() = value4; + + // Will be deleted. + ValueMetric value5 = createValueMetric("VALUE5", matcher2, 2, nullopt, {}); + int64_t value5Id = value5.id(); + *config.add_value_metric() = value5; + + EXPECT_TRUE(initConfig(config)); + + // Used later to ensure the condition wizard is replaced. Get it before doing the update. + sp oldMatcherWizard = + static_cast(oldMetricProducers[0].get())->mEventMatcherWizard; + EXPECT_EQ(oldMatcherWizard->getStrongCount(), 6); + + // Change value2, causing it to be replaced. + value2.set_aggregation_type(ValueMetric::AVG); + + // Mark predicate 2 as replaced. Causes value3 to be replaced. + set replacedConditions = {predicate2Id}; + + // Mark state 2 as replaced. Causes value4 to be replaced. + set replacedStates = {state2Id}; + + // New value metric. + ValueMetric value6 = createValueMetric("VALUE6", matcher5, 2, predicate1Id, {state1Id}); + int64_t value6Id = value6.id(); + + // Map the matchers and predicates in reverse order to force the indices to change. + std::unordered_map newAtomMatchingTrackerMap; + const int matcher5Index = 0; + newAtomMatchingTrackerMap[matcher5Id] = 0; + const int matcher4Index = 1; + newAtomMatchingTrackerMap[matcher4Id] = 1; + const int matcher3Index = 2; + newAtomMatchingTrackerMap[matcher3Id] = 2; + const int matcher2Index = 3; + newAtomMatchingTrackerMap[matcher2Id] = 3; + const int matcher1Index = 4; + newAtomMatchingTrackerMap[matcher1Id] = 4; + // Use the existing matchers. A bit hacky, but saves code and we don't rely on them. + vector> newAtomMatchingTrackers(5); + std::reverse_copy(oldAtomMatchingTrackers.begin(), oldAtomMatchingTrackers.end(), + newAtomMatchingTrackers.begin()); + + std::unordered_map newConditionTrackerMap; + const int predicate2Index = 0; + newConditionTrackerMap[predicate2Id] = 0; + const int predicate1Index = 1; + newConditionTrackerMap[predicate1Id] = 1; + // Use the existing conditionTrackers. A bit hacky, but saves code and we don't rely on them. + vector> newConditionTrackers(2); + std::reverse_copy(oldConditionTrackers.begin(), oldConditionTrackers.end(), + newConditionTrackers.begin()); + // Say that predicate1 & predicate2 is unknown since the initial condition never changed. + vector conditionCache = {ConditionState::kUnknown, ConditionState::kUnknown}; + + StatsdConfig newConfig; + *newConfig.add_value_metric() = value6; + const int value6Index = 0; + *newConfig.add_value_metric() = value3; + const int value3Index = 1; + *newConfig.add_value_metric() = value1; + const int value1Index = 2; + *newConfig.add_value_metric() = value4; + const int value4Index = 3; + *newConfig.add_value_metric() = value2; + const int value2Index = 4; + + *newConfig.add_state() = state1; + *newConfig.add_state() = state2; + + unordered_map stateAtomIdMap; + unordered_map> allStateGroupMaps; + map stateProtoHashes; + EXPECT_TRUE(initStates(newConfig, stateAtomIdMap, allStateGroupMaps, stateProtoHashes)); + + // Output data structures to validate. + unordered_map newMetricProducerMap; + vector> newMetricProducers; + unordered_map> conditionToMetricMap; + unordered_map> trackerToMetricMap; + set noReportMetricIds; + unordered_map> activationAtomTrackerToMetricMap; + unordered_map> deactivationAtomTrackerToMetricMap; + vector metricsWithActivation; + set replacedMetrics; + EXPECT_TRUE(updateMetrics( + key, newConfig, /*timeBaseNs=*/123, /*currentTimeNs=*/12345, new StatsPullerManager(), + oldAtomMatchingTrackerMap, newAtomMatchingTrackerMap, /*replacedMatchers=*/{}, + newAtomMatchingTrackers, newConditionTrackerMap, replacedConditions, + newConditionTrackers, conditionCache, stateAtomIdMap, allStateGroupMaps, replacedStates, + oldMetricProducerMap, oldMetricProducers, newMetricProducerMap, newMetricProducers, + conditionToMetricMap, trackerToMetricMap, noReportMetricIds, + activationAtomTrackerToMetricMap, deactivationAtomTrackerToMetricMap, + metricsWithActivation, replacedMetrics)); + + unordered_map expectedMetricProducerMap = { + {value1Id, value1Index}, {value2Id, value2Index}, {value3Id, value3Index}, + {value4Id, value4Index}, {value6Id, value6Index}, + }; + EXPECT_THAT(newMetricProducerMap, ContainerEq(expectedMetricProducerMap)); + EXPECT_EQ(replacedMetrics, set({value2Id, value3Id, value4Id})); + + // Make sure preserved metrics are the same. + ASSERT_EQ(newMetricProducers.size(), 5); + EXPECT_EQ(oldMetricProducers[oldMetricProducerMap.at(value1Id)], + newMetricProducers[newMetricProducerMap.at(value1Id)]); + + // Make sure replaced metrics are different. + EXPECT_NE(oldMetricProducers[oldMetricProducerMap.at(value2Id)], + newMetricProducers[newMetricProducerMap.at(value2Id)]); + EXPECT_NE(oldMetricProducers[oldMetricProducerMap.at(value3Id)], + newMetricProducers[newMetricProducerMap.at(value3Id)]); + EXPECT_NE(oldMetricProducers[oldMetricProducerMap.at(value4Id)], + newMetricProducers[newMetricProducerMap.at(value4Id)]); + + // Verify the conditionToMetricMap. + ASSERT_EQ(conditionToMetricMap.size(), 2); + const vector& condition1Metrics = conditionToMetricMap[predicate1Index]; + EXPECT_THAT(condition1Metrics, UnorderedElementsAre(value1Index, value6Index)); + const vector& condition2Metrics = conditionToMetricMap[predicate2Index]; + EXPECT_THAT(condition2Metrics, UnorderedElementsAre(value3Index)); + + // Verify the trackerToMetricMap. + ASSERT_EQ(trackerToMetricMap.size(), 4); + const vector& matcher1Metrics = trackerToMetricMap[matcher1Index]; + EXPECT_THAT(matcher1Metrics, UnorderedElementsAre(value2Index)); + const vector& matcher3Metrics = trackerToMetricMap[matcher3Index]; + EXPECT_THAT(matcher3Metrics, UnorderedElementsAre(value4Index)); + const vector& matcher4Metrics = trackerToMetricMap[matcher4Index]; + EXPECT_THAT(matcher4Metrics, UnorderedElementsAre(value1Index)); + const vector& matcher5Metrics = trackerToMetricMap[matcher5Index]; + EXPECT_THAT(matcher5Metrics, UnorderedElementsAre(value3Index, value6Index)); + + // Verify event activation/deactivation maps. + ASSERT_EQ(activationAtomTrackerToMetricMap.size(), 0); + ASSERT_EQ(deactivationAtomTrackerToMetricMap.size(), 0); + ASSERT_EQ(metricsWithActivation.size(), 0); + + // Verify tracker indices/ids/conditions/states are correct. + ValueMetricProducer* valueProducer1 = + static_cast(newMetricProducers[value1Index].get()); + EXPECT_EQ(valueProducer1->getMetricId(), value1Id); + EXPECT_EQ(valueProducer1->mConditionTrackerIndex, predicate1Index); + EXPECT_EQ(valueProducer1->mCondition, ConditionState::kUnknown); + EXPECT_EQ(valueProducer1->mWhatMatcherIndex, matcher4Index); + ValueMetricProducer* valueProducer2 = + static_cast(newMetricProducers[value2Index].get()); + EXPECT_EQ(valueProducer2->getMetricId(), value2Id); + EXPECT_EQ(valueProducer2->mConditionTrackerIndex, -1); + EXPECT_EQ(valueProducer2->mCondition, ConditionState::kTrue); + EXPECT_EQ(valueProducer2->mWhatMatcherIndex, matcher1Index); + ValueMetricProducer* valueProducer3 = + static_cast(newMetricProducers[value3Index].get()); + EXPECT_EQ(valueProducer3->getMetricId(), value3Id); + EXPECT_EQ(valueProducer3->mConditionTrackerIndex, predicate2Index); + EXPECT_EQ(valueProducer3->mCondition, ConditionState::kUnknown); + EXPECT_EQ(valueProducer3->mWhatMatcherIndex, matcher5Index); + ValueMetricProducer* valueProducer4 = + static_cast(newMetricProducers[value4Index].get()); + EXPECT_EQ(valueProducer4->getMetricId(), value4Id); + EXPECT_EQ(valueProducer4->mConditionTrackerIndex, -1); + EXPECT_EQ(valueProducer4->mCondition, ConditionState::kTrue); + EXPECT_EQ(valueProducer4->mWhatMatcherIndex, matcher3Index); + ValueMetricProducer* valueProducer6 = + static_cast(newMetricProducers[value6Index].get()); + EXPECT_EQ(valueProducer6->getMetricId(), value6Id); + EXPECT_EQ(valueProducer6->mConditionTrackerIndex, predicate1Index); + EXPECT_EQ(valueProducer6->mCondition, ConditionState::kUnknown); + EXPECT_EQ(valueProducer6->mWhatMatcherIndex, matcher5Index); + + sp newMatcherWizard = valueProducer1->mEventMatcherWizard; + EXPECT_NE(newMatcherWizard, oldMatcherWizard); + EXPECT_EQ(newMatcherWizard->getStrongCount(), 6); + oldMetricProducers.clear(); + // Only reference to the old wizard should be the one in the test. + EXPECT_EQ(oldMatcherWizard->getStrongCount(), 1); +} + +TEST_F(ConfigUpdateTest, TestUpdateMetricActivations) { + StatsdConfig config; + // Add atom matchers + AtomMatcher matcher1 = CreateScreenTurnedOnAtomMatcher(); + int64_t matcher1Id = matcher1.id(); + *config.add_atom_matcher() = matcher1; + + AtomMatcher matcher2 = CreateScreenTurnedOffAtomMatcher(); + int64_t matcher2Id = matcher2.id(); + *config.add_atom_matcher() = matcher2; + + AtomMatcher matcher3 = CreateStartScheduledJobAtomMatcher(); + int64_t matcher3Id = matcher3.id(); + *config.add_atom_matcher() = matcher3; + + AtomMatcher matcher4 = CreateFinishScheduledJobAtomMatcher(); + int64_t matcher4Id = matcher4.id(); + *config.add_atom_matcher() = matcher4; + + // Add an event metric with multiple activations. + EventMetric event1 = createEventMetric("EVENT1", matcher1Id, nullopt); + int64_t event1Id = event1.id(); + *config.add_event_metric() = event1; + + int64_t matcher2TtlSec = 2, matcher3TtlSec = 3, matcher4TtlSec = 4; + MetricActivation metricActivation; + metricActivation.set_metric_id(event1Id); + EventActivation* activation = metricActivation.add_event_activation(); + activation->set_atom_matcher_id(matcher2Id); + activation->set_ttl_seconds(matcher2TtlSec); + activation->set_activation_type(ACTIVATE_IMMEDIATELY); + activation->set_deactivation_atom_matcher_id(matcher1Id); + activation = metricActivation.add_event_activation(); + activation->set_atom_matcher_id(matcher3Id); + activation->set_ttl_seconds(matcher3TtlSec); + activation->set_activation_type(ACTIVATE_ON_BOOT); + activation->set_deactivation_atom_matcher_id(matcher1Id); + activation = metricActivation.add_event_activation(); + activation->set_atom_matcher_id(matcher4Id); + activation->set_ttl_seconds(matcher4TtlSec); + activation->set_activation_type(ACTIVATE_IMMEDIATELY); + activation->set_deactivation_atom_matcher_id(matcher2Id); + *config.add_metric_activation() = metricActivation; + + EXPECT_TRUE(initConfig(config)); + + // Activate some of the event activations. + ASSERT_EQ(oldMetricProducers[0]->getMetricId(), event1Id); + int64_t matcher2StartNs = 12345; + oldMetricProducers[0]->activate(oldAtomMatchingTrackerMap[matcher2Id], matcher2StartNs); + int64_t matcher3StartNs = 23456; + oldMetricProducers[0]->activate(oldAtomMatchingTrackerMap[matcher3Id], matcher3StartNs); + EXPECT_TRUE(oldMetricProducers[0]->isActive()); + + // Map the matchers and predicates in reverse order to force the indices to change. + std::unordered_map newAtomMatchingTrackerMap; + const int matcher4Index = 0; + newAtomMatchingTrackerMap[matcher4Id] = 0; + const int matcher3Index = 1; + newAtomMatchingTrackerMap[matcher3Id] = 1; + const int matcher2Index = 2; + newAtomMatchingTrackerMap[matcher2Id] = 2; + const int matcher1Index = 3; + newAtomMatchingTrackerMap[matcher1Id] = 3; + // Use the existing matchers. A bit hacky, but saves code and we don't rely on them. + vector> newAtomMatchingTrackers(4); + std::reverse_copy(oldAtomMatchingTrackers.begin(), oldAtomMatchingTrackers.end(), + newAtomMatchingTrackers.begin()); + set replacedMatchers; + + unordered_map newConditionTrackerMap; + vector> newConditionTrackers; + set replacedConditions; + vector conditionCache; + unordered_map newMetricProducerMap; + vector> newMetricProducers; + unordered_map> conditionToMetricMap; + unordered_map> trackerToMetricMap; + set noReportMetricIds; + unordered_map> activationAtomTrackerToMetricMap; + unordered_map> deactivationAtomTrackerToMetricMap; + vector metricsWithActivation; + set replacedMetrics; + EXPECT_TRUE(updateMetrics( + key, config, /*timeBaseNs=*/123, /*currentTimeNs=*/12345, new StatsPullerManager(), + oldAtomMatchingTrackerMap, newAtomMatchingTrackerMap, replacedMatchers, + newAtomMatchingTrackers, newConditionTrackerMap, replacedConditions, + newConditionTrackers, conditionCache, /*stateAtomIdMap=*/{}, /*allStateGroupMaps=*/{}, + /*replacedStates=*/{}, oldMetricProducerMap, oldMetricProducers, newMetricProducerMap, + newMetricProducers, conditionToMetricMap, trackerToMetricMap, noReportMetricIds, + activationAtomTrackerToMetricMap, deactivationAtomTrackerToMetricMap, + metricsWithActivation, replacedMetrics)); + + // Verify event activation/deactivation maps. + ASSERT_EQ(activationAtomTrackerToMetricMap.size(), 3); + EXPECT_THAT(activationAtomTrackerToMetricMap[matcher2Index], UnorderedElementsAre(0)); + EXPECT_THAT(activationAtomTrackerToMetricMap[matcher3Index], UnorderedElementsAre(0)); + EXPECT_THAT(activationAtomTrackerToMetricMap[matcher4Index], UnorderedElementsAre(0)); + ASSERT_EQ(deactivationAtomTrackerToMetricMap.size(), 2); + EXPECT_THAT(deactivationAtomTrackerToMetricMap[matcher1Index], UnorderedElementsAre(0, 0)); + EXPECT_THAT(deactivationAtomTrackerToMetricMap[matcher2Index], UnorderedElementsAre(0)); + ASSERT_EQ(metricsWithActivation.size(), 1); + EXPECT_THAT(metricsWithActivation, UnorderedElementsAre(0)); + + // Verify mEventActivation and mEventDeactivation map of the producer. + sp producer = newMetricProducers[0]; + EXPECT_TRUE(producer->isActive()); + ASSERT_EQ(producer->mEventActivationMap.size(), 3); + shared_ptr matcher2Activation = producer->mEventActivationMap[matcher2Index]; + EXPECT_EQ(matcher2Activation->ttl_ns, matcher2TtlSec * NS_PER_SEC); + EXPECT_EQ(matcher2Activation->activationType, ACTIVATE_IMMEDIATELY); + EXPECT_EQ(matcher2Activation->state, kActive); + EXPECT_EQ(matcher2Activation->start_ns, matcher2StartNs); + shared_ptr matcher3Activation = producer->mEventActivationMap[matcher3Index]; + EXPECT_EQ(matcher3Activation->ttl_ns, matcher3TtlSec * NS_PER_SEC); + EXPECT_EQ(matcher3Activation->activationType, ACTIVATE_ON_BOOT); + EXPECT_EQ(matcher3Activation->state, kActiveOnBoot); + shared_ptr matcher4Activation = producer->mEventActivationMap[matcher4Index]; + EXPECT_EQ(matcher4Activation->ttl_ns, matcher4TtlSec * NS_PER_SEC); + EXPECT_EQ(matcher4Activation->activationType, ACTIVATE_IMMEDIATELY); + EXPECT_EQ(matcher4Activation->state, kNotActive); + + ASSERT_EQ(producer->mEventDeactivationMap.size(), 2); + EXPECT_THAT(producer->mEventDeactivationMap[matcher1Index], + UnorderedElementsAre(matcher2Activation, matcher3Activation)); + EXPECT_THAT(producer->mEventDeactivationMap[matcher2Index], + UnorderedElementsAre(matcher4Activation)); +} + +TEST_F(ConfigUpdateTest, TestUpdateMetricsMultipleTypes) { + StatsdConfig config; + // Add atom matchers/predicates/states. These are mostly needed for initStatsdConfig + AtomMatcher matcher1 = CreateScreenTurnedOnAtomMatcher(); + int64_t matcher1Id = matcher1.id(); + *config.add_atom_matcher() = matcher1; + + AtomMatcher matcher2 = CreateScreenTurnedOffAtomMatcher(); + int64_t matcher2Id = matcher2.id(); + *config.add_atom_matcher() = matcher2; + + AtomMatcher matcher3 = CreateTemperatureAtomMatcher(); + int64_t matcher3Id = matcher3.id(); + *config.add_atom_matcher() = matcher3; + + Predicate predicate1 = CreateScreenIsOnPredicate(); + int64_t predicate1Id = predicate1.id(); + *config.add_predicate() = predicate1; + + // Add a few count metrics. + // Will be preserved. + CountMetric countMetric = createCountMetric("COUNT1", matcher1Id, predicate1Id, {}); + int64_t countMetricId = countMetric.id(); + *config.add_count_metric() = countMetric; + + // Will be replaced since matcher2 is replaced. + EventMetric eventMetric = createEventMetric("EVENT1", matcher2Id, nullopt); + int64_t eventMetricId = eventMetric.id(); + *config.add_event_metric() = eventMetric; + + // Will be replaced because the definition changes - a predicate is added. + GaugeMetric gaugeMetric = createGaugeMetric("GAUGE1", matcher3Id, + GaugeMetric::RANDOM_ONE_SAMPLE, nullopt, nullopt); + int64_t gaugeMetricId = gaugeMetric.id(); + *config.add_gauge_metric() = gaugeMetric; + + // Preserved. + ValueMetric valueMetric = createValueMetric("VALUE1", matcher3, 2, predicate1Id, {}); + int64_t valueMetricId = valueMetric.id(); + *config.add_value_metric() = valueMetric; + + // Preserved. + DurationMetric durationMetric = createDurationMetric("DURATION1", predicate1Id, nullopt, {}); + int64_t durationMetricId = durationMetric.id(); + *config.add_duration_metric() = durationMetric; + + EXPECT_TRUE(initConfig(config)); + + // Used later to ensure the condition wizard is replaced. Get it before doing the update. + sp oldConditionWizard = oldMetricProducers[0]->mWizard; + EXPECT_EQ(oldConditionWizard->getStrongCount(), 6); + + // Mark matcher 2 as replaced. Causes eventMetric to be replaced. + set replacedMatchers; + replacedMatchers.insert(matcher2Id); + + // Add predicate1 as a predicate on gaugeMetric, causing it to be replaced. + gaugeMetric.set_condition(predicate1Id); + + // Map the matchers and predicates in reverse order to force the indices to change. + std::unordered_map newAtomMatchingTrackerMap; + const int matcher3Index = 0; + newAtomMatchingTrackerMap[matcher3Id] = 0; + const int matcher2Index = 1; + newAtomMatchingTrackerMap[matcher2Id] = 1; + const int matcher1Index = 2; + newAtomMatchingTrackerMap[matcher1Id] = 2; + // Use the existing matchers. A bit hacky, but saves code and we don't rely on them. + vector> newAtomMatchingTrackers(3); + std::reverse_copy(oldAtomMatchingTrackers.begin(), oldAtomMatchingTrackers.end(), + newAtomMatchingTrackers.begin()); + + std::unordered_map newConditionTrackerMap; + const int predicate1Index = 0; + newConditionTrackerMap[predicate1Id] = 0; + // Use the existing conditionTrackers. A bit hacky, but saves code and we don't rely on them. + vector> newConditionTrackers(1); + std::reverse_copy(oldConditionTrackers.begin(), oldConditionTrackers.end(), + newConditionTrackers.begin()); + vector conditionCache = {ConditionState::kUnknown}; + + // The order matters. we parse in the order of: count, duration, event, value, gauge. + StatsdConfig newConfig; + *newConfig.add_count_metric() = countMetric; + const int countMetricIndex = 0; + *newConfig.add_duration_metric() = durationMetric; + const int durationMetricIndex = 1; + *newConfig.add_event_metric() = eventMetric; + const int eventMetricIndex = 2; + *newConfig.add_value_metric() = valueMetric; + const int valueMetricIndex = 3; + *newConfig.add_gauge_metric() = gaugeMetric; + const int gaugeMetricIndex = 4; + + // Add the predicate since duration metric needs it. + *newConfig.add_predicate() = predicate1; + + // Output data structures to validate. + unordered_map newMetricProducerMap; + vector> newMetricProducers; + unordered_map> conditionToMetricMap; + unordered_map> trackerToMetricMap; + set noReportMetricIds; + unordered_map> activationAtomTrackerToMetricMap; + unordered_map> deactivationAtomTrackerToMetricMap; + vector metricsWithActivation; + set replacedMetrics; + EXPECT_TRUE(updateMetrics( + key, newConfig, /*timeBaseNs=*/123, /*currentTimeNs=*/12345, new StatsPullerManager(), + oldAtomMatchingTrackerMap, newAtomMatchingTrackerMap, replacedMatchers, + newAtomMatchingTrackers, newConditionTrackerMap, /*replacedConditions=*/{}, + newConditionTrackers, conditionCache, /*stateAtomIdMap*/ {}, /*allStateGroupMaps=*/{}, + /*replacedStates=*/{}, oldMetricProducerMap, oldMetricProducers, newMetricProducerMap, + newMetricProducers, conditionToMetricMap, trackerToMetricMap, noReportMetricIds, + activationAtomTrackerToMetricMap, deactivationAtomTrackerToMetricMap, + metricsWithActivation, replacedMetrics)); + + unordered_map expectedMetricProducerMap = { + {countMetricId, countMetricIndex}, {durationMetricId, durationMetricIndex}, + {eventMetricId, eventMetricIndex}, {valueMetricId, valueMetricIndex}, + {gaugeMetricId, gaugeMetricIndex}, + }; + EXPECT_THAT(newMetricProducerMap, ContainerEq(expectedMetricProducerMap)); + + EXPECT_EQ(replacedMetrics, set({eventMetricId, gaugeMetricId})); + + // Make sure preserved metrics are the same. + ASSERT_EQ(newMetricProducers.size(), 5); + EXPECT_EQ(oldMetricProducers[oldMetricProducerMap.at(countMetricId)], + newMetricProducers[newMetricProducerMap.at(countMetricId)]); + EXPECT_EQ(oldMetricProducers[oldMetricProducerMap.at(durationMetricId)], + newMetricProducers[newMetricProducerMap.at(durationMetricId)]); + EXPECT_EQ(oldMetricProducers[oldMetricProducerMap.at(valueMetricId)], + newMetricProducers[newMetricProducerMap.at(valueMetricId)]); + + // Make sure replaced metrics are different. + EXPECT_NE(oldMetricProducers[oldMetricProducerMap.at(eventMetricId)], + newMetricProducers[newMetricProducerMap.at(eventMetricId)]); + EXPECT_NE(oldMetricProducers[oldMetricProducerMap.at(gaugeMetricId)], + newMetricProducers[newMetricProducerMap.at(gaugeMetricId)]); + + // Verify the conditionToMetricMap. + ASSERT_EQ(conditionToMetricMap.size(), 1); + const vector& condition1Metrics = conditionToMetricMap[predicate1Index]; + EXPECT_THAT(condition1Metrics, + UnorderedElementsAre(countMetricIndex, gaugeMetricIndex, valueMetricIndex)); + + // Verify the trackerToMetricMap. + ASSERT_EQ(trackerToMetricMap.size(), 3); + const vector& matcher1Metrics = trackerToMetricMap[matcher1Index]; + EXPECT_THAT(matcher1Metrics, UnorderedElementsAre(countMetricIndex, durationMetricIndex)); + const vector& matcher2Metrics = trackerToMetricMap[matcher2Index]; + EXPECT_THAT(matcher2Metrics, UnorderedElementsAre(eventMetricIndex, durationMetricIndex)); + const vector& matcher3Metrics = trackerToMetricMap[matcher3Index]; + EXPECT_THAT(matcher3Metrics, UnorderedElementsAre(gaugeMetricIndex, valueMetricIndex)); + + // Verify event activation/deactivation maps. + ASSERT_EQ(activationAtomTrackerToMetricMap.size(), 0); + ASSERT_EQ(deactivationAtomTrackerToMetricMap.size(), 0); + ASSERT_EQ(metricsWithActivation.size(), 0); + + // Verify tracker indices/ids/conditions are correct. + EXPECT_EQ(newMetricProducers[countMetricIndex]->getMetricId(), countMetricId); + EXPECT_EQ(newMetricProducers[countMetricIndex]->mConditionTrackerIndex, predicate1Index); + EXPECT_EQ(newMetricProducers[countMetricIndex]->mCondition, ConditionState::kUnknown); + EXPECT_EQ(newMetricProducers[durationMetricIndex]->getMetricId(), durationMetricId); + EXPECT_EQ(newMetricProducers[durationMetricIndex]->mConditionTrackerIndex, -1); + EXPECT_EQ(newMetricProducers[durationMetricIndex]->mCondition, ConditionState::kTrue); + EXPECT_EQ(newMetricProducers[eventMetricIndex]->getMetricId(), eventMetricId); + EXPECT_EQ(newMetricProducers[eventMetricIndex]->mConditionTrackerIndex, -1); + EXPECT_EQ(newMetricProducers[eventMetricIndex]->mCondition, ConditionState::kTrue); + EXPECT_EQ(newMetricProducers[gaugeMetricIndex]->getMetricId(), gaugeMetricId); + EXPECT_EQ(newMetricProducers[gaugeMetricIndex]->mConditionTrackerIndex, predicate1Index); + EXPECT_EQ(newMetricProducers[gaugeMetricIndex]->mCondition, ConditionState::kUnknown); + + sp newConditionWizard = newMetricProducers[0]->mWizard; + EXPECT_NE(newConditionWizard, oldConditionWizard); + EXPECT_EQ(newConditionWizard->getStrongCount(), 6); + oldMetricProducers.clear(); + // Only reference to the old wizard should be the one in the test. + EXPECT_EQ(oldConditionWizard->getStrongCount(), 1); +} + +TEST_F(ConfigUpdateTest, TestAlertPreserve) { + StatsdConfig config; + AtomMatcher whatMatcher = CreateScreenBrightnessChangedAtomMatcher(); + *config.add_atom_matcher() = whatMatcher; + + *config.add_count_metric() = createCountMetric("VALUE1", whatMatcher.id(), nullopt, {}); + + Alert alert = createAlert("Alert1", config.count_metric(0).id(), 1, 1); + *config.add_alert() = alert; + EXPECT_TRUE(initConfig(config)); + + UpdateStatus updateStatus = UPDATE_UNKNOWN; + EXPECT_TRUE(determineAlertUpdateStatus(alert, oldAlertTrackerMap, oldAnomalyTrackers, + /*replacedMetrics*/ {}, updateStatus)); + EXPECT_EQ(updateStatus, UPDATE_PRESERVE); +} + +TEST_F(ConfigUpdateTest, TestAlertMetricChanged) { + StatsdConfig config; + AtomMatcher whatMatcher = CreateScreenBrightnessChangedAtomMatcher(); + *config.add_atom_matcher() = whatMatcher; + + CountMetric metric = createCountMetric("VALUE1", whatMatcher.id(), nullopt, {}); + *config.add_count_metric() = metric; + + Alert alert = createAlert("Alert1", config.count_metric(0).id(), 1, 1); + *config.add_alert() = alert; + EXPECT_TRUE(initConfig(config)); + + UpdateStatus updateStatus = UPDATE_UNKNOWN; + EXPECT_TRUE(determineAlertUpdateStatus(alert, oldAlertTrackerMap, oldAnomalyTrackers, + /*replacedMetrics*/ {metric.id()}, updateStatus)); + EXPECT_EQ(updateStatus, UPDATE_REPLACE); +} + +TEST_F(ConfigUpdateTest, TestAlertDefinitionChanged) { + StatsdConfig config; + AtomMatcher whatMatcher = CreateScreenBrightnessChangedAtomMatcher(); + *config.add_atom_matcher() = whatMatcher; + + *config.add_count_metric() = createCountMetric("VALUE1", whatMatcher.id(), nullopt, {}); + + Alert alert = createAlert("Alert1", config.count_metric(0).id(), 1, 1); + *config.add_alert() = alert; + EXPECT_TRUE(initConfig(config)); + + alert.set_num_buckets(2); + + UpdateStatus updateStatus = UPDATE_UNKNOWN; + EXPECT_TRUE(determineAlertUpdateStatus(alert, oldAlertTrackerMap, oldAnomalyTrackers, + /*replacedMetrics*/ {}, updateStatus)); + EXPECT_EQ(updateStatus, UPDATE_REPLACE); +} + +TEST_F(ConfigUpdateTest, TestUpdateAlerts) { + StatsdConfig config; + // Add atom matchers/predicates/metrics. These are mostly needed for initStatsdConfig + *config.add_atom_matcher() = CreateScreenTurnedOnAtomMatcher(); + *config.add_atom_matcher() = CreateScreenTurnedOffAtomMatcher(); + *config.add_predicate() = CreateScreenIsOnPredicate(); + + CountMetric countMetric = createCountMetric("COUNT1", config.atom_matcher(0).id(), nullopt, {}); + int64_t countMetricId = countMetric.id(); + *config.add_count_metric() = countMetric; + + DurationMetric durationMetric = + createDurationMetric("DURATION1", config.predicate(0).id(), nullopt, {}); + int64_t durationMetricId = durationMetric.id(); + *config.add_duration_metric() = durationMetric; + + // Add alerts. + // Preserved. + Alert alert1 = createAlert("Alert1", durationMetricId, /*buckets*/ 1, /*triggerSum*/ 5000); + int64_t alert1Id = alert1.id(); + *config.add_alert() = alert1; + + // Replaced. + Alert alert2 = createAlert("Alert2", countMetricId, /*buckets*/ 1, /*triggerSum*/ 2); + int64_t alert2Id = alert2.id(); + *config.add_alert() = alert2; + + // Replaced. + Alert alert3 = createAlert("Alert3", durationMetricId, /*buckets*/ 3, /*triggerSum*/ 5000); + int64_t alert3Id = alert3.id(); + *config.add_alert() = alert3; + + // Add Subscriptions. + Subscription subscription1 = createSubscription("S1", Subscription::ALERT, alert1Id); + *config.add_subscription() = subscription1; + Subscription subscription2 = createSubscription("S2", Subscription::ALERT, alert1Id); + *config.add_subscription() = subscription2; + Subscription subscription3 = createSubscription("S3", Subscription::ALERT, alert2Id); + *config.add_subscription() = subscription3; + + EXPECT_TRUE(initConfig(config)); + + // Add a duration tracker to the duration metric to ensure durationTrackers are updated + // with the proper anomalyTrackers. + unique_ptr event = CreateScreenStateChangedEvent( + timeBaseNs + 1, android::view::DisplayStateEnum::DISPLAY_STATE_ON); + oldMetricProducers[1]->onMatchedLogEvent(0, *event.get()); + + // Change the count metric. Causes alert2 to be replaced. + config.mutable_count_metric(0)->set_bucket(ONE_DAY); + // Change num buckets on alert3, causing replacement. + alert3.set_num_buckets(5); + + // New alert. + Alert alert4 = createAlert("Alert4", durationMetricId, /*buckets*/ 3, /*triggerSum*/ 10000); + int64_t alert4Id = alert4.id(); + + // Move subscription2 to be on alert2 and make a new subscription. + subscription2.set_rule_id(alert2Id); + Subscription subscription4 = createSubscription("S4", Subscription::ALERT, alert2Id); + + // Create the new config. Modify the old one to avoid adding the matchers/predicates. + // Add alerts in different order so the map is changed. + config.clear_alert(); + *config.add_alert() = alert4; + const int alert4Index = 0; + *config.add_alert() = alert3; + const int alert3Index = 1; + *config.add_alert() = alert1; + const int alert1Index = 2; + *config.add_alert() = alert2; + const int alert2Index = 3; + + // Subscription3 is removed. + config.clear_subscription(); + *config.add_subscription() = subscription4; + *config.add_subscription() = subscription2; + *config.add_subscription() = subscription1; + + // Output data structures from update metrics. Don't care about the outputs besides + // replacedMetrics, but need to do this so that the metrics clear their anomaly trackers. + unordered_map newMetricProducerMap; + vector> newMetricProducers; + unordered_map> conditionToMetricMap; + unordered_map> trackerToMetricMap; + set noReportMetricIds; + unordered_map> activationAtomTrackerToMetricMap; + unordered_map> deactivationAtomTrackerToMetricMap; + vector metricsWithActivation; + set replacedMetrics; + int64_t currentTimeNs = 12345; + EXPECT_TRUE(updateMetrics( + key, config, /*timeBaseNs=*/123, currentTimeNs, new StatsPullerManager(), + oldAtomMatchingTrackerMap, oldAtomMatchingTrackerMap, /*replacedMatchers*/ {}, + oldAtomMatchingTrackers, oldConditionTrackerMap, /*replacedConditions=*/{}, + oldConditionTrackers, {ConditionState::kUnknown}, /*stateAtomIdMap*/ {}, + /*allStateGroupMaps=*/{}, + /*replacedStates=*/{}, oldMetricProducerMap, oldMetricProducers, newMetricProducerMap, + newMetricProducers, conditionToMetricMap, trackerToMetricMap, noReportMetricIds, + activationAtomTrackerToMetricMap, deactivationAtomTrackerToMetricMap, + metricsWithActivation, replacedMetrics)); + + EXPECT_EQ(replacedMetrics, set({countMetricId})); + + unordered_map newAlertTrackerMap; + vector> newAnomalyTrackers; + EXPECT_TRUE(updateAlerts(config, currentTimeNs, newMetricProducerMap, replacedMetrics, + oldAlertTrackerMap, oldAnomalyTrackers, anomalyAlarmMonitor, + newMetricProducers, newAlertTrackerMap, newAnomalyTrackers)); + + unordered_map expectedAlertMap = { + {alert1Id, alert1Index}, + {alert2Id, alert2Index}, + {alert3Id, alert3Index}, + {alert4Id, alert4Index}, + }; + EXPECT_THAT(newAlertTrackerMap, ContainerEq(expectedAlertMap)); + + // Make sure preserved alerts are the same. + ASSERT_EQ(newAnomalyTrackers.size(), 4); + EXPECT_EQ(oldAnomalyTrackers[oldAlertTrackerMap.at(alert1Id)], + newAnomalyTrackers[newAlertTrackerMap.at(alert1Id)]); + + // Make sure replaced alerts are different. + EXPECT_NE(oldAnomalyTrackers[oldAlertTrackerMap.at(alert2Id)], + newAnomalyTrackers[newAlertTrackerMap.at(alert2Id)]); + EXPECT_NE(oldAnomalyTrackers[oldAlertTrackerMap.at(alert3Id)], + newAnomalyTrackers[newAlertTrackerMap.at(alert3Id)]); + + // Verify the alerts have the correct anomaly trackers. + ASSERT_EQ(newMetricProducers.size(), 2); + EXPECT_THAT(newMetricProducers[0]->mAnomalyTrackers, + UnorderedElementsAre(newAnomalyTrackers[alert2Index])); + // For durationMetric, make sure the duration trackers get the updated anomalyTrackers. + DurationMetricProducer* durationProducer = + static_cast(newMetricProducers[1].get()); + EXPECT_THAT( + durationProducer->mAnomalyTrackers, + UnorderedElementsAre(newAnomalyTrackers[alert1Index], newAnomalyTrackers[alert3Index], + newAnomalyTrackers[alert4Index])); + ASSERT_EQ(durationProducer->mCurrentSlicedDurationTrackerMap.size(), 1); + for (const auto& durationTrackerIt : durationProducer->mCurrentSlicedDurationTrackerMap) { + EXPECT_EQ(durationTrackerIt.second->mAnomalyTrackers, durationProducer->mAnomalyTrackers); + } + + // Verify alerts have the correct subscriptions. Use subscription id as proxy for equivalency. + vector alert1Subscriptions; + for (const Subscription& subscription : newAnomalyTrackers[alert1Index]->mSubscriptions) { + alert1Subscriptions.push_back(subscription.id()); + } + EXPECT_THAT(alert1Subscriptions, UnorderedElementsAre(subscription1.id())); + vector alert2Subscriptions; + for (const Subscription& subscription : newAnomalyTrackers[alert2Index]->mSubscriptions) { + alert2Subscriptions.push_back(subscription.id()); + } + EXPECT_THAT(alert2Subscriptions, UnorderedElementsAre(subscription2.id(), subscription4.id())); + EXPECT_THAT(newAnomalyTrackers[alert3Index]->mSubscriptions, IsEmpty()); + EXPECT_THAT(newAnomalyTrackers[alert4Index]->mSubscriptions, IsEmpty()); +} + +TEST_F(ConfigUpdateTest, TestUpdateAlarms) { + StatsdConfig config; + // Add alarms. + Alarm alarm1 = createAlarm("Alarm1", /*offset*/ 1 * MS_PER_SEC, /*period*/ 50 * MS_PER_SEC); + int64_t alarm1Id = alarm1.id(); + *config.add_alarm() = alarm1; + + Alarm alarm2 = createAlarm("Alarm2", /*offset*/ 1 * MS_PER_SEC, /*period*/ 2000 * MS_PER_SEC); + int64_t alarm2Id = alarm2.id(); + *config.add_alarm() = alarm2; + + Alarm alarm3 = createAlarm("Alarm3", /*offset*/ 10 * MS_PER_SEC, /*period*/ 5000 * MS_PER_SEC); + int64_t alarm3Id = alarm3.id(); + *config.add_alarm() = alarm3; + + // Add Subscriptions. + Subscription subscription1 = createSubscription("S1", Subscription::ALARM, alarm1Id); + *config.add_subscription() = subscription1; + Subscription subscription2 = createSubscription("S2", Subscription::ALARM, alarm1Id); + *config.add_subscription() = subscription2; + Subscription subscription3 = createSubscription("S3", Subscription::ALARM, alarm2Id); + *config.add_subscription() = subscription3; + + EXPECT_TRUE(initConfig(config)); + + ASSERT_EQ(oldAlarmTrackers.size(), 3); + // Config is created at statsd start time, so just add the offsets. + EXPECT_EQ(oldAlarmTrackers[0]->getAlarmTimestampSec(), timeBaseNs / NS_PER_SEC + 1); + EXPECT_EQ(oldAlarmTrackers[1]->getAlarmTimestampSec(), timeBaseNs / NS_PER_SEC + 1); + EXPECT_EQ(oldAlarmTrackers[2]->getAlarmTimestampSec(), timeBaseNs / NS_PER_SEC + 10); + + // Change alarm2/alarm3. + config.mutable_alarm(1)->set_offset_millis(5 * MS_PER_SEC); + config.mutable_alarm(2)->set_period_millis(10000 * MS_PER_SEC); + + // Move subscription2 to be on alarm2 and make a new subscription. + config.mutable_subscription(1)->set_rule_id(alarm2Id); + Subscription subscription4 = createSubscription("S4", Subscription::ALARM, alarm1Id); + *config.add_subscription() = subscription4; + + // Update time is 2 seconds after the base time. + int64_t currentTimeNs = timeBaseNs + 2 * NS_PER_SEC; + vector> newAlarmTrackers; + EXPECT_TRUE(initAlarms(config, key, periodicAlarmMonitor, timeBaseNs, currentTimeNs, + newAlarmTrackers)); + + ASSERT_EQ(newAlarmTrackers.size(), 3); + // Config is updated 2 seconds after statsd start + // The offset has passed for alarm1, but not for alarms 2/3. + EXPECT_EQ(newAlarmTrackers[0]->getAlarmTimestampSec(), timeBaseNs / NS_PER_SEC + 1 + 50); + EXPECT_EQ(newAlarmTrackers[1]->getAlarmTimestampSec(), timeBaseNs / NS_PER_SEC + 5); + EXPECT_EQ(newAlarmTrackers[2]->getAlarmTimestampSec(), timeBaseNs / NS_PER_SEC + 10); + + // Verify alarms have the correct subscriptions. Use subscription id as proxy for equivalency. + vector alarm1Subscriptions; + for (const Subscription& subscription : newAlarmTrackers[0]->mSubscriptions) { + alarm1Subscriptions.push_back(subscription.id()); + } + EXPECT_THAT(alarm1Subscriptions, UnorderedElementsAre(subscription1.id(), subscription4.id())); + vector alarm2Subscriptions; + for (const Subscription& subscription : newAlarmTrackers[1]->mSubscriptions) { + alarm2Subscriptions.push_back(subscription.id()); + } + EXPECT_THAT(alarm2Subscriptions, UnorderedElementsAre(subscription2.id(), subscription3.id())); + EXPECT_THAT(newAlarmTrackers[2]->mSubscriptions, IsEmpty()); + + // Verify the alarm monitor is updated accordingly once the old alarms are removed. + // Alarm2 fires the earliest. + oldAlarmTrackers.clear(); + EXPECT_EQ(periodicAlarmMonitor->getRegisteredAlarmTimeSec(), timeBaseNs / NS_PER_SEC + 5); + + // Do another update 60 seconds after config creation time, after the offsets of each alarm. + currentTimeNs = timeBaseNs + 60 * NS_PER_SEC; + newAlarmTrackers.clear(); + EXPECT_TRUE(initAlarms(config, key, periodicAlarmMonitor, timeBaseNs, currentTimeNs, + newAlarmTrackers)); + + ASSERT_EQ(newAlarmTrackers.size(), 3); + // Config is updated one minute after statsd start. + // Two periods have passed for alarm 1, one has passed for alarms2/3. + EXPECT_EQ(newAlarmTrackers[0]->getAlarmTimestampSec(), timeBaseNs / NS_PER_SEC + 1 + 2 * 50); + EXPECT_EQ(newAlarmTrackers[1]->getAlarmTimestampSec(), timeBaseNs / NS_PER_SEC + 5 + 2000); + EXPECT_EQ(newAlarmTrackers[2]->getAlarmTimestampSec(), timeBaseNs / NS_PER_SEC + 10 + 10000); +} + +} // namespace statsd +} // namespace os +} // namespace android + +#else +GTEST_LOG_(INFO) << "This test does nothing.\n"; +#endif diff --git a/statsd/tests/metrics/parsing_utils/metrics_manager_util_test.cpp b/statsd/tests/metrics/parsing_utils/metrics_manager_util_test.cpp new file mode 100644 index 00000000..16a5f366 --- /dev/null +++ b/statsd/tests/metrics/parsing_utils/metrics_manager_util_test.cpp @@ -0,0 +1,938 @@ +// Copyright (C) 2020 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 "src/metrics/parsing_utils/metrics_manager_util.h" + +#include +#include +#include +#include + +#include +#include +#include + +#include "src/statsd_config.pb.h" +#include "src/condition/ConditionTracker.h" +#include "src/matchers/AtomMatchingTracker.h" +#include "src/metrics/CountMetricProducer.h" +#include "src/metrics/DurationMetricProducer.h" +#include "src/metrics/GaugeMetricProducer.h" +#include "src/metrics/MetricProducer.h" +#include "src/metrics/ValueMetricProducer.h" +#include "src/state/StateManager.h" +#include "tests/metrics/metrics_test_helper.h" +#include "tests/statsd_test_util.h" + +using namespace testing; +using android::sp; +using android::os::statsd::Predicate; +using std::map; +using std::set; +using std::unordered_map; +using std::vector; + +#ifdef __ANDROID__ + +namespace android { +namespace os { +namespace statsd { + +namespace { +const ConfigKey kConfigKey(0, 12345); +const long kAlertId = 3; + +const long timeBaseSec = 1000; + +StatsdConfig buildGoodConfig() { + StatsdConfig config; + config.set_id(12345); + + AtomMatcher* eventMatcher = config.add_atom_matcher(); + eventMatcher->set_id(StringToId("SCREEN_IS_ON")); + + SimpleAtomMatcher* simpleAtomMatcher = eventMatcher->mutable_simple_atom_matcher(); + simpleAtomMatcher->set_atom_id(2 /*SCREEN_STATE_CHANGE*/); + simpleAtomMatcher->add_field_value_matcher()->set_field( + 1 /*SCREEN_STATE_CHANGE__DISPLAY_STATE*/); + simpleAtomMatcher->mutable_field_value_matcher(0)->set_eq_int( + 2 /*SCREEN_STATE_CHANGE__DISPLAY_STATE__STATE_ON*/); + + eventMatcher = config.add_atom_matcher(); + eventMatcher->set_id(StringToId("SCREEN_IS_OFF")); + + simpleAtomMatcher = eventMatcher->mutable_simple_atom_matcher(); + simpleAtomMatcher->set_atom_id(2 /*SCREEN_STATE_CHANGE*/); + simpleAtomMatcher->add_field_value_matcher()->set_field( + 1 /*SCREEN_STATE_CHANGE__DISPLAY_STATE*/); + simpleAtomMatcher->mutable_field_value_matcher(0)->set_eq_int( + 1 /*SCREEN_STATE_CHANGE__DISPLAY_STATE__STATE_OFF*/); + + eventMatcher = config.add_atom_matcher(); + eventMatcher->set_id(StringToId("SCREEN_ON_OR_OFF")); + + AtomMatcher_Combination* combination = eventMatcher->mutable_combination(); + combination->set_operation(LogicalOperation::OR); + combination->add_matcher(StringToId("SCREEN_IS_ON")); + combination->add_matcher(StringToId("SCREEN_IS_OFF")); + + CountMetric* metric = config.add_count_metric(); + metric->set_id(3); + metric->set_what(StringToId("SCREEN_IS_ON")); + metric->set_bucket(ONE_MINUTE); + metric->mutable_dimensions_in_what()->set_field(2 /*SCREEN_STATE_CHANGE*/); + metric->mutable_dimensions_in_what()->add_child()->set_field(1); + + config.add_no_report_metric(3); + + auto alert = config.add_alert(); + alert->set_id(kAlertId); + alert->set_metric_id(3); + alert->set_num_buckets(10); + alert->set_refractory_period_secs(100); + alert->set_trigger_if_sum_gt(100); + return config; +} + +StatsdConfig buildCircleMatchers() { + StatsdConfig config; + config.set_id(12345); + + AtomMatcher* eventMatcher = config.add_atom_matcher(); + eventMatcher->set_id(StringToId("SCREEN_IS_ON")); + + SimpleAtomMatcher* simpleAtomMatcher = eventMatcher->mutable_simple_atom_matcher(); + simpleAtomMatcher->set_atom_id(2 /*SCREEN_STATE_CHANGE*/); + simpleAtomMatcher->add_field_value_matcher()->set_field( + 1 /*SCREEN_STATE_CHANGE__DISPLAY_STATE*/); + simpleAtomMatcher->mutable_field_value_matcher(0)->set_eq_int( + 2 /*SCREEN_STATE_CHANGE__DISPLAY_STATE__STATE_ON*/); + + eventMatcher = config.add_atom_matcher(); + eventMatcher->set_id(StringToId("SCREEN_ON_OR_OFF")); + + AtomMatcher_Combination* combination = eventMatcher->mutable_combination(); + combination->set_operation(LogicalOperation::OR); + combination->add_matcher(StringToId("SCREEN_IS_ON")); + // Circle dependency + combination->add_matcher(StringToId("SCREEN_ON_OR_OFF")); + + return config; +} + +StatsdConfig buildAlertWithUnknownMetric() { + StatsdConfig config; + config.set_id(12345); + + AtomMatcher* eventMatcher = config.add_atom_matcher(); + eventMatcher->set_id(StringToId("SCREEN_IS_ON")); + + CountMetric* metric = config.add_count_metric(); + metric->set_id(3); + metric->set_what(StringToId("SCREEN_IS_ON")); + metric->set_bucket(ONE_MINUTE); + metric->mutable_dimensions_in_what()->set_field(2 /*SCREEN_STATE_CHANGE*/); + metric->mutable_dimensions_in_what()->add_child()->set_field(1); + + auto alert = config.add_alert(); + alert->set_id(3); + alert->set_metric_id(2); + alert->set_num_buckets(10); + alert->set_refractory_period_secs(100); + alert->set_trigger_if_sum_gt(100); + return config; +} + +StatsdConfig buildMissingMatchers() { + StatsdConfig config; + config.set_id(12345); + + AtomMatcher* eventMatcher = config.add_atom_matcher(); + eventMatcher->set_id(StringToId("SCREEN_IS_ON")); + + SimpleAtomMatcher* simpleAtomMatcher = eventMatcher->mutable_simple_atom_matcher(); + simpleAtomMatcher->set_atom_id(2 /*SCREEN_STATE_CHANGE*/); + simpleAtomMatcher->add_field_value_matcher()->set_field( + 1 /*SCREEN_STATE_CHANGE__DISPLAY_STATE*/); + simpleAtomMatcher->mutable_field_value_matcher(0)->set_eq_int( + 2 /*SCREEN_STATE_CHANGE__DISPLAY_STATE__STATE_ON*/); + + eventMatcher = config.add_atom_matcher(); + eventMatcher->set_id(StringToId("SCREEN_ON_OR_OFF")); + + AtomMatcher_Combination* combination = eventMatcher->mutable_combination(); + combination->set_operation(LogicalOperation::OR); + combination->add_matcher(StringToId("SCREEN_IS_ON")); + // undefined matcher + combination->add_matcher(StringToId("ABC")); + + return config; +} + +StatsdConfig buildMissingPredicate() { + StatsdConfig config; + config.set_id(12345); + + CountMetric* metric = config.add_count_metric(); + metric->set_id(3); + metric->set_what(StringToId("SCREEN_EVENT")); + metric->set_bucket(ONE_MINUTE); + metric->set_condition(StringToId("SOME_CONDITION")); + + AtomMatcher* eventMatcher = config.add_atom_matcher(); + eventMatcher->set_id(StringToId("SCREEN_EVENT")); + + SimpleAtomMatcher* simpleAtomMatcher = eventMatcher->mutable_simple_atom_matcher(); + simpleAtomMatcher->set_atom_id(2); + + return config; +} + +StatsdConfig buildDimensionMetricsWithMultiTags() { + StatsdConfig config; + config.set_id(12345); + + AtomMatcher* eventMatcher = config.add_atom_matcher(); + eventMatcher->set_id(StringToId("BATTERY_VERY_LOW")); + SimpleAtomMatcher* simpleAtomMatcher = eventMatcher->mutable_simple_atom_matcher(); + simpleAtomMatcher->set_atom_id(2); + + eventMatcher = config.add_atom_matcher(); + eventMatcher->set_id(StringToId("BATTERY_VERY_VERY_LOW")); + simpleAtomMatcher = eventMatcher->mutable_simple_atom_matcher(); + simpleAtomMatcher->set_atom_id(3); + + eventMatcher = config.add_atom_matcher(); + eventMatcher->set_id(StringToId("BATTERY_LOW")); + + AtomMatcher_Combination* combination = eventMatcher->mutable_combination(); + combination->set_operation(LogicalOperation::OR); + combination->add_matcher(StringToId("BATTERY_VERY_LOW")); + combination->add_matcher(StringToId("BATTERY_VERY_VERY_LOW")); + + // Count process state changes, slice by uid, while SCREEN_IS_OFF + CountMetric* metric = config.add_count_metric(); + metric->set_id(3); + metric->set_what(StringToId("BATTERY_LOW")); + metric->set_bucket(ONE_MINUTE); + // This case is interesting. We want to dimension across two atoms. + metric->mutable_dimensions_in_what()->add_child()->set_field(1); + + auto alert = config.add_alert(); + alert->set_id(kAlertId); + alert->set_metric_id(3); + alert->set_num_buckets(10); + alert->set_refractory_period_secs(100); + alert->set_trigger_if_sum_gt(100); + return config; +} + +StatsdConfig buildCirclePredicates() { + StatsdConfig config; + config.set_id(12345); + + AtomMatcher* eventMatcher = config.add_atom_matcher(); + eventMatcher->set_id(StringToId("SCREEN_IS_ON")); + + SimpleAtomMatcher* simpleAtomMatcher = eventMatcher->mutable_simple_atom_matcher(); + simpleAtomMatcher->set_atom_id(2 /*SCREEN_STATE_CHANGE*/); + simpleAtomMatcher->add_field_value_matcher()->set_field( + 1 /*SCREEN_STATE_CHANGE__DISPLAY_STATE*/); + simpleAtomMatcher->mutable_field_value_matcher(0)->set_eq_int( + 2 /*SCREEN_STATE_CHANGE__DISPLAY_STATE__STATE_ON*/); + + eventMatcher = config.add_atom_matcher(); + eventMatcher->set_id(StringToId("SCREEN_IS_OFF")); + + simpleAtomMatcher = eventMatcher->mutable_simple_atom_matcher(); + simpleAtomMatcher->set_atom_id(2 /*SCREEN_STATE_CHANGE*/); + simpleAtomMatcher->add_field_value_matcher()->set_field( + 1 /*SCREEN_STATE_CHANGE__DISPLAY_STATE*/); + simpleAtomMatcher->mutable_field_value_matcher(0)->set_eq_int( + 1 /*SCREEN_STATE_CHANGE__DISPLAY_STATE__STATE_OFF*/); + + auto condition = config.add_predicate(); + condition->set_id(StringToId("SCREEN_IS_ON")); + SimplePredicate* simplePredicate = condition->mutable_simple_predicate(); + simplePredicate->set_start(StringToId("SCREEN_IS_ON")); + simplePredicate->set_stop(StringToId("SCREEN_IS_OFF")); + + condition = config.add_predicate(); + condition->set_id(StringToId("SCREEN_IS_EITHER_ON_OFF")); + + Predicate_Combination* combination = condition->mutable_combination(); + combination->set_operation(LogicalOperation::OR); + combination->add_predicate(StringToId("SCREEN_IS_ON")); + combination->add_predicate(StringToId("SCREEN_IS_EITHER_ON_OFF")); + + return config; +} + +StatsdConfig buildConfigWithDifferentPredicates() { + StatsdConfig config; + config.set_id(12345); + + auto pulledAtomMatcher = + CreateSimpleAtomMatcher("SUBSYSTEM_SLEEP", util::SUBSYSTEM_SLEEP_STATE); + *config.add_atom_matcher() = pulledAtomMatcher; + auto screenOnAtomMatcher = CreateScreenTurnedOnAtomMatcher(); + *config.add_atom_matcher() = screenOnAtomMatcher; + auto screenOffAtomMatcher = CreateScreenTurnedOffAtomMatcher(); + *config.add_atom_matcher() = screenOffAtomMatcher; + auto batteryNoneAtomMatcher = CreateBatteryStateNoneMatcher(); + *config.add_atom_matcher() = batteryNoneAtomMatcher; + auto batteryUsbAtomMatcher = CreateBatteryStateUsbMatcher(); + *config.add_atom_matcher() = batteryUsbAtomMatcher; + + // Simple condition with InitialValue set to default (unknown). + auto screenOnUnknownPredicate = CreateScreenIsOnPredicate(); + *config.add_predicate() = screenOnUnknownPredicate; + + // Simple condition with InitialValue set to false. + auto screenOnFalsePredicate = config.add_predicate(); + screenOnFalsePredicate->set_id(StringToId("ScreenIsOnInitialFalse")); + SimplePredicate* simpleScreenOnFalsePredicate = + screenOnFalsePredicate->mutable_simple_predicate(); + simpleScreenOnFalsePredicate->set_start(screenOnAtomMatcher.id()); + simpleScreenOnFalsePredicate->set_stop(screenOffAtomMatcher.id()); + simpleScreenOnFalsePredicate->set_initial_value(SimplePredicate_InitialValue_FALSE); + + // Simple condition with InitialValue set to false. + auto onBatteryFalsePredicate = config.add_predicate(); + onBatteryFalsePredicate->set_id(StringToId("OnBatteryInitialFalse")); + SimplePredicate* simpleOnBatteryFalsePredicate = + onBatteryFalsePredicate->mutable_simple_predicate(); + simpleOnBatteryFalsePredicate->set_start(batteryNoneAtomMatcher.id()); + simpleOnBatteryFalsePredicate->set_stop(batteryUsbAtomMatcher.id()); + simpleOnBatteryFalsePredicate->set_initial_value(SimplePredicate_InitialValue_FALSE); + + // Combination condition with both simple condition InitialValues set to false. + auto screenOnFalseOnBatteryFalsePredicate = config.add_predicate(); + screenOnFalseOnBatteryFalsePredicate->set_id(StringToId("ScreenOnFalseOnBatteryFalse")); + screenOnFalseOnBatteryFalsePredicate->mutable_combination()->set_operation( + LogicalOperation::AND); + addPredicateToPredicateCombination(*screenOnFalsePredicate, + screenOnFalseOnBatteryFalsePredicate); + addPredicateToPredicateCombination(*onBatteryFalsePredicate, + screenOnFalseOnBatteryFalsePredicate); + + // Combination condition with one simple condition InitialValue set to unknown and one set to + // false. + auto screenOnUnknownOnBatteryFalsePredicate = config.add_predicate(); + screenOnUnknownOnBatteryFalsePredicate->set_id(StringToId("ScreenOnUnknowneOnBatteryFalse")); + screenOnUnknownOnBatteryFalsePredicate->mutable_combination()->set_operation( + LogicalOperation::AND); + addPredicateToPredicateCombination(screenOnUnknownPredicate, + screenOnUnknownOnBatteryFalsePredicate); + addPredicateToPredicateCombination(*onBatteryFalsePredicate, + screenOnUnknownOnBatteryFalsePredicate); + + // Simple condition metric with initial value false. + ValueMetric* metric1 = config.add_value_metric(); + metric1->set_id(StringToId("ValueSubsystemSleepWhileScreenOnInitialFalse")); + metric1->set_what(pulledAtomMatcher.id()); + *metric1->mutable_value_field() = + CreateDimensions(util::SUBSYSTEM_SLEEP_STATE, {4 /* time sleeping field */}); + metric1->set_bucket(FIVE_MINUTES); + metric1->set_condition(screenOnFalsePredicate->id()); + + // Simple condition metric with initial value unknown. + ValueMetric* metric2 = config.add_value_metric(); + metric2->set_id(StringToId("ValueSubsystemSleepWhileScreenOnInitialUnknown")); + metric2->set_what(pulledAtomMatcher.id()); + *metric2->mutable_value_field() = + CreateDimensions(util::SUBSYSTEM_SLEEP_STATE, {4 /* time sleeping field */}); + metric2->set_bucket(FIVE_MINUTES); + metric2->set_condition(screenOnUnknownPredicate.id()); + + // Combination condition metric with initial values false and false. + ValueMetric* metric3 = config.add_value_metric(); + metric3->set_id(StringToId("ValueSubsystemSleepWhileScreenOnFalseDeviceUnpluggedFalse")); + metric3->set_what(pulledAtomMatcher.id()); + *metric3->mutable_value_field() = + CreateDimensions(util::SUBSYSTEM_SLEEP_STATE, {4 /* time sleeping field */}); + metric3->set_bucket(FIVE_MINUTES); + metric3->set_condition(screenOnFalseOnBatteryFalsePredicate->id()); + + // Combination condition metric with initial values unknown and false. + ValueMetric* metric4 = config.add_value_metric(); + metric4->set_id(StringToId("ValueSubsystemSleepWhileScreenOnUnknownDeviceUnpluggedFalse")); + metric4->set_what(pulledAtomMatcher.id()); + *metric4->mutable_value_field() = + CreateDimensions(util::SUBSYSTEM_SLEEP_STATE, {4 /* time sleeping field */}); + metric4->set_bucket(FIVE_MINUTES); + metric4->set_condition(screenOnUnknownOnBatteryFalsePredicate->id()); + + return config; +} +} // anonymous namespace + +TEST(MetricsManagerTest, TestInitialConditions) { + sp uidMap = new UidMap(); + sp pullerManager = new StatsPullerManager(); + sp anomalyAlarmMonitor; + sp periodicAlarmMonitor; + StatsdConfig config = buildConfigWithDifferentPredicates(); + set allTagIds; + vector> allAtomMatchingTrackers; + unordered_map atomMatchingTrackerMap; + vector> allConditionTrackers; + unordered_map conditionTrackerMap; + vector> allMetricProducers; + unordered_map metricProducerMap; + std::vector> allAnomalyTrackers; + std::vector> allAlarmTrackers; + unordered_map> conditionToMetricMap; + unordered_map> trackerToMetricMap; + unordered_map> trackerToConditionMap; + unordered_map> activationAtomTrackerToMetricMap; + unordered_map> deactivationAtomTrackerToMetricMap; + unordered_map alertTrackerMap; + vector metricsWithActivation; + map stateProtoHashes; + std::set noReportMetricIds; + + EXPECT_TRUE(initStatsdConfig( + kConfigKey, config, uidMap, pullerManager, anomalyAlarmMonitor, periodicAlarmMonitor, + timeBaseSec, timeBaseSec, allTagIds, allAtomMatchingTrackers, atomMatchingTrackerMap, + allConditionTrackers, conditionTrackerMap, allMetricProducers, metricProducerMap, + allAnomalyTrackers, allAlarmTrackers, conditionToMetricMap, trackerToMetricMap, + trackerToConditionMap, activationAtomTrackerToMetricMap, + deactivationAtomTrackerToMetricMap, alertTrackerMap, metricsWithActivation, + stateProtoHashes, noReportMetricIds)); + ASSERT_EQ(4u, allMetricProducers.size()); + ASSERT_EQ(5u, allConditionTrackers.size()); + + ConditionKey queryKey; + vector conditionCache(5, ConditionState::kNotEvaluated); + + allConditionTrackers[3]->isConditionMet(queryKey, allConditionTrackers, false, conditionCache); + allConditionTrackers[4]->isConditionMet(queryKey, allConditionTrackers, false, conditionCache); + EXPECT_EQ(ConditionState::kUnknown, conditionCache[0]); + EXPECT_EQ(ConditionState::kFalse, conditionCache[1]); + EXPECT_EQ(ConditionState::kFalse, conditionCache[2]); + EXPECT_EQ(ConditionState::kFalse, conditionCache[3]); + EXPECT_EQ(ConditionState::kUnknown, conditionCache[4]); + + EXPECT_EQ(ConditionState::kFalse, allMetricProducers[0]->mCondition); + EXPECT_EQ(ConditionState::kUnknown, allMetricProducers[1]->mCondition); + EXPECT_EQ(ConditionState::kFalse, allMetricProducers[2]->mCondition); + EXPECT_EQ(ConditionState::kUnknown, allMetricProducers[3]->mCondition); +} + +TEST(MetricsManagerTest, TestGoodConfig) { + sp uidMap = new UidMap(); + sp pullerManager = new StatsPullerManager(); + sp anomalyAlarmMonitor; + sp periodicAlarmMonitor; + StatsdConfig config = buildGoodConfig(); + set allTagIds; + vector> allAtomMatchingTrackers; + unordered_map atomMatchingTrackerMap; + vector> allConditionTrackers; + unordered_map conditionTrackerMap; + vector> allMetricProducers; + unordered_map metricProducerMap; + std::vector> allAnomalyTrackers; + std::vector> allAlarmTrackers; + unordered_map> conditionToMetricMap; + unordered_map> trackerToMetricMap; + unordered_map> trackerToConditionMap; + unordered_map> activationAtomTrackerToMetricMap; + unordered_map> deactivationAtomTrackerToMetricMap; + unordered_map alertTrackerMap; + vector metricsWithActivation; + map stateProtoHashes; + std::set noReportMetricIds; + + EXPECT_TRUE(initStatsdConfig( + kConfigKey, config, uidMap, pullerManager, anomalyAlarmMonitor, periodicAlarmMonitor, + timeBaseSec, timeBaseSec, allTagIds, allAtomMatchingTrackers, atomMatchingTrackerMap, + allConditionTrackers, conditionTrackerMap, allMetricProducers, metricProducerMap, + allAnomalyTrackers, allAlarmTrackers, conditionToMetricMap, trackerToMetricMap, + trackerToConditionMap, activationAtomTrackerToMetricMap, + deactivationAtomTrackerToMetricMap, alertTrackerMap, metricsWithActivation, + stateProtoHashes, noReportMetricIds)); + ASSERT_EQ(1u, allMetricProducers.size()); + EXPECT_THAT(metricProducerMap, UnorderedElementsAre(Pair(config.count_metric(0).id(), 0))); + ASSERT_EQ(1u, allAnomalyTrackers.size()); + ASSERT_EQ(1u, noReportMetricIds.size()); + ASSERT_EQ(1u, alertTrackerMap.size()); + EXPECT_NE(alertTrackerMap.find(kAlertId), alertTrackerMap.end()); + EXPECT_EQ(alertTrackerMap.find(kAlertId)->second, 0); +} + +TEST(MetricsManagerTest, TestDimensionMetricsWithMultiTags) { + sp uidMap = new UidMap(); + sp pullerManager = new StatsPullerManager(); + sp anomalyAlarmMonitor; + sp periodicAlarmMonitor; + StatsdConfig config = buildDimensionMetricsWithMultiTags(); + set allTagIds; + vector> allAtomMatchingTrackers; + unordered_map atomMatchingTrackerMap; + vector> allConditionTrackers; + unordered_map conditionTrackerMap; + vector> allMetricProducers; + unordered_map metricProducerMap; + std::vector> allAnomalyTrackers; + std::vector> allAlarmTrackers; + unordered_map> conditionToMetricMap; + unordered_map> trackerToMetricMap; + unordered_map> trackerToConditionMap; + unordered_map> activationAtomTrackerToMetricMap; + unordered_map> deactivationAtomTrackerToMetricMap; + unordered_map alertTrackerMap; + vector metricsWithActivation; + map stateProtoHashes; + std::set noReportMetricIds; + + EXPECT_FALSE(initStatsdConfig( + kConfigKey, config, uidMap, pullerManager, anomalyAlarmMonitor, periodicAlarmMonitor, + timeBaseSec, timeBaseSec, allTagIds, allAtomMatchingTrackers, atomMatchingTrackerMap, + allConditionTrackers, conditionTrackerMap, allMetricProducers, metricProducerMap, + allAnomalyTrackers, allAlarmTrackers, conditionToMetricMap, trackerToMetricMap, + trackerToConditionMap, activationAtomTrackerToMetricMap, + deactivationAtomTrackerToMetricMap, alertTrackerMap, metricsWithActivation, + stateProtoHashes, noReportMetricIds)); +} + +TEST(MetricsManagerTest, TestCircleLogMatcherDependency) { + sp uidMap = new UidMap(); + sp pullerManager = new StatsPullerManager(); + sp anomalyAlarmMonitor; + sp periodicAlarmMonitor; + StatsdConfig config = buildCircleMatchers(); + set allTagIds; + vector> allAtomMatchingTrackers; + unordered_map atomMatchingTrackerMap; + vector> allConditionTrackers; + unordered_map conditionTrackerMap; + vector> allMetricProducers; + unordered_map metricProducerMap; + std::vector> allAnomalyTrackers; + std::vector> allAlarmTrackers; + unordered_map> conditionToMetricMap; + unordered_map> trackerToMetricMap; + unordered_map> trackerToConditionMap; + unordered_map> activationAtomTrackerToMetricMap; + unordered_map> deactivationAtomTrackerToMetricMap; + unordered_map alertTrackerMap; + vector metricsWithActivation; + map stateProtoHashes; + std::set noReportMetricIds; + + EXPECT_FALSE(initStatsdConfig( + kConfigKey, config, uidMap, pullerManager, anomalyAlarmMonitor, periodicAlarmMonitor, + timeBaseSec, timeBaseSec, allTagIds, allAtomMatchingTrackers, atomMatchingTrackerMap, + allConditionTrackers, conditionTrackerMap, allMetricProducers, metricProducerMap, + allAnomalyTrackers, allAlarmTrackers, conditionToMetricMap, trackerToMetricMap, + trackerToConditionMap, activationAtomTrackerToMetricMap, + deactivationAtomTrackerToMetricMap, alertTrackerMap, metricsWithActivation, + stateProtoHashes, noReportMetricIds)); +} + +TEST(MetricsManagerTest, TestMissingMatchers) { + sp uidMap = new UidMap(); + sp pullerManager = new StatsPullerManager(); + sp anomalyAlarmMonitor; + sp periodicAlarmMonitor; + StatsdConfig config = buildMissingMatchers(); + set allTagIds; + vector> allAtomMatchingTrackers; + unordered_map atomMatchingTrackerMap; + vector> allConditionTrackers; + unordered_map conditionTrackerMap; + vector> allMetricProducers; + unordered_map metricProducerMap; + std::vector> allAnomalyTrackers; + std::vector> allAlarmTrackers; + unordered_map> conditionToMetricMap; + unordered_map> trackerToMetricMap; + unordered_map> trackerToConditionMap; + unordered_map> activationAtomTrackerToMetricMap; + unordered_map> deactivationAtomTrackerToMetricMap; + unordered_map alertTrackerMap; + vector metricsWithActivation; + map stateProtoHashes; + std::set noReportMetricIds; + EXPECT_FALSE(initStatsdConfig( + kConfigKey, config, uidMap, pullerManager, anomalyAlarmMonitor, periodicAlarmMonitor, + timeBaseSec, timeBaseSec, allTagIds, allAtomMatchingTrackers, atomMatchingTrackerMap, + allConditionTrackers, conditionTrackerMap, allMetricProducers, metricProducerMap, + allAnomalyTrackers, allAlarmTrackers, conditionToMetricMap, trackerToMetricMap, + trackerToConditionMap, activationAtomTrackerToMetricMap, + deactivationAtomTrackerToMetricMap, alertTrackerMap, metricsWithActivation, + stateProtoHashes, noReportMetricIds)); +} + +TEST(MetricsManagerTest, TestMissingPredicate) { + sp uidMap = new UidMap(); + sp pullerManager = new StatsPullerManager(); + sp anomalyAlarmMonitor; + sp periodicAlarmMonitor; + StatsdConfig config = buildMissingPredicate(); + set allTagIds; + vector> allAtomMatchingTrackers; + unordered_map atomMatchingTrackerMap; + vector> allConditionTrackers; + unordered_map conditionTrackerMap; + vector> allMetricProducers; + unordered_map metricProducerMap; + std::vector> allAnomalyTrackers; + std::vector> allAlarmTrackers; + unordered_map> conditionToMetricMap; + unordered_map> trackerToMetricMap; + unordered_map> trackerToConditionMap; + unordered_map> activationAtomTrackerToMetricMap; + unordered_map> deactivationAtomTrackerToMetricMap; + unordered_map alertTrackerMap; + vector metricsWithActivation; + map stateProtoHashes; + std::set noReportMetricIds; + EXPECT_FALSE(initStatsdConfig( + kConfigKey, config, uidMap, pullerManager, anomalyAlarmMonitor, periodicAlarmMonitor, + timeBaseSec, timeBaseSec, allTagIds, allAtomMatchingTrackers, atomMatchingTrackerMap, + allConditionTrackers, conditionTrackerMap, allMetricProducers, metricProducerMap, + allAnomalyTrackers, allAlarmTrackers, conditionToMetricMap, trackerToMetricMap, + trackerToConditionMap, activationAtomTrackerToMetricMap, + deactivationAtomTrackerToMetricMap, alertTrackerMap, metricsWithActivation, + stateProtoHashes, noReportMetricIds)); +} + +TEST(MetricsManagerTest, TestCirclePredicateDependency) { + sp uidMap = new UidMap(); + sp pullerManager = new StatsPullerManager(); + sp anomalyAlarmMonitor; + sp periodicAlarmMonitor; + StatsdConfig config = buildCirclePredicates(); + set allTagIds; + vector> allAtomMatchingTrackers; + unordered_map atomMatchingTrackerMap; + vector> allConditionTrackers; + unordered_map conditionTrackerMap; + vector> allMetricProducers; + unordered_map metricProducerMap; + std::vector> allAnomalyTrackers; + std::vector> allAlarmTrackers; + unordered_map> conditionToMetricMap; + unordered_map> trackerToMetricMap; + unordered_map> trackerToConditionMap; + unordered_map> activationAtomTrackerToMetricMap; + unordered_map> deactivationAtomTrackerToMetricMap; + unordered_map alertTrackerMap; + vector metricsWithActivation; + map stateProtoHashes; + std::set noReportMetricIds; + + EXPECT_FALSE(initStatsdConfig( + kConfigKey, config, uidMap, pullerManager, anomalyAlarmMonitor, periodicAlarmMonitor, + timeBaseSec, timeBaseSec, allTagIds, allAtomMatchingTrackers, atomMatchingTrackerMap, + allConditionTrackers, conditionTrackerMap, allMetricProducers, metricProducerMap, + allAnomalyTrackers, allAlarmTrackers, conditionToMetricMap, trackerToMetricMap, + trackerToConditionMap, activationAtomTrackerToMetricMap, + deactivationAtomTrackerToMetricMap, alertTrackerMap, metricsWithActivation, + stateProtoHashes, noReportMetricIds)); +} + +TEST(MetricsManagerTest, testAlertWithUnknownMetric) { + sp uidMap = new UidMap(); + sp pullerManager = new StatsPullerManager(); + sp anomalyAlarmMonitor; + sp periodicAlarmMonitor; + StatsdConfig config = buildAlertWithUnknownMetric(); + set allTagIds; + vector> allAtomMatchingTrackers; + unordered_map atomMatchingTrackerMap; + vector> allConditionTrackers; + unordered_map conditionTrackerMap; + vector> allMetricProducers; + unordered_map metricProducerMap; + std::vector> allAnomalyTrackers; + std::vector> allAlarmTrackers; + unordered_map> conditionToMetricMap; + unordered_map> trackerToMetricMap; + unordered_map> trackerToConditionMap; + unordered_map> activationAtomTrackerToMetricMap; + unordered_map> deactivationAtomTrackerToMetricMap; + unordered_map alertTrackerMap; + vector metricsWithActivation; + map stateProtoHashes; + std::set noReportMetricIds; + + EXPECT_FALSE(initStatsdConfig( + kConfigKey, config, uidMap, pullerManager, anomalyAlarmMonitor, periodicAlarmMonitor, + timeBaseSec, timeBaseSec, allTagIds, allAtomMatchingTrackers, atomMatchingTrackerMap, + allConditionTrackers, conditionTrackerMap, allMetricProducers, metricProducerMap, + allAnomalyTrackers, allAlarmTrackers, conditionToMetricMap, trackerToMetricMap, + trackerToConditionMap, activationAtomTrackerToMetricMap, + deactivationAtomTrackerToMetricMap, alertTrackerMap, metricsWithActivation, + stateProtoHashes, noReportMetricIds)); +} + +TEST(MetricsManagerTest, TestCreateAtomMatchingTrackerInvalidMatcher) { + sp uidMap = new UidMap(); + AtomMatcher matcher; + // Matcher has no contents_case (simple/combination), so it is invalid. + matcher.set_id(21); + EXPECT_EQ(createAtomMatchingTracker(matcher, 0, uidMap), nullptr); +} + +TEST(MetricsManagerTest, TestCreateAtomMatchingTrackerSimple) { + int index = 1; + int64_t id = 123; + sp uidMap = new UidMap(); + AtomMatcher matcher; + matcher.set_id(id); + SimpleAtomMatcher* simpleAtomMatcher = matcher.mutable_simple_atom_matcher(); + simpleAtomMatcher->set_atom_id(util::SCREEN_STATE_CHANGED); + simpleAtomMatcher->add_field_value_matcher()->set_field( + 1 /*SCREEN_STATE_CHANGE__DISPLAY_STATE*/); + simpleAtomMatcher->mutable_field_value_matcher(0)->set_eq_int( + android::view::DisplayStateEnum::DISPLAY_STATE_ON); + + sp tracker = createAtomMatchingTracker(matcher, index, uidMap); + EXPECT_NE(tracker, nullptr); + + EXPECT_TRUE(tracker->mInitialized); + EXPECT_EQ(tracker->getId(), id); + EXPECT_EQ(tracker->mIndex, index); + const set& atomIds = tracker->getAtomIds(); + ASSERT_EQ(atomIds.size(), 1); + EXPECT_EQ(atomIds.count(util::SCREEN_STATE_CHANGED), 1); +} + +TEST(MetricsManagerTest, TestCreateAtomMatchingTrackerCombination) { + int index = 1; + int64_t id = 123; + sp uidMap = new UidMap(); + AtomMatcher matcher; + matcher.set_id(id); + AtomMatcher_Combination* combination = matcher.mutable_combination(); + combination->set_operation(LogicalOperation::OR); + combination->add_matcher(123); + combination->add_matcher(223); + + sp tracker = createAtomMatchingTracker(matcher, index, uidMap); + EXPECT_NE(tracker, nullptr); + + // Combination matchers need to be initialized first. + EXPECT_FALSE(tracker->mInitialized); + EXPECT_EQ(tracker->getId(), id); + EXPECT_EQ(tracker->mIndex, index); + const set& atomIds = tracker->getAtomIds(); + ASSERT_EQ(atomIds.size(), 0); +} + +TEST(MetricsManagerTest, TestCreateConditionTrackerInvalid) { + const ConfigKey key(123, 456); + // Predicate has no contents_case (simple/combination), so it is invalid. + Predicate predicate; + predicate.set_id(21); + unordered_map atomTrackerMap; + EXPECT_EQ(createConditionTracker(key, predicate, 0, atomTrackerMap), nullptr); +} + +TEST(MetricsManagerTest, TestCreateConditionTrackerSimple) { + int index = 1; + int64_t id = 987; + const ConfigKey key(123, 456); + + int startMatcherIndex = 2, stopMatcherIndex = 0, stopAllMatcherIndex = 1; + int64_t startMatcherId = 246, stopMatcherId = 153, stopAllMatcherId = 975; + + Predicate predicate; + predicate.set_id(id); + SimplePredicate* simplePredicate = predicate.mutable_simple_predicate(); + simplePredicate->set_start(startMatcherId); + simplePredicate->set_stop(stopMatcherId); + simplePredicate->set_stop_all(stopAllMatcherId); + + unordered_map atomTrackerMap; + atomTrackerMap[startMatcherId] = startMatcherIndex; + atomTrackerMap[stopMatcherId] = stopMatcherIndex; + atomTrackerMap[stopAllMatcherId] = stopAllMatcherIndex; + + sp tracker = createConditionTracker(key, predicate, index, atomTrackerMap); + EXPECT_EQ(tracker->getConditionId(), id); + EXPECT_EQ(tracker->isSliced(), false); + EXPECT_TRUE(tracker->IsSimpleCondition()); + const set& interestedMatchers = tracker->getAtomMatchingTrackerIndex(); + ASSERT_EQ(interestedMatchers.size(), 3); + ASSERT_EQ(interestedMatchers.count(startMatcherIndex), 1); + ASSERT_EQ(interestedMatchers.count(stopMatcherIndex), 1); + ASSERT_EQ(interestedMatchers.count(stopAllMatcherIndex), 1); +} + +TEST(MetricsManagerTest, TestCreateConditionTrackerCombination) { + int index = 1; + int64_t id = 987; + const ConfigKey key(123, 456); + + Predicate predicate; + predicate.set_id(id); + Predicate_Combination* combinationPredicate = predicate.mutable_combination(); + combinationPredicate->set_operation(LogicalOperation::AND); + combinationPredicate->add_predicate(888); + combinationPredicate->add_predicate(777); + + // Combination conditions must be initialized to set most state. + unordered_map atomTrackerMap; + sp tracker = createConditionTracker(key, predicate, index, atomTrackerMap); + EXPECT_EQ(tracker->getConditionId(), id); + EXPECT_FALSE(tracker->IsSimpleCondition()); +} + +TEST(MetricsManagerTest, TestCreateAnomalyTrackerInvalidMetric) { + Alert alert; + alert.set_id(123); + alert.set_metric_id(1); + alert.set_trigger_if_sum_gt(1); + alert.set_num_buckets(1); + + sp anomalyAlarmMonitor; + vector> metricProducers; + // Pass in empty metric producers, causing an error. + EXPECT_EQ(createAnomalyTracker(alert, anomalyAlarmMonitor, UPDATE_NEW, /*updateTime=*/123, {}, + metricProducers), + nullopt); +} + +TEST(MetricsManagerTest, TestCreateAnomalyTrackerNoThreshold) { + int64_t metricId = 1; + Alert alert; + alert.set_id(123); + alert.set_metric_id(metricId); + alert.set_num_buckets(1); + + CountMetric metric; + metric.set_id(metricId); + metric.set_bucket(ONE_MINUTE); + sp wizard = new NaggyMock(); + vector> metricProducers({new CountMetricProducer( + kConfigKey, metric, 0, {ConditionState::kUnknown}, wizard, 0x0123456789, 0, 0)}); + sp anomalyAlarmMonitor; + EXPECT_EQ(createAnomalyTracker(alert, anomalyAlarmMonitor, UPDATE_NEW, /*updateTime=*/123, + {{1, 0}}, metricProducers), + nullopt); +} + +TEST(MetricsManagerTest, TestCreateAnomalyTrackerMissingBuckets) { + int64_t metricId = 1; + Alert alert; + alert.set_id(123); + alert.set_metric_id(metricId); + alert.set_trigger_if_sum_gt(1); + + CountMetric metric; + metric.set_id(metricId); + metric.set_bucket(ONE_MINUTE); + sp wizard = new NaggyMock(); + vector> metricProducers({new CountMetricProducer( + kConfigKey, metric, 0, {ConditionState::kUnknown}, wizard, 0x0123456789, 0, 0)}); + sp anomalyAlarmMonitor; + EXPECT_EQ(createAnomalyTracker(alert, anomalyAlarmMonitor, UPDATE_NEW, /*updateTime=*/123, + {{1, 0}}, metricProducers), + nullopt); +} + +TEST(MetricsManagerTest, TestCreateAnomalyTrackerGood) { + int64_t metricId = 1; + Alert alert; + alert.set_id(123); + alert.set_metric_id(metricId); + alert.set_trigger_if_sum_gt(1); + alert.set_num_buckets(1); + + CountMetric metric; + metric.set_id(metricId); + metric.set_bucket(ONE_MINUTE); + sp wizard = new NaggyMock(); + vector> metricProducers({new CountMetricProducer( + kConfigKey, metric, 0, {ConditionState::kUnknown}, wizard, 0x0123456789, 0, 0)}); + sp anomalyAlarmMonitor; + EXPECT_NE(createAnomalyTracker(alert, anomalyAlarmMonitor, UPDATE_NEW, /*updateTime=*/123, + {{1, 0}}, metricProducers), + nullopt); +} + +TEST(MetricsManagerTest, TestCreateAnomalyTrackerDurationTooLong) { + int64_t metricId = 1; + Alert alert; + alert.set_id(123); + alert.set_metric_id(metricId); + // Impossible for alert to fire since the time is bigger than bucketSize * numBuckets + alert.set_trigger_if_sum_gt(MillisToNano(TimeUnitToBucketSizeInMillis(ONE_MINUTE)) + 1); + alert.set_num_buckets(1); + + DurationMetric metric; + metric.set_id(metricId); + metric.set_bucket(ONE_MINUTE); + metric.set_aggregation_type(DurationMetric_AggregationType_SUM); + FieldMatcher dimensions; + sp wizard = new NaggyMock(); + vector> metricProducers({new DurationMetricProducer( + kConfigKey, metric, -1 /*no condition*/, {}, -1 /* what index not needed*/, + 1 /* start index */, 2 /* stop index */, 3 /* stop_all index */, false /*nesting*/, + wizard, 0x0123456789, dimensions, 0, 0)}); + sp anomalyAlarmMonitor; + EXPECT_EQ(createAnomalyTracker(alert, anomalyAlarmMonitor, UPDATE_NEW, /*updateTime=*/123, + {{1, 0}}, metricProducers), + nullopt); +} + +TEST(MetricsManagerTest, TestCreateDurationProducerDimensionsInWhatInvalid) { + StatsdConfig config; + config.add_allowed_log_source("AID_ROOT"); + *config.add_atom_matcher() = CreateAcquireWakelockAtomMatcher(); + *config.add_atom_matcher() = CreateReleaseWakelockAtomMatcher(); + *config.add_atom_matcher() = CreateMoveToBackgroundAtomMatcher(); + *config.add_atom_matcher() = CreateMoveToForegroundAtomMatcher(); + + Predicate holdingWakelockPredicate = CreateHoldingWakelockPredicate(); + // The predicate is dimensioning by first attribution node by uid. + FieldMatcher dimensions = + CreateAttributionUidDimensions(util::WAKELOCK_STATE_CHANGED, {Position::FIRST}); + *holdingWakelockPredicate.mutable_simple_predicate()->mutable_dimensions() = dimensions; + *config.add_predicate() = holdingWakelockPredicate; + + DurationMetric* durationMetric = config.add_duration_metric(); + durationMetric->set_id(StringToId("WakelockDuration")); + durationMetric->set_what(holdingWakelockPredicate.id()); + durationMetric->set_aggregation_type(DurationMetric::SUM); + // The metric is dimensioning by first attribution node by uid AND tag. + // Invalid since the predicate only dimensions by uid. + *durationMetric->mutable_dimensions_in_what() = CreateAttributionUidAndOtherDimensions( + util::WAKELOCK_STATE_CHANGED, {Position::FIRST}, {3 /* tag */}); + durationMetric->set_bucket(FIVE_MINUTES); + + ConfigKey key(123, 987); + uint64_t timeNs = 456; + sp pullerManager = new StatsPullerManager(); + sp anomalyAlarmMonitor; + sp periodicAlarmMonitor; + sp uidMap; + sp metricsManager = + new MetricsManager(key, config, timeNs, timeNs, uidMap, pullerManager, + anomalyAlarmMonitor, periodicAlarmMonitor); + EXPECT_FALSE(metricsManager->isConfigValid()); +} + +} // namespace statsd +} // namespace os +} // namespace android + +#else +GTEST_LOG_(INFO) << "This test does nothing.\n"; +#endif diff --git a/statsd/tests/shell/ShellSubscriber_test.cpp b/statsd/tests/shell/ShellSubscriber_test.cpp new file mode 100644 index 00000000..37f5bf33 --- /dev/null +++ b/statsd/tests/shell/ShellSubscriber_test.cpp @@ -0,0 +1,207 @@ +// Copyright (C) 2018 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 "src/shell/ShellSubscriber.h" + +#include +#include +#include + +#include + +#include "src/shell/shell_config.pb.h" +#include "src/shell/shell_data.pb.h" +#include "frameworks/proto_logging/stats/atoms.pb.h" +#include "stats_event.h" +#include "tests/metrics/metrics_test_helper.h" +#include "tests/statsd_test_util.h" + +using namespace android::os::statsd; +using android::sp; +using std::vector; +using testing::_; +using testing::Invoke; +using testing::NaggyMock; +using testing::StrictMock; + +#ifdef __ANDROID__ + +void runShellTest(ShellSubscription config, sp uidMap, + sp pullerManager, + const vector>& pushedEvents, + const ShellData& expectedData) { + // set up 2 pipes for read/write config and data + int fds_config[2]; + ASSERT_EQ(0, pipe(fds_config)); + + int fds_data[2]; + ASSERT_EQ(0, pipe(fds_data)); + + size_t bufferSize = config.ByteSize(); + // write the config to pipe, first write size of the config + write(fds_config[1], &bufferSize, sizeof(bufferSize)); + // then write config itself + vector buffer(bufferSize); + config.SerializeToArray(&buffer[0], bufferSize); + write(fds_config[1], buffer.data(), bufferSize); + close(fds_config[1]); + + sp shellClient = new ShellSubscriber(uidMap, pullerManager); + + // mimic a binder thread that a shell subscriber runs on. it would block. + std::thread reader([&shellClient, &fds_config, &fds_data] { + shellClient->startNewSubscription(fds_config[0], fds_data[1], /*timeoutSec=*/-1); + }); + reader.detach(); + + // let the shell subscriber to receive the config from pipe. + std::this_thread::sleep_for(100ms); + + if (pushedEvents.size() > 0) { + // send a log event that matches the config. + std::thread log_reader([&shellClient, &pushedEvents] { + for (const auto& event : pushedEvents) { + shellClient->onLogEvent(*event); + } + }); + + log_reader.detach(); + + if (log_reader.joinable()) { + log_reader.join(); + } + } + + // wait for the data to be written. + std::this_thread::sleep_for(100ms); + + // Because we might receive heartbeats from statsd, consisting of data sizes + // of 0, encapsulate reads within a while loop. + bool readAtom = false; + while (!readAtom) { + // Read the atom size. + size_t dataSize = 0; + read(fds_data[0], &dataSize, sizeof(dataSize)); + if (dataSize == 0) continue; + EXPECT_EQ(expectedData.ByteSize(), int(dataSize)); + + // Read that much data in proto binary format. + vector dataBuffer(dataSize); + EXPECT_EQ((int)dataSize, read(fds_data[0], dataBuffer.data(), dataSize)); + + // Make sure the received bytes can be parsed to an atom. + ShellData receivedAtom; + EXPECT_TRUE(receivedAtom.ParseFromArray(dataBuffer.data(), dataSize) != 0); + + // Serialize the expected atom to byte array and compare to make sure + // they are the same. + vector expectedAtomBuffer(expectedData.ByteSize()); + expectedData.SerializeToArray(expectedAtomBuffer.data(), expectedData.ByteSize()); + EXPECT_EQ(expectedAtomBuffer, dataBuffer); + + readAtom = true; + } + + close(fds_data[0]); + if (reader.joinable()) { + reader.join(); + } +} + +TEST(ShellSubscriberTest, testPushedSubscription) { + sp uidMap = new NaggyMock(); + + sp pullerManager = new StrictMock(); + vector> pushedList; + + // Create the LogEvent from an AStatsEvent + std::unique_ptr logEvent = CreateScreenStateChangedEvent( + 1000 /*timestamp*/, ::android::view::DisplayStateEnum::DISPLAY_STATE_ON); + pushedList.push_back(std::move(logEvent)); + + // create a simple config to get screen events + ShellSubscription config; + config.add_pushed()->set_atom_id(29); + + // this is the expected screen event atom. + ShellData shellData; + shellData.add_atom()->mutable_screen_state_changed()->set_state( + ::android::view::DisplayStateEnum::DISPLAY_STATE_ON); + + runShellTest(config, uidMap, pullerManager, pushedList, shellData); +} + +namespace { + +int kUid1 = 1000; +int kUid2 = 2000; + +int kCpuTime1 = 100; +int kCpuTime2 = 200; + +ShellData getExpectedShellData() { + ShellData shellData; + auto* atom1 = shellData.add_atom()->mutable_cpu_active_time(); + atom1->set_uid(kUid1); + atom1->set_time_millis(kCpuTime1); + + auto* atom2 = shellData.add_atom()->mutable_cpu_active_time(); + atom2->set_uid(kUid2); + atom2->set_time_millis(kCpuTime2); + + return shellData; +} + +ShellSubscription getPulledConfig() { + ShellSubscription config; + auto* pull_config = config.add_pulled(); + pull_config->mutable_matcher()->set_atom_id(10016); + pull_config->set_freq_millis(2000); + return config; +} + +shared_ptr makeCpuActiveTimeAtom(int32_t uid, int64_t timeMillis) { + AStatsEvent* statsEvent = AStatsEvent_obtain(); + AStatsEvent_setAtomId(statsEvent, 10016); + AStatsEvent_overwriteTimestamp(statsEvent, 1111L); + AStatsEvent_writeInt32(statsEvent, uid); + AStatsEvent_writeInt64(statsEvent, timeMillis); + + std::shared_ptr logEvent = std::make_shared(/*uid=*/0, /*pid=*/0); + parseStatsEventToLogEvent(statsEvent, logEvent.get()); + return logEvent; +} + +} // namespace + +TEST(ShellSubscriberTest, testPulledSubscription) { + sp uidMap = new NaggyMock(); + + sp pullerManager = new StrictMock(); + const vector uids = {AID_SYSTEM}; + EXPECT_CALL(*pullerManager, Pull(10016, uids, _, _)) + .WillRepeatedly(Invoke([](int tagId, const vector&, const int64_t, + vector>* data) { + data->clear(); + data->push_back(makeCpuActiveTimeAtom(/*uid=*/kUid1, /*timeMillis=*/kCpuTime1)); + data->push_back(makeCpuActiveTimeAtom(/*uid=*/kUid2, /*timeMillis=*/kCpuTime2)); + return true; + })); + runShellTest(getPulledConfig(), uidMap, pullerManager, vector>(), + getExpectedShellData()); +} + +#else +GTEST_LOG_(INFO) << "This test does nothing.\n"; +#endif diff --git a/statsd/tests/state/StateTracker_test.cpp b/statsd/tests/state/StateTracker_test.cpp new file mode 100644 index 00000000..6516c152 --- /dev/null +++ b/statsd/tests/state/StateTracker_test.cpp @@ -0,0 +1,571 @@ +/* + * Copyright (C) 2019, 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 "state/StateTracker.h" + +#include +#include + +#include "state/StateListener.h" +#include "state/StateManager.h" +#include "state/StateTracker.h" +#include "stats_event.h" +#include "tests/statsd_test_util.h" + +#ifdef __ANDROID__ + +namespace android { +namespace os { +namespace statsd { + +const int32_t timestampNs = 1000; + +/** + * Mock StateListener class for testing. + * Stores primary key and state pairs. + */ +class TestStateListener : public virtual StateListener { +public: + TestStateListener(){}; + + virtual ~TestStateListener(){}; + + struct Update { + Update(const HashableDimensionKey& key, int state) : mKey(key), mState(state){}; + HashableDimensionKey mKey; + int mState; + }; + + std::vector updates; + + void onStateChanged(const int64_t eventTimeNs, const int32_t atomId, + const HashableDimensionKey& primaryKey, const FieldValue& oldState, + const FieldValue& newState) { + updates.emplace_back(primaryKey, newState.mValue.int_value); + } +}; + +int getStateInt(StateManager& mgr, int atomId, const HashableDimensionKey& queryKey) { + FieldValue output; + mgr.getStateValue(atomId, queryKey, &output); + return output.mValue.int_value; +} + +// START: build event functions. +// Incorrect event - missing fields +std::unique_ptr buildIncorrectOverlayEvent(int uid, const std::string& packageName, + int state) { + AStatsEvent* statsEvent = AStatsEvent_obtain(); + AStatsEvent_setAtomId(statsEvent, util::OVERLAY_STATE_CHANGED); + AStatsEvent_overwriteTimestamp(statsEvent, 1000); + + AStatsEvent_writeInt32(statsEvent, uid); + AStatsEvent_writeString(statsEvent, packageName.c_str()); + // Missing field 3 - using_alert_window. + AStatsEvent_writeInt32(statsEvent, state); + + std::unique_ptr logEvent = std::make_unique(/*uid=*/0, /*pid=*/0); + parseStatsEventToLogEvent(statsEvent, logEvent.get()); + return logEvent; +} + +// Incorrect event - exclusive state has wrong type +std::unique_ptr buildOverlayEventBadStateType(int uid, const std::string& packageName) { + AStatsEvent* statsEvent = AStatsEvent_obtain(); + AStatsEvent_setAtomId(statsEvent, util::OVERLAY_STATE_CHANGED); + AStatsEvent_overwriteTimestamp(statsEvent, 1000); + + AStatsEvent_writeInt32(statsEvent, uid); + AStatsEvent_writeString(statsEvent, packageName.c_str()); + AStatsEvent_writeInt32(statsEvent, true); // using_alert_window + AStatsEvent_writeString(statsEvent, "string"); // exclusive state: string instead of int + + std::unique_ptr logEvent = std::make_unique(/*uid=*/0, /*pid=*/0); + parseStatsEventToLogEvent(statsEvent, logEvent.get()); + return logEvent; +} +// END: build event functions. + +TEST(StateListenerTest, TestStateListenerWeakPointer) { + sp listener = new TestStateListener(); + wp wListener = listener; + listener = nullptr; // let go of listener + EXPECT_TRUE(wListener.promote() == nullptr); +} + +TEST(StateManagerTest, TestStateManagerGetInstance) { + sp listener1 = new TestStateListener(); + StateManager& mgr = StateManager::getInstance(); + mgr.clear(); + + mgr.registerListener(util::SCREEN_STATE_CHANGED, listener1); + EXPECT_EQ(1, mgr.getStateTrackersCount()); + EXPECT_EQ(1, StateManager::getInstance().getStateTrackersCount()); +} + +TEST(StateManagerTest, TestOnLogEvent) { + sp uidMap = makeMockUidMapForPackage("com.android.systemui", {10111}); + sp listener1 = new TestStateListener(); + StateManager mgr; + mgr.updateLogSources(uidMap); + // Add StateTracker by registering a listener. + mgr.registerListener(util::SCREEN_STATE_CHANGED, listener1); + + // log event using AID_ROOT + std::unique_ptr event = CreateScreenStateChangedEvent( + timestampNs, android::view::DisplayStateEnum::DISPLAY_STATE_ON); + mgr.onLogEvent(*event); + + // check StateTracker was updated by querying for state + HashableDimensionKey queryKey = DEFAULT_DIMENSION_KEY; + EXPECT_EQ(android::view::DisplayStateEnum::DISPLAY_STATE_ON, + getStateInt(mgr, util::SCREEN_STATE_CHANGED, queryKey)); + + // log event using mocked uid + event = CreateScreenStateChangedEvent( + timestampNs, android::view::DisplayStateEnum::DISPLAY_STATE_OFF, 10111); + mgr.onLogEvent(*event); + + // check StateTracker was updated by querying for state + queryKey = DEFAULT_DIMENSION_KEY; + EXPECT_EQ(android::view::DisplayStateEnum::DISPLAY_STATE_OFF, + getStateInt(mgr, util::SCREEN_STATE_CHANGED, queryKey)); + + // log event using non-whitelisted uid + event = CreateScreenStateChangedEvent(timestampNs, + android::view::DisplayStateEnum::DISPLAY_STATE_ON, 10112); + mgr.onLogEvent(*event); + + // check StateTracker was NOT updated by querying for state + queryKey = DEFAULT_DIMENSION_KEY; + EXPECT_EQ(android::view::DisplayStateEnum::DISPLAY_STATE_OFF, + getStateInt(mgr, util::SCREEN_STATE_CHANGED, queryKey)); + + // log event using AID_SYSTEM + event = CreateScreenStateChangedEvent( + timestampNs, android::view::DisplayStateEnum::DISPLAY_STATE_ON, AID_SYSTEM); + mgr.onLogEvent(*event); + + // check StateTracker was updated by querying for state + queryKey = DEFAULT_DIMENSION_KEY; + EXPECT_EQ(android::view::DisplayStateEnum::DISPLAY_STATE_ON, + getStateInt(mgr, util::SCREEN_STATE_CHANGED, queryKey)); +} + +/** + * Test registering listeners to StateTrackers + * + * - StateManager will create a new StateTracker if it doesn't already exist + * and then register the listener to the StateTracker. + * - If a listener is already registered to a StateTracker, it is not added again. + * - StateTrackers are only created for atoms that are state atoms. + */ +TEST(StateTrackerTest, TestRegisterListener) { + sp listener1 = new TestStateListener(); + sp listener2 = new TestStateListener(); + StateManager mgr; + + // Register listener to non-existing StateTracker + EXPECT_EQ(0, mgr.getStateTrackersCount()); + mgr.registerListener(util::SCREEN_STATE_CHANGED, listener1); + EXPECT_EQ(1, mgr.getStateTrackersCount()); + EXPECT_EQ(1, mgr.getListenersCount(util::SCREEN_STATE_CHANGED)); + + // Register listener to existing StateTracker + mgr.registerListener(util::SCREEN_STATE_CHANGED, listener2); + EXPECT_EQ(1, mgr.getStateTrackersCount()); + EXPECT_EQ(2, mgr.getListenersCount(util::SCREEN_STATE_CHANGED)); + + // Register already registered listener to existing StateTracker + mgr.registerListener(util::SCREEN_STATE_CHANGED, listener2); + EXPECT_EQ(1, mgr.getStateTrackersCount()); + EXPECT_EQ(2, mgr.getListenersCount(util::SCREEN_STATE_CHANGED)); + + // Register listener to non-state atom + mgr.registerListener(util::BATTERY_LEVEL_CHANGED, listener2); + EXPECT_EQ(2, mgr.getStateTrackersCount()); +} + +/** + * Test unregistering listeners from StateTrackers + * + * - StateManager will unregister listeners from a StateTracker only if the + * StateTracker exists and the listener is registered to the StateTracker. + * - Once all listeners are removed from a StateTracker, the StateTracker + * is also removed. + */ +TEST(StateTrackerTest, TestUnregisterListener) { + sp listener1 = new TestStateListener(); + sp listener2 = new TestStateListener(); + StateManager mgr; + + // Unregister listener from non-existing StateTracker + EXPECT_EQ(0, mgr.getStateTrackersCount()); + mgr.unregisterListener(util::SCREEN_STATE_CHANGED, listener1); + EXPECT_EQ(0, mgr.getStateTrackersCount()); + EXPECT_EQ(-1, mgr.getListenersCount(util::SCREEN_STATE_CHANGED)); + + // Unregister non-registered listener from existing StateTracker + mgr.registerListener(util::SCREEN_STATE_CHANGED, listener1); + EXPECT_EQ(1, mgr.getStateTrackersCount()); + EXPECT_EQ(1, mgr.getListenersCount(util::SCREEN_STATE_CHANGED)); + mgr.unregisterListener(util::SCREEN_STATE_CHANGED, listener2); + EXPECT_EQ(1, mgr.getStateTrackersCount()); + EXPECT_EQ(1, mgr.getListenersCount(util::SCREEN_STATE_CHANGED)); + + // Unregister second-to-last listener from StateTracker + mgr.registerListener(util::SCREEN_STATE_CHANGED, listener2); + mgr.unregisterListener(util::SCREEN_STATE_CHANGED, listener1); + EXPECT_EQ(1, mgr.getStateTrackersCount()); + EXPECT_EQ(1, mgr.getListenersCount(util::SCREEN_STATE_CHANGED)); + + // Unregister last listener from StateTracker + mgr.unregisterListener(util::SCREEN_STATE_CHANGED, listener2); + EXPECT_EQ(0, mgr.getStateTrackersCount()); + EXPECT_EQ(-1, mgr.getListenersCount(util::SCREEN_STATE_CHANGED)); +} + +/** + * Test a binary state atom with nested counting. + * + * To go from an "ON" state to an "OFF" state with nested counting, we must see + * an equal number of "OFF" events as "ON" events. + * For example, ACQUIRE, ACQUIRE, RELEASE will still be in the ACQUIRE state. + * ACQUIRE, ACQUIRE, RELEASE, RELEASE will be in the RELEASE state. + */ +TEST(StateTrackerTest, TestStateChangeNested) { + sp listener = new TestStateListener(); + StateManager mgr; + mgr.registerListener(util::WAKELOCK_STATE_CHANGED, listener); + + std::vector attributionUids1 = {1000}; + std::vector attributionTags1 = {"tag"}; + + std::unique_ptr event1 = CreateAcquireWakelockEvent(timestampNs, attributionUids1, + attributionTags1, "wakelockName"); + mgr.onLogEvent(*event1); + ASSERT_EQ(1, listener->updates.size()); + EXPECT_EQ(1000, listener->updates[0].mKey.getValues()[0].mValue.int_value); + EXPECT_EQ(1, listener->updates[0].mState); + listener->updates.clear(); + + std::unique_ptr event2 = CreateAcquireWakelockEvent( + timestampNs + 1000, attributionUids1, attributionTags1, "wakelockName"); + mgr.onLogEvent(*event2); + ASSERT_EQ(0, listener->updates.size()); + + std::unique_ptr event3 = CreateReleaseWakelockEvent( + timestampNs + 2000, attributionUids1, attributionTags1, "wakelockName"); + mgr.onLogEvent(*event3); + ASSERT_EQ(0, listener->updates.size()); + + std::unique_ptr event4 = CreateReleaseWakelockEvent( + timestampNs + 3000, attributionUids1, attributionTags1, "wakelockName"); + mgr.onLogEvent(*event4); + ASSERT_EQ(1, listener->updates.size()); + EXPECT_EQ(1000, listener->updates[0].mKey.getValues()[0].mValue.int_value); + EXPECT_EQ(0, listener->updates[0].mState); +} + +/** + * Test a state atom with a reset state. + * + * If the reset state value is seen, every state in the map is set to the default + * state and every listener is notified. + */ +TEST(StateTrackerTest, TestStateChangeReset) { + sp listener = new TestStateListener(); + StateManager mgr; + mgr.registerListener(util::BLE_SCAN_STATE_CHANGED, listener); + + std::vector attributionUids1 = {1000}; + std::vector attributionTags1 = {"tag1"}; + std::vector attributionUids2 = {2000}; + + std::unique_ptr event1 = + CreateBleScanStateChangedEvent(timestampNs, attributionUids1, attributionTags1, + BleScanStateChanged::ON, false, false, false); + mgr.onLogEvent(*event1); + ASSERT_EQ(1, listener->updates.size()); + EXPECT_EQ(1000, listener->updates[0].mKey.getValues()[0].mValue.int_value); + EXPECT_EQ(BleScanStateChanged::ON, listener->updates[0].mState); + FieldValue stateFieldValue; + mgr.getStateValue(util::BLE_SCAN_STATE_CHANGED, listener->updates[0].mKey, &stateFieldValue); + EXPECT_EQ(BleScanStateChanged::ON, stateFieldValue.mValue.int_value); + listener->updates.clear(); + + std::unique_ptr event2 = + CreateBleScanStateChangedEvent(timestampNs + 1000, attributionUids2, attributionTags1, + BleScanStateChanged::ON, false, false, false); + mgr.onLogEvent(*event2); + ASSERT_EQ(1, listener->updates.size()); + EXPECT_EQ(2000, listener->updates[0].mKey.getValues()[0].mValue.int_value); + EXPECT_EQ(BleScanStateChanged::ON, listener->updates[0].mState); + mgr.getStateValue(util::BLE_SCAN_STATE_CHANGED, listener->updates[0].mKey, &stateFieldValue); + EXPECT_EQ(BleScanStateChanged::ON, stateFieldValue.mValue.int_value); + listener->updates.clear(); + + std::unique_ptr event3 = + CreateBleScanStateChangedEvent(timestampNs + 2000, attributionUids2, attributionTags1, + BleScanStateChanged::RESET, false, false, false); + mgr.onLogEvent(*event3); + ASSERT_EQ(2, listener->updates.size()); + for (const TestStateListener::Update& update : listener->updates) { + EXPECT_EQ(BleScanStateChanged::OFF, update.mState); + + mgr.getStateValue(util::BLE_SCAN_STATE_CHANGED, update.mKey, &stateFieldValue); + EXPECT_EQ(BleScanStateChanged::OFF, stateFieldValue.mValue.int_value); + } +} + +/** + * Test StateManager's onLogEvent and StateListener's onStateChanged correctly + * updates listener for states without primary keys. + */ +TEST(StateTrackerTest, TestStateChangeNoPrimaryFields) { + sp listener1 = new TestStateListener(); + StateManager mgr; + mgr.registerListener(util::SCREEN_STATE_CHANGED, listener1); + + // log event + std::unique_ptr event = CreateScreenStateChangedEvent( + timestampNs, android::view::DisplayStateEnum::DISPLAY_STATE_ON); + mgr.onLogEvent(*event); + + // check listener was updated + ASSERT_EQ(1, listener1->updates.size()); + EXPECT_EQ(DEFAULT_DIMENSION_KEY, listener1->updates[0].mKey); + EXPECT_EQ(2, listener1->updates[0].mState); + + // check StateTracker was updated by querying for state + HashableDimensionKey queryKey = DEFAULT_DIMENSION_KEY; + EXPECT_EQ(android::view::DisplayStateEnum::DISPLAY_STATE_ON, + getStateInt(mgr, util::SCREEN_STATE_CHANGED, queryKey)); +} + +/** + * Test StateManager's onLogEvent and StateListener's onStateChanged correctly + * updates listener for states with one primary key. + */ +TEST(StateTrackerTest, TestStateChangeOnePrimaryField) { + sp listener1 = new TestStateListener(); + StateManager mgr; + mgr.registerListener(util::UID_PROCESS_STATE_CHANGED, listener1); + + // log event + std::unique_ptr event = CreateUidProcessStateChangedEvent( + timestampNs, 1000 /*uid*/, android::app::ProcessStateEnum::PROCESS_STATE_TOP); + mgr.onLogEvent(*event); + + // check listener was updated + ASSERT_EQ(1, listener1->updates.size()); + EXPECT_EQ(1000, listener1->updates[0].mKey.getValues()[0].mValue.int_value); + EXPECT_EQ(1002, listener1->updates[0].mState); + + // check StateTracker was updated by querying for state + HashableDimensionKey queryKey; + getUidProcessKey(1000 /* uid */, &queryKey); + EXPECT_EQ(android::app::ProcessStateEnum::PROCESS_STATE_TOP, + getStateInt(mgr, util::UID_PROCESS_STATE_CHANGED, queryKey)); +} + +TEST(StateTrackerTest, TestStateChangePrimaryFieldAttrChain) { + sp listener1 = new TestStateListener(); + StateManager mgr; + mgr.registerListener(util::WAKELOCK_STATE_CHANGED, listener1); + + // Log event. + std::vector attributionUids = {1001}; + std::vector attributionTags = {"tag1"}; + + std::unique_ptr event = CreateAcquireWakelockEvent(timestampNs, attributionUids, + attributionTags, "wakelockName"); + mgr.onLogEvent(*event); + EXPECT_EQ(1, mgr.getStateTrackersCount()); + EXPECT_EQ(1, mgr.getListenersCount(util::WAKELOCK_STATE_CHANGED)); + + // Check listener was updated. + ASSERT_EQ(1, listener1->updates.size()); + ASSERT_EQ(3, listener1->updates[0].mKey.getValues().size()); + EXPECT_EQ(1001, listener1->updates[0].mKey.getValues()[0].mValue.int_value); + EXPECT_EQ(1, listener1->updates[0].mKey.getValues()[1].mValue.int_value); + EXPECT_EQ("wakelockName", listener1->updates[0].mKey.getValues()[2].mValue.str_value); + EXPECT_EQ(WakelockStateChanged::ACQUIRE, listener1->updates[0].mState); + + // Check StateTracker was updated by querying for state. + HashableDimensionKey queryKey; + getPartialWakelockKey(1001 /* uid */, "wakelockName", &queryKey); + EXPECT_EQ(WakelockStateChanged::ACQUIRE, + getStateInt(mgr, util::WAKELOCK_STATE_CHANGED, queryKey)); + + // No state stored for this query key. + HashableDimensionKey queryKey2; + getPartialWakelockKey(1002 /* uid */, "tag1", &queryKey2); + EXPECT_EQ(-1 /*StateTracker::kStateUnknown*/, + getStateInt(mgr, util::WAKELOCK_STATE_CHANGED, queryKey2)); + + // Partial query fails. + HashableDimensionKey queryKey3; + getPartialWakelockKey(1001 /* uid */, &queryKey3); + EXPECT_EQ(-1 /*StateTracker::kStateUnknown*/, + getStateInt(mgr, util::WAKELOCK_STATE_CHANGED, queryKey3)); +} + +/** + * Test StateManager's onLogEvent and StateListener's onStateChanged correctly + * updates listener for states with multiple primary keys. + */ +TEST(StateTrackerTest, TestStateChangeMultiplePrimaryFields) { + sp listener1 = new TestStateListener(); + StateManager mgr; + mgr.registerListener(util::OVERLAY_STATE_CHANGED, listener1); + + // log event + std::unique_ptr event = CreateOverlayStateChangedEvent( + timestampNs, 1000 /* uid */, "package1", true /*using_alert_window*/, + OverlayStateChanged::ENTERED); + mgr.onLogEvent(*event); + + // check listener was updated + ASSERT_EQ(1, listener1->updates.size()); + EXPECT_EQ(1000, listener1->updates[0].mKey.getValues()[0].mValue.int_value); + EXPECT_EQ(1, listener1->updates[0].mState); + + // check StateTracker was updated by querying for state + HashableDimensionKey queryKey; + getOverlayKey(1000 /* uid */, "package1", &queryKey); + EXPECT_EQ(OverlayStateChanged::ENTERED, + getStateInt(mgr, util::OVERLAY_STATE_CHANGED, queryKey)); +} + +/** + * Test StateManager's onLogEvent and StateListener's onStateChanged + * when there is an error extracting state from log event. Listener is not + * updated of state change. + */ +TEST(StateTrackerTest, TestStateChangeEventError) { + sp listener1 = new TestStateListener(); + StateManager mgr; + mgr.registerListener(util::OVERLAY_STATE_CHANGED, listener1); + + // log event + std::shared_ptr event1 = + buildIncorrectOverlayEvent(1000 /* uid */, "package1", 1 /* state */); + std::shared_ptr event2 = buildOverlayEventBadStateType(1001 /* uid */, "package2"); + + // check listener was updated + mgr.onLogEvent(*event1); + ASSERT_EQ(0, listener1->updates.size()); + mgr.onLogEvent(*event2); + ASSERT_EQ(0, listener1->updates.size()); +} + +TEST(StateTrackerTest, TestStateQuery) { + sp listener1 = new TestStateListener(); + sp listener2 = new TestStateListener(); + sp listener3 = new TestStateListener(); + sp listener4 = new TestStateListener(); + StateManager mgr; + mgr.registerListener(util::SCREEN_STATE_CHANGED, listener1); + mgr.registerListener(util::UID_PROCESS_STATE_CHANGED, listener2); + mgr.registerListener(util::OVERLAY_STATE_CHANGED, listener3); + mgr.registerListener(util::WAKELOCK_STATE_CHANGED, listener4); + + std::unique_ptr event1 = CreateUidProcessStateChangedEvent( + timestampNs, 1000 /*uid*/, + android::app::ProcessStateEnum::PROCESS_STATE_TOP); // state value: 1002 + std::unique_ptr event2 = CreateUidProcessStateChangedEvent( + timestampNs + 1000, 1001 /*uid*/, + android::app::ProcessStateEnum::PROCESS_STATE_FOREGROUND_SERVICE); // state value: + // 1003 + std::unique_ptr event3 = CreateUidProcessStateChangedEvent( + timestampNs + 2000, 1002 /*uid*/, + android::app::ProcessStateEnum::PROCESS_STATE_PERSISTENT); // state value: 1000 + std::unique_ptr event4 = CreateUidProcessStateChangedEvent( + timestampNs + 3000, 1001 /*uid*/, + android::app::ProcessStateEnum::PROCESS_STATE_TOP); // state value: 1002 + std::unique_ptr event5 = CreateScreenStateChangedEvent( + timestampNs + 4000, android::view::DisplayStateEnum::DISPLAY_STATE_ON); + std::unique_ptr event6 = CreateOverlayStateChangedEvent( + timestampNs + 5000, 1000 /*uid*/, "package1", true /*using_alert_window*/, + OverlayStateChanged::ENTERED); + std::unique_ptr event7 = CreateOverlayStateChangedEvent( + timestampNs + 6000, 1000 /*uid*/, "package2", true /*using_alert_window*/, + OverlayStateChanged::EXITED); + + std::vector attributionUids = {1005}; + std::vector attributionTags = {"tag"}; + + std::unique_ptr event8 = CreateAcquireWakelockEvent( + timestampNs + 7000, attributionUids, attributionTags, "wakelock1"); + std::unique_ptr event9 = CreateReleaseWakelockEvent( + timestampNs + 8000, attributionUids, attributionTags, "wakelock2"); + + mgr.onLogEvent(*event1); + mgr.onLogEvent(*event2); + mgr.onLogEvent(*event3); + mgr.onLogEvent(*event5); + mgr.onLogEvent(*event5); + mgr.onLogEvent(*event6); + mgr.onLogEvent(*event7); + mgr.onLogEvent(*event8); + mgr.onLogEvent(*event9); + + // Query for UidProcessState of uid 1001 + HashableDimensionKey queryKey1; + getUidProcessKey(1001, &queryKey1); + EXPECT_EQ(android::app::ProcessStateEnum::PROCESS_STATE_FOREGROUND_SERVICE, + getStateInt(mgr, util::UID_PROCESS_STATE_CHANGED, queryKey1)); + + // Query for UidProcessState of uid 1004 - not in state map + HashableDimensionKey queryKey2; + getUidProcessKey(1004, &queryKey2); + EXPECT_EQ(-1, getStateInt(mgr, util::UID_PROCESS_STATE_CHANGED, + queryKey2)); // default state + + // Query for UidProcessState of uid 1001 - after change in state + mgr.onLogEvent(*event4); + EXPECT_EQ(android::app::ProcessStateEnum::PROCESS_STATE_TOP, + getStateInt(mgr, util::UID_PROCESS_STATE_CHANGED, queryKey1)); + + // Query for ScreenState + EXPECT_EQ(android::view::DisplayStateEnum::DISPLAY_STATE_ON, + getStateInt(mgr, util::SCREEN_STATE_CHANGED, DEFAULT_DIMENSION_KEY)); + + // Query for OverlayState of uid 1000, package name "package2" + HashableDimensionKey queryKey3; + getOverlayKey(1000, "package2", &queryKey3); + EXPECT_EQ(OverlayStateChanged::EXITED, + getStateInt(mgr, util::OVERLAY_STATE_CHANGED, queryKey3)); + + // Query for WakelockState of uid 1005, tag 2 + HashableDimensionKey queryKey4; + getPartialWakelockKey(1005, "wakelock2", &queryKey4); + EXPECT_EQ(WakelockStateChanged::RELEASE, + getStateInt(mgr, util::WAKELOCK_STATE_CHANGED, queryKey4)); + + // Query for WakelockState of uid 1005, tag 1 + HashableDimensionKey queryKey5; + getPartialWakelockKey(1005, "wakelock1", &queryKey5); + EXPECT_EQ(WakelockStateChanged::ACQUIRE, + getStateInt(mgr, util::WAKELOCK_STATE_CHANGED, queryKey5)); +} + +} // namespace statsd +} // namespace os +} // namespace android +#else +GTEST_LOG_(INFO) << "This test does nothing.\n"; +#endif diff --git a/statsd/tests/statsd_test_util.cpp b/statsd/tests/statsd_test_util.cpp new file mode 100644 index 00000000..65062724 --- /dev/null +++ b/statsd/tests/statsd_test_util.cpp @@ -0,0 +1,1624 @@ +// 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 "statsd_test_util.h" + +#include + +#include "matchers/SimpleAtomMatchingTracker.h" +#include "stats_event.h" + +using aidl::android::util::StatsEventParcel; +using std::shared_ptr; + +namespace android { +namespace os { +namespace statsd { + +StatsLogReport outputStreamToProto(ProtoOutputStream* proto) { + vector bytes; + bytes.resize(proto->size()); + size_t pos = 0; + sp reader = proto->data(); + + while (reader->readBuffer() != NULL) { + size_t toRead = reader->currentToRead(); + std::memcpy(&((bytes)[pos]), reader->readBuffer(), toRead); + pos += toRead; + reader->move(toRead); + } + + StatsLogReport report; + report.ParseFromArray(bytes.data(), bytes.size()); + return report; +} + +AtomMatcher CreateSimpleAtomMatcher(const string& name, int atomId) { + 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(atomId); + return atom_matcher; +} + +AtomMatcher CreateTemperatureAtomMatcher() { + return CreateSimpleAtomMatcher("TemperatureMatcher", util::TEMPERATURE); +} + +AtomMatcher CreateScheduledJobStateChangedAtomMatcher(const string& name, + ScheduledJobStateChanged::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(util::SCHEDULED_JOB_STATE_CHANGED); + auto field_value_matcher = simple_atom_matcher->add_field_value_matcher(); + field_value_matcher->set_field(3); // State field. + field_value_matcher->set_eq_int(state); + return atom_matcher; +} + +AtomMatcher CreateStartScheduledJobAtomMatcher() { + return CreateScheduledJobStateChangedAtomMatcher("ScheduledJobStart", + ScheduledJobStateChanged::STARTED); +} + +AtomMatcher CreateFinishScheduledJobAtomMatcher() { + return CreateScheduledJobStateChangedAtomMatcher("ScheduledJobFinish", + ScheduledJobStateChanged::FINISHED); +} + +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(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(util::UID_PROCESS_STATE_CHANGED); + return atom_matcher; +} + +AtomMatcher CreateWakelockStateChangedAtomMatcher(const string& name, + WakelockStateChanged::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(util::WAKELOCK_STATE_CHANGED); + auto field_value_matcher = simple_atom_matcher->add_field_value_matcher(); + field_value_matcher->set_field(4); // State field. + field_value_matcher->set_eq_int(state); + return atom_matcher; +} + +AtomMatcher CreateAcquireWakelockAtomMatcher() { + return CreateWakelockStateChangedAtomMatcher("AcquireWakelock", WakelockStateChanged::ACQUIRE); +} + +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(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 CreateBatteryStateChangedAtomMatcher(const string& name, + BatteryPluggedStateEnum 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(util::PLUGGED_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 CreateBatteryStateNoneMatcher() { + return CreateBatteryStateChangedAtomMatcher("BatteryPluggedNone", + BatteryPluggedStateEnum::BATTERY_PLUGGED_NONE); +} + +AtomMatcher CreateBatteryStateUsbMatcher() { + return CreateBatteryStateChangedAtomMatcher("BatteryPluggedUsb", + BatteryPluggedStateEnum::BATTERY_PLUGGED_USB); +} + +AtomMatcher CreateScreenStateChangedAtomMatcher( + const string& name, android::view::DisplayStateEnum 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(util::SCREEN_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 CreateScreenTurnedOnAtomMatcher() { + return CreateScreenStateChangedAtomMatcher("ScreenTurnedOn", + android::view::DisplayStateEnum::DISPLAY_STATE_ON); +} + +AtomMatcher CreateScreenTurnedOffAtomMatcher() { + return CreateScreenStateChangedAtomMatcher("ScreenTurnedOff", + ::android::view::DisplayStateEnum::DISPLAY_STATE_OFF); +} + +AtomMatcher CreateSyncStateChangedAtomMatcher( + const string& name, SyncStateChanged::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(util::SYNC_STATE_CHANGED); + auto field_value_matcher = simple_atom_matcher->add_field_value_matcher(); + field_value_matcher->set_field(3); // State field. + field_value_matcher->set_eq_int(state); + return atom_matcher; +} + +AtomMatcher CreateSyncStartAtomMatcher() { + return CreateSyncStateChangedAtomMatcher("SyncStart", SyncStateChanged::ON); +} + +AtomMatcher CreateSyncEndAtomMatcher() { + return CreateSyncStateChangedAtomMatcher("SyncEnd", SyncStateChanged::OFF); +} + +AtomMatcher CreateActivityForegroundStateChangedAtomMatcher( + const string& name, ActivityForegroundStateChanged::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(util::ACTIVITY_FOREGROUND_STATE_CHANGED); + auto field_value_matcher = simple_atom_matcher->add_field_value_matcher(); + field_value_matcher->set_field(4); // Activity field. + field_value_matcher->set_eq_int(state); + return atom_matcher; +} + +AtomMatcher CreateMoveToBackgroundAtomMatcher() { + return CreateActivityForegroundStateChangedAtomMatcher( + "Background", ActivityForegroundStateChanged::BACKGROUND); +} + +AtomMatcher CreateMoveToForegroundAtomMatcher() { + return CreateActivityForegroundStateChangedAtomMatcher( + "Foreground", ActivityForegroundStateChanged::FOREGROUND); +} + +AtomMatcher CreateProcessLifeCycleStateChangedAtomMatcher( + const string& name, ProcessLifeCycleStateChanged::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(util::PROCESS_LIFE_CYCLE_STATE_CHANGED); + auto field_value_matcher = simple_atom_matcher->add_field_value_matcher(); + field_value_matcher->set_field(3); // Process state field. + field_value_matcher->set_eq_int(state); + return atom_matcher; +} + +AtomMatcher CreateProcessCrashAtomMatcher() { + return CreateProcessLifeCycleStateChangedAtomMatcher( + "Crashed", ProcessLifeCycleStateChanged::CRASHED); +} + +void addMatcherToMatcherCombination(const AtomMatcher& matcher, AtomMatcher* combinationMatcher) { + combinationMatcher->mutable_combination()->add_matcher(matcher.id()); +} + +Predicate CreateScheduledJobPredicate() { + Predicate predicate; + predicate.set_id(StringToId("ScheduledJobRunningPredicate")); + predicate.mutable_simple_predicate()->set_start(StringToId("ScheduledJobStart")); + predicate.mutable_simple_predicate()->set_stop(StringToId("ScheduledJobFinish")); + return predicate; +} + +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 CreateDeviceUnpluggedPredicate() { + Predicate predicate; + predicate.set_id(StringToId("DeviceUnplugged")); + predicate.mutable_simple_predicate()->set_start(StringToId("BatteryPluggedNone")); + predicate.mutable_simple_predicate()->set_stop(StringToId("BatteryPluggedUsb")); + return predicate; +} + +Predicate CreateScreenIsOnPredicate() { + Predicate predicate; + predicate.set_id(StringToId("ScreenIsOn")); + predicate.mutable_simple_predicate()->set_start(StringToId("ScreenTurnedOn")); + predicate.mutable_simple_predicate()->set_stop(StringToId("ScreenTurnedOff")); + return predicate; +} + +Predicate CreateScreenIsOffPredicate() { + Predicate predicate; + predicate.set_id(1111123); + predicate.mutable_simple_predicate()->set_start(StringToId("ScreenTurnedOff")); + predicate.mutable_simple_predicate()->set_stop(StringToId("ScreenTurnedOn")); + return predicate; +} + +Predicate CreateHoldingWakelockPredicate() { + Predicate predicate; + predicate.set_id(StringToId("HoldingWakelock")); + predicate.mutable_simple_predicate()->set_start(StringToId("AcquireWakelock")); + predicate.mutable_simple_predicate()->set_stop(StringToId("ReleaseWakelock")); + return predicate; +} + +Predicate CreateIsSyncingPredicate() { + Predicate predicate; + predicate.set_id(33333333333333); + predicate.mutable_simple_predicate()->set_start(StringToId("SyncStart")); + predicate.mutable_simple_predicate()->set_stop(StringToId("SyncEnd")); + return predicate; +} + +Predicate CreateIsInBackgroundPredicate() { + Predicate predicate; + predicate.set_id(StringToId("IsInBackground")); + predicate.mutable_simple_predicate()->set_start(StringToId("Background")); + predicate.mutable_simple_predicate()->set_stop(StringToId("Foreground")); + return predicate; +} + +State CreateScreenState() { + State state; + state.set_id(StringToId("ScreenState")); + state.set_atom_id(util::SCREEN_STATE_CHANGED); + return state; +} + +State CreateUidProcessState() { + State state; + state.set_id(StringToId("UidProcessState")); + state.set_atom_id(util::UID_PROCESS_STATE_CHANGED); + return state; +} + +State CreateOverlayState() { + State state; + state.set_id(StringToId("OverlayState")); + state.set_atom_id(util::OVERLAY_STATE_CHANGED); + return state; +} + +State CreateScreenStateWithOnOffMap(int64_t screenOnId, int64_t screenOffId) { + State state; + state.set_id(StringToId("ScreenStateOnOff")); + state.set_atom_id(util::SCREEN_STATE_CHANGED); + + auto map = CreateScreenStateOnOffMap(screenOnId, screenOffId); + *state.mutable_map() = map; + + return state; +} + +State CreateScreenStateWithSimpleOnOffMap(int64_t screenOnId, int64_t screenOffId) { + State state; + state.set_id(StringToId("ScreenStateSimpleOnOff")); + state.set_atom_id(util::SCREEN_STATE_CHANGED); + + auto map = CreateScreenStateSimpleOnOffMap(screenOnId, screenOffId); + *state.mutable_map() = map; + + return state; +} + +StateMap_StateGroup CreateScreenStateOnGroup(int64_t screenOnId) { + StateMap_StateGroup group; + group.set_group_id(screenOnId); + group.add_value(android::view::DisplayStateEnum::DISPLAY_STATE_ON); + group.add_value(android::view::DisplayStateEnum::DISPLAY_STATE_VR); + group.add_value(android::view::DisplayStateEnum::DISPLAY_STATE_ON_SUSPEND); + return group; +} + +StateMap_StateGroup CreateScreenStateOffGroup(int64_t screenOffId) { + StateMap_StateGroup group; + group.set_group_id(screenOffId); + group.add_value(android::view::DisplayStateEnum::DISPLAY_STATE_OFF); + group.add_value(android::view::DisplayStateEnum::DISPLAY_STATE_DOZE); + group.add_value(android::view::DisplayStateEnum::DISPLAY_STATE_DOZE_SUSPEND); + return group; +} + +StateMap_StateGroup CreateScreenStateSimpleOnGroup(int64_t screenOnId) { + StateMap_StateGroup group; + group.set_group_id(screenOnId); + group.add_value(android::view::DisplayStateEnum::DISPLAY_STATE_ON); + return group; +} + +StateMap_StateGroup CreateScreenStateSimpleOffGroup(int64_t screenOffId) { + StateMap_StateGroup group; + group.set_group_id(screenOffId); + group.add_value(android::view::DisplayStateEnum::DISPLAY_STATE_OFF); + return group; +} + +StateMap CreateScreenStateOnOffMap(int64_t screenOnId, int64_t screenOffId) { + StateMap map; + *map.add_group() = CreateScreenStateOnGroup(screenOnId); + *map.add_group() = CreateScreenStateOffGroup(screenOffId); + return map; +} + +StateMap CreateScreenStateSimpleOnOffMap(int64_t screenOnId, int64_t screenOffId) { + StateMap map; + *map.add_group() = CreateScreenStateSimpleOnGroup(screenOnId); + *map.add_group() = CreateScreenStateSimpleOffGroup(screenOffId); + return map; +} + +void addPredicateToPredicateCombination(const Predicate& predicate, + Predicate* combinationPredicate) { + combinationPredicate->mutable_combination()->add_predicate(predicate.id()); +} + +FieldMatcher CreateAttributionUidDimensions(const int atomId, + const std::vector& positions) { + FieldMatcher dimensions; + dimensions.set_field(atomId); + for (const auto position : positions) { + auto child = dimensions.add_child(); + child->set_field(1); + child->set_position(position); + child->add_child()->set_field(1); + } + return dimensions; +} + +FieldMatcher CreateAttributionUidAndTagDimensions(const int atomId, + const std::vector& positions) { + FieldMatcher dimensions; + dimensions.set_field(atomId); + for (const auto position : positions) { + auto child = dimensions.add_child(); + child->set_field(1); + child->set_position(position); + child->add_child()->set_field(1); + child->add_child()->set_field(2); + } + return dimensions; +} + +FieldMatcher CreateDimensions(const int atomId, const std::vector& fields) { + FieldMatcher dimensions; + dimensions.set_field(atomId); + for (const int field : fields) { + dimensions.add_child()->set_field(field); + } + return dimensions; +} + +FieldMatcher CreateAttributionUidAndOtherDimensions(const int atomId, + const std::vector& positions, + const std::vector& fields) { + FieldMatcher dimensions = CreateAttributionUidDimensions(atomId, positions); + + for (const int field : fields) { + dimensions.add_child()->set_field(field); + } + return dimensions; +} + +EventMetric createEventMetric(const string& name, const int64_t what, + const optional& condition) { + EventMetric metric; + metric.set_id(StringToId(name)); + metric.set_what(what); + if (condition) { + metric.set_condition(condition.value()); + } + return metric; +} + +CountMetric createCountMetric(const string& name, const int64_t what, + const optional& condition, const vector& states) { + CountMetric metric; + metric.set_id(StringToId(name)); + metric.set_what(what); + metric.set_bucket(TEN_MINUTES); + if (condition) { + metric.set_condition(condition.value()); + } + for (const int64_t state : states) { + metric.add_slice_by_state(state); + } + return metric; +} + +DurationMetric createDurationMetric(const string& name, const int64_t what, + const optional& condition, + const vector& states) { + DurationMetric metric; + metric.set_id(StringToId(name)); + metric.set_what(what); + metric.set_bucket(TEN_MINUTES); + if (condition) { + metric.set_condition(condition.value()); + } + for (const int64_t state : states) { + metric.add_slice_by_state(state); + } + return metric; +} + +GaugeMetric createGaugeMetric(const string& name, const int64_t what, + const GaugeMetric::SamplingType samplingType, + const optional& condition, + const optional& triggerEvent) { + GaugeMetric metric; + metric.set_id(StringToId(name)); + metric.set_what(what); + metric.set_bucket(TEN_MINUTES); + metric.set_sampling_type(samplingType); + if (condition) { + metric.set_condition(condition.value()); + } + if (triggerEvent) { + metric.set_trigger_event(triggerEvent.value()); + } + metric.mutable_gauge_fields_filter()->set_include_all(true); + return metric; +} + +ValueMetric createValueMetric(const string& name, const AtomMatcher& what, const int valueField, + const optional& condition, const vector& states) { + ValueMetric metric; + metric.set_id(StringToId(name)); + metric.set_what(what.id()); + metric.set_bucket(TEN_MINUTES); + metric.mutable_value_field()->set_field(what.simple_atom_matcher().atom_id()); + metric.mutable_value_field()->add_child()->set_field(valueField); + if (condition) { + metric.set_condition(condition.value()); + } + for (const int64_t state : states) { + metric.add_slice_by_state(state); + } + return metric; +} + +Alert createAlert(const string& name, const int64_t metricId, const int buckets, + const int64_t triggerSum) { + Alert alert; + alert.set_id(StringToId(name)); + alert.set_metric_id(metricId); + alert.set_num_buckets(buckets); + alert.set_trigger_if_sum_gt(triggerSum); + return alert; +} + +Alarm createAlarm(const string& name, const int64_t offsetMillis, const int64_t periodMillis) { + Alarm alarm; + alarm.set_id(StringToId(name)); + alarm.set_offset_millis(offsetMillis); + alarm.set_period_millis(periodMillis); + return alarm; +} + +Subscription createSubscription(const string& name, const Subscription_RuleType type, + const int64_t ruleId) { + Subscription subscription; + subscription.set_id(StringToId(name)); + subscription.set_rule_type(type); + subscription.set_rule_id(ruleId); + subscription.mutable_broadcast_subscriber_details(); + return subscription; +} + +// START: get primary key functions +void getUidProcessKey(int uid, HashableDimensionKey* key) { + int pos1[] = {1, 0, 0}; + Field field1(27 /* atom id */, pos1, 0 /* depth */); + Value value1((int32_t)uid); + + key->addValue(FieldValue(field1, value1)); +} + +void getOverlayKey(int uid, string packageName, HashableDimensionKey* key) { + int pos1[] = {1, 0, 0}; + int pos2[] = {2, 0, 0}; + + Field field1(59 /* atom id */, pos1, 0 /* depth */); + Field field2(59 /* atom id */, pos2, 0 /* depth */); + + Value value1((int32_t)uid); + Value value2(packageName); + + key->addValue(FieldValue(field1, value1)); + key->addValue(FieldValue(field2, value2)); +} + +void getPartialWakelockKey(int uid, const std::string& tag, HashableDimensionKey* key) { + int pos1[] = {1, 1, 1}; + int pos3[] = {2, 0, 0}; + int pos4[] = {3, 0, 0}; + + Field field1(10 /* atom id */, pos1, 2 /* depth */); + + Field field3(10 /* atom id */, pos3, 0 /* depth */); + Field field4(10 /* atom id */, pos4, 0 /* depth */); + + Value value1((int32_t)uid); + Value value3((int32_t)1 /*partial*/); + Value value4(tag); + + key->addValue(FieldValue(field1, value1)); + key->addValue(FieldValue(field3, value3)); + key->addValue(FieldValue(field4, value4)); +} + +void getPartialWakelockKey(int uid, HashableDimensionKey* key) { + int pos1[] = {1, 1, 1}; + int pos3[] = {2, 0, 0}; + + Field field1(10 /* atom id */, pos1, 2 /* depth */); + Field field3(10 /* atom id */, pos3, 0 /* depth */); + + Value value1((int32_t)uid); + Value value3((int32_t)1 /*partial*/); + + key->addValue(FieldValue(field1, value1)); + key->addValue(FieldValue(field3, value3)); +} +// END: get primary key functions + +void writeAttribution(AStatsEvent* statsEvent, const vector& attributionUids, + const vector& attributionTags) { + vector cTags(attributionTags.size()); + for (int i = 0; i < cTags.size(); i++) { + cTags[i] = attributionTags[i].c_str(); + } + + AStatsEvent_writeAttributionChain(statsEvent, + reinterpret_cast(attributionUids.data()), + cTags.data(), attributionUids.size()); +} + +void parseStatsEventToLogEvent(AStatsEvent* statsEvent, LogEvent* logEvent) { + AStatsEvent_build(statsEvent); + + size_t size; + uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size); + logEvent->parseBuffer(buf, size); + + AStatsEvent_release(statsEvent); +} + +void CreateTwoValueLogEvent(LogEvent* logEvent, int atomId, int64_t eventTimeNs, int32_t value1, + int32_t value2) { + AStatsEvent* statsEvent = AStatsEvent_obtain(); + AStatsEvent_setAtomId(statsEvent, atomId); + AStatsEvent_overwriteTimestamp(statsEvent, eventTimeNs); + + AStatsEvent_writeInt32(statsEvent, value1); + AStatsEvent_writeInt32(statsEvent, value2); + + parseStatsEventToLogEvent(statsEvent, logEvent); +} + +shared_ptr CreateTwoValueLogEvent(int atomId, int64_t eventTimeNs, int32_t value1, + int32_t value2) { + shared_ptr logEvent = std::make_shared(/*uid=*/0, /*pid=*/0); + CreateTwoValueLogEvent(logEvent.get(), atomId, eventTimeNs, value1, value2); + return logEvent; +} + +void CreateThreeValueLogEvent(LogEvent* logEvent, int atomId, int64_t eventTimeNs, int32_t value1, + int32_t value2, int32_t value3) { + AStatsEvent* statsEvent = AStatsEvent_obtain(); + AStatsEvent_setAtomId(statsEvent, atomId); + AStatsEvent_overwriteTimestamp(statsEvent, eventTimeNs); + + AStatsEvent_writeInt32(statsEvent, value1); + AStatsEvent_writeInt32(statsEvent, value2); + AStatsEvent_writeInt32(statsEvent, value3); + + parseStatsEventToLogEvent(statsEvent, logEvent); +} + +shared_ptr CreateThreeValueLogEvent(int atomId, int64_t eventTimeNs, int32_t value1, + int32_t value2, int32_t value3) { + shared_ptr logEvent = std::make_shared(/*uid=*/0, /*pid=*/0); + CreateThreeValueLogEvent(logEvent.get(), atomId, eventTimeNs, value1, value2, value3); + return logEvent; +} + +void CreateRepeatedValueLogEvent(LogEvent* logEvent, int atomId, int64_t eventTimeNs, + int32_t value) { + AStatsEvent* statsEvent = AStatsEvent_obtain(); + AStatsEvent_setAtomId(statsEvent, atomId); + AStatsEvent_overwriteTimestamp(statsEvent, eventTimeNs); + + AStatsEvent_writeInt32(statsEvent, value); + AStatsEvent_writeInt32(statsEvent, value); + + parseStatsEventToLogEvent(statsEvent, logEvent); +} + +shared_ptr CreateRepeatedValueLogEvent(int atomId, int64_t eventTimeNs, int32_t value) { + shared_ptr logEvent = std::make_shared(/*uid=*/0, /*pid=*/0); + CreateRepeatedValueLogEvent(logEvent.get(), atomId, eventTimeNs, value); + return logEvent; +} + +void CreateNoValuesLogEvent(LogEvent* logEvent, int atomId, int64_t eventTimeNs) { + AStatsEvent* statsEvent = AStatsEvent_obtain(); + AStatsEvent_setAtomId(statsEvent, atomId); + AStatsEvent_overwriteTimestamp(statsEvent, eventTimeNs); + + parseStatsEventToLogEvent(statsEvent, logEvent); +} + +shared_ptr CreateNoValuesLogEvent(int atomId, int64_t eventTimeNs) { + shared_ptr logEvent = std::make_shared(/*uid=*/0, /*pid=*/0); + CreateNoValuesLogEvent(logEvent.get(), atomId, eventTimeNs); + return logEvent; +} + +shared_ptr makeUidLogEvent(int atomId, int64_t eventTimeNs, int uid, int data1, + int data2) { + AStatsEvent* statsEvent = AStatsEvent_obtain(); + AStatsEvent_setAtomId(statsEvent, atomId); + AStatsEvent_overwriteTimestamp(statsEvent, eventTimeNs); + + AStatsEvent_writeInt32(statsEvent, uid); + AStatsEvent_addBoolAnnotation(statsEvent, ANNOTATION_ID_IS_UID, true); + AStatsEvent_writeInt32(statsEvent, data1); + AStatsEvent_writeInt32(statsEvent, data2); + + shared_ptr logEvent = std::make_shared(/*uid=*/0, /*pid=*/0); + parseStatsEventToLogEvent(statsEvent, logEvent.get()); + return logEvent; +} + +shared_ptr makeAttributionLogEvent(int atomId, int64_t eventTimeNs, + const vector& uids, const vector& tags, + int data1, int data2) { + AStatsEvent* statsEvent = AStatsEvent_obtain(); + AStatsEvent_setAtomId(statsEvent, atomId); + AStatsEvent_overwriteTimestamp(statsEvent, eventTimeNs); + + writeAttribution(statsEvent, uids, tags); + AStatsEvent_writeInt32(statsEvent, data1); + AStatsEvent_writeInt32(statsEvent, data2); + + shared_ptr logEvent = std::make_shared(/*uid=*/0, /*pid=*/0); + parseStatsEventToLogEvent(statsEvent, logEvent.get()); + return logEvent; +} + +sp makeMockUidMapForOneHost(int hostUid, const vector& isolatedUids) { + sp uidMap = new NaggyMock(); + EXPECT_CALL(*uidMap, getHostUidOrSelf(_)).WillRepeatedly(ReturnArg<0>()); + for (const int isolatedUid : isolatedUids) { + EXPECT_CALL(*uidMap, getHostUidOrSelf(isolatedUid)).WillRepeatedly(Return(hostUid)); + } + + return uidMap; +} + +sp makeMockUidMapForPackage(const string& pkg, const set& uids) { + sp uidMap = new StrictMock(); + EXPECT_CALL(*uidMap, getAppUid(_)).Times(AnyNumber()); + EXPECT_CALL(*uidMap, getAppUid(pkg)).WillRepeatedly(Return(uids)); + + return uidMap; +} + +std::unique_ptr CreateScreenStateChangedEvent(uint64_t timestampNs, + const android::view::DisplayStateEnum state, + int loggerUid) { + AStatsEvent* statsEvent = AStatsEvent_obtain(); + AStatsEvent_setAtomId(statsEvent, util::SCREEN_STATE_CHANGED); + AStatsEvent_overwriteTimestamp(statsEvent, timestampNs); + AStatsEvent_writeInt32(statsEvent, state); + AStatsEvent_addBoolAnnotation(statsEvent, util::ANNOTATION_ID_EXCLUSIVE_STATE, true); + AStatsEvent_addBoolAnnotation(statsEvent, util::ANNOTATION_ID_STATE_NESTED, false); + + std::unique_ptr logEvent = std::make_unique(loggerUid, /*pid=*/0); + parseStatsEventToLogEvent(statsEvent, logEvent.get()); + return logEvent; +} + +std::unique_ptr CreateBatterySaverOnEvent(uint64_t timestampNs) { + AStatsEvent* statsEvent = AStatsEvent_obtain(); + AStatsEvent_setAtomId(statsEvent, util::BATTERY_SAVER_MODE_STATE_CHANGED); + AStatsEvent_overwriteTimestamp(statsEvent, timestampNs); + AStatsEvent_writeInt32(statsEvent, BatterySaverModeStateChanged::ON); + AStatsEvent_addBoolAnnotation(statsEvent, util::ANNOTATION_ID_EXCLUSIVE_STATE, true); + AStatsEvent_addBoolAnnotation(statsEvent, util::ANNOTATION_ID_STATE_NESTED, false); + + std::unique_ptr logEvent = std::make_unique(/*uid=*/0, /*pid=*/0); + parseStatsEventToLogEvent(statsEvent, logEvent.get()); + return logEvent; +} + +std::unique_ptr CreateBatterySaverOffEvent(uint64_t timestampNs) { + AStatsEvent* statsEvent = AStatsEvent_obtain(); + AStatsEvent_setAtomId(statsEvent, util::BATTERY_SAVER_MODE_STATE_CHANGED); + AStatsEvent_overwriteTimestamp(statsEvent, timestampNs); + AStatsEvent_writeInt32(statsEvent, BatterySaverModeStateChanged::OFF); + AStatsEvent_addBoolAnnotation(statsEvent, util::ANNOTATION_ID_EXCLUSIVE_STATE, true); + AStatsEvent_addBoolAnnotation(statsEvent, util::ANNOTATION_ID_STATE_NESTED, false); + + std::unique_ptr logEvent = std::make_unique(/*uid=*/0, /*pid=*/0); + parseStatsEventToLogEvent(statsEvent, logEvent.get()); + return logEvent; +} + +std::unique_ptr CreateBatteryStateChangedEvent(const uint64_t timestampNs, const BatteryPluggedStateEnum state) { + AStatsEvent* statsEvent = AStatsEvent_obtain(); + AStatsEvent_setAtomId(statsEvent, util::PLUGGED_STATE_CHANGED); + AStatsEvent_overwriteTimestamp(statsEvent, timestampNs); + AStatsEvent_writeInt32(statsEvent, state); + + std::unique_ptr logEvent = std::make_unique(/*uid=*/0, /*pid=*/0); + parseStatsEventToLogEvent(statsEvent, logEvent.get()); + return logEvent; +} + +std::unique_ptr CreateScreenBrightnessChangedEvent(uint64_t timestampNs, int level) { + AStatsEvent* statsEvent = AStatsEvent_obtain(); + AStatsEvent_setAtomId(statsEvent, util::SCREEN_BRIGHTNESS_CHANGED); + AStatsEvent_overwriteTimestamp(statsEvent, timestampNs); + AStatsEvent_writeInt32(statsEvent, level); + + std::unique_ptr logEvent = std::make_unique(/*uid=*/0, /*pid=*/0); + parseStatsEventToLogEvent(statsEvent, logEvent.get()); + return logEvent; +} + +std::unique_ptr CreateScheduledJobStateChangedEvent( + const vector& attributionUids, const vector& attributionTags, + const string& jobName, const ScheduledJobStateChanged::State state, uint64_t timestampNs) { + AStatsEvent* statsEvent = AStatsEvent_obtain(); + AStatsEvent_setAtomId(statsEvent, util::SCHEDULED_JOB_STATE_CHANGED); + AStatsEvent_overwriteTimestamp(statsEvent, timestampNs); + + writeAttribution(statsEvent, attributionUids, attributionTags); + AStatsEvent_writeString(statsEvent, jobName.c_str()); + AStatsEvent_writeInt32(statsEvent, state); + + std::unique_ptr logEvent = std::make_unique(/*uid=*/0, /*pid=*/0); + parseStatsEventToLogEvent(statsEvent, logEvent.get()); + return logEvent; +} + +std::unique_ptr CreateStartScheduledJobEvent(uint64_t timestampNs, + const vector& attributionUids, + const vector& attributionTags, + const string& jobName) { + return CreateScheduledJobStateChangedEvent(attributionUids, attributionTags, jobName, + ScheduledJobStateChanged::STARTED, timestampNs); +} + +// Create log event when scheduled job finishes. +std::unique_ptr CreateFinishScheduledJobEvent(uint64_t timestampNs, + const vector& attributionUids, + const vector& attributionTags, + const string& jobName) { + return CreateScheduledJobStateChangedEvent(attributionUids, attributionTags, jobName, + ScheduledJobStateChanged::FINISHED, timestampNs); +} + +std::unique_ptr CreateWakelockStateChangedEvent(uint64_t timestampNs, + const vector& attributionUids, + const vector& attributionTags, + const string& wakelockName, + const WakelockStateChanged::State state) { + AStatsEvent* statsEvent = AStatsEvent_obtain(); + AStatsEvent_setAtomId(statsEvent, util::WAKELOCK_STATE_CHANGED); + AStatsEvent_overwriteTimestamp(statsEvent, timestampNs); + + writeAttribution(statsEvent, attributionUids, attributionTags); + AStatsEvent_addBoolAnnotation(statsEvent, util::ANNOTATION_ID_PRIMARY_FIELD_FIRST_UID, true); + AStatsEvent_writeInt32(statsEvent, android::os::WakeLockLevelEnum::PARTIAL_WAKE_LOCK); + AStatsEvent_addBoolAnnotation(statsEvent, util::ANNOTATION_ID_PRIMARY_FIELD, true); + AStatsEvent_writeString(statsEvent, wakelockName.c_str()); + AStatsEvent_addBoolAnnotation(statsEvent, util::ANNOTATION_ID_PRIMARY_FIELD, true); + AStatsEvent_writeInt32(statsEvent, state); + AStatsEvent_addBoolAnnotation(statsEvent, util::ANNOTATION_ID_EXCLUSIVE_STATE, true); + AStatsEvent_addBoolAnnotation(statsEvent, util::ANNOTATION_ID_STATE_NESTED, true); + + std::unique_ptr logEvent = std::make_unique(/*uid=*/0, /*pid=*/0); + parseStatsEventToLogEvent(statsEvent, logEvent.get()); + return logEvent; +} + +std::unique_ptr CreateAcquireWakelockEvent(uint64_t timestampNs, + const vector& attributionUids, + const vector& attributionTags, + const string& wakelockName) { + return CreateWakelockStateChangedEvent(timestampNs, attributionUids, attributionTags, + wakelockName, WakelockStateChanged::ACQUIRE); +} + +std::unique_ptr CreateReleaseWakelockEvent(uint64_t timestampNs, + const vector& attributionUids, + const vector& attributionTags, + const string& wakelockName) { + return CreateWakelockStateChangedEvent(timestampNs, attributionUids, attributionTags, + wakelockName, WakelockStateChanged::RELEASE); +} + +std::unique_ptr CreateActivityForegroundStateChangedEvent( + uint64_t timestampNs, const int uid, const ActivityForegroundStateChanged::State state) { + AStatsEvent* statsEvent = AStatsEvent_obtain(); + AStatsEvent_setAtomId(statsEvent, util::ACTIVITY_FOREGROUND_STATE_CHANGED); + AStatsEvent_overwriteTimestamp(statsEvent, timestampNs); + + AStatsEvent_writeInt32(statsEvent, uid); + AStatsEvent_writeString(statsEvent, "pkg_name"); + AStatsEvent_writeString(statsEvent, "class_name"); + AStatsEvent_writeInt32(statsEvent, state); + + std::unique_ptr logEvent = std::make_unique(/*uid=*/0, /*pid=*/0); + parseStatsEventToLogEvent(statsEvent, logEvent.get()); + return logEvent; +} + +std::unique_ptr CreateMoveToBackgroundEvent(uint64_t timestampNs, const int uid) { + return CreateActivityForegroundStateChangedEvent(timestampNs, uid, + ActivityForegroundStateChanged::BACKGROUND); +} + +std::unique_ptr CreateMoveToForegroundEvent(uint64_t timestampNs, const int uid) { + return CreateActivityForegroundStateChangedEvent(timestampNs, uid, + ActivityForegroundStateChanged::FOREGROUND); +} + +std::unique_ptr CreateSyncStateChangedEvent(uint64_t timestampNs, + const vector& attributionUids, + const vector& attributionTags, + const string& name, + const SyncStateChanged::State state) { + AStatsEvent* statsEvent = AStatsEvent_obtain(); + AStatsEvent_setAtomId(statsEvent, util::SYNC_STATE_CHANGED); + AStatsEvent_overwriteTimestamp(statsEvent, timestampNs); + + writeAttribution(statsEvent, attributionUids, attributionTags); + AStatsEvent_writeString(statsEvent, name.c_str()); + AStatsEvent_writeInt32(statsEvent, state); + + std::unique_ptr logEvent = std::make_unique(/*uid=*/0, /*pid=*/0); + parseStatsEventToLogEvent(statsEvent, logEvent.get()); + return logEvent; +} + +std::unique_ptr CreateSyncStartEvent(uint64_t timestampNs, + const vector& attributionUids, + const vector& attributionTags, + const string& name) { + return CreateSyncStateChangedEvent(timestampNs, attributionUids, attributionTags, name, + SyncStateChanged::ON); +} + +std::unique_ptr CreateSyncEndEvent(uint64_t timestampNs, + const vector& attributionUids, + const vector& attributionTags, + const string& name) { + return CreateSyncStateChangedEvent(timestampNs, attributionUids, attributionTags, name, + SyncStateChanged::OFF); +} + +std::unique_ptr CreateProcessLifeCycleStateChangedEvent( + uint64_t timestampNs, const int uid, const ProcessLifeCycleStateChanged::State state) { + AStatsEvent* statsEvent = AStatsEvent_obtain(); + AStatsEvent_setAtomId(statsEvent, util::PROCESS_LIFE_CYCLE_STATE_CHANGED); + AStatsEvent_overwriteTimestamp(statsEvent, timestampNs); + + AStatsEvent_writeInt32(statsEvent, uid); + AStatsEvent_writeString(statsEvent, ""); + AStatsEvent_writeInt32(statsEvent, state); + + std::unique_ptr logEvent = std::make_unique(/*uid=*/0, /*pid=*/0); + parseStatsEventToLogEvent(statsEvent, logEvent.get()); + return logEvent; +} + +std::unique_ptr CreateAppCrashEvent(uint64_t timestampNs, const int uid) { + return CreateProcessLifeCycleStateChangedEvent(timestampNs, uid, + ProcessLifeCycleStateChanged::CRASHED); +} + +std::unique_ptr CreateAppCrashOccurredEvent(uint64_t timestampNs, const int uid) { + AStatsEvent* statsEvent = AStatsEvent_obtain(); + AStatsEvent_setAtomId(statsEvent, util::APP_CRASH_OCCURRED); + AStatsEvent_overwriteTimestamp(statsEvent, timestampNs); + + AStatsEvent_writeInt32(statsEvent, uid); + AStatsEvent_writeString(statsEvent, "eventType"); + AStatsEvent_writeString(statsEvent, "processName"); + + std::unique_ptr logEvent = std::make_unique(/*uid=*/0, /*pid=*/0); + parseStatsEventToLogEvent(statsEvent, logEvent.get()); + return logEvent; +} + +std::unique_ptr CreateIsolatedUidChangedEvent(uint64_t timestampNs, int hostUid, + int isolatedUid, bool is_create) { + AStatsEvent* statsEvent = AStatsEvent_obtain(); + AStatsEvent_setAtomId(statsEvent, util::ISOLATED_UID_CHANGED); + AStatsEvent_overwriteTimestamp(statsEvent, timestampNs); + + AStatsEvent_writeInt32(statsEvent, hostUid); + AStatsEvent_writeInt32(statsEvent, isolatedUid); + AStatsEvent_writeInt32(statsEvent, is_create); + + std::unique_ptr logEvent = std::make_unique(/*uid=*/0, /*pid=*/0); + parseStatsEventToLogEvent(statsEvent, logEvent.get()); + return logEvent; +} + +std::unique_ptr CreateUidProcessStateChangedEvent( + uint64_t timestampNs, int uid, const android::app::ProcessStateEnum state) { + AStatsEvent* statsEvent = AStatsEvent_obtain(); + AStatsEvent_setAtomId(statsEvent, util::UID_PROCESS_STATE_CHANGED); + AStatsEvent_overwriteTimestamp(statsEvent, timestampNs); + + AStatsEvent_writeInt32(statsEvent, uid); + AStatsEvent_addBoolAnnotation(statsEvent, util::ANNOTATION_ID_IS_UID, true); + AStatsEvent_addBoolAnnotation(statsEvent, util::ANNOTATION_ID_PRIMARY_FIELD, true); + AStatsEvent_writeInt32(statsEvent, state); + AStatsEvent_addBoolAnnotation(statsEvent, util::ANNOTATION_ID_EXCLUSIVE_STATE, true); + AStatsEvent_addBoolAnnotation(statsEvent, util::ANNOTATION_ID_STATE_NESTED, false); + + std::unique_ptr logEvent = std::make_unique(/*uid=*/0, /*pid=*/0); + parseStatsEventToLogEvent(statsEvent, logEvent.get()); + return logEvent; +} + +std::unique_ptr CreateBleScanStateChangedEvent(uint64_t timestampNs, + const vector& attributionUids, + const vector& attributionTags, + const BleScanStateChanged::State state, + const bool filtered, const bool firstMatch, + const bool opportunistic) { + AStatsEvent* statsEvent = AStatsEvent_obtain(); + AStatsEvent_setAtomId(statsEvent, util::BLE_SCAN_STATE_CHANGED); + AStatsEvent_overwriteTimestamp(statsEvent, timestampNs); + + writeAttribution(statsEvent, attributionUids, attributionTags); + AStatsEvent_addBoolAnnotation(statsEvent, util::ANNOTATION_ID_PRIMARY_FIELD_FIRST_UID, true); + AStatsEvent_writeInt32(statsEvent, state); + AStatsEvent_addBoolAnnotation(statsEvent, util::ANNOTATION_ID_EXCLUSIVE_STATE, true); + AStatsEvent_addBoolAnnotation(statsEvent, util::ANNOTATION_ID_STATE_NESTED, true); + if (state == util::BLE_SCAN_STATE_CHANGED__STATE__RESET) { + AStatsEvent_addInt32Annotation(statsEvent, ANNOTATION_ID_TRIGGER_STATE_RESET, + util::BLE_SCAN_STATE_CHANGED__STATE__OFF); + } + AStatsEvent_writeBool(statsEvent, filtered); // filtered + AStatsEvent_addBoolAnnotation(statsEvent, util::ANNOTATION_ID_PRIMARY_FIELD, true); + AStatsEvent_writeBool(statsEvent, firstMatch); // first match + AStatsEvent_addBoolAnnotation(statsEvent, util::ANNOTATION_ID_PRIMARY_FIELD, true); + AStatsEvent_writeBool(statsEvent, opportunistic); // opportunistic + AStatsEvent_addBoolAnnotation(statsEvent, util::ANNOTATION_ID_PRIMARY_FIELD, true); + + std::unique_ptr logEvent = std::make_unique(/*uid=*/0, /*pid=*/0); + parseStatsEventToLogEvent(statsEvent, logEvent.get()); + return logEvent; +} + +std::unique_ptr CreateOverlayStateChangedEvent(int64_t timestampNs, const int32_t uid, + const string& packageName, + const bool usingAlertWindow, + const OverlayStateChanged::State state) { + AStatsEvent* statsEvent = AStatsEvent_obtain(); + AStatsEvent_setAtomId(statsEvent, util::OVERLAY_STATE_CHANGED); + AStatsEvent_overwriteTimestamp(statsEvent, timestampNs); + + AStatsEvent_writeInt32(statsEvent, uid); + AStatsEvent_addBoolAnnotation(statsEvent, util::ANNOTATION_ID_IS_UID, true); + AStatsEvent_addBoolAnnotation(statsEvent, util::ANNOTATION_ID_PRIMARY_FIELD, true); + AStatsEvent_writeString(statsEvent, packageName.c_str()); + AStatsEvent_addBoolAnnotation(statsEvent, util::ANNOTATION_ID_PRIMARY_FIELD, true); + AStatsEvent_writeBool(statsEvent, usingAlertWindow); + AStatsEvent_writeInt32(statsEvent, state); + AStatsEvent_addBoolAnnotation(statsEvent, util::ANNOTATION_ID_EXCLUSIVE_STATE, true); + AStatsEvent_addBoolAnnotation(statsEvent, util::ANNOTATION_ID_STATE_NESTED, false); + + std::unique_ptr logEvent = std::make_unique(/*uid=*/0, /*pid=*/0); + parseStatsEventToLogEvent(statsEvent, logEvent.get()); + return logEvent; +} + +std::unique_ptr CreateAppStartOccurredEvent( + uint64_t timestampNs, const int uid, const string& pkgName, + AppStartOccurred::TransitionType type, const string& activityName, + const string& callingPkgName, const bool isInstantApp, int64_t activityStartMs) { + AStatsEvent* statsEvent = AStatsEvent_obtain(); + AStatsEvent_setAtomId(statsEvent, util::APP_START_OCCURRED); + AStatsEvent_overwriteTimestamp(statsEvent, timestampNs); + + AStatsEvent_writeInt32(statsEvent, uid); + AStatsEvent_writeString(statsEvent, pkgName.c_str()); + AStatsEvent_writeInt32(statsEvent, type); + AStatsEvent_writeString(statsEvent, activityName.c_str()); + AStatsEvent_writeString(statsEvent, callingPkgName.c_str()); + AStatsEvent_writeInt32(statsEvent, isInstantApp); + AStatsEvent_writeInt32(statsEvent, activityStartMs); + + std::unique_ptr logEvent = std::make_unique(/*uid=*/0, /*pid=*/0); + parseStatsEventToLogEvent(statsEvent, logEvent.get()); + return logEvent; +} + +sp CreateStatsLogProcessor(const int64_t timeBaseNs, const int64_t currentTimeNs, + const StatsdConfig& config, const ConfigKey& key, + const shared_ptr& puller, + const int32_t atomTag, const sp uidMap) { + sp pullerManager = new StatsPullerManager(); + if (puller != nullptr) { + pullerManager->RegisterPullAtomCallback(/*uid=*/0, atomTag, NS_PER_SEC, NS_PER_SEC * 10, {}, + puller); + } + sp anomalyAlarmMonitor = + new AlarmMonitor(1, + [](const shared_ptr&, int64_t){}, + [](const shared_ptr&){}); + sp periodicAlarmMonitor = + new AlarmMonitor(1, + [](const shared_ptr&, int64_t){}, + [](const shared_ptr&){}); + sp processor = + new StatsLogProcessor(uidMap, pullerManager, anomalyAlarmMonitor, periodicAlarmMonitor, + timeBaseNs, [](const ConfigKey&) { return true; }, + [](const int&, const vector&) {return true;}); + processor->OnConfigUpdated(currentTimeNs, key, config); + return processor; +} + +void sortLogEventsByTimestamp(std::vector> *events) { + std::sort(events->begin(), events->end(), + [](const std::unique_ptr& a, const std::unique_ptr& b) { + return a->GetElapsedTimestampNs() < b->GetElapsedTimestampNs(); + }); +} + +int64_t StringToId(const string& str) { + return static_cast(std::hash()(str)); +} + +sp createEventMatcherWizard( + int tagId, int matcherIndex, const vector& fieldValueMatchers) { + sp uidMap = new UidMap(); + SimpleAtomMatcher atomMatcher; + atomMatcher.set_atom_id(tagId); + for (const FieldValueMatcher& fvm : fieldValueMatchers) { + *atomMatcher.add_field_value_matcher() = fvm; + } + uint64_t matcherHash = 0x12345678; + int64_t matcherId = 678; + return new EventMatcherWizard({new SimpleAtomMatchingTracker( + matcherId, matcherIndex, matcherHash, atomMatcher, uidMap)}); +} + +StatsDimensionsValueParcel CreateAttributionUidDimensionsValueParcel(const int atomId, + const int uid) { + StatsDimensionsValueParcel root; + root.field = atomId; + root.valueType = STATS_DIMENSIONS_VALUE_TUPLE_TYPE; + StatsDimensionsValueParcel attrNode; + attrNode.field = 1; + attrNode.valueType = STATS_DIMENSIONS_VALUE_TUPLE_TYPE; + StatsDimensionsValueParcel posInAttrChain; + posInAttrChain.field = 1; + posInAttrChain.valueType = STATS_DIMENSIONS_VALUE_TUPLE_TYPE; + StatsDimensionsValueParcel uidNode; + uidNode.field = 1; + uidNode.valueType = STATS_DIMENSIONS_VALUE_INT_TYPE; + uidNode.intValue = uid; + posInAttrChain.tupleValue.push_back(uidNode); + attrNode.tupleValue.push_back(posInAttrChain); + root.tupleValue.push_back(attrNode); + return root; +} + +void ValidateUidDimension(const DimensionsValue& value, int atomId, int uid) { + EXPECT_EQ(value.field(), atomId); + ASSERT_EQ(value.value_tuple().dimensions_value_size(), 1); + EXPECT_EQ(value.value_tuple().dimensions_value(0).field(), 1); + EXPECT_EQ(value.value_tuple().dimensions_value(0).value_int(), uid); +} + +void ValidateWakelockAttributionUidAndTagDimension(const DimensionsValue& value, const int atomId, + const int uid, const string& tag) { + EXPECT_EQ(value.field(), atomId); + ASSERT_EQ(value.value_tuple().dimensions_value_size(), 2); + // Attribution field. + EXPECT_EQ(value.value_tuple().dimensions_value(0).field(), 1); + // Uid field. + ASSERT_EQ(value.value_tuple().dimensions_value(0).value_tuple().dimensions_value_size(), 1); + EXPECT_EQ(value.value_tuple().dimensions_value(0).value_tuple().dimensions_value(0).field(), 1); + EXPECT_EQ(value.value_tuple().dimensions_value(0).value_tuple().dimensions_value(0).value_int(), + uid); + // Tag field. + EXPECT_EQ(value.value_tuple().dimensions_value(1).field(), 3); + EXPECT_EQ(value.value_tuple().dimensions_value(1).value_str(), tag); +} + +void ValidateAttributionUidDimension(const DimensionsValue& value, int atomId, int uid) { + EXPECT_EQ(value.field(), atomId); + ASSERT_EQ(value.value_tuple().dimensions_value_size(), 1); + // Attribution field. + EXPECT_EQ(value.value_tuple().dimensions_value(0).field(), 1); + // Uid only. + EXPECT_EQ(value.value_tuple().dimensions_value(0) + .value_tuple().dimensions_value_size(), 1); + EXPECT_EQ(value.value_tuple().dimensions_value(0) + .value_tuple().dimensions_value(0).field(), 1); + EXPECT_EQ(value.value_tuple().dimensions_value(0) + .value_tuple().dimensions_value(0).value_int(), uid); +} + +void ValidateUidDimension(const DimensionsValue& value, int node_idx, int atomId, int uid) { + EXPECT_EQ(value.field(), atomId); + ASSERT_GT(value.value_tuple().dimensions_value_size(), node_idx); + // Attribution field. + EXPECT_EQ(value.value_tuple().dimensions_value(node_idx).field(), 1); + EXPECT_EQ(value.value_tuple().dimensions_value(node_idx) + .value_tuple().dimensions_value(0).field(), 1); + EXPECT_EQ(value.value_tuple().dimensions_value(node_idx) + .value_tuple().dimensions_value(0).value_int(), uid); +} + +void ValidateAttributionUidAndTagDimension( + const DimensionsValue& value, int node_idx, int atomId, int uid, const std::string& tag) { + EXPECT_EQ(value.field(), atomId); + ASSERT_GT(value.value_tuple().dimensions_value_size(), node_idx); + // Attribution field. + EXPECT_EQ(1, value.value_tuple().dimensions_value(node_idx).field()); + // Uid only. + EXPECT_EQ(2, value.value_tuple().dimensions_value(node_idx) + .value_tuple().dimensions_value_size()); + EXPECT_EQ(1, value.value_tuple().dimensions_value(node_idx) + .value_tuple().dimensions_value(0).field()); + EXPECT_EQ(uid, value.value_tuple().dimensions_value(node_idx) + .value_tuple().dimensions_value(0).value_int()); + EXPECT_EQ(2, value.value_tuple().dimensions_value(node_idx) + .value_tuple().dimensions_value(1).field()); + EXPECT_EQ(tag, value.value_tuple().dimensions_value(node_idx) + .value_tuple().dimensions_value(1).value_str()); +} + +void ValidateAttributionUidAndTagDimension( + const DimensionsValue& value, int atomId, int uid, const std::string& tag) { + EXPECT_EQ(value.field(), atomId); + ASSERT_EQ(1, value.value_tuple().dimensions_value_size()); + // Attribution field. + EXPECT_EQ(1, value.value_tuple().dimensions_value(0).field()); + // Uid only. + EXPECT_EQ(value.value_tuple().dimensions_value(0) + .value_tuple().dimensions_value_size(), 2); + EXPECT_EQ(value.value_tuple().dimensions_value(0) + .value_tuple().dimensions_value(0).field(), 1); + EXPECT_EQ(value.value_tuple().dimensions_value(0) + .value_tuple().dimensions_value(0).value_int(), uid); + EXPECT_EQ(value.value_tuple().dimensions_value(0) + .value_tuple().dimensions_value(1).field(), 2); + EXPECT_EQ(value.value_tuple().dimensions_value(0) + .value_tuple().dimensions_value(1).value_str(), tag); +} + +void ValidateStateValue(const google::protobuf::RepeatedPtrField& stateValues, + int atomId, int64_t value) { + ASSERT_EQ(stateValues.size(), 1); + ASSERT_EQ(stateValues[0].atom_id(), atomId); + switch (stateValues[0].contents_case()) { + case StateValue::ContentsCase::kValue: + EXPECT_EQ(stateValues[0].value(), (int32_t)value); + break; + case StateValue::ContentsCase::kGroupId: + EXPECT_EQ(stateValues[0].group_id(), value); + break; + default: + FAIL() << "State value should have either a value or a group id"; + } +} + +void ValidateCountBucket(const CountBucketInfo& countBucket, int64_t startTimeNs, int64_t endTimeNs, + int64_t count) { + EXPECT_EQ(countBucket.start_bucket_elapsed_nanos(), startTimeNs); + EXPECT_EQ(countBucket.end_bucket_elapsed_nanos(), endTimeNs); + EXPECT_EQ(countBucket.count(), count); +} + +void ValidateDurationBucket(const DurationBucketInfo& bucket, int64_t startTimeNs, + int64_t endTimeNs, int64_t durationNs) { + EXPECT_EQ(bucket.start_bucket_elapsed_nanos(), startTimeNs); + EXPECT_EQ(bucket.end_bucket_elapsed_nanos(), endTimeNs); + EXPECT_EQ(bucket.duration_nanos(), durationNs); +} + +void ValidateGaugeBucketTimes(const GaugeBucketInfo& gaugeBucket, int64_t startTimeNs, + int64_t endTimeNs, vector eventTimesNs) { + EXPECT_EQ(gaugeBucket.start_bucket_elapsed_nanos(), startTimeNs); + EXPECT_EQ(gaugeBucket.end_bucket_elapsed_nanos(), endTimeNs); + EXPECT_EQ(gaugeBucket.elapsed_timestamp_nanos_size(), eventTimesNs.size()); + for (int i = 0; i < eventTimesNs.size(); i++) { + EXPECT_EQ(gaugeBucket.elapsed_timestamp_nanos(i), eventTimesNs[i]); + } +} + +void ValidateValueBucket(const ValueBucketInfo& bucket, int64_t startTimeNs, int64_t endTimeNs, + int64_t value, int64_t conditionTrueNs) { + EXPECT_EQ(bucket.start_bucket_elapsed_nanos(), startTimeNs); + EXPECT_EQ(bucket.end_bucket_elapsed_nanos(), endTimeNs); + ASSERT_EQ(bucket.values_size(), 1); + if (bucket.values(0).has_value_double()) { + EXPECT_EQ((int64_t)bucket.values(0).value_double(), value); + } else { + EXPECT_EQ(bucket.values(0).value_long(), value); + } + if (conditionTrueNs > 0) { + EXPECT_EQ(bucket.condition_true_nanos(), conditionTrueNs); + } +} + +bool EqualsTo(const DimensionsValue& s1, const DimensionsValue& s2) { + if (s1.field() != s2.field()) { + return false; + } + if (s1.value_case() != s2.value_case()) { + return false; + } + switch (s1.value_case()) { + case DimensionsValue::ValueCase::kValueStr: + return (s1.value_str() == s2.value_str()); + case DimensionsValue::ValueCase::kValueInt: + return s1.value_int() == s2.value_int(); + case DimensionsValue::ValueCase::kValueLong: + return s1.value_long() == s2.value_long(); + case DimensionsValue::ValueCase::kValueBool: + return s1.value_bool() == s2.value_bool(); + case DimensionsValue::ValueCase::kValueFloat: + return s1.value_float() == s2.value_float(); + case DimensionsValue::ValueCase::kValueTuple: { + if (s1.value_tuple().dimensions_value_size() != + s2.value_tuple().dimensions_value_size()) { + return false; + } + bool allMatched = true; + for (int i = 0; allMatched && i < s1.value_tuple().dimensions_value_size(); ++i) { + allMatched &= EqualsTo(s1.value_tuple().dimensions_value(i), + s2.value_tuple().dimensions_value(i)); + } + return allMatched; + } + case DimensionsValue::ValueCase::VALUE_NOT_SET: + default: + return true; + } +} + +bool LessThan(const google::protobuf::RepeatedPtrField& s1, + const google::protobuf::RepeatedPtrField& s2) { + if (s1.size() != s2.size()) { + return s1.size() < s2.size(); + } + for (int i = 0; i < s1.size(); i++) { + const StateValue& state1 = s1[i]; + const StateValue& state2 = s2[i]; + if (state1.atom_id() != state2.atom_id()) { + return state1.atom_id() < state2.atom_id(); + } + if (state1.value() != state2.value()) { + return state1.value() < state2.value(); + } + if (state1.group_id() != state2.group_id()) { + return state1.group_id() < state2.group_id(); + } + } + return false; +} + +bool LessThan(const DimensionsValue& s1, const DimensionsValue& s2) { + if (s1.field() != s2.field()) { + return s1.field() < s2.field(); + } + if (s1.value_case() != s2.value_case()) { + return s1.value_case() < s2.value_case(); + } + switch (s1.value_case()) { + case DimensionsValue::ValueCase::kValueStr: + return s1.value_str() < s2.value_str(); + case DimensionsValue::ValueCase::kValueInt: + return s1.value_int() < s2.value_int(); + case DimensionsValue::ValueCase::kValueLong: + return s1.value_long() < s2.value_long(); + case DimensionsValue::ValueCase::kValueBool: + return (int)s1.value_bool() < (int)s2.value_bool(); + case DimensionsValue::ValueCase::kValueFloat: + return s1.value_float() < s2.value_float(); + case DimensionsValue::ValueCase::kValueTuple: { + if (s1.value_tuple().dimensions_value_size() != + s2.value_tuple().dimensions_value_size()) { + return s1.value_tuple().dimensions_value_size() < + s2.value_tuple().dimensions_value_size(); + } + for (int i = 0; i < s1.value_tuple().dimensions_value_size(); ++i) { + if (EqualsTo(s1.value_tuple().dimensions_value(i), + s2.value_tuple().dimensions_value(i))) { + continue; + } else { + return LessThan(s1.value_tuple().dimensions_value(i), + s2.value_tuple().dimensions_value(i)); + } + } + return false; + } + case DimensionsValue::ValueCase::VALUE_NOT_SET: + default: + return false; + } +} + +bool LessThan(const DimensionsPair& s1, const DimensionsPair& s2) { + if (LessThan(s1.dimInWhat, s2.dimInWhat)) { + return true; + } else if (LessThan(s2.dimInWhat, s1.dimInWhat)) { + return false; + } + + return LessThan(s1.stateValues, s2.stateValues); +} + +void backfillStringInDimension(const std::map& str_map, + DimensionsValue* dimension) { + if (dimension->has_value_str_hash()) { + auto it = str_map.find((uint64_t)(dimension->value_str_hash())); + if (it != str_map.end()) { + dimension->clear_value_str_hash(); + dimension->set_value_str(it->second); + } else { + ALOGE("Can not find the string hash: %llu", + (unsigned long long)dimension->value_str_hash()); + } + } else if (dimension->has_value_tuple()) { + auto value_tuple = dimension->mutable_value_tuple(); + for (int i = 0; i < value_tuple->dimensions_value_size(); ++i) { + backfillStringInDimension(str_map, value_tuple->mutable_dimensions_value(i)); + } + } +} + +void backfillStringInReport(ConfigMetricsReport *config_report) { + std::map str_map; + for (const auto& str : config_report->strings()) { + uint64_t hash = Hash64(str); + if (str_map.find(hash) != str_map.end()) { + ALOGE("String hash conflicts: %s %s", str.c_str(), str_map[hash].c_str()); + } + str_map[hash] = str; + } + for (int i = 0; i < config_report->metrics_size(); ++i) { + auto metric_report = config_report->mutable_metrics(i); + if (metric_report->has_count_metrics()) { + backfillStringInDimension(str_map, metric_report->mutable_count_metrics()); + } else if (metric_report->has_duration_metrics()) { + backfillStringInDimension(str_map, metric_report->mutable_duration_metrics()); + } else if (metric_report->has_gauge_metrics()) { + backfillStringInDimension(str_map, metric_report->mutable_gauge_metrics()); + } else if (metric_report->has_value_metrics()) { + backfillStringInDimension(str_map, metric_report->mutable_value_metrics()); + } + } + // Backfill the package names. + for (int i = 0 ; i < config_report->uid_map().snapshots_size(); ++i) { + auto snapshot = config_report->mutable_uid_map()->mutable_snapshots(i); + for (int j = 0 ; j < snapshot->package_info_size(); ++j) { + auto package_info = snapshot->mutable_package_info(j); + if (package_info->has_name_hash()) { + auto it = str_map.find((uint64_t)(package_info->name_hash())); + if (it != str_map.end()) { + package_info->clear_name_hash(); + package_info->set_name(it->second); + } else { + ALOGE("Can not find the string package name hash: %llu", + (unsigned long long)package_info->name_hash()); + } + + } + } + } + // Backfill the app name in app changes. + for (int i = 0 ; i < config_report->uid_map().changes_size(); ++i) { + auto change = config_report->mutable_uid_map()->mutable_changes(i); + if (change->has_app_hash()) { + auto it = str_map.find((uint64_t)(change->app_hash())); + if (it != str_map.end()) { + change->clear_app_hash(); + change->set_app(it->second); + } else { + ALOGE("Can not find the string change app name hash: %llu", + (unsigned long long)change->app_hash()); + } + } + } +} + +void backfillStringInReport(ConfigMetricsReportList *config_report_list) { + for (int i = 0; i < config_report_list->reports_size(); ++i) { + backfillStringInReport(config_report_list->mutable_reports(i)); + } +} + +bool backfillDimensionPath(const DimensionsValue& path, + const google::protobuf::RepeatedPtrField& leafValues, + int* leafIndex, + DimensionsValue* dimension) { + dimension->set_field(path.field()); + if (path.has_value_tuple()) { + for (int i = 0; i < path.value_tuple().dimensions_value_size(); ++i) { + if (!backfillDimensionPath( + path.value_tuple().dimensions_value(i), leafValues, leafIndex, + dimension->mutable_value_tuple()->add_dimensions_value())) { + return false; + } + } + } else { + if (*leafIndex < 0 || *leafIndex >= leafValues.size()) { + return false; + } + dimension->MergeFrom(leafValues.Get(*leafIndex)); + (*leafIndex)++; + } + return true; +} + +bool backfillDimensionPath(const DimensionsValue& path, + const google::protobuf::RepeatedPtrField& leafValues, + DimensionsValue* dimension) { + int leafIndex = 0; + return backfillDimensionPath(path, leafValues, &leafIndex, dimension); +} + +void backfillDimensionPath(ConfigMetricsReportList *config_report_list) { + for (int i = 0; i < config_report_list->reports_size(); ++i) { + auto report = config_report_list->mutable_reports(i); + for (int j = 0; j < report->metrics_size(); ++j) { + auto metric_report = report->mutable_metrics(j); + if (metric_report->has_dimensions_path_in_what() || + metric_report->has_dimensions_path_in_condition()) { + auto whatPath = metric_report->dimensions_path_in_what(); + auto conditionPath = metric_report->dimensions_path_in_condition(); + if (metric_report->has_count_metrics()) { + backfillDimensionPath(whatPath, conditionPath, + metric_report->mutable_count_metrics()); + } else if (metric_report->has_duration_metrics()) { + backfillDimensionPath(whatPath, conditionPath, + metric_report->mutable_duration_metrics()); + } else if (metric_report->has_gauge_metrics()) { + backfillDimensionPath(whatPath, conditionPath, + metric_report->mutable_gauge_metrics()); + } else if (metric_report->has_value_metrics()) { + backfillDimensionPath(whatPath, conditionPath, + metric_report->mutable_value_metrics()); + } + metric_report->clear_dimensions_path_in_what(); + metric_report->clear_dimensions_path_in_condition(); + } + } + } +} + +void backfillStartEndTimestamp(StatsLogReport *report) { + const int64_t timeBaseNs = report->time_base_elapsed_nano_seconds(); + const int64_t bucketSizeNs = report->bucket_size_nano_seconds(); + if (report->has_count_metrics()) { + backfillStartEndTimestampForMetrics( + timeBaseNs, bucketSizeNs, report->mutable_count_metrics()); + } else if (report->has_duration_metrics()) { + backfillStartEndTimestampForMetrics( + timeBaseNs, bucketSizeNs, report->mutable_duration_metrics()); + } else if (report->has_gauge_metrics()) { + backfillStartEndTimestampForMetrics( + timeBaseNs, bucketSizeNs, report->mutable_gauge_metrics()); + if (report->gauge_metrics().skipped_size() > 0) { + backfillStartEndTimestampForSkippedBuckets( + timeBaseNs, report->mutable_gauge_metrics()); + } + } else if (report->has_value_metrics()) { + backfillStartEndTimestampForMetrics( + timeBaseNs, bucketSizeNs, report->mutable_value_metrics()); + if (report->value_metrics().skipped_size() > 0) { + backfillStartEndTimestampForSkippedBuckets( + timeBaseNs, report->mutable_value_metrics()); + } + } +} + +void backfillStartEndTimestamp(ConfigMetricsReport *config_report) { + for (int j = 0; j < config_report->metrics_size(); ++j) { + backfillStartEndTimestamp(config_report->mutable_metrics(j)); + } +} + +void backfillStartEndTimestamp(ConfigMetricsReportList *config_report_list) { + for (int i = 0; i < config_report_list->reports_size(); ++i) { + backfillStartEndTimestamp(config_report_list->mutable_reports(i)); + } +} + +Status FakeSubsystemSleepCallback::onPullAtom(int atomTag, + const shared_ptr& resultReceiver) { + // Convert stats_events into StatsEventParcels. + std::vector parcels; + for (int i = 1; i < 3; i++) { + AStatsEvent* event = AStatsEvent_obtain(); + AStatsEvent_setAtomId(event, atomTag); + std::string subsystemName = "subsystem_name_"; + subsystemName = subsystemName + std::to_string(i); + AStatsEvent_writeString(event, subsystemName.c_str()); + AStatsEvent_writeString(event, "subsystem_subname foo"); + AStatsEvent_writeInt64(event, /*count= */ i); + AStatsEvent_writeInt64(event, /*time_millis= */ pullNum * pullNum * 100 + i); + AStatsEvent_build(event); + size_t size; + uint8_t* buffer = AStatsEvent_getBuffer(event, &size); + + StatsEventParcel p; + // vector.assign() creates a copy, but this is inevitable unless + // stats_event.h/c uses a vector as opposed to a buffer. + p.buffer.assign(buffer, buffer + size); + parcels.push_back(std::move(p)); + AStatsEvent_release(event); + } + pullNum++; + resultReceiver->pullFinished(atomTag, /*success=*/true, parcels); + return Status::ok(); +} + +} // namespace statsd +} // namespace os +} // namespace android diff --git a/statsd/tests/statsd_test_util.h b/statsd/tests/statsd_test_util.h new file mode 100644 index 00000000..00a246cf --- /dev/null +++ b/statsd/tests/statsd_test_util.h @@ -0,0 +1,545 @@ +// 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. + +#pragma once + +#include +#include +#include +#include +#include +#include + +#include "src/stats_log.pb.h" +#include "src/statsd_config.pb.h" +#include "src/StatsLogProcessor.h" +#include "src/hash.h" +#include "src/logd/LogEvent.h" +#include "src/matchers/EventMatcherWizard.h" +#include "src/packages/UidMap.h" +#include "src/stats_log_util.h" +#include "stats_event.h" +#include "statslog_statsdtest.h" + +namespace android { +namespace os { +namespace statsd { + +using namespace testing; +using ::aidl::android::os::BnPullAtomCallback; +using ::aidl::android::os::IPullAtomCallback; +using ::aidl::android::os::IPullAtomResultReceiver; +using android::util::ProtoReader; +using google::protobuf::RepeatedPtrField; +using Status = ::ndk::ScopedAStatus; + +const int SCREEN_STATE_ATOM_ID = util::SCREEN_STATE_CHANGED; +const int UID_PROCESS_STATE_ATOM_ID = util::UID_PROCESS_STATE_CHANGED; + +enum BucketSplitEvent { APP_UPGRADE, BOOT_COMPLETE }; + +class MockUidMap : public UidMap { +public: + MOCK_METHOD(int, getHostUidOrSelf, (int uid), (const)); + MOCK_METHOD(std::set, getAppUid, (const string& package), (const)); +}; + +class MockPendingIntentRef : public aidl::android::os::BnPendingIntentRef { +public: + MOCK_METHOD1(sendDataBroadcast, Status(int64_t lastReportTimeNs)); + MOCK_METHOD1(sendActiveConfigsChangedBroadcast, Status(const vector& configIds)); + MOCK_METHOD6(sendSubscriberBroadcast, + Status(int64_t configUid, int64_t configId, int64_t subscriptionId, + int64_t subscriptionRuleId, const vector& cookies, + const StatsDimensionsValueParcel& dimensionsValueParcel)); +}; + +// Converts a ProtoOutputStream to a StatsLogReport proto. +StatsLogReport outputStreamToProto(ProtoOutputStream* proto); + +// Create AtomMatcher proto to simply match a specific atom type. +AtomMatcher CreateSimpleAtomMatcher(const string& name, int atomId); + +// Create AtomMatcher proto for temperature atom. +AtomMatcher CreateTemperatureAtomMatcher(); + +// Create AtomMatcher proto for scheduled job state changed. +AtomMatcher CreateScheduledJobStateChangedAtomMatcher(); + +// Create AtomMatcher proto for starting a scheduled job. +AtomMatcher CreateStartScheduledJobAtomMatcher(); + +// Create AtomMatcher proto for a scheduled job is done. +AtomMatcher CreateFinishScheduledJobAtomMatcher(); + +// 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 battery state none mode. +AtomMatcher CreateBatteryStateNoneMatcher(); + +// Create AtomMatcher proto for battery state usb mode. +AtomMatcher CreateBatteryStateUsbMatcher(); + +// Create AtomMatcher proto for process state changed. +AtomMatcher CreateUidProcessStateChangedAtomMatcher(); + +// Create AtomMatcher proto for acquiring wakelock. +AtomMatcher CreateAcquireWakelockAtomMatcher(); + +// Create AtomMatcher proto for releasing wakelock. +AtomMatcher CreateReleaseWakelockAtomMatcher() ; + +// Create AtomMatcher proto for screen turned on. +AtomMatcher CreateScreenTurnedOnAtomMatcher(); + +// Create AtomMatcher proto for screen turned off. +AtomMatcher CreateScreenTurnedOffAtomMatcher(); + +// Create AtomMatcher proto for app sync turned on. +AtomMatcher CreateSyncStartAtomMatcher(); + +// Create AtomMatcher proto for app sync turned off. +AtomMatcher CreateSyncEndAtomMatcher(); + +// Create AtomMatcher proto for app sync moves to background. +AtomMatcher CreateMoveToBackgroundAtomMatcher(); + +// Create AtomMatcher proto for app sync moves to foreground. +AtomMatcher CreateMoveToForegroundAtomMatcher(); + +// Create AtomMatcher proto for process crashes +AtomMatcher CreateProcessCrashAtomMatcher() ; + +// Add an AtomMatcher to a combination AtomMatcher. +void addMatcherToMatcherCombination(const AtomMatcher& matcher, AtomMatcher* combinationMatcher); + +// Create Predicate proto for screen is on. +Predicate CreateScreenIsOnPredicate(); + +// Create Predicate proto for screen is off. +Predicate CreateScreenIsOffPredicate(); + +// Create Predicate proto for a running scheduled job. +Predicate CreateScheduledJobPredicate(); + +// Create Predicate proto for battery saver mode. +Predicate CreateBatterySaverModePredicate(); + +// Create Predicate proto for device unplogged mode. +Predicate CreateDeviceUnpluggedPredicate(); + +// Create Predicate proto for holding wakelock. +Predicate CreateHoldingWakelockPredicate(); + +// Create a Predicate proto for app syncing. +Predicate CreateIsSyncingPredicate(); + +// Create a Predicate proto for app is in background. +Predicate CreateIsInBackgroundPredicate(); + +// Create State proto for screen state atom. +State CreateScreenState(); + +// Create State proto for uid process state atom. +State CreateUidProcessState(); + +// Create State proto for overlay state atom. +State CreateOverlayState(); + +// Create State proto for screen state atom with on/off map. +State CreateScreenStateWithOnOffMap(int64_t screenOnId, int64_t screenOffId); + +// Create State proto for screen state atom with simple on/off map. +State CreateScreenStateWithSimpleOnOffMap(int64_t screenOnId, int64_t screenOffId); + +// Create StateGroup proto for ScreenState ON group +StateMap_StateGroup CreateScreenStateOnGroup(int64_t screenOnId); + +// Create StateGroup proto for ScreenState OFF group +StateMap_StateGroup CreateScreenStateOffGroup(int64_t screenOffId); + +// Create StateGroup proto for simple ScreenState ON group +StateMap_StateGroup CreateScreenStateSimpleOnGroup(int64_t screenOnId); + +// Create StateGroup proto for simple ScreenState OFF group +StateMap_StateGroup CreateScreenStateSimpleOffGroup(int64_t screenOffId); + +// Create StateMap proto for ScreenState ON/OFF map +StateMap CreateScreenStateOnOffMap(int64_t screenOnId, int64_t screenOffId); + +// Create StateMap proto for simple ScreenState ON/OFF map +StateMap CreateScreenStateSimpleOnOffMap(int64_t screenOnId, int64_t screenOffId); + +// Add a predicate to the predicate combination. +void addPredicateToPredicateCombination(const Predicate& predicate, Predicate* combination); + +// Create dimensions from primitive fields. +FieldMatcher CreateDimensions(const int atomId, const std::vector& fields); + +// Create dimensions by attribution uid and tag. +FieldMatcher CreateAttributionUidAndTagDimensions(const int atomId, + const std::vector& positions); + +// Create dimensions by attribution uid only. +FieldMatcher CreateAttributionUidDimensions(const int atomId, + const std::vector& positions); + +FieldMatcher CreateAttributionUidAndOtherDimensions(const int atomId, + const std::vector& positions, + const std::vector& fields); + +EventMetric createEventMetric(const string& name, const int64_t what, + const optional& condition); + +CountMetric createCountMetric(const string& name, const int64_t what, + const optional& condition, const vector& states); + +DurationMetric createDurationMetric(const string& name, const int64_t what, + const optional& condition, + const vector& states); + +GaugeMetric createGaugeMetric(const string& name, const int64_t what, + const GaugeMetric::SamplingType samplingType, + const optional& condition, + const optional& triggerEvent); + +ValueMetric createValueMetric(const string& name, const AtomMatcher& what, const int valueField, + const optional& condition, const vector& states); + +Alert createAlert(const string& name, const int64_t metricId, const int buckets, + const int64_t triggerSum); + +Alarm createAlarm(const string& name, const int64_t offsetMillis, const int64_t periodMillis); + +Subscription createSubscription(const string& name, const Subscription_RuleType type, + const int64_t ruleId); + +// START: get primary key functions +// These functions take in atom field information and create FieldValues which are stored in the +// given HashableDimensionKey. +void getUidProcessKey(int uid, HashableDimensionKey* key); + +void getOverlayKey(int uid, string packageName, HashableDimensionKey* key); + +void getPartialWakelockKey(int uid, const std::string& tag, HashableDimensionKey* key); + +void getPartialWakelockKey(int uid, HashableDimensionKey* key); +// END: get primary key functions + +void writeAttribution(AStatsEvent* statsEvent, const vector& attributionUids, + const vector& attributionTags); + +// Builds statsEvent to get buffer that is parsed into logEvent then releases statsEvent. +void parseStatsEventToLogEvent(AStatsEvent* statsEvent, LogEvent* logEvent); + +shared_ptr CreateTwoValueLogEvent(int atomId, int64_t eventTimeNs, int32_t value1, + int32_t value2); + +void CreateTwoValueLogEvent(LogEvent* logEvent, int atomId, int64_t eventTimeNs, int32_t value1, + int32_t value2); + +shared_ptr CreateThreeValueLogEvent(int atomId, int64_t eventTimeNs, int32_t value1, + int32_t value2, int32_t value3); + +void CreateThreeValueLogEvent(LogEvent* logEvent, int atomId, int64_t eventTimeNs, int32_t value1, + int32_t value2, int32_t value3); + +// The repeated value log event helpers create a log event with two int fields, both +// set to the same value. This is useful for testing metrics that are only interested +// in the value of the second field but still need the first field to be populated. +std::shared_ptr CreateRepeatedValueLogEvent(int atomId, int64_t eventTimeNs, + int32_t value); + +void CreateRepeatedValueLogEvent(LogEvent* logEvent, int atomId, int64_t eventTimeNs, + int32_t value); + +std::shared_ptr CreateNoValuesLogEvent(int atomId, int64_t eventTimeNs); + +void CreateNoValuesLogEvent(LogEvent* logEvent, int atomId, int64_t eventTimeNs); + +std::shared_ptr makeUidLogEvent(int atomId, int64_t eventTimeNs, int uid, int data1, + int data2); + +std::shared_ptr makeAttributionLogEvent(int atomId, int64_t eventTimeNs, + const vector& uids, + const vector& tags, int data1, int data2); + +sp makeMockUidMapForOneHost(int hostUid, const vector& isolatedUids); + +sp makeMockUidMapForPackage(const string& pkg, const set& uids); + +// Create log event for screen state changed. +std::unique_ptr CreateScreenStateChangedEvent(uint64_t timestampNs, + const android::view::DisplayStateEnum state, + int loggerUid = 0); + +// Create log event for screen brightness state changed. +std::unique_ptr CreateScreenBrightnessChangedEvent(uint64_t timestampNs, int level); + +// Create log event when scheduled job starts. +std::unique_ptr CreateStartScheduledJobEvent(uint64_t timestampNs, + const vector& attributionUids, + const vector& attributionTags, + const string& jobName); + +// Create log event when scheduled job finishes. +std::unique_ptr CreateFinishScheduledJobEvent(uint64_t timestampNs, + const vector& attributionUids, + const vector& attributionTags, + const string& jobName); + +// 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 when battery state changes. +std::unique_ptr CreateBatteryStateChangedEvent(const uint64_t timestampNs, const BatteryPluggedStateEnum state); + +// Create log event for app moving to background. +std::unique_ptr CreateMoveToBackgroundEvent(uint64_t timestampNs, const int uid); + +// Create log event for app moving to foreground. +std::unique_ptr CreateMoveToForegroundEvent(uint64_t timestampNs, const int uid); + +// Create log event when the app sync starts. +std::unique_ptr CreateSyncStartEvent(uint64_t timestampNs, const vector& uids, + const vector& tags, const string& name); + +// Create log event when the app sync ends. +std::unique_ptr CreateSyncEndEvent(uint64_t timestampNs, const vector& uids, + const vector& tags, const string& name); + +// Create log event when the app sync ends. +std::unique_ptr CreateAppCrashEvent(uint64_t timestampNs, const int uid); + +// Create log event for an app crash. +std::unique_ptr CreateAppCrashOccurredEvent(uint64_t timestampNs, const int uid); + +// Create log event for acquiring wakelock. +std::unique_ptr CreateAcquireWakelockEvent(uint64_t timestampNs, const vector& uids, + const vector& tags, + const string& wakelockName); + +// Create log event for releasing wakelock. +std::unique_ptr CreateReleaseWakelockEvent(uint64_t timestampNs, const vector& uids, + const vector& tags, + const string& wakelockName); + +// Create log event for releasing wakelock. +std::unique_ptr CreateIsolatedUidChangedEvent(uint64_t timestampNs, int hostUid, + int isolatedUid, bool is_create); + +// Create log event for uid process state change. +std::unique_ptr CreateUidProcessStateChangedEvent( + uint64_t timestampNs, int uid, const android::app::ProcessStateEnum state); + +std::unique_ptr CreateBleScanStateChangedEvent(uint64_t timestampNs, + const vector& attributionUids, + const vector& attributionTags, + const BleScanStateChanged::State state, + const bool filtered, const bool firstMatch, + const bool opportunistic); + +std::unique_ptr CreateOverlayStateChangedEvent(int64_t timestampNs, const int32_t uid, + const string& packageName, + const bool usingAlertWindow, + const OverlayStateChanged::State state); + +std::unique_ptr CreateAppStartOccurredEvent( + uint64_t timestampNs, const int uid, const string& pkg_name, + AppStartOccurred::TransitionType type, const string& activity_name, + const string& calling_pkg_name, const bool is_instant_app, int64_t activity_start_msec); + +// Create a statsd log event processor upon the start time in seconds, config and key. +sp CreateStatsLogProcessor(const int64_t timeBaseNs, const int64_t currentTimeNs, + const StatsdConfig& config, const ConfigKey& key, + const shared_ptr& puller = nullptr, + const int32_t atomTag = 0 /*for puller only*/, + const sp = new UidMap()); + +// Util function to sort the log events by timestamp. +void sortLogEventsByTimestamp(std::vector> *events); + +int64_t StringToId(const string& str); + +sp createEventMatcherWizard( + int tagId, int matcherIndex, const std::vector& fieldValueMatchers = {}); + +StatsDimensionsValueParcel CreateAttributionUidDimensionsValueParcel(const int atomId, + const int uid); + +void ValidateUidDimension(const DimensionsValue& value, int atomId, int uid); +void ValidateWakelockAttributionUidAndTagDimension(const DimensionsValue& value, const int atomId, + const int uid, const string& tag); +void ValidateUidDimension(const DimensionsValue& value, int node_idx, int atomId, int uid); +void ValidateAttributionUidDimension(const DimensionsValue& value, int atomId, int uid); +void ValidateAttributionUidAndTagDimension( + const DimensionsValue& value, int atomId, int uid, const std::string& tag); +void ValidateAttributionUidAndTagDimension( + const DimensionsValue& value, int node_idx, int atomId, int uid, const std::string& tag); +void ValidateStateValue(const google::protobuf::RepeatedPtrField& stateValues, + int atomId, int64_t value); + +void ValidateCountBucket(const CountBucketInfo& countBucket, int64_t startTimeNs, int64_t endTimeNs, + int64_t count); +void ValidateDurationBucket(const DurationBucketInfo& bucket, int64_t startTimeNs, + int64_t endTimeNs, int64_t durationNs); +void ValidateGaugeBucketTimes(const GaugeBucketInfo& gaugeBucket, int64_t startTimeNs, + int64_t endTimeNs, vector eventTimesNs); +void ValidateValueBucket(const ValueBucketInfo& bucket, int64_t startTimeNs, int64_t endTimeNs, + int64_t value, int64_t conditionTrueNs); + +struct DimensionsPair { + DimensionsPair(DimensionsValue m1, google::protobuf::RepeatedPtrField m2) + : dimInWhat(m1), stateValues(m2){}; + + DimensionsValue dimInWhat; + google::protobuf::RepeatedPtrField stateValues; +}; + +bool LessThan(const StateValue& s1, const StateValue& s2); +bool LessThan(const DimensionsValue& s1, const DimensionsValue& s2); +bool LessThan(const DimensionsPair& s1, const DimensionsPair& s2); + + +void backfillStartEndTimestamp(ConfigMetricsReport *config_report); +void backfillStartEndTimestamp(ConfigMetricsReportList *config_report_list); + +void backfillStringInReport(ConfigMetricsReportList *config_report_list); +void backfillStringInDimension(const std::map& str_map, + DimensionsValue* dimension); + +template +void backfillStringInDimension(const std::map& str_map, + T* metrics) { + for (int i = 0; i < metrics->data_size(); ++i) { + auto data = metrics->mutable_data(i); + if (data->has_dimensions_in_what()) { + backfillStringInDimension(str_map, data->mutable_dimensions_in_what()); + } + if (data->has_dimensions_in_condition()) { + backfillStringInDimension(str_map, data->mutable_dimensions_in_condition()); + } + } +} + +void backfillDimensionPath(ConfigMetricsReportList* config_report_list); + +bool backfillDimensionPath(const DimensionsValue& path, + const google::protobuf::RepeatedPtrField& leafValues, + DimensionsValue* dimension); + +class FakeSubsystemSleepCallback : public BnPullAtomCallback { +public: + // Track the number of pulls. + int pullNum = 1; + Status onPullAtom(int atomTag, + const shared_ptr& resultReceiver) override; +}; + +template +void backfillDimensionPath(const DimensionsValue& whatPath, + const DimensionsValue& conditionPath, + T* metricData) { + for (int i = 0; i < metricData->data_size(); ++i) { + auto data = metricData->mutable_data(i); + if (data->dimension_leaf_values_in_what_size() > 0) { + backfillDimensionPath(whatPath, data->dimension_leaf_values_in_what(), + data->mutable_dimensions_in_what()); + data->clear_dimension_leaf_values_in_what(); + } + if (data->dimension_leaf_values_in_condition_size() > 0) { + backfillDimensionPath(conditionPath, data->dimension_leaf_values_in_condition(), + data->mutable_dimensions_in_condition()); + data->clear_dimension_leaf_values_in_condition(); + } + } +} + +struct DimensionCompare { + bool operator()(const DimensionsPair& s1, const DimensionsPair& s2) const { + return LessThan(s1, s2); + } +}; + +template +void sortMetricDataByDimensionsValue(const T& metricData, T* sortedMetricData) { + std::map dimensionIndexMap; + for (int i = 0; i < metricData.data_size(); ++i) { + dimensionIndexMap.insert( + std::make_pair(DimensionsPair(metricData.data(i).dimensions_in_what(), + metricData.data(i).slice_by_state()), + i)); + } + for (const auto& itr : dimensionIndexMap) { + *sortedMetricData->add_data() = metricData.data(itr.second); + } +} + +template +void backfillStartEndTimestampForFullBucket( + const int64_t timeBaseNs, const int64_t bucketSizeNs, T* bucket) { + bucket->set_start_bucket_elapsed_nanos(timeBaseNs + bucketSizeNs * bucket->bucket_num()); + bucket->set_end_bucket_elapsed_nanos( + timeBaseNs + bucketSizeNs * bucket->bucket_num() + bucketSizeNs); + bucket->clear_bucket_num(); +} + +template +void backfillStartEndTimestampForPartialBucket(const int64_t timeBaseNs, T* bucket) { + if (bucket->has_start_bucket_elapsed_millis()) { + bucket->set_start_bucket_elapsed_nanos( + MillisToNano(bucket->start_bucket_elapsed_millis())); + bucket->clear_start_bucket_elapsed_millis(); + } + if (bucket->has_end_bucket_elapsed_millis()) { + bucket->set_end_bucket_elapsed_nanos( + MillisToNano(bucket->end_bucket_elapsed_millis())); + bucket->clear_end_bucket_elapsed_millis(); + } +} + +template +void backfillStartEndTimestampForMetrics(const int64_t timeBaseNs, const int64_t bucketSizeNs, + T* metrics) { + for (int i = 0; i < metrics->data_size(); ++i) { + auto data = metrics->mutable_data(i); + for (int j = 0; j < data->bucket_info_size(); ++j) { + auto bucket = data->mutable_bucket_info(j); + if (bucket->has_bucket_num()) { + backfillStartEndTimestampForFullBucket(timeBaseNs, bucketSizeNs, bucket); + } else { + backfillStartEndTimestampForPartialBucket(timeBaseNs, bucket); + } + } + } +} + +template +void backfillStartEndTimestampForSkippedBuckets(const int64_t timeBaseNs, T* metrics) { + for (int i = 0; i < metrics->skipped_size(); ++i) { + backfillStartEndTimestampForPartialBucket(timeBaseNs, metrics->mutable_skipped(i)); + } +} +} // namespace statsd +} // namespace os +} // namespace android diff --git a/statsd/tests/storage/StorageManager_test.cpp b/statsd/tests/storage/StorageManager_test.cpp new file mode 100644 index 00000000..74eafbf5 --- /dev/null +++ b/statsd/tests/storage/StorageManager_test.cpp @@ -0,0 +1,204 @@ +// Copyright (C) 2019 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 +#include +#include +#include "src/storage/StorageManager.h" + +#ifdef __ANDROID__ + +namespace android { +namespace os { +namespace statsd { + +using namespace testing; +using std::make_shared; +using std::shared_ptr; +using std::vector; +using testing::Contains; + +TEST(StorageManagerTest, TrainInfoReadWriteTest) { + InstallTrainInfo trainInfo; + trainInfo.trainVersionCode = 12345; + trainInfo.trainName = "This is a train name #)$(&&$"; + trainInfo.status = 1; + const char* expIds = "test_ids"; + trainInfo.experimentIds.assign(expIds, expIds + strlen(expIds)); + + bool result; + + result = StorageManager::writeTrainInfo(trainInfo); + + EXPECT_TRUE(result); + + InstallTrainInfo trainInfoResult; + result = StorageManager::readTrainInfo(trainInfo.trainName, trainInfoResult); + EXPECT_TRUE(result); + + EXPECT_EQ(trainInfo.trainVersionCode, trainInfoResult.trainVersionCode); + ASSERT_EQ(trainInfo.trainName.size(), trainInfoResult.trainName.size()); + EXPECT_EQ(trainInfo.trainName, trainInfoResult.trainName); + EXPECT_EQ(trainInfo.status, trainInfoResult.status); + ASSERT_EQ(trainInfo.experimentIds.size(), trainInfoResult.experimentIds.size()); + EXPECT_EQ(trainInfo.experimentIds, trainInfoResult.experimentIds); +} + +TEST(StorageManagerTest, TrainInfoReadWriteTrainNameSizeOneTest) { + InstallTrainInfo trainInfo; + trainInfo.trainVersionCode = 12345; + trainInfo.trainName = "{"; + trainInfo.status = 1; + const char* expIds = "test_ids"; + trainInfo.experimentIds.assign(expIds, expIds + strlen(expIds)); + + bool result; + + result = StorageManager::writeTrainInfo(trainInfo); + + EXPECT_TRUE(result); + + InstallTrainInfo trainInfoResult; + result = StorageManager::readTrainInfo(trainInfo.trainName, trainInfoResult); + EXPECT_TRUE(result); + + EXPECT_EQ(trainInfo.trainVersionCode, trainInfoResult.trainVersionCode); + ASSERT_EQ(trainInfo.trainName.size(), trainInfoResult.trainName.size()); + EXPECT_EQ(trainInfo.trainName, trainInfoResult.trainName); + EXPECT_EQ(trainInfo.status, trainInfoResult.status); + ASSERT_EQ(trainInfo.experimentIds.size(), trainInfoResult.experimentIds.size()); + EXPECT_EQ(trainInfo.experimentIds, trainInfoResult.experimentIds); +} + +TEST(StorageManagerTest, SortFileTest) { + vector list; + // assume now sec is 500 + list.emplace_back("200_5000_123454", false, 20, 300); + list.emplace_back("300_2000_123454_history", true, 30, 200); + list.emplace_back("400_100009_123454_history", true, 40, 100); + list.emplace_back("100_2000_123454", false, 50, 400); + + StorageManager::sortFiles(&list); + EXPECT_EQ("200_5000_123454", list[0].mFileName); + EXPECT_EQ("100_2000_123454", list[1].mFileName); + EXPECT_EQ("400_100009_123454_history", list[2].mFileName); + EXPECT_EQ("300_2000_123454_history", list[3].mFileName); +} + +const string testDir = "/data/misc/stats-data/"; +const string file1 = testDir + "2557169347_1066_1"; +const string file2 = testDir + "2557169349_1066_1"; +const string file1_history = file1 + "_history"; +const string file2_history = file2 + "_history"; + +bool prepareLocalHistoryTestFiles() { + android::base::unique_fd fd(TEMP_FAILURE_RETRY( + open(file1.c_str(), O_WRONLY | O_CREAT | O_CLOEXEC, S_IRUSR | S_IWUSR))); + if (fd != -1) { + dprintf(fd, "content"); + } else { + return false; + } + + android::base::unique_fd fd2(TEMP_FAILURE_RETRY( + open(file2.c_str(), O_WRONLY | O_CREAT | O_CLOEXEC, S_IRUSR | S_IWUSR))); + if (fd2 != -1) { + dprintf(fd2, "content"); + } else { + return false; + } + return true; +} + +void clearLocalHistoryTestFiles() { + TEMP_FAILURE_RETRY(remove(file1.c_str())); + TEMP_FAILURE_RETRY(remove(file2.c_str())); + TEMP_FAILURE_RETRY(remove(file1_history.c_str())); + TEMP_FAILURE_RETRY(remove(file2_history.c_str())); +} + +bool fileExist(string name) { + android::base::unique_fd fd(TEMP_FAILURE_RETRY(open(name.c_str(), O_RDONLY | O_CLOEXEC))); + return fd != -1; +} + +/* The following AppendConfigReportTests test the 4 combinations of [whether erase data] [whether + * the caller is adb] */ +TEST(StorageManagerTest, AppendConfigReportTest1) { + EXPECT_TRUE(prepareLocalHistoryTestFiles()); + + ProtoOutputStream out; + StorageManager::appendConfigMetricsReport(ConfigKey(1066, 1), &out, false /*erase?*/, + false /*isAdb?*/); + + EXPECT_FALSE(fileExist(file1)); + EXPECT_FALSE(fileExist(file2)); + + EXPECT_TRUE(fileExist(file1_history)); + EXPECT_TRUE(fileExist(file2_history)); + clearLocalHistoryTestFiles(); +} + +TEST(StorageManagerTest, AppendConfigReportTest2) { + EXPECT_TRUE(prepareLocalHistoryTestFiles()); + + ProtoOutputStream out; + StorageManager::appendConfigMetricsReport(ConfigKey(1066, 1), &out, true /*erase?*/, + false /*isAdb?*/); + + EXPECT_FALSE(fileExist(file1)); + EXPECT_FALSE(fileExist(file2)); + EXPECT_FALSE(fileExist(file1_history)); + EXPECT_FALSE(fileExist(file2_history)); + + clearLocalHistoryTestFiles(); +} + +TEST(StorageManagerTest, AppendConfigReportTest3) { + EXPECT_TRUE(prepareLocalHistoryTestFiles()); + + ProtoOutputStream out; + StorageManager::appendConfigMetricsReport(ConfigKey(1066, 1), &out, false /*erase?*/, + true /*isAdb?*/); + + EXPECT_TRUE(fileExist(file1)); + EXPECT_TRUE(fileExist(file2)); + EXPECT_FALSE(fileExist(file1_history)); + EXPECT_FALSE(fileExist(file2_history)); + + clearLocalHistoryTestFiles(); +} + +TEST(StorageManagerTest, AppendConfigReportTest4) { + EXPECT_TRUE(prepareLocalHistoryTestFiles()); + + ProtoOutputStream out; + StorageManager::appendConfigMetricsReport(ConfigKey(1066, 1), &out, true /*erase?*/, + true /*isAdb?*/); + + EXPECT_FALSE(fileExist(file1)); + EXPECT_FALSE(fileExist(file2)); + EXPECT_FALSE(fileExist(file1_history)); + EXPECT_FALSE(fileExist(file2_history)); + + clearLocalHistoryTestFiles(); +} + +} // namespace statsd +} // namespace os +} // namespace android +#else +GTEST_LOG_(INFO) << "This test does nothing.\n"; +#endif diff --git a/statsd/tests/subscriber/SubscriberReporter_test.cpp b/statsd/tests/subscriber/SubscriberReporter_test.cpp new file mode 100644 index 00000000..c6a888ff --- /dev/null +++ b/statsd/tests/subscriber/SubscriberReporter_test.cpp @@ -0,0 +1,107 @@ +// Copyright (C) 2020 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 "subscriber/SubscriberReporter.h" + +#include +#include +#include + +#include "tests/statsd_test_util.h" + +using namespace testing; +using ::ndk::SharedRefBase; +using std::unordered_map; + +#ifdef __ANDROID__ + +namespace android { +namespace os { +namespace statsd { + +namespace { + +const ConfigKey configKey1(0, 12345); +const ConfigKey configKey2(0, 123456); +const int64_t subscriptionId1 = 1, subscriptionId2 = 2; + +} // anonymous namespace + +class SubscriberReporterTest : public ::testing::Test { +public: + SubscriberReporterTest() { + } + const shared_ptr pir1 = + SharedRefBase::make>(); + const shared_ptr pir2 = + SharedRefBase::make>(); + const shared_ptr pir3 = + SharedRefBase::make>(); + // Two subscribers on config key 1, one on config key 2. + void SetUp() override { + SubscriberReporter::getInstance().setBroadcastSubscriber(configKey1, subscriptionId1, pir1); + SubscriberReporter::getInstance().setBroadcastSubscriber(configKey1, subscriptionId2, pir2); + SubscriberReporter::getInstance().setBroadcastSubscriber(configKey2, subscriptionId1, pir3); + } + + void TearDown() override { + SubscriberReporter::getInstance(); + SubscriberReporter::getInstance().unsetBroadcastSubscriber(configKey1, subscriptionId1); + SubscriberReporter::getInstance().unsetBroadcastSubscriber(configKey1, subscriptionId2); + SubscriberReporter::getInstance().unsetBroadcastSubscriber(configKey2, subscriptionId1); + EXPECT_EQ(pir1.use_count(), 1); + EXPECT_EQ(pir2.use_count(), 1); + EXPECT_EQ(pir3.use_count(), 1); + ASSERT_TRUE(SubscriberReporter::getInstance().mIntentMap.empty()); + } +}; + +TEST_F(SubscriberReporterTest, TestBroadcastSubscriberDeathRemovesPir) { + SubscriberReporter::broadcastSubscriberDied(pir1.get()); + EXPECT_EQ(pir1.use_count(), 1); + + unordered_map>> + expectedIntentMap = {{configKey1, {{subscriptionId2, pir2}}}, + {configKey2, {{subscriptionId1, pir3}}}}; + EXPECT_THAT(SubscriberReporter::getInstance().mIntentMap, ContainerEq(expectedIntentMap)); +} + +TEST_F(SubscriberReporterTest, TestBroadcastSubscriberDeathRemovesPirAndConfigKey) { + SubscriberReporter::broadcastSubscriberDied(pir3.get()); + + EXPECT_EQ(pir3.use_count(), 1); + unordered_map>> + expectedIntentMap = {{configKey1, {{subscriptionId1, pir1}, {subscriptionId2, pir2}}}}; + EXPECT_THAT(SubscriberReporter::getInstance().mIntentMap, ContainerEq(expectedIntentMap)); +} + +TEST_F(SubscriberReporterTest, TestBroadcastSubscriberDeathKeepsReplacedPir) { + const shared_ptr pir4 = + SharedRefBase::make>(); + SubscriberReporter::getInstance().setBroadcastSubscriber(configKey1, subscriptionId1, pir4); + + // pir1 dies, but pir4 has replaced it with the same keys. The map should remain the same. + SubscriberReporter::broadcastSubscriberDied(pir1.get()); + + unordered_map>> + expectedIntentMap = {{configKey1, {{subscriptionId1, pir4}, {subscriptionId2, pir2}}}, + {configKey2, {{subscriptionId1, pir3}}}}; + EXPECT_THAT(SubscriberReporter::getInstance().mIntentMap, ContainerEq(expectedIntentMap)); +} +} // namespace statsd +} // namespace os +} // namespace android +#else +GTEST_LOG_(INFO) << "This test does nothing.\n"; +#endif diff --git a/statsd/tests/utils/MultiConditionTrigger_test.cpp b/statsd/tests/utils/MultiConditionTrigger_test.cpp new file mode 100644 index 00000000..32cecd3b --- /dev/null +++ b/statsd/tests/utils/MultiConditionTrigger_test.cpp @@ -0,0 +1,174 @@ +/* + * Copyright (C) 2020 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 "utils/MultiConditionTrigger.h" + +#include + +#include +#include +#include +#include + +#ifdef __ANDROID__ + +using namespace std; +using std::this_thread::sleep_for; + +namespace android { +namespace os { +namespace statsd { + +TEST(MultiConditionTrigger, TestMultipleConditions) { + int numConditions = 5; + string t1 = "t1", t2 = "t2", t3 = "t3", t4 = "t4", t5 = "t5"; + set conditionNames = {t1, t2, t3, t4, t5}; + + mutex lock; + condition_variable cv; + bool triggerCalled = false; + + // Mark done as true and notify in the done. + MultiConditionTrigger trigger(conditionNames, [&lock, &cv, &triggerCalled] { + { + lock_guard lg(lock); + triggerCalled = true; + } + cv.notify_all(); + }); + + vector threads; + vector done(numConditions, 0); + + int i = 0; + for (const string& conditionName : conditionNames) { + threads.emplace_back([&done, &conditionName, &trigger, i] { + sleep_for(chrono::milliseconds(3)); + done[i] = 1; + trigger.markComplete(conditionName); + }); + i++; + } + + unique_lock unique_lk(lock); + cv.wait(unique_lk, [&triggerCalled] { + return triggerCalled; + }); + + for (i = 0; i < numConditions; i++) { + EXPECT_EQ(done[i], 1); + } + + for (i = 0; i < numConditions; i++) { + threads[i].join(); + } +} + +TEST(MultiConditionTrigger, TestNoConditions) { + mutex lock; + condition_variable cv; + bool triggerCalled = false; + + MultiConditionTrigger trigger({}, [&lock, &cv, &triggerCalled] { + { + lock_guard lg(lock); + triggerCalled = true; + } + cv.notify_all(); + }); + + unique_lock unique_lk(lock); + cv.wait(unique_lk, [&triggerCalled] { return triggerCalled; }); + EXPECT_TRUE(triggerCalled); + // Ensure that trigger occurs immediately if no events need to be completed. +} + +TEST(MultiConditionTrigger, TestMarkCompleteCalledBySameCondition) { + string t1 = "t1", t2 = "t2"; + set conditionNames = {t1, t2}; + + mutex lock; + condition_variable cv; + bool triggerCalled = false; + + MultiConditionTrigger trigger(conditionNames, [&lock, &cv, &triggerCalled] { + { + lock_guard lg(lock); + triggerCalled = true; + } + cv.notify_all(); + }); + + trigger.markComplete(t1); + trigger.markComplete(t1); + + // Ensure that the trigger still hasn't fired. + { + lock_guard lg(lock); + EXPECT_FALSE(triggerCalled); + } + + trigger.markComplete(t2); + unique_lock unique_lk(lock); + cv.wait(unique_lk, [&triggerCalled] { return triggerCalled; }); + EXPECT_TRUE(triggerCalled); +} + +TEST(MultiConditionTrigger, TestTriggerOnlyCalledOnce) { + string t1 = "t1"; + set conditionNames = {t1}; + + mutex lock; + condition_variable cv; + bool triggerCalled = false; + int triggerCount = 0; + + MultiConditionTrigger trigger(conditionNames, [&lock, &cv, &triggerCalled, &triggerCount] { + { + lock_guard lg(lock); + triggerCount++; + triggerCalled = true; + } + cv.notify_all(); + }); + + trigger.markComplete(t1); + + // Ensure that the trigger fired. + { + unique_lock unique_lk(lock); + cv.wait(unique_lk, [&triggerCalled] { return triggerCalled; }); + EXPECT_TRUE(triggerCalled); + EXPECT_EQ(triggerCount, 1); + triggerCalled = false; + } + + trigger.markComplete(t1); + + // Ensure that the trigger does not fire again. + { + unique_lock unique_lk(lock); + cv.wait_for(unique_lk, chrono::milliseconds(5), [&triggerCalled] { return triggerCalled; }); + EXPECT_FALSE(triggerCalled); + EXPECT_EQ(triggerCount, 1); + } +} + +} // namespace statsd +} // namespace os +} // namespace android +#else +GTEST_LOG_(INFO) << "This test does nothing.\n"; +#endif diff --git a/statsd/tools/localtools/Android.bp b/statsd/tools/localtools/Android.bp new file mode 100644 index 00000000..69a43a8f --- /dev/null +++ b/statsd/tools/localtools/Android.bp @@ -0,0 +1,46 @@ +java_binary_host { + name: "statsd_localdrive", + manifest: "localdrive_manifest.txt", + srcs: [ + "src/com/android/statsd/shelltools/localdrive/*.java", + "src/com/android/statsd/shelltools/Utils.java", + ], + static_libs: [ + "platformprotos", + "guava", + ], +} + +java_library_host { + name: "statsd_testdrive_lib", + srcs: [ + "src/com/android/statsd/shelltools/testdrive/*.java", + "src/com/android/statsd/shelltools/Utils.java", + ], + static_libs: [ + "platformprotos", + "guava", + ], +} + + +java_binary_host { + name: "statsd_testdrive", + manifest: "testdrive_manifest.txt", + static_libs: [ + "statsd_testdrive_lib", + ], +} + +java_test_host { + name: "statsd_testdrive_test", + test_suites: ["general-tests"], + srcs: ["test/com/android/statsd/shelltools/testdrive/*.java"], + static_libs: [ + "statsd_testdrive_lib", + "junit", + "platformprotos", + "guava", + ], +} + diff --git a/statsd/tools/localtools/TEST_MAPPING b/statsd/tools/localtools/TEST_MAPPING new file mode 100644 index 00000000..7c8a3db5 --- /dev/null +++ b/statsd/tools/localtools/TEST_MAPPING @@ -0,0 +1,8 @@ +{ + "presubmit": [ + { + "name": "statsd_testdrive_test", + "host": true + } + ] +} diff --git a/statsd/tools/localtools/localdrive_manifest.txt b/statsd/tools/localtools/localdrive_manifest.txt new file mode 100644 index 00000000..035cea11 --- /dev/null +++ b/statsd/tools/localtools/localdrive_manifest.txt @@ -0,0 +1 @@ +Main-class: com.android.statsd.shelltools.localdrive.LocalDrive diff --git a/statsd/tools/localtools/src/com/android/statsd/shelltools/Utils.java b/statsd/tools/localtools/src/com/android/statsd/shelltools/Utils.java new file mode 100644 index 00000000..6a74480b --- /dev/null +++ b/statsd/tools/localtools/src/com/android/statsd/shelltools/Utils.java @@ -0,0 +1,284 @@ +/* + * Copyright (C) 2018 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. + */ +package com.android.statsd.shelltools; + +import com.android.os.StatsLog.ConfigMetricsReportList; + +import com.google.common.io.Files; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.List; +import java.util.logging.ConsoleHandler; +import java.util.logging.Formatter; +import java.util.logging.Level; +import java.util.logging.LogRecord; +import java.util.logging.Logger; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Utilities for local use of statsd. + */ +public class Utils { + + public static final String CMD_DUMP_REPORT = "cmd stats dump-report"; + public static final String CMD_LOG_APP_BREADCRUMB = "cmd stats log-app-breadcrumb"; + public static final String CMD_REMOVE_CONFIG = "cmd stats config remove"; + public static final String CMD_UPDATE_CONFIG = "cmd stats config update"; + + public static final String SHELL_UID = "2000"; // Use shell, even if rooted. + + /** + * Runs adb shell command with output directed to outputFile if non-null. + */ + public static void runCommand(File outputFile, Logger logger, String... commands) + throws IOException, InterruptedException { + ProcessBuilder pb = new ProcessBuilder(commands); + if (outputFile != null && outputFile.exists() && outputFile.canWrite()) { + pb.redirectOutput(outputFile); + } + Process process = pb.start(); + + // Capture any errors + StringBuilder err = new StringBuilder(); + BufferedReader br = new BufferedReader(new InputStreamReader(process.getErrorStream())); + for (String line = br.readLine(); line != null; line = br.readLine()) { + err.append(line).append('\n'); + } + logger.severe(err.toString()); + + // Check result + if (process.waitFor() == 0) { + logger.fine("Adb command successful."); + } else { + logger.severe("Abnormal adb shell termination for: " + String.join(",", commands)); + throw new RuntimeException("Error running adb command: " + err.toString()); + } + } + + /** + * Dumps the report from the device and converts it to a ConfigMetricsReportList. + * Erases the data if clearData is true. + * @param configId id of the config + * @param clearData whether to erase the report data from statsd after getting the report. + * @param useShellUid Pulls data for the {@link SHELL_UID} instead of the caller's uid. + * @param logger Logger to log error messages + * @return + * @throws IOException + * @throws InterruptedException + */ + public static ConfigMetricsReportList getReportList(long configId, boolean clearData, + boolean useShellUid, Logger logger, String deviceSerial) + throws IOException, InterruptedException { + try { + File outputFile = File.createTempFile("statsdret", ".bin"); + outputFile.deleteOnExit(); + runCommand( + outputFile, + logger, + "adb", + "-s", + deviceSerial, + "shell", + CMD_DUMP_REPORT, + useShellUid ? SHELL_UID : "", + String.valueOf(configId), + clearData ? "" : "--keep_data", + "--include_current_bucket", + "--proto"); + ConfigMetricsReportList reportList = + ConfigMetricsReportList.parseFrom(new FileInputStream(outputFile)); + return reportList; + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + logger.severe("Failed to fetch and parse the statsd output report. " + + "Perhaps there is not a valid statsd config for the requested " + + (useShellUid ? ("uid=" + SHELL_UID + ", ") : "") + + "configId=" + configId + + "."); + throw (e); + } + } + + /** + * Logs an AppBreadcrumbReported atom. + * @param label which label to log for the app breadcrumb atom. + * @param state which state to log for the app breadcrumb atom. + * @param logger Logger to log error messages + * + * @throws IOException + * @throws InterruptedException + */ + public static void logAppBreadcrumb(int label, int state, Logger logger, String deviceSerial) + throws IOException, InterruptedException { + runCommand( + null, + logger, + "adb", + "-s", + deviceSerial, + "shell", + CMD_LOG_APP_BREADCRUMB, + String.valueOf(label), + String.valueOf(state)); + } + public static void setUpLogger(Logger logger, boolean debug) { + ConsoleHandler handler = new ConsoleHandler(); + handler.setFormatter(new LocalToolsFormatter()); + logger.setUseParentHandlers(false); + if (debug) { + handler.setLevel(Level.ALL); + logger.setLevel(Level.ALL); + } + logger.addHandler(handler); + } + + /** + * Attempt to determine whether tool will work with this statsd, i.e. whether statsd is + * minCodename or higher. + * Algorithm: true if (sdk >= minSdk) || (sdk == minSdk-1 && codeName.startsWith(minCodeName)) + * If all else fails, assume it will work (letting future commands deal with any errors). + */ + public static boolean isAcceptableStatsd(Logger logger, int minSdk, String minCodename, + String deviceSerial) { + BufferedReader in = null; + try { + File outFileSdk = File.createTempFile("shelltools_sdk", "tmp"); + outFileSdk.deleteOnExit(); + runCommand(outFileSdk, logger, + "adb", "-s", deviceSerial, "shell", "getprop", "ro.build.version.sdk"); + in = new BufferedReader(new InputStreamReader(new FileInputStream(outFileSdk))); + // If NullPointerException/NumberFormatException/etc., just catch and return true. + int sdk = Integer.parseInt(in.readLine().trim()); + if (sdk >= minSdk) { + return true; + } else if (sdk == minSdk - 1) { // Could be minSdk-1, or could be minSdk development. + in.close(); + File outFileCode = File.createTempFile("shelltools_codename", "tmp"); + outFileCode.deleteOnExit(); + runCommand(outFileCode, logger, + "adb", "-s", deviceSerial, "shell", "getprop", "ro.build.version.codename"); + in = new BufferedReader(new InputStreamReader(new FileInputStream(outFileCode))); + return in.readLine().startsWith(minCodename); + } else { + return false; + } + } catch (Exception e) { + logger.fine("Could not determine whether statsd version is compatibile " + + "with tool: " + e.toString()); + } finally { + try { + if (in != null) { + in.close(); + } + } catch (IOException e) { + logger.fine("Could not close temporary file: " + e.toString()); + } + } + // Could not determine whether statsd is acceptable version. + // Just assume it is; if it isn't, we'll just get future errors via adb and deal with them. + return true; + } + + public static class LocalToolsFormatter extends Formatter { + public String format(LogRecord record) { + return record.getMessage() + "\n"; + } + } + + /** + * Parse the result of "adb devices" to return the list of connected devices. + * @param logger Logger to log error messages + * @return List of the serial numbers of the connected devices. + */ + public static List getDeviceSerials(Logger logger) { + try { + ArrayList devices = new ArrayList<>(); + File outFile = File.createTempFile("device_serial", "tmp"); + outFile.deleteOnExit(); + Utils.runCommand(outFile, logger, "adb", "devices"); + List outputLines = Files.readLines(outFile, Charset.defaultCharset()); + Pattern regex = Pattern.compile("^(.*)\tdevice$"); + for (String line : outputLines) { + Matcher m = regex.matcher(line); + if (m.find()) { + devices.add(m.group(1)); + } + } + return devices; + } catch (Exception ex) { + logger.log(Level.SEVERE, "Failed to list connected devices: " + ex.getMessage()); + } + return null; + } + + /** + * Returns ANDROID_SERIAL environment variable, or null if that is undefined or unavailable. + * @param logger Destination of error messages. + * @return String value of ANDROID_SERIAL environment variable, or null. + */ + public static String getDefaultDevice(Logger logger) { + try { + return System.getenv("ANDROID_SERIAL"); + } catch (Exception ex) { + logger.log(Level.SEVERE, "Failed to check ANDROID_SERIAL environment variable.", + ex); + } + return null; + } + + /** + * Returns the device to use if one can be deduced, or null. + * @param device Command-line specified device, or null. + * @param connectedDevices List of all connected devices. + * @param defaultDevice Environment-variable specified device, or null. + * @param logger Destination of error messages. + * @return Device to use, or null. + */ + public static String chooseDevice(String device, List connectedDevices, + String defaultDevice, Logger logger) { + if (connectedDevices == null || connectedDevices.isEmpty()) { + logger.severe("No connected device."); + return null; + } + if (device != null) { + if (connectedDevices.contains(device)) { + return device; + } + logger.severe("Device not connected: " + device); + return null; + } + if (connectedDevices.size() == 1) { + return connectedDevices.get(0); + } + if (defaultDevice != null) { + if (connectedDevices.contains(defaultDevice)) { + return defaultDevice; + } else { + logger.severe("ANDROID_SERIAL device is not connected: " + defaultDevice); + return null; + } + } + logger.severe("More than one device is connected. Choose one" + + " with -s DEVICE_SERIAL or environment variable ANDROID_SERIAL."); + return null; + } +} diff --git a/statsd/tools/localtools/src/com/android/statsd/shelltools/localdrive/LocalDrive.java b/statsd/tools/localtools/src/com/android/statsd/shelltools/localdrive/LocalDrive.java new file mode 100644 index 00000000..ec3c7df7 --- /dev/null +++ b/statsd/tools/localtools/src/com/android/statsd/shelltools/localdrive/LocalDrive.java @@ -0,0 +1,379 @@ +/* + * Copyright (C) 2018 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. + */ +package com.android.statsd.shelltools.localdrive; + +import com.android.internal.os.StatsdConfigProto.StatsdConfig; +import com.android.os.StatsLog.ConfigMetricsReport; +import com.android.os.StatsLog.ConfigMetricsReportList; +import com.android.statsd.shelltools.Utils; + +import com.google.common.io.Files; +import com.google.protobuf.TextFormat; + +import java.io.File; +import java.io.FileReader; +import java.io.IOException; +import java.util.List; +import java.util.logging.Logger; + +/** + * Tool for using statsd locally. Can upload a config and get the data. Handles + * both binary and human-readable protos. + * To make: make statsd_localdrive + * To run: statsd_localdrive (i.e. ./out/host/linux-x86/bin/statsd_localdrive) + */ +public class LocalDrive { + private static final boolean DEBUG = false; + + public static final int MIN_SDK = 29; + public static final String MIN_CODENAME = "Q"; + + public static final long DEFAULT_CONFIG_ID = 56789; + + public static final String BINARY_FLAG = "--binary"; + public static final String CLEAR_DATA = "--clear"; + public static final String NO_UID_MAP_FLAG = "--no-uid-map"; + + public static final String HELP_STRING = + "Usage:\n\n" + + + "statsd_localdrive [-s DEVICE_SERIAL] upload CONFIG_FILE [CONFIG_ID] [--binary]\n" + + " Uploads the given statsd config file (in binary or human-readable-text format).\n" + + " If a config with this id already exists, removes it first.\n" + + " CONFIG_FILE Location of config file on host.\n" + + " CONFIG_ID Long ID to associate with this config. If absent, uses " + + DEFAULT_CONFIG_ID + ".\n" + + " --binary Config is in binary format; otherwise, assumed human-readable text.\n" + + // Similar to: adb shell cmd stats config update SHELL_UID CONFIG_ID + "\n" + + + "statsd_localdrive [-s DEVICE_SERIAL] update CONFIG_FILE [CONFIG_ID] [--binary]\n" + + " Same as upload, but does not remove the old config first (if it already exists).\n" + + // Similar to: adb shell cmd stats config update SHELL_UID CONFIG_ID + "\n" + + + "statsd_localdrive [-s DEVICE_SERIAL] get-data [CONFIG_ID] [--clear] [--binary] [--no-uid-map]\n" + + " Prints the output statslog data (in binary or human-readable-text format).\n" + + " CONFIG_ID Long ID of the config. If absent, uses " + DEFAULT_CONFIG_ID + ".\n" + + " --binary Output should be in binary, instead of default human-readable text.\n" + + " Binary output can be redirected as usual (e.g. > FILENAME).\n" + + " --no-uid-map Do not include the uid-map (the very lengthy uid<-->pkgName map).\n" + + " --clear Erase the data from statsd afterwards. Does not remove the config.\n" + + // Similar to: adb shell cmd stats dump-report SHELL_UID CONFIG_ID [--keep_data] + // --include_current_bucket --proto + "\n" + + + "statsd_localdrive [-s DEVICE_SERIAL] remove [CONFIG_ID]\n" + + " Removes the config.\n" + + " CONFIG_ID Long ID of the config. If absent, uses " + DEFAULT_CONFIG_ID + ".\n" + + // Equivalent to: adb shell cmd stats config remove SHELL_UID CONFIG_ID + "\n" + + + "statsd_localdrive [-s DEVICE_SERIAL] clear [CONFIG_ID]\n" + + " Clears the data associated with the config.\n" + + " CONFIG_ID Long ID of the config. If absent, uses " + DEFAULT_CONFIG_ID + ".\n" + + // Similar to: adb shell cmd stats dump-report SHELL_UID CONFIG_ID + // --include_current_bucket --proto + ""; + + + private static final Logger sLogger = Logger.getLogger(LocalDrive.class.getName()); + + /** Usage: make statsd_localdrive && statsd_localdrive */ + public static void main(String[] args) { + Utils.setUpLogger(sLogger, DEBUG); + if (args.length == 0) { + printHelp(); + return; + } + + int remainingArgsLength = args.length; + String deviceSerial = null; + if (args[0].equals("-s")) { + if (args.length == 1) { + printHelp(); + } + deviceSerial = args[1]; + remainingArgsLength -= 2; + } + + List connectedDevices = Utils.getDeviceSerials(sLogger); + deviceSerial = Utils.chooseDevice(deviceSerial, connectedDevices, + Utils.getDefaultDevice(sLogger), sLogger); + if (deviceSerial == null) { + return; + } + + if (!Utils.isAcceptableStatsd(sLogger, MIN_SDK, MIN_CODENAME, deviceSerial)) { + sLogger.severe("LocalDrive only works with statsd versions for Android " + + MIN_CODENAME + " or higher."); + return; + } + + int idx = args.length - remainingArgsLength; + if (remainingArgsLength > 0) { + switch (args[idx]) { + case "clear": + cmdClear(args, idx, deviceSerial); + return; + case "get-data": + cmdGetData(args, idx, deviceSerial); + return; + case "remove": + cmdRemove(args, idx); + return; + case "update": + cmdUpdate(args, idx, deviceSerial); + return; + case "upload": + cmdUpload(args, idx, deviceSerial); + return; + } + } + printHelp(); + } + + private static void printHelp() { + sLogger.info(HELP_STRING); + } + + // upload CONFIG_FILE [CONFIG_ID] [--binary] + private static boolean cmdUpload(String[] args, int idx, String deviceSerial) { + return updateConfig(args, idx, true, deviceSerial); + } + + // update CONFIG_FILE [CONFIG_ID] [--binary] + private static boolean cmdUpdate(String[] args, int idx, String deviceSerial) { + return updateConfig(args, idx, false, deviceSerial); + } + + private static boolean updateConfig(String[] args, int idx, boolean removeOldConfig, + String deviceSerial) { + int argCount = args.length - 1 - idx; // Used up one for upload/update. + + // Get CONFIG_FILE + if (argCount < 1) { + sLogger.severe("No config file provided."); + printHelp(); + return false; + } + final String origConfigLocation = args[idx + 1]; + if (!new File(origConfigLocation).exists()) { + sLogger.severe("Error - Cannot find the provided config file: " + origConfigLocation); + return false; + } + argCount--; + + // Get --binary + boolean binary = contains(args, idx + 2, BINARY_FLAG); + if (binary) argCount --; + + // Get CONFIG_ID + long configId; + try { + configId = getConfigId(argCount < 1, args, idx + 2); + } catch (NumberFormatException e) { + sLogger.severe("Invalid config id provided."); + printHelp(); + return false; + } + sLogger.fine(String.format("updateConfig with %s %d %b %b", + origConfigLocation, configId, binary, removeOldConfig)); + + // Remove the old config. + if (removeOldConfig) { + try { + Utils.runCommand(null, sLogger, "adb", "shell", Utils.CMD_REMOVE_CONFIG, + Utils.SHELL_UID, String.valueOf(configId)); + Utils.getReportList(configId, true /* clearData */, true /* SHELL_UID */, sLogger, + deviceSerial); + } catch (InterruptedException | IOException e) { + sLogger.severe("Failed to remove config: " + e.getMessage()); + return false; + } + } + + // Upload the config. + String configLocation; + if (binary) { + configLocation = origConfigLocation; + } else { + StatsdConfig.Builder builder = StatsdConfig.newBuilder(); + try { + TextFormat.merge(new FileReader(origConfigLocation), builder); + } catch (IOException e) { + sLogger.severe("Failed to read config file " + origConfigLocation + ": " + + e.getMessage()); + return false; + } + + try { + File tempConfigFile = File.createTempFile("statsdconfig", ".config"); + tempConfigFile.deleteOnExit(); + Files.write(builder.build().toByteArray(), tempConfigFile); + configLocation = tempConfigFile.getAbsolutePath(); + } catch (IOException e) { + sLogger.severe("Failed to write temp config file: " + e.getMessage()); + return false; + } + } + String remotePath = "/data/local/tmp/statsdconfig.config"; + try { + Utils.runCommand(null, sLogger, "adb", "push", configLocation, remotePath); + Utils.runCommand(null, sLogger, "adb", "shell", "cat", remotePath, "|", + Utils.CMD_UPDATE_CONFIG, Utils.SHELL_UID, String.valueOf(configId)); + } catch (InterruptedException | IOException e) { + sLogger.severe("Failed to update config: " + e.getMessage()); + return false; + } + return true; + } + + // get-data [CONFIG_ID] [--clear] [--binary] [--no-uid-map] + private static boolean cmdGetData(String[] args, int idx, String deviceSerial) { + boolean binary = contains(args, idx + 1, BINARY_FLAG); + boolean noUidMap = contains(args, idx + 1, NO_UID_MAP_FLAG); + boolean clearData = contains(args, idx + 1, CLEAR_DATA); + + // Get CONFIG_ID + int argCount = args.length - 1 - idx; // Used up one for get-data. + if (binary) argCount--; + if (noUidMap) argCount--; + if (clearData) argCount--; + long configId; + try { + configId = getConfigId(argCount < 1, args, idx + 1); + } catch (NumberFormatException e) { + sLogger.severe("Invalid config id provided."); + printHelp(); + return false; + } + sLogger.fine(String.format("cmdGetData with %d %b %b %b", + configId, clearData, binary, noUidMap)); + + // Get the StatsLog + // Even if the args request no modifications, we still parse it to make sure it's valid. + ConfigMetricsReportList reportList; + try { + reportList = Utils.getReportList(configId, clearData, true /* SHELL_UID */, sLogger, + deviceSerial); + } catch (IOException | InterruptedException e) { + sLogger.severe("Failed to get report list: " + e.getMessage()); + return false; + } + if (noUidMap) { + ConfigMetricsReportList.Builder builder + = ConfigMetricsReportList.newBuilder(reportList); + // Clear the reports, then add them back without their UidMap. + builder.clearReports(); + for (ConfigMetricsReport report : reportList.getReportsList()) { + builder.addReports(ConfigMetricsReport.newBuilder(report).clearUidMap()); + } + reportList = builder.build(); + } + + if (!binary) { + sLogger.info(reportList.toString()); + } else { + try { + System.out.write(reportList.toByteArray()); + } catch (IOException e) { + sLogger.severe("Failed to output binary statslog proto: " + + e.getMessage()); + return false; + } + } + return true; + } + + // clear [CONFIG_ID] + private static boolean cmdClear(String[] args, int idx, String deviceSerial) { + // Get CONFIG_ID + long configId; + try { + configId = getConfigId(false, args, idx + 1); + } catch (NumberFormatException e) { + sLogger.severe("Invalid config id provided."); + printHelp(); + return false; + } + sLogger.fine(String.format("cmdClear with %d", configId)); + + try { + Utils.getReportList(configId, true /* clearData */, true /* SHELL_UID */, sLogger, + deviceSerial); + } catch (IOException | InterruptedException e) { + sLogger.severe("Failed to get report list: " + e.getMessage()); + return false; + } + return true; + } + + // remove [CONFIG_ID] + private static boolean cmdRemove(String[] args, int idx) { + // Get CONFIG_ID + long configId; + try { + configId = getConfigId(false, args, idx + 1); + } catch (NumberFormatException e) { + sLogger.severe("Invalid config id provided."); + printHelp(); + return false; + } + sLogger.fine(String.format("cmdRemove with %d", configId)); + + try { + Utils.runCommand(null, sLogger, "adb", "shell", Utils.CMD_REMOVE_CONFIG, + Utils.SHELL_UID, String.valueOf(configId)); + } catch (InterruptedException | IOException e) { + sLogger.severe("Failed to remove config: " + e.getMessage()); + return false; + } + return true; + } + + /** + * Searches through the array to see if it contains (precisely) the given value, starting + * at the given firstIdx. + */ + private static boolean contains(String[] array, int firstIdx, String value) { + if (value == null) return false; + if (firstIdx < 0) return false; + for (int i = firstIdx; i < array.length; i++) { + if (value.equals(array[i])) { + return true; + } + } + return false; + } + + /** + * Gets the config id from args[idx], or returns DEFAULT_CONFIG_ID if args[idx] does not exist. + * If justUseDefault, overrides and just uses DEFAULT_CONFIG_ID instead. + */ + private static long getConfigId(boolean justUseDefault, String[] args, int idx) + throws NumberFormatException { + if (justUseDefault || args.length <= idx || idx < 0) { + return DEFAULT_CONFIG_ID; + } + try { + return Long.valueOf(args[idx]); + } catch (NumberFormatException e) { + sLogger.severe("Bad config id provided: " + args[idx]); + throw e; + } + } +} diff --git a/statsd/tools/localtools/src/com/android/statsd/shelltools/testdrive/TestDrive.java b/statsd/tools/localtools/src/com/android/statsd/shelltools/testdrive/TestDrive.java new file mode 100644 index 00000000..51bcad11 --- /dev/null +++ b/statsd/tools/localtools/src/com/android/statsd/shelltools/testdrive/TestDrive.java @@ -0,0 +1,419 @@ +/* + * Copyright (C) 2018 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. + */ +package com.android.statsd.shelltools.testdrive; + +import com.android.internal.os.StatsdConfigProto; +import com.android.internal.os.StatsdConfigProto.AtomMatcher; +import com.android.internal.os.StatsdConfigProto.EventMetric; +import com.android.internal.os.StatsdConfigProto.FieldFilter; +import com.android.internal.os.StatsdConfigProto.GaugeMetric; +import com.android.internal.os.StatsdConfigProto.PullAtomPackages; +import com.android.internal.os.StatsdConfigProto.SimpleAtomMatcher; +import com.android.internal.os.StatsdConfigProto.StatsdConfig; +import com.android.internal.os.StatsdConfigProto.TimeUnit; +import com.android.os.AtomsProto.Atom; +import com.android.os.StatsLog; +import com.android.os.StatsLog.ConfigMetricsReport; +import com.android.os.StatsLog.ConfigMetricsReportList; +import com.android.os.StatsLog.StatsLogReport; +import com.android.statsd.shelltools.Utils; + +import com.google.common.annotations.VisibleForTesting; +import com.google.common.io.Files; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.TreeSet; +import java.util.logging.Level; +import java.util.logging.Logger; + +public class TestDrive { + + private static final int METRIC_ID_BASE = 1111; + private static final long ATOM_MATCHER_ID_BASE = 1234567; + private static final long APP_BREADCRUMB_MATCHER_ID = 1111111; + private static final int PULL_ATOM_START = 10000; + private static final int MAX_PLATFORM_ATOM_TAG = 100000; + private static final int VENDOR_PULLED_ATOM_START_TAG = 150000; + private static final long CONFIG_ID = 54321; + private static final String[] ALLOWED_LOG_SOURCES = { + "AID_GRAPHICS", + "AID_INCIDENTD", + "AID_STATSD", + "AID_RADIO", + "com.android.systemui", + "com.android.vending", + "AID_SYSTEM", + "AID_ROOT", + "AID_BLUETOOTH", + "AID_LMKD", + "com.android.managedprovisioning", + "AID_MEDIA", + "AID_NETWORK_STACK", + "com.google.android.providers.media.module", + }; + private static final String[] DEFAULT_PULL_SOURCES = { + "AID_SYSTEM", + "AID_RADIO" + }; + private static final Logger LOGGER = Logger.getLogger(TestDrive.class.getName()); + + @VisibleForTesting + String mDeviceSerial = null; + @VisibleForTesting + Dumper mDumper = new BasicDumper(); + + public static void main(String[] args) { + final Configuration configuration = new Configuration(); + final TestDrive testDrive = new TestDrive(); + Utils.setUpLogger(LOGGER, false); + + if (!testDrive.processArgs(configuration, args, + Utils.getDeviceSerials(LOGGER), Utils.getDefaultDevice(LOGGER))) { + return; + } + + final ConfigMetricsReportList reports = testDrive.testDriveAndGetReports( + configuration.createConfig(), configuration.hasPulledAtoms(), + configuration.hasPushedAtoms()); + if (reports != null) { + configuration.dumpMetrics(reports, testDrive.mDumper); + } + } + + boolean processArgs(Configuration configuration, String[] args, List connectedDevices, + String defaultDevice) { + if (args.length < 1) { + LOGGER.severe("Usage: ./test_drive [-one] " + + "[-p additional_allowed_package] " + + "[-s DEVICE_SERIAL_NUMBER] " + + " ... "); + return false; + } + + int first_arg = 0; + // Consume all flags, which must precede all atoms + for (; first_arg < args.length; ++first_arg) { + String arg = args[first_arg]; + int remaining_args = args.length - first_arg; + if (remaining_args >= 2 && arg.equals("-one")) { + LOGGER.info("Creating one event metric to catch all pushed atoms."); + configuration.mOnePushedAtomEvent = true; + } else if (remaining_args >= 2 && arg.equals("-terse")) { + LOGGER.info("Terse output format."); + mDumper = new TerseDumper(); + } else if (remaining_args >= 3 && arg.equals("-p")) { + configuration.mAdditionalAllowedPackage = args[++first_arg]; + } else if (remaining_args >= 3 && arg.equals("-s")) { + mDeviceSerial = args[++first_arg]; + } else { + break; // Found the atom list + } + } + + mDeviceSerial = Utils.chooseDevice(mDeviceSerial, connectedDevices, defaultDevice, LOGGER); + if (mDeviceSerial == null) { + return false; + } + + for ( ; first_arg < args.length; ++first_arg) { + String atom = args[first_arg]; + try { + configuration.addAtom(Integer.valueOf(atom)); + } catch (NumberFormatException e) { + LOGGER.severe("Bad atom id provided: " + atom); + } + } + + return configuration.hasPulledAtoms() || configuration.hasPushedAtoms(); + } + + private ConfigMetricsReportList testDriveAndGetReports(StatsdConfig config, + boolean hasPulledAtoms, boolean hasPushedAtoms) { + if (config == null) { + LOGGER.severe("Failed to create valid config."); + return null; + } + + String remoteConfigPath = null; + try { + remoteConfigPath = pushConfig(config, mDeviceSerial); + LOGGER.info("Pushed the following config to statsd on device '" + mDeviceSerial + + "':"); + LOGGER.info(config.toString()); + if (hasPushedAtoms) { + LOGGER.info("Now please play with the device to trigger the event."); + } + if (!hasPulledAtoms) { + LOGGER.info( + "All events should be dumped after 1 min ..."); + Thread.sleep(60_000); + } else { + LOGGER.info("All events should be dumped after 1.5 minutes ..."); + Thread.sleep(15_000); + Utils.logAppBreadcrumb(0, 0, LOGGER, mDeviceSerial); + Thread.sleep(75_000); + } + return Utils.getReportList(CONFIG_ID, true, false, LOGGER, + mDeviceSerial); + } catch (Exception e) { + LOGGER.log(Level.SEVERE, "Failed to test drive: " + e.getMessage(), e); + } finally { + removeConfig(mDeviceSerial); + if (remoteConfigPath != null) { + try { + Utils.runCommand(null, LOGGER, + "adb", "-s", mDeviceSerial, "shell", "rm", + remoteConfigPath); + } catch (Exception e) { + LOGGER.log(Level.WARNING, + "Unable to remove remote config file: " + remoteConfigPath, e); + } + } + } + return null; + } + + static class Configuration { + boolean mOnePushedAtomEvent = false; + @VisibleForTesting + Set mPushedAtoms = new TreeSet<>(); + @VisibleForTesting + Set mPulledAtoms = new TreeSet<>(); + @VisibleForTesting + String mAdditionalAllowedPackage = null; + private final Set mTrackedMetrics = new HashSet<>(); + + private void dumpMetrics(ConfigMetricsReportList reportList, Dumper dumper) { + // We may get multiple reports. Take the last one. + ConfigMetricsReport report = reportList.getReports(reportList.getReportsCount() - 1); + for (StatsLogReport statsLog : report.getMetricsList()) { + if (isTrackedMetric(statsLog.getMetricId())) { + dumper.dump(statsLog); + } + } + } + + boolean isTrackedMetric(long metricId) { + return mTrackedMetrics.contains(metricId); + } + + static boolean isPulledAtom(int atomId) { + return atomId >= PULL_ATOM_START && atomId <= MAX_PLATFORM_ATOM_TAG + || atomId >= VENDOR_PULLED_ATOM_START_TAG; + } + + void addAtom(Integer atom) { + if (Atom.getDescriptor().findFieldByNumber(atom) == null) { + LOGGER.severe("No such atom found: " + atom); + return; + } + if (isPulledAtom(atom)) { + mPulledAtoms.add(atom); + } else { + mPushedAtoms.add(atom); + } + } + + private boolean hasPulledAtoms() { + return !mPulledAtoms.isEmpty(); + } + + private boolean hasPushedAtoms() { + return !mPushedAtoms.isEmpty(); + } + + StatsdConfig createConfig() { + long metricId = METRIC_ID_BASE; + long atomMatcherId = ATOM_MATCHER_ID_BASE; + + StatsdConfig.Builder builder = baseBuilder(); + + if (hasPulledAtoms()) { + builder.addAtomMatcher( + createAtomMatcher( + Atom.APP_BREADCRUMB_REPORTED_FIELD_NUMBER, + APP_BREADCRUMB_MATCHER_ID)); + } + + for (int atomId : mPulledAtoms) { + builder.addAtomMatcher(createAtomMatcher(atomId, atomMatcherId)); + GaugeMetric.Builder gaugeMetricBuilder = GaugeMetric.newBuilder(); + gaugeMetricBuilder + .setId(metricId) + .setWhat(atomMatcherId) + .setTriggerEvent(APP_BREADCRUMB_MATCHER_ID) + .setGaugeFieldsFilter(FieldFilter.newBuilder().setIncludeAll(true).build()) + .setBucket(TimeUnit.ONE_MINUTE) + .setSamplingType(GaugeMetric.SamplingType.FIRST_N_SAMPLES) + .setMaxNumGaugeAtomsPerBucket(100); + builder.addGaugeMetric(gaugeMetricBuilder.build()); + atomMatcherId++; + mTrackedMetrics.add(metricId++); + } + + // A simple atom matcher for each pushed atom. + List simpleAtomMatchers = new ArrayList<>(); + for (int atomId : mPushedAtoms) { + final AtomMatcher atomMatcher = createAtomMatcher(atomId, atomMatcherId++); + simpleAtomMatchers.add(atomMatcher); + builder.addAtomMatcher(atomMatcher); + } + + if (mOnePushedAtomEvent) { + // Create a union event metric, using an matcher that matches all pulled atoms. + AtomMatcher unionAtomMatcher = createUnionMatcher(simpleAtomMatchers, + atomMatcherId); + builder.addAtomMatcher(unionAtomMatcher); + EventMetric.Builder eventMetricBuilder = EventMetric.newBuilder(); + eventMetricBuilder.setId(metricId).setWhat(unionAtomMatcher.getId()); + builder.addEventMetric(eventMetricBuilder.build()); + mTrackedMetrics.add(metricId++); + } else { + // Create multiple event metrics, one per pulled atom. + for (AtomMatcher atomMatcher : simpleAtomMatchers) { + EventMetric.Builder eventMetricBuilder = EventMetric.newBuilder(); + eventMetricBuilder + .setId(metricId) + .setWhat(atomMatcher.getId()); + builder.addEventMetric(eventMetricBuilder.build()); + mTrackedMetrics.add(metricId++); + } + } + + return builder.build(); + } + + private static AtomMatcher createAtomMatcher(int atomId, long matcherId) { + AtomMatcher.Builder atomMatcherBuilder = AtomMatcher.newBuilder(); + atomMatcherBuilder + .setId(matcherId) + .setSimpleAtomMatcher(SimpleAtomMatcher.newBuilder().setAtomId(atomId)); + return atomMatcherBuilder.build(); + } + + private AtomMatcher createUnionMatcher(List simpleAtomMatchers, + long atomMatcherId) { + AtomMatcher.Combination.Builder combinationBuilder = + AtomMatcher.Combination.newBuilder(); + combinationBuilder.setOperation(StatsdConfigProto.LogicalOperation.OR); + for (AtomMatcher matcher : simpleAtomMatchers) { + combinationBuilder.addMatcher(matcher.getId()); + } + AtomMatcher.Builder atomMatcherBuilder = AtomMatcher.newBuilder(); + atomMatcherBuilder.setId(atomMatcherId).setCombination(combinationBuilder.build()); + return atomMatcherBuilder.build(); + } + + private StatsdConfig.Builder baseBuilder() { + ArrayList allowedSources = new ArrayList<>(); + Collections.addAll(allowedSources, ALLOWED_LOG_SOURCES); + if (mAdditionalAllowedPackage != null) { + allowedSources.add(mAdditionalAllowedPackage); + } + return StatsdConfig.newBuilder() + .addAllAllowedLogSource(allowedSources) + .addAllDefaultPullPackages(Arrays.asList(DEFAULT_PULL_SOURCES)) + .addPullAtomPackages(PullAtomPackages.newBuilder() + .setAtomId(Atom.GPU_STATS_GLOBAL_INFO_FIELD_NUMBER) + .addPackages("AID_GPU_SERVICE")) + .addPullAtomPackages(PullAtomPackages.newBuilder() + .setAtomId(Atom.GPU_STATS_APP_INFO_FIELD_NUMBER) + .addPackages("AID_GPU_SERVICE")) + .addPullAtomPackages(PullAtomPackages.newBuilder() + .setAtomId(Atom.TRAIN_INFO_FIELD_NUMBER) + .addPackages("AID_STATSD")) + .addPullAtomPackages(PullAtomPackages.newBuilder() + .setAtomId(Atom.GENERAL_EXTERNAL_STORAGE_ACCESS_STATS_FIELD_NUMBER) + .addPackages("com.google.android.providers.media.module")) + .setHashStringsInMetricReport(false); + } + } + + interface Dumper { + void dump(StatsLogReport report); + } + + static class BasicDumper implements Dumper { + @Override + public void dump(StatsLogReport report) { + System.out.println(report.toString()); + } + } + + static class TerseDumper extends BasicDumper { + @Override + public void dump(StatsLogReport report) { + if (report.hasGaugeMetrics()) { + dumpGaugeMetrics(report); + } + if (report.hasEventMetrics()) { + dumpEventMetrics(report); + } + } + void dumpEventMetrics(StatsLogReport report) { + final List data = report.getEventMetrics().getDataList(); + if (data.isEmpty()) { + return; + } + long firstTimestampNanos = data.get(0).getElapsedTimestampNanos(); + for (StatsLog.EventMetricData event : data) { + final double deltaSec = (event.getElapsedTimestampNanos() - firstTimestampNanos) + / 1e9; + System.out.println( + String.format("+%.3fs: %s", deltaSec, event.getAtom().toString())); + } + } + void dumpGaugeMetrics(StatsLogReport report) { + final List data = report.getGaugeMetrics().getDataList(); + if (data.isEmpty()) { + return; + } + for (StatsLog.GaugeMetricData gauge : data) { + System.out.println(gauge.toString()); + } + } + } + + private static String pushConfig(StatsdConfig config, String deviceSerial) + throws IOException, InterruptedException { + File configFile = File.createTempFile("statsdconfig", ".config"); + configFile.deleteOnExit(); + Files.write(config.toByteArray(), configFile); + String remotePath = "/data/local/tmp/" + configFile.getName(); + Utils.runCommand(null, LOGGER, "adb", "-s", deviceSerial, + "push", configFile.getAbsolutePath(), remotePath); + Utils.runCommand(null, LOGGER, "adb", "-s", deviceSerial, + "shell", "cat", remotePath, "|", Utils.CMD_UPDATE_CONFIG, + String.valueOf(CONFIG_ID)); + return remotePath; + } + + private static void removeConfig(String deviceSerial) { + try { + Utils.runCommand(null, LOGGER, "adb", "-s", deviceSerial, + "shell", Utils.CMD_REMOVE_CONFIG, String.valueOf(CONFIG_ID)); + } catch (Exception e) { + LOGGER.severe("Failed to remove config: " + e.getMessage()); + } + } +} diff --git a/statsd/tools/localtools/test/com/android/statsd/shelltools/testdrive/ConfigurationTest.java b/statsd/tools/localtools/test/com/android/statsd/shelltools/testdrive/ConfigurationTest.java new file mode 100644 index 00000000..b1cc60f7 --- /dev/null +++ b/statsd/tools/localtools/test/com/android/statsd/shelltools/testdrive/ConfigurationTest.java @@ -0,0 +1,326 @@ +/* + * Copyright (C) 2020 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. + */ + +package com.android.statsd.shelltools.testdrive; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import com.android.internal.os.StatsdConfigProto; +import com.android.internal.os.StatsdConfigProto.StatsdConfig; +import com.android.os.AtomsProto; + +import org.junit.Test; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * Tests for {@link TestDrive} + */ +public class ConfigurationTest { + + private StatsdConfigProto.AtomMatcher findAndRemoveAtomMatcherById( + List atomMatchers, long id) { + int numMatches = 0; + StatsdConfigProto.AtomMatcher match = null; + for (StatsdConfigProto.AtomMatcher atomMatcher : atomMatchers) { + if (id == atomMatcher.getId()) { + ++numMatches; + match = atomMatcher; + } + } + if (numMatches == 1) { + atomMatchers.remove(match); + return match; + } + return null; // Too many, or not found + } + + private final TestDrive.Configuration mConfiguration = new TestDrive.Configuration(); + + @Test + public void testOnePushed() { + final int atom = 90; + assertFalse(TestDrive.Configuration.isPulledAtom(atom)); + mConfiguration.addAtom(atom); + StatsdConfig config = mConfiguration.createConfig(); + + //event_metric { + // id: 1111 + // what: 1234567 + //} + //atom_matcher { + // id: 1234567 + // simple_atom_matcher { + // atom_id: 90 + // } + //} + + assertEquals(1, config.getEventMetricCount()); + assertEquals(0, config.getGaugeMetricCount()); + + assertTrue(mConfiguration.isTrackedMetric(config.getEventMetric(0).getId())); + + final List atomMatchers = + new ArrayList<>(config.getAtomMatcherList()); + assertEquals(atom, + findAndRemoveAtomMatcherById(atomMatchers, config.getEventMetric(0).getWhat()) + .getSimpleAtomMatcher().getAtomId()); + assertEquals(0, atomMatchers.size()); + } + + @Test + public void testOnePulled() { + final int atom = 10022; + assertTrue(TestDrive.Configuration.isPulledAtom(atom)); + mConfiguration.addAtom(atom); + StatsdConfig config = mConfiguration.createConfig(); + + //gauge_metric { + // id: 1111 + // what: 1234567 + // gauge_fields_filter { + // include_all: true + // } + // bucket: ONE_MINUTE + // sampling_type: FIRST_N_SAMPLES + // max_num_gauge_atoms_per_bucket: 100 + // trigger_event: 1111111 + //} + //atom_matcher { + // id: 1111111 + // simple_atom_matcher { + // atom_id: 47 + // } + //} + //atom_matcher { + // id: 1234567 + // simple_atom_matcher { + // atom_id: 10022 + // } + //} + + assertEquals(0, config.getEventMetricCount()); + assertEquals(1, config.getGaugeMetricCount()); + + assertTrue(mConfiguration.isTrackedMetric(config.getGaugeMetric(0).getId())); + + final StatsdConfigProto.GaugeMetric gaugeMetric = config.getGaugeMetric(0); + assertTrue(gaugeMetric.getGaugeFieldsFilter().getIncludeAll()); + + final List atomMatchers = + new ArrayList<>(config.getAtomMatcherList()); + assertEquals(atom, + findAndRemoveAtomMatcherById(atomMatchers, gaugeMetric.getWhat()) + .getSimpleAtomMatcher().getAtomId()); + assertEquals(AtomsProto.Atom.APP_BREADCRUMB_REPORTED_FIELD_NUMBER, + findAndRemoveAtomMatcherById(atomMatchers, gaugeMetric.getTriggerEvent()) + .getSimpleAtomMatcher().getAtomId()); + assertEquals(0, atomMatchers.size()); + } + + @Test + public void testOnePulledTwoPushed() { + final int pulledAtom = 10022; + assertTrue(TestDrive.Configuration.isPulledAtom(pulledAtom)); + mConfiguration.addAtom(pulledAtom); + + Integer[] pushedAtoms = new Integer[]{244, 245}; + for (int atom : pushedAtoms) { + assertFalse(TestDrive.Configuration.isPulledAtom(atom)); + mConfiguration.addAtom(atom); + } + StatsdConfig config = mConfiguration.createConfig(); + + // event_metric { + // id: 1111 + // what: 1234567 + // } + // event_metric { + // id: 1112 + // what: 1234568 + // } + // gauge_metric { + // id: 1114 + // what: 1234570 + // gauge_fields_filter { + // include_all: true + // } + // bucket: ONE_MINUTE + // sampling_type: FIRST_N_SAMPLES + // max_num_gauge_atoms_per_bucket: 100 + // trigger_event: 1111111 + // } + // atom_matcher { + // id: 1111111 + // simple_atom_matcher { + // atom_id: 47 + // } + // } + // atom_matcher { + // id: 1234567 + // simple_atom_matcher { + // atom_id: 244 + // } + // } + // atom_matcher { + // id: 1234568 + // simple_atom_matcher { + // atom_id: 245 + // } + // } + // atom_matcher { + // id: 1234570 + // simple_atom_matcher { + // atom_id: 10022 + // } + // } + + assertEquals(2, config.getEventMetricCount()); + assertEquals(1, config.getGaugeMetricCount()); + + final StatsdConfigProto.GaugeMetric gaugeMetric = config.getGaugeMetric(0); + assertTrue(mConfiguration.isTrackedMetric(gaugeMetric.getId())); + assertTrue(gaugeMetric.getGaugeFieldsFilter().getIncludeAll()); + for (StatsdConfigProto.EventMetric eventMetric : config.getEventMetricList()) { + assertTrue(mConfiguration.isTrackedMetric(eventMetric.getId())); + } + + final List atomMatchers = + new ArrayList<>(config.getAtomMatcherList()); + + assertEquals(pulledAtom, findAndRemoveAtomMatcherById(atomMatchers, gaugeMetric.getWhat()) + .getSimpleAtomMatcher().getAtomId()); + assertEquals(AtomsProto.Atom.APP_BREADCRUMB_REPORTED_FIELD_NUMBER, + findAndRemoveAtomMatcherById(atomMatchers, gaugeMetric.getTriggerEvent()) + .getSimpleAtomMatcher().getAtomId()); + + Integer[] actualAtoms = new Integer[]{ + findAndRemoveAtomMatcherById(atomMatchers, config.getEventMetric(0).getWhat()) + .getSimpleAtomMatcher().getAtomId(), + findAndRemoveAtomMatcherById(atomMatchers, config.getEventMetric(1).getWhat()) + .getSimpleAtomMatcher().getAtomId()}; + Arrays.sort(actualAtoms); + assertArrayEquals(pushedAtoms, actualAtoms); + + assertEquals(0, atomMatchers.size()); + } + + @Test + public void testOnePulledTwoPushedTogether() { + mConfiguration.mOnePushedAtomEvent = true; // Use one event grabbing all pushed atoms + + final int pulledAtom = 10022; + assertTrue(TestDrive.Configuration.isPulledAtom(pulledAtom)); + mConfiguration.addAtom(pulledAtom); + + Integer[] pushedAtoms = new Integer[]{244, 245}; + for (int atom : pushedAtoms) { + assertFalse(TestDrive.Configuration.isPulledAtom(atom)); + mConfiguration.addAtom(atom); + } + StatsdConfig config = mConfiguration.createConfig(); + + // event_metric { + // id: 1112 + // what: 1234570 + // } + // gauge_metric { + // id: 1111 + // what: 1234567 + // gauge_fields_filter { + // include_all: true + // } + // bucket: ONE_MINUTE + // sampling_type: FIRST_N_SAMPLES + // max_num_gauge_atoms_per_bucket: 100 + // trigger_event: 1111111 + // } + // atom_matcher { + // id: 1111111 + // simple_atom_matcher { + // atom_id: 47 + // } + // } + // atom_matcher { + // id: 1234567 + // simple_atom_matcher { + // atom_id: 10022 + // } + // } + // atom_matcher { + // id: 1234568 + // simple_atom_matcher { + // atom_id: 244 + // } + // } + // atom_matcher { + // id: 1234569 + // simple_atom_matcher { + // atom_id: 245 + // } + // } + // atom_matcher { + // id: 1234570 + // combination { + // operation: OR + // matcher: 1234568 + // matcher: 1234569 + // } + // } + + assertEquals(1, config.getEventMetricCount()); + assertEquals(1, config.getGaugeMetricCount()); + + final StatsdConfigProto.GaugeMetric gaugeMetric = config.getGaugeMetric(0); + assertTrue(mConfiguration.isTrackedMetric(gaugeMetric.getId())); + assertTrue(gaugeMetric.getGaugeFieldsFilter().getIncludeAll()); + + StatsdConfigProto.EventMetric eventMetric = config.getEventMetric(0); + assertTrue(mConfiguration.isTrackedMetric(eventMetric.getId())); + + final List atomMatchers = + new ArrayList<>(config.getAtomMatcherList()); + + assertEquals(pulledAtom, findAndRemoveAtomMatcherById(atomMatchers, gaugeMetric.getWhat()) + .getSimpleAtomMatcher().getAtomId()); + assertEquals(AtomsProto.Atom.APP_BREADCRUMB_REPORTED_FIELD_NUMBER, + findAndRemoveAtomMatcherById(atomMatchers, gaugeMetric.getTriggerEvent()) + .getSimpleAtomMatcher().getAtomId()); + + StatsdConfigProto.AtomMatcher unionMatcher = findAndRemoveAtomMatcherById(atomMatchers, + eventMetric.getWhat()); + assertNotNull(unionMatcher.getCombination()); + assertEquals(2, unionMatcher.getCombination().getMatcherCount()); + + Integer[] actualAtoms = new Integer[]{ + findAndRemoveAtomMatcherById(atomMatchers, + unionMatcher.getCombination().getMatcher(0)) + .getSimpleAtomMatcher().getAtomId(), + findAndRemoveAtomMatcherById(atomMatchers, + unionMatcher.getCombination().getMatcher(1)) + .getSimpleAtomMatcher().getAtomId()}; + Arrays.sort(actualAtoms); + assertArrayEquals(pushedAtoms, actualAtoms); + + assertEquals(0, atomMatchers.size()); + } +} diff --git a/statsd/tools/localtools/test/com/android/statsd/shelltools/testdrive/TestDriveTest.java b/statsd/tools/localtools/test/com/android/statsd/shelltools/testdrive/TestDriveTest.java new file mode 100644 index 00000000..363fac0c --- /dev/null +++ b/statsd/tools/localtools/test/com/android/statsd/shelltools/testdrive/TestDriveTest.java @@ -0,0 +1,195 @@ +/* + * Copyright (C) 2020 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. + */ + +package com.android.statsd.shelltools.testdrive; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.List; + +/** + * Tests for {@link TestDrive} + */ +@RunWith(Parameterized.class) +public class TestDriveTest { + /** + * Expected results of a single iteration of the paramerized test. + */ + static class Expect { + public boolean success; + public Integer[] atoms; + public boolean onePushedAtomEvent = false; + public String extraPackage = null; + public String target; + public boolean terse = false; + + static Expect success(Integer... atoms) { + return new Expect(true, atoms, + TARGET); + } + Expect(boolean success, Integer[] atoms, String target) { + this.success = success; + this.atoms = atoms; + this.target = target; + } + static final Expect FAILURE = new Expect(false, null, null); + Expect onePushedAtomEvent() { + this.onePushedAtomEvent = true; + return this; + } + Expect extraPackage() { + this.extraPackage = TestDriveTest.PACKAGE; + return this; + } + Expect terse() { + this.terse = true; + return this; + } + } + + @Parameterized.Parameter(0) + public String[] mArgs; + + @Parameterized.Parameter(1) + public List mConnectedDevices; + + @Parameterized.Parameter(2) + public String mDefaultDevice; + + @Parameterized.Parameter(3) + public Expect mExpect; + + private static final String TARGET = "target"; + private static final List TARGET_AND_OTHER = Arrays.asList("otherDevice", + TARGET); + private static final List TWO_OTHER_DEVICES = Arrays.asList( + "other1", "other2"); + private static final List TARGET_ONLY = Collections.singletonList(TARGET); + private static final List NOT_TARGET = Collections.singletonList("other"); + private static final List NO_DEVICES = Collections.emptyList(); + private static final String PACKAGE = "extraPackage"; + + @Parameterized.Parameters + public static Collection data() { + return Arrays.asList( + new Object[]{new String[]{}, null, null, + Expect.FAILURE}, // Usage explanation + new Object[]{new String[]{"244", "245"}, null, null, + Expect.FAILURE}, // Failure looking up connected devices + new Object[]{new String[]{"244", "245"}, NO_DEVICES, null, + Expect.FAILURE}, // No connected devices + new Object[]{new String[]{"-s", TARGET, "244", "245"}, NOT_TARGET, null, + Expect.FAILURE}, // Wrong device connected + new Object[]{new String[]{"244", "245"}, TWO_OTHER_DEVICES, null, + Expect.FAILURE}, // Wrong devices connected + new Object[]{new String[]{"244", "245"}, TARGET_ONLY, null, + Expect.success(244, 245)}, // If only one device connected, guess that one + new Object[]{new String[]{"244", "not_an_atom"}, TARGET_ONLY, null, + Expect.success(244)}, // Ignore non-atoms + new Object[]{new String[]{"not_an_atom"}, TARGET_ONLY, null, + Expect.FAILURE}, // Require at least one atom + new Object[]{new String[]{"244", "245"}, TWO_OTHER_DEVICES, TARGET, + Expect.FAILURE}, // ANDROID_SERIAL specifies non-connected target + new Object[]{new String[]{"244", "245"}, TARGET_AND_OTHER, TARGET, + Expect.success(244, 245)}, // ANDROID_SERIAL specifies a valid target + new Object[]{new String[]{"244", "245"}, TARGET_AND_OTHER, null, + Expect.FAILURE}, // Two connected devices, no indication of which to use + new Object[]{new String[]{"-one", "244", "245"}, TARGET_ONLY, null, + Expect.success(244, 245).onePushedAtomEvent()}, + new Object[]{new String[]{"-terse", "-one", "244", "245"}, TARGET_ONLY, null, + Expect.success(244, 245).onePushedAtomEvent().terse()}, + new Object[]{new String[]{"-one", "-terse", "244", "245"}, TARGET_ONLY, null, + Expect.success(244, 245).onePushedAtomEvent().terse()}, + new Object[]{new String[]{"-p", PACKAGE, "244", "245"}, TARGET_ONLY, null, + Expect.success(244, 245).extraPackage()}, + new Object[]{new String[]{"-p", PACKAGE, "-one", "244", "245"}, TARGET_ONLY, null, + Expect.success(244, 245).extraPackage().onePushedAtomEvent()}, + new Object[]{new String[]{"-one", "-p", PACKAGE, "244", "245"}, TARGET_ONLY, null, + Expect.success(244, 245).extraPackage().onePushedAtomEvent()}, + new Object[]{new String[]{"-s", TARGET, "-one", "-p", PACKAGE, "244", "245"}, + TARGET_AND_OTHER, null, + Expect.success(244, 245).extraPackage().onePushedAtomEvent()}, + new Object[]{new String[]{"-one", "-s", TARGET, "-p", PACKAGE, "244", "245"}, + TARGET_AND_OTHER, null, + Expect.success(244, 245).extraPackage().onePushedAtomEvent()}, + new Object[]{new String[]{"-one", "-p", PACKAGE, "-s", TARGET, "244", "245"}, + TARGET_AND_OTHER, null, + Expect.success(244, 245).extraPackage().onePushedAtomEvent()}, + new Object[]{new String[]{"-terse", "-one", "-p", PACKAGE, "-s", TARGET, + "244", "245"}, + TARGET_AND_OTHER, null, + Expect.success(244, 245).extraPackage().onePushedAtomEvent().terse()}, + new Object[]{new String[]{"-one", "-terse", "-p", PACKAGE, "-s", TARGET, + "244", "245"}, + TARGET_AND_OTHER, null, + Expect.success(244, 245).extraPackage().onePushedAtomEvent().terse()}, + new Object[]{new String[]{"-one", "-p", PACKAGE, "-terse", "-s", TARGET, + "244", "245"}, + TARGET_AND_OTHER, null, + Expect.success(244, 245).extraPackage().onePushedAtomEvent().terse()}, + new Object[]{new String[]{"-one", "-p", PACKAGE, "-s", TARGET, "-terse", + "244", "245"}, + TARGET_AND_OTHER, null, + Expect.success(244, 245).extraPackage().onePushedAtomEvent().terse()} + ); + } + + private final TestDrive.Configuration mConfiguration = new TestDrive.Configuration(); + private final TestDrive mTestDrive = new TestDrive(); + + private static Integer[] collectAtoms(TestDrive.Configuration configuration) { + Integer[] result = new Integer[configuration.mPulledAtoms.size() + + configuration.mPushedAtoms.size()]; + int result_index = 0; + for (Integer atom : configuration.mPushedAtoms) { + result[result_index++] = atom; + } + for (Integer atom : configuration.mPulledAtoms) { + result[result_index++] = atom; + } + Arrays.sort(result); + return result; + } + + @Test + public void testProcessArgs() { + boolean result = mTestDrive.processArgs(mConfiguration, mArgs, mConnectedDevices, + mDefaultDevice); + if (mExpect.success) { + assertTrue(result); + assertArrayEquals(mExpect.atoms, collectAtoms(mConfiguration)); + assertEquals(mExpect.onePushedAtomEvent, mConfiguration.mOnePushedAtomEvent); + assertEquals(mExpect.target, mTestDrive.mDeviceSerial); + if (mExpect.terse) { + assertEquals(TestDrive.TerseDumper.class, mTestDrive.mDumper.getClass()); + } else { + assertEquals(TestDrive.BasicDumper.class, mTestDrive.mDumper.getClass()); + } + } else { + assertFalse(result); + } + } +} diff --git a/statsd/tools/localtools/testdrive_manifest.txt b/statsd/tools/localtools/testdrive_manifest.txt new file mode 100644 index 00000000..625ebfa4 --- /dev/null +++ b/statsd/tools/localtools/testdrive_manifest.txt @@ -0,0 +1 @@ +Main-class: com.android.statsd.shelltools.testdrive.TestDrive -- cgit v1.2.3 From 9ac4cd40c020a11591250516f0835ef0bc869306 Mon Sep 17 00:00:00 2001 From: Chris Wailes Date: Thu, 18 Mar 2021 15:46:28 -0700 Subject: Added artd to the UID map Test: build and boot Bug: 177273468 Change-Id: Ief3d8fe12152e8953cd3d3169b7bc3e224144e34 Merged-In: Ibb065d64be3775c699583215f7f376a1a5492ac1 --- statsd/src/packages/UidMap.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/statsd/src/packages/UidMap.cpp b/statsd/src/packages/UidMap.cpp index 32be1c6b..56a3f2b8 100644 --- a/statsd/src/packages/UidMap.cpp +++ b/statsd/src/packages/UidMap.cpp @@ -554,6 +554,7 @@ const std::map UidMap::sAidToUidMapping = {{"AID_ROOT", 0}, {"AID_EXT_DATA_RW", 1078}, {"AID_EXT_OBB_RW", 1079}, {"AID_CONTEXT_HUB", 1080}, + {"AID_ARTD", 1082}, {"AID_SHELL", 2000}, {"AID_CACHE", 2001}, {"AID_DIAG", 2002}, -- cgit v1.2.3 From 2082d91fdcddac5936f4f5e3a74c1d620942271a Mon Sep 17 00:00:00 2001 From: Jeffrey Huang Date: Fri, 23 Apr 2021 11:16:31 -0700 Subject: Deflake TestResetBaseOnPullDelayExceeded The test previously set max_pull_delay to 0, but value metric does not allow a value of 0 and makes it the default value. If the pull happens too quickly in the test, it will cause a failure. Bug: 182961471 Test: atest statsd_test Change-Id: I8feb5249a1e5cc229e6b0144f8da43f35fde77ad Merged-In: Ibb065d64be3775c699583215f7f376a1a5492ac1 --- statsd/src/metrics/ValueMetricProducer.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/statsd/src/metrics/ValueMetricProducer.cpp b/statsd/src/metrics/ValueMetricProducer.cpp index e766289f..af0e6b1c 100644 --- a/statsd/src/metrics/ValueMetricProducer.cpp +++ b/statsd/src/metrics/ValueMetricProducer.cpp @@ -114,7 +114,7 @@ ValueMetricProducer::ValueMetricProducer( mUseZeroDefaultBase(metric.use_zero_default_base()), mHasGlobalBase(false), mCurrentBucketIsSkipped(false), - mMaxPullDelayNs(metric.max_pull_delay_sec() > 0 ? metric.max_pull_delay_sec() * NS_PER_SEC + mMaxPullDelayNs(metric.has_max_pull_delay_sec() ? metric.max_pull_delay_sec() * NS_PER_SEC : StatsdStats::kPullMaxDelayNs), mSplitBucketForAppUpgrade(metric.split_bucket_for_app_upgrade()), // Condition timer will be set later within the constructor after pulling events -- cgit v1.2.3 From a72195a78e161eee2c4daa16c58cd69c6c099d6e Mon Sep 17 00:00:00 2001 From: tsaichristine Date: Mon, 10 May 2021 11:58:31 -0700 Subject: Remove StatsdStats logging for late events in duration metric This logging was added to track the number of late events that occur in duration metric which is no longer needed. Test: atest statsd_test Bug: 187730992 Change-Id: I964a42008b006e96781174496123451a5ee2c9c5 Merged-In: Ibb065d64be3775c699583215f7f376a1a5492ac1 --- statsd/src/guardrail/StatsdStats.cpp | 9 -------- statsd/src/guardrail/StatsdStats.h | 8 ------- statsd/src/metrics/DurationMetricProducer.cpp | 30 --------------------------- statsd/src/stats_log.proto | 4 +--- statsd/src/stats_log_util.cpp | 9 -------- statsd/tests/guardrail/StatsdStats_test.cpp | 9 -------- 6 files changed, 1 insertion(+), 68 deletions(-) diff --git a/statsd/src/guardrail/StatsdStats.cpp b/statsd/src/guardrail/StatsdStats.cpp index f4e01ced..53c13dfd 100644 --- a/statsd/src/guardrail/StatsdStats.cpp +++ b/statsd/src/guardrail/StatsdStats.cpp @@ -521,15 +521,6 @@ void StatsdStats::noteLateLogEventSkipped(int64_t metricId) { getAtomMetricStats(metricId).lateLogEventSkipped++; } -void StatsdStats::noteLateLogEvent(int64_t metricId, int64_t extraDurationNs) { - lock_guard lock(mLock); - AtomMetricStats& metricStats = getAtomMetricStats(metricId); - metricStats.lateLogEvent++; - metricStats.sumLateLogEventExtraDurationNs += extraDurationNs; - metricStats.maxLateLogEventExtraDurationNs = - std::max(metricStats.maxLateLogEventExtraDurationNs, extraDurationNs); -} - void StatsdStats::noteSkippedForwardBuckets(int64_t metricId) { lock_guard lock(mLock); getAtomMetricStats(metricId).skippedForwardBuckets++; diff --git a/statsd/src/guardrail/StatsdStats.h b/statsd/src/guardrail/StatsdStats.h index 40cfa3ac..ef7663a6 100644 --- a/statsd/src/guardrail/StatsdStats.h +++ b/statsd/src/guardrail/StatsdStats.h @@ -418,11 +418,6 @@ public: */ void noteLateLogEventSkipped(int64_t metricId); - /** - * A log event was too late, arrived in the wrong bucket. - */ - void noteLateLogEvent(int64_t metricId, int64_t extraDurationNs); - /** * Buckets were skipped as time elapsed without any data for them */ @@ -547,9 +542,6 @@ public: int64_t maxBucketBoundaryDelayNs = 0; long bucketUnknownCondition = 0; long bucketCount = 0; - long lateLogEvent = 0; - int64_t sumLateLogEventExtraDurationNs = 0; - int64_t maxLateLogEventExtraDurationNs = 0; } AtomMetricStats; private: diff --git a/statsd/src/metrics/DurationMetricProducer.cpp b/statsd/src/metrics/DurationMetricProducer.cpp index 733fb388..04c18e32 100644 --- a/statsd/src/metrics/DurationMetricProducer.cpp +++ b/statsd/src/metrics/DurationMetricProducer.cpp @@ -307,12 +307,6 @@ void DurationMetricProducer::onStateChanged(const int64_t eventTimeNs, const int flushIfNeededLocked(eventTimeNs); - // Log late event and extra duration. - if (eventTimeNs < mCurrentBucketStartTimeNs) { - StatsdStats::getInstance().noteLateLogEvent(mMetricId, - mCurrentBucketStartTimeNs - eventTimeNs); - } - // Each duration tracker is mapped to a different whatKey (a set of values from the // dimensionsInWhat fields). We notify all trackers iff the primaryKey field values from the // state change event are a subset of the tracker's whatKey field values. @@ -431,12 +425,6 @@ void DurationMetricProducer::onSlicedConditionMayChangeLocked(bool overallCondit return; } - // Log late event and extra duration. - if (eventTime < mCurrentBucketStartTimeNs) { - StatsdStats::getInstance().noteLateLogEvent(mMetricId, - mCurrentBucketStartTimeNs - eventTime); - } - flushIfNeededLocked(eventTime); if (!mConditionSliced) { @@ -454,12 +442,6 @@ void DurationMetricProducer::onActiveStateChangedLocked(const int64_t& eventTime return; } - // Log late event and extra duration. - if (eventTimeNs < mCurrentBucketStartTimeNs) { - StatsdStats::getInstance().noteLateLogEvent(mMetricId, - mCurrentBucketStartTimeNs - eventTimeNs); - } - if (mIsActive) { flushIfNeededLocked(eventTimeNs); } @@ -486,12 +468,6 @@ void DurationMetricProducer::onConditionChangedLocked(const bool conditionMet, return; } - // Log late event and extra duration. - if (eventTime < mCurrentBucketStartTimeNs) { - StatsdStats::getInstance().noteLateLogEvent(mMetricId, - mCurrentBucketStartTimeNs - eventTime); - } - flushIfNeededLocked(eventTime); for (auto& whatIt : mCurrentSlicedDurationTrackerMap) { whatIt.second->onConditionChanged(conditionMet, eventTime); @@ -708,12 +684,6 @@ void DurationMetricProducer::handleMatchedLogEventValuesLocked(const size_t matc return; } - // Log late event and extra duration. - if (eventTimeNs < mCurrentBucketStartTimeNs) { - StatsdStats::getInstance().noteLateLogEvent(mMetricId, - mCurrentBucketStartTimeNs - eventTimeNs); - } - if (mIsActive) { flushIfNeededLocked(eventTimeNs); } diff --git a/statsd/src/stats_log.proto b/statsd/src/stats_log.proto index f91c4c08..f99af10d 100644 --- a/statsd/src/stats_log.proto +++ b/statsd/src/stats_log.proto @@ -488,9 +488,7 @@ message StatsdStatsReport { optional int64 max_bucket_boundary_delay_ns = 10; optional int64 bucket_unknown_condition = 11; optional int64 bucket_count = 12; - optional int64 late_log_event = 13; - optional int64 sum_late_log_event_extra_duration_ns = 14; - optional int64 max_late_log_event_extra_duration_ns = 15; + reserved 13 to 15; } repeated AtomMetricStats atom_metric_stats = 17; diff --git a/statsd/src/stats_log_util.cpp b/statsd/src/stats_log_util.cpp index a7e4d703..1f5dd01c 100644 --- a/statsd/src/stats_log_util.cpp +++ b/statsd/src/stats_log_util.cpp @@ -99,9 +99,6 @@ const int FIELD_ID_MIN_BUCKET_BOUNDARY_DELAY_NS = 9; const int FIELD_ID_MAX_BUCKET_BOUNDARY_DELAY_NS = 10; const int FIELD_ID_BUCKET_UNKNOWN_CONDITION = 11; const int FIELD_ID_BUCKET_COUNT = 12; -const int FIELD_ID_LATE_LOG_EVENT = 13; -const int FIELD_ID_SUM_LATE_LOG_EVENT_EXTRA_DURATION_NS = 14; -const int FIELD_ID_MAX_LATE_LOG_EVENT_EXTRA_DURATION_NS = 15; namespace { @@ -550,12 +547,6 @@ void writeAtomMetricStatsToStream(const std::pairend(token); } diff --git a/statsd/tests/guardrail/StatsdStats_test.cpp b/statsd/tests/guardrail/StatsdStats_test.cpp index 5a824c53..81fcbe95 100644 --- a/statsd/tests/guardrail/StatsdStats_test.cpp +++ b/statsd/tests/guardrail/StatsdStats_test.cpp @@ -345,9 +345,6 @@ TEST(StatsdStatsTest, TestAtomMetricsStats) { // old event, we get it from the stats buffer. should be ignored. stats.noteBucketDropped(10000000000LL); - stats.noteLateLogEvent(10000000000LL, 10L); - stats.noteLateLogEvent(10000000000LL, 50L); - stats.noteBucketBoundaryDelayNs(10000000000LL, -1L); stats.noteBucketBoundaryDelayNs(10000000000LL, -10L); stats.noteBucketBoundaryDelayNs(10000000000LL, 2L); @@ -367,18 +364,12 @@ TEST(StatsdStatsTest, TestAtomMetricsStats) { EXPECT_EQ(1L, atomStats.bucket_dropped()); EXPECT_EQ(-10L, atomStats.min_bucket_boundary_delay_ns()); EXPECT_EQ(2L, atomStats.max_bucket_boundary_delay_ns()); - EXPECT_EQ(2L, atomStats.late_log_event()); - EXPECT_EQ(60L, atomStats.sum_late_log_event_extra_duration_ns()); - EXPECT_EQ(50L, atomStats.max_late_log_event_extra_duration_ns()); auto atomStats2 = report.atom_metric_stats(1); EXPECT_EQ(10000000001LL, atomStats2.metric_id()); EXPECT_EQ(0L, atomStats2.bucket_dropped()); EXPECT_EQ(0L, atomStats2.min_bucket_boundary_delay_ns()); EXPECT_EQ(1L, atomStats2.max_bucket_boundary_delay_ns()); - EXPECT_EQ(0L, atomStats2.late_log_event()); - EXPECT_EQ(0L, atomStats2.sum_late_log_event_extra_duration_ns()); - EXPECT_EQ(0L, atomStats2.max_late_log_event_extra_duration_ns()); } TEST(StatsdStatsTest, TestAnomalyMonitor) { -- cgit v1.2.3 From b18766e0563f05cfd72e0f22054da0d6a2bbc5ee Mon Sep 17 00:00:00 2001 From: Siarhei Vishniakou Date: Tue, 18 May 2021 07:35:39 +0000 Subject: Add libkll dependencies as whole_static_libs If we try to link against libkll as a shared library without including the dependencies of encoder and proto, we would get an error that the proto symbols are not found. However, we would like to link against libkll as a shared library in order to save space in case someone else decides to use it, or at least to avoid exposing these dependencies to the calling code. Currently, the static_libs of libkll do not propagate to the user of libkll due to b/169779783. Change the dependencies to whole_static_libs so that we can use libkll as a shared_library. Bug: 182225759 Test: m libinputdispatcher Change-Id: I01076d771e9432f08a3b5f8426c14759b56f3e12 --- lib/libkll/Android.bp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/libkll/Android.bp b/lib/libkll/Android.bp index 8181dedb..65ea1ca1 100644 --- a/lib/libkll/Android.bp +++ b/lib/libkll/Android.bp @@ -28,7 +28,7 @@ cc_library { "kll.cpp", "sampler.cpp", ], - static_libs: [ + whole_static_libs: [ "libkll-encoder", "libkll-protos", ], -- cgit v1.2.3