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 "ValueMetricProducer.h"
21
22 #include <kll.h>
23 #include <limits.h>
24 #include <stdlib.h>
25
26 #include "FieldValue.h"
27 #include "HashableDimensionKey.h"
28 #include "guardrail/StatsdStats.h"
29 #include "metrics/parsing_utils/metrics_manager_util.h"
30 #include "stats_log_util.h"
31 #include "stats_util.h"
32
33 using android::util::FIELD_COUNT_REPEATED;
34 using android::util::FIELD_TYPE_BOOL;
35 using android::util::FIELD_TYPE_INT32;
36 using android::util::FIELD_TYPE_INT64;
37 using android::util::FIELD_TYPE_MESSAGE;
38 using android::util::ProtoOutputStream;
39 using dist_proc::aggregation::KllQuantile;
40 using std::optional;
41 using std::shared_ptr;
42 using std::unique_ptr;
43 using std::unordered_map;
44 using std::vector;
45
46 namespace android {
47 namespace os {
48 namespace statsd {
49
50 // for StatsLogReport
51 const int FIELD_ID_ID = 1;
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 const int FIELD_ID_DIMENSION_GUARDRAIL_HIT = 17;
57 const int FIELD_ID_ESTIMATED_MEMORY_BYTES = 18;
58 // for *MetricDataWrapper
59 const int FIELD_ID_DATA = 1;
60 const int FIELD_ID_SKIPPED = 2;
61 // for SkippedBuckets
62 const int FIELD_ID_SKIPPED_START_MILLIS = 3;
63 const int FIELD_ID_SKIPPED_END_MILLIS = 4;
64 const int FIELD_ID_SKIPPED_DROP_EVENT = 5;
65 // for DumpEvent Proto
66 const int FIELD_ID_BUCKET_DROP_REASON = 1;
67 const int FIELD_ID_DROP_TIME = 2;
68 // for *MetricData
69 const int FIELD_ID_DIMENSION_IN_WHAT = 1;
70 const int FIELD_ID_BUCKET_INFO = 3;
71 const int FIELD_ID_DIMENSION_LEAF_IN_WHAT = 4;
72 const int FIELD_ID_SLICE_BY_STATE = 6;
73
74 template <typename AggregatedValue, typename DimExtras>
ValueMetricProducer(const int64_t metricId,const ConfigKey & key,const uint64_t protoHash,const PullOptions & pullOptions,const BucketOptions & bucketOptions,const WhatOptions & whatOptions,const ConditionOptions & conditionOptions,const StateOptions & stateOptions,const ActivationOptions & activationOptions,const GuardrailOptions & guardrailOptions,const wp<ConfigMetadataProvider> configMetadataProvider)75 ValueMetricProducer<AggregatedValue, DimExtras>::ValueMetricProducer(
76 const int64_t metricId, const ConfigKey& key, const uint64_t protoHash,
77 const PullOptions& pullOptions, const BucketOptions& bucketOptions,
78 const WhatOptions& whatOptions, const ConditionOptions& conditionOptions,
79 const StateOptions& stateOptions, const ActivationOptions& activationOptions,
80 const GuardrailOptions& guardrailOptions,
81 const wp<ConfigMetadataProvider> configMetadataProvider)
82 : MetricProducer(metricId, key, bucketOptions.timeBaseNs, conditionOptions.conditionIndex,
83 conditionOptions.initialConditionCache, conditionOptions.conditionWizard,
84 protoHash, activationOptions.eventActivationMap,
85 activationOptions.eventDeactivationMap, stateOptions.slicedStateAtoms,
86 stateOptions.stateGroupMap, bucketOptions.splitBucketForAppUpgrade,
87 configMetadataProvider),
88 mWhatMatcherIndex(whatOptions.whatMatcherIndex),
89 mEventMatcherWizard(whatOptions.matcherWizard),
90 mPullerManager(pullOptions.pullerManager),
91 mFieldMatchers(whatOptions.fieldMatchers),
92 mPullAtomId(pullOptions.pullAtomId),
93 mMinBucketSizeNs(bucketOptions.minBucketSizeNs),
94 mDimensionSoftLimit(guardrailOptions.dimensionSoftLimit),
95 mDimensionHardLimit(guardrailOptions.dimensionHardLimit),
96 mCurrentBucketIsSkipped(false),
97 mConditionCorrectionThresholdNs(bucketOptions.conditionCorrectionThresholdNs) {
98 // TODO(b/185722221): inject directly via initializer list in MetricProducer.
99 mBucketSizeNs = bucketOptions.bucketSizeNs;
100
101 // TODO(b/185770171): inject dimensionsInWhat related fields via constructor.
102 if (whatOptions.dimensionsInWhat.field() > 0) {
103 translateFieldMatcher(whatOptions.dimensionsInWhat, &mDimensionsInWhat);
104 }
105 mContainANYPositionInDimensionsInWhat = whatOptions.containsAnyPositionInDimensionsInWhat;
106 mShouldUseNestedDimensions = whatOptions.shouldUseNestedDimensions;
107
108 if (conditionOptions.conditionLinks.size() > 0) {
109 for (const auto& link : conditionOptions.conditionLinks) {
110 Metric2Condition mc;
111 mc.conditionId = link.condition();
112 translateFieldMatcher(link.fields_in_what(), &mc.metricFields);
113 translateFieldMatcher(link.fields_in_condition(), &mc.conditionFields);
114 mMetric2ConditionLinks.push_back(mc);
115 }
116
117 // TODO(b/185770739): use !mMetric2ConditionLinks.empty() instead
118 mConditionSliced = true;
119 }
120
121 for (const auto& stateLink : stateOptions.stateLinks) {
122 Metric2State ms;
123 ms.stateAtomId = stateLink.state_atom_id();
124 translateFieldMatcher(stateLink.fields_in_what(), &ms.metricFields);
125 translateFieldMatcher(stateLink.fields_in_state(), &ms.stateFields);
126 mMetric2StateLinks.push_back(ms);
127 }
128
129 const int64_t numBucketsForward = calcBucketsForwardCount(bucketOptions.startTimeNs);
130 mCurrentBucketNum = numBucketsForward;
131
132 flushIfNeededLocked(bucketOptions.startTimeNs);
133
134 if (isPulled()) {
135 mPullerManager->RegisterReceiver(mPullAtomId, mConfigKey, this, getCurrentBucketEndTimeNs(),
136 mBucketSizeNs);
137 }
138
139 // Only do this for partial buckets like first bucket. All other buckets should use
140 // flushIfNeeded to adjust start and end to bucket boundaries.
141 // Adjust start for partial bucket
142 mCurrentBucketStartTimeNs = bucketOptions.startTimeNs;
143 mConditionTimer.newBucketStart(mCurrentBucketStartTimeNs, mCurrentBucketStartTimeNs);
144
145 // Now that activations are processed, start the condition timer if needed.
146 mConditionTimer.onConditionChanged(mIsActive && mCondition == ConditionState::kTrue,
147 mCurrentBucketStartTimeNs);
148 }
149
150 template <typename AggregatedValue, typename DimExtras>
~ValueMetricProducer()151 ValueMetricProducer<AggregatedValue, DimExtras>::~ValueMetricProducer() {
152 VLOG("~ValueMetricProducer() called");
153 if (isPulled()) {
154 mPullerManager->UnRegisterReceiver(mPullAtomId, mConfigKey, this);
155 }
156 }
157
158 template <typename AggregatedValue, typename DimExtras>
onStatsdInitCompleted(const int64_t eventTimeNs)159 void ValueMetricProducer<AggregatedValue, DimExtras>::onStatsdInitCompleted(
160 const int64_t eventTimeNs) {
161 ATRACE_CALL();
162 lock_guard<mutex> lock(mMutex);
163
164 if (isPulled() && mCondition == ConditionState::kTrue && mIsActive) {
165 pullAndMatchEventsLocked(eventTimeNs);
166 }
167 flushCurrentBucketLocked(eventTimeNs, eventTimeNs);
168 }
169
170 template <typename AggregatedValue, typename DimExtras>
notifyAppUpgradeInternalLocked(const int64_t eventTimeNs)171 void ValueMetricProducer<AggregatedValue, DimExtras>::notifyAppUpgradeInternalLocked(
172 const int64_t eventTimeNs) {
173 if (isPulled() && mCondition == ConditionState::kTrue && mIsActive) {
174 pullAndMatchEventsLocked(eventTimeNs);
175 }
176 flushCurrentBucketLocked(eventTimeNs, eventTimeNs);
177 }
178
179 template <typename AggregatedValue, typename DimExtras>
180 optional<InvalidConfigReason>
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)181 ValueMetricProducer<AggregatedValue, DimExtras>::onConfigUpdatedLocked(
182 const StatsdConfig& config, const int configIndex, const int metricIndex,
183 const vector<sp<AtomMatchingTracker>>& allAtomMatchingTrackers,
184 const unordered_map<int64_t, int>& oldAtomMatchingTrackerMap,
185 const unordered_map<int64_t, int>& newAtomMatchingTrackerMap,
186 const sp<EventMatcherWizard>& matcherWizard,
187 const vector<sp<ConditionTracker>>& allConditionTrackers,
188 const unordered_map<int64_t, int>& conditionTrackerMap, const sp<ConditionWizard>& wizard,
189 const unordered_map<int64_t, int>& metricToActivationMap,
190 unordered_map<int, vector<int>>& trackerToMetricMap,
191 unordered_map<int, vector<int>>& conditionToMetricMap,
192 unordered_map<int, vector<int>>& activationAtomTrackerToMetricMap,
193 unordered_map<int, vector<int>>& deactivationAtomTrackerToMetricMap,
194 vector<int>& metricsWithActivation) {
195 optional<InvalidConfigReason> invalidConfigReason = MetricProducer::onConfigUpdatedLocked(
196 config, configIndex, metricIndex, allAtomMatchingTrackers, oldAtomMatchingTrackerMap,
197 newAtomMatchingTrackerMap, matcherWizard, allConditionTrackers, conditionTrackerMap,
198 wizard, metricToActivationMap, trackerToMetricMap, conditionToMetricMap,
199 activationAtomTrackerToMetricMap, deactivationAtomTrackerToMetricMap,
200 metricsWithActivation);
201 if (invalidConfigReason.has_value()) {
202 return invalidConfigReason;
203 }
204 // Update appropriate indices: mWhatMatcherIndex, mConditionIndex and MetricsManager maps.
205 const int64_t atomMatcherId = getWhatAtomMatcherIdForMetric(config, configIndex);
206 invalidConfigReason = handleMetricWithAtomMatchingTrackers(
207 atomMatcherId, mMetricId, metricIndex, /*enforceOneAtom=*/false,
208 allAtomMatchingTrackers, newAtomMatchingTrackerMap, trackerToMetricMap,
209 mWhatMatcherIndex);
210 if (invalidConfigReason.has_value()) {
211 return invalidConfigReason;
212 }
213 const optional<int64_t>& conditionIdOpt = getConditionIdForMetric(config, configIndex);
214 const ConditionLinks& conditionLinks = getConditionLinksForMetric(config, configIndex);
215 if (conditionIdOpt.has_value()) {
216 invalidConfigReason = handleMetricWithConditions(
217 conditionIdOpt.value(), mMetricId, metricIndex, conditionTrackerMap, conditionLinks,
218 allConditionTrackers, mConditionTrackerIndex, conditionToMetricMap);
219 if (invalidConfigReason.has_value()) {
220 return invalidConfigReason;
221 }
222 }
223 sp<EventMatcherWizard> tmpEventWizard = mEventMatcherWizard;
224 mEventMatcherWizard = matcherWizard;
225 return nullopt;
226 }
227
228 template <typename AggregatedValue, typename DimExtras>
computeValueBucketSizeLocked(const bool isFullBucket,const MetricDimensionKey & dimKey,const bool isFirstBucket,const PastBucket<AggregatedValue> & bucket) const229 size_t ValueMetricProducer<AggregatedValue, DimExtras>::computeValueBucketSizeLocked(
230 const bool isFullBucket, const MetricDimensionKey& dimKey, const bool isFirstBucket,
231 const PastBucket<AggregatedValue>& bucket) const {
232 size_t bucketSize =
233 MetricProducer::computeBucketSizeLocked(isFullBucket, dimKey, isFirstBucket);
234
235 for (const auto& value : bucket.aggregates) {
236 bucketSize += getAggregatedValueSize(value);
237 }
238
239 // ConditionTrueNanos
240 if (mConditionTrackerIndex >= 0 || !mSlicedStateAtoms.empty()) {
241 bucketSize += sizeof(int64_t);
242 }
243
244 // ConditionCorrectionNanos
245 if (getDumpProtoFields().conditionCorrectionNsFieldId.has_value() && isPulled() &&
246 mConditionCorrectionThresholdNs &&
247 (abs(bucket.mConditionCorrectionNs) >= mConditionCorrectionThresholdNs)) {
248 bucketSize += sizeof(int64_t);
249 }
250 return bucketSize;
251 }
252
253 template <typename AggregatedValue, typename DimExtras>
onStateChanged(int64_t eventTimeNs,int32_t atomId,const HashableDimensionKey & primaryKey,const FieldValue & oldState,const FieldValue & newState)254 void ValueMetricProducer<AggregatedValue, DimExtras>::onStateChanged(
255 int64_t eventTimeNs, int32_t atomId, const HashableDimensionKey& primaryKey,
256 const FieldValue& oldState, const FieldValue& newState) {
257 std::lock_guard<std::mutex> lock(mMutex);
258 VLOG("ValueMetricProducer %lld onStateChanged time %lld, State %d, key %s, %d -> %d",
259 (long long)mMetricId, (long long)eventTimeNs, atomId, primaryKey.toString().c_str(),
260 oldState.mValue.int_value, newState.mValue.int_value);
261
262 FieldValue oldStateCopy = oldState;
263 FieldValue newStateCopy = newState;
264 mapStateValue(atomId, &oldStateCopy);
265 mapStateValue(atomId, &newStateCopy);
266
267 // If old and new states are in the same StateGroup, then we do not need to
268 // pull for this state change.
269 if (oldStateCopy == newStateCopy) {
270 return;
271 }
272
273 // If condition is not true or metric is not active, we do not need to pull
274 // for this state change.
275 if (mCondition != ConditionState::kTrue || !mIsActive) {
276 return;
277 }
278
279 if (isEventLateLocked(eventTimeNs)) {
280 VLOG("Skip event due to late arrival: %lld vs %lld", (long long)eventTimeNs,
281 (long long)mCurrentBucketStartTimeNs);
282 invalidateCurrentBucket(eventTimeNs, BucketDropReason::EVENT_IN_WRONG_BUCKET);
283 return;
284 }
285
286 if (isPulled()) {
287 mStateChangePrimaryKey.first = atomId;
288 mStateChangePrimaryKey.second = primaryKey;
289 // TODO(b/185796114): pass mStateChangePrimaryKey as an argument to
290 // pullAndMatchEventsLocked
291 pullAndMatchEventsLocked(eventTimeNs);
292 mStateChangePrimaryKey.first = 0;
293 mStateChangePrimaryKey.second = DEFAULT_DIMENSION_KEY;
294 }
295 flushIfNeededLocked(eventTimeNs);
296 }
297
298 template <typename AggregatedValue, typename DimExtras>
onSlicedConditionMayChangeLocked(bool overallCondition,const int64_t eventTime)299 void ValueMetricProducer<AggregatedValue, DimExtras>::onSlicedConditionMayChangeLocked(
300 bool overallCondition, const int64_t eventTime) {
301 VLOG("Metric %lld onSlicedConditionMayChange", (long long)mMetricId);
302 }
303
304 template <typename AggregatedValue, typename DimExtras>
dropDataLocked(const int64_t dropTimeNs)305 void ValueMetricProducer<AggregatedValue, DimExtras>::dropDataLocked(const int64_t dropTimeNs) {
306 StatsdStats::getInstance().noteBucketDropped(mMetricId);
307
308 // The current partial bucket is not flushed and does not require a pull,
309 // so the data is still valid.
310 flushIfNeededLocked(dropTimeNs);
311 clearPastBucketsLocked(dropTimeNs);
312 }
313
314 template <typename AggregatedValue, typename DimExtras>
clearPastBucketsLocked(const int64_t dumpTimeNs)315 void ValueMetricProducer<AggregatedValue, DimExtras>::clearPastBucketsLocked(
316 const int64_t dumpTimeNs) {
317 mPastBuckets.clear();
318 mSkippedBuckets.clear();
319 mTotalDataSize = 0;
320 }
321
322 template <typename AggregatedValue, typename DimExtras>
onDumpReportLocked(const int64_t dumpTimeNs,const bool includeCurrentPartialBucket,const bool eraseData,const DumpLatency dumpLatency,set<string> * strSet,ProtoOutputStream * protoOutput)323 void ValueMetricProducer<AggregatedValue, DimExtras>::onDumpReportLocked(
324 const int64_t dumpTimeNs, const bool includeCurrentPartialBucket, const bool eraseData,
325 const DumpLatency dumpLatency, set<string>* strSet, ProtoOutputStream* protoOutput) {
326 VLOG("metric %lld dump report now...", (long long)mMetricId);
327
328 // Pulled metrics need to pull before flushing, which is why they do not call flushIfNeeded.
329 // TODO: b/249823426 see if we can pull and call flushIfneeded for pulled value metrics.
330 if (!isPulled()) {
331 flushIfNeededLocked(dumpTimeNs);
332 }
333 if (includeCurrentPartialBucket) {
334 // For pull metrics, we need to do a pull at bucket boundaries. If we do not do that the
335 // current bucket will have incomplete data and the next will have the wrong snapshot to do
336 // a diff against. If the condition is false, we are fine since the base data is reset and
337 // we are not tracking anything.
338 if (isPulled() && mCondition == ConditionState::kTrue && mIsActive) {
339 switch (dumpLatency) {
340 case FAST:
341 invalidateCurrentBucket(dumpTimeNs, BucketDropReason::DUMP_REPORT_REQUESTED);
342 break;
343 case NO_TIME_CONSTRAINTS:
344 pullAndMatchEventsLocked(dumpTimeNs);
345 break;
346 }
347 }
348 flushCurrentBucketLocked(dumpTimeNs, dumpTimeNs);
349 }
350
351 protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_ID, (long long)mMetricId);
352 protoOutput->write(FIELD_TYPE_BOOL | FIELD_ID_IS_ACTIVE, isActiveLocked());
353 if (mPastBuckets.empty() && mSkippedBuckets.empty()) {
354 return;
355 }
356
357 protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_ESTIMATED_MEMORY_BYTES,
358 (long long)byteSizeLocked());
359
360 if (StatsdStats::getInstance().hasHitDimensionGuardrail(mMetricId)) {
361 protoOutput->write(FIELD_TYPE_BOOL | FIELD_ID_DIMENSION_GUARDRAIL_HIT, true);
362 }
363 protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_TIME_BASE, (long long)mTimeBaseNs);
364 protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_BUCKET_SIZE, (long long)mBucketSizeNs);
365 // Fills the dimension path if not slicing by a primitive repeated field or position ALL.
366 if (!mShouldUseNestedDimensions) {
367 if (!mDimensionsInWhat.empty()) {
368 uint64_t dimenPathToken =
369 protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_ID_DIMENSION_PATH_IN_WHAT);
370 writeDimensionPathToProto(mDimensionsInWhat, protoOutput);
371 protoOutput->end(dimenPathToken);
372 }
373 }
374
375 const auto& [metricTypeFieldId, bucketNumFieldId, startBucketMsFieldId, endBucketMsFieldId,
376 conditionTrueNsFieldId,
377 conditionCorrectionNsFieldId] = getDumpProtoFields();
378
379 uint64_t protoToken = protoOutput->start(FIELD_TYPE_MESSAGE | metricTypeFieldId);
380
381 for (const auto& skippedBucket : mSkippedBuckets) {
382 uint64_t wrapperToken =
383 protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_SKIPPED);
384 protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_SKIPPED_START_MILLIS,
385 (long long)(NanoToMillis(skippedBucket.bucketStartTimeNs)));
386 protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_SKIPPED_END_MILLIS,
387 (long long)(NanoToMillis(skippedBucket.bucketEndTimeNs)));
388 for (const auto& dropEvent : skippedBucket.dropEvents) {
389 uint64_t dropEventToken = protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED |
390 FIELD_ID_SKIPPED_DROP_EVENT);
391 protoOutput->write(FIELD_TYPE_INT32 | FIELD_ID_BUCKET_DROP_REASON, dropEvent.reason);
392 protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_DROP_TIME,
393 (long long)(NanoToMillis(dropEvent.dropTimeNs)));
394 protoOutput->end(dropEventToken);
395 }
396 protoOutput->end(wrapperToken);
397 }
398
399 for (const auto& [metricDimensionKey, buckets] : mPastBuckets) {
400 VLOG(" dimension key %s", metricDimensionKey.toString().c_str());
401 uint64_t wrapperToken =
402 protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_DATA);
403
404 // First fill dimension.
405 if (mShouldUseNestedDimensions) {
406 uint64_t dimensionToken =
407 protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_ID_DIMENSION_IN_WHAT);
408 writeDimensionToProto(metricDimensionKey.getDimensionKeyInWhat(), strSet, protoOutput);
409 protoOutput->end(dimensionToken);
410 } else {
411 writeDimensionLeafNodesToProto(metricDimensionKey.getDimensionKeyInWhat(),
412 FIELD_ID_DIMENSION_LEAF_IN_WHAT, strSet, protoOutput);
413 }
414
415 // Then fill slice_by_state.
416 for (auto state : metricDimensionKey.getStateValuesKey().getValues()) {
417 uint64_t stateToken = protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED |
418 FIELD_ID_SLICE_BY_STATE);
419 writeStateToProto(state, protoOutput);
420 protoOutput->end(stateToken);
421 }
422
423 // Then fill bucket_info (*BucketInfo).
424 for (const auto& bucket : buckets) {
425 uint64_t bucketInfoToken = protoOutput->start(
426 FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_BUCKET_INFO);
427
428 if (bucket.mBucketEndNs - bucket.mBucketStartNs != mBucketSizeNs) {
429 protoOutput->write(FIELD_TYPE_INT64 | startBucketMsFieldId,
430 (long long)NanoToMillis(bucket.mBucketStartNs));
431 protoOutput->write(FIELD_TYPE_INT64 | endBucketMsFieldId,
432 (long long)NanoToMillis(bucket.mBucketEndNs));
433 } else {
434 protoOutput->write(FIELD_TYPE_INT64 | bucketNumFieldId,
435 (long long)(getBucketNumFromEndTimeNs(bucket.mBucketEndNs)));
436 }
437 // We only write the condition timer value if the metric has a
438 // condition and/or is sliced by state.
439 // If the metric is sliced by state, the condition timer value is
440 // also sliced by state to reflect time spent in that state.
441 if (mConditionTrackerIndex >= 0 || !mSlicedStateAtoms.empty()) {
442 protoOutput->write(FIELD_TYPE_INT64 | conditionTrueNsFieldId,
443 (long long)bucket.mConditionTrueNs);
444 }
445
446 if (conditionCorrectionNsFieldId) {
447 // We write the condition correction value when below conditions are true:
448 // - if metric is pulled
449 // - if it is enabled by metric configuration via dedicated field,
450 // see condition_correction_threshold_nanos
451 // - if the abs(value) >= condition_correction_threshold_nanos
452
453 if (isPulled() && mConditionCorrectionThresholdNs &&
454 (abs(bucket.mConditionCorrectionNs) >= mConditionCorrectionThresholdNs)) {
455 protoOutput->write(FIELD_TYPE_INT64 | conditionCorrectionNsFieldId.value(),
456 (long long)bucket.mConditionCorrectionNs);
457 }
458 }
459
460 for (int i = 0; i < (int)bucket.aggIndex.size(); i++) {
461 VLOG("\t bucket [%lld - %lld]", (long long)bucket.mBucketStartNs,
462 (long long)bucket.mBucketEndNs);
463 int sampleSize = !bucket.sampleSizes.empty() ? bucket.sampleSizes[i] : 0;
464 writePastBucketAggregateToProto(bucket.aggIndex[i], bucket.aggregates[i],
465 sampleSize, protoOutput);
466 }
467 protoOutput->end(bucketInfoToken);
468 }
469 protoOutput->end(wrapperToken);
470 }
471 protoOutput->end(protoToken);
472
473 VLOG("metric %lld done with dump report...", (long long)mMetricId);
474 if (eraseData) {
475 mPastBuckets.clear();
476 mSkippedBuckets.clear();
477 mTotalDataSize = 0;
478 }
479 }
480
481 template <typename AggregatedValue, typename DimExtras>
invalidateCurrentBucket(const int64_t dropTimeNs,const BucketDropReason reason)482 void ValueMetricProducer<AggregatedValue, DimExtras>::invalidateCurrentBucket(
483 const int64_t dropTimeNs, const BucketDropReason reason) {
484 if (!mCurrentBucketIsSkipped) {
485 // Only report to StatsdStats once per invalid bucket.
486 StatsdStats::getInstance().noteInvalidatedBucket(mMetricId);
487 }
488
489 skipCurrentBucket(dropTimeNs, reason);
490 }
491
492 template <typename AggregatedValue, typename DimExtras>
skipCurrentBucket(const int64_t dropTimeNs,const BucketDropReason reason)493 void ValueMetricProducer<AggregatedValue, DimExtras>::skipCurrentBucket(
494 const int64_t dropTimeNs, const BucketDropReason reason) {
495 if (!mIsActive) {
496 // Don't keep track of skipped buckets if metric is not active.
497 return;
498 }
499
500 if (!maxDropEventsReached()) {
501 mCurrentSkippedBucket.dropEvents.push_back(buildDropEvent(dropTimeNs, reason));
502 }
503 mCurrentBucketIsSkipped = true;
504 }
505
506 // Handle active state change. Active state change is *mostly* treated like a condition change:
507 // - drop bucket if active state change event arrives too late
508 // - if condition is true, pull data on active state changes
509 // - ConditionTimer tracks changes based on AND of condition and active state.
510 template <typename AggregatedValue, typename DimExtras>
onActiveStateChangedLocked(const int64_t eventTimeNs,const bool isActive)511 void ValueMetricProducer<AggregatedValue, DimExtras>::onActiveStateChangedLocked(
512 const int64_t eventTimeNs, const bool isActive) {
513 const bool eventLate = isEventLateLocked(eventTimeNs);
514 if (eventLate) {
515 // Drop bucket because event arrived too late, ie. we are missing data for this bucket.
516 StatsdStats::getInstance().noteLateLogEventSkipped(mMetricId);
517 invalidateCurrentBucket(eventTimeNs, BucketDropReason::EVENT_IN_WRONG_BUCKET);
518 }
519
520 if (ConditionState::kTrue != mCondition) {
521 // Call parent method before early return.
522 MetricProducer::onActiveStateChangedLocked(eventTimeNs, isActive);
523 return;
524 }
525
526 // Pull on active state changes.
527 if (!eventLate) {
528 if (isPulled()) {
529 pullAndMatchEventsLocked(eventTimeNs);
530 }
531
532 onActiveStateChangedInternalLocked(eventTimeNs, isActive);
533 }
534
535 // Once any pulls are processed, call through to parent method which might flush the current
536 // bucket.
537 MetricProducer::onActiveStateChangedLocked(eventTimeNs, isActive);
538
539 // Let condition timer know of new active state.
540 mConditionTimer.onConditionChanged(isActive, eventTimeNs);
541
542 updateCurrentSlicedBucketConditionTimers(isActive, eventTimeNs);
543 }
544
545 template <typename AggregatedValue, typename DimExtras>
onConditionChangedLocked(const bool condition,const int64_t eventTimeNs)546 void ValueMetricProducer<AggregatedValue, DimExtras>::onConditionChangedLocked(
547 const bool condition, const int64_t eventTimeNs) {
548 const ConditionState newCondition = condition ? ConditionState::kTrue : ConditionState::kFalse;
549 const ConditionState oldCondition = mCondition;
550
551 if (!mIsActive) {
552 mCondition = newCondition;
553 return;
554 }
555
556 // If the event arrived late, mark the bucket as invalid and skip the event.
557 if (isEventLateLocked(eventTimeNs)) {
558 VLOG("Skip event due to late arrival: %lld vs %lld", (long long)eventTimeNs,
559 (long long)mCurrentBucketStartTimeNs);
560 StatsdStats::getInstance().noteLateLogEventSkipped(mMetricId);
561 StatsdStats::getInstance().noteConditionChangeInNextBucket(mMetricId);
562 invalidateCurrentBucket(eventTimeNs, BucketDropReason::EVENT_IN_WRONG_BUCKET);
563 mCondition = newCondition;
564 mConditionTimer.onConditionChanged(newCondition, eventTimeNs);
565 updateCurrentSlicedBucketConditionTimers(newCondition, eventTimeNs);
566 return;
567 }
568
569 // If the previous condition was unknown, mark the bucket as invalid
570 // because the bucket will contain partial data. For example, the condition
571 // change might happen close to the end of the bucket and we might miss a
572 // lot of data.
573 // We still want to pull to set the base for diffed metrics.
574 if (oldCondition == ConditionState::kUnknown) {
575 invalidateCurrentBucket(eventTimeNs, BucketDropReason::CONDITION_UNKNOWN);
576 }
577
578 // Pull and match for the following condition change cases:
579 // unknown/false -> true - condition changed
580 // true -> false - condition changed
581 // true -> true - old condition was true so we can flush the bucket at the
582 // end if needed.
583 //
584 // We don’t need to pull for unknown -> false or false -> false.
585 //
586 // onConditionChangedLocked might happen on bucket boundaries if this is
587 // called before #onDataPulled.
588 if (isPulled() &&
589 (newCondition == ConditionState::kTrue || oldCondition == ConditionState::kTrue)) {
590 pullAndMatchEventsLocked(eventTimeNs);
591 }
592
593 onConditionChangedInternalLocked(oldCondition, newCondition, eventTimeNs);
594
595 // Update condition state after pulling.
596 mCondition = newCondition;
597
598 flushIfNeededLocked(eventTimeNs);
599
600 mConditionTimer.onConditionChanged(newCondition, eventTimeNs);
601 updateCurrentSlicedBucketConditionTimers(newCondition, eventTimeNs);
602 }
603
604 template <typename AggregatedValue, typename DimExtras>
updateCurrentSlicedBucketConditionTimers(bool newCondition,int64_t eventTimeNs)605 void ValueMetricProducer<AggregatedValue, DimExtras>::updateCurrentSlicedBucketConditionTimers(
606 bool newCondition, int64_t eventTimeNs) {
607 if (mSlicedStateAtoms.empty()) {
608 return;
609 }
610
611 // Utilize the current state key of each DimensionsInWhat key to determine
612 // which condition timers to update.
613 //
614 // Assumes that the MetricDimensionKey exists in `mCurrentSlicedBucket`.
615 for (const auto& [dimensionInWhatKey, dimensionInWhatInfo] : mDimInfos) {
616 // If the new condition is true, turn ON the condition timer only if
617 // the DimensionInWhat key was present in the data.
618 mCurrentSlicedBucket[MetricDimensionKey(dimensionInWhatKey,
619 dimensionInWhatInfo.currentState)]
620 .conditionTimer.onConditionChanged(
621 newCondition && dimensionInWhatInfo.hasCurrentState, eventTimeNs);
622 }
623 }
624
625 template <typename AggregatedValue, typename DimExtras>
dumpStatesLocked(int out,bool verbose) const626 void ValueMetricProducer<AggregatedValue, DimExtras>::dumpStatesLocked(int out,
627 bool verbose) const {
628 if (mCurrentSlicedBucket.size() == 0) {
629 return;
630 }
631
632 dprintf(out, "ValueMetricProducer %lld dimension size %lu\n", (long long)mMetricId,
633 (unsigned long)mCurrentSlicedBucket.size());
634 if (verbose) {
635 for (const auto& [metricDimensionKey, currentBucket] : mCurrentSlicedBucket) {
636 for (const Interval& interval : currentBucket.intervals) {
637 dprintf(out, "\t(what)%s\t(states)%s (aggregate)%s\n",
638 metricDimensionKey.getDimensionKeyInWhat().toString().c_str(),
639 metricDimensionKey.getStateValuesKey().toString().c_str(),
640 aggregatedValueToString(interval.aggregate).c_str());
641 }
642 }
643 }
644 }
645
646 template <typename AggregatedValue, typename DimExtras>
hasReachedGuardRailLimit() const647 bool ValueMetricProducer<AggregatedValue, DimExtras>::hasReachedGuardRailLimit() const {
648 return mCurrentSlicedBucket.size() >= mDimensionHardLimit;
649 }
650
651 template <typename AggregatedValue, typename DimExtras>
hitGuardRailLocked(const MetricDimensionKey & newKey) const652 bool ValueMetricProducer<AggregatedValue, DimExtras>::hitGuardRailLocked(
653 const MetricDimensionKey& newKey) const {
654 // ===========GuardRail==============
655 // 1. Report the tuple count if the tuple count > soft limit
656 if (mCurrentSlicedBucket.find(newKey) != mCurrentSlicedBucket.end()) {
657 return false;
658 }
659 if (mCurrentSlicedBucket.size() > mDimensionSoftLimit - 1) {
660 size_t newTupleCount = mCurrentSlicedBucket.size() + 1;
661 StatsdStats::getInstance().noteMetricDimensionSize(mConfigKey, mMetricId, newTupleCount);
662 // 2. Don't add more tuples, we are above the allowed threshold. Drop the data.
663 if (hasReachedGuardRailLimit()) {
664 if (!mHasHitGuardrail) {
665 ALOGE("ValueMetricProducer %lld dropping data for dimension key %s",
666 (long long)mMetricId, newKey.toString().c_str());
667 mHasHitGuardrail = true;
668 }
669 StatsdStats::getInstance().noteHardDimensionLimitReached(mMetricId);
670 return true;
671 }
672 }
673
674 return false;
675 }
676
677 template <typename AggregatedValue, typename DimExtras>
onMatchedLogEventInternalLocked(const size_t matcherIndex,const MetricDimensionKey & eventKey,const ConditionKey & conditionKey,bool condition,const LogEvent & event,const map<int,HashableDimensionKey> & statePrimaryKeys)678 void ValueMetricProducer<AggregatedValue, DimExtras>::onMatchedLogEventInternalLocked(
679 const size_t matcherIndex, const MetricDimensionKey& eventKey,
680 const ConditionKey& conditionKey, bool condition, const LogEvent& event,
681 const map<int, HashableDimensionKey>& statePrimaryKeys) {
682 // Skip this event if a state change occurred for a different primary key.
683 auto it = statePrimaryKeys.find(mStateChangePrimaryKey.first);
684 // Check that both the atom id and the primary key are equal.
685 if (it != statePrimaryKeys.end() && it->second != mStateChangePrimaryKey.second) {
686 VLOG("ValueMetric skip event with primary key %s because state change primary key "
687 "is %s",
688 it->second.toString().c_str(), mStateChangePrimaryKey.second.toString().c_str());
689 return;
690 }
691
692 const int64_t eventTimeNs = event.GetElapsedTimestampNs();
693 if (isEventLateLocked(eventTimeNs)) {
694 VLOG("Skip event due to late arrival: %lld vs %lld", (long long)eventTimeNs,
695 (long long)mCurrentBucketStartTimeNs);
696 return;
697 }
698
699 const auto whatKey = eventKey.getDimensionKeyInWhat();
700 mMatchedMetricDimensionKeys.insert(whatKey);
701
702 if (!isPulled()) {
703 // Only flushing for pushed because for pulled metrics, we need to do a pull first.
704 flushIfNeededLocked(eventTimeNs);
705 }
706
707 if (canSkipLogEventLocked(eventKey, condition, eventTimeNs, statePrimaryKeys)) {
708 return;
709 }
710
711 if (hitGuardRailLocked(eventKey)) {
712 return;
713 }
714
715 const auto& returnVal = mDimInfos.emplace(whatKey, DimensionsInWhatInfo(getUnknownStateKey()));
716 DimensionsInWhatInfo& dimensionsInWhatInfo = returnVal.first->second;
717 const HashableDimensionKey& oldStateKey = dimensionsInWhatInfo.currentState;
718 CurrentBucket& currentBucket = mCurrentSlicedBucket[MetricDimensionKey(whatKey, oldStateKey)];
719
720 // Ensure we turn on the condition timer in the case where dimensions
721 // were missing on a previous pull due to a state change.
722 const auto stateKey = eventKey.getStateValuesKey();
723 const bool stateChange = oldStateKey != stateKey || !dimensionsInWhatInfo.hasCurrentState;
724
725 // We need to get the intervals stored with the previous state key so we can
726 // close these value intervals.
727 vector<Interval>& intervals = currentBucket.intervals;
728 if (intervals.size() < mFieldMatchers.size()) {
729 VLOG("Resizing number of intervals to %d", (int)mFieldMatchers.size());
730 intervals.resize(mFieldMatchers.size());
731 }
732
733 dimensionsInWhatInfo.hasCurrentState = true;
734 dimensionsInWhatInfo.currentState = stateKey;
735
736 dimensionsInWhatInfo.seenNewData |= aggregateFields(eventTimeNs, eventKey, event, intervals,
737 dimensionsInWhatInfo.dimExtras);
738
739 // State change.
740 if (!mSlicedStateAtoms.empty() && stateChange) {
741 // Turn OFF the condition timer for the previous state key.
742 currentBucket.conditionTimer.onConditionChanged(false, eventTimeNs);
743
744 // Turn ON the condition timer for the new state key.
745 mCurrentSlicedBucket[MetricDimensionKey(whatKey, stateKey)]
746 .conditionTimer.onConditionChanged(true, eventTimeNs);
747 }
748 }
749
750 // For pulled metrics, we always need to make sure we do a pull before flushing the bucket
751 // if mCondition and mIsActive are true!
752 template <typename AggregatedValue, typename DimExtras>
flushIfNeededLocked(const int64_t eventTimeNs)753 void ValueMetricProducer<AggregatedValue, DimExtras>::flushIfNeededLocked(
754 const int64_t eventTimeNs) {
755 const int64_t currentBucketEndTimeNs = getCurrentBucketEndTimeNs();
756 if (eventTimeNs < currentBucketEndTimeNs) {
757 VLOG("eventTime is %lld, less than current bucket end time %lld", (long long)eventTimeNs,
758 (long long)(currentBucketEndTimeNs));
759 return;
760 }
761 int64_t numBucketsForward = calcBucketsForwardCount(eventTimeNs);
762 int64_t nextBucketStartTimeNs =
763 currentBucketEndTimeNs + (numBucketsForward - 1) * mBucketSizeNs;
764 flushCurrentBucketLocked(eventTimeNs, nextBucketStartTimeNs);
765 }
766
767 template <typename AggregatedValue, typename DimExtras>
calcBucketsForwardCount(const int64_t eventTimeNs) const768 int64_t ValueMetricProducer<AggregatedValue, DimExtras>::calcBucketsForwardCount(
769 const int64_t eventTimeNs) const {
770 int64_t currentBucketEndTimeNs = getCurrentBucketEndTimeNs();
771 if (eventTimeNs < currentBucketEndTimeNs) {
772 return 0;
773 }
774 return 1 + (eventTimeNs - currentBucketEndTimeNs) / mBucketSizeNs;
775 }
776
777 template <typename AggregatedValue, typename DimExtras>
flushCurrentBucketLocked(const int64_t eventTimeNs,const int64_t nextBucketStartTimeNs)778 void ValueMetricProducer<AggregatedValue, DimExtras>::flushCurrentBucketLocked(
779 const int64_t eventTimeNs, const int64_t nextBucketStartTimeNs) {
780 if (mCondition == ConditionState::kUnknown) {
781 StatsdStats::getInstance().noteBucketUnknownCondition(mMetricId);
782 invalidateCurrentBucket(eventTimeNs, BucketDropReason::CONDITION_UNKNOWN);
783 }
784
785 VLOG("finalizing bucket for %ld, dumping %d slices", (long)mCurrentBucketStartTimeNs,
786 (int)mCurrentSlicedBucket.size());
787
788 closeCurrentBucket(eventTimeNs, nextBucketStartTimeNs);
789 initNextSlicedBucket(nextBucketStartTimeNs);
790
791 // Update the condition timer again, in case we skipped buckets.
792 mConditionTimer.newBucketStart(eventTimeNs, nextBucketStartTimeNs);
793
794 // NOTE: Update the condition timers in `mCurrentSlicedBucket` only when slicing
795 // by state. Otherwise, the "global" condition timer will be used.
796 if (!mSlicedStateAtoms.empty()) {
797 for (auto& [metricDimensionKey, currentBucket] : mCurrentSlicedBucket) {
798 currentBucket.conditionTimer.newBucketStart(eventTimeNs, nextBucketStartTimeNs);
799 }
800 }
801 mCurrentBucketNum += calcBucketsForwardCount(eventTimeNs);
802 }
803
804 template <typename AggregatedValue, typename DimExtras>
closeCurrentBucket(const int64_t eventTimeNs,const int64_t nextBucketStartTimeNs)805 void ValueMetricProducer<AggregatedValue, DimExtras>::closeCurrentBucket(
806 const int64_t eventTimeNs, const int64_t nextBucketStartTimeNs) {
807 const int64_t fullBucketEndTimeNs = getCurrentBucketEndTimeNs();
808 int64_t bucketEndTimeNs = fullBucketEndTimeNs;
809 int64_t numBucketsForward = calcBucketsForwardCount(eventTimeNs);
810
811 if (multipleBucketsSkipped(numBucketsForward)) {
812 VLOG("Skipping forward %lld buckets", (long long)numBucketsForward);
813 StatsdStats::getInstance().noteSkippedForwardBuckets(mMetricId);
814 // Something went wrong. Maybe the device was sleeping for a long time. It is better
815 // to mark the current bucket as invalid. The last pull might have been successful though.
816 invalidateCurrentBucket(eventTimeNs, BucketDropReason::MULTIPLE_BUCKETS_SKIPPED);
817
818 // End the bucket at the next bucket start time so the entire interval is skipped.
819 bucketEndTimeNs = nextBucketStartTimeNs;
820 } else if (eventTimeNs < fullBucketEndTimeNs) {
821 bucketEndTimeNs = eventTimeNs;
822 }
823
824 // Close the current bucket
825 const auto [globalConditionDurationNs, globalConditionCorrectionNs] =
826 mConditionTimer.newBucketStart(eventTimeNs, bucketEndTimeNs);
827
828 bool isBucketLargeEnough = bucketEndTimeNs - mCurrentBucketStartTimeNs >= mMinBucketSizeNs;
829 if (!isBucketLargeEnough) {
830 skipCurrentBucket(eventTimeNs, BucketDropReason::BUCKET_TOO_SMALL);
831 }
832 if (!mCurrentBucketIsSkipped) {
833 bool bucketHasData = false;
834 // The current bucket is large enough to keep.
835 for (auto& [metricDimensionKey, currentBucket] : mCurrentSlicedBucket) {
836 PastBucket<AggregatedValue> bucket =
837 buildPartialBucket(bucketEndTimeNs, currentBucket.intervals);
838 if (bucket.aggIndex.empty()) {
839 continue;
840 }
841 bucketHasData = true;
842 if (!mSlicedStateAtoms.empty()) {
843 const auto [conditionDurationNs, conditionCorrectionNs] =
844 currentBucket.conditionTimer.newBucketStart(eventTimeNs, bucketEndTimeNs);
845 bucket.mConditionTrueNs = conditionDurationNs;
846 bucket.mConditionCorrectionNs = conditionCorrectionNs;
847 } else {
848 bucket.mConditionTrueNs = globalConditionDurationNs;
849 bucket.mConditionCorrectionNs = globalConditionCorrectionNs;
850 }
851
852 auto& bucketList = mPastBuckets[metricDimensionKey];
853 const bool isFirstBucket = bucketList.empty();
854 mTotalDataSize += computeValueBucketSizeLocked(
855 eventTimeNs >= fullBucketEndTimeNs, metricDimensionKey, isFirstBucket, bucket);
856 bucketList.push_back(std::move(bucket));
857 }
858 if (!bucketHasData) {
859 skipCurrentBucket(eventTimeNs, BucketDropReason::NO_DATA);
860 }
861 }
862
863 if (mCurrentBucketIsSkipped) {
864 mCurrentSkippedBucket.bucketStartTimeNs = mCurrentBucketStartTimeNs;
865 mCurrentSkippedBucket.bucketEndTimeNs = bucketEndTimeNs;
866 mSkippedBuckets.push_back(mCurrentSkippedBucket);
867 mTotalDataSize += computeSkippedBucketSizeLocked(mCurrentSkippedBucket);
868 }
869
870 // This means that the current bucket was not flushed before a forced bucket split.
871 // This can happen if an app update or a dump report with includeCurrentPartialBucket is
872 // requested before we get a chance to flush the bucket due to receiving new data, either from
873 // the statsd socket or the StatsPullerManager.
874 if (bucketEndTimeNs < nextBucketStartTimeNs) {
875 SkippedBucket bucketInGap;
876 bucketInGap.bucketStartTimeNs = bucketEndTimeNs;
877 bucketInGap.bucketEndTimeNs = nextBucketStartTimeNs;
878 bucketInGap.dropEvents.emplace_back(buildDropEvent(eventTimeNs, BucketDropReason::NO_DATA));
879 mSkippedBuckets.emplace_back(bucketInGap);
880 }
881 }
882
883 template <typename AggregatedValue, typename DimExtras>
initNextSlicedBucket(int64_t nextBucketStartTimeNs)884 void ValueMetricProducer<AggregatedValue, DimExtras>::initNextSlicedBucket(
885 int64_t nextBucketStartTimeNs) {
886 StatsdStats::getInstance().noteBucketCount(mMetricId);
887 if (mSlicedStateAtoms.empty()) {
888 mCurrentSlicedBucket.clear();
889 } else {
890 for (auto it = mCurrentSlicedBucket.begin(); it != mCurrentSlicedBucket.end();) {
891 bool obsolete = true;
892 for (auto& interval : it->second.intervals) {
893 interval.sampleSize = 0;
894 }
895
896 // When slicing by state, only delete the MetricDimensionKey when the
897 // state key in the MetricDimensionKey is not the current state key.
898 const HashableDimensionKey& dimensionInWhatKey = it->first.getDimensionKeyInWhat();
899 const auto& currentDimInfoItr = mDimInfos.find(dimensionInWhatKey);
900
901 if ((currentDimInfoItr != mDimInfos.end()) &&
902 (it->first.getStateValuesKey() == currentDimInfoItr->second.currentState)) {
903 obsolete = false;
904 }
905 if (obsolete) {
906 it = mCurrentSlicedBucket.erase(it);
907 } else {
908 it++;
909 }
910 }
911 }
912 for (auto it = mDimInfos.begin(); it != mDimInfos.end();) {
913 if (!it->second.seenNewData) {
914 it = mDimInfos.erase(it);
915 } else {
916 it->second.seenNewData = false;
917 it++;
918 }
919 }
920
921 mCurrentBucketIsSkipped = false;
922 mCurrentSkippedBucket.reset();
923
924 mCurrentBucketStartTimeNs = nextBucketStartTimeNs;
925 // Reset mHasHitGuardrail boolean since bucket was reset
926 mHasHitGuardrail = false;
927 VLOG("metric %lld: new bucket start time: %lld", (long long)mMetricId,
928 (long long)mCurrentBucketStartTimeNs);
929 }
930
931 // Explicit template instantiations
932 template class ValueMetricProducer<Value, vector<optional<Value>>>;
933 template class ValueMetricProducer<unique_ptr<KllQuantile>, Empty>;
934
935 } // namespace statsd
936 } // namespace os
937 } // namespace android
938