Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 967b2051 authored by Yao Chen's avatar Yao Chen
Browse files

Make SimpleCondition satisfactorily complicated.

+ Support nested counting
+ Support StopAll
+ Added default_condition to SimpleCondition config
+ Some refactoring/clean up
+ Added unit tests

Test: Added unit tests, statsd_test
Change-Id: I6564ac2e068ce6810e8090c0818064c625c7847a
parent 6ef8030a
Loading
Loading
Loading
Loading
+2 −2
Original line number Diff line number Diff line
@@ -151,19 +151,19 @@ LOCAL_SRC_FILES := \
    $(statsd_common_src) \
    tests/AnomalyMonitor_test.cpp \
    tests/anomaly/AnomalyTracker_test.cpp \
    tests/ConditionTracker_test.cpp \
    tests/ConfigManager_test.cpp \
    tests/indexed_priority_queue_test.cpp \
    tests/LogEntryMatcher_test.cpp \
    tests/LogReader_test.cpp \
    tests/MetricsManager_test.cpp \
    tests/UidMap_test.cpp \
    tests/condition/CombinationConditionTracker_test.cpp \
    tests/condition/SimpleConditionTracker_test.cpp \
    tests/metrics/OringDurationTracker_test.cpp \
    tests/metrics/MaxDurationTracker_test.cpp \
    tests/metrics/CountMetricProducer_test.cpp \
    tests/metrics/EventMetricProducer_test.cpp


LOCAL_STATIC_LIBRARIES := \
    libgmock

+16 −18
Original line number Diff line number Diff line
@@ -115,25 +115,25 @@ void CombinationConditionTracker::isConditionMet(
            evaluateCombinationCondition(mChildren, mLogicalOperation, conditionCache);
}

