1 /* 2 * Copyright (C) 2017 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 #define DEBUG false // STOPSHIP if true 18 #include "Log.h" 19 20 #include "SimpleConditionTracker.h" 21 #include "guardrail/StatsdStats.h" 22 23 namespace android { 24 namespace os { 25 namespace statsd { 26 27 using std::unordered_map; 28 29 SimpleConditionTracker::SimpleConditionTracker( 30 const ConfigKey& key, const int64_t& id, const uint64_t protoHash, const int index, 31 const SimplePredicate& simplePredicate, 32 const unordered_map<int64_t, int>& atomMatchingTrackerMap) 33 : ConditionTracker(id, index, protoHash), 34 mConfigKey(key), 35 mContainANYPositionInInternalDimensions(false) { 36 VLOG("creating SimpleConditionTracker %lld", (long long)mConditionId); 37 mCountNesting = simplePredicate.count_nesting(); 38 39 setMatcherIndices(simplePredicate, atomMatchingTrackerMap); 40 41 if (simplePredicate.has_dimensions()) { 42 translateFieldMatcher(simplePredicate.dimensions(), &mOutputDimensions); 43 if (mOutputDimensions.size() > 0) { 44 mSliced = true; 45 } 46 mContainANYPositionInInternalDimensions = HasPositionANY(simplePredicate.dimensions()); 47 } 48 49 if (simplePredicate.initial_value() == SimplePredicate_InitialValue_FALSE) { 50 mInitialValue = ConditionState::kFalse; 51 } else { 52 mInitialValue = ConditionState::kUnknown; 53 } 54 55 mInitialized = true; 56 } 57 58 SimpleConditionTracker::~SimpleConditionTracker() { 59 VLOG("~SimpleConditionTracker()"); 60 } 61 62 bool SimpleConditionTracker::init(const vector<Predicate>& allConditionConfig, 63 const vector<sp<ConditionTracker>>& allConditionTrackers, 64 const unordered_map<int64_t, int>& conditionIdIndexMap, 65 vector<bool>& stack, vector<ConditionState>& conditionCache) { 66 // SimpleConditionTracker does not have dependency on other conditions, thus we just return 67 // if the initialization was successful. 68 ConditionKey conditionKey; 69 if (mSliced) { 70 conditionKey[mConditionId] = DEFAULT_DIMENSION_KEY; 71 } 72 isConditionMet(conditionKey, allConditionTrackers, mSliced, conditionCache); 73 return mInitialized; 74 } 75 76 bool SimpleConditionTracker::onConfigUpdated( 77 const vector<Predicate>& allConditionProtos, const int index, 78 const vector<sp<ConditionTracker>>& allConditionTrackers, 79 const unordered_map<int64_t, int>& atomMatchingTrackerMap, 80 const unordered_map<int64_t, int>& conditionTrackerMap) { 81 ConditionTracker::onConfigUpdated(allConditionProtos, index, allConditionTrackers, 82 atomMatchingTrackerMap, conditionTrackerMap); 83 setMatcherIndices(allConditionProtos[index].simple_predicate(), atomMatchingTrackerMap); 84 return true; 85 } 86 87 void SimpleConditionTracker::setMatcherIndices( 88 const SimplePredicate& simplePredicate, 89 const unordered_map<int64_t, int>& atomMatchingTrackerMap) { 90 mTrackerIndex.clear(); 91 if (simplePredicate.has_start()) { 92 auto pair = atomMatchingTrackerMap.find(simplePredicate.start()); 93 if (pair == atomMatchingTrackerMap.end()) { 94 ALOGW("Start matcher %lld not found in the config", (long long)simplePredicate.start()); 95 return; 96 } 97 mStartLogMatcherIndex = pair->second; 98 mTrackerIndex.insert(mStartLogMatcherIndex); 99 } else { 100 mStartLogMatcherIndex = -1; 101 } 102 103 if (simplePredicate.has_stop()) { 104 auto pair = atomMatchingTrackerMap.find(simplePredicate.stop()); 105 if (pair == atomMatchingTrackerMap.end()) { 106 ALOGW("Stop matcher %lld not found in the config", (long long)simplePredicate.stop()); 107 return; 108 } 109 mStopLogMatcherIndex = pair->second; 110 mTrackerIndex.insert(mStopLogMatcherIndex); 111 } else { 112 mStopLogMatcherIndex = -1; 113 } 114 115 if (simplePredicate.has_stop_all()) { 116 auto pair = atomMatchingTrackerMap.find(simplePredicate.stop_all()); 117 if (pair == atomMatchingTrackerMap.end()) { 118 ALOGW("Stop all matcher %lld found in the config", 119 (long long)simplePredicate.stop_all()); 120 return; 121 } 122 mStopAllLogMatcherIndex = pair->second; 123 mTrackerIndex.insert(mStopAllLogMatcherIndex); 124 } else { 125 mStopAllLogMatcherIndex = -1; 126 } 127 } 128 129 void SimpleConditionTracker::dumpState() { 130 VLOG("%lld DUMP:", (long long)mConditionId); 131 for (const auto& pair : mSlicedConditionState) { 132 VLOG("\t%s : %d", pair.first.toString().c_str(), pair.second); 133 } 134 135 VLOG("Changed to true keys: \n"); 136 for (const auto& key : mLastChangedToTrueDimensions) { 137 VLOG("%s", key.toString().c_str()); 138 } 139 VLOG("Changed to false keys: \n"); 140 for (const auto& key : mLastChangedToFalseDimensions) { 141 VLOG("%s", key.toString().c_str()); 142 } 143 } 144 145 void SimpleConditionTracker::handleStopAll(std::vector<ConditionState>& conditionCache, 146 std::vector<bool>& conditionChangedCache) { 147 // Unless the default condition is false, and there was nothing started, otherwise we have 148 // triggered a condition change. 149 conditionChangedCache[mIndex] = 150 (mInitialValue == ConditionState::kFalse && mSlicedConditionState.empty()) ? false 151 : true; 152 153 for (const auto& cond : mSlicedConditionState) { 154 if (cond.second > 0) { 155 mLastChangedToFalseDimensions.insert(cond.first); 156 } 157 } 158 159 // After StopAll, we know everything has stopped. From now on, default condition is false. 160 mInitialValue = ConditionState::kFalse; 161 mSlicedConditionState.clear(); 162 conditionCache[mIndex] = ConditionState::kFalse; 163 } 164 165 bool SimpleConditionTracker::hitGuardRail(const HashableDimensionKey& newKey) { 166 if (!mSliced || mSlicedConditionState.find(newKey) != mSlicedConditionState.end()) { 167 // if the condition is not sliced or the key is not new, we are good! 168 return false; 169 } 170 // 1. Report the tuple count if the tuple count > soft limit 171 if (mSlicedConditionState.size() > StatsdStats::kDimensionKeySizeSoftLimit - 1) { 172 size_t newTupleCount = mSlicedConditionState.size() + 1; 173 StatsdStats::getInstance().noteConditionDimensionSize(mConfigKey, mConditionId, newTupleCount); 174 // 2. Don't add more tuples, we are above the allowed threshold. Drop the data. 175 if (newTupleCount > StatsdStats::kDimensionKeySizeHardLimit) { 176 ALOGE("Predicate %lld dropping data for dimension key %s", 177 (long long)mConditionId, newKey.toString().c_str()); 178 return true; 179 } 180 } 181 return false; 182 } 183 184 void SimpleConditionTracker::handleConditionEvent(const HashableDimensionKey& outputKey, 185 bool matchStart, ConditionState* conditionCache, 186 bool* conditionChangedCache) { 187 bool changed = false; 188 auto outputIt = mSlicedConditionState.find(outputKey); 189 ConditionState newCondition; 190 if (hitGuardRail(outputKey)) { 191 (*conditionChangedCache) = false; 192 // Tells the caller it's evaluated. 193 (*conditionCache) = ConditionState::kUnknown; 194 return; 195 } 196 if (outputIt == mSlicedConditionState.end()) { 197 // We get a new output key. 198 newCondition = matchStart ? ConditionState::kTrue : ConditionState::kFalse; 199 if (matchStart && mInitialValue != ConditionState::kTrue) { 200 mSlicedConditionState[outputKey] = 1; 201 changed = true; 202 mLastChangedToTrueDimensions.insert(outputKey); 203 } else if (mInitialValue != ConditionState::kFalse) { 204 // it's a stop and we don't have history about it. 205 // If the default condition is not false, it means this stop is valuable to us. 206 mSlicedConditionState[outputKey] = 0; 207 mLastChangedToFalseDimensions.insert(outputKey); 208 changed = true; 209 } 210 } else { 211 // we have history about this output key. 212 auto& startedCount = outputIt->second; 213 // assign the old value first. 214 newCondition = startedCount > 0 ? ConditionState::kTrue : ConditionState::kFalse; 215 if (matchStart) { 216 if (startedCount == 0) { 217 mLastChangedToTrueDimensions.insert(outputKey); 218 // This condition for this output key will change from false -> true 219 changed = true; 220 } 221 222 // it's ok to do ++ here, even if we don't count nesting. The >1 counts will be treated 223 // as 1 if not counting nesting. 224 startedCount++; 225 newCondition = ConditionState::kTrue; 226 } else { 227 // This is a stop event. 228 if (startedCount > 0) { 229 if (mCountNesting) { 230 startedCount--; 231 if (startedCount == 0) { 232 newCondition = ConditionState::kFalse; 233 } 234 } else { 235 // not counting nesting, so ignore the number of starts, stop now. 236 startedCount = 0; 237 newCondition = ConditionState::kFalse; 238 } 239 // if everything has stopped for this output key, condition true -> false; 240 if (startedCount == 0) { 241 mLastChangedToFalseDimensions.insert(outputKey); 242 changed = true; 243 } 244 } 245 246 // if default condition is false, it means we don't need to keep the false values. 247 if (mInitialValue == ConditionState::kFalse && startedCount == 0) { 248 mSlicedConditionState.erase(outputIt); 249 VLOG("erase key %s", outputKey.toString().c_str()); 250 } 251 } 252 } 253 254 // dump all dimensions for debugging 255 if (DEBUG) { 256 dumpState(); 257 } 258 259 (*conditionChangedCache) = changed; 260 (*conditionCache) = newCondition; 261 262 VLOG("SimplePredicate %lld nonSlicedChange? %d", (long long)mConditionId, 263 conditionChangedCache[mIndex] == true); 264 } 265 266 void SimpleConditionTracker::evaluateCondition( 267 const LogEvent& event, 268 const vector<MatchingState>& eventMatcherValues, 269 const vector<sp<ConditionTracker>>& mAllConditions, 270 vector<ConditionState>& conditionCache, 271 vector<bool>& conditionChangedCache) { 272 if (conditionCache[mIndex] != ConditionState::kNotEvaluated) { 273 // it has been evaluated. 274 VLOG("Yes, already evaluated, %lld %d", 275 (long long)mConditionId, conditionCache[mIndex]); 276 return; 277 } 278 mLastChangedToTrueDimensions.clear(); 279 mLastChangedToFalseDimensions.clear(); 280 281 if (mStopAllLogMatcherIndex >= 0 && mStopAllLogMatcherIndex < int(eventMatcherValues.size()) && 282 eventMatcherValues[mStopAllLogMatcherIndex] == MatchingState::kMatched) { 283 handleStopAll(conditionCache, conditionChangedCache); 284 return; 285 } 286 287 int matchedState = -1; 288 // Note: The order to evaluate the following start, stop, stop_all matters. 289 // The priority of overwrite is stop_all > stop > start. 290 if (mStartLogMatcherIndex >= 0 && 291 eventMatcherValues[mStartLogMatcherIndex] == MatchingState::kMatched) { 292 matchedState = 1; 293 } 294 295 if (mStopLogMatcherIndex >= 0 && 296 eventMatcherValues[mStopLogMatcherIndex] == MatchingState::kMatched) { 297 matchedState = 0; 298 } 299 300 if (matchedState < 0) { 301 // The event doesn't match this condition. So we just report existing condition values. 302 conditionChangedCache[mIndex] = false; 303 if (mSliced) { 304 // if the condition result is sliced. The overall condition is true if any of the sliced 305 // condition is true 306 conditionCache[mIndex] = mInitialValue; 307 for (const auto& slicedCondition : mSlicedConditionState) { 308 if (slicedCondition.second > 0) { 309 conditionCache[mIndex] = ConditionState::kTrue; 310 break; 311 } 312 } 313 } else { 314 const auto& itr = mSlicedConditionState.find(DEFAULT_DIMENSION_KEY); 315 if (itr == mSlicedConditionState.end()) { 316 // condition not sliced, but we haven't seen the matched start or stop yet. so 317 // return initial value. 318 conditionCache[mIndex] = mInitialValue; 319 } else { 320 // return the cached condition. 321 conditionCache[mIndex] = 322 itr->second > 0 ? ConditionState::kTrue : ConditionState::kFalse; 323 } 324 } 325 return; 326 } 327 328 ConditionState overallState = mInitialValue; 329 bool overallChanged = false; 330 331 if (mOutputDimensions.size() == 0) { 332 handleConditionEvent(DEFAULT_DIMENSION_KEY, matchedState == 1, &overallState, 333 &overallChanged); 334 } else if (!mContainANYPositionInInternalDimensions) { 335 HashableDimensionKey outputValue; 336 filterValues(mOutputDimensions, event.getValues(), &outputValue); 337 338 // If this event has multiple nodes in the attribution chain, this log event probably will 339 // generate multiple dimensions. If so, we will find if the condition changes for any 340 // dimension and ask the corresponding metric producer to verify whether the actual sliced 341 // condition has changed or not. 342 // A high level assumption is that a predicate is either sliced or unsliced. We will never 343 // have both sliced and unsliced version of a predicate. 344 handleConditionEvent(outputValue, matchedState == 1, &overallState, &overallChanged); 345 } else { 346 ALOGE("The condition tracker should not be sliced by ANY position matcher."); 347 } 348 conditionCache[mIndex] = overallState; 349 conditionChangedCache[mIndex] = overallChanged; 350 } 351 352 void SimpleConditionTracker::isConditionMet( 353 const ConditionKey& conditionParameters, const vector<sp<ConditionTracker>>& allConditions, 354 const bool isPartialLink, 355 vector<ConditionState>& conditionCache) const { 356 357 if (conditionCache[mIndex] != ConditionState::kNotEvaluated) { 358 // it has been evaluated. 359 VLOG("Yes, already evaluated, %lld %d", 360 (long long)mConditionId, conditionCache[mIndex]); 361 return; 362 } 363 const auto pair = conditionParameters.find(mConditionId); 364 365 if (pair == conditionParameters.end()) { 366 ConditionState conditionState = ConditionState::kNotEvaluated; 367 conditionState = conditionState | mInitialValue; 368 if (!mSliced) { 369 const auto& itr = mSlicedConditionState.find(DEFAULT_DIMENSION_KEY); 370 if (itr != mSlicedConditionState.end()) { 371 ConditionState sliceState = 372 itr->second > 0 ? ConditionState::kTrue : ConditionState::kFalse; 373 conditionState = conditionState | sliceState; 374 } 375 } 376 conditionCache[mIndex] = conditionState; 377 return; 378 } 379 380 ConditionState conditionState = ConditionState::kNotEvaluated; 381 const HashableDimensionKey& key = pair->second; 382 if (isPartialLink) { 383 // For unseen key, check whether the require dimensions are subset of sliced condition 384 // output. 385 conditionState = conditionState | mInitialValue; 386 for (const auto& slice : mSlicedConditionState) { 387 ConditionState sliceState = 388 slice.second > 0 ? ConditionState::kTrue : ConditionState::kFalse; 389 if (slice.first.contains(key)) { 390 conditionState = conditionState | sliceState; 391 } 392 } 393 } else { 394 auto startedCountIt = mSlicedConditionState.find(key); 395 conditionState = conditionState | mInitialValue; 396 if (startedCountIt != mSlicedConditionState.end()) { 397 ConditionState sliceState = 398 startedCountIt->second > 0 ? ConditionState::kTrue : ConditionState::kFalse; 399 conditionState = conditionState | sliceState; 400 } 401 402 } 403 conditionCache[mIndex] = conditionState; 404 VLOG("Predicate %lld return %d", (long long)mConditionId, conditionCache[mIndex]); 405 } 406 407 } // namespace statsd 408 } // namespace os 409 } // namespace android 410