diff options
| author | 2018-03-02 17:54:08 +0000 | |
|---|---|---|
| committer | 2018-03-02 17:54:08 +0000 | |
| commit | cbecb1a0060b79d5b1453b793fc32050f2700599 (patch) | |
| tree | c6c175b2c27338c7777b2302a2ee6b15149e91fe | |
| parent | b2cdabf7aa209c20e1c0492855713741e885736b (diff) | |
| parent | 580ea321b16c71ddec515553761d6f37359bd3a0 (diff) | |
Merge "Add StateTracker."
| -rw-r--r-- | cmds/statsd/Android.mk | 2 | ||||
| -rw-r--r-- | cmds/statsd/src/FieldValue.h | 17 | ||||
| -rw-r--r-- | cmds/statsd/src/condition/CombinationConditionTracker.h | 23 | ||||
| -rw-r--r-- | cmds/statsd/src/condition/ConditionTracker.h | 5 | ||||
| -rw-r--r-- | cmds/statsd/src/condition/ConditionWizard.cpp | 10 | ||||
| -rw-r--r-- | cmds/statsd/src/condition/ConditionWizard.h | 4 | ||||
| -rw-r--r-- | cmds/statsd/src/condition/SimpleConditionTracker.cpp | 37 | ||||
| -rw-r--r-- | cmds/statsd/src/condition/SimpleConditionTracker.h | 22 | ||||
| -rw-r--r-- | cmds/statsd/src/condition/StateTracker.cpp | 233 | ||||
| -rw-r--r-- | cmds/statsd/src/condition/StateTracker.h | 110 | ||||
| -rw-r--r-- | cmds/statsd/src/metrics/metrics_manager_util.cpp | 57 | ||||
| -rw-r--r-- | cmds/statsd/src/metrics/metrics_manager_util.h | 2 | ||||
| -rw-r--r-- | cmds/statsd/tests/condition/StateTracker_test.cpp | 112 |
13 files changed, 619 insertions, 15 deletions
diff --git a/cmds/statsd/Android.mk b/cmds/statsd/Android.mk index 7f76ab14a171..5873942952e8 100644 --- a/cmds/statsd/Android.mk +++ b/cmds/statsd/Android.mk @@ -30,6 +30,7 @@ statsd_common_src := \ src/condition/condition_util.cpp \ src/condition/SimpleConditionTracker.cpp \ src/condition/ConditionWizard.cpp \ + src/condition/StateTracker.cpp \ src/config/ConfigKey.cpp \ src/config/ConfigListener.cpp \ src/config/ConfigManager.cpp \ @@ -189,6 +190,7 @@ LOCAL_SRC_FILES := \ tests/FieldValue_test.cpp \ tests/condition/CombinationConditionTracker_test.cpp \ tests/condition/SimpleConditionTracker_test.cpp \ + tests/condition/StateTracker_test.cpp \ tests/metrics/OringDurationTracker_test.cpp \ tests/metrics/MaxDurationTracker_test.cpp \ tests/metrics/CountMetricProducer_test.cpp \ diff --git a/cmds/statsd/src/FieldValue.h b/cmds/statsd/src/FieldValue.h index b0e2c43e8cdd..621d0be9e853 100644 --- a/cmds/statsd/src/FieldValue.h +++ b/cmds/statsd/src/FieldValue.h @@ -41,6 +41,7 @@ int32_t encodeMatcherMask(int32_t mask[], int32_t depth); 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. @@ -201,9 +202,9 @@ public: * } * * We translate the FieldMatcher into a Field, and mask - * First: [Matcher Field] 0x02010101 [Mask]0xffff7fff - * Last: [Matcher Field] 0x02018001 [Mask]0xffff80ff - * Any: [Matcher Field] 0x02010001 [Mask]0xffff00ff + * First: [Matcher Field] 0x02010101 [Mask]0xff7f7f7f + * Last: [Matcher Field] 0x02018001 [Mask]0xff7f807f + * Any: [Matcher Field] 0x02010001 [Mask]0xff7f007f * * [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 @@ -235,9 +236,17 @@ struct Matcher { 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); +} + /** * A wrapper for a union type to contain multiple types of values. * diff --git a/cmds/statsd/src/condition/CombinationConditionTracker.h b/cmds/statsd/src/condition/CombinationConditionTracker.h index ba185f6661ec..7b8dc6bfd938 100644 --- a/cmds/statsd/src/condition/CombinationConditionTracker.h +++ b/cmds/statsd/src/condition/CombinationConditionTracker.h @@ -52,6 +52,29 @@ public: const vector<Matcher>& dimensionFields, std::unordered_set<HashableDimensionKey>& dimensionsKeySet) const override; + // Only one child predicate can have dimension. + const std::set<HashableDimensionKey>* getChangedToTrueDimensions( + const std::vector<sp<ConditionTracker>>& 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<HashableDimensionKey>* getChangedToFalseDimensions( + const std::vector<sp<ConditionTracker>>& allConditions) const override { + for (const auto& child : mChildren) { + auto result = allConditions[child]->getChangedToFalseDimensions(allConditions); + if (result != nullptr) { + return result; + } + } + return nullptr; + } + private: LogicalOperation mLogicalOperation; diff --git a/cmds/statsd/src/condition/ConditionTracker.h b/cmds/statsd/src/condition/ConditionTracker.h index 2612a9a06da1..856a3a0467ea 100644 --- a/cmds/statsd/src/condition/ConditionTracker.h +++ b/cmds/statsd/src/condition/ConditionTracker.h @@ -111,6 +111,11 @@ public: return mSliced; } + virtual const std::set<HashableDimensionKey>* getChangedToTrueDimensions( + const std::vector<sp<ConditionTracker>>& allConditions) const = 0; + virtual const std::set<HashableDimensionKey>* getChangedToFalseDimensions( + const std::vector<sp<ConditionTracker>>& allConditions) const = 0; + protected: const int64_t mConditionId; diff --git a/cmds/statsd/src/condition/ConditionWizard.cpp b/cmds/statsd/src/condition/ConditionWizard.cpp index c8722c362fe6..952b0cccf217 100644 --- a/cmds/statsd/src/condition/ConditionWizard.cpp +++ b/cmds/statsd/src/condition/ConditionWizard.cpp @@ -41,6 +41,16 @@ ConditionState ConditionWizard::getMetConditionDimension( *dimensionsKeySet); } +const set<HashableDimensionKey>* ConditionWizard::getChangedToTrueDimensions( + const int index) const { + return mAllConditions[index]->getChangedToTrueDimensions(mAllConditions); +} + +const set<HashableDimensionKey>* ConditionWizard::getChangedToFalseDimensions( + const int index) const { + return mAllConditions[index]->getChangedToFalseDimensions(mAllConditions); +} + } // namespace statsd } // namespace os } // namespace android
\ No newline at end of file diff --git a/cmds/statsd/src/condition/ConditionWizard.h b/cmds/statsd/src/condition/ConditionWizard.h index 4831d5622a3d..fcfdc2a5815b 100644 --- a/cmds/statsd/src/condition/ConditionWizard.h +++ b/cmds/statsd/src/condition/ConditionWizard.h @@ -47,6 +47,10 @@ public: const int index, const vector<Matcher>& dimensionFields, std::unordered_set<HashableDimensionKey>* dimensionsKeySet) const; + virtual const std::set<HashableDimensionKey>* getChangedToTrueDimensions(const int index) const; + virtual const std::set<HashableDimensionKey>* getChangedToFalseDimensions( + const int index) const; + private: std::vector<sp<ConditionTracker>> mAllConditions; }; diff --git a/cmds/statsd/src/condition/SimpleConditionTracker.cpp b/cmds/statsd/src/condition/SimpleConditionTracker.cpp index 624119f3ad04..9e27a8b51a99 100644 --- a/cmds/statsd/src/condition/SimpleConditionTracker.cpp +++ b/cmds/statsd/src/condition/SimpleConditionTracker.cpp @@ -20,8 +20,6 @@ #include "SimpleConditionTracker.h" #include "guardrail/StatsdStats.h" -#include <log/logprint.h> - namespace android { namespace os { namespace statsd { @@ -108,11 +106,20 @@ bool SimpleConditionTracker::init(const vector<Predicate>& allConditionConfig, return mInitialized; } -void print(const map<HashableDimensionKey, int>& conditions, const int64_t& id) { - VLOG("%lld DUMP:", (long long)id); - for (const auto& pair : conditions) { +void SimpleConditionTracker::dumpState() { + VLOG("%lld DUMP:", (long long)mConditionId); + for (const auto& pair : mSlicedConditionState) { VLOG("\t%s : %d", pair.first.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<ConditionState>& conditionCache, @@ -123,6 +130,12 @@ void SimpleConditionTracker::handleStopAll(std::vector<ConditionState>& conditio (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(); @@ -166,10 +179,12 @@ void SimpleConditionTracker::handleConditionEvent(const HashableDimensionKey& ou if (matchStart && mInitialValue != ConditionState::kTrue) { mSlicedConditionState.insert(std::make_pair(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.insert(std::make_pair(outputKey, 0)); + mLastChangedToFalseDimensions.insert(outputKey); changed = true; } } else { @@ -179,6 +194,7 @@ void SimpleConditionTracker::handleConditionEvent(const HashableDimensionKey& ou 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; } @@ -202,6 +218,7 @@ void SimpleConditionTracker::handleConditionEvent(const HashableDimensionKey& ou } // if everything has stopped for this output key, condition true -> false; if (startedCount == 0) { + mLastChangedToFalseDimensions.insert(outputKey); changed = true; } } @@ -216,7 +233,7 @@ void SimpleConditionTracker::handleConditionEvent(const HashableDimensionKey& ou // dump all dimensions for debugging if (DEBUG) { - print(mSlicedConditionState, mConditionId); + dumpState(); } (*conditionChangedCache) = changed; @@ -237,6 +254,8 @@ void SimpleConditionTracker::evaluateCondition( (long long)mConditionId, conditionCache[mIndex]); return; } + mLastChangedToTrueDimensions.clear(); + mLastChangedToFalseDimensions.clear(); if (mStopAllLogMatcherIndex >= 0 && mStopAllLogMatcherIndex < int(eventMatcherValues.size()) && eventMatcherValues[mStopAllLogMatcherIndex] == MatchingState::kMatched) { @@ -297,14 +316,14 @@ void SimpleConditionTracker::evaluateCondition( // 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. for (const HashableDimensionKey& outputValue : outputValues) { - // For sliced conditions, the value in the cache is not used. We don't need to update - // the overall condition state. - ConditionState tempState = ConditionState::kUnknown; + ConditionState tempState; bool tempChanged = false; handleConditionEvent(outputValue, matchedState == 1, &tempState, &tempChanged); if (tempChanged) { overallChanged = true; } + // ConditionState's | operator is overridden + overallState = overallState | tempState; } } conditionCache[mIndex] = overallState; diff --git a/cmds/statsd/src/condition/SimpleConditionTracker.h b/cmds/statsd/src/condition/SimpleConditionTracker.h index c56512935297..e4b72b832215 100644 --- a/cmds/statsd/src/condition/SimpleConditionTracker.h +++ b/cmds/statsd/src/condition/SimpleConditionTracker.h @@ -57,6 +57,23 @@ public: const vector<Matcher>& dimensionFields, std::unordered_set<HashableDimensionKey>& dimensionsKeySet) const override; + virtual const std::set<HashableDimensionKey>* getChangedToTrueDimensions( + const std::vector<sp<ConditionTracker>>& allConditions) const { + if (mSliced) { + return &mLastChangedToTrueDimensions; + } else { + return nullptr; + } + } + virtual const std::set<HashableDimensionKey>* getChangedToFalseDimensions( + const std::vector<sp<ConditionTracker>>& allConditions) const { + if (mSliced) { + return &mLastChangedToFalseDimensions; + } else { + return nullptr; + } + } + private: const ConfigKey mConfigKey; // The index of the LogEventMatcher which defines the start. @@ -75,6 +92,9 @@ private: std::vector<Matcher> mOutputDimensions; + std::set<HashableDimensionKey> mLastChangedToTrueDimensions; + std::set<HashableDimensionKey> mLastChangedToFalseDimensions; + int mDimensionTag; std::map<HashableDimensionKey, int> mSlicedConditionState; @@ -87,6 +107,8 @@ private: bool hitGuardRail(const HashableDimensionKey& newKey); + void dumpState(); + FRIEND_TEST(SimpleConditionTrackerTest, TestSlicedCondition); FRIEND_TEST(SimpleConditionTrackerTest, TestSlicedWithNoOutputDim); FRIEND_TEST(SimpleConditionTrackerTest, TestStopAll); diff --git a/cmds/statsd/src/condition/StateTracker.cpp b/cmds/statsd/src/condition/StateTracker.cpp new file mode 100644 index 000000000000..e479f93d9a29 --- /dev/null +++ b/cmds/statsd/src/condition/StateTracker.cpp @@ -0,0 +1,233 @@ +/* + * Copyright 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 "StateTracker.h" +#include "guardrail/StatsdStats.h" + +namespace android { +namespace os { +namespace statsd { + +using std::string; +using std::unordered_set; +using std::vector; + +StateTracker::StateTracker(const ConfigKey& key, const int64_t& id, const int index, + const SimplePredicate& simplePredicate, + const unordered_map<int64_t, int>& trackerNameIndexMap, + const vector<Matcher> primaryKeys) + : ConditionTracker(id, index), mConfigKey(key), mPrimaryKeys(primaryKeys) { + if (simplePredicate.has_start()) { + auto pair = trackerNameIndexMap.find(simplePredicate.start()); + if (pair == trackerNameIndexMap.end()) { + ALOGW("Start matcher %lld not found in the config", (long long)simplePredicate.start()); + return; + } + mStartLogMatcherIndex = pair->second; + mTrackerIndex.insert(mStartLogMatcherIndex); + } else { + ALOGW("Condition %lld must have a start matcher", (long long)id); + return; + } + + if (simplePredicate.has_dimensions()) { + translateFieldMatcher(simplePredicate.dimensions(), &mOutputDimensions); + if (mOutputDimensions.size() > 0) { + mSliced = true; + mDimensionTag = mOutputDimensions[0].mMatcher.getTag(); + } else { + ALOGW("Condition %lld has invalid dimensions", (long long)id); + return; + } + } else { + ALOGW("Condition %lld being a state tracker, but has no dimension", (long long)id); + return; + } + + if (simplePredicate.initial_value() == SimplePredicate_InitialValue_FALSE) { + mInitialValue = ConditionState::kFalse; + } else { + mInitialValue = ConditionState::kUnknown; + } + + mNonSlicedConditionState = mInitialValue; + mInitialized = true; +} + +StateTracker::~StateTracker() { + VLOG("~StateTracker()"); +} + +bool StateTracker::init(const vector<Predicate>& allConditionConfig, + const vector<sp<ConditionTracker>>& allConditionTrackers, + const unordered_map<int64_t, int>& conditionIdIndexMap, + vector<bool>& stack) { + return mInitialized; +} + +void StateTracker::dumpState() { + VLOG("StateTracker %lld DUMP:", (long long)mConditionId); + for (const auto& value : mSlicedState) { + VLOG("\t%s -> %s", value.first.toString().c_str(), value.second.toString().c_str()); + } + VLOG("Last Changed to True: "); + for (const auto& value : mLastChangedToTrueDimensions) { + VLOG("%s", value.toString().c_str()); + } + VLOG("Last Changed to False: "); + for (const auto& value : mLastChangedToFalseDimensions) { + VLOG("%s", value.toString().c_str()); + } +} + +bool StateTracker::hitGuardRail(const HashableDimensionKey& newKey) { + if (mSlicedState.find(newKey) != mSlicedState.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 (mSlicedState.size() > StatsdStats::kDimensionKeySizeSoftLimit - 1) { + size_t newTupleCount = mSlicedState.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.c_str()); + return true; + } + } + return false; +} + +void StateTracker::evaluateCondition(const LogEvent& event, + const vector<MatchingState>& eventMatcherValues, + const vector<sp<ConditionTracker>>& mAllConditions, + vector<ConditionState>& conditionCache, + vector<bool>& conditionChangedCache) { + mLastChangedToTrueDimensions.clear(); + mLastChangedToFalseDimensions.clear(); + if (conditionCache[mIndex] != ConditionState::kNotEvaluated) { + // it has been evaluated. + VLOG("Yes, already evaluated, %lld %d", (long long)mConditionId, conditionCache[mIndex]); + return; + } + + if (mStartLogMatcherIndex >= 0 && + eventMatcherValues[mStartLogMatcherIndex] != MatchingState::kMatched) { + conditionCache[mIndex] = + mSlicedState.size() > 0 ? ConditionState::kTrue : ConditionState::kFalse; + conditionChangedCache[mIndex] = false; + return; + } + + VLOG("StateTracker evaluate event %s", event.ToString().c_str()); + + vector<HashableDimensionKey> keys; + vector<HashableDimensionKey> outputs; + filterValues(mPrimaryKeys, event.getValues(), &keys); + filterValues(mOutputDimensions, event.getValues(), &outputs); + if (keys.size() != 1 || outputs.size() != 1) { + ALOGE("More than 1 states in the event?? panic now!"); + conditionCache[mIndex] = + mSlicedState.size() > 0 ? ConditionState::kTrue : ConditionState::kFalse; + conditionChangedCache[mIndex] = false; + return; + } + // Primary key can exclusive fields must be simple fields. so there won't be more than + // one keys matched. + const auto& primaryKey = keys[0]; + const auto& state = outputs[0]; + hitGuardRail(primaryKey); + + VLOG("StateTracker: key %s state %s", primaryKey.toString().c_str(), state.toString().c_str()); + + auto it = mSlicedState.find(primaryKey); + if (it == mSlicedState.end()) { + mSlicedState[primaryKey] = state; + conditionCache[mIndex] = ConditionState::kTrue; + mLastChangedToTrueDimensions.insert(state); + conditionChangedCache[mIndex] = true; + } else if (!(it->second == state)) { + mLastChangedToFalseDimensions.insert(it->second); + mLastChangedToTrueDimensions.insert(state); + mSlicedState[primaryKey] = state; + conditionCache[mIndex] = ConditionState::kTrue; + conditionChangedCache[mIndex] = true; + } else { + conditionCache[mIndex] = ConditionState::kTrue; + conditionChangedCache[mIndex] = false; + } + + if (DEBUG) { + dumpState(); + } + return; +} + +void StateTracker::isConditionMet( + const ConditionKey& conditionParameters, const vector<sp<ConditionTracker>>& allConditions, + const vector<Matcher>& dimensionFields, vector<ConditionState>& conditionCache, + std::unordered_set<HashableDimensionKey>& dimensionsKeySet) 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()) { + if (mSlicedState.size() > 0) { + conditionCache[mIndex] = ConditionState::kTrue; + + for (const auto& state : mSlicedState) { + dimensionsKeySet.insert(state.second); + } + } else { + conditionCache[mIndex] = ConditionState::kUnknown; + } + return; + } + + const auto& primaryKeys = pair->second; + conditionCache[mIndex] = mInitialValue; + for (const auto& primaryKey : primaryKeys) { + auto it = mSlicedState.find(primaryKey); + if (it != mSlicedState.end()) { + conditionCache[mIndex] = ConditionState::kTrue; + dimensionsKeySet.insert(it->second); + } + } +} + +ConditionState StateTracker::getMetConditionDimension( + const std::vector<sp<ConditionTracker>>& allConditions, + const vector<Matcher>& dimensionFields, + std::unordered_set<HashableDimensionKey>& dimensionsKeySet) const { + if (mSlicedState.size() > 0) { + for (const auto& state : mSlicedState) { + dimensionsKeySet.insert(state.second); + } + return ConditionState::kTrue; + } + + return mInitialValue; +} + +} // namespace statsd +} // namespace os +} // namespace android
\ No newline at end of file diff --git a/cmds/statsd/src/condition/StateTracker.h b/cmds/statsd/src/condition/StateTracker.h new file mode 100644 index 000000000000..3fe6e60225d2 --- /dev/null +++ b/cmds/statsd/src/condition/StateTracker.h @@ -0,0 +1,110 @@ +/* + * Copyright 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 <gtest/gtest_prod.h> +#include "ConditionTracker.h" +#include "config/ConfigKey.h" +#include "frameworks/base/cmds/statsd/src/statsd_config.pb.h" +#include "stats_util.h" + +namespace android { +namespace os { +namespace statsd { + +class StateTracker : public virtual ConditionTracker { +public: + StateTracker(const ConfigKey& key, const int64_t& id, const int index, + const SimplePredicate& simplePredicate, + const std::unordered_map<int64_t, int>& trackerNameIndexMap, + const vector<Matcher> primaryKeys); + + ~StateTracker(); + + bool init(const std::vector<Predicate>& allConditionConfig, + const std::vector<sp<ConditionTracker>>& allConditionTrackers, + const std::unordered_map<int64_t, int>& conditionIdIndexMap, + std::vector<bool>& stack) override; + + void evaluateCondition(const LogEvent& event, + const std::vector<MatchingState>& eventMatcherValues, + const std::vector<sp<ConditionTracker>>& mAllConditions, + std::vector<ConditionState>& conditionCache, + std::vector<bool>& changedCache) override; + + /** + * Note: dimensionFields will be ignored in StateTracker, because we demand metrics + * must take the entire dimension fields from StateTracker. This is to make implementation + * simple and efficient. + * + * For example: wakelock duration by uid process states: + * dimension in condition must be {uid, process state}. + */ + void isConditionMet(const ConditionKey& conditionParameters, + const std::vector<sp<ConditionTracker>>& allConditions, + const vector<Matcher>& dimensionFields, + std::vector<ConditionState>& conditionCache, + std::unordered_set<HashableDimensionKey>& dimensionsKeySet) const override; + + /** + * Note: dimensionFields will be ignored in StateTracker, because we demand metrics + * must take the entire dimension fields from StateTracker. This is to make implementation + * simple and efficient. + */ + ConditionState getMetConditionDimension( + const std::vector<sp<ConditionTracker>>& allConditions, + const vector<Matcher>& dimensionFields, + std::unordered_set<HashableDimensionKey>& dimensionsKeySet) const override; + + virtual const std::set<HashableDimensionKey>* getChangedToTrueDimensions( + const std::vector<sp<ConditionTracker>>& allConditions) const { + return &mLastChangedToTrueDimensions; + } + + virtual const std::set<HashableDimensionKey>* getChangedToFalseDimensions( + const std::vector<sp<ConditionTracker>>& allConditions) const { + return &mLastChangedToFalseDimensions; + } + +private: + const ConfigKey mConfigKey; + + // The index of the LogEventMatcher which defines the start. + int mStartLogMatcherIndex; + + std::set<HashableDimensionKey> mLastChangedToTrueDimensions; + std::set<HashableDimensionKey> mLastChangedToFalseDimensions; + + std::vector<Matcher> mOutputDimensions; + std::vector<Matcher> mPrimaryKeys; + + ConditionState mInitialValue; + + int mDimensionTag; + + void dumpState(); + + bool hitGuardRail(const HashableDimensionKey& newKey); + + // maps from [primary_key] to [primary_key, exclusive_state]. + std::unordered_map<HashableDimensionKey, HashableDimensionKey> mSlicedState; + + FRIEND_TEST(StateTrackerTest, TestStateChange); +}; + +} // namespace statsd +} // namespace os +} // namespace android
\ No newline at end of file diff --git a/cmds/statsd/src/metrics/metrics_manager_util.cpp b/cmds/statsd/src/metrics/metrics_manager_util.cpp index 9912afa01c3d..b5afef2502f8 100644 --- a/cmds/statsd/src/metrics/metrics_manager_util.cpp +++ b/cmds/statsd/src/metrics/metrics_manager_util.cpp @@ -21,6 +21,7 @@ #include "../condition/CombinationConditionTracker.h" #include "../condition/SimpleConditionTracker.h" +#include "../condition/StateTracker.h" #include "../external/StatsPullerManager.h" #include "../matchers/CombinationLogMatchingTracker.h" #include "../matchers/SimpleLogMatchingTracker.h" @@ -31,6 +32,7 @@ #include "../metrics/ValueMetricProducer.h" #include "stats_util.h" +#include "statslog.h" using std::set; using std::string; @@ -157,6 +159,49 @@ bool initLogTrackers(const StatsdConfig& config, const UidMap& uidMap, return true; } +/** + * A StateTracker is built from a SimplePredicate which has only "start", and no "stop" + * or "stop_all". The start must be an atom matcher that matches a state atom. It must + * have dimension, the dimension must be the state atom's primary fields plus exclusive state + * field. For example, the StateTracker is used in tracking UidProcessState and ScreenState. + * + */ +bool isStateTracker(const SimplePredicate& simplePredicate, vector<Matcher>* primaryKeys) { + // 1. must not have "stop". must have "dimension" + if (!simplePredicate.has_stop() && simplePredicate.has_dimensions()) { + // TODO: need to check the start atom matcher too. + auto it = android::util::kStateAtomsFieldOptions.find(simplePredicate.dimensions().field()); + // 2. must be based on a state atom. + if (it != android::util::kStateAtomsFieldOptions.end()) { + // 3. dimension must be primary fields + state field IN ORDER + size_t expectedDimensionCount = it->second.primaryFields.size() + 1; + vector<Matcher> dimensions; + translateFieldMatcher(simplePredicate.dimensions(), &dimensions); + if (dimensions.size() != expectedDimensionCount) { + return false; + } + // 3.1 check the primary fields first. + size_t index = 0; + for (const auto& field : it->second.primaryFields) { + Matcher matcher = getSimpleMatcher(it->first, field); + if (!(matcher == dimensions[index])) { + return false; + } + primaryKeys->push_back(matcher); + index++; + } + Matcher stateFieldMatcher = + getSimpleMatcher(it->first, it->second.exclusiveField); + // 3.2 last dimension should be the exclusive field. + if (!(dimensions.back() == stateFieldMatcher)) { + return false; + } + return true; + } + } + return false; +} // namespace statsd + bool initConditions(const ConfigKey& key, const StatsdConfig& config, const unordered_map<int64_t, int>& logTrackerMap, unordered_map<int64_t, int>& conditionTrackerMap, @@ -172,8 +217,16 @@ bool initConditions(const ConfigKey& key, const StatsdConfig& config, int index = allConditionTrackers.size(); switch (condition.contents_case()) { case Predicate::ContentsCase::kSimplePredicate: { - allConditionTrackers.push_back(new SimpleConditionTracker( - key, condition.id(), index, condition.simple_predicate(), logTrackerMap)); + vector<Matcher> primaryKeys; + if (isStateTracker(condition.simple_predicate(), &primaryKeys)) { + allConditionTrackers.push_back(new StateTracker(key, condition.id(), index, + condition.simple_predicate(), + logTrackerMap, primaryKeys)); + } else { + allConditionTrackers.push_back(new SimpleConditionTracker( + key, condition.id(), index, condition.simple_predicate(), + logTrackerMap)); + } break; } case Predicate::ContentsCase::kCombination: { diff --git a/cmds/statsd/src/metrics/metrics_manager_util.h b/cmds/statsd/src/metrics/metrics_manager_util.h index edda53d5e0cf..386de0b84aa0 100644 --- a/cmds/statsd/src/metrics/metrics_manager_util.h +++ b/cmds/statsd/src/metrics/metrics_manager_util.h @@ -110,6 +110,8 @@ bool initStatsdConfig(const ConfigKey& key, const StatsdConfig& config, std::unordered_map<int, std::vector<int>>& trackerToConditionMap, std::set<int64_t> &noReportMetricIds); +bool isStateTracker(const SimplePredicate& simplePredicate, std::vector<Matcher>* primaryKeys); + } // namespace statsd } // namespace os } // namespace android diff --git a/cmds/statsd/tests/condition/StateTracker_test.cpp b/cmds/statsd/tests/condition/StateTracker_test.cpp new file mode 100644 index 000000000000..9a66254afce0 --- /dev/null +++ b/cmds/statsd/tests/condition/StateTracker_test.cpp @@ -0,0 +1,112 @@ +// 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/StateTracker.h" +#include "tests/statsd_test_util.h" + +#include <gmock/gmock.h> +#include <gtest/gtest.h> +#include <stdio.h> +#include <numeric> +#include <vector> + +using std::map; +using std::unordered_map; +using std::vector; + +#ifdef __ANDROID__ +namespace android { +namespace os { +namespace statsd { + +const int kUidProcTag = 27; + +SimplePredicate getUidProcStatePredicate() { + SimplePredicate simplePredicate; + simplePredicate.set_start(StringToId("UidProcState")); + + simplePredicate.mutable_dimensions()->set_field(kUidProcTag); + simplePredicate.mutable_dimensions()->add_child()->set_field(1); + simplePredicate.mutable_dimensions()->add_child()->set_field(2); + + simplePredicate.set_count_nesting(false); + return simplePredicate; +} + +void makeUidProcStateEvent(int32_t uid, int32_t state, LogEvent* event) { + event->write(uid); + event->write(state); + event->init(); +} + +TEST(StateTrackerTest, TestStateChange) { + int uid1 = 111; + int uid2 = 222; + + int state1 = 1001; + int state2 = 1002; + unordered_map<int64_t, int> trackerNameIndexMap; + trackerNameIndexMap[StringToId("UidProcState")] = 0; + vector<Matcher> primaryFields; + primaryFields.push_back(getSimpleMatcher(kUidProcTag, 1)); + StateTracker tracker(ConfigKey(12, 123), 123, 0, getUidProcStatePredicate(), + trackerNameIndexMap, primaryFields); + + LogEvent event(kUidProcTag, 0 /*timestamp*/); + makeUidProcStateEvent(uid1, state1, &event); + + vector<MatchingState> matcherState; + matcherState.push_back(MatchingState::kMatched); + vector<sp<ConditionTracker>> allPredicates; + vector<ConditionState> conditionCache(1, ConditionState::kNotEvaluated); + vector<bool> changedCache(1, false); + + tracker.evaluateCondition(event, matcherState, allPredicates, conditionCache, changedCache); + EXPECT_EQ(1ULL, tracker.mLastChangedToTrueDimensions.size()); + EXPECT_EQ(0ULL, tracker.mLastChangedToFalseDimensions.size()); + EXPECT_TRUE(changedCache[0]); + + changedCache[0] = false; + conditionCache[0] = ConditionState::kNotEvaluated; + tracker.evaluateCondition(event, matcherState, allPredicates, conditionCache, changedCache); + EXPECT_EQ(0ULL, tracker.mLastChangedToTrueDimensions.size()); + EXPECT_EQ(0ULL, tracker.mLastChangedToFalseDimensions.size()); + EXPECT_FALSE(changedCache[0]); + + LogEvent event2(kUidProcTag, 0 /*timestamp*/); + makeUidProcStateEvent(uid1, state2, &event2); + + changedCache[0] = false; + conditionCache[0] = ConditionState::kNotEvaluated; + tracker.evaluateCondition(event2, matcherState, allPredicates, conditionCache, changedCache); + EXPECT_EQ(1ULL, tracker.mLastChangedToTrueDimensions.size()); + EXPECT_EQ(1ULL, tracker.mLastChangedToFalseDimensions.size()); + EXPECT_TRUE(changedCache[0]); + + LogEvent event3(kUidProcTag, 0 /*timestamp*/); + makeUidProcStateEvent(uid2, state1, &event3); + changedCache[0] = false; + conditionCache[0] = ConditionState::kNotEvaluated; + tracker.evaluateCondition(event3, matcherState, allPredicates, conditionCache, changedCache); + EXPECT_EQ(1ULL, tracker.mLastChangedToTrueDimensions.size()); + EXPECT_EQ(0ULL, tracker.mLastChangedToFalseDimensions.size()); + EXPECT_TRUE(changedCache[0]); +} + +} // namespace statsd +} // namespace os +} // namespace android +#else +GTEST_LOG_(INFO) << "This test does nothing.\n"; +#endif |