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