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 "CountMetricProducer.h"
21
22 #include <inttypes.h>
23 #include <limits.h>
24 #include <stdlib.h>
25
26 #include "guardrail/StatsdStats.h"
27 #include "metrics/parsing_utils/metrics_manager_util.h"
28 #include "stats_log_util.h"
29 #include "stats_util.h"
30
31 using android::util::FIELD_COUNT_REPEATED;
32 using android::util::FIELD_TYPE_BOOL;
33 using android::util::FIELD_TYPE_FLOAT;
34 using android::util::FIELD_TYPE_INT32;
35 using android::util::FIELD_TYPE_INT64;
36 using android::util::FIELD_TYPE_MESSAGE;
37 using android::util::FIELD_TYPE_STRING;
38 using android::util::ProtoOutputStream;
39 using std::map;
40 using std::string;
41 using std::unordered_map;
42 using std::vector;
43 using std::shared_ptr;
44
45 namespace android {
46 namespace os {
47 namespace statsd {
48
49 // for StatsLogReport
50 const int FIELD_ID_ID = 1;
51 const int FIELD_ID_COUNT_METRICS = 5;
52 const int FIELD_ID_TIME_BASE = 9;
53 const int FIELD_ID_BUCKET_SIZE = 10;
54 const int FIELD_ID_DIMENSION_PATH_IN_WHAT = 11;
55 const int FIELD_ID_IS_ACTIVE = 14;
56
57 // for CountMetricDataWrapper
58 const int FIELD_ID_DATA = 1;
59 // for CountMetricData
60 const int FIELD_ID_DIMENSION_IN_WHAT = 1;
61 const int FIELD_ID_SLICE_BY_STATE = 6;
62 const int FIELD_ID_BUCKET_INFO = 3;
63 const int FIELD_ID_DIMENSION_LEAF_IN_WHAT = 4;
64 // for CountBucketInfo
65 const int FIELD_ID_COUNT = 3;
66 const int FIELD_ID_BUCKET_NUM = 4;
67 const int FIELD_ID_START_BUCKET_ELAPSED_MILLIS = 5;
68 const int FIELD_ID_END_BUCKET_ELAPSED_MILLIS = 6;
69
CountMetricProducer(const ConfigKey & key,const CountMetric & metric,const int conditionIndex,const vector<ConditionState> & initialConditionCache,const sp<ConditionWizard> & wizard,const uint64_t protoHash,const int64_t timeBaseNs,const int64_t startTimeNs,const unordered_map<int,shared_ptr<Activation>> & eventActivationMap,const unordered_map<int,vector<shared_ptr<Activation>>> & eventDeactivationMap,const vector<int> & slicedStateAtoms,const unordered_map<int,unordered_map<int,int64_t>> & stateGroupMap)70 CountMetricProducer::CountMetricProducer(
71 const ConfigKey& key, const CountMetric& metric, const int conditionIndex,
72 const vector<ConditionState>& initialConditionCache, const sp<ConditionWizard>& wizard,
73 const uint64_t protoHash, const int64_t timeBaseNs, const int64_t startTimeNs,
74 const unordered_map<int, shared_ptr<Activation>>& eventActivationMap,
75 const unordered_map<int, vector<shared_ptr<Activation>>>& eventDeactivationMap,
76 const vector<int>& slicedStateAtoms,
77 const unordered_map<int, unordered_map<int, int64_t>>& stateGroupMap)
78 : MetricProducer(metric.id(), key, timeBaseNs, conditionIndex, initialConditionCache, wizard,
79 protoHash, eventActivationMap, eventDeactivationMap, slicedStateAtoms,
80 stateGroupMap) {
81 if (metric.has_bucket()) {
82 mBucketSizeNs =
83 TimeUnitToBucketSizeInMillisGuardrailed(key.GetUid(), metric.bucket()) * 1000000;
84 } else {
85 mBucketSizeNs = LLONG_MAX;
86 }
87
88 if (metric.has_dimensions_in_what()) {
89 translateFieldMatcher(metric.dimensions_in_what(), &mDimensionsInWhat);
90 mContainANYPositionInDimensionsInWhat = HasPositionANY(metric.dimensions_in_what());
91 }
92
93 mSliceByPositionALL = HasPositionALL(metric.dimensions_in_what());
94
95 if (metric.links().size() > 0) {
96 for (const auto& link : metric.links()) {
97 Metric2Condition mc;
98 mc.conditionId = link.condition();
99 translateFieldMatcher(link.fields_in_what(), &mc.metricFields);
100 translateFieldMatcher(link.fields_in_condition(), &mc.conditionFields);
101 mMetric2ConditionLinks.push_back(mc);
102 }
103 mConditionSliced = true;
104 }
105
106 for (const auto& stateLink : metric.state_link()) {
107 Metric2State ms;
108 ms.stateAtomId = stateLink.state_atom_id();
109 translateFieldMatcher(stateLink.fields_in_what(), &ms.metricFields);
110 translateFieldMatcher(stateLink.fields_in_state(), &ms.stateFields);
111 mMetric2StateLinks.push_back(ms);
112 }
113
114 if (metric.has_threshold()) {
115 mUploadThreshold = metric.threshold();
116 }
117
118 flushIfNeededLocked(startTimeNs);
119 // Adjust start for partial bucket
120 mCurrentBucketStartTimeNs = startTimeNs;
121
122 VLOG("metric %lld created. bucket size %lld start_time: %lld", (long long)metric.id(),
123 (long long)mBucketSizeNs, (long long)mTimeBaseNs);
124 }
125
~CountMetricProducer()126 CountMetricProducer::~CountMetricProducer() {
127 VLOG("~CountMetricProducer() called");
128 }
129
onConfigUpdatedLocked(const StatsdConfig & config,const int configIndex,const int metricIndex,const vector<sp<AtomMatchingTracker>> & allAtomMatchingTrackers,const unordered_map<int64_t,int> & oldAtomMatchingTrackerMap,const unordered_map<int64_t,int> & newAtomMatchingTrackerMap,const sp<EventMatcherWizard> & matcherWizard,const vector<sp<ConditionTracker>> & allConditionTrackers,const unordered_map<int64_t,int> & conditionTrackerMap,const sp<ConditionWizard> & wizard,const unordered_map<int64_t,int> & metricToActivationMap,unordered_map<int,vector<int>> & trackerToMetricMap,unordered_map<int,vector<int>> & conditionToMetricMap,unordered_map<int,vector<int>> & activationAtomTrackerToMetricMap,unordered_map<int,vector<int>> & deactivationAtomTrackerToMetricMap,vector<int> & metricsWithActivation)130 bool CountMetricProducer::onConfigUpdatedLocked(
131 const StatsdConfig& config, const int configIndex, const int metricIndex,
132 const vector<sp<AtomMatchingTracker>>& allAtomMatchingTrackers,
133 const unordered_map<int64_t, int>& oldAtomMatchingTrackerMap,
134 const unordered_map<int64_t, int>& newAtomMatchingTrackerMap,
135 const sp<EventMatcherWizard>& matcherWizard,
136 const vector<sp<ConditionTracker>>& allConditionTrackers,
137 const unordered_map<int64_t, int>& conditionTrackerMap, const sp<ConditionWizard>& wizard,
138 const unordered_map<int64_t, int>& metricToActivationMap,
139 unordered_map<int, vector<int>>& trackerToMetricMap,
140 unordered_map<int, vector<int>>& conditionToMetricMap,
141 unordered_map<int, vector<int>>& activationAtomTrackerToMetricMap,
142 unordered_map<int, vector<int>>& deactivationAtomTrackerToMetricMap,
143 vector<int>& metricsWithActivation) {
144 if (!MetricProducer::onConfigUpdatedLocked(
145 config, configIndex, metricIndex, allAtomMatchingTrackers,
146 oldAtomMatchingTrackerMap, newAtomMatchingTrackerMap, matcherWizard,
147 allConditionTrackers, conditionTrackerMap, wizard, metricToActivationMap,
148 trackerToMetricMap, conditionToMetricMap, activationAtomTrackerToMetricMap,
149 deactivationAtomTrackerToMetricMap, metricsWithActivation)) {
150 return false;
151 }
152
153 const CountMetric& metric = config.count_metric(configIndex);
154 int trackerIndex;
155 // Update appropriate indices, specifically mConditionIndex and MetricsManager maps.
156 if (!handleMetricWithAtomMatchingTrackers(metric.what(), metricIndex, false,
157 allAtomMatchingTrackers, newAtomMatchingTrackerMap,
158 trackerToMetricMap, trackerIndex)) {
159 return false;
160 }
161
162 if (metric.has_condition() &&
163 !handleMetricWithConditions(metric.condition(), metricIndex, conditionTrackerMap,
164 metric.links(), allConditionTrackers, mConditionTrackerIndex,
165 conditionToMetricMap)) {
166 return false;
167 }
168 return true;
169 }
170
onStateChanged(const int64_t eventTimeNs,const int32_t atomId,const HashableDimensionKey & primaryKey,const FieldValue & oldState,const FieldValue & newState)171 void CountMetricProducer::onStateChanged(const int64_t eventTimeNs, const int32_t atomId,
172 const HashableDimensionKey& primaryKey,
173 const FieldValue& oldState, const FieldValue& newState) {
174 VLOG("CountMetric %lld onStateChanged time %lld, State%d, key %s, %d -> %d",
175 (long long)mMetricId, (long long)eventTimeNs, atomId, primaryKey.toString().c_str(),
176 oldState.mValue.int_value, newState.mValue.int_value);
177 }
178
dumpStatesLocked(FILE * out,bool verbose) const179 void CountMetricProducer::dumpStatesLocked(FILE* out, bool verbose) const {
180 if (mCurrentSlicedCounter == nullptr ||
181 mCurrentSlicedCounter->size() == 0) {
182 return;
183 }
184
185 fprintf(out, "CountMetric %lld dimension size %lu\n", (long long)mMetricId,
186 (unsigned long)mCurrentSlicedCounter->size());
187 if (verbose) {
188 for (const auto& it : *mCurrentSlicedCounter) {
189 fprintf(out, "\t(what)%s\t(state)%s %lld\n",
190 it.first.getDimensionKeyInWhat().toString().c_str(),
191 it.first.getStateValuesKey().toString().c_str(), (unsigned long long)it.second);
192 }
193 }
194 }
195
onSlicedConditionMayChangeLocked(bool overallCondition,const int64_t eventTime)196 void CountMetricProducer::onSlicedConditionMayChangeLocked(bool overallCondition,
197 const int64_t eventTime) {
198 VLOG("Metric %lld onSlicedConditionMayChange", (long long)mMetricId);
199 }
200
201
clearPastBucketsLocked(const int64_t dumpTimeNs)202 void CountMetricProducer::clearPastBucketsLocked(const int64_t dumpTimeNs) {
203 mPastBuckets.clear();
204 }
205
onDumpReportLocked(const int64_t dumpTimeNs,const bool include_current_partial_bucket,const bool erase_data,const DumpLatency dumpLatency,std::set<string> * str_set,ProtoOutputStream * protoOutput)206 void CountMetricProducer::onDumpReportLocked(const int64_t dumpTimeNs,
207 const bool include_current_partial_bucket,
208 const bool erase_data,
209 const DumpLatency dumpLatency,
210 std::set<string> *str_set,
211 ProtoOutputStream* protoOutput) {
212 if (include_current_partial_bucket) {
213 flushLocked(dumpTimeNs);
214 } else {
215 flushIfNeededLocked(dumpTimeNs);
216 }
217 protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_ID, (long long)mMetricId);
218 protoOutput->write(FIELD_TYPE_BOOL | FIELD_ID_IS_ACTIVE, isActiveLocked());
219
220
221 if (mPastBuckets.empty()) {
222 return;
223 }
224 protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_TIME_BASE, (long long)mTimeBaseNs);
225 protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_BUCKET_SIZE, (long long)mBucketSizeNs);
226
227 // Fills the dimension path if not slicing by ALL.
228 if (!mSliceByPositionALL) {
229 if (!mDimensionsInWhat.empty()) {
230 uint64_t dimenPathToken = protoOutput->start(
231 FIELD_TYPE_MESSAGE | FIELD_ID_DIMENSION_PATH_IN_WHAT);
232 writeDimensionPathToProto(mDimensionsInWhat, protoOutput);
233 protoOutput->end(dimenPathToken);
234 }
235 }
236
237 uint64_t protoToken = protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_ID_COUNT_METRICS);
238
239 for (const auto& counter : mPastBuckets) {
240 const MetricDimensionKey& dimensionKey = counter.first;
241 VLOG(" dimension key %s", dimensionKey.toString().c_str());
242
243 uint64_t wrapperToken =
244 protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_DATA);
245
246 // First fill dimension.
247 if (mSliceByPositionALL) {
248 uint64_t dimensionToken = protoOutput->start(
249 FIELD_TYPE_MESSAGE | FIELD_ID_DIMENSION_IN_WHAT);
250 writeDimensionToProto(dimensionKey.getDimensionKeyInWhat(), str_set, protoOutput);
251 protoOutput->end(dimensionToken);
252 } else {
253 writeDimensionLeafNodesToProto(dimensionKey.getDimensionKeyInWhat(),
254 FIELD_ID_DIMENSION_LEAF_IN_WHAT, str_set, protoOutput);
255 }
256 // Then fill slice_by_state.
257 for (auto state : dimensionKey.getStateValuesKey().getValues()) {
258 uint64_t stateToken = protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED |
259 FIELD_ID_SLICE_BY_STATE);
260 writeStateToProto(state, protoOutput);
261 protoOutput->end(stateToken);
262 }
263 // Then fill bucket_info (CountBucketInfo).
264 for (const auto& bucket : counter.second) {
265 uint64_t bucketInfoToken = protoOutput->start(
266 FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_BUCKET_INFO);
267 // Partial bucket.
268 if (bucket.mBucketEndNs - bucket.mBucketStartNs != mBucketSizeNs) {
269 protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_START_BUCKET_ELAPSED_MILLIS,
270 (long long)NanoToMillis(bucket.mBucketStartNs));
271 protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_END_BUCKET_ELAPSED_MILLIS,
272 (long long)NanoToMillis(bucket.mBucketEndNs));
273 } else {
274 protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_BUCKET_NUM,
275 (long long)(getBucketNumFromEndTimeNs(bucket.mBucketEndNs)));
276 }
277 protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_COUNT, (long long)bucket.mCount);
278 protoOutput->end(bucketInfoToken);
279 VLOG("\t bucket [%lld - %lld] count: %lld", (long long)bucket.mBucketStartNs,
280 (long long)bucket.mBucketEndNs, (long long)bucket.mCount);
281 }
282 protoOutput->end(wrapperToken);
283 }
284
285 protoOutput->end(protoToken);
286
287 if (erase_data) {
288 mPastBuckets.clear();
289 }
290 }
291
dropDataLocked(const int64_t dropTimeNs)292 void CountMetricProducer::dropDataLocked(const int64_t dropTimeNs) {
293 flushIfNeededLocked(dropTimeNs);
294 StatsdStats::getInstance().noteBucketDropped(mMetricId);
295 mPastBuckets.clear();
296 }
297
onConditionChangedLocked(const bool conditionMet,const int64_t eventTime)298 void CountMetricProducer::onConditionChangedLocked(const bool conditionMet,
299 const int64_t eventTime) {
300 VLOG("Metric %lld onConditionChanged", (long long)mMetricId);
301 mCondition = conditionMet ? ConditionState::kTrue : ConditionState::kFalse;
302 }
303
hitGuardRailLocked(const MetricDimensionKey & newKey)304 bool CountMetricProducer::hitGuardRailLocked(const MetricDimensionKey& newKey) {
305 if (mCurrentSlicedCounter->find(newKey) != mCurrentSlicedCounter->end()) {
306 return false;
307 }
308 // ===========GuardRail==============
309 // 1. Report the tuple count if the tuple count > soft limit
310 if (mCurrentSlicedCounter->size() > StatsdStats::kDimensionKeySizeSoftLimit - 1) {
311 size_t newTupleCount = mCurrentSlicedCounter->size() + 1;
312 StatsdStats::getInstance().noteMetricDimensionSize(mConfigKey, mMetricId, newTupleCount);
313 // 2. Don't add more tuples, we are above the allowed threshold. Drop the data.
314 if (newTupleCount > StatsdStats::kDimensionKeySizeHardLimit) {
315 ALOGE("CountMetric %lld dropping data for dimension key %s",
316 (long long)mMetricId, newKey.toString().c_str());
317 StatsdStats::getInstance().noteHardDimensionLimitReached(mMetricId);
318 return true;
319 }
320 }
321
322 return false;
323 }
324
onMatchedLogEventInternalLocked(const size_t matcherIndex,const MetricDimensionKey & eventKey,const ConditionKey & conditionKey,bool condition,const LogEvent & event,const map<int,HashableDimensionKey> & statePrimaryKeys)325 void CountMetricProducer::onMatchedLogEventInternalLocked(
326 const size_t matcherIndex, const MetricDimensionKey& eventKey,
327 const ConditionKey& conditionKey, bool condition, const LogEvent& event,
328 const map<int, HashableDimensionKey>& statePrimaryKeys) {
329 int64_t eventTimeNs = event.GetElapsedTimestampNs();
330 flushIfNeededLocked(eventTimeNs);
331
332 if (!condition) {
333 return;
334 }
335
336 auto it = mCurrentSlicedCounter->find(eventKey);
337 if (it == mCurrentSlicedCounter->end()) {
338 // ===========GuardRail==============
339 if (hitGuardRailLocked(eventKey)) {
340 return;
341 }
342 // create a counter for the new key
343 (*mCurrentSlicedCounter)[eventKey] = 1;
344 } else {
345 // increment the existing value
346 auto& count = it->second;
347 count++;
348 }
349 for (auto& tracker : mAnomalyTrackers) {
350 int64_t countWholeBucket = mCurrentSlicedCounter->find(eventKey)->second;
351 auto prev = mCurrentFullCounters->find(eventKey);
352 if (prev != mCurrentFullCounters->end()) {
353 countWholeBucket += prev->second;
354 }
355 tracker->detectAndDeclareAnomaly(eventTimeNs, mCurrentBucketNum, mMetricId, eventKey,
356 countWholeBucket);
357 }
358
359 VLOG("metric %lld %s->%lld", (long long)mMetricId, eventKey.toString().c_str(),
360 (long long)(*mCurrentSlicedCounter)[eventKey]);
361 }
362
363 // When a new matched event comes in, we check if event falls into the current
364 // bucket. If not, flush the old counter to past buckets and initialize the new bucket.
flushIfNeededLocked(const int64_t & eventTimeNs)365 void CountMetricProducer::flushIfNeededLocked(const int64_t& eventTimeNs) {
366 int64_t currentBucketEndTimeNs = getCurrentBucketEndTimeNs();
367 if (eventTimeNs < currentBucketEndTimeNs) {
368 return;
369 }
370
371 // Setup the bucket start time and number.
372 int64_t numBucketsForward = 1 + (eventTimeNs - currentBucketEndTimeNs) / mBucketSizeNs;
373 int64_t nextBucketNs = currentBucketEndTimeNs + (numBucketsForward - 1) * mBucketSizeNs;
374 flushCurrentBucketLocked(eventTimeNs, nextBucketNs);
375
376 mCurrentBucketNum += numBucketsForward;
377 VLOG("metric %lld: new bucket start time: %lld", (long long)mMetricId,
378 (long long)mCurrentBucketStartTimeNs);
379 }
380
countPassesThreshold(const int64_t & count)381 bool CountMetricProducer::countPassesThreshold(const int64_t& count) {
382 if (mUploadThreshold == nullopt) {
383 return true;
384 }
385
386 switch (mUploadThreshold->value_comparison_case()) {
387 case UploadThreshold::kLtInt:
388 return count < mUploadThreshold->lt_int();
389 case UploadThreshold::kGtInt:
390 return count > mUploadThreshold->gt_int();
391 case UploadThreshold::kLteInt:
392 return count <= mUploadThreshold->lte_int();
393 case UploadThreshold::kGteInt:
394 return count >= mUploadThreshold->gte_int();
395 default:
396 ALOGE("Count metric incorrect upload threshold type used");
397 return false;
398 }
399 }
400
flushCurrentBucketLocked(const int64_t & eventTimeNs,const int64_t & nextBucketStartTimeNs)401 void CountMetricProducer::flushCurrentBucketLocked(const int64_t& eventTimeNs,
402 const int64_t& nextBucketStartTimeNs) {
403 int64_t fullBucketEndTimeNs = getCurrentBucketEndTimeNs();
404 CountBucket info;
405 info.mBucketStartNs = mCurrentBucketStartTimeNs;
406 if (eventTimeNs < fullBucketEndTimeNs) {
407 info.mBucketEndNs = eventTimeNs;
408 } else {
409 info.mBucketEndNs = fullBucketEndTimeNs;
410 }
411 for (const auto& counter : *mCurrentSlicedCounter) {
412 if (countPassesThreshold(counter.second)) {
413 info.mCount = counter.second;
414 auto& bucketList = mPastBuckets[counter.first];
415 bucketList.push_back(info);
416 VLOG("metric %lld, dump key value: %s -> %lld", (long long)mMetricId,
417 counter.first.toString().c_str(), (long long)counter.second);
418 }
419 }
420
421 // If we have finished a full bucket, then send this to anomaly tracker.
422 if (eventTimeNs > fullBucketEndTimeNs) {
423 // Accumulate partial buckets with current value and then send to anomaly tracker.
424 if (mCurrentFullCounters->size() > 0) {
425 for (const auto& keyValuePair : *mCurrentSlicedCounter) {
426 (*mCurrentFullCounters)[keyValuePair.first] += keyValuePair.second;
427 }
428 for (auto& tracker : mAnomalyTrackers) {
429 tracker->addPastBucket(mCurrentFullCounters, mCurrentBucketNum);
430 }
431 mCurrentFullCounters = std::make_shared<DimToValMap>();
432 } else {
433 // Skip aggregating the partial buckets since there's no previous partial bucket.
434 for (auto& tracker : mAnomalyTrackers) {
435 tracker->addPastBucket(mCurrentSlicedCounter, mCurrentBucketNum);
436 }
437 }
438 } else {
439 // Accumulate partial bucket.
440 for (const auto& keyValuePair : *mCurrentSlicedCounter) {
441 (*mCurrentFullCounters)[keyValuePair.first] += keyValuePair.second;
442 }
443 }
444
445 StatsdStats::getInstance().noteBucketCount(mMetricId);
446 // Only resets the counters, but doesn't setup the times nor numbers.
447 // (Do not clear since the old one is still referenced in mAnomalyTrackers).
448 mCurrentSlicedCounter = std::make_shared<DimToValMap>();
449 mCurrentBucketStartTimeNs = nextBucketStartTimeNs;
450 }
451
452 // Rough estimate of CountMetricProducer buffer stored. This number will be
453 // greater than actual data size as it contains each dimension of
454 // CountMetricData is duplicated.
byteSizeLocked() const455 size_t CountMetricProducer::byteSizeLocked() const {
456 size_t totalSize = 0;
457 for (const auto& pair : mPastBuckets) {
458 totalSize += pair.second.size() * kBucketSize;
459 }
460 return totalSize;
461 }
462
463 } // namespace statsd
464 } // namespace os
465 } // namespace android
466