Add StateTracker.
StateTracker is a special condition tracker that's based on a state atom.
State atoms are annotated in atoms.proto.
The rules for StateTracker:
1. must not have "stop". must have "dimension"
2. must be based on a state atom.
3. it must have the all primary fields and the exclusive state field in its dimension.
For example UidProcessStateTracker, will have output dimension {uid, state}.
Test: unit tests added.
Change-Id: I6b77e58e9fabe61f7326daf929577d8b2cfbf27b
diff --git a/cmds/statsd/Android.mk b/cmds/statsd/Android.mk
index 7f76ab1..5873942 100644
--- a/cmds/statsd/Android.mk
+++ b/cmds/statsd/Android.mk
@@ -30,6 +30,7 @@
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 @@
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 b0e2c43..621d0be9 100644
--- a/cmds/statsd/src/FieldValue.h
+++ b/cmds/statsd/src/FieldValue.h
@@ -41,6 +41,7 @@
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 @@
* }
*
* 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 @@
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 ba185f6..7b8dc6b 100644
--- a/cmds/statsd/src/condition/CombinationConditionTracker.h
+++ b/cmds/statsd/src/condition/CombinationConditionTracker.h
@@ -52,6 +52,29 @@
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 2612a9a..856a3a0 100644
--- a/cmds/statsd/src/condition/ConditionTracker.h
+++ b/cmds/statsd/src/condition/ConditionTracker.h
@@ -111,6 +111,11 @@
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 c8722c3..952b0cc 100644
--- a/cmds/statsd/src/condition/ConditionWizard.cpp
+++ b/cmds/statsd/src/condition/ConditionWizard.cpp
@@ -41,6 +41,16 @@
*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 4831d56..fcfdc2a 100644
--- a/cmds/statsd/src/condition/ConditionWizard.h
+++ b/cmds/statsd/src/condition/ConditionWizard.h
@@ -47,6 +47,10 @@
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 624119f3..9e27a8b 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 @@
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 @@
(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 @@
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 @@
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 @@
}
// if everything has stopped for this output key, condition true -> false;
if (startedCount == 0) {
+ mLastChangedToFalseDimensions.insert(outputKey);
changed = true;
}
}
@@ -216,7 +233,7 @@
// dump all dimensions for debugging
if (DEBUG) {
- print(mSlicedConditionState, mConditionId);
+ dumpState();
}
(*conditionChangedCache) = changed;
@@ -237,6 +254,8 @@
(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 @@
// 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 c565129..e4b72b8 100644
--- a/cmds/statsd/src/condition/SimpleConditionTracker.h
+++ b/cmds/statsd/src/condition/SimpleConditionTracker.h
@@ -57,6 +57,23 @@
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 @@
std::vector<Matcher> mOutputDimensions;
+ std::set<HashableDimensionKey> mLastChangedToTrueDimensions;
+ std::set<HashableDimensionKey> mLastChangedToFalseDimensions;
+
int mDimensionTag;
std::map<HashableDimensionKey, int> mSlicedConditionState;
@@ -87,6 +107,8 @@
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 0000000..e479f93
--- /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 0000000..3fe6e60
--- /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 9912afa..b5afef2 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 @@
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 @@
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 edda53d..386de0b 100644
--- a/cmds/statsd/src/metrics/metrics_manager_util.h
+++ b/cmds/statsd/src/metrics/metrics_manager_util.h
@@ -110,6 +110,8 @@
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 0000000..9a66254
--- /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