diff options
25 files changed, 1517 insertions, 224 deletions
diff --git a/cmds/statsd/Android.bp b/cmds/statsd/Android.bp index a3214606c32f..20f9e76b4436 100644 --- a/cmds/statsd/Android.bp +++ b/cmds/statsd/Android.bp @@ -337,6 +337,7 @@ cc_test { "tests/external/StatsPullerManager_test.cpp", "tests/FieldValue_test.cpp", "tests/guardrail/StatsdStats_test.cpp", + "tests/HashableDimensionKey_test.cpp", "tests/indexed_priority_queue_test.cpp", "tests/log_event/LogEventQueue_test.cpp", "tests/LogEntryMatcher_test.cpp", diff --git a/cmds/statsd/src/HashableDimensionKey.cpp b/cmds/statsd/src/HashableDimensionKey.cpp index 23d8f59e94ea..29249f4a6c55 100644 --- a/cmds/statsd/src/HashableDimensionKey.cpp +++ b/cmds/statsd/src/HashableDimensionKey.cpp @@ -230,6 +230,47 @@ void getDimensionForState(const std::vector<FieldValue>& eventValues, const Metr } } +bool containsLinkedStateValues(const HashableDimensionKey& whatKey, + const HashableDimensionKey& primaryKey, + const vector<Metric2State>& stateLinks, const int32_t stateAtomId) { + if (whatKey.getValues().size() < primaryKey.getValues().size()) { + ALOGE("Contains linked values false: whatKey is too small"); + return false; + } + + for (const auto& primaryValue : primaryKey.getValues()) { + bool found = false; + for (const auto& whatValue : whatKey.getValues()) { + if (linked(stateLinks, stateAtomId, primaryValue.mField, whatValue.mField) && + primaryValue.mValue == whatValue.mValue) { + found = true; + break; + } + } + if (!found) { + return false; + } + } + return true; +} + +bool linked(const vector<Metric2State>& stateLinks, const int32_t stateAtomId, + const Field& stateField, const Field& metricField) { + for (auto stateLink : stateLinks) { + if (stateLink.stateAtomId != stateAtomId) { + continue; + } + + for (size_t i = 0; i < stateLink.stateFields.size(); i++) { + if (stateLink.stateFields[i].mMatcher == stateField && + stateLink.metricFields[i].mMatcher == metricField) { + return true; + } + } + } + return false; +} + bool LessThan(const vector<FieldValue>& s1, const vector<FieldValue>& s2) { if (s1.size() != s2.size()) { return s1.size() < s2.size(); diff --git a/cmds/statsd/src/HashableDimensionKey.h b/cmds/statsd/src/HashableDimensionKey.h index a766bbae00d3..33a502497746 100644 --- a/cmds/statsd/src/HashableDimensionKey.h +++ b/cmds/statsd/src/HashableDimensionKey.h @@ -110,6 +110,10 @@ public: return mStateValuesKey; } + inline HashableDimensionKey* getMutableStateValuesKey() { + return &mStateValuesKey; + } + inline void setStateValuesKey(const HashableDimensionKey& key) { mStateValuesKey = key; } @@ -169,6 +173,32 @@ void getDimensionForCondition(const std::vector<FieldValue>& eventValues, void getDimensionForState(const std::vector<FieldValue>& eventValues, const Metric2State& link, HashableDimensionKey* statePrimaryKey); +/** + * Returns true if the primaryKey values are a subset of the whatKey values. + * The values from the primaryKey come from the state atom, so we need to + * check that a link exists between the state atom field and what atom field. + * + * Example: + * whatKey = [Atom: 10, {uid: 1005, wakelock_name: "compose"}] + * statePrimaryKey = [Atom: 27, {uid: 1005}] + * Returns true IF one of the Metric2State links Atom 10's uid to Atom 27's uid + * + * Example: + * whatKey = [Atom: 10, {uid: 1005, wakelock_name: "compose"}] + * statePrimaryKey = [Atom: 59, {uid: 1005, package_name: "system"}] + * Returns false + */ +bool containsLinkedStateValues(const HashableDimensionKey& whatKey, + const HashableDimensionKey& primaryKey, + const std::vector<Metric2State>& stateLinks, + const int32_t stateAtomId); + +/** + * Returns true if there is a Metric2State link that links the stateField and + * the metricField (they are equal fields from different atoms). + */ +bool linked(const std::vector<Metric2State>& stateLinks, const int32_t stateAtomId, + const Field& stateField, const Field& metricField); } // namespace statsd } // namespace os } // namespace android diff --git a/cmds/statsd/src/StatsLogProcessor.h b/cmds/statsd/src/StatsLogProcessor.h index 14585c3df870..97512ed7595c 100644 --- a/cmds/statsd/src/StatsLogProcessor.h +++ b/cmds/statsd/src/StatsLogProcessor.h @@ -334,6 +334,11 @@ private: FRIEND_TEST(DurationMetricE2eTest, TestWithCondition); FRIEND_TEST(DurationMetricE2eTest, TestWithSlicedCondition); FRIEND_TEST(DurationMetricE2eTest, TestWithActivationAndSlicedCondition); + FRIEND_TEST(DurationMetricE2eTest, TestWithSlicedState); + FRIEND_TEST(DurationMetricE2eTest, TestWithConditionAndSlicedState); + FRIEND_TEST(DurationMetricE2eTest, TestWithSlicedStateMapped); + FRIEND_TEST(DurationMetricE2eTest, TestSlicedStatePrimaryFieldsNotSubsetDimInWhat); + FRIEND_TEST(DurationMetricE2eTest, TestWithSlicedStatePrimaryFieldsSubset); FRIEND_TEST(ValueMetricE2eTest, TestInitWithSlicedState); FRIEND_TEST(ValueMetricE2eTest, TestInitWithSlicedState_WithDimensions); diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto index 57d4d78cc06d..5cd00c35b05c 100644 --- a/cmds/statsd/src/atoms.proto +++ b/cmds/statsd/src/atoms.proto @@ -123,7 +123,8 @@ message Atom { BatteryLevelChanged battery_level_changed = 30 [(module) = "framework", (module) = "statsdtest"]; ChargingStateChanged charging_state_changed = 31 [(module) = "framework"]; - PluggedStateChanged plugged_state_changed = 32 [(module) = "framework"]; + PluggedStateChanged plugged_state_changed = 32 + [(module) = "framework", (module) = "statsdtest"]; InteractiveStateChanged interactive_state_changed = 33 [(module) = "framework"]; TouchEventReported touch_event_reported = 34; WakeupAlarmOccurred wakeup_alarm_occurred = 35 [(module) = "framework"]; diff --git a/cmds/statsd/src/metrics/DurationMetricProducer.cpp b/cmds/statsd/src/metrics/DurationMetricProducer.cpp index e85b97514242..0de92f3d9f47 100644 --- a/cmds/statsd/src/metrics/DurationMetricProducer.cpp +++ b/cmds/statsd/src/metrics/DurationMetricProducer.cpp @@ -55,6 +55,7 @@ const int FIELD_ID_DATA = 1; const int FIELD_ID_DIMENSION_IN_WHAT = 1; const int FIELD_ID_BUCKET_INFO = 3; const int FIELD_ID_DIMENSION_LEAF_IN_WHAT = 4; +const int FIELD_ID_SLICE_BY_STATE = 6; // for DurationBucketInfo const int FIELD_ID_DURATION = 3; const int FIELD_ID_BUCKET_NUM = 4; @@ -115,6 +116,14 @@ DurationMetricProducer::DurationMetricProducer( } mUnSlicedPartCondition = ConditionState::kUnknown; + for (const auto& stateLink : metric.state_link()) { + Metric2State ms; + ms.stateAtomId = stateLink.state_atom_id(); + translateFieldMatcher(stateLink.fields_in_what(), &ms.metricFields); + translateFieldMatcher(stateLink.fields_in_state(), &ms.stateFields); + mMetric2StateLinks.push_back(ms); + } + mUseWhatDimensionAsInternalDimension = equalDimensions(mDimensionsInWhat, mInternalDimensions); if (mWizard != nullptr && mConditionTrackerIndex >= 0 && mMetric2ConditionLinks.size() == 1) { @@ -150,21 +159,49 @@ sp<AnomalyTracker> DurationMetricProducer::addAnomalyTracker( return anomalyTracker; } +void DurationMetricProducer::onStateChanged(const int64_t eventTimeNs, const int32_t atomId, + const HashableDimensionKey& primaryKey, + const int32_t oldState, const int32_t newState) { + // Create a FieldValue object to hold the new state. + FieldValue value; + value.mValue.setInt(newState); + // Check if this metric has a StateMap. If so, map the new state value to + // the correct state group id. + mapStateValue(atomId, &value); + + flushIfNeededLocked(eventTimeNs); + + // Each duration tracker is mapped to a different whatKey (a set of values from the + // dimensionsInWhat fields). We notify all trackers iff the primaryKey field values from the + // state change event are a subset of the tracker's whatKey field values. + // + // Ex. For a duration metric dimensioned on uid and tag: + // DurationTracker1 whatKey = uid: 1001, tag: 1 + // DurationTracker2 whatKey = uid: 1002, tag 1 + // + // If the state change primaryKey = uid: 1001, we only notify DurationTracker1 of a state + // change. + for (auto& whatIt : mCurrentSlicedDurationTrackerMap) { + if (!containsLinkedStateValues(whatIt.first, primaryKey, mMetric2StateLinks, atomId)) { + continue; + } + whatIt.second->onStateChanged(eventTimeNs, atomId, value); + } +} + unique_ptr<DurationTracker> DurationMetricProducer::createDurationTracker( const MetricDimensionKey& eventKey) const { switch (mAggregationType) { case DurationMetric_AggregationType_SUM: return make_unique<OringDurationTracker>( - mConfigKey, mMetricId, eventKey, mWizard, mConditionTrackerIndex, - mNested, mCurrentBucketStartTimeNs, mCurrentBucketNum, - mTimeBaseNs, mBucketSizeNs, mConditionSliced, - mHasLinksToAllConditionDimensionsInTracker, mAnomalyTrackers); + mConfigKey, mMetricId, eventKey, mWizard, mConditionTrackerIndex, mNested, + mCurrentBucketStartTimeNs, mCurrentBucketNum, mTimeBaseNs, mBucketSizeNs, + mConditionSliced, mHasLinksToAllConditionDimensionsInTracker, mAnomalyTrackers); case DurationMetric_AggregationType_MAX_SPARSE: return make_unique<MaxDurationTracker>( - mConfigKey, mMetricId, eventKey, mWizard, mConditionTrackerIndex, - mNested, mCurrentBucketStartTimeNs, mCurrentBucketNum, - mTimeBaseNs, mBucketSizeNs, mConditionSliced, - mHasLinksToAllConditionDimensionsInTracker, mAnomalyTrackers); + mConfigKey, mMetricId, eventKey, mWizard, mConditionTrackerIndex, mNested, + mCurrentBucketStartTimeNs, mCurrentBucketNum, mTimeBaseNs, mBucketSizeNs, + mConditionSliced, mHasLinksToAllConditionDimensionsInTracker, mAnomalyTrackers); } } @@ -364,6 +401,13 @@ void DurationMetricProducer::onDumpReportLocked(const int64_t dumpTimeNs, writeDimensionLeafNodesToProto(dimensionKey.getDimensionKeyInWhat(), FIELD_ID_DIMENSION_LEAF_IN_WHAT, str_set, protoOutput); } + // Then fill slice_by_state. + for (auto state : dimensionKey.getStateValuesKey().getValues()) { + uint64_t stateToken = protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | + FIELD_ID_SLICE_BY_STATE); + writeStateToProto(state, protoOutput); + protoOutput->end(stateToken); + } // Then fill bucket_info (DurationBucketInfo). for (const auto& bucket : pair.second) { uint64_t bucketInfoToken = protoOutput->start( @@ -460,7 +504,6 @@ void DurationMetricProducer::handleStartEvent(const MetricDimensionKey& eventKey const ConditionKey& conditionKeys, bool condition, const LogEvent& event) { const auto& whatKey = eventKey.getDimensionKeyInWhat(); - auto whatIt = mCurrentSlicedDurationTrackerMap.find(whatKey); if (whatIt == mCurrentSlicedDurationTrackerMap.end()) { if (hitGuardRailLocked(eventKey)) { @@ -471,19 +514,18 @@ void DurationMetricProducer::handleStartEvent(const MetricDimensionKey& eventKey auto it = mCurrentSlicedDurationTrackerMap.find(whatKey); if (mUseWhatDimensionAsInternalDimension) { - it->second->noteStart(whatKey, condition, - event.GetElapsedTimestampNs(), conditionKeys); + it->second->noteStart(whatKey, condition, event.GetElapsedTimestampNs(), conditionKeys); return; } if (mInternalDimensions.empty()) { - it->second->noteStart(DEFAULT_DIMENSION_KEY, condition, - event.GetElapsedTimestampNs(), conditionKeys); + it->second->noteStart(DEFAULT_DIMENSION_KEY, condition, event.GetElapsedTimestampNs(), + conditionKeys); } else { HashableDimensionKey dimensionKey = DEFAULT_DIMENSION_KEY; filterValues(mInternalDimensions, event.getValues(), &dimensionKey); - it->second->noteStart( - dimensionKey, condition, event.GetElapsedTimestampNs(), conditionKeys); + it->second->noteStart(dimensionKey, condition, event.GetElapsedTimestampNs(), + conditionKeys); } } @@ -519,6 +561,41 @@ void DurationMetricProducer::onMatchedLogEventLocked(const size_t matcherIndex, filterValues(mDimensionsInWhat, event.getValues(), &dimensionInWhat); } + // Stores atom id to primary key pairs for each state atom that the metric is + // sliced by. + std::map<int, HashableDimensionKey> statePrimaryKeys; + + // For states with primary fields, use MetricStateLinks to get the primary + // field values from the log event. These values will form a primary key + // that will be used to query StateTracker for the correct state value. + for (const auto& stateLink : mMetric2StateLinks) { + getDimensionForState(event.getValues(), stateLink, + &statePrimaryKeys[stateLink.stateAtomId]); + } + + // For each sliced state, query StateTracker for the state value using + // either the primary key from the previous step or the DEFAULT_DIMENSION_KEY. + // + // Expected functionality: for any case where the MetricStateLinks are + // initialized incorrectly (ex. # of state links != # of primary fields, no + // links are provided for a state with primary fields, links are provided + // in the wrong order, etc.), StateTracker will simply return kStateUnknown + // when queried using an incorrect key. + HashableDimensionKey stateValuesKey = DEFAULT_DIMENSION_KEY; + for (auto atomId : mSlicedStateAtoms) { + FieldValue value; + if (statePrimaryKeys.find(atomId) != statePrimaryKeys.end()) { + // found a primary key for this state, query using the key + queryStateValue(atomId, statePrimaryKeys[atomId], &value); + } else { + // if no MetricStateLinks exist for this state atom, + // query using the default dimension key (empty HashableDimensionKey) + queryStateValue(atomId, DEFAULT_DIMENSION_KEY, &value); + } + mapStateValue(atomId, &value); + stateValuesKey.addValue(value); + } + // Handles Stop events. if (matcherIndex == mStopIndex) { if (mUseWhatDimensionAsInternalDimension) { @@ -559,8 +636,8 @@ void DurationMetricProducer::onMatchedLogEventLocked(const size_t matcherIndex, condition = condition && mIsActive; - handleStartEvent(MetricDimensionKey(dimensionInWhat, DEFAULT_DIMENSION_KEY), conditionKey, - condition, event); + handleStartEvent(MetricDimensionKey(dimensionInWhat, stateValuesKey), conditionKey, condition, + event); } size_t DurationMetricProducer::byteSizeLocked() const { diff --git a/cmds/statsd/src/metrics/DurationMetricProducer.h b/cmds/statsd/src/metrics/DurationMetricProducer.h index 06da0f64aedb..cc48f99add01 100644 --- a/cmds/statsd/src/metrics/DurationMetricProducer.h +++ b/cmds/statsd/src/metrics/DurationMetricProducer.h @@ -54,6 +54,10 @@ public: sp<AnomalyTracker> addAnomalyTracker(const Alert &alert, const sp<AlarmMonitor>& anomalyAlarmMonitor) override; + void onStateChanged(const int64_t eventTimeNs, const int32_t atomId, + const HashableDimensionKey& primaryKey, const int32_t oldState, + const int32_t newState) override; + protected: void onMatchedLogEventLocked(const size_t matcherIndex, const LogEvent& event) override; @@ -137,7 +141,7 @@ private: // Helper function to create a duration tracker given the metric aggregation type. std::unique_ptr<DurationTracker> createDurationTracker( - const MetricDimensionKey& eventKey) const; + const MetricDimensionKey& eventKey) const; // This hides the base class's std::vector<sp<AnomalyTracker>> mAnomalyTrackers std::vector<sp<DurationAnomalyTracker>> mAnomalyTrackers; diff --git a/cmds/statsd/src/metrics/MetricProducer.cpp b/cmds/statsd/src/metrics/MetricProducer.cpp index be754e29b5bd..2518d85eb6a1 100644 --- a/cmds/statsd/src/metrics/MetricProducer.cpp +++ b/cmds/statsd/src/metrics/MetricProducer.cpp @@ -120,12 +120,13 @@ void MetricProducer::onMatchedLogEventLocked(const size_t matcherIndex, const Lo FieldValue value; if (statePrimaryKeys.find(atomId) != statePrimaryKeys.end()) { // found a primary key for this state, query using the key - getMappedStateValue(atomId, statePrimaryKeys[atomId], &value); + queryStateValue(atomId, statePrimaryKeys[atomId], &value); } else { // if no MetricStateLinks exist for this state atom, // query using the default dimension key (empty HashableDimensionKey) - getMappedStateValue(atomId, DEFAULT_DIMENSION_KEY, &value); + queryStateValue(atomId, DEFAULT_DIMENSION_KEY, &value); } + mapStateValue(atomId, &value); stateValuesKey.addValue(value); } @@ -264,15 +265,17 @@ void MetricProducer::writeActiveMetricToProtoOutputStream( } } -void MetricProducer::getMappedStateValue(const int32_t atomId, const HashableDimensionKey& queryKey, - FieldValue* value) { +void MetricProducer::queryStateValue(const int32_t atomId, const HashableDimensionKey& queryKey, + FieldValue* value) { if (!StateManager::getInstance().getStateValue(atomId, queryKey, value)) { value->mValue = Value(StateTracker::kStateUnknown); value->mField.setTag(atomId); ALOGW("StateTracker not found for state atom %d", atomId); return; } +} +void MetricProducer::mapStateValue(const int32_t atomId, FieldValue* value) { // check if there is a state map for this atom auto atomIt = mStateGroupMap.find(atomId); if (atomIt == mStateGroupMap.end()) { diff --git a/cmds/statsd/src/metrics/MetricProducer.h b/cmds/statsd/src/metrics/MetricProducer.h index 4c4cd8940b24..4550e65b6438 100644 --- a/cmds/statsd/src/metrics/MetricProducer.h +++ b/cmds/statsd/src/metrics/MetricProducer.h @@ -187,7 +187,8 @@ public: }; void onStateChanged(const int64_t eventTimeNs, const int32_t atomId, - const HashableDimensionKey& primaryKey, int oldState, int newState){}; + const HashableDimensionKey& primaryKey, const int32_t oldState, + const int32_t newState){}; // Output the metrics data to [protoOutput]. All metrics reports end with the same timestamp. // This method clears all the past buckets. @@ -379,11 +380,15 @@ protected: return (endNs - mTimeBaseNs) / mBucketSizeNs - 1; } - // Query StateManager for original state value. - // If no state map exists for this atom, return the original value. - // Otherwise, return the group_id mapped to the atom and original value. - void getMappedStateValue(const int32_t atomId, const HashableDimensionKey& queryKey, - FieldValue* value); + // Query StateManager for original state value using the queryKey. + // The field and value are output. + void queryStateValue(const int32_t atomId, const HashableDimensionKey& queryKey, + FieldValue* value); + + // If a state map exists for the given atom, replace the original state + // value with the group id mapped to the value. + // If no state map exists, keep the original state value. + void mapStateValue(const int32_t atomId, FieldValue* value); DropEvent buildDropEvent(const int64_t dropTimeNs, const BucketDropReason reason); @@ -467,6 +472,11 @@ protected: FRIEND_TEST(DurationMetricE2eTest, TestWithCondition); FRIEND_TEST(DurationMetricE2eTest, TestWithSlicedCondition); FRIEND_TEST(DurationMetricE2eTest, TestWithActivationAndSlicedCondition); + FRIEND_TEST(DurationMetricE2eTest, TestWithSlicedState); + FRIEND_TEST(DurationMetricE2eTest, TestWithConditionAndSlicedState); + FRIEND_TEST(DurationMetricE2eTest, TestWithSlicedStateMapped); + FRIEND_TEST(DurationMetricE2eTest, TestSlicedStatePrimaryFieldsNotSubsetDimInWhat); + FRIEND_TEST(DurationMetricE2eTest, TestWithSlicedStatePrimaryFieldsSubset); FRIEND_TEST(MetricActivationE2eTest, TestCountMetric); FRIEND_TEST(MetricActivationE2eTest, TestCountMetricWithOneDeactivation); diff --git a/cmds/statsd/src/metrics/MetricsManager.h b/cmds/statsd/src/metrics/MetricsManager.h index 3fb9166bf9bf..1fd6572cc760 100644 --- a/cmds/statsd/src/metrics/MetricsManager.h +++ b/cmds/statsd/src/metrics/MetricsManager.h @@ -329,6 +329,11 @@ private: FRIEND_TEST(DurationMetricE2eTest, TestWithCondition); FRIEND_TEST(DurationMetricE2eTest, TestWithSlicedCondition); FRIEND_TEST(DurationMetricE2eTest, TestWithActivationAndSlicedCondition); + FRIEND_TEST(DurationMetricE2eTest, TestWithSlicedState); + FRIEND_TEST(DurationMetricE2eTest, TestWithConditionAndSlicedState); + FRIEND_TEST(DurationMetricE2eTest, TestWithSlicedStateMapped); + FRIEND_TEST(DurationMetricE2eTest, TestWithSlicedStatePrimaryFieldsSuperset); + FRIEND_TEST(DurationMetricE2eTest, TestWithSlicedStatePrimaryFieldsSubset); FRIEND_TEST(ValueMetricE2eTest, TestInitWithSlicedState); FRIEND_TEST(ValueMetricE2eTest, TestInitWithSlicedState_WithDimensions); diff --git a/cmds/statsd/src/metrics/duration_helper/DurationTracker.h b/cmds/statsd/src/metrics/duration_helper/DurationTracker.h index afe93d445e1d..8d59d1362919 100644 --- a/cmds/statsd/src/metrics/duration_helper/DurationTracker.h +++ b/cmds/statsd/src/metrics/duration_helper/DurationTracker.h @@ -56,11 +56,19 @@ struct DurationBucket { int64_t mDuration; }; +struct DurationValues { + // Recorded duration for current partial bucket. + int64_t mDuration; + + // Sum of past partial bucket durations in current full bucket. + // Used for anomaly detection. + int64_t mDurationFullBucket; +}; + class DurationTracker { public: DurationTracker(const ConfigKey& key, const int64_t& id, const MetricDimensionKey& eventKey, - sp<ConditionWizard> wizard, int conditionIndex, - bool nesting, + sp<ConditionWizard> wizard, int conditionIndex, bool nesting, int64_t currentBucketStartNs, int64_t currentBucketNum, int64_t startTimeNs, int64_t bucketSizeNs, bool conditionSliced, bool fullLink, const std::vector<sp<DurationAnomalyTracker>>& anomalyTrackers) @@ -73,7 +81,6 @@ public: mNested(nesting), mCurrentBucketStartTimeNs(currentBucketStartNs), mDuration(0), - mDurationFullBucket(0), mCurrentBucketNum(currentBucketNum), mStartTimeNs(startTimeNs), mConditionSliced(conditionSliced), @@ -82,8 +89,8 @@ public: virtual ~DurationTracker(){}; - virtual void noteStart(const HashableDimensionKey& key, bool condition, - const int64_t eventTime, const ConditionKey& conditionKey) = 0; + virtual void noteStart(const HashableDimensionKey& key, bool condition, const int64_t eventTime, + const ConditionKey& conditionKey) = 0; virtual void noteStop(const HashableDimensionKey& key, const int64_t eventTime, const bool stopAll) = 0; virtual void noteStopAll(const int64_t eventTime) = 0; @@ -91,6 +98,9 @@ public: virtual void onSlicedConditionMayChange(bool overallCondition, const int64_t timestamp) = 0; virtual void onConditionChanged(bool condition, const int64_t timestamp) = 0; + virtual void onStateChanged(const int64_t timestamp, const int32_t atomId, + const FieldValue& newState) = 0; + // Flush stale buckets if needed, and return true if the tracker has no on-going duration // events, so that the owner can safely remove the tracker. virtual bool flushIfNeeded( @@ -109,9 +119,12 @@ public: // Dump internal states for debugging virtual void dumpStates(FILE* out, bool verbose) const = 0; - void setEventKey(const MetricDimensionKey& eventKey) { - mEventKey = eventKey; - } + virtual int64_t getCurrentStateKeyDuration() const = 0; + + virtual int64_t getCurrentStateKeyFullBucketDuration() const = 0; + + // Replace old value with new value for the given state atom. + virtual void updateCurrentStateKey(const int32_t atomId, const FieldValue& newState) = 0; protected: int64_t getCurrentBucketEndTimeNs() const { @@ -140,10 +153,11 @@ protected: } } - void addPastBucketToAnomalyTrackers(const int64_t& bucketValue, const int64_t& bucketNum) { + void addPastBucketToAnomalyTrackers(const MetricDimensionKey eventKey, + const int64_t& bucketValue, const int64_t& bucketNum) { for (auto& anomalyTracker : mAnomalyTrackers) { if (anomalyTracker != nullptr) { - anomalyTracker->addPastBucket(mEventKey, bucketValue, bucketNum); + anomalyTracker->addPastBucket(eventKey, bucketValue, bucketNum); } } } @@ -164,6 +178,10 @@ protected: return mStartTimeNs + (mCurrentBucketNum + 1) * mBucketSizeNs; } + void setEventKey(const MetricDimensionKey& eventKey) { + mEventKey = eventKey; + } + // A reference to the DurationMetricProducer's config key. const ConfigKey& mConfigKey; @@ -183,7 +201,8 @@ protected: int64_t mDuration; // current recorded duration result (for partial bucket) - int64_t mDurationFullBucket; // Sum of past partial buckets in current full bucket. + // Recorded duration results for each state key in the current partial bucket. + std::unordered_map<HashableDimensionKey, DurationValues> mStateKeyDurationMap; int64_t mCurrentBucketNum; diff --git a/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.cpp b/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.cpp index 2be5855e90e6..ee4e1672411f 100644 --- a/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.cpp +++ b/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.cpp @@ -26,15 +26,14 @@ namespace statsd { MaxDurationTracker::MaxDurationTracker(const ConfigKey& key, const int64_t& id, const MetricDimensionKey& eventKey, - sp<ConditionWizard> wizard, int conditionIndex, - bool nesting, + sp<ConditionWizard> wizard, int conditionIndex, bool nesting, int64_t currentBucketStartNs, int64_t currentBucketNum, int64_t startTimeNs, int64_t bucketSizeNs, bool conditionSliced, bool fullLink, const vector<sp<DurationAnomalyTracker>>& anomalyTrackers) - : DurationTracker(key, id, eventKey, wizard, conditionIndex, nesting, - currentBucketStartNs, currentBucketNum, startTimeNs, bucketSizeNs, - conditionSliced, fullLink, anomalyTrackers) { + : DurationTracker(key, id, eventKey, wizard, conditionIndex, nesting, currentBucketStartNs, + currentBucketNum, startTimeNs, bucketSizeNs, conditionSliced, fullLink, + anomalyTrackers) { } bool MaxDurationTracker::hitGuardRail(const HashableDimensionKey& newKey) { @@ -91,7 +90,6 @@ void MaxDurationTracker::noteStart(const HashableDimensionKey& key, bool conditi } } - void MaxDurationTracker::noteStop(const HashableDimensionKey& key, const int64_t eventTime, bool forceStop) { VLOG("MaxDuration: key %s stop", key.toString().c_str()); @@ -240,6 +238,11 @@ void MaxDurationTracker::onSlicedConditionMayChange(bool overallCondition, } } +void MaxDurationTracker::onStateChanged(const int64_t timestamp, const int32_t atomId, + const FieldValue& newState) { + ALOGE("MaxDurationTracker does not handle sliced state changes."); +} + void MaxDurationTracker::onConditionChanged(bool condition, const int64_t timestamp) { for (auto& pair : mInfos) { noteConditionChanged(pair.first, condition, timestamp); @@ -309,6 +312,20 @@ void MaxDurationTracker::dumpStates(FILE* out, bool verbose) const { fprintf(out, "\t\t current duration %lld\n", (long long)mDuration); } +int64_t MaxDurationTracker::getCurrentStateKeyDuration() const { + ALOGE("MaxDurationTracker does not handle sliced state changes."); + return -1; +} + +int64_t MaxDurationTracker::getCurrentStateKeyFullBucketDuration() const { + ALOGE("MaxDurationTracker does not handle sliced state changes."); + return -1; +} + +void MaxDurationTracker::updateCurrentStateKey(const int32_t atomId, const FieldValue& newState) { + ALOGE("MaxDurationTracker does not handle sliced state changes."); +} + } // namespace statsd } // namespace os } // namespace android diff --git a/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.h b/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.h index efb8dc70afd1..2891c6e1138a 100644 --- a/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.h +++ b/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.h @@ -54,10 +54,19 @@ public: void onSlicedConditionMayChange(bool overallCondition, const int64_t timestamp) override; void onConditionChanged(bool condition, const int64_t timestamp) override; + void onStateChanged(const int64_t timestamp, const int32_t atomId, + const FieldValue& newState) override; + int64_t predictAnomalyTimestampNs(const DurationAnomalyTracker& anomalyTracker, const int64_t currentTimestamp) const override; void dumpStates(FILE* out, bool verbose) const override; + int64_t getCurrentStateKeyDuration() const override; + + int64_t getCurrentStateKeyFullBucketDuration() const override; + + void updateCurrentStateKey(const int32_t atomId, const FieldValue& newState); + private: // Returns true if at least one of the mInfos is started. bool anyStarted(); diff --git a/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.cpp b/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.cpp index 57f39656fdfe..19b2fe89989d 100644 --- a/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.cpp +++ b/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.cpp @@ -26,13 +26,12 @@ using std::pair; OringDurationTracker::OringDurationTracker( const ConfigKey& key, const int64_t& id, const MetricDimensionKey& eventKey, - sp<ConditionWizard> wizard, int conditionIndex, - bool nesting, int64_t currentBucketStartNs, int64_t currentBucketNum, - int64_t startTimeNs, int64_t bucketSizeNs, bool conditionSliced, bool fullLink, - const vector<sp<DurationAnomalyTracker>>& anomalyTrackers) - : DurationTracker(key, id, eventKey, wizard, conditionIndex, nesting, - currentBucketStartNs, currentBucketNum, startTimeNs, bucketSizeNs, - conditionSliced, fullLink, anomalyTrackers), + sp<ConditionWizard> wizard, int conditionIndex, bool nesting, int64_t currentBucketStartNs, + int64_t currentBucketNum, int64_t startTimeNs, int64_t bucketSizeNs, bool conditionSliced, + bool fullLink, const vector<sp<DurationAnomalyTracker>>& anomalyTrackers) + : DurationTracker(key, id, eventKey, wizard, conditionIndex, nesting, currentBucketStartNs, + currentBucketNum, startTimeNs, bucketSizeNs, conditionSliced, fullLink, + anomalyTrackers), mStarted(), mPaused() { mLastStartTime = 0; @@ -90,10 +89,14 @@ void OringDurationTracker::noteStop(const HashableDimensionKey& key, const int64 mConditionKeyMap.erase(key); } if (mStarted.empty()) { - mDuration += (timestamp - mLastStartTime); - detectAndDeclareAnomaly(timestamp, mCurrentBucketNum, mDuration + mDurationFullBucket); - VLOG("record duration %lld, total %lld ", (long long)timestamp - mLastStartTime, - (long long)mDuration); + mStateKeyDurationMap[mEventKey.getStateValuesKey()].mDuration += + (timestamp - mLastStartTime); + detectAndDeclareAnomaly( + timestamp, mCurrentBucketNum, + getCurrentStateKeyDuration() + getCurrentStateKeyFullBucketDuration()); + VLOG("record duration %lld, total duration %lld for state key %s", + (long long)timestamp - mLastStartTime, (long long)getCurrentStateKeyDuration(), + mEventKey.getStateValuesKey().toString().c_str()); } } @@ -112,10 +115,14 @@ void OringDurationTracker::noteStop(const HashableDimensionKey& key, const int64 void OringDurationTracker::noteStopAll(const int64_t timestamp) { if (!mStarted.empty()) { - mDuration += (timestamp - mLastStartTime); - VLOG("Oring Stop all: record duration %lld %lld ", (long long)timestamp - mLastStartTime, - (long long)mDuration); - detectAndDeclareAnomaly(timestamp, mCurrentBucketNum, mDuration + mDurationFullBucket); + mStateKeyDurationMap[mEventKey.getStateValuesKey()].mDuration += + (timestamp - mLastStartTime); + VLOG("Oring Stop all: record duration %lld, total duration %lld for state key %s", + (long long)timestamp - mLastStartTime, (long long)getCurrentStateKeyDuration(), + mEventKey.getStateValuesKey().toString().c_str()); + detectAndDeclareAnomaly( + timestamp, mCurrentBucketNum, + getCurrentStateKeyDuration() + getCurrentStateKeyFullBucketDuration()); } stopAnomalyAlarm(timestamp); @@ -146,21 +153,36 @@ bool OringDurationTracker::flushCurrentBucket( // Process the current bucket. if (mStarted.size() > 0) { - mDuration += (currentBucketEndTimeNs - mLastStartTime); + // Calculate the duration for the current state key. + mStateKeyDurationMap[mEventKey.getStateValuesKey()].mDuration += + (currentBucketEndTimeNs - mLastStartTime); } - if (mDuration > 0) { - DurationBucket current_info; - current_info.mBucketStartNs = mCurrentBucketStartTimeNs; - current_info.mBucketEndNs = currentBucketEndTimeNs; - current_info.mDuration = mDuration; - (*output)[mEventKey].push_back(current_info); - mDurationFullBucket += mDuration; - VLOG(" duration: %lld", (long long)current_info.mDuration); - } - if (eventTimeNs > fullBucketEnd) { - // End of full bucket, can send to anomaly tracker now. - addPastBucketToAnomalyTrackers(mDurationFullBucket, mCurrentBucketNum); - mDurationFullBucket = 0; + // Store DurationBucket info for each whatKey, stateKey pair. + // Note: The whatKey stored in mEventKey is constant for each DurationTracker, while the + // stateKey stored in mEventKey is only the current stateKey. mStateKeyDurationMap is used to + // store durations for each stateKey, so we need to flush the bucket by creating a + // DurationBucket for each stateKey. + for (auto& durationIt : mStateKeyDurationMap) { + if (durationIt.second.mDuration > 0) { + DurationBucket current_info; + current_info.mBucketStartNs = mCurrentBucketStartTimeNs; + current_info.mBucketEndNs = currentBucketEndTimeNs; + current_info.mDuration = durationIt.second.mDuration; + (*output)[MetricDimensionKey(mEventKey.getDimensionKeyInWhat(), durationIt.first)] + .push_back(current_info); + + durationIt.second.mDurationFullBucket += durationIt.second.mDuration; + VLOG(" duration: %lld", (long long)current_info.mDuration); + } + + if (eventTimeNs > fullBucketEnd) { + // End of full bucket, can send to anomaly tracker now. + addPastBucketToAnomalyTrackers( + MetricDimensionKey(mEventKey.getDimensionKeyInWhat(), durationIt.first), + getCurrentStateKeyFullBucketDuration(), mCurrentBucketNum); + durationIt.second.mDurationFullBucket = 0; + } + durationIt.second.mDuration = 0; } if (mStarted.size() > 0) { @@ -169,20 +191,19 @@ bool OringDurationTracker::flushCurrentBucket( info.mBucketStartNs = fullBucketEnd + mBucketSizeNs * (i - 1); info.mBucketEndNs = info.mBucketStartNs + mBucketSizeNs; info.mDuration = mBucketSizeNs; + // Full duration buckets are attributed to the current stateKey. (*output)[mEventKey].push_back(info); // Safe to send these buckets to anomaly tracker since they must be full buckets. // If it's a partial bucket, numBucketsForward would be 0. - addPastBucketToAnomalyTrackers(info.mDuration, mCurrentBucketNum + i); + addPastBucketToAnomalyTrackers(mEventKey, info.mDuration, mCurrentBucketNum + i); VLOG(" add filling bucket with duration %lld", (long long)info.mDuration); } } else { if (numBucketsForward >= 2) { - addPastBucketToAnomalyTrackers(0, mCurrentBucketNum + numBucketsForward - 1); + addPastBucketToAnomalyTrackers(mEventKey, 0, mCurrentBucketNum + numBucketsForward - 1); } } - mDuration = 0; - if (numBucketsForward > 0) { mCurrentBucketStartTimeNs = fullBucketEnd + (numBucketsForward - 1) * mBucketSizeNs; mCurrentBucketNum += numBucketsForward; @@ -229,10 +250,14 @@ void OringDurationTracker::onSlicedConditionMayChange(bool overallCondition, } if (mStarted.empty()) { - mDuration += (timestamp - mLastStartTime); - VLOG("Duration add %lld , to %lld ", (long long)(timestamp - mLastStartTime), - (long long)mDuration); - detectAndDeclareAnomaly(timestamp, mCurrentBucketNum, mDuration + mDurationFullBucket); + mStateKeyDurationMap[mEventKey.getStateValuesKey()].mDuration += + (timestamp - mLastStartTime); + VLOG("record duration %lld, total duration %lld for state key %s", + (long long)(timestamp - mLastStartTime), (long long)getCurrentStateKeyDuration(), + mEventKey.getStateValuesKey().toString().c_str()); + detectAndDeclareAnomaly( + timestamp, mCurrentBucketNum, + getCurrentStateKeyDuration() + getCurrentStateKeyFullBucketDuration()); } } @@ -288,10 +313,13 @@ void OringDurationTracker::onConditionChanged(bool condition, const int64_t time } else { if (!mStarted.empty()) { VLOG("Condition false, all paused"); - mDuration += (timestamp - mLastStartTime); + mStateKeyDurationMap[mEventKey.getStateValuesKey()].mDuration += + (timestamp - mLastStartTime); mPaused.insert(mStarted.begin(), mStarted.end()); mStarted.clear(); - detectAndDeclareAnomaly(timestamp, mCurrentBucketNum, mDuration + mDurationFullBucket); + detectAndDeclareAnomaly( + timestamp, mCurrentBucketNum, + getCurrentStateKeyDuration() + getCurrentStateKeyFullBucketDuration()); } } if (mStarted.empty()) { @@ -299,6 +327,20 @@ void OringDurationTracker::onConditionChanged(bool condition, const int64_t time } } +void OringDurationTracker::onStateChanged(const int64_t timestamp, const int32_t atomId, + const FieldValue& newState) { + // If no keys are being tracked, update the current state key and return. + if (mStarted.empty()) { + updateCurrentStateKey(atomId, newState); + return; + } + // Add the current duration length to the previous state key and then update + // the last start time and current state key. + mStateKeyDurationMap[mEventKey.getStateValuesKey()].mDuration += (timestamp - mLastStartTime); + mLastStartTime = timestamp; + updateCurrentStateKey(atomId, newState); +} + int64_t OringDurationTracker::predictAnomalyTimestampNs( const DurationAnomalyTracker& anomalyTracker, const int64_t eventTimestampNs) const { @@ -308,12 +350,13 @@ int64_t OringDurationTracker::predictAnomalyTimestampNs( // The timestamp of the current bucket end. const int64_t currentBucketEndNs = getCurrentBucketEndTimeNs(); - // The past duration ns for the current bucket. - int64_t currentBucketPastNs = mDuration + mDurationFullBucket; + // The past duration ns for the current bucket of the current stateKey. + int64_t currentStateBucketPastNs = + getCurrentStateKeyDuration() + getCurrentStateKeyFullBucketDuration(); // As we move into the future, old buckets get overwritten (so their old data is erased). // Sum of past durations. Will change as we overwrite old buckets. - int64_t pastNs = currentBucketPastNs + anomalyTracker.getSumOverPastBuckets(mEventKey); + int64_t pastNs = currentStateBucketPastNs + anomalyTracker.getSumOverPastBuckets(mEventKey); // The refractory period end timestamp for dimension mEventKey. const int64_t refractoryPeriodEndNs = @@ -372,7 +415,7 @@ int64_t OringDurationTracker::predictAnomalyTimestampNs( mEventKey, mCurrentBucketNum - anomalyTracker.getNumOfPastBuckets() + futureBucketIdx); } else if (futureBucketIdx == anomalyTracker.getNumOfPastBuckets()) { - pastNs -= (currentBucketPastNs + (currentBucketEndNs - eventTimestampNs)); + pastNs -= (currentStateBucketPastNs + (currentBucketEndNs - eventTimestampNs)); } } @@ -382,7 +425,34 @@ int64_t OringDurationTracker::predictAnomalyTimestampNs( void OringDurationTracker::dumpStates(FILE* out, bool verbose) const { fprintf(out, "\t\t started count %lu\n", (unsigned long)mStarted.size()); fprintf(out, "\t\t paused count %lu\n", (unsigned long)mPaused.size()); - fprintf(out, "\t\t current duration %lld\n", (long long)mDuration); + fprintf(out, "\t\t current duration %lld\n", (long long)getCurrentStateKeyDuration()); +} + +int64_t OringDurationTracker::getCurrentStateKeyDuration() const { + auto it = mStateKeyDurationMap.find(mEventKey.getStateValuesKey()); + if (it == mStateKeyDurationMap.end()) { + return 0; + } else { + return it->second.mDuration; + } +} + +int64_t OringDurationTracker::getCurrentStateKeyFullBucketDuration() const { + auto it = mStateKeyDurationMap.find(mEventKey.getStateValuesKey()); + if (it == mStateKeyDurationMap.end()) { + return 0; + } else { + return it->second.mDurationFullBucket; + } +} + +void OringDurationTracker::updateCurrentStateKey(const int32_t atomId, const FieldValue& newState) { + HashableDimensionKey* stateValuesKey = mEventKey.getMutableStateValuesKey(); + for (size_t i = 0; i < stateValuesKey->getValues().size(); i++) { + if (stateValuesKey->getValues()[i].mField.getTag() == atomId) { + stateValuesKey->mutableValue(i)->mValue = newState.mValue; + } + } } } // namespace statsd diff --git a/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.h b/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.h index f44e3275b83d..bd8017a7decd 100644 --- a/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.h +++ b/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.h @@ -28,10 +28,9 @@ class OringDurationTracker : public DurationTracker { public: OringDurationTracker(const ConfigKey& key, const int64_t& id, const MetricDimensionKey& eventKey, sp<ConditionWizard> wizard, - int conditionIndex, - bool nesting, int64_t currentBucketStartNs, int64_t currentBucketNum, - int64_t startTimeNs, int64_t bucketSizeNs, bool conditionSliced, - bool fullLink, + int conditionIndex, bool nesting, int64_t currentBucketStartNs, + int64_t currentBucketNum, int64_t startTimeNs, int64_t bucketSizeNs, + bool conditionSliced, bool fullLink, const std::vector<sp<DurationAnomalyTracker>>& anomalyTrackers); OringDurationTracker(const OringDurationTracker& tracker) = default; @@ -45,6 +44,9 @@ public: void onSlicedConditionMayChange(bool overallCondition, const int64_t timestamp) override; void onConditionChanged(bool condition, const int64_t timestamp) override; + void onStateChanged(const int64_t timestamp, const int32_t atomId, + const FieldValue& newState) override; + bool flushCurrentBucket( const int64_t& eventTimeNs, std::unordered_map<MetricDimensionKey, std::vector<DurationBucket>>* output) override; @@ -56,6 +58,12 @@ public: const int64_t currentTimestamp) const override; void dumpStates(FILE* out, bool verbose) const override; + int64_t getCurrentStateKeyDuration() const override; + + int64_t getCurrentStateKeyFullBucketDuration() const override; + + void updateCurrentStateKey(const int32_t atomId, const FieldValue& newState); + private: // We don't need to keep track of individual durations. The information that's needed is: // 1) which keys are started. We record the first start time. diff --git a/cmds/statsd/src/metrics/metrics_manager_util.cpp b/cmds/statsd/src/metrics/metrics_manager_util.cpp index 3810c486cf88..0d0788e05e0a 100644 --- a/cmds/statsd/src/metrics/metrics_manager_util.cpp +++ b/cmds/statsd/src/metrics/metrics_manager_util.cpp @@ -564,6 +564,33 @@ bool initMetrics(const ConfigKey& key, const StatsdConfig& config, const int64_t } } + std::vector<int> slicedStateAtoms; + unordered_map<int, unordered_map<int, int64_t>> stateGroupMap; + if (metric.slice_by_state_size() > 0) { + if (metric.aggregation_type() == DurationMetric::MAX_SPARSE) { + ALOGE("DurationMetric with aggregation type MAX_SPARSE cannot be sliced by state"); + return false; + } + if (!handleMetricWithStates(config, metric.slice_by_state(), stateAtomIdMap, + allStateGroupMaps, slicedStateAtoms, stateGroupMap)) { + return false; + } + } else { + if (metric.state_link_size() > 0) { + ALOGW("DurationMetric has a MetricStateLink but doesn't have a sliced state"); + return false; + } + } + + // Check that all metric state links are a subset of dimensions_in_what fields. + std::vector<Matcher> dimensionsInWhat; + translateFieldMatcher(metric.dimensions_in_what(), &dimensionsInWhat); + for (const auto& stateLink : metric.state_link()) { + if (!handleMetricWithStateLink(stateLink.fields_in_what(), dimensionsInWhat)) { + return false; + } + } + unordered_map<int, shared_ptr<Activation>> eventActivationMap; unordered_map<int, vector<shared_ptr<Activation>>> eventDeactivationMap; bool success = handleMetricActivation(config, metric.id(), metricIndex, @@ -575,7 +602,8 @@ bool initMetrics(const ConfigKey& key, const StatsdConfig& config, const int64_t sp<MetricProducer> durationMetric = new DurationMetricProducer( key, metric, conditionIndex, trackerIndices[0], trackerIndices[1], trackerIndices[2], nesting, wizard, internalDimensions, timeBaseTimeNs, - currentTimeNs, eventActivationMap, eventDeactivationMap); + currentTimeNs, eventActivationMap, eventDeactivationMap, slicedStateAtoms, + stateGroupMap); allMetricProducers.push_back(durationMetric); } diff --git a/cmds/statsd/src/stats_log.proto b/cmds/statsd/src/stats_log.proto index c45274e4a3de..ed98f50bcc48 100644 --- a/cmds/statsd/src/stats_log.proto +++ b/cmds/statsd/src/stats_log.proto @@ -103,12 +103,14 @@ message DurationBucketInfo { message DurationMetricData { optional DimensionsValue dimensions_in_what = 1; - optional DimensionsValue dimensions_in_condition = 2 [deprecated = true]; + repeated StateValue slice_by_state = 6; repeated DurationBucketInfo bucket_info = 3; repeated DimensionsValue dimension_leaf_values_in_what = 4; + optional DimensionsValue dimensions_in_condition = 2 [deprecated = true]; + repeated DimensionsValue dimension_leaf_values_in_condition = 5 [deprecated = true]; } diff --git a/cmds/statsd/src/statsd_config.proto b/cmds/statsd/src/statsd_config.proto index 83d9484c77ba..c7407bd9af1e 100644 --- a/cmds/statsd/src/statsd_config.proto +++ b/cmds/statsd/src/statsd_config.proto @@ -227,8 +227,12 @@ message DurationMetric { optional int64 condition = 3; + repeated int64 slice_by_state = 9; + repeated MetricConditionLink links = 4; + repeated MetricStateLink state_link = 10; + enum AggregationType { SUM = 1; @@ -238,9 +242,9 @@ message DurationMetric { optional FieldMatcher dimensions_in_what = 6; - optional FieldMatcher dimensions_in_condition = 8 [deprecated = true]; - optional TimeUnit bucket = 7; + + optional FieldMatcher dimensions_in_condition = 8 [deprecated = true]; } message GaugeMetric { diff --git a/cmds/statsd/tests/HashableDimensionKey_test.cpp b/cmds/statsd/tests/HashableDimensionKey_test.cpp new file mode 100644 index 000000000000..29adcd08a7b8 --- /dev/null +++ b/cmds/statsd/tests/HashableDimensionKey_test.cpp @@ -0,0 +1,137 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "src/HashableDimensionKey.h" + +#include <gtest/gtest.h> + +#include "frameworks/base/cmds/statsd/src/statsd_config.pb.h" +#include "statsd_test_util.h" + +#ifdef __ANDROID__ + +using android::util::ProtoReader; + +namespace android { +namespace os { +namespace statsd { + +/** + * Test that #containsLinkedStateValues returns false when the whatKey is + * smaller than the primaryKey. + */ +TEST(HashableDimensionKeyTest, TestContainsLinkedStateValues_WhatKeyTooSmall) { + std::vector<Metric2State> mMetric2StateLinks; + + int32_t uid1 = 1000; + HashableDimensionKey whatKey = DEFAULT_DIMENSION_KEY; + HashableDimensionKey primaryKey; + getUidProcessKey(uid1, &primaryKey); + + EXPECT_FALSE(containsLinkedStateValues(whatKey, primaryKey, mMetric2StateLinks, + UID_PROCESS_STATE_ATOM_ID)); +} + +/** + * Test that #containsLinkedStateValues returns false when the linked values + * are not equal. + */ +TEST(HashableDimensionKeyTest, TestContainsLinkedStateValues_UnequalLinkedValues) { + int stateAtomId = UID_PROCESS_STATE_ATOM_ID; + + FieldMatcher whatMatcher; + whatMatcher.set_field(util::OVERLAY_STATE_CHANGED); + FieldMatcher* child11 = whatMatcher.add_child(); + child11->set_field(1); + + FieldMatcher stateMatcher; + stateMatcher.set_field(stateAtomId); + FieldMatcher* child21 = stateMatcher.add_child(); + child21->set_field(1); + + std::vector<Metric2State> mMetric2StateLinks; + Metric2State ms; + ms.stateAtomId = stateAtomId; + translateFieldMatcher(whatMatcher, &ms.metricFields); + translateFieldMatcher(stateMatcher, &ms.stateFields); + mMetric2StateLinks.push_back(ms); + + int32_t uid1 = 1000; + int32_t uid2 = 1001; + HashableDimensionKey whatKey; + getOverlayKey(uid2, "package", &whatKey); + HashableDimensionKey primaryKey; + getUidProcessKey(uid1, &primaryKey); + + EXPECT_FALSE(containsLinkedStateValues(whatKey, primaryKey, mMetric2StateLinks, stateAtomId)); +} + +/** + * Test that #containsLinkedStateValues returns false when there is no link + * between the key values. + */ +TEST(HashableDimensionKeyTest, TestContainsLinkedStateValues_MissingMetric2StateLinks) { + int stateAtomId = UID_PROCESS_STATE_ATOM_ID; + + std::vector<Metric2State> mMetric2StateLinks; + + int32_t uid1 = 1000; + HashableDimensionKey whatKey; + getOverlayKey(uid1, "package", &whatKey); + HashableDimensionKey primaryKey; + getUidProcessKey(uid1, &primaryKey); + + EXPECT_FALSE(containsLinkedStateValues(whatKey, primaryKey, mMetric2StateLinks, stateAtomId)); +} + +/** + * Test that #containsLinkedStateValues returns true when the key values are + * linked and equal. + */ +TEST(HashableDimensionKeyTest, TestContainsLinkedStateValues_AllConditionsMet) { + int stateAtomId = UID_PROCESS_STATE_ATOM_ID; + + FieldMatcher whatMatcher; + whatMatcher.set_field(util::OVERLAY_STATE_CHANGED); + FieldMatcher* child11 = whatMatcher.add_child(); + child11->set_field(1); + + FieldMatcher stateMatcher; + stateMatcher.set_field(stateAtomId); + FieldMatcher* child21 = stateMatcher.add_child(); + child21->set_field(1); + + std::vector<Metric2State> mMetric2StateLinks; + Metric2State ms; + ms.stateAtomId = stateAtomId; + translateFieldMatcher(whatMatcher, &ms.metricFields); + translateFieldMatcher(stateMatcher, &ms.stateFields); + mMetric2StateLinks.push_back(ms); + + int32_t uid1 = 1000; + HashableDimensionKey whatKey; + getOverlayKey(uid1, "package", &whatKey); + HashableDimensionKey primaryKey; + getUidProcessKey(uid1, &primaryKey); + + EXPECT_TRUE(containsLinkedStateValues(whatKey, primaryKey, mMetric2StateLinks, stateAtomId)); +} + +} // namespace statsd +} // namespace os +} // namespace android +#else +GTEST_LOG_(INFO) << "This test does nothing.\n"; +#endif diff --git a/cmds/statsd/tests/e2e/DurationMetric_e2e_test.cpp b/cmds/statsd/tests/e2e/DurationMetric_e2e_test.cpp index ae2a0f50d6ca..2659944684e1 100644 --- a/cmds/statsd/tests/e2e/DurationMetric_e2e_test.cpp +++ b/cmds/statsd/tests/e2e/DurationMetric_e2e_test.cpp @@ -14,12 +14,13 @@ #include <gtest/gtest.h> +#include <vector> + #include "src/StatsLogProcessor.h" +#include "src/state/StateTracker.h" #include "src/stats_log_util.h" #include "tests/statsd_test_util.h" -#include <vector> - namespace android { namespace os { namespace statsd { @@ -101,7 +102,7 @@ TEST(DurationMetricE2eTest, TestOneBucket) { reports.reports(0).metrics(0).duration_metrics(); EXPECT_EQ(1, durationMetrics.data_size()); - auto data = durationMetrics.data(0); + DurationMetricData data = durationMetrics.data(0); EXPECT_EQ(1, data.bucket_info_size()); EXPECT_EQ(durationEndNs - durationStartNs, data.bucket_info(0).duration_nanos()); EXPECT_EQ(configAddedTimeNs, data.bucket_info(0).start_bucket_elapsed_nanos()); @@ -183,7 +184,7 @@ TEST(DurationMetricE2eTest, TestTwoBuckets) { reports.reports(0).metrics(0).duration_metrics(); EXPECT_EQ(1, durationMetrics.data_size()); - auto data = durationMetrics.data(0); + DurationMetricData data = durationMetrics.data(0); EXPECT_EQ(1, data.bucket_info_size()); auto bucketInfo = data.bucket_info(0); @@ -353,7 +354,7 @@ TEST(DurationMetricE2eTest, TestWithActivation) { reports.reports(0).metrics(0).duration_metrics(); EXPECT_EQ(1, durationMetrics.data_size()); - auto data = durationMetrics.data(0); + DurationMetricData data = durationMetrics.data(0); EXPECT_EQ(1, data.bucket_info_size()); auto bucketInfo = data.bucket_info(0); @@ -434,7 +435,7 @@ TEST(DurationMetricE2eTest, TestWithCondition) { EXPECT_EQ(1, reports.reports(0).metrics_size()); EXPECT_EQ(1, reports.reports(0).metrics(0).duration_metrics().data_size()); - auto data = reports.reports(0).metrics(0).duration_metrics().data(0); + DurationMetricData data = reports.reports(0).metrics(0).duration_metrics().data(0); // Validate bucket info. EXPECT_EQ(1, data.bucket_info_size()); @@ -533,7 +534,7 @@ TEST(DurationMetricE2eTest, TestWithSlicedCondition) { EXPECT_EQ(1, reports.reports(0).metrics_size()); EXPECT_EQ(1, reports.reports(0).metrics(0).duration_metrics().data_size()); - auto data = reports.reports(0).metrics(0).duration_metrics().data(0); + DurationMetricData data = reports.reports(0).metrics(0).duration_metrics().data(0); // Validate dimension value. ValidateAttributionUidDimension(data.dimensions_in_what(), util::WAKELOCK_STATE_CHANGED, appUid); @@ -691,7 +692,7 @@ TEST(DurationMetricE2eTest, TestWithActivationAndSlicedCondition) { EXPECT_EQ(1, reports.reports(0).metrics_size()); EXPECT_EQ(1, reports.reports(0).metrics(0).duration_metrics().data_size()); - auto data = reports.reports(0).metrics(0).duration_metrics().data(0); + DurationMetricData data = reports.reports(0).metrics(0).duration_metrics().data(0); // Validate dimension value. ValidateAttributionUidDimension(data.dimensions_in_what(), util::WAKELOCK_STATE_CHANGED, appUid); @@ -709,6 +710,734 @@ TEST(DurationMetricE2eTest, TestWithActivationAndSlicedCondition) { EXPECT_EQ(bucketStartTimeNs + bucketSizeNs - duration2StartNs, bucketInfo.duration_nanos()); } +TEST(DurationMetricE2eTest, TestWithSlicedState) { + // Initialize config. + StatsdConfig config; + config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root. + + *config.add_atom_matcher() = CreateBatterySaverModeStartAtomMatcher(); + *config.add_atom_matcher() = CreateBatterySaverModeStopAtomMatcher(); + + auto batterySaverModePredicate = CreateBatterySaverModePredicate(); + *config.add_predicate() = batterySaverModePredicate; + + auto screenState = CreateScreenState(); + *config.add_state() = screenState; + + // Create duration metric that slices by screen state. + auto durationMetric = config.add_duration_metric(); + durationMetric->set_id(StringToId("DurationBatterySaverModeSliceScreen")); + durationMetric->set_what(batterySaverModePredicate.id()); + durationMetric->add_slice_by_state(screenState.id()); + durationMetric->set_aggregation_type(DurationMetric::SUM); + durationMetric->set_bucket(FIVE_MINUTES); + + // Initialize StatsLogProcessor. + int uid = 12345; + int64_t cfgId = 98765; + ConfigKey cfgKey(uid, cfgId); + uint64_t bucketStartTimeNs = 10000000000; // 0:10 + uint64_t bucketSizeNs = + TimeUnitToBucketSizeInMillis(config.duration_metric(0).bucket()) * 1000000LL; + auto processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey); + + EXPECT_EQ(processor->mMetricsManagers.size(), 1u); + sp<MetricsManager> metricsManager = processor->mMetricsManagers.begin()->second; + EXPECT_TRUE(metricsManager->isConfigValid()); + EXPECT_EQ(metricsManager->mAllMetricProducers.size(), 1); + EXPECT_TRUE(metricsManager->isActive()); + sp<MetricProducer> metricProducer = metricsManager->mAllMetricProducers[0]; + EXPECT_TRUE(metricProducer->mIsActive); + EXPECT_EQ(metricProducer->mSlicedStateAtoms.size(), 1); + EXPECT_EQ(metricProducer->mSlicedStateAtoms.at(0), SCREEN_STATE_ATOM_ID); + EXPECT_EQ(metricProducer->mStateGroupMap.size(), 0); + + // Check that StateTrackers were initialized correctly. + EXPECT_EQ(1, StateManager::getInstance().getStateTrackersCount()); + EXPECT_EQ(1, StateManager::getInstance().getListenersCount(SCREEN_STATE_ATOM_ID)); + + /* + bucket #1 bucket #2 + | 1 2 3 4 5 6 7 8 9 10 (minutes) + |-----------------------------|-----------------------------|-- + ON OFF ON (BatterySaverMode) + | | | (ScreenIsOnEvent) + | | (ScreenIsOffEvent) + | (ScreenDozeEvent) + */ + // Initialize log events. + std::vector<std::unique_ptr<LogEvent>> events; + events.push_back(CreateScreenStateChangedEvent( + bucketStartTimeNs + 10 * NS_PER_SEC, + android::view::DisplayStateEnum::DISPLAY_STATE_ON)); // 0:20 + events.push_back(CreateBatterySaverOnEvent(bucketStartTimeNs + 20 * NS_PER_SEC)); // 0:30 + events.push_back(CreateScreenStateChangedEvent( + bucketStartTimeNs + 50 * NS_PER_SEC, + android::view::DisplayStateEnum::DISPLAY_STATE_OFF)); // 1:00 + events.push_back(CreateScreenStateChangedEvent( + bucketStartTimeNs + 80 * NS_PER_SEC, + android::view::DisplayStateEnum::DISPLAY_STATE_DOZE)); // 1:30 + events.push_back(CreateScreenStateChangedEvent( + bucketStartTimeNs + 120 * NS_PER_SEC, + android::view::DisplayStateEnum::DISPLAY_STATE_ON)); // 2:10 + events.push_back(CreateBatterySaverOffEvent(bucketStartTimeNs + 200 * NS_PER_SEC)); // 3:30 + events.push_back(CreateScreenStateChangedEvent( + bucketStartTimeNs + 250 * NS_PER_SEC, + android::view::DisplayStateEnum::DISPLAY_STATE_OFF)); // 4:20 + events.push_back(CreateBatterySaverOnEvent(bucketStartTimeNs + 280 * NS_PER_SEC)); // 4:50 + + // Bucket boundary. + events.push_back(CreateScreenStateChangedEvent( + bucketStartTimeNs + 310 * NS_PER_SEC, + android::view::DisplayStateEnum::DISPLAY_STATE_ON)); // 5:20 + + // Send log events to StatsLogProcessor. + for (auto& event : events) { + processor->OnLogEvent(event.get()); + } + + // Check dump report. + vector<uint8_t> buffer; + ConfigMetricsReportList reports; + processor->onDumpReport(cfgKey, bucketStartTimeNs + 360 * NS_PER_SEC, + true /* include current partial bucket */, true, ADB_DUMP, FAST, + &buffer); // 6:10 + EXPECT_GT(buffer.size(), 0); + EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size())); + backfillDimensionPath(&reports); + backfillStringInReport(&reports); + backfillStartEndTimestamp(&reports); + + EXPECT_EQ(1, reports.reports_size()); + EXPECT_EQ(1, reports.reports(0).metrics_size()); + EXPECT_TRUE(reports.reports(0).metrics(0).has_duration_metrics()); + EXPECT_EQ(3, reports.reports(0).metrics(0).duration_metrics().data_size()); + + DurationMetricData data = reports.reports(0).metrics(0).duration_metrics().data(0); + EXPECT_EQ(1, data.slice_by_state_size()); + EXPECT_EQ(SCREEN_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); + EXPECT_TRUE(data.slice_by_state(0).has_value()); + EXPECT_EQ(android::view::DisplayStateEnum::DISPLAY_STATE_OFF, data.slice_by_state(0).value()); + EXPECT_EQ(2, data.bucket_info_size()); + EXPECT_EQ(50 * NS_PER_SEC, data.bucket_info(0).duration_nanos()); + EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).start_bucket_elapsed_nanos()); + EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(0).end_bucket_elapsed_nanos()); + EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(1).duration_nanos()); + EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(1).start_bucket_elapsed_nanos()); + EXPECT_EQ(370 * NS_PER_SEC, data.bucket_info(1).end_bucket_elapsed_nanos()); + + data = reports.reports(0).metrics(0).duration_metrics().data(1); + EXPECT_EQ(1, data.slice_by_state_size()); + EXPECT_EQ(SCREEN_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); + EXPECT_TRUE(data.slice_by_state(0).has_value()); + EXPECT_EQ(android::view::DisplayStateEnum::DISPLAY_STATE_ON, data.slice_by_state(0).value()); + EXPECT_EQ(2, data.bucket_info_size()); + EXPECT_EQ(110 * NS_PER_SEC, data.bucket_info(0).duration_nanos()); + EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).start_bucket_elapsed_nanos()); + EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(0).end_bucket_elapsed_nanos()); + EXPECT_EQ(50 * NS_PER_SEC, data.bucket_info(1).duration_nanos()); + EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(1).start_bucket_elapsed_nanos()); + EXPECT_EQ(370 * NS_PER_SEC, data.bucket_info(1).end_bucket_elapsed_nanos()); + + data = reports.reports(0).metrics(0).duration_metrics().data(2); + EXPECT_EQ(1, data.slice_by_state_size()); + EXPECT_EQ(SCREEN_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); + EXPECT_TRUE(data.slice_by_state(0).has_value()); + EXPECT_EQ(android::view::DisplayStateEnum::DISPLAY_STATE_DOZE, data.slice_by_state(0).value()); + EXPECT_EQ(1, data.bucket_info_size()); + EXPECT_EQ(40 * NS_PER_SEC, data.bucket_info(0).duration_nanos()); + EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).start_bucket_elapsed_nanos()); + EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(0).end_bucket_elapsed_nanos()); +} + +TEST(DurationMetricE2eTest, TestWithConditionAndSlicedState) { + // Initialize config. + StatsdConfig config; + config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root. + + *config.add_atom_matcher() = CreateBatterySaverModeStartAtomMatcher(); + *config.add_atom_matcher() = CreateBatterySaverModeStopAtomMatcher(); + *config.add_atom_matcher() = CreateBatteryStateNoneMatcher(); + *config.add_atom_matcher() = CreateBatteryStateUsbMatcher(); + + auto batterySaverModePredicate = CreateBatterySaverModePredicate(); + *config.add_predicate() = batterySaverModePredicate; + + auto deviceUnpluggedPredicate = CreateDeviceUnpluggedPredicate(); + *config.add_predicate() = deviceUnpluggedPredicate; + + auto screenState = CreateScreenState(); + *config.add_state() = screenState; + + // Create duration metric that has a condition and slices by screen state. + auto durationMetric = config.add_duration_metric(); + durationMetric->set_id(StringToId("DurationBatterySaverModeOnBatterySliceScreen")); + durationMetric->set_what(batterySaverModePredicate.id()); + durationMetric->set_condition(deviceUnpluggedPredicate.id()); + durationMetric->add_slice_by_state(screenState.id()); + durationMetric->set_aggregation_type(DurationMetric::SUM); + durationMetric->set_bucket(FIVE_MINUTES); + + // Initialize StatsLogProcessor. + int uid = 12345; + int64_t cfgId = 98765; + ConfigKey cfgKey(uid, cfgId); + uint64_t bucketStartTimeNs = 10000000000; // 0:10 + uint64_t bucketSizeNs = + TimeUnitToBucketSizeInMillis(config.duration_metric(0).bucket()) * 1000000LL; + auto processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey); + + EXPECT_EQ(processor->mMetricsManagers.size(), 1u); + sp<MetricsManager> metricsManager = processor->mMetricsManagers.begin()->second; + EXPECT_TRUE(metricsManager->isConfigValid()); + EXPECT_EQ(metricsManager->mAllMetricProducers.size(), 1); + EXPECT_TRUE(metricsManager->isActive()); + sp<MetricProducer> metricProducer = metricsManager->mAllMetricProducers[0]; + EXPECT_TRUE(metricProducer->mIsActive); + EXPECT_EQ(metricProducer->mSlicedStateAtoms.size(), 1); + EXPECT_EQ(metricProducer->mSlicedStateAtoms.at(0), SCREEN_STATE_ATOM_ID); + EXPECT_EQ(metricProducer->mStateGroupMap.size(), 0); + + // Check that StateTrackers were initialized correctly. + EXPECT_EQ(1, StateManager::getInstance().getStateTrackersCount()); + EXPECT_EQ(1, StateManager::getInstance().getListenersCount(SCREEN_STATE_ATOM_ID)); + + /* + bucket #1 bucket #2 + | 1 2 3 4 5 6 7 8 (minutes) + |---------------------------------------|------------------ + ON OFF ON (BatterySaverMode) + T F T (DeviceUnpluggedPredicate) + | | | (ScreenIsOnEvent) + | | | (ScreenIsOffEvent) + | (ScreenDozeEvent) + */ + // Initialize log events. + std::vector<std::unique_ptr<LogEvent>> events; + events.push_back(CreateBatterySaverOnEvent(bucketStartTimeNs + 20 * NS_PER_SEC)); // 0:30 + events.push_back(CreateScreenStateChangedEvent( + bucketStartTimeNs + 60 * NS_PER_SEC, + android::view::DisplayStateEnum::DISPLAY_STATE_ON)); // 1:10 + events.push_back(CreateScreenStateChangedEvent( + bucketStartTimeNs + 80 * NS_PER_SEC, + android::view::DisplayStateEnum::DISPLAY_STATE_OFF)); // 1:30 + events.push_back( + CreateBatteryStateChangedEvent(bucketStartTimeNs + 110 * NS_PER_SEC, + BatteryPluggedStateEnum::BATTERY_PLUGGED_NONE)); // 2:00 + events.push_back(CreateScreenStateChangedEvent( + bucketStartTimeNs + 145 * NS_PER_SEC, + android::view::DisplayStateEnum::DISPLAY_STATE_ON)); // 2:35 + events.push_back(CreateScreenStateChangedEvent( + bucketStartTimeNs + 170 * NS_PER_SEC, + android::view::DisplayStateEnum::DISPLAY_STATE_OFF)); // 3:00 + events.push_back( + CreateBatteryStateChangedEvent(bucketStartTimeNs + 180 * NS_PER_SEC, + BatteryPluggedStateEnum::BATTERY_PLUGGED_USB)); // 3:10 + events.push_back(CreateScreenStateChangedEvent( + bucketStartTimeNs + 200 * NS_PER_SEC, + android::view::DisplayStateEnum::DISPLAY_STATE_DOZE)); // 3:30 + events.push_back( + CreateBatteryStateChangedEvent(bucketStartTimeNs + 230 * NS_PER_SEC, + BatteryPluggedStateEnum::BATTERY_PLUGGED_NONE)); // 4:00 + events.push_back(CreateScreenStateChangedEvent( + bucketStartTimeNs + 260 * NS_PER_SEC, + android::view::DisplayStateEnum::DISPLAY_STATE_ON)); // 4:30 + events.push_back(CreateBatterySaverOffEvent(bucketStartTimeNs + 280 * NS_PER_SEC)); // 4:50 + + // Bucket boundary. + events.push_back(CreateBatterySaverOnEvent(bucketStartTimeNs + 320 * NS_PER_SEC)); // 5:30 + events.push_back(CreateScreenStateChangedEvent( + bucketStartTimeNs + 380 * NS_PER_SEC, + android::view::DisplayStateEnum::DISPLAY_STATE_OFF)); // 6:30 + + // Send log events to StatsLogProcessor. + for (auto& event : events) { + processor->OnLogEvent(event.get()); + } + + // Check dump report. + vector<uint8_t> buffer; + ConfigMetricsReportList reports; + processor->onDumpReport(cfgKey, bucketStartTimeNs + 410 * NS_PER_SEC, + true /* include current partial bucket */, true, ADB_DUMP, FAST, + &buffer); + EXPECT_GT(buffer.size(), 0); + EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size())); + backfillDimensionPath(&reports); + backfillStringInReport(&reports); + backfillStartEndTimestamp(&reports); + + EXPECT_EQ(1, reports.reports_size()); + EXPECT_EQ(1, reports.reports(0).metrics_size()); + EXPECT_TRUE(reports.reports(0).metrics(0).has_duration_metrics()); + EXPECT_EQ(3, reports.reports(0).metrics(0).duration_metrics().data_size()); + + DurationMetricData data = reports.reports(0).metrics(0).duration_metrics().data(0); + EXPECT_EQ(1, data.slice_by_state_size()); + EXPECT_EQ(SCREEN_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); + EXPECT_TRUE(data.slice_by_state(0).has_value()); + EXPECT_EQ(android::view::DisplayStateEnum::DISPLAY_STATE_OFF, data.slice_by_state(0).value()); + EXPECT_EQ(2, data.bucket_info_size()); + EXPECT_EQ(45 * NS_PER_SEC, data.bucket_info(0).duration_nanos()); + EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).start_bucket_elapsed_nanos()); + EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(0).end_bucket_elapsed_nanos()); + EXPECT_EQ(30 * NS_PER_SEC, data.bucket_info(1).duration_nanos()); + EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(1).start_bucket_elapsed_nanos()); + EXPECT_EQ(420 * NS_PER_SEC, data.bucket_info(1).end_bucket_elapsed_nanos()); + + data = reports.reports(0).metrics(0).duration_metrics().data(2); + EXPECT_EQ(1, data.slice_by_state_size()); + EXPECT_EQ(SCREEN_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); + EXPECT_TRUE(data.slice_by_state(0).has_value()); + EXPECT_EQ(android::view::DisplayStateEnum::DISPLAY_STATE_ON, data.slice_by_state(0).value()); + EXPECT_EQ(2, data.bucket_info_size()); + EXPECT_EQ(45 * NS_PER_SEC, data.bucket_info(0).duration_nanos()); + EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).start_bucket_elapsed_nanos()); + EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(0).end_bucket_elapsed_nanos()); + EXPECT_EQ(60 * NS_PER_SEC, data.bucket_info(1).duration_nanos()); + EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(1).start_bucket_elapsed_nanos()); + EXPECT_EQ(420 * NS_PER_SEC, data.bucket_info(1).end_bucket_elapsed_nanos()); + + data = reports.reports(0).metrics(0).duration_metrics().data(1); + EXPECT_EQ(1, data.slice_by_state_size()); + EXPECT_EQ(SCREEN_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); + EXPECT_TRUE(data.slice_by_state(0).has_value()); + EXPECT_EQ(android::view::DisplayStateEnum::DISPLAY_STATE_DOZE, data.slice_by_state(0).value()); + EXPECT_EQ(1, data.bucket_info_size()); + EXPECT_EQ(30 * NS_PER_SEC, data.bucket_info(0).duration_nanos()); + EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).start_bucket_elapsed_nanos()); + EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(0).end_bucket_elapsed_nanos()); +} + +TEST(DurationMetricE2eTest, TestWithSlicedStateMapped) { + // Initialize config. + StatsdConfig config; + config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root. + + *config.add_atom_matcher() = CreateBatterySaverModeStartAtomMatcher(); + *config.add_atom_matcher() = CreateBatterySaverModeStopAtomMatcher(); + + auto batterySaverModePredicate = CreateBatterySaverModePredicate(); + *config.add_predicate() = batterySaverModePredicate; + + auto screenStateWithMap = CreateScreenStateWithOnOffMap(); + *config.add_state() = screenStateWithMap; + + // Create duration metric that slices by mapped screen state. + auto durationMetric = config.add_duration_metric(); + durationMetric->set_id(StringToId("DurationBatterySaverModeSliceScreenMapped")); + durationMetric->set_what(batterySaverModePredicate.id()); + durationMetric->add_slice_by_state(screenStateWithMap.id()); + durationMetric->set_aggregation_type(DurationMetric::SUM); + durationMetric->set_bucket(FIVE_MINUTES); + + // Initialize StatsLogProcessor. + int uid = 12345; + int64_t cfgId = 98765; + ConfigKey cfgKey(uid, cfgId); + uint64_t bucketStartTimeNs = 10000000000; // 0:10 + uint64_t bucketSizeNs = + TimeUnitToBucketSizeInMillis(config.duration_metric(0).bucket()) * 1000000LL; + auto processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey); + + EXPECT_EQ(processor->mMetricsManagers.size(), 1u); + sp<MetricsManager> metricsManager = processor->mMetricsManagers.begin()->second; + EXPECT_TRUE(metricsManager->isConfigValid()); + EXPECT_EQ(metricsManager->mAllMetricProducers.size(), 1); + EXPECT_TRUE(metricsManager->isActive()); + sp<MetricProducer> metricProducer = metricsManager->mAllMetricProducers[0]; + EXPECT_TRUE(metricProducer->mIsActive); + EXPECT_EQ(metricProducer->mSlicedStateAtoms.size(), 1); + EXPECT_EQ(metricProducer->mSlicedStateAtoms.at(0), SCREEN_STATE_ATOM_ID); + EXPECT_EQ(metricProducer->mStateGroupMap.size(), 1); + + // Check that StateTrackers were initialized correctly. + EXPECT_EQ(1, StateManager::getInstance().getStateTrackersCount()); + EXPECT_EQ(1, StateManager::getInstance().getListenersCount(SCREEN_STATE_ATOM_ID)); + + /* + bucket #1 bucket #2 + | 1 2 3 4 5 6 7 8 9 10 (minutes) + |-----------------------------|-----------------------------|-- + ON OFF ON (BatterySaverMode) + ---------------------------------------------------------SCREEN_OFF events + | | (ScreenStateOffEvent = 1) + | (ScreenStateDozeEvent = 3) + | (ScreenStateDozeSuspendEvent = 4) + ---------------------------------------------------------SCREEN_ON events + | | | (ScreenStateOnEvent = 2) + | (ScreenStateVrEvent = 5) + | (ScreenStateOnSuspendEvent = 6) + */ + // Initialize log events. + std::vector<std::unique_ptr<LogEvent>> events; + events.push_back(CreateScreenStateChangedEvent( + bucketStartTimeNs + 10 * NS_PER_SEC, + android::view::DisplayStateEnum::DISPLAY_STATE_ON)); // 0:20 + events.push_back(CreateBatterySaverOnEvent(bucketStartTimeNs + 20 * NS_PER_SEC)); // 0:30 + events.push_back(CreateScreenStateChangedEvent( + bucketStartTimeNs + 70 * NS_PER_SEC, + android::view::DisplayStateEnum::DISPLAY_STATE_OFF)); // 1:20 + events.push_back(CreateScreenStateChangedEvent( + bucketStartTimeNs + 100 * NS_PER_SEC, + android::view::DisplayStateEnum::DISPLAY_STATE_DOZE)); // 1:50 + events.push_back(CreateScreenStateChangedEvent( + bucketStartTimeNs + 120 * NS_PER_SEC, + android::view::DisplayStateEnum::DISPLAY_STATE_ON)); // 2:10 + events.push_back(CreateScreenStateChangedEvent( + bucketStartTimeNs + 170 * NS_PER_SEC, + android::view::DisplayStateEnum::DISPLAY_STATE_VR)); // 3:00 + events.push_back(CreateBatterySaverOffEvent(bucketStartTimeNs + 200 * NS_PER_SEC)); // 3:30 + events.push_back(CreateScreenStateChangedEvent( + bucketStartTimeNs + 250 * NS_PER_SEC, + android::view::DisplayStateEnum::DISPLAY_STATE_OFF)); // 4:20 + events.push_back(CreateBatterySaverOnEvent(bucketStartTimeNs + 280 * NS_PER_SEC)); // 4:50 + + // Bucket boundary 5:10. + events.push_back(CreateScreenStateChangedEvent( + bucketStartTimeNs + 320 * NS_PER_SEC, + android::view::DisplayStateEnum::DISPLAY_STATE_ON)); // 5:30 + events.push_back(CreateScreenStateChangedEvent( + bucketStartTimeNs + 390 * NS_PER_SEC, + android::view::DisplayStateEnum::DISPLAY_STATE_ON_SUSPEND)); // 6:40 + events.push_back(CreateScreenStateChangedEvent( + bucketStartTimeNs + 430 * NS_PER_SEC, + android::view::DisplayStateEnum::DISPLAY_STATE_DOZE_SUSPEND)); // 7:20 + // Send log events to StatsLogProcessor. + for (auto& event : events) { + processor->OnLogEvent(event.get()); + } + + // Check dump report. + vector<uint8_t> buffer; + ConfigMetricsReportList reports; + processor->onDumpReport(cfgKey, bucketStartTimeNs + 490 * NS_PER_SEC, + true /* include current partial bucket */, true, ADB_DUMP, FAST, + &buffer); + EXPECT_GT(buffer.size(), 0); + EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size())); + backfillDimensionPath(&reports); + backfillStringInReport(&reports); + backfillStartEndTimestamp(&reports); + + EXPECT_EQ(1, reports.reports_size()); + EXPECT_EQ(1, reports.reports(0).metrics_size()); + EXPECT_TRUE(reports.reports(0).metrics(0).has_duration_metrics()); + EXPECT_EQ(2, reports.reports(0).metrics(0).duration_metrics().data_size()); + + DurationMetricData data = reports.reports(0).metrics(0).duration_metrics().data(0); + EXPECT_EQ(1, data.slice_by_state_size()); + EXPECT_EQ(SCREEN_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); + EXPECT_TRUE(data.slice_by_state(0).has_group_id()); + EXPECT_EQ(StringToId("SCREEN_ON"), data.slice_by_state(0).group_id()); + EXPECT_EQ(2, data.bucket_info_size()); + EXPECT_EQ(130 * NS_PER_SEC, data.bucket_info(0).duration_nanos()); + EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).start_bucket_elapsed_nanos()); + EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(0).end_bucket_elapsed_nanos()); + EXPECT_EQ(110 * NS_PER_SEC, data.bucket_info(1).duration_nanos()); + EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(1).start_bucket_elapsed_nanos()); + EXPECT_EQ(500 * NS_PER_SEC, data.bucket_info(1).end_bucket_elapsed_nanos()); + + data = reports.reports(0).metrics(0).duration_metrics().data(1); + EXPECT_EQ(1, data.slice_by_state_size()); + EXPECT_EQ(SCREEN_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); + EXPECT_TRUE(data.slice_by_state(0).has_group_id()); + EXPECT_EQ(StringToId("SCREEN_OFF"), data.slice_by_state(0).group_id()); + EXPECT_EQ(2, data.bucket_info_size()); + EXPECT_EQ(70 * NS_PER_SEC, data.bucket_info(0).duration_nanos()); + EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).start_bucket_elapsed_nanos()); + EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(0).end_bucket_elapsed_nanos()); + EXPECT_EQ(80 * NS_PER_SEC, data.bucket_info(1).duration_nanos()); + EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(1).start_bucket_elapsed_nanos()); + EXPECT_EQ(500 * NS_PER_SEC, data.bucket_info(1).end_bucket_elapsed_nanos()); +} + +TEST(DurationMetricE2eTest, TestSlicedStatePrimaryFieldsNotSubsetDimInWhat) { + // Initialize config. + StatsdConfig config; + config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root. + + *config.add_atom_matcher() = CreateAcquireWakelockAtomMatcher(); + *config.add_atom_matcher() = CreateReleaseWakelockAtomMatcher(); + + auto holdingWakelockPredicate = CreateHoldingWakelockPredicate(); + *config.add_predicate() = holdingWakelockPredicate; + + auto uidProcessState = CreateUidProcessState(); + *config.add_state() = uidProcessState; + + // Create duration metric that slices by uid process state. + auto durationMetric = config.add_duration_metric(); + durationMetric->set_id(StringToId("DurationHoldingWakelockSliceUidProcessState")); + durationMetric->set_what(holdingWakelockPredicate.id()); + durationMetric->add_slice_by_state(uidProcessState.id()); + durationMetric->set_aggregation_type(DurationMetric::SUM); + durationMetric->set_bucket(FIVE_MINUTES); + + // The state has only one primary field (uid). + auto stateLink = durationMetric->add_state_link(); + stateLink->set_state_atom_id(UID_PROCESS_STATE_ATOM_ID); + auto fieldsInWhat = stateLink->mutable_fields_in_what(); + *fieldsInWhat = CreateAttributionUidDimensions(util::WAKELOCK_STATE_CHANGED, {Position::FIRST}); + auto fieldsInState = stateLink->mutable_fields_in_state(); + *fieldsInState = CreateDimensions(UID_PROCESS_STATE_ATOM_ID, {1 /* uid */}); + + // Initialize StatsLogProcessor. + int uid = 12345; + int64_t cfgId = 98765; + ConfigKey cfgKey(uid, cfgId); + uint64_t bucketStartTimeNs = 10000000000; // 0:10 + uint64_t bucketSizeNs = + TimeUnitToBucketSizeInMillis(config.duration_metric(0).bucket()) * 1000000LL; + auto processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey); + + // This config is rejected because the dimension in what fields are not a superset of the sliced + // state primary fields. + EXPECT_EQ(processor->mMetricsManagers.size(), 0); +} + +TEST(DurationMetricE2eTest, TestWithSlicedStatePrimaryFieldsSubset) { + // Initialize config. + StatsdConfig config; + config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root. + + *config.add_atom_matcher() = CreateAcquireWakelockAtomMatcher(); + *config.add_atom_matcher() = CreateReleaseWakelockAtomMatcher(); + + auto holdingWakelockPredicate = CreateHoldingWakelockPredicate(); + *config.add_predicate() = holdingWakelockPredicate; + + auto uidProcessState = CreateUidProcessState(); + *config.add_state() = uidProcessState; + + // Create duration metric that slices by uid process state. + auto durationMetric = config.add_duration_metric(); + durationMetric->set_id(StringToId("DurationPartialWakelockPerTagUidSliceProcessState")); + durationMetric->set_what(holdingWakelockPredicate.id()); + durationMetric->add_slice_by_state(uidProcessState.id()); + durationMetric->set_aggregation_type(DurationMetric::SUM); + durationMetric->set_bucket(FIVE_MINUTES); + + // The metric is dimensioning by first uid of attribution node and tag. + *durationMetric->mutable_dimensions_in_what() = CreateAttributionUidAndOtherDimensions( + util::WAKELOCK_STATE_CHANGED, {Position::FIRST}, {3 /* tag */}); + // The state has only one primary field (uid). + auto stateLink = durationMetric->add_state_link(); + stateLink->set_state_atom_id(UID_PROCESS_STATE_ATOM_ID); + auto fieldsInWhat = stateLink->mutable_fields_in_what(); + *fieldsInWhat = CreateAttributionUidDimensions(util::WAKELOCK_STATE_CHANGED, {Position::FIRST}); + auto fieldsInState = stateLink->mutable_fields_in_state(); + *fieldsInState = CreateDimensions(UID_PROCESS_STATE_ATOM_ID, {1 /* uid */}); + + // Initialize StatsLogProcessor. + int uid = 12345; + int64_t cfgId = 98765; + ConfigKey cfgKey(uid, cfgId); + uint64_t bucketStartTimeNs = 10000000000; // 0:10 + uint64_t bucketSizeNs = + TimeUnitToBucketSizeInMillis(config.duration_metric(0).bucket()) * 1000000LL; + auto processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey); + + EXPECT_EQ(processor->mMetricsManagers.size(), 1u); + sp<MetricsManager> metricsManager = processor->mMetricsManagers.begin()->second; + EXPECT_TRUE(metricsManager->isConfigValid()); + EXPECT_EQ(metricsManager->mAllMetricProducers.size(), 1); + EXPECT_TRUE(metricsManager->isActive()); + sp<MetricProducer> metricProducer = metricsManager->mAllMetricProducers[0]; + EXPECT_TRUE(metricProducer->mIsActive); + EXPECT_EQ(metricProducer->mSlicedStateAtoms.size(), 1); + EXPECT_EQ(metricProducer->mSlicedStateAtoms.at(0), UID_PROCESS_STATE_ATOM_ID); + EXPECT_EQ(metricProducer->mStateGroupMap.size(), 0); + + // Check that StateTrackers were initialized correctly. + EXPECT_EQ(1, StateManager::getInstance().getStateTrackersCount()); + EXPECT_EQ(1, StateManager::getInstance().getListenersCount(UID_PROCESS_STATE_ATOM_ID)); + + // Initialize log events. + int appUid1 = 1001; + int appUid2 = 1002; + std::vector<int> attributionUids1 = {appUid1}; + std::vector<string> attributionTags1 = {"App1"}; + + std::vector<int> attributionUids2 = {appUid2}; + std::vector<string> attributionTags2 = {"App2"}; + + std::vector<std::unique_ptr<LogEvent>> events; + events.push_back(CreateUidProcessStateChangedEvent( + bucketStartTimeNs + 10 * NS_PER_SEC, appUid1, + android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_FOREGROUND)); // 0:20 + events.push_back(CreateAcquireWakelockEvent(bucketStartTimeNs + 20 * NS_PER_SEC, + attributionUids1, attributionTags1, + "wakelock1")); // 0:30 + events.push_back(CreateAcquireWakelockEvent(bucketStartTimeNs + 25 * NS_PER_SEC, + attributionUids1, attributionTags1, + "wakelock2")); // 0:35 + events.push_back(CreateAcquireWakelockEvent(bucketStartTimeNs + 30 * NS_PER_SEC, + attributionUids2, attributionTags2, + "wakelock1")); // 0:40 + events.push_back(CreateAcquireWakelockEvent(bucketStartTimeNs + 35 * NS_PER_SEC, + attributionUids2, attributionTags2, + "wakelock2")); // 0:45 + events.push_back(CreateUidProcessStateChangedEvent( + bucketStartTimeNs + 50 * NS_PER_SEC, appUid2, + android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_BACKGROUND)); // 1:00 + events.push_back(CreateUidProcessStateChangedEvent( + bucketStartTimeNs + 60 * NS_PER_SEC, appUid1, + android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_BACKGROUND)); // 1:10 + events.push_back(CreateReleaseWakelockEvent(bucketStartTimeNs + 100 * NS_PER_SEC, + attributionUids2, attributionTags2, + "wakelock1")); // 1:50 + events.push_back(CreateUidProcessStateChangedEvent( + bucketStartTimeNs + 120 * NS_PER_SEC, appUid2, + android::app::ProcessStateEnum::PROCESS_STATE_FOREGROUND_SERVICE)); // 2:10 + events.push_back(CreateReleaseWakelockEvent(bucketStartTimeNs + 200 * NS_PER_SEC, + attributionUids1, attributionTags1, + "wakelock2")); // 3:30 + + // Send log events to StatsLogProcessor. + for (auto& event : events) { + processor->OnLogEvent(event.get()); + } + + // Check dump report. + vector<uint8_t> buffer; + ConfigMetricsReportList reports; + processor->onDumpReport(cfgKey, bucketStartTimeNs + 320 * NS_PER_SEC, + true /* include current partial bucket */, true, ADB_DUMP, FAST, + &buffer); + EXPECT_GT(buffer.size(), 0); + EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size())); + backfillDimensionPath(&reports); + backfillStringInReport(&reports); + backfillStartEndTimestamp(&reports); + + EXPECT_EQ(1, reports.reports_size()); + EXPECT_EQ(1, reports.reports(0).metrics_size()); + EXPECT_TRUE(reports.reports(0).metrics(0).has_duration_metrics()); + EXPECT_EQ(9, reports.reports(0).metrics(0).duration_metrics().data_size()); + + DurationMetricData data = reports.reports(0).metrics(0).duration_metrics().data(0); + ValidateWakelockAttributionUidAndTagDimension(data.dimensions_in_what(), 10, appUid1, + "wakelock2"); + EXPECT_EQ(1, data.slice_by_state_size()); + EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); + EXPECT_TRUE(data.slice_by_state(0).has_value()); + EXPECT_EQ(android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_FOREGROUND, + data.slice_by_state(0).value()); + EXPECT_EQ(1, data.bucket_info_size()); + EXPECT_EQ(35 * NS_PER_SEC, data.bucket_info(0).duration_nanos()); + EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).start_bucket_elapsed_nanos()); + EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(0).end_bucket_elapsed_nanos()); + + data = reports.reports(0).metrics(0).duration_metrics().data(1); + ValidateWakelockAttributionUidAndTagDimension(data.dimensions_in_what(), 10, appUid1, + "wakelock2"); + EXPECT_EQ(1, data.slice_by_state_size()); + EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); + EXPECT_TRUE(data.slice_by_state(0).has_value()); + EXPECT_EQ(android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_BACKGROUND, + data.slice_by_state(0).value()); + EXPECT_EQ(1, data.bucket_info_size()); + EXPECT_EQ(140 * NS_PER_SEC, data.bucket_info(0).duration_nanos()); + EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).start_bucket_elapsed_nanos()); + EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(0).end_bucket_elapsed_nanos()); + + data = reports.reports(0).metrics(0).duration_metrics().data(2); + ValidateWakelockAttributionUidAndTagDimension(data.dimensions_in_what(), 10, appUid2, + "wakelock1"); + EXPECT_EQ(1, data.slice_by_state_size()); + EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); + EXPECT_TRUE(data.slice_by_state(0).has_value()); + EXPECT_EQ(-1 /* StateTracker:: kStateUnknown */, data.slice_by_state(0).value()); + EXPECT_EQ(1, data.bucket_info_size()); + EXPECT_EQ(20 * NS_PER_SEC, data.bucket_info(0).duration_nanos()); + EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).start_bucket_elapsed_nanos()); + EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(0).end_bucket_elapsed_nanos()); + + data = reports.reports(0).metrics(0).duration_metrics().data(3); + ValidateWakelockAttributionUidAndTagDimension(data.dimensions_in_what(), 10, appUid1, + "wakelock1"); + EXPECT_EQ(1, data.slice_by_state_size()); + EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); + EXPECT_TRUE(data.slice_by_state(0).has_value()); + EXPECT_EQ(android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_BACKGROUND, + data.slice_by_state(0).value()); + EXPECT_EQ(2, data.bucket_info_size()); + EXPECT_EQ(240 * NS_PER_SEC, data.bucket_info(0).duration_nanos()); + EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).start_bucket_elapsed_nanos()); + EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(0).end_bucket_elapsed_nanos()); + EXPECT_EQ(20 * NS_PER_SEC, data.bucket_info(1).duration_nanos()); + EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(1).start_bucket_elapsed_nanos()); + EXPECT_EQ(330 * NS_PER_SEC, data.bucket_info(1).end_bucket_elapsed_nanos()); + + data = reports.reports(0).metrics(0).duration_metrics().data(4); + ValidateWakelockAttributionUidAndTagDimension(data.dimensions_in_what(), 10, appUid2, + "wakelock1"); + EXPECT_EQ(1, data.slice_by_state_size()); + EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); + EXPECT_TRUE(data.slice_by_state(0).has_value()); + EXPECT_EQ(android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_BACKGROUND, + data.slice_by_state(0).value()); + EXPECT_EQ(1, data.bucket_info_size()); + EXPECT_EQ(50 * NS_PER_SEC, data.bucket_info(0).duration_nanos()); + EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).start_bucket_elapsed_nanos()); + EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(0).end_bucket_elapsed_nanos()); + + data = reports.reports(0).metrics(0).duration_metrics().data(5); + ValidateWakelockAttributionUidAndTagDimension(data.dimensions_in_what(), 10, appUid2, + "wakelock2"); + EXPECT_EQ(1, data.slice_by_state_size()); + EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); + EXPECT_TRUE(data.slice_by_state(0).has_value()); + EXPECT_EQ(android::app::ProcessStateEnum::PROCESS_STATE_FOREGROUND_SERVICE, + data.slice_by_state(0).value()); + EXPECT_EQ(2, data.bucket_info_size()); + EXPECT_EQ(180 * NS_PER_SEC, data.bucket_info(0).duration_nanos()); + EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).start_bucket_elapsed_nanos()); + EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(0).end_bucket_elapsed_nanos()); + EXPECT_EQ(20 * NS_PER_SEC, data.bucket_info(1).duration_nanos()); + EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(1).start_bucket_elapsed_nanos()); + EXPECT_EQ(330 * NS_PER_SEC, data.bucket_info(1).end_bucket_elapsed_nanos()); + + data = reports.reports(0).metrics(0).duration_metrics().data(6); + ValidateWakelockAttributionUidAndTagDimension(data.dimensions_in_what(), 10, appUid2, + "wakelock2"); + EXPECT_EQ(1, data.slice_by_state_size()); + EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); + EXPECT_TRUE(data.slice_by_state(0).has_value()); + EXPECT_EQ(-1 /* StateTracker:: kStateUnknown */, data.slice_by_state(0).value()); + EXPECT_EQ(1, data.bucket_info_size()); + EXPECT_EQ(15 * NS_PER_SEC, data.bucket_info(0).duration_nanos()); + EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).start_bucket_elapsed_nanos()); + EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(0).end_bucket_elapsed_nanos()); + + data = reports.reports(0).metrics(0).duration_metrics().data(7); + ValidateWakelockAttributionUidAndTagDimension(data.dimensions_in_what(), 10, appUid1, + "wakelock1"); + EXPECT_EQ(1, data.slice_by_state_size()); + EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); + EXPECT_TRUE(data.slice_by_state(0).has_value()); + EXPECT_EQ(android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_FOREGROUND, + data.slice_by_state(0).value()); + EXPECT_EQ(1, data.bucket_info_size()); + EXPECT_EQ(40 * NS_PER_SEC, data.bucket_info(0).duration_nanos()); + EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).start_bucket_elapsed_nanos()); + EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(0).end_bucket_elapsed_nanos()); + + data = reports.reports(0).metrics(0).duration_metrics().data(8); + ValidateWakelockAttributionUidAndTagDimension(data.dimensions_in_what(), 10, appUid2, + "wakelock2"); + EXPECT_EQ(1, data.slice_by_state_size()); + EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); + EXPECT_TRUE(data.slice_by_state(0).has_value()); + EXPECT_EQ(android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_BACKGROUND, + data.slice_by_state(0).value()); + EXPECT_EQ(1, data.bucket_info_size()); + EXPECT_EQ(70 * NS_PER_SEC, data.bucket_info(0).duration_nanos()); + EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).start_bucket_elapsed_nanos()); + EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(0).end_bucket_elapsed_nanos()); +} + #else GTEST_LOG_(INFO) << "This test does nothing.\n"; #endif diff --git a/cmds/statsd/tests/metrics/MaxDurationTracker_test.cpp b/cmds/statsd/tests/metrics/MaxDurationTracker_test.cpp index 100220b730d7..d2f0f57e0f54 100644 --- a/cmds/statsd/tests/metrics/MaxDurationTracker_test.cpp +++ b/cmds/statsd/tests/metrics/MaxDurationTracker_test.cpp @@ -62,9 +62,8 @@ TEST(MaxDurationTrackerTest, TestSimpleMaxDuration) { int64_t bucketNum = 0; int64_t metricId = 1; - MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, -1, - false, bucketStartTimeNs, bucketNum, bucketStartTimeNs, bucketSizeNs, - false, false, {}); + MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, -1, false, bucketStartTimeNs, + bucketNum, bucketStartTimeNs, bucketSizeNs, false, false, {}); tracker.noteStart(key1, true, bucketStartTimeNs, ConditionKey()); // Event starts again. This would not change anything as it already starts. @@ -97,9 +96,8 @@ TEST(MaxDurationTrackerTest, TestStopAll) { int64_t bucketNum = 0; int64_t metricId = 1; - MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, -1, - false, bucketStartTimeNs, bucketNum, bucketStartTimeNs, bucketSizeNs, - false, false, {}); + MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, -1, false, bucketStartTimeNs, + bucketNum, bucketStartTimeNs, bucketSizeNs, false, false, {}); tracker.noteStart(key1, true, bucketStartTimeNs + 1, ConditionKey()); @@ -132,9 +130,8 @@ TEST(MaxDurationTrackerTest, TestCrossBucketBoundary) { int64_t bucketNum = 0; int64_t metricId = 1; - MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, -1, - false, bucketStartTimeNs, bucketNum, bucketStartTimeNs, bucketSizeNs, - false, false, {}); + MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, -1, false, bucketStartTimeNs, + bucketNum, bucketStartTimeNs, bucketSizeNs, false, false, {}); // The event starts. tracker.noteStart(DEFAULT_DIMENSION_KEY, true, bucketStartTimeNs + 1, ConditionKey()); @@ -172,9 +169,8 @@ TEST(MaxDurationTrackerTest, TestCrossBucketBoundary_nested) { int64_t bucketNum = 0; int64_t metricId = 1; - MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, -1, - true, bucketStartTimeNs, bucketNum, bucketStartTimeNs, bucketSizeNs, - false, false, {}); + MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, -1, true, bucketStartTimeNs, + bucketNum, bucketStartTimeNs, bucketSizeNs, false, false, {}); // 2 starts tracker.noteStart(DEFAULT_DIMENSION_KEY, true, bucketStartTimeNs + 1, ConditionKey()); @@ -218,9 +214,8 @@ TEST(MaxDurationTrackerTest, TestMaxDurationWithCondition) { int64_t eventStopTimeNs = conditionStops2 + 8 * NS_PER_SEC; int64_t metricId = 1; - MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, - false, bucketStartTimeNs, 0, bucketStartTimeNs, bucketSizeNs, true, - false, {}); + MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, false, bucketStartTimeNs, + 0, bucketStartTimeNs, bucketSizeNs, true, false, {}); EXPECT_TRUE(tracker.mAnomalyTrackers.empty()); tracker.noteStart(key1, false, eventStartTimeNs, conditionKey1); @@ -267,9 +262,9 @@ TEST(MaxDurationTrackerTest, TestAnomalyDetection) { sp<AlarmMonitor> alarmMonitor; sp<DurationAnomalyTracker> anomalyTracker = new DurationAnomalyTracker(alert, kConfigKey, alarmMonitor); - MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, - false, bucketStartTimeNs, bucketNum, bucketStartTimeNs, bucketSizeNs, - true, false, {anomalyTracker}); + MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, false, bucketStartTimeNs, + bucketNum, bucketStartTimeNs, bucketSizeNs, true, false, + {anomalyTracker}); tracker.noteStart(key1, true, eventStartTimeNs, conditionKey1); sp<const InternalAlarm> alarm = anomalyTracker->mAlarms.begin()->second; @@ -326,9 +321,9 @@ TEST(MaxDurationTrackerTest, TestAnomalyPredictedTimestamp) { sp<AlarmMonitor> alarmMonitor; sp<DurationAnomalyTracker> anomalyTracker = new DurationAnomalyTracker(alert, kConfigKey, alarmMonitor); - MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, - false, bucketStartTimeNs, bucketNum, bucketStartTimeNs, bucketSizeNs, - true, false, {anomalyTracker}); + MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, false, bucketStartTimeNs, + bucketNum, bucketStartTimeNs, bucketSizeNs, true, false, + {anomalyTracker}); tracker.noteStart(key1, false, eventStartTimeNs, conditionKey1); tracker.noteConditionChanged(key1, true, conditionStarts1); @@ -408,9 +403,9 @@ TEST(MaxDurationTrackerTest, TestAnomalyPredictedTimestamp_UpdatedOnStop) { sp<AlarmMonitor> alarmMonitor; sp<DurationAnomalyTracker> anomalyTracker = new DurationAnomalyTracker(alert, kConfigKey, alarmMonitor); - MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, - false, bucketStartTimeNs, bucketNum, bucketStartTimeNs, bucketSizeNs, - true, false, {anomalyTracker}); + MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, false, bucketStartTimeNs, + bucketNum, bucketStartTimeNs, bucketSizeNs, true, false, + {anomalyTracker}); tracker.noteStart(key1, true, eventStartTimeNs1, conditionKey1); tracker.noteStart(key2, true, eventStartTimeNs2, conditionKey2); diff --git a/cmds/statsd/tests/metrics/OringDurationTracker_test.cpp b/cmds/statsd/tests/metrics/OringDurationTracker_test.cpp index 1cd7bdbf7bb0..39d3919dffd0 100644 --- a/cmds/statsd/tests/metrics/OringDurationTracker_test.cpp +++ b/cmds/statsd/tests/metrics/OringDurationTracker_test.cpp @@ -61,9 +61,9 @@ TEST(OringDurationTrackerTest, TestDurationOverlap) { int64_t eventStartTimeNs = bucketStartTimeNs + 1; int64_t durationTimeNs = 2 * 1000; - OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, - false, bucketStartTimeNs, bucketNum, bucketStartTimeNs, - bucketSizeNs, false, false, {}); + OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, false, + bucketStartTimeNs, bucketNum, bucketStartTimeNs, bucketSizeNs, + false, false, {}); tracker.noteStart(kEventKey1, true, eventStartTimeNs, ConditionKey()); EXPECT_EQ((long long)eventStartTimeNs, tracker.mLastStartTime); @@ -92,9 +92,8 @@ TEST(OringDurationTrackerTest, TestDurationNested) { int64_t bucketNum = 0; int64_t eventStartTimeNs = bucketStartTimeNs + 1; - OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, - true, bucketStartTimeNs, bucketNum, bucketStartTimeNs, - bucketSizeNs, false, false, {}); + OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, true, bucketStartTimeNs, + bucketNum, bucketStartTimeNs, bucketSizeNs, false, false, {}); tracker.noteStart(kEventKey1, true, eventStartTimeNs, ConditionKey()); tracker.noteStart(kEventKey1, true, eventStartTimeNs + 10, ConditionKey()); // overlapping wl @@ -124,9 +123,8 @@ TEST(OringDurationTrackerTest, TestStopAll) { int64_t bucketNum = 0; int64_t eventStartTimeNs = bucketStartTimeNs + 1; - OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, - true, bucketStartTimeNs, bucketNum, bucketStartTimeNs, - bucketSizeNs, false, false, {}); + OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, true, bucketStartTimeNs, + bucketNum, bucketStartTimeNs, bucketSizeNs, false, false, {}); tracker.noteStart(kEventKey1, true, eventStartTimeNs, ConditionKey()); tracker.noteStart(kEventKey2, true, eventStartTimeNs + 10, ConditionKey()); // overlapping wl @@ -154,9 +152,8 @@ TEST(OringDurationTrackerTest, TestCrossBucketBoundary) { int64_t eventStartTimeNs = bucketStartTimeNs + 1; int64_t durationTimeNs = 2 * 1000; - OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, - true, bucketStartTimeNs, bucketNum, bucketStartTimeNs, - bucketSizeNs, false, false, {}); + OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, true, bucketStartTimeNs, + bucketNum, bucketStartTimeNs, bucketSizeNs, false, false, {}); tracker.noteStart(kEventKey1, true, eventStartTimeNs, ConditionKey()); EXPECT_EQ((long long)eventStartTimeNs, tracker.mLastStartTime); @@ -198,9 +195,9 @@ TEST(OringDurationTrackerTest, TestDurationConditionChange) { int64_t eventStartTimeNs = bucketStartTimeNs + 1; int64_t durationTimeNs = 2 * 1000; - OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, - false, bucketStartTimeNs, bucketNum, bucketStartTimeNs, - bucketSizeNs, true, false, {}); + OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, false, + bucketStartTimeNs, bucketNum, bucketStartTimeNs, bucketSizeNs, + true, false, {}); tracker.noteStart(kEventKey1, true, eventStartTimeNs, key1); @@ -237,9 +234,9 @@ TEST(OringDurationTrackerTest, TestDurationConditionChange2) { int64_t eventStartTimeNs = bucketStartTimeNs + 1; int64_t durationTimeNs = 2 * 1000; - OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, - false, bucketStartTimeNs, bucketNum, bucketStartTimeNs, - bucketSizeNs, true, false, {}); + OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, false, + bucketStartTimeNs, bucketNum, bucketStartTimeNs, bucketSizeNs, + true, false, {}); tracker.noteStart(kEventKey1, true, eventStartTimeNs, key1); // condition to false; record duration 5n @@ -275,9 +272,8 @@ TEST(OringDurationTrackerTest, TestDurationConditionChangeNested) { int64_t bucketNum = 0; int64_t eventStartTimeNs = bucketStartTimeNs + 1; - OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, - true, bucketStartTimeNs, bucketNum, bucketStartTimeNs, - bucketSizeNs, true, false, {}); + OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, true, bucketStartTimeNs, + bucketNum, bucketStartTimeNs, bucketSizeNs, true, false, {}); tracker.noteStart(kEventKey1, true, eventStartTimeNs, key1); tracker.noteStart(kEventKey1, true, eventStartTimeNs + 2, key1); @@ -316,9 +312,9 @@ TEST(OringDurationTrackerTest, TestPredictAnomalyTimestamp) { sp<AlarmMonitor> alarmMonitor; sp<DurationAnomalyTracker> anomalyTracker = new DurationAnomalyTracker(alert, kConfigKey, alarmMonitor); - OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, - true, bucketStartTimeNs, bucketNum, bucketStartTimeNs, - bucketSizeNs, true, false, {anomalyTracker}); + OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, true, bucketStartTimeNs, + bucketNum, bucketStartTimeNs, bucketSizeNs, true, false, + {anomalyTracker}); // Nothing in the past bucket. tracker.noteStart(DEFAULT_DIMENSION_KEY, true, eventStartTimeNs, ConditionKey()); @@ -422,9 +418,8 @@ TEST(OringDurationTrackerTest, TestPredictAnomalyTimestamp3) { sp<AlarmMonitor> alarmMonitor; sp<DurationAnomalyTracker> anomalyTracker = new DurationAnomalyTracker(alert, kConfigKey, alarmMonitor); - OringDurationTracker tracker(kConfigKey, metricId, DEFAULT_METRIC_DIMENSION_KEY, - wizard, 1, - true, bucketStartTimeNs, bucketNum, bucketStartTimeNs, + OringDurationTracker tracker(kConfigKey, metricId, DEFAULT_METRIC_DIMENSION_KEY, wizard, + 1, true, bucketStartTimeNs, bucketNum, bucketStartTimeNs, bucketSizeNs, true, false, {anomalyTracker}); int64_t eventStartTimeNs = bucketStartTimeNs + 9 * NS_PER_SEC; @@ -481,15 +476,15 @@ TEST(OringDurationTrackerTest, TestAnomalyDetectionExpiredAlarm) { sp<AlarmMonitor> alarmMonitor; sp<DurationAnomalyTracker> anomalyTracker = new DurationAnomalyTracker(alert, kConfigKey, alarmMonitor); - OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, - true /*nesting*/, bucketStartTimeNs, bucketNum, bucketStartTimeNs, - bucketSizeNs, false, false, {anomalyTracker}); + OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, true /*nesting*/, + bucketStartTimeNs, bucketNum, bucketStartTimeNs, bucketSizeNs, + false, false, {anomalyTracker}); tracker.noteStart(kEventKey1, true, eventStartTimeNs, ConditionKey()); tracker.noteStop(kEventKey1, eventStartTimeNs + 10, false); EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(eventKey), 0U); EXPECT_TRUE(tracker.mStarted.empty()); - EXPECT_EQ(10LL, tracker.mDuration); // 10ns + EXPECT_EQ(10LL, tracker.mStateKeyDurationMap[DEFAULT_DIMENSION_KEY].mDuration); // 10ns EXPECT_EQ(0u, tracker.mStarted.size()); @@ -530,11 +525,11 @@ TEST(OringDurationTrackerTest, TestAnomalyDetectionFiredAlarm) { sp<AlarmMonitor> alarmMonitor; sp<DurationAnomalyTracker> anomalyTracker = new DurationAnomalyTracker(alert, kConfigKey, alarmMonitor); - OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, - true /*nesting*/, bucketStartTimeNs, 0, bucketStartTimeNs, - bucketSizeNs, false, false, {anomalyTracker}); + OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, true /*nesting*/, + bucketStartTimeNs, 0, bucketStartTimeNs, bucketSizeNs, false, + false, {anomalyTracker}); - tracker.noteStart(kEventKey1, true, 15 * NS_PER_SEC, conkey); // start key1 + tracker.noteStart(kEventKey1, true, 15 * NS_PER_SEC, conkey); // start key1 EXPECT_EQ(1u, anomalyTracker->mAlarms.size()); sp<const InternalAlarm> alarm = anomalyTracker->mAlarms.begin()->second; EXPECT_EQ((long long)(55ULL * NS_PER_SEC), (long long)(alarm->timestampSec * NS_PER_SEC)); @@ -544,13 +539,13 @@ TEST(OringDurationTrackerTest, TestAnomalyDetectionFiredAlarm) { EXPECT_EQ(0u, anomalyTracker->mAlarms.size()); EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(eventKey), 0U); - tracker.noteStart(kEventKey1, true, 22 * NS_PER_SEC, conkey); // start key1 again + tracker.noteStart(kEventKey1, true, 22 * NS_PER_SEC, conkey); // start key1 again EXPECT_EQ(1u, anomalyTracker->mAlarms.size()); alarm = anomalyTracker->mAlarms.begin()->second; EXPECT_EQ((long long)(60ULL * NS_PER_SEC), (long long)(alarm->timestampSec * NS_PER_SEC)); EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(eventKey), 0U); - tracker.noteStart(kEventKey2, true, 32 * NS_PER_SEC, conkey); // start key2 + tracker.noteStart(kEventKey2, true, 32 * NS_PER_SEC, conkey); // start key2 EXPECT_EQ(1u, anomalyTracker->mAlarms.size()); alarm = anomalyTracker->mAlarms.begin()->second; EXPECT_EQ((long long)(60ULL * NS_PER_SEC), (long long)(alarm->timestampSec * NS_PER_SEC)); diff --git a/cmds/statsd/tests/state/StateTracker_test.cpp b/cmds/statsd/tests/state/StateTracker_test.cpp index a0e00954531f..a5b8e1c50c33 100644 --- a/cmds/statsd/tests/state/StateTracker_test.cpp +++ b/cmds/statsd/tests/state/StateTracker_test.cpp @@ -105,63 +105,6 @@ std::unique_ptr<LogEvent> buildOverlayEventBadStateType(int uid, const std::stri } // END: build event functions. -// START: get primary key functions -void getUidProcessKey(int uid, HashableDimensionKey* key) { - int pos1[] = {1, 0, 0}; - Field field1(27 /* atom id */, pos1, 0 /* depth */); - Value value1((int32_t)uid); - - key->addValue(FieldValue(field1, value1)); -} - -void getOverlayKey(int uid, string packageName, HashableDimensionKey* key) { - int pos1[] = {1, 0, 0}; - int pos2[] = {2, 0, 0}; - - Field field1(59 /* atom id */, pos1, 0 /* depth */); - Field field2(59 /* atom id */, pos2, 0 /* depth */); - - Value value1((int32_t)uid); - Value value2(packageName); - - key->addValue(FieldValue(field1, value1)); - key->addValue(FieldValue(field2, value2)); -} - -void getPartialWakelockKey(int uid, const std::string& tag, HashableDimensionKey* key) { - int pos1[] = {1, 1, 1}; - int pos3[] = {2, 0, 0}; - int pos4[] = {3, 0, 0}; - - Field field1(10 /* atom id */, pos1, 2 /* depth */); - - Field field3(10 /* atom id */, pos3, 0 /* depth */); - Field field4(10 /* atom id */, pos4, 0 /* depth */); - - Value value1((int32_t)uid); - Value value3((int32_t)1 /*partial*/); - Value value4(tag); - - key->addValue(FieldValue(field1, value1)); - key->addValue(FieldValue(field3, value3)); - key->addValue(FieldValue(field4, value4)); -} - -void getPartialWakelockKey(int uid, HashableDimensionKey* key) { - int pos1[] = {1, 1, 1}; - int pos3[] = {2, 0, 0}; - - Field field1(10 /* atom id */, pos1, 2 /* depth */); - Field field3(10 /* atom id */, pos3, 0 /* depth */); - - Value value1((int32_t)uid); - Value value3((int32_t)1 /*partial*/); - - key->addValue(FieldValue(field1, value1)); - key->addValue(FieldValue(field3, value3)); -} -// END: get primary key functions - TEST(StateListenerTest, TestStateListenerWeakPointer) { sp<TestStateListener> listener = new TestStateListener(); wp<TestStateListener> wListener = listener; diff --git a/cmds/statsd/tests/statsd_test_util.cpp b/cmds/statsd/tests/statsd_test_util.cpp index 8c8836b94f56..2f81c2ded8b0 100644 --- a/cmds/statsd/tests/statsd_test_util.cpp +++ b/cmds/statsd/tests/statsd_test_util.cpp @@ -135,6 +135,27 @@ AtomMatcher CreateBatterySaverModeStopAtomMatcher() { "BatterySaverModeStop", BatterySaverModeStateChanged::OFF); } +AtomMatcher CreateBatteryStateChangedAtomMatcher(const string& name, + BatteryPluggedStateEnum state) { + AtomMatcher atom_matcher; + atom_matcher.set_id(StringToId(name)); + auto simple_atom_matcher = atom_matcher.mutable_simple_atom_matcher(); + simple_atom_matcher->set_atom_id(util::PLUGGED_STATE_CHANGED); + auto field_value_matcher = simple_atom_matcher->add_field_value_matcher(); + field_value_matcher->set_field(1); // State field. + field_value_matcher->set_eq_int(state); + return atom_matcher; +} + +AtomMatcher CreateBatteryStateNoneMatcher() { + return CreateBatteryStateChangedAtomMatcher("BatteryPluggedNone", + BatteryPluggedStateEnum::BATTERY_PLUGGED_NONE); +} + +AtomMatcher CreateBatteryStateUsbMatcher() { + return CreateBatteryStateChangedAtomMatcher("BatteryPluggedUsb", + BatteryPluggedStateEnum::BATTERY_PLUGGED_USB); +} AtomMatcher CreateScreenStateChangedAtomMatcher( const string& name, android::view::DisplayStateEnum state) { @@ -234,6 +255,14 @@ Predicate CreateBatterySaverModePredicate() { return predicate; } +Predicate CreateDeviceUnpluggedPredicate() { + Predicate predicate; + predicate.set_id(StringToId("DeviceUnplugged")); + predicate.mutable_simple_predicate()->set_start(StringToId("BatteryPluggedNone")); + predicate.mutable_simple_predicate()->set_stop(StringToId("BatteryPluggedUsb")); + return predicate; +} + Predicate CreateScreenIsOnPredicate() { Predicate predicate; predicate.set_id(StringToId("ScreenIsOn")); @@ -410,6 +439,74 @@ FieldMatcher CreateDimensions(const int atomId, const std::vector<int>& fields) return dimensions; } +FieldMatcher CreateAttributionUidAndOtherDimensions(const int atomId, + const std::vector<Position>& positions, + const std::vector<int>& fields) { + FieldMatcher dimensions = CreateAttributionUidDimensions(atomId, positions); + + for (const int field : fields) { + dimensions.add_child()->set_field(field); + } + return dimensions; +} + +// START: get primary key functions +void getUidProcessKey(int uid, HashableDimensionKey* key) { + int pos1[] = {1, 0, 0}; + Field field1(27 /* atom id */, pos1, 0 /* depth */); + Value value1((int32_t)uid); + + key->addValue(FieldValue(field1, value1)); +} + +void getOverlayKey(int uid, string packageName, HashableDimensionKey* key) { + int pos1[] = {1, 0, 0}; + int pos2[] = {2, 0, 0}; + + Field field1(59 /* atom id */, pos1, 0 /* depth */); + Field field2(59 /* atom id */, pos2, 0 /* depth */); + + Value value1((int32_t)uid); + Value value2(packageName); + + key->addValue(FieldValue(field1, value1)); + key->addValue(FieldValue(field2, value2)); +} + +void getPartialWakelockKey(int uid, const std::string& tag, HashableDimensionKey* key) { + int pos1[] = {1, 1, 1}; + int pos3[] = {2, 0, 0}; + int pos4[] = {3, 0, 0}; + + Field field1(10 /* atom id */, pos1, 2 /* depth */); + + Field field3(10 /* atom id */, pos3, 0 /* depth */); + Field field4(10 /* atom id */, pos4, 0 /* depth */); + + Value value1((int32_t)uid); + Value value3((int32_t)1 /*partial*/); + Value value4(tag); + + key->addValue(FieldValue(field1, value1)); + key->addValue(FieldValue(field3, value3)); + key->addValue(FieldValue(field4, value4)); +} + +void getPartialWakelockKey(int uid, HashableDimensionKey* key) { + int pos1[] = {1, 1, 1}; + int pos3[] = {2, 0, 0}; + + Field field1(10 /* atom id */, pos1, 2 /* depth */); + Field field3(10 /* atom id */, pos3, 0 /* depth */); + + Value value1((int32_t)uid); + Value value3((int32_t)1 /*partial*/); + + key->addValue(FieldValue(field1, value1)); + key->addValue(FieldValue(field3, value3)); +} +// END: get primary key functions + shared_ptr<LogEvent> CreateTwoValueLogEvent(int atomId, int64_t eventTimeNs, int32_t value1, int32_t value2) { AStatsEvent* statsEvent = AStatsEvent_obtain(); @@ -595,6 +692,23 @@ std::unique_ptr<LogEvent> CreateBatterySaverOffEvent(uint64_t timestampNs) { return logEvent; } +std::unique_ptr<LogEvent> CreateBatteryStateChangedEvent(const uint64_t timestampNs, const BatteryPluggedStateEnum state) { + AStatsEvent* statsEvent = AStatsEvent_obtain(); + AStatsEvent_setAtomId(statsEvent, util::PLUGGED_STATE_CHANGED); + AStatsEvent_overwriteTimestamp(statsEvent, timestampNs); + + AStatsEvent_writeInt32(statsEvent, state); + AStatsEvent_build(statsEvent); + + size_t size; + uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size); + + std::unique_ptr<LogEvent> logEvent = std::make_unique<LogEvent>(/*uid=*/0, /*pid=*/0); + logEvent->parseBuffer(buf, size); + AStatsEvent_release(statsEvent); + return logEvent; +} + std::unique_ptr<LogEvent> CreateScreenBrightnessChangedEvent(uint64_t timestampNs, int level) { AStatsEvent* statsEvent = AStatsEvent_obtain(); AStatsEvent_setAtomId(statsEvent, util::SCREEN_BRIGHTNESS_CHANGED); @@ -964,6 +1078,22 @@ int64_t StringToId(const string& str) { return static_cast<int64_t>(std::hash<std::string>()(str)); } +void ValidateWakelockAttributionUidAndTagDimension(const DimensionsValue& value, const int atomId, + const int uid, const string& tag) { + EXPECT_EQ(value.field(), atomId); + EXPECT_EQ(value.value_tuple().dimensions_value_size(), 2); + // Attribution field. + EXPECT_EQ(value.value_tuple().dimensions_value(0).field(), 1); + // Uid field. + EXPECT_EQ(value.value_tuple().dimensions_value(0).value_tuple().dimensions_value_size(), 1); + EXPECT_EQ(value.value_tuple().dimensions_value(0).value_tuple().dimensions_value(0).field(), 1); + EXPECT_EQ(value.value_tuple().dimensions_value(0).value_tuple().dimensions_value(0).value_int(), + uid); + // Tag field. + EXPECT_EQ(value.value_tuple().dimensions_value(1).field(), 3); + EXPECT_EQ(value.value_tuple().dimensions_value(1).value_str(), tag); +} + void ValidateAttributionUidDimension(const DimensionsValue& value, int atomId, int uid) { EXPECT_EQ(value.field(), atomId); EXPECT_EQ(value.value_tuple().dimensions_value_size(), 1); diff --git a/cmds/statsd/tests/statsd_test_util.h b/cmds/statsd/tests/statsd_test_util.h index 7c017554d511..715ba2b73169 100644 --- a/cmds/statsd/tests/statsd_test_util.h +++ b/cmds/statsd/tests/statsd_test_util.h @@ -68,6 +68,12 @@ AtomMatcher CreateBatterySaverModeStartAtomMatcher(); // Create AtomMatcher proto for stopping battery save mode. AtomMatcher CreateBatterySaverModeStopAtomMatcher(); +// Create AtomMatcher proto for battery state none mode. +AtomMatcher CreateBatteryStateNoneMatcher(); + +// Create AtomMatcher proto for battery state usb mode. +AtomMatcher CreateBatteryStateUsbMatcher(); + // Create AtomMatcher proto for process state changed. AtomMatcher CreateUidProcessStateChangedAtomMatcher(); @@ -110,6 +116,9 @@ Predicate CreateScheduledJobPredicate(); // Create Predicate proto for battery saver mode. Predicate CreateBatterySaverModePredicate(); +// Create Predicate proto for device unplogged mode. +Predicate CreateDeviceUnpluggedPredicate(); + // Create Predicate proto for holding wakelock. Predicate CreateHoldingWakelockPredicate(); @@ -164,6 +173,22 @@ FieldMatcher CreateAttributionUidAndTagDimensions(const int atomId, FieldMatcher CreateAttributionUidDimensions(const int atomId, const std::vector<Position>& positions); +FieldMatcher CreateAttributionUidAndOtherDimensions(const int atomId, + const std::vector<Position>& positions, + const std::vector<int>& fields); + +// START: get primary key functions +// These functions take in atom field information and create FieldValues which are stored in the +// given HashableDimensionKey. +void getUidProcessKey(int uid, HashableDimensionKey* key); + +void getOverlayKey(int uid, string packageName, HashableDimensionKey* key); + +void getPartialWakelockKey(int uid, const std::string& tag, HashableDimensionKey* key); + +void getPartialWakelockKey(int uid, HashableDimensionKey* key); +// END: get primary key functions + shared_ptr<LogEvent> CreateTwoValueLogEvent(int atomId, int64_t eventTimeNs, int32_t value1, int32_t value2); @@ -213,6 +238,9 @@ std::unique_ptr<LogEvent> CreateBatterySaverOnEvent(uint64_t timestampNs); // Create log event when battery saver stops. std::unique_ptr<LogEvent> CreateBatterySaverOffEvent(uint64_t timestampNs); +// Create log event when battery state changes. +std::unique_ptr<LogEvent> CreateBatteryStateChangedEvent(const uint64_t timestampNs, const BatteryPluggedStateEnum state); + // Create log event for app moving to background. std::unique_ptr<LogEvent> CreateMoveToBackgroundEvent(uint64_t timestampNs, const int uid); @@ -277,6 +305,8 @@ void sortLogEventsByTimestamp(std::vector<std::unique_ptr<LogEvent>> *events); int64_t StringToId(const string& str); +void ValidateWakelockAttributionUidAndTagDimension(const DimensionsValue& value, const int atomId, + const int uid, const string& tag); void ValidateUidDimension(const DimensionsValue& value, int node_idx, int atomId, int uid); void ValidateAttributionUidDimension(const DimensionsValue& value, int atomId, int uid); void ValidateAttributionUidAndTagDimension( |