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