|  | /* | 
|  | * 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 int index, | 
|  | const SimplePredicate& simplePredicate, | 
|  | const unordered_map<int64_t, int>& trackerNameIndexMap) | 
|  | : ConditionTracker(id, index), mConfigKey(key), mContainANYPositionInInternalDimensions(false) { | 
|  | VLOG("creating SimpleConditionTracker %lld", (long long)mConditionId); | 
|  | mCountNesting = simplePredicate.count_nesting(); | 
|  |  | 
|  | 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 { | 
|  | mStartLogMatcherIndex = -1; | 
|  | } | 
|  |  | 
|  | if (simplePredicate.has_stop()) { | 
|  | auto pair = trackerNameIndexMap.find(simplePredicate.stop()); | 
|  | if (pair == trackerNameIndexMap.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 = trackerNameIndexMap.find(simplePredicate.stop_all()); | 
|  | if (pair == trackerNameIndexMap.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; | 
|  | } | 
|  |  | 
|  | if (simplePredicate.has_dimensions()) { | 
|  | translateFieldMatcher(simplePredicate.dimensions(), &mOutputDimensions); | 
|  | if (mOutputDimensions.size() > 0) { | 
|  | mSliced = true; | 
|  | mDimensionTag = mOutputDimensions[0].mMatcher.getTag(); | 
|  | } | 
|  | 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<Predicate>& allConditionConfig, | 
|  | const vector<sp<ConditionTracker>>& allConditionTrackers, | 
|  | const unordered_map<int64_t, int>& conditionIdIndexMap, | 
|  | vector<bool>& stack, | 
|  | vector<ConditionState>& initialConditionCache) { | 
|  | // SimpleConditionTracker does not have dependency on other conditions, thus we just return | 
|  | // if the initialization was successful. | 
|  | initialConditionCache[mIndex] = mInitialValue; | 
|  | return mInitialized; | 
|  | } | 
|  |  | 
|  | 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<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; | 
|  |  | 
|  | 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<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, %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<sp<ConditionTracker>>& allConditions, | 
|  | const bool isPartialLink, | 
|  | vector<ConditionState>& 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 |