bool CombinationConditionTracker::evaluateCondition(
void CombinationConditionTracker::evaluateCondition(
        const LogEvent& event, const std::vector<MatchingState>& eventMatcherValues,
        const std::vector<sp<ConditionTracker>>& mAllConditions,
        std::vector<ConditionState>& nonSlicedConditionCache,
        std::vector<bool>& nonSlicedChangedCache, vector<bool>& slicedConditionChanged) {
        std::vector<bool>& conditionChangedCache) {
    // value is up to date.
    if (nonSlicedConditionCache[mIndex] != ConditionState::kNotEvaluated) {
        return false;
        return;
    }

    for (const int childIndex : mChildren) {
        if (nonSlicedConditionCache[childIndex] == ConditionState::kNotEvaluated) {
            const sp<ConditionTracker>& child = mAllConditions[childIndex];
            child->evaluateCondition(event, eventMatcherValues, mAllConditions,
                                     nonSlicedConditionCache, nonSlicedChangedCache,
                                     slicedConditionChanged);
                                     nonSlicedConditionCache, conditionChangedCache);
        }
    }

    if (!mSliced) {
        ConditionState newCondition =
                evaluateCombinationCondition(mChildren, mLogicalOperation, nonSlicedConditionCache);

@@ -142,22 +142,20 @@ bool CombinationConditionTracker::evaluateCondition(

        nonSlicedConditionCache[mIndex] = mNonSlicedConditionState;

    nonSlicedChangedCache[mIndex] = nonSlicedChanged;

    if (mSliced) {
        conditionChangedCache[mIndex] = nonSlicedChanged;
    } else {
        for (const int childIndex : mChildren) {
            // If any of the sliced condition in children condition changes, the combination
            // condition may be changed too.
            if (slicedConditionChanged[childIndex]) {
                slicedConditionChanged[mIndex] = true;
            if (conditionChangedCache[childIndex]) {
                conditionChangedCache[mIndex] = true;
                break;
            }
        }
        nonSlicedConditionCache[mIndex] = ConditionState::kUnknown;
        ALOGD("CombinationCondition %s sliced may changed? %d", mName.c_str(),
              slicedConditionChanged[mIndex] == true);
              conditionChangedCache[mIndex] == true);
    }

    return nonSlicedChanged;
}

}  // namespace statsd
+2 −3
Original line number Diff line number Diff line
@@ -35,12 +35,11 @@ public:
              const std::unordered_map<std::string, int>& conditionNameIndexMap,
              std::vector<bool>& stack) override;

    bool evaluateCondition(const LogEvent& event,
    void evaluateCondition(const LogEvent& event,
                           const std::vector<MatchingState>& eventMatcherValues,
                           const std::vector<sp<ConditionTracker>>& mAllConditions,
                           std::vector<ConditionState>& conditionCache,
                           std::vector<bool>& changedCache,
                           std::vector<bool>& slicedConditionMayChanged) override;
                           std::vector<bool>& changedCache) override;

    void isConditionMet(const std::map<std::string, HashableDimensionKey>& conditionParameters,
                        const std::vector<sp<ConditionTracker>>& allConditions,
+5 −10
Original line number Diff line number Diff line
@@ -56,25 +56,20 @@ public:
                      std::vector<bool>& stack) = 0;

    // evaluate current condition given the new event.
    // return true if the condition state changed, false if the condition state is not changed.
    // event: the new log event
    // eventMatcherValues: the results of the LogMatcherTrackers. LogMatcherTrackers always process
    //                     event before ConditionTrackers, because ConditionTracker depends on
    //                     LogMatchingTrackers.
    // mAllConditions: the list of all ConditionTracker
    // conditionCache: the cached non-sliced condition of the ConditionTrackers for this new event.
    // nonSlicedConditionChanged: the bit map to record whether non-sliced condition has changed.
    // slicedConditionMayChanged: the bit map to record whether sliced condition may have changed.
    //      Because sliced condition needs parameters to determine the value. So the sliced
    //      condition is not pushed to metrics. We only inform the relevant metrics that the sliced
    //      condition may have changed, and metrics should pull the conditions that they are
    //      interested in.
    virtual bool evaluateCondition(const LogEvent& 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<MatchingState>& eventMatcherValues,
                                   const std::vector<sp<ConditionTracker>>& mAllConditions,
                                   std::vector<ConditionState>& conditionCache,
                                   std::vector<bool>& nonSlicedConditionChanged,
                                   std::vector<bool>& slicedConditionMayChanged) = 0;
                                   std::vector<bool>& conditionChanged) = 0;

    // Return the current condition state.
    virtual ConditionState isConditionMet() {
+129 −82
Original line number Diff line number Diff line
@@ -74,13 +74,21 @@ SimpleConditionTracker::SimpleConditionTracker(
        mStopAllLogMatcherIndex = -1;
    }

    mDimension.insert(mDimension.begin(), simpleCondition.dimension().begin(),
    mOutputDimension.insert(mOutputDimension.begin(), simpleCondition.dimension().begin(),
                            simpleCondition.dimension().end());

    if (mDimension.size() > 0) {
    if (mOutputDimension.size() > 0) {
        mSliced = true;
    }

    if (simpleCondition.initial_value() == SimpleCondition_InitialValue_FALSE) {
        mInitialValue = ConditionState::kFalse;
    } else {
        mInitialValue = ConditionState::kUnknown;
    }

    mNonSlicedConditionState = mInitialValue;

    mInitialized = true;
}

@@ -97,127 +105,166 @@ bool SimpleConditionTracker::init(const vector<Condition>& allConditionConfig,
    return mInitialized;
}

void print(unordered_map<HashableDimensionKey, ConditionState>& conditions, const string& name) {
void print(map<HashableDimensionKey, int>& conditions, const string& name) {
    VLOG("%s DUMP:", name.c_str());

    for (const auto& pair : conditions) {
        VLOG("\t%s %d", pair.first.c_str(), pair.second);
        VLOG("\t%s : %d", pair.first.c_str(), pair.second);
    }
}

bool SimpleConditionTracker::evaluateCondition(const LogEvent& event,
                                               const vector<MatchingState>& eventMatcherValues,
                                               const vector<sp<ConditionTracker>>& mAllConditions,
                                               vector<ConditionState>& conditionCache,
                                               vector<bool>& nonSlicedConditionChanged,
                                               std::vector<bool>& slicedConditionChanged) {
    if (conditionCache[mIndex] != ConditionState::kNotEvaluated) {
        // it has been evaluated.
        VLOG("Yes, already evaluated, %s %d", mName.c_str(), mNonSlicedConditionState);
        return false;
void SimpleConditionTracker::handleStopAll(std::vector<ConditionState>& conditionCache,
                                           std::vector<bool>& 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;

    // After StopAll, we know everything has stopped. From now on, default condition is false.
    mInitialValue = ConditionState::kFalse;
    mSlicedConditionState.clear();
    conditionCache[mIndex] = ConditionState::kFalse;
}

    // Ignore nesting, because we know we cannot trust ourselves on tracking nesting conditions.

    ConditionState newCondition = mNonSlicedConditionState;
    bool matched = false;
    // 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) {
        matched = true;
        newCondition = ConditionState::kTrue;
void SimpleConditionTracker::handleConditionEvent(const HashableDimensionKey& outputKey,
                                                  bool matchStart,
                                                  std::vector<ConditionState>& conditionCache,
                                                  std::vector<bool>& conditionChangedCache) {
    bool changed = false;
    auto outputIt = mSlicedConditionState.find(outputKey);
    ConditionState newCondition;
    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;
        } 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;
            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) {
                // This condition for this output key will change from false -> true
                changed = true;
            }

    if (mStopLogMatcherIndex >= 0 &&
        eventMatcherValues[mStopLogMatcherIndex] == MatchingState::kMatched) {
        matched = 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;
                    }

    bool stopAll = false;
    if (mStopAllLogMatcherIndex >= 0 &&
        eventMatcherValues[mStopAllLogMatcherIndex] == MatchingState::kMatched) {
        matched = true;
                } else {
                    // not counting nesting, so ignore the number of starts, stop now.
                    startedCount = 0;
                    newCondition = ConditionState::kFalse;
        stopAll = true;
                }
                // if everything has stopped for this output key, condition true -> false;
                if (startedCount == 0) {
                    changed = true;
                }
            }

    if (matched == false) {
        slicedConditionChanged[mIndex] = false;
        nonSlicedConditionChanged[mIndex] = false;
        conditionCache[mIndex] = mNonSlicedConditionState;
        return false;
            // 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.c_str());
            }
        }
    }

    bool nonSlicedChanged = mNonSlicedConditionState != newCondition;
    // dump all dimensions for debugging
    if (DEBUG) {
        print(mSlicedConditionState, mName);
    }

    bool slicedChanged = false;
    conditionChangedCache[mIndex] = changed;
    conditionCache[mIndex] = newCondition;

    if (stopAll) {
        // TODO: handle stop all; all dimension should be cleared.
    VLOG("SimpleCondition %s nonSlicedChange? %d", mName.c_str(),
         conditionChangedCache[mIndex] == true);
}


    if (mDimension.size() > 0) {
        HashableDimensionKey hashableKey = getHashableKey(getDimensionKey(event, mDimension));
        if (mSlicedConditionState.find(hashableKey) == mSlicedConditionState.end() ||
            mSlicedConditionState[hashableKey] != newCondition) {
            slicedChanged = true;
            mSlicedConditionState[hashableKey] = newCondition;
void SimpleConditionTracker::evaluateCondition(const LogEvent& event,
                                               const vector<MatchingState>& eventMatcherValues,
                                               const vector<sp<ConditionTracker>>& mAllConditions,
                                               vector<ConditionState>& conditionCache,
                                               vector<bool>& conditionChangedCache) {
    if (conditionCache[mIndex] != ConditionState::kNotEvaluated) {
        // it has been evaluated.
        VLOG("Yes, already evaluated, %s %d", mName.c_str(), mNonSlicedConditionState);
        return;
    }
        VLOG("key: %s %d", hashableKey.c_str(), newCondition);
        // dump all dimensions for debugging
        if (DEBUG) {
            print(mSlicedConditionState, mName);

    if (mStopAllLogMatcherIndex >= 0 &&
        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;
    }

    // even if this SimpleCondition is not sliced, it may be part of a sliced CombinationCondition
    // if the nonSliced condition changed, it may affect the sliced condition in the parent node.
    // so mark the slicedConditionChanged to be true.
    // For example: APP_IN_BACKGROUND_OR_SCREEN_OFF
    //     APP_IN_BACKGROUND is sliced [App_A->True, App_B->False].
    //     SCREEN_OFF is not sliced, and it changes from False -> True;
    //     We need to populate this change to parent condition. Because for App_B,
    //     the APP_IN_BACKGROUND_OR_SCREEN_OFF condition would change from False->True.
    slicedConditionChanged[mIndex] = mSliced ? slicedChanged : nonSlicedChanged;
    nonSlicedConditionChanged[mIndex] = nonSlicedChanged;
    if (mStopLogMatcherIndex >= 0 &&
        eventMatcherValues[mStopLogMatcherIndex] == MatchingState::kMatched) {
        matchedState = 0;
    }

    VLOG("SimpleCondition %s nonSlicedChange? %d  SlicedChanged? %d", mName.c_str(),
         nonSlicedConditionChanged[mIndex] == true, slicedConditionChanged[mIndex] == true);
    mNonSlicedConditionState = newCondition;
    if (matchedState < 0) {
        conditionChangedCache[mIndex] = false;
        conditionCache[mIndex] = mNonSlicedConditionState;
        return;
    }

    return nonSlicedConditionChanged[mIndex];
    // outputKey is the output key values. e.g, uid:1234
    const HashableDimensionKey outputKey = getHashableKey(getDimensionKey(event, mOutputDimension));
    handleConditionEvent(outputKey, matchedState == 1, conditionCache, conditionChangedCache);
}

void SimpleConditionTracker::isConditionMet(
        const map<string, HashableDimensionKey>& conditionParameters,
        const vector<sp<ConditionTracker>>& allConditions, vector<ConditionState>& conditionCache) {
    const auto pair = conditionParameters.find(mName);
    if (pair == conditionParameters.end()) {
        // the query does not need my sliced condition. just return the non sliced condition.
        conditionCache[mIndex] = mNonSlicedConditionState;
        VLOG("Condition %s return %d", mName.c_str(), mNonSlicedConditionState);
    HashableDimensionKey key =
            (pair == conditionParameters.end()) ? DEFAULT_DIMENSION_KEY : pair->second;

    if (pair == conditionParameters.end() && mOutputDimension.size() > 0) {
        ALOGE("Condition %s output has dimension, but it's not specified in the query!",
              mName.c_str());
        conditionCache[mIndex] = mInitialValue;
        return;
    }

    const HashableDimensionKey& key = pair->second;
    VLOG("simpleCondition %s query key: %s", mName.c_str(), key.c_str());

    if (mSlicedConditionState.find(key) == mSlicedConditionState.end()) {
        // never seen this key before. the condition is unknown to us.
        conditionCache[mIndex] = ConditionState::kUnknown;
    auto startedCountIt = mSlicedConditionState.find(key);
    if (startedCountIt == mSlicedConditionState.end()) {
        conditionCache[mIndex] = mInitialValue;
    } else {
        conditionCache[mIndex] = mSlicedConditionState[key];
        conditionCache[mIndex] =
                startedCountIt->second > 0 ? ConditionState::kTrue : ConditionState::kFalse;
    }

    VLOG("Condition %s return %d", mName.c_str(), conditionCache[mIndex]);

    if (DEBUG) {
        print(mSlicedConditionState, mName);
    }
}

}  // namespace statsd
Loading