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<uint8_t> & 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<uint8_t>& 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<uint8_t> & conditionChangedCache)147 void SimpleConditionTracker::handleStopAll(std::vector<ConditionState>& conditionCache,
148                                            std::vector<uint8_t>& 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) const167 bool SimpleConditionTracker::hitGuardRail(const HashableDimensionKey& newKey) const {
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) {
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);
266 }
267 
evaluateCondition(const LogEvent & event,const vector<MatchingState> & eventMatcherValues,const vector<sp<ConditionTracker>> & mAllConditions,vector<ConditionState> & conditionCache,vector<uint8_t> & conditionChangedCache)268 void SimpleConditionTracker::evaluateCondition(const LogEvent& event,
269                                                const vector<MatchingState>& eventMatcherValues,
270                                                const vector<sp<ConditionTracker>>& mAllConditions,
271                                                vector<ConditionState>& conditionCache,
272                                                vector<uint8_t>& conditionChangedCache) {
273     if (conditionCache[mIndex] != ConditionState::kNotEvaluated) {
274         // it has been evaluated.
275         VLOG("Yes, already evaluated, %lld %d",
276             (long long)mConditionId, conditionCache[mIndex]);
277         return;
278     }
279     mLastChangedToTrueDimensions.clear();
280     mLastChangedToFalseDimensions.clear();
281 
282     if (mStopAllLogMatcherIndex >= 0 && mStopAllLogMatcherIndex < int(eventMatcherValues.size()) &&
283         eventMatcherValues[mStopAllLogMatcherIndex] == MatchingState::kMatched) {
284         handleStopAll(conditionCache, conditionChangedCache);
285         return;
286     }
287 
288     int matchedState = -1;
289     // Note: The order to evaluate the following start, stop, stop_all matters.
290     // The priority of overwrite is stop_all > stop > start.
291     if (mStartLogMatcherIndex >= 0 &&
292         eventMatcherValues[mStartLogMatcherIndex] == MatchingState::kMatched) {
293         matchedState = 1;
294     }
295 
296     if (mStopLogMatcherIndex >= 0 &&
297         eventMatcherValues[mStopLogMatcherIndex] == MatchingState::kMatched) {
298         matchedState = 0;
299     }
300 
301     if (matchedState < 0) {
302         // The event doesn't match this condition. So we just report existing condition values.
303         conditionChangedCache[mIndex] = false;
304         if (mSliced) {
305             // if the condition result is sliced. The overall condition is true if any of the sliced
306             // condition is true
307             conditionCache[mIndex] = mInitialValue;
308             for (const auto& slicedCondition : mSlicedConditionState) {
309                 if (slicedCondition.second > 0) {
310                     conditionCache[mIndex] = ConditionState::kTrue;
311                     break;
312                 }
313             }
314         } else {
315             const auto& itr = mSlicedConditionState.find(DEFAULT_DIMENSION_KEY);
316             if (itr == mSlicedConditionState.end()) {
317                 // condition not sliced, but we haven't seen the matched start or stop yet. so
318                 // return initial value.
319                 conditionCache[mIndex] = mInitialValue;
320             } else {
321                 // return the cached condition.
322                 conditionCache[mIndex] =
323                         itr->second > 0 ? ConditionState::kTrue : ConditionState::kFalse;
324             }
325         }
326         return;
327     }
328 
329     ConditionState overallState = mInitialValue;
330     bool overallChanged = false;
331 
332     if (mOutputDimensions.size() == 0) {
333         handleConditionEvent(DEFAULT_DIMENSION_KEY, matchedState == 1, &overallState,
334                              &overallChanged);
335     } else if (!mContainANYPositionInInternalDimensions) {
336         HashableDimensionKey outputValue;
337         filterValues(mOutputDimensions, event.getValues(), &outputValue);
338 
339         // If this event has multiple nodes in the attribution chain,  this log event probably will
340         // generate multiple dimensions. If so, we will find if the condition changes for any
341         // dimension and ask the corresponding metric producer to verify whether the actual sliced
342         // condition has changed or not.
343         // A high level assumption is that a predicate is either sliced or unsliced. We will never
344         // have both sliced and unsliced version of a predicate.
345         handleConditionEvent(outputValue, matchedState == 1, &overallState, &overallChanged);
346     } else {
347         ALOGE("The condition tracker should not be sliced by ANY position matcher.");
348     }
349     conditionCache[mIndex] = overallState;
350     conditionChangedCache[mIndex] = overallChanged;
351 }
352 
isConditionMet(const ConditionKey & conditionParameters,const vector<sp<ConditionTracker>> & allConditions,const bool isPartialLink,vector<ConditionState> & conditionCache) const353 void SimpleConditionTracker::isConditionMet(
354         const ConditionKey& conditionParameters, const vector<sp<ConditionTracker>>& allConditions,
355         const bool isPartialLink,
356         vector<ConditionState>& conditionCache) const {
357 
358     if (conditionCache[mIndex] != ConditionState::kNotEvaluated) {
359         // it has been evaluated.
360         VLOG("Yes, already evaluated, %lld %d",
361             (long long)mConditionId, conditionCache[mIndex]);
362         return;
363     }
364     const auto pair = conditionParameters.find(mConditionId);
365 
366     if (pair == conditionParameters.end()) {
367         ConditionState conditionState = ConditionState::kNotEvaluated;
368         conditionState = conditionState | mInitialValue;
369         if (!mSliced) {
370             const auto& itr = mSlicedConditionState.find(DEFAULT_DIMENSION_KEY);
371             if (itr != mSlicedConditionState.end()) {
372                 ConditionState sliceState =
373                     itr->second > 0 ? ConditionState::kTrue : ConditionState::kFalse;
374                 conditionState = conditionState | sliceState;
375             }
376         }
377         conditionCache[mIndex] = conditionState;
378         return;
379     }
380 
381     ConditionState conditionState = ConditionState::kNotEvaluated;
382     const HashableDimensionKey& key = pair->second;
383     if (isPartialLink) {
384         // For unseen key, check whether the require dimensions are subset of sliced condition
385         // output.
386         conditionState = conditionState | mInitialValue;
387         for (const auto& slice : mSlicedConditionState) {
388             ConditionState sliceState =
389                 slice.second > 0 ? ConditionState::kTrue : ConditionState::kFalse;
390             if (slice.first.contains(key)) {
391                 conditionState = conditionState | sliceState;
392             }
393         }
394     } else {
395         auto startedCountIt = mSlicedConditionState.find(key);
396         conditionState = conditionState | mInitialValue;
397         if (startedCountIt != mSlicedConditionState.end()) {
398             ConditionState sliceState =
399                 startedCountIt->second > 0 ? ConditionState::kTrue : ConditionState::kFalse;
400             conditionState = conditionState | sliceState;
401         }
402 
403     }
404     conditionCache[mIndex] = conditionState;
405     VLOG("Predicate %lld return %d", (long long)mConditionId, conditionCache[mIndex]);
406 }
407 
408 }  // namespace statsd
409 }  // namespace os
410 }  // namespace android
411