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