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