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 "MetricProducer.h"
21 
22 #include "../guardrail/StatsdStats.h"
23 #include "metrics/parsing_utils/metrics_manager_util.h"
24 #include "state/StateTracker.h"
25 
26 using android::util::FIELD_COUNT_REPEATED;
27 using android::util::FIELD_TYPE_ENUM;
28 using android::util::FIELD_TYPE_INT32;
29 using android::util::FIELD_TYPE_INT64;
30 using android::util::FIELD_TYPE_MESSAGE;
31 using android::util::ProtoOutputStream;
32 
33 namespace android {
34 namespace os {
35 namespace statsd {
36 
37 // for ActiveMetric
38 const int FIELD_ID_ACTIVE_METRIC_ID = 1;
39 const int FIELD_ID_ACTIVE_METRIC_ACTIVATION = 2;
40 
41 // for ActiveEventActivation
42 const int FIELD_ID_ACTIVE_EVENT_ACTIVATION_ATOM_MATCHER_INDEX = 1;
43 const int FIELD_ID_ACTIVE_EVENT_ACTIVATION_REMAINING_TTL_NANOS = 2;
44 const int FIELD_ID_ACTIVE_EVENT_ACTIVATION_STATE = 3;
45 
MetricProducer(int64_t metricId,const ConfigKey & key,const int64_t timeBaseNs,const int conditionIndex,const vector<ConditionState> & initialConditionCache,const sp<ConditionWizard> & wizard,const uint64_t protoHash,const std::unordered_map<int,std::shared_ptr<Activation>> & eventActivationMap,const std::unordered_map<int,std::vector<std::shared_ptr<Activation>>> & eventDeactivationMap,const vector<int> & slicedStateAtoms,const unordered_map<int,unordered_map<int,int64_t>> & stateGroupMap,const optional<bool> splitBucketForAppUpgrade,const wp<ConfigMetadataProvider> configMetadataProvider)46 MetricProducer::MetricProducer(
47         int64_t metricId, const ConfigKey& key, const int64_t timeBaseNs, const int conditionIndex,
48         const vector<ConditionState>& initialConditionCache, const sp<ConditionWizard>& wizard,
49         const uint64_t protoHash,
50         const std::unordered_map<int, std::shared_ptr<Activation>>& eventActivationMap,
51         const std::unordered_map<int, std::vector<std::shared_ptr<Activation>>>&
52                 eventDeactivationMap,
53         const vector<int>& slicedStateAtoms,
54         const unordered_map<int, unordered_map<int, int64_t>>& stateGroupMap,
55         const optional<bool> splitBucketForAppUpgrade,
56         const wp<ConfigMetadataProvider> configMetadataProvider)
57     : mMetricId(metricId),
58       mProtoHash(protoHash),
59       mConfigKey(key),
60       mValid(true),
61       mTimeBaseNs(timeBaseNs),
62       mCurrentBucketStartTimeNs(timeBaseNs),
63       mCurrentBucketNum(0),
64       mCondition(initialCondition(conditionIndex, initialConditionCache)),
65       // For metrics with pull events, condition timer will be set later within the constructor
66       mConditionTimer(false, timeBaseNs),
67       mConditionTrackerIndex(conditionIndex),
68       mConditionSliced(false),
69       mWizard(wizard),
70       mContainANYPositionInDimensionsInWhat(false),
71       mShouldUseNestedDimensions(false),
72       mHasLinksToAllConditionDimensionsInTracker(false),
73       mEventActivationMap(eventActivationMap),
74       mEventDeactivationMap(eventDeactivationMap),
75       mIsActive(mEventActivationMap.empty()),
76       mSlicedStateAtoms(slicedStateAtoms),
77       mStateGroupMap(stateGroupMap),
78       mSplitBucketForAppUpgrade(splitBucketForAppUpgrade),
79       mHasHitGuardrail(false),
80       mSampledWhatFields({}),
81       mShardCount(0),
82       mConfigMetadataProvider(configMetadataProvider) {
83 }
84 
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)85 optional<InvalidConfigReason> MetricProducer::onConfigUpdatedLocked(
86         const StatsdConfig& config, const int configIndex, const int metricIndex,
87         const vector<sp<AtomMatchingTracker>>& allAtomMatchingTrackers,
88         const unordered_map<int64_t, int>& oldAtomMatchingTrackerMap,
89         const unordered_map<int64_t, int>& newAtomMatchingTrackerMap,
90         const sp<EventMatcherWizard>& matcherWizard,
91         const vector<sp<ConditionTracker>>& allConditionTrackers,
92         const unordered_map<int64_t, int>& conditionTrackerMap, const sp<ConditionWizard>& wizard,
93         const unordered_map<int64_t, int>& metricToActivationMap,
94         unordered_map<int, vector<int>>& trackerToMetricMap,
95         unordered_map<int, vector<int>>& conditionToMetricMap,
96         unordered_map<int, vector<int>>& activationAtomTrackerToMetricMap,
97         unordered_map<int, vector<int>>& deactivationAtomTrackerToMetricMap,
98         vector<int>& metricsWithActivation) {
99     sp<ConditionWizard> tmpWizard = mWizard;
100     mWizard = wizard;
101 
102     unordered_map<int, shared_ptr<Activation>> newEventActivationMap;
103     unordered_map<int, vector<shared_ptr<Activation>>> newEventDeactivationMap;
104     optional<InvalidConfigReason> invalidConfigReason = handleMetricActivationOnConfigUpdate(
105             config, mMetricId, metricIndex, metricToActivationMap, oldAtomMatchingTrackerMap,
106             newAtomMatchingTrackerMap, mEventActivationMap, activationAtomTrackerToMetricMap,
107             deactivationAtomTrackerToMetricMap, metricsWithActivation, newEventActivationMap,
108             newEventDeactivationMap);
109     if (invalidConfigReason.has_value()) {
110         return invalidConfigReason;
111     }
112     mEventActivationMap = newEventActivationMap;
113     mEventDeactivationMap = newEventDeactivationMap;
114     mAnomalyTrackers.clear();
115     return nullopt;
116 }
117 
onMatchedLogEventLocked(const size_t matcherIndex,const LogEvent & event)118 void MetricProducer::onMatchedLogEventLocked(const size_t matcherIndex, const LogEvent& event) {
119     if (!mIsActive) {
120         return;
121     }
122     int64_t eventTimeNs = event.GetElapsedTimestampNs();
123     // this is old event, maybe statsd restarted?
124     if (eventTimeNs < mTimeBaseNs) {
125         return;
126     }
127 
128     if (!passesSampleCheckLocked(event.getValues())) {
129         return;
130     }
131 
132     bool condition;
133     ConditionKey conditionKey;
134     if (mConditionSliced) {
135         for (const auto& link : mMetric2ConditionLinks) {
136             getDimensionForCondition(event.getValues(), link, &conditionKey[link.conditionId]);
137         }
138         auto conditionState =
139             mWizard->query(mConditionTrackerIndex, conditionKey,
140                            !mHasLinksToAllConditionDimensionsInTracker);
141         condition = (conditionState == ConditionState::kTrue);
142     } else {
143         // TODO: The unknown condition state is not handled here, we should fix it.
144         condition = mCondition == ConditionState::kTrue;
145     }
146 
147     // Stores atom id to primary key pairs for each state atom that the metric is
148     // sliced by.
149     std::map<int32_t, HashableDimensionKey> statePrimaryKeys;
150 
151     // For states with primary fields, use MetricStateLinks to get the primary
152     // field values from the log event. These values will form a primary key
153     // that will be used to query StateTracker for the correct state value.
154     for (const auto& stateLink : mMetric2StateLinks) {
155         getDimensionForState(event.getValues(), stateLink,
156                              &statePrimaryKeys[stateLink.stateAtomId]);
157     }
158 
159     // For each sliced state, query StateTracker for the state value using
160     // either the primary key from the previous step or the DEFAULT_DIMENSION_KEY.
161     //
162     // Expected functionality: for any case where the MetricStateLinks are
163     // initialized incorrectly (ex. # of state links != # of primary fields, no
164     // links are provided for a state with primary fields, links are provided
165     // in the wrong order, etc.), StateTracker will simply return kStateUnknown
166     // when queried using an incorrect key.
167     HashableDimensionKey stateValuesKey;
168     for (auto atomId : mSlicedStateAtoms) {
169         FieldValue value;
170         if (statePrimaryKeys.find(atomId) != statePrimaryKeys.end()) {
171             // found a primary key for this state, query using the key
172             queryStateValue(atomId, statePrimaryKeys[atomId], &value);
173         } else {
174             // if no MetricStateLinks exist for this state atom,
175             // query using the default dimension key (empty HashableDimensionKey)
176             queryStateValue(atomId, DEFAULT_DIMENSION_KEY, &value);
177         }
178         mapStateValue(atomId, &value);
179         stateValuesKey.addValue(value);
180     }
181 
182     HashableDimensionKey dimensionInWhat;
183     filterValues(mDimensionsInWhat, event.getValues(), &dimensionInWhat);
184     MetricDimensionKey metricKey(dimensionInWhat, stateValuesKey);
185     onMatchedLogEventInternalLocked(matcherIndex, metricKey, conditionKey, condition, event,
186                                     statePrimaryKeys);
187 }
188 
189 /**
190  * @brief Updates data corruption state overriding lower severity state with a higher
191  *        Inherited classes are responsible for proper severity determination according
192  *        to loss parameters (see @determineCorruptionSeverity)
193  */
onMatchedLogEventLostLocked(int32_t,DataCorruptedReason reason,LostAtomType atomType)194 void MetricProducer::onMatchedLogEventLostLocked(int32_t /*atomId*/, DataCorruptedReason reason,
195                                                  LostAtomType atomType) {
196     const DataCorruptionSeverity newSeverity = determineCorruptionSeverity(reason, atomType);
197     switch (reason) {
198         case DATA_CORRUPTED_SOCKET_LOSS:
199             mDataCorruptedDueToSocketLoss = std::max(mDataCorruptedDueToSocketLoss, newSeverity);
200             break;
201         case DATA_CORRUPTED_EVENT_QUEUE_OVERFLOW:
202             mDataCorruptedDueToQueueOverflow =
203                     std::max(mDataCorruptedDueToQueueOverflow, newSeverity);
204             break;
205         default:
206             ALOGW("Unsupported data corruption reason reported %d", reason);
207             break;
208     }
209 }
210 
resetDataCorruptionFlagsLocked()211 void MetricProducer::resetDataCorruptionFlagsLocked() {
212     if (mDataCorruptedDueToSocketLoss != DataCorruptionSeverity::kUnrecoverable) {
213         mDataCorruptedDueToSocketLoss = DataCorruptionSeverity::kNone;
214     }
215     if (mDataCorruptedDueToQueueOverflow != DataCorruptionSeverity::kUnrecoverable) {
216         mDataCorruptedDueToQueueOverflow = DataCorruptionSeverity::kNone;
217     }
218 }
219 
evaluateActiveStateLocked(int64_t elapsedTimestampNs)220 bool MetricProducer::evaluateActiveStateLocked(int64_t elapsedTimestampNs) {
221     bool isActive = mEventActivationMap.empty();
222     for (auto& it : mEventActivationMap) {
223         if (it.second->state == ActivationState::kActive &&
224             elapsedTimestampNs > it.second->ttl_ns + it.second->start_ns) {
225             it.second->state = ActivationState::kNotActive;
226         }
227         if (it.second->state == ActivationState::kActive) {
228             isActive = true;
229         }
230     }
231     return isActive;
232 }
233 
flushIfExpire(int64_t elapsedTimestampNs)234 void MetricProducer::flushIfExpire(int64_t elapsedTimestampNs) {
235     std::lock_guard<std::mutex> lock(mMutex);
236     if (!mIsActive) {
237         return;
238     }
239     const bool isActive = evaluateActiveStateLocked(elapsedTimestampNs);
240     if (!isActive) {  // Metric went from active to not active.
241         onActiveStateChangedLocked(elapsedTimestampNs, false);
242 
243         // Set mIsActive to false after onActiveStateChangedLocked to ensure any pulls that occur
244         // through onActiveStateChangedLocked are processed.
245         mIsActive = false;
246     }
247 }
248 
activateLocked(int activationTrackerIndex,int64_t elapsedTimestampNs)249 void MetricProducer::activateLocked(int activationTrackerIndex, int64_t elapsedTimestampNs) {
250     auto it = mEventActivationMap.find(activationTrackerIndex);
251     if (it == mEventActivationMap.end()) {
252         return;
253     }
254     auto& activation = it->second;
255     if (ACTIVATE_ON_BOOT == activation->activationType) {
256         if (ActivationState::kNotActive == activation->state) {
257             activation->state = ActivationState::kActiveOnBoot;
258         }
259         // If the Activation is already active or set to kActiveOnBoot, do nothing.
260         return;
261     }
262     activation->start_ns = elapsedTimestampNs;
263     activation->state = ActivationState::kActive;
264     if (!mIsActive) {  // Metric was previously inactive and now is active.
265         // Set mIsActive to true before onActiveStateChangedLocked to ensure any pulls that occur
266         // through onActiveStateChangedLocked are processed.
267         mIsActive = true;
268 
269         onActiveStateChangedLocked(elapsedTimestampNs, true);
270     }
271 }
272 
cancelEventActivationLocked(int deactivationTrackerIndex)273 void MetricProducer::cancelEventActivationLocked(int deactivationTrackerIndex) {
274     auto it = mEventDeactivationMap.find(deactivationTrackerIndex);
275     if (it == mEventDeactivationMap.end()) {
276         return;
277     }
278     for (auto& activationToCancelIt : it->second) {
279         activationToCancelIt->state = ActivationState::kNotActive;
280     }
281 }
282 
loadActiveMetricLocked(const ActiveMetric & activeMetric,int64_t currentTimeNs)283 void MetricProducer::loadActiveMetricLocked(const ActiveMetric& activeMetric,
284                                             int64_t currentTimeNs) {
285     if (mEventActivationMap.size() == 0) {
286         return;
287     }
288     for (int i = 0; i < activeMetric.activation_size(); i++) {
289         const auto& activeEventActivation = activeMetric.activation(i);
290         auto it = mEventActivationMap.find(activeEventActivation.atom_matcher_index());
291         if (it == mEventActivationMap.end()) {
292             ALOGE("Saved event activation not found");
293             continue;
294         }
295         auto& activation = it->second;
296         // If the event activation does not have a state, assume it is active.
297         if (!activeEventActivation.has_state() ||
298                 activeEventActivation.state() == ActiveEventActivation::ACTIVE) {
299             // We don't want to change the ttl for future activations, so we set the start_ns
300             // such that start_ns + ttl_ns == currentTimeNs + remaining_ttl_nanos
301             activation->start_ns =
302                 currentTimeNs + activeEventActivation.remaining_ttl_nanos() - activation->ttl_ns;
303             activation->state = ActivationState::kActive;
304             mIsActive = true;
305         } else if (activeEventActivation.state() == ActiveEventActivation::ACTIVATE_ON_BOOT) {
306             activation->state = ActivationState::kActiveOnBoot;
307         }
308     }
309 }
310 
writeActiveMetricToProtoOutputStream(int64_t currentTimeNs,const DumpReportReason reason,ProtoOutputStream * proto)311 void MetricProducer::writeActiveMetricToProtoOutputStream(
312         int64_t currentTimeNs, const DumpReportReason reason, ProtoOutputStream* proto) {
313     proto->write(FIELD_TYPE_INT64 | FIELD_ID_ACTIVE_METRIC_ID, (long long)mMetricId);
314     for (auto& it : mEventActivationMap) {
315         const int atom_matcher_index = it.first;
316         const std::shared_ptr<Activation>& activation = it.second;
317 
318         if (ActivationState::kNotActive == activation->state ||
319                 (ActivationState::kActive == activation->state &&
320                  activation->start_ns + activation->ttl_ns < currentTimeNs)) {
321             continue;
322         }
323 
324         const uint64_t activationToken = proto->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED |
325                 FIELD_ID_ACTIVE_METRIC_ACTIVATION);
326         proto->write(FIELD_TYPE_INT32 | FIELD_ID_ACTIVE_EVENT_ACTIVATION_ATOM_MATCHER_INDEX,
327                 atom_matcher_index);
328         if (ActivationState::kActive == activation->state) {
329             const int64_t remainingTtlNs =
330                     activation->start_ns + activation->ttl_ns - currentTimeNs;
331             proto->write(FIELD_TYPE_INT64 | FIELD_ID_ACTIVE_EVENT_ACTIVATION_REMAINING_TTL_NANOS,
332                     (long long)remainingTtlNs);
333             proto->write(FIELD_TYPE_ENUM | FIELD_ID_ACTIVE_EVENT_ACTIVATION_STATE,
334                     ActiveEventActivation::ACTIVE);
335 
336         } else if (ActivationState::kActiveOnBoot == activation->state) {
337             if (reason == DEVICE_SHUTDOWN || reason == TERMINATION_SIGNAL_RECEIVED) {
338                 proto->write(
339                         FIELD_TYPE_INT64 | FIELD_ID_ACTIVE_EVENT_ACTIVATION_REMAINING_TTL_NANOS,
340                         (long long)activation->ttl_ns);
341                 proto->write(FIELD_TYPE_ENUM | FIELD_ID_ACTIVE_EVENT_ACTIVATION_STATE,
342                                     ActiveEventActivation::ACTIVE);
343             } else if (reason == STATSCOMPANION_DIED) {
344                 // We are saving because of system server death, not due to a device shutdown.
345                 // Next time we load, we do not want to activate metrics that activate on boot.
346                 proto->write(FIELD_TYPE_ENUM | FIELD_ID_ACTIVE_EVENT_ACTIVATION_STATE,
347                                                     ActiveEventActivation::ACTIVATE_ON_BOOT);
348             }
349         }
350         proto->end(activationToken);
351     }
352 }
353 
queryStateValue(int32_t atomId,const HashableDimensionKey & queryKey,FieldValue * value)354 void MetricProducer::queryStateValue(int32_t atomId, const HashableDimensionKey& queryKey,
355                                      FieldValue* value) {
356     if (!StateManager::getInstance().getStateValue(atomId, queryKey, value)) {
357         value->mValue = Value(StateTracker::kStateUnknown);
358         value->mField.setTag(atomId);
359         ALOGW("StateTracker not found for state atom %d", atomId);
360         return;
361     }
362 }
363 
mapStateValue(int32_t atomId,FieldValue * value)364 void MetricProducer::mapStateValue(int32_t atomId, FieldValue* value) {
365     // check if there is a state map for this atom
366     auto atomIt = mStateGroupMap.find(atomId);
367     if (atomIt == mStateGroupMap.end()) {
368         return;
369     }
370     auto valueIt = atomIt->second.find(value->mValue.int_value);
371     if (valueIt == atomIt->second.end()) {
372         // state map exists, but value was not put in a state group
373         // so set mValue to kStateUnknown
374         // TODO(tsaichristine): handle incomplete state maps
375         value->mValue.setInt(StateTracker::kStateUnknown);
376     } else {
377         // set mValue to group_id
378         value->mValue.setLong(valueIt->second);
379     }
380 }
381 
getUnknownStateKey()382 HashableDimensionKey MetricProducer::getUnknownStateKey() {
383     HashableDimensionKey stateKey;
384     for (auto atom : mSlicedStateAtoms) {
385         FieldValue fieldValue;
386         fieldValue.mField.setTag(atom);
387         fieldValue.mValue.setInt(StateTracker::kStateUnknown);
388         stateKey.addValue(fieldValue);
389     }
390     return stateKey;
391 }
392 
buildDropEvent(const int64_t dropTimeNs,const BucketDropReason reason) const393 DropEvent MetricProducer::buildDropEvent(const int64_t dropTimeNs,
394                                          const BucketDropReason reason) const {
395     DropEvent event;
396     event.reason = reason;
397     event.dropTimeNs = dropTimeNs;
398     return event;
399 }
400 
maxDropEventsReached() const401 bool MetricProducer::maxDropEventsReached() const {
402     return mCurrentSkippedBucket.dropEvents.size() >= StatsdStats::kMaxLoggedBucketDropEvents;
403 }
404 
passesSampleCheckLocked(const vector<FieldValue> & values) const405 bool MetricProducer::passesSampleCheckLocked(const vector<FieldValue>& values) const {
406     // Only perform sampling if shard count is correct and there is a sampled what field.
407     if (mShardCount <= 1 || mSampledWhatFields.size() == 0) {
408         return true;
409     }
410     // If filtering fails, don't perform sampling. Event could be a gauge trigger event or stop all
411     // event.
412     FieldValue sampleFieldValue;
413     if (!filterValues(mSampledWhatFields[0], values, &sampleFieldValue)) {
414         return true;
415     }
416     return shouldKeepSample(sampleFieldValue, ShardOffsetProvider::getInstance().getShardOffset(),
417                             mShardCount);
418 }
419 
getConfigMetadataProvider() const420 sp<ConfigMetadataProvider> MetricProducer::getConfigMetadataProvider() const {
421     sp<ConfigMetadataProvider> provider = mConfigMetadataProvider.promote();
422     if (provider == nullptr) {
423         ALOGE("Could not promote ConfigMetadataProvider");
424         StatsdStats::getInstance().noteConfigMetadataProviderPromotionFailed(mConfigKey);
425     }
426     return provider;
427 }
428 
computeBucketSizeLocked(const bool isFullBucket,const MetricDimensionKey & dimKey,const bool isFirstBucket) const429 size_t MetricProducer::computeBucketSizeLocked(const bool isFullBucket,
430                                                const MetricDimensionKey& dimKey,
431                                                const bool isFirstBucket) const {
432     size_t bucketSize = 0;
433 
434     // Bucket timestamps or bucket number
435     bucketSize += isFullBucket ? sizeof(int32_t) : 2 * sizeof(int64_t);
436 
437     // Each dimension / state key can have multiple buckets. Add the size only for the first bucket.
438     if (isFirstBucket) {
439         bucketSize += dimKey.getSize(mShouldUseNestedDimensions);
440     }
441 
442     return bucketSize;
443 }
444 
computeOverheadSizeLocked(const bool hasPastBuckets,const bool dimensionGuardrailHit) const445 size_t MetricProducer::computeOverheadSizeLocked(const bool hasPastBuckets,
446                                                  const bool dimensionGuardrailHit) const {
447     size_t overheadSize = 0;
448 
449     // MetricId + isActive
450     overheadSize += sizeof(int64_t) + sizeof(bool);
451 
452     if (hasPastBuckets) {
453         if (dimensionGuardrailHit) {
454             overheadSize += sizeof(int32_t);
455         }
456 
457         // estimated_memory_bytes
458         overheadSize += sizeof(int32_t);
459         // mTimeBase and mBucketSizeNs
460         overheadSize += 2 * sizeof(int64_t);
461 
462         if (!mShouldUseNestedDimensions) {
463             // Assume dimensions data adds an additional atomTag + # of dimension fields
464             overheadSize += sizeof(int32_t);
465             overheadSize += sizeof(int32_t) * mDimensionsInWhat.size();
466         }
467     }
468     return overheadSize;
469 }
470 
computeSkippedBucketSizeLocked(const SkippedBucket & skippedBucket) const471 size_t MetricProducer::computeSkippedBucketSizeLocked(const SkippedBucket& skippedBucket) const {
472     size_t skippedBucketSize = 0;
473 
474     // Bucket Start, Bucket End
475     skippedBucketSize += 2 * sizeof(int64_t);
476 
477     // DropType, Drop Time
478     skippedBucketSize += (sizeof(int32_t) + sizeof(int64_t)) * skippedBucket.dropEvents.size();
479 
480     return skippedBucketSize;
481 }
482 
483 }  // namespace statsd
484 }  // namespace os
485 }  // namespace android
486