blob: 1dcc8f96131ade58650975b9281ba7ab6983269f [file] [log] [blame]
Yao Chencaf339d2017-10-06 16:01:10 -07001/*
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
Yao Chen5110bed2017-10-23 12:50:02 -070017#define DEBUG false // STOPSHIP if true
Joe Onorato9fc9edf2017-10-15 20:08:52 -070018#include "Log.h"
Yao Chencaf339d2017-10-06 16:01:10 -070019
20#include "SimpleConditionTracker.h"
Yao Chenb3561512017-11-21 18:07:17 -080021#include "guardrail/StatsdStats.h"
Joe Onorato9fc9edf2017-10-15 20:08:52 -070022
Joe Onorato9fc9edf2017-10-15 20:08:52 -070023namespace android {
24namespace os {
25namespace statsd {
26
Yao Chencaf339d2017-10-06 16:01:10 -070027using std::unordered_map;
Yao Chencaf339d2017-10-06 16:01:10 -070028
Yao Chencaf339d2017-10-06 16:01:10 -070029SimpleConditionTracker::SimpleConditionTracker(
Tej Singhcc970142020-08-13 18:37:22 -070030 const ConfigKey& key, const int64_t& id, const uint64_t protoHash, const int index,
Stefan Lafon12d01fa2017-12-04 20:56:09 -080031 const SimplePredicate& simplePredicate,
Tej Singhcc970142020-08-13 18:37:22 -070032 const unordered_map<int64_t, int>& atomMatchingTrackerMap)
33 : ConditionTracker(id, index, protoHash),
34 mConfigKey(key),
35 mContainANYPositionInInternalDimensions(false) {
Yangster-mac94e197c2018-01-02 16:03:03 -080036 VLOG("creating SimpleConditionTracker %lld", (long long)mConditionId);
Stefan Lafon12d01fa2017-12-04 20:56:09 -080037 mCountNesting = simplePredicate.count_nesting();
Yao Chencaf339d2017-10-06 16:01:10 -070038
Tej Singhcc970142020-08-13 18:37:22 -070039 setMatcherIndices(simplePredicate, atomMatchingTrackerMap);
Yao Chencaf339d2017-10-06 16:01:10 -070040
Yao Chen8a8d16c2018-02-08 14:50:40 -080041 if (simplePredicate.has_dimensions()) {
42 translateFieldMatcher(simplePredicate.dimensions(), &mOutputDimensions);
43 if (mOutputDimensions.size() > 0) {
44 mSliced = true;
Yao Chen8a8d16c2018-02-08 14:50:40 -080045 }
Yangster13fb7e42018-03-07 17:30:49 -080046 mContainANYPositionInInternalDimensions = HasPositionANY(simplePredicate.dimensions());
Yao Chen5154a3792017-10-30 22:57:06 -070047 }
48
Stefan Lafon12d01fa2017-12-04 20:56:09 -080049 if (simplePredicate.initial_value() == SimplePredicate_InitialValue_FALSE) {
Yao Chen967b2052017-11-07 16:36:43 -080050 mInitialValue = ConditionState::kFalse;
51 } else {
52 mInitialValue = ConditionState::kUnknown;
53 }
54
Yao Chencaf339d2017-10-06 16:01:10 -070055 mInitialized = true;
56}
57
58SimpleConditionTracker::~SimpleConditionTracker() {
59 VLOG("~SimpleConditionTracker()");
60}
61
Stefan Lafon12d01fa2017-12-04 20:56:09 -080062bool SimpleConditionTracker::init(const vector<Predicate>& allConditionConfig,
Yao Chencaf339d2017-10-06 16:01:10 -070063 const vector<sp<ConditionTracker>>& allConditionTrackers,
Yangster-mac94e197c2018-01-02 16:03:03 -080064 const unordered_map<int64_t, int>& conditionIdIndexMap,
Tej Singhcfdf6712020-08-12 01:57:49 -070065 vector<bool>& stack, vector<ConditionState>& conditionCache) {
Yao Chencaf339d2017-10-06 16:01:10 -070066 // SimpleConditionTracker does not have dependency on other conditions, thus we just return
67 // if the initialization was successful.
Tej Singhcfdf6712020-08-12 01:57:49 -070068 ConditionKey conditionKey;
69 if (mSliced) {
70 conditionKey[mConditionId] = DEFAULT_DIMENSION_KEY;
71 }
72 isConditionMet(conditionKey, allConditionTrackers, mSliced, conditionCache);
Yao Chencaf339d2017-10-06 16:01:10 -070073 return mInitialized;
74}
75
Tej Singhcc970142020-08-13 18:37:22 -070076bool 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
87void 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
Yao Chen580ea3212018-02-26 14:21:54 -0800129void SimpleConditionTracker::dumpState() {
130 VLOG("%lld DUMP:", (long long)mConditionId);
131 for (const auto& pair : mSlicedConditionState) {
Yangster13fb7e42018-03-07 17:30:49 -0800132 VLOG("\t%s : %d", pair.first.toString().c_str(), pair.second);
Yao Chen729093d2017-10-16 10:33:26 -0700133 }
Yao Chen580ea3212018-02-26 14:21:54 -0800134
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 }
Yao Chen729093d2017-10-16 10:33:26 -0700143}
144
Yao Chen967b2052017-11-07 16:36:43 -0800145void 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
Yao Chen580ea3212018-02-26 14:21:54 -0800153 for (const auto& cond : mSlicedConditionState) {
154 if (cond.second > 0) {
155 mLastChangedToFalseDimensions.insert(cond.first);
156 }
157 }
158
Yao Chen967b2052017-11-07 16:36:43 -0800159 // 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
Yao Chenb3561512017-11-21 18:07:17 -0800165bool 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;
Yangster-mac94e197c2018-01-02 16:03:03 -0800173 StatsdStats::getInstance().noteConditionDimensionSize(mConfigKey, mConditionId, newTupleCount);
Yao Chenb3561512017-11-21 18:07:17 -0800174 // 2. Don't add more tuples, we are above the allowed threshold. Drop the data.
175 if (newTupleCount > StatsdStats::kDimensionKeySizeHardLimit) {
Yangster-mac94e197c2018-01-02 16:03:03 -0800176 ALOGE("Predicate %lld dropping data for dimension key %s",
Yangster13fb7e42018-03-07 17:30:49 -0800177 (long long)mConditionId, newKey.toString().c_str());
Yao Chenb3561512017-11-21 18:07:17 -0800178 return true;
179 }
180 }
181 return false;
182}
183
Yao Chen967b2052017-11-07 16:36:43 -0800184void SimpleConditionTracker::handleConditionEvent(const HashableDimensionKey& outputKey,
Yao Chen8a8d16c2018-02-08 14:50:40 -0800185 bool matchStart, ConditionState* conditionCache,
186 bool* conditionChangedCache) {
Yao Chen967b2052017-11-07 16:36:43 -0800187 bool changed = false;
188 auto outputIt = mSlicedConditionState.find(outputKey);
189 ConditionState newCondition;
Yao Chenb3561512017-11-21 18:07:17 -0800190 if (hitGuardRail(outputKey)) {
Yao Chen8a8d16c2018-02-08 14:50:40 -0800191 (*conditionChangedCache) = false;
Yao Chenb3561512017-11-21 18:07:17 -0800192 // Tells the caller it's evaluated.
Yao Chen8a8d16c2018-02-08 14:50:40 -0800193 (*conditionCache) = ConditionState::kUnknown;
Yao Chenb3561512017-11-21 18:07:17 -0800194 return;
195 }
Yao Chen967b2052017-11-07 16:36:43 -0800196 if (outputIt == mSlicedConditionState.end()) {
197 // We get a new output key.
198 newCondition = matchStart ? ConditionState::kTrue : ConditionState::kFalse;
199 if (matchStart && mInitialValue != ConditionState::kTrue) {
Yangster13fb7e42018-03-07 17:30:49 -0800200 mSlicedConditionState[outputKey] = 1;
Yao Chen967b2052017-11-07 16:36:43 -0800201 changed = true;
Yao Chen580ea3212018-02-26 14:21:54 -0800202 mLastChangedToTrueDimensions.insert(outputKey);
Yao Chen967b2052017-11-07 16:36:43 -0800203 } 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.
Yangster13fb7e42018-03-07 17:30:49 -0800206 mSlicedConditionState[outputKey] = 0;
Yao Chen580ea3212018-02-26 14:21:54 -0800207 mLastChangedToFalseDimensions.insert(outputKey);
Yao Chen967b2052017-11-07 16:36:43 -0800208 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) {
Yao Chen580ea3212018-02-26 14:21:54 -0800217 mLastChangedToTrueDimensions.insert(outputKey);
Yao Chen967b2052017-11-07 16:36:43 -0800218 // 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) {
Yao Chen580ea3212018-02-26 14:21:54 -0800241 mLastChangedToFalseDimensions.insert(outputKey);
Yao Chen967b2052017-11-07 16:36:43 -0800242 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);
Yangster13fb7e42018-03-07 17:30:49 -0800249 VLOG("erase key %s", outputKey.toString().c_str());
Yao Chen967b2052017-11-07 16:36:43 -0800250 }
251 }
252 }
253
254 // dump all dimensions for debugging
255 if (DEBUG) {
Yao Chen580ea3212018-02-26 14:21:54 -0800256 dumpState();
Yao Chen967b2052017-11-07 16:36:43 -0800257 }
258
Yao Chen8a8d16c2018-02-08 14:50:40 -0800259 (*conditionChangedCache) = changed;
260 (*conditionCache) = newCondition;
Yangster13fb7e42018-03-07 17:30:49 -0800261
Yangster-mac94e197c2018-01-02 16:03:03 -0800262 VLOG("SimplePredicate %lld nonSlicedChange? %d", (long long)mConditionId,
Yao Chen967b2052017-11-07 16:36:43 -0800263 conditionChangedCache[mIndex] == true);
264}
265
Yangster-mac93694462018-01-22 20:49:31 -0800266void 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) {
Yao Chencaf339d2017-10-06 16:01:10 -0700272 if (conditionCache[mIndex] != ConditionState::kNotEvaluated) {
273 // it has been evaluated.
Yangster-mac94e197c2018-01-02 16:03:03 -0800274 VLOG("Yes, already evaluated, %lld %d",
275 (long long)mConditionId, conditionCache[mIndex]);
Yao Chen967b2052017-11-07 16:36:43 -0800276 return;
Yao Chencaf339d2017-10-06 16:01:10 -0700277 }
Yao Chen580ea3212018-02-26 14:21:54 -0800278 mLastChangedToTrueDimensions.clear();
279 mLastChangedToFalseDimensions.clear();
Yao Chencaf339d2017-10-06 16:01:10 -0700280
David Chenc18abed2017-11-22 16:47:59 -0800281 if (mStopAllLogMatcherIndex >= 0 && mStopAllLogMatcherIndex < int(eventMatcherValues.size()) &&
Yao Chen967b2052017-11-07 16:36:43 -0800282 eventMatcherValues[mStopAllLogMatcherIndex] == MatchingState::kMatched) {
283 handleStopAll(conditionCache, conditionChangedCache);
284 return;
285 }
Yao Chen729093d2017-10-16 10:33:26 -0700286
Yao Chen967b2052017-11-07 16:36:43 -0800287 int matchedState = -1;
Yao Chencaf339d2017-10-06 16:01:10 -0700288 // 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) {
Yao Chen967b2052017-11-07 16:36:43 -0800292 matchedState = 1;
Yao Chencaf339d2017-10-06 16:01:10 -0700293 }
294
295 if (mStopLogMatcherIndex >= 0 &&
296 eventMatcherValues[mStopLogMatcherIndex] == MatchingState::kMatched) {
Yao Chen967b2052017-11-07 16:36:43 -0800297 matchedState = 0;
Yao Chencaf339d2017-10-06 16:01:10 -0700298 }
299
Yao Chen967b2052017-11-07 16:36:43 -0800300 if (matchedState < 0) {
Yao Chend41c4222017-11-15 19:26:14 -0800301 // The event doesn't match this condition. So we just report existing condition values.
Yao Chen967b2052017-11-07 16:36:43 -0800302 conditionChangedCache[mIndex] = false;
Yao Chend41c4222017-11-15 19:26:14 -0800303 if (mSliced) {
Yao Chen427d3722018-03-22 15:21:52 -0700304 // if the condition result is sliced. The overall condition is true if any of the sliced
305 // condition is true
Yangster-mac93694462018-01-22 20:49:31 -0800306 conditionCache[mIndex] = mInitialValue;
Yao Chen427d3722018-03-22 15:21:52 -0700307 for (const auto& slicedCondition : mSlicedConditionState) {
308 if (slicedCondition.second > 0) {
309 conditionCache[mIndex] = ConditionState::kTrue;
310 break;
311 }
312 }
Yao Chend41c4222017-11-15 19:26:14 -0800313 } else {
Yangster7c334a12017-11-22 14:24:24 -0800314 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 }
Yao Chend41c4222017-11-15 19:26:14 -0800324 }
Yao Chen967b2052017-11-07 16:36:43 -0800325 return;
Yao Chen729093d2017-10-16 10:33:26 -0700326 }
327
Yao Chen8a8d16c2018-02-08 14:50:40 -0800328 ConditionState overallState = mInitialValue;
329 bool overallChanged = false;
330
331 if (mOutputDimensions.size() == 0) {
332 handleConditionEvent(DEFAULT_DIMENSION_KEY, matchedState == 1, &overallState,
333 &overallChanged);
Yangster13fb7e42018-03-07 17:30:49 -0800334 } 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);
Yangster-mac20877162017-12-22 17:19:39 -0800345 } else {
Yangster-mace06cfd72018-03-10 23:22:59 -0800346 ALOGE("The condition tracker should not be sliced by ANY position matcher.");
Yangster-mac20877162017-12-22 17:19:39 -0800347 }
Yao Chen8a8d16c2018-02-08 14:50:40 -0800348 conditionCache[mIndex] = overallState;
349 conditionChangedCache[mIndex] = overallChanged;
Yao Chen729093d2017-10-16 10:33:26 -0700350}
351
352void SimpleConditionTracker::isConditionMet(
Yao Chen8a8d16c2018-02-08 14:50:40 -0800353 const ConditionKey& conditionParameters, const vector<sp<ConditionTracker>>& allConditions,
Yangster13fb7e42018-03-07 17:30:49 -0800354 const bool isPartialLink,
tsaichristine76853372019-08-06 17:17:03 -0700355 vector<ConditionState>& conditionCache) const {
Yao Chen8a8d16c2018-02-08 14:50:40 -0800356
Yangster-mac93694462018-01-22 20:49:31 -0800357 if (conditionCache[mIndex] != ConditionState::kNotEvaluated) {
358 // it has been evaluated.
359 VLOG("Yes, already evaluated, %lld %d",
360 (long long)mConditionId, conditionCache[mIndex]);
Yao Chen729093d2017-10-16 10:33:26 -0700361 return;
362 }
Yangster-mac93694462018-01-22 20:49:31 -0800363 const auto pair = conditionParameters.find(mConditionId);
364
365 if (pair == conditionParameters.end()) {
366 ConditionState conditionState = ConditionState::kNotEvaluated;
tsaichristine76853372019-08-06 17:17:03 -0700367 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;
Yangster-mac93694462018-01-22 20:49:31 -0800374 }
375 }
376 conditionCache[mIndex] = conditionState;
377 return;
378 }
Yao Chen729093d2017-10-16 10:33:26 -0700379
Yangster-mac20877162017-12-22 17:19:39 -0800380 ConditionState conditionState = ConditionState::kNotEvaluated;
Yangster13fb7e42018-03-07 17:30:49 -0800381 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;
Yangster13fb7e42018-03-07 17:30:49 -0800391 }
392 }
393 } else {
Yangster-mac20877162017-12-22 17:19:39 -0800394 auto startedCountIt = mSlicedConditionState.find(key);
Yangster13fb7e42018-03-07 17:30:49 -0800395 conditionState = conditionState | mInitialValue;
Yangster-mac20877162017-12-22 17:19:39 -0800396 if (startedCountIt != mSlicedConditionState.end()) {
Yangster-mac93694462018-01-22 20:49:31 -0800397 ConditionState sliceState =
398 startedCountIt->second > 0 ? ConditionState::kTrue : ConditionState::kFalse;
399 conditionState = conditionState | sliceState;
Yangster-mac20877162017-12-22 17:19:39 -0800400 }
Yangster13fb7e42018-03-07 17:30:49 -0800401
402 }
Yangster-mac20877162017-12-22 17:19:39 -0800403 conditionCache[mIndex] = conditionState;
Yangster-mac94e197c2018-01-02 16:03:03 -0800404 VLOG("Predicate %lld return %d", (long long)mConditionId, conditionCache[mIndex]);
Yao Chencaf339d2017-10-06 16:01:10 -0700405}
406
Yao Chencaf339d2017-10-06 16:01:10 -0700407} // namespace statsd
408} // namespace os
409} // namespace